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,441 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* memory-proposals/store.mjs — atomic JSONL append + per-wave quota counter
|
|
3
|
+
* + per-wave summary writer for memory proposal records.
|
|
4
|
+
*
|
|
5
|
+
* Design:
|
|
6
|
+
* - Lock file: `.orchestrator/metrics/proposals-write.lock`
|
|
7
|
+
* - Lock idiom: tmp-file + linkSync(tmp, lock) — mirrors session-lock.mjs
|
|
8
|
+
* `createStateLockExclusive` pattern (POSIX-atomic create-or-fail).
|
|
9
|
+
* - Proposals JSONL: `.orchestrator/metrics/proposals.jsonl` (O_APPEND)
|
|
10
|
+
* - Per-wave summary: `.orchestrator/metrics/proposals-summary-<wave-id>.json`
|
|
11
|
+
* - Confidence floor check: BEFORE lock acquisition (no I/O, no lock held).
|
|
12
|
+
* - Quota check: INSIDE lock (count + decide + write serialized).
|
|
13
|
+
*
|
|
14
|
+
* No external dependencies — Node 20+ stdlib only.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import fs from 'node:fs';
|
|
18
|
+
import path from 'node:path';
|
|
19
|
+
import crypto from 'node:crypto';
|
|
20
|
+
import { serializeProposal } from './schema.mjs';
|
|
21
|
+
import { validatePathInsideProject } from '../path-utils.mjs';
|
|
22
|
+
import { tryAcquireFileLock, releaseFileLock } from '../file-lock.mjs';
|
|
23
|
+
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
// Constants
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
const PROPOSALS_JSONL = '.orchestrator/metrics/proposals.jsonl';
|
|
29
|
+
const PROPOSALS_LOCK = '.orchestrator/metrics/proposals-write.lock';
|
|
30
|
+
const PROPOSALS_SUMMARY_PREFIX = '.orchestrator/metrics/proposals-summary-';
|
|
31
|
+
|
|
32
|
+
const DEFAULT_QUOTA_PER_WAVE = 5;
|
|
33
|
+
const DEFAULT_CONFIDENCE_FLOOR = 0.5;
|
|
34
|
+
const DEFAULT_LOCK_TIMEOUT_MS = 1000;
|
|
35
|
+
const LOCK_POLL_MS = 50;
|
|
36
|
+
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
// Internal helpers
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Resolve a metrics-relative path against repoRoot, with path-traversal guard.
|
|
43
|
+
* Throws a TypeError on traversal attempts — callers have already validated
|
|
44
|
+
* repoRoot is a trusted absolute path.
|
|
45
|
+
*
|
|
46
|
+
* Delegates to validatePathInsideProject (#544 M3 — canonical two-phase guard)
|
|
47
|
+
* with canonicalizeRoot:true so macOS tmpdir paths (where /var resolves to
|
|
48
|
+
* /private/var) are handled consistently in tests and production.
|
|
49
|
+
*
|
|
50
|
+
* Trust boundary (#554 A4, low severity): On block, the TypeError surfaces
|
|
51
|
+
* the resolved absolute path (`${resolved}`). This path-info disclosure is
|
|
52
|
+
* acceptable per the project's local-trust model — only reachable via an
|
|
53
|
+
* attacker-controlled `relPath`, surfaced only to local stderr and the
|
|
54
|
+
* memory-propose audit log (operator-readable). NOT for environments where
|
|
55
|
+
* filesystem layout is sensitive.
|
|
56
|
+
*
|
|
57
|
+
* @param {string} repoRoot — absolute path to the repo working directory
|
|
58
|
+
* @param {string} relPath — path relative to repoRoot (must not escape it)
|
|
59
|
+
* @returns {string} resolved absolute path (realPath when the target exists, else lexicalPath)
|
|
60
|
+
*/
|
|
61
|
+
function safePath(repoRoot, relPath) {
|
|
62
|
+
const result = validatePathInsideProject(relPath, repoRoot, { canonicalizeRoot: true });
|
|
63
|
+
if (!result.ok) {
|
|
64
|
+
const resolved = path.resolve(repoRoot, relPath);
|
|
65
|
+
// #548 A5 — surface result.reason ('lexical' | 'symlink' | 'input') in the
|
|
66
|
+
// error so operators can distinguish path-traversal classes at a glance:
|
|
67
|
+
// - 'lexical' → relPath escaped repoRoot via ../../ before symlink resolution
|
|
68
|
+
// - 'symlink' → relPath resolved through a symlink that points outside repoRoot
|
|
69
|
+
// - 'input' → relPath was malformed (empty, non-string, null byte)
|
|
70
|
+
throw new TypeError(
|
|
71
|
+
`store.mjs: path traversal blocked: ${resolved} is outside ${repoRoot} (reason: ${result.reason})`
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
return result.realPath ?? result.lexicalPath;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Ensure the parent directory of `filePath` exists.
|
|
79
|
+
* @param {string} filePath
|
|
80
|
+
*/
|
|
81
|
+
function ensureDir(filePath) {
|
|
82
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Build the absolute path to the lock file.
|
|
87
|
+
* @param {string} repoRoot
|
|
88
|
+
* @returns {string}
|
|
89
|
+
*/
|
|
90
|
+
function lockPathFor(repoRoot) {
|
|
91
|
+
return safePath(repoRoot, PROPOSALS_LOCK);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Build the absolute path to the JSONL proposals file.
|
|
96
|
+
* @param {string} repoRoot
|
|
97
|
+
* @returns {string}
|
|
98
|
+
*/
|
|
99
|
+
function jsonlPathFor(repoRoot) {
|
|
100
|
+
return safePath(repoRoot, PROPOSALS_JSONL);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Build the absolute path to the per-wave summary JSON file.
|
|
105
|
+
* wave_id values are restricted to alphanumerics + hyphens/underscores to
|
|
106
|
+
* prevent filename injection — any other character causes a throw.
|
|
107
|
+
*
|
|
108
|
+
* @param {string} repoRoot
|
|
109
|
+
* @param {string} waveId
|
|
110
|
+
* @returns {string}
|
|
111
|
+
*/
|
|
112
|
+
function summaryPathFor(repoRoot, waveId) {
|
|
113
|
+
if (!/^[A-Za-z0-9_-]+$/.test(waveId)) {
|
|
114
|
+
throw new TypeError(`store.mjs: waveId contains invalid characters: ${waveId}`);
|
|
115
|
+
}
|
|
116
|
+
return safePath(repoRoot, `${PROPOSALS_SUMMARY_PREFIX}${waveId}.json`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Acquire the proposals write-lock via spin-poll.
|
|
121
|
+
* Returns when the lock is acquired, or after timeoutMs.
|
|
122
|
+
*
|
|
123
|
+
* Delegates to the shared file-lock primitive (issue #630). Behavior is
|
|
124
|
+
* preserved EXACTLY:
|
|
125
|
+
*
|
|
126
|
+
* - Compact body `{pid, host, acquiredAt}` (indent:null on create).
|
|
127
|
+
* - 3-state distinction via `signalVanished:true` — the ENOENT-on-read race
|
|
128
|
+
* surfaces as `{ acquired: false, reason: 'vanished' }`, and this loop
|
|
129
|
+
* defers WITHOUT any override side-effect. CRITICAL: never override on
|
|
130
|
+
* `vanished` — a concurrent acquirer may have already taken the freshly
|
|
131
|
+
* vacant lock; overriding would create a double-holder race (regression of
|
|
132
|
+
* the C9 8-worker test). This was the reason the memory-proposals copy
|
|
133
|
+
* surfaced `vanished` as a distinct third reason rather than collapsing it
|
|
134
|
+
* into `held` (#548 A1).
|
|
135
|
+
* - PID staleCheck: same-host + dead PID → atomic override + WARN to stderr.
|
|
136
|
+
* - WARN channel: `process.stderr.write` with the original messages.
|
|
137
|
+
* - Override tmp prefix `.proposals-write.lock.tmp`, compact override body
|
|
138
|
+
* (overrideIndent:null) — preserves the original `replaceLockAtomic` format.
|
|
139
|
+
*
|
|
140
|
+
* #548 A5 — TOCTOU acknowledgment (now centralized in file-lock.mjs's
|
|
141
|
+
* isExistingStale → isPidAliveOnHost): a sub-millisecond race exists between
|
|
142
|
+
* the PID-liveness check and the override write. The window is two sync
|
|
143
|
+
* syscalls; the consequence is bounded (override a stale lock nobody used).
|
|
144
|
+
* Accepted, not eliminated. See file-lock.mjs `isPidAliveOnHost` remarks.
|
|
145
|
+
*
|
|
146
|
+
* @param {string} lockFile
|
|
147
|
+
* @param {number} timeoutMs
|
|
148
|
+
* @returns {Promise<{ ok: true } | { ok: false, reason: 'timeout' | 'fs-error', error?: string }>}
|
|
149
|
+
*/
|
|
150
|
+
async function acquireProposalsLock(lockFile, timeoutMs) {
|
|
151
|
+
const deadline = Date.now() + timeoutMs;
|
|
152
|
+
|
|
153
|
+
for (;;) {
|
|
154
|
+
const result = tryAcquireFileLock(lockFile, {
|
|
155
|
+
staleCheck: 'pid',
|
|
156
|
+
signalVanished: true,
|
|
157
|
+
indent: null,
|
|
158
|
+
overrideIndent: null,
|
|
159
|
+
tmpPrefix: '.proposals-write.lock.tmp',
|
|
160
|
+
warn: (msg) => process.stderr.write(msg),
|
|
161
|
+
warnMessage: (reason, _lp, existing) =>
|
|
162
|
+
existing === null
|
|
163
|
+
? '⚠ memory-proposals lock: stale lock detected (unparseable body) — reclaiming\n'
|
|
164
|
+
: `⚠ memory-proposals lock: stale lock detected (pid=${existing.pid}, host=${existing.host}) — reclaiming\n`,
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
if (result.acquired) return { ok: true };
|
|
168
|
+
if (result.reason === 'fs-error') return { ok: false, reason: 'fs-error', error: result.error };
|
|
169
|
+
|
|
170
|
+
// reason === 'vanished' (concurrent release race) OR 'held' (live holder /
|
|
171
|
+
// cross-host). Both defer to the next poll iteration without overriding.
|
|
172
|
+
if (Date.now() >= deadline) {
|
|
173
|
+
return { ok: false, reason: 'timeout', error: 'lock-timeout' };
|
|
174
|
+
}
|
|
175
|
+
await new Promise((resolve) => setTimeout(resolve, LOCK_POLL_MS));
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Release (unlink) the proposals write-lock. Idempotent — ENOENT is ignored.
|
|
181
|
+
*
|
|
182
|
+
* Delegates to releaseFileLock with `ownerGuard:false` — reproducing the
|
|
183
|
+
* original unconditional unlink. PRESERVE this: the memory-proposals release is
|
|
184
|
+
* intentionally NOT owner-guarded (it always runs inside the same process that
|
|
185
|
+
* acquired, immediately after the JSONL append, so the on-disk lock is always
|
|
186
|
+
* ours). Non-ENOENT fs errors are logged to stderr, never re-thrown.
|
|
187
|
+
*
|
|
188
|
+
* @param {string} lockFile
|
|
189
|
+
*/
|
|
190
|
+
function releaseProposalsLock(lockFile) {
|
|
191
|
+
releaseFileLock(lockFile, {
|
|
192
|
+
ownerGuard: false,
|
|
193
|
+
warn: (errToken) => process.stderr.write(`store.mjs: lock release failed (${errToken})\n`),
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Count lines in a JSONL file where `wave_id === waveId`.
|
|
199
|
+
* Returns 0 if the file does not exist.
|
|
200
|
+
*
|
|
201
|
+
* Complexity: O(lines) — acceptable for quota ≤ 5 in a session.
|
|
202
|
+
*
|
|
203
|
+
* @param {string} jsonlPath
|
|
204
|
+
* @param {string} waveId
|
|
205
|
+
* @returns {number}
|
|
206
|
+
*/
|
|
207
|
+
function countWaveLines(jsonlPath, waveId) {
|
|
208
|
+
let raw;
|
|
209
|
+
try {
|
|
210
|
+
raw = fs.readFileSync(jsonlPath, 'utf8');
|
|
211
|
+
} catch (err) {
|
|
212
|
+
if (err.code === 'ENOENT') return 0;
|
|
213
|
+
throw err;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
let count = 0;
|
|
217
|
+
for (const line of raw.split('\n')) {
|
|
218
|
+
if (line.trim().length === 0) continue;
|
|
219
|
+
try {
|
|
220
|
+
const obj = JSON.parse(line);
|
|
221
|
+
if (obj && obj.wave_id === waveId) count++;
|
|
222
|
+
} catch {
|
|
223
|
+
/* skip malformed lines */
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return count;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Read the per-wave summary JSON, or return a zeroed summary if absent.
|
|
231
|
+
* @param {string} summaryPath
|
|
232
|
+
* @returns {{ queued: number, dropped: number, below_floor: number, fs_error: number }}
|
|
233
|
+
*/
|
|
234
|
+
function readSummary(summaryPath) {
|
|
235
|
+
try {
|
|
236
|
+
const raw = fs.readFileSync(summaryPath, 'utf8');
|
|
237
|
+
const parsed = JSON.parse(raw);
|
|
238
|
+
if (parsed && typeof parsed === 'object') {
|
|
239
|
+
return {
|
|
240
|
+
queued: parsed.queued ?? 0,
|
|
241
|
+
dropped: parsed.dropped ?? 0,
|
|
242
|
+
below_floor: parsed.below_floor ?? 0,
|
|
243
|
+
fs_error: parsed.fs_error ?? 0,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
} catch {
|
|
247
|
+
/* ENOENT or malformed — return zeros */
|
|
248
|
+
}
|
|
249
|
+
return { queued: 0, dropped: 0, below_floor: 0, fs_error: 0 };
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Atomically increment one field in the per-wave summary JSON.
|
|
254
|
+
* Uses a tmp + rename to avoid partial-write races.
|
|
255
|
+
* Failures are silently swallowed — the summary is diagnostic, not critical.
|
|
256
|
+
*
|
|
257
|
+
* @param {string} summaryPath
|
|
258
|
+
* @param {keyof ReturnType<typeof readSummary>} field
|
|
259
|
+
*/
|
|
260
|
+
function incrementSummary(summaryPath, field) {
|
|
261
|
+
try {
|
|
262
|
+
ensureDir(summaryPath);
|
|
263
|
+
const current = readSummary(summaryPath);
|
|
264
|
+
current[field] = (current[field] ?? 0) + 1;
|
|
265
|
+
const tmpSuffix = crypto.randomBytes(4).toString('hex');
|
|
266
|
+
const tmpFile = `${summaryPath}.tmp.${tmpSuffix}`;
|
|
267
|
+
fs.writeFileSync(tmpFile, JSON.stringify(current, null, 2) + '\n', 'utf8');
|
|
268
|
+
fs.renameSync(tmpFile, summaryPath);
|
|
269
|
+
} catch {
|
|
270
|
+
/* best-effort: summary writes are non-critical */
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// ---------------------------------------------------------------------------
|
|
275
|
+
// Exported API
|
|
276
|
+
// ---------------------------------------------------------------------------
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Append a validated ProposalRecord to the proposals JSONL, subject to
|
|
280
|
+
* per-wave quota and confidence floor enforcement.
|
|
281
|
+
*
|
|
282
|
+
* Flow:
|
|
283
|
+
* 1. Confidence check BEFORE lock (no I/O needed).
|
|
284
|
+
* 2. Acquire lock (spin-poll 50ms, timeout 1000ms default).
|
|
285
|
+
* 3. Inside lock: count + decide + append.
|
|
286
|
+
* 4. Release lock in finally.
|
|
287
|
+
*
|
|
288
|
+
* @param {object} opts
|
|
289
|
+
* @param {object} opts.record — validated ProposalRecord (from schema.mjs)
|
|
290
|
+
* @param {string} opts.repoRoot — absolute path to repo working directory
|
|
291
|
+
* @param {string} opts.waveId — e.g. 'W2'; must match record.wave_id
|
|
292
|
+
* @param {number} [opts.quotaPerWave=5]
|
|
293
|
+
* @param {number} [opts.confidenceFloor=0.5]
|
|
294
|
+
* @param {number} [opts.lockTimeoutMs=1000]
|
|
295
|
+
* @returns {Promise<{
|
|
296
|
+
* status: 'queued' | 'quota-exceeded' | 'below-floor' | 'fs-error',
|
|
297
|
+
* position?: string,
|
|
298
|
+
* quota?: number,
|
|
299
|
+
* dropped?: number,
|
|
300
|
+
* error?: string
|
|
301
|
+
* }>}
|
|
302
|
+
*/
|
|
303
|
+
export async function appendProposal({
|
|
304
|
+
record,
|
|
305
|
+
repoRoot,
|
|
306
|
+
waveId,
|
|
307
|
+
quotaPerWave = DEFAULT_QUOTA_PER_WAVE,
|
|
308
|
+
confidenceFloor = DEFAULT_CONFIDENCE_FLOOR,
|
|
309
|
+
lockTimeoutMs = DEFAULT_LOCK_TIMEOUT_MS,
|
|
310
|
+
}) {
|
|
311
|
+
const summaryPath = summaryPathFor(repoRoot, waveId);
|
|
312
|
+
|
|
313
|
+
// ------------------------------------------------------------------
|
|
314
|
+
// Step 1: Confidence floor — before lock, no I/O needed.
|
|
315
|
+
// ------------------------------------------------------------------
|
|
316
|
+
if (
|
|
317
|
+
typeof record.confidence !== 'number' ||
|
|
318
|
+
record.confidence < confidenceFloor
|
|
319
|
+
) {
|
|
320
|
+
incrementSummary(summaryPath, 'below_floor');
|
|
321
|
+
return { status: 'below-floor' };
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const lockFile = lockPathFor(repoRoot);
|
|
325
|
+
const jsonlPath = jsonlPathFor(repoRoot);
|
|
326
|
+
|
|
327
|
+
// ------------------------------------------------------------------
|
|
328
|
+
// Step 2: Acquire lock. Early return if acquire fails (no lock held → no
|
|
329
|
+
// release needed). After this point any path that reaches the finally
|
|
330
|
+
// below holds the lock and must release it.
|
|
331
|
+
// ------------------------------------------------------------------
|
|
332
|
+
const lockResult = await acquireProposalsLock(lockFile, lockTimeoutMs);
|
|
333
|
+
if (!lockResult.ok) {
|
|
334
|
+
incrementSummary(summaryPath, 'fs_error');
|
|
335
|
+
return { status: 'fs-error', error: lockResult.error ?? lockResult.reason };
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
try {
|
|
339
|
+
// ------------------------------------------------------------------
|
|
340
|
+
// Step 3a: Count current proposals for this wave.
|
|
341
|
+
// ------------------------------------------------------------------
|
|
342
|
+
let currentCount;
|
|
343
|
+
try {
|
|
344
|
+
currentCount = countWaveLines(jsonlPath, waveId);
|
|
345
|
+
} catch (err) {
|
|
346
|
+
incrementSummary(summaryPath, 'fs_error');
|
|
347
|
+
return { status: 'fs-error', error: err.message };
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// ------------------------------------------------------------------
|
|
351
|
+
// Step 3b: Quota check.
|
|
352
|
+
// ------------------------------------------------------------------
|
|
353
|
+
if (currentCount >= quotaPerWave) {
|
|
354
|
+
const summary = readSummary(summaryPath);
|
|
355
|
+
const droppedSoFar = summary.dropped;
|
|
356
|
+
incrementSummary(summaryPath, 'dropped');
|
|
357
|
+
return {
|
|
358
|
+
status: 'quota-exceeded',
|
|
359
|
+
quota: quotaPerWave,
|
|
360
|
+
dropped: droppedSoFar + 1,
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// ------------------------------------------------------------------
|
|
365
|
+
// Step 3c: Append to JSONL (O_APPEND via appendFileSync).
|
|
366
|
+
// ------------------------------------------------------------------
|
|
367
|
+
try {
|
|
368
|
+
ensureDir(jsonlPath);
|
|
369
|
+
fs.appendFileSync(jsonlPath, serializeProposal(record) + '\n', 'utf8');
|
|
370
|
+
} catch (err) {
|
|
371
|
+
incrementSummary(summaryPath, 'fs_error');
|
|
372
|
+
return { status: 'fs-error', error: err.message };
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
incrementSummary(summaryPath, 'queued');
|
|
376
|
+
const position = `${currentCount + 1}/${quotaPerWave}`;
|
|
377
|
+
return { status: 'queued', position };
|
|
378
|
+
} finally {
|
|
379
|
+
// ------------------------------------------------------------------
|
|
380
|
+
// Step 4: Always release lock (idempotent unlink). Reaching this finally
|
|
381
|
+
// implies the lock was acquired above — early-return covers the
|
|
382
|
+
// non-acquired branch.
|
|
383
|
+
// ------------------------------------------------------------------
|
|
384
|
+
releaseProposalsLock(lockFile);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Count the number of proposals recorded for a given wave_id.
|
|
390
|
+
* Reads proposals.jsonl without acquiring the lock — suitable for diagnostics
|
|
391
|
+
* and reads where eventual consistency is acceptable.
|
|
392
|
+
*
|
|
393
|
+
* Returns 0 when the file does not exist.
|
|
394
|
+
*
|
|
395
|
+
* @param {object} opts
|
|
396
|
+
* @param {string} opts.repoRoot
|
|
397
|
+
* @param {string} opts.waveId
|
|
398
|
+
* @returns {Promise<number>}
|
|
399
|
+
*/
|
|
400
|
+
export async function countProposalsForWave({ repoRoot, waveId }) {
|
|
401
|
+
const jsonlPath = jsonlPathFor(repoRoot);
|
|
402
|
+
try {
|
|
403
|
+
return countWaveLines(jsonlPath, waveId);
|
|
404
|
+
} catch {
|
|
405
|
+
return 0;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Read the per-wave summary for diagnostic purposes.
|
|
411
|
+
* Returns null when the summary file does not exist.
|
|
412
|
+
*
|
|
413
|
+
* @param {object} opts
|
|
414
|
+
* @param {string} opts.repoRoot
|
|
415
|
+
* @param {string} opts.waveId
|
|
416
|
+
* @returns {Promise<{
|
|
417
|
+
* queued: number,
|
|
418
|
+
* dropped: number,
|
|
419
|
+
* below_floor: number,
|
|
420
|
+
* fs_error: number
|
|
421
|
+
* } | null>}
|
|
422
|
+
*/
|
|
423
|
+
export async function readWaveSummary({ repoRoot, waveId }) {
|
|
424
|
+
const summaryPath = summaryPathFor(repoRoot, waveId);
|
|
425
|
+
try {
|
|
426
|
+
const raw = fs.readFileSync(summaryPath, 'utf8');
|
|
427
|
+
const parsed = JSON.parse(raw);
|
|
428
|
+
if (parsed && typeof parsed === 'object') {
|
|
429
|
+
return {
|
|
430
|
+
queued: parsed.queued ?? 0,
|
|
431
|
+
dropped: parsed.dropped ?? 0,
|
|
432
|
+
below_floor: parsed.below_floor ?? 0,
|
|
433
|
+
fs_error: parsed.fs_error ?? 0,
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
return null;
|
|
437
|
+
} catch (err) {
|
|
438
|
+
if (err.code === 'ENOENT') return null;
|
|
439
|
+
return null;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mission-Status Enum + Transition Validator (issue #340).
|
|
3
|
+
*
|
|
4
|
+
* Pure ESM, no I/O, no external dependencies. All functions are deterministic.
|
|
5
|
+
*
|
|
6
|
+
* Five-state lifecycle for wave-plan items:
|
|
7
|
+
*
|
|
8
|
+
* brainstormed → validated → in-dev → testing → completed
|
|
9
|
+
*
|
|
10
|
+
* Rollbacks to `brainstormed` are permitted from any state (user can reset scope).
|
|
11
|
+
* Idempotent self-transitions are always allowed.
|
|
12
|
+
* All other transitions are rejected.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Canonical ordered list of valid mission-status values.
|
|
17
|
+
*
|
|
18
|
+
* @type {readonly string[]}
|
|
19
|
+
*/
|
|
20
|
+
export const MISSION_STATUS_VALUES = Object.freeze([
|
|
21
|
+
'brainstormed',
|
|
22
|
+
'validated',
|
|
23
|
+
'in-dev',
|
|
24
|
+
'testing',
|
|
25
|
+
'completed',
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Allowed forward transitions (excluding self-transitions and rollback to
|
|
30
|
+
* 'brainstormed', which are handled separately in isValidMissionStatusTransition).
|
|
31
|
+
*
|
|
32
|
+
* @type {ReadonlyMap<string, string>}
|
|
33
|
+
*/
|
|
34
|
+
const FORWARD_TRANSITIONS = new Map([
|
|
35
|
+
['brainstormed', 'validated'],
|
|
36
|
+
['validated', 'in-dev'],
|
|
37
|
+
['in-dev', 'testing'],
|
|
38
|
+
['testing', 'completed'],
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Returns true when `value` is one of the five canonical status strings.
|
|
43
|
+
*
|
|
44
|
+
* @param {unknown} value
|
|
45
|
+
* @returns {boolean}
|
|
46
|
+
*/
|
|
47
|
+
export function isValidMissionStatus(value) {
|
|
48
|
+
return typeof value === 'string' && MISSION_STATUS_VALUES.includes(value);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Returns true when the transition from `from` → `to` is permitted.
|
|
53
|
+
*
|
|
54
|
+
* Allowed transitions:
|
|
55
|
+
* - Forward: brainstormed→validated, validated→in-dev, in-dev→testing, testing→completed
|
|
56
|
+
* - Idempotent: any state → itself
|
|
57
|
+
* - Rollback: any state → brainstormed
|
|
58
|
+
*
|
|
59
|
+
* All other transitions return false.
|
|
60
|
+
*
|
|
61
|
+
* @param {string} from - current status value
|
|
62
|
+
* @param {string} to - target status value
|
|
63
|
+
* @returns {boolean}
|
|
64
|
+
*/
|
|
65
|
+
export function isValidMissionStatusTransition(from, to) {
|
|
66
|
+
if (!isValidMissionStatus(from) || !isValidMissionStatus(to)) return false;
|
|
67
|
+
// Idempotent self-transition
|
|
68
|
+
if (from === to) return true;
|
|
69
|
+
// Rollback to brainstormed from any state
|
|
70
|
+
if (to === 'brainstormed') return true;
|
|
71
|
+
// Forward transition
|
|
72
|
+
return FORWARD_TRANSITIONS.get(from) === to;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Validates a mission-status entry object.
|
|
77
|
+
*
|
|
78
|
+
* Expected shape:
|
|
79
|
+
* { id: string, task: string, wave: number, status: <MISSION_STATUS_VALUES> }
|
|
80
|
+
*
|
|
81
|
+
* @param {unknown} obj
|
|
82
|
+
* @returns {{ ok: boolean, errors: string[] }}
|
|
83
|
+
*/
|
|
84
|
+
export function validateMissionStatusEntry(obj) {
|
|
85
|
+
const errors = [];
|
|
86
|
+
if (obj === null || typeof obj !== 'object' || Array.isArray(obj)) {
|
|
87
|
+
return { ok: false, errors: ['entry must be a non-null, non-array object'] };
|
|
88
|
+
}
|
|
89
|
+
const entry = /** @type {Record<string, unknown>} */ (obj);
|
|
90
|
+
|
|
91
|
+
// id — non-empty string
|
|
92
|
+
if (typeof entry.id !== 'string' || entry.id.trim() === '') {
|
|
93
|
+
errors.push('id must be a non-empty string');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// task — non-empty string
|
|
97
|
+
if (typeof entry.task !== 'string' || entry.task.trim() === '') {
|
|
98
|
+
errors.push('task must be a non-empty string');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// wave — integer >= 1
|
|
102
|
+
if (!Number.isInteger(entry.wave) || /** @type {number} */ (entry.wave) < 1) {
|
|
103
|
+
errors.push('wave must be a positive integer');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// status — one of MISSION_STATUS_VALUES
|
|
107
|
+
if (!isValidMissionStatus(entry.status)) {
|
|
108
|
+
errors.push(
|
|
109
|
+
`status must be one of [${MISSION_STATUS_VALUES.join(', ')}], got: ${JSON.stringify(entry.status)}`,
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return { ok: errors.length === 0, errors };
|
|
114
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Alternatives-list builder for the mode-selector heuristic (W1A2 split, issue #358).
|
|
3
|
+
*
|
|
4
|
+
* Exports: buildAlternatives, hasActiveSignals.
|
|
5
|
+
* Imports: scoring (for scoreMode + helpers), constants (for ALL_MODES).
|
|
6
|
+
*
|
|
7
|
+
* Pure functions: no I/O, no side effects.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { ALL_MODES } from './constants.mjs';
|
|
11
|
+
import { scoreMode, safeArray, safeBootstrapLock } from './scoring.mjs';
|
|
12
|
+
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// Public API
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Return true if any "active signal" field is non-trivially present.
|
|
19
|
+
* Used to decide whether alternatives should be computed (branch 3).
|
|
20
|
+
*
|
|
21
|
+
* "Non-trivially present" means:
|
|
22
|
+
* - learnings: non-empty array
|
|
23
|
+
* - recentSessions: non-empty array
|
|
24
|
+
* - bootstrapLock: non-null object
|
|
25
|
+
*
|
|
26
|
+
* When no active signals exist, all modes score identically at base 0.5
|
|
27
|
+
* so alternatives carry no information. Return [] to keep the passthrough
|
|
28
|
+
* contract stable (existing tests assert alternatives === [] for bare calls).
|
|
29
|
+
*
|
|
30
|
+
* @param {object} signals
|
|
31
|
+
* @returns {boolean}
|
|
32
|
+
*/
|
|
33
|
+
export function hasActiveSignals(signals) {
|
|
34
|
+
const ls = safeArray(signals.learnings);
|
|
35
|
+
const rs = safeArray(signals.recentSessions);
|
|
36
|
+
return ls.length > 0 || rs.length > 0 || safeBootstrapLock(signals) !== null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Build the alternatives array for branch 3 (passthrough-weighted).
|
|
41
|
+
* Returns 0–3 entries sorted by confidence descending, each with confidence >= 0.1.
|
|
42
|
+
*
|
|
43
|
+
* When no active signals are present, returns [] (all modes score identically
|
|
44
|
+
* at 0.5, so alternatives carry no information — and this preserves the
|
|
45
|
+
* existing passthrough test contract).
|
|
46
|
+
*
|
|
47
|
+
* @param {string} chosenMode
|
|
48
|
+
* @param {object} signals
|
|
49
|
+
* @returns {Array<{mode: string, confidence: number}>}
|
|
50
|
+
*/
|
|
51
|
+
export function buildAlternatives(chosenMode, signals) {
|
|
52
|
+
if (!hasActiveSignals(signals)) {
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const otherModes = ALL_MODES.filter((m) => m !== chosenMode);
|
|
57
|
+
const scored = otherModes
|
|
58
|
+
.map((m) => ({ mode: m, confidence: scoreMode(m, signals) }))
|
|
59
|
+
.filter((e) => e.confidence >= 0.1)
|
|
60
|
+
.sort((a, b) => b.confidence - a.confidence)
|
|
61
|
+
.slice(0, 3);
|
|
62
|
+
|
|
63
|
+
return scored;
|
|
64
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mode-selector constants (W1A2 split, issue #358).
|
|
3
|
+
*
|
|
4
|
+
* Leaf module — no imports from other mode-selector submodules.
|
|
5
|
+
* Pure data: DEFAULT_MODE, ALL_MODES, TIER_MODE_MAP.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** @type {'feature'} */
|
|
9
|
+
export const DEFAULT_MODE = 'feature';
|
|
10
|
+
|
|
11
|
+
/** All valid mode values — mirrors VALID_MODES in recommendations-v0.mjs. */
|
|
12
|
+
export const ALL_MODES = Object.freeze([
|
|
13
|
+
'housekeeping',
|
|
14
|
+
'feature',
|
|
15
|
+
'deep',
|
|
16
|
+
'discovery',
|
|
17
|
+
'evolve',
|
|
18
|
+
'plan-retro',
|
|
19
|
+
]);
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Bootstrap tier → preferred mode alignment map.
|
|
23
|
+
* @type {Readonly<Record<string, string>>}
|
|
24
|
+
*/
|
|
25
|
+
export const TIER_MODE_MAP = Object.freeze({
|
|
26
|
+
fast: 'housekeeping',
|
|
27
|
+
standard: 'feature',
|
|
28
|
+
deep: 'deep',
|
|
29
|
+
});
|