@sienklogic/plan-build-run 2.0.2 → 2.1.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 (156) hide show
  1. package/dashboard/src/routes/pages.routes.js +11 -4
  2. package/dashboard/src/services/dashboard.service.js +81 -17
  3. package/dashboard/src/services/phase.service.js +30 -24
  4. package/dashboard/src/services/roadmap.service.js +3 -3
  5. package/dashboard/src/views/partials/phase-content.ejs +5 -4
  6. package/package.json +1 -1
  7. package/plugins/cursor-pbr/.cursor-plugin/plugin.json +1 -1
  8. package/plugins/cursor-pbr/CHANGELOG.md +15 -0
  9. package/plugins/cursor-pbr/README.md +118 -0
  10. package/plugins/cursor-pbr/agents/codebase-mapper.md +108 -0
  11. package/plugins/cursor-pbr/agents/debugger.md +168 -0
  12. package/plugins/cursor-pbr/agents/executor.md +236 -0
  13. package/plugins/cursor-pbr/agents/general.md +87 -0
  14. package/plugins/cursor-pbr/agents/integration-checker.md +87 -0
  15. package/plugins/cursor-pbr/agents/plan-checker.md +198 -0
  16. package/plugins/cursor-pbr/agents/planner.md +180 -0
  17. package/plugins/cursor-pbr/agents/researcher.md +162 -0
  18. package/plugins/cursor-pbr/agents/synthesizer.md +101 -0
  19. package/plugins/cursor-pbr/agents/verifier.md +193 -0
  20. package/plugins/cursor-pbr/assets/logo.svg +21 -0
  21. package/plugins/cursor-pbr/hooks/hooks.json +189 -7
  22. package/plugins/cursor-pbr/references/agent-anti-patterns.md +25 -0
  23. package/plugins/cursor-pbr/references/agent-interactions.md +135 -0
  24. package/plugins/cursor-pbr/references/agent-teams.md +55 -0
  25. package/plugins/cursor-pbr/references/checkpoints.md +158 -0
  26. package/plugins/cursor-pbr/references/common-bug-patterns.md +14 -0
  27. package/plugins/cursor-pbr/references/config-reference.md +442 -0
  28. package/plugins/cursor-pbr/references/continuation-format.md +213 -0
  29. package/plugins/cursor-pbr/references/deviation-rules.md +113 -0
  30. package/plugins/cursor-pbr/references/git-integration.md +227 -0
  31. package/plugins/cursor-pbr/references/integration-patterns.md +118 -0
  32. package/plugins/cursor-pbr/references/model-profiles.md +100 -0
  33. package/plugins/cursor-pbr/references/model-selection.md +32 -0
  34. package/plugins/cursor-pbr/references/pbr-rules.md +194 -0
  35. package/plugins/cursor-pbr/references/plan-authoring.md +182 -0
  36. package/plugins/cursor-pbr/references/plan-format.md +288 -0
  37. package/plugins/cursor-pbr/references/planning-config.md +214 -0
  38. package/plugins/cursor-pbr/references/questioning.md +215 -0
  39. package/plugins/cursor-pbr/references/reading-verification.md +128 -0
  40. package/plugins/cursor-pbr/references/stub-patterns.md +161 -0
  41. package/plugins/cursor-pbr/references/subagent-coordination.md +120 -0
  42. package/plugins/cursor-pbr/references/ui-formatting.md +462 -0
  43. package/plugins/cursor-pbr/references/verification-patterns.md +199 -0
  44. package/plugins/cursor-pbr/references/wave-execution.md +96 -0
  45. package/plugins/cursor-pbr/rules/pbr-workflow.mdc +48 -0
  46. package/plugins/cursor-pbr/setup.ps1 +78 -0
  47. package/plugins/cursor-pbr/setup.sh +83 -0
  48. package/plugins/cursor-pbr/skills/begin/SKILL.md +566 -0
  49. package/plugins/cursor-pbr/skills/begin/templates/PROJECT.md.tmpl +34 -0
  50. package/plugins/cursor-pbr/skills/begin/templates/REQUIREMENTS.md.tmpl +19 -0
  51. package/plugins/cursor-pbr/skills/begin/templates/STATE.md.tmpl +50 -0
  52. package/plugins/cursor-pbr/skills/begin/templates/config.json.tmpl +64 -0
  53. package/plugins/cursor-pbr/skills/begin/templates/researcher-prompt.md.tmpl +20 -0
  54. package/plugins/cursor-pbr/skills/begin/templates/roadmap-prompt.md.tmpl +31 -0
  55. package/plugins/cursor-pbr/skills/begin/templates/synthesis-prompt.md.tmpl +17 -0
  56. package/plugins/cursor-pbr/skills/build/SKILL.md +902 -0
  57. package/plugins/cursor-pbr/skills/config/SKILL.md +253 -0
  58. package/plugins/cursor-pbr/skills/continue/SKILL.md +159 -0
  59. package/plugins/cursor-pbr/skills/debug/SKILL.md +512 -0
  60. package/plugins/cursor-pbr/skills/debug/templates/continuation-prompt.md.tmpl +17 -0
  61. package/plugins/cursor-pbr/skills/debug/templates/initial-investigation-prompt.md.tmpl +28 -0
  62. package/plugins/cursor-pbr/skills/discuss/SKILL.md +344 -0
  63. package/plugins/cursor-pbr/skills/discuss/templates/CONTEXT.md.tmpl +62 -0
  64. package/plugins/cursor-pbr/skills/discuss/templates/decision-categories.md +10 -0
  65. package/plugins/cursor-pbr/skills/explore/SKILL.md +375 -0
  66. package/plugins/cursor-pbr/skills/health/SKILL.md +218 -0
  67. package/plugins/cursor-pbr/skills/health/templates/check-pattern.md.tmpl +31 -0
  68. package/plugins/cursor-pbr/skills/health/templates/output-format.md.tmpl +64 -0
  69. package/plugins/cursor-pbr/skills/help/SKILL.md +152 -0
  70. package/plugins/cursor-pbr/skills/import/SKILL.md +499 -0
  71. package/plugins/cursor-pbr/skills/milestone/SKILL.md +701 -0
  72. package/plugins/cursor-pbr/skills/milestone/templates/audit-report.md.tmpl +49 -0
  73. package/plugins/cursor-pbr/skills/milestone/templates/stats-file.md.tmpl +31 -0
  74. package/plugins/cursor-pbr/skills/note/SKILL.md +228 -0
  75. package/plugins/cursor-pbr/skills/pause/SKILL.md +246 -0
  76. package/plugins/cursor-pbr/skills/pause/templates/continue-here.md.tmpl +72 -0
  77. package/plugins/cursor-pbr/skills/plan/SKILL.md +648 -0
  78. package/plugins/cursor-pbr/skills/plan/templates/checker-prompt.md.tmpl +22 -0
  79. package/plugins/cursor-pbr/skills/plan/templates/gap-closure-prompt.md.tmpl +33 -0
  80. package/plugins/cursor-pbr/skills/plan/templates/planner-prompt.md.tmpl +39 -0
  81. package/plugins/cursor-pbr/skills/plan/templates/researcher-prompt.md.tmpl +20 -0
  82. package/plugins/cursor-pbr/skills/plan/templates/revision-prompt.md.tmpl +24 -0
  83. package/plugins/cursor-pbr/skills/quick/SKILL.md +351 -0
  84. package/plugins/cursor-pbr/skills/resume/SKILL.md +399 -0
  85. package/plugins/cursor-pbr/skills/review/SKILL.md +649 -0
  86. package/plugins/cursor-pbr/skills/review/templates/debugger-prompt.md.tmpl +61 -0
  87. package/plugins/cursor-pbr/skills/review/templates/gap-planner-prompt.md.tmpl +41 -0
  88. package/plugins/cursor-pbr/skills/review/templates/verifier-prompt.md.tmpl +116 -0
  89. package/plugins/cursor-pbr/skills/scan/SKILL.md +301 -0
  90. package/plugins/cursor-pbr/skills/scan/templates/mapper-prompt.md.tmpl +202 -0
  91. package/plugins/cursor-pbr/skills/setup/SKILL.md +250 -0
  92. package/plugins/cursor-pbr/skills/shared/commit-planning-docs.md +36 -0
  93. package/plugins/cursor-pbr/skills/shared/config-loading.md +103 -0
  94. package/plugins/cursor-pbr/skills/shared/context-budget.md +41 -0
  95. package/plugins/cursor-pbr/skills/shared/context-loader-task.md +87 -0
  96. package/plugins/cursor-pbr/skills/shared/digest-select.md +80 -0
  97. package/plugins/cursor-pbr/skills/shared/domain-probes.md +126 -0
  98. package/plugins/cursor-pbr/skills/shared/error-reporting.md +80 -0
  99. package/plugins/cursor-pbr/skills/shared/gate-prompts.md +389 -0
  100. package/plugins/cursor-pbr/skills/shared/phase-argument-parsing.md +46 -0
  101. package/plugins/cursor-pbr/skills/shared/progress-display.md +54 -0
  102. package/plugins/cursor-pbr/skills/shared/revision-loop.md +82 -0
  103. package/plugins/cursor-pbr/skills/shared/state-loading.md +63 -0
  104. package/plugins/cursor-pbr/skills/shared/state-update.md +162 -0
  105. package/plugins/cursor-pbr/skills/shared/universal-anti-patterns.md +34 -0
  106. package/plugins/cursor-pbr/skills/status/SKILL.md +362 -0
  107. package/plugins/cursor-pbr/skills/todo/SKILL.md +195 -0
  108. package/plugins/cursor-pbr/templates/CONTEXT.md.tmpl +53 -0
  109. package/plugins/cursor-pbr/templates/INTEGRATION-REPORT.md.tmpl +152 -0
  110. package/plugins/cursor-pbr/templates/RESEARCH-SUMMARY.md.tmpl +98 -0
  111. package/plugins/cursor-pbr/templates/ROADMAP.md.tmpl +41 -0
  112. package/plugins/cursor-pbr/templates/SUMMARY.md.tmpl +82 -0
  113. package/plugins/cursor-pbr/templates/VERIFICATION-DETAIL.md.tmpl +117 -0
  114. package/plugins/cursor-pbr/templates/continue-here.md.tmpl +74 -0
  115. package/plugins/cursor-pbr/templates/prompt-partials/phase-project-context.md.tmpl +38 -0
  116. package/plugins/pbr/agents/codebase-mapper.md +41 -206
  117. package/plugins/pbr/agents/debugger.md +65 -171
  118. package/plugins/pbr/agents/executor.md +90 -275
  119. package/plugins/pbr/agents/general.md +27 -97
  120. package/plugins/pbr/agents/integration-checker.md +35 -112
  121. package/plugins/pbr/agents/plan-checker.md +71 -164
  122. package/plugins/pbr/agents/planner.md +75 -246
  123. package/plugins/pbr/agents/researcher.md +63 -255
  124. package/plugins/pbr/agents/synthesizer.md +49 -174
  125. package/plugins/pbr/agents/verifier.md +75 -366
  126. package/plugins/pbr/hooks/hooks.json +14 -10
  127. package/plugins/pbr/scripts/auto-continue.js +20 -4
  128. package/plugins/pbr/scripts/check-dangerous-commands.js +1 -1
  129. package/plugins/pbr/scripts/check-phase-boundary.js +1 -1
  130. package/plugins/pbr/scripts/check-plan-format.js +3 -3
  131. package/plugins/pbr/scripts/check-roadmap-sync.js +3 -3
  132. package/plugins/pbr/scripts/check-skill-workflow.js +1 -1
  133. package/plugins/pbr/scripts/check-state-sync.js +2 -2
  134. package/plugins/pbr/scripts/check-subagent-output.js +1 -1
  135. package/plugins/pbr/scripts/check-summary-gate.js +198 -0
  136. package/plugins/pbr/scripts/context-budget-check.js +1 -1
  137. package/plugins/pbr/scripts/event-handler.js +2 -2
  138. package/plugins/pbr/scripts/event-logger.js +1 -1
  139. package/plugins/pbr/scripts/log-subagent.js +1 -1
  140. package/plugins/pbr/scripts/pbr-tools.js +1 -1
  141. package/plugins/pbr/scripts/post-write-dispatch.js +1 -1
  142. package/plugins/pbr/scripts/post-write-quality.js +1 -1
  143. package/plugins/pbr/scripts/pre-bash-dispatch.js +1 -1
  144. package/plugins/pbr/scripts/pre-write-dispatch.js +16 -3
  145. package/plugins/pbr/scripts/session-cleanup.js +1 -1
  146. package/plugins/pbr/scripts/status-line.js +1 -1
  147. package/plugins/pbr/scripts/suggest-compact.js +1 -1
  148. package/plugins/pbr/scripts/task-completed.js +1 -1
  149. package/plugins/pbr/scripts/track-context-budget.js +11 -6
  150. package/plugins/pbr/scripts/validate-commit.js +1 -1
  151. package/plugins/pbr/scripts/validate-task.js +1 -1
  152. package/plugins/cursor-pbr/agents/.gitkeep +0 -0
  153. package/plugins/cursor-pbr/references/.gitkeep +0 -0
  154. package/plugins/cursor-pbr/rules/.gitkeep +0 -0
  155. package/plugins/cursor-pbr/skills/.gitkeep +0 -0
  156. package/plugins/cursor-pbr/templates/.gitkeep +0 -0
@@ -96,7 +96,7 @@ function main() {
96
96
  });
97
97
 
98
98
  const output = {
99
- message: `${basename} warnings:\n${result.warnings.map(i => ` - ${i}`).join('\n')}`
99
+ additionalContext: `${basename} warnings:\n${result.warnings.map(i => ` - ${i}`).join('\n')}`
100
100
  };
101
101
  process.stdout.write(JSON.stringify(output));
102
102
  } else {
@@ -258,7 +258,7 @@ function checkPlanWrite(data) {
258
258
  if (result.warnings.length > 0) {
259
259
  logHook('check-plan-format', 'PostToolUse', 'warn', { file: basename, warnings: result.warnings });
260
260
  logEvent('workflow', eventType, { file: basename, status: 'warn', warningCount: result.warnings.length });
261
- return { output: { message: `${basename} warnings:\n${result.warnings.map(i => ` - ${i}`).join('\n')}` } };
261
+ return { output: { additionalContext: `${basename} warnings:\n${result.warnings.map(i => ` - ${i}`).join('\n')}` } };
262
262
  }
263
263
 
264
264
  logHook('check-plan-format', 'PostToolUse', 'pass', { file: basename });
@@ -267,4 +267,4 @@ function checkPlanWrite(data) {
267
267
  }
268
268
 
269
269
  module.exports = { validatePlan, validateSummary, checkPlanWrite };
270
- if (require.main === module) { main(); }
270
+ if (require.main === module || process.argv[1] === __filename) { main(); }
@@ -80,7 +80,7 @@ function main() {
80
80
  });
81
81
 
82
82
  const output = {
83
- message: `ROADMAP.md out of sync: Phase ${stateInfo.phase} is "${roadmapStatus}" in ROADMAP.md but "${stateInfo.status}" in STATE.md. Update the Phase Overview table in ROADMAP.md to match.`
83
+ additionalContext: `ROADMAP.md out of sync: Phase ${stateInfo.phase} is "${roadmapStatus}" in ROADMAP.md but "${stateInfo.status}" in STATE.md. Update the Phase Overview table in ROADMAP.md to match.`
84
84
  };
85
85
  process.stdout.write(JSON.stringify(output));
86
86
  } else {
@@ -238,7 +238,7 @@ function checkSync(data) {
238
238
  });
239
239
  return {
240
240
  output: {
241
- message: `ROADMAP.md out of sync: Phase ${stateInfo.phase} is "${roadmapStatus}" in ROADMAP.md but "${stateInfo.status}" in STATE.md. Update the Phase Overview table in ROADMAP.md to match.`
241
+ additionalContext: `ROADMAP.md out of sync: Phase ${stateInfo.phase} is "${roadmapStatus}" in ROADMAP.md but "${stateInfo.status}" in STATE.md. Update the Phase Overview table in ROADMAP.md to match.`
242
242
  }
243
243
  };
244
244
  }
@@ -319,4 +319,4 @@ function checkFilesystemDrift(roadmapContent, phasesDir) {
319
319
  }
320
320
 
321
321
  module.exports = { parseState, getRoadmapPhaseStatus, checkSync, parseRoadmapPhases, checkFilesystemDrift };
322
- if (require.main === module) { main(); }
322
+ if (require.main === module || process.argv[1] === __filename) { main(); }
@@ -259,4 +259,4 @@ function checkWorkflow(data) {
259
259
  }
260
260
 
261
261
  module.exports = { readActiveSkill, checkSkillRules, hasPlanFile, checkWorkflow };
262
- if (require.main === module) { main(); }
262
+ if (require.main === module || process.argv[1] === __filename) { main(); }
@@ -437,7 +437,7 @@ function checkStateSync(data) {
437
437
  const msg = `Auto-synced tracking files: ${messages.join('; ')}`;
438
438
  logHook('check-state-sync', 'PostToolUse', 'sync', { phase: phaseNum, updates: messages });
439
439
  logEvent('workflow', 'state-sync', { phase: phaseNum, trigger: isSummary ? 'summary' : 'verification', updates: messages });
440
- return { output: { message: msg } };
440
+ return { output: { additionalContext: msg } };
441
441
  }
442
442
 
443
443
  logHook('check-state-sync', 'PostToolUse', 'skip', { reason: 'no tracking files to update', phase: phaseNum });
@@ -464,7 +464,7 @@ function main() {
464
464
  });
465
465
  }
466
466
 
467
- if (require.main === module) { main(); }
467
+ if (require.main === module || process.argv[1] === __filename) { main(); }
468
468
  module.exports = {
469
469
  extractPhaseNum,
470
470
  countPhaseArtifacts,
@@ -141,4 +141,4 @@ function main() {
141
141
  }
142
142
 
143
143
  module.exports = { AGENT_OUTPUTS, findInPhaseDir };
144
- if (require.main === module) { main(); }
144
+ if (require.main === module || process.argv[1] === __filename) { main(); }
@@ -0,0 +1,198 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * PreToolUse hook (Write|Edit): Blocks STATE.md updates that advance
5
+ * phase status to "built" or "verified" unless a SUMMARY file exists
6
+ * for the current phase.
7
+ *
8
+ * This prevents the scenario where verification runs and STATE.md is
9
+ * updated but the executor never wrote a SUMMARY — leaving the project
10
+ * in an inconsistent state.
11
+ *
12
+ * Trigger: Write|Edit to STATE.md (via pre-write-dispatch.js)
13
+ *
14
+ * Logic:
15
+ * 1. Only fires when the target file is STATE.md
16
+ * 2. Reads the new content being written (from tool_input)
17
+ * 3. Extracts the phase slug and status from frontmatter
18
+ * 4. If status is advancing to "built", "verified", or "complete",
19
+ * checks that a SUMMARY-*.md file exists in the phase directory
20
+ * 5. Blocks (exit 2) if no SUMMARY found
21
+ *
22
+ * Exit codes:
23
+ * 0 = allowed or not applicable
24
+ * 2 = blocked (no SUMMARY exists for advancing phase)
25
+ */
26
+
27
+ const fs = require('fs');
28
+ const path = require('path');
29
+ const { logHook } = require('./hook-logger');
30
+
31
+ // Statuses that indicate a phase has been executed
32
+ const ADVANCED_STATUSES = ['built', 'verified', 'complete', 'building'];
33
+
34
+ /**
35
+ * Extract YAML frontmatter values from markdown content.
36
+ * Returns an object with parsed key-value pairs.
37
+ */
38
+ function parseFrontmatter(content) {
39
+ const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
40
+ if (!match) return {};
41
+ const result = {};
42
+ for (const line of match[1].split(/\r?\n/)) {
43
+ const kv = line.match(/^(\w[\w_]*):\s*"?([^"\r\n]*)"?$/);
44
+ if (kv) result[kv[1]] = kv[2].trim();
45
+ }
46
+ return result;
47
+ }
48
+
49
+ /**
50
+ * Check if a SUMMARY file exists for the given phase directory.
51
+ */
52
+ function hasSummaryFile(phaseDir) {
53
+ if (!fs.existsSync(phaseDir)) return false;
54
+ try {
55
+ const files = fs.readdirSync(phaseDir);
56
+ return files.some(f => /^SUMMARY.*\.md$/i.test(f));
57
+ } catch (_e) {
58
+ return false;
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Find the phase directory matching a slug or phase number.
64
+ */
65
+ function findPhaseDir(planningDir, phaseSlug, phaseNumber) {
66
+ const phasesDir = path.join(planningDir, 'phases');
67
+ if (!fs.existsSync(phasesDir)) return null;
68
+
69
+ try {
70
+ const dirs = fs.readdirSync(phasesDir);
71
+
72
+ // Try exact slug match first
73
+ if (phaseSlug) {
74
+ const match = dirs.find(d => d === phaseSlug);
75
+ if (match) return path.join(phasesDir, match);
76
+ }
77
+
78
+ // Fall back to phase number prefix
79
+ if (phaseNumber) {
80
+ const padded = String(phaseNumber).padStart(2, '0');
81
+ const match = dirs.find(d => d.startsWith(padded + '-'));
82
+ if (match) return path.join(phasesDir, match);
83
+ }
84
+ } catch (_e) {
85
+ // best-effort
86
+ }
87
+ return null;
88
+ }
89
+
90
+ function checkSummaryGate(data) {
91
+ const filePath = data.tool_input?.file_path || data.tool_input?.path || '';
92
+ if (!filePath) return null;
93
+
94
+ // Only check STATE.md writes
95
+ const normalizedPath = filePath.replace(/\\/g, '/');
96
+ if (!normalizedPath.endsWith('.planning/STATE.md')) return null;
97
+
98
+ // Get the new content being written
99
+ // For Write: tool_input.content
100
+ // For Edit: tool_input.new_string (partial — check for status changes)
101
+ const content = data.tool_input?.content || '';
102
+ const newString = data.tool_input?.new_string || '';
103
+ const textToCheck = content || newString;
104
+
105
+ if (!textToCheck) return null;
106
+
107
+ // Parse frontmatter from the content
108
+ const fm = parseFrontmatter(textToCheck);
109
+ const newStatus = (fm.status || '').toLowerCase();
110
+
111
+ // For Edit operations, check if the new_string contains a status advancement
112
+ let editAdvancing = false;
113
+ if (!content && newString) {
114
+ const statusMatch = newString.match(/status:\s*"?(\w+)"?/);
115
+ if (statusMatch) {
116
+ const editStatus = statusMatch[1].toLowerCase();
117
+ editAdvancing = ADVANCED_STATUSES.includes(editStatus);
118
+ }
119
+ }
120
+
121
+ // Only gate on status advancement
122
+ if (!ADVANCED_STATUSES.includes(newStatus) && !editAdvancing) return null;
123
+
124
+ // Determine phase from content
125
+ const cwd = process.cwd();
126
+ const planningDir = path.join(cwd, '.planning');
127
+ const phaseSlug = fm.phase_slug || '';
128
+ const phaseNumber = fm.current_phase || '';
129
+
130
+ // Also try to extract from body text for Edit operations
131
+ let effectiveSlug = phaseSlug;
132
+ let effectiveNumber = phaseNumber;
133
+ if (!effectiveSlug && !effectiveNumber) {
134
+ const slugMatch = textToCheck.match(/phase_slug:\s*"?([^"\r\n]+)"?/);
135
+ const numMatch = textToCheck.match(/current_phase:\s*(\d+)/);
136
+ if (slugMatch) effectiveSlug = slugMatch[1].trim();
137
+ if (numMatch) effectiveNumber = numMatch[1];
138
+ }
139
+
140
+ // For Edit operations where we only see the diff, read current STATE.md
141
+ if (!effectiveSlug && !effectiveNumber) {
142
+ try {
143
+ const currentState = fs.readFileSync(path.join(planningDir, 'STATE.md'), 'utf8');
144
+ const currentFm = parseFrontmatter(currentState);
145
+ effectiveSlug = currentFm.phase_slug || '';
146
+ effectiveNumber = currentFm.current_phase || '';
147
+ } catch (_e) {
148
+ // Can't determine phase — allow the write
149
+ return null;
150
+ }
151
+ }
152
+
153
+ const phaseDir = findPhaseDir(planningDir, effectiveSlug, effectiveNumber);
154
+ if (!phaseDir) return null; // Can't find phase dir — don't block
155
+
156
+ if (hasSummaryFile(phaseDir)) return null; // SUMMARY exists — all good
157
+
158
+ const effectiveStatus = editAdvancing
159
+ ? (newString.match(/status:\s*"?(\w+)"?/) || [])[1]
160
+ : newStatus;
161
+
162
+ logHook('check-summary-gate', 'PreToolUse', 'block', {
163
+ status: effectiveStatus,
164
+ phase: effectiveSlug || effectiveNumber,
165
+ phaseDir: path.basename(phaseDir)
166
+ });
167
+
168
+ return {
169
+ exitCode: 2,
170
+ output: {
171
+ decision: 'block',
172
+ reason: `SUMMARY gate: Cannot set status to "${effectiveStatus}" — no SUMMARY file found in ${path.basename(phaseDir)}/.\n\nThe executor must write a SUMMARY-{plan_id}.md before STATE.md can advance. This prevents inconsistent state where a phase appears complete but has no build receipt.\n\nTo fix:\n 1. Run the executor to generate the SUMMARY file\n 2. Or manually create SUMMARY-{plan_id}.md in .planning/phases/${path.basename(phaseDir)}/`
173
+ }
174
+ };
175
+ }
176
+
177
+ // Standalone mode
178
+ function main() {
179
+ let input = '';
180
+ process.stdin.setEncoding('utf8');
181
+ process.stdin.on('data', (chunk) => { input += chunk; });
182
+ process.stdin.on('end', () => {
183
+ try {
184
+ const data = JSON.parse(input);
185
+ const result = checkSummaryGate(data);
186
+ if (result) {
187
+ process.stdout.write(JSON.stringify(result.output));
188
+ process.exit(result.exitCode || 0);
189
+ }
190
+ process.exit(0);
191
+ } catch (_e) {
192
+ process.exit(0);
193
+ }
194
+ });
195
+ }
196
+
197
+ module.exports = { checkSummaryGate, parseFrontmatter, hasSummaryFile, findPhaseDir, ADVANCED_STATUSES };
198
+ if (require.main === module || process.argv[1] === __filename) { main(); }
@@ -284,4 +284,4 @@ function buildRecoveryContext(activeOp, roadmapSummary, currentPlan, configHighl
284
284
  }
285
285
 
286
286
  module.exports = { readRoadmapSummary, readCurrentPlan, readConfigHighlights, buildRecoveryContext, readRecentErrors, readRecentAgents };
287
- if (require.main === module) { main(); }
287
+ if (require.main === module || process.argv[1] === __filename) { main(); }
@@ -141,11 +141,11 @@ function main() {
141
141
  writeAutoVerifySignal(planningDir, stateInfo.phase);
142
142
 
143
143
  const output = {
144
- message: `Executor complete. Auto-verification queued for Phase ${stateInfo.phase}.`
144
+ additionalContext: `Executor complete. Auto-verification queued for Phase ${stateInfo.phase}.`
145
145
  };
146
146
  process.stdout.write(JSON.stringify(output));
147
147
  process.exit(0);
148
148
  }
149
149
 
150
150
  module.exports = { isExecutorAgent, shouldAutoVerify, getPhaseFromState };
151
- if (require.main === module) { main(); }
151
+ if (require.main === module || process.argv[1] === __filename) { main(); }
@@ -88,5 +88,5 @@ function main() {
88
88
  process.exit(0);
89
89
  }
90
90
 
91
- if (require.main === module) { main(); }
91
+ if (require.main === module || process.argv[1] === __filename) { main(); }
92
92
  module.exports = { logEvent };
@@ -161,4 +161,4 @@ function buildAgentContext() {
161
161
  }
162
162
 
163
163
  module.exports = { buildAgentContext, resolveAgentType };
164
- if (require.main === module) { main(); }
164
+ if (require.main === module || process.argv[1] === __filename) { main(); }
@@ -1374,5 +1374,5 @@ function atomicWrite(filePath, content) {
1374
1374
  }
1375
1375
  }
1376
1376
 
1377
- if (require.main === module) { main(); }
1377
+ if (require.main === module || process.argv[1] === __filename) { main(); }
1378
1378
  module.exports = { parseStateMd, parseRoadmapMd, parseYamlFrontmatter, parseMustHaves, countMustHaves, stateLoad, stateCheckProgress, configLoad, configClearCache, configValidate, lockedFileUpdate, planIndex, determinePhaseStatus, findFiles, atomicWrite, tailLines, frontmatter, mustHavesCollect, phaseInfo, stateUpdate, roadmapUpdateStatus, roadmapUpdatePlans, updateLegacyStateField, updateFrontmatterField, updateTableRow, findRoadmapRow, resolveDepthProfile, DEPTH_PROFILE_DEFAULTS, historyAppend, historyLoad, VALID_STATUS_TRANSITIONS, validateStatusTransition };
@@ -63,4 +63,4 @@ function main() {
63
63
  });
64
64
  }
65
65
 
66
- if (require.main === module) { main(); }
66
+ if (require.main === module || process.argv[1] === __filename) { main(); }
@@ -204,4 +204,4 @@ function detectConsoleLogs(filePath) {
204
204
  }
205
205
 
206
206
  module.exports = { checkQuality, loadHooksConfig, findLocalBin, runPrettier, runTypeCheck, detectConsoleLogs };
207
- if (require.main === module) { main(); }
207
+ if (require.main === module || process.argv[1] === __filename) { main(); }
@@ -83,4 +83,4 @@ function main() {
83
83
  });
84
84
  }
85
85
 
86
- if (require.main === module) { main(); }
86
+ if (require.main === module || process.argv[1] === __filename) { main(); }
@@ -15,13 +15,18 @@
15
15
  * happen at all in the current workflow state, there's no point
16
16
  * evaluating boundary or sprawl rules. Can block (exit 2).
17
17
  *
18
- * 2. check-phase-boundary Guards against writes that target files
18
+ * 2. check-summary-gate Blocks STATE.md status advancement
19
+ * (to built/verified/complete) unless a SUMMARY file exists for
20
+ * the current phase. Prevents inconsistent state where a phase
21
+ * appears complete but has no build receipt. Can block (exit 2).
22
+ *
23
+ * 3. check-phase-boundary — Guards against writes that target files
19
24
  * outside the current phase directory. Runs second because once
20
25
  * we know the write is allowed by workflow rules, we need to
21
26
  * verify it's scoped to the correct phase. Can block (exit 2)
22
27
  * or warn (exit 0 with message).
23
28
  *
24
- * 3. check-doc-sprawl — Prevents creation of new .md/.txt files
29
+ * 4. check-doc-sprawl — Prevents creation of new .md/.txt files
25
30
  * outside a known allowlist (when enabled in config). Runs last
26
31
  * because it's the most granular check — only relevant for new
27
32
  * documentation files, not all writes. Can block (exit 2).
@@ -53,6 +58,7 @@
53
58
  */
54
59
 
55
60
  const { checkWorkflow } = require('./check-skill-workflow');
61
+ const { checkSummaryGate } = require('./check-summary-gate');
56
62
  const { checkBoundary } = require('./check-phase-boundary');
57
63
  const { checkDocSprawl } = require('./check-doc-sprawl');
58
64
 
@@ -72,6 +78,13 @@ function main() {
72
78
  process.exit(workflowResult.exitCode || 0);
73
79
  }
74
80
 
81
+ // SUMMARY gate — blocks STATE.md advancement without SUMMARY
82
+ const summaryGateResult = checkSummaryGate(data);
83
+ if (summaryGateResult) {
84
+ process.stdout.write(JSON.stringify(summaryGateResult.output));
85
+ process.exit(summaryGateResult.exitCode || 0);
86
+ }
87
+
75
88
  // Phase boundary check — can block or warn
76
89
  const boundaryResult = checkBoundary(data);
77
90
  if (boundaryResult) {
@@ -94,4 +107,4 @@ function main() {
94
107
  });
95
108
  }
96
109
 
97
- if (require.main === module) { main(); }
110
+ if (require.main === module || process.argv[1] === __filename) { main(); }
@@ -251,4 +251,4 @@ function main() {
251
251
  }
252
252
 
253
253
  module.exports = { writeSessionHistory, tryRemove, cleanStaleCheckpoints, rotateHooksLog, findOrphanedProgressFiles };
254
- if (require.main === module) { main(); }
254
+ if (require.main === module || process.argv[1] === __filename) { main(); }
@@ -284,5 +284,5 @@ function buildStatusLine(content, ctxPercent, cfg, stdinData) {
284
284
  return parts.join(` ${c.dim}\u2502${c.reset} `);
285
285
  }
286
286
 
287
- if (require.main === module) { main(); }
287
+ if (require.main === module || process.argv[1] === __filename) { main(); }
288
288
  module.exports = { buildStatusLine, buildContextBar, getContextPercent, getGitInfo, formatDuration, loadStatusLineConfig, DEFAULTS };
@@ -116,4 +116,4 @@ function resetCounter(planningDir) {
116
116
  }
117
117
 
118
118
  module.exports = { checkCompaction, loadCounter, saveCounter, getThreshold, resetCounter, DEFAULT_THRESHOLD, REMINDER_INTERVAL };
119
- if (require.main === module) { main(); }
119
+ if (require.main === module || process.argv[1] === __filename) { main(); }
@@ -41,5 +41,5 @@ function main() {
41
41
  process.exit(0);
42
42
  }
43
43
 
44
- if (require.main === module) { main(); }
44
+ if (require.main === module || process.argv[1] === __filename) { main(); }
45
45
  module.exports = { main };
@@ -52,9 +52,11 @@ function main() {
52
52
  }
53
53
  }
54
54
 
55
- // Estimate chars read (use limit if provided, otherwise assume ~2000 lines × 40 chars avg)
55
+ // Estimate chars read from actual output or limit, with a conservative default.
56
+ // Previous default of 80k (2000 lines × 40 chars) caused every read to cross
57
+ // the 50k milestone, flooding logs with warnings on every single Read call.
56
58
  const limit = data.tool_input?.limit;
57
- const estimatedChars = limit ? limit * 40 : 80000;
59
+ const estimatedChars = limit ? limit * 40 : 8000;
58
60
  // Use actual output length if available
59
61
  const actualChars = data.tool_output ? String(data.tool_output).length : estimatedChars;
60
62
 
@@ -65,7 +67,7 @@ function main() {
65
67
  const currentSkill = readFileSafe(skillPath);
66
68
  let tracker = loadTracker(trackerPath);
67
69
 
68
- if (tracker.skill !== currentSkill) {
70
+ if (tracker.skill !== currentSkill || tracker.files.length > 200) {
69
71
  tracker = { skill: currentSkill, reads: 0, total_chars: 0, files: [] };
70
72
  }
71
73
 
@@ -77,11 +79,14 @@ function main() {
77
79
  tracker.files.push(filePath);
78
80
  }
79
81
 
80
- // Save tracker
82
+ // Save tracker (atomic write to avoid corruption from concurrent hooks)
81
83
  try {
82
- fs.writeFileSync(trackerPath, JSON.stringify(tracker), 'utf8');
84
+ const tmpPath = trackerPath + '.' + process.pid;
85
+ fs.writeFileSync(tmpPath, JSON.stringify(tracker), 'utf8');
86
+ fs.renameSync(tmpPath, trackerPath);
83
87
  } catch (_e) {
84
- // Best-effort
88
+ // Best-effort — clean up temp file if rename failed
89
+ try { fs.unlinkSync(trackerPath + '.' + process.pid); } catch (_e2) { /* best-effort cleanup */ }
85
90
  }
86
91
 
87
92
  // Check thresholds — only warn at milestone crossings, not every read
@@ -197,4 +197,4 @@ function extractCommitMessage(command) {
197
197
  }
198
198
 
199
199
  module.exports = { checkCommit };
200
- if (require.main === module) { main(); }
200
+ if (require.main === module || process.argv[1] === __filename) { main(); }
@@ -103,4 +103,4 @@ function main() {
103
103
  }
104
104
 
105
105
  module.exports = { checkTask, KNOWN_AGENTS, MAX_DESCRIPTION_LENGTH };
106
- if (require.main === module) { main(); }
106
+ if (require.main === module || process.argv[1] === __filename) { main(); }
File without changes
File without changes
File without changes
File without changes
File without changes