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,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* quality-gates-cache.mjs — Baseline quality-gate result cache (#258).
|
|
3
|
+
*
|
|
4
|
+
* Purpose:
|
|
5
|
+
* Wave-executor Incremental quality checks run once per session but were
|
|
6
|
+
* previously re-executed after every implementation wave, even when the
|
|
7
|
+
* session's dependency tree and session-start ref hadn't changed. This
|
|
8
|
+
* module persists the session-start Baseline result and lets wave-executor
|
|
9
|
+
* skip Incremental when the cache is still valid and the working-tree
|
|
10
|
+
* diff remains narrow.
|
|
11
|
+
*
|
|
12
|
+
* INVARIANT: Full Gate at session-end AND at the Quality wave is NEVER skipped
|
|
13
|
+
* regardless of cache state. shouldSkipIncremental({ waveRole: 'Quality' })
|
|
14
|
+
* hard-returns skip=false (reason 'quality-wave-full-gate-mandate') BEFORE any
|
|
15
|
+
* cache logic runs; the module otherwise only short-circuits Incremental in the
|
|
16
|
+
* Impl waves of wave-executor.
|
|
17
|
+
*
|
|
18
|
+
* Storage:
|
|
19
|
+
* .orchestrator/metrics/baseline-results.jsonl (append-only JSONL, one
|
|
20
|
+
* record per session baseline run — consistent with sessions.jsonl,
|
|
21
|
+
* events.jsonl, learnings.jsonl, audit.jsonl precedent).
|
|
22
|
+
*
|
|
23
|
+
* Record schema (version 1):
|
|
24
|
+
* {
|
|
25
|
+
* version: 1,
|
|
26
|
+
* session_id: string,
|
|
27
|
+
* session_start_ref: string, // git sha captured at session-start
|
|
28
|
+
* captured_at: string, // ISO 8601 UTC
|
|
29
|
+
* dependency_hash: string, // sha256 of package.json + lockfile
|
|
30
|
+
* results: {
|
|
31
|
+
* typecheck: { status: 'pass'|'fail', error_count?: number },
|
|
32
|
+
* test: { status: 'pass'|'fail' },
|
|
33
|
+
* lint: { status: 'pass'|'fail' }
|
|
34
|
+
* }
|
|
35
|
+
* }
|
|
36
|
+
*
|
|
37
|
+
* Validity rules (see isCacheValid):
|
|
38
|
+
* - session_start_ref matches current session
|
|
39
|
+
* - dependency_hash matches current package.json + lockfile
|
|
40
|
+
* - captured_at within TTL (default 7 days)
|
|
41
|
+
* - all results.* statuses === 'pass'
|
|
42
|
+
*
|
|
43
|
+
* Skip rule (see shouldSkipIncremental):
|
|
44
|
+
* cache valid AND changed-file-count < 50
|
|
45
|
+
*
|
|
46
|
+
* Fail-safe: every public function returns a structured result; none throw.
|
|
47
|
+
* On error, shouldSkipIncremental returns skip=false (Incremental runs),
|
|
48
|
+
* loadLatestBaselineResult returns null.
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
import fs from 'node:fs';
|
|
52
|
+
import path from 'node:path';
|
|
53
|
+
import { spawnSync } from 'node:child_process';
|
|
54
|
+
import { digestMultiBufferSha256 } from './crypto-digest-utils.mjs';
|
|
55
|
+
import { isIgnoredRootFile } from './package-manager.mjs';
|
|
56
|
+
|
|
57
|
+
const CACHE_RELATIVE_PATH = '.orchestrator/metrics/baseline-results.jsonl';
|
|
58
|
+
const DEFAULT_TTL_DAYS = 7;
|
|
59
|
+
const DEFAULT_SCOPE_THRESHOLD = 50;
|
|
60
|
+
const RECORD_VERSION = 1;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Compute a sha256 hash over package.json plus the active lockfile.
|
|
64
|
+
* Missing lockfile → hash only package.json.
|
|
65
|
+
* Missing package.json → returns the sha256 of the empty string (stable null).
|
|
66
|
+
*
|
|
67
|
+
* @param {string} repoRoot Absolute path to repo root.
|
|
68
|
+
* @returns {string} Hex sha256 digest.
|
|
69
|
+
*/
|
|
70
|
+
export function computeDependencyHash(repoRoot) {
|
|
71
|
+
const buffers = [];
|
|
72
|
+
const pkgPath = path.join(repoRoot, 'package.json');
|
|
73
|
+
if (fs.existsSync(pkgPath)) {
|
|
74
|
+
buffers.push(fs.readFileSync(pkgPath));
|
|
75
|
+
}
|
|
76
|
+
// Prefer pnpm-lock.yaml (project standard), then package-lock.json, then yarn.lock.
|
|
77
|
+
// A gitignored root lockfile is skipped — a stray/local-only lockfile must
|
|
78
|
+
// not dominate the dependency hash (issue #715 bug class).
|
|
79
|
+
const lockCandidates = ['pnpm-lock.yaml', 'package-lock.json', 'yarn.lock'];
|
|
80
|
+
for (const candidate of lockCandidates) {
|
|
81
|
+
const candidatePath = path.join(repoRoot, candidate);
|
|
82
|
+
if (fs.existsSync(candidatePath) && !isIgnoredRootFile(repoRoot, candidate)) {
|
|
83
|
+
buffers.push(fs.readFileSync(candidatePath));
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return digestMultiBufferSha256(buffers);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Append one baseline-result record to the JSONL cache.
|
|
92
|
+
* Creates the metrics directory if missing. Atomic append via appendFileSync.
|
|
93
|
+
*
|
|
94
|
+
* @param {object} params
|
|
95
|
+
* @param {string} params.repoRoot
|
|
96
|
+
* @param {string} params.sessionId
|
|
97
|
+
* @param {string} params.sessionStartRef
|
|
98
|
+
* @param {object} params.results { typecheck, test, lint } — see module header.
|
|
99
|
+
*/
|
|
100
|
+
export function saveBaselineResult({ repoRoot, sessionId, sessionStartRef, results }) {
|
|
101
|
+
const cachePath = path.join(repoRoot, CACHE_RELATIVE_PATH);
|
|
102
|
+
const cacheDir = path.dirname(cachePath);
|
|
103
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
104
|
+
const record = {
|
|
105
|
+
version: RECORD_VERSION,
|
|
106
|
+
session_id: sessionId,
|
|
107
|
+
session_start_ref: sessionStartRef,
|
|
108
|
+
captured_at: new Date().toISOString(),
|
|
109
|
+
dependency_hash: computeDependencyHash(repoRoot),
|
|
110
|
+
results,
|
|
111
|
+
};
|
|
112
|
+
fs.appendFileSync(cachePath, JSON.stringify(record) + '\n');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Return the most recent record from the JSONL cache, or null if the file
|
|
117
|
+
* is missing, empty, or the last line is unparseable.
|
|
118
|
+
*
|
|
119
|
+
* Never throws.
|
|
120
|
+
*
|
|
121
|
+
* @param {object} params
|
|
122
|
+
* @param {string} params.repoRoot
|
|
123
|
+
* @returns {object|null}
|
|
124
|
+
*/
|
|
125
|
+
export function loadLatestBaselineResult({ repoRoot }) {
|
|
126
|
+
try {
|
|
127
|
+
const cachePath = path.join(repoRoot, CACHE_RELATIVE_PATH);
|
|
128
|
+
if (!fs.existsSync(cachePath)) return null;
|
|
129
|
+
const raw = fs.readFileSync(cachePath, 'utf8');
|
|
130
|
+
if (!raw.trim()) return null;
|
|
131
|
+
const lines = raw.split('\n').filter((line) => line.trim().length > 0);
|
|
132
|
+
if (lines.length === 0) return null;
|
|
133
|
+
const last = lines[lines.length - 1];
|
|
134
|
+
return JSON.parse(last);
|
|
135
|
+
} catch {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Validate a cache record against current session context.
|
|
142
|
+
*
|
|
143
|
+
* @param {object} params
|
|
144
|
+
* @param {string} params.repoRoot
|
|
145
|
+
* @param {object|null} params.latestRecord
|
|
146
|
+
* @param {string} params.currentSessionStartRef
|
|
147
|
+
* @param {number} [params.ttlDays=7]
|
|
148
|
+
* @returns {{ valid: boolean, reason: string }}
|
|
149
|
+
*/
|
|
150
|
+
export function isCacheValid({ repoRoot, latestRecord, currentSessionStartRef, ttlDays = DEFAULT_TTL_DAYS }) {
|
|
151
|
+
if (!latestRecord) {
|
|
152
|
+
return { valid: false, reason: 'no-record' };
|
|
153
|
+
}
|
|
154
|
+
if (latestRecord.session_start_ref !== currentSessionStartRef) {
|
|
155
|
+
return { valid: false, reason: 'session-ref-mismatch' };
|
|
156
|
+
}
|
|
157
|
+
const currentDepHash = computeDependencyHash(repoRoot);
|
|
158
|
+
if (latestRecord.dependency_hash !== currentDepHash) {
|
|
159
|
+
return { valid: false, reason: 'dependency-changed' };
|
|
160
|
+
}
|
|
161
|
+
const capturedAt = Date.parse(latestRecord.captured_at);
|
|
162
|
+
if (Number.isNaN(capturedAt)) {
|
|
163
|
+
return { valid: false, reason: 'ttl-expired' };
|
|
164
|
+
}
|
|
165
|
+
const ageMs = Date.now() - capturedAt;
|
|
166
|
+
const ttlMs = ttlDays * 24 * 60 * 60 * 1000;
|
|
167
|
+
if (ageMs > ttlMs) {
|
|
168
|
+
return { valid: false, reason: 'ttl-expired' };
|
|
169
|
+
}
|
|
170
|
+
const r = latestRecord.results || {};
|
|
171
|
+
const statuses = [r.typecheck?.status, r.test?.status, r.lint?.status];
|
|
172
|
+
if (!statuses.every((s) => s === 'pass')) {
|
|
173
|
+
return { valid: false, reason: 'baseline-had-failures' };
|
|
174
|
+
}
|
|
175
|
+
return { valid: true, reason: 'cache-hit' };
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* High-level convenience used by wave-executor.
|
|
180
|
+
*
|
|
181
|
+
* Returns { skip, reason, changedFileCount }. Never throws — on any error
|
|
182
|
+
* (git failure, unreadable cache, etc.) returns skip=false so Incremental
|
|
183
|
+
* runs. Fail safe = run Incremental.
|
|
184
|
+
*
|
|
185
|
+
* @param {object} params
|
|
186
|
+
* @param {string} params.repoRoot
|
|
187
|
+
* @param {string} params.sessionStartRef
|
|
188
|
+
* @param {number} [params.scopeThreshold=50]
|
|
189
|
+
* @param {string|null} [params.waveRole=null] Role of the wave being gated.
|
|
190
|
+
* When 'Quality' (case-insensitive, whitespace-trimmed — e.g. 'quality',
|
|
191
|
+
* ' Quality ' all match), the function hard-returns skip=false BEFORE any
|
|
192
|
+
* cache logic so the Quality-wave Full Gate is mechanically un-skippable
|
|
193
|
+
* (#724 C6). All other values (null/undefined/Impl-*) preserve the cache
|
|
194
|
+
* short-circuit path.
|
|
195
|
+
* @returns {{ skip: boolean, reason: string, changedFileCount: number }}
|
|
196
|
+
*/
|
|
197
|
+
export function shouldSkipIncremental({ repoRoot, sessionStartRef, scopeThreshold = DEFAULT_SCOPE_THRESHOLD, waveRole = null }) {
|
|
198
|
+
// C6 (#724): the Quality wave's Full Gate is NEVER skipped, regardless of a
|
|
199
|
+
// valid cache or a narrow diff. This mirrors the session-end close-safety
|
|
200
|
+
// invariant and makes the "After Quality: Full Gate" mandate mechanical
|
|
201
|
+
// rather than prose-only. Hard-return BEFORE any cache/git logic.
|
|
202
|
+
// Case-insensitive + trimmed match (#F-B) — a coordinator prose typo like
|
|
203
|
+
// 'quality' or 'Quality ' must not silently disable this invariant.
|
|
204
|
+
if (String(waveRole ?? '').trim().toLowerCase() === 'quality') {
|
|
205
|
+
return { skip: false, reason: 'quality-wave-full-gate-mandate', changedFileCount: -1 };
|
|
206
|
+
}
|
|
207
|
+
try {
|
|
208
|
+
const latestRecord = loadLatestBaselineResult({ repoRoot });
|
|
209
|
+
const validity = isCacheValid({ repoRoot, latestRecord, currentSessionStartRef: sessionStartRef });
|
|
210
|
+
if (!validity.valid) {
|
|
211
|
+
return { skip: false, reason: validity.reason, changedFileCount: -1 };
|
|
212
|
+
}
|
|
213
|
+
const diff = spawnSync('git', ['diff', '--name-only', `${sessionStartRef}..HEAD`], {
|
|
214
|
+
cwd: repoRoot,
|
|
215
|
+
encoding: 'utf8',
|
|
216
|
+
});
|
|
217
|
+
if (diff.status !== 0) {
|
|
218
|
+
return { skip: false, reason: 'git-diff-failed', changedFileCount: -1 };
|
|
219
|
+
}
|
|
220
|
+
const changedFileCount = diff.stdout.split('\n').filter((line) => line.trim().length > 0).length;
|
|
221
|
+
if (changedFileCount >= scopeThreshold) {
|
|
222
|
+
return { skip: false, reason: 'scope-too-large', changedFileCount };
|
|
223
|
+
}
|
|
224
|
+
return { skip: true, reason: 'cache-hit', changedFileCount };
|
|
225
|
+
} catch (err) {
|
|
226
|
+
return { skip: false, reason: `error:${err?.message ?? 'unknown'}`, changedFileCount: -1 };
|
|
227
|
+
}
|
|
228
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quality-gates policy loader.
|
|
3
|
+
*
|
|
4
|
+
* Reads .orchestrator/policy/quality-gates.json (issue #183).
|
|
5
|
+
* Consumed by scripts/run-quality-gate.mjs and the quality-gates skill.
|
|
6
|
+
*
|
|
7
|
+
* Policy-file-first: when a valid policy exists, its commands take precedence
|
|
8
|
+
* over the project-instruction file's Session Config (CLAUDE.md, or AGENTS.md
|
|
9
|
+
* on Codex CLI — see skills/_shared/instruction-file-resolution.md). Falls
|
|
10
|
+
* back to caller-provided defaults when the file is missing or malformed.
|
|
11
|
+
*
|
|
12
|
+
* Never throws.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
16
|
+
import { join } from 'node:path';
|
|
17
|
+
|
|
18
|
+
const REQUIRED_KEYS = ['test', 'typecheck', 'lint'];
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Loads and shape-validates the quality-gates policy file.
|
|
22
|
+
*
|
|
23
|
+
* @param {string} repoRoot - repository root (where .orchestrator/ lives)
|
|
24
|
+
* @returns {object|null} parsed policy, or null if missing/invalid
|
|
25
|
+
*/
|
|
26
|
+
export function loadQualityGatesPolicy(repoRoot) {
|
|
27
|
+
const path = join(repoRoot, '.orchestrator/policy/quality-gates.json');
|
|
28
|
+
if (!existsSync(path)) return null;
|
|
29
|
+
|
|
30
|
+
let parsed;
|
|
31
|
+
try {
|
|
32
|
+
parsed = JSON.parse(readFileSync(path, 'utf8'));
|
|
33
|
+
} catch (err) {
|
|
34
|
+
process.stderr.write(`⚠ quality-gates policy: malformed JSON at ${path}: ${err.message}\n`);
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
39
|
+
process.stderr.write(`⚠ quality-gates policy: root must be an object (${path})\n`);
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
if (parsed['version'] !== 1) {
|
|
43
|
+
process.stderr.write(`⚠ quality-gates policy: unsupported version ${JSON.stringify(parsed['version'])} (${path})\n`);
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
const commands = parsed['commands'];
|
|
47
|
+
if (commands === null || typeof commands !== 'object' || Array.isArray(commands)) {
|
|
48
|
+
process.stderr.write(`⚠ quality-gates policy: commands must be an object (${path})\n`);
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
for (const key of REQUIRED_KEYS) {
|
|
52
|
+
const entry = commands[key];
|
|
53
|
+
if (
|
|
54
|
+
entry === null ||
|
|
55
|
+
typeof entry !== 'object' ||
|
|
56
|
+
Array.isArray(entry) ||
|
|
57
|
+
typeof entry['command'] !== 'string' ||
|
|
58
|
+
entry['command'].trim() === ''
|
|
59
|
+
) {
|
|
60
|
+
process.stderr.write(`⚠ quality-gates policy: commands.${key}.command must be a non-empty string (${path})\n`);
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return parsed;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Resolves a command from policy or falls back to the caller's default.
|
|
69
|
+
*
|
|
70
|
+
* @param {object|null} policy - result of loadQualityGatesPolicy
|
|
71
|
+
* @param {"test"|"typecheck"|"lint"} key
|
|
72
|
+
* @param {string} fallback - fallback command when policy lacks the key
|
|
73
|
+
* @returns {string}
|
|
74
|
+
*/
|
|
75
|
+
export function resolveCommand(policy, key, fallback) {
|
|
76
|
+
if (policy && policy.commands && policy.commands[key] && typeof policy.commands[key].command === 'string') {
|
|
77
|
+
return policy.commands[key].command;
|
|
78
|
+
}
|
|
79
|
+
return fallback;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Maps a Session Config key (e.g. "test-command") from the project-instruction
|
|
84
|
+
* file (CLAUDE.md or AGENTS.md alias) to the policy-file key ("test"). Used by
|
|
85
|
+
* run-quality-gate.mjs.
|
|
86
|
+
*
|
|
87
|
+
* @param {string} configKey
|
|
88
|
+
* @returns {"test"|"typecheck"|"lint"|null}
|
|
89
|
+
*/
|
|
90
|
+
export function configKeyToPolicyKey(configKey) {
|
|
91
|
+
if (configKey === 'test-command') return 'test';
|
|
92
|
+
if (configKey === 'typecheck-command') return 'typecheck';
|
|
93
|
+
if (configKey === 'lint-command') return 'lint';
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v0 recommendation heuristic (issue #272, Epic #271 Phase A).
|
|
3
|
+
*
|
|
4
|
+
* Deterministic three-branch rule. Consumed by session-end Phase 3.7a Writer
|
|
5
|
+
* and (via STATE.md handoff) by session-start Phase 1.5 Reader.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const VALID_MODES = new Set([
|
|
9
|
+
'housekeeping',
|
|
10
|
+
'feature',
|
|
11
|
+
'deep',
|
|
12
|
+
'discovery',
|
|
13
|
+
'evolve',
|
|
14
|
+
'plan-retro',
|
|
15
|
+
]);
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Compute the v0 recommendation for the next session.
|
|
19
|
+
*
|
|
20
|
+
* Rules (evaluated in order, first match wins):
|
|
21
|
+
* 1. completion_rate < 0.50 → plan-retro
|
|
22
|
+
* 2. carryover_ratio ≥ 0.30 → deep
|
|
23
|
+
* 3. otherwise → feature
|
|
24
|
+
*
|
|
25
|
+
* @param {object} input
|
|
26
|
+
* @param {number} input.completionRate — 0.00..1.00; share of planned issues closed
|
|
27
|
+
* @param {number} input.carryoverRatio — 0.00..1.00; carryover / planned (0 when planned=0)
|
|
28
|
+
* @param {Array<number|string>} [input.carryoverIssues] — IDs/IIDs of carried-over issues,
|
|
29
|
+
* pre-sorted (priority:critical/high first, FIFO tiebreak). Sliced to 0-5 entries for
|
|
30
|
+
* the `priorities` field.
|
|
31
|
+
* @returns {{mode: string, priorities: number[], rationale: string}}
|
|
32
|
+
*/
|
|
33
|
+
export function computeV0Recommendation({
|
|
34
|
+
completionRate,
|
|
35
|
+
carryoverRatio,
|
|
36
|
+
carryoverIssues = [],
|
|
37
|
+
}) {
|
|
38
|
+
if (typeof completionRate !== 'number' || Number.isNaN(completionRate)) {
|
|
39
|
+
throw new TypeError('completionRate must be a number');
|
|
40
|
+
}
|
|
41
|
+
if (typeof carryoverRatio !== 'number' || Number.isNaN(carryoverRatio)) {
|
|
42
|
+
throw new TypeError('carryoverRatio must be a number');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const priorities = Array.isArray(carryoverIssues)
|
|
46
|
+
? carryoverIssues.slice(0, 5).map((x) => (typeof x === 'number' ? x : parseInt(String(x), 10)))
|
|
47
|
+
: [];
|
|
48
|
+
|
|
49
|
+
let mode;
|
|
50
|
+
let rationale;
|
|
51
|
+
if (completionRate < 0.5) {
|
|
52
|
+
mode = 'plan-retro';
|
|
53
|
+
rationale = 'v0: completion <50% → retro';
|
|
54
|
+
} else if (carryoverRatio >= 0.3) {
|
|
55
|
+
mode = 'deep';
|
|
56
|
+
rationale = 'v0: carryover ≥30% → deep';
|
|
57
|
+
} else {
|
|
58
|
+
mode = 'feature';
|
|
59
|
+
rationale = 'v0: default clean completion';
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return { mode, priorities, rationale };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Validate that a recommendation's mode is one of the allowed values.
|
|
67
|
+
* Defensive for Reader-side parsing — unknown modes are rejected rather than
|
|
68
|
+
* silently propagated into Phase B's mode-selector.
|
|
69
|
+
*/
|
|
70
|
+
export function isValidMode(mode) {
|
|
71
|
+
return typeof mode === 'string' && VALID_MODES.has(mode);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
// Dispatcher precursor SUGGESTIONS (#678, PRD §2 P2.4 / line 56, §4 line 244)
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Read-only precursor modes the dispatcher MENU may route to (`/discovery`,
|
|
80
|
+
* `/plan`). These are SUGGESTIONS only — never execution-wave modes. The
|
|
81
|
+
* non-execution guardrail lives in mode-selector.mjs (global-max swap excludes
|
|
82
|
+
* these from primary-mode promotion); this set is the source of truth for what
|
|
83
|
+
* the dispatcher is allowed to offer as a precursor.
|
|
84
|
+
* @type {ReadonlyArray<string>}
|
|
85
|
+
*/
|
|
86
|
+
export const PRECURSOR_MODES = Object.freeze(['discovery', 'plan-retro']);
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* True when `mode` is a read-only precursor (discovery / plan-retro). Mirrors
|
|
90
|
+
* `isNonExecutionMode` in mode-selector/scoring.mjs but kept local so the
|
|
91
|
+
* dispatcher can import suggestion logic without pulling in the scoring graph.
|
|
92
|
+
* @param {unknown} mode
|
|
93
|
+
* @returns {boolean}
|
|
94
|
+
*/
|
|
95
|
+
export function isPrecursorMode(mode) {
|
|
96
|
+
return typeof mode === 'string' && PRECURSOR_MODES.includes(mode);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Suggest read-only precursor session-types for the dispatcher menu (#678).
|
|
101
|
+
*
|
|
102
|
+
* Pure, deterministic, additive. NEVER returns an execution mode — only the
|
|
103
|
+
* read-only precursors (`discovery` → /discovery, `plan-retro` → /plan). The
|
|
104
|
+
* dispatcher presents these as optional routes in its owner-confirmation AUQ;
|
|
105
|
+
* they are suggestions, not the executed primary `mode`.
|
|
106
|
+
*
|
|
107
|
+
* Heuristic (both independent — a session may justify both):
|
|
108
|
+
* - `plan-retro` when there is real carry-forward to plan/retrospect:
|
|
109
|
+
* completion_rate < 0.5 OR carryover_ratio ≥ 0.3.
|
|
110
|
+
* - `discovery` when context/understanding is thin and exploration helps:
|
|
111
|
+
* no recent sessions (cold start) OR an explicit `discoveryHint`.
|
|
112
|
+
*
|
|
113
|
+
* @param {object|null|undefined} signals
|
|
114
|
+
* @param {number} [signals.completionRate]
|
|
115
|
+
* @param {number} [signals.carryoverRatio]
|
|
116
|
+
* @param {Array<unknown>} [signals.recentSessions]
|
|
117
|
+
* @param {boolean} [signals.discoveryHint] — explicit "explore first" hint.
|
|
118
|
+
* @returns {Array<{mode: string, route: string, rationale: string}>}
|
|
119
|
+
* 0–2 entries; deterministic order (plan-retro before discovery when both).
|
|
120
|
+
*/
|
|
121
|
+
export function suggestPrecursors(signals) {
|
|
122
|
+
if (signals === null || typeof signals !== 'object') {
|
|
123
|
+
return [];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const completionRate =
|
|
127
|
+
typeof signals.completionRate === 'number' && !Number.isNaN(signals.completionRate)
|
|
128
|
+
? signals.completionRate
|
|
129
|
+
: null;
|
|
130
|
+
const carryoverRatio =
|
|
131
|
+
typeof signals.carryoverRatio === 'number' && !Number.isNaN(signals.carryoverRatio)
|
|
132
|
+
? signals.carryoverRatio
|
|
133
|
+
: null;
|
|
134
|
+
const recentSessions = Array.isArray(signals.recentSessions) ? signals.recentSessions : [];
|
|
135
|
+
|
|
136
|
+
/** @type {Array<{mode: string, route: string, rationale: string}>} */
|
|
137
|
+
const suggestions = [];
|
|
138
|
+
|
|
139
|
+
if ((completionRate !== null && completionRate < 0.5) || (carryoverRatio !== null && carryoverRatio >= 0.3)) {
|
|
140
|
+
suggestions.push({
|
|
141
|
+
mode: 'plan-retro',
|
|
142
|
+
route: '/plan',
|
|
143
|
+
rationale: 'low completion / carryover → plan-retro precursor',
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (recentSessions.length === 0 || signals.discoveryHint === true) {
|
|
148
|
+
suggestions.push({
|
|
149
|
+
mode: 'discovery',
|
|
150
|
+
route: '/discovery',
|
|
151
|
+
rationale: 'cold start / explore-first hint → discovery precursor',
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return suggestions;
|
|
156
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* eligibility.mjs — Reconciliation-engine eligibility filter (Epic #693 FA2, issue #695).
|
|
3
|
+
*
|
|
4
|
+
* Decides which `learnings.jsonl` entries are eligible to be converted into a
|
|
5
|
+
* conditional `.claude/rules/*.md` rule. A learning is a plain in-memory JSON
|
|
6
|
+
* object; this module classifies and partitions such objects without any I/O.
|
|
7
|
+
*
|
|
8
|
+
* ── Safe posture (INVERTED allow-list) ───────────────────────────────────────
|
|
9
|
+
* The engine default-rejects. Only a learning whose `type` is in
|
|
10
|
+
* {@link CONVERT_TYPES} AND that carries a non-empty `file_paths[]` (the scope a
|
|
11
|
+
* conditional rule needs) is eligible. Everything else is rejected with a
|
|
12
|
+
* specific audit reason. This is the safe default the issue demands: a corpus
|
|
13
|
+
* of unknown types never silently produces a rule.
|
|
14
|
+
*
|
|
15
|
+
* ── The file-carrier key ─────────────────────────────────────────────────────
|
|
16
|
+
* The scope key we read is `file_paths` (an array of repo-relative paths).
|
|
17
|
+
* Provenance (corrected 2026-07-02, Epic #723 B2): the live corpus historically
|
|
18
|
+
* carried the scope under `files`, NOT `file_paths` — so this reader matched
|
|
19
|
+
* nothing (0/108 records had `file_paths`). Two fixes now converge on
|
|
20
|
+
* `file_paths` and keep this reader correct:
|
|
21
|
+
* 1. `scripts/backfill-learnings.mjs` normalized the on-disk corpus
|
|
22
|
+
* `files` → `file_paths` (one-shot, 2026-07-02).
|
|
23
|
+
* 2. The schema SSOT (`learnings/schema.mjs` normalizeDialects) renames
|
|
24
|
+
* `files` → `file_paths` on every read + migration going forward.
|
|
25
|
+
* We continue to read `file_paths` — it is now the canonical scope key. The
|
|
26
|
+
* engine's default JSONL loader also runs records through migrateLegacyLearning
|
|
27
|
+
* + normalizeLearning, so direct `runReconcile({ repoRoot })` calls see the same
|
|
28
|
+
* legacy alias + `files`→`file_paths` mapping as the backfill/read funnels.
|
|
29
|
+
*
|
|
30
|
+
* ── Type-name mapping (issue → real) ─────────────────────────────────────────
|
|
31
|
+
* issue 'fragile-file' -> real 'fragile-file' (verbatim; 1 instance)
|
|
32
|
+
* issue 'recurring-issue' -> real 'recurring-issue' (verbatim; 1 instance)
|
|
33
|
+
* issue 'anti-pattern' -> real 'anti-pattern' (verbatim; 22 instances)
|
|
34
|
+
* Forward-compat spec names with 0 instances today are also included so a future
|
|
35
|
+
* renamed corpus still converts (they convert nothing now — harmless). See the
|
|
36
|
+
* CONVERT_TYPES doc for the full 2026-07-02 instance census.
|
|
37
|
+
*
|
|
38
|
+
* Pure functions — depends only on the pure `learnings/schema` type registry
|
|
39
|
+
* (a frozen constant; no I/O). Deterministic, no file I/O.
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
import { LEARNING_TYPE_REGISTRY, LEARNING_TTL_DAYS, deriveExpiresAt } from '../learnings/schema.mjs';
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Recovery-stub / legacy-backfill placeholder insight signature (issue #741.2).
|
|
46
|
+
* Legacy-recovery records occasionally carry a stub insight instead of a real
|
|
47
|
+
* one (e.g. `"(legacy record — insight backfilled during 2026-07-02 recovery)"`).
|
|
48
|
+
* A placeholder insight would produce an EMPTY rule body downstream (the
|
|
49
|
+
* renderer has nothing meaningful to render) — reject it honestly at the
|
|
50
|
+
* eligibility gate instead of letting a hollow rule reach the proposal stage.
|
|
51
|
+
* @type {RegExp}
|
|
52
|
+
*/
|
|
53
|
+
const PLACEHOLDER_RE = /insight backfilled during .* recovery|^\(?legacy record\b/i;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* The learning `type` values that may become a conditional rule. DERIVED
|
|
57
|
+
* (Epic #723 I1, issue #733 Teil b) from `scripts/lib/learnings/schema.mjs`'s
|
|
58
|
+
* `LEARNING_TYPE_REGISTRY`: every type whose `ruleConvertible` flag is `true`.
|
|
59
|
+
*
|
|
60
|
+
* Instance census (corrected 2026-07-02, Epic #723 B2 — the earlier comment
|
|
61
|
+
* was a stale mis-count that inverted `fragile-file` and `fragile-pattern`),
|
|
62
|
+
* kept here as a historical record (the registry itself carries no counts):
|
|
63
|
+
* - 'anti-pattern' — 22 instances
|
|
64
|
+
* - 'architecture-pattern' — 2 instances
|
|
65
|
+
* - 'convention' — 1 instance
|
|
66
|
+
* - 'design-pattern' — 1 instance
|
|
67
|
+
* - 'fragile-file' — 1 instance
|
|
68
|
+
* - 'recurring-issue' — 1 instance
|
|
69
|
+
* - 'fragile-pattern' — 0 instances (kept for forward-compat)
|
|
70
|
+
* - 'stagnation-class-frequency' — 0 instances (kept for forward-compat)
|
|
71
|
+
*
|
|
72
|
+
* The membership is UNCHANGED from the prior hand-maintained literal — every
|
|
73
|
+
* listed type stays in the allow-list, so a future renamed/backfilled corpus
|
|
74
|
+
* still converts and 0-instance types convert nothing today.
|
|
75
|
+
*
|
|
76
|
+
* @type {Set<string>}
|
|
77
|
+
*/
|
|
78
|
+
export const CONVERT_TYPES = new Set(
|
|
79
|
+
Object.entries(LEARNING_TYPE_REGISTRY)
|
|
80
|
+
.filter(([, meta]) => meta.ruleConvertible)
|
|
81
|
+
.map(([type]) => type)
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Classify a single learning record for rule-conversion eligibility.
|
|
86
|
+
*
|
|
87
|
+
* INVERTED allow-list: eligible only when the type is in CONVERT_TYPES AND a
|
|
88
|
+
* non-empty `file_paths[]` is present. The type gate is evaluated before the
|
|
89
|
+
* file gate, so an out-of-allow-list type is rejected as such even when it
|
|
90
|
+
* carries file paths.
|
|
91
|
+
*
|
|
92
|
+
* Two further gates run AFTER the type + file gates succeed (issue #741):
|
|
93
|
+
* - placeholder-insight (#741.2, always-on): an empty or recovery-stub
|
|
94
|
+
* insight would produce an empty rule body — rejected honestly rather
|
|
95
|
+
* than proposed with hollow content. `minInsightChars` additionally
|
|
96
|
+
* rejects an insight that is non-empty but too short to be useful; it is
|
|
97
|
+
* opt-in (inert when omitted).
|
|
98
|
+
* - already-expired-at-proposal (#741.1c, opt-in via `now`): a learning
|
|
99
|
+
* whose natural TTL (`created_at` + per-type TTL) already elapsed before
|
|
100
|
+
* proposal time is rejected honestly instead of silently floored back to
|
|
101
|
+
* life. Inert when `now` is omitted, so existing single-arg callers are
|
|
102
|
+
* unaffected.
|
|
103
|
+
*
|
|
104
|
+
* Defensive: a null / non-object record, or one missing `type`, is rejected
|
|
105
|
+
* (never throws).
|
|
106
|
+
*
|
|
107
|
+
* @param {unknown} learning - an in-memory learning object.
|
|
108
|
+
* @param {{ now?: number, minInsightChars?: number }} [opts] - now: injectable
|
|
109
|
+
* clock (ms epoch) gating the expiry check; minInsightChars: opt-in
|
|
110
|
+
* minimum insight length gating the placeholder check.
|
|
111
|
+
* @returns {{ eligible: boolean, reason: string }}
|
|
112
|
+
*/
|
|
113
|
+
export function classifyLearning(learning, { now, minInsightChars } = {}) {
|
|
114
|
+
if (
|
|
115
|
+
learning === null ||
|
|
116
|
+
typeof learning !== 'object' ||
|
|
117
|
+
Array.isArray(learning) ||
|
|
118
|
+
typeof learning.type !== 'string' ||
|
|
119
|
+
learning.type.length === 0
|
|
120
|
+
) {
|
|
121
|
+
return { eligible: false, reason: 'invalid learning record (missing type)' };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const { type } = learning;
|
|
125
|
+
|
|
126
|
+
// Type gate beats file gate (safe posture: default-reject unknown types).
|
|
127
|
+
if (!CONVERT_TYPES.has(type)) {
|
|
128
|
+
return {
|
|
129
|
+
eligible: false,
|
|
130
|
+
reason: `type '${type}' not in convert allow-list — default-reject`,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const filePaths = learning.file_paths;
|
|
135
|
+
if (!Array.isArray(filePaths) || filePaths.length === 0) {
|
|
136
|
+
return {
|
|
137
|
+
eligible: false,
|
|
138
|
+
reason: `eligible type '${type}' but empty file_paths[] — cannot scope a conditional rule`,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Placeholder-insight gate (#741.2, always-on) — empty or recovery-stub
|
|
143
|
+
// insight would produce an empty rule body downstream.
|
|
144
|
+
const insight = typeof learning.insight === 'string' ? learning.insight.trim() : '';
|
|
145
|
+
if (insight === '' || PLACEHOLDER_RE.test(insight)) {
|
|
146
|
+
return {
|
|
147
|
+
eligible: false,
|
|
148
|
+
reason: `placeholder-insight — insight is empty or a recovery placeholder ("${insight.slice(0, 40)}")`,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
if (typeof minInsightChars === 'number' && minInsightChars > 0 && insight.length < minInsightChars) {
|
|
152
|
+
return {
|
|
153
|
+
eligible: false,
|
|
154
|
+
reason: `placeholder-insight — insight ${insight.length} chars < min-insight-chars ${minInsightChars}`,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Already-expired-at-proposal gate (#741.1c, opt-in via `now`) — a learning
|
|
159
|
+
// whose natural TTL already elapsed before proposal time is rejected
|
|
160
|
+
// honestly rather than silently floored back to life at emit time.
|
|
161
|
+
if (typeof now === 'number' && Number.isFinite(now)) {
|
|
162
|
+
const naturalExpiryIso = deriveExpiresAt(learning.created_at, type);
|
|
163
|
+
const naturalExpiryMs = Date.parse(naturalExpiryIso);
|
|
164
|
+
if (Number.isFinite(naturalExpiryMs) && naturalExpiryMs < now) {
|
|
165
|
+
const ttlDays = LEARNING_TTL_DAYS[type] ?? LEARNING_TTL_DAYS.default;
|
|
166
|
+
return {
|
|
167
|
+
eligible: false,
|
|
168
|
+
reason: `already-expired-at-proposal — natural TTL (created_at + ${ttlDays}d) elapsed at ${naturalExpiryIso.slice(0, 10)} before proposal`,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
eligible: true,
|
|
175
|
+
reason: `eligible: ${type} with ${filePaths.length} file path(s)`,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Partition a list of learnings into eligible records and rejected records
|
|
181
|
+
* (each rejection carrying the specific audit reason).
|
|
182
|
+
*
|
|
183
|
+
* @param {unknown[]} learnings - list of in-memory learning objects.
|
|
184
|
+
* @param {{ now?: number, minInsightChars?: number }} [opts] - forwarded verbatim
|
|
185
|
+
* to {@link classifyLearning} for every record.
|
|
186
|
+
* @returns {{ eligible: object[], rejected: Array<{ learning: object, reason: string }> }}
|
|
187
|
+
*/
|
|
188
|
+
export function filterEligible(learnings, opts = {}) {
|
|
189
|
+
const eligible = [];
|
|
190
|
+
const rejected = [];
|
|
191
|
+
|
|
192
|
+
const list = Array.isArray(learnings) ? learnings : [];
|
|
193
|
+
for (const learning of list) {
|
|
194
|
+
const { eligible: isEligible, reason } = classifyLearning(learning, opts);
|
|
195
|
+
if (isEligible) {
|
|
196
|
+
eligible.push(learning);
|
|
197
|
+
} else {
|
|
198
|
+
rejected.push({ learning, reason });
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return { eligible, rejected };
|
|
203
|
+
}
|