session-orchestrator 3.16.0
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/.claude-plugin/marketplace.json +29 -0
- package/.claude-plugin/plugin.json +18 -0
- package/.codex-plugin/agents/explorer.toml +14 -0
- package/.codex-plugin/agents/session-reviewer.toml +23 -0
- package/.codex-plugin/agents/wave-worker.toml +15 -0
- package/.codex-plugin/config.toml +20 -0
- package/.codex-plugin/plugin.json +37 -0
- package/.cursor/rules/000-session-orchestrator.mdc +73 -0
- package/.cursor/rules/010-session-workflow.mdc +170 -0
- package/.cursor/rules/020-quality-gates.mdc +128 -0
- package/.cursor/rules/030-wave-execution.mdc +216 -0
- package/.cursor/rules/040-discovery.mdc +242 -0
- package/.cursor/rules/050-plan.mdc +235 -0
- package/.cursor/rules/060-evolve.mdc +232 -0
- package/.cursor/rules/070-gitlab-ops.mdc +246 -0
- package/.cursor/rules/080-ecosystem-health.mdc +145 -0
- package/.mcp.json +8 -0
- package/CHANGELOG.md +1544 -0
- package/LICENSE +21 -0
- package/NOTICE +64 -0
- package/README.md +242 -0
- package/SECURITY.md +90 -0
- package/agents/AGENTS.md +136 -0
- package/agents/analyst.md +99 -0
- package/agents/architect-reviewer.md +93 -0
- package/agents/code-implementer.md +106 -0
- package/agents/db-specialist.md +104 -0
- package/agents/dialectic-deriver.md +139 -0
- package/agents/docs-writer.md +113 -0
- package/agents/eval-judge.md +146 -0
- package/agents/memory-proposal-collector.md +297 -0
- package/agents/qa-strategist.md +102 -0
- package/agents/schemas/analyst.schema.json +46 -0
- package/agents/schemas/architect-reviewer.schema.json +50 -0
- package/agents/schemas/code-implementer.schema.json +61 -0
- package/agents/schemas/db-specialist.schema.json +80 -0
- package/agents/schemas/docs-writer.schema.json +56 -0
- package/agents/schemas/persona-panel-sidecar.schema.json +245 -0
- package/agents/schemas/qa-strategist.schema.json +46 -0
- package/agents/schemas/security-reviewer.schema.json +86 -0
- package/agents/schemas/session-reviewer.schema.json +69 -0
- package/agents/schemas/test-writer.schema.json +69 -0
- package/agents/schemas/ui-developer.schema.json +90 -0
- package/agents/schemas/ux-evaluator.schema.json +51 -0
- package/agents/security-reviewer.md +236 -0
- package/agents/session-reviewer.md +201 -0
- package/agents/skill-applied-judge.md +122 -0
- package/agents/test-writer.md +123 -0
- package/agents/ui-developer.md +109 -0
- package/agents/ux-evaluator.md +161 -0
- package/assets/icon.svg +11 -0
- package/assets/og-card.png +0 -0
- package/assets/og-card.svg +47 -0
- package/commands/autopilot-multi.md +74 -0
- package/commands/autopilot.md +80 -0
- package/commands/bootstrap.md +56 -0
- package/commands/brainstorm.md +48 -0
- package/commands/close.md +24 -0
- package/commands/debug.md +36 -0
- package/commands/discovery.md +32 -0
- package/commands/dispatcher.md +59 -0
- package/commands/eval.md +28 -0
- package/commands/evolve.md +10 -0
- package/commands/go.md +41 -0
- package/commands/grill.md +45 -0
- package/commands/harness-audit.md +26 -0
- package/commands/memory-cleanup.md +25 -0
- package/commands/persona-panel.md +121 -0
- package/commands/plan.md +15 -0
- package/commands/portfolio.md +97 -0
- package/commands/reconcile.md +23 -0
- package/commands/repo-audit.md +24 -0
- package/commands/session.md +30 -0
- package/commands/spinout.md +15 -0
- package/commands/sunset-review.md +27 -0
- package/commands/templates-ack.md +96 -0
- package/commands/test.md +97 -0
- package/docs/README.md +105 -0
- package/docs/USER-GUIDE.md +1403 -0
- package/docs/ci-setup.md +81 -0
- package/docs/codex-setup.md +142 -0
- package/docs/components.md +74 -0
- package/docs/cursor-setup.md +104 -0
- package/docs/events-schema.md +81 -0
- package/docs/migration-v3.md +148 -0
- package/docs/owner-config-schema.md +154 -0
- package/docs/persona-panel.md +433 -0
- package/docs/pi-setup.md +115 -0
- package/docs/plugin-architecture-v3.md +296 -0
- package/docs/pm-skills-marketplace.md +114 -0
- package/docs/policy-cache-validation-2026-04-28.md +118 -0
- package/docs/rule-authoring.md +316 -0
- package/docs/session-config-reference.md +1439 -0
- package/docs/session-config-template.md +961 -0
- package/docs/vault-docs-architecture.md +297 -0
- package/hooks/_lib/lock-bootstrap.mjs +272 -0
- package/hooks/_lib/lock-reconcile.mjs +93 -0
- package/hooks/_lib/profile-gate.mjs +95 -0
- package/hooks/_lib/transcript-history.mjs +211 -0
- package/hooks/agent-teams-h3-test.sh +362 -0
- package/hooks/config-protection.mjs +0 -0
- package/hooks/cwd-change-restore.mjs +131 -0
- package/hooks/enforce-commands.mjs +179 -0
- package/hooks/enforce-scope.mjs +273 -0
- package/hooks/hooks-codex.json +60 -0
- package/hooks/hooks-cursor.json +15 -0
- package/hooks/hooks-pi.json +115 -0
- package/hooks/hooks.json +215 -0
- package/hooks/loop-guard.mjs +260 -0
- package/hooks/on-session-end.mjs +217 -0
- package/hooks/on-session-start.mjs +660 -0
- package/hooks/on-stop.mjs +294 -0
- package/hooks/operator-steer.mjs +64 -0
- package/hooks/post-edit-validate.mjs +225 -0
- package/hooks/post-subagent-discovery-validator.mjs +398 -0
- package/hooks/post-tool-batch-wave-signal.mjs +328 -0
- package/hooks/post-tool-failure-corrective-context.mjs +248 -0
- package/hooks/post-tooluse-frontend-slop.mjs +184 -0
- package/hooks/pre-bash-destructive-guard.mjs +515 -0
- package/hooks/pre-bash-memory-propose-audit.mjs +206 -0
- package/hooks/pre-bash-staging-fence.mjs +223 -0
- package/hooks/pre-bash-templates-first.mjs +404 -0
- package/hooks/run-node.sh +72 -0
- package/hooks/skill-invocation-telemetry.mjs +99 -0
- package/hooks/subagent-telemetry.mjs +249 -0
- package/hooks/wave-scope-commit-guard.mjs +191 -0
- package/monitors/monitors.json +14 -0
- package/output-styles/finding-report.md +48 -0
- package/output-styles/session-report.md +53 -0
- package/output-styles/wave-summary.md +38 -0
- package/package.json +94 -0
- package/pi/extensions/session-orchestrator.ts +25 -0
- package/pi/prompts/autopilot-multi.md +12 -0
- package/pi/prompts/autopilot.md +12 -0
- package/pi/prompts/bootstrap.md +12 -0
- package/pi/prompts/brainstorm.md +12 -0
- package/pi/prompts/close.md +11 -0
- package/pi/prompts/debug.md +12 -0
- package/pi/prompts/discovery.md +12 -0
- package/pi/prompts/dispatcher.md +12 -0
- package/pi/prompts/eval.md +12 -0
- package/pi/prompts/evolve.md +12 -0
- package/pi/prompts/go.md +12 -0
- package/pi/prompts/grill.md +12 -0
- package/pi/prompts/harness-audit.md +12 -0
- package/pi/prompts/memory-cleanup.md +12 -0
- package/pi/prompts/persona-panel.md +12 -0
- package/pi/prompts/plan.md +12 -0
- package/pi/prompts/portfolio.md +12 -0
- package/pi/prompts/reconcile.md +12 -0
- package/pi/prompts/repo-audit.md +12 -0
- package/pi/prompts/session.md +12 -0
- package/pi/prompts/spinout.md +12 -0
- package/pi/prompts/sunset-review.md +12 -0
- package/pi/prompts/templates-ack.md +12 -0
- package/pi/prompts/test.md +12 -0
- package/rules/_index.md +51 -0
- package/rules/always-on/commit-discipline.md +26 -0
- package/rules/always-on/npm-quality-gates.md +26 -0
- package/rules/always-on/parallel-sessions.md +43 -0
- package/rules/opt-in-domain/prompt-caching.md +270 -0
- package/rules/opt-in-stack/backend-data.md +188 -0
- package/rules/opt-in-stack/backend.md +390 -0
- package/rules/opt-in-stack/frontend.md +98 -0
- package/rules/opt-in-stack/security-web.md +194 -0
- package/rules/opt-in-stack/swift.md +65 -0
- package/scripts/archive-closed-prds.mjs +416 -0
- package/scripts/autopilot-multi.mjs +802 -0
- package/scripts/autopilot.mjs +383 -0
- package/scripts/backfill-abandoned-sessions.mjs +265 -0
- package/scripts/backfill-learnings-expires.mjs +196 -0
- package/scripts/backfill-learnings.mjs +203 -0
- package/scripts/backfill-sessions.mjs +282 -0
- package/scripts/check-doc-consistency.sh +279 -0
- package/scripts/check-package-manager.mjs +445 -0
- package/scripts/ci/assert-vitest-green.mjs +267 -0
- package/scripts/codex-install.mjs +435 -0
- package/scripts/compute-grounding-injection.sh +186 -0
- package/scripts/cursor-install.mjs +113 -0
- package/scripts/dialectic-deriver.mjs +573 -0
- package/scripts/emit-event.mjs +160 -0
- package/scripts/emit-session.mjs +212 -0
- package/scripts/eval-session.mjs +262 -0
- package/scripts/export-hw-learnings.mjs +437 -0
- package/scripts/gc-stale-worktrees.mjs +666 -0
- package/scripts/generate-pi-prompts.mjs +127 -0
- package/scripts/harness-audit.mjs +287 -0
- package/scripts/lib/agent-frontmatter.mjs +266 -0
- package/scripts/lib/agent-output-schema.mjs +166 -0
- package/scripts/lib/agent-status.mjs +303 -0
- package/scripts/lib/ajv-loader.mjs +34 -0
- package/scripts/lib/auto-dialectic.mjs +382 -0
- package/scripts/lib/auto-dream.mjs +471 -0
- package/scripts/lib/autonomy/suitability.mjs +212 -0
- package/scripts/lib/autopilot/dep-graph.mjs +417 -0
- package/scripts/lib/autopilot/durable-telemetry.mjs +121 -0
- package/scripts/lib/autopilot/flags.mjs +104 -0
- package/scripts/lib/autopilot/kill-switches.mjs +174 -0
- package/scripts/lib/autopilot/loop.mjs +320 -0
- package/scripts/lib/autopilot/mr-draft.mjs +520 -0
- package/scripts/lib/autopilot/multi-killswitch.mjs +184 -0
- package/scripts/lib/autopilot/recent-runs.mjs +106 -0
- package/scripts/lib/autopilot/stall-sampler.mjs +97 -0
- package/scripts/lib/autopilot/telemetry.mjs +224 -0
- package/scripts/lib/autopilot/worktree-pipeline.mjs +605 -0
- package/scripts/lib/autopilot-telemetry.mjs +11 -0
- package/scripts/lib/autopilot.mjs +39 -0
- package/scripts/lib/backlog-scan.mjs +179 -0
- package/scripts/lib/bootstrap-lock-freshness.mjs +260 -0
- package/scripts/lib/bootstrap-lock-refresh.mjs +186 -0
- package/scripts/lib/build-live-signals.mjs +150 -0
- package/scripts/lib/ci-status-banner.mjs +425 -0
- package/scripts/lib/claude-md-budget-lint.mjs +246 -0
- package/scripts/lib/cli-flags.mjs +158 -0
- package/scripts/lib/codex/plugin-contract.mjs +610 -0
- package/scripts/lib/cold-start-detector.mjs +240 -0
- package/scripts/lib/command-blocker.mjs +458 -0
- package/scripts/lib/common.mjs +333 -0
- package/scripts/lib/config/auto-dream.mjs +77 -0
- package/scripts/lib/config/block-header.mjs +94 -0
- package/scripts/lib/config/broken-window.mjs +114 -0
- package/scripts/lib/config/coercers.mjs +248 -0
- package/scripts/lib/config/cold-start.mjs +92 -0
- package/scripts/lib/config/config-protection.mjs +120 -0
- package/scripts/lib/config/cross-repo.mjs +104 -0
- package/scripts/lib/config/custom-phases.mjs +213 -0
- package/scripts/lib/config/dialectic.mjs +92 -0
- package/scripts/lib/config/discovery-validator.mjs +75 -0
- package/scripts/lib/config/dispatcher-autonomy-capture.mjs +240 -0
- package/scripts/lib/config/dispatcher-autonomy.mjs +152 -0
- package/scripts/lib/config/docs-orchestrator.mjs +90 -0
- package/scripts/lib/config/docs-staleness.mjs +96 -0
- package/scripts/lib/config/drift-check.mjs +155 -0
- package/scripts/lib/config/eval.mjs +130 -0
- package/scripts/lib/config/events-rotation.mjs +74 -0
- package/scripts/lib/config/evolve.mjs +308 -0
- package/scripts/lib/config/frontend-slop-hook.mjs +104 -0
- package/scripts/lib/config/gitlab-portfolio.mjs +150 -0
- package/scripts/lib/config/handover-gate.mjs +106 -0
- package/scripts/lib/config/host-paths.mjs +76 -0
- package/scripts/lib/config/io.mjs +54 -0
- package/scripts/lib/config/loop-guard.mjs +117 -0
- package/scripts/lib/config/memory.mjs +150 -0
- package/scripts/lib/config/persona-gate-wave.mjs +258 -0
- package/scripts/lib/config/reconcile.mjs +205 -0
- package/scripts/lib/config/section-extractor.mjs +100 -0
- package/scripts/lib/config/skill-evolution.mjs +112 -0
- package/scripts/lib/config/slopcheck.mjs +99 -0
- package/scripts/lib/config/state-md-lock.mjs +83 -0
- package/scripts/lib/config/templates-first.mjs +94 -0
- package/scripts/lib/config/test.mjs +113 -0
- package/scripts/lib/config/vault-integration.mjs +201 -0
- package/scripts/lib/config/vault-mirror-quality.mjs +99 -0
- package/scripts/lib/config/vault-staleness.mjs +84 -0
- package/scripts/lib/config/vault-sync.mjs +96 -0
- package/scripts/lib/config/verification-auto-fix.mjs +84 -0
- package/scripts/lib/config/wave-reviewers.mjs +133 -0
- package/scripts/lib/config-schema.mjs +345 -0
- package/scripts/lib/config.mjs +474 -0
- package/scripts/lib/convergence-monitor.mjs +389 -0
- package/scripts/lib/coordinator-snapshot.mjs +371 -0
- package/scripts/lib/crypto-digest-utils.mjs +91 -0
- package/scripts/lib/discovery/helpers.mjs +127 -0
- package/scripts/lib/discovery/triage-state.mjs +279 -0
- package/scripts/lib/dispatcher/cli.mjs +257 -0
- package/scripts/lib/dispatcher/enumerate.mjs +243 -0
- package/scripts/lib/dispatcher/rank.mjs +363 -0
- package/scripts/lib/ecosystem-health.mjs +224 -0
- package/scripts/lib/ecosystem-wizard/ci-detector.mjs +18 -0
- package/scripts/lib/ecosystem-wizard/config-parser.mjs +54 -0
- package/scripts/lib/ecosystem-wizard/config-writer.mjs +287 -0
- package/scripts/lib/ecosystem-wizard/package-manager-detector.mjs +42 -0
- package/scripts/lib/ecosystem-wizard/wizard-prompt.mjs +246 -0
- package/scripts/lib/ecosystem-wizard.mjs +48 -0
- package/scripts/lib/env-check.mjs +89 -0
- package/scripts/lib/eval/engine.mjs +605 -0
- package/scripts/lib/eval/judge.mjs +433 -0
- package/scripts/lib/eval/report.mjs +367 -0
- package/scripts/lib/eval/schema.mjs +618 -0
- package/scripts/lib/eval/session-resolve.mjs +137 -0
- package/scripts/lib/eval/sink.mjs +77 -0
- package/scripts/lib/events-rotation.mjs +86 -0
- package/scripts/lib/events-schema.mjs +81 -0
- package/scripts/lib/events.mjs +80 -0
- package/scripts/lib/evolve/autonomy-verdict.mjs +461 -0
- package/scripts/lib/evolve/autopilot-effectiveness.mjs +293 -0
- package/scripts/lib/exclusivity-matrix.mjs +68 -0
- package/scripts/lib/fetch-baseline.mjs +311 -0
- package/scripts/lib/file-lock.mjs +512 -0
- package/scripts/lib/frontend-detect/detect.mjs +138 -0
- package/scripts/lib/frontend-detect/rules.mjs +295 -0
- package/scripts/lib/frontmatter-guard.mjs +241 -0
- package/scripts/lib/gates/echo-stub-detect.mjs +39 -0
- package/scripts/lib/gates/gate-baseline.mjs +42 -0
- package/scripts/lib/gates/gate-full.mjs +85 -0
- package/scripts/lib/gates/gate-helpers.mjs +231 -0
- package/scripts/lib/gates/gate-incremental.mjs +76 -0
- package/scripts/lib/gates/gate-per-file.mjs +55 -0
- package/scripts/lib/gitlab-ops/stale-mr-sweep.mjs +447 -0
- package/scripts/lib/gitlab-portfolio/aggregator.mjs +383 -0
- package/scripts/lib/gitlab-portfolio/cli.mjs +428 -0
- package/scripts/lib/gitlab-portfolio/markdown-writer.mjs +289 -0
- package/scripts/lib/gitlab-portfolio/vcs-detect.mjs +182 -0
- package/scripts/lib/handover-gate.mjs +222 -0
- package/scripts/lib/hardening.mjs +43 -0
- package/scripts/lib/hardware-pattern-detector.mjs +238 -0
- package/scripts/lib/harness-audit/categories/category1.mjs +123 -0
- package/scripts/lib/harness-audit/categories/category2.mjs +145 -0
- package/scripts/lib/harness-audit/categories/category3.mjs +143 -0
- package/scripts/lib/harness-audit/categories/category4.mjs +202 -0
- package/scripts/lib/harness-audit/categories/category5.mjs +152 -0
- package/scripts/lib/harness-audit/categories/category6.mjs +211 -0
- package/scripts/lib/harness-audit/categories/category7.mjs +125 -0
- package/scripts/lib/harness-audit/categories/category8.mjs +328 -0
- package/scripts/lib/harness-audit/categories/category9.mjs +294 -0
- package/scripts/lib/harness-audit/categories/helpers.mjs +165 -0
- package/scripts/lib/harness-audit/categories.mjs +19 -0
- package/scripts/lib/historical-guard.mjs +15 -0
- package/scripts/lib/host-identity.mjs +262 -0
- package/scripts/lib/instruction-budget-guard.mjs +332 -0
- package/scripts/lib/io.mjs +304 -0
- package/scripts/lib/issue-close-strip-labels.mjs +161 -0
- package/scripts/lib/language-mappers/README.md +57 -0
- package/scripts/lib/language-mappers/index.mjs +165 -0
- package/scripts/lib/language-mappers/markdown.mjs +149 -0
- package/scripts/lib/language-mappers/python.mjs +249 -0
- package/scripts/lib/language-mappers/swift.mjs +201 -0
- package/scripts/lib/language-mappers/typescript.mjs +433 -0
- package/scripts/lib/learnings/expiry-sweep.mjs +164 -0
- package/scripts/lib/learnings/filters.mjs +43 -0
- package/scripts/lib/learnings/io.mjs +255 -0
- package/scripts/lib/learnings/schema.mjs +518 -0
- package/scripts/lib/learnings/surface.mjs +207 -0
- package/scripts/lib/learnings.mjs +42 -0
- package/scripts/lib/lock-reaper.mjs +648 -0
- package/scripts/lib/locks/index.mjs +31 -0
- package/scripts/lib/locks/lock-body.mjs +62 -0
- package/scripts/lib/locks/staging-fence-lock.mjs +267 -0
- package/scripts/lib/locks/state-md-lock.mjs +351 -0
- package/scripts/lib/loop-readiness-banner.mjs +144 -0
- package/scripts/lib/memory-banner.mjs +478 -0
- package/scripts/lib/memory-cleanup/worktree-sweep.mjs +108 -0
- package/scripts/lib/memory-cleanup-stamp.mjs +56 -0
- package/scripts/lib/memory-paths.mjs +31 -0
- package/scripts/lib/memory-proposals/collector.mjs +334 -0
- package/scripts/lib/memory-proposals/schema.mjs +289 -0
- package/scripts/lib/memory-proposals/sink.mjs +507 -0
- package/scripts/lib/memory-proposals/store.mjs +441 -0
- package/scripts/lib/mission-status-schema.mjs +114 -0
- package/scripts/lib/mode-selector/alternatives.mjs +64 -0
- package/scripts/lib/mode-selector/constants.mjs +29 -0
- package/scripts/lib/mode-selector/context-pressure.mjs +157 -0
- package/scripts/lib/mode-selector/rationale.mjs +55 -0
- package/scripts/lib/mode-selector/scoring.mjs +221 -0
- package/scripts/lib/mode-selector-accuracy.mjs +121 -0
- package/scripts/lib/mode-selector.mjs +160 -0
- package/scripts/lib/multi-provider-build/providers.mjs +64 -0
- package/scripts/lib/multi-provider-build/templating.mjs +130 -0
- package/scripts/lib/named-baseline-resolver.mjs +233 -0
- package/scripts/lib/named-vault-resolver.mjs +433 -0
- package/scripts/lib/owner-config/coerce.mjs +29 -0
- package/scripts/lib/owner-config/constants.mjs +21 -0
- package/scripts/lib/owner-config/defaults.mjs +50 -0
- package/scripts/lib/owner-config/error.mjs +19 -0
- package/scripts/lib/owner-config/index.mjs +13 -0
- package/scripts/lib/owner-config/merge.mjs +52 -0
- package/scripts/lib/owner-config/validate.mjs +259 -0
- package/scripts/lib/owner-config-banner.mjs +126 -0
- package/scripts/lib/owner-config-loader.mjs +159 -0
- package/scripts/lib/owner-config.example.yaml +72 -0
- package/scripts/lib/owner-config.mjs +28 -0
- package/scripts/lib/owner-interview.mjs +243 -0
- package/scripts/lib/owner-yaml.mjs +571 -0
- package/scripts/lib/package-manager.mjs +160 -0
- package/scripts/lib/path-utils.mjs +217 -0
- package/scripts/lib/peer-cards/merger.mjs +310 -0
- package/scripts/lib/peer-cards/reader.mjs +125 -0
- package/scripts/lib/peer-cards/schema.mjs +230 -0
- package/scripts/lib/peer-cards/staleness-banner.mjs +86 -0
- package/scripts/lib/peer-cards/writer.mjs +138 -0
- package/scripts/lib/peer-discovery.mjs +200 -0
- package/scripts/lib/persona-panel/catalog-loader.mjs +577 -0
- package/scripts/lib/persona-panel/consolidator.mjs +370 -0
- package/scripts/lib/persona-panel/persona-runner.mjs +375 -0
- package/scripts/lib/persona-panel/threshold.mjs +130 -0
- package/scripts/lib/pi-hook-bridge.mjs +328 -0
- package/scripts/lib/platform.mjs +266 -0
- package/scripts/lib/playwright-driver/runner.mjs +297 -0
- package/scripts/lib/plugin-root.mjs +210 -0
- package/scripts/lib/pre-dispatch-check.mjs +126 -0
- package/scripts/lib/product-repo-detect.mjs +121 -0
- package/scripts/lib/profiles/registry.mjs +176 -0
- package/scripts/lib/profiles/schema.mjs +209 -0
- package/scripts/lib/qg-command-drift-banner.mjs +88 -0
- package/scripts/lib/quality-gate/diagnostics.mjs +92 -0
- package/scripts/lib/quality-gate.mjs +536 -0
- package/scripts/lib/quality-gates-cache.mjs +228 -0
- package/scripts/lib/quality-gates-policy.mjs +95 -0
- package/scripts/lib/recommendations-v0.mjs +156 -0
- package/scripts/lib/reconcile/eligibility.mjs +203 -0
- package/scripts/lib/reconcile/emitter.mjs +244 -0
- package/scripts/lib/reconcile/engine.mjs +412 -0
- package/scripts/lib/reconcile/idempotency.mjs +239 -0
- package/scripts/lib/reconcile/renderer.mjs +211 -0
- package/scripts/lib/reconcile/writer.mjs +293 -0
- package/scripts/lib/reconcile-nudge-banner.mjs +284 -0
- package/scripts/lib/resource-probe/evaluate.mjs +190 -0
- package/scripts/lib/resource-probe/parsers.mjs +181 -0
- package/scripts/lib/resource-probe/probe-platform.mjs +300 -0
- package/scripts/lib/resource-probe.mjs +95 -0
- package/scripts/lib/rule-loader.mjs +552 -0
- package/scripts/lib/rules-sync.mjs +439 -0
- package/scripts/lib/scope-gate.mjs +496 -0
- package/scripts/lib/session-close-backfill.mjs +539 -0
- package/scripts/lib/session-discovery.mjs +256 -0
- package/scripts/lib/session-end/phase-skip.mjs +357 -0
- package/scripts/lib/session-end/worktree-cleanup.mjs +112 -0
- package/scripts/lib/session-id.mjs +362 -0
- package/scripts/lib/session-lock.mjs +703 -0
- package/scripts/lib/session-registry.mjs +355 -0
- package/scripts/lib/session-schema/aliases.mjs +71 -0
- package/scripts/lib/session-schema/constants.mjs +115 -0
- package/scripts/lib/session-schema/normalizer.mjs +66 -0
- package/scripts/lib/session-schema/timestamps.mjs +64 -0
- package/scripts/lib/session-schema/validator.mjs +453 -0
- package/scripts/lib/session-schema.mjs +71 -0
- package/scripts/lib/session-token-rollup.mjs +137 -0
- package/scripts/lib/sessions-staleness-banner.mjs +247 -0
- package/scripts/lib/skill-evolution/blast-radius-classifier.mjs +114 -0
- package/scripts/lib/skill-evolution/candidate-intake.mjs +270 -0
- package/scripts/lib/skill-evolution/config-validation-gate.mjs +279 -0
- package/scripts/lib/skill-evolution/engine.mjs +719 -0
- package/scripts/lib/skill-evolution/idempotency.mjs +279 -0
- package/scripts/lib/skill-evolution/mr-opener.mjs +507 -0
- package/scripts/lib/skill-health/join.mjs +181 -0
- package/scripts/lib/skill-health/score.mjs +123 -0
- package/scripts/lib/skill-invocations-schema.mjs +214 -0
- package/scripts/lib/skill-judge.mjs +348 -0
- package/scripts/lib/skill-judgments-schema.mjs +264 -0
- package/scripts/lib/slopcheck.mjs +501 -0
- package/scripts/lib/soul-resolve.mjs +118 -0
- package/scripts/lib/spiral-carryover.mjs +495 -0
- package/scripts/lib/state-md/body-sections.mjs +851 -0
- package/scripts/lib/state-md/frontmatter-mutators.mjs +453 -0
- package/scripts/lib/state-md/mission-status.mjs +247 -0
- package/scripts/lib/state-md/recommendations.mjs +57 -0
- package/scripts/lib/state-md/yaml-parser.mjs +234 -0
- package/scripts/lib/state-md-peer-guard.mjs +232 -0
- package/scripts/lib/state-md.mjs +53 -0
- package/scripts/lib/subagents-schema.mjs +309 -0
- package/scripts/lib/sunset/walker.mjs +1192 -0
- package/scripts/lib/test-runner/artifact-paths.mjs +94 -0
- package/scripts/lib/test-runner/fingerprint.mjs +33 -0
- package/scripts/lib/test-runner/issue-reconcile.mjs +770 -0
- package/scripts/lib/tmux-layout/layouts.mjs +224 -0
- package/scripts/lib/tmux-layout/telemetry-stats.mjs +100 -0
- package/scripts/lib/tmux-layout/telemetry.mjs +88 -0
- package/scripts/lib/tmux-layout/tmux-shell.mjs +82 -0
- package/scripts/lib/tmux-layout/vcs-detector.mjs +88 -0
- package/scripts/lib/validate/check-agents.mjs +457 -0
- package/scripts/lib/validate/check-codex-plugin.mjs +37 -0
- package/scripts/lib/validate/check-commands.mjs +148 -0
- package/scripts/lib/validate/check-component-paths.mjs +112 -0
- package/scripts/lib/validate/check-dead-bridge.mjs +180 -0
- package/scripts/lib/validate/check-hooks-symmetry.mjs +258 -0
- package/scripts/lib/validate/check-json-files.mjs +116 -0
- package/scripts/lib/validate/check-owner-leakage.mjs +1011 -0
- package/scripts/lib/validate/check-path-utils-canary.mjs +175 -0
- package/scripts/lib/validate/check-peekaboo-driver-canary.mjs +201 -0
- package/scripts/lib/validate/check-pi-package.mjs +110 -0
- package/scripts/lib/validate/check-pi-prompts.mjs +43 -0
- package/scripts/lib/validate/check-playwright-mcp-canary.mjs +154 -0
- package/scripts/lib/validate/check-plugin-json.mjs +96 -0
- package/scripts/lib/validate/check-plugin-monitors.mjs +206 -0
- package/scripts/lib/validate/check-plugin-schema.mjs +137 -0
- package/scripts/lib/validate/check-rules.mjs +143 -0
- package/scripts/lib/validate/check-session-plan-routing.mjs +154 -0
- package/scripts/lib/validate/check-test-fixture-shapes.mjs +280 -0
- package/scripts/lib/validate/check-unicode-safety.mjs +533 -0
- package/scripts/lib/validate/confidential-names.mjs +169 -0
- package/scripts/lib/validate/dead-bridge-corpus.mjs +141 -0
- package/scripts/lib/validate/dead-bridge-detectors.mjs +568 -0
- package/scripts/lib/validate/tier-inference.mjs +100 -0
- package/scripts/lib/validate-vendored-rules.mjs +519 -0
- package/scripts/lib/vault-archive.mjs +404 -0
- package/scripts/lib/vault-backfill/glab.mjs +164 -0
- package/scripts/lib/vault-backfill/manifest.mjs +75 -0
- package/scripts/lib/vault-backfill/template.mjs +130 -0
- package/scripts/lib/vault-consolidate-fs.mjs +331 -0
- package/scripts/lib/vault-migration-rules.mjs +155 -0
- package/scripts/lib/vault-mirror/auto-commit.mjs +203 -0
- package/scripts/lib/vault-mirror/namespace.mjs +152 -0
- package/scripts/lib/vault-mirror/process.mjs +567 -0
- package/scripts/lib/vault-mirror/pseudonym-map.mjs +164 -0
- package/scripts/lib/vault-mirror/render-learnings.mjs +201 -0
- package/scripts/lib/vault-mirror/render-sessions.mjs +367 -0
- package/scripts/lib/vault-mirror/render.mjs +8 -0
- package/scripts/lib/vault-mirror/utils.mjs +217 -0
- package/scripts/lib/vault-relocation-rules.mjs +555 -0
- package/scripts/lib/vault-repo-backfill.mjs +235 -0
- package/scripts/lib/vault-staleness-banner.mjs +142 -0
- package/scripts/lib/vault-status/board-writer.mjs +769 -0
- package/scripts/lib/vault-status/narrative-mirror.mjs +544 -0
- package/scripts/lib/vault-sync-baseline.mjs +152 -0
- package/scripts/lib/wave-context.mjs +29 -0
- package/scripts/lib/wave-executor/pool.mjs +248 -0
- package/scripts/lib/wave-resource-gate.mjs +204 -0
- package/scripts/lib/wave-sizing.mjs +75 -0
- package/scripts/lib/webhook-url.mjs +105 -0
- package/scripts/lib/workspace.mjs +198 -0
- package/scripts/lib/worktree/constants.mjs +35 -0
- package/scripts/lib/worktree/index.mjs +17 -0
- package/scripts/lib/worktree/lifecycle.mjs +287 -0
- package/scripts/lib/worktree/listing.mjs +118 -0
- package/scripts/lib/worktree/meta.mjs +64 -0
- package/scripts/lib/worktree-freshness.mjs +313 -0
- package/scripts/lib/worktree.mjs +15 -0
- package/scripts/lifecycle-sim-v6.mjs +347 -0
- package/scripts/lock-reaper.mjs +185 -0
- package/scripts/mcp-server.sh +241 -0
- package/scripts/measure-policy-cache-effectiveness.mjs +427 -0
- package/scripts/memory-propose.mjs +464 -0
- package/scripts/migrate-cold-start-seed.mjs +404 -0
- package/scripts/migrate-learnings-jsonl.mjs +189 -0
- package/scripts/migrate-legacy-learnings.sh +61 -0
- package/scripts/migrate-sessions-jsonl.mjs +448 -0
- package/scripts/migrate-subagents-jsonl.mjs +196 -0
- package/scripts/migrate-vault-paths.mjs +796 -0
- package/scripts/parse-config.mjs +149 -0
- package/scripts/pi-install.mjs +117 -0
- package/scripts/print-applicable-rules.mjs +247 -0
- package/scripts/promote-vault-strict.mjs +496 -0
- package/scripts/relocate-vault-corpus.mjs +1178 -0
- package/scripts/run-migrate-v2-cross-repo.mjs +385 -0
- package/scripts/run-quality-gate.mjs +216 -0
- package/scripts/spikes/h3-agent-teams/preflight.sh +53 -0
- package/scripts/spikes/h3-agent-teams/run-h3.sh +112 -0
- package/scripts/spikes/h3-agent-teams/setup.sh +137 -0
- package/scripts/spikes/h3-agent-teams/toggle.sh +38 -0
- package/scripts/sweep-expired-learnings.mjs +135 -0
- package/scripts/sync-vault-schema.mjs +376 -0
- package/scripts/tests/fixtures/fetch-baseline/sample-rule.md +8 -0
- package/scripts/tmux-layout.mjs +245 -0
- package/scripts/token-audit.sh +191 -0
- package/scripts/typecheck.mjs +42 -0
- package/scripts/upload-social-preview.mjs +316 -0
- package/scripts/validate-config.mjs +46 -0
- package/scripts/validate-plugin-manifests.mjs +163 -0
- package/scripts/validate-plugin.mjs +264 -0
- package/scripts/validate-wave-scope.mjs +289 -0
- package/scripts/vault-backfill.mjs +404 -0
- package/scripts/vault-consolidate.mjs +596 -0
- package/scripts/vault-integration-watcher.mjs +394 -0
- package/scripts/vault-mirror.mjs +430 -0
- package/skills/_shared/bootstrap-gate.md +111 -0
- package/skills/_shared/config-reading.md +226 -0
- package/skills/_shared/instruction-file-resolution.md +79 -0
- package/skills/_shared/model-selection.md +64 -0
- package/skills/_shared/monitor-patterns.md +300 -0
- package/skills/_shared/parallel-aware-auq.md +121 -0
- package/skills/_shared/parallel-aware-preamble.md +185 -0
- package/skills/_shared/platform-tools.md +96 -0
- package/skills/_shared/state-ownership.md +221 -0
- package/skills/architecture/DEEPENING.md +37 -0
- package/skills/architecture/INTERFACE-DESIGN.md +44 -0
- package/skills/architecture/LANGUAGE.md +53 -0
- package/skills/architecture/SKILL.md +92 -0
- package/skills/autopilot/SKILL.md +419 -0
- package/skills/bootstrap/SKILL.md +592 -0
- package/skills/bootstrap/STATE.md.template +24 -0
- package/skills/bootstrap/_shared-template.md +243 -0
- package/skills/bootstrap/deep-template.md +659 -0
- package/skills/bootstrap/fast-template.md +251 -0
- package/skills/bootstrap/intensity-heuristic.md +80 -0
- package/skills/bootstrap/public-fallback.md +342 -0
- package/skills/bootstrap/standard-template.md +736 -0
- package/skills/bootstrap/templates/agents/project-code-review.md +18 -0
- package/skills/bootstrap/templates/agents/project-discovery.md +18 -0
- package/skills/bootstrap/templates/agents/project-quality-gate.md +18 -0
- package/skills/brainstorm/SKILL.md +268 -0
- package/skills/brainstorm/soul.md +49 -0
- package/skills/claude-md-drift-check/SKILL.md +186 -0
- package/skills/claude-md-drift-check/checker.mjs +1380 -0
- package/skills/claude-md-drift-check/checker.sh +37 -0
- package/skills/claude-md-drift-check/package.json +16 -0
- package/skills/convergence-monitoring/README.md +39 -0
- package/skills/convergence-monitoring/SIGNALS.md +246 -0
- package/skills/convergence-monitoring/SKILL.md +285 -0
- package/skills/daily/SKILL.md +222 -0
- package/skills/daily/generate.sh +92 -0
- package/skills/daily/templates/daily.md.tpl +36 -0
- package/skills/debug/SKILL.md +188 -0
- package/skills/debug/soul.md +35 -0
- package/skills/discovery/SKILL.md +567 -0
- package/skills/discovery/issue-templates.md +237 -0
- package/skills/discovery/probes/docs-staleness.mjs +195 -0
- package/skills/discovery/probes/frontend-slop.mjs +186 -0
- package/skills/discovery/probes/ssot-code-diff.mjs +310 -0
- package/skills/discovery/probes/supply-chain-slopcheck.mjs +440 -0
- package/skills/discovery/probes/vault-narrative-staleness.mjs +355 -0
- package/skills/discovery/probes/vault-staleness.mjs +272 -0
- package/skills/discovery/probes-arch.md +252 -0
- package/skills/discovery/probes-audit.md +95 -0
- package/skills/discovery/probes-code.md +329 -0
- package/skills/discovery/probes-docs.md +76 -0
- package/skills/discovery/probes-feature.md +150 -0
- package/skills/discovery/probes-infra.md +138 -0
- package/skills/discovery/probes-intro.md +25 -0
- package/skills/discovery/probes-session.md +495 -0
- package/skills/discovery/probes-supply-chain.md +94 -0
- package/skills/discovery/probes-ui.md +147 -0
- package/skills/discovery/probes-vault.md +64 -0
- package/skills/discovery/slop-patterns.md +115 -0
- package/skills/dispatcher/SKILL.md +173 -0
- package/skills/docs-orchestrator/SKILL.md +362 -0
- package/skills/docs-orchestrator/audience-mapping.md +140 -0
- package/skills/domain-model/ADR-FORMAT.md +47 -0
- package/skills/domain-model/CONTEXT-FORMAT.md +77 -0
- package/skills/domain-model/SKILL.md +85 -0
- package/skills/ecosystem-health/SKILL.md +119 -0
- package/skills/ecosystem-health/wizard.md +193 -0
- package/skills/eval/SKILL.md +293 -0
- package/skills/eval/rubric-v1.md +218 -0
- package/skills/evolve/SKILL.md +546 -0
- package/skills/frontmatter-guard/SKILL.md +126 -0
- package/skills/gitlab-ops/SKILL.md +368 -0
- package/skills/gitlab-portfolio/SKILL.md +196 -0
- package/skills/grill/SKILL.md +185 -0
- package/skills/grill/soul.md +55 -0
- package/skills/hook-development/SKILL.md +413 -0
- package/skills/mcp-builder/SKILL.md +260 -0
- package/skills/memory-cleanup/SKILL.md +310 -0
- package/skills/mode-selector/SKILL.md +226 -0
- package/skills/peekaboo-driver/SKILL.md +237 -0
- package/skills/peekaboo-driver/soul.md +32 -0
- package/skills/persona-panel/SKILL.md +365 -0
- package/skills/persona-panel/persona-format.md +205 -0
- package/skills/persona-panel/presets/designer-lens.md +87 -0
- package/skills/persona-panel/presets/engineer-lens.md +88 -0
- package/skills/persona-panel/presets/pm-lens.md +86 -0
- package/skills/plan/SKILL.md +496 -0
- package/skills/plan/mode-feature.md +141 -0
- package/skills/plan/mode-new.md +297 -0
- package/skills/plan/mode-retro.md +271 -0
- package/skills/plan/prd-feature-template.md +132 -0
- package/skills/plan/prd-full-template.md +151 -0
- package/skills/plan/prd-reviewer-prompt.md +103 -0
- package/skills/plan/retro-template.md +75 -0
- package/skills/plan/soul.md +62 -0
- package/skills/playwright-driver/SKILL.md +226 -0
- package/skills/playwright-driver/soul.md +30 -0
- package/skills/quality-gates/SKILL.md +212 -0
- package/skills/reconcile/SKILL.md +324 -0
- package/skills/repo-audit/SKILL.md +272 -0
- package/skills/session-end/SKILL.md +1044 -0
- package/skills/session-end/discovery-scan.md +37 -0
- package/skills/session-end/drift-operations.md +97 -0
- package/skills/session-end/learning-patterns.md +78 -0
- package/skills/session-end/metrics-collection.md +175 -0
- package/skills/session-end/phase-3-2-docs-verification.md +148 -0
- package/skills/session-end/phase-3-6-tail.md +344 -0
- package/skills/session-end/phase-3-7a-recommendations.md +86 -0
- package/skills/session-end/plan-verification.md +288 -0
- package/skills/session-end/session-metrics-write.md +223 -0
- package/skills/session-end/vault-operations.md +50 -0
- package/skills/session-end/verification-checklist.md +20 -0
- package/skills/session-plan/SKILL.md +554 -0
- package/skills/session-plan/wave-template.md +37 -0
- package/skills/session-start/SKILL.md +1043 -0
- package/skills/session-start/phase-2-5-docs-planning.md +119 -0
- package/skills/session-start/phase-4-5-resource-health.md +49 -0
- package/skills/session-start/phase-7-1-premise-check.md +47 -0
- package/skills/session-start/phase-7-5-mode-selector.md +237 -0
- package/skills/session-start/phase-8-5-express-path.md +61 -0
- package/skills/session-start/presentation-format.md +81 -0
- package/skills/session-start/soul.md +57 -0
- package/skills/skill-creator/SKILL.md +168 -0
- package/skills/spinout/SKILL.md +76 -0
- package/skills/sunset-review/SKILL.md +96 -0
- package/skills/test-runner/SKILL.md +362 -0
- package/skills/test-runner/rubric-v1.md +388 -0
- package/skills/test-runner/soul.md +46 -0
- package/skills/tmux-layout/SKILL.md +104 -0
- package/skills/ubiquitous-language/SKILL.md +97 -0
- package/skills/using-orchestrator/SKILL.md +144 -0
- package/skills/vault-mirror/SKILL.md +234 -0
- package/skills/vault-sync/SKILL.md +319 -0
- package/skills/vault-sync/package-lock.json +40 -0
- package/skills/vault-sync/package.json +11 -0
- package/skills/vault-sync/tests/fixtures/archive-test-vault/90-archive/bad-archived.md +8 -0
- package/skills/vault-sync/tests/fixtures/archive-test-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/archive-test-vault/live-note.md +8 -0
- package/skills/vault-sync/tests/fixtures/broken-frontmatter-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/broken-frontmatter-vault/bad-type.md +8 -0
- package/skills/vault-sync/tests/fixtures/broken-frontmatter-vault/good-note.md +8 -0
- package/skills/vault-sync/tests/fixtures/clean-vault/.obsidian/config.md +8 -0
- package/skills/vault-sync/tests/fixtures/clean-vault/01-projects/foo/projects-baseline.md +10 -0
- package/skills/vault-sync/tests/fixtures/clean-vault/03-daily/daily-2026-04-13.md +8 -0
- package/skills/vault-sync/tests/fixtures/clean-vault/README.md +3 -0
- package/skills/vault-sync/tests/fixtures/clean-vault/hello-world.md +11 -0
- package/skills/vault-sync/tests/fixtures/dangling-link-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/dangling-link-vault/has-dangling.md +9 -0
- package/skills/vault-sync/tests/fixtures/dangling-link-vault/real-target.md +8 -0
- package/skills/vault-sync/tests/fixtures/empty-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/missing-field-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/missing-field-vault/missing-id.md +7 -0
- package/skills/vault-sync/tests/fixtures/nested-tag-vault/03-daily/daily-2026-04-13.md +9 -0
- package/skills/vault-sync/tests/fixtures/nested-tag-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/nested-tag-vault/nested-tags-note.md +11 -0
- package/skills/vault-sync/tests/fixtures/no-frontmatter-vault/README.md +3 -0
- package/skills/vault-sync/tests/fixtures/no-frontmatter-vault/_MOC.md +3 -0
- package/skills/vault-sync/tests/fixtures/no-frontmatter-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/with-moc-vault/_MOC.md +11 -0
- package/skills/vault-sync/tests/fixtures/with-moc-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/with-moc-vault/hello-world.md +11 -0
- package/skills/vault-sync/tests/schema-drift.test.mjs +133 -0
- package/skills/vault-sync/validator.mjs +658 -0
- package/skills/vault-sync/validator.sh +55 -0
- package/skills/wave-executor/SKILL.md +496 -0
- package/skills/wave-executor/circuit-breaker.md +169 -0
- package/skills/wave-executor/wave-loop.md +1043 -0
- package/skills/write-executable-plan/SKILL.md +237 -0
- package/skills/write-executable-plan/plan-template.md +154 -0
- package/templates/_minimal/CLAUDE.md.tmpl +41 -0
- package/templates/_minimal/README.md.tmpl +15 -0
- package/templates/_minimal/gitignore.tmpl +47 -0
- package/templates/_shared/harte-regeln.md +16 -0
- package/templates/_shared/loop.md +90 -0
- package/templates/_shared/rules/parallel-sessions.md +77 -0
- package/templates/nextjs-minimal/README.md +30 -0
- package/templates/nextjs-minimal/app/layout.tsx +18 -0
- package/templates/nextjs-minimal/app/page.tsx +7 -0
- package/templates/nextjs-minimal/eslint.config.mjs +16 -0
- package/templates/nextjs-minimal/next.config.mjs +4 -0
- package/templates/nextjs-minimal/package.json +27 -0
- package/templates/nextjs-minimal/tsconfig.json +23 -0
- package/templates/node-minimal/README.md +33 -0
- package/templates/node-minimal/eslint.config.mjs +10 -0
- package/templates/node-minimal/package.json +21 -0
- package/templates/node-minimal/src/index.ts +1 -0
- package/templates/node-minimal/tests/sanity.test.ts +5 -0
- package/templates/node-minimal/tsconfig.json +17 -0
- package/templates/personas/README.md +150 -0
- package/templates/personas/accounting-compliance.v1.md +120 -0
- package/templates/personas/accounting-tax-advisor.v1.md +116 -0
- package/templates/personas/buyer-p1-cto.v1.md +125 -0
- package/templates/personas/buyer-p2-kanzlei.v1.md +134 -0
- package/templates/personas/buyer-p3-build.v1.md +130 -0
- package/templates/personas/buyer-p4-tech-veto.v1.md +130 -0
- package/templates/personas/buyer-p5-solo.v1.md +132 -0
- package/templates/personas/buyer-p6-ld.v1.md +130 -0
- package/templates/personas/klima-ai-expert.v1.md +114 -0
- package/templates/personas/klima-physicist.v1.md +117 -0
- package/templates/python-uv/README.md +28 -0
- package/templates/python-uv/pyproject.toml +32 -0
- package/templates/python-uv/src/__PROJECT_NAME__/__init__.py +0 -0
- package/templates/python-uv/src/__PROJECT_NAME__/main.py +6 -0
- package/templates/python-uv/tests/test_sanity.py +2 -0
- package/templates/static-html/README.md +19 -0
- package/templates/static-html/index.html +15 -0
- package/templates/static-html/script.js +1 -0
- package/templates/static-html/styles.css +26 -0
|
@@ -0,0 +1,719 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* engine.mjs — C2 auto-repair ORCHESTRATOR for the Skill Self-Evolution
|
|
3
|
+
* Foundation (Epic #643 → issue #647).
|
|
4
|
+
*
|
|
5
|
+
* This is the HEART of #647: it composes the five W2 leaf modules into a single
|
|
6
|
+
* repair engine that implements the gate-per-artifact-type decision matrix
|
|
7
|
+
* (autonomy × posture × gate × evidence). R5 blast-radius enforcement lives here
|
|
8
|
+
* — the matrix is the load-bearing deliverable, not the file-write mechanics.
|
|
9
|
+
*
|
|
10
|
+
* COMPOSITION (the five siblings, all DI-injectable via `opts`):
|
|
11
|
+
* - candidate-intake.mjs → extractCandidates (pure transform)
|
|
12
|
+
* - idempotency.mjs → mergeCandidates / markProcessed / isProcessed
|
|
13
|
+
* / loadCandidates (store I/O + supersession)
|
|
14
|
+
* - blast-radius-classifier.mjs→ classifyTarget (R5 posture/gate triple)
|
|
15
|
+
* - config-validation-gate.mjs → runConfigValidationGate (deterministic gate)
|
|
16
|
+
* - mr-opener.mjs → openRepairMr (MR/PR for prose targets)
|
|
17
|
+
*
|
|
18
|
+
* PIPELINE:
|
|
19
|
+
* extractCandidates(learnings, driftResult)
|
|
20
|
+
* → mergeCandidates (persist + supersede)
|
|
21
|
+
* → for each NON-processed candidate:
|
|
22
|
+
* classify → decide per matrix → act
|
|
23
|
+
*
|
|
24
|
+
* DECISION MATRIX (implemented EXACTLY — see the gherkins / matrix rows):
|
|
25
|
+
* floor = config['skill-evolution']['evidence-floor'] (default 0.5)
|
|
26
|
+
* evidenceOk = Number.isFinite(c.evidence) && c.evidence >= floor (fail-closed)
|
|
27
|
+
* posture = classifyTarget(target).posture
|
|
28
|
+
*
|
|
29
|
+
* 1. autonomy 'off' → advisory-only (ALWAYS) [R4,R5,R13,R14,R16]
|
|
30
|
+
* 2. posture 'always-mr' (plugin/local/unknown):
|
|
31
|
+
* autonomy 'advisory' | 'autonomous-gated' → open-mr [R1,R2,R3,R15]
|
|
32
|
+
* 3. posture 'autonomous-gated' (local-config):
|
|
33
|
+
* autonomy 'advisory' → advisory-only [R11,R12]
|
|
34
|
+
* autonomy 'autonomous-gated':
|
|
35
|
+
* gateGreen && evidenceOk → autonomous-apply [R6 — the ONLY one]
|
|
36
|
+
* else → open-mr (carries failing-gate ctx) [R7..R10,R17]
|
|
37
|
+
*
|
|
38
|
+
* INVARIANTS (W4 asserts these):
|
|
39
|
+
* - autonomous-apply happens in EXACTLY the R6 condition. Nowhere else.
|
|
40
|
+
* - autonomy:off ⇒ every outcome advisory-only.
|
|
41
|
+
* - plugin-skill / local-skill ⇒ outcome ∈ {open-mr, advisory-only} ONLY.
|
|
42
|
+
* - gate not green ⇒ no autonomous-apply.
|
|
43
|
+
* - evidence < floor (or missing/NaN) ⇒ no autonomous-apply.
|
|
44
|
+
* - empty candidate set ⇒ outcomes:[], summary all-zero, touches no disk.
|
|
45
|
+
*
|
|
46
|
+
* CONTRACT: NEVER throws. Per-candidate logic is wrapped so one failure becomes a
|
|
47
|
+
* `no-op` outcome (with detail) and the loop continues.
|
|
48
|
+
*
|
|
49
|
+
* Part of Epic #643 → issue #647 (C2 auto-repair engine).
|
|
50
|
+
*/
|
|
51
|
+
|
|
52
|
+
import { readFileSync, writeFileSync, renameSync } from 'node:fs';
|
|
53
|
+
import path from 'node:path';
|
|
54
|
+
import { randomBytes } from 'node:crypto';
|
|
55
|
+
|
|
56
|
+
import { extractCandidates as realExtractCandidates } from './candidate-intake.mjs';
|
|
57
|
+
import {
|
|
58
|
+
mergeCandidates as realMergeCandidates,
|
|
59
|
+
markProcessed as realMarkProcessed,
|
|
60
|
+
isProcessed as realIsProcessed,
|
|
61
|
+
} from './idempotency.mjs';
|
|
62
|
+
import { classifyTarget as realClassifyTarget } from './blast-radius-classifier.mjs';
|
|
63
|
+
import { runConfigValidationGate as realRunConfigValidationGate } from './config-validation-gate.mjs';
|
|
64
|
+
import { openRepairMr as realOpenRepairMr } from './mr-opener.mjs';
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @typedef {import('./candidate-intake.mjs').RepairCandidate} RepairCandidate
|
|
68
|
+
*/
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @typedef {'autonomous-apply'|'open-mr'|'advisory-only'|'no-op'} RepairDecision
|
|
72
|
+
*/
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* @typedef {Object} RepairOutcome
|
|
76
|
+
* @property {string} candidateId The candidate's idempotency id.
|
|
77
|
+
* @property {string} targetPath Repo-relative path the repair targets.
|
|
78
|
+
* @property {import('./blast-radius-classifier.mjs').TargetType} targetType
|
|
79
|
+
* The R5 blast-radius target type.
|
|
80
|
+
* @property {RepairDecision} decision The matrix decision applied.
|
|
81
|
+
* @property {string} detail Human-readable detail / reason for the decision.
|
|
82
|
+
*/
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* @typedef {Object} RepairSummary
|
|
86
|
+
* @property {number} autonomousApplied Count of `autonomous-apply` outcomes.
|
|
87
|
+
* @property {number} mrsOpened Count of `open-mr` outcomes.
|
|
88
|
+
* @property {number} advisories Count of `advisory-only` outcomes.
|
|
89
|
+
* @property {number} blocked Count of `no-op` outcomes (errors/skips).
|
|
90
|
+
* @property {number} total Count of all outcomes emitted.
|
|
91
|
+
*/
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* @typedef {Object} RepairEngineResult
|
|
95
|
+
* @property {RepairOutcome[]} outcomes
|
|
96
|
+
* @property {RepairSummary} summary
|
|
97
|
+
*/
|
|
98
|
+
|
|
99
|
+
const DEFAULT_EVIDENCE_FLOOR = 0.5;
|
|
100
|
+
const VALID_AUTONOMY = new Set(['off', 'advisory', 'autonomous-gated']);
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Default no-op logger. Replaced by `opts.log` when provided.
|
|
104
|
+
* @param {string} _level
|
|
105
|
+
* @param {string} _msg
|
|
106
|
+
* @returns {void}
|
|
107
|
+
*/
|
|
108
|
+
function noopLog(_level, _msg) {
|
|
109
|
+
/* intentionally empty — diagnostics are opt-in via opts.log */
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Read the `skill-evolution` sub-config from the parsed Session Config, applying
|
|
114
|
+
* conservative, fail-closed defaults. An absent / malformed block yields the
|
|
115
|
+
* safe default (`autonomy: 'off'`, floor 0.5, judge false).
|
|
116
|
+
*
|
|
117
|
+
* @param {Record<string, unknown>|null|undefined} config — full parsed Session Config.
|
|
118
|
+
* @returns {{ autonomy: 'off'|'advisory'|'autonomous-gated', evidenceFloor: number, judge: boolean }}
|
|
119
|
+
*/
|
|
120
|
+
function readSkillEvolutionConfig(config) {
|
|
121
|
+
const block =
|
|
122
|
+
config && typeof config === 'object' && typeof config['skill-evolution'] === 'object'
|
|
123
|
+
? /** @type {Record<string, unknown>} */ (config['skill-evolution'])
|
|
124
|
+
: {};
|
|
125
|
+
|
|
126
|
+
const rawAutonomy = block.autonomy;
|
|
127
|
+
const autonomy =
|
|
128
|
+
typeof rawAutonomy === 'string' && VALID_AUTONOMY.has(rawAutonomy)
|
|
129
|
+
? /** @type {'off'|'advisory'|'autonomous-gated'} */ (rawAutonomy)
|
|
130
|
+
: 'off'; // fail-closed default
|
|
131
|
+
|
|
132
|
+
const rawFloor = block['evidence-floor'];
|
|
133
|
+
// Coerce a stringified floor (e.g. an unparsed `evidence-floor: "0.9"`) so an
|
|
134
|
+
// operator's intended stricter gate is honoured instead of silently reverting
|
|
135
|
+
// to the default. null/undefined/empty/object → NaN → default (never 0).
|
|
136
|
+
const coercedFloor =
|
|
137
|
+
typeof rawFloor === 'number'
|
|
138
|
+
? rawFloor
|
|
139
|
+
: typeof rawFloor === 'string' && rawFloor.trim() !== ''
|
|
140
|
+
? Number(rawFloor)
|
|
141
|
+
: NaN;
|
|
142
|
+
const evidenceFloor =
|
|
143
|
+
Number.isFinite(coercedFloor) && coercedFloor >= 0 && coercedFloor <= 1
|
|
144
|
+
? coercedFloor
|
|
145
|
+
: DEFAULT_EVIDENCE_FLOOR;
|
|
146
|
+
|
|
147
|
+
const judge = block.judge === true;
|
|
148
|
+
|
|
149
|
+
return { autonomy, evidenceFloor, judge };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* The ONE supported autonomous-apply shape (Option A — re-derive from prose).
|
|
154
|
+
* Anchored to the `command-count` `proposed_change` template emitted by
|
|
155
|
+
* candidate-intake.mjs (`Update narrative '<N> commands' to actual <M>`).
|
|
156
|
+
* Both numbers are embedded in the prose string; capturing them avoids a
|
|
157
|
+
* candidate-schema change. Anything that does not match is OUT OF WHITELIST.
|
|
158
|
+
* @type {RegExp}
|
|
159
|
+
*/
|
|
160
|
+
const COMMAND_COUNT_SHAPE = /'(\d+)\s+commands'\s+to\s+actual\s+(\d+)/;
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* The string a not-yet-applied unsupported shape returns as its `reason`. The
|
|
164
|
+
* engine narrowly matches `/unsupported-shape/` against this to re-route to the
|
|
165
|
+
* MR path (rather than silently stamping a candidate it cannot apply).
|
|
166
|
+
*/
|
|
167
|
+
const UNSUPPORTED_SHAPE_REASON = 'unsupported-shape — out of whitelist';
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Parse the whitelisted command-count shape out of a candidate's PROSE
|
|
171
|
+
* `proposed_change`. Returns the claimed (narrative) and actual (filesystem)
|
|
172
|
+
* counts, or `null` when the prose is not the supported shape.
|
|
173
|
+
* @param {RepairCandidate} candidate
|
|
174
|
+
* @returns {{ claimedN: number, actualM: number } | null}
|
|
175
|
+
*/
|
|
176
|
+
function parseCommandCountShape(candidate) {
|
|
177
|
+
const proposed =
|
|
178
|
+
candidate && typeof candidate.proposed_change === 'string' ? candidate.proposed_change : '';
|
|
179
|
+
const m = COMMAND_COUNT_SHAPE.exec(proposed);
|
|
180
|
+
if (!m) return null;
|
|
181
|
+
return { claimedN: Number(m[1]), actualM: Number(m[2]) };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Resolve a candidate's `target_path` against `repoRoot` with a fail-closed
|
|
186
|
+
* repo-escape guard (mirrors mr-opener.mjs Step 3 + blast-radius-classifier.mjs).
|
|
187
|
+
* Returns the absolute path when the target is genuinely inside the repo, or
|
|
188
|
+
* `null` when it escapes / is the root itself / inputs are unusable.
|
|
189
|
+
* @param {string} repoRoot
|
|
190
|
+
* @param {string} targetPath
|
|
191
|
+
* @returns {string|null}
|
|
192
|
+
*/
|
|
193
|
+
function resolveInsideRepo(repoRoot, targetPath) {
|
|
194
|
+
if (typeof repoRoot !== 'string' || repoRoot.length === 0) return null;
|
|
195
|
+
if (typeof targetPath !== 'string' || targetPath.length === 0) return null;
|
|
196
|
+
const abs = path.resolve(repoRoot, targetPath);
|
|
197
|
+
const rel = path.relative(repoRoot, abs);
|
|
198
|
+
if (rel === '' || rel.startsWith('..') || path.isAbsolute(rel)) return null;
|
|
199
|
+
return abs;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Apply the command-count number swap to file content. Finds the FIRST narrative
|
|
204
|
+
* occurrence of `<claimedN>` immediately preceding a `commands` token (tolerating
|
|
205
|
+
* the `/commands` and `slash commands` narrative variants the drift-check matches
|
|
206
|
+
* via `/\b(\d+)\s+(?:\/)?commands?\b/`, and an optional wrapping single-quote),
|
|
207
|
+
* and replaces ONLY that leading number with `actualM`. Every other byte — and
|
|
208
|
+
* the `commands` token itself — is preserved verbatim.
|
|
209
|
+
*
|
|
210
|
+
* Returns the rewritten content, or `null` when no occurrence is found OR the
|
|
211
|
+
* narrative number already equals `actualM` (idempotent no-op).
|
|
212
|
+
* @param {string} content
|
|
213
|
+
* @param {number} claimedN
|
|
214
|
+
* @param {number} actualM
|
|
215
|
+
* @returns {string|null}
|
|
216
|
+
*/
|
|
217
|
+
function applyCommandCountSwap(content, claimedN, actualM) {
|
|
218
|
+
if (typeof content !== 'string' || content.length === 0) return null;
|
|
219
|
+
if (claimedN === actualM) return null; // already current — no-op
|
|
220
|
+
// Anchor on the claimed number followed by the (drift-check-shaped) commands
|
|
221
|
+
// token. \b before the number prevents matching a substring of a larger int
|
|
222
|
+
// (e.g. claimedN=8 must not match inside "18 commands"). The number group is
|
|
223
|
+
// the only segment we replace; the trailing token group is re-emitted as-is.
|
|
224
|
+
const swapRe = new RegExp(`\\b${claimedN}(\\s+(?:/|slash\\s+)?commands?\\b)`);
|
|
225
|
+
if (!swapRe.test(content)) return null; // claimed text not found — no-op
|
|
226
|
+
return content.replace(swapRe, `${actualM}$1`);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Default applyConfigRepair seam — the REAL autonomous mutator (issue #651).
|
|
231
|
+
*
|
|
232
|
+
* Implements the §C2-G2 contract for the whitelisted `command-count` drift
|
|
233
|
+
* shape ONLY. Every step is defense-in-depth + idempotent:
|
|
234
|
+
* 1. Repo-escape guard: resolve target inside `repoRoot`; escape ⇒ no-op.
|
|
235
|
+
* 2. Evidence-kind gate: only `filesystem-fact` (drift-check) candidates may
|
|
236
|
+
* auto-apply; `confidence` (learning) candidates carry attacker-influenceable
|
|
237
|
+
* prose and are routed to the MR path via `unsupported-shape`. (Security H1.)
|
|
238
|
+
* 3. Whitelist: parse the command-count shape from PROSE; miss ⇒
|
|
239
|
+
* `unsupported-shape` (the engine re-routes that to an MR — never stamps).
|
|
240
|
+
* 4. Mutation: swap the ONE narrative number, atomically (tmp + rename),
|
|
241
|
+
* content-level idempotent (already-current / text-absent ⇒ no write).
|
|
242
|
+
*
|
|
243
|
+
* NEVER throws — every error path degrades to `{ ok: true, applied: false }`.
|
|
244
|
+
* Does NOT stamp `processed_at`; the engine owns the G2 stamp.
|
|
245
|
+
*
|
|
246
|
+
* @param {RepairCandidate} candidate — the local-config candidate to apply.
|
|
247
|
+
* @param {string} [repoRoot] — repo root for path resolution (engine injects it).
|
|
248
|
+
* @returns {Promise<{ ok: boolean, applied: boolean, reason?: string }>}
|
|
249
|
+
*/
|
|
250
|
+
async function defaultApplyConfigRepair(candidate, repoRoot) {
|
|
251
|
+
// Step 1 — repo-escape guard (fail-closed; mirrors mr-opener.mjs L353-361).
|
|
252
|
+
const abs = resolveInsideRepo(repoRoot, candidate?.target_path);
|
|
253
|
+
if (abs === null) {
|
|
254
|
+
return { ok: true, applied: false, reason: 'target escapes repo' };
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Step 2 — autonomous-apply is restricted to filesystem-fact candidates (drift-check origin).
|
|
258
|
+
// Learning-sourced candidates (evidence_kind: 'confidence') carry attacker-influenceable prose
|
|
259
|
+
// in proposed_change; route them to the MR path, never auto-apply. (Security review H1, session-3.)
|
|
260
|
+
if (candidate?.evidence_kind !== 'filesystem-fact') {
|
|
261
|
+
return { ok: true, applied: false, reason: UNSUPPORTED_SHAPE_REASON };
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Step 3 — whitelist gate. Only the command-count shape may auto-apply.
|
|
265
|
+
const parsed = parseCommandCountShape(candidate);
|
|
266
|
+
if (parsed === null) {
|
|
267
|
+
return { ok: true, applied: false, reason: UNSUPPORTED_SHAPE_REASON };
|
|
268
|
+
}
|
|
269
|
+
const { claimedN, actualM } = parsed;
|
|
270
|
+
|
|
271
|
+
// Step 4 — scoped, minimal, idempotent mutation (atomic write-back).
|
|
272
|
+
let content;
|
|
273
|
+
try {
|
|
274
|
+
content = readFileSync(abs, 'utf8');
|
|
275
|
+
} catch (err) {
|
|
276
|
+
return { ok: true, applied: false, reason: `read failed: ${err?.message ?? String(err)}` };
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const next = applyCommandCountSwap(content, claimedN, actualM);
|
|
280
|
+
if (next === null || next === content) {
|
|
281
|
+
return { ok: true, applied: false, reason: 'already-current (no-op)' };
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
try {
|
|
285
|
+
const dir = path.dirname(abs);
|
|
286
|
+
const tmpFile = path.join(dir, `.${path.basename(abs)}.${randomBytes(6).toString('hex')}.tmp`);
|
|
287
|
+
writeFileSync(tmpFile, next, { encoding: 'utf8' });
|
|
288
|
+
renameSync(tmpFile, abs);
|
|
289
|
+
} catch (err) {
|
|
290
|
+
return { ok: true, applied: false, reason: `write failed: ${err?.message ?? String(err)}` };
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return { ok: true, applied: true };
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Default buildDiff seam — derive a {@link RepairDiff} from a candidate for the
|
|
298
|
+
* MR-opener (issue #651). For the whitelisted `command-count` shape on a config
|
|
299
|
+
* target it computes the FULL rewritten file content (same one-line swap as the
|
|
300
|
+
* apply seam) so the MR-opener can write the change; otherwise — unsupported
|
|
301
|
+
* prose shape, read error, or escape — it degrades to a `{ raw }`-only describe
|
|
302
|
+
* MR (the MR-opener opens a description-only MR when `content` is absent).
|
|
303
|
+
*
|
|
304
|
+
* Synchronous and NEVER throws.
|
|
305
|
+
*
|
|
306
|
+
* @param {RepairCandidate} candidate
|
|
307
|
+
* @param {string} [repoRoot] — repo root for path resolution (engine injects it).
|
|
308
|
+
* @returns {{ content?: string, raw?: string }}
|
|
309
|
+
*/
|
|
310
|
+
function defaultBuildDiff(candidate, repoRoot) {
|
|
311
|
+
const proposed =
|
|
312
|
+
candidate && typeof candidate.proposed_change === 'string' ? candidate.proposed_change : '';
|
|
313
|
+
|
|
314
|
+
const abs = resolveInsideRepo(repoRoot, candidate?.target_path);
|
|
315
|
+
const parsed = parseCommandCountShape(candidate);
|
|
316
|
+
if (abs === null || parsed === null) {
|
|
317
|
+
return { raw: proposed };
|
|
318
|
+
}
|
|
319
|
+
const { claimedN, actualM } = parsed;
|
|
320
|
+
|
|
321
|
+
let content;
|
|
322
|
+
try {
|
|
323
|
+
content = readFileSync(abs, 'utf8');
|
|
324
|
+
} catch {
|
|
325
|
+
return { raw: proposed };
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const next = applyCommandCountSwap(content, claimedN, actualM);
|
|
329
|
+
if (next === null || next === content) {
|
|
330
|
+
// Already current / claimed text absent → nothing to rewrite; describe only.
|
|
331
|
+
return { raw: proposed };
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Synthesise a minimal unified-diff preview of the one changed line. The
|
|
335
|
+
// describe path (`raw`) names the exact old/new narrative lines for the MR body.
|
|
336
|
+
const oldLine = `${claimedN} commands`;
|
|
337
|
+
const newLine = `${actualM} commands`;
|
|
338
|
+
const rel = path.relative(repoRoot, abs).split(path.sep).join('/');
|
|
339
|
+
return {
|
|
340
|
+
content: next,
|
|
341
|
+
raw: `--- a/${rel}\n+++ b/${rel}\n-${oldLine}\n+${newLine}\n`,
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Decide the matrix outcome for a single classified candidate. Pure decision
|
|
347
|
+
* function over (autonomy, posture, gateGreen, evidenceOk) — no I/O. Returns the
|
|
348
|
+
* decision plus a short detail string. The CALLER performs the side effects
|
|
349
|
+
* (gate run, apply, MR open) — this only encodes the matrix branching for
|
|
350
|
+
* non-config postures. The config branch is handled in {@link decideAndAct}
|
|
351
|
+
* because it must run the gate before deciding.
|
|
352
|
+
*
|
|
353
|
+
* @param {'off'|'advisory'|'autonomous-gated'} autonomy
|
|
354
|
+
* @param {import('./blast-radius-classifier.mjs').Posture} posture
|
|
355
|
+
* @returns {{ decision: 'open-mr'|'advisory-only'|'config-branch', detail: string }}
|
|
356
|
+
*/
|
|
357
|
+
function decideNonConfig(autonomy, posture) {
|
|
358
|
+
// Row group 1 — autonomy off ⇒ advisory-only ALWAYS (every target type).
|
|
359
|
+
if (autonomy === 'off') {
|
|
360
|
+
return { decision: 'advisory-only', detail: 'autonomy off — advisory only (safe default)' };
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Row group 2 — prose targets (plugin-skill / local-skill / unknown) ⇒
|
|
364
|
+
// always-mr posture. MR is the floor; never autonomous. [R1,R2,R3,R15]
|
|
365
|
+
if (posture === 'always-mr') {
|
|
366
|
+
return { decision: 'open-mr', detail: 'always-mr posture — prose target routed through MR' };
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Otherwise this is the autonomous-gated (local-config) posture — the caller
|
|
370
|
+
// must run the gate to decide. Signalled via the 'config-branch' sentinel.
|
|
371
|
+
return { decision: 'config-branch', detail: '' };
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Run the matrix decision for one candidate and perform the corresponding side
|
|
376
|
+
* effect. NEVER throws — any failure degrades to a `no-op` outcome.
|
|
377
|
+
*
|
|
378
|
+
* @param {Object} ctx
|
|
379
|
+
* @param {RepairCandidate} ctx.candidate
|
|
380
|
+
* @param {string} ctx.repoRoot
|
|
381
|
+
* @param {{ autonomy: 'off'|'advisory'|'autonomous-gated', evidenceFloor: number }} ctx.se
|
|
382
|
+
* @param {boolean} ctx.dryRun
|
|
383
|
+
* @param {Object} ctx.seams — resolved DI seams.
|
|
384
|
+
* @returns {Promise<RepairOutcome>}
|
|
385
|
+
*/
|
|
386
|
+
async function decideAndAct({ candidate, repoRoot, se, dryRun, seams }) {
|
|
387
|
+
const { autonomy, evidenceFloor: floor } = se;
|
|
388
|
+
const candidateId = candidate.id;
|
|
389
|
+
const targetPath = candidate.target_path;
|
|
390
|
+
|
|
391
|
+
// --- R5 blast-radius classification --------------------------------------
|
|
392
|
+
const classification = seams.classifyTarget(targetPath, { repoRoot });
|
|
393
|
+
const { targetType, posture } = classification;
|
|
394
|
+
|
|
395
|
+
// --- evidence gate (fail-closed: missing / NaN ⇒ false) ------------------
|
|
396
|
+
const evidenceOk = Number.isFinite(candidate.evidence) && candidate.evidence >= floor;
|
|
397
|
+
|
|
398
|
+
// --- matrix branch for off / always-mr -----------------------------------
|
|
399
|
+
const branch = decideNonConfig(autonomy, posture);
|
|
400
|
+
if (branch.decision === 'advisory-only') {
|
|
401
|
+
return finishAdvisory({ candidate, candidateId, targetPath, targetType, dryRun, seams, detail: branch.detail });
|
|
402
|
+
}
|
|
403
|
+
if (branch.decision === 'open-mr') {
|
|
404
|
+
return finishOpenMr({
|
|
405
|
+
candidate, candidateId, targetPath, targetType, repoRoot, dryRun, seams,
|
|
406
|
+
detail: branch.detail,
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// --- config-branch: posture 'autonomous-gated' (local-config) ------------
|
|
411
|
+
// autonomy 'advisory' ⇒ advisory-only (never auto-applies/opens MR autonomously,
|
|
412
|
+
// even when gate+evidence would qualify). [R11,R12]
|
|
413
|
+
if (autonomy === 'advisory') {
|
|
414
|
+
return finishAdvisory({
|
|
415
|
+
candidate, candidateId, targetPath, targetType, dryRun, seams,
|
|
416
|
+
detail: 'advisory autonomy on local-config — advisory only (no autonomous action)',
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// autonomy 'autonomous-gated' on local-config — run the gate, then decide.
|
|
421
|
+
// Both branches of the try/catch assign these, so no initializer is needed.
|
|
422
|
+
let gateGreen;
|
|
423
|
+
let gateDetail;
|
|
424
|
+
try {
|
|
425
|
+
const gate = await seams.runConfigValidationGate({ repoRoot });
|
|
426
|
+
gateGreen = gate && gate.ok === true;
|
|
427
|
+
gateDetail = gateGreen ? 'config-validation gate green' : 'config-validation gate not green';
|
|
428
|
+
} catch (err) {
|
|
429
|
+
// Gate threw (a real gate never throws, but a mocked seam might). Fail-closed.
|
|
430
|
+
gateGreen = false;
|
|
431
|
+
gateDetail = `config-validation gate errored: ${err?.message ?? String(err)}`;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// R6 — the ONLY autonomous-apply row: gate green AND evidence ≥ floor.
|
|
435
|
+
if (gateGreen && evidenceOk) {
|
|
436
|
+
if (dryRun === true) {
|
|
437
|
+
// Dry-run: preview only — do NOT mutate or stamp.
|
|
438
|
+
return {
|
|
439
|
+
candidateId,
|
|
440
|
+
targetPath,
|
|
441
|
+
targetType,
|
|
442
|
+
decision: 'autonomous-apply',
|
|
443
|
+
detail: 'dry-run preview',
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
// Real autonomous apply: applyConfigRepair → markProcessed.
|
|
447
|
+
try {
|
|
448
|
+
const applyResult = await seams.applyConfigRepair(candidate, repoRoot);
|
|
449
|
+
const applied = applyResult && applyResult.applied === true;
|
|
450
|
+
const applyReason = typeof applyResult?.reason === 'string' ? applyResult.reason : '';
|
|
451
|
+
|
|
452
|
+
// Re-route NARROWLY: only when the apply was declined because the shape is
|
|
453
|
+
// OUT OF WHITELIST do we open an MR (so the change is not silently
|
|
454
|
+
// stamped-and-dropped). Every OTHER non-applied reason that WON'T change on
|
|
455
|
+
// retry (already-current, target escapes repo) keeps the existing
|
|
456
|
+
// stamp-and-return behaviour — and existing engine.test.mjs mocks (generic
|
|
457
|
+
// results without an `unsupported-shape` reason) are unaffected.
|
|
458
|
+
if (!applied && /unsupported-shape/.test(applyReason)) {
|
|
459
|
+
return finishOpenMr({
|
|
460
|
+
candidate, candidateId, targetPath, targetType, repoRoot, dryRun, seams,
|
|
461
|
+
detail: `apply declined (${applyReason}) — routed to MR`,
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// TRANSIENT I/O failure (read/write): do NOT stamp (so the drift retries on
|
|
466
|
+
// a future run once the FS issue clears) and do NOT count as an apply (so
|
|
467
|
+
// telemetry isn't inflated by a buried failure). Degrade to a `no-op` that
|
|
468
|
+
// surfaces the error reason. (#651 FIX 2 — silent-failure / telemetry-inflate)
|
|
469
|
+
if (!applied && /read failed|write failed/i.test(applyReason)) {
|
|
470
|
+
return {
|
|
471
|
+
candidateId,
|
|
472
|
+
targetPath,
|
|
473
|
+
targetType,
|
|
474
|
+
decision: 'no-op',
|
|
475
|
+
detail: `autonomous-apply aborted — ${applyReason} (not stamped; will retry)`,
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
const stamp = seams.markProcessed({ id: candidateId, repoRoot });
|
|
480
|
+
const stampOk = stamp && stamp.ok === true;
|
|
481
|
+
const detailParts = [
|
|
482
|
+
`gate green + evidence ${candidate.evidence} ≥ ${floor}`,
|
|
483
|
+
applied ? 'applied' : `apply deferred (${applyReason || 'seam stub'})`,
|
|
484
|
+
stampOk ? 'marked processed' : `markProcessed: ${stamp?.reason ?? 'unknown'}`,
|
|
485
|
+
];
|
|
486
|
+
return {
|
|
487
|
+
candidateId,
|
|
488
|
+
targetPath,
|
|
489
|
+
targetType,
|
|
490
|
+
decision: 'autonomous-apply',
|
|
491
|
+
detail: detailParts.join('; '),
|
|
492
|
+
};
|
|
493
|
+
} catch (err) {
|
|
494
|
+
// Apply/stamp threw — degrade to no-op so the loop continues.
|
|
495
|
+
return {
|
|
496
|
+
candidateId,
|
|
497
|
+
targetPath,
|
|
498
|
+
targetType,
|
|
499
|
+
decision: 'no-op',
|
|
500
|
+
detail: `autonomous-apply errored: ${err?.message ?? String(err)}`,
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// Else — gate fail OR evidence below floor OR gate un-evaluable ⇒ open-mr
|
|
506
|
+
// (fallback; the MR carries the failing-gate context). [R7,R8,R9,R10,R17]
|
|
507
|
+
const fallbackDetail = `fallback to MR — ${gateDetail}; evidence ${
|
|
508
|
+
evidenceOk ? `≥ ${floor}` : `below ${floor} (or missing)`
|
|
509
|
+
}`;
|
|
510
|
+
return finishOpenMr({
|
|
511
|
+
candidate, candidateId, targetPath, targetType, repoRoot, dryRun, seams,
|
|
512
|
+
detail: fallbackDetail,
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* Build an `advisory-only` outcome.
|
|
518
|
+
* @returns {RepairOutcome}
|
|
519
|
+
*/
|
|
520
|
+
function finishAdvisory({ candidateId, targetPath, targetType, detail }) {
|
|
521
|
+
return { candidateId, targetPath, targetType, decision: 'advisory-only', detail };
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* Open an MR for the candidate via the mr-opener seam, mapping its result to a
|
|
526
|
+
* matrix outcome. NEVER throws (the seam itself never throws, but we guard the
|
|
527
|
+
* call anyway). An MR opener `advisory`/`blocked` action maps to `advisory-only`
|
|
528
|
+
* / `no-op` respectively, with the opener's reason carried into `detail`.
|
|
529
|
+
*
|
|
530
|
+
* @returns {Promise<RepairOutcome>}
|
|
531
|
+
*/
|
|
532
|
+
async function finishOpenMr({ candidate, candidateId, targetPath, targetType, repoRoot, dryRun, seams, detail }) {
|
|
533
|
+
let diff;
|
|
534
|
+
try {
|
|
535
|
+
diff = seams.buildDiff(candidate, repoRoot);
|
|
536
|
+
} catch (err) {
|
|
537
|
+
return {
|
|
538
|
+
candidateId, targetPath, targetType,
|
|
539
|
+
decision: 'no-op',
|
|
540
|
+
detail: `buildDiff errored: ${err?.message ?? String(err)}`,
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
let result;
|
|
545
|
+
try {
|
|
546
|
+
result = await seams.openRepairMr(
|
|
547
|
+
{ candidate, diff, repoRoot, dryRun },
|
|
548
|
+
{ log: seams.log },
|
|
549
|
+
);
|
|
550
|
+
} catch (err) {
|
|
551
|
+
return {
|
|
552
|
+
candidateId, targetPath, targetType,
|
|
553
|
+
decision: 'no-op',
|
|
554
|
+
detail: `openRepairMr errored: ${err?.message ?? String(err)}`,
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
const action = result && typeof result.action === 'string' ? result.action : 'advisory';
|
|
559
|
+
const reason = result && typeof result.reason === 'string' ? result.reason : '';
|
|
560
|
+
const combined = reason ? `${detail} → ${reason}` : detail;
|
|
561
|
+
|
|
562
|
+
if (action === 'mr-opened') {
|
|
563
|
+
const url = result.mrUrl ? ` (${result.mrUrl})` : '';
|
|
564
|
+
return { candidateId, targetPath, targetType, decision: 'open-mr', detail: `${combined}${url}` };
|
|
565
|
+
}
|
|
566
|
+
if (action === 'advisory') {
|
|
567
|
+
return { candidateId, targetPath, targetType, decision: 'open-mr', detail: combined };
|
|
568
|
+
}
|
|
569
|
+
// action === 'blocked' (gate failure inside the opener) → no-op.
|
|
570
|
+
return { candidateId, targetPath, targetType, decision: 'no-op', detail: `blocked: ${combined}` };
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* Run the C2 auto-repair engine.
|
|
575
|
+
*
|
|
576
|
+
* Composes the five W2 leaf modules into the gate-per-artifact-type decision
|
|
577
|
+
* matrix. NEVER throws — per-candidate failures degrade to `no-op` outcomes.
|
|
578
|
+
*
|
|
579
|
+
* @param {Object} params
|
|
580
|
+
* @param {string} params.repoRoot — absolute path to the repo root.
|
|
581
|
+
* @param {Record<string, unknown>|null} [params.config] — full parsed Session Config
|
|
582
|
+
* (the engine reads `config['skill-evolution']`).
|
|
583
|
+
* @param {Array<Record<string, unknown>>} [params.learnings] — `/evolve` learning records.
|
|
584
|
+
* @param {{ status?: string, errors?: Array<Record<string, unknown>> }|null} [params.driftResult]
|
|
585
|
+
* claude-md-drift-check output.
|
|
586
|
+
* @param {boolean} [params.dryRun=false] — when true, no mutation/MR/stamp; previews only.
|
|
587
|
+
* @param {Object} [opts] — DI seams (all default to the real sibling functions).
|
|
588
|
+
* @param {typeof realExtractCandidates} [opts.extractCandidates]
|
|
589
|
+
* @param {typeof realMergeCandidates} [opts.mergeCandidates]
|
|
590
|
+
* @param {typeof realMarkProcessed} [opts.markProcessed]
|
|
591
|
+
* @param {typeof realIsProcessed} [opts.isProcessed]
|
|
592
|
+
* @param {typeof realClassifyTarget} [opts.classifyTarget]
|
|
593
|
+
* @param {typeof realRunConfigValidationGate} [opts.runConfigValidationGate]
|
|
594
|
+
* @param {typeof realOpenRepairMr} [opts.openRepairMr]
|
|
595
|
+
* @param {(candidate: RepairCandidate, repoRoot?: string) => Promise<{ok:boolean, applied:boolean, reason?:string}>} [opts.applyConfigRepair]
|
|
596
|
+
* @param {(candidate: RepairCandidate, repoRoot?: string) => { content?: string, raw?: string }} [opts.buildDiff]
|
|
597
|
+
* @param {(level: string, msg: string) => void} [opts.log]
|
|
598
|
+
* @returns {Promise<RepairEngineResult>}
|
|
599
|
+
*/
|
|
600
|
+
export async function runRepairEngine(
|
|
601
|
+
{ repoRoot, config, learnings = [], driftResult = null, dryRun = false } = {},
|
|
602
|
+
opts = {},
|
|
603
|
+
) {
|
|
604
|
+
// --- Resolve DI seams (real siblings as defaults) ------------------------
|
|
605
|
+
const seams = {
|
|
606
|
+
extractCandidates:
|
|
607
|
+
typeof opts.extractCandidates === 'function' ? opts.extractCandidates : realExtractCandidates,
|
|
608
|
+
mergeCandidates:
|
|
609
|
+
typeof opts.mergeCandidates === 'function' ? opts.mergeCandidates : realMergeCandidates,
|
|
610
|
+
markProcessed:
|
|
611
|
+
typeof opts.markProcessed === 'function' ? opts.markProcessed : realMarkProcessed,
|
|
612
|
+
isProcessed: typeof opts.isProcessed === 'function' ? opts.isProcessed : realIsProcessed,
|
|
613
|
+
classifyTarget:
|
|
614
|
+
typeof opts.classifyTarget === 'function' ? opts.classifyTarget : realClassifyTarget,
|
|
615
|
+
runConfigValidationGate:
|
|
616
|
+
typeof opts.runConfigValidationGate === 'function'
|
|
617
|
+
? opts.runConfigValidationGate
|
|
618
|
+
: realRunConfigValidationGate,
|
|
619
|
+
openRepairMr: typeof opts.openRepairMr === 'function' ? opts.openRepairMr : realOpenRepairMr,
|
|
620
|
+
applyConfigRepair:
|
|
621
|
+
typeof opts.applyConfigRepair === 'function' ? opts.applyConfigRepair : defaultApplyConfigRepair,
|
|
622
|
+
buildDiff: typeof opts.buildDiff === 'function' ? opts.buildDiff : defaultBuildDiff,
|
|
623
|
+
log: typeof opts.log === 'function' ? opts.log : noopLog,
|
|
624
|
+
};
|
|
625
|
+
|
|
626
|
+
const se = readSkillEvolutionConfig(config);
|
|
627
|
+
|
|
628
|
+
/** @type {RepairOutcome[]} */
|
|
629
|
+
const outcomes = [];
|
|
630
|
+
const summary = { autonomousApplied: 0, mrsOpened: 0, advisories: 0, blocked: 0, total: 0 };
|
|
631
|
+
|
|
632
|
+
// --- Pipeline step 1 — intake (pure transform) ---------------------------
|
|
633
|
+
let candidates;
|
|
634
|
+
try {
|
|
635
|
+
candidates = seams.extractCandidates({
|
|
636
|
+
learnings,
|
|
637
|
+
driftResult,
|
|
638
|
+
repoRoot,
|
|
639
|
+
evidenceFloor: se.evidenceFloor,
|
|
640
|
+
});
|
|
641
|
+
} catch (err) {
|
|
642
|
+
seams.log('error', `engine: extractCandidates threw — ${err?.message ?? err}`);
|
|
643
|
+
candidates = [];
|
|
644
|
+
}
|
|
645
|
+
if (!Array.isArray(candidates)) candidates = [];
|
|
646
|
+
|
|
647
|
+
// Empty candidate set ⇒ outcomes:[], summary all-zero, touches no disk.
|
|
648
|
+
if (candidates.length === 0) {
|
|
649
|
+
return { outcomes, summary };
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// --- Pipeline step 2 — merge (persist + supersede) -----------------------
|
|
653
|
+
try {
|
|
654
|
+
seams.mergeCandidates({ candidates, repoRoot });
|
|
655
|
+
} catch (err) {
|
|
656
|
+
seams.log('error', `engine: mergeCandidates threw — ${err?.message ?? err}`);
|
|
657
|
+
// Persistence failure is non-fatal: continue with the in-memory candidates.
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
// --- Pipeline step 3 — per-candidate classify → decide → act -------------
|
|
661
|
+
for (const candidate of candidates) {
|
|
662
|
+
// Guard a malformed candidate (no id / target_path) → no-op.
|
|
663
|
+
if (!candidate || typeof candidate.id !== 'string' || typeof candidate.target_path !== 'string') {
|
|
664
|
+
outcomes.push({
|
|
665
|
+
candidateId: candidate?.id ?? '(unknown)',
|
|
666
|
+
targetPath: candidate?.target_path ?? '(unknown)',
|
|
667
|
+
targetType: 'unknown',
|
|
668
|
+
decision: 'no-op',
|
|
669
|
+
detail: 'malformed candidate (missing id/target_path)',
|
|
670
|
+
});
|
|
671
|
+
continue;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
// Skip candidates already processed (idempotency). Both branches assign.
|
|
675
|
+
let processed;
|
|
676
|
+
try {
|
|
677
|
+
processed = seams.isProcessed({ id: candidate.id, repoRoot });
|
|
678
|
+
} catch (err) {
|
|
679
|
+
seams.log('error', `engine: isProcessed threw — ${err?.message ?? err}`);
|
|
680
|
+
processed = false;
|
|
681
|
+
}
|
|
682
|
+
if (processed) {
|
|
683
|
+
outcomes.push({
|
|
684
|
+
candidateId: candidate.id,
|
|
685
|
+
targetPath: candidate.target_path,
|
|
686
|
+
targetType: 'unknown',
|
|
687
|
+
decision: 'no-op',
|
|
688
|
+
detail: 'already processed (idempotent skip)',
|
|
689
|
+
});
|
|
690
|
+
continue;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
// Decide + act (never throws — degrades to no-op internally).
|
|
694
|
+
let outcome;
|
|
695
|
+
try {
|
|
696
|
+
outcome = await decideAndAct({ candidate, repoRoot, se, dryRun, seams });
|
|
697
|
+
} catch (err) {
|
|
698
|
+
outcome = {
|
|
699
|
+
candidateId: candidate.id,
|
|
700
|
+
targetPath: candidate.target_path,
|
|
701
|
+
targetType: 'unknown',
|
|
702
|
+
decision: 'no-op',
|
|
703
|
+
detail: `engine: decideAndAct threw — ${err?.message ?? String(err)}`,
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
outcomes.push(outcome);
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
// --- Summary tally -------------------------------------------------------
|
|
710
|
+
for (const o of outcomes) {
|
|
711
|
+
summary.total += 1;
|
|
712
|
+
if (o.decision === 'autonomous-apply') summary.autonomousApplied += 1;
|
|
713
|
+
else if (o.decision === 'open-mr') summary.mrsOpened += 1;
|
|
714
|
+
else if (o.decision === 'advisory-only') summary.advisories += 1;
|
|
715
|
+
else summary.blocked += 1; // 'no-op'
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
return { outcomes, summary };
|
|
719
|
+
}
|