@sienklogic/plan-build-run 2.19.0 → 2.19.2

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 (951) hide show
  1. package/CHANGELOG.md +1287 -303
  2. package/CLAUDE.md +74 -39
  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 +151 -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 +234 -0
  16. package/dashboard/server/routes/config.js +64 -0
  17. package/dashboard/server/routes/health.js +98 -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 +233 -0
  29. package/dashboard/server/services/file-watcher.js +105 -0
  30. package/dashboard/server/services/planning-reader.js +727 -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 +1298 -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 +101 -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 +212 -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 +219 -36
  156. package/plugins/pbr/agents/roadmapper.md +397 -0
  157. package/plugins/pbr/agents/synthesizer.md +166 -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 +155 -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 +209 -0
  226. package/plugins/pbr/dist/check-read-first.js +345 -0
  227. package/plugins/pbr/dist/check-roadmap-sync.js +507 -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 +396 -0
  231. package/plugins/pbr/dist/check-summary-gate.js +188 -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 +155 -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 +658 -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 +519 -0
  251. package/plugins/pbr/dist/pbr-tools.js +1961 -0
  252. package/plugins/pbr/dist/post-bash-triage.js +96 -0
  253. package/plugins/pbr/dist/post-compact.js +135 -0
  254. package/plugins/pbr/dist/post-hoc.js +237 -0
  255. package/plugins/pbr/dist/post-write-dispatch.js +243 -0
  256. package/plugins/pbr/dist/post-write-quality.js +208 -0
  257. package/plugins/pbr/dist/pre-bash-dispatch.js +212 -0
  258. package/plugins/pbr/dist/pre-skill-dispatch.js +114 -0
  259. package/plugins/pbr/dist/pre-task-dispatch.js +269 -0
  260. package/plugins/pbr/dist/pre-write-dispatch.js +234 -0
  261. package/plugins/pbr/dist/progress-tracker.js +173 -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 +653 -0
  268. package/plugins/pbr/dist/session-tracker.js +124 -0
  269. package/plugins/pbr/dist/status-line.js +849 -0
  270. package/plugins/pbr/dist/suggest-compact.js +307 -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 +215 -0
  277. package/plugins/pbr/dist/validate-skill-args.js +222 -0
  278. package/plugins/pbr/dist/validate-task.js +271 -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/checkpoints.md +723 -104
  297. package/plugins/pbr/references/config-reference.md +376 -10
  298. package/plugins/pbr/references/continuation-format.md +1 -0
  299. package/plugins/pbr/references/decimal-phase-calculation.md +65 -0
  300. package/plugins/pbr/references/deviation-rules.md +12 -0
  301. package/plugins/pbr/references/git-integration.md +110 -27
  302. package/plugins/pbr/references/git-planning-commit.md +35 -0
  303. package/plugins/pbr/references/model-profile-resolution.md +34 -0
  304. package/plugins/pbr/references/model-profiles.md +90 -7
  305. package/plugins/pbr/references/model-selection.md +1 -1
  306. package/plugins/pbr/references/node-repair.md +48 -0
  307. package/plugins/pbr/references/plan-authoring.md +65 -0
  308. package/plugins/pbr/references/plan-format.md +161 -10
  309. package/plugins/pbr/references/questioning.md +138 -49
  310. package/plugins/pbr/references/reading-verification.md +4 -4
  311. package/plugins/pbr/references/tdd.md +263 -0
  312. package/plugins/pbr/references/ui-brand.md +449 -0
  313. package/plugins/pbr/references/verification-overrides.md +39 -0
  314. package/plugins/pbr/references/verification-patterns.md +529 -113
  315. package/plugins/pbr/scripts/architecture-guard.js +59 -0
  316. package/plugins/pbr/scripts/audit-checks/behavioral-compliance.js +2098 -0
  317. package/plugins/pbr/scripts/audit-checks/error-analysis.js +989 -0
  318. package/plugins/pbr/scripts/audit-checks/feature-verification.js +723 -0
  319. package/plugins/pbr/scripts/audit-checks/index.js +433 -0
  320. package/plugins/pbr/scripts/audit-checks/infrastructure.js +816 -0
  321. package/plugins/pbr/scripts/audit-checks/quality-metrics.js +452 -0
  322. package/plugins/pbr/scripts/audit-checks/session-quality.js +980 -0
  323. package/plugins/pbr/scripts/audit-checks/si-agent-hook-config-checks.js +396 -0
  324. package/plugins/pbr/scripts/audit-checks/si-cross-cutting-checks.js +272 -0
  325. package/plugins/pbr/scripts/audit-checks/si-skill-checks.js +424 -0
  326. package/plugins/pbr/scripts/audit-checks/workflow-compliance.js +1175 -0
  327. package/plugins/pbr/scripts/audit-dimensions.js +556 -0
  328. package/plugins/pbr/scripts/auto-continue.js +192 -31
  329. package/plugins/pbr/scripts/block-skill-self-read.js +124 -0
  330. package/plugins/pbr/scripts/check-agent-state-write.js +63 -0
  331. package/plugins/pbr/scripts/check-config-change.js +155 -0
  332. package/plugins/pbr/scripts/check-cross-plugin-sync.js +93 -0
  333. package/plugins/pbr/scripts/check-dangerous-commands.js +18 -5
  334. package/plugins/pbr/scripts/check-direct-state-write.js +37 -0
  335. package/plugins/pbr/scripts/check-phase-boundary.js +3 -8
  336. package/plugins/pbr/scripts/check-plan-format.js +135 -278
  337. package/plugins/pbr/scripts/check-read-first.js +345 -0
  338. package/plugins/pbr/scripts/check-roadmap-sync.js +182 -21
  339. package/plugins/pbr/scripts/check-skill-workflow.js +24 -27
  340. package/plugins/pbr/scripts/check-state-sync.js +339 -215
  341. package/plugins/pbr/scripts/check-subagent-output.js +281 -275
  342. package/plugins/pbr/scripts/check-summary-gate.js +5 -15
  343. package/plugins/pbr/scripts/config-schema.json +1134 -95
  344. package/plugins/pbr/scripts/context-bridge.js +425 -0
  345. package/plugins/pbr/scripts/context-budget-check.js +169 -14
  346. package/plugins/pbr/scripts/context-quality.js +271 -0
  347. package/plugins/pbr/scripts/enforce-context-budget.js +138 -0
  348. package/plugins/pbr/scripts/enforce-pbr-workflow.js +277 -0
  349. package/plugins/pbr/scripts/event-handler.js +127 -87
  350. package/plugins/pbr/scripts/event-logger.js +58 -25
  351. package/plugins/pbr/scripts/feedback-loop.js +155 -0
  352. package/plugins/pbr/scripts/graph-update.js +422 -0
  353. package/plugins/pbr/scripts/hook-logger.js +69 -35
  354. package/plugins/pbr/scripts/hook-server-client.js +361 -0
  355. package/plugins/pbr/scripts/hook-server.js +658 -0
  356. package/plugins/pbr/scripts/hooks-schema.json +13 -5
  357. package/plugins/pbr/scripts/instructions-loaded.js +173 -0
  358. package/plugins/pbr/scripts/intent-router.cjs +147 -0
  359. package/plugins/pbr/scripts/intercept-plan-mode.js +52 -18
  360. package/plugins/pbr/scripts/lib/alternatives.js +203 -0
  361. package/plugins/pbr/scripts/lib/audit.js +65 -0
  362. package/plugins/pbr/scripts/lib/auto-cleanup.js +221 -0
  363. package/plugins/pbr/scripts/lib/auto-verify.js +103 -0
  364. package/plugins/pbr/scripts/lib/build.js +719 -0
  365. package/plugins/pbr/scripts/lib/ci-fix-loop.js +228 -0
  366. package/plugins/pbr/scripts/lib/commands.js +483 -0
  367. package/plugins/pbr/scripts/lib/compound.js +216 -0
  368. package/plugins/pbr/scripts/lib/config.js +1308 -0
  369. package/plugins/pbr/scripts/lib/context.js +254 -0
  370. package/plugins/pbr/scripts/lib/contextual-help.js +183 -0
  371. package/plugins/pbr/scripts/lib/convention-detector.js +413 -0
  372. package/plugins/pbr/scripts/lib/core.js +1569 -0
  373. package/plugins/pbr/scripts/lib/dashboard-launch.js +364 -0
  374. package/plugins/pbr/scripts/lib/data-hygiene.js +179 -0
  375. package/plugins/pbr/scripts/lib/decision-extraction.js +183 -0
  376. package/plugins/pbr/scripts/lib/decisions.js +194 -0
  377. package/plugins/pbr/scripts/lib/dependency-break.js +147 -0
  378. package/plugins/pbr/scripts/lib/format-validators.js +1025 -0
  379. package/plugins/pbr/scripts/lib/frontmatter.js +302 -0
  380. package/plugins/pbr/scripts/lib/gates/advisories.js +129 -0
  381. package/plugins/pbr/scripts/lib/gates/build-dependency.js +115 -0
  382. package/plugins/pbr/scripts/lib/gates/build-executor.js +104 -0
  383. package/plugins/pbr/scripts/lib/gates/doc-existence.js +46 -0
  384. package/plugins/pbr/scripts/lib/gates/helpers.js +93 -0
  385. package/plugins/pbr/scripts/lib/gates/inline-execution.js +185 -0
  386. package/plugins/pbr/scripts/lib/gates/milestone-complete.js +136 -0
  387. package/plugins/pbr/scripts/lib/gates/milestone-summary.js +119 -0
  388. package/plugins/pbr/scripts/lib/gates/multi-phase-loader.js +147 -0
  389. package/plugins/pbr/scripts/lib/gates/plan-executor.js +36 -0
  390. package/plugins/pbr/scripts/lib/gates/plan-validation.js +114 -0
  391. package/plugins/pbr/scripts/lib/gates/quick-executor.js +76 -0
  392. package/plugins/pbr/scripts/lib/gates/review-planner.js +61 -0
  393. package/plugins/pbr/scripts/lib/gates/review-verifier.js +69 -0
  394. package/plugins/pbr/scripts/lib/gates/rich-agent-context.js +137 -0
  395. package/plugins/pbr/scripts/lib/gates/user-confirmation.js +93 -0
  396. package/plugins/pbr/scripts/lib/graph-cli.js +89 -0
  397. package/plugins/pbr/scripts/lib/graph.js +553 -0
  398. package/plugins/pbr/scripts/lib/health-checks.js +107 -0
  399. package/plugins/pbr/scripts/lib/health-phase06.js +120 -0
  400. package/plugins/pbr/scripts/lib/health.js +132 -0
  401. package/plugins/pbr/scripts/lib/help.js +100 -0
  402. package/plugins/pbr/scripts/lib/history.js +150 -0
  403. package/plugins/pbr/scripts/lib/impact-analysis.js +319 -0
  404. package/plugins/pbr/scripts/lib/incidents.js +190 -0
  405. package/plugins/pbr/scripts/lib/init.js +643 -0
  406. package/plugins/pbr/scripts/lib/insights-parser.js +320 -0
  407. package/plugins/pbr/scripts/lib/intel.js +653 -0
  408. package/plugins/pbr/scripts/lib/learnings.js +511 -0
  409. package/plugins/pbr/scripts/lib/migrate.js +298 -0
  410. package/plugins/pbr/scripts/lib/milestone.js +306 -0
  411. package/plugins/pbr/scripts/lib/negative-knowledge.js +194 -0
  412. package/plugins/pbr/scripts/lib/notification-throttle.js +141 -0
  413. package/plugins/pbr/scripts/lib/onboarding-generator.js +288 -0
  414. package/plugins/pbr/scripts/lib/parse-args.js +134 -0
  415. package/plugins/pbr/scripts/lib/pattern-routing.js +55 -0
  416. package/plugins/pbr/scripts/lib/patterns.js +272 -0
  417. package/plugins/pbr/scripts/lib/perf.js +190 -0
  418. package/plugins/pbr/scripts/lib/phase.js +1025 -0
  419. package/plugins/pbr/scripts/lib/pid-lock.js +154 -0
  420. package/plugins/pbr/scripts/lib/post-hoc.js +160 -0
  421. package/plugins/pbr/scripts/lib/pre-commit-checks.js +220 -0
  422. package/plugins/pbr/scripts/lib/pre-research.js +126 -0
  423. package/plugins/pbr/scripts/lib/preview.js +174 -0
  424. package/plugins/pbr/scripts/lib/progress-visualization.js +296 -0
  425. package/plugins/pbr/scripts/lib/quick-init.js +131 -0
  426. package/plugins/pbr/scripts/lib/reference.js +236 -0
  427. package/plugins/pbr/scripts/lib/requirements.js +153 -0
  428. package/plugins/pbr/scripts/lib/resolve-root.js +66 -0
  429. package/plugins/pbr/scripts/lib/reverse-spec.js +259 -0
  430. package/plugins/pbr/scripts/lib/roadmap.js +1089 -0
  431. package/plugins/pbr/scripts/lib/security-scan.js +200 -0
  432. package/plugins/pbr/scripts/lib/session-briefing.js +895 -0
  433. package/plugins/pbr/scripts/lib/skill-section.js +99 -0
  434. package/plugins/pbr/scripts/lib/smart-next-task.js +198 -0
  435. package/plugins/pbr/scripts/lib/snapshot-manager.js +232 -0
  436. package/plugins/pbr/scripts/lib/spec-diff.js +209 -0
  437. package/plugins/pbr/scripts/lib/spec-engine.js +189 -0
  438. package/plugins/pbr/scripts/lib/spot-check.js +539 -0
  439. package/plugins/pbr/scripts/lib/state-queue.js +171 -0
  440. package/plugins/pbr/scripts/lib/state.js +1082 -0
  441. package/plugins/pbr/scripts/lib/status-render.js +511 -0
  442. package/plugins/pbr/scripts/lib/step-verify.js +149 -0
  443. package/plugins/pbr/scripts/lib/subagent-validators.js +1059 -0
  444. package/plugins/pbr/scripts/lib/suggest-next.js +435 -0
  445. package/plugins/pbr/scripts/lib/tech-debt-scanner.js +116 -0
  446. package/plugins/pbr/scripts/lib/templates.js +362 -0
  447. package/plugins/pbr/scripts/lib/test-selection.js +163 -0
  448. package/plugins/pbr/scripts/lib/todo.js +300 -0
  449. package/plugins/pbr/scripts/lib/verify.js +1483 -0
  450. package/plugins/pbr/scripts/log-notification.js +131 -0
  451. package/plugins/pbr/scripts/log-subagent.js +203 -18
  452. package/plugins/pbr/scripts/log-tool-failure.js +60 -8
  453. package/plugins/pbr/scripts/milestone-learnings.js +519 -0
  454. package/plugins/pbr/scripts/package.json +1 -1
  455. package/plugins/pbr/scripts/pbr-tools.js +1754 -1171
  456. package/plugins/pbr/scripts/post-bash-triage.js +96 -0
  457. package/plugins/pbr/scripts/post-compact.js +135 -0
  458. package/plugins/pbr/scripts/post-hoc.js +237 -0
  459. package/plugins/pbr/scripts/post-write-dispatch.js +201 -31
  460. package/plugins/pbr/scripts/post-write-quality.js +4 -3
  461. package/plugins/pbr/scripts/pre-bash-dispatch.js +147 -51
  462. package/plugins/pbr/scripts/pre-skill-dispatch.js +114 -0
  463. package/plugins/pbr/scripts/pre-task-dispatch.js +269 -0
  464. package/plugins/pbr/scripts/pre-write-dispatch.js +170 -73
  465. package/plugins/pbr/scripts/progress-tracker.js +122 -310
  466. package/plugins/pbr/scripts/prompt-guard.js +114 -0
  467. package/plugins/pbr/scripts/prompt-routing.js +209 -0
  468. package/plugins/pbr/scripts/quick-status.js +179 -0
  469. package/plugins/pbr/scripts/record-incident.js +37 -0
  470. package/plugins/pbr/scripts/risk-classifier.cjs +123 -0
  471. package/plugins/pbr/scripts/run-hook.js +62 -10
  472. package/plugins/pbr/scripts/session-cleanup.js +428 -29
  473. package/plugins/pbr/scripts/session-tracker.js +124 -0
  474. package/plugins/pbr/scripts/status-line.js +593 -32
  475. package/plugins/pbr/scripts/suggest-compact.js +201 -13
  476. package/plugins/pbr/scripts/sync-context-to-claude.js +100 -0
  477. package/plugins/pbr/scripts/task-completed.js +165 -4
  478. package/plugins/pbr/scripts/test/config.test.js +126 -0
  479. package/plugins/pbr/scripts/test/cross-platform.test.js +131 -0
  480. package/plugins/pbr/scripts/test/fixtures/config.json +20 -0
  481. package/plugins/pbr/scripts/test/fixtures/plan.md +54 -0
  482. package/plugins/pbr/scripts/test/fixtures/project.md +30 -0
  483. package/plugins/pbr/scripts/test/fixtures/roadmap.md +55 -0
  484. package/plugins/pbr/scripts/test/fixtures/state.md +60 -0
  485. package/plugins/pbr/scripts/test/fixtures/summary.md +35 -0
  486. package/plugins/pbr/scripts/test/fixtures.test.js +184 -0
  487. package/plugins/pbr/scripts/test/phase.test.js +142 -0
  488. package/plugins/pbr/scripts/test/roadmap.test.js +96 -0
  489. package/plugins/pbr/scripts/test/state.test.js +155 -0
  490. package/plugins/pbr/scripts/track-context-budget.js +368 -99
  491. package/plugins/pbr/scripts/track-user-gates.js +88 -0
  492. package/plugins/pbr/scripts/trust-tracker.js +193 -0
  493. package/plugins/pbr/scripts/validate-commit.js +41 -26
  494. package/plugins/pbr/scripts/validate-skill-args.js +87 -15
  495. package/plugins/pbr/scripts/validate-task.js +83 -627
  496. package/plugins/pbr/scripts/worktree-create.js +144 -0
  497. package/plugins/pbr/scripts/worktree-remove.js +147 -0
  498. package/plugins/pbr/skills/audit/SKILL.md +195 -24
  499. package/plugins/pbr/skills/audit-fix/SKILL.md +326 -0
  500. package/plugins/pbr/skills/autonomous/SKILL.md +545 -0
  501. package/plugins/pbr/skills/backlog/SKILL.md +56 -0
  502. package/plugins/pbr/skills/begin/SKILL.md +508 -153
  503. package/plugins/pbr/skills/begin/templates/STATE.md.tmpl +1 -2
  504. package/plugins/pbr/skills/begin/templates/config.json.tmpl +411 -36
  505. package/plugins/pbr/skills/begin/templates/researcher-prompt.md.tmpl +28 -0
  506. package/plugins/pbr/skills/begin/templates/roadmap-prompt.md.tmpl +28 -3
  507. package/plugins/pbr/skills/begin/templates/synthesis-prompt.md.tmpl +33 -5
  508. package/plugins/pbr/skills/build/SKILL.md +1040 -354
  509. package/plugins/pbr/skills/build/templates/continuation-prompt.md.tmpl +26 -0
  510. package/plugins/pbr/skills/build/templates/executor-prompt.md.tmpl +77 -0
  511. package/plugins/pbr/skills/build/templates/inline-verifier-prompt.md.tmpl +33 -0
  512. package/plugins/pbr/skills/config/SKILL.md +112 -9
  513. package/plugins/pbr/skills/continue/SKILL.md +113 -33
  514. package/plugins/pbr/skills/dashboard/SKILL.md +21 -9
  515. package/plugins/pbr/skills/debug/SKILL.md +70 -12
  516. package/plugins/pbr/skills/debug/templates/continuation-prompt.md.tmpl +12 -1
  517. package/plugins/pbr/skills/debug/templates/initial-investigation-prompt.md.tmpl +12 -5
  518. package/plugins/pbr/skills/discuss/SKILL.md +206 -25
  519. package/plugins/pbr/skills/discuss/templates/CONTEXT.md.tmpl +21 -1
  520. package/plugins/pbr/skills/do/SKILL.md +119 -24
  521. package/plugins/pbr/skills/explore/SKILL.md +95 -20
  522. package/plugins/pbr/skills/fast/SKILL.md +94 -0
  523. package/plugins/pbr/skills/forensics/SKILL.md +144 -0
  524. package/plugins/pbr/skills/health/SKILL.md +35 -117
  525. package/plugins/pbr/skills/help/SKILL.md +84 -101
  526. package/plugins/pbr/skills/import/SKILL.md +332 -13
  527. package/plugins/pbr/skills/intel/SKILL.md +131 -0
  528. package/plugins/pbr/skills/list-phase-assumptions/SKILL.md +231 -0
  529. package/plugins/pbr/skills/milestone/SKILL.md +421 -263
  530. package/plugins/pbr/skills/milestone/templates/audit-output.md.tmpl +76 -0
  531. package/plugins/pbr/skills/milestone/templates/complete-output.md.tmpl +32 -0
  532. package/plugins/pbr/skills/milestone/templates/edge-cases.md +54 -0
  533. package/plugins/pbr/skills/milestone/templates/gaps-output.md.tmpl +25 -0
  534. package/plugins/pbr/skills/milestone/templates/integration-checker-prompt.md.tmpl +25 -0
  535. package/plugins/pbr/skills/milestone/templates/new-output.md.tmpl +29 -0
  536. package/plugins/pbr/skills/milestone-summary/SKILL.md +86 -0
  537. package/plugins/pbr/skills/note/SKILL.md +20 -4
  538. package/plugins/pbr/skills/pause/SKILL.md +54 -14
  539. package/plugins/pbr/skills/pause/templates/continue-here.md.tmpl +33 -52
  540. package/plugins/pbr/skills/plan/SKILL.md +526 -280
  541. package/plugins/pbr/skills/plan/templates/checker-prompt.md.tmpl +5 -2
  542. package/plugins/pbr/skills/plan/templates/completion-output.md.tmpl +27 -0
  543. package/plugins/pbr/skills/plan/templates/planner-prompt.md.tmpl +27 -1
  544. package/plugins/pbr/skills/plan/templates/revision-prompt.md.tmpl +21 -5
  545. package/plugins/pbr/skills/profile/SKILL.md +185 -0
  546. package/plugins/pbr/skills/profile-user/SKILL.md +227 -0
  547. package/plugins/pbr/skills/quick/SKILL.md +435 -100
  548. package/plugins/pbr/skills/release/SKILL.md +206 -0
  549. package/plugins/pbr/skills/resume/SKILL.md +170 -46
  550. package/plugins/pbr/skills/review/SKILL.md +217 -164
  551. package/plugins/pbr/skills/review/templates/verifier-prompt.md.tmpl +7 -0
  552. package/plugins/pbr/skills/scan/SKILL.md +152 -106
  553. package/plugins/pbr/skills/scan/templates/mapper-prompt.md.tmpl +5 -56
  554. package/plugins/pbr/skills/seed/SKILL.md +87 -0
  555. package/plugins/pbr/skills/session-report/SKILL.md +130 -0
  556. package/plugins/pbr/skills/setup/SKILL.md +150 -202
  557. package/plugins/pbr/skills/shared/agent-context-enrichment.md +21 -0
  558. package/plugins/pbr/skills/shared/agent-type-resolution.md +32 -0
  559. package/plugins/pbr/skills/shared/commit-planning-docs.md +8 -0
  560. package/plugins/pbr/skills/shared/context-budget.md +66 -1
  561. package/plugins/pbr/skills/shared/context-loader-task.md +18 -11
  562. package/plugins/pbr/skills/shared/digest-select.md +2 -2
  563. package/plugins/pbr/skills/shared/domain-probes.md +1 -1
  564. package/plugins/pbr/skills/shared/error-reporting.md +38 -60
  565. package/plugins/pbr/skills/shared/gate-prompts.md +4 -2
  566. package/plugins/pbr/skills/shared/memory-capture.md +48 -0
  567. package/plugins/pbr/skills/shared/phase-argument-parsing.md +4 -4
  568. package/plugins/pbr/skills/shared/revision-loop.md +24 -6
  569. package/plugins/pbr/skills/shared/state-update.md +49 -56
  570. package/plugins/pbr/skills/shared/universal-anti-patterns.md +27 -4
  571. package/plugins/pbr/skills/ship/SKILL.md +155 -0
  572. package/plugins/pbr/skills/stats/SKILL.md +80 -0
  573. package/plugins/pbr/skills/status/SKILL.md +185 -119
  574. package/plugins/pbr/skills/test/SKILL.md +254 -0
  575. package/plugins/pbr/skills/thread/SKILL.md +73 -0
  576. package/plugins/pbr/skills/todo/SKILL.md +28 -72
  577. package/plugins/pbr/skills/ui-phase/SKILL.md +180 -0
  578. package/plugins/pbr/skills/ui-review/SKILL.md +206 -0
  579. package/plugins/pbr/skills/undo/SKILL.md +221 -0
  580. package/plugins/pbr/skills/validate-phase/SKILL.md +362 -0
  581. package/plugins/pbr/templates/CONTEXT.md.tmpl +45 -20
  582. package/plugins/pbr/templates/DISCOVERY.md.tmpl +29 -0
  583. package/plugins/pbr/templates/DISCUSSION-LOG.md.tmpl +49 -0
  584. package/plugins/pbr/templates/HANDOFF.json.tmpl +30 -0
  585. package/plugins/pbr/templates/INTEGRATION-REPORT.md.tmpl +18 -2
  586. package/plugins/pbr/templates/MILESTONE-AUDIT.md.tmpl +44 -0
  587. package/plugins/pbr/templates/PROJECT.md.tmpl +126 -0
  588. package/plugins/pbr/templates/REQUIREMENTS.md.tmpl +96 -0
  589. package/plugins/pbr/templates/RETROSPECTIVE.md.tmpl +43 -0
  590. package/plugins/pbr/templates/ROADMAP.md.tmpl +108 -14
  591. package/plugins/pbr/templates/SUMMARY-complex.md.tmpl +133 -0
  592. package/plugins/pbr/templates/SUMMARY-minimal.md.tmpl +55 -0
  593. package/plugins/pbr/templates/SUMMARY.md.tmpl +21 -0
  594. package/plugins/pbr/templates/UAT.md.tmpl +94 -0
  595. package/plugins/pbr/templates/UI-SPEC.md.tmpl +144 -0
  596. package/plugins/pbr/templates/VALIDATION.md.tmpl +94 -0
  597. package/plugins/pbr/templates/VERIFICATION-DETAIL.md.tmpl +49 -13
  598. package/plugins/pbr/templates/project-CONTEXT.md.tmpl +59 -0
  599. package/plugins/pbr/templates/research-outputs/ARCHITECTURE.md.tmpl +91 -0
  600. package/plugins/pbr/templates/research-outputs/FEATURES.md.tmpl +64 -0
  601. package/plugins/pbr/templates/research-outputs/PITFALLS.md.tmpl +50 -0
  602. package/plugins/pbr/templates/research-outputs/STACK.md.tmpl +63 -0
  603. package/plugins/pbr/templates/research-outputs/SUMMARY.md.tmpl +98 -0
  604. package/scripts/build-hooks.js +61 -0
  605. package/scripts/check-ci.js +100 -0
  606. package/scripts/clean-changelog.js +364 -0
  607. package/scripts/generate-derivatives.js +581 -0
  608. package/scripts/posttest.js +93 -0
  609. package/scripts/release.js +262 -0
  610. package/scripts/run-tests.cjs +29 -0
  611. package/scripts/test-wrapper.js +43 -0
  612. package/dashboard/bin/cli.js +0 -25
  613. package/dashboard/public/css/layout.css +0 -704
  614. package/dashboard/public/css/status-colors.css +0 -98
  615. package/dashboard/public/css/tokens.css +0 -59
  616. package/dashboard/public/js/htmx-title.js +0 -5
  617. package/dashboard/public/js/sidebar-toggle.js +0 -34
  618. package/dashboard/public/js/sse-client.js +0 -100
  619. package/dashboard/public/js/theme-toggle.js +0 -46
  620. package/dashboard/src/app.js +0 -91
  621. package/dashboard/src/middleware/current-phase.js +0 -24
  622. package/dashboard/src/middleware/errorHandler.js +0 -52
  623. package/dashboard/src/middleware/notFoundHandler.js +0 -9
  624. package/dashboard/src/repositories/planning.repository.js +0 -130
  625. package/dashboard/src/routes/events.routes.js +0 -45
  626. package/dashboard/src/routes/index.routes.js +0 -35
  627. package/dashboard/src/routes/pages.routes.js +0 -426
  628. package/dashboard/src/server.js +0 -42
  629. package/dashboard/src/services/analytics.service.js +0 -141
  630. package/dashboard/src/services/dashboard.service.js +0 -309
  631. package/dashboard/src/services/milestone.service.js +0 -222
  632. package/dashboard/src/services/notes.service.js +0 -50
  633. package/dashboard/src/services/phase.service.js +0 -232
  634. package/dashboard/src/services/project.service.js +0 -57
  635. package/dashboard/src/services/roadmap.service.js +0 -258
  636. package/dashboard/src/services/sse.service.js +0 -58
  637. package/dashboard/src/services/todo.service.js +0 -272
  638. package/dashboard/src/services/watcher.service.js +0 -48
  639. package/dashboard/src/utils/cache.js +0 -55
  640. package/dashboard/src/views/analytics.ejs +0 -5
  641. package/dashboard/src/views/coming-soon.ejs +0 -11
  642. package/dashboard/src/views/dependencies.ejs +0 -5
  643. package/dashboard/src/views/error.ejs +0 -20
  644. package/dashboard/src/views/index.ejs +0 -5
  645. package/dashboard/src/views/milestone-detail.ejs +0 -5
  646. package/dashboard/src/views/milestones.ejs +0 -5
  647. package/dashboard/src/views/notes.ejs +0 -5
  648. package/dashboard/src/views/partials/analytics-content.ejs +0 -90
  649. package/dashboard/src/views/partials/breadcrumbs.ejs +0 -14
  650. package/dashboard/src/views/partials/dashboard-content.ejs +0 -84
  651. package/dashboard/src/views/partials/dependencies-content.ejs +0 -48
  652. package/dashboard/src/views/partials/empty-state.ejs +0 -7
  653. package/dashboard/src/views/partials/footer.ejs +0 -3
  654. package/dashboard/src/views/partials/head.ejs +0 -30
  655. package/dashboard/src/views/partials/header.ejs +0 -21
  656. package/dashboard/src/views/partials/layout-bottom.ejs +0 -43
  657. package/dashboard/src/views/partials/layout-top.ejs +0 -16
  658. package/dashboard/src/views/partials/milestone-detail-content.ejs +0 -20
  659. package/dashboard/src/views/partials/milestones-content.ejs +0 -88
  660. package/dashboard/src/views/partials/notes-content.ejs +0 -23
  661. package/dashboard/src/views/partials/phase-content.ejs +0 -193
  662. package/dashboard/src/views/partials/phase-doc-content.ejs +0 -38
  663. package/dashboard/src/views/partials/phases-content.ejs +0 -124
  664. package/dashboard/src/views/partials/roadmap-content.ejs +0 -180
  665. package/dashboard/src/views/partials/sidebar.ejs +0 -99
  666. package/dashboard/src/views/partials/todo-create-content.ejs +0 -54
  667. package/dashboard/src/views/partials/todo-detail-content.ejs +0 -42
  668. package/dashboard/src/views/partials/todos-content.ejs +0 -97
  669. package/dashboard/src/views/phase-detail.ejs +0 -5
  670. package/dashboard/src/views/phase-doc.ejs +0 -5
  671. package/dashboard/src/views/phases.ejs +0 -5
  672. package/dashboard/src/views/roadmap.ejs +0 -5
  673. package/dashboard/src/views/todo-create.ejs +0 -5
  674. package/dashboard/src/views/todo-detail.ejs +0 -5
  675. package/dashboard/src/views/todos.ejs +0 -5
  676. package/plugins/copilot-pbr/CHANGELOG.md +0 -19
  677. package/plugins/copilot-pbr/README.md +0 -139
  678. package/plugins/copilot-pbr/agents/audit.agent.md +0 -113
  679. package/plugins/copilot-pbr/agents/codebase-mapper.agent.md +0 -151
  680. package/plugins/copilot-pbr/agents/debugger.agent.md +0 -182
  681. package/plugins/copilot-pbr/agents/executor.agent.md +0 -267
  682. package/plugins/copilot-pbr/agents/general.agent.md +0 -88
  683. package/plugins/copilot-pbr/agents/integration-checker.agent.md +0 -119
  684. package/plugins/copilot-pbr/agents/plan-checker.agent.md +0 -208
  685. package/plugins/copilot-pbr/agents/planner.agent.md +0 -238
  686. package/plugins/copilot-pbr/agents/researcher.agent.md +0 -186
  687. package/plugins/copilot-pbr/agents/synthesizer.agent.md +0 -126
  688. package/plugins/copilot-pbr/agents/verifier.agent.md +0 -228
  689. package/plugins/copilot-pbr/hooks/hooks.json +0 -156
  690. package/plugins/copilot-pbr/plugin.json +0 -30
  691. package/plugins/copilot-pbr/references/agent-anti-patterns.md +0 -25
  692. package/plugins/copilot-pbr/references/agent-contracts.md +0 -297
  693. package/plugins/copilot-pbr/references/agent-interactions.md +0 -135
  694. package/plugins/copilot-pbr/references/agent-teams.md +0 -55
  695. package/plugins/copilot-pbr/references/checkpoints.md +0 -158
  696. package/plugins/copilot-pbr/references/common-bug-patterns.md +0 -14
  697. package/plugins/copilot-pbr/references/config-reference.md +0 -442
  698. package/plugins/copilot-pbr/references/continuation-format.md +0 -213
  699. package/plugins/copilot-pbr/references/deviation-rules.md +0 -113
  700. package/plugins/copilot-pbr/references/git-integration.md +0 -227
  701. package/plugins/copilot-pbr/references/integration-patterns.md +0 -118
  702. package/plugins/copilot-pbr/references/model-profiles.md +0 -100
  703. package/plugins/copilot-pbr/references/model-selection.md +0 -32
  704. package/plugins/copilot-pbr/references/pbr-rules.md +0 -195
  705. package/plugins/copilot-pbr/references/pbr-tools-cli.md +0 -285
  706. package/plugins/copilot-pbr/references/plan-authoring.md +0 -182
  707. package/plugins/copilot-pbr/references/plan-format.md +0 -288
  708. package/plugins/copilot-pbr/references/planning-config.md +0 -214
  709. package/plugins/copilot-pbr/references/questioning.md +0 -215
  710. package/plugins/copilot-pbr/references/reading-verification.md +0 -128
  711. package/plugins/copilot-pbr/references/stub-patterns.md +0 -161
  712. package/plugins/copilot-pbr/references/subagent-coordination.md +0 -120
  713. package/plugins/copilot-pbr/references/ui-formatting.md +0 -444
  714. package/plugins/copilot-pbr/references/verification-patterns.md +0 -199
  715. package/plugins/copilot-pbr/references/wave-execution.md +0 -96
  716. package/plugins/copilot-pbr/rules/pbr-workflow.mdc +0 -48
  717. package/plugins/copilot-pbr/setup.ps1 +0 -93
  718. package/plugins/copilot-pbr/setup.sh +0 -93
  719. package/plugins/copilot-pbr/skills/audit/SKILL.md +0 -330
  720. package/plugins/copilot-pbr/skills/begin/SKILL.md +0 -589
  721. package/plugins/copilot-pbr/skills/begin/templates/PROJECT.md.tmpl +0 -34
  722. package/plugins/copilot-pbr/skills/begin/templates/REQUIREMENTS.md.tmpl +0 -19
  723. package/plugins/copilot-pbr/skills/begin/templates/STATE.md.tmpl +0 -50
  724. package/plugins/copilot-pbr/skills/begin/templates/config.json.tmpl +0 -64
  725. package/plugins/copilot-pbr/skills/begin/templates/researcher-prompt.md.tmpl +0 -20
  726. package/plugins/copilot-pbr/skills/begin/templates/roadmap-prompt.md.tmpl +0 -31
  727. package/plugins/copilot-pbr/skills/begin/templates/synthesis-prompt.md.tmpl +0 -17
  728. package/plugins/copilot-pbr/skills/build/SKILL.md +0 -960
  729. package/plugins/copilot-pbr/skills/config/SKILL.md +0 -250
  730. package/plugins/copilot-pbr/skills/continue/SKILL.md +0 -159
  731. package/plugins/copilot-pbr/skills/dashboard/SKILL.md +0 -43
  732. package/plugins/copilot-pbr/skills/debug/SKILL.md +0 -508
  733. package/plugins/copilot-pbr/skills/debug/templates/continuation-prompt.md.tmpl +0 -17
  734. package/plugins/copilot-pbr/skills/debug/templates/initial-investigation-prompt.md.tmpl +0 -28
  735. package/plugins/copilot-pbr/skills/discuss/SKILL.md +0 -353
  736. package/plugins/copilot-pbr/skills/discuss/templates/CONTEXT.md.tmpl +0 -62
  737. package/plugins/copilot-pbr/skills/discuss/templates/decision-categories.md +0 -10
  738. package/plugins/copilot-pbr/skills/do/SKILL.md +0 -66
  739. package/plugins/copilot-pbr/skills/explore/SKILL.md +0 -373
  740. package/plugins/copilot-pbr/skills/health/SKILL.md +0 -283
  741. package/plugins/copilot-pbr/skills/health/templates/check-pattern.md.tmpl +0 -31
  742. package/plugins/copilot-pbr/skills/health/templates/output-format.md.tmpl +0 -64
  743. package/plugins/copilot-pbr/skills/help/SKILL.md +0 -170
  744. package/plugins/copilot-pbr/skills/import/SKILL.md +0 -502
  745. package/plugins/copilot-pbr/skills/milestone/SKILL.md +0 -745
  746. package/plugins/copilot-pbr/skills/milestone/templates/audit-report.md.tmpl +0 -49
  747. package/plugins/copilot-pbr/skills/milestone/templates/stats-file.md.tmpl +0 -31
  748. package/plugins/copilot-pbr/skills/note/SKILL.md +0 -213
  749. package/plugins/copilot-pbr/skills/pause/SKILL.md +0 -247
  750. package/plugins/copilot-pbr/skills/pause/templates/continue-here.md.tmpl +0 -72
  751. package/plugins/copilot-pbr/skills/plan/SKILL.md +0 -662
  752. package/plugins/copilot-pbr/skills/plan/templates/checker-prompt.md.tmpl +0 -22
  753. package/plugins/copilot-pbr/skills/plan/templates/gap-closure-prompt.md.tmpl +0 -33
  754. package/plugins/copilot-pbr/skills/plan/templates/planner-prompt.md.tmpl +0 -39
  755. package/plugins/copilot-pbr/skills/plan/templates/researcher-prompt.md.tmpl +0 -20
  756. package/plugins/copilot-pbr/skills/plan/templates/revision-prompt.md.tmpl +0 -24
  757. package/plugins/copilot-pbr/skills/quick/SKILL.md +0 -376
  758. package/plugins/copilot-pbr/skills/resume/SKILL.md +0 -399
  759. package/plugins/copilot-pbr/skills/review/SKILL.md +0 -653
  760. package/plugins/copilot-pbr/skills/review/templates/debugger-prompt.md.tmpl +0 -61
  761. package/plugins/copilot-pbr/skills/review/templates/gap-planner-prompt.md.tmpl +0 -41
  762. package/plugins/copilot-pbr/skills/review/templates/verifier-prompt.md.tmpl +0 -116
  763. package/plugins/copilot-pbr/skills/scan/SKILL.md +0 -299
  764. package/plugins/copilot-pbr/skills/scan/templates/mapper-prompt.md.tmpl +0 -202
  765. package/plugins/copilot-pbr/skills/setup/SKILL.md +0 -296
  766. package/plugins/copilot-pbr/skills/shared/commit-planning-docs.md +0 -36
  767. package/plugins/copilot-pbr/skills/shared/config-loading.md +0 -103
  768. package/plugins/copilot-pbr/skills/shared/context-budget.md +0 -41
  769. package/plugins/copilot-pbr/skills/shared/context-loader-task.md +0 -87
  770. package/plugins/copilot-pbr/skills/shared/digest-select.md +0 -80
  771. package/plugins/copilot-pbr/skills/shared/domain-probes.md +0 -126
  772. package/plugins/copilot-pbr/skills/shared/error-reporting.md +0 -81
  773. package/plugins/copilot-pbr/skills/shared/gate-prompts.md +0 -389
  774. package/plugins/copilot-pbr/skills/shared/phase-argument-parsing.md +0 -46
  775. package/plugins/copilot-pbr/skills/shared/progress-display.md +0 -53
  776. package/plugins/copilot-pbr/skills/shared/revision-loop.md +0 -82
  777. package/plugins/copilot-pbr/skills/shared/state-loading.md +0 -63
  778. package/plugins/copilot-pbr/skills/shared/state-update.md +0 -162
  779. package/plugins/copilot-pbr/skills/shared/universal-anti-patterns.md +0 -38
  780. package/plugins/copilot-pbr/skills/status/SKILL.md +0 -362
  781. package/plugins/copilot-pbr/skills/statusline/SKILL.md +0 -149
  782. package/plugins/copilot-pbr/skills/todo/SKILL.md +0 -279
  783. package/plugins/copilot-pbr/templates/CONTEXT.md.tmpl +0 -53
  784. package/plugins/copilot-pbr/templates/INTEGRATION-REPORT.md.tmpl +0 -152
  785. package/plugins/copilot-pbr/templates/RESEARCH-SUMMARY.md.tmpl +0 -98
  786. package/plugins/copilot-pbr/templates/ROADMAP.md.tmpl +0 -41
  787. package/plugins/copilot-pbr/templates/SUMMARY.md.tmpl +0 -82
  788. package/plugins/copilot-pbr/templates/VERIFICATION-DETAIL.md.tmpl +0 -117
  789. package/plugins/copilot-pbr/templates/codebase/ARCHITECTURE.md.tmpl +0 -98
  790. package/plugins/copilot-pbr/templates/codebase/CONCERNS.md.tmpl +0 -93
  791. package/plugins/copilot-pbr/templates/codebase/CONVENTIONS.md.tmpl +0 -104
  792. package/plugins/copilot-pbr/templates/codebase/INTEGRATIONS.md.tmpl +0 -78
  793. package/plugins/copilot-pbr/templates/codebase/STACK.md.tmpl +0 -78
  794. package/plugins/copilot-pbr/templates/codebase/STRUCTURE.md.tmpl +0 -80
  795. package/plugins/copilot-pbr/templates/codebase/TESTING.md.tmpl +0 -107
  796. package/plugins/copilot-pbr/templates/continue-here.md.tmpl +0 -74
  797. package/plugins/copilot-pbr/templates/prompt-partials/phase-project-context.md.tmpl +0 -38
  798. package/plugins/copilot-pbr/templates/research/ARCHITECTURE.md.tmpl +0 -124
  799. package/plugins/copilot-pbr/templates/research/STACK.md.tmpl +0 -71
  800. package/plugins/copilot-pbr/templates/research/SUMMARY.md.tmpl +0 -112
  801. package/plugins/copilot-pbr/templates/research-outputs/phase-research.md.tmpl +0 -81
  802. package/plugins/copilot-pbr/templates/research-outputs/project-research.md.tmpl +0 -99
  803. package/plugins/copilot-pbr/templates/research-outputs/synthesis.md.tmpl +0 -36
  804. package/plugins/cursor-pbr/.cursor-plugin/plugin.json +0 -32
  805. package/plugins/cursor-pbr/CHANGELOG.md +0 -15
  806. package/plugins/cursor-pbr/README.md +0 -123
  807. package/plugins/cursor-pbr/agents/audit.md +0 -178
  808. package/plugins/cursor-pbr/agents/codebase-mapper.md +0 -150
  809. package/plugins/cursor-pbr/agents/debugger.md +0 -181
  810. package/plugins/cursor-pbr/agents/executor.md +0 -266
  811. package/plugins/cursor-pbr/agents/general.md +0 -87
  812. package/plugins/cursor-pbr/agents/integration-checker.md +0 -118
  813. package/plugins/cursor-pbr/agents/plan-checker.md +0 -207
  814. package/plugins/cursor-pbr/agents/planner.md +0 -237
  815. package/plugins/cursor-pbr/agents/researcher.md +0 -185
  816. package/plugins/cursor-pbr/agents/synthesizer.md +0 -125
  817. package/plugins/cursor-pbr/agents/verifier.md +0 -227
  818. package/plugins/cursor-pbr/assets/.gitkeep +0 -0
  819. package/plugins/cursor-pbr/assets/logo.svg +0 -21
  820. package/plugins/cursor-pbr/hooks/hooks.json +0 -213
  821. package/plugins/cursor-pbr/references/agent-anti-patterns.md +0 -25
  822. package/plugins/cursor-pbr/references/agent-contracts.md +0 -297
  823. package/plugins/cursor-pbr/references/agent-interactions.md +0 -135
  824. package/plugins/cursor-pbr/references/agent-teams.md +0 -55
  825. package/plugins/cursor-pbr/references/checkpoints.md +0 -158
  826. package/plugins/cursor-pbr/references/common-bug-patterns.md +0 -14
  827. package/plugins/cursor-pbr/references/config-reference.md +0 -442
  828. package/plugins/cursor-pbr/references/continuation-format.md +0 -213
  829. package/plugins/cursor-pbr/references/deviation-rules.md +0 -113
  830. package/plugins/cursor-pbr/references/git-integration.md +0 -227
  831. package/plugins/cursor-pbr/references/integration-patterns.md +0 -118
  832. package/plugins/cursor-pbr/references/model-profiles.md +0 -100
  833. package/plugins/cursor-pbr/references/model-selection.md +0 -32
  834. package/plugins/cursor-pbr/references/pbr-rules.md +0 -195
  835. package/plugins/cursor-pbr/references/pbr-tools-cli.md +0 -285
  836. package/plugins/cursor-pbr/references/plan-authoring.md +0 -182
  837. package/plugins/cursor-pbr/references/plan-format.md +0 -288
  838. package/plugins/cursor-pbr/references/planning-config.md +0 -214
  839. package/plugins/cursor-pbr/references/questioning.md +0 -215
  840. package/plugins/cursor-pbr/references/reading-verification.md +0 -128
  841. package/plugins/cursor-pbr/references/stub-patterns.md +0 -161
  842. package/plugins/cursor-pbr/references/subagent-coordination.md +0 -120
  843. package/plugins/cursor-pbr/references/ui-formatting.md +0 -444
  844. package/plugins/cursor-pbr/references/verification-patterns.md +0 -199
  845. package/plugins/cursor-pbr/references/wave-execution.md +0 -96
  846. package/plugins/cursor-pbr/rules/pbr-workflow.mdc +0 -48
  847. package/plugins/cursor-pbr/setup.ps1 +0 -78
  848. package/plugins/cursor-pbr/setup.sh +0 -83
  849. package/plugins/cursor-pbr/skills/audit/SKILL.md +0 -331
  850. package/plugins/cursor-pbr/skills/begin/SKILL.md +0 -589
  851. package/plugins/cursor-pbr/skills/begin/templates/PROJECT.md.tmpl +0 -34
  852. package/plugins/cursor-pbr/skills/begin/templates/REQUIREMENTS.md.tmpl +0 -19
  853. package/plugins/cursor-pbr/skills/begin/templates/STATE.md.tmpl +0 -50
  854. package/plugins/cursor-pbr/skills/begin/templates/config.json.tmpl +0 -64
  855. package/plugins/cursor-pbr/skills/begin/templates/researcher-prompt.md.tmpl +0 -20
  856. package/plugins/cursor-pbr/skills/begin/templates/roadmap-prompt.md.tmpl +0 -31
  857. package/plugins/cursor-pbr/skills/begin/templates/synthesis-prompt.md.tmpl +0 -17
  858. package/plugins/cursor-pbr/skills/build/SKILL.md +0 -961
  859. package/plugins/cursor-pbr/skills/config/SKILL.md +0 -252
  860. package/plugins/cursor-pbr/skills/continue/SKILL.md +0 -159
  861. package/plugins/cursor-pbr/skills/dashboard/SKILL.md +0 -44
  862. package/plugins/cursor-pbr/skills/debug/SKILL.md +0 -512
  863. package/plugins/cursor-pbr/skills/debug/templates/continuation-prompt.md.tmpl +0 -17
  864. package/plugins/cursor-pbr/skills/debug/templates/initial-investigation-prompt.md.tmpl +0 -28
  865. package/plugins/cursor-pbr/skills/discuss/SKILL.md +0 -354
  866. package/plugins/cursor-pbr/skills/discuss/templates/CONTEXT.md.tmpl +0 -62
  867. package/plugins/cursor-pbr/skills/discuss/templates/decision-categories.md +0 -10
  868. package/plugins/cursor-pbr/skills/do/SKILL.md +0 -67
  869. package/plugins/cursor-pbr/skills/explore/SKILL.md +0 -376
  870. package/plugins/cursor-pbr/skills/health/SKILL.md +0 -283
  871. package/plugins/cursor-pbr/skills/health/templates/check-pattern.md.tmpl +0 -31
  872. package/plugins/cursor-pbr/skills/health/templates/output-format.md.tmpl +0 -64
  873. package/plugins/cursor-pbr/skills/help/SKILL.md +0 -170
  874. package/plugins/cursor-pbr/skills/import/SKILL.md +0 -505
  875. package/plugins/cursor-pbr/skills/milestone/SKILL.md +0 -746
  876. package/plugins/cursor-pbr/skills/milestone/templates/audit-report.md.tmpl +0 -49
  877. package/plugins/cursor-pbr/skills/milestone/templates/stats-file.md.tmpl +0 -31
  878. package/plugins/cursor-pbr/skills/note/SKILL.md +0 -214
  879. package/plugins/cursor-pbr/skills/pause/SKILL.md +0 -248
  880. package/plugins/cursor-pbr/skills/pause/templates/continue-here.md.tmpl +0 -72
  881. package/plugins/cursor-pbr/skills/plan/SKILL.md +0 -663
  882. package/plugins/cursor-pbr/skills/plan/templates/checker-prompt.md.tmpl +0 -22
  883. package/plugins/cursor-pbr/skills/plan/templates/gap-closure-prompt.md.tmpl +0 -33
  884. package/plugins/cursor-pbr/skills/plan/templates/planner-prompt.md.tmpl +0 -39
  885. package/plugins/cursor-pbr/skills/plan/templates/researcher-prompt.md.tmpl +0 -20
  886. package/plugins/cursor-pbr/skills/plan/templates/revision-prompt.md.tmpl +0 -24
  887. package/plugins/cursor-pbr/skills/quick/SKILL.md +0 -376
  888. package/plugins/cursor-pbr/skills/resume/SKILL.md +0 -399
  889. package/plugins/cursor-pbr/skills/review/SKILL.md +0 -654
  890. package/plugins/cursor-pbr/skills/review/templates/debugger-prompt.md.tmpl +0 -61
  891. package/plugins/cursor-pbr/skills/review/templates/gap-planner-prompt.md.tmpl +0 -41
  892. package/plugins/cursor-pbr/skills/review/templates/verifier-prompt.md.tmpl +0 -116
  893. package/plugins/cursor-pbr/skills/scan/SKILL.md +0 -300
  894. package/plugins/cursor-pbr/skills/scan/templates/mapper-prompt.md.tmpl +0 -202
  895. package/plugins/cursor-pbr/skills/setup/SKILL.md +0 -296
  896. package/plugins/cursor-pbr/skills/shared/commit-planning-docs.md +0 -36
  897. package/plugins/cursor-pbr/skills/shared/config-loading.md +0 -103
  898. package/plugins/cursor-pbr/skills/shared/context-budget.md +0 -41
  899. package/plugins/cursor-pbr/skills/shared/context-loader-task.md +0 -87
  900. package/plugins/cursor-pbr/skills/shared/digest-select.md +0 -80
  901. package/plugins/cursor-pbr/skills/shared/domain-probes.md +0 -126
  902. package/plugins/cursor-pbr/skills/shared/error-reporting.md +0 -81
  903. package/plugins/cursor-pbr/skills/shared/gate-prompts.md +0 -389
  904. package/plugins/cursor-pbr/skills/shared/phase-argument-parsing.md +0 -46
  905. package/plugins/cursor-pbr/skills/shared/progress-display.md +0 -53
  906. package/plugins/cursor-pbr/skills/shared/revision-loop.md +0 -82
  907. package/plugins/cursor-pbr/skills/shared/state-loading.md +0 -63
  908. package/plugins/cursor-pbr/skills/shared/state-update.md +0 -162
  909. package/plugins/cursor-pbr/skills/shared/universal-anti-patterns.md +0 -38
  910. package/plugins/cursor-pbr/skills/status/SKILL.md +0 -362
  911. package/plugins/cursor-pbr/skills/statusline/SKILL.md +0 -150
  912. package/plugins/cursor-pbr/skills/todo/SKILL.md +0 -280
  913. package/plugins/cursor-pbr/templates/CONTEXT.md.tmpl +0 -53
  914. package/plugins/cursor-pbr/templates/INTEGRATION-REPORT.md.tmpl +0 -152
  915. package/plugins/cursor-pbr/templates/RESEARCH-SUMMARY.md.tmpl +0 -98
  916. package/plugins/cursor-pbr/templates/ROADMAP.md.tmpl +0 -41
  917. package/plugins/cursor-pbr/templates/SUMMARY.md.tmpl +0 -82
  918. package/plugins/cursor-pbr/templates/VERIFICATION-DETAIL.md.tmpl +0 -117
  919. package/plugins/cursor-pbr/templates/codebase/ARCHITECTURE.md.tmpl +0 -98
  920. package/plugins/cursor-pbr/templates/codebase/CONCERNS.md.tmpl +0 -93
  921. package/plugins/cursor-pbr/templates/codebase/CONVENTIONS.md.tmpl +0 -104
  922. package/plugins/cursor-pbr/templates/codebase/INTEGRATIONS.md.tmpl +0 -78
  923. package/plugins/cursor-pbr/templates/codebase/STACK.md.tmpl +0 -78
  924. package/plugins/cursor-pbr/templates/codebase/STRUCTURE.md.tmpl +0 -80
  925. package/plugins/cursor-pbr/templates/codebase/TESTING.md.tmpl +0 -107
  926. package/plugins/cursor-pbr/templates/continue-here.md.tmpl +0 -74
  927. package/plugins/cursor-pbr/templates/prompt-partials/phase-project-context.md.tmpl +0 -38
  928. package/plugins/cursor-pbr/templates/research/ARCHITECTURE.md.tmpl +0 -124
  929. package/plugins/cursor-pbr/templates/research/STACK.md.tmpl +0 -71
  930. package/plugins/cursor-pbr/templates/research/SUMMARY.md.tmpl +0 -112
  931. package/plugins/cursor-pbr/templates/research-outputs/phase-research.md.tmpl +0 -81
  932. package/plugins/cursor-pbr/templates/research-outputs/project-research.md.tmpl +0 -99
  933. package/plugins/cursor-pbr/templates/research-outputs/synthesis.md.tmpl +0 -36
  934. package/plugins/pbr/references/agent-interactions.md +0 -134
  935. package/plugins/pbr/references/pbr-rules.md +0 -194
  936. package/plugins/pbr/references/pbr-tools-cli.md +0 -285
  937. package/plugins/pbr/references/planning-config.md +0 -213
  938. package/plugins/pbr/references/subagent-coordination.md +0 -119
  939. package/plugins/pbr/references/ui-formatting.md +0 -444
  940. package/plugins/pbr/scripts/validate-plugin-structure.js +0 -183
  941. package/plugins/pbr/skills/milestone/templates/audit-report.md.tmpl +0 -48
  942. package/plugins/pbr/skills/shared/progress-display.md +0 -53
  943. package/plugins/pbr/skills/shared/state-loading.md +0 -62
  944. package/plugins/pbr/templates/RESEARCH-SUMMARY.md.tmpl +0 -97
  945. package/plugins/pbr/templates/research/ARCHITECTURE.md.tmpl +0 -124
  946. package/plugins/pbr/templates/research/STACK.md.tmpl +0 -71
  947. package/plugins/pbr/templates/research/SUMMARY.md.tmpl +0 -112
  948. package/plugins/pbr/templates/research-outputs/phase-research.md.tmpl +0 -81
  949. package/plugins/pbr/templates/research-outputs/project-research.md.tmpl +0 -99
  950. package/plugins/pbr/templates/research-outputs/synthesis.md.tmpl +0 -36
  951. /package/plugins/pbr/references/{agent-anti-patterns.md → archive/agent-anti-patterns.md} +0 -0
package/bin/install.js ADDED
@@ -0,0 +1,2752 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const os = require('os');
6
+ const readline = require('readline');
7
+ const crypto = require('crypto');
8
+
9
+ // Colors
10
+ const cyan = '\x1b[36m';
11
+ const green = '\x1b[32m';
12
+ const yellow = '\x1b[33m';
13
+ const dim = '\x1b[2m';
14
+ const reset = '\x1b[0m';
15
+
16
+ // Normalize WSL/Git Bash paths (/mnt/d/... -> D:\...) on Windows
17
+ function normalizePath(p) {
18
+ if (os.platform() === 'win32' && /^\/mnt\/[a-z]\//i.test(p)) {
19
+ const drive = p.charAt(5).toUpperCase();
20
+ return drive + ':' + p.slice(6).replace(/\//g, '\\');
21
+ }
22
+ return p;
23
+ }
24
+
25
+ // Codex config.toml constants
26
+ const PBR_CODEX_MARKER = '# PBR Agent Configuration \u2014 managed by plan-build-run installer';
27
+
28
+ const CODEX_AGENT_SANDBOX = {
29
+ 'pbr-audit': 'read-only',
30
+ 'pbr-codebase-mapper': 'workspace-write',
31
+ 'pbr-debugger': 'workspace-write',
32
+ 'pbr-dev-sync': 'workspace-write',
33
+ 'pbr-executor': 'workspace-write',
34
+ 'pbr-general': 'workspace-write',
35
+ 'pbr-integration-checker': 'read-only',
36
+ 'pbr-nyquist-auditor': 'read-only',
37
+ 'pbr-plan-checker': 'read-only',
38
+ 'pbr-planner': 'workspace-write',
39
+ 'pbr-researcher': 'workspace-write',
40
+ 'pbr-roadmapper': 'workspace-write',
41
+ 'pbr-synthesizer': 'workspace-write',
42
+ 'pbr-verifier': 'workspace-write',
43
+ };
44
+
45
+ // Get version from package.json
46
+ const pkg = require('../package.json');
47
+
48
+ // Parse args
49
+ const args = process.argv.slice(2);
50
+ const hasGlobal = args.includes('--global') || args.includes('-g');
51
+ const hasLocal = args.includes('--local') || args.includes('-l');
52
+ const hasOpencode = args.includes('--opencode');
53
+ const hasClaude = args.includes('--claude');
54
+ const hasGemini = args.includes('--gemini');
55
+ const hasCodex = args.includes('--codex');
56
+ const hasBoth = args.includes('--both'); // Legacy flag, keeps working
57
+ const hasAll = args.includes('--all');
58
+ const hasUninstall = args.includes('--uninstall') || args.includes('-u');
59
+
60
+ // Runtime selection - can be set by flags or interactive prompt
61
+ let selectedRuntimes = [];
62
+ if (hasAll) {
63
+ selectedRuntimes = ['claude', 'opencode', 'gemini', 'codex'];
64
+ } else if (hasBoth) {
65
+ selectedRuntimes = ['claude', 'opencode'];
66
+ } else {
67
+ if (hasOpencode) selectedRuntimes.push('opencode');
68
+ if (hasClaude) selectedRuntimes.push('claude');
69
+ if (hasGemini) selectedRuntimes.push('gemini');
70
+ if (hasCodex) selectedRuntimes.push('codex');
71
+ }
72
+
73
+ /**
74
+ * Convert a pathPrefix (which uses absolute paths for global installs) to a
75
+ * $HOME-relative form for replacing $HOME/.claude/ references in bash code blocks.
76
+ * Preserves $HOME as a shell variable so paths remain portable across machines.
77
+ */
78
+ function toHomePrefix(pathPrefix) {
79
+ const home = os.homedir().replace(/\\/g, '/');
80
+ const normalized = pathPrefix.replace(/\\/g, '/');
81
+ if (normalized.startsWith(home)) {
82
+ return '$HOME' + normalized.slice(home.length);
83
+ }
84
+ // For relative paths or paths not under $HOME, return as-is
85
+ return normalized;
86
+ }
87
+
88
+ // Helper to get directory name for a runtime (used for local/project installs)
89
+ function getDirName(runtime) {
90
+ if (runtime === 'opencode') return '.opencode';
91
+ if (runtime === 'gemini') return '.gemini';
92
+ if (runtime === 'codex') return '.codex';
93
+ return '.claude';
94
+ }
95
+
96
+ /**
97
+ * Get the config directory path relative to home directory for a runtime
98
+ * Used for templating hooks that use path.join(homeDir, '<configDir>', ...)
99
+ * @param {string} runtime - 'claude', 'opencode', 'gemini', or 'codex'
100
+ * @param {boolean} isGlobal - Whether this is a global install
101
+ */
102
+ function getConfigDirFromHome(runtime, isGlobal) {
103
+ if (!isGlobal) {
104
+ // Local installs use the same dir name pattern
105
+ return `'${getDirName(runtime)}'`;
106
+ }
107
+ // Global installs - OpenCode uses XDG path structure
108
+ if (runtime === 'opencode') {
109
+ // OpenCode: ~/.config/opencode -> '.config', 'opencode'
110
+ // Return as comma-separated for path.join() replacement
111
+ return "'.config', 'opencode'";
112
+ }
113
+ if (runtime === 'gemini') return "'.gemini'";
114
+ if (runtime === 'codex') return "'.codex'";
115
+ return "'.claude'";
116
+ }
117
+
118
+ /**
119
+ * Get the global config directory for OpenCode
120
+ * OpenCode follows XDG Base Directory spec and uses ~/.config/opencode/
121
+ * Priority: OPENCODE_CONFIG_DIR > dirname(OPENCODE_CONFIG) > XDG_CONFIG_HOME/opencode > ~/.config/opencode
122
+ */
123
+ function getOpencodeGlobalDir() {
124
+ // 1. Explicit OPENCODE_CONFIG_DIR env var
125
+ if (process.env.OPENCODE_CONFIG_DIR) {
126
+ return expandTilde(process.env.OPENCODE_CONFIG_DIR);
127
+ }
128
+
129
+ // 2. OPENCODE_CONFIG env var (use its directory)
130
+ if (process.env.OPENCODE_CONFIG) {
131
+ return path.dirname(expandTilde(process.env.OPENCODE_CONFIG));
132
+ }
133
+
134
+ // 3. XDG_CONFIG_HOME/opencode
135
+ if (process.env.XDG_CONFIG_HOME) {
136
+ return path.join(expandTilde(process.env.XDG_CONFIG_HOME), 'opencode');
137
+ }
138
+
139
+ // 4. Default: ~/.config/opencode (XDG default)
140
+ return path.join(os.homedir(), '.config', 'opencode');
141
+ }
142
+
143
+ /**
144
+ * Get the global config directory for a runtime
145
+ * @param {string} runtime - 'claude', 'opencode', 'gemini', or 'codex'
146
+ * @param {string|null} explicitDir - Explicit directory from --config-dir flag
147
+ */
148
+ function getGlobalDir(runtime, explicitDir = null) {
149
+ if (runtime === 'opencode') {
150
+ // For OpenCode, --config-dir overrides env vars
151
+ if (explicitDir) {
152
+ return expandTilde(explicitDir);
153
+ }
154
+ return getOpencodeGlobalDir();
155
+ }
156
+
157
+ if (runtime === 'gemini') {
158
+ // Gemini: --config-dir > GEMINI_CONFIG_DIR > ~/.gemini
159
+ if (explicitDir) {
160
+ return expandTilde(explicitDir);
161
+ }
162
+ if (process.env.GEMINI_CONFIG_DIR) {
163
+ return expandTilde(process.env.GEMINI_CONFIG_DIR);
164
+ }
165
+ return path.join(os.homedir(), '.gemini');
166
+ }
167
+
168
+ if (runtime === 'codex') {
169
+ // Codex: --config-dir > CODEX_HOME > ~/.codex
170
+ if (explicitDir) {
171
+ return expandTilde(explicitDir);
172
+ }
173
+ if (process.env.CODEX_HOME) {
174
+ return expandTilde(process.env.CODEX_HOME);
175
+ }
176
+ return path.join(os.homedir(), '.codex');
177
+ }
178
+
179
+ // Claude Code: --config-dir > CLAUDE_CONFIG_DIR > ~/.claude
180
+ if (explicitDir) {
181
+ return expandTilde(explicitDir);
182
+ }
183
+ if (process.env.CLAUDE_CONFIG_DIR) {
184
+ return expandTilde(process.env.CLAUDE_CONFIG_DIR);
185
+ }
186
+ return path.join(os.homedir(), '.claude');
187
+ }
188
+
189
+ const banner = '\n' +
190
+ cyan + ' ██████╗ ██████╗ ██████╗\n' +
191
+ ' ██╔══██╗██╔══██╗██╔══██╗\n' +
192
+ ' ██████╔╝██████╔╝██████╔╝\n' +
193
+ ' ██╔═══╝ ██╔══██╗██╔══██╗\n' +
194
+ ' ██║ ██████╔╝██║ ██║\n' +
195
+ ' ╚═╝ ╚═════╝ ╚═╝ ╚═╝' + reset + '\n' +
196
+ '\n' +
197
+ ' Plan-Build-Run ' + dim + 'v' + pkg.version + reset + '\n' +
198
+ ' A meta-prompting, context engineering and spec-driven\n' +
199
+ ' development system for Claude Code, OpenCode, Gemini, and Codex.\n';
200
+
201
+ // Parse --config-dir argument
202
+ function parseConfigDirArg() {
203
+ const configDirIndex = args.findIndex(arg => arg === '--config-dir' || arg === '-c');
204
+ if (configDirIndex !== -1) {
205
+ const nextArg = args[configDirIndex + 1];
206
+ // Error if --config-dir is provided without a value or next arg is another flag
207
+ if (!nextArg || nextArg.startsWith('-')) {
208
+ console.error(` ${yellow}--config-dir requires a path argument${reset}`);
209
+ process.exit(1);
210
+ }
211
+ return nextArg;
212
+ }
213
+ // Also handle --config-dir=value format
214
+ const configDirArg = args.find(arg => arg.startsWith('--config-dir=') || arg.startsWith('-c='));
215
+ if (configDirArg) {
216
+ const value = configDirArg.split('=')[1];
217
+ if (!value) {
218
+ console.error(` ${yellow}--config-dir requires a non-empty path${reset}`);
219
+ process.exit(1);
220
+ }
221
+ return value;
222
+ }
223
+ return null;
224
+ }
225
+ const explicitConfigDir = parseConfigDirArg();
226
+ const hasHelp = args.includes('--help') || args.includes('-h');
227
+ const forceStatusline = args.includes('--force-statusline');
228
+
229
+ console.log(banner);
230
+
231
+ // Show help if requested
232
+ if (hasHelp) {
233
+ console.log(` ${yellow}Usage:${reset} npx @sienklogic/plan-build-run [options]\n\n ${yellow}Options:${reset}\n ${cyan}-g, --global${reset} Install globally (to config directory)\n ${cyan}-l, --local${reset} Install locally (to current directory)\n ${cyan}--claude${reset} Install for Claude Code only\n ${cyan}--opencode${reset} Install for OpenCode only\n ${cyan}--gemini${reset} Install for Gemini only\n ${cyan}--codex${reset} Install for Codex only\n ${cyan}--all${reset} Install for all runtimes\n ${cyan}-u, --uninstall${reset} Uninstall PBR (remove all PBR files)\n ${cyan}-c, --config-dir <path>${reset} Specify custom config directory\n ${cyan}-h, --help${reset} Show this help message\n ${cyan}--force-statusline${reset} Replace existing statusline config\n\n ${yellow}Examples:${reset}\n ${dim}# Interactive install (prompts for runtime and location)${reset}\n npx @sienklogic/plan-build-run\n\n ${dim}# Install for Claude Code globally${reset}\n npx @sienklogic/plan-build-run --claude --global\n\n ${dim}# Install for Gemini globally${reset}\n npx @sienklogic/plan-build-run --gemini --global\n\n ${dim}# Install for Codex globally${reset}\n npx @sienklogic/plan-build-run --codex --global\n\n ${dim}# Install for all runtimes globally${reset}\n npx @sienklogic/plan-build-run --all --global\n\n ${dim}# Install to custom config directory${reset}\n npx @sienklogic/plan-build-run --codex --global --config-dir ~/.codex-work\n\n ${dim}# Install to current project only${reset}\n npx @sienklogic/plan-build-run --claude --local\n\n ${dim}# Uninstall PBR from Codex globally${reset}\n npx @sienklogic/plan-build-run --codex --global --uninstall\n\n ${yellow}Notes:${reset}\n The --config-dir option is useful when you have multiple configurations.\n It takes priority over CLAUDE_CONFIG_DIR / GEMINI_CONFIG_DIR / CODEX_HOME environment variables.\n`);
234
+ process.exit(0);
235
+ }
236
+
237
+ /**
238
+ * Expand ~ to home directory (shell doesn't expand in env vars passed to node)
239
+ */
240
+ function expandTilde(filePath) {
241
+ if (filePath && filePath.startsWith('~/')) {
242
+ return path.join(os.homedir(), filePath.slice(2));
243
+ }
244
+ return filePath;
245
+ }
246
+
247
+ /**
248
+ * Build a hook command path using forward slashes for cross-platform compatibility.
249
+ * On Windows, $HOME is not expanded by cmd.exe/PowerShell, so we use the actual path.
250
+ */
251
+ function buildHookCommand(configDir, hookName) {
252
+ // Use forward slashes for Node.js compatibility on all platforms
253
+ const hooksPath = configDir.replace(/\\/g, '/') + '/hooks/' + hookName;
254
+ return `node "${hooksPath}"`;
255
+ }
256
+
257
+ /**
258
+ * Read and parse settings.json, returning empty object if it doesn't exist
259
+ */
260
+ function readSettings(settingsPath) {
261
+ if (fs.existsSync(settingsPath)) {
262
+ try {
263
+ return JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
264
+ } catch (e) {
265
+ return {};
266
+ }
267
+ }
268
+ return {};
269
+ }
270
+
271
+ /**
272
+ * Write settings.json with proper formatting
273
+ */
274
+ function writeSettings(settingsPath, settings) {
275
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
276
+ }
277
+
278
+ // Cache for attribution settings (populated once per runtime during install)
279
+ const attributionCache = new Map();
280
+
281
+ /**
282
+ * Get commit attribution setting for a runtime
283
+ * @param {string} runtime - 'claude', 'opencode', 'gemini', or 'codex'
284
+ * @returns {null|undefined|string} null = remove, undefined = keep default, string = custom
285
+ */
286
+ function getCommitAttribution(runtime) {
287
+ // Return cached value if available
288
+ if (attributionCache.has(runtime)) {
289
+ return attributionCache.get(runtime);
290
+ }
291
+
292
+ let result;
293
+
294
+ if (runtime === 'opencode') {
295
+ const config = readSettings(path.join(getGlobalDir('opencode', null), 'opencode.json'));
296
+ result = config.disable_ai_attribution === true ? null : undefined;
297
+ } else if (runtime === 'gemini') {
298
+ // Gemini: check gemini settings.json for attribution config
299
+ const settings = readSettings(path.join(getGlobalDir('gemini', explicitConfigDir), 'settings.json'));
300
+ if (!settings.attribution || settings.attribution.commit === undefined) {
301
+ result = undefined;
302
+ } else if (settings.attribution.commit === '') {
303
+ result = null;
304
+ } else {
305
+ result = settings.attribution.commit;
306
+ }
307
+ } else if (runtime === 'claude') {
308
+ // Claude Code
309
+ const settings = readSettings(path.join(getGlobalDir('claude', explicitConfigDir), 'settings.json'));
310
+ if (!settings.attribution || settings.attribution.commit === undefined) {
311
+ result = undefined;
312
+ } else if (settings.attribution.commit === '') {
313
+ result = null;
314
+ } else {
315
+ result = settings.attribution.commit;
316
+ }
317
+ } else {
318
+ // Codex currently has no attribution setting equivalent
319
+ result = undefined;
320
+ }
321
+
322
+ // Cache and return
323
+ attributionCache.set(runtime, result);
324
+ return result;
325
+ }
326
+
327
+ /**
328
+ * Process Co-Authored-By lines based on attribution setting
329
+ * @param {string} content - File content to process
330
+ * @param {null|undefined|string} attribution - null=remove, undefined=keep, string=replace
331
+ * @returns {string} Processed content
332
+ */
333
+ function processAttribution(content, attribution) {
334
+ if (attribution === null) {
335
+ // Remove Co-Authored-By lines and the preceding blank line
336
+ return content.replace(/(\r?\n){2}Co-Authored-By:.*$/gim, '');
337
+ }
338
+ if (attribution === undefined) {
339
+ return content;
340
+ }
341
+ // Replace with custom attribution (escape $ to prevent backreference injection)
342
+ const safeAttribution = attribution.replace(/\$/g, '$$$$');
343
+ return content.replace(/Co-Authored-By:.*$/gim, `Co-Authored-By: ${safeAttribution}`);
344
+ }
345
+
346
+ /**
347
+ * Convert Claude Code frontmatter to opencode format
348
+ * - Converts 'allowed-tools:' array to 'permission:' object
349
+ * @param {string} content - Markdown file content with YAML frontmatter
350
+ * @returns {string} - Content with converted frontmatter
351
+ */
352
+ // Color name to hex mapping for opencode compatibility
353
+ const colorNameToHex = {
354
+ cyan: '#00FFFF',
355
+ red: '#FF0000',
356
+ green: '#00FF00',
357
+ blue: '#0000FF',
358
+ yellow: '#FFFF00',
359
+ magenta: '#FF00FF',
360
+ orange: '#FFA500',
361
+ purple: '#800080',
362
+ pink: '#FFC0CB',
363
+ white: '#FFFFFF',
364
+ black: '#000000',
365
+ gray: '#808080',
366
+ grey: '#808080',
367
+ };
368
+
369
+ // Tool name mapping from Claude Code to OpenCode
370
+ // OpenCode uses lowercase tool names; special mappings for renamed tools
371
+ const claudeToOpencodeTools = {
372
+ AskUserQuestion: 'question',
373
+ SlashCommand: 'skill',
374
+ TodoWrite: 'todowrite',
375
+ WebFetch: 'webfetch',
376
+ WebSearch: 'websearch', // Plugin/MCP - keep for compatibility
377
+ };
378
+
379
+ // Tool name mapping from Claude Code to Gemini CLI
380
+ // Gemini CLI uses snake_case built-in tool names
381
+ const claudeToGeminiTools = {
382
+ Read: 'read_file',
383
+ Write: 'write_file',
384
+ Edit: 'replace',
385
+ Bash: 'run_shell_command',
386
+ Glob: 'glob',
387
+ Grep: 'search_file_content',
388
+ WebSearch: 'google_web_search',
389
+ WebFetch: 'web_fetch',
390
+ TodoWrite: 'write_todos',
391
+ AskUserQuestion: 'ask_user',
392
+ };
393
+
394
+ /**
395
+ * Convert a Claude Code tool name to OpenCode format
396
+ * - Applies special mappings (AskUserQuestion -> question, etc.)
397
+ * - Converts to lowercase (except MCP tools which keep their format)
398
+ */
399
+ function convertToolName(claudeTool) {
400
+ // Check for special mapping first
401
+ if (claudeToOpencodeTools[claudeTool]) {
402
+ return claudeToOpencodeTools[claudeTool];
403
+ }
404
+ // MCP tools (mcp__*) keep their format
405
+ if (claudeTool.startsWith('mcp__')) {
406
+ return claudeTool;
407
+ }
408
+ // Default: convert to lowercase
409
+ return claudeTool.toLowerCase();
410
+ }
411
+
412
+ /**
413
+ * Convert a Claude Code tool name to Gemini CLI format
414
+ * - Applies Claude→Gemini mapping (Read→read_file, Bash→run_shell_command, etc.)
415
+ * - Filters out MCP tools (mcp__*) — they are auto-discovered at runtime in Gemini
416
+ * - Filters out Task — agents are auto-registered as tools in Gemini
417
+ * @returns {string|null} Gemini tool name, or null if tool should be excluded
418
+ */
419
+ function convertGeminiToolName(claudeTool) {
420
+ // MCP tools: exclude — auto-discovered from mcpServers config at runtime
421
+ if (claudeTool.startsWith('mcp__')) {
422
+ return null;
423
+ }
424
+ // Task: exclude — agents are auto-registered as callable tools
425
+ if (claudeTool === 'Task') {
426
+ return null;
427
+ }
428
+ // Check for explicit mapping
429
+ if (claudeToGeminiTools[claudeTool]) {
430
+ return claudeToGeminiTools[claudeTool];
431
+ }
432
+ // Default: lowercase
433
+ return claudeTool.toLowerCase();
434
+ }
435
+
436
+ function toSingleLine(value) {
437
+ return value.replace(/\s+/g, ' ').trim();
438
+ }
439
+
440
+ function yamlQuote(value) {
441
+ return JSON.stringify(value);
442
+ }
443
+
444
+ function extractFrontmatterAndBody(content) {
445
+ if (!content.startsWith('---')) {
446
+ return { frontmatter: null, body: content };
447
+ }
448
+
449
+ const endIndex = content.indexOf('---', 3);
450
+ if (endIndex === -1) {
451
+ return { frontmatter: null, body: content };
452
+ }
453
+
454
+ return {
455
+ frontmatter: content.substring(3, endIndex).trim(),
456
+ body: content.substring(endIndex + 3),
457
+ };
458
+ }
459
+
460
+ function extractFrontmatterField(frontmatter, fieldName) {
461
+ const regex = new RegExp(`^${fieldName}:\\s*(.+)$`, 'm');
462
+ const match = frontmatter.match(regex);
463
+ if (!match) return null;
464
+ return match[1].trim().replace(/^['"]|['"]$/g, '');
465
+ }
466
+
467
+ function convertSlashCommandsToCodexSkillMentions(content) {
468
+ let converted = content.replace(/\/pbr:([a-z0-9-]+)/gi, (_, commandName) => {
469
+ return `$pbr-${String(commandName).toLowerCase()}`;
470
+ });
471
+ converted = converted.replace(/\/pbr-help\b/g, '$pbr-help');
472
+ return converted;
473
+ }
474
+
475
+ function convertClaudeToCodexMarkdown(content) {
476
+ let converted = convertSlashCommandsToCodexSkillMentions(content);
477
+ converted = converted.replace(/\$ARGUMENTS\b/g, '{{PBR_ARGS}}');
478
+ return converted;
479
+ }
480
+
481
+ function getCodexSkillAdapterHeader(skillName) {
482
+ const invocation = `$${skillName}`;
483
+ return `<codex_skill_adapter>
484
+ ## A. Skill Invocation
485
+ - This skill is invoked by mentioning \`${invocation}\`.
486
+ - Treat all user text after \`${invocation}\` as \`{{PBR_ARGS}}\`.
487
+ - If no arguments are present, treat \`{{PBR_ARGS}}\` as empty.
488
+
489
+ ## B. AskUserQuestion → request_user_input Mapping
490
+ PBR workflows use \`AskUserQuestion\` (Claude Code syntax). Translate to Codex \`request_user_input\`:
491
+
492
+ Parameter mapping:
493
+ - \`header\` → \`header\`
494
+ - \`question\` → \`question\`
495
+ - Options formatted as \`"Label" — description\` → \`{label: "Label", description: "description"}\`
496
+ - Generate \`id\` from header: lowercase, replace spaces with underscores
497
+
498
+ Batched calls:
499
+ - \`AskUserQuestion([q1, q2])\` → single \`request_user_input\` with multiple entries in \`questions[]\`
500
+
501
+ Multi-select workaround:
502
+ - Codex has no \`multiSelect\`. Use sequential single-selects, or present a numbered freeform list asking the user to enter comma-separated numbers.
503
+
504
+ Execute mode fallback:
505
+ - When \`request_user_input\` is rejected (Execute mode), present a plain-text numbered list and pick a reasonable default.
506
+
507
+ ## C. Task() → spawn_agent Mapping
508
+ PBR workflows use \`Task(...)\` (Claude Code syntax). Translate to Codex collaboration tools:
509
+
510
+ Direct mapping:
511
+ - \`Task(subagent_type="X", prompt="Y")\` → \`spawn_agent(agent_type="X", message="Y")\`
512
+ - \`Task(model="...")\` → omit (Codex uses per-role config, not inline model selection)
513
+ - \`fork_context: false\` by default — PBR agents load their own context via \`<files_to_read>\` blocks
514
+
515
+ Parallel fan-out:
516
+ - Spawn multiple agents → collect agent IDs → \`wait(ids)\` for all to complete
517
+
518
+ Result parsing:
519
+ - Look for structured markers in agent output: \`CHECKPOINT\`, \`PLAN COMPLETE\`, \`SUMMARY\`, etc.
520
+ - \`close_agent(id)\` after collecting results from each agent
521
+ </codex_skill_adapter>`;
522
+ }
523
+
524
+ function convertClaudeCommandToCodexSkill(content, skillName) {
525
+ const converted = convertClaudeToCodexMarkdown(content);
526
+ const { frontmatter, body } = extractFrontmatterAndBody(converted);
527
+ let description = `Run PBR workflow ${skillName}.`;
528
+ if (frontmatter) {
529
+ const maybeDescription = extractFrontmatterField(frontmatter, 'description');
530
+ if (maybeDescription) {
531
+ description = maybeDescription;
532
+ }
533
+ }
534
+ description = toSingleLine(description);
535
+ const shortDescription = description.length > 180 ? `${description.slice(0, 177)}...` : description;
536
+ const adapter = getCodexSkillAdapterHeader(skillName);
537
+
538
+ return `---\nname: ${yamlQuote(skillName)}\ndescription: ${yamlQuote(description)}\nmetadata:\n short-description: ${yamlQuote(shortDescription)}\n---\n\n${adapter}\n\n${body.trimStart()}`;
539
+ }
540
+
541
+ /**
542
+ * Convert Claude Code agent markdown to Codex agent format.
543
+ * Applies base markdown conversions, then adds a <codex_agent_role> header
544
+ * and cleans up frontmatter (removes tools/color fields).
545
+ */
546
+ function convertClaudeAgentToCodexAgent(content) {
547
+ let converted = convertClaudeToCodexMarkdown(content);
548
+
549
+ const { frontmatter, body } = extractFrontmatterAndBody(converted);
550
+ if (!frontmatter) return converted;
551
+
552
+ const name = extractFrontmatterField(frontmatter, 'name') || 'unknown';
553
+ const description = extractFrontmatterField(frontmatter, 'description') || '';
554
+ const tools = extractFrontmatterField(frontmatter, 'tools') || '';
555
+
556
+ const roleHeader = `<codex_agent_role>
557
+ role: ${name}
558
+ tools: ${tools}
559
+ purpose: ${toSingleLine(description)}
560
+ </codex_agent_role>`;
561
+
562
+ const cleanFrontmatter = `---\nname: ${yamlQuote(name)}\ndescription: ${yamlQuote(toSingleLine(description))}\n---`;
563
+
564
+ return `${cleanFrontmatter}\n\n${roleHeader}\n${body}`;
565
+ }
566
+
567
+ /**
568
+ * Generate a per-agent .toml config file for Codex.
569
+ * Sets sandbox_mode and developer_instructions from the agent markdown body.
570
+ */
571
+ function generateCodexAgentToml(agentName, agentContent) {
572
+ const sandboxMode = CODEX_AGENT_SANDBOX[agentName] || 'read-only';
573
+ const { body } = extractFrontmatterAndBody(agentContent);
574
+ const instructions = body.trim();
575
+
576
+ const lines = [
577
+ `sandbox_mode = "${sandboxMode}"`,
578
+ `developer_instructions = """`,
579
+ instructions,
580
+ `"""`,
581
+ ];
582
+ return lines.join('\n') + '\n';
583
+ }
584
+
585
+ /**
586
+ * Generate the PBR config block for Codex config.toml.
587
+ * @param {Array<{name: string, description: string}>} agents
588
+ */
589
+ function generateCodexConfigBlock(agents) {
590
+ const lines = [
591
+ PBR_CODEX_MARKER,
592
+ '[features]',
593
+ 'multi_agent = true',
594
+ 'default_mode_request_user_input = true',
595
+ '',
596
+ '[agents]',
597
+ 'max_threads = 4',
598
+ 'max_depth = 2',
599
+ '',
600
+ ];
601
+
602
+ for (const { name, description } of agents) {
603
+ lines.push(`[agents.${name}]`);
604
+ lines.push(`description = ${JSON.stringify(description)}`);
605
+ lines.push(`config_file = "agents/${name}.toml"`);
606
+ lines.push('');
607
+ }
608
+
609
+ return lines.join('\n');
610
+ }
611
+
612
+ /**
613
+ * Strip PBR sections from Codex config.toml content.
614
+ * Returns cleaned content, or null if file would be empty.
615
+ */
616
+ function stripPbrFromCodexConfig(content) {
617
+ const markerIndex = content.indexOf(PBR_CODEX_MARKER);
618
+
619
+ if (markerIndex !== -1) {
620
+ // Has PBR marker — remove everything from marker to EOF
621
+ let before = content.substring(0, markerIndex).trimEnd();
622
+ // Also strip PBR-injected feature keys above the marker (Case 3 inject)
623
+ before = before.replace(/^multi_agent\s*=\s*true\s*\n?/m, '');
624
+ before = before.replace(/^default_mode_request_user_input\s*=\s*true\s*\n?/m, '');
625
+ before = before.replace(/^\[features\]\s*\n(?=\[|$)/m, '');
626
+ before = before.replace(/\n{3,}/g, '\n\n').trim();
627
+ if (!before) return null;
628
+ return before + '\n';
629
+ }
630
+
631
+ // No marker but may have PBR-injected feature keys
632
+ let cleaned = content;
633
+ cleaned = cleaned.replace(/^multi_agent\s*=\s*true\s*\n?/m, '');
634
+ cleaned = cleaned.replace(/^default_mode_request_user_input\s*=\s*true\s*\n?/m, '');
635
+
636
+ // Remove [agents.pbr-*] sections (from header to next section or EOF)
637
+ cleaned = cleaned.replace(/^\[agents\.pbr-[^\]]+\]\n(?:(?!\[)[^\n]*\n?)*/gm, '');
638
+
639
+ // Remove [features] section if now empty (only header, no keys before next section)
640
+ cleaned = cleaned.replace(/^\[features\]\s*\n(?=\[|$)/m, '');
641
+
642
+ // Remove [agents] section if now empty
643
+ cleaned = cleaned.replace(/^\[agents\]\s*\n(?=\[|$)/m, '');
644
+
645
+ // Clean up excessive blank lines
646
+ cleaned = cleaned.replace(/\n{3,}/g, '\n\n').trim();
647
+
648
+ if (!cleaned) return null;
649
+ return cleaned + '\n';
650
+ }
651
+
652
+ /**
653
+ * Merge PBR config block into an existing or new config.toml.
654
+ * Three cases: new file, existing with PBR marker, existing without marker.
655
+ */
656
+ function mergeCodexConfig(configPath, pbrBlock) {
657
+ // Case 1: No config.toml — create fresh
658
+ if (!fs.existsSync(configPath)) {
659
+ fs.writeFileSync(configPath, pbrBlock + '\n');
660
+ return;
661
+ }
662
+
663
+ const existing = fs.readFileSync(configPath, 'utf8');
664
+ const markerIndex = existing.indexOf(PBR_CODEX_MARKER);
665
+
666
+ // Case 2: Has PBR marker — truncate and re-append
667
+ if (markerIndex !== -1) {
668
+ let before = existing.substring(0, markerIndex).trimEnd();
669
+ if (before) {
670
+ // Strip any PBR-managed sections that leaked above the marker from previous installs
671
+ before = before.replace(/^\[agents\.pbr-[^\]]+\]\n(?:(?!\[)[^\n]*\n?)*/gm, '');
672
+ before = before.replace(/^\[agents\]\n(?:(?!\[)[^\n]*\n?)*/m, '');
673
+ before = before.replace(/\n{3,}/g, '\n\n').trimEnd();
674
+
675
+ // Re-inject feature keys if user has [features] above the marker
676
+ const hasFeatures = /^\[features\]\s*$/m.test(before);
677
+ if (hasFeatures) {
678
+ if (!before.includes('multi_agent')) {
679
+ before = before.replace(/^\[features\]\s*$/m, '[features]\nmulti_agent = true');
680
+ }
681
+ if (!before.includes('default_mode_request_user_input')) {
682
+ before = before.replace(/^\[features\].*$/m, '$&\ndefault_mode_request_user_input = true');
683
+ }
684
+ }
685
+ // Skip [features] from pbrBlock if user already has it
686
+ const block = hasFeatures
687
+ ? PBR_CODEX_MARKER + '\n' + pbrBlock.substring(pbrBlock.indexOf('[agents]'))
688
+ : pbrBlock;
689
+ fs.writeFileSync(configPath, before + '\n\n' + block + '\n');
690
+ } else {
691
+ fs.writeFileSync(configPath, pbrBlock + '\n');
692
+ }
693
+ return;
694
+ }
695
+
696
+ // Case 3: No marker — inject features if needed, append agents
697
+ let content = existing;
698
+ const featuresRegex = /^\[features\]\s*$/m;
699
+ const hasFeatures = featuresRegex.test(content);
700
+
701
+ if (hasFeatures) {
702
+ if (!content.includes('multi_agent')) {
703
+ content = content.replace(featuresRegex, '[features]\nmulti_agent = true');
704
+ }
705
+ if (!content.includes('default_mode_request_user_input')) {
706
+ content = content.replace(/^\[features\].*$/m, '$&\ndefault_mode_request_user_input = true');
707
+ }
708
+ // Append agents block (skip the [features] section from pbrBlock)
709
+ const agentsBlock = pbrBlock.substring(pbrBlock.indexOf('[agents]'));
710
+ content = content.trimEnd() + '\n\n' + PBR_CODEX_MARKER + '\n' + agentsBlock + '\n';
711
+ } else {
712
+ content = content.trimEnd() + '\n\n' + pbrBlock + '\n';
713
+ }
714
+
715
+ fs.writeFileSync(configPath, content);
716
+ }
717
+
718
+ /**
719
+ * Generate config.toml and per-agent .toml files for Codex.
720
+ * Reads agent .md files from source, extracts metadata, writes .toml configs.
721
+ */
722
+ function installCodexConfig(targetDir, agentsSrc) {
723
+ const configPath = path.join(targetDir, 'config.toml');
724
+ const agentsTomlDir = path.join(targetDir, 'agents');
725
+ fs.mkdirSync(agentsTomlDir, { recursive: true });
726
+
727
+ const agentEntries = fs.readdirSync(agentsSrc).filter(f => f.endsWith('.md'));
728
+ const agents = [];
729
+
730
+ // Compute the Codex pathPrefix for replacing .claude paths
731
+ const codexPathPrefix = `${targetDir.replace(/\\/g, '/')}/`;
732
+
733
+ for (const file of agentEntries) {
734
+ let content = fs.readFileSync(path.join(agentsSrc, file), 'utf8');
735
+ // Replace .claude paths before generating TOML (source files use ~/.claude and $HOME/.claude)
736
+ content = content.replace(/~\/\.claude\//g, codexPathPrefix);
737
+ content = content.replace(/\$HOME\/\.claude\//g, toHomePrefix(codexPathPrefix));
738
+ const { frontmatter } = extractFrontmatterAndBody(content);
739
+ const name = extractFrontmatterField(frontmatter, 'name') || file.replace('.md', '');
740
+ const description = extractFrontmatterField(frontmatter, 'description') || '';
741
+
742
+ agents.push({ name, description: toSingleLine(description) });
743
+
744
+ const tomlContent = generateCodexAgentToml(name, content);
745
+ fs.writeFileSync(path.join(agentsTomlDir, `${name}.toml`), tomlContent);
746
+ }
747
+
748
+ const pbrBlock = generateCodexConfigBlock(agents);
749
+ mergeCodexConfig(configPath, pbrBlock);
750
+
751
+ return agents.length;
752
+ }
753
+
754
+ /**
755
+ * Strip HTML <sub> tags for Gemini CLI output
756
+ * Terminals don't support subscript — Gemini renders these as raw HTML.
757
+ * Converts <sub>text</sub> to italic *(text)* for readable terminal output.
758
+ */
759
+ function stripSubTags(content) {
760
+ return content.replace(/<sub>(.*?)<\/sub>/g, '*($1)*');
761
+ }
762
+
763
+ /**
764
+ * Convert Claude Code agent frontmatter to Gemini CLI format
765
+ * Gemini agents use .md files with YAML frontmatter, same as Claude,
766
+ * but with different field names and formats:
767
+ * - tools: must be a YAML array (not comma-separated string)
768
+ * - tool names: must use Gemini built-in names (read_file, not Read)
769
+ * - color: must be removed (causes validation error)
770
+ * - mcp__* tools: must be excluded (auto-discovered at runtime)
771
+ */
772
+ function convertClaudeToGeminiAgent(content) {
773
+ if (!content.startsWith('---')) return content;
774
+
775
+ const endIndex = content.indexOf('---', 3);
776
+ if (endIndex === -1) return content;
777
+
778
+ const frontmatter = content.substring(3, endIndex).trim();
779
+ const body = content.substring(endIndex + 3);
780
+
781
+ const lines = frontmatter.split('\n');
782
+ const newLines = [];
783
+ let inAllowedTools = false;
784
+ const tools = [];
785
+
786
+ for (const line of lines) {
787
+ const trimmed = line.trim();
788
+
789
+ // Convert allowed-tools YAML array to tools list
790
+ if (trimmed.startsWith('allowed-tools:')) {
791
+ inAllowedTools = true;
792
+ continue;
793
+ }
794
+
795
+ // Handle inline tools: field (comma-separated string)
796
+ if (trimmed.startsWith('tools:')) {
797
+ const toolsValue = trimmed.substring(6).trim();
798
+ if (toolsValue) {
799
+ const parsed = toolsValue.split(',').map(t => t.trim()).filter(t => t);
800
+ for (const t of parsed) {
801
+ const mapped = convertGeminiToolName(t);
802
+ if (mapped) tools.push(mapped);
803
+ }
804
+ } else {
805
+ // tools: with no value means YAML array follows
806
+ inAllowedTools = true;
807
+ }
808
+ continue;
809
+ }
810
+
811
+ // Strip color field (not supported by Gemini CLI, causes validation error)
812
+ if (trimmed.startsWith('color:')) continue;
813
+
814
+ // Collect allowed-tools/tools array items
815
+ if (inAllowedTools) {
816
+ if (trimmed.startsWith('- ')) {
817
+ const mapped = convertGeminiToolName(trimmed.substring(2).trim());
818
+ if (mapped) tools.push(mapped);
819
+ continue;
820
+ } else if (trimmed && !trimmed.startsWith('-')) {
821
+ inAllowedTools = false;
822
+ }
823
+ }
824
+
825
+ if (!inAllowedTools) {
826
+ newLines.push(line);
827
+ }
828
+ }
829
+
830
+ // Add tools as YAML array (Gemini requires array format)
831
+ if (tools.length > 0) {
832
+ newLines.push('tools:');
833
+ for (const tool of tools) {
834
+ newLines.push(` - ${tool}`);
835
+ }
836
+ }
837
+
838
+ const newFrontmatter = newLines.join('\n').trim();
839
+
840
+ // Escape ${VAR} patterns in agent body for Gemini CLI compatibility.
841
+ // Gemini's templateString() treats all ${word} patterns as template variables
842
+ // and throws "Template validation failed: Missing required input parameters"
843
+ // when they can't be resolved. PBR agents use ${PHASE}, ${PLAN}, etc. as
844
+ // shell variables in bash code blocks — convert to $VAR (no braces) which
845
+ // is equivalent bash and invisible to Gemini's /\$\{(\w+)\}/g regex.
846
+ const escapedBody = body.replace(/\$\{(\w+)\}/g, '$$$1');
847
+
848
+ return `---\n${newFrontmatter}\n---${stripSubTags(escapedBody)}`;
849
+ }
850
+
851
+ function convertClaudeToOpencodeFrontmatter(content) {
852
+ // Replace tool name references in content (applies to all files)
853
+ let convertedContent = content;
854
+ convertedContent = convertedContent.replace(/\bAskUserQuestion\b/g, 'question');
855
+ convertedContent = convertedContent.replace(/\bSlashCommand\b/g, 'skill');
856
+ convertedContent = convertedContent.replace(/\bTodoWrite\b/g, 'todowrite');
857
+ // Replace /pbr:command with /pbr-command for opencode (flat command structure)
858
+ convertedContent = convertedContent.replace(/\/pbr:/g, '/pbr-');
859
+ // Replace ~/.claude and $HOME/.claude with OpenCode's config location
860
+ convertedContent = convertedContent.replace(/~\/\.claude\b/g, '~/.config/opencode');
861
+ convertedContent = convertedContent.replace(/\$HOME\/\.claude\b/g, '$HOME/.config/opencode');
862
+ // Replace general-purpose subagent type with OpenCode's equivalent "general"
863
+ convertedContent = convertedContent.replace(/subagent_type="general-purpose"/g, 'subagent_type="general"');
864
+
865
+ // Check if content has frontmatter
866
+ if (!convertedContent.startsWith('---')) {
867
+ return convertedContent;
868
+ }
869
+
870
+ // Find the end of frontmatter
871
+ const endIndex = convertedContent.indexOf('---', 3);
872
+ if (endIndex === -1) {
873
+ return convertedContent;
874
+ }
875
+
876
+ const frontmatter = convertedContent.substring(3, endIndex).trim();
877
+ const body = convertedContent.substring(endIndex + 3);
878
+
879
+ // Parse frontmatter line by line (simple YAML parsing)
880
+ const lines = frontmatter.split('\n');
881
+ const newLines = [];
882
+ let inAllowedTools = false;
883
+ const allowedTools = [];
884
+
885
+ for (const line of lines) {
886
+ const trimmed = line.trim();
887
+
888
+ // Detect start of allowed-tools array
889
+ if (trimmed.startsWith('allowed-tools:')) {
890
+ inAllowedTools = true;
891
+ continue;
892
+ }
893
+
894
+ // Detect inline tools: field (comma-separated string)
895
+ if (trimmed.startsWith('tools:')) {
896
+ const toolsValue = trimmed.substring(6).trim();
897
+ if (toolsValue) {
898
+ // Parse comma-separated tools
899
+ const tools = toolsValue.split(',').map(t => t.trim()).filter(t => t);
900
+ allowedTools.push(...tools);
901
+ }
902
+ continue;
903
+ }
904
+
905
+ // Remove name: field - opencode uses filename for command name
906
+ if (trimmed.startsWith('name:')) {
907
+ continue;
908
+ }
909
+
910
+ // Convert color names to hex for opencode
911
+ if (trimmed.startsWith('color:')) {
912
+ const colorValue = trimmed.substring(6).trim().toLowerCase();
913
+ const hexColor = colorNameToHex[colorValue];
914
+ if (hexColor) {
915
+ newLines.push(`color: "${hexColor}"`);
916
+ } else if (colorValue.startsWith('#')) {
917
+ // Validate hex color format (#RGB or #RRGGBB)
918
+ if (/^#[0-9a-f]{3}$|^#[0-9a-f]{6}$/i.test(colorValue)) {
919
+ // Already hex and valid, keep as is
920
+ newLines.push(line);
921
+ }
922
+ // Skip invalid hex colors
923
+ }
924
+ // Skip unknown color names
925
+ continue;
926
+ }
927
+
928
+ // Collect allowed-tools items
929
+ if (inAllowedTools) {
930
+ if (trimmed.startsWith('- ')) {
931
+ allowedTools.push(trimmed.substring(2).trim());
932
+ continue;
933
+ } else if (trimmed && !trimmed.startsWith('-')) {
934
+ // End of array, new field started
935
+ inAllowedTools = false;
936
+ }
937
+ }
938
+
939
+ // Keep other fields (including name: which opencode ignores)
940
+ if (!inAllowedTools) {
941
+ newLines.push(line);
942
+ }
943
+ }
944
+
945
+ // Add tools object if we had allowed-tools or tools
946
+ if (allowedTools.length > 0) {
947
+ newLines.push('tools:');
948
+ for (const tool of allowedTools) {
949
+ newLines.push(` ${convertToolName(tool)}: true`);
950
+ }
951
+ }
952
+
953
+ // Rebuild frontmatter (body already has tool names converted)
954
+ const newFrontmatter = newLines.join('\n').trim();
955
+ return `---\n${newFrontmatter}\n---${body}`;
956
+ }
957
+
958
+ /**
959
+ * Convert Claude Code markdown command to Gemini TOML format
960
+ * @param {string} content - Markdown file content with YAML frontmatter
961
+ * @returns {string} - TOML content
962
+ */
963
+ function convertClaudeToGeminiToml(content) {
964
+ // Check if content has frontmatter
965
+ if (!content.startsWith('---')) {
966
+ return `prompt = ${JSON.stringify(content)}\n`;
967
+ }
968
+
969
+ const endIndex = content.indexOf('---', 3);
970
+ if (endIndex === -1) {
971
+ return `prompt = ${JSON.stringify(content)}\n`;
972
+ }
973
+
974
+ const frontmatter = content.substring(3, endIndex).trim();
975
+ const body = content.substring(endIndex + 3).trim();
976
+
977
+ // Extract description from frontmatter
978
+ let description = '';
979
+ const lines = frontmatter.split('\n');
980
+ for (const line of lines) {
981
+ const trimmed = line.trim();
982
+ if (trimmed.startsWith('description:')) {
983
+ description = trimmed.substring(12).trim();
984
+ break;
985
+ }
986
+ }
987
+
988
+ // Construct TOML
989
+ let toml = '';
990
+ if (description) {
991
+ toml += `description = ${JSON.stringify(description)}\n`;
992
+ }
993
+
994
+ toml += `prompt = ${JSON.stringify(body)}\n`;
995
+
996
+ return toml;
997
+ }
998
+
999
+ /**
1000
+ * Copy commands to a flat structure for OpenCode
1001
+ * OpenCode expects: command/pbr-help.md (invoked as /pbr-help)
1002
+ * Source structure: commands/pbr/help.md
1003
+ *
1004
+ * @param {string} srcDir - Source directory (e.g., commands/pbr/)
1005
+ * @param {string} destDir - Destination directory (e.g., command/)
1006
+ * @param {string} prefix - Prefix for filenames (e.g., 'pbr')
1007
+ * @param {string} pathPrefix - Path prefix for file references
1008
+ * @param {string} runtime - Target runtime ('claude' or 'opencode')
1009
+ */
1010
+ function copyFlattenedCommands(srcDir, destDir, prefix, pathPrefix, runtime) {
1011
+ if (!fs.existsSync(srcDir)) {
1012
+ return;
1013
+ }
1014
+
1015
+ // Remove old pbr-*.md files before copying new ones
1016
+ if (fs.existsSync(destDir)) {
1017
+ for (const file of fs.readdirSync(destDir)) {
1018
+ if (file.startsWith(`${prefix}-`) && file.endsWith('.md')) {
1019
+ fs.unlinkSync(path.join(destDir, file));
1020
+ }
1021
+ }
1022
+ } else {
1023
+ fs.mkdirSync(destDir, { recursive: true });
1024
+ }
1025
+
1026
+ const entries = fs.readdirSync(srcDir, { withFileTypes: true });
1027
+
1028
+ for (const entry of entries) {
1029
+ const srcPath = path.join(srcDir, entry.name);
1030
+
1031
+ if (entry.isDirectory()) {
1032
+ // Recurse into subdirectories, adding to prefix
1033
+ // e.g., commands/pbr/debug/start.md -> command/pbr-debug-start.md
1034
+ copyFlattenedCommands(srcPath, destDir, `${prefix}-${entry.name}`, pathPrefix, runtime);
1035
+ } else if (entry.name.endsWith('.md')) {
1036
+ // Flatten: help.md -> pbr-help.md
1037
+ const baseName = entry.name.replace('.md', '');
1038
+ const destName = `${prefix}-${baseName}.md`;
1039
+ const destPath = path.join(destDir, destName);
1040
+
1041
+ let content = fs.readFileSync(srcPath, 'utf8');
1042
+
1043
+ // Resolve skill stubs to inline full SKILL.md content
1044
+ const repoRoot = path.resolve(srcDir, '..', '..');
1045
+ const resolved = resolveSkillStub(content, repoRoot);
1046
+ if (resolved) content = resolved;
1047
+
1048
+ const globalClaudeRegex = /~\/\.claude\//g;
1049
+ const globalClaudeHomeRegex = /\$HOME\/\.claude\//g;
1050
+ const localClaudeRegex = /\.\/\.claude\//g;
1051
+ const opencodeDirRegex = /~\/\.opencode\//g;
1052
+ content = content.replace(globalClaudeRegex, pathPrefix);
1053
+ content = content.replace(globalClaudeHomeRegex, toHomePrefix(pathPrefix));
1054
+ content = content.replace(localClaudeRegex, `./${getDirName(runtime)}/`);
1055
+ content = content.replace(opencodeDirRegex, pathPrefix);
1056
+ content = processAttribution(content, getCommitAttribution(runtime));
1057
+ content = convertClaudeToOpencodeFrontmatter(content);
1058
+
1059
+ fs.writeFileSync(destPath, content);
1060
+ }
1061
+ }
1062
+ }
1063
+
1064
+ function listCodexSkillNames(skillsDir, prefix = 'pbr-') {
1065
+ if (!fs.existsSync(skillsDir)) return [];
1066
+ const entries = fs.readdirSync(skillsDir, { withFileTypes: true });
1067
+ return entries
1068
+ .filter(entry => entry.isDirectory() && entry.name.startsWith(prefix))
1069
+ .filter(entry => fs.existsSync(path.join(skillsDir, entry.name, 'SKILL.md')))
1070
+ .map(entry => entry.name)
1071
+ .sort();
1072
+ }
1073
+
1074
+ function copyCommandsAsCodexSkills(srcDir, skillsDir, prefix, pathPrefix, runtime) {
1075
+ if (!fs.existsSync(srcDir)) {
1076
+ return;
1077
+ }
1078
+
1079
+ fs.mkdirSync(skillsDir, { recursive: true });
1080
+
1081
+ // Remove previous PBR Codex skills to avoid stale command skills.
1082
+ const existing = fs.readdirSync(skillsDir, { withFileTypes: true });
1083
+ for (const entry of existing) {
1084
+ if (entry.isDirectory() && entry.name.startsWith(`${prefix}-`)) {
1085
+ fs.rmSync(path.join(skillsDir, entry.name), { recursive: true });
1086
+ }
1087
+ }
1088
+
1089
+ function recurse(currentSrcDir, currentPrefix) {
1090
+ const entries = fs.readdirSync(currentSrcDir, { withFileTypes: true });
1091
+
1092
+ for (const entry of entries) {
1093
+ const srcPath = path.join(currentSrcDir, entry.name);
1094
+ if (entry.isDirectory()) {
1095
+ recurse(srcPath, `${currentPrefix}-${entry.name}`);
1096
+ continue;
1097
+ }
1098
+
1099
+ if (!entry.name.endsWith('.md')) {
1100
+ continue;
1101
+ }
1102
+
1103
+ const baseName = entry.name.replace('.md', '');
1104
+ const skillName = `${currentPrefix}-${baseName}`;
1105
+ const skillDir = path.join(skillsDir, skillName);
1106
+ fs.mkdirSync(skillDir, { recursive: true });
1107
+
1108
+ let content = fs.readFileSync(srcPath, 'utf8');
1109
+
1110
+ // Resolve skill stubs to inline full SKILL.md content
1111
+ const repoRoot = path.resolve(currentSrcDir, '..', '..');
1112
+ const resolved = resolveSkillStub(content, repoRoot);
1113
+ if (resolved) content = resolved;
1114
+
1115
+ const globalClaudeRegex = /~\/\.claude\//g;
1116
+ const globalClaudeHomeRegex = /\$HOME\/\.claude\//g;
1117
+ const localClaudeRegex = /\.\/\.claude\//g;
1118
+ const codexDirRegex = /~\/\.codex\//g;
1119
+ content = content.replace(globalClaudeRegex, pathPrefix);
1120
+ content = content.replace(globalClaudeHomeRegex, toHomePrefix(pathPrefix));
1121
+ content = content.replace(localClaudeRegex, `./${getDirName(runtime)}/`);
1122
+ content = content.replace(codexDirRegex, pathPrefix);
1123
+ content = processAttribution(content, getCommitAttribution(runtime));
1124
+ content = convertClaudeCommandToCodexSkill(content, skillName);
1125
+
1126
+ fs.writeFileSync(path.join(skillDir, 'SKILL.md'), content);
1127
+ }
1128
+ }
1129
+
1130
+ recurse(srcDir, prefix);
1131
+ }
1132
+
1133
+ /**
1134
+ * Recursively copy directory, replacing paths in .md files
1135
+ * Deletes existing destDir first to remove orphaned files from previous versions
1136
+ * @param {string} srcDir - Source directory
1137
+ * @param {string} destDir - Destination directory
1138
+ * @param {string} pathPrefix - Path prefix for file references
1139
+ * @param {string} runtime - Target runtime ('claude', 'opencode', 'gemini', 'codex')
1140
+ */
1141
+ // Matches both forms:
1142
+ // "This command is provided by the `pbr:audit` skill."
1143
+ // "This command is provided by the `pbr:todo` skill with subcommand `add`."
1144
+ const SKILL_STUB_RE = /^This command is provided by the `pbr:([\w-]+)` skill(?:\s+with subcommand `([\w-]+)`)?\.\s*$/m;
1145
+
1146
+ /**
1147
+ * Resolve a command stub to its full SKILL.md content.
1148
+ * Command stubs are thin wrappers like "This command is provided by the `pbr:audit` skill."
1149
+ * This function finds the corresponding SKILL.md and merges its content into the command,
1150
+ * preserving the command's frontmatter (with allowed-tools/argument-hint from the skill).
1151
+ *
1152
+ * For subcommand stubs (e.g., "with subcommand `add`"), the resolved content includes
1153
+ * a subcommand directive so the skill knows which action to take.
1154
+ *
1155
+ * @param {string} content - The command file content
1156
+ * @param {string} repoRoot - Root of the PBR repo (where plan-build-run/skills/ lives)
1157
+ * @returns {string|null} Merged content, or null if not a stub or SKILL.md not found
1158
+ */
1159
+ function resolveSkillStub(content, repoRoot) {
1160
+ const match = content.match(SKILL_STUB_RE);
1161
+ if (!match) return null;
1162
+
1163
+ const skillName = match[1];
1164
+ const subcommand = match[2] || null;
1165
+ const skillPath = path.join(repoRoot, 'plan-build-run', 'skills', skillName, 'SKILL.md');
1166
+ if (!fs.existsSync(skillPath)) return null;
1167
+
1168
+ const skillContent = fs.readFileSync(skillPath, 'utf8');
1169
+
1170
+ // Parse command frontmatter
1171
+ const cmdFmMatch = content.match(/^---\n([\s\S]*?)\n---\n/);
1172
+ // Parse skill frontmatter
1173
+ const skillFmMatch = skillContent.match(/^---\n([\s\S]*?)\n---\n/);
1174
+ const skillBody = skillFmMatch
1175
+ ? skillContent.slice(skillFmMatch[0].length)
1176
+ : skillContent;
1177
+
1178
+ // Merge frontmatter: start with command's, add skill's allowed-tools and argument-hint
1179
+ let mergedFm = cmdFmMatch ? cmdFmMatch[1] : '';
1180
+ if (skillFmMatch) {
1181
+ const skillFm = skillFmMatch[1];
1182
+ // Add allowed-tools from skill if command doesn't have it
1183
+ if (!mergedFm.includes('allowed-tools') && skillFm.includes('allowed-tools')) {
1184
+ const toolsLine = skillFm.match(/^allowed-tools:.*$/m);
1185
+ if (toolsLine) mergedFm += '\n' + toolsLine[0];
1186
+ }
1187
+ // Add argument-hint from skill if command doesn't have it
1188
+ if (!mergedFm.includes('argument-hint') && skillFm.includes('argument-hint')) {
1189
+ const hintLine = skillFm.match(/^argument-hint:.*$/m);
1190
+ if (hintLine) mergedFm += '\n' + hintLine[0];
1191
+ }
1192
+ }
1193
+
1194
+ // For subcommand stubs, prepend a directive so the skill knows the active subcommand
1195
+ const subcommandDirective = subcommand
1196
+ ? `\n**SUBCOMMAND: \`${subcommand}\`** — Execute only the \`${subcommand}\` action from this skill.\n`
1197
+ : '';
1198
+
1199
+ return `---\n${mergedFm}\n---\n${subcommandDirective}${skillBody}`;
1200
+ }
1201
+
1202
+ function copyWithPathReplacement(srcDir, destDir, pathPrefix, runtime, isCommand = false) {
1203
+ const isOpencode = runtime === 'opencode';
1204
+ const isCodex = runtime === 'codex';
1205
+ const dirName = getDirName(runtime);
1206
+
1207
+ // Clean install: remove existing destination to prevent orphaned files
1208
+ if (fs.existsSync(destDir)) {
1209
+ fs.rmSync(destDir, { recursive: true });
1210
+ }
1211
+ fs.mkdirSync(destDir, { recursive: true });
1212
+
1213
+ const entries = fs.readdirSync(srcDir, { withFileTypes: true });
1214
+
1215
+ for (const entry of entries) {
1216
+ const srcPath = path.join(srcDir, entry.name);
1217
+ const destPath = path.join(destDir, entry.name);
1218
+
1219
+ if (entry.isDirectory()) {
1220
+ copyWithPathReplacement(srcPath, destPath, pathPrefix, runtime, isCommand);
1221
+ } else if (entry.name.endsWith('.md')) {
1222
+ // Replace ~/.claude/ and $HOME/.claude/ and ./.claude/ with runtime-appropriate paths
1223
+ let content = fs.readFileSync(srcPath, 'utf8');
1224
+
1225
+ // Resolve command stubs: inline the full SKILL.md content so Claude Code
1226
+ // gets the complete instructions when the user invokes /pbr:<command>.
1227
+ // Without this, command files are thin stubs that say "provided by skill X"
1228
+ // and Claude loops trying to load the skill content that never arrives.
1229
+ if (isCommand) {
1230
+ const repoRoot = path.resolve(srcDir, '..', '..');
1231
+ const resolved = resolveSkillStub(content, repoRoot);
1232
+ if (resolved) content = resolved;
1233
+ }
1234
+
1235
+ const globalClaudeRegex = /~\/\.claude\//g;
1236
+ const globalClaudeHomeRegex = /\$HOME\/\.claude\//g;
1237
+ const localClaudeRegex = /\.\/\.claude\//g;
1238
+ content = content.replace(globalClaudeRegex, pathPrefix);
1239
+ content = content.replace(globalClaudeHomeRegex, toHomePrefix(pathPrefix));
1240
+ content = content.replace(localClaudeRegex, `./${dirName}/`);
1241
+ content = processAttribution(content, getCommitAttribution(runtime));
1242
+
1243
+ // Convert frontmatter for opencode compatibility
1244
+ if (isOpencode) {
1245
+ content = convertClaudeToOpencodeFrontmatter(content);
1246
+ fs.writeFileSync(destPath, content);
1247
+ } else if (runtime === 'gemini') {
1248
+ if (isCommand) {
1249
+ // Convert to TOML for Gemini (strip <sub> tags — terminals can't render subscript)
1250
+ content = stripSubTags(content);
1251
+ const tomlContent = convertClaudeToGeminiToml(content);
1252
+ // Replace extension with .toml
1253
+ const tomlPath = destPath.replace(/\.md$/, '.toml');
1254
+ fs.writeFileSync(tomlPath, tomlContent);
1255
+ } else {
1256
+ fs.writeFileSync(destPath, content);
1257
+ }
1258
+ } else if (isCodex) {
1259
+ content = convertClaudeToCodexMarkdown(content);
1260
+ fs.writeFileSync(destPath, content);
1261
+ } else {
1262
+ fs.writeFileSync(destPath, content);
1263
+ }
1264
+ } else {
1265
+ fs.copyFileSync(srcPath, destPath);
1266
+ }
1267
+ }
1268
+ }
1269
+
1270
+ /**
1271
+ * Clean up orphaned files from previous PBR versions
1272
+ */
1273
+ function cleanupOrphanedFiles(configDir) {
1274
+ const orphanedFiles = [
1275
+ 'hooks/pbr-notify.sh', // Removed in v1.6.x
1276
+ 'hooks/statusline.js', // Renamed to pbr-statusline.js in v1.9.0
1277
+ 'hooks/pbr-context-monitor.js', // Removed in v2.0 — merged into track-context-budget.js
1278
+ ];
1279
+
1280
+ for (const relPath of orphanedFiles) {
1281
+ const fullPath = path.join(configDir, relPath);
1282
+ if (fs.existsSync(fullPath)) {
1283
+ fs.unlinkSync(fullPath);
1284
+ console.log(` ${green}✓${reset} Removed orphaned ${relPath}`);
1285
+ }
1286
+ }
1287
+ }
1288
+
1289
+ /**
1290
+ * Clean up orphaned hook registrations from settings.json
1291
+ */
1292
+ function cleanupOrphanedHooks(settings) {
1293
+ const orphanedHookPatterns = [
1294
+ 'pbr-notify.sh', // Removed in v1.6.x
1295
+ 'hooks/statusline.js', // Renamed to pbr-statusline.js in v1.9.0
1296
+ 'pbr-intel-index.js', // Removed in v1.9.2
1297
+ 'pbr-intel-session.js', // Removed in v1.9.2
1298
+ 'pbr-intel-prune.js', // Removed in v1.9.2
1299
+ ];
1300
+
1301
+ let cleanedHooks = false;
1302
+
1303
+ // Check all hook event types (Stop, SessionStart, etc.)
1304
+ if (settings.hooks) {
1305
+ for (const eventType of Object.keys(settings.hooks)) {
1306
+ const hookEntries = settings.hooks[eventType];
1307
+ if (Array.isArray(hookEntries)) {
1308
+ // Filter out entries that contain orphaned hooks
1309
+ const filtered = hookEntries.filter(entry => {
1310
+ if (entry.hooks && Array.isArray(entry.hooks)) {
1311
+ // Check if any hook in this entry matches orphaned patterns
1312
+ const hasOrphaned = entry.hooks.some(h =>
1313
+ h.command && orphanedHookPatterns.some(pattern => h.command.includes(pattern))
1314
+ );
1315
+ if (hasOrphaned) {
1316
+ cleanedHooks = true;
1317
+ return false; // Remove this entry
1318
+ }
1319
+ }
1320
+ return true; // Keep this entry
1321
+ });
1322
+ settings.hooks[eventType] = filtered;
1323
+ }
1324
+ }
1325
+ }
1326
+
1327
+ if (cleanedHooks) {
1328
+ console.log(` ${green}✓${reset} Removed orphaned hook registrations`);
1329
+ }
1330
+
1331
+ // Fix #330: Update statusLine if it points to old PBR statusline.js path
1332
+ // Only match the specific old PBR path pattern (hooks/statusline.js),
1333
+ // not third-party statusline scripts that happen to contain 'statusline.js'
1334
+ if (settings.statusLine && settings.statusLine.command &&
1335
+ /hooks[\/\\]statusline\.js/.test(settings.statusLine.command)) {
1336
+ settings.statusLine.command = settings.statusLine.command.replace(
1337
+ /hooks([\/\\])statusline\.js/,
1338
+ 'hooks$1pbr-statusline.js'
1339
+ );
1340
+ console.log(` ${green}✓${reset} Updated statusline path (hooks/statusline.js → hooks/pbr-statusline.js)`);
1341
+ }
1342
+
1343
+ return settings;
1344
+ }
1345
+
1346
+ /**
1347
+ * Uninstall PBR from the specified directory for a specific runtime
1348
+ * Removes only PBR-specific files/directories, preserves user content
1349
+ * @param {boolean} isGlobal - Whether to uninstall from global or local
1350
+ * @param {string} runtime - Target runtime ('claude', 'opencode', 'gemini', 'codex')
1351
+ */
1352
+ function uninstall(isGlobal, runtime = 'claude') {
1353
+ const isOpencode = runtime === 'opencode';
1354
+ const isCodex = runtime === 'codex';
1355
+ const dirName = getDirName(runtime);
1356
+
1357
+ // Get the target directory based on runtime and install type
1358
+ const targetDir = isGlobal
1359
+ ? getGlobalDir(runtime, explicitConfigDir)
1360
+ : path.join(process.cwd(), dirName);
1361
+
1362
+ const locationLabel = isGlobal
1363
+ ? targetDir.replace(os.homedir(), '~')
1364
+ : targetDir.replace(process.cwd(), '.');
1365
+
1366
+ let runtimeLabel = 'Claude Code';
1367
+ if (runtime === 'opencode') runtimeLabel = 'OpenCode';
1368
+ if (runtime === 'gemini') runtimeLabel = 'Gemini';
1369
+ if (runtime === 'codex') runtimeLabel = 'Codex';
1370
+
1371
+ console.log(` Uninstalling PBR from ${cyan}${runtimeLabel}${reset} at ${cyan}${locationLabel}${reset}\n`);
1372
+
1373
+ // Check if target directory exists
1374
+ if (!fs.existsSync(targetDir)) {
1375
+ console.log(` ${yellow}⚠${reset} Directory does not exist: ${locationLabel}`);
1376
+ console.log(` Nothing to uninstall.\n`);
1377
+ return;
1378
+ }
1379
+
1380
+ let removedCount = 0;
1381
+
1382
+ // 1. Remove PBR commands/skills
1383
+ if (isOpencode) {
1384
+ // OpenCode: remove command/pbr-*.md files
1385
+ const commandDir = path.join(targetDir, 'command');
1386
+ if (fs.existsSync(commandDir)) {
1387
+ const files = fs.readdirSync(commandDir);
1388
+ for (const file of files) {
1389
+ if (file.startsWith('pbr-') && file.endsWith('.md')) {
1390
+ fs.unlinkSync(path.join(commandDir, file));
1391
+ removedCount++;
1392
+ }
1393
+ }
1394
+ console.log(` ${green}✓${reset} Removed PBR commands from command/`);
1395
+ }
1396
+ } else if (isCodex) {
1397
+ // Codex: remove skills/pbr-*/SKILL.md skill directories
1398
+ const skillsDir = path.join(targetDir, 'skills');
1399
+ if (fs.existsSync(skillsDir)) {
1400
+ let skillCount = 0;
1401
+ const entries = fs.readdirSync(skillsDir, { withFileTypes: true });
1402
+ for (const entry of entries) {
1403
+ if (entry.isDirectory() && entry.name.startsWith('pbr-')) {
1404
+ fs.rmSync(path.join(skillsDir, entry.name), { recursive: true });
1405
+ skillCount++;
1406
+ }
1407
+ }
1408
+ if (skillCount > 0) {
1409
+ removedCount++;
1410
+ console.log(` ${green}✓${reset} Removed ${skillCount} Codex skills`);
1411
+ }
1412
+ }
1413
+
1414
+ // Codex: remove PBR agent .toml config files
1415
+ const codexAgentsDir = path.join(targetDir, 'agents');
1416
+ if (fs.existsSync(codexAgentsDir)) {
1417
+ const tomlFiles = fs.readdirSync(codexAgentsDir);
1418
+ let tomlCount = 0;
1419
+ for (const file of tomlFiles) {
1420
+ if (file.startsWith('pbr-') && file.endsWith('.toml')) {
1421
+ fs.unlinkSync(path.join(codexAgentsDir, file));
1422
+ tomlCount++;
1423
+ }
1424
+ }
1425
+ if (tomlCount > 0) {
1426
+ removedCount++;
1427
+ console.log(` ${green}✓${reset} Removed ${tomlCount} agent .toml configs`);
1428
+ }
1429
+ }
1430
+
1431
+ // Codex: clean PBR sections from config.toml
1432
+ const configPath = path.join(targetDir, 'config.toml');
1433
+ if (fs.existsSync(configPath)) {
1434
+ const content = fs.readFileSync(configPath, 'utf8');
1435
+ const cleaned = stripPbrFromCodexConfig(content);
1436
+ if (cleaned === null) {
1437
+ // File is empty after stripping — delete it
1438
+ fs.unlinkSync(configPath);
1439
+ removedCount++;
1440
+ console.log(` ${green}✓${reset} Removed config.toml (was PBR-only)`);
1441
+ } else if (cleaned !== content) {
1442
+ fs.writeFileSync(configPath, cleaned);
1443
+ removedCount++;
1444
+ console.log(` ${green}✓${reset} Cleaned PBR sections from config.toml`);
1445
+ }
1446
+ }
1447
+ } else {
1448
+ // Claude Code & Gemini: remove commands/pbr/ directory
1449
+ const pbrCommandsDir = path.join(targetDir, 'commands', 'pbr');
1450
+ if (fs.existsSync(pbrCommandsDir)) {
1451
+ fs.rmSync(pbrCommandsDir, { recursive: true });
1452
+ removedCount++;
1453
+ console.log(` ${green}✓${reset} Removed commands/pbr/`);
1454
+ }
1455
+ }
1456
+
1457
+ // 2. Remove plan-build-run directory
1458
+ const pbrDir = path.join(targetDir, 'plan-build-run');
1459
+ if (fs.existsSync(pbrDir)) {
1460
+ fs.rmSync(pbrDir, { recursive: true });
1461
+ removedCount++;
1462
+ console.log(` ${green}✓${reset} Removed plan-build-run/`);
1463
+ }
1464
+
1465
+ // 3. Remove PBR agents (pbr-*.md files only)
1466
+ const agentsDir = path.join(targetDir, 'agents');
1467
+ if (fs.existsSync(agentsDir)) {
1468
+ const files = fs.readdirSync(agentsDir);
1469
+ let agentCount = 0;
1470
+ for (const file of files) {
1471
+ if (file.startsWith('pbr-') && file.endsWith('.md')) {
1472
+ fs.unlinkSync(path.join(agentsDir, file));
1473
+ agentCount++;
1474
+ }
1475
+ }
1476
+ if (agentCount > 0) {
1477
+ removedCount++;
1478
+ console.log(` ${green}✓${reset} Removed ${agentCount} PBR agents`);
1479
+ }
1480
+ }
1481
+
1482
+ // 4. Remove PBR hooks
1483
+ const hooksDir = path.join(targetDir, 'hooks');
1484
+ if (fs.existsSync(hooksDir)) {
1485
+ const pbrHooks = ['pbr-statusline.js', 'pbr-check-update.js', 'pbr-check-update.sh', 'pbr-context-monitor.js'];
1486
+ let hookCount = 0;
1487
+ for (const hook of pbrHooks) {
1488
+ const hookPath = path.join(hooksDir, hook);
1489
+ if (fs.existsSync(hookPath)) {
1490
+ fs.unlinkSync(hookPath);
1491
+ hookCount++;
1492
+ }
1493
+ }
1494
+ if (hookCount > 0) {
1495
+ removedCount++;
1496
+ console.log(` ${green}✓${reset} Removed ${hookCount} PBR hooks`);
1497
+ }
1498
+ }
1499
+
1500
+ // 5. Remove PBR package.json (CommonJS mode marker)
1501
+ const pkgJsonPath = path.join(targetDir, 'package.json');
1502
+ if (fs.existsSync(pkgJsonPath)) {
1503
+ try {
1504
+ const content = fs.readFileSync(pkgJsonPath, 'utf8').trim();
1505
+ // Only remove if it's our minimal CommonJS marker
1506
+ if (content === '{"type":"commonjs"}') {
1507
+ fs.unlinkSync(pkgJsonPath);
1508
+ removedCount++;
1509
+ console.log(` ${green}✓${reset} Removed PBR package.json`);
1510
+ }
1511
+ } catch (e) {
1512
+ // Ignore read errors
1513
+ }
1514
+ }
1515
+
1516
+ // 6. Clean up settings.json (remove PBR hooks and statusline)
1517
+ const settingsPath = path.join(targetDir, 'settings.json');
1518
+ if (fs.existsSync(settingsPath)) {
1519
+ let settings = readSettings(settingsPath);
1520
+ let settingsModified = false;
1521
+
1522
+ // Remove PBR statusline if it references our hook
1523
+ if (settings.statusLine && settings.statusLine.command &&
1524
+ settings.statusLine.command.includes('pbr-statusline')) {
1525
+ delete settings.statusLine;
1526
+ settingsModified = true;
1527
+ console.log(` ${green}✓${reset} Removed PBR statusline from settings`);
1528
+ }
1529
+
1530
+ // Remove PBR hooks from SessionStart
1531
+ if (settings.hooks && settings.hooks.SessionStart) {
1532
+ const before = settings.hooks.SessionStart.length;
1533
+ settings.hooks.SessionStart = settings.hooks.SessionStart.filter(entry => {
1534
+ if (entry.hooks && Array.isArray(entry.hooks)) {
1535
+ // Filter out PBR hooks
1536
+ const hasPbrHook = entry.hooks.some(h =>
1537
+ h.command && (h.command.includes('pbr-check-update') || h.command.includes('pbr-statusline'))
1538
+ );
1539
+ return !hasPbrHook;
1540
+ }
1541
+ return true;
1542
+ });
1543
+ if (settings.hooks.SessionStart.length < before) {
1544
+ settingsModified = true;
1545
+ console.log(` ${green}✓${reset} Removed PBR hooks from settings`);
1546
+ }
1547
+ // Clean up empty array
1548
+ if (settings.hooks.SessionStart.length === 0) {
1549
+ delete settings.hooks.SessionStart;
1550
+ }
1551
+ }
1552
+
1553
+ // Remove PBR hooks from PostToolUse and AfterTool (Gemini uses AfterTool)
1554
+ for (const eventName of ['PostToolUse', 'AfterTool']) {
1555
+ if (settings.hooks && settings.hooks[eventName]) {
1556
+ const before = settings.hooks[eventName].length;
1557
+ settings.hooks[eventName] = settings.hooks[eventName].filter(entry => {
1558
+ if (entry.hooks && Array.isArray(entry.hooks)) {
1559
+ const hasPbrHook = entry.hooks.some(h =>
1560
+ h.command && h.command.includes('pbr-context-monitor')
1561
+ );
1562
+ return !hasPbrHook;
1563
+ }
1564
+ return true;
1565
+ });
1566
+ if (settings.hooks[eventName].length < before) {
1567
+ settingsModified = true;
1568
+ console.log(` ${green}✓${reset} Removed context monitor hook from settings`);
1569
+ }
1570
+ if (settings.hooks[eventName].length === 0) {
1571
+ delete settings.hooks[eventName];
1572
+ }
1573
+ }
1574
+ }
1575
+
1576
+ // Clean up empty hooks object
1577
+ if (settings.hooks && Object.keys(settings.hooks).length === 0) {
1578
+ delete settings.hooks;
1579
+ }
1580
+
1581
+ if (settingsModified) {
1582
+ writeSettings(settingsPath, settings);
1583
+ removedCount++;
1584
+ }
1585
+ }
1586
+
1587
+ // 6. For OpenCode, clean up permissions from opencode.json
1588
+ if (isOpencode) {
1589
+ // For local uninstalls, clean up ./.opencode/opencode.json
1590
+ // For global uninstalls, clean up ~/.config/opencode/opencode.json
1591
+ const opencodeConfigDir = isGlobal
1592
+ ? getOpencodeGlobalDir()
1593
+ : path.join(process.cwd(), '.opencode');
1594
+ const configPath = path.join(opencodeConfigDir, 'opencode.json');
1595
+ if (fs.existsSync(configPath)) {
1596
+ try {
1597
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
1598
+ let modified = false;
1599
+
1600
+ // Remove PBR permission entries
1601
+ if (config.permission) {
1602
+ for (const permType of ['read', 'external_directory']) {
1603
+ if (config.permission[permType]) {
1604
+ const keys = Object.keys(config.permission[permType]);
1605
+ for (const key of keys) {
1606
+ if (key.includes('plan-build-run')) {
1607
+ delete config.permission[permType][key];
1608
+ modified = true;
1609
+ }
1610
+ }
1611
+ // Clean up empty objects
1612
+ if (Object.keys(config.permission[permType]).length === 0) {
1613
+ delete config.permission[permType];
1614
+ }
1615
+ }
1616
+ }
1617
+ if (Object.keys(config.permission).length === 0) {
1618
+ delete config.permission;
1619
+ }
1620
+ }
1621
+
1622
+ if (modified) {
1623
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
1624
+ removedCount++;
1625
+ console.log(` ${green}✓${reset} Removed PBR permissions from opencode.json`);
1626
+ }
1627
+ } catch (e) {
1628
+ // Ignore JSON parse errors
1629
+ }
1630
+ }
1631
+ }
1632
+
1633
+ if (removedCount === 0) {
1634
+ console.log(` ${yellow}⚠${reset} No PBR files found to remove.`);
1635
+ }
1636
+
1637
+ console.log(`
1638
+ ${green}Done!${reset} PBR has been uninstalled from ${runtimeLabel}.
1639
+ Your other files and settings have been preserved.
1640
+ `);
1641
+ }
1642
+
1643
+ /**
1644
+ * Parse JSONC (JSON with Comments) by stripping comments and trailing commas.
1645
+ * OpenCode supports JSONC format via jsonc-parser, so users may have comments.
1646
+ * This is a lightweight inline parser to avoid adding dependencies.
1647
+ */
1648
+ function parseJsonc(content) {
1649
+ // Strip BOM if present
1650
+ if (content.charCodeAt(0) === 0xFEFF) {
1651
+ content = content.slice(1);
1652
+ }
1653
+
1654
+ // Remove single-line and block comments while preserving strings
1655
+ let result = '';
1656
+ let inString = false;
1657
+ let i = 0;
1658
+ while (i < content.length) {
1659
+ const char = content[i];
1660
+ const next = content[i + 1];
1661
+
1662
+ if (inString) {
1663
+ result += char;
1664
+ // Handle escape sequences
1665
+ if (char === '\\' && i + 1 < content.length) {
1666
+ result += next;
1667
+ i += 2;
1668
+ continue;
1669
+ }
1670
+ if (char === '"') {
1671
+ inString = false;
1672
+ }
1673
+ i++;
1674
+ } else {
1675
+ if (char === '"') {
1676
+ inString = true;
1677
+ result += char;
1678
+ i++;
1679
+ } else if (char === '/' && next === '/') {
1680
+ // Skip single-line comment until end of line
1681
+ while (i < content.length && content[i] !== '\n') {
1682
+ i++;
1683
+ }
1684
+ } else if (char === '/' && next === '*') {
1685
+ // Skip block comment
1686
+ i += 2;
1687
+ while (i < content.length - 1 && !(content[i] === '*' && content[i + 1] === '/')) {
1688
+ i++;
1689
+ }
1690
+ i += 2; // Skip closing */
1691
+ } else {
1692
+ result += char;
1693
+ i++;
1694
+ }
1695
+ }
1696
+ }
1697
+
1698
+ // Remove trailing commas before } or ]
1699
+ result = result.replace(/,(\s*[}\]])/g, '$1');
1700
+
1701
+ return JSON.parse(result);
1702
+ }
1703
+
1704
+ /**
1705
+ * Configure OpenCode permissions to allow reading PBR reference docs
1706
+ * This prevents permission prompts when PBR accesses the plan-build-run directory
1707
+ * @param {boolean} isGlobal - Whether this is a global or local install
1708
+ */
1709
+ function configureOpencodePermissions(isGlobal = true) {
1710
+ // For local installs, use ./.opencode/opencode.json
1711
+ // For global installs, use ~/.config/opencode/opencode.json
1712
+ const opencodeConfigDir = isGlobal
1713
+ ? getOpencodeGlobalDir()
1714
+ : path.join(process.cwd(), '.opencode');
1715
+ const configPath = path.join(opencodeConfigDir, 'opencode.json');
1716
+
1717
+ // Ensure config directory exists
1718
+ fs.mkdirSync(opencodeConfigDir, { recursive: true });
1719
+
1720
+ // Read existing config or create empty object
1721
+ let config = {};
1722
+ if (fs.existsSync(configPath)) {
1723
+ try {
1724
+ const content = fs.readFileSync(configPath, 'utf8');
1725
+ config = parseJsonc(content);
1726
+ } catch (e) {
1727
+ // Cannot parse - DO NOT overwrite user's config
1728
+ console.log(` ${yellow}⚠${reset} Could not parse opencode.json - skipping permission config`);
1729
+ console.log(` ${dim}Reason: ${e.message}${reset}`);
1730
+ console.log(` ${dim}Your config was NOT modified. Fix the syntax manually if needed.${reset}`);
1731
+ return;
1732
+ }
1733
+ }
1734
+
1735
+ // Ensure permission structure exists
1736
+ if (!config.permission) {
1737
+ config.permission = {};
1738
+ }
1739
+
1740
+ // Build the PBR path using the actual config directory
1741
+ // Use ~ shorthand if it's in the default location, otherwise use full path
1742
+ const defaultConfigDir = path.join(os.homedir(), '.config', 'opencode');
1743
+ const pbrPath = opencodeConfigDir === defaultConfigDir
1744
+ ? '~/.config/opencode/plan-build-run/*'
1745
+ : `${opencodeConfigDir.replace(/\\/g, '/')}/plan-build-run/*`;
1746
+
1747
+ let modified = false;
1748
+
1749
+ // Configure read permission
1750
+ if (!config.permission.read || typeof config.permission.read !== 'object') {
1751
+ config.permission.read = {};
1752
+ }
1753
+ if (config.permission.read[pbrPath] !== 'allow') {
1754
+ config.permission.read[pbrPath] = 'allow';
1755
+ modified = true;
1756
+ }
1757
+
1758
+ // Configure external_directory permission (the safety guard for paths outside project)
1759
+ if (!config.permission.external_directory || typeof config.permission.external_directory !== 'object') {
1760
+ config.permission.external_directory = {};
1761
+ }
1762
+ if (config.permission.external_directory[pbrPath] !== 'allow') {
1763
+ config.permission.external_directory[pbrPath] = 'allow';
1764
+ modified = true;
1765
+ }
1766
+
1767
+ if (!modified) {
1768
+ return; // Already configured
1769
+ }
1770
+
1771
+ // Write config back
1772
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
1773
+ console.log(` ${green}✓${reset} Configured read permission for PBR docs`);
1774
+ }
1775
+
1776
+ /**
1777
+ * Verify a directory exists and contains files
1778
+ */
1779
+ function verifyInstalled(dirPath, description) {
1780
+ if (!fs.existsSync(dirPath)) {
1781
+ console.error(` ${yellow}✗${reset} Failed to install ${description}: directory not created`);
1782
+ return false;
1783
+ }
1784
+ try {
1785
+ const entries = fs.readdirSync(dirPath);
1786
+ if (entries.length === 0) {
1787
+ console.error(` ${yellow}✗${reset} Failed to install ${description}: directory is empty`);
1788
+ return false;
1789
+ }
1790
+ } catch (e) {
1791
+ console.error(` ${yellow}✗${reset} Failed to install ${description}: ${e.message}`);
1792
+ return false;
1793
+ }
1794
+ return true;
1795
+ }
1796
+
1797
+ /**
1798
+ * Verify a file exists
1799
+ */
1800
+ function verifyFileInstalled(filePath, description) {
1801
+ if (!fs.existsSync(filePath)) {
1802
+ console.error(` ${yellow}✗${reset} Failed to install ${description}: file not created`);
1803
+ return false;
1804
+ }
1805
+ return true;
1806
+ }
1807
+
1808
+ /**
1809
+ * Install to the specified directory for a specific runtime
1810
+ * @param {boolean} isGlobal - Whether to install globally or locally
1811
+ * @param {string} runtime - Target runtime ('claude', 'opencode', 'gemini', 'codex')
1812
+ */
1813
+
1814
+ // ──────────────────────────────────────────────────────
1815
+ // Local Patch Persistence
1816
+ // ──────────────────────────────────────────────────────
1817
+
1818
+ const PATCHES_DIR_NAME = 'pbr-local-patches';
1819
+ const MANIFEST_NAME = 'pbr-file-manifest.json';
1820
+
1821
+ /**
1822
+ * Compute SHA256 hash of file contents
1823
+ */
1824
+ function fileHash(filePath) {
1825
+ const content = fs.readFileSync(filePath);
1826
+ return crypto.createHash('sha256').update(content).digest('hex');
1827
+ }
1828
+
1829
+ /**
1830
+ * Recursively collect all files in dir with their hashes
1831
+ */
1832
+ function generateManifest(dir, baseDir) {
1833
+ if (!baseDir) baseDir = dir;
1834
+ const manifest = {};
1835
+ if (!fs.existsSync(dir)) return manifest;
1836
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
1837
+ for (const entry of entries) {
1838
+ const fullPath = path.join(dir, entry.name);
1839
+ const relPath = path.relative(baseDir, fullPath).replace(/\\/g, '/');
1840
+ if (entry.isDirectory()) {
1841
+ Object.assign(manifest, generateManifest(fullPath, baseDir));
1842
+ } else {
1843
+ manifest[relPath] = fileHash(fullPath);
1844
+ }
1845
+ }
1846
+ return manifest;
1847
+ }
1848
+
1849
+ /**
1850
+ * Write file manifest after installation for future modification detection
1851
+ */
1852
+ function writeManifest(configDir, runtime = 'claude') {
1853
+ const isOpencode = runtime === 'opencode';
1854
+ const isCodex = runtime === 'codex';
1855
+ const pbrDir = path.join(configDir, 'plan-build-run');
1856
+ const commandsDir = path.join(configDir, 'commands', 'pbr');
1857
+ const opencodeCommandDir = path.join(configDir, 'command');
1858
+ const codexSkillsDir = path.join(configDir, 'skills');
1859
+ const agentsDir = path.join(configDir, 'agents');
1860
+ const manifest = {
1861
+ version: pkg.version,
1862
+ timestamp: new Date().toISOString(),
1863
+ sourceDir: normalizePath(path.resolve(path.join(__dirname, '..'))),
1864
+ files: {}
1865
+ };
1866
+
1867
+ const pbrHashes = generateManifest(pbrDir);
1868
+ for (const [rel, hash] of Object.entries(pbrHashes)) {
1869
+ manifest.files['plan-build-run/' + rel] = hash;
1870
+ }
1871
+ if (!isOpencode && !isCodex && fs.existsSync(commandsDir)) {
1872
+ const cmdHashes = generateManifest(commandsDir);
1873
+ for (const [rel, hash] of Object.entries(cmdHashes)) {
1874
+ manifest.files['commands/pbr/' + rel] = hash;
1875
+ }
1876
+ }
1877
+ if (isOpencode && fs.existsSync(opencodeCommandDir)) {
1878
+ for (const file of fs.readdirSync(opencodeCommandDir)) {
1879
+ if (file.startsWith('pbr-') && file.endsWith('.md')) {
1880
+ manifest.files['command/' + file] = fileHash(path.join(opencodeCommandDir, file));
1881
+ }
1882
+ }
1883
+ }
1884
+ if (isCodex && fs.existsSync(codexSkillsDir)) {
1885
+ for (const skillName of listCodexSkillNames(codexSkillsDir)) {
1886
+ const skillRoot = path.join(codexSkillsDir, skillName);
1887
+ const skillHashes = generateManifest(skillRoot);
1888
+ for (const [rel, hash] of Object.entries(skillHashes)) {
1889
+ manifest.files[`skills/${skillName}/${rel}`] = hash;
1890
+ }
1891
+ }
1892
+ }
1893
+ if (fs.existsSync(agentsDir)) {
1894
+ for (const file of fs.readdirSync(agentsDir)) {
1895
+ if (file.startsWith('pbr-') && file.endsWith('.md')) {
1896
+ manifest.files['agents/' + file] = fileHash(path.join(agentsDir, file));
1897
+ }
1898
+ }
1899
+ }
1900
+
1901
+ fs.writeFileSync(path.join(configDir, MANIFEST_NAME), JSON.stringify(manifest, null, 2));
1902
+ return manifest;
1903
+ }
1904
+
1905
+ /**
1906
+ * Detect user-modified PBR files by comparing against install manifest.
1907
+ * Backs up modified files to pbr-local-patches/ for reapply after update.
1908
+ */
1909
+ function saveLocalPatches(configDir) {
1910
+ const manifestPath = path.join(configDir, MANIFEST_NAME);
1911
+ if (!fs.existsSync(manifestPath)) return [];
1912
+
1913
+ let manifest;
1914
+ try { manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8')); } catch { return []; }
1915
+
1916
+ const patchesDir = path.join(configDir, PATCHES_DIR_NAME);
1917
+ const modified = [];
1918
+
1919
+ for (const [relPath, originalHash] of Object.entries(manifest.files || {})) {
1920
+ const fullPath = path.join(configDir, relPath);
1921
+ if (!fs.existsSync(fullPath)) continue;
1922
+ const currentHash = fileHash(fullPath);
1923
+ if (currentHash !== originalHash) {
1924
+ const backupPath = path.join(patchesDir, relPath);
1925
+ fs.mkdirSync(path.dirname(backupPath), { recursive: true });
1926
+ fs.copyFileSync(fullPath, backupPath);
1927
+ modified.push(relPath);
1928
+ }
1929
+ }
1930
+
1931
+ if (modified.length > 0) {
1932
+ const meta = {
1933
+ backed_up_at: new Date().toISOString(),
1934
+ from_version: manifest.version,
1935
+ files: modified
1936
+ };
1937
+ fs.writeFileSync(path.join(patchesDir, 'backup-meta.json'), JSON.stringify(meta, null, 2));
1938
+ console.log(' ' + yellow + 'i' + reset + ' Found ' + modified.length + ' locally modified PBR file(s) — backed up to ' + PATCHES_DIR_NAME + '/');
1939
+ for (const f of modified) {
1940
+ console.log(' ' + dim + f + reset);
1941
+ }
1942
+ }
1943
+ return modified;
1944
+ }
1945
+
1946
+ /**
1947
+ * After install, report backed-up patches for user to reapply.
1948
+ */
1949
+ function reportLocalPatches(configDir, runtime = 'claude') {
1950
+ const patchesDir = path.join(configDir, PATCHES_DIR_NAME);
1951
+ const metaPath = path.join(patchesDir, 'backup-meta.json');
1952
+ if (!fs.existsSync(metaPath)) return [];
1953
+
1954
+ let meta;
1955
+ try { meta = JSON.parse(fs.readFileSync(metaPath, 'utf8')); } catch { return []; }
1956
+
1957
+ if (meta.files && meta.files.length > 0) {
1958
+ const reapplyCommand = runtime === 'opencode'
1959
+ ? '/pbr-reapply-patches'
1960
+ : runtime === 'codex'
1961
+ ? '$pbr-reapply-patches'
1962
+ : '/pbr:reapply-patches';
1963
+ console.log('');
1964
+ console.log(' ' + yellow + 'Local patches detected' + reset + ' (from v' + meta.from_version + '):');
1965
+ for (const f of meta.files) {
1966
+ console.log(' ' + cyan + f + reset);
1967
+ }
1968
+ console.log('');
1969
+ console.log(' Your modifications are saved in ' + cyan + PATCHES_DIR_NAME + '/' + reset);
1970
+ console.log(' Run ' + cyan + reapplyCommand + reset + ' to merge them into the new version.');
1971
+ console.log(' Or manually compare and merge the files.');
1972
+ console.log('');
1973
+ }
1974
+ return meta.files || [];
1975
+ }
1976
+
1977
+ function install(isGlobal, runtime = 'claude') {
1978
+ const isOpencode = runtime === 'opencode';
1979
+ const isGemini = runtime === 'gemini';
1980
+ const isCodex = runtime === 'codex';
1981
+ const dirName = getDirName(runtime);
1982
+ const src = path.join(__dirname, '..');
1983
+
1984
+ // Get the target directory based on runtime and install type
1985
+ const targetDir = isGlobal
1986
+ ? getGlobalDir(runtime, explicitConfigDir)
1987
+ : path.join(process.cwd(), dirName);
1988
+
1989
+ const locationLabel = isGlobal
1990
+ ? targetDir.replace(os.homedir(), '~')
1991
+ : targetDir.replace(process.cwd(), '.');
1992
+
1993
+ // Path prefix for file references in markdown content
1994
+ // For global installs: use full path
1995
+ // For local installs: use relative
1996
+ const pathPrefix = isGlobal
1997
+ ? `${targetDir.replace(/\\/g, '/')}/`
1998
+ : `./${dirName}/`;
1999
+
2000
+ let runtimeLabel = 'Claude Code';
2001
+ if (isOpencode) runtimeLabel = 'OpenCode';
2002
+ if (isGemini) runtimeLabel = 'Gemini';
2003
+ if (isCodex) runtimeLabel = 'Codex';
2004
+
2005
+ console.log(` Installing for ${cyan}${runtimeLabel}${reset} to ${cyan}${locationLabel}${reset}\n`);
2006
+
2007
+ // Track installation failures
2008
+ const failures = [];
2009
+
2010
+ // Save any locally modified PBR files before they get wiped
2011
+ saveLocalPatches(targetDir);
2012
+
2013
+ // Clean up orphaned files from previous versions
2014
+ cleanupOrphanedFiles(targetDir);
2015
+
2016
+ // OpenCode uses command/ (flat), Codex uses skills/, Claude/Gemini use commands/pbr/
2017
+ if (isOpencode) {
2018
+ // OpenCode: flat structure in command/ directory
2019
+ const commandDir = path.join(targetDir, 'command');
2020
+ fs.mkdirSync(commandDir, { recursive: true });
2021
+
2022
+ // Copy commands/pbr/*.md as command/pbr-*.md (flatten structure)
2023
+ const pbrSrc = path.join(src, 'plugins', 'pbr', 'commands');
2024
+ copyFlattenedCommands(pbrSrc, commandDir, 'pbr', pathPrefix, runtime);
2025
+ if (verifyInstalled(commandDir, 'command/pbr-*')) {
2026
+ const count = fs.readdirSync(commandDir).filter(f => f.startsWith('pbr-')).length;
2027
+ console.log(` ${green}✓${reset} Installed ${count} commands to command/`);
2028
+ } else {
2029
+ failures.push('command/pbr-*');
2030
+ }
2031
+ } else if (isCodex) {
2032
+ const skillsDir = path.join(targetDir, 'skills');
2033
+ const pbrSrc = path.join(src, 'plugins', 'pbr', 'commands');
2034
+ copyCommandsAsCodexSkills(pbrSrc, skillsDir, 'pbr', pathPrefix, runtime);
2035
+ const installedSkillNames = listCodexSkillNames(skillsDir);
2036
+ if (installedSkillNames.length > 0) {
2037
+ console.log(` ${green}✓${reset} Installed ${installedSkillNames.length} skills to skills/`);
2038
+ } else {
2039
+ failures.push('skills/pbr-*');
2040
+ }
2041
+ } else {
2042
+ // Claude Code & Gemini: nested structure in commands/ directory
2043
+ const commandsDir = path.join(targetDir, 'commands');
2044
+ fs.mkdirSync(commandsDir, { recursive: true });
2045
+
2046
+ const pbrSrc = path.join(src, 'plugins', 'pbr', 'commands');
2047
+ const pbrDest = path.join(commandsDir, 'pbr');
2048
+ copyWithPathReplacement(pbrSrc, pbrDest, pathPrefix, runtime, true);
2049
+ if (verifyInstalled(pbrDest, 'commands/pbr')) {
2050
+ console.log(` ${green}✓${reset} Installed commands/pbr`);
2051
+ } else {
2052
+ failures.push('commands/pbr');
2053
+ }
2054
+ }
2055
+
2056
+ // Copy plan-build-run skill with path replacement
2057
+ const skillSrc = path.join(src, 'plan-build-run');
2058
+ const skillDest = path.join(targetDir, 'plan-build-run');
2059
+ copyWithPathReplacement(skillSrc, skillDest, pathPrefix, runtime);
2060
+ if (verifyInstalled(skillDest, 'plan-build-run')) {
2061
+ console.log(` ${green}✓${reset} Installed plan-build-run`);
2062
+ } else {
2063
+ failures.push('plan-build-run');
2064
+ }
2065
+
2066
+ // Copy agents to agents directory
2067
+ const agentsSrc = path.join(src, 'plugins', 'pbr', 'agents');
2068
+ if (fs.existsSync(agentsSrc)) {
2069
+ const agentsDest = path.join(targetDir, 'agents');
2070
+ fs.mkdirSync(agentsDest, { recursive: true });
2071
+
2072
+ // Remove old PBR agents (pbr-*.md) before copying new ones
2073
+ if (fs.existsSync(agentsDest)) {
2074
+ for (const file of fs.readdirSync(agentsDest)) {
2075
+ if (file.startsWith('pbr-') && file.endsWith('.md')) {
2076
+ fs.unlinkSync(path.join(agentsDest, file));
2077
+ }
2078
+ }
2079
+ }
2080
+
2081
+ // Copy new agents
2082
+ const agentEntries = fs.readdirSync(agentsSrc, { withFileTypes: true });
2083
+ for (const entry of agentEntries) {
2084
+ if (entry.isFile() && entry.name.endsWith('.md')) {
2085
+ let content = fs.readFileSync(path.join(agentsSrc, entry.name), 'utf8');
2086
+ // Replace ~/.claude/ and $HOME/.claude/ as they are the source of truth in the repo
2087
+ const dirRegex = /~\/\.claude\//g;
2088
+ const homeDirRegex = /\$HOME\/\.claude\//g;
2089
+ content = content.replace(dirRegex, pathPrefix);
2090
+ content = content.replace(homeDirRegex, toHomePrefix(pathPrefix));
2091
+ content = processAttribution(content, getCommitAttribution(runtime));
2092
+ // Convert frontmatter for runtime compatibility
2093
+ if (isOpencode) {
2094
+ content = convertClaudeToOpencodeFrontmatter(content);
2095
+ } else if (isGemini) {
2096
+ content = convertClaudeToGeminiAgent(content);
2097
+ } else if (isCodex) {
2098
+ content = convertClaudeAgentToCodexAgent(content);
2099
+ }
2100
+ fs.writeFileSync(path.join(agentsDest, entry.name), content);
2101
+ }
2102
+ }
2103
+ if (verifyInstalled(agentsDest, 'agents')) {
2104
+ console.log(` ${green}✓${reset} Installed agents`);
2105
+ } else {
2106
+ failures.push('agents');
2107
+ }
2108
+ }
2109
+
2110
+ // Copy CHANGELOG.md
2111
+ const changelogSrc = path.join(src, 'CHANGELOG.md');
2112
+ const changelogDest = path.join(targetDir, 'plan-build-run', 'CHANGELOG.md');
2113
+ if (fs.existsSync(changelogSrc)) {
2114
+ fs.copyFileSync(changelogSrc, changelogDest);
2115
+ if (verifyFileInstalled(changelogDest, 'CHANGELOG.md')) {
2116
+ console.log(` ${green}✓${reset} Installed CHANGELOG.md`);
2117
+ } else {
2118
+ failures.push('CHANGELOG.md');
2119
+ }
2120
+ }
2121
+
2122
+ // Write VERSION file
2123
+ const versionDest = path.join(targetDir, 'plan-build-run', 'VERSION');
2124
+ fs.writeFileSync(versionDest, pkg.version);
2125
+ if (verifyFileInstalled(versionDest, 'VERSION')) {
2126
+ console.log(` ${green}✓${reset} Wrote VERSION (${pkg.version})`);
2127
+ } else {
2128
+ failures.push('VERSION');
2129
+ }
2130
+
2131
+ if (!isCodex) {
2132
+ // Write package.json to force CommonJS mode for PBR scripts
2133
+ // Prevents "require is not defined" errors when project has "type": "module"
2134
+ // Node.js walks up looking for package.json - this stops inheritance from project
2135
+ const pkgJsonDest = path.join(targetDir, 'package.json');
2136
+ fs.writeFileSync(pkgJsonDest, '{"type":"commonjs"}\n');
2137
+ console.log(` ${green}✓${reset} Wrote package.json (CommonJS mode)`);
2138
+
2139
+ // Copy hooks from dist/ (bundled with dependencies)
2140
+ // Template paths for the target runtime (replaces '.claude' with correct config dir)
2141
+ const hooksSrc = path.join(src, 'hooks', 'dist');
2142
+ if (fs.existsSync(hooksSrc)) {
2143
+ const hooksDest = path.join(targetDir, 'hooks');
2144
+ fs.mkdirSync(hooksDest, { recursive: true });
2145
+ const hookEntries = fs.readdirSync(hooksSrc);
2146
+ const configDirReplacement = getConfigDirFromHome(runtime, isGlobal);
2147
+ for (const entry of hookEntries) {
2148
+ const srcFile = path.join(hooksSrc, entry);
2149
+ if (fs.statSync(srcFile).isFile()) {
2150
+ const destFile = path.join(hooksDest, entry);
2151
+ // Template .js files to replace '.claude' with runtime-specific config dir
2152
+ if (entry.endsWith('.js')) {
2153
+ let content = fs.readFileSync(srcFile, 'utf8');
2154
+ content = content.replace(/'\.claude'/g, configDirReplacement);
2155
+ fs.writeFileSync(destFile, content);
2156
+ } else {
2157
+ fs.copyFileSync(srcFile, destFile);
2158
+ }
2159
+ }
2160
+ }
2161
+ if (verifyInstalled(hooksDest, 'hooks')) {
2162
+ console.log(` ${green}✓${reset} Installed hooks (bundled)`);
2163
+ } else {
2164
+ failures.push('hooks');
2165
+ }
2166
+ }
2167
+ }
2168
+
2169
+ if (failures.length > 0) {
2170
+ console.error(`\n ${yellow}Installation incomplete!${reset} Failed: ${failures.join(', ')}`);
2171
+ process.exit(1);
2172
+ }
2173
+
2174
+ // Write file manifest for future modification detection
2175
+ writeManifest(targetDir, runtime);
2176
+ console.log(` ${green}✓${reset} Wrote file manifest (${MANIFEST_NAME})`);
2177
+
2178
+ // Report dashboard availability
2179
+ const dashboardDir = path.join(src, 'dashboard');
2180
+ if (fs.existsSync(path.join(dashboardDir, 'bin', 'cli.cjs'))) {
2181
+ console.log(` ${green}✓${reset} Dashboard available ${dim}(served from source repo)${reset}`);
2182
+ } else {
2183
+ console.log(` ${dim}-${reset} Dashboard not available ${dim}(not found in source repo)${reset}`);
2184
+ }
2185
+
2186
+ // Report any backed-up local patches
2187
+ reportLocalPatches(targetDir, runtime);
2188
+
2189
+ // Verify no leaked .claude paths in non-Claude runtimes
2190
+ if (runtime !== 'claude') {
2191
+ const leakedPaths = [];
2192
+ function scanForLeakedPaths(dir) {
2193
+ if (!fs.existsSync(dir)) return;
2194
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
2195
+ for (const entry of entries) {
2196
+ const fullPath = path.join(dir, entry.name);
2197
+ if (entry.isDirectory()) {
2198
+ scanForLeakedPaths(fullPath);
2199
+ } else if ((entry.name.endsWith('.md') || entry.name.endsWith('.toml')) && entry.name !== 'CHANGELOG.md') {
2200
+ const content = fs.readFileSync(fullPath, 'utf8');
2201
+ const matches = content.match(/(?:~|\$HOME)\/\.claude\b/g);
2202
+ if (matches) {
2203
+ leakedPaths.push({ file: fullPath.replace(targetDir + '/', ''), count: matches.length });
2204
+ }
2205
+ }
2206
+ }
2207
+ }
2208
+ scanForLeakedPaths(targetDir);
2209
+ if (leakedPaths.length > 0) {
2210
+ const totalLeaks = leakedPaths.reduce((sum, l) => sum + l.count, 0);
2211
+ console.warn(`\n ${yellow}⚠${reset} Found ${totalLeaks} unreplaced .claude path reference(s) in ${leakedPaths.length} file(s):`);
2212
+ for (const leak of leakedPaths.slice(0, 5)) {
2213
+ console.warn(` ${dim}${leak.file}${reset} (${leak.count})`);
2214
+ }
2215
+ if (leakedPaths.length > 5) {
2216
+ console.warn(` ${dim}... and ${leakedPaths.length - 5} more file(s)${reset}`);
2217
+ }
2218
+ console.warn(` ${dim}These paths may not resolve correctly for ${runtimeLabel}.${reset}`);
2219
+ }
2220
+ }
2221
+
2222
+ if (isCodex) {
2223
+ // Generate Codex config.toml and per-agent .toml files
2224
+ const agentCount = installCodexConfig(targetDir, agentsSrc);
2225
+ console.log(` ${green}✓${reset} Generated config.toml with ${agentCount} agent roles`);
2226
+ console.log(` ${green}✓${reset} Generated ${agentCount} agent .toml config files`);
2227
+ return { settingsPath: null, settings: null, statuslineCommand: null, runtime };
2228
+ }
2229
+
2230
+ // Configure statusline and hooks in settings.json
2231
+ // Gemini uses AfterTool/BeforeTool instead of PostToolUse/PreToolUse for tool hooks
2232
+ const postToolEvent = runtime === 'gemini' ? 'AfterTool' : 'PostToolUse';
2233
+ const preToolEvent = runtime === 'gemini' ? 'BeforeTool' : 'PreToolUse';
2234
+ const settingsPath = path.join(targetDir, 'settings.json');
2235
+ const settings = cleanupOrphanedHooks(readSettings(settingsPath));
2236
+ const statuslineCommand = isGlobal
2237
+ ? buildHookCommand(targetDir, 'pbr-statusline.js')
2238
+ : 'node ' + dirName + '/hooks/pbr-statusline.js';
2239
+ const updateCheckCommand = isGlobal
2240
+ ? buildHookCommand(targetDir, 'pbr-check-update.js')
2241
+ : 'node ' + dirName + '/hooks/pbr-check-update.js';
2242
+ // Build direct hook commands (no hook-server-client middleman)
2243
+ function directHookCmd(hookFile) {
2244
+ return isGlobal
2245
+ ? buildHookCommand(targetDir, hookFile)
2246
+ : 'node ' + dirName + '/hooks/' + hookFile;
2247
+ }
2248
+ const runHookCmd = directHookCmd('run-hook.js');
2249
+ const hookServerCmd = isGlobal
2250
+ ? buildHookCommand(targetDir, 'hook-server.js')
2251
+ : 'node ' + dirName + '/hooks/hook-server.js';
2252
+
2253
+ // Enable experimental agents for Gemini CLI (required for custom sub-agents)
2254
+ if (isGemini) {
2255
+ if (!settings.experimental) {
2256
+ settings.experimental = {};
2257
+ }
2258
+ if (!settings.experimental.enableAgents) {
2259
+ settings.experimental.enableAgents = true;
2260
+ console.log(` ${green}✓${reset} Enabled experimental agents`);
2261
+ }
2262
+ }
2263
+
2264
+ // Configure all hooks (skip for opencode which doesn't support settings.json hooks)
2265
+ // Only add hook entries if the script files actually exist at the target location
2266
+ if (!isOpencode) {
2267
+ const hooksDir = path.join(targetDir, 'hooks');
2268
+
2269
+ if (!settings.hooks) {
2270
+ settings.hooks = {};
2271
+ }
2272
+
2273
+ // --- Helper to add a hook entry if it doesn't already exist ---
2274
+ function addHookEntry(event, matcher, commandSuffix, identifier) {
2275
+ if (!settings.hooks[event]) {
2276
+ settings.hooks[event] = [];
2277
+ }
2278
+ const hasHook = settings.hooks[event].some(entry =>
2279
+ entry.hooks && entry.hooks.some(h => h.command && h.command.includes(identifier))
2280
+ );
2281
+ if (!hasHook) {
2282
+ const entry = {
2283
+ hooks: [{ type: 'command', command: commandSuffix }]
2284
+ };
2285
+ if (matcher) {
2286
+ entry.matcher = matcher;
2287
+ }
2288
+ settings.hooks[event].push(entry);
2289
+ return true;
2290
+ }
2291
+ return false;
2292
+ }
2293
+
2294
+ // --- SessionStart: update checker ---
2295
+ const updateCheckScript = path.join(hooksDir, 'pbr-check-update.js');
2296
+ if (fs.existsSync(updateCheckScript)) {
2297
+ if (addHookEntry('SessionStart', null, updateCheckCommand, 'pbr-check-update')) {
2298
+ console.log(` ${green}✓${reset} Configured update check hook`);
2299
+ }
2300
+ } else {
2301
+ console.log(` ${yellow}⚠${reset} Skipping update check hook (script not found)`);
2302
+ }
2303
+
2304
+ // --- SessionStart: hook-server spawn (detached background process) ---
2305
+ const hookServerScript = path.join(hooksDir, 'hook-server.js');
2306
+ if (fs.existsSync(hookServerScript)) {
2307
+ if (!settings.hooks.SessionStart) {
2308
+ settings.hooks.SessionStart = [];
2309
+ }
2310
+ const hasHookServer = settings.hooks.SessionStart.some(entry =>
2311
+ entry.hooks && entry.hooks.some(h => h.command && h.command.includes('hook-server'))
2312
+ );
2313
+ if (!hasHookServer) {
2314
+ // Spawn hook-server as detached process so SessionStart doesn't block
2315
+ const spawnCmd = `node -e "const{spawn}=require('child_process');const p=spawn('node',['${hookServerScript.replace(/\\/g, '/')}','--dir','.'],{detached:true,stdio:'ignore'});p.unref();"`;
2316
+ settings.hooks.SessionStart.push({
2317
+ hooks: [{ type: 'command', command: spawnCmd }],
2318
+ timeout: 5000
2319
+ });
2320
+ console.log(` ${green}✓${reset} Configured hook-server spawn`);
2321
+ }
2322
+ } else {
2323
+ console.log(` ${yellow}⚠${reset} Skipping hook-server spawn (script not found)`);
2324
+ }
2325
+
2326
+ // --- PreToolUse: Bash, Write, Edit dispatchers (direct calls, no hook-server-client) ---
2327
+ const preBashScript = path.join(hooksDir, 'pre-bash-dispatch.js');
2328
+ if (fs.existsSync(preBashScript)) {
2329
+ if (addHookEntry(preToolEvent, 'Bash', directHookCmd('pre-bash-dispatch.js'), 'pre-bash-dispatch')) {
2330
+ console.log(` ${green}✓${reset} Configured PreToolUse:Bash hook`);
2331
+ }
2332
+ // Also register validate-commit on Bash
2333
+ if (fs.existsSync(path.join(hooksDir, 'validate-commit.js'))) {
2334
+ addHookEntry(preToolEvent, 'Bash', directHookCmd('validate-commit.js'), 'validate-commit');
2335
+ }
2336
+ }
2337
+ const preWriteScript = path.join(hooksDir, 'pre-write-dispatch.js');
2338
+ if (fs.existsSync(preWriteScript)) {
2339
+ if (addHookEntry(preToolEvent, 'Write', directHookCmd('pre-write-dispatch.js'), 'pre-write-dispatch')) {
2340
+ console.log(` ${green}✓${reset} Configured PreToolUse:Write hook`);
2341
+ }
2342
+ // Edit uses the same handler
2343
+ addHookEntry(preToolEvent, 'Edit', directHookCmd('pre-write-dispatch.js'), 'pre-write-dispatch');
2344
+ console.log(` ${green}✓${reset} Configured PreToolUse:Edit hook`);
2345
+
2346
+ // Also register check-skill-workflow on Write/Edit
2347
+ if (fs.existsSync(path.join(hooksDir, 'check-skill-workflow.js'))) {
2348
+ addHookEntry(preToolEvent, 'Write', directHookCmd('check-skill-workflow.js'), 'check-skill-workflow');
2349
+ addHookEntry(preToolEvent, 'Edit', directHookCmd('check-skill-workflow.js'), 'check-skill-workflow');
2350
+ }
2351
+ }
2352
+
2353
+ // --- PostToolUse: Read, Write, Edit, Bash, Task dispatchers ---
2354
+ if (fs.existsSync(path.join(hooksDir, 'track-context-budget.js'))) {
2355
+ if (addHookEntry(postToolEvent, 'Read', directHookCmd('track-context-budget.js'), 'track-context-budget')) {
2356
+ console.log(` ${green}✓${reset} Configured PostToolUse:Read hook`);
2357
+ }
2358
+ }
2359
+ if (fs.existsSync(path.join(hooksDir, 'post-write-dispatch.js'))) {
2360
+ if (addHookEntry(postToolEvent, 'Write', directHookCmd('post-write-dispatch.js'), 'post-write-dispatch')) {
2361
+ console.log(` ${green}✓${reset} Configured PostToolUse:Write hook`);
2362
+ }
2363
+ addHookEntry(postToolEvent, 'Edit', directHookCmd('post-write-dispatch.js'), 'post-write-dispatch');
2364
+ console.log(` ${green}✓${reset} Configured PostToolUse:Edit hook`);
2365
+ }
2366
+ if (fs.existsSync(path.join(hooksDir, 'post-bash-dispatch.js'))) {
2367
+ addHookEntry(postToolEvent, 'Bash', directHookCmd('post-bash-dispatch.js'), 'post-bash-dispatch');
2368
+ }
2369
+ if (fs.existsSync(path.join(hooksDir, 'suggest-compact.js'))) {
2370
+ if (addHookEntry(postToolEvent, 'Bash', directHookCmd('suggest-compact.js'), 'suggest-compact')) {
2371
+ console.log(` ${green}✓${reset} Configured PostToolUse:Bash hook`);
2372
+ }
2373
+ }
2374
+ if (fs.existsSync(path.join(hooksDir, 'check-subagent-output.js'))) {
2375
+ if (addHookEntry(postToolEvent, 'Task', directHookCmd('check-subagent-output.js'), 'check-subagent-output')) {
2376
+ console.log(` ${green}✓${reset} Configured PostToolUse:Task hook`);
2377
+ }
2378
+ }
2379
+
2380
+ // --- PostToolUseFailure: log-tool-failure ---
2381
+ if (fs.existsSync(path.join(hooksDir, 'log-tool-failure.js'))) {
2382
+ if (addHookEntry('PostToolUseFailure', null, directHookCmd('log-tool-failure.js'), 'log-tool-failure')) {
2383
+ console.log(` ${green}✓${reset} Configured PostToolUseFailure hook`);
2384
+ }
2385
+ }
2386
+
2387
+ // --- SubagentStop: log-subagent + event-handler ---
2388
+ if (fs.existsSync(path.join(hooksDir, 'log-subagent.js'))) {
2389
+ if (addHookEntry('SubagentStop', null, directHookCmd('log-subagent.js'), 'log-subagent')) {
2390
+ console.log(` ${green}✓${reset} Configured SubagentStop hook`);
2391
+ }
2392
+ }
2393
+ if (fs.existsSync(path.join(hooksDir, 'event-handler.js'))) {
2394
+ addHookEntry('SubagentStop', null, directHookCmd('event-handler.js'), 'event-handler');
2395
+ }
2396
+
2397
+ // --- PreCompact: context-budget-check ---
2398
+ if (fs.existsSync(path.join(hooksDir, 'context-budget-check.js'))) {
2399
+ if (addHookEntry('PreCompact', null, directHookCmd('context-budget-check.js'), 'context-budget-check')) {
2400
+ console.log(` ${green}✓${reset} Configured PreCompact hook`);
2401
+ }
2402
+ }
2403
+
2404
+ // --- WorktreeCreate/Remove ---
2405
+ if (fs.existsSync(path.join(hooksDir, 'worktree-create.js'))) {
2406
+ addHookEntry('WorktreeCreate', null, directHookCmd('worktree-create.js'), 'worktree-create');
2407
+ }
2408
+ if (fs.existsSync(path.join(hooksDir, 'worktree-remove.js'))) {
2409
+ addHookEntry('WorktreeRemove', null, directHookCmd('worktree-remove.js'), 'worktree-remove');
2410
+ }
2411
+
2412
+ // --- SessionStart: progress-tracker ---
2413
+ if (fs.existsSync(path.join(hooksDir, 'progress-tracker.js'))) {
2414
+ if (addHookEntry('SessionStart', null, directHookCmd('progress-tracker.js'), 'progress-tracker')) {
2415
+ console.log(` ${green}✓${reset} Configured SessionStart:progress-tracker hook`);
2416
+ }
2417
+ }
2418
+
2419
+ // --- Stop: auto-continue ---
2420
+ if (fs.existsSync(path.join(hooksDir, 'auto-continue.js'))) {
2421
+ if (addHookEntry('Stop', null, directHookCmd('auto-continue.js'), 'auto-continue')) {
2422
+ console.log(` ${green}✓${reset} Configured Stop:auto-continue hook`);
2423
+ }
2424
+ }
2425
+
2426
+ // --- TaskCompleted: task-completed ---
2427
+ if (fs.existsSync(path.join(hooksDir, 'task-completed.js'))) {
2428
+ if (addHookEntry('TaskCompleted', null, directHookCmd('task-completed.js'), 'task-completed')) {
2429
+ console.log(` ${green}✓${reset} Configured TaskCompleted hook`);
2430
+ }
2431
+ }
2432
+
2433
+ // --- SubagentStart: log-subagent ---
2434
+ if (fs.existsSync(path.join(hooksDir, 'log-subagent.js'))) {
2435
+ if (addHookEntry('SubagentStart', null, directHookCmd('log-subagent.js'), 'log-subagent-start')) {
2436
+ console.log(` ${green}✓${reset} Configured SubagentStart hook`);
2437
+ }
2438
+ }
2439
+
2440
+ // --- InstructionsLoaded: instructions-loaded ---
2441
+ if (fs.existsSync(path.join(hooksDir, 'instructions-loaded.js'))) {
2442
+ if (addHookEntry('InstructionsLoaded', null, directHookCmd('instructions-loaded.js'), 'instructions-loaded')) {
2443
+ console.log(` ${green}✓${reset} Configured InstructionsLoaded hook`);
2444
+ }
2445
+ }
2446
+
2447
+ // --- Notification: log-notification ---
2448
+ if (fs.existsSync(path.join(hooksDir, 'log-notification.js'))) {
2449
+ if (addHookEntry('Notification', null, directHookCmd('log-notification.js'), 'log-notification')) {
2450
+ console.log(` ${green}✓${reset} Configured Notification hook`);
2451
+ }
2452
+ }
2453
+
2454
+ // --- UserPromptSubmit: prompt-routing ---
2455
+ if (fs.existsSync(path.join(hooksDir, 'prompt-routing.js'))) {
2456
+ if (addHookEntry('UserPromptSubmit', null, directHookCmd('prompt-routing.js'), 'prompt-routing')) {
2457
+ console.log(` ${green}✓${reset} Configured UserPromptSubmit hook`);
2458
+ }
2459
+ }
2460
+
2461
+ // --- ConfigChange: check-config-change ---
2462
+ if (fs.existsSync(path.join(hooksDir, 'check-config-change.js'))) {
2463
+ if (addHookEntry('ConfigChange', null, directHookCmd('check-config-change.js'), 'check-config-change')) {
2464
+ console.log(` ${green}✓${reset} Configured ConfigChange hook`);
2465
+ }
2466
+ }
2467
+
2468
+ // --- SessionEnd: session-cleanup (uses run-hook.js directly, not hook-server-client) ---
2469
+ const runHookScript = path.join(hooksDir, 'run-hook.js');
2470
+ if (fs.existsSync(runHookScript)) {
2471
+ if (addHookEntry('SessionEnd', null, runHookCmd + ' session-cleanup.js', 'session-cleanup')) {
2472
+ console.log(` ${green}✓${reset} Configured SessionEnd hook`);
2473
+ }
2474
+ } else {
2475
+ console.log(` ${yellow}⚠${reset} Skipping SessionEnd hook (run-hook.js not found)`);
2476
+ }
2477
+
2478
+ // Legacy pbr-context-monitor removed -- functionality merged into track-context-budget.js
2479
+ }
2480
+
2481
+ return { settingsPath, settings, statuslineCommand, runtime };
2482
+ }
2483
+
2484
+ /**
2485
+ * Apply statusline config, then print completion message
2486
+ */
2487
+ function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallStatusline, runtime = 'claude', isGlobal = true) {
2488
+ const isOpencode = runtime === 'opencode';
2489
+ const isCodex = runtime === 'codex';
2490
+
2491
+ if (shouldInstallStatusline && !isOpencode && !isCodex) {
2492
+ settings.statusLine = {
2493
+ type: 'command',
2494
+ command: statuslineCommand
2495
+ };
2496
+ console.log(` ${green}✓${reset} Configured statusline`);
2497
+ }
2498
+
2499
+ // Write settings when runtime supports settings.json
2500
+ if (!isCodex) {
2501
+ writeSettings(settingsPath, settings);
2502
+ }
2503
+
2504
+ // Configure OpenCode permissions
2505
+ if (isOpencode) {
2506
+ configureOpencodePermissions(isGlobal);
2507
+ }
2508
+
2509
+ let program = 'Claude Code';
2510
+ if (runtime === 'opencode') program = 'OpenCode';
2511
+ if (runtime === 'gemini') program = 'Gemini';
2512
+ if (runtime === 'codex') program = 'Codex';
2513
+
2514
+ let command = '/pbr:new-project';
2515
+ if (runtime === 'opencode') command = '/pbr-new-project';
2516
+ if (runtime === 'codex') command = '$pbr-new-project';
2517
+ console.log(`
2518
+ ${green}Done!${reset} Open a blank directory in ${program} and run ${cyan}${command}${reset}.
2519
+
2520
+ ${cyan}Join the community:${reset} https://discord.gg/pbr
2521
+ `);
2522
+ }
2523
+
2524
+ /**
2525
+ * Handle statusline configuration with optional prompt
2526
+ */
2527
+ function handleStatusline(settings, isInteractive, callback) {
2528
+ const hasExisting = settings.statusLine != null;
2529
+
2530
+ if (!hasExisting) {
2531
+ callback(true);
2532
+ return;
2533
+ }
2534
+
2535
+ if (forceStatusline) {
2536
+ callback(true);
2537
+ return;
2538
+ }
2539
+
2540
+ if (!isInteractive) {
2541
+ console.log(` ${yellow}⚠${reset} Skipping statusline (already configured)`);
2542
+ console.log(` Use ${cyan}--force-statusline${reset} to replace\n`);
2543
+ callback(false);
2544
+ return;
2545
+ }
2546
+
2547
+ const existingCmd = settings.statusLine.command || settings.statusLine.url || '(custom)';
2548
+
2549
+ const rl = readline.createInterface({
2550
+ input: process.stdin,
2551
+ output: process.stdout
2552
+ });
2553
+
2554
+ console.log(`
2555
+ ${yellow}⚠${reset} Existing statusline detected\n
2556
+ Your current statusline:
2557
+ ${dim}command: ${existingCmd}${reset}
2558
+
2559
+ PBR includes a statusline showing:
2560
+ • Model name
2561
+ • Current task (from todo list)
2562
+ • Context window usage (color-coded)
2563
+
2564
+ ${cyan}1${reset}) Keep existing
2565
+ ${cyan}2${reset}) Replace with PBR statusline
2566
+ `);
2567
+
2568
+ rl.question(` Choice ${dim}[1]${reset}: `, (answer) => {
2569
+ rl.close();
2570
+ const choice = answer.trim() || '1';
2571
+ callback(choice === '2');
2572
+ });
2573
+ }
2574
+
2575
+ /**
2576
+ * Prompt for runtime selection
2577
+ */
2578
+ function promptRuntime(callback) {
2579
+ const rl = readline.createInterface({
2580
+ input: process.stdin,
2581
+ output: process.stdout
2582
+ });
2583
+
2584
+ let answered = false;
2585
+
2586
+ rl.on('close', () => {
2587
+ if (!answered) {
2588
+ answered = true;
2589
+ console.log(`\n ${yellow}Installation cancelled${reset}\n`);
2590
+ process.exit(0);
2591
+ }
2592
+ });
2593
+
2594
+ console.log(` ${yellow}Which runtime(s) would you like to install for?${reset}\n\n ${cyan}1${reset}) Claude Code ${dim}(~/.claude)${reset}
2595
+ ${cyan}2${reset}) OpenCode ${dim}(~/.config/opencode)${reset} - open source, free models
2596
+ ${cyan}3${reset}) Gemini ${dim}(~/.gemini)${reset}
2597
+ ${cyan}4${reset}) Codex ${dim}(~/.codex)${reset}
2598
+ ${cyan}5${reset}) All
2599
+ `);
2600
+
2601
+ rl.question(` Choice ${dim}[1]${reset}: `, (answer) => {
2602
+ answered = true;
2603
+ rl.close();
2604
+ const choice = answer.trim() || '1';
2605
+ if (choice === '5') {
2606
+ callback(['claude', 'opencode', 'gemini', 'codex']);
2607
+ } else if (choice === '4') {
2608
+ callback(['codex']);
2609
+ } else if (choice === '3') {
2610
+ callback(['gemini']);
2611
+ } else if (choice === '2') {
2612
+ callback(['opencode']);
2613
+ } else {
2614
+ callback(['claude']);
2615
+ }
2616
+ });
2617
+ }
2618
+
2619
+ /**
2620
+ * Prompt for install location
2621
+ */
2622
+ function promptLocation(runtimes) {
2623
+ if (!process.stdin.isTTY) {
2624
+ console.log(` ${yellow}Non-interactive terminal detected, defaulting to global install${reset}\n`);
2625
+ installAllRuntimes(runtimes, true, false);
2626
+ return;
2627
+ }
2628
+
2629
+ const rl = readline.createInterface({
2630
+ input: process.stdin,
2631
+ output: process.stdout
2632
+ });
2633
+
2634
+ let answered = false;
2635
+
2636
+ rl.on('close', () => {
2637
+ if (!answered) {
2638
+ answered = true;
2639
+ console.log(`\n ${yellow}Installation cancelled${reset}\n`);
2640
+ process.exit(0);
2641
+ }
2642
+ });
2643
+
2644
+ const pathExamples = runtimes.map(r => {
2645
+ const globalPath = getGlobalDir(r, explicitConfigDir);
2646
+ return globalPath.replace(os.homedir(), '~');
2647
+ }).join(', ');
2648
+
2649
+ const localExamples = runtimes.map(r => `./${getDirName(r)}`).join(', ');
2650
+
2651
+ console.log(` ${yellow}Where would you like to install?${reset}\n\n ${cyan}1${reset}) Global ${dim}(${pathExamples})${reset} - available in all projects
2652
+ ${cyan}2${reset}) Local ${dim}(${localExamples})${reset} - this project only
2653
+ `);
2654
+
2655
+ rl.question(` Choice ${dim}[1]${reset}: `, (answer) => {
2656
+ answered = true;
2657
+ rl.close();
2658
+ const choice = answer.trim() || '1';
2659
+ const isGlobal = choice !== '2';
2660
+ installAllRuntimes(runtimes, isGlobal, true);
2661
+ });
2662
+ }
2663
+
2664
+ /**
2665
+ * Install PBR for all selected runtimes
2666
+ */
2667
+ function installAllRuntimes(runtimes, isGlobal, isInteractive) {
2668
+ const results = [];
2669
+
2670
+ for (const runtime of runtimes) {
2671
+ const result = install(isGlobal, runtime);
2672
+ results.push(result);
2673
+ }
2674
+
2675
+ const statuslineRuntimes = ['claude', 'gemini'];
2676
+ const primaryStatuslineResult = results.find(r => statuslineRuntimes.includes(r.runtime));
2677
+
2678
+ const finalize = (shouldInstallStatusline) => {
2679
+ for (const result of results) {
2680
+ const useStatusline = statuslineRuntimes.includes(result.runtime) && shouldInstallStatusline;
2681
+ finishInstall(
2682
+ result.settingsPath,
2683
+ result.settings,
2684
+ result.statuslineCommand,
2685
+ useStatusline,
2686
+ result.runtime,
2687
+ isGlobal
2688
+ );
2689
+ }
2690
+ };
2691
+
2692
+ if (primaryStatuslineResult) {
2693
+ handleStatusline(primaryStatuslineResult.settings, isInteractive, finalize);
2694
+ } else {
2695
+ finalize(false);
2696
+ }
2697
+ }
2698
+
2699
+ // Test-only exports — skip main logic when loaded as a module for testing
2700
+ if (process.env.PBR_TEST_MODE) {
2701
+ module.exports = {
2702
+ getCodexSkillAdapterHeader,
2703
+ convertClaudeAgentToCodexAgent,
2704
+ generateCodexAgentToml,
2705
+ generateCodexConfigBlock,
2706
+ stripPbrFromCodexConfig,
2707
+ mergeCodexConfig,
2708
+ installCodexConfig,
2709
+ convertClaudeCommandToCodexSkill,
2710
+ PBR_CODEX_MARKER,
2711
+ CODEX_AGENT_SANDBOX,
2712
+ };
2713
+ } else {
2714
+
2715
+ // Main logic
2716
+ if (hasGlobal && hasLocal) {
2717
+ console.error(` ${yellow}Cannot specify both --global and --local${reset}`);
2718
+ process.exit(1);
2719
+ } else if (explicitConfigDir && hasLocal) {
2720
+ console.error(` ${yellow}Cannot use --config-dir with --local${reset}`);
2721
+ process.exit(1);
2722
+ } else if (hasUninstall) {
2723
+ if (!hasGlobal && !hasLocal) {
2724
+ console.error(` ${yellow}--uninstall requires --global or --local${reset}`);
2725
+ process.exit(1);
2726
+ }
2727
+ const runtimes = selectedRuntimes.length > 0 ? selectedRuntimes : ['claude'];
2728
+ for (const runtime of runtimes) {
2729
+ uninstall(hasGlobal, runtime);
2730
+ }
2731
+ } else if (selectedRuntimes.length > 0) {
2732
+ if (!hasGlobal && !hasLocal) {
2733
+ promptLocation(selectedRuntimes);
2734
+ } else {
2735
+ installAllRuntimes(selectedRuntimes, hasGlobal, false);
2736
+ }
2737
+ } else if (hasGlobal || hasLocal) {
2738
+ // Default to Claude if no runtime specified but location is
2739
+ installAllRuntimes(['claude'], hasGlobal, false);
2740
+ } else {
2741
+ // Interactive
2742
+ if (!process.stdin.isTTY) {
2743
+ console.log(` ${yellow}Non-interactive terminal detected, defaulting to Claude Code global install${reset}\n`);
2744
+ installAllRuntimes(['claude'], true, false);
2745
+ } else {
2746
+ promptRuntime((runtimes) => {
2747
+ promptLocation(runtimes);
2748
+ });
2749
+ }
2750
+ }
2751
+
2752
+ } // end of else block for PBR_TEST_MODE