@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,254 +1,254 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* SessionEnd cleanup hook.
|
|
5
|
-
*
|
|
6
|
-
* Removes stale planning artifacts that shouldn't persist across sessions:
|
|
7
|
-
* - .planning/.auto-next (prevents confusion on next session start)
|
|
8
|
-
* - .planning/.active-operation (stale operation lock)
|
|
9
|
-
* - .planning/.active-skill (stale skill tracking)
|
|
10
|
-
*
|
|
11
|
-
* Additional cleanup:
|
|
12
|
-
* - Removes stale .checkpoint-manifest.json files (>24h old)
|
|
13
|
-
* - Rotates hooks.jsonl when >200KB (moves to hooks.jsonl.1)
|
|
14
|
-
* - Warns about orphaned .PROGRESS-* files (executor crash artifacts)
|
|
15
|
-
* - Writes session summary to logs/sessions.jsonl
|
|
16
|
-
*
|
|
17
|
-
* Logs session end with reason to hook-log.
|
|
18
|
-
* Non-blocking — best-effort cleanup, fails silently.
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
const fs = require('fs');
|
|
22
|
-
const path = require('path');
|
|
23
|
-
const { logHook } = require('./hook-logger');
|
|
24
|
-
const { tailLines } = require('./pbr-tools');
|
|
25
|
-
|
|
26
|
-
function readStdin() {
|
|
27
|
-
try {
|
|
28
|
-
const input = fs.readFileSync(0, 'utf8').trim();
|
|
29
|
-
if (input) return JSON.parse(input);
|
|
30
|
-
} catch (_e) {
|
|
31
|
-
// empty or non-JSON stdin
|
|
32
|
-
}
|
|
33
|
-
return {};
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function tryRemove(filePath) {
|
|
37
|
-
try {
|
|
38
|
-
if (fs.existsSync(filePath)) {
|
|
39
|
-
fs.unlinkSync(filePath);
|
|
40
|
-
return true;
|
|
41
|
-
}
|
|
42
|
-
} catch (_e) {
|
|
43
|
-
// best-effort — don't fail the hook
|
|
44
|
-
}
|
|
45
|
-
return false;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const STALE_CHECKPOINT_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
49
|
-
const MAX_HOOKS_LOG_BYTES = 200 * 1024; // 200KB
|
|
50
|
-
|
|
51
|
-
function cleanStaleCheckpoints(planningDir) {
|
|
52
|
-
const removed = [];
|
|
53
|
-
try {
|
|
54
|
-
const phasesDir = path.join(planningDir, 'phases');
|
|
55
|
-
if (!fs.existsSync(phasesDir)) return removed;
|
|
56
|
-
|
|
57
|
-
const dirs = fs.readdirSync(phasesDir);
|
|
58
|
-
for (const dir of dirs) {
|
|
59
|
-
const manifestPath = path.join(phasesDir, dir, '.checkpoint-manifest.json');
|
|
60
|
-
if (!fs.existsSync(manifestPath)) continue;
|
|
61
|
-
|
|
62
|
-
const stat = fs.statSync(manifestPath);
|
|
63
|
-
const ageMs = Date.now() - stat.mtimeMs;
|
|
64
|
-
if (ageMs > STALE_CHECKPOINT_MS) {
|
|
65
|
-
fs.unlinkSync(manifestPath);
|
|
66
|
-
removed.push(path.join('phases', dir, '.checkpoint-manifest.json'));
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
} catch (_e) {
|
|
70
|
-
// best-effort
|
|
71
|
-
}
|
|
72
|
-
return removed;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function rotateHooksLog(planningDir) {
|
|
76
|
-
try {
|
|
77
|
-
const logsDir = path.join(planningDir, 'logs');
|
|
78
|
-
const hooksLog = path.join(logsDir, 'hooks.jsonl');
|
|
79
|
-
if (!fs.existsSync(hooksLog)) return false;
|
|
80
|
-
|
|
81
|
-
const stat = fs.statSync(hooksLog);
|
|
82
|
-
if (stat.size <= MAX_HOOKS_LOG_BYTES) return false;
|
|
83
|
-
|
|
84
|
-
const rotatedPath = hooksLog + '.1';
|
|
85
|
-
// Overwrite any existing .1 file
|
|
86
|
-
fs.renameSync(hooksLog, rotatedPath);
|
|
87
|
-
return true;
|
|
88
|
-
} catch (_e) {
|
|
89
|
-
return false;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function findOrphanedProgressFiles(planningDir) {
|
|
94
|
-
const orphans = [];
|
|
95
|
-
try {
|
|
96
|
-
const phasesDir = path.join(planningDir, 'phases');
|
|
97
|
-
if (!fs.existsSync(phasesDir)) return orphans;
|
|
98
|
-
|
|
99
|
-
const dirs = fs.readdirSync(phasesDir);
|
|
100
|
-
for (const dir of dirs) {
|
|
101
|
-
const phaseDir = path.join(phasesDir, dir);
|
|
102
|
-
const files = fs.readdirSync(phaseDir);
|
|
103
|
-
for (const file of files) {
|
|
104
|
-
if (file.startsWith('.PROGRESS-')) {
|
|
105
|
-
orphans.push(path.join('phases', dir, file));
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
} catch (_e) {
|
|
110
|
-
// best-effort
|
|
111
|
-
}
|
|
112
|
-
return orphans;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const MAX_SESSION_ENTRIES = 100;
|
|
116
|
-
|
|
117
|
-
function writeSessionHistory(planningDir, data) {
|
|
118
|
-
try {
|
|
119
|
-
const logsDir = path.join(planningDir, 'logs');
|
|
120
|
-
if (!fs.existsSync(logsDir)) {
|
|
121
|
-
fs.mkdirSync(logsDir, { recursive: true });
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const sessionsFile = path.join(logsDir, 'sessions.jsonl');
|
|
125
|
-
|
|
126
|
-
// Mine existing logs for session stats
|
|
127
|
-
const hooksLog = path.join(logsDir, 'hooks.jsonl');
|
|
128
|
-
const eventsLog = path.join(logsDir, 'events.jsonl');
|
|
129
|
-
|
|
130
|
-
let agentsSpawned = 0;
|
|
131
|
-
let commitsCreated = 0;
|
|
132
|
-
const commandsRun = [];
|
|
133
|
-
let sessionStart = null;
|
|
134
|
-
|
|
135
|
-
// Count agents from hooks log (SubagentStart entries)
|
|
136
|
-
// Hooks log is capped at 200 entries; read last 200 to cover the full session
|
|
137
|
-
const hookLines = tailLines(hooksLog, 200);
|
|
138
|
-
for (const line of hookLines) {
|
|
139
|
-
try {
|
|
140
|
-
const entry = JSON.parse(line);
|
|
141
|
-
if (entry.event === 'SubagentStart' && entry.decision === 'spawned') {
|
|
142
|
-
agentsSpawned++;
|
|
143
|
-
}
|
|
144
|
-
// Track earliest timestamp as session start
|
|
145
|
-
if (entry.ts && (!sessionStart || entry.ts < sessionStart)) {
|
|
146
|
-
sessionStart = entry.ts;
|
|
147
|
-
}
|
|
148
|
-
} catch (_e) { /* skip malformed lines */ }
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Count commits and commands from events log
|
|
152
|
-
// Read last 200 entries — sufficient for a single session's events
|
|
153
|
-
const eventLines = tailLines(eventsLog, 200);
|
|
154
|
-
for (const line of eventLines) {
|
|
155
|
-
try {
|
|
156
|
-
const entry = JSON.parse(line);
|
|
157
|
-
if (entry.event === 'commit-validated' && entry.status === 'allow') {
|
|
158
|
-
commitsCreated++;
|
|
159
|
-
}
|
|
160
|
-
if (entry.cat === 'workflow' && entry.event) {
|
|
161
|
-
if (!commandsRun.includes(entry.event)) {
|
|
162
|
-
commandsRun.push(entry.event);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
if (entry.ts && (!sessionStart || entry.ts < sessionStart)) {
|
|
166
|
-
sessionStart = entry.ts;
|
|
167
|
-
}
|
|
168
|
-
} catch (_e) { /* skip malformed lines */ }
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const sessionEnd = new Date().toISOString();
|
|
172
|
-
let durationMinutes = null;
|
|
173
|
-
if (sessionStart) {
|
|
174
|
-
durationMinutes = Math.round((new Date(sessionEnd) - new Date(sessionStart)) / 60000);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
const summary = {
|
|
178
|
-
start: sessionStart || sessionEnd,
|
|
179
|
-
end: sessionEnd,
|
|
180
|
-
duration_minutes: durationMinutes,
|
|
181
|
-
reason: data.reason || null,
|
|
182
|
-
agents_spawned: agentsSpawned,
|
|
183
|
-
commits_created: commitsCreated,
|
|
184
|
-
commands_run: commandsRun
|
|
185
|
-
};
|
|
186
|
-
|
|
187
|
-
// Append to sessions.jsonl, cap at MAX_SESSION_ENTRIES
|
|
188
|
-
let lines = [];
|
|
189
|
-
if (fs.existsSync(sessionsFile)) {
|
|
190
|
-
const content = fs.readFileSync(sessionsFile, 'utf8').trim();
|
|
191
|
-
if (content) {
|
|
192
|
-
lines = content.split('\n');
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
lines.push(JSON.stringify(summary));
|
|
196
|
-
if (lines.length > MAX_SESSION_ENTRIES) {
|
|
197
|
-
lines = lines.slice(lines.length - MAX_SESSION_ENTRIES);
|
|
198
|
-
}
|
|
199
|
-
fs.writeFileSync(sessionsFile, lines.join('\n') + '\n', 'utf8');
|
|
200
|
-
} catch (_e) {
|
|
201
|
-
// Best-effort — don't fail the hook
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
function main() {
|
|
206
|
-
const data = readStdin();
|
|
207
|
-
const cwd = process.cwd();
|
|
208
|
-
const planningDir = path.join(cwd, '.planning');
|
|
209
|
-
|
|
210
|
-
if (!fs.existsSync(planningDir)) {
|
|
211
|
-
process.exit(0);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
const cleaned = [];
|
|
215
|
-
|
|
216
|
-
if (tryRemove(path.join(planningDir, '.auto-next'))) {
|
|
217
|
-
cleaned.push('.auto-next');
|
|
218
|
-
}
|
|
219
|
-
if (tryRemove(path.join(planningDir, '.active-operation'))) {
|
|
220
|
-
cleaned.push('.active-operation');
|
|
221
|
-
}
|
|
222
|
-
if (tryRemove(path.join(planningDir, '.active-skill'))) {
|
|
223
|
-
cleaned.push('.active-skill');
|
|
224
|
-
}
|
|
225
|
-
if (tryRemove(path.join(planningDir, '.active-plan'))) {
|
|
226
|
-
cleaned.push('.active-plan');
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
// Clean stale checkpoint manifests (>24h old)
|
|
230
|
-
const staleCheckpoints = cleanStaleCheckpoints(planningDir);
|
|
231
|
-
cleaned.push(...staleCheckpoints);
|
|
232
|
-
|
|
233
|
-
// Rotate hooks.jsonl if >200KB
|
|
234
|
-
const rotated = rotateHooksLog(planningDir);
|
|
235
|
-
|
|
236
|
-
// Detect orphaned .PROGRESS-* files (executor crash artifacts)
|
|
237
|
-
const orphans = findOrphanedProgressFiles(planningDir);
|
|
238
|
-
|
|
239
|
-
// Write session history log
|
|
240
|
-
writeSessionHistory(planningDir, data);
|
|
241
|
-
|
|
242
|
-
const decision = cleaned.length > 0 ? 'cleaned' : 'nothing';
|
|
243
|
-
logHook('session-cleanup', 'SessionEnd', decision, {
|
|
244
|
-
reason: data.reason || null,
|
|
245
|
-
removed: cleaned,
|
|
246
|
-
log_rotated: rotated,
|
|
247
|
-
orphaned_progress_files: orphans.length > 0 ? orphans : undefined
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
process.exit(0);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
module.exports = { writeSessionHistory, tryRemove, cleanStaleCheckpoints, rotateHooksLog, findOrphanedProgressFiles };
|
|
254
|
-
if (require.main === module) { main(); }
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* SessionEnd cleanup hook.
|
|
5
|
+
*
|
|
6
|
+
* Removes stale planning artifacts that shouldn't persist across sessions:
|
|
7
|
+
* - .planning/.auto-next (prevents confusion on next session start)
|
|
8
|
+
* - .planning/.active-operation (stale operation lock)
|
|
9
|
+
* - .planning/.active-skill (stale skill tracking)
|
|
10
|
+
*
|
|
11
|
+
* Additional cleanup:
|
|
12
|
+
* - Removes stale .checkpoint-manifest.json files (>24h old)
|
|
13
|
+
* - Rotates hooks.jsonl when >200KB (moves to hooks.jsonl.1)
|
|
14
|
+
* - Warns about orphaned .PROGRESS-* files (executor crash artifacts)
|
|
15
|
+
* - Writes session summary to logs/sessions.jsonl
|
|
16
|
+
*
|
|
17
|
+
* Logs session end with reason to hook-log.
|
|
18
|
+
* Non-blocking — best-effort cleanup, fails silently.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const fs = require('fs');
|
|
22
|
+
const path = require('path');
|
|
23
|
+
const { logHook } = require('./hook-logger');
|
|
24
|
+
const { tailLines } = require('./pbr-tools');
|
|
25
|
+
|
|
26
|
+
function readStdin() {
|
|
27
|
+
try {
|
|
28
|
+
const input = fs.readFileSync(0, 'utf8').trim();
|
|
29
|
+
if (input) return JSON.parse(input);
|
|
30
|
+
} catch (_e) {
|
|
31
|
+
// empty or non-JSON stdin
|
|
32
|
+
}
|
|
33
|
+
return {};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function tryRemove(filePath) {
|
|
37
|
+
try {
|
|
38
|
+
if (fs.existsSync(filePath)) {
|
|
39
|
+
fs.unlinkSync(filePath);
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
} catch (_e) {
|
|
43
|
+
// best-effort — don't fail the hook
|
|
44
|
+
}
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const STALE_CHECKPOINT_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
49
|
+
const MAX_HOOKS_LOG_BYTES = 200 * 1024; // 200KB
|
|
50
|
+
|
|
51
|
+
function cleanStaleCheckpoints(planningDir) {
|
|
52
|
+
const removed = [];
|
|
53
|
+
try {
|
|
54
|
+
const phasesDir = path.join(planningDir, 'phases');
|
|
55
|
+
if (!fs.existsSync(phasesDir)) return removed;
|
|
56
|
+
|
|
57
|
+
const dirs = fs.readdirSync(phasesDir);
|
|
58
|
+
for (const dir of dirs) {
|
|
59
|
+
const manifestPath = path.join(phasesDir, dir, '.checkpoint-manifest.json');
|
|
60
|
+
if (!fs.existsSync(manifestPath)) continue;
|
|
61
|
+
|
|
62
|
+
const stat = fs.statSync(manifestPath);
|
|
63
|
+
const ageMs = Date.now() - stat.mtimeMs;
|
|
64
|
+
if (ageMs > STALE_CHECKPOINT_MS) {
|
|
65
|
+
fs.unlinkSync(manifestPath);
|
|
66
|
+
removed.push(path.join('phases', dir, '.checkpoint-manifest.json'));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
} catch (_e) {
|
|
70
|
+
// best-effort
|
|
71
|
+
}
|
|
72
|
+
return removed;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function rotateHooksLog(planningDir) {
|
|
76
|
+
try {
|
|
77
|
+
const logsDir = path.join(planningDir, 'logs');
|
|
78
|
+
const hooksLog = path.join(logsDir, 'hooks.jsonl');
|
|
79
|
+
if (!fs.existsSync(hooksLog)) return false;
|
|
80
|
+
|
|
81
|
+
const stat = fs.statSync(hooksLog);
|
|
82
|
+
if (stat.size <= MAX_HOOKS_LOG_BYTES) return false;
|
|
83
|
+
|
|
84
|
+
const rotatedPath = hooksLog + '.1';
|
|
85
|
+
// Overwrite any existing .1 file
|
|
86
|
+
fs.renameSync(hooksLog, rotatedPath);
|
|
87
|
+
return true;
|
|
88
|
+
} catch (_e) {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function findOrphanedProgressFiles(planningDir) {
|
|
94
|
+
const orphans = [];
|
|
95
|
+
try {
|
|
96
|
+
const phasesDir = path.join(planningDir, 'phases');
|
|
97
|
+
if (!fs.existsSync(phasesDir)) return orphans;
|
|
98
|
+
|
|
99
|
+
const dirs = fs.readdirSync(phasesDir);
|
|
100
|
+
for (const dir of dirs) {
|
|
101
|
+
const phaseDir = path.join(phasesDir, dir);
|
|
102
|
+
const files = fs.readdirSync(phaseDir);
|
|
103
|
+
for (const file of files) {
|
|
104
|
+
if (file.startsWith('.PROGRESS-')) {
|
|
105
|
+
orphans.push(path.join('phases', dir, file));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
} catch (_e) {
|
|
110
|
+
// best-effort
|
|
111
|
+
}
|
|
112
|
+
return orphans;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const MAX_SESSION_ENTRIES = 100;
|
|
116
|
+
|
|
117
|
+
function writeSessionHistory(planningDir, data) {
|
|
118
|
+
try {
|
|
119
|
+
const logsDir = path.join(planningDir, 'logs');
|
|
120
|
+
if (!fs.existsSync(logsDir)) {
|
|
121
|
+
fs.mkdirSync(logsDir, { recursive: true });
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const sessionsFile = path.join(logsDir, 'sessions.jsonl');
|
|
125
|
+
|
|
126
|
+
// Mine existing logs for session stats
|
|
127
|
+
const hooksLog = path.join(logsDir, 'hooks.jsonl');
|
|
128
|
+
const eventsLog = path.join(logsDir, 'events.jsonl');
|
|
129
|
+
|
|
130
|
+
let agentsSpawned = 0;
|
|
131
|
+
let commitsCreated = 0;
|
|
132
|
+
const commandsRun = [];
|
|
133
|
+
let sessionStart = null;
|
|
134
|
+
|
|
135
|
+
// Count agents from hooks log (SubagentStart entries)
|
|
136
|
+
// Hooks log is capped at 200 entries; read last 200 to cover the full session
|
|
137
|
+
const hookLines = tailLines(hooksLog, 200);
|
|
138
|
+
for (const line of hookLines) {
|
|
139
|
+
try {
|
|
140
|
+
const entry = JSON.parse(line);
|
|
141
|
+
if (entry.event === 'SubagentStart' && entry.decision === 'spawned') {
|
|
142
|
+
agentsSpawned++;
|
|
143
|
+
}
|
|
144
|
+
// Track earliest timestamp as session start
|
|
145
|
+
if (entry.ts && (!sessionStart || entry.ts < sessionStart)) {
|
|
146
|
+
sessionStart = entry.ts;
|
|
147
|
+
}
|
|
148
|
+
} catch (_e) { /* skip malformed lines */ }
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Count commits and commands from events log
|
|
152
|
+
// Read last 200 entries — sufficient for a single session's events
|
|
153
|
+
const eventLines = tailLines(eventsLog, 200);
|
|
154
|
+
for (const line of eventLines) {
|
|
155
|
+
try {
|
|
156
|
+
const entry = JSON.parse(line);
|
|
157
|
+
if (entry.event === 'commit-validated' && entry.status === 'allow') {
|
|
158
|
+
commitsCreated++;
|
|
159
|
+
}
|
|
160
|
+
if (entry.cat === 'workflow' && entry.event) {
|
|
161
|
+
if (!commandsRun.includes(entry.event)) {
|
|
162
|
+
commandsRun.push(entry.event);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
if (entry.ts && (!sessionStart || entry.ts < sessionStart)) {
|
|
166
|
+
sessionStart = entry.ts;
|
|
167
|
+
}
|
|
168
|
+
} catch (_e) { /* skip malformed lines */ }
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const sessionEnd = new Date().toISOString();
|
|
172
|
+
let durationMinutes = null;
|
|
173
|
+
if (sessionStart) {
|
|
174
|
+
durationMinutes = Math.round((new Date(sessionEnd) - new Date(sessionStart)) / 60000);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const summary = {
|
|
178
|
+
start: sessionStart || sessionEnd,
|
|
179
|
+
end: sessionEnd,
|
|
180
|
+
duration_minutes: durationMinutes,
|
|
181
|
+
reason: data.reason || null,
|
|
182
|
+
agents_spawned: agentsSpawned,
|
|
183
|
+
commits_created: commitsCreated,
|
|
184
|
+
commands_run: commandsRun
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
// Append to sessions.jsonl, cap at MAX_SESSION_ENTRIES
|
|
188
|
+
let lines = [];
|
|
189
|
+
if (fs.existsSync(sessionsFile)) {
|
|
190
|
+
const content = fs.readFileSync(sessionsFile, 'utf8').trim();
|
|
191
|
+
if (content) {
|
|
192
|
+
lines = content.split('\n');
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
lines.push(JSON.stringify(summary));
|
|
196
|
+
if (lines.length > MAX_SESSION_ENTRIES) {
|
|
197
|
+
lines = lines.slice(lines.length - MAX_SESSION_ENTRIES);
|
|
198
|
+
}
|
|
199
|
+
fs.writeFileSync(sessionsFile, lines.join('\n') + '\n', 'utf8');
|
|
200
|
+
} catch (_e) {
|
|
201
|
+
// Best-effort — don't fail the hook
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function main() {
|
|
206
|
+
const data = readStdin();
|
|
207
|
+
const cwd = process.cwd();
|
|
208
|
+
const planningDir = path.join(cwd, '.planning');
|
|
209
|
+
|
|
210
|
+
if (!fs.existsSync(planningDir)) {
|
|
211
|
+
process.exit(0);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const cleaned = [];
|
|
215
|
+
|
|
216
|
+
if (tryRemove(path.join(planningDir, '.auto-next'))) {
|
|
217
|
+
cleaned.push('.auto-next');
|
|
218
|
+
}
|
|
219
|
+
if (tryRemove(path.join(planningDir, '.active-operation'))) {
|
|
220
|
+
cleaned.push('.active-operation');
|
|
221
|
+
}
|
|
222
|
+
if (tryRemove(path.join(planningDir, '.active-skill'))) {
|
|
223
|
+
cleaned.push('.active-skill');
|
|
224
|
+
}
|
|
225
|
+
if (tryRemove(path.join(planningDir, '.active-plan'))) {
|
|
226
|
+
cleaned.push('.active-plan');
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Clean stale checkpoint manifests (>24h old)
|
|
230
|
+
const staleCheckpoints = cleanStaleCheckpoints(planningDir);
|
|
231
|
+
cleaned.push(...staleCheckpoints);
|
|
232
|
+
|
|
233
|
+
// Rotate hooks.jsonl if >200KB
|
|
234
|
+
const rotated = rotateHooksLog(planningDir);
|
|
235
|
+
|
|
236
|
+
// Detect orphaned .PROGRESS-* files (executor crash artifacts)
|
|
237
|
+
const orphans = findOrphanedProgressFiles(planningDir);
|
|
238
|
+
|
|
239
|
+
// Write session history log
|
|
240
|
+
writeSessionHistory(planningDir, data);
|
|
241
|
+
|
|
242
|
+
const decision = cleaned.length > 0 ? 'cleaned' : 'nothing';
|
|
243
|
+
logHook('session-cleanup', 'SessionEnd', decision, {
|
|
244
|
+
reason: data.reason || null,
|
|
245
|
+
removed: cleaned,
|
|
246
|
+
log_rotated: rotated,
|
|
247
|
+
orphaned_progress_files: orphans.length > 0 ? orphans : undefined
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
process.exit(0);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
module.exports = { writeSessionHistory, tryRemove, cleanStaleCheckpoints, rotateHooksLog, findOrphanedProgressFiles };
|
|
254
|
+
if (require.main === module) { main(); }
|