claude-prism 1.7.2 → 1.8.1

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-prism",
3
- "version": "1.7.2",
3
+ "version": "1.8.1",
4
4
  "description": "AI agent harness implementing the EUDEC methodology",
5
5
  "author": { "name": "lazysaturday91" },
6
6
  "repository": "https://github.com/lazysaturday91/claude-prism",
package/CHANGELOG.md CHANGED
@@ -5,6 +5,28 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.8.1] — 2026-03-09
9
+
10
+ ### Changed
11
+ - **ESSENCE phase expanded** — Entry Judgment (Top-down/Bottom-up/Hybrid), Top-Down Removal Method with Counterexample test, Bottom-Up Competitive Exploration for novel problems
12
+ - **Scope Classification** added to DECOMPOSE — Core/Support/Out of Scope concentric circle model; "Out of Scope must not be empty" constraint
13
+ - **Self-Correction Triggers** — each trigger now specifies explicit fallback phase (→ ESSENCE/UNDERSTAND/DECOMPOSE); new triggers for scope expansion and error type oscillation
14
+ - `rules-lean.md` synced with Entry Judgment summary and fallback annotations
15
+
16
+ ## [1.8.0] — 2026-03-06
17
+
18
+ ### Added
19
+ - **Plan progress auto-tracking** — `PostToolUse [Edit|Write]` hook tracks file-level progress against active plan's "Files in Scope"
20
+ - `plan-progress-tracker` rule: matches edited files to scoped files, records milestones (25/50/75%), auto-transitions `draft → active` on first edit
21
+ - `PostToolUse [Edit|Write]` event matcher added to `settings.json` — enables hooks on file edits, not just Bash commands
22
+ - `parseScopedFiles(content)` in `plan-lifecycle.mjs` — extracts file paths from "Files in Scope" section
23
+ - `ensureFrontmatter(planPath, content)` in `plan-lifecycle.mjs` — auto-backfills frontmatter for plans without status (derives from checkbox progress)
24
+ - Self-Correction Trigger: "Plan file checkboxes not updated after batch"
25
+ - 27 new tests covering parseScopedFiles, ensureFrontmatter, plan-progress-tracker, and regression guards
26
+
27
+ ### Fixed
28
+ - `mergeSettings()` now compares both command and matcher — prevents skipping new matchers for existing hook commands during `prism update`
29
+
8
30
  ## [1.7.2] — 2026-03-06
9
31
 
10
32
  ### Fixed
@@ -0,0 +1,121 @@
1
+ /**
2
+ * claude-prism — Plan Progress Tracker
3
+ * PostToolUse rule: tracks file-level progress against active plan's "Files in Scope"
4
+ */
5
+
6
+ import { readFileSync, existsSync, readdirSync } from 'fs';
7
+ import { join, relative } from 'path';
8
+ import { readJsonState, writeJsonState } from '../lib/state.mjs';
9
+ import { parseScopedFiles, ensureFrontmatter, updatePlanStatus, appendHistory } from '../lib/plan-lifecycle.mjs';
10
+ import { parseFrontmatter } from '../lib/handoff.mjs';
11
+
12
+ export const planProgressTracker = {
13
+ name: 'plan-progress-tracker',
14
+
15
+ /**
16
+ * @param {Object} ctx - { action, filePath, ... }
17
+ * @param {Object} config - Prism config (projectRoot, hooks settings)
18
+ * @param {string} stateDir - Session state directory
19
+ * @returns {{ type: string, message?: string }}
20
+ */
21
+ evaluate(ctx, config, stateDir) {
22
+ // Only process Edit/Write actions
23
+ if (ctx.action !== 'edit' && ctx.action !== 'write') return { type: 'pass' };
24
+ if (!ctx.filePath) return { type: 'pass' };
25
+
26
+ const projectRoot = config.projectRoot || process.cwd();
27
+
28
+ // Find most recent active/draft plan
29
+ const plan = findActivePlan(projectRoot);
30
+ if (!plan) return { type: 'pass' };
31
+
32
+ // Parse "Files in Scope" → check if edited file is in scope
33
+ const scopedFiles = parseScopedFiles(plan.content);
34
+ if (scopedFiles.length === 0) return { type: 'pass' };
35
+
36
+ const relativePath = relative(projectRoot, ctx.filePath);
37
+ const inScope = scopedFiles.some(f =>
38
+ relativePath === f || relativePath.endsWith(f) || f.endsWith(relativePath)
39
+ );
40
+ if (!inScope) return { type: 'pass' };
41
+
42
+ // Accumulate touched files in session state
43
+ const state = readJsonState(stateDir, 'plan-progress') || { touched: [], recordedMilestones: [] };
44
+ if (!state.recordedMilestones) state.recordedMilestones = [];
45
+ if (!state.touched.includes(relativePath)) {
46
+ state.touched.push(relativePath);
47
+ }
48
+
49
+ // Ensure frontmatter exists (auto-backfill if missing)
50
+ try {
51
+ ensureFrontmatter(plan.path, plan.content);
52
+ } catch { /* should not break the hook */ }
53
+
54
+ // Calculate progress
55
+ const pct = Math.floor((state.touched.length / scopedFiles.length) * 100);
56
+ const fm = plan.frontmatter;
57
+ const planStatus = fm.status || 'active';
58
+
59
+ // draft → active (first scoped file edit)
60
+ if (planStatus === 'draft') {
61
+ try {
62
+ updatePlanStatus(plan.path, 'active');
63
+ appendHistory(projectRoot, {
64
+ plan: plan.name, event: 'status_change',
65
+ from: 'draft', to: 'active',
66
+ actor: 'hook:plan-progress-tracker',
67
+ detail: 'First scoped file edited'
68
+ });
69
+ } catch { /* lifecycle errors should not break the hook */ }
70
+ }
71
+
72
+ // Progress milestones (25%, 50%, 75%)
73
+ for (const m of [25, 50, 75]) {
74
+ if (pct >= m && !state.recordedMilestones.includes(m)) {
75
+ state.recordedMilestones.push(m);
76
+ try {
77
+ appendHistory(projectRoot, {
78
+ plan: plan.name, event: 'progress',
79
+ actor: 'hook:plan-progress-tracker',
80
+ detail: `Progress: ${state.touched.length}/${scopedFiles.length} (${pct}%)`
81
+ });
82
+ } catch { /* ignore */ }
83
+ }
84
+ }
85
+
86
+ // Save state
87
+ writeJsonState(stateDir, 'plan-progress', state);
88
+
89
+ return {
90
+ type: 'info',
91
+ message: `🌈 Plan progress: ${state.touched.length}/${scopedFiles.length} files (${pct}%) — ${plan.name}`
92
+ };
93
+ }
94
+ };
95
+
96
+ /**
97
+ * Find the most recent active or draft plan in .prism/plans/
98
+ * @param {string} projectRoot
99
+ * @returns {{ name: string, path: string, content: string, frontmatter: Object }|null}
100
+ */
101
+ function findActivePlan(projectRoot) {
102
+ const plansDir = join(projectRoot, '.prism', 'plans');
103
+ if (!existsSync(plansDir)) return null;
104
+
105
+ let files;
106
+ try {
107
+ files = readdirSync(plansDir).filter(f => f.endsWith('.md')).sort().reverse();
108
+ } catch { return null; }
109
+
110
+ for (const f of files) {
111
+ const path = join(plansDir, f);
112
+ let content;
113
+ try { content = readFileSync(path, 'utf8'); } catch { continue; }
114
+ const fm = parseFrontmatter(content);
115
+ const status = fm.status || 'active';
116
+ if (status === 'active' || status === 'draft') {
117
+ return { name: f, path, content, frontmatter: fm };
118
+ }
119
+ }
120
+ return null;
121
+ }
package/lib/installer.mjs CHANGED
@@ -66,6 +66,7 @@ export async function init(projectDir, options = {}) {
66
66
  copyFileSync(join(hooksSourceDir, 'session-end-handler.mjs'), join(rulesDestDir, 'session-end-handler.mjs'));
67
67
  copyFileSync(join(hooksSourceDir, 'subagent-scope-injector.mjs'), join(rulesDestDir, 'subagent-scope-injector.mjs'));
68
68
  copyFileSync(join(hooksSourceDir, 'task-plan-sync.mjs'), join(rulesDestDir, 'task-plan-sync.mjs'));
69
+ copyFileSync(join(hooksSourceDir, 'plan-progress-tracker.mjs'), join(rulesDestDir, 'plan-progress-tracker.mjs'));
69
70
 
70
71
  // Copy lib dependencies
71
72
  const libDestDir = join(claudeDir, 'lib');
@@ -661,7 +662,7 @@ export function dryRun(projectDir, options = {}) {
661
662
  });
662
663
  }
663
664
 
664
- for (const rule of ['commit-guard.mjs', 'test-tracker.mjs', 'plan-enforcement.mjs', 'precompact-handler.mjs', 'session-end-handler.mjs', 'subagent-scope-injector.mjs', 'task-plan-sync.mjs']) {
665
+ for (const rule of ['commit-guard.mjs', 'test-tracker.mjs', 'plan-enforcement.mjs', 'precompact-handler.mjs', 'session-end-handler.mjs', 'subagent-scope-injector.mjs', 'task-plan-sync.mjs', 'plan-progress-tracker.mjs']) {
665
666
  const target = join(claudeDir, 'rules', rule);
666
667
  actions.push({
667
668
  type: 'rule',
@@ -904,6 +905,7 @@ function mergeSettings(claudeDir) {
904
905
  for (const hook of hookList) {
905
906
  const alreadyExists = existing.hooks[event].some(
906
907
  h => h.hooks?.[0]?.command === hook.hooks?.[0]?.command
908
+ && (h.matcher || '') === (hook.matcher || '')
907
909
  );
908
910
  if (!alreadyExists) {
909
911
  existing.hooks[event].push(hook);
@@ -119,6 +119,49 @@ export function resolvePlan(projectRoot, planName) {
119
119
  return plans.find(p => (p.status || 'active') === 'active') || plans[0];
120
120
  }
121
121
 
122
+ // ─── Scoped Files & Frontmatter Helpers ───
123
+
124
+ /**
125
+ * Parse "Files in Scope" section from plan content
126
+ * Extracts backtick-wrapped file paths (e.g., `path/to/file.ts`)
127
+ * @param {string} content - Plan markdown content
128
+ * @returns {string[]} File paths extracted
129
+ */
130
+ export function parseScopedFiles(content) {
131
+ const section = content.match(/##\s+Files\s+in\s+Scope\s*\n([\s\S]*?)(?=\n##|\n---|\s*$)/i);
132
+ if (!section) return [];
133
+ return [...section[1].matchAll(/`([^`]+\.[a-z]+)`/g)].map(m => m[1]);
134
+ }
135
+
136
+ /**
137
+ * Ensure a plan file has frontmatter; add if missing
138
+ * Derives status from checkbox progress (0/N → draft, M/N → active, N/N → completed)
139
+ * @param {string} planPath - Absolute path to plan file
140
+ * @param {string} content - Plan file content
141
+ */
142
+ export function ensureFrontmatter(planPath, content) {
143
+ const fm = parseFrontmatter(content);
144
+ if (fm.status) return; // Already has frontmatter with status
145
+
146
+ // Derive status from checkbox state
147
+ let total = 0, done = 0;
148
+ for (const line of content.split('\n')) {
149
+ if (/^[-*]\s+\[x\]/i.test(line)) { total++; done++; }
150
+ else if (/^[-*]\s+\[ \]/.test(line)) { total++; }
151
+ }
152
+
153
+ let status = 'active';
154
+ if (total > 0 && done === 0) status = 'draft';
155
+ else if (total > 0 && done === total) status = 'completed';
156
+
157
+ // Extract date from filename (YYYY-MM-DD-topic.md)
158
+ const dateMatch = planPath.match(/(\d{4}-\d{2}-\d{2})/);
159
+ const created = dateMatch ? dateMatch[1] : new Date().toISOString().slice(0, 10);
160
+
161
+ const fmBlock = `---\nstatus: ${status}\ncreated: ${created}\n---\n\n`;
162
+ writeFileSync(planPath, fmBlock + content);
163
+ }
164
+
122
165
  // ─── Plan Discovery & Import ───
123
166
 
124
167
  const PLAN_PATTERN = /^\d{4}-\d{2}-\d{2}-.+\.md$/;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-prism",
3
- "version": "1.7.2",
3
+ "version": "1.8.1",
4
4
  "description": "AI agent harness implementing the EUDEC methodology — Essence, Understand, Decompose, Execute, Checkpoint.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -30,6 +30,13 @@ For the full methodology, run `/claude-prism:prism`.
30
30
  | **Standard** | 3-5 files, 50-200 LOC | Run `/claude-prism:prism` for full EUDEC guidance |
31
31
  | **Full** | 6+ files, 200+ LOC, or unclear scope | Run `/claude-prism:prism` for full EUDEC with plan file |
32
32
 
33
+ ## Entry Judgment
34
+
35
+ Before extracting essence: "Can existing elements be removed from this problem?"
36
+ - **YES** (prior art exists) → Top-down: list components, remove one at a time, what remains is essence
37
+ - **NO** (blank slate) → Bottom-up: collect broadly, score, compete 2-3 candidates, select survivor
38
+ - **PARTIAL** → Split the problem, apply each path to its part
39
+
33
40
  ## Task Type Derivation
34
41
 
35
42
  | Essence Character | Type | Path |
@@ -69,11 +76,12 @@ After 3 failed fixes: STOP. Discuss with user.
69
76
 
70
77
  ## Self-Correction Triggers
71
78
 
72
- - Same file edited 3+ times → investigate root cause
73
- - Editing file not in plan → scope change needed?
74
- - 3 consecutive test failures → back to essence
79
+ - Same file edited 3+ times → investigate root cause → **Fallback: UNDERSTAND**
80
+ - Editing file not in plan → scope change needed? → **Fallback: DECOMPOSE**
81
+ - 3 consecutive test failures → **Fallback: ESSENCE (was the essence wrong?)**
75
82
  - 5 turns autonomous → report progress before continuing
76
- - Adding workarounds to fix workarounds → design problem, step back
83
+ - Adding workarounds to fix workarounds → **Fallback: ESSENCE (re-extract)**
84
+ - Scope expanding beyond plan → **Fallback: DECOMPOSE (re-classify scope)**
77
85
 
78
86
  ## Rationalization Defense
79
87
 
@@ -29,13 +29,41 @@ The approach: **Essence → Simplify → Expand**. Strip down to the core of the
29
29
 
30
30
  Starting point for all work. Strip down to the core of the problem before implementation.
31
31
 
32
- ### 1-1. Essence Extraction (3 Steps)
32
+ Essence = "the thing without which it ceases to be what it is."
33
33
 
34
- | Step | Question | Output |
35
- |------|----------|--------|
36
- | **Extract** | "What do they actually want?" | Essence statement (1 sentence) |
37
- | **Simplify** | "What's the smallest working version?" | Minimal case |
38
- | **Expansion path** | "How do we grow from here?" | Expansion steps (2-4 steps) |
34
+ ### 1-1. Entry Judgment
35
+
36
+ Before extracting essence, determine the approach:
37
+
38
+ > "Can existing elements be removed from this problem?" (Are there references, prior art, existing solutions?)
39
+
40
+ | Answer | Meaning | Path |
41
+ |--------|---------|------|
42
+ | **YES** | References, existing products, prior art exist | → Top-down (removal method) |
43
+ | **NO** | No precedent, blank slate, novel domain | → Bottom-up (competitive exploration) |
44
+ | **PARTIAL** | Some parts have references, some don't | → Hybrid (split, apply each path independently, merge in output) |
45
+
46
+ Most coding tasks are **YES** (top-down). Bottom-up is for genuinely novel problems (new product direction, undefined feature space).
47
+
48
+ ### 1-2. Essence Extraction — Top-Down (Removal Method)
49
+
50
+ The default path. Remove non-essential elements to reveal what remains.
51
+
52
+ ```
53
+ Step 1. List all components of the problem/system
54
+ Step 2. Remove one at a time, asking:
55
+ "Without this, is it still the thing?"
56
+ → YES (still the thing) → not essential, remove
57
+ → NO (no longer the thing) → essence candidate
58
+ Step 3. Validate remaining candidates:
59
+ "Do these alone justify the thing's existence?"
60
+ → YES → essence confirmed
61
+ → NO → restore one removed item, re-test
62
+ Step 4. Counterexample test:
63
+ "Can I name one scenario where this essence is wrong?"
64
+ → NO → proceed
65
+ → YES → weaken confidence, document the counterexample, and verify the essence still holds from a different angle (e.g., different user persona, edge case, or opposing assumption)
66
+ ```
39
67
 
40
68
  **Output format:**
41
69
  ```
@@ -45,7 +73,28 @@ Starting point for all work. Strip down to the core of the problem before implem
45
73
  - Expansion path: minimal → [step1] → [step2] → [complete]
46
74
  ```
47
75
 
48
- ### 1-2. Task Type Derivation
76
+ ### 1-3. Essence Extraction — Bottom-Up (Competitive Exploration)
77
+
78
+ For novel problems with no prior art. Generate multiple essence candidates, compete them, select the survivor.
79
+
80
+ ```
81
+ Step 1. Collect broadly (don't judge yet — quantity > quality)
82
+ Stop when: information saturates OR 3+ independent perspectives gathered OR time box hit
83
+ Step 2. Filter (two-pass):
84
+ 1st pass: gut feel — strong / moderate / weak (drop weak)
85
+ 2nd pass: score remaining on frequency (1-3), impact (1-3), connectivity (1-3)
86
+ → 7-9 points: essence candidate
87
+ → 4-6 points: support element (reuse in DECOMPOSE)
88
+ Step 3. Cluster similar candidates → select top 2-3
89
+ If only 1 candidate remains → create its opposite, compete them
90
+ Step 4. Parallel exploration: "If this were the essence, what would we build?"
91
+ → Score difference ≤2 or irreversible decision → explore all equally
92
+ → Score difference >2 and reversible → staged elimination
93
+ Step 5. Converge: which candidate solves the user's problem more directly?
94
+ Feasibility is NOT a factor here (that's DECOMPOSE's job)
95
+ ```
96
+
97
+ ### 1-4. Task Type Derivation
49
98
 
50
99
  The task type naturally emerges from the essence:
51
100
 
@@ -59,7 +108,7 @@ The task type naturally emerges from the essence:
59
108
 
60
109
  **Migration shortcut**: When applying the same transformation to 10+ files, don't decompose into individual file tasks. Define the pattern once, apply in batches of 5-10, verify after each batch. Scope guard thresholds are raised automatically when a plan file exists.
61
110
 
62
- ### 1-3. Essence Validation (Error Prevention)
111
+ ### 1-5. Essence Validation (Error Prevention)
63
112
 
64
113
  | Trap | Response |
65
114
  |------|----------|
@@ -70,15 +119,7 @@ The task type naturally emerges from the essence:
70
119
 
71
120
  **Core test**: If the essence statement contains specific technology/tool names → it's still at solution level, not essence. Go one level higher.
72
121
 
73
- ### 1-4. Quality Gate: ESSENCE UNDERSTAND
74
-
75
- Before moving to UNDERSTAND, verify:
76
- - [ ] Essence statement is technology-neutral (holds without naming specific tools/libraries)
77
- - [ ] Minimal case is truly "minimal" (can it be reduced further?)
78
- - [ ] Each step in the expansion path works independently
79
- - [ ] Task type has been clearly derived
80
-
81
- ### 1-5. Adaptive Weight (Task Size Routing)
122
+ ### 1-6. Adaptive Weight (Task Size Routing)
82
123
 
83
124
  After extracting essence and task type, assess task weight to select the appropriate EUDEC path:
84
125
 
@@ -98,6 +139,15 @@ After extracting essence and task type, assess task weight to select the appropr
98
139
 
99
140
  Bugfixes skip ESSENCE extraction, UNDERSTAND ceremony, and DECOMPOSE entirely. The 4-step debugging protocol (4-3) is the complete path.
100
141
 
142
+ ### 1-7. Quality Gate: ESSENCE → UNDERSTAND
143
+
144
+ Before moving to UNDERSTAND, verify:
145
+ - [ ] Essence statement is technology-neutral (holds without naming specific tools/libraries)
146
+ - [ ] Minimal case is truly "minimal" (can it be reduced further?)
147
+ - [ ] Each step in the expansion path works independently
148
+ - [ ] Task type has been clearly derived
149
+ - [ ] Counterexample test passed (no unresolved counterexamples)
150
+
101
151
  ---
102
152
 
103
153
  ## EUDEC 2. UNDERSTAND — Understanding Protocol
@@ -171,7 +221,23 @@ If no code change is needed (architecture review, cause analysis, investigation)
171
221
  | **Refactor** | 3+ files affected (same advisory as Feature) | Decompose into batches. Plan file if 6+ files or 3+ modules. |
172
222
  | **Investigation** | — | Skip decomposition. Define exploration scope. |
173
223
 
174
- ### 3-2. Decomposition Principles (Feature/Refactor only)
224
+ ### 3-2. Scope Classification (before decomposing)
225
+
226
+ Before breaking into batches, classify all changes using the concentric circle model:
227
+
228
+ ```
229
+ "Is this required for the essence to work?"
230
+ → YES → Core (must be in scope)
231
+ → NO → "Does this amplify the essence's value?"
232
+ → YES → Support (nice to have, lower priority)
233
+ → NO → Out of Scope (explicitly excluded)
234
+ ```
235
+
236
+ - Only **Core** items enter batch decomposition
237
+ - **Support** items are noted for later (not in current plan)
238
+ - **Out of Scope** must not be empty — if everything is "Core", scope classification has failed (return to ESSENCE)
239
+
240
+ ### 3-3. Decomposition Principles (Feature/Refactor only)
175
241
 
176
242
  1. **Independent verification**: each unit has a pass criterion
177
243
  2. **Files specified**: each task lists files to create/modify
@@ -188,7 +254,7 @@ If no code change is needed (architecture review, cause analysis, investigation)
188
254
  - **[S]-only: up to 8 per batch** (independent small changes can be batched aggressively)
189
255
  - Aligns with 4-1 adaptive batch size (simple/mechanical: 5-8 per batch)
190
256
 
191
- ### 3-3. Plan File Persistence
257
+ ### 3-4. Plan File Persistence
192
258
 
193
259
  Save multi-step plans (6+ files) as markdown:
194
260
  - **Path**: `.prism/plans/YYYY-MM-DD-<topic>.md`
@@ -229,7 +295,7 @@ Tech stack, key decisions, 2-3 sentences max.
229
295
  - [Known unknowns or potential blockers]
230
296
  ```
231
297
 
232
- ### 3-4. Pre-Decomposition Check
298
+ ### 3-5. Pre-Decomposition Check
233
299
 
234
300
  Before creating the plan:
235
301
  - [ ] **Codebase audit**: grep/search to verify targets actually exist in code (don't trust assumptions from prior sessions)
@@ -240,7 +306,7 @@ Before creating the plan:
240
306
 
241
307
  **Staleness prevention**: If plan targets (files to change, patterns to replace) no longer exist in the codebase, mark them as "already completed" before starting execution. Never start a plan without verifying its targets are real.
242
308
 
243
- ### 3-5. Quality Gate: DECOMPOSE → EXECUTE
309
+ ### 3-6. Quality Gate: DECOMPOSE → EXECUTE
244
310
 
245
311
  Before starting execution, all must pass:
246
312
  - [ ] Plan file exists and targets verified against codebase
@@ -315,23 +381,24 @@ Choose verification proportional to the **risk of the change**, not the file pat
315
381
 
316
382
  ### 4-4. Self-Correction Triggers
317
383
 
318
- - Same file edited 3+ times → "Possible thrashing. Investigate root cause."
319
- - Editing file not in plan → "Scope change needed?"
320
- - 3 consecutive test failures → "Approach problem. Back to ESSENCE did we get the essence wrong?"
384
+ - Same file edited 3+ times → "Possible thrashing. Investigate root cause." → **Fallback: UNDERSTAND (re-examine the problem)**
385
+ - Editing file not in plan → "Scope change needed?" → **Fallback: DECOMPOSE (re-classify scope)**
386
+ - 3 consecutive test failures → "Approach problem." **Fallback: ESSENCE (was the essence wrong?)**
321
387
  - New package needed → "Confirm with user"
322
388
  - 5 turns autonomous → "Report progress before continuing"
323
- - Adding workarounds to fix workarounds → "Design problem. Step back."
389
+ - Adding workarounds to fix workarounds → "Design problem." **Fallback: ESSENCE (re-extract)**
324
390
  - Copy-pasting similar code 3+ times → "Need abstraction? Ask user."
325
391
  - Dependency version mismatch detected → "Resolve before continuing."
392
+ - Plan file checkboxes not updated after batch → "Update plan checkboxes and frontmatter before continuing"
393
+ - Scope expanding beyond plan → "Scope creep." → **Fallback: DECOMPOSE (re-run scope classification)**
394
+ - Error messages changing type across fixes → "Chasing symptoms, not root cause." → **Fallback: ESSENCE**
326
395
 
327
396
  **Goal Recitation** (prevents drift in long sessions):
328
397
  - At every batch boundary, re-read the plan file and confirm: "Current work aligns with: [original goal]"
329
398
  - If current work does not serve the original goal → STOP, report drift, return to plan
330
399
 
331
400
  **Thrashing Detector** (beyond simple edit counting):
332
- - Successive edits reverting previous changes (oscillation) → "Reverting own work. Wrong approach."
333
- - Scope of changes expanding beyond plan → "Scope creep. Return to DECOMPOSE."
334
- - Error messages changing type across fixes → "Chasing symptoms, not root cause. Back to ESSENCE."
401
+ - Successive edits reverting previous changes (oscillation) → "Reverting own work. Wrong approach." → **Fallback: ESSENCE**
335
402
 
336
403
  ### 4-5. Scope Guard
337
404
 
@@ -1,7 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import { runPipelineAsync } from '../lib/pipeline.mjs';
3
3
  import { testTracker } from '../rules/test-tracker.mjs';
4
+ import { planProgressTracker } from '../rules/plan-progress-tracker.mjs';
4
5
 
5
6
  await runPipelineAsync([
6
7
  { name: 'test-tracker', rule: testTracker },
8
+ { name: 'plan-progress-tracker', rule: planProgressTracker },
7
9
  ], 'PostToolUse');
@@ -32,6 +32,16 @@
32
32
  "timeout": 5000
33
33
  }
34
34
  ]
35
+ },
36
+ {
37
+ "matcher": "Edit|Write",
38
+ "hooks": [
39
+ {
40
+ "type": "command",
41
+ "command": "node .claude/hooks/post-tool.mjs",
42
+ "timeout": 5000
43
+ }
44
+ ]
35
45
  }
36
46
  ],
37
47
  "PreCompact": [