worclaude 2.2.6 → 2.3.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 +97 -0
- package/README.md +114 -37
- package/package.json +7 -2
- package/src/commands/doctor.js +507 -104
- package/src/commands/init.js +103 -19
- package/src/core/detector.js +34 -10
- package/src/core/merger.js +48 -3
- package/src/core/scaffolder.js +58 -1
- package/src/data/agents.js +5 -0
- package/src/index.js +2 -1
- package/src/prompts/claude-md-merge.js +5 -0
- package/templates/agents/universal/build-validator.md +23 -0
- package/templates/agents/universal/code-simplifier.md +11 -0
- package/templates/agents/universal/plan-reviewer.md +39 -0
- package/templates/agents/universal/test-writer.md +25 -0
- package/templates/agents/universal/verify-app.md +12 -0
- package/templates/commands/build-fix.md +5 -0
- package/templates/commands/commit-push-pr.md +6 -0
- package/templates/commands/compact-safe.md +5 -0
- package/templates/commands/conflict-resolver.md +5 -0
- package/templates/commands/end.md +10 -0
- package/templates/commands/learn.md +33 -0
- package/templates/commands/refactor-clean.md +10 -0
- package/templates/commands/review-changes.md +5 -0
- package/templates/commands/review-plan.md +5 -0
- package/templates/commands/setup.md +5 -0
- package/templates/commands/start.md +10 -0
- package/templates/commands/status.md +5 -0
- package/templates/commands/sync.md +5 -0
- package/templates/commands/techdebt.md +5 -0
- package/templates/commands/test-coverage.md +5 -0
- package/templates/commands/update-claude-md.md +5 -0
- package/templates/commands/verify.md +11 -0
- package/templates/core/agents-md.md +25 -0
- package/templates/core/claude-md.md +17 -0
- package/templates/hooks/README.md +106 -0
- package/templates/hooks/correction-detect.cjs +48 -0
- package/templates/hooks/examples/prompt-hook-commit-validator.json +16 -0
- package/templates/hooks/learn-capture.cjs +179 -0
- package/templates/hooks/pre-compact-save.cjs +60 -0
- package/templates/hooks/skill-hint.cjs +66 -0
- package/templates/memory/decisions.md +11 -0
- package/templates/memory/preferences.md +17 -0
- package/templates/settings/base.json +56 -8
- package/templates/skills/templates/backend-conventions.md +1 -0
- package/templates/skills/templates/frontend-design-system.md +1 -0
- package/templates/skills/templates/project-patterns.md +1 -0
- package/templates/skills/universal/coding-principles.md +60 -0
- package/templates/skills/universal/context-management.md +21 -0
- package/templates/skills/universal/planning-with-files.md +11 -0
- package/templates/skills/universal/verification.md +18 -0
|
@@ -43,3 +43,8 @@ If anything fails, fix it.
|
|
|
43
43
|
Use exactly this message format — no trailers or Co-Authored-By lines.
|
|
44
44
|
|
|
45
45
|
Do NOT push. Do NOT create a PR. The user will run /sync next.
|
|
46
|
+
|
|
47
|
+
## Trigger Phrases
|
|
48
|
+
- "resolve conflicts"
|
|
49
|
+
- "fix merge conflicts"
|
|
50
|
+
- "merge conflict"
|
|
@@ -4,6 +4,10 @@ description: "Mid-task stop — writes handoff file and session summary for next
|
|
|
4
4
|
|
|
5
5
|
Use this ONLY when stopping work mid-task without committing.
|
|
6
6
|
|
|
7
|
+
When invoked with arguments, use them as the description of current work. Example: `/end implementing user registration`
|
|
8
|
+
|
|
9
|
+
Arguments: $ARGUMENTS
|
|
10
|
+
|
|
7
11
|
Do NOT update PROGRESS.md — /sync handles that on develop after merging.
|
|
8
12
|
|
|
9
13
|
## Pre-flight: Worktree Safety
|
|
@@ -35,3 +39,9 @@ If you are working in a git worktree (not the main checkout):
|
|
|
35
39
|
5. git commit -m "wip: handoff for [task description]"
|
|
36
40
|
Use exactly this message format — no trailers or Co-Authored-By lines.
|
|
37
41
|
6. git push
|
|
42
|
+
|
|
43
|
+
## Trigger Phrases
|
|
44
|
+
- "stop working"
|
|
45
|
+
- "end session"
|
|
46
|
+
- "save and stop"
|
|
47
|
+
- "I need to go"
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Capture a correction or convention as a persistent learning"
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
The user wants to capture a learning from this session.
|
|
6
|
+
|
|
7
|
+
When invoked with arguments, use them as the learning to capture.
|
|
8
|
+
Example: `/learn Always use conventional commits for this project`
|
|
9
|
+
|
|
10
|
+
If no arguments provided, ask the user what they want to remember.
|
|
11
|
+
|
|
12
|
+
Format it as a [LEARN] block:
|
|
13
|
+
|
|
14
|
+
[LEARN] Category: One-line rule description
|
|
15
|
+
Mistake: What went wrong (optional)
|
|
16
|
+
Correction: What should happen instead (optional)
|
|
17
|
+
|
|
18
|
+
Write this to `.claude/learnings/{category-slug}.md` with YAML frontmatter:
|
|
19
|
+
- created: today's date (YYYY-MM-DD)
|
|
20
|
+
- category: from the [LEARN] block
|
|
21
|
+
- project: current project name (from package.json name field, or directory name)
|
|
22
|
+
- times_applied: 0
|
|
23
|
+
|
|
24
|
+
Update `.claude/learnings/index.json` to include the new entry.
|
|
25
|
+
If index.json doesn't exist, create it with `{ "learnings": [] }`.
|
|
26
|
+
|
|
27
|
+
Confirm to the user what was saved and where.
|
|
28
|
+
|
|
29
|
+
## Trigger Phrases
|
|
30
|
+
- "remember this"
|
|
31
|
+
- "learn this"
|
|
32
|
+
- "save this rule"
|
|
33
|
+
- "capture this correction"
|
|
@@ -7,6 +7,10 @@ runs INLINE in your current session — it reads uncommitted changes,
|
|
|
7
7
|
improves them in place, and leaves everything uncommitted for
|
|
8
8
|
/commit-push-pr.
|
|
9
9
|
|
|
10
|
+
When invoked with arguments, use them to scope the cleanup. Example: `/refactor-clean src/core/merger.js`
|
|
11
|
+
|
|
12
|
+
Arguments: $ARGUMENTS
|
|
13
|
+
|
|
10
14
|
Do NOT spawn a subagent or worktree for this. Work directly on
|
|
11
15
|
the files in the current working directory.
|
|
12
16
|
|
|
@@ -54,3 +58,9 @@ the files in the current working directory.
|
|
|
54
58
|
- After implementing a feature, before /verify and /commit-push-pr
|
|
55
59
|
- Weekly maintenance pass
|
|
56
60
|
- When /review-changes flagged issues you want to fix
|
|
61
|
+
|
|
62
|
+
## Trigger Phrases
|
|
63
|
+
- "clean up the code"
|
|
64
|
+
- "refactor this"
|
|
65
|
+
- "simplify"
|
|
66
|
+
- "tidy up"
|
|
@@ -114,3 +114,8 @@ these files with real, project-specific content:
|
|
|
114
114
|
features from the interview, marking completed items.
|
|
115
115
|
|
|
116
116
|
Show the user what files were updated and offer to review each one.
|
|
117
|
+
|
|
118
|
+
## Trigger Phrases
|
|
119
|
+
- "set up the project"
|
|
120
|
+
- "configure this project"
|
|
121
|
+
- "project interview"
|
|
@@ -5,6 +5,10 @@ description: "Load session context, check for handoff files, detect drift since
|
|
|
5
5
|
The SessionStart hook has already loaded CLAUDE.md, PROGRESS.md,
|
|
6
6
|
and the most recent session summary into context.
|
|
7
7
|
|
|
8
|
+
When invoked with arguments, use them as the task focus. Example: `/start implement auth module`
|
|
9
|
+
|
|
10
|
+
Arguments: $ARGUMENTS
|
|
11
|
+
|
|
8
12
|
Your job is to supplement that with drift detection and additional context.
|
|
9
13
|
|
|
10
14
|
## 1. Drift Detection
|
|
@@ -73,3 +77,9 @@ Summarize:
|
|
|
73
77
|
- What was last completed (from session summary loaded by hook)
|
|
74
78
|
- What's next (from PROGRESS.md loaded by hook)
|
|
75
79
|
- Any blockers or notes
|
|
80
|
+
|
|
81
|
+
## Trigger Phrases
|
|
82
|
+
- "start a new session"
|
|
83
|
+
- "begin working"
|
|
84
|
+
- "load context"
|
|
85
|
+
- "what changed since last time"
|
|
@@ -52,3 +52,8 @@ and tell the user to run /conflict-resolver first.
|
|
|
52
52
|
Use exactly this message format — no trailers or Co-Authored-By lines.
|
|
53
53
|
10. git push origin develop
|
|
54
54
|
11. gh pr create --base main with description of what was merged
|
|
55
|
+
|
|
56
|
+
## Trigger Phrases
|
|
57
|
+
- "sync progress"
|
|
58
|
+
- "update shared files"
|
|
59
|
+
- "post-merge sync"
|
|
@@ -55,3 +55,8 @@ Delegates to the test-writer agent for test creation.
|
|
|
55
55
|
- After implementing a large feature
|
|
56
56
|
- When coverage drops below project threshold (check CLAUDE.md)
|
|
57
57
|
- During periodic maintenance
|
|
58
|
+
|
|
59
|
+
## Trigger Phrases
|
|
60
|
+
- "check test coverage"
|
|
61
|
+
- "what needs tests"
|
|
62
|
+
- "coverage gaps"
|
|
@@ -3,6 +3,11 @@ description: "Run full project verification — tests, build, lint, type checkin
|
|
|
3
3
|
---
|
|
4
4
|
|
|
5
5
|
Run full project verification:
|
|
6
|
+
|
|
7
|
+
When invoked with arguments, use them to scope what to verify. Example: `/verify just the auth module`
|
|
8
|
+
|
|
9
|
+
Arguments: $ARGUMENTS
|
|
10
|
+
|
|
6
11
|
1. Run the test suite
|
|
7
12
|
2. Run the build
|
|
8
13
|
3. Run the linter
|
|
@@ -10,3 +15,9 @@ Run full project verification:
|
|
|
10
15
|
5. Run any domain-specific verification
|
|
11
16
|
|
|
12
17
|
Report results clearly. Do not proceed if any check fails.
|
|
18
|
+
|
|
19
|
+
## Trigger Phrases
|
|
20
|
+
- "verify everything"
|
|
21
|
+
- "run all checks"
|
|
22
|
+
- "is this working"
|
|
23
|
+
- "test and lint"
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
{project_name} — {description}
|
|
4
|
+
|
|
5
|
+
## Tech Stack
|
|
6
|
+
{tech_stack_filled_during_init}
|
|
7
|
+
|
|
8
|
+
## Build & Test Commands
|
|
9
|
+
{commands_filled_during_init}
|
|
10
|
+
|
|
11
|
+
## Code Conventions
|
|
12
|
+
- Follow existing patterns in the codebase
|
|
13
|
+
- Test before committing
|
|
14
|
+
- Read source files before modifying them
|
|
15
|
+
- Ask if ambiguous, do not guess
|
|
16
|
+
|
|
17
|
+
## Project Structure
|
|
18
|
+
- `src/` — Source code
|
|
19
|
+
- `tests/` — Test files
|
|
20
|
+
- `docs/` — Documentation
|
|
21
|
+
|
|
22
|
+
## Key Principles
|
|
23
|
+
- Source of truth: docs/spec/SPEC.md
|
|
24
|
+
- One task at a time, verify each before moving on
|
|
25
|
+
- Never invent features not in the specification
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
See `.claude/skills/` — load only what's relevant:
|
|
17
17
|
- context-management/SKILL.md — Session lifecycle
|
|
18
18
|
- claude-md-maintenance/SKILL.md — CLAUDE.md self-healing
|
|
19
|
+
- coding-principles/SKILL.md — Behavioral principles: assumptions, simplicity, surgical changes, verification
|
|
19
20
|
- git-conventions/SKILL.md — Commits, branches, versioning
|
|
20
21
|
- planning-with-files/SKILL.md — Implementation planning
|
|
21
22
|
- prompt-engineering/SKILL.md — Prompting patterns and quality
|
|
@@ -45,6 +46,22 @@ See `.claude/skills/` — load only what's relevant:
|
|
|
45
46
|
7. Mediocre fix → scrap it, implement elegantly.
|
|
46
47
|
8. Feature branches NEVER modify shared-state files. Those are updated only on develop via /sync after merging PRs. See git-conventions.md Shared-State Files for the canonical list.
|
|
47
48
|
9. Never add Co-Authored-By trailers, AI attribution footers, or "Generated with" signatures to commits or PRs.
|
|
49
|
+
10. Surgical changes only — every changed line must trace to the request. Don't "improve" adjacent code, comments, or formatting.
|
|
50
|
+
11. Push back when simpler approaches exist. Present alternatives, don't pick silently.
|
|
51
|
+
12. Transform tasks to success criteria. "Fix the bug" → "Write a failing test, then make it pass."
|
|
52
|
+
|
|
53
|
+
## Memory Architecture
|
|
54
|
+
|
|
55
|
+
- This file: static project rules. Keep under 200 lines.
|
|
56
|
+
- Native memory (`/memory`): auto-captured project knowledge.
|
|
57
|
+
- Persistent corrections: `.claude/learnings/` via [LEARN] blocks or `/learn`.
|
|
58
|
+
- Path-scoped rules: `.claude/rules/` with YAML frontmatter.
|
|
59
|
+
- Session state: `.claude/sessions/` (gitignored).{memory_architecture_extras}
|
|
60
|
+
- Do NOT write session learnings or auto-captured patterns here.
|
|
61
|
+
|
|
62
|
+
## Learnings
|
|
63
|
+
|
|
64
|
+
Corrections captured via [LEARN] blocks live in `.claude/learnings/`. SessionStart loads recent ones automatically.
|
|
48
65
|
|
|
49
66
|
## Gotchas
|
|
50
67
|
[Grows during development]
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Worclaude Hooks
|
|
2
|
+
|
|
3
|
+
This directory holds Node.js hook scripts that `worclaude init` copies
|
|
4
|
+
into `.claude/hooks/` of the scaffolded project. Each script corresponds
|
|
5
|
+
to a `hooks[<event>]` entry in `templates/settings/base.json`.
|
|
6
|
+
|
|
7
|
+
`scaffoldHooks()` only copies `*.cjs` and `*.js` files — this `README.md`
|
|
8
|
+
and everything under `examples/` stays in the Worclaude repo and is NOT
|
|
9
|
+
shipped into user projects.
|
|
10
|
+
|
|
11
|
+
## Scaffolded hooks
|
|
12
|
+
|
|
13
|
+
| File | Event | Purpose |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| `pre-compact-save.cjs` | `PreCompact` | Writes a git context snapshot to `.claude/sessions/` before auto-compaction so nothing is lost if compaction truncates state. |
|
|
16
|
+
| `correction-detect.cjs` | `UserPromptSubmit` | Regex-matches user prompts against correction patterns and learn signals. On match, writes a hint to `.claude/.correction-hint`. |
|
|
17
|
+
| `learn-capture.cjs` | `Stop` | Scans the session transcript for `[LEARN] …` blocks and persists them to `.claude/learnings/` with an `index.json`. Uses a file-based re-entry flag to tolerate hypothetical re-entrant Stop events. |
|
|
18
|
+
| `skill-hint.cjs` | `UserPromptSubmit` | Token-overlap match between the user's prompt and installed skill directory names. Emits a `[Skill hint]` line when a match is found. |
|
|
19
|
+
|
|
20
|
+
All scripts:
|
|
21
|
+
- use the `.cjs` extension so `require()` works regardless of the host project's `package.json` `"type": "module"` setting
|
|
22
|
+
- read JSON from stdin, handle malformed input gracefully, and always exit 0
|
|
23
|
+
- suppress stderr noise from git or filesystem helpers
|
|
24
|
+
|
|
25
|
+
## Hook profiles
|
|
26
|
+
|
|
27
|
+
Every hook except `SessionStart`, `PostCompact`, and `PreCompact` is
|
|
28
|
+
gated by the `WORCLAUDE_HOOK_PROFILE` environment variable:
|
|
29
|
+
|
|
30
|
+
| Profile | Runs |
|
|
31
|
+
|---|---|
|
|
32
|
+
| `minimal` | Only context-loading hooks (`SessionStart`, `PostCompact`, `PreCompact`). All other hooks exit immediately. |
|
|
33
|
+
| `standard` (default) | All hooks including formatter on `PostToolUse`, learnings capture on `Stop`, notifications on `SessionEnd`/`Notification`. |
|
|
34
|
+
| `strict` | Standard + TypeScript type-checking after every `Write`/`Edit`. |
|
|
35
|
+
|
|
36
|
+
Users override the profile via shell env: `export WORCLAUDE_HOOK_PROFILE=minimal`.
|
|
37
|
+
|
|
38
|
+
## Handler types
|
|
39
|
+
|
|
40
|
+
Claude Code supports three hook handler types. Worclaude scaffolds
|
|
41
|
+
`type: "command"` hooks; the others are available for custom use.
|
|
42
|
+
|
|
43
|
+
### `command` (what we scaffold)
|
|
44
|
+
|
|
45
|
+
Runs a shell command. Stdin receives JSON context; stdout/stderr are
|
|
46
|
+
surfaced in the session.
|
|
47
|
+
|
|
48
|
+
```json
|
|
49
|
+
{
|
|
50
|
+
"type": "command",
|
|
51
|
+
"command": "node .claude/hooks/my-hook.cjs",
|
|
52
|
+
"async": true
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### `prompt` (cheap LLM-powered validation)
|
|
57
|
+
|
|
58
|
+
Runs a small prompt through a Claude model (defaults to Haiku).
|
|
59
|
+
`$ARGUMENTS` is replaced with the hook's input JSON. The model must
|
|
60
|
+
reply with `{"ok": true}` or `{"ok": false, "reason": "..."}`. An `ok:
|
|
61
|
+
false` response blocks the tool with the reason displayed.
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
{
|
|
65
|
+
"type": "prompt",
|
|
66
|
+
"prompt": "Check this: $ARGUMENTS. Reply {\"ok\": true} or {\"ok\": false, \"reason\": \"...\"}.",
|
|
67
|
+
"model": "haiku",
|
|
68
|
+
"timeout": 30
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`model` is a sibling of `type`; the `model` field accepts aliases
|
|
73
|
+
(`haiku`, `sonnet`, `opus`) or full model IDs. Default timeout is 30s.
|
|
74
|
+
|
|
75
|
+
### `agent` (full subagent invocation)
|
|
76
|
+
|
|
77
|
+
Runs a subagent. Heaviest option — use sparingly.
|
|
78
|
+
|
|
79
|
+
## Prompt-hook example
|
|
80
|
+
|
|
81
|
+
`examples/prompt-hook-commit-validator.json` is a complete, valid
|
|
82
|
+
`PreToolUse` prompt hook that validates git commit messages against
|
|
83
|
+
Conventional Commits. To enable it in a scaffolded project:
|
|
84
|
+
|
|
85
|
+
1. Open the project's `.claude/settings.json`.
|
|
86
|
+
2. Merge the `PreToolUse` entry from the example into the `hooks`
|
|
87
|
+
object (or append if `PreToolUse` already exists).
|
|
88
|
+
3. Test by running `git commit -m 'bad message'` inside a Claude Code
|
|
89
|
+
session — the hook should reject with a reason.
|
|
90
|
+
|
|
91
|
+
Prompt hooks consume Haiku tokens. Expect a few cents per 1000 commits.
|
|
92
|
+
|
|
93
|
+
## Adding custom hooks
|
|
94
|
+
|
|
95
|
+
- Drop a new `.cjs` or `.js` into `.claude/hooks/`.
|
|
96
|
+
- Reference it from `.claude/settings.json` under the appropriate event.
|
|
97
|
+
- Gate it on `WORCLAUDE_HOOK_PROFILE` if it is expensive or optional.
|
|
98
|
+
- `disableSkillShellExecution` in Claude Code settings does NOT affect
|
|
99
|
+
hook scripts — hooks are executed by the Claude Code hook runtime,
|
|
100
|
+
not by skill inline-shell evaluation. Hook scripts run even when
|
|
101
|
+
`disableSkillShellExecution` is enabled.
|
|
102
|
+
|
|
103
|
+
## Further reading
|
|
104
|
+
|
|
105
|
+
- Claude Code hooks reference: `/docs/reference/hooks.md` in this repo.
|
|
106
|
+
- Worclaude hook profiles: `CLAUDE.md` of this repo, "Hook Profiles" section.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
// UserPromptSubmit hook: detects correction and learn patterns in user prompts.
|
|
5
|
+
// Outputs a hint to stdout if a pattern matches; empty output otherwise.
|
|
6
|
+
// No file I/O, no network. Always exits 0.
|
|
7
|
+
|
|
8
|
+
const { readFileSync } = require('fs');
|
|
9
|
+
|
|
10
|
+
const correctionPatterns = [
|
|
11
|
+
/no,?\s*(that's|thats)?\s*(wrong|incorrect|not right)/i,
|
|
12
|
+
/you\s*(should|shouldn't|need to|forgot)/i,
|
|
13
|
+
/that's not what I (meant|asked|wanted)/i,
|
|
14
|
+
/wrong file/i,
|
|
15
|
+
/undo that/i,
|
|
16
|
+
/don't do that/i,
|
|
17
|
+
/actually,?\s/i,
|
|
18
|
+
/I said /i,
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
const learnPatterns = [
|
|
22
|
+
/remember (this|that)/i,
|
|
23
|
+
/add (this|that) to (your )?rules/i,
|
|
24
|
+
/don't (do|make) that (again|mistake)/i,
|
|
25
|
+
/learn from this/i,
|
|
26
|
+
/\[LEARN\]/i,
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
const data = JSON.parse(readFileSync(0, 'utf8'));
|
|
31
|
+
const prompt = data.input?.prompt || '';
|
|
32
|
+
|
|
33
|
+
if (prompt) {
|
|
34
|
+
if (learnPatterns.some((p) => p.test(prompt))) {
|
|
35
|
+
process.stdout.write(
|
|
36
|
+
'[Learn trigger detected] Capture this as a [LEARN] block with category, rule, and optional mistake/correction.\n'
|
|
37
|
+
);
|
|
38
|
+
} else if (correctionPatterns.some((p) => p.test(prompt))) {
|
|
39
|
+
process.stdout.write(
|
|
40
|
+
'[Correction detected] Consider proposing a [LEARN] block if this is a generalizable rule.\n'
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
} catch {
|
|
45
|
+
// Never block user input
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
process.exit(0);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"PreToolUse": [
|
|
3
|
+
{
|
|
4
|
+
"matcher": "Bash",
|
|
5
|
+
"hooks": [
|
|
6
|
+
{
|
|
7
|
+
"type": "prompt",
|
|
8
|
+
"prompt": "Check if this git commit message follows Conventional Commits (feat/fix/refactor/docs/test/chore/ci/build/perf/style/revert). Message: $ARGUMENTS. Reply with JSON only: {\"ok\": true} or {\"ok\": false, \"reason\": \"...\"}.",
|
|
9
|
+
"model": "haiku",
|
|
10
|
+
"timeout": 30,
|
|
11
|
+
"statusMessage": "Validating commit message format"
|
|
12
|
+
}
|
|
13
|
+
]
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
// Stop hook: scans the transcript for [LEARN] blocks and persists them
|
|
5
|
+
// to .claude/learnings/ as markdown files with YAML frontmatter.
|
|
6
|
+
// Always exits 0 — never blocks session stop.
|
|
7
|
+
|
|
8
|
+
const { readFileSync, writeFileSync, mkdirSync, existsSync, statSync, unlinkSync } = require('fs');
|
|
9
|
+
const { join, basename } = require('path');
|
|
10
|
+
|
|
11
|
+
const LEARN_REGEX =
|
|
12
|
+
/\[LEARN\]\s*([\w][\w\s-]*?)\s*:\s*(.+?)(?:\r?\nMistake:\s*(.+?))?(?:\r?\nCorrection:\s*(.+?))?(?=\r?\n\[LEARN\]|\r?\n\r?\n|$)/gim;
|
|
13
|
+
|
|
14
|
+
const STALE_THRESHOLD_MS = 30000; // 30 seconds
|
|
15
|
+
|
|
16
|
+
function slugify(text) {
|
|
17
|
+
return text
|
|
18
|
+
.toLowerCase()
|
|
19
|
+
.replace(/\s+/g, '-')
|
|
20
|
+
.replace(/[^a-z0-9-]/g, '');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function checkStopHookActive(cwd) {
|
|
24
|
+
const flagPath = join(cwd, '.claude', '.stop-hook-active');
|
|
25
|
+
if (existsSync(flagPath)) {
|
|
26
|
+
try {
|
|
27
|
+
const stat = statSync(flagPath);
|
|
28
|
+
if (Date.now() - stat.mtimeMs < STALE_THRESHOLD_MS) {
|
|
29
|
+
return true; // Another stop hook is active
|
|
30
|
+
}
|
|
31
|
+
} catch {
|
|
32
|
+
// Flag file unreadable — proceed
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function setStopHookActive(cwd) {
|
|
39
|
+
const flagPath = join(cwd, '.claude', '.stop-hook-active');
|
|
40
|
+
try {
|
|
41
|
+
writeFileSync(flagPath, String(Date.now()));
|
|
42
|
+
} catch {
|
|
43
|
+
// Non-critical
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function clearStopHookActive(cwd) {
|
|
48
|
+
const flagPath = join(cwd, '.claude', '.stop-hook-active');
|
|
49
|
+
try {
|
|
50
|
+
unlinkSync(flagPath);
|
|
51
|
+
} catch {
|
|
52
|
+
// Non-critical
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function readIndex(learningsDir) {
|
|
57
|
+
const indexPath = join(learningsDir, 'index.json');
|
|
58
|
+
if (existsSync(indexPath)) {
|
|
59
|
+
try {
|
|
60
|
+
return JSON.parse(readFileSync(indexPath, 'utf8'));
|
|
61
|
+
} catch {
|
|
62
|
+
return { learnings: [] };
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return { learnings: [] };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function writeIndex(learningsDir, index) {
|
|
69
|
+
writeFileSync(join(learningsDir, 'index.json'), JSON.stringify(index, null, 2) + '\n');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function extractLearnings(transcriptPath) {
|
|
73
|
+
if (!existsSync(transcriptPath)) return [];
|
|
74
|
+
|
|
75
|
+
const lines = readFileSync(transcriptPath, 'utf8').split(/\r?\n/).filter(Boolean);
|
|
76
|
+
const learnings = [];
|
|
77
|
+
|
|
78
|
+
// Scan last 20 lines for assistant messages containing [LEARN] blocks
|
|
79
|
+
const recent = lines.slice(-20);
|
|
80
|
+
for (const line of recent) {
|
|
81
|
+
try {
|
|
82
|
+
const entry = JSON.parse(line);
|
|
83
|
+
if (entry.type !== 'assistant') continue;
|
|
84
|
+
|
|
85
|
+
const content = entry.message?.content;
|
|
86
|
+
if (!Array.isArray(content)) continue;
|
|
87
|
+
|
|
88
|
+
for (const block of content) {
|
|
89
|
+
if (block.type !== 'text' || !block.text) continue;
|
|
90
|
+
|
|
91
|
+
let match;
|
|
92
|
+
LEARN_REGEX.lastIndex = 0;
|
|
93
|
+
while ((match = LEARN_REGEX.exec(block.text)) !== null) {
|
|
94
|
+
learnings.push({
|
|
95
|
+
category: match[1].trim(),
|
|
96
|
+
rule: match[2].trim(),
|
|
97
|
+
mistake: match[3] ? match[3].trim() : null,
|
|
98
|
+
correction: match[4] ? match[4].trim() : null,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
} catch {
|
|
103
|
+
// Skip malformed JSONL lines
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return learnings;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
const input = readFileSync(0, 'utf8');
|
|
112
|
+
const data = JSON.parse(input);
|
|
113
|
+
const cwd = data.cwd || process.cwd();
|
|
114
|
+
const transcriptPath = data.transcript_path;
|
|
115
|
+
|
|
116
|
+
if (checkStopHookActive(cwd)) process.exit(0);
|
|
117
|
+
setStopHookActive(cwd);
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
if (!transcriptPath) process.exit(0);
|
|
121
|
+
|
|
122
|
+
const learnings = extractLearnings(transcriptPath);
|
|
123
|
+
if (learnings.length === 0) process.exit(0);
|
|
124
|
+
|
|
125
|
+
const learningsDir = join(cwd, '.claude', 'learnings');
|
|
126
|
+
mkdirSync(learningsDir, { recursive: true });
|
|
127
|
+
|
|
128
|
+
const index = readIndex(learningsDir);
|
|
129
|
+
const today = new Date().toISOString().split('T')[0];
|
|
130
|
+
const projectName = basename(cwd);
|
|
131
|
+
|
|
132
|
+
for (const learning of learnings) {
|
|
133
|
+
const slug = slugify(learning.category);
|
|
134
|
+
const filename = `${slug}.md`;
|
|
135
|
+
const filePath = join(learningsDir, filename);
|
|
136
|
+
|
|
137
|
+
const entry = [
|
|
138
|
+
'---',
|
|
139
|
+
`created: ${today}`,
|
|
140
|
+
`category: ${learning.category}`,
|
|
141
|
+
`project: ${projectName}`,
|
|
142
|
+
'times_applied: 0',
|
|
143
|
+
'---',
|
|
144
|
+
'',
|
|
145
|
+
`**Rule:** ${learning.rule}`,
|
|
146
|
+
];
|
|
147
|
+
if (learning.mistake) entry.push(`**Mistake:** ${learning.mistake}`);
|
|
148
|
+
if (learning.correction) entry.push(`**Correction:** ${learning.correction}`);
|
|
149
|
+
entry.push('');
|
|
150
|
+
|
|
151
|
+
if (existsSync(filePath)) {
|
|
152
|
+
const existing = readFileSync(filePath, 'utf8');
|
|
153
|
+
writeFileSync(filePath, existing + '\n' + entry.join('\n'));
|
|
154
|
+
} else {
|
|
155
|
+
writeFileSync(filePath, entry.join('\n'));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const existingEntry = index.learnings.find((l) => l.file === filename);
|
|
159
|
+
if (existingEntry) {
|
|
160
|
+
existingEntry.created = today;
|
|
161
|
+
} else {
|
|
162
|
+
index.learnings.push({
|
|
163
|
+
file: filename,
|
|
164
|
+
category: learning.category,
|
|
165
|
+
created: today,
|
|
166
|
+
times_applied: 0,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
writeIndex(learningsDir, index);
|
|
172
|
+
} finally {
|
|
173
|
+
clearStopHookActive(cwd);
|
|
174
|
+
}
|
|
175
|
+
} catch {
|
|
176
|
+
// Never block session stop
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
process.exit(0);
|