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,453 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Frontmatter mutation helpers for STATE.md.
|
|
3
|
+
*
|
|
4
|
+
* Pure functions — no file I/O.
|
|
5
|
+
*
|
|
6
|
+
* Plus a small I/O-touching surface (`writeStateMd`, `updateFrontmatterFieldsOnDisk`,
|
|
7
|
+
* `touchUpdatedFieldOnDisk`) added for PRD 2026-05-22 § 4 Pattern 1 (issue #518).
|
|
8
|
+
* These delegate to the pure helpers above but route the actual readFileSync /
|
|
9
|
+
* writeFileSync through `withStateMdLock` from session-lock.mjs so concurrent
|
|
10
|
+
* writers serialise mechanically rather than relying on PSA-004 discipline.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, renameSync } from 'node:fs';
|
|
14
|
+
import { dirname, join, resolve as resolvePath } from 'node:path';
|
|
15
|
+
import { parseStateMd, serializeStateMd } from './yaml-parser.mjs';
|
|
16
|
+
import { withStateMdLock } from '../session-lock.mjs';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Sets frontmatter.updated to the given ISO 8601 timestamp and returns the
|
|
20
|
+
* new contents. If the file has no frontmatter, returns input unchanged.
|
|
21
|
+
*
|
|
22
|
+
* @param {string} contents
|
|
23
|
+
* @param {string} isoTimestamp
|
|
24
|
+
* @returns {string}
|
|
25
|
+
*/
|
|
26
|
+
export function touchUpdatedField(contents, isoTimestamp) {
|
|
27
|
+
const parsed = parseStateMd(contents);
|
|
28
|
+
if (parsed === null) return contents;
|
|
29
|
+
parsed.frontmatter.updated = isoTimestamp;
|
|
30
|
+
return serializeStateMd(parsed);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Additively writes frontmatter keys. Only keys present in `fields` are
|
|
35
|
+
* touched; all other existing frontmatter keys (including unknown
|
|
36
|
+
* extensions) are preserved verbatim.
|
|
37
|
+
*
|
|
38
|
+
* Value semantics:
|
|
39
|
+
* - `null` or `undefined` value → key is DELETED from the frontmatter
|
|
40
|
+
* - anything else → key is set/overwritten
|
|
41
|
+
*
|
|
42
|
+
* No-ops if `contents` has no frontmatter (returns input unchanged).
|
|
43
|
+
*
|
|
44
|
+
* @param {string} contents
|
|
45
|
+
* @param {object} fields
|
|
46
|
+
* @returns {string}
|
|
47
|
+
*/
|
|
48
|
+
export function updateFrontmatterFields(contents, fields) {
|
|
49
|
+
const parsed = parseStateMd(contents);
|
|
50
|
+
if (parsed === null) return contents;
|
|
51
|
+
if (fields === null || typeof fields !== 'object' || Array.isArray(fields)) {
|
|
52
|
+
return contents;
|
|
53
|
+
}
|
|
54
|
+
for (const [k, v] of Object.entries(fields)) {
|
|
55
|
+
if (v === null || v === undefined) {
|
|
56
|
+
delete parsed.frontmatter[k];
|
|
57
|
+
} else {
|
|
58
|
+
parsed.frontmatter[k] = v;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return serializeStateMd(parsed);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
// On-disk wrappers (PRD 2026-05-22 § 4 — Pattern 1, issue #518)
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
//
|
|
68
|
+
// All STATE.md writes from .mjs callers SHOULD route through `writeStateMd`.
|
|
69
|
+
// Skill bodies that previously did inline `readFileSync(STATE) → transform →
|
|
70
|
+
// writeFileSync(STATE)` migrate to `await writeStateMd(repoRoot, contents => …)`,
|
|
71
|
+
// which mechanically serialises concurrent writers via `withStateMdLock`.
|
|
72
|
+
|
|
73
|
+
// Canonical STATE.md path candidates (matches harness-audit category1 order).
|
|
74
|
+
const STATE_MD_CANDIDATES = [
|
|
75
|
+
'.claude/STATE.md',
|
|
76
|
+
'.codex/STATE.md',
|
|
77
|
+
'.cursor/STATE.md',
|
|
78
|
+
'.pi/STATE.md',
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
function preferredStateMdCandidate() {
|
|
82
|
+
const envStateDir = process.env.SO_STATE_DIR;
|
|
83
|
+
if (typeof envStateDir === 'string' && envStateDir.length > 0) {
|
|
84
|
+
return join(envStateDir, 'STATE.md');
|
|
85
|
+
}
|
|
86
|
+
switch (process.env.SO_PLATFORM) {
|
|
87
|
+
case 'codex': return '.codex/STATE.md';
|
|
88
|
+
case 'cursor': return '.cursor/STATE.md';
|
|
89
|
+
case 'pi': return '.pi/STATE.md';
|
|
90
|
+
default: return '.claude/STATE.md';
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function orderedStateMdCandidates() {
|
|
95
|
+
const preferred = preferredStateMdCandidate();
|
|
96
|
+
return [
|
|
97
|
+
preferred,
|
|
98
|
+
...STATE_MD_CANDIDATES.filter((candidate) => candidate !== preferred),
|
|
99
|
+
];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Resolve the active STATE.md path under a given repo root.
|
|
104
|
+
*
|
|
105
|
+
* Returns the first existing candidate, checking the active platform first.
|
|
106
|
+
* Falls back to the active platform's state dir when none exist (matches the
|
|
107
|
+
* create-on-first-write semantics expected by callers).
|
|
108
|
+
*
|
|
109
|
+
* @param {string|undefined} repoRoot
|
|
110
|
+
* @returns {string} Absolute path to STATE.md.
|
|
111
|
+
*/
|
|
112
|
+
export function resolveStateMdPath(repoRoot) {
|
|
113
|
+
const root = repoRoot ?? process.cwd();
|
|
114
|
+
const candidates = orderedStateMdCandidates();
|
|
115
|
+
for (const candidate of candidates) {
|
|
116
|
+
const abs = resolvePath(join(root, candidate));
|
|
117
|
+
if (existsSync(abs)) return abs;
|
|
118
|
+
}
|
|
119
|
+
return resolvePath(join(root, candidates[0]));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ---------------------------------------------------------------------------
|
|
123
|
+
// Size-ceiling guard (issue #739)
|
|
124
|
+
// ---------------------------------------------------------------------------
|
|
125
|
+
//
|
|
126
|
+
// Incident: editing STATE.md frontmatter via updateFrontmatterFields ballooned
|
|
127
|
+
// the file from ~6KB to 6.3MB. Root cause is a yaml-parser/serializer
|
|
128
|
+
// asymmetry — serializeScalar() JSON-escapes special characters (quotes,
|
|
129
|
+
// backslashes) but parseScalar() strips the surrounding quotes WITHOUT
|
|
130
|
+
// unescaping the interior escape sequences. A frontmatter scalar containing a
|
|
131
|
+
// literal `"` therefore gains an extra layer of backslash-escaping on every
|
|
132
|
+
// parse→serialize round-trip, and repeated writes (each innocuous on its own)
|
|
133
|
+
// compound into exponential growth. See `.claude/STATE.md` § "What Not To
|
|
134
|
+
// Retry" for the incident note (main-2026-06-26-session-2).
|
|
135
|
+
//
|
|
136
|
+
// This guard is MECHANICAL and content-agnostic — it never inspects
|
|
137
|
+
// frontmatter shape, only the byte size of the proposed write relative to (a)
|
|
138
|
+
// an absolute ceiling and (b) the prior on-disk size. It refuses a write that
|
|
139
|
+
// would breach either bound rather than let a corrupt/ballooning write land,
|
|
140
|
+
// leaving the last-known-good on-disk contents intact.
|
|
141
|
+
//
|
|
142
|
+
// A DEEPER fix — a frontmatter-safe round-trip verification
|
|
143
|
+
// (`serializeStateMd(parseStateMd(after)) === after`) — was evaluated at the
|
|
144
|
+
// time and deliberately NOT shipped as a rejection gate, because the
|
|
145
|
+
// yaml-parser asymmetry it would have been checking FOR (parseScalar
|
|
146
|
+
// stripping quotes without unescaping the interior) made it false-positive on
|
|
147
|
+
// ordinary operator-authored content on its very first write, before any
|
|
148
|
+
// corruption had occurred.
|
|
149
|
+
//
|
|
150
|
+
// #747 (this session) closed that root-cause asymmetry directly in
|
|
151
|
+
// yaml-parser.mjs (parseScalar now unescapes via `JSON.parse`; serializeScalar
|
|
152
|
+
// force-quotes coercible strings) — see that module's docstring for the
|
|
153
|
+
// inverse-property contract. With the asymmetry gone, the round-trip verify
|
|
154
|
+
// is now SAFE to ship as an active guard: `evaluateFrontmatterSafe` below
|
|
155
|
+
// re-verifies it, scoped to the FRONTMATTER BLOCK only (not the full
|
|
156
|
+
// document). Scoping choice, empirically derived: `after` is always the
|
|
157
|
+
// TRANSFORMER's return value, and every transformer in this module family
|
|
158
|
+
// (`updateFrontmatterFields`, `touchUpdatedField`, and every body-section /
|
|
159
|
+
// mission-status writer) produces `after` via `serializeStateMd(...)` as its
|
|
160
|
+
// final step — so `after` is itself always fresh serializer output, and a
|
|
161
|
+
// full-document `serializeStateMd(parseStateMd(after)) === after` check DOES
|
|
162
|
+
// hold for it (verified against the live repo's `.claude/STATE.md` and a
|
|
163
|
+
// battery of quote/umlaut/array/block-seq fixtures). However, an EARLIER,
|
|
164
|
+
// weaker probe against the raw ON-DISK file (not transformer output) showed a
|
|
165
|
+
// body-only blank-line normalization difference — a pre-existing, unrelated
|
|
166
|
+
// serializeStateMd behaviour (it always emits a blank line between the
|
|
167
|
+
// closing `---` and the body) that has nothing to do with the frontmatter
|
|
168
|
+
// asymmetry this guard exists to catch. Scoping the check to the frontmatter
|
|
169
|
+
// block alone sidesteps that unrelated body-formatting difference entirely,
|
|
170
|
+
// keeps the guard's blast radius precisely matched to its name, and remains
|
|
171
|
+
// the narrower, safer gate for a mechanism whose whole history is "shipped
|
|
172
|
+
// too eagerly once already flagged legitimate content."
|
|
173
|
+
|
|
174
|
+
/** Absolute ceiling (bytes) above which a STATE.md write is refused outright. */
|
|
175
|
+
export const DEFAULT_STATE_MD_SIZE_CEILING_BYTES = 262144; // 256 KB
|
|
176
|
+
|
|
177
|
+
/** A write more than this many times the prior on-disk size is refused, even under the absolute ceiling. */
|
|
178
|
+
export const STATE_MD_SIZE_CEILING_RATIO = 5;
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Evaluates the proposed `after` contents against the size-ceiling guard.
|
|
182
|
+
*
|
|
183
|
+
* Two independent checks, either of which is a breach:
|
|
184
|
+
* - Absolute: `after` byte-size exceeds `ceilingBytes`.
|
|
185
|
+
* - Ratio: when `before` is non-empty, `after` byte-size exceeds
|
|
186
|
+
* `STATE_MD_SIZE_CEILING_RATIO` times the `before` byte-size. Skipped on
|
|
187
|
+
* first-writes (`before === ''`) — there is no prior size to ratio against.
|
|
188
|
+
*
|
|
189
|
+
* @param {string} before
|
|
190
|
+
* @param {string} after
|
|
191
|
+
* @param {number} ceilingBytes
|
|
192
|
+
* @returns {{ breached: boolean, reason: string|null, afterBytes: number }}
|
|
193
|
+
*/
|
|
194
|
+
function evaluateSizeCeiling(before, after, ceilingBytes) {
|
|
195
|
+
const afterBytes = Buffer.byteLength(after, 'utf8');
|
|
196
|
+
if (afterBytes > ceilingBytes) {
|
|
197
|
+
return {
|
|
198
|
+
breached: true,
|
|
199
|
+
reason: `after-size ${afterBytes}B exceeds absolute ceiling ${ceilingBytes}B`,
|
|
200
|
+
afterBytes,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
if (before.length > 0) {
|
|
204
|
+
const beforeBytes = Buffer.byteLength(before, 'utf8');
|
|
205
|
+
if (afterBytes > beforeBytes * STATE_MD_SIZE_CEILING_RATIO) {
|
|
206
|
+
return {
|
|
207
|
+
breached: true,
|
|
208
|
+
reason: `after-size ${afterBytes}B exceeds ${STATE_MD_SIZE_CEILING_RATIO}x prior on-disk size ${beforeBytes}B`,
|
|
209
|
+
afterBytes,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return { breached: false, reason: null, afterBytes };
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// ---------------------------------------------------------------------------
|
|
217
|
+
// Frontmatter-safe round-trip guard (issue #747 follow-up)
|
|
218
|
+
// ---------------------------------------------------------------------------
|
|
219
|
+
|
|
220
|
+
/** Matches the raw `---\n<frontmatter>\n---` block, mirroring yaml-parser.mjs's FRONTMATTER_RE. */
|
|
221
|
+
const FRONTMATTER_BLOCK_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Extracts the raw frontmatter block text (the bytes between the opening and
|
|
225
|
+
* closing `---` fences, exclusive) from a STATE.md-shaped string. Returns
|
|
226
|
+
* `null` when `text` has no matching frontmatter fence.
|
|
227
|
+
*
|
|
228
|
+
* @param {string} text
|
|
229
|
+
* @returns {string|null}
|
|
230
|
+
*/
|
|
231
|
+
function extractFrontmatterBlock(text) {
|
|
232
|
+
const match = FRONTMATTER_BLOCK_RE.exec(text);
|
|
233
|
+
return match ? match[1] : null;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Evaluates whether `after`'s frontmatter block is a byte-fixpoint under a
|
|
238
|
+
* further `serializeStateMd(parseStateMd(...))` round-trip.
|
|
239
|
+
*
|
|
240
|
+
* Scoped to the frontmatter block only (see the "Frontmatter-safe round-trip
|
|
241
|
+
* guard" comment above `DEFAULT_STATE_MD_SIZE_CEILING_BYTES` for the scoping
|
|
242
|
+
* rationale). Content with no parseable frontmatter (`parseStateMd(after) ===
|
|
243
|
+
* null`) is treated as SAFE — there is nothing frontmatter-shaped to verify,
|
|
244
|
+
* and this keeps non-STATE.md-shaped writes (e.g. arbitrary test fixtures)
|
|
245
|
+
* ungated by this check, matching `evaluateSizeCeiling`'s content-agnostic
|
|
246
|
+
* posture for that case.
|
|
247
|
+
*
|
|
248
|
+
* Never throws — mirrors the never-throw contract of yaml-parser.mjs.
|
|
249
|
+
*
|
|
250
|
+
* @param {string} after
|
|
251
|
+
* @returns {{ unsafe: boolean, reason: string|null }}
|
|
252
|
+
*/
|
|
253
|
+
export function evaluateFrontmatterSafe(after) {
|
|
254
|
+
const parsed = parseStateMd(after);
|
|
255
|
+
if (parsed === null) {
|
|
256
|
+
return { unsafe: false, reason: null };
|
|
257
|
+
}
|
|
258
|
+
const reserialized = serializeStateMd(parsed);
|
|
259
|
+
const beforeBlock = extractFrontmatterBlock(after);
|
|
260
|
+
const afterBlock = extractFrontmatterBlock(reserialized);
|
|
261
|
+
if (beforeBlock !== afterBlock) {
|
|
262
|
+
return {
|
|
263
|
+
unsafe: true,
|
|
264
|
+
reason:
|
|
265
|
+
`frontmatter block is not a serialize(parse(after)) byte-fixpoint ` +
|
|
266
|
+
`(${Buffer.byteLength(beforeBlock ?? '', 'utf8')}B -> ${Buffer.byteLength(afterBlock ?? '', 'utf8')}B)`,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
return { unsafe: false, reason: null };
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Atomic write helper: write to a sibling tmp file then rename. Avoids
|
|
274
|
+
* partial-write races with concurrent readers (e.g. session-start probes
|
|
275
|
+
* reading STATE.md while a write is in flight).
|
|
276
|
+
*
|
|
277
|
+
* @param {string} filePath
|
|
278
|
+
* @param {string} contents
|
|
279
|
+
*/
|
|
280
|
+
function writeFileAtomic(filePath, contents) {
|
|
281
|
+
const dir = dirname(filePath);
|
|
282
|
+
mkdirSync(dir, { recursive: true });
|
|
283
|
+
// Sibling tmp name uses high-entropy suffix to avoid collisions across
|
|
284
|
+
// concurrent attempts. Tmp + rename is atomic on the same filesystem (POSIX).
|
|
285
|
+
const tmpSuffix = Math.random().toString(36).slice(2, 10) + Date.now().toString(36);
|
|
286
|
+
const tmpFile = `${filePath}.tmp.${tmpSuffix}`;
|
|
287
|
+
writeFileSync(tmpFile, contents, { encoding: 'utf8' });
|
|
288
|
+
renameSync(tmpFile, filePath);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Lock-guarded STATE.md read-transform-write.
|
|
293
|
+
*
|
|
294
|
+
* Acquires `.orchestrator/state.lock` for the duration of the read +
|
|
295
|
+
* transform + write, releasing it on completion or throw. Concurrent
|
|
296
|
+
* callers serialize on the lock; the second caller observes the first
|
|
297
|
+
* caller's write.
|
|
298
|
+
*
|
|
299
|
+
* The transformer receives the CURRENT contents (string) and MUST return
|
|
300
|
+
* the new contents (string). If the transformer returns `null` or
|
|
301
|
+
* `undefined`, the write is skipped and the lock released — this is the
|
|
302
|
+
* intended path for "read, decide, no-op" flows.
|
|
303
|
+
*
|
|
304
|
+
* If STATE.md does not exist on disk, the transformer receives an empty
|
|
305
|
+
* string. Callers that need to assert presence should `existsSync` upfront.
|
|
306
|
+
*
|
|
307
|
+
* Size-ceiling guard (issue #739): before writing, `after` is checked against
|
|
308
|
+
* an absolute byte-size ceiling and a ratio-vs-`before` ceiling (see
|
|
309
|
+
* `evaluateSizeCeiling`). A breach REFUSES the write (leaves the prior
|
|
310
|
+
* on-disk contents intact — last-known-good) and emits a `⚠` WARN to
|
|
311
|
+
* `process.stderr`, rather than throwing — STATE.md is load-bearing and a
|
|
312
|
+
* hard throw here could wedge a session mid-wave. Pass
|
|
313
|
+
* `opts.throwOnCeiling: true` to opt into a thrown Error instead.
|
|
314
|
+
*
|
|
315
|
+
* Frontmatter-safe round-trip guard (issue #747 follow-up): after the
|
|
316
|
+
* size-ceiling check passes, `after`'s frontmatter block is independently
|
|
317
|
+
* verified to be a byte-fixpoint under a further
|
|
318
|
+
* `serializeStateMd(parseStateMd(after))` round-trip (see
|
|
319
|
+
* `evaluateFrontmatterSafe`). This is now ACTIVE (it was evaluated and
|
|
320
|
+
* deliberately left unshipped when issue #739 first landed, because the
|
|
321
|
+
* yaml-parser asymmetry it exists to catch made it false-positive on ordinary
|
|
322
|
+
* content at the time) — #747 closed that asymmetry in yaml-parser.mjs, so
|
|
323
|
+
* the check is safe to enforce. It catches FUTURE serializer/parser drift
|
|
324
|
+
* that reintroduces a non-idempotent frontmatter round-trip — the exact
|
|
325
|
+
* incident class behind #739 — rather than the symptom (file size) the
|
|
326
|
+
* size-ceiling guard was limited to. Same non-throw, WARN-then-refuse
|
|
327
|
+
* contract as the size-ceiling guard: pass `opts.throwOnFrontmatterUnsafe:
|
|
328
|
+
* true` to opt into a thrown Error instead.
|
|
329
|
+
*
|
|
330
|
+
* @param {string|undefined} repoRoot
|
|
331
|
+
* @param {(contents: string) => string|null|undefined|Promise<string|null|undefined>} transformer
|
|
332
|
+
* @param {object} [opts]
|
|
333
|
+
* @param {number} [opts.timeoutMs] — passed to acquireStateLock.
|
|
334
|
+
* @param {string} [opts.holder] — human-readable holder identifier.
|
|
335
|
+
* @param {number} [opts.pollMs] — test-only override of poll cadence.
|
|
336
|
+
* @param {number} [opts._ceilingBytes] — test-only override of the absolute
|
|
337
|
+
* size ceiling (default `DEFAULT_STATE_MD_SIZE_CEILING_BYTES`, 256 KB).
|
|
338
|
+
* Production callers MUST omit this option.
|
|
339
|
+
* @param {boolean} [opts.throwOnCeiling] — when true, a size-ceiling breach
|
|
340
|
+
* throws an `Error` (`.code === 'STATE_MD_SIZE_CEILING'`) instead of
|
|
341
|
+
* returning a no-op result. Default `false`.
|
|
342
|
+
* @param {boolean} [opts.throwOnFrontmatterUnsafe] — when true, a
|
|
343
|
+
* frontmatter-unsafe breach throws an `Error`
|
|
344
|
+
* (`.code === 'STATE_MD_FRONTMATTER_UNSAFE'`) instead of returning a no-op
|
|
345
|
+
* result. Default `false`.
|
|
346
|
+
* @returns {Promise<{ written: boolean, path: string, contents: string|null, reason?: string }>}
|
|
347
|
+
*/
|
|
348
|
+
export async function writeStateMd(repoRoot, transformer, opts = {}) {
|
|
349
|
+
if (typeof transformer !== 'function') {
|
|
350
|
+
throw new TypeError('writeStateMd: transformer must be a function');
|
|
351
|
+
}
|
|
352
|
+
const statePath = resolveStateMdPath(repoRoot);
|
|
353
|
+
return withStateMdLock(
|
|
354
|
+
repoRoot,
|
|
355
|
+
async () => {
|
|
356
|
+
const before = existsSync(statePath) ? readFileSync(statePath, 'utf8') : '';
|
|
357
|
+
const after = await transformer(before);
|
|
358
|
+
if (after === null || after === undefined) {
|
|
359
|
+
return { written: false, path: statePath, contents: null };
|
|
360
|
+
}
|
|
361
|
+
if (typeof after !== 'string') {
|
|
362
|
+
throw new TypeError('writeStateMd: transformer must return a string or null');
|
|
363
|
+
}
|
|
364
|
+
if (after === before) {
|
|
365
|
+
// No textual change — skip the write to keep mtime stable.
|
|
366
|
+
return { written: false, path: statePath, contents: before };
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const ceilingBytes = typeof opts._ceilingBytes === 'number' && opts._ceilingBytes > 0
|
|
370
|
+
? opts._ceilingBytes
|
|
371
|
+
: DEFAULT_STATE_MD_SIZE_CEILING_BYTES;
|
|
372
|
+
const ceilingCheck = evaluateSizeCeiling(before, after, ceilingBytes);
|
|
373
|
+
if (ceilingCheck.breached) {
|
|
374
|
+
process.stderr.write(
|
|
375
|
+
`⚠ writeStateMd: refusing write to ${statePath} (reason: size-ceiling) — ${ceilingCheck.reason}\n`
|
|
376
|
+
);
|
|
377
|
+
if (opts.throwOnCeiling === true) {
|
|
378
|
+
const err = new Error(`writeStateMd: size-ceiling breach — ${ceilingCheck.reason}`);
|
|
379
|
+
err.code = 'STATE_MD_SIZE_CEILING';
|
|
380
|
+
throw err;
|
|
381
|
+
}
|
|
382
|
+
return { written: false, path: statePath, contents: before, reason: 'size-ceiling' };
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const frontmatterCheck = evaluateFrontmatterSafe(after);
|
|
386
|
+
if (frontmatterCheck.unsafe) {
|
|
387
|
+
process.stderr.write(
|
|
388
|
+
`⚠ writeStateMd: refusing write to ${statePath} (reason: frontmatter-unsafe) — ${frontmatterCheck.reason}\n`
|
|
389
|
+
);
|
|
390
|
+
if (opts.throwOnFrontmatterUnsafe === true) {
|
|
391
|
+
const err = new Error(`writeStateMd: frontmatter-unsafe breach — ${frontmatterCheck.reason}`);
|
|
392
|
+
err.code = 'STATE_MD_FRONTMATTER_UNSAFE';
|
|
393
|
+
throw err;
|
|
394
|
+
}
|
|
395
|
+
return { written: false, path: statePath, contents: before, reason: 'frontmatter-unsafe' };
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
writeFileAtomic(statePath, after);
|
|
399
|
+
return { written: true, path: statePath, contents: after };
|
|
400
|
+
},
|
|
401
|
+
{ timeoutMs: opts.timeoutMs, holder: opts.holder, pollMs: opts.pollMs }
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Guard: throws a clear Error when repoRoot is undefined, null, or empty.
|
|
407
|
+
* Parallel-session CWD drift (PSA rules) makes `process.cwd()` fallbacks a
|
|
408
|
+
* footgun — callers MUST be explicit about which repo root they target.
|
|
409
|
+
*
|
|
410
|
+
* @param {unknown} repoRoot
|
|
411
|
+
* @param {string} fnName — name of the calling function, for error messages
|
|
412
|
+
*/
|
|
413
|
+
function requireRepoRoot(repoRoot, fnName) {
|
|
414
|
+
if (!repoRoot) {
|
|
415
|
+
throw new Error(
|
|
416
|
+
`${fnName}: repoRoot is required (got ${typeof repoRoot}). Pass an explicit repo root, e.g. via execSync('git rev-parse --show-toplevel').`
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Convenience wrapper: apply `updateFrontmatterFields` under the state-lock.
|
|
423
|
+
* Pure callers (in-memory transforms) should keep using
|
|
424
|
+
* `updateFrontmatterFields` directly — this wrapper exists for call sites
|
|
425
|
+
* that previously did the read + transform + write inline.
|
|
426
|
+
*
|
|
427
|
+
* @param {string} repoRoot — absolute path to the repository root (required)
|
|
428
|
+
* @param {object} fields
|
|
429
|
+
* @param {object} [opts]
|
|
430
|
+
* @returns {Promise<{ written: boolean, path: string, contents: string|null }>}
|
|
431
|
+
* @throws {Error} when repoRoot is undefined, null, or empty
|
|
432
|
+
*/
|
|
433
|
+
export async function updateFrontmatterFieldsOnDisk(repoRoot, fields, opts = {}) {
|
|
434
|
+
requireRepoRoot(repoRoot, 'updateFrontmatterFieldsOnDisk');
|
|
435
|
+
return writeStateMd(repoRoot, (contents) => updateFrontmatterFields(contents, fields), opts);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Convenience wrapper: apply `touchUpdatedField` under the state-lock.
|
|
440
|
+
*
|
|
441
|
+
* @param {string} repoRoot — absolute path to the repository root (required)
|
|
442
|
+
* @param {string} [isoTimestamp] — defaults to current time when omitted.
|
|
443
|
+
* @param {object} [opts]
|
|
444
|
+
* @returns {Promise<{ written: boolean, path: string, contents: string|null }>}
|
|
445
|
+
* @throws {Error} when repoRoot is undefined, null, or empty
|
|
446
|
+
*/
|
|
447
|
+
export async function touchUpdatedFieldOnDisk(repoRoot, isoTimestamp, opts = {}) {
|
|
448
|
+
requireRepoRoot(repoRoot, 'touchUpdatedFieldOnDisk');
|
|
449
|
+
const ts = typeof isoTimestamp === 'string' && isoTimestamp.length > 0
|
|
450
|
+
? isoTimestamp
|
|
451
|
+
: new Date().toISOString();
|
|
452
|
+
return writeStateMd(repoRoot, (contents) => touchUpdatedField(contents, ts), opts);
|
|
453
|
+
}
|