@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.
Files changed (225) hide show
  1. package/CHANGELOG.md +56 -56
  2. package/CLAUDE.md +149 -149
  3. package/LICENSE +21 -21
  4. package/README.md +247 -247
  5. package/dashboard/bin/cli.js +25 -25
  6. package/dashboard/package.json +34 -34
  7. package/dashboard/public/css/layout.css +406 -406
  8. package/dashboard/public/css/status-colors.css +98 -98
  9. package/dashboard/public/js/htmx-title.js +5 -5
  10. package/dashboard/public/js/sidebar-toggle.js +20 -20
  11. package/dashboard/src/app.js +78 -78
  12. package/dashboard/src/middleware/errorHandler.js +52 -52
  13. package/dashboard/src/middleware/notFoundHandler.js +9 -9
  14. package/dashboard/src/repositories/planning.repository.js +128 -128
  15. package/dashboard/src/routes/events.routes.js +40 -40
  16. package/dashboard/src/routes/index.routes.js +31 -31
  17. package/dashboard/src/routes/pages.routes.js +245 -195
  18. package/dashboard/src/server.js +42 -42
  19. package/dashboard/src/services/dashboard.service.js +222 -222
  20. package/dashboard/src/services/phase.service.js +220 -167
  21. package/dashboard/src/services/project.service.js +57 -57
  22. package/dashboard/src/services/roadmap.service.js +171 -171
  23. package/dashboard/src/services/sse.service.js +58 -58
  24. package/dashboard/src/services/todo.service.js +254 -254
  25. package/dashboard/src/services/watcher.service.js +48 -48
  26. package/dashboard/src/views/coming-soon.ejs +11 -11
  27. package/dashboard/src/views/error.ejs +13 -13
  28. package/dashboard/src/views/index.ejs +5 -5
  29. package/dashboard/src/views/layout.ejs +1 -1
  30. package/dashboard/src/views/partials/dashboard-content.ejs +77 -77
  31. package/dashboard/src/views/partials/footer.ejs +3 -3
  32. package/dashboard/src/views/partials/head.ejs +21 -21
  33. package/dashboard/src/views/partials/header.ejs +12 -12
  34. package/dashboard/src/views/partials/layout-bottom.ejs +15 -15
  35. package/dashboard/src/views/partials/layout-top.ejs +8 -8
  36. package/dashboard/src/views/partials/phase-content.ejs +188 -181
  37. package/dashboard/src/views/partials/phase-doc-content.ejs +38 -0
  38. package/dashboard/src/views/partials/phases-content.ejs +117 -117
  39. package/dashboard/src/views/partials/roadmap-content.ejs +142 -142
  40. package/dashboard/src/views/partials/sidebar.ejs +38 -38
  41. package/dashboard/src/views/partials/todo-create-content.ejs +53 -53
  42. package/dashboard/src/views/partials/todo-detail-content.ejs +38 -38
  43. package/dashboard/src/views/partials/todos-content.ejs +53 -53
  44. package/dashboard/src/views/phase-detail.ejs +5 -5
  45. package/dashboard/src/views/phase-doc.ejs +5 -0
  46. package/dashboard/src/views/phases.ejs +5 -5
  47. package/dashboard/src/views/roadmap.ejs +5 -5
  48. package/dashboard/src/views/todo-create.ejs +5 -5
  49. package/dashboard/src/views/todo-detail.ejs +5 -5
  50. package/dashboard/src/views/todos.ejs +5 -5
  51. package/package.json +57 -57
  52. package/plugins/pbr/.claude-plugin/plugin.json +13 -13
  53. package/plugins/pbr/UI-CONSISTENCY-GAPS.md +61 -61
  54. package/plugins/pbr/agents/codebase-mapper.md +279 -271
  55. package/plugins/pbr/agents/debugger.md +281 -281
  56. package/plugins/pbr/agents/executor.md +428 -407
  57. package/plugins/pbr/agents/general.md +164 -164
  58. package/plugins/pbr/agents/integration-checker.md +169 -141
  59. package/plugins/pbr/agents/plan-checker.md +296 -280
  60. package/plugins/pbr/agents/planner.md +358 -358
  61. package/plugins/pbr/agents/researcher.md +363 -363
  62. package/plugins/pbr/agents/synthesizer.md +230 -230
  63. package/plugins/pbr/agents/verifier.md +489 -454
  64. package/plugins/pbr/commands/begin.md +5 -5
  65. package/plugins/pbr/commands/build.md +5 -5
  66. package/plugins/pbr/commands/config.md +5 -5
  67. package/plugins/pbr/commands/continue.md +5 -5
  68. package/plugins/pbr/commands/debug.md +5 -5
  69. package/plugins/pbr/commands/discuss.md +5 -5
  70. package/plugins/pbr/commands/explore.md +5 -5
  71. package/plugins/pbr/commands/health.md +5 -5
  72. package/plugins/pbr/commands/help.md +5 -5
  73. package/plugins/pbr/commands/import.md +5 -5
  74. package/plugins/pbr/commands/milestone.md +5 -5
  75. package/plugins/pbr/commands/note.md +5 -5
  76. package/plugins/pbr/commands/pause.md +5 -5
  77. package/plugins/pbr/commands/plan.md +5 -5
  78. package/plugins/pbr/commands/quick.md +5 -5
  79. package/plugins/pbr/commands/resume.md +5 -5
  80. package/plugins/pbr/commands/review.md +5 -5
  81. package/plugins/pbr/commands/scan.md +5 -5
  82. package/plugins/pbr/commands/setup.md +5 -5
  83. package/plugins/pbr/commands/status.md +5 -5
  84. package/plugins/pbr/commands/todo.md +5 -5
  85. package/plugins/pbr/contexts/dev.md +27 -27
  86. package/plugins/pbr/contexts/research.md +28 -28
  87. package/plugins/pbr/contexts/review.md +36 -36
  88. package/plugins/pbr/hooks/hooks.json +183 -183
  89. package/plugins/pbr/references/agent-anti-patterns.md +24 -24
  90. package/plugins/pbr/references/agent-interactions.md +134 -134
  91. package/plugins/pbr/references/agent-teams.md +54 -54
  92. package/plugins/pbr/references/checkpoints.md +157 -157
  93. package/plugins/pbr/references/common-bug-patterns.md +13 -13
  94. package/plugins/pbr/references/config-reference.md +441 -0
  95. package/plugins/pbr/references/continuation-format.md +212 -212
  96. package/plugins/pbr/references/deviation-rules.md +112 -112
  97. package/plugins/pbr/references/git-integration.md +226 -226
  98. package/plugins/pbr/references/integration-patterns.md +117 -117
  99. package/plugins/pbr/references/model-profiles.md +99 -99
  100. package/plugins/pbr/references/model-selection.md +31 -31
  101. package/plugins/pbr/references/pbr-rules.md +193 -193
  102. package/plugins/pbr/references/plan-authoring.md +181 -181
  103. package/plugins/pbr/references/plan-format.md +287 -283
  104. package/plugins/pbr/references/planning-config.md +213 -213
  105. package/plugins/pbr/references/questioning.md +214 -214
  106. package/plugins/pbr/references/reading-verification.md +127 -127
  107. package/plugins/pbr/references/stub-patterns.md +160 -160
  108. package/plugins/pbr/references/subagent-coordination.md +119 -119
  109. package/plugins/pbr/references/ui-formatting.md +461 -399
  110. package/plugins/pbr/references/verification-patterns.md +198 -198
  111. package/plugins/pbr/references/wave-execution.md +95 -95
  112. package/plugins/pbr/scripts/auto-continue.js +80 -80
  113. package/plugins/pbr/scripts/check-dangerous-commands.js +136 -136
  114. package/plugins/pbr/scripts/check-doc-sprawl.js +102 -102
  115. package/plugins/pbr/scripts/check-phase-boundary.js +196 -196
  116. package/plugins/pbr/scripts/check-plan-format.js +270 -270
  117. package/plugins/pbr/scripts/check-roadmap-sync.js +322 -252
  118. package/plugins/pbr/scripts/check-skill-workflow.js +262 -262
  119. package/plugins/pbr/scripts/check-state-sync.js +476 -476
  120. package/plugins/pbr/scripts/check-subagent-output.js +144 -144
  121. package/plugins/pbr/scripts/config-schema.json +251 -251
  122. package/plugins/pbr/scripts/context-budget-check.js +287 -287
  123. package/plugins/pbr/scripts/event-handler.js +151 -151
  124. package/plugins/pbr/scripts/event-logger.js +92 -92
  125. package/plugins/pbr/scripts/hook-logger.js +80 -76
  126. package/plugins/pbr/scripts/hooks-schema.json +79 -79
  127. package/plugins/pbr/scripts/log-subagent.js +164 -152
  128. package/plugins/pbr/scripts/log-tool-failure.js +88 -88
  129. package/plugins/pbr/scripts/pbr-tools.js +1378 -1301
  130. package/plugins/pbr/scripts/post-write-dispatch.js +66 -66
  131. package/plugins/pbr/scripts/post-write-quality.js +207 -207
  132. package/plugins/pbr/scripts/pre-bash-dispatch.js +86 -56
  133. package/plugins/pbr/scripts/pre-write-dispatch.js +97 -62
  134. package/plugins/pbr/scripts/progress-tracker.js +281 -228
  135. package/plugins/pbr/scripts/run-hook.js +92 -0
  136. package/plugins/pbr/scripts/session-cleanup.js +254 -254
  137. package/plugins/pbr/scripts/status-line.js +288 -285
  138. package/plugins/pbr/scripts/suggest-compact.js +119 -119
  139. package/plugins/pbr/scripts/task-completed.js +45 -45
  140. package/plugins/pbr/scripts/track-context-budget.js +149 -119
  141. package/plugins/pbr/scripts/validate-commit.js +200 -200
  142. package/plugins/pbr/scripts/validate-plugin-structure.js +183 -172
  143. package/plugins/pbr/scripts/validate-task.js +106 -0
  144. package/plugins/pbr/skills/begin/SKILL.md +594 -545
  145. package/plugins/pbr/skills/begin/templates/PROJECT.md.tmpl +33 -33
  146. package/plugins/pbr/skills/begin/templates/REQUIREMENTS.md.tmpl +18 -18
  147. package/plugins/pbr/skills/begin/templates/STATE.md.tmpl +49 -49
  148. package/plugins/pbr/skills/begin/templates/config.json.tmpl +64 -63
  149. package/plugins/pbr/skills/begin/templates/researcher-prompt.md.tmpl +19 -19
  150. package/plugins/pbr/skills/begin/templates/roadmap-prompt.md.tmpl +30 -30
  151. package/plugins/pbr/skills/begin/templates/synthesis-prompt.md.tmpl +16 -16
  152. package/plugins/pbr/skills/build/SKILL.md +943 -962
  153. package/plugins/pbr/skills/config/SKILL.md +256 -241
  154. package/plugins/pbr/skills/continue/SKILL.md +164 -127
  155. package/plugins/pbr/skills/debug/SKILL.md +515 -489
  156. package/plugins/pbr/skills/debug/templates/continuation-prompt.md.tmpl +16 -16
  157. package/plugins/pbr/skills/debug/templates/initial-investigation-prompt.md.tmpl +27 -27
  158. package/plugins/pbr/skills/discuss/SKILL.md +347 -338
  159. package/plugins/pbr/skills/discuss/templates/CONTEXT.md.tmpl +61 -61
  160. package/plugins/pbr/skills/discuss/templates/decision-categories.md +9 -9
  161. package/plugins/pbr/skills/explore/SKILL.md +378 -362
  162. package/plugins/pbr/skills/health/SKILL.md +221 -186
  163. package/plugins/pbr/skills/health/templates/check-pattern.md.tmpl +30 -30
  164. package/plugins/pbr/skills/health/templates/output-format.md.tmpl +63 -63
  165. package/plugins/pbr/skills/help/SKILL.md +155 -140
  166. package/plugins/pbr/skills/import/SKILL.md +504 -490
  167. package/plugins/pbr/skills/milestone/SKILL.md +704 -673
  168. package/plugins/pbr/skills/milestone/templates/audit-report.md.tmpl +48 -48
  169. package/plugins/pbr/skills/milestone/templates/stats-file.md.tmpl +30 -30
  170. package/plugins/pbr/skills/note/SKILL.md +231 -212
  171. package/plugins/pbr/skills/pause/SKILL.md +249 -235
  172. package/plugins/pbr/skills/pause/templates/continue-here.md.tmpl +71 -71
  173. package/plugins/pbr/skills/plan/SKILL.md +685 -628
  174. package/plugins/pbr/skills/plan/decimal-phase-calc.md +98 -98
  175. package/plugins/pbr/skills/plan/templates/checker-prompt.md.tmpl +21 -21
  176. package/plugins/pbr/skills/plan/templates/gap-closure-prompt.md.tmpl +32 -32
  177. package/plugins/pbr/skills/plan/templates/planner-prompt.md.tmpl +38 -38
  178. package/plugins/pbr/skills/plan/templates/researcher-prompt.md.tmpl +19 -19
  179. package/plugins/pbr/skills/plan/templates/revision-prompt.md.tmpl +23 -23
  180. package/plugins/pbr/skills/quick/SKILL.md +354 -335
  181. package/plugins/pbr/skills/resume/SKILL.md +402 -388
  182. package/plugins/pbr/skills/review/SKILL.md +686 -652
  183. package/plugins/pbr/skills/review/templates/debugger-prompt.md.tmpl +60 -60
  184. package/plugins/pbr/skills/review/templates/gap-planner-prompt.md.tmpl +40 -40
  185. package/plugins/pbr/skills/review/templates/verifier-prompt.md.tmpl +115 -115
  186. package/plugins/pbr/skills/scan/SKILL.md +304 -269
  187. package/plugins/pbr/skills/scan/templates/mapper-prompt.md.tmpl +201 -201
  188. package/plugins/pbr/skills/setup/SKILL.md +253 -227
  189. package/plugins/pbr/skills/shared/commit-planning-docs.md +35 -35
  190. package/plugins/pbr/skills/shared/config-loading.md +102 -102
  191. package/plugins/pbr/skills/shared/context-budget.md +40 -40
  192. package/plugins/pbr/skills/shared/context-loader-task.md +86 -86
  193. package/plugins/pbr/skills/shared/digest-select.md +79 -79
  194. package/plugins/pbr/skills/shared/domain-probes.md +125 -125
  195. package/plugins/pbr/skills/shared/error-reporting.md +79 -79
  196. package/plugins/pbr/skills/shared/gate-prompts.md +388 -388
  197. package/plugins/pbr/skills/shared/phase-argument-parsing.md +45 -45
  198. package/plugins/pbr/skills/shared/progress-display.md +53 -53
  199. package/plugins/pbr/skills/shared/revision-loop.md +81 -81
  200. package/plugins/pbr/skills/shared/state-loading.md +62 -62
  201. package/plugins/pbr/skills/shared/state-update.md +161 -161
  202. package/plugins/pbr/skills/shared/universal-anti-patterns.md +33 -33
  203. package/plugins/pbr/skills/status/SKILL.md +367 -353
  204. package/plugins/pbr/skills/todo/SKILL.md +198 -181
  205. package/plugins/pbr/templates/CONTEXT.md.tmpl +52 -52
  206. package/plugins/pbr/templates/INTEGRATION-REPORT.md.tmpl +151 -151
  207. package/plugins/pbr/templates/RESEARCH-SUMMARY.md.tmpl +97 -97
  208. package/plugins/pbr/templates/ROADMAP.md.tmpl +40 -40
  209. package/plugins/pbr/templates/SUMMARY.md.tmpl +81 -81
  210. package/plugins/pbr/templates/VERIFICATION-DETAIL.md.tmpl +116 -116
  211. package/plugins/pbr/templates/codebase/ARCHITECTURE.md.tmpl +98 -98
  212. package/plugins/pbr/templates/codebase/CONCERNS.md.tmpl +93 -93
  213. package/plugins/pbr/templates/codebase/CONVENTIONS.md.tmpl +104 -104
  214. package/plugins/pbr/templates/codebase/INTEGRATIONS.md.tmpl +78 -78
  215. package/plugins/pbr/templates/codebase/STACK.md.tmpl +78 -78
  216. package/plugins/pbr/templates/codebase/STRUCTURE.md.tmpl +80 -80
  217. package/plugins/pbr/templates/codebase/TESTING.md.tmpl +107 -107
  218. package/plugins/pbr/templates/continue-here.md.tmpl +73 -73
  219. package/plugins/pbr/templates/prompt-partials/phase-project-context.md.tmpl +37 -37
  220. package/plugins/pbr/templates/research/ARCHITECTURE.md.tmpl +124 -124
  221. package/plugins/pbr/templates/research/STACK.md.tmpl +71 -71
  222. package/plugins/pbr/templates/research/SUMMARY.md.tmpl +112 -112
  223. package/plugins/pbr/templates/research-outputs/phase-research.md.tmpl +81 -81
  224. package/plugins/pbr/templates/research-outputs/project-research.md.tmpl +99 -99
  225. package/plugins/pbr/templates/research-outputs/synthesis.md.tmpl +36 -36
@@ -1,80 +1,80 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * Stop hook: Auto-continue via signal files.
5
- *
6
- * When enabled (features.auto_continue: true in config.json),
7
- * reads .planning/.auto-next signal file on session stop.
8
- * If present, reads the next command and injects it.
9
- * Signal file is ONE-SHOT: read and delete to prevent infinite loops.
10
- *
11
- * Hard stops (signal file NOT written):
12
- * - Milestone completion
13
- * - human_needed flag set
14
- * - Execution errors
15
- * - Gap closure attempted 3+ times
16
- */
17
-
18
- const fs = require('fs');
19
- const path = require('path');
20
- const { logHook } = require('./hook-logger');
21
- const { configLoad } = require('./pbr-tools');
22
-
23
- function main() {
24
- try {
25
- const cwd = process.cwd();
26
- const planningDir = path.join(cwd, '.planning');
27
- const signalPath = path.join(planningDir, '.auto-next');
28
-
29
- // Check if auto-continue is enabled
30
- const config = configLoad(planningDir);
31
- if (!config || !config.features || !config.features.auto_continue) {
32
- process.exit(0);
33
- }
34
-
35
- // Check for signal file
36
- if (!fs.existsSync(signalPath)) {
37
- logHook('auto-continue', 'Stop', 'no-signal', {});
38
- process.exit(0);
39
- }
40
-
41
- // Read and DELETE the signal file (one-shot)
42
- const nextCommand = fs.readFileSync(signalPath, 'utf8').trim();
43
- // Retry unlink with exponential backoff for Windows file locking (antivirus/indexer)
44
- for (let attempt = 0; attempt < 3; attempt++) {
45
- try {
46
- fs.unlinkSync(signalPath);
47
- break;
48
- } catch (unlinkErr) {
49
- if (attempt === 2) {
50
- logHook('auto-continue', 'Stop', 'unlink-failed', { error: unlinkErr.message });
51
- } else {
52
- // Exponential backoff: 100ms, 200ms
53
- const delay = 100 * Math.pow(2, attempt);
54
- const start = Date.now();
55
- while (Date.now() - start < delay) { /* busy-wait */ }
56
- }
57
- }
58
- }
59
-
60
- if (!nextCommand) {
61
- logHook('auto-continue', 'Stop', 'empty-signal', {});
62
- process.exit(0);
63
- }
64
-
65
- logHook('auto-continue', 'Stop', 'continue', { next: nextCommand });
66
-
67
- // Output the next command for Claude Code to execute
68
- const output = {
69
- message: `Auto-continuing with: ${nextCommand}`,
70
- command: nextCommand
71
- };
72
- process.stdout.write(JSON.stringify(output));
73
- process.exit(0);
74
- } catch (_e) {
75
- // Don't block on errors
76
- process.exit(0);
77
- }
78
- }
79
-
80
- main();
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Stop hook: Auto-continue via signal files.
5
+ *
6
+ * When enabled (features.auto_continue: true in config.json),
7
+ * reads .planning/.auto-next signal file on session stop.
8
+ * If present, reads the next command and injects it.
9
+ * Signal file is ONE-SHOT: read and delete to prevent infinite loops.
10
+ *
11
+ * Hard stops (signal file NOT written):
12
+ * - Milestone completion
13
+ * - human_needed flag set
14
+ * - Execution errors
15
+ * - Gap closure attempted 3+ times
16
+ */
17
+
18
+ const fs = require('fs');
19
+ const path = require('path');
20
+ const { logHook } = require('./hook-logger');
21
+ const { configLoad } = require('./pbr-tools');
22
+
23
+ function main() {
24
+ try {
25
+ const cwd = process.cwd();
26
+ const planningDir = path.join(cwd, '.planning');
27
+ const signalPath = path.join(planningDir, '.auto-next');
28
+
29
+ // Check if auto-continue is enabled
30
+ const config = configLoad(planningDir);
31
+ if (!config || !config.features || !config.features.auto_continue) {
32
+ process.exit(0);
33
+ }
34
+
35
+ // Check for signal file
36
+ if (!fs.existsSync(signalPath)) {
37
+ logHook('auto-continue', 'Stop', 'no-signal', {});
38
+ process.exit(0);
39
+ }
40
+
41
+ // Read and DELETE the signal file (one-shot)
42
+ const nextCommand = fs.readFileSync(signalPath, 'utf8').trim();
43
+ // Retry unlink with exponential backoff for Windows file locking (antivirus/indexer)
44
+ for (let attempt = 0; attempt < 3; attempt++) {
45
+ try {
46
+ fs.unlinkSync(signalPath);
47
+ break;
48
+ } catch (unlinkErr) {
49
+ if (attempt === 2) {
50
+ logHook('auto-continue', 'Stop', 'unlink-failed', { error: unlinkErr.message });
51
+ } else {
52
+ // Exponential backoff: 100ms, 200ms
53
+ const delay = 100 * Math.pow(2, attempt);
54
+ const start = Date.now();
55
+ while (Date.now() - start < delay) { /* busy-wait */ }
56
+ }
57
+ }
58
+ }
59
+
60
+ if (!nextCommand) {
61
+ logHook('auto-continue', 'Stop', 'empty-signal', {});
62
+ process.exit(0);
63
+ }
64
+
65
+ logHook('auto-continue', 'Stop', 'continue', { next: nextCommand });
66
+
67
+ // Output the next command for Claude Code to execute
68
+ const output = {
69
+ message: `Auto-continuing with: ${nextCommand}`,
70
+ command: nextCommand
71
+ };
72
+ process.stdout.write(JSON.stringify(output));
73
+ process.exit(0);
74
+ } catch (_e) {
75
+ // Don't block on errors
76
+ process.exit(0);
77
+ }
78
+ }
79
+
80
+ main();
@@ -1,136 +1,136 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * PreToolUse hook: Blocks dangerous Bash commands that could destroy
5
- * planning state or repository integrity.
6
- *
7
- * Patterns blocked (exit 2):
8
- * - rm -rf .planning (or any rm that targets .planning/)
9
- * - git reset --hard
10
- * - git push --force / -f to main/master
11
- * - git clean -fd / -fxd (removes untracked files including .planning/)
12
- *
13
- * Patterns warned (exit 0, additionalContext):
14
- * - Large rm operations (rm -rf on project directories)
15
- * - git checkout -- . (discards all unstaged changes)
16
- *
17
- * Exit codes:
18
- * 0 = not a dangerous command, or a warning-only match
19
- * 2 = blocked (destructive command detected)
20
- */
21
-
22
- const { logHook } = require('./hook-logger');
23
-
24
- // Commands that are outright blocked
25
- const BLOCK_PATTERNS = [
26
- {
27
- pattern: /\brm\s+(-[a-zA-Z]*r[a-zA-Z]*f[a-zA-Z]*|-[a-zA-Z]*f[a-zA-Z]*r[a-zA-Z]*)\s+.*\.planning\b/,
28
- reason: 'rm -rf targeting .planning/ directory — this would destroy all project state.'
29
- },
30
- {
31
- pattern: /\brm\s+(-[a-zA-Z]*r[a-zA-Z]*f[a-zA-Z]*|-[a-zA-Z]*f[a-zA-Z]*r[a-zA-Z]*)\s+.*\.planning[/\\]/,
32
- reason: 'rm -rf targeting files inside .planning/ — this would destroy project state.'
33
- },
34
- {
35
- pattern: /\bgit\s+reset\s+--hard\b/,
36
- reason: 'git reset --hard discards all uncommitted changes. Use git stash or git checkout for specific files instead.'
37
- },
38
- {
39
- pattern: /\bgit\s+push\s+.*(-f|--force)\b.*\b(main|master)\b/,
40
- reason: 'Force-pushing to main/master can destroy shared history. This is almost never what you want.'
41
- },
42
- {
43
- pattern: /\bgit\s+push\s+.*\b(main|master)\b.*(-f|--force)\b/,
44
- reason: 'Force-pushing to main/master can destroy shared history. This is almost never what you want.'
45
- },
46
- {
47
- pattern: /\bgit\s+clean\s+(-[a-zA-Z]*f[a-zA-Z]*d|-[a-zA-Z]*d[a-zA-Z]*f)\b/,
48
- reason: 'git clean -fd removes untracked files including .planning/ contents. Use specific file paths instead.'
49
- }
50
- ];
51
-
52
- // Commands that produce warnings but are not blocked
53
- const WARN_PATTERNS = [
54
- {
55
- pattern: /\bgit\s+checkout\s+--\s+\.\s*$/,
56
- message: 'git checkout -- . discards ALL unstaged changes. Consider targeting specific files.'
57
- },
58
- {
59
- pattern: /\bgit\s+push\s+.*(-f|--force)\b/,
60
- message: 'Force-pushing can overwrite remote history. Ensure this is intentional.'
61
- }
62
- ];
63
-
64
- /**
65
- * Check a parsed hook data object for dangerous commands.
66
- * Returns { output, exitCode } if the command should be blocked/warned, or null if allowed.
67
- * Used by pre-bash-dispatch.js for consolidated hook execution.
68
- */
69
- function checkDangerous(data) {
70
- const command = data.tool_input?.command || '';
71
-
72
- // Skip empty commands
73
- if (!command.trim()) {
74
- return null;
75
- }
76
-
77
- // Check block patterns
78
- for (const { pattern, reason } of BLOCK_PATTERNS) {
79
- if (pattern.test(command)) {
80
- logHook('check-dangerous-commands', 'PreToolUse', 'block', {
81
- command: command.substring(0, 200),
82
- reason
83
- });
84
- return {
85
- output: {
86
- decision: 'block',
87
- reason: `Dangerous command blocked.\n\n${reason}\n\nCommand: ${command.substring(0, 150)}`
88
- },
89
- exitCode: 2
90
- };
91
- }
92
- }
93
-
94
- // Check warn patterns
95
- for (const { pattern, message } of WARN_PATTERNS) {
96
- if (pattern.test(command)) {
97
- logHook('check-dangerous-commands', 'PreToolUse', 'warn', {
98
- command: command.substring(0, 200),
99
- warning: message
100
- });
101
- return {
102
- output: {
103
- additionalContext: `Warning: ${message}`
104
- },
105
- exitCode: 0
106
- };
107
- }
108
- }
109
-
110
- // No match — allow
111
- return null;
112
- }
113
-
114
- function main() {
115
- let input = '';
116
-
117
- process.stdin.setEncoding('utf8');
118
- process.stdin.on('data', (chunk) => { input += chunk; });
119
- process.stdin.on('end', () => {
120
- try {
121
- const data = JSON.parse(input);
122
- const result = checkDangerous(data);
123
- if (result) {
124
- process.stdout.write(JSON.stringify(result.output));
125
- process.exit(result.exitCode);
126
- }
127
- process.exit(0);
128
- } catch (_e) {
129
- // Parse error — don't block
130
- process.exit(0);
131
- }
132
- });
133
- }
134
-
135
- module.exports = { BLOCK_PATTERNS, WARN_PATTERNS, checkDangerous };
136
- if (require.main === module) { main(); }
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * PreToolUse hook: Blocks dangerous Bash commands that could destroy
5
+ * planning state or repository integrity.
6
+ *
7
+ * Patterns blocked (exit 2):
8
+ * - rm -rf .planning (or any rm that targets .planning/)
9
+ * - git reset --hard
10
+ * - git push --force / -f to main/master
11
+ * - git clean -fd / -fxd (removes untracked files including .planning/)
12
+ *
13
+ * Patterns warned (exit 0, additionalContext):
14
+ * - Large rm operations (rm -rf on project directories)
15
+ * - git checkout -- . (discards all unstaged changes)
16
+ *
17
+ * Exit codes:
18
+ * 0 = not a dangerous command, or a warning-only match
19
+ * 2 = blocked (destructive command detected)
20
+ */
21
+
22
+ const { logHook } = require('./hook-logger');
23
+
24
+ // Commands that are outright blocked
25
+ const BLOCK_PATTERNS = [
26
+ {
27
+ pattern: /\brm\s+(-[a-zA-Z]*r[a-zA-Z]*f[a-zA-Z]*|-[a-zA-Z]*f[a-zA-Z]*r[a-zA-Z]*)\s+.*\.planning\b/,
28
+ reason: 'rm -rf targeting .planning/ directory — this would destroy all project state.'
29
+ },
30
+ {
31
+ pattern: /\brm\s+(-[a-zA-Z]*r[a-zA-Z]*f[a-zA-Z]*|-[a-zA-Z]*f[a-zA-Z]*r[a-zA-Z]*)\s+.*\.planning[/\\]/,
32
+ reason: 'rm -rf targeting files inside .planning/ — this would destroy project state.'
33
+ },
34
+ {
35
+ pattern: /\bgit\s+reset\s+--hard\b/,
36
+ reason: 'git reset --hard discards all uncommitted changes. Use git stash or git checkout for specific files instead.'
37
+ },
38
+ {
39
+ pattern: /\bgit\s+push\s+.*(-f|--force)\b.*\b(main|master)\b/,
40
+ reason: 'Force-pushing to main/master can destroy shared history. This is almost never what you want.'
41
+ },
42
+ {
43
+ pattern: /\bgit\s+push\s+.*\b(main|master)\b.*(-f|--force)\b/,
44
+ reason: 'Force-pushing to main/master can destroy shared history. This is almost never what you want.'
45
+ },
46
+ {
47
+ pattern: /\bgit\s+clean\s+(-[a-zA-Z]*f[a-zA-Z]*d|-[a-zA-Z]*d[a-zA-Z]*f)\b/,
48
+ reason: 'git clean -fd removes untracked files including .planning/ contents. Use specific file paths instead.'
49
+ }
50
+ ];
51
+
52
+ // Commands that produce warnings but are not blocked
53
+ const WARN_PATTERNS = [
54
+ {
55
+ pattern: /\bgit\s+checkout\s+--\s+\.\s*$/,
56
+ message: 'git checkout -- . discards ALL unstaged changes. Consider targeting specific files.'
57
+ },
58
+ {
59
+ pattern: /\bgit\s+push\s+.*(-f|--force)\b/,
60
+ message: 'Force-pushing can overwrite remote history. Ensure this is intentional.'
61
+ }
62
+ ];
63
+
64
+ /**
65
+ * Check a parsed hook data object for dangerous commands.
66
+ * Returns { output, exitCode } if the command should be blocked/warned, or null if allowed.
67
+ * Used by pre-bash-dispatch.js for consolidated hook execution.
68
+ */
69
+ function checkDangerous(data) {
70
+ const command = data.tool_input?.command || '';
71
+
72
+ // Skip empty commands
73
+ if (!command.trim()) {
74
+ return null;
75
+ }
76
+
77
+ // Check block patterns
78
+ for (const { pattern, reason } of BLOCK_PATTERNS) {
79
+ if (pattern.test(command)) {
80
+ logHook('check-dangerous-commands', 'PreToolUse', 'block', {
81
+ command: command.substring(0, 200),
82
+ reason
83
+ });
84
+ return {
85
+ output: {
86
+ decision: 'block',
87
+ reason: `Dangerous command blocked.\n\n${reason}\n\nCommand: ${command.substring(0, 150)}`
88
+ },
89
+ exitCode: 2
90
+ };
91
+ }
92
+ }
93
+
94
+ // Check warn patterns
95
+ for (const { pattern, message } of WARN_PATTERNS) {
96
+ if (pattern.test(command)) {
97
+ logHook('check-dangerous-commands', 'PreToolUse', 'warn', {
98
+ command: command.substring(0, 200),
99
+ warning: message
100
+ });
101
+ return {
102
+ output: {
103
+ additionalContext: `Warning: ${message}`
104
+ },
105
+ exitCode: 0
106
+ };
107
+ }
108
+ }
109
+
110
+ // No match — allow
111
+ return null;
112
+ }
113
+
114
+ function main() {
115
+ let input = '';
116
+
117
+ process.stdin.setEncoding('utf8');
118
+ process.stdin.on('data', (chunk) => { input += chunk; });
119
+ process.stdin.on('end', () => {
120
+ try {
121
+ const data = JSON.parse(input);
122
+ const result = checkDangerous(data);
123
+ if (result) {
124
+ process.stdout.write(JSON.stringify(result.output));
125
+ process.exit(result.exitCode);
126
+ }
127
+ process.exit(0);
128
+ } catch (_e) {
129
+ // Parse error — don't block
130
+ process.exit(0);
131
+ }
132
+ });
133
+ }
134
+
135
+ module.exports = { BLOCK_PATTERNS, WARN_PATTERNS, checkDangerous };
136
+ if (require.main === module) { main(); }
@@ -1,102 +1,102 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * PreToolUse check: Blocks creation of new .md/.txt documentation files
5
- * outside a known allowlist, preventing doc sprawl during builds.
6
- *
7
- * Opt-in via .planning/config.json: { "hooks": { "blockDocSprawl": true } }
8
- *
9
- * Allowlist:
10
- * - README.md, CLAUDE.md, CONTRIBUTING.md, CHANGELOG.md, LICENSE.md, LICENSE, LICENSE.txt
11
- * - Any file under .planning/, .claude/, node_modules/, .git/
12
- * - Any file that already exists (edits to existing docs are always allowed)
13
- *
14
- * Called by pre-write-dispatch.js — not wired directly in hooks.json.
15
- *
16
- * Exit codes (when standalone):
17
- * 0 = pass (allowed)
18
- * 2 = block (not on allowlist)
19
- */
20
-
21
- const fs = require('fs');
22
- const path = require('path');
23
- const { logHook } = require('./hook-logger');
24
-
25
- const ALLOWED_DOC_BASENAMES = new Set([
26
- 'readme.md',
27
- 'claude.md',
28
- 'contributing.md',
29
- 'changelog.md',
30
- 'license.md',
31
- 'license',
32
- 'license.txt',
33
- ]);
34
-
35
- const ALLOWED_DIR_SEGMENTS = [
36
- '.planning',
37
- '.claude',
38
- 'node_modules',
39
- '.git',
40
- ];
41
-
42
- const DOC_EXTENSIONS = new Set(['.md', '.txt']);
43
-
44
- /**
45
- * Check if a Write operation would create a disallowed documentation file.
46
- * @param {Object} data - Parsed hook input (tool_input, etc.)
47
- * @param {string} [cwd] - Working directory override (defaults to process.cwd())
48
- * @returns {null|{exitCode: number, output: Object}} null if allowed, block result otherwise
49
- */
50
- function checkDocSprawl(data, cwd) {
51
- const filePath = data.tool_input?.file_path || data.tool_input?.path || '';
52
- if (!filePath) return null;
53
-
54
- const ext = path.extname(filePath).toLowerCase();
55
- if (!DOC_EXTENSIONS.has(ext)) return null;
56
-
57
- // Only block NEW file creation — existing docs can always be edited
58
- if (fs.existsSync(filePath)) return null;
59
-
60
- // Check config — disabled by default
61
- const effectiveCwd = cwd || process.cwd();
62
- if (!isBlockDocSprawlEnabled(effectiveCwd)) return null;
63
-
64
- // Check basename allowlist
65
- const basename = path.basename(filePath).toLowerCase();
66
- if (ALLOWED_DOC_BASENAMES.has(basename)) return null;
67
-
68
- // Check if file is in an allowed directory
69
- const normalized = filePath.replace(/\\/g, '/');
70
- for (const seg of ALLOWED_DIR_SEGMENTS) {
71
- if (normalized.includes(`/${seg}/`)) return null;
72
- }
73
-
74
- logHook('check-doc-sprawl', 'PreToolUse', 'block', {
75
- file: path.basename(filePath),
76
- ext
77
- });
78
-
79
- return {
80
- exitCode: 2,
81
- output: {
82
- decision: 'block',
83
- reason: `[Doc Sprawl] Blocked creation of ${path.basename(filePath)}. ` +
84
- 'Only known docs (README.md, CLAUDE.md, CONTRIBUTING.md, CHANGELOG.md, LICENSE.md) ' +
85
- 'and .planning/ files are allowed when blockDocSprawl is enabled. ' +
86
- 'Add content to an existing file instead, or disable hooks.blockDocSprawl in config.'
87
- }
88
- };
89
- }
90
-
91
- function isBlockDocSprawlEnabled(cwd) {
92
- try {
93
- const configPath = path.join(cwd, '.planning', 'config.json');
94
- if (!fs.existsSync(configPath)) return false;
95
- const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
96
- return !!config.hooks?.blockDocSprawl;
97
- } catch (_e) {
98
- return false;
99
- }
100
- }
101
-
102
- module.exports = { checkDocSprawl, isBlockDocSprawlEnabled, ALLOWED_DOC_BASENAMES, ALLOWED_DIR_SEGMENTS, DOC_EXTENSIONS };
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * PreToolUse check: Blocks creation of new .md/.txt documentation files
5
+ * outside a known allowlist, preventing doc sprawl during builds.
6
+ *
7
+ * Opt-in via .planning/config.json: { "hooks": { "blockDocSprawl": true } }
8
+ *
9
+ * Allowlist:
10
+ * - README.md, CLAUDE.md, CONTRIBUTING.md, CHANGELOG.md, LICENSE.md, LICENSE, LICENSE.txt
11
+ * - Any file under .planning/, .claude/, node_modules/, .git/
12
+ * - Any file that already exists (edits to existing docs are always allowed)
13
+ *
14
+ * Called by pre-write-dispatch.js — not wired directly in hooks.json.
15
+ *
16
+ * Exit codes (when standalone):
17
+ * 0 = pass (allowed)
18
+ * 2 = block (not on allowlist)
19
+ */
20
+
21
+ const fs = require('fs');
22
+ const path = require('path');
23
+ const { logHook } = require('./hook-logger');
24
+
25
+ const ALLOWED_DOC_BASENAMES = new Set([
26
+ 'readme.md',
27
+ 'claude.md',
28
+ 'contributing.md',
29
+ 'changelog.md',
30
+ 'license.md',
31
+ 'license',
32
+ 'license.txt',
33
+ ]);
34
+
35
+ const ALLOWED_DIR_SEGMENTS = [
36
+ '.planning',
37
+ '.claude',
38
+ 'node_modules',
39
+ '.git',
40
+ ];
41
+
42
+ const DOC_EXTENSIONS = new Set(['.md', '.txt']);
43
+
44
+ /**
45
+ * Check if a Write operation would create a disallowed documentation file.
46
+ * @param {Object} data - Parsed hook input (tool_input, etc.)
47
+ * @param {string} [cwd] - Working directory override (defaults to process.cwd())
48
+ * @returns {null|{exitCode: number, output: Object}} null if allowed, block result otherwise
49
+ */
50
+ function checkDocSprawl(data, cwd) {
51
+ const filePath = data.tool_input?.file_path || data.tool_input?.path || '';
52
+ if (!filePath) return null;
53
+
54
+ const ext = path.extname(filePath).toLowerCase();
55
+ if (!DOC_EXTENSIONS.has(ext)) return null;
56
+
57
+ // Only block NEW file creation — existing docs can always be edited
58
+ if (fs.existsSync(filePath)) return null;
59
+
60
+ // Check config — disabled by default
61
+ const effectiveCwd = cwd || process.cwd();
62
+ if (!isBlockDocSprawlEnabled(effectiveCwd)) return null;
63
+
64
+ // Check basename allowlist
65
+ const basename = path.basename(filePath).toLowerCase();
66
+ if (ALLOWED_DOC_BASENAMES.has(basename)) return null;
67
+
68
+ // Check if file is in an allowed directory
69
+ const normalized = filePath.replace(/\\/g, '/');
70
+ for (const seg of ALLOWED_DIR_SEGMENTS) {
71
+ if (normalized.includes(`/${seg}/`)) return null;
72
+ }
73
+
74
+ logHook('check-doc-sprawl', 'PreToolUse', 'block', {
75
+ file: path.basename(filePath),
76
+ ext
77
+ });
78
+
79
+ return {
80
+ exitCode: 2,
81
+ output: {
82
+ decision: 'block',
83
+ reason: `[Doc Sprawl] Blocked creation of ${path.basename(filePath)}. ` +
84
+ 'Only known docs (README.md, CLAUDE.md, CONTRIBUTING.md, CHANGELOG.md, LICENSE.md) ' +
85
+ 'and .planning/ files are allowed when blockDocSprawl is enabled. ' +
86
+ 'Add content to an existing file instead, or disable hooks.blockDocSprawl in config.'
87
+ }
88
+ };
89
+ }
90
+
91
+ function isBlockDocSprawlEnabled(cwd) {
92
+ try {
93
+ const configPath = path.join(cwd, '.planning', 'config.json');
94
+ if (!fs.existsSync(configPath)) return false;
95
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
96
+ return !!config.hooks?.blockDocSprawl;
97
+ } catch (_e) {
98
+ return false;
99
+ }
100
+ }
101
+
102
+ module.exports = { checkDocSprawl, isBlockDocSprawlEnabled, ALLOWED_DOC_BASENAMES, ALLOWED_DIR_SEGMENTS, DOC_EXTENSIONS };