@sienklogic/plan-build-run 2.0.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/CHANGELOG.md +56 -0
- package/CLAUDE.md +149 -0
- package/LICENSE +21 -0
- package/README.md +247 -0
- package/dashboard/bin/cli.js +25 -0
- package/dashboard/package.json +34 -0
- package/dashboard/public/.gitkeep +0 -0
- package/dashboard/public/css/layout.css +406 -0
- package/dashboard/public/css/status-colors.css +98 -0
- package/dashboard/public/js/htmx-title.js +5 -0
- package/dashboard/public/js/sidebar-toggle.js +20 -0
- package/dashboard/src/app.js +78 -0
- package/dashboard/src/middleware/errorHandler.js +52 -0
- package/dashboard/src/middleware/notFoundHandler.js +9 -0
- package/dashboard/src/repositories/planning.repository.js +128 -0
- package/dashboard/src/routes/events.routes.js +40 -0
- package/dashboard/src/routes/index.routes.js +31 -0
- package/dashboard/src/routes/pages.routes.js +195 -0
- package/dashboard/src/server.js +42 -0
- package/dashboard/src/services/dashboard.service.js +222 -0
- package/dashboard/src/services/phase.service.js +167 -0
- package/dashboard/src/services/project.service.js +57 -0
- package/dashboard/src/services/roadmap.service.js +171 -0
- package/dashboard/src/services/sse.service.js +58 -0
- package/dashboard/src/services/todo.service.js +254 -0
- package/dashboard/src/services/watcher.service.js +48 -0
- package/dashboard/src/views/coming-soon.ejs +11 -0
- package/dashboard/src/views/error.ejs +13 -0
- package/dashboard/src/views/index.ejs +5 -0
- package/dashboard/src/views/layout.ejs +1 -0
- package/dashboard/src/views/partials/dashboard-content.ejs +77 -0
- package/dashboard/src/views/partials/footer.ejs +3 -0
- package/dashboard/src/views/partials/head.ejs +21 -0
- package/dashboard/src/views/partials/header.ejs +12 -0
- package/dashboard/src/views/partials/layout-bottom.ejs +15 -0
- package/dashboard/src/views/partials/layout-top.ejs +8 -0
- package/dashboard/src/views/partials/phase-content.ejs +181 -0
- package/dashboard/src/views/partials/phases-content.ejs +117 -0
- package/dashboard/src/views/partials/roadmap-content.ejs +142 -0
- package/dashboard/src/views/partials/sidebar.ejs +38 -0
- package/dashboard/src/views/partials/todo-create-content.ejs +53 -0
- package/dashboard/src/views/partials/todo-detail-content.ejs +38 -0
- package/dashboard/src/views/partials/todos-content.ejs +53 -0
- package/dashboard/src/views/phase-detail.ejs +5 -0
- package/dashboard/src/views/phases.ejs +5 -0
- package/dashboard/src/views/roadmap.ejs +5 -0
- package/dashboard/src/views/todo-create.ejs +5 -0
- package/dashboard/src/views/todo-detail.ejs +5 -0
- package/dashboard/src/views/todos.ejs +5 -0
- package/package.json +57 -0
- package/plugins/pbr/.claude-plugin/plugin.json +13 -0
- package/plugins/pbr/UI-CONSISTENCY-GAPS.md +61 -0
- package/plugins/pbr/agents/codebase-mapper.md +271 -0
- package/plugins/pbr/agents/debugger.md +281 -0
- package/plugins/pbr/agents/executor.md +407 -0
- package/plugins/pbr/agents/general.md +164 -0
- package/plugins/pbr/agents/integration-checker.md +141 -0
- package/plugins/pbr/agents/plan-checker.md +280 -0
- package/plugins/pbr/agents/planner.md +358 -0
- package/plugins/pbr/agents/researcher.md +363 -0
- package/plugins/pbr/agents/synthesizer.md +230 -0
- package/plugins/pbr/agents/verifier.md +454 -0
- package/plugins/pbr/commands/begin.md +5 -0
- package/plugins/pbr/commands/build.md +5 -0
- package/plugins/pbr/commands/config.md +5 -0
- package/plugins/pbr/commands/continue.md +5 -0
- package/plugins/pbr/commands/debug.md +5 -0
- package/plugins/pbr/commands/discuss.md +5 -0
- package/plugins/pbr/commands/explore.md +5 -0
- package/plugins/pbr/commands/health.md +5 -0
- package/plugins/pbr/commands/help.md +5 -0
- package/plugins/pbr/commands/import.md +5 -0
- package/plugins/pbr/commands/milestone.md +5 -0
- package/plugins/pbr/commands/note.md +5 -0
- package/plugins/pbr/commands/pause.md +5 -0
- package/plugins/pbr/commands/plan.md +5 -0
- package/plugins/pbr/commands/quick.md +5 -0
- package/plugins/pbr/commands/resume.md +5 -0
- package/plugins/pbr/commands/review.md +5 -0
- package/plugins/pbr/commands/scan.md +5 -0
- package/plugins/pbr/commands/setup.md +5 -0
- package/plugins/pbr/commands/status.md +5 -0
- package/plugins/pbr/commands/todo.md +5 -0
- package/plugins/pbr/contexts/dev.md +27 -0
- package/plugins/pbr/contexts/research.md +28 -0
- package/plugins/pbr/contexts/review.md +36 -0
- package/plugins/pbr/hooks/hooks.json +183 -0
- package/plugins/pbr/references/agent-anti-patterns.md +24 -0
- package/plugins/pbr/references/agent-interactions.md +134 -0
- package/plugins/pbr/references/agent-teams.md +54 -0
- package/plugins/pbr/references/checkpoints.md +157 -0
- package/plugins/pbr/references/common-bug-patterns.md +13 -0
- package/plugins/pbr/references/continuation-format.md +212 -0
- package/plugins/pbr/references/deviation-rules.md +112 -0
- package/plugins/pbr/references/git-integration.md +226 -0
- package/plugins/pbr/references/integration-patterns.md +117 -0
- package/plugins/pbr/references/model-profiles.md +99 -0
- package/plugins/pbr/references/model-selection.md +31 -0
- package/plugins/pbr/references/pbr-rules.md +193 -0
- package/plugins/pbr/references/plan-authoring.md +181 -0
- package/plugins/pbr/references/plan-format.md +283 -0
- package/plugins/pbr/references/planning-config.md +213 -0
- package/plugins/pbr/references/questioning.md +214 -0
- package/plugins/pbr/references/reading-verification.md +127 -0
- package/plugins/pbr/references/stub-patterns.md +160 -0
- package/plugins/pbr/references/subagent-coordination.md +119 -0
- package/plugins/pbr/references/ui-formatting.md +399 -0
- package/plugins/pbr/references/verification-patterns.md +198 -0
- package/plugins/pbr/references/wave-execution.md +95 -0
- package/plugins/pbr/scripts/auto-continue.js +80 -0
- package/plugins/pbr/scripts/check-dangerous-commands.js +136 -0
- package/plugins/pbr/scripts/check-doc-sprawl.js +102 -0
- package/plugins/pbr/scripts/check-phase-boundary.js +196 -0
- package/plugins/pbr/scripts/check-plan-format.js +270 -0
- package/plugins/pbr/scripts/check-roadmap-sync.js +252 -0
- package/plugins/pbr/scripts/check-skill-workflow.js +262 -0
- package/plugins/pbr/scripts/check-state-sync.js +476 -0
- package/plugins/pbr/scripts/check-subagent-output.js +144 -0
- package/plugins/pbr/scripts/config-schema.json +251 -0
- package/plugins/pbr/scripts/context-budget-check.js +287 -0
- package/plugins/pbr/scripts/event-handler.js +151 -0
- package/plugins/pbr/scripts/event-logger.js +92 -0
- package/plugins/pbr/scripts/hook-logger.js +76 -0
- package/plugins/pbr/scripts/hooks-schema.json +79 -0
- package/plugins/pbr/scripts/log-subagent.js +152 -0
- package/plugins/pbr/scripts/log-tool-failure.js +88 -0
- package/plugins/pbr/scripts/pbr-tools.js +1301 -0
- package/plugins/pbr/scripts/post-write-dispatch.js +66 -0
- package/plugins/pbr/scripts/post-write-quality.js +207 -0
- package/plugins/pbr/scripts/pre-bash-dispatch.js +56 -0
- package/plugins/pbr/scripts/pre-write-dispatch.js +62 -0
- package/plugins/pbr/scripts/progress-tracker.js +228 -0
- package/plugins/pbr/scripts/session-cleanup.js +254 -0
- package/plugins/pbr/scripts/status-line.js +285 -0
- package/plugins/pbr/scripts/suggest-compact.js +119 -0
- package/plugins/pbr/scripts/task-completed.js +45 -0
- package/plugins/pbr/scripts/track-context-budget.js +119 -0
- package/plugins/pbr/scripts/validate-commit.js +200 -0
- package/plugins/pbr/scripts/validate-plugin-structure.js +172 -0
- package/plugins/pbr/skills/begin/SKILL.md +545 -0
- package/plugins/pbr/skills/begin/templates/PROJECT.md.tmpl +33 -0
- package/plugins/pbr/skills/begin/templates/REQUIREMENTS.md.tmpl +18 -0
- package/plugins/pbr/skills/begin/templates/STATE.md.tmpl +49 -0
- package/plugins/pbr/skills/begin/templates/config.json.tmpl +63 -0
- package/plugins/pbr/skills/begin/templates/researcher-prompt.md.tmpl +19 -0
- package/plugins/pbr/skills/begin/templates/roadmap-prompt.md.tmpl +30 -0
- package/plugins/pbr/skills/begin/templates/synthesis-prompt.md.tmpl +16 -0
- package/plugins/pbr/skills/build/SKILL.md +962 -0
- package/plugins/pbr/skills/config/SKILL.md +241 -0
- package/plugins/pbr/skills/continue/SKILL.md +127 -0
- package/plugins/pbr/skills/debug/SKILL.md +489 -0
- package/plugins/pbr/skills/debug/templates/continuation-prompt.md.tmpl +16 -0
- package/plugins/pbr/skills/debug/templates/initial-investigation-prompt.md.tmpl +27 -0
- package/plugins/pbr/skills/discuss/SKILL.md +338 -0
- package/plugins/pbr/skills/discuss/templates/CONTEXT.md.tmpl +61 -0
- package/plugins/pbr/skills/discuss/templates/decision-categories.md +9 -0
- package/plugins/pbr/skills/explore/SKILL.md +362 -0
- package/plugins/pbr/skills/health/SKILL.md +186 -0
- package/plugins/pbr/skills/health/templates/check-pattern.md.tmpl +30 -0
- package/plugins/pbr/skills/health/templates/output-format.md.tmpl +63 -0
- package/plugins/pbr/skills/help/SKILL.md +140 -0
- package/plugins/pbr/skills/import/SKILL.md +490 -0
- package/plugins/pbr/skills/milestone/SKILL.md +673 -0
- package/plugins/pbr/skills/milestone/templates/audit-report.md.tmpl +48 -0
- package/plugins/pbr/skills/milestone/templates/stats-file.md.tmpl +30 -0
- package/plugins/pbr/skills/note/SKILL.md +212 -0
- package/plugins/pbr/skills/pause/SKILL.md +235 -0
- package/plugins/pbr/skills/pause/templates/continue-here.md.tmpl +71 -0
- package/plugins/pbr/skills/plan/SKILL.md +628 -0
- package/plugins/pbr/skills/plan/decimal-phase-calc.md +98 -0
- package/plugins/pbr/skills/plan/templates/checker-prompt.md.tmpl +21 -0
- package/plugins/pbr/skills/plan/templates/gap-closure-prompt.md.tmpl +32 -0
- package/plugins/pbr/skills/plan/templates/planner-prompt.md.tmpl +38 -0
- package/plugins/pbr/skills/plan/templates/researcher-prompt.md.tmpl +19 -0
- package/plugins/pbr/skills/plan/templates/revision-prompt.md.tmpl +23 -0
- package/plugins/pbr/skills/quick/SKILL.md +335 -0
- package/plugins/pbr/skills/resume/SKILL.md +388 -0
- package/plugins/pbr/skills/review/SKILL.md +652 -0
- package/plugins/pbr/skills/review/templates/debugger-prompt.md.tmpl +60 -0
- package/plugins/pbr/skills/review/templates/gap-planner-prompt.md.tmpl +40 -0
- package/plugins/pbr/skills/review/templates/verifier-prompt.md.tmpl +115 -0
- package/plugins/pbr/skills/scan/SKILL.md +269 -0
- package/plugins/pbr/skills/scan/templates/mapper-prompt.md.tmpl +201 -0
- package/plugins/pbr/skills/setup/SKILL.md +227 -0
- package/plugins/pbr/skills/shared/commit-planning-docs.md +35 -0
- package/plugins/pbr/skills/shared/config-loading.md +102 -0
- package/plugins/pbr/skills/shared/context-budget.md +40 -0
- package/plugins/pbr/skills/shared/context-loader-task.md +86 -0
- package/plugins/pbr/skills/shared/digest-select.md +79 -0
- package/plugins/pbr/skills/shared/domain-probes.md +125 -0
- package/plugins/pbr/skills/shared/error-reporting.md +79 -0
- package/plugins/pbr/skills/shared/gate-prompts.md +388 -0
- package/plugins/pbr/skills/shared/phase-argument-parsing.md +45 -0
- package/plugins/pbr/skills/shared/progress-display.md +53 -0
- package/plugins/pbr/skills/shared/revision-loop.md +81 -0
- package/plugins/pbr/skills/shared/state-loading.md +62 -0
- package/plugins/pbr/skills/shared/state-update.md +161 -0
- package/plugins/pbr/skills/shared/universal-anti-patterns.md +33 -0
- package/plugins/pbr/skills/status/SKILL.md +353 -0
- package/plugins/pbr/skills/todo/SKILL.md +181 -0
- package/plugins/pbr/templates/CONTEXT.md.tmpl +52 -0
- package/plugins/pbr/templates/INTEGRATION-REPORT.md.tmpl +151 -0
- package/plugins/pbr/templates/RESEARCH-SUMMARY.md.tmpl +97 -0
- package/plugins/pbr/templates/ROADMAP.md.tmpl +40 -0
- package/plugins/pbr/templates/SUMMARY.md.tmpl +81 -0
- package/plugins/pbr/templates/VERIFICATION-DETAIL.md.tmpl +116 -0
- package/plugins/pbr/templates/codebase/ARCHITECTURE.md.tmpl +98 -0
- package/plugins/pbr/templates/codebase/CONCERNS.md.tmpl +93 -0
- package/plugins/pbr/templates/codebase/CONVENTIONS.md.tmpl +104 -0
- package/plugins/pbr/templates/codebase/INTEGRATIONS.md.tmpl +78 -0
- package/plugins/pbr/templates/codebase/STACK.md.tmpl +78 -0
- package/plugins/pbr/templates/codebase/STRUCTURE.md.tmpl +80 -0
- package/plugins/pbr/templates/codebase/TESTING.md.tmpl +107 -0
- package/plugins/pbr/templates/continue-here.md.tmpl +73 -0
- package/plugins/pbr/templates/prompt-partials/phase-project-context.md.tmpl +37 -0
- package/plugins/pbr/templates/research/ARCHITECTURE.md.tmpl +124 -0
- package/plugins/pbr/templates/research/STACK.md.tmpl +71 -0
- package/plugins/pbr/templates/research/SUMMARY.md.tmpl +112 -0
- package/plugins/pbr/templates/research-outputs/phase-research.md.tmpl +81 -0
- package/plugins/pbr/templates/research-outputs/project-research.md.tmpl +99 -0
- package/plugins/pbr/templates/research-outputs/synthesis.md.tmpl +36 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Workflow event logger for Plan-Build-Run observability.
|
|
5
|
+
*
|
|
6
|
+
* Usage as module:
|
|
7
|
+
* const { logEvent } = require('./event-logger');
|
|
8
|
+
* logEvent('workflow', 'phase-start', { phase: 3, name: 'API' });
|
|
9
|
+
*
|
|
10
|
+
* Usage as CLI:
|
|
11
|
+
* node event-logger.js <category> <event> [JSON-details]
|
|
12
|
+
*
|
|
13
|
+
* Log file: .planning/logs/events.jsonl
|
|
14
|
+
* Format: One JSON line per entry (JSONL)
|
|
15
|
+
* Rotation: Keeps last 1000 entries max
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const fs = require('fs');
|
|
19
|
+
const path = require('path');
|
|
20
|
+
|
|
21
|
+
const MAX_ENTRIES = 1000;
|
|
22
|
+
|
|
23
|
+
function getLogPath() {
|
|
24
|
+
const cwd = process.cwd();
|
|
25
|
+
const planningDir = path.join(cwd, '.planning');
|
|
26
|
+
if (!fs.existsSync(planningDir)) return null;
|
|
27
|
+
const logsDir = path.join(planningDir, 'logs');
|
|
28
|
+
if (!fs.existsSync(logsDir)) {
|
|
29
|
+
fs.mkdirSync(logsDir, { recursive: true });
|
|
30
|
+
}
|
|
31
|
+
return path.join(logsDir, 'events.jsonl');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function logEvent(category, event, details = {}) {
|
|
35
|
+
const logPath = getLogPath();
|
|
36
|
+
if (!logPath) return;
|
|
37
|
+
|
|
38
|
+
const entry = {
|
|
39
|
+
ts: new Date().toISOString(),
|
|
40
|
+
cat: category,
|
|
41
|
+
event,
|
|
42
|
+
...details
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
let lines = [];
|
|
47
|
+
if (fs.existsSync(logPath)) {
|
|
48
|
+
const content = fs.readFileSync(logPath, 'utf8').trim();
|
|
49
|
+
if (content) {
|
|
50
|
+
lines = content.split('\n');
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
lines.push(JSON.stringify(entry));
|
|
55
|
+
|
|
56
|
+
if (lines.length > MAX_ENTRIES) {
|
|
57
|
+
lines = lines.slice(lines.length - MAX_ENTRIES);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
fs.writeFileSync(logPath, lines.join('\n') + '\n', 'utf8');
|
|
61
|
+
} catch (_e) {
|
|
62
|
+
// Best-effort logging — never fail the caller
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// CLI mode
|
|
67
|
+
function main() {
|
|
68
|
+
const args = process.argv.slice(2);
|
|
69
|
+
const category = args[0];
|
|
70
|
+
const event = args[1];
|
|
71
|
+
let details = {};
|
|
72
|
+
|
|
73
|
+
if (args[2]) {
|
|
74
|
+
try {
|
|
75
|
+
details = JSON.parse(args[2]);
|
|
76
|
+
} catch (_e) {
|
|
77
|
+
details = { raw: args[2] };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (!category || !event) {
|
|
82
|
+
process.stdout.write(JSON.stringify({ error: 'Usage: event-logger.js <category> <event> [JSON-details]' }));
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
logEvent(category, event, details);
|
|
87
|
+
process.stdout.write(JSON.stringify({ logged: true, category, event }));
|
|
88
|
+
process.exit(0);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (require.main === module) { main(); }
|
|
92
|
+
module.exports = { logEvent };
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Shared hook execution logger.
|
|
5
|
+
*
|
|
6
|
+
* Usage in hooks:
|
|
7
|
+
* const { logHook } = require('./hook-logger');
|
|
8
|
+
* logHook('validate-commit', 'PreToolUse', 'allow', { message: 'chore: ...' });
|
|
9
|
+
*
|
|
10
|
+
* Log file: .planning/logs/hooks.jsonl (in the project's .planning directory)
|
|
11
|
+
* Format: One JSON line per entry (JSONL)
|
|
12
|
+
* Rotation: Keeps last 200 entries max
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
|
|
18
|
+
const MAX_ENTRIES = 200;
|
|
19
|
+
|
|
20
|
+
function getLogPath() {
|
|
21
|
+
const cwd = process.cwd();
|
|
22
|
+
const planningDir = path.join(cwd, '.planning');
|
|
23
|
+
if (!fs.existsSync(planningDir)) return null;
|
|
24
|
+
|
|
25
|
+
const logsDir = path.join(planningDir, 'logs');
|
|
26
|
+
const newPath = path.join(logsDir, 'hooks.jsonl');
|
|
27
|
+
const oldPath = path.join(planningDir, '.hook-log');
|
|
28
|
+
|
|
29
|
+
// Auto-create logs/ directory if it doesn't exist
|
|
30
|
+
if (!fs.existsSync(logsDir)) {
|
|
31
|
+
fs.mkdirSync(logsDir, { recursive: true });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// One-time migration: move old .hook-log to new location
|
|
35
|
+
if (fs.existsSync(oldPath) && !fs.existsSync(newPath)) {
|
|
36
|
+
fs.renameSync(oldPath, newPath);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return newPath;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function logHook(hookName, eventType, decision, details = {}) {
|
|
43
|
+
const logPath = getLogPath();
|
|
44
|
+
if (!logPath) return; // Not a Plan-Build-Run project
|
|
45
|
+
|
|
46
|
+
const entry = {
|
|
47
|
+
ts: new Date().toISOString(),
|
|
48
|
+
hook: hookName,
|
|
49
|
+
event: eventType,
|
|
50
|
+
decision,
|
|
51
|
+
...details
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
let lines = [];
|
|
56
|
+
if (fs.existsSync(logPath)) {
|
|
57
|
+
const content = fs.readFileSync(logPath, 'utf8').trim();
|
|
58
|
+
if (content) {
|
|
59
|
+
lines = content.split('\n');
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
lines.push(JSON.stringify(entry));
|
|
64
|
+
|
|
65
|
+
// Keep only last MAX_ENTRIES
|
|
66
|
+
if (lines.length > MAX_ENTRIES) {
|
|
67
|
+
lines = lines.slice(lines.length - MAX_ENTRIES);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
fs.writeFileSync(logPath, lines.join('\n') + '\n', 'utf8');
|
|
71
|
+
} catch (_e) {
|
|
72
|
+
// Best-effort logging — never fail the hook
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
module.exports = { logHook };
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"title": "Plan-Build-Run Hooks",
|
|
4
|
+
"description": "Schema for Claude Code hooks.json configuration",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"properties": {
|
|
7
|
+
"$schema": { "type": "string" },
|
|
8
|
+
"description": { "type": "string" },
|
|
9
|
+
"hooks": {
|
|
10
|
+
"type": "object",
|
|
11
|
+
"properties": {
|
|
12
|
+
"SessionStart": { "$ref": "#/definitions/hookEntryList" },
|
|
13
|
+
"PreToolUse": { "$ref": "#/definitions/hookEntryList" },
|
|
14
|
+
"PostToolUse": { "$ref": "#/definitions/hookEntryList" },
|
|
15
|
+
"PostToolUseFailure": { "$ref": "#/definitions/hookEntryList" },
|
|
16
|
+
"PreCompact": { "$ref": "#/definitions/hookEntryList" },
|
|
17
|
+
"Stop": { "$ref": "#/definitions/hookEntryList" },
|
|
18
|
+
"SubagentStart": { "$ref": "#/definitions/hookEntryList" },
|
|
19
|
+
"SubagentStop": { "$ref": "#/definitions/hookEntryList" },
|
|
20
|
+
"TaskCompleted": { "$ref": "#/definitions/hookEntryList" },
|
|
21
|
+
"SessionEnd": { "$ref": "#/definitions/hookEntryList" }
|
|
22
|
+
},
|
|
23
|
+
"additionalProperties": false
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"required": ["hooks"],
|
|
27
|
+
"additionalProperties": true,
|
|
28
|
+
"definitions": {
|
|
29
|
+
"hookEntryList": {
|
|
30
|
+
"type": "array",
|
|
31
|
+
"items": { "$ref": "#/definitions/hookEntry" }
|
|
32
|
+
},
|
|
33
|
+
"hookEntry": {
|
|
34
|
+
"type": "object",
|
|
35
|
+
"properties": {
|
|
36
|
+
"matcher": {
|
|
37
|
+
"type": "string",
|
|
38
|
+
"description": "Regex tested against the tool name (e.g. 'Write|Edit', 'Bash', 'Task')"
|
|
39
|
+
},
|
|
40
|
+
"hooks": {
|
|
41
|
+
"type": "array",
|
|
42
|
+
"items": { "$ref": "#/definitions/hookCommand" }
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"required": ["hooks"],
|
|
46
|
+
"additionalProperties": false
|
|
47
|
+
},
|
|
48
|
+
"hookCommand": {
|
|
49
|
+
"type": "object",
|
|
50
|
+
"properties": {
|
|
51
|
+
"type": {
|
|
52
|
+
"type": "string",
|
|
53
|
+
"enum": ["command"],
|
|
54
|
+
"description": "Hook type — currently only 'command' is supported"
|
|
55
|
+
},
|
|
56
|
+
"command": {
|
|
57
|
+
"type": "string",
|
|
58
|
+
"description": "Shell command to execute (use ${CLAUDE_PLUGIN_ROOT} for script paths)"
|
|
59
|
+
},
|
|
60
|
+
"statusMessage": {
|
|
61
|
+
"type": "string",
|
|
62
|
+
"description": "Status message shown in Claude Code UI while hook runs"
|
|
63
|
+
},
|
|
64
|
+
"async": {
|
|
65
|
+
"type": "boolean",
|
|
66
|
+
"description": "Run in background without blocking (stdout not captured)",
|
|
67
|
+
"default": false
|
|
68
|
+
},
|
|
69
|
+
"timeout": {
|
|
70
|
+
"type": "number",
|
|
71
|
+
"minimum": 1,
|
|
72
|
+
"description": "Maximum seconds before hook is killed (required when async: true)"
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
"required": ["type", "command"],
|
|
76
|
+
"additionalProperties": false
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* SubagentStart / SubagentStop logging hook.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* node log-subagent.js start — called on SubagentStart
|
|
8
|
+
* node log-subagent.js stop — called on SubagentStop
|
|
9
|
+
*
|
|
10
|
+
* On start: logs spawn event and injects project context via additionalContext.
|
|
11
|
+
* On stop: logs completion event.
|
|
12
|
+
*
|
|
13
|
+
* Non-blocking — exits 0 always.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const path = require('path');
|
|
18
|
+
const { logHook } = require('./hook-logger');
|
|
19
|
+
const { logEvent } = require('./event-logger');
|
|
20
|
+
const { configLoad } = require('./pbr-tools');
|
|
21
|
+
|
|
22
|
+
function readStdin() {
|
|
23
|
+
try {
|
|
24
|
+
const input = fs.readFileSync(0, 'utf8').trim();
|
|
25
|
+
if (input) return JSON.parse(input);
|
|
26
|
+
} catch (_e) {
|
|
27
|
+
// empty or non-JSON stdin
|
|
28
|
+
}
|
|
29
|
+
return {};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function main() {
|
|
33
|
+
const action = process.argv[2]; // 'start' or 'stop'
|
|
34
|
+
const data = readStdin();
|
|
35
|
+
|
|
36
|
+
if (action === 'start') {
|
|
37
|
+
logHook('log-subagent', 'SubagentStart', 'spawned', {
|
|
38
|
+
agent_id: data.agent_id || null,
|
|
39
|
+
agent_type: data.agent_type || data.subagent_type || null,
|
|
40
|
+
description: data.description || null
|
|
41
|
+
});
|
|
42
|
+
logEvent('agent', 'spawn', {
|
|
43
|
+
agent_id: data.agent_id || null,
|
|
44
|
+
agent_type: data.agent_type || data.subagent_type || null,
|
|
45
|
+
description: data.description || null
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// Write .active-agent signal so other hooks know a subagent is running
|
|
49
|
+
writeActiveAgent(data.agent_type || data.subagent_type || 'unknown');
|
|
50
|
+
|
|
51
|
+
// Inject project context into subagent
|
|
52
|
+
const context = buildAgentContext();
|
|
53
|
+
if (context) {
|
|
54
|
+
const output = {
|
|
55
|
+
hookSpecificOutput: {
|
|
56
|
+
hookEventName: 'SubagentStart',
|
|
57
|
+
additionalContext: context
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
process.stdout.write(JSON.stringify(output));
|
|
61
|
+
}
|
|
62
|
+
} else if (action === 'stop') {
|
|
63
|
+
// Remove .active-agent signal
|
|
64
|
+
removeActiveAgent();
|
|
65
|
+
logHook('log-subagent', 'SubagentStop', 'completed', {
|
|
66
|
+
agent_id: data.agent_id || null,
|
|
67
|
+
agent_type: data.agent_type || data.subagent_type || null,
|
|
68
|
+
duration_ms: data.duration_ms || null
|
|
69
|
+
});
|
|
70
|
+
logEvent('agent', 'complete', {
|
|
71
|
+
agent_id: data.agent_id || null,
|
|
72
|
+
agent_type: data.agent_type || data.subagent_type || null,
|
|
73
|
+
duration_ms: data.duration_ms || null
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
process.exit(0);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function writeActiveAgent(agentType) {
|
|
81
|
+
try {
|
|
82
|
+
const cwd = process.cwd();
|
|
83
|
+
const filePath = path.join(cwd, '.planning', '.active-agent');
|
|
84
|
+
if (fs.existsSync(path.join(cwd, '.planning'))) {
|
|
85
|
+
fs.writeFileSync(filePath, agentType, 'utf8');
|
|
86
|
+
}
|
|
87
|
+
} catch (_e) {
|
|
88
|
+
// Best-effort
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function removeActiveAgent() {
|
|
93
|
+
try {
|
|
94
|
+
const cwd = process.cwd();
|
|
95
|
+
const filePath = path.join(cwd, '.planning', '.active-agent');
|
|
96
|
+
if (fs.existsSync(filePath)) {
|
|
97
|
+
fs.unlinkSync(filePath);
|
|
98
|
+
}
|
|
99
|
+
} catch (_e) {
|
|
100
|
+
// Best-effort
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function buildAgentContext() {
|
|
105
|
+
const cwd = process.cwd();
|
|
106
|
+
const planningDir = path.join(cwd, '.planning');
|
|
107
|
+
|
|
108
|
+
if (!fs.existsSync(planningDir)) return '';
|
|
109
|
+
|
|
110
|
+
const parts = [];
|
|
111
|
+
|
|
112
|
+
// Current phase and status from STATE.md
|
|
113
|
+
const stateFile = path.join(planningDir, 'STATE.md');
|
|
114
|
+
if (fs.existsSync(stateFile)) {
|
|
115
|
+
try {
|
|
116
|
+
const state = fs.readFileSync(stateFile, 'utf8');
|
|
117
|
+
const phaseMatch = state.match(/Phase:\s*(\d+)\s+of\s+(\d+)/);
|
|
118
|
+
const statusMatch = state.match(/\*{0,2}(?:Phase\s+)?Status\*{0,2}:\s*["']?(\w+)["']?/i);
|
|
119
|
+
if (phaseMatch) {
|
|
120
|
+
parts.push(`Phase ${phaseMatch[1]} of ${phaseMatch[2]}${statusMatch ? ' (' + statusMatch[1] + ')' : ''}`);
|
|
121
|
+
}
|
|
122
|
+
} catch (_e) {
|
|
123
|
+
// skip
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Active skill context
|
|
128
|
+
const activeSkillFile = path.join(planningDir, '.active-skill');
|
|
129
|
+
if (fs.existsSync(activeSkillFile)) {
|
|
130
|
+
try {
|
|
131
|
+
const skill = fs.readFileSync(activeSkillFile, 'utf8').trim();
|
|
132
|
+
if (skill) parts.push(`Active skill: /pbr:${skill}`);
|
|
133
|
+
} catch (_e) {
|
|
134
|
+
// skip
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Config highlights
|
|
139
|
+
const config = configLoad(planningDir);
|
|
140
|
+
if (config) {
|
|
141
|
+
const configParts = [];
|
|
142
|
+
if (config.depth) configParts.push(`depth=${config.depth}`);
|
|
143
|
+
if (config.git && config.git.auto_commit !== undefined) configParts.push(`auto_commit=${config.git.auto_commit}`);
|
|
144
|
+
if (configParts.length > 0) parts.push(`Config: ${configParts.join(', ')}`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (parts.length === 0) return '';
|
|
148
|
+
return '[Plan-Build-Run Project Context] ' + parts.join(' | ');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
module.exports = { buildAgentContext };
|
|
152
|
+
if (require.main === module) { main(); }
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* PostToolUseFailure hook: Logs tool failures to events.jsonl
|
|
5
|
+
* and provides recovery hints via additionalContext.
|
|
6
|
+
*
|
|
7
|
+
* Fires when any tool execution fails. Captures tool name, error,
|
|
8
|
+
* and session context for debugging.
|
|
9
|
+
*
|
|
10
|
+
* Exit codes:
|
|
11
|
+
* 0 = always (informational hook, never blocks)
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const { logHook } = require('./hook-logger');
|
|
15
|
+
const { logEvent } = require('./event-logger');
|
|
16
|
+
|
|
17
|
+
function readStdin() {
|
|
18
|
+
return new Promise((resolve) => {
|
|
19
|
+
let input = '';
|
|
20
|
+
process.stdin.setEncoding('utf8');
|
|
21
|
+
process.stdin.on('data', (chunk) => { input += chunk; });
|
|
22
|
+
process.stdin.on('end', () => {
|
|
23
|
+
try {
|
|
24
|
+
resolve(JSON.parse(input));
|
|
25
|
+
} catch (_e) {
|
|
26
|
+
resolve({});
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function main() {
|
|
33
|
+
const data = await readStdin();
|
|
34
|
+
|
|
35
|
+
const toolName = data.tool_name || 'unknown';
|
|
36
|
+
const error = data.error || 'unknown error';
|
|
37
|
+
const isInterrupt = data.is_interrupt || false;
|
|
38
|
+
const toolInput = data.tool_input || {};
|
|
39
|
+
|
|
40
|
+
// Log to hooks.jsonl
|
|
41
|
+
logHook('log-tool-failure', 'PostToolUseFailure', 'logged', {
|
|
42
|
+
tool: toolName,
|
|
43
|
+
error: typeof error === 'string' ? error.substring(0, 200) : JSON.stringify(error).substring(0, 200),
|
|
44
|
+
interrupt: isInterrupt
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Log to events.jsonl with more detail
|
|
48
|
+
logEvent('tool', 'failure', {
|
|
49
|
+
tool: toolName,
|
|
50
|
+
error: typeof error === 'string' ? error.substring(0, 500) : JSON.stringify(error).substring(0, 500),
|
|
51
|
+
interrupt: isInterrupt,
|
|
52
|
+
input_summary: summarizeInput(toolName, toolInput)
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Provide recovery hints for Bash failures (most common actionable failure)
|
|
56
|
+
if (toolName === 'Bash' && !isInterrupt) {
|
|
57
|
+
const output = {
|
|
58
|
+
hookSpecificOutput: {
|
|
59
|
+
hookEventName: 'PostToolUseFailure',
|
|
60
|
+
additionalContext: 'Bash command failed. If this is a recurring issue, consider using /pbr:debug for systematic investigation.'
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
process.stdout.write(JSON.stringify(output));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
process.exit(0);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function summarizeInput(toolName, toolInput) {
|
|
70
|
+
switch (toolName) {
|
|
71
|
+
case 'Bash':
|
|
72
|
+
return (toolInput.command || '').substring(0, 100);
|
|
73
|
+
case 'Write':
|
|
74
|
+
case 'Read':
|
|
75
|
+
case 'Edit':
|
|
76
|
+
return toolInput.file_path || '';
|
|
77
|
+
case 'Glob':
|
|
78
|
+
return toolInput.pattern || '';
|
|
79
|
+
case 'Grep':
|
|
80
|
+
return toolInput.pattern || '';
|
|
81
|
+
case 'Task':
|
|
82
|
+
return (toolInput.description || '').substring(0, 100);
|
|
83
|
+
default:
|
|
84
|
+
return '';
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
main();
|