@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.
- package/dashboard/src/routes/pages.routes.js +11 -4
- package/dashboard/src/services/dashboard.service.js +81 -17
- package/dashboard/src/services/phase.service.js +30 -24
- package/dashboard/src/services/roadmap.service.js +3 -3
- package/dashboard/src/views/partials/phase-content.ejs +5 -4
- package/package.json +1 -1
- package/plugins/cursor-pbr/.cursor-plugin/plugin.json +1 -1
- package/plugins/cursor-pbr/CHANGELOG.md +15 -0
- package/plugins/cursor-pbr/README.md +118 -0
- package/plugins/cursor-pbr/agents/codebase-mapper.md +108 -0
- package/plugins/cursor-pbr/agents/debugger.md +168 -0
- package/plugins/cursor-pbr/agents/executor.md +236 -0
- package/plugins/cursor-pbr/agents/general.md +87 -0
- package/plugins/cursor-pbr/agents/integration-checker.md +87 -0
- package/plugins/cursor-pbr/agents/plan-checker.md +198 -0
- package/plugins/cursor-pbr/agents/planner.md +180 -0
- package/plugins/cursor-pbr/agents/researcher.md +162 -0
- package/plugins/cursor-pbr/agents/synthesizer.md +101 -0
- package/plugins/cursor-pbr/agents/verifier.md +193 -0
- package/plugins/cursor-pbr/assets/logo.svg +21 -0
- package/plugins/cursor-pbr/hooks/hooks.json +189 -7
- package/plugins/cursor-pbr/references/agent-anti-patterns.md +25 -0
- package/plugins/cursor-pbr/references/agent-interactions.md +135 -0
- package/plugins/cursor-pbr/references/agent-teams.md +55 -0
- package/plugins/cursor-pbr/references/checkpoints.md +158 -0
- package/plugins/cursor-pbr/references/common-bug-patterns.md +14 -0
- package/plugins/cursor-pbr/references/config-reference.md +442 -0
- package/plugins/cursor-pbr/references/continuation-format.md +213 -0
- package/plugins/cursor-pbr/references/deviation-rules.md +113 -0
- package/plugins/cursor-pbr/references/git-integration.md +227 -0
- package/plugins/cursor-pbr/references/integration-patterns.md +118 -0
- package/plugins/cursor-pbr/references/model-profiles.md +100 -0
- package/plugins/cursor-pbr/references/model-selection.md +32 -0
- package/plugins/cursor-pbr/references/pbr-rules.md +194 -0
- package/plugins/cursor-pbr/references/plan-authoring.md +182 -0
- package/plugins/cursor-pbr/references/plan-format.md +288 -0
- package/plugins/cursor-pbr/references/planning-config.md +214 -0
- package/plugins/cursor-pbr/references/questioning.md +215 -0
- package/plugins/cursor-pbr/references/reading-verification.md +128 -0
- package/plugins/cursor-pbr/references/stub-patterns.md +161 -0
- package/plugins/cursor-pbr/references/subagent-coordination.md +120 -0
- package/plugins/cursor-pbr/references/ui-formatting.md +462 -0
- package/plugins/cursor-pbr/references/verification-patterns.md +199 -0
- package/plugins/cursor-pbr/references/wave-execution.md +96 -0
- package/plugins/cursor-pbr/rules/pbr-workflow.mdc +48 -0
- package/plugins/cursor-pbr/setup.ps1 +78 -0
- package/plugins/cursor-pbr/setup.sh +83 -0
- package/plugins/cursor-pbr/skills/begin/SKILL.md +566 -0
- package/plugins/cursor-pbr/skills/begin/templates/PROJECT.md.tmpl +34 -0
- package/plugins/cursor-pbr/skills/begin/templates/REQUIREMENTS.md.tmpl +19 -0
- package/plugins/cursor-pbr/skills/begin/templates/STATE.md.tmpl +50 -0
- package/plugins/cursor-pbr/skills/begin/templates/config.json.tmpl +64 -0
- package/plugins/cursor-pbr/skills/begin/templates/researcher-prompt.md.tmpl +20 -0
- package/plugins/cursor-pbr/skills/begin/templates/roadmap-prompt.md.tmpl +31 -0
- package/plugins/cursor-pbr/skills/begin/templates/synthesis-prompt.md.tmpl +17 -0
- package/plugins/cursor-pbr/skills/build/SKILL.md +902 -0
- package/plugins/cursor-pbr/skills/config/SKILL.md +253 -0
- package/plugins/cursor-pbr/skills/continue/SKILL.md +159 -0
- package/plugins/cursor-pbr/skills/debug/SKILL.md +512 -0
- package/plugins/cursor-pbr/skills/debug/templates/continuation-prompt.md.tmpl +17 -0
- package/plugins/cursor-pbr/skills/debug/templates/initial-investigation-prompt.md.tmpl +28 -0
- package/plugins/cursor-pbr/skills/discuss/SKILL.md +344 -0
- package/plugins/cursor-pbr/skills/discuss/templates/CONTEXT.md.tmpl +62 -0
- package/plugins/cursor-pbr/skills/discuss/templates/decision-categories.md +10 -0
- package/plugins/cursor-pbr/skills/explore/SKILL.md +375 -0
- package/plugins/cursor-pbr/skills/health/SKILL.md +218 -0
- package/plugins/cursor-pbr/skills/health/templates/check-pattern.md.tmpl +31 -0
- package/plugins/cursor-pbr/skills/health/templates/output-format.md.tmpl +64 -0
- package/plugins/cursor-pbr/skills/help/SKILL.md +152 -0
- package/plugins/cursor-pbr/skills/import/SKILL.md +499 -0
- package/plugins/cursor-pbr/skills/milestone/SKILL.md +701 -0
- package/plugins/cursor-pbr/skills/milestone/templates/audit-report.md.tmpl +49 -0
- package/plugins/cursor-pbr/skills/milestone/templates/stats-file.md.tmpl +31 -0
- package/plugins/cursor-pbr/skills/note/SKILL.md +228 -0
- package/plugins/cursor-pbr/skills/pause/SKILL.md +246 -0
- package/plugins/cursor-pbr/skills/pause/templates/continue-here.md.tmpl +72 -0
- package/plugins/cursor-pbr/skills/plan/SKILL.md +648 -0
- package/plugins/cursor-pbr/skills/plan/templates/checker-prompt.md.tmpl +22 -0
- package/plugins/cursor-pbr/skills/plan/templates/gap-closure-prompt.md.tmpl +33 -0
- package/plugins/cursor-pbr/skills/plan/templates/planner-prompt.md.tmpl +39 -0
- package/plugins/cursor-pbr/skills/plan/templates/researcher-prompt.md.tmpl +20 -0
- package/plugins/cursor-pbr/skills/plan/templates/revision-prompt.md.tmpl +24 -0
- package/plugins/cursor-pbr/skills/quick/SKILL.md +351 -0
- package/plugins/cursor-pbr/skills/resume/SKILL.md +399 -0
- package/plugins/cursor-pbr/skills/review/SKILL.md +649 -0
- package/plugins/cursor-pbr/skills/review/templates/debugger-prompt.md.tmpl +61 -0
- package/plugins/cursor-pbr/skills/review/templates/gap-planner-prompt.md.tmpl +41 -0
- package/plugins/cursor-pbr/skills/review/templates/verifier-prompt.md.tmpl +116 -0
- package/plugins/cursor-pbr/skills/scan/SKILL.md +301 -0
- package/plugins/cursor-pbr/skills/scan/templates/mapper-prompt.md.tmpl +202 -0
- package/plugins/cursor-pbr/skills/setup/SKILL.md +250 -0
- package/plugins/cursor-pbr/skills/shared/commit-planning-docs.md +36 -0
- package/plugins/cursor-pbr/skills/shared/config-loading.md +103 -0
- package/plugins/cursor-pbr/skills/shared/context-budget.md +41 -0
- package/plugins/cursor-pbr/skills/shared/context-loader-task.md +87 -0
- package/plugins/cursor-pbr/skills/shared/digest-select.md +80 -0
- package/plugins/cursor-pbr/skills/shared/domain-probes.md +126 -0
- package/plugins/cursor-pbr/skills/shared/error-reporting.md +80 -0
- package/plugins/cursor-pbr/skills/shared/gate-prompts.md +389 -0
- package/plugins/cursor-pbr/skills/shared/phase-argument-parsing.md +46 -0
- package/plugins/cursor-pbr/skills/shared/progress-display.md +54 -0
- package/plugins/cursor-pbr/skills/shared/revision-loop.md +82 -0
- package/plugins/cursor-pbr/skills/shared/state-loading.md +63 -0
- package/plugins/cursor-pbr/skills/shared/state-update.md +162 -0
- package/plugins/cursor-pbr/skills/shared/universal-anti-patterns.md +34 -0
- package/plugins/cursor-pbr/skills/status/SKILL.md +362 -0
- package/plugins/cursor-pbr/skills/todo/SKILL.md +195 -0
- package/plugins/cursor-pbr/templates/CONTEXT.md.tmpl +53 -0
- package/plugins/cursor-pbr/templates/INTEGRATION-REPORT.md.tmpl +152 -0
- package/plugins/cursor-pbr/templates/RESEARCH-SUMMARY.md.tmpl +98 -0
- package/plugins/cursor-pbr/templates/ROADMAP.md.tmpl +41 -0
- package/plugins/cursor-pbr/templates/SUMMARY.md.tmpl +82 -0
- package/plugins/cursor-pbr/templates/VERIFICATION-DETAIL.md.tmpl +117 -0
- package/plugins/cursor-pbr/templates/continue-here.md.tmpl +74 -0
- package/plugins/cursor-pbr/templates/prompt-partials/phase-project-context.md.tmpl +38 -0
- package/plugins/pbr/agents/codebase-mapper.md +41 -206
- package/plugins/pbr/agents/debugger.md +65 -171
- package/plugins/pbr/agents/executor.md +90 -275
- package/plugins/pbr/agents/general.md +27 -97
- package/plugins/pbr/agents/integration-checker.md +35 -112
- package/plugins/pbr/agents/plan-checker.md +71 -164
- package/plugins/pbr/agents/planner.md +75 -246
- package/plugins/pbr/agents/researcher.md +63 -255
- package/plugins/pbr/agents/synthesizer.md +49 -174
- package/plugins/pbr/agents/verifier.md +75 -366
- package/plugins/pbr/hooks/hooks.json +14 -10
- package/plugins/pbr/scripts/auto-continue.js +20 -4
- package/plugins/pbr/scripts/check-dangerous-commands.js +1 -1
- package/plugins/pbr/scripts/check-phase-boundary.js +1 -1
- package/plugins/pbr/scripts/check-plan-format.js +3 -3
- package/plugins/pbr/scripts/check-roadmap-sync.js +3 -3
- package/plugins/pbr/scripts/check-skill-workflow.js +1 -1
- package/plugins/pbr/scripts/check-state-sync.js +2 -2
- package/plugins/pbr/scripts/check-subagent-output.js +1 -1
- package/plugins/pbr/scripts/check-summary-gate.js +198 -0
- package/plugins/pbr/scripts/context-budget-check.js +1 -1
- package/plugins/pbr/scripts/event-handler.js +2 -2
- package/plugins/pbr/scripts/event-logger.js +1 -1
- package/plugins/pbr/scripts/log-subagent.js +1 -1
- package/plugins/pbr/scripts/pbr-tools.js +1 -1
- package/plugins/pbr/scripts/post-write-dispatch.js +1 -1
- package/plugins/pbr/scripts/post-write-quality.js +1 -1
- package/plugins/pbr/scripts/pre-bash-dispatch.js +1 -1
- package/plugins/pbr/scripts/pre-write-dispatch.js +16 -3
- package/plugins/pbr/scripts/session-cleanup.js +1 -1
- package/plugins/pbr/scripts/status-line.js +1 -1
- package/plugins/pbr/scripts/suggest-compact.js +1 -1
- package/plugins/pbr/scripts/task-completed.js +1 -1
- package/plugins/pbr/scripts/track-context-budget.js +11 -6
- package/plugins/pbr/scripts/validate-commit.js +1 -1
- package/plugins/pbr/scripts/validate-task.js +1 -1
- package/plugins/cursor-pbr/agents/.gitkeep +0 -0
- package/plugins/cursor-pbr/references/.gitkeep +0 -0
- package/plugins/cursor-pbr/rules/.gitkeep +0 -0
- package/plugins/cursor-pbr/skills/.gitkeep +0 -0
- package/plugins/cursor-pbr/templates/.gitkeep +0 -0
|
@@ -96,7 +96,7 @@ function main() {
|
|
|
96
96
|
});
|
|
97
97
|
|
|
98
98
|
const output = {
|
|
99
|
-
|
|
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: {
|
|
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
|
-
|
|
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
|
-
|
|
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(); }
|
|
@@ -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: {
|
|
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,
|
|
@@ -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
|
-
|
|
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(); }
|
|
@@ -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 };
|
|
@@ -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(); }
|
|
@@ -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-
|
|
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
|
-
*
|
|
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(); }
|
|
@@ -52,9 +52,11 @@ function main() {
|
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
// Estimate chars read
|
|
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 :
|
|
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
|
-
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|