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
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* session-commit-files.mjs — which files did one session commit, in which of
|
|
4
|
+
* its own work trees (#1659).
|
|
5
|
+
*
|
|
6
|
+
* Read-only CLI over scripts/lib/session-work-trees.mjs: the session's
|
|
7
|
+
* `(tree, start_ref)` pairs come from the `orchestrator.session.work_tree`
|
|
8
|
+
* events in the default events ledger, and every tree is diffed INSIDE ITSELF
|
|
9
|
+
* against its own start ref (`git -C <tree> diff --name-only <ref>..HEAD`).
|
|
10
|
+
* Never a diff across two trees, never a write.
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* node scripts/session-commit-files.mjs [--session-id <raw-id>] [--json]
|
|
14
|
+
*
|
|
15
|
+
* Exit codes: 0 success (also with zero trees), 2 usage error (unknown flag,
|
|
16
|
+
* missing session id). Deviates from cli-design.md's `1` for input errors by
|
|
17
|
+
* coordinator contract (#1659); documented in `--help`.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { parseArgs } from 'node:util';
|
|
21
|
+
|
|
22
|
+
import { isMainModule } from './lib/is-main-module.mjs';
|
|
23
|
+
import { readSessionWorkTrees, sessionCommitFiles } from './lib/session-work-trees.mjs';
|
|
24
|
+
|
|
25
|
+
const USAGE = `Usage: node scripts/session-commit-files.mjs [--session-id <raw-id>] [--json]
|
|
26
|
+
|
|
27
|
+
List the files one session committed, per own work tree (#1659).
|
|
28
|
+
|
|
29
|
+
Reads the session's (tree, start_ref) pairs from the
|
|
30
|
+
orchestrator.session.work_tree events of the default events ledger and runs
|
|
31
|
+
'git -C <tree> diff --name-only <start_ref>..HEAD' inside each tree only.
|
|
32
|
+
Read-only: writes nothing.
|
|
33
|
+
|
|
34
|
+
Options:
|
|
35
|
+
--session-id <id> raw session id (default: $CLAUDE_CODE_SESSION_ID)
|
|
36
|
+
--json print { trees, perTree, skipped, union, malformed, invalid,
|
|
37
|
+
complete[, error] } as JSON (complete: null = no ledger found)
|
|
38
|
+
-h, --help show this help
|
|
39
|
+
|
|
40
|
+
Default output (stdout): one line per tree '<tree>\\t<start_ref>\\t<n> files',
|
|
41
|
+
a blank line, then the union of files, one per line. Skipped trees and notes
|
|
42
|
+
go to stderr.
|
|
43
|
+
|
|
44
|
+
Exit codes:
|
|
45
|
+
0 success (also when the session recorded no trees)
|
|
46
|
+
2 usage error (unknown option, no session id)
|
|
47
|
+
|
|
48
|
+
Example:
|
|
49
|
+
node scripts/session-commit-files.mjs --session-id "$CLAUDE_CODE_SESSION_ID" --json`;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Run the CLI.
|
|
53
|
+
*
|
|
54
|
+
* @param {string[]} argv — arguments after the script path.
|
|
55
|
+
* @param {{ env?: NodeJS.ProcessEnv,
|
|
56
|
+
* stdout?: (s: string) => void, stderr?: (s: string) => void }} [io]
|
|
57
|
+
* @returns {number} exit code
|
|
58
|
+
*/
|
|
59
|
+
export function main(argv, {
|
|
60
|
+
env = process.env,
|
|
61
|
+
stdout = (s) => process.stdout.write(s),
|
|
62
|
+
stderr = (s) => process.stderr.write(s),
|
|
63
|
+
} = {}) {
|
|
64
|
+
let values;
|
|
65
|
+
try {
|
|
66
|
+
({ values } = parseArgs({
|
|
67
|
+
args: argv,
|
|
68
|
+
options: {
|
|
69
|
+
'session-id': { type: 'string' },
|
|
70
|
+
json: { type: 'boolean', default: false },
|
|
71
|
+
help: { type: 'boolean', short: 'h', default: false },
|
|
72
|
+
},
|
|
73
|
+
strict: true,
|
|
74
|
+
allowPositionals: false,
|
|
75
|
+
}));
|
|
76
|
+
} catch (err) {
|
|
77
|
+
stderr(`session-commit-files: ${err.message}\n${USAGE}\n`);
|
|
78
|
+
return 2;
|
|
79
|
+
}
|
|
80
|
+
if (values.help) {
|
|
81
|
+
stdout(`${USAGE}\n`);
|
|
82
|
+
return 0;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Process-local identity only — never a repo-global file (#1659).
|
|
86
|
+
const sessionId = (values['session-id'] ?? env.CLAUDE_CODE_SESSION_ID ?? '').trim();
|
|
87
|
+
if (sessionId === '') {
|
|
88
|
+
stderr('session-commit-files: no session id (pass --session-id or set CLAUDE_CODE_SESSION_ID)\n');
|
|
89
|
+
return 2;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const read = readSessionWorkTrees({ sessionId });
|
|
93
|
+
const files = sessionCommitFiles({ trees: read.trees });
|
|
94
|
+
|
|
95
|
+
// Notes go to stderr in BOTH modes, so `--json` never hides that the
|
|
96
|
+
// answer is partial or that no ledger exists (#1659).
|
|
97
|
+
if (read.error) stderr(`session-commit-files: ledger read failed: ${read.error}\n`);
|
|
98
|
+
if (read.complete === null && !read.error) stderr('session-commit-files: no events ledger found\n');
|
|
99
|
+
if (read.malformed > 0) stderr(`session-commit-files: ${read.malformed} unreadable ledger line(s)\n`);
|
|
100
|
+
if (read.invalid > 0) {
|
|
101
|
+
stderr(`session-commit-files: ignored ${read.invalid} own work_tree event(s) without a valid tree/start_ref\n`);
|
|
102
|
+
}
|
|
103
|
+
for (const s of files.skipped) {
|
|
104
|
+
stderr(`session-commit-files: skipped ${s.tree} (${s.start_ref}): ${s.reason}\n`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (values.json) {
|
|
108
|
+
stdout(`${JSON.stringify({
|
|
109
|
+
trees: read.trees,
|
|
110
|
+
perTree: files.perTree,
|
|
111
|
+
skipped: files.skipped,
|
|
112
|
+
union: files.union,
|
|
113
|
+
malformed: read.malformed,
|
|
114
|
+
invalid: read.invalid,
|
|
115
|
+
complete: read.complete,
|
|
116
|
+
...(read.error ? { error: read.error } : {}),
|
|
117
|
+
}, null, 2)}\n`);
|
|
118
|
+
return 0;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (read.trees.length === 0) {
|
|
122
|
+
if (read.complete !== null) stderr(`session-commit-files: no work trees recorded for session ${sessionId}\n`);
|
|
123
|
+
return 0;
|
|
124
|
+
}
|
|
125
|
+
if (files.perTree.length === 0) return 0; // every tree skipped — reported on stderr above
|
|
126
|
+
const lines = files.perTree.map((t) => `${t.tree}\t${t.start_ref}\t${t.files.length} files`);
|
|
127
|
+
stdout(`${lines.join('\n')}\n\n${files.union.map((f) => `${f}\n`).join('')}`);
|
|
128
|
+
return 0;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (isMainModule(import.meta.url)) {
|
|
132
|
+
process.exitCode = main(process.argv.slice(2));
|
|
133
|
+
}
|
package/scripts/site-numbers.mjs
CHANGED
|
@@ -110,9 +110,10 @@ import { join, resolve, relative, sep } from 'node:path';
|
|
|
110
110
|
import { execFileSync } from 'node:child_process';
|
|
111
111
|
|
|
112
112
|
import { writeStdoutLineSync, writeJsonAtomicSync } from './lib/io.mjs';
|
|
113
|
-
import { readCanonicalSessions } from './lib/sessions-canonical.mjs';
|
|
113
|
+
import { isMeasuredSession, readCanonicalSessions } from './lib/sessions-canonical.mjs';
|
|
114
114
|
import { isMainModule } from './lib/is-main-module.mjs';
|
|
115
115
|
import { slashCommandNames } from './lib/user-invocable-skills.mjs';
|
|
116
|
+
import { resolveEventsLedgerRoot } from './lib/platform.mjs';
|
|
116
117
|
|
|
117
118
|
/** Machine-readable schema tag for the --json envelope. */
|
|
118
119
|
export const SCHEMA = 'site-numbers/1';
|
|
@@ -393,6 +394,30 @@ export function isDirty(root) {
|
|
|
393
394
|
}
|
|
394
395
|
}
|
|
395
396
|
|
|
397
|
+
/** Metrics counted from a gitignored ledger — the ones `--write` must never republish from the snapshot. */
|
|
398
|
+
const LEDGER_METRICS = Object.freeze(['sessions', 'learnings']);
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Path of a gitignored metrics ledger. A linked worktree carries none of its own,
|
|
402
|
+
* so it reads its main checkout's copy (#1555) — the same mapping
|
|
403
|
+
* `scripts/resolve-session-invocation.mjs` applies to sessions.jsonl. Returns the
|
|
404
|
+
* root's own path when neither exists, so an absent ledger still reads as `null`
|
|
405
|
+
* downstream and the snapshot fallback stays reachable for a fresh clone.
|
|
406
|
+
*
|
|
407
|
+
* @param {string} root
|
|
408
|
+
* @param {string} name e.g. `sessions.jsonl`
|
|
409
|
+
* @returns {string}
|
|
410
|
+
*/
|
|
411
|
+
function ledgerFile(root, name) {
|
|
412
|
+
// ledger-root: this tree's own copy first, on purpose; the shared one is the fallback below
|
|
413
|
+
const own = join(root, '.orchestrator', 'metrics', name);
|
|
414
|
+
if (existsSync(own)) return own;
|
|
415
|
+
const main = resolveEventsLedgerRoot(root);
|
|
416
|
+
// ledger-root: the shared copy in the main checkout, read on purpose beside the own one
|
|
417
|
+
const shared = join(main, '.orchestrator', 'metrics', name);
|
|
418
|
+
return main !== resolve(root) && existsSync(shared) ? shared : own;
|
|
419
|
+
}
|
|
420
|
+
|
|
396
421
|
// ---------------------------------------------------------------------------
|
|
397
422
|
// The metric table — the part that must never be re-derived by hand
|
|
398
423
|
// ---------------------------------------------------------------------------
|
|
@@ -472,9 +497,9 @@ export const METRIC_DEFS = Object.freeze([
|
|
|
472
497
|
// Measured on THIS repo's own ledger @ 2ccea0f2: raw line count 289 vs
|
|
473
498
|
// canonical (deduped) count 278 — an over-count of 11 records, which is the
|
|
474
499
|
// bug's magnitude this migration removes.
|
|
475
|
-
source: 'grep -c . .orchestrator/metrics/sessions.jsonl (then deduped per session_id — see readCanonicalSessions, #1186)',
|
|
500
|
+
source: 'grep -c . .orchestrator/metrics/sessions.jsonl (then deduped per session_id — see readCanonicalSessions, #1186; synthetic backfill stubs excluded — isMeasuredSession, audit E1)',
|
|
476
501
|
compute: (root) => {
|
|
477
|
-
const file =
|
|
502
|
+
const file = ledgerFile(root, 'sessions.jsonl');
|
|
478
503
|
// Same absent/non-file guard as countJsonlEntries, kept explicit here
|
|
479
504
|
// because readCanonicalSessions returns [] (not null) on a missing ledger
|
|
480
505
|
// — collapsing that into fmtCount(0) would silently defeat the
|
|
@@ -487,7 +512,9 @@ export const METRIC_DEFS = Object.freeze([
|
|
|
487
512
|
// identified for dedup. Measured @ 2ccea0f2 on this repo's own ledger:
|
|
488
513
|
// 0 such rows (raw 289 lines == 289 identified), so the drop is inert
|
|
489
514
|
// here; it only matters for a future malformed/legacy row.
|
|
490
|
-
|
|
515
|
+
// Audit E1: count MEASURED sessions only — synthetic backfill stubs are
|
|
516
|
+
// records of a start, not sessions (`isMeasuredSession`).
|
|
517
|
+
return fmtCount(readCanonicalSessions({ filePath: file }).filter(isMeasuredSession).length);
|
|
491
518
|
},
|
|
492
519
|
},
|
|
493
520
|
{
|
|
@@ -497,7 +524,7 @@ export const METRIC_DEFS = Object.freeze([
|
|
|
497
524
|
snapshotFallback: true,
|
|
498
525
|
source: 'grep -c . .orchestrator/metrics/learnings.jsonl',
|
|
499
526
|
compute: (root) => {
|
|
500
|
-
const r = countJsonlEntries(
|
|
527
|
+
const r = countJsonlEntries(ledgerFile(root, 'learnings.jsonl'));
|
|
501
528
|
return r === null ? null : fmtCount(r.entries);
|
|
502
529
|
},
|
|
503
530
|
},
|
|
@@ -743,7 +770,7 @@ export function collect(root, ctx = {}) {
|
|
|
743
770
|
['sessions', 'sessions.jsonl'],
|
|
744
771
|
['learnings', 'learnings.jsonl'],
|
|
745
772
|
]) {
|
|
746
|
-
const r = countJsonlEntries(
|
|
773
|
+
const r = countJsonlEntries(ledgerFile(root, file));
|
|
747
774
|
if (r && r.malformed > 0) {
|
|
748
775
|
// #1186: the two ledgers now disagree on what a malformed line DOES to
|
|
749
776
|
// the published count. `learnings` is still the raw countJsonlEntries
|
|
@@ -759,7 +786,7 @@ export function collect(root, ctx = {}) {
|
|
|
759
786
|
}
|
|
760
787
|
}
|
|
761
788
|
|
|
762
|
-
return { values, missing, warnings, fromSnapshot };
|
|
789
|
+
return { values, missing, warnings, fromSnapshot, localFallbacks };
|
|
763
790
|
}
|
|
764
791
|
|
|
765
792
|
// ---------------------------------------------------------------------------
|
|
@@ -1307,7 +1334,7 @@ export function main(argv = process.argv.slice(2), env = {}) {
|
|
|
1307
1334
|
return 2;
|
|
1308
1335
|
}
|
|
1309
1336
|
|
|
1310
|
-
const { values, missing, warnings, fromSnapshot } = collect(root, { usage: env.usage });
|
|
1337
|
+
const { values, missing, warnings, fromSnapshot, localFallbacks } = collect(root, { usage: env.usage });
|
|
1311
1338
|
if (missing.length > 0) {
|
|
1312
1339
|
stderr(
|
|
1313
1340
|
`Error: could not measure ${missing.join(', ')} under ${root} — ` +
|
|
@@ -1315,6 +1342,22 @@ export function main(argv = process.argv.slice(2), env = {}) {
|
|
|
1315
1342
|
);
|
|
1316
1343
|
return 2;
|
|
1317
1344
|
}
|
|
1345
|
+
// #1555: a --write of the SERVED site publishes what it measured. A ledger
|
|
1346
|
+
// metric answered by the snapshot is the previous release's number, not a
|
|
1347
|
+
// measurement (5.11.0 and 5.12.0 both republished 475 sessions / 220
|
|
1348
|
+
// learnings from a release worktree), so it is refused. Only the real site/
|
|
1349
|
+
// is guarded; a --write --site <fixture> run publishes nothing. counted-sha
|
|
1350
|
+
// keeps its snapshot answer — the tested no-.git contract below.
|
|
1351
|
+
const writesServedSite = args.write && resolve(siteDir) === resolve(join(root, 'site'));
|
|
1352
|
+
const staleLedgers = localFallbacks.filter((id) => LEDGER_METRICS.includes(id));
|
|
1353
|
+
if (writesServedSite && staleLedgers.length > 0) {
|
|
1354
|
+
stderr(
|
|
1355
|
+
`Error: ${staleLedgers.join(', ')} not measurable under ${root} — the gitignored ledgers are absent ` +
|
|
1356
|
+
`here and in the main checkout; refusing to republish the old ${CENSUS_FILE.join('/')} values. ` +
|
|
1357
|
+
'Run --write from a checkout that has them.',
|
|
1358
|
+
);
|
|
1359
|
+
return 2;
|
|
1360
|
+
}
|
|
1318
1361
|
for (const w of warnings) stderr(`WARN: ${w}`);
|
|
1319
1362
|
|
|
1320
1363
|
const dirty = isDirty(root);
|
|
@@ -1489,7 +1532,7 @@ export function main(argv = process.argv.slice(2), env = {}) {
|
|
|
1489
1532
|
// release preflight's clean-tree check. A fixture run is a read of the repo,
|
|
1490
1533
|
// never a write to it.
|
|
1491
1534
|
let censusWritten = false;
|
|
1492
|
-
const writesCensus =
|
|
1535
|
+
const writesCensus = writesServedSite;
|
|
1493
1536
|
if (writesCensus && ok) {
|
|
1494
1537
|
const res = writeCensusSnapshot(root, values);
|
|
1495
1538
|
if (res.ok) {
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* snapshot-probe.mjs — run validate-plugin and the test suite on the curated public
|
|
4
|
+
* snapshot tree, materialized outside the repo without a remote (#1712).
|
|
5
|
+
* Library: scripts/lib/snapshot-probe.mjs.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { readFileSync } from 'node:fs';
|
|
9
|
+
import { dirname, join } from 'node:path';
|
|
10
|
+
import { fileURLToPath } from 'node:url';
|
|
11
|
+
import { parseArgs } from 'node:util';
|
|
12
|
+
|
|
13
|
+
import { isMainModule } from './lib/is-main-module.mjs';
|
|
14
|
+
import { KNOWN_STEPS, SnapshotProbeSystemError, runSnapshotProbe } from './lib/snapshot-probe.mjs';
|
|
15
|
+
|
|
16
|
+
const REPO_ROOT = join(dirname(fileURLToPath(import.meta.url)), '..');
|
|
17
|
+
|
|
18
|
+
export const HELP = `Usage: node scripts/snapshot-probe.mjs [options]
|
|
19
|
+
|
|
20
|
+
Builds the curated public GitHub snapshot of a source commit (the tree minus the paths in
|
|
21
|
+
.orchestrator/policy/public-export.json — the same helper the export uses), materializes it
|
|
22
|
+
under $TMPDIR as a one-commit git repo WITHOUT any remote, and runs the steps there. Catches
|
|
23
|
+
tests and validators that depend on a file the public export drops.
|
|
24
|
+
|
|
25
|
+
Options:
|
|
26
|
+
--source <rev> source commit (default HEAD)
|
|
27
|
+
--steps <list> comma-separated steps, from: ${KNOWN_STEPS.join(', ')} (default: both)
|
|
28
|
+
--host <alias> run the 'test' step on an offload host instead: one 'remote-gate' step
|
|
29
|
+
(the pre-push router's full gate against the snapshot commit, no gate
|
|
30
|
+
telemetry); validate-plugin still runs locally
|
|
31
|
+
--local run the 'test' step (full suite) on this host instead
|
|
32
|
+
The 'test' step needs exactly one of --host or --local.
|
|
33
|
+
--keep keep the materialized snapshot directory (printed as dir)
|
|
34
|
+
--no-receipt do not write .orchestrator/tmp/snapshot-probe/<tree>.json
|
|
35
|
+
--json print one JSON object instead of the human summary (remote gate
|
|
36
|
+
output goes to stderr)
|
|
37
|
+
--version print the plugin version
|
|
38
|
+
-h, --help show this help
|
|
39
|
+
|
|
40
|
+
Exit codes:
|
|
41
|
+
0 every step passed
|
|
42
|
+
1 usage error
|
|
43
|
+
2 system error: git, tar or mkdtemp failure, or the remote gate did not run the suite
|
|
44
|
+
to completion (host unknown or not ready, fallback, gate-timeout, a timed-out check,
|
|
45
|
+
no test result) — no receipt is written
|
|
46
|
+
3 the probe ran and the snapshot is red (a step failed or timed out)
|
|
47
|
+
|
|
48
|
+
A receipt is written only when a suite step ran ('test' or 'remote-gate'); a
|
|
49
|
+
--steps validate-plugin run prints its result and leaves any existing receipt untouched.
|
|
50
|
+
The release preflight accepts only receipts whose steps include a passing suite step:
|
|
51
|
+
'test' (--local) or 'remote-gate' (--host).
|
|
52
|
+
|
|
53
|
+
Examples:
|
|
54
|
+
node scripts/snapshot-probe.mjs --steps validate-plugin
|
|
55
|
+
node scripts/snapshot-probe.mjs --host m5 --json
|
|
56
|
+
node scripts/snapshot-probe.mjs --local --json
|
|
57
|
+
`;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Parse argv into probe options.
|
|
61
|
+
* @param {string[]} argv
|
|
62
|
+
* @returns {{ ok: true, opts: object } | { ok: false, error: string } | { ok: true, help: true } | { ok: true, version: true }}
|
|
63
|
+
*/
|
|
64
|
+
export function parseCli(argv) {
|
|
65
|
+
let values;
|
|
66
|
+
try {
|
|
67
|
+
({ values } = parseArgs({
|
|
68
|
+
args: argv,
|
|
69
|
+
strict: true,
|
|
70
|
+
allowPositionals: false,
|
|
71
|
+
options: {
|
|
72
|
+
source: { type: 'string', default: 'HEAD' },
|
|
73
|
+
steps: { type: 'string' },
|
|
74
|
+
local: { type: 'boolean', default: false },
|
|
75
|
+
host: { type: 'string' },
|
|
76
|
+
keep: { type: 'boolean', default: false },
|
|
77
|
+
'no-receipt': { type: 'boolean', default: false },
|
|
78
|
+
json: { type: 'boolean', default: false },
|
|
79
|
+
version: { type: 'boolean', default: false },
|
|
80
|
+
help: { type: 'boolean', short: 'h', default: false },
|
|
81
|
+
},
|
|
82
|
+
}));
|
|
83
|
+
} catch (err) {
|
|
84
|
+
return { ok: false, error: err.message };
|
|
85
|
+
}
|
|
86
|
+
if (values.help) return { ok: true, help: true };
|
|
87
|
+
if (values.version) return { ok: true, version: true };
|
|
88
|
+
const steps = values.steps === undefined ? [...KNOWN_STEPS] : values.steps.split(',').map((s) => s.trim()).filter(Boolean);
|
|
89
|
+
if (steps.length === 0) return { ok: false, error: '--steps is empty' };
|
|
90
|
+
const unknown = steps.filter((s) => !KNOWN_STEPS.includes(s));
|
|
91
|
+
if (unknown.length > 0) return { ok: false, error: `unknown step(s): ${unknown.join(', ')} (known: ${KNOWN_STEPS.join(', ')})` };
|
|
92
|
+
if (values.local && values.host !== undefined) return { ok: false, error: '--host and --local are mutually exclusive' };
|
|
93
|
+
if (values.host !== undefined && !/^[A-Za-z0-9._-]+$/.test(values.host)) return { ok: false, error: `invalid --host alias '${values.host}'` };
|
|
94
|
+
// Host rule: full suites belong on a remote host; the local host only with --local.
|
|
95
|
+
if (steps.includes('test') && !values.local && values.host === undefined) {
|
|
96
|
+
return { ok: false, error: "the 'test' step runs the full suite — pass --host <alias> or --local, or use --steps validate-plugin" };
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
ok: true,
|
|
100
|
+
opts: { source: values.source, steps, keep: values.keep, writeReceipt: !values['no-receipt'], json: values.json, ...(values.host !== undefined ? { host: values.host } : {}) },
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function humanSummary(r) {
|
|
105
|
+
const lines = [
|
|
106
|
+
`snapshot probe ${r.ok ? 'GREEN' : 'RED'} — source ${r.sourceSha.slice(0, 12)}, tree ${r.snapshotTree.slice(0, 12)} (route ${r.route})`,
|
|
107
|
+
`files: ${r.counts.source} source, ${r.counts.excluded} excluded, ${r.counts.exported} exported`,
|
|
108
|
+
];
|
|
109
|
+
for (const s of r.steps) {
|
|
110
|
+
lines.push(` ${s.name}: ${s.status} (exit ${s.exitCode}, ${Math.round(s.durationMs / 1000)}s)`);
|
|
111
|
+
if (s.reason) lines.push(` ${s.reason}`);
|
|
112
|
+
if (s.failedFiles) for (const f of s.failedFiles) lines.push(` FAIL ${f}`);
|
|
113
|
+
if (s.tail) lines.push(...s.tail.split('\n').map((l) => ` | ${l}`));
|
|
114
|
+
}
|
|
115
|
+
if (r.dir) lines.push(`kept: ${r.dir}`);
|
|
116
|
+
return lines.join('\n');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* @param {string[]} argv
|
|
121
|
+
* @param {{ repoRoot?: string, probe?: typeof runSnapshotProbe }} [deps]
|
|
122
|
+
* @returns {Promise<number>} exit code
|
|
123
|
+
*/
|
|
124
|
+
export async function main(argv, { repoRoot = REPO_ROOT, probe = runSnapshotProbe } = {}) {
|
|
125
|
+
const parsed = parseCli(argv);
|
|
126
|
+
if (!parsed.ok) {
|
|
127
|
+
process.stderr.write(`snapshot-probe: ${parsed.error}\n\n${HELP}`);
|
|
128
|
+
return 1;
|
|
129
|
+
}
|
|
130
|
+
if (parsed.help) {
|
|
131
|
+
process.stdout.write(HELP);
|
|
132
|
+
return 0;
|
|
133
|
+
}
|
|
134
|
+
if (parsed.version) {
|
|
135
|
+
process.stdout.write(`${JSON.parse(readFileSync(join(repoRoot, 'package.json'), 'utf8')).version}\n`);
|
|
136
|
+
return 0;
|
|
137
|
+
}
|
|
138
|
+
const { json, ...opts } = parsed.opts;
|
|
139
|
+
let result;
|
|
140
|
+
try {
|
|
141
|
+
result = await probe({ repoRoot, ...opts });
|
|
142
|
+
} catch (err) {
|
|
143
|
+
const message = err instanceof SnapshotProbeSystemError ? err.message : `unexpected failure: ${err?.message ?? err}`;
|
|
144
|
+
if (json) process.stdout.write(`${JSON.stringify({ ok: false, error: message })}\n`);
|
|
145
|
+
process.stderr.write(`snapshot-probe: ${message}\n`);
|
|
146
|
+
return 2;
|
|
147
|
+
}
|
|
148
|
+
process.stdout.write(json ? `${JSON.stringify(result)}\n` : `${humanSummary(result)}\n`);
|
|
149
|
+
return result.ok ? 0 : 3;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (isMainModule(import.meta.url)) {
|
|
153
|
+
main(process.argv.slice(2)).then((code) => {
|
|
154
|
+
process.exitCode = code;
|
|
155
|
+
});
|
|
156
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { execFile } from 'node:child_process';
|
|
7
|
+
import { promisify, parseArgs } from 'node:util';
|
|
8
|
+
import { measure } from './lib/so-hud/runtime.mjs';
|
|
9
|
+
import { isMainModule } from './lib/is-main-module.mjs';
|
|
10
|
+
import { parseSessionId } from './lib/session-id.mjs';
|
|
11
|
+
const exec = promisify(execFile);
|
|
12
|
+
const SO_ROOT = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
|
|
13
|
+
|
|
14
|
+
export async function main(argv, deps = {}) {
|
|
15
|
+
const output = deps.output ?? (s => process.stdout.write(s));
|
|
16
|
+
const error = deps.error ?? (s => process.stderr.write(s));
|
|
17
|
+
let values;
|
|
18
|
+
try {
|
|
19
|
+
({ values } = parseArgs({ args: argv, options: { json: { type: 'boolean' }, 'repo-root': { type: 'string' }, 'session-id': { type: 'string' }, 'register-root': { type: 'boolean' }, help: { type: 'boolean' } }, allowPositionals: false }));
|
|
20
|
+
if (values.help) {
|
|
21
|
+
output('so-hud --json [--repo-root <path>] [--session-id <raw-uuid>] [--register-root]\nRead-only HUD JSON v1. --register-root registers this SO installation (0600).\nDefault repo: git toplevel of cwd. --session-id: the session whose agents are counted, read beside its own session.lock (#1730); without it the id comes from CLAUDE_SESSION_ID/SO_SESSION_ID and agents are read in the repo.\nExit 0: measured; 2: invocation/registration error; 6: repository unavailable.\n');
|
|
22
|
+
return 0;
|
|
23
|
+
}
|
|
24
|
+
if (!values.json && !values['register-root']) throw new Error('--json required');
|
|
25
|
+
if (values['session-id'] !== undefined && parseSessionId(values['session-id'])?.format !== 'uuid') throw new Error('bad --session-id');
|
|
26
|
+
} catch (e) {
|
|
27
|
+
error(e?.message === 'bad --session-id' ? 'so-hud: --session-id braucht eine Roh-UUID; siehe --help\n' : 'so-hud: --json erforderlich; siehe --help\n');
|
|
28
|
+
return 2;
|
|
29
|
+
}
|
|
30
|
+
if (values['register-root']) {
|
|
31
|
+
try {
|
|
32
|
+
const dir = path.join(deps.home ?? os.homedir(), '.config/session-orchestrator/hud');
|
|
33
|
+
await fs.mkdir(dir, { recursive: true, mode: 0o700 });
|
|
34
|
+
await fs.chmod(dir, 0o700);
|
|
35
|
+
const target = path.join(dir, 'so-root');
|
|
36
|
+
const temp = `${target}.${process.pid}.tmp`;
|
|
37
|
+
try { await fs.writeFile(temp, `${SO_ROOT}\n`, { mode: 0o600, flag: 'wx' }); await fs.rename(temp, target); }
|
|
38
|
+
finally { await fs.unlink(temp).catch(() => {}); }
|
|
39
|
+
} catch { error('so-hud: Registrierung fehlgeschlagen\n'); return 2; }
|
|
40
|
+
if (!values.json) return 0;
|
|
41
|
+
}
|
|
42
|
+
let root;
|
|
43
|
+
try {
|
|
44
|
+
// A session started in a subdirectory measures its working copy: the git toplevel. Not a repo → the given
|
|
45
|
+
// directory itself (never fail the measurement).
|
|
46
|
+
const given = values['repo-root'] ? path.resolve(values['repo-root']) : null;
|
|
47
|
+
const top = await (deps.exec ?? exec)('git', [...(given ? ['-C', given] : []), 'rev-parse', '--show-toplevel'], { timeout: 700 })
|
|
48
|
+
.then(r => r.stdout.trim(), () => '');
|
|
49
|
+
if (!top && !given) throw new Error('missing');
|
|
50
|
+
root = await fs.realpath(top || given);
|
|
51
|
+
// git answers in physical spelling, but the session writes file paths in the spelling it was given, and the
|
|
52
|
+
// collision guard compares those as strings: keep the caller's spelling of the toplevel where it is derivable.
|
|
53
|
+
// Peers are compared physically in collect.mjs. The caller's spelling is used only when it resolves to the
|
|
54
|
+
// physical toplevel: a symlinked last path part (<d>/elsewhere/scripts → <d>/repo/scripts) would otherwise
|
|
55
|
+
// map to <d>/elsewhere, which is not the repository.
|
|
56
|
+
if (given) {
|
|
57
|
+
const sub = path.relative(root, await fs.realpath(given));
|
|
58
|
+
let candidate = null;
|
|
59
|
+
if (sub === '') candidate = given;
|
|
60
|
+
else if (sub !== '..' && !sub.startsWith(`..${path.sep}`) && given.endsWith(`${path.sep}${sub}`)) candidate = given.slice(0, -sub.length - 1);
|
|
61
|
+
if (candidate !== null && await fs.realpath(candidate).catch(() => null) === root) root = candidate;
|
|
62
|
+
}
|
|
63
|
+
if (!(await fs.stat(root)).isDirectory()) throw new Error('missing');
|
|
64
|
+
} catch { error('so-hud: Repo nicht ermittelbar\n'); return 6; }
|
|
65
|
+
try { output(`${JSON.stringify(await (deps.measure ?? measure)(root, { sessionId: values['session-id'] ?? null }))}\n`); return 0; }
|
|
66
|
+
catch { error('so-hud: Messung fehlgeschlagen\n'); return 6; }
|
|
67
|
+
}
|
|
68
|
+
if (isMainModule(import.meta.url)) process.exitCode = await main(process.argv.slice(2));
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/** session-start Phase 1.5 Idle Reset (#1560): rewrite a completed STATE.md to idle, under the STATE.md lock. */
|
|
3
|
+
import { readFileSync, statSync } from 'node:fs';
|
|
4
|
+
import { resolve } from 'node:path';
|
|
5
|
+
import { parseArgs } from 'node:util';
|
|
6
|
+
import { resetStateMdToIdleOnDisk } from './lib/state-md/idle-reset.mjs';
|
|
7
|
+
|
|
8
|
+
const HELP = `Usage: node scripts/state-md-idle-reset.mjs [--repo-root <dir>] [--json]
|
|
9
|
+
|
|
10
|
+
Reset the repo's STATE.md to idle when its status is completed (session-start
|
|
11
|
+
Phase 1.5, Idle Reset rules 1-7): status idle, current-wave 0, Wave History
|
|
12
|
+
demoted into Previous Session, Deviations emptied, scope-baseline-* keys
|
|
13
|
+
deleted. The five recommendation keys stay in the frontmatter so the Phase 7.5
|
|
14
|
+
Mode-Selector can read them. Any other status is a no-op; reasons:
|
|
15
|
+
status-not-completed, unparseable, missing (no or empty STATE.md),
|
|
16
|
+
foreign-state-md (only another platform's foreign STATE.md exists, #1618).
|
|
17
|
+
|
|
18
|
+
--repo-root <dir> Repository root holding the state dir (default: cwd)
|
|
19
|
+
--json Emit {"changed", "reason", "path"} instead of one line
|
|
20
|
+
--help Show this help
|
|
21
|
+
--version Show the plugin version
|
|
22
|
+
|
|
23
|
+
Example: node scripts/state-md-idle-reset.mjs --json
|
|
24
|
+
Exit codes: 0 reset or no-op, 1 bad CLI arguments,
|
|
25
|
+
2 I/O error, --repo-root not a directory, or a write the
|
|
26
|
+
STATE.md guards refused
|
|
27
|
+
`;
|
|
28
|
+
|
|
29
|
+
/** Reasons under which writeStateMd refused a wanted write. */
|
|
30
|
+
const REFUSED = new Set(['size-ceiling', 'frontmatter-unsafe']);
|
|
31
|
+
|
|
32
|
+
let options;
|
|
33
|
+
try {
|
|
34
|
+
options = parseArgs({
|
|
35
|
+
options: {
|
|
36
|
+
'repo-root': { type: 'string' },
|
|
37
|
+
json: { type: 'boolean' },
|
|
38
|
+
help: { type: 'boolean' },
|
|
39
|
+
version: { type: 'boolean' },
|
|
40
|
+
},
|
|
41
|
+
allowPositionals: false,
|
|
42
|
+
}).values;
|
|
43
|
+
} catch (error) {
|
|
44
|
+
process.stderr.write(`state-md-idle-reset: ${error.message}\n`);
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (options.help) {
|
|
49
|
+
process.stdout.write(HELP);
|
|
50
|
+
} else if (options.version) {
|
|
51
|
+
process.stdout.write(
|
|
52
|
+
`${JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version}\n`,
|
|
53
|
+
);
|
|
54
|
+
} else {
|
|
55
|
+
const repoRoot = resolve(options['repo-root'] ?? process.cwd());
|
|
56
|
+
// The STATE.md lock would otherwise create <repoRoot>/.orchestrator/ for a
|
|
57
|
+
// root that does not exist and report a harmless-looking `missing`.
|
|
58
|
+
let rootStat;
|
|
59
|
+
try {
|
|
60
|
+
rootStat = statSync(repoRoot, { throwIfNoEntry: false });
|
|
61
|
+
} catch {
|
|
62
|
+
rootStat = undefined; // EACCES / ELOOP: an I/O error, exit 2 below
|
|
63
|
+
}
|
|
64
|
+
if (!rootStat?.isDirectory()) {
|
|
65
|
+
process.stderr.write(`state-md-idle-reset: --repo-root is not a directory: ${repoRoot}\n`);
|
|
66
|
+
process.exit(2);
|
|
67
|
+
}
|
|
68
|
+
let result;
|
|
69
|
+
try {
|
|
70
|
+
result = await resetStateMdToIdleOnDisk(repoRoot);
|
|
71
|
+
} catch (error) {
|
|
72
|
+
process.stderr.write(`state-md-idle-reset: ${error.message}\n`);
|
|
73
|
+
process.exit(2);
|
|
74
|
+
}
|
|
75
|
+
const reason = result.reason ?? null;
|
|
76
|
+
process.stdout.write(
|
|
77
|
+
options.json
|
|
78
|
+
? `${JSON.stringify({ changed: result.changed, reason, path: result.path })}\n`
|
|
79
|
+
: `${result.changed ? 'reset to idle' : `unchanged (${reason})`}: ${result.path}\n`,
|
|
80
|
+
);
|
|
81
|
+
if (REFUSED.has(reason)) process.exit(2);
|
|
82
|
+
}
|