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
package/scripts/lib/events.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* events.mjs — JSONL event emission + optional webhook POST for session-orchestrator.
|
|
3
3
|
*
|
|
4
4
|
* Replaces scripts/lib/events.sh. Windows-safe (no hardcoded path separators).
|
|
5
|
-
* Uses
|
|
5
|
+
* Uses Stdlib HTTP and fs.promises — no external dependencies.
|
|
6
6
|
*
|
|
7
7
|
* Part of v3.0.0 migration (Epic #124, issue #133).
|
|
8
8
|
* Issue #228: removed hardcoded personal-domain default URL. Clank Event Bus URL
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
* 0.0961 ms/call.
|
|
40
40
|
*/
|
|
41
41
|
|
|
42
|
+
import { httpRequest } from './http-request.mjs';
|
|
42
43
|
import {
|
|
43
44
|
promises as fs,
|
|
44
45
|
closeSync,
|
|
@@ -54,7 +55,7 @@ import { tmpdir } from 'node:os';
|
|
|
54
55
|
import path from 'node:path';
|
|
55
56
|
import { getProjectDir, resolveEventsLedgerRoot, SO_SHARED_DIR } from './platform.mjs';
|
|
56
57
|
import { readLock } from './session-lock.mjs';
|
|
57
|
-
import {
|
|
58
|
+
import { resolveStateMdWriteTarget } from './state-md/frontmatter-mutators.mjs';
|
|
58
59
|
import {
|
|
59
60
|
classifyManifestSession,
|
|
60
61
|
readProcessLocalSessionIds,
|
|
@@ -282,9 +283,12 @@ export function attributionForRecord(root = getProjectDir()) {
|
|
|
282
283
|
/**
|
|
283
284
|
* Absolute path of the active `wave-scope.json`, or `null` when none exists.
|
|
284
285
|
*
|
|
285
|
-
*
|
|
286
|
-
*
|
|
287
|
-
* Cursor run finds its own manifest rather than a stale `.claude/`
|
|
286
|
+
* This session's state dir is tried first (the dir of
|
|
287
|
+
* `resolveStateMdWriteTarget()`'s path), then the remaining candidates — so a
|
|
288
|
+
* Codex or Cursor run finds its own manifest rather than a stale `.claude/`
|
|
289
|
+
* one. #1636: the write target, not the first existing STATE.md, because a
|
|
290
|
+
* foreign STATE.md in another platform's dir would put that dir's (foreign)
|
|
291
|
+
* manifest first and hide this session's own wave.
|
|
288
292
|
*
|
|
289
293
|
* @param {string} root
|
|
290
294
|
* @returns {string|null}
|
|
@@ -292,7 +296,7 @@ export function attributionForRecord(root = getProjectDir()) {
|
|
|
292
296
|
function waveScopePath(root) {
|
|
293
297
|
const dirs = [];
|
|
294
298
|
try {
|
|
295
|
-
dirs.push(path.dirname(
|
|
299
|
+
dirs.push(path.dirname(resolveStateMdWriteTarget(root).path));
|
|
296
300
|
} catch {
|
|
297
301
|
/* fall through to the fixed candidate list */
|
|
298
302
|
}
|
|
@@ -364,8 +368,9 @@ function waveForRecord(root, attribution) {
|
|
|
364
368
|
*
|
|
365
369
|
* Writes `{timestamp, event, schema_version, ...payload}` as a single JSON line
|
|
366
370
|
* to `.orchestrator/metrics/events.jsonl` (creates parent directory if needed).
|
|
367
|
-
* If both `CLANK_EVENT_SECRET` and `CLANK_EVENT_URL` are set, fires
|
|
368
|
-
* fire-and-forget POST to `CLANK_EVENT_URL` with a
|
|
371
|
+
* If both `CLANK_EVENT_SECRET` and `CLANK_EVENT_URL` are set, fires a
|
|
372
|
+
* fire-and-forget POST (not awaited, #1612 R1-03) to `CLANK_EVENT_URL` with a
|
|
373
|
+
* 3-second timeout over node:https (`scripts/lib/http-request.mjs`). Network
|
|
369
374
|
* errors are swallowed. Write errors propagate to the caller. No personal-domain
|
|
370
375
|
* default URL exists — both vars must be set explicitly (#228).
|
|
371
376
|
*
|
|
@@ -476,7 +481,7 @@ export async function emitEvent(type, payload = {}, opts = {}) {
|
|
|
476
481
|
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
477
482
|
await fs.appendFile(filePath, line, 'utf8');
|
|
478
483
|
|
|
479
|
-
//
|
|
484
|
+
// R1-03: do not await: multiple events per hook × 3s exceed the 5s hook budget.
|
|
480
485
|
// No personal-domain default: CLANK_EVENT_URL must be set explicitly (#228).
|
|
481
486
|
if (process.env.CLANK_EVENT_SECRET && process.env.CLANK_EVENT_URL) {
|
|
482
487
|
const url = process.env.CLANK_EVENT_URL;
|
|
@@ -486,14 +491,15 @@ export async function emitEvent(type, payload = {}, opts = {}) {
|
|
|
486
491
|
payload,
|
|
487
492
|
});
|
|
488
493
|
|
|
489
|
-
|
|
494
|
+
void httpRequest(`${url}/api/webhooks/events`, {
|
|
490
495
|
method: 'POST',
|
|
491
496
|
headers: {
|
|
492
497
|
'Content-Type': 'application/json',
|
|
493
498
|
'Authorization': `Bearer ${process.env.CLANK_EVENT_SECRET}`,
|
|
494
499
|
},
|
|
495
500
|
body,
|
|
496
|
-
|
|
501
|
+
timeoutMs: 3000,
|
|
502
|
+
maxBytes: 64 * 1024,
|
|
497
503
|
}).catch(() => {});
|
|
498
504
|
}
|
|
499
505
|
}
|
|
@@ -948,7 +954,12 @@ export function scanEventsBackwards(opts = {}) {
|
|
|
948
954
|
let fd;
|
|
949
955
|
try {
|
|
950
956
|
fd = openSync(source.path, 'r');
|
|
951
|
-
|
|
957
|
+
const stats = fstatSync(fd);
|
|
958
|
+
// Windows opens a directory "as a file" with size 0; without this it
|
|
959
|
+
// would read as an EMPTY source instead of an unreadable one (POSIX
|
|
960
|
+
// throws EISDIR on the first read).
|
|
961
|
+
if (!stats.isFile()) throw new Error(`not a regular file: ${source.path}`);
|
|
962
|
+
let pos = stats.size;
|
|
952
963
|
/** Partial line at the FRONT of everything read from THIS source so far. */
|
|
953
964
|
let carry = Buffer.alloc(0);
|
|
954
965
|
while (pos > 0) {
|
|
@@ -223,15 +223,10 @@ function readinessConfidence(autopilotSummary, judgmentSummary, score) {
|
|
|
223
223
|
/**
|
|
224
224
|
* Summarize autopilot run history plus type-8 mode effectiveness rollups.
|
|
225
225
|
*
|
|
226
|
-
*
|
|
227
|
-
*
|
|
228
|
-
*
|
|
229
|
-
*
|
|
230
|
-
* guarantees transitively and duplicates neither. The abandoned filter alone
|
|
231
|
-
* was not enough: a `supersedes` pair and an exact same-id duplicate are two
|
|
232
|
-
* records of one session that both survive it. See
|
|
233
|
-
* `autopilot-effectiveness.mjs` `groupByMode()` (which canonicalizes via
|
|
234
|
-
* `canonicalizeSessions(sessions, { keepUnidentified: true })` before filtering).
|
|
226
|
+
* Measured-work filtering (#1584) AND duplicate-identity collapse (#1167):
|
|
227
|
+
* sessions pass through groupByMode(), which canonicalizes physical identities
|
|
228
|
+
* (keeping ID-less legacy rows) and applies isMeasuredSession before bucketing.
|
|
229
|
+
* This function inherits both guarantees without a separate population filter.
|
|
235
230
|
*
|
|
236
231
|
* @param {Array} autopilotRuns
|
|
237
232
|
* @param {Array} sessions
|
|
@@ -26,8 +26,7 @@
|
|
|
26
26
|
|
|
27
27
|
import { randomUUID } from 'node:crypto';
|
|
28
28
|
|
|
29
|
-
import {
|
|
30
|
-
import { canonicalizeSessions } from '../sessions-canonical.mjs';
|
|
29
|
+
import { canonicalizeSessions, isMeasuredSession } from '../sessions-canonical.mjs';
|
|
31
30
|
|
|
32
31
|
// ---------------------------------------------------------------------------
|
|
33
32
|
// Constants
|
|
@@ -160,11 +159,9 @@ function mean(values) {
|
|
|
160
159
|
* whose autopilot_run_id matches a known run). For the skeleton it is used
|
|
161
160
|
* only as a non-emptiness signal.
|
|
162
161
|
*
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
* they must not inflate `n_manual`/`n_autopilot` or dilute the
|
|
167
|
-
* completion/carryover averages. See `scripts/lib/session-schema/filters.mjs`.
|
|
162
|
+
* Measured-session filtering (#1584): canonical identities are counted only
|
|
163
|
+
* when the shared isMeasuredSession predicate sees real work. Synthetic rows
|
|
164
|
+
* cannot inflate counts or dilute averages, even with completed status.
|
|
168
165
|
*
|
|
169
166
|
* Mode resolution (#834): `mode` is a LEGACY alias for the canonical
|
|
170
167
|
* `session_type` field (`session-schema/constants.mjs` `SESSION_KEY_ALIASES`).
|
|
@@ -182,23 +179,16 @@ export function groupByMode(autopilotRuns, sessions) {
|
|
|
182
179
|
const out = new Map();
|
|
183
180
|
if (!Array.isArray(sessions) || sessions.length === 0) return out;
|
|
184
181
|
|
|
185
|
-
// Canonicalize BEFORE
|
|
186
|
-
//
|
|
187
|
-
// real survive it — a `supersedes` pair (the backfilled stub plus the
|
|
188
|
-
// authoritative `completed` record that refutes it) and an exact same-id
|
|
189
|
-
// duplicate line both do. Each of those inflates `n_manual` / `n_autopilot`
|
|
190
|
-
// and skews every mean computed from the bucket, so the identity collapse
|
|
191
|
-
// has to happen first; the two compose in exactly this order (see
|
|
192
|
-
// `sessions-canonical.mjs` § "What this module does not do").
|
|
182
|
+
// Canonicalize BEFORE the shared measured-work filter (#1167, #1584):
|
|
183
|
+
// duplicate identities and supersedes pairs must not inflate bucket counts.
|
|
193
184
|
//
|
|
194
185
|
// `keepUnidentified: true` because effectiveness analysis must not LOSE the
|
|
195
186
|
// id-less rows `canonicalizeSessions` drops by default: legacy ledger rows
|
|
196
187
|
// and every in-repo fixture of the pre-id era carry `session_type` and
|
|
197
188
|
// metrics but no id, and dropping them would silently shrink `n_manual`
|
|
198
189
|
// instead of de-duplicating it.
|
|
199
|
-
const realSessions =
|
|
200
|
-
|
|
201
|
-
);
|
|
190
|
+
const realSessions = canonicalizeSessions(sessions, { keepUnidentified: true })
|
|
191
|
+
.filter(isMeasuredSession);
|
|
202
192
|
if (realSessions.length === 0) return out;
|
|
203
193
|
|
|
204
194
|
// Optional: known autopilot_run_id set for stricter pairing. Empty set means
|
|
@@ -26,6 +26,10 @@
|
|
|
26
26
|
* `repo` field resolves to the reader's own git common dir ({@link readAuflagen},
|
|
27
27
|
* #1520 / navigator#190).
|
|
28
28
|
*
|
|
29
|
+
* There is no Rücklauf claim marker (removed in #1611): the owner inbox only lets
|
|
30
|
+
* a session rest when it reported in the turn of its last answer, so every
|
|
31
|
+
* waiting turn reports and nothing here suppresses a repeat.
|
|
32
|
+
*
|
|
29
33
|
* Session ids become file names, so {@link isSafeSessionId} is the security
|
|
30
34
|
* boundary between check-in input and the filesystem: every path builder that
|
|
31
35
|
* takes a session id refuses an unsafe one with a TypeError.
|
|
@@ -64,6 +68,23 @@ export function navigatorDir({ env = process.env, home = os.homedir() } = {}) {
|
|
|
64
68
|
return path.join(home, '.config', 'navigator');
|
|
65
69
|
}
|
|
66
70
|
|
|
71
|
+
/**
|
|
72
|
+
* True when fleet integration is explicitly configured or its default directory exists.
|
|
73
|
+
* A nonblank override opts in even before the directory is created; stat errors disable it.
|
|
74
|
+
* @param {{ env?: Record<string, string|undefined>, home?: string }} [opts]
|
|
75
|
+
* @returns {boolean}
|
|
76
|
+
*/
|
|
77
|
+
export function fleetIntegrationEnabled({ env = process.env, home = os.homedir() } = {}) {
|
|
78
|
+
const raw = env[NAVIGATOR_CONFIG_DIR_ENV];
|
|
79
|
+
const override = typeof raw === 'string' ? raw.trim() : '';
|
|
80
|
+
if (override) return true;
|
|
81
|
+
try {
|
|
82
|
+
return fsSync.statSync(path.join(home, '.config', 'navigator')).isDirectory();
|
|
83
|
+
} catch {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
67
88
|
/**
|
|
68
89
|
* Path of the navigator lease file.
|
|
69
90
|
* @param {{ env?: Record<string, string|undefined>, home?: string }} [opts]
|
|
@@ -5,11 +5,15 @@
|
|
|
5
5
|
// Required env: TYPECHECK_CMD, TEST_CMD, LINT_CMD (each may equal "skip")
|
|
6
6
|
// Optional env: SESSION_START_REF
|
|
7
7
|
|
|
8
|
+
import { mkdtempSync, readFileSync, realpathSync, rmSync } from 'node:fs';
|
|
9
|
+
import { tmpdir } from 'node:os';
|
|
10
|
+
import path from 'node:path';
|
|
8
11
|
import {
|
|
9
12
|
runCheck,
|
|
10
13
|
extractCount,
|
|
11
14
|
extractTestCounts,
|
|
12
15
|
extractFailedTestFiles,
|
|
16
|
+
extractTimeoutOnlyFailedFiles,
|
|
13
17
|
collectDebugArtifacts,
|
|
14
18
|
commandKillFields,
|
|
15
19
|
} from './gate-helpers.mjs';
|
|
@@ -109,6 +113,147 @@ const failedTestFiles = testResult.status === 'fail'
|
|
|
109
113
|
? extractFailedTestFiles(testResult.fullOutput ?? testResult.output ?? '')
|
|
110
114
|
: [];
|
|
111
115
|
|
|
116
|
+
// --- Serial re-run of timeout-only failures (#1673) ---
|
|
117
|
+
// Under host load the parallel suite loses tests to their timeout while the
|
|
118
|
+
// code is fine: four pre-push runs on m5 blocked with four different red sets,
|
|
119
|
+
// none in the pushed diff, and the pushes then went out with the bypass. When
|
|
120
|
+
// EVERY failure of the run is a timeout, exactly those files run once more,
|
|
121
|
+
// one file at a time; only a clean re-run turns the test step green. An
|
|
122
|
+
// assertion failure is never re-run (it may be a genuine flake), a killed
|
|
123
|
+
// test command is never re-run (no complete error list), and the CI pipeline
|
|
124
|
+
// keeps running without this re-run, so it stays the strict instance.
|
|
125
|
+
// NAMED CEILING (BV-004): 10 files. More timeouts than that is not a few
|
|
126
|
+
// load victims but a broken run, and a serial re-run would double the wait.
|
|
127
|
+
// Revisit when a blocked push shows 11+ timeout-only files under load.
|
|
128
|
+
const RERUN_MAX_FILES = 10;
|
|
129
|
+
// The names go into a shell command line, so they must be plain paths.
|
|
130
|
+
const RERUN_FILE_NAME = /^[\w.@+][\w./@+-]*$/;
|
|
131
|
+
// Paths placed inside double quotes on that line: nothing the shell expands.
|
|
132
|
+
// NAMED CEILING (BV-004): a backslash is refused, so on Windows (`C:\…`
|
|
133
|
+
// temp paths) no re-run happens and the gate stays red as before. Revisit with
|
|
134
|
+
// the Windows gate work (#1612); cmd.exe also expands `%VAR%` inside quotes.
|
|
135
|
+
const QUOTABLE_PATH = /^[^"$`\\%\n\r]+$/;
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* The re-run command: the gate's OWN test command with the file filters and
|
|
139
|
+
* reporters appended, so its config, project selection, environment prefix and
|
|
140
|
+
* binary are exactly those of the first run (Codex review round 2, H2). Only
|
|
141
|
+
* shapes whose trailing arguments demonstrably reach vitest are accepted;
|
|
142
|
+
* anything else returns null and nothing is re-run.
|
|
143
|
+
* NAMED CEILING (BV-004): `npm test` / `npm run test` and a direct
|
|
144
|
+
* `[VAR=… ][exec|npx ][path/]vitest run|--run` without shell operators. pnpm/yarn pass
|
|
145
|
+
* `--` differently by version and are not accepted until measured. Revisit
|
|
146
|
+
* when a consumer repo with another test command hits #1673.
|
|
147
|
+
*/
|
|
148
|
+
function rerunCommand(cmd, args) {
|
|
149
|
+
const trimmed = (cmd ?? '').trim();
|
|
150
|
+
if (/^npm (?:run )?test$/.test(trimmed)) return `${trimmed} -- ${args}`;
|
|
151
|
+
// `[VAR=value …] [exec|npx] [path/]vitest run` — vitest itself is the program,
|
|
152
|
+
// so the appended arguments are its own; no wrapper that might drop them.
|
|
153
|
+
const direct = /^(?:[A-Za-z_][A-Za-z0-9_]*=[^\s;&|`$()<>]*\s+)*(?:(?:exec|npx)\s+)?(?:[^\s;&|`$()<>]*\/)?vitest (?:run|--run)$/;
|
|
154
|
+
if (direct.test(trimmed)) return `${trimmed} ${args}`;
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const realOrSelf = (p) => {
|
|
159
|
+
try {
|
|
160
|
+
return realpathSync(p);
|
|
161
|
+
} catch {
|
|
162
|
+
return p;
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Judge the re-run from vitest's JSON report, never from its exit code or its
|
|
168
|
+
* summary counts alone: the default reporter does not name passing files, so
|
|
169
|
+
* only the report proves that EXACTLY the requested files ran and passed (a
|
|
170
|
+
* filter matching another file, or a config excluding the file, would
|
|
171
|
+
* otherwise read as green). Returns `{ reason, passed, failed }`, `reason`
|
|
172
|
+
* null when clean.
|
|
173
|
+
*/
|
|
174
|
+
function judgeRerunReport(reportPath, files, cwd) {
|
|
175
|
+
let report;
|
|
176
|
+
try {
|
|
177
|
+
report = JSON.parse(readFileSync(reportPath, 'utf8'));
|
|
178
|
+
} catch {
|
|
179
|
+
return { reason: 'report-unreadable', passed: null, failed: null };
|
|
180
|
+
}
|
|
181
|
+
const counts = { passed: report?.numPassedTests ?? null, failed: report?.numFailedTests ?? null };
|
|
182
|
+
if (report?.success !== true || report.numFailedTests !== 0 || !Array.isArray(report.testResults)) {
|
|
183
|
+
return { reason: 'report-not-clean', ...counts };
|
|
184
|
+
}
|
|
185
|
+
const root = realOrSelf(cwd);
|
|
186
|
+
const ran = report.testResults.map((t) => [
|
|
187
|
+
path.relative(root, realOrSelf(String(t?.name ?? ''))).split(path.sep).join('/'),
|
|
188
|
+
t?.status,
|
|
189
|
+
]);
|
|
190
|
+
const wanted = new Set(files);
|
|
191
|
+
const exact =
|
|
192
|
+
ran.length === files.length && ran.every(([file, status]) => status === 'passed' && wanted.delete(file));
|
|
193
|
+
return { reason: exact && wanted.size === 0 ? null : 'file-set-mismatch', ...counts };
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
let rerun = null;
|
|
197
|
+
let rerunResult = null;
|
|
198
|
+
// Only a run that ENDED with exit 1 — not by a signal (`exitSignal`, which
|
|
199
|
+
// `exitCode` alone reports as 1), not by the gate's own kill — and printed
|
|
200
|
+
// vitest's closing `Test Files` summary (`testCounts.files`). A signal that hits
|
|
201
|
+
// vitest under `npm test` surfaces as npm exit 143 (measured, npm 11.19.1), so
|
|
202
|
+
// both routes of an externally ended run stay out (Codex review rounds 2+3, M3).
|
|
203
|
+
if (
|
|
204
|
+
testResult.status === 'fail' &&
|
|
205
|
+
testResult.timedOut !== true &&
|
|
206
|
+
testResult.exitCode === 1 &&
|
|
207
|
+
!testResult.exitSignal &&
|
|
208
|
+
(testResult.killSignals ?? []).length === 0 &&
|
|
209
|
+
testCounts.files
|
|
210
|
+
) {
|
|
211
|
+
const timeouts = extractTimeoutOnlyFailedFiles(testResult.fullOutput ?? testResult.output ?? '');
|
|
212
|
+
if (
|
|
213
|
+
timeouts.eligible &&
|
|
214
|
+
timeouts.files.length <= RERUN_MAX_FILES &&
|
|
215
|
+
timeouts.files.every((f) => RERUN_FILE_NAME.test(f))
|
|
216
|
+
) {
|
|
217
|
+
let reportDir = null;
|
|
218
|
+
try {
|
|
219
|
+
reportDir = mkdtempSync(path.join(tmpdir(), 'gate-rerun-'));
|
|
220
|
+
const reportPath = path.join(reportDir, 'report.json');
|
|
221
|
+
const command = QUOTABLE_PATH.test(reportPath)
|
|
222
|
+
? rerunCommand(
|
|
223
|
+
testCmd,
|
|
224
|
+
// `default` keeps a readable failure on stderr; `json` is the proof.
|
|
225
|
+
`--no-file-parallelism --reporter=default --reporter=json --outputFile.json="${reportPath}" ${timeouts.files.join(' ')}`,
|
|
226
|
+
)
|
|
227
|
+
: null;
|
|
228
|
+
if (command !== null) {
|
|
229
|
+
rerunResult = await runCheck(command, CHECK_OPTS);
|
|
230
|
+
const judged = judgeRerunReport(reportPath, timeouts.files, process.cwd());
|
|
231
|
+
const verdict = rerunResult.status === 'pass' ? judged : { ...judged, reason: 'exit-non-zero' };
|
|
232
|
+
rerun = {
|
|
233
|
+
files: timeouts.files,
|
|
234
|
+
status: verdict.reason === null ? 'pass' : 'fail',
|
|
235
|
+
...(verdict.reason ? { reason: verdict.reason } : {}),
|
|
236
|
+
passed: verdict.passed,
|
|
237
|
+
failed: verdict.failed,
|
|
238
|
+
...commandKillFields(rerunResult),
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
} catch (err) {
|
|
242
|
+
// Any failure around the re-run keeps the step red and still names the files.
|
|
243
|
+
rerun = { files: timeouts.files, status: 'fail', reason: `rerun-error: ${err?.code ?? err?.message ?? 'unknown'}`, passed: null, failed: null };
|
|
244
|
+
} finally {
|
|
245
|
+
if (reportDir !== null) {
|
|
246
|
+
try {
|
|
247
|
+
rmSync(reportDir, { recursive: true, force: true });
|
|
248
|
+
} catch {
|
|
249
|
+
/* a leftover temp dir must not crash the gate or flip its verdict */
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
const testStatus = rerun?.status === 'pass' ? 'pass' : testResult.status;
|
|
256
|
+
|
|
112
257
|
const fileFields = testCounts.files
|
|
113
258
|
? {
|
|
114
259
|
files_total: testCounts.files.total,
|
|
@@ -119,7 +264,9 @@ const fileFields = testCounts.files
|
|
|
119
264
|
// test-case `failed: 0` that a file-level failure explains. Greppable —
|
|
120
265
|
// a consumer no longer has to recompute the contradiction by hand.
|
|
121
266
|
suite_died:
|
|
122
|
-
|
|
267
|
+
// `testStatus`: a suite whose only failure was a hook timeout and that
|
|
268
|
+
// re-ran clean (#1673) did not die.
|
|
269
|
+
testStatus === 'fail' && testCounts.failed === 0 && testCounts.files.failed > 0,
|
|
123
270
|
}
|
|
124
271
|
: {};
|
|
125
272
|
|
|
@@ -154,8 +301,10 @@ const output = {
|
|
|
154
301
|
// re-derived as `total - passed` downstream: the derivation and the parse can
|
|
155
302
|
// disagree, and only an explicit third number lets a consumer check
|
|
156
303
|
// `passed + failed === total` as a real producer/consumer drift guard.
|
|
304
|
+
// `status` is the step's verdict after a re-run; the counts below stay the
|
|
305
|
+
// first, parallel run's measurement, and `rerun` says what turned it green.
|
|
157
306
|
test: {
|
|
158
|
-
status:
|
|
307
|
+
status: testStatus,
|
|
159
308
|
total: testCounts.total,
|
|
160
309
|
passed: testCounts.passed,
|
|
161
310
|
failed: testCounts.failed,
|
|
@@ -166,6 +315,7 @@ const output = {
|
|
|
166
315
|
// document and the test-case triple keeps `total === passed + failed`.
|
|
167
316
|
...fileFields,
|
|
168
317
|
...commandKillFields(testResult),
|
|
318
|
+
...(rerun ? { rerun } : {}),
|
|
169
319
|
},
|
|
170
320
|
lint: { status: lintResult.status, warnings: lintWarnings, ...commandKillFields(lintResult) },
|
|
171
321
|
debug_artifacts: debugArtifacts,
|
|
@@ -175,10 +325,19 @@ const output = {
|
|
|
175
325
|
process.stdout.write(JSON.stringify(output) + '\n');
|
|
176
326
|
|
|
177
327
|
// Exit 2 if any check failed; skipped counts as pass.
|
|
178
|
-
const failed = [tcResult,
|
|
179
|
-
(
|
|
328
|
+
const failed = [tcResult.status, testStatus, lintResult.status].some(
|
|
329
|
+
(status) => status === 'fail',
|
|
180
330
|
);
|
|
181
331
|
|
|
332
|
+
// Every re-run is named, green or not, so a load-sensitive file stays visible
|
|
333
|
+
// even when the push goes through.
|
|
334
|
+
if (rerun) {
|
|
335
|
+
process.stderr.write(
|
|
336
|
+
`\n──── test re-run: ${rerun.files.length} file(s) failed only by timeout, re-ran serially → ${rerun.status} ────\n` +
|
|
337
|
+
rerun.files.map((f) => ` ${f}\n`).join(''),
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
|
|
182
341
|
// --- Failure disclosure ---
|
|
183
342
|
// A gate that BLOCKS and says nothing is not concise, it is unusable. Measured
|
|
184
343
|
// 2026-08-23: a pre-push run emitted exactly
|
|
@@ -207,20 +366,22 @@ if (failed) {
|
|
|
207
366
|
// Names FIRST, raw output after. Under the pre-push hook the raw block is
|
|
208
367
|
// hundreds of lines; the operator reads the top of it, and the one fact he
|
|
209
368
|
// needs to act (`npx vitest run <file>`) must not sit at the bottom.
|
|
210
|
-
if (failedTestFiles.length > 0) {
|
|
369
|
+
if (testStatus === 'fail' && failedTestFiles.length > 0) {
|
|
211
370
|
process.stderr.write(
|
|
212
371
|
`\n──── failing test files (${failedTestFiles.length}) ────\n` +
|
|
213
372
|
failedTestFiles.map((f) => ` ${f}\n`).join('') +
|
|
214
373
|
` reproduce: npx vitest run ${failedTestFiles.join(' ')}\n`,
|
|
215
374
|
);
|
|
216
375
|
}
|
|
217
|
-
for (const [name, result] of [
|
|
218
|
-
['typecheck', tcResult],
|
|
219
|
-
['test', testResult],
|
|
220
|
-
['
|
|
376
|
+
for (const [name, result, status] of [
|
|
377
|
+
['typecheck', tcResult, tcResult.status],
|
|
378
|
+
['test', testResult, testStatus],
|
|
379
|
+
['test re-run', rerunResult, rerun?.status],
|
|
380
|
+
['lint', lintResult, lintResult.status],
|
|
221
381
|
]) {
|
|
222
|
-
if (
|
|
223
|
-
|
|
382
|
+
if (status !== 'fail') continue;
|
|
383
|
+
// A re-run that never started (temp dir, spawn) has no result, only a reason.
|
|
384
|
+
const raw = result ? (result.fullOutput ?? result.output ?? '') : `(not run: ${rerun?.reason ?? 'unknown'})\n`;
|
|
224
385
|
process.stderr.write(`\n──── ${name} FAILED — raw output ────\n`);
|
|
225
386
|
process.stderr.write(raw.length > 0 ? raw : '(the gate captured no output)\n');
|
|
226
387
|
process.stderr.write(`──── end ${name} ────\n`);
|