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,851 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Body-section helpers for STATE.md.
|
|
3
|
+
*
|
|
4
|
+
* Pure functions — no file I/O.
|
|
5
|
+
*
|
|
6
|
+
* Plus on-disk wrappers (`appendDeviationOnDisk`, `markExpressPathCompleteOnDisk`,
|
|
7
|
+
* `recordAutoCommitOnDisk`) added for PRD 2026-05-22 § 4 Pattern 1 (issue #518).
|
|
8
|
+
* The on-disk wrappers delegate to the pure helpers above and route the
|
|
9
|
+
* read+write cycle through `writeStateMd` from frontmatter-mutators.mjs, which
|
|
10
|
+
* acquires `.orchestrator/state.lock` for mechanical serialization (PSA-004).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { parseStateMd, serializeStateMd } from './yaml-parser.mjs';
|
|
14
|
+
import {
|
|
15
|
+
updateFrontmatterFields,
|
|
16
|
+
touchUpdatedField,
|
|
17
|
+
writeStateMd,
|
|
18
|
+
} from './frontmatter-mutators.mjs';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Extracts the current-wave banner info from the `## Current Wave` body
|
|
22
|
+
* section: first non-blank line after the heading.
|
|
23
|
+
*
|
|
24
|
+
* @param {string} contents
|
|
25
|
+
* @returns {{waveNumber: number|null, description: string}|null}
|
|
26
|
+
*/
|
|
27
|
+
export function readCurrentTask(contents) {
|
|
28
|
+
const parsed = parseStateMd(contents);
|
|
29
|
+
if (parsed === null) return null;
|
|
30
|
+
const lines = parsed.body.split(/\r?\n/);
|
|
31
|
+
let i = 0;
|
|
32
|
+
while (i < lines.length && !/^##\s+Current Wave\b/.test(lines[i])) i++;
|
|
33
|
+
if (i === lines.length) return null;
|
|
34
|
+
i++;
|
|
35
|
+
while (i < lines.length && lines[i].trim() === '') i++;
|
|
36
|
+
if (i === lines.length) return null;
|
|
37
|
+
const description = lines[i].trim();
|
|
38
|
+
const waveMatch = /^Wave\s+(\d+)\b/.exec(description);
|
|
39
|
+
const waveNumber = waveMatch ? parseInt(waveMatch[1], 10) : null;
|
|
40
|
+
return { waveNumber, description };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Appends a timestamped bullet to the `## Deviations` section in the STATE.md body.
|
|
45
|
+
* Creates the section if missing. Replaces a `(none yet)` placeholder if present.
|
|
46
|
+
*
|
|
47
|
+
* Defensive guard (issue #560): when `isoTimestamp` is omitted, `undefined`, `null`,
|
|
48
|
+
* or a non-string value, defaults to `new Date().toISOString()`. This prevents the
|
|
49
|
+
* literal text `undefined` from rendering in the deviations log when a caller
|
|
50
|
+
* forgets to compute the timestamp (the deep-2115 inter-wave 2→3 incident).
|
|
51
|
+
*
|
|
52
|
+
* @param {string} contents
|
|
53
|
+
* @param {string} [isoTimestamp] - ISO 8601 UTC, e.g. '2026-05-01T17:50:00Z'.
|
|
54
|
+
* When omitted or not a non-empty string, defaults to `new Date().toISOString()`.
|
|
55
|
+
* @param {string} message - free text; no leading dash, no surrounding brackets
|
|
56
|
+
* @returns {string}
|
|
57
|
+
*/
|
|
58
|
+
export function appendDeviation(contents, isoTimestamp, message) {
|
|
59
|
+
const parsed = parseStateMd(contents);
|
|
60
|
+
if (parsed === null) return contents;
|
|
61
|
+
const ts = (typeof isoTimestamp === 'string' && isoTimestamp.length > 0)
|
|
62
|
+
? isoTimestamp
|
|
63
|
+
: new Date().toISOString();
|
|
64
|
+
const bullet = `- [${ts}] ${message}`;
|
|
65
|
+
const lines = parsed.body.split('\n');
|
|
66
|
+
let headingIdx = -1;
|
|
67
|
+
for (let i = 0; i < lines.length; i++) {
|
|
68
|
+
if (/^##\s+Deviations\b/.test(lines[i])) {
|
|
69
|
+
headingIdx = i;
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (headingIdx === -1) {
|
|
74
|
+
// Append section at end of body. Ensure trailing newline before adding section.
|
|
75
|
+
let bodyOut = parsed.body;
|
|
76
|
+
if (!bodyOut.endsWith('\n')) bodyOut += '\n';
|
|
77
|
+
bodyOut += `\n## Deviations\n\n${bullet}\n`;
|
|
78
|
+
return serializeStateMd({ frontmatter: parsed.frontmatter, body: bodyOut });
|
|
79
|
+
}
|
|
80
|
+
// Find end-of-section: next `## ` heading or end of lines.
|
|
81
|
+
let sectionEnd = lines.length;
|
|
82
|
+
for (let i = headingIdx + 1; i < lines.length; i++) {
|
|
83
|
+
if (/^##\s+/.test(lines[i])) {
|
|
84
|
+
sectionEnd = i;
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// Within section body (headingIdx+1 .. sectionEnd-1), look for placeholder
|
|
89
|
+
// or last bullet.
|
|
90
|
+
let placeholderIdx = -1;
|
|
91
|
+
let lastBulletIdx = -1;
|
|
92
|
+
for (let i = headingIdx + 1; i < sectionEnd; i++) {
|
|
93
|
+
const t = lines[i].trim();
|
|
94
|
+
if (t === '(none yet)' || t === '_(none yet)_' || t === '*(none yet)*') {
|
|
95
|
+
placeholderIdx = i;
|
|
96
|
+
}
|
|
97
|
+
if (/^-\s+/.test(t)) {
|
|
98
|
+
lastBulletIdx = i;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (placeholderIdx !== -1) {
|
|
102
|
+
lines[placeholderIdx] = bullet;
|
|
103
|
+
} else if (lastBulletIdx !== -1) {
|
|
104
|
+
lines.splice(lastBulletIdx + 1, 0, bullet);
|
|
105
|
+
} else {
|
|
106
|
+
// Insert after heading, with one blank line between heading and bullet.
|
|
107
|
+
// Trim any leading blank lines in section, then insert.
|
|
108
|
+
let insertAt = headingIdx + 1;
|
|
109
|
+
// Skip existing blank lines after heading
|
|
110
|
+
while (insertAt < sectionEnd && lines[insertAt].trim() === '') insertAt++;
|
|
111
|
+
// We want exactly: heading, blank, bullet, [existing trailing content].
|
|
112
|
+
const before = lines.slice(0, headingIdx + 1);
|
|
113
|
+
const after = lines.slice(insertAt);
|
|
114
|
+
const rebuilt = [...before, '', bullet, ...after];
|
|
115
|
+
return serializeStateMd({
|
|
116
|
+
frontmatter: parsed.frontmatter,
|
|
117
|
+
body: rebuilt.join('\n'),
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
return serializeStateMd({
|
|
121
|
+
frontmatter: parsed.frontmatter,
|
|
122
|
+
body: lines.join('\n'),
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Maximum number of top-level `## What Not To Retry` entries retained.
|
|
128
|
+
*
|
|
129
|
+
* "Pruned on success" (issue #623) is implemented as a simple last-N FIFO:
|
|
130
|
+
* after each append, the section is trimmed to the most-recent
|
|
131
|
+
* `MAX_WHAT_NOT_TO_RETRY` entries (oldest dropped first). This is NOT a
|
|
132
|
+
* per-entry success-clear — entries are not individually retired when an
|
|
133
|
+
* approach later succeeds; the cap is the only pruning mechanism. The slot is
|
|
134
|
+
* a cross-session continuity record that SURVIVES the completed-branch Idle
|
|
135
|
+
* Reset (unlike per-session `## Deviations`).
|
|
136
|
+
*/
|
|
137
|
+
export const MAX_WHAT_NOT_TO_RETRY = 10;
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Collapse every run of CR/LF into a single space (#623).
|
|
141
|
+
*
|
|
142
|
+
* `## What Not To Retry` entries are single-line bullets and `readWhatNotToRetry`
|
|
143
|
+
* reads ONE line per field, so an embedded newline in `approach` or `why_failed`
|
|
144
|
+
* would lose everything after line 1 on round-trip. Collapsing here keeps the
|
|
145
|
+
* entry single-line and lossless. Leading/trailing whitespace introduced by the
|
|
146
|
+
* collapse is trimmed.
|
|
147
|
+
*
|
|
148
|
+
* @param {string} value
|
|
149
|
+
* @returns {string}
|
|
150
|
+
*/
|
|
151
|
+
function collapseNewlines(value) {
|
|
152
|
+
return value.replace(/\r?\n+/g, ' ').trim();
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Reads the `## What Not To Retry` section into an array of entries.
|
|
157
|
+
*
|
|
158
|
+
* Each top-level `- **<approach>** (<session_id>, <date>)` bullet (optionally
|
|
159
|
+
* followed by an indented `- why: <why_failed>` sub-bullet) becomes one entry.
|
|
160
|
+
* Returns `[]` when the section is absent, empty, or holds only the
|
|
161
|
+
* `(none yet)` placeholder. Returns `[]` on unparseable input.
|
|
162
|
+
*
|
|
163
|
+
* @param {string} contents
|
|
164
|
+
* @returns {Array<{approach: string, why_failed: string, session_id: string, date: string}>}
|
|
165
|
+
*/
|
|
166
|
+
export function readWhatNotToRetry(contents) {
|
|
167
|
+
const parsed = parseStateMd(contents);
|
|
168
|
+
if (parsed === null) return [];
|
|
169
|
+
const lines = parsed.body.split(/\r?\n/);
|
|
170
|
+
let i = 0;
|
|
171
|
+
while (i < lines.length && !/^##\s+What Not To Retry\b/.test(lines[i])) i++;
|
|
172
|
+
if (i === lines.length) return [];
|
|
173
|
+
i++;
|
|
174
|
+
const entries = [];
|
|
175
|
+
for (; i < lines.length; i++) {
|
|
176
|
+
if (/^##\s+/.test(lines[i])) break;
|
|
177
|
+
// Top-level entry: `- **<approach>** (<session_id>, <date>)`
|
|
178
|
+
const head = /^-\s+\*\*(.+?)\*\*\s+\((.+),\s*(.+)\)\s*$/.exec(lines[i]);
|
|
179
|
+
if (!head) continue;
|
|
180
|
+
const approach = head[1];
|
|
181
|
+
const session_id = head[2].trim();
|
|
182
|
+
const date = head[3].trim();
|
|
183
|
+
let why_failed = '';
|
|
184
|
+
// Look ahead for the `- why: <...>` sub-bullet within this entry.
|
|
185
|
+
for (let j = i + 1; j < lines.length; j++) {
|
|
186
|
+
if (/^##\s+/.test(lines[j])) break;
|
|
187
|
+
if (/^-\s+\*\*/.test(lines[j].trim())) break; // next top-level entry
|
|
188
|
+
const why = /^-\s+why:\s*(.*)$/.exec(lines[j].trim());
|
|
189
|
+
if (why) {
|
|
190
|
+
why_failed = why[1];
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
entries.push({ approach, why_failed, session_id, date });
|
|
195
|
+
}
|
|
196
|
+
return entries;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Appends an entry to the `## What Not To Retry` section in the STATE.md body.
|
|
201
|
+
*
|
|
202
|
+
* Creates the section if missing. Replaces a `(none yet)` placeholder if
|
|
203
|
+
* present. After appending, prunes the section to the most-recent
|
|
204
|
+
* `MAX_WHAT_NOT_TO_RETRY` top-level entries (FIFO — oldest dropped first).
|
|
205
|
+
*
|
|
206
|
+
* Mirrors `appendDeviation` in structure. No-op (returns input unchanged) on
|
|
207
|
+
* unparseable input.
|
|
208
|
+
*
|
|
209
|
+
* @param {string} contents
|
|
210
|
+
* @param {object} entry
|
|
211
|
+
* @param {string} [entry.approach] — the approach that failed; defaults to '(unspecified approach)'
|
|
212
|
+
* @param {string} [entry.why_failed] — why it failed; defaults to '(no reason recorded)'
|
|
213
|
+
* @param {string} [entry.session_id] — originating session id; defaults to 'unknown-session'
|
|
214
|
+
* @param {string} [entry.date] — YYYY-MM-DD; defaults to today (UTC date slice)
|
|
215
|
+
* @returns {string}
|
|
216
|
+
*/
|
|
217
|
+
export function appendWhatNotToRetry(contents, entry) {
|
|
218
|
+
const parsed = parseStateMd(contents);
|
|
219
|
+
if (parsed === null) return contents;
|
|
220
|
+
const e = entry || {};
|
|
221
|
+
const approach = (typeof e.approach === 'string' && e.approach.length > 0)
|
|
222
|
+
? e.approach
|
|
223
|
+
: '(unspecified approach)';
|
|
224
|
+
const why = (typeof e.why_failed === 'string' && e.why_failed.length > 0)
|
|
225
|
+
? e.why_failed
|
|
226
|
+
: '(no reason recorded)';
|
|
227
|
+
const sid = (typeof e.session_id === 'string' && e.session_id.length > 0)
|
|
228
|
+
? e.session_id
|
|
229
|
+
: 'unknown-session';
|
|
230
|
+
const date = (typeof e.date === 'string' && e.date.length > 0)
|
|
231
|
+
? e.date
|
|
232
|
+
: new Date().toISOString().slice(0, 10);
|
|
233
|
+
|
|
234
|
+
// Collapse embedded newlines (#623): the entry renders as single-line bullets
|
|
235
|
+
// (`- **<approach>**` + ` - why: <why>`), and readWhatNotToRetry reads ONE
|
|
236
|
+
// line per field. A multi-line `approach`/`why_failed` would silently truncate
|
|
237
|
+
// everything after line 1 on round-trip. Collapse `\r?\n+` runs to a single
|
|
238
|
+
// space so the entry stays single-line and round-trips losslessly.
|
|
239
|
+
const headBullet = `- **${collapseNewlines(approach)}** (${sid}, ${date})`;
|
|
240
|
+
const whyBullet = ` - why: ${collapseNewlines(why)}`;
|
|
241
|
+
|
|
242
|
+
const lines = parsed.body.split('\n');
|
|
243
|
+
let headingIdx = -1;
|
|
244
|
+
for (let i = 0; i < lines.length; i++) {
|
|
245
|
+
if (/^##\s+What Not To Retry\b/.test(lines[i])) {
|
|
246
|
+
headingIdx = i;
|
|
247
|
+
break;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
let rebuiltBody;
|
|
252
|
+
if (headingIdx === -1) {
|
|
253
|
+
// Append section at end of body. Ensure trailing newline before adding.
|
|
254
|
+
let bodyOut = parsed.body;
|
|
255
|
+
if (!bodyOut.endsWith('\n')) bodyOut += '\n';
|
|
256
|
+
bodyOut += `\n## What Not To Retry\n\n${headBullet}\n${whyBullet}\n`;
|
|
257
|
+
rebuiltBody = bodyOut;
|
|
258
|
+
} else {
|
|
259
|
+
// Find end-of-section: next `## ` heading or end of lines.
|
|
260
|
+
let sectionEnd = lines.length;
|
|
261
|
+
for (let i = headingIdx + 1; i < lines.length; i++) {
|
|
262
|
+
if (/^##\s+/.test(lines[i])) {
|
|
263
|
+
sectionEnd = i;
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
// Look for placeholder or last top-level entry bullet within the section.
|
|
268
|
+
let placeholderIdx = -1;
|
|
269
|
+
let lastEntryIdx = -1;
|
|
270
|
+
for (let i = headingIdx + 1; i < sectionEnd; i++) {
|
|
271
|
+
const t = lines[i].trim();
|
|
272
|
+
if (t === '(none yet)' || t === '_(none yet)_' || t === '*(none yet)*') {
|
|
273
|
+
placeholderIdx = i;
|
|
274
|
+
}
|
|
275
|
+
if (/^-\s+\*\*/.test(t)) {
|
|
276
|
+
lastEntryIdx = i;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
if (placeholderIdx !== -1) {
|
|
280
|
+
// Replace the placeholder line with the head bullet, then insert why.
|
|
281
|
+
lines.splice(placeholderIdx, 1, headBullet, whyBullet);
|
|
282
|
+
} else if (lastEntryIdx !== -1) {
|
|
283
|
+
// Insert after the last entry's why sub-bullet (or the entry itself):
|
|
284
|
+
// scan forward over any indented sub-bullets belonging to lastEntryIdx.
|
|
285
|
+
let insertAt = lastEntryIdx + 1;
|
|
286
|
+
while (
|
|
287
|
+
insertAt < sectionEnd &&
|
|
288
|
+
lines[insertAt].trim() !== '' &&
|
|
289
|
+
!/^-\s+\*\*/.test(lines[insertAt].trim()) &&
|
|
290
|
+
/^\s+-\s+/.test(lines[insertAt])
|
|
291
|
+
) {
|
|
292
|
+
insertAt++;
|
|
293
|
+
}
|
|
294
|
+
lines.splice(insertAt, 0, headBullet, whyBullet);
|
|
295
|
+
} else {
|
|
296
|
+
// Empty section (heading only): insert heading, blank, entry, why.
|
|
297
|
+
let insertAt = headingIdx + 1;
|
|
298
|
+
while (insertAt < sectionEnd && lines[insertAt].trim() === '') insertAt++;
|
|
299
|
+
const before = lines.slice(0, headingIdx + 1);
|
|
300
|
+
const after = lines.slice(insertAt);
|
|
301
|
+
const rebuilt = [...before, '', headBullet, whyBullet, ...after];
|
|
302
|
+
rebuiltBody = rebuilt.join('\n');
|
|
303
|
+
}
|
|
304
|
+
if (rebuiltBody === undefined) {
|
|
305
|
+
rebuiltBody = lines.join('\n');
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Prune to the most-recent MAX_WHAT_NOT_TO_RETRY top-level entries (FIFO).
|
|
310
|
+
rebuiltBody = pruneWhatNotToRetry(rebuiltBody);
|
|
311
|
+
|
|
312
|
+
return serializeStateMd({ frontmatter: parsed.frontmatter, body: rebuiltBody });
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Trims the `## What Not To Retry` section to the most-recent
|
|
317
|
+
* `MAX_WHAT_NOT_TO_RETRY` top-level entries, dropping the oldest. Each entry is
|
|
318
|
+
* its top-level `- **...**` bullet plus any immediately-following indented
|
|
319
|
+
* sub-bullets (e.g. ` - why: ...`). No-op when the section is absent or the
|
|
320
|
+
* entry count is at/under the cap.
|
|
321
|
+
*
|
|
322
|
+
* @param {string} body — STATE.md body (no frontmatter)
|
|
323
|
+
* @returns {string}
|
|
324
|
+
*/
|
|
325
|
+
function pruneWhatNotToRetry(body) {
|
|
326
|
+
const lines = body.split('\n');
|
|
327
|
+
let headingIdx = -1;
|
|
328
|
+
for (let i = 0; i < lines.length; i++) {
|
|
329
|
+
if (/^##\s+What Not To Retry\b/.test(lines[i])) {
|
|
330
|
+
headingIdx = i;
|
|
331
|
+
break;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
if (headingIdx === -1) return body;
|
|
335
|
+
let sectionEnd = lines.length;
|
|
336
|
+
for (let i = headingIdx + 1; i < lines.length; i++) {
|
|
337
|
+
if (/^##\s+/.test(lines[i])) {
|
|
338
|
+
sectionEnd = i;
|
|
339
|
+
break;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
// Collect the start index of each top-level entry within the section.
|
|
343
|
+
const entryStarts = [];
|
|
344
|
+
for (let i = headingIdx + 1; i < sectionEnd; i++) {
|
|
345
|
+
if (/^-\s+\*\*/.test(lines[i].trim())) entryStarts.push(i);
|
|
346
|
+
}
|
|
347
|
+
if (entryStarts.length <= MAX_WHAT_NOT_TO_RETRY) return body;
|
|
348
|
+
// Keep the last MAX_WHAT_NOT_TO_RETRY entries; drop everything from the first
|
|
349
|
+
// entry up to (but not including) the first kept entry.
|
|
350
|
+
const dropCount = entryStarts.length - MAX_WHAT_NOT_TO_RETRY;
|
|
351
|
+
const firstKeptIdx = entryStarts[dropCount];
|
|
352
|
+
const firstDropIdx = entryStarts[0];
|
|
353
|
+
lines.splice(firstDropIdx, firstKeptIdx - firstDropIdx);
|
|
354
|
+
return lines.join('\n');
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// ---------------------------------------------------------------------------
|
|
358
|
+
// Open Questions (Close Handover-Alignment-Gate, PRD 2026-07-07)
|
|
359
|
+
// ---------------------------------------------------------------------------
|
|
360
|
+
//
|
|
361
|
+
// Unresolved user-facing questions surfaced by wave agents during a session,
|
|
362
|
+
// collected at inter-wave checkpoints, and (optionally) marked answered by
|
|
363
|
+
// session-end. This is a cross-session-visible channel — unlike per-session
|
|
364
|
+
// `## Deviations`, unanswered questions are meant to surface to the NEXT
|
|
365
|
+
// session's operator via the Handover-Alignment-Gate.
|
|
366
|
+
//
|
|
367
|
+
// SSOT format note: the bullet template lives in ONE place
|
|
368
|
+
// (`formatOpenQuestionBullet`) and `OPEN_QUESTION_RE` is the ONE regex that
|
|
369
|
+
// parses it back. A reader regex that drifts from the writer's template
|
|
370
|
+
// silently returns `[]` on any format change — the narrative-mirror.mjs
|
|
371
|
+
// lockstep gotcha. Keep both derived from this single definition.
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Maximum number of `## Open Questions` entries retained on disk.
|
|
375
|
+
*
|
|
376
|
+
* This is a storage cap (FIFO, oldest dropped first) — distinct from the
|
|
377
|
+
* Handover-Alignment-Gate's own `max-open-questions` config, which caps how
|
|
378
|
+
* many questions are surfaced/asked in a single gate run, not how many are
|
|
379
|
+
* stored in STATE.md.
|
|
380
|
+
*/
|
|
381
|
+
export const MAX_OPEN_QUESTIONS_STORED = 20;
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Builds a single `## Open Questions` bullet line from an entry. The single
|
|
385
|
+
* source of truth for the on-disk format — `OPEN_QUESTION_RE` below MUST stay
|
|
386
|
+
* in lockstep with this template.
|
|
387
|
+
*
|
|
388
|
+
* @param {object} entry
|
|
389
|
+
* @param {string} entry.question
|
|
390
|
+
* @param {string} entry.source
|
|
391
|
+
* @param {'high'|'medium'|'low'} entry.priority
|
|
392
|
+
* @param {boolean} [entry.answered=false]
|
|
393
|
+
* @param {string} [entry.answer]
|
|
394
|
+
* @returns {string}
|
|
395
|
+
*/
|
|
396
|
+
function formatOpenQuestionBullet({ question, source, priority, answered = false, answer }) {
|
|
397
|
+
const base = `- [${answered ? 'x' : ' '}] ${question} (source: ${source}, prio: ${priority})`;
|
|
398
|
+
if (answered) {
|
|
399
|
+
return `${base} → Antwort: ${typeof answer === 'string' ? answer : ''}`;
|
|
400
|
+
}
|
|
401
|
+
return base;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Parses one `## Open Questions` bullet line. Derived in lockstep with
|
|
406
|
+
* `formatOpenQuestionBullet` — see the SSOT note above.
|
|
407
|
+
*
|
|
408
|
+
* Capture groups: 1=checkbox state (' '|'x'), 2=question, 3=source,
|
|
409
|
+
* 4=priority, 5=answer (only present when checkbox is 'x' and an
|
|
410
|
+
* `→ Antwort: ...` suffix follows).
|
|
411
|
+
*
|
|
412
|
+
* The `source` group intentionally uses `(.+?)` rather than a comma-excluding
|
|
413
|
+
* `([^,]+?)`: the writer (`formatOpenQuestionBullet`) permits free-text
|
|
414
|
+
* `source` values that may themselves contain a comma (e.g.
|
|
415
|
+
* `'W2/agent-desc, extra'`). A comma-intolerant capture would silently fail to
|
|
416
|
+
* match such a writer-valid bullet — the question would vanish from the
|
|
417
|
+
* roundtrip with no error. `(.+?)` stays unambiguously anchored because the
|
|
418
|
+
* lazy quantifier backtracks until it finds the trailing
|
|
419
|
+
* `,\s*prio:(high|medium|low)\)` literal, which cannot itself appear inside a
|
|
420
|
+
* legitimate `source` value.
|
|
421
|
+
*/
|
|
422
|
+
const OPEN_QUESTION_RE =
|
|
423
|
+
/^-\s+\[([ x])\]\s+(.+?)\s+\(source:\s*(.+?),\s*prio:\s*(high|medium|low)\)(?:\s*→\s*Antwort:\s*(.*))?$/;
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Reads the `## Open Questions` section into an array of entries.
|
|
427
|
+
*
|
|
428
|
+
* Returns `[]` when the section is absent, empty, holds only the
|
|
429
|
+
* `(none yet)` placeholder, or on unparseable input. Lines that do not match
|
|
430
|
+
* `OPEN_QUESTION_RE` (malformed bullets) are silently skipped rather than
|
|
431
|
+
* throwing — tolerant of hand-edited STATE.md content.
|
|
432
|
+
*
|
|
433
|
+
* @param {string} contents
|
|
434
|
+
* @returns {Array<{question: string, source: string, priority: 'high'|'medium'|'low', answered: boolean, answer?: string}>}
|
|
435
|
+
*/
|
|
436
|
+
export function readOpenQuestions(contents) {
|
|
437
|
+
const parsed = parseStateMd(contents);
|
|
438
|
+
if (parsed === null) return [];
|
|
439
|
+
const lines = parsed.body.split(/\r?\n/);
|
|
440
|
+
let i = 0;
|
|
441
|
+
while (i < lines.length && !/^##\s+Open Questions\b/.test(lines[i])) i++;
|
|
442
|
+
if (i === lines.length) return [];
|
|
443
|
+
i++;
|
|
444
|
+
const entries = [];
|
|
445
|
+
for (; i < lines.length; i++) {
|
|
446
|
+
if (/^##\s+/.test(lines[i])) break;
|
|
447
|
+
const m = OPEN_QUESTION_RE.exec(lines[i].trim());
|
|
448
|
+
if (!m) continue;
|
|
449
|
+
const answered = m[1] === 'x';
|
|
450
|
+
const entry = {
|
|
451
|
+
question: m[2].trim(),
|
|
452
|
+
source: m[3].trim(),
|
|
453
|
+
priority: m[4],
|
|
454
|
+
answered,
|
|
455
|
+
};
|
|
456
|
+
if (answered && typeof m[5] === 'string') {
|
|
457
|
+
entry.answer = m[5].trim();
|
|
458
|
+
}
|
|
459
|
+
entries.push(entry);
|
|
460
|
+
}
|
|
461
|
+
return entries;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Appends an entry to the `## Open Questions` section in the STATE.md body.
|
|
466
|
+
*
|
|
467
|
+
* Creates the section if missing. Replaces a `(none yet)` placeholder if
|
|
468
|
+
* present. Deduplicates by question text — keyed replace-else-append, like
|
|
469
|
+
* `setMissionStatus` (mission-status.mjs): if an existing bullet's question
|
|
470
|
+
* text matches (after newline-collapse + trim), it is replaced in place
|
|
471
|
+
* (reset to unanswered) rather than duplicated. After appending, prunes the
|
|
472
|
+
* section to the most-recent `MAX_OPEN_QUESTIONS_STORED` entries (FIFO —
|
|
473
|
+
* oldest dropped first).
|
|
474
|
+
*
|
|
475
|
+
* No-op (returns input unchanged) on unparseable input.
|
|
476
|
+
*
|
|
477
|
+
* @param {string} contents
|
|
478
|
+
* @param {object} entry
|
|
479
|
+
* @param {string} [entry.question] — defaults to '(unspecified question)'
|
|
480
|
+
* @param {string} [entry.source] — defaults to 'unknown-source'
|
|
481
|
+
* @param {'high'|'medium'|'low'} [entry.priority] — defaults to 'medium'
|
|
482
|
+
* @returns {string}
|
|
483
|
+
*/
|
|
484
|
+
export function appendOpenQuestion(contents, entry) {
|
|
485
|
+
const parsed = parseStateMd(contents);
|
|
486
|
+
if (parsed === null) return contents;
|
|
487
|
+
const e = entry || {};
|
|
488
|
+
const question = (typeof e.question === 'string' && e.question.length > 0)
|
|
489
|
+
? collapseNewlines(e.question)
|
|
490
|
+
: '(unspecified question)';
|
|
491
|
+
const source = (typeof e.source === 'string' && e.source.length > 0)
|
|
492
|
+
? collapseNewlines(e.source)
|
|
493
|
+
: 'unknown-source';
|
|
494
|
+
const priority = ['high', 'medium', 'low'].includes(e.priority) ? e.priority : 'medium';
|
|
495
|
+
|
|
496
|
+
const bullet = formatOpenQuestionBullet({ question, source, priority, answered: false });
|
|
497
|
+
|
|
498
|
+
const lines = parsed.body.split('\n');
|
|
499
|
+
let headingIdx = -1;
|
|
500
|
+
for (let i = 0; i < lines.length; i++) {
|
|
501
|
+
if (/^##\s+Open Questions\b/.test(lines[i])) {
|
|
502
|
+
headingIdx = i;
|
|
503
|
+
break;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
let rebuiltBody;
|
|
508
|
+
if (headingIdx === -1) {
|
|
509
|
+
// Append section at end of body. Ensure trailing newline before adding.
|
|
510
|
+
let bodyOut = parsed.body;
|
|
511
|
+
if (!bodyOut.endsWith('\n')) bodyOut += '\n';
|
|
512
|
+
bodyOut += `\n## Open Questions\n\n${bullet}\n`;
|
|
513
|
+
rebuiltBody = bodyOut;
|
|
514
|
+
} else {
|
|
515
|
+
// Find end-of-section: next `## ` heading or end of lines.
|
|
516
|
+
let sectionEnd = lines.length;
|
|
517
|
+
for (let i = headingIdx + 1; i < lines.length; i++) {
|
|
518
|
+
if (/^##\s+/.test(lines[i])) {
|
|
519
|
+
sectionEnd = i;
|
|
520
|
+
break;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
// Look for a dedup match, a placeholder, or the last bullet within the section.
|
|
524
|
+
let existingIdx = -1;
|
|
525
|
+
let placeholderIdx = -1;
|
|
526
|
+
let lastBulletIdx = -1;
|
|
527
|
+
for (let i = headingIdx + 1; i < sectionEnd; i++) {
|
|
528
|
+
const t = lines[i].trim();
|
|
529
|
+
if (t === '(none yet)' || t === '_(none yet)_' || t === '*(none yet)*') {
|
|
530
|
+
placeholderIdx = i;
|
|
531
|
+
}
|
|
532
|
+
const m = OPEN_QUESTION_RE.exec(t);
|
|
533
|
+
if (m) {
|
|
534
|
+
lastBulletIdx = i;
|
|
535
|
+
if (m[2].trim() === question) existingIdx = i;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
if (existingIdx !== -1) {
|
|
539
|
+
lines[existingIdx] = bullet;
|
|
540
|
+
} else if (placeholderIdx !== -1) {
|
|
541
|
+
lines[placeholderIdx] = bullet;
|
|
542
|
+
} else if (lastBulletIdx !== -1) {
|
|
543
|
+
lines.splice(lastBulletIdx + 1, 0, bullet);
|
|
544
|
+
} else {
|
|
545
|
+
// Empty section (heading only): insert heading, blank, bullet.
|
|
546
|
+
let insertAt = headingIdx + 1;
|
|
547
|
+
while (insertAt < sectionEnd && lines[insertAt].trim() === '') insertAt++;
|
|
548
|
+
const before = lines.slice(0, headingIdx + 1);
|
|
549
|
+
const after = lines.slice(insertAt);
|
|
550
|
+
const rebuilt = [...before, '', bullet, ...after];
|
|
551
|
+
rebuiltBody = rebuilt.join('\n');
|
|
552
|
+
}
|
|
553
|
+
if (rebuiltBody === undefined) {
|
|
554
|
+
rebuiltBody = lines.join('\n');
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
rebuiltBody = pruneOpenQuestions(rebuiltBody);
|
|
559
|
+
|
|
560
|
+
return serializeStateMd({ frontmatter: parsed.frontmatter, body: rebuiltBody });
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* Trims the `## Open Questions` section to the most-recent
|
|
565
|
+
* `MAX_OPEN_QUESTIONS_STORED` entries, dropping the oldest. Unlike
|
|
566
|
+
* `## What Not To Retry`, each entry is exactly one line (no sub-bullets).
|
|
567
|
+
* No-op when the section is absent or the entry count is at/under the cap.
|
|
568
|
+
*
|
|
569
|
+
* @param {string} body — STATE.md body (no frontmatter)
|
|
570
|
+
* @returns {string}
|
|
571
|
+
*/
|
|
572
|
+
function pruneOpenQuestions(body) {
|
|
573
|
+
const lines = body.split('\n');
|
|
574
|
+
let headingIdx = -1;
|
|
575
|
+
for (let i = 0; i < lines.length; i++) {
|
|
576
|
+
if (/^##\s+Open Questions\b/.test(lines[i])) {
|
|
577
|
+
headingIdx = i;
|
|
578
|
+
break;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
if (headingIdx === -1) return body;
|
|
582
|
+
let sectionEnd = lines.length;
|
|
583
|
+
for (let i = headingIdx + 1; i < lines.length; i++) {
|
|
584
|
+
if (/^##\s+/.test(lines[i])) {
|
|
585
|
+
sectionEnd = i;
|
|
586
|
+
break;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
const entryIdxs = [];
|
|
590
|
+
for (let i = headingIdx + 1; i < sectionEnd; i++) {
|
|
591
|
+
if (OPEN_QUESTION_RE.test(lines[i].trim())) entryIdxs.push(i);
|
|
592
|
+
}
|
|
593
|
+
if (entryIdxs.length <= MAX_OPEN_QUESTIONS_STORED) return body;
|
|
594
|
+
const dropCount = entryIdxs.length - MAX_OPEN_QUESTIONS_STORED;
|
|
595
|
+
const firstKeptIdx = entryIdxs[dropCount];
|
|
596
|
+
const firstDropIdx = entryIdxs[0];
|
|
597
|
+
lines.splice(firstDropIdx, firstKeptIdx - firstDropIdx);
|
|
598
|
+
return lines.join('\n');
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/**
|
|
602
|
+
* Flips an unanswered `## Open Questions` bullet to answered:
|
|
603
|
+
* `- [ ] <q> (...)` → `- [x] <q> (...) → Antwort: <answer>`.
|
|
604
|
+
*
|
|
605
|
+
* No-op (returns input unchanged) when: the section is absent, the question
|
|
606
|
+
* text is not found (matched by trimmed, newline-collapsed equality against
|
|
607
|
+
* each bullet's question field), or the matched bullet is already answered.
|
|
608
|
+
*
|
|
609
|
+
* @param {string} contents
|
|
610
|
+
* @param {string} question — must match an existing unanswered bullet's question text
|
|
611
|
+
* @param {string} answer
|
|
612
|
+
* @returns {string}
|
|
613
|
+
*/
|
|
614
|
+
export function markOpenQuestionAnswered(contents, question, answer) {
|
|
615
|
+
const parsed = parseStateMd(contents);
|
|
616
|
+
if (parsed === null) return contents;
|
|
617
|
+
if (typeof question !== 'string' || question.length === 0) return contents;
|
|
618
|
+
const needle = collapseNewlines(question);
|
|
619
|
+
|
|
620
|
+
const lines = parsed.body.split('\n');
|
|
621
|
+
let headingIdx = -1;
|
|
622
|
+
for (let i = 0; i < lines.length; i++) {
|
|
623
|
+
if (/^##\s+Open Questions\b/.test(lines[i])) {
|
|
624
|
+
headingIdx = i;
|
|
625
|
+
break;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
if (headingIdx === -1) return contents;
|
|
629
|
+
let sectionEnd = lines.length;
|
|
630
|
+
for (let i = headingIdx + 1; i < lines.length; i++) {
|
|
631
|
+
if (/^##\s+/.test(lines[i])) {
|
|
632
|
+
sectionEnd = i;
|
|
633
|
+
break;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
for (let i = headingIdx + 1; i < sectionEnd; i++) {
|
|
638
|
+
const m = OPEN_QUESTION_RE.exec(lines[i].trim());
|
|
639
|
+
if (!m) continue;
|
|
640
|
+
if (m[2].trim() !== needle) continue;
|
|
641
|
+
if (m[1] === 'x') return contents; // already answered — no-op
|
|
642
|
+
lines[i] = formatOpenQuestionBullet({
|
|
643
|
+
question: m[2].trim(),
|
|
644
|
+
source: m[3].trim(),
|
|
645
|
+
priority: m[4],
|
|
646
|
+
answered: true,
|
|
647
|
+
answer: typeof answer === 'string' ? collapseNewlines(answer) : '',
|
|
648
|
+
});
|
|
649
|
+
return serializeStateMd({ frontmatter: parsed.frontmatter, body: lines.join('\n') });
|
|
650
|
+
}
|
|
651
|
+
return contents; // question not found
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
/**
|
|
655
|
+
* Records an auto-commit checkpoint in the `## Deviations` section of STATE.md.
|
|
656
|
+
*
|
|
657
|
+
* Phase-1 stub — appends a human-readable deviation entry only. Does NOT perform
|
|
658
|
+
* any git operations. The procedural commit body (`scripts/lib/auto-commit.mjs`)
|
|
659
|
+
* is deferred to V3.6 (GitLab #214). Until then, callers (wave-executor coordinator)
|
|
660
|
+
* invoke this function AFTER the git commit succeeds to create the audit trail.
|
|
661
|
+
*
|
|
662
|
+
* @param {string} contents - current STATE.md text
|
|
663
|
+
* @param {object} options
|
|
664
|
+
* @param {string} options.sha - short git SHA of the auto-commit (e.g. 'a1b2c3d')
|
|
665
|
+
* @param {number} options.waveN - wave number that triggered the checkpoint
|
|
666
|
+
* @param {string} options.waveResultSummary - one-line summary, e.g. 'Impl-Core, Quality-Lite PASS, 12 files'
|
|
667
|
+
* @param {string} [options.timestamp] - ISO 8601 UTC; defaults to current time
|
|
668
|
+
* @returns {string} updated STATE.md text
|
|
669
|
+
*/
|
|
670
|
+
export function recordAutoCommit(contents, options) {
|
|
671
|
+
const opts = options || {};
|
|
672
|
+
const { sha, waveN, waveResultSummary } = opts;
|
|
673
|
+
const timestamp = opts.timestamp ?? new Date().toISOString();
|
|
674
|
+
const message = `Wave ${waveN} auto-commit: ${sha} (${waveResultSummary})`;
|
|
675
|
+
return appendDeviation(contents, timestamp, message);
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
/**
|
|
679
|
+
* Finalizes a STATE.md after express-path coord-direct execution.
|
|
680
|
+
* Sets `status: completed`, appends an `Express path:` deviation, refreshes `updated`.
|
|
681
|
+
* No-op (returns input unchanged) if `contents` is unparseable.
|
|
682
|
+
*
|
|
683
|
+
* @param {string} contents
|
|
684
|
+
* @param {object} options
|
|
685
|
+
* @param {number} options.taskCount - number of tasks executed coord-direct (required)
|
|
686
|
+
* @param {string} [options.sessionType='housekeeping']
|
|
687
|
+
* @param {boolean} [options.expressPathEnabled=true]
|
|
688
|
+
* @param {string} [options.timestamp] - ISO 8601, defaults to current time
|
|
689
|
+
* @returns {string}
|
|
690
|
+
*/
|
|
691
|
+
export function markExpressPathComplete(contents, options) {
|
|
692
|
+
const parsed = parseStateMd(contents);
|
|
693
|
+
if (parsed === null) return contents;
|
|
694
|
+
const opts = options || {};
|
|
695
|
+
const taskCount = opts.taskCount;
|
|
696
|
+
const sessionType = opts.sessionType ?? 'housekeeping';
|
|
697
|
+
const expressPathEnabled = opts.expressPathEnabled ?? true;
|
|
698
|
+
const timestamp = opts.timestamp ?? new Date().toISOString();
|
|
699
|
+
const message = `Express path: ${taskCount} tasks executed coord-direct (express-path.enabled: ${expressPathEnabled}, session-type: ${sessionType}, scope: ${taskCount} issues)`;
|
|
700
|
+
let next = updateFrontmatterFields(contents, { status: 'completed' });
|
|
701
|
+
next = appendDeviation(next, timestamp, message);
|
|
702
|
+
next = touchUpdatedField(next, timestamp);
|
|
703
|
+
return next;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// ---------------------------------------------------------------------------
|
|
707
|
+
// On-disk wrappers (PRD 2026-05-22 § 4 — Pattern 1, issue #518)
|
|
708
|
+
// ---------------------------------------------------------------------------
|
|
709
|
+
|
|
710
|
+
/**
|
|
711
|
+
* Guard: throws a clear Error when repoRoot is undefined, null, or empty.
|
|
712
|
+
* Parallel-session CWD drift (PSA rules) makes `process.cwd()` fallbacks a
|
|
713
|
+
* footgun — callers MUST be explicit about which repo root they target.
|
|
714
|
+
*
|
|
715
|
+
* @param {unknown} repoRoot
|
|
716
|
+
* @param {string} fnName — name of the calling function, for error messages
|
|
717
|
+
*/
|
|
718
|
+
function requireRepoRoot(repoRoot, fnName) {
|
|
719
|
+
if (!repoRoot) {
|
|
720
|
+
throw new Error(
|
|
721
|
+
`${fnName}: repoRoot is required (got ${typeof repoRoot}). Pass an explicit repo root, e.g. via execSync('git rev-parse --show-toplevel').`
|
|
722
|
+
);
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
/**
|
|
727
|
+
* Lock-guarded append to the `## Deviations` section.
|
|
728
|
+
*
|
|
729
|
+
* Delegates to the pure `appendDeviation` helper; the read+write cycle is
|
|
730
|
+
* serialized via `withStateMdLock` (PSA-004 mechanical enforcement).
|
|
731
|
+
*
|
|
732
|
+
* When `isoTimestamp` is omitted, `undefined`, `null`, or a non-string value,
|
|
733
|
+
* the underlying `appendDeviation` defaults to `new Date().toISOString()` (issue #560).
|
|
734
|
+
*
|
|
735
|
+
* @param {string} repoRoot — absolute path to the repository root (required)
|
|
736
|
+
* @param {string} [isoTimestamp] — defaults to current time if omitted
|
|
737
|
+
* @param {string} message
|
|
738
|
+
* @param {object} [opts]
|
|
739
|
+
* @returns {Promise<{ written: boolean, path: string, contents: string|null }>}
|
|
740
|
+
* @throws {Error} when repoRoot is undefined, null, or empty
|
|
741
|
+
*/
|
|
742
|
+
export async function appendDeviationOnDisk(repoRoot, isoTimestamp, message, opts = {}) {
|
|
743
|
+
requireRepoRoot(repoRoot, 'appendDeviationOnDisk');
|
|
744
|
+
return writeStateMd(
|
|
745
|
+
repoRoot,
|
|
746
|
+
(contents) => appendDeviation(contents, isoTimestamp, message),
|
|
747
|
+
opts
|
|
748
|
+
);
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
/**
|
|
752
|
+
* Lock-guarded append to the `## What Not To Retry` section (#623).
|
|
753
|
+
*
|
|
754
|
+
* Delegates to the pure `appendWhatNotToRetry` helper; the read+write cycle is
|
|
755
|
+
* serialized via `withStateMdLock` (PSA-005 mechanical enforcement).
|
|
756
|
+
*
|
|
757
|
+
* @param {string} repoRoot — absolute path to the repository root (required)
|
|
758
|
+
* @param {object} entry See appendWhatNotToRetry signature above.
|
|
759
|
+
* @param {object} [opts]
|
|
760
|
+
* @returns {Promise<{ written: boolean, path: string, contents: string|null }>}
|
|
761
|
+
* @throws {Error} when repoRoot is undefined, null, or empty
|
|
762
|
+
*/
|
|
763
|
+
export async function appendWhatNotToRetryOnDisk(repoRoot, entry, opts = {}) {
|
|
764
|
+
requireRepoRoot(repoRoot, 'appendWhatNotToRetryOnDisk');
|
|
765
|
+
return writeStateMd(
|
|
766
|
+
repoRoot,
|
|
767
|
+
(contents) => appendWhatNotToRetry(contents, entry),
|
|
768
|
+
opts
|
|
769
|
+
);
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
/**
|
|
773
|
+
* Lock-guarded `recordAutoCommit` for express-path / wave-checkpoint flows.
|
|
774
|
+
*
|
|
775
|
+
* @param {string} repoRoot — absolute path to the repository root (required)
|
|
776
|
+
* @param {{ sha: string, waveN: number, waveResultSummary: string, timestamp?: string }} options
|
|
777
|
+
* @param {object} [opts]
|
|
778
|
+
* @returns {Promise<{ written: boolean, path: string, contents: string|null }>}
|
|
779
|
+
* @throws {Error} when repoRoot is undefined, null, or empty
|
|
780
|
+
*/
|
|
781
|
+
export async function recordAutoCommitOnDisk(repoRoot, options, opts = {}) {
|
|
782
|
+
requireRepoRoot(repoRoot, 'recordAutoCommitOnDisk');
|
|
783
|
+
return writeStateMd(
|
|
784
|
+
repoRoot,
|
|
785
|
+
(contents) => recordAutoCommit(contents, options),
|
|
786
|
+
opts
|
|
787
|
+
);
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
/**
|
|
791
|
+
* Lock-guarded `markExpressPathComplete` — used by session-end and the
|
|
792
|
+
* express-path skill flow.
|
|
793
|
+
*
|
|
794
|
+
* @param {string} repoRoot — absolute path to the repository root (required)
|
|
795
|
+
* @param {object} options See markExpressPathComplete signature above.
|
|
796
|
+
* @param {object} [opts]
|
|
797
|
+
* @returns {Promise<{ written: boolean, path: string, contents: string|null }>}
|
|
798
|
+
* @throws {Error} when repoRoot is undefined, null, or empty
|
|
799
|
+
*/
|
|
800
|
+
export async function markExpressPathCompleteOnDisk(repoRoot, options, opts = {}) {
|
|
801
|
+
requireRepoRoot(repoRoot, 'markExpressPathCompleteOnDisk');
|
|
802
|
+
return writeStateMd(
|
|
803
|
+
repoRoot,
|
|
804
|
+
(contents) => markExpressPathComplete(contents, options),
|
|
805
|
+
opts
|
|
806
|
+
);
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
/**
|
|
810
|
+
* Lock-guarded append to the `## Open Questions` section (Close
|
|
811
|
+
* Handover-Alignment-Gate, PRD 2026-07-07).
|
|
812
|
+
*
|
|
813
|
+
* Delegates to the pure `appendOpenQuestion` helper; the read+write cycle is
|
|
814
|
+
* serialized via `withStateMdLock` (PSA-005 mechanical enforcement — the
|
|
815
|
+
* lock is acquired exclusively by `writeStateMd`, never by this wrapper
|
|
816
|
+
* directly, to avoid a non-reentrant-mutex deadlock).
|
|
817
|
+
*
|
|
818
|
+
* @param {string} repoRoot — absolute path to the repository root (required)
|
|
819
|
+
* @param {object} entry See appendOpenQuestion signature above.
|
|
820
|
+
* @param {object} [opts]
|
|
821
|
+
* @returns {Promise<{ written: boolean, path: string, contents: string|null }>}
|
|
822
|
+
* @throws {Error} when repoRoot is undefined, null, or empty
|
|
823
|
+
*/
|
|
824
|
+
export async function appendOpenQuestionOnDisk(repoRoot, entry, opts = {}) {
|
|
825
|
+
requireRepoRoot(repoRoot, 'appendOpenQuestionOnDisk');
|
|
826
|
+
return writeStateMd(
|
|
827
|
+
repoRoot,
|
|
828
|
+
(contents) => appendOpenQuestion(contents, entry),
|
|
829
|
+
opts
|
|
830
|
+
);
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
/**
|
|
834
|
+
* Lock-guarded `markOpenQuestionAnswered` — used by session-end to record an
|
|
835
|
+
* answer against a previously-surfaced open question.
|
|
836
|
+
*
|
|
837
|
+
* @param {string} repoRoot — absolute path to the repository root (required)
|
|
838
|
+
* @param {string} question
|
|
839
|
+
* @param {string} answer
|
|
840
|
+
* @param {object} [opts]
|
|
841
|
+
* @returns {Promise<{ written: boolean, path: string, contents: string|null }>}
|
|
842
|
+
* @throws {Error} when repoRoot is undefined, null, or empty
|
|
843
|
+
*/
|
|
844
|
+
export async function markOpenQuestionAnsweredOnDisk(repoRoot, question, answer, opts = {}) {
|
|
845
|
+
requireRepoRoot(repoRoot, 'markOpenQuestionAnsweredOnDisk');
|
|
846
|
+
return writeStateMd(
|
|
847
|
+
repoRoot,
|
|
848
|
+
(contents) => markOpenQuestionAnswered(contents, question, answer),
|
|
849
|
+
opts
|
|
850
|
+
);
|
|
851
|
+
}
|