@sienklogic/plan-build-run 2.19.0 → 2.19.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 (977) hide show
  1. package/CHANGELOG.md +1265 -320
  2. package/CLAUDE.md +56 -34
  3. package/LICENSE +2 -1
  4. package/README.md +412 -177
  5. package/bin/install.js +2752 -0
  6. package/dashboard/bin/cli.cjs +96 -0
  7. package/dashboard/bin/stop.cjs +129 -0
  8. package/dashboard/eslint.config.js +37 -0
  9. package/dashboard/index.html +20 -0
  10. package/dashboard/package.json +27 -25
  11. package/dashboard/server/index.js +142 -0
  12. package/dashboard/server/lib/frontmatter.js +92 -0
  13. package/dashboard/server/middleware/static.js +35 -0
  14. package/dashboard/server/package.json +16 -0
  15. package/dashboard/server/routes/agents.js +213 -0
  16. package/dashboard/server/routes/config.js +64 -0
  17. package/dashboard/server/routes/health.js +95 -0
  18. package/dashboard/server/routes/incidents.js +78 -0
  19. package/dashboard/server/routes/intel.js +69 -0
  20. package/dashboard/server/routes/memory.js +107 -0
  21. package/dashboard/server/routes/planning.js +234 -0
  22. package/dashboard/server/routes/progress.js +77 -0
  23. package/dashboard/server/routes/projects.js +36 -0
  24. package/dashboard/server/routes/requirements.js +40 -0
  25. package/dashboard/server/routes/roadmap.js +69 -0
  26. package/dashboard/server/routes/sessions.js +70 -0
  27. package/dashboard/server/routes/status.js +25 -0
  28. package/dashboard/server/routes/telemetry.js +214 -0
  29. package/dashboard/server/services/file-watcher.js +105 -0
  30. package/dashboard/server/services/planning-reader.js +741 -0
  31. package/dashboard/server/test/cli.test.js +34 -0
  32. package/dashboard/server/test/frontmatter.test.js +104 -0
  33. package/dashboard/server/test/isolation.test.js +32 -0
  34. package/dashboard/server/test/planning-reader.test.js +151 -0
  35. package/dashboard/server/test/routes.test.js +91 -0
  36. package/dashboard/server/test/ws.test.js +81 -0
  37. package/dashboard/server/ws.js +96 -0
  38. package/dashboard/src/App.jsx +165 -0
  39. package/dashboard/src/components/charts/BudgetBars.jsx +42 -0
  40. package/dashboard/src/components/charts/ContextRadar.jsx +34 -0
  41. package/dashboard/src/components/charts/PhaseDonut.jsx +66 -0
  42. package/dashboard/src/components/charts/SuccessTrend.jsx +45 -0
  43. package/dashboard/src/components/charts/TokenChart.jsx +55 -0
  44. package/dashboard/src/components/charts/index.js +5 -0
  45. package/dashboard/src/components/config/CfgSection.jsx +93 -0
  46. package/dashboard/src/components/layout/Header.jsx +89 -0
  47. package/dashboard/src/components/layout/ProjectSwitcher.jsx +160 -0
  48. package/dashboard/src/components/layout/Sidebar.jsx +161 -0
  49. package/dashboard/src/components/ui/AutoModeBanner.jsx +138 -0
  50. package/dashboard/src/components/ui/BackButton.jsx +27 -0
  51. package/dashboard/src/components/ui/Badge.jsx +27 -0
  52. package/dashboard/src/components/ui/Card.jsx +23 -0
  53. package/dashboard/src/components/ui/ChartTooltip.jsx +48 -0
  54. package/dashboard/src/components/ui/CheckpointBox.jsx +110 -0
  55. package/dashboard/src/components/ui/CodeBlock.jsx +27 -0
  56. package/dashboard/src/components/ui/ConfidenceBadge.jsx +20 -0
  57. package/dashboard/src/components/ui/ConfirmModal.jsx +161 -0
  58. package/dashboard/src/components/ui/ConnectionBanner.jsx +60 -0
  59. package/dashboard/src/components/ui/ErrorBoundary.jsx +106 -0
  60. package/dashboard/src/components/ui/ErrorBox.jsx +107 -0
  61. package/dashboard/src/components/ui/KeyValue.jsx +33 -0
  62. package/dashboard/src/components/ui/LoadingSkeleton.jsx +84 -0
  63. package/dashboard/src/components/ui/MetricCard.jsx +58 -0
  64. package/dashboard/src/components/ui/NextUpBlock.jsx +92 -0
  65. package/dashboard/src/components/ui/NumberInput.jsx +44 -0
  66. package/dashboard/src/components/ui/PBRBanner.jsx +47 -0
  67. package/dashboard/src/components/ui/PipelineView.jsx +130 -0
  68. package/dashboard/src/components/ui/ProgressBar.jsx +28 -0
  69. package/dashboard/src/components/ui/ProgressDisplay.jsx +47 -0
  70. package/dashboard/src/components/ui/QualityGateBadge.jsx +15 -0
  71. package/dashboard/src/components/ui/SectionTitle.jsx +35 -0
  72. package/dashboard/src/components/ui/SelectInput.jsx +45 -0
  73. package/dashboard/src/components/ui/StatusDot.jsx +51 -0
  74. package/dashboard/src/components/ui/StatusSymbol.jsx +49 -0
  75. package/dashboard/src/components/ui/TabBar.jsx +41 -0
  76. package/dashboard/src/components/ui/TextInput.jsx +42 -0
  77. package/dashboard/src/components/ui/Toast.jsx +117 -0
  78. package/dashboard/src/components/ui/Toggle.jsx +70 -0
  79. package/dashboard/src/components/ui/index.js +29 -0
  80. package/dashboard/src/hooks/useDocumentTitle.js +16 -0
  81. package/dashboard/src/hooks/useFetch.js +50 -0
  82. package/dashboard/src/hooks/useToast.jsx +43 -0
  83. package/dashboard/src/hooks/useWebSocket.js +103 -0
  84. package/dashboard/src/lib/api.js +112 -0
  85. package/dashboard/src/lib/configSchema.js +189 -0
  86. package/dashboard/src/lib/constants.js +22 -0
  87. package/dashboard/src/main.jsx +15 -0
  88. package/dashboard/src/pages/AgentsPage.jsx +191 -0
  89. package/dashboard/src/pages/ConfigPage.jsx +298 -0
  90. package/dashboard/src/pages/HooksPage.jsx +412 -0
  91. package/dashboard/src/pages/IncidentsPage.jsx +135 -0
  92. package/dashboard/src/pages/IntelPage.jsx +193 -0
  93. package/dashboard/src/pages/LiveFeed.jsx +274 -0
  94. package/dashboard/src/pages/MemoryPage.jsx +107 -0
  95. package/dashboard/src/pages/OnboardingPage.jsx +117 -0
  96. package/dashboard/src/pages/Overview.jsx +360 -0
  97. package/dashboard/src/pages/PhaseDetailView.jsx +216 -0
  98. package/dashboard/src/pages/PlanningPage.jsx +181 -0
  99. package/dashboard/src/pages/ProgressPage.jsx +249 -0
  100. package/dashboard/src/pages/ResearchPage.jsx +129 -0
  101. package/dashboard/src/pages/RoadmapPage.jsx +251 -0
  102. package/dashboard/src/pages/SessionsPage.jsx +117 -0
  103. package/dashboard/src/pages/Telemetry.jsx +166 -0
  104. package/dashboard/src/pages/planning/DecisionsTab.jsx +153 -0
  105. package/dashboard/src/pages/planning/FilesTab.jsx +420 -0
  106. package/dashboard/src/pages/planning/MilestoneDetail.jsx +319 -0
  107. package/dashboard/src/pages/planning/MilestonesTab.jsx +151 -0
  108. package/dashboard/src/pages/planning/NotesTab.jsx +251 -0
  109. package/dashboard/src/pages/planning/PhasesTab.jsx +218 -0
  110. package/dashboard/src/pages/planning/QuickTab.jsx +50 -0
  111. package/dashboard/src/pages/planning/ResearchTab.jsx +103 -0
  112. package/dashboard/src/pages/planning/TodosTab.jsx +297 -0
  113. package/dashboard/src/theme/ThemeProvider.jsx +38 -0
  114. package/dashboard/src/theme/tokens.js +17 -0
  115. package/dashboard/tests/components/ConfirmModal.test.jsx +179 -0
  116. package/dashboard/tests/components/ConnectionBanner.test.jsx +37 -0
  117. package/dashboard/tests/components/ErrorBoundary.test.jsx +59 -0
  118. package/dashboard/tests/components/LoadingSkeleton.test.jsx +46 -0
  119. package/dashboard/tests/components/ToastContainer.test.jsx +47 -0
  120. package/dashboard/tests/components/Toggle.test.jsx +61 -0
  121. package/dashboard/tests/hooks/useFetch.test.jsx +77 -0
  122. package/dashboard/tests/hooks/useToast.test.jsx +78 -0
  123. package/dashboard/tests/hooks/useWebSocket.test.jsx +128 -0
  124. package/dashboard/tests/pages/ConfigPage.test.jsx +199 -0
  125. package/dashboard/tests/pages/PlanningPage.test.jsx +119 -0
  126. package/dashboard/tests/pages/planning/FilesTab.test.jsx +198 -0
  127. package/dashboard/tests/pages/planning/NotesTab.test.jsx +178 -0
  128. package/dashboard/tests/pages/planning/TodosTab.test.jsx +188 -0
  129. package/dashboard/tests/performance.test.jsx +46 -0
  130. package/dashboard/tests/routes/config.test.js +98 -0
  131. package/dashboard/tests/routes/health.test.js +40 -0
  132. package/dashboard/tests/routes/planning.test.js +112 -0
  133. package/dashboard/tests/routes/roadmap.test.js +91 -0
  134. package/dashboard/tests/routes/status.test.js +131 -0
  135. package/dashboard/tests/server/planning-reader.test.js +153 -0
  136. package/dashboard/tests/setup.js +7 -0
  137. package/dashboard/vite.config.js +41 -0
  138. package/package.json +55 -40
  139. package/plan-build-run/bin/config-schema.json +1420 -0
  140. package/plugins/pbr/.claude-plugin/plugin.json +1 -1
  141. package/plugins/pbr/CLAUDE.md +19 -0
  142. package/plugins/pbr/UI-CONSISTENCY-GAPS.md +1 -1
  143. package/plugins/pbr/agents/advisor-researcher.md +100 -0
  144. package/plugins/pbr/agents/audit.md +205 -89
  145. package/plugins/pbr/agents/codebase-mapper.md +158 -23
  146. package/plugins/pbr/agents/debugger.md +216 -34
  147. package/plugins/pbr/agents/dev-sync.md +206 -0
  148. package/plugins/pbr/agents/executor.md +717 -39
  149. package/plugins/pbr/agents/general.md +71 -6
  150. package/plugins/pbr/agents/integration-checker.md +146 -30
  151. package/plugins/pbr/agents/intel-updater.md +332 -0
  152. package/plugins/pbr/agents/nyquist-auditor.md +253 -0
  153. package/plugins/pbr/agents/plan-checker.md +265 -65
  154. package/plugins/pbr/agents/planner.md +440 -42
  155. package/plugins/pbr/agents/researcher.md +223 -36
  156. package/plugins/pbr/agents/roadmapper.md +397 -0
  157. package/plugins/pbr/agents/synthesizer.md +170 -26
  158. package/plugins/pbr/agents/ui-checker.md +203 -0
  159. package/plugins/pbr/agents/ui-researcher.md +224 -0
  160. package/plugins/pbr/agents/verifier.md +495 -47
  161. package/plugins/pbr/commands/add-phase.md +75 -0
  162. package/plugins/pbr/commands/add-todo.md +8 -0
  163. package/plugins/pbr/commands/audit-fix.md +5 -0
  164. package/plugins/pbr/commands/audit-milestone.md +8 -0
  165. package/plugins/pbr/commands/autonomous.md +5 -0
  166. package/plugins/pbr/commands/backlog.md +6 -0
  167. package/plugins/pbr/commands/check-todos.md +8 -0
  168. package/plugins/pbr/commands/complete-milestone.md +8 -0
  169. package/plugins/pbr/commands/config.md +1 -1
  170. package/plugins/pbr/commands/discuss-phase.md +6 -0
  171. package/plugins/pbr/commands/do.md +5 -0
  172. package/plugins/pbr/commands/execute-phase.md +6 -0
  173. package/plugins/pbr/commands/fast.md +6 -0
  174. package/plugins/pbr/commands/forensics.md +6 -0
  175. package/plugins/pbr/commands/import.md +1 -1
  176. package/plugins/pbr/commands/insert-phase.md +65 -0
  177. package/plugins/pbr/commands/intel.md +5 -0
  178. package/plugins/pbr/commands/join-discord.md +11 -0
  179. package/plugins/pbr/commands/list-phase-assumptions.md +5 -0
  180. package/plugins/pbr/commands/map-codebase.md +6 -0
  181. package/plugins/pbr/commands/milestone-summary.md +6 -0
  182. package/plugins/pbr/commands/new-milestone.md +8 -0
  183. package/plugins/pbr/commands/new-project.md +6 -0
  184. package/plugins/pbr/commands/pause-work.md +5 -0
  185. package/plugins/pbr/commands/plan-milestone-gaps.md +7 -0
  186. package/plugins/pbr/commands/plan-phase.md +6 -0
  187. package/plugins/pbr/commands/plant-seed.md +6 -0
  188. package/plugins/pbr/commands/profile-user.md +5 -0
  189. package/plugins/pbr/commands/profile.md +5 -0
  190. package/plugins/pbr/commands/progress.md +6 -0
  191. package/plugins/pbr/commands/quick.md +1 -1
  192. package/plugins/pbr/commands/reapply-patches.md +47 -0
  193. package/plugins/pbr/commands/release.md +6 -0
  194. package/plugins/pbr/commands/remove-phase.md +66 -0
  195. package/plugins/pbr/commands/research-phase.md +59 -0
  196. package/plugins/pbr/commands/resume-work.md +5 -0
  197. package/plugins/pbr/commands/seed.md +6 -0
  198. package/plugins/pbr/commands/session-report.md +5 -0
  199. package/plugins/pbr/commands/set-profile.md +6 -0
  200. package/plugins/pbr/commands/settings.md +5 -0
  201. package/plugins/pbr/commands/setup.md +1 -1
  202. package/plugins/pbr/commands/ship.md +5 -0
  203. package/plugins/pbr/commands/stats.md +6 -0
  204. package/plugins/pbr/commands/test.md +5 -0
  205. package/plugins/pbr/commands/thread.md +6 -0
  206. package/plugins/pbr/commands/todo.md +1 -1
  207. package/plugins/pbr/commands/ui-phase.md +5 -0
  208. package/plugins/pbr/commands/ui-review.md +5 -0
  209. package/plugins/pbr/commands/undo.md +5 -0
  210. package/plugins/pbr/commands/update.md +37 -0
  211. package/plugins/pbr/commands/validate-phase.md +5 -0
  212. package/plugins/pbr/commands/verify-work.md +6 -0
  213. package/plugins/pbr/dashboard/package-lock.json +6 -0
  214. package/plugins/pbr/dist/architecture-guard.js +59 -0
  215. package/plugins/pbr/dist/audit-dimensions.js +556 -0
  216. package/plugins/pbr/dist/auto-continue.js +277 -0
  217. package/plugins/pbr/dist/block-skill-self-read.js +124 -0
  218. package/plugins/pbr/dist/check-agent-state-write.js +63 -0
  219. package/plugins/pbr/dist/check-config-change.js +162 -0
  220. package/plugins/pbr/dist/check-cross-plugin-sync.js +93 -0
  221. package/plugins/pbr/dist/check-dangerous-commands.js +193 -0
  222. package/plugins/pbr/dist/check-direct-state-write.js +37 -0
  223. package/plugins/pbr/dist/check-doc-sprawl.js +102 -0
  224. package/plugins/pbr/dist/check-phase-boundary.js +191 -0
  225. package/plugins/pbr/dist/check-plan-format.js +241 -0
  226. package/plugins/pbr/dist/check-read-first.js +345 -0
  227. package/plugins/pbr/dist/check-roadmap-sync.js +503 -0
  228. package/plugins/pbr/dist/check-skill-workflow.js +354 -0
  229. package/plugins/pbr/dist/check-state-sync.js +658 -0
  230. package/plugins/pbr/dist/check-subagent-output.js +452 -0
  231. package/plugins/pbr/dist/check-summary-gate.js +199 -0
  232. package/plugins/pbr/dist/context-bridge.js +425 -0
  233. package/plugins/pbr/dist/context-budget-check.js +442 -0
  234. package/plugins/pbr/dist/context-quality.js +271 -0
  235. package/plugins/pbr/dist/enforce-context-budget.js +138 -0
  236. package/plugins/pbr/dist/enforce-pbr-workflow.js +277 -0
  237. package/plugins/pbr/dist/event-handler.js +202 -0
  238. package/plugins/pbr/dist/event-logger.js +125 -0
  239. package/plugins/pbr/dist/feedback-loop.js +172 -0
  240. package/plugins/pbr/dist/graph-update.js +422 -0
  241. package/plugins/pbr/dist/hook-logger.js +114 -0
  242. package/plugins/pbr/dist/hook-server-client.js +361 -0
  243. package/plugins/pbr/dist/hook-server.js +606 -0
  244. package/plugins/pbr/dist/hooks-schema.json +87 -0
  245. package/plugins/pbr/dist/instructions-loaded.js +173 -0
  246. package/plugins/pbr/dist/intercept-plan-mode.js +81 -0
  247. package/plugins/pbr/dist/log-notification.js +131 -0
  248. package/plugins/pbr/dist/log-subagent.js +349 -0
  249. package/plugins/pbr/dist/log-tool-failure.js +140 -0
  250. package/plugins/pbr/dist/milestone-learnings.js +569 -0
  251. package/plugins/pbr/dist/pbr-tools.js +2044 -0
  252. package/plugins/pbr/dist/post-bash-triage.js +154 -0
  253. package/plugins/pbr/dist/post-compact.js +135 -0
  254. package/plugins/pbr/dist/post-hoc.js +286 -0
  255. package/plugins/pbr/dist/post-write-dispatch.js +279 -0
  256. package/plugins/pbr/dist/post-write-quality.js +208 -0
  257. package/plugins/pbr/dist/pre-bash-dispatch.js +218 -0
  258. package/plugins/pbr/dist/pre-skill-dispatch.js +114 -0
  259. package/plugins/pbr/dist/pre-task-dispatch.js +297 -0
  260. package/plugins/pbr/dist/pre-write-dispatch.js +234 -0
  261. package/plugins/pbr/dist/progress-tracker.js +198 -0
  262. package/plugins/pbr/dist/prompt-guard.js +114 -0
  263. package/plugins/pbr/dist/prompt-routing.js +209 -0
  264. package/plugins/pbr/dist/quick-status.js +179 -0
  265. package/plugins/pbr/dist/record-incident.js +37 -0
  266. package/plugins/pbr/dist/run-hook.js +144 -0
  267. package/plugins/pbr/dist/session-cleanup.js +683 -0
  268. package/plugins/pbr/dist/session-tracker.js +124 -0
  269. package/plugins/pbr/dist/status-line.js +847 -0
  270. package/plugins/pbr/dist/suggest-compact.js +315 -0
  271. package/plugins/pbr/dist/sync-context-to-claude.js +100 -0
  272. package/plugins/pbr/dist/task-completed.js +206 -0
  273. package/plugins/pbr/dist/track-context-budget.js +432 -0
  274. package/plugins/pbr/dist/track-user-gates.js +88 -0
  275. package/plugins/pbr/dist/trust-tracker.js +193 -0
  276. package/plugins/pbr/dist/validate-commit.js +271 -0
  277. package/plugins/pbr/dist/validate-skill-args.js +222 -0
  278. package/plugins/pbr/dist/validate-task.js +301 -0
  279. package/plugins/pbr/dist/worktree-create.js +144 -0
  280. package/plugins/pbr/dist/worktree-remove.js +147 -0
  281. package/plugins/pbr/hooks/hooks.json +143 -60
  282. package/plugins/pbr/references/agent-contracts.md +39 -8
  283. package/plugins/pbr/references/agent-teams.md +3 -3
  284. package/plugins/pbr/references/archive/checkpoints.md +189 -0
  285. package/plugins/pbr/references/archive/context-quality-tiers.md +45 -0
  286. package/plugins/pbr/references/archive/hook-ordering.md +89 -0
  287. package/plugins/pbr/references/archive/limitations.md +106 -0
  288. package/plugins/pbr/references/archive/pbr-rules.md +194 -0
  289. package/plugins/pbr/references/archive/pbr-tools-cli.md +415 -0
  290. package/plugins/pbr/references/archive/pretooluse-jsonl-behavior.md +58 -0
  291. package/plugins/pbr/references/archive/signal-files.md +41 -0
  292. package/plugins/pbr/references/archive/tmux-setup.md +288 -0
  293. package/plugins/pbr/references/archive/verification-matrix.md +34 -0
  294. package/plugins/pbr/references/archive/verification-patterns.md +277 -0
  295. package/plugins/pbr/references/archive/worktree-sparse-checkout.md +86 -0
  296. package/plugins/pbr/references/behavioral-contexts.md +53 -0
  297. package/plugins/pbr/references/checkpoints.md +723 -104
  298. package/plugins/pbr/references/config-reference.md +472 -10
  299. package/plugins/pbr/references/continuation-format.md +1 -0
  300. package/plugins/pbr/references/decimal-phase-calculation.md +65 -0
  301. package/plugins/pbr/references/deviation-rules.md +12 -0
  302. package/plugins/pbr/references/git-integration.md +110 -27
  303. package/plugins/pbr/references/git-planning-commit.md +35 -0
  304. package/plugins/pbr/references/model-profile-resolution.md +34 -0
  305. package/plugins/pbr/references/model-profiles.md +90 -7
  306. package/plugins/pbr/references/model-selection.md +1 -1
  307. package/plugins/pbr/references/node-repair.md +48 -0
  308. package/plugins/pbr/references/plan-authoring.md +65 -0
  309. package/plugins/pbr/references/plan-format.md +161 -10
  310. package/plugins/pbr/references/questioning.md +138 -49
  311. package/plugins/pbr/references/reading-verification.md +4 -4
  312. package/plugins/pbr/references/tdd.md +263 -0
  313. package/plugins/pbr/references/ui-brand.md +449 -0
  314. package/plugins/pbr/references/verification-overrides.md +39 -0
  315. package/plugins/pbr/references/verification-patterns.md +529 -113
  316. package/plugins/pbr/scripts/architecture-guard.js +59 -0
  317. package/plugins/pbr/scripts/audit-checks/behavioral-compliance.js +2098 -0
  318. package/plugins/pbr/scripts/audit-checks/error-analysis.js +989 -0
  319. package/plugins/pbr/scripts/audit-checks/feature-verification.js +723 -0
  320. package/plugins/pbr/scripts/audit-checks/index.js +433 -0
  321. package/plugins/pbr/scripts/audit-checks/infrastructure.js +816 -0
  322. package/plugins/pbr/scripts/audit-checks/quality-metrics.js +452 -0
  323. package/plugins/pbr/scripts/audit-checks/session-quality.js +980 -0
  324. package/plugins/pbr/scripts/audit-checks/si-agent-hook-config-checks.js +466 -0
  325. package/plugins/pbr/scripts/audit-checks/si-cross-cutting-checks.js +272 -0
  326. package/plugins/pbr/scripts/audit-checks/si-skill-checks.js +424 -0
  327. package/plugins/pbr/scripts/audit-checks/workflow-compliance.js +1211 -0
  328. package/plugins/pbr/scripts/audit-dimensions.js +556 -0
  329. package/plugins/pbr/scripts/auto-continue.js +192 -31
  330. package/plugins/pbr/scripts/block-skill-self-read.js +124 -0
  331. package/plugins/pbr/scripts/check-agent-state-write.js +63 -0
  332. package/plugins/pbr/scripts/check-config-change.js +162 -0
  333. package/plugins/pbr/scripts/check-cross-plugin-sync.js +93 -0
  334. package/plugins/pbr/scripts/check-dangerous-commands.js +18 -5
  335. package/plugins/pbr/scripts/check-direct-state-write.js +37 -0
  336. package/plugins/pbr/scripts/check-phase-boundary.js +3 -8
  337. package/plugins/pbr/scripts/check-plan-format.js +166 -277
  338. package/plugins/pbr/scripts/check-read-first.js +345 -0
  339. package/plugins/pbr/scripts/check-roadmap-sync.js +167 -10
  340. package/plugins/pbr/scripts/check-skill-workflow.js +24 -27
  341. package/plugins/pbr/scripts/check-state-sync.js +339 -215
  342. package/plugins/pbr/scripts/check-subagent-output.js +338 -276
  343. package/plugins/pbr/scripts/check-summary-gate.js +2 -1
  344. package/plugins/pbr/scripts/config-schema.json +1247 -95
  345. package/plugins/pbr/scripts/context-bridge.js +425 -0
  346. package/plugins/pbr/scripts/context-budget-check.js +169 -14
  347. package/plugins/pbr/scripts/context-quality.js +271 -0
  348. package/plugins/pbr/scripts/enforce-context-budget.js +138 -0
  349. package/plugins/pbr/scripts/enforce-pbr-workflow.js +277 -0
  350. package/plugins/pbr/scripts/event-handler.js +127 -87
  351. package/plugins/pbr/scripts/event-logger.js +58 -25
  352. package/plugins/pbr/scripts/feedback-loop.js +172 -0
  353. package/plugins/pbr/scripts/graph-update.js +422 -0
  354. package/plugins/pbr/scripts/hook-logger.js +69 -35
  355. package/plugins/pbr/scripts/hook-server-client.js +361 -0
  356. package/plugins/pbr/scripts/hook-server.js +606 -0
  357. package/plugins/pbr/scripts/hooks-schema.json +13 -5
  358. package/plugins/pbr/scripts/instructions-loaded.js +173 -0
  359. package/plugins/pbr/scripts/intent-router.cjs +147 -0
  360. package/plugins/pbr/scripts/intercept-plan-mode.js +52 -18
  361. package/plugins/pbr/scripts/lib/alternatives.js +203 -0
  362. package/plugins/pbr/scripts/lib/audit.js +65 -0
  363. package/plugins/pbr/scripts/lib/auto-cleanup.js +221 -0
  364. package/plugins/pbr/scripts/lib/auto-verify.js +103 -0
  365. package/plugins/pbr/scripts/lib/autonomy.js +91 -0
  366. package/plugins/pbr/scripts/lib/build.js +719 -0
  367. package/plugins/pbr/scripts/lib/ci-fix-loop.js +228 -0
  368. package/plugins/pbr/scripts/lib/circuit-state.js +133 -0
  369. package/plugins/pbr/scripts/lib/commands.js +483 -0
  370. package/plugins/pbr/scripts/lib/completion.js +377 -0
  371. package/plugins/pbr/scripts/lib/compound.js +216 -0
  372. package/plugins/pbr/scripts/lib/config.js +1315 -0
  373. package/plugins/pbr/scripts/lib/context.js +254 -0
  374. package/plugins/pbr/scripts/lib/contextual-help.js +207 -0
  375. package/plugins/pbr/scripts/lib/convention-detector.js +413 -0
  376. package/plugins/pbr/scripts/lib/core.js +1569 -0
  377. package/plugins/pbr/scripts/lib/dashboard-launch.js +364 -0
  378. package/plugins/pbr/scripts/lib/data-hygiene.js +179 -0
  379. package/plugins/pbr/scripts/lib/decision-extraction.js +183 -0
  380. package/plugins/pbr/scripts/lib/decisions.js +194 -0
  381. package/plugins/pbr/scripts/lib/dependency-break.js +147 -0
  382. package/plugins/pbr/scripts/lib/format-validators.js +1050 -0
  383. package/plugins/pbr/scripts/lib/frontmatter.js +302 -0
  384. package/plugins/pbr/scripts/lib/gates/advisories.js +129 -0
  385. package/plugins/pbr/scripts/lib/gates/build-dependency.js +115 -0
  386. package/plugins/pbr/scripts/lib/gates/build-executor.js +104 -0
  387. package/plugins/pbr/scripts/lib/gates/doc-existence.js +46 -0
  388. package/plugins/pbr/scripts/lib/gates/helpers.js +93 -0
  389. package/plugins/pbr/scripts/lib/gates/inline-execution.js +185 -0
  390. package/plugins/pbr/scripts/lib/gates/milestone-complete.js +136 -0
  391. package/plugins/pbr/scripts/lib/gates/milestone-summary.js +119 -0
  392. package/plugins/pbr/scripts/lib/gates/multi-phase-loader.js +147 -0
  393. package/plugins/pbr/scripts/lib/gates/plan-executor.js +36 -0
  394. package/plugins/pbr/scripts/lib/gates/plan-validation.js +114 -0
  395. package/plugins/pbr/scripts/lib/gates/quick-executor.js +76 -0
  396. package/plugins/pbr/scripts/lib/gates/review-planner.js +61 -0
  397. package/plugins/pbr/scripts/lib/gates/review-verifier.js +69 -0
  398. package/plugins/pbr/scripts/lib/gates/rich-agent-context.js +143 -0
  399. package/plugins/pbr/scripts/lib/gates/user-confirmation.js +93 -0
  400. package/plugins/pbr/scripts/lib/graph-cli.js +89 -0
  401. package/plugins/pbr/scripts/lib/graph.js +553 -0
  402. package/plugins/pbr/scripts/lib/health-checks.js +107 -0
  403. package/plugins/pbr/scripts/lib/health-phase06.js +120 -0
  404. package/plugins/pbr/scripts/lib/health.js +133 -0
  405. package/plugins/pbr/scripts/lib/help.js +151 -0
  406. package/plugins/pbr/scripts/lib/history.js +150 -0
  407. package/plugins/pbr/scripts/lib/hypothesis-runner.js +127 -0
  408. package/plugins/pbr/scripts/lib/impact-analysis.js +319 -0
  409. package/plugins/pbr/scripts/lib/incidents.js +190 -0
  410. package/plugins/pbr/scripts/lib/init.js +643 -0
  411. package/plugins/pbr/scripts/lib/insights-parser.js +320 -0
  412. package/plugins/pbr/scripts/lib/intel.js +653 -0
  413. package/plugins/pbr/scripts/lib/learnings.js +511 -0
  414. package/plugins/pbr/scripts/lib/local-llm/client.js +237 -0
  415. package/plugins/pbr/scripts/lib/local-llm/health.js +12 -0
  416. package/plugins/pbr/scripts/lib/local-llm/index.js +89 -0
  417. package/plugins/pbr/scripts/lib/local-llm/metrics.js +20 -0
  418. package/plugins/pbr/scripts/lib/local-llm/operations/classify-artifact.js +4 -0
  419. package/plugins/pbr/scripts/lib/local-llm/operations/classify-commit.js +4 -0
  420. package/plugins/pbr/scripts/lib/local-llm/operations/classify-error.js +4 -0
  421. package/plugins/pbr/scripts/lib/local-llm/operations/classify-file-intent.js +4 -0
  422. package/plugins/pbr/scripts/lib/local-llm/operations/score-source.js +72 -0
  423. package/plugins/pbr/scripts/lib/local-llm/operations/summarize-context.js +62 -0
  424. package/plugins/pbr/scripts/lib/local-llm/operations/triage-test-output.js +12 -0
  425. package/plugins/pbr/scripts/lib/local-llm/operations/validate-task.js +4 -0
  426. package/plugins/pbr/scripts/lib/local-llm/router.js +101 -0
  427. package/plugins/pbr/scripts/lib/local-llm/shadow.js +60 -0
  428. package/plugins/pbr/scripts/lib/local-llm/threshold-tuner.js +118 -0
  429. package/plugins/pbr/scripts/lib/migrate.js +298 -0
  430. package/plugins/pbr/scripts/lib/milestone.js +306 -0
  431. package/plugins/pbr/scripts/lib/negative-knowledge.js +194 -0
  432. package/plugins/pbr/scripts/lib/notification-throttle.js +141 -0
  433. package/plugins/pbr/scripts/lib/onboarding-generator.js +288 -0
  434. package/plugins/pbr/scripts/lib/parse-args.js +134 -0
  435. package/plugins/pbr/scripts/lib/pattern-routing.js +55 -0
  436. package/plugins/pbr/scripts/lib/patterns.js +272 -0
  437. package/plugins/pbr/scripts/lib/perf.js +190 -0
  438. package/plugins/pbr/scripts/lib/phase.js +1027 -0
  439. package/plugins/pbr/scripts/lib/pid-lock.js +154 -0
  440. package/plugins/pbr/scripts/lib/post-hoc.js +160 -0
  441. package/plugins/pbr/scripts/lib/pre-commit-checks.js +220 -0
  442. package/plugins/pbr/scripts/lib/pre-research.js +133 -0
  443. package/plugins/pbr/scripts/lib/preview.js +174 -0
  444. package/plugins/pbr/scripts/lib/progress-visualization.js +296 -0
  445. package/plugins/pbr/scripts/lib/quick-init.js +131 -0
  446. package/plugins/pbr/scripts/lib/reference.js +236 -0
  447. package/plugins/pbr/scripts/lib/requirements.js +153 -0
  448. package/plugins/pbr/scripts/lib/resolve-root.js +66 -0
  449. package/plugins/pbr/scripts/lib/reverse-spec.js +259 -0
  450. package/plugins/pbr/scripts/lib/roadmap.js +1113 -0
  451. package/plugins/pbr/scripts/lib/security-scan.js +200 -0
  452. package/plugins/pbr/scripts/lib/session-briefing.js +895 -0
  453. package/plugins/pbr/scripts/lib/skill-section.js +99 -0
  454. package/plugins/pbr/scripts/lib/smart-next-task.js +207 -0
  455. package/plugins/pbr/scripts/lib/snapshot-manager.js +232 -0
  456. package/plugins/pbr/scripts/lib/spec-diff.js +209 -0
  457. package/plugins/pbr/scripts/lib/spec-engine.js +189 -0
  458. package/plugins/pbr/scripts/lib/spot-check.js +642 -0
  459. package/plugins/pbr/scripts/lib/state-queue.js +171 -0
  460. package/plugins/pbr/scripts/lib/state.js +1187 -0
  461. package/plugins/pbr/scripts/lib/status-render.js +511 -0
  462. package/plugins/pbr/scripts/lib/step-verify.js +149 -0
  463. package/plugins/pbr/scripts/lib/subagent-validators.js +1059 -0
  464. package/plugins/pbr/scripts/lib/suggest-next.js +435 -0
  465. package/plugins/pbr/scripts/lib/team-composer.js +87 -0
  466. package/plugins/pbr/scripts/lib/team-coordinator.js +153 -0
  467. package/plugins/pbr/scripts/lib/tech-debt-scanner.js +116 -0
  468. package/plugins/pbr/scripts/lib/template.js +222 -0
  469. package/plugins/pbr/scripts/lib/templates.js +362 -0
  470. package/plugins/pbr/scripts/lib/test-cache.js +54 -0
  471. package/plugins/pbr/scripts/lib/test-selection.js +163 -0
  472. package/plugins/pbr/scripts/lib/todo.js +300 -0
  473. package/plugins/pbr/scripts/lib/trust-gate.js +84 -0
  474. package/plugins/pbr/scripts/lib/verify.js +1473 -0
  475. package/plugins/pbr/scripts/lib/wiring-check.js +196 -0
  476. package/plugins/pbr/scripts/log-notification.js +131 -0
  477. package/plugins/pbr/scripts/log-subagent.js +203 -18
  478. package/plugins/pbr/scripts/log-tool-failure.js +60 -8
  479. package/plugins/pbr/scripts/milestone-learnings.js +569 -0
  480. package/plugins/pbr/scripts/package.json +1 -1
  481. package/plugins/pbr/scripts/pbr-tools.js +1833 -1167
  482. package/plugins/pbr/scripts/post-bash-triage.js +154 -0
  483. package/plugins/pbr/scripts/post-compact.js +135 -0
  484. package/plugins/pbr/scripts/post-hoc.js +286 -0
  485. package/plugins/pbr/scripts/post-write-dispatch.js +237 -31
  486. package/plugins/pbr/scripts/post-write-quality.js +4 -3
  487. package/plugins/pbr/scripts/pre-bash-dispatch.js +154 -52
  488. package/plugins/pbr/scripts/pre-skill-dispatch.js +114 -0
  489. package/plugins/pbr/scripts/pre-task-dispatch.js +297 -0
  490. package/plugins/pbr/scripts/pre-write-dispatch.js +170 -73
  491. package/plugins/pbr/scripts/progress-tracker.js +144 -307
  492. package/plugins/pbr/scripts/prompt-guard.js +114 -0
  493. package/plugins/pbr/scripts/prompt-routing.js +209 -0
  494. package/plugins/pbr/scripts/quick-status.js +179 -0
  495. package/plugins/pbr/scripts/record-incident.js +37 -0
  496. package/plugins/pbr/scripts/risk-classifier.cjs +123 -0
  497. package/plugins/pbr/scripts/run-hook.js +62 -10
  498. package/plugins/pbr/scripts/session-cleanup.js +458 -29
  499. package/plugins/pbr/scripts/session-tracker.js +124 -0
  500. package/plugins/pbr/scripts/status-line.js +591 -32
  501. package/plugins/pbr/scripts/suggest-compact.js +203 -7
  502. package/plugins/pbr/scripts/sync-context-to-claude.js +100 -0
  503. package/plugins/pbr/scripts/task-completed.js +165 -4
  504. package/plugins/pbr/scripts/test/config.test.js +126 -0
  505. package/plugins/pbr/scripts/test/cross-platform.test.js +131 -0
  506. package/plugins/pbr/scripts/test/fixtures/config.json +20 -0
  507. package/plugins/pbr/scripts/test/fixtures/plan.md +54 -0
  508. package/plugins/pbr/scripts/test/fixtures/project.md +30 -0
  509. package/plugins/pbr/scripts/test/fixtures/roadmap.md +55 -0
  510. package/plugins/pbr/scripts/test/fixtures/state.md +60 -0
  511. package/plugins/pbr/scripts/test/fixtures/summary.md +35 -0
  512. package/plugins/pbr/scripts/test/fixtures.test.js +184 -0
  513. package/plugins/pbr/scripts/test/phase.test.js +142 -0
  514. package/plugins/pbr/scripts/test/roadmap.test.js +96 -0
  515. package/plugins/pbr/scripts/test/state.test.js +163 -0
  516. package/plugins/pbr/scripts/track-context-budget.js +368 -99
  517. package/plugins/pbr/scripts/track-user-gates.js +88 -0
  518. package/plugins/pbr/scripts/trust-tracker.js +193 -0
  519. package/plugins/pbr/scripts/validate-commit.js +97 -26
  520. package/plugins/pbr/scripts/validate-skill-args.js +87 -15
  521. package/plugins/pbr/scripts/validate-task.js +112 -626
  522. package/plugins/pbr/scripts/worktree-create.js +144 -0
  523. package/plugins/pbr/scripts/worktree-remove.js +147 -0
  524. package/plugins/pbr/skills/audit/SKILL.md +195 -24
  525. package/plugins/pbr/skills/audit-fix/SKILL.md +326 -0
  526. package/plugins/pbr/skills/autonomous/SKILL.md +545 -0
  527. package/plugins/pbr/skills/backlog/SKILL.md +56 -0
  528. package/plugins/pbr/skills/begin/SKILL.md +507 -153
  529. package/plugins/pbr/skills/begin/templates/STATE.md.tmpl +1 -2
  530. package/plugins/pbr/skills/begin/templates/config.json.tmpl +415 -36
  531. package/plugins/pbr/skills/begin/templates/researcher-prompt.md.tmpl +28 -0
  532. package/plugins/pbr/skills/begin/templates/roadmap-prompt.md.tmpl +28 -3
  533. package/plugins/pbr/skills/begin/templates/synthesis-prompt.md.tmpl +33 -5
  534. package/plugins/pbr/skills/build/SKILL.md +1040 -354
  535. package/plugins/pbr/skills/build/templates/continuation-prompt.md.tmpl +26 -0
  536. package/plugins/pbr/skills/build/templates/executor-prompt.md.tmpl +77 -0
  537. package/plugins/pbr/skills/build/templates/inline-verifier-prompt.md.tmpl +33 -0
  538. package/plugins/pbr/skills/config/SKILL.md +111 -9
  539. package/plugins/pbr/skills/continue/SKILL.md +113 -33
  540. package/plugins/pbr/skills/dashboard/SKILL.md +21 -9
  541. package/plugins/pbr/skills/debug/SKILL.md +70 -12
  542. package/plugins/pbr/skills/debug/templates/continuation-prompt.md.tmpl +12 -1
  543. package/plugins/pbr/skills/debug/templates/initial-investigation-prompt.md.tmpl +12 -5
  544. package/plugins/pbr/skills/discuss/SKILL.md +206 -25
  545. package/plugins/pbr/skills/discuss/templates/CONTEXT.md.tmpl +21 -1
  546. package/plugins/pbr/skills/do/SKILL.md +119 -24
  547. package/plugins/pbr/skills/explore/SKILL.md +95 -20
  548. package/plugins/pbr/skills/fast/SKILL.md +94 -0
  549. package/plugins/pbr/skills/forensics/SKILL.md +144 -0
  550. package/plugins/pbr/skills/health/SKILL.md +35 -117
  551. package/plugins/pbr/skills/help/SKILL.md +83 -101
  552. package/plugins/pbr/skills/import/SKILL.md +332 -13
  553. package/plugins/pbr/skills/intel/SKILL.md +131 -0
  554. package/plugins/pbr/skills/list-phase-assumptions/SKILL.md +231 -0
  555. package/plugins/pbr/skills/milestone/SKILL.md +421 -263
  556. package/plugins/pbr/skills/milestone/templates/audit-output.md.tmpl +76 -0
  557. package/plugins/pbr/skills/milestone/templates/complete-output.md.tmpl +32 -0
  558. package/plugins/pbr/skills/milestone/templates/edge-cases.md +54 -0
  559. package/plugins/pbr/skills/milestone/templates/gaps-output.md.tmpl +25 -0
  560. package/plugins/pbr/skills/milestone/templates/integration-checker-prompt.md.tmpl +25 -0
  561. package/plugins/pbr/skills/milestone/templates/new-output.md.tmpl +29 -0
  562. package/plugins/pbr/skills/milestone-summary/SKILL.md +86 -0
  563. package/plugins/pbr/skills/note/SKILL.md +20 -4
  564. package/plugins/pbr/skills/pause/SKILL.md +53 -14
  565. package/plugins/pbr/skills/pause/templates/continue-here.md.tmpl +33 -52
  566. package/plugins/pbr/skills/plan/SKILL.md +526 -280
  567. package/plugins/pbr/skills/plan/templates/checker-prompt.md.tmpl +5 -2
  568. package/plugins/pbr/skills/plan/templates/completion-output.md.tmpl +27 -0
  569. package/plugins/pbr/skills/plan/templates/planner-prompt.md.tmpl +27 -1
  570. package/plugins/pbr/skills/plan/templates/revision-prompt.md.tmpl +21 -5
  571. package/plugins/pbr/skills/profile/SKILL.md +185 -0
  572. package/plugins/pbr/skills/profile-user/SKILL.md +226 -0
  573. package/plugins/pbr/skills/quick/SKILL.md +434 -100
  574. package/plugins/pbr/skills/release/SKILL.md +206 -0
  575. package/plugins/pbr/skills/resume/SKILL.md +169 -46
  576. package/plugins/pbr/skills/review/SKILL.md +217 -164
  577. package/plugins/pbr/skills/review/templates/verifier-prompt.md.tmpl +7 -0
  578. package/plugins/pbr/skills/scan/SKILL.md +151 -106
  579. package/plugins/pbr/skills/scan/templates/mapper-prompt.md.tmpl +5 -56
  580. package/plugins/pbr/skills/seed/SKILL.md +87 -0
  581. package/plugins/pbr/skills/session-report/SKILL.md +130 -0
  582. package/plugins/pbr/skills/setup/SKILL.md +149 -202
  583. package/plugins/pbr/skills/shared/agent-context-enrichment.md +21 -0
  584. package/plugins/pbr/skills/shared/agent-type-resolution.md +32 -0
  585. package/plugins/pbr/skills/shared/commit-planning-docs.md +8 -0
  586. package/plugins/pbr/skills/shared/context-budget.md +66 -1
  587. package/plugins/pbr/skills/shared/context-loader-task.md +18 -11
  588. package/plugins/pbr/skills/shared/digest-select.md +2 -2
  589. package/plugins/pbr/skills/shared/domain-probes.md +1 -1
  590. package/plugins/pbr/skills/shared/error-reporting.md +38 -60
  591. package/plugins/pbr/skills/shared/gate-prompts.md +4 -2
  592. package/plugins/pbr/skills/shared/memory-capture.md +48 -0
  593. package/plugins/pbr/skills/shared/phase-argument-parsing.md +4 -4
  594. package/plugins/pbr/skills/shared/revision-loop.md +24 -6
  595. package/plugins/pbr/skills/shared/state-update.md +47 -54
  596. package/plugins/pbr/skills/shared/universal-anti-patterns.md +27 -4
  597. package/plugins/pbr/skills/ship/SKILL.md +155 -0
  598. package/plugins/pbr/skills/stats/SKILL.md +80 -0
  599. package/plugins/pbr/skills/status/SKILL.md +184 -119
  600. package/plugins/pbr/skills/test/SKILL.md +254 -0
  601. package/plugins/pbr/skills/thread/SKILL.md +73 -0
  602. package/plugins/pbr/skills/todo/SKILL.md +28 -72
  603. package/plugins/pbr/skills/ui-phase/SKILL.md +180 -0
  604. package/plugins/pbr/skills/ui-review/SKILL.md +206 -0
  605. package/plugins/pbr/skills/undo/SKILL.md +221 -0
  606. package/plugins/pbr/skills/validate-phase/SKILL.md +362 -0
  607. package/plugins/pbr/templates/CONTEXT.md.tmpl +45 -20
  608. package/plugins/pbr/templates/DISCOVERY.md.tmpl +29 -0
  609. package/plugins/pbr/templates/DISCUSSION-LOG.md.tmpl +49 -0
  610. package/plugins/pbr/templates/HANDOFF.json.tmpl +30 -0
  611. package/plugins/pbr/templates/INTEGRATION-REPORT.md.tmpl +18 -2
  612. package/plugins/pbr/templates/MILESTONE-AUDIT.md.tmpl +44 -0
  613. package/plugins/pbr/templates/PROJECT.md.tmpl +126 -0
  614. package/plugins/pbr/templates/REQUIREMENTS.md.tmpl +96 -0
  615. package/plugins/pbr/templates/RETROSPECTIVE.md.tmpl +43 -0
  616. package/plugins/pbr/templates/ROADMAP.md.tmpl +108 -14
  617. package/plugins/pbr/templates/SUMMARY-complex.md.tmpl +133 -0
  618. package/plugins/pbr/templates/SUMMARY-minimal.md.tmpl +55 -0
  619. package/plugins/pbr/templates/SUMMARY.md.tmpl +21 -0
  620. package/plugins/pbr/templates/UAT.md.tmpl +94 -0
  621. package/plugins/pbr/templates/UI-SPEC.md.tmpl +144 -0
  622. package/plugins/pbr/templates/VALIDATION.md.tmpl +94 -0
  623. package/plugins/pbr/templates/VERIFICATION-DETAIL.md.tmpl +49 -13
  624. package/plugins/pbr/templates/project-CONTEXT.md.tmpl +59 -0
  625. package/plugins/pbr/templates/research-outputs/ARCHITECTURE.md.tmpl +91 -0
  626. package/plugins/pbr/templates/research-outputs/FEATURES.md.tmpl +64 -0
  627. package/plugins/pbr/templates/research-outputs/PITFALLS.md.tmpl +50 -0
  628. package/plugins/pbr/templates/research-outputs/STACK.md.tmpl +63 -0
  629. package/plugins/pbr/templates/research-outputs/SUMMARY.md.tmpl +98 -0
  630. package/scripts/build-hooks.js +61 -0
  631. package/scripts/check-ci.js +100 -0
  632. package/scripts/clean-changelog.js +364 -0
  633. package/scripts/generate-derivatives.js +581 -0
  634. package/scripts/posttest.js +93 -0
  635. package/scripts/release.js +262 -0
  636. package/scripts/run-tests.cjs +29 -0
  637. package/scripts/test-wrapper.js +43 -0
  638. package/dashboard/bin/cli.js +0 -25
  639. package/dashboard/public/css/layout.css +0 -704
  640. package/dashboard/public/css/status-colors.css +0 -98
  641. package/dashboard/public/css/tokens.css +0 -59
  642. package/dashboard/public/js/htmx-title.js +0 -5
  643. package/dashboard/public/js/sidebar-toggle.js +0 -34
  644. package/dashboard/public/js/sse-client.js +0 -100
  645. package/dashboard/public/js/theme-toggle.js +0 -46
  646. package/dashboard/src/app.js +0 -91
  647. package/dashboard/src/middleware/current-phase.js +0 -24
  648. package/dashboard/src/middleware/errorHandler.js +0 -52
  649. package/dashboard/src/middleware/notFoundHandler.js +0 -9
  650. package/dashboard/src/repositories/planning.repository.js +0 -130
  651. package/dashboard/src/routes/events.routes.js +0 -45
  652. package/dashboard/src/routes/index.routes.js +0 -35
  653. package/dashboard/src/routes/pages.routes.js +0 -426
  654. package/dashboard/src/server.js +0 -42
  655. package/dashboard/src/services/analytics.service.js +0 -141
  656. package/dashboard/src/services/dashboard.service.js +0 -309
  657. package/dashboard/src/services/milestone.service.js +0 -222
  658. package/dashboard/src/services/notes.service.js +0 -50
  659. package/dashboard/src/services/phase.service.js +0 -232
  660. package/dashboard/src/services/project.service.js +0 -57
  661. package/dashboard/src/services/roadmap.service.js +0 -258
  662. package/dashboard/src/services/sse.service.js +0 -58
  663. package/dashboard/src/services/todo.service.js +0 -272
  664. package/dashboard/src/services/watcher.service.js +0 -48
  665. package/dashboard/src/utils/cache.js +0 -55
  666. package/dashboard/src/views/analytics.ejs +0 -5
  667. package/dashboard/src/views/coming-soon.ejs +0 -11
  668. package/dashboard/src/views/dependencies.ejs +0 -5
  669. package/dashboard/src/views/error.ejs +0 -20
  670. package/dashboard/src/views/index.ejs +0 -5
  671. package/dashboard/src/views/milestone-detail.ejs +0 -5
  672. package/dashboard/src/views/milestones.ejs +0 -5
  673. package/dashboard/src/views/notes.ejs +0 -5
  674. package/dashboard/src/views/partials/analytics-content.ejs +0 -90
  675. package/dashboard/src/views/partials/breadcrumbs.ejs +0 -14
  676. package/dashboard/src/views/partials/dashboard-content.ejs +0 -84
  677. package/dashboard/src/views/partials/dependencies-content.ejs +0 -48
  678. package/dashboard/src/views/partials/empty-state.ejs +0 -7
  679. package/dashboard/src/views/partials/footer.ejs +0 -3
  680. package/dashboard/src/views/partials/head.ejs +0 -30
  681. package/dashboard/src/views/partials/header.ejs +0 -21
  682. package/dashboard/src/views/partials/layout-bottom.ejs +0 -43
  683. package/dashboard/src/views/partials/layout-top.ejs +0 -16
  684. package/dashboard/src/views/partials/milestone-detail-content.ejs +0 -20
  685. package/dashboard/src/views/partials/milestones-content.ejs +0 -88
  686. package/dashboard/src/views/partials/notes-content.ejs +0 -23
  687. package/dashboard/src/views/partials/phase-content.ejs +0 -193
  688. package/dashboard/src/views/partials/phase-doc-content.ejs +0 -38
  689. package/dashboard/src/views/partials/phases-content.ejs +0 -124
  690. package/dashboard/src/views/partials/roadmap-content.ejs +0 -180
  691. package/dashboard/src/views/partials/sidebar.ejs +0 -99
  692. package/dashboard/src/views/partials/todo-create-content.ejs +0 -54
  693. package/dashboard/src/views/partials/todo-detail-content.ejs +0 -42
  694. package/dashboard/src/views/partials/todos-content.ejs +0 -97
  695. package/dashboard/src/views/phase-detail.ejs +0 -5
  696. package/dashboard/src/views/phase-doc.ejs +0 -5
  697. package/dashboard/src/views/phases.ejs +0 -5
  698. package/dashboard/src/views/roadmap.ejs +0 -5
  699. package/dashboard/src/views/todo-create.ejs +0 -5
  700. package/dashboard/src/views/todo-detail.ejs +0 -5
  701. package/dashboard/src/views/todos.ejs +0 -5
  702. package/plugins/copilot-pbr/CHANGELOG.md +0 -19
  703. package/plugins/copilot-pbr/README.md +0 -139
  704. package/plugins/copilot-pbr/agents/audit.agent.md +0 -113
  705. package/plugins/copilot-pbr/agents/codebase-mapper.agent.md +0 -151
  706. package/plugins/copilot-pbr/agents/debugger.agent.md +0 -182
  707. package/plugins/copilot-pbr/agents/executor.agent.md +0 -267
  708. package/plugins/copilot-pbr/agents/general.agent.md +0 -88
  709. package/plugins/copilot-pbr/agents/integration-checker.agent.md +0 -119
  710. package/plugins/copilot-pbr/agents/plan-checker.agent.md +0 -208
  711. package/plugins/copilot-pbr/agents/planner.agent.md +0 -238
  712. package/plugins/copilot-pbr/agents/researcher.agent.md +0 -186
  713. package/plugins/copilot-pbr/agents/synthesizer.agent.md +0 -126
  714. package/plugins/copilot-pbr/agents/verifier.agent.md +0 -228
  715. package/plugins/copilot-pbr/hooks/hooks.json +0 -156
  716. package/plugins/copilot-pbr/plugin.json +0 -30
  717. package/plugins/copilot-pbr/references/agent-anti-patterns.md +0 -25
  718. package/plugins/copilot-pbr/references/agent-contracts.md +0 -297
  719. package/plugins/copilot-pbr/references/agent-interactions.md +0 -135
  720. package/plugins/copilot-pbr/references/agent-teams.md +0 -55
  721. package/plugins/copilot-pbr/references/checkpoints.md +0 -158
  722. package/plugins/copilot-pbr/references/common-bug-patterns.md +0 -14
  723. package/plugins/copilot-pbr/references/config-reference.md +0 -442
  724. package/plugins/copilot-pbr/references/continuation-format.md +0 -213
  725. package/plugins/copilot-pbr/references/deviation-rules.md +0 -113
  726. package/plugins/copilot-pbr/references/git-integration.md +0 -227
  727. package/plugins/copilot-pbr/references/integration-patterns.md +0 -118
  728. package/plugins/copilot-pbr/references/model-profiles.md +0 -100
  729. package/plugins/copilot-pbr/references/model-selection.md +0 -32
  730. package/plugins/copilot-pbr/references/pbr-rules.md +0 -195
  731. package/plugins/copilot-pbr/references/pbr-tools-cli.md +0 -285
  732. package/plugins/copilot-pbr/references/plan-authoring.md +0 -182
  733. package/plugins/copilot-pbr/references/plan-format.md +0 -288
  734. package/plugins/copilot-pbr/references/planning-config.md +0 -214
  735. package/plugins/copilot-pbr/references/questioning.md +0 -215
  736. package/plugins/copilot-pbr/references/reading-verification.md +0 -128
  737. package/plugins/copilot-pbr/references/stub-patterns.md +0 -161
  738. package/plugins/copilot-pbr/references/subagent-coordination.md +0 -120
  739. package/plugins/copilot-pbr/references/ui-formatting.md +0 -444
  740. package/plugins/copilot-pbr/references/verification-patterns.md +0 -199
  741. package/plugins/copilot-pbr/references/wave-execution.md +0 -96
  742. package/plugins/copilot-pbr/rules/pbr-workflow.mdc +0 -48
  743. package/plugins/copilot-pbr/setup.ps1 +0 -93
  744. package/plugins/copilot-pbr/setup.sh +0 -93
  745. package/plugins/copilot-pbr/skills/audit/SKILL.md +0 -330
  746. package/plugins/copilot-pbr/skills/begin/SKILL.md +0 -589
  747. package/plugins/copilot-pbr/skills/begin/templates/PROJECT.md.tmpl +0 -34
  748. package/plugins/copilot-pbr/skills/begin/templates/REQUIREMENTS.md.tmpl +0 -19
  749. package/plugins/copilot-pbr/skills/begin/templates/STATE.md.tmpl +0 -50
  750. package/plugins/copilot-pbr/skills/begin/templates/config.json.tmpl +0 -64
  751. package/plugins/copilot-pbr/skills/begin/templates/researcher-prompt.md.tmpl +0 -20
  752. package/plugins/copilot-pbr/skills/begin/templates/roadmap-prompt.md.tmpl +0 -31
  753. package/plugins/copilot-pbr/skills/begin/templates/synthesis-prompt.md.tmpl +0 -17
  754. package/plugins/copilot-pbr/skills/build/SKILL.md +0 -960
  755. package/plugins/copilot-pbr/skills/config/SKILL.md +0 -250
  756. package/plugins/copilot-pbr/skills/continue/SKILL.md +0 -159
  757. package/plugins/copilot-pbr/skills/dashboard/SKILL.md +0 -43
  758. package/plugins/copilot-pbr/skills/debug/SKILL.md +0 -508
  759. package/plugins/copilot-pbr/skills/debug/templates/continuation-prompt.md.tmpl +0 -17
  760. package/plugins/copilot-pbr/skills/debug/templates/initial-investigation-prompt.md.tmpl +0 -28
  761. package/plugins/copilot-pbr/skills/discuss/SKILL.md +0 -353
  762. package/plugins/copilot-pbr/skills/discuss/templates/CONTEXT.md.tmpl +0 -62
  763. package/plugins/copilot-pbr/skills/discuss/templates/decision-categories.md +0 -10
  764. package/plugins/copilot-pbr/skills/do/SKILL.md +0 -66
  765. package/plugins/copilot-pbr/skills/explore/SKILL.md +0 -373
  766. package/plugins/copilot-pbr/skills/health/SKILL.md +0 -283
  767. package/plugins/copilot-pbr/skills/health/templates/check-pattern.md.tmpl +0 -31
  768. package/plugins/copilot-pbr/skills/health/templates/output-format.md.tmpl +0 -64
  769. package/plugins/copilot-pbr/skills/help/SKILL.md +0 -170
  770. package/plugins/copilot-pbr/skills/import/SKILL.md +0 -502
  771. package/plugins/copilot-pbr/skills/milestone/SKILL.md +0 -745
  772. package/plugins/copilot-pbr/skills/milestone/templates/audit-report.md.tmpl +0 -49
  773. package/plugins/copilot-pbr/skills/milestone/templates/stats-file.md.tmpl +0 -31
  774. package/plugins/copilot-pbr/skills/note/SKILL.md +0 -213
  775. package/plugins/copilot-pbr/skills/pause/SKILL.md +0 -247
  776. package/plugins/copilot-pbr/skills/pause/templates/continue-here.md.tmpl +0 -72
  777. package/plugins/copilot-pbr/skills/plan/SKILL.md +0 -662
  778. package/plugins/copilot-pbr/skills/plan/templates/checker-prompt.md.tmpl +0 -22
  779. package/plugins/copilot-pbr/skills/plan/templates/gap-closure-prompt.md.tmpl +0 -33
  780. package/plugins/copilot-pbr/skills/plan/templates/planner-prompt.md.tmpl +0 -39
  781. package/plugins/copilot-pbr/skills/plan/templates/researcher-prompt.md.tmpl +0 -20
  782. package/plugins/copilot-pbr/skills/plan/templates/revision-prompt.md.tmpl +0 -24
  783. package/plugins/copilot-pbr/skills/quick/SKILL.md +0 -376
  784. package/plugins/copilot-pbr/skills/resume/SKILL.md +0 -399
  785. package/plugins/copilot-pbr/skills/review/SKILL.md +0 -653
  786. package/plugins/copilot-pbr/skills/review/templates/debugger-prompt.md.tmpl +0 -61
  787. package/plugins/copilot-pbr/skills/review/templates/gap-planner-prompt.md.tmpl +0 -41
  788. package/plugins/copilot-pbr/skills/review/templates/verifier-prompt.md.tmpl +0 -116
  789. package/plugins/copilot-pbr/skills/scan/SKILL.md +0 -299
  790. package/plugins/copilot-pbr/skills/scan/templates/mapper-prompt.md.tmpl +0 -202
  791. package/plugins/copilot-pbr/skills/setup/SKILL.md +0 -296
  792. package/plugins/copilot-pbr/skills/shared/commit-planning-docs.md +0 -36
  793. package/plugins/copilot-pbr/skills/shared/config-loading.md +0 -103
  794. package/plugins/copilot-pbr/skills/shared/context-budget.md +0 -41
  795. package/plugins/copilot-pbr/skills/shared/context-loader-task.md +0 -87
  796. package/plugins/copilot-pbr/skills/shared/digest-select.md +0 -80
  797. package/plugins/copilot-pbr/skills/shared/domain-probes.md +0 -126
  798. package/plugins/copilot-pbr/skills/shared/error-reporting.md +0 -81
  799. package/plugins/copilot-pbr/skills/shared/gate-prompts.md +0 -389
  800. package/plugins/copilot-pbr/skills/shared/phase-argument-parsing.md +0 -46
  801. package/plugins/copilot-pbr/skills/shared/progress-display.md +0 -53
  802. package/plugins/copilot-pbr/skills/shared/revision-loop.md +0 -82
  803. package/plugins/copilot-pbr/skills/shared/state-loading.md +0 -63
  804. package/plugins/copilot-pbr/skills/shared/state-update.md +0 -162
  805. package/plugins/copilot-pbr/skills/shared/universal-anti-patterns.md +0 -38
  806. package/plugins/copilot-pbr/skills/status/SKILL.md +0 -362
  807. package/plugins/copilot-pbr/skills/statusline/SKILL.md +0 -149
  808. package/plugins/copilot-pbr/skills/todo/SKILL.md +0 -279
  809. package/plugins/copilot-pbr/templates/CONTEXT.md.tmpl +0 -53
  810. package/plugins/copilot-pbr/templates/INTEGRATION-REPORT.md.tmpl +0 -152
  811. package/plugins/copilot-pbr/templates/RESEARCH-SUMMARY.md.tmpl +0 -98
  812. package/plugins/copilot-pbr/templates/ROADMAP.md.tmpl +0 -41
  813. package/plugins/copilot-pbr/templates/SUMMARY.md.tmpl +0 -82
  814. package/plugins/copilot-pbr/templates/VERIFICATION-DETAIL.md.tmpl +0 -117
  815. package/plugins/copilot-pbr/templates/codebase/ARCHITECTURE.md.tmpl +0 -98
  816. package/plugins/copilot-pbr/templates/codebase/CONCERNS.md.tmpl +0 -93
  817. package/plugins/copilot-pbr/templates/codebase/CONVENTIONS.md.tmpl +0 -104
  818. package/plugins/copilot-pbr/templates/codebase/INTEGRATIONS.md.tmpl +0 -78
  819. package/plugins/copilot-pbr/templates/codebase/STACK.md.tmpl +0 -78
  820. package/plugins/copilot-pbr/templates/codebase/STRUCTURE.md.tmpl +0 -80
  821. package/plugins/copilot-pbr/templates/codebase/TESTING.md.tmpl +0 -107
  822. package/plugins/copilot-pbr/templates/continue-here.md.tmpl +0 -74
  823. package/plugins/copilot-pbr/templates/prompt-partials/phase-project-context.md.tmpl +0 -38
  824. package/plugins/copilot-pbr/templates/research/ARCHITECTURE.md.tmpl +0 -124
  825. package/plugins/copilot-pbr/templates/research/STACK.md.tmpl +0 -71
  826. package/plugins/copilot-pbr/templates/research/SUMMARY.md.tmpl +0 -112
  827. package/plugins/copilot-pbr/templates/research-outputs/phase-research.md.tmpl +0 -81
  828. package/plugins/copilot-pbr/templates/research-outputs/project-research.md.tmpl +0 -99
  829. package/plugins/copilot-pbr/templates/research-outputs/synthesis.md.tmpl +0 -36
  830. package/plugins/cursor-pbr/.cursor-plugin/plugin.json +0 -32
  831. package/plugins/cursor-pbr/CHANGELOG.md +0 -15
  832. package/plugins/cursor-pbr/README.md +0 -123
  833. package/plugins/cursor-pbr/agents/audit.md +0 -178
  834. package/plugins/cursor-pbr/agents/codebase-mapper.md +0 -150
  835. package/plugins/cursor-pbr/agents/debugger.md +0 -181
  836. package/plugins/cursor-pbr/agents/executor.md +0 -266
  837. package/plugins/cursor-pbr/agents/general.md +0 -87
  838. package/plugins/cursor-pbr/agents/integration-checker.md +0 -118
  839. package/plugins/cursor-pbr/agents/plan-checker.md +0 -207
  840. package/plugins/cursor-pbr/agents/planner.md +0 -237
  841. package/plugins/cursor-pbr/agents/researcher.md +0 -185
  842. package/plugins/cursor-pbr/agents/synthesizer.md +0 -125
  843. package/plugins/cursor-pbr/agents/verifier.md +0 -227
  844. package/plugins/cursor-pbr/assets/.gitkeep +0 -0
  845. package/plugins/cursor-pbr/assets/logo.svg +0 -21
  846. package/plugins/cursor-pbr/hooks/hooks.json +0 -213
  847. package/plugins/cursor-pbr/references/agent-anti-patterns.md +0 -25
  848. package/plugins/cursor-pbr/references/agent-contracts.md +0 -297
  849. package/plugins/cursor-pbr/references/agent-interactions.md +0 -135
  850. package/plugins/cursor-pbr/references/agent-teams.md +0 -55
  851. package/plugins/cursor-pbr/references/checkpoints.md +0 -158
  852. package/plugins/cursor-pbr/references/common-bug-patterns.md +0 -14
  853. package/plugins/cursor-pbr/references/config-reference.md +0 -442
  854. package/plugins/cursor-pbr/references/continuation-format.md +0 -213
  855. package/plugins/cursor-pbr/references/deviation-rules.md +0 -113
  856. package/plugins/cursor-pbr/references/git-integration.md +0 -227
  857. package/plugins/cursor-pbr/references/integration-patterns.md +0 -118
  858. package/plugins/cursor-pbr/references/model-profiles.md +0 -100
  859. package/plugins/cursor-pbr/references/model-selection.md +0 -32
  860. package/plugins/cursor-pbr/references/pbr-rules.md +0 -195
  861. package/plugins/cursor-pbr/references/pbr-tools-cli.md +0 -285
  862. package/plugins/cursor-pbr/references/plan-authoring.md +0 -182
  863. package/plugins/cursor-pbr/references/plan-format.md +0 -288
  864. package/plugins/cursor-pbr/references/planning-config.md +0 -214
  865. package/plugins/cursor-pbr/references/questioning.md +0 -215
  866. package/plugins/cursor-pbr/references/reading-verification.md +0 -128
  867. package/plugins/cursor-pbr/references/stub-patterns.md +0 -161
  868. package/plugins/cursor-pbr/references/subagent-coordination.md +0 -120
  869. package/plugins/cursor-pbr/references/ui-formatting.md +0 -444
  870. package/plugins/cursor-pbr/references/verification-patterns.md +0 -199
  871. package/plugins/cursor-pbr/references/wave-execution.md +0 -96
  872. package/plugins/cursor-pbr/rules/pbr-workflow.mdc +0 -48
  873. package/plugins/cursor-pbr/setup.ps1 +0 -78
  874. package/plugins/cursor-pbr/setup.sh +0 -83
  875. package/plugins/cursor-pbr/skills/audit/SKILL.md +0 -331
  876. package/plugins/cursor-pbr/skills/begin/SKILL.md +0 -589
  877. package/plugins/cursor-pbr/skills/begin/templates/PROJECT.md.tmpl +0 -34
  878. package/plugins/cursor-pbr/skills/begin/templates/REQUIREMENTS.md.tmpl +0 -19
  879. package/plugins/cursor-pbr/skills/begin/templates/STATE.md.tmpl +0 -50
  880. package/plugins/cursor-pbr/skills/begin/templates/config.json.tmpl +0 -64
  881. package/plugins/cursor-pbr/skills/begin/templates/researcher-prompt.md.tmpl +0 -20
  882. package/plugins/cursor-pbr/skills/begin/templates/roadmap-prompt.md.tmpl +0 -31
  883. package/plugins/cursor-pbr/skills/begin/templates/synthesis-prompt.md.tmpl +0 -17
  884. package/plugins/cursor-pbr/skills/build/SKILL.md +0 -961
  885. package/plugins/cursor-pbr/skills/config/SKILL.md +0 -252
  886. package/plugins/cursor-pbr/skills/continue/SKILL.md +0 -159
  887. package/plugins/cursor-pbr/skills/dashboard/SKILL.md +0 -44
  888. package/plugins/cursor-pbr/skills/debug/SKILL.md +0 -512
  889. package/plugins/cursor-pbr/skills/debug/templates/continuation-prompt.md.tmpl +0 -17
  890. package/plugins/cursor-pbr/skills/debug/templates/initial-investigation-prompt.md.tmpl +0 -28
  891. package/plugins/cursor-pbr/skills/discuss/SKILL.md +0 -354
  892. package/plugins/cursor-pbr/skills/discuss/templates/CONTEXT.md.tmpl +0 -62
  893. package/plugins/cursor-pbr/skills/discuss/templates/decision-categories.md +0 -10
  894. package/plugins/cursor-pbr/skills/do/SKILL.md +0 -67
  895. package/plugins/cursor-pbr/skills/explore/SKILL.md +0 -376
  896. package/plugins/cursor-pbr/skills/health/SKILL.md +0 -283
  897. package/plugins/cursor-pbr/skills/health/templates/check-pattern.md.tmpl +0 -31
  898. package/plugins/cursor-pbr/skills/health/templates/output-format.md.tmpl +0 -64
  899. package/plugins/cursor-pbr/skills/help/SKILL.md +0 -170
  900. package/plugins/cursor-pbr/skills/import/SKILL.md +0 -505
  901. package/plugins/cursor-pbr/skills/milestone/SKILL.md +0 -746
  902. package/plugins/cursor-pbr/skills/milestone/templates/audit-report.md.tmpl +0 -49
  903. package/plugins/cursor-pbr/skills/milestone/templates/stats-file.md.tmpl +0 -31
  904. package/plugins/cursor-pbr/skills/note/SKILL.md +0 -214
  905. package/plugins/cursor-pbr/skills/pause/SKILL.md +0 -248
  906. package/plugins/cursor-pbr/skills/pause/templates/continue-here.md.tmpl +0 -72
  907. package/plugins/cursor-pbr/skills/plan/SKILL.md +0 -663
  908. package/plugins/cursor-pbr/skills/plan/templates/checker-prompt.md.tmpl +0 -22
  909. package/plugins/cursor-pbr/skills/plan/templates/gap-closure-prompt.md.tmpl +0 -33
  910. package/plugins/cursor-pbr/skills/plan/templates/planner-prompt.md.tmpl +0 -39
  911. package/plugins/cursor-pbr/skills/plan/templates/researcher-prompt.md.tmpl +0 -20
  912. package/plugins/cursor-pbr/skills/plan/templates/revision-prompt.md.tmpl +0 -24
  913. package/plugins/cursor-pbr/skills/quick/SKILL.md +0 -376
  914. package/plugins/cursor-pbr/skills/resume/SKILL.md +0 -399
  915. package/plugins/cursor-pbr/skills/review/SKILL.md +0 -654
  916. package/plugins/cursor-pbr/skills/review/templates/debugger-prompt.md.tmpl +0 -61
  917. package/plugins/cursor-pbr/skills/review/templates/gap-planner-prompt.md.tmpl +0 -41
  918. package/plugins/cursor-pbr/skills/review/templates/verifier-prompt.md.tmpl +0 -116
  919. package/plugins/cursor-pbr/skills/scan/SKILL.md +0 -300
  920. package/plugins/cursor-pbr/skills/scan/templates/mapper-prompt.md.tmpl +0 -202
  921. package/plugins/cursor-pbr/skills/setup/SKILL.md +0 -296
  922. package/plugins/cursor-pbr/skills/shared/commit-planning-docs.md +0 -36
  923. package/plugins/cursor-pbr/skills/shared/config-loading.md +0 -103
  924. package/plugins/cursor-pbr/skills/shared/context-budget.md +0 -41
  925. package/plugins/cursor-pbr/skills/shared/context-loader-task.md +0 -87
  926. package/plugins/cursor-pbr/skills/shared/digest-select.md +0 -80
  927. package/plugins/cursor-pbr/skills/shared/domain-probes.md +0 -126
  928. package/plugins/cursor-pbr/skills/shared/error-reporting.md +0 -81
  929. package/plugins/cursor-pbr/skills/shared/gate-prompts.md +0 -389
  930. package/plugins/cursor-pbr/skills/shared/phase-argument-parsing.md +0 -46
  931. package/plugins/cursor-pbr/skills/shared/progress-display.md +0 -53
  932. package/plugins/cursor-pbr/skills/shared/revision-loop.md +0 -82
  933. package/plugins/cursor-pbr/skills/shared/state-loading.md +0 -63
  934. package/plugins/cursor-pbr/skills/shared/state-update.md +0 -162
  935. package/plugins/cursor-pbr/skills/shared/universal-anti-patterns.md +0 -38
  936. package/plugins/cursor-pbr/skills/status/SKILL.md +0 -362
  937. package/plugins/cursor-pbr/skills/statusline/SKILL.md +0 -150
  938. package/plugins/cursor-pbr/skills/todo/SKILL.md +0 -280
  939. package/plugins/cursor-pbr/templates/CONTEXT.md.tmpl +0 -53
  940. package/plugins/cursor-pbr/templates/INTEGRATION-REPORT.md.tmpl +0 -152
  941. package/plugins/cursor-pbr/templates/RESEARCH-SUMMARY.md.tmpl +0 -98
  942. package/plugins/cursor-pbr/templates/ROADMAP.md.tmpl +0 -41
  943. package/plugins/cursor-pbr/templates/SUMMARY.md.tmpl +0 -82
  944. package/plugins/cursor-pbr/templates/VERIFICATION-DETAIL.md.tmpl +0 -117
  945. package/plugins/cursor-pbr/templates/codebase/ARCHITECTURE.md.tmpl +0 -98
  946. package/plugins/cursor-pbr/templates/codebase/CONCERNS.md.tmpl +0 -93
  947. package/plugins/cursor-pbr/templates/codebase/CONVENTIONS.md.tmpl +0 -104
  948. package/plugins/cursor-pbr/templates/codebase/INTEGRATIONS.md.tmpl +0 -78
  949. package/plugins/cursor-pbr/templates/codebase/STACK.md.tmpl +0 -78
  950. package/plugins/cursor-pbr/templates/codebase/STRUCTURE.md.tmpl +0 -80
  951. package/plugins/cursor-pbr/templates/codebase/TESTING.md.tmpl +0 -107
  952. package/plugins/cursor-pbr/templates/continue-here.md.tmpl +0 -74
  953. package/plugins/cursor-pbr/templates/prompt-partials/phase-project-context.md.tmpl +0 -38
  954. package/plugins/cursor-pbr/templates/research/ARCHITECTURE.md.tmpl +0 -124
  955. package/plugins/cursor-pbr/templates/research/STACK.md.tmpl +0 -71
  956. package/plugins/cursor-pbr/templates/research/SUMMARY.md.tmpl +0 -112
  957. package/plugins/cursor-pbr/templates/research-outputs/phase-research.md.tmpl +0 -81
  958. package/plugins/cursor-pbr/templates/research-outputs/project-research.md.tmpl +0 -99
  959. package/plugins/cursor-pbr/templates/research-outputs/synthesis.md.tmpl +0 -36
  960. package/plugins/pbr/references/agent-interactions.md +0 -134
  961. package/plugins/pbr/references/pbr-rules.md +0 -194
  962. package/plugins/pbr/references/pbr-tools-cli.md +0 -285
  963. package/plugins/pbr/references/planning-config.md +0 -213
  964. package/plugins/pbr/references/subagent-coordination.md +0 -119
  965. package/plugins/pbr/references/ui-formatting.md +0 -444
  966. package/plugins/pbr/scripts/validate-plugin-structure.js +0 -183
  967. package/plugins/pbr/skills/milestone/templates/audit-report.md.tmpl +0 -48
  968. package/plugins/pbr/skills/shared/progress-display.md +0 -53
  969. package/plugins/pbr/skills/shared/state-loading.md +0 -62
  970. package/plugins/pbr/templates/RESEARCH-SUMMARY.md.tmpl +0 -97
  971. package/plugins/pbr/templates/research/ARCHITECTURE.md.tmpl +0 -124
  972. package/plugins/pbr/templates/research/STACK.md.tmpl +0 -71
  973. package/plugins/pbr/templates/research/SUMMARY.md.tmpl +0 -112
  974. package/plugins/pbr/templates/research-outputs/phase-research.md.tmpl +0 -81
  975. package/plugins/pbr/templates/research-outputs/project-research.md.tmpl +0 -99
  976. package/plugins/pbr/templates/research-outputs/synthesis.md.tmpl +0 -36
  977. /package/plugins/pbr/references/{agent-anti-patterns.md → archive/agent-anti-patterns.md} +0 -0
@@ -3,8 +3,16 @@
3
3
  /**
4
4
  * pbr-tools.js — Structured JSON state operations for Plan-Build-Run skills.
5
5
  *
6
- * Provides read-only commands that return JSON, replacing LLM-based text parsing
7
- * of STATE.md, ROADMAP.md, and config.json. Skills call this via:
6
+ * Thin dispatcher that imports from lib/ modules. All core logic lives in:
7
+ * lib/core.js — Foundation utilities (parsers, file ops, constants)
8
+ * lib/config.js — Config loading, validation, depth profiles
9
+ * lib/state.js — STATE.md operations (load, update, patch, advance)
10
+ * lib/roadmap.js — ROADMAP.md operations (parse, update status/plans)
11
+ * lib/phase.js — Phase operations (add, remove, list, info, plan-index)
12
+ * lib/init.js — Compound init commands (execute-phase, plan-phase, etc.)
13
+ * lib/history.js — History operations (append to STATE.md ## History, load with HISTORY.md fallback)
14
+ *
15
+ * Skills call this via:
8
16
  * node ${CLAUDE_PLUGIN_ROOT}/scripts/pbr-tools.js <command> [args]
9
17
  *
10
18
  * Commands:
@@ -12,1367 +20,2025 @@
12
20
  * state check-progress — Recalculate progress from filesystem
13
21
  * state update <f> <v> — Atomically update a STATE.md field
14
22
  * config validate — Validate config.json against schema
23
+ * config get <dot.path> — Read a config value by dot-path key
24
+ * intel query <term> — Search intel files for a term
25
+ * intel status — Report staleness of each intel file
26
+ * intel diff — Show changes since last refresh snapshot
27
+ * requirements mark-complete <ids> — Mark comma-separated REQ-IDs as complete
15
28
  * plan-index <phase> — Plan inventory for a phase, grouped by wave
16
29
  * frontmatter <filepath> — Parse .md file's YAML frontmatter → JSON
17
30
  * must-haves <phase> — Collect all must-haves from phase plans → JSON
18
31
  * phase-info <phase> — Comprehensive single-phase status → JSON
19
32
  * roadmap update-status <phase> <status> — Update phase status in ROADMAP.md
20
33
  * roadmap update-plans <phase> <complete> <total> — Update phase plans in ROADMAP.md
21
- * history append <type> <title> [body] Append record to HISTORY.md
22
- * history load Load all HISTORY.md records as JSON
34
+ * roadmap reconcile — Reconcile ROADMAP statuses against disk state
35
+ * roadmap get-phase <N> Get comprehensive phase info JSON (alias for phase-info)
36
+ * roadmap append-phase [--goal "..."] [--name "..."] [--depends-on N] — Append a new phase to the roadmap
37
+ * history append <type> <title> [body] — Append record to STATE.md ## History (fallback: HISTORY.md)
38
+ * history load — Load history records as JSON (STATE.md first, HISTORY.md fallback)
39
+ * todo list [--theme X] [--status Y] — List todos as JSON (default: pending)
40
+ * todo get <NNN> — Get a specific todo by number
41
+ * todo add <title> [--priority P] [--theme T] — Add a new todo
42
+ * todo done <NNN> — Mark a todo as complete
43
+ * auto-cleanup --phase N | --milestone vN — Auto-close todos and archive notes matching phase/milestone deliverables
44
+ * state reconcile — Detect and repair STATE.md/ROADMAP.md desync
45
+ * state backup — Create timestamped backup of STATE.md and ROADMAP.md
46
+ * llm <subcommand> — DEPRECATED: local_llm removed (no-op)
47
+ * validate-project — Comprehensive .planning/ integrity check
48
+ * phase add <slug> [--after N] [--goal "..."] [--depends-on N] — Add phase with ROADMAP.md integration
49
+ * phase remove <N> — Remove an empty phase directory (with renumbering)
50
+ * phase list [--status S] [--before N] — List phase directories with optional status/before filters
51
+ * phase complete <N> — Mark phase N complete, advance STATE.md to next phase
52
+ * phase insert <N> <slug> [--goal "..."] [--depends-on N] — Insert phase at position N, renumber subsequent
53
+ * phase commits-for <N> — Read .phase-manifest.json for phase N, output commits JSON. Falls back to git log
54
+ * phase first-last-commit <N> — Output { first, last } commit hashes from manifest or git log
55
+ * compound init-phase <N> <slug> [--goal "..."] [--depends-on N] — Atomically create phase dir + STATE + ROADMAP
56
+ * compound complete-phase <N> — Atomically validate SUMMARY + update STATE + ROADMAP
57
+ * compound init-milestone <version> [--name "..."] [--phases "N-M"] — Atomically create milestone archive structure
58
+ * learnings ingest <json-file> — Ingest a learning entry into global store
59
+ * learnings query [--tags X] [--min-confidence Y] [--stack S] [--type T] — Query learnings
60
+ * learnings check-thresholds — Check deferral trigger conditions
61
+ * learnings copy-global <path> <proj> — Copy cross_project LEARNINGS.md to ~/.claude/pbr-knowledge/
62
+ * learnings query-global [--tags X] [--project P] — Query global knowledge files
63
+ * data status — Freshness report for research/, intel/, codebase/ directories
64
+ * data prune --before <ISO-date> [--dry-run] — Archive stale research/codebase files
65
+ * incidents list [--limit N] — List recent incidents as JSON array
66
+ * incidents summary — Aggregate incident stats by type/severity/source
67
+ * incidents query [--type T] [--severity S] [--source S] [--last Nd|Nh] — Filter incidents
68
+ * nk record --title "..." --category "..." --files "f1,f2" --tried "..." --failed "..." — Record negative knowledge entry
69
+ * nk list [--category X] [--phase X] [--status X] — List negative knowledge entries
70
+ * nk resolve <slug> — Mark a negative knowledge entry as resolved
71
+ * insights import <html-path> [--project <name>] — Parse insights HTML report into learnings
72
+ * audit plan-checks [--last N] — List all plan-check results from hooks.jsonl (default: last 30 days)
73
+ * hooks perf [--last N] [--json] — Show P50/P95/P99 hook performance report from hooks-*.jsonl
74
+ * spot-check <phaseSlug> <planId> — Verify SUMMARY, key_files, and commits exist for a plan
75
+ * staleness-check <phase-slug> — Check if phase plans are stale vs dependencies
76
+ * summary-gate <phase-slug> <plan-id> — Verify SUMMARY.md exists, non-empty, valid frontmatter
77
+ * checkpoint init <phase-slug> [--plans "id1,id2"] — Initialize checkpoint manifest
78
+ * checkpoint update <phase-slug> --wave N --resolved id [--sha hash] — Update manifest
79
+ * seeds match <phase-slug> <phase-number> — Find matching seed files for a phase
80
+ * session get <key> — Read a key from .planning/.session.json
81
+ * session set <key> <value> — Write a key to .planning/.session.json
82
+ * session clear [key] — Delete .session.json or set key to null
83
+ * session dump — Print entire .session.json content
84
+ * skill-section <skill> <section> — Extract a section from a skill's SKILL.md → JSON
85
+ * skill-section --list <skill> — List all headings in a skill → JSON
86
+ * step-verify [skill] [step] [checklist-json] — Validate per-step completion checklist → JSON
87
+ * build-preview [phase-slug] — Preview what /pbr:execute-phase would do for a phase → JSON
88
+ * claim acquire <phase-slug> --session-id <id> --skill <name> — Acquire phase claim
89
+ * claim release <phase-slug> --session-id <id> — Release phase claim
90
+ * claim list — List all active phase claims
91
+ * suggest-alternatives phase-not-found [slug] — List available phases for unknown slug → JSON
92
+ * suggest-alternatives missing-prereq [phase] — List missing prerequisites for a phase → JSON
93
+ * suggest-alternatives config-invalid [field] [val] — List valid values for invalid config field → JSON
94
+ * help — List all skills with name, description, tools, argument-hint → JSON
95
+ * skill-metadata <name> — Get metadata for a single skill → JSON
96
+ *
97
+ * Environment: PBR_PROJECT_ROOT — Override project root directory (used when hooks fire from subagent cwd)
23
98
  */
24
99
 
25
100
  const fs = require('fs');
26
101
  const path = require('path');
27
102
 
28
- const cwd = process.cwd();
29
- const planningDir = path.join(cwd, '.planning');
103
+ // --- Import lib modules ---
104
+ const {
105
+ KNOWN_AGENTS,
106
+ VALID_STATUS_TRANSITIONS,
107
+ validateStatusTransition,
108
+ output,
109
+ error,
110
+ parseYamlFrontmatter,
111
+ parseMustHaves,
112
+ findFiles,
113
+ tailLines,
114
+ countMustHaves,
115
+ determinePhaseStatus,
116
+ atomicWrite,
117
+ lockedFileUpdate,
118
+ writeActiveSkill,
119
+ sessionLoad,
120
+ sessionSave,
121
+ SESSION_ALLOWED_KEYS,
122
+ STALE_SESSION_MS,
123
+ resolveSessionPath,
124
+ acquireClaim,
125
+ releaseClaim,
126
+ releaseSessionClaims: _releaseSessionClaims,
127
+ listClaims: _listClaims
128
+ } = require('./lib/core');
129
+
130
+ const {
131
+ configLoad: _configLoad,
132
+ configClearCache: _configClearCache,
133
+ configValidate: _configValidate,
134
+ configFormat: _configFormat,
135
+ configWrite: _configWrite,
136
+ resolveDepthProfile,
137
+ DEPTH_PROFILE_DEFAULTS,
138
+ loadUserDefaults,
139
+ saveUserDefaults,
140
+ mergeUserDefaults,
141
+ USER_DEFAULTS_PATH
142
+ } = require('./lib/config');
143
+
144
+ const {
145
+ parseStateMd,
146
+ updateLegacyStateField,
147
+ updateFrontmatterField,
148
+ stateLoad: _stateLoad,
149
+ stateCheckProgress: _stateCheckProgress,
150
+ stateUpdate: _stateUpdate,
151
+ statePatch: _statePatch,
152
+ stateAdvancePlan: _stateAdvancePlan,
153
+ stateRecordMetric: _stateRecordMetric,
154
+ stateRecordActivity: _stateRecordActivity,
155
+ stateUpdateProgress: _stateUpdateProgress
156
+ } = require('./lib/state');
157
+
158
+ const {
159
+ stateReconcile: _stateReconcile,
160
+ stateBackup: _stateBackup
161
+ } = require('./lib/state');
162
+
163
+ const {
164
+ parseRoadmapMd,
165
+ findRoadmapRow,
166
+ updateTableRow,
167
+ roadmapUpdateStatus: _roadmapUpdateStatus,
168
+ roadmapUpdatePlans: _roadmapUpdatePlans,
169
+ roadmapAnalyze: _roadmapAnalyze,
170
+ roadmapAppendPhase: _roadmapAppendPhase,
171
+ roadmapRemovePhase: _roadmapRemovePhase,
172
+ roadmapRenumberPhases: _roadmapRenumberPhases,
173
+ roadmapInsertPhase: _roadmapInsertPhase,
174
+ reconcileRoadmapStatuses: _reconcileRoadmapStatuses
175
+ } = require('./lib/roadmap');
176
+
177
+ const {
178
+ frontmatter: _frontmatter,
179
+ planIndex: _planIndex,
180
+ mustHavesCollect: _mustHavesCollect,
181
+ phaseInfo: _phaseInfo,
182
+ phaseAdd: _phaseAdd,
183
+ phaseRemove: _phaseRemove,
184
+ phaseList: _phaseList,
185
+ milestoneStats: _milestoneStats,
186
+ phaseComplete: _phaseComplete,
187
+ phaseInsert: _phaseInsert,
188
+ phaseNextNumber: _phaseNextNumber
189
+ } = require('./lib/phase');
190
+
191
+ const {
192
+ compoundInitPhase: _compoundInitPhase,
193
+ compoundCompletePhase: _compoundCompletePhase,
194
+ compoundInitMilestone: _compoundInitMilestone
195
+ } = require('./lib/compound');
196
+
197
+ const {
198
+ initExecutePhase: _initExecutePhase,
199
+ initPlanPhase: _initPlanPhase,
200
+ initQuick: _initQuick,
201
+ initVerifyWork: _initVerifyWork,
202
+ initResume: _initResume,
203
+ initProgress: _initProgress,
204
+ initStateBundle: _initStateBundle,
205
+ initContinue: _initContinue,
206
+ initMilestone: _initMilestone,
207
+ initBegin: _initBegin,
208
+ initStatus: _initStatus,
209
+ initMapCodebase: _initMapCodebase
210
+ } = require('./lib/init');
211
+
212
+ const {
213
+ historyAppend: _historyAppend,
214
+ historyLoad: _historyLoad
215
+ } = require('./lib/history');
216
+
217
+ const {
218
+ todoList: _todoList,
219
+ todoGet: _todoGet,
220
+ todoAdd: _todoAdd,
221
+ todoDone: _todoDone
222
+ } = require('./lib/todo');
223
+
224
+ const {
225
+ autoCloseTodos: _autoCloseTodos,
226
+ autoArchiveNotes: _autoArchiveNotes
227
+ } = require('./lib/auto-cleanup');
228
+
229
+ const {
230
+ applyMigrations: _applyMigrations
231
+ } = require('./lib/migrate');
232
+
233
+ const {
234
+ spotCheck: _spotCheck,
235
+ verifySpotCheck: _verifySpotCheck
236
+ } = require('./lib/spot-check');
237
+
238
+ const {
239
+ cmdVerifySummary: _cmdVerifySummary,
240
+ cmdVerifyPlanStructure: _cmdVerifyPlanStructure,
241
+ cmdVerifyPhaseCompleteness: _cmdVerifyPhaseCompleteness,
242
+ cmdVerifyArtifacts: _cmdVerifyArtifacts,
243
+ cmdVerifyKeyLinks: _cmdVerifyKeyLinks,
244
+ cmdVerifyCommits: _cmdVerifyCommits,
245
+ cmdVerifyReferences: _cmdVerifyReferences,
246
+ } = require('./lib/verify');
247
+
248
+ const {
249
+ learningsIngest: _learningsIngest,
250
+ learningsQuery: _learningsQuery,
251
+ checkDeferralThresholds: _checkDeferralThresholds,
252
+ copyToGlobal: _copyToGlobal,
253
+ queryGlobal: _queryGlobal
254
+ } = require('./lib/learnings');
255
+
256
+ const {
257
+ insightsImport: _insightsImport
258
+ } = require('./lib/insights-parser');
259
+
260
+ const {
261
+ referenceGet: _referenceGet
262
+ } = require('./lib/reference');
263
+
264
+ const {
265
+ skillSection: _skillSection,
266
+ listAvailableSkills: _listAvailableSkills
267
+ } = require('./lib/skill-section');
268
+
269
+ const {
270
+ helpList: _helpList,
271
+ skillMetadata: _skillMetadata
272
+ } = require('./lib/help');
273
+
274
+ const {
275
+ stepVerify: _stepVerify
276
+ } = require('./lib/step-verify');
277
+
278
+ const {
279
+ buildPreview: _buildPreview
280
+ } = require('./lib/preview');
281
+
282
+ const {
283
+ contextTriage: _contextTriage
284
+ } = require('./lib/context');
285
+
286
+ const {
287
+ phaseAlternatives: _phaseAlternatives,
288
+ prerequisiteAlternatives: _prereqAlternatives,
289
+ configAlternatives: _configAlternatives
290
+ } = require('./lib/alternatives');
291
+
292
+ const {
293
+ stalenessCheck: _stalenessCheck,
294
+ summaryGate: _summaryGate,
295
+ checkpointInit: _checkpointInit,
296
+ checkpointUpdate: _checkpointUpdate,
297
+ seedsMatch: _seedsMatch,
298
+ ciPoll: _ciPoll,
299
+ rollback: _rollback
300
+ } = require('./lib/build');
301
+
302
+ const {
303
+ parseJestOutput: _parseJestOutput,
304
+ parseLintOutput: _parseLintOutput,
305
+ autoFixLint: _autoFixLint,
306
+ runCiFixLoop: _runCiFixLoop
307
+ } = require('./lib/ci-fix-loop');
308
+
309
+ const {
310
+ statusRender: _statusRender
311
+ } = require('./lib/status-render');
312
+
313
+ const {
314
+ suggestNext: _suggestNext
315
+ } = require('./lib/suggest-next');
316
+
317
+ const {
318
+ quickStatus: _quickStatus
319
+ } = require('./quick-status');
320
+
321
+ // --- Local LLM imports removed (feature archived in phase 53) ---
322
+
323
+ const {
324
+ dataStatus: _dataStatus,
325
+ dataPrune: _dataPrune
326
+ } = require('./lib/data-hygiene');
327
+
328
+ const {
329
+ list: _incidentsList,
330
+ query: _incidentsQuery,
331
+ summary: _incidentsSummary
332
+ } = require('./lib/incidents');
333
+
334
+ // --- Module-level state (for backwards compatibility) ---
335
+
336
+ let cwd = process.env.PBR_PROJECT_ROOT || process.cwd();
337
+ // MSYS path bridging: Git Bash on Windows can produce /d/Repos/... paths
338
+ // that Node.js cannot resolve. Convert to D:\Repos\... form.
339
+ const _msysCwdMatch = cwd.match(/^\/([a-zA-Z])\/(.*)/);
340
+ if (_msysCwdMatch) cwd = _msysCwdMatch[1] + ':' + path.sep + _msysCwdMatch[2].replace(/\//g, path.sep);
341
+ let planningDir = path.join(cwd, '.planning');
342
+
343
+ // --- Wrapper functions that pass planningDir to lib modules ---
344
+ // These preserve the original function signatures (no planningDir param)
345
+ // so existing callers (hook scripts, tests) continue to work.
30
346
 
31
- // --- Phase status transition state machine ---
347
+ function configLoad(dir) {
348
+ return _configLoad(dir || planningDir);
349
+ }
32
350
 
33
- /**
34
- * Valid phase status transitions. Each key is a current status, and its value
35
- * is an array of statuses that are legal to transition to. This is advisory —
36
- * invalid transitions produce a stderr warning but are not blocked, to avoid
37
- * breaking existing workflows.
38
- *
39
- * State machine:
40
- * pending -> planned, skipped
41
- * planned -> building
42
- * building -> built, partial, needs_fixes
43
- * built -> verified, needs_fixes
44
- * partial -> building, needs_fixes
45
- * verified -> building (re-execution)
46
- * needs_fixes -> planned, building
47
- * skipped -> pending (unskip)
48
- */
49
- const VALID_STATUS_TRANSITIONS = {
50
- pending: ['planned', 'skipped'],
51
- planned: ['building'],
52
- building: ['built', 'partial', 'needs_fixes'],
53
- built: ['verified', 'needs_fixes'],
54
- partial: ['building', 'needs_fixes'],
55
- verified: ['building'],
56
- needs_fixes: ['planned', 'building'],
57
- skipped: ['pending']
58
- };
351
+ function configClearCache() {
352
+ _configClearCache();
353
+ cwd = process.env.PBR_PROJECT_ROOT || process.cwd();
354
+ const _msysResetMatch = cwd.match(/^\/([a-zA-Z])\/(.*)/);
355
+ if (_msysResetMatch) cwd = _msysResetMatch[1] + ':' + path.sep + _msysResetMatch[2].replace(/\//g, path.sep);
356
+ planningDir = path.join(cwd, '.planning');
357
+ }
59
358
 
60
- /**
61
- * Check whether a phase status transition is valid according to the state machine.
62
- * Returns { valid, warning? } — never blocks, only advises.
63
- *
64
- * @param {string} oldStatus - Current phase status
65
- * @param {string} newStatus - Desired phase status
66
- * @returns {{ valid: boolean, warning?: string }}
67
- */
68
- function validateStatusTransition(oldStatus, newStatus) {
69
- const from = (oldStatus || '').trim().toLowerCase();
70
- const to = (newStatus || '').trim().toLowerCase();
359
+ function configValidate(preloadedConfig) {
360
+ return _configValidate(preloadedConfig, planningDir);
361
+ }
71
362
 
72
- // If the status isn't changing, that's always fine
73
- if (from === to) {
74
- return { valid: true };
75
- }
363
+ function insightsImport(htmlPath, projectName) {
364
+ return _insightsImport(htmlPath, projectName, planningDir);
365
+ }
76
366
 
77
- // If the old status is unknown to our map, we can't validate — allow it
78
- if (!VALID_STATUS_TRANSITIONS[from]) {
79
- return { valid: true };
80
- }
367
+ function stateLoad() {
368
+ return _stateLoad(planningDir);
369
+ }
81
370
 
82
- const allowed = VALID_STATUS_TRANSITIONS[from];
83
- if (allowed.includes(to)) {
84
- return { valid: true };
85
- }
371
+ function stateCheckProgress() {
372
+ return _stateCheckProgress(planningDir);
373
+ }
86
374
 
87
- return {
88
- valid: false,
89
- warning: `Suspicious status transition: "${from}" -> "${to}". Expected one of: [${allowed.join(', ')}]. Proceeding anyway (advisory).`
90
- };
375
+ function stateUpdate(field, value) {
376
+ return _stateUpdate(field, value, planningDir);
91
377
  }
92
378
 
93
- // --- Cached config loader ---
379
+ function statePatch(jsonStr) {
380
+ return _statePatch(jsonStr, planningDir);
381
+ }
94
382
 
95
- let _configCache = null;
96
- let _configMtime = 0;
97
- let _configPath = null;
383
+ function stateAdvancePlan() {
384
+ return _stateAdvancePlan(planningDir);
385
+ }
98
386
 
99
- /**
100
- * Load config.json with in-process mtime-based caching.
101
- * Returns the parsed config object, or null if not found / parse error.
102
- * Cache invalidates when file mtime changes or path differs.
103
- *
104
- * @param {string} [dir] - Path to .planning directory (defaults to cwd/.planning)
105
- * @returns {object|null} Parsed config or null
106
- */
107
- function configLoad(dir) {
108
- const configPath = path.join(dir || planningDir, 'config.json');
109
- try {
110
- if (!fs.existsSync(configPath)) return null;
111
- const stat = fs.statSync(configPath);
112
- const mtime = stat.mtimeMs;
113
- if (_configCache && mtime === _configMtime && configPath === _configPath) {
114
- return _configCache;
115
- }
116
- _configCache = JSON.parse(fs.readFileSync(configPath, 'utf8'));
117
- _configMtime = mtime;
118
- _configPath = configPath;
119
- return _configCache;
120
- } catch (_e) {
121
- return null;
122
- }
387
+ function stateRecordMetric(metricArgs) {
388
+ return _stateRecordMetric(metricArgs, planningDir);
123
389
  }
124
390
 
125
- /**
126
- * Clear the configLoad() in-process cache.
127
- * Useful in tests where multiple temp directories are used in rapid succession.
128
- */
129
- function configClearCache() {
130
- _configCache = null;
131
- _configMtime = 0;
132
- _configPath = null;
391
+ function stateRecordActivity(description) {
392
+ return _stateRecordActivity(description, planningDir);
133
393
  }
134
394
 
135
- /**
136
- * Read the last N lines from a file efficiently.
137
- * Reads the entire file but only parses (JSON.parse) the trailing entries.
138
- * For JSONL files where full parsing is expensive, this avoids parsing
139
- * all lines when you only need recent entries.
140
- *
141
- * @param {string} filePath - Absolute path to the file
142
- * @param {number} n - Number of trailing lines to return
143
- * @returns {string[]} Array of raw line strings (last n lines)
144
- */
145
- function tailLines(filePath, n) {
146
- try {
147
- if (!fs.existsSync(filePath)) return [];
148
- const content = fs.readFileSync(filePath, 'utf8').trim();
149
- if (!content) return [];
150
- const lines = content.split('\n');
151
- if (lines.length <= n) return lines;
152
- return lines.slice(lines.length - n);
153
- } catch (_e) {
154
- return [];
155
- }
395
+ function stateUpdateProgress() {
396
+ return _stateUpdateProgress(planningDir);
156
397
  }
157
398
 
158
- /**
159
- * Built-in depth profile defaults. These define the effective settings
160
- * for each depth level. User config.depth_profiles overrides these.
161
- */
162
- const DEPTH_PROFILE_DEFAULTS = {
163
- quick: {
164
- 'features.research_phase': false,
165
- 'features.plan_checking': false,
166
- 'features.goal_verification': false,
167
- 'features.inline_verify': false,
168
- 'scan.mapper_count': 2,
169
- 'scan.mapper_areas': ['tech', 'arch'],
170
- 'debug.max_hypothesis_rounds': 3
171
- },
172
- standard: {
173
- 'features.research_phase': true,
174
- 'features.plan_checking': true,
175
- 'features.goal_verification': true,
176
- 'features.inline_verify': false,
177
- 'scan.mapper_count': 4,
178
- 'scan.mapper_areas': ['tech', 'arch', 'quality', 'concerns'],
179
- 'debug.max_hypothesis_rounds': 5
180
- },
181
- comprehensive: {
182
- 'features.research_phase': true,
183
- 'features.plan_checking': true,
184
- 'features.goal_verification': true,
185
- 'features.inline_verify': true,
186
- 'scan.mapper_count': 4,
187
- 'scan.mapper_areas': ['tech', 'arch', 'quality', 'concerns'],
188
- 'debug.max_hypothesis_rounds': 10
189
- }
190
- };
399
+ function stateReconcile() {
400
+ return _stateReconcile(planningDir);
401
+ }
191
402
 
192
- /**
193
- * Resolve the effective depth profile for the current config.
194
- * Merges built-in defaults with any user overrides from config.depth_profiles.
195
- *
196
- * @param {object|null} config - Parsed config.json (from configLoad). If null, returns 'standard' defaults.
197
- * @returns {{ depth: string, profile: object }} The resolved depth name and flattened profile settings.
198
- */
199
- function resolveDepthProfile(config) {
200
- const depth = (config && config.depth) || 'standard';
201
- const defaults = DEPTH_PROFILE_DEFAULTS[depth] || DEPTH_PROFILE_DEFAULTS.standard;
403
+ function stateBackup() {
404
+ return _stateBackup(planningDir);
405
+ }
202
406
 
203
- // Merge user overrides if present
204
- const userOverrides = (config && config.depth_profiles && config.depth_profiles[depth]) || {};
205
- const profile = { ...defaults, ...userOverrides };
407
+ function roadmapAnalyze() {
408
+ return _roadmapAnalyze(planningDir);
409
+ }
206
410
 
207
- return { depth, profile };
411
+ function roadmapUpdateStatus(phaseNum, newStatus) {
412
+ return _roadmapUpdateStatus(phaseNum, newStatus, planningDir);
208
413
  }
209
414
 
210
- function main() {
211
- const args = process.argv.slice(2);
212
- const command = args[0];
213
- const subcommand = args[1];
415
+ function roadmapUpdatePlans(phaseNum, complete, total) {
416
+ return _roadmapUpdatePlans(phaseNum, complete, total, planningDir);
417
+ }
214
418
 
215
- try {
216
- if (command === 'state' && subcommand === 'load') {
217
- output(stateLoad());
218
- } else if (command === 'state' && subcommand === 'check-progress') {
219
- output(stateCheckProgress());
220
- } else if (command === 'state' && subcommand === 'update') {
221
- const field = args[2];
222
- const value = args[3];
223
- if (!field || value === undefined) {
224
- error('Usage: pbr-tools.js state update <field> <value>\nFields: current_phase, status, plans_complete, last_activity');
225
- }
226
- output(stateUpdate(field, value));
227
- } else if (command === 'config' && subcommand === 'validate') {
228
- output(configValidate());
229
- } else if (command === 'config' && subcommand === 'resolve-depth') {
230
- const dir = args[2] || undefined;
231
- const config = configLoad(dir);
232
- output(resolveDepthProfile(config));
233
- } else if (command === 'plan-index') {
234
- const phase = args[1];
235
- if (!phase) {
236
- error('Usage: pbr-tools.js plan-index <phase-number>');
237
- }
238
- output(planIndex(phase));
239
- } else if (command === 'frontmatter') {
240
- const filePath = args[1];
241
- if (!filePath) {
242
- error('Usage: pbr-tools.js frontmatter <filepath>');
243
- }
244
- output(frontmatter(filePath));
245
- } else if (command === 'must-haves') {
246
- const phase = args[1];
247
- if (!phase) {
248
- error('Usage: pbr-tools.js must-haves <phase-number>');
249
- }
250
- output(mustHavesCollect(phase));
251
- } else if (command === 'phase-info') {
252
- const phase = args[1];
253
- if (!phase) {
254
- error('Usage: pbr-tools.js phase-info <phase-number>');
255
- }
256
- output(phaseInfo(phase));
257
- } else if (command === 'roadmap' && subcommand === 'update-status') {
258
- const phase = args[2];
259
- const status = args[3];
260
- if (!phase || !status) {
261
- error('Usage: pbr-tools.js roadmap update-status <phase-number> <status>');
262
- }
263
- output(roadmapUpdateStatus(phase, status));
264
- } else if (command === 'roadmap' && subcommand === 'update-plans') {
265
- const phase = args[2];
266
- const complete = args[3];
267
- const total = args[4];
268
- if (!phase || complete === undefined || total === undefined) {
269
- error('Usage: pbr-tools.js roadmap update-plans <phase-number> <complete> <total>');
270
- }
271
- output(roadmapUpdatePlans(phase, complete, total));
272
- } else if (command === 'history' && subcommand === 'append') {
273
- const type = args[2]; // 'milestone' or 'phase'
274
- const title = args[3];
275
- const body = args[4] || '';
276
- if (!type || !title) {
277
- error('Usage: pbr-tools.js history append <milestone|phase> <title> [body]');
278
- }
279
- output(historyAppend({ type, title, body }));
280
- } else if (command === 'history' && subcommand === 'load') {
281
- output(historyLoad());
282
- } else if (command === 'event') {
283
- const category = args[1];
284
- const event = args[2];
285
- let details = {};
286
- if (args[3]) {
287
- try { details = JSON.parse(args[3]); } catch (_e) { details = { raw: args[3] }; }
288
- }
289
- if (!category || !event) {
290
- error('Usage: pbr-tools.js event <category> <event> [JSON-details]');
291
- }
292
- const { logEvent } = require('./event-logger');
293
- logEvent(category, event, details);
294
- output({ logged: true, category, event });
295
- } else {
296
- error(`Unknown command: ${args.join(' ')}\nCommands: state load|check-progress|update, config validate, plan-index, frontmatter, must-haves, phase-info, roadmap update-status|update-plans, history append|load, event`);
297
- }
298
- } catch (e) {
299
- error(e.message);
300
- }
419
+ function roadmapReconcile() {
420
+ return _reconcileRoadmapStatuses(planningDir);
301
421
  }
302
422
 
303
- // --- Commands ---
423
+ function frontmatter(filePath) {
424
+ return _frontmatter(filePath);
425
+ }
304
426
 
305
- function stateLoad() {
306
- const result = {
307
- exists: false,
308
- config: null,
309
- state: null,
310
- roadmap: null,
311
- phase_count: 0,
312
- current_phase: null,
313
- progress: null
314
- };
427
+ function planIndex(phaseNum) {
428
+ return _planIndex(phaseNum, planningDir);
429
+ }
315
430
 
316
- if (!fs.existsSync(planningDir)) {
317
- return result;
318
- }
319
- result.exists = true;
431
+ function mustHavesCollect(phaseNum) {
432
+ return _mustHavesCollect(phaseNum, planningDir);
433
+ }
320
434
 
321
- // Load config.json
322
- const configPath = path.join(planningDir, 'config.json');
323
- if (fs.existsSync(configPath)) {
324
- try {
325
- result.config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
326
- } catch (_) {
327
- result.config = { _error: 'Failed to parse config.json' };
328
- }
329
- }
435
+ function phaseInfo(phaseNum) {
436
+ return _phaseInfo(phaseNum, planningDir);
437
+ }
330
438
 
331
- // Load STATE.md
332
- const statePath = path.join(planningDir, 'STATE.md');
333
- if (fs.existsSync(statePath)) {
334
- const content = fs.readFileSync(statePath, 'utf8');
335
- result.state = parseStateMd(content);
336
- }
439
+ function phaseAdd(slug, afterPhase, options) {
440
+ return _phaseAdd(slug, afterPhase, planningDir, options);
441
+ }
337
442
 
338
- // Load ROADMAP.md
339
- const roadmapPath = path.join(planningDir, 'ROADMAP.md');
340
- if (fs.existsSync(roadmapPath)) {
341
- const content = fs.readFileSync(roadmapPath, 'utf8');
342
- result.roadmap = parseRoadmapMd(content);
343
- result.phase_count = result.roadmap.phases.length;
344
- }
443
+ function phaseRemove(phaseNum) {
444
+ return _phaseRemove(phaseNum, planningDir);
445
+ }
345
446
 
346
- // Extract current phase
347
- if (result.state && result.state.current_phase) {
348
- result.current_phase = result.state.current_phase;
349
- }
447
+ function phaseList(opts) {
448
+ return _phaseList(planningDir, opts);
449
+ }
350
450
 
351
- // Calculate progress
352
- result.progress = calculateProgress();
451
+ function phaseNextNumber() {
452
+ return _phaseNextNumber(planningDir);
453
+ }
353
454
 
354
- return result;
455
+ function phaseComplete(phaseNum) {
456
+ return _phaseComplete(phaseNum, planningDir);
355
457
  }
356
458
 
357
- function stateCheckProgress() {
358
- const phasesDir = path.join(planningDir, 'phases');
359
- if (!fs.existsSync(phasesDir)) {
360
- return { phases: [], total_plans: 0, completed_plans: 0, percentage: 0 };
361
- }
459
+ function phaseInsert(position, slug, options) {
460
+ return _phaseInsert(position, slug, planningDir, options);
461
+ }
362
462
 
363
- const phases = [];
364
- let totalPlans = 0;
365
- let completedPlans = 0;
366
-
367
- const entries = fs.readdirSync(phasesDir, { withFileTypes: true })
368
- .filter(e => e.isDirectory())
369
- .sort((a, b) => a.name.localeCompare(b.name));
370
-
371
- for (const entry of entries) {
372
- const phaseDir = path.join(phasesDir, entry.name);
373
- const plans = findFiles(phaseDir, /-PLAN\.md$/);
374
- const summaries = findFiles(phaseDir, /^SUMMARY-.*\.md$/);
375
- const verification = fs.existsSync(path.join(phaseDir, 'VERIFICATION.md'));
376
-
377
- const completedSummaries = summaries.filter(s => {
378
- const content = fs.readFileSync(path.join(phaseDir, s), 'utf8');
379
- return /status:\s*["']?complete/i.test(content);
380
- });
381
-
382
- const phaseInfo = {
383
- directory: entry.name,
384
- plans: plans.length,
385
- summaries: summaries.length,
386
- completed: completedSummaries.length,
387
- has_verification: verification,
388
- status: determinePhaseStatus(plans.length, completedSummaries.length, summaries.length, verification, phaseDir)
389
- };
390
-
391
- phases.push(phaseInfo);
392
- totalPlans += plans.length;
393
- completedPlans += completedSummaries.length;
394
- }
463
+ function milestoneStats(version) {
464
+ return _milestoneStats(version, planningDir);
465
+ }
395
466
 
396
- return {
397
- phases,
398
- total_plans: totalPlans,
399
- completed_plans: completedPlans,
400
- percentage: totalPlans > 0 ? Math.round((completedPlans / totalPlans) * 100) : 0
401
- };
467
+ function compoundInitPhase(phaseNum, slug, opts) {
468
+ return _compoundInitPhase(phaseNum, slug, planningDir, opts);
402
469
  }
403
470
 
404
- function planIndex(phaseNum) {
405
- const phasesDir = path.join(planningDir, 'phases');
406
- if (!fs.existsSync(phasesDir)) {
407
- return { error: 'No phases directory found' };
408
- }
471
+ function compoundCompletePhase(phaseNum) {
472
+ return _compoundCompletePhase(phaseNum, planningDir);
473
+ }
409
474
 
410
- // Find phase directory matching the number
411
- const entries = fs.readdirSync(phasesDir, { withFileTypes: true })
412
- .filter(e => e.isDirectory());
475
+ function compoundInitMilestone(version, opts) {
476
+ return _compoundInitMilestone(version, planningDir, opts);
477
+ }
413
478
 
414
- const phaseDir = entries.find(e => e.name.startsWith(phaseNum.padStart(2, '0') + '-'));
415
- if (!phaseDir) {
416
- return { error: `No phase directory found matching phase ${phaseNum}` };
417
- }
479
+ function initExecutePhase(phaseNum, overridePlanningDir, overrideModel) {
480
+ return _initExecutePhase(phaseNum, overridePlanningDir || planningDir, overrideModel);
481
+ }
418
482
 
419
- const fullDir = path.join(phasesDir, phaseDir.name);
420
- const planFiles = findFiles(fullDir, /-PLAN\.md$/);
421
-
422
- const plans = [];
423
- const waves = {};
424
-
425
- for (const file of planFiles) {
426
- const content = fs.readFileSync(path.join(fullDir, file), 'utf8');
427
- const frontmatter = parseYamlFrontmatter(content);
428
-
429
- const plan = {
430
- file,
431
- plan_id: frontmatter.plan || file.replace(/-PLAN\.md$/, ''),
432
- wave: parseInt(frontmatter.wave, 10) || 1,
433
- type: frontmatter.type || 'unknown',
434
- autonomous: frontmatter.autonomous !== false,
435
- depends_on: frontmatter.depends_on || [],
436
- gap_closure: frontmatter.gap_closure || false,
437
- has_summary: fs.existsSync(path.join(fullDir, `SUMMARY-${frontmatter.plan || ''}.md`)),
438
- must_haves_count: countMustHaves(frontmatter.must_haves)
439
- };
440
-
441
- plans.push(plan);
442
-
443
- const waveKey = `wave_${plan.wave}`;
444
- if (!waves[waveKey]) waves[waveKey] = [];
445
- waves[waveKey].push(plan.plan_id);
446
- }
483
+ function initPlanPhase(phaseNum, overridePlanningDir, overrideModel) {
484
+ return _initPlanPhase(phaseNum, overridePlanningDir || planningDir, overrideModel);
485
+ }
447
486
 
448
- return {
449
- phase: phaseDir.name,
450
- total_plans: plans.length,
451
- plans,
452
- waves
453
- };
487
+ function initQuick(description) {
488
+ return _initQuick(description, planningDir);
454
489
  }
455
490
 
456
- function configValidate(preloadedConfig) {
457
- let config;
458
- if (preloadedConfig) {
459
- config = preloadedConfig;
460
- } else {
461
- const configPath = path.join(planningDir, 'config.json');
462
- if (!fs.existsSync(configPath)) {
463
- return { valid: false, errors: ['config.json not found'], warnings: [] };
464
- }
491
+ function initVerifyWork(phaseNum, overridePlanningDir, overrideModel) {
492
+ return _initVerifyWork(phaseNum, overridePlanningDir || planningDir, overrideModel);
493
+ }
465
494
 
466
- try {
467
- config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
468
- } catch (e) {
469
- return { valid: false, errors: [`config.json is not valid JSON: ${e.message}`], warnings: [] };
470
- }
471
- }
495
+ function initResume() {
496
+ return _initResume(planningDir);
497
+ }
472
498
 
473
- const schema = JSON.parse(fs.readFileSync(path.join(__dirname, 'config-schema.json'), 'utf8'));
474
- const warnings = [];
475
- const errors = [];
499
+ function initProgress() {
500
+ return _initProgress(planningDir);
501
+ }
476
502
 
477
- validateObject(config, schema, '', errors, warnings);
503
+ function initContinue() {
504
+ return _initContinue(planningDir);
505
+ }
478
506
 
479
- // Semantic conflict detection — logical contradictions that pass schema validation
480
- // Clear contradictions → errors; ambiguous/preference issues → warnings
481
- if (config.mode === 'autonomous' && config.gates) {
482
- const activeGates = Object.entries(config.gates || {}).filter(([, v]) => v === true).map(([k]) => k);
483
- if (activeGates.length > 0) {
484
- errors.push(`mode=autonomous with active gates (${activeGates.join(', ')}): gates are unreachable in autonomous mode`);
485
- }
486
- }
487
- if (config.features && config.features.auto_continue && config.mode === 'interactive') {
488
- warnings.push('features.auto_continue=true with mode=interactive: auto_continue only fires in autonomous mode');
489
- }
490
- if (config.parallelization) {
491
- if (config.parallelization.enabled === false && config.parallelization.plan_level === true) {
492
- warnings.push('parallelization.enabled=false with plan_level=true: plan_level is ignored when parallelization is disabled');
493
- }
494
- if (config.parallelization.max_concurrent_agents === 1 && config.teams && config.teams.coordination) {
495
- errors.push('parallelization.max_concurrent_agents=1 with teams.coordination set: teams require concurrent agents to be useful');
496
- }
497
- }
507
+ function initMilestone() {
508
+ return _initMilestone(planningDir);
509
+ }
498
510
 
499
- return {
500
- valid: errors.length === 0,
501
- errors,
502
- warnings
503
- };
511
+ function initBegin() {
512
+ return _initBegin(planningDir);
504
513
  }
505
514
 
506
- // --- New read-only commands ---
515
+ function initStatus() {
516
+ return _initStatus(planningDir);
517
+ }
507
518
 
508
- /**
509
- * Parse a markdown file's YAML frontmatter and return as JSON.
510
- * Wraps parseYamlFrontmatter() + parseMustHaves().
511
- */
512
- function frontmatter(filePath) {
513
- const resolved = path.resolve(filePath);
514
- if (!fs.existsSync(resolved)) {
515
- return { error: `File not found: ${resolved}` };
516
- }
517
- const content = fs.readFileSync(resolved, 'utf8');
518
- return parseYamlFrontmatter(content);
519
+ function initMapCodebase() {
520
+ return _initMapCodebase(planningDir);
519
521
  }
520
522
 
521
- /**
522
- * Collect all must-haves from all PLAN.md files in a phase.
523
- * Returns per-plan grouping + flat deduplicated list + total count.
524
- */
525
- function mustHavesCollect(phaseNum) {
526
- const phasesDir = path.join(planningDir, 'phases');
527
- if (!fs.existsSync(phasesDir)) {
528
- return { error: 'No phases directory found' };
529
- }
523
+ function stateBundle(phaseNum) {
524
+ return _initStateBundle(phaseNum, planningDir);
525
+ }
530
526
 
531
- const entries = fs.readdirSync(phasesDir, { withFileTypes: true })
532
- .filter(e => e.isDirectory());
533
- const phaseDir = entries.find(e => e.name.startsWith(phaseNum.padStart(2, '0') + '-'));
534
- if (!phaseDir) {
535
- return { error: `No phase directory found matching phase ${phaseNum}` };
536
- }
527
+ function historyAppend(entry, dir) {
528
+ return _historyAppend(entry, dir || planningDir);
529
+ }
537
530
 
538
- const fullDir = path.join(phasesDir, phaseDir.name);
539
- const planFiles = findFiles(fullDir, /-PLAN\.md$/);
531
+ function historyLoad(dir) {
532
+ return _historyLoad(dir || planningDir);
533
+ }
540
534
 
541
- const perPlan = {};
542
- const allTruths = new Set();
543
- const allArtifacts = new Set();
544
- const allKeyLinks = new Set();
535
+ function todoList(opts) {
536
+ return _todoList(planningDir, opts);
537
+ }
545
538
 
546
- for (const file of planFiles) {
547
- const content = fs.readFileSync(path.join(fullDir, file), 'utf8');
548
- const fm = parseYamlFrontmatter(content);
549
- const planId = fm.plan || file.replace(/-PLAN\.md$/, '');
550
- const mh = fm.must_haves || { truths: [], artifacts: [], key_links: [] };
539
+ function todoGet(num) {
540
+ return _todoGet(planningDir, num);
541
+ }
551
542
 
552
- perPlan[planId] = mh;
553
- (mh.truths || []).forEach(t => allTruths.add(t));
554
- (mh.artifacts || []).forEach(a => allArtifacts.add(a));
555
- (mh.key_links || []).forEach(k => allKeyLinks.add(k));
556
- }
543
+ function todoAdd(title, opts) {
544
+ return _todoAdd(planningDir, title, opts);
545
+ }
557
546
 
558
- const all = {
559
- truths: [...allTruths],
560
- artifacts: [...allArtifacts],
561
- key_links: [...allKeyLinks]
562
- };
547
+ function todoDone(num) {
548
+ return _todoDone(planningDir, num);
549
+ }
563
550
 
564
- return {
565
- phase: phaseDir.name,
566
- plans: perPlan,
567
- all,
568
- total: all.truths.length + all.artifacts.length + all.key_links.length
569
- };
551
+ function autoCloseTodos(context) {
552
+ return _autoCloseTodos(planningDir, context);
570
553
  }
571
554
 
572
- /**
573
- * Comprehensive single-phase status combining roadmap, filesystem, and plan data.
574
- */
575
- function phaseInfo(phaseNum) {
576
- const phasesDir = path.join(planningDir, 'phases');
577
- if (!fs.existsSync(phasesDir)) {
578
- return { error: 'No phases directory found' };
579
- }
555
+ function autoArchiveNotes(context) {
556
+ return _autoArchiveNotes(planningDir, context);
557
+ }
580
558
 
581
- const entries = fs.readdirSync(phasesDir, { withFileTypes: true })
582
- .filter(e => e.isDirectory());
583
- const phaseDir = entries.find(e => e.name.startsWith(phaseNum.padStart(2, '0') + '-'));
584
- if (!phaseDir) {
585
- return { error: `No phase directory found matching phase ${phaseNum}` };
586
- }
559
+ function incidentsList(opts) {
560
+ return _incidentsList({ ...opts, planningDir });
561
+ }
562
+ function incidentsQuery(filter, opts) {
563
+ return _incidentsQuery(filter, { ...opts, planningDir });
564
+ }
565
+ function incidentsSummary(opts) {
566
+ return _incidentsSummary({ ...opts, planningDir });
567
+ }
587
568
 
588
- const fullDir = path.join(phasesDir, phaseDir.name);
569
+ function intelQuery(term) {
570
+ const { intelQuery: _intelQuery } = require('./lib/intel');
571
+ return _intelQuery(term, planningDir);
572
+ }
589
573
 
590
- // Get roadmap info
591
- let roadmapInfo = null;
592
- const roadmapPath = path.join(planningDir, 'ROADMAP.md');
593
- if (fs.existsSync(roadmapPath)) {
594
- const roadmapContent = fs.readFileSync(roadmapPath, 'utf8');
595
- const roadmap = parseRoadmapMd(roadmapContent);
596
- roadmapInfo = roadmap.phases.find(p => p.number === phaseNum.padStart(2, '0')) || null;
597
- }
574
+ function intelStatus() {
575
+ const { intelStatus: _intelStatus } = require('./lib/intel');
576
+ return _intelStatus(planningDir);
577
+ }
598
578
 
599
- // Get plan index
600
- const plans = planIndex(phaseNum);
579
+ function intelDiff() {
580
+ const { intelDiff: _intelDiff } = require('./lib/intel');
581
+ return _intelDiff(planningDir);
582
+ }
601
583
 
602
- // Check for verification
603
- const verificationPath = path.join(fullDir, 'VERIFICATION.md');
604
- let verification = null;
605
- if (fs.existsSync(verificationPath)) {
606
- const vContent = fs.readFileSync(verificationPath, 'utf8');
607
- verification = parseYamlFrontmatter(vContent);
608
- }
584
+ function requirementsMarkComplete(ids) {
585
+ const { updateRequirementStatus } = require('./lib/requirements');
586
+ return updateRequirementStatus(planningDir, ids, 'done');
587
+ }
609
588
 
610
- // Check summaries
611
- const summaryFiles = findFiles(fullDir, /^SUMMARY-.*\.md$/);
612
- const summaries = summaryFiles.map(f => {
613
- const content = fs.readFileSync(path.join(fullDir, f), 'utf8');
614
- const fm = parseYamlFrontmatter(content);
615
- return { file: f, plan: fm.plan || f.replace(/^SUMMARY-|\.md$/g, ''), status: fm.status || 'unknown' };
616
- });
589
+ function roadmapAppendPhase(phaseNum, name, goal, dependsOn) {
590
+ return _roadmapAppendPhase(planningDir, phaseNum, name, goal, dependsOn);
591
+ }
617
592
 
618
- // Determine filesystem status
619
- const planCount = plans.total_plans || 0;
620
- const completedCount = summaries.filter(s => s.status === 'complete').length;
621
- const hasVerification = fs.existsSync(verificationPath);
622
- const fsStatus = determinePhaseStatus(planCount, completedCount, summaryFiles.length, hasVerification, fullDir);
593
+ function dataStatus() {
594
+ return _dataStatus(planningDir);
595
+ }
623
596
 
624
- return {
625
- phase: phaseDir.name,
626
- name: roadmapInfo ? roadmapInfo.name : phaseDir.name.replace(/^\d+-/, ''),
627
- goal: roadmapInfo ? roadmapInfo.goal : null,
628
- roadmap_status: roadmapInfo ? roadmapInfo.status : null,
629
- filesystem_status: fsStatus,
630
- plans: plans.plans || [],
631
- plan_count: planCount,
632
- summaries,
633
- completed: completedCount,
634
- verification,
635
- has_context: fs.existsSync(path.join(fullDir, 'CONTEXT.md'))
636
- };
597
+ function dataPrune(options) {
598
+ return _dataPrune(planningDir, options);
637
599
  }
638
600
 
639
- // --- Mutation commands ---
601
+ function migrate(options) {
602
+ return _applyMigrations(planningDir, options);
603
+ }
640
604
 
641
- /**
642
- * Atomically update a field in STATE.md using lockedFileUpdate.
643
- * Supports both legacy and frontmatter (v2) formats.
644
- *
645
- * @param {string} field - One of: current_phase, status, plans_complete, last_activity
646
- * @param {string} value - New value (use 'now' for last_activity to auto-timestamp)
647
- */
648
- function stateUpdate(field, value) {
649
- const statePath = path.join(planningDir, 'STATE.md');
650
- if (!fs.existsSync(statePath)) {
651
- return { success: false, error: 'STATE.md not found' };
652
- }
653
-
654
- const validFields = ['current_phase', 'status', 'plans_complete', 'last_activity'];
655
- if (!validFields.includes(field)) {
656
- return { success: false, error: `Invalid field: ${field}. Valid fields: ${validFields.join(', ')}` };
657
- }
658
-
659
- // Auto-timestamp
660
- if (field === 'last_activity' && value === 'now') {
661
- value = new Date().toISOString().slice(0, 19).replace('T', ' ');
662
- }
663
-
664
- const result = lockedFileUpdate(statePath, (content) => {
665
- const fm = parseYamlFrontmatter(content);
666
- if (fm.version === 2 || fm.current_phase !== undefined) {
667
- return updateFrontmatterField(content, field, value);
668
- }
669
- return updateLegacyStateField(content, field, value);
670
- });
605
+ function spotCheck(phaseDir, planId) {
606
+ return _spotCheck(planningDir, phaseDir, planId);
607
+ }
671
608
 
672
- if (result.success) {
673
- return { success: true, field, value };
674
- }
675
- return { success: false, error: result.error };
609
+ function verifySpotCheck(type, dirPath) {
610
+ return _verifySpotCheck(type, dirPath);
676
611
  }
677
612
 
678
- /**
679
- * Append a record to HISTORY.md. Creates the file if it doesn't exist.
680
- * Each entry is a markdown section appended at the end.
681
- *
682
- * @param {object} entry - { type: 'milestone'|'phase', title: string, body: string }
683
- * @param {string} [dir] - Path to .planning directory (defaults to cwd/.planning)
684
- * @returns {{success: boolean, error?: string}}
685
- */
686
- function historyAppend(entry, dir) {
687
- const historyPath = path.join(dir || planningDir, 'HISTORY.md');
688
- const timestamp = new Date().toISOString().slice(0, 10);
613
+ function referenceGet(name, options) {
614
+ // Resolve plugin root try CLAUDE_PLUGIN_ROOT env, then walk up from __dirname
615
+ const pluginRoot = process.env.CLAUDE_PLUGIN_ROOT || path.resolve(__dirname, '..');
616
+ // Fix MSYS paths on Windows (same pattern as run-hook.js)
617
+ let root = pluginRoot;
618
+ const msysMatch = root.match(/^\/([a-zA-Z])\/(.*)/);
619
+ if (msysMatch) root = msysMatch[1] + ':' + path.sep + msysMatch[2];
620
+ return _referenceGet(name, options, root);
621
+ }
689
622
 
690
- let header = '';
691
- if (!fs.existsSync(historyPath)) {
692
- header = '# Project History\n\nCompleted milestones and phase records. This file is append-only.\n\n';
693
- }
623
+ function resolvePluginRoot() {
624
+ // Resolve plugin root — try CLAUDE_PLUGIN_ROOT env, then walk up from __dirname
625
+ const pluginRoot = process.env.CLAUDE_PLUGIN_ROOT || path.resolve(__dirname, '..');
626
+ // Fix MSYS paths on Windows (same pattern as run-hook.js)
627
+ let root = pluginRoot;
628
+ const msysMatch = root.match(/^\/([a-zA-Z])\/(.*)/);
629
+ if (msysMatch) root = msysMatch[1] + ':' + path.sep + msysMatch[2];
630
+ return root;
631
+ }
694
632
 
695
- const section = `${header}## ${entry.type === 'milestone' ? 'Milestone' : 'Phase'}: ${entry.title}\n_Completed: ${timestamp}_\n\n${entry.body.trim()}\n\n---\n\n`;
633
+ function skillSectionGet(skillName, sectionQuery) {
634
+ return _skillSection(skillName, sectionQuery, resolvePluginRoot());
635
+ }
696
636
 
697
- try {
698
- fs.appendFileSync(historyPath, section, 'utf8');
699
- return { success: true };
700
- } catch (e) {
701
- return { success: false, error: e.message };
637
+ function listSkillHeadings(skillName) {
638
+ const { listHeadings } = require('./lib/reference');
639
+ const root = resolvePluginRoot();
640
+ const skillPath = require('path').join(root, 'skills', skillName, 'SKILL.md');
641
+ if (!require('fs').existsSync(skillPath)) {
642
+ return { error: `Skill not found: ${skillName}`, available: _listAvailableSkills(root) };
702
643
  }
644
+ const content = require('fs').readFileSync(skillPath, 'utf8');
645
+ return { skill: skillName, headings: listHeadings(content) };
703
646
  }
704
647
 
705
- /**
706
- * Load HISTORY.md and parse it into structured records.
707
- * Returns null if HISTORY.md doesn't exist.
708
- *
709
- * @param {string} [dir] - Path to .planning directory
710
- * @returns {object|null} { records: [{type, title, date, body}], line_count }
711
- */
712
- function historyLoad(dir) {
713
- const historyPath = path.join(dir || planningDir, 'HISTORY.md');
714
- if (!fs.existsSync(historyPath)) return null;
715
-
716
- const content = fs.readFileSync(historyPath, 'utf8');
717
- const records = [];
718
- const sectionRegex = /^## (Milestone|Phase): (.+)\n_Completed: (\d{4}-\d{2}-\d{2})_\n\n([\s\S]*?)(?=\n---|\s*$)/gm;
719
-
720
- let match;
721
- while ((match = sectionRegex.exec(content)) !== null) {
722
- records.push({
723
- type: match[1].toLowerCase(),
724
- title: match[2].trim(),
725
- date: match[3],
726
- body: match[4].trim()
727
- });
728
- }
648
+ function contextTriage(options) {
649
+ return _contextTriage(options, planningDir);
650
+ }
729
651
 
730
- return {
731
- records,
732
- line_count: content.split('\n').length
733
- };
652
+ function stalenessCheck(phaseSlug) { return _stalenessCheck(phaseSlug, planningDir); }
653
+ function summaryGate(phaseSlug, planId) { return _summaryGate(phaseSlug, planId, planningDir); }
654
+ function checkpointInit(phaseSlug, plans) { return _checkpointInit(phaseSlug, plans, planningDir); }
655
+ function checkpointUpdate(phaseSlug, opts) { return _checkpointUpdate(phaseSlug, opts, planningDir); }
656
+ function seedsMatch(phaseSlug, phaseNum) { return _seedsMatch(phaseSlug, phaseNum, planningDir); }
657
+ function ciPoll(runId, timeoutSecs) { return _ciPoll(runId, timeoutSecs, planningDir); }
658
+ function ciFix(options) { return _runCiFixLoop({ ...options, cwd: path.resolve('.') }); }
659
+ function rollbackPlan(manifestPath) { return _rollback(manifestPath, planningDir); }
660
+
661
+ function helpListCmd() {
662
+ const root = resolvePluginRoot();
663
+ return _helpList(root);
664
+ }
665
+ function skillMetadataCmd(skillName) {
666
+ const root = resolvePluginRoot();
667
+ return _skillMetadata(skillName, root);
734
668
  }
735
669
 
670
+ function quickStatus() { return _quickStatus(planningDir); }
671
+
736
672
  /**
737
- * Update the Status column for a phase in ROADMAP.md's Phase Overview table.
673
+ * Build cleanup context from phase SUMMARY files and git log.
674
+ * @param {string} phaseNum - Phase number (e.g. "38")
675
+ * @returns {{ phaseName: string, phaseNum: string, keyFiles: string[], commitMessages: string[], summaryDescriptions: string[] }}
738
676
  */
739
- function roadmapUpdateStatus(phaseNum, newStatus) {
740
- const roadmapPath = path.join(planningDir, 'ROADMAP.md');
741
- if (!fs.existsSync(roadmapPath)) {
742
- return { success: false, error: 'ROADMAP.md not found' };
743
- }
677
+ function buildCleanupContext(phaseNum) {
678
+ const padded = String(phaseNum).padStart(2, '0');
679
+ const phasesDir = path.join(planningDir, 'phases');
680
+ if (!fs.existsSync(phasesDir)) throw new Error('No phases directory found');
744
681
 
745
- let oldStatus = null;
682
+ const phaseDir = fs.readdirSync(phasesDir).find(d => d.startsWith(padded + '-'));
683
+ if (!phaseDir) throw new Error(`Phase ${phaseNum} directory not found`);
746
684
 
747
- const result = lockedFileUpdate(roadmapPath, (content) => {
748
- const lines = content.split('\n');
749
- const rowIdx = findRoadmapRow(lines, phaseNum);
750
- if (rowIdx === -1) {
751
- return content; // No matching row found
752
- }
753
- const parts = lines[rowIdx].split('|');
754
- oldStatus = parts[6] ? parts[6].trim() : 'unknown';
755
- lines[rowIdx] = updateTableRow(lines[rowIdx], 5, newStatus);
756
- return lines.join('\n');
757
- });
758
-
759
- if (!oldStatus) {
760
- return { success: false, error: `Phase ${phaseNum} not found in ROADMAP.md table` };
761
- }
685
+ const phaseName = phaseDir.replace(/^\d+-/, '').replace(/-/g, ' ');
686
+ const phaseDirPath = path.join(phasesDir, phaseDir);
762
687
 
763
- // Advisory transition validation warn on suspicious transitions but don't block
764
- const transition = validateStatusTransition(oldStatus, newStatus);
765
- if (!transition.valid && transition.warning) {
766
- process.stderr.write(`[pbr-tools] WARNING: ${transition.warning}\n`);
688
+ // Collect key_files and descriptions from all SUMMARY files
689
+ const keyFiles = [];
690
+ const summaryDescriptions = [];
691
+ const summaryFiles = fs.readdirSync(phaseDirPath).filter(f => /^SUMMARY/i.test(f) && f.endsWith('.md'));
692
+ for (const sf of summaryFiles) {
693
+ try {
694
+ const content = fs.readFileSync(path.join(phaseDirPath, sf), 'utf8');
695
+ const fm = parseYamlFrontmatter(content);
696
+ if (fm.key_files && Array.isArray(fm.key_files)) {
697
+ keyFiles.push(...fm.key_files.map(kf => typeof kf === 'string' ? kf.split(':')[0].trim() : ''));
698
+ }
699
+ if (fm.provides && Array.isArray(fm.provides)) {
700
+ summaryDescriptions.push(...fm.provides);
701
+ }
702
+ } catch (_e) { /* skip unreadable summaries */ }
767
703
  }
768
704
 
769
- if (result.success) {
770
- const response = { success: true, old_status: oldStatus, new_status: newStatus };
771
- if (!transition.valid) {
772
- response.transition_warning = transition.warning;
773
- }
774
- return response;
775
- }
776
- return { success: false, error: result.error };
705
+ // Get recent commit messages
706
+ let commitMessages = [];
707
+ try {
708
+ const { execSync } = require('child_process');
709
+ const log = execSync('git log --oneline -20', { encoding: 'utf8', cwd: path.join(planningDir, '..') });
710
+ commitMessages = log.split('\n').filter(l => l.trim()).map(l => {
711
+ const parts = l.match(/^[0-9a-f]+\s+(.*)/);
712
+ return parts ? parts[1] : '';
713
+ }).filter(Boolean);
714
+ } catch (_e) { /* git not available */ }
715
+
716
+ return { phaseName, phaseNum: String(phaseNum), keyFiles, commitMessages, summaryDescriptions };
777
717
  }
778
718
 
719
+ // --- Phase commit query functions ---
720
+
779
721
  /**
780
- * Update the Plans column for a phase in ROADMAP.md's Phase Overview table.
722
+ * Read .phase-manifest.json for phase N, output JSON array of commits.
723
+ * Falls back to git log if no manifest exists.
781
724
  */
782
- function roadmapUpdatePlans(phaseNum, complete, total) {
783
- const roadmapPath = path.join(planningDir, 'ROADMAP.md');
784
- if (!fs.existsSync(roadmapPath)) {
785
- return { success: false, error: 'ROADMAP.md not found' };
786
- }
725
+ function _phaseCommitsFor(phaseNum) {
726
+ const padded = String(phaseNum).padStart(2, '0');
727
+ const phasesDir = path.join(planningDir, 'phases');
728
+ if (!fs.existsSync(phasesDir)) return { error: 'No phases directory found' };
787
729
 
788
- let oldPlans = null;
789
- const newPlans = `${complete}/${total}`;
730
+ const phaseDir = fs.readdirSync(phasesDir).find(d => d.startsWith(padded + '-'));
731
+ if (!phaseDir) return { error: `Phase ${phaseNum} directory not found` };
790
732
 
791
- const result = lockedFileUpdate(roadmapPath, (content) => {
792
- const lines = content.split('\n');
793
- const rowIdx = findRoadmapRow(lines, phaseNum);
794
- if (rowIdx === -1) {
795
- return content;
796
- }
797
- const parts = lines[rowIdx].split('|');
798
- oldPlans = parts[4] ? parts[4].trim() : 'unknown';
799
- lines[rowIdx] = updateTableRow(lines[rowIdx], 3, newPlans);
800
- return lines.join('\n');
801
- });
802
-
803
- if (!oldPlans) {
804
- return { success: false, error: `Phase ${phaseNum} not found in ROADMAP.md table` };
733
+ const manifestPath = path.join(phasesDir, phaseDir, '.phase-manifest.json');
734
+ if (fs.existsSync(manifestPath)) {
735
+ try {
736
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
737
+ return { source: 'manifest', commits: manifest.commits || [] };
738
+ } catch (_e) { /* fall through to git log */ }
805
739
  }
806
740
 
807
- if (result.success) {
808
- return { success: true, old_plans: oldPlans, new_plans: newPlans };
741
+ // Fallback: scan git log for commits matching this phase scope
742
+ try {
743
+ const { execSync } = require('child_process');
744
+ const log = execSync('git log --oneline --no-merges -100', { encoding: 'utf8' });
745
+ const phasePattern = new RegExp(`\\((${padded}-|phase.*${phaseNum})`, 'i');
746
+ const commits = log.split('\n')
747
+ .filter(l => l.trim() && phasePattern.test(l))
748
+ .map(l => {
749
+ const parts = l.match(/^([0-9a-f]+)\s+(.*)/);
750
+ return parts ? { hash: parts[1], message: parts[2] } : null;
751
+ })
752
+ .filter(Boolean);
753
+ return { source: 'git_log', commits };
754
+ } catch (_e) {
755
+ return { error: 'Could not read git log', commits: [] };
809
756
  }
810
- return { success: false, error: result.error };
811
757
  }
812
758
 
813
- // --- Mutation helpers ---
814
-
815
759
  /**
816
- * Update a field in legacy (non-frontmatter) STATE.md content.
817
- * Pure function: content in, content out.
760
+ * Output { first, last } commit hashes from phase manifest or git log.
818
761
  */
819
- function updateLegacyStateField(content, field, value) {
820
- const lines = content.split('\n');
821
-
822
- switch (field) {
823
- case 'current_phase': {
824
- const idx = lines.findIndex(l => /Phase:\s*\d+\s+of\s+\d+/.test(l));
825
- if (idx !== -1) {
826
- lines[idx] = lines[idx].replace(/(Phase:\s*)\d+/, (_, prefix) => `${prefix}${value}`);
827
- }
828
- break;
829
- }
830
- case 'status': {
831
- const idx = lines.findIndex(l => /^Status:/i.test(l));
832
- if (idx !== -1) {
833
- lines[idx] = `Status: ${value}`;
834
- } else {
835
- const phaseIdx = lines.findIndex(l => /Phase:/.test(l));
836
- if (phaseIdx !== -1) {
837
- lines.splice(phaseIdx + 1, 0, `Status: ${value}`);
838
- } else {
839
- lines.push(`Status: ${value}`);
840
- }
841
- }
842
- break;
843
- }
844
- case 'plans_complete': {
845
- const idx = lines.findIndex(l => /Plan:\s*\d+\s+of\s+\d+/.test(l));
846
- if (idx !== -1) {
847
- lines[idx] = lines[idx].replace(/(Plan:\s*)\d+/, (_, prefix) => `${prefix}${value}`);
848
- }
849
- break;
850
- }
851
- case 'last_activity': {
852
- const idx = lines.findIndex(l => /^Last Activity:/i.test(l));
853
- if (idx !== -1) {
854
- lines[idx] = `Last Activity: ${value}`;
855
- } else {
856
- const statusIdx = lines.findIndex(l => /^Status:/i.test(l));
857
- if (statusIdx !== -1) {
858
- lines.splice(statusIdx + 1, 0, `Last Activity: ${value}`);
859
- } else {
860
- lines.push(`Last Activity: ${value}`);
861
- }
862
- }
863
- break;
864
- }
865
- }
866
-
867
- return lines.join('\n');
762
+ function _phaseFirstLastCommit(phaseNum) {
763
+ const result = _phaseCommitsFor(phaseNum);
764
+ if (result.error && !result.commits) return result;
765
+ const commits = result.commits || [];
766
+ return {
767
+ source: result.source,
768
+ first: commits.length > 0 ? commits[0].hash : null,
769
+ last: commits.length > 0 ? commits[commits.length - 1].hash : null,
770
+ total: commits.length
771
+ };
868
772
  }
869
773
 
870
- /**
871
- * Update a field in YAML frontmatter content.
872
- * Pure function: content in, content out.
873
- */
874
- function updateFrontmatterField(content, field, value) {
875
- const match = content.match(/^(---\s*\n)([\s\S]*?)(\n---)/);
876
- if (!match) return content;
877
-
878
- const before = match[1];
879
- let yaml = match[2];
880
- const after = match[3];
881
- const rest = content.slice(match[0].length);
882
-
883
- // Format value: integers stay bare, strings get quotes
884
- const isNum = /^\d+$/.test(String(value));
885
- const formatted = isNum ? value : `"${value}"`;
886
-
887
- const fieldRegex = new RegExp(`^(${field})\\s*:.*$`, 'm');
888
- if (fieldRegex.test(yaml)) {
889
- yaml = yaml.replace(fieldRegex, () => `${field}: ${formatted}`);
890
- } else {
891
- yaml = yaml + `\n${field}: ${formatted}`;
892
- }
774
+ // --- Claim wrapper functions ---
893
775
 
894
- return before + yaml + after + rest;
776
+ function claimAcquire(phaseSlug, sessionId, skill) {
777
+ const phaseDir = path.join(planningDir, 'phases', phaseSlug);
778
+ if (!fs.existsSync(phaseDir)) return { error: `Phase directory not found: ${phaseSlug}` };
779
+ return acquireClaim(planningDir, phaseDir, sessionId, skill);
895
780
  }
896
781
 
897
- /**
898
- * Find the row index of a phase in a ROADMAP.md table.
899
- * @returns {number} Line index or -1 if not found
900
- */
901
- function findRoadmapRow(lines, phaseNum) {
902
- const paddedPhase = phaseNum.padStart(2, '0');
903
- for (let i = 0; i < lines.length; i++) {
904
- if (!lines[i].includes('|')) continue;
905
- const parts = lines[i].split('|');
906
- if (parts.length < 3) continue;
907
- const phaseCol = parts[1] ? parts[1].trim() : '';
908
- if (phaseCol === paddedPhase) {
909
- return i;
910
- }
911
- }
912
- return -1;
782
+ function claimRelease(phaseSlug, sessionId) {
783
+ const phaseDir = path.join(planningDir, 'phases', phaseSlug);
784
+ if (!fs.existsSync(phaseDir)) return { error: `Phase directory not found: ${phaseSlug}` };
785
+ return releaseClaim(planningDir, phaseDir, sessionId);
913
786
  }
914
787
 
915
- /**
916
- * Update a specific column in a markdown table row.
917
- * @param {string} row - The full table row string (e.g., "| 01 | Setup | ... |")
918
- * @param {number} columnIndex - 0-based column index (Phase=0, Name=1, ..., Status=5)
919
- * @param {string} newValue - New cell value
920
- * @returns {string} Updated row
921
- */
922
- function updateTableRow(row, columnIndex, newValue) {
923
- const parts = row.split('|');
924
- // parts[0] is empty (before first |), data starts at parts[1]
925
- const partIndex = columnIndex + 1;
926
- if (partIndex < parts.length) {
927
- parts[partIndex] = ` ${newValue} `;
928
- }
929
- return parts.join('|');
788
+ function claimList() {
789
+ return _listClaims(planningDir);
930
790
  }
931
791
 
792
+ // --- validateProject stays here (cross-cutting across modules) ---
793
+
932
794
  /**
933
- * Lightweight JSON Schema validator — supports type, enum, properties,
934
- * additionalProperties, minimum, maximum for the config schema.
795
+ * Comprehensive .planning/ integrity check.
796
+ * Returns { valid, errors, warnings, checks } errors mean workflow should not proceed.
935
797
  */
936
- function validateObject(value, schema, prefix, errors, warnings) {
937
- if (schema.type && typeof value !== schema.type) {
938
- if (!(schema.type === 'integer' && typeof value === 'number' && Number.isInteger(value))) {
939
- errors.push(`${prefix || 'root'}: expected ${schema.type}, got ${typeof value}`);
940
- return;
941
- }
942
- }
798
+ function validateProject() {
799
+ const checks = [];
800
+ const errors = [];
801
+ const warnings = [];
943
802
 
944
- if (schema.enum && !schema.enum.includes(value)) {
945
- errors.push(`${prefix || 'root'}: value "${value}" not in allowed values [${schema.enum.join(', ')}]`);
946
- return;
803
+ // 1. .planning/ directory exists
804
+ if (!fs.existsSync(planningDir)) {
805
+ return { valid: false, errors: ['.planning/ directory not found'], warnings: [], checks: ['directory_exists: FAIL'] };
947
806
  }
807
+ checks.push('directory_exists: PASS');
948
808
 
949
- if (schema.minimum !== undefined && value < schema.minimum) {
950
- errors.push(`${prefix || 'root'}: value ${value} is below minimum ${schema.minimum}`);
951
- }
952
- if (schema.maximum !== undefined && value > schema.maximum) {
953
- errors.push(`${prefix || 'root'}: value ${value} is above maximum ${schema.maximum}`);
809
+ // 2. config.json exists and is valid
810
+ const config = configLoad();
811
+ if (!config) {
812
+ errors.push('config.json missing or invalid JSON');
813
+ checks.push('config_valid: FAIL');
814
+ } else {
815
+ const configResult = configValidate(config);
816
+ if (!configResult.valid) {
817
+ errors.push(...configResult.errors.map(e => 'config: ' + e));
818
+ }
819
+ warnings.push(...(configResult.warnings || []).map(w => 'config: ' + w));
820
+ checks.push('config_valid: ' + (configResult.valid ? 'PASS' : 'FAIL'));
954
821
  }
955
822
 
956
- if (schema.type === 'object' && schema.properties) {
957
- const knownKeys = new Set(Object.keys(schema.properties));
958
-
959
- for (const key of Object.keys(value)) {
960
- const fullKey = prefix ? `${prefix}.${key}` : key;
961
- if (!knownKeys.has(key)) {
962
- if (schema.additionalProperties === false) {
963
- warnings.push(`${fullKey}: unrecognized key (possible typo?)`);
964
- }
965
- continue;
823
+ // 3. STATE.md exists and has valid frontmatter
824
+ const statePath = path.join(planningDir, 'STATE.md');
825
+ if (!fs.existsSync(statePath)) {
826
+ errors.push('STATE.md not found');
827
+ checks.push('state_exists: FAIL');
828
+ } else {
829
+ try {
830
+ const stateContent = fs.readFileSync(statePath, 'utf8');
831
+ const fm = parseYamlFrontmatter(stateContent);
832
+ if (!fm || !fm.current_phase) {
833
+ warnings.push('STATE.md frontmatter missing current_phase');
834
+ checks.push('state_frontmatter: WARN');
835
+ } else {
836
+ checks.push('state_frontmatter: PASS');
966
837
  }
967
- validateObject(value[key], schema.properties[key], fullKey, errors, warnings);
838
+ } catch (e) {
839
+ errors.push('STATE.md unreadable: ' + e.message);
840
+ checks.push('state_readable: FAIL');
968
841
  }
969
842
  }
970
- }
971
-
972
- /**
973
- * Locked file update: read-modify-write with exclusive lockfile.
974
- * Prevents concurrent writes to STATE.md and ROADMAP.md.
975
- *
976
- * @param {string} filePath - Absolute path to the file to update
977
- * @param {function} updateFn - Receives current content, returns new content
978
- * @param {object} opts - Options: { retries: 3, retryDelayMs: 100, timeoutMs: 5000 }
979
- * @returns {object} { success, content?, error? }
980
- */
981
- function lockedFileUpdate(filePath, updateFn, opts = {}) {
982
- const retries = opts.retries || 3;
983
- const retryDelayMs = opts.retryDelayMs || 100;
984
- const timeoutMs = opts.timeoutMs || 5000;
985
- const lockPath = filePath + '.lock';
986
843
 
987
- let lockFd = null;
988
- let lockAcquired = false;
844
+ // 4. ROADMAP.md exists
845
+ const roadmapPath = path.join(planningDir, 'ROADMAP.md');
846
+ if (!fs.existsSync(roadmapPath)) {
847
+ warnings.push('ROADMAP.md not found (may be a new project)');
848
+ checks.push('roadmap_exists: WARN');
849
+ } else {
850
+ checks.push('roadmap_exists: PASS');
851
+ }
989
852
 
853
+ // 5. Phase directory matches STATE.md current_phase
990
854
  try {
991
- // Acquire lock with retries
992
- for (let attempt = 0; attempt < retries; attempt++) {
993
- try {
994
- lockFd = fs.openSync(lockPath, 'wx');
995
- lockAcquired = true;
996
- break;
997
- } catch (e) {
998
- if (e.code === 'EEXIST') {
999
- // Lock exists — check if stale (older than timeoutMs)
1000
- try {
1001
- const stats = fs.statSync(lockPath);
1002
- if (Date.now() - stats.mtimeMs > timeoutMs) {
1003
- // Stale lock — remove and retry
1004
- fs.unlinkSync(lockPath);
1005
- continue;
1006
- }
1007
- } catch (_statErr) {
1008
- // Lock disappeared between check — retry
1009
- continue;
1010
- }
1011
-
1012
- if (attempt < retries - 1) {
1013
- // Wait and retry
1014
- const waitMs = retryDelayMs * (attempt + 1);
1015
- const start = Date.now();
1016
- while (Date.now() - start < waitMs) {
1017
- // Busy wait (synchronous context)
1018
- }
1019
- continue;
855
+ if (fs.existsSync(statePath)) {
856
+ const stateContent = fs.readFileSync(statePath, 'utf8');
857
+ const fm = parseYamlFrontmatter(stateContent);
858
+ if (fm && fm.current_phase) {
859
+ const phaseNum = String(fm.current_phase).padStart(2, '0');
860
+ const phasesDir = path.join(planningDir, 'phases');
861
+ if (fs.existsSync(phasesDir)) {
862
+ const dirs = fs.readdirSync(phasesDir).filter(d => d.startsWith(phaseNum + '-'));
863
+ if (dirs.length === 0) {
864
+ warnings.push(`Phase directory for current_phase ${fm.current_phase} not found in .planning/phases/`);
865
+ checks.push('phase_directory: WARN');
866
+ } else {
867
+ checks.push('phase_directory: PASS');
1020
868
  }
1021
- return { success: false, error: `Could not acquire lock for ${path.basename(filePath)} after ${retries} attempts` };
1022
869
  }
1023
- throw e;
1024
870
  }
1025
871
  }
872
+ } catch (_e) { /* best effort */ }
1026
873
 
1027
- if (!lockAcquired) {
1028
- return { success: false, error: `Could not acquire lock for ${path.basename(filePath)}` };
1029
- }
1030
-
1031
- // Write PID to lock file for debugging
1032
- fs.writeSync(lockFd, `${process.pid}`);
1033
- fs.closeSync(lockFd);
1034
- lockFd = null;
874
+ // 6. No stale .active-skill (>2 hours old)
875
+ const activeSkillPath = path.join(planningDir, '.active-skill');
876
+ if (fs.existsSync(activeSkillPath)) {
877
+ try {
878
+ const stat = fs.statSync(activeSkillPath);
879
+ const ageMs = Date.now() - stat.mtimeMs;
880
+ if (ageMs > 2 * 60 * 60 * 1000) {
881
+ const ageHours = Math.round(ageMs / (60 * 60 * 1000));
882
+ warnings.push(`.active-skill is ${ageHours}h old — may be stale from a crashed session`);
883
+ checks.push('active_skill_fresh: WARN');
884
+ } else {
885
+ checks.push('active_skill_fresh: PASS');
886
+ }
887
+ } catch (_e) { checks.push('active_skill_fresh: SKIP'); }
888
+ } else {
889
+ checks.push('active_skill_fresh: SKIP');
890
+ }
1035
891
 
1036
- // Read current content
1037
- let content = '';
1038
- if (fs.existsSync(filePath)) {
1039
- content = fs.readFileSync(filePath, 'utf8');
892
+ // 7. No .tmp files left from atomic writes
893
+ try {
894
+ const tmpFiles = fs.readdirSync(planningDir).filter(f => f.endsWith('.tmp.' + process.pid) || f.match(/\.tmp\.\d+$/));
895
+ if (tmpFiles.length > 0) {
896
+ warnings.push(`Found ${tmpFiles.length} leftover temp files in .planning/: ${tmpFiles.join(', ')}`);
897
+ checks.push('no_temp_files: WARN');
898
+ } else {
899
+ checks.push('no_temp_files: PASS');
1040
900
  }
901
+ } catch (_e) { /* best effort */ }
1041
902
 
1042
- // Apply update
1043
- const newContent = updateFn(content);
903
+ // 8. Session directory scan — count active, flag stale
904
+ const sessionsResult = { count: 0, active: [], stale: [] };
905
+ const sessionsDir = path.join(planningDir, '.sessions');
906
+ try {
907
+ if (fs.existsSync(sessionsDir)) {
908
+ const entries = fs.readdirSync(sessionsDir, { withFileTypes: true });
909
+ for (const entry of entries) {
910
+ if (!entry.isDirectory()) continue;
911
+ sessionsResult.count++;
912
+ sessionsResult.active.push(entry.name);
913
+
914
+ // Check for staleness via meta.json
915
+ const metaPath = path.join(sessionsDir, entry.name, 'meta.json');
916
+ try {
917
+ const meta = JSON.parse(fs.readFileSync(metaPath, 'utf8'));
918
+ const ageMs = Date.now() - new Date(meta.created).getTime();
919
+ if (ageMs > STALE_SESSION_MS) {
920
+ sessionsResult.stale.push(entry.name);
921
+ }
922
+ } catch (_metaErr) {
923
+ // Fall back to directory mtime
924
+ try {
925
+ const stats = fs.statSync(path.join(sessionsDir, entry.name));
926
+ const ageMs = Date.now() - stats.mtimeMs;
927
+ if (ageMs > STALE_SESSION_MS) {
928
+ sessionsResult.stale.push(entry.name);
929
+ }
930
+ } catch (_statErr) { /* skip */ }
931
+ }
932
+ }
1044
933
 
1045
- // Write back atomically
1046
- const writeResult = atomicWrite(filePath, newContent);
1047
- if (!writeResult.success) {
1048
- return { success: false, error: writeResult.error };
1049
- }
934
+ if (sessionsResult.stale.length > 0) {
935
+ warnings.push(`${sessionsResult.stale.length} stale session(s) found: ${sessionsResult.stale.join(', ')}. Run cleanStaleSessions to remove.`);
936
+ checks.push('sessions_stale: WARN');
937
+ } else {
938
+ checks.push('sessions_stale: PASS');
939
+ }
1050
940
 
1051
- return { success: true, content: newContent };
1052
- } catch (e) {
1053
- return { success: false, error: e.message };
1054
- } finally {
1055
- // Close fd if still open
1056
- try {
1057
- if (lockFd !== null) fs.closeSync(lockFd);
1058
- } catch (_e) { /* ignore */ }
1059
- // Only release lock if we acquired it
1060
- if (lockAcquired) {
1061
- try {
1062
- fs.unlinkSync(lockPath);
1063
- } catch (_e) { /* ignore — may already be cleaned up */ }
941
+ // Check for singleton .active-skill coexisting with session-scoped ones
942
+ if (fs.existsSync(path.join(planningDir, '.active-skill'))) {
943
+ let hasSessionSkill = false;
944
+ for (const sid of sessionsResult.active) {
945
+ if (fs.existsSync(path.join(sessionsDir, sid, '.active-skill'))) {
946
+ hasSessionSkill = true;
947
+ break;
948
+ }
949
+ }
950
+ if (hasSessionSkill) {
951
+ warnings.push('.active-skill exists at both singleton and session-scoped paths — possible migration artifact. Consider removing the singleton .planning/.active-skill.');
952
+ checks.push('active_skill_dual: WARN');
953
+ }
954
+ }
1064
955
  }
1065
- }
1066
- }
1067
-
1068
- // --- Parsers ---
956
+ } catch (_e) { /* best effort */ }
1069
957
 
1070
- function parseStateMd(content) {
1071
- const result = {
1072
- current_phase: null,
1073
- phase_name: null,
1074
- progress: null,
1075
- status: null,
1076
- line_count: content.split('\n').length,
1077
- format: 'legacy' // 'legacy' or 'frontmatter'
958
+ return {
959
+ valid: errors.length === 0,
960
+ errors,
961
+ warnings,
962
+ checks,
963
+ sessions: sessionsResult
1078
964
  };
965
+ }
1079
966
 
1080
- // Check for YAML frontmatter (version 2 format)
1081
- const frontmatter = parseYamlFrontmatter(content);
1082
- if (frontmatter.version === 2 || frontmatter.current_phase !== undefined) {
1083
- result.format = 'frontmatter';
1084
- result.current_phase = frontmatter.current_phase || null;
1085
- result.total_phases = frontmatter.total_phases || null;
1086
- result.phase_name = frontmatter.phase_slug || frontmatter.phase_name || null;
1087
- result.status = frontmatter.status || null;
1088
- result.progress = frontmatter.progress_percent !== undefined ? frontmatter.progress_percent : null;
1089
- result.plans_total = frontmatter.plans_total || null;
1090
- result.plans_complete = frontmatter.plans_complete || null;
1091
- result.last_activity = frontmatter.last_activity || null;
1092
- result.last_command = frontmatter.last_command || null;
1093
- result.blockers = frontmatter.blockers || [];
1094
- return result;
1095
- }
1096
-
1097
- // Legacy regex-based parsing (version 1 format, no frontmatter)
1098
- // DEPRECATED (2026-02): v1 STATE.md format (no YAML frontmatter) is deprecated.
1099
- // New projects should use v2 (frontmatter) format, generated by /pbr:setup.
1100
- // v1 support will be removed in a future major version.
1101
- process.stderr.write('[pbr] WARNING: STATE.md uses legacy v1 format. Run /pbr:setup to migrate to v2 format.\n');
1102
- // Extract "Phase: N of M"
1103
- const phaseMatch = content.match(/Phase:\s*(\d+)\s+of\s+(\d+)/);
1104
- if (phaseMatch) {
1105
- result.current_phase = parseInt(phaseMatch[1], 10);
1106
- result.total_phases = parseInt(phaseMatch[2], 10);
1107
- }
967
+ // --- Spec subcommand handler ---
1108
968
 
1109
- // Extract phase name (line after "Phase:")
1110
- const nameMatch = content.match(/--\s+(.+?)(?:\n|$)/);
1111
- if (nameMatch) {
1112
- result.phase_name = nameMatch[1].trim();
1113
- }
969
+ /**
970
+ * Handle spec subcommands: parse, diff, reverse, impact.
971
+ * @param {string[]} args - raw CLI args (args[0] is 'spec', args[1] is subcommand)
972
+ * @param {string} pDir - planningDir
973
+ * @param {string} projectRoot - cwd
974
+ * @param {Function} outputFn - output function
975
+ * @param {Function} errorFn - error function
976
+ */
977
+ function handleSpec(args, pDir, projectRoot, outputFn, errorFn) {
978
+ const subcommand = args[1];
1114
979
 
1115
- // Extract progress percentage
1116
- const progressMatch = content.match(/(\d+)%/);
1117
- if (progressMatch) {
1118
- result.progress = parseInt(progressMatch[1], 10);
1119
- }
980
+ // Parse common flags
981
+ const formatIdx = args.indexOf('--format');
982
+ const format = formatIdx !== -1 ? args[formatIdx + 1] : 'json';
983
+ const projectRootIdx = args.indexOf('--project-root');
984
+ const effectiveRoot = projectRootIdx !== -1 ? args[projectRootIdx + 1] : projectRoot;
1120
985
 
1121
- // Extract plan status
1122
- const statusMatch = content.match(/Status:\s*(.+?)(?:\n|$)/i);
1123
- if (statusMatch) {
1124
- result.status = statusMatch[1].trim();
986
+ // Load config for feature toggle checks
987
+ let config = {};
988
+ try {
989
+ const configPath = path.join(pDir, 'config.json');
990
+ if (fs.existsSync(configPath)) {
991
+ config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
992
+ }
993
+ } catch (_e) {
994
+ // Config unreadable — use defaults
1125
995
  }
996
+ const features = config.features || {};
1126
997
 
1127
- return result;
1128
- }
1129
-
1130
- function parseRoadmapMd(content) {
1131
- const result = { phases: [], has_progress_table: false };
1132
-
1133
- // Find Phase Overview table
1134
- const overviewMatch = content.match(/## Phase Overview[\s\S]*?\|[\s\S]*?(?=\n##|\s*$)/);
1135
- if (overviewMatch) {
1136
- const rows = overviewMatch[0].split('\n').filter(r => r.includes('|'));
1137
- // Skip header and separator rows
1138
- for (let i = 2; i < rows.length; i++) {
1139
- const cols = rows[i].split('|').map(c => c.trim()).filter(Boolean);
1140
- if (cols.length >= 3) {
1141
- result.phases.push({
1142
- number: cols[0],
1143
- name: cols[1],
1144
- goal: cols[2],
1145
- plans: cols[3] || '',
1146
- wave: cols[4] || '',
1147
- status: cols[5] || 'pending'
998
+ // Audit log helper
999
+ function writeAuditLog(cmd, featureName, status, fileCount) {
1000
+ try {
1001
+ const logDir = path.join(pDir, 'logs');
1002
+ if (fs.existsSync(logDir)) {
1003
+ const logFile = path.join(logDir, 'spec-engine.jsonl');
1004
+ const entry = JSON.stringify({
1005
+ ts: new Date().toISOString(),
1006
+ cmd: `spec.${cmd}`,
1007
+ feature: featureName,
1008
+ status,
1009
+ files: fileCount || 0,
1148
1010
  });
1011
+ fs.appendFileSync(logFile, entry + '\n', 'utf-8');
1149
1012
  }
1013
+ } catch (_e2) {
1014
+ // No-op if log dir missing or unwritable
1150
1015
  }
1151
1016
  }
1152
1017
 
1153
- // Check for Progress table
1154
- result.has_progress_table = /## Progress/.test(content);
1155
-
1156
- return result;
1157
- }
1158
-
1159
- function parseYamlFrontmatter(content) {
1160
- const match = content.match(/^---\s*\n([\s\S]*?)\n---/);
1161
- if (!match) return {};
1162
-
1163
- const yaml = match[1];
1164
- const result = {};
1165
-
1166
- // Simple YAML parser for flat and basic nested values
1167
- const lines = yaml.split('\n');
1168
- let currentKey = null;
1018
+ if (!subcommand || subcommand === '--help' || subcommand === 'help') {
1019
+ const usageText = [
1020
+ 'Usage: spec <subcommand> [args] [--format json|markdown]',
1021
+ '',
1022
+ 'Subcommands:',
1023
+ ' parse <plan-file> Parse PLAN.md into structured JSON',
1024
+ ' diff <file-a> <file-b> Semantic diff between two PLAN.md versions',
1025
+ ' reverse <file...> Generate spec from source files',
1026
+ ' impact <file...> Predict impact of changed files',
1027
+ '',
1028
+ 'Flags:',
1029
+ ' --format json|markdown Output format (default: json)',
1030
+ ' --project-root <path> Project root for impact analysis',
1031
+ ].join('\n');
1032
+ outputFn(null, true, usageText);
1033
+ return;
1034
+ }
1169
1035
 
1170
- for (const line of lines) {
1171
- // Array item
1172
- if (/^\s+-\s+/.test(line) && currentKey) {
1173
- const val = line.replace(/^\s+-\s+/, '').trim().replace(/^["']|["']$/g, '');
1174
- if (!result[currentKey]) result[currentKey] = [];
1175
- if (Array.isArray(result[currentKey])) {
1176
- result[currentKey].push(val);
1177
- }
1178
- continue;
1036
+ if (subcommand === 'parse') {
1037
+ const planFile = args[2];
1038
+ if (!planFile) { errorFn('Usage: spec parse <plan-file>'); return; }
1039
+ const { parsePlanToSpec } = require('./lib/spec-engine');
1040
+ let content;
1041
+ try {
1042
+ content = fs.readFileSync(planFile, 'utf-8');
1043
+ } catch (e) {
1044
+ errorFn(`Cannot read file: ${planFile}: ${e.message}`);
1045
+ return;
1179
1046
  }
1047
+ const spec = parsePlanToSpec(content);
1048
+ writeAuditLog('parse', 'machine_executable_plans', 'ok', 1);
1049
+ outputFn({ frontmatter: spec.frontmatter, tasks: spec.tasks });
1050
+ return;
1051
+ }
1180
1052
 
1181
- // Key-value pair
1182
- const kvMatch = line.match(/^(\w[\w_]*)\s*:\s*(.*)/);
1183
- if (kvMatch) {
1184
- currentKey = kvMatch[1];
1185
- let val = kvMatch[2].trim();
1186
-
1187
- if (val === '' || val === '|') {
1188
- // Possible array or block follows
1189
- continue;
1190
- }
1191
-
1192
- // Handle arrays on same line: [a, b, c]
1193
- if (val.startsWith('[') && val.endsWith(']')) {
1194
- result[currentKey] = val.slice(1, -1).split(',')
1195
- .map(v => v.trim().replace(/^["']|["']$/g, ''))
1196
- .filter(Boolean);
1197
- continue;
1198
- }
1199
-
1200
- // Clean quotes
1201
- val = val.replace(/^["']|["']$/g, '');
1202
-
1203
- // Type coercion
1204
- if (val === 'true') val = true;
1205
- else if (val === 'false') val = false;
1206
- else if (/^\d+$/.test(val)) val = parseInt(val, 10);
1207
-
1208
- result[currentKey] = val;
1053
+ if (subcommand === 'diff') {
1054
+ if (features.spec_diffing === false) {
1055
+ outputFn({ error: 'Feature disabled. Enable features.spec_diffing in config.json' });
1056
+ return;
1057
+ }
1058
+ const fileA = args[2];
1059
+ const fileB = args[3];
1060
+ if (!fileA || !fileB) { errorFn('Usage: spec diff <file-a> <file-b>'); return; }
1061
+ const { diffPlanFiles, formatDiff } = require('./lib/spec-diff');
1062
+ let contentA, contentB;
1063
+ try {
1064
+ contentA = fs.readFileSync(fileA, 'utf-8');
1065
+ contentB = fs.readFileSync(fileB, 'utf-8');
1066
+ } catch (e) {
1067
+ errorFn(`Cannot read file: ${e.message}`);
1068
+ return;
1069
+ }
1070
+ const diff = diffPlanFiles(contentA, contentB);
1071
+ writeAuditLog('diff', 'spec_diffing', 'ok', 2);
1072
+ if (format === 'markdown') {
1073
+ outputFn(null, true, formatDiff(diff, 'markdown'));
1074
+ } else {
1075
+ outputFn(diff);
1209
1076
  }
1077
+ return;
1210
1078
  }
1211
1079
 
1212
- // Handle must_haves as a nested object
1213
- if (yaml.includes('must_haves:')) {
1214
- result.must_haves = parseMustHaves(yaml);
1080
+ if (subcommand === 'reverse') {
1081
+ if (features.reverse_spec === false) {
1082
+ outputFn({ error: 'Feature disabled. Enable features.reverse_spec in config.json' });
1083
+ return;
1084
+ }
1085
+ const files = [];
1086
+ for (let i = 2; i < args.length; i++) {
1087
+ if (!args[i].startsWith('--')) files.push(args[i]);
1088
+ }
1089
+ if (files.length === 0) { errorFn('Usage: spec reverse <file...>'); return; }
1090
+ const { generateReverseSpec } = require('./lib/reverse-spec');
1091
+ const { serializeSpec } = require('./lib/spec-engine');
1092
+ const spec = generateReverseSpec(files, { readFile: (p) => fs.readFileSync(p, 'utf-8') });
1093
+ writeAuditLog('reverse', 'reverse_spec', 'ok', files.length);
1094
+ if (format === 'markdown') {
1095
+ outputFn(null, true, serializeSpec(spec));
1096
+ } else {
1097
+ outputFn(spec);
1098
+ }
1099
+ return;
1215
1100
  }
1216
1101
 
1217
- return result;
1218
- }
1219
-
1220
- function parseMustHaves(yaml) {
1221
- const result = { truths: [], artifacts: [], key_links: [] };
1222
- let section = null;
1223
-
1224
- const inMustHaves = yaml.split('\n');
1225
- let collecting = false;
1226
-
1227
- for (const line of inMustHaves) {
1228
- if (/^\s*must_haves:/.test(line)) {
1229
- collecting = true;
1230
- continue;
1102
+ if (subcommand === 'impact') {
1103
+ if (features.predictive_impact === false) {
1104
+ outputFn({ error: 'Feature disabled. Enable features.predictive_impact in config.json' });
1105
+ return;
1231
1106
  }
1232
- if (collecting) {
1233
- if (/^\s{2}truths:/.test(line)) { section = 'truths'; continue; }
1234
- if (/^\s{2}artifacts:/.test(line)) { section = 'artifacts'; continue; }
1235
- if (/^\s{2}key_links:/.test(line)) { section = 'key_links'; continue; }
1236
- if (/^\w/.test(line)) break; // New top-level key, stop
1237
-
1238
- if (section && /^\s+-\s+/.test(line)) {
1239
- result[section].push(line.replace(/^\s+-\s+/, '').trim().replace(/^["']|["']$/g, ''));
1240
- }
1107
+ const files = [];
1108
+ for (let i = 2; i < args.length; i++) {
1109
+ if (!args[i].startsWith('--') && args[i - 1] !== '--project-root') files.push(args[i]);
1241
1110
  }
1111
+ if (files.length === 0) { errorFn('Usage: spec impact <file...>'); return; }
1112
+ const { analyzeImpact } = require('./lib/impact-analysis');
1113
+ const report = analyzeImpact(files, effectiveRoot);
1114
+ writeAuditLog('impact', 'predictive_impact', 'ok', files.length);
1115
+ outputFn(report);
1116
+ return;
1242
1117
  }
1243
1118
 
1244
- return result;
1119
+ errorFn(`Unknown spec subcommand: ${subcommand}\nAvailable: parse, diff, reverse, impact`);
1245
1120
  }
1246
1121
 
1247
- // --- Helpers ---
1122
+ // --- CLI entry point ---
1248
1123
 
1249
- function findFiles(dir, pattern) {
1250
- try {
1251
- return fs.readdirSync(dir).filter(f => pattern.test(f)).sort();
1252
- } catch (_) {
1253
- return [];
1254
- }
1255
- }
1124
+ async function main() {
1125
+ const args = process.argv.slice(2);
1126
+ const command = args[0];
1127
+ const subcommand = args[1];
1256
1128
 
1257
- function determinePhaseStatus(planCount, completedCount, summaryCount, hasVerification, phaseDir) {
1258
- if (planCount === 0) {
1259
- // Check for CONTEXT.md (discussed only)
1260
- if (fs.existsSync(path.join(phaseDir, 'CONTEXT.md'))) return 'discussed';
1261
- return 'not_started';
1262
- }
1263
- if (completedCount === 0 && summaryCount === 0) return 'planned';
1264
- if (completedCount < planCount) return 'building';
1265
- if (!hasVerification) return 'built';
1266
- // Check verification status
1267
1129
  try {
1268
- const vContent = fs.readFileSync(path.join(phaseDir, 'VERIFICATION.md'), 'utf8');
1269
- if (/status:\s*["']?passed/i.test(vContent)) return 'verified';
1270
- if (/status:\s*["']?gaps_found/i.test(vContent)) return 'needs_fixes';
1271
- return 'reviewed';
1272
- } catch (_) {
1273
- return 'built';
1274
- }
1275
- }
1276
-
1277
- function countMustHaves(mustHaves) {
1278
- if (!mustHaves) return 0;
1279
- return (mustHaves.truths || []).length +
1280
- (mustHaves.artifacts || []).length +
1281
- (mustHaves.key_links || []).length;
1282
- }
1283
-
1284
- function calculateProgress() {
1285
- const phasesDir = path.join(planningDir, 'phases');
1286
- if (!fs.existsSync(phasesDir)) {
1287
- return { total: 0, completed: 0, percentage: 0 };
1288
- }
1130
+ if (command === 'state' && subcommand === 'load') {
1131
+ output(stateLoad());
1132
+ } else if (command === 'state' && subcommand === 'check-progress') {
1133
+ output(stateCheckProgress());
1134
+ } else if (command === 'state' && subcommand === 'update') {
1135
+ const field = args[2];
1136
+ const value = args[3];
1137
+ if (!field || value === undefined) {
1138
+ error('Usage: pbr-tools.js state update <field> <value>\nFields: current_phase, status, plans_complete, last_activity, progress_percent, phase_slug, last_command, blockers');
1139
+ }
1140
+ output(stateUpdate(field, value));
1141
+ } else if (command === 'config' && subcommand === 'validate') {
1142
+ output(configValidate());
1143
+ } else if (command === 'validate' && subcommand === 'health') {
1144
+ const { getAllPhase10Checks } = require('./lib/health-checks');
1145
+ const checks = getAllPhase10Checks(planningDir);
1146
+ output({ phase10: checks, timestamp: new Date().toISOString() });
1147
+ } else if (command === 'config' && subcommand === 'load-defaults') {
1148
+ const defaults = loadUserDefaults();
1149
+ output(defaults || { exists: false, path: USER_DEFAULTS_PATH });
1150
+ } else if (command === 'config' && subcommand === 'save-defaults') {
1151
+ const config = configLoad();
1152
+ if (!config) error('No config.json found. Run /pbr:setup first.');
1153
+ output(saveUserDefaults(config));
1154
+ } else if (command === 'config' && subcommand === 'format') {
1155
+ const config = configLoad();
1156
+ if (!config) error('No config.json found.');
1157
+ _configWrite(planningDir, config);
1158
+ output({ formatted: true, path: path.join(planningDir, 'config.json') });
1159
+ } else if (command === 'config' && subcommand === 'resolve-depth') {
1160
+ const dir = args[2] || undefined;
1161
+ const config = configLoad(dir);
1162
+ output(resolveDepthProfile(config));
1163
+ } else if (command === 'config' && subcommand === 'get') {
1164
+ const key = args[2];
1165
+ if (!key) { error('Usage: config get <dot.path.key>'); }
1166
+ const cfg = configLoad();
1167
+ if (!cfg) { error('No config.json found'); }
1168
+ const parts = key.split('.');
1169
+ let val = cfg;
1170
+ for (const p of parts) {
1171
+ if (val == null || typeof val !== 'object') { val = undefined; break; }
1172
+ val = val[p];
1173
+ }
1174
+ if (val === undefined) { error(`Config key not found: ${key}`); }
1175
+ output(typeof val === 'object' ? val : { value: val });
1176
+
1177
+ } else if (command === 'intel') {
1178
+ const subCmd = args[1];
1179
+ if (subCmd === 'query') {
1180
+ const term = args[2];
1181
+ if (!term) { error('Usage: intel query <term>'); }
1182
+ output(intelQuery(term));
1183
+ } else if (subCmd === 'status') {
1184
+ output(intelStatus());
1185
+ } else if (subCmd === 'diff') {
1186
+ output(intelDiff());
1187
+ } else {
1188
+ error('Usage: intel <query|status|diff>');
1189
+ }
1289
1190
 
1290
- let total = 0;
1291
- let completed = 0;
1191
+ } else if (command === 'requirements' && subcommand === 'mark-complete') {
1192
+ const ids = args[2];
1193
+ if (!ids) { error('Usage: requirements mark-complete <comma-separated-REQ-IDs>'); }
1194
+ const idList = ids.split(',').map(s => s.trim());
1195
+ output(requirementsMarkComplete(idList));
1292
1196
 
1293
- const entries = fs.readdirSync(phasesDir, { withFileTypes: true })
1294
- .filter(e => e.isDirectory());
1197
+ } else if (command === 'plan-index') {
1198
+ const phase = args[1];
1199
+ if (!phase) {
1200
+ error('Usage: pbr-tools.js plan-index <phase-number>');
1201
+ }
1202
+ output(planIndex(phase));
1203
+ } else if (command === 'frontmatter') {
1204
+ const filePath = args[1];
1205
+ if (!filePath) {
1206
+ error('Usage: pbr-tools.js frontmatter <filepath>');
1207
+ }
1208
+ output(frontmatter(filePath));
1209
+ } else if (command === 'must-haves') {
1210
+ const phase = args[1];
1211
+ if (!phase) {
1212
+ error('Usage: pbr-tools.js must-haves <phase-number>');
1213
+ }
1214
+ output(mustHavesCollect(phase));
1215
+ } else if (command === 'phase-info') {
1216
+ const phase = args[1];
1217
+ if (!phase) {
1218
+ error('Usage: pbr-tools.js phase-info <phase-number>');
1219
+ }
1220
+ output(phaseInfo(phase));
1221
+ } else if (command === 'roadmap' && subcommand === 'update-status') {
1222
+ const phase = args[2];
1223
+ const status = args[3];
1224
+ if (!phase || !status) {
1225
+ error('Usage: pbr-tools.js roadmap update-status <phase-number> <status>');
1226
+ }
1227
+ output(roadmapUpdateStatus(phase, status));
1228
+ } else if (command === 'roadmap' && subcommand === 'update-plans') {
1229
+ const phase = args[2];
1230
+ const complete = args[3];
1231
+ const total = args[4];
1232
+ if (!phase || complete === undefined || total === undefined) {
1233
+ error('Usage: pbr-tools.js roadmap update-plans <phase-number> <complete> <total>');
1234
+ }
1235
+ output(roadmapUpdatePlans(phase, complete, total));
1236
+ } else if (command === 'roadmap' && subcommand === 'analyze') {
1237
+ output(roadmapAnalyze());
1238
+ } else if (command === 'roadmap' && subcommand === 'reconcile') {
1239
+ const result = roadmapReconcile();
1240
+ if (result.fixed > 0) {
1241
+ process.stderr.write(`Reconciled ${result.fixed} ROADMAP entries\n`);
1242
+ for (const m of result.mismatches) {
1243
+ process.stderr.write(` Phase ${m.phase}: ${m.from} -> ${m.to}\n`);
1244
+ }
1245
+ } else {
1246
+ process.stderr.write('ROADMAP statuses are consistent\n');
1247
+ }
1248
+ output(result);
1249
+ } else if (command === 'roadmap' && subcommand === 'get-phase') {
1250
+ const phaseNum = args[2];
1251
+ if (!phaseNum) { error('Usage: roadmap get-phase <phase_num>'); }
1252
+ output(phaseInfo(phaseNum));
1253
+ } else if (command === 'roadmap' && subcommand === 'append-phase') {
1254
+ const goalIdx = args.indexOf('--goal');
1255
+ const goal = goalIdx >= 0 ? args[goalIdx + 1] : args[2] || '';
1256
+ const nameIdx = args.indexOf('--name');
1257
+ const name = nameIdx >= 0 ? args[nameIdx + 1] : '';
1258
+ const depIdx = args.indexOf('--depends-on');
1259
+ const dependsOn = depIdx >= 0 ? args[depIdx + 1] : null;
1260
+ const analysis = roadmapAnalyze();
1261
+ const maxPhase = analysis.phases ? Math.max(...analysis.phases.map(p => p.num || 0), 0) : 0;
1262
+ const nextNum = maxPhase + 1;
1263
+ output(roadmapAppendPhase(nextNum, name || goal, goal, dependsOn));
1295
1264
 
1296
- for (const entry of entries) {
1297
- const dir = path.join(phasesDir, entry.name);
1298
- const plans = findFiles(dir, /-PLAN\.md$/);
1299
- total += plans.length;
1265
+ } else if (command === 'history' && subcommand === 'append') {
1266
+ const type = args[2]; // 'milestone' or 'phase'
1267
+ const title = args[3];
1268
+ const body = args[4] || '';
1269
+ if (!type || !title) {
1270
+ error('Usage: pbr-tools.js history append <milestone|phase> <title> [body]');
1271
+ }
1272
+ output(historyAppend({ type, title, body }));
1273
+ } else if (command === 'history' && subcommand === 'load') {
1274
+ output(historyLoad());
1275
+ } else if (command === 'event') {
1276
+ const category = args[1];
1277
+ const event = args[2];
1278
+ let details = {};
1279
+ if (args[3]) {
1280
+ try { details = JSON.parse(args[3]); } catch (_e) { details = { raw: args[3] }; }
1281
+ }
1282
+ if (!category || !event) {
1283
+ error('Usage: pbr-tools.js event <category> <event> [JSON-details]');
1284
+ }
1285
+ const { logEvent } = require('./event-logger');
1286
+ logEvent(category, event, details);
1287
+ output({ logged: true, category, event });
1288
+ } else if (command === 'llm') {
1289
+ // local_llm feature has been removed (phase 53). All subcommands are no-ops.
1290
+ output({ deprecated: true, message: 'local_llm feature has been removed. This subcommand is a no-op.' });
1291
+ // --- Compound init commands ---
1292
+ } else if (command === "init" && subcommand === "execute-phase") {
1293
+ const phase = args[2];
1294
+ if (!phase) error("Usage: pbr-tools.js init execute-phase <phase-number>");
1295
+ output(initExecutePhase(phase));
1296
+ } else if (command === "init" && subcommand === "plan-phase") {
1297
+ const phase = args[2];
1298
+ if (!phase) error("Usage: pbr-tools.js init plan-phase <phase-number>");
1299
+ output(initPlanPhase(phase));
1300
+ } else if (command === "init" && subcommand === "quick") {
1301
+ const desc = args.slice(2).join(" ") || "";
1302
+ output(initQuick(desc));
1303
+ } else if (command === "init" && subcommand === "verify-work") {
1304
+ const phase = args[2];
1305
+ if (!phase) error("Usage: pbr-tools.js init verify-work <phase-number>");
1306
+ output(initVerifyWork(phase));
1307
+ } else if (command === "init" && subcommand === "resume") {
1308
+ output(initResume());
1309
+ } else if (command === "init" && subcommand === "progress") {
1310
+ output(initProgress());
1311
+ } else if (command === "init" && subcommand === "continue") {
1312
+ output(initContinue());
1313
+ } else if (command === "init" && subcommand === "milestone") {
1314
+ output(initMilestone());
1315
+ } else if (command === "init" && subcommand === "begin") {
1316
+ output(initBegin());
1317
+ } else if (command === "init" && subcommand === "status") {
1318
+ output(initStatus());
1319
+ } else if (command === "init" && subcommand === "map-codebase") {
1320
+ output(initMapCodebase());
1321
+ } else if (command === 'state-bundle') {
1322
+ const phaseNum = args[1];
1323
+ if (!phaseNum) error('Usage: pbr-tools.js state-bundle <phase-number>');
1324
+ output(stateBundle(phaseNum));
1325
+ // --- State patch/advance/metric ---
1326
+ } else if (command === "state" && subcommand === "patch") {
1327
+ const jsonStr = args[2];
1328
+ if (!jsonStr) error("Usage: pbr-tools.js state patch JSON");
1329
+ output(statePatch(jsonStr));
1330
+ } else if (command === "state" && subcommand === "advance-plan") {
1331
+ output(stateAdvancePlan());
1332
+ } else if (command === "state" && subcommand === "record-metric") {
1333
+ output(stateRecordMetric(args.slice(2)));
1334
+ } else if (command === "state" && subcommand === "record-activity") {
1335
+ const description = args.slice(2).join(' ');
1336
+ if (!description) error("Usage: pbr-tools.js state record-activity <description>");
1337
+ output(stateRecordActivity(description));
1338
+ } else if (command === "state" && subcommand === "update-progress") {
1339
+ output(stateUpdateProgress());
1340
+ } else if (command === 'state' && subcommand === 'reconcile') {
1341
+ output(stateReconcile());
1342
+ } else if (command === 'state' && subcommand === 'backup') {
1343
+ output(stateBackup());
1344
+ } else if (command === 'state' && subcommand === 'enqueue') {
1345
+ const field = args[2];
1346
+ const value = args[3];
1347
+ if (!field || value === undefined) { error('Usage: state enqueue <field> <value>'); }
1348
+ const { stateEnqueue } = require('./lib/state-queue');
1349
+ output(stateEnqueue(field, value, planningDir));
1350
+ } else if (command === 'state' && subcommand === 'drain') {
1351
+ const { stateDrain } = require('./lib/state-queue');
1352
+ output(stateDrain(planningDir));
1353
+ } else if (command === 'phase' && subcommand === 'add') {
1354
+ const slug = args[2];
1355
+ if (!slug) { error('Usage: phase add <slug> [--after N] [--goal "..."] [--depends-on N]'); }
1356
+ const afterIdx = args.indexOf('--after');
1357
+ const afterPhase = afterIdx !== -1 ? args[afterIdx + 1] : null;
1358
+ const goalIdx = args.indexOf('--goal');
1359
+ const goal = goalIdx !== -1 ? args[goalIdx + 1] : null;
1360
+ const depIdx = args.indexOf('--depends-on');
1361
+ const dependsOn = depIdx !== -1 ? args[depIdx + 1] : null;
1362
+ const addOpts = {};
1363
+ if (goal) addOpts.goal = goal;
1364
+ if (dependsOn) addOpts.dependsOn = dependsOn;
1365
+ output(phaseAdd(slug, afterPhase, Object.keys(addOpts).length > 0 ? addOpts : undefined));
1366
+ } else if (command === 'phase' && subcommand === 'remove') {
1367
+ const phaseNum = args[2];
1368
+ if (!phaseNum) { error('Usage: phase remove <phase_num>'); }
1369
+ output(phaseRemove(phaseNum));
1370
+ } else if (command === 'phase' && subcommand === 'list') {
1371
+ const statusIdx = args.indexOf('--status');
1372
+ const beforeIdx = args.indexOf('--before');
1373
+ const listOpts = {};
1374
+ if (statusIdx >= 0) listOpts.status = args[statusIdx + 1];
1375
+ if (beforeIdx >= 0) listOpts.before = args[beforeIdx + 1];
1376
+ output(phaseList(listOpts));
1377
+ } else if (command === 'phase' && subcommand === 'next-number') {
1378
+ output(phaseNextNumber());
1379
+ } else if (command === 'phase' && subcommand === 'complete') {
1380
+ const phaseNum = args[2];
1381
+ if (!phaseNum) { error('Usage: phase complete <phase_num>'); }
1382
+ output(phaseComplete(phaseNum));
1383
+ } else if (command === 'phase' && subcommand === 'insert') {
1384
+ const position = args[2];
1385
+ const slug = args[3];
1386
+ if (!position || !slug) { error('Usage: phase insert <N> <slug> [--goal "..."] [--depends-on N]'); }
1387
+ const goalIdx = args.indexOf('--goal');
1388
+ const goal = goalIdx !== -1 ? args[goalIdx + 1] : null;
1389
+ const depIdx = args.indexOf('--depends-on');
1390
+ const dependsOn = depIdx !== -1 ? args[depIdx + 1] : null;
1391
+ const insertOpts = {};
1392
+ if (goal) insertOpts.goal = goal;
1393
+ if (dependsOn) insertOpts.dependsOn = dependsOn;
1394
+ output(phaseInsert(parseInt(position, 10), slug, Object.keys(insertOpts).length > 0 ? insertOpts : undefined));
1395
+ } else if (command === 'phase' && subcommand === 'commits-for') {
1396
+ const phaseNum = args[2];
1397
+ if (!phaseNum) { error('Usage: phase commits-for <N>'); }
1398
+ output(_phaseCommitsFor(phaseNum));
1399
+ } else if (command === 'phase' && subcommand === 'first-last-commit') {
1400
+ const phaseNum = args[2];
1401
+ if (!phaseNum) { error('Usage: phase first-last-commit <N>'); }
1402
+ output(_phaseFirstLastCommit(phaseNum));
1403
+ } else if (command === 'compound' && subcommand === 'init-phase') {
1404
+ const phaseNum = args[2];
1405
+ const slug = args[3];
1406
+ if (!phaseNum || !slug) error('Usage: compound init-phase <N> <slug> [--goal "..."] [--depends-on N]');
1407
+ const goalIdx = args.indexOf('--goal');
1408
+ const goal = goalIdx !== -1 ? args[goalIdx + 1] : null;
1409
+ const depIdx = args.indexOf('--depends-on');
1410
+ const dependsOn = depIdx !== -1 ? args[depIdx + 1] : null;
1411
+ output(compoundInitPhase(phaseNum, slug, { goal, dependsOn }));
1412
+ } else if (command === 'compound' && subcommand === 'complete-phase') {
1413
+ const phaseNum = args[2];
1414
+ if (!phaseNum) error('Usage: compound complete-phase <N>');
1415
+ output(compoundCompletePhase(phaseNum));
1416
+ } else if (command === 'compound' && subcommand === 'init-milestone') {
1417
+ const version = args[2];
1418
+ if (!version) error('Usage: compound init-milestone <version> [--name "..."] [--phases "N-M"]');
1419
+ const nameIdx = args.indexOf('--name');
1420
+ const name = nameIdx !== -1 ? args[nameIdx + 1] : null;
1421
+ const phasesIdx = args.indexOf('--phases');
1422
+ const phases = phasesIdx !== -1 ? args[phasesIdx + 1] : null;
1423
+ output(compoundInitMilestone(version, { name, phases }));
1424
+ } else if (command === 'todo' && subcommand === 'list') {
1425
+ const opts = {};
1426
+ const themeIdx = args.indexOf('--theme');
1427
+ if (themeIdx !== -1 && args[themeIdx + 1]) opts.theme = args[themeIdx + 1];
1428
+ const statusIdx = args.indexOf('--status');
1429
+ if (statusIdx !== -1 && args[statusIdx + 1]) opts.status = args[statusIdx + 1];
1430
+ output(todoList(opts));
1431
+ } else if (command === 'todo' && subcommand === 'get') {
1432
+ const num = args[2];
1433
+ if (!num) error('Usage: pbr-tools.js todo get <NNN>');
1434
+ output(todoGet(num));
1435
+ } else if (command === 'todo' && subcommand === 'add') {
1436
+ const titleParts = [];
1437
+ const opts = {};
1438
+ // Parse: todo add <title words...> [--priority P1] [--theme security] [--source cli]
1439
+ for (let i = 2; i < args.length; i++) {
1440
+ if (args[i] === '--priority' && args[i + 1]) { opts.priority = args[++i]; }
1441
+ else if (args[i] === '--theme' && args[i + 1]) { opts.theme = args[++i]; }
1442
+ else if (args[i] === '--source' && args[i + 1]) { opts.source = args[++i]; }
1443
+ else { titleParts.push(args[i]); }
1444
+ }
1445
+ const title = titleParts.join(' ');
1446
+ if (!title) error('Usage: pbr-tools.js todo add <title> [--priority P1|P2|P3] [--theme <theme>]');
1447
+ output(todoAdd(title, opts));
1448
+ } else if (command === 'todo' && subcommand === 'done') {
1449
+ const num = args[2];
1450
+ if (!num) error('Usage: pbr-tools.js todo done <NNN>');
1451
+ output(todoDone(num));
1452
+ } else if (command === 'auto-cleanup') {
1453
+ const phaseFlag = args.indexOf('--phase');
1454
+ const milestoneFlag = args.indexOf('--milestone');
1455
+ if (phaseFlag !== -1 && args[phaseFlag + 1]) {
1456
+ const phaseNum = args[phaseFlag + 1];
1457
+ const context = buildCleanupContext(phaseNum);
1458
+ const todoResult = autoCloseTodos(context);
1459
+ const noteResult = autoArchiveNotes(context);
1460
+ output({ phase: phaseNum, todos: todoResult, notes: noteResult });
1461
+ } else if (milestoneFlag !== -1 && args[milestoneFlag + 1]) {
1462
+ const version = args[milestoneFlag + 1];
1463
+ // Parse ROADMAP.md to find phases in this milestone
1464
+ const roadmapPath = path.join(planningDir, 'ROADMAP.md');
1465
+ if (!fs.existsSync(roadmapPath)) { error('ROADMAP.md not found'); }
1466
+ const roadmap = fs.readFileSync(roadmapPath, 'utf8');
1467
+ const milestoneMatch = roadmap.match(new RegExp('Milestone.*' + version.replace(/\./g, '\\.') + '[\\s\\S]*?Phases:\\s*(\\d+)\\s*-\\s*(\\d+)'));
1468
+ if (!milestoneMatch) { error('Milestone ' + version + ' not found in ROADMAP.md'); }
1469
+ const startPhase = parseInt(milestoneMatch[1]);
1470
+ const endPhase = parseInt(milestoneMatch[2]);
1471
+ const allResults = { milestone: version, phases: [] };
1472
+ for (let p = startPhase; p <= endPhase; p++) {
1473
+ try {
1474
+ const ctx = buildCleanupContext(String(p));
1475
+ const todoRes = autoCloseTodos(ctx);
1476
+ const noteRes = autoArchiveNotes(ctx);
1477
+ allResults.phases.push({ phase: p, todos: todoRes, notes: noteRes });
1478
+ } catch (_e) { /* skip phases without SUMMARY */ }
1479
+ }
1480
+ output(allResults);
1481
+ } else {
1482
+ error('Usage: auto-cleanup --phase N | --milestone vN');
1483
+ }
1484
+ } else if (command === 'migrate') {
1485
+ const dryRun = args.includes('--dry-run');
1486
+ const force = args.includes('--force');
1487
+ const result = await migrate({ dryRun, force });
1488
+ output(result);
1489
+ } else if (command === 'learnings') {
1490
+ const subCmd = args[1];
1491
+
1492
+ if (subCmd === 'ingest') {
1493
+ // learnings ingest <json-file-path>
1494
+ const jsonFile = args[2];
1495
+ if (!jsonFile) { error('Usage: learnings ingest <json-file>'); process.exit(1); }
1496
+ const raw = fs.readFileSync(jsonFile, 'utf8');
1497
+ const entry = JSON.parse(raw);
1498
+ const result = _learningsIngest(entry);
1499
+ output(result);
1500
+
1501
+ } else if (subCmd === 'query') {
1502
+ // learnings query [--tags tag1,tag2] [--min-confidence low|medium|high] [--stack react] [--type tech-pattern]
1503
+ const filters = {};
1504
+ for (let i = 2; i < args.length; i++) {
1505
+ if (args[i] === '--tags' && args[i + 1]) { filters.tags = args[++i].split(',').map(t => t.trim()); }
1506
+ else if (args[i] === '--min-confidence' && args[i + 1]) { filters.minConfidence = args[++i]; }
1507
+ else if (args[i] === '--stack' && args[i + 1]) { filters.stack = args[++i]; }
1508
+ else if (args[i] === '--type' && args[i + 1]) { filters.type = args[++i]; }
1509
+ }
1510
+ const results = _learningsQuery(filters);
1511
+ output(results);
1512
+
1513
+ } else if (subCmd === 'check-thresholds') {
1514
+ // learnings check-thresholds — for progress-tracker to call
1515
+ const triggered = _checkDeferralThresholds();
1516
+ output(triggered);
1517
+
1518
+ } else if (subCmd === 'copy-global') {
1519
+ const filePath = args[2];
1520
+ const projectName = args[3];
1521
+ if (!filePath || !projectName) { error('Usage: learnings copy-global <learnings-md-path> <project-name>'); process.exit(1); }
1522
+ output(_copyToGlobal(filePath, projectName));
1523
+
1524
+ } else if (subCmd === 'query-global') {
1525
+ const filters = {};
1526
+ for (let i = 2; i < args.length; i++) {
1527
+ if (args[i] === '--tags' && args[i + 1]) { filters.tags = args[++i].split(',').map(t => t.trim()); }
1528
+ else if (args[i] === '--project' && args[i + 1]) { filters.project = args[++i]; }
1529
+ }
1530
+ output(_queryGlobal(filters));
1300
1531
 
1301
- const summaries = findFiles(dir, /^SUMMARY-.*\.md$/);
1302
- for (const s of summaries) {
1303
- const content = fs.readFileSync(path.join(dir, s), 'utf8');
1304
- if (/status:\s*["']?complete/i.test(content)) completed++;
1305
- }
1306
- }
1532
+ } else {
1533
+ error('Usage: learnings <ingest|query|check-thresholds|copy-global|query-global>');
1534
+ process.exit(1);
1535
+ }
1536
+ } else if (command === 'insights') {
1537
+ const subCmd = args[1];
1538
+ if (subCmd === 'import') {
1539
+ const htmlPath = args[2];
1540
+ if (!htmlPath) { error('Usage: insights import <html-file-path> [--project <name>]'); process.exit(1); }
1541
+ let projectName;
1542
+ for (let i = 3; i < args.length; i++) {
1543
+ if (args[i] === '--project' && args[i + 1]) { projectName = args[++i]; }
1544
+ }
1545
+ const result = insightsImport(htmlPath, projectName);
1546
+ output(result);
1547
+ } else {
1548
+ error('Usage: insights <import>');
1549
+ process.exit(1);
1550
+ }
1551
+ } else if (command === 'verify' && subcommand === 'spot-check') {
1552
+ const scType = args[2];
1553
+ const scPath = args[3];
1554
+ if (!scType || !scPath) { error('Usage: verify spot-check <type> <path> (types: plan, summary, verification, quick)'); }
1555
+ const result = verifySpotCheck(scType, scPath);
1556
+ if (result.error) { process.stdout.write(JSON.stringify(result, null, 2) + '\n'); process.exit(1); }
1557
+ output(result);
1558
+
1559
+ } else if (command === 'verify' && subcommand === 'summary') {
1560
+ const spath = args[2];
1561
+ const cfIdx = args.indexOf('--check-files');
1562
+ const cf = cfIdx !== -1 ? parseInt(args[cfIdx + 1], 10) : 2;
1563
+ if (!spath) { error('Usage: verify summary <path> [--check-files N]'); }
1564
+ _cmdVerifySummary(cwd, spath, cf, false);
1565
+
1566
+ } else if (command === 'verify' && subcommand === 'plan-structure') {
1567
+ const ppath = args[2];
1568
+ if (!ppath) { error('Usage: verify plan-structure <path>'); }
1569
+ _cmdVerifyPlanStructure(cwd, ppath, false);
1570
+
1571
+ } else if (command === 'verify' && subcommand === 'phase-completeness') {
1572
+ const phase = args[2];
1573
+ if (!phase) { error('Usage: verify phase-completeness <phase>'); }
1574
+ _cmdVerifyPhaseCompleteness(cwd, phase, false);
1575
+
1576
+ } else if (command === 'verify' && subcommand === 'artifacts') {
1577
+ const planPath = args[2];
1578
+ if (!planPath) { error('Usage: verify artifacts <plan-path>'); }
1579
+ _cmdVerifyArtifacts(cwd, planPath, false);
1580
+
1581
+ } else if (command === 'verify' && subcommand === 'key-links') {
1582
+ const planPath = args[2];
1583
+ if (!planPath) { error('Usage: verify key-links <plan-path>'); }
1584
+ _cmdVerifyKeyLinks(cwd, planPath, false);
1585
+
1586
+ } else if (command === 'verify' && subcommand === 'commits') {
1587
+ const hashes = args.slice(2);
1588
+ if (!hashes.length) { error('Usage: verify commits <hash1> [hash2] ...'); }
1589
+ _cmdVerifyCommits(cwd, hashes, false);
1590
+
1591
+ } else if (command === 'verify' && subcommand === 'references') {
1592
+ const rpath = args[2];
1593
+ if (!rpath) { error('Usage: verify references <path>'); }
1594
+ _cmdVerifyReferences(cwd, rpath, false);
1595
+
1596
+ } else if (command === 'spot-check') {
1597
+ // spot-check <phaseSlug> <planId>
1598
+ // Returns JSON: { ok, summary_exists, key_files_checked, commits_present, detail }
1599
+ const phaseSlug = args[1];
1600
+ const planId = args[2];
1601
+ if (!phaseSlug || !planId) {
1602
+ error('Usage: spot-check <phaseSlug> <planId>');
1603
+ }
1604
+ output(spotCheck(phaseSlug, planId));
1605
+ } else if (command === 'staleness-check') {
1606
+ const slug = args[1];
1607
+ if (!slug) { error('Usage: staleness-check <phase-slug>'); process.exit(1); }
1608
+ output(stalenessCheck(slug));
1609
+ } else if (command === 'summary-gate') {
1610
+ const [slug, planId] = args.slice(1);
1611
+ if (!slug || !planId) { error('Usage: summary-gate <phase-slug> <plan-id>'); process.exit(1); }
1612
+ output(summaryGate(slug, planId));
1613
+ } else if (command === 'checkpoint') {
1614
+ const sub = args[1];
1615
+ const slug = args[2];
1616
+ if (sub === 'init') {
1617
+ const plans = args[3] || '';
1618
+ output(checkpointInit(slug, plans));
1619
+ } else if (sub === 'update') {
1620
+ const waveIdx = args.indexOf('--wave');
1621
+ const wave = waveIdx !== -1 ? parseInt(args[waveIdx + 1], 10) : 1;
1622
+ const resolvedIdx = args.indexOf('--resolved');
1623
+ const resolved = resolvedIdx !== -1 ? args[resolvedIdx + 1] : '';
1624
+ const shaIdx = args.indexOf('--sha');
1625
+ const sha = shaIdx !== -1 ? args[shaIdx + 1] : '';
1626
+ output(checkpointUpdate(slug, { wave, resolved, sha }));
1627
+ } else {
1628
+ error('Usage: checkpoint init|update <phase-slug> [options]'); process.exit(1);
1629
+ }
1630
+ } else if (command === 'seeds') {
1631
+ const sub = args[1];
1632
+ if (sub === 'match') {
1633
+ const slug = args[2];
1634
+ const num = args[3];
1635
+ if (!slug) { error('Usage: seeds match <phase-slug> <phase-number>'); process.exit(1); }
1636
+ output(seedsMatch(slug, num));
1637
+ } else {
1638
+ error('Usage: seeds match <phase-slug> <phase-number>'); process.exit(1);
1639
+ }
1640
+ } else if (command === 'ci-poll') {
1641
+ const runId = args[1];
1642
+ const timeoutIdx = args.indexOf('--timeout');
1643
+ const timeoutSecs = timeoutIdx !== -1 ? parseInt(args[timeoutIdx + 1], 10) : 300;
1644
+ if (!runId) { error('Usage: pbr-tools.js ci-poll <run-id> [--timeout <seconds>]'); return; }
1645
+ output(ciPoll(runId, timeoutSecs));
1646
+ } else if (command === 'rollback') {
1647
+ const manifestPath = args[1];
1648
+ if (!manifestPath) { error('Usage: pbr-tools.js rollback <manifest-path>'); return; }
1649
+ output(rollbackPlan(manifestPath));
1650
+ } else if (command === 'session') {
1651
+ const sub = args[1];
1652
+ // Extract --session-id flag from remaining args
1653
+ const sessionIdIdx = args.indexOf('--session-id');
1654
+ const sessionId = sessionIdIdx !== -1 ? args[sessionIdIdx + 1] : null;
1655
+ // Filter out --session-id and its value from positional args
1656
+ const positional = sessionIdIdx !== -1
1657
+ ? args.filter((_a, i) => i !== sessionIdIdx && i !== sessionIdIdx + 1)
1658
+ : args;
1659
+ const key = positional[2];
1660
+ const value = positional[3];
1661
+ const dir = planningDir;
1662
+ if (sub === 'get') {
1663
+ if (!key) { error('Usage: pbr-tools.js session get <key> [--session-id <id>]'); return; }
1664
+ if (!SESSION_ALLOWED_KEYS.includes(key)) { error(`Unknown session key: ${key}. Allowed: ${SESSION_ALLOWED_KEYS.join(', ')}`); return; }
1665
+ const data = sessionLoad(dir, sessionId);
1666
+ const val = Object.prototype.hasOwnProperty.call(data, key) ? data[key] : null;
1667
+ output({ key, value: val });
1668
+ } else if (sub === 'set') {
1669
+ if (!key || value === undefined) { error('Usage: pbr-tools.js session set <key> <value> [--session-id <id>]'); return; }
1670
+ if (!SESSION_ALLOWED_KEYS.includes(key)) { error(`Unknown session key: ${key}. Allowed: ${SESSION_ALLOWED_KEYS.join(', ')}`); return; }
1671
+ // Coerce numeric strings
1672
+ let coerced = value;
1673
+ if (/^\d+$/.test(value)) coerced = parseInt(value, 10);
1674
+ else if (value === 'null') coerced = null;
1675
+ const result = sessionSave(dir, { [key]: coerced }, sessionId);
1676
+ if (!result.success) { error(result.error || 'Failed to save session'); return; }
1677
+ output({ ok: true });
1678
+ } else if (sub === 'clear') {
1679
+ if (key) {
1680
+ // Clear a specific key — set to null
1681
+ if (!SESSION_ALLOWED_KEYS.includes(key)) { error(`Unknown session key: ${key}. Allowed: ${SESSION_ALLOWED_KEYS.join(', ')}`); return; }
1682
+ const result = sessionSave(dir, { [key]: null }, sessionId);
1683
+ if (!result.success) { error(result.error || 'Failed to clear session key'); return; }
1684
+ } else {
1685
+ // Clear entire session file
1686
+ const sessionPath = sessionId
1687
+ ? resolveSessionPath(dir, '.session.json', sessionId)
1688
+ : path.join(dir, '.session.json');
1689
+ try { if (fs.existsSync(sessionPath)) fs.unlinkSync(sessionPath); } catch (e) { error(e.message); return; }
1690
+ }
1691
+ output({ ok: true });
1692
+ } else if (sub === 'dump') {
1693
+ const data = sessionLoad(dir, sessionId);
1694
+ output(data);
1695
+ } else {
1696
+ error('Usage: pbr-tools.js session get|set|clear|dump <key> [value] [--session-id <id>]');
1697
+ }
1698
+ } else if (command === 'context-triage') {
1699
+ const options = {};
1700
+ const agentsIdx = args.indexOf('--agents-done');
1701
+ if (agentsIdx !== -1) options.agentsDone = parseInt(args[agentsIdx + 1], 10);
1702
+ const plansIdx = args.indexOf('--plans-total');
1703
+ if (plansIdx !== -1) options.plansTotal = parseInt(args[plansIdx + 1], 10);
1704
+ const stepIdx = args.indexOf('--step');
1705
+ if (stepIdx !== -1) options.currentStep = args[stepIdx + 1];
1706
+ output(contextTriage(options));
1707
+ } else if (command === 'reference') {
1708
+ const name = args[1];
1709
+ if (!name) error('Usage: pbr-tools.js reference <name> [--section <heading>] [--list]');
1710
+ const listFlag = args.includes('--list');
1711
+ const sectionIdx = args.indexOf('--section');
1712
+ const section = sectionIdx !== -1 ? args.slice(sectionIdx + 1).join(' ') : null;
1713
+ output(referenceGet(name, { section: section, list: listFlag }));
1714
+ } else if (command === 'milestone-stats') {
1715
+ const version = args[1];
1716
+ if (!version) error('Usage: pbr-tools.js milestone-stats <version>');
1717
+ output(milestoneStats(version));
1718
+ } else if (command === 'validate-project') {
1719
+ output(validateProject());
1720
+ } else if (command === 'skill-section') {
1721
+ // skill-section --list <skill> — list all headings
1722
+ if (args[1] === '--list') {
1723
+ const skillName = args[2];
1724
+ if (!skillName) { error('Usage: pbr-tools.js skill-section --list <skill>'); return; }
1725
+ const listResult = listSkillHeadings(skillName);
1726
+ output(listResult);
1727
+ if (listResult.error) process.exit(1);
1728
+ } else {
1729
+ // skill-section <skill> <section...>
1730
+ const skillName = args[1];
1731
+ const sectionQuery = args.slice(2).join(' ');
1732
+ if (!skillName || !sectionQuery) {
1733
+ error('Usage: pbr-tools.js skill-section <skill> <section>');
1734
+ return;
1735
+ }
1736
+ const secResult = skillSectionGet(skillName, sectionQuery);
1737
+ output(secResult);
1738
+ if (secResult.error) process.exit(1);
1739
+ }
1740
+ } else if (command === 'step-verify') {
1741
+ // step-verify <skill> <step> <checklist-json>
1742
+ const skill = args[1];
1743
+ const step = args[2];
1744
+ const checklistStr = args[3] || '[]';
1745
+ let checklist;
1746
+ try {
1747
+ checklist = JSON.parse(checklistStr);
1748
+ } catch (_e) {
1749
+ output({ error: 'Invalid checklist JSON' });
1750
+ process.exit(1);
1751
+ return;
1752
+ }
1753
+ const svPlanningDir = planningDir;
1754
+ const svContext = {
1755
+ planningDir: svPlanningDir,
1756
+ phaseSlug: process.env.PBR_PHASE_SLUG || '',
1757
+ planId: process.env.PBR_PLAN_ID || ''
1758
+ };
1759
+ const svResult = _stepVerify(skill, step, checklist, svContext);
1760
+ output(svResult);
1761
+ if (svResult.error || svResult.all_passed === false) process.exit(1);
1762
+ } else if (command === 'build-preview') {
1763
+ const phaseSlug = args[1];
1764
+ if (!phaseSlug) {
1765
+ error('Usage: pbr-tools.js build-preview <phase-slug>');
1766
+ return;
1767
+ }
1768
+ const previewPlanningDir = planningDir;
1769
+ const previewPluginRoot = path.resolve(__dirname, '..');
1770
+ const result = _buildPreview(phaseSlug, {}, previewPlanningDir, previewPluginRoot);
1771
+ if (result && result.error) {
1772
+ output(result);
1773
+ process.exit(1);
1774
+ }
1775
+ output(result);
1776
+ } else if (command === 'suggest-alternatives') {
1777
+ const errorType = args[1];
1778
+ const altPlanningDir = planningDir;
1779
+ if (errorType === 'phase-not-found') {
1780
+ output(_phaseAlternatives(args[2] || '', altPlanningDir));
1781
+ } else if (errorType === 'missing-prereq') {
1782
+ output(_prereqAlternatives(args[2] || '', altPlanningDir));
1783
+ } else if (errorType === 'config-invalid') {
1784
+ output(_configAlternatives(args[2] || '', args[3] || '', altPlanningDir));
1785
+ } else {
1786
+ output({ error: 'Unknown error type. Valid: phase-not-found, missing-prereq, config-invalid' });
1787
+ process.exit(1);
1788
+ }
1789
+ } else if (command === 'claim' && subcommand === 'acquire') {
1790
+ const phaseSlug = args[2];
1791
+ const sidIdx = args.indexOf('--session-id');
1792
+ const sessionId = sidIdx !== -1 ? args[sidIdx + 1] : null;
1793
+ const skillIdx = args.indexOf('--skill');
1794
+ const skill = skillIdx !== -1 ? args[skillIdx + 1] : 'unknown';
1795
+ if (!phaseSlug || !sessionId) {
1796
+ error('Usage: pbr-tools.js claim acquire <phase-slug> --session-id <id> --skill <name>');
1797
+ }
1798
+ output(claimAcquire(phaseSlug, sessionId, skill));
1799
+ } else if (command === 'claim' && subcommand === 'release') {
1800
+ const phaseSlug = args[2];
1801
+ const sidIdx = args.indexOf('--session-id');
1802
+ const sessionId = sidIdx !== -1 ? args[sidIdx + 1] : null;
1803
+ if (!phaseSlug || !sessionId) {
1804
+ error('Usage: pbr-tools.js claim release <phase-slug> --session-id <id>');
1805
+ }
1806
+ output(claimRelease(phaseSlug, sessionId));
1807
+ } else if (command === 'claim' && subcommand === 'list') {
1808
+ output(claimList());
1809
+ } else if (command === 'status' && subcommand === 'render') {
1810
+ output(_statusRender(planningDir));
1811
+ } else if (command === 'suggest-next') {
1812
+ output(_suggestNext(planningDir));
1813
+ } else if (command === 'tmux' && subcommand === 'detect') {
1814
+ const tmuxEnv = process.env.TMUX || '';
1815
+ const result = {
1816
+ in_tmux: !!tmuxEnv,
1817
+ pane: process.env.TMUX_PANE || null,
1818
+ session: null
1819
+ };
1820
+ if (tmuxEnv) {
1821
+ // TMUX env format: /socket/path,PID,INDEX
1822
+ const parts = tmuxEnv.split(',');
1823
+ if (parts.length >= 1) {
1824
+ result.session = parts[0].split('/').pop() || null;
1825
+ }
1826
+ }
1827
+ output(result);
1828
+
1829
+ // ─── Quick Task Operations ─────────────────────────────────────────────────
1830
+ } else if (command === 'quick' && subcommand === 'init') {
1831
+ const desc = args.slice(2).join(' ') || '';
1832
+ const quickInitMod = require('./lib/quick-init.js');
1833
+ output(quickInitMod.quickInit(desc, planningDir));
1834
+
1835
+ // ─── Slug Generation ─────────────────────────────────────────────────────
1836
+ } else if (command === 'generate-slug' || command === 'slug-generate') {
1837
+ const text = args.slice(1).join(' ');
1838
+ if (!text) error('Usage: pbr-tools.js generate-slug <text>');
1839
+ const slug = text.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
1840
+ output({ slug });
1841
+
1842
+ // ─── Parse Args ────────────────────────────────────────────────────────────
1843
+ } else if (command === 'parse-args') {
1844
+ const type = args[1];
1845
+ const rawInput = args.slice(2).join(' ');
1846
+ if (!type) error('Usage: pbr-tools.js parse-args <type> <args>\nTypes: plan, quick');
1847
+ const { parseArgs } = require('./lib/parse-args');
1848
+ output(parseArgs(type, rawInput));
1849
+
1850
+ // ─── Status Fingerprint ──────────────────────────────────────────────────
1851
+ } else if (command === 'status' && subcommand === 'fingerprint') {
1852
+ const crypto = require('crypto');
1853
+ const files = {};
1854
+ let combinedContent = '';
1855
+ for (const name of ['STATE.md', 'ROADMAP.md']) {
1856
+ const filePath = path.join(planningDir, name);
1857
+ try {
1858
+ const content = fs.readFileSync(filePath, 'utf8');
1859
+ const stat = fs.statSync(filePath);
1860
+ const hash = crypto.createHash('sha256').update(content).digest('hex').slice(0, 8);
1861
+ files[name] = {
1862
+ hash,
1863
+ mtime: stat.mtime.toISOString(),
1864
+ lines: content.split('\n').length
1865
+ };
1866
+ combinedContent += content;
1867
+ } catch {
1868
+ files[name] = { hash: null, mtime: null, lines: 0 };
1869
+ }
1870
+ }
1871
+ const fingerprint = combinedContent
1872
+ ? crypto.createHash('sha256').update(combinedContent).digest('hex').slice(0, 8)
1873
+ : null;
1874
+ let phaseDirs = 0;
1875
+ const phasesDir = path.join(planningDir, 'phases');
1876
+ try {
1877
+ const entries = fs.readdirSync(phasesDir, { withFileTypes: true });
1878
+ phaseDirs = entries.filter(e => e.isDirectory()).length;
1879
+ } catch { /* no phases dir */ }
1880
+ output({
1881
+ fingerprint,
1882
+ files,
1883
+ phase_dirs: phaseDirs,
1884
+ timestamp: new Date().toISOString()
1885
+ });
1886
+
1887
+ } else if (command === 'quick-status') {
1888
+ const result = quickStatus();
1889
+ process.stdout.write(result.text + '\n');
1890
+
1891
+ } else if (command === 'ci-fix') {
1892
+ const dryRun = args.includes('--dry-run');
1893
+ const maxIterIdx = args.indexOf('--max-iterations');
1894
+ const maxIterations = maxIterIdx !== -1 ? parseInt(args[maxIterIdx + 1], 10) : 3;
1895
+ output(ciFix({ dryRun, maxIterations }));
1896
+
1897
+ // ─── Incidents ───────────────────────────────────────────────────────────
1898
+ } else if (command === 'incidents') {
1899
+ const sub = args[1];
1900
+ if (sub === 'list') {
1901
+ const limitIdx = args.indexOf('--limit');
1902
+ const limit = limitIdx !== -1 ? parseInt(args[limitIdx + 1], 10) : 50;
1903
+ output(incidentsList({ limit }));
1904
+ } else if (sub === 'summary') {
1905
+ output(incidentsSummary());
1906
+ } else if (sub === 'query') {
1907
+ const filter = {};
1908
+ for (let i = 2; i < args.length; i++) {
1909
+ if (args[i] === '--type' && args[i + 1]) { filter.type = args[++i]; }
1910
+ else if (args[i] === '--severity' && args[i + 1]) { filter.severity = args[++i]; }
1911
+ else if (args[i] === '--source' && args[i + 1]) { filter.source = args[++i]; }
1912
+ else if (args[i] === '--last' && args[i + 1]) { filter.last = args[++i]; }
1913
+ }
1914
+ output(incidentsQuery(filter));
1915
+ } else {
1916
+ error('Usage: incidents <list|summary|query> [--type T] [--severity S] [--source S] [--last Nd|Nh] [--limit N]');
1917
+ }
1307
1918
 
1308
- return {
1309
- total,
1310
- completed,
1311
- percentage: total > 0 ? Math.round((completed / total) * 100) : 0
1312
- };
1313
- }
1919
+ } else if (command === 'nk') {
1920
+ // Lazy require to avoid loading for unrelated commands
1921
+ const nkLib = require(path.join(__dirname, 'lib', 'negative-knowledge'));
1922
+
1923
+ if (subcommand === 'record') {
1924
+ const titleIdx = args.indexOf('--title');
1925
+ const categoryIdx = args.indexOf('--category');
1926
+ const filesIdx = args.indexOf('--files');
1927
+ const triedIdx = args.indexOf('--tried');
1928
+ const failedIdx = args.indexOf('--failed');
1929
+ const workedIdx = args.indexOf('--worked');
1930
+ const phaseIdx = args.indexOf('--phase');
1931
+
1932
+ const title = titleIdx !== -1 ? args[titleIdx + 1] : null;
1933
+ const category = categoryIdx !== -1 ? args[categoryIdx + 1] : null;
1934
+ const tried = triedIdx !== -1 ? args[triedIdx + 1] : null;
1935
+ const failed = failedIdx !== -1 ? args[failedIdx + 1] : null;
1936
+
1937
+ if (!title || !category || !tried || !failed) {
1938
+ error('Usage: pbr-tools.js nk record --title "..." --category "build-failure" --files "f1,f2" --tried "..." --failed "..."\nRequired: --title, --category, --tried, --failed\nCategories: build-failure, verification-gap, plan-revision, debug-finding');
1939
+ }
1314
1940
 
1315
- function output(data) {
1316
- process.stdout.write(JSON.stringify(data, null, 2));
1317
- process.exit(0);
1318
- }
1941
+ const filesInvolved = filesIdx !== -1 ? (args[filesIdx + 1] || '').split(',').filter(Boolean) : [];
1942
+ const whatWorked = workedIdx !== -1 ? args[workedIdx + 1] : '';
1943
+ const phase = phaseIdx !== -1 ? args[phaseIdx + 1] : '';
1319
1944
 
1320
- function error(msg) {
1321
- process.stdout.write(JSON.stringify({ error: msg }));
1322
- process.exit(1);
1323
- }
1945
+ const result = nkLib.recordFailure(planningDir, {
1946
+ title, category, filesInvolved,
1947
+ whatTried: tried, whyFailed: failed,
1948
+ whatWorked, phase
1949
+ });
1950
+ output({ ok: true, path: result.path, slug: result.slug });
1951
+
1952
+ } else if (subcommand === 'list') {
1953
+ const catIdx = args.indexOf('--category');
1954
+ const phIdx = args.indexOf('--phase');
1955
+ const stIdx = args.indexOf('--status');
1956
+ const filters = {};
1957
+ if (catIdx !== -1) filters.category = args[catIdx + 1];
1958
+ if (phIdx !== -1) filters.phase = args[phIdx + 1];
1959
+ if (stIdx !== -1) filters.status = args[stIdx + 1];
1960
+ output(nkLib.listFailures(planningDir, filters));
1961
+
1962
+ } else if (subcommand === 'resolve') {
1963
+ const slug = args[2];
1964
+ if (!slug) {
1965
+ error('Usage: pbr-tools.js nk resolve <slug>');
1966
+ }
1967
+ nkLib.resolveEntry(planningDir, slug);
1968
+ output({ ok: true });
1324
1969
 
1325
- /**
1326
- * Write content to a file atomically: write to .tmp, backup original to .bak,
1327
- * rename .tmp over original. On failure, restore from .bak if available.
1328
- *
1329
- * @param {string} filePath - Target file path
1330
- * @param {string} content - Content to write
1331
- * @returns {{success: boolean, error?: string}} Result
1332
- */
1333
- function atomicWrite(filePath, content) {
1334
- const tmpPath = filePath + '.tmp';
1335
- const bakPath = filePath + '.bak';
1970
+ } else {
1971
+ error('Usage: pbr-tools.js nk <record|list|resolve>\n nk record --title "..." --category "..." --files "f1,f2" --tried "..." --failed "..."\n nk list [--category X] [--phase X] [--status X]\n nk resolve <slug>');
1972
+ }
1336
1973
 
1337
- try {
1338
- // 1. Write to temp file
1339
- fs.writeFileSync(tmpPath, content, 'utf8');
1974
+ } else if (command === 'data') {
1975
+ const sub = args[1];
1976
+ if (sub === 'status') {
1977
+ output(dataStatus());
1978
+ } else if (sub === 'prune') {
1979
+ const beforeIdx = args.indexOf('--before');
1980
+ const before = beforeIdx !== -1 ? args[beforeIdx + 1] : null;
1981
+ const dryRun = args.includes('--dry-run');
1982
+ if (!before) {
1983
+ error('Usage: pbr-tools.js data prune --before <ISO-date> [--dry-run]');
1984
+ }
1985
+ output(dataPrune({ before, dryRun }));
1986
+ } else {
1987
+ error('Usage: pbr-tools.js data <status|prune>\n data status — freshness report for research/, intel/, codebase/\n data prune --before <ISO-date> [--dry-run] — archive stale files');
1988
+ }
1340
1989
 
1341
- // 2. Backup original if it exists
1342
- if (fs.existsSync(filePath)) {
1343
- try {
1344
- fs.copyFileSync(filePath, bakPath);
1345
- } catch (_e) {
1346
- // Backup failure is non-fatal — proceed with rename
1990
+ // --- Graph Operations ---
1991
+ } else if (command === 'graph') {
1992
+ const graphCli = require('./lib/graph-cli');
1993
+ graphCli.handleGraphCommand(subcommand, args, planningDir, cwd, output, error);
1994
+
1995
+ // --- Audit Operations ---
1996
+ } else if (command === 'audit' && subcommand === 'plan-checks') {
1997
+ const { auditPlanChecks } = require('./lib/audit');
1998
+ const lastIdx = args.indexOf('--last');
1999
+ const last = lastIdx !== -1 ? parseInt(args[lastIdx + 1], 10) : 30;
2000
+ output(auditPlanChecks({ last }));
2001
+
2002
+ // --- Hooks Perf Report ---
2003
+ } else if (command === 'hooks' && subcommand === 'perf') {
2004
+ const { summarizeHookPerf, formatPerfTable, loadPerfEntries } = require('./lib/perf');
2005
+ const lastIdx = args.indexOf('--last');
2006
+ const last = lastIdx !== -1 ? parseInt(args[lastIdx + 1], 10) : undefined;
2007
+ const jsonFlag = args.includes('--json');
2008
+ const entries = loadPerfEntries(planningDir, { last });
2009
+ const summary = summarizeHookPerf(entries);
2010
+ if (jsonFlag) {
2011
+ output(summary);
2012
+ } else {
2013
+ const table = formatPerfTable(summary);
2014
+ process.stdout.write(table + '\n');
2015
+ process.stdout.write(`\nEntries analyzed: ${entries.length}\n`);
1347
2016
  }
1348
- }
1349
2017
 
1350
- // 3. Rename temp over original (atomic on most filesystems)
1351
- fs.renameSync(tmpPath, filePath);
2018
+ // --- Spec Operations ---
2019
+ } else if (command === 'spec') {
2020
+ handleSpec(args, planningDir, cwd, output, error);
1352
2021
 
1353
- return { success: true };
1354
- } catch (e) {
1355
- // Rename failed — try to restore from backup
1356
- try {
1357
- if (fs.existsSync(bakPath)) {
1358
- fs.copyFileSync(bakPath, filePath);
1359
- }
1360
- } catch (_restoreErr) {
1361
- // Restore also failed — nothing more we can do
1362
- }
2022
+ // --- Help Operations ---
2023
+ } else if (command === 'help') {
2024
+ output(helpListCmd());
1363
2025
 
1364
- // Clean up temp file if it still exists
1365
- try {
1366
- if (fs.existsSync(tmpPath)) {
1367
- fs.unlinkSync(tmpPath);
1368
- }
1369
- } catch (_cleanupErr) {
1370
- // Best-effort cleanup
1371
- }
2026
+ } else if (command === 'skill-metadata') {
2027
+ const skillName = args[1];
2028
+ if (!skillName) { error('Usage: pbr-tools.js skill-metadata <name>'); return; }
2029
+ const result = skillMetadataCmd(skillName);
2030
+ output(result);
2031
+ if (result.error) process.exit(1);
1372
2032
 
1373
- return { success: false, error: e.message };
2033
+ } else {
2034
+ error(`Unknown command: ${args.join(' ')}\nCommands: state load|check-progress|update|patch|advance-plan|record-metric, config validate|load-defaults|save-defaults|resolve-depth, validate health, validate-project, verify summary|plan-structure|phase-completeness|artifacts|key-links|commits|references|spot-check, migrate [--dry-run] [--force], init execute-phase|plan-phase|quick|verify-work|resume|progress|map-codebase, state-bundle <phase>, plan-index, frontmatter, must-haves, phase-info, phase add|remove|list|complete, roadmap update-status|update-plans, history append|load, todo list|get|add|done, auto-cleanup --phase N|--milestone vN, event, llm health|status|classify|score-source|classify-error|summarize|metrics [--session <ISO>]|adjust-thresholds, learnings ingest|query|check-thresholds, incidents list|summary|query, nk record|list|resolve, data status|prune, graph build|query|impact|stats, hooks perf [--last N] [--json], spec parse|diff|reverse|impact, milestone-stats <version>, context-triage [--agents-done N] [--plans-total N] [--step NAME], ci-poll <run-id> [--timeout <seconds>], ci-fix [--dry-run] [--max-iterations N], rollback <manifest-path>, session get|set|clear|dump, claim acquire|release|list, skill-section <skill> <section>|--list <skill>, step-verify <skill> <step> <checklist-json>, suggest-alternatives phase-not-found|missing-prereq|config-invalid [args], tmux detect, quick init, generate-slug|slug-generate, parse-args plan|quick, status fingerprint, quick-status, help, skill-metadata <name>`);
2035
+ }
2036
+ } catch (e) {
2037
+ error(e.message);
1374
2038
  }
1375
2039
  }
1376
2040
 
1377
- if (require.main === module || process.argv[1] === __filename) { main(); }
1378
- module.exports = { parseStateMd, parseRoadmapMd, parseYamlFrontmatter, parseMustHaves, countMustHaves, stateLoad, stateCheckProgress, configLoad, configClearCache, configValidate, lockedFileUpdate, planIndex, determinePhaseStatus, findFiles, atomicWrite, tailLines, frontmatter, mustHavesCollect, phaseInfo, stateUpdate, roadmapUpdateStatus, roadmapUpdatePlans, updateLegacyStateField, updateFrontmatterField, updateTableRow, findRoadmapRow, resolveDepthProfile, DEPTH_PROFILE_DEFAULTS, historyAppend, historyLoad, VALID_STATUS_TRANSITIONS, validateStatusTransition };
2041
+ if (require.main === module || process.argv[1] === __filename) { main().catch(err => { process.stderr.write(err.message + '\n'); process.exit(1); }); }
2042
+ module.exports = { KNOWN_AGENTS, initExecutePhase, initPlanPhase, initQuick, initVerifyWork, initResume, initProgress, initStateBundle: stateBundle, stateBundle, statePatch, stateAdvancePlan, stateRecordMetric, stateRecordActivity, stateUpdateProgress, parseStateMd, parseRoadmapMd, parseYamlFrontmatter, parseMustHaves, countMustHaves, stateLoad, stateCheckProgress, configLoad, configClearCache, configValidate, lockedFileUpdate, planIndex, determinePhaseStatus, findFiles, atomicWrite, tailLines, frontmatter, mustHavesCollect, phaseInfo, stateUpdate, roadmapUpdateStatus, roadmapUpdatePlans, roadmapAnalyze, updateLegacyStateField, updateFrontmatterField, updateTableRow, findRoadmapRow, resolveDepthProfile, DEPTH_PROFILE_DEFAULTS, historyAppend, historyLoad, VALID_STATUS_TRANSITIONS, validateStatusTransition, writeActiveSkill, validateProject, phaseAdd, phaseRemove, phaseList, loadUserDefaults, saveUserDefaults, mergeUserDefaults, USER_DEFAULTS_PATH, todoList, todoGet, todoAdd, todoDone, migrate, spotCheck, referenceGet, milestoneStats, contextTriage, stalenessCheck, summaryGate, checkpointInit, checkpointUpdate, seedsMatch, ciPoll, ciFix, parseJestOutput: _parseJestOutput, parseLintOutput: _parseLintOutput, autoFixLint: _autoFixLint, runCiFixLoop: _runCiFixLoop, rollbackPlan, sessionLoad, sessionSave, SESSION_ALLOWED_KEYS, claimAcquire, claimRelease, claimList, skillSectionGet, listSkillHeadings, stepVerify: _stepVerify, phaseAlternatives: _phaseAlternatives, prerequisiteAlternatives: _prereqAlternatives, configAlternatives: _configAlternatives, phaseComplete, phaseInsert, quickStatus, autoCloseTodos, autoArchiveNotes, buildCleanupContext, incidentsList, incidentsQuery, incidentsSummary, helpListCmd, skillMetadataCmd, initMapCodebase };
2043
+ // NOTE: validateProject, phaseAdd, phaseRemove, phaseList were previously CLI-only (not exported).
2044
+ // They are now exported for testability. This is additive and backwards-compatible.