Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .claude/commands/cmmsg.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
Compare all staged and unstaged changes against the main branch, then write a conventional commit message based on what changed.

Steps:
1. Run `git diff HEAD` to see unstaged changes and `git diff --cached` for staged changes. Also run `git status` to see untracked files.
2. Analyze the diff: identify what files changed, what was added/removed/modified, and why (infer from context).
3. Draft a commit message following Conventional Commits format:
- `feat:` new feature
- `fix:` bug fix
- `refactor:` refactor without behavior change
- `style:` formatting/styling only
- `chore:` tooling, config, deps
- `docs:` documentation
- Subject line: imperative mood, ≤72 chars, no period
- Body (optional): explain *why*, not *what*
4. Output the commit message in a code block so the user can copy it easily.
5. Ask the user if they want to proceed with this commit message, and if so, stage all relevant files and commit using that message.

If the user provides arguments (e.g. `/cmmsg fix login bug`), use that as a hint or override for the commit subject.
24 changes: 24 additions & 0 deletions .claude/settings.example.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit|MultiEdit",
"hooks": [
{
"type": "command",
"command": "node $PWD/hooks/tsc.js"
}
]
},
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "jq . > post-log.json"
}
]
}
]
}
}
15 changes: 15 additions & 0 deletions .claude/skills/create-pr/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
name: create-pr
description: Pushes current branch and creates a pull request. Use when the user asks to open or create a PR.
allowed-tools: Bash
---

1. Run `git status` and `git log origin/main..HEAD --oneline` to understand what will be included.
2. If there are uncommitted changes, invoke the cmmsg skill to help the user commit first, then continue.
3. Push the current branch: `git push -u origin HEAD`.
4. Draft a PR title (≤70 chars) and body based on the commits since main:
- ## Summary: 1–3 bullet points of what changed and why
- ## Test plan: checklist of how to verify
5. Show the draft to the user and ask for confirmation before creating.
6. Create the PR with `gh pr create` using the confirmed title and body.
7. Return the PR URL.
17 changes: 17 additions & 0 deletions .claude/skills/pre-push-review/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
name: pre-push-review
description: Reviews unpushed commits before pushing to remote. Use when the user asks to push code or wants a code review before pushing.
allowed-tools: Bash
argument-hint: "[branch]"
user-invocable: true
---

1. Run `git log origin/main..HEAD --oneline` to list commits to be pushed.
2. Run `git diff origin/main..HEAD -- . ':(exclude)package-lock.json' ':(exclude)*.lock'` to get the full diff.
3. If no diff, reply "沒有需要推送的變更。" and stop.
4. Review the diff and report only real issues:
- 🔴 BLOCKING:bugs、security vulnerabilities、obvious logic errors — must fix before pushing
- 🟡 WARNING:code smells、potential issues — not required but worth noting
- ✅ LGTM — if no issues found
5. Do NOT comment on style preferences, whitespace, formatting, or pure refactoring.
6. Ask "要繼續 push 嗎?" and only run `git push` if the user confirms.
5 changes: 4 additions & 1 deletion .githooks/pre-push
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
#!/bin/sh
cat - | node scripts/pre-push-review.mjs
set -e
echo "🔍 Checking lint..."
npm run lint
echo "✅ Pre-push checks passed."
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,5 @@ README-template.md

tailwind-full.config.js

.claude
post-log.json
.playwright-mcp
4 changes: 4 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ npm run lint # ESLint — zero warnings allowed (--max-warnings 0)

There is no test suite configured.

## Code Style

- Always use TypeScript strict mode. Never use `any`; prefer explicit types or `unknown`.

## Architecture

This is a Frontend Mentor quiz app built with React, Redux Toolkit, React Router v6, and Tailwind CSS.
Expand Down
88 changes: 88 additions & 0 deletions hooks/tsc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import * as ts from "typescript";
import * as path from "path";

// Read stdin
async function readInput() {
const chunks = [];
for await (const chunk of process.stdin) {
chunks.push(chunk);
}
return JSON.parse(Buffer.concat(chunks).toString());
}

function runTypeCheck(configPath) {
// Parse the tsconfig.json file
const configFile = ts.readConfigFile(configPath, ts.sys.readFile);
if (configFile.error) {
console.error(
ts.formatDiagnostic(configFile.error, {
getCanonicalFileName: (x) => x,
getCurrentDirectory: ts.sys.getCurrentDirectory,
getNewLine: () => ts.sys.newLine,
})
);
return;
}

// Parse the configuration
const parseConfigHost = {
fileExists: ts.sys.fileExists,
readFile: ts.sys.readFile,
readDirectory: ts.sys.readDirectory,
getCurrentDirectory: ts.sys.getCurrentDirectory,
onUnRecoverableConfigFileDiagnostic: () => {},
};

const parsed = ts.parseJsonConfigFileContent(
configFile.config,
parseConfigHost,
path.dirname(configPath)
);

// Override to ensure no emit
const compilerOptions = {
...parsed.options,
noEmit: true,
};

// Create the program
const program = ts.createProgram(parsed.fileNames, compilerOptions);

// Get all diagnostics
const allDiagnostics = ts.getPreEmitDiagnostics(program);

// Format and display diagnostics
if (allDiagnostics.length > 0) {
const formatHost = {
getCanonicalFileName: (path) => path,
getCurrentDirectory: ts.sys.getCurrentDirectory,
getNewLine: () => ts.sys.newLine,
};

const formattedDiagnostics = ts.formatDiagnostics(
allDiagnostics,
formatHost
);
return formattedDiagnostics; // Type check failed
}

return null; // Type check passed
}

async function main() {
const input = await readInput();
const file = input.tool_response?.filePath || input.tool_input?.file_path;

// Only check TypeScript files
if (!file || !/\.(ts|tsx)$/.test(file)) {
process.exit(0);
}

const typeChecks = runTypeCheck("./tsconfig.json");
if (typeChecks) {
console.error(typeChecks);
process.exit(2);
}
}

main();
Loading
Loading