@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.
Files changed (221) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/CLAUDE.md +149 -0
  3. package/LICENSE +21 -0
  4. package/README.md +247 -0
  5. package/dashboard/bin/cli.js +25 -0
  6. package/dashboard/package.json +34 -0
  7. package/dashboard/public/.gitkeep +0 -0
  8. package/dashboard/public/css/layout.css +406 -0
  9. package/dashboard/public/css/status-colors.css +98 -0
  10. package/dashboard/public/js/htmx-title.js +5 -0
  11. package/dashboard/public/js/sidebar-toggle.js +20 -0
  12. package/dashboard/src/app.js +78 -0
  13. package/dashboard/src/middleware/errorHandler.js +52 -0
  14. package/dashboard/src/middleware/notFoundHandler.js +9 -0
  15. package/dashboard/src/repositories/planning.repository.js +128 -0
  16. package/dashboard/src/routes/events.routes.js +40 -0
  17. package/dashboard/src/routes/index.routes.js +31 -0
  18. package/dashboard/src/routes/pages.routes.js +195 -0
  19. package/dashboard/src/server.js +42 -0
  20. package/dashboard/src/services/dashboard.service.js +222 -0
  21. package/dashboard/src/services/phase.service.js +167 -0
  22. package/dashboard/src/services/project.service.js +57 -0
  23. package/dashboard/src/services/roadmap.service.js +171 -0
  24. package/dashboard/src/services/sse.service.js +58 -0
  25. package/dashboard/src/services/todo.service.js +254 -0
  26. package/dashboard/src/services/watcher.service.js +48 -0
  27. package/dashboard/src/views/coming-soon.ejs +11 -0
  28. package/dashboard/src/views/error.ejs +13 -0
  29. package/dashboard/src/views/index.ejs +5 -0
  30. package/dashboard/src/views/layout.ejs +1 -0
  31. package/dashboard/src/views/partials/dashboard-content.ejs +77 -0
  32. package/dashboard/src/views/partials/footer.ejs +3 -0
  33. package/dashboard/src/views/partials/head.ejs +21 -0
  34. package/dashboard/src/views/partials/header.ejs +12 -0
  35. package/dashboard/src/views/partials/layout-bottom.ejs +15 -0
  36. package/dashboard/src/views/partials/layout-top.ejs +8 -0
  37. package/dashboard/src/views/partials/phase-content.ejs +181 -0
  38. package/dashboard/src/views/partials/phases-content.ejs +117 -0
  39. package/dashboard/src/views/partials/roadmap-content.ejs +142 -0
  40. package/dashboard/src/views/partials/sidebar.ejs +38 -0
  41. package/dashboard/src/views/partials/todo-create-content.ejs +53 -0
  42. package/dashboard/src/views/partials/todo-detail-content.ejs +38 -0
  43. package/dashboard/src/views/partials/todos-content.ejs +53 -0
  44. package/dashboard/src/views/phase-detail.ejs +5 -0
  45. package/dashboard/src/views/phases.ejs +5 -0
  46. package/dashboard/src/views/roadmap.ejs +5 -0
  47. package/dashboard/src/views/todo-create.ejs +5 -0
  48. package/dashboard/src/views/todo-detail.ejs +5 -0
  49. package/dashboard/src/views/todos.ejs +5 -0
  50. package/package.json +57 -0
  51. package/plugins/pbr/.claude-plugin/plugin.json +13 -0
  52. package/plugins/pbr/UI-CONSISTENCY-GAPS.md +61 -0
  53. package/plugins/pbr/agents/codebase-mapper.md +271 -0
  54. package/plugins/pbr/agents/debugger.md +281 -0
  55. package/plugins/pbr/agents/executor.md +407 -0
  56. package/plugins/pbr/agents/general.md +164 -0
  57. package/plugins/pbr/agents/integration-checker.md +141 -0
  58. package/plugins/pbr/agents/plan-checker.md +280 -0
  59. package/plugins/pbr/agents/planner.md +358 -0
  60. package/plugins/pbr/agents/researcher.md +363 -0
  61. package/plugins/pbr/agents/synthesizer.md +230 -0
  62. package/plugins/pbr/agents/verifier.md +454 -0
  63. package/plugins/pbr/commands/begin.md +5 -0
  64. package/plugins/pbr/commands/build.md +5 -0
  65. package/plugins/pbr/commands/config.md +5 -0
  66. package/plugins/pbr/commands/continue.md +5 -0
  67. package/plugins/pbr/commands/debug.md +5 -0
  68. package/plugins/pbr/commands/discuss.md +5 -0
  69. package/plugins/pbr/commands/explore.md +5 -0
  70. package/plugins/pbr/commands/health.md +5 -0
  71. package/plugins/pbr/commands/help.md +5 -0
  72. package/plugins/pbr/commands/import.md +5 -0
  73. package/plugins/pbr/commands/milestone.md +5 -0
  74. package/plugins/pbr/commands/note.md +5 -0
  75. package/plugins/pbr/commands/pause.md +5 -0
  76. package/plugins/pbr/commands/plan.md +5 -0
  77. package/plugins/pbr/commands/quick.md +5 -0
  78. package/plugins/pbr/commands/resume.md +5 -0
  79. package/plugins/pbr/commands/review.md +5 -0
  80. package/plugins/pbr/commands/scan.md +5 -0
  81. package/plugins/pbr/commands/setup.md +5 -0
  82. package/plugins/pbr/commands/status.md +5 -0
  83. package/plugins/pbr/commands/todo.md +5 -0
  84. package/plugins/pbr/contexts/dev.md +27 -0
  85. package/plugins/pbr/contexts/research.md +28 -0
  86. package/plugins/pbr/contexts/review.md +36 -0
  87. package/plugins/pbr/hooks/hooks.json +183 -0
  88. package/plugins/pbr/references/agent-anti-patterns.md +24 -0
  89. package/plugins/pbr/references/agent-interactions.md +134 -0
  90. package/plugins/pbr/references/agent-teams.md +54 -0
  91. package/plugins/pbr/references/checkpoints.md +157 -0
  92. package/plugins/pbr/references/common-bug-patterns.md +13 -0
  93. package/plugins/pbr/references/continuation-format.md +212 -0
  94. package/plugins/pbr/references/deviation-rules.md +112 -0
  95. package/plugins/pbr/references/git-integration.md +226 -0
  96. package/plugins/pbr/references/integration-patterns.md +117 -0
  97. package/plugins/pbr/references/model-profiles.md +99 -0
  98. package/plugins/pbr/references/model-selection.md +31 -0
  99. package/plugins/pbr/references/pbr-rules.md +193 -0
  100. package/plugins/pbr/references/plan-authoring.md +181 -0
  101. package/plugins/pbr/references/plan-format.md +283 -0
  102. package/plugins/pbr/references/planning-config.md +213 -0
  103. package/plugins/pbr/references/questioning.md +214 -0
  104. package/plugins/pbr/references/reading-verification.md +127 -0
  105. package/plugins/pbr/references/stub-patterns.md +160 -0
  106. package/plugins/pbr/references/subagent-coordination.md +119 -0
  107. package/plugins/pbr/references/ui-formatting.md +399 -0
  108. package/plugins/pbr/references/verification-patterns.md +198 -0
  109. package/plugins/pbr/references/wave-execution.md +95 -0
  110. package/plugins/pbr/scripts/auto-continue.js +80 -0
  111. package/plugins/pbr/scripts/check-dangerous-commands.js +136 -0
  112. package/plugins/pbr/scripts/check-doc-sprawl.js +102 -0
  113. package/plugins/pbr/scripts/check-phase-boundary.js +196 -0
  114. package/plugins/pbr/scripts/check-plan-format.js +270 -0
  115. package/plugins/pbr/scripts/check-roadmap-sync.js +252 -0
  116. package/plugins/pbr/scripts/check-skill-workflow.js +262 -0
  117. package/plugins/pbr/scripts/check-state-sync.js +476 -0
  118. package/plugins/pbr/scripts/check-subagent-output.js +144 -0
  119. package/plugins/pbr/scripts/config-schema.json +251 -0
  120. package/plugins/pbr/scripts/context-budget-check.js +287 -0
  121. package/plugins/pbr/scripts/event-handler.js +151 -0
  122. package/plugins/pbr/scripts/event-logger.js +92 -0
  123. package/plugins/pbr/scripts/hook-logger.js +76 -0
  124. package/plugins/pbr/scripts/hooks-schema.json +79 -0
  125. package/plugins/pbr/scripts/log-subagent.js +152 -0
  126. package/plugins/pbr/scripts/log-tool-failure.js +88 -0
  127. package/plugins/pbr/scripts/pbr-tools.js +1301 -0
  128. package/plugins/pbr/scripts/post-write-dispatch.js +66 -0
  129. package/plugins/pbr/scripts/post-write-quality.js +207 -0
  130. package/plugins/pbr/scripts/pre-bash-dispatch.js +56 -0
  131. package/plugins/pbr/scripts/pre-write-dispatch.js +62 -0
  132. package/plugins/pbr/scripts/progress-tracker.js +228 -0
  133. package/plugins/pbr/scripts/session-cleanup.js +254 -0
  134. package/plugins/pbr/scripts/status-line.js +285 -0
  135. package/plugins/pbr/scripts/suggest-compact.js +119 -0
  136. package/plugins/pbr/scripts/task-completed.js +45 -0
  137. package/plugins/pbr/scripts/track-context-budget.js +119 -0
  138. package/plugins/pbr/scripts/validate-commit.js +200 -0
  139. package/plugins/pbr/scripts/validate-plugin-structure.js +172 -0
  140. package/plugins/pbr/skills/begin/SKILL.md +545 -0
  141. package/plugins/pbr/skills/begin/templates/PROJECT.md.tmpl +33 -0
  142. package/plugins/pbr/skills/begin/templates/REQUIREMENTS.md.tmpl +18 -0
  143. package/plugins/pbr/skills/begin/templates/STATE.md.tmpl +49 -0
  144. package/plugins/pbr/skills/begin/templates/config.json.tmpl +63 -0
  145. package/plugins/pbr/skills/begin/templates/researcher-prompt.md.tmpl +19 -0
  146. package/plugins/pbr/skills/begin/templates/roadmap-prompt.md.tmpl +30 -0
  147. package/plugins/pbr/skills/begin/templates/synthesis-prompt.md.tmpl +16 -0
  148. package/plugins/pbr/skills/build/SKILL.md +962 -0
  149. package/plugins/pbr/skills/config/SKILL.md +241 -0
  150. package/plugins/pbr/skills/continue/SKILL.md +127 -0
  151. package/plugins/pbr/skills/debug/SKILL.md +489 -0
  152. package/plugins/pbr/skills/debug/templates/continuation-prompt.md.tmpl +16 -0
  153. package/plugins/pbr/skills/debug/templates/initial-investigation-prompt.md.tmpl +27 -0
  154. package/plugins/pbr/skills/discuss/SKILL.md +338 -0
  155. package/plugins/pbr/skills/discuss/templates/CONTEXT.md.tmpl +61 -0
  156. package/plugins/pbr/skills/discuss/templates/decision-categories.md +9 -0
  157. package/plugins/pbr/skills/explore/SKILL.md +362 -0
  158. package/plugins/pbr/skills/health/SKILL.md +186 -0
  159. package/plugins/pbr/skills/health/templates/check-pattern.md.tmpl +30 -0
  160. package/plugins/pbr/skills/health/templates/output-format.md.tmpl +63 -0
  161. package/plugins/pbr/skills/help/SKILL.md +140 -0
  162. package/plugins/pbr/skills/import/SKILL.md +490 -0
  163. package/plugins/pbr/skills/milestone/SKILL.md +673 -0
  164. package/plugins/pbr/skills/milestone/templates/audit-report.md.tmpl +48 -0
  165. package/plugins/pbr/skills/milestone/templates/stats-file.md.tmpl +30 -0
  166. package/plugins/pbr/skills/note/SKILL.md +212 -0
  167. package/plugins/pbr/skills/pause/SKILL.md +235 -0
  168. package/plugins/pbr/skills/pause/templates/continue-here.md.tmpl +71 -0
  169. package/plugins/pbr/skills/plan/SKILL.md +628 -0
  170. package/plugins/pbr/skills/plan/decimal-phase-calc.md +98 -0
  171. package/plugins/pbr/skills/plan/templates/checker-prompt.md.tmpl +21 -0
  172. package/plugins/pbr/skills/plan/templates/gap-closure-prompt.md.tmpl +32 -0
  173. package/plugins/pbr/skills/plan/templates/planner-prompt.md.tmpl +38 -0
  174. package/plugins/pbr/skills/plan/templates/researcher-prompt.md.tmpl +19 -0
  175. package/plugins/pbr/skills/plan/templates/revision-prompt.md.tmpl +23 -0
  176. package/plugins/pbr/skills/quick/SKILL.md +335 -0
  177. package/plugins/pbr/skills/resume/SKILL.md +388 -0
  178. package/plugins/pbr/skills/review/SKILL.md +652 -0
  179. package/plugins/pbr/skills/review/templates/debugger-prompt.md.tmpl +60 -0
  180. package/plugins/pbr/skills/review/templates/gap-planner-prompt.md.tmpl +40 -0
  181. package/plugins/pbr/skills/review/templates/verifier-prompt.md.tmpl +115 -0
  182. package/plugins/pbr/skills/scan/SKILL.md +269 -0
  183. package/plugins/pbr/skills/scan/templates/mapper-prompt.md.tmpl +201 -0
  184. package/plugins/pbr/skills/setup/SKILL.md +227 -0
  185. package/plugins/pbr/skills/shared/commit-planning-docs.md +35 -0
  186. package/plugins/pbr/skills/shared/config-loading.md +102 -0
  187. package/plugins/pbr/skills/shared/context-budget.md +40 -0
  188. package/plugins/pbr/skills/shared/context-loader-task.md +86 -0
  189. package/plugins/pbr/skills/shared/digest-select.md +79 -0
  190. package/plugins/pbr/skills/shared/domain-probes.md +125 -0
  191. package/plugins/pbr/skills/shared/error-reporting.md +79 -0
  192. package/plugins/pbr/skills/shared/gate-prompts.md +388 -0
  193. package/plugins/pbr/skills/shared/phase-argument-parsing.md +45 -0
  194. package/plugins/pbr/skills/shared/progress-display.md +53 -0
  195. package/plugins/pbr/skills/shared/revision-loop.md +81 -0
  196. package/plugins/pbr/skills/shared/state-loading.md +62 -0
  197. package/plugins/pbr/skills/shared/state-update.md +161 -0
  198. package/plugins/pbr/skills/shared/universal-anti-patterns.md +33 -0
  199. package/plugins/pbr/skills/status/SKILL.md +353 -0
  200. package/plugins/pbr/skills/todo/SKILL.md +181 -0
  201. package/plugins/pbr/templates/CONTEXT.md.tmpl +52 -0
  202. package/plugins/pbr/templates/INTEGRATION-REPORT.md.tmpl +151 -0
  203. package/plugins/pbr/templates/RESEARCH-SUMMARY.md.tmpl +97 -0
  204. package/plugins/pbr/templates/ROADMAP.md.tmpl +40 -0
  205. package/plugins/pbr/templates/SUMMARY.md.tmpl +81 -0
  206. package/plugins/pbr/templates/VERIFICATION-DETAIL.md.tmpl +116 -0
  207. package/plugins/pbr/templates/codebase/ARCHITECTURE.md.tmpl +98 -0
  208. package/plugins/pbr/templates/codebase/CONCERNS.md.tmpl +93 -0
  209. package/plugins/pbr/templates/codebase/CONVENTIONS.md.tmpl +104 -0
  210. package/plugins/pbr/templates/codebase/INTEGRATIONS.md.tmpl +78 -0
  211. package/plugins/pbr/templates/codebase/STACK.md.tmpl +78 -0
  212. package/plugins/pbr/templates/codebase/STRUCTURE.md.tmpl +80 -0
  213. package/plugins/pbr/templates/codebase/TESTING.md.tmpl +107 -0
  214. package/plugins/pbr/templates/continue-here.md.tmpl +73 -0
  215. package/plugins/pbr/templates/prompt-partials/phase-project-context.md.tmpl +37 -0
  216. package/plugins/pbr/templates/research/ARCHITECTURE.md.tmpl +124 -0
  217. package/plugins/pbr/templates/research/STACK.md.tmpl +71 -0
  218. package/plugins/pbr/templates/research/SUMMARY.md.tmpl +112 -0
  219. package/plugins/pbr/templates/research-outputs/phase-research.md.tmpl +81 -0
  220. package/plugins/pbr/templates/research-outputs/project-research.md.tmpl +99 -0
  221. package/plugins/pbr/templates/research-outputs/synthesis.md.tmpl +36 -0
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * PostToolUse dispatcher for Write|Edit hooks.
5
+ *
6
+ * Consolidates check-plan-format.js and check-roadmap-sync.js
7
+ * into a single process, reading stdin once and routing to the
8
+ * appropriate check based on the file path. This halves the
9
+ * process spawns per Write/Edit call.
10
+ *
11
+ * Routing:
12
+ * - PLAN.md or SUMMARY*.md → plan format validation
13
+ * - STATE.md → roadmap sync check
14
+ * - SUMMARY*.md or VERIFICATION.md in .planning/phases/ → state sync (auto-update tracking files)
15
+ * - Other files → exit immediately (no work needed)
16
+ *
17
+ * Exit codes:
18
+ * 0 = always (PostToolUse hooks are advisory)
19
+ */
20
+
21
+ const { checkPlanWrite } = require('./check-plan-format');
22
+ const { checkSync } = require('./check-roadmap-sync');
23
+ const { checkStateSync } = require('./check-state-sync');
24
+
25
+ function main() {
26
+ let input = '';
27
+
28
+ process.stdin.setEncoding('utf8');
29
+ process.stdin.on('data', (chunk) => { input += chunk; });
30
+ process.stdin.on('end', () => {
31
+ try {
32
+ const data = JSON.parse(input);
33
+
34
+ // Plan format check (PLAN.md, SUMMARY*.md)
35
+ // Note: SUMMARY files intentionally trigger BOTH this check AND the state-sync
36
+ // check below. The plan format check validates frontmatter structure, while
37
+ // state-sync auto-updates ROADMAP.md and STATE.md tracking fields.
38
+ const planResult = checkPlanWrite(data);
39
+ if (planResult) {
40
+ process.stdout.write(JSON.stringify(planResult.output));
41
+ process.exit(0);
42
+ }
43
+
44
+ // Roadmap sync check (STATE.md)
45
+ const syncResult = checkSync(data);
46
+ if (syncResult) {
47
+ process.stdout.write(JSON.stringify(syncResult.output));
48
+ process.exit(0);
49
+ }
50
+
51
+ // State sync check (SUMMARY/VERIFICATION → STATE.md + ROADMAP.md)
52
+ const stateSyncResult = checkStateSync(data);
53
+ if (stateSyncResult) {
54
+ process.stdout.write(JSON.stringify(stateSyncResult.output));
55
+ process.exit(0);
56
+ }
57
+
58
+ process.exit(0);
59
+ } catch (_e) {
60
+ // Don't block on parse errors
61
+ process.exit(0);
62
+ }
63
+ });
64
+ }
65
+
66
+ if (require.main === module) { main(); }
@@ -0,0 +1,207 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * PostToolUse quality check hook for Write|Edit.
5
+ *
6
+ * Runs opt-in code quality checks on modified JS/TS files:
7
+ * 1. autoFormat: Run Prettier on the file (requires local installation)
8
+ * 2. typeCheck: Run tsc --noEmit filtered to the edited file (requires local typescript)
9
+ * 3. detectConsoleLogs: Warn about console.log statements left in the file
10
+ *
11
+ * All checks disabled by default. Enable via .planning/config.json:
12
+ * { "hooks": { "autoFormat": true, "typeCheck": true, "detectConsoleLogs": true } }
13
+ *
14
+ * Only processes JS/TS files (.js, .jsx, .ts, .tsx, .mjs, .cjs).
15
+ * Silently skips if tools (prettier, tsc) are not installed in the project.
16
+ *
17
+ * Exit codes:
18
+ * 0 = always (PostToolUse hook, advisory only)
19
+ */
20
+
21
+ const fs = require('fs');
22
+ const path = require('path');
23
+ const { execSync } = require('child_process');
24
+ const { logHook } = require('./hook-logger');
25
+
26
+ const JS_TS_EXTENSIONS = new Set(['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs']);
27
+ const TS_EXTENSIONS = new Set(['.ts', '.tsx']);
28
+
29
+ function main() {
30
+ let input = '';
31
+
32
+ process.stdin.setEncoding('utf8');
33
+ process.stdin.on('data', (chunk) => { input += chunk; });
34
+ process.stdin.on('end', () => {
35
+ try {
36
+ const data = JSON.parse(input);
37
+ const result = checkQuality(data);
38
+ if (result) {
39
+ process.stdout.write(JSON.stringify(result.output));
40
+ }
41
+ process.exit(0);
42
+ } catch (_e) {
43
+ process.exit(0);
44
+ }
45
+ });
46
+ }
47
+
48
+ /**
49
+ * Core quality check logic for use by dispatchers or standalone.
50
+ * @param {Object} data - Parsed hook input (tool_input, etc.)
51
+ * @returns {null|{output: Object}} null if no checks fired, result otherwise
52
+ */
53
+ function checkQuality(data) {
54
+ const filePath = data.tool_input?.file_path || data.tool_input?.path || '';
55
+ if (!filePath) return null;
56
+
57
+ const ext = path.extname(filePath).toLowerCase();
58
+ if (!JS_TS_EXTENSIONS.has(ext)) return null;
59
+
60
+ const cwd = process.cwd();
61
+ const config = loadHooksConfig(cwd);
62
+
63
+ // No quality hooks enabled — early exit
64
+ if (!config.autoFormat && !config.typeCheck && !config.detectConsoleLogs) return null;
65
+
66
+ const messages = [];
67
+
68
+ // 1. Auto-format with Prettier
69
+ if (config.autoFormat) {
70
+ const msg = runPrettier(filePath, cwd);
71
+ if (msg) messages.push(msg);
72
+ }
73
+
74
+ // 2. TypeScript type-check (only for .ts/.tsx)
75
+ if (config.typeCheck && TS_EXTENSIONS.has(ext)) {
76
+ const msg = runTypeCheck(filePath, cwd);
77
+ if (msg) messages.push(msg);
78
+ }
79
+
80
+ // 3. Console.log detection
81
+ if (config.detectConsoleLogs) {
82
+ const msg = detectConsoleLogs(filePath);
83
+ if (msg) messages.push(msg);
84
+ }
85
+
86
+ if (messages.length === 0) return null;
87
+
88
+ logHook('post-write-quality', 'PostToolUse', 'quality-check', {
89
+ file: path.basename(filePath),
90
+ checks: messages.length
91
+ });
92
+
93
+ return {
94
+ output: {
95
+ additionalContext: messages.join('\n')
96
+ }
97
+ };
98
+ }
99
+
100
+ /**
101
+ * Load the hooks section from .planning/config.json.
102
+ * Returns {} if not found or not configured.
103
+ */
104
+ function loadHooksConfig(cwd) {
105
+ const configPath = path.join(cwd, '.planning', 'config.json');
106
+ if (!fs.existsSync(configPath)) return {};
107
+ try {
108
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
109
+ return config.hooks || {};
110
+ } catch (_e) {
111
+ return {};
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Find a locally installed binary in node_modules/.bin/.
117
+ * Returns the full path or null if not found.
118
+ */
119
+ function findLocalBin(cwd, name) {
120
+ const candidates = process.platform === 'win32'
121
+ ? [path.join(cwd, 'node_modules', '.bin', name + '.cmd'),
122
+ path.join(cwd, 'node_modules', '.bin', name)]
123
+ : [path.join(cwd, 'node_modules', '.bin', name)];
124
+ return candidates.find(c => fs.existsSync(c)) || null;
125
+ }
126
+
127
+ /**
128
+ * Run Prettier on the file. Returns a message string or null.
129
+ */
130
+ function runPrettier(filePath, cwd) {
131
+ const bin = findLocalBin(cwd, 'prettier');
132
+ if (!bin) return null;
133
+
134
+ try {
135
+ execSync(`"${bin}" --write "${filePath}"`, {
136
+ cwd,
137
+ timeout: 15000,
138
+ stdio: 'pipe',
139
+ });
140
+ return `[Auto-format] Prettier reformatted ${path.basename(filePath)}. File on disk may differ from context — re-read before further edits.`;
141
+ } catch (_e) {
142
+ // Prettier failed (syntax error, unsupported file, etc.) — skip
143
+ return null;
144
+ }
145
+ }
146
+
147
+ /**
148
+ * Run tsc --noEmit and filter to errors in the modified file.
149
+ * Returns a message string or null.
150
+ */
151
+ function runTypeCheck(filePath, cwd) {
152
+ const bin = findLocalBin(cwd, 'tsc');
153
+ if (!bin) return null;
154
+ if (!fs.existsSync(path.join(cwd, 'tsconfig.json'))) return null;
155
+
156
+ try {
157
+ execSync(`"${bin}" --noEmit`, {
158
+ cwd,
159
+ timeout: 30000,
160
+ stdio: 'pipe',
161
+ encoding: 'utf8',
162
+ });
163
+ return null; // Clean pass — no errors
164
+ } catch (e) {
165
+ const output = (e.stdout || '').toString();
166
+ const basename = path.basename(filePath);
167
+ const relevantLines = output.split('\n')
168
+ .filter(line => line.includes(basename) && /error TS\d+/.test(line));
169
+
170
+ if (relevantLines.length === 0) return null;
171
+ const detail = relevantLines.slice(0, 3).join('\n ');
172
+ const extra = relevantLines.length > 3 ? `\n ...and ${relevantLines.length - 3} more` : '';
173
+ return `[Type Check] ${relevantLines.length} error(s) in ${basename}:\n ${detail}${extra}`;
174
+ }
175
+ }
176
+
177
+ /**
178
+ * Scan file for console.log statements. Returns a message string or null.
179
+ */
180
+ function detectConsoleLogs(filePath) {
181
+ if (!fs.existsSync(filePath)) return null;
182
+
183
+ try {
184
+ const content = fs.readFileSync(filePath, 'utf8');
185
+ const lines = content.split('\n');
186
+ const matches = [];
187
+
188
+ for (let i = 0; i < lines.length; i++) {
189
+ const line = lines[i];
190
+ if (/^\s*\/\//.test(line)) continue; // skip single-line comments
191
+ if (/\bconsole\.log\s*\(/.test(line)) {
192
+ matches.push({ line: i + 1, text: line.trim().substring(0, 80) });
193
+ }
194
+ }
195
+
196
+ if (matches.length === 0) return null;
197
+
198
+ const detail = matches.slice(0, 3).map(m => ` L${m.line}: ${m.text}`).join('\n');
199
+ const extra = matches.length > 3 ? `\n ...and ${matches.length - 3} more` : '';
200
+ return `[Console.log] ${matches.length} console.log(s) in ${path.basename(filePath)}:\n${detail}${extra}`;
201
+ } catch (_e) {
202
+ return null;
203
+ }
204
+ }
205
+
206
+ module.exports = { checkQuality, loadHooksConfig, findLocalBin, runPrettier, runTypeCheck, detectConsoleLogs };
207
+ if (require.main === module) { main(); }
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * PreToolUse dispatcher for Bash hooks.
5
+ *
6
+ * Consolidates check-dangerous-commands.js and validate-commit.js
7
+ * into a single process, reading stdin once and routing to both
8
+ * checks sequentially. This halves the process spawns per Bash call.
9
+ *
10
+ * Check order:
11
+ * 1. Dangerous commands check (can block destructive operations)
12
+ * 2. Commit validation (can block badly-formatted commits)
13
+ *
14
+ * Exit codes:
15
+ * 0 = allowed or warning only
16
+ * 2 = blocked (dangerous command or invalid commit format)
17
+ */
18
+
19
+ const { logHook } = require('./hook-logger');
20
+ const { checkDangerous } = require('./check-dangerous-commands');
21
+ const { checkCommit } = require('./validate-commit');
22
+
23
+ function main() {
24
+ let input = '';
25
+
26
+ process.stdin.setEncoding('utf8');
27
+ process.stdin.on('data', (chunk) => { input += chunk; });
28
+ process.stdin.on('end', () => {
29
+ try {
30
+ const data = JSON.parse(input);
31
+
32
+ // Dangerous commands check first — can block
33
+ const dangerousResult = checkDangerous(data);
34
+ if (dangerousResult) {
35
+ logHook('pre-bash-dispatch', 'PreToolUse', 'dispatched', { handler: 'check-dangerous-commands' });
36
+ process.stdout.write(JSON.stringify(dangerousResult.output));
37
+ process.exit(dangerousResult.exitCode);
38
+ }
39
+
40
+ // Commit validation check — can block
41
+ const commitResult = checkCommit(data);
42
+ if (commitResult) {
43
+ logHook('pre-bash-dispatch', 'PreToolUse', 'dispatched', { handler: 'validate-commit' });
44
+ process.stdout.write(JSON.stringify(commitResult.output));
45
+ process.exit(commitResult.exitCode);
46
+ }
47
+
48
+ process.exit(0);
49
+ } catch (_e) {
50
+ // Don't block on errors
51
+ process.exit(0);
52
+ }
53
+ });
54
+ }
55
+
56
+ if (require.main === module) { main(); }
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * PreToolUse dispatcher for Write|Edit hooks.
5
+ *
6
+ * Consolidates check-skill-workflow.js, check-phase-boundary.js,
7
+ * and check-doc-sprawl.js into a single process, reading stdin once
8
+ * and running all checks sequentially.
9
+ *
10
+ * Check order matters: skill workflow runs first (can block writes
11
+ * that violate planning rules), then phase boundary (can block or
12
+ * warn about cross-phase writes), then doc sprawl (blocks new .md/.txt
13
+ * files outside the allowlist when enabled).
14
+ *
15
+ * Exit codes:
16
+ * 0 = allowed or warning only
17
+ * 2 = blocked (workflow violation or phase boundary enforcement)
18
+ */
19
+
20
+ const { checkWorkflow } = require('./check-skill-workflow');
21
+ const { checkBoundary } = require('./check-phase-boundary');
22
+ const { checkDocSprawl } = require('./check-doc-sprawl');
23
+
24
+ function main() {
25
+ let input = '';
26
+
27
+ process.stdin.setEncoding('utf8');
28
+ process.stdin.on('data', (chunk) => { input += chunk; });
29
+ process.stdin.on('end', () => {
30
+ try {
31
+ const data = JSON.parse(input);
32
+
33
+ // Skill workflow check first — can block
34
+ const workflowResult = checkWorkflow(data);
35
+ if (workflowResult) {
36
+ process.stdout.write(JSON.stringify(workflowResult.output));
37
+ process.exit(workflowResult.exitCode || 0);
38
+ }
39
+
40
+ // Phase boundary check — can block or warn
41
+ const boundaryResult = checkBoundary(data);
42
+ if (boundaryResult) {
43
+ process.stdout.write(JSON.stringify(boundaryResult.output));
44
+ process.exit(boundaryResult.exitCode || 0);
45
+ }
46
+
47
+ // Doc sprawl check — blocks new .md/.txt outside allowlist
48
+ const sprawlResult = checkDocSprawl(data);
49
+ if (sprawlResult) {
50
+ process.stdout.write(JSON.stringify(sprawlResult.output));
51
+ process.exit(sprawlResult.exitCode || 0);
52
+ }
53
+
54
+ process.exit(0);
55
+ } catch (_e) {
56
+ // Don't block on errors
57
+ process.exit(0);
58
+ }
59
+ });
60
+ }
61
+
62
+ if (require.main === module) { main(); }
@@ -0,0 +1,228 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * SessionStart hook: Auto-detects .planning/ directory and injects
5
+ * project state as additionalContext.
6
+ *
7
+ * If no .planning/ directory exists, exits silently (non-Plan-Build-Run project).
8
+ * If STATE.md exists, reads and outputs a concise summary.
9
+ */
10
+
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+ const os = require('os');
14
+ const { execSync } = require('child_process');
15
+ const { logHook } = require('./hook-logger');
16
+ const { logEvent } = require('./event-logger');
17
+ const { configLoad } = require('./pbr-tools');
18
+
19
+ function main() {
20
+ const cwd = process.cwd();
21
+ const planningDir = path.join(cwd, '.planning');
22
+ const stateFile = path.join(planningDir, 'STATE.md');
23
+
24
+ // Not a Plan-Build-Run project
25
+ if (!fs.existsSync(planningDir)) {
26
+ process.exit(0);
27
+ }
28
+
29
+ // Reset compaction counter for new session
30
+ const { resetCounter } = require('./suggest-compact');
31
+ resetCounter(planningDir);
32
+
33
+ const context = buildContext(planningDir, stateFile);
34
+
35
+ if (context) {
36
+ const output = {
37
+ additionalContext: context
38
+ };
39
+ process.stdout.write(JSON.stringify(output));
40
+ logHook('progress-tracker', 'SessionStart', 'injected', { hasState: true });
41
+ logEvent('workflow', 'session-start', { hasState: true });
42
+ } else {
43
+ logHook('progress-tracker', 'SessionStart', 'skipped', { hasState: false });
44
+ logEvent('workflow', 'session-start', { hasState: false });
45
+ }
46
+
47
+ process.exit(0);
48
+ }
49
+
50
+ function buildContext(planningDir, stateFile) {
51
+ const parts = [];
52
+
53
+ parts.push('[Plan-Build-Run Project Detected]');
54
+
55
+ // Git context
56
+ try {
57
+ const branch = execSync('git rev-parse --abbrev-ref HEAD', { encoding: 'utf8', timeout: 3000 }).trim();
58
+ const porcelain = execSync('git status --porcelain', { encoding: 'utf8', timeout: 3000 }).trim();
59
+ const uncommitted = porcelain ? porcelain.split('\n').length : 0;
60
+ const recentCommits = execSync('git log -5 --oneline', { encoding: 'utf8', timeout: 3000 }).trim();
61
+ parts.push(`\nGit: ${branch} (${uncommitted} uncommitted file${uncommitted !== 1 ? 's' : ''})`);
62
+ if (recentCommits) {
63
+ parts.push(`Recent commits:\n${recentCommits}`);
64
+ }
65
+ } catch (_e) {
66
+ // Not a git repo or git not available — skip
67
+ }
68
+
69
+ // Read STATE.md if it exists
70
+ if (fs.existsSync(stateFile)) {
71
+ const state = fs.readFileSync(stateFile, 'utf8');
72
+
73
+ // Extract key sections
74
+ const position = extractSection(state, 'Current Position');
75
+ if (position) {
76
+ parts.push(`\nPosition:\n${position}`);
77
+ }
78
+
79
+ const blockers = extractSection(state, 'Blockers/Concerns');
80
+ if (blockers && !blockers.includes('None')) {
81
+ parts.push(`\nBlockers:\n${blockers}`);
82
+ }
83
+
84
+ const continuity = extractSection(state, 'Session Continuity');
85
+ if (continuity) {
86
+ parts.push(`\nLast Session:\n${continuity}`);
87
+ }
88
+ } else {
89
+ parts.push('\nNo STATE.md found. Run /pbr:begin to initialize or /pbr:status to check.');
90
+ }
91
+
92
+ // Check for .continue-here.md files
93
+ const phasesDir = path.join(planningDir, 'phases');
94
+ if (fs.existsSync(phasesDir)) {
95
+ const continueFiles = findContinueFiles(phasesDir);
96
+ if (continueFiles.length > 0) {
97
+ parts.push(`\nPaused work found: ${continueFiles.join(', ')}`);
98
+ parts.push('Run /pbr:resume to pick up where you left off.');
99
+ }
100
+ }
101
+
102
+ // Check for config and validate
103
+ const config = configLoad(planningDir);
104
+ if (config) {
105
+ parts.push(`\nConfig: depth=${config.depth || 'standard'}, mode=${config.mode || 'interactive'}`);
106
+
107
+ // Validate config against schema (reuse already-loaded config)
108
+ const schemaPath = path.join(__dirname, 'config-schema.json');
109
+ if (fs.existsSync(schemaPath)) {
110
+ const { configValidate } = require('./pbr-tools');
111
+ const validation = configValidate(config);
112
+ if (validation.warnings.length > 0) {
113
+ parts.push(`\nConfig warnings: ${validation.warnings.join('; ')}`);
114
+ }
115
+ if (validation.errors.length > 0) {
116
+ parts.push(`\nConfig errors: ${validation.errors.join('; ')}`);
117
+ }
118
+ }
119
+ }
120
+
121
+ // Check for quick notes
122
+ const projectNotesFile = path.join(planningDir, 'NOTES.md');
123
+ const globalNotesFile = path.join(os.homedir(), '.claude', 'notes.md');
124
+ const projectNoteCount = countNotes(projectNotesFile);
125
+ const globalNoteCount = countNotes(globalNotesFile);
126
+ if (projectNoteCount > 0 || globalNoteCount > 0) {
127
+ const noteParts = [];
128
+ if (projectNoteCount > 0) noteParts.push(`${projectNoteCount} project`);
129
+ if (globalNoteCount > 0) noteParts.push(`${globalNoteCount} global`);
130
+ parts.push(`\nNotes: ${noteParts.join(', ')}. \`/pbr:note list\` to review.`);
131
+ }
132
+
133
+ // Check ROADMAP/STATE sync (S>M-2)
134
+ const roadmapFile = path.join(planningDir, 'ROADMAP.md');
135
+ if (fs.existsSync(stateFile) && fs.existsSync(roadmapFile)) {
136
+ try {
137
+ const roadmap = fs.readFileSync(roadmapFile, 'utf8');
138
+ const state = fs.readFileSync(stateFile, 'utf8');
139
+
140
+ // Extract current phase from STATE.md
141
+ const phaseMatch = state.match(/Phase:\s*(\d+)\s+of\s+\d+/);
142
+ if (phaseMatch) {
143
+ const currentPhase = parseInt(phaseMatch[1], 10);
144
+ // Check if ROADMAP shows this phase as already verified/complete
145
+ const progressTable = roadmap.match(/## Progress[\s\S]*?\|[\s\S]*?(?=\n##|\s*$)/);
146
+ if (progressTable) {
147
+ const rows = progressTable[0].split('\n').filter(r => r.includes('|'));
148
+ for (const row of rows) {
149
+ const cols = row.split('|').map(c => c.trim()).filter(Boolean);
150
+ if (cols.length >= 4) {
151
+ const phaseNum = parseInt(cols[0], 10);
152
+ const status = cols[3] ? cols[3].toLowerCase() : '';
153
+ if (phaseNum === currentPhase && (status === 'verified' || status === 'complete')) {
154
+ parts.push(`\nWarning: STATE.md may be outdated — ROADMAP.md shows phase ${currentPhase} as ${status}.`);
155
+ }
156
+ }
157
+ }
158
+ }
159
+ }
160
+ } catch (_e) {
161
+ // Ignore parse errors
162
+ }
163
+ }
164
+
165
+ // Check for stale .auto-next signal (S>M-9)
166
+ const autoNextFile = path.join(planningDir, '.auto-next');
167
+ if (fs.existsSync(autoNextFile)) {
168
+ try {
169
+ const stats = fs.statSync(autoNextFile);
170
+ const ageMs = Date.now() - stats.mtimeMs;
171
+ const ageMinutes = Math.floor(ageMs / 60000);
172
+ if (ageMinutes > 10) {
173
+ parts.push(`\nWarning: Stale .auto-next signal found (${ageMinutes} minutes old). This may trigger an unexpected command. Consider deleting .planning/.auto-next.`);
174
+ logHook('progress-tracker', 'SessionStart', 'stale-auto-next', { ageMinutes });
175
+ }
176
+ } catch (_e) {
177
+ // Ignore errors
178
+ }
179
+ }
180
+
181
+ parts.push('\nAvailable commands: /pbr:status, /pbr:plan, /pbr:build, /pbr:review, /pbr:help');
182
+
183
+ return parts.join('\n');
184
+ }
185
+
186
+ function extractSection(content, heading) {
187
+ const regex = new RegExp(`##\\s+${escapeRegex(heading)}\\s*\\n([\\s\\S]*?)(?=\\n##\\s|$)`);
188
+ const match = content.match(regex);
189
+ if (!match) return null;
190
+ const section = match[1].trim();
191
+ // Return first 5 lines max
192
+ return section.split('\n').slice(0, 5).join('\n');
193
+ }
194
+
195
+ function escapeRegex(str) {
196
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
197
+ }
198
+
199
+ function findContinueFiles(dir) {
200
+ const results = [];
201
+ try {
202
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
203
+ for (const entry of entries) {
204
+ const fullPath = path.join(dir, entry.name);
205
+ if (entry.isDirectory()) {
206
+ results.push(...findContinueFiles(fullPath));
207
+ } else if (entry.name.includes('.continue-here')) {
208
+ results.push(path.relative(dir, fullPath));
209
+ }
210
+ }
211
+ } catch (_e) {
212
+ // Ignore permission errors
213
+ }
214
+ return results;
215
+ }
216
+
217
+ function countNotes(filePath) {
218
+ try {
219
+ if (!fs.existsSync(filePath)) return 0;
220
+ const content = fs.readFileSync(filePath, 'utf8');
221
+ const lines = content.split('\n');
222
+ return lines.filter(l => /^- \[/.test(l) && !l.includes('[promoted]')).length;
223
+ } catch (_e) {
224
+ return 0;
225
+ }
226
+ }
227
+
228
+ main();