@sienklogic/plan-build-run 2.19.0 → 2.19.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1287 -303
- package/CLAUDE.md +74 -39
- package/LICENSE +2 -1
- package/README.md +412 -177
- package/bin/install.js +2752 -0
- package/dashboard/bin/cli.cjs +96 -0
- package/dashboard/bin/stop.cjs +129 -0
- package/dashboard/eslint.config.js +37 -0
- package/dashboard/index.html +20 -0
- package/dashboard/package.json +27 -25
- package/dashboard/server/index.js +151 -0
- package/dashboard/server/lib/frontmatter.js +92 -0
- package/dashboard/server/middleware/static.js +35 -0
- package/dashboard/server/package.json +16 -0
- package/dashboard/server/routes/agents.js +234 -0
- package/dashboard/server/routes/config.js +64 -0
- package/dashboard/server/routes/health.js +98 -0
- package/dashboard/server/routes/incidents.js +78 -0
- package/dashboard/server/routes/intel.js +69 -0
- package/dashboard/server/routes/memory.js +107 -0
- package/dashboard/server/routes/planning.js +234 -0
- package/dashboard/server/routes/progress.js +77 -0
- package/dashboard/server/routes/projects.js +36 -0
- package/dashboard/server/routes/requirements.js +40 -0
- package/dashboard/server/routes/roadmap.js +69 -0
- package/dashboard/server/routes/sessions.js +70 -0
- package/dashboard/server/routes/status.js +25 -0
- package/dashboard/server/routes/telemetry.js +233 -0
- package/dashboard/server/services/file-watcher.js +105 -0
- package/dashboard/server/services/planning-reader.js +727 -0
- package/dashboard/server/test/cli.test.js +34 -0
- package/dashboard/server/test/frontmatter.test.js +104 -0
- package/dashboard/server/test/isolation.test.js +32 -0
- package/dashboard/server/test/planning-reader.test.js +151 -0
- package/dashboard/server/test/routes.test.js +91 -0
- package/dashboard/server/test/ws.test.js +81 -0
- package/dashboard/server/ws.js +96 -0
- package/dashboard/src/App.jsx +165 -0
- package/dashboard/src/components/charts/BudgetBars.jsx +42 -0
- package/dashboard/src/components/charts/ContextRadar.jsx +34 -0
- package/dashboard/src/components/charts/PhaseDonut.jsx +66 -0
- package/dashboard/src/components/charts/SuccessTrend.jsx +45 -0
- package/dashboard/src/components/charts/TokenChart.jsx +55 -0
- package/dashboard/src/components/charts/index.js +5 -0
- package/dashboard/src/components/config/CfgSection.jsx +93 -0
- package/dashboard/src/components/layout/Header.jsx +89 -0
- package/dashboard/src/components/layout/ProjectSwitcher.jsx +160 -0
- package/dashboard/src/components/layout/Sidebar.jsx +161 -0
- package/dashboard/src/components/ui/AutoModeBanner.jsx +138 -0
- package/dashboard/src/components/ui/BackButton.jsx +27 -0
- package/dashboard/src/components/ui/Badge.jsx +27 -0
- package/dashboard/src/components/ui/Card.jsx +23 -0
- package/dashboard/src/components/ui/ChartTooltip.jsx +48 -0
- package/dashboard/src/components/ui/CheckpointBox.jsx +110 -0
- package/dashboard/src/components/ui/CodeBlock.jsx +27 -0
- package/dashboard/src/components/ui/ConfidenceBadge.jsx +20 -0
- package/dashboard/src/components/ui/ConfirmModal.jsx +161 -0
- package/dashboard/src/components/ui/ConnectionBanner.jsx +60 -0
- package/dashboard/src/components/ui/ErrorBoundary.jsx +106 -0
- package/dashboard/src/components/ui/ErrorBox.jsx +107 -0
- package/dashboard/src/components/ui/KeyValue.jsx +33 -0
- package/dashboard/src/components/ui/LoadingSkeleton.jsx +84 -0
- package/dashboard/src/components/ui/MetricCard.jsx +58 -0
- package/dashboard/src/components/ui/NextUpBlock.jsx +92 -0
- package/dashboard/src/components/ui/NumberInput.jsx +44 -0
- package/dashboard/src/components/ui/PBRBanner.jsx +47 -0
- package/dashboard/src/components/ui/PipelineView.jsx +130 -0
- package/dashboard/src/components/ui/ProgressBar.jsx +28 -0
- package/dashboard/src/components/ui/ProgressDisplay.jsx +47 -0
- package/dashboard/src/components/ui/QualityGateBadge.jsx +15 -0
- package/dashboard/src/components/ui/SectionTitle.jsx +35 -0
- package/dashboard/src/components/ui/SelectInput.jsx +45 -0
- package/dashboard/src/components/ui/StatusDot.jsx +51 -0
- package/dashboard/src/components/ui/StatusSymbol.jsx +49 -0
- package/dashboard/src/components/ui/TabBar.jsx +41 -0
- package/dashboard/src/components/ui/TextInput.jsx +42 -0
- package/dashboard/src/components/ui/Toast.jsx +117 -0
- package/dashboard/src/components/ui/Toggle.jsx +70 -0
- package/dashboard/src/components/ui/index.js +29 -0
- package/dashboard/src/hooks/useDocumentTitle.js +16 -0
- package/dashboard/src/hooks/useFetch.js +50 -0
- package/dashboard/src/hooks/useToast.jsx +43 -0
- package/dashboard/src/hooks/useWebSocket.js +103 -0
- package/dashboard/src/lib/api.js +112 -0
- package/dashboard/src/lib/configSchema.js +189 -0
- package/dashboard/src/lib/constants.js +22 -0
- package/dashboard/src/main.jsx +15 -0
- package/dashboard/src/pages/AgentsPage.jsx +191 -0
- package/dashboard/src/pages/ConfigPage.jsx +298 -0
- package/dashboard/src/pages/HooksPage.jsx +412 -0
- package/dashboard/src/pages/IncidentsPage.jsx +135 -0
- package/dashboard/src/pages/IntelPage.jsx +193 -0
- package/dashboard/src/pages/LiveFeed.jsx +274 -0
- package/dashboard/src/pages/MemoryPage.jsx +107 -0
- package/dashboard/src/pages/OnboardingPage.jsx +117 -0
- package/dashboard/src/pages/Overview.jsx +360 -0
- package/dashboard/src/pages/PhaseDetailView.jsx +216 -0
- package/dashboard/src/pages/PlanningPage.jsx +181 -0
- package/dashboard/src/pages/ProgressPage.jsx +249 -0
- package/dashboard/src/pages/ResearchPage.jsx +129 -0
- package/dashboard/src/pages/RoadmapPage.jsx +251 -0
- package/dashboard/src/pages/SessionsPage.jsx +117 -0
- package/dashboard/src/pages/Telemetry.jsx +166 -0
- package/dashboard/src/pages/planning/DecisionsTab.jsx +153 -0
- package/dashboard/src/pages/planning/FilesTab.jsx +420 -0
- package/dashboard/src/pages/planning/MilestoneDetail.jsx +319 -0
- package/dashboard/src/pages/planning/MilestonesTab.jsx +151 -0
- package/dashboard/src/pages/planning/NotesTab.jsx +251 -0
- package/dashboard/src/pages/planning/PhasesTab.jsx +218 -0
- package/dashboard/src/pages/planning/QuickTab.jsx +50 -0
- package/dashboard/src/pages/planning/ResearchTab.jsx +103 -0
- package/dashboard/src/pages/planning/TodosTab.jsx +297 -0
- package/dashboard/src/theme/ThemeProvider.jsx +38 -0
- package/dashboard/src/theme/tokens.js +17 -0
- package/dashboard/tests/components/ConfirmModal.test.jsx +179 -0
- package/dashboard/tests/components/ConnectionBanner.test.jsx +37 -0
- package/dashboard/tests/components/ErrorBoundary.test.jsx +59 -0
- package/dashboard/tests/components/LoadingSkeleton.test.jsx +46 -0
- package/dashboard/tests/components/ToastContainer.test.jsx +47 -0
- package/dashboard/tests/components/Toggle.test.jsx +61 -0
- package/dashboard/tests/hooks/useFetch.test.jsx +77 -0
- package/dashboard/tests/hooks/useToast.test.jsx +78 -0
- package/dashboard/tests/hooks/useWebSocket.test.jsx +128 -0
- package/dashboard/tests/pages/ConfigPage.test.jsx +199 -0
- package/dashboard/tests/pages/PlanningPage.test.jsx +119 -0
- package/dashboard/tests/pages/planning/FilesTab.test.jsx +198 -0
- package/dashboard/tests/pages/planning/NotesTab.test.jsx +178 -0
- package/dashboard/tests/pages/planning/TodosTab.test.jsx +188 -0
- package/dashboard/tests/performance.test.jsx +46 -0
- package/dashboard/tests/routes/config.test.js +98 -0
- package/dashboard/tests/routes/health.test.js +40 -0
- package/dashboard/tests/routes/planning.test.js +112 -0
- package/dashboard/tests/routes/roadmap.test.js +91 -0
- package/dashboard/tests/routes/status.test.js +131 -0
- package/dashboard/tests/server/planning-reader.test.js +153 -0
- package/dashboard/tests/setup.js +7 -0
- package/dashboard/vite.config.js +41 -0
- package/package.json +55 -40
- package/plan-build-run/bin/config-schema.json +1298 -0
- package/plugins/pbr/.claude-plugin/plugin.json +1 -1
- package/plugins/pbr/CLAUDE.md +19 -0
- package/plugins/pbr/UI-CONSISTENCY-GAPS.md +1 -1
- package/plugins/pbr/agents/advisor-researcher.md +101 -0
- package/plugins/pbr/agents/audit.md +205 -89
- package/plugins/pbr/agents/codebase-mapper.md +158 -23
- package/plugins/pbr/agents/debugger.md +212 -34
- package/plugins/pbr/agents/dev-sync.md +206 -0
- package/plugins/pbr/agents/executor.md +717 -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 +332 -0
- package/plugins/pbr/agents/nyquist-auditor.md +253 -0
- package/plugins/pbr/agents/plan-checker.md +265 -65
- package/plugins/pbr/agents/planner.md +440 -42
- package/plugins/pbr/agents/researcher.md +219 -36
- package/plugins/pbr/agents/roadmapper.md +397 -0
- package/plugins/pbr/agents/synthesizer.md +166 -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 +495 -47
- package/plugins/pbr/commands/add-phase.md +75 -0
- package/plugins/pbr/commands/add-todo.md +8 -0
- package/plugins/pbr/commands/audit-fix.md +5 -0
- package/plugins/pbr/commands/audit-milestone.md +8 -0
- package/plugins/pbr/commands/autonomous.md +5 -0
- package/plugins/pbr/commands/backlog.md +6 -0
- package/plugins/pbr/commands/check-todos.md +8 -0
- package/plugins/pbr/commands/complete-milestone.md +8 -0
- package/plugins/pbr/commands/config.md +1 -1
- package/plugins/pbr/commands/discuss-phase.md +6 -0
- package/plugins/pbr/commands/do.md +5 -0
- package/plugins/pbr/commands/execute-phase.md +6 -0
- package/plugins/pbr/commands/fast.md +6 -0
- package/plugins/pbr/commands/forensics.md +6 -0
- package/plugins/pbr/commands/import.md +1 -1
- package/plugins/pbr/commands/insert-phase.md +65 -0
- package/plugins/pbr/commands/intel.md +5 -0
- package/plugins/pbr/commands/join-discord.md +11 -0
- package/plugins/pbr/commands/list-phase-assumptions.md +5 -0
- package/plugins/pbr/commands/map-codebase.md +6 -0
- package/plugins/pbr/commands/milestone-summary.md +6 -0
- package/plugins/pbr/commands/new-milestone.md +8 -0
- package/plugins/pbr/commands/new-project.md +6 -0
- package/plugins/pbr/commands/pause-work.md +5 -0
- package/plugins/pbr/commands/plan-milestone-gaps.md +7 -0
- package/plugins/pbr/commands/plan-phase.md +6 -0
- package/plugins/pbr/commands/plant-seed.md +6 -0
- package/plugins/pbr/commands/profile-user.md +5 -0
- package/plugins/pbr/commands/profile.md +5 -0
- package/plugins/pbr/commands/progress.md +6 -0
- package/plugins/pbr/commands/quick.md +1 -1
- package/plugins/pbr/commands/reapply-patches.md +47 -0
- package/plugins/pbr/commands/release.md +6 -0
- package/plugins/pbr/commands/remove-phase.md +66 -0
- package/plugins/pbr/commands/research-phase.md +59 -0
- package/plugins/pbr/commands/resume-work.md +5 -0
- package/plugins/pbr/commands/seed.md +6 -0
- package/plugins/pbr/commands/session-report.md +5 -0
- package/plugins/pbr/commands/set-profile.md +6 -0
- package/plugins/pbr/commands/settings.md +5 -0
- package/plugins/pbr/commands/setup.md +1 -1
- package/plugins/pbr/commands/ship.md +5 -0
- package/plugins/pbr/commands/stats.md +6 -0
- package/plugins/pbr/commands/test.md +5 -0
- package/plugins/pbr/commands/thread.md +6 -0
- package/plugins/pbr/commands/todo.md +1 -1
- package/plugins/pbr/commands/ui-phase.md +5 -0
- package/plugins/pbr/commands/ui-review.md +5 -0
- package/plugins/pbr/commands/undo.md +5 -0
- package/plugins/pbr/commands/update.md +37 -0
- package/plugins/pbr/commands/validate-phase.md +5 -0
- package/plugins/pbr/commands/verify-work.md +6 -0
- package/plugins/pbr/dashboard/package-lock.json +6 -0
- package/plugins/pbr/dist/architecture-guard.js +59 -0
- package/plugins/pbr/dist/audit-dimensions.js +556 -0
- package/plugins/pbr/dist/auto-continue.js +277 -0
- package/plugins/pbr/dist/block-skill-self-read.js +124 -0
- package/plugins/pbr/dist/check-agent-state-write.js +63 -0
- package/plugins/pbr/dist/check-config-change.js +155 -0
- package/plugins/pbr/dist/check-cross-plugin-sync.js +93 -0
- package/plugins/pbr/dist/check-dangerous-commands.js +193 -0
- package/plugins/pbr/dist/check-direct-state-write.js +37 -0
- package/plugins/pbr/dist/check-doc-sprawl.js +102 -0
- package/plugins/pbr/dist/check-phase-boundary.js +191 -0
- package/plugins/pbr/dist/check-plan-format.js +209 -0
- package/plugins/pbr/dist/check-read-first.js +345 -0
- package/plugins/pbr/dist/check-roadmap-sync.js +507 -0
- package/plugins/pbr/dist/check-skill-workflow.js +354 -0
- package/plugins/pbr/dist/check-state-sync.js +658 -0
- package/plugins/pbr/dist/check-subagent-output.js +396 -0
- package/plugins/pbr/dist/check-summary-gate.js +188 -0
- package/plugins/pbr/dist/context-bridge.js +425 -0
- package/plugins/pbr/dist/context-budget-check.js +442 -0
- package/plugins/pbr/dist/context-quality.js +271 -0
- package/plugins/pbr/dist/enforce-context-budget.js +138 -0
- package/plugins/pbr/dist/enforce-pbr-workflow.js +277 -0
- package/plugins/pbr/dist/event-handler.js +202 -0
- package/plugins/pbr/dist/event-logger.js +125 -0
- package/plugins/pbr/dist/feedback-loop.js +155 -0
- package/plugins/pbr/dist/graph-update.js +422 -0
- package/plugins/pbr/dist/hook-logger.js +114 -0
- package/plugins/pbr/dist/hook-server-client.js +361 -0
- package/plugins/pbr/dist/hook-server.js +658 -0
- package/plugins/pbr/dist/hooks-schema.json +87 -0
- package/plugins/pbr/dist/instructions-loaded.js +173 -0
- package/plugins/pbr/dist/intercept-plan-mode.js +81 -0
- package/plugins/pbr/dist/log-notification.js +131 -0
- package/plugins/pbr/dist/log-subagent.js +349 -0
- package/plugins/pbr/dist/log-tool-failure.js +140 -0
- package/plugins/pbr/dist/milestone-learnings.js +519 -0
- package/plugins/pbr/dist/pbr-tools.js +1961 -0
- package/plugins/pbr/dist/post-bash-triage.js +96 -0
- package/plugins/pbr/dist/post-compact.js +135 -0
- package/plugins/pbr/dist/post-hoc.js +237 -0
- package/plugins/pbr/dist/post-write-dispatch.js +243 -0
- package/plugins/pbr/dist/post-write-quality.js +208 -0
- package/plugins/pbr/dist/pre-bash-dispatch.js +212 -0
- package/plugins/pbr/dist/pre-skill-dispatch.js +114 -0
- package/plugins/pbr/dist/pre-task-dispatch.js +269 -0
- package/plugins/pbr/dist/pre-write-dispatch.js +234 -0
- package/plugins/pbr/dist/progress-tracker.js +173 -0
- package/plugins/pbr/dist/prompt-guard.js +114 -0
- package/plugins/pbr/dist/prompt-routing.js +209 -0
- package/plugins/pbr/dist/quick-status.js +179 -0
- package/plugins/pbr/dist/record-incident.js +37 -0
- package/plugins/pbr/dist/run-hook.js +144 -0
- package/plugins/pbr/dist/session-cleanup.js +653 -0
- package/plugins/pbr/dist/session-tracker.js +124 -0
- package/plugins/pbr/dist/status-line.js +849 -0
- package/plugins/pbr/dist/suggest-compact.js +307 -0
- package/plugins/pbr/dist/sync-context-to-claude.js +100 -0
- package/plugins/pbr/dist/task-completed.js +206 -0
- package/plugins/pbr/dist/track-context-budget.js +432 -0
- package/plugins/pbr/dist/track-user-gates.js +88 -0
- package/plugins/pbr/dist/trust-tracker.js +193 -0
- package/plugins/pbr/dist/validate-commit.js +215 -0
- package/plugins/pbr/dist/validate-skill-args.js +222 -0
- package/plugins/pbr/dist/validate-task.js +271 -0
- package/plugins/pbr/dist/worktree-create.js +144 -0
- package/plugins/pbr/dist/worktree-remove.js +147 -0
- package/plugins/pbr/hooks/hooks.json +143 -60
- package/plugins/pbr/references/agent-contracts.md +39 -8
- package/plugins/pbr/references/agent-teams.md +3 -3
- package/plugins/pbr/references/archive/checkpoints.md +189 -0
- package/plugins/pbr/references/archive/context-quality-tiers.md +45 -0
- package/plugins/pbr/references/archive/hook-ordering.md +89 -0
- package/plugins/pbr/references/archive/limitations.md +106 -0
- package/plugins/pbr/references/archive/pbr-rules.md +194 -0
- package/plugins/pbr/references/archive/pbr-tools-cli.md +415 -0
- package/plugins/pbr/references/archive/pretooluse-jsonl-behavior.md +58 -0
- package/plugins/pbr/references/archive/signal-files.md +41 -0
- package/plugins/pbr/references/archive/tmux-setup.md +288 -0
- package/plugins/pbr/references/archive/verification-matrix.md +34 -0
- package/plugins/pbr/references/archive/verification-patterns.md +277 -0
- package/plugins/pbr/references/archive/worktree-sparse-checkout.md +86 -0
- package/plugins/pbr/references/checkpoints.md +723 -104
- package/plugins/pbr/references/config-reference.md +376 -10
- package/plugins/pbr/references/continuation-format.md +1 -0
- package/plugins/pbr/references/decimal-phase-calculation.md +65 -0
- package/plugins/pbr/references/deviation-rules.md +12 -0
- package/plugins/pbr/references/git-integration.md +110 -27
- package/plugins/pbr/references/git-planning-commit.md +35 -0
- package/plugins/pbr/references/model-profile-resolution.md +34 -0
- package/plugins/pbr/references/model-profiles.md +90 -7
- package/plugins/pbr/references/model-selection.md +1 -1
- package/plugins/pbr/references/node-repair.md +48 -0
- package/plugins/pbr/references/plan-authoring.md +65 -0
- package/plugins/pbr/references/plan-format.md +161 -10
- package/plugins/pbr/references/questioning.md +138 -49
- package/plugins/pbr/references/reading-verification.md +4 -4
- package/plugins/pbr/references/tdd.md +263 -0
- package/plugins/pbr/references/ui-brand.md +449 -0
- package/plugins/pbr/references/verification-overrides.md +39 -0
- package/plugins/pbr/references/verification-patterns.md +529 -113
- package/plugins/pbr/scripts/architecture-guard.js +59 -0
- package/plugins/pbr/scripts/audit-checks/behavioral-compliance.js +2098 -0
- package/plugins/pbr/scripts/audit-checks/error-analysis.js +989 -0
- package/plugins/pbr/scripts/audit-checks/feature-verification.js +723 -0
- package/plugins/pbr/scripts/audit-checks/index.js +433 -0
- package/plugins/pbr/scripts/audit-checks/infrastructure.js +816 -0
- package/plugins/pbr/scripts/audit-checks/quality-metrics.js +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 +396 -0
- package/plugins/pbr/scripts/audit-checks/si-cross-cutting-checks.js +272 -0
- package/plugins/pbr/scripts/audit-checks/si-skill-checks.js +424 -0
- package/plugins/pbr/scripts/audit-checks/workflow-compliance.js +1175 -0
- package/plugins/pbr/scripts/audit-dimensions.js +556 -0
- package/plugins/pbr/scripts/auto-continue.js +192 -31
- package/plugins/pbr/scripts/block-skill-self-read.js +124 -0
- package/plugins/pbr/scripts/check-agent-state-write.js +63 -0
- package/plugins/pbr/scripts/check-config-change.js +155 -0
- package/plugins/pbr/scripts/check-cross-plugin-sync.js +93 -0
- package/plugins/pbr/scripts/check-dangerous-commands.js +18 -5
- package/plugins/pbr/scripts/check-direct-state-write.js +37 -0
- package/plugins/pbr/scripts/check-phase-boundary.js +3 -8
- package/plugins/pbr/scripts/check-plan-format.js +135 -278
- package/plugins/pbr/scripts/check-read-first.js +345 -0
- package/plugins/pbr/scripts/check-roadmap-sync.js +182 -21
- package/plugins/pbr/scripts/check-skill-workflow.js +24 -27
- package/plugins/pbr/scripts/check-state-sync.js +339 -215
- package/plugins/pbr/scripts/check-subagent-output.js +281 -275
- package/plugins/pbr/scripts/check-summary-gate.js +5 -15
- package/plugins/pbr/scripts/config-schema.json +1134 -95
- package/plugins/pbr/scripts/context-bridge.js +425 -0
- package/plugins/pbr/scripts/context-budget-check.js +169 -14
- package/plugins/pbr/scripts/context-quality.js +271 -0
- package/plugins/pbr/scripts/enforce-context-budget.js +138 -0
- package/plugins/pbr/scripts/enforce-pbr-workflow.js +277 -0
- package/plugins/pbr/scripts/event-handler.js +127 -87
- package/plugins/pbr/scripts/event-logger.js +58 -25
- package/plugins/pbr/scripts/feedback-loop.js +155 -0
- package/plugins/pbr/scripts/graph-update.js +422 -0
- package/plugins/pbr/scripts/hook-logger.js +69 -35
- package/plugins/pbr/scripts/hook-server-client.js +361 -0
- package/plugins/pbr/scripts/hook-server.js +658 -0
- package/plugins/pbr/scripts/hooks-schema.json +13 -5
- package/plugins/pbr/scripts/instructions-loaded.js +173 -0
- package/plugins/pbr/scripts/intent-router.cjs +147 -0
- package/plugins/pbr/scripts/intercept-plan-mode.js +52 -18
- package/plugins/pbr/scripts/lib/alternatives.js +203 -0
- package/plugins/pbr/scripts/lib/audit.js +65 -0
- package/plugins/pbr/scripts/lib/auto-cleanup.js +221 -0
- package/plugins/pbr/scripts/lib/auto-verify.js +103 -0
- package/plugins/pbr/scripts/lib/build.js +719 -0
- package/plugins/pbr/scripts/lib/ci-fix-loop.js +228 -0
- package/plugins/pbr/scripts/lib/commands.js +483 -0
- package/plugins/pbr/scripts/lib/compound.js +216 -0
- package/plugins/pbr/scripts/lib/config.js +1308 -0
- package/plugins/pbr/scripts/lib/context.js +254 -0
- package/plugins/pbr/scripts/lib/contextual-help.js +183 -0
- package/plugins/pbr/scripts/lib/convention-detector.js +413 -0
- package/plugins/pbr/scripts/lib/core.js +1569 -0
- package/plugins/pbr/scripts/lib/dashboard-launch.js +364 -0
- package/plugins/pbr/scripts/lib/data-hygiene.js +179 -0
- package/plugins/pbr/scripts/lib/decision-extraction.js +183 -0
- package/plugins/pbr/scripts/lib/decisions.js +194 -0
- package/plugins/pbr/scripts/lib/dependency-break.js +147 -0
- package/plugins/pbr/scripts/lib/format-validators.js +1025 -0
- package/plugins/pbr/scripts/lib/frontmatter.js +302 -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 +93 -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/plan-validation.js +114 -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 +137 -0
- package/plugins/pbr/scripts/lib/gates/user-confirmation.js +93 -0
- package/plugins/pbr/scripts/lib/graph-cli.js +89 -0
- package/plugins/pbr/scripts/lib/graph.js +553 -0
- package/plugins/pbr/scripts/lib/health-checks.js +107 -0
- package/plugins/pbr/scripts/lib/health-phase06.js +120 -0
- package/plugins/pbr/scripts/lib/health.js +132 -0
- package/plugins/pbr/scripts/lib/help.js +100 -0
- package/plugins/pbr/scripts/lib/history.js +150 -0
- package/plugins/pbr/scripts/lib/impact-analysis.js +319 -0
- package/plugins/pbr/scripts/lib/incidents.js +190 -0
- package/plugins/pbr/scripts/lib/init.js +643 -0
- package/plugins/pbr/scripts/lib/insights-parser.js +320 -0
- package/plugins/pbr/scripts/lib/intel.js +653 -0
- package/plugins/pbr/scripts/lib/learnings.js +511 -0
- package/plugins/pbr/scripts/lib/migrate.js +298 -0
- package/plugins/pbr/scripts/lib/milestone.js +306 -0
- package/plugins/pbr/scripts/lib/negative-knowledge.js +194 -0
- package/plugins/pbr/scripts/lib/notification-throttle.js +141 -0
- package/plugins/pbr/scripts/lib/onboarding-generator.js +288 -0
- package/plugins/pbr/scripts/lib/parse-args.js +134 -0
- package/plugins/pbr/scripts/lib/pattern-routing.js +55 -0
- package/plugins/pbr/scripts/lib/patterns.js +272 -0
- package/plugins/pbr/scripts/lib/perf.js +190 -0
- package/plugins/pbr/scripts/lib/phase.js +1025 -0
- package/plugins/pbr/scripts/lib/pid-lock.js +154 -0
- package/plugins/pbr/scripts/lib/post-hoc.js +160 -0
- package/plugins/pbr/scripts/lib/pre-commit-checks.js +220 -0
- package/plugins/pbr/scripts/lib/pre-research.js +126 -0
- package/plugins/pbr/scripts/lib/preview.js +174 -0
- package/plugins/pbr/scripts/lib/progress-visualization.js +296 -0
- package/plugins/pbr/scripts/lib/quick-init.js +131 -0
- package/plugins/pbr/scripts/lib/reference.js +236 -0
- package/plugins/pbr/scripts/lib/requirements.js +153 -0
- package/plugins/pbr/scripts/lib/resolve-root.js +66 -0
- package/plugins/pbr/scripts/lib/reverse-spec.js +259 -0
- package/plugins/pbr/scripts/lib/roadmap.js +1089 -0
- package/plugins/pbr/scripts/lib/security-scan.js +200 -0
- package/plugins/pbr/scripts/lib/session-briefing.js +895 -0
- package/plugins/pbr/scripts/lib/skill-section.js +99 -0
- package/plugins/pbr/scripts/lib/smart-next-task.js +198 -0
- package/plugins/pbr/scripts/lib/snapshot-manager.js +232 -0
- package/plugins/pbr/scripts/lib/spec-diff.js +209 -0
- package/plugins/pbr/scripts/lib/spec-engine.js +189 -0
- package/plugins/pbr/scripts/lib/spot-check.js +539 -0
- package/plugins/pbr/scripts/lib/state-queue.js +171 -0
- package/plugins/pbr/scripts/lib/state.js +1082 -0
- package/plugins/pbr/scripts/lib/status-render.js +511 -0
- package/plugins/pbr/scripts/lib/step-verify.js +149 -0
- package/plugins/pbr/scripts/lib/subagent-validators.js +1059 -0
- package/plugins/pbr/scripts/lib/suggest-next.js +435 -0
- package/plugins/pbr/scripts/lib/tech-debt-scanner.js +116 -0
- package/plugins/pbr/scripts/lib/templates.js +362 -0
- package/plugins/pbr/scripts/lib/test-selection.js +163 -0
- package/plugins/pbr/scripts/lib/todo.js +300 -0
- package/plugins/pbr/scripts/lib/verify.js +1483 -0
- package/plugins/pbr/scripts/log-notification.js +131 -0
- package/plugins/pbr/scripts/log-subagent.js +203 -18
- package/plugins/pbr/scripts/log-tool-failure.js +60 -8
- package/plugins/pbr/scripts/milestone-learnings.js +519 -0
- package/plugins/pbr/scripts/package.json +1 -1
- package/plugins/pbr/scripts/pbr-tools.js +1754 -1171
- package/plugins/pbr/scripts/post-bash-triage.js +96 -0
- package/plugins/pbr/scripts/post-compact.js +135 -0
- package/plugins/pbr/scripts/post-hoc.js +237 -0
- package/plugins/pbr/scripts/post-write-dispatch.js +201 -31
- package/plugins/pbr/scripts/post-write-quality.js +4 -3
- package/plugins/pbr/scripts/pre-bash-dispatch.js +147 -51
- package/plugins/pbr/scripts/pre-skill-dispatch.js +114 -0
- package/plugins/pbr/scripts/pre-task-dispatch.js +269 -0
- package/plugins/pbr/scripts/pre-write-dispatch.js +170 -73
- package/plugins/pbr/scripts/progress-tracker.js +122 -310
- package/plugins/pbr/scripts/prompt-guard.js +114 -0
- package/plugins/pbr/scripts/prompt-routing.js +209 -0
- package/plugins/pbr/scripts/quick-status.js +179 -0
- package/plugins/pbr/scripts/record-incident.js +37 -0
- package/plugins/pbr/scripts/risk-classifier.cjs +123 -0
- package/plugins/pbr/scripts/run-hook.js +62 -10
- package/plugins/pbr/scripts/session-cleanup.js +428 -29
- package/plugins/pbr/scripts/session-tracker.js +124 -0
- package/plugins/pbr/scripts/status-line.js +593 -32
- package/plugins/pbr/scripts/suggest-compact.js +201 -13
- package/plugins/pbr/scripts/sync-context-to-claude.js +100 -0
- package/plugins/pbr/scripts/task-completed.js +165 -4
- package/plugins/pbr/scripts/test/config.test.js +126 -0
- package/plugins/pbr/scripts/test/cross-platform.test.js +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 +155 -0
- package/plugins/pbr/scripts/track-context-budget.js +368 -99
- package/plugins/pbr/scripts/track-user-gates.js +88 -0
- package/plugins/pbr/scripts/trust-tracker.js +193 -0
- package/plugins/pbr/scripts/validate-commit.js +41 -26
- package/plugins/pbr/scripts/validate-skill-args.js +87 -15
- package/plugins/pbr/scripts/validate-task.js +83 -627
- package/plugins/pbr/scripts/worktree-create.js +144 -0
- package/plugins/pbr/scripts/worktree-remove.js +147 -0
- package/plugins/pbr/skills/audit/SKILL.md +195 -24
- package/plugins/pbr/skills/audit-fix/SKILL.md +326 -0
- package/plugins/pbr/skills/autonomous/SKILL.md +545 -0
- package/plugins/pbr/skills/backlog/SKILL.md +56 -0
- package/plugins/pbr/skills/begin/SKILL.md +508 -153
- package/plugins/pbr/skills/begin/templates/STATE.md.tmpl +1 -2
- package/plugins/pbr/skills/begin/templates/config.json.tmpl +411 -36
- package/plugins/pbr/skills/begin/templates/researcher-prompt.md.tmpl +28 -0
- package/plugins/pbr/skills/begin/templates/roadmap-prompt.md.tmpl +28 -3
- package/plugins/pbr/skills/begin/templates/synthesis-prompt.md.tmpl +33 -5
- package/plugins/pbr/skills/build/SKILL.md +1040 -354
- 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 +112 -9
- package/plugins/pbr/skills/continue/SKILL.md +113 -33
- package/plugins/pbr/skills/dashboard/SKILL.md +21 -9
- package/plugins/pbr/skills/debug/SKILL.md +70 -12
- package/plugins/pbr/skills/debug/templates/continuation-prompt.md.tmpl +12 -1
- package/plugins/pbr/skills/debug/templates/initial-investigation-prompt.md.tmpl +12 -5
- package/plugins/pbr/skills/discuss/SKILL.md +206 -25
- package/plugins/pbr/skills/discuss/templates/CONTEXT.md.tmpl +21 -1
- package/plugins/pbr/skills/do/SKILL.md +119 -24
- package/plugins/pbr/skills/explore/SKILL.md +95 -20
- package/plugins/pbr/skills/fast/SKILL.md +94 -0
- package/plugins/pbr/skills/forensics/SKILL.md +144 -0
- package/plugins/pbr/skills/health/SKILL.md +35 -117
- package/plugins/pbr/skills/help/SKILL.md +84 -101
- package/plugins/pbr/skills/import/SKILL.md +332 -13
- package/plugins/pbr/skills/intel/SKILL.md +131 -0
- package/plugins/pbr/skills/list-phase-assumptions/SKILL.md +231 -0
- package/plugins/pbr/skills/milestone/SKILL.md +421 -263
- package/plugins/pbr/skills/milestone/templates/audit-output.md.tmpl +76 -0
- package/plugins/pbr/skills/milestone/templates/complete-output.md.tmpl +32 -0
- package/plugins/pbr/skills/milestone/templates/edge-cases.md +54 -0
- package/plugins/pbr/skills/milestone/templates/gaps-output.md.tmpl +25 -0
- package/plugins/pbr/skills/milestone/templates/integration-checker-prompt.md.tmpl +25 -0
- package/plugins/pbr/skills/milestone/templates/new-output.md.tmpl +29 -0
- package/plugins/pbr/skills/milestone-summary/SKILL.md +86 -0
- package/plugins/pbr/skills/note/SKILL.md +20 -4
- package/plugins/pbr/skills/pause/SKILL.md +54 -14
- package/plugins/pbr/skills/pause/templates/continue-here.md.tmpl +33 -52
- package/plugins/pbr/skills/plan/SKILL.md +526 -280
- package/plugins/pbr/skills/plan/templates/checker-prompt.md.tmpl +5 -2
- package/plugins/pbr/skills/plan/templates/completion-output.md.tmpl +27 -0
- package/plugins/pbr/skills/plan/templates/planner-prompt.md.tmpl +27 -1
- package/plugins/pbr/skills/plan/templates/revision-prompt.md.tmpl +21 -5
- package/plugins/pbr/skills/profile/SKILL.md +185 -0
- package/plugins/pbr/skills/profile-user/SKILL.md +227 -0
- package/plugins/pbr/skills/quick/SKILL.md +435 -100
- package/plugins/pbr/skills/release/SKILL.md +206 -0
- package/plugins/pbr/skills/resume/SKILL.md +170 -46
- package/plugins/pbr/skills/review/SKILL.md +217 -164
- package/plugins/pbr/skills/review/templates/verifier-prompt.md.tmpl +7 -0
- package/plugins/pbr/skills/scan/SKILL.md +152 -106
- package/plugins/pbr/skills/scan/templates/mapper-prompt.md.tmpl +5 -56
- package/plugins/pbr/skills/seed/SKILL.md +87 -0
- package/plugins/pbr/skills/session-report/SKILL.md +130 -0
- package/plugins/pbr/skills/setup/SKILL.md +150 -202
- package/plugins/pbr/skills/shared/agent-context-enrichment.md +21 -0
- package/plugins/pbr/skills/shared/agent-type-resolution.md +32 -0
- package/plugins/pbr/skills/shared/commit-planning-docs.md +8 -0
- package/plugins/pbr/skills/shared/context-budget.md +66 -1
- package/plugins/pbr/skills/shared/context-loader-task.md +18 -11
- package/plugins/pbr/skills/shared/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 +49 -56
- package/plugins/pbr/skills/shared/universal-anti-patterns.md +27 -4
- package/plugins/pbr/skills/ship/SKILL.md +155 -0
- package/plugins/pbr/skills/stats/SKILL.md +80 -0
- package/plugins/pbr/skills/status/SKILL.md +185 -119
- package/plugins/pbr/skills/test/SKILL.md +254 -0
- package/plugins/pbr/skills/thread/SKILL.md +73 -0
- package/plugins/pbr/skills/todo/SKILL.md +28 -72
- package/plugins/pbr/skills/ui-phase/SKILL.md +180 -0
- package/plugins/pbr/skills/ui-review/SKILL.md +206 -0
- package/plugins/pbr/skills/undo/SKILL.md +221 -0
- package/plugins/pbr/skills/validate-phase/SKILL.md +362 -0
- package/plugins/pbr/templates/CONTEXT.md.tmpl +45 -20
- package/plugins/pbr/templates/DISCOVERY.md.tmpl +29 -0
- package/plugins/pbr/templates/DISCUSSION-LOG.md.tmpl +49 -0
- package/plugins/pbr/templates/HANDOFF.json.tmpl +30 -0
- package/plugins/pbr/templates/INTEGRATION-REPORT.md.tmpl +18 -2
- package/plugins/pbr/templates/MILESTONE-AUDIT.md.tmpl +44 -0
- package/plugins/pbr/templates/PROJECT.md.tmpl +126 -0
- package/plugins/pbr/templates/REQUIREMENTS.md.tmpl +96 -0
- package/plugins/pbr/templates/RETROSPECTIVE.md.tmpl +43 -0
- package/plugins/pbr/templates/ROADMAP.md.tmpl +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/UAT.md.tmpl +94 -0
- package/plugins/pbr/templates/UI-SPEC.md.tmpl +144 -0
- package/plugins/pbr/templates/VALIDATION.md.tmpl +94 -0
- package/plugins/pbr/templates/VERIFICATION-DETAIL.md.tmpl +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 +364 -0
- package/scripts/generate-derivatives.js +581 -0
- package/scripts/posttest.js +93 -0
- package/scripts/release.js +262 -0
- package/scripts/run-tests.cjs +29 -0
- package/scripts/test-wrapper.js +43 -0
- package/dashboard/bin/cli.js +0 -25
- package/dashboard/public/css/layout.css +0 -704
- package/dashboard/public/css/status-colors.css +0 -98
- package/dashboard/public/css/tokens.css +0 -59
- package/dashboard/public/js/htmx-title.js +0 -5
- package/dashboard/public/js/sidebar-toggle.js +0 -34
- package/dashboard/public/js/sse-client.js +0 -100
- package/dashboard/public/js/theme-toggle.js +0 -46
- package/dashboard/src/app.js +0 -91
- package/dashboard/src/middleware/current-phase.js +0 -24
- package/dashboard/src/middleware/errorHandler.js +0 -52
- package/dashboard/src/middleware/notFoundHandler.js +0 -9
- package/dashboard/src/repositories/planning.repository.js +0 -130
- package/dashboard/src/routes/events.routes.js +0 -45
- package/dashboard/src/routes/index.routes.js +0 -35
- package/dashboard/src/routes/pages.routes.js +0 -426
- package/dashboard/src/server.js +0 -42
- package/dashboard/src/services/analytics.service.js +0 -141
- package/dashboard/src/services/dashboard.service.js +0 -309
- package/dashboard/src/services/milestone.service.js +0 -222
- package/dashboard/src/services/notes.service.js +0 -50
- package/dashboard/src/services/phase.service.js +0 -232
- package/dashboard/src/services/project.service.js +0 -57
- package/dashboard/src/services/roadmap.service.js +0 -258
- package/dashboard/src/services/sse.service.js +0 -58
- package/dashboard/src/services/todo.service.js +0 -272
- package/dashboard/src/services/watcher.service.js +0 -48
- package/dashboard/src/utils/cache.js +0 -55
- package/dashboard/src/views/analytics.ejs +0 -5
- package/dashboard/src/views/coming-soon.ejs +0 -11
- package/dashboard/src/views/dependencies.ejs +0 -5
- package/dashboard/src/views/error.ejs +0 -20
- package/dashboard/src/views/index.ejs +0 -5
- package/dashboard/src/views/milestone-detail.ejs +0 -5
- package/dashboard/src/views/milestones.ejs +0 -5
- package/dashboard/src/views/notes.ejs +0 -5
- package/dashboard/src/views/partials/analytics-content.ejs +0 -90
- package/dashboard/src/views/partials/breadcrumbs.ejs +0 -14
- package/dashboard/src/views/partials/dashboard-content.ejs +0 -84
- package/dashboard/src/views/partials/dependencies-content.ejs +0 -48
- package/dashboard/src/views/partials/empty-state.ejs +0 -7
- package/dashboard/src/views/partials/footer.ejs +0 -3
- package/dashboard/src/views/partials/head.ejs +0 -30
- package/dashboard/src/views/partials/header.ejs +0 -21
- package/dashboard/src/views/partials/layout-bottom.ejs +0 -43
- package/dashboard/src/views/partials/layout-top.ejs +0 -16
- package/dashboard/src/views/partials/milestone-detail-content.ejs +0 -20
- package/dashboard/src/views/partials/milestones-content.ejs +0 -88
- package/dashboard/src/views/partials/notes-content.ejs +0 -23
- package/dashboard/src/views/partials/phase-content.ejs +0 -193
- package/dashboard/src/views/partials/phase-doc-content.ejs +0 -38
- package/dashboard/src/views/partials/phases-content.ejs +0 -124
- package/dashboard/src/views/partials/roadmap-content.ejs +0 -180
- package/dashboard/src/views/partials/sidebar.ejs +0 -99
- package/dashboard/src/views/partials/todo-create-content.ejs +0 -54
- package/dashboard/src/views/partials/todo-detail-content.ejs +0 -42
- package/dashboard/src/views/partials/todos-content.ejs +0 -97
- package/dashboard/src/views/phase-detail.ejs +0 -5
- package/dashboard/src/views/phase-doc.ejs +0 -5
- package/dashboard/src/views/phases.ejs +0 -5
- package/dashboard/src/views/roadmap.ejs +0 -5
- package/dashboard/src/views/todo-create.ejs +0 -5
- package/dashboard/src/views/todo-detail.ejs +0 -5
- package/dashboard/src/views/todos.ejs +0 -5
- package/plugins/copilot-pbr/CHANGELOG.md +0 -19
- package/plugins/copilot-pbr/README.md +0 -139
- package/plugins/copilot-pbr/agents/audit.agent.md +0 -113
- package/plugins/copilot-pbr/agents/codebase-mapper.agent.md +0 -151
- package/plugins/copilot-pbr/agents/debugger.agent.md +0 -182
- 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 -208
- 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 -156
- package/plugins/copilot-pbr/plugin.json +0 -30
- package/plugins/copilot-pbr/references/agent-anti-patterns.md +0 -25
- package/plugins/copilot-pbr/references/agent-contracts.md +0 -297
- package/plugins/copilot-pbr/references/agent-interactions.md +0 -135
- package/plugins/copilot-pbr/references/agent-teams.md +0 -55
- package/plugins/copilot-pbr/references/checkpoints.md +0 -158
- package/plugins/copilot-pbr/references/common-bug-patterns.md +0 -14
- package/plugins/copilot-pbr/references/config-reference.md +0 -442
- package/plugins/copilot-pbr/references/continuation-format.md +0 -213
- package/plugins/copilot-pbr/references/deviation-rules.md +0 -113
- package/plugins/copilot-pbr/references/git-integration.md +0 -227
- package/plugins/copilot-pbr/references/integration-patterns.md +0 -118
- package/plugins/copilot-pbr/references/model-profiles.md +0 -100
- package/plugins/copilot-pbr/references/model-selection.md +0 -32
- package/plugins/copilot-pbr/references/pbr-rules.md +0 -195
- package/plugins/copilot-pbr/references/pbr-tools-cli.md +0 -285
- package/plugins/copilot-pbr/references/plan-authoring.md +0 -182
- package/plugins/copilot-pbr/references/plan-format.md +0 -288
- package/plugins/copilot-pbr/references/planning-config.md +0 -214
- package/plugins/copilot-pbr/references/questioning.md +0 -215
- package/plugins/copilot-pbr/references/reading-verification.md +0 -128
- package/plugins/copilot-pbr/references/stub-patterns.md +0 -161
- package/plugins/copilot-pbr/references/subagent-coordination.md +0 -120
- package/plugins/copilot-pbr/references/ui-formatting.md +0 -444
- package/plugins/copilot-pbr/references/verification-patterns.md +0 -199
- package/plugins/copilot-pbr/references/wave-execution.md +0 -96
- package/plugins/copilot-pbr/rules/pbr-workflow.mdc +0 -48
- package/plugins/copilot-pbr/setup.ps1 +0 -93
- package/plugins/copilot-pbr/setup.sh +0 -93
- package/plugins/copilot-pbr/skills/audit/SKILL.md +0 -330
- package/plugins/copilot-pbr/skills/begin/SKILL.md +0 -589
- package/plugins/copilot-pbr/skills/begin/templates/PROJECT.md.tmpl +0 -34
- package/plugins/copilot-pbr/skills/begin/templates/REQUIREMENTS.md.tmpl +0 -19
- package/plugins/copilot-pbr/skills/begin/templates/STATE.md.tmpl +0 -50
- package/plugins/copilot-pbr/skills/begin/templates/config.json.tmpl +0 -64
- package/plugins/copilot-pbr/skills/begin/templates/researcher-prompt.md.tmpl +0 -20
- package/plugins/copilot-pbr/skills/begin/templates/roadmap-prompt.md.tmpl +0 -31
- package/plugins/copilot-pbr/skills/begin/templates/synthesis-prompt.md.tmpl +0 -17
- package/plugins/copilot-pbr/skills/build/SKILL.md +0 -960
- package/plugins/copilot-pbr/skills/config/SKILL.md +0 -250
- package/plugins/copilot-pbr/skills/continue/SKILL.md +0 -159
- package/plugins/copilot-pbr/skills/dashboard/SKILL.md +0 -43
- package/plugins/copilot-pbr/skills/debug/SKILL.md +0 -508
- package/plugins/copilot-pbr/skills/debug/templates/continuation-prompt.md.tmpl +0 -17
- package/plugins/copilot-pbr/skills/debug/templates/initial-investigation-prompt.md.tmpl +0 -28
- package/plugins/copilot-pbr/skills/discuss/SKILL.md +0 -353
- package/plugins/copilot-pbr/skills/discuss/templates/CONTEXT.md.tmpl +0 -62
- package/plugins/copilot-pbr/skills/discuss/templates/decision-categories.md +0 -10
- package/plugins/copilot-pbr/skills/do/SKILL.md +0 -66
- package/plugins/copilot-pbr/skills/explore/SKILL.md +0 -373
- package/plugins/copilot-pbr/skills/health/SKILL.md +0 -283
- package/plugins/copilot-pbr/skills/health/templates/check-pattern.md.tmpl +0 -31
- package/plugins/copilot-pbr/skills/health/templates/output-format.md.tmpl +0 -64
- package/plugins/copilot-pbr/skills/help/SKILL.md +0 -170
- 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 -123
- package/plugins/cursor-pbr/agents/audit.md +0 -178
- package/plugins/cursor-pbr/agents/codebase-mapper.md +0 -150
- package/plugins/cursor-pbr/agents/debugger.md +0 -181
- 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 -207
- 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 -213
- package/plugins/cursor-pbr/references/agent-anti-patterns.md +0 -25
- package/plugins/cursor-pbr/references/agent-contracts.md +0 -297
- package/plugins/cursor-pbr/references/agent-interactions.md +0 -135
- package/plugins/cursor-pbr/references/agent-teams.md +0 -55
- package/plugins/cursor-pbr/references/checkpoints.md +0 -158
- package/plugins/cursor-pbr/references/common-bug-patterns.md +0 -14
- package/plugins/cursor-pbr/references/config-reference.md +0 -442
- package/plugins/cursor-pbr/references/continuation-format.md +0 -213
- package/plugins/cursor-pbr/references/deviation-rules.md +0 -113
- package/plugins/cursor-pbr/references/git-integration.md +0 -227
- package/plugins/cursor-pbr/references/integration-patterns.md +0 -118
- package/plugins/cursor-pbr/references/model-profiles.md +0 -100
- package/plugins/cursor-pbr/references/model-selection.md +0 -32
- package/plugins/cursor-pbr/references/pbr-rules.md +0 -195
- package/plugins/cursor-pbr/references/pbr-tools-cli.md +0 -285
- package/plugins/cursor-pbr/references/plan-authoring.md +0 -182
- package/plugins/cursor-pbr/references/plan-format.md +0 -288
- package/plugins/cursor-pbr/references/planning-config.md +0 -214
- package/plugins/cursor-pbr/references/questioning.md +0 -215
- package/plugins/cursor-pbr/references/reading-verification.md +0 -128
- package/plugins/cursor-pbr/references/stub-patterns.md +0 -161
- package/plugins/cursor-pbr/references/subagent-coordination.md +0 -120
- package/plugins/cursor-pbr/references/ui-formatting.md +0 -444
- package/plugins/cursor-pbr/references/verification-patterns.md +0 -199
- package/plugins/cursor-pbr/references/wave-execution.md +0 -96
- package/plugins/cursor-pbr/rules/pbr-workflow.mdc +0 -48
- package/plugins/cursor-pbr/setup.ps1 +0 -78
- package/plugins/cursor-pbr/setup.sh +0 -83
- package/plugins/cursor-pbr/skills/audit/SKILL.md +0 -331
- package/plugins/cursor-pbr/skills/begin/SKILL.md +0 -589
- package/plugins/cursor-pbr/skills/begin/templates/PROJECT.md.tmpl +0 -34
- package/plugins/cursor-pbr/skills/begin/templates/REQUIREMENTS.md.tmpl +0 -19
- package/plugins/cursor-pbr/skills/begin/templates/STATE.md.tmpl +0 -50
- package/plugins/cursor-pbr/skills/begin/templates/config.json.tmpl +0 -64
- package/plugins/cursor-pbr/skills/begin/templates/researcher-prompt.md.tmpl +0 -20
- package/plugins/cursor-pbr/skills/begin/templates/roadmap-prompt.md.tmpl +0 -31
- package/plugins/cursor-pbr/skills/begin/templates/synthesis-prompt.md.tmpl +0 -17
- package/plugins/cursor-pbr/skills/build/SKILL.md +0 -961
- package/plugins/cursor-pbr/skills/config/SKILL.md +0 -252
- package/plugins/cursor-pbr/skills/continue/SKILL.md +0 -159
- package/plugins/cursor-pbr/skills/dashboard/SKILL.md +0 -44
- package/plugins/cursor-pbr/skills/debug/SKILL.md +0 -512
- package/plugins/cursor-pbr/skills/debug/templates/continuation-prompt.md.tmpl +0 -17
- package/plugins/cursor-pbr/skills/debug/templates/initial-investigation-prompt.md.tmpl +0 -28
- package/plugins/cursor-pbr/skills/discuss/SKILL.md +0 -354
- package/plugins/cursor-pbr/skills/discuss/templates/CONTEXT.md.tmpl +0 -62
- package/plugins/cursor-pbr/skills/discuss/templates/decision-categories.md +0 -10
- package/plugins/cursor-pbr/skills/do/SKILL.md +0 -67
- package/plugins/cursor-pbr/skills/explore/SKILL.md +0 -376
- package/plugins/cursor-pbr/skills/health/SKILL.md +0 -283
- package/plugins/cursor-pbr/skills/health/templates/check-pattern.md.tmpl +0 -31
- package/plugins/cursor-pbr/skills/health/templates/output-format.md.tmpl +0 -64
- package/plugins/cursor-pbr/skills/help/SKILL.md +0 -170
- 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/pbr-rules.md +0 -194
- package/plugins/pbr/references/pbr-tools-cli.md +0 -285
- package/plugins/pbr/references/planning-config.md +0 -213
- package/plugins/pbr/references/subagent-coordination.md +0 -119
- package/plugins/pbr/references/ui-formatting.md +0 -444
- package/plugins/pbr/scripts/validate-plugin-structure.js +0 -183
- package/plugins/pbr/skills/milestone/templates/audit-report.md.tmpl +0 -48
- package/plugins/pbr/skills/shared/progress-display.md +0 -53
- package/plugins/pbr/skills/shared/state-loading.md +0 -62
- package/plugins/pbr/templates/RESEARCH-SUMMARY.md.tmpl +0 -97
- package/plugins/pbr/templates/research/ARCHITECTURE.md.tmpl +0 -124
- package/plugins/pbr/templates/research/STACK.md.tmpl +0 -71
- package/plugins/pbr/templates/research/SUMMARY.md.tmpl +0 -112
- package/plugins/pbr/templates/research-outputs/phase-research.md.tmpl +0 -81
- package/plugins/pbr/templates/research-outputs/project-research.md.tmpl +0 -99
- package/plugins/pbr/templates/research-outputs/synthesis.md.tmpl +0 -36
- /package/plugins/pbr/references/{agent-anti-patterns.md → archive/agent-anti-patterns.md} +0 -0
|
@@ -0,0 +1,2098 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// Shared JSONL Helpers
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Return the logs directory for a given .planning directory.
|
|
12
|
+
* @param {string} planningDir - Path to .planning/
|
|
13
|
+
* @returns {string}
|
|
14
|
+
*/
|
|
15
|
+
function getLogsDir(planningDir) {
|
|
16
|
+
return path.join(planningDir, 'logs');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Read all JSONL files matching `{prefix}-*.jsonl` in a directory.
|
|
21
|
+
* Parses each line as JSON, skipping malformed lines.
|
|
22
|
+
* Returns array sorted by timestamp (ts field).
|
|
23
|
+
*
|
|
24
|
+
* @param {string} dir - Directory to scan
|
|
25
|
+
* @param {string} prefix - File prefix (e.g., 'events', 'hooks')
|
|
26
|
+
* @returns {Array<Object>} Parsed entries sorted by timestamp
|
|
27
|
+
*/
|
|
28
|
+
function readJsonlFiles(dir, prefix) {
|
|
29
|
+
if (!fs.existsSync(dir)) return [];
|
|
30
|
+
|
|
31
|
+
const entries = [];
|
|
32
|
+
const pattern = new RegExp(`^${prefix}-.*\\.jsonl$`);
|
|
33
|
+
|
|
34
|
+
let files;
|
|
35
|
+
try {
|
|
36
|
+
files = fs.readdirSync(dir).filter(f => pattern.test(f)).sort();
|
|
37
|
+
} catch (_e) {
|
|
38
|
+
return [];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
for (const file of files) {
|
|
42
|
+
try {
|
|
43
|
+
const content = fs.readFileSync(path.join(dir, file), 'utf8');
|
|
44
|
+
const lines = content.split('\n').filter(l => l.trim());
|
|
45
|
+
for (const line of lines) {
|
|
46
|
+
try {
|
|
47
|
+
entries.push(JSON.parse(line));
|
|
48
|
+
} catch (_e) {
|
|
49
|
+
// Skip malformed JSON lines
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
} catch (_e) {
|
|
53
|
+
// Skip unreadable files
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Sort by timestamp
|
|
58
|
+
entries.sort((a, b) => {
|
|
59
|
+
const ta = a.ts || a.timestamp || '';
|
|
60
|
+
const tb = b.ts || b.timestamp || '';
|
|
61
|
+
return ta < tb ? -1 : ta > tb ? 1 : 0;
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
return entries;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Read session event logs from .planning/logs/events-*.jsonl.
|
|
69
|
+
* @param {string} planningDir - Path to .planning/
|
|
70
|
+
* @returns {Array<Object>}
|
|
71
|
+
*/
|
|
72
|
+
function readSessionEvents(planningDir) {
|
|
73
|
+
return readJsonlFiles(getLogsDir(planningDir), 'events');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Read hook logs from .planning/logs/hooks-*.jsonl.
|
|
78
|
+
* @param {string} planningDir - Path to .planning/
|
|
79
|
+
* @returns {Array<Object>}
|
|
80
|
+
*/
|
|
81
|
+
function readHookLogs(planningDir) {
|
|
82
|
+
return readJsonlFiles(getLogsDir(planningDir), 'hooks');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
// BC-01: Skill Sequence Compliance
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Known PBR skill names relevant to workflow ordering.
|
|
91
|
+
*/
|
|
92
|
+
const WORKFLOW_SKILLS = ['plan', 'build', 'verify', 'review', 'begin', 'autonomous', 'continue'];
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Valid skill ordering within a phase: plan < build < verify.
|
|
96
|
+
* Lower index = must come first.
|
|
97
|
+
*/
|
|
98
|
+
const SKILL_ORDER = { plan: 0, build: 1, verify: 2, review: 3 };
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Extract phase number from an event entry.
|
|
102
|
+
* Looks in various fields for phase references.
|
|
103
|
+
* @param {Object} entry - JSONL entry
|
|
104
|
+
* @returns {string|null} Phase number or null
|
|
105
|
+
*/
|
|
106
|
+
function extractPhaseFromEntry(entry) {
|
|
107
|
+
// Check direct phase field
|
|
108
|
+
if (entry.phase) return String(entry.phase);
|
|
109
|
+
|
|
110
|
+
// Check details object
|
|
111
|
+
if (entry.details && entry.details.phase) return String(entry.details.phase);
|
|
112
|
+
|
|
113
|
+
// Check event string for phase references like "phase 3" or "03-"
|
|
114
|
+
const searchStr = JSON.stringify(entry);
|
|
115
|
+
const phaseMatch = searchStr.match(/phase[- _]?(\d+)/i) || searchStr.match(/(\d{2})-\d{2}/);
|
|
116
|
+
if (phaseMatch) return String(parseInt(phaseMatch[1], 10));
|
|
117
|
+
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Extract skill name from an event entry.
|
|
123
|
+
* @param {Object} entry - JSONL entry
|
|
124
|
+
* @returns {string|null}
|
|
125
|
+
*/
|
|
126
|
+
function extractSkillFromEntry(entry) {
|
|
127
|
+
// Direct event field matching skill names
|
|
128
|
+
if (entry.event && WORKFLOW_SKILLS.includes(entry.event)) return entry.event;
|
|
129
|
+
|
|
130
|
+
// Category-based skill detection
|
|
131
|
+
if (entry.cat === 'skill' && entry.event) return entry.event;
|
|
132
|
+
|
|
133
|
+
// Check for skill in hook entries (check-skill-workflow)
|
|
134
|
+
if (entry.hook === 'check-skill-workflow' && entry.details) {
|
|
135
|
+
const skill = entry.details.skill || entry.details.active_skill;
|
|
136
|
+
if (skill) return skill;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Check for pbr: prefix in event names
|
|
140
|
+
if (entry.event && entry.event.startsWith('pbr:')) {
|
|
141
|
+
const name = entry.event.replace('pbr:', '').split('-')[0];
|
|
142
|
+
if (WORKFLOW_SKILLS.includes(name)) return name;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* BC-01: Check that skills are invoked in valid order within each phase.
|
|
150
|
+
* plan must precede build, build must precede verify.
|
|
151
|
+
*
|
|
152
|
+
* @param {string} planningDir - Path to .planning/
|
|
153
|
+
* @param {Object} [_config] - Config object (unused, for API consistency)
|
|
154
|
+
* @returns {{ status: string, evidence: Array<string>, message: string }}
|
|
155
|
+
*/
|
|
156
|
+
function checkSkillSequenceCompliance(planningDir, _config) {
|
|
157
|
+
const events = readSessionEvents(planningDir);
|
|
158
|
+
const hookLogs = readHookLogs(planningDir);
|
|
159
|
+
const allEntries = [...events, ...hookLogs].sort((a, b) => {
|
|
160
|
+
const ta = a.ts || a.timestamp || '';
|
|
161
|
+
const tb = b.ts || b.timestamp || '';
|
|
162
|
+
return ta < tb ? -1 : ta > tb ? 1 : 0;
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
if (allEntries.length === 0) {
|
|
166
|
+
return {
|
|
167
|
+
status: 'pass',
|
|
168
|
+
evidence: [],
|
|
169
|
+
message: 'BC-01: No session data available — cannot assess skill sequence'
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Build per-phase skill invocation timeline
|
|
174
|
+
// Map<phase, Array<{ skill, ts }>>
|
|
175
|
+
const phaseSkills = new Map();
|
|
176
|
+
|
|
177
|
+
for (const entry of allEntries) {
|
|
178
|
+
const skill = extractSkillFromEntry(entry);
|
|
179
|
+
const phase = extractPhaseFromEntry(entry);
|
|
180
|
+
if (!skill || !phase) continue;
|
|
181
|
+
if (!(skill in SKILL_ORDER)) continue;
|
|
182
|
+
|
|
183
|
+
if (!phaseSkills.has(phase)) phaseSkills.set(phase, []);
|
|
184
|
+
phaseSkills.get(phase).push({
|
|
185
|
+
skill,
|
|
186
|
+
ts: entry.ts || entry.timestamp || ''
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const evidence = [];
|
|
191
|
+
|
|
192
|
+
for (const [phase, invocations] of phaseSkills) {
|
|
193
|
+
// Check ordering: for each pair, later skills should not appear before earlier ones
|
|
194
|
+
for (let i = 0; i < invocations.length; i++) {
|
|
195
|
+
for (let j = i + 1; j < invocations.length; j++) {
|
|
196
|
+
const earlier = invocations[i];
|
|
197
|
+
const later = invocations[j];
|
|
198
|
+
if (SKILL_ORDER[later.skill] < SKILL_ORDER[earlier.skill]) {
|
|
199
|
+
const time = later.ts ? ` at ${later.ts}` : '';
|
|
200
|
+
evidence.push(
|
|
201
|
+
`Phase ${phase}: ${later.skill} invoked before ${earlier.skill}` +
|
|
202
|
+
` (${later.skill}${time}, no prior ${earlier.skill} event)`
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (evidence.length > 0) {
|
|
210
|
+
return {
|
|
211
|
+
status: 'fail',
|
|
212
|
+
evidence,
|
|
213
|
+
message: `BC-01: Found ${evidence.length} skill ordering violation(s)`
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return {
|
|
218
|
+
status: 'pass',
|
|
219
|
+
evidence: [],
|
|
220
|
+
message: 'BC-01: All skill invocations follow valid ordering (plan < build < verify)'
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// ---------------------------------------------------------------------------
|
|
225
|
+
// BC-02: State Machine Transition Validation
|
|
226
|
+
// ---------------------------------------------------------------------------
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Valid state transitions in PBR workflow.
|
|
230
|
+
* Key = from status, Value = set of valid "to" statuses.
|
|
231
|
+
*/
|
|
232
|
+
const VALID_TRANSITIONS = {
|
|
233
|
+
not_started: new Set(['discussed', 'ready_to_plan', 'planning']),
|
|
234
|
+
discussed: new Set(['ready_to_plan', 'planning']),
|
|
235
|
+
ready_to_plan: new Set(['planning']),
|
|
236
|
+
planning: new Set(['planned', 'building']), // building = inline execution skips planned
|
|
237
|
+
planned: new Set(['ready_to_execute', 'building']),
|
|
238
|
+
ready_to_execute: new Set(['building']),
|
|
239
|
+
building: new Set(['built', 'partial']),
|
|
240
|
+
built: new Set(['verified', 'needs_fixes']),
|
|
241
|
+
partial: new Set(['building', 'needs_fixes']),
|
|
242
|
+
verified: new Set(['complete']),
|
|
243
|
+
needs_fixes: new Set(['building', 'planning']),
|
|
244
|
+
complete: new Set([]), // terminal
|
|
245
|
+
skipped: new Set([]), // terminal
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Extract status value from a JSONL entry that represents a state write.
|
|
250
|
+
* @param {Object} entry - JSONL entry
|
|
251
|
+
* @returns {string|null} Status value or null
|
|
252
|
+
*/
|
|
253
|
+
function extractStatusFromEntry(entry) {
|
|
254
|
+
// Hook log entries from check-state-sync
|
|
255
|
+
if (entry.hook === 'check-state-sync' && entry.details) {
|
|
256
|
+
return entry.details.status || entry.details.new_status || null;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Event logger entries for state writes
|
|
260
|
+
if (entry.event === 'state-update' || entry.event === 'status-change') {
|
|
261
|
+
return (entry.details && entry.details.status) || entry.status || null;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Generic: look for status in tool_input targeting STATE.md
|
|
265
|
+
if (entry.tool_input && typeof entry.tool_input === 'string') {
|
|
266
|
+
if (entry.tool_input.includes('STATE.md')) {
|
|
267
|
+
const statusMatch = entry.tool_input.match(/status:\s*["']?(\w+)/);
|
|
268
|
+
if (statusMatch) return statusMatch[1];
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Check details.content for STATE.md writes
|
|
273
|
+
if (entry.details && entry.details.content && typeof entry.details.content === 'string') {
|
|
274
|
+
if (entry.details.file && entry.details.file.includes('STATE.md')) {
|
|
275
|
+
const statusMatch = entry.details.content.match(/status:\s*["']?(\w+)/);
|
|
276
|
+
if (statusMatch) return statusMatch[1];
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* BC-02: Check that state transitions follow the valid state machine.
|
|
285
|
+
* Detects invalid transitions like planning->verified (skipping building).
|
|
286
|
+
*
|
|
287
|
+
* @param {string} planningDir - Path to .planning/
|
|
288
|
+
* @param {Object} [_config] - Config object (unused, for API consistency)
|
|
289
|
+
* @returns {{ status: string, evidence: Array<string>, message: string }}
|
|
290
|
+
*/
|
|
291
|
+
function checkStateMachineTransitions(planningDir, _config) {
|
|
292
|
+
const events = readSessionEvents(planningDir);
|
|
293
|
+
const hookLogs = readHookLogs(planningDir);
|
|
294
|
+
const allEntries = [...events, ...hookLogs].sort((a, b) => {
|
|
295
|
+
const ta = a.ts || a.timestamp || '';
|
|
296
|
+
const tb = b.ts || b.timestamp || '';
|
|
297
|
+
return ta < tb ? -1 : ta > tb ? 1 : 0;
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
if (allEntries.length === 0) {
|
|
301
|
+
return {
|
|
302
|
+
status: 'pass',
|
|
303
|
+
evidence: [],
|
|
304
|
+
message: 'BC-02: No session data available — cannot assess state transitions'
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Collect status transitions per phase
|
|
309
|
+
// Map<phase, Array<{ status, ts }>>
|
|
310
|
+
const phaseTransitions = new Map();
|
|
311
|
+
|
|
312
|
+
for (const entry of allEntries) {
|
|
313
|
+
const status = extractStatusFromEntry(entry);
|
|
314
|
+
const phase = extractPhaseFromEntry(entry);
|
|
315
|
+
if (!status || !phase) continue;
|
|
316
|
+
|
|
317
|
+
if (!phaseTransitions.has(phase)) phaseTransitions.set(phase, []);
|
|
318
|
+
const transitions = phaseTransitions.get(phase);
|
|
319
|
+
|
|
320
|
+
// Deduplicate consecutive identical statuses
|
|
321
|
+
if (transitions.length > 0 && transitions[transitions.length - 1].status === status) continue;
|
|
322
|
+
|
|
323
|
+
transitions.push({
|
|
324
|
+
status,
|
|
325
|
+
ts: entry.ts || entry.timestamp || ''
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const evidence = [];
|
|
330
|
+
|
|
331
|
+
for (const [phase, transitions] of phaseTransitions) {
|
|
332
|
+
for (let i = 0; i < transitions.length - 1; i++) {
|
|
333
|
+
const from = transitions[i].status;
|
|
334
|
+
const to = transitions[i + 1].status;
|
|
335
|
+
const validTargets = VALID_TRANSITIONS[from];
|
|
336
|
+
|
|
337
|
+
if (!validTargets || !validTargets.has(to)) {
|
|
338
|
+
const time = transitions[i + 1].ts ? ` at ${transitions[i + 1].ts}` : '';
|
|
339
|
+
evidence.push(
|
|
340
|
+
`Phase ${phase}: ${from}->${to} (invalid transition${time})`
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (evidence.length > 0) {
|
|
347
|
+
return {
|
|
348
|
+
status: 'fail',
|
|
349
|
+
evidence,
|
|
350
|
+
message: `BC-02: Found ${evidence.length} invalid state transition(s)`
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
return {
|
|
355
|
+
status: 'pass',
|
|
356
|
+
evidence: [],
|
|
357
|
+
message: 'BC-02: All state transitions follow valid state machine'
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// ---------------------------------------------------------------------------
|
|
362
|
+
// BC-03: Pre-Condition Verification
|
|
363
|
+
// ---------------------------------------------------------------------------
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Check if a file write event targets a specific pattern.
|
|
367
|
+
* @param {Object} entry - JSONL entry
|
|
368
|
+
* @param {RegExp} pattern - Pattern to match against file paths
|
|
369
|
+
* @returns {boolean}
|
|
370
|
+
*/
|
|
371
|
+
function entryTargetsFile(entry, pattern) {
|
|
372
|
+
// Check tool_input for file path
|
|
373
|
+
if (entry.tool_input) {
|
|
374
|
+
const input = typeof entry.tool_input === 'string' ? entry.tool_input : JSON.stringify(entry.tool_input);
|
|
375
|
+
if (pattern.test(input)) return true;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Check details for file path
|
|
379
|
+
if (entry.details) {
|
|
380
|
+
const details = typeof entry.details === 'string' ? entry.details : JSON.stringify(entry.details);
|
|
381
|
+
if (pattern.test(details)) return true;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Check file/path fields directly
|
|
385
|
+
if (entry.file && pattern.test(entry.file)) return true;
|
|
386
|
+
if (entry.path && pattern.test(entry.path)) return true;
|
|
387
|
+
|
|
388
|
+
return false;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Check if an entry represents a build-start event.
|
|
393
|
+
* @param {Object} entry - JSONL entry
|
|
394
|
+
* @returns {boolean}
|
|
395
|
+
*/
|
|
396
|
+
function isBuildStart(entry) {
|
|
397
|
+
// Skill invocation of build
|
|
398
|
+
if (entry.event === 'build' || entry.event === 'pbr:build') return true;
|
|
399
|
+
if (entry.cat === 'skill' && entry.event === 'build') return true;
|
|
400
|
+
|
|
401
|
+
// Task spawn with executor agent
|
|
402
|
+
if (entry.event === 'task-spawn' && entry.details) {
|
|
403
|
+
if (entry.details.agent === 'executor' || entry.details.subagent_type === 'pbr:executor') return true;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Hook log from validate-task with buildExecutorGate
|
|
407
|
+
if (entry.hook === 'validate-task' && entry.details) {
|
|
408
|
+
if (entry.details.gate === 'buildExecutorGate' || entry.details.check === 'buildExecutorGate') return true;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
return false;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Check if an entry represents a verify-start event.
|
|
416
|
+
* @param {Object} entry - JSONL entry
|
|
417
|
+
* @returns {boolean}
|
|
418
|
+
*/
|
|
419
|
+
function isVerifyStart(entry) {
|
|
420
|
+
if (entry.event === 'verify' || entry.event === 'pbr:verify') return true;
|
|
421
|
+
if (entry.cat === 'skill' && entry.event === 'verify') return true;
|
|
422
|
+
|
|
423
|
+
// Task spawn with verifier agent
|
|
424
|
+
if (entry.event === 'task-spawn' && entry.details) {
|
|
425
|
+
if (entry.details.agent === 'verifier' || entry.details.subagent_type === 'pbr:verifier') return true;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
return false;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* BC-03: Check that pre-conditions are met before skill execution.
|
|
433
|
+
* Build requires PLAN-*.md to exist, verify requires SUMMARY*.md to exist.
|
|
434
|
+
*
|
|
435
|
+
* @param {string} planningDir - Path to .planning/
|
|
436
|
+
* @param {Object} [_config] - Config object (unused, for API consistency)
|
|
437
|
+
* @returns {{ status: string, evidence: Array<string>, message: string }}
|
|
438
|
+
*/
|
|
439
|
+
function checkPreConditionVerification(planningDir, _config) {
|
|
440
|
+
const events = readSessionEvents(planningDir);
|
|
441
|
+
const hookLogs = readHookLogs(planningDir);
|
|
442
|
+
const allEntries = [...events, ...hookLogs].sort((a, b) => {
|
|
443
|
+
const ta = a.ts || a.timestamp || '';
|
|
444
|
+
const tb = b.ts || b.timestamp || '';
|
|
445
|
+
return ta < tb ? -1 : ta > tb ? 1 : 0;
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
if (allEntries.length === 0) {
|
|
449
|
+
return {
|
|
450
|
+
status: 'pass',
|
|
451
|
+
evidence: [],
|
|
452
|
+
message: 'BC-03: No session data available — cannot assess pre-conditions'
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const evidence = [];
|
|
457
|
+
const planPattern = /PLAN-?\d*\.md/i;
|
|
458
|
+
const summaryPattern = /SUMMARY/i;
|
|
459
|
+
|
|
460
|
+
// Track which file patterns have been written so far
|
|
461
|
+
let planWriteSeen = false;
|
|
462
|
+
let summaryWriteSeen = false;
|
|
463
|
+
|
|
464
|
+
// Also check hook logs for validate-task entries that confirm pre-conditions
|
|
465
|
+
const gateChecks = hookLogs.filter(e =>
|
|
466
|
+
e.hook === 'validate-task' && e.details &&
|
|
467
|
+
(e.details.gate === 'buildExecutorGate' || e.details.check === 'buildExecutorGate')
|
|
468
|
+
);
|
|
469
|
+
const gateBlocks = gateChecks.filter(e =>
|
|
470
|
+
e.details && (e.details.decision === 'block' || e.details.result === 'block')
|
|
471
|
+
);
|
|
472
|
+
|
|
473
|
+
for (const entry of allEntries) {
|
|
474
|
+
// Track file writes
|
|
475
|
+
if (entryTargetsFile(entry, planPattern)) {
|
|
476
|
+
planWriteSeen = true;
|
|
477
|
+
}
|
|
478
|
+
if (entryTargetsFile(entry, summaryPattern)) {
|
|
479
|
+
summaryWriteSeen = true;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// Check build pre-conditions
|
|
483
|
+
if (isBuildStart(entry)) {
|
|
484
|
+
const phase = extractPhaseFromEntry(entry);
|
|
485
|
+
if (!planWriteSeen) {
|
|
486
|
+
// Check if validate-task gate caught this
|
|
487
|
+
const wasCaught = gateBlocks.some(g => {
|
|
488
|
+
const gPhase = extractPhaseFromEntry(g);
|
|
489
|
+
return gPhase === phase;
|
|
490
|
+
});
|
|
491
|
+
if (wasCaught) continue; // Gate caught it, not a violation
|
|
492
|
+
|
|
493
|
+
const time = entry.ts || entry.timestamp || '';
|
|
494
|
+
evidence.push(
|
|
495
|
+
`${phase ? `Phase ${phase} ` : ''}build started but no PLAN-*.md write event found in prior session activity` +
|
|
496
|
+
(time ? ` (build at ${time})` : '')
|
|
497
|
+
);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// Check verify pre-conditions
|
|
502
|
+
if (isVerifyStart(entry)) {
|
|
503
|
+
const phase = extractPhaseFromEntry(entry);
|
|
504
|
+
if (!summaryWriteSeen) {
|
|
505
|
+
const time = entry.ts || entry.timestamp || '';
|
|
506
|
+
evidence.push(
|
|
507
|
+
`${phase ? `Phase ${phase} ` : ''}verify started but no SUMMARY.md write event found in prior session activity` +
|
|
508
|
+
(time ? ` (verify at ${time})` : '')
|
|
509
|
+
);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
if (evidence.length > 0) {
|
|
515
|
+
// Use 'warn' for ambiguous cases (session data might be incomplete)
|
|
516
|
+
const hasAmbiguity = evidence.some(e => e.includes('no') && e.includes('write event'));
|
|
517
|
+
return {
|
|
518
|
+
status: hasAmbiguity ? 'warn' : 'fail',
|
|
519
|
+
evidence,
|
|
520
|
+
message: `BC-03: Found ${evidence.length} pre-condition concern(s) — data may be incomplete`
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
return {
|
|
525
|
+
status: 'pass',
|
|
526
|
+
evidence: [],
|
|
527
|
+
message: 'BC-03: All pre-conditions verified (PLAN before build, SUMMARY before verify)'
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// ---------------------------------------------------------------------------
|
|
532
|
+
// BC-04: Post-Condition Verification
|
|
533
|
+
// ---------------------------------------------------------------------------
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* Check if an entry represents a build-complete event.
|
|
537
|
+
* @param {Object} entry - JSONL entry
|
|
538
|
+
* @returns {boolean}
|
|
539
|
+
*/
|
|
540
|
+
function isBuildComplete(entry) {
|
|
541
|
+
// Task completion for executor agent
|
|
542
|
+
if (entry.event === 'task-complete' && entry.details) {
|
|
543
|
+
if (entry.details.agent === 'executor' || entry.details.subagent_type === 'pbr:executor') return true;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// Subagent stop for executor
|
|
547
|
+
if (entry.event === 'subagent-stop' && entry.details) {
|
|
548
|
+
if (entry.details.agent === 'executor' || entry.details.subagent_type === 'pbr:executor') return true;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// Hook log from check-subagent-output indicating build completion
|
|
552
|
+
if (entry.hook === 'check-subagent-output' && entry.details) {
|
|
553
|
+
if (entry.details.skill === 'build') return true;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
return false;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* Check if an entry represents a verify-complete event.
|
|
561
|
+
* @param {Object} entry - JSONL entry
|
|
562
|
+
* @returns {boolean}
|
|
563
|
+
*/
|
|
564
|
+
function isVerifyComplete(entry) {
|
|
565
|
+
if (entry.event === 'task-complete' && entry.details) {
|
|
566
|
+
if (entry.details.agent === 'verifier' || entry.details.subagent_type === 'pbr:verifier') return true;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
if (entry.event === 'subagent-stop' && entry.details) {
|
|
570
|
+
if (entry.details.agent === 'verifier' || entry.details.subagent_type === 'pbr:verifier') return true;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
if (entry.hook === 'check-subagent-output' && entry.details) {
|
|
574
|
+
if (entry.details.skill === 'verify') return true;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
return false;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
* BC-04: Check that post-conditions are met after skill execution.
|
|
582
|
+
* Build must produce SUMMARY.md, verify must produce VERIFICATION.md.
|
|
583
|
+
*
|
|
584
|
+
* @param {string} planningDir - Path to .planning/
|
|
585
|
+
* @param {Object} [_config] - Config object (unused, for API consistency)
|
|
586
|
+
* @returns {{ status: string, evidence: Array<string>, message: string }}
|
|
587
|
+
*/
|
|
588
|
+
function checkPostConditionVerification(planningDir, _config) {
|
|
589
|
+
const events = readSessionEvents(planningDir);
|
|
590
|
+
const hookLogs = readHookLogs(planningDir);
|
|
591
|
+
const allEntries = [...events, ...hookLogs].sort((a, b) => {
|
|
592
|
+
const ta = a.ts || a.timestamp || '';
|
|
593
|
+
const tb = b.ts || b.timestamp || '';
|
|
594
|
+
return ta < tb ? -1 : ta > tb ? 1 : 0;
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
if (allEntries.length === 0) {
|
|
598
|
+
return {
|
|
599
|
+
status: 'pass',
|
|
600
|
+
evidence: [],
|
|
601
|
+
message: 'BC-04: No session data available — cannot assess post-conditions'
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
const evidence = [];
|
|
606
|
+
const summaryPattern = /SUMMARY.*\.md/i;
|
|
607
|
+
const verificationPattern = /VERIFICATION.*\.md/i;
|
|
608
|
+
|
|
609
|
+
// Track build-complete and verify-complete events, then look for subsequent artifact writes
|
|
610
|
+
const buildCompletes = [];
|
|
611
|
+
const verifyCompletes = [];
|
|
612
|
+
|
|
613
|
+
for (const entry of allEntries) {
|
|
614
|
+
if (isBuildComplete(entry)) {
|
|
615
|
+
const phase = extractPhaseFromEntry(entry);
|
|
616
|
+
buildCompletes.push({ phase, ts: entry.ts || entry.timestamp || '' });
|
|
617
|
+
}
|
|
618
|
+
if (isVerifyComplete(entry)) {
|
|
619
|
+
const phase = extractPhaseFromEntry(entry);
|
|
620
|
+
verifyCompletes.push({ phase, ts: entry.ts || entry.timestamp || '' });
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// For each build-complete, check if SUMMARY.md was written in the session
|
|
625
|
+
for (const bc of buildCompletes) {
|
|
626
|
+
const summaryWritten = allEntries.some(e => entryTargetsFile(e, summaryPattern));
|
|
627
|
+
if (!summaryWritten) {
|
|
628
|
+
const phaseLabel = bc.phase ? `Phase ${bc.phase}` : 'Unknown phase';
|
|
629
|
+
evidence.push(`${phaseLabel}: build completed but no SUMMARY.md written in session`);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// For each verify-complete, check if VERIFICATION.md was written in the session
|
|
634
|
+
for (const vc of verifyCompletes) {
|
|
635
|
+
const verificationWritten = allEntries.some(e => entryTargetsFile(e, verificationPattern));
|
|
636
|
+
if (!verificationWritten) {
|
|
637
|
+
const phaseLabel = vc.phase ? `Phase ${vc.phase}` : 'Unknown phase';
|
|
638
|
+
evidence.push(`${phaseLabel}: verify completed but no VERIFICATION.md written in session`);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
if (evidence.length > 0) {
|
|
643
|
+
return {
|
|
644
|
+
status: 'warn',
|
|
645
|
+
evidence,
|
|
646
|
+
message: `BC-04: Found ${evidence.length} missing post-condition artifact(s)`
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
return {
|
|
651
|
+
status: 'pass',
|
|
652
|
+
evidence: [],
|
|
653
|
+
message: 'BC-04: All post-conditions verified (SUMMARY after build, VERIFICATION after verify)'
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
// ---------------------------------------------------------------------------
|
|
658
|
+
// BC-05: Orchestrator Budget Discipline
|
|
659
|
+
// ---------------------------------------------------------------------------
|
|
660
|
+
|
|
661
|
+
/**
|
|
662
|
+
* Check if an event appears to be from orchestrator context (not inside a Task subagent).
|
|
663
|
+
* Events inside Task() typically have subagent markers or task_id fields.
|
|
664
|
+
* @param {Object} entry - JSONL entry
|
|
665
|
+
* @returns {boolean} true if this looks like an orchestrator-level event
|
|
666
|
+
*/
|
|
667
|
+
function isOrchestratorLevel(entry) {
|
|
668
|
+
// Events with subagent or task markers are inside Task() contexts
|
|
669
|
+
if (entry.task_id) return false;
|
|
670
|
+
if (entry.subagent) return false;
|
|
671
|
+
if (entry.details && entry.details.subagent_type) return false;
|
|
672
|
+
if (entry.details && entry.details.task_id) return false;
|
|
673
|
+
if (entry.cat === 'subagent') return false;
|
|
674
|
+
|
|
675
|
+
// Hook logs from within subagents
|
|
676
|
+
if (entry.hook && entry.details && entry.details.inside_task) return false;
|
|
677
|
+
|
|
678
|
+
return true;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
/**
|
|
682
|
+
* Check if a file path is an executor-level file that the orchestrator should not read.
|
|
683
|
+
* @param {string} filePath - File path to check
|
|
684
|
+
* @returns {boolean}
|
|
685
|
+
*/
|
|
686
|
+
function isExecutorLevelFile(filePath) {
|
|
687
|
+
if (!filePath) return false;
|
|
688
|
+
const normalized = filePath.replace(/\\/g, '/');
|
|
689
|
+
|
|
690
|
+
// Source files that should be delegated to executor
|
|
691
|
+
if (/plugins\/pbr\/scripts\//.test(normalized)) return true;
|
|
692
|
+
if (/\/src\//.test(normalized)) return true;
|
|
693
|
+
|
|
694
|
+
// Plan action details (the executor reads these, not the orchestrator)
|
|
695
|
+
if (/PLAN-?\d+\.md$/i.test(normalized)) return true;
|
|
696
|
+
|
|
697
|
+
return false;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
/**
|
|
701
|
+
* BC-05: Check that the orchestrator stays within its context budget.
|
|
702
|
+
* Flags orchestrator-level reads of executor files and budget overruns.
|
|
703
|
+
*
|
|
704
|
+
* @param {string} planningDir - Path to .planning/
|
|
705
|
+
* @param {Object} [config] - Config object with orchestrator_budget_pct
|
|
706
|
+
* @returns {{ status: string, evidence: Array<string>, message: string }}
|
|
707
|
+
*/
|
|
708
|
+
function checkOrchestratorBudgetDiscipline(planningDir, config) {
|
|
709
|
+
const events = readSessionEvents(planningDir);
|
|
710
|
+
const hookLogs = readHookLogs(planningDir);
|
|
711
|
+
const allEntries = [...events, ...hookLogs].sort((a, b) => {
|
|
712
|
+
const ta = a.ts || a.timestamp || '';
|
|
713
|
+
const tb = b.ts || b.timestamp || '';
|
|
714
|
+
return ta < tb ? -1 : ta > tb ? 1 : 0;
|
|
715
|
+
});
|
|
716
|
+
|
|
717
|
+
if (allEntries.length === 0) {
|
|
718
|
+
return {
|
|
719
|
+
status: 'pass',
|
|
720
|
+
evidence: [],
|
|
721
|
+
message: 'BC-05: No session data available — cannot assess orchestrator budget'
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
const evidence = [];
|
|
726
|
+
const budgetPct = (config && config.orchestrator_budget_pct) || 35;
|
|
727
|
+
|
|
728
|
+
// Find orchestrator-level Read events targeting executor files
|
|
729
|
+
const orchestratorSourceReads = [];
|
|
730
|
+
for (const entry of allEntries) {
|
|
731
|
+
if (!isOrchestratorLevel(entry)) continue;
|
|
732
|
+
|
|
733
|
+
// Look for Read tool events
|
|
734
|
+
const isRead = entry.tool === 'Read' || entry.event === 'read' ||
|
|
735
|
+
(entry.details && entry.details.tool === 'Read');
|
|
736
|
+
if (!isRead) continue;
|
|
737
|
+
|
|
738
|
+
// Extract file path
|
|
739
|
+
const filePath = (entry.tool_input && entry.tool_input.file_path) ||
|
|
740
|
+
(entry.details && entry.details.file_path) ||
|
|
741
|
+
entry.file || entry.path || '';
|
|
742
|
+
|
|
743
|
+
if (isExecutorLevelFile(filePath)) {
|
|
744
|
+
orchestratorSourceReads.push(filePath);
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
if (orchestratorSourceReads.length > 0) {
|
|
749
|
+
evidence.push(
|
|
750
|
+
`Orchestrator read ${orchestratorSourceReads.length} executor-level file(s) directly: ` +
|
|
751
|
+
orchestratorSourceReads.slice(0, 5).map(f => path.basename(f)).join(', ') +
|
|
752
|
+
(orchestratorSourceReads.length > 5 ? ` (+${orchestratorSourceReads.length - 5} more)` : '')
|
|
753
|
+
);
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
// Check context budget data from .context-budget.json
|
|
757
|
+
const budgetPath = path.join(planningDir, '.context-budget.json');
|
|
758
|
+
try {
|
|
759
|
+
if (fs.existsSync(budgetPath)) {
|
|
760
|
+
const budgetData = JSON.parse(fs.readFileSync(budgetPath, 'utf8'));
|
|
761
|
+
const pctUsed = budgetData.pct_used || budgetData.estimated_percent || 0;
|
|
762
|
+
if (pctUsed > budgetPct) {
|
|
763
|
+
evidence.push(
|
|
764
|
+
`Orchestrator context budget exceeded: ${pctUsed}% used (threshold: ${budgetPct}%)`
|
|
765
|
+
);
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
} catch (_e) {
|
|
769
|
+
// Budget file may not exist or be unreadable — not an error
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
if (evidence.length > 0) {
|
|
773
|
+
return {
|
|
774
|
+
status: 'warn',
|
|
775
|
+
evidence,
|
|
776
|
+
message: `BC-05: Orchestrator budget discipline concern(s) found`
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
return {
|
|
781
|
+
status: 'pass',
|
|
782
|
+
evidence: [],
|
|
783
|
+
message: `BC-05: Orchestrator stayed within budget (threshold: ${budgetPct}%)`
|
|
784
|
+
};
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
// ---------------------------------------------------------------------------
|
|
788
|
+
// BC-06: Artifact Creation Order
|
|
789
|
+
// ---------------------------------------------------------------------------
|
|
790
|
+
|
|
791
|
+
/**
|
|
792
|
+
* Artifact types in expected creation order within a phase.
|
|
793
|
+
* Lower index = must be created first.
|
|
794
|
+
*/
|
|
795
|
+
const ARTIFACT_ORDER = {
|
|
796
|
+
'PLAN': 0,
|
|
797
|
+
'SUMMARY': 1,
|
|
798
|
+
'VERIFICATION': 2,
|
|
799
|
+
};
|
|
800
|
+
|
|
801
|
+
/**
|
|
802
|
+
* Extract artifact type and phase directory from a file path.
|
|
803
|
+
* @param {string} filePath - File path from event
|
|
804
|
+
* @returns {{ artifact: string, phaseDir: string }|null}
|
|
805
|
+
*/
|
|
806
|
+
function extractArtifactInfo(filePath) {
|
|
807
|
+
if (!filePath) return null;
|
|
808
|
+
const normalized = filePath.replace(/\\/g, '/');
|
|
809
|
+
|
|
810
|
+
// Match phase directory patterns like phases/03-something/PLAN-01.md
|
|
811
|
+
const phaseMatch = normalized.match(/phases\/(\d{2}-[^/]+)\//);
|
|
812
|
+
if (!phaseMatch) return null;
|
|
813
|
+
|
|
814
|
+
const phaseDir = phaseMatch[1];
|
|
815
|
+
const basename = path.basename(normalized);
|
|
816
|
+
|
|
817
|
+
if (/^PLAN/i.test(basename)) return { artifact: 'PLAN', phaseDir };
|
|
818
|
+
if (/^SUMMARY/i.test(basename)) return { artifact: 'SUMMARY', phaseDir };
|
|
819
|
+
if (/^VERIFICATION/i.test(basename)) return { artifact: 'VERIFICATION', phaseDir };
|
|
820
|
+
|
|
821
|
+
return null;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
/**
|
|
825
|
+
* Check if an entry is a git commit event (Bash tool running git commit).
|
|
826
|
+
* @param {Object} entry - JSONL entry
|
|
827
|
+
* @returns {string|null} Phase directory if commit is phase-related, null otherwise
|
|
828
|
+
*/
|
|
829
|
+
function extractCommitPhase(entry) {
|
|
830
|
+
// Look for Bash tool events containing "git commit"
|
|
831
|
+
const input = entry.tool_input || (entry.details && entry.details.command) || '';
|
|
832
|
+
const inputStr = typeof input === 'string' ? input : JSON.stringify(input);
|
|
833
|
+
|
|
834
|
+
if (!/git\s+commit/i.test(inputStr)) return null;
|
|
835
|
+
|
|
836
|
+
// Try to extract phase from the commit message or context
|
|
837
|
+
const phaseMatch = inputStr.match(/(\d{2})-(\d{2})/);
|
|
838
|
+
if (phaseMatch) {
|
|
839
|
+
// This gives us a phase-plan reference; return just the phase part
|
|
840
|
+
return phaseMatch[1];
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
return null;
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
/**
|
|
847
|
+
* BC-06: Check that artifacts are created in the expected order within each phase.
|
|
848
|
+
* Expected: PLAN write < commit(s) < SUMMARY write < VERIFICATION write.
|
|
849
|
+
*
|
|
850
|
+
* @param {string} planningDir - Path to .planning/
|
|
851
|
+
* @param {Object} [_config] - Config object (unused, for API consistency)
|
|
852
|
+
* @returns {{ status: string, evidence: Array<string>, message: string }}
|
|
853
|
+
*/
|
|
854
|
+
function checkArtifactCreationOrder(planningDir, _config) {
|
|
855
|
+
const events = readSessionEvents(planningDir);
|
|
856
|
+
const hookLogs = readHookLogs(planningDir);
|
|
857
|
+
const allEntries = [...events, ...hookLogs].sort((a, b) => {
|
|
858
|
+
const ta = a.ts || a.timestamp || '';
|
|
859
|
+
const tb = b.ts || b.timestamp || '';
|
|
860
|
+
return ta < tb ? -1 : ta > tb ? 1 : 0;
|
|
861
|
+
});
|
|
862
|
+
|
|
863
|
+
if (allEntries.length === 0) {
|
|
864
|
+
return {
|
|
865
|
+
status: 'pass',
|
|
866
|
+
evidence: [],
|
|
867
|
+
message: 'BC-06: No session data available — cannot assess artifact order'
|
|
868
|
+
};
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
// Collect artifact write timestamps per phase directory
|
|
872
|
+
// Map<phaseDir, Map<artifactType, { ts: string, index: number }>>
|
|
873
|
+
const phaseArtifacts = new Map();
|
|
874
|
+
|
|
875
|
+
let eventIndex = 0;
|
|
876
|
+
for (const entry of allEntries) {
|
|
877
|
+
eventIndex++;
|
|
878
|
+
const ts = entry.ts || entry.timestamp || '';
|
|
879
|
+
|
|
880
|
+
// Check for Write/Edit events targeting artifact files
|
|
881
|
+
const isWrite = entry.tool === 'Write' || entry.tool === 'Edit' ||
|
|
882
|
+
(entry.details && (entry.details.tool === 'Write' || entry.details.tool === 'Edit'));
|
|
883
|
+
|
|
884
|
+
if (isWrite) {
|
|
885
|
+
const filePath = (entry.tool_input && (entry.tool_input.file_path || entry.tool_input.filePath)) ||
|
|
886
|
+
(entry.details && (entry.details.file_path || entry.details.file)) ||
|
|
887
|
+
entry.file || entry.path || '';
|
|
888
|
+
|
|
889
|
+
const info = extractArtifactInfo(filePath);
|
|
890
|
+
if (info) {
|
|
891
|
+
if (!phaseArtifacts.has(info.phaseDir)) phaseArtifacts.set(info.phaseDir, new Map());
|
|
892
|
+
const artifacts = phaseArtifacts.get(info.phaseDir);
|
|
893
|
+
// Record first occurrence only (the initial write matters for ordering)
|
|
894
|
+
if (!artifacts.has(info.artifact)) {
|
|
895
|
+
artifacts.set(info.artifact, { ts, index: eventIndex });
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
// Also check entryTargetsFile for artifact patterns
|
|
901
|
+
if (!isWrite) {
|
|
902
|
+
for (const [artifactName, _order] of Object.entries(ARTIFACT_ORDER)) {
|
|
903
|
+
const pattern = new RegExp(`${artifactName}.*\\.md`, 'i');
|
|
904
|
+
if (entryTargetsFile(entry, pattern)) {
|
|
905
|
+
// Try to extract phase from the entry
|
|
906
|
+
const searchStr = JSON.stringify(entry);
|
|
907
|
+
const pdMatch = searchStr.match(/phases\/(\d{2}-[^/"]+)\//);
|
|
908
|
+
if (pdMatch) {
|
|
909
|
+
const phaseDir = pdMatch[1];
|
|
910
|
+
if (!phaseArtifacts.has(phaseDir)) phaseArtifacts.set(phaseDir, new Map());
|
|
911
|
+
const artifacts = phaseArtifacts.get(phaseDir);
|
|
912
|
+
if (!artifacts.has(artifactName)) {
|
|
913
|
+
artifacts.set(artifactName, { ts, index: eventIndex });
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
const evidence = [];
|
|
922
|
+
|
|
923
|
+
// Check ordering within each phase
|
|
924
|
+
for (const [phaseDir, artifacts] of phaseArtifacts) {
|
|
925
|
+
const entries = Array.from(artifacts.entries())
|
|
926
|
+
.map(([name, data]) => ({ name, ...data, order: ARTIFACT_ORDER[name] }))
|
|
927
|
+
.sort((a, b) => a.index - b.index); // Sort by actual occurrence order
|
|
928
|
+
|
|
929
|
+
// Compare each pair: if a later-ordered artifact appears before an earlier one
|
|
930
|
+
for (let i = 0; i < entries.length; i++) {
|
|
931
|
+
for (let j = i + 1; j < entries.length; j++) {
|
|
932
|
+
if (entries[j].order < entries[i].order) {
|
|
933
|
+
const timeA = entries[i].ts ? ` (${entries[i].ts.substring(11, 16)})` : '';
|
|
934
|
+
const timeB = entries[j].ts ? ` (${entries[j].ts.substring(11, 16)})` : '';
|
|
935
|
+
evidence.push(
|
|
936
|
+
`${phaseDir}: ${entries[j].name}.md written${timeB} before ${entries[i].name}.md${timeA} — out of order`
|
|
937
|
+
);
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
if (evidence.length > 0) {
|
|
944
|
+
return {
|
|
945
|
+
status: 'warn',
|
|
946
|
+
evidence,
|
|
947
|
+
message: `BC-06: Found ${evidence.length} out-of-order artifact creation(s)`
|
|
948
|
+
};
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
return {
|
|
952
|
+
status: 'pass',
|
|
953
|
+
evidence: [],
|
|
954
|
+
message: 'BC-06: All artifacts created in expected order (PLAN < SUMMARY < VERIFICATION)'
|
|
955
|
+
};
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
// ---------------------------------------------------------------------------
|
|
959
|
+
// BC-07: CRITICAL Marker Compliance
|
|
960
|
+
// ---------------------------------------------------------------------------
|
|
961
|
+
|
|
962
|
+
/**
|
|
963
|
+
* Known CRITICAL steps keyed by skill name.
|
|
964
|
+
* Each entry maps a skill to the file patterns that MUST be written after invocation.
|
|
965
|
+
*/
|
|
966
|
+
const CRITICAL_STEPS = {
|
|
967
|
+
build: [
|
|
968
|
+
{ description: 'SUMMARY.md written after build', pattern: /SUMMARY.*\.md/i },
|
|
969
|
+
],
|
|
970
|
+
quick: [
|
|
971
|
+
{ description: 'quick task directory created', pattern: /\.planning\/quick\/\d{3}-[^/]+\//i },
|
|
972
|
+
],
|
|
973
|
+
begin: [
|
|
974
|
+
{ description: '.planning/ directory and STATE.md created', pattern: /STATE\.md/i },
|
|
975
|
+
],
|
|
976
|
+
};
|
|
977
|
+
|
|
978
|
+
/**
|
|
979
|
+
* Check if an entry represents a skill invocation.
|
|
980
|
+
* @param {Object} entry - JSONL entry
|
|
981
|
+
* @returns {string|null} skill name or null
|
|
982
|
+
*/
|
|
983
|
+
function extractSkillInvocation(entry) {
|
|
984
|
+
// Direct skill event
|
|
985
|
+
if (entry.cat === 'skill' && entry.event) return entry.event;
|
|
986
|
+
|
|
987
|
+
// pbr: prefix events
|
|
988
|
+
if (entry.event && entry.event.startsWith('pbr:')) {
|
|
989
|
+
return entry.event.replace('pbr:', '').split('-')[0];
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
// Skill field in details
|
|
993
|
+
if (entry.details && entry.details.skill) return entry.details.skill;
|
|
994
|
+
|
|
995
|
+
return null;
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
/**
|
|
999
|
+
* BC-07: Check that CRITICAL/STOP markers in skills were followed by the LLM.
|
|
1000
|
+
* Detects when expected artifact writes are missing after skill invocations.
|
|
1001
|
+
*
|
|
1002
|
+
* @param {string} planningDir - Path to .planning/
|
|
1003
|
+
* @param {Object} [_config] - Config object (unused, for API consistency)
|
|
1004
|
+
* @returns {{ status: string, evidence: Array<string>, message: string }}
|
|
1005
|
+
*/
|
|
1006
|
+
function checkCriticalMarkerCompliance(planningDir, _config) {
|
|
1007
|
+
const events = readSessionEvents(planningDir);
|
|
1008
|
+
const hookLogs = readHookLogs(planningDir);
|
|
1009
|
+
const allEntries = [...events, ...hookLogs].sort((a, b) => {
|
|
1010
|
+
const ta = a.ts || a.timestamp || '';
|
|
1011
|
+
const tb = b.ts || b.timestamp || '';
|
|
1012
|
+
return ta < tb ? -1 : ta > tb ? 1 : 0;
|
|
1013
|
+
});
|
|
1014
|
+
|
|
1015
|
+
if (allEntries.length === 0) {
|
|
1016
|
+
return {
|
|
1017
|
+
status: 'pass',
|
|
1018
|
+
evidence: [],
|
|
1019
|
+
message: 'BC-07: No session data available — cannot assess CRITICAL marker compliance'
|
|
1020
|
+
};
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
const evidence = [];
|
|
1024
|
+
|
|
1025
|
+
// Collect skill invocations and all write targets
|
|
1026
|
+
const skillInvocations = []; // { skill, ts, index }
|
|
1027
|
+
const writeTargets = []; // { target: string, index }
|
|
1028
|
+
|
|
1029
|
+
let idx = 0;
|
|
1030
|
+
for (const entry of allEntries) {
|
|
1031
|
+
idx++;
|
|
1032
|
+
const skill = extractSkillInvocation(entry);
|
|
1033
|
+
if (skill && CRITICAL_STEPS[skill]) {
|
|
1034
|
+
skillInvocations.push({ skill, ts: entry.ts || entry.timestamp || '', index: idx });
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
// Collect all write events
|
|
1038
|
+
const isWrite = entry.tool === 'Write' || entry.tool === 'Edit' ||
|
|
1039
|
+
(entry.details && (entry.details.tool === 'Write' || entry.details.tool === 'Edit'));
|
|
1040
|
+
|
|
1041
|
+
if (isWrite) {
|
|
1042
|
+
const filePath = (entry.tool_input && (entry.tool_input.file_path || entry.tool_input.filePath)) ||
|
|
1043
|
+
(entry.details && (entry.details.file_path || entry.details.file)) ||
|
|
1044
|
+
entry.file || entry.path || '';
|
|
1045
|
+
if (filePath) writeTargets.push({ target: filePath, index: idx });
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
// Also check entry details for file references
|
|
1049
|
+
if (entryTargetsFile(entry, /\.(md|json)$/i)) {
|
|
1050
|
+
const filePath = (entry.tool_input && (entry.tool_input.file_path || entry.tool_input.filePath)) ||
|
|
1051
|
+
(entry.details && (entry.details.file_path || entry.details.file)) ||
|
|
1052
|
+
entry.file || entry.path || '';
|
|
1053
|
+
if (filePath) writeTargets.push({ target: filePath, index: idx });
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
// For each skill invocation, check if CRITICAL writes occurred afterward
|
|
1058
|
+
for (const invocation of skillInvocations) {
|
|
1059
|
+
const steps = CRITICAL_STEPS[invocation.skill];
|
|
1060
|
+
for (const step of steps) {
|
|
1061
|
+
const hasWrite = writeTargets.some(
|
|
1062
|
+
w => w.index > invocation.index && step.pattern.test(w.target)
|
|
1063
|
+
);
|
|
1064
|
+
if (!hasWrite) {
|
|
1065
|
+
const time = invocation.ts ? ` at ${invocation.ts}` : '';
|
|
1066
|
+
evidence.push(
|
|
1067
|
+
`${invocation.skill} skill invoked${time} but ${step.description} — CRITICAL step may have been skipped`
|
|
1068
|
+
);
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
if (evidence.length > 0) {
|
|
1074
|
+
return {
|
|
1075
|
+
status: 'warn',
|
|
1076
|
+
evidence,
|
|
1077
|
+
message: `BC-07: Found ${evidence.length} potentially skipped CRITICAL step(s)`
|
|
1078
|
+
};
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
return {
|
|
1082
|
+
status: 'pass',
|
|
1083
|
+
evidence: [],
|
|
1084
|
+
message: 'BC-07: All CRITICAL marker steps appear to have been followed'
|
|
1085
|
+
};
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
// ---------------------------------------------------------------------------
|
|
1089
|
+
// BC-08: Gate Compliance
|
|
1090
|
+
// ---------------------------------------------------------------------------
|
|
1091
|
+
|
|
1092
|
+
/**
|
|
1093
|
+
* BC-08: Check that gate behavior matches config settings.
|
|
1094
|
+
* In autonomous mode with gates disabled, AskUserQuestion should not be used for gate prompts.
|
|
1095
|
+
* In interactive mode with gates enabled, AskUserQuestion should be present.
|
|
1096
|
+
*
|
|
1097
|
+
* @param {string} planningDir - Path to .planning/
|
|
1098
|
+
* @param {Object} [config] - Config object with mode and gates settings
|
|
1099
|
+
* @returns {{ status: string, evidence: Array<string>, message: string }}
|
|
1100
|
+
*/
|
|
1101
|
+
function checkGateCompliance(planningDir, config) {
|
|
1102
|
+
const events = readSessionEvents(planningDir);
|
|
1103
|
+
const hookLogs = readHookLogs(planningDir);
|
|
1104
|
+
const allEntries = [...events, ...hookLogs].sort((a, b) => {
|
|
1105
|
+
const ta = a.ts || a.timestamp || '';
|
|
1106
|
+
const tb = b.ts || b.timestamp || '';
|
|
1107
|
+
return ta < tb ? -1 : ta > tb ? 1 : 0;
|
|
1108
|
+
});
|
|
1109
|
+
|
|
1110
|
+
if (allEntries.length === 0) {
|
|
1111
|
+
return {
|
|
1112
|
+
status: 'pass',
|
|
1113
|
+
evidence: [],
|
|
1114
|
+
message: 'BC-08: No session data available — cannot assess gate compliance'
|
|
1115
|
+
};
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
// Determine mode and gate settings
|
|
1119
|
+
const mode = (config && config.mode) || 'interactive';
|
|
1120
|
+
const gates = (config && config.gates) || {};
|
|
1121
|
+
const isAutonomous = mode === 'autonomous';
|
|
1122
|
+
const allGatesFalse = Object.keys(gates).length > 0 &&
|
|
1123
|
+
Object.keys(gates).filter(k => k.startsWith('confirm_')).every(k => !gates[k]);
|
|
1124
|
+
|
|
1125
|
+
const evidence = [];
|
|
1126
|
+
|
|
1127
|
+
// Find AskUserQuestion events that look gate-like
|
|
1128
|
+
const gatePatterns = /\b(confirm|proceed|approve|accept|continue\s+with|go\s+ahead)\b/i;
|
|
1129
|
+
const gateAskEvents = [];
|
|
1130
|
+
|
|
1131
|
+
for (const entry of allEntries) {
|
|
1132
|
+
const isAsk = entry.tool === 'AskUserQuestion' ||
|
|
1133
|
+
(entry.details && entry.details.tool === 'AskUserQuestion');
|
|
1134
|
+
if (!isAsk) continue;
|
|
1135
|
+
|
|
1136
|
+
// Extract the question content
|
|
1137
|
+
const question = (entry.tool_input && (entry.tool_input.question || entry.tool_input.message)) ||
|
|
1138
|
+
(entry.details && (entry.details.question || entry.details.message)) || '';
|
|
1139
|
+
const questionStr = typeof question === 'string' ? question : JSON.stringify(question);
|
|
1140
|
+
|
|
1141
|
+
if (gatePatterns.test(questionStr)) {
|
|
1142
|
+
gateAskEvents.push({
|
|
1143
|
+
question: questionStr.substring(0, 100),
|
|
1144
|
+
ts: entry.ts || entry.timestamp || ''
|
|
1145
|
+
});
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
if (isAutonomous && allGatesFalse && gateAskEvents.length > 0) {
|
|
1150
|
+
for (const ask of gateAskEvents) {
|
|
1151
|
+
const time = ask.ts ? ` at ${ask.ts}` : '';
|
|
1152
|
+
evidence.push(
|
|
1153
|
+
`Autonomous mode with gates disabled but AskUserQuestion asked "${ask.question}"${time}`
|
|
1154
|
+
);
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
if (!isAutonomous && Object.keys(gates).some(k => k.startsWith('confirm_') && gates[k])) {
|
|
1159
|
+
// Interactive mode with some gates enabled — check if gates were actually used
|
|
1160
|
+
const enabledGates = Object.keys(gates).filter(k => k.startsWith('confirm_') && gates[k]);
|
|
1161
|
+
if (gateAskEvents.length === 0 && enabledGates.length > 0) {
|
|
1162
|
+
evidence.push(
|
|
1163
|
+
`Interactive mode with ${enabledGates.length} gate(s) enabled (${enabledGates.join(', ')}) ` +
|
|
1164
|
+
`but no gate-like AskUserQuestion events detected — gates may have been skipped`
|
|
1165
|
+
);
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
if (evidence.length > 0) {
|
|
1170
|
+
return {
|
|
1171
|
+
status: 'warn',
|
|
1172
|
+
evidence,
|
|
1173
|
+
message: `BC-08: Gate behavior does not match config (${evidence.length} concern(s))`
|
|
1174
|
+
};
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
return {
|
|
1178
|
+
status: 'pass',
|
|
1179
|
+
evidence: [],
|
|
1180
|
+
message: `BC-08: Gate behavior aligned with config (mode=${mode}, gates=${isAutonomous && allGatesFalse ? 'all disabled' : 'active'})`
|
|
1181
|
+
};
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
// ---------------------------------------------------------------------------
|
|
1185
|
+
// BC-09: Enforce-PBR-Workflow Advisory Tracking
|
|
1186
|
+
// ---------------------------------------------------------------------------
|
|
1187
|
+
|
|
1188
|
+
/**
|
|
1189
|
+
* BC-09: Track enforce-PBR-workflow hook advisories and whether they were addressed.
|
|
1190
|
+
* Reads hook logs for prompt-routing and check-skill-workflow advisory entries,
|
|
1191
|
+
* then checks if the advised PBR skill was subsequently used.
|
|
1192
|
+
*
|
|
1193
|
+
* @param {string} planningDir - Path to .planning/
|
|
1194
|
+
* @param {Object} [config] - Config object with workflow.enforce_pbr_skills setting
|
|
1195
|
+
* @returns {{ status: string, evidence: Array<string>, message: string }}
|
|
1196
|
+
*/
|
|
1197
|
+
function checkEnforceWorkflowAdvisory(planningDir, config) {
|
|
1198
|
+
// Check enforce_pbr_skills setting
|
|
1199
|
+
const enforceSetting = (config && config.workflow && config.workflow.enforce_pbr_skills) || 'off';
|
|
1200
|
+
|
|
1201
|
+
if (enforceSetting === 'off') {
|
|
1202
|
+
return {
|
|
1203
|
+
status: 'pass',
|
|
1204
|
+
evidence: [],
|
|
1205
|
+
message: 'BC-09: enforce_pbr_skills is disabled — advisory tracking skipped'
|
|
1206
|
+
};
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
const hookLogs = readHookLogs(planningDir);
|
|
1210
|
+
const events = readSessionEvents(planningDir);
|
|
1211
|
+
const allEntries = [...events, ...hookLogs].sort((a, b) => {
|
|
1212
|
+
const ta = a.ts || a.timestamp || '';
|
|
1213
|
+
const tb = b.ts || b.timestamp || '';
|
|
1214
|
+
return ta < tb ? -1 : ta > tb ? 1 : 0;
|
|
1215
|
+
});
|
|
1216
|
+
|
|
1217
|
+
if (allEntries.length === 0) {
|
|
1218
|
+
return {
|
|
1219
|
+
status: 'info',
|
|
1220
|
+
evidence: [],
|
|
1221
|
+
message: 'BC-09: No session data available — cannot assess workflow advisory compliance'
|
|
1222
|
+
};
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
// Collect advisory entries from prompt-routing and check-skill-workflow hooks
|
|
1226
|
+
const advisories = [];
|
|
1227
|
+
const skillUsages = []; // Track actual PBR skill invocations
|
|
1228
|
+
|
|
1229
|
+
for (const entry of allEntries) {
|
|
1230
|
+
// Detect advisory warnings from relevant hooks
|
|
1231
|
+
const isAdvisoryHook = entry.hook === 'prompt-routing' ||
|
|
1232
|
+
entry.hook === 'check-skill-workflow' ||
|
|
1233
|
+
entry.hook === 'enforce-pbr-workflow';
|
|
1234
|
+
|
|
1235
|
+
if (isAdvisoryHook && entry.details) {
|
|
1236
|
+
const isAdvisory = entry.details.level === 'advisory' ||
|
|
1237
|
+
entry.details.decision === 'allow' ||
|
|
1238
|
+
entry.details.type === 'advisory' ||
|
|
1239
|
+
(entry.details.message && /suggest|consider|recommend|use \/pbr:/i.test(entry.details.message));
|
|
1240
|
+
|
|
1241
|
+
if (isAdvisory) {
|
|
1242
|
+
// Extract the suggested command if available
|
|
1243
|
+
const suggestedCommand = entry.details.suggested_command ||
|
|
1244
|
+
entry.details.command || '';
|
|
1245
|
+
const message = entry.details.message || entry.details.reason || '';
|
|
1246
|
+
const cmdMatch = message.match(/\/pbr:(\w+)/);
|
|
1247
|
+
const suggestedSkill = cmdMatch ? cmdMatch[1] : (suggestedCommand.replace('/pbr:', '') || null);
|
|
1248
|
+
|
|
1249
|
+
advisories.push({
|
|
1250
|
+
ts: entry.ts || entry.timestamp || '',
|
|
1251
|
+
suggestedSkill,
|
|
1252
|
+
message: message.substring(0, 120),
|
|
1253
|
+
index: allEntries.indexOf(entry)
|
|
1254
|
+
});
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
// Track PBR skill usages
|
|
1259
|
+
const skill = extractSkillInvocation(entry);
|
|
1260
|
+
if (skill) {
|
|
1261
|
+
skillUsages.push({
|
|
1262
|
+
skill,
|
|
1263
|
+
ts: entry.ts || entry.timestamp || '',
|
|
1264
|
+
index: allEntries.indexOf(entry)
|
|
1265
|
+
});
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
if (advisories.length === 0) {
|
|
1270
|
+
return {
|
|
1271
|
+
status: 'info',
|
|
1272
|
+
evidence: [],
|
|
1273
|
+
message: `BC-09: No workflow advisories issued (enforce_pbr_skills=${enforceSetting})`
|
|
1274
|
+
};
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
// For each advisory, check if the suggested skill was used afterward
|
|
1278
|
+
let heeded = 0;
|
|
1279
|
+
let ignored = 0;
|
|
1280
|
+
const ignoredExamples = [];
|
|
1281
|
+
|
|
1282
|
+
for (const advisory of advisories) {
|
|
1283
|
+
if (!advisory.suggestedSkill) {
|
|
1284
|
+
// Cannot determine if heeded without a specific suggestion
|
|
1285
|
+
continue;
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
const wasHeeded = skillUsages.some(
|
|
1289
|
+
s => s.index > advisory.index && s.skill === advisory.suggestedSkill
|
|
1290
|
+
);
|
|
1291
|
+
|
|
1292
|
+
if (wasHeeded) {
|
|
1293
|
+
heeded++;
|
|
1294
|
+
} else {
|
|
1295
|
+
ignored++;
|
|
1296
|
+
if (ignoredExamples.length < 3) {
|
|
1297
|
+
const time = advisory.ts ? ` at ${advisory.ts}` : '';
|
|
1298
|
+
ignoredExamples.push(
|
|
1299
|
+
`Advisory to use /pbr:${advisory.suggestedSkill} was ignored${time}: "${advisory.message}"`
|
|
1300
|
+
);
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
const total = heeded + ignored;
|
|
1306
|
+
const complianceRate = total > 0 ? Math.round((heeded / total) * 100) : 100;
|
|
1307
|
+
|
|
1308
|
+
const evidence = [
|
|
1309
|
+
`${advisories.length} workflow advisories issued, ${heeded} heeded, ${ignored} ignored (${complianceRate}% compliance rate)`,
|
|
1310
|
+
...ignoredExamples
|
|
1311
|
+
];
|
|
1312
|
+
|
|
1313
|
+
return {
|
|
1314
|
+
status: 'info',
|
|
1315
|
+
evidence,
|
|
1316
|
+
message: `BC-09: ${total} trackable advisories — ${complianceRate}% compliance rate (enforce_pbr_skills=${enforceSetting})`
|
|
1317
|
+
};
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
// ---------------------------------------------------------------------------
|
|
1321
|
+
// BC-10: Unmanaged Commit Detection
|
|
1322
|
+
// ---------------------------------------------------------------------------
|
|
1323
|
+
|
|
1324
|
+
/**
|
|
1325
|
+
* BC-10: Detect git commits made outside PBR skill context.
|
|
1326
|
+
* Cross-references .active-skill state against git commit Bash events.
|
|
1327
|
+
* Commits without an active PBR skill context are "unmanaged".
|
|
1328
|
+
*
|
|
1329
|
+
* @param {string} planningDir - Path to .planning/
|
|
1330
|
+
* @param {Object} [_config] - Config object (unused, for API consistency)
|
|
1331
|
+
* @returns {{ status: string, evidence: Array<string>, message: string }}
|
|
1332
|
+
*/
|
|
1333
|
+
function checkUnmanagedCommitDetection(planningDir, _config) {
|
|
1334
|
+
const events = readSessionEvents(planningDir);
|
|
1335
|
+
const hookLogs = readHookLogs(planningDir);
|
|
1336
|
+
const allEntries = [...events, ...hookLogs].sort((a, b) => {
|
|
1337
|
+
const ta = a.ts || a.timestamp || '';
|
|
1338
|
+
const tb = b.ts || b.timestamp || '';
|
|
1339
|
+
return ta < tb ? -1 : ta > tb ? 1 : 0;
|
|
1340
|
+
});
|
|
1341
|
+
|
|
1342
|
+
if (allEntries.length === 0) {
|
|
1343
|
+
return {
|
|
1344
|
+
status: 'pass',
|
|
1345
|
+
evidence: [],
|
|
1346
|
+
message: 'BC-10: No session data available — cannot assess commit management'
|
|
1347
|
+
};
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
const evidence = [];
|
|
1351
|
+
const commitFormatPattern = /^(feat|fix|refactor|test|docs|chore|wip|revert)\([a-zA-Z0-9._-]+\):\s+.+/;
|
|
1352
|
+
|
|
1353
|
+
// Track active-skill state over time from hook logs
|
|
1354
|
+
// Hook logs from validate-commit contain the commit validation context
|
|
1355
|
+
const activeSkillEntries = [];
|
|
1356
|
+
for (const entry of allEntries) {
|
|
1357
|
+
// Detect active-skill state changes from hook logs
|
|
1358
|
+
if (entry.hook && entry.details && entry.details.active_skill != null) {
|
|
1359
|
+
activeSkillEntries.push({
|
|
1360
|
+
skill: entry.details.active_skill,
|
|
1361
|
+
ts: entry.ts || entry.timestamp || '',
|
|
1362
|
+
});
|
|
1363
|
+
}
|
|
1364
|
+
// Also detect skill invocations as implicit active-skill markers
|
|
1365
|
+
const skill = extractSkillInvocation(entry);
|
|
1366
|
+
if (skill) {
|
|
1367
|
+
activeSkillEntries.push({ skill, ts: entry.ts || entry.timestamp || '' });
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
// Find all git commit Bash events
|
|
1372
|
+
for (const entry of allEntries) {
|
|
1373
|
+
const command = (entry.tool_input && entry.tool_input.command) ||
|
|
1374
|
+
(entry.details && entry.details.command) || '';
|
|
1375
|
+
const commandStr = typeof command === 'string' ? command : JSON.stringify(command);
|
|
1376
|
+
|
|
1377
|
+
if (!/\bgit\s+commit\b/i.test(commandStr)) continue;
|
|
1378
|
+
|
|
1379
|
+
const ts = entry.ts || entry.timestamp || '';
|
|
1380
|
+
const time = ts ? ts.substring(11, 16) : 'unknown';
|
|
1381
|
+
|
|
1382
|
+
// Extract commit message for format check
|
|
1383
|
+
const msgMatch = commandStr.match(/-m\s+["']([^"']+)["']/) ||
|
|
1384
|
+
commandStr.match(/<<'?EOF'?\s*\n([\s\S]*?)\nEOF/);
|
|
1385
|
+
const commitMsg = msgMatch ? msgMatch[1].trim().split('\n')[0].trim() : '';
|
|
1386
|
+
|
|
1387
|
+
// Check if there was an active PBR skill at the time of this commit
|
|
1388
|
+
// Look at the most recent active-skill entry before this commit timestamp
|
|
1389
|
+
const priorSkills = activeSkillEntries.filter(s => s.ts <= ts || !ts);
|
|
1390
|
+
const lastSkill = priorSkills.length > 0 ? priorSkills[priorSkills.length - 1] : null;
|
|
1391
|
+
|
|
1392
|
+
// Also check validate-commit hook logs for this commit — if the hook fired,
|
|
1393
|
+
// there may be active-skill info
|
|
1394
|
+
const commitHookEntry = allEntries.find(e =>
|
|
1395
|
+
e.hook === 'validate-commit' &&
|
|
1396
|
+
e.details && e.details.message === commitMsg
|
|
1397
|
+
);
|
|
1398
|
+
const hookActiveSkill = commitHookEntry && commitHookEntry.details &&
|
|
1399
|
+
commitHookEntry.details.active_skill;
|
|
1400
|
+
|
|
1401
|
+
const hasActiveSkill = (lastSkill && lastSkill.skill) || hookActiveSkill;
|
|
1402
|
+
|
|
1403
|
+
if (!hasActiveSkill) {
|
|
1404
|
+
// No active skill context detected
|
|
1405
|
+
const hasGoodFormat = commitFormatPattern.test(commitMsg);
|
|
1406
|
+
const formatNote = hasGoodFormat ? '' : ' (also lacks conventional format)';
|
|
1407
|
+
evidence.push(
|
|
1408
|
+
`Commit '${commitMsg || '(unparseable)'}' at ${time} — no active PBR skill context detected${formatNote}`
|
|
1409
|
+
);
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
if (evidence.length > 0) {
|
|
1414
|
+
return {
|
|
1415
|
+
status: 'warn',
|
|
1416
|
+
evidence,
|
|
1417
|
+
message: `BC-10: Found ${evidence.length} unmanaged commit(s) outside PBR skill context`
|
|
1418
|
+
};
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
return {
|
|
1422
|
+
status: 'pass',
|
|
1423
|
+
evidence: [],
|
|
1424
|
+
message: 'BC-10: All commits were within PBR skill context'
|
|
1425
|
+
};
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
// ---------------------------------------------------------------------------
|
|
1429
|
+
// BC-11: Context Delegation Threshold
|
|
1430
|
+
// ---------------------------------------------------------------------------
|
|
1431
|
+
|
|
1432
|
+
/**
|
|
1433
|
+
* BC-11: Detect when context exceeded inline_context_cap_pct but no subagent was spawned.
|
|
1434
|
+
* Uses config.workflow.inline_context_cap_pct threshold.
|
|
1435
|
+
*
|
|
1436
|
+
* @param {string} planningDir - Path to .planning/
|
|
1437
|
+
* @param {Object} [config] - Config object with workflow.inline_context_cap_pct
|
|
1438
|
+
* @returns {{ status: string, evidence: Array<string>, message: string }}
|
|
1439
|
+
*/
|
|
1440
|
+
function checkContextDelegationThreshold(planningDir, config) {
|
|
1441
|
+
const hookLogs = readHookLogs(planningDir);
|
|
1442
|
+
const events = readSessionEvents(planningDir);
|
|
1443
|
+
const allEntries = [...events, ...hookLogs].sort((a, b) => {
|
|
1444
|
+
const ta = a.ts || a.timestamp || '';
|
|
1445
|
+
const tb = b.ts || b.timestamp || '';
|
|
1446
|
+
return ta < tb ? -1 : ta > tb ? 1 : 0;
|
|
1447
|
+
});
|
|
1448
|
+
|
|
1449
|
+
if (allEntries.length === 0) {
|
|
1450
|
+
return {
|
|
1451
|
+
status: 'pass',
|
|
1452
|
+
evidence: [],
|
|
1453
|
+
message: 'BC-11: No session data available — cannot assess context delegation'
|
|
1454
|
+
};
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
// Read inline_context_cap_pct from config (default 50)
|
|
1458
|
+
const capPct = (config && config.workflow && config.workflow.inline_context_cap_pct != null)
|
|
1459
|
+
? config.workflow.inline_context_cap_pct
|
|
1460
|
+
: 50;
|
|
1461
|
+
|
|
1462
|
+
const evidence = [];
|
|
1463
|
+
|
|
1464
|
+
// Find context usage reports from suggest-compact or track-context-budget hooks
|
|
1465
|
+
for (let i = 0; i < allEntries.length; i++) {
|
|
1466
|
+
const entry = allEntries[i];
|
|
1467
|
+
|
|
1468
|
+
// Look for context percentage reports
|
|
1469
|
+
const isContextHook = entry.hook === 'suggest-compact' ||
|
|
1470
|
+
entry.hook === 'track-context-budget' ||
|
|
1471
|
+
entry.hook === 'context-budget-check';
|
|
1472
|
+
|
|
1473
|
+
if (!isContextHook) continue;
|
|
1474
|
+
|
|
1475
|
+
// Extract context percentage from entry details
|
|
1476
|
+
let pctUsed = null;
|
|
1477
|
+
if (entry.details) {
|
|
1478
|
+
pctUsed = entry.details.pct_used || entry.details.estimated_percent ||
|
|
1479
|
+
entry.details.percent || entry.details.context_pct || null;
|
|
1480
|
+
// Also check tier info — DEGRADING is typically >=50%, POOR >=70%
|
|
1481
|
+
if (pctUsed == null && entry.details.tier) {
|
|
1482
|
+
if (entry.details.tier === 'POOR' || entry.details.tier === 'CRITICAL') pctUsed = 70;
|
|
1483
|
+
else if (entry.details.tier === 'DEGRADING') pctUsed = 55;
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
if (pctUsed == null || pctUsed <= capPct) continue;
|
|
1488
|
+
|
|
1489
|
+
const ts = entry.ts || entry.timestamp || '';
|
|
1490
|
+
const time = ts ? ts.substring(11, 16) : 'unknown';
|
|
1491
|
+
|
|
1492
|
+
// Check if a Task (subagent) was spawned in the next 5 events
|
|
1493
|
+
const lookAhead = allEntries.slice(i + 1, i + 6);
|
|
1494
|
+
const taskSpawned = lookAhead.some(e =>
|
|
1495
|
+
e.tool === 'Task' ||
|
|
1496
|
+
e.event === 'subagent-start' ||
|
|
1497
|
+
e.event === 'task-start' ||
|
|
1498
|
+
(e.details && e.details.tool === 'Task') ||
|
|
1499
|
+
(e.details && e.details.subagent_type)
|
|
1500
|
+
);
|
|
1501
|
+
|
|
1502
|
+
if (!taskSpawned) {
|
|
1503
|
+
evidence.push(
|
|
1504
|
+
`Context at ${pctUsed}% (cap: ${capPct}%) at ${time} but no subagent spawned in next 5 tool calls`
|
|
1505
|
+
);
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
if (evidence.length > 0) {
|
|
1510
|
+
return {
|
|
1511
|
+
status: 'warn',
|
|
1512
|
+
evidence,
|
|
1513
|
+
message: `BC-11: Context delegation threshold breached ${evidence.length} time(s) without subagent spawn`
|
|
1514
|
+
};
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
return {
|
|
1518
|
+
status: 'pass',
|
|
1519
|
+
evidence: [],
|
|
1520
|
+
message: `BC-11: Context delegation threshold (${capPct}%) respected — subagents spawned when needed`
|
|
1521
|
+
};
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
// ---------------------------------------------------------------------------
|
|
1525
|
+
// BC-12: Skill Self-Read Prevention
|
|
1526
|
+
// ---------------------------------------------------------------------------
|
|
1527
|
+
|
|
1528
|
+
/**
|
|
1529
|
+
* BC-12: Detect skills that wasted tokens reading their own SKILL.md.
|
|
1530
|
+
* Claude Code auto-loads SKILL.md for the active skill, so reading it again
|
|
1531
|
+
* is a token waste.
|
|
1532
|
+
*
|
|
1533
|
+
* @param {string} planningDir - Path to .planning/
|
|
1534
|
+
* @param {Object} [_config] - Config object (unused, for API consistency)
|
|
1535
|
+
* @returns {{ status: string, evidence: Array<string>, message: string }}
|
|
1536
|
+
*/
|
|
1537
|
+
function checkSkillSelfReadPrevention(planningDir, _config) {
|
|
1538
|
+
const events = readSessionEvents(planningDir);
|
|
1539
|
+
const hookLogs = readHookLogs(planningDir);
|
|
1540
|
+
const allEntries = [...events, ...hookLogs].sort((a, b) => {
|
|
1541
|
+
const ta = a.ts || a.timestamp || '';
|
|
1542
|
+
const tb = b.ts || b.timestamp || '';
|
|
1543
|
+
return ta < tb ? -1 : ta > tb ? 1 : 0;
|
|
1544
|
+
});
|
|
1545
|
+
|
|
1546
|
+
if (allEntries.length === 0) {
|
|
1547
|
+
return {
|
|
1548
|
+
status: 'pass',
|
|
1549
|
+
evidence: [],
|
|
1550
|
+
message: 'BC-12: No session data available — cannot assess skill self-reads'
|
|
1551
|
+
};
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
const evidence = [];
|
|
1555
|
+
|
|
1556
|
+
// Track active-skill state and detect Read events for SKILL.md files
|
|
1557
|
+
let currentSkill = null;
|
|
1558
|
+
|
|
1559
|
+
for (const entry of allEntries) {
|
|
1560
|
+
// Update current active skill from hook logs
|
|
1561
|
+
if (entry.hook && entry.details && entry.details.active_skill != null) {
|
|
1562
|
+
currentSkill = entry.details.active_skill;
|
|
1563
|
+
}
|
|
1564
|
+
// Also update from skill invocations
|
|
1565
|
+
const invokedSkill = extractSkillInvocation(entry);
|
|
1566
|
+
if (invokedSkill) {
|
|
1567
|
+
currentSkill = invokedSkill;
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
// Skip entries from agent contexts (agents may legitimately read skill files)
|
|
1571
|
+
if (entry.task_id || entry.subagent ||
|
|
1572
|
+
(entry.details && (entry.details.subagent_type || entry.details.task_id || entry.details.inside_task))) {
|
|
1573
|
+
continue;
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
// Detect Read tool events targeting SKILL.md files
|
|
1577
|
+
const isRead = entry.tool === 'Read' ||
|
|
1578
|
+
(entry.details && entry.details.tool === 'Read');
|
|
1579
|
+
if (!isRead) continue;
|
|
1580
|
+
|
|
1581
|
+
const filePath = (entry.tool_input && entry.tool_input.file_path) ||
|
|
1582
|
+
(entry.details && (entry.details.file_path || entry.details.file)) ||
|
|
1583
|
+
entry.file || entry.path || '';
|
|
1584
|
+
const normalized = filePath.replace(/\\/g, '/');
|
|
1585
|
+
|
|
1586
|
+
// Check if this is a SKILL.md file under /skills/
|
|
1587
|
+
if (!/\/skills\//.test(normalized) || !/SKILL\.md$/i.test(normalized)) continue;
|
|
1588
|
+
|
|
1589
|
+
// Extract skill name from path: skills/{skill-name}/SKILL.md
|
|
1590
|
+
const skillMatch = normalized.match(/\/skills\/([^/]+)\/SKILL\.md$/i);
|
|
1591
|
+
if (!skillMatch) continue;
|
|
1592
|
+
const readSkillName = skillMatch[1];
|
|
1593
|
+
|
|
1594
|
+
// Check if the active skill matches the SKILL.md being read
|
|
1595
|
+
if (currentSkill && readSkillName === currentSkill) {
|
|
1596
|
+
const ts = entry.ts || entry.timestamp || '';
|
|
1597
|
+
const time = ts ? ts.substring(11, 16) : 'unknown';
|
|
1598
|
+
evidence.push(
|
|
1599
|
+
`${currentSkill} skill read its own skills/${currentSkill}/SKILL.md at ${time} — wasted tokens`
|
|
1600
|
+
);
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
if (evidence.length > 0) {
|
|
1605
|
+
return {
|
|
1606
|
+
status: 'info',
|
|
1607
|
+
evidence,
|
|
1608
|
+
message: `BC-12: Found ${evidence.length} skill self-read(s) — token waste detected`
|
|
1609
|
+
};
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
return {
|
|
1613
|
+
status: 'pass',
|
|
1614
|
+
evidence: [],
|
|
1615
|
+
message: 'BC-12: No skill self-reads detected'
|
|
1616
|
+
};
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
// ---------------------------------------------------------------------------
|
|
1620
|
+
// BC-13: Hook Output Effectiveness
|
|
1621
|
+
// ---------------------------------------------------------------------------
|
|
1622
|
+
|
|
1623
|
+
/**
|
|
1624
|
+
* BC-13: Check whether hook advisory outputs were actually followed by the LLM.
|
|
1625
|
+
* Reads hook logs for additionalContext/warning outputs, then checks subsequent
|
|
1626
|
+
* session events to see if the LLM's next action aligned with the advisory.
|
|
1627
|
+
*
|
|
1628
|
+
* @param {string} planningDir - Path to .planning/
|
|
1629
|
+
* @param {Object} [_config] - Config object (unused, for API consistency)
|
|
1630
|
+
* @returns {{ status: string, evidence: Array<string>, message: string }}
|
|
1631
|
+
*/
|
|
1632
|
+
function checkHookOutputEffectiveness(planningDir, _config) {
|
|
1633
|
+
const hookLogs = readHookLogs(planningDir);
|
|
1634
|
+
const events = readSessionEvents(planningDir);
|
|
1635
|
+
const allEntries = [...events, ...hookLogs].sort((a, b) => {
|
|
1636
|
+
const ta = a.ts || a.timestamp || '';
|
|
1637
|
+
const tb = b.ts || b.timestamp || '';
|
|
1638
|
+
return ta < tb ? -1 : ta > tb ? 1 : 0;
|
|
1639
|
+
});
|
|
1640
|
+
|
|
1641
|
+
if (allEntries.length === 0) {
|
|
1642
|
+
return {
|
|
1643
|
+
status: 'pass',
|
|
1644
|
+
evidence: [],
|
|
1645
|
+
message: 'BC-13: No session data available — cannot assess hook output effectiveness'
|
|
1646
|
+
};
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
// Find hook entries that produced advisory output (additionalContext, warnings)
|
|
1650
|
+
const advisoryHookEntries = [];
|
|
1651
|
+
for (let i = 0; i < allEntries.length; i++) {
|
|
1652
|
+
const entry = allEntries[i];
|
|
1653
|
+
if (!entry.hook) continue;
|
|
1654
|
+
|
|
1655
|
+
const hasAdvisory =
|
|
1656
|
+
(entry.details && entry.details.additionalContext) ||
|
|
1657
|
+
(entry.details && entry.details.warning) ||
|
|
1658
|
+
(entry.details && entry.details.level === 'advisory') ||
|
|
1659
|
+
(entry.output && typeof entry.output === 'string' && entry.output.includes('additionalContext'));
|
|
1660
|
+
|
|
1661
|
+
if (hasAdvisory) {
|
|
1662
|
+
// Extract advisory content
|
|
1663
|
+
const advisory = (entry.details && entry.details.additionalContext) ||
|
|
1664
|
+
(entry.details && entry.details.warning) ||
|
|
1665
|
+
(entry.details && entry.details.message) || '';
|
|
1666
|
+
advisoryHookEntries.push({ index: i, hook: entry.hook, advisory, ts: entry.ts || entry.timestamp || '' });
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
if (advisoryHookEntries.length === 0) {
|
|
1671
|
+
return {
|
|
1672
|
+
status: 'info',
|
|
1673
|
+
evidence: ['No hook advisory outputs found in logs'],
|
|
1674
|
+
message: 'BC-13: No hook advisories detected — nothing to measure effectiveness against'
|
|
1675
|
+
};
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
// For each advisory, check if the next LLM action appears to comply
|
|
1679
|
+
// Heuristic: if the advisory mentions "block" or "stop" and the next tool call
|
|
1680
|
+
// is in the same file/area, the advisory was ignored
|
|
1681
|
+
let followed = 0;
|
|
1682
|
+
let ignored = 0;
|
|
1683
|
+
const evidence = [];
|
|
1684
|
+
|
|
1685
|
+
for (const adv of advisoryHookEntries) {
|
|
1686
|
+
// Look at next 3 entries after the advisory
|
|
1687
|
+
const lookAhead = allEntries.slice(adv.index + 1, adv.index + 4);
|
|
1688
|
+
|
|
1689
|
+
// Check if advisory suggested stopping/blocking and LLM continued same action
|
|
1690
|
+
const advisoryStr = typeof adv.advisory === 'string' ? adv.advisory : JSON.stringify(adv.advisory);
|
|
1691
|
+
const suggestsStop = /\b(stop|block|avoid|do not|don't|warning|caution)\b/i.test(advisoryStr);
|
|
1692
|
+
|
|
1693
|
+
if (suggestsStop) {
|
|
1694
|
+
// If the LLM's next action is a Write/Edit to the same area, advisory was ignored
|
|
1695
|
+
const nextWrite = lookAhead.find(e =>
|
|
1696
|
+
e.tool === 'Write' || e.tool === 'Edit' ||
|
|
1697
|
+
(e.details && (e.details.tool === 'Write' || e.details.tool === 'Edit'))
|
|
1698
|
+
);
|
|
1699
|
+
if (nextWrite) {
|
|
1700
|
+
ignored++;
|
|
1701
|
+
if (evidence.length < 3) {
|
|
1702
|
+
const time = adv.ts ? ` at ${adv.ts.substring(11, 16)}` : '';
|
|
1703
|
+
evidence.push(`${adv.hook} advisory${time} suggested caution but LLM proceeded with write`);
|
|
1704
|
+
}
|
|
1705
|
+
} else {
|
|
1706
|
+
followed++;
|
|
1707
|
+
}
|
|
1708
|
+
} else {
|
|
1709
|
+
// Non-blocking advisory — assume followed unless we see contradictory evidence
|
|
1710
|
+
followed++;
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
const total = followed + ignored;
|
|
1715
|
+
const complianceRate = total > 0 ? Math.round((followed / total) * 100) : 100;
|
|
1716
|
+
|
|
1717
|
+
evidence.unshift(`Hook advisories: ${total} total, ${followed} followed, ${ignored} ignored (${complianceRate}% compliance)`);
|
|
1718
|
+
|
|
1719
|
+
let status;
|
|
1720
|
+
if (complianceRate >= 70) {
|
|
1721
|
+
status = 'pass';
|
|
1722
|
+
} else {
|
|
1723
|
+
status = 'warn';
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1726
|
+
return {
|
|
1727
|
+
status,
|
|
1728
|
+
evidence,
|
|
1729
|
+
message: `BC-13: Hook output effectiveness: ${complianceRate}% of advisories followed (${followed}/${total})`
|
|
1730
|
+
};
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
// ---------------------------------------------------------------------------
|
|
1734
|
+
// BC-14: Agent Scope Compliance
|
|
1735
|
+
// ---------------------------------------------------------------------------
|
|
1736
|
+
|
|
1737
|
+
/**
|
|
1738
|
+
* BC-14: Check that agents only modified files listed in their PLAN.md files_modified.
|
|
1739
|
+
* Compares SUMMARY.md key_files against PLAN.md files_modified for each phase.
|
|
1740
|
+
*
|
|
1741
|
+
* @param {string} planningDir - Path to .planning/
|
|
1742
|
+
* @param {Object} [_config] - Config object (unused, for API consistency)
|
|
1743
|
+
* @returns {{ status: string, evidence: Array<string>, message: string }}
|
|
1744
|
+
*/
|
|
1745
|
+
function checkAgentScopeCompliance(planningDir, _config) {
|
|
1746
|
+
const phasesDir = path.join(planningDir, 'phases');
|
|
1747
|
+
if (!fs.existsSync(phasesDir)) {
|
|
1748
|
+
return {
|
|
1749
|
+
status: 'info',
|
|
1750
|
+
evidence: ['No phases directory found'],
|
|
1751
|
+
message: 'BC-14: No phases directory — cannot assess agent scope compliance'
|
|
1752
|
+
};
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
let phaseDirs;
|
|
1756
|
+
try {
|
|
1757
|
+
phaseDirs = fs.readdirSync(phasesDir, { withFileTypes: true })
|
|
1758
|
+
.filter(d => d.isDirectory())
|
|
1759
|
+
.map(d => d.name);
|
|
1760
|
+
} catch (_e) {
|
|
1761
|
+
return {
|
|
1762
|
+
status: 'info',
|
|
1763
|
+
evidence: ['Could not read phases directory'],
|
|
1764
|
+
message: 'BC-14: Could not read phases directory'
|
|
1765
|
+
};
|
|
1766
|
+
}
|
|
1767
|
+
|
|
1768
|
+
const evidence = [];
|
|
1769
|
+
let totalPlans = 0;
|
|
1770
|
+
let outOfScope = 0;
|
|
1771
|
+
|
|
1772
|
+
for (const phaseDir of phaseDirs) {
|
|
1773
|
+
const phaseFullPath = path.join(phasesDir, phaseDir);
|
|
1774
|
+
|
|
1775
|
+
// Find PLAN files
|
|
1776
|
+
let files;
|
|
1777
|
+
try {
|
|
1778
|
+
files = fs.readdirSync(phaseFullPath).filter(f => /^PLAN.*\.md$/i.test(f));
|
|
1779
|
+
} catch (_e) {
|
|
1780
|
+
continue;
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
for (const planFile of files) {
|
|
1784
|
+
// Extract plan ID from filename (e.g., PLAN-01.md -> 01)
|
|
1785
|
+
const planIdMatch = planFile.match(/PLAN-?(\d+)/i);
|
|
1786
|
+
if (!planIdMatch) continue;
|
|
1787
|
+
const planId = planIdMatch[1];
|
|
1788
|
+
|
|
1789
|
+
// Read PLAN file for files_modified
|
|
1790
|
+
let planContent;
|
|
1791
|
+
try {
|
|
1792
|
+
planContent = fs.readFileSync(path.join(phaseFullPath, planFile), 'utf8');
|
|
1793
|
+
} catch (_e) {
|
|
1794
|
+
continue;
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1797
|
+
// Extract files_modified from YAML frontmatter
|
|
1798
|
+
const fmMatch = planContent.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
1799
|
+
if (!fmMatch) continue;
|
|
1800
|
+
const fm = fmMatch[1];
|
|
1801
|
+
|
|
1802
|
+
const plannedFiles = [];
|
|
1803
|
+
const fmLines = fm.split(/\r?\n/);
|
|
1804
|
+
let inFilesModified = false;
|
|
1805
|
+
for (const line of fmLines) {
|
|
1806
|
+
if (/^\s*files_modified\s*:/.test(line)) {
|
|
1807
|
+
inFilesModified = true;
|
|
1808
|
+
continue;
|
|
1809
|
+
}
|
|
1810
|
+
if (inFilesModified) {
|
|
1811
|
+
if (/^\s*-\s+"?(.+?)"?\s*$/.test(line)) {
|
|
1812
|
+
const fileMatch = line.match(/^\s*-\s+"?(.+?)"?\s*$/);
|
|
1813
|
+
if (fileMatch) plannedFiles.push(fileMatch[1].trim());
|
|
1814
|
+
} else if (/^\s*\w/.test(line)) {
|
|
1815
|
+
inFilesModified = false;
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
if (plannedFiles.length === 0) continue;
|
|
1821
|
+
|
|
1822
|
+
// Find matching SUMMARY file
|
|
1823
|
+
const summaryPattern = new RegExp(`SUMMARY.*${planId}.*\\.md$`, 'i');
|
|
1824
|
+
let summaryFiles;
|
|
1825
|
+
try {
|
|
1826
|
+
summaryFiles = fs.readdirSync(phaseFullPath).filter(f => summaryPattern.test(f));
|
|
1827
|
+
} catch (_e) {
|
|
1828
|
+
continue;
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
for (const summaryFile of summaryFiles) {
|
|
1832
|
+
let summaryContent;
|
|
1833
|
+
try {
|
|
1834
|
+
summaryContent = fs.readFileSync(path.join(phaseFullPath, summaryFile), 'utf8');
|
|
1835
|
+
} catch (_e) {
|
|
1836
|
+
continue;
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
// Extract key_files from SUMMARY frontmatter
|
|
1840
|
+
const sfmMatch = summaryContent.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
1841
|
+
if (!sfmMatch) continue;
|
|
1842
|
+
const sfm = sfmMatch[1];
|
|
1843
|
+
|
|
1844
|
+
const actualFiles = [];
|
|
1845
|
+
const sfmLines = sfm.split(/\r?\n/);
|
|
1846
|
+
let inKeyFiles = false;
|
|
1847
|
+
for (const line of sfmLines) {
|
|
1848
|
+
if (/^\s*key_files\s*:/.test(line)) {
|
|
1849
|
+
inKeyFiles = true;
|
|
1850
|
+
continue;
|
|
1851
|
+
}
|
|
1852
|
+
if (inKeyFiles) {
|
|
1853
|
+
if (/^\s*-\s+"?(.+?)"?\s*$/.test(line)) {
|
|
1854
|
+
const fileMatch = line.match(/^\s*-\s+"?(.+?)"?\s*$/);
|
|
1855
|
+
if (fileMatch) {
|
|
1856
|
+
// key_files may have ": description" suffix, strip it
|
|
1857
|
+
const filePath = fileMatch[1].split(':')[0].trim();
|
|
1858
|
+
actualFiles.push(filePath);
|
|
1859
|
+
}
|
|
1860
|
+
} else if (/^\s*\w/.test(line)) {
|
|
1861
|
+
inKeyFiles = false;
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1866
|
+
totalPlans++;
|
|
1867
|
+
|
|
1868
|
+
// Compare: any actualFiles not in plannedFiles?
|
|
1869
|
+
const outOfScopeFiles = actualFiles.filter(af =>
|
|
1870
|
+
!plannedFiles.some(pf => af.includes(pf) || pf.includes(af))
|
|
1871
|
+
);
|
|
1872
|
+
|
|
1873
|
+
if (outOfScopeFiles.length > 0) {
|
|
1874
|
+
outOfScope++;
|
|
1875
|
+
evidence.push(
|
|
1876
|
+
`${phaseDir}/${planFile}: ${outOfScopeFiles.length} file(s) modified outside plan scope: ${outOfScopeFiles.slice(0, 3).join(', ')}`
|
|
1877
|
+
);
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1883
|
+
if (totalPlans === 0) {
|
|
1884
|
+
return {
|
|
1885
|
+
status: 'info',
|
|
1886
|
+
evidence: ['No PLAN/SUMMARY pairs found to compare'],
|
|
1887
|
+
message: 'BC-14: No plan/summary pairs found — cannot assess scope compliance'
|
|
1888
|
+
};
|
|
1889
|
+
}
|
|
1890
|
+
|
|
1891
|
+
evidence.unshift(`Checked ${totalPlans} plan(s): ${outOfScope} had out-of-scope modifications`);
|
|
1892
|
+
|
|
1893
|
+
if (outOfScope > 0) {
|
|
1894
|
+
return {
|
|
1895
|
+
status: 'warn',
|
|
1896
|
+
evidence,
|
|
1897
|
+
message: `BC-14: ${outOfScope}/${totalPlans} plan(s) had files modified outside declared scope`
|
|
1898
|
+
};
|
|
1899
|
+
}
|
|
1900
|
+
|
|
1901
|
+
return {
|
|
1902
|
+
status: 'pass',
|
|
1903
|
+
evidence,
|
|
1904
|
+
message: `BC-14: All ${totalPlans} plan(s) stayed within declared file scope`
|
|
1905
|
+
};
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1908
|
+
// ---------------------------------------------------------------------------
|
|
1909
|
+
// BC-15: Agent Plan Adherence
|
|
1910
|
+
// ---------------------------------------------------------------------------
|
|
1911
|
+
|
|
1912
|
+
/**
|
|
1913
|
+
* BC-15: Check that executor task completion counts match planned task counts.
|
|
1914
|
+
* Compares SUMMARY.md tasks_completed against PLAN.md <task> block count.
|
|
1915
|
+
*
|
|
1916
|
+
* @param {string} planningDir - Path to .planning/
|
|
1917
|
+
* @param {Object} [_config] - Config object (unused, for API consistency)
|
|
1918
|
+
* @returns {{ status: string, evidence: Array<string>, message: string }}
|
|
1919
|
+
*/
|
|
1920
|
+
function checkAgentPlanAdherence(planningDir, _config) {
|
|
1921
|
+
const phasesDir = path.join(planningDir, 'phases');
|
|
1922
|
+
if (!fs.existsSync(phasesDir)) {
|
|
1923
|
+
return {
|
|
1924
|
+
status: 'info',
|
|
1925
|
+
evidence: ['No phases directory found'],
|
|
1926
|
+
message: 'BC-15: No phases directory — cannot assess plan adherence'
|
|
1927
|
+
};
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1930
|
+
let phaseDirs;
|
|
1931
|
+
try {
|
|
1932
|
+
phaseDirs = fs.readdirSync(phasesDir, { withFileTypes: true })
|
|
1933
|
+
.filter(d => d.isDirectory())
|
|
1934
|
+
.map(d => d.name);
|
|
1935
|
+
} catch (_e) {
|
|
1936
|
+
return {
|
|
1937
|
+
status: 'info',
|
|
1938
|
+
evidence: ['Could not read phases directory'],
|
|
1939
|
+
message: 'BC-15: Could not read phases directory'
|
|
1940
|
+
};
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1943
|
+
const evidence = [];
|
|
1944
|
+
let totalPlans = 0;
|
|
1945
|
+
let mismatches = 0;
|
|
1946
|
+
|
|
1947
|
+
for (const phaseDir of phaseDirs) {
|
|
1948
|
+
const phaseFullPath = path.join(phasesDir, phaseDir);
|
|
1949
|
+
|
|
1950
|
+
let files;
|
|
1951
|
+
try {
|
|
1952
|
+
files = fs.readdirSync(phaseFullPath).filter(f => /^PLAN.*\.md$/i.test(f));
|
|
1953
|
+
} catch (_e) {
|
|
1954
|
+
continue;
|
|
1955
|
+
}
|
|
1956
|
+
|
|
1957
|
+
for (const planFile of files) {
|
|
1958
|
+
const planIdMatch = planFile.match(/PLAN-?(\d+)/i);
|
|
1959
|
+
if (!planIdMatch) continue;
|
|
1960
|
+
const planId = planIdMatch[1];
|
|
1961
|
+
|
|
1962
|
+
// Count <task> blocks in PLAN
|
|
1963
|
+
let planContent;
|
|
1964
|
+
try {
|
|
1965
|
+
planContent = fs.readFileSync(path.join(phaseFullPath, planFile), 'utf8');
|
|
1966
|
+
} catch (_e) {
|
|
1967
|
+
continue;
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1970
|
+
const taskMatches = planContent.match(/<task\s/g);
|
|
1971
|
+
const plannedTasks = taskMatches ? taskMatches.length : 0;
|
|
1972
|
+
if (plannedTasks === 0) continue;
|
|
1973
|
+
|
|
1974
|
+
// Find matching SUMMARY
|
|
1975
|
+
const summaryPattern = new RegExp(`SUMMARY.*${planId}.*\\.md$`, 'i');
|
|
1976
|
+
let summaryFiles;
|
|
1977
|
+
try {
|
|
1978
|
+
summaryFiles = fs.readdirSync(phaseFullPath).filter(f => summaryPattern.test(f));
|
|
1979
|
+
} catch (_e) {
|
|
1980
|
+
continue;
|
|
1981
|
+
}
|
|
1982
|
+
|
|
1983
|
+
for (const summaryFile of summaryFiles) {
|
|
1984
|
+
let summaryContent;
|
|
1985
|
+
try {
|
|
1986
|
+
summaryContent = fs.readFileSync(path.join(phaseFullPath, summaryFile), 'utf8');
|
|
1987
|
+
} catch (_e) {
|
|
1988
|
+
continue;
|
|
1989
|
+
}
|
|
1990
|
+
|
|
1991
|
+
totalPlans++;
|
|
1992
|
+
|
|
1993
|
+
// Extract tasks_completed from SUMMARY frontmatter
|
|
1994
|
+
const fmMatch = summaryContent.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
1995
|
+
if (!fmMatch) {
|
|
1996
|
+
mismatches++;
|
|
1997
|
+
evidence.push(`${phaseDir}/${summaryFile}: no frontmatter — cannot determine tasks_completed`);
|
|
1998
|
+
continue;
|
|
1999
|
+
}
|
|
2000
|
+
|
|
2001
|
+
const fm = fmMatch[1];
|
|
2002
|
+
const completedMatch = fm.match(/tasks_completed\s*:\s*(\d+)/);
|
|
2003
|
+
const totalMatch = fm.match(/tasks_total\s*:\s*(\d+)/);
|
|
2004
|
+
|
|
2005
|
+
const completedTasks = completedMatch ? parseInt(completedMatch[1], 10) : null;
|
|
2006
|
+
const reportedTotal = totalMatch ? parseInt(totalMatch[1], 10) : null;
|
|
2007
|
+
|
|
2008
|
+
if (completedTasks === null) {
|
|
2009
|
+
// Check status field as fallback
|
|
2010
|
+
const statusMatch = fm.match(/status\s*:\s*["']?(\w+)/);
|
|
2011
|
+
const status = statusMatch ? statusMatch[1] : 'unknown';
|
|
2012
|
+
if (status === 'complete') {
|
|
2013
|
+
// Assume all tasks completed if status is complete
|
|
2014
|
+
continue;
|
|
2015
|
+
}
|
|
2016
|
+
mismatches++;
|
|
2017
|
+
evidence.push(`${phaseDir}/${summaryFile}: no tasks_completed field (status: ${status})`);
|
|
2018
|
+
continue;
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2021
|
+
// Check: planned == completed?
|
|
2022
|
+
if (completedTasks !== plannedTasks) {
|
|
2023
|
+
mismatches++;
|
|
2024
|
+
evidence.push(
|
|
2025
|
+
`${phaseDir}/${planFile}: planned ${plannedTasks} tasks, completed ${completedTasks}` +
|
|
2026
|
+
(reportedTotal ? ` (reported total: ${reportedTotal})` : '')
|
|
2027
|
+
);
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
2030
|
+
}
|
|
2031
|
+
}
|
|
2032
|
+
|
|
2033
|
+
if (totalPlans === 0) {
|
|
2034
|
+
return {
|
|
2035
|
+
status: 'info',
|
|
2036
|
+
evidence: ['No PLAN/SUMMARY pairs found to compare'],
|
|
2037
|
+
message: 'BC-15: No plan/summary pairs found — cannot assess plan adherence'
|
|
2038
|
+
};
|
|
2039
|
+
}
|
|
2040
|
+
|
|
2041
|
+
evidence.unshift(`Checked ${totalPlans} plan(s): ${mismatches} had task count mismatches`);
|
|
2042
|
+
|
|
2043
|
+
if (mismatches > 0) {
|
|
2044
|
+
return {
|
|
2045
|
+
status: 'warn',
|
|
2046
|
+
evidence,
|
|
2047
|
+
message: `BC-15: ${mismatches}/${totalPlans} plan(s) had task completion mismatches`
|
|
2048
|
+
};
|
|
2049
|
+
}
|
|
2050
|
+
|
|
2051
|
+
return {
|
|
2052
|
+
status: 'pass',
|
|
2053
|
+
evidence,
|
|
2054
|
+
message: `BC-15: All ${totalPlans} plan(s) completed their planned task counts`
|
|
2055
|
+
};
|
|
2056
|
+
}
|
|
2057
|
+
|
|
2058
|
+
// ---------------------------------------------------------------------------
|
|
2059
|
+
// Exports
|
|
2060
|
+
// ---------------------------------------------------------------------------
|
|
2061
|
+
|
|
2062
|
+
module.exports = {
|
|
2063
|
+
// Shared helpers
|
|
2064
|
+
getLogsDir,
|
|
2065
|
+
readJsonlFiles,
|
|
2066
|
+
readSessionEvents,
|
|
2067
|
+
readHookLogs,
|
|
2068
|
+
// BC-01
|
|
2069
|
+
checkSkillSequenceCompliance,
|
|
2070
|
+
// BC-02
|
|
2071
|
+
checkStateMachineTransitions,
|
|
2072
|
+
// BC-03
|
|
2073
|
+
checkPreConditionVerification,
|
|
2074
|
+
// BC-04
|
|
2075
|
+
checkPostConditionVerification,
|
|
2076
|
+
// BC-05
|
|
2077
|
+
checkOrchestratorBudgetDiscipline,
|
|
2078
|
+
// BC-06
|
|
2079
|
+
checkArtifactCreationOrder,
|
|
2080
|
+
// BC-07
|
|
2081
|
+
checkCriticalMarkerCompliance,
|
|
2082
|
+
// BC-08
|
|
2083
|
+
checkGateCompliance,
|
|
2084
|
+
// BC-09
|
|
2085
|
+
checkEnforceWorkflowAdvisory,
|
|
2086
|
+
// BC-10
|
|
2087
|
+
checkUnmanagedCommitDetection,
|
|
2088
|
+
// BC-11
|
|
2089
|
+
checkContextDelegationThreshold,
|
|
2090
|
+
// BC-12
|
|
2091
|
+
checkSkillSelfReadPrevention,
|
|
2092
|
+
// BC-13
|
|
2093
|
+
checkHookOutputEffectiveness,
|
|
2094
|
+
// BC-14
|
|
2095
|
+
checkAgentScopeCompliance,
|
|
2096
|
+
// BC-15
|
|
2097
|
+
checkAgentPlanAdherence,
|
|
2098
|
+
};
|