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,244 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* emitter.mjs — Reconciliation-engine activation-metadata emitter (Epic #693 FA2, issue #695).
|
|
3
|
+
*
|
|
4
|
+
* Converts a single ALREADY-ELIGIBLE learning (one that passed
|
|
5
|
+
* {@link module:reconcile/eligibility.classifyLearning} — i.e. carries a
|
|
6
|
+
* non-empty `file_paths[]`) into the activation-metadata object a renderer
|
|
7
|
+
* (the next wave) turns into `.claude/rules/<slug>.md` frontmatter.
|
|
8
|
+
*
|
|
9
|
+
* Pure function — no file I/O. Node 20+ stdlib only (`node:path` for dirname,
|
|
10
|
+
* the sibling learnings/schema.mjs for the TTL-derived expiry).
|
|
11
|
+
*
|
|
12
|
+
* ── The never-always-on invariant (the "brandmauer") ─────────────────────────
|
|
13
|
+
* docs/rule-authoring.md § "The Never-Always-On Invariant". Any rule with
|
|
14
|
+
* `auto-generated: true` MUST carry ≥1 activation axis (`globs` OR `mode` OR
|
|
15
|
+
* `host-class`), a `learning-key`, and an `expires-at`. This emitter ALWAYS
|
|
16
|
+
* stamps `autoGenerated: true` / `alwaysApply: false`, always emits a
|
|
17
|
+
* `learningKey` and an `expiresAt`, and THROWS when it cannot produce at least
|
|
18
|
+
* one activation axis (empty globs AND no hostClass). It can never emit an
|
|
19
|
+
* always-on auto-generated rule.
|
|
20
|
+
*
|
|
21
|
+
* `mode` is intentionally NOT emitted: a learning's `scope`
|
|
22
|
+
* (local/private/public) is a privacy tier, NOT a session mode — conflating the
|
|
23
|
+
* two would mis-gate the rule. The activation axes used here are `globs`
|
|
24
|
+
* (always, derived from file_paths) and optionally `host-class`.
|
|
25
|
+
*
|
|
26
|
+
* The output key names match the camelCase RuleEntry fields the rule-loader
|
|
27
|
+
* surfaces (`globs`, `description`, `learningKey`, `confidence`, `expiresAt`,
|
|
28
|
+
* `autoGenerated`, `alwaysApply`, `hostClass`).
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import { dirname } from 'node:path';
|
|
32
|
+
|
|
33
|
+
import { deriveExpiresAt } from '../learnings/schema.mjs';
|
|
34
|
+
|
|
35
|
+
const DAY_MS = 86400 * 1000;
|
|
36
|
+
const DESCRIPTION_MAX = 120;
|
|
37
|
+
|
|
38
|
+
// The "born-dead expiry" floor (issue #741.1a). A rule whose NATURAL expiry
|
|
39
|
+
// (created_at + TTL) already sits in the past — or within this many days of
|
|
40
|
+
// `now` — at proposal time would be excluded on first read: rule-loader.mjs
|
|
41
|
+
// drops any rule whose `expires-at` satisfies `Date.parse(expiry) < Date.now()`
|
|
42
|
+
// (strict `<`). 7 days sits below every per-type TTL in LEARNING_TTL_DAYS
|
|
43
|
+
// (min 30d), so the floor only ever RAISES a near-dead/past expiry — it never
|
|
44
|
+
// shortens a healthy future one.
|
|
45
|
+
const MIN_RULE_DAYS_DEFAULT = 7;
|
|
46
|
+
|
|
47
|
+
// Control chars: C0 range (U+0000–U+001F, includes \n \r \t) plus DEL (U+007F).
|
|
48
|
+
// A newline in frontmatter would corrupt the YAML parse — stripping these is
|
|
49
|
+
// critical. Written with \u escapes so the source stays pure ASCII.
|
|
50
|
+
// eslint-disable-next-line no-control-regex
|
|
51
|
+
const CONTROL_CHARS_RE = /[\u0000-\u001F\u007F]/g;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Slugify a string into a stable kebab-case token.
|
|
55
|
+
* Lowercases, collapses every run of non-`[a-z0-9]` chars into a single `-`,
|
|
56
|
+
* and trims leading/trailing `-`. UUID subjects produce ugly-but-stable keys.
|
|
57
|
+
*
|
|
58
|
+
* @param {string} s
|
|
59
|
+
* @returns {string}
|
|
60
|
+
*/
|
|
61
|
+
function kebab(s) {
|
|
62
|
+
return String(s)
|
|
63
|
+
.toLowerCase()
|
|
64
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
65
|
+
.replace(/^-+|-+$/g, '');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Derive non-empty directory globs from a learning's `file_paths`.
|
|
70
|
+
*
|
|
71
|
+
* For each path: take its directory (`path.dirname`) and emit `<dir>/**`. When
|
|
72
|
+
* the file sits at the repo top level (`dirname` === '.'), emit the bare
|
|
73
|
+
* basename pattern instead of `./**` (e.g. `"foo.mjs"`). Results are deduped,
|
|
74
|
+
* order-preserving on first occurrence.
|
|
75
|
+
*
|
|
76
|
+
* @param {string[]} filePaths
|
|
77
|
+
* @returns {string[]}
|
|
78
|
+
*/
|
|
79
|
+
function globsFromFilePaths(filePaths) {
|
|
80
|
+
const out = [];
|
|
81
|
+
const seen = new Set();
|
|
82
|
+
for (const raw of filePaths) {
|
|
83
|
+
if (typeof raw !== 'string' || raw === '') continue;
|
|
84
|
+
const normalized = raw.replace(/\\/g, '/');
|
|
85
|
+
const dir = dirname(normalized);
|
|
86
|
+
const pattern = dir === '.' ? normalized : `${dir}/**`;
|
|
87
|
+
if (!seen.has(pattern)) {
|
|
88
|
+
seen.add(pattern);
|
|
89
|
+
out.push(pattern);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return out;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Build a single-line, frontmatter-safe description from a learning.
|
|
97
|
+
*
|
|
98
|
+
* Source precedence: `insight` → `title` → `subject`. Takes the first sentence
|
|
99
|
+
* (split on `. `), strips ALL newlines and control chars, collapses internal
|
|
100
|
+
* whitespace, and hard-truncates to {@link DESCRIPTION_MAX} chars (appending
|
|
101
|
+
* `…` when it had to cut).
|
|
102
|
+
*
|
|
103
|
+
* @param {object} learning
|
|
104
|
+
* @returns {string}
|
|
105
|
+
*/
|
|
106
|
+
function buildDescription(learning) {
|
|
107
|
+
const source =
|
|
108
|
+
typeof learning.insight === 'string' && learning.insight !== ''
|
|
109
|
+
? learning.insight
|
|
110
|
+
: learning.title || learning.subject || '';
|
|
111
|
+
|
|
112
|
+
// First sentence — split on '. ' so the headline stays terse.
|
|
113
|
+
const firstSentence = String(source).split('. ')[0];
|
|
114
|
+
|
|
115
|
+
const cleaned = firstSentence
|
|
116
|
+
.replace(CONTROL_CHARS_RE, ' ')
|
|
117
|
+
.replace(/\s+/g, ' ')
|
|
118
|
+
.trim()
|
|
119
|
+
// A trailing period from the first sentence reads oddly once truncated.
|
|
120
|
+
.replace(/\.$/, '');
|
|
121
|
+
|
|
122
|
+
if (cleaned.length <= DESCRIPTION_MAX) return cleaned;
|
|
123
|
+
// Reserve one char for the ellipsis.
|
|
124
|
+
return `${cleaned.slice(0, DESCRIPTION_MAX - 1).trimEnd()}…`;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Compute the `expiresAt` YYYY-MM-DD date string for the emitted rule.
|
|
129
|
+
*
|
|
130
|
+
* When `ruleExpiryDays` is a finite number, expiry = `created_at` + N days.
|
|
131
|
+
* Otherwise delegate to {@link deriveExpiresAt} (per-type TTL, default 60d).
|
|
132
|
+
* When `created_at` is missing/unparseable, fall back to the injectable `now`
|
|
133
|
+
* (epoch ms or Date) so the result stays deterministic under test.
|
|
134
|
+
*
|
|
135
|
+
* The result is then FLOORED at `now + minRuleDays` (default
|
|
136
|
+
* {@link MIN_RULE_DAYS_DEFAULT}) so an older learning whose natural expiry has
|
|
137
|
+
* already elapsed — or sits within the floor window — never produces a
|
|
138
|
+
* born-dead rule (issue #741.1a). The floor only ever raises the expiry; a
|
|
139
|
+
* healthy future expiry passes through unchanged.
|
|
140
|
+
*
|
|
141
|
+
* @param {object} learning
|
|
142
|
+
* @param {number|undefined} ruleExpiryDays
|
|
143
|
+
* @param {number|Date|undefined} now
|
|
144
|
+
* @param {number|undefined} minRuleDays - floor window in days; defaults to
|
|
145
|
+
* {@link MIN_RULE_DAYS_DEFAULT} when not a finite positive number.
|
|
146
|
+
* @returns {string} YYYY-MM-DD
|
|
147
|
+
*/
|
|
148
|
+
function computeExpiresAt(learning, ruleExpiryDays, now, minRuleDays) {
|
|
149
|
+
const toYmd = (iso) => String(iso).slice(0, 10);
|
|
150
|
+
const nowMs = () =>
|
|
151
|
+
now instanceof Date ? now.getTime() : typeof now === 'number' ? now : Date.now();
|
|
152
|
+
|
|
153
|
+
let derivedMs;
|
|
154
|
+
if (typeof ruleExpiryDays === 'number' && Number.isFinite(ruleExpiryDays)) {
|
|
155
|
+
let baseMs = Date.parse(learning.created_at);
|
|
156
|
+
if (!Number.isFinite(baseMs)) baseMs = nowMs();
|
|
157
|
+
derivedMs = baseMs + ruleExpiryDays * DAY_MS;
|
|
158
|
+
} else {
|
|
159
|
+
// No explicit override → per-type TTL. deriveExpiresAt falls back to
|
|
160
|
+
// `Date.now()` internally when created_at is missing; to keep the injectable
|
|
161
|
+
// `now` honoured for tests, substitute its ISO form as created_at in that case.
|
|
162
|
+
let createdAt = learning.created_at;
|
|
163
|
+
if (typeof createdAt !== 'string' || !Number.isFinite(Date.parse(createdAt))) {
|
|
164
|
+
createdAt = new Date(nowMs()).toISOString();
|
|
165
|
+
}
|
|
166
|
+
derivedMs = Date.parse(deriveExpiresAt(createdAt, learning.type));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const days =
|
|
170
|
+
Number.isFinite(minRuleDays) && minRuleDays > 0 ? minRuleDays : MIN_RULE_DAYS_DEFAULT;
|
|
171
|
+
const floorMs = nowMs() + days * DAY_MS;
|
|
172
|
+
return toYmd(new Date(Math.max(derivedMs, floorMs)).toISOString());
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Convert one eligible learning into renderer-ready activation metadata.
|
|
177
|
+
*
|
|
178
|
+
* @param {object} learning - an eligible learning (non-empty `file_paths[]`),
|
|
179
|
+
* carrying `type`, `subject`/`title`, `insight`, `confidence`, `created_at`,
|
|
180
|
+
* and optionally `host_class`.
|
|
181
|
+
* @param {object} [opts]
|
|
182
|
+
* @param {number} [opts.ruleExpiryDays] - explicit expiry window in days; when a
|
|
183
|
+
* finite number, overrides the per-type TTL.
|
|
184
|
+
* @param {number|Date} [opts.now] - injectable clock used ONLY as a fallback when
|
|
185
|
+
* `created_at` is missing/unparseable (keeps expiry deterministic in tests).
|
|
186
|
+
* @param {number} [opts.minRuleDays] - floor window (days) applied to the
|
|
187
|
+
* emitted expiry so it never falls in the past — see
|
|
188
|
+
* {@link computeExpiresAt} and {@link MIN_RULE_DAYS_DEFAULT}. Defaults
|
|
189
|
+
* internally when omitted; config plumbing for this is a later wave.
|
|
190
|
+
* @returns {{
|
|
191
|
+
* globs: string[],
|
|
192
|
+
* description: string,
|
|
193
|
+
* learningKey: string,
|
|
194
|
+
* confidence: number,
|
|
195
|
+
* expiresAt: string,
|
|
196
|
+
* autoGenerated: true,
|
|
197
|
+
* alwaysApply: false,
|
|
198
|
+
* hostClass?: string,
|
|
199
|
+
* }}
|
|
200
|
+
* @throws {Error} when no activation axis can be produced (empty globs AND no
|
|
201
|
+
* hostClass) — the never-always-on invariant.
|
|
202
|
+
*/
|
|
203
|
+
export function toActivationMetadata(learning, { ruleExpiryDays, now, minRuleDays } = {}) {
|
|
204
|
+
if (learning === null || typeof learning !== 'object' || Array.isArray(learning)) {
|
|
205
|
+
throw new Error('emitter: learning must be a non-null object');
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const filePaths = Array.isArray(learning.file_paths) ? learning.file_paths : [];
|
|
209
|
+
const globs = globsFromFilePaths(filePaths);
|
|
210
|
+
|
|
211
|
+
const hostClass =
|
|
212
|
+
typeof learning.host_class === 'string' && learning.host_class !== ''
|
|
213
|
+
? learning.host_class
|
|
214
|
+
: undefined;
|
|
215
|
+
|
|
216
|
+
// The brandmauer: an auto-generated rule must carry ≥1 activation axis. If we
|
|
217
|
+
// could derive neither a glob nor a host-class, refuse — never emit always-on.
|
|
218
|
+
if (globs.length === 0 && hostClass === undefined) {
|
|
219
|
+
throw new Error(
|
|
220
|
+
'emitter: no activation axis (globs/host-class) — refusing to emit always-on auto-generated rule (never-always-on invariant)',
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const subjectOrTitle = learning.title || learning.subject || '';
|
|
225
|
+
const learningKey = `${learning.type}/${kebab(subjectOrTitle)}`;
|
|
226
|
+
|
|
227
|
+
const confidence = typeof learning.confidence === 'number' ? learning.confidence : 0;
|
|
228
|
+
|
|
229
|
+
const metadata = {
|
|
230
|
+
globs,
|
|
231
|
+
description: buildDescription(learning),
|
|
232
|
+
learningKey,
|
|
233
|
+
confidence,
|
|
234
|
+
expiresAt: computeExpiresAt(learning, ruleExpiryDays, now, minRuleDays),
|
|
235
|
+
autoGenerated: true,
|
|
236
|
+
alwaysApply: false,
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
if (hostClass !== undefined) {
|
|
240
|
+
metadata.hostClass = hostClass;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return metadata;
|
|
244
|
+
}
|
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* engine.mjs — Reconciliation-engine ORCHESTRATOR (Epic #693 FA2, issue #695).
|
|
3
|
+
*
|
|
4
|
+
* This is the keystone of #695: it composes the four already-verified sibling
|
|
5
|
+
* leaf modules into a single never-throwing pipeline that turns the
|
|
6
|
+
* `learnings.jsonl` corpus into RULE PROPOSALS, records each proposal/rejection
|
|
7
|
+
* in the idempotency sidecar, and returns the rendered rule content for the
|
|
8
|
+
* operator to approve. It is the reconcile-side analogue of
|
|
9
|
+
* `scripts/lib/skill-evolution/engine.mjs` (the C2 repair orchestrator) and
|
|
10
|
+
* mirrors its defensive posture: DI seams for every disk touch, a never-throws
|
|
11
|
+
* public boundary, and an empty short-circuit that touches no disk.
|
|
12
|
+
*
|
|
13
|
+
* COMPOSITION (the four siblings):
|
|
14
|
+
* - eligibility.mjs → filterEligible (pure partition — INVERTED allow-list)
|
|
15
|
+
* - emitter.mjs → toActivationMetadata (eligible learning → metadata; THROWS on no axis)
|
|
16
|
+
* - renderer.mjs → renderRule (metadata → `.claude/rules/<slug>.md` string)
|
|
17
|
+
* - idempotency.mjs → mergeCandidates / makeCandidateId (store I/O + logical dedupe)
|
|
18
|
+
*
|
|
19
|
+
* PIPELINE (load → short-circuit → filter → per-item → record → summary):
|
|
20
|
+
* loadLearnings()
|
|
21
|
+
* → empty? return zeroed result, touch no disk
|
|
22
|
+
* → filterEligible(learnings)
|
|
23
|
+
* → per eligible: toActivationMetadata → renderRule → makeCandidateId → proposal
|
|
24
|
+
* → per rejected: record rejection with audit reason
|
|
25
|
+
* → mergeCandidates({ candidates, repoRoot }) (SKIPPED on dryRun)
|
|
26
|
+
* → return { proposals, rejected, summary }
|
|
27
|
+
*
|
|
28
|
+
* ── CRITICAL scope constraint (the FA2/FA3 brandmauer) ───────────────────────
|
|
29
|
+
* The engine COMPUTES proposals and records them in the idempotency sidecar.
|
|
30
|
+
* It MUST NOT write any file into `.claude/rules/` — that write happens only in
|
|
31
|
+
* FA3 (#696) AFTER operator approval. The rendered content lives inside each
|
|
32
|
+
* proposal object; the engine never persists a rule file. The ONLY disk write
|
|
33
|
+
* the engine performs is into the reconcile-candidates sidecar (via
|
|
34
|
+
* idempotency.mjs), and even that is skipped under `dryRun`.
|
|
35
|
+
*
|
|
36
|
+
* ── never-throws contract ────────────────────────────────────────────────────
|
|
37
|
+
* `runReconcile` NEVER throws to its caller. A per-learning emit/render failure
|
|
38
|
+
* degrades to a recorded rejection (never a crash); any unexpected top-level
|
|
39
|
+
* error returns a zeroed result with an `error` field.
|
|
40
|
+
*
|
|
41
|
+
* Plain Node ESM, no external deps — Node 20+ stdlib + the four siblings only.
|
|
42
|
+
*
|
|
43
|
+
* Part of Epic #693 → issue #695 (FA2 Reconciliation Engine).
|
|
44
|
+
*
|
|
45
|
+
* @typedef {Object} ReconcileProposal
|
|
46
|
+
* @property {string} learningKey - the logical learning key (from the emitter metadata).
|
|
47
|
+
* @property {string} slug - the `.claude/rules/<slug>.md` slug.
|
|
48
|
+
* @property {string} path - the intended repo-relative rule path (NOT written).
|
|
49
|
+
* @property {string} content - the rendered rule markdown (NOT written).
|
|
50
|
+
* @property {number} confidence - the learning's confidence.
|
|
51
|
+
* @property {string} candidateId - the deterministic sidecar candidate id.
|
|
52
|
+
* @property {'proposed'} status
|
|
53
|
+
*
|
|
54
|
+
* @typedef {Object} ReconcileRejection
|
|
55
|
+
* @property {string|null} learningKey - best-effort logical key, or null when underivable.
|
|
56
|
+
* @property {string} type - the learning's `type` (or 'unknown').
|
|
57
|
+
* @property {string} reason - the audit reason for rejection.
|
|
58
|
+
* @property {'rejected'} status
|
|
59
|
+
*
|
|
60
|
+
* @typedef {Object} ReconcileSummary
|
|
61
|
+
* @property {number} totalLearnings
|
|
62
|
+
* @property {number} eligible
|
|
63
|
+
* @property {number} proposed
|
|
64
|
+
* @property {number} rejected
|
|
65
|
+
* @property {boolean} written
|
|
66
|
+
*
|
|
67
|
+
* @typedef {Object} ReconcileResult
|
|
68
|
+
* @property {ReconcileProposal[]} proposals
|
|
69
|
+
* @property {ReconcileRejection[]} rejected
|
|
70
|
+
* @property {ReconcileSummary} summary
|
|
71
|
+
* @property {string} [error] - present only when the never-throws top-level guard fired.
|
|
72
|
+
*/
|
|
73
|
+
|
|
74
|
+
import { readFileSync } from 'node:fs';
|
|
75
|
+
import { isAbsolute, join } from 'node:path';
|
|
76
|
+
|
|
77
|
+
import { migrateLegacyLearning, normalizeLearning } from '../learnings/schema.mjs';
|
|
78
|
+
import { filterEligible } from './eligibility.mjs';
|
|
79
|
+
import { toActivationMetadata } from './emitter.mjs';
|
|
80
|
+
import { renderRule } from './renderer.mjs';
|
|
81
|
+
import { makeCandidateId, mergeCandidates as realMergeCandidates } from './idempotency.mjs';
|
|
82
|
+
|
|
83
|
+
/** Default repo-relative location of the learnings corpus. */
|
|
84
|
+
const DEFAULT_LEARNINGS_PATH = '.orchestrator/metrics/learnings.jsonl';
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Build a fully-zeroed result (the empty / error shape). Touches no disk.
|
|
88
|
+
* @param {string} [error]
|
|
89
|
+
* @returns {ReconcileResult}
|
|
90
|
+
*/
|
|
91
|
+
function zeroedResult(error) {
|
|
92
|
+
/** @type {ReconcileResult} */
|
|
93
|
+
const result = {
|
|
94
|
+
proposals: [],
|
|
95
|
+
rejected: [],
|
|
96
|
+
summary: {
|
|
97
|
+
totalLearnings: 0,
|
|
98
|
+
eligible: 0,
|
|
99
|
+
proposed: 0,
|
|
100
|
+
rejected: 0,
|
|
101
|
+
written: false,
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
if (typeof error === 'string') result.error = error;
|
|
105
|
+
return result;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Default learnings loader — read + parse `<repoRoot>/.orchestrator/metrics/learnings.jsonl`
|
|
110
|
+
* line-by-line, migrate/normalize records through the learnings schema SSOT,
|
|
111
|
+
* and skip blank/malformed lines. A missing file yields `[]`. Never throws (a
|
|
112
|
+
* read error degrades to `[]`).
|
|
113
|
+
*
|
|
114
|
+
* @param {string|undefined} repoRoot
|
|
115
|
+
* @returns {Array<Record<string, unknown>>}
|
|
116
|
+
*/
|
|
117
|
+
function defaultLoadLearnings(repoRoot) {
|
|
118
|
+
const root = typeof repoRoot === 'string' && repoRoot.length > 0 ? repoRoot : process.cwd();
|
|
119
|
+
const absPath = isAbsolute(DEFAULT_LEARNINGS_PATH)
|
|
120
|
+
? DEFAULT_LEARNINGS_PATH
|
|
121
|
+
: join(root, DEFAULT_LEARNINGS_PATH);
|
|
122
|
+
|
|
123
|
+
let raw;
|
|
124
|
+
try {
|
|
125
|
+
raw = readFileSync(absPath, 'utf8');
|
|
126
|
+
} catch {
|
|
127
|
+
return []; // ENOENT or any read error → empty corpus.
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** @type {Array<Record<string, unknown>>} */
|
|
131
|
+
const records = [];
|
|
132
|
+
for (const line of raw.split('\n')) {
|
|
133
|
+
const trimmed = line.trim();
|
|
134
|
+
if (trimmed.length === 0) continue;
|
|
135
|
+
let parsed;
|
|
136
|
+
try {
|
|
137
|
+
parsed = JSON.parse(trimmed);
|
|
138
|
+
} catch {
|
|
139
|
+
continue; // skip malformed line
|
|
140
|
+
}
|
|
141
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
142
|
+
records.push(
|
|
143
|
+
/** @type {Record<string, unknown>} */ (normalizeLearning(migrateLegacyLearning(parsed))),
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return records;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Best-effort logical key for a REJECTED learning (rejections never run the
|
|
152
|
+
* emitter, so there is no metadata.learningKey). Mirrors the emitter's key
|
|
153
|
+
* shape `${type}/${kebab(subject||title)}` when both halves are present; falls
|
|
154
|
+
* back to `null` when the type or subject/title is unusable. Never throws.
|
|
155
|
+
*
|
|
156
|
+
* @param {unknown} learning
|
|
157
|
+
* @returns {string|null}
|
|
158
|
+
*/
|
|
159
|
+
function rejectedLearningKey(learning) {
|
|
160
|
+
if (learning === null || typeof learning !== 'object' || Array.isArray(learning)) return null;
|
|
161
|
+
const rec = /** @type {Record<string, unknown>} */ (learning);
|
|
162
|
+
const type = typeof rec.type === 'string' && rec.type !== '' ? rec.type : '';
|
|
163
|
+
const subjectOrTitle =
|
|
164
|
+
(typeof rec.title === 'string' && rec.title !== '' ? rec.title : '') ||
|
|
165
|
+
(typeof rec.subject === 'string' && rec.subject !== '' ? rec.subject : '');
|
|
166
|
+
if (type === '' || subjectOrTitle === '') return null;
|
|
167
|
+
const kebab = subjectOrTitle
|
|
168
|
+
.toLowerCase()
|
|
169
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
170
|
+
.replace(/^-+|-+$/g, '');
|
|
171
|
+
return `${type}/${kebab}`;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Resolve the `type` string of a (possibly malformed) learning for the rejected
|
|
176
|
+
* record. Never throws.
|
|
177
|
+
* @param {unknown} learning
|
|
178
|
+
* @returns {string}
|
|
179
|
+
*/
|
|
180
|
+
function learningType(learning) {
|
|
181
|
+
if (learning && typeof learning === 'object' && !Array.isArray(learning)) {
|
|
182
|
+
const t = /** @type {Record<string, unknown>} */ (learning).type;
|
|
183
|
+
if (typeof t === 'string' && t.length > 0) return t;
|
|
184
|
+
}
|
|
185
|
+
return 'unknown';
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Build a sidecar ReconcileCandidate line-record (idempotency.mjs schema) for a
|
|
190
|
+
* proposed or rejected learning. `created_at` is stamped from the injectable
|
|
191
|
+
* clock so output stays deterministic under test.
|
|
192
|
+
*
|
|
193
|
+
* @param {Object} params
|
|
194
|
+
* @param {string} params.id
|
|
195
|
+
* @param {string|null} params.learningKey
|
|
196
|
+
* @param {string} params.slug
|
|
197
|
+
* @param {'proposed'|'rejected'} params.status
|
|
198
|
+
* @param {string} params.reason
|
|
199
|
+
* @param {number} params.confidence
|
|
200
|
+
* @param {string} params.createdAt - ISO timestamp.
|
|
201
|
+
* @returns {import('./idempotency.mjs').ReconcileCandidate}
|
|
202
|
+
*/
|
|
203
|
+
function buildCandidate({ id, learningKey, slug, status, reason, confidence, createdAt }) {
|
|
204
|
+
return {
|
|
205
|
+
id,
|
|
206
|
+
schema_version: 1,
|
|
207
|
+
learning_key: typeof learningKey === 'string' ? learningKey : '',
|
|
208
|
+
slug,
|
|
209
|
+
status,
|
|
210
|
+
reason,
|
|
211
|
+
confidence,
|
|
212
|
+
created_at: createdAt,
|
|
213
|
+
processed_at: null,
|
|
214
|
+
superseded_by: null,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Run the reconciliation engine.
|
|
220
|
+
*
|
|
221
|
+
* Composes the four leaf modules into the full proposal pipeline. NEVER throws —
|
|
222
|
+
* per-learning failures degrade to recorded rejections; any unexpected
|
|
223
|
+
* top-level error returns a zeroed result with an `error` field.
|
|
224
|
+
*
|
|
225
|
+
* The engine COMPUTES and RECORDS proposals; it NEVER writes `.claude/rules/`
|
|
226
|
+
* (that is FA3 / #696, post-approval). Its only disk write is into the
|
|
227
|
+
* reconcile-candidates sidecar (via the `merge` seam), skipped when `dryRun`.
|
|
228
|
+
*
|
|
229
|
+
* @param {Object} [params]
|
|
230
|
+
* @param {string} [params.repoRoot] - repo root; defaults to `process.cwd()` for the default loader/merge.
|
|
231
|
+
* @param {number} [params.ruleExpiryDays]- explicit rule expiry window (passed to the emitter).
|
|
232
|
+
* @param {number} [params.minRuleDays] - floor window (days) applied to the emitted `expires-at`
|
|
233
|
+
* so it never falls in the past (forwarded to the emitter — see `emitter.mjs`
|
|
234
|
+
* `computeExpiresAt`). Defaults internally (7d) when omitted.
|
|
235
|
+
* @param {number} [params.minInsightChars]- opt-in minimum insight length gating the
|
|
236
|
+
* eligibility placeholder-insight check (forwarded to `filterEligible`). Inert
|
|
237
|
+
* (no additional rejections) when omitted.
|
|
238
|
+
* @param {number|Date} [params.now] - injectable clock (emitter fallback + candidate `created_at`).
|
|
239
|
+
* @param {boolean} [params.dryRun] - when true, compute proposals but SKIP the merge entirely
|
|
240
|
+
* (also accepted as `opts.dryRun`; either location sets it).
|
|
241
|
+
* @param {Object} [opts] - DI seams (all default to real behaviour).
|
|
242
|
+
* @param {(repoRoot?: string) => Array<Record<string, unknown>>} [opts.loadLearnings]
|
|
243
|
+
* override the file read (REQUIRED for tests to avoid disk).
|
|
244
|
+
* @param {Array<Record<string, unknown>>} [opts.learnings]
|
|
245
|
+
* direct learnings injection (takes precedence over `loadLearnings`).
|
|
246
|
+
* @param {typeof realMergeCandidates} [opts.merge]
|
|
247
|
+
* override the sidecar merge (so tests never touch real `.orchestrator/runtime/`).
|
|
248
|
+
* @param {boolean} [opts.dryRun] - when true, compute proposals but SKIP the merge entirely.
|
|
249
|
+
* @returns {Promise<ReconcileResult>}
|
|
250
|
+
*/
|
|
251
|
+
export async function runReconcile(
|
|
252
|
+
{ repoRoot, ruleExpiryDays, minRuleDays, minInsightChars, now, dryRun: dryRunParam } = {},
|
|
253
|
+
opts = {},
|
|
254
|
+
) {
|
|
255
|
+
try {
|
|
256
|
+
// --- Resolve DI seams (real behaviour as defaults) ---------------------
|
|
257
|
+
// dryRun may arrive in the first arg (acceptance criterion 1) OR in opts
|
|
258
|
+
// (the documented DI seam) — either location flips it on.
|
|
259
|
+
const dryRun = dryRunParam === true || opts.dryRun === true;
|
|
260
|
+
const merge = typeof opts.merge === 'function' ? opts.merge : realMergeCandidates;
|
|
261
|
+
|
|
262
|
+
// --- Pipeline step 1 — load learnings ----------------------------------
|
|
263
|
+
/** @type {Array<Record<string, unknown>>} */
|
|
264
|
+
let learnings;
|
|
265
|
+
if (Array.isArray(opts.learnings)) {
|
|
266
|
+
learnings = opts.learnings;
|
|
267
|
+
} else if (typeof opts.loadLearnings === 'function') {
|
|
268
|
+
learnings = opts.loadLearnings(repoRoot);
|
|
269
|
+
} else {
|
|
270
|
+
learnings = defaultLoadLearnings(repoRoot);
|
|
271
|
+
}
|
|
272
|
+
if (!Array.isArray(learnings)) learnings = [];
|
|
273
|
+
|
|
274
|
+
const totalLearnings = learnings.length;
|
|
275
|
+
|
|
276
|
+
// --- Pipeline step 2 — empty short-circuit (touches no disk) ------------
|
|
277
|
+
if (totalLearnings === 0) {
|
|
278
|
+
return zeroedResult();
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Deterministic created_at for sidecar records (honours injectable clock).
|
|
282
|
+
const nowMs =
|
|
283
|
+
now instanceof Date ? now.getTime() : typeof now === 'number' ? now : Date.now();
|
|
284
|
+
const createdAt = new Date(nowMs).toISOString();
|
|
285
|
+
|
|
286
|
+
// --- Pipeline step 3 — partition ---------------------------------------
|
|
287
|
+
const { eligible, rejected: rejectedLearnings } = filterEligible(learnings, {
|
|
288
|
+
now: nowMs,
|
|
289
|
+
minInsightChars,
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
/** @type {ReconcileProposal[]} */
|
|
293
|
+
const proposals = [];
|
|
294
|
+
/** @type {ReconcileRejection[]} */
|
|
295
|
+
const rejected = [];
|
|
296
|
+
/** @type {import('./idempotency.mjs').ReconcileCandidate[]} */
|
|
297
|
+
const candidates = [];
|
|
298
|
+
|
|
299
|
+
// --- Pipeline step 4 — per eligible learning (wrapped per-item) ---------
|
|
300
|
+
for (const learning of eligible) {
|
|
301
|
+
try {
|
|
302
|
+
const metadata = toActivationMetadata(learning, { ruleExpiryDays, now, minRuleDays });
|
|
303
|
+
const { slug, path, content } = renderRule(learning, metadata);
|
|
304
|
+
const candidateId = makeCandidateId(metadata.learningKey, slug);
|
|
305
|
+
|
|
306
|
+
proposals.push({
|
|
307
|
+
learningKey: metadata.learningKey,
|
|
308
|
+
slug,
|
|
309
|
+
path,
|
|
310
|
+
content,
|
|
311
|
+
confidence: metadata.confidence,
|
|
312
|
+
candidateId,
|
|
313
|
+
status: 'proposed',
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
candidates.push(
|
|
317
|
+
buildCandidate({
|
|
318
|
+
id: candidateId,
|
|
319
|
+
learningKey: metadata.learningKey,
|
|
320
|
+
slug,
|
|
321
|
+
status: 'proposed',
|
|
322
|
+
reason: 'reconciliation engine proposed a conditional rule',
|
|
323
|
+
confidence: metadata.confidence,
|
|
324
|
+
createdAt,
|
|
325
|
+
}),
|
|
326
|
+
);
|
|
327
|
+
} catch (err) {
|
|
328
|
+
// A single bad learning must not crash the run — degrade to a rejection.
|
|
329
|
+
const msg = err && err.message ? err.message : String(err);
|
|
330
|
+
const learningKey = rejectedLearningKey(learning);
|
|
331
|
+
const reason = `emit/render error: ${msg}`;
|
|
332
|
+
rejected.push({
|
|
333
|
+
learningKey,
|
|
334
|
+
type: learningType(learning),
|
|
335
|
+
reason,
|
|
336
|
+
status: 'rejected',
|
|
337
|
+
});
|
|
338
|
+
candidates.push(
|
|
339
|
+
buildCandidate({
|
|
340
|
+
id: makeCandidateId(learningKey ?? '', `rejected-${learningType(learning)}`),
|
|
341
|
+
learningKey,
|
|
342
|
+
slug: '',
|
|
343
|
+
status: 'rejected',
|
|
344
|
+
reason,
|
|
345
|
+
confidence:
|
|
346
|
+
learning && typeof learning === 'object' && typeof learning.confidence === 'number'
|
|
347
|
+
? learning.confidence
|
|
348
|
+
: 0,
|
|
349
|
+
createdAt,
|
|
350
|
+
}),
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// --- Pipeline step 5 — per rejected learning (eligibility rejects) ------
|
|
356
|
+
for (const { learning, reason } of rejectedLearnings) {
|
|
357
|
+
const learningKey = rejectedLearningKey(learning);
|
|
358
|
+
const type = learningType(learning);
|
|
359
|
+
rejected.push({
|
|
360
|
+
learningKey,
|
|
361
|
+
type,
|
|
362
|
+
reason,
|
|
363
|
+
status: 'rejected',
|
|
364
|
+
});
|
|
365
|
+
candidates.push(
|
|
366
|
+
buildCandidate({
|
|
367
|
+
id: makeCandidateId(learningKey ?? '', `rejected-${type}`),
|
|
368
|
+
learningKey,
|
|
369
|
+
slug: '',
|
|
370
|
+
status: 'rejected',
|
|
371
|
+
reason,
|
|
372
|
+
confidence:
|
|
373
|
+
learning && typeof learning === 'object' && typeof learning.confidence === 'number'
|
|
374
|
+
? learning.confidence
|
|
375
|
+
: 0,
|
|
376
|
+
createdAt,
|
|
377
|
+
}),
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// --- Pipeline step 6 — record into the idempotency sidecar --------------
|
|
382
|
+
// The engine's ONLY disk write — and it is skipped entirely under dryRun.
|
|
383
|
+
// It never writes `.claude/rules/` (FA3 / #696 owns that, post-approval).
|
|
384
|
+
let written = false;
|
|
385
|
+
if (!dryRun) {
|
|
386
|
+
try {
|
|
387
|
+
const mergeResult = merge({ candidates, repoRoot });
|
|
388
|
+
written = !!(mergeResult && mergeResult.written === true);
|
|
389
|
+
} catch {
|
|
390
|
+
// Merge failure is non-fatal; proposals still returned, written stays false.
|
|
391
|
+
written = false;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// --- Pipeline step 7 — summary -----------------------------------------
|
|
396
|
+
return {
|
|
397
|
+
proposals,
|
|
398
|
+
rejected,
|
|
399
|
+
summary: {
|
|
400
|
+
totalLearnings,
|
|
401
|
+
eligible: eligible.length,
|
|
402
|
+
proposed: proposals.length,
|
|
403
|
+
rejected: rejected.length,
|
|
404
|
+
written,
|
|
405
|
+
},
|
|
406
|
+
};
|
|
407
|
+
} catch (err) {
|
|
408
|
+
// never-throws top-level guard.
|
|
409
|
+
const msg = err && err.message ? err.message : String(err);
|
|
410
|
+
return zeroedResult(msg);
|
|
411
|
+
}
|
|
412
|
+
}
|