session-orchestrator 5.11.0 → 5.13.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 +12 -1
- package/.claude-plugin/plugin.json +4 -1
- package/.codex-plugin/agents/explorer.toml +1 -1
- package/.codex-plugin/agents/session-reviewer.toml +2 -2
- package/.codex-plugin/agents/wave-worker.toml +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/.orchestrator/policy/blocked-commands.json +8 -0
- package/.orchestrator/policy/public-export.json +30 -0
- package/CHANGELOG.md +180 -3
- package/README.md +126 -117
- package/agents/analyst.md +7 -2
- package/agents/architect-reviewer.md +7 -2
- package/agents/code-implementer.md +4 -2
- package/agents/db-specialist.md +9 -2
- package/agents/dialectic-deriver.md +9 -3
- package/agents/docs-writer.md +7 -2
- package/agents/eval-judge.md +7 -1
- package/agents/qa-strategist.md +7 -2
- package/agents/security-reviewer.md +9 -2
- package/agents/session-reviewer.md +18 -2
- package/agents/skill-applied-judge.md +7 -1
- package/agents/test-writer.md +9 -2
- package/agents/ui-developer.md +9 -2
- package/agents/ux-evaluator.md +6 -1
- package/commands/session.md +4 -2
- package/commands/templates-ack.md +89 -18
- package/docs/agent-authoring.md +30 -11
- package/docs/ci-setup.md +38 -2
- package/docs/codex-setup.md +33 -9
- package/docs/components.md +13 -2
- package/docs/cursor-setup.md +3 -3
- package/docs/events-schema.md +5 -4
- package/docs/evidence-candidates.md +63 -0
- package/docs/fleet-checkin-audit.md +7 -4
- package/docs/github-mirror-protection.md +150 -3
- package/docs/install.md +102 -25
- package/docs/instruction-delivery.md +1 -1
- package/docs/migration-v4.md +2 -2
- package/docs/mods.md +118 -0
- package/docs/owner-config-schema.md +1 -0
- package/docs/plugin-architecture-v3.md +1 -1
- package/docs/pm-skills-marketplace.md +4 -3
- package/docs/rule-authoring.md +24 -13
- package/docs/scope-collision-guard.md +2 -1
- package/docs/session-config-reference.md +55 -21
- package/docs/session-config-template.md +32 -24
- package/docs/sessions-ledger-repair.md +137 -0
- package/docs/vault-docs-architecture.md +1 -1
- package/hooks/_lib/hook-import-set.json +406 -73
- package/hooks/_lib/hook-root.mjs +301 -0
- package/hooks/_lib/lock-bootstrap.mjs +434 -4
- package/hooks/_lib/orchestrator-ignore.mjs +3 -0
- package/hooks/_lib/subagent-transcript.mjs +2 -2
- package/hooks/_lib/vcs-create-matcher.mjs +13 -17
- package/hooks/cwd-change-restore.mjs +5 -1
- package/hooks/enforce-commands.mjs +4 -3
- package/hooks/enforce-scope.mjs +11 -1
- package/hooks/hooks-codex.json +48 -2
- package/hooks/hooks.json +31 -31
- package/hooks/on-session-end.mjs +38 -15
- package/hooks/on-session-start.mjs +158 -59
- package/hooks/on-stop.mjs +11 -6
- package/hooks/post-bash-issue-budget-refund.mjs +6 -1
- package/hooks/post-bash-write-verify.mjs +8 -4
- package/hooks/post-edit-validate.mjs +45 -52
- package/hooks/post-subagent-discovery-validator.mjs +2 -2
- package/hooks/post-tool-batch-wave-signal.mjs +8 -4
- package/hooks/post-tool-failure-corrective-context.mjs +5 -1
- package/hooks/pre-bash-destructive-guard.mjs +220 -8
- package/hooks/pre-bash-issue-budget.mjs +6 -1
- package/hooks/pre-bash-sessions-ledger-guard.mjs +691 -123
- package/hooks/pre-bash-staging-fence.mjs +258 -14
- package/hooks/pre-bash-templates-first.mjs +28 -27
- package/hooks/run-node.sh +1 -1
- package/hooks/skill-invocation-telemetry.mjs +15 -8
- package/hooks/subagent-telemetry.mjs +77 -14
- package/hooks/wave-scope-commit-guard.mjs +143 -14
- package/mods/so-hud/.claude-plugin/plugin.json +11 -0
- package/mods/so-hud/README.md +57 -0
- package/mods/so-hud/hooks/beacon-io.mjs +101 -0
- package/mods/so-hud/hooks/heredoc-scan.ts +394 -0
- package/mods/so-hud/hooks/hooks.json +1 -0
- package/mods/so-hud/hooks/logic.ts +182 -0
- package/mods/so-hud/hooks/register.tsx +670 -0
- package/mods/so-hud/hooks/rules.ts +2998 -0
- package/mods/so-hud/tsconfig.json +8 -0
- package/mods/so-hud/types/index.d.ts +36 -0
- package/package.json +4 -3
- package/rules/_index.md +1 -1
- package/rules/always-on/ask-via-tool.md +1 -1
- package/rules/always-on/loop-and-monitor.md +17 -194
- package/rules/always-on/parallel-sessions.md +3 -1
- package/rules/opt-in-stack/backend-data.md +1 -1
- package/rules/opt-in-stack/backend.md +1 -1
- package/rules/opt-in-stack/security-web.md +5 -1
- package/scripts/agent-usage-census.mjs +79 -0
- package/scripts/apply-session-learnings.mjs +34 -6
- package/scripts/archive-closed-prds.mjs +21 -5
- package/scripts/autopilot.mjs +11 -5
- package/scripts/backfill-abandoned-sessions.mjs +13 -3
- package/scripts/backfill-evidence-digest.mjs +2 -1
- package/scripts/backfill-learnings-from-vault.mjs +61 -6
- package/scripts/backfill-sessions.mjs +16 -2
- package/scripts/check-package-manager.mjs +26 -8
- package/scripts/check-sessions-integrity.mjs +4 -1
- package/scripts/ci/assert-vitest-green.mjs +3 -0
- package/scripts/ci/windows-smoke.mjs +201 -0
- package/scripts/close-precheck.mjs +55 -0
- package/scripts/dialectic-deriver.mjs +20 -12
- package/scripts/emit-event.mjs +2 -2
- package/scripts/emit-session.mjs +239 -20
- package/scripts/eval-session.mjs +17 -3
- package/scripts/export-hw-learnings.mjs +29 -2
- package/scripts/fleet-checkin-audit.mjs +8 -4
- package/scripts/fleet-checkin.mjs +73 -3
- package/scripts/gc-stale-worktrees.mjs +6 -1
- package/scripts/github-export.mjs +128 -0
- package/scripts/github-protection-audit.mjs +3 -1
- package/scripts/handover-queue-check.mjs +209 -0
- package/scripts/harness-audit.mjs +6 -4
- package/scripts/lib/agent-frontmatter.mjs +57 -10
- package/scripts/lib/agent-status.mjs +125 -7
- package/scripts/lib/agent-usage-census.mjs +394 -0
- package/scripts/lib/auto-dialectic.mjs +17 -10
- package/scripts/lib/auto-dream.mjs +22 -14
- package/scripts/lib/autopilot/loop.mjs +3 -1
- package/scripts/lib/autopilot/recent-runs.mjs +4 -2
- package/scripts/lib/autopilot/stall-sampler.mjs +2 -1
- package/scripts/lib/autopilot/telemetry.mjs +5 -1
- package/scripts/lib/autopilot/worktree-pipeline.mjs +5 -5
- package/scripts/lib/baseline-archetypes.mjs +10 -2
- package/scripts/lib/blocked-commands-policy.mjs +4 -3
- package/scripts/lib/build-live-signals.mjs +13 -9
- package/scripts/lib/ci-status-banner.mjs +329 -24
- package/scripts/lib/codex/plugin-contract.mjs +8 -0
- package/scripts/lib/cold-start-detector.mjs +5 -2
- package/scripts/lib/command-blocker.mjs +603 -43
- package/scripts/lib/common.mjs +3 -2
- package/scripts/lib/config/cross-repo.mjs +74 -7
- package/scripts/lib/config/host-paths.mjs +6 -2
- package/scripts/lib/config/remote-hosts.mjs +2 -2
- package/scripts/lib/config/section-extractor.mjs +65 -11
- package/scripts/lib/config/template-keys.mjs +153 -0
- package/scripts/lib/config/vault-integration.mjs +1 -0
- package/scripts/lib/config.mjs +3 -3
- package/scripts/lib/coordinator-snapshot.mjs +1 -1
- package/scripts/lib/discovery/triage-state.mjs +2 -1
- package/scripts/lib/dispatcher/rank.mjs +5 -7
- package/scripts/lib/ecosystem-health.mjs +2 -2
- package/scripts/lib/eval/engine.mjs +3 -2
- package/scripts/lib/eval/session-resolve.mjs +6 -2
- package/scripts/lib/eval/sink.mjs +4 -3
- package/scripts/lib/events-retention-banner.mjs +3 -2
- package/scripts/lib/events-schema.mjs +10 -0
- package/scripts/lib/events.mjs +23 -12
- package/scripts/lib/evolve/autonomy-verdict.mjs +4 -9
- package/scripts/lib/evolve/autopilot-effectiveness.mjs +8 -18
- package/scripts/lib/fleet-protocol.mjs +21 -0
- package/scripts/lib/gates/gate-full.mjs +172 -11
- package/scripts/lib/gates/gate-helpers.mjs +236 -23
- package/scripts/lib/git-config-drift.mjs +141 -7
- package/scripts/lib/github-export.mjs +447 -0
- package/scripts/lib/gitlab-portfolio/cli.mjs +16 -5
- package/scripts/lib/handover-queue-marks.mjs +159 -0
- package/scripts/lib/hardware-pattern-detector.mjs +103 -13
- package/scripts/lib/harness-audit/categories/category1.mjs +16 -4
- package/scripts/lib/harness-audit/categories/category4.mjs +11 -8
- package/scripts/lib/harness-audit/categories/category6.mjs +25 -5
- package/scripts/lib/harness-audit/categories/category9.mjs +10 -4
- package/scripts/lib/host-identity.mjs +9 -5
- package/scripts/lib/http-request.mjs +64 -0
- package/scripts/lib/instruction-budget-guard.mjs +30 -22
- package/scripts/lib/io.mjs +6 -3
- package/scripts/lib/issue-budget-reconcile.mjs +3 -2
- package/scripts/lib/issue-budget.mjs +36 -7
- package/scripts/lib/learnings/evidence-candidates.mjs +154 -0
- package/scripts/lib/learnings/expiry-sweep.mjs +99 -7
- package/scripts/lib/learnings/io.mjs +289 -15
- package/scripts/lib/learnings/select.mjs +53 -4
- package/scripts/lib/learnings/store-role.mjs +209 -0
- package/scripts/lib/learnings/surface.mjs +46 -7
- package/scripts/lib/ledger-path.mjs +113 -0
- package/scripts/lib/maintenance-due-banner.mjs +63 -12
- package/scripts/lib/memory-banner.mjs +5 -6
- package/scripts/lib/memory-proposals/collector.mjs +5 -2
- package/scripts/lib/memory-proposals/sink.mjs +7 -4
- package/scripts/lib/memory-proposals/store.mjs +22 -5
- package/scripts/lib/mode-selector-accuracy.mjs +2 -1
- package/scripts/lib/no-follow-open.mjs +57 -0
- package/scripts/lib/orphan-reaper/reaper-audit.mjs +4 -2
- package/scripts/lib/own-lock-root.mjs +95 -0
- package/scripts/lib/owner-interview.mjs +2 -1
- package/scripts/lib/owner-yaml.mjs +70 -7
- package/scripts/lib/platform.mjs +161 -9
- package/scripts/lib/plugin-update-banner.mjs +15 -11
- package/scripts/lib/pre-push-local-partial.mjs +117 -0
- package/scripts/lib/pre-push-remote-gate.mjs +151 -25
- package/scripts/lib/process-group.mjs +270 -34
- package/scripts/lib/project-hygiene.mjs +28 -0
- package/scripts/lib/quality-gate.mjs +33 -9
- package/scripts/lib/reconcile/backlog.mjs +29 -15
- package/scripts/lib/reconcile/engine.mjs +26 -2
- package/scripts/lib/reconcile/idempotency.mjs +65 -3
- package/scripts/lib/reconcile/rule-expiry-sweep.mjs +53 -12
- package/scripts/lib/reconcile/verdict-ledger.mjs +224 -0
- package/scripts/lib/reconcile-nudge-banner.mjs +40 -6
- package/scripts/lib/resource-probe/evaluate.mjs +32 -9
- package/scripts/lib/resource-probe/probe-platform.mjs +3 -1
- package/scripts/lib/rule-loader.mjs +20 -10
- package/scripts/lib/scope-baseline.mjs +123 -8
- package/scripts/lib/scope-echo.mjs +5 -2
- package/scripts/lib/scope-gate.mjs +55 -20
- package/scripts/lib/session-close-backfill.mjs +75 -7
- package/scripts/lib/session-end/close-precheck.mjs +151 -0
- package/scripts/lib/session-end/phase-skip.mjs +8 -7
- package/scripts/lib/session-end/tail-runner.mjs +7 -2
- package/scripts/lib/session-end/vault-orphans.mjs +205 -0
- package/scripts/lib/session-id.mjs +9 -1
- package/scripts/lib/session-identity/own-session.mjs +63 -0
- package/scripts/lib/session-invocation.mjs +14 -3
- package/scripts/lib/session-lock.mjs +114 -0
- package/scripts/lib/session-record-repair.mjs +245 -35
- package/scripts/lib/session-registry.mjs +85 -12
- package/scripts/lib/session-schema/constants.mjs +9 -0
- package/scripts/lib/session-schema/filters.mjs +56 -1
- package/scripts/lib/session-schema/validator.mjs +15 -5
- package/scripts/lib/session-start-probes.mjs +149 -22
- package/scripts/lib/session-token-rollup.mjs +2 -1
- package/scripts/lib/session-work-trees.mjs +166 -0
- package/scripts/lib/sessions-canonical.mjs +61 -3
- package/scripts/lib/sessions-integrity-banner.mjs +17 -39
- package/scripts/lib/sessions-staleness-banner.mjs +8 -5
- package/scripts/lib/sh-quote.mjs +1 -0
- package/scripts/lib/skill-evidence-window.mjs +5 -1
- package/scripts/lib/skill-health/join.mjs +10 -9
- package/scripts/lib/skill-invocations-schema.mjs +13 -0
- package/scripts/lib/skill-judgments-schema.mjs +6 -2
- package/scripts/lib/snapshot-probe.mjs +414 -0
- package/scripts/lib/so-hud/collect.mjs +174 -0
- package/scripts/lib/so-hud/runtime.mjs +87 -0
- package/scripts/lib/spiral-carryover.mjs +8 -1
- package/scripts/lib/state-md/frontmatter-mutators.mjs +266 -8
- package/scripts/lib/state-md/idle-reset.mjs +248 -0
- package/scripts/lib/state-md/mission-status.mjs +5 -1
- package/scripts/lib/state-md/recommendations.mjs +26 -4
- package/scripts/lib/state-md-peer-guard.mjs +2 -0
- package/scripts/lib/state-md.mjs +31 -8
- package/scripts/lib/sunset/walker.mjs +4 -3
- package/scripts/lib/telemetry/consent.mjs +4 -2
- package/scripts/lib/telemetry/queue.mjs +3 -3
- package/scripts/lib/telemetry/sync.mjs +45 -9
- package/scripts/lib/test-runner/artifact-paths.mjs +10 -2
- package/scripts/lib/tmux-layout/layouts.mjs +20 -3
- package/scripts/lib/ux-grill/paths.mjs +5 -1
- package/scripts/lib/validate/check-agents.mjs +37 -6
- package/scripts/lib/validate/check-component-paths.mjs +25 -1
- package/scripts/lib/validate/check-guard-requires-parity.mjs +3 -3
- package/scripts/lib/validate/check-hooks-symmetry.mjs +53 -32
- package/scripts/lib/validate/check-json-files.mjs +3 -1
- package/scripts/lib/validate/check-learning-provenance.mjs +3 -2
- package/scripts/lib/validate/check-learnings-shell-writes.mjs +10 -4
- package/scripts/lib/validate/check-metrics-ledger-root.mjs +234 -0
- package/scripts/lib/validate/check-owner-leakage.mjs +159 -11
- package/scripts/lib/validate/check-test-posix-shell.mjs +246 -0
- package/scripts/lib/validate/check-unwired-features.mjs +4 -2
- package/scripts/lib/validate/check-vcs-repo-flag-positional.mjs +58 -0
- package/scripts/lib/validate/check-vcs-repo-flag.mjs +259 -73
- package/scripts/lib/validate/check-workflow-continue-on-error.mjs +63 -0
- package/scripts/lib/validate/confidential-names.mjs +3 -1
- package/scripts/lib/vault-mirror/migrate-ids.mjs +321 -0
- package/scripts/lib/vault-mirror/namespace.mjs +22 -9
- package/scripts/lib/vault-mirror/process.mjs +197 -65
- package/scripts/lib/vault-mirror/prune.mjs +5 -2
- package/scripts/lib/vault-mirror/render-learnings.mjs +8 -7
- package/scripts/lib/vault-mirror/render-sessions.mjs +36 -53
- package/scripts/lib/vault-mirror/retention.mjs +2 -2
- package/scripts/lib/vault-mirror/utils.mjs +106 -13
- package/scripts/lib/vault-staleness-banner.mjs +4 -7
- package/scripts/lib/vault-status/board-writer.mjs +34 -5
- package/scripts/lib/vault-status/narrative-mirror.mjs +66 -7
- package/scripts/lib/wave-executor/foreign-dispatch.mjs +20 -0
- package/scripts/lib/win-process-tree.mjs +137 -0
- package/scripts/lib/workspace.mjs +1 -1
- package/scripts/lib/worktree/constants.mjs +1 -1
- package/scripts/lib/worktree/lifecycle.mjs +1 -1
- package/scripts/lib/worktree/listing.mjs +1 -1
- package/scripts/lib/zx.mjs +50 -0
- package/scripts/mcp-server.mjs +16 -7
- package/scripts/mcp-server.sh +13 -83
- package/scripts/measure-policy-cache-effectiveness.mjs +7 -5
- package/scripts/memory-propose.mjs +10 -3
- package/scripts/migrate-cold-start-seed.mjs +7 -0
- package/scripts/migrate-legacy-learnings.sh +22 -0
- package/scripts/migrate-sessions-jsonl.mjs +16 -2
- package/scripts/migrate-vault-paths.mjs +29 -13
- package/scripts/parse-config.mjs +71 -14
- package/scripts/print-learnings-index.mjs +25 -2
- package/scripts/release-session-lock.mjs +94 -7
- package/scripts/release.mjs +451 -24
- package/scripts/relocate-vault-corpus.mjs +11 -7
- package/scripts/repair-invalid-sessions.mjs +68 -3
- package/scripts/resolve-session-invocation.mjs +57 -8
- package/scripts/run-migrate-v2-cross-repo.mjs +56 -1
- package/scripts/run-quality-gate.mjs +33 -7
- package/scripts/self-update.mjs +56 -7
- package/scripts/session-commit-files.mjs +133 -0
- package/scripts/site-numbers.mjs +52 -9
- package/scripts/snapshot-probe.mjs +156 -0
- package/scripts/so-hud.mjs +68 -0
- package/scripts/state-md-idle-reset.mjs +82 -0
- package/scripts/sweep-expired-learnings.mjs +54 -6
- package/scripts/sweep-expired-rules.mjs +4 -1
- package/scripts/validate-plugin.mjs +22 -0
- package/scripts/vault-integration-watcher.mjs +54 -6
- package/scripts/vault-mirror.mjs +9 -0
- package/scripts/vault-orphans.mjs +171 -0
- package/skills/_shared/fleet-protocol.md +166 -3
- package/skills/_shared/instruction-file-resolution.md +1 -1
- package/skills/_shared/monitor-patterns.md +44 -25
- package/skills/_shared/platform-tools.md +7 -0
- package/skills/_shared/state-ownership.md +5 -4
- package/skills/architecture/references/domain-model.md +1 -1
- package/skills/autopilot/SKILL.md +3 -2
- package/skills/bootstrap/SKILL.md +3 -1
- package/skills/bootstrap/_shared-template.md +4 -4
- package/skills/bootstrap/fast-template.md +1 -1
- package/skills/bootstrap/private-contract.md +1 -1
- package/skills/bootstrap/references/bootstrap-retroactive-flow.md +17 -9
- package/skills/bootstrap/templates/agents/project-code-review.md +5 -1
- package/skills/bootstrap/templates/agents/project-discovery.md +5 -1
- package/skills/bootstrap/templates/agents/project-quality-gate.md +5 -1
- package/skills/claude-md-drift-check/SKILL.md +3 -3
- package/skills/claude-md-drift-check/checker.mjs +61 -101
- package/skills/close/SKILL.md +20 -7
- package/skills/discovery/probes/docs-staleness.mjs +7 -5
- package/skills/discovery/probes/frontend-slop.mjs +7 -3
- package/skills/discovery/probes/ssot-code-diff.mjs +5 -3
- package/skills/discovery/probes/supply-chain-slopcheck.mjs +10 -8
- package/skills/discovery/probes/vault-narrative-staleness.mjs +30 -10
- package/skills/discovery/probes/vault-staleness.mjs +5 -3
- package/skills/discovery/probes-session.md +3 -0
- package/skills/discovery/probes-vault.md +2 -0
- package/skills/dispatcher/SKILL.md +1 -1
- package/skills/eval/SKILL.md +3 -1
- package/skills/evolve/SKILL.md +2 -0
- package/skills/evolve/references/evolve-analyze-mode.md +3 -3
- package/skills/evolve/references/evolve-dialectic-mode.md +1 -1
- package/skills/gitlab-ops/SKILL.md +7 -6
- package/skills/gitlab-portfolio/SKILL.md +2 -2
- package/skills/go/SKILL.md +13 -1
- package/skills/npm-publish/SKILL.md +5 -2
- package/skills/plan/SKILL.md +23 -10
- package/skills/plan/mode-feature.md +13 -8
- package/skills/plan/mode-new.md +6 -14
- package/skills/plan/prd-feature-template.md +19 -0
- package/skills/plan/prd-full-template.md +12 -5
- package/skills/plan/prd-premortem-panel.md +48 -0
- package/skills/plan/prd-reviewer-prompt.md +11 -3
- package/skills/plan/soul.md +1 -1
- package/skills/portfolio/SKILL.md +5 -6
- package/skills/quality-gates/SKILL.md +2 -4
- package/skills/reconcile/SKILL.md +27 -4
- package/skills/release/SKILL.md +16 -8
- package/skills/session-end/SKILL.md +97 -13
- package/skills/session-end/phase-3-2-docs-verification.md +1 -0
- package/skills/session-end/phase-3-6-tail.md +11 -7
- package/skills/session-end/phase-3-7a-recommendations.md +9 -2
- package/skills/session-end/plan-verification.md +16 -1
- package/skills/session-end/references/phase-3-7e-vault-orphans.md +63 -0
- package/skills/session-end/references/phase-3-documentation-updates.md +25 -13
- package/skills/session-end/references/phase-5-issue-cleanup.md +1 -1
- package/skills/session-end/references/session-summary-template.md +8 -0
- package/skills/session-end/session-metrics-write.md +7 -4
- package/skills/session-plan/SKILL.md +5 -3
- package/skills/session-start/SKILL.md +10 -4
- package/skills/session-start/phase-4-5-resource-health.md +11 -5
- package/skills/session-start/phase-7-5-mode-selector.md +3 -1
- package/skills/session-start/phase-8-5-express-path.md +4 -3
- package/skills/session-start/references/phase-1-2-session-lock.md +7 -5
- package/skills/session-start/references/phase-1-5-session-continuity.md +13 -5
- package/skills/session-start/references/phase-1-7-vault-status-board.md +1 -1
- package/skills/session-start/references/phase-4-ssot-environment-check.md +3 -2
- package/skills/tmux-layout/SKILL.md +2 -2
- package/skills/vault-sync/validator.mjs +3 -2
- package/skills/wave-executor/SKILL.md +5 -4
- package/skills/wave-executor/references/wave-executor-state-init.md +3 -3
- package/skills/wave-executor/references/wave-loop-dispatch.md +11 -5
- package/skills/wave-executor/references/wave-loop-review.md +13 -5
- package/skills/wave-executor/references/wave-loop-scope-manifest.md +1 -1
- package/AGENTS.md +0 -207
- package/assets/og-card.png +0 -0
- package/assets/og-card.svg +0 -47
- package/assets/wave-lifecycle.svg +0 -98
|
@@ -53,6 +53,7 @@ import {
|
|
|
53
53
|
_setResolverForTest,
|
|
54
54
|
} from './lib/vault-relocation-rules.mjs';
|
|
55
55
|
import { buildBackfillIndex, parseSessionId } from './lib/vault-repo-backfill.mjs';
|
|
56
|
+
import { noteIdMatches } from './lib/vault-mirror/utils.mjs';
|
|
56
57
|
import { parseColumnFlags, CliFlagError } from './lib/cli-flags.mjs';
|
|
57
58
|
import { isMainModule } from './lib/is-main-module.mjs';
|
|
58
59
|
import { atomicWriteText, envelopeToError } from './lib/io.mjs';
|
|
@@ -358,7 +359,7 @@ async function buildCrossRepoIndices(reposRoot) {
|
|
|
358
359
|
* Only UNANIMOUS mappings are learned; an identity whose source-repo:/repo:
|
|
359
360
|
* sessions disagree on the namespace is left unmapped (ambiguous → no remap).
|
|
360
361
|
*
|
|
361
|
-
* @param {{ sidIndex: Map<string, Set<string>>, parsedVaultSessions: Array<{id:string, frontmatter:object}> }} args
|
|
362
|
+
* @param {{ sidIndex: Map<string, Set<string>>, parsedVaultSessions: Array<{id:string, sid?:string, frontmatter:object}> }} args
|
|
362
363
|
* @returns {Map<string, string>} identity → canonical namespace
|
|
363
364
|
*/
|
|
364
365
|
function learnCanonicalMap({ sidIndex, parsedVaultSessions }) {
|
|
@@ -366,11 +367,11 @@ function learnCanonicalMap({ sidIndex, parsedVaultSessions }) {
|
|
|
366
367
|
// 'AcmeWidgetV2' → 'acmewidgetv2' === 'acme-widget-v2' → 'acmewidgetv2'.
|
|
367
368
|
const normalize = (s) => String(s).toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
368
369
|
const votes = new Map(); // identity → Map<canonicalNs, count>
|
|
369
|
-
for (const { id, frontmatter } of parsedVaultSessions) {
|
|
370
|
+
for (const { id, sid = id, frontmatter } of parsedVaultSessions) {
|
|
370
371
|
if (!id || !frontmatter || !hasSessionRepoSignal(frontmatter)) continue;
|
|
371
372
|
const { namespace } = namespaceForSession(frontmatter); // source-repo:/repo: → canonical
|
|
372
373
|
if (!isConfident(namespace)) continue; // skip _unsorted/redacted-repo/unknown-repo
|
|
373
|
-
const identities = sidIndex.get(
|
|
374
|
+
const identities = sidIndex.get(sid);
|
|
374
375
|
if (!identities) continue;
|
|
375
376
|
// Only an UNAMBIGUOUS source-repo:/repo: note teaches: if its id appears in >1 repo's jsonl,
|
|
376
377
|
// it would mis-teach every other identity its own namespace (collision pollution).
|
|
@@ -478,11 +479,14 @@ async function collectParsedVaultSessions(sessionsDir) {
|
|
|
478
479
|
continue;
|
|
479
480
|
}
|
|
480
481
|
const frontmatter = parseRelocationFrontmatter(content);
|
|
482
|
+
const stem = path.basename(filePath).replace(/\.md$/, '');
|
|
481
483
|
// Prefer the frontmatter id; fall back to the basename (the session id by convention).
|
|
482
|
-
const id =
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
484
|
+
const id = (typeof frontmatter.id === 'string' && frontmatter.id.trim()) || stem;
|
|
485
|
+
// #1602: a namespaced id (`<source-repo>-<session id>`) is no session id; the
|
|
486
|
+
// cross-repo index joins on the file stem it names. `id` stays the backfill
|
|
487
|
+
// key, because namespaceForSession looks that index up by `frontmatter.id`.
|
|
488
|
+
const sid = noteIdMatches(id, stem, frontmatter['source-repo']) ? stem : id;
|
|
489
|
+
if (id) out.push({ id, sid, frontmatter });
|
|
486
490
|
}
|
|
487
491
|
|
|
488
492
|
return out;
|
|
@@ -47,7 +47,7 @@ import { parseArgs } from 'node:util';
|
|
|
47
47
|
import path from 'node:path';
|
|
48
48
|
|
|
49
49
|
import { repairLedger, CANONICAL_LEDGER_REL } from './lib/session-record-repair.mjs';
|
|
50
|
-
import { getProjectDir } from './lib/platform.mjs';
|
|
50
|
+
import { getProjectDir, resolveEventsLedgerRoot } from './lib/platform.mjs';
|
|
51
51
|
import { isMainModule } from './lib/is-main-module.mjs';
|
|
52
52
|
|
|
53
53
|
const USAGE =
|
|
@@ -60,7 +60,8 @@ const USAGE =
|
|
|
60
60
|
CANONICAL_LEDGER_REL +
|
|
61
61
|
');\n' +
|
|
62
62
|
' must resolve inside <repo-root>/.orchestrator/metrics/\n' +
|
|
63
|
-
' --repo-root project root (default: resolved project dir
|
|
63
|
+
' --repo-root project root (default: the resolved project dir\'s ledger root —\n' +
|
|
64
|
+
' its main checkout when run from a linked worktree)\n' +
|
|
64
65
|
'Exit codes: 0 completed, 1 arg error, 2 system error, 3 post-verification failed\n' +
|
|
65
66
|
' (backup restored), 4 unrepairable remain (N lines kept byte-identical;\n' +
|
|
66
67
|
' every repairable line fixed — dry-run: would remain)\n';
|
|
@@ -77,6 +78,7 @@ const USAGE =
|
|
|
77
78
|
* @returns {{ok: true, file: string}|{ok: false, metricsDir: string, resolved: string}}
|
|
78
79
|
*/
|
|
79
80
|
function resolveLedgerTarget(file, repoRoot) {
|
|
81
|
+
// ledger-root: explicit --repo-root by design; pre-bash-sessions-ledger-guard judges the same root
|
|
80
82
|
const metricsDir = path.resolve(repoRoot, '.orchestrator', 'metrics');
|
|
81
83
|
// `absFile` (normalized, `..` collapsed) is what the repair uses — NOT the
|
|
82
84
|
// symlink-resolved form, so the canonical-path equality in `verifyWritten`
|
|
@@ -108,6 +110,44 @@ function resolveLedgerTarget(file, repoRoot) {
|
|
|
108
110
|
return { ok: true, file: absFile };
|
|
109
111
|
}
|
|
110
112
|
|
|
113
|
+
/**
|
|
114
|
+
* Refuse an explicit `--repo-root` whose ledger path follows a symlink (#1665).
|
|
115
|
+
* The ledger guard admits the absolute `--apply` form only after
|
|
116
|
+
* `isSymlinkFreeAbsolute` (hooks/pre-bash-sessions-ledger-guard.mjs) accepted
|
|
117
|
+
* the root and the ledger, but it checks BEFORE this process runs, and
|
|
118
|
+
* {@link resolveLedgerTarget} follows symlinks via `realpathSync` — a link
|
|
119
|
+
* swapped in between the two would redirect the repair (TOCTOU).
|
|
120
|
+
*
|
|
121
|
+
* Same rule as the hook: NO component of the path may be a symlink — the root,
|
|
122
|
+
* every ancestor (so a root under macOS `/tmp` → `/private/tmp` is refused,
|
|
123
|
+
* exactly as the hook refuses it), `.orchestrator/`, `metrics/` and the ledger
|
|
124
|
+
* file — and the root must equal its own `realpath`. `lstat` judges the link
|
|
125
|
+
* itself, never its target. One deliberate difference: a MISSING component is
|
|
126
|
+
* not a symlink here; the repair reports it as the I/O error it is.
|
|
127
|
+
*
|
|
128
|
+
* @param {string} repoRoot - the explicit `--repo-root` value
|
|
129
|
+
* @param {string} ledgerFile - the requested ledger path under that root
|
|
130
|
+
* @returns {string|null} the reason to refuse, or null
|
|
131
|
+
*/
|
|
132
|
+
function symlinkRefusal(repoRoot, ledgerFile) {
|
|
133
|
+
for (let current = path.resolve(ledgerFile); ; current = path.dirname(current)) {
|
|
134
|
+
try {
|
|
135
|
+
if (fs.lstatSync(current).isSymbolicLink()) return `${current} is a symlink`;
|
|
136
|
+
} catch {
|
|
137
|
+
/* absent — nothing to follow */
|
|
138
|
+
}
|
|
139
|
+
if (path.dirname(current) === current) break;
|
|
140
|
+
}
|
|
141
|
+
const root = path.resolve(repoRoot);
|
|
142
|
+
try {
|
|
143
|
+
const real = fs.realpathSync.native(root);
|
|
144
|
+
if (real !== root) return `${root} resolves to ${real}`;
|
|
145
|
+
} catch {
|
|
146
|
+
/* absent — the repair reports the missing ledger */
|
|
147
|
+
}
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
|
|
111
151
|
/**
|
|
112
152
|
* Human-readable summary — mirrors `scripts/backfill-abandoned-sessions.mjs`
|
|
113
153
|
* `renderHuman()` so the two migration CLIs read the same way.
|
|
@@ -123,6 +163,9 @@ export function renderHuman(s) {
|
|
|
123
163
|
lines.push(` invalid before: ${s.invalid_before}`);
|
|
124
164
|
lines.push(` ${s.mode === 'apply' ? 'repaired: ' : 'would repair: '} ${s.repaired}`);
|
|
125
165
|
lines.push(` invalid after: ${s.invalid_after}${s.mode === 'apply' ? '' : ' (projected)'}`);
|
|
166
|
+
if (s.healed_valid > 0) {
|
|
167
|
+
lines.push(` healed (valid): ${s.healed_valid}`);
|
|
168
|
+
}
|
|
126
169
|
if (s.unparseable > 0) {
|
|
127
170
|
lines.push(` unparseable: ${s.unparseable} (passed through untouched)`);
|
|
128
171
|
}
|
|
@@ -148,6 +191,10 @@ export function renderHuman(s) {
|
|
|
148
191
|
}
|
|
149
192
|
if (s.ok === false) {
|
|
150
193
|
lines.push(' POST-VERIFICATION FAILED — backup restored, ledger unchanged.');
|
|
194
|
+
// #1670: lines appended after the rename are kept, not rolled back with the repair.
|
|
195
|
+
if (s.restore_appended_bytes > 0) {
|
|
196
|
+
lines.push(` kept: ${s.restore_appended_bytes} byte(s) appended concurrently after the rename`);
|
|
197
|
+
}
|
|
151
198
|
} else if (s.errors.length > 0) {
|
|
152
199
|
lines.push(
|
|
153
200
|
s.mode === 'apply'
|
|
@@ -201,9 +248,27 @@ async function main() {
|
|
|
201
248
|
|
|
202
249
|
// --apply is an explicit opt-in; absent it (or with --dry-run) we never write.
|
|
203
250
|
const apply = values.apply === true && values['dry-run'] !== true;
|
|
204
|
-
|
|
251
|
+
// #1592: only the DEFAULT follows the ledger to the main checkout, where
|
|
252
|
+
// every writer appends. An explicit --repo-root stays the literal target: the
|
|
253
|
+
// ledger guard sanctions the absolute --apply form only for a --repo-root
|
|
254
|
+
// equal to the measured Git top-level and outside the resolved vault — mapping
|
|
255
|
+
// it would let a linked worktree of the vault write the vault's main ledger.
|
|
256
|
+
const repoRoot = values['repo-root'] || resolveEventsLedgerRoot(getProjectDir());
|
|
205
257
|
const requestedFile = values.file || path.join(repoRoot, CANONICAL_LEDGER_REL);
|
|
206
258
|
|
|
259
|
+
// #1665: an explicit root is the whole target identity — never follow a
|
|
260
|
+
// symlink anywhere on the ledger path, checked BEFORE any I/O (dry-run too).
|
|
261
|
+
if (values['repo-root']) {
|
|
262
|
+
const refusal = symlinkRefusal(repoRoot, requestedFile);
|
|
263
|
+
if (refusal) {
|
|
264
|
+
process.stderr.write(
|
|
265
|
+
`repair-invalid-sessions: refusing --repo-root ${repoRoot}: ${refusal} ` +
|
|
266
|
+
`(the ledger path under an explicit --repo-root must not follow symlinks — #1665)\n`
|
|
267
|
+
);
|
|
268
|
+
process.exit(1);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
207
272
|
// MED-3: bound the target to <repoRoot>/.orchestrator/metrics/ BEFORE any I/O.
|
|
208
273
|
const target = resolveLedgerTarget(requestedFile, repoRoot);
|
|
209
274
|
if (!target.ok) {
|
|
@@ -1,19 +1,60 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/** Stdin-only boundary for commands/session.md; never executes task context. */
|
|
3
|
-
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { accessSync, constants, readFileSync, statSync } from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
4
5
|
import { parseArgs } from 'node:util';
|
|
5
6
|
import { resolveSessionInvocation } from './lib/session-invocation.mjs';
|
|
7
|
+
import { readCanonicalSessions, isMeasuredSession } from './lib/sessions-canonical.mjs';
|
|
8
|
+
import { resolveEventsLedgerRoot } from './lib/platform.mjs';
|
|
6
9
|
|
|
7
|
-
|
|
10
|
+
/**
|
|
11
|
+
* One ledger: `false` when absent, `undefined` when present but not a readable
|
|
12
|
+
* file, else whether it holds a measured session.
|
|
13
|
+
*
|
|
14
|
+
* @param {string} root
|
|
15
|
+
* @returns {boolean|undefined}
|
|
16
|
+
*/
|
|
17
|
+
function ledgerHasHistory(root) {
|
|
18
|
+
// ledger-root: root is resolveEventsLedgerRoot-mapped by the only caller, measureHistory
|
|
19
|
+
const file = join(root, '.orchestrator', 'metrics', 'sessions.jsonl');
|
|
20
|
+
try {
|
|
21
|
+
if (!statSync(file).isFile()) return undefined;
|
|
22
|
+
accessSync(file, constants.R_OK);
|
|
23
|
+
} catch (error) {
|
|
24
|
+
return error?.code === 'ENOENT' ? false : undefined;
|
|
25
|
+
}
|
|
26
|
+
return readCanonicalSessions({ filePath: file }).some(isMeasuredSession);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Whether the repo holds a measured session of its own: `true`/`false`, or
|
|
31
|
+
* `undefined` when that cannot be measured. sessions.jsonl is gitignored, so a
|
|
32
|
+
* linked worktree reads its main checkout's ledger (`resolveEventsLedgerRoot`),
|
|
33
|
+
* never a worktree-local shadow. A ledger that exists but
|
|
34
|
+
* cannot be read is "not measured", never "no history".
|
|
35
|
+
*
|
|
36
|
+
* @param {string} repoRoot
|
|
37
|
+
* @returns {boolean|undefined}
|
|
38
|
+
*/
|
|
39
|
+
function measureHistory(repoRoot) {
|
|
40
|
+
return ledgerHasHistory(resolveEventsLedgerRoot(repoRoot));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const HELP = `Usage: node scripts/resolve-session-invocation.mjs [--json] [--repo-root <dir>] < arguments.txt
|
|
8
44
|
|
|
9
45
|
Read the complete /session argument text from UTF-8 stdin.
|
|
10
46
|
Resolve the leading housekeeping, feature, deep or ultradeep token; preserve
|
|
11
47
|
the rest as task context. Empty input defaults to deep. An invalid mode emits
|
|
12
48
|
a warning and resolves to deep, as the session command has always specified.
|
|
49
|
+
A bare /session in a repo whose .orchestrator/metrics/sessions.jsonl holds no
|
|
50
|
+
measured session adds defaultReason "no-session-history": ask for task and mode
|
|
51
|
+
before starting. A linked git worktree reads its main checkout's ledger; an
|
|
52
|
+
unreadable ledger counts as not measured and adds nothing.
|
|
13
53
|
|
|
14
|
-
--json
|
|
15
|
-
--
|
|
16
|
-
--
|
|
54
|
+
--json Emit one JSON object instead of a human-readable summary
|
|
55
|
+
--repo-root <dir> Project root whose session history is read (default: cwd)
|
|
56
|
+
--help Show this help
|
|
57
|
+
--version Show the plugin version
|
|
17
58
|
|
|
18
59
|
Example: node scripts/resolve-session-invocation.mjs --json < arguments.txt
|
|
19
60
|
Write arguments.txt with a file tool; do not interpolate user text into a shell.
|
|
@@ -23,7 +64,12 @@ Exit codes: 0 resolved (including fallback), 1 bad CLI arguments, 2 stdin I/O er
|
|
|
23
64
|
let options;
|
|
24
65
|
try {
|
|
25
66
|
options = parseArgs({
|
|
26
|
-
options: {
|
|
67
|
+
options: {
|
|
68
|
+
json: { type: 'boolean' },
|
|
69
|
+
'repo-root': { type: 'string' },
|
|
70
|
+
help: { type: 'boolean' },
|
|
71
|
+
version: { type: 'boolean' },
|
|
72
|
+
},
|
|
27
73
|
allowPositionals: false,
|
|
28
74
|
}).values;
|
|
29
75
|
} catch (error) {
|
|
@@ -45,7 +91,10 @@ if (options.help) {
|
|
|
45
91
|
process.stderr.write('session-invocation: could not read UTF-8 stdin\n');
|
|
46
92
|
process.exit(2);
|
|
47
93
|
}
|
|
48
|
-
|
|
94
|
+
// History is read only for a bare /session: an explicit mode never needs it.
|
|
95
|
+
const hasHistory =
|
|
96
|
+
input.trim() === '' ? measureHistory(options['repo-root'] || process.cwd()) : undefined;
|
|
97
|
+
const resolved = resolveSessionInvocation(input, { hasHistory });
|
|
49
98
|
if (resolved.invalidMode !== undefined) {
|
|
50
99
|
process.stderr.write(
|
|
51
100
|
`Invalid session type '${resolved.invalidMode}'. Valid types: housekeeping, feature, deep (alias: ultradeep). Falling back to deep.\n`,
|
|
@@ -54,6 +103,6 @@ if (options.help) {
|
|
|
54
103
|
process.stdout.write(
|
|
55
104
|
options.json
|
|
56
105
|
? `${JSON.stringify(resolved)}\n`
|
|
57
|
-
: `Session type: ${resolved.sessionType}${resolved.profile ? ` (profile: ${resolved.profile})` : ''}\nTask context: ${resolved.context}\n`,
|
|
106
|
+
: `Session type: ${resolved.sessionType}${resolved.profile ? ` (profile: ${resolved.profile})` : ''}${resolved.defaultReason ? ` (default: ${resolved.defaultReason})` : ''}\nTask context: ${resolved.context}\n`,
|
|
58
107
|
);
|
|
59
108
|
}
|
|
@@ -30,6 +30,13 @@
|
|
|
30
30
|
* 2 I/O error — incl. a store lock not acquired under --apply: that repo
|
|
31
31
|
* is left unwritten and reported `status: 'error'`, the run continues
|
|
32
32
|
* with the next repo, and exits 2 at the end
|
|
33
|
+
* 3 --apply on a host whose learnings-store role is `inbox` (#1516): no
|
|
34
|
+
* store is rewritten; every repo is still migrated in memory and reported
|
|
35
|
+
* `status: 'refused-store-role'`. An I/O error (2) takes precedence.
|
|
36
|
+
* 4 --apply on a host whose learnings-store role is INVALID (#1516: set but
|
|
37
|
+
* not writer|inbox, or owner.yaml unreadable/unparseable): no store is
|
|
38
|
+
* rewritten; every repo is still migrated in memory and reported
|
|
39
|
+
* `status: 'refused-store-role-invalid'`. An I/O error (2) takes precedence.
|
|
33
40
|
*/
|
|
34
41
|
|
|
35
42
|
import { existsSync, readFileSync, writeFileSync, copyFileSync } from 'node:fs';
|
|
@@ -44,6 +51,7 @@ import { LearningsLockError, withLearningsLock } from './lib/learnings/io.mjs';
|
|
|
44
51
|
import { getCrossRepoProjects, getConfinementRoot } from './lib/config/cross-repo.mjs';
|
|
45
52
|
import { validatePathInsideProject } from './lib/path-utils.mjs';
|
|
46
53
|
import { atomicWriteWithBackup, envelopeToError } from './lib/io.mjs';
|
|
54
|
+
import { resolveStoreRole } from './lib/learnings/store-role.mjs';
|
|
47
55
|
|
|
48
56
|
const LEARNINGS_REL = '.orchestrator/metrics/learnings.jsonl';
|
|
49
57
|
|
|
@@ -65,6 +73,11 @@ Options:
|
|
|
65
73
|
--out <path> Write output to file instead of stdout.
|
|
66
74
|
|
|
67
75
|
Exit codes: 0 success 1 input error 2 I/O error
|
|
76
|
+
3 --apply refused: this host's learnings-store role is inbox (#1516),
|
|
77
|
+
nothing written (repos reported status refused-store-role)
|
|
78
|
+
4 --apply refused: this host's learnings-store role is invalid (#1516:
|
|
79
|
+
SO_LEARNINGS_STORE_ROLE or owner.yaml learnings.store-role set but
|
|
80
|
+
unusable), nothing written (status refused-store-role-invalid)
|
|
68
81
|
`);
|
|
69
82
|
process.exit(0);
|
|
70
83
|
}
|
|
@@ -141,7 +154,10 @@ if (reposArg) {
|
|
|
141
154
|
/**
|
|
142
155
|
* @typedef {Object} RepoResult
|
|
143
156
|
* @property {string} repo - absolute repo path
|
|
144
|
-
* @property {string} status - 'skipped' | 'dry-run' | 'applied' | 'error'
|
|
157
|
+
* @property {string} status - 'skipped' | 'dry-run' | 'applied' | 'error' |
|
|
158
|
+
* 'refused-store-role' (--apply on an inbox-role host, #1516: migrated in
|
|
159
|
+
* memory, nothing written) | 'refused-store-role-invalid' (--apply while the
|
|
160
|
+
* host's store role is invalid, #1516: likewise nothing written)
|
|
145
161
|
* @property {number} total - total records parsed
|
|
146
162
|
* @property {number} invalidPre - invalid records before migration
|
|
147
163
|
* @property {number} invalidPost - invalid records after migration
|
|
@@ -200,6 +216,14 @@ function emptyResult(repoPath, status, error) {
|
|
|
200
216
|
/** Repos whose store lock was not acquired under --apply (nothing written there). */
|
|
201
217
|
const lockFailedRepos = [];
|
|
202
218
|
|
|
219
|
+
// #1516: an inbox-role host never rewrites a canonical learnings.jsonl. The role
|
|
220
|
+
// is host-wide (env / owner.yaml), so it is resolved once for every repo.
|
|
221
|
+
// Fail-closed: an INVALID role (set but unusable) refuses as well, with its
|
|
222
|
+
// own status and exit code — only an unset role means writer.
|
|
223
|
+
const storeRole = applyFlag ? resolveStoreRole() : null;
|
|
224
|
+
const refuseStoreWrites = storeRole?.role === 'inbox';
|
|
225
|
+
const refuseInvalidRole = storeRole?.role === 'invalid';
|
|
226
|
+
|
|
203
227
|
/**
|
|
204
228
|
* Process a single repo. Returns a RepoResult.
|
|
205
229
|
*
|
|
@@ -216,6 +240,18 @@ async function processRepo(repoPath, apply) {
|
|
|
216
240
|
const learningsPath = join(repoPath, LEARNINGS_REL);
|
|
217
241
|
|
|
218
242
|
if (!existsSync(learningsPath)) return emptyResult(repoPath, 'skipped', null);
|
|
243
|
+
if (apply && refuseStoreWrites) {
|
|
244
|
+
const res = migrateStore(repoPath, learningsPath, false);
|
|
245
|
+
return res.status === 'error'
|
|
246
|
+
? res
|
|
247
|
+
: { ...res, status: 'refused-store-role', error: 'store-role inbox: write skipped' };
|
|
248
|
+
}
|
|
249
|
+
if (apply && refuseInvalidRole) {
|
|
250
|
+
const res = migrateStore(repoPath, learningsPath, false);
|
|
251
|
+
return res.status === 'error'
|
|
252
|
+
? res
|
|
253
|
+
: { ...res, status: 'refused-store-role-invalid', error: 'store-role invalid: write skipped' };
|
|
254
|
+
}
|
|
219
255
|
if (!apply) return migrateStore(repoPath, learningsPath, false);
|
|
220
256
|
|
|
221
257
|
try {
|
|
@@ -524,4 +560,23 @@ if (ioFailed.length > 0) {
|
|
|
524
560
|
exitCode = 2;
|
|
525
561
|
}
|
|
526
562
|
|
|
563
|
+
const refusedRepos = results.filter((r) => r.status === 'refused-store-role');
|
|
564
|
+
if (refusedRepos.length > 0) {
|
|
565
|
+
process.stderr.write(
|
|
566
|
+
`run-migrate-v2: learnings store role is inbox on this host (#1516) — --apply wrote nothing ` +
|
|
567
|
+
`in ${refusedRepos.length} repo(s); run it on the writer host\n`
|
|
568
|
+
);
|
|
569
|
+
if (exitCode === 0) exitCode = 3;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
const invalidRoleRepos = results.filter((r) => r.status === 'refused-store-role-invalid');
|
|
573
|
+
if (invalidRoleRepos.length > 0) {
|
|
574
|
+
process.stderr.write(
|
|
575
|
+
`run-migrate-v2: learnings store role is invalid on this host (#1516: ${storeRole?.reason}) — ` +
|
|
576
|
+
`--apply wrote nothing in ${invalidRoleRepos.length} repo(s); fix owner.yaml learnings.store-role ` +
|
|
577
|
+
`or SO_LEARNINGS_STORE_ROLE\n`
|
|
578
|
+
);
|
|
579
|
+
if (exitCode === 0) exitCode = 4;
|
|
580
|
+
}
|
|
581
|
+
|
|
527
582
|
process.exit(exitCode);
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
* 0 would be a false green (Epic #1425 A3).
|
|
31
31
|
*
|
|
32
32
|
* The gate sub-scripts in scripts/lib/gates/ are NOT reimplemented here; they are
|
|
33
|
-
* invoked via spawnInGroup()
|
|
33
|
+
* invoked via spawnInGroup() using argv (a detached group on POSIX) with the
|
|
34
34
|
* required env vars.
|
|
35
35
|
*
|
|
36
36
|
* References:
|
|
@@ -59,7 +59,6 @@ import { resolveWaveNumber } from './lib/quality-gate-wave.mjs';
|
|
|
59
59
|
import { readProcessLocalSessionIds } from './lib/session-identity/own-session.mjs';
|
|
60
60
|
import { gateKillFields } from './lib/quality-gate.mjs';
|
|
61
61
|
import { isMainModule } from './lib/is-main-module.mjs';
|
|
62
|
-
import { shellQuote } from './lib/sh-quote.mjs';
|
|
63
62
|
|
|
64
63
|
// ---------------------------------------------------------------------------
|
|
65
64
|
// Constants
|
|
@@ -226,6 +225,28 @@ function failedFilesFromGateStdout(stdout) {
|
|
|
226
225
|
return named.length > 0 ? named : null;
|
|
227
226
|
}
|
|
228
227
|
|
|
228
|
+
/**
|
|
229
|
+
* Lift `test.rerun` (`{ files, status }`) out of a gate sub-script's JSON
|
|
230
|
+
* stdout envelope (#1673). Only `gate-full.mjs` publishes it, and only when it
|
|
231
|
+
* re-ran timeout-only failures. `null` for every other case. Never throws.
|
|
232
|
+
*
|
|
233
|
+
* @param {string} stdout — the gate sub-script's captured stdout.
|
|
234
|
+
* @returns {{ files: string[], status: 'pass'|'fail' }|null}
|
|
235
|
+
*/
|
|
236
|
+
function rerunFromGateStdout(stdout) {
|
|
237
|
+
if (typeof stdout !== 'string' || !stdout.trim()) return null;
|
|
238
|
+
let parsed;
|
|
239
|
+
try {
|
|
240
|
+
parsed = JSON.parse(stdout);
|
|
241
|
+
} catch {
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
const rerun = parsed?.test?.rerun;
|
|
245
|
+
if (!rerun || !Array.isArray(rerun.files) || (rerun.status !== 'pass' && rerun.status !== 'fail')) return null;
|
|
246
|
+
const files = rerun.files.filter((f) => typeof f === 'string' && f.trim());
|
|
247
|
+
return files.length > 0 ? { files, status: rerun.status } : null;
|
|
248
|
+
}
|
|
249
|
+
|
|
229
250
|
/**
|
|
230
251
|
* The reporting variants — they exit 0 even after a failing check, so their
|
|
231
252
|
* event NAME never carries the check verdict (#1459). `full-gate` exits non-zero
|
|
@@ -379,7 +400,9 @@ async function main() {
|
|
|
379
400
|
if (config) {
|
|
380
401
|
if (existsSync(config)) {
|
|
381
402
|
try {
|
|
382
|
-
|
|
403
|
+
// Windows PowerShell 5.1 `Set-Content -Encoding utf8` writes a BOM, which
|
|
404
|
+
// JSON.parse rejects; the gate then ran the npm defaults instead (#1612).
|
|
405
|
+
configJson = JSON.parse(readFileSync(config, 'utf8').replace(/^\uFEFF/, ''));
|
|
383
406
|
} catch (err) {
|
|
384
407
|
warn(`Could not parse config file '${config}': ${err.message}; using defaults`);
|
|
385
408
|
}
|
|
@@ -496,9 +519,8 @@ async function main() {
|
|
|
496
519
|
// Handing it a child with `stderr: 'inherit'` leaves `child.stderr` null, the
|
|
497
520
|
// module's own `child.stderr?.on(…)` a no-op, and the gate's warnings live on
|
|
498
521
|
// the operator's terminal exactly as before.
|
|
499
|
-
//
|
|
500
|
-
|
|
501
|
-
const gateCommand = `node ${shellQuote(gatePath)}`;
|
|
522
|
+
// argv preserves spaces and apostrophes on POSIX and Windows.
|
|
523
|
+
const gateCommand = [process.execPath, gatePath];
|
|
502
524
|
// Derived from the SAME resolved per-command ceiling published as
|
|
503
525
|
// `GATE_TIMEOUT_MS` above — not from `resolveGateTimeoutMs()` a second time.
|
|
504
526
|
// The outer cap must stay strictly ABOVE the inner one so the inner kill fires
|
|
@@ -531,7 +553,7 @@ async function main() {
|
|
|
531
553
|
// terminal's foreground group: the first read from an inherited TTY earns it
|
|
532
554
|
// SIGTTIN, which stops the whole group until the outer ceiling kills it. An
|
|
533
555
|
// ignored stdin turns that hang into an immediate EOF.
|
|
534
|
-
spawnFn: (command, options) => spawn(command, { ...options, stdio: ['ignore', 'pipe', 'inherit'] }),
|
|
556
|
+
spawnFn: (command, args, options) => spawn(command, args, { ...options, stdio: ['ignore', 'pipe', 'inherit'] }),
|
|
535
557
|
});
|
|
536
558
|
|
|
537
559
|
// `pid: -1` is `spawnInGroup`'s spawn-failure channel (it never rejects).
|
|
@@ -595,6 +617,9 @@ async function main() {
|
|
|
595
617
|
// The names behind `counts.failed`. Absent, never `[]` — see
|
|
596
618
|
// `failedFilesFromGateStdout`.
|
|
597
619
|
const failedFiles = failedFilesFromGateStdout(gateStdout);
|
|
620
|
+
// The serial re-run of timeout-only failures (#1673): which files it named
|
|
621
|
+
// and how it ended, so the rate of load-rescued gates stays measurable.
|
|
622
|
+
const rerun = rerunFromGateStdout(gateStdout);
|
|
598
623
|
// Wave-scope sidecar is read from the SAME project dir the event lands in
|
|
599
624
|
// (emitEvent's own destination precedence), so a tmp-scoped run cannot pick
|
|
600
625
|
// up the host repo's live wave. Mirrors the hook's projectDir resolution.
|
|
@@ -625,6 +650,7 @@ async function main() {
|
|
|
625
650
|
...(checkFailed !== null ? { check_failed: checkFailed } : {}),
|
|
626
651
|
...(counts ? { counts } : {}),
|
|
627
652
|
...(failedFiles ? { failed_files: failedFiles } : {}),
|
|
653
|
+
...(rerun ? { rerun } : {}),
|
|
628
654
|
...(waveNumber !== null ? { wave_number: waveNumber } : {}),
|
|
629
655
|
...sessionAttribution(ledgerRoot ?? repoRoot),
|
|
630
656
|
},
|
package/scripts/self-update.mjs
CHANGED
|
@@ -25,9 +25,12 @@
|
|
|
25
25
|
* 2026-10-03). The step refuses, naming the remove + add
|
|
26
26
|
* commands, while ANY directory marketplace reads this clone;
|
|
27
27
|
* GitHub/npm sources update as before, with a one-line hint.
|
|
28
|
-
* 3. codex — only if the plugin is already installed in Codex
|
|
29
|
-
* scripts/codex-install.mjs, which
|
|
30
|
-
* packed STAGE_DIR (#1518), never from
|
|
28
|
+
* 3. codex — only if the plugin is already installed in Codex (under any
|
|
29
|
+
* marketplace): re-run scripts/codex-install.mjs, which
|
|
30
|
+
* installs from the same packed STAGE_DIR (#1518), never from
|
|
31
|
+
* this clone. Any personal-marketplace `@local` install fails
|
|
32
|
+
* read-only with the cleanup steps, wherever its source points
|
|
33
|
+
* (#1608): codex-install would remove it.
|
|
31
34
|
*
|
|
32
35
|
* Never deletes an old plugin cache directory: the running session still
|
|
33
36
|
* loads it until the harness restarts.
|
|
@@ -316,15 +319,61 @@ export function updateClaude({ run, log }, opts, ctx = {}) {
|
|
|
316
319
|
};
|
|
317
320
|
}
|
|
318
321
|
|
|
319
|
-
|
|
320
|
-
|
|
322
|
+
/**
|
|
323
|
+
* Installed session-orchestrator rows of the human `codex plugin list` table
|
|
324
|
+
* (`PLUGIN STATUS VERSION SOURCE`, columns padded with 2+ spaces). The id
|
|
325
|
+
* carries whatever marketplace installed it — `session-orchestrator@local`
|
|
326
|
+
* from the personal marketplace (#1608) — so the name must end at `@` or the
|
|
327
|
+
* column gap: `session-orchestrator-foo@x` is a different plugin.
|
|
328
|
+
* @param {string | undefined} listText
|
|
329
|
+
* @returns {Array<{id: string, marketplace: string | undefined, source: string | undefined}>}
|
|
330
|
+
*/
|
|
331
|
+
function findCodexInstalls(listText) {
|
|
332
|
+
const row = new RegExp(`^${PLUGIN_NAME}(?:@(\\S+))?\\s+installed\\b`);
|
|
333
|
+
return (listText ?? '').split(/\r?\n/).flatMap((raw) => {
|
|
334
|
+
const line = raw.trim();
|
|
335
|
+
const m = row.exec(line);
|
|
336
|
+
if (!m) return [];
|
|
337
|
+
const cols = line.split(/\s{2,}/);
|
|
338
|
+
return [{ id: cols[0], marketplace: m[1], source: cols.length > 2 ? cols.at(-1) : undefined }];
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* The Codex step. `ctx` exists for tests (tmp clone, no PATH lookup);
|
|
344
|
+
* production passes nothing.
|
|
345
|
+
*/
|
|
346
|
+
export function updateCodex({ run, log }, opts, ctx = {}) {
|
|
347
|
+
const { soRoot = SO_ROOT, hasCodex = () => commandOnPath('codex') } = ctx;
|
|
348
|
+
if (!hasCodex()) return { step: 'codex', status: 'skipped', detail: 'codex not on PATH' };
|
|
321
349
|
const list = run('codex', ['plugin', 'list'], { mutates: false });
|
|
322
350
|
if (!list.ok) return { step: 'codex', status: 'failed', detail: list.detail };
|
|
323
|
-
|
|
351
|
+
const installs = findCodexInstalls(list.stdout);
|
|
352
|
+
if (installs.length === 0) {
|
|
324
353
|
return { step: 'codex', status: 'skipped', detail: 'plugin not installed in Codex (install: node scripts/codex-install.mjs)' };
|
|
325
354
|
}
|
|
355
|
+
// `@local` comes from the personal marketplace
|
|
356
|
+
// (~/.agents/plugins/marketplace.json), whose directory source Codex caches
|
|
357
|
+
// whole, a checkout's .env.local included (#1518). codex-install would remove
|
|
358
|
+
// `@local` — a re-registration the operator owns — so this step only names
|
|
359
|
+
// the cleanup (same steps as codex-install's personal-marketplace hint),
|
|
360
|
+
// wherever the source points: run from a worktree it is the main checkout,
|
|
361
|
+
// not soRoot, and a source match would let codex-install remove it (#1608).
|
|
362
|
+
const local = installs.find((i) => i.marketplace === 'local');
|
|
363
|
+
if (local) {
|
|
364
|
+
const link = local.source ? `the link '${local.source}'` : 'its source link';
|
|
365
|
+
return {
|
|
366
|
+
step: 'codex',
|
|
367
|
+
status: 'failed',
|
|
368
|
+
detail: `${local.id} is installed from the personal marketplace${local.source ? ` via '${local.source}'` : ''} `
|
|
369
|
+
+ `— its Codex cache copies the whole directory, a checkout's .env.local included (#1518). Clean up once: `
|
|
370
|
+
+ `(1) node scripts/codex-install.mjs (installs ${PLUGIN_ID} from the packed copy, then removes ${local.id}); `
|
|
371
|
+
+ `(2) remove that entry from ~/.agents/plugins/marketplace.json; (3) delete ${link} if it is a symlink; `
|
|
372
|
+
+ `(4) after restarting Codex, delete the old cache folders under ~/.codex/plugins/cache/local/.`,
|
|
373
|
+
};
|
|
374
|
+
}
|
|
326
375
|
log('codex: re-running codex-install');
|
|
327
|
-
const r = run(process.execPath, [path.join(
|
|
376
|
+
const r = run(process.execPath, [path.join(soRoot, 'scripts', 'codex-install.mjs'), '--json']);
|
|
328
377
|
if (!r.ok) return { step: 'codex', status: 'failed', detail: r.detail };
|
|
329
378
|
if (opts.dryRun) return { step: 'codex', status: 'ok', detail: 'dry-run' };
|
|
330
379
|
try {
|