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
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
|
+
}
|
package/scripts/so-hud.mjs
CHANGED
|
@@ -7,6 +7,7 @@ import { execFile } from 'node:child_process';
|
|
|
7
7
|
import { promisify, parseArgs } from 'node:util';
|
|
8
8
|
import { measure } from './lib/so-hud/runtime.mjs';
|
|
9
9
|
import { isMainModule } from './lib/is-main-module.mjs';
|
|
10
|
+
import { parseSessionId } from './lib/session-id.mjs';
|
|
10
11
|
const exec = promisify(execFile);
|
|
11
12
|
const SO_ROOT = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
|
|
12
13
|
|
|
@@ -15,13 +16,17 @@ export async function main(argv, deps = {}) {
|
|
|
15
16
|
const error = deps.error ?? (s => process.stderr.write(s));
|
|
16
17
|
let values;
|
|
17
18
|
try {
|
|
18
|
-
({ values } = parseArgs({ args: argv, options: { json: { type: 'boolean' }, 'repo-root': { type: 'string' }, 'register-root': { type: 'boolean' }, help: { type: 'boolean' } }, allowPositionals: false }));
|
|
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 }));
|
|
19
20
|
if (values.help) {
|
|
20
|
-
output('so-hud --json [--repo-root <path>] [--register-root]\nRead-only HUD JSON v1. --register-root registers this SO installation (0600).\nDefault repo: git toplevel of cwd.
|
|
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');
|
|
21
22
|
return 0;
|
|
22
23
|
}
|
|
23
24
|
if (!values.json && !values['register-root']) throw new Error('--json required');
|
|
24
|
-
|
|
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
|
+
}
|
|
25
30
|
if (values['register-root']) {
|
|
26
31
|
try {
|
|
27
32
|
const dir = path.join(deps.home ?? os.homedir(), '.config/session-orchestrator/hud');
|
|
@@ -36,10 +41,28 @@ export async function main(argv, deps = {}) {
|
|
|
36
41
|
}
|
|
37
42
|
let root;
|
|
38
43
|
try {
|
|
39
|
-
|
|
40
|
-
|
|
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');
|
|
41
64
|
} catch { error('so-hud: Repo nicht ermittelbar\n'); return 6; }
|
|
42
|
-
try { output(`${JSON.stringify(await (deps.measure ?? measure)(root))}\n`); return 0; }
|
|
65
|
+
try { output(`${JSON.stringify(await (deps.measure ?? measure)(root, { sessionId: values['session-id'] ?? null }))}\n`); return 0; }
|
|
43
66
|
catch { error('so-hud: Messung fehlgeschlagen\n'); return 6; }
|
|
44
67
|
}
|
|
45
68
|
if (isMainModule(import.meta.url)) process.exitCode = await main(process.argv.slice(2));
|
|
@@ -12,7 +12,8 @@ Phase 1.5, Idle Reset rules 1-7): status idle, current-wave 0, Wave History
|
|
|
12
12
|
demoted into Previous Session, Deviations emptied, scope-baseline-* keys
|
|
13
13
|
deleted. The five recommendation keys stay in the frontmatter so the Phase 7.5
|
|
14
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)
|
|
15
|
+
status-not-completed, unparseable, missing (no or empty STATE.md),
|
|
16
|
+
foreign-state-md (only another platform's foreign STATE.md exists, #1618).
|
|
16
17
|
|
|
17
18
|
--repo-root <dir> Repository root holding the state dir (default: cwd)
|
|
18
19
|
--json Emit {"changed", "reason", "path"} instead of one line
|
|
@@ -139,6 +139,7 @@ import { atomicWriteText, envelopeToError } from './lib/io.mjs';
|
|
|
139
139
|
import { validateLearning } from './lib/learnings/schema.mjs';
|
|
140
140
|
import { validatePathInsideProject } from './lib/path-utils.mjs';
|
|
141
141
|
import { emitEvolveCompleted } from './lib/learnings/evolve-telemetry.mjs';
|
|
142
|
+
import { resolveLedgerPath, resolveLedgerRoot } from './lib/ledger-path.mjs';
|
|
142
143
|
|
|
143
144
|
const DEFAULT_FILE = '.orchestrator/metrics/learnings.jsonl';
|
|
144
145
|
const DEFAULT_ARCHIVE = '.orchestrator/metrics/learnings-archive.jsonl';
|
|
@@ -257,6 +258,7 @@ function parseArgs(argv) {
|
|
|
257
258
|
generation: null,
|
|
258
259
|
acceptEmbedded: false,
|
|
259
260
|
};
|
|
261
|
+
let fileExplicit = false;
|
|
260
262
|
for (let i = 0; i < argv.length; i++) {
|
|
261
263
|
const a = argv[i];
|
|
262
264
|
if (a === '--prune') {
|
|
@@ -296,6 +298,7 @@ function parseArgs(argv) {
|
|
|
296
298
|
args.snapshot = pathValue(a, argv[++i]);
|
|
297
299
|
} else if (a === '--file') {
|
|
298
300
|
args.file = pathValue(a, argv[++i]);
|
|
301
|
+
fileExplicit = true;
|
|
299
302
|
} else if (a === '--archive') {
|
|
300
303
|
args.archive = pathValue(a, argv[++i]);
|
|
301
304
|
args.archiveExplicit = true;
|
|
@@ -343,6 +346,12 @@ function parseArgs(argv) {
|
|
|
343
346
|
}
|
|
344
347
|
}
|
|
345
348
|
|
|
349
|
+
// #1722: the DEFAULT store/archive route through the ledger resolver (main
|
|
350
|
+
// checkout when cwd is a linked worktree that gitignores them); outside one
|
|
351
|
+
// the relative defaults are kept verbatim. Explicit --file/--archive stay.
|
|
352
|
+
if (!fileExplicit) args.file = routedDefault(DEFAULT_FILE);
|
|
353
|
+
if (!args.archiveExplicit) args.archive = routedDefault(DEFAULT_ARCHIVE);
|
|
354
|
+
|
|
346
355
|
// Mode/flag mismatches are usage errors, never silent no-ops: a `--grace-days`
|
|
347
356
|
// that the prune path ignores would read as "the grace window applied" in a
|
|
348
357
|
// transcript, and an `--entries` the sweep ignores would read as "my next
|
|
@@ -381,7 +390,7 @@ function parseArgs(argv) {
|
|
|
381
390
|
// (macOS /var → /private/var) refused a store inside it. The file goes
|
|
382
391
|
// through resolveLedger (realpath when it exists), because
|
|
383
392
|
// canonicalizeRoot realpaths only the root.
|
|
384
|
-
if (
|
|
393
|
+
if (ledgerRootOf(args.file, args.repoRoot) === null) {
|
|
385
394
|
usageError(`--file ${args.file} lies outside --repo-root ${args.repoRoot} — nothing written`);
|
|
386
395
|
}
|
|
387
396
|
}
|
|
@@ -441,7 +450,9 @@ async function runSweep(args) {
|
|
|
441
450
|
} else {
|
|
442
451
|
process.stdout.write(
|
|
443
452
|
`sweep-expired-learnings: scanned=${summary.scanned} kept=${summary.kept} ` +
|
|
444
|
-
`archived=${summary.archived} dry_run=${summary.dryRun} archive=${summary.archivePath}
|
|
453
|
+
`archived=${summary.archived} dry_run=${summary.dryRun} archive=${summary.archivePath}` +
|
|
454
|
+
// An inbox-role apply (#1516) wrote nothing; say so, as --json does.
|
|
455
|
+
`${summary.skipped ? ` skipped=${summary.skipped}` : ''}\n`
|
|
445
456
|
);
|
|
446
457
|
}
|
|
447
458
|
}
|
|
@@ -609,6 +620,39 @@ function resolveLedger(p) {
|
|
|
609
620
|
}
|
|
610
621
|
}
|
|
611
622
|
|
|
623
|
+
/**
|
|
624
|
+
* The default ledger for this cwd (#1722): `rel` verbatim unless the resolver
|
|
625
|
+
* maps it elsewhere (linked worktree that gitignores it), then that absolute path.
|
|
626
|
+
*
|
|
627
|
+
* @param {string} rel
|
|
628
|
+
* @returns {string}
|
|
629
|
+
*/
|
|
630
|
+
function routedDefault(rel) {
|
|
631
|
+
const routed = resolveLedgerPath(rel, process.cwd());
|
|
632
|
+
return routed === path.resolve(rel) ? rel : routed;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/**
|
|
636
|
+
* Which root a store lies inside, canonically (#1500 F4): `repoRoot` itself, or
|
|
637
|
+
* its ledger root (the main checkout of a linked worktree, #1722) — but only for
|
|
638
|
+
* a file inside the DIRECTORY of the mapped default store, never anywhere in
|
|
639
|
+
* that other checkout (a worktree session must not rewrite a foreign tree).
|
|
640
|
+
* `null` when it lies outside both.
|
|
641
|
+
*
|
|
642
|
+
* @param {string} file
|
|
643
|
+
* @param {string} repoRoot
|
|
644
|
+
* @returns {string|null}
|
|
645
|
+
*/
|
|
646
|
+
function ledgerRootOf(file, repoRoot) {
|
|
647
|
+
const abs = resolveLedger(file);
|
|
648
|
+
if (validatePathInsideProject(abs, repoRoot, { canonicalizeRoot: true }).ok) return repoRoot;
|
|
649
|
+
const storeDir = path.dirname(resolveLedgerPath(DEFAULT_FILE, repoRoot));
|
|
650
|
+
if (validatePathInsideProject(abs, storeDir, { canonicalizeRoot: true }).ok) {
|
|
651
|
+
return resolveLedgerRoot(DEFAULT_FILE, repoRoot);
|
|
652
|
+
}
|
|
653
|
+
return null;
|
|
654
|
+
}
|
|
655
|
+
|
|
612
656
|
/**
|
|
613
657
|
* Exit 1 when `--snapshot` would replace the store or the archive. The rename
|
|
614
658
|
* lands the header as the store's line 1 with no `.bak` (and drops malformed
|
|
@@ -820,7 +864,9 @@ async function runPrune(args) {
|
|
|
820
864
|
// hand-assembled `--entries` sidecar) never wrote anything, so it must not
|
|
821
865
|
// report a completed run either. Best-effort — never changes this
|
|
822
866
|
// command's exit code or stdout contract.
|
|
823
|
-
|
|
867
|
+
// An inbox-role apply (#1516) returns `skipped`: it archived nothing and did
|
|
868
|
+
// not rewrite the store, so there is no completed store write to report.
|
|
869
|
+
if (!args.dryRun && !result.skipped) {
|
|
824
870
|
await emitEvolveCompleted({
|
|
825
871
|
repoRoot: args.repoRoot,
|
|
826
872
|
appended: args.appended,
|
|
@@ -841,7 +887,7 @@ async function runPrune(args) {
|
|
|
841
887
|
process.stdout.write(
|
|
842
888
|
`sweep-expired-learnings: prune scanned=${summary.scanned} kept=${summary.kept} ` +
|
|
843
889
|
`archived=${summary.archived} by_reason=${byReason} dry_run=${summary.dryRun} ` +
|
|
844
|
-
`archive=${summary.archivePath}\n`
|
|
890
|
+
`archive=${summary.archivePath}${summary.skipped ? ` skipped=${summary.skipped}` : ''}\n`
|
|
845
891
|
);
|
|
846
892
|
}
|
|
847
893
|
}
|
|
@@ -858,7 +904,7 @@ async function runPrune(args) {
|
|
|
858
904
|
async function emitMalformedDropped(args, result) {
|
|
859
905
|
// Both sides canonical, as the parseArgs check judged them: a lexical
|
|
860
906
|
// relative() across /var vs /private/var reads `../../private/...`.
|
|
861
|
-
const root = resolveLedger(args.repoRoot);
|
|
907
|
+
const root = resolveLedger(ledgerRootOf(args.file, args.repoRoot) ?? args.repoRoot);
|
|
862
908
|
const rel = (p) => path.relative(root, resolveLedger(p));
|
|
863
909
|
try {
|
|
864
910
|
const { emitEvent, sessionAttribution } = await import('./lib/events.mjs');
|
|
@@ -916,7 +962,9 @@ async function runDropMalformed(args) {
|
|
|
916
962
|
process.exit(2);
|
|
917
963
|
}
|
|
918
964
|
|
|
919
|
-
|
|
965
|
+
// An inbox-role host (#1516) returns the dry-run probe with `skipped` set:
|
|
966
|
+
// nothing was dropped, so no event may claim it was.
|
|
967
|
+
if (!args.dryRun && !result.skipped) await emitMalformedDropped(args, result);
|
|
920
968
|
|
|
921
969
|
const summary = {
|
|
922
970
|
file: args.file,
|
|
@@ -105,6 +105,7 @@ export function parseArgs(argv) {
|
|
|
105
105
|
} else if (a === '--learnings') {
|
|
106
106
|
const raw = argv[++i];
|
|
107
107
|
if (typeof raw !== 'string' || raw.length === 0) usageError('--learnings requires a path');
|
|
108
|
+
if (path.isAbsolute(raw)) usageError('--learnings requires a repo-relative path');
|
|
108
109
|
args.learnings = raw;
|
|
109
110
|
} else if (a === '--help' || a === '-h') {
|
|
110
111
|
printHelp();
|
|
@@ -140,6 +141,7 @@ export function summarize(plan, args, applied) {
|
|
|
140
141
|
expired_entries: plan.plans.reduce((n, p) => n + p.expiredPairIds.length, 0),
|
|
141
142
|
unresolved_pairs: plan.plans.reduce((n, p) => n + p.unresolvedPairIds.length, 0),
|
|
142
143
|
malformed_lines: plan.malformedLines,
|
|
144
|
+
archive_malformed_lines: plan.archiveMalformedLines,
|
|
143
145
|
rules_dir_readable: plan.ok,
|
|
144
146
|
skipped: plan.skipped,
|
|
145
147
|
plans: plan.plans.map(({ nextContent: _nextContent, ...rest }) => rest),
|
|
@@ -178,7 +180,8 @@ async function main(argv) {
|
|
|
178
180
|
`(${summary.header_raises} header-raise(s)), ` +
|
|
179
181
|
`${summary.deletes} delete(s), ${summary.expired_entries} expired entr(ies), ` +
|
|
180
182
|
`${summary.unresolved_pairs} unresolved pair(s), ${summary.skipped.length} skipped, ` +
|
|
181
|
-
`${summary.malformed_lines} malformed learnings line(s)
|
|
183
|
+
`${summary.malformed_lines} malformed learnings line(s), ` +
|
|
184
|
+
`${summary.archive_malformed_lines} malformed archive line(s)\n`,
|
|
182
185
|
);
|
|
183
186
|
for (const s of plan.skipped) process.stdout.write(` skipped ${s.file}: ${s.reason}\n`);
|
|
184
187
|
for (const p of plan.plans) {
|
|
@@ -481,6 +481,11 @@ runCheck('check-learning-provenance.mjs');
|
|
|
481
481
|
process.stdout.write('\n');
|
|
482
482
|
runCheck('check-vcs-repo-flag.mjs');
|
|
483
483
|
|
|
484
|
+
// Separate advice contract (#1024): name the PR or drop a rejected repo flag.
|
|
485
|
+
// WARN-only, with tool errors surfaced through FAIL lines as in the bare census.
|
|
486
|
+
process.stdout.write('\n');
|
|
487
|
+
runCheck('check-vcs-repo-flag-positional.mjs');
|
|
488
|
+
|
|
484
489
|
// WARN-only (#1023): a `gh`/`glab` command cited in docs that no released CLI
|
|
485
490
|
// ever had — `glab repo edit --visibility`, `glab group list` — costs an
|
|
486
491
|
// operator a failed command and a re-derivation. The oracle is the CLI's own
|
|
@@ -539,6 +544,13 @@ if (runCheck('check-learnings-shell-writes.mjs') !== 0) checkFailed = 1;
|
|
|
539
544
|
process.stdout.write('\n');
|
|
540
545
|
runCheck('check-test-git-config-target.mjs');
|
|
541
546
|
|
|
547
|
+
// BLOCKING (#1613): a test that spawns `sh`/`bash` by bare name is red on native
|
|
548
|
+
// Windows (no sh on PATH, bash is WSL) — 301 red tests at fd8d3673. It may block
|
|
549
|
+
// because the backlog was drained in the same change: the live census leaves only
|
|
550
|
+
// three files in open MRs, baselined inside the checker as WARN.
|
|
551
|
+
process.stdout.write('\n');
|
|
552
|
+
if (runCheck('check-test-posix-shell.mjs') !== 0) checkFailed = 1;
|
|
553
|
+
|
|
542
554
|
// BLOCKING (#1183): every `emitEvent()` call site under hooks/**/*.mjs must be
|
|
543
555
|
// try/catch-guarded (emitEvent() throws EventValidationError on a malformed
|
|
544
556
|
// record — an unguarded call aborts the hook process). Five pre-existing
|
|
@@ -548,6 +560,16 @@ runCheck('check-test-git-config-target.mjs');
|
|
|
548
560
|
process.stdout.write('\n');
|
|
549
561
|
if (runCheck('check-hooks-emit-event-guard.mjs') !== 0) checkFailed = 1;
|
|
550
562
|
|
|
563
|
+
process.stdout.write('\n'); if (runCheck('check-workflow-continue-on-error.mjs') !== 0) checkFailed = 1;
|
|
564
|
+
|
|
565
|
+
// BLOCKING (#1722): a module under scripts/ or hooks/ that builds a
|
|
566
|
+
// `.orchestrator/metrics` path must route it through resolveLedgerPath()
|
|
567
|
+
// (events/sessions: resolveEventsLedgerRoot()). The ledgers are gitignored, so
|
|
568
|
+
// from a linked worktree an unrouted writer leaves a copy that is lost with the
|
|
569
|
+
// worktree. Deliberately local sites sit on the checker's own ALLOWLIST.
|
|
570
|
+
process.stdout.write('\n');
|
|
571
|
+
if (runCheck('check-metrics-ledger-root.mjs') !== 0) checkFailed = 1;
|
|
572
|
+
|
|
551
573
|
// BLOCKING (#1184): every scripts/lib/validate/check-*.mjs must be referenced
|
|
552
574
|
// by basename from this file, .husky/pre-commit, or .gitlab-ci.yml — or carry
|
|
553
575
|
// a `// registration: standalone <reason>` header marker declaring itself
|