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,539 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* session-close-backfill.mjs — SessionEnd close-through backfill (Epic #724 C1).
|
|
3
|
+
*
|
|
4
|
+
* Fleet-wide only ~27% of started sessions ever reach a /close (this repo: 137
|
|
5
|
+
* distinct `orchestrator.session.started` events vs 36 sessions.jsonl records).
|
|
6
|
+
* When a session terminates without running /close, no session record is ever
|
|
7
|
+
* written — the work is invisible to every downstream sessions.jsonl consumer.
|
|
8
|
+
*
|
|
9
|
+
* This module reconstructs a MINIMAL `status: 'abandoned'` stub from the
|
|
10
|
+
* `.orchestrator/metrics/events.jsonl` breadcrumbs the lifecycle hooks already
|
|
11
|
+
* emit, and appends it through the SAME validate + round-trip + append path the
|
|
12
|
+
* normal writer (`scripts/emit-session.mjs`) uses. It is invoked best-effort
|
|
13
|
+
* from `hooks/on-session-end.mjs` and by the one-time historical migration CLI
|
|
14
|
+
* `scripts/backfill-abandoned-sessions.mjs`.
|
|
15
|
+
*
|
|
16
|
+
* ── ID BRIDGE ────────────────────────────────────────────────────────────────
|
|
17
|
+
* sessions.jsonl records are keyed by SEMANTIC ids (`main-2026-05-27-session-1`).
|
|
18
|
+
* events.jsonl carries the harness UUID on `session.started` / `stop` / `ended`.
|
|
19
|
+
* The bridge is the `orchestrator.session.lock.acquired` event, which is the
|
|
20
|
+
* only record carrying BOTH `session_id` (UUID) and `semantic_session_id`.
|
|
21
|
+
* Only ~1/3 of sessions ever emit a lock.acquired, so when the bridge is
|
|
22
|
+
* missing we fall back to a synthetic id + `_synthetic_session_id: true`.
|
|
23
|
+
*
|
|
24
|
+
* ── SAFETY POSTURE ──────────────────────────────────────────────────────────
|
|
25
|
+
* - No-throw: every path returns a structured `{ action, ... }` result; the
|
|
26
|
+
* hook must never be pushed past its teardown timeout by an exception.
|
|
27
|
+
* - Dedupe: never double-write a session already present in sessions.jsonl.
|
|
28
|
+
* - Liveness guard: never backfill over a FOREIGN live session.lock (PSA).
|
|
29
|
+
* - TOCTOU marker: an atomic `openSync(..., 'wx')` claim file keyed by the
|
|
30
|
+
* final id serialises concurrent backfill attempts (mirrors the
|
|
31
|
+
* on-session-start registry-slot claim, #587).
|
|
32
|
+
* - dryRun: computes + validates the stub without touching disk (used by the
|
|
33
|
+
* migration CLI's `--dry-run` default).
|
|
34
|
+
*
|
|
35
|
+
* Plain Node ESM. Named exports. DI-friendly via `deps` (mirrors enumerate.mjs).
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
import fs from 'node:fs';
|
|
39
|
+
import path from 'node:path';
|
|
40
|
+
import crypto from 'node:crypto';
|
|
41
|
+
|
|
42
|
+
import { appendJsonl as defaultAppendJsonl } from './common.mjs';
|
|
43
|
+
import { readLock as defaultReadLock, isLockLive as defaultIsLockLive, DEFAULT_TTL_HOURS } from './session-lock.mjs';
|
|
44
|
+
import { validateSession as defaultValidateSession } from './session-schema/validator.mjs';
|
|
45
|
+
import { serializeSessionLineChecked as defaultSerialize } from '../emit-session.mjs';
|
|
46
|
+
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
// Constants
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
|
|
51
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
52
|
+
|
|
53
|
+
/** session_type enum accepted by the schema — lock.mode is coerced against it. */
|
|
54
|
+
const VALID_SESSION_TYPES = new Set(['feature', 'deep', 'housekeeping']);
|
|
55
|
+
|
|
56
|
+
const EVENT_STARTED = 'orchestrator.session.started';
|
|
57
|
+
const EVENT_LOCK_ACQUIRED = 'orchestrator.session.lock.acquired';
|
|
58
|
+
const EVENT_STOPPED = 'orchestrator.session.stopped';
|
|
59
|
+
const EVENT_ENDED = 'orchestrator.session.ended';
|
|
60
|
+
|
|
61
|
+
const EVENTS_REL = ['.orchestrator', 'metrics', 'events.jsonl'];
|
|
62
|
+
const SESSIONS_REL = ['.orchestrator', 'metrics', 'sessions.jsonl'];
|
|
63
|
+
const BACKFILL_LOG_REL = ['.orchestrator', 'metrics', 'session-close-backfill.log'];
|
|
64
|
+
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
// Helpers
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
|
|
69
|
+
/** True when `s` is a canonical UUID (v4-shaped). */
|
|
70
|
+
export function isUuid(s) {
|
|
71
|
+
return typeof s === 'string' && UUID_RE.test(s);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Filesystem-safe marker filename for an arbitrary session id. */
|
|
75
|
+
function markerName(id) {
|
|
76
|
+
const cleaned = String(id).replace(/[^a-zA-Z0-9._-]/g, '_').slice(0, 120);
|
|
77
|
+
return `.backfilled-${cleaned}.marker`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Read a JSONL file into an array of parsed objects. Missing file → []; each
|
|
82
|
+
* malformed line is skipped rather than aborting the whole read. Never throws.
|
|
83
|
+
*/
|
|
84
|
+
function readJsonlSafe(readFileSync, filePath) {
|
|
85
|
+
let raw;
|
|
86
|
+
try {
|
|
87
|
+
raw = readFileSync(filePath, 'utf8');
|
|
88
|
+
} catch {
|
|
89
|
+
return [];
|
|
90
|
+
}
|
|
91
|
+
const out = [];
|
|
92
|
+
for (const line of raw.split('\n')) {
|
|
93
|
+
const trimmed = line.trim();
|
|
94
|
+
if (!trimmed) continue;
|
|
95
|
+
try {
|
|
96
|
+
out.push(JSON.parse(trimmed));
|
|
97
|
+
} catch {
|
|
98
|
+
/* skip malformed line */
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Normalise any parseable timestamp to canonical ISO-8601 UTC ms form. */
|
|
105
|
+
function canonicalIso(value, fallbackMs) {
|
|
106
|
+
const ms = typeof value === 'string' ? Date.parse(value) : NaN;
|
|
107
|
+
if (!Number.isNaN(ms)) return new Date(ms).toISOString();
|
|
108
|
+
const fb = Number.isFinite(fallbackMs) ? fallbackMs : Date.now();
|
|
109
|
+
return new Date(fb).toISOString();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Walk events.jsonl and gather everything known about one session, bridging
|
|
114
|
+
* UUID ↔ semantic id via lock.acquired. Returns provenance used to synthesize
|
|
115
|
+
* the record (never throws).
|
|
116
|
+
*
|
|
117
|
+
* @param {Array<object>} events
|
|
118
|
+
* @param {{ sessionId: string|null, semanticSessionId: string|null }} ids
|
|
119
|
+
*/
|
|
120
|
+
function collectSessionEvents(events, { sessionId, semanticSessionId }) {
|
|
121
|
+
const uuids = new Set();
|
|
122
|
+
if (isUuid(sessionId)) uuids.add(sessionId);
|
|
123
|
+
|
|
124
|
+
let mode = null;
|
|
125
|
+
let semanticFromLock = null;
|
|
126
|
+
|
|
127
|
+
// First pass — lock.acquired bridges the UUID set + carries mode + semantic.
|
|
128
|
+
for (const ev of events) {
|
|
129
|
+
if (ev.event !== EVENT_LOCK_ACQUIRED) continue;
|
|
130
|
+
const matchesUuid = isUuid(sessionId) && ev.session_id === sessionId;
|
|
131
|
+
const matchesSemantic =
|
|
132
|
+
(semanticSessionId && ev.semantic_session_id === semanticSessionId) ||
|
|
133
|
+
(!isUuid(sessionId) && sessionId && ev.semantic_session_id === sessionId);
|
|
134
|
+
if (!matchesUuid && !matchesSemantic) continue;
|
|
135
|
+
if (typeof ev.session_id === 'string') uuids.add(ev.session_id);
|
|
136
|
+
if (typeof ev.mode === 'string') mode = ev.mode;
|
|
137
|
+
if (typeof ev.semantic_session_id === 'string') semanticFromLock = ev.semantic_session_id;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Second pass — started + terminal timestamps from every matched UUID.
|
|
141
|
+
let startedAt = null;
|
|
142
|
+
let branch = null;
|
|
143
|
+
let project = null;
|
|
144
|
+
let lastTerminalMs = null;
|
|
145
|
+
let earliestMs = null;
|
|
146
|
+
// lastEventMs (#731): max timestamp over ALL matched events (not just the
|
|
147
|
+
// terminal STOPPED/ENDED subset lastTerminalMs tracks) — the dead-by-age
|
|
148
|
+
// relaxation needs "when did we last hear from this candidate at all",
|
|
149
|
+
// since a session that never emitted a terminal event is exactly the
|
|
150
|
+
// abandoned case this module exists to reconstruct.
|
|
151
|
+
let lastEventMs = null;
|
|
152
|
+
|
|
153
|
+
for (const ev of events) {
|
|
154
|
+
if (typeof ev.session_id !== 'string' || !uuids.has(ev.session_id)) continue;
|
|
155
|
+
const ts = typeof ev.timestamp === 'string' ? Date.parse(ev.timestamp) : NaN;
|
|
156
|
+
if (!Number.isNaN(ts)) {
|
|
157
|
+
earliestMs = earliestMs === null ? ts : Math.min(earliestMs, ts);
|
|
158
|
+
lastEventMs = lastEventMs === null ? ts : Math.max(lastEventMs, ts);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (ev.event === EVENT_STARTED) {
|
|
162
|
+
if (typeof ev.timestamp === 'string') startedAt = ev.timestamp;
|
|
163
|
+
if (typeof ev.branch === 'string' && ev.branch.length > 0) branch = ev.branch;
|
|
164
|
+
if (typeof ev.project === 'string') project = ev.project;
|
|
165
|
+
} else if (ev.event === EVENT_STOPPED || ev.event === EVENT_ENDED) {
|
|
166
|
+
if (!Number.isNaN(ts)) {
|
|
167
|
+
lastTerminalMs = lastTerminalMs === null ? ts : Math.max(lastTerminalMs, ts);
|
|
168
|
+
}
|
|
169
|
+
if (typeof ev.branch === 'string' && ev.branch.length > 0 && !branch) branch = ev.branch;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return { uuids, mode, semanticFromLock, startedAt, branch, project, lastTerminalMs, earliestMs, lastEventMs };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Determine whether a candidate should be treated as dead-by-age DESPITE a
|
|
178
|
+
* live FOREIGN session.lock (#731 — the historical migration CLI blocks
|
|
179
|
+
* itself: every run happens FROM an active session, so the current lock is
|
|
180
|
+
* always live and shadows every candidate regardless of how old it is).
|
|
181
|
+
*
|
|
182
|
+
* True when EITHER:
|
|
183
|
+
* - `assumeDeadBeforeMs` is set and the candidate's last known event
|
|
184
|
+
* strictly PREDATES it (operator-supplied cutoff, CLI `--assume-dead-before`).
|
|
185
|
+
* - `relaxDeadByAge` is set and the candidate's last known event is older
|
|
186
|
+
* than the lock's own default TTL window (`DEFAULT_TTL_HOURS`) — a
|
|
187
|
+
* session that stopped emitting events longer ago than a lock can even
|
|
188
|
+
* stay live cannot legitimately be "blocked" by that unrelated lock.
|
|
189
|
+
*
|
|
190
|
+
* Both conditions require a resolvable `lastEventMs` — an unknown last-event
|
|
191
|
+
* time (gap in events.jsonl) never unlocks the relaxation, erring toward the
|
|
192
|
+
* existing conservative (block) behaviour. NOT exported: internal to the
|
|
193
|
+
* liveness guard below; default caller behaviour (both params absent) always
|
|
194
|
+
* returns false, i.e. identical to pre-#731 behaviour.
|
|
195
|
+
*
|
|
196
|
+
* @param {{ relaxDeadByAge: boolean, assumeDeadBeforeMs: number|null, lastEventMs: number|null, nowMs: number }} args
|
|
197
|
+
* @returns {boolean}
|
|
198
|
+
*/
|
|
199
|
+
function isCandidateDeadByAge({ relaxDeadByAge, assumeDeadBeforeMs, lastEventMs, nowMs }) {
|
|
200
|
+
if (typeof lastEventMs !== 'number' || Number.isNaN(lastEventMs)) return false;
|
|
201
|
+
if (
|
|
202
|
+
typeof assumeDeadBeforeMs === 'number' &&
|
|
203
|
+
!Number.isNaN(assumeDeadBeforeMs) &&
|
|
204
|
+
lastEventMs < assumeDeadBeforeMs
|
|
205
|
+
) {
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
if (relaxDeadByAge === true) {
|
|
209
|
+
const ttlMs = DEFAULT_TTL_HOURS * 3600 * 1000;
|
|
210
|
+
if (nowMs - lastEventMs > ttlMs) return true;
|
|
211
|
+
}
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Build the abandoned-session stub record. Required fields with no events
|
|
217
|
+
* source are defaulted to empty/zero and enumerated in
|
|
218
|
+
* `_backfill_incomplete_fields`; the mode → session_type coercion sets
|
|
219
|
+
* `_session_type_inferred`.
|
|
220
|
+
*/
|
|
221
|
+
function synthesizeRecord({ recordId, synthetic, gathered, nowMs }) {
|
|
222
|
+
const startedIso = canonicalIso(gathered.startedAt, gathered.earliestMs ?? nowMs);
|
|
223
|
+
const startedMs = Date.parse(startedIso);
|
|
224
|
+
const terminalMs = Number.isFinite(gathered.lastTerminalMs) ? gathered.lastTerminalMs : nowMs;
|
|
225
|
+
// completed_at = last terminal event (or now), never earlier than started_at.
|
|
226
|
+
const completedIso = new Date(Math.max(startedMs, terminalMs)).toISOString();
|
|
227
|
+
|
|
228
|
+
let sessionType = 'housekeeping';
|
|
229
|
+
let inferred = true;
|
|
230
|
+
if (gathered.mode && VALID_SESSION_TYPES.has(gathered.mode)) {
|
|
231
|
+
sessionType = gathered.mode;
|
|
232
|
+
inferred = false;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const startedFound = typeof gathered.startedAt === 'string';
|
|
236
|
+
const branchFound = typeof gathered.branch === 'string' && gathered.branch.length > 0;
|
|
237
|
+
|
|
238
|
+
const incomplete = ['total_waves', 'waves', 'agent_summary', 'total_agents', 'total_files_changed'];
|
|
239
|
+
if (!startedFound) incomplete.push('started_at');
|
|
240
|
+
if (!branchFound) incomplete.push('branch');
|
|
241
|
+
|
|
242
|
+
const record = {
|
|
243
|
+
session_id: recordId,
|
|
244
|
+
session_type: sessionType,
|
|
245
|
+
started_at: startedIso,
|
|
246
|
+
completed_at: completedIso,
|
|
247
|
+
total_waves: 0,
|
|
248
|
+
waves: [],
|
|
249
|
+
agent_summary: { complete: 0, partial: 0, failed: 0, spiral: 0 },
|
|
250
|
+
total_agents: 0,
|
|
251
|
+
total_files_changed: 0,
|
|
252
|
+
status: 'abandoned',
|
|
253
|
+
// Issue #773 — an abandoned session never ran Phase 1.65, so its carryover
|
|
254
|
+
// is genuinely UNKNOWN. Emit `null` (not 0) so downstream effectiveness
|
|
255
|
+
// consumers can tell "not measured" apart from "measured zero" — 0 here
|
|
256
|
+
// would resurrect the very carryover=0 blind spot #773 exists to close.
|
|
257
|
+
effectiveness: { carryover: null },
|
|
258
|
+
_backfill_source: 'events-jsonl',
|
|
259
|
+
_backfill_incomplete_fields: incomplete,
|
|
260
|
+
};
|
|
261
|
+
if (branchFound) record.branch = gathered.branch;
|
|
262
|
+
if (inferred) record._session_type_inferred = true;
|
|
263
|
+
if (synthetic) record._synthetic_session_id = true;
|
|
264
|
+
return record;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Dedupe against sessions.jsonl. Returns a `skipped-already-recorded` result
|
|
269
|
+
* when `recordId` (or a UUID `sessionId` written directly as a key) is already
|
|
270
|
+
* present, else `null`. Reads the (small) sessions.jsonl exactly once.
|
|
271
|
+
*/
|
|
272
|
+
function checkAlreadyRecorded(readFileSync, sessionsPath, { recordId, sessionId }) {
|
|
273
|
+
const sessionRecords = readJsonlSafe(readFileSync, sessionsPath);
|
|
274
|
+
const existingIds = new Set(
|
|
275
|
+
sessionRecords.map((r) => (r && typeof r.session_id === 'string' ? r.session_id : null)).filter(Boolean)
|
|
276
|
+
);
|
|
277
|
+
if (existingIds.has(recordId)) {
|
|
278
|
+
return { action: 'skipped-already-recorded', sessionId: recordId };
|
|
279
|
+
}
|
|
280
|
+
// Defensive: a prior record keyed directly by the UUID also counts.
|
|
281
|
+
if (isUuid(sessionId) && existingIds.has(sessionId)) {
|
|
282
|
+
return { action: 'skipped-already-recorded', sessionId };
|
|
283
|
+
}
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// ---------------------------------------------------------------------------
|
|
288
|
+
// Public API
|
|
289
|
+
// ---------------------------------------------------------------------------
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Backfill an abandoned session record into sessions.jsonl from events.jsonl.
|
|
293
|
+
*
|
|
294
|
+
* Never throws. Returns one of:
|
|
295
|
+
* { action: 'backfilled', sessionId, record, deadByAge? } — written to disk
|
|
296
|
+
* { action: 'would-backfill', sessionId, record, deadByAge? } — dryRun only, not written
|
|
297
|
+
* { action: 'skipped-no-identifier' } — neither id known
|
|
298
|
+
* { action: 'skipped-already-recorded', sessionId } — already in sessions.jsonl
|
|
299
|
+
* { action: 'skipped-foreign-live-lock', sessionId, lockSessionId }
|
|
300
|
+
* { action: 'skipped-marker-exists', sessionId } — lost the TOCTOU claim
|
|
301
|
+
* { action: 'error', error, sessionId? } — any failure, swallowed
|
|
302
|
+
*
|
|
303
|
+
* `deadByAge: true` is present on `backfilled` / `would-backfill` ONLY when a
|
|
304
|
+
* foreign live lock was present AND bypassed via `relaxDeadByAge` /
|
|
305
|
+
* `assumeDeadBeforeMs` (#731) — it never appears on the default path, so
|
|
306
|
+
* callers can distinguish "genuinely no conflicting lock" from "we relaxed
|
|
307
|
+
* past one" without re-deriving the guard logic.
|
|
308
|
+
*
|
|
309
|
+
* @param {object} args
|
|
310
|
+
* @param {string} args.repoRoot absolute project root
|
|
311
|
+
* @param {string|null} [args.sessionId] harness UUID (SessionEnd stdin) or semantic id
|
|
312
|
+
* @param {string|null} [args.semanticSessionId] semantic id when already known (current-session.json)
|
|
313
|
+
* @param {number|string} [args.now] ms-since-epoch (test seam) or ISO string
|
|
314
|
+
* @param {boolean} [args.dryRun=false] compute + validate only, no marker/write
|
|
315
|
+
* @param {boolean} [args.relaxDeadByAge=false]
|
|
316
|
+
* #731 — when true, a FOREIGN live lock no longer blocks a candidate whose
|
|
317
|
+
* last known event (`lastEventMs`) is older than `DEFAULT_TTL_HOURS`
|
|
318
|
+
* (session-lock.mjs SSOT). Purely additive: default `false` reproduces the
|
|
319
|
+
* original always-block behaviour EXACTLY. Intended for the one-time
|
|
320
|
+
* historical migration CLI only — `hooks/on-session-end.mjs` must NEVER
|
|
321
|
+
* pass this (a hook-time foreign lock is by definition a real, active
|
|
322
|
+
* session, not stale history).
|
|
323
|
+
* @param {number|null} [args.assumeDeadBeforeMs=null]
|
|
324
|
+
* #731 — operator-supplied cutoff (ms-since-epoch). A candidate whose
|
|
325
|
+
* `lastEventMs` strictly predates this value bypasses a foreign live lock
|
|
326
|
+
* regardless of `relaxDeadByAge`. Corresponds to the CLI's
|
|
327
|
+
* `--assume-dead-before <ISO>` flag.
|
|
328
|
+
* @param {object} [args.deps] DI overrides (fs, appendJsonl, readLock, …)
|
|
329
|
+
* @returns {Promise<object>}
|
|
330
|
+
*/
|
|
331
|
+
export async function backfillAbandonedSession({
|
|
332
|
+
repoRoot,
|
|
333
|
+
sessionId = null,
|
|
334
|
+
semanticSessionId = null,
|
|
335
|
+
now = Date.now(),
|
|
336
|
+
dryRun = false,
|
|
337
|
+
relaxDeadByAge = false,
|
|
338
|
+
assumeDeadBeforeMs = null,
|
|
339
|
+
deps = {},
|
|
340
|
+
} = {}) {
|
|
341
|
+
const {
|
|
342
|
+
readFileSync = fs.readFileSync,
|
|
343
|
+
appendJsonl = defaultAppendJsonl,
|
|
344
|
+
readLock = defaultReadLock,
|
|
345
|
+
isLockLive = defaultIsLockLive,
|
|
346
|
+
openSync = fs.openSync,
|
|
347
|
+
closeSync = fs.closeSync,
|
|
348
|
+
validateSession = defaultValidateSession,
|
|
349
|
+
serializeSessionLineChecked = defaultSerialize,
|
|
350
|
+
log = null,
|
|
351
|
+
} = deps;
|
|
352
|
+
|
|
353
|
+
const nowMs = resolveNowMs(now);
|
|
354
|
+
const result = await run();
|
|
355
|
+
logBreadcrumb(result);
|
|
356
|
+
return result;
|
|
357
|
+
|
|
358
|
+
async function run() {
|
|
359
|
+
try {
|
|
360
|
+
if (typeof repoRoot !== 'string' || repoRoot.length === 0) {
|
|
361
|
+
return { action: 'error', error: 'repoRoot must be a non-empty string' };
|
|
362
|
+
}
|
|
363
|
+
if (!sessionId && !semanticSessionId) {
|
|
364
|
+
return { action: 'skipped-no-identifier' };
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const eventsPath = path.join(repoRoot, ...EVENTS_REL);
|
|
368
|
+
const sessionsPath = path.join(repoRoot, ...SESSIONS_REL);
|
|
369
|
+
|
|
370
|
+
// -- Resolve the SEMANTIC record id (sessions.jsonl key) ----------------
|
|
371
|
+
// Two-phase so the common SessionEnd path stays cheap: when the id is
|
|
372
|
+
// already known WITHOUT scanning events (semanticSessionId given, or
|
|
373
|
+
// sessionId is itself a semantic id), dedupe against the tiny
|
|
374
|
+
// sessions.jsonl FIRST and short-circuit if the record already exists —
|
|
375
|
+
// avoiding the two-pass walk over an events.jsonl that can be MBs. The
|
|
376
|
+
// events read is deferred to the branches that genuinely need it: the
|
|
377
|
+
// UUID→semantic lock bridge, the synthetic-id mint, and record synthesis.
|
|
378
|
+
// (When sessionId is a non-UUID semantic id, the ORIGINAL lock-bridge
|
|
379
|
+
// branch resolved to that SAME id — collectSessionEvents only sets
|
|
380
|
+
// semanticFromLock=sessionId in that case — so resolving it early is
|
|
381
|
+
// behaviour-identical, just cheaper.)
|
|
382
|
+
let recordId = null;
|
|
383
|
+
let synthetic = false;
|
|
384
|
+
if (semanticSessionId) {
|
|
385
|
+
recordId = semanticSessionId;
|
|
386
|
+
} else if (sessionId && !isUuid(sessionId)) {
|
|
387
|
+
// sessionId is already a semantic id (Claude Code generated-semantic path).
|
|
388
|
+
recordId = sessionId;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// -- Cheap dedupe FIRST when the id needs no events scan -----------------
|
|
392
|
+
if (recordId !== null) {
|
|
393
|
+
const dupe = checkAlreadyRecorded(readFileSync, sessionsPath, { recordId, sessionId });
|
|
394
|
+
if (dupe) return dupe;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// -- Read events (needed to synthesize, and to bridge UUID→semantic) ----
|
|
398
|
+
const events = readJsonlSafe(readFileSync, eventsPath);
|
|
399
|
+
const gathered = collectSessionEvents(events, { sessionId, semanticSessionId });
|
|
400
|
+
|
|
401
|
+
// -- Resolve a deferred id from the lock bridge or a synthetic mint ------
|
|
402
|
+
if (recordId === null) {
|
|
403
|
+
if (gathered.semanticFromLock) {
|
|
404
|
+
recordId = gathered.semanticFromLock;
|
|
405
|
+
} else {
|
|
406
|
+
// No semantic bridge — mint a synthetic id. Both components are STABLE
|
|
407
|
+
// across re-runs so dedupe/marker suppress a double write (idempotency
|
|
408
|
+
// is load-bearing for the one-time CLI, #724 C1):
|
|
409
|
+
// - date ← the session's own started_at (immutable in events), not
|
|
410
|
+
// run-time now, so a migration run on any day is stable.
|
|
411
|
+
// - suffix← sha256 of the source UUID, not random.
|
|
412
|
+
const branchSlug = (gathered.branch || 'unknown').replace(/[^a-zA-Z0-9._-]/g, '-');
|
|
413
|
+
const date = canonicalIso(gathered.startedAt, gathered.earliestMs ?? nowMs).slice(0, 10);
|
|
414
|
+
const suffix = crypto.createHash('sha256').update(String(sessionId)).digest('hex').slice(0, 8);
|
|
415
|
+
recordId = `${branchSlug}-${date}-abandoned-${suffix}`;
|
|
416
|
+
synthetic = true;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Dedupe once the events-derived id is known.
|
|
420
|
+
const dupe = checkAlreadyRecorded(readFileSync, sessionsPath, { recordId, sessionId });
|
|
421
|
+
if (dupe) return dupe;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// -- Liveness guard — never overwrite a FOREIGN live lock ---------------
|
|
425
|
+
// deadByAge (#731): set when a foreign live lock was present but the
|
|
426
|
+
// candidate qualified for relaxation — surfaced on the final result so
|
|
427
|
+
// callers (the migration CLI's summary) can count relaxed backfills.
|
|
428
|
+
let deadByAge = false;
|
|
429
|
+
let lock = null;
|
|
430
|
+
try {
|
|
431
|
+
lock = readLock({ repoRoot });
|
|
432
|
+
} catch {
|
|
433
|
+
lock = null;
|
|
434
|
+
}
|
|
435
|
+
if (lock) {
|
|
436
|
+
const ownByUuid = Boolean(sessionId) && lock.session_id === sessionId;
|
|
437
|
+
const ownBySemantic =
|
|
438
|
+
(Boolean(semanticSessionId) && lock.semantic_session_id === semanticSessionId) ||
|
|
439
|
+
(Boolean(recordId) && lock.semantic_session_id === recordId);
|
|
440
|
+
const foreign = !ownByUuid && !ownBySemantic;
|
|
441
|
+
if (foreign && isLockLive(lock, nowMs)) {
|
|
442
|
+
const relaxed = isCandidateDeadByAge({
|
|
443
|
+
relaxDeadByAge,
|
|
444
|
+
assumeDeadBeforeMs,
|
|
445
|
+
lastEventMs: gathered.lastEventMs,
|
|
446
|
+
nowMs,
|
|
447
|
+
});
|
|
448
|
+
if (!relaxed) {
|
|
449
|
+
return { action: 'skipped-foreign-live-lock', sessionId: recordId, lockSessionId: lock.session_id };
|
|
450
|
+
}
|
|
451
|
+
deadByAge = true;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// -- Synthesize + validate (round-trip gate) BEFORE any disk mutation ---
|
|
456
|
+
const record = synthesizeRecord({ recordId, synthetic, gathered, nowMs });
|
|
457
|
+
let validated;
|
|
458
|
+
try {
|
|
459
|
+
validated = validateSession(record);
|
|
460
|
+
serializeSessionLineChecked(validated);
|
|
461
|
+
} catch (err) {
|
|
462
|
+
return { action: 'error', error: `validation: ${err?.message ?? String(err)}`, sessionId: recordId };
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
if (dryRun) {
|
|
466
|
+
return {
|
|
467
|
+
action: 'would-backfill',
|
|
468
|
+
sessionId: recordId,
|
|
469
|
+
record: validated,
|
|
470
|
+
...(deadByAge ? { deadByAge: true } : {}),
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// -- TOCTOU marker — atomic create-or-fail keyed by the final id --------
|
|
475
|
+
const markerPath = path.join(repoRoot, '.orchestrator', 'metrics', markerName(recordId));
|
|
476
|
+
try {
|
|
477
|
+
const fd = openSync(markerPath, 'wx');
|
|
478
|
+
closeSync(fd);
|
|
479
|
+
} catch (err) {
|
|
480
|
+
if (err && err.code === 'EEXIST') {
|
|
481
|
+
return { action: 'skipped-marker-exists', sessionId: recordId };
|
|
482
|
+
}
|
|
483
|
+
// Any other fs error on the marker → refuse to write without the guard.
|
|
484
|
+
return { action: 'error', error: `marker: ${err?.message ?? String(err)}`, sessionId: recordId };
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// -- Write via the shared append path -----------------------------------
|
|
488
|
+
try {
|
|
489
|
+
await appendJsonl(sessionsPath, validated);
|
|
490
|
+
} catch (err) {
|
|
491
|
+
return { action: 'error', error: `append: ${err?.message ?? String(err)}`, sessionId: recordId };
|
|
492
|
+
}
|
|
493
|
+
return {
|
|
494
|
+
action: 'backfilled',
|
|
495
|
+
sessionId: recordId,
|
|
496
|
+
record: validated,
|
|
497
|
+
...(deadByAge ? { deadByAge: true } : {}),
|
|
498
|
+
};
|
|
499
|
+
} catch (err) {
|
|
500
|
+
// Absolute backstop — the hook must never see an exception from here.
|
|
501
|
+
return { action: 'error', error: err?.message ?? String(err) };
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/** Best-effort JSONL breadcrumb (project-local; never cascades). */
|
|
506
|
+
function logBreadcrumb(res) {
|
|
507
|
+
try {
|
|
508
|
+
if (typeof log === 'function') {
|
|
509
|
+
log(res);
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
if (typeof repoRoot !== 'string' || repoRoot.length === 0) return;
|
|
513
|
+
const logPath = path.join(repoRoot, ...BACKFILL_LOG_REL);
|
|
514
|
+
fs.mkdirSync(path.dirname(logPath), { recursive: true });
|
|
515
|
+
fs.appendFileSync(
|
|
516
|
+
logPath,
|
|
517
|
+
JSON.stringify({
|
|
518
|
+
timestamp: new Date(nowMs).toISOString(),
|
|
519
|
+
action: res.action,
|
|
520
|
+
session_id: res.sessionId ?? null,
|
|
521
|
+
...(res.error ? { error: res.error } : {}),
|
|
522
|
+
}) + '\n',
|
|
523
|
+
'utf8'
|
|
524
|
+
);
|
|
525
|
+
} catch {
|
|
526
|
+
/* never let logging cascade into the caller */
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/** Normalise the `now` arg (number ms | ISO string | undefined) → ms. */
|
|
532
|
+
function resolveNowMs(now) {
|
|
533
|
+
if (typeof now === 'number' && Number.isFinite(now)) return now;
|
|
534
|
+
if (typeof now === 'string') {
|
|
535
|
+
const ms = Date.parse(now);
|
|
536
|
+
if (!Number.isNaN(ms)) return ms;
|
|
537
|
+
}
|
|
538
|
+
return Date.now();
|
|
539
|
+
}
|