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,355 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* session-registry.mjs — Per-session heartbeat files + peer detection.
|
|
3
|
+
*
|
|
4
|
+
* Part of Sub-Epic F (#159) of Epic #157. Backs hooks/on-session-start.mjs
|
|
5
|
+
* peer-detection (F2, #168) and hooks/on-stop.mjs clean deregister + zombie
|
|
6
|
+
* sweep (F3, #169).
|
|
7
|
+
*
|
|
8
|
+
* Registry location: `~/.config/session-orchestrator/sessions/active/<sessionId>.json`
|
|
9
|
+
* Sweep log: `~/.config/session-orchestrator/sessions/sweep.log` (JSONL)
|
|
10
|
+
*
|
|
11
|
+
* Overridable via env var `SO_SESSION_REGISTRY_DIR` (points to the parent
|
|
12
|
+
* `sessions/` directory, not `active/`) — used by tests for isolation.
|
|
13
|
+
*
|
|
14
|
+
* Heartbeat schema (per issue #167):
|
|
15
|
+
* {
|
|
16
|
+
* session_id, pid, platform, repo_path_hash, repo_name, branch,
|
|
17
|
+
* started_at, last_heartbeat, status ('active'|'wave'|'idle'),
|
|
18
|
+
* current_wave, host_class, mode (Epic #583 W2-I3)
|
|
19
|
+
* }
|
|
20
|
+
*
|
|
21
|
+
* Schema v2 (Epic #583, W2-I3): `mode` field added so the exclusivity-matrix
|
|
22
|
+
* can classify cross-repo registry entries correctly. Without it, every
|
|
23
|
+
* registry-sourced peer was bucketed as `mode='session'` → classifyMode threw
|
|
24
|
+
* → fell back to `parallel-ok`, silently bypassing the exclusivity matrix
|
|
25
|
+
* for cross-repo entries (D5 from Epic #583 audit). The field is optional
|
|
26
|
+
* on read for back-compat with v1 entries (defaults to null).
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import os from 'node:os';
|
|
30
|
+
import path from 'node:path';
|
|
31
|
+
import crypto from 'node:crypto';
|
|
32
|
+
import { promises as fs } from 'node:fs';
|
|
33
|
+
import { digestSha256 } from './crypto-digest-utils.mjs';
|
|
34
|
+
|
|
35
|
+
import { appendFileSync, mkdirSync } from 'node:fs';
|
|
36
|
+
|
|
37
|
+
import { utcTimestamp, appendJsonl } from './common.mjs';
|
|
38
|
+
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
// Paths
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
/** Parent directory for all session-registry state. */
|
|
44
|
+
export function registryBaseDir() {
|
|
45
|
+
const override = process.env.SO_SESSION_REGISTRY_DIR;
|
|
46
|
+
if (override && override.length > 0) return override;
|
|
47
|
+
return path.join(os.homedir(), '.config', 'session-orchestrator', 'sessions');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Directory holding one JSON file per active session. */
|
|
51
|
+
export function activeDir() {
|
|
52
|
+
return path.join(registryBaseDir(), 'active');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Zombie-sweep observability log (JSONL). */
|
|
56
|
+
export function sweepLogPath() {
|
|
57
|
+
return path.join(registryBaseDir(), 'sweep.log');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function entryPath(sessionId) {
|
|
61
|
+
_assertSessionId(sessionId);
|
|
62
|
+
return path.join(activeDir(), `${sessionId}.json`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
// Primitives
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Stable SHA-256 hex of an absolute path. Used so registry entries can be
|
|
71
|
+
* correlated by repo without exposing local filesystem layout.
|
|
72
|
+
*/
|
|
73
|
+
export function repoPathHash(absPath) {
|
|
74
|
+
if (typeof absPath !== 'string' || absPath.length === 0) {
|
|
75
|
+
throw new TypeError('repoPathHash: absPath must be a non-empty string');
|
|
76
|
+
}
|
|
77
|
+
return digestSha256(path.resolve(absPath));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function _assertSessionId(sessionId) {
|
|
81
|
+
if (typeof sessionId !== 'string' || sessionId.length === 0) {
|
|
82
|
+
throw new TypeError('session_id must be a non-empty string');
|
|
83
|
+
}
|
|
84
|
+
// Guard against path separators / traversal in filename.
|
|
85
|
+
if (sessionId.includes('/') || sessionId.includes('\\') || sessionId.includes('\x00') || sessionId === '.' || sessionId === '..') {
|
|
86
|
+
throw new TypeError('session_id must not contain path separators or null bytes');
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function _writeJsonAtomic(filePath, data, { mode = 0o600 } = {}) {
|
|
91
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
92
|
+
const tmp = `${filePath}.tmp-${process.pid}-${crypto.randomBytes(4).toString('hex')}`;
|
|
93
|
+
await fs.writeFile(tmp, JSON.stringify(data, null, 2) + '\n', 'utf8');
|
|
94
|
+
await fs.rename(tmp, filePath);
|
|
95
|
+
try { await fs.chmod(filePath, mode); } catch { /* best effort */ }
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async function _readJsonSafe(filePath) {
|
|
99
|
+
try {
|
|
100
|
+
const raw = await fs.readFile(filePath, 'utf8');
|
|
101
|
+
return JSON.parse(raw);
|
|
102
|
+
} catch {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function _validEntry(obj) {
|
|
108
|
+
if (!obj
|
|
109
|
+
|| typeof obj !== 'object'
|
|
110
|
+
|| typeof obj.session_id !== 'string'
|
|
111
|
+
|| typeof obj.last_heartbeat !== 'string'
|
|
112
|
+
|| typeof obj.started_at !== 'string') {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
// Schema v2 (Epic #583): `mode` is optional. When present it MUST be a string
|
|
116
|
+
// (no number / object / array smuggling). When absent (v1 entry), it is
|
|
117
|
+
// accepted — back-compat with pre-#583 registry files.
|
|
118
|
+
if ('mode' in obj && obj.mode !== null && typeof obj.mode !== 'string') {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function _ageMinutes(isoTimestamp, now = Date.now()) {
|
|
125
|
+
const t = Date.parse(isoTimestamp);
|
|
126
|
+
if (Number.isNaN(t)) return Infinity;
|
|
127
|
+
return (now - t) / 60_000;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
// Public API
|
|
132
|
+
// ---------------------------------------------------------------------------
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Append a single JSONL observability event to sweep.log. Non-throwing:
|
|
136
|
+
* if writing to sweep.log itself fails the error is silently swallowed so
|
|
137
|
+
* callers never cascade a log-write failure into a blocking error.
|
|
138
|
+
*
|
|
139
|
+
* Uses appendFileSync (sync) intentionally — this is called from a catch
|
|
140
|
+
* branch where fire-and-forget sync writes are simpler and safer than
|
|
141
|
+
* spawning a new async chain. Lines stay well under PIPE_BUF (4 KiB) so
|
|
142
|
+
* the append is effectively atomic on POSIX.
|
|
143
|
+
*
|
|
144
|
+
* @param {{ event: string, session_id: string|null, error: string }} opts
|
|
145
|
+
*/
|
|
146
|
+
export function logSweepEvent({ event, session_id, error }) {
|
|
147
|
+
const line = JSON.stringify({
|
|
148
|
+
timestamp: new Date().toISOString(),
|
|
149
|
+
event,
|
|
150
|
+
session_id: session_id ?? null,
|
|
151
|
+
error: typeof error === 'string' ? error : String(error),
|
|
152
|
+
}) + '\n';
|
|
153
|
+
try {
|
|
154
|
+
const logPath = sweepLogPath();
|
|
155
|
+
try { mkdirSync(path.dirname(logPath), { recursive: true }); } catch { /* best effort */ }
|
|
156
|
+
appendFileSync(logPath, line, 'utf8');
|
|
157
|
+
} catch { /* last-resort silent no-op — never let log-write cascade */ }
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Create the heartbeat file for this session.
|
|
162
|
+
*
|
|
163
|
+
* @param {object} opts
|
|
164
|
+
* @param {string} opts.sessionId — unique id (from hook stdin payload preferred; uuid fallback)
|
|
165
|
+
* @param {string} opts.projectRoot — absolute path to the project root
|
|
166
|
+
* @param {string} [opts.branch] — current git branch (optional)
|
|
167
|
+
* @param {string} [opts.platform] — 'claude' | 'codex' | 'cursor' | null
|
|
168
|
+
* @param {string} [opts.hostClass] — from getHostFingerprint().host_class
|
|
169
|
+
* @param {number} [opts.pid] — defaults to process.pid
|
|
170
|
+
* @param {string} [opts.status] — 'active' | 'wave' | 'idle' (default 'active')
|
|
171
|
+
* @param {number} [opts.currentWave] — default 0
|
|
172
|
+
* @param {string} [opts.mode] — session mode (e.g., 'deep', 'feature', 'housekeeping').
|
|
173
|
+
* Schema v2 (Epic #583, W2-I3): when present, propagated into the entry so
|
|
174
|
+
* discovery + exclusivity-matrix classification work for cross-repo peers.
|
|
175
|
+
* Default: `null` (back-compat with v1 entries).
|
|
176
|
+
* @returns {Promise<object>} the written entry
|
|
177
|
+
*/
|
|
178
|
+
export async function registerSelf(opts) {
|
|
179
|
+
if (!opts || typeof opts !== 'object') {
|
|
180
|
+
throw new TypeError('registerSelf: opts is required');
|
|
181
|
+
}
|
|
182
|
+
const { sessionId, projectRoot } = opts;
|
|
183
|
+
_assertSessionId(sessionId);
|
|
184
|
+
if (typeof projectRoot !== 'string' || projectRoot.length === 0) {
|
|
185
|
+
throw new TypeError('registerSelf: projectRoot must be a non-empty string');
|
|
186
|
+
}
|
|
187
|
+
// Schema v2 (Epic #583): validate mode shape if provided.
|
|
188
|
+
if (opts.mode !== undefined && opts.mode !== null && typeof opts.mode !== 'string') {
|
|
189
|
+
throw new TypeError('registerSelf: mode must be a string when provided');
|
|
190
|
+
}
|
|
191
|
+
// Schema v2.1 (Epic #583 W5-F1c — Q5 H1): semanticSessionId field for #587 completion.
|
|
192
|
+
if (opts.semanticSessionId !== undefined && opts.semanticSessionId !== null && typeof opts.semanticSessionId !== 'string') {
|
|
193
|
+
throw new TypeError('registerSelf: semanticSessionId must be a string when provided');
|
|
194
|
+
}
|
|
195
|
+
const now = utcTimestamp();
|
|
196
|
+
const entry = {
|
|
197
|
+
session_id: sessionId,
|
|
198
|
+
semantic_session_id: opts.semanticSessionId ?? null,
|
|
199
|
+
pid: opts.pid ?? process.pid,
|
|
200
|
+
platform: opts.platform ?? null,
|
|
201
|
+
repo_path_hash: repoPathHash(projectRoot),
|
|
202
|
+
repo_name: path.basename(path.resolve(projectRoot)),
|
|
203
|
+
branch: opts.branch ?? null,
|
|
204
|
+
started_at: now,
|
|
205
|
+
last_heartbeat: now,
|
|
206
|
+
status: opts.status ?? 'active',
|
|
207
|
+
current_wave: opts.currentWave ?? 0,
|
|
208
|
+
host_class: opts.hostClass ?? null,
|
|
209
|
+
mode: opts.mode ?? null,
|
|
210
|
+
};
|
|
211
|
+
await _writeJsonAtomic(entryPath(sessionId), entry);
|
|
212
|
+
return entry;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Refresh last_heartbeat on the existing entry (and optionally status /
|
|
217
|
+
* current_wave). No-op if the entry is missing.
|
|
218
|
+
*/
|
|
219
|
+
export async function heartbeat(sessionId, patch = {}) {
|
|
220
|
+
const file = entryPath(sessionId);
|
|
221
|
+
const existing = await _readJsonSafe(file);
|
|
222
|
+
if (!_validEntry(existing)) return null;
|
|
223
|
+
const updated = {
|
|
224
|
+
...existing,
|
|
225
|
+
last_heartbeat: utcTimestamp(),
|
|
226
|
+
...(patch.status !== undefined ? { status: patch.status } : {}),
|
|
227
|
+
...(patch.currentWave !== undefined ? { current_wave: patch.currentWave } : {}),
|
|
228
|
+
};
|
|
229
|
+
await _writeJsonAtomic(file, updated);
|
|
230
|
+
return updated;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** Read every valid heartbeat entry (ignoring malformed files). */
|
|
234
|
+
export async function readRegistry() {
|
|
235
|
+
const dir = activeDir();
|
|
236
|
+
let names;
|
|
237
|
+
try {
|
|
238
|
+
names = await fs.readdir(dir);
|
|
239
|
+
} catch {
|
|
240
|
+
return [];
|
|
241
|
+
}
|
|
242
|
+
const entries = [];
|
|
243
|
+
for (const name of names) {
|
|
244
|
+
if (!name.endsWith('.json')) continue;
|
|
245
|
+
const parsed = await _readJsonSafe(path.join(dir, name));
|
|
246
|
+
if (_validEntry(parsed)) entries.push(parsed);
|
|
247
|
+
}
|
|
248
|
+
return entries;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Determine whether a registry entry is "fresh" — last_heartbeat within
|
|
253
|
+
* `freshnessMin` minutes of `now`. Mirrors detectPeers()'s age filter but
|
|
254
|
+
* exposes the rule as a pure function so callers (session-discovery,
|
|
255
|
+
* exclusivity-matrix consumers) can apply it without re-deriving the formula.
|
|
256
|
+
*
|
|
257
|
+
* Schema v2 (Epic #583, W2-I3): used by discoverActiveSessions() to filter
|
|
258
|
+
* registry-sourced sessions when locks are absent / stale.
|
|
259
|
+
*
|
|
260
|
+
* @param {object} entry Registry entry object
|
|
261
|
+
* @param {object} [opts]
|
|
262
|
+
* @param {number} [opts.freshnessMin=15]
|
|
263
|
+
* @param {number} [opts.now] ms-since-epoch (test seam)
|
|
264
|
+
* @returns {boolean}
|
|
265
|
+
*/
|
|
266
|
+
export function isRegistryEntryFresh(entry, { freshnessMin = 15, now = Date.now() } = {}) {
|
|
267
|
+
if (!_validEntry(entry)) return false;
|
|
268
|
+
return _ageMinutes(entry.last_heartbeat, now) <= freshnessMin;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Live peers on this host — excludes self and entries whose last_heartbeat is
|
|
273
|
+
* older than `freshnessMin` minutes. Stale entries remain on disk (sweep is
|
|
274
|
+
* the only thing that removes them).
|
|
275
|
+
*/
|
|
276
|
+
export async function detectPeers({ sessionId, freshnessMin = 15 } = {}) {
|
|
277
|
+
const now = Date.now();
|
|
278
|
+
const all = await readRegistry();
|
|
279
|
+
return all.filter((e) => {
|
|
280
|
+
if (sessionId && e.session_id === sessionId) return false;
|
|
281
|
+
return _ageMinutes(e.last_heartbeat, now) <= freshnessMin;
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Remove heartbeat entries older than `thresholdMin` minutes. Appends one
|
|
287
|
+
* JSONL line per removal to sweepLogPath() for observability.
|
|
288
|
+
*
|
|
289
|
+
* @returns {Promise<{removed: string[], logged: number}>}
|
|
290
|
+
*/
|
|
291
|
+
export async function sweepZombies({ thresholdMin = 60, now = Date.now() } = {}) {
|
|
292
|
+
const dir = activeDir();
|
|
293
|
+
let names;
|
|
294
|
+
try {
|
|
295
|
+
names = await fs.readdir(dir);
|
|
296
|
+
} catch {
|
|
297
|
+
return { removed: [], logged: 0 };
|
|
298
|
+
}
|
|
299
|
+
const removed = [];
|
|
300
|
+
let logged = 0;
|
|
301
|
+
for (const name of names) {
|
|
302
|
+
if (!name.endsWith('.json')) continue;
|
|
303
|
+
const full = path.join(dir, name);
|
|
304
|
+
const parsed = await _readJsonSafe(full);
|
|
305
|
+
const valid = _validEntry(parsed);
|
|
306
|
+
let age;
|
|
307
|
+
if (valid) {
|
|
308
|
+
age = _ageMinutes(parsed.last_heartbeat, now);
|
|
309
|
+
} else {
|
|
310
|
+
// Malformed files can be fresh semantic-session claim files. Use mtime so
|
|
311
|
+
// a concurrent SessionStart claim is not swept before registerSelf()
|
|
312
|
+
// overwrites it with the real heartbeat entry.
|
|
313
|
+
try {
|
|
314
|
+
const info = await fs.stat(full);
|
|
315
|
+
age = (now - info.mtimeMs) / 60_000;
|
|
316
|
+
} catch {
|
|
317
|
+
age = Infinity;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
if (age > thresholdMin) {
|
|
321
|
+
try {
|
|
322
|
+
await fs.unlink(full);
|
|
323
|
+
removed.push(name);
|
|
324
|
+
try {
|
|
325
|
+
await appendJsonl(sweepLogPath(), {
|
|
326
|
+
timestamp: new Date(now).toISOString(),
|
|
327
|
+
session_id: parsed?.session_id ?? null,
|
|
328
|
+
file: name,
|
|
329
|
+
age_minutes: Number.isFinite(age) ? Math.round(age) : null,
|
|
330
|
+
reason: valid ? 'stale-heartbeat' : 'malformed-entry',
|
|
331
|
+
});
|
|
332
|
+
logged += 1;
|
|
333
|
+
} catch { /* log best-effort */ }
|
|
334
|
+
} catch { /* file might have been removed by another sweep */ }
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
return { removed, logged };
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Remove this session's heartbeat file. Idempotent — missing file is not an
|
|
342
|
+
* error (clean-stop after a crashed-sweep scenario).
|
|
343
|
+
*
|
|
344
|
+
* @returns {Promise<boolean>} true if a file was removed
|
|
345
|
+
*/
|
|
346
|
+
export async function deregisterSelf(sessionId) {
|
|
347
|
+
_assertSessionId(sessionId);
|
|
348
|
+
try {
|
|
349
|
+
await fs.unlink(entryPath(sessionId));
|
|
350
|
+
return true;
|
|
351
|
+
} catch (err) {
|
|
352
|
+
if (err && err.code === 'ENOENT') return false;
|
|
353
|
+
throw err;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* session-schema/aliases.mjs — aliasLegacyEndedAt.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from scripts/lib/session-schema.mjs (W1A3 split).
|
|
5
|
+
* Pure function. Stdlib only. No imports from siblings or parent barrel.
|
|
6
|
+
* isPlainObject is duplicated inline (Option 1 — submodule isolation).
|
|
7
|
+
*
|
|
8
|
+
* Exports: aliasLegacyEndedAt
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// Internal helper (intentional duplication — Option 1 isolation)
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
function isPlainObject(v) {
|
|
16
|
+
return v !== null && typeof v === 'object' && !Array.isArray(v);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// Public API
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Migrate legacy `ended_at` + `duration_ms` shape to canonical
|
|
25
|
+
* `completed_at` (issue #321). Some pre-canonical writers produced records
|
|
26
|
+
* with `ended_at` instead of `completed_at`; this helper aliases the field
|
|
27
|
+
* so the canonical validator does not reject the record on a missing
|
|
28
|
+
* required field.
|
|
29
|
+
*
|
|
30
|
+
* Behaviour:
|
|
31
|
+
* - If `completed_at` is absent and `ended_at` is a string, alias
|
|
32
|
+
* `completed_at <- ended_at`.
|
|
33
|
+
* - If both `completed_at` and `ended_at` are present and DIFFER, prefer
|
|
34
|
+
* `completed_at` (do not overwrite) and tag `_completed_at_conflict: true`
|
|
35
|
+
* for forensics.
|
|
36
|
+
* - If both `completed_at` and `started_at` end up present, drop the now-
|
|
37
|
+
* redundant `duration_ms` (derivable; canonical schema uses
|
|
38
|
+
* `duration_seconds`).
|
|
39
|
+
*
|
|
40
|
+
* Never throws. Returns either the original entry (no migration needed) or
|
|
41
|
+
* a NEW object with the migration applied.
|
|
42
|
+
*
|
|
43
|
+
* @param {object} entry — session record
|
|
44
|
+
* @returns {object} either the original entry or a NEW object
|
|
45
|
+
*/
|
|
46
|
+
export function aliasLegacyEndedAt(entry) {
|
|
47
|
+
if (!isPlainObject(entry)) return entry;
|
|
48
|
+
const hasCompleted = typeof entry.completed_at === 'string';
|
|
49
|
+
const hasEnded = typeof entry.ended_at === 'string';
|
|
50
|
+
if (!hasCompleted && !hasEnded) return entry;
|
|
51
|
+
|
|
52
|
+
let next = entry;
|
|
53
|
+
|
|
54
|
+
if (!hasCompleted && hasEnded) {
|
|
55
|
+
next = { ...entry, completed_at: entry.ended_at };
|
|
56
|
+
} else if (hasCompleted && hasEnded && entry.completed_at !== entry.ended_at) {
|
|
57
|
+
next = { ...entry, _completed_at_conflict: true };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Drop derivable duration_ms once we have both start + completed.
|
|
61
|
+
if (
|
|
62
|
+
'duration_ms' in next &&
|
|
63
|
+
typeof next.completed_at === 'string' &&
|
|
64
|
+
typeof next.started_at === 'string'
|
|
65
|
+
) {
|
|
66
|
+
if (next === entry) next = { ...entry };
|
|
67
|
+
delete next.duration_ms;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return next;
|
|
71
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* session-schema/constants.mjs — pure data constants for session schema.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from scripts/lib/session-schema.mjs (W1A3 split).
|
|
5
|
+
* Leaf module — no imports from siblings or parent.
|
|
6
|
+
*
|
|
7
|
+
* Exports: CURRENT_SESSION_SCHEMA_VERSION, SESSION_KEY_ALIASES,
|
|
8
|
+
* VALID_SESSION_TYPES, REQUIRED_FIELDS, AGENT_SUMMARY_FIELDS
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// Version
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Current session-record schema version. New writes are stamped with this
|
|
17
|
+
* value by validateSession. Records read without `schema_version` are tagged
|
|
18
|
+
* as 0 (pre-versioning legacy) by normalizeSession.
|
|
19
|
+
*
|
|
20
|
+
* Bumped 1 -> 2 via issue #372 (2026-07-02): gate evidence — 135/135
|
|
21
|
+
* production entries in .orchestrator/metrics/sessions.jsonl validate
|
|
22
|
+
* cleanly against the accepted-version set in validator.mjs. The bump is
|
|
23
|
+
* additive-only; no required-field or shape change accompanies it.
|
|
24
|
+
*/
|
|
25
|
+
export const CURRENT_SESSION_SCHEMA_VERSION = 2;
|
|
26
|
+
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// Key aliases (safe renames — no value transformation)
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Safe key aliases — same-shape renames only (no value transformation).
|
|
33
|
+
* Applied by normalizeSession on read so legacy records can be consumed by
|
|
34
|
+
* canonical-key consumers without rewriting the file.
|
|
35
|
+
*
|
|
36
|
+
* Identity is frozen so callers can do strict-equality checks on the object
|
|
37
|
+
* reference across re-imports.
|
|
38
|
+
*/
|
|
39
|
+
export const SESSION_KEY_ALIASES = Object.freeze({
|
|
40
|
+
type: 'session_type',
|
|
41
|
+
mode: 'session_type',
|
|
42
|
+
closed_issues: 'issues_closed',
|
|
43
|
+
new_issues: 'issues_created',
|
|
44
|
+
issues_filed: 'issues_created',
|
|
45
|
+
issues_planned: 'planned_issues',
|
|
46
|
+
files_changed: 'total_files_changed',
|
|
47
|
+
snapshots: 'snapshots_created',
|
|
48
|
+
learnings: 'learnings_added',
|
|
49
|
+
waves_total: 'total_waves',
|
|
50
|
+
waves_completed: 'total_waves',
|
|
51
|
+
head_ref: 'branch',
|
|
52
|
+
isolation_override: 'isolation',
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
// Enums / required field lists
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
/** Closed set of valid session_type values. */
|
|
60
|
+
export const VALID_SESSION_TYPES = Object.freeze(['feature', 'deep', 'housekeeping']);
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Required fields for a schema_version=1 record. Validated by validateSession
|
|
64
|
+
* before any write reaches disk.
|
|
65
|
+
*/
|
|
66
|
+
export const REQUIRED_FIELDS = Object.freeze([
|
|
67
|
+
'session_id',
|
|
68
|
+
'session_type',
|
|
69
|
+
'started_at',
|
|
70
|
+
'completed_at',
|
|
71
|
+
'total_waves',
|
|
72
|
+
'waves',
|
|
73
|
+
'agent_summary',
|
|
74
|
+
'total_agents',
|
|
75
|
+
'total_files_changed',
|
|
76
|
+
]);
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Required numeric counters inside the agent_summary object. All must be
|
|
80
|
+
* non-negative numbers.
|
|
81
|
+
*/
|
|
82
|
+
export const AGENT_SUMMARY_FIELDS = Object.freeze(['complete', 'partial', 'failed', 'spiral']);
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Optional additive fields introduced for the remote-agent substrate (ADR-364 thin-slice).
|
|
86
|
+
* These are NOT in REQUIRED_FIELDS — older entries lacking them validate cleanly.
|
|
87
|
+
* Validator: see `_validateOptionalFields` in validator.mjs.
|
|
88
|
+
*/
|
|
89
|
+
export const OPTIONAL_FIELDS = Object.freeze([
|
|
90
|
+
'agent_identity',
|
|
91
|
+
'worktree_path',
|
|
92
|
+
'parent_run_id',
|
|
93
|
+
'lease_acquired_at',
|
|
94
|
+
'lease_ttl_seconds',
|
|
95
|
+
'expected_cost_tier',
|
|
96
|
+
// Epic #644 — session-level token rollup fields (additive, v1-compatible).
|
|
97
|
+
'total_token_input',
|
|
98
|
+
'total_token_output',
|
|
99
|
+
'subagents_with_tokens',
|
|
100
|
+
// Epic #724 C1 — SessionEnd close-through backfill provenance fields.
|
|
101
|
+
// `status` distinguishes normally-closed ('completed') from hook-backfilled
|
|
102
|
+
// ('abandoned') records. The `_backfill_*` markers record how a stub was
|
|
103
|
+
// synthesized when a session terminated without running /close. All are
|
|
104
|
+
// additive-optional: pre-#724 records lacking them validate cleanly.
|
|
105
|
+
'status',
|
|
106
|
+
'_backfill_source',
|
|
107
|
+
'_backfill_incomplete_fields',
|
|
108
|
+
'_session_type_inferred',
|
|
109
|
+
'_synthetic_session_id',
|
|
110
|
+
// #773 — Handover-Alignment-Gate telemetry (additive, v1-compatible).
|
|
111
|
+
// Non-negative integers; absent = gate did not run / not measured.
|
|
112
|
+
'open_questions_asked',
|
|
113
|
+
'open_questions_answered',
|
|
114
|
+
'open_questions_deferred',
|
|
115
|
+
]);
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* session-schema/normalizer.mjs — normalizeSession (read-path normalization).
|
|
3
|
+
*
|
|
4
|
+
* Extracted from scripts/lib/session-schema.mjs (W1A3 split).
|
|
5
|
+
* Imports: constants.mjs. No imports from siblings (validator, timestamps,
|
|
6
|
+
* aliases) or parent barrel.
|
|
7
|
+
*
|
|
8
|
+
* Exports: normalizeSession
|
|
9
|
+
* Module-private: _warnedMissingSchemaVersion (Set, per-process dedupe)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { SESSION_KEY_ALIASES } from './constants.mjs';
|
|
13
|
+
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Module-level state
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
// Keyed by session_id (or '<unknown>'). Each unique id warns at most once
|
|
19
|
+
// per process, preventing log-spam on large sessions.jsonl files.
|
|
20
|
+
const _warnedMissingSchemaVersion = new Set();
|
|
21
|
+
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// Public API
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Normalize a session entry read from disk. Applies SAFE key aliases, tags
|
|
28
|
+
* legacy entries without `schema_version` as 0 (distinct from
|
|
29
|
+
* CURRENT_SESSION_SCHEMA_VERSION=2 which is stamped on new writes; bumped
|
|
30
|
+
* 1 -> 2 via #372).
|
|
31
|
+
*
|
|
32
|
+
* Never throws. Malformed input (null, non-object, array) is passed through
|
|
33
|
+
* unchanged. Original keys are preserved alongside their canonical alias for
|
|
34
|
+
* debugging.
|
|
35
|
+
*
|
|
36
|
+
* @param {any} entry
|
|
37
|
+
* @returns {any} normalized entry (or original if non-object)
|
|
38
|
+
*/
|
|
39
|
+
export function normalizeSession(entry) {
|
|
40
|
+
if (!entry || typeof entry !== 'object' || Array.isArray(entry)) return entry;
|
|
41
|
+
|
|
42
|
+
const next = { ...entry };
|
|
43
|
+
|
|
44
|
+
// Apply safe key aliases (same-shape renames). Preserve the old key.
|
|
45
|
+
for (const [oldKey, newKey] of Object.entries(SESSION_KEY_ALIASES)) {
|
|
46
|
+
if (oldKey in next && !(newKey in next)) {
|
|
47
|
+
next[newKey] = next[oldKey];
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// schema_version — legacy entries tagged as 0 (NOT CURRENT_SESSION_SCHEMA_VERSION).
|
|
52
|
+
if ('schema_version' in next && next.schema_version !== undefined) {
|
|
53
|
+
// Preserve existing version.
|
|
54
|
+
} else {
|
|
55
|
+
next.schema_version = 0;
|
|
56
|
+
const warnKey = next.session_id ?? '<unknown>';
|
|
57
|
+
if (!_warnedMissingSchemaVersion.has(warnKey)) {
|
|
58
|
+
_warnedMissingSchemaVersion.add(warnKey);
|
|
59
|
+
console.error(
|
|
60
|
+
`[sessions] WARN: record missing schema_version (session_id=${warnKey}); treating as schema_version=0 (pre-versioning legacy)`
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return next;
|
|
66
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* session-schema/timestamps.mjs — clampTimestampsMonotonic.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from scripts/lib/session-schema.mjs (W1A3 split).
|
|
5
|
+
* Pure function. Stdlib only. No imports from siblings or parent barrel.
|
|
6
|
+
* isPlainObject is duplicated inline (Option 1 — submodule isolation).
|
|
7
|
+
*
|
|
8
|
+
* Exports: clampTimestampsMonotonic
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// Internal helper (intentional duplication — Option 1 isolation)
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
function isPlainObject(v) {
|
|
16
|
+
return v !== null && typeof v === 'object' && !Array.isArray(v);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// Public API
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Clamp `completed_at` to be >= `started_at` when an inversion is detected.
|
|
25
|
+
*
|
|
26
|
+
* Defends against clock skew, manual STATE.md frontmatter edits, and any
|
|
27
|
+
* other writer that produces a `completed_at < started_at` violation. Rather
|
|
28
|
+
* than throw and lose the session record entirely, we mathematically clamp
|
|
29
|
+
* `completed_at` to equal `started_at` (duration = 0) and tag the entry with
|
|
30
|
+
* forensics fields so the original timestamp is recoverable.
|
|
31
|
+
*
|
|
32
|
+
* Behaviour:
|
|
33
|
+
* - If either field is absent, return the entry unchanged (let validate
|
|
34
|
+
* surface the missing-field error).
|
|
35
|
+
* - If either field is unparsable, return the entry unchanged (validate
|
|
36
|
+
* will surface the parse error with a clearer message).
|
|
37
|
+
* - If `completed_at >= started_at`, return the entry unchanged.
|
|
38
|
+
* - Otherwise return a NEW object with:
|
|
39
|
+
* completed_at: <started_at>
|
|
40
|
+
* _clamped: true
|
|
41
|
+
* _original_completed_at: <orig completed_at>
|
|
42
|
+
*
|
|
43
|
+
* Never throws. Caller is responsible for emitting any warning log.
|
|
44
|
+
*
|
|
45
|
+
* @param {object} entry — session record (may be the raw input)
|
|
46
|
+
* @returns {object} either the original entry (no clamp needed) or a NEW
|
|
47
|
+
* object with clamp applied + forensics fields
|
|
48
|
+
*/
|
|
49
|
+
export function clampTimestampsMonotonic(entry) {
|
|
50
|
+
if (!isPlainObject(entry)) return entry;
|
|
51
|
+
if (typeof entry.started_at !== 'string' || typeof entry.completed_at !== 'string') {
|
|
52
|
+
return entry;
|
|
53
|
+
}
|
|
54
|
+
const startedMs = Date.parse(entry.started_at);
|
|
55
|
+
const completedMs = Date.parse(entry.completed_at);
|
|
56
|
+
if (Number.isNaN(startedMs) || Number.isNaN(completedMs)) return entry;
|
|
57
|
+
if (completedMs >= startedMs) return entry;
|
|
58
|
+
return {
|
|
59
|
+
...entry,
|
|
60
|
+
completed_at: entry.started_at,
|
|
61
|
+
_clamped: true,
|
|
62
|
+
_original_completed_at: entry.completed_at,
|
|
63
|
+
};
|
|
64
|
+
}
|