@sienklogic/plan-build-run 2.0.0 → 2.0.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.
- package/CHANGELOG.md +56 -56
- package/CLAUDE.md +149 -149
- package/LICENSE +21 -21
- package/README.md +247 -247
- package/dashboard/bin/cli.js +25 -25
- package/dashboard/package.json +34 -34
- package/dashboard/public/css/layout.css +406 -406
- package/dashboard/public/css/status-colors.css +98 -98
- package/dashboard/public/js/htmx-title.js +5 -5
- package/dashboard/public/js/sidebar-toggle.js +20 -20
- package/dashboard/src/app.js +78 -78
- package/dashboard/src/middleware/errorHandler.js +52 -52
- package/dashboard/src/middleware/notFoundHandler.js +9 -9
- package/dashboard/src/repositories/planning.repository.js +128 -128
- package/dashboard/src/routes/events.routes.js +40 -40
- package/dashboard/src/routes/index.routes.js +31 -31
- package/dashboard/src/routes/pages.routes.js +245 -195
- package/dashboard/src/server.js +42 -42
- package/dashboard/src/services/dashboard.service.js +222 -222
- package/dashboard/src/services/phase.service.js +220 -167
- package/dashboard/src/services/project.service.js +57 -57
- package/dashboard/src/services/roadmap.service.js +171 -171
- package/dashboard/src/services/sse.service.js +58 -58
- package/dashboard/src/services/todo.service.js +254 -254
- package/dashboard/src/services/watcher.service.js +48 -48
- package/dashboard/src/views/coming-soon.ejs +11 -11
- package/dashboard/src/views/error.ejs +13 -13
- package/dashboard/src/views/index.ejs +5 -5
- package/dashboard/src/views/layout.ejs +1 -1
- package/dashboard/src/views/partials/dashboard-content.ejs +77 -77
- package/dashboard/src/views/partials/footer.ejs +3 -3
- package/dashboard/src/views/partials/head.ejs +21 -21
- package/dashboard/src/views/partials/header.ejs +12 -12
- package/dashboard/src/views/partials/layout-bottom.ejs +15 -15
- package/dashboard/src/views/partials/layout-top.ejs +8 -8
- package/dashboard/src/views/partials/phase-content.ejs +188 -181
- package/dashboard/src/views/partials/phase-doc-content.ejs +38 -0
- package/dashboard/src/views/partials/phases-content.ejs +117 -117
- package/dashboard/src/views/partials/roadmap-content.ejs +142 -142
- package/dashboard/src/views/partials/sidebar.ejs +38 -38
- package/dashboard/src/views/partials/todo-create-content.ejs +53 -53
- package/dashboard/src/views/partials/todo-detail-content.ejs +38 -38
- package/dashboard/src/views/partials/todos-content.ejs +53 -53
- package/dashboard/src/views/phase-detail.ejs +5 -5
- package/dashboard/src/views/phase-doc.ejs +5 -0
- package/dashboard/src/views/phases.ejs +5 -5
- package/dashboard/src/views/roadmap.ejs +5 -5
- package/dashboard/src/views/todo-create.ejs +5 -5
- package/dashboard/src/views/todo-detail.ejs +5 -5
- package/dashboard/src/views/todos.ejs +5 -5
- package/package.json +57 -57
- package/plugins/pbr/.claude-plugin/plugin.json +13 -13
- package/plugins/pbr/UI-CONSISTENCY-GAPS.md +61 -61
- package/plugins/pbr/agents/codebase-mapper.md +279 -271
- package/plugins/pbr/agents/debugger.md +281 -281
- package/plugins/pbr/agents/executor.md +428 -407
- package/plugins/pbr/agents/general.md +164 -164
- package/plugins/pbr/agents/integration-checker.md +169 -141
- package/plugins/pbr/agents/plan-checker.md +296 -280
- package/plugins/pbr/agents/planner.md +358 -358
- package/plugins/pbr/agents/researcher.md +363 -363
- package/plugins/pbr/agents/synthesizer.md +230 -230
- package/plugins/pbr/agents/verifier.md +489 -454
- package/plugins/pbr/commands/begin.md +5 -5
- package/plugins/pbr/commands/build.md +5 -5
- package/plugins/pbr/commands/config.md +5 -5
- package/plugins/pbr/commands/continue.md +5 -5
- package/plugins/pbr/commands/debug.md +5 -5
- package/plugins/pbr/commands/discuss.md +5 -5
- package/plugins/pbr/commands/explore.md +5 -5
- package/plugins/pbr/commands/health.md +5 -5
- package/plugins/pbr/commands/help.md +5 -5
- package/plugins/pbr/commands/import.md +5 -5
- package/plugins/pbr/commands/milestone.md +5 -5
- package/plugins/pbr/commands/note.md +5 -5
- package/plugins/pbr/commands/pause.md +5 -5
- package/plugins/pbr/commands/plan.md +5 -5
- package/plugins/pbr/commands/quick.md +5 -5
- package/plugins/pbr/commands/resume.md +5 -5
- package/plugins/pbr/commands/review.md +5 -5
- package/plugins/pbr/commands/scan.md +5 -5
- package/plugins/pbr/commands/setup.md +5 -5
- package/plugins/pbr/commands/status.md +5 -5
- package/plugins/pbr/commands/todo.md +5 -5
- package/plugins/pbr/contexts/dev.md +27 -27
- package/plugins/pbr/contexts/research.md +28 -28
- package/plugins/pbr/contexts/review.md +36 -36
- package/plugins/pbr/hooks/hooks.json +183 -183
- package/plugins/pbr/references/agent-anti-patterns.md +24 -24
- package/plugins/pbr/references/agent-interactions.md +134 -134
- package/plugins/pbr/references/agent-teams.md +54 -54
- package/plugins/pbr/references/checkpoints.md +157 -157
- package/plugins/pbr/references/common-bug-patterns.md +13 -13
- package/plugins/pbr/references/config-reference.md +441 -0
- package/plugins/pbr/references/continuation-format.md +212 -212
- package/plugins/pbr/references/deviation-rules.md +112 -112
- package/plugins/pbr/references/git-integration.md +226 -226
- package/plugins/pbr/references/integration-patterns.md +117 -117
- package/plugins/pbr/references/model-profiles.md +99 -99
- package/plugins/pbr/references/model-selection.md +31 -31
- package/plugins/pbr/references/pbr-rules.md +193 -193
- package/plugins/pbr/references/plan-authoring.md +181 -181
- package/plugins/pbr/references/plan-format.md +287 -283
- package/plugins/pbr/references/planning-config.md +213 -213
- package/plugins/pbr/references/questioning.md +214 -214
- package/plugins/pbr/references/reading-verification.md +127 -127
- package/plugins/pbr/references/stub-patterns.md +160 -160
- package/plugins/pbr/references/subagent-coordination.md +119 -119
- package/plugins/pbr/references/ui-formatting.md +461 -399
- package/plugins/pbr/references/verification-patterns.md +198 -198
- package/plugins/pbr/references/wave-execution.md +95 -95
- package/plugins/pbr/scripts/auto-continue.js +80 -80
- package/plugins/pbr/scripts/check-dangerous-commands.js +136 -136
- package/plugins/pbr/scripts/check-doc-sprawl.js +102 -102
- package/plugins/pbr/scripts/check-phase-boundary.js +196 -196
- package/plugins/pbr/scripts/check-plan-format.js +270 -270
- package/plugins/pbr/scripts/check-roadmap-sync.js +322 -252
- package/plugins/pbr/scripts/check-skill-workflow.js +262 -262
- package/plugins/pbr/scripts/check-state-sync.js +476 -476
- package/plugins/pbr/scripts/check-subagent-output.js +144 -144
- package/plugins/pbr/scripts/config-schema.json +251 -251
- package/plugins/pbr/scripts/context-budget-check.js +287 -287
- package/plugins/pbr/scripts/event-handler.js +151 -151
- package/plugins/pbr/scripts/event-logger.js +92 -92
- package/plugins/pbr/scripts/hook-logger.js +80 -76
- package/plugins/pbr/scripts/hooks-schema.json +79 -79
- package/plugins/pbr/scripts/log-subagent.js +164 -152
- package/plugins/pbr/scripts/log-tool-failure.js +88 -88
- package/plugins/pbr/scripts/pbr-tools.js +1378 -1301
- package/plugins/pbr/scripts/post-write-dispatch.js +66 -66
- package/plugins/pbr/scripts/post-write-quality.js +207 -207
- package/plugins/pbr/scripts/pre-bash-dispatch.js +86 -56
- package/plugins/pbr/scripts/pre-write-dispatch.js +97 -62
- package/plugins/pbr/scripts/progress-tracker.js +281 -228
- package/plugins/pbr/scripts/run-hook.js +92 -0
- package/plugins/pbr/scripts/session-cleanup.js +254 -254
- package/plugins/pbr/scripts/status-line.js +288 -285
- package/plugins/pbr/scripts/suggest-compact.js +119 -119
- package/plugins/pbr/scripts/task-completed.js +45 -45
- package/plugins/pbr/scripts/track-context-budget.js +149 -119
- package/plugins/pbr/scripts/validate-commit.js +200 -200
- package/plugins/pbr/scripts/validate-plugin-structure.js +183 -172
- package/plugins/pbr/scripts/validate-task.js +106 -0
- package/plugins/pbr/skills/begin/SKILL.md +594 -545
- package/plugins/pbr/skills/begin/templates/PROJECT.md.tmpl +33 -33
- package/plugins/pbr/skills/begin/templates/REQUIREMENTS.md.tmpl +18 -18
- package/plugins/pbr/skills/begin/templates/STATE.md.tmpl +49 -49
- package/plugins/pbr/skills/begin/templates/config.json.tmpl +64 -63
- package/plugins/pbr/skills/begin/templates/researcher-prompt.md.tmpl +19 -19
- package/plugins/pbr/skills/begin/templates/roadmap-prompt.md.tmpl +30 -30
- package/plugins/pbr/skills/begin/templates/synthesis-prompt.md.tmpl +16 -16
- package/plugins/pbr/skills/build/SKILL.md +943 -962
- package/plugins/pbr/skills/config/SKILL.md +256 -241
- package/plugins/pbr/skills/continue/SKILL.md +164 -127
- package/plugins/pbr/skills/debug/SKILL.md +515 -489
- package/plugins/pbr/skills/debug/templates/continuation-prompt.md.tmpl +16 -16
- package/plugins/pbr/skills/debug/templates/initial-investigation-prompt.md.tmpl +27 -27
- package/plugins/pbr/skills/discuss/SKILL.md +347 -338
- package/plugins/pbr/skills/discuss/templates/CONTEXT.md.tmpl +61 -61
- package/plugins/pbr/skills/discuss/templates/decision-categories.md +9 -9
- package/plugins/pbr/skills/explore/SKILL.md +378 -362
- package/plugins/pbr/skills/health/SKILL.md +221 -186
- package/plugins/pbr/skills/health/templates/check-pattern.md.tmpl +30 -30
- package/plugins/pbr/skills/health/templates/output-format.md.tmpl +63 -63
- package/plugins/pbr/skills/help/SKILL.md +155 -140
- package/plugins/pbr/skills/import/SKILL.md +504 -490
- package/plugins/pbr/skills/milestone/SKILL.md +704 -673
- package/plugins/pbr/skills/milestone/templates/audit-report.md.tmpl +48 -48
- package/plugins/pbr/skills/milestone/templates/stats-file.md.tmpl +30 -30
- package/plugins/pbr/skills/note/SKILL.md +231 -212
- package/plugins/pbr/skills/pause/SKILL.md +249 -235
- package/plugins/pbr/skills/pause/templates/continue-here.md.tmpl +71 -71
- package/plugins/pbr/skills/plan/SKILL.md +685 -628
- package/plugins/pbr/skills/plan/decimal-phase-calc.md +98 -98
- package/plugins/pbr/skills/plan/templates/checker-prompt.md.tmpl +21 -21
- package/plugins/pbr/skills/plan/templates/gap-closure-prompt.md.tmpl +32 -32
- package/plugins/pbr/skills/plan/templates/planner-prompt.md.tmpl +38 -38
- package/plugins/pbr/skills/plan/templates/researcher-prompt.md.tmpl +19 -19
- package/plugins/pbr/skills/plan/templates/revision-prompt.md.tmpl +23 -23
- package/plugins/pbr/skills/quick/SKILL.md +354 -335
- package/plugins/pbr/skills/resume/SKILL.md +402 -388
- package/plugins/pbr/skills/review/SKILL.md +686 -652
- package/plugins/pbr/skills/review/templates/debugger-prompt.md.tmpl +60 -60
- package/plugins/pbr/skills/review/templates/gap-planner-prompt.md.tmpl +40 -40
- package/plugins/pbr/skills/review/templates/verifier-prompt.md.tmpl +115 -115
- package/plugins/pbr/skills/scan/SKILL.md +304 -269
- package/plugins/pbr/skills/scan/templates/mapper-prompt.md.tmpl +201 -201
- package/plugins/pbr/skills/setup/SKILL.md +253 -227
- package/plugins/pbr/skills/shared/commit-planning-docs.md +35 -35
- package/plugins/pbr/skills/shared/config-loading.md +102 -102
- package/plugins/pbr/skills/shared/context-budget.md +40 -40
- package/plugins/pbr/skills/shared/context-loader-task.md +86 -86
- package/plugins/pbr/skills/shared/digest-select.md +79 -79
- package/plugins/pbr/skills/shared/domain-probes.md +125 -125
- package/plugins/pbr/skills/shared/error-reporting.md +79 -79
- package/plugins/pbr/skills/shared/gate-prompts.md +388 -388
- package/plugins/pbr/skills/shared/phase-argument-parsing.md +45 -45
- package/plugins/pbr/skills/shared/progress-display.md +53 -53
- package/plugins/pbr/skills/shared/revision-loop.md +81 -81
- package/plugins/pbr/skills/shared/state-loading.md +62 -62
- package/plugins/pbr/skills/shared/state-update.md +161 -161
- package/plugins/pbr/skills/shared/universal-anti-patterns.md +33 -33
- package/plugins/pbr/skills/status/SKILL.md +367 -353
- package/plugins/pbr/skills/todo/SKILL.md +198 -181
- package/plugins/pbr/templates/CONTEXT.md.tmpl +52 -52
- package/plugins/pbr/templates/INTEGRATION-REPORT.md.tmpl +151 -151
- package/plugins/pbr/templates/RESEARCH-SUMMARY.md.tmpl +97 -97
- package/plugins/pbr/templates/ROADMAP.md.tmpl +40 -40
- package/plugins/pbr/templates/SUMMARY.md.tmpl +81 -81
- package/plugins/pbr/templates/VERIFICATION-DETAIL.md.tmpl +116 -116
- package/plugins/pbr/templates/codebase/ARCHITECTURE.md.tmpl +98 -98
- package/plugins/pbr/templates/codebase/CONCERNS.md.tmpl +93 -93
- package/plugins/pbr/templates/codebase/CONVENTIONS.md.tmpl +104 -104
- package/plugins/pbr/templates/codebase/INTEGRATIONS.md.tmpl +78 -78
- package/plugins/pbr/templates/codebase/STACK.md.tmpl +78 -78
- package/plugins/pbr/templates/codebase/STRUCTURE.md.tmpl +80 -80
- package/plugins/pbr/templates/codebase/TESTING.md.tmpl +107 -107
- package/plugins/pbr/templates/continue-here.md.tmpl +73 -73
- package/plugins/pbr/templates/prompt-partials/phase-project-context.md.tmpl +37 -37
- package/plugins/pbr/templates/research/ARCHITECTURE.md.tmpl +124 -124
- package/plugins/pbr/templates/research/STACK.md.tmpl +71 -71
- package/plugins/pbr/templates/research/SUMMARY.md.tmpl +112 -112
- package/plugins/pbr/templates/research-outputs/phase-research.md.tmpl +81 -81
- package/plugins/pbr/templates/research-outputs/project-research.md.tmpl +99 -99
- package/plugins/pbr/templates/research-outputs/synthesis.md.tmpl +36 -36
|
@@ -1,287 +1,287 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* PreCompact hook: Preserves current state to STATE.md before
|
|
5
|
-
* lossy context compaction.
|
|
6
|
-
*
|
|
7
|
-
* Updates STATE.md with:
|
|
8
|
-
* - Timestamp of last compaction
|
|
9
|
-
* - ROADMAP progress summary (phase list, status)
|
|
10
|
-
* - Current plan context (objective from latest PLAN.md)
|
|
11
|
-
* - Config highlights (depth, mode, models, gates)
|
|
12
|
-
* - Active operation context
|
|
13
|
-
*
|
|
14
|
-
* Also outputs additionalContext for post-compaction recovery.
|
|
15
|
-
*
|
|
16
|
-
* Exit codes:
|
|
17
|
-
* 0 = always (informational hook, never blocks)
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
const fs = require('fs');
|
|
21
|
-
const path = require('path');
|
|
22
|
-
const { logHook } = require('./hook-logger');
|
|
23
|
-
const { logEvent } = require('./event-logger');
|
|
24
|
-
const { atomicWrite, configLoad, tailLines } = require('./pbr-tools');
|
|
25
|
-
|
|
26
|
-
function main() {
|
|
27
|
-
const cwd = process.cwd();
|
|
28
|
-
const planningDir = path.join(cwd, '.planning');
|
|
29
|
-
const stateFile = path.join(planningDir, 'STATE.md');
|
|
30
|
-
|
|
31
|
-
// Not a Plan-Build-Run project or no STATE.md
|
|
32
|
-
if (!fs.existsSync(stateFile)) {
|
|
33
|
-
process.exit(0);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
try {
|
|
37
|
-
let content = fs.readFileSync(stateFile, 'utf8');
|
|
38
|
-
const timestamp = new Date().toISOString();
|
|
39
|
-
|
|
40
|
-
// Gather context from multiple sources
|
|
41
|
-
const activeOp = readActiveOperation(planningDir);
|
|
42
|
-
const roadmapSummary = readRoadmapSummary(planningDir);
|
|
43
|
-
const currentPlan = readCurrentPlan(planningDir, content);
|
|
44
|
-
const configHighlights = readConfigHighlights(planningDir);
|
|
45
|
-
const recentErrors = readRecentErrors(planningDir, 3);
|
|
46
|
-
const recentAgents = readRecentAgents(planningDir, 5);
|
|
47
|
-
|
|
48
|
-
// Build continuity section
|
|
49
|
-
const continuityParts = [
|
|
50
|
-
`Last session: ${timestamp}`,
|
|
51
|
-
'Compaction occurred: context was auto-compacted at this point'
|
|
52
|
-
];
|
|
53
|
-
|
|
54
|
-
if (activeOp) {
|
|
55
|
-
continuityParts.push(`Active operation at compaction: ${activeOp}`);
|
|
56
|
-
}
|
|
57
|
-
if (roadmapSummary) {
|
|
58
|
-
continuityParts.push(`Roadmap progress:\n${roadmapSummary}`);
|
|
59
|
-
}
|
|
60
|
-
if (currentPlan) {
|
|
61
|
-
continuityParts.push(`Current plan: ${currentPlan}`);
|
|
62
|
-
}
|
|
63
|
-
if (configHighlights) {
|
|
64
|
-
continuityParts.push(`Config: ${configHighlights}`);
|
|
65
|
-
}
|
|
66
|
-
if (recentErrors.length > 0) {
|
|
67
|
-
continuityParts.push(`Recent errors:\n${recentErrors.map(e => ' - ' + e).join('\n')}`);
|
|
68
|
-
}
|
|
69
|
-
if (recentAgents.length > 0) {
|
|
70
|
-
continuityParts.push(`Recent agents: ${recentAgents.join(', ')}`);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
continuityParts.push('Note: Some conversation context may have been lost. Check STATE.md and SUMMARY.md files for ground truth.');
|
|
74
|
-
|
|
75
|
-
// Update or add Session Continuity section
|
|
76
|
-
const continuityHeader = '## Session Continuity';
|
|
77
|
-
const continuityContent = continuityParts.join('\n');
|
|
78
|
-
|
|
79
|
-
if (content.includes(continuityHeader)) {
|
|
80
|
-
// Replace existing section
|
|
81
|
-
content = content.replace(
|
|
82
|
-
/## Session Continuity[\s\S]*?(?=\n## |\n---|\s*$)/,
|
|
83
|
-
`${continuityHeader}\n${continuityContent}\n`
|
|
84
|
-
);
|
|
85
|
-
} else {
|
|
86
|
-
// Append section
|
|
87
|
-
content = content.trimEnd() + `\n\n${continuityHeader}\n${continuityContent}\n`;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
atomicWrite(stateFile, content);
|
|
91
|
-
|
|
92
|
-
// Output additionalContext for post-compaction recovery
|
|
93
|
-
const recoveryContext = buildRecoveryContext(activeOp, roadmapSummary, currentPlan, configHighlights, recentErrors, recentAgents);
|
|
94
|
-
if (recoveryContext) {
|
|
95
|
-
const output = {
|
|
96
|
-
additionalContext: recoveryContext
|
|
97
|
-
};
|
|
98
|
-
process.stdout.write(JSON.stringify(output));
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
logHook('context-budget-check', 'PreCompact', 'saved', {
|
|
102
|
-
stateFile: 'STATE.md',
|
|
103
|
-
hasRoadmap: !!roadmapSummary,
|
|
104
|
-
hasPlan: !!currentPlan,
|
|
105
|
-
hasConfig: !!configHighlights
|
|
106
|
-
});
|
|
107
|
-
logEvent('workflow', 'compaction', { timestamp });
|
|
108
|
-
} catch (e) {
|
|
109
|
-
logHook('context-budget-check', 'PreCompact', 'error', { error: e.message });
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
process.exit(0);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function readActiveOperation(planningDir) {
|
|
116
|
-
const activeOpFile = path.join(planningDir, '.active-operation');
|
|
117
|
-
if (!fs.existsSync(activeOpFile)) return '';
|
|
118
|
-
try {
|
|
119
|
-
return fs.readFileSync(activeOpFile, 'utf8').trim();
|
|
120
|
-
} catch (_e) {
|
|
121
|
-
return '';
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function readRoadmapSummary(planningDir) {
|
|
126
|
-
const roadmapFile = path.join(planningDir, 'ROADMAP.md');
|
|
127
|
-
if (!fs.existsSync(roadmapFile)) return '';
|
|
128
|
-
|
|
129
|
-
try {
|
|
130
|
-
const roadmap = fs.readFileSync(roadmapFile, 'utf8');
|
|
131
|
-
|
|
132
|
-
// Extract progress table
|
|
133
|
-
const progressMatch = roadmap.match(/## Progress[\s\S]*?\|[\s\S]*?(?=\n##|\s*$)/);
|
|
134
|
-
if (!progressMatch) return '';
|
|
135
|
-
|
|
136
|
-
const rows = progressMatch[0].split('\n').filter(r => r.includes('|'));
|
|
137
|
-
// Skip header and separator rows
|
|
138
|
-
const dataRows = rows.filter(r => !r.includes('---') && !r.toLowerCase().includes('phase'));
|
|
139
|
-
|
|
140
|
-
if (dataRows.length === 0) return '';
|
|
141
|
-
|
|
142
|
-
// Build compact summary
|
|
143
|
-
const phases = [];
|
|
144
|
-
for (const row of dataRows) {
|
|
145
|
-
const cols = row.split('|').map(c => c.trim()).filter(Boolean);
|
|
146
|
-
if (cols.length >= 4) {
|
|
147
|
-
const num = cols[0];
|
|
148
|
-
const name = cols[1] || '';
|
|
149
|
-
const status = cols[3] || '';
|
|
150
|
-
if (num && /^\d+/.test(num)) {
|
|
151
|
-
phases.push(` Phase ${num} (${name.substring(0, 30)}): ${status}`);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
return phases.join('\n');
|
|
157
|
-
} catch (_e) {
|
|
158
|
-
return '';
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function readCurrentPlan(planningDir, stateContent) {
|
|
163
|
-
// Prefer .active-plan signal file (definitive) over directory listing (guesswork)
|
|
164
|
-
const activePlanFile = path.join(planningDir, '.active-plan');
|
|
165
|
-
if (fs.existsSync(activePlanFile)) {
|
|
166
|
-
try {
|
|
167
|
-
const activePlan = fs.readFileSync(activePlanFile, 'utf8').trim();
|
|
168
|
-
if (activePlan) return activePlan;
|
|
169
|
-
} catch (_e) { /* fall through */ }
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// Fallback: Extract current phase from STATE.md and find latest plan
|
|
173
|
-
const phaseMatch = stateContent.match(/Phase:\s*(\d+)\s+of\s+\d+/);
|
|
174
|
-
if (!phaseMatch) return '';
|
|
175
|
-
|
|
176
|
-
const currentPhase = phaseMatch[1].padStart(2, '0');
|
|
177
|
-
|
|
178
|
-
// Find the phase directory
|
|
179
|
-
const phasesDir = path.join(planningDir, 'phases');
|
|
180
|
-
if (!fs.existsSync(phasesDir)) return '';
|
|
181
|
-
|
|
182
|
-
try {
|
|
183
|
-
const dirs = fs.readdirSync(phasesDir).filter(d => d.startsWith(currentPhase));
|
|
184
|
-
if (dirs.length === 0) return '';
|
|
185
|
-
|
|
186
|
-
const phaseDir = path.join(phasesDir, dirs[0]);
|
|
187
|
-
|
|
188
|
-
// Find PLAN.md files
|
|
189
|
-
const planFiles = fs.readdirSync(phaseDir).filter(f => f.endsWith('PLAN.md'));
|
|
190
|
-
if (planFiles.length === 0) return 'No PLAN.md found in current phase';
|
|
191
|
-
|
|
192
|
-
// Read the last plan's objective only (frontmatter + objective tag)
|
|
193
|
-
const planFile = path.join(phaseDir, planFiles[planFiles.length - 1]);
|
|
194
|
-
const planContent = fs.readFileSync(planFile, 'utf8');
|
|
195
|
-
|
|
196
|
-
const objMatch = planContent.match(/<objective>([\s\S]*?)<\/objective>/);
|
|
197
|
-
const objective = objMatch ? objMatch[1].trim().substring(0, 150) : '';
|
|
198
|
-
|
|
199
|
-
return `${dirs[0]}/${planFiles[planFiles.length - 1]}${objective ? ' — ' + objective : ''}`;
|
|
200
|
-
} catch (_e) {
|
|
201
|
-
return '';
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
function readRecentErrors(planningDir, maxErrors) {
|
|
206
|
-
const count = maxErrors || 3;
|
|
207
|
-
try {
|
|
208
|
-
const eventsLog = path.join(planningDir, 'logs', 'events.jsonl');
|
|
209
|
-
// Only read the last 50 lines — errors are rare, so 50 tail lines
|
|
210
|
-
// is more than enough to find the most recent ones
|
|
211
|
-
const lines = tailLines(eventsLog, 50);
|
|
212
|
-
if (lines.length === 0) return [];
|
|
213
|
-
|
|
214
|
-
const errors = [];
|
|
215
|
-
// Read backwards for most recent
|
|
216
|
-
for (let i = lines.length - 1; i >= 0 && errors.length < count; i--) {
|
|
217
|
-
try {
|
|
218
|
-
const entry = JSON.parse(lines[i]);
|
|
219
|
-
if (entry.cat === 'error' || entry.event === 'tool-failure' || (entry.cat === 'workflow' && entry.status === 'block')) {
|
|
220
|
-
errors.push(`${entry.event || entry.cat}: ${entry.error || entry.reason || entry.message || 'unknown'}`.substring(0, 120));
|
|
221
|
-
}
|
|
222
|
-
} catch (_e) { /* skip */ }
|
|
223
|
-
}
|
|
224
|
-
return errors;
|
|
225
|
-
} catch (_e) {
|
|
226
|
-
return [];
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
function readRecentAgents(planningDir, maxAgents) {
|
|
231
|
-
const count = maxAgents || 5;
|
|
232
|
-
try {
|
|
233
|
-
const hooksLog = path.join(planningDir, 'logs', 'hooks.jsonl');
|
|
234
|
-
// Only read the last 30 lines — agent spawns are interspersed with
|
|
235
|
-
// other hook events, so 30 tail lines covers recent agents well
|
|
236
|
-
const lines = tailLines(hooksLog, 30);
|
|
237
|
-
if (lines.length === 0) return [];
|
|
238
|
-
|
|
239
|
-
const agents = [];
|
|
240
|
-
// Read backwards for most recent
|
|
241
|
-
for (let i = lines.length - 1; i >= 0 && agents.length < count; i--) {
|
|
242
|
-
try {
|
|
243
|
-
const entry = JSON.parse(lines[i]);
|
|
244
|
-
if (entry.event === 'SubagentStart' && entry.decision === 'spawned') {
|
|
245
|
-
const type = entry.agent_type || 'unknown';
|
|
246
|
-
const desc = entry.description ? ` (${entry.description.substring(0, 60)})` : '';
|
|
247
|
-
agents.push(`${type}${desc}`);
|
|
248
|
-
}
|
|
249
|
-
} catch (_e) { /* skip */ }
|
|
250
|
-
}
|
|
251
|
-
return agents.reverse(); // Chronological order
|
|
252
|
-
} catch (_e) {
|
|
253
|
-
return [];
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
function readConfigHighlights(planningDir) {
|
|
258
|
-
const config = configLoad(planningDir);
|
|
259
|
-
if (!config) return '';
|
|
260
|
-
|
|
261
|
-
const parts = [];
|
|
262
|
-
if (config.depth) parts.push(`depth=${config.depth}`);
|
|
263
|
-
if (config.mode) parts.push(`mode=${config.mode}`);
|
|
264
|
-
if (config.models && config.models.executor) parts.push(`executor=${config.models.executor}`);
|
|
265
|
-
if (config.gates && config.gates.verification !== undefined) parts.push(`verify=${config.gates.verification}`);
|
|
266
|
-
if (config.git && config.git.auto_commit !== undefined) parts.push(`auto_commit=${config.git.auto_commit}`);
|
|
267
|
-
return parts.join(', ');
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
function buildRecoveryContext(activeOp, roadmapSummary, currentPlan, configHighlights, recentErrors, recentAgents) {
|
|
271
|
-
const parts = ['[Post-Compaction Recovery] Context was auto-compacted. Key state preserved:'];
|
|
272
|
-
|
|
273
|
-
if (activeOp) parts.push(`Active operation: ${activeOp}`);
|
|
274
|
-
if (currentPlan) parts.push(`Current plan: ${currentPlan}`);
|
|
275
|
-
if (configHighlights) parts.push(`Config: ${configHighlights}`);
|
|
276
|
-
if (recentErrors && recentErrors.length > 0) parts.push(`Recent errors: ${recentErrors.join('; ')}`);
|
|
277
|
-
if (recentAgents && recentAgents.length > 0) parts.push(`Recent agents: ${recentAgents.join(', ')}`);
|
|
278
|
-
if (roadmapSummary) parts.push(`Progress:\n${roadmapSummary}`);
|
|
279
|
-
|
|
280
|
-
parts.push('Read .planning/STATE.md for full context.');
|
|
281
|
-
|
|
282
|
-
// Only return if we have something meaningful beyond header and footer
|
|
283
|
-
return parts.length > 2 ? parts.join('\n') : '';
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
module.exports = { readRoadmapSummary, readCurrentPlan, readConfigHighlights, buildRecoveryContext, readRecentErrors, readRecentAgents };
|
|
287
|
-
if (require.main === module) { main(); }
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* PreCompact hook: Preserves current state to STATE.md before
|
|
5
|
+
* lossy context compaction.
|
|
6
|
+
*
|
|
7
|
+
* Updates STATE.md with:
|
|
8
|
+
* - Timestamp of last compaction
|
|
9
|
+
* - ROADMAP progress summary (phase list, status)
|
|
10
|
+
* - Current plan context (objective from latest PLAN.md)
|
|
11
|
+
* - Config highlights (depth, mode, models, gates)
|
|
12
|
+
* - Active operation context
|
|
13
|
+
*
|
|
14
|
+
* Also outputs additionalContext for post-compaction recovery.
|
|
15
|
+
*
|
|
16
|
+
* Exit codes:
|
|
17
|
+
* 0 = always (informational hook, never blocks)
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const fs = require('fs');
|
|
21
|
+
const path = require('path');
|
|
22
|
+
const { logHook } = require('./hook-logger');
|
|
23
|
+
const { logEvent } = require('./event-logger');
|
|
24
|
+
const { atomicWrite, configLoad, tailLines } = require('./pbr-tools');
|
|
25
|
+
|
|
26
|
+
function main() {
|
|
27
|
+
const cwd = process.cwd();
|
|
28
|
+
const planningDir = path.join(cwd, '.planning');
|
|
29
|
+
const stateFile = path.join(planningDir, 'STATE.md');
|
|
30
|
+
|
|
31
|
+
// Not a Plan-Build-Run project or no STATE.md
|
|
32
|
+
if (!fs.existsSync(stateFile)) {
|
|
33
|
+
process.exit(0);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
let content = fs.readFileSync(stateFile, 'utf8');
|
|
38
|
+
const timestamp = new Date().toISOString();
|
|
39
|
+
|
|
40
|
+
// Gather context from multiple sources
|
|
41
|
+
const activeOp = readActiveOperation(planningDir);
|
|
42
|
+
const roadmapSummary = readRoadmapSummary(planningDir);
|
|
43
|
+
const currentPlan = readCurrentPlan(planningDir, content);
|
|
44
|
+
const configHighlights = readConfigHighlights(planningDir);
|
|
45
|
+
const recentErrors = readRecentErrors(planningDir, 3);
|
|
46
|
+
const recentAgents = readRecentAgents(planningDir, 5);
|
|
47
|
+
|
|
48
|
+
// Build continuity section
|
|
49
|
+
const continuityParts = [
|
|
50
|
+
`Last session: ${timestamp}`,
|
|
51
|
+
'Compaction occurred: context was auto-compacted at this point'
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
if (activeOp) {
|
|
55
|
+
continuityParts.push(`Active operation at compaction: ${activeOp}`);
|
|
56
|
+
}
|
|
57
|
+
if (roadmapSummary) {
|
|
58
|
+
continuityParts.push(`Roadmap progress:\n${roadmapSummary}`);
|
|
59
|
+
}
|
|
60
|
+
if (currentPlan) {
|
|
61
|
+
continuityParts.push(`Current plan: ${currentPlan}`);
|
|
62
|
+
}
|
|
63
|
+
if (configHighlights) {
|
|
64
|
+
continuityParts.push(`Config: ${configHighlights}`);
|
|
65
|
+
}
|
|
66
|
+
if (recentErrors.length > 0) {
|
|
67
|
+
continuityParts.push(`Recent errors:\n${recentErrors.map(e => ' - ' + e).join('\n')}`);
|
|
68
|
+
}
|
|
69
|
+
if (recentAgents.length > 0) {
|
|
70
|
+
continuityParts.push(`Recent agents: ${recentAgents.join(', ')}`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
continuityParts.push('Note: Some conversation context may have been lost. Check STATE.md and SUMMARY.md files for ground truth.');
|
|
74
|
+
|
|
75
|
+
// Update or add Session Continuity section
|
|
76
|
+
const continuityHeader = '## Session Continuity';
|
|
77
|
+
const continuityContent = continuityParts.join('\n');
|
|
78
|
+
|
|
79
|
+
if (content.includes(continuityHeader)) {
|
|
80
|
+
// Replace existing section
|
|
81
|
+
content = content.replace(
|
|
82
|
+
/## Session Continuity[\s\S]*?(?=\n## |\n---|\s*$)/,
|
|
83
|
+
`${continuityHeader}\n${continuityContent}\n`
|
|
84
|
+
);
|
|
85
|
+
} else {
|
|
86
|
+
// Append section
|
|
87
|
+
content = content.trimEnd() + `\n\n${continuityHeader}\n${continuityContent}\n`;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
atomicWrite(stateFile, content);
|
|
91
|
+
|
|
92
|
+
// Output additionalContext for post-compaction recovery
|
|
93
|
+
const recoveryContext = buildRecoveryContext(activeOp, roadmapSummary, currentPlan, configHighlights, recentErrors, recentAgents);
|
|
94
|
+
if (recoveryContext) {
|
|
95
|
+
const output = {
|
|
96
|
+
additionalContext: recoveryContext
|
|
97
|
+
};
|
|
98
|
+
process.stdout.write(JSON.stringify(output));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
logHook('context-budget-check', 'PreCompact', 'saved', {
|
|
102
|
+
stateFile: 'STATE.md',
|
|
103
|
+
hasRoadmap: !!roadmapSummary,
|
|
104
|
+
hasPlan: !!currentPlan,
|
|
105
|
+
hasConfig: !!configHighlights
|
|
106
|
+
});
|
|
107
|
+
logEvent('workflow', 'compaction', { timestamp });
|
|
108
|
+
} catch (e) {
|
|
109
|
+
logHook('context-budget-check', 'PreCompact', 'error', { error: e.message });
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
process.exit(0);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function readActiveOperation(planningDir) {
|
|
116
|
+
const activeOpFile = path.join(planningDir, '.active-operation');
|
|
117
|
+
if (!fs.existsSync(activeOpFile)) return '';
|
|
118
|
+
try {
|
|
119
|
+
return fs.readFileSync(activeOpFile, 'utf8').trim();
|
|
120
|
+
} catch (_e) {
|
|
121
|
+
return '';
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function readRoadmapSummary(planningDir) {
|
|
126
|
+
const roadmapFile = path.join(planningDir, 'ROADMAP.md');
|
|
127
|
+
if (!fs.existsSync(roadmapFile)) return '';
|
|
128
|
+
|
|
129
|
+
try {
|
|
130
|
+
const roadmap = fs.readFileSync(roadmapFile, 'utf8');
|
|
131
|
+
|
|
132
|
+
// Extract progress table
|
|
133
|
+
const progressMatch = roadmap.match(/## Progress[\s\S]*?\|[\s\S]*?(?=\n##|\s*$)/);
|
|
134
|
+
if (!progressMatch) return '';
|
|
135
|
+
|
|
136
|
+
const rows = progressMatch[0].split('\n').filter(r => r.includes('|'));
|
|
137
|
+
// Skip header and separator rows
|
|
138
|
+
const dataRows = rows.filter(r => !r.includes('---') && !r.toLowerCase().includes('phase'));
|
|
139
|
+
|
|
140
|
+
if (dataRows.length === 0) return '';
|
|
141
|
+
|
|
142
|
+
// Build compact summary
|
|
143
|
+
const phases = [];
|
|
144
|
+
for (const row of dataRows) {
|
|
145
|
+
const cols = row.split('|').map(c => c.trim()).filter(Boolean);
|
|
146
|
+
if (cols.length >= 4) {
|
|
147
|
+
const num = cols[0];
|
|
148
|
+
const name = cols[1] || '';
|
|
149
|
+
const status = cols[3] || '';
|
|
150
|
+
if (num && /^\d+/.test(num)) {
|
|
151
|
+
phases.push(` Phase ${num} (${name.substring(0, 30)}): ${status}`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return phases.join('\n');
|
|
157
|
+
} catch (_e) {
|
|
158
|
+
return '';
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function readCurrentPlan(planningDir, stateContent) {
|
|
163
|
+
// Prefer .active-plan signal file (definitive) over directory listing (guesswork)
|
|
164
|
+
const activePlanFile = path.join(planningDir, '.active-plan');
|
|
165
|
+
if (fs.existsSync(activePlanFile)) {
|
|
166
|
+
try {
|
|
167
|
+
const activePlan = fs.readFileSync(activePlanFile, 'utf8').trim();
|
|
168
|
+
if (activePlan) return activePlan;
|
|
169
|
+
} catch (_e) { /* fall through */ }
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Fallback: Extract current phase from STATE.md and find latest plan
|
|
173
|
+
const phaseMatch = stateContent.match(/Phase:\s*(\d+)\s+of\s+\d+/);
|
|
174
|
+
if (!phaseMatch) return '';
|
|
175
|
+
|
|
176
|
+
const currentPhase = phaseMatch[1].padStart(2, '0');
|
|
177
|
+
|
|
178
|
+
// Find the phase directory
|
|
179
|
+
const phasesDir = path.join(planningDir, 'phases');
|
|
180
|
+
if (!fs.existsSync(phasesDir)) return '';
|
|
181
|
+
|
|
182
|
+
try {
|
|
183
|
+
const dirs = fs.readdirSync(phasesDir).filter(d => d.startsWith(currentPhase));
|
|
184
|
+
if (dirs.length === 0) return '';
|
|
185
|
+
|
|
186
|
+
const phaseDir = path.join(phasesDir, dirs[0]);
|
|
187
|
+
|
|
188
|
+
// Find PLAN.md files
|
|
189
|
+
const planFiles = fs.readdirSync(phaseDir).filter(f => f.endsWith('PLAN.md'));
|
|
190
|
+
if (planFiles.length === 0) return 'No PLAN.md found in current phase';
|
|
191
|
+
|
|
192
|
+
// Read the last plan's objective only (frontmatter + objective tag)
|
|
193
|
+
const planFile = path.join(phaseDir, planFiles[planFiles.length - 1]);
|
|
194
|
+
const planContent = fs.readFileSync(planFile, 'utf8');
|
|
195
|
+
|
|
196
|
+
const objMatch = planContent.match(/<objective>([\s\S]*?)<\/objective>/);
|
|
197
|
+
const objective = objMatch ? objMatch[1].trim().substring(0, 150) : '';
|
|
198
|
+
|
|
199
|
+
return `${dirs[0]}/${planFiles[planFiles.length - 1]}${objective ? ' — ' + objective : ''}`;
|
|
200
|
+
} catch (_e) {
|
|
201
|
+
return '';
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function readRecentErrors(planningDir, maxErrors) {
|
|
206
|
+
const count = maxErrors || 3;
|
|
207
|
+
try {
|
|
208
|
+
const eventsLog = path.join(planningDir, 'logs', 'events.jsonl');
|
|
209
|
+
// Only read the last 50 lines — errors are rare, so 50 tail lines
|
|
210
|
+
// is more than enough to find the most recent ones
|
|
211
|
+
const lines = tailLines(eventsLog, 50);
|
|
212
|
+
if (lines.length === 0) return [];
|
|
213
|
+
|
|
214
|
+
const errors = [];
|
|
215
|
+
// Read backwards for most recent
|
|
216
|
+
for (let i = lines.length - 1; i >= 0 && errors.length < count; i--) {
|
|
217
|
+
try {
|
|
218
|
+
const entry = JSON.parse(lines[i]);
|
|
219
|
+
if (entry.cat === 'error' || entry.event === 'tool-failure' || (entry.cat === 'workflow' && entry.status === 'block')) {
|
|
220
|
+
errors.push(`${entry.event || entry.cat}: ${entry.error || entry.reason || entry.message || 'unknown'}`.substring(0, 120));
|
|
221
|
+
}
|
|
222
|
+
} catch (_e) { /* skip */ }
|
|
223
|
+
}
|
|
224
|
+
return errors;
|
|
225
|
+
} catch (_e) {
|
|
226
|
+
return [];
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function readRecentAgents(planningDir, maxAgents) {
|
|
231
|
+
const count = maxAgents || 5;
|
|
232
|
+
try {
|
|
233
|
+
const hooksLog = path.join(planningDir, 'logs', 'hooks.jsonl');
|
|
234
|
+
// Only read the last 30 lines — agent spawns are interspersed with
|
|
235
|
+
// other hook events, so 30 tail lines covers recent agents well
|
|
236
|
+
const lines = tailLines(hooksLog, 30);
|
|
237
|
+
if (lines.length === 0) return [];
|
|
238
|
+
|
|
239
|
+
const agents = [];
|
|
240
|
+
// Read backwards for most recent
|
|
241
|
+
for (let i = lines.length - 1; i >= 0 && agents.length < count; i--) {
|
|
242
|
+
try {
|
|
243
|
+
const entry = JSON.parse(lines[i]);
|
|
244
|
+
if (entry.event === 'SubagentStart' && entry.decision === 'spawned') {
|
|
245
|
+
const type = entry.agent_type || 'unknown';
|
|
246
|
+
const desc = entry.description ? ` (${entry.description.substring(0, 60)})` : '';
|
|
247
|
+
agents.push(`${type}${desc}`);
|
|
248
|
+
}
|
|
249
|
+
} catch (_e) { /* skip */ }
|
|
250
|
+
}
|
|
251
|
+
return agents.reverse(); // Chronological order
|
|
252
|
+
} catch (_e) {
|
|
253
|
+
return [];
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function readConfigHighlights(planningDir) {
|
|
258
|
+
const config = configLoad(planningDir);
|
|
259
|
+
if (!config) return '';
|
|
260
|
+
|
|
261
|
+
const parts = [];
|
|
262
|
+
if (config.depth) parts.push(`depth=${config.depth}`);
|
|
263
|
+
if (config.mode) parts.push(`mode=${config.mode}`);
|
|
264
|
+
if (config.models && config.models.executor) parts.push(`executor=${config.models.executor}`);
|
|
265
|
+
if (config.gates && config.gates.verification !== undefined) parts.push(`verify=${config.gates.verification}`);
|
|
266
|
+
if (config.git && config.git.auto_commit !== undefined) parts.push(`auto_commit=${config.git.auto_commit}`);
|
|
267
|
+
return parts.join(', ');
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function buildRecoveryContext(activeOp, roadmapSummary, currentPlan, configHighlights, recentErrors, recentAgents) {
|
|
271
|
+
const parts = ['[Post-Compaction Recovery] Context was auto-compacted. Key state preserved:'];
|
|
272
|
+
|
|
273
|
+
if (activeOp) parts.push(`Active operation: ${activeOp}`);
|
|
274
|
+
if (currentPlan) parts.push(`Current plan: ${currentPlan}`);
|
|
275
|
+
if (configHighlights) parts.push(`Config: ${configHighlights}`);
|
|
276
|
+
if (recentErrors && recentErrors.length > 0) parts.push(`Recent errors: ${recentErrors.join('; ')}`);
|
|
277
|
+
if (recentAgents && recentAgents.length > 0) parts.push(`Recent agents: ${recentAgents.join(', ')}`);
|
|
278
|
+
if (roadmapSummary) parts.push(`Progress:\n${roadmapSummary}`);
|
|
279
|
+
|
|
280
|
+
parts.push('Read .planning/STATE.md for full context.');
|
|
281
|
+
|
|
282
|
+
// Only return if we have something meaningful beyond header and footer
|
|
283
|
+
return parts.length > 2 ? parts.join('\n') : '';
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
module.exports = { readRoadmapSummary, readCurrentPlan, readConfigHighlights, buildRecoveryContext, readRecentErrors, readRecentAgents };
|
|
287
|
+
if (require.main === module) { main(); }
|