claude-toolkit 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/README.md +27 -32
- package/bin/cli.ts +198 -162
- package/bin/postinstall.mjs +69 -0
- package/core/agents/ct-code-reviewer.md +5 -4
- package/core/agents/ct-github-workflow.md +28 -15
- package/core/commands/ct/code-quality.md +22 -2
- package/core/commands/ct/onboard.md +8 -3
- package/core/commands/ct/pr-review.md +22 -88
- package/core/commands/ct/pr-summary.md +6 -3
- package/core/commands/ct/ticket.md +20 -3
- package/core/hooks/{skill-eval.js → skill-eval.cjs} +103 -41
- package/core/hooks/skill-rules.schema.json +4 -0
- package/core/skills/ct-systematic-debugging/SKILL.md +1 -1
- package/core/skills/ct-testing-patterns/SKILL.md +5 -7
- package/core/skills/ct-typescript-conventions/SKILL.md +15 -11
- package/core/skills/ct-verification-before-completion/SKILL.md +2 -1
- package/docs/README.md +4 -0
- package/docs/commands/code-quality.md +2 -8
- package/docs/commands/onboard.md +2 -11
- package/docs/commands/pr-review.md +2 -13
- package/docs/commands/pr-summary.md +2 -2
- package/docs/commands/proto-check.md +4 -4
- package/docs/commands/ticket.md +2 -2
- package/docs/skills/systematic-debugging.md +1 -1
- package/docs/skills/testing-patterns.md +78 -56
- package/docs/skills/typescript-conventions.md +23 -10
- package/docs/skills/verification-before-completion.md +1 -1
- package/package.json +3 -1
- package/src/generator.ts +84 -12
- package/src/utils.ts +8 -15
- package/{core/commands/ct → stacks/protobuf/commands}/proto-check.md +11 -2
- package/templates/skill-rules.base.json +1 -29
- package/core/hooks/skill-eval.sh +0 -35
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
3
|
-
description:
|
|
2
|
+
name: ct-code-reviewer
|
|
3
|
+
description: Senior code reviewer with stack-aware analysis
|
|
4
4
|
model: opus
|
|
5
|
+
tools: Read, Grep, Glob, Bash
|
|
5
6
|
---
|
|
6
7
|
|
|
7
8
|
# Code Reviewer Agent
|
|
@@ -75,7 +76,7 @@ You are a senior code reviewer. Your job is to review code changes thoroughly, p
|
|
|
75
76
|
|
|
76
77
|
## Output Format
|
|
77
78
|
|
|
78
|
-
|
|
79
|
+
````markdown
|
|
79
80
|
## Code Review
|
|
80
81
|
|
|
81
82
|
**Reviewed:** {files or PR description}
|
|
@@ -112,7 +113,7 @@ You are a senior code reviewer. Your job is to review code changes thoroughly, p
|
|
|
112
113
|
|
|
113
114
|
### Summary
|
|
114
115
|
{1-3 sentence overall assessment. Be direct.}
|
|
115
|
-
|
|
116
|
+
````
|
|
116
117
|
|
|
117
118
|
## Principles
|
|
118
119
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
3
|
-
description:
|
|
2
|
+
name: ct-github-workflow
|
|
3
|
+
description: Git workflow assistant for branching, commits, and PRs
|
|
4
4
|
model: sonnet
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -64,18 +64,27 @@ perf(search): add database index for full-text queries
|
|
|
64
64
|
## PR Creation Workflow
|
|
65
65
|
|
|
66
66
|
### 1. Pre-flight Checks
|
|
67
|
-
```bash
|
|
68
|
-
# Verify tests pass
|
|
69
|
-
{project test command}
|
|
70
67
|
|
|
71
|
-
|
|
72
|
-
|
|
68
|
+
First run the project's own verification commands (substitute this project's actual scripts — these are placeholders, not literal commands):
|
|
69
|
+
- **Tests** — run the project's test command
|
|
70
|
+
- **Types** — run the project's typecheck command
|
|
71
|
+
- **Lint** — run the project's lint command
|
|
73
72
|
|
|
74
|
-
|
|
75
|
-
{project lint command}
|
|
73
|
+
Then confirm the branch is current with, and conflict-free against, the default branch:
|
|
76
74
|
|
|
77
|
-
|
|
78
|
-
|
|
75
|
+
```bash
|
|
76
|
+
# Resolve the default branch (main, master, ...) instead of assuming main
|
|
77
|
+
BASE=$(git symbolic-ref --short refs/remotes/origin/HEAD 2>/dev/null | sed 's@^origin/@@')
|
|
78
|
+
BASE=${BASE:-main}
|
|
79
|
+
git fetch origin "$BASE"
|
|
80
|
+
|
|
81
|
+
# Up-to-date check: a non-zero exit means the base has commits not yet on this
|
|
82
|
+
# branch (i.e. the branch is behind) — this is NOT a conflict signal.
|
|
83
|
+
git merge-base --is-ancestor "origin/$BASE" HEAD
|
|
84
|
+
|
|
85
|
+
# Real merge-conflict probe (does not touch the working tree): a non-zero exit
|
|
86
|
+
# means merging the base into this branch would produce conflicts.
|
|
87
|
+
git merge-tree --write-tree "origin/$BASE" HEAD >/dev/null
|
|
79
88
|
```
|
|
80
89
|
|
|
81
90
|
### 2. PR Title
|
|
@@ -114,8 +123,12 @@ Keep under 70 characters. The title should tell a reviewer what this PR does wit
|
|
|
114
123
|
## Workflow Commands
|
|
115
124
|
|
|
116
125
|
```bash
|
|
117
|
-
#
|
|
118
|
-
git
|
|
126
|
+
# Resolve the default branch once (main, master, ...)
|
|
127
|
+
BASE=$(git symbolic-ref --short refs/remotes/origin/HEAD 2>/dev/null | sed 's@^origin/@@')
|
|
128
|
+
BASE=${BASE:-main}
|
|
129
|
+
|
|
130
|
+
# Create feature branch from the latest default branch
|
|
131
|
+
git checkout "$BASE" && git pull && git checkout -b feat/description
|
|
119
132
|
|
|
120
133
|
# Stage and commit with conventional message
|
|
121
134
|
git add {files}
|
|
@@ -125,8 +138,8 @@ git commit -m "feat(scope): description"
|
|
|
125
138
|
git push -u origin HEAD
|
|
126
139
|
gh pr create --title "feat(scope): description" --body "..."
|
|
127
140
|
|
|
128
|
-
# Update branch with latest
|
|
129
|
-
git fetch origin
|
|
141
|
+
# Update branch with the latest default branch
|
|
142
|
+
git fetch origin "$BASE" && git rebase "origin/$BASE"
|
|
130
143
|
```
|
|
131
144
|
|
|
132
145
|
## Principles
|
|
@@ -1,7 +1,25 @@
|
|
|
1
1
|
---
|
|
2
|
-
description:
|
|
2
|
+
description: Run code quality checks on a directory or file
|
|
3
|
+
argument-hint: "[path]"
|
|
3
4
|
allowed-tools:
|
|
4
|
-
- Bash
|
|
5
|
+
- Bash(eslint:*)
|
|
6
|
+
- Bash(tsc:*)
|
|
7
|
+
- Bash(prettier:*)
|
|
8
|
+
- Bash(biome:*)
|
|
9
|
+
- Bash(ruff:*)
|
|
10
|
+
- Bash(mypy:*)
|
|
11
|
+
- Bash(pyright:*)
|
|
12
|
+
- Bash(black:*)
|
|
13
|
+
- Bash(cargo clippy:*)
|
|
14
|
+
- Bash(cargo check:*)
|
|
15
|
+
- Bash(rustfmt:*)
|
|
16
|
+
- Bash(gofmt:*)
|
|
17
|
+
- Bash(go vet:*)
|
|
18
|
+
- Bash(golangci-lint:*)
|
|
19
|
+
- Bash(npm run:*)
|
|
20
|
+
- Bash(pnpm:*)
|
|
21
|
+
- Bash(bun run:*)
|
|
22
|
+
- Bash(yarn:*)
|
|
5
23
|
- Read
|
|
6
24
|
- Glob
|
|
7
25
|
- Grep
|
|
@@ -11,6 +29,8 @@ allowed-tools:
|
|
|
11
29
|
|
|
12
30
|
Run lint, typecheck, and format checks against the target directory or file. Report all issues found and suggest fixes.
|
|
13
31
|
|
|
32
|
+
**Target:** `$ARGUMENTS` (defaults to the current directory if empty).
|
|
33
|
+
|
|
14
34
|
## Workflow
|
|
15
35
|
|
|
16
36
|
1. **Detect project tooling** by reading `package.json`, `pyproject.toml`, `Makefile`, `Cargo.toml`, or equivalent config files in the project root. Identify which linter, typechecker, and formatter are configured.
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
---
|
|
2
|
-
description:
|
|
2
|
+
description: Onboard yourself to a codebase area or task
|
|
3
|
+
argument-hint: "[area]"
|
|
3
4
|
allowed-tools:
|
|
4
|
-
- Bash
|
|
5
|
+
- Bash(git log:*)
|
|
6
|
+
- Bash(git ls-files:*)
|
|
7
|
+
- Bash(git remote:*)
|
|
8
|
+
- Bash(ls:*)
|
|
9
|
+
- Bash(find:*)
|
|
5
10
|
- Read
|
|
6
11
|
- Glob
|
|
7
12
|
- Grep
|
|
@@ -13,7 +18,7 @@ Systematically explore and understand a codebase area, module, or task context.
|
|
|
13
18
|
|
|
14
19
|
## Workflow
|
|
15
20
|
|
|
16
|
-
1. **Understand the scope.** The
|
|
21
|
+
1. **Understand the scope.** The scope is given by `$ARGUMENTS` (e.g., "the auth module", "the payments flow", "this repo"). If `$ARGUMENTS` is empty, start from the project root.
|
|
17
22
|
|
|
18
23
|
2. **Read foundational files first:**
|
|
19
24
|
- `README.md`, `CLAUDE.md`, `CONTRIBUTING.md` (project conventions)
|
|
@@ -1,104 +1,38 @@
|
|
|
1
1
|
---
|
|
2
|
-
description:
|
|
2
|
+
description: Review a pull request using project standards
|
|
3
|
+
argument-hint: "[pr-number]"
|
|
3
4
|
allowed-tools:
|
|
4
|
-
- Bash
|
|
5
|
+
- Bash(gh pr view:*)
|
|
6
|
+
- Bash(gh pr diff:*)
|
|
7
|
+
- Bash(gh pr checkout:*)
|
|
8
|
+
- Bash(git diff:*)
|
|
9
|
+
- Bash(git log:*)
|
|
10
|
+
- Bash(git merge-base:*)
|
|
11
|
+
- Bash(git branch:*)
|
|
5
12
|
- Read
|
|
6
|
-
-
|
|
7
|
-
- Grep
|
|
13
|
+
- Task
|
|
8
14
|
---
|
|
9
15
|
|
|
10
16
|
# Pull Request Review
|
|
11
17
|
|
|
12
|
-
|
|
18
|
+
Review the current branch's PR, or a specific PR number passed as `$ARGUMENTS`, by delegating the analysis to the `ct-code-reviewer` agent — the single source of truth for the review checklist, severity model, and output format.
|
|
13
19
|
|
|
14
20
|
## Workflow
|
|
15
21
|
|
|
16
|
-
1. **Get PR context.**
|
|
22
|
+
1. **Get PR context.**
|
|
23
|
+
- If `$ARGUMENTS` contains a PR number, fetch the PR's **actual** changes (metadata alone has no patch):
|
|
24
|
+
- `gh pr view $ARGUMENTS --json title,body,files,commits` for the title, description, and file list.
|
|
25
|
+
- `gh pr diff $ARGUMENTS` to read the actual patch.
|
|
26
|
+
- If you need to read changed files at the PR's revision rather than the local checkout, run `gh pr checkout $ARGUMENTS` first so the working tree matches the PR branch.
|
|
27
|
+
- Otherwise (no PR number), diff the current branch against its base branch (`main` or `master`): resolve the base with `git merge-base`, then use `git diff {base}..HEAD` and `git log {base}..HEAD --oneline --no-merges`.
|
|
17
28
|
|
|
18
|
-
2. **
|
|
29
|
+
2. **Delegate the review to the `ct-code-reviewer` agent.** Spawn the `ct-code-reviewer` agent with the Task tool, handing it the PR title/description and the diff (and, when you ran `gh pr checkout`, permission to read the changed files in full). The agent applies its own review checklist, severity model, and output format — do not restate them here.
|
|
19
30
|
|
|
20
|
-
3. **
|
|
21
|
-
|
|
22
|
-
4. **Compile findings** into a structured review.
|
|
23
|
-
|
|
24
|
-
## Review Checklist
|
|
25
|
-
|
|
26
|
-
### Logic Correctness
|
|
27
|
-
- Are conditionals and branches correct? Any off-by-one errors?
|
|
28
|
-
- Are edge cases handled (empty inputs, null values, boundary conditions)?
|
|
29
|
-
- Does the control flow match the intended behavior?
|
|
30
|
-
- Are there any unreachable code paths?
|
|
31
|
-
|
|
32
|
-
### Type Safety
|
|
33
|
-
- Are types precise (no `any`, no unsafe casts, no overly broad unions)?
|
|
34
|
-
- Is type narrowing used correctly before accessing properties?
|
|
35
|
-
- Are generic constraints appropriate?
|
|
36
|
-
- Are return types explicit where they should be?
|
|
37
|
-
|
|
38
|
-
### Error Handling
|
|
39
|
-
- Are errors caught and handled meaningfully (not swallowed)?
|
|
40
|
-
- Do error messages provide enough context for debugging?
|
|
41
|
-
- Are error boundaries or fallbacks in place where needed?
|
|
42
|
-
- Is cleanup logic (finally blocks, defer, etc.) correct?
|
|
43
|
-
|
|
44
|
-
### Test Coverage
|
|
45
|
-
- Are new behaviors covered by tests?
|
|
46
|
-
- Do tests focus on behavior, not implementation details?
|
|
47
|
-
- Are edge cases and error paths tested?
|
|
48
|
-
- Are test descriptions clear and descriptive?
|
|
49
|
-
|
|
50
|
-
### Performance
|
|
51
|
-
- Any unnecessary re-renders, recomputations, or allocations?
|
|
52
|
-
- Any N+1 query patterns or missing pagination?
|
|
53
|
-
- Any missing memoization for expensive operations?
|
|
54
|
-
- Any potential memory leaks (unclosed resources, dangling listeners)?
|
|
55
|
-
|
|
56
|
-
### Security
|
|
57
|
-
- Any injection vulnerabilities (SQL, XSS, command injection)?
|
|
58
|
-
- Are user inputs validated and sanitized?
|
|
59
|
-
- Are auth checks present on protected operations?
|
|
60
|
-
- Are secrets or sensitive data properly handled (not logged, not exposed)?
|
|
61
|
-
|
|
62
|
-
### Code Style
|
|
63
|
-
- Does naming follow project conventions?
|
|
64
|
-
- Is the code readable without excessive comments?
|
|
65
|
-
- Are abstractions at the right level (not over- or under-engineered)?
|
|
66
|
-
- Is there unnecessary duplication?
|
|
67
|
-
|
|
68
|
-
## Output Format
|
|
69
|
-
|
|
70
|
-
```markdown
|
|
71
|
-
## PR Review: {title}
|
|
72
|
-
|
|
73
|
-
**Branch:** {branch} -> {base}
|
|
74
|
-
**Files changed:** {count}
|
|
75
|
-
**Verdict:** {APPROVE | REQUEST_CHANGES | COMMENT}
|
|
76
|
-
|
|
77
|
-
### Critical Issues
|
|
78
|
-
> Must be fixed before merge.
|
|
79
|
-
|
|
80
|
-
- **[CRITICAL]** {file}:{line} - {description}
|
|
81
|
-
|
|
82
|
-
### Warnings
|
|
83
|
-
> Should be addressed, but not blocking.
|
|
84
|
-
|
|
85
|
-
- **[WARNING]** {file}:{line} - {description}
|
|
86
|
-
|
|
87
|
-
### Suggestions
|
|
88
|
-
> Nice-to-have improvements.
|
|
89
|
-
|
|
90
|
-
- **[SUGGESTION]** {file}:{line} - {description}
|
|
91
|
-
|
|
92
|
-
### Positive Notes
|
|
93
|
-
- {things done well}
|
|
94
|
-
|
|
95
|
-
### Summary
|
|
96
|
-
{1-3 sentence overall assessment}
|
|
97
|
-
```
|
|
31
|
+
3. **Return the agent's review to the user verbatim.** Add a one-line note only if you had to skip something (e.g. the PR was too large to check out, or `gh` was unavailable).
|
|
98
32
|
|
|
99
33
|
## Notes
|
|
100
34
|
|
|
101
|
-
-
|
|
102
|
-
-
|
|
103
|
-
- Acknowledge good patterns, not just problems.
|
|
35
|
+
- The `ct-code-reviewer` agent is the canonical review checklist and output format. This command only gathers context and delegates, so the checklist and severity model never drift between the two.
|
|
36
|
+
- Prefer `gh pr diff` / `gh pr checkout` over reading local files when a PR number is given — the local branch is usually not the PR branch.
|
|
104
37
|
- If the PR is too large to review effectively, say so and suggest splitting it.
|
|
38
|
+
- If `gh` is not installed or the PR number is invalid, fall back to reviewing the current branch's diff against its base.
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
---
|
|
2
|
-
description:
|
|
2
|
+
description: Generate a PR summary from the current branch
|
|
3
3
|
allowed-tools:
|
|
4
|
-
- Bash
|
|
4
|
+
- Bash(git diff:*)
|
|
5
|
+
- Bash(git log:*)
|
|
6
|
+
- Bash(git merge-base:*)
|
|
7
|
+
- Bash(git branch:*)
|
|
5
8
|
- Read
|
|
6
9
|
---
|
|
7
10
|
|
|
@@ -15,7 +18,7 @@ Generate a structured pull request summary by analyzing the diff and commit hist
|
|
|
15
18
|
|
|
16
19
|
2. **Gather the diff.** Run `git diff {base}...HEAD --stat` for an overview and `git diff {base}...HEAD` for full changes.
|
|
17
20
|
|
|
18
|
-
3. **Gather commit history.** Run `git log {base}
|
|
21
|
+
3. **Gather commit history.** Run `git log {base}..HEAD --oneline --no-merges` (two-dot: commits on HEAD but not on base) to understand the progression of changes.
|
|
19
22
|
|
|
20
23
|
4. **Analyze changes** by reading modified files to understand the "why" behind each change. Group related changes together.
|
|
21
24
|
|
|
@@ -1,7 +1,24 @@
|
|
|
1
1
|
---
|
|
2
|
-
description:
|
|
2
|
+
description: Work end-to-end on a ticket or issue
|
|
3
|
+
argument-hint: "[issue-number | description]"
|
|
3
4
|
allowed-tools:
|
|
4
|
-
- Bash
|
|
5
|
+
- Bash(git status:*)
|
|
6
|
+
- Bash(git checkout:*)
|
|
7
|
+
- Bash(git switch:*)
|
|
8
|
+
- Bash(git branch:*)
|
|
9
|
+
- Bash(git add:*)
|
|
10
|
+
- Bash(git commit:*)
|
|
11
|
+
- Bash(git push:*)
|
|
12
|
+
- Bash(git diff:*)
|
|
13
|
+
- Bash(git log:*)
|
|
14
|
+
- Bash(gh issue view:*)
|
|
15
|
+
- Bash(gh pr create:*)
|
|
16
|
+
- Bash(gh pr view:*)
|
|
17
|
+
- Bash(npm run:*)
|
|
18
|
+
- Bash(pnpm:*)
|
|
19
|
+
- Bash(bun run:*)
|
|
20
|
+
- Bash(bun test:*)
|
|
21
|
+
- Bash(yarn:*)
|
|
5
22
|
- Read
|
|
6
23
|
- Write
|
|
7
24
|
- Edit
|
|
@@ -17,7 +34,7 @@ Work on a ticket or issue end-to-end: understand it, explore the codebase, plan
|
|
|
17
34
|
|
|
18
35
|
### Phase 1: Understand
|
|
19
36
|
|
|
20
|
-
1. **Read the ticket.** If a GitHub issue number
|
|
37
|
+
1. **Read the ticket.** If `$ARGUMENTS` is a GitHub issue number, fetch it with `gh issue view $ARGUMENTS`. Otherwise, treat `$ARGUMENTS` as the ticket description.
|
|
21
38
|
|
|
22
39
|
2. **Clarify requirements.** Identify acceptance criteria, edge cases, and constraints. If anything is ambiguous, ask before proceeding.
|
|
23
40
|
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
1
|
/**
|
|
3
2
|
* Skill Evaluation Engine v2.0
|
|
4
3
|
*
|
|
@@ -18,6 +17,10 @@ const path = require("node:path");
|
|
|
18
17
|
// Configuration
|
|
19
18
|
const RULES_PATH = path.join(__dirname, "skill-rules.json");
|
|
20
19
|
|
|
20
|
+
// Cap the text handed to the (potentially quadratic) regex scans. Long single-line
|
|
21
|
+
// pastes — minified bundles, one-line JSON — can otherwise blow past the 5s hook timeout.
|
|
22
|
+
const MAX_PROMPT_LENGTH = 50_000;
|
|
23
|
+
|
|
21
24
|
/**
|
|
22
25
|
* @typedef {Object} SkillMatch
|
|
23
26
|
* @property {string} name - Skill name
|
|
@@ -36,7 +39,7 @@ function loadRules() {
|
|
|
36
39
|
return JSON.parse(content);
|
|
37
40
|
} catch (error) {
|
|
38
41
|
console.error(`Failed to load skill rules: ${error.message}`);
|
|
39
|
-
|
|
42
|
+
return null;
|
|
40
43
|
}
|
|
41
44
|
}
|
|
42
45
|
|
|
@@ -46,29 +49,33 @@ function loadRules() {
|
|
|
46
49
|
* @returns {string[]} Array of detected file paths
|
|
47
50
|
*/
|
|
48
51
|
function extractFilePaths(prompt) {
|
|
52
|
+
// Normalize backslashes so Windows paths match, and cap length for the regex scans.
|
|
53
|
+
const text = String(prompt).slice(0, MAX_PROMPT_LENGTH).replace(/\\/g, "/");
|
|
49
54
|
const paths = new Set();
|
|
50
55
|
|
|
51
|
-
// Match explicit paths with extensions
|
|
56
|
+
// Match explicit paths with extensions. Negative lookbehind lets a candidate start
|
|
57
|
+
// after any non-path delimiter — "(", ",", ":", "=", whitespace, quotes, or BOL.
|
|
52
58
|
const extensionPattern =
|
|
53
|
-
/(
|
|
54
|
-
for (const match of
|
|
59
|
+
/(?<![\w\-./])([\w\-./]+\.(?:[tj]sx?|json|gql|ya?ml|md|sh|rs|proto|toml|wasm))\b/gi;
|
|
60
|
+
for (const match of text.matchAll(extensionPattern)) {
|
|
55
61
|
paths.add(match[1]);
|
|
56
62
|
}
|
|
57
63
|
|
|
58
|
-
// Match paths starting with common directories
|
|
64
|
+
// Match paths starting with common directories.
|
|
59
65
|
const dirPattern =
|
|
60
|
-
/(
|
|
61
|
-
for (const match of
|
|
66
|
+
/(?<![\w\-./])((?:src|app|components|worker|workers|proto|hooks|utils|services|styles|locales|tests|\.claude|\.github)\/[\w\-./]+)/gi;
|
|
67
|
+
for (const match of text.matchAll(dirPattern)) {
|
|
62
68
|
paths.add(match[1]);
|
|
63
69
|
}
|
|
64
70
|
|
|
65
|
-
// Match quoted paths
|
|
71
|
+
// Match quoted paths.
|
|
66
72
|
const quotedPattern = /["'`]([\w\-./]+\/[\w\-./]+)["'`]/g;
|
|
67
|
-
for (const match of
|
|
73
|
+
for (const match of text.matchAll(quotedPattern)) {
|
|
68
74
|
paths.add(match[1]);
|
|
69
75
|
}
|
|
70
76
|
|
|
71
|
-
|
|
77
|
+
// Require at least one letter so ratios/fractions like "3/4" are not treated as paths.
|
|
78
|
+
return Array.from(paths).filter((p) => /[A-Za-z]/.test(p));
|
|
72
79
|
}
|
|
73
80
|
|
|
74
81
|
/**
|
|
@@ -94,18 +101,19 @@ function matchesPattern(text, pattern, flags = "i") {
|
|
|
94
101
|
* @returns {boolean}
|
|
95
102
|
*/
|
|
96
103
|
function matchesGlob(filePath, globPattern) {
|
|
104
|
+
const normalized = String(filePath).replace(/\\/g, "/");
|
|
97
105
|
const regexPattern = globPattern
|
|
98
|
-
.replace(
|
|
99
|
-
.replace(/\*\*\//g, "<<<
|
|
100
|
-
.replace(/\*\*/g, "<<<
|
|
106
|
+
.replace(/[.+^${}()|[\]]/g, "\\$&")
|
|
107
|
+
.replace(/\*\*\//g, "<<<GLOBSTARSLASH>>>")
|
|
108
|
+
.replace(/\*\*/g, "<<<GLOBSTAR>>>")
|
|
101
109
|
.replace(/\*/g, "[^/]*")
|
|
102
|
-
.replace(
|
|
103
|
-
.replace(/<<<
|
|
104
|
-
.replace(
|
|
110
|
+
.replace(/\?/g, ".")
|
|
111
|
+
.replace(/<<<GLOBSTARSLASH>>>/g, "(?:.*/)?")
|
|
112
|
+
.replace(/<<<GLOBSTAR>>>/g, ".*");
|
|
105
113
|
|
|
106
114
|
try {
|
|
107
115
|
const regex = new RegExp(`^${regexPattern}$`, "i");
|
|
108
|
-
return regex.test(
|
|
116
|
+
return regex.test(normalized);
|
|
109
117
|
} catch {
|
|
110
118
|
return false;
|
|
111
119
|
}
|
|
@@ -257,20 +265,48 @@ function formatConfidence(score, minScore) {
|
|
|
257
265
|
return "LOW";
|
|
258
266
|
}
|
|
259
267
|
|
|
268
|
+
// Strip characters that could break out of the <user-prompt-submit-hook> block or
|
|
269
|
+
// forge system-reminder content when a rule-derived string is echoed into output (F04).
|
|
270
|
+
function sanitize(value) {
|
|
271
|
+
return String(value)
|
|
272
|
+
.replace(/[<>\r\n]/g, " ")
|
|
273
|
+
.trim();
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Confirm the skill actually ships before ordering Claude to invoke it (F03).
|
|
277
|
+
// baseDir is .claude/skills at runtime (path.join(__dirname, "..", "skills")).
|
|
278
|
+
function skillExists(name, skillsDir) {
|
|
279
|
+
try {
|
|
280
|
+
return fs.existsSync(path.join(skillsDir, name, "SKILL.md"));
|
|
281
|
+
} catch {
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
260
286
|
/**
|
|
261
287
|
* Main evaluation function
|
|
262
288
|
*/
|
|
263
|
-
function evaluate(prompt) {
|
|
264
|
-
const rules = loadRules();
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
const
|
|
289
|
+
function evaluate(prompt, opts = {}) {
|
|
290
|
+
const rules = opts.rules !== undefined ? opts.rules : loadRules();
|
|
291
|
+
if (!rules?.skills) return "";
|
|
292
|
+
const skillsDir = opts.skillsDir ?? path.join(__dirname, "..", "skills");
|
|
293
|
+
|
|
294
|
+
const {
|
|
295
|
+
minConfidenceScore = 3,
|
|
296
|
+
maxSkillsToShow = 5,
|
|
297
|
+
showMatchReasons = true,
|
|
298
|
+
} = rules.config ?? {};
|
|
299
|
+
|
|
300
|
+
// Cap the text before the pattern scans (F09), and coerce non-strings (F21).
|
|
301
|
+
const cappedPrompt = String(prompt).slice(0, MAX_PROMPT_LENGTH);
|
|
302
|
+
const promptLower = cappedPrompt.toLowerCase();
|
|
303
|
+
const filePaths = extractFilePaths(cappedPrompt);
|
|
304
|
+
const skills = rules.skills;
|
|
269
305
|
|
|
270
306
|
const matches = [];
|
|
271
307
|
for (const [name, skill] of Object.entries(skills)) {
|
|
272
|
-
const match = evaluateSkill(name, skill,
|
|
273
|
-
if (match && match.score >=
|
|
308
|
+
const match = evaluateSkill(name, skill, cappedPrompt, promptLower, filePaths, rules);
|
|
309
|
+
if (match && match.score >= minConfidenceScore && skillExists(match.name, skillsDir)) {
|
|
274
310
|
matches.push(match);
|
|
275
311
|
}
|
|
276
312
|
}
|
|
@@ -284,29 +320,31 @@ function evaluate(prompt) {
|
|
|
284
320
|
return b.priority - a.priority;
|
|
285
321
|
});
|
|
286
322
|
|
|
287
|
-
const topMatches = matches.slice(0,
|
|
288
|
-
const relatedSkills = getRelatedSkills(topMatches, skills)
|
|
323
|
+
const topMatches = matches.slice(0, maxSkillsToShow);
|
|
324
|
+
const relatedSkills = getRelatedSkills(topMatches, skills).filter((n) =>
|
|
325
|
+
skillExists(n, skillsDir),
|
|
326
|
+
);
|
|
289
327
|
|
|
290
328
|
let output = "<user-prompt-submit-hook>\n";
|
|
291
329
|
output += "SKILL ACTIVATION REQUIRED\n\n";
|
|
292
330
|
|
|
293
331
|
if (filePaths.length > 0) {
|
|
294
|
-
output += `Detected file paths: ${filePaths.join(", ")}\n\n`;
|
|
332
|
+
output += `Detected file paths: ${filePaths.map(sanitize).join(", ")}\n\n`;
|
|
295
333
|
}
|
|
296
334
|
|
|
297
335
|
output += "Matched skills (ranked by relevance):\n";
|
|
298
336
|
|
|
299
337
|
for (let i = 0; i < topMatches.length; i++) {
|
|
300
338
|
const match = topMatches[i];
|
|
301
|
-
const confidence = formatConfidence(match.score,
|
|
302
|
-
output += `${i + 1}. ${match.name} (${confidence} confidence)\n`;
|
|
303
|
-
if (
|
|
304
|
-
output += ` Matched: ${match.reasons.slice(0, 3).join(", ")}\n`;
|
|
339
|
+
const confidence = formatConfidence(match.score, minConfidenceScore);
|
|
340
|
+
output += `${i + 1}. ${sanitize(match.name)} (${confidence} confidence)\n`;
|
|
341
|
+
if (showMatchReasons && match.reasons.length > 0) {
|
|
342
|
+
output += ` Matched: ${match.reasons.slice(0, 3).map(sanitize).join(", ")}\n`;
|
|
305
343
|
}
|
|
306
344
|
}
|
|
307
345
|
|
|
308
346
|
if (relatedSkills.length > 0) {
|
|
309
|
-
output += `\nRelated skills to consider: ${relatedSkills.join(", ")}\n`;
|
|
347
|
+
output += `\nRelated skills to consider: ${relatedSkills.map(sanitize).join(", ")}\n`;
|
|
310
348
|
}
|
|
311
349
|
|
|
312
350
|
output += "\nBefore implementing, you MUST:\n";
|
|
@@ -314,9 +352,9 @@ function evaluate(prompt) {
|
|
|
314
352
|
output += "2. ACTIVATE: Invoke the Skill tool for each YES skill\n";
|
|
315
353
|
output += "3. IMPLEMENT: Only proceed after skill activation\n";
|
|
316
354
|
output += "\nExample evaluation:\n";
|
|
317
|
-
output += `- ${topMatches[0].name}: YES - [your reasoning]\n`;
|
|
355
|
+
output += `- ${sanitize(topMatches[0].name)}: YES - [your reasoning]\n`;
|
|
318
356
|
if (topMatches.length > 1) {
|
|
319
|
-
output += `- ${topMatches[1].name}: NO - [your reasoning]\n`;
|
|
357
|
+
output += `- ${sanitize(topMatches[1].name)}: NO - [your reasoning]\n`;
|
|
320
358
|
}
|
|
321
359
|
output += "\nDO NOT skip this step. Invoke relevant skills NOW.\n";
|
|
322
360
|
output += "</user-prompt-submit-hook>";
|
|
@@ -334,18 +372,25 @@ function main() {
|
|
|
334
372
|
input += chunk;
|
|
335
373
|
});
|
|
336
374
|
|
|
375
|
+
// F36: never leave stdin errors unhandled (would otherwise hang to the hook timeout).
|
|
376
|
+
process.stdin.on("error", () => {
|
|
377
|
+
process.exitCode = 0;
|
|
378
|
+
});
|
|
379
|
+
|
|
337
380
|
process.stdin.on("end", () => {
|
|
338
381
|
let prompt = "";
|
|
339
382
|
|
|
340
383
|
try {
|
|
341
384
|
const data = JSON.parse(input);
|
|
342
|
-
|
|
385
|
+
// F21: only accept a string prompt; anything else is treated as empty.
|
|
386
|
+
prompt = typeof data.prompt === "string" ? data.prompt : "";
|
|
343
387
|
} catch {
|
|
344
388
|
prompt = input;
|
|
345
389
|
}
|
|
346
390
|
|
|
347
|
-
if (!prompt.trim()) {
|
|
348
|
-
process.
|
|
391
|
+
if (typeof prompt !== "string" || !prompt.trim()) {
|
|
392
|
+
process.exitCode = 0;
|
|
393
|
+
return;
|
|
349
394
|
}
|
|
350
395
|
|
|
351
396
|
try {
|
|
@@ -357,8 +402,25 @@ function main() {
|
|
|
357
402
|
console.error(`Skill evaluation failed: ${error.message}`);
|
|
358
403
|
}
|
|
359
404
|
|
|
360
|
-
process
|
|
405
|
+
// F35: set exitCode and let the process drain stdout naturally rather than
|
|
406
|
+
// process.exit(0), which can truncate a large console.log on POSIX pipes.
|
|
407
|
+
process.exitCode = 0;
|
|
361
408
|
});
|
|
362
409
|
}
|
|
363
410
|
|
|
364
|
-
main
|
|
411
|
+
if (require.main === module) {
|
|
412
|
+
main();
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
module.exports = {
|
|
416
|
+
matchesGlob,
|
|
417
|
+
matchesPattern,
|
|
418
|
+
extractFilePaths,
|
|
419
|
+
matchDirectoryMapping,
|
|
420
|
+
evaluateSkill,
|
|
421
|
+
getRelatedSkills,
|
|
422
|
+
formatConfidence,
|
|
423
|
+
skillExists,
|
|
424
|
+
sanitize,
|
|
425
|
+
evaluate,
|
|
426
|
+
};
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"config": {
|
|
18
18
|
"type": "object",
|
|
19
19
|
"description": "Global configuration options",
|
|
20
|
+
"additionalProperties": false,
|
|
20
21
|
"required": ["minConfidenceScore", "showMatchReasons", "maxSkillsToShow"],
|
|
21
22
|
"properties": {
|
|
22
23
|
"minConfidenceScore": {
|
|
@@ -39,6 +40,7 @@
|
|
|
39
40
|
"scoring": {
|
|
40
41
|
"type": "object",
|
|
41
42
|
"description": "Points assigned to each match type",
|
|
43
|
+
"additionalProperties": false,
|
|
42
44
|
"required": [
|
|
43
45
|
"keyword",
|
|
44
46
|
"keywordPattern",
|
|
@@ -68,6 +70,7 @@
|
|
|
68
70
|
"description": "Skill definitions and their triggers",
|
|
69
71
|
"additionalProperties": {
|
|
70
72
|
"type": "object",
|
|
73
|
+
"additionalProperties": false,
|
|
71
74
|
"required": ["description", "triggers"],
|
|
72
75
|
"properties": {
|
|
73
76
|
"description": { "type": "string" },
|
|
@@ -79,6 +82,7 @@
|
|
|
79
82
|
},
|
|
80
83
|
"triggers": {
|
|
81
84
|
"type": "object",
|
|
85
|
+
"additionalProperties": false,
|
|
82
86
|
"properties": {
|
|
83
87
|
"keywords": { "type": "array", "items": { "type": "string" } },
|
|
84
88
|
"keywordPatterns": {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: ct-systematic-debugging
|
|
3
|
-
description: Four-phase methodology for diagnosing and fixing bugs efficiently without guesswork.
|
|
3
|
+
description: Four-phase methodology for diagnosing and fixing bugs efficiently without guesswork. Use when investigating a bug, test failure, or unexpected behavior before proposing a fix
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Systematic Debugging
|