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,471 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* auto-dream.mjs — Auto-Dream Post-Session Hook helper (issue #502, PRD F2.2).
|
|
3
|
+
*
|
|
4
|
+
* Provides the decision + I/O surface used by session-end Phase 3.6.5 and by
|
|
5
|
+
* /memory-cleanup --dry-run / --apply-pending. Decides whether the post-session
|
|
6
|
+
* dream should fire, writes the proposed diff to `.orchestrator/pending-dream.md`
|
|
7
|
+
* atomically, and applies it in a follow-up session.
|
|
8
|
+
*
|
|
9
|
+
* Decision inputs (PRD F2.2 acceptance criteria):
|
|
10
|
+
* - memory-cleanup-threshold (default 5) — sessions since last cleanup
|
|
11
|
+
* - memory-cleanup-soft-limit (default 180) — MEMORY.md line ceiling
|
|
12
|
+
* - kill-switch: threshold === 0 → never trigger
|
|
13
|
+
*
|
|
14
|
+
* Proposal-emit min-confidence filter (issue #566): the `auto-dream.min-confidence`
|
|
15
|
+
* key (default 0.5) is PARSED in `scripts/lib/config/auto-dream.mjs`, but its FILTER
|
|
16
|
+
* BEHAVIOR lives in `collectProposals()` (`scripts/lib/memory-proposals/collector.mjs`
|
|
17
|
+
* ~L319). Accepted trade-off (#589 MED-2): filter co-located with the queue it filters;
|
|
18
|
+
* renaming to a `memory.proposals.*` key would break #566's naming. Phase 3.6.3.
|
|
19
|
+
*
|
|
20
|
+
* No external deps — Node 20+ stdlib only.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { readFile, writeFile, rename, unlink, mkdir } from 'node:fs/promises';
|
|
24
|
+
import { existsSync, statSync } from 'node:fs';
|
|
25
|
+
import { randomUUID } from 'node:crypto';
|
|
26
|
+
import path from 'node:path';
|
|
27
|
+
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
// Signal reader — MEMORY.md size + sessions-since-last-cleanup
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Read decision signals used by `shouldDispatchAutoDream`.
|
|
34
|
+
*
|
|
35
|
+
* Returns:
|
|
36
|
+
* - memoryLines: line count of MEMORY.md (0 if absent).
|
|
37
|
+
* - lastCleanupAt: max `memory_cleanup_at` ISO timestamp across sessions.jsonl
|
|
38
|
+
* (null if no entry carries the field).
|
|
39
|
+
* - sessionsSinceCleanup: count of sessions.jsonl entries with `started_at >
|
|
40
|
+
* lastCleanupAt` (or total entries when lastCleanupAt is null).
|
|
41
|
+
* - sessionsFilePath: resolved path to sessions.jsonl (for diagnostics).
|
|
42
|
+
*
|
|
43
|
+
* Schema-additive: `memory_cleanup_at` is read but never assumed present.
|
|
44
|
+
*
|
|
45
|
+
* @param {object} args
|
|
46
|
+
* @param {string} args.repoRoot Absolute path to the repo root.
|
|
47
|
+
* @param {string} args.memoryDir Absolute path to the memory dir (use resolveMemoryDir() from './memory-paths.mjs').
|
|
48
|
+
* @returns {Promise<{memoryLines:number, lastCleanupAt:string|null, sessionsSinceCleanup:number, sessionsFilePath:string}>}
|
|
49
|
+
*/
|
|
50
|
+
export async function readDreamSignals({ repoRoot, memoryDir }) {
|
|
51
|
+
const memoryPath = path.join(memoryDir, 'MEMORY.md');
|
|
52
|
+
const sessionsFilePath = path.join(repoRoot, '.orchestrator', 'metrics', 'sessions.jsonl');
|
|
53
|
+
|
|
54
|
+
// 1. Line count of MEMORY.md (missing → 0)
|
|
55
|
+
let memoryLines = 0;
|
|
56
|
+
if (existsSync(memoryPath)) {
|
|
57
|
+
const raw = await readFile(memoryPath, 'utf8');
|
|
58
|
+
memoryLines = raw.length === 0 ? 0 : raw.split('\n').length;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// 2. Read sessions.jsonl entries to compute lastCleanupAt + sessionsSinceCleanup
|
|
62
|
+
let lastCleanupAt = null;
|
|
63
|
+
let sessionsSinceCleanup = 0;
|
|
64
|
+
|
|
65
|
+
if (existsSync(sessionsFilePath)) {
|
|
66
|
+
const raw = await readFile(sessionsFilePath, 'utf8');
|
|
67
|
+
const lines = raw.split('\n').filter((l) => l.length > 0);
|
|
68
|
+
const entries = [];
|
|
69
|
+
for (const line of lines) {
|
|
70
|
+
try {
|
|
71
|
+
entries.push(JSON.parse(line));
|
|
72
|
+
} catch {
|
|
73
|
+
// Malformed line — skip silently, this is a best-effort signal reader.
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Find the most recent memory_cleanup_at timestamp across all entries.
|
|
78
|
+
for (const entry of entries) {
|
|
79
|
+
const ts = entry.memory_cleanup_at;
|
|
80
|
+
if (typeof ts === 'string' && ts.length > 0) {
|
|
81
|
+
if (lastCleanupAt === null || ts > lastCleanupAt) {
|
|
82
|
+
lastCleanupAt = ts;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Count entries newer than the last cleanup (or total when no cleanup ever ran).
|
|
88
|
+
if (lastCleanupAt === null) {
|
|
89
|
+
sessionsSinceCleanup = entries.length;
|
|
90
|
+
} else {
|
|
91
|
+
for (const entry of entries) {
|
|
92
|
+
const startedAt = entry.started_at;
|
|
93
|
+
if (typeof startedAt === 'string' && startedAt > lastCleanupAt) {
|
|
94
|
+
sessionsSinceCleanup += 1;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return { memoryLines, lastCleanupAt, sessionsSinceCleanup, sessionsFilePath };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
// Decision function
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Decide whether session-end Phase 3.6.5 should dispatch /memory-cleanup --dry-run.
|
|
109
|
+
*
|
|
110
|
+
* Rules (PRD F2.2):
|
|
111
|
+
* - threshold === 0 → never trigger (kill-switch).
|
|
112
|
+
* - memoryLines > softLimit → trigger (size-based).
|
|
113
|
+
* - sessionsSinceLastCleanup >= threshold → trigger (cadence-based).
|
|
114
|
+
*
|
|
115
|
+
* The function is read-only — it never writes any file. Callers may pass an
|
|
116
|
+
* explicit `signals` object for testing; when omitted, readDreamSignals() is
|
|
117
|
+
* invoked.
|
|
118
|
+
*
|
|
119
|
+
* @param {object} args
|
|
120
|
+
* @param {string} args.repoRoot
|
|
121
|
+
* @param {string} args.memoryDir
|
|
122
|
+
* @param {number} [args.threshold=5] memory-cleanup-threshold from config.
|
|
123
|
+
* @param {number} [args.softLimit=180] memory-cleanup-soft-limit from config.
|
|
124
|
+
* @param {object} [args.signals] Pre-computed signals (skips disk reads).
|
|
125
|
+
* @returns {Promise<{trigger:boolean, reason:string, signals:object}>}
|
|
126
|
+
*/
|
|
127
|
+
export async function shouldDispatchAutoDream({
|
|
128
|
+
repoRoot,
|
|
129
|
+
memoryDir,
|
|
130
|
+
threshold = 5,
|
|
131
|
+
softLimit = 180,
|
|
132
|
+
signals,
|
|
133
|
+
} = {}) {
|
|
134
|
+
// Kill-switch first — bail before any I/O.
|
|
135
|
+
if (threshold === 0) {
|
|
136
|
+
return {
|
|
137
|
+
trigger: false,
|
|
138
|
+
reason: 'kill-switch (memory-cleanup-threshold=0)',
|
|
139
|
+
signals: signals ?? { memoryLines: 0, sessionsSinceCleanup: 0, lastCleanupAt: null },
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const resolved = signals ?? (await readDreamSignals({ repoRoot, memoryDir }));
|
|
144
|
+
|
|
145
|
+
// Size-based trigger first — overrides cadence.
|
|
146
|
+
if (resolved.memoryLines > softLimit) {
|
|
147
|
+
return {
|
|
148
|
+
trigger: true,
|
|
149
|
+
reason: `memory-soft-limit-exceeded (lines=${resolved.memoryLines} > softLimit=${softLimit})`,
|
|
150
|
+
signals: resolved,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Cadence-based trigger.
|
|
155
|
+
if (resolved.sessionsSinceCleanup >= threshold) {
|
|
156
|
+
return {
|
|
157
|
+
trigger: true,
|
|
158
|
+
reason: `cadence-threshold-met (sessions-since-cleanup=${resolved.sessionsSinceCleanup} >= threshold=${threshold})`,
|
|
159
|
+
signals: resolved,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
trigger: false,
|
|
165
|
+
reason: `under-thresholds (memory=${resolved.memoryLines}/${softLimit}, sessions=${resolved.sessionsSinceCleanup}/${threshold})`,
|
|
166
|
+
signals: resolved,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ---------------------------------------------------------------------------
|
|
171
|
+
// pending-dream.md — atomic write / read / apply
|
|
172
|
+
// ---------------------------------------------------------------------------
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Resolve the canonical pending-dream sidecar path. The file lives under
|
|
176
|
+
* `.orchestrator/pending-dream.md` relative to the repo root so it survives
|
|
177
|
+
* across sessions while staying out of the vault-mirror scope.
|
|
178
|
+
*
|
|
179
|
+
* @param {string} repoRoot
|
|
180
|
+
* @returns {string}
|
|
181
|
+
*/
|
|
182
|
+
function pendingDreamPath(repoRoot) {
|
|
183
|
+
return path.join(repoRoot, '.orchestrator', 'pending-dream.md');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Write the proposed dream diff to `.orchestrator/pending-dream.md` atomically.
|
|
188
|
+
*
|
|
189
|
+
* Caller supplies the body (a Markdown document containing the diff and any
|
|
190
|
+
* narrative). This helper prepends a minimal YAML frontmatter block carrying
|
|
191
|
+
* the metadata session-end's Final Report and the next session's --apply-pending
|
|
192
|
+
* step both rely on.
|
|
193
|
+
*
|
|
194
|
+
* Atomicity: write to `<path>.<rand>.tmp`, then rename(). Same-fs rename is
|
|
195
|
+
* atomic on POSIX — observers see either the previous file or the new one,
|
|
196
|
+
* never a half-written intermediate.
|
|
197
|
+
*
|
|
198
|
+
* @param {object} args
|
|
199
|
+
* @param {string} args.repoRoot
|
|
200
|
+
* @param {string} args.diff Complete-replacement MEMORY.md body, in exactly one
|
|
201
|
+
* fenced ```markdown block (never git-style unified-diff hunks) — see the
|
|
202
|
+
* Serialisation contract in `skills/memory-cleanup/SKILL.md`.
|
|
203
|
+
* @param {string} [args.sourceSession] Session id that produced the proposal.
|
|
204
|
+
* @param {number} [args.memoryLinesBefore] MEMORY.md line count before.
|
|
205
|
+
* @param {number} [args.proposedLinesAfter] MEMORY.md line count after the proposed diff.
|
|
206
|
+
* @returns {Promise<{path:string, bytes:number}>}
|
|
207
|
+
*/
|
|
208
|
+
export async function writePendingDream({
|
|
209
|
+
repoRoot,
|
|
210
|
+
diff,
|
|
211
|
+
sourceSession = null,
|
|
212
|
+
memoryLinesBefore = null,
|
|
213
|
+
proposedLinesAfter = null,
|
|
214
|
+
}) {
|
|
215
|
+
if (typeof diff !== 'string' || diff.trim().length === 0) {
|
|
216
|
+
throw new TypeError('writePendingDream: diff must be a non-empty string');
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const target = pendingDreamPath(repoRoot);
|
|
220
|
+
await mkdir(path.dirname(target), { recursive: true });
|
|
221
|
+
|
|
222
|
+
const generatedAt = new Date().toISOString();
|
|
223
|
+
const frontmatter = [
|
|
224
|
+
'---',
|
|
225
|
+
`generated_at: ${generatedAt}`,
|
|
226
|
+
`source_session: ${JSON.stringify(sourceSession ?? 'unknown')}`,
|
|
227
|
+
`memory_lines_before: ${JSON.stringify(memoryLinesBefore ?? null)}`,
|
|
228
|
+
`proposed_lines_after: ${JSON.stringify(proposedLinesAfter ?? null)}`,
|
|
229
|
+
'---',
|
|
230
|
+
'',
|
|
231
|
+
].join('\n');
|
|
232
|
+
|
|
233
|
+
const content = `${frontmatter}${diff}${diff.endsWith('\n') ? '' : '\n'}`;
|
|
234
|
+
const tmp = `${target}.${randomUUID().slice(0, 8)}.tmp`;
|
|
235
|
+
await writeFile(tmp, content, 'utf8');
|
|
236
|
+
await rename(tmp, target);
|
|
237
|
+
|
|
238
|
+
return { path: target, bytes: Buffer.byteLength(content, 'utf8') };
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Read `.orchestrator/pending-dream.md` if present. Returns the raw file body
|
|
243
|
+
* (including frontmatter) so callers can decide how to parse it. Returns null
|
|
244
|
+
* when the file is absent.
|
|
245
|
+
*
|
|
246
|
+
* @param {object} args
|
|
247
|
+
* @param {string} args.repoRoot
|
|
248
|
+
* @returns {Promise<string|null>}
|
|
249
|
+
*/
|
|
250
|
+
export async function readPendingDream({ repoRoot }) {
|
|
251
|
+
const target = pendingDreamPath(repoRoot);
|
|
252
|
+
if (!existsSync(target)) return null;
|
|
253
|
+
return readFile(target, 'utf8');
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Internal: parse the YAML-style frontmatter block at the top of a
|
|
258
|
+
* pending-dream.md file. Returns the parsed fields and the body that follows.
|
|
259
|
+
*
|
|
260
|
+
* The frontmatter is intentionally hand-rolled (no yaml dep) — it only carries
|
|
261
|
+
* the three flat keys writePendingDream() emits.
|
|
262
|
+
*
|
|
263
|
+
* @param {string} content
|
|
264
|
+
* @returns {{frontmatter: Record<string,string>, body: string}}
|
|
265
|
+
*/
|
|
266
|
+
function parsePendingDream(content) {
|
|
267
|
+
const lines = content.split('\n');
|
|
268
|
+
if (lines[0] !== '---') {
|
|
269
|
+
return { frontmatter: {}, body: content };
|
|
270
|
+
}
|
|
271
|
+
const frontmatter = {};
|
|
272
|
+
let i = 1;
|
|
273
|
+
while (i < lines.length && lines[i] !== '---') {
|
|
274
|
+
const line = lines[i];
|
|
275
|
+
const idx = line.indexOf(':');
|
|
276
|
+
if (idx > 0) {
|
|
277
|
+
const key = line.slice(0, idx).trim();
|
|
278
|
+
const val = line.slice(idx + 1).trim();
|
|
279
|
+
frontmatter[key] = val;
|
|
280
|
+
}
|
|
281
|
+
i += 1;
|
|
282
|
+
}
|
|
283
|
+
// Skip the closing '---' and an optional blank separator line.
|
|
284
|
+
let bodyStart = i + 1;
|
|
285
|
+
if (lines[bodyStart] === '') bodyStart += 1;
|
|
286
|
+
return { frontmatter, body: lines.slice(bodyStart).join('\n') };
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Internal: extract a fenced code block tagged as `diff` or `markdown` from
|
|
291
|
+
* the body, returning its inner contents. When no fenced diff is present,
|
|
292
|
+
* returns the full body verbatim — letting --dry-run drop a freeform proposal.
|
|
293
|
+
*
|
|
294
|
+
* @param {string} body
|
|
295
|
+
* @returns {string}
|
|
296
|
+
*/
|
|
297
|
+
function extractDiffBlock(body) {
|
|
298
|
+
const fenceMatch = body.match(/```(?:diff|markdown)?\n([\s\S]*?)```/);
|
|
299
|
+
if (fenceMatch) return fenceMatch[1];
|
|
300
|
+
return body;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Internal: detect whether an extracted block is a git-style unified diff
|
|
305
|
+
* (hunks meant to be applied against an existing file) rather than a
|
|
306
|
+
* complete-replacement Markdown body (issue #717).
|
|
307
|
+
*
|
|
308
|
+
* Detection is keyed on git-diff-SPECIFIC line signatures — deliberately NOT
|
|
309
|
+
* a bare `^[+-]` / `^- ` matcher, because MEMORY.md legitimately contains
|
|
310
|
+
* markdown bullets (`- [Title](file.md) — hook`) that such a naive matcher
|
|
311
|
+
* would false-positive on.
|
|
312
|
+
*
|
|
313
|
+
* @param {string} block
|
|
314
|
+
* @returns {boolean}
|
|
315
|
+
*/
|
|
316
|
+
function isGitStyleDiff(block) {
|
|
317
|
+
return /^--- /m.test(block) || /^\+\+\+ /m.test(block) || /^@@ .* @@/m.test(block);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Internal: count fenced ```diff / ```markdown (or untagged ```) code blocks
|
|
322
|
+
* in a raw sidecar body. More than one such fence is the multi-file /
|
|
323
|
+
* multi-hunk sidecar signature (issue #717) — `extractDiffBlock()` only ever
|
|
324
|
+
* consumes the FIRST fence, so a multi-fence body silently drops every
|
|
325
|
+
* subsequent hunk/section if applied naively.
|
|
326
|
+
*
|
|
327
|
+
* @param {string} body
|
|
328
|
+
* @returns {number}
|
|
329
|
+
*/
|
|
330
|
+
function countFencedBlocks(body) {
|
|
331
|
+
const matches = body.match(/```(?:diff|markdown)?\n[\s\S]*?```/g);
|
|
332
|
+
return matches ? matches.length : 0;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Internal: detect a fenced code block tagged with a language extractDiffBlock()
|
|
337
|
+
* does not recognize (issue #720) — e.g. ```js, ```python. extractDiffBlock()'s
|
|
338
|
+
* regex only matches `diff` / `markdown` / untagged (bare ``` immediately
|
|
339
|
+
* followed by a newline) fences; any OTHER tag word sits between the opening
|
|
340
|
+
* ``` and the required newline, so the match fails outright and
|
|
341
|
+
* extractDiffBlock() falls back to returning the RAW body verbatim — literal
|
|
342
|
+
* fence markers included — which would otherwise be written straight into
|
|
343
|
+
* MEMORY.md unrefused.
|
|
344
|
+
*
|
|
345
|
+
* Detection is keyed on `extractedBlock === body` (extraction found nothing to
|
|
346
|
+
* strip) AND the body containing an actual fence-opening line. The first half
|
|
347
|
+
* of that conjunction alone is not sufficient: a genuinely fence-free freeform
|
|
348
|
+
* body (issue #502's accepted verbatim-fallback contract) also has
|
|
349
|
+
* `extractedBlock === body`, and must NOT be refused.
|
|
350
|
+
*
|
|
351
|
+
* @param {string} body
|
|
352
|
+
* @param {string} extractedBlock
|
|
353
|
+
* @returns {boolean}
|
|
354
|
+
*/
|
|
355
|
+
function hasUnrecognizedFence(body, extractedBlock) {
|
|
356
|
+
return extractedBlock === body && /^```/m.test(body);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Apply the pending dream by overwriting MEMORY.md with the body the
|
|
361
|
+
* proposal carries, then deleting the sidecar.
|
|
362
|
+
*
|
|
363
|
+
* Behaviour (PRD F2.2):
|
|
364
|
+
* - Returns `{ applied: false, reason: 'missing' }` when no sidecar exists.
|
|
365
|
+
* - Returns `{ applied: false, reason: 'stale' }` when the sidecar is older
|
|
366
|
+
* than 14 days (caller should re-run --dry-run).
|
|
367
|
+
* - Returns `{ applied: false, reason: 'stale-index', driftMs }` (#788) when
|
|
368
|
+
* MEMORY.md's mtime is newer than the sidecar's `generated_at` — MEMORY.md
|
|
369
|
+
* was updated BETWEEN the producing --dry-run and this apply, so a
|
|
370
|
+
* complete-replacement write would clobber those interim edits. MEMORY.md
|
|
371
|
+
* is left untouched and the sidecar is PRESERVED so the caller can re-run
|
|
372
|
+
* --dry-run against the current MEMORY.md. A missing MEMORY.md never
|
|
373
|
+
* triggers this (nothing to clobber). Strict `>`: equal mtimes are clock
|
|
374
|
+
* resolution noise, not a drift signal.
|
|
375
|
+
* - Returns `{ applied: false, reason: 'unsupported-format' }` (#717) when
|
|
376
|
+
* the extracted block is a git-style diff (`isGitStyleDiff()`), the raw
|
|
377
|
+
* sidecar body contains more than one fenced block (`countFencedBlocks()`
|
|
378
|
+
* > 1), or the body contains a fence tagged with an unrecognized language
|
|
379
|
+
* (`hasUnrecognizedFence()`, issue #720 — e.g. ```js, ```python; only
|
|
380
|
+
* `diff` / `markdown` / untagged fences are recognized). MEMORY.md is left
|
|
381
|
+
* untouched and the sidecar is PRESERVED (not unlinked) so the proposal
|
|
382
|
+
* can be regenerated in the correct format.
|
|
383
|
+
* - On success: deletes the sidecar, returns line-count deltas.
|
|
384
|
+
*
|
|
385
|
+
* The proposal is expected to embed the *complete* replacement body of
|
|
386
|
+
* MEMORY.md inside a single fenced ```` ```diff ```` or ```` ```markdown ````
|
|
387
|
+
* block. Free-form bodies are accepted but treated as the full replacement
|
|
388
|
+
* verbatim — the dry-run subagent owns the format; this helper is the
|
|
389
|
+
* consumer, and it refuses formats it cannot safely apply (see above).
|
|
390
|
+
*
|
|
391
|
+
* @param {object} args
|
|
392
|
+
* @param {string} args.repoRoot
|
|
393
|
+
* @param {string} args.memoryDir
|
|
394
|
+
* @param {number} [args.maxAgeDays=14]
|
|
395
|
+
* @returns {Promise<{applied:boolean, reason?:string, ageDays?:number, driftMs?:number, linesBefore?:number, linesAfter?:number, path?:string}>}
|
|
396
|
+
*/
|
|
397
|
+
export async function applyPendingDream({ repoRoot, memoryDir, maxAgeDays = 14 }) {
|
|
398
|
+
const target = pendingDreamPath(repoRoot);
|
|
399
|
+
if (!existsSync(target)) {
|
|
400
|
+
return { applied: false, reason: 'missing' };
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const content = await readFile(target, 'utf8');
|
|
404
|
+
const { frontmatter, body } = parsePendingDream(content);
|
|
405
|
+
|
|
406
|
+
const memoryPath = path.join(memoryDir, 'MEMORY.md');
|
|
407
|
+
|
|
408
|
+
// Staleness check — refuse if generated_at is older than maxAgeDays.
|
|
409
|
+
const generatedAt = frontmatter.generated_at;
|
|
410
|
+
if (typeof generatedAt === 'string' && generatedAt.length > 0) {
|
|
411
|
+
const generatedMs = Date.parse(generatedAt);
|
|
412
|
+
if (!Number.isNaN(generatedMs)) {
|
|
413
|
+
const ageDays = (Date.now() - generatedMs) / (1000 * 60 * 60 * 24);
|
|
414
|
+
if (ageDays > maxAgeDays) {
|
|
415
|
+
return { applied: false, reason: 'stale', ageDays: Math.round(ageDays) };
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// Drift check (#788) — refuse if MEMORY.md changed AFTER the producing
|
|
419
|
+
// --dry-run. applyPendingDream is a complete-replacement write frozen on
|
|
420
|
+
// the dry-run snapshot; an interim MEMORY.md edit would be silently
|
|
421
|
+
// clobbered. A missing MEMORY.md has nothing to clobber.
|
|
422
|
+
//
|
|
423
|
+
// Resolution note: `generated_at` is `new Date().toISOString()`, truncated
|
|
424
|
+
// to whole milliseconds, whereas statSync().mtimeMs carries sub-ms
|
|
425
|
+
// (nanosecond) precision on APFS/ext4. Comparing them raw makes a
|
|
426
|
+
// MEMORY.md written in the SAME millisecond as the sidecar (mtimeMs
|
|
427
|
+
// fractionally > the floored generatedMs) look like drift. Floor mtimeMs
|
|
428
|
+
// to the resolution `generated_at` actually has, then apply strict `>`
|
|
429
|
+
// (mirrors `ageDays > maxAgeDays`): same-millisecond = clock-resolution
|
|
430
|
+
// noise, not a drift signal; a later write (≥1ms) is a real signal.
|
|
431
|
+
if (existsSync(memoryPath)) {
|
|
432
|
+
const memoryMtimeMs = statSync(memoryPath).mtimeMs;
|
|
433
|
+
if (Math.floor(memoryMtimeMs) > generatedMs) {
|
|
434
|
+
return { applied: false, reason: 'stale-index', driftMs: Math.round(memoryMtimeMs - generatedMs) };
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
let linesBefore = 0;
|
|
441
|
+
if (existsSync(memoryPath)) {
|
|
442
|
+
const raw = await readFile(memoryPath, 'utf8');
|
|
443
|
+
linesBefore = raw.length === 0 ? 0 : raw.split('\n').length;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
const extractedBlock = extractDiffBlock(body);
|
|
447
|
+
|
|
448
|
+
// Guard (#717, extended #720): refuse formats this applier cannot safely
|
|
449
|
+
// consume. A git-style diff (hunks), a multi-fence body, or a fence tagged
|
|
450
|
+
// with a language extractDiffBlock() doesn't recognize (e.g. ```js) written
|
|
451
|
+
// verbatim (fence markers included) as the new MEMORY.md would corrupt the
|
|
452
|
+
// index / silently drop subsequent sections / pollute MEMORY.md with a
|
|
453
|
+
// literal fence marker. Preserve the sidecar — do NOT write, do NOT unlink
|
|
454
|
+
// — so the operator can regenerate a complete-body proposal via --dry-run.
|
|
455
|
+
if (isGitStyleDiff(extractedBlock) || countFencedBlocks(body) > 1 || hasUnrecognizedFence(body, extractedBlock)) {
|
|
456
|
+
return { applied: false, reason: 'unsupported-format' };
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
const newBody = extractedBlock.trimEnd() + '\n';
|
|
460
|
+
await mkdir(path.dirname(memoryPath), { recursive: true });
|
|
461
|
+
const tmp = `${memoryPath}.${randomUUID().slice(0, 8)}.tmp`;
|
|
462
|
+
await writeFile(tmp, newBody, 'utf8');
|
|
463
|
+
await rename(tmp, memoryPath);
|
|
464
|
+
|
|
465
|
+
const linesAfter = newBody.length === 0 ? 0 : newBody.split('\n').length;
|
|
466
|
+
|
|
467
|
+
// Consume the sidecar — success leaves no pending file behind.
|
|
468
|
+
await unlink(target);
|
|
469
|
+
|
|
470
|
+
return { applied: true, linesBefore, linesAfter, path: memoryPath };
|
|
471
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* autonomy/suitability.mjs — pure four-gate suitability verdict for autonomous
|
|
3
|
+
* dispatch (GitLab issue #680, Epic #673 cross-repo dispatcher).
|
|
4
|
+
*
|
|
5
|
+
* `computeSuitabilityVerdict(deps)` answers ONE question: "is this repo/run a
|
|
6
|
+
* SAFE candidate for autonomous action right now?" It is a pure decision function
|
|
7
|
+
* over four AND-composed gates — confidence, kill-switch rate, CI status, and
|
|
8
|
+
* resource verdict. Every signal is INJECTED by the caller (the dispatcher); the
|
|
9
|
+
* engine reads no files and calls nothing external (no checkCiStatus / evaluate /
|
|
10
|
+
* selectMode). The dispatcher supplies the snapshots; this module only judges them.
|
|
11
|
+
*
|
|
12
|
+
* THE FOUR-GATE CONTRACT (suitable = G1 && G2 && G3 && G4):
|
|
13
|
+
* G1 confidenceOk = Number.isFinite(confidence) && confidence >= confidenceFloor
|
|
14
|
+
* (fail-closed: null/undefined/NaN ⇒ false)
|
|
15
|
+
* G2 killSwitchOk = recentRuns.length < 5 ? PASS (omitted, warned)
|
|
16
|
+
* : (fired / N) < 0.2
|
|
17
|
+
* (non-array recentRuns ⇒ [] ⇒ omitted+warned; malformed
|
|
18
|
+
* run records / non-string kill_switch ⇒ NOT-fired)
|
|
19
|
+
* G3 ciNotRed = ci.status !== 'red' (null, 'unknown', AND malformed
|
|
20
|
+
* ci — non-object / missing status / non-string status —
|
|
21
|
+
* are "no signal" ⇒ pass + warn)
|
|
22
|
+
* G4 resourceNotCritical = String(resourceVerdict).toLowerCase().trim() !== 'critical'
|
|
23
|
+
* (green/warn/degraded/null pass; 'CRITICAL' IS caught;
|
|
24
|
+
* non-string ⇒ "no signal" ⇒ pass + warn)
|
|
25
|
+
*
|
|
26
|
+
* PRD override "CI red OR resource critical ⇒ non-suitable REGARDLESS of
|
|
27
|
+
* confidence" needs no special branch — it is satisfied by the pure AND. The
|
|
28
|
+
* `forcedFail` flag is computed ONLY to word the rationale.
|
|
29
|
+
*
|
|
30
|
+
* Mirrors engine.mjs: the `Number.isFinite && >=` fail-closed gate, the
|
|
31
|
+
* [0,1]-clamped floor coercion (default 0.5, NEVER 0), the `detailParts.join('; ')`
|
|
32
|
+
* rationale style (engine.mjs L481-485), and the never-throws contract.
|
|
33
|
+
*
|
|
34
|
+
* Pure + DI, NEVER throws, NO I/O. NOT wired into dispatcher launch this session
|
|
35
|
+
* (that is #682). No imports beyond node builtins (none needed).
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
const DEFAULT_CONFIDENCE_FLOOR = 0.5;
|
|
39
|
+
const KILL_SWITCH_MIN_RUNS = 5; // below this the kill-switch gate is OMITTED (treated as PASS)
|
|
40
|
+
const KILL_SWITCH_MAX_RATE = 0.2; // fired/N must be strictly below this
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @typedef {Object} SuitabilityDeps
|
|
44
|
+
* @property {'off'|'advisory'|'autonomous-gated'} [autonomy]
|
|
45
|
+
* Autonomy dial. Forward-compat ONLY — does NOT gate `suitable`. When
|
|
46
|
+
* 'off' an advisory warning is pushed, but the verdict is unchanged.
|
|
47
|
+
* @property {number} [confidenceFloor=0.5]
|
|
48
|
+
* Minimum confidence. Coerced like engine.mjs: finite && in [0,1] else 0.5.
|
|
49
|
+
* @property {number} confidence
|
|
50
|
+
* mode-selector 0..1 float (injected). null/undefined/NaN ⇒ G1 FAILS.
|
|
51
|
+
* @property {null | { status?: ('green'|'red'|'unknown'), ok?: boolean }} [ci]
|
|
52
|
+
* checkCiStatus result. null = no signal (passes G3, warns). Malformed
|
|
53
|
+
* (non-object / missing status / non-string status) is treated as
|
|
54
|
+
* absent (passes G3 + 'CI signal malformed' warning) — never throws.
|
|
55
|
+
* @property {'green'|'warn'|'degraded'|'critical'|null} [resourceVerdict]
|
|
56
|
+
* Resource verdict. null = no signal (passes G4, warns). A string is
|
|
57
|
+
* lowercase-trim-normalized before the !== 'critical' check (so
|
|
58
|
+
* 'CRITICAL' fails). A non-string non-null value is "no signal"
|
|
59
|
+
* (passes G4 + 'resource signal malformed' warning) — never throws.
|
|
60
|
+
* @property {Array<{ kill_switch?: string|null }>} [recentRuns]
|
|
61
|
+
* Repo-scoped most-recent-N autopilot run records (caller supplies; the
|
|
62
|
+
* engine never reads autopilot.jsonl). A non-array value is coerced to
|
|
63
|
+
* [] (G2 omitted + warning). Null/non-object records and non-string
|
|
64
|
+
* kill_switch fields count as NOT-fired — never throws.
|
|
65
|
+
*/
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* @typedef {Object} SuitabilityVerdict
|
|
69
|
+
* @property {boolean} suitable The four-gate AND.
|
|
70
|
+
* @property {number} confidence The validated injected confidence (NaN/missing ⇒ 0 echo).
|
|
71
|
+
* @property {string} rationale One line, segments joined with '; '.
|
|
72
|
+
* @property {string[]} warnings Omission / absent-signal / dial advisory notes.
|
|
73
|
+
*/
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Coerce the confidence floor exactly as engine.mjs does: a finite number in the
|
|
77
|
+
* closed interval [0,1] is honoured; everything else (null/undefined/NaN/string/
|
|
78
|
+
* out-of-range) falls back to the safe default 0.5 — NEVER 0.
|
|
79
|
+
* @param {unknown} rawFloor
|
|
80
|
+
* @returns {number}
|
|
81
|
+
*/
|
|
82
|
+
function coerceFloor(rawFloor) {
|
|
83
|
+
const coerced =
|
|
84
|
+
typeof rawFloor === 'number'
|
|
85
|
+
? rawFloor
|
|
86
|
+
: typeof rawFloor === 'string' && rawFloor.trim() !== ''
|
|
87
|
+
? Number(rawFloor)
|
|
88
|
+
: NaN;
|
|
89
|
+
return Number.isFinite(coerced) && coerced >= 0 && coerced <= 1 ? coerced : DEFAULT_CONFIDENCE_FLOOR;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Compute the autonomous-dispatch suitability verdict over four injected signal
|
|
94
|
+
* gates. PURE, NEVER throws, NO I/O. The caller (dispatcher) is responsible for
|
|
95
|
+
* gathering ci / resourceVerdict / confidence / recentRuns and acting on the
|
|
96
|
+
* verdict (#682 wires this into launch).
|
|
97
|
+
*
|
|
98
|
+
* @param {SuitabilityDeps} [deps]
|
|
99
|
+
* @returns {SuitabilityVerdict}
|
|
100
|
+
*/
|
|
101
|
+
export function computeSuitabilityVerdict(deps = {}) {
|
|
102
|
+
const {
|
|
103
|
+
autonomy,
|
|
104
|
+
confidenceFloor: rawFloor,
|
|
105
|
+
confidence: rawConfidence,
|
|
106
|
+
ci = null,
|
|
107
|
+
resourceVerdict = null,
|
|
108
|
+
recentRuns = [],
|
|
109
|
+
} = deps && typeof deps === 'object' ? deps : {};
|
|
110
|
+
|
|
111
|
+
/** @type {string[]} */
|
|
112
|
+
const warnings = [];
|
|
113
|
+
|
|
114
|
+
const floor = coerceFloor(rawFloor);
|
|
115
|
+
|
|
116
|
+
// --- G1 — confidence gate (fail-closed: null/undefined/NaN ⇒ false) --------
|
|
117
|
+
const confidenceOk = Number.isFinite(rawConfidence) && rawConfidence >= floor;
|
|
118
|
+
// Echo the validated confidence; NaN/missing ⇒ 0 (G1 already failed regardless).
|
|
119
|
+
const confidenceEcho = Number.isFinite(rawConfidence) ? rawConfidence : 0;
|
|
120
|
+
|
|
121
|
+
// --- G2 — kill-switch rate gate (OMITTED below the run-count floor) --------
|
|
122
|
+
// Defensive: a non-array recentRuns (undefined/null/string/object — e.g. a
|
|
123
|
+
// malformed autopilot.jsonl read) is coerced to [] and warned, never thrown.
|
|
124
|
+
const recentRunsIsArray = Array.isArray(recentRuns);
|
|
125
|
+
const runs = recentRunsIsArray ? recentRuns : [];
|
|
126
|
+
if (!recentRunsIsArray && recentRuns !== undefined && recentRuns !== null) {
|
|
127
|
+
warnings.push('recentRuns not an array — kill-switch gate omitted');
|
|
128
|
+
}
|
|
129
|
+
const n = runs.length;
|
|
130
|
+
let killSwitchOk;
|
|
131
|
+
let killSwitchSegment;
|
|
132
|
+
if (n < KILL_SWITCH_MIN_RUNS) {
|
|
133
|
+
killSwitchOk = true; // OMITTED ⇒ treat as PASS (insufficient signal)
|
|
134
|
+
warnings.push(`kill-switch signal omitted: only ${n}<${KILL_SWITCH_MIN_RUNS} runs`);
|
|
135
|
+
killSwitchSegment = `omitted (${n}<${KILL_SWITCH_MIN_RUNS} runs)`;
|
|
136
|
+
} else {
|
|
137
|
+
let fired = 0;
|
|
138
|
+
for (const run of runs) {
|
|
139
|
+
// Defensive: a null / non-object run, a record MISSING kill_switch, or a
|
|
140
|
+
// kill_switch of a non-string type (number/object/boolean) all count as
|
|
141
|
+
// NOT-fired. ONLY a non-empty string counts as fired. Never throws.
|
|
142
|
+
const ks = run && typeof run === 'object' ? run.kill_switch : undefined;
|
|
143
|
+
if (typeof ks === 'string' && ks.length > 0) fired += 1;
|
|
144
|
+
}
|
|
145
|
+
const rate = fired / n;
|
|
146
|
+
killSwitchOk = rate < KILL_SWITCH_MAX_RATE;
|
|
147
|
+
const ratePct = Math.round(rate * 100) / 100;
|
|
148
|
+
killSwitchSegment = `${fired}/${n} fired rate=${ratePct} ${killSwitchOk ? 'ok' : 'FAIL'}`;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// --- G3 — CI gate (null & 'unknown' pass; only 'red' fails) ----------------
|
|
152
|
+
// Defensive: `ci` present but not an object, an object without `status`, or a
|
|
153
|
+
// non-string `status` ⇒ "no signal" (gate PASSES + warning), NEVER a crash.
|
|
154
|
+
// ONLY a literal `status === 'red'` fails the gate.
|
|
155
|
+
const ciAbsent = ci === null || ci === undefined;
|
|
156
|
+
const ciStatus = !ciAbsent && typeof ci === 'object' && typeof ci.status === 'string' ? ci.status : null;
|
|
157
|
+
const ciMalformed = !ciAbsent && ciStatus === null; // present but unusable
|
|
158
|
+
const ciNotRed = ciStatus !== 'red'; // null status (absent/malformed) passes
|
|
159
|
+
if (ciAbsent) warnings.push('CI signal absent');
|
|
160
|
+
else if (ciMalformed) warnings.push('CI signal malformed — treated as absent');
|
|
161
|
+
const ciSegment = ciAbsent ? 'absent' : ciMalformed ? 'malformed' : `${ciStatus}`;
|
|
162
|
+
|
|
163
|
+
// --- G4 — resource gate (green/warn/degraded/null pass; only 'critical' fails)
|
|
164
|
+
// Defensive: normalize a STRING verdict via toLowerCase().trim() so 'CRITICAL'
|
|
165
|
+
// / ' critical ' are correctly caught (only exact lowercase 'critical' fails).
|
|
166
|
+
// A non-string, non-null verdict (number/object/bool) is "no signal" ⇒ PASS +
|
|
167
|
+
// warning, never a crash. null/undefined = absent (passes, warns).
|
|
168
|
+
const resourceAbsent = resourceVerdict === null || resourceVerdict === undefined;
|
|
169
|
+
const resourceIsString = typeof resourceVerdict === 'string';
|
|
170
|
+
const resourceMalformed = !resourceAbsent && !resourceIsString; // present but unusable
|
|
171
|
+
const resourceNorm = resourceIsString ? resourceVerdict.toLowerCase().trim() : null;
|
|
172
|
+
const resourceNotCritical = resourceNorm !== 'critical'; // non-string/absent ⇒ passes
|
|
173
|
+
if (resourceAbsent) warnings.push('resource signal absent');
|
|
174
|
+
else if (resourceMalformed) warnings.push('resource signal malformed — treated as absent');
|
|
175
|
+
const resourceSegment = resourceAbsent
|
|
176
|
+
? 'absent'
|
|
177
|
+
: resourceMalformed
|
|
178
|
+
? 'malformed'
|
|
179
|
+
: `${resourceNorm}`;
|
|
180
|
+
|
|
181
|
+
// --- Autonomy dial (forward-compat advisory ONLY — never flips `suitable`) -
|
|
182
|
+
if (autonomy === 'off') {
|
|
183
|
+
warnings.push('autonomy off — verdict advisory only; caller will confirm');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// --- The four-gate AND -----------------------------------------------------
|
|
187
|
+
const suitable = confidenceOk && killSwitchOk && ciNotRed && resourceNotCritical;
|
|
188
|
+
|
|
189
|
+
// `forcedFail` exists ONLY to word the rationale — the PRD override is already
|
|
190
|
+
// satisfied by the pure AND above (CI red / resource critical fail their gate).
|
|
191
|
+
const forcedFail = !ciNotRed || !resourceNotCritical;
|
|
192
|
+
|
|
193
|
+
// --- Rationale (segments joined with '; ', mirroring engine.mjs L481-485) --
|
|
194
|
+
const detailParts = [`suitable=${suitable}`];
|
|
195
|
+
if (forcedFail) {
|
|
196
|
+
const reasons = [];
|
|
197
|
+
if (!ciNotRed) reasons.push('CI red');
|
|
198
|
+
if (!resourceNotCritical) reasons.push('resource critical');
|
|
199
|
+
detailParts.push(`FORCED: ${reasons.join(' + ')}`);
|
|
200
|
+
}
|
|
201
|
+
detailParts.push(`confidence ${confidenceEcho}>=${floor} ${confidenceOk ? 'ok' : 'FAIL'}`);
|
|
202
|
+
detailParts.push(`kill-switch ${killSwitchSegment}`);
|
|
203
|
+
detailParts.push(`CI ${ciSegment} ${ciNotRed ? 'ok' : 'FAIL'}`);
|
|
204
|
+
detailParts.push(`resource ${resourceSegment} ${resourceNotCritical ? 'ok' : 'FAIL'}`);
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
suitable,
|
|
208
|
+
confidence: confidenceEcho,
|
|
209
|
+
rationale: detailParts.join('; '),
|
|
210
|
+
warnings,
|
|
211
|
+
};
|
|
212
|
+
}
|