@sienklogic/plan-build-run 2.12.0 → 2.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1102 -234
- package/CLAUDE.md +54 -34
- package/LICENSE +2 -1
- package/README.md +261 -182
- package/agents/pbr-audit.md +266 -0
- package/agents/pbr-codebase-mapper.md +236 -0
- package/agents/pbr-debugger.md +312 -0
- package/agents/pbr-dev-sync.md +220 -0
- package/agents/pbr-executor.md +591 -0
- package/agents/pbr-general.md +191 -0
- package/agents/pbr-integration-checker.md +237 -0
- package/agents/pbr-intel-updater.md +296 -0
- package/agents/pbr-nyquist-auditor.md +252 -0
- package/agents/pbr-plan-checker.md +312 -0
- package/agents/pbr-planner.md +539 -0
- package/agents/pbr-researcher.md +314 -0
- package/agents/pbr-roadmapper.md +346 -0
- package/agents/pbr-synthesizer.md +271 -0
- package/agents/pbr-ui-checker.md +202 -0
- package/agents/pbr-ui-researcher.md +223 -0
- package/agents/pbr-verifier.md +495 -0
- package/bin/install.js +2752 -0
- package/commands/pbr/add-phase.md +75 -0
- package/commands/pbr/add-todo.md +8 -0
- package/commands/pbr/audit-milestone.md +8 -0
- package/commands/pbr/audit.md +5 -0
- package/commands/pbr/autonomous.md +5 -0
- package/commands/pbr/begin.md +5 -0
- package/commands/pbr/build.md +5 -0
- package/commands/pbr/check-todos.md +8 -0
- package/commands/pbr/complete-milestone.md +8 -0
- package/commands/pbr/config.md +5 -0
- package/commands/pbr/continue.md +5 -0
- package/commands/pbr/dashboard.md +5 -0
- package/commands/pbr/debug.md +5 -0
- package/commands/pbr/discuss-phase.md +6 -0
- package/commands/pbr/discuss.md +5 -0
- package/commands/pbr/do.md +5 -0
- package/commands/pbr/execute-phase.md +6 -0
- package/commands/pbr/explore.md +5 -0
- package/commands/pbr/health.md +5 -0
- package/commands/pbr/help.md +5 -0
- package/commands/pbr/import.md +5 -0
- package/commands/pbr/insert-phase.md +65 -0
- package/commands/pbr/intel.md +5 -0
- package/commands/pbr/join-discord.md +11 -0
- package/commands/pbr/list-phase-assumptions.md +69 -0
- package/commands/pbr/map-codebase.md +6 -0
- package/commands/pbr/milestone.md +5 -0
- package/commands/pbr/new-milestone.md +8 -0
- package/commands/pbr/new-project.md +6 -0
- package/commands/pbr/note.md +5 -0
- package/commands/pbr/pause-work.md +5 -0
- package/commands/pbr/pause.md +5 -0
- package/commands/pbr/plan-milestone-gaps.md +7 -0
- package/commands/pbr/plan-phase.md +6 -0
- package/commands/pbr/plan.md +5 -0
- package/commands/pbr/profile-user.md +5 -0
- package/commands/pbr/profile.md +5 -0
- package/commands/pbr/progress.md +6 -0
- package/commands/pbr/quick.md +5 -0
- package/commands/pbr/reapply-patches.md +47 -0
- package/commands/pbr/release.md +6 -0
- package/commands/pbr/remove-phase.md +66 -0
- package/commands/pbr/research-phase.md +59 -0
- package/commands/pbr/resume-work.md +5 -0
- package/commands/pbr/resume.md +5 -0
- package/commands/pbr/review.md +5 -0
- package/commands/pbr/scan.md +5 -0
- package/commands/pbr/session-report.md +5 -0
- package/commands/pbr/set-profile.md +6 -0
- package/commands/pbr/settings.md +5 -0
- package/commands/pbr/setup.md +5 -0
- package/commands/pbr/ship.md +5 -0
- package/commands/pbr/status.md +5 -0
- package/commands/pbr/statusline.md +5 -0
- package/commands/pbr/test.md +5 -0
- package/commands/pbr/todo.md +5 -0
- package/commands/pbr/ui-phase.md +5 -0
- package/commands/pbr/ui-review.md +5 -0
- package/commands/pbr/undo.md +5 -0
- package/commands/pbr/update.md +37 -0
- package/commands/pbr/validate-phase.md +5 -0
- package/commands/pbr/verify-work.md +6 -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 +28 -23
- package/dashboard/server/index.js +136 -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 +213 -0
- package/dashboard/server/routes/config.js +64 -0
- package/dashboard/server/routes/health.js +95 -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/status.js +25 -0
- package/dashboard/server/routes/telemetry.js +171 -0
- package/dashboard/server/services/file-watcher.js +105 -0
- package/dashboard/server/services/planning-reader.js +741 -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 +154 -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 +18 -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/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/RoadmapPage.jsx +251 -0
- package/dashboard/src/pages/Telemetry.jsx +113 -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/hooks/dist/auto-continue.js +277 -0
- package/hooks/dist/block-skill-self-read.js +80 -0
- package/hooks/dist/check-agent-state-write.js +63 -0
- package/hooks/dist/check-config-change.js +188 -0
- package/hooks/dist/check-dangerous-commands.js +185 -0
- package/hooks/dist/check-doc-sprawl.js +102 -0
- package/hooks/dist/check-phase-boundary.js +191 -0
- package/hooks/dist/check-plan-format.js +227 -0
- package/hooks/dist/check-roadmap-sync.js +503 -0
- package/hooks/dist/check-skill-workflow.js +354 -0
- package/hooks/dist/check-state-sync.js +637 -0
- package/hooks/dist/check-subagent-output.js +401 -0
- package/hooks/dist/check-summary-gate.js +199 -0
- package/hooks/dist/context-bridge.js +406 -0
- package/hooks/dist/context-budget-check.js +442 -0
- package/hooks/dist/context-quality.js +271 -0
- package/hooks/dist/enforce-pbr-workflow.js +277 -0
- package/hooks/dist/event-handler.js +203 -0
- package/hooks/dist/event-logger.js +125 -0
- package/hooks/dist/hook-logger.js +112 -0
- package/hooks/dist/hook-server-client.js +342 -0
- package/hooks/dist/hook-server.js +352 -0
- package/hooks/dist/hooks-schema.json +85 -0
- package/hooks/dist/hooks.json +309 -0
- package/hooks/dist/instructions-loaded.js +107 -0
- package/hooks/dist/intel-queue.js +152 -0
- package/hooks/dist/intercept-plan-mode.js +50 -0
- package/hooks/dist/log-notification.js +125 -0
- package/hooks/dist/log-subagent.js +306 -0
- package/hooks/dist/log-tool-failure.js +140 -0
- package/hooks/dist/milestone-learnings.js +569 -0
- package/hooks/dist/pbr-tools.js +5 -0
- package/hooks/dist/post-bash-triage.js +152 -0
- package/hooks/dist/post-compact.js +135 -0
- package/hooks/dist/post-write-dispatch.js +277 -0
- package/hooks/dist/post-write-quality.js +208 -0
- package/hooks/dist/pre-bash-dispatch.js +158 -0
- package/hooks/dist/pre-write-dispatch.js +165 -0
- package/hooks/dist/progress-tracker.js +198 -0
- package/hooks/dist/prompt-routing.js +209 -0
- package/hooks/dist/run-hook.js +144 -0
- package/hooks/dist/session-cleanup.js +617 -0
- package/hooks/dist/session-tracker.js +124 -0
- package/hooks/dist/status-line.js +793 -0
- package/hooks/dist/suggest-compact.js +296 -0
- package/hooks/dist/sync-context-to-claude.js +100 -0
- package/hooks/dist/task-completed.js +206 -0
- package/hooks/dist/track-context-budget.js +405 -0
- package/hooks/dist/trust-tracker.js +193 -0
- package/hooks/dist/validate-commit.js +270 -0
- package/hooks/dist/validate-skill-args.js +222 -0
- package/hooks/dist/validate-task.js +272 -0
- package/hooks/dist/worktree-create.js +144 -0
- package/hooks/dist/worktree-remove.js +147 -0
- package/package.json +59 -40
- package/plan-build-run/bin/config-schema.json +1416 -0
- package/plan-build-run/bin/dashboard-launch.cjs +114 -0
- package/plan-build-run/bin/event-logger.cjs +92 -0
- package/plan-build-run/bin/lib/alternatives.cjs +198 -0
- package/plan-build-run/bin/lib/auto-cleanup.cjs +7 -0
- package/plan-build-run/bin/lib/build.cjs +717 -0
- package/plan-build-run/bin/lib/circuit-state.cjs +133 -0
- package/plan-build-run/bin/lib/commands.cjs +482 -0
- package/plan-build-run/bin/lib/config.cjs +770 -0
- package/plan-build-run/bin/lib/context.cjs +216 -0
- package/plan-build-run/bin/lib/contextual-help.cjs +207 -0
- package/plan-build-run/bin/lib/core.cjs +1563 -0
- package/plan-build-run/bin/lib/decisions.cjs +194 -0
- package/plan-build-run/bin/lib/frontmatter.cjs +299 -0
- package/plan-build-run/bin/lib/gates/advisories.cjs +129 -0
- package/plan-build-run/bin/lib/gates/build-dependency.cjs +115 -0
- package/plan-build-run/bin/lib/gates/build-executor.cjs +104 -0
- package/plan-build-run/bin/lib/gates/doc-existence.cjs +46 -0
- package/plan-build-run/bin/lib/gates/helpers.cjs +93 -0
- package/plan-build-run/bin/lib/gates/inline-execution.cjs +185 -0
- package/plan-build-run/bin/lib/gates/milestone-complete.cjs +136 -0
- package/plan-build-run/bin/lib/gates/milestone-summary.cjs +119 -0
- package/plan-build-run/bin/lib/gates/plan-executor.cjs +36 -0
- package/plan-build-run/bin/lib/gates/quick-executor.cjs +76 -0
- package/plan-build-run/bin/lib/gates/review-planner.cjs +61 -0
- package/plan-build-run/bin/lib/gates/review-verifier.cjs +69 -0
- package/plan-build-run/bin/lib/graph-cli.cjs +89 -0
- package/plan-build-run/bin/lib/graph.cjs +554 -0
- package/plan-build-run/bin/lib/health-phase06.cjs +120 -0
- package/plan-build-run/bin/lib/health.cjs +133 -0
- package/plan-build-run/bin/lib/history.cjs +147 -0
- package/plan-build-run/bin/lib/hypothesis-runner.cjs +127 -0
- package/plan-build-run/bin/lib/impact-analysis.cjs +319 -0
- package/plan-build-run/bin/lib/incidents.cjs +190 -0
- package/plan-build-run/bin/lib/init.cjs +367 -0
- package/plan-build-run/bin/lib/intel.cjs +653 -0
- package/plan-build-run/bin/lib/learnings.cjs +511 -0
- package/plan-build-run/bin/lib/local-llm/health.cjs +12 -0
- package/plan-build-run/bin/lib/local-llm/index.cjs +89 -0
- package/plan-build-run/bin/lib/local-llm/metrics.cjs +20 -0
- package/plan-build-run/bin/lib/local-llm/operations/classify-artifact.cjs +4 -0
- package/plan-build-run/bin/lib/local-llm/operations/classify-commit.cjs +4 -0
- package/plan-build-run/bin/lib/local-llm/operations/classify-error.cjs +4 -0
- package/plan-build-run/bin/lib/local-llm/operations/classify-file-intent.cjs +4 -0
- package/plan-build-run/bin/lib/local-llm/operations/triage-test-output.cjs +12 -0
- package/plan-build-run/bin/lib/local-llm/operations/validate-task.cjs +4 -0
- package/plan-build-run/bin/lib/migrate.cjs +298 -0
- package/plan-build-run/bin/lib/milestone.cjs +306 -0
- package/plan-build-run/bin/lib/negative-knowledge.cjs +194 -0
- package/plan-build-run/bin/lib/onboarding-generator.cjs +288 -0
- package/plan-build-run/bin/lib/parse-args.cjs +134 -0
- package/plan-build-run/bin/lib/patterns.cjs +272 -0
- package/plan-build-run/bin/lib/phase.cjs +1021 -0
- package/plan-build-run/bin/lib/post-hoc.cjs +160 -0
- package/plan-build-run/bin/lib/preview.cjs +174 -0
- package/plan-build-run/bin/lib/progress-visualization.cjs +296 -0
- package/plan-build-run/bin/lib/quick-init.cjs +131 -0
- package/plan-build-run/bin/lib/reference.cjs +234 -0
- package/plan-build-run/bin/lib/requirements.cjs +153 -0
- package/plan-build-run/bin/lib/reverse-spec.cjs +259 -0
- package/plan-build-run/bin/lib/roadmap.cjs +1097 -0
- package/plan-build-run/bin/lib/security-scan.cjs +200 -0
- package/plan-build-run/bin/lib/skill-section.cjs +98 -0
- package/plan-build-run/bin/lib/spec-diff.cjs +209 -0
- package/plan-build-run/bin/lib/spec-engine.cjs +189 -0
- package/plan-build-run/bin/lib/spot-check.cjs +510 -0
- package/plan-build-run/bin/lib/state.cjs +1050 -0
- package/plan-build-run/bin/lib/status-render.cjs +527 -0
- package/plan-build-run/bin/lib/step-verify.cjs +149 -0
- package/plan-build-run/bin/lib/suggest-next.cjs +316 -0
- package/plan-build-run/bin/lib/team-composer.cjs +85 -0
- package/plan-build-run/bin/lib/team-coordinator.cjs +151 -0
- package/plan-build-run/bin/lib/template.cjs +222 -0
- package/plan-build-run/bin/lib/templates.cjs +362 -0
- package/plan-build-run/bin/lib/test-selection.cjs +163 -0
- package/plan-build-run/bin/lib/todo.cjs +300 -0
- package/plan-build-run/bin/lib/validation.cjs +187 -0
- package/plan-build-run/bin/lib/verify.cjs +1451 -0
- package/plan-build-run/bin/pbr-tools.cjs +1877 -0
- package/plan-build-run/references/CLAUDE.md +7 -0
- package/plan-build-run/references/agent-contracts.md +326 -0
- package/plan-build-run/references/agent-teams.md +54 -0
- package/plan-build-run/references/behavioral-contexts.md +53 -0
- package/plan-build-run/references/checkpoints.md +776 -0
- package/plan-build-run/references/config-reference.md +613 -0
- package/plan-build-run/references/continuation-format.md +249 -0
- package/plan-build-run/references/debugging/CLAUDE.md +7 -0
- package/plan-build-run/references/decimal-phase-calculation.md +65 -0
- package/plan-build-run/references/git-integration.md +309 -0
- package/plan-build-run/references/git-planning-commit.md +38 -0
- package/plan-build-run/references/model-profile-resolution.md +34 -0
- package/plan-build-run/references/model-profiles.md +182 -0
- package/plan-build-run/references/model-selection.md +53 -0
- package/plan-build-run/references/phase-argument-parsing.md +61 -0
- package/plan-build-run/references/plan-authoring.md +246 -0
- package/plan-build-run/references/plan-format.md +351 -0
- package/plan-build-run/references/planning-config.md +200 -0
- package/plan-build-run/references/questioning.md +162 -0
- package/plan-build-run/references/reading-verification.md +127 -0
- package/plan-build-run/references/stub-patterns.md +160 -0
- package/plan-build-run/references/tdd.md +263 -0
- package/plan-build-run/references/ui-brand.md +187 -0
- package/plan-build-run/references/verification-overrides.md +38 -0
- package/plan-build-run/references/verification-patterns.md +612 -0
- package/plan-build-run/references/wave-execution.md +52 -0
- package/plan-build-run/skills/audit/SKILL.md +347 -0
- package/plan-build-run/skills/autonomous/SKILL.md +460 -0
- package/plan-build-run/skills/begin/SKILL.md +926 -0
- package/plan-build-run/skills/begin/templates/PROJECT.md.tmpl +33 -0
- package/plan-build-run/skills/begin/templates/REQUIREMENTS.md.tmpl +18 -0
- package/plan-build-run/skills/begin/templates/STATE.md.tmpl +48 -0
- package/plan-build-run/skills/begin/templates/config.json.tmpl +451 -0
- package/plan-build-run/skills/begin/templates/project-CONTEXT.md.tmpl +19 -0
- package/plan-build-run/skills/begin/templates/researcher-prompt.md.tmpl +47 -0
- package/plan-build-run/skills/begin/templates/roadmap-prompt.md.tmpl +49 -0
- package/plan-build-run/skills/begin/templates/synthesis-prompt.md.tmpl +44 -0
- package/plan-build-run/skills/build/SKILL.md +1655 -0
- package/plan-build-run/skills/build/templates/continuation-prompt.md.tmpl +26 -0
- package/plan-build-run/skills/build/templates/executor-prompt.md.tmpl +70 -0
- package/plan-build-run/skills/build/templates/inline-verifier-prompt.md.tmpl +33 -0
- package/plan-build-run/skills/config/SKILL.md +357 -0
- package/plan-build-run/skills/continue/SKILL.md +266 -0
- package/plan-build-run/skills/dashboard/SKILL.md +12 -0
- package/plan-build-run/skills/debug/SKILL.md +573 -0
- package/plan-build-run/skills/debug/templates/continuation-prompt.md.tmpl +27 -0
- package/plan-build-run/skills/debug/templates/initial-investigation-prompt.md.tmpl +34 -0
- package/plan-build-run/skills/discuss/SKILL.md +489 -0
- package/plan-build-run/skills/discuss/templates/CONTEXT.md.tmpl +61 -0
- package/plan-build-run/skills/discuss/templates/decision-categories.md +9 -0
- package/plan-build-run/skills/discuss/templates/project-CONTEXT.md.tmpl +19 -0
- package/plan-build-run/skills/do/SKILL.md +165 -0
- package/plan-build-run/skills/explore/SKILL.md +449 -0
- package/plan-build-run/skills/health/SKILL.md +332 -0
- package/plan-build-run/skills/health/templates/check-pattern.md.tmpl +30 -0
- package/plan-build-run/skills/health/templates/output-format.md.tmpl +63 -0
- package/plan-build-run/skills/help/SKILL.md +236 -0
- package/plan-build-run/skills/import/SKILL.md +827 -0
- package/plan-build-run/skills/intel/SKILL.md +131 -0
- package/plan-build-run/skills/milestone/SKILL.md +825 -0
- package/plan-build-run/skills/milestone/templates/audit-output.md.tmpl +76 -0
- package/plan-build-run/skills/milestone/templates/complete-output.md.tmpl +32 -0
- package/plan-build-run/skills/milestone/templates/edge-cases.md +54 -0
- package/plan-build-run/skills/milestone/templates/gaps-output.md.tmpl +25 -0
- package/plan-build-run/skills/milestone/templates/integration-checker-prompt.md.tmpl +25 -0
- package/plan-build-run/skills/milestone/templates/new-output.md.tmpl +29 -0
- package/plan-build-run/skills/milestone/templates/stats-file.md.tmpl +30 -0
- package/plan-build-run/skills/note/SKILL.md +221 -0
- package/plan-build-run/skills/pause/SKILL.md +259 -0
- package/plan-build-run/skills/pause/templates/continue-here.md.tmpl +71 -0
- package/plan-build-run/skills/plan/SKILL.md +852 -0
- package/plan-build-run/skills/plan/decimal-phase-calc.md +98 -0
- package/plan-build-run/skills/plan/templates/checker-prompt.md.tmpl +21 -0
- package/plan-build-run/skills/plan/templates/completion-output.md.tmpl +27 -0
- package/plan-build-run/skills/plan/templates/gap-closure-prompt.md.tmpl +32 -0
- package/plan-build-run/skills/plan/templates/planner-prompt.md.tmpl +38 -0
- package/plan-build-run/skills/plan/templates/prompt-partials/phase-project-context.md.tmpl +21 -0
- package/plan-build-run/skills/plan/templates/researcher-prompt.md.tmpl +19 -0
- package/plan-build-run/skills/plan/templates/revision-prompt.md.tmpl +23 -0
- package/plan-build-run/skills/profile/SKILL.md +173 -0
- package/plan-build-run/skills/profile-user/SKILL.md +220 -0
- package/plan-build-run/skills/quick/SKILL.md +727 -0
- package/plan-build-run/skills/release/SKILL.md +206 -0
- package/plan-build-run/skills/resume/SKILL.md +499 -0
- package/plan-build-run/skills/review/SKILL.md +763 -0
- package/plan-build-run/skills/review/templates/debugger-prompt.md.tmpl +60 -0
- package/plan-build-run/skills/review/templates/gap-planner-prompt.md.tmpl +40 -0
- package/plan-build-run/skills/review/templates/verifier-prompt.md.tmpl +115 -0
- package/plan-build-run/skills/scan/SKILL.md +330 -0
- package/plan-build-run/skills/scan/templates/mapper-prompt.md.tmpl +201 -0
- package/plan-build-run/skills/session-report/SKILL.md +128 -0
- package/plan-build-run/skills/setup/SKILL.md +246 -0
- package/plan-build-run/skills/shared/agent-type-resolution.md +20 -0
- package/plan-build-run/skills/shared/commit-planning-docs.md +43 -0
- package/plan-build-run/skills/shared/config-loading.md +102 -0
- package/plan-build-run/skills/shared/context-budget.md +105 -0
- package/plan-build-run/skills/shared/context-loader-task.md +91 -0
- package/plan-build-run/skills/shared/digest-select.md +79 -0
- package/plan-build-run/skills/shared/domain-probes.md +125 -0
- package/plan-build-run/skills/shared/error-reporting.md +59 -0
- package/plan-build-run/skills/shared/gate-prompts.md +390 -0
- package/plan-build-run/skills/shared/phase-argument-parsing.md +45 -0
- package/plan-build-run/skills/shared/revision-loop.md +81 -0
- package/plan-build-run/skills/shared/state-update.md +154 -0
- package/plan-build-run/skills/shared/universal-anti-patterns.md +59 -0
- package/plan-build-run/skills/ship/SKILL.md +154 -0
- package/plan-build-run/skills/status/SKILL.md +520 -0
- package/plan-build-run/skills/statusline/SKILL.md +151 -0
- package/plan-build-run/skills/test/SKILL.md +254 -0
- package/plan-build-run/skills/todo/SKILL.md +285 -0
- package/plan-build-run/skills/ui-phase/SKILL.md +177 -0
- package/plan-build-run/skills/ui-review/SKILL.md +204 -0
- package/plan-build-run/skills/undo/SKILL.md +216 -0
- package/plan-build-run/skills/validate-phase/SKILL.md +358 -0
- package/plan-build-run/templates/CLAUDE.md +7 -0
- package/plan-build-run/templates/DEBUG.md +164 -0
- package/plan-build-run/templates/UAT.md +247 -0
- package/plan-build-run/templates/VALIDATION.md +76 -0
- package/plan-build-run/templates/codebase/architecture.md +255 -0
- package/plan-build-run/templates/codebase/concerns.md +310 -0
- package/plan-build-run/templates/codebase/conventions.md +307 -0
- package/plan-build-run/templates/codebase/integrations.md +280 -0
- package/plan-build-run/templates/codebase/stack.md +186 -0
- package/plan-build-run/templates/codebase/structure.md +285 -0
- package/plan-build-run/templates/codebase/testing.md +480 -0
- package/plan-build-run/templates/config.json +37 -0
- package/plan-build-run/templates/context.md +297 -0
- package/plan-build-run/templates/continue-here.md +78 -0
- package/plan-build-run/templates/crud-flow-verification.md +277 -0
- package/plan-build-run/templates/debug-subagent-prompt.md +91 -0
- package/plan-build-run/templates/deferred-items.md +19 -0
- package/plan-build-run/templates/discovery.md +146 -0
- package/plan-build-run/templates/milestone-archive.md +123 -0
- package/plan-build-run/templates/milestone.md +115 -0
- package/plan-build-run/templates/phase-prompt.md +569 -0
- package/plan-build-run/templates/planner-subagent-prompt.md +117 -0
- package/plan-build-run/templates/project.md +184 -0
- package/plan-build-run/templates/requirements.md +231 -0
- package/plan-build-run/templates/research-outputs/ARCHITECTURE.md.tmpl +86 -0
- package/plan-build-run/templates/research-outputs/FEATURES.md.tmpl +77 -0
- package/plan-build-run/templates/research-outputs/PITFALLS.md.tmpl +65 -0
- package/plan-build-run/templates/research-outputs/STACK.md.tmpl +80 -0
- package/plan-build-run/templates/research-project/ARCHITECTURE.md +204 -0
- package/plan-build-run/templates/research-project/FEATURES.md +147 -0
- package/plan-build-run/templates/research-project/PITFALLS.md +200 -0
- package/plan-build-run/templates/research-project/STACK.md +120 -0
- package/plan-build-run/templates/research-project/SUMMARY.md +170 -0
- package/plan-build-run/templates/research.md +552 -0
- package/plan-build-run/templates/retrospective.md +54 -0
- package/plan-build-run/templates/roadmap.md +202 -0
- package/plan-build-run/templates/seed.md +16 -0
- package/plan-build-run/templates/state.md +176 -0
- package/plan-build-run/templates/summary-complex.md +59 -0
- package/plan-build-run/templates/summary-minimal.md +41 -0
- package/plan-build-run/templates/summary-standard.md +48 -0
- package/plan-build-run/templates/summary.md +248 -0
- package/plan-build-run/templates/user-setup.md +311 -0
- package/plan-build-run/templates/verification-report.md +322 -0
- package/plan-build-run/workflows/add-phase.md +111 -0
- package/plan-build-run/workflows/add-todo.md +157 -0
- package/plan-build-run/workflows/audit-milestone.md +241 -0
- package/plan-build-run/workflows/check-todos.md +176 -0
- package/plan-build-run/workflows/complete-milestone.md +644 -0
- package/plan-build-run/workflows/diagnose-issues.md +219 -0
- package/plan-build-run/workflows/discovery-phase.md +289 -0
- package/plan-build-run/workflows/discuss-phase.md +429 -0
- package/plan-build-run/workflows/execute-phase.md +439 -0
- package/plan-build-run/workflows/execute-plan.md +437 -0
- package/plan-build-run/workflows/explore.md +150 -0
- package/plan-build-run/workflows/help.md +470 -0
- package/plan-build-run/workflows/insert-phase.md +129 -0
- package/plan-build-run/workflows/list-phase-assumptions.md +178 -0
- package/plan-build-run/workflows/map-codebase.md +327 -0
- package/plan-build-run/workflows/new-milestone.md +373 -0
- package/plan-build-run/workflows/new-project.md +1009 -0
- package/plan-build-run/workflows/note.md +90 -0
- package/plan-build-run/workflows/pause-work.md +122 -0
- package/plan-build-run/workflows/plan-milestone-gaps.md +256 -0
- package/plan-build-run/workflows/plan-phase.md +376 -0
- package/plan-build-run/workflows/progress.md +431 -0
- package/plan-build-run/workflows/quick.md +230 -0
- package/plan-build-run/workflows/remove-phase.md +154 -0
- package/plan-build-run/workflows/research-phase.md +74 -0
- package/plan-build-run/workflows/resume-project.md +306 -0
- package/plan-build-run/workflows/set-profile.md +80 -0
- package/plan-build-run/workflows/settings.md +145 -0
- package/plan-build-run/workflows/transition.md +539 -0
- package/plan-build-run/workflows/update.md +212 -0
- package/plan-build-run/workflows/verify-phase.md +226 -0
- package/plan-build-run/workflows/verify-work.md +465 -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/audit.md +285 -0
- package/plugins/pbr/agents/codebase-mapper.md +110 -18
- package/plugins/pbr/agents/debugger.md +231 -29
- package/plugins/pbr/agents/dev-sync.md +206 -0
- package/plugins/pbr/agents/executor.md +601 -39
- package/plugins/pbr/agents/general.md +71 -6
- package/plugins/pbr/agents/integration-checker.md +146 -30
- package/plugins/pbr/agents/intel-updater.md +333 -0
- package/plugins/pbr/agents/nyquist-auditor.md +253 -0
- package/plugins/pbr/agents/plan-checker.md +177 -60
- package/plugins/pbr/agents/planner.md +404 -42
- package/plugins/pbr/agents/researcher.md +239 -36
- package/plugins/pbr/agents/roadmapper.md +384 -0
- package/plugins/pbr/agents/synthesizer.md +169 -26
- package/plugins/pbr/agents/ui-checker.md +203 -0
- package/plugins/pbr/agents/ui-researcher.md +224 -0
- package/plugins/pbr/agents/verifier.md +452 -48
- package/plugins/pbr/commands/add-phase.md +75 -0
- package/plugins/pbr/commands/add-todo.md +8 -0
- package/plugins/pbr/commands/audit-milestone.md +8 -0
- package/plugins/pbr/commands/audit.md +5 -0
- package/plugins/pbr/commands/autonomous.md +5 -0
- package/plugins/pbr/commands/begin.md +1 -1
- package/plugins/pbr/commands/build.md +1 -1
- package/plugins/pbr/commands/check-todos.md +8 -0
- package/plugins/pbr/commands/complete-milestone.md +8 -0
- package/plugins/pbr/commands/config.md +2 -2
- package/plugins/pbr/commands/continue.md +1 -1
- package/plugins/pbr/commands/dashboard.md +1 -1
- package/plugins/pbr/commands/debug.md +1 -1
- package/plugins/pbr/commands/discuss-phase.md +6 -0
- package/plugins/pbr/commands/discuss.md +1 -1
- package/plugins/pbr/commands/do.md +5 -0
- package/plugins/pbr/commands/execute-phase.md +6 -0
- package/plugins/pbr/commands/explore.md +1 -1
- package/plugins/pbr/commands/health.md +1 -1
- package/plugins/pbr/commands/help.md +1 -1
- package/plugins/pbr/commands/import.md +2 -2
- 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 +69 -0
- package/plugins/pbr/commands/map-codebase.md +6 -0
- package/plugins/pbr/commands/milestone.md +1 -1
- package/plugins/pbr/commands/new-milestone.md +8 -0
- package/plugins/pbr/commands/new-project.md +6 -0
- package/plugins/pbr/commands/note.md +1 -1
- package/plugins/pbr/commands/pause-work.md +5 -0
- package/plugins/pbr/commands/pause.md +1 -1
- package/plugins/pbr/commands/plan-milestone-gaps.md +7 -0
- package/plugins/pbr/commands/plan-phase.md +6 -0
- package/plugins/pbr/commands/plan.md +1 -1
- 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 +2 -2
- 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/resume.md +1 -1
- package/plugins/pbr/commands/review.md +1 -1
- package/plugins/pbr/commands/scan.md +1 -1
- 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 +2 -2
- package/plugins/pbr/commands/ship.md +5 -0
- package/plugins/pbr/commands/status.md +1 -1
- package/plugins/pbr/commands/statusline.md +1 -1
- package/plugins/pbr/commands/test.md +5 -0
- package/plugins/pbr/commands/todo.md +2 -2
- 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/hooks/hooks.json +102 -13
- package/plugins/pbr/references/agent-contracts.md +37 -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/pbr-rules.md +194 -0
- package/plugins/pbr/references/archive/verification-patterns.md +277 -0
- package/plugins/pbr/references/config-reference.md +182 -10
- package/plugins/pbr/references/continuation-format.md +1 -0
- package/plugins/pbr/references/deviation-rules.md +12 -0
- package/plugins/pbr/references/git-integration.md +110 -27
- package/plugins/pbr/references/hook-ordering.md +89 -0
- package/plugins/pbr/references/limitations.md +106 -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/pbr-tools-cli.md +132 -2
- package/plugins/pbr/references/plan-authoring.md +65 -0
- package/plugins/pbr/references/plan-format.md +161 -10
- package/plugins/pbr/references/pretooluse-jsonl-behavior.md +58 -0
- package/plugins/pbr/references/questioning.md +138 -49
- package/plugins/pbr/references/reading-verification.md +4 -4
- package/plugins/pbr/references/signal-files.md +41 -0
- package/plugins/pbr/references/tmux-setup.md +288 -0
- package/plugins/pbr/references/ui-brand.md +449 -0
- package/plugins/pbr/references/worktree-sparse-checkout.md +86 -0
- package/plugins/pbr/scripts/architecture-guard.js +257 -0
- package/plugins/pbr/scripts/audit-checks/behavioral-compliance.js +2098 -0
- package/plugins/pbr/scripts/audit-checks/error-analysis.js +895 -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 +452 -0
- package/plugins/pbr/scripts/audit-checks/session-quality.js +980 -0
- package/plugins/pbr/scripts/audit-checks/si-agent-hook-config-checks.js +467 -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 +1210 -0
- package/plugins/pbr/scripts/audit-dimensions.js +552 -0
- package/plugins/pbr/scripts/auto-continue.js +211 -32
- package/plugins/pbr/scripts/block-skill-self-read.js +85 -0
- package/plugins/pbr/scripts/check-agent-state-write.js +74 -0
- package/plugins/pbr/scripts/check-config-change.js +188 -0
- package/plugins/pbr/scripts/check-cross-plugin-sync.js +93 -0
- package/plugins/pbr/scripts/check-dangerous-commands.js +9 -5
- package/plugins/pbr/scripts/check-direct-state-write.js +37 -0
- package/plugins/pbr/scripts/check-phase-boundary.js +2 -8
- package/plugins/pbr/scripts/check-plan-format.js +149 -274
- package/plugins/pbr/scripts/check-roadmap-sync.js +167 -10
- package/plugins/pbr/scripts/check-skill-workflow.js +38 -34
- package/plugins/pbr/scripts/check-state-sync.js +344 -216
- package/plugins/pbr/scripts/check-subagent-output.js +294 -283
- package/plugins/pbr/scripts/check-summary-gate.js +1 -1
- package/plugins/pbr/scripts/config-schema.json +1252 -95
- package/plugins/pbr/scripts/context-bridge.js +439 -0
- package/plugins/pbr/scripts/context-budget-check.js +89 -9
- package/plugins/pbr/scripts/context-quality.js +272 -0
- package/plugins/pbr/scripts/enforce-pbr-workflow.js +277 -0
- package/plugins/pbr/scripts/event-handler.js +129 -77
- package/plugins/pbr/scripts/event-logger.js +61 -26
- package/plugins/pbr/scripts/feedback-loop.js +172 -0
- package/plugins/pbr/scripts/graph-update.js +199 -0
- package/plugins/pbr/scripts/hook-logger.js +76 -35
- package/plugins/pbr/scripts/hook-server-client.js +258 -0
- package/plugins/pbr/scripts/hook-server.js +334 -0
- package/plugins/pbr/scripts/hooks-schema.json +5 -1
- package/plugins/pbr/scripts/instructions-loaded.js +107 -0
- package/plugins/pbr/scripts/intel-queue.js +152 -0
- package/plugins/pbr/scripts/intent-router.cjs +147 -0
- package/plugins/pbr/scripts/intercept-plan-mode.js +52 -0
- package/plugins/pbr/scripts/lib/alternatives.js +203 -0
- package/plugins/pbr/scripts/lib/auto-cleanup.js +221 -0
- package/plugins/pbr/scripts/lib/auto-verify.js +103 -0
- package/plugins/pbr/scripts/lib/autonomy.js +91 -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/circuit-state.js +133 -0
- package/plugins/pbr/scripts/lib/config.js +901 -0
- package/plugins/pbr/scripts/lib/context.js +254 -0
- package/plugins/pbr/scripts/lib/convention-detector.js +413 -0
- package/plugins/pbr/scripts/lib/core.js +939 -0
- package/plugins/pbr/scripts/lib/dashboard-launch.js +170 -0
- package/plugins/pbr/scripts/lib/decision-extraction.js +267 -0
- package/plugins/pbr/scripts/lib/dependency-break.js +147 -0
- package/plugins/pbr/scripts/lib/format-validators.js +947 -0
- package/plugins/pbr/scripts/lib/gates/advisories.js +129 -0
- package/plugins/pbr/scripts/lib/gates/build-dependency.js +115 -0
- package/plugins/pbr/scripts/lib/gates/build-executor.js +104 -0
- package/plugins/pbr/scripts/lib/gates/doc-existence.js +46 -0
- package/plugins/pbr/scripts/lib/gates/helpers.js +141 -0
- package/plugins/pbr/scripts/lib/gates/inline-execution.js +185 -0
- package/plugins/pbr/scripts/lib/gates/milestone-complete.js +136 -0
- package/plugins/pbr/scripts/lib/gates/milestone-summary.js +119 -0
- package/plugins/pbr/scripts/lib/gates/multi-phase-loader.js +147 -0
- package/plugins/pbr/scripts/lib/gates/plan-executor.js +36 -0
- package/plugins/pbr/scripts/lib/gates/quick-executor.js +76 -0
- package/plugins/pbr/scripts/lib/gates/review-planner.js +61 -0
- package/plugins/pbr/scripts/lib/gates/review-verifier.js +69 -0
- package/plugins/pbr/scripts/lib/gates/rich-agent-context.js +143 -0
- package/plugins/pbr/scripts/lib/health-checks.js +215 -0
- package/plugins/pbr/scripts/lib/history.js +150 -0
- package/plugins/pbr/scripts/lib/init.js +302 -0
- package/plugins/pbr/scripts/lib/learnings.js +432 -0
- package/plugins/pbr/scripts/lib/migrate.js +169 -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/phase.js +935 -0
- package/plugins/pbr/scripts/lib/pre-research.js +133 -0
- package/plugins/pbr/scripts/lib/preview.js +174 -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/resolve-root.js +66 -0
- package/plugins/pbr/scripts/lib/roadmap.js +784 -0
- package/plugins/pbr/scripts/lib/session-briefing.js +879 -0
- package/plugins/pbr/scripts/lib/skill-section.js +99 -0
- package/plugins/pbr/scripts/lib/smart-next-task.js +225 -0
- package/plugins/pbr/scripts/lib/snapshot-manager.js +225 -0
- package/plugins/pbr/scripts/lib/spot-check.js +509 -0
- package/plugins/pbr/scripts/lib/state.js +565 -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 +1010 -0
- package/plugins/pbr/scripts/lib/suggest-next.js +316 -0
- package/plugins/pbr/scripts/lib/tech-debt-scanner.js +116 -0
- package/plugins/pbr/scripts/lib/test-cache.js +54 -0
- package/plugins/pbr/scripts/lib/todo.js +300 -0
- package/plugins/pbr/scripts/lib/trust-gate.js +84 -0
- package/plugins/pbr/scripts/local-llm/client.js +237 -0
- package/plugins/pbr/scripts/local-llm/health.js +220 -0
- package/plugins/pbr/scripts/local-llm/metrics.js +340 -0
- package/plugins/pbr/scripts/local-llm/operations/classify-artifact.js +76 -0
- package/plugins/pbr/scripts/local-llm/operations/classify-commit.js +137 -0
- package/plugins/pbr/scripts/local-llm/operations/classify-error.js +75 -0
- package/plugins/pbr/scripts/local-llm/operations/classify-file-intent.js +171 -0
- package/plugins/pbr/scripts/local-llm/operations/score-source.js +72 -0
- package/plugins/pbr/scripts/local-llm/operations/summarize-context.js +62 -0
- package/plugins/pbr/scripts/local-llm/operations/triage-test-output.js +72 -0
- package/plugins/pbr/scripts/local-llm/operations/validate-task.js +59 -0
- package/plugins/pbr/scripts/local-llm/router.js +101 -0
- package/plugins/pbr/scripts/local-llm/shadow.js +60 -0
- package/plugins/pbr/scripts/local-llm/threshold-tuner.js +118 -0
- package/plugins/pbr/scripts/log-subagent.js +129 -26
- package/plugins/pbr/scripts/log-tool-failure.js +57 -4
- package/plugins/pbr/scripts/milestone-learnings.js +569 -0
- package/plugins/pbr/scripts/package.json +1 -0
- package/plugins/pbr/scripts/pbr-tools.js +1361 -1214
- package/plugins/pbr/scripts/post-bash-triage.js +163 -0
- package/plugins/pbr/scripts/post-compact.js +135 -0
- package/plugins/pbr/scripts/post-hoc.js +286 -0
- package/plugins/pbr/scripts/post-write-dispatch.js +315 -30
- package/plugins/pbr/scripts/post-write-quality.js +3 -3
- package/plugins/pbr/scripts/pre-bash-dispatch.js +81 -4
- package/plugins/pbr/scripts/pre-write-dispatch.js +76 -20
- package/plugins/pbr/scripts/progress-tracker.js +145 -291
- 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 +49 -8
- package/plugins/pbr/scripts/session-cleanup.js +379 -7
- package/plugins/pbr/scripts/session-tracker.js +124 -0
- package/plugins/pbr/scripts/status-line.js +540 -27
- package/plugins/pbr/scripts/suggest-compact.js +183 -7
- package/plugins/pbr/scripts/sync-context-to-claude.js +100 -0
- package/plugins/pbr/scripts/task-completed.js +125 -3
- package/plugins/pbr/scripts/test/config.test.js +126 -0
- package/plugins/pbr/scripts/test/cross-platform.test.js +131 -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 +163 -0
- package/plugins/pbr/scripts/track-context-budget.js +326 -100
- package/plugins/pbr/scripts/trust-tracker.js +193 -0
- package/plugins/pbr/scripts/validate-commit.js +76 -11
- package/plugins/pbr/scripts/validate-skill-args.js +18 -14
- package/plugins/pbr/scripts/validate-task.js +93 -626
- 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 +478 -0
- package/plugins/pbr/skills/autonomous/SKILL.md +460 -0
- package/plugins/pbr/skills/begin/SKILL.md +447 -142
- package/plugins/pbr/skills/begin/templates/STATE.md.tmpl +1 -2
- package/plugins/pbr/skills/begin/templates/config.json.tmpl +423 -36
- package/plugins/pbr/skills/begin/templates/researcher-prompt.md.tmpl +28 -0
- package/plugins/pbr/skills/begin/templates/roadmap-prompt.md.tmpl +26 -3
- package/plugins/pbr/skills/begin/templates/synthesis-prompt.md.tmpl +33 -5
- package/plugins/pbr/skills/build/SKILL.md +1010 -327
- 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/config/SKILL.md +108 -9
- package/plugins/pbr/skills/continue/SKILL.md +118 -19
- package/plugins/pbr/skills/dashboard/SKILL.md +21 -9
- package/plugins/pbr/skills/debug/SKILL.md +62 -10
- 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 +155 -23
- 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 +83 -16
- package/plugins/pbr/skills/health/SKILL.md +74 -17
- package/plugins/pbr/skills/help/SKILL.md +123 -39
- package/plugins/pbr/skills/import/SKILL.md +327 -13
- package/plugins/pbr/skills/intel/SKILL.md +131 -0
- package/plugins/pbr/skills/milestone/SKILL.md +342 -260
- 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/note/SKILL.md +10 -2
- package/plugins/pbr/skills/pause/SKILL.md +51 -7
- package/plugins/pbr/skills/pause/templates/continue-here.md.tmpl +33 -52
- package/plugins/pbr/skills/plan/SKILL.md +418 -268
- package/plugins/pbr/skills/plan/templates/checker-prompt.md.tmpl +5 -2
- package/plugins/pbr/skills/plan/templates/completion-output.md.tmpl +27 -0
- package/plugins/pbr/skills/plan/templates/revision-prompt.md.tmpl +21 -5
- package/plugins/pbr/skills/profile/SKILL.md +183 -0
- package/plugins/pbr/skills/profile-user/SKILL.md +224 -0
- package/plugins/pbr/skills/quick/SKILL.md +440 -95
- package/plugins/pbr/skills/release/SKILL.md +206 -0
- package/plugins/pbr/skills/resume/SKILL.md +122 -27
- package/plugins/pbr/skills/review/SKILL.md +219 -154
- package/plugins/pbr/skills/review/templates/verifier-prompt.md.tmpl +7 -0
- package/plugins/pbr/skills/scan/SKILL.md +36 -12
- package/plugins/pbr/skills/scan/templates/mapper-prompt.md.tmpl +1 -1
- package/plugins/pbr/skills/session-report/SKILL.md +128 -0
- package/plugins/pbr/skills/setup/SKILL.md +149 -202
- package/plugins/pbr/skills/shared/agent-context-enrichment.md +21 -0
- package/plugins/pbr/skills/shared/agent-type-resolution.md +20 -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 +15 -8
- 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 +47 -54
- package/plugins/pbr/skills/shared/universal-anti-patterns.md +27 -4
- package/plugins/pbr/skills/ship/SKILL.md +154 -0
- package/plugins/pbr/skills/status/SKILL.md +201 -53
- package/plugins/pbr/skills/test/SKILL.md +254 -0
- package/plugins/pbr/skills/todo/SKILL.md +13 -11
- package/plugins/pbr/skills/ui-phase/SKILL.md +179 -0
- package/plugins/pbr/skills/ui-review/SKILL.md +206 -0
- package/plugins/pbr/skills/undo/SKILL.md +218 -0
- package/plugins/pbr/skills/validate-phase/SKILL.md +358 -0
- package/plugins/pbr/templates/CONTEXT.md.tmpl +45 -20
- package/plugins/pbr/templates/DISCOVERY.md.tmpl +29 -0
- package/plugins/pbr/templates/HANDOFF.json.tmpl +30 -0
- package/plugins/pbr/templates/INTEGRATION-REPORT.md.tmpl +18 -2
- package/plugins/pbr/templates/KNOWLEDGE.md.tmpl +39 -0
- 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 +108 -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/VERIFICATION-DETAIL.md.tmpl +49 -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 +362 -0
- package/scripts/generate-derivatives.js +581 -0
- package/scripts/posttest.js +93 -0
- package/scripts/release.js +196 -0
- package/scripts/run-tests.cjs +29 -0
- package/dashboard/bin/cli.js +0 -25
- package/dashboard/public/css/layout.css +0 -472
- package/dashboard/public/css/status-colors.css +0 -98
- package/dashboard/public/js/htmx-title.js +0 -5
- package/dashboard/public/js/sidebar-toggle.js +0 -20
- package/dashboard/src/app.js +0 -78
- 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 -40
- package/dashboard/src/routes/index.routes.js +0 -31
- package/dashboard/src/routes/pages.routes.js +0 -308
- package/dashboard/src/server.js +0 -42
- package/dashboard/src/services/dashboard.service.js +0 -309
- package/dashboard/src/services/milestone.service.js +0 -154
- package/dashboard/src/services/phase.service.js +0 -226
- package/dashboard/src/services/project.service.js +0 -57
- package/dashboard/src/services/roadmap.service.js +0 -171
- package/dashboard/src/services/sse.service.js +0 -58
- package/dashboard/src/services/todo.service.js +0 -254
- package/dashboard/src/services/watcher.service.js +0 -48
- package/dashboard/src/views/coming-soon.ejs +0 -11
- package/dashboard/src/views/error.ejs +0 -13
- package/dashboard/src/views/index.ejs +0 -5
- package/dashboard/src/views/layout.ejs +0 -1
- package/dashboard/src/views/milestone-detail.ejs +0 -5
- package/dashboard/src/views/milestones.ejs +0 -5
- package/dashboard/src/views/partials/dashboard-content.ejs +0 -77
- package/dashboard/src/views/partials/footer.ejs +0 -3
- package/dashboard/src/views/partials/head.ejs +0 -27
- package/dashboard/src/views/partials/header.ejs +0 -12
- package/dashboard/src/views/partials/layout-bottom.ejs +0 -15
- package/dashboard/src/views/partials/layout-top.ejs +0 -8
- package/dashboard/src/views/partials/milestone-detail-content.ejs +0 -19
- package/dashboard/src/views/partials/milestones-content.ejs +0 -44
- package/dashboard/src/views/partials/phase-content.ejs +0 -189
- package/dashboard/src/views/partials/phase-doc-content.ejs +0 -38
- package/dashboard/src/views/partials/phases-content.ejs +0 -117
- package/dashboard/src/views/partials/roadmap-content.ejs +0 -142
- package/dashboard/src/views/partials/sidebar.ejs +0 -46
- package/dashboard/src/views/partials/todo-create-content.ejs +0 -53
- package/dashboard/src/views/partials/todo-detail-content.ejs +0 -38
- package/dashboard/src/views/partials/todos-content.ejs +0 -53
- 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 -129
- package/plugins/copilot-pbr/agents/codebase-mapper.agent.md +0 -151
- package/plugins/copilot-pbr/agents/debugger.agent.md +0 -172
- package/plugins/copilot-pbr/agents/executor.agent.md +0 -267
- package/plugins/copilot-pbr/agents/general.agent.md +0 -88
- package/plugins/copilot-pbr/agents/integration-checker.agent.md +0 -119
- package/plugins/copilot-pbr/agents/plan-checker.agent.md +0 -199
- package/plugins/copilot-pbr/agents/planner.agent.md +0 -238
- package/plugins/copilot-pbr/agents/researcher.agent.md +0 -186
- 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 -144
- 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/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 -955
- 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 -274
- 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 -152
- package/plugins/copilot-pbr/skills/import/SKILL.md +0 -502
- package/plugins/copilot-pbr/skills/milestone/SKILL.md +0 -745
- 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-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 -162
- 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 -41
- 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 -118
- package/plugins/cursor-pbr/agents/codebase-mapper.md +0 -150
- package/plugins/cursor-pbr/agents/debugger.md +0 -171
- package/plugins/cursor-pbr/agents/executor.md +0 -266
- package/plugins/cursor-pbr/agents/general.md +0 -87
- package/plugins/cursor-pbr/agents/integration-checker.md +0 -118
- package/plugins/cursor-pbr/agents/plan-checker.md +0 -198
- package/plugins/cursor-pbr/agents/planner.md +0 -237
- package/plugins/cursor-pbr/agents/researcher.md +0 -185
- 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 -203
- 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/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 -956
- 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 -274
- 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 -152
- package/plugins/cursor-pbr/skills/import/SKILL.md +0 -505
- package/plugins/cursor-pbr/skills/milestone/SKILL.md +0 -746
- 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-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 -162
- 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 -41
- 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/checkpoints.md +0 -157
- package/plugins/pbr/references/pbr-rules.md +0 -194
- 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/references/verification-patterns.md +0 -198
- 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/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
|
@@ -3,8 +3,16 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* pbr-tools.js — Structured JSON state operations for Plan-Build-Run skills.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* Thin dispatcher that imports from lib/ modules. All core logic lives in:
|
|
7
|
+
* lib/core.js — Foundation utilities (parsers, file ops, constants)
|
|
8
|
+
* lib/config.js — Config loading, validation, depth profiles
|
|
9
|
+
* lib/state.js — STATE.md operations (load, update, patch, advance)
|
|
10
|
+
* lib/roadmap.js — ROADMAP.md operations (parse, update status/plans)
|
|
11
|
+
* lib/phase.js — Phase operations (add, remove, list, info, plan-index)
|
|
12
|
+
* lib/init.js — Compound init commands (execute-phase, plan-phase, etc.)
|
|
13
|
+
* lib/history.js — History operations (append to STATE.md ## History, load with HISTORY.md fallback)
|
|
14
|
+
*
|
|
15
|
+
* Skills call this via:
|
|
8
16
|
* node ${CLAUDE_PLUGIN_ROOT}/scripts/pbr-tools.js <command> [args]
|
|
9
17
|
*
|
|
10
18
|
* Commands:
|
|
@@ -18,196 +26,777 @@
|
|
|
18
26
|
* phase-info <phase> — Comprehensive single-phase status → JSON
|
|
19
27
|
* roadmap update-status <phase> <status> — Update phase status in ROADMAP.md
|
|
20
28
|
* roadmap update-plans <phase> <complete> <total> — Update phase plans in ROADMAP.md
|
|
21
|
-
* history append <type> <title> [body] — Append record to HISTORY.md
|
|
22
|
-
* history load — Load
|
|
29
|
+
* history append <type> <title> [body] — Append record to STATE.md ## History (fallback: HISTORY.md)
|
|
30
|
+
* history load — Load history records as JSON (STATE.md first, HISTORY.md fallback)
|
|
31
|
+
* todo list [--theme X] [--status Y] — List todos as JSON (default: pending)
|
|
32
|
+
* todo get <NNN> — Get a specific todo by number
|
|
33
|
+
* todo add <title> [--priority P] [--theme T] — Add a new todo
|
|
34
|
+
* todo done <NNN> — Mark a todo as complete
|
|
35
|
+
* auto-cleanup --phase N | --milestone vN — Auto-close todos and archive notes matching phase/milestone deliverables
|
|
36
|
+
* llm metrics [--session <ISO>] — Lifetime or session-scoped LLM usage metrics
|
|
37
|
+
* validate-project — Comprehensive .planning/ integrity check
|
|
38
|
+
* phase add <slug> [--after N] [--goal "..."] [--depends-on N] — Add phase with ROADMAP.md integration
|
|
39
|
+
* phase remove <N> — Remove an empty phase directory (with renumbering)
|
|
40
|
+
* phase list — List all phase directories with status
|
|
41
|
+
* phase complete <N> — Mark phase N complete, advance STATE.md to next phase
|
|
42
|
+
* phase insert <N> <slug> [--goal "..."] [--depends-on N] — Insert phase at position N, renumber subsequent
|
|
43
|
+
* phase commits-for <N> — Read .phase-manifest.json for phase N, output commits JSON. Falls back to git log
|
|
44
|
+
* phase first-last-commit <N> — Output { first, last } commit hashes from manifest or git log
|
|
45
|
+
* learnings ingest <json-file> — Ingest a learning entry into global store
|
|
46
|
+
* learnings query [--tags X] [--min-confidence Y] [--stack S] [--type T] — Query learnings
|
|
47
|
+
* learnings check-thresholds — Check deferral trigger conditions
|
|
48
|
+
* learnings copy-global <path> <proj> — Copy cross_project LEARNINGS.md to ~/.claude/pbr-knowledge/
|
|
49
|
+
* learnings query-global [--tags X] [--project P] — Query global knowledge files
|
|
50
|
+
* spot-check <phaseSlug> <planId> — Verify SUMMARY, key_files, and commits exist for a plan
|
|
51
|
+
* staleness-check <phase-slug> — Check if phase plans are stale vs dependencies
|
|
52
|
+
* summary-gate <phase-slug> <plan-id> — Verify SUMMARY.md exists, non-empty, valid frontmatter
|
|
53
|
+
* checkpoint init <phase-slug> [--plans "id1,id2"] — Initialize checkpoint manifest
|
|
54
|
+
* checkpoint update <phase-slug> --wave N --resolved id [--sha hash] — Update manifest
|
|
55
|
+
* seeds match <phase-slug> <phase-number> — Find matching seed files for a phase
|
|
56
|
+
* session get <key> — Read a key from .planning/.session.json
|
|
57
|
+
* session set <key> <value> — Write a key to .planning/.session.json
|
|
58
|
+
* session clear [key] — Delete .session.json or set key to null
|
|
59
|
+
* session dump — Print entire .session.json content
|
|
60
|
+
* skill-section <skill> <section> — Extract a section from a skill's SKILL.md → JSON
|
|
61
|
+
* skill-section --list <skill> — List all headings in a skill → JSON
|
|
62
|
+
* step-verify [skill] [step] [checklist-json] — Validate per-step completion checklist → JSON
|
|
63
|
+
* build-preview [phase-slug] — Preview what /pbr:execute-phase would do for a phase → JSON
|
|
64
|
+
* claim acquire <phase-slug> --session-id <id> --skill <name> — Acquire phase claim
|
|
65
|
+
* claim release <phase-slug> --session-id <id> — Release phase claim
|
|
66
|
+
* claim list — List all active phase claims
|
|
67
|
+
* suggest-alternatives phase-not-found [slug] — List available phases for unknown slug → JSON
|
|
68
|
+
* suggest-alternatives missing-prereq [phase] — List missing prerequisites for a phase → JSON
|
|
69
|
+
* suggest-alternatives config-invalid [field] [val] — List valid values for invalid config field → JSON
|
|
70
|
+
*
|
|
71
|
+
* Environment: PBR_PROJECT_ROOT — Override project root directory (used when hooks fire from subagent cwd)
|
|
23
72
|
*/
|
|
24
73
|
|
|
25
74
|
const fs = require('fs');
|
|
26
75
|
const path = require('path');
|
|
27
76
|
|
|
28
|
-
|
|
29
|
-
const
|
|
77
|
+
// --- Import lib modules ---
|
|
78
|
+
const {
|
|
79
|
+
KNOWN_AGENTS,
|
|
80
|
+
VALID_STATUS_TRANSITIONS,
|
|
81
|
+
validateStatusTransition,
|
|
82
|
+
output,
|
|
83
|
+
error,
|
|
84
|
+
parseYamlFrontmatter,
|
|
85
|
+
parseMustHaves,
|
|
86
|
+
findFiles,
|
|
87
|
+
tailLines,
|
|
88
|
+
countMustHaves,
|
|
89
|
+
determinePhaseStatus,
|
|
90
|
+
atomicWrite,
|
|
91
|
+
lockedFileUpdate,
|
|
92
|
+
writeActiveSkill,
|
|
93
|
+
sessionLoad,
|
|
94
|
+
sessionSave,
|
|
95
|
+
SESSION_ALLOWED_KEYS,
|
|
96
|
+
STALE_SESSION_MS,
|
|
97
|
+
resolveSessionPath,
|
|
98
|
+
acquireClaim,
|
|
99
|
+
releaseClaim,
|
|
100
|
+
releaseSessionClaims: _releaseSessionClaims,
|
|
101
|
+
listClaims: _listClaims
|
|
102
|
+
} = require('./lib/core');
|
|
103
|
+
|
|
104
|
+
const {
|
|
105
|
+
configLoad: _configLoad,
|
|
106
|
+
configClearCache: _configClearCache,
|
|
107
|
+
configValidate: _configValidate,
|
|
108
|
+
configFormat: _configFormat,
|
|
109
|
+
configWrite: _configWrite,
|
|
110
|
+
resolveDepthProfile,
|
|
111
|
+
DEPTH_PROFILE_DEFAULTS,
|
|
112
|
+
loadUserDefaults,
|
|
113
|
+
saveUserDefaults,
|
|
114
|
+
mergeUserDefaults,
|
|
115
|
+
USER_DEFAULTS_PATH
|
|
116
|
+
} = require('./lib/config');
|
|
117
|
+
|
|
118
|
+
const {
|
|
119
|
+
parseStateMd,
|
|
120
|
+
updateLegacyStateField,
|
|
121
|
+
updateFrontmatterField,
|
|
122
|
+
stateLoad: _stateLoad,
|
|
123
|
+
stateCheckProgress: _stateCheckProgress,
|
|
124
|
+
stateUpdate: _stateUpdate,
|
|
125
|
+
statePatch: _statePatch,
|
|
126
|
+
stateAdvancePlan: _stateAdvancePlan,
|
|
127
|
+
stateRecordMetric: _stateRecordMetric,
|
|
128
|
+
stateRecordActivity: _stateRecordActivity,
|
|
129
|
+
stateUpdateProgress: _stateUpdateProgress
|
|
130
|
+
} = require('./lib/state');
|
|
131
|
+
|
|
132
|
+
const {
|
|
133
|
+
parseRoadmapMd,
|
|
134
|
+
findRoadmapRow,
|
|
135
|
+
updateTableRow,
|
|
136
|
+
roadmapUpdateStatus: _roadmapUpdateStatus,
|
|
137
|
+
roadmapUpdatePlans: _roadmapUpdatePlans,
|
|
138
|
+
roadmapAnalyze: _roadmapAnalyze,
|
|
139
|
+
roadmapAppendPhase: _roadmapAppendPhase,
|
|
140
|
+
roadmapRemovePhase: _roadmapRemovePhase,
|
|
141
|
+
roadmapRenumberPhases: _roadmapRenumberPhases,
|
|
142
|
+
roadmapInsertPhase: _roadmapInsertPhase
|
|
143
|
+
} = require('./lib/roadmap');
|
|
144
|
+
|
|
145
|
+
const {
|
|
146
|
+
frontmatter: _frontmatter,
|
|
147
|
+
planIndex: _planIndex,
|
|
148
|
+
mustHavesCollect: _mustHavesCollect,
|
|
149
|
+
phaseInfo: _phaseInfo,
|
|
150
|
+
phaseAdd: _phaseAdd,
|
|
151
|
+
phaseRemove: _phaseRemove,
|
|
152
|
+
phaseList: _phaseList,
|
|
153
|
+
milestoneStats: _milestoneStats,
|
|
154
|
+
phaseComplete: _phaseComplete,
|
|
155
|
+
phaseInsert: _phaseInsert
|
|
156
|
+
} = require('./lib/phase');
|
|
157
|
+
|
|
158
|
+
const {
|
|
159
|
+
initExecutePhase: _initExecutePhase,
|
|
160
|
+
initPlanPhase: _initPlanPhase,
|
|
161
|
+
initQuick: _initQuick,
|
|
162
|
+
initVerifyWork: _initVerifyWork,
|
|
163
|
+
initResume: _initResume,
|
|
164
|
+
initProgress: _initProgress,
|
|
165
|
+
initStateBundle: _initStateBundle
|
|
166
|
+
} = require('./lib/init');
|
|
167
|
+
|
|
168
|
+
const {
|
|
169
|
+
historyAppend: _historyAppend,
|
|
170
|
+
historyLoad: _historyLoad
|
|
171
|
+
} = require('./lib/history');
|
|
172
|
+
|
|
173
|
+
const {
|
|
174
|
+
todoList: _todoList,
|
|
175
|
+
todoGet: _todoGet,
|
|
176
|
+
todoAdd: _todoAdd,
|
|
177
|
+
todoDone: _todoDone
|
|
178
|
+
} = require('./lib/todo');
|
|
179
|
+
|
|
180
|
+
const {
|
|
181
|
+
autoCloseTodos: _autoCloseTodos,
|
|
182
|
+
autoArchiveNotes: _autoArchiveNotes
|
|
183
|
+
} = require('./lib/auto-cleanup');
|
|
184
|
+
|
|
185
|
+
const {
|
|
186
|
+
applyMigrations: _applyMigrations
|
|
187
|
+
} = require('./lib/migrate');
|
|
188
|
+
|
|
189
|
+
const {
|
|
190
|
+
spotCheck: _spotCheck,
|
|
191
|
+
verifySpotCheck: _verifySpotCheck
|
|
192
|
+
} = require('./lib/spot-check');
|
|
193
|
+
|
|
194
|
+
const {
|
|
195
|
+
learningsIngest: _learningsIngest,
|
|
196
|
+
learningsQuery: _learningsQuery,
|
|
197
|
+
checkDeferralThresholds: _checkDeferralThresholds,
|
|
198
|
+
copyToGlobal: _copyToGlobal,
|
|
199
|
+
queryGlobal: _queryGlobal
|
|
200
|
+
} = require('./lib/learnings');
|
|
201
|
+
|
|
202
|
+
const {
|
|
203
|
+
referenceGet: _referenceGet
|
|
204
|
+
} = require('./lib/reference');
|
|
205
|
+
|
|
206
|
+
const {
|
|
207
|
+
skillSection: _skillSection,
|
|
208
|
+
listAvailableSkills: _listAvailableSkills
|
|
209
|
+
} = require('./lib/skill-section');
|
|
210
|
+
|
|
211
|
+
const {
|
|
212
|
+
stepVerify: _stepVerify
|
|
213
|
+
} = require('./lib/step-verify');
|
|
214
|
+
|
|
215
|
+
const {
|
|
216
|
+
buildPreview: _buildPreview
|
|
217
|
+
} = require('./lib/preview');
|
|
218
|
+
|
|
219
|
+
const {
|
|
220
|
+
contextTriage: _contextTriage
|
|
221
|
+
} = require('./lib/context');
|
|
222
|
+
|
|
223
|
+
const {
|
|
224
|
+
phaseAlternatives: _phaseAlternatives,
|
|
225
|
+
prerequisiteAlternatives: _prereqAlternatives,
|
|
226
|
+
configAlternatives: _configAlternatives
|
|
227
|
+
} = require('./lib/alternatives');
|
|
228
|
+
|
|
229
|
+
const {
|
|
230
|
+
stalenessCheck: _stalenessCheck,
|
|
231
|
+
summaryGate: _summaryGate,
|
|
232
|
+
checkpointInit: _checkpointInit,
|
|
233
|
+
checkpointUpdate: _checkpointUpdate,
|
|
234
|
+
seedsMatch: _seedsMatch,
|
|
235
|
+
ciPoll: _ciPoll,
|
|
236
|
+
rollback: _rollback
|
|
237
|
+
} = require('./lib/build');
|
|
238
|
+
|
|
239
|
+
const {
|
|
240
|
+
parseJestOutput: _parseJestOutput,
|
|
241
|
+
parseLintOutput: _parseLintOutput,
|
|
242
|
+
autoFixLint: _autoFixLint,
|
|
243
|
+
runCiFixLoop: _runCiFixLoop
|
|
244
|
+
} = require('./lib/ci-fix-loop');
|
|
245
|
+
|
|
246
|
+
const {
|
|
247
|
+
statusRender: _statusRender
|
|
248
|
+
} = require('./lib/status-render');
|
|
249
|
+
|
|
250
|
+
const {
|
|
251
|
+
suggestNext: _suggestNext
|
|
252
|
+
} = require('./lib/suggest-next');
|
|
253
|
+
|
|
254
|
+
const {
|
|
255
|
+
quickStatus: _quickStatus
|
|
256
|
+
} = require('./quick-status');
|
|
257
|
+
|
|
258
|
+
// --- Local LLM imports (not extracted — separate module tree) ---
|
|
259
|
+
const { resolveConfig, checkHealth } = require('./local-llm/health');
|
|
260
|
+
const { classifyArtifact } = require('./local-llm/operations/classify-artifact');
|
|
261
|
+
const { scoreSource } = require('./local-llm/operations/score-source');
|
|
262
|
+
const { classifyError } = require('./local-llm/operations/classify-error');
|
|
263
|
+
const { summarizeContext } = require('./local-llm/operations/summarize-context');
|
|
264
|
+
const { readSessionMetrics, summarizeMetrics, computeLifetimeMetrics } = require('./local-llm/metrics');
|
|
265
|
+
const { computeThresholdAdjustments } = require('./local-llm/threshold-tuner');
|
|
266
|
+
|
|
267
|
+
// --- Module-level state (for backwards compatibility) ---
|
|
268
|
+
|
|
269
|
+
let cwd = process.env.PBR_PROJECT_ROOT || process.cwd();
|
|
270
|
+
// MSYS path bridging: Git Bash on Windows can produce /d/Repos/... paths
|
|
271
|
+
// that Node.js cannot resolve. Convert to D:\Repos\... form.
|
|
272
|
+
const _msysCwdMatch = cwd.match(/^\/([a-zA-Z])\/(.*)/);
|
|
273
|
+
if (_msysCwdMatch) cwd = _msysCwdMatch[1] + ':' + path.sep + _msysCwdMatch[2].replace(/\//g, path.sep);
|
|
274
|
+
let planningDir = path.join(cwd, '.planning');
|
|
275
|
+
|
|
276
|
+
// --- Wrapper functions that pass planningDir to lib modules ---
|
|
277
|
+
// These preserve the original function signatures (no planningDir param)
|
|
278
|
+
// so existing callers (hook scripts, tests) continue to work.
|
|
30
279
|
|
|
31
|
-
|
|
280
|
+
function configLoad(dir) {
|
|
281
|
+
return _configLoad(dir || planningDir);
|
|
282
|
+
}
|
|
32
283
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
* pending -> planned, skipped
|
|
41
|
-
* planned -> building
|
|
42
|
-
* building -> built, partial, needs_fixes
|
|
43
|
-
* built -> verified, needs_fixes
|
|
44
|
-
* partial -> building, needs_fixes
|
|
45
|
-
* verified -> building (re-execution)
|
|
46
|
-
* needs_fixes -> planned, building
|
|
47
|
-
* skipped -> pending (unskip)
|
|
48
|
-
*/
|
|
49
|
-
const VALID_STATUS_TRANSITIONS = {
|
|
50
|
-
pending: ['planned', 'skipped'],
|
|
51
|
-
planned: ['building'],
|
|
52
|
-
building: ['built', 'partial', 'needs_fixes'],
|
|
53
|
-
built: ['verified', 'needs_fixes'],
|
|
54
|
-
partial: ['building', 'needs_fixes'],
|
|
55
|
-
verified: ['building'],
|
|
56
|
-
needs_fixes: ['planned', 'building'],
|
|
57
|
-
skipped: ['pending']
|
|
58
|
-
};
|
|
284
|
+
function configClearCache() {
|
|
285
|
+
_configClearCache();
|
|
286
|
+
cwd = process.env.PBR_PROJECT_ROOT || process.cwd();
|
|
287
|
+
const _msysResetMatch = cwd.match(/^\/([a-zA-Z])\/(.*)/);
|
|
288
|
+
if (_msysResetMatch) cwd = _msysResetMatch[1] + ':' + path.sep + _msysResetMatch[2].replace(/\//g, path.sep);
|
|
289
|
+
planningDir = path.join(cwd, '.planning');
|
|
290
|
+
}
|
|
59
291
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
*
|
|
64
|
-
* @param {string} oldStatus - Current phase status
|
|
65
|
-
* @param {string} newStatus - Desired phase status
|
|
66
|
-
* @returns {{ valid: boolean, warning?: string }}
|
|
67
|
-
*/
|
|
68
|
-
function validateStatusTransition(oldStatus, newStatus) {
|
|
69
|
-
const from = (oldStatus || '').trim().toLowerCase();
|
|
70
|
-
const to = (newStatus || '').trim().toLowerCase();
|
|
292
|
+
function configValidate(preloadedConfig) {
|
|
293
|
+
return _configValidate(preloadedConfig, planningDir);
|
|
294
|
+
}
|
|
71
295
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
296
|
+
function stateLoad() {
|
|
297
|
+
return _stateLoad(planningDir);
|
|
298
|
+
}
|
|
76
299
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
300
|
+
function stateCheckProgress() {
|
|
301
|
+
return _stateCheckProgress(planningDir);
|
|
302
|
+
}
|
|
81
303
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
304
|
+
function stateUpdate(field, value) {
|
|
305
|
+
return _stateUpdate(field, value, planningDir);
|
|
306
|
+
}
|
|
86
307
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
warning: `Suspicious status transition: "${from}" -> "${to}". Expected one of: [${allowed.join(', ')}]. Proceeding anyway (advisory).`
|
|
90
|
-
};
|
|
308
|
+
function statePatch(jsonStr) {
|
|
309
|
+
return _statePatch(jsonStr, planningDir);
|
|
91
310
|
}
|
|
92
311
|
|
|
93
|
-
|
|
312
|
+
function stateAdvancePlan() {
|
|
313
|
+
return _stateAdvancePlan(planningDir);
|
|
314
|
+
}
|
|
94
315
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
316
|
+
function stateRecordMetric(metricArgs) {
|
|
317
|
+
return _stateRecordMetric(metricArgs, planningDir);
|
|
318
|
+
}
|
|
98
319
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
function
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
320
|
+
function stateRecordActivity(description) {
|
|
321
|
+
return _stateRecordActivity(description, planningDir);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function stateUpdateProgress() {
|
|
325
|
+
return _stateUpdateProgress(planningDir);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function roadmapAnalyze() {
|
|
329
|
+
return _roadmapAnalyze(planningDir);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function roadmapUpdateStatus(phaseNum, newStatus) {
|
|
333
|
+
return _roadmapUpdateStatus(phaseNum, newStatus, planningDir);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function roadmapUpdatePlans(phaseNum, complete, total) {
|
|
337
|
+
return _roadmapUpdatePlans(phaseNum, complete, total, planningDir);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function frontmatter(filePath) {
|
|
341
|
+
return _frontmatter(filePath);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function planIndex(phaseNum) {
|
|
345
|
+
return _planIndex(phaseNum, planningDir);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function mustHavesCollect(phaseNum) {
|
|
349
|
+
return _mustHavesCollect(phaseNum, planningDir);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function phaseInfo(phaseNum) {
|
|
353
|
+
return _phaseInfo(phaseNum, planningDir);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function phaseAdd(slug, afterPhase, options) {
|
|
357
|
+
return _phaseAdd(slug, afterPhase, planningDir, options);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function phaseRemove(phaseNum) {
|
|
361
|
+
return _phaseRemove(phaseNum, planningDir);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function phaseList() {
|
|
365
|
+
return _phaseList(planningDir);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function phaseComplete(phaseNum) {
|
|
369
|
+
return _phaseComplete(phaseNum, planningDir);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function phaseInsert(position, slug, options) {
|
|
373
|
+
return _phaseInsert(position, slug, planningDir, options);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function milestoneStats(version) {
|
|
377
|
+
return _milestoneStats(version, planningDir);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function initExecutePhase(phaseNum, overridePlanningDir, overrideModel) {
|
|
381
|
+
return _initExecutePhase(phaseNum, overridePlanningDir || planningDir, overrideModel);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function initPlanPhase(phaseNum, overridePlanningDir, overrideModel) {
|
|
385
|
+
return _initPlanPhase(phaseNum, overridePlanningDir || planningDir, overrideModel);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function initQuick(description) {
|
|
389
|
+
return _initQuick(description, planningDir);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function initVerifyWork(phaseNum, overridePlanningDir, overrideModel) {
|
|
393
|
+
return _initVerifyWork(phaseNum, overridePlanningDir || planningDir, overrideModel);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function initResume() {
|
|
397
|
+
return _initResume(planningDir);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function initProgress() {
|
|
401
|
+
return _initProgress(planningDir);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function stateBundle(phaseNum) {
|
|
405
|
+
return _initStateBundle(phaseNum, planningDir);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function historyAppend(entry, dir) {
|
|
409
|
+
return _historyAppend(entry, dir || planningDir);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function historyLoad(dir) {
|
|
413
|
+
return _historyLoad(dir || planningDir);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function todoList(opts) {
|
|
417
|
+
return _todoList(planningDir, opts);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function todoGet(num) {
|
|
421
|
+
return _todoGet(planningDir, num);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function todoAdd(title, opts) {
|
|
425
|
+
return _todoAdd(planningDir, title, opts);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function todoDone(num) {
|
|
429
|
+
return _todoDone(planningDir, num);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function autoCloseTodos(context) {
|
|
433
|
+
return _autoCloseTodos(planningDir, context);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function autoArchiveNotes(context) {
|
|
437
|
+
return _autoArchiveNotes(planningDir, context);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function migrate(options) {
|
|
441
|
+
return _applyMigrations(planningDir, options);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function spotCheck(phaseDir, planId) {
|
|
445
|
+
return _spotCheck(planningDir, phaseDir, planId);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function verifySpotCheck(type, dirPath) {
|
|
449
|
+
return _verifySpotCheck(type, dirPath);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function referenceGet(name, options) {
|
|
453
|
+
// Resolve plugin root — try CLAUDE_PLUGIN_ROOT env, then walk up from __dirname
|
|
454
|
+
const pluginRoot = process.env.CLAUDE_PLUGIN_ROOT || path.resolve(__dirname, '..');
|
|
455
|
+
// Fix MSYS paths on Windows (same pattern as run-hook.js)
|
|
456
|
+
let root = pluginRoot;
|
|
457
|
+
const msysMatch = root.match(/^\/([a-zA-Z])\/(.*)/);
|
|
458
|
+
if (msysMatch) root = msysMatch[1] + ':' + path.sep + msysMatch[2];
|
|
459
|
+
return _referenceGet(name, options, root);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function resolvePluginRoot() {
|
|
463
|
+
// Resolve plugin root — try CLAUDE_PLUGIN_ROOT env, then walk up from __dirname
|
|
464
|
+
const pluginRoot = process.env.CLAUDE_PLUGIN_ROOT || path.resolve(__dirname, '..');
|
|
465
|
+
// Fix MSYS paths on Windows (same pattern as run-hook.js)
|
|
466
|
+
let root = pluginRoot;
|
|
467
|
+
const msysMatch = root.match(/^\/([a-zA-Z])\/(.*)/);
|
|
468
|
+
if (msysMatch) root = msysMatch[1] + ':' + path.sep + msysMatch[2];
|
|
469
|
+
return root;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
function skillSectionGet(skillName, sectionQuery) {
|
|
473
|
+
return _skillSection(skillName, sectionQuery, resolvePluginRoot());
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function listSkillHeadings(skillName) {
|
|
477
|
+
const { listHeadings } = require('./lib/reference');
|
|
478
|
+
const root = resolvePluginRoot();
|
|
479
|
+
const skillPath = require('path').join(root, 'skills', skillName, 'SKILL.md');
|
|
480
|
+
if (!require('fs').existsSync(skillPath)) {
|
|
481
|
+
return { error: `Skill not found: ${skillName}`, available: _listAvailableSkills(root) };
|
|
122
482
|
}
|
|
483
|
+
const content = require('fs').readFileSync(skillPath, 'utf8');
|
|
484
|
+
return { skill: skillName, headings: listHeadings(content) };
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function contextTriage(options) {
|
|
488
|
+
return _contextTriage(options, planningDir);
|
|
123
489
|
}
|
|
124
490
|
|
|
491
|
+
function stalenessCheck(phaseSlug) { return _stalenessCheck(phaseSlug, planningDir); }
|
|
492
|
+
function summaryGate(phaseSlug, planId) { return _summaryGate(phaseSlug, planId, planningDir); }
|
|
493
|
+
function checkpointInit(phaseSlug, plans) { return _checkpointInit(phaseSlug, plans, planningDir); }
|
|
494
|
+
function checkpointUpdate(phaseSlug, opts) { return _checkpointUpdate(phaseSlug, opts, planningDir); }
|
|
495
|
+
function seedsMatch(phaseSlug, phaseNum) { return _seedsMatch(phaseSlug, phaseNum, planningDir); }
|
|
496
|
+
function ciPoll(runId, timeoutSecs) { return _ciPoll(runId, timeoutSecs, planningDir); }
|
|
497
|
+
function ciFix(options) { return _runCiFixLoop({ ...options, cwd: path.resolve('.') }); }
|
|
498
|
+
function rollbackPlan(manifestPath) { return _rollback(manifestPath, planningDir); }
|
|
499
|
+
|
|
500
|
+
function quickStatus() { return _quickStatus(planningDir); }
|
|
501
|
+
|
|
125
502
|
/**
|
|
126
|
-
*
|
|
127
|
-
*
|
|
503
|
+
* Build cleanup context from phase SUMMARY files and git log.
|
|
504
|
+
* @param {string} phaseNum - Phase number (e.g. "38")
|
|
505
|
+
* @returns {{ phaseName: string, phaseNum: string, keyFiles: string[], commitMessages: string[], summaryDescriptions: string[] }}
|
|
128
506
|
*/
|
|
129
|
-
function
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
507
|
+
function buildCleanupContext(phaseNum) {
|
|
508
|
+
const padded = String(phaseNum).padStart(2, '0');
|
|
509
|
+
const phasesDir = path.join(planningDir, 'phases');
|
|
510
|
+
if (!fs.existsSync(phasesDir)) throw new Error('No phases directory found');
|
|
511
|
+
|
|
512
|
+
const phaseDir = fs.readdirSync(phasesDir).find(d => d.startsWith(padded + '-'));
|
|
513
|
+
if (!phaseDir) throw new Error(`Phase ${phaseNum} directory not found`);
|
|
514
|
+
|
|
515
|
+
const phaseName = phaseDir.replace(/^\d+-/, '').replace(/-/g, ' ');
|
|
516
|
+
const phaseDirPath = path.join(phasesDir, phaseDir);
|
|
517
|
+
|
|
518
|
+
// Collect key_files and descriptions from all SUMMARY files
|
|
519
|
+
const keyFiles = [];
|
|
520
|
+
const summaryDescriptions = [];
|
|
521
|
+
const summaryFiles = fs.readdirSync(phaseDirPath).filter(f => /^SUMMARY/i.test(f) && f.endsWith('.md'));
|
|
522
|
+
for (const sf of summaryFiles) {
|
|
523
|
+
try {
|
|
524
|
+
const content = fs.readFileSync(path.join(phaseDirPath, sf), 'utf8');
|
|
525
|
+
const fm = parseYamlFrontmatter(content);
|
|
526
|
+
if (fm.key_files && Array.isArray(fm.key_files)) {
|
|
527
|
+
keyFiles.push(...fm.key_files.map(kf => typeof kf === 'string' ? kf.split(':')[0].trim() : ''));
|
|
528
|
+
}
|
|
529
|
+
if (fm.provides && Array.isArray(fm.provides)) {
|
|
530
|
+
summaryDescriptions.push(...fm.provides);
|
|
531
|
+
}
|
|
532
|
+
} catch (_e) { /* skip unreadable summaries */ }
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// Get recent commit messages
|
|
536
|
+
let commitMessages = [];
|
|
537
|
+
try {
|
|
538
|
+
const { execSync } = require('child_process');
|
|
539
|
+
const log = execSync('git log --oneline -20', { encoding: 'utf8', cwd: path.join(planningDir, '..') });
|
|
540
|
+
commitMessages = log.split('\n').filter(l => l.trim()).map(l => {
|
|
541
|
+
const parts = l.match(/^[0-9a-f]+\s+(.*)/);
|
|
542
|
+
return parts ? parts[1] : '';
|
|
543
|
+
}).filter(Boolean);
|
|
544
|
+
} catch (_e) { /* git not available */ }
|
|
545
|
+
|
|
546
|
+
return { phaseName, phaseNum: String(phaseNum), keyFiles, commitMessages, summaryDescriptions };
|
|
133
547
|
}
|
|
134
548
|
|
|
549
|
+
// --- Phase commit query functions ---
|
|
550
|
+
|
|
135
551
|
/**
|
|
136
|
-
* Read
|
|
137
|
-
*
|
|
138
|
-
* For JSONL files where full parsing is expensive, this avoids parsing
|
|
139
|
-
* all lines when you only need recent entries.
|
|
140
|
-
*
|
|
141
|
-
* @param {string} filePath - Absolute path to the file
|
|
142
|
-
* @param {number} n - Number of trailing lines to return
|
|
143
|
-
* @returns {string[]} Array of raw line strings (last n lines)
|
|
552
|
+
* Read .phase-manifest.json for phase N, output JSON array of commits.
|
|
553
|
+
* Falls back to git log if no manifest exists.
|
|
144
554
|
*/
|
|
145
|
-
function
|
|
555
|
+
function _phaseCommitsFor(phaseNum) {
|
|
556
|
+
const padded = String(phaseNum).padStart(2, '0');
|
|
557
|
+
const phasesDir = path.join(planningDir, 'phases');
|
|
558
|
+
if (!fs.existsSync(phasesDir)) return { error: 'No phases directory found' };
|
|
559
|
+
|
|
560
|
+
const phaseDir = fs.readdirSync(phasesDir).find(d => d.startsWith(padded + '-'));
|
|
561
|
+
if (!phaseDir) return { error: `Phase ${phaseNum} directory not found` };
|
|
562
|
+
|
|
563
|
+
const manifestPath = path.join(phasesDir, phaseDir, '.phase-manifest.json');
|
|
564
|
+
if (fs.existsSync(manifestPath)) {
|
|
565
|
+
try {
|
|
566
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
567
|
+
return { source: 'manifest', commits: manifest.commits || [] };
|
|
568
|
+
} catch (_e) { /* fall through to git log */ }
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// Fallback: scan git log for commits matching this phase scope
|
|
146
572
|
try {
|
|
147
|
-
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
573
|
+
const { execSync } = require('child_process');
|
|
574
|
+
const log = execSync('git log --oneline --no-merges -100', { encoding: 'utf8' });
|
|
575
|
+
const phasePattern = new RegExp(`\\((${padded}-|phase.*${phaseNum})`, 'i');
|
|
576
|
+
const commits = log.split('\n')
|
|
577
|
+
.filter(l => l.trim() && phasePattern.test(l))
|
|
578
|
+
.map(l => {
|
|
579
|
+
const parts = l.match(/^([0-9a-f]+)\s+(.*)/);
|
|
580
|
+
return parts ? { hash: parts[1], message: parts[2] } : null;
|
|
581
|
+
})
|
|
582
|
+
.filter(Boolean);
|
|
583
|
+
return { source: 'git_log', commits };
|
|
153
584
|
} catch (_e) {
|
|
154
|
-
return [];
|
|
585
|
+
return { error: 'Could not read git log', commits: [] };
|
|
155
586
|
}
|
|
156
587
|
}
|
|
157
588
|
|
|
158
589
|
/**
|
|
159
|
-
*
|
|
160
|
-
* for each depth level. User config.depth_profiles overrides these.
|
|
590
|
+
* Output { first, last } commit hashes from phase manifest or git log.
|
|
161
591
|
*/
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
}
|
|
592
|
+
function _phaseFirstLastCommit(phaseNum) {
|
|
593
|
+
const result = _phaseCommitsFor(phaseNum);
|
|
594
|
+
if (result.error && !result.commits) return result;
|
|
595
|
+
const commits = result.commits || [];
|
|
596
|
+
return {
|
|
597
|
+
source: result.source,
|
|
598
|
+
first: commits.length > 0 ? commits[0].hash : null,
|
|
599
|
+
last: commits.length > 0 ? commits[commits.length - 1].hash : null,
|
|
600
|
+
total: commits.length
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// --- Claim wrapper functions ---
|
|
605
|
+
|
|
606
|
+
function claimAcquire(phaseSlug, sessionId, skill) {
|
|
607
|
+
const phaseDir = path.join(planningDir, 'phases', phaseSlug);
|
|
608
|
+
if (!fs.existsSync(phaseDir)) return { error: `Phase directory not found: ${phaseSlug}` };
|
|
609
|
+
return acquireClaim(planningDir, phaseDir, sessionId, skill);
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
function claimRelease(phaseSlug, sessionId) {
|
|
613
|
+
const phaseDir = path.join(planningDir, 'phases', phaseSlug);
|
|
614
|
+
if (!fs.existsSync(phaseDir)) return { error: `Phase directory not found: ${phaseSlug}` };
|
|
615
|
+
return releaseClaim(planningDir, phaseDir, sessionId);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
function claimList() {
|
|
619
|
+
return _listClaims(planningDir);
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
// --- validateProject stays here (cross-cutting across modules) ---
|
|
191
623
|
|
|
192
624
|
/**
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
*
|
|
196
|
-
* @param {object|null} config - Parsed config.json (from configLoad). If null, returns 'standard' defaults.
|
|
197
|
-
* @returns {{ depth: string, profile: object }} The resolved depth name and flattened profile settings.
|
|
625
|
+
* Comprehensive .planning/ integrity check.
|
|
626
|
+
* Returns { valid, errors, warnings, checks } — errors mean workflow should not proceed.
|
|
198
627
|
*/
|
|
199
|
-
function
|
|
200
|
-
const
|
|
201
|
-
const
|
|
628
|
+
function validateProject() {
|
|
629
|
+
const checks = [];
|
|
630
|
+
const errors = [];
|
|
631
|
+
const warnings = [];
|
|
632
|
+
|
|
633
|
+
// 1. .planning/ directory exists
|
|
634
|
+
if (!fs.existsSync(planningDir)) {
|
|
635
|
+
return { valid: false, errors: ['.planning/ directory not found'], warnings: [], checks: ['directory_exists: FAIL'] };
|
|
636
|
+
}
|
|
637
|
+
checks.push('directory_exists: PASS');
|
|
638
|
+
|
|
639
|
+
// 2. config.json exists and is valid
|
|
640
|
+
const config = configLoad();
|
|
641
|
+
if (!config) {
|
|
642
|
+
errors.push('config.json missing or invalid JSON');
|
|
643
|
+
checks.push('config_valid: FAIL');
|
|
644
|
+
} else {
|
|
645
|
+
const configResult = configValidate(config);
|
|
646
|
+
if (!configResult.valid) {
|
|
647
|
+
errors.push(...configResult.errors.map(e => 'config: ' + e));
|
|
648
|
+
}
|
|
649
|
+
warnings.push(...(configResult.warnings || []).map(w => 'config: ' + w));
|
|
650
|
+
checks.push('config_valid: ' + (configResult.valid ? 'PASS' : 'FAIL'));
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// 3. STATE.md exists and has valid frontmatter
|
|
654
|
+
const statePath = path.join(planningDir, 'STATE.md');
|
|
655
|
+
if (!fs.existsSync(statePath)) {
|
|
656
|
+
errors.push('STATE.md not found');
|
|
657
|
+
checks.push('state_exists: FAIL');
|
|
658
|
+
} else {
|
|
659
|
+
try {
|
|
660
|
+
const stateContent = fs.readFileSync(statePath, 'utf8');
|
|
661
|
+
const fm = parseYamlFrontmatter(stateContent);
|
|
662
|
+
if (!fm || !fm.current_phase) {
|
|
663
|
+
warnings.push('STATE.md frontmatter missing current_phase');
|
|
664
|
+
checks.push('state_frontmatter: WARN');
|
|
665
|
+
} else {
|
|
666
|
+
checks.push('state_frontmatter: PASS');
|
|
667
|
+
}
|
|
668
|
+
} catch (e) {
|
|
669
|
+
errors.push('STATE.md unreadable: ' + e.message);
|
|
670
|
+
checks.push('state_readable: FAIL');
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
// 4. ROADMAP.md exists
|
|
675
|
+
const roadmapPath = path.join(planningDir, 'ROADMAP.md');
|
|
676
|
+
if (!fs.existsSync(roadmapPath)) {
|
|
677
|
+
warnings.push('ROADMAP.md not found (may be a new project)');
|
|
678
|
+
checks.push('roadmap_exists: WARN');
|
|
679
|
+
} else {
|
|
680
|
+
checks.push('roadmap_exists: PASS');
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
// 5. Phase directory matches STATE.md current_phase
|
|
684
|
+
try {
|
|
685
|
+
if (fs.existsSync(statePath)) {
|
|
686
|
+
const stateContent = fs.readFileSync(statePath, 'utf8');
|
|
687
|
+
const fm = parseYamlFrontmatter(stateContent);
|
|
688
|
+
if (fm && fm.current_phase) {
|
|
689
|
+
const phaseNum = String(fm.current_phase).padStart(2, '0');
|
|
690
|
+
const phasesDir = path.join(planningDir, 'phases');
|
|
691
|
+
if (fs.existsSync(phasesDir)) {
|
|
692
|
+
const dirs = fs.readdirSync(phasesDir).filter(d => d.startsWith(phaseNum + '-'));
|
|
693
|
+
if (dirs.length === 0) {
|
|
694
|
+
warnings.push(`Phase directory for current_phase ${fm.current_phase} not found in .planning/phases/`);
|
|
695
|
+
checks.push('phase_directory: WARN');
|
|
696
|
+
} else {
|
|
697
|
+
checks.push('phase_directory: PASS');
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
} catch (_e) { /* best effort */ }
|
|
703
|
+
|
|
704
|
+
// 6. No stale .active-skill (>2 hours old)
|
|
705
|
+
const activeSkillPath = path.join(planningDir, '.active-skill');
|
|
706
|
+
if (fs.existsSync(activeSkillPath)) {
|
|
707
|
+
try {
|
|
708
|
+
const stat = fs.statSync(activeSkillPath);
|
|
709
|
+
const ageMs = Date.now() - stat.mtimeMs;
|
|
710
|
+
if (ageMs > 2 * 60 * 60 * 1000) {
|
|
711
|
+
const ageHours = Math.round(ageMs / (60 * 60 * 1000));
|
|
712
|
+
warnings.push(`.active-skill is ${ageHours}h old — may be stale from a crashed session`);
|
|
713
|
+
checks.push('active_skill_fresh: WARN');
|
|
714
|
+
} else {
|
|
715
|
+
checks.push('active_skill_fresh: PASS');
|
|
716
|
+
}
|
|
717
|
+
} catch (_e) { checks.push('active_skill_fresh: SKIP'); }
|
|
718
|
+
} else {
|
|
719
|
+
checks.push('active_skill_fresh: SKIP');
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
// 7. No .tmp files left from atomic writes
|
|
723
|
+
try {
|
|
724
|
+
const tmpFiles = fs.readdirSync(planningDir).filter(f => f.endsWith('.tmp.' + process.pid) || f.match(/\.tmp\.\d+$/));
|
|
725
|
+
if (tmpFiles.length > 0) {
|
|
726
|
+
warnings.push(`Found ${tmpFiles.length} leftover temp files in .planning/: ${tmpFiles.join(', ')}`);
|
|
727
|
+
checks.push('no_temp_files: WARN');
|
|
728
|
+
} else {
|
|
729
|
+
checks.push('no_temp_files: PASS');
|
|
730
|
+
}
|
|
731
|
+
} catch (_e) { /* best effort */ }
|
|
732
|
+
|
|
733
|
+
// 8. Session directory scan — count active, flag stale
|
|
734
|
+
const sessionsResult = { count: 0, active: [], stale: [] };
|
|
735
|
+
const sessionsDir = path.join(planningDir, '.sessions');
|
|
736
|
+
try {
|
|
737
|
+
if (fs.existsSync(sessionsDir)) {
|
|
738
|
+
const entries = fs.readdirSync(sessionsDir, { withFileTypes: true });
|
|
739
|
+
for (const entry of entries) {
|
|
740
|
+
if (!entry.isDirectory()) continue;
|
|
741
|
+
sessionsResult.count++;
|
|
742
|
+
sessionsResult.active.push(entry.name);
|
|
743
|
+
|
|
744
|
+
// Check for staleness via meta.json
|
|
745
|
+
const metaPath = path.join(sessionsDir, entry.name, 'meta.json');
|
|
746
|
+
try {
|
|
747
|
+
const meta = JSON.parse(fs.readFileSync(metaPath, 'utf8'));
|
|
748
|
+
const ageMs = Date.now() - new Date(meta.created).getTime();
|
|
749
|
+
if (ageMs > STALE_SESSION_MS) {
|
|
750
|
+
sessionsResult.stale.push(entry.name);
|
|
751
|
+
}
|
|
752
|
+
} catch (_metaErr) {
|
|
753
|
+
// Fall back to directory mtime
|
|
754
|
+
try {
|
|
755
|
+
const stats = fs.statSync(path.join(sessionsDir, entry.name));
|
|
756
|
+
const ageMs = Date.now() - stats.mtimeMs;
|
|
757
|
+
if (ageMs > STALE_SESSION_MS) {
|
|
758
|
+
sessionsResult.stale.push(entry.name);
|
|
759
|
+
}
|
|
760
|
+
} catch (_statErr) { /* skip */ }
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
if (sessionsResult.stale.length > 0) {
|
|
765
|
+
warnings.push(`${sessionsResult.stale.length} stale session(s) found: ${sessionsResult.stale.join(', ')}. Run cleanStaleSessions to remove.`);
|
|
766
|
+
checks.push('sessions_stale: WARN');
|
|
767
|
+
} else {
|
|
768
|
+
checks.push('sessions_stale: PASS');
|
|
769
|
+
}
|
|
202
770
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
771
|
+
// Check for singleton .active-skill coexisting with session-scoped ones
|
|
772
|
+
if (fs.existsSync(path.join(planningDir, '.active-skill'))) {
|
|
773
|
+
let hasSessionSkill = false;
|
|
774
|
+
for (const sid of sessionsResult.active) {
|
|
775
|
+
if (fs.existsSync(path.join(sessionsDir, sid, '.active-skill'))) {
|
|
776
|
+
hasSessionSkill = true;
|
|
777
|
+
break;
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
if (hasSessionSkill) {
|
|
781
|
+
warnings.push('.active-skill exists at both singleton and session-scoped paths — possible migration artifact. Consider removing the singleton .planning/.active-skill.');
|
|
782
|
+
checks.push('active_skill_dual: WARN');
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
} catch (_e) { /* best effort */ }
|
|
206
787
|
|
|
207
|
-
return {
|
|
788
|
+
return {
|
|
789
|
+
valid: errors.length === 0,
|
|
790
|
+
errors,
|
|
791
|
+
warnings,
|
|
792
|
+
checks,
|
|
793
|
+
sessions: sessionsResult
|
|
794
|
+
};
|
|
208
795
|
}
|
|
209
796
|
|
|
210
|
-
|
|
797
|
+
// --- CLI entry point ---
|
|
798
|
+
|
|
799
|
+
async function main() {
|
|
211
800
|
const args = process.argv.slice(2);
|
|
212
801
|
const command = args[0];
|
|
213
802
|
const subcommand = args[1];
|
|
@@ -221,11 +810,27 @@ function main() {
|
|
|
221
810
|
const field = args[2];
|
|
222
811
|
const value = args[3];
|
|
223
812
|
if (!field || value === undefined) {
|
|
224
|
-
error('Usage: pbr-tools.js state update <field> <value>\nFields: current_phase, status, plans_complete, last_activity');
|
|
813
|
+
error('Usage: pbr-tools.js state update <field> <value>\nFields: current_phase, status, plans_complete, last_activity, progress_percent, phase_slug, last_command, blockers');
|
|
225
814
|
}
|
|
226
815
|
output(stateUpdate(field, value));
|
|
227
816
|
} else if (command === 'config' && subcommand === 'validate') {
|
|
228
817
|
output(configValidate());
|
|
818
|
+
} else if (command === 'validate' && subcommand === 'health') {
|
|
819
|
+
const { getAllPhase10Checks } = require('./lib/health-checks');
|
|
820
|
+
const checks = getAllPhase10Checks(planningDir);
|
|
821
|
+
output({ phase10: checks, timestamp: new Date().toISOString() });
|
|
822
|
+
} else if (command === 'config' && subcommand === 'load-defaults') {
|
|
823
|
+
const defaults = loadUserDefaults();
|
|
824
|
+
output(defaults || { exists: false, path: USER_DEFAULTS_PATH });
|
|
825
|
+
} else if (command === 'config' && subcommand === 'save-defaults') {
|
|
826
|
+
const config = configLoad();
|
|
827
|
+
if (!config) error('No config.json found. Run /pbr:setup first.');
|
|
828
|
+
output(saveUserDefaults(config));
|
|
829
|
+
} else if (command === 'config' && subcommand === 'format') {
|
|
830
|
+
const config = configLoad();
|
|
831
|
+
if (!config) error('No config.json found.');
|
|
832
|
+
_configWrite(planningDir, config);
|
|
833
|
+
output({ formatted: true, path: path.join(planningDir, 'config.json') });
|
|
229
834
|
} else if (command === 'config' && subcommand === 'resolve-depth') {
|
|
230
835
|
const dir = args[2] || undefined;
|
|
231
836
|
const config = configLoad(dir);
|
|
@@ -269,6 +874,8 @@ function main() {
|
|
|
269
874
|
error('Usage: pbr-tools.js roadmap update-plans <phase-number> <complete> <total>');
|
|
270
875
|
}
|
|
271
876
|
output(roadmapUpdatePlans(phase, complete, total));
|
|
877
|
+
} else if (command === 'roadmap' && subcommand === 'analyze') {
|
|
878
|
+
output(roadmapAnalyze());
|
|
272
879
|
} else if (command === 'history' && subcommand === 'append') {
|
|
273
880
|
const type = args[2]; // 'milestone' or 'phase'
|
|
274
881
|
const title = args[3];
|
|
@@ -292,1087 +899,627 @@ function main() {
|
|
|
292
899
|
const { logEvent } = require('./event-logger');
|
|
293
900
|
logEvent(category, event, details);
|
|
294
901
|
output({ logged: true, category, event });
|
|
295
|
-
} else {
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
902
|
+
} else if (command === 'llm' && subcommand === 'health') {
|
|
903
|
+
let rawConfig = {};
|
|
904
|
+
try { rawConfig = configLoad(planningDir) || {}; } catch (_e) { /* use defaults */ }
|
|
905
|
+
const llmConfig = resolveConfig(rawConfig.local_llm);
|
|
906
|
+
const health = await checkHealth(llmConfig);
|
|
907
|
+
output(health);
|
|
908
|
+
} else if (command === 'llm' && subcommand === 'status') {
|
|
909
|
+
let rawConfig = {};
|
|
910
|
+
try { rawConfig = configLoad(planningDir) || {}; } catch (_e) { /* use defaults */ }
|
|
911
|
+
const llmConfig = resolveConfig(rawConfig.local_llm);
|
|
912
|
+
output({
|
|
913
|
+
enabled: llmConfig.enabled,
|
|
914
|
+
model: llmConfig.model,
|
|
915
|
+
endpoint: llmConfig.endpoint,
|
|
916
|
+
features: llmConfig.features,
|
|
917
|
+
metrics_file: path.join(planningDir, 'logs', 'local-llm-metrics.jsonl'),
|
|
918
|
+
timeout_ms: llmConfig.timeout_ms,
|
|
919
|
+
disable_after_failures: llmConfig.advanced.disable_after_failures
|
|
920
|
+
});
|
|
921
|
+
} else if (command === 'llm' && subcommand === 'classify') {
|
|
922
|
+
const fileType = args[2];
|
|
923
|
+
const filePath = args[3];
|
|
924
|
+
if (!fileType || !filePath) {
|
|
925
|
+
error('Usage: pbr-tools.js llm classify <PLAN|SUMMARY> <filepath>');
|
|
926
|
+
}
|
|
927
|
+
const upperType = fileType.toUpperCase();
|
|
928
|
+
if (upperType !== 'PLAN' && upperType !== 'SUMMARY') {
|
|
929
|
+
error('llm classify: fileType must be PLAN or SUMMARY');
|
|
930
|
+
}
|
|
931
|
+
let content = '';
|
|
932
|
+
try {
|
|
933
|
+
content = fs.readFileSync(filePath, 'utf8');
|
|
934
|
+
} catch (_e) {
|
|
935
|
+
error('llm classify: cannot read file: ' + filePath);
|
|
936
|
+
}
|
|
937
|
+
let rawConfig = {};
|
|
938
|
+
try { rawConfig = configLoad(planningDir) || {}; } catch (_e) { /* use defaults */ }
|
|
939
|
+
const llmConfig = resolveConfig(rawConfig.local_llm);
|
|
940
|
+
const result = await classifyArtifact(llmConfig, planningDir, content, upperType, undefined);
|
|
941
|
+
output(result || { classification: null, reason: 'LLM disabled or unavailable' });
|
|
942
|
+
} else if (command === 'llm' && subcommand === 'score-source') {
|
|
943
|
+
const sourceUrl = args[2];
|
|
944
|
+
const filePath = args[3];
|
|
945
|
+
if (!sourceUrl || !filePath) {
|
|
946
|
+
error('Usage: pbr-tools.js llm score-source <url> <file-path>');
|
|
947
|
+
}
|
|
948
|
+
if (!fs.existsSync(filePath)) {
|
|
949
|
+
error('File not found: ' + filePath);
|
|
950
|
+
}
|
|
951
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
952
|
+
let rawConfig = {};
|
|
953
|
+
try { rawConfig = configLoad(planningDir) || {}; } catch (_e) { /* use defaults */ }
|
|
954
|
+
const llmConfig = resolveConfig(rawConfig.local_llm);
|
|
955
|
+
const result = await scoreSource(llmConfig, planningDir, content, sourceUrl, undefined);
|
|
956
|
+
output(result || { level: null, reason: 'LLM disabled or unavailable' });
|
|
957
|
+
} else if (command === 'llm' && subcommand === 'classify-error') {
|
|
958
|
+
const filePath = args[2];
|
|
959
|
+
const agentType = args[3] || 'unknown';
|
|
960
|
+
if (!filePath) {
|
|
961
|
+
error('Usage: pbr-tools.js llm classify-error <file-path> [agent-type]');
|
|
962
|
+
}
|
|
963
|
+
if (!fs.existsSync(filePath)) {
|
|
964
|
+
error('File not found: ' + filePath);
|
|
965
|
+
}
|
|
966
|
+
const errorText = fs.readFileSync(filePath, 'utf8');
|
|
967
|
+
let rawConfig = {};
|
|
968
|
+
try { rawConfig = configLoad(planningDir) || {}; } catch (_e) { /* use defaults */ }
|
|
969
|
+
const llmConfig = resolveConfig(rawConfig.local_llm);
|
|
970
|
+
const result = await classifyError(llmConfig, planningDir, errorText, agentType, undefined);
|
|
971
|
+
output(result || { category: null, reason: 'LLM disabled or unavailable' });
|
|
972
|
+
} else if (command === 'llm' && subcommand === 'summarize') {
|
|
973
|
+
const filePath = args[2];
|
|
974
|
+
const maxWords = args[3] ? parseInt(args[3], 10) : undefined;
|
|
975
|
+
if (!filePath) {
|
|
976
|
+
error('Usage: pbr-tools.js llm summarize <file-path> [max-words]');
|
|
977
|
+
}
|
|
978
|
+
if (!fs.existsSync(filePath)) {
|
|
979
|
+
error('File not found: ' + filePath);
|
|
980
|
+
}
|
|
981
|
+
const contextText = fs.readFileSync(filePath, 'utf8');
|
|
982
|
+
let rawConfig = {};
|
|
983
|
+
try { rawConfig = configLoad(planningDir) || {}; } catch (_e) { /* use defaults */ }
|
|
984
|
+
const llmConfig = resolveConfig(rawConfig.local_llm);
|
|
985
|
+
const result = await summarizeContext(llmConfig, planningDir, contextText, maxWords, undefined);
|
|
986
|
+
output(result || { summary: null, reason: 'LLM disabled or unavailable' });
|
|
987
|
+
} else if (command === 'llm' && subcommand === 'metrics') {
|
|
988
|
+
const sessionFlag = args[2]; // '--session'
|
|
989
|
+
const sessionStart = args[3]; // ISO timestamp
|
|
990
|
+
let rawConfig = {};
|
|
991
|
+
try { rawConfig = configLoad(planningDir) || {}; } catch (_e) { /* defaults */ }
|
|
992
|
+
const rate = rawConfig.local_llm && rawConfig.local_llm.metrics && rawConfig.local_llm.metrics.frontier_token_rate
|
|
993
|
+
? rawConfig.local_llm.metrics.frontier_token_rate : 3.0;
|
|
994
|
+
if (sessionFlag === '--session' && sessionStart) {
|
|
995
|
+
const entries = readSessionMetrics(planningDir, sessionStart);
|
|
996
|
+
const summary = summarizeMetrics(entries, rate);
|
|
997
|
+
output({ scope: 'session', session_start: sessionStart, ...summary });
|
|
998
|
+
} else {
|
|
999
|
+
const lifetime = computeLifetimeMetrics(planningDir, rate);
|
|
1000
|
+
output({ scope: 'lifetime', ...lifetime });
|
|
1001
|
+
}
|
|
1002
|
+
} else if (command === 'llm' && subcommand === 'adjust-thresholds') {
|
|
1003
|
+
let rawConfig = {};
|
|
1004
|
+
try { rawConfig = configLoad(planningDir) || {}; } catch (_e) { /* use defaults */ }
|
|
1005
|
+
const llmConfig = resolveConfig(rawConfig.local_llm);
|
|
1006
|
+
const currentThreshold = llmConfig.advanced.confidence_threshold;
|
|
1007
|
+
const suggestions = computeThresholdAdjustments(planningDir, currentThreshold);
|
|
1008
|
+
output(suggestions.length > 0
|
|
1009
|
+
? { suggestions }
|
|
1010
|
+
: { suggestions: [], message: 'Not enough shadow samples yet (need >= 20 per operation)' });
|
|
1011
|
+
// --- Compound init commands ---
|
|
1012
|
+
} else if (command === "init" && subcommand === "execute-phase") {
|
|
1013
|
+
const phase = args[2];
|
|
1014
|
+
if (!phase) error("Usage: pbr-tools.js init execute-phase <phase-number>");
|
|
1015
|
+
output(initExecutePhase(phase));
|
|
1016
|
+
} else if (command === "init" && subcommand === "plan-phase") {
|
|
1017
|
+
const phase = args[2];
|
|
1018
|
+
if (!phase) error("Usage: pbr-tools.js init plan-phase <phase-number>");
|
|
1019
|
+
output(initPlanPhase(phase));
|
|
1020
|
+
} else if (command === "init" && subcommand === "quick") {
|
|
1021
|
+
const desc = args.slice(2).join(" ") || "";
|
|
1022
|
+
output(initQuick(desc));
|
|
1023
|
+
} else if (command === "init" && subcommand === "verify-work") {
|
|
1024
|
+
const phase = args[2];
|
|
1025
|
+
if (!phase) error("Usage: pbr-tools.js init verify-work <phase-number>");
|
|
1026
|
+
output(initVerifyWork(phase));
|
|
1027
|
+
} else if (command === "init" && subcommand === "resume") {
|
|
1028
|
+
output(initResume());
|
|
1029
|
+
} else if (command === "init" && subcommand === "progress") {
|
|
1030
|
+
output(initProgress());
|
|
1031
|
+
} else if (command === 'state-bundle') {
|
|
1032
|
+
const phaseNum = args[1];
|
|
1033
|
+
if (!phaseNum) error('Usage: pbr-tools.js state-bundle <phase-number>');
|
|
1034
|
+
output(stateBundle(phaseNum));
|
|
1035
|
+
// --- State patch/advance/metric ---
|
|
1036
|
+
} else if (command === "state" && subcommand === "patch") {
|
|
1037
|
+
const jsonStr = args[2];
|
|
1038
|
+
if (!jsonStr) error("Usage: pbr-tools.js state patch JSON");
|
|
1039
|
+
output(statePatch(jsonStr));
|
|
1040
|
+
} else if (command === "state" && subcommand === "advance-plan") {
|
|
1041
|
+
output(stateAdvancePlan());
|
|
1042
|
+
} else if (command === "state" && subcommand === "record-metric") {
|
|
1043
|
+
output(stateRecordMetric(args.slice(2)));
|
|
1044
|
+
} else if (command === "state" && subcommand === "record-activity") {
|
|
1045
|
+
const description = args.slice(2).join(' ');
|
|
1046
|
+
if (!description) error("Usage: pbr-tools.js state record-activity <description>");
|
|
1047
|
+
output(stateRecordActivity(description));
|
|
1048
|
+
} else if (command === "state" && subcommand === "update-progress") {
|
|
1049
|
+
output(stateUpdateProgress());
|
|
1050
|
+
} else if (command === 'phase' && subcommand === 'add') {
|
|
1051
|
+
const slug = args[2];
|
|
1052
|
+
if (!slug) { error('Usage: phase add <slug> [--after N] [--goal "..."] [--depends-on N]'); }
|
|
1053
|
+
const afterIdx = args.indexOf('--after');
|
|
1054
|
+
const afterPhase = afterIdx !== -1 ? args[afterIdx + 1] : null;
|
|
1055
|
+
const goalIdx = args.indexOf('--goal');
|
|
1056
|
+
const goal = goalIdx !== -1 ? args[goalIdx + 1] : null;
|
|
1057
|
+
const depIdx = args.indexOf('--depends-on');
|
|
1058
|
+
const dependsOn = depIdx !== -1 ? args[depIdx + 1] : null;
|
|
1059
|
+
const addOpts = {};
|
|
1060
|
+
if (goal) addOpts.goal = goal;
|
|
1061
|
+
if (dependsOn) addOpts.dependsOn = dependsOn;
|
|
1062
|
+
output(phaseAdd(slug, afterPhase, Object.keys(addOpts).length > 0 ? addOpts : undefined));
|
|
1063
|
+
} else if (command === 'phase' && subcommand === 'remove') {
|
|
1064
|
+
const phaseNum = args[2];
|
|
1065
|
+
if (!phaseNum) { error('Usage: phase remove <phase_num>'); }
|
|
1066
|
+
output(phaseRemove(phaseNum));
|
|
1067
|
+
} else if (command === 'phase' && subcommand === 'list') {
|
|
1068
|
+
output(phaseList());
|
|
1069
|
+
} else if (command === 'phase' && subcommand === 'complete') {
|
|
1070
|
+
const phaseNum = args[2];
|
|
1071
|
+
if (!phaseNum) { error('Usage: phase complete <phase_num>'); }
|
|
1072
|
+
output(phaseComplete(phaseNum));
|
|
1073
|
+
} else if (command === 'phase' && subcommand === 'insert') {
|
|
1074
|
+
const position = args[2];
|
|
1075
|
+
const slug = args[3];
|
|
1076
|
+
if (!position || !slug) { error('Usage: phase insert <N> <slug> [--goal "..."] [--depends-on N]'); }
|
|
1077
|
+
const goalIdx = args.indexOf('--goal');
|
|
1078
|
+
const goal = goalIdx !== -1 ? args[goalIdx + 1] : null;
|
|
1079
|
+
const depIdx = args.indexOf('--depends-on');
|
|
1080
|
+
const dependsOn = depIdx !== -1 ? args[depIdx + 1] : null;
|
|
1081
|
+
const insertOpts = {};
|
|
1082
|
+
if (goal) insertOpts.goal = goal;
|
|
1083
|
+
if (dependsOn) insertOpts.dependsOn = dependsOn;
|
|
1084
|
+
output(phaseInsert(parseInt(position, 10), slug, Object.keys(insertOpts).length > 0 ? insertOpts : undefined));
|
|
1085
|
+
} else if (command === 'phase' && subcommand === 'commits-for') {
|
|
1086
|
+
const phaseNum = args[2];
|
|
1087
|
+
if (!phaseNum) { error('Usage: phase commits-for <N>'); }
|
|
1088
|
+
output(_phaseCommitsFor(phaseNum));
|
|
1089
|
+
} else if (command === 'phase' && subcommand === 'first-last-commit') {
|
|
1090
|
+
const phaseNum = args[2];
|
|
1091
|
+
if (!phaseNum) { error('Usage: phase first-last-commit <N>'); }
|
|
1092
|
+
output(_phaseFirstLastCommit(phaseNum));
|
|
1093
|
+
} else if (command === 'todo' && subcommand === 'list') {
|
|
1094
|
+
const opts = {};
|
|
1095
|
+
const themeIdx = args.indexOf('--theme');
|
|
1096
|
+
if (themeIdx !== -1 && args[themeIdx + 1]) opts.theme = args[themeIdx + 1];
|
|
1097
|
+
const statusIdx = args.indexOf('--status');
|
|
1098
|
+
if (statusIdx !== -1 && args[statusIdx + 1]) opts.status = args[statusIdx + 1];
|
|
1099
|
+
output(todoList(opts));
|
|
1100
|
+
} else if (command === 'todo' && subcommand === 'get') {
|
|
1101
|
+
const num = args[2];
|
|
1102
|
+
if (!num) error('Usage: pbr-tools.js todo get <NNN>');
|
|
1103
|
+
output(todoGet(num));
|
|
1104
|
+
} else if (command === 'todo' && subcommand === 'add') {
|
|
1105
|
+
const titleParts = [];
|
|
1106
|
+
const opts = {};
|
|
1107
|
+
// Parse: todo add <title words...> [--priority P1] [--theme security] [--source cli]
|
|
1108
|
+
for (let i = 2; i < args.length; i++) {
|
|
1109
|
+
if (args[i] === '--priority' && args[i + 1]) { opts.priority = args[++i]; }
|
|
1110
|
+
else if (args[i] === '--theme' && args[i + 1]) { opts.theme = args[++i]; }
|
|
1111
|
+
else if (args[i] === '--source' && args[i + 1]) { opts.source = args[++i]; }
|
|
1112
|
+
else { titleParts.push(args[i]); }
|
|
1113
|
+
}
|
|
1114
|
+
const title = titleParts.join(' ');
|
|
1115
|
+
if (!title) error('Usage: pbr-tools.js todo add <title> [--priority P1|P2|P3] [--theme <theme>]');
|
|
1116
|
+
output(todoAdd(title, opts));
|
|
1117
|
+
} else if (command === 'todo' && subcommand === 'done') {
|
|
1118
|
+
const num = args[2];
|
|
1119
|
+
if (!num) error('Usage: pbr-tools.js todo done <NNN>');
|
|
1120
|
+
output(todoDone(num));
|
|
1121
|
+
} else if (command === 'auto-cleanup') {
|
|
1122
|
+
const phaseFlag = args.indexOf('--phase');
|
|
1123
|
+
const milestoneFlag = args.indexOf('--milestone');
|
|
1124
|
+
if (phaseFlag !== -1 && args[phaseFlag + 1]) {
|
|
1125
|
+
const phaseNum = args[phaseFlag + 1];
|
|
1126
|
+
const context = buildCleanupContext(phaseNum);
|
|
1127
|
+
const todoResult = autoCloseTodos(context);
|
|
1128
|
+
const noteResult = autoArchiveNotes(context);
|
|
1129
|
+
output({ phase: phaseNum, todos: todoResult, notes: noteResult });
|
|
1130
|
+
} else if (milestoneFlag !== -1 && args[milestoneFlag + 1]) {
|
|
1131
|
+
const version = args[milestoneFlag + 1];
|
|
1132
|
+
// Parse ROADMAP.md to find phases in this milestone
|
|
1133
|
+
const roadmapPath = path.join(planningDir, 'ROADMAP.md');
|
|
1134
|
+
if (!fs.existsSync(roadmapPath)) { error('ROADMAP.md not found'); }
|
|
1135
|
+
const roadmap = fs.readFileSync(roadmapPath, 'utf8');
|
|
1136
|
+
const milestoneMatch = roadmap.match(new RegExp('Milestone.*' + version.replace(/\./g, '\\.') + '[\\s\\S]*?Phases:\\s*(\\d+)\\s*-\\s*(\\d+)'));
|
|
1137
|
+
if (!milestoneMatch) { error('Milestone ' + version + ' not found in ROADMAP.md'); }
|
|
1138
|
+
const startPhase = parseInt(milestoneMatch[1]);
|
|
1139
|
+
const endPhase = parseInt(milestoneMatch[2]);
|
|
1140
|
+
const allResults = { milestone: version, phases: [] };
|
|
1141
|
+
for (let p = startPhase; p <= endPhase; p++) {
|
|
1142
|
+
try {
|
|
1143
|
+
const ctx = buildCleanupContext(String(p));
|
|
1144
|
+
const todoRes = autoCloseTodos(ctx);
|
|
1145
|
+
const noteRes = autoArchiveNotes(ctx);
|
|
1146
|
+
allResults.phases.push({ phase: p, todos: todoRes, notes: noteRes });
|
|
1147
|
+
} catch (_e) { /* skip phases without SUMMARY */ }
|
|
1148
|
+
}
|
|
1149
|
+
output(allResults);
|
|
1150
|
+
} else {
|
|
1151
|
+
error('Usage: auto-cleanup --phase N | --milestone vN');
|
|
1152
|
+
}
|
|
1153
|
+
} else if (command === 'migrate') {
|
|
1154
|
+
const dryRun = args.includes('--dry-run');
|
|
1155
|
+
const force = args.includes('--force');
|
|
1156
|
+
const result = await migrate({ dryRun, force });
|
|
1157
|
+
output(result);
|
|
1158
|
+
} else if (command === 'learnings') {
|
|
1159
|
+
const subCmd = args[1];
|
|
1160
|
+
|
|
1161
|
+
if (subCmd === 'ingest') {
|
|
1162
|
+
// learnings ingest <json-file-path>
|
|
1163
|
+
const jsonFile = args[2];
|
|
1164
|
+
if (!jsonFile) { error('Usage: learnings ingest <json-file>'); process.exit(1); }
|
|
1165
|
+
const raw = fs.readFileSync(jsonFile, 'utf8');
|
|
1166
|
+
const entry = JSON.parse(raw);
|
|
1167
|
+
const result = _learningsIngest(entry);
|
|
1168
|
+
output(result);
|
|
1169
|
+
|
|
1170
|
+
} else if (subCmd === 'query') {
|
|
1171
|
+
// learnings query [--tags tag1,tag2] [--min-confidence low|medium|high] [--stack react] [--type tech-pattern]
|
|
1172
|
+
const filters = {};
|
|
1173
|
+
for (let i = 2; i < args.length; i++) {
|
|
1174
|
+
if (args[i] === '--tags' && args[i + 1]) { filters.tags = args[++i].split(',').map(t => t.trim()); }
|
|
1175
|
+
else if (args[i] === '--min-confidence' && args[i + 1]) { filters.minConfidence = args[++i]; }
|
|
1176
|
+
else if (args[i] === '--stack' && args[i + 1]) { filters.stack = args[++i]; }
|
|
1177
|
+
else if (args[i] === '--type' && args[i + 1]) { filters.type = args[++i]; }
|
|
1178
|
+
}
|
|
1179
|
+
const results = _learningsQuery(filters);
|
|
1180
|
+
output(results);
|
|
1181
|
+
|
|
1182
|
+
} else if (subCmd === 'check-thresholds') {
|
|
1183
|
+
// learnings check-thresholds — for progress-tracker to call
|
|
1184
|
+
const triggered = _checkDeferralThresholds();
|
|
1185
|
+
output(triggered);
|
|
1186
|
+
|
|
1187
|
+
} else if (subCmd === 'copy-global') {
|
|
1188
|
+
const filePath = args[2];
|
|
1189
|
+
const projectName = args[3];
|
|
1190
|
+
if (!filePath || !projectName) { error('Usage: learnings copy-global <learnings-md-path> <project-name>'); process.exit(1); }
|
|
1191
|
+
output(_copyToGlobal(filePath, projectName));
|
|
1192
|
+
|
|
1193
|
+
} else if (subCmd === 'query-global') {
|
|
1194
|
+
const filters = {};
|
|
1195
|
+
for (let i = 2; i < args.length; i++) {
|
|
1196
|
+
if (args[i] === '--tags' && args[i + 1]) { filters.tags = args[++i].split(',').map(t => t.trim()); }
|
|
1197
|
+
else if (args[i] === '--project' && args[i + 1]) { filters.project = args[++i]; }
|
|
1198
|
+
}
|
|
1199
|
+
output(_queryGlobal(filters));
|
|
337
1200
|
|
|
338
|
-
// Load ROADMAP.md
|
|
339
|
-
const roadmapPath = path.join(planningDir, 'ROADMAP.md');
|
|
340
|
-
if (fs.existsSync(roadmapPath)) {
|
|
341
|
-
const content = fs.readFileSync(roadmapPath, 'utf8');
|
|
342
|
-
result.roadmap = parseRoadmapMd(content);
|
|
343
|
-
result.phase_count = result.roadmap.phases.length;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
// Extract current phase
|
|
347
|
-
if (result.state && result.state.current_phase) {
|
|
348
|
-
result.current_phase = result.state.current_phase;
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
// Calculate progress
|
|
352
|
-
result.progress = calculateProgress();
|
|
353
|
-
|
|
354
|
-
return result;
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
function stateCheckProgress() {
|
|
358
|
-
const phasesDir = path.join(planningDir, 'phases');
|
|
359
|
-
if (!fs.existsSync(phasesDir)) {
|
|
360
|
-
return { phases: [], total_plans: 0, completed_plans: 0, percentage: 0 };
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
const phases = [];
|
|
364
|
-
let totalPlans = 0;
|
|
365
|
-
let completedPlans = 0;
|
|
366
|
-
|
|
367
|
-
const entries = fs.readdirSync(phasesDir, { withFileTypes: true })
|
|
368
|
-
.filter(e => e.isDirectory())
|
|
369
|
-
.sort((a, b) => a.name.localeCompare(b.name));
|
|
370
|
-
|
|
371
|
-
for (const entry of entries) {
|
|
372
|
-
const phaseDir = path.join(phasesDir, entry.name);
|
|
373
|
-
const plans = findFiles(phaseDir, /-PLAN\.md$/);
|
|
374
|
-
const summaries = findFiles(phaseDir, /^SUMMARY-.*\.md$/);
|
|
375
|
-
const verification = fs.existsSync(path.join(phaseDir, 'VERIFICATION.md'));
|
|
376
|
-
|
|
377
|
-
const completedSummaries = summaries.filter(s => {
|
|
378
|
-
const content = fs.readFileSync(path.join(phaseDir, s), 'utf8');
|
|
379
|
-
return /status:\s*["']?complete/i.test(content);
|
|
380
|
-
});
|
|
381
|
-
|
|
382
|
-
const phaseInfo = {
|
|
383
|
-
directory: entry.name,
|
|
384
|
-
plans: plans.length,
|
|
385
|
-
summaries: summaries.length,
|
|
386
|
-
completed: completedSummaries.length,
|
|
387
|
-
has_verification: verification,
|
|
388
|
-
status: determinePhaseStatus(plans.length, completedSummaries.length, summaries.length, verification, phaseDir)
|
|
389
|
-
};
|
|
390
|
-
|
|
391
|
-
phases.push(phaseInfo);
|
|
392
|
-
totalPlans += plans.length;
|
|
393
|
-
completedPlans += completedSummaries.length;
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
return {
|
|
397
|
-
phases,
|
|
398
|
-
total_plans: totalPlans,
|
|
399
|
-
completed_plans: completedPlans,
|
|
400
|
-
percentage: totalPlans > 0 ? Math.round((completedPlans / totalPlans) * 100) : 0
|
|
401
|
-
};
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
function planIndex(phaseNum) {
|
|
405
|
-
const phasesDir = path.join(planningDir, 'phases');
|
|
406
|
-
if (!fs.existsSync(phasesDir)) {
|
|
407
|
-
return { error: 'No phases directory found' };
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
// Find phase directory matching the number
|
|
411
|
-
const entries = fs.readdirSync(phasesDir, { withFileTypes: true })
|
|
412
|
-
.filter(e => e.isDirectory());
|
|
413
|
-
|
|
414
|
-
const phaseDir = entries.find(e => e.name.startsWith(phaseNum.padStart(2, '0') + '-'));
|
|
415
|
-
if (!phaseDir) {
|
|
416
|
-
return { error: `No phase directory found matching phase ${phaseNum}` };
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
const fullDir = path.join(phasesDir, phaseDir.name);
|
|
420
|
-
const planFiles = findFiles(fullDir, /-PLAN\.md$/);
|
|
421
|
-
|
|
422
|
-
const plans = [];
|
|
423
|
-
const waves = {};
|
|
424
|
-
|
|
425
|
-
for (const file of planFiles) {
|
|
426
|
-
const content = fs.readFileSync(path.join(fullDir, file), 'utf8');
|
|
427
|
-
const frontmatter = parseYamlFrontmatter(content);
|
|
428
|
-
|
|
429
|
-
const plan = {
|
|
430
|
-
file,
|
|
431
|
-
plan_id: frontmatter.plan || file.replace(/-PLAN\.md$/, ''),
|
|
432
|
-
wave: parseInt(frontmatter.wave, 10) || 1,
|
|
433
|
-
type: frontmatter.type || 'unknown',
|
|
434
|
-
autonomous: frontmatter.autonomous !== false,
|
|
435
|
-
depends_on: frontmatter.depends_on || [],
|
|
436
|
-
gap_closure: frontmatter.gap_closure || false,
|
|
437
|
-
has_summary: fs.existsSync(path.join(fullDir, `SUMMARY-${frontmatter.plan || ''}.md`)),
|
|
438
|
-
must_haves_count: countMustHaves(frontmatter.must_haves)
|
|
439
|
-
};
|
|
440
|
-
|
|
441
|
-
plans.push(plan);
|
|
442
|
-
|
|
443
|
-
const waveKey = `wave_${plan.wave}`;
|
|
444
|
-
if (!waves[waveKey]) waves[waveKey] = [];
|
|
445
|
-
waves[waveKey].push(plan.plan_id);
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
return {
|
|
449
|
-
phase: phaseDir.name,
|
|
450
|
-
total_plans: plans.length,
|
|
451
|
-
plans,
|
|
452
|
-
waves
|
|
453
|
-
};
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
function configValidate(preloadedConfig) {
|
|
457
|
-
let config;
|
|
458
|
-
if (preloadedConfig) {
|
|
459
|
-
config = preloadedConfig;
|
|
460
|
-
} else {
|
|
461
|
-
const configPath = path.join(planningDir, 'config.json');
|
|
462
|
-
if (!fs.existsSync(configPath)) {
|
|
463
|
-
return { valid: false, errors: ['config.json not found'], warnings: [] };
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
try {
|
|
467
|
-
config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
468
|
-
} catch (e) {
|
|
469
|
-
return { valid: false, errors: [`config.json is not valid JSON: ${e.message}`], warnings: [] };
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
const schema = JSON.parse(fs.readFileSync(path.join(__dirname, 'config-schema.json'), 'utf8'));
|
|
474
|
-
const warnings = [];
|
|
475
|
-
const errors = [];
|
|
476
|
-
|
|
477
|
-
validateObject(config, schema, '', errors, warnings);
|
|
478
|
-
|
|
479
|
-
// Semantic conflict detection — logical contradictions that pass schema validation
|
|
480
|
-
// Clear contradictions → errors; ambiguous/preference issues → warnings
|
|
481
|
-
if (config.mode === 'autonomous' && config.gates) {
|
|
482
|
-
const activeGates = Object.entries(config.gates || {}).filter(([, v]) => v === true).map(([k]) => k);
|
|
483
|
-
if (activeGates.length > 0) {
|
|
484
|
-
errors.push(`mode=autonomous with active gates (${activeGates.join(', ')}): gates are unreachable in autonomous mode`);
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
if (config.features && config.features.auto_continue && config.mode === 'interactive') {
|
|
488
|
-
warnings.push('features.auto_continue=true with mode=interactive: auto_continue only fires in autonomous mode');
|
|
489
|
-
}
|
|
490
|
-
if (config.parallelization) {
|
|
491
|
-
if (config.parallelization.enabled === false && config.parallelization.plan_level === true) {
|
|
492
|
-
warnings.push('parallelization.enabled=false with plan_level=true: plan_level is ignored when parallelization is disabled');
|
|
493
|
-
}
|
|
494
|
-
if (config.parallelization.max_concurrent_agents === 1 && config.teams && config.teams.coordination) {
|
|
495
|
-
errors.push('parallelization.max_concurrent_agents=1 with teams.coordination set: teams require concurrent agents to be useful');
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
return {
|
|
500
|
-
valid: errors.length === 0,
|
|
501
|
-
errors,
|
|
502
|
-
warnings
|
|
503
|
-
};
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
// --- New read-only commands ---
|
|
507
|
-
|
|
508
|
-
/**
|
|
509
|
-
* Parse a markdown file's YAML frontmatter and return as JSON.
|
|
510
|
-
* Wraps parseYamlFrontmatter() + parseMustHaves().
|
|
511
|
-
*/
|
|
512
|
-
function frontmatter(filePath) {
|
|
513
|
-
const resolved = path.resolve(filePath);
|
|
514
|
-
if (!fs.existsSync(resolved)) {
|
|
515
|
-
return { error: `File not found: ${resolved}` };
|
|
516
|
-
}
|
|
517
|
-
const content = fs.readFileSync(resolved, 'utf8');
|
|
518
|
-
return parseYamlFrontmatter(content);
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
/**
|
|
522
|
-
* Collect all must-haves from all PLAN.md files in a phase.
|
|
523
|
-
* Returns per-plan grouping + flat deduplicated list + total count.
|
|
524
|
-
*/
|
|
525
|
-
function mustHavesCollect(phaseNum) {
|
|
526
|
-
const phasesDir = path.join(planningDir, 'phases');
|
|
527
|
-
if (!fs.existsSync(phasesDir)) {
|
|
528
|
-
return { error: 'No phases directory found' };
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
const entries = fs.readdirSync(phasesDir, { withFileTypes: true })
|
|
532
|
-
.filter(e => e.isDirectory());
|
|
533
|
-
const phaseDir = entries.find(e => e.name.startsWith(phaseNum.padStart(2, '0') + '-'));
|
|
534
|
-
if (!phaseDir) {
|
|
535
|
-
return { error: `No phase directory found matching phase ${phaseNum}` };
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
const fullDir = path.join(phasesDir, phaseDir.name);
|
|
539
|
-
const planFiles = findFiles(fullDir, /-PLAN\.md$/);
|
|
540
|
-
|
|
541
|
-
const perPlan = {};
|
|
542
|
-
const allTruths = new Set();
|
|
543
|
-
const allArtifacts = new Set();
|
|
544
|
-
const allKeyLinks = new Set();
|
|
545
|
-
|
|
546
|
-
for (const file of planFiles) {
|
|
547
|
-
const content = fs.readFileSync(path.join(fullDir, file), 'utf8');
|
|
548
|
-
const fm = parseYamlFrontmatter(content);
|
|
549
|
-
const planId = fm.plan || file.replace(/-PLAN\.md$/, '');
|
|
550
|
-
const mh = fm.must_haves || { truths: [], artifacts: [], key_links: [] };
|
|
551
|
-
|
|
552
|
-
perPlan[planId] = mh;
|
|
553
|
-
(mh.truths || []).forEach(t => allTruths.add(t));
|
|
554
|
-
(mh.artifacts || []).forEach(a => allArtifacts.add(a));
|
|
555
|
-
(mh.key_links || []).forEach(k => allKeyLinks.add(k));
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
const all = {
|
|
559
|
-
truths: [...allTruths],
|
|
560
|
-
artifacts: [...allArtifacts],
|
|
561
|
-
key_links: [...allKeyLinks]
|
|
562
|
-
};
|
|
563
|
-
|
|
564
|
-
return {
|
|
565
|
-
phase: phaseDir.name,
|
|
566
|
-
plans: perPlan,
|
|
567
|
-
all,
|
|
568
|
-
total: all.truths.length + all.artifacts.length + all.key_links.length
|
|
569
|
-
};
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
/**
|
|
573
|
-
* Comprehensive single-phase status combining roadmap, filesystem, and plan data.
|
|
574
|
-
*/
|
|
575
|
-
function phaseInfo(phaseNum) {
|
|
576
|
-
const phasesDir = path.join(planningDir, 'phases');
|
|
577
|
-
if (!fs.existsSync(phasesDir)) {
|
|
578
|
-
return { error: 'No phases directory found' };
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
const entries = fs.readdirSync(phasesDir, { withFileTypes: true })
|
|
582
|
-
.filter(e => e.isDirectory());
|
|
583
|
-
const phaseDir = entries.find(e => e.name.startsWith(phaseNum.padStart(2, '0') + '-'));
|
|
584
|
-
if (!phaseDir) {
|
|
585
|
-
return { error: `No phase directory found matching phase ${phaseNum}` };
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
const fullDir = path.join(phasesDir, phaseDir.name);
|
|
589
|
-
|
|
590
|
-
// Get roadmap info
|
|
591
|
-
let roadmapInfo = null;
|
|
592
|
-
const roadmapPath = path.join(planningDir, 'ROADMAP.md');
|
|
593
|
-
if (fs.existsSync(roadmapPath)) {
|
|
594
|
-
const roadmapContent = fs.readFileSync(roadmapPath, 'utf8');
|
|
595
|
-
const roadmap = parseRoadmapMd(roadmapContent);
|
|
596
|
-
roadmapInfo = roadmap.phases.find(p => p.number === phaseNum.padStart(2, '0')) || null;
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
// Get plan index
|
|
600
|
-
const plans = planIndex(phaseNum);
|
|
601
|
-
|
|
602
|
-
// Check for verification
|
|
603
|
-
const verificationPath = path.join(fullDir, 'VERIFICATION.md');
|
|
604
|
-
let verification = null;
|
|
605
|
-
if (fs.existsSync(verificationPath)) {
|
|
606
|
-
const vContent = fs.readFileSync(verificationPath, 'utf8');
|
|
607
|
-
verification = parseYamlFrontmatter(vContent);
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
// Check summaries
|
|
611
|
-
const summaryFiles = findFiles(fullDir, /^SUMMARY-.*\.md$/);
|
|
612
|
-
const summaries = summaryFiles.map(f => {
|
|
613
|
-
const content = fs.readFileSync(path.join(fullDir, f), 'utf8');
|
|
614
|
-
const fm = parseYamlFrontmatter(content);
|
|
615
|
-
return { file: f, plan: fm.plan || f.replace(/^SUMMARY-|\.md$/g, ''), status: fm.status || 'unknown' };
|
|
616
|
-
});
|
|
617
|
-
|
|
618
|
-
// Determine filesystem status
|
|
619
|
-
const planCount = plans.total_plans || 0;
|
|
620
|
-
const completedCount = summaries.filter(s => s.status === 'complete').length;
|
|
621
|
-
const hasVerification = fs.existsSync(verificationPath);
|
|
622
|
-
const fsStatus = determinePhaseStatus(planCount, completedCount, summaryFiles.length, hasVerification, fullDir);
|
|
623
|
-
|
|
624
|
-
return {
|
|
625
|
-
phase: phaseDir.name,
|
|
626
|
-
name: roadmapInfo ? roadmapInfo.name : phaseDir.name.replace(/^\d+-/, ''),
|
|
627
|
-
goal: roadmapInfo ? roadmapInfo.goal : null,
|
|
628
|
-
roadmap_status: roadmapInfo ? roadmapInfo.status : null,
|
|
629
|
-
filesystem_status: fsStatus,
|
|
630
|
-
plans: plans.plans || [],
|
|
631
|
-
plan_count: planCount,
|
|
632
|
-
summaries,
|
|
633
|
-
completed: completedCount,
|
|
634
|
-
verification,
|
|
635
|
-
has_context: fs.existsSync(path.join(fullDir, 'CONTEXT.md'))
|
|
636
|
-
};
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
// --- Mutation commands ---
|
|
640
|
-
|
|
641
|
-
/**
|
|
642
|
-
* Atomically update a field in STATE.md using lockedFileUpdate.
|
|
643
|
-
* Supports both legacy and frontmatter (v2) formats.
|
|
644
|
-
*
|
|
645
|
-
* @param {string} field - One of: current_phase, status, plans_complete, last_activity
|
|
646
|
-
* @param {string} value - New value (use 'now' for last_activity to auto-timestamp)
|
|
647
|
-
*/
|
|
648
|
-
function stateUpdate(field, value) {
|
|
649
|
-
const statePath = path.join(planningDir, 'STATE.md');
|
|
650
|
-
if (!fs.existsSync(statePath)) {
|
|
651
|
-
return { success: false, error: 'STATE.md not found' };
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
const validFields = ['current_phase', 'status', 'plans_complete', 'last_activity'];
|
|
655
|
-
if (!validFields.includes(field)) {
|
|
656
|
-
return { success: false, error: `Invalid field: ${field}. Valid fields: ${validFields.join(', ')}` };
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
// Auto-timestamp
|
|
660
|
-
if (field === 'last_activity' && value === 'now') {
|
|
661
|
-
value = new Date().toISOString().slice(0, 19).replace('T', ' ');
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
const result = lockedFileUpdate(statePath, (content) => {
|
|
665
|
-
const fm = parseYamlFrontmatter(content);
|
|
666
|
-
if (fm.version === 2 || fm.current_phase !== undefined) {
|
|
667
|
-
return updateFrontmatterField(content, field, value);
|
|
668
|
-
}
|
|
669
|
-
return updateLegacyStateField(content, field, value);
|
|
670
|
-
});
|
|
671
|
-
|
|
672
|
-
if (result.success) {
|
|
673
|
-
return { success: true, field, value };
|
|
674
|
-
}
|
|
675
|
-
return { success: false, error: result.error };
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
/**
|
|
679
|
-
* Append a record to HISTORY.md. Creates the file if it doesn't exist.
|
|
680
|
-
* Each entry is a markdown section appended at the end.
|
|
681
|
-
*
|
|
682
|
-
* @param {object} entry - { type: 'milestone'|'phase', title: string, body: string }
|
|
683
|
-
* @param {string} [dir] - Path to .planning directory (defaults to cwd/.planning)
|
|
684
|
-
* @returns {{success: boolean, error?: string}}
|
|
685
|
-
*/
|
|
686
|
-
function historyAppend(entry, dir) {
|
|
687
|
-
const historyPath = path.join(dir || planningDir, 'HISTORY.md');
|
|
688
|
-
const timestamp = new Date().toISOString().slice(0, 10);
|
|
689
|
-
|
|
690
|
-
let header = '';
|
|
691
|
-
if (!fs.existsSync(historyPath)) {
|
|
692
|
-
header = '# Project History\n\nCompleted milestones and phase records. This file is append-only.\n\n';
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
const section = `${header}## ${entry.type === 'milestone' ? 'Milestone' : 'Phase'}: ${entry.title}\n_Completed: ${timestamp}_\n\n${entry.body.trim()}\n\n---\n\n`;
|
|
696
|
-
|
|
697
|
-
try {
|
|
698
|
-
fs.appendFileSync(historyPath, section, 'utf8');
|
|
699
|
-
return { success: true };
|
|
700
|
-
} catch (e) {
|
|
701
|
-
return { success: false, error: e.message };
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
/**
|
|
706
|
-
* Load HISTORY.md and parse it into structured records.
|
|
707
|
-
* Returns null if HISTORY.md doesn't exist.
|
|
708
|
-
*
|
|
709
|
-
* @param {string} [dir] - Path to .planning directory
|
|
710
|
-
* @returns {object|null} { records: [{type, title, date, body}], line_count }
|
|
711
|
-
*/
|
|
712
|
-
function historyLoad(dir) {
|
|
713
|
-
const historyPath = path.join(dir || planningDir, 'HISTORY.md');
|
|
714
|
-
if (!fs.existsSync(historyPath)) return null;
|
|
715
|
-
|
|
716
|
-
const content = fs.readFileSync(historyPath, 'utf8');
|
|
717
|
-
const records = [];
|
|
718
|
-
const sectionRegex = /^## (Milestone|Phase): (.+)\n_Completed: (\d{4}-\d{2}-\d{2})_\n\n([\s\S]*?)(?=\n---|\s*$)/gm;
|
|
719
|
-
|
|
720
|
-
let match;
|
|
721
|
-
while ((match = sectionRegex.exec(content)) !== null) {
|
|
722
|
-
records.push({
|
|
723
|
-
type: match[1].toLowerCase(),
|
|
724
|
-
title: match[2].trim(),
|
|
725
|
-
date: match[3],
|
|
726
|
-
body: match[4].trim()
|
|
727
|
-
});
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
return {
|
|
731
|
-
records,
|
|
732
|
-
line_count: content.split('\n').length
|
|
733
|
-
};
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
/**
|
|
737
|
-
* Update the Status column for a phase in ROADMAP.md's Phase Overview table.
|
|
738
|
-
*/
|
|
739
|
-
function roadmapUpdateStatus(phaseNum, newStatus) {
|
|
740
|
-
const roadmapPath = path.join(planningDir, 'ROADMAP.md');
|
|
741
|
-
if (!fs.existsSync(roadmapPath)) {
|
|
742
|
-
return { success: false, error: 'ROADMAP.md not found' };
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
let oldStatus = null;
|
|
746
|
-
|
|
747
|
-
const result = lockedFileUpdate(roadmapPath, (content) => {
|
|
748
|
-
const lines = content.split('\n');
|
|
749
|
-
const rowIdx = findRoadmapRow(lines, phaseNum);
|
|
750
|
-
if (rowIdx === -1) {
|
|
751
|
-
return content; // No matching row found
|
|
752
|
-
}
|
|
753
|
-
const parts = lines[rowIdx].split('|');
|
|
754
|
-
oldStatus = parts[6] ? parts[6].trim() : 'unknown';
|
|
755
|
-
lines[rowIdx] = updateTableRow(lines[rowIdx], 5, newStatus);
|
|
756
|
-
return lines.join('\n');
|
|
757
|
-
});
|
|
758
|
-
|
|
759
|
-
if (!oldStatus) {
|
|
760
|
-
return { success: false, error: `Phase ${phaseNum} not found in ROADMAP.md table` };
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
// Advisory transition validation — warn on suspicious transitions but don't block
|
|
764
|
-
const transition = validateStatusTransition(oldStatus, newStatus);
|
|
765
|
-
if (!transition.valid && transition.warning) {
|
|
766
|
-
process.stderr.write(`[pbr-tools] WARNING: ${transition.warning}\n`);
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
if (result.success) {
|
|
770
|
-
const response = { success: true, old_status: oldStatus, new_status: newStatus };
|
|
771
|
-
if (!transition.valid) {
|
|
772
|
-
response.transition_warning = transition.warning;
|
|
773
|
-
}
|
|
774
|
-
return response;
|
|
775
|
-
}
|
|
776
|
-
return { success: false, error: result.error };
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
/**
|
|
780
|
-
* Update the Plans column for a phase in ROADMAP.md's Phase Overview table.
|
|
781
|
-
*/
|
|
782
|
-
function roadmapUpdatePlans(phaseNum, complete, total) {
|
|
783
|
-
const roadmapPath = path.join(planningDir, 'ROADMAP.md');
|
|
784
|
-
if (!fs.existsSync(roadmapPath)) {
|
|
785
|
-
return { success: false, error: 'ROADMAP.md not found' };
|
|
786
|
-
}
|
|
787
|
-
|
|
788
|
-
let oldPlans = null;
|
|
789
|
-
const newPlans = `${complete}/${total}`;
|
|
790
|
-
|
|
791
|
-
const result = lockedFileUpdate(roadmapPath, (content) => {
|
|
792
|
-
const lines = content.split('\n');
|
|
793
|
-
const rowIdx = findRoadmapRow(lines, phaseNum);
|
|
794
|
-
if (rowIdx === -1) {
|
|
795
|
-
return content;
|
|
796
|
-
}
|
|
797
|
-
const parts = lines[rowIdx].split('|');
|
|
798
|
-
oldPlans = parts[4] ? parts[4].trim() : 'unknown';
|
|
799
|
-
lines[rowIdx] = updateTableRow(lines[rowIdx], 3, newPlans);
|
|
800
|
-
return lines.join('\n');
|
|
801
|
-
});
|
|
802
|
-
|
|
803
|
-
if (!oldPlans) {
|
|
804
|
-
return { success: false, error: `Phase ${phaseNum} not found in ROADMAP.md table` };
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
if (result.success) {
|
|
808
|
-
return { success: true, old_plans: oldPlans, new_plans: newPlans };
|
|
809
|
-
}
|
|
810
|
-
return { success: false, error: result.error };
|
|
811
|
-
}
|
|
812
|
-
|
|
813
|
-
// --- Mutation helpers ---
|
|
814
|
-
|
|
815
|
-
/**
|
|
816
|
-
* Update a field in legacy (non-frontmatter) STATE.md content.
|
|
817
|
-
* Pure function: content in, content out.
|
|
818
|
-
*/
|
|
819
|
-
function updateLegacyStateField(content, field, value) {
|
|
820
|
-
const lines = content.split('\n');
|
|
821
|
-
|
|
822
|
-
switch (field) {
|
|
823
|
-
case 'current_phase': {
|
|
824
|
-
const idx = lines.findIndex(l => /Phase:\s*\d+\s+of\s+\d+/.test(l));
|
|
825
|
-
if (idx !== -1) {
|
|
826
|
-
lines[idx] = lines[idx].replace(/(Phase:\s*)\d+/, `$1${value}`);
|
|
827
|
-
}
|
|
828
|
-
break;
|
|
829
|
-
}
|
|
830
|
-
case 'status': {
|
|
831
|
-
const idx = lines.findIndex(l => /^Status:/i.test(l));
|
|
832
|
-
if (idx !== -1) {
|
|
833
|
-
lines[idx] = `Status: ${value}`;
|
|
834
1201
|
} else {
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
lines.splice(phaseIdx + 1, 0, `Status: ${value}`);
|
|
838
|
-
} else {
|
|
839
|
-
lines.push(`Status: ${value}`);
|
|
840
|
-
}
|
|
1202
|
+
error('Usage: learnings <ingest|query|check-thresholds|copy-global|query-global>');
|
|
1203
|
+
process.exit(1);
|
|
841
1204
|
}
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
1205
|
+
} else if (command === 'verify' && subcommand === 'spot-check') {
|
|
1206
|
+
const scType = args[2];
|
|
1207
|
+
const scPath = args[3];
|
|
1208
|
+
if (!scType || !scPath) { error('Usage: verify spot-check <type> <path> (types: plan, summary, verification, quick)'); }
|
|
1209
|
+
const result = verifySpotCheck(scType, scPath);
|
|
1210
|
+
if (result.error) { process.stdout.write(JSON.stringify(result, null, 2) + '\n'); process.exit(1); }
|
|
1211
|
+
output(result);
|
|
1212
|
+
|
|
1213
|
+
} else if (command === 'spot-check') {
|
|
1214
|
+
// spot-check <phaseSlug> <planId>
|
|
1215
|
+
// Returns JSON: { ok, summary_exists, key_files_checked, commits_present, detail }
|
|
1216
|
+
const phaseSlug = args[1];
|
|
1217
|
+
const planId = args[2];
|
|
1218
|
+
if (!phaseSlug || !planId) {
|
|
1219
|
+
error('Usage: spot-check <phaseSlug> <planId>');
|
|
848
1220
|
}
|
|
849
|
-
|
|
850
|
-
}
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
1221
|
+
output(spotCheck(phaseSlug, planId));
|
|
1222
|
+
} else if (command === 'staleness-check') {
|
|
1223
|
+
const slug = args[1];
|
|
1224
|
+
if (!slug) { error('Usage: staleness-check <phase-slug>'); process.exit(1); }
|
|
1225
|
+
output(stalenessCheck(slug));
|
|
1226
|
+
} else if (command === 'summary-gate') {
|
|
1227
|
+
const [slug, planId] = args.slice(1);
|
|
1228
|
+
if (!slug || !planId) { error('Usage: summary-gate <phase-slug> <plan-id>'); process.exit(1); }
|
|
1229
|
+
output(summaryGate(slug, planId));
|
|
1230
|
+
} else if (command === 'checkpoint') {
|
|
1231
|
+
const sub = args[1];
|
|
1232
|
+
const slug = args[2];
|
|
1233
|
+
if (sub === 'init') {
|
|
1234
|
+
const plans = args[3] || '';
|
|
1235
|
+
output(checkpointInit(slug, plans));
|
|
1236
|
+
} else if (sub === 'update') {
|
|
1237
|
+
const waveIdx = args.indexOf('--wave');
|
|
1238
|
+
const wave = waveIdx !== -1 ? parseInt(args[waveIdx + 1], 10) : 1;
|
|
1239
|
+
const resolvedIdx = args.indexOf('--resolved');
|
|
1240
|
+
const resolved = resolvedIdx !== -1 ? args[resolvedIdx + 1] : '';
|
|
1241
|
+
const shaIdx = args.indexOf('--sha');
|
|
1242
|
+
const sha = shaIdx !== -1 ? args[shaIdx + 1] : '';
|
|
1243
|
+
output(checkpointUpdate(slug, { wave, resolved, sha }));
|
|
1244
|
+
} else {
|
|
1245
|
+
error('Usage: checkpoint init|update <phase-slug> [options]'); process.exit(1);
|
|
1246
|
+
}
|
|
1247
|
+
} else if (command === 'seeds') {
|
|
1248
|
+
const sub = args[1];
|
|
1249
|
+
if (sub === 'match') {
|
|
1250
|
+
const slug = args[2];
|
|
1251
|
+
const num = args[3];
|
|
1252
|
+
if (!slug) { error('Usage: seeds match <phase-slug> <phase-number>'); process.exit(1); }
|
|
1253
|
+
output(seedsMatch(slug, num));
|
|
855
1254
|
} else {
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
1255
|
+
error('Usage: seeds match <phase-slug> <phase-number>'); process.exit(1);
|
|
1256
|
+
}
|
|
1257
|
+
} else if (command === 'ci-poll') {
|
|
1258
|
+
const runId = args[1];
|
|
1259
|
+
const timeoutIdx = args.indexOf('--timeout');
|
|
1260
|
+
const timeoutSecs = timeoutIdx !== -1 ? parseInt(args[timeoutIdx + 1], 10) : 300;
|
|
1261
|
+
if (!runId) { error('Usage: pbr-tools.js ci-poll <run-id> [--timeout <seconds>]'); return; }
|
|
1262
|
+
output(ciPoll(runId, timeoutSecs));
|
|
1263
|
+
} else if (command === 'rollback') {
|
|
1264
|
+
const manifestPath = args[1];
|
|
1265
|
+
if (!manifestPath) { error('Usage: pbr-tools.js rollback <manifest-path>'); return; }
|
|
1266
|
+
output(rollbackPlan(manifestPath));
|
|
1267
|
+
} else if (command === 'session') {
|
|
1268
|
+
const sub = args[1];
|
|
1269
|
+
// Extract --session-id flag from remaining args
|
|
1270
|
+
const sessionIdIdx = args.indexOf('--session-id');
|
|
1271
|
+
const sessionId = sessionIdIdx !== -1 ? args[sessionIdIdx + 1] : null;
|
|
1272
|
+
// Filter out --session-id and its value from positional args
|
|
1273
|
+
const positional = sessionIdIdx !== -1
|
|
1274
|
+
? args.filter((_a, i) => i !== sessionIdIdx && i !== sessionIdIdx + 1)
|
|
1275
|
+
: args;
|
|
1276
|
+
const key = positional[2];
|
|
1277
|
+
const value = positional[3];
|
|
1278
|
+
const dir = planningDir;
|
|
1279
|
+
if (sub === 'get') {
|
|
1280
|
+
if (!key) { error('Usage: pbr-tools.js session get <key> [--session-id <id>]'); return; }
|
|
1281
|
+
if (!SESSION_ALLOWED_KEYS.includes(key)) { error(`Unknown session key: ${key}. Allowed: ${SESSION_ALLOWED_KEYS.join(', ')}`); return; }
|
|
1282
|
+
const data = sessionLoad(dir, sessionId);
|
|
1283
|
+
const val = Object.prototype.hasOwnProperty.call(data, key) ? data[key] : null;
|
|
1284
|
+
output({ key, value: val });
|
|
1285
|
+
} else if (sub === 'set') {
|
|
1286
|
+
if (!key || value === undefined) { error('Usage: pbr-tools.js session set <key> <value> [--session-id <id>]'); return; }
|
|
1287
|
+
if (!SESSION_ALLOWED_KEYS.includes(key)) { error(`Unknown session key: ${key}. Allowed: ${SESSION_ALLOWED_KEYS.join(', ')}`); return; }
|
|
1288
|
+
// Coerce numeric strings
|
|
1289
|
+
let coerced = value;
|
|
1290
|
+
if (/^\d+$/.test(value)) coerced = parseInt(value, 10);
|
|
1291
|
+
else if (value === 'null') coerced = null;
|
|
1292
|
+
const result = sessionSave(dir, { [key]: coerced }, sessionId);
|
|
1293
|
+
if (!result.success) { error(result.error || 'Failed to save session'); return; }
|
|
1294
|
+
output({ ok: true });
|
|
1295
|
+
} else if (sub === 'clear') {
|
|
1296
|
+
if (key) {
|
|
1297
|
+
// Clear a specific key — set to null
|
|
1298
|
+
if (!SESSION_ALLOWED_KEYS.includes(key)) { error(`Unknown session key: ${key}. Allowed: ${SESSION_ALLOWED_KEYS.join(', ')}`); return; }
|
|
1299
|
+
const result = sessionSave(dir, { [key]: null }, sessionId);
|
|
1300
|
+
if (!result.success) { error(result.error || 'Failed to clear session key'); return; }
|
|
859
1301
|
} else {
|
|
860
|
-
|
|
1302
|
+
// Clear entire session file
|
|
1303
|
+
const sessionPath = sessionId
|
|
1304
|
+
? resolveSessionPath(dir, '.session.json', sessionId)
|
|
1305
|
+
: path.join(dir, '.session.json');
|
|
1306
|
+
try { if (fs.existsSync(sessionPath)) fs.unlinkSync(sessionPath); } catch (e) { error(e.message); return; }
|
|
861
1307
|
}
|
|
1308
|
+
output({ ok: true });
|
|
1309
|
+
} else if (sub === 'dump') {
|
|
1310
|
+
const data = sessionLoad(dir, sessionId);
|
|
1311
|
+
output(data);
|
|
1312
|
+
} else {
|
|
1313
|
+
error('Usage: pbr-tools.js session get|set|clear|dump <key> [value] [--session-id <id>]');
|
|
862
1314
|
}
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
*/
|
|
901
|
-
function findRoadmapRow(lines, phaseNum) {
|
|
902
|
-
const paddedPhase = phaseNum.padStart(2, '0');
|
|
903
|
-
for (let i = 0; i < lines.length; i++) {
|
|
904
|
-
if (!lines[i].includes('|')) continue;
|
|
905
|
-
const parts = lines[i].split('|');
|
|
906
|
-
if (parts.length < 3) continue;
|
|
907
|
-
const phaseCol = parts[1] ? parts[1].trim() : '';
|
|
908
|
-
if (phaseCol === paddedPhase) {
|
|
909
|
-
return i;
|
|
910
|
-
}
|
|
911
|
-
}
|
|
912
|
-
return -1;
|
|
913
|
-
}
|
|
914
|
-
|
|
915
|
-
/**
|
|
916
|
-
* Update a specific column in a markdown table row.
|
|
917
|
-
* @param {string} row - The full table row string (e.g., "| 01 | Setup | ... |")
|
|
918
|
-
* @param {number} columnIndex - 0-based column index (Phase=0, Name=1, ..., Status=5)
|
|
919
|
-
* @param {string} newValue - New cell value
|
|
920
|
-
* @returns {string} Updated row
|
|
921
|
-
*/
|
|
922
|
-
function updateTableRow(row, columnIndex, newValue) {
|
|
923
|
-
const parts = row.split('|');
|
|
924
|
-
// parts[0] is empty (before first |), data starts at parts[1]
|
|
925
|
-
const partIndex = columnIndex + 1;
|
|
926
|
-
if (partIndex < parts.length) {
|
|
927
|
-
parts[partIndex] = ` ${newValue} `;
|
|
928
|
-
}
|
|
929
|
-
return parts.join('|');
|
|
930
|
-
}
|
|
931
|
-
|
|
932
|
-
/**
|
|
933
|
-
* Lightweight JSON Schema validator — supports type, enum, properties,
|
|
934
|
-
* additionalProperties, minimum, maximum for the config schema.
|
|
935
|
-
*/
|
|
936
|
-
function validateObject(value, schema, prefix, errors, warnings) {
|
|
937
|
-
if (schema.type && typeof value !== schema.type) {
|
|
938
|
-
if (!(schema.type === 'integer' && typeof value === 'number' && Number.isInteger(value))) {
|
|
939
|
-
errors.push(`${prefix || 'root'}: expected ${schema.type}, got ${typeof value}`);
|
|
940
|
-
return;
|
|
941
|
-
}
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
if (schema.enum && !schema.enum.includes(value)) {
|
|
945
|
-
errors.push(`${prefix || 'root'}: value "${value}" not in allowed values [${schema.enum.join(', ')}]`);
|
|
946
|
-
return;
|
|
947
|
-
}
|
|
948
|
-
|
|
949
|
-
if (schema.minimum !== undefined && value < schema.minimum) {
|
|
950
|
-
errors.push(`${prefix || 'root'}: value ${value} is below minimum ${schema.minimum}`);
|
|
951
|
-
}
|
|
952
|
-
if (schema.maximum !== undefined && value > schema.maximum) {
|
|
953
|
-
errors.push(`${prefix || 'root'}: value ${value} is above maximum ${schema.maximum}`);
|
|
954
|
-
}
|
|
955
|
-
|
|
956
|
-
if (schema.type === 'object' && schema.properties) {
|
|
957
|
-
const knownKeys = new Set(Object.keys(schema.properties));
|
|
958
|
-
|
|
959
|
-
for (const key of Object.keys(value)) {
|
|
960
|
-
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
961
|
-
if (!knownKeys.has(key)) {
|
|
962
|
-
if (schema.additionalProperties === false) {
|
|
963
|
-
warnings.push(`${fullKey}: unrecognized key (possible typo?)`);
|
|
1315
|
+
} else if (command === 'context-triage') {
|
|
1316
|
+
const options = {};
|
|
1317
|
+
const agentsIdx = args.indexOf('--agents-done');
|
|
1318
|
+
if (agentsIdx !== -1) options.agentsDone = parseInt(args[agentsIdx + 1], 10);
|
|
1319
|
+
const plansIdx = args.indexOf('--plans-total');
|
|
1320
|
+
if (plansIdx !== -1) options.plansTotal = parseInt(args[plansIdx + 1], 10);
|
|
1321
|
+
const stepIdx = args.indexOf('--step');
|
|
1322
|
+
if (stepIdx !== -1) options.currentStep = args[stepIdx + 1];
|
|
1323
|
+
output(contextTriage(options));
|
|
1324
|
+
} else if (command === 'reference') {
|
|
1325
|
+
const name = args[1];
|
|
1326
|
+
if (!name) error('Usage: pbr-tools.js reference <name> [--section <heading>] [--list]');
|
|
1327
|
+
const listFlag = args.includes('--list');
|
|
1328
|
+
const sectionIdx = args.indexOf('--section');
|
|
1329
|
+
const section = sectionIdx !== -1 ? args.slice(sectionIdx + 1).join(' ') : null;
|
|
1330
|
+
output(referenceGet(name, { section: section, list: listFlag }));
|
|
1331
|
+
} else if (command === 'milestone-stats') {
|
|
1332
|
+
const version = args[1];
|
|
1333
|
+
if (!version) error('Usage: pbr-tools.js milestone-stats <version>');
|
|
1334
|
+
output(milestoneStats(version));
|
|
1335
|
+
} else if (command === 'validate-project') {
|
|
1336
|
+
output(validateProject());
|
|
1337
|
+
} else if (command === 'skill-section') {
|
|
1338
|
+
// skill-section --list <skill> — list all headings
|
|
1339
|
+
if (args[1] === '--list') {
|
|
1340
|
+
const skillName = args[2];
|
|
1341
|
+
if (!skillName) { error('Usage: pbr-tools.js skill-section --list <skill>'); return; }
|
|
1342
|
+
const listResult = listSkillHeadings(skillName);
|
|
1343
|
+
output(listResult);
|
|
1344
|
+
if (listResult.error) process.exit(1);
|
|
1345
|
+
} else {
|
|
1346
|
+
// skill-section <skill> <section...>
|
|
1347
|
+
const skillName = args[1];
|
|
1348
|
+
const sectionQuery = args.slice(2).join(' ');
|
|
1349
|
+
if (!skillName || !sectionQuery) {
|
|
1350
|
+
error('Usage: pbr-tools.js skill-section <skill> <section>');
|
|
1351
|
+
return;
|
|
964
1352
|
}
|
|
965
|
-
|
|
1353
|
+
const secResult = skillSectionGet(skillName, sectionQuery);
|
|
1354
|
+
output(secResult);
|
|
1355
|
+
if (secResult.error) process.exit(1);
|
|
966
1356
|
}
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
* Locked file update: read-modify-write with exclusive lockfile.
|
|
974
|
-
* Prevents concurrent writes to STATE.md and ROADMAP.md.
|
|
975
|
-
*
|
|
976
|
-
* @param {string} filePath - Absolute path to the file to update
|
|
977
|
-
* @param {function} updateFn - Receives current content, returns new content
|
|
978
|
-
* @param {object} opts - Options: { retries: 3, retryDelayMs: 100, timeoutMs: 5000 }
|
|
979
|
-
* @returns {object} { success, content?, error? }
|
|
980
|
-
*/
|
|
981
|
-
function lockedFileUpdate(filePath, updateFn, opts = {}) {
|
|
982
|
-
const retries = opts.retries || 3;
|
|
983
|
-
const retryDelayMs = opts.retryDelayMs || 100;
|
|
984
|
-
const timeoutMs = opts.timeoutMs || 5000;
|
|
985
|
-
const lockPath = filePath + '.lock';
|
|
986
|
-
|
|
987
|
-
let lockFd = null;
|
|
988
|
-
let lockAcquired = false;
|
|
989
|
-
|
|
990
|
-
try {
|
|
991
|
-
// Acquire lock with retries
|
|
992
|
-
for (let attempt = 0; attempt < retries; attempt++) {
|
|
1357
|
+
} else if (command === 'step-verify') {
|
|
1358
|
+
// step-verify <skill> <step> <checklist-json>
|
|
1359
|
+
const skill = args[1];
|
|
1360
|
+
const step = args[2];
|
|
1361
|
+
const checklistStr = args[3] || '[]';
|
|
1362
|
+
let checklist;
|
|
993
1363
|
try {
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
// Lock exists — check if stale (older than timeoutMs)
|
|
1000
|
-
try {
|
|
1001
|
-
const stats = fs.statSync(lockPath);
|
|
1002
|
-
if (Date.now() - stats.mtimeMs > timeoutMs) {
|
|
1003
|
-
// Stale lock — remove and retry
|
|
1004
|
-
fs.unlinkSync(lockPath);
|
|
1005
|
-
continue;
|
|
1006
|
-
}
|
|
1007
|
-
} catch (_statErr) {
|
|
1008
|
-
// Lock disappeared between check — retry
|
|
1009
|
-
continue;
|
|
1010
|
-
}
|
|
1011
|
-
|
|
1012
|
-
if (attempt < retries - 1) {
|
|
1013
|
-
// Wait and retry
|
|
1014
|
-
const waitMs = retryDelayMs * (attempt + 1);
|
|
1015
|
-
const start = Date.now();
|
|
1016
|
-
while (Date.now() - start < waitMs) {
|
|
1017
|
-
// Busy wait (synchronous context)
|
|
1018
|
-
}
|
|
1019
|
-
continue;
|
|
1020
|
-
}
|
|
1021
|
-
return { success: false, error: `Could not acquire lock for ${path.basename(filePath)} after ${retries} attempts` };
|
|
1022
|
-
}
|
|
1023
|
-
throw e;
|
|
1364
|
+
checklist = JSON.parse(checklistStr);
|
|
1365
|
+
} catch (_e) {
|
|
1366
|
+
output({ error: 'Invalid checklist JSON' });
|
|
1367
|
+
process.exit(1);
|
|
1368
|
+
return;
|
|
1024
1369
|
}
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
content = fs.readFileSync(filePath, 'utf8');
|
|
1040
|
-
}
|
|
1041
|
-
|
|
1042
|
-
// Apply update
|
|
1043
|
-
const newContent = updateFn(content);
|
|
1044
|
-
|
|
1045
|
-
// Write back atomically
|
|
1046
|
-
const writeResult = atomicWrite(filePath, newContent);
|
|
1047
|
-
if (!writeResult.success) {
|
|
1048
|
-
return { success: false, error: writeResult.error };
|
|
1049
|
-
}
|
|
1050
|
-
|
|
1051
|
-
return { success: true, content: newContent };
|
|
1052
|
-
} catch (e) {
|
|
1053
|
-
return { success: false, error: e.message };
|
|
1054
|
-
} finally {
|
|
1055
|
-
// Close fd if still open
|
|
1056
|
-
try {
|
|
1057
|
-
if (lockFd !== null) fs.closeSync(lockFd);
|
|
1058
|
-
} catch (_e) { /* ignore */ }
|
|
1059
|
-
// Only release lock if we acquired it
|
|
1060
|
-
if (lockAcquired) {
|
|
1061
|
-
try {
|
|
1062
|
-
fs.unlinkSync(lockPath);
|
|
1063
|
-
} catch (_e) { /* ignore — may already be cleaned up */ }
|
|
1064
|
-
}
|
|
1065
|
-
}
|
|
1066
|
-
}
|
|
1067
|
-
|
|
1068
|
-
// --- Parsers ---
|
|
1069
|
-
|
|
1070
|
-
function parseStateMd(content) {
|
|
1071
|
-
const result = {
|
|
1072
|
-
current_phase: null,
|
|
1073
|
-
phase_name: null,
|
|
1074
|
-
progress: null,
|
|
1075
|
-
status: null,
|
|
1076
|
-
line_count: content.split('\n').length,
|
|
1077
|
-
format: 'legacy' // 'legacy' or 'frontmatter'
|
|
1078
|
-
};
|
|
1079
|
-
|
|
1080
|
-
// Check for YAML frontmatter (version 2 format)
|
|
1081
|
-
const frontmatter = parseYamlFrontmatter(content);
|
|
1082
|
-
if (frontmatter.version === 2 || frontmatter.current_phase !== undefined) {
|
|
1083
|
-
result.format = 'frontmatter';
|
|
1084
|
-
result.current_phase = frontmatter.current_phase || null;
|
|
1085
|
-
result.total_phases = frontmatter.total_phases || null;
|
|
1086
|
-
result.phase_name = frontmatter.phase_slug || frontmatter.phase_name || null;
|
|
1087
|
-
result.status = frontmatter.status || null;
|
|
1088
|
-
result.progress = frontmatter.progress_percent !== undefined ? frontmatter.progress_percent : null;
|
|
1089
|
-
result.plans_total = frontmatter.plans_total || null;
|
|
1090
|
-
result.plans_complete = frontmatter.plans_complete || null;
|
|
1091
|
-
result.last_activity = frontmatter.last_activity || null;
|
|
1092
|
-
result.last_command = frontmatter.last_command || null;
|
|
1093
|
-
result.blockers = frontmatter.blockers || [];
|
|
1094
|
-
return result;
|
|
1095
|
-
}
|
|
1096
|
-
|
|
1097
|
-
// Legacy regex-based parsing (version 1 format, no frontmatter)
|
|
1098
|
-
// DEPRECATED (2026-02): v1 STATE.md format (no YAML frontmatter) is deprecated.
|
|
1099
|
-
// New projects should use v2 (frontmatter) format, generated by /pbr:setup.
|
|
1100
|
-
// v1 support will be removed in a future major version.
|
|
1101
|
-
process.stderr.write('[pbr] WARNING: STATE.md uses legacy v1 format. Run /pbr:setup to migrate to v2 format.\n');
|
|
1102
|
-
// Extract "Phase: N of M"
|
|
1103
|
-
const phaseMatch = content.match(/Phase:\s*(\d+)\s+of\s+(\d+)/);
|
|
1104
|
-
if (phaseMatch) {
|
|
1105
|
-
result.current_phase = parseInt(phaseMatch[1], 10);
|
|
1106
|
-
result.total_phases = parseInt(phaseMatch[2], 10);
|
|
1107
|
-
}
|
|
1108
|
-
|
|
1109
|
-
// Extract phase name (line after "Phase:")
|
|
1110
|
-
const nameMatch = content.match(/--\s+(.+?)(?:\n|$)/);
|
|
1111
|
-
if (nameMatch) {
|
|
1112
|
-
result.phase_name = nameMatch[1].trim();
|
|
1113
|
-
}
|
|
1114
|
-
|
|
1115
|
-
// Extract progress percentage
|
|
1116
|
-
const progressMatch = content.match(/(\d+)%/);
|
|
1117
|
-
if (progressMatch) {
|
|
1118
|
-
result.progress = parseInt(progressMatch[1], 10);
|
|
1119
|
-
}
|
|
1120
|
-
|
|
1121
|
-
// Extract plan status
|
|
1122
|
-
const statusMatch = content.match(/Status:\s*(.+?)(?:\n|$)/i);
|
|
1123
|
-
if (statusMatch) {
|
|
1124
|
-
result.status = statusMatch[1].trim();
|
|
1125
|
-
}
|
|
1126
|
-
|
|
1127
|
-
return result;
|
|
1128
|
-
}
|
|
1129
|
-
|
|
1130
|
-
function parseRoadmapMd(content) {
|
|
1131
|
-
const result = { phases: [], has_progress_table: false };
|
|
1132
|
-
|
|
1133
|
-
// Find Phase Overview table
|
|
1134
|
-
const overviewMatch = content.match(/## Phase Overview[\s\S]*?\|[\s\S]*?(?=\n##|\s*$)/);
|
|
1135
|
-
if (overviewMatch) {
|
|
1136
|
-
const rows = overviewMatch[0].split('\n').filter(r => r.includes('|'));
|
|
1137
|
-
// Skip header and separator rows
|
|
1138
|
-
for (let i = 2; i < rows.length; i++) {
|
|
1139
|
-
const cols = rows[i].split('|').map(c => c.trim()).filter(Boolean);
|
|
1140
|
-
if (cols.length >= 3) {
|
|
1141
|
-
result.phases.push({
|
|
1142
|
-
number: cols[0],
|
|
1143
|
-
name: cols[1],
|
|
1144
|
-
goal: cols[2],
|
|
1145
|
-
plans: cols[3] || '',
|
|
1146
|
-
wave: cols[4] || '',
|
|
1147
|
-
status: cols[5] || 'pending'
|
|
1148
|
-
});
|
|
1370
|
+
const svPlanningDir = planningDir;
|
|
1371
|
+
const svContext = {
|
|
1372
|
+
planningDir: svPlanningDir,
|
|
1373
|
+
phaseSlug: process.env.PBR_PHASE_SLUG || '',
|
|
1374
|
+
planId: process.env.PBR_PLAN_ID || ''
|
|
1375
|
+
};
|
|
1376
|
+
const svResult = _stepVerify(skill, step, checklist, svContext);
|
|
1377
|
+
output(svResult);
|
|
1378
|
+
if (svResult.error || svResult.all_passed === false) process.exit(1);
|
|
1379
|
+
} else if (command === 'build-preview') {
|
|
1380
|
+
const phaseSlug = args[1];
|
|
1381
|
+
if (!phaseSlug) {
|
|
1382
|
+
error('Usage: pbr-tools.js build-preview <phase-slug>');
|
|
1383
|
+
return;
|
|
1149
1384
|
}
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
return result;
|
|
1157
|
-
}
|
|
1158
|
-
|
|
1159
|
-
function parseYamlFrontmatter(content) {
|
|
1160
|
-
const match = content.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
1161
|
-
if (!match) return {};
|
|
1162
|
-
|
|
1163
|
-
const yaml = match[1];
|
|
1164
|
-
const result = {};
|
|
1165
|
-
|
|
1166
|
-
// Simple YAML parser for flat and basic nested values
|
|
1167
|
-
const lines = yaml.split('\n');
|
|
1168
|
-
let currentKey = null;
|
|
1169
|
-
|
|
1170
|
-
for (const line of lines) {
|
|
1171
|
-
// Array item
|
|
1172
|
-
if (/^\s+-\s+/.test(line) && currentKey) {
|
|
1173
|
-
const val = line.replace(/^\s+-\s+/, '').trim().replace(/^["']|["']$/g, '');
|
|
1174
|
-
if (!result[currentKey]) result[currentKey] = [];
|
|
1175
|
-
if (Array.isArray(result[currentKey])) {
|
|
1176
|
-
result[currentKey].push(val);
|
|
1385
|
+
const previewPlanningDir = planningDir;
|
|
1386
|
+
const previewPluginRoot = path.resolve(__dirname, '..');
|
|
1387
|
+
const result = _buildPreview(phaseSlug, {}, previewPlanningDir, previewPluginRoot);
|
|
1388
|
+
if (result && result.error) {
|
|
1389
|
+
output(result);
|
|
1390
|
+
process.exit(1);
|
|
1177
1391
|
}
|
|
1178
|
-
|
|
1179
|
-
}
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1392
|
+
output(result);
|
|
1393
|
+
} else if (command === 'suggest-alternatives') {
|
|
1394
|
+
const errorType = args[1];
|
|
1395
|
+
const altPlanningDir = planningDir;
|
|
1396
|
+
if (errorType === 'phase-not-found') {
|
|
1397
|
+
output(_phaseAlternatives(args[2] || '', altPlanningDir));
|
|
1398
|
+
} else if (errorType === 'missing-prereq') {
|
|
1399
|
+
output(_prereqAlternatives(args[2] || '', altPlanningDir));
|
|
1400
|
+
} else if (errorType === 'config-invalid') {
|
|
1401
|
+
output(_configAlternatives(args[2] || '', args[3] || '', altPlanningDir));
|
|
1402
|
+
} else {
|
|
1403
|
+
output({ error: 'Unknown error type. Valid: phase-not-found, missing-prereq, config-invalid' });
|
|
1404
|
+
process.exit(1);
|
|
1190
1405
|
}
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1406
|
+
} else if (command === 'claim' && subcommand === 'acquire') {
|
|
1407
|
+
const phaseSlug = args[2];
|
|
1408
|
+
const sidIdx = args.indexOf('--session-id');
|
|
1409
|
+
const sessionId = sidIdx !== -1 ? args[sidIdx + 1] : null;
|
|
1410
|
+
const skillIdx = args.indexOf('--skill');
|
|
1411
|
+
const skill = skillIdx !== -1 ? args[skillIdx + 1] : 'unknown';
|
|
1412
|
+
if (!phaseSlug || !sessionId) {
|
|
1413
|
+
error('Usage: pbr-tools.js claim acquire <phase-slug> --session-id <id> --skill <name>');
|
|
1198
1414
|
}
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
if (
|
|
1205
|
-
|
|
1206
|
-
else if (/^\d+$/.test(val)) val = parseInt(val, 10);
|
|
1207
|
-
|
|
1208
|
-
result[currentKey] = val;
|
|
1209
|
-
}
|
|
1210
|
-
}
|
|
1211
|
-
|
|
1212
|
-
// Handle must_haves as a nested object
|
|
1213
|
-
if (yaml.includes('must_haves:')) {
|
|
1214
|
-
result.must_haves = parseMustHaves(yaml);
|
|
1215
|
-
}
|
|
1216
|
-
|
|
1217
|
-
return result;
|
|
1218
|
-
}
|
|
1219
|
-
|
|
1220
|
-
function parseMustHaves(yaml) {
|
|
1221
|
-
const result = { truths: [], artifacts: [], key_links: [] };
|
|
1222
|
-
let section = null;
|
|
1223
|
-
|
|
1224
|
-
const inMustHaves = yaml.split('\n');
|
|
1225
|
-
let collecting = false;
|
|
1226
|
-
|
|
1227
|
-
for (const line of inMustHaves) {
|
|
1228
|
-
if (/^\s*must_haves:/.test(line)) {
|
|
1229
|
-
collecting = true;
|
|
1230
|
-
continue;
|
|
1231
|
-
}
|
|
1232
|
-
if (collecting) {
|
|
1233
|
-
if (/^\s{2}truths:/.test(line)) { section = 'truths'; continue; }
|
|
1234
|
-
if (/^\s{2}artifacts:/.test(line)) { section = 'artifacts'; continue; }
|
|
1235
|
-
if (/^\s{2}key_links:/.test(line)) { section = 'key_links'; continue; }
|
|
1236
|
-
if (/^\w/.test(line)) break; // New top-level key, stop
|
|
1237
|
-
|
|
1238
|
-
if (section && /^\s+-\s+/.test(line)) {
|
|
1239
|
-
result[section].push(line.replace(/^\s+-\s+/, '').trim().replace(/^["']|["']$/g, ''));
|
|
1415
|
+
output(claimAcquire(phaseSlug, sessionId, skill));
|
|
1416
|
+
} else if (command === 'claim' && subcommand === 'release') {
|
|
1417
|
+
const phaseSlug = args[2];
|
|
1418
|
+
const sidIdx = args.indexOf('--session-id');
|
|
1419
|
+
const sessionId = sidIdx !== -1 ? args[sidIdx + 1] : null;
|
|
1420
|
+
if (!phaseSlug || !sessionId) {
|
|
1421
|
+
error('Usage: pbr-tools.js claim release <phase-slug> --session-id <id>');
|
|
1240
1422
|
}
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
return 'not_started';
|
|
1262
|
-
}
|
|
1263
|
-
if (completedCount === 0 && summaryCount === 0) return 'planned';
|
|
1264
|
-
if (completedCount < planCount) return 'building';
|
|
1265
|
-
if (!hasVerification) return 'built';
|
|
1266
|
-
// Check verification status
|
|
1267
|
-
try {
|
|
1268
|
-
const vContent = fs.readFileSync(path.join(phaseDir, 'VERIFICATION.md'), 'utf8');
|
|
1269
|
-
if (/status:\s*["']?passed/i.test(vContent)) return 'verified';
|
|
1270
|
-
if (/status:\s*["']?gaps_found/i.test(vContent)) return 'needs_fixes';
|
|
1271
|
-
return 'reviewed';
|
|
1272
|
-
} catch (_) {
|
|
1273
|
-
return 'built';
|
|
1274
|
-
}
|
|
1275
|
-
}
|
|
1276
|
-
|
|
1277
|
-
function countMustHaves(mustHaves) {
|
|
1278
|
-
if (!mustHaves) return 0;
|
|
1279
|
-
return (mustHaves.truths || []).length +
|
|
1280
|
-
(mustHaves.artifacts || []).length +
|
|
1281
|
-
(mustHaves.key_links || []).length;
|
|
1282
|
-
}
|
|
1283
|
-
|
|
1284
|
-
function calculateProgress() {
|
|
1285
|
-
const phasesDir = path.join(planningDir, 'phases');
|
|
1286
|
-
if (!fs.existsSync(phasesDir)) {
|
|
1287
|
-
return { total: 0, completed: 0, percentage: 0 };
|
|
1288
|
-
}
|
|
1289
|
-
|
|
1290
|
-
let total = 0;
|
|
1291
|
-
let completed = 0;
|
|
1292
|
-
|
|
1293
|
-
const entries = fs.readdirSync(phasesDir, { withFileTypes: true })
|
|
1294
|
-
.filter(e => e.isDirectory());
|
|
1295
|
-
|
|
1296
|
-
for (const entry of entries) {
|
|
1297
|
-
const dir = path.join(phasesDir, entry.name);
|
|
1298
|
-
const plans = findFiles(dir, /-PLAN\.md$/);
|
|
1299
|
-
total += plans.length;
|
|
1300
|
-
|
|
1301
|
-
const summaries = findFiles(dir, /^SUMMARY-.*\.md$/);
|
|
1302
|
-
for (const s of summaries) {
|
|
1303
|
-
const content = fs.readFileSync(path.join(dir, s), 'utf8');
|
|
1304
|
-
if (/status:\s*["']?complete/i.test(content)) completed++;
|
|
1305
|
-
}
|
|
1306
|
-
}
|
|
1307
|
-
|
|
1308
|
-
return {
|
|
1309
|
-
total,
|
|
1310
|
-
completed,
|
|
1311
|
-
percentage: total > 0 ? Math.round((completed / total) * 100) : 0
|
|
1312
|
-
};
|
|
1313
|
-
}
|
|
1314
|
-
|
|
1315
|
-
function output(data) {
|
|
1316
|
-
process.stdout.write(JSON.stringify(data, null, 2));
|
|
1317
|
-
process.exit(0);
|
|
1318
|
-
}
|
|
1319
|
-
|
|
1320
|
-
function error(msg) {
|
|
1321
|
-
process.stdout.write(JSON.stringify({ error: msg }));
|
|
1322
|
-
process.exit(1);
|
|
1323
|
-
}
|
|
1324
|
-
|
|
1325
|
-
/**
|
|
1326
|
-
* Write content to a file atomically: write to .tmp, backup original to .bak,
|
|
1327
|
-
* rename .tmp over original. On failure, restore from .bak if available.
|
|
1328
|
-
*
|
|
1329
|
-
* @param {string} filePath - Target file path
|
|
1330
|
-
* @param {string} content - Content to write
|
|
1331
|
-
* @returns {{success: boolean, error?: string}} Result
|
|
1332
|
-
*/
|
|
1333
|
-
function atomicWrite(filePath, content) {
|
|
1334
|
-
const tmpPath = filePath + '.tmp';
|
|
1335
|
-
const bakPath = filePath + '.bak';
|
|
1336
|
-
|
|
1337
|
-
try {
|
|
1338
|
-
// 1. Write to temp file
|
|
1339
|
-
fs.writeFileSync(tmpPath, content, 'utf8');
|
|
1340
|
-
|
|
1341
|
-
// 2. Backup original if it exists
|
|
1342
|
-
if (fs.existsSync(filePath)) {
|
|
1343
|
-
try {
|
|
1344
|
-
fs.copyFileSync(filePath, bakPath);
|
|
1345
|
-
} catch (_e) {
|
|
1346
|
-
// Backup failure is non-fatal — proceed with rename
|
|
1423
|
+
output(claimRelease(phaseSlug, sessionId));
|
|
1424
|
+
} else if (command === 'claim' && subcommand === 'list') {
|
|
1425
|
+
output(claimList());
|
|
1426
|
+
} else if (command === 'status' && subcommand === 'render') {
|
|
1427
|
+
output(_statusRender(planningDir));
|
|
1428
|
+
} else if (command === 'suggest-next') {
|
|
1429
|
+
output(_suggestNext(planningDir));
|
|
1430
|
+
} else if (command === 'tmux' && subcommand === 'detect') {
|
|
1431
|
+
const tmuxEnv = process.env.TMUX || '';
|
|
1432
|
+
const result = {
|
|
1433
|
+
in_tmux: !!tmuxEnv,
|
|
1434
|
+
pane: process.env.TMUX_PANE || null,
|
|
1435
|
+
session: null
|
|
1436
|
+
};
|
|
1437
|
+
if (tmuxEnv) {
|
|
1438
|
+
// TMUX env format: /socket/path,PID,INDEX
|
|
1439
|
+
const parts = tmuxEnv.split(',');
|
|
1440
|
+
if (parts.length >= 1) {
|
|
1441
|
+
result.session = parts[0].split('/').pop() || null;
|
|
1442
|
+
}
|
|
1347
1443
|
}
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
//
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1444
|
+
output(result);
|
|
1445
|
+
|
|
1446
|
+
// ─── Quick Task Operations ─────────────────────────────────────────────────
|
|
1447
|
+
} else if (command === 'quick' && subcommand === 'init') {
|
|
1448
|
+
const desc = args.slice(2).join(' ') || '';
|
|
1449
|
+
const quickInitMod = require('./lib/quick-init.js');
|
|
1450
|
+
output(quickInitMod.quickInit(desc, planningDir));
|
|
1451
|
+
|
|
1452
|
+
// ─── Slug Generation ─────────────────────────────────────────────────────
|
|
1453
|
+
} else if (command === 'generate-slug' || command === 'slug-generate') {
|
|
1454
|
+
const text = args.slice(1).join(' ');
|
|
1455
|
+
if (!text) error('Usage: pbr-tools.js generate-slug <text>');
|
|
1456
|
+
const slug = text.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
|
|
1457
|
+
output({ slug });
|
|
1458
|
+
|
|
1459
|
+
// ─── Parse Args ────────────────────────────────────────────────────────────
|
|
1460
|
+
} else if (command === 'parse-args') {
|
|
1461
|
+
const type = args[1];
|
|
1462
|
+
const rawInput = args.slice(2).join(' ');
|
|
1463
|
+
if (!type) error('Usage: pbr-tools.js parse-args <type> <args>\nTypes: plan, quick');
|
|
1464
|
+
const { parseArgs } = require('./lib/parse-args');
|
|
1465
|
+
output(parseArgs(type, rawInput));
|
|
1466
|
+
|
|
1467
|
+
// ─── Status Fingerprint ──────────────────────────────────────────────────
|
|
1468
|
+
} else if (command === 'status' && subcommand === 'fingerprint') {
|
|
1469
|
+
const crypto = require('crypto');
|
|
1470
|
+
const files = {};
|
|
1471
|
+
let combinedContent = '';
|
|
1472
|
+
for (const name of ['STATE.md', 'ROADMAP.md']) {
|
|
1473
|
+
const filePath = path.join(planningDir, name);
|
|
1474
|
+
try {
|
|
1475
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
1476
|
+
const stat = fs.statSync(filePath);
|
|
1477
|
+
const hash = crypto.createHash('sha256').update(content).digest('hex').slice(0, 8);
|
|
1478
|
+
files[name] = {
|
|
1479
|
+
hash,
|
|
1480
|
+
mtime: stat.mtime.toISOString(),
|
|
1481
|
+
lines: content.split('\n').length
|
|
1482
|
+
};
|
|
1483
|
+
combinedContent += content;
|
|
1484
|
+
} catch {
|
|
1485
|
+
files[name] = { hash: null, mtime: null, lines: 0 };
|
|
1486
|
+
}
|
|
1359
1487
|
}
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1488
|
+
const fingerprint = combinedContent
|
|
1489
|
+
? crypto.createHash('sha256').update(combinedContent).digest('hex').slice(0, 8)
|
|
1490
|
+
: null;
|
|
1491
|
+
let phaseDirs = 0;
|
|
1492
|
+
const phasesDir = path.join(planningDir, 'phases');
|
|
1493
|
+
try {
|
|
1494
|
+
const entries = fs.readdirSync(phasesDir, { withFileTypes: true });
|
|
1495
|
+
phaseDirs = entries.filter(e => e.isDirectory()).length;
|
|
1496
|
+
} catch { /* no phases dir */ }
|
|
1497
|
+
output({
|
|
1498
|
+
fingerprint,
|
|
1499
|
+
files,
|
|
1500
|
+
phase_dirs: phaseDirs,
|
|
1501
|
+
timestamp: new Date().toISOString()
|
|
1502
|
+
});
|
|
1503
|
+
|
|
1504
|
+
} else if (command === 'quick-status') {
|
|
1505
|
+
const result = quickStatus();
|
|
1506
|
+
process.stdout.write(result.text + '\n');
|
|
1507
|
+
|
|
1508
|
+
} else if (command === 'ci-fix') {
|
|
1509
|
+
const dryRun = args.includes('--dry-run');
|
|
1510
|
+
const maxIterIdx = args.indexOf('--max-iterations');
|
|
1511
|
+
const maxIterations = maxIterIdx !== -1 ? parseInt(args[maxIterIdx + 1], 10) : 3;
|
|
1512
|
+
output(ciFix({ dryRun, maxIterations }));
|
|
1363
1513
|
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
if (fs.existsSync(tmpPath)) {
|
|
1367
|
-
fs.unlinkSync(tmpPath);
|
|
1368
|
-
}
|
|
1369
|
-
} catch (_cleanupErr) {
|
|
1370
|
-
// Best-effort cleanup
|
|
1514
|
+
} else {
|
|
1515
|
+
error(`Unknown command: ${args.join(' ')}\nCommands: state load|check-progress|update|patch|advance-plan|record-metric, config validate|load-defaults|save-defaults|resolve-depth, validate health, validate-project, migrate [--dry-run] [--force], init execute-phase|plan-phase|quick|verify-work|resume|progress, state-bundle <phase>, plan-index, frontmatter, must-haves, phase-info, phase add|remove|list|complete, roadmap update-status|update-plans, history append|load, todo list|get|add|done, auto-cleanup --phase N|--milestone vN, event, llm health|status|classify|score-source|classify-error|summarize|metrics [--session <ISO>]|adjust-thresholds, learnings ingest|query|check-thresholds, milestone-stats <version>, context-triage [--agents-done N] [--plans-total N] [--step NAME], ci-poll <run-id> [--timeout <seconds>], ci-fix [--dry-run] [--max-iterations N], rollback <manifest-path>, session get|set|clear|dump, claim acquire|release|list, skill-section <skill> <section>|--list <skill>, step-verify <skill> <step> <checklist-json>, suggest-alternatives phase-not-found|missing-prereq|config-invalid [args], tmux detect, quick init, generate-slug|slug-generate, parse-args plan|quick, status fingerprint, quick-status`);
|
|
1371
1516
|
}
|
|
1372
|
-
|
|
1373
|
-
|
|
1517
|
+
} catch (e) {
|
|
1518
|
+
error(e.message);
|
|
1374
1519
|
}
|
|
1375
1520
|
}
|
|
1376
1521
|
|
|
1377
|
-
if (require.main === module || process.argv[1] === __filename) { main(); }
|
|
1378
|
-
module.exports = { parseStateMd, parseRoadmapMd, parseYamlFrontmatter, parseMustHaves, countMustHaves, stateLoad, stateCheckProgress, configLoad, configClearCache, configValidate, lockedFileUpdate, planIndex, determinePhaseStatus, findFiles, atomicWrite, tailLines, frontmatter, mustHavesCollect, phaseInfo, stateUpdate, roadmapUpdateStatus, roadmapUpdatePlans, updateLegacyStateField, updateFrontmatterField, updateTableRow, findRoadmapRow, resolveDepthProfile, DEPTH_PROFILE_DEFAULTS, historyAppend, historyLoad, VALID_STATUS_TRANSITIONS, validateStatusTransition };
|
|
1522
|
+
if (require.main === module || process.argv[1] === __filename) { main().catch(err => { process.stderr.write(err.message + '\n'); process.exit(1); }); }
|
|
1523
|
+
module.exports = { KNOWN_AGENTS, initExecutePhase, initPlanPhase, initQuick, initVerifyWork, initResume, initProgress, initStateBundle: stateBundle, stateBundle, statePatch, stateAdvancePlan, stateRecordMetric, stateRecordActivity, stateUpdateProgress, parseStateMd, parseRoadmapMd, parseYamlFrontmatter, parseMustHaves, countMustHaves, stateLoad, stateCheckProgress, configLoad, configClearCache, configValidate, lockedFileUpdate, planIndex, determinePhaseStatus, findFiles, atomicWrite, tailLines, frontmatter, mustHavesCollect, phaseInfo, stateUpdate, roadmapUpdateStatus, roadmapUpdatePlans, roadmapAnalyze, updateLegacyStateField, updateFrontmatterField, updateTableRow, findRoadmapRow, resolveDepthProfile, DEPTH_PROFILE_DEFAULTS, historyAppend, historyLoad, VALID_STATUS_TRANSITIONS, validateStatusTransition, writeActiveSkill, validateProject, phaseAdd, phaseRemove, phaseList, loadUserDefaults, saveUserDefaults, mergeUserDefaults, USER_DEFAULTS_PATH, todoList, todoGet, todoAdd, todoDone, migrate, spotCheck, referenceGet, milestoneStats, contextTriage, stalenessCheck, summaryGate, checkpointInit, checkpointUpdate, seedsMatch, ciPoll, ciFix, parseJestOutput: _parseJestOutput, parseLintOutput: _parseLintOutput, autoFixLint: _autoFixLint, runCiFixLoop: _runCiFixLoop, rollbackPlan, sessionLoad, sessionSave, SESSION_ALLOWED_KEYS, claimAcquire, claimRelease, claimList, skillSectionGet, listSkillHeadings, stepVerify: _stepVerify, phaseAlternatives: _phaseAlternatives, prerequisiteAlternatives: _prereqAlternatives, configAlternatives: _configAlternatives, phaseComplete, phaseInsert, quickStatus, autoCloseTodos, autoArchiveNotes, buildCleanupContext };
|
|
1524
|
+
// NOTE: validateProject, phaseAdd, phaseRemove, phaseList were previously CLI-only (not exported).
|
|
1525
|
+
// They are now exported for testability. This is additive and backwards-compatible.
|