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.
Files changed (51) hide show
  1. package/CHANGELOG.md +97 -0
  2. package/README.md +114 -37
  3. package/package.json +7 -2
  4. package/src/commands/doctor.js +507 -104
  5. package/src/commands/init.js +103 -19
  6. package/src/core/detector.js +34 -10
  7. package/src/core/merger.js +48 -3
  8. package/src/core/scaffolder.js +58 -1
  9. package/src/data/agents.js +5 -0
  10. package/src/index.js +2 -1
  11. package/src/prompts/claude-md-merge.js +5 -0
  12. package/templates/agents/universal/build-validator.md +23 -0
  13. package/templates/agents/universal/code-simplifier.md +11 -0
  14. package/templates/agents/universal/plan-reviewer.md +39 -0
  15. package/templates/agents/universal/test-writer.md +25 -0
  16. package/templates/agents/universal/verify-app.md +12 -0
  17. package/templates/commands/build-fix.md +5 -0
  18. package/templates/commands/commit-push-pr.md +6 -0
  19. package/templates/commands/compact-safe.md +5 -0
  20. package/templates/commands/conflict-resolver.md +5 -0
  21. package/templates/commands/end.md +10 -0
  22. package/templates/commands/learn.md +33 -0
  23. package/templates/commands/refactor-clean.md +10 -0
  24. package/templates/commands/review-changes.md +5 -0
  25. package/templates/commands/review-plan.md +5 -0
  26. package/templates/commands/setup.md +5 -0
  27. package/templates/commands/start.md +10 -0
  28. package/templates/commands/status.md +5 -0
  29. package/templates/commands/sync.md +5 -0
  30. package/templates/commands/techdebt.md +5 -0
  31. package/templates/commands/test-coverage.md +5 -0
  32. package/templates/commands/update-claude-md.md +5 -0
  33. package/templates/commands/verify.md +11 -0
  34. package/templates/core/agents-md.md +25 -0
  35. package/templates/core/claude-md.md +17 -0
  36. package/templates/hooks/README.md +106 -0
  37. package/templates/hooks/correction-detect.cjs +48 -0
  38. package/templates/hooks/examples/prompt-hook-commit-validator.json +16 -0
  39. package/templates/hooks/learn-capture.cjs +179 -0
  40. package/templates/hooks/pre-compact-save.cjs +60 -0
  41. package/templates/hooks/skill-hint.cjs +66 -0
  42. package/templates/memory/decisions.md +11 -0
  43. package/templates/memory/preferences.md +17 -0
  44. package/templates/settings/base.json +56 -8
  45. package/templates/skills/templates/backend-conventions.md +1 -0
  46. package/templates/skills/templates/frontend-design-system.md +1 -0
  47. package/templates/skills/templates/project-patterns.md +1 -0
  48. package/templates/skills/universal/coding-principles.md +60 -0
  49. package/templates/skills/universal/context-management.md +21 -0
  50. package/templates/skills/universal/planning-with-files.md +11 -0
  51. package/templates/skills/universal/verification.md +18 -0
@@ -10,3 +10,8 @@ After compaction, briefly confirm:
10
10
  - Current task
11
11
  - Current branch
12
12
  - What was just being worked on
13
+
14
+ ## Trigger Phrases
15
+ - "compact context"
16
+ - "free up context"
17
+ - "running low on context"
@@ -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"
@@ -22,3 +22,8 @@ Only analyze and report.
22
22
 
23
23
  The user will decide which findings to act on and apply fixes themselves.
24
24
  Do NOT apply any fixes. Do NOT touch any files. REPORT ONLY.
25
+
26
+ ## Trigger Phrases
27
+ - "review my changes"
28
+ - "what did I change"
29
+ - "code review"
@@ -12,3 +12,8 @@ review the plan for:
12
12
  - SPEC.md alignment
13
13
 
14
14
  Wait for the review and address all feedback before proceeding.
15
+
16
+ ## Trigger Phrases
17
+ - "review this plan"
18
+ - "check my implementation plan"
19
+ - "plan review"
@@ -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"
@@ -8,3 +8,8 @@ Report current session state:
8
8
  - Test status (last run results)
9
9
  - Context usage estimate
10
10
  - Any blockers or pending decisions
11
+
12
+ ## Trigger Phrases
13
+ - "what's the status"
14
+ - "where am I"
15
+ - "what am I working on"
@@ -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"
@@ -11,3 +11,8 @@ Scan the codebase for technical debt:
11
11
  - Inconsistent patterns
12
12
 
13
13
  Report findings organized by severity. Fix quick wins directly.
14
+
15
+ ## Trigger Phrases
16
+ - "find tech debt"
17
+ - "scan for issues"
18
+ - "code quality scan"
@@ -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"
@@ -11,3 +11,8 @@ Review what happened:
11
11
 
12
12
  Write the proposed additions to the Gotchas section or
13
13
  Critical Rules section. Show the diff before applying.
14
+
15
+ ## Trigger Phrases
16
+ - "update CLAUDE.md"
17
+ - "add to rules"
18
+ - "update project rules"
@@ -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);