@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,262 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * PreToolUse hook (Write|Edit): Enforces skill-specific workflow rules.
5
+ *
6
+ * Reads .planning/.active-skill to determine which skill is running.
7
+ * Each skill can have rules about what files can be written and when.
8
+ *
9
+ * Current rules:
10
+ * - /pbr:quick: Cannot write files outside .planning/ until a PLAN.md
11
+ * exists in .planning/quick/. This prevents the orchestrator from
12
+ * skipping the planning steps and jumping straight to implementation.
13
+ *
14
+ * Skills opt in by writing .planning/.active-skill at the start of
15
+ * their execution. If the file doesn't exist, this hook does nothing.
16
+ *
17
+ * Exit codes:
18
+ * 0 = allowed or not applicable
19
+ * 2 = blocked (workflow violation)
20
+ */
21
+
22
+ const fs = require('fs');
23
+ const path = require('path');
24
+ const { logHook } = require('./hook-logger');
25
+ const { logEvent } = require('./event-logger');
26
+
27
+ function main() {
28
+ let input = '';
29
+
30
+ process.stdin.setEncoding('utf8');
31
+ process.stdin.on('data', (chunk) => { input += chunk; });
32
+ process.stdin.on('end', () => {
33
+ try {
34
+ const data = JSON.parse(input);
35
+ const filePath = data.tool_input?.file_path || data.tool_input?.path || '';
36
+
37
+ if (!filePath) {
38
+ process.exit(0);
39
+ }
40
+
41
+ const cwd = process.cwd();
42
+ const planningDir = path.join(cwd, '.planning');
43
+
44
+ // Read active skill
45
+ const activeSkill = readActiveSkill(planningDir);
46
+ if (!activeSkill) {
47
+ process.exit(0);
48
+ }
49
+
50
+ // Apply skill-specific rules
51
+ const violation = checkSkillRules(activeSkill, filePath, planningDir);
52
+ if (violation) {
53
+ logHook('check-skill-workflow', 'PreToolUse', 'block', {
54
+ skill: activeSkill,
55
+ file: path.basename(filePath),
56
+ rule: violation.rule
57
+ });
58
+ logEvent('workflow', 'skill-workflow-block', {
59
+ skill: activeSkill,
60
+ file: path.basename(filePath),
61
+ rule: violation.rule
62
+ });
63
+
64
+ const output = {
65
+ decision: 'block',
66
+ reason: violation.message
67
+ };
68
+ process.stdout.write(JSON.stringify(output));
69
+ process.exit(2);
70
+ }
71
+
72
+ process.exit(0);
73
+ } catch (_e) {
74
+ // Don't block on errors
75
+ process.exit(0);
76
+ }
77
+ });
78
+ }
79
+
80
+ function readActiveSkill(planningDir) {
81
+ const skillFile = path.join(planningDir, '.active-skill');
82
+ if (!fs.existsSync(skillFile)) return null;
83
+
84
+ try {
85
+ const content = fs.readFileSync(skillFile, 'utf8').trim();
86
+ return content || null;
87
+ } catch (_e) {
88
+ return null;
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Check skill-specific workflow rules.
94
+ * Returns { rule, message } if violated, null if OK.
95
+ */
96
+ function checkSkillRules(skill, filePath, planningDir) {
97
+ const normalizedPath = filePath.replace(/\\/g, '/');
98
+ const normalizedPlanning = planningDir.replace(/\\/g, '/');
99
+ // Check with both raw paths and resolved symlinks (macOS /var → /private/var)
100
+ let isInPlanning = normalizedPath.startsWith(normalizedPlanning);
101
+ if (!isInPlanning) {
102
+ try {
103
+ const resolvedPlanning = fs.realpathSync(planningDir).replace(/\\/g, '/');
104
+ isInPlanning = normalizedPath.startsWith(resolvedPlanning);
105
+ } catch (_e) { /* not resolvable */ }
106
+ }
107
+
108
+ // Check for orchestrator writing agent artifacts (any skill)
109
+ const artifactViolation = checkArtifactRules(filePath, planningDir);
110
+ if (artifactViolation) return artifactViolation;
111
+
112
+ switch (skill) {
113
+ case 'quick':
114
+ return checkQuickRules(filePath, isInPlanning, planningDir);
115
+ case 'build':
116
+ return checkBuildRules(filePath, isInPlanning, planningDir);
117
+ default:
118
+ return null;
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Artifact rules (all skills):
124
+ * - SUMMARY.md and VERIFICATION.md should only be written by subagents
125
+ * - If .active-agent exists, a subagent is running (allow writes)
126
+ * - If .active-agent does NOT exist, the orchestrator is writing (block)
127
+ */
128
+ function checkArtifactRules(filePath, planningDir) {
129
+ const basename = path.basename(filePath);
130
+
131
+ // Only check SUMMARY and VERIFICATION files in phase directories
132
+ const isSummary = /^SUMMARY.*\.md$/i.test(basename);
133
+ const isVerification = /^VERIFICATION.*\.md$/i.test(basename);
134
+ if (!isSummary && !isVerification) return null;
135
+
136
+ // If .active-agent exists, a subagent is running — allow
137
+ const activeAgentFile = path.join(planningDir, '.active-agent');
138
+ if (fs.existsSync(activeAgentFile)) return null;
139
+
140
+ const artifactType = isSummary ? 'SUMMARY.md' : 'VERIFICATION.md';
141
+ return {
142
+ rule: 'orchestrator-artifact-write',
143
+ message: `Workflow violation: ${artifactType} should be written by a subagent, not the orchestrator.\n\nBlocked: ${filePath}\n\nDelegate this write to a Task(subagent_type: "pbr:executor") or Task(subagent_type: "pbr:verifier") agent.`
144
+ };
145
+ }
146
+
147
+ /**
148
+ * /pbr:quick rules:
149
+ * - Cannot write files outside .planning/ until PLAN.md exists in .planning/quick/
150
+ * - This prevents the orchestrator from skipping directly to implementation
151
+ */
152
+ function checkQuickRules(filePath, isInPlanning, planningDir) {
153
+ // Writes to .planning/ are always allowed (creating plan, state, etc.)
154
+ if (isInPlanning) return null;
155
+
156
+ // Check if any PLAN.md exists under .planning/quick/
157
+ const quickDir = path.join(planningDir, 'quick');
158
+ if (hasPlanFile(quickDir)) return null;
159
+
160
+ return {
161
+ rule: 'quick-requires-plan',
162
+ message: `Workflow violation: /pbr:quick must create a PLAN.md before writing source code.\n\nBlocked: ${filePath}\n\nComplete Steps 4-6 of the quick workflow first:\n 1. Create .planning/quick/{NNN}-{slug}/ directory\n 2. Write PLAN.md with at least one <task> block\n 3. Then spawn the executor to implement`
163
+ };
164
+ }
165
+
166
+ /**
167
+ * /pbr:build rules:
168
+ * - Cannot write files outside .planning/ unless a PLAN.md exists for the current phase
169
+ */
170
+ function checkBuildRules(filePath, isInPlanning, planningDir) {
171
+ // Writes to .planning/ are always allowed
172
+ if (isInPlanning) return null;
173
+
174
+ // Check if any PLAN.md exists under .planning/phases/
175
+ const phasesDir = path.join(planningDir, 'phases');
176
+ if (!fs.existsSync(phasesDir)) {
177
+ return {
178
+ rule: 'build-requires-plan',
179
+ message: `Workflow violation: /pbr:build requires a planned phase before writing source code.\n\nBlocked: ${filePath}\n\nRun /pbr:plan first to create a phase plan.`
180
+ };
181
+ }
182
+
183
+ // Check current phase directory for PLAN.md
184
+ const stateFile = path.join(planningDir, 'STATE.md');
185
+ if (!fs.existsSync(stateFile)) return null;
186
+
187
+ try {
188
+ const state = fs.readFileSync(stateFile, 'utf8');
189
+ const phaseMatch = state.match(/Phase:\s*(\d+)\s+of\s+\d+/);
190
+ if (!phaseMatch) return null;
191
+
192
+ const currentPhase = phaseMatch[1].padStart(2, '0');
193
+ const dirs = fs.readdirSync(phasesDir).filter(d => d.startsWith(currentPhase));
194
+ if (dirs.length === 0) return null;
195
+
196
+ const phaseDir = path.join(phasesDir, dirs[0]);
197
+ if (hasPlanFile(phaseDir)) return null;
198
+
199
+ return {
200
+ rule: 'build-requires-plan',
201
+ message: `Workflow violation: /pbr:build requires a PLAN.md for phase ${currentPhase} before writing source code.\n\nBlocked: ${filePath}\n\nRun /pbr:plan ${currentPhase} first.`
202
+ };
203
+ } catch (_e) {
204
+ return null;
205
+ }
206
+ }
207
+
208
+ /**
209
+ * Check if any PLAN.md file exists in a directory (recursive one level).
210
+ */
211
+ function hasPlanFile(dir) {
212
+ if (!fs.existsSync(dir)) return false;
213
+
214
+ try {
215
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
216
+ for (const entry of entries) {
217
+ if (entry.isFile() && entry.name.endsWith('PLAN.md')) return true;
218
+ if (entry.isDirectory()) {
219
+ const subEntries = fs.readdirSync(path.join(dir, entry.name));
220
+ if (subEntries.some(f => f.endsWith('PLAN.md'))) return true;
221
+ }
222
+ }
223
+ } catch (_e) {
224
+ // skip
225
+ }
226
+ return false;
227
+ }
228
+
229
+ /**
230
+ * Core workflow check logic for use by dispatchers.
231
+ * @param {Object} data - Parsed hook input (tool_input, etc.)
232
+ * @returns {null|{exitCode: number, output: Object}} null if pass, result otherwise
233
+ */
234
+ function checkWorkflow(data) {
235
+ const filePath = data.tool_input?.file_path || data.tool_input?.path || '';
236
+ if (!filePath) return null;
237
+
238
+ const cwd = process.cwd();
239
+ const planningDir = path.join(cwd, '.planning');
240
+
241
+ const activeSkill = readActiveSkill(planningDir);
242
+ if (!activeSkill) return null;
243
+
244
+ const violation = checkSkillRules(activeSkill, filePath, planningDir);
245
+ if (violation) {
246
+ logHook('check-skill-workflow', 'PreToolUse', 'block', {
247
+ skill: activeSkill, file: path.basename(filePath), rule: violation.rule
248
+ });
249
+ logEvent('workflow', 'skill-workflow-block', {
250
+ skill: activeSkill, file: path.basename(filePath), rule: violation.rule
251
+ });
252
+ return {
253
+ exitCode: 2,
254
+ output: { decision: 'block', reason: violation.message }
255
+ };
256
+ }
257
+
258
+ return null;
259
+ }
260
+
261
+ module.exports = { readActiveSkill, checkSkillRules, hasPlanFile, checkWorkflow };
262
+ if (require.main === module) { main(); }