session-orchestrator 5.2.0 → 5.4.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/.agents/skills/architecture/SKILL.md +3 -1
- package/.agents/skills/autopilot/SKILL.md +5 -1
- package/.agents/skills/autopilot/agents/openai.yaml +5 -0
- package/.agents/skills/bootstrap/SKILL.md +5 -1
- package/.agents/skills/bootstrap/agents/openai.yaml +5 -0
- package/.agents/skills/brainstorm/SKILL.md +5 -1
- package/.agents/skills/brainstorm/agents/openai.yaml +5 -0
- package/.agents/skills/claude-md-drift-check/SKILL.md +4 -2
- package/.agents/skills/close/SKILL.md +5 -1
- package/.agents/skills/close/agents/openai.yaml +5 -0
- package/.agents/skills/convergence-monitoring/SKILL.md +4 -2
- package/.agents/skills/debug/SKILL.md +5 -1
- package/.agents/skills/debug/agents/openai.yaml +5 -0
- package/.agents/skills/discovery/SKILL.md +5 -1
- package/.agents/skills/discovery/agents/openai.yaml +5 -0
- package/.agents/skills/dispatcher/SKILL.md +5 -1
- package/.agents/skills/dispatcher/agents/openai.yaml +5 -0
- package/.agents/skills/docs-orchestrator/SKILL.md +3 -1
- package/.agents/skills/ecosystem-health/SKILL.md +3 -1
- package/.agents/skills/eli5/SKILL.md +5 -1
- package/.agents/skills/eli5/agents/openai.yaml +5 -0
- package/.agents/skills/eval/SKILL.md +6 -2
- package/.agents/skills/eval/agents/openai.yaml +5 -0
- package/.agents/skills/evolve/SKILL.md +6 -2
- package/.agents/skills/evolve/agents/openai.yaml +5 -0
- package/.agents/skills/frontmatter-guard/SKILL.md +3 -1
- package/.agents/skills/gitlab-ops/SKILL.md +3 -1
- package/.agents/skills/gitlab-portfolio/SKILL.md +3 -1
- package/.agents/skills/go/SKILL.md +5 -1
- package/.agents/skills/go/agents/openai.yaml +5 -0
- package/.agents/skills/grill/SKILL.md +5 -1
- package/.agents/skills/grill/agents/openai.yaml +5 -0
- package/.agents/skills/harness-audit/SKILL.md +5 -1
- package/.agents/skills/harness-audit/agents/openai.yaml +5 -0
- package/.agents/skills/hook-development/SKILL.md +3 -1
- package/.agents/skills/mcp-builder/SKILL.md +3 -1
- package/.agents/skills/memory-cleanup/SKILL.md +5 -1
- package/.agents/skills/memory-cleanup/agents/openai.yaml +5 -0
- package/.agents/skills/mode-selector/SKILL.md +3 -1
- package/.agents/skills/navigator/SKILL.md +22 -0
- package/.agents/skills/navigator/agents/openai.yaml +5 -0
- package/.agents/skills/npm-publish/SKILL.md +4 -2
- package/.agents/skills/peekaboo-driver/SKILL.md +3 -1
- package/.agents/skills/persona-panel/SKILL.md +5 -1
- package/.agents/skills/persona-panel/agents/openai.yaml +5 -0
- package/.agents/skills/plan/SKILL.md +5 -1
- package/.agents/skills/plan/agents/openai.yaml +5 -0
- package/.agents/skills/playwright-driver/SKILL.md +3 -1
- package/.agents/skills/portfolio/SKILL.md +5 -1
- package/.agents/skills/portfolio/agents/openai.yaml +5 -0
- package/.agents/skills/quality-gates/SKILL.md +3 -1
- package/.agents/skills/reconcile/SKILL.md +5 -1
- package/.agents/skills/reconcile/agents/openai.yaml +5 -0
- package/.agents/skills/release/SKILL.md +5 -1
- package/.agents/skills/release/agents/openai.yaml +5 -0
- package/.agents/skills/remote-offload/SKILL.md +3 -1
- package/.agents/skills/repo-audit/SKILL.md +5 -1
- package/.agents/skills/repo-audit/agents/openai.yaml +5 -0
- package/.agents/skills/session/SKILL.md +21 -0
- package/.agents/skills/session/agents/openai.yaml +5 -0
- package/.agents/skills/session-end/SKILL.md +3 -1
- package/.agents/skills/session-plan/SKILL.md +3 -1
- package/.agents/skills/session-start/SKILL.md +3 -1
- package/.agents/skills/spinout/SKILL.md +5 -1
- package/.agents/skills/spinout/agents/openai.yaml +5 -0
- package/.agents/skills/sunset-review/SKILL.md +5 -1
- package/.agents/skills/sunset-review/agents/openai.yaml +5 -0
- package/.agents/skills/templates-ack/SKILL.md +21 -0
- package/.agents/skills/templates-ack/agents/openai.yaml +5 -0
- package/.agents/skills/test/SKILL.md +5 -1
- package/.agents/skills/test/agents/openai.yaml +5 -0
- package/.agents/skills/test-audit/SKILL.md +21 -0
- package/.agents/skills/test-audit/agents/openai.yaml +5 -0
- package/.agents/skills/test-runner/SKILL.md +3 -1
- package/.agents/skills/tmux-layout/SKILL.md +3 -1
- package/.agents/skills/using-orchestrator/SKILL.md +3 -1
- package/.agents/skills/ux-grill/SKILL.md +5 -1
- package/.agents/skills/ux-grill/agents/openai.yaml +5 -0
- package/.agents/skills/vault-mirror/SKILL.md +3 -1
- package/.agents/skills/vault-sync/SKILL.md +3 -1
- package/.agents/skills/wave-executor/SKILL.md +3 -1
- package/.agents/skills/write-executable-plan/SKILL.md +3 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +4 -4
- package/.codex-plugin/skills/claude-md-drift-check/SKILL.md +1 -1
- package/.codex-plugin/skills/convergence-monitoring/SKILL.md +1 -3
- package/.codex-plugin/skills/eval/SKILL.md +1 -1
- package/.codex-plugin/skills/evolve/SKILL.md +1 -1
- package/.codex-plugin/skills/navigator/SKILL.md +22 -0
- package/.codex-plugin/skills/{convergence-monitoring → navigator}/agents/openai.yaml +2 -2
- package/.codex-plugin/skills/npm-publish/SKILL.md +1 -3
- package/.codex-plugin/skills/session/SKILL.md +1 -1
- package/.codex-plugin/skills/test-audit/SKILL.md +21 -0
- package/.codex-plugin/skills/{npm-publish → test-audit}/agents/openai.yaml +1 -1
- package/.cursor/commands/eval.md +1 -1
- package/.cursor/commands/navigator.md +15 -0
- package/.cursor/commands/session.md +1 -1
- package/.cursor/commands/test-audit.md +14 -0
- package/.cursor/rules/000-session-orchestrator.mdc +0 -2
- package/.cursor/rules/050-plan.mdc +1 -1
- package/.cursor/rules/060-evolve.mdc +27 -12
- package/.cursor/skills/claude-md-drift-check/SKILL.md +1 -1
- package/.cursor/skills/convergence-monitoring/SKILL.md +1 -0
- package/.cursor/skills/eval/SKILL.md +1 -1
- package/.cursor/skills/navigator/SKILL.md +13 -0
- package/.cursor/skills/npm-publish/SKILL.md +1 -0
- package/.cursor/skills/test-audit/SKILL.md +12 -0
- package/.cursor-plugin/plugin.json +1 -1
- package/.orchestrator/policy/blocked-commands.json +12 -3
- package/AGENTS.md +9 -30
- package/CHANGELOG.md +206 -0
- package/README.md +13 -9
- package/SECURITY.md +12 -0
- package/agents/dialectic-deriver.md +13 -10
- package/agents/eval-judge.md +67 -45
- package/agents/skill-applied-judge.md +34 -19
- package/commands/session.md +7 -3
- package/docs/agent-authoring.md +2 -2
- package/docs/baseline.md +22 -7
- package/docs/ci-setup.md +57 -4
- package/docs/codex-setup.md +14 -2
- package/docs/components.md +12 -10
- package/docs/events-schema.md +65 -15
- package/docs/persona-panel.md +5 -4
- package/docs/rule-authoring.md +84 -17
- package/docs/scope-collision-guard.md +26 -4
- package/docs/session-config-reference.md +154 -12
- package/docs/session-config-template.md +41 -3
- package/docs/telemetry.md +2 -0
- package/hooks/_lib/hook-import-set.json +87 -6
- package/hooks/_lib/subagent-transcript.mjs +582 -31
- package/hooks/config-protection.mjs +11 -3
- package/hooks/cwd-change-restore.mjs +11 -3
- package/hooks/enforce-commands.mjs +70 -23
- package/hooks/enforce-scope.mjs +143 -33
- package/hooks/hooks-codex.json +1 -1
- package/hooks/hooks-cursor.json +5 -0
- package/hooks/hooks-pi.json +5 -0
- package/hooks/hooks.json +6 -1
- package/hooks/loop-guard.mjs +11 -3
- package/hooks/on-session-end.mjs +58 -23
- package/hooks/on-session-start.mjs +101 -11
- package/hooks/on-stop.mjs +170 -22
- package/hooks/operator-steer.mjs +11 -3
- package/hooks/post-bash-issue-budget-refund.mjs +18 -8
- package/hooks/post-bash-write-verify.mjs +3 -2
- package/hooks/post-edit-import-probe.mjs +17 -9
- package/hooks/post-edit-validate.mjs +13 -5
- package/hooks/post-subagent-discovery-validator.mjs +98 -13
- package/hooks/post-tool-batch-wave-signal.mjs +202 -38
- package/hooks/post-tool-failure-corrective-context.mjs +11 -5
- package/hooks/post-tooluse-frontend-slop.mjs +10 -4
- package/hooks/pre-auq-clarity.mjs +15 -2
- package/hooks/pre-bash-destructive-guard.mjs +80 -9
- package/hooks/pre-bash-issue-budget.mjs +16 -11
- package/hooks/pre-bash-memory-propose-audit.mjs +137 -56
- package/hooks/pre-bash-sessions-ledger-guard.mjs +419 -22
- package/hooks/pre-bash-staging-fence.mjs +335 -31
- package/hooks/pre-bash-templates-first.mjs +19 -14
- package/hooks/pre-edit-sessions-ledger-guard.mjs +162 -0
- package/hooks/pre-task-scope-disjoint.mjs +792 -74
- package/hooks/subagent-telemetry.mjs +111 -38
- package/hooks/wave-scope-commit-guard.mjs +197 -100
- package/monitors/monitors.json +1 -1
- package/output-styles/wave-summary.md +1 -1
- package/package.json +3 -3
- package/pi/prompts/eval.md +1 -1
- package/pi/prompts/navigator.md +12 -0
- package/pi/prompts/session.md +1 -1
- package/pi/prompts/test-audit.md +12 -0
- package/rules/README.md +7 -2
- package/rules/_index.md +3 -3
- package/rules/always-on/bash-harness-pitfalls.md +7 -1
- package/rules/opt-in-domain/prompt-caching.md +6 -2
- package/rules/opt-in-stack/backend-data.md +10 -2
- package/rules/opt-in-stack/backend.md +10 -4
- package/rules/opt-in-stack/frontend.md +7 -2
- package/rules/opt-in-stack/security-web.md +10 -4
- package/rules/opt-in-stack/swift.md +6 -2
- package/scripts/apply-session-learnings.mjs +492 -0
- package/scripts/archive-closed-prds.mjs +2 -1
- package/scripts/autopilot.mjs +70 -53
- package/scripts/backfill-abandoned-sessions.mjs +118 -16
- package/scripts/baseline-archetypes.mjs +1 -1
- package/scripts/check-package-manager.mjs +62 -5
- package/scripts/check-sessions-integrity.mjs +300 -0
- package/scripts/cursor-install.mjs +1 -4
- package/scripts/dialectic-deriver.mjs +50 -13
- package/scripts/emit-session.mjs +371 -31
- package/scripts/eval-session.mjs +65 -3
- package/scripts/export-hw-learnings.mjs +22 -3
- package/scripts/fleet-checkin.mjs +147 -0
- package/scripts/generate-agents-skills.mjs +102 -29
- package/scripts/generate-cursor-adapter.mjs +61 -16
- package/scripts/lib/agent-frontmatter.mjs +3 -3
- package/scripts/lib/agent-status.mjs +2 -31
- package/scripts/lib/auq/clarity.mjs +10 -2
- package/scripts/lib/auq/parse.mjs +12 -31
- package/scripts/lib/auq/schema.mjs +56 -41
- package/scripts/lib/auto-dialectic.mjs +304 -15
- package/scripts/lib/auto-dream.mjs +7 -6
- package/scripts/lib/autopilot/flags.mjs +18 -2
- package/scripts/lib/autopilot/kill-switches.mjs +6 -3
- package/scripts/lib/autopilot/loop.mjs +19 -2
- package/scripts/lib/autopilot/stall-sampler.mjs +80 -23
- package/scripts/lib/baseline-archetypes.mjs +41 -22
- package/scripts/lib/ci-status-banner.mjs +376 -16
- package/scripts/lib/command-blocker.mjs +275 -28
- package/scripts/lib/config/dialectic.mjs +12 -3
- package/scripts/lib/config/gate.mjs +74 -0
- package/scripts/lib/config/host-paths.mjs +79 -0
- package/scripts/lib/config/persona-gate-wave.mjs +3 -3
- package/scripts/lib/config/reaper.mjs +163 -0
- package/scripts/lib/config-schema.mjs +13 -0
- package/scripts/lib/config.mjs +85 -10
- package/scripts/lib/convergence-monitor.mjs +74 -11
- package/scripts/lib/ecosystem-health.mjs +11 -0
- package/scripts/lib/eval/engine.mjs +421 -53
- package/scripts/lib/eval/judge.mjs +463 -40
- package/scripts/lib/eval/schema.mjs +10 -1
- package/scripts/lib/events-rotation.mjs +221 -25
- package/scripts/lib/events-schema.mjs +114 -0
- package/scripts/lib/events.mjs +545 -7
- package/scripts/lib/fleet-protocol.mjs +270 -0
- package/scripts/lib/frontmatter-guard.mjs +21 -10
- package/scripts/lib/gates/gate-baseline.mjs +40 -5
- package/scripts/lib/gates/gate-full.mjs +35 -5
- package/scripts/lib/gates/gate-helpers.mjs +336 -23
- package/scripts/lib/gates/gate-incremental.mjs +49 -23
- package/scripts/lib/gates/gate-per-file.mjs +42 -8
- package/scripts/lib/gitlab-portfolio/markdown-writer.mjs +6 -1
- package/scripts/lib/instruction-budget-guard.mjs +208 -8
- package/scripts/lib/io.mjs +42 -8
- package/scripts/lib/issue-close-strip-labels.mjs +207 -49
- package/scripts/lib/js-mask.mjs +197 -0
- package/scripts/lib/learnings/evolve-telemetry.mjs +11 -7
- package/scripts/lib/learnings/expiry-sweep.mjs +34 -3
- package/scripts/lib/learnings/io.mjs +154 -26
- package/scripts/lib/learnings/schema.mjs +163 -15
- package/scripts/lib/learnings/sizing-subject.mjs +2 -2
- package/scripts/lib/maintenance-due-banner.mjs +53 -88
- package/scripts/lib/memory-proposals/schema.mjs +11 -5
- package/scripts/lib/named-baseline-resolver.mjs +53 -28
- package/scripts/lib/named-vault-resolver.mjs +104 -5
- package/scripts/lib/orphan-reaper/defaults.mjs +41 -0
- package/scripts/lib/orphan-reaper/ps-snapshot.mjs +181 -0
- package/scripts/lib/orphan-reaper/reaper-audit.mjs +291 -0
- package/scripts/lib/orphan-reaper/reaper-decide.mjs +395 -0
- package/scripts/lib/orphan-reaper/scan-throttle.mjs +92 -0
- package/scripts/lib/orphan-reaper/scan.mjs +506 -0
- package/scripts/lib/orphan-reaper.mjs +316 -0
- package/scripts/lib/owner-yaml.mjs +104 -6
- package/scripts/lib/peer-cards/merger.mjs +48 -10
- package/scripts/lib/peer-cards/reader.mjs +78 -2
- package/scripts/lib/persona-panel/catalog-loader.mjs +1 -1
- package/scripts/lib/process-group.mjs +950 -0
- package/scripts/lib/project-hygiene.mjs +21 -1
- package/scripts/lib/quality-gate.mjs +152 -33
- package/scripts/lib/reconcile/backlog.mjs +368 -0
- package/scripts/lib/reconcile/engine.mjs +55 -188
- package/scripts/lib/reconcile/rule-expiry-sweep.mjs +302 -60
- package/scripts/lib/reconcile/sanitize.mjs +69 -3
- package/scripts/lib/reconcile-nudge-banner.mjs +138 -45
- package/scripts/lib/resource-probe/parsers.mjs +31 -0
- package/scripts/lib/resource-probe.mjs +1 -1
- package/scripts/lib/rule-loader.mjs +141 -74
- package/scripts/lib/rules-sync.mjs +27 -12
- package/scripts/lib/scope-baseline.mjs +3 -4
- package/scripts/lib/scope-echo.mjs +39 -2
- package/scripts/lib/scope-gate.mjs +605 -1
- package/scripts/lib/session-close-backfill.mjs +33 -6
- package/scripts/lib/session-id.mjs +9 -20
- package/scripts/lib/session-invocation.mjs +20 -0
- package/scripts/lib/session-record-repair.mjs +183 -26
- package/scripts/lib/session-registry.mjs +27 -2
- package/scripts/lib/session-schema/constants.mjs +56 -2
- package/scripts/lib/session-schema/normalizer.mjs +56 -4
- package/scripts/lib/session-schema/validator.mjs +28 -1
- package/scripts/lib/session-schema.mjs +8 -3
- package/scripts/lib/session-start-probes.mjs +95 -10
- package/scripts/lib/session-token-rollup.mjs +245 -52
- package/scripts/lib/sessions-canonical.mjs +118 -4
- package/scripts/lib/sessions-integrity-banner.mjs +7 -1
- package/scripts/lib/sessions-staleness-banner.mjs +193 -51
- package/scripts/lib/sh-quote.mjs +29 -0
- package/scripts/lib/skill-evidence-window.mjs +891 -0
- package/scripts/lib/skill-evolution/candidate-intake.mjs +133 -12
- package/scripts/lib/skill-evolution/engine.mjs +18 -9
- package/scripts/lib/skill-judge.mjs +45 -3
- package/scripts/lib/subagents-schema.mjs +30 -3
- package/scripts/lib/tail-window.mjs +56 -0
- package/scripts/lib/telemetry/pricing.mjs +118 -71
- package/scripts/lib/telemetry/schema.mjs +30 -0
- package/scripts/lib/telemetry/sync.mjs +61 -6
- package/scripts/lib/telemetry-flush-health-banner.mjs +4 -22
- package/scripts/lib/test-runner/issue-reconcile.mjs +48 -16
- package/scripts/lib/tests-src-ratio.mjs +140 -23
- package/scripts/lib/tmux-layout/layouts.mjs +1 -11
- package/scripts/lib/tmux-layout/telemetry-stats.mjs +72 -13
- package/scripts/lib/tmux-layout/tmux-shell.mjs +2 -11
- package/scripts/lib/user-invocable-skills.mjs +23 -3
- package/scripts/lib/ux-grill/reconcile.mjs +48 -22
- package/scripts/lib/validate/check-agents-skills.mjs +26 -15
- package/scripts/lib/validate/check-agents.mjs +1 -1
- package/scripts/lib/validate/check-cursor-adapter.mjs +1 -0
- package/scripts/lib/validate/check-entry-guard.mjs +13 -50
- package/scripts/lib/validate/check-hook-entry-guards.mjs +636 -0
- package/scripts/lib/validate/check-hooks-symmetry.mjs +5 -0
- package/scripts/lib/validate/check-learnings-shell-writes.mjs +214 -0
- package/scripts/lib/validate/check-pi-prompts.mjs +1 -0
- package/scripts/lib/validate/check-rules.mjs +7 -5
- package/scripts/lib/validate/check-skill-links.mjs +9 -1
- package/scripts/lib/validate/check-skill-script-paths.mjs +239 -27
- package/scripts/lib/validate/check-test-git-config-target.mjs +24 -34
- package/scripts/lib/validate/check-test-value-bans.mjs +87 -14
- package/scripts/lib/validate/check-unicode-safety.mjs +1 -0
- package/scripts/lib/validate/check-untracked-test-deps.mjs +7 -102
- package/scripts/lib/validate/check-unwired-features.mjs +130 -27
- package/scripts/lib/validate/check-validator-registration.mjs +34 -10
- package/scripts/lib/validate/confidential-names.mjs +10 -0
- package/scripts/lib/validate-vendored-rules.mjs +210 -84
- package/scripts/lib/vault-mirror/namespace.mjs +46 -8
- package/scripts/lib/vault-mirror/process.mjs +10 -3
- package/scripts/lib/vault-mirror/render-sessions.mjs +12 -2
- package/scripts/lib/vault-mirror/telemetry.mjs +8 -1
- package/scripts/lib/vault-status/board-writer.mjs +48 -11
- package/scripts/lib/vault-status/narrative-mirror.mjs +71 -16
- package/scripts/lib/vault-yaml.mjs +118 -0
- package/scripts/lib/wave-executor/dispatch-common.mjs +10 -18
- package/scripts/lib/wave-executor/foreign-dispatch.mjs +1 -1
- package/scripts/lib/wave-executor/remote-dispatch.mjs +1 -1
- package/scripts/lib/wave-resource-gate.mjs +34 -8
- package/scripts/lib/worktree/lifecycle.mjs +153 -1
- package/scripts/memory-propose.mjs +18 -5
- package/scripts/release-session-lock.mjs +306 -0
- package/scripts/release.mjs +40 -8
- package/scripts/repair-invalid-sessions.mjs +37 -4
- package/scripts/resolve-session-invocation.mjs +59 -0
- package/scripts/run-migrate-v2-cross-repo.mjs +158 -30
- package/scripts/run-quality-gate.mjs +357 -197
- package/scripts/site-numbers.mjs +163 -5
- package/scripts/sweep-expired-learnings.mjs +56 -5
- package/scripts/sweep-expired-rules.mjs +16 -4
- package/scripts/validate-plugin.mjs +19 -0
- package/scripts/validate-wave-scope.mjs +32 -105
- package/scripts/vault-integration-watcher.mjs +2 -1
- package/scripts/vault-mirror.mjs +81 -47
- package/scripts/wave-scope-binding.mjs +2 -1
- package/skills/_shared/config-reading.md +3 -3
- package/skills/_shared/fleet-protocol.md +131 -0
- package/skills/_shared/platform-tools.md +23 -11
- package/skills/_shared/state-ownership.md +5 -0
- package/skills/autopilot/SKILL.md +47 -18
- package/skills/bootstrap/SKILL.md +1 -1
- package/skills/bootstrap/_shared-template.md +1 -1
- package/skills/bootstrap/fast-template.md +1 -1
- package/skills/bootstrap/private-contract.md +2 -1
- package/skills/bootstrap/public-fallback.md +6 -3
- package/skills/bootstrap/references/bootstrap-retroactive-flow.md +2 -1
- package/skills/claude-md-drift-check/SKILL.md +10 -10
- package/skills/claude-md-drift-check/checker.mjs +26 -18
- package/skills/convergence-monitoring/README.md +8 -1
- package/skills/convergence-monitoring/SIGNALS.md +50 -6
- package/skills/convergence-monitoring/SKILL.md +15 -6
- package/skills/eval/SKILL.md +39 -24
- package/skills/eval/rubric-v1.md +1 -0
- package/skills/eval/rubric-v2.md +457 -0
- package/skills/evolve/SKILL.md +3 -3
- package/skills/evolve/references/evolve-analyze-mode.md +10 -6
- package/skills/evolve/references/evolve-dialectic-mode.md +42 -25
- package/skills/gitlab-ops/SKILL.md +3 -2
- package/skills/memory-cleanup/SKILL.md +1 -1
- package/skills/navigator/SKILL.md +193 -0
- package/skills/navigator/references/account-check.sh +87 -0
- package/skills/navigator/references/fleet-decisions.mjs +231 -0
- package/skills/navigator/references/slot-check.sh +114 -0
- package/skills/npm-publish/SKILL.md +1 -1
- package/skills/persona-panel/SKILL.md +2 -2
- package/skills/persona-panel/persona-format.md +2 -2
- package/skills/persona-panel/presets/designer-lens.md +1 -1
- package/skills/persona-panel/presets/engineer-lens.md +1 -1
- package/skills/persona-panel/presets/pm-lens.md +1 -1
- package/skills/plan/mode-retro.md +19 -4
- package/skills/quality-gates/SKILL.md +6 -4
- package/skills/reconcile/SKILL.md +11 -0
- package/skills/remote-offload/SKILL.md +4 -2
- package/skills/session-end/SKILL.md +13 -16
- package/skills/session-end/discovery-scan.md +1 -1
- package/skills/session-end/learning-patterns.md +22 -12
- package/skills/session-end/metrics-collection.md +5 -6
- package/skills/session-end/phase-3-6-tail.md +55 -9
- package/skills/session-end/references/phase-3-documentation-updates.md +2 -2
- package/skills/session-end/references/phase-4a-worktree-cleanup.md +2 -0
- package/skills/session-end/references/phase-5-issue-cleanup.md +9 -14
- package/skills/session-end/references/session-summary-template.md +1 -1
- package/skills/session-end/session-metrics-write.md +32 -60
- package/skills/session-plan/SKILL.md +17 -8
- package/skills/session-plan/references/session-plan-task-classification.md +2 -2
- package/skills/session-start/SKILL.md +6 -0
- package/skills/session-start/references/operations-contract.md +4 -0
- 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 +4 -3
- package/skills/test-audit/SKILL.md +152 -0
- package/skills/test-audit/references/campaign.md +111 -0
- package/skills/test-audit/references/mutate.sh +50 -0
- package/skills/ux-grill/SKILL.md +1 -1
- package/skills/wave-executor/SKILL.md +9 -5
- package/skills/wave-executor/circuit-breaker.md +2 -0
- package/skills/wave-executor/references/wave-executor-state-init.md +8 -3
- package/skills/wave-executor/references/wave-loop-dispatch.md +5 -2
- package/skills/wave-executor/references/wave-loop-review.md +3 -5
- package/templates/personas/README.md +5 -5
- package/templates/personas/accounting-compliance.v1.md +1 -1
- package/templates/personas/accounting-tax-advisor.v1.md +1 -1
- package/templates/personas/buyer-p1-cto.v1.md +1 -1
- package/templates/personas/buyer-p2-kanzlei.v1.md +1 -1
- package/templates/personas/buyer-p3-build.v1.md +1 -1
- package/templates/personas/buyer-p4-tech-veto.v1.md +1 -1
- package/templates/personas/buyer-p5-solo.v1.md +1 -1
- package/templates/personas/buyer-p6-ld.v1.md +1 -1
- package/templates/personas/klima-ai-expert.v1.md +1 -1
- package/templates/personas/klima-physicist.v1.md +1 -1
- package/.cursor/commands/convergence-monitoring.md +0 -13
- package/.cursor/commands/npm-publish.md +0 -13
- package/pi/prompts/convergence-monitoring.md +0 -11
- package/pi/prompts/npm-publish.md +0 -11
|
@@ -23,9 +23,15 @@
|
|
|
23
23
|
* 0 — pass (or informational; non-blocking variants always exit 0)
|
|
24
24
|
* 1 — script error (bad arguments, missing dependencies)
|
|
25
25
|
* 2 — gate failed (full-gate only: typecheck/test/lint errors)
|
|
26
|
+
* 124 — the gate sub-script exceeded the wall-clock ceiling and its whole
|
|
27
|
+
* process GROUP was killed (SIGTERM→SIGKILL). Same value coreutils
|
|
28
|
+
* `timeout(1)` uses. Applies to EVERY variant, including the otherwise
|
|
29
|
+
* non-blocking ones: a killed gate measured nothing, so reporting it as
|
|
30
|
+
* 0 would be a false green (Epic #1425 A3).
|
|
26
31
|
*
|
|
27
32
|
* The gate sub-scripts in scripts/lib/gates/ are NOT reimplemented here; they are
|
|
28
|
-
* invoked via
|
|
33
|
+
* invoked via spawnInGroup() — a detached, process-group-leading shell — with the
|
|
34
|
+
* required env vars.
|
|
29
35
|
*
|
|
30
36
|
* References:
|
|
31
37
|
* scripts/run-quality-gate.sh — original shell orchestrator
|
|
@@ -37,13 +43,23 @@
|
|
|
37
43
|
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
38
44
|
import { join, dirname, resolve } from 'node:path';
|
|
39
45
|
import { fileURLToPath } from 'node:url';
|
|
40
|
-
import {
|
|
46
|
+
import { spawn } from 'node:child_process';
|
|
41
47
|
|
|
42
48
|
import { die, warn } from './lib/common.mjs';
|
|
43
49
|
import { loadQualityGatesPolicy, resolveCommand } from './lib/quality-gates-policy.mjs';
|
|
44
50
|
import { emitEvent, sessionAttribution } from './lib/events.mjs';
|
|
45
|
-
import {
|
|
51
|
+
import {
|
|
52
|
+
admitSuiteCounts,
|
|
53
|
+
joinGateKillFields,
|
|
54
|
+
publishGateOutcome,
|
|
55
|
+
resolveGateTimeoutMs,
|
|
56
|
+
} from './lib/gates/gate-helpers.mjs';
|
|
57
|
+
import { buildCommandSignature, spawnInGroup } from './lib/process-group.mjs';
|
|
58
|
+
import { readProcessLocalSessionIds } from './lib/session-identity/own-session.mjs';
|
|
46
59
|
import { findScopeFile } from './lib/scope-gate.mjs';
|
|
60
|
+
import { gateKillFields } from './lib/quality-gate.mjs';
|
|
61
|
+
import { isMainModule } from './lib/is-main-module.mjs';
|
|
62
|
+
import { shellQuote } from './lib/sh-quote.mjs';
|
|
47
63
|
|
|
48
64
|
// ---------------------------------------------------------------------------
|
|
49
65
|
// Constants
|
|
@@ -62,6 +78,24 @@ const VALID_VARIANTS = ['baseline', 'incremental', 'full-gate', 'per-file'];
|
|
|
62
78
|
*/
|
|
63
79
|
const GATE_STDOUT_MAX_BUFFER_BYTES = 64 * 1024 * 1024;
|
|
64
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Head-room between the ceiling the gate's OWN commands run under
|
|
83
|
+
* (`resolveGateTimeoutMs()`, applied per command inside `runCheck`) and the one
|
|
84
|
+
* this wrapper applies to the gate sub-script as a whole.
|
|
85
|
+
*
|
|
86
|
+
* The inner cap must fire FIRST: it kills exactly the wedged command and still
|
|
87
|
+
* lets the gate print its JSON envelope, name the failing check on stderr and
|
|
88
|
+
* exit 2. This outer cap is the backstop for the case the inner one cannot
|
|
89
|
+
* reach — a gate wedged outside `runCheck`, or a kill ladder that left a
|
|
90
|
+
* survivor holding the pipe open.
|
|
91
|
+
*
|
|
92
|
+
* Named ceiling (BV-004): 60 s. It only has to cover one full SIGTERM→grace→
|
|
93
|
+
* SIGKILL→verify ladder (`DEFAULT_KILL_GRACE_MS` 10 s + 0.5 s verify + 1 s
|
|
94
|
+
* deadline slack ≈ 11.5 s) plus the gate's own JSON write. Revisit if the grace
|
|
95
|
+
* period is ever raised past ~45 s.
|
|
96
|
+
*/
|
|
97
|
+
const GATE_OUTER_TIMEOUT_RESERVE_MS = 60_000;
|
|
98
|
+
|
|
65
99
|
const DEFAULT_TEST_CMD = 'npm test';
|
|
66
100
|
const DEFAULT_TYPECHECK_CMD = 'npm run typecheck';
|
|
67
101
|
const DEFAULT_LINT_CMD = 'npm run lint';
|
|
@@ -74,63 +108,6 @@ const GATE_SCRIPT = {
|
|
|
74
108
|
'per-file': join(GATES_DIR, 'gate-per-file.mjs'),
|
|
75
109
|
};
|
|
76
110
|
|
|
77
|
-
// ---------------------------------------------------------------------------
|
|
78
|
-
// Argument parsing
|
|
79
|
-
// ---------------------------------------------------------------------------
|
|
80
|
-
|
|
81
|
-
const argv = process.argv.slice(2);
|
|
82
|
-
|
|
83
|
-
if (argv.includes('-h') || argv.includes('--help')) {
|
|
84
|
-
process.stdout.write(
|
|
85
|
-
'Usage: run-quality-gate.mjs --variant <variant> [--config <json-or-file>] ' +
|
|
86
|
-
'[--files <file1,file2,...>] [--session-start-ref <ref>] [--ledger-root <path>]\n\n' +
|
|
87
|
-
'Variants: baseline, incremental, full-gate, per-file\n\n' +
|
|
88
|
-
'Exit codes:\n' +
|
|
89
|
-
' 0 — pass (non-blocking variants always exit 0)\n' +
|
|
90
|
-
' 1 — script error (bad arguments, missing dependencies)\n' +
|
|
91
|
-
' 2 — gate failed (full-gate only)\n',
|
|
92
|
-
);
|
|
93
|
-
process.exit(0);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
let variant = '';
|
|
97
|
-
let config = '';
|
|
98
|
-
let files = '';
|
|
99
|
-
let sessionStartRef = '';
|
|
100
|
-
let ledgerRootArg = '';
|
|
101
|
-
|
|
102
|
-
for (let i = 0; i < argv.length; i++) {
|
|
103
|
-
const arg = argv[i];
|
|
104
|
-
switch (arg) {
|
|
105
|
-
case '--variant':
|
|
106
|
-
if (i + 1 >= argv.length) die('Missing value for --variant');
|
|
107
|
-
variant = argv[++i];
|
|
108
|
-
break;
|
|
109
|
-
case '--config':
|
|
110
|
-
if (i + 1 >= argv.length) die('Missing value for --config');
|
|
111
|
-
config = argv[++i];
|
|
112
|
-
break;
|
|
113
|
-
case '--files':
|
|
114
|
-
if (i + 1 >= argv.length) die('Missing value for --files');
|
|
115
|
-
files = argv[++i];
|
|
116
|
-
break;
|
|
117
|
-
case '--session-start-ref':
|
|
118
|
-
if (i + 1 >= argv.length) die('Missing value for --session-start-ref');
|
|
119
|
-
sessionStartRef = argv[++i];
|
|
120
|
-
break;
|
|
121
|
-
case '--ledger-root':
|
|
122
|
-
if (i + 1 >= argv.length) die('Missing value for --ledger-root');
|
|
123
|
-
ledgerRootArg = argv[++i];
|
|
124
|
-
break;
|
|
125
|
-
default:
|
|
126
|
-
die(`Unknown argument: ${arg}`);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
if (!variant) die('Missing required argument: --variant');
|
|
131
|
-
if (!VALID_VARIANTS.includes(variant)) {
|
|
132
|
-
die(`Invalid variant: '${variant}' (allowed: ${VALID_VARIANTS.join(', ')})`);
|
|
133
|
-
}
|
|
134
111
|
|
|
135
112
|
// ---------------------------------------------------------------------------
|
|
136
113
|
// Command resolution — policy-file-first (#183), then config, then defaults
|
|
@@ -179,9 +156,11 @@ function extractCommand(policy, policyKey, configKey, configJson, defaultCmd) {
|
|
|
179
156
|
* The four rejections that stay here are envelope-shaped, not numeric:
|
|
180
157
|
*
|
|
181
158
|
* 1. stdout is absent or not parseable JSON;
|
|
182
|
-
* 2. `test` is not
|
|
183
|
-
* `gate-
|
|
184
|
-
*
|
|
159
|
+
* 2. `test` is not an object. Every variant emits `test` as an object since
|
|
160
|
+
* #1457 pkt 12, but only `gate-full.mjs` puts counts in it —
|
|
161
|
+
* `gate-{baseline,incremental,per-file}.mjs` publish `{ status, …kill
|
|
162
|
+
* ladder }` only, which {@link admitSuiteCounts} refuses for lack of a
|
|
163
|
+
* finite `passed` / `total`;
|
|
185
164
|
* 3. the test gate was skipped (`status` neither `pass` nor `fail`);
|
|
186
165
|
* 4. the test COMMAND was detected as a stub (`echo …` / no-op) — a stub's
|
|
187
166
|
* output parses to 0/0, which would be a fabricated zero.
|
|
@@ -225,7 +204,8 @@ function suiteCountsFromGateStdout(stdout) {
|
|
|
225
204
|
* the key instead of publishing an empty array that reads as "no file failed".
|
|
226
205
|
*
|
|
227
206
|
* Only `gate-full.mjs` publishes the key, and only when the runner printed a
|
|
228
|
-
* file-level summary; every other variant
|
|
207
|
+
* file-level summary; every other variant's `test` object carries only
|
|
208
|
+
* `status` and the kill ladder.
|
|
229
209
|
*
|
|
230
210
|
* Never throws.
|
|
231
211
|
*
|
|
@@ -319,147 +299,327 @@ function resolveWaveNumber(projectDir) {
|
|
|
319
299
|
}
|
|
320
300
|
}
|
|
321
301
|
|
|
322
|
-
//
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
302
|
+
// ---------------------------------------------------------------------------
|
|
303
|
+
// Main — the CLI body. Guarded below so a bare `import()` runs none of it.
|
|
304
|
+
// ---------------------------------------------------------------------------
|
|
305
|
+
|
|
306
|
+
async function main() {
|
|
307
|
+
// Argument parsing
|
|
308
|
+
const argv = process.argv.slice(2);
|
|
309
|
+
|
|
310
|
+
if (argv.includes('-h') || argv.includes('--help')) {
|
|
311
|
+
process.stdout.write(
|
|
312
|
+
'Usage: run-quality-gate.mjs --variant <variant> [--config <json-or-file>] ' +
|
|
313
|
+
'[--files <file1,file2,...>] [--session-start-ref <ref>] [--ledger-root <path>]\n\n' +
|
|
314
|
+
'Variants: baseline, incremental, full-gate, per-file\n\n' +
|
|
315
|
+
'Exit codes:\n' +
|
|
316
|
+
' 0 — pass (non-blocking variants always exit 0)\n' +
|
|
317
|
+
' 1 — script error (bad arguments, missing dependencies)\n' +
|
|
318
|
+
' 2 — gate failed (full-gate only)\n' +
|
|
319
|
+
' 124 — gate timed out; its process group was killed (SIGTERM→SIGKILL)\n',
|
|
320
|
+
);
|
|
321
|
+
process.exit(0);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
let variant = '';
|
|
325
|
+
let config = '';
|
|
326
|
+
let files = '';
|
|
327
|
+
let sessionStartRef = '';
|
|
328
|
+
let ledgerRootArg = '';
|
|
329
|
+
|
|
330
|
+
for (let i = 0; i < argv.length; i++) {
|
|
331
|
+
const arg = argv[i];
|
|
332
|
+
switch (arg) {
|
|
333
|
+
case '--variant':
|
|
334
|
+
if (i + 1 >= argv.length) die('Missing value for --variant');
|
|
335
|
+
variant = argv[++i];
|
|
336
|
+
break;
|
|
337
|
+
case '--config':
|
|
338
|
+
if (i + 1 >= argv.length) die('Missing value for --config');
|
|
339
|
+
config = argv[++i];
|
|
340
|
+
break;
|
|
341
|
+
case '--files':
|
|
342
|
+
if (i + 1 >= argv.length) die('Missing value for --files');
|
|
343
|
+
files = argv[++i];
|
|
344
|
+
break;
|
|
345
|
+
case '--session-start-ref':
|
|
346
|
+
if (i + 1 >= argv.length) die('Missing value for --session-start-ref');
|
|
347
|
+
sessionStartRef = argv[++i];
|
|
348
|
+
break;
|
|
349
|
+
case '--ledger-root':
|
|
350
|
+
if (i + 1 >= argv.length) die('Missing value for --ledger-root');
|
|
351
|
+
ledgerRootArg = argv[++i];
|
|
352
|
+
break;
|
|
353
|
+
default:
|
|
354
|
+
die(`Unknown argument: ${arg}`);
|
|
334
355
|
}
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
if (!variant) die('Missing required argument: --variant');
|
|
359
|
+
if (!VALID_VARIANTS.includes(variant)) {
|
|
360
|
+
die(`Invalid variant: '${variant}' (allowed: ${VALID_VARIANTS.join(', ')})`);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Load policy file (never throws)
|
|
364
|
+
const repoRoot = process.cwd();
|
|
365
|
+
const policy = loadQualityGatesPolicy(repoRoot);
|
|
366
|
+
|
|
367
|
+
// Parse --config (JSON string or file path)
|
|
368
|
+
let configJson = null;
|
|
369
|
+
if (config) {
|
|
370
|
+
if (existsSync(config)) {
|
|
371
|
+
try {
|
|
372
|
+
configJson = JSON.parse(readFileSync(config, 'utf8'));
|
|
373
|
+
} catch (err) {
|
|
374
|
+
warn(`Could not parse config file '${config}': ${err.message}; using defaults`);
|
|
375
|
+
}
|
|
376
|
+
} else {
|
|
377
|
+
try {
|
|
378
|
+
configJson = JSON.parse(config);
|
|
379
|
+
} catch {
|
|
380
|
+
warn('Config is neither a valid file path nor valid JSON; using defaults');
|
|
381
|
+
}
|
|
340
382
|
}
|
|
341
383
|
}
|
|
342
|
-
}
|
|
343
384
|
|
|
344
|
-
const TYPECHECK_CMD = extractCommand(policy, 'typecheck', 'typecheck-command', configJson, DEFAULT_TYPECHECK_CMD);
|
|
345
|
-
const TEST_CMD = extractCommand(policy, 'test', 'test-command', configJson, DEFAULT_TEST_CMD);
|
|
346
|
-
const LINT_CMD = extractCommand(policy, 'lint', 'lint-command', configJson, DEFAULT_LINT_CMD);
|
|
385
|
+
const TYPECHECK_CMD = extractCommand(policy, 'typecheck', 'typecheck-command', configJson, DEFAULT_TYPECHECK_CMD);
|
|
386
|
+
const TEST_CMD = extractCommand(policy, 'test', 'test-command', configJson, DEFAULT_TEST_CMD);
|
|
387
|
+
const LINT_CMD = extractCommand(policy, 'lint', 'lint-command', configJson, DEFAULT_LINT_CMD);
|
|
347
388
|
|
|
348
|
-
// ---------------------------------------------------------------------------
|
|
349
|
-
// Gate dispatch — shell-out to existing gate-*.mjs sub-scripts
|
|
350
|
-
// ---------------------------------------------------------------------------
|
|
389
|
+
// ---------------------------------------------------------------------------
|
|
390
|
+
// Gate dispatch — shell-out to existing gate-*.mjs sub-scripts
|
|
391
|
+
// ---------------------------------------------------------------------------
|
|
351
392
|
|
|
352
|
-
const gatePath = GATE_SCRIPT[variant];
|
|
393
|
+
const gatePath = GATE_SCRIPT[variant];
|
|
353
394
|
|
|
354
|
-
if (!existsSync(gatePath)) {
|
|
355
|
-
|
|
356
|
-
}
|
|
395
|
+
if (!existsSync(gatePath)) {
|
|
396
|
+
die(`Gate script not found: ${gatePath}`);
|
|
397
|
+
}
|
|
357
398
|
|
|
358
|
-
//
|
|
359
|
-
//
|
|
360
|
-
//
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
//
|
|
364
|
-
//
|
|
365
|
-
//
|
|
366
|
-
//
|
|
367
|
-
//
|
|
368
|
-
//
|
|
369
|
-
//
|
|
370
|
-
//
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
399
|
+
// Resolved BEFORE the spawn (it used to sit beside the telemetry block below):
|
|
400
|
+
// the gate-process ledger the spawn writes is pinned to the same root as the
|
|
401
|
+
// event, for the same reason — see the spawn's `repoRoot` comment.
|
|
402
|
+
const ledgerRoot = resolveLedgerRoot(ledgerRootArg);
|
|
403
|
+
|
|
404
|
+
// `npm_config_loglevel` is INHERITED by every descendant, and the pre-push hook
|
|
405
|
+
// invokes this script as `npm run --silent quality-gate` — which sets it to
|
|
406
|
+
// `silent`. That level then reached the gate's own children: `npm pack
|
|
407
|
+
// --dry-run` emitted ZERO `npm notice` lines instead of 818 (measured
|
|
408
|
+
// 2026-08-22), so the release leakage test saw an empty listing, and several
|
|
409
|
+
// validate-plugin/e2e tests that shell out to npm went red the same way. Every
|
|
410
|
+
// one of them passes under a bare `npm test` and fails only INSIDE the gate,
|
|
411
|
+
// which is the hardest shape to diagnose and cost an hour of chasing phantoms.
|
|
412
|
+
//
|
|
413
|
+
// Pinned rather than deleted: an explicit level makes the gate's children
|
|
414
|
+
// independent of how the gate itself was invoked. `--silent` still does its real
|
|
415
|
+
// job — keeping THIS process's stdout to the single JSON envelope — because the
|
|
416
|
+
// children's output is captured by `runCheck`, never streamed.
|
|
417
|
+
/**
|
|
418
|
+
* Per-command wall-clock ceiling for PATH B (#1425 A3 / #1432).
|
|
419
|
+
*
|
|
420
|
+
* Precedence, highest first:
|
|
421
|
+
* 1. `SO_GATE_TIMEOUT_MS` — the OPERATOR override. It is inherited by every
|
|
422
|
+
* descendant, so a gate child that re-derives the ceiling gets the same
|
|
423
|
+
* answer; `resolveGateTimeoutMs()` already reads it.
|
|
424
|
+
* 2. Session Config `gate.timeout-path-b-ms` — the COMMITTED default.
|
|
425
|
+
* 3. `DEFAULT_GATE_TIMEOUT_MS` (900 000), via `resolveGateTimeoutMs()`.
|
|
426
|
+
*
|
|
427
|
+
* Published to the gate sub-script as `GATE_TIMEOUT_MS` — a DIFFERENT name from
|
|
428
|
+
* the operator override on purpose: writing the resolved value back into
|
|
429
|
+
* `SO_GATE_TIMEOUT_MS` would make a committed default indistinguishable from an
|
|
430
|
+
* operator decision for every process further down the tree.
|
|
431
|
+
*/
|
|
432
|
+
const configuredGateTimeoutMs = (() => {
|
|
433
|
+
const block = configJson !== null && typeof configJson === 'object' ? configJson.gate : null;
|
|
434
|
+
const raw = block && typeof block === 'object' ? block['timeout-path-b-ms'] : undefined;
|
|
435
|
+
return Number.isFinite(raw) && raw > 0 ? raw : null;
|
|
436
|
+
})();
|
|
437
|
+
const operatorTimeoutOverride = (process.env.SO_GATE_TIMEOUT_MS || '').trim() !== '';
|
|
438
|
+
const commandTimeoutMs = operatorTimeoutOverride || configuredGateTimeoutMs === null
|
|
439
|
+
? resolveGateTimeoutMs()
|
|
440
|
+
: configuredGateTimeoutMs;
|
|
441
|
+
|
|
442
|
+
// Gate-process REGISTER root — the same precedence the telemetry destination
|
|
443
|
+
// uses below (`--ledger-root` > project-dir env > cwd), so a sandboxed test that
|
|
444
|
+
// only sets CLAUDE_PROJECT_DIR never writes register lines into the checkout.
|
|
445
|
+
const gateLedgerRoot =
|
|
446
|
+
ledgerRoot ?? process.env.CLAUDE_PROJECT_DIR ?? process.env.CODEX_PROJECT_DIR ?? repoRoot;
|
|
447
|
+
|
|
448
|
+
const env = {
|
|
449
|
+
...process.env,
|
|
450
|
+
npm_config_loglevel: 'notice',
|
|
451
|
+
TYPECHECK_CMD,
|
|
452
|
+
TEST_CMD,
|
|
453
|
+
LINT_CMD,
|
|
454
|
+
GATE_TIMEOUT_MS: String(commandTimeoutMs),
|
|
455
|
+
// Same resolution the wrapper uses for its OWN spawn (`--ledger-root` > repo
|
|
456
|
+
// root): the gate sub-scripts pass it to runCheck() so every register line of
|
|
457
|
+
// one gate run lands in ONE ledger (#1425 A4, W5 fix-pass).
|
|
458
|
+
GATE_LEDGER_ROOT: gateLedgerRoot,
|
|
459
|
+
FILES: files,
|
|
460
|
+
SESSION_START_REF: sessionStartRef,
|
|
461
|
+
};
|
|
462
|
+
|
|
463
|
+
// The gate sub-script runs as the LEADER OF ITS OWN PROCESS GROUP under a
|
|
464
|
+
// wall-clock ceiling (Epic #1425 A3). Before this, `spawnSync('node', [gatePath])`
|
|
465
|
+
// had NO timeout at all and no group semantics: a wedged `tsgo` or vitest worker
|
|
466
|
+
// two levels down was reparented to PPID 1 and kept its memory (2026-09-20: four
|
|
467
|
+
// such orphans, up to 8.0 GB RSS each, host at 13 % free).
|
|
468
|
+
//
|
|
469
|
+
// stdout is PIPED (not inherited) so the suite counts the gate already computed
|
|
470
|
+
// can be lifted straight off its JSON envelope into telemetry (#954) instead of
|
|
471
|
+
// travelling as prose through the STATE.md header. The envelope is re-emitted
|
|
472
|
+
// verbatim below, so the stdout contract is unchanged — a gate sub-script writes
|
|
473
|
+
// exactly one JSON line at the very end (its own child commands are captured by
|
|
474
|
+
// `runCheck`), so nothing streamed before and nothing streams now.
|
|
475
|
+
//
|
|
476
|
+
// stderr stays INHERITED, and that is why `spawnFn` is overridden here rather
|
|
477
|
+
// than left at its default: `spawnInGroup` merges stdout and stderr into one
|
|
478
|
+
// capture, which would interleave the gate's failure disclosure (hundreds of
|
|
479
|
+
// lines, #1149) into the single JSON document every consumer parses off stdout.
|
|
480
|
+
// Handing it a child with `stderr: 'inherit'` leaves `child.stderr` null, the
|
|
481
|
+
// module's own `child.stderr?.on(…)` a no-op, and the gate's warnings live on
|
|
482
|
+
// the operator's terminal exactly as before.
|
|
483
|
+
// Quoted: the gate path derives from `import.meta.url`, so a space in the
|
|
484
|
+
// checkout path must not split the command.
|
|
485
|
+
const gateCommand = `node ${shellQuote(gatePath)}`;
|
|
486
|
+
// Derived from the SAME resolved per-command ceiling published as
|
|
487
|
+
// `GATE_TIMEOUT_MS` above — not from `resolveGateTimeoutMs()` a second time.
|
|
488
|
+
// The outer cap must stay strictly ABOVE the inner one so the inner kill fires
|
|
489
|
+
// first and the gate can still print its envelope; re-deriving here would leave
|
|
490
|
+
// the outer cap at the 900 000 default while a committed
|
|
491
|
+
// `gate.timeout-path-b-ms` above it made the inner cap the later of the two.
|
|
492
|
+
const gateTimeoutMs = commandTimeoutMs + GATE_OUTER_TIMEOUT_RESERVE_MS;
|
|
493
|
+
const result = await spawnInGroup(gateCommand, {
|
|
494
|
+
cwd: repoRoot,
|
|
495
|
+
env,
|
|
496
|
+
timeoutMs: gateTimeoutMs,
|
|
497
|
+
maxOutputBytes: GATE_STDOUT_MAX_BUFFER_BYTES,
|
|
498
|
+
// The ledger is what the orphan reaper (#1425 B) reads to tell its OWN gate
|
|
499
|
+
// processes from every other `node` on the host, so it is pinned to the same
|
|
500
|
+
// root the telemetry is — under the pre-push hook the tree the gate runs in
|
|
501
|
+
// is deleted seconds later, and the record with it.
|
|
502
|
+
repoRoot: gateLedgerRoot,
|
|
503
|
+
commandSignature: buildCommandSignature(gateCommand),
|
|
504
|
+
// The OWNER the ledger records, from the PROCESS-LOCAL witness only — the same
|
|
505
|
+
// rule `scripts/lib/quality-gate.mjs` follows for the gate commands it spawns
|
|
506
|
+
// (`.claude/rules/identity-and-locks.md`: rank witnesses, never union them).
|
|
507
|
+
// `.orchestrator/session.lock` is deliberately NOT consulted: it is a
|
|
508
|
+
// repo-GLOBAL artefact any session in this working copy may hold, so reading
|
|
509
|
+
// it would stamp a PEER's id onto our own gate process and point the orphan
|
|
510
|
+
// reaper (#1425 B) at the wrong owner. No witness → `null`, which the ledger
|
|
511
|
+
// records as "owner unknown" — the state every record carried before this.
|
|
512
|
+
sessionId: [...readProcessLocalSessionIds({ env: process.env, hookInput: null })][0] ?? null,
|
|
513
|
+
// stdin is IGNORED, not inherited. A gate sub-script reads no stdin, but a
|
|
514
|
+
// DETACHED child is in its own process group and is therefore not the
|
|
515
|
+
// terminal's foreground group: the first read from an inherited TTY earns it
|
|
516
|
+
// SIGTTIN, which stops the whole group until the outer ceiling kills it. An
|
|
517
|
+
// ignored stdin turns that hang into an immediate EOF.
|
|
518
|
+
spawnFn: (command, options) => spawn(command, { ...options, stdio: ['ignore', 'pipe', 'inherit'] }),
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
// `pid: -1` is `spawnInGroup`'s spawn-failure channel (it never rejects).
|
|
522
|
+
if (result.pid === -1) {
|
|
523
|
+
die(`Failed to run gate script: ${result.fullOutput.trim()}`);
|
|
524
|
+
}
|
|
380
525
|
|
|
381
|
-
//
|
|
382
|
-
//
|
|
383
|
-
//
|
|
384
|
-
//
|
|
385
|
-
//
|
|
386
|
-
//
|
|
387
|
-
//
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
const
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
526
|
+
// On timeout the child was KILLED before it could write its envelope, so its
|
|
527
|
+
// capture is at best a partial JSON document. Publishing that would hand every
|
|
528
|
+
// stdout consumer a parse error where a named failure belongs; publishing the
|
|
529
|
+
// partial text AND an envelope would break the one-document contract. So the
|
|
530
|
+
// capture goes to stderr, where the operator can still read it, and stdout
|
|
531
|
+
// carries a complete `gate-timeout` envelope instead.
|
|
532
|
+
//
|
|
533
|
+
// The DECISION lives in `publishGateOutcome` (gate-helpers.mjs) and only the
|
|
534
|
+
// WRITES live here: this branch needs a real 16-minute gate to reach, so while
|
|
535
|
+
// the decision was inline it was pinned by nothing.
|
|
536
|
+
const outcome = publishGateOutcome({ result, variant, timeoutMs: gateTimeoutMs });
|
|
537
|
+
const gateStdout = result.timedOut ? '' : result.fullOutput;
|
|
538
|
+
if (outcome.stderr) process.stderr.write(outcome.stderr);
|
|
539
|
+
if (outcome.stdout) process.stdout.write(outcome.stdout);
|
|
540
|
+
for (const line of outcome.warnings) warn(line);
|
|
541
|
+
|
|
542
|
+
// Quality-gate telemetry — one canonical event per gate run via emitEvent
|
|
543
|
+
// (single emission path). `sessionAttribution` is the shared helper in
|
|
544
|
+
// events.mjs (#941); this CLI wrapper runs against the CWD `repoRoot`, so the
|
|
545
|
+
// bare emitEvent destination (SO_PROJECT_DIR default) is correct here.
|
|
546
|
+
//
|
|
547
|
+
// EXCEPT under the pre-push hook, which is the one caller that runs the gate in
|
|
548
|
+
// a tree that is about to be DELETED. `.husky/pre-push` materialises the tracked
|
|
549
|
+
// tree into a temp dir and deliberately scrubs every `*PROJECT_DIR` name before
|
|
550
|
+
// invoking the gate there, so `getProjectDir()` resolves to that temp tree (it
|
|
551
|
+
// carries both a CLAUDE.md (or AGENTS.md) and a .git) and the record lands in
|
|
552
|
+
// `<tmp>/.orchestrator/metrics/events.jsonl`, which the hook's EXIT trap then
|
|
553
|
+
// removes. Measured 2026-09-06: a pre-push run that BLOCKED a push left no
|
|
554
|
+
// `orchestrator.quality_gate.failed` line in this repo's ledger at all — the
|
|
555
|
+
// gate failure was, by construction, the one event that could never be recorded.
|
|
556
|
+
//
|
|
557
|
+
// `--ledger-root` is that hook's channel for handing back the root it already
|
|
558
|
+
// knows (`git rev-parse --show-toplevel`, read BEFORE it cds). It pins ONLY the
|
|
559
|
+
// telemetry destination and the attribution root — every other path the gate
|
|
560
|
+
// resolves stays inside the tree actually under test, which is the whole point
|
|
561
|
+
// of the materialisation. Absent (every other caller) → unchanged behaviour:
|
|
562
|
+
// `emitEvent`'s own default resolution.
|
|
563
|
+
//
|
|
564
|
+
// It is an ARGV FLAG and not an env var, and that is load-bearing. Measured
|
|
565
|
+
// 2026-09-06 with the env-var form: `SO_GATE_LEDGER_ROOT=$tmp npx vitest run
|
|
566
|
+
// tests/scripts/run-quality-gate.test.mjs -t "telemetry emission"` → `8 failed |
|
|
567
|
+
// 1 passed`. The chain was: hook exports the var → `npm run quality-gate` →
|
|
568
|
+
// `gate-full.mjs` spawns `npm test` → every vitest worker inherits it → the
|
|
569
|
+
// suite's own gate spawns spread `...process.env`, so the pinned root outranked
|
|
570
|
+
// their per-test project dir and the gate's telemetry tests wrote to the hook's
|
|
571
|
+
// root. The gate that releases 4.0.0 would have blocked on itself. An env var is
|
|
572
|
+
// inherited by every descendant; a flag reaches exactly one process.
|
|
573
|
+
//
|
|
574
|
+
// Best-effort: a telemetry failure must NEVER alter the gate's authoritative
|
|
575
|
+
// exit code — which is why the counts parse also lives inside this try.
|
|
576
|
+
const exitCode = outcome.exitCode;
|
|
577
|
+
try {
|
|
578
|
+
const counts = suiteCountsFromGateStdout(gateStdout);
|
|
579
|
+
// The names behind `counts.failed`. Absent, never `[]` — see
|
|
580
|
+
// `failedFilesFromGateStdout`.
|
|
581
|
+
const failedFiles = failedFilesFromGateStdout(gateStdout);
|
|
582
|
+
// Wave-scope sidecar is read from the SAME project dir the event lands in
|
|
583
|
+
// (emitEvent's own destination precedence), so a tmp-scoped run cannot pick
|
|
584
|
+
// up the host repo's live wave. Mirrors the hook's projectDir resolution.
|
|
585
|
+
const waveNumber = resolveWaveNumber(
|
|
586
|
+
ledgerRoot ?? process.env.CLAUDE_PROJECT_DIR ?? process.env.CODEX_PROJECT_DIR ?? repoRoot,
|
|
587
|
+
);
|
|
588
|
+
// `timed_out` / `survivors` / `kill_signals`: the OUTER ladder (the gate
|
|
589
|
+
// sub-script as a whole, #1439 — same builder as the library emitter)
|
|
590
|
+
// OR-joined with the INNER per-command ladders the envelope publishes
|
|
591
|
+
// (#1457). Keys omitted when the inner ladder was not measured and the
|
|
592
|
+
// outer one did not fire — see `joinGateKillFields`.
|
|
593
|
+
const killFields = joinGateKillFields({ outer: gateKillFields(result), gateStdout });
|
|
594
|
+
// A killed gate is `failed` in every variant (#1459, owner decision
|
|
595
|
+
// 2026-10-01). The reporting variants (baseline / incremental / per-file)
|
|
596
|
+
// exit 0 even when a command was killed, so the exit code alone would
|
|
597
|
+
// record that run as `passed`. Their exit code stays 0 on purpose: it has
|
|
598
|
+
// never carried the check verdict there (a failing check exits 0 too).
|
|
599
|
+
const failed = exitCode !== 0 || killFields.timed_out === true;
|
|
600
|
+
await emitEvent(
|
|
601
|
+
`orchestrator.quality_gate.${failed ? 'failed' : 'passed'}`,
|
|
602
|
+
{
|
|
603
|
+
variant,
|
|
604
|
+
exit_code: exitCode,
|
|
605
|
+
...killFields,
|
|
606
|
+
...(counts ? { counts } : {}),
|
|
607
|
+
...(failedFiles ? { failed_files: failedFiles } : {}),
|
|
608
|
+
...(waveNumber !== null ? { wave_number: waveNumber } : {}),
|
|
609
|
+
...sessionAttribution(ledgerRoot ?? repoRoot),
|
|
610
|
+
},
|
|
611
|
+
// `{}` is byte-identical to omitting the argument (`opts.repoRoot ??
|
|
612
|
+
// getProjectDir()`), so the default path is untouched.
|
|
613
|
+
ledgerRoot ? { repoRoot: ledgerRoot } : {},
|
|
614
|
+
);
|
|
615
|
+
} catch { /* best-effort telemetry — gate result is authoritative */ }
|
|
616
|
+
|
|
617
|
+
process.exit(exitCode);
|
|
400
618
|
}
|
|
401
619
|
|
|
402
|
-
//
|
|
403
|
-
//
|
|
404
|
-
//
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
// a tree that is about to be DELETED. `.husky/pre-push` materialises the tracked
|
|
409
|
-
// tree into a temp dir and deliberately scrubs every `*PROJECT_DIR` name before
|
|
410
|
-
// invoking the gate there, so `getProjectDir()` resolves to that temp tree (it
|
|
411
|
-
// carries both a CLAUDE.md (or AGENTS.md) and a .git) and the record lands in
|
|
412
|
-
// `<tmp>/.orchestrator/metrics/events.jsonl`, which the hook's EXIT trap then
|
|
413
|
-
// removes. Measured 2026-09-06: a pre-push run that BLOCKED a push left no
|
|
414
|
-
// `orchestrator.quality_gate.failed` line in this repo's ledger at all — the
|
|
415
|
-
// gate failure was, by construction, the one event that could never be recorded.
|
|
416
|
-
//
|
|
417
|
-
// `--ledger-root` is that hook's channel for handing back the root it already
|
|
418
|
-
// knows (`git rev-parse --show-toplevel`, read BEFORE it cds). It pins ONLY the
|
|
419
|
-
// telemetry destination and the attribution root — every other path the gate
|
|
420
|
-
// resolves stays inside the tree actually under test, which is the whole point
|
|
421
|
-
// of the materialisation. Absent (every other caller) → unchanged behaviour:
|
|
422
|
-
// `emitEvent`'s own default resolution.
|
|
423
|
-
//
|
|
424
|
-
// It is an ARGV FLAG and not an env var, and that is load-bearing. Measured
|
|
425
|
-
// 2026-09-06 with the env-var form: `SO_GATE_LEDGER_ROOT=$tmp npx vitest run
|
|
426
|
-
// tests/scripts/run-quality-gate.test.mjs -t "telemetry emission"` → `8 failed |
|
|
427
|
-
// 1 passed`. The chain was: hook exports the var → `npm run quality-gate` →
|
|
428
|
-
// `gate-full.mjs` spawns `npm test` → every vitest worker inherits it → the
|
|
429
|
-
// suite's own gate spawns spread `...process.env`, so the pinned root outranked
|
|
430
|
-
// their per-test project dir and the gate's telemetry tests wrote to the hook's
|
|
431
|
-
// root. The gate that releases 4.0.0 would have blocked on itself. An env var is
|
|
432
|
-
// inherited by every descendant; a flag reaches exactly one process.
|
|
433
|
-
//
|
|
434
|
-
// Best-effort: a telemetry failure must NEVER alter the gate's authoritative
|
|
435
|
-
// exit code — which is why the counts parse also lives inside this try.
|
|
436
|
-
const exitCode = result.status ?? 1;
|
|
437
|
-
const ledgerRoot = resolveLedgerRoot(ledgerRootArg);
|
|
438
|
-
try {
|
|
439
|
-
const counts = suiteCountsFromGateStdout(gateStdout);
|
|
440
|
-
// The names behind `counts.failed`. Absent, never `[]` — see
|
|
441
|
-
// `failedFilesFromGateStdout`.
|
|
442
|
-
const failedFiles = failedFilesFromGateStdout(gateStdout);
|
|
443
|
-
// Wave-scope sidecar is read from the SAME project dir the event lands in
|
|
444
|
-
// (emitEvent's own destination precedence), so a tmp-scoped run cannot pick
|
|
445
|
-
// up the host repo's live wave. Mirrors the hook's projectDir resolution.
|
|
446
|
-
const waveNumber = resolveWaveNumber(
|
|
447
|
-
ledgerRoot ?? process.env.CLAUDE_PROJECT_DIR ?? process.env.CODEX_PROJECT_DIR ?? repoRoot,
|
|
448
|
-
);
|
|
449
|
-
await emitEvent(
|
|
450
|
-
`orchestrator.quality_gate.${exitCode === 0 ? 'passed' : 'failed'}`,
|
|
451
|
-
{
|
|
452
|
-
variant,
|
|
453
|
-
exit_code: exitCode,
|
|
454
|
-
...(counts ? { counts } : {}),
|
|
455
|
-
...(failedFiles ? { failed_files: failedFiles } : {}),
|
|
456
|
-
...(waveNumber !== null ? { wave_number: waveNumber } : {}),
|
|
457
|
-
...sessionAttribution(ledgerRoot ?? repoRoot),
|
|
458
|
-
},
|
|
459
|
-
// `{}` is byte-identical to omitting the argument (`opts.repoRoot ??
|
|
460
|
-
// getProjectDir()`), so the default path is untouched.
|
|
461
|
-
ledgerRoot ? { repoRoot: ledgerRoot } : {},
|
|
462
|
-
);
|
|
463
|
-
} catch { /* best-effort telemetry — gate result is authoritative */ }
|
|
464
|
-
|
|
465
|
-
process.exit(exitCode);
|
|
620
|
+
// Entry guard (#1440 C1): run only as the node script `npm run quality-gate` /
|
|
621
|
+
// the pre-push hook exec — a bare `import()` must parse no argv, spawn no gate
|
|
622
|
+
// and never `process.exit` the importing process.
|
|
623
|
+
if (isMainModule(import.meta.url)) {
|
|
624
|
+
await main();
|
|
625
|
+
}
|