@sienklogic/plan-build-run 2.21.1 → 2.21.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.
- package/CHANGELOG.md +1331 -323
- package/CLAUDE.md +75 -40
- package/LICENSE +2 -1
- package/README.md +412 -177
- package/bin/install.js +2752 -0
- package/dashboard/bin/cli.cjs +96 -0
- package/dashboard/bin/stop.cjs +129 -0
- package/dashboard/eslint.config.js +37 -0
- package/dashboard/index.html +20 -0
- package/dashboard/package.json +27 -25
- package/dashboard/server/index.js +151 -0
- package/dashboard/server/lib/frontmatter.js +92 -0
- package/dashboard/server/middleware/static.js +35 -0
- package/dashboard/server/package.json +16 -0
- package/dashboard/server/routes/agents.js +234 -0
- package/dashboard/server/routes/config.js +64 -0
- package/dashboard/server/routes/health.js +98 -0
- package/dashboard/server/routes/incidents.js +78 -0
- package/dashboard/server/routes/intel.js +69 -0
- package/dashboard/server/routes/memory.js +107 -0
- package/dashboard/server/routes/planning.js +234 -0
- package/dashboard/server/routes/progress.js +77 -0
- package/dashboard/server/routes/projects.js +36 -0
- package/dashboard/server/routes/requirements.js +40 -0
- package/dashboard/server/routes/roadmap.js +69 -0
- package/dashboard/server/routes/sessions.js +70 -0
- package/dashboard/server/routes/status.js +25 -0
- package/dashboard/server/routes/telemetry.js +233 -0
- package/dashboard/server/services/file-watcher.js +105 -0
- package/dashboard/server/services/planning-reader.js +727 -0
- package/dashboard/server/test/cli.test.js +34 -0
- package/dashboard/server/test/frontmatter.test.js +104 -0
- package/dashboard/server/test/isolation.test.js +32 -0
- package/dashboard/server/test/planning-reader.test.js +151 -0
- package/dashboard/server/test/routes.test.js +91 -0
- package/dashboard/server/test/ws.test.js +81 -0
- package/dashboard/server/ws.js +96 -0
- package/dashboard/src/App.jsx +165 -0
- package/dashboard/src/components/charts/BudgetBars.jsx +42 -0
- package/dashboard/src/components/charts/ContextRadar.jsx +34 -0
- package/dashboard/src/components/charts/PhaseDonut.jsx +66 -0
- package/dashboard/src/components/charts/SuccessTrend.jsx +45 -0
- package/dashboard/src/components/charts/TokenChart.jsx +55 -0
- package/dashboard/src/components/charts/index.js +5 -0
- package/dashboard/src/components/config/CfgSection.jsx +93 -0
- package/dashboard/src/components/layout/Header.jsx +89 -0
- package/dashboard/src/components/layout/ProjectSwitcher.jsx +160 -0
- package/dashboard/src/components/layout/Sidebar.jsx +161 -0
- package/dashboard/src/components/ui/AutoModeBanner.jsx +138 -0
- package/dashboard/src/components/ui/BackButton.jsx +27 -0
- package/dashboard/src/components/ui/Badge.jsx +27 -0
- package/dashboard/src/components/ui/Card.jsx +23 -0
- package/dashboard/src/components/ui/ChartTooltip.jsx +48 -0
- package/dashboard/src/components/ui/CheckpointBox.jsx +110 -0
- package/dashboard/src/components/ui/CodeBlock.jsx +27 -0
- package/dashboard/src/components/ui/ConfidenceBadge.jsx +20 -0
- package/dashboard/src/components/ui/ConfirmModal.jsx +161 -0
- package/dashboard/src/components/ui/ConnectionBanner.jsx +60 -0
- package/dashboard/src/components/ui/ErrorBoundary.jsx +106 -0
- package/dashboard/src/components/ui/ErrorBox.jsx +107 -0
- package/dashboard/src/components/ui/KeyValue.jsx +33 -0
- package/dashboard/src/components/ui/LoadingSkeleton.jsx +84 -0
- package/dashboard/src/components/ui/MetricCard.jsx +58 -0
- package/dashboard/src/components/ui/NextUpBlock.jsx +92 -0
- package/dashboard/src/components/ui/NumberInput.jsx +44 -0
- package/dashboard/src/components/ui/PBRBanner.jsx +47 -0
- package/dashboard/src/components/ui/PipelineView.jsx +130 -0
- package/dashboard/src/components/ui/ProgressBar.jsx +28 -0
- package/dashboard/src/components/ui/ProgressDisplay.jsx +47 -0
- package/dashboard/src/components/ui/QualityGateBadge.jsx +15 -0
- package/dashboard/src/components/ui/SectionTitle.jsx +35 -0
- package/dashboard/src/components/ui/SelectInput.jsx +45 -0
- package/dashboard/src/components/ui/StatusDot.jsx +51 -0
- package/dashboard/src/components/ui/StatusSymbol.jsx +49 -0
- package/dashboard/src/components/ui/TabBar.jsx +41 -0
- package/dashboard/src/components/ui/TextInput.jsx +42 -0
- package/dashboard/src/components/ui/Toast.jsx +117 -0
- package/dashboard/src/components/ui/Toggle.jsx +70 -0
- package/dashboard/src/components/ui/index.js +29 -0
- package/dashboard/src/hooks/useDocumentTitle.js +16 -0
- package/dashboard/src/hooks/useFetch.js +50 -0
- package/dashboard/src/hooks/useToast.jsx +43 -0
- package/dashboard/src/hooks/useWebSocket.js +103 -0
- package/dashboard/src/lib/api.js +112 -0
- package/dashboard/src/lib/configSchema.js +189 -0
- package/dashboard/src/lib/constants.js +22 -0
- package/dashboard/src/main.jsx +15 -0
- package/dashboard/src/pages/AgentsPage.jsx +191 -0
- package/dashboard/src/pages/ConfigPage.jsx +298 -0
- package/dashboard/src/pages/HooksPage.jsx +412 -0
- package/dashboard/src/pages/IncidentsPage.jsx +135 -0
- package/dashboard/src/pages/IntelPage.jsx +193 -0
- package/dashboard/src/pages/LiveFeed.jsx +274 -0
- package/dashboard/src/pages/MemoryPage.jsx +107 -0
- package/dashboard/src/pages/OnboardingPage.jsx +117 -0
- package/dashboard/src/pages/Overview.jsx +360 -0
- package/dashboard/src/pages/PhaseDetailView.jsx +216 -0
- package/dashboard/src/pages/PlanningPage.jsx +181 -0
- package/dashboard/src/pages/ProgressPage.jsx +249 -0
- package/dashboard/src/pages/ResearchPage.jsx +129 -0
- package/dashboard/src/pages/RoadmapPage.jsx +251 -0
- package/dashboard/src/pages/SessionsPage.jsx +117 -0
- package/dashboard/src/pages/Telemetry.jsx +166 -0
- package/dashboard/src/pages/planning/DecisionsTab.jsx +153 -0
- package/dashboard/src/pages/planning/FilesTab.jsx +420 -0
- package/dashboard/src/pages/planning/MilestoneDetail.jsx +319 -0
- package/dashboard/src/pages/planning/MilestonesTab.jsx +151 -0
- package/dashboard/src/pages/planning/NotesTab.jsx +251 -0
- package/dashboard/src/pages/planning/PhasesTab.jsx +218 -0
- package/dashboard/src/pages/planning/QuickTab.jsx +50 -0
- package/dashboard/src/pages/planning/ResearchTab.jsx +103 -0
- package/dashboard/src/pages/planning/TodosTab.jsx +297 -0
- package/dashboard/src/theme/ThemeProvider.jsx +38 -0
- package/dashboard/src/theme/tokens.js +17 -0
- package/dashboard/tests/components/ConfirmModal.test.jsx +179 -0
- package/dashboard/tests/components/ConnectionBanner.test.jsx +37 -0
- package/dashboard/tests/components/ErrorBoundary.test.jsx +59 -0
- package/dashboard/tests/components/LoadingSkeleton.test.jsx +46 -0
- package/dashboard/tests/components/ToastContainer.test.jsx +47 -0
- package/dashboard/tests/components/Toggle.test.jsx +61 -0
- package/dashboard/tests/hooks/useFetch.test.jsx +77 -0
- package/dashboard/tests/hooks/useToast.test.jsx +78 -0
- package/dashboard/tests/hooks/useWebSocket.test.jsx +128 -0
- package/dashboard/tests/pages/ConfigPage.test.jsx +199 -0
- package/dashboard/tests/pages/PlanningPage.test.jsx +119 -0
- package/dashboard/tests/pages/planning/FilesTab.test.jsx +198 -0
- package/dashboard/tests/pages/planning/NotesTab.test.jsx +178 -0
- package/dashboard/tests/pages/planning/TodosTab.test.jsx +188 -0
- package/dashboard/tests/performance.test.jsx +46 -0
- package/dashboard/tests/routes/config.test.js +98 -0
- package/dashboard/tests/routes/health.test.js +40 -0
- package/dashboard/tests/routes/planning.test.js +112 -0
- package/dashboard/tests/routes/roadmap.test.js +91 -0
- package/dashboard/tests/routes/status.test.js +131 -0
- package/dashboard/tests/server/planning-reader.test.js +153 -0
- package/dashboard/tests/setup.js +7 -0
- package/dashboard/vite.config.js +41 -0
- package/package.json +56 -41
- package/plan-build-run/bin/config-schema.json +1298 -0
- package/plugins/pbr/.claude-plugin/plugin.json +1 -1
- package/plugins/pbr/CLAUDE.md +19 -0
- package/plugins/pbr/UI-CONSISTENCY-GAPS.md +1 -1
- package/plugins/pbr/agents/advisor-researcher.md +101 -0
- package/plugins/pbr/agents/audit.md +207 -89
- package/plugins/pbr/agents/codebase-mapper.md +158 -23
- package/plugins/pbr/agents/debugger.md +210 -34
- package/plugins/pbr/agents/dev-sync.md +206 -0
- package/plugins/pbr/agents/executor.md +734 -38
- package/plugins/pbr/agents/general.md +69 -5
- package/plugins/pbr/agents/integration-checker.md +147 -31
- package/plugins/pbr/agents/intel-updater.md +332 -0
- package/plugins/pbr/agents/nyquist-auditor.md +254 -0
- package/plugins/pbr/agents/plan-checker.md +268 -65
- package/plugins/pbr/agents/planner.md +449 -41
- package/plugins/pbr/agents/researcher.md +218 -37
- package/plugins/pbr/agents/roadmapper.md +398 -0
- package/plugins/pbr/agents/synthesizer.md +166 -25
- package/plugins/pbr/agents/ui-checker.md +204 -0
- package/plugins/pbr/agents/ui-researcher.md +224 -0
- package/plugins/pbr/agents/verifier.md +570 -46
- package/plugins/pbr/commands/add-phase.md +75 -0
- package/plugins/pbr/commands/add-todo.md +8 -0
- package/plugins/pbr/commands/audit-fix.md +5 -0
- package/plugins/pbr/commands/audit-milestone.md +8 -0
- package/plugins/pbr/commands/autonomous.md +5 -0
- package/plugins/pbr/commands/backlog.md +6 -0
- package/plugins/pbr/commands/check-todos.md +8 -0
- package/plugins/pbr/commands/complete-milestone.md +8 -0
- package/plugins/pbr/commands/config.md +1 -1
- package/plugins/pbr/commands/discuss-phase.md +6 -0
- package/plugins/pbr/commands/execute-phase.md +6 -0
- package/plugins/pbr/commands/fast.md +6 -0
- package/plugins/pbr/commands/forensics.md +6 -0
- package/plugins/pbr/commands/import.md +1 -1
- package/plugins/pbr/commands/insert-phase.md +65 -0
- package/plugins/pbr/commands/intel.md +5 -0
- package/plugins/pbr/commands/join-discord.md +11 -0
- package/plugins/pbr/commands/list-phase-assumptions.md +5 -0
- package/plugins/pbr/commands/map-codebase.md +6 -0
- package/plugins/pbr/commands/milestone-summary.md +6 -0
- package/plugins/pbr/commands/new-milestone.md +8 -0
- package/plugins/pbr/commands/new-project.md +6 -0
- package/plugins/pbr/commands/pause-work.md +5 -0
- package/plugins/pbr/commands/plan-milestone-gaps.md +7 -0
- package/plugins/pbr/commands/plan-phase.md +6 -0
- package/plugins/pbr/commands/plant-seed.md +6 -0
- package/plugins/pbr/commands/profile-user.md +5 -0
- package/plugins/pbr/commands/profile.md +5 -0
- package/plugins/pbr/commands/progress.md +6 -0
- package/plugins/pbr/commands/quick.md +1 -1
- package/plugins/pbr/commands/reapply-patches.md +47 -0
- package/plugins/pbr/commands/release.md +6 -0
- package/plugins/pbr/commands/remove-phase.md +66 -0
- package/plugins/pbr/commands/research-phase.md +59 -0
- package/plugins/pbr/commands/resume-work.md +5 -0
- package/plugins/pbr/commands/seed.md +6 -0
- package/plugins/pbr/commands/session-report.md +5 -0
- package/plugins/pbr/commands/set-profile.md +6 -0
- package/plugins/pbr/commands/settings.md +5 -0
- package/plugins/pbr/commands/setup.md +1 -1
- package/plugins/pbr/commands/ship.md +5 -0
- package/plugins/pbr/commands/stats.md +6 -0
- package/plugins/pbr/commands/test.md +5 -0
- package/plugins/pbr/commands/thread.md +6 -0
- package/plugins/pbr/commands/todo.md +1 -1
- package/plugins/pbr/commands/ui-phase.md +5 -0
- package/plugins/pbr/commands/ui-review.md +5 -0
- package/plugins/pbr/commands/undo.md +5 -0
- package/plugins/pbr/commands/update.md +37 -0
- package/plugins/pbr/commands/validate-phase.md +5 -0
- package/plugins/pbr/commands/verify-work.md +6 -0
- package/plugins/pbr/dashboard/package-lock.json +6 -0
- package/plugins/pbr/dist/architecture-guard.js +76 -0
- package/plugins/pbr/dist/audit-dimensions.js +556 -0
- package/plugins/pbr/dist/auto-continue.js +277 -0
- package/plugins/pbr/dist/block-skill-self-read.js +124 -0
- package/plugins/pbr/dist/check-agent-state-write.js +63 -0
- package/plugins/pbr/dist/check-config-change.js +213 -0
- package/plugins/pbr/dist/check-cross-plugin-sync.js +93 -0
- package/plugins/pbr/dist/check-dangerous-commands.js +193 -0
- package/plugins/pbr/dist/check-direct-state-write.js +37 -0
- package/plugins/pbr/dist/check-doc-sprawl.js +102 -0
- package/plugins/pbr/dist/check-phase-boundary.js +191 -0
- package/plugins/pbr/dist/check-plan-format.js +227 -0
- package/plugins/pbr/dist/check-read-first.js +345 -0
- package/plugins/pbr/dist/check-roadmap-sync.js +507 -0
- package/plugins/pbr/dist/check-skill-workflow.js +354 -0
- package/plugins/pbr/dist/check-state-sync.js +676 -0
- package/plugins/pbr/dist/check-subagent-output.js +425 -0
- package/plugins/pbr/dist/check-summary-gate.js +188 -0
- package/plugins/pbr/dist/context-bridge.js +425 -0
- package/plugins/pbr/dist/context-budget-check.js +442 -0
- package/plugins/pbr/dist/context-quality.js +271 -0
- package/plugins/pbr/dist/enforce-context-budget.js +138 -0
- package/plugins/pbr/dist/enforce-pbr-workflow.js +277 -0
- package/plugins/pbr/dist/event-handler.js +212 -0
- package/plugins/pbr/dist/event-logger.js +125 -0
- package/plugins/pbr/dist/feedback-loop.js +155 -0
- package/plugins/pbr/dist/graph-update.js +422 -0
- package/plugins/pbr/dist/hook-logger.js +114 -0
- package/plugins/pbr/dist/hook-server-client.js +361 -0
- package/plugins/pbr/dist/hook-server.js +664 -0
- package/plugins/pbr/dist/hooks-schema.json +87 -0
- package/plugins/pbr/dist/instructions-loaded.js +173 -0
- package/plugins/pbr/dist/intercept-plan-mode.js +81 -0
- package/plugins/pbr/dist/log-notification.js +131 -0
- package/plugins/pbr/dist/log-subagent.js +367 -0
- package/plugins/pbr/dist/log-tool-failure.js +140 -0
- package/plugins/pbr/dist/milestone-learnings.js +519 -0
- package/plugins/pbr/dist/pbr-tools.js +493 -0
- package/plugins/pbr/dist/post-bash-triage.js +96 -0
- package/plugins/pbr/dist/post-compact.js +135 -0
- package/plugins/pbr/dist/post-hoc.js +237 -0
- package/plugins/pbr/dist/post-write-dispatch.js +243 -0
- package/plugins/pbr/dist/post-write-quality.js +208 -0
- package/plugins/pbr/dist/pre-bash-dispatch.js +212 -0
- package/plugins/pbr/dist/pre-skill-dispatch.js +114 -0
- package/plugins/pbr/dist/pre-task-dispatch.js +269 -0
- package/plugins/pbr/dist/pre-write-dispatch.js +234 -0
- package/plugins/pbr/dist/progress-tracker.js +173 -0
- package/plugins/pbr/dist/prompt-guard.js +114 -0
- package/plugins/pbr/dist/prompt-routing.js +209 -0
- package/plugins/pbr/dist/quick-status.js +179 -0
- package/plugins/pbr/dist/record-incident.js +37 -0
- package/plugins/pbr/dist/run-hook.js +132 -0
- package/plugins/pbr/dist/session-cleanup.js +653 -0
- package/plugins/pbr/dist/session-tracker.js +124 -0
- package/plugins/pbr/dist/status-line.js +849 -0
- package/plugins/pbr/dist/suggest-compact.js +307 -0
- package/plugins/pbr/dist/sync-context-to-claude.js +100 -0
- package/plugins/pbr/dist/task-completed.js +206 -0
- package/plugins/pbr/dist/track-context-budget.js +432 -0
- package/plugins/pbr/dist/track-user-gates.js +88 -0
- package/plugins/pbr/dist/trust-tracker.js +193 -0
- package/plugins/pbr/dist/validate-commit.js +233 -0
- package/plugins/pbr/dist/validate-skill-args.js +222 -0
- package/plugins/pbr/dist/validate-task.js +271 -0
- package/plugins/pbr/dist/worktree-create.js +144 -0
- package/plugins/pbr/dist/worktree-remove.js +147 -0
- package/plugins/pbr/hooks/hooks.json +137 -65
- package/plugins/pbr/references/agent-contracts.md +39 -8
- package/plugins/pbr/references/agent-teams.md +3 -3
- package/plugins/pbr/references/archive/checkpoints.md +189 -0
- package/plugins/pbr/references/archive/context-quality-tiers.md +45 -0
- package/plugins/pbr/references/archive/hook-ordering.md +89 -0
- package/plugins/pbr/references/archive/limitations.md +106 -0
- package/plugins/pbr/references/archive/pbr-rules.md +194 -0
- package/plugins/pbr/references/archive/pbr-tools-cli.md +415 -0
- package/plugins/pbr/references/archive/pretooluse-jsonl-behavior.md +58 -0
- package/plugins/pbr/references/archive/signal-files.md +41 -0
- package/plugins/pbr/references/archive/tmux-setup.md +288 -0
- package/plugins/pbr/references/archive/verification-matrix.md +34 -0
- package/plugins/pbr/references/archive/verification-patterns.md +277 -0
- package/plugins/pbr/references/archive/worktree-sparse-checkout.md +86 -0
- package/plugins/pbr/references/assumptions.md +42 -0
- package/plugins/pbr/references/checkpoints.md +723 -104
- package/plugins/pbr/references/config-reference.md +387 -10
- package/plugins/pbr/references/continuation-format.md +1 -0
- package/plugins/pbr/references/decimal-phase-calculation.md +65 -0
- package/plugins/pbr/references/deviation-rules.md +12 -0
- package/plugins/pbr/references/few-shot-examples/audit.md +77 -0
- package/plugins/pbr/references/few-shot-examples/check-plan-format.md +172 -0
- package/plugins/pbr/references/few-shot-examples/check-subagent-output.md +118 -0
- package/plugins/pbr/references/few-shot-examples/integration-checker.md +70 -0
- package/plugins/pbr/references/few-shot-examples/nyquist-auditor.md +83 -0
- package/plugins/pbr/references/few-shot-examples/plan-checker.md +73 -0
- package/plugins/pbr/references/few-shot-examples/ui-checker.md +71 -0
- package/plugins/pbr/references/few-shot-examples/verifier.md +109 -0
- package/plugins/pbr/references/git-integration.md +110 -27
- package/plugins/pbr/references/git-planning-commit.md +35 -0
- package/plugins/pbr/references/model-profile-resolution.md +34 -0
- package/plugins/pbr/references/model-profiles.md +90 -7
- package/plugins/pbr/references/model-selection.md +1 -1
- package/plugins/pbr/references/node-repair.md +48 -0
- package/plugins/pbr/references/plan-authoring.md +65 -0
- package/plugins/pbr/references/plan-format.md +184 -10
- package/plugins/pbr/references/questioning.md +138 -49
- package/plugins/pbr/references/reading-verification.md +4 -4
- package/plugins/pbr/references/tdd.md +263 -0
- package/plugins/pbr/references/thinking-models-planning.md +47 -0
- package/plugins/pbr/references/thinking-models-verification.md +44 -0
- package/plugins/pbr/references/ui-brand.md +449 -0
- package/plugins/pbr/references/verification-overrides.md +39 -0
- package/plugins/pbr/references/verification-patterns.md +529 -113
- package/plugins/pbr/scripts/architecture-guard.js +76 -0
- package/plugins/pbr/scripts/audit-checks/behavioral-compliance.js +2098 -0
- package/plugins/pbr/scripts/audit-checks/error-analysis.js +989 -0
- package/plugins/pbr/scripts/audit-checks/feature-verification.js +723 -0
- package/plugins/pbr/scripts/audit-checks/index.js +433 -0
- package/plugins/pbr/scripts/audit-checks/infrastructure.js +816 -0
- package/plugins/pbr/scripts/audit-checks/quality-metrics.js +455 -0
- package/plugins/pbr/scripts/audit-checks/session-quality.js +980 -0
- package/plugins/pbr/scripts/audit-checks/si-agent-hook-config-checks.js +396 -0
- package/plugins/pbr/scripts/audit-checks/si-cross-cutting-checks.js +272 -0
- package/plugins/pbr/scripts/audit-checks/si-skill-checks.js +424 -0
- package/plugins/pbr/scripts/audit-checks/workflow-compliance.js +1175 -0
- package/plugins/pbr/scripts/audit-dimensions.js +556 -0
- package/plugins/pbr/scripts/auto-continue.js +192 -37
- package/plugins/pbr/scripts/block-skill-self-read.js +124 -0
- package/plugins/pbr/scripts/check-agent-state-write.js +63 -0
- package/plugins/pbr/scripts/check-config-change.js +84 -1
- package/plugins/pbr/scripts/check-cross-plugin-sync.js +93 -0
- package/plugins/pbr/scripts/check-dangerous-commands.js +18 -5
- package/plugins/pbr/scripts/check-direct-state-write.js +37 -0
- package/plugins/pbr/scripts/check-phase-boundary.js +3 -2
- package/plugins/pbr/scripts/check-plan-format.js +153 -354
- package/plugins/pbr/scripts/check-read-first.js +345 -0
- package/plugins/pbr/scripts/check-roadmap-sync.js +174 -19
- package/plugins/pbr/scripts/check-skill-workflow.js +21 -16
- package/plugins/pbr/scripts/check-state-sync.js +352 -220
- package/plugins/pbr/scripts/check-subagent-output.js +296 -333
- package/plugins/pbr/scripts/check-summary-gate.js +5 -15
- package/plugins/pbr/scripts/commands/benchmarks.js +195 -0
- package/plugins/pbr/scripts/commands/calibrate.js +530 -0
- package/plugins/pbr/scripts/commands/config.js +72 -0
- package/plugins/pbr/scripts/commands/misc.js +779 -0
- package/plugins/pbr/scripts/commands/phase.js +293 -0
- package/plugins/pbr/scripts/commands/roadmap.js +75 -0
- package/plugins/pbr/scripts/commands/state.js +84 -0
- package/plugins/pbr/scripts/commands/stress-test.js +349 -0
- package/plugins/pbr/scripts/commands/todo.js +191 -0
- package/plugins/pbr/scripts/commands/verify.js +169 -0
- package/plugins/pbr/scripts/config-schema.json +1183 -95
- package/plugins/pbr/scripts/context-bridge.js +425 -0
- package/plugins/pbr/scripts/context-budget-check.js +171 -16
- package/plugins/pbr/scripts/context-quality.js +271 -0
- package/plugins/pbr/scripts/enforce-context-budget.js +138 -0
- package/plugins/pbr/scripts/enforce-pbr-workflow.js +277 -0
- package/plugins/pbr/scripts/event-handler.js +137 -87
- package/plugins/pbr/scripts/event-logger.js +58 -25
- package/plugins/pbr/scripts/feedback-loop.js +155 -0
- package/plugins/pbr/scripts/graph-update.js +422 -0
- package/plugins/pbr/scripts/hook-logger.js +69 -35
- package/plugins/pbr/scripts/hook-server-client.js +361 -0
- package/plugins/pbr/scripts/hook-server.js +664 -0
- package/plugins/pbr/scripts/hooks-schema.json +12 -5
- package/plugins/pbr/scripts/instructions-loaded.js +173 -0
- package/plugins/pbr/scripts/intent-router.cjs +147 -0
- package/plugins/pbr/scripts/intercept-plan-mode.js +52 -18
- package/plugins/pbr/scripts/lib/alternatives.js +203 -0
- package/plugins/pbr/scripts/lib/audit.js +65 -0
- package/plugins/pbr/scripts/lib/auto-cleanup.js +221 -0
- package/plugins/pbr/scripts/lib/auto-verify.js +123 -0
- package/plugins/pbr/scripts/lib/benchmark.js +190 -0
- package/plugins/pbr/scripts/lib/build.js +719 -0
- package/plugins/pbr/scripts/lib/ci-fix-loop.js +228 -0
- package/plugins/pbr/scripts/lib/commands.js +483 -0
- package/plugins/pbr/scripts/lib/compound.js +222 -0
- package/plugins/pbr/scripts/lib/config-cache.js +83 -0
- package/plugins/pbr/scripts/lib/config.js +1469 -0
- package/plugins/pbr/scripts/lib/context.js +254 -0
- package/plugins/pbr/scripts/lib/contextual-help.js +183 -0
- package/plugins/pbr/scripts/lib/convention-detector.js +413 -0
- package/plugins/pbr/scripts/lib/core.js +1585 -0
- package/plugins/pbr/scripts/lib/dashboard-launch.js +364 -0
- package/plugins/pbr/scripts/lib/data-hygiene.js +179 -0
- package/plugins/pbr/scripts/lib/decision-extraction.js +183 -0
- package/plugins/pbr/scripts/lib/decisions.js +194 -0
- package/plugins/pbr/scripts/lib/dependency-break.js +147 -0
- package/plugins/pbr/scripts/lib/format-validators.js +1049 -0
- package/plugins/pbr/scripts/lib/frontmatter.js +302 -0
- package/plugins/pbr/scripts/lib/gates/advisories.js +133 -0
- package/plugins/pbr/scripts/lib/gates/build-dependency.js +118 -0
- package/plugins/pbr/scripts/lib/gates/build-executor.js +106 -0
- package/plugins/pbr/scripts/lib/gates/doc-existence.js +46 -0
- package/plugins/pbr/scripts/lib/gates/helpers.js +98 -0
- package/plugins/pbr/scripts/lib/gates/inline-execution.js +187 -0
- package/plugins/pbr/scripts/lib/gates/milestone-complete.js +139 -0
- package/plugins/pbr/scripts/lib/gates/milestone-summary.js +121 -0
- package/plugins/pbr/scripts/lib/gates/multi-phase-loader.js +149 -0
- package/plugins/pbr/scripts/lib/gates/plan-executor.js +36 -0
- package/plugins/pbr/scripts/lib/gates/plan-validation.js +115 -0
- package/plugins/pbr/scripts/lib/gates/quick-executor.js +78 -0
- package/plugins/pbr/scripts/lib/gates/review-planner.js +63 -0
- package/plugins/pbr/scripts/lib/gates/review-verifier.js +71 -0
- package/plugins/pbr/scripts/lib/gates/rich-agent-context.js +148 -0
- package/plugins/pbr/scripts/lib/gates/sprint-preflight.js +30 -0
- package/plugins/pbr/scripts/lib/gates/user-confirmation.js +95 -0
- package/plugins/pbr/scripts/lib/graph-cli.js +89 -0
- package/plugins/pbr/scripts/lib/graph.js +553 -0
- package/plugins/pbr/scripts/lib/handoff-validators.js +224 -0
- package/plugins/pbr/scripts/lib/health-checks.js +107 -0
- package/plugins/pbr/scripts/lib/health-phase06.js +120 -0
- package/plugins/pbr/scripts/lib/health.js +132 -0
- package/plugins/pbr/scripts/lib/help.js +100 -0
- package/plugins/pbr/scripts/lib/history.js +150 -0
- package/plugins/pbr/scripts/lib/impact-analysis.js +319 -0
- package/plugins/pbr/scripts/lib/incidents.js +190 -0
- package/plugins/pbr/scripts/lib/init.js +643 -0
- package/plugins/pbr/scripts/lib/insights-parser.js +320 -0
- package/plugins/pbr/scripts/lib/intel.js +653 -0
- package/plugins/pbr/scripts/lib/learnings.js +511 -0
- package/plugins/pbr/scripts/lib/migrate.js +309 -0
- package/plugins/pbr/scripts/lib/milestone.js +306 -0
- package/plugins/pbr/scripts/lib/msys-path.js +20 -0
- package/plugins/pbr/scripts/lib/negative-knowledge.js +194 -0
- package/plugins/pbr/scripts/lib/notification-throttle.js +141 -0
- package/plugins/pbr/scripts/lib/onboarding-generator.js +288 -0
- package/plugins/pbr/scripts/lib/parse-args.js +134 -0
- package/plugins/pbr/scripts/lib/pattern-routing.js +55 -0
- package/plugins/pbr/scripts/lib/patterns.js +272 -0
- package/plugins/pbr/scripts/lib/perf.js +190 -0
- package/plugins/pbr/scripts/lib/phase.js +1043 -0
- package/plugins/pbr/scripts/lib/pid-lock.js +156 -0
- package/plugins/pbr/scripts/lib/post-hoc.js +160 -0
- package/plugins/pbr/scripts/lib/pre-commit-checks.js +220 -0
- package/plugins/pbr/scripts/lib/pre-research.js +126 -0
- package/plugins/pbr/scripts/lib/premature-completion.js +312 -0
- package/plugins/pbr/scripts/lib/preview.js +174 -0
- package/plugins/pbr/scripts/lib/progress-visualization.js +296 -0
- package/plugins/pbr/scripts/lib/quick-init.js +131 -0
- package/plugins/pbr/scripts/lib/reference.js +236 -0
- package/plugins/pbr/scripts/lib/requirements.js +153 -0
- package/plugins/pbr/scripts/lib/resolve-root.js +66 -0
- package/plugins/pbr/scripts/lib/reverse-spec.js +259 -0
- package/plugins/pbr/scripts/lib/roadmap.js +1089 -0
- package/plugins/pbr/scripts/lib/security-scan.js +200 -0
- package/plugins/pbr/scripts/lib/session-briefing.js +918 -0
- package/plugins/pbr/scripts/lib/skill-section.js +99 -0
- package/plugins/pbr/scripts/lib/smart-next-task.js +198 -0
- package/plugins/pbr/scripts/lib/snapshot-manager.js +232 -0
- package/plugins/pbr/scripts/lib/spec-diff.js +209 -0
- package/plugins/pbr/scripts/lib/spec-engine.js +189 -0
- package/plugins/pbr/scripts/lib/spot-check.js +539 -0
- package/plugins/pbr/scripts/lib/state-queue.js +171 -0
- package/plugins/pbr/scripts/lib/state.js +1082 -0
- package/plugins/pbr/scripts/lib/status-render.js +511 -0
- package/plugins/pbr/scripts/lib/step-verify.js +149 -0
- package/plugins/pbr/scripts/lib/subagent-validators.js +1119 -0
- package/plugins/pbr/scripts/lib/suggest-next.js +435 -0
- package/plugins/pbr/scripts/lib/tech-debt-scanner.js +116 -0
- package/plugins/pbr/scripts/lib/templates.js +362 -0
- package/plugins/pbr/scripts/lib/test-selection.js +163 -0
- package/plugins/pbr/scripts/lib/todo.js +300 -0
- package/plugins/pbr/scripts/lib/verify.js +1561 -0
- package/plugins/pbr/scripts/log-notification.js +131 -0
- package/plugins/pbr/scripts/log-subagent.js +221 -18
- package/plugins/pbr/scripts/log-tool-failure.js +60 -5
- package/plugins/pbr/scripts/milestone-learnings.js +519 -0
- package/plugins/pbr/scripts/package.json +1 -1
- package/plugins/pbr/scripts/pbr-tools.js +362 -1247
- package/plugins/pbr/scripts/post-bash-triage.js +96 -0
- package/plugins/pbr/scripts/post-compact.js +135 -0
- package/plugins/pbr/scripts/post-hoc.js +237 -0
- package/plugins/pbr/scripts/post-write-dispatch.js +201 -31
- package/plugins/pbr/scripts/post-write-quality.js +4 -3
- package/plugins/pbr/scripts/pre-bash-dispatch.js +147 -51
- package/plugins/pbr/scripts/pre-skill-dispatch.js +114 -0
- package/plugins/pbr/scripts/pre-task-dispatch.js +269 -0
- package/plugins/pbr/scripts/pre-write-dispatch.js +170 -72
- package/plugins/pbr/scripts/progress-tracker.js +121 -324
- package/plugins/pbr/scripts/prompt-guard.js +114 -0
- package/plugins/pbr/scripts/prompt-routing.js +209 -0
- package/plugins/pbr/scripts/quick-status.js +179 -0
- package/plugins/pbr/scripts/record-incident.js +37 -0
- package/plugins/pbr/scripts/risk-classifier.cjs +123 -0
- package/plugins/pbr/scripts/run-hook.js +63 -23
- package/plugins/pbr/scripts/session-cleanup.js +428 -29
- package/plugins/pbr/scripts/session-tracker.js +124 -0
- package/plugins/pbr/scripts/status-line.js +571 -43
- package/plugins/pbr/scripts/suggest-compact.js +201 -13
- package/plugins/pbr/scripts/sync-context-to-claude.js +100 -0
- package/plugins/pbr/scripts/task-completed.js +165 -4
- package/plugins/pbr/scripts/test/config.test.js +126 -0
- package/plugins/pbr/scripts/test/cross-platform.test.js +120 -0
- package/plugins/pbr/scripts/test/fixtures/config.json +20 -0
- package/plugins/pbr/scripts/test/fixtures/plan.md +54 -0
- package/plugins/pbr/scripts/test/fixtures/project.md +30 -0
- package/plugins/pbr/scripts/test/fixtures/roadmap.md +55 -0
- package/plugins/pbr/scripts/test/fixtures/state.md +60 -0
- package/plugins/pbr/scripts/test/fixtures/summary.md +35 -0
- package/plugins/pbr/scripts/test/fixtures.test.js +184 -0
- package/plugins/pbr/scripts/test/phase.test.js +142 -0
- package/plugins/pbr/scripts/test/roadmap.test.js +96 -0
- package/plugins/pbr/scripts/test/state.test.js +155 -0
- package/plugins/pbr/scripts/track-context-budget.js +368 -104
- package/plugins/pbr/scripts/track-user-gates.js +88 -0
- package/plugins/pbr/scripts/trust-tracker.js +193 -0
- package/plugins/pbr/scripts/validate-commit.js +51 -19
- package/plugins/pbr/scripts/validate-skill-args.js +85 -14
- package/plugins/pbr/scripts/validate-task.js +83 -622
- package/plugins/pbr/scripts/worktree-create.js +144 -0
- package/plugins/pbr/scripts/worktree-remove.js +147 -0
- package/plugins/pbr/skills/audit/SKILL.md +195 -24
- package/plugins/pbr/skills/audit-fix/SKILL.md +326 -0
- package/plugins/pbr/skills/autonomous/SKILL.md +551 -0
- package/plugins/pbr/skills/backlog/SKILL.md +56 -0
- package/plugins/pbr/skills/begin/SKILL.md +508 -153
- package/plugins/pbr/skills/begin/templates/STATE.md.tmpl +1 -2
- package/plugins/pbr/skills/begin/templates/config.json.tmpl +419 -36
- package/plugins/pbr/skills/begin/templates/researcher-prompt.md.tmpl +28 -0
- package/plugins/pbr/skills/begin/templates/roadmap-prompt.md.tmpl +28 -3
- package/plugins/pbr/skills/begin/templates/synthesis-prompt.md.tmpl +33 -5
- package/plugins/pbr/skills/build/SKILL.md +1180 -357
- package/plugins/pbr/skills/build/templates/continuation-prompt.md.tmpl +26 -0
- package/plugins/pbr/skills/build/templates/executor-prompt.md.tmpl +77 -0
- package/plugins/pbr/skills/build/templates/inline-verifier-prompt.md.tmpl +33 -0
- package/plugins/pbr/skills/build/templates/qa-round-context.md.tmpl +16 -0
- package/plugins/pbr/skills/config/SKILL.md +112 -9
- package/plugins/pbr/skills/continue/SKILL.md +113 -33
- package/plugins/pbr/skills/dashboard/SKILL.md +21 -9
- package/plugins/pbr/skills/debug/SKILL.md +70 -12
- package/plugins/pbr/skills/debug/templates/continuation-prompt.md.tmpl +12 -1
- package/plugins/pbr/skills/debug/templates/initial-investigation-prompt.md.tmpl +12 -5
- package/plugins/pbr/skills/discuss/SKILL.md +205 -24
- package/plugins/pbr/skills/discuss/templates/CONTEXT.md.tmpl +21 -1
- package/plugins/pbr/skills/do/SKILL.md +119 -24
- package/plugins/pbr/skills/explore/SKILL.md +95 -20
- package/plugins/pbr/skills/fast/SKILL.md +94 -0
- package/plugins/pbr/skills/forensics/SKILL.md +144 -0
- package/plugins/pbr/skills/health/SKILL.md +35 -115
- package/plugins/pbr/skills/help/SKILL.md +83 -123
- package/plugins/pbr/skills/import/SKILL.md +332 -13
- package/plugins/pbr/skills/intel/SKILL.md +131 -0
- package/plugins/pbr/skills/list-phase-assumptions/SKILL.md +231 -0
- package/plugins/pbr/skills/milestone/SKILL.md +383 -274
- package/plugins/pbr/skills/milestone/templates/audit-output.md.tmpl +76 -0
- package/plugins/pbr/skills/milestone/templates/complete-output.md.tmpl +32 -0
- package/plugins/pbr/skills/milestone/templates/edge-cases.md +54 -0
- package/plugins/pbr/skills/milestone/templates/gaps-output.md.tmpl +25 -0
- package/plugins/pbr/skills/milestone/templates/integration-checker-prompt.md.tmpl +25 -0
- package/plugins/pbr/skills/milestone/templates/new-output.md.tmpl +29 -0
- package/plugins/pbr/skills/milestone-summary/SKILL.md +86 -0
- package/plugins/pbr/skills/note/SKILL.md +20 -4
- package/plugins/pbr/skills/pause/SKILL.md +54 -14
- package/plugins/pbr/skills/pause/templates/continue-here.md.tmpl +33 -52
- package/plugins/pbr/skills/plan/SKILL.md +526 -280
- package/plugins/pbr/skills/plan/templates/checker-prompt.md.tmpl +20 -2
- package/plugins/pbr/skills/plan/templates/completion-output.md.tmpl +27 -0
- package/plugins/pbr/skills/plan/templates/planner-prompt.md.tmpl +43 -1
- package/plugins/pbr/skills/plan/templates/revision-prompt.md.tmpl +21 -5
- package/plugins/pbr/skills/profile/SKILL.md +185 -0
- package/plugins/pbr/skills/profile-user/SKILL.md +227 -0
- package/plugins/pbr/skills/quick/SKILL.md +435 -100
- package/plugins/pbr/skills/release/SKILL.md +206 -0
- package/plugins/pbr/skills/resume/SKILL.md +170 -46
- package/plugins/pbr/skills/review/SKILL.md +233 -165
- package/plugins/pbr/skills/review/templates/verifier-prompt.md.tmpl +7 -0
- package/plugins/pbr/skills/scan/SKILL.md +152 -106
- package/plugins/pbr/skills/scan/templates/mapper-prompt.md.tmpl +5 -56
- package/plugins/pbr/skills/seed/SKILL.md +87 -0
- package/plugins/pbr/skills/session-report/SKILL.md +130 -0
- package/plugins/pbr/skills/setup/SKILL.md +150 -202
- package/plugins/pbr/skills/shared/agent-context-enrichment.md +21 -0
- package/plugins/pbr/skills/shared/agent-type-resolution.md +32 -0
- package/plugins/pbr/skills/shared/commit-planning-docs.md +8 -0
- package/plugins/pbr/skills/shared/context-budget.md +66 -1
- package/plugins/pbr/skills/shared/context-loader-task.md +18 -11
- package/plugins/pbr/skills/shared/criterion-writing.md +58 -0
- package/plugins/pbr/skills/shared/digest-select.md +2 -2
- package/plugins/pbr/skills/shared/domain-probes.md +1 -1
- package/plugins/pbr/skills/shared/error-reporting.md +38 -60
- package/plugins/pbr/skills/shared/gate-prompts.md +4 -2
- package/plugins/pbr/skills/shared/memory-capture.md +48 -0
- package/plugins/pbr/skills/shared/phase-argument-parsing.md +4 -4
- package/plugins/pbr/skills/shared/revision-loop.md +24 -6
- package/plugins/pbr/skills/shared/state-update.md +46 -61
- package/plugins/pbr/skills/shared/universal-anti-patterns.md +27 -4
- package/plugins/pbr/skills/ship/SKILL.md +155 -0
- package/plugins/pbr/skills/stats/SKILL.md +110 -0
- package/plugins/pbr/skills/status/SKILL.md +185 -119
- package/plugins/pbr/skills/test/SKILL.md +254 -0
- package/plugins/pbr/skills/thread/SKILL.md +73 -0
- package/plugins/pbr/skills/todo/SKILL.md +28 -72
- package/plugins/pbr/skills/ui-phase/SKILL.md +180 -0
- package/plugins/pbr/skills/ui-review/SKILL.md +206 -0
- package/plugins/pbr/skills/undo/SKILL.md +221 -0
- package/plugins/pbr/skills/validate-phase/SKILL.md +362 -0
- package/plugins/pbr/templates/CONTEXT.md.tmpl +45 -20
- package/plugins/pbr/templates/DISCOVERY.md.tmpl +29 -0
- package/plugins/pbr/templates/DISCUSSION-LOG.md.tmpl +49 -0
- package/plugins/pbr/templates/HANDOFF.json.tmpl +30 -0
- package/plugins/pbr/templates/INTEGRATION-REPORT.md.tmpl +18 -2
- package/plugins/pbr/templates/MILESTONE-AUDIT.md.tmpl +44 -0
- package/plugins/pbr/templates/PROJECT.md.tmpl +126 -0
- package/plugins/pbr/templates/REQUIREMENTS.md.tmpl +96 -0
- package/plugins/pbr/templates/RETROSPECTIVE.md.tmpl +43 -0
- package/plugins/pbr/templates/ROADMAP.md.tmpl +106 -14
- package/plugins/pbr/templates/SUMMARY-complex.md.tmpl +133 -0
- package/plugins/pbr/templates/SUMMARY-minimal.md.tmpl +55 -0
- package/plugins/pbr/templates/SUMMARY.md.tmpl +21 -0
- package/plugins/pbr/templates/UAT.md.tmpl +94 -0
- package/plugins/pbr/templates/UI-SPEC.md.tmpl +144 -0
- package/plugins/pbr/templates/VALIDATION.md.tmpl +94 -0
- package/plugins/pbr/templates/VERIFICATION-DETAIL.md.tmpl +54 -13
- package/plugins/pbr/templates/project-CONTEXT.md.tmpl +59 -0
- package/plugins/pbr/templates/research-outputs/ARCHITECTURE.md.tmpl +91 -0
- package/plugins/pbr/templates/research-outputs/FEATURES.md.tmpl +64 -0
- package/plugins/pbr/templates/research-outputs/PITFALLS.md.tmpl +50 -0
- package/plugins/pbr/templates/research-outputs/STACK.md.tmpl +63 -0
- package/plugins/pbr/templates/research-outputs/SUMMARY.md.tmpl +98 -0
- package/scripts/build-hooks.js +61 -0
- package/scripts/check-ci.js +100 -0
- package/scripts/clean-changelog.js +364 -0
- package/scripts/generate-derivatives.js +581 -0
- package/scripts/posttest.js +93 -0
- package/scripts/release.js +262 -0
- package/scripts/run-tests.cjs +29 -0
- package/scripts/test-wrapper.js +43 -0
- package/dashboard/bin/cli.js +0 -25
- package/dashboard/public/css/layout.css +0 -704
- package/dashboard/public/css/status-colors.css +0 -98
- package/dashboard/public/css/tokens.css +0 -59
- package/dashboard/public/js/htmx-title.js +0 -5
- package/dashboard/public/js/sidebar-toggle.js +0 -34
- package/dashboard/public/js/sse-client.js +0 -100
- package/dashboard/public/js/theme-toggle.js +0 -46
- package/dashboard/src/app.js +0 -91
- package/dashboard/src/middleware/current-phase.js +0 -24
- package/dashboard/src/middleware/errorHandler.js +0 -52
- package/dashboard/src/middleware/notFoundHandler.js +0 -9
- package/dashboard/src/repositories/planning.repository.js +0 -130
- package/dashboard/src/routes/events.routes.js +0 -45
- package/dashboard/src/routes/index.routes.js +0 -35
- package/dashboard/src/routes/pages.routes.js +0 -426
- package/dashboard/src/server.js +0 -42
- package/dashboard/src/services/analytics.service.js +0 -141
- package/dashboard/src/services/dashboard.service.js +0 -309
- package/dashboard/src/services/milestone.service.js +0 -222
- package/dashboard/src/services/notes.service.js +0 -50
- package/dashboard/src/services/phase.service.js +0 -232
- package/dashboard/src/services/project.service.js +0 -57
- package/dashboard/src/services/roadmap.service.js +0 -258
- package/dashboard/src/services/sse.service.js +0 -58
- package/dashboard/src/services/todo.service.js +0 -272
- package/dashboard/src/services/watcher.service.js +0 -48
- package/dashboard/src/utils/cache.js +0 -55
- package/dashboard/src/views/analytics.ejs +0 -5
- package/dashboard/src/views/coming-soon.ejs +0 -11
- package/dashboard/src/views/dependencies.ejs +0 -5
- package/dashboard/src/views/error.ejs +0 -20
- package/dashboard/src/views/index.ejs +0 -5
- package/dashboard/src/views/milestone-detail.ejs +0 -5
- package/dashboard/src/views/milestones.ejs +0 -5
- package/dashboard/src/views/notes.ejs +0 -5
- package/dashboard/src/views/partials/analytics-content.ejs +0 -90
- package/dashboard/src/views/partials/breadcrumbs.ejs +0 -14
- package/dashboard/src/views/partials/dashboard-content.ejs +0 -84
- package/dashboard/src/views/partials/dependencies-content.ejs +0 -48
- package/dashboard/src/views/partials/empty-state.ejs +0 -7
- package/dashboard/src/views/partials/footer.ejs +0 -3
- package/dashboard/src/views/partials/head.ejs +0 -30
- package/dashboard/src/views/partials/header.ejs +0 -21
- package/dashboard/src/views/partials/layout-bottom.ejs +0 -43
- package/dashboard/src/views/partials/layout-top.ejs +0 -16
- package/dashboard/src/views/partials/milestone-detail-content.ejs +0 -20
- package/dashboard/src/views/partials/milestones-content.ejs +0 -88
- package/dashboard/src/views/partials/notes-content.ejs +0 -23
- package/dashboard/src/views/partials/phase-content.ejs +0 -193
- package/dashboard/src/views/partials/phase-doc-content.ejs +0 -38
- package/dashboard/src/views/partials/phases-content.ejs +0 -124
- package/dashboard/src/views/partials/roadmap-content.ejs +0 -180
- package/dashboard/src/views/partials/sidebar.ejs +0 -99
- package/dashboard/src/views/partials/todo-create-content.ejs +0 -54
- package/dashboard/src/views/partials/todo-detail-content.ejs +0 -42
- package/dashboard/src/views/partials/todos-content.ejs +0 -97
- package/dashboard/src/views/phase-detail.ejs +0 -5
- package/dashboard/src/views/phase-doc.ejs +0 -5
- package/dashboard/src/views/phases.ejs +0 -5
- package/dashboard/src/views/roadmap.ejs +0 -5
- package/dashboard/src/views/todo-create.ejs +0 -5
- package/dashboard/src/views/todo-detail.ejs +0 -5
- package/dashboard/src/views/todos.ejs +0 -5
- package/plugins/copilot-pbr/CHANGELOG.md +0 -19
- package/plugins/copilot-pbr/README.md +0 -139
- package/plugins/copilot-pbr/agents/audit.agent.md +0 -113
- package/plugins/copilot-pbr/agents/codebase-mapper.agent.md +0 -151
- package/plugins/copilot-pbr/agents/debugger.agent.md +0 -184
- package/plugins/copilot-pbr/agents/executor.agent.md +0 -269
- package/plugins/copilot-pbr/agents/general.agent.md +0 -89
- package/plugins/copilot-pbr/agents/integration-checker.agent.md +0 -121
- package/plugins/copilot-pbr/agents/plan-checker.agent.md +0 -208
- package/plugins/copilot-pbr/agents/planner.agent.md +0 -240
- package/plugins/copilot-pbr/agents/researcher.agent.md +0 -188
- package/plugins/copilot-pbr/agents/synthesizer.agent.md +0 -126
- package/plugins/copilot-pbr/agents/verifier.agent.md +0 -228
- package/plugins/copilot-pbr/hooks/hooks.json +0 -258
- package/plugins/copilot-pbr/plugin.json +0 -30
- package/plugins/copilot-pbr/references/agent-anti-patterns.md +0 -25
- package/plugins/copilot-pbr/references/agent-contracts.md +0 -297
- package/plugins/copilot-pbr/references/agent-interactions.md +0 -135
- package/plugins/copilot-pbr/references/agent-teams.md +0 -55
- package/plugins/copilot-pbr/references/checkpoints.md +0 -158
- package/plugins/copilot-pbr/references/common-bug-patterns.md +0 -14
- package/plugins/copilot-pbr/references/config-reference.md +0 -442
- package/plugins/copilot-pbr/references/continuation-format.md +0 -213
- package/plugins/copilot-pbr/references/deviation-rules.md +0 -113
- package/plugins/copilot-pbr/references/git-integration.md +0 -227
- package/plugins/copilot-pbr/references/integration-patterns.md +0 -118
- package/plugins/copilot-pbr/references/model-profiles.md +0 -100
- package/plugins/copilot-pbr/references/model-selection.md +0 -32
- package/plugins/copilot-pbr/references/pbr-rules.md +0 -195
- package/plugins/copilot-pbr/references/pbr-tools-cli.md +0 -285
- package/plugins/copilot-pbr/references/plan-authoring.md +0 -182
- package/plugins/copilot-pbr/references/plan-format.md +0 -288
- package/plugins/copilot-pbr/references/planning-config.md +0 -214
- package/plugins/copilot-pbr/references/questioning.md +0 -215
- package/plugins/copilot-pbr/references/reading-verification.md +0 -128
- package/plugins/copilot-pbr/references/stub-patterns.md +0 -161
- package/plugins/copilot-pbr/references/subagent-coordination.md +0 -120
- package/plugins/copilot-pbr/references/ui-formatting.md +0 -444
- package/plugins/copilot-pbr/references/verification-patterns.md +0 -199
- package/plugins/copilot-pbr/references/wave-execution.md +0 -96
- package/plugins/copilot-pbr/rules/pbr-workflow.mdc +0 -48
- package/plugins/copilot-pbr/setup.ps1 +0 -93
- package/plugins/copilot-pbr/setup.sh +0 -93
- package/plugins/copilot-pbr/skills/audit/SKILL.md +0 -330
- package/plugins/copilot-pbr/skills/begin/SKILL.md +0 -589
- package/plugins/copilot-pbr/skills/begin/templates/PROJECT.md.tmpl +0 -34
- package/plugins/copilot-pbr/skills/begin/templates/REQUIREMENTS.md.tmpl +0 -19
- package/plugins/copilot-pbr/skills/begin/templates/STATE.md.tmpl +0 -50
- package/plugins/copilot-pbr/skills/begin/templates/config.json.tmpl +0 -64
- package/plugins/copilot-pbr/skills/begin/templates/researcher-prompt.md.tmpl +0 -20
- package/plugins/copilot-pbr/skills/begin/templates/roadmap-prompt.md.tmpl +0 -31
- package/plugins/copilot-pbr/skills/begin/templates/synthesis-prompt.md.tmpl +0 -17
- package/plugins/copilot-pbr/skills/build/SKILL.md +0 -959
- package/plugins/copilot-pbr/skills/config/SKILL.md +0 -250
- package/plugins/copilot-pbr/skills/continue/SKILL.md +0 -159
- package/plugins/copilot-pbr/skills/dashboard/SKILL.md +0 -43
- package/plugins/copilot-pbr/skills/debug/SKILL.md +0 -508
- package/plugins/copilot-pbr/skills/debug/templates/continuation-prompt.md.tmpl +0 -17
- package/plugins/copilot-pbr/skills/debug/templates/initial-investigation-prompt.md.tmpl +0 -28
- package/plugins/copilot-pbr/skills/discuss/SKILL.md +0 -353
- package/plugins/copilot-pbr/skills/discuss/templates/CONTEXT.md.tmpl +0 -62
- package/plugins/copilot-pbr/skills/discuss/templates/decision-categories.md +0 -10
- package/plugins/copilot-pbr/skills/do/SKILL.md +0 -66
- package/plugins/copilot-pbr/skills/explore/SKILL.md +0 -373
- package/plugins/copilot-pbr/skills/health/SKILL.md +0 -283
- package/plugins/copilot-pbr/skills/health/templates/check-pattern.md.tmpl +0 -31
- package/plugins/copilot-pbr/skills/health/templates/output-format.md.tmpl +0 -64
- package/plugins/copilot-pbr/skills/help/SKILL.md +0 -193
- package/plugins/copilot-pbr/skills/import/SKILL.md +0 -502
- package/plugins/copilot-pbr/skills/milestone/SKILL.md +0 -806
- package/plugins/copilot-pbr/skills/milestone/templates/audit-report.md.tmpl +0 -49
- package/plugins/copilot-pbr/skills/milestone/templates/stats-file.md.tmpl +0 -31
- package/plugins/copilot-pbr/skills/note/SKILL.md +0 -213
- package/plugins/copilot-pbr/skills/pause/SKILL.md +0 -247
- package/plugins/copilot-pbr/skills/pause/templates/continue-here.md.tmpl +0 -72
- package/plugins/copilot-pbr/skills/plan/SKILL.md +0 -662
- package/plugins/copilot-pbr/skills/plan/templates/checker-prompt.md.tmpl +0 -22
- package/plugins/copilot-pbr/skills/plan/templates/gap-closure-prompt.md.tmpl +0 -33
- package/plugins/copilot-pbr/skills/plan/templates/planner-prompt.md.tmpl +0 -39
- package/plugins/copilot-pbr/skills/plan/templates/researcher-prompt.md.tmpl +0 -20
- package/plugins/copilot-pbr/skills/plan/templates/revision-prompt.md.tmpl +0 -24
- package/plugins/copilot-pbr/skills/quick/SKILL.md +0 -376
- package/plugins/copilot-pbr/skills/resume/SKILL.md +0 -399
- package/plugins/copilot-pbr/skills/review/SKILL.md +0 -653
- package/plugins/copilot-pbr/skills/review/templates/debugger-prompt.md.tmpl +0 -61
- package/plugins/copilot-pbr/skills/review/templates/gap-planner-prompt.md.tmpl +0 -41
- package/plugins/copilot-pbr/skills/review/templates/verifier-prompt.md.tmpl +0 -116
- package/plugins/copilot-pbr/skills/scan/SKILL.md +0 -299
- package/plugins/copilot-pbr/skills/scan/templates/mapper-prompt.md.tmpl +0 -202
- package/plugins/copilot-pbr/skills/setup/SKILL.md +0 -296
- package/plugins/copilot-pbr/skills/shared/commit-planning-docs.md +0 -36
- package/plugins/copilot-pbr/skills/shared/config-loading.md +0 -103
- package/plugins/copilot-pbr/skills/shared/context-budget.md +0 -41
- package/plugins/copilot-pbr/skills/shared/context-loader-task.md +0 -87
- package/plugins/copilot-pbr/skills/shared/digest-select.md +0 -80
- package/plugins/copilot-pbr/skills/shared/domain-probes.md +0 -126
- package/plugins/copilot-pbr/skills/shared/error-recovery-strategies.md +0 -51
- package/plugins/copilot-pbr/skills/shared/error-reporting.md +0 -81
- package/plugins/copilot-pbr/skills/shared/gate-prompts.md +0 -389
- package/plugins/copilot-pbr/skills/shared/phase-argument-parsing.md +0 -46
- package/plugins/copilot-pbr/skills/shared/progress-display.md +0 -53
- package/plugins/copilot-pbr/skills/shared/revision-loop.md +0 -82
- package/plugins/copilot-pbr/skills/shared/state-loading.md +0 -63
- package/plugins/copilot-pbr/skills/shared/state-update.md +0 -170
- package/plugins/copilot-pbr/skills/shared/universal-anti-patterns.md +0 -38
- package/plugins/copilot-pbr/skills/status/SKILL.md +0 -362
- package/plugins/copilot-pbr/skills/statusline/SKILL.md +0 -149
- package/plugins/copilot-pbr/skills/todo/SKILL.md +0 -279
- package/plugins/copilot-pbr/templates/CONTEXT.md.tmpl +0 -53
- package/plugins/copilot-pbr/templates/INTEGRATION-REPORT.md.tmpl +0 -152
- package/plugins/copilot-pbr/templates/RESEARCH-SUMMARY.md.tmpl +0 -98
- package/plugins/copilot-pbr/templates/ROADMAP.md.tmpl +0 -48
- package/plugins/copilot-pbr/templates/SUMMARY.md.tmpl +0 -82
- package/plugins/copilot-pbr/templates/VERIFICATION-DETAIL.md.tmpl +0 -117
- package/plugins/copilot-pbr/templates/codebase/ARCHITECTURE.md.tmpl +0 -98
- package/plugins/copilot-pbr/templates/codebase/CONCERNS.md.tmpl +0 -93
- package/plugins/copilot-pbr/templates/codebase/CONVENTIONS.md.tmpl +0 -104
- package/plugins/copilot-pbr/templates/codebase/INTEGRATIONS.md.tmpl +0 -78
- package/plugins/copilot-pbr/templates/codebase/STACK.md.tmpl +0 -78
- package/plugins/copilot-pbr/templates/codebase/STRUCTURE.md.tmpl +0 -80
- package/plugins/copilot-pbr/templates/codebase/TESTING.md.tmpl +0 -107
- package/plugins/copilot-pbr/templates/continue-here.md.tmpl +0 -74
- package/plugins/copilot-pbr/templates/prompt-partials/phase-project-context.md.tmpl +0 -38
- package/plugins/copilot-pbr/templates/research/ARCHITECTURE.md.tmpl +0 -124
- package/plugins/copilot-pbr/templates/research/STACK.md.tmpl +0 -71
- package/plugins/copilot-pbr/templates/research/SUMMARY.md.tmpl +0 -112
- package/plugins/copilot-pbr/templates/research-outputs/phase-research.md.tmpl +0 -81
- package/plugins/copilot-pbr/templates/research-outputs/project-research.md.tmpl +0 -99
- package/plugins/copilot-pbr/templates/research-outputs/synthesis.md.tmpl +0 -36
- package/plugins/cursor-pbr/.cursor-plugin/plugin.json +0 -32
- package/plugins/cursor-pbr/CHANGELOG.md +0 -15
- package/plugins/cursor-pbr/README.md +0 -123
- package/plugins/cursor-pbr/agents/audit.md +0 -178
- package/plugins/cursor-pbr/agents/codebase-mapper.md +0 -150
- package/plugins/cursor-pbr/agents/debugger.md +0 -183
- package/plugins/cursor-pbr/agents/executor.md +0 -268
- package/plugins/cursor-pbr/agents/general.md +0 -88
- package/plugins/cursor-pbr/agents/integration-checker.md +0 -120
- package/plugins/cursor-pbr/agents/plan-checker.md +0 -207
- package/plugins/cursor-pbr/agents/planner.md +0 -239
- package/plugins/cursor-pbr/agents/researcher.md +0 -187
- package/plugins/cursor-pbr/agents/synthesizer.md +0 -125
- package/plugins/cursor-pbr/agents/verifier.md +0 -227
- package/plugins/cursor-pbr/assets/.gitkeep +0 -0
- package/plugins/cursor-pbr/assets/logo.svg +0 -21
- package/plugins/cursor-pbr/hooks/hooks.json +0 -224
- package/plugins/cursor-pbr/references/agent-anti-patterns.md +0 -25
- package/plugins/cursor-pbr/references/agent-contracts.md +0 -297
- package/plugins/cursor-pbr/references/agent-interactions.md +0 -135
- package/plugins/cursor-pbr/references/agent-teams.md +0 -55
- package/plugins/cursor-pbr/references/checkpoints.md +0 -158
- package/plugins/cursor-pbr/references/common-bug-patterns.md +0 -14
- package/plugins/cursor-pbr/references/config-reference.md +0 -442
- package/plugins/cursor-pbr/references/continuation-format.md +0 -213
- package/plugins/cursor-pbr/references/deviation-rules.md +0 -113
- package/plugins/cursor-pbr/references/git-integration.md +0 -227
- package/plugins/cursor-pbr/references/integration-patterns.md +0 -118
- package/plugins/cursor-pbr/references/model-profiles.md +0 -100
- package/plugins/cursor-pbr/references/model-selection.md +0 -32
- package/plugins/cursor-pbr/references/pbr-rules.md +0 -195
- package/plugins/cursor-pbr/references/pbr-tools-cli.md +0 -285
- package/plugins/cursor-pbr/references/plan-authoring.md +0 -182
- package/plugins/cursor-pbr/references/plan-format.md +0 -288
- package/plugins/cursor-pbr/references/planning-config.md +0 -214
- package/plugins/cursor-pbr/references/questioning.md +0 -215
- package/plugins/cursor-pbr/references/reading-verification.md +0 -128
- package/plugins/cursor-pbr/references/stub-patterns.md +0 -161
- package/plugins/cursor-pbr/references/subagent-coordination.md +0 -120
- package/plugins/cursor-pbr/references/ui-formatting.md +0 -444
- package/plugins/cursor-pbr/references/verification-patterns.md +0 -199
- package/plugins/cursor-pbr/references/wave-execution.md +0 -96
- package/plugins/cursor-pbr/rules/pbr-workflow.mdc +0 -48
- package/plugins/cursor-pbr/setup.ps1 +0 -78
- package/plugins/cursor-pbr/setup.sh +0 -83
- package/plugins/cursor-pbr/skills/audit/SKILL.md +0 -331
- package/plugins/cursor-pbr/skills/begin/SKILL.md +0 -589
- package/plugins/cursor-pbr/skills/begin/templates/PROJECT.md.tmpl +0 -34
- package/plugins/cursor-pbr/skills/begin/templates/REQUIREMENTS.md.tmpl +0 -19
- package/plugins/cursor-pbr/skills/begin/templates/STATE.md.tmpl +0 -50
- package/plugins/cursor-pbr/skills/begin/templates/config.json.tmpl +0 -64
- package/plugins/cursor-pbr/skills/begin/templates/researcher-prompt.md.tmpl +0 -20
- package/plugins/cursor-pbr/skills/begin/templates/roadmap-prompt.md.tmpl +0 -31
- package/plugins/cursor-pbr/skills/begin/templates/synthesis-prompt.md.tmpl +0 -17
- package/plugins/cursor-pbr/skills/build/SKILL.md +0 -960
- package/plugins/cursor-pbr/skills/config/SKILL.md +0 -252
- package/plugins/cursor-pbr/skills/continue/SKILL.md +0 -159
- package/plugins/cursor-pbr/skills/dashboard/SKILL.md +0 -44
- package/plugins/cursor-pbr/skills/debug/SKILL.md +0 -512
- package/plugins/cursor-pbr/skills/debug/templates/continuation-prompt.md.tmpl +0 -17
- package/plugins/cursor-pbr/skills/debug/templates/initial-investigation-prompt.md.tmpl +0 -28
- package/plugins/cursor-pbr/skills/discuss/SKILL.md +0 -354
- package/plugins/cursor-pbr/skills/discuss/templates/CONTEXT.md.tmpl +0 -62
- package/plugins/cursor-pbr/skills/discuss/templates/decision-categories.md +0 -10
- package/plugins/cursor-pbr/skills/do/SKILL.md +0 -67
- package/plugins/cursor-pbr/skills/explore/SKILL.md +0 -376
- package/plugins/cursor-pbr/skills/health/SKILL.md +0 -283
- package/plugins/cursor-pbr/skills/health/templates/check-pattern.md.tmpl +0 -31
- package/plugins/cursor-pbr/skills/health/templates/output-format.md.tmpl +0 -64
- package/plugins/cursor-pbr/skills/help/SKILL.md +0 -193
- package/plugins/cursor-pbr/skills/import/SKILL.md +0 -505
- package/plugins/cursor-pbr/skills/milestone/SKILL.md +0 -807
- package/plugins/cursor-pbr/skills/milestone/templates/audit-report.md.tmpl +0 -49
- package/plugins/cursor-pbr/skills/milestone/templates/stats-file.md.tmpl +0 -31
- package/plugins/cursor-pbr/skills/note/SKILL.md +0 -214
- package/plugins/cursor-pbr/skills/pause/SKILL.md +0 -248
- package/plugins/cursor-pbr/skills/pause/templates/continue-here.md.tmpl +0 -72
- package/plugins/cursor-pbr/skills/plan/SKILL.md +0 -663
- package/plugins/cursor-pbr/skills/plan/templates/checker-prompt.md.tmpl +0 -22
- package/plugins/cursor-pbr/skills/plan/templates/gap-closure-prompt.md.tmpl +0 -33
- package/plugins/cursor-pbr/skills/plan/templates/planner-prompt.md.tmpl +0 -39
- package/plugins/cursor-pbr/skills/plan/templates/researcher-prompt.md.tmpl +0 -20
- package/plugins/cursor-pbr/skills/plan/templates/revision-prompt.md.tmpl +0 -24
- package/plugins/cursor-pbr/skills/quick/SKILL.md +0 -376
- package/plugins/cursor-pbr/skills/resume/SKILL.md +0 -399
- package/plugins/cursor-pbr/skills/review/SKILL.md +0 -654
- package/plugins/cursor-pbr/skills/review/templates/debugger-prompt.md.tmpl +0 -61
- package/plugins/cursor-pbr/skills/review/templates/gap-planner-prompt.md.tmpl +0 -41
- package/plugins/cursor-pbr/skills/review/templates/verifier-prompt.md.tmpl +0 -116
- package/plugins/cursor-pbr/skills/scan/SKILL.md +0 -300
- package/plugins/cursor-pbr/skills/scan/templates/mapper-prompt.md.tmpl +0 -202
- package/plugins/cursor-pbr/skills/setup/SKILL.md +0 -296
- package/plugins/cursor-pbr/skills/shared/commit-planning-docs.md +0 -36
- package/plugins/cursor-pbr/skills/shared/config-loading.md +0 -103
- package/plugins/cursor-pbr/skills/shared/context-budget.md +0 -41
- package/plugins/cursor-pbr/skills/shared/context-loader-task.md +0 -87
- package/plugins/cursor-pbr/skills/shared/digest-select.md +0 -80
- package/plugins/cursor-pbr/skills/shared/domain-probes.md +0 -126
- package/plugins/cursor-pbr/skills/shared/error-recovery-strategies.md +0 -51
- package/plugins/cursor-pbr/skills/shared/error-reporting.md +0 -81
- package/plugins/cursor-pbr/skills/shared/gate-prompts.md +0 -389
- package/plugins/cursor-pbr/skills/shared/phase-argument-parsing.md +0 -46
- package/plugins/cursor-pbr/skills/shared/progress-display.md +0 -53
- package/plugins/cursor-pbr/skills/shared/revision-loop.md +0 -82
- package/plugins/cursor-pbr/skills/shared/state-loading.md +0 -63
- package/plugins/cursor-pbr/skills/shared/state-update.md +0 -170
- package/plugins/cursor-pbr/skills/shared/universal-anti-patterns.md +0 -38
- package/plugins/cursor-pbr/skills/status/SKILL.md +0 -362
- package/plugins/cursor-pbr/skills/statusline/SKILL.md +0 -150
- package/plugins/cursor-pbr/skills/todo/SKILL.md +0 -280
- package/plugins/cursor-pbr/templates/CONTEXT.md.tmpl +0 -53
- package/plugins/cursor-pbr/templates/INTEGRATION-REPORT.md.tmpl +0 -152
- package/plugins/cursor-pbr/templates/RESEARCH-SUMMARY.md.tmpl +0 -98
- package/plugins/cursor-pbr/templates/ROADMAP.md.tmpl +0 -48
- package/plugins/cursor-pbr/templates/SUMMARY.md.tmpl +0 -82
- package/plugins/cursor-pbr/templates/VERIFICATION-DETAIL.md.tmpl +0 -117
- package/plugins/cursor-pbr/templates/codebase/ARCHITECTURE.md.tmpl +0 -98
- package/plugins/cursor-pbr/templates/codebase/CONCERNS.md.tmpl +0 -93
- package/plugins/cursor-pbr/templates/codebase/CONVENTIONS.md.tmpl +0 -104
- package/plugins/cursor-pbr/templates/codebase/INTEGRATIONS.md.tmpl +0 -78
- package/plugins/cursor-pbr/templates/codebase/STACK.md.tmpl +0 -78
- package/plugins/cursor-pbr/templates/codebase/STRUCTURE.md.tmpl +0 -80
- package/plugins/cursor-pbr/templates/codebase/TESTING.md.tmpl +0 -107
- package/plugins/cursor-pbr/templates/continue-here.md.tmpl +0 -74
- package/plugins/cursor-pbr/templates/prompt-partials/phase-project-context.md.tmpl +0 -38
- package/plugins/cursor-pbr/templates/research/ARCHITECTURE.md.tmpl +0 -124
- package/plugins/cursor-pbr/templates/research/STACK.md.tmpl +0 -71
- package/plugins/cursor-pbr/templates/research/SUMMARY.md.tmpl +0 -112
- package/plugins/cursor-pbr/templates/research-outputs/phase-research.md.tmpl +0 -81
- package/plugins/cursor-pbr/templates/research-outputs/project-research.md.tmpl +0 -99
- package/plugins/cursor-pbr/templates/research-outputs/synthesis.md.tmpl +0 -36
- package/plugins/pbr/references/agent-interactions.md +0 -134
- package/plugins/pbr/references/pbr-rules.md +0 -194
- package/plugins/pbr/references/pbr-tools-cli.md +0 -285
- package/plugins/pbr/references/planning-config.md +0 -213
- package/plugins/pbr/references/subagent-coordination.md +0 -119
- package/plugins/pbr/references/ui-formatting.md +0 -444
- package/plugins/pbr/scripts/validate-plugin-structure.js +0 -183
- package/plugins/pbr/skills/milestone/templates/audit-report.md.tmpl +0 -48
- package/plugins/pbr/skills/shared/error-recovery-strategies.md +0 -51
- package/plugins/pbr/skills/shared/progress-display.md +0 -53
- package/plugins/pbr/skills/shared/state-loading.md +0 -62
- package/plugins/pbr/templates/RESEARCH-SUMMARY.md.tmpl +0 -97
- package/plugins/pbr/templates/research/ARCHITECTURE.md.tmpl +0 -124
- package/plugins/pbr/templates/research/STACK.md.tmpl +0 -71
- package/plugins/pbr/templates/research/SUMMARY.md.tmpl +0 -112
- package/plugins/pbr/templates/research-outputs/phase-research.md.tmpl +0 -81
- package/plugins/pbr/templates/research-outputs/project-research.md.tmpl +0 -99
- package/plugins/pbr/templates/research-outputs/synthesis.md.tmpl +0 -36
- /package/plugins/pbr/references/{agent-anti-patterns.md → archive/agent-anti-patterns.md} +0 -0
|
@@ -0,0 +1,1585 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/core.cjs — Foundation utilities for Plan-Build-Run tools.
|
|
3
|
+
*
|
|
4
|
+
* Pure utility functions with no dependencies on other lib modules.
|
|
5
|
+
* Provides: output/error formatting, YAML frontmatter parsing, status transitions,
|
|
6
|
+
* file operations (atomicWrite, lockedFileUpdate, findFiles, tailLines),
|
|
7
|
+
* session management, phase claiming, path utilities, and shared constants.
|
|
8
|
+
*
|
|
9
|
+
* Hybrid module merging PBR reference features with GSD-unique utilities.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const os = require('os');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const { execSync } = require('child_process');
|
|
16
|
+
const { logHook } = require('../hook-logger');
|
|
17
|
+
const { normalizeMsysPath } = require('./msys-path');
|
|
18
|
+
|
|
19
|
+
// ─── Module-level planningDir with MSYS path bridging ─────────────────────────
|
|
20
|
+
|
|
21
|
+
let cwd = process.env.PBR_PROJECT_ROOT || process.cwd();
|
|
22
|
+
cwd = normalizeMsysPath(cwd);
|
|
23
|
+
|
|
24
|
+
let planningDir = path.join(cwd, '.planning');
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Override the working directory for subagent use.
|
|
28
|
+
* Updates both cwd and planningDir.
|
|
29
|
+
*
|
|
30
|
+
* @param {string} newCwd - New working directory path
|
|
31
|
+
*/
|
|
32
|
+
function setCwd(newCwd) {
|
|
33
|
+
cwd = newCwd;
|
|
34
|
+
cwd = normalizeMsysPath(cwd);
|
|
35
|
+
planningDir = path.join(cwd, '.planning');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ─── Canonical agent list ─────────────────────────────────────────────────────
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Canonical list of known PBR agent types.
|
|
42
|
+
* Used by validate-task and check-subagent-output to avoid drift.
|
|
43
|
+
*/
|
|
44
|
+
const KNOWN_AGENTS = [
|
|
45
|
+
'executor',
|
|
46
|
+
'planner',
|
|
47
|
+
'verifier',
|
|
48
|
+
'researcher',
|
|
49
|
+
'synthesizer',
|
|
50
|
+
'plan-checker',
|
|
51
|
+
'integration-checker',
|
|
52
|
+
'debugger',
|
|
53
|
+
'codebase-mapper',
|
|
54
|
+
'audit',
|
|
55
|
+
'general',
|
|
56
|
+
'dev-sync',
|
|
57
|
+
'roadmapper',
|
|
58
|
+
'nyquist-auditor',
|
|
59
|
+
'intel-updater',
|
|
60
|
+
'ui-checker',
|
|
61
|
+
'ui-researcher'
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
// ─── Phase status transition state machine ────────────────────────────────────
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Valid phase status transitions. Each key is a current status, and its value
|
|
68
|
+
* is an array of statuses that are legal to transition to. This is advisory —
|
|
69
|
+
* invalid transitions produce a stderr warning but are not blocked.
|
|
70
|
+
*
|
|
71
|
+
* State machine:
|
|
72
|
+
* pending -> planned, skipped
|
|
73
|
+
* planned -> building
|
|
74
|
+
* building -> built, partial, needs_fixes
|
|
75
|
+
* built -> verified, needs_fixes
|
|
76
|
+
* partial -> building, needs_fixes
|
|
77
|
+
* verified -> building (re-execution)
|
|
78
|
+
* needs_fixes -> planned, building
|
|
79
|
+
* skipped -> pending (unskip)
|
|
80
|
+
*/
|
|
81
|
+
const VALID_STATUS_TRANSITIONS = {
|
|
82
|
+
not_started: ['discussed', 'ready_to_plan', 'planned', 'skipped'],
|
|
83
|
+
discussed: ['ready_to_plan', 'planning'],
|
|
84
|
+
ready_to_plan: ['planning', 'planned'],
|
|
85
|
+
planning: ['planned'],
|
|
86
|
+
planned: ['ready_to_execute', 'building'],
|
|
87
|
+
ready_to_execute: ['building'],
|
|
88
|
+
building: ['built', 'partial', 'needs_fixes'],
|
|
89
|
+
built: ['verified', 'needs_fixes'],
|
|
90
|
+
partial: ['building', 'needs_fixes'],
|
|
91
|
+
verified: ['complete', 'building'],
|
|
92
|
+
needs_fixes: ['planned', 'building', 'ready_to_plan'],
|
|
93
|
+
complete: [],
|
|
94
|
+
skipped: ['not_started', 'pending'],
|
|
95
|
+
// Legacy aliases (backward compat)
|
|
96
|
+
pending: ['planned', 'discussed', 'skipped', 'not_started']
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Human-readable labels for plan/phase statuses.
|
|
101
|
+
*/
|
|
102
|
+
const STATUS_LABELS = {
|
|
103
|
+
not_started: 'Not Started',
|
|
104
|
+
discussed: 'Discussed',
|
|
105
|
+
ready_to_plan: 'Ready to Plan',
|
|
106
|
+
planning: 'Planning',
|
|
107
|
+
planned: 'Planned',
|
|
108
|
+
ready_to_execute: 'Ready to Execute',
|
|
109
|
+
building: 'Building',
|
|
110
|
+
built: 'Built',
|
|
111
|
+
partial: 'Partial',
|
|
112
|
+
verified: 'Verified',
|
|
113
|
+
needs_fixes: 'Needs Fixes',
|
|
114
|
+
complete: 'Complete',
|
|
115
|
+
skipped: 'Skipped',
|
|
116
|
+
// Legacy aliases
|
|
117
|
+
pending: 'Not Started',
|
|
118
|
+
reviewed: 'Verified'
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Check whether a phase status transition is valid according to the state machine.
|
|
123
|
+
* Returns { valid, warning? } — never blocks, only advises.
|
|
124
|
+
*
|
|
125
|
+
* @param {string} oldStatus - Current phase status
|
|
126
|
+
* @param {string} newStatus - Desired phase status
|
|
127
|
+
* @returns {{ valid: boolean, warning?: string }}
|
|
128
|
+
*/
|
|
129
|
+
function validateStatusTransition(oldStatus, newStatus) {
|
|
130
|
+
const from = (oldStatus || '').trim().toLowerCase();
|
|
131
|
+
const to = (newStatus || '').trim().toLowerCase();
|
|
132
|
+
|
|
133
|
+
if (from === to) return { valid: true };
|
|
134
|
+
|
|
135
|
+
if (!VALID_STATUS_TRANSITIONS[from]) return { valid: true };
|
|
136
|
+
|
|
137
|
+
const allowed = VALID_STATUS_TRANSITIONS[from];
|
|
138
|
+
if (allowed.includes(to)) return { valid: true };
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
valid: false,
|
|
142
|
+
warning: `Suspicious status transition: "${from}" -> "${to}". Expected one of: [${allowed.join(', ')}]. Proceeding anyway (advisory).`
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ─── Model Profile Table ─────────────────────────────────────────────────────
|
|
147
|
+
|
|
148
|
+
const MODEL_PROFILES = {
|
|
149
|
+
'pbr-planner': { quality: 'opus', balanced: 'opus', budget: 'sonnet' },
|
|
150
|
+
'pbr-roadmapper': { quality: 'opus', balanced: 'sonnet', budget: 'sonnet' },
|
|
151
|
+
'pbr-executor': { quality: 'opus', balanced: 'sonnet', budget: 'sonnet' },
|
|
152
|
+
'pbr-researcher': { quality: 'opus', balanced: 'sonnet', budget: 'haiku' },
|
|
153
|
+
'pbr-synthesizer': { quality: 'sonnet', balanced: 'sonnet', budget: 'haiku' },
|
|
154
|
+
'pbr-debugger': { quality: 'opus', balanced: 'sonnet', budget: 'sonnet' },
|
|
155
|
+
'pbr-codebase-mapper': { quality: 'sonnet', balanced: 'haiku', budget: 'haiku' },
|
|
156
|
+
'pbr-verifier': { quality: 'sonnet', balanced: 'sonnet', budget: 'haiku' },
|
|
157
|
+
'pbr-plan-checker': { quality: 'sonnet', balanced: 'sonnet', budget: 'haiku' },
|
|
158
|
+
'pbr-integration-checker': { quality: 'sonnet', balanced: 'sonnet', budget: 'haiku' },
|
|
159
|
+
'pbr-nyquist-auditor': { quality: 'sonnet', balanced: 'sonnet', budget: 'haiku' },
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
// ─── Path helpers ─────────────────────────────────────────────────────────────
|
|
163
|
+
|
|
164
|
+
/** Normalize a path to always use forward slashes (cross-platform). */
|
|
165
|
+
function toPosixPath(p) {
|
|
166
|
+
return p.split(path.sep).join('/');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// ─── Output helpers ───────────────────────────────────────────────────────────
|
|
170
|
+
|
|
171
|
+
function output(data, raw, rawValue) {
|
|
172
|
+
if (raw && rawValue !== undefined) {
|
|
173
|
+
process.stdout.write(String(rawValue));
|
|
174
|
+
} else {
|
|
175
|
+
const json = JSON.stringify(data, null, 2);
|
|
176
|
+
if (json.length > 8192) {
|
|
177
|
+
const tmpPath = path.join(os.tmpdir(), `pbr-${Date.now()}.json`);
|
|
178
|
+
fs.writeFileSync(tmpPath, json, 'utf8');
|
|
179
|
+
process.stdout.write('@file:' + tmpPath + '\n');
|
|
180
|
+
} else {
|
|
181
|
+
process.stdout.write(json + '\n');
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
process.exit(0);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function error(msg) {
|
|
188
|
+
process.stderr.write('Error: ' + msg + '\n');
|
|
189
|
+
process.exit(1);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// ─── File & path utilities ────────────────────────────────────────────────────
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Read a file safely, returning null on any error.
|
|
196
|
+
*
|
|
197
|
+
* @param {string} filePath - Absolute path to the file
|
|
198
|
+
* @returns {string|null} File contents or null
|
|
199
|
+
*/
|
|
200
|
+
function safeReadFile(filePath) {
|
|
201
|
+
try {
|
|
202
|
+
return fs.readFileSync(filePath, 'utf8');
|
|
203
|
+
} catch {
|
|
204
|
+
// intentionally silent: file may not exist
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Ensure a directory exists, creating it recursively if needed.
|
|
211
|
+
*
|
|
212
|
+
* @param {string} dirPath - Directory path to ensure
|
|
213
|
+
*/
|
|
214
|
+
function ensureDir(dirPath) {
|
|
215
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Find files in a directory matching a regex pattern.
|
|
220
|
+
*
|
|
221
|
+
* @param {string} dir - Directory to search
|
|
222
|
+
* @param {RegExp} pattern - Pattern to match filenames against
|
|
223
|
+
* @returns {string[]} Sorted array of matching filenames
|
|
224
|
+
*/
|
|
225
|
+
function findFiles(dir, pattern) {
|
|
226
|
+
try {
|
|
227
|
+
return fs.readdirSync(dir).filter(f => pattern.test(f)).sort();
|
|
228
|
+
} catch (_) {
|
|
229
|
+
// intentionally silent: directory may not exist
|
|
230
|
+
return [];
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Read the last N lines from a file.
|
|
236
|
+
*
|
|
237
|
+
* @param {string} filePath - Absolute path to the file
|
|
238
|
+
* @param {number} n - Number of trailing lines to return
|
|
239
|
+
* @returns {string[]} Array of line strings
|
|
240
|
+
*/
|
|
241
|
+
function tailLines(filePath, n) {
|
|
242
|
+
try {
|
|
243
|
+
if (!fs.existsSync(filePath)) return [];
|
|
244
|
+
const content = fs.readFileSync(filePath, 'utf8').trim();
|
|
245
|
+
if (!content) return [];
|
|
246
|
+
const lines = content.replace(/\r\n/g, '\n').split('\n');
|
|
247
|
+
if (lines.length <= n) return lines;
|
|
248
|
+
return lines.slice(lines.length - n);
|
|
249
|
+
} catch (_e) {
|
|
250
|
+
// intentionally silent: file may not exist
|
|
251
|
+
return [];
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// ─── Git utilities ────────────────────────────────────────────────────────────
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Execute a git command and return the result.
|
|
259
|
+
*
|
|
260
|
+
* @param {string} gitCwd - Working directory for git
|
|
261
|
+
* @param {string[]} args - Git command arguments
|
|
262
|
+
* @returns {{ exitCode: number, stdout: string, stderr: string }}
|
|
263
|
+
*/
|
|
264
|
+
function execGit(gitCwd, args) {
|
|
265
|
+
try {
|
|
266
|
+
const escaped = args.map(a => {
|
|
267
|
+
if (/^[a-zA-Z0-9._\-/=:@]+$/.test(a)) return a;
|
|
268
|
+
return "'" + a.replace(/'/g, "'\\''") + "'";
|
|
269
|
+
});
|
|
270
|
+
const stdout = execSync('git ' + escaped.join(' '), {
|
|
271
|
+
cwd: gitCwd,
|
|
272
|
+
stdio: 'pipe',
|
|
273
|
+
encoding: 'utf-8',
|
|
274
|
+
});
|
|
275
|
+
return { exitCode: 0, stdout: stdout.trim(), stderr: '' };
|
|
276
|
+
} catch (err) {
|
|
277
|
+
// intentionally silent: git command failures are normal control flow
|
|
278
|
+
return {
|
|
279
|
+
exitCode: err.status ?? 1,
|
|
280
|
+
stdout: (err.stdout ?? '').toString().trim(),
|
|
281
|
+
stderr: (err.stderr ?? '').toString().trim(),
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Check if a path is git-ignored.
|
|
288
|
+
*
|
|
289
|
+
* @param {string} gitCwd - Working directory
|
|
290
|
+
* @param {string} targetPath - Path to check
|
|
291
|
+
* @returns {boolean}
|
|
292
|
+
*/
|
|
293
|
+
function isGitIgnored(gitCwd, targetPath) {
|
|
294
|
+
try {
|
|
295
|
+
execSync('git check-ignore -q --no-index -- ' + targetPath.replace(/[^a-zA-Z0-9._\-/]/g, ''), {
|
|
296
|
+
cwd: gitCwd,
|
|
297
|
+
stdio: 'pipe',
|
|
298
|
+
});
|
|
299
|
+
return true;
|
|
300
|
+
} catch {
|
|
301
|
+
// intentionally silent: non-zero exit means not ignored
|
|
302
|
+
return false;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// ─── YAML frontmatter parsing ─────────────────────────────────────────────────
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Parse YAML frontmatter from markdown content.
|
|
310
|
+
* Handles flat key-value pairs, inline arrays, and multi-line arrays.
|
|
311
|
+
*
|
|
312
|
+
* @param {string} content - Markdown content with optional frontmatter
|
|
313
|
+
* @returns {object} Parsed frontmatter as a plain object
|
|
314
|
+
*/
|
|
315
|
+
function parseYamlFrontmatter(content) {
|
|
316
|
+
const normalized = content.replace(/\r\n/g, '\n');
|
|
317
|
+
const match = normalized.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
318
|
+
if (!match) return {};
|
|
319
|
+
|
|
320
|
+
const yaml = match[1];
|
|
321
|
+
const result = {};
|
|
322
|
+
|
|
323
|
+
const lines = yaml.split('\n');
|
|
324
|
+
let currentKey = null;
|
|
325
|
+
|
|
326
|
+
for (const line of lines) {
|
|
327
|
+
// Array item
|
|
328
|
+
if (/^\s+-\s+/.test(line) && currentKey) {
|
|
329
|
+
const val = line.replace(/^\s+-\s+/, '').trim().replace(/^["']|["']$/g, '');
|
|
330
|
+
if (!result[currentKey]) result[currentKey] = [];
|
|
331
|
+
if (Array.isArray(result[currentKey])) {
|
|
332
|
+
result[currentKey].push(val);
|
|
333
|
+
}
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Key-value pair
|
|
338
|
+
const kvMatch = line.match(/^(\w[\w_]*)\s*:\s*(.*)/);
|
|
339
|
+
if (kvMatch) {
|
|
340
|
+
currentKey = kvMatch[1];
|
|
341
|
+
let val = kvMatch[2].trim();
|
|
342
|
+
|
|
343
|
+
if (val === '' || val === '|') continue;
|
|
344
|
+
|
|
345
|
+
// Handle arrays on same line: [a, b, c]
|
|
346
|
+
if (val.startsWith('[') && val.endsWith(']')) {
|
|
347
|
+
result[currentKey] = val.slice(1, -1).split(',')
|
|
348
|
+
.map(v => v.trim().replace(/^["']|["']$/g, ''))
|
|
349
|
+
.filter(Boolean);
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Clean quotes
|
|
354
|
+
val = val.replace(/^["']|["']$/g, '');
|
|
355
|
+
|
|
356
|
+
// Type coercion
|
|
357
|
+
if (val === 'true') val = true;
|
|
358
|
+
else if (val === 'false') val = false;
|
|
359
|
+
else if (/^\d+$/.test(val)) val = parseInt(val, 10);
|
|
360
|
+
|
|
361
|
+
result[currentKey] = val;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Handle must_haves as a nested object
|
|
366
|
+
if (yaml.includes('must_haves:')) {
|
|
367
|
+
result.must_haves = parseMustHaves(yaml);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
return result;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Parse the must_haves section from YAML frontmatter.
|
|
375
|
+
*
|
|
376
|
+
* @param {string} yaml - Raw YAML content (without --- delimiters)
|
|
377
|
+
* @returns {{ truths: string[], artifacts: string[], key_links: string[] }}
|
|
378
|
+
*/
|
|
379
|
+
function parseMustHaves(yaml) {
|
|
380
|
+
const result = { truths: [], artifacts: [], key_links: [] };
|
|
381
|
+
let section = null;
|
|
382
|
+
|
|
383
|
+
const inMustHaves = yaml.replace(/\r\n/g, '\n').split('\n');
|
|
384
|
+
let collecting = false;
|
|
385
|
+
|
|
386
|
+
for (const line of inMustHaves) {
|
|
387
|
+
if (/^\s*must_haves:/.test(line)) {
|
|
388
|
+
collecting = true;
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
if (collecting) {
|
|
392
|
+
if (/^\s{2}truths:/.test(line)) { section = 'truths'; continue; }
|
|
393
|
+
if (/^\s{2}artifacts:/.test(line)) { section = 'artifacts'; continue; }
|
|
394
|
+
if (/^\s{2}key_links:/.test(line)) { section = 'key_links'; continue; }
|
|
395
|
+
if (/^\w/.test(line)) break;
|
|
396
|
+
|
|
397
|
+
if (section && /^\s+-\s+/.test(line)) {
|
|
398
|
+
result[section].push(line.replace(/^\s+-\s+/, '').trim().replace(/^["']|["']$/g, ''));
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
return result;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Set/update YAML frontmatter fields in markdown content.
|
|
408
|
+
* Creates frontmatter block if none exists.
|
|
409
|
+
*
|
|
410
|
+
* @param {string} content - Markdown content
|
|
411
|
+
* @param {object} updates - Key-value pairs to set in frontmatter
|
|
412
|
+
* @returns {string} Updated content
|
|
413
|
+
*/
|
|
414
|
+
function setYamlFrontmatter(content, updates) {
|
|
415
|
+
const normalized = content.replace(/\r\n/g, '\n');
|
|
416
|
+
const match = normalized.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
417
|
+
|
|
418
|
+
if (!match) {
|
|
419
|
+
// No existing frontmatter — create one
|
|
420
|
+
const lines = Object.entries(updates).map(([k, v]) => {
|
|
421
|
+
if (Array.isArray(v)) {
|
|
422
|
+
return `${k}:\n${v.map(item => ` - ${item}`).join('\n')}`;
|
|
423
|
+
}
|
|
424
|
+
if (typeof v === 'string' && (v.includes(':') || v.includes('#'))) {
|
|
425
|
+
return `${k}: "${v}"`;
|
|
426
|
+
}
|
|
427
|
+
return `${k}: ${v}`;
|
|
428
|
+
});
|
|
429
|
+
return `---\n${lines.join('\n')}\n---\n${normalized}`;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
let yaml = match[1];
|
|
433
|
+
|
|
434
|
+
for (const [key, value] of Object.entries(updates)) {
|
|
435
|
+
const keyRegex = new RegExp(`^(${key})\\s*:.*$`, 'm');
|
|
436
|
+
const formatted = typeof value === 'string' && (value.includes(':') || value.includes('#'))
|
|
437
|
+
? `"${value}"`
|
|
438
|
+
: String(value);
|
|
439
|
+
|
|
440
|
+
if (keyRegex.test(yaml)) {
|
|
441
|
+
yaml = yaml.replace(keyRegex, `${key}: ${formatted}`);
|
|
442
|
+
} else {
|
|
443
|
+
yaml += `\n${key}: ${formatted}`;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
return normalized.replace(/^---\s*\n[\s\S]*?\n---/, `---\n${yaml}\n---`);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// ─── Misc utilities ───────────────────────────────────────────────────────────
|
|
451
|
+
|
|
452
|
+
function countMustHaves(mustHaves) {
|
|
453
|
+
if (!mustHaves) return 0;
|
|
454
|
+
return (mustHaves.truths || []).length +
|
|
455
|
+
(mustHaves.artifacts || []).length +
|
|
456
|
+
(mustHaves.key_links || []).length;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function determinePhaseStatus(planCount, completedCount, summaryCount, hasVerification, phaseDir) {
|
|
460
|
+
if (planCount === 0) {
|
|
461
|
+
if (fs.existsSync(path.join(phaseDir, 'CONTEXT.md'))) return 'discussed';
|
|
462
|
+
return 'not_started';
|
|
463
|
+
}
|
|
464
|
+
if (completedCount === 0 && summaryCount === 0) return 'planned';
|
|
465
|
+
if (completedCount < planCount) return 'building';
|
|
466
|
+
if (!hasVerification) return 'built';
|
|
467
|
+
try {
|
|
468
|
+
const vContent = fs.readFileSync(path.join(phaseDir, 'VERIFICATION.md'), 'utf8');
|
|
469
|
+
if (/status:\s*["']?passed/i.test(vContent)) return 'verified';
|
|
470
|
+
if (/status:\s*["']?gaps_found/i.test(vContent)) return 'needs_fixes';
|
|
471
|
+
return 'reviewed';
|
|
472
|
+
} catch (_) {
|
|
473
|
+
// intentionally silent: VERIFICATION.md may not exist
|
|
474
|
+
return 'built';
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
function calculateProgress(pDir) {
|
|
479
|
+
const phasesDir = path.join(pDir, 'phases');
|
|
480
|
+
if (!fs.existsSync(phasesDir)) {
|
|
481
|
+
return { total: 0, completed: 0, percentage: 0 };
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
let total = 0;
|
|
485
|
+
let completed = 0;
|
|
486
|
+
|
|
487
|
+
const entries = fs.readdirSync(phasesDir, { withFileTypes: true })
|
|
488
|
+
.filter(e => e.isDirectory());
|
|
489
|
+
|
|
490
|
+
for (const entry of entries) {
|
|
491
|
+
const dir = path.join(phasesDir, entry.name);
|
|
492
|
+
const plans = findFiles(dir, /PLAN.*\.md$/i);
|
|
493
|
+
total += plans.length;
|
|
494
|
+
|
|
495
|
+
const summaries = findFiles(dir, /^SUMMARY-.*\.md$/);
|
|
496
|
+
for (const s of summaries) {
|
|
497
|
+
const content = fs.readFileSync(path.join(dir, s), 'utf8');
|
|
498
|
+
if (/status:\s*["']?complete/i.test(content)) completed++;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
return {
|
|
503
|
+
total,
|
|
504
|
+
completed,
|
|
505
|
+
percentage: total > 0 ? Math.round((completed / total) * 100) : 0
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Return an ISO 8601 UTC timestamp string.
|
|
511
|
+
*
|
|
512
|
+
* @returns {string} ISO timestamp
|
|
513
|
+
*/
|
|
514
|
+
function currentTimestamp() {
|
|
515
|
+
return new Date().toISOString();
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* Generate a URL-safe slug from a string.
|
|
520
|
+
*
|
|
521
|
+
* @param {string} text - Input text
|
|
522
|
+
* @returns {string|null} Slugified string or null
|
|
523
|
+
*/
|
|
524
|
+
function generateSlug(text) {
|
|
525
|
+
if (!text) return null;
|
|
526
|
+
return text.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* Resolve the model name for an agent type based on config.
|
|
531
|
+
*
|
|
532
|
+
* @param {string} agentType - Agent type key (e.g., 'pbr-executor')
|
|
533
|
+
* @param {object} config - Config object with model_profile and optional model_overrides
|
|
534
|
+
* @returns {string} Resolved model name
|
|
535
|
+
*/
|
|
536
|
+
function resolveModel(agentType, config) {
|
|
537
|
+
// Check per-agent override first
|
|
538
|
+
const override = config && config.model_overrides && config.model_overrides[agentType];
|
|
539
|
+
if (override) {
|
|
540
|
+
return override === 'opus' ? 'inherit' : override;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// Fall back to profile lookup
|
|
544
|
+
const profile = (config && config.model_profile) || 'balanced';
|
|
545
|
+
const agentModels = MODEL_PROFILES[agentType];
|
|
546
|
+
if (!agentModels) return 'sonnet';
|
|
547
|
+
const resolved = agentModels[profile] || agentModels['balanced'] || 'sonnet';
|
|
548
|
+
return resolved === 'opus' ? 'inherit' : resolved;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// ─── Regex and phase utilities ────────────────────────────────────────────────
|
|
552
|
+
|
|
553
|
+
function escapeRegex(value) {
|
|
554
|
+
return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
function normalizePhaseName(phase) {
|
|
558
|
+
const match = String(phase).match(/^(\d+)([A-Z])?((?:\.\d+)*)/i);
|
|
559
|
+
if (!match) return phase;
|
|
560
|
+
const padded = match[1].padStart(2, '0');
|
|
561
|
+
const letter = match[2] ? match[2].toUpperCase() : '';
|
|
562
|
+
const decimal = match[3] || '';
|
|
563
|
+
return padded + letter + decimal;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
function comparePhaseNum(a, b) {
|
|
567
|
+
const pa = String(a).match(/^(\d+)([A-Z])?((?:\.\d+)*)/i);
|
|
568
|
+
const pb = String(b).match(/^(\d+)([A-Z])?((?:\.\d+)*)/i);
|
|
569
|
+
if (!pa || !pb) return String(a).localeCompare(String(b));
|
|
570
|
+
const intDiff = parseInt(pa[1], 10) - parseInt(pb[1], 10);
|
|
571
|
+
if (intDiff !== 0) return intDiff;
|
|
572
|
+
const la = (pa[2] || '').toUpperCase();
|
|
573
|
+
const lb = (pb[2] || '').toUpperCase();
|
|
574
|
+
if (la !== lb) {
|
|
575
|
+
if (!la) return -1;
|
|
576
|
+
if (!lb) return 1;
|
|
577
|
+
return la < lb ? -1 : 1;
|
|
578
|
+
}
|
|
579
|
+
const aDecParts = pa[3] ? pa[3].slice(1).split('.').map(p => parseInt(p, 10)) : [];
|
|
580
|
+
const bDecParts = pb[3] ? pb[3].slice(1).split('.').map(p => parseInt(p, 10)) : [];
|
|
581
|
+
const maxLen = Math.max(aDecParts.length, bDecParts.length);
|
|
582
|
+
if (aDecParts.length === 0 && bDecParts.length > 0) return -1;
|
|
583
|
+
if (bDecParts.length === 0 && aDecParts.length > 0) return 1;
|
|
584
|
+
for (let i = 0; i < maxLen; i++) {
|
|
585
|
+
const av = Number.isFinite(aDecParts[i]) ? aDecParts[i] : 0;
|
|
586
|
+
const bv = Number.isFinite(bDecParts[i]) ? bDecParts[i] : 0;
|
|
587
|
+
if (av !== bv) return av - bv;
|
|
588
|
+
}
|
|
589
|
+
return 0;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
function searchPhaseInDir(baseDir, relBase, normalized) {
|
|
593
|
+
try {
|
|
594
|
+
const entries = fs.readdirSync(baseDir, { withFileTypes: true });
|
|
595
|
+
const dirs = entries.filter(e => e.isDirectory()).map(e => e.name).sort((a, b) => comparePhaseNum(a, b));
|
|
596
|
+
const match = dirs.find(d => d.startsWith(normalized));
|
|
597
|
+
if (!match) return null;
|
|
598
|
+
|
|
599
|
+
const dirMatch = match.match(/^(\d+[A-Z]?(?:\.\d+)*)-?(.*)/i);
|
|
600
|
+
const phaseNumber = dirMatch ? dirMatch[1] : normalized;
|
|
601
|
+
const phaseName = dirMatch && dirMatch[2] ? dirMatch[2] : null;
|
|
602
|
+
const phaseDir = path.join(baseDir, match);
|
|
603
|
+
const phaseFiles = fs.readdirSync(phaseDir);
|
|
604
|
+
|
|
605
|
+
const plans = phaseFiles.filter(f => f.endsWith('-PLAN.md') || f === 'PLAN.md').sort();
|
|
606
|
+
const summaries = phaseFiles.filter(f => f.endsWith('-SUMMARY.md') || f === 'SUMMARY.md').sort();
|
|
607
|
+
const hasResearch = phaseFiles.some(f => f.endsWith('-RESEARCH.md') || f === 'RESEARCH.md');
|
|
608
|
+
const hasContext = phaseFiles.some(f => f.endsWith('-CONTEXT.md') || f === 'CONTEXT.md');
|
|
609
|
+
const hasVerification = phaseFiles.some(f => f.endsWith('-VERIFICATION.md') || f === 'VERIFICATION.md');
|
|
610
|
+
|
|
611
|
+
const completedPlanIds = new Set(
|
|
612
|
+
summaries.map(s => s.replace('-SUMMARY.md', '').replace('SUMMARY.md', ''))
|
|
613
|
+
);
|
|
614
|
+
const incompletePlans = plans.filter(p => {
|
|
615
|
+
const planId = p.replace('-PLAN.md', '').replace('PLAN.md', '');
|
|
616
|
+
return !completedPlanIds.has(planId);
|
|
617
|
+
});
|
|
618
|
+
|
|
619
|
+
return {
|
|
620
|
+
found: true,
|
|
621
|
+
directory: toPosixPath(path.join(relBase, match)),
|
|
622
|
+
phase_number: phaseNumber,
|
|
623
|
+
phase_name: phaseName,
|
|
624
|
+
phase_slug: phaseName ? phaseName.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '') : null,
|
|
625
|
+
plans,
|
|
626
|
+
summaries,
|
|
627
|
+
incomplete_plans: incompletePlans,
|
|
628
|
+
has_research: hasResearch,
|
|
629
|
+
has_context: hasContext,
|
|
630
|
+
has_verification: hasVerification,
|
|
631
|
+
};
|
|
632
|
+
} catch (e) {
|
|
633
|
+
logHook('core', 'debug', 'Failed to search phase in directory', { error: e.message });
|
|
634
|
+
return null;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
function findPhaseInternal(phaseCwd, phase) {
|
|
639
|
+
if (!phase) return null;
|
|
640
|
+
|
|
641
|
+
const phasesDir = path.join(phaseCwd, '.planning', 'phases');
|
|
642
|
+
const normalized = normalizePhaseName(phase);
|
|
643
|
+
|
|
644
|
+
const current = searchPhaseInDir(phasesDir, '.planning/phases', normalized);
|
|
645
|
+
if (current) return current;
|
|
646
|
+
|
|
647
|
+
const milestonesDir = path.join(phaseCwd, '.planning', 'milestones');
|
|
648
|
+
if (!fs.existsSync(milestonesDir)) return null;
|
|
649
|
+
|
|
650
|
+
try {
|
|
651
|
+
const milestoneEntries = fs.readdirSync(milestonesDir, { withFileTypes: true });
|
|
652
|
+
const archiveDirs = milestoneEntries
|
|
653
|
+
.filter(e => e.isDirectory() && /^v[\d.]+-phases$/.test(e.name))
|
|
654
|
+
.map(e => e.name)
|
|
655
|
+
.sort()
|
|
656
|
+
.reverse();
|
|
657
|
+
|
|
658
|
+
for (const archiveName of archiveDirs) {
|
|
659
|
+
const version = archiveName.match(/^(v[\d.]+)-phases$/)[1];
|
|
660
|
+
const archivePath = path.join(milestonesDir, archiveName);
|
|
661
|
+
const relBase = '.planning/milestones/' + archiveName;
|
|
662
|
+
const result = searchPhaseInDir(archivePath, relBase, normalized);
|
|
663
|
+
if (result) {
|
|
664
|
+
result.archived = version;
|
|
665
|
+
return result;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
} catch (e) { logHook('core', 'debug', 'Failed to search milestone archives', { error: e.message }); }
|
|
669
|
+
|
|
670
|
+
return null;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
function getArchivedPhaseDirs(archCwd) {
|
|
674
|
+
const milestonesDir = path.join(archCwd, '.planning', 'milestones');
|
|
675
|
+
const results = [];
|
|
676
|
+
|
|
677
|
+
if (!fs.existsSync(milestonesDir)) return results;
|
|
678
|
+
|
|
679
|
+
try {
|
|
680
|
+
const milestoneEntries = fs.readdirSync(milestonesDir, { withFileTypes: true });
|
|
681
|
+
const phaseDirs = milestoneEntries
|
|
682
|
+
.filter(e => e.isDirectory() && /^v[\d.]+-phases$/.test(e.name))
|
|
683
|
+
.map(e => e.name)
|
|
684
|
+
.sort()
|
|
685
|
+
.reverse();
|
|
686
|
+
|
|
687
|
+
for (const archiveName of phaseDirs) {
|
|
688
|
+
const version = archiveName.match(/^(v[\d.]+)-phases$/)[1];
|
|
689
|
+
const archivePath = path.join(milestonesDir, archiveName);
|
|
690
|
+
const entries = fs.readdirSync(archivePath, { withFileTypes: true });
|
|
691
|
+
const dirs = entries.filter(e => e.isDirectory()).map(e => e.name).sort((a, b) => comparePhaseNum(a, b));
|
|
692
|
+
|
|
693
|
+
for (const dir of dirs) {
|
|
694
|
+
results.push({
|
|
695
|
+
name: dir,
|
|
696
|
+
milestone: version,
|
|
697
|
+
basePath: path.join('.planning', 'milestones', archiveName),
|
|
698
|
+
fullPath: path.join(archivePath, dir),
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
} catch (e) { logHook('core', 'debug', 'Failed to list archived phase dirs', { error: e.message }); }
|
|
703
|
+
|
|
704
|
+
return results;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
// ─── Roadmap & milestone utilities ────────────────────────────────────────────
|
|
708
|
+
|
|
709
|
+
function getRoadmapPhaseInternal(rmCwd, phaseNum) {
|
|
710
|
+
if (!phaseNum) return null;
|
|
711
|
+
const roadmapPath = path.join(rmCwd, '.planning', 'ROADMAP.md');
|
|
712
|
+
if (!fs.existsSync(roadmapPath)) return null;
|
|
713
|
+
|
|
714
|
+
try {
|
|
715
|
+
const content = fs.readFileSync(roadmapPath, 'utf8');
|
|
716
|
+
const escapedPhase = escapeRegex(phaseNum.toString());
|
|
717
|
+
const phasePattern = new RegExp(`#{2,4}\\s*Phase\\s+${escapedPhase}:\\s*([^\\n]+)`, 'i');
|
|
718
|
+
const headerMatch = content.match(phasePattern);
|
|
719
|
+
if (!headerMatch) return null;
|
|
720
|
+
|
|
721
|
+
const phaseName = headerMatch[1].trim();
|
|
722
|
+
const headerIndex = headerMatch.index;
|
|
723
|
+
const restOfContent = content.slice(headerIndex);
|
|
724
|
+
const nextHeaderMatch = restOfContent.match(/\n#{2,4}\s+Phase\s+\d/i);
|
|
725
|
+
const sectionEnd = nextHeaderMatch ? headerIndex + nextHeaderMatch.index : content.length;
|
|
726
|
+
const section = content.slice(headerIndex, sectionEnd).trim();
|
|
727
|
+
|
|
728
|
+
const goalMatch = section.match(/\*\*Goal:\*\*\s*([^\n]+)/i);
|
|
729
|
+
const goal = goalMatch ? goalMatch[1].trim() : null;
|
|
730
|
+
|
|
731
|
+
return {
|
|
732
|
+
found: true,
|
|
733
|
+
phase_number: phaseNum.toString(),
|
|
734
|
+
phase_name: phaseName,
|
|
735
|
+
goal,
|
|
736
|
+
section,
|
|
737
|
+
};
|
|
738
|
+
} catch (e) {
|
|
739
|
+
logHook('core', 'debug', 'Failed to read roadmap for phase lookup', { error: e.message });
|
|
740
|
+
return null;
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
function getMilestoneInfo(miCwd) {
|
|
745
|
+
try {
|
|
746
|
+
const roadmap = fs.readFileSync(path.join(miCwd, '.planning', 'ROADMAP.md'), 'utf8');
|
|
747
|
+
|
|
748
|
+
const inProgressMatch = roadmap.match(/\u{1F6A7}\s*\*\*v(\d+\.\d+)\s+([^*]+)\*\*/u);
|
|
749
|
+
if (inProgressMatch) {
|
|
750
|
+
return {
|
|
751
|
+
version: 'v' + inProgressMatch[1],
|
|
752
|
+
name: inProgressMatch[2].trim(),
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
const cleaned = roadmap.replace(/<details>[\s\S]*?<\/details>/gi, '');
|
|
757
|
+
const headingMatch = cleaned.match(/## .*v(\d+\.\d+)[:\s]+([^\n(]+)/);
|
|
758
|
+
if (headingMatch) {
|
|
759
|
+
return {
|
|
760
|
+
version: 'v' + headingMatch[1],
|
|
761
|
+
name: headingMatch[2].trim(),
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
const versionMatch = cleaned.match(/v(\d+\.\d+)/);
|
|
765
|
+
return {
|
|
766
|
+
version: versionMatch ? versionMatch[0] : 'v1.0',
|
|
767
|
+
name: 'milestone',
|
|
768
|
+
};
|
|
769
|
+
} catch (e) {
|
|
770
|
+
logHook('core', 'debug', 'Failed to read milestone info', { error: e.message });
|
|
771
|
+
return { version: 'v1.0', name: 'milestone' };
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
/**
|
|
776
|
+
* Returns a filter function that checks whether a phase directory belongs
|
|
777
|
+
* to the current milestone based on ROADMAP.md phase headings.
|
|
778
|
+
*/
|
|
779
|
+
function getMilestonePhaseFilter(filterCwd) {
|
|
780
|
+
const milestonePhaseNums = new Set();
|
|
781
|
+
try {
|
|
782
|
+
const roadmap = fs.readFileSync(path.join(filterCwd, '.planning', 'ROADMAP.md'), 'utf8');
|
|
783
|
+
const phasePattern = /#{2,4}\s*Phase\s+(\d+[A-Z]?(?:\.\d+)*)\s*:/gi;
|
|
784
|
+
let m;
|
|
785
|
+
while ((m = phasePattern.exec(roadmap)) !== null) {
|
|
786
|
+
milestonePhaseNums.add(m[1]);
|
|
787
|
+
}
|
|
788
|
+
} catch (e) { logHook('core', 'debug', 'Failed to read roadmap for milestone filter', { error: e.message }); }
|
|
789
|
+
|
|
790
|
+
if (milestonePhaseNums.size === 0) {
|
|
791
|
+
const passAll = () => true;
|
|
792
|
+
passAll.phaseCount = 0;
|
|
793
|
+
return passAll;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
const normalized = new Set(
|
|
797
|
+
[...milestonePhaseNums].map(n => (n.replace(/^0+/, '') || '0').toLowerCase())
|
|
798
|
+
);
|
|
799
|
+
|
|
800
|
+
function isDirInMilestone(dirName) {
|
|
801
|
+
const dm = dirName.match(/^0*(\d+[A-Za-z]?(?:\.\d+)*)/);
|
|
802
|
+
if (!dm) return false;
|
|
803
|
+
return normalized.has(dm[1].toLowerCase());
|
|
804
|
+
}
|
|
805
|
+
isDirInMilestone.phaseCount = milestonePhaseNums.size;
|
|
806
|
+
return isDirInMilestone;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
// ─── Atomic file operations ───────────────────────────────────────────────────
|
|
810
|
+
|
|
811
|
+
/**
|
|
812
|
+
* Write content to a file atomically: write to .tmp, backup original to .bak,
|
|
813
|
+
* rename .tmp over original. On failure, restore from .bak if available.
|
|
814
|
+
*
|
|
815
|
+
* @param {string} filePath - Target file path
|
|
816
|
+
* @param {string} content - Content to write
|
|
817
|
+
* @returns {{success: boolean, error?: string}} Result
|
|
818
|
+
*/
|
|
819
|
+
function atomicWrite(filePath, content) {
|
|
820
|
+
const tmpPath = filePath + '.tmp';
|
|
821
|
+
const bakPath = filePath + '.bak';
|
|
822
|
+
|
|
823
|
+
try {
|
|
824
|
+
fs.writeFileSync(tmpPath, content, 'utf8');
|
|
825
|
+
|
|
826
|
+
if (fs.existsSync(filePath)) {
|
|
827
|
+
try { fs.copyFileSync(filePath, bakPath); } catch (_e) { /* intentionally silent: backup is non-fatal */ }
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
fs.renameSync(tmpPath, filePath);
|
|
831
|
+
|
|
832
|
+
try {
|
|
833
|
+
if (fs.existsSync(bakPath)) fs.unlinkSync(bakPath);
|
|
834
|
+
} catch (_e) { /* intentionally silent: non-fatal */ }
|
|
835
|
+
|
|
836
|
+
return { success: true };
|
|
837
|
+
} catch (e) {
|
|
838
|
+
try {
|
|
839
|
+
if (fs.existsSync(bakPath)) fs.copyFileSync(bakPath, filePath);
|
|
840
|
+
} catch (_restoreErr) { /* intentionally silent: restore is last resort */ }
|
|
841
|
+
try {
|
|
842
|
+
if (fs.existsSync(tmpPath)) fs.unlinkSync(tmpPath);
|
|
843
|
+
} catch (_cleanupErr) { /* intentionally silent: tmp cleanup is non-fatal */ }
|
|
844
|
+
|
|
845
|
+
return { success: false, error: e.message };
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
/**
|
|
850
|
+
* Locked file update: read-modify-write with exclusive lockfile.
|
|
851
|
+
* Prevents concurrent writes to STATE.md and ROADMAP.md.
|
|
852
|
+
*
|
|
853
|
+
* @param {string} filePath - Absolute path to the file to update
|
|
854
|
+
* @param {function} updateFn - Receives current content, returns new content
|
|
855
|
+
* @param {object} opts - Options: { retries: 3, retryDelayMs: 100, timeoutMs: 5000 }
|
|
856
|
+
* @returns {object} { success, content?, error? }
|
|
857
|
+
*/
|
|
858
|
+
async function lockedFileUpdate(filePath, updateFn, opts = {}) {
|
|
859
|
+
const retries = opts.retries || 10;
|
|
860
|
+
const retryDelayMs = opts.retryDelayMs || 50;
|
|
861
|
+
const timeoutMs = opts.timeoutMs || 10000;
|
|
862
|
+
const lockPath = filePath + '.lock';
|
|
863
|
+
|
|
864
|
+
// Async sleep helper — does NOT block the event loop
|
|
865
|
+
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
866
|
+
|
|
867
|
+
let lockFd = null;
|
|
868
|
+
let lockAcquired = false;
|
|
869
|
+
|
|
870
|
+
try {
|
|
871
|
+
for (let attempt = 0; attempt < retries; attempt++) {
|
|
872
|
+
try {
|
|
873
|
+
lockFd = await fs.promises.open(lockPath, 'wx');
|
|
874
|
+
lockAcquired = true;
|
|
875
|
+
break;
|
|
876
|
+
} catch (e) { // intentionally silent: lock contention is expected
|
|
877
|
+
if (e.code === 'EEXIST') {
|
|
878
|
+
// Check for stale lock
|
|
879
|
+
try {
|
|
880
|
+
const stats = await fs.promises.stat(lockPath);
|
|
881
|
+
if (Date.now() - stats.mtimeMs > timeoutMs) {
|
|
882
|
+
try { await fs.promises.unlink(lockPath); } catch (_unlinkErr) { /* best effort */ }
|
|
883
|
+
continue;
|
|
884
|
+
}
|
|
885
|
+
} catch (_statErr) { // intentionally silent: lock stat failed
|
|
886
|
+
continue;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
if (attempt < retries - 1) {
|
|
890
|
+
const baseWait = retryDelayMs * Math.pow(2, attempt);
|
|
891
|
+
const jitter = Math.floor(Math.random() * retryDelayMs);
|
|
892
|
+
const waitMs = Math.min(baseWait + jitter, 2000);
|
|
893
|
+
await sleep(waitMs);
|
|
894
|
+
continue;
|
|
895
|
+
}
|
|
896
|
+
// Last retry exhausted — break to fall through to last-resort write
|
|
897
|
+
break;
|
|
898
|
+
}
|
|
899
|
+
throw e;
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
if (!lockAcquired) {
|
|
904
|
+
process.stderr.write(`[pbr] WARN: lock contention on ${path.basename(filePath)} after ${retries} attempts — writing without lock\n`);
|
|
905
|
+
// Fall through to read-modify-write below (last-resort write)
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
if (lockAcquired) {
|
|
909
|
+
await lockFd.write(`${process.pid}`);
|
|
910
|
+
await lockFd.close();
|
|
911
|
+
lockFd = null;
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
let content = '';
|
|
915
|
+
if (fs.existsSync(filePath)) {
|
|
916
|
+
content = fs.readFileSync(filePath, 'utf8');
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
const newContent = updateFn(content);
|
|
920
|
+
|
|
921
|
+
const writeResult = atomicWrite(filePath, newContent);
|
|
922
|
+
if (!writeResult.success) {
|
|
923
|
+
return { success: false, error: writeResult.error };
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
return { success: true, content: newContent };
|
|
927
|
+
} catch (e) {
|
|
928
|
+
logHook('core', 'debug', 'lockedFileUpdate failed', { error: e.message });
|
|
929
|
+
return { success: false, error: e.message };
|
|
930
|
+
} finally {
|
|
931
|
+
try {
|
|
932
|
+
if (lockFd !== null) {
|
|
933
|
+
try { await lockFd.close(); } catch (_e) { /* intentionally silent */ }
|
|
934
|
+
}
|
|
935
|
+
} catch (_e) { /* intentionally silent: fd close in finally */ }
|
|
936
|
+
if (lockAcquired) {
|
|
937
|
+
try { await fs.promises.unlink(lockPath); } catch (_e) { /* intentionally silent: lock cleanup in finally block */ }
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
// ─── Lightweight JSON Schema validator ────────────────────────────────────────
|
|
943
|
+
|
|
944
|
+
/**
|
|
945
|
+
* Validate an object against a simple JSON Schema subset.
|
|
946
|
+
* Supports type, enum, properties, additionalProperties, minimum, maximum.
|
|
947
|
+
*
|
|
948
|
+
* @param {*} value - Value to validate
|
|
949
|
+
* @param {object} schema - JSON Schema subset
|
|
950
|
+
* @param {string} prefix - Path prefix for error messages
|
|
951
|
+
* @param {string[]} errors - Array to push errors to
|
|
952
|
+
* @param {string[]} warnings - Array to push warnings to
|
|
953
|
+
*/
|
|
954
|
+
function validateObject(value, schema, prefix, errors, warnings) {
|
|
955
|
+
if (schema.type) {
|
|
956
|
+
const types = Array.isArray(schema.type) ? schema.type : [schema.type];
|
|
957
|
+
const actualType = typeof value;
|
|
958
|
+
const typeMatch = types.some(t => {
|
|
959
|
+
if (t === 'integer') return actualType === 'number' && Number.isInteger(value);
|
|
960
|
+
return actualType === t;
|
|
961
|
+
});
|
|
962
|
+
if (!typeMatch) {
|
|
963
|
+
errors.push(`${prefix || 'root'}: expected ${types.join('|')}, got ${actualType}`);
|
|
964
|
+
return;
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
if (schema.enum && !schema.enum.includes(value)) {
|
|
969
|
+
errors.push(`${prefix || 'root'}: value "${value}" not in allowed values [${schema.enum.join(', ')}]`);
|
|
970
|
+
return;
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
if (schema.minimum !== undefined && value < schema.minimum) {
|
|
974
|
+
errors.push(`${prefix || 'root'}: value ${value} is below minimum ${schema.minimum}`);
|
|
975
|
+
}
|
|
976
|
+
if (schema.maximum !== undefined && value > schema.maximum) {
|
|
977
|
+
errors.push(`${prefix || 'root'}: value ${value} is above maximum ${schema.maximum}`);
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
if (schema.type === 'object' && schema.properties) {
|
|
981
|
+
const knownKeys = new Set(Object.keys(schema.properties));
|
|
982
|
+
for (const key of Object.keys(value)) {
|
|
983
|
+
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
984
|
+
if (!knownKeys.has(key)) {
|
|
985
|
+
if (schema.additionalProperties === false) {
|
|
986
|
+
warnings.push(`${fullKey}: unrecognized key (possible typo?)`);
|
|
987
|
+
}
|
|
988
|
+
continue;
|
|
989
|
+
}
|
|
990
|
+
validateObject(value[key], schema.properties[key], fullKey, errors, warnings);
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
// ─── Session-scoped path resolution ───────────────────────────────────────────
|
|
996
|
+
|
|
997
|
+
const STALE_SESSION_MS = 4 * 60 * 60 * 1000; // 4 hours
|
|
998
|
+
|
|
999
|
+
/**
|
|
1000
|
+
* Resolve a session-scoped file path.
|
|
1001
|
+
*
|
|
1002
|
+
* @param {string} pDir - Path to .planning/ directory
|
|
1003
|
+
* @param {string} filename - Filename to resolve
|
|
1004
|
+
* @param {string} sessionId - Session identifier
|
|
1005
|
+
* @returns {string} Resolved path
|
|
1006
|
+
*/
|
|
1007
|
+
function resolveSessionPath(pDir, filename, sessionId) {
|
|
1008
|
+
return path.join(pDir, '.sessions', sessionId, filename);
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
/**
|
|
1012
|
+
* Ensure session directory exists and write meta.json.
|
|
1013
|
+
*
|
|
1014
|
+
* @param {string} pDir - Path to .planning/ directory
|
|
1015
|
+
* @param {string} sessionId - Session identifier
|
|
1016
|
+
*/
|
|
1017
|
+
function ensureSessionDir(pDir, sessionId) {
|
|
1018
|
+
const dirPath = path.join(pDir, '.sessions', sessionId);
|
|
1019
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
1020
|
+
const metaPath = path.join(dirPath, 'meta.json');
|
|
1021
|
+
if (!fs.existsSync(metaPath)) {
|
|
1022
|
+
fs.writeFileSync(metaPath, JSON.stringify({
|
|
1023
|
+
session_id: sessionId,
|
|
1024
|
+
created: new Date().toISOString(),
|
|
1025
|
+
pid: process.pid
|
|
1026
|
+
}, null, 2), 'utf8');
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
/**
|
|
1031
|
+
* Remove a session directory and all its contents.
|
|
1032
|
+
*
|
|
1033
|
+
* @param {string} pDir - Path to .planning/ directory
|
|
1034
|
+
* @param {string} sessionId - Session identifier
|
|
1035
|
+
*/
|
|
1036
|
+
function removeSessionDir(pDir, sessionId) {
|
|
1037
|
+
const dirPath = path.join(pDir, '.sessions', sessionId);
|
|
1038
|
+
if (fs.existsSync(dirPath)) {
|
|
1039
|
+
fs.rmSync(dirPath, { recursive: true, force: true });
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
/**
|
|
1044
|
+
* Remove stale session directories older than STALE_SESSION_MS.
|
|
1045
|
+
*
|
|
1046
|
+
* @param {string} pDir - Path to .planning/ directory
|
|
1047
|
+
* @returns {Array<{sessionId: string, age: number}>} Removed sessions
|
|
1048
|
+
*/
|
|
1049
|
+
function cleanStaleSessions(pDir) {
|
|
1050
|
+
const sessionsDir = path.join(pDir, '.sessions');
|
|
1051
|
+
if (!fs.existsSync(sessionsDir)) return [];
|
|
1052
|
+
|
|
1053
|
+
const removed = [];
|
|
1054
|
+
try {
|
|
1055
|
+
const entries = fs.readdirSync(sessionsDir, { withFileTypes: true });
|
|
1056
|
+
for (const entry of entries) {
|
|
1057
|
+
if (!entry.isDirectory()) continue;
|
|
1058
|
+
const dirPath = path.join(sessionsDir, entry.name);
|
|
1059
|
+
let ageMs = 0;
|
|
1060
|
+
|
|
1061
|
+
const metaPath = path.join(dirPath, 'meta.json');
|
|
1062
|
+
try {
|
|
1063
|
+
const meta = JSON.parse(fs.readFileSync(metaPath, 'utf8'));
|
|
1064
|
+
ageMs = Date.now() - new Date(meta.created).getTime();
|
|
1065
|
+
} catch (_e) {
|
|
1066
|
+
// intentionally silent: meta.json may not exist or be malformed
|
|
1067
|
+
try {
|
|
1068
|
+
const stats = fs.statSync(dirPath);
|
|
1069
|
+
ageMs = Date.now() - stats.mtimeMs;
|
|
1070
|
+
} catch (_statErr) {
|
|
1071
|
+
// intentionally silent: stat failure means skip this session
|
|
1072
|
+
continue;
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
if (ageMs > STALE_SESSION_MS) {
|
|
1077
|
+
fs.rmSync(dirPath, { recursive: true, force: true });
|
|
1078
|
+
removed.push({ sessionId: entry.name, age: ageMs });
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
} catch (_e) { logHook('core', 'debug', 'Failed during stale session cleanup'); }
|
|
1082
|
+
|
|
1083
|
+
return removed;
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
// ─── Session state management ─────────────────────────────────────────────────
|
|
1087
|
+
|
|
1088
|
+
const SESSION_ALLOWED_KEYS = ['activeSkill', 'compactCounter', 'sessionStart', 'activeOperation', 'activePlan'];
|
|
1089
|
+
|
|
1090
|
+
/**
|
|
1091
|
+
* Load .session.json from .planning/ directory.
|
|
1092
|
+
*
|
|
1093
|
+
* @param {string} dir - Path to .planning/ directory
|
|
1094
|
+
* @param {string} [sessionId] - Session identifier for session-scoped path
|
|
1095
|
+
* @returns {object} Parsed session data or empty object
|
|
1096
|
+
*/
|
|
1097
|
+
function sessionLoad(dir, sessionId) {
|
|
1098
|
+
const sessionPath = sessionId
|
|
1099
|
+
? resolveSessionPath(dir, '.session.json', sessionId)
|
|
1100
|
+
: path.join(dir, '.session.json');
|
|
1101
|
+
try {
|
|
1102
|
+
if (!fs.existsSync(sessionPath)) return {};
|
|
1103
|
+
const content = fs.readFileSync(sessionPath, 'utf8');
|
|
1104
|
+
return JSON.parse(content);
|
|
1105
|
+
} catch (_e) {
|
|
1106
|
+
// intentionally silent: session file may not exist
|
|
1107
|
+
return {};
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
/**
|
|
1112
|
+
* Save data to .session.json using atomic write.
|
|
1113
|
+
* Merges provided data with existing session data.
|
|
1114
|
+
*
|
|
1115
|
+
* @param {string} dir - Path to .planning/ directory
|
|
1116
|
+
* @param {object} data - Key-value pairs to merge into session
|
|
1117
|
+
* @param {string} [sessionId] - Session identifier for session-scoped path
|
|
1118
|
+
* @returns {{ success: boolean, error?: string }}
|
|
1119
|
+
*/
|
|
1120
|
+
function sessionSave(dir, data, sessionId) {
|
|
1121
|
+
const sessionPath = sessionId
|
|
1122
|
+
? resolveSessionPath(dir, '.session.json', sessionId)
|
|
1123
|
+
: path.join(dir, '.session.json');
|
|
1124
|
+
const tmpPath = sessionPath + '.tmp';
|
|
1125
|
+
try {
|
|
1126
|
+
if (sessionId) ensureSessionDir(dir, sessionId);
|
|
1127
|
+
const existing = sessionLoad(dir, sessionId);
|
|
1128
|
+
const merged = Object.assign(existing, data);
|
|
1129
|
+
fs.writeFileSync(tmpPath, JSON.stringify(merged, null, 2), 'utf8');
|
|
1130
|
+
fs.renameSync(tmpPath, sessionPath);
|
|
1131
|
+
return { success: true };
|
|
1132
|
+
} catch (e) {
|
|
1133
|
+
try { if (fs.existsSync(tmpPath)) fs.unlinkSync(tmpPath); } catch (_) { /* intentionally silent: tmp cleanup is non-fatal */ }
|
|
1134
|
+
return { success: false, error: e.message };
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
/**
|
|
1139
|
+
* Clear session data by removing the .session.json file.
|
|
1140
|
+
*
|
|
1141
|
+
* @param {string} dir - Path to .planning/ directory
|
|
1142
|
+
* @param {string} [sessionId] - Session identifier for session-scoped path
|
|
1143
|
+
* @returns {{ success: boolean, error?: string }}
|
|
1144
|
+
*/
|
|
1145
|
+
function sessionClear(dir, sessionId) {
|
|
1146
|
+
const sessionPath = sessionId
|
|
1147
|
+
? resolveSessionPath(dir, '.session.json', sessionId)
|
|
1148
|
+
: path.join(dir, '.session.json');
|
|
1149
|
+
try {
|
|
1150
|
+
if (fs.existsSync(sessionPath)) fs.unlinkSync(sessionPath);
|
|
1151
|
+
return { success: true };
|
|
1152
|
+
} catch (e) {
|
|
1153
|
+
logHook('core', 'debug', 'Failed to clear session', { error: e.message });
|
|
1154
|
+
return { success: false, error: e.message };
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
/**
|
|
1159
|
+
* Dump all session data as a JSON object for debugging.
|
|
1160
|
+
*
|
|
1161
|
+
* @param {string} dir - Path to .planning/ directory
|
|
1162
|
+
* @param {string} [sessionId] - Session identifier for session-scoped path
|
|
1163
|
+
* @returns {object} Session data including metadata
|
|
1164
|
+
*/
|
|
1165
|
+
function sessionDump(dir, sessionId) {
|
|
1166
|
+
const data = sessionLoad(dir, sessionId);
|
|
1167
|
+
const sessionPath = sessionId
|
|
1168
|
+
? resolveSessionPath(dir, '.session.json', sessionId)
|
|
1169
|
+
: path.join(dir, '.session.json');
|
|
1170
|
+
return {
|
|
1171
|
+
path: sessionPath,
|
|
1172
|
+
exists: fs.existsSync(sessionPath),
|
|
1173
|
+
data,
|
|
1174
|
+
keys: Object.keys(data)
|
|
1175
|
+
};
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
/**
|
|
1179
|
+
* Write .active-skill with OS-level mutual exclusion.
|
|
1180
|
+
*
|
|
1181
|
+
* @param {string} pDir - Path to .planning/ directory
|
|
1182
|
+
* @param {string} skillName - Skill name to write
|
|
1183
|
+
* @param {string} [sessionId] - Session identifier for session-scoped path
|
|
1184
|
+
* @returns {{success: boolean, warning?: string}} Result
|
|
1185
|
+
*/
|
|
1186
|
+
function writeActiveSkill(pDir, skillName, sessionId) {
|
|
1187
|
+
const skillFile = sessionId
|
|
1188
|
+
? resolveSessionPath(pDir, '.active-skill', sessionId)
|
|
1189
|
+
: path.join(pDir, '.active-skill');
|
|
1190
|
+
const lockFile = skillFile + '.lock';
|
|
1191
|
+
const staleThresholdMs = 60 * 60 * 1000;
|
|
1192
|
+
|
|
1193
|
+
if (sessionId) ensureSessionDir(pDir, sessionId);
|
|
1194
|
+
|
|
1195
|
+
let lockFd = null;
|
|
1196
|
+
try {
|
|
1197
|
+
lockFd = fs.openSync(lockFile, 'wx');
|
|
1198
|
+
fs.writeSync(lockFd, `${process.pid}`);
|
|
1199
|
+
fs.closeSync(lockFd);
|
|
1200
|
+
lockFd = null;
|
|
1201
|
+
|
|
1202
|
+
let warning = null;
|
|
1203
|
+
if (fs.existsSync(skillFile)) {
|
|
1204
|
+
try {
|
|
1205
|
+
const stats = fs.statSync(skillFile);
|
|
1206
|
+
const ageMs = Date.now() - stats.mtimeMs;
|
|
1207
|
+
if (ageMs < staleThresholdMs) {
|
|
1208
|
+
const existing = fs.readFileSync(skillFile, 'utf8').trim();
|
|
1209
|
+
warning = `.active-skill already set to "${existing}" (${Math.round(ageMs / 60000)}min ago). Overwriting.`;
|
|
1210
|
+
}
|
|
1211
|
+
} catch (_e) { /* intentionally silent: file may have been deleted concurrently */ }
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
fs.writeFileSync(skillFile, skillName, 'utf8');
|
|
1215
|
+
try { sessionSave(pDir, { activeSkill: skillName }, sessionId); } catch (_e) { /* intentionally silent: session save is non-fatal */ }
|
|
1216
|
+
try { fs.unlinkSync(lockFile); } catch (_e) { /* intentionally silent: lock cleanup is non-fatal */ }
|
|
1217
|
+
|
|
1218
|
+
return { success: true, warning };
|
|
1219
|
+
} catch (e) {
|
|
1220
|
+
try { if (lockFd !== null) fs.closeSync(lockFd); } catch (_e) { /* intentionally silent: fd close on error path */ }
|
|
1221
|
+
|
|
1222
|
+
if (e.code === 'EEXIST') {
|
|
1223
|
+
try {
|
|
1224
|
+
const lockStats = fs.statSync(lockFile);
|
|
1225
|
+
const lockAgeMs = Date.now() - lockStats.mtimeMs;
|
|
1226
|
+
if (lockAgeMs > staleThresholdMs) {
|
|
1227
|
+
fs.unlinkSync(lockFile);
|
|
1228
|
+
return writeActiveSkill(pDir, skillName, sessionId);
|
|
1229
|
+
}
|
|
1230
|
+
} catch (_statErr) {
|
|
1231
|
+
// intentionally silent: lock stat failed, retry write
|
|
1232
|
+
return writeActiveSkill(pDir, skillName, sessionId);
|
|
1233
|
+
}
|
|
1234
|
+
return { success: false, warning: `.active-skill.lock held by another process.` };
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
try {
|
|
1238
|
+
fs.writeFileSync(skillFile, skillName, 'utf8');
|
|
1239
|
+
return { success: true, warning: `Lock failed (${e.code}), wrote without lock` };
|
|
1240
|
+
} catch (writeErr) {
|
|
1241
|
+
logHook('core', 'warn', 'Failed to write .active-skill', { error: writeErr.message });
|
|
1242
|
+
return { success: false, warning: `Failed to write .active-skill: ${writeErr.message}` };
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
// ─── Phase claiming ───────────────────────────────────────────────────────────
|
|
1248
|
+
|
|
1249
|
+
/**
|
|
1250
|
+
* Check whether a claim is stale (its session directory no longer exists).
|
|
1251
|
+
*
|
|
1252
|
+
* @param {object} claimData - Parsed .claim JSON (must have session_id)
|
|
1253
|
+
* @param {string} pDir - Path to .planning/ directory
|
|
1254
|
+
* @returns {{ stale: boolean, reason?: string }}
|
|
1255
|
+
*/
|
|
1256
|
+
function isClaimStale(claimData, pDir) {
|
|
1257
|
+
const sessionDir = path.join(pDir, '.sessions', claimData.session_id);
|
|
1258
|
+
if (!fs.existsSync(sessionDir)) {
|
|
1259
|
+
return { stale: true, reason: 'session_dir_missing' };
|
|
1260
|
+
}
|
|
1261
|
+
return { stale: false };
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
/**
|
|
1265
|
+
* Acquire a phase claim for a session. Auto-releases stale claims.
|
|
1266
|
+
*
|
|
1267
|
+
* @param {string} pDir - Path to .planning/ directory
|
|
1268
|
+
* @param {string} phaseDir - Absolute path to the phase directory
|
|
1269
|
+
* @param {string} sessionId - Session identifier
|
|
1270
|
+
* @param {string} skill - Skill name acquiring the claim
|
|
1271
|
+
* @returns {{ acquired: boolean, conflict?: object, auto_released?: object }}
|
|
1272
|
+
*/
|
|
1273
|
+
function acquireClaim(pDir, phaseDir, sessionId, skill) {
|
|
1274
|
+
const claimPath = path.join(phaseDir, '.claim');
|
|
1275
|
+
let autoReleased = null;
|
|
1276
|
+
|
|
1277
|
+
if (fs.existsSync(claimPath)) {
|
|
1278
|
+
try {
|
|
1279
|
+
const existing = JSON.parse(fs.readFileSync(claimPath, 'utf8'));
|
|
1280
|
+
if (existing.session_id !== sessionId) {
|
|
1281
|
+
const staleCheck = isClaimStale(existing, pDir);
|
|
1282
|
+
if (staleCheck.stale) {
|
|
1283
|
+
fs.unlinkSync(claimPath);
|
|
1284
|
+
autoReleased = existing;
|
|
1285
|
+
} else {
|
|
1286
|
+
return {
|
|
1287
|
+
acquired: false,
|
|
1288
|
+
conflict: {
|
|
1289
|
+
session_id: existing.session_id,
|
|
1290
|
+
skill: existing.skill,
|
|
1291
|
+
started: existing.started,
|
|
1292
|
+
pid: existing.pid
|
|
1293
|
+
},
|
|
1294
|
+
auto_released: null
|
|
1295
|
+
};
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
} catch (_e) {
|
|
1299
|
+
try { fs.unlinkSync(claimPath); } catch (_unlinkErr) { /* intentionally silent: claim cleanup */ }
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
const claimData = {
|
|
1304
|
+
session_id: sessionId,
|
|
1305
|
+
skill: skill,
|
|
1306
|
+
started: new Date().toISOString(),
|
|
1307
|
+
pid: process.pid
|
|
1308
|
+
};
|
|
1309
|
+
fs.writeFileSync(claimPath, JSON.stringify(claimData, null, 2), 'utf8');
|
|
1310
|
+
|
|
1311
|
+
return { acquired: true, conflict: null, auto_released: autoReleased };
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
/**
|
|
1315
|
+
* Release a phase claim owned by a specific session.
|
|
1316
|
+
*
|
|
1317
|
+
* @param {string} _pDir - Path to .planning/ directory (unused, for API consistency)
|
|
1318
|
+
* @param {string} phaseDir - Absolute path to the phase directory
|
|
1319
|
+
* @param {string} sessionId - Session identifier
|
|
1320
|
+
* @returns {{ released: boolean, reason?: string, owner?: string }}
|
|
1321
|
+
*/
|
|
1322
|
+
function releaseClaim(_pDir, phaseDir, sessionId) {
|
|
1323
|
+
const claimPath = path.join(phaseDir, '.claim');
|
|
1324
|
+
|
|
1325
|
+
if (!fs.existsSync(claimPath)) {
|
|
1326
|
+
return { released: false, reason: 'no_claim' };
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
try {
|
|
1330
|
+
const claim = JSON.parse(fs.readFileSync(claimPath, 'utf8'));
|
|
1331
|
+
if (claim.session_id !== sessionId) {
|
|
1332
|
+
return { released: false, reason: 'not_owner', owner: claim.session_id };
|
|
1333
|
+
}
|
|
1334
|
+
fs.unlinkSync(claimPath);
|
|
1335
|
+
return { released: true };
|
|
1336
|
+
} catch (_e) {
|
|
1337
|
+
try { fs.unlinkSync(claimPath); } catch (_unlinkErr) { /* intentionally silent: claim cleanup */ }
|
|
1338
|
+
return { released: true };
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
/**
|
|
1343
|
+
* List all active phase claims.
|
|
1344
|
+
*
|
|
1345
|
+
* @param {string} pDir - Path to .planning/ directory
|
|
1346
|
+
* @returns {{ claims: Array<object> }}
|
|
1347
|
+
*/
|
|
1348
|
+
function listClaims(pDir) {
|
|
1349
|
+
const phasesDir = path.join(pDir, 'phases');
|
|
1350
|
+
if (!fs.existsSync(phasesDir)) {
|
|
1351
|
+
return { claims: [] };
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
const results = [];
|
|
1355
|
+
try {
|
|
1356
|
+
const entries = fs.readdirSync(phasesDir, { withFileTypes: true });
|
|
1357
|
+
for (const entry of entries) {
|
|
1358
|
+
if (!entry.isDirectory()) continue;
|
|
1359
|
+
const claimPath = path.join(phasesDir, entry.name, '.claim');
|
|
1360
|
+
if (!fs.existsSync(claimPath)) continue;
|
|
1361
|
+
try {
|
|
1362
|
+
const claimData = JSON.parse(fs.readFileSync(claimPath, 'utf8'));
|
|
1363
|
+
results.push({
|
|
1364
|
+
phase: entry.name,
|
|
1365
|
+
...claimData,
|
|
1366
|
+
stale: isClaimStale(claimData, pDir).stale
|
|
1367
|
+
});
|
|
1368
|
+
} catch (_e) { logHook('core', 'debug', 'Skipping malformed claim file'); }
|
|
1369
|
+
}
|
|
1370
|
+
} catch (_e) { logHook('core', 'debug', 'Failed to list claims'); }
|
|
1371
|
+
|
|
1372
|
+
return { claims: results };
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
/**
|
|
1376
|
+
* Release all claims held by a specific session across all phase directories.
|
|
1377
|
+
*
|
|
1378
|
+
* @param {string} pDir - Path to .planning/ directory
|
|
1379
|
+
* @param {string} sessionId - Session identifier
|
|
1380
|
+
* @returns {{ released: string[] }}
|
|
1381
|
+
*/
|
|
1382
|
+
function releaseSessionClaims(pDir, sessionId) {
|
|
1383
|
+
const phasesDir = path.join(pDir, 'phases');
|
|
1384
|
+
if (!fs.existsSync(phasesDir)) {
|
|
1385
|
+
return { released: [] };
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
const released = [];
|
|
1389
|
+
try {
|
|
1390
|
+
const entries = fs.readdirSync(phasesDir, { withFileTypes: true });
|
|
1391
|
+
for (const entry of entries) {
|
|
1392
|
+
if (!entry.isDirectory()) continue;
|
|
1393
|
+
const claimPath = path.join(phasesDir, entry.name, '.claim');
|
|
1394
|
+
if (!fs.existsSync(claimPath)) continue;
|
|
1395
|
+
try {
|
|
1396
|
+
const claimData = JSON.parse(fs.readFileSync(claimPath, 'utf8'));
|
|
1397
|
+
if (claimData.session_id === sessionId) {
|
|
1398
|
+
fs.unlinkSync(claimPath);
|
|
1399
|
+
released.push(entry.name);
|
|
1400
|
+
}
|
|
1401
|
+
} catch (_e) { logHook('core', 'debug', 'Skipping malformed claim'); }
|
|
1402
|
+
}
|
|
1403
|
+
} catch (_e) { logHook('core', 'debug', 'Failed to release session claims'); }
|
|
1404
|
+
|
|
1405
|
+
return { released };
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
// ─── Config loader (lightweight, used by core only) ───────────────────────────
|
|
1409
|
+
|
|
1410
|
+
function loadConfig(configCwd) {
|
|
1411
|
+
const configPath = path.join(configCwd, '.planning', 'config.json');
|
|
1412
|
+
const defaults = {
|
|
1413
|
+
model_profile: 'balanced',
|
|
1414
|
+
commit_docs: true,
|
|
1415
|
+
search_gitignored: false,
|
|
1416
|
+
branching_strategy: 'none',
|
|
1417
|
+
phase_branch_template: 'pbr/phase-{phase}-{slug}',
|
|
1418
|
+
milestone_branch_template: 'pbr/{milestone}-{slug}',
|
|
1419
|
+
research: true,
|
|
1420
|
+
plan_checker: true,
|
|
1421
|
+
verifier: true,
|
|
1422
|
+
nyquist_validation: true,
|
|
1423
|
+
parallelization: true,
|
|
1424
|
+
brave_search: false,
|
|
1425
|
+
};
|
|
1426
|
+
|
|
1427
|
+
try {
|
|
1428
|
+
const raw = fs.readFileSync(configPath, 'utf8');
|
|
1429
|
+
const parsed = JSON.parse(raw);
|
|
1430
|
+
|
|
1431
|
+
if ('depth' in parsed && !('granularity' in parsed)) {
|
|
1432
|
+
const depthToGranularity = { quick: 'coarse', standard: 'standard', comprehensive: 'fine' };
|
|
1433
|
+
parsed.granularity = depthToGranularity[parsed.depth] || parsed.depth;
|
|
1434
|
+
delete parsed.depth;
|
|
1435
|
+
try { fs.writeFileSync(configPath, JSON.stringify(parsed, null, 2), 'utf8'); } catch (e) { logHook('core', 'debug', 'Failed to write migrated config', { error: e.message }); }
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
const get = (key, nested) => {
|
|
1439
|
+
if (parsed[key] !== undefined) return parsed[key];
|
|
1440
|
+
if (nested && parsed[nested.section] && parsed[nested.section][nested.field] !== undefined) {
|
|
1441
|
+
return parsed[nested.section][nested.field];
|
|
1442
|
+
}
|
|
1443
|
+
return undefined;
|
|
1444
|
+
};
|
|
1445
|
+
|
|
1446
|
+
const parallelization = (() => {
|
|
1447
|
+
const val = get('parallelization');
|
|
1448
|
+
if (typeof val === 'boolean') return val;
|
|
1449
|
+
if (typeof val === 'object' && val !== null && 'enabled' in val) return val.enabled;
|
|
1450
|
+
return defaults.parallelization;
|
|
1451
|
+
})();
|
|
1452
|
+
|
|
1453
|
+
return {
|
|
1454
|
+
model_profile: get('model_profile') ?? defaults.model_profile,
|
|
1455
|
+
commit_docs: get('commit_docs', { section: 'planning', field: 'commit_docs' }) ?? defaults.commit_docs,
|
|
1456
|
+
search_gitignored: get('search_gitignored', { section: 'planning', field: 'search_gitignored' }) ?? defaults.search_gitignored,
|
|
1457
|
+
branching_strategy: get('branching_strategy', { section: 'git', field: 'branching_strategy' }) ?? defaults.branching_strategy,
|
|
1458
|
+
phase_branch_template: get('phase_branch_template', { section: 'git', field: 'phase_branch_template' }) ?? defaults.phase_branch_template,
|
|
1459
|
+
milestone_branch_template: get('milestone_branch_template', { section: 'git', field: 'milestone_branch_template' }) ?? defaults.milestone_branch_template,
|
|
1460
|
+
research: get('research', { section: 'workflow', field: 'research' }) ?? defaults.research,
|
|
1461
|
+
plan_checker: get('plan_checker', { section: 'workflow', field: 'plan_check' }) ?? defaults.plan_checker,
|
|
1462
|
+
verifier: get('verifier', { section: 'workflow', field: 'verifier' }) ?? defaults.verifier,
|
|
1463
|
+
nyquist_validation: get('nyquist_validation', { section: 'workflow', field: 'nyquist_validation' }) ?? defaults.nyquist_validation,
|
|
1464
|
+
parallelization,
|
|
1465
|
+
brave_search: get('brave_search') ?? defaults.brave_search,
|
|
1466
|
+
model_overrides: parsed.model_overrides || null,
|
|
1467
|
+
};
|
|
1468
|
+
} catch (e) {
|
|
1469
|
+
logHook('core', 'debug', 'Failed to load config, using defaults', { error: e.message });
|
|
1470
|
+
return defaults;
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
/**
|
|
1475
|
+
* Check if a path is git-ignored, scoped to a cwd.
|
|
1476
|
+
*
|
|
1477
|
+
* @param {string} igCwd - Working directory
|
|
1478
|
+
* @param {string} targetPath - Path to check
|
|
1479
|
+
* @returns {boolean}
|
|
1480
|
+
*/
|
|
1481
|
+
function pathExistsInternal(peCwd, targetPath) {
|
|
1482
|
+
const fullPath = path.isAbsolute(targetPath) ? targetPath : path.join(peCwd, targetPath);
|
|
1483
|
+
try {
|
|
1484
|
+
fs.statSync(fullPath);
|
|
1485
|
+
return true;
|
|
1486
|
+
} catch {
|
|
1487
|
+
// intentionally silent: path existence check
|
|
1488
|
+
return false;
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
function resolveModelInternal(rmCwd, agentType) {
|
|
1493
|
+
const config = loadConfig(rmCwd);
|
|
1494
|
+
return resolveModel(agentType, config);
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
// ─── Exports ──────────────────────────────────────────────────────────────────
|
|
1498
|
+
|
|
1499
|
+
module.exports = {
|
|
1500
|
+
// Module-level state
|
|
1501
|
+
setCwd,
|
|
1502
|
+
|
|
1503
|
+
// Constants
|
|
1504
|
+
KNOWN_AGENTS,
|
|
1505
|
+
VALID_STATUS_TRANSITIONS,
|
|
1506
|
+
STATUS_LABELS,
|
|
1507
|
+
MODEL_PROFILES,
|
|
1508
|
+
SESSION_ALLOWED_KEYS,
|
|
1509
|
+
STALE_SESSION_MS,
|
|
1510
|
+
|
|
1511
|
+
// Status transitions
|
|
1512
|
+
validateStatusTransition,
|
|
1513
|
+
|
|
1514
|
+
// Output
|
|
1515
|
+
output,
|
|
1516
|
+
error,
|
|
1517
|
+
|
|
1518
|
+
// Path & file utilities
|
|
1519
|
+
toPosixPath,
|
|
1520
|
+
safeReadFile,
|
|
1521
|
+
ensureDir,
|
|
1522
|
+
findFiles,
|
|
1523
|
+
tailLines,
|
|
1524
|
+
escapeRegex,
|
|
1525
|
+
|
|
1526
|
+
// Git utilities
|
|
1527
|
+
execGit,
|
|
1528
|
+
isGitIgnored,
|
|
1529
|
+
|
|
1530
|
+
// YAML frontmatter
|
|
1531
|
+
parseYamlFrontmatter,
|
|
1532
|
+
parseMustHaves,
|
|
1533
|
+
setYamlFrontmatter,
|
|
1534
|
+
|
|
1535
|
+
// Misc utilities
|
|
1536
|
+
countMustHaves,
|
|
1537
|
+
determinePhaseStatus,
|
|
1538
|
+
calculateProgress,
|
|
1539
|
+
currentTimestamp,
|
|
1540
|
+
generateSlug,
|
|
1541
|
+
resolveModel,
|
|
1542
|
+
|
|
1543
|
+
// Phase utilities
|
|
1544
|
+
normalizePhaseName,
|
|
1545
|
+
comparePhaseNum,
|
|
1546
|
+
searchPhaseInDir,
|
|
1547
|
+
findPhaseInternal,
|
|
1548
|
+
getArchivedPhaseDirs,
|
|
1549
|
+
|
|
1550
|
+
// Roadmap & milestone
|
|
1551
|
+
getRoadmapPhaseInternal,
|
|
1552
|
+
getMilestoneInfo,
|
|
1553
|
+
getMilestonePhaseFilter,
|
|
1554
|
+
|
|
1555
|
+
// Config loader (lightweight)
|
|
1556
|
+
loadConfig,
|
|
1557
|
+
pathExistsInternal,
|
|
1558
|
+
resolveModelInternal,
|
|
1559
|
+
generateSlugInternal: generateSlug,
|
|
1560
|
+
|
|
1561
|
+
// Atomic operations
|
|
1562
|
+
atomicWrite,
|
|
1563
|
+
lockedFileUpdate,
|
|
1564
|
+
|
|
1565
|
+
// Schema validation
|
|
1566
|
+
validateObject,
|
|
1567
|
+
|
|
1568
|
+
// Session management
|
|
1569
|
+
resolveSessionPath,
|
|
1570
|
+
ensureSessionDir,
|
|
1571
|
+
removeSessionDir,
|
|
1572
|
+
cleanStaleSessions,
|
|
1573
|
+
sessionLoad,
|
|
1574
|
+
sessionSave,
|
|
1575
|
+
sessionClear,
|
|
1576
|
+
sessionDump,
|
|
1577
|
+
writeActiveSkill,
|
|
1578
|
+
|
|
1579
|
+
// Phase claiming
|
|
1580
|
+
isClaimStale,
|
|
1581
|
+
acquireClaim,
|
|
1582
|
+
releaseClaim,
|
|
1583
|
+
listClaims,
|
|
1584
|
+
releaseSessionClaims,
|
|
1585
|
+
};
|