session-orchestrator 5.12.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 +2 -2
- 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 +162 -4
- package/README.md +126 -127
- 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 +1 -1
- 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 +90 -17
- 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 +49 -15
- package/docs/session-config-template.md +30 -22
- 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 +139 -49
- 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 -2
- 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 +39 -27
- 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/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 +2 -2
- 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 +4 -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-id.mjs +9 -1
- package/scripts/lib/session-identity/own-session.mjs +63 -0
- package/scripts/lib/session-lock.mjs +114 -0
- package/scripts/lib/session-record-repair.mjs +245 -35
- package/scripts/lib/session-registry.mjs +72 -9
- 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 +49 -5
- package/scripts/lib/so-hud/runtime.mjs +15 -4
- 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 +14 -5
- package/scripts/lib/state-md/mission-status.mjs +5 -1
- 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 +19 -7
- package/scripts/lib/vault-mirror/process.mjs +56 -27
- package/scripts/lib/vault-mirror/prune.mjs +5 -2
- package/scripts/lib/vault-mirror/render-learnings.mjs +3 -3
- 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 +79 -0
- package/scripts/lib/vault-staleness-banner.mjs +4 -7
- package/scripts/lib/vault-status/board-writer.mjs +2 -1
- 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 +10 -17
- 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 +29 -6
- package/scripts/state-md-idle-reset.mjs +2 -1
- 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/skills/_shared/fleet-protocol.md +142 -1
- 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 +3 -2
- package/skills/architecture/references/domain-model.md +1 -1
- 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 +94 -11
- 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-documentation-updates.md +14 -12
- package/skills/session-end/references/phase-5-issue-cleanup.md +1 -1
- package/skills/session-end/references/session-summary-template.md +4 -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 +9 -3
- package/skills/session-start/phase-4-5-resource-health.md +11 -5
- 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 +2 -2
- 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/assets/og-card.png +0 -0
- package/assets/og-card.svg +0 -47
- package/assets/wave-lifecycle.svg +0 -98
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* lifecycle → config.mjs (readConfigFile, parseSessionConfig)
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
import { $, nothrow, ProcessOutput } from 'zx';
|
|
20
|
+
import { $, nothrow, ProcessOutput } from '../zx.mjs';
|
|
21
21
|
import fs from 'node:fs/promises';
|
|
22
22
|
import os from 'node:os';
|
|
23
23
|
import path from 'node:path';
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { existsSync as fileExistsSync } from 'node:fs';
|
|
2
|
+
import { win32 as path } from 'node:path';
|
|
3
|
+
import { $, quote } from 'zx';
|
|
4
|
+
|
|
5
|
+
export * from 'zx';
|
|
6
|
+
|
|
7
|
+
// Windows 11 / Node 24.14 / zx 8.8.5 / Git 2.55, measured 2026-10-14:
|
|
8
|
+
// MSYS halves backslashes in Node's -c command line before bash interprets
|
|
9
|
+
// ANSI-C quoting (\b becomes backspace, \r CR). Doubling zx's escapes gave
|
|
10
|
+
// 14/14 exact argument roundtrips, versus 10/14 with stock quote (#1612).
|
|
11
|
+
// Always start from stock quote, so repeated configuration cannot stack escapes.
|
|
12
|
+
const quoteGitBash = (arg) => quote(arg).replace(/\\/g, '\\\\');
|
|
13
|
+
|
|
14
|
+
/** Configure the zx singleton; injection also permits Windows checks on POSIX. */
|
|
15
|
+
export function configureZx({
|
|
16
|
+
platform = process.platform,
|
|
17
|
+
existsSync = fileExistsSync,
|
|
18
|
+
target = $,
|
|
19
|
+
env = process.env,
|
|
20
|
+
} = {}) {
|
|
21
|
+
if (platform !== 'win32') return;
|
|
22
|
+
|
|
23
|
+
const shell = typeof target.shell === 'string' ? path.normalize(target.shell) : '';
|
|
24
|
+
const wsl = path.join(env.SystemRoot || 'C:\\Windows', 'System32', 'bash.exe');
|
|
25
|
+
if (!target.shell || shell.toLowerCase() === wsl.toLowerCase()) {
|
|
26
|
+
const candidates = [env.ProgramFiles, env['ProgramFiles(x86)']]
|
|
27
|
+
.filter(Boolean).map(dir => path.join(dir, 'Git', 'bin', 'bash.exe'));
|
|
28
|
+
let bash = candidates.find(candidate => existsSync(candidate));
|
|
29
|
+
if (!bash) {
|
|
30
|
+
for (const dir of (env.PATH || '').split(';').filter(Boolean)) {
|
|
31
|
+
const gitDir = dir.replace(/^"|"$/g, '');
|
|
32
|
+
if (!existsSync(path.join(gitDir, 'git.exe'))) continue;
|
|
33
|
+
const candidate = path.resolve(gitDir, '..', 'bin', 'bash.exe');
|
|
34
|
+
if (existsSync(candidate)) {
|
|
35
|
+
bash = candidate;
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (!bash) return;
|
|
41
|
+
target.shell = bash;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (typeof target.shell === 'string' &&
|
|
45
|
+
/\\(?:Git\\bin|usr\\bin)\\bash\.exe$/i.test(path.normalize(target.shell))) {
|
|
46
|
+
target.quote = quoteGitBash;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
configureZx();
|
package/scripts/mcp-server.mjs
CHANGED
|
@@ -6,6 +6,8 @@ import { join } from 'node:path';
|
|
|
6
6
|
import { createInterface } from 'node:readline';
|
|
7
7
|
import { isSessionConfigHeading } from './lib/config/section-extractor.mjs';
|
|
8
8
|
import { isMainModule } from './lib/is-main-module.mjs';
|
|
9
|
+
import { canonicalizeSessions, isMeasuredSession } from './lib/sessions-canonical.mjs';
|
|
10
|
+
import { resolveEventsLedgerRoot } from './lib/platform.mjs';
|
|
9
11
|
|
|
10
12
|
const SERVER_INFO = { name: 'session-orchestrator', version: '2.0.0' };
|
|
11
13
|
const TOOLS = [
|
|
@@ -16,7 +18,7 @@ const TOOLS = [
|
|
|
16
18
|
},
|
|
17
19
|
{
|
|
18
20
|
name: 'session_metrics',
|
|
19
|
-
description: 'Reads the last 5
|
|
21
|
+
description: 'Reads the last 5 MEASURED session metrics entries from .orchestrator/metrics/sessions.jsonl (unmeasured records excluded)',
|
|
20
22
|
inputSchema: { type: 'object', properties: {}, required: [] },
|
|
21
23
|
},
|
|
22
24
|
];
|
|
@@ -65,24 +67,31 @@ export function readSessionConfig(root) {
|
|
|
65
67
|
return body.join('\n').replace(/\n+$/, '') || `No '## Session Config' section found in ${file}`;
|
|
66
68
|
}
|
|
67
69
|
|
|
68
|
-
/** Read the last five
|
|
70
|
+
/** Read the last five canonical measured records, with exclusions and token coverage. */
|
|
69
71
|
export function readSessionMetrics(root) {
|
|
70
|
-
const file = ['.orchestrator', '.claude'].map((dir) => join(root, dir, 'metrics', 'sessions.jsonl'))
|
|
72
|
+
const file = ['.orchestrator', '.claude'].map((dir) => join(resolveEventsLedgerRoot(root), dir, 'metrics', 'sessions.jsonl'))
|
|
71
73
|
.find((path) => isFile(path));
|
|
72
74
|
if (!file) return 'No metrics found (checked .orchestrator/metrics/ and .claude/metrics/)';
|
|
73
75
|
const entries = [];
|
|
74
76
|
let stubCount = 0;
|
|
77
|
+
const parsed = [];
|
|
75
78
|
for (const line of readFileSync(file, 'utf8').split(/\r?\n/)) {
|
|
76
79
|
let record;
|
|
77
80
|
try { record = JSON.parse(line); } catch { continue; }
|
|
78
|
-
//
|
|
81
|
+
// Preserve torn-write/non-record guards and actionable read errors.
|
|
79
82
|
if (!record || typeof record !== 'object' || Array.isArray(record)) continue;
|
|
80
|
-
|
|
83
|
+
parsed.push(record);
|
|
84
|
+
}
|
|
85
|
+
// Keep ID-less legacy measurements and original append order for the window.
|
|
86
|
+
const canonical = new Set(canonicalizeSessions(parsed, { keepUnidentified: true }));
|
|
87
|
+
for (const record of parsed) {
|
|
88
|
+
if (!canonical.has(record)) continue;
|
|
89
|
+
if (!isMeasuredSession(record)) { stubCount++; continue; }
|
|
81
90
|
entries.push(record);
|
|
82
91
|
if (entries.length > 5) entries.shift();
|
|
83
92
|
}
|
|
84
93
|
if (!entries.length) {
|
|
85
|
-
return stubCount ? `No
|
|
94
|
+
return stubCount ? `No measured sessions (unmeasured records excluded: ${stubCount})`
|
|
86
95
|
: 'No metrics found (file is empty)';
|
|
87
96
|
}
|
|
88
97
|
const record = entries.findLast((entry) => (entry.total_token_input !== null && entry.total_token_input !== undefined)
|
|
@@ -95,7 +104,7 @@ export function readSessionMetrics(root) {
|
|
|
95
104
|
+ ` [session: ${record.session_id ?? 'unknown'}]`
|
|
96
105
|
: 'tokens: no token data in last 5 sessions (subagent telemetry not yet captured)';
|
|
97
106
|
return entries.map((entry) => JSON.stringify(entry)).join('\n')
|
|
98
|
-
+ `\n\
|
|
107
|
+
+ `\n\nunmeasured records excluded: ${stubCount}\n\n--- token summary ---\n${summary}`;
|
|
99
108
|
}
|
|
100
109
|
|
|
101
110
|
/** Handle one JSON-RPC line. null means a notification or blank line, not a reply. */
|
package/scripts/mcp-server.sh
CHANGED
|
@@ -8,8 +8,8 @@ set -euo pipefail
|
|
|
8
8
|
# - session_config — reads Session Config from the project instruction file
|
|
9
9
|
# (CLAUDE.md, or AGENTS.md alias on Codex CLI — see
|
|
10
10
|
# skills/_shared/instruction-file-resolution.md)
|
|
11
|
-
# - session_metrics — reads last 5
|
|
12
|
-
#
|
|
11
|
+
# - session_metrics — reads last 5 canonical MEASURED session metrics entries
|
|
12
|
+
# via the shared native reader (#1584)
|
|
13
13
|
|
|
14
14
|
# ---------------------------------------------------------------------------
|
|
15
15
|
# Helpers
|
|
@@ -85,7 +85,7 @@ handle_tools_list() {
|
|
|
85
85
|
},
|
|
86
86
|
{
|
|
87
87
|
"name": "session_metrics",
|
|
88
|
-
"description": "Reads the last 5
|
|
88
|
+
"description": "Reads the last 5 MEASURED session metrics entries from .orchestrator/metrics/sessions.jsonl (unmeasured records excluded)",
|
|
89
89
|
"inputSchema": {"type": "object", "properties": {}, "required": []}
|
|
90
90
|
}
|
|
91
91
|
]
|
|
@@ -171,88 +171,18 @@ tool_session_metrics() {
|
|
|
171
171
|
return
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
if
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
# — 0 waves, seconds of runtime) BEFORE taking the tail, so a recent phantom
|
|
185
|
-
# cannot displace real session records out of the last-5 window.
|
|
186
|
-
#
|
|
187
|
-
# `-R` (raw-input) + `fromjson?` parses each line individually and SKIPS
|
|
188
|
-
# unparseable ones instead of aborting the whole stream — plain
|
|
189
|
-
# `jq -c 'select(...)'` aborts at the FIRST malformed line (jq: parse error,
|
|
190
|
-
# exit 5), which is fatal here because sessions.jsonl is append-only from
|
|
191
|
-
# multiple writers and a torn write is exactly the case that matters. This
|
|
192
|
-
# mirrors the per-line try/catch behaviour of the .mjs path
|
|
193
|
-
# (scripts/lib/session-schema/filters.mjs).
|
|
194
|
-
local entries
|
|
195
|
-
entries=$(jq -R -c 'fromjson? | select(.status != "abandoned")' "$metrics_file" 2>/dev/null | tail -n 5) || true
|
|
196
|
-
|
|
197
|
-
# Report the stubs the filter above dropped (#1296) — counted over the WHOLE
|
|
198
|
-
# file, not the tail, with the same torn-line tolerance (`fromjson?`, plus
|
|
199
|
-
# `.status?` so a non-object line cannot abort the count either). Computed
|
|
200
|
-
# BEFORE the empty check: a stubs-only ledger is not an empty file.
|
|
201
|
-
local stub_count
|
|
202
|
-
stub_count=$(jq -R -n '[inputs | fromjson? | select(.status? == "abandoned")] | length' "$metrics_file" 2>/dev/null) || stub_count=0
|
|
203
|
-
[[ -z "$stub_count" ]] && stub_count=0
|
|
204
|
-
|
|
205
|
-
if [[ -z "$entries" ]]; then
|
|
206
|
-
if [[ "$stub_count" -gt 0 ]]; then
|
|
207
|
-
respond "$id" "$(text_content "No real sessions (abandoned stubs excluded: ${stub_count})")"
|
|
208
|
-
else
|
|
209
|
-
respond "$id" "$(text_content "No metrics found (file is empty)")"
|
|
210
|
-
fi
|
|
174
|
+
# Delegate to the same reader as the native transport: canonical identity,
|
|
175
|
+
# measured-session definition, main-checkout ledger and token summary.
|
|
176
|
+
local server_dir output
|
|
177
|
+
server_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
|
|
178
|
+
if ! output=$(node --input-type=module -e '
|
|
179
|
+
import { pathToFileURL } from "node:url";
|
|
180
|
+
const { readSessionMetrics } = await import(pathToFileURL(process.argv[2]).href);
|
|
181
|
+
process.stdout.write(readSessionMetrics(process.argv[3]));
|
|
182
|
+
' session-metrics-reader "$server_dir/mcp-server.mjs" "$project_root" </dev/null); then
|
|
183
|
+
respond_error "$id" -32603 "Unable to read session data"
|
|
211
184
|
return
|
|
212
185
|
fi
|
|
213
|
-
|
|
214
|
-
# Derive a token summary from the most-recent record that carries token totals.
|
|
215
|
-
# Fields total_token_input / total_token_output / subagents_with_tokens are
|
|
216
|
-
# optional (null when no subagent token data was captured for that session).
|
|
217
|
-
# Low coverage (subagents_with_tokens == 0 while matched_records > 0) means
|
|
218
|
-
# the total is PARTIAL — do not read it as "free / zero cost".
|
|
219
|
-
local token_summary
|
|
220
|
-
# Use -Rrs (raw-input + slurp + raw-output) so jq receives the multi-line
|
|
221
|
-
# JSONL block as a single string, can split + parse each line independently,
|
|
222
|
-
# and outputs plain text (no surrounding JSON quotes on the result string).
|
|
223
|
-
# The plain -r flag alone does not work for multi-record JSONL piped as one
|
|
224
|
-
# string — it treats the entire input as a single JSON value.
|
|
225
|
-
token_summary=$(printf '%s' "$entries" | \
|
|
226
|
-
jq -Rrs '
|
|
227
|
-
# Parse each non-empty line as JSON; skip malformed lines.
|
|
228
|
-
[ split("\n")[] | select(length > 0) | (try fromjson catch null) | select(. != null) ]
|
|
229
|
-
| reverse
|
|
230
|
-
| ( map(select( (.total_token_input != null) or (.total_token_output != null) )) | first ) as $rec
|
|
231
|
-
| if $rec then
|
|
232
|
-
"tokens: \($rec.total_token_input // "?") in / \($rec.total_token_output // "?") out" +
|
|
233
|
-
" (coverage: \($rec.subagents_with_tokens // 0) subagents)" +
|
|
234
|
-
( if ($rec.subagents_with_tokens // 0) == 0 then
|
|
235
|
-
" ⚠ partial — subagent token data missing; total is not a reliable cost estimate"
|
|
236
|
-
else "" end ) +
|
|
237
|
-
" [session: \($rec.session_id // "unknown")]"
|
|
238
|
-
else
|
|
239
|
-
"tokens: no token data in last 5 sessions (subagent telemetry not yet captured)"
|
|
240
|
-
end
|
|
241
|
-
' 2>/dev/null) || token_summary=""
|
|
242
|
-
|
|
243
|
-
local output
|
|
244
|
-
entries="${entries}
|
|
245
|
-
|
|
246
|
-
abandoned stubs excluded: ${stub_count}"
|
|
247
|
-
if [[ -n "$token_summary" ]]; then
|
|
248
|
-
output="${entries}
|
|
249
|
-
|
|
250
|
-
--- token summary ---
|
|
251
|
-
${token_summary}"
|
|
252
|
-
else
|
|
253
|
-
output="$entries"
|
|
254
|
-
fi
|
|
255
|
-
|
|
256
186
|
respond "$id" "$(text_content "$output")"
|
|
257
187
|
}
|
|
258
188
|
|
|
@@ -37,7 +37,7 @@ import { spawnSync } from 'node:child_process';
|
|
|
37
37
|
import { mkdtempSync, writeFileSync, rmSync, mkdirSync } from 'node:fs';
|
|
38
38
|
import { join, resolve } from 'node:path';
|
|
39
39
|
import { tmpdir } from 'node:os';
|
|
40
|
-
import { fileURLToPath } from 'node:url';
|
|
40
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
41
41
|
|
|
42
42
|
// ---------------------------------------------------------------------------
|
|
43
43
|
// CLI flags
|
|
@@ -57,8 +57,8 @@ const _REPO_ROOT = resolve(flagValue('--repo-root', process.cwd()));
|
|
|
57
57
|
const JSON_MODE = flagBool('--json');
|
|
58
58
|
|
|
59
59
|
const SCRIPT_DIR = fileURLToPath(new URL('.', import.meta.url));
|
|
60
|
-
const CACHE_MODULE = join(SCRIPT_DIR, 'lib', 'quality-gates-cache.mjs');
|
|
61
|
-
const POLICY_MODULE = join(SCRIPT_DIR, 'lib', 'quality-gates-policy.mjs');
|
|
60
|
+
const CACHE_MODULE = pathToFileURL(join(SCRIPT_DIR, 'lib', 'quality-gates-cache.mjs')).href;
|
|
61
|
+
const POLICY_MODULE = pathToFileURL(join(SCRIPT_DIR, 'lib', 'quality-gates-policy.mjs')).href;
|
|
62
62
|
|
|
63
63
|
// ---------------------------------------------------------------------------
|
|
64
64
|
// Helpers
|
|
@@ -340,11 +340,13 @@ async function main() {
|
|
|
340
340
|
} else {
|
|
341
341
|
printHumanReport(report);
|
|
342
342
|
}
|
|
343
|
-
|
|
344
|
-
process.exit(0);
|
|
345
343
|
} finally {
|
|
346
344
|
teardownTmpRepo(tmpRepo);
|
|
347
345
|
}
|
|
346
|
+
// Exit only AFTER the finally: process.exit() ends the process on the spot,
|
|
347
|
+
// so calling it inside the try skipped teardownTmpRepo and leaked one
|
|
348
|
+
// pce-repo-* dir under os.tmpdir() on every successful run (#1648).
|
|
349
|
+
process.exit(0);
|
|
348
350
|
}
|
|
349
351
|
|
|
350
352
|
function deriveRecommendation(cacheSubprocess, cacheInProcess, policySubprocess, _policyInProcess) {
|
|
@@ -278,12 +278,19 @@ let stateMdReadOk = false;
|
|
|
278
278
|
let stateDir = null;
|
|
279
279
|
|
|
280
280
|
try {
|
|
281
|
-
// resolveStateMdPath from state-md.mjs: falls back to .claude/STATE.md
|
|
282
281
|
const stateMdMod = await import('./lib/state-md.mjs');
|
|
283
|
-
const
|
|
282
|
+
const { resolveStateMdWriteTarget, warnForeignStateMd } = await import(
|
|
283
|
+
'./lib/state-md/frontmatter-mutators.mjs'
|
|
284
|
+
);
|
|
285
|
+
// #1636: the write side's ownership rule — another platform's STATE.md not
|
|
286
|
+
// proven to be this process's takes the "missing" branch. On rejection
|
|
287
|
+
// `stateMdPath` is this platform's own target, so stateDir (also the wave
|
|
288
|
+
// manifest's dir) never points into the foreign platform's state dir.
|
|
289
|
+
const { path: stateMdPath, rejected } = resolveStateMdWriteTarget(process.cwd());
|
|
290
|
+
if (rejected) warnForeignStateMd('memory-propose', rejected, stateMdPath);
|
|
284
291
|
stateDir = dirname(stateMdPath);
|
|
285
292
|
|
|
286
|
-
if (!existsSync(stateMdPath)) {
|
|
293
|
+
if (rejected || !existsSync(stateMdPath)) {
|
|
287
294
|
if (!dryRun) {
|
|
288
295
|
exit(
|
|
289
296
|
{ status: STATUS.REJECTED_WRONG_CONTEXT, detail: 'STATE.md missing or unparseable' },
|
|
@@ -245,6 +245,13 @@ async function processRepo(repo, { apply }) {
|
|
|
245
245
|
|
|
246
246
|
// Sessions.jsonl liveness check — even if marker is absent, an active repo
|
|
247
247
|
// should not get a "welcome" banner.
|
|
248
|
+
// Deliberately THIS path's own ledger, not resolveEventsLedgerRoot(repo)
|
|
249
|
+
// (#1592): the inputs are main-checkout repo paths by construction
|
|
250
|
+
// (--repos / dormant-repos), and the marker above is seeded into this same
|
|
251
|
+
// path — liveness and seed stay one checkout. Ceiling: a linked worktree
|
|
252
|
+
// passed via --repos is judged by its own (absent) ledger; revisit if
|
|
253
|
+
// worktree paths ever enter dormant-repos.
|
|
254
|
+
// ledger-root: deliberate own-path ledger (#1592), see the comment above
|
|
248
255
|
const sessionsPath = join(repo, SESSIONS_REL);
|
|
249
256
|
try {
|
|
250
257
|
if (await sessionsHasContent(sessionsPath)) {
|
|
@@ -7,6 +7,28 @@ set -euo pipefail
|
|
|
7
7
|
ROOT="${1:-$(pwd)}"
|
|
8
8
|
CANONICAL="$ROOT/.orchestrator/metrics/learnings.jsonl"
|
|
9
9
|
TODAY="$(date +%Y-%m-%d)"
|
|
10
|
+
|
|
11
|
+
# #1516: a host whose learnings-store role is `inbox` never rewrites the
|
|
12
|
+
# canonical store. The role resolver is the JS one (env SO_LEARNINGS_STORE_ROLE
|
|
13
|
+
# > owner.yaml learnings.store-role > writer), imported relative to this script.
|
|
14
|
+
# Fail-closed: if node fails, or the role is `invalid` (set but unusable) or
|
|
15
|
+
# anything other than writer/inbox, nothing is written and the script exits 4.
|
|
16
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
17
|
+
STORE_ROLE="$(node --input-type=module -e '
|
|
18
|
+
const { pathToFileURL } = await import("node:url");
|
|
19
|
+
const m = await import(pathToFileURL(process.argv[1]).href);
|
|
20
|
+
process.stdout.write(m.resolveStoreRole().role);
|
|
21
|
+
' "$SCRIPT_DIR/lib/learnings/store-role.mjs" 2>/dev/null)" || STORE_ROLE="unresolved"
|
|
22
|
+
if [ "$STORE_ROLE" = "inbox" ]; then
|
|
23
|
+
echo "migrate-legacy-learnings: learnings store role is inbox on this host (#1516) — nothing written; run it on the writer host" >&2
|
|
24
|
+
printf '{"status":"refused_store_role"}\n'
|
|
25
|
+
exit 3
|
|
26
|
+
fi
|
|
27
|
+
if [ "$STORE_ROLE" != "writer" ]; then
|
|
28
|
+
echo "migrate-legacy-learnings: learnings store role is ${STORE_ROLE} on this host (#1516) — nothing written; fix owner.yaml learnings.store-role or SO_LEARNINGS_STORE_ROLE" >&2
|
|
29
|
+
printf '{"status":"refused_store_role_invalid"}\n'
|
|
30
|
+
exit 4
|
|
31
|
+
fi
|
|
10
32
|
LEGACY_PATHS=("$ROOT/.claude/metrics/learnings.jsonl" "$ROOT/.codex/metrics/learnings.jsonl" "$ROOT/.cursor/metrics/learnings.jsonl")
|
|
11
33
|
|
|
12
34
|
tmp=""
|
|
@@ -39,9 +39,10 @@
|
|
|
39
39
|
*/
|
|
40
40
|
|
|
41
41
|
import { readFileSync, copyFileSync, existsSync } from 'node:fs';
|
|
42
|
-
import { basename } from 'node:path';
|
|
42
|
+
import { basename, join, resolve } from 'node:path';
|
|
43
43
|
import { atomicWriteWithBackup, envelopeToError } from './lib/io.mjs';
|
|
44
44
|
import { validateSession, normalizeSession, ValidationError } from './lib/session-schema.mjs';
|
|
45
|
+
import { resolveEventsLedgerRoot } from './lib/platform.mjs';
|
|
45
46
|
import { isMainModule } from './lib/is-main-module.mjs';
|
|
46
47
|
|
|
47
48
|
const DEFAULT_FILE = '.orchestrator/metrics/sessions.jsonl';
|
|
@@ -327,8 +328,21 @@ function processEntry(raw) {
|
|
|
327
328
|
// CLI
|
|
328
329
|
// ---------------------------------------------------------------------------
|
|
329
330
|
|
|
331
|
+
/**
|
|
332
|
+
* Default target: sessions.jsonl of the repo's one ledger root (#1514/#1584,
|
|
333
|
+
* #1722) — the main checkout when cwd is a linked worktree. Outside one the
|
|
334
|
+
* relative DEFAULT_FILE is kept verbatim, so paths in output do not change.
|
|
335
|
+
*
|
|
336
|
+
* @returns {string}
|
|
337
|
+
*/
|
|
338
|
+
function defaultFile() {
|
|
339
|
+
const root = resolveEventsLedgerRoot(process.cwd());
|
|
340
|
+
// ledger-root: `root` is resolveEventsLedgerRoot() of the line above
|
|
341
|
+
return root === resolve(process.cwd()) ? DEFAULT_FILE : join(root, DEFAULT_FILE);
|
|
342
|
+
}
|
|
343
|
+
|
|
330
344
|
function parseArgs(argv) {
|
|
331
|
-
const args = { file:
|
|
345
|
+
const args = { file: defaultFile(), apply: false };
|
|
332
346
|
for (let i = 0; i < argv.length; i++) {
|
|
333
347
|
const a = argv[i];
|
|
334
348
|
if (a === '--file') {
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
* migrate-vault-paths.mjs — one-shot cross-repo migration for username-drift in vault/project paths.
|
|
4
4
|
*
|
|
5
5
|
* Rewrites a literal "from" username path-segment to a "to" segment across markdown
|
|
6
|
-
* files under
|
|
6
|
+
* files under the projects root (getConfinementRoot(): SO_PROJECTS_ROOT >
|
|
7
|
+
* owner.yaml paths.projects-root > ~/Projects). Only the username path segment changes; the trailing
|
|
7
8
|
* path is preserved.
|
|
8
9
|
*
|
|
9
10
|
* Typical use: a host username change (`/Users/oldname/` → `/Users/newname/`) leaves
|
|
@@ -23,7 +24,7 @@
|
|
|
23
24
|
* Target repo resolution (in priority order):
|
|
24
25
|
* 1. --repos <comma,list> (explicit CLI override)
|
|
25
26
|
* 2. audited-repos: [...] in the same vault-migration-rules.yaml
|
|
26
|
-
* 3. <none, no --repos> → scan only the auto-discover sweep under
|
|
27
|
+
* 3. <none, no --repos> → scan only the auto-discover sweep under <projects-root>/**.
|
|
27
28
|
*
|
|
28
29
|
* Flags:
|
|
29
30
|
* --from SEG Literal source segment (e.g. '/Users/oldname/'). Required if no config entry.
|
|
@@ -61,13 +62,13 @@ import { parseColumnFlags, CliFlagError } from './lib/cli-flags.mjs';
|
|
|
61
62
|
import { isMainModule } from './lib/is-main-module.mjs';
|
|
62
63
|
import { atomicWriteText, envelopeToError } from './lib/io.mjs';
|
|
63
64
|
import { loadHostPaths, resolveHostPath } from './lib/config/host-paths.mjs';
|
|
65
|
+
import { getConfinementRoot } from './lib/config/cross-repo.mjs';
|
|
64
66
|
|
|
65
67
|
// ---------------------------------------------------------------------------
|
|
66
68
|
// Constants
|
|
67
69
|
// ---------------------------------------------------------------------------
|
|
68
70
|
|
|
69
71
|
const HOME = os.homedir();
|
|
70
|
-
const PROJECTS_ROOT = path.join(HOME, 'Projects');
|
|
71
72
|
|
|
72
73
|
// Set at startup from CLI args or config. Helpers read these; main() resolves them.
|
|
73
74
|
let OLD_SEGMENT = null;
|
|
@@ -221,7 +222,8 @@ function printHelp() {
|
|
|
221
222
|
`Usage: migrate-vault-paths.mjs --from <old/> --to <new/> [--dry-run|--apply] [--repos <comma,list>] [--json] [--help]
|
|
222
223
|
|
|
223
224
|
Rewrites a literal "from" path segment to a "to" segment across markdown files
|
|
224
|
-
under
|
|
225
|
+
under the projects root (SO_PROJECTS_ROOT / owner.yaml paths.projects-root,
|
|
226
|
+
default ~/Projects), skipping tests/, fixtures/, .git/, vault-backups, and
|
|
225
227
|
historical contexts (decisions.md, history/, archive*).
|
|
226
228
|
|
|
227
229
|
Both --from and --to are required unless username-rewrites[0] is set in
|
|
@@ -236,7 +238,7 @@ Options:
|
|
|
236
238
|
--apply Write fixes in-place (atomic: tmp + rename)
|
|
237
239
|
--repos LIST Comma-separated repo names or paths to scan; falls back
|
|
238
240
|
to audited-repos: [...] in vault-migration-rules.yaml,
|
|
239
|
-
then to
|
|
241
|
+
then to <projects-root>/** auto-discovery sweep
|
|
240
242
|
--json Emit JSONL records on stdout (one per hit)
|
|
241
243
|
--help, -h Show this help
|
|
242
244
|
|
|
@@ -249,8 +251,21 @@ Exit codes: 0 success 1 input error 2 I/O error
|
|
|
249
251
|
// Repo discovery
|
|
250
252
|
// ---------------------------------------------------------------------------
|
|
251
253
|
|
|
254
|
+
let _projectsRoot;
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Projects root for this run, resolved once (#1622): getConfinementRoot() reads
|
|
258
|
+
* owner.yaml and WARNs on an invalid SO_PROJECTS_ROOT, so per-repo calls repeated both.
|
|
259
|
+
* @returns {string}
|
|
260
|
+
*/
|
|
261
|
+
function projectsRoot() {
|
|
262
|
+
_projectsRoot ??= getConfinementRoot();
|
|
263
|
+
return _projectsRoot;
|
|
264
|
+
}
|
|
265
|
+
|
|
252
266
|
/**
|
|
253
|
-
* Resolve a repo name to an absolute path by searching
|
|
267
|
+
* Resolve a repo name to an absolute path by searching <projects-root>/**
|
|
268
|
+
* (getConfinementRoot(), #1567).
|
|
254
269
|
* If the input is already an absolute path that exists, returns it as-is.
|
|
255
270
|
* Returns null when no match is found.
|
|
256
271
|
*/
|
|
@@ -258,10 +273,10 @@ function resolveRepoPath(nameOrPath) {
|
|
|
258
273
|
if (path.isAbsolute(nameOrPath)) {
|
|
259
274
|
return existsSync(nameOrPath) ? nameOrPath : null;
|
|
260
275
|
}
|
|
261
|
-
// Search up to 4 levels deep under
|
|
276
|
+
// Search up to 4 levels deep under the projects root
|
|
262
277
|
const result = spawnSync(
|
|
263
278
|
'find',
|
|
264
|
-
[
|
|
279
|
+
[projectsRoot(), '-maxdepth', '4', '-type', 'd', '-name', nameOrPath],
|
|
265
280
|
{ encoding: 'utf8' },
|
|
266
281
|
);
|
|
267
282
|
if (result.status !== 0) return null;
|
|
@@ -358,14 +373,15 @@ function findMissingSegmentFiles(roots) {
|
|
|
358
373
|
}
|
|
359
374
|
|
|
360
375
|
/**
|
|
361
|
-
* Auto-discover ALL repos under
|
|
376
|
+
* Auto-discover ALL repos under <projects-root>/** that contain the OLD_SEGMENT
|
|
362
377
|
* literal OR a missing-segment `vault-dir:` drift. Used in tandem with the
|
|
363
378
|
* default-target list to find drift we don't already know about.
|
|
364
379
|
*/
|
|
365
380
|
function discoverAdditionalFiles() {
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
381
|
+
const root = projectsRoot();
|
|
382
|
+
if (!existsSync(root)) return [];
|
|
383
|
+
const set = new Set(findCandidateFiles([root]));
|
|
384
|
+
for (const f of findMissingSegmentFiles([root])) set.add(f);
|
|
369
385
|
return [...set];
|
|
370
386
|
}
|
|
371
387
|
|
|
@@ -599,7 +615,7 @@ async function main() {
|
|
|
599
615
|
let candidateFiles = [...candidateSet];
|
|
600
616
|
|
|
601
617
|
// If NO explicit repo list was given (neither --repos nor config.audited-repos),
|
|
602
|
-
// expand scan to discover anything else under
|
|
618
|
+
// expand scan to discover anything else under <projects-root>/**.
|
|
603
619
|
if (!explicitRepos) {
|
|
604
620
|
const additional = discoverAdditionalFiles();
|
|
605
621
|
const set = new Set(candidateFiles);
|
package/scripts/parse-config.mjs
CHANGED
|
@@ -8,11 +8,12 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Output: Single JSON object to stdout with ALL config fields (defaults applied).
|
|
10
10
|
* `--json` is an explicit NO-OP alias for that default — accepted because
|
|
11
|
-
* `docs/session-config-template.md` (
|
|
12
|
-
* `node scripts/parse-config.mjs --json` as THE
|
|
13
|
-
* flag used to be swallowed by the
|
|
14
|
-
* `File not found: --json` (exit 1).
|
|
15
|
-
* requires every CLI to accept
|
|
11
|
+
* `docs/session-config-template.md` ("How to use" and "Validation &
|
|
12
|
+
* Bootstrap") documents `node scripts/parse-config.mjs --json` as THE
|
|
13
|
+
* validation command, and the flag used to be swallowed by the
|
|
14
|
+
* positional-path slot and rejected with `File not found: --json` (exit 1).
|
|
15
|
+
* `cli-design.md` § JSON-First Output also requires every CLI to accept
|
|
16
|
+
* `--json`.
|
|
16
17
|
* Exit codes: 0 success, 1 error (message to stderr)
|
|
17
18
|
*
|
|
18
19
|
* Unparsable lines inside `## Session Config` (#1097) are reported per line on
|
|
@@ -20,6 +21,11 @@
|
|
|
20
21
|
* `enforcement: strict`; `off` is silent. stdout is unchanged for any
|
|
21
22
|
* well-formed block — a warning never alters the emitted JSON.
|
|
22
23
|
*
|
|
24
|
+
* Unknown top-level keys (#1562) — a key the plugin's own
|
|
25
|
+
* `docs/session-config-template.md` does not document, i.e. a typo or a key
|
|
26
|
+
* from a newer plugin version — are reported per line on stderr under `warn`
|
|
27
|
+
* AND `strict` (a report, never a refusal); `off` is silent. stdout unchanged.
|
|
28
|
+
*
|
|
23
29
|
* Environment:
|
|
24
30
|
* SO_CONFIG_FILE — override filename (e.g. "AGENTS.md") resolved from project root
|
|
25
31
|
* SO_SKIP_CONFIG_VALIDATION — set to "1" to bypass validate-config.mjs
|
|
@@ -30,7 +36,8 @@ import { spawnSync } from 'node:child_process';
|
|
|
30
36
|
import { join, dirname, resolve, parse as parsePath } from 'node:path';
|
|
31
37
|
import { fileURLToPath } from 'node:url';
|
|
32
38
|
import { parseSessionConfig } from './lib/config.mjs';
|
|
33
|
-
import { collectUnparsableLines } from './lib/config/section-extractor.mjs';
|
|
39
|
+
import { collectUnparsableLines, collectTopLevelKeyLines } from './lib/config/section-extractor.mjs';
|
|
40
|
+
import { knownTemplateKeys } from './lib/config/template-keys.mjs';
|
|
34
41
|
import { findUnterminatedComment } from './lib/config/block-preprocess.mjs';
|
|
35
42
|
import { ENFORCEMENT_VALUES } from './lib/config-schema.mjs';
|
|
36
43
|
|
|
@@ -189,15 +196,15 @@ try {
|
|
|
189
196
|
|
|
190
197
|
const unparsableLines = collectUnparsableLines(content);
|
|
191
198
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
const enforcement = ENFORCEMENT_VALUES.has(config.enforcement) ? config.enforcement : 'warn';
|
|
199
|
+
// Belt-and-braces, not a live branch: `parseSessionConfig` already REFUSES
|
|
200
|
+
// an out-of-vocabulary enforcement value above (measured: `enforcement:
|
|
201
|
+
// banana` exits 1 with "must be strict|warn|off" and never reaches here), so
|
|
202
|
+
// `config.enforcement` is one of the three by the time this line runs. The
|
|
203
|
+
// fallback exists for the direction that matters if that ever loosens — an
|
|
204
|
+
// unknown value must degrade to `warn`, never arm the `strict` refusal.
|
|
205
|
+
const enforcement = ENFORCEMENT_VALUES.has(config.enforcement) ? config.enforcement : 'warn';
|
|
200
206
|
|
|
207
|
+
if (unparsableLines.length > 0) {
|
|
201
208
|
if (enforcement !== 'off') {
|
|
202
209
|
// Ceiling: 20 named lines. A whole prose section pasted into the block is
|
|
203
210
|
// one defect, not 200, and the strict path below exits immediately after
|
|
@@ -230,6 +237,56 @@ if (unparsableLines.length > 0) {
|
|
|
230
237
|
}
|
|
231
238
|
}
|
|
232
239
|
|
|
240
|
+
// ---------------------------------------------------------------------------
|
|
241
|
+
// Unknown-key report (#1562) — a typo'd key is not a silent default either
|
|
242
|
+
// ---------------------------------------------------------------------------
|
|
243
|
+
//
|
|
244
|
+
// A well-formed line with a mis-spelled key (`persistance: true`) parses
|
|
245
|
+
// cleanly, so the gate above never sees it: the key lands in the KV map under a
|
|
246
|
+
// name nothing reads, and the real key keeps its default. The population of
|
|
247
|
+
// known keys is the plugin's OWN template (first + last `## Session Config`
|
|
248
|
+
// block, the population drift-check Check 6 reads) plus every top-level key
|
|
249
|
+
// `parseSessionConfig()` emits, so it moves with the plugin version, never
|
|
250
|
+
// with the repo.
|
|
251
|
+
//
|
|
252
|
+
// A report, never a refusal — even under `strict`: a key from a newer plugin
|
|
253
|
+
// version is not a defect of the repo. Three states stay apart on purpose:
|
|
254
|
+
// measured with findings (one line per key), measured clean (silence), and NOT
|
|
255
|
+
// measured (one line saying why) — "the check could not run" must never read
|
|
256
|
+
// like "no unknown keys".
|
|
257
|
+
|
|
258
|
+
if (enforcement !== 'off') {
|
|
259
|
+
const templatePath = join(SCRIPT_DIR, '..', 'docs', 'session-config-template.md');
|
|
260
|
+
let known = null;
|
|
261
|
+
let notRunReason = null;
|
|
262
|
+
try {
|
|
263
|
+
known = knownTemplateKeys(readFileSync(templatePath, 'utf8'));
|
|
264
|
+
if (!known) notRunReason = 'it has no distinct starter and opt-in `## Session Config` blocks';
|
|
265
|
+
// A key the parser itself emits is read, wherever the template documents it:
|
|
266
|
+
// `skill-evolution` and `dispatcher-autonomy` are parsed from the whole file
|
|
267
|
+
// (their parity-exempt template blocks sit outside `## Session Config`), so
|
|
268
|
+
// the same key inside the block is applied and must not read "no effect".
|
|
269
|
+
else for (const key of Object.keys(config)) known.add(key);
|
|
270
|
+
} catch (err) {
|
|
271
|
+
notRunReason = err.code === 'ENOENT' ? 'it was not found' : `it could not be read (${err.code ?? err.message})`;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (notRunReason) {
|
|
275
|
+
process.stderr.write(
|
|
276
|
+
`⚠ parse-config.mjs: unknown Session Config key check did not run — ${templatePath}: ${notRunReason}\n`,
|
|
277
|
+
);
|
|
278
|
+
} else {
|
|
279
|
+
const configName = parsePath(configFile).base;
|
|
280
|
+
for (const { line, key } of collectTopLevelKeyLines(content)) {
|
|
281
|
+
if (known.has(key)) continue;
|
|
282
|
+
process.stderr.write(
|
|
283
|
+
`⚠ ${configName}:${line}: unknown Session Config key '${key}' — not in docs/session-config-template.md ` +
|
|
284
|
+
'(a typo, or a key from a newer plugin version); it has no effect\n',
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
233
290
|
// jq -n produces pretty-printed JSON without a trailing newline — match that format
|
|
234
291
|
const assembledJson = JSON.stringify(config, null, 2);
|
|
235
292
|
|