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
|
@@ -8,10 +8,265 @@
|
|
|
8
8
|
|
|
9
9
|
import { execSync } from 'node:child_process';
|
|
10
10
|
import { readFileSync } from 'node:fs';
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
DEFAULT_GATE_TIMEOUT_MS,
|
|
14
|
+
buildCommandSignature,
|
|
15
|
+
spawnInGroup,
|
|
16
|
+
} from '../process-group.mjs';
|
|
11
17
|
import { detectStubCommand } from './echo-stub-detect.mjs';
|
|
12
18
|
|
|
13
19
|
const RUN_CHECK_MAX_BUFFER_BYTES = 64 * 1024 * 1024;
|
|
14
20
|
|
|
21
|
+
/** Lines of captured output kept in the human-facing `output` tail. */
|
|
22
|
+
const OUTPUT_TAIL_LINES = 5;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Name of the per-invocation override for the gate wall-clock ceiling.
|
|
26
|
+
*
|
|
27
|
+
* Read at CALL time, never at module load, so a test (or a caller that sets it
|
|
28
|
+
* for one child) is not defeated by import order.
|
|
29
|
+
*
|
|
30
|
+
* Named ceiling (BV-004): this is an ENV var, and an env var is inherited by
|
|
31
|
+
* every descendant — the exact shape that made `SO_GATE_LEDGER_ROOT` reach
|
|
32
|
+
* every vitest worker on 2026-09-06 (`scripts/run-quality-gate.mjs`, the
|
|
33
|
+
* `--ledger-root` block). It is acceptable here only because the value is a
|
|
34
|
+
* CEILING every descendant should honour anyway. A future Session-Config
|
|
35
|
+
* wiring of `gate.timeout-path-b-ms` should prefer the explicit `timeoutMs`
|
|
36
|
+
* option below — which reaches exactly one call — over exporting this name.
|
|
37
|
+
*/
|
|
38
|
+
export const GATE_TIMEOUT_ENV = 'SO_GATE_TIMEOUT_MS';
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The wall-clock ceiling one gate command is allowed, in ms.
|
|
42
|
+
*
|
|
43
|
+
* Precedence: `process.env.SO_GATE_TIMEOUT_MS` when set to a finite positive
|
|
44
|
+
* number, else {@link DEFAULT_GATE_TIMEOUT_MS} (900_000 — the PRD's
|
|
45
|
+
* `gate.timeout-path-b-ms`, deliberately the same 15 min the synchronous
|
|
46
|
+
* path A already had, so both gate paths are allowed exactly as long).
|
|
47
|
+
*
|
|
48
|
+
* A non-numeric or non-positive value is IGNORED rather than honoured: a typo
|
|
49
|
+
* that parsed as 0 would disable the cap, which is the failure this whole
|
|
50
|
+
* change exists to remove.
|
|
51
|
+
*
|
|
52
|
+
* @returns {number} positive milliseconds
|
|
53
|
+
*/
|
|
54
|
+
export function resolveGateTimeoutMs() {
|
|
55
|
+
const raw = (process.env[GATE_TIMEOUT_ENV] || '').trim();
|
|
56
|
+
if (!raw) return DEFAULT_GATE_TIMEOUT_MS;
|
|
57
|
+
const parsed = Number(raw);
|
|
58
|
+
if (!Number.isFinite(parsed) || parsed <= 0) return DEFAULT_GATE_TIMEOUT_MS;
|
|
59
|
+
return parsed;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Last {@link OUTPUT_TAIL_LINES} lines of a captured text, trimmed.
|
|
64
|
+
*
|
|
65
|
+
* @param {string} text
|
|
66
|
+
* @returns {string}
|
|
67
|
+
*/
|
|
68
|
+
function tailOf(text) {
|
|
69
|
+
return String(text ?? '').split('\n').slice(-OUTPUT_TAIL_LINES).join('\n').trim();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The one line a timed-out gate command MUST carry.
|
|
74
|
+
*
|
|
75
|
+
* A timeout is a FAILURE with a name, never a silent `fail`: without this line
|
|
76
|
+
* a killed command is indistinguishable in the envelope from a command that
|
|
77
|
+
* exited non-zero on its own, and the operator has nothing to act on. It names
|
|
78
|
+
* the ceiling that fired, the process GROUP that was signalled, the ladder that
|
|
79
|
+
* ran, and — the part an exit code can never carry — anything that SURVIVED
|
|
80
|
+
* SIGKILL (PRD B6: a sent signal proves nothing).
|
|
81
|
+
*
|
|
82
|
+
* @param {number} timeoutMs
|
|
83
|
+
* @param {{pgid: number, killSignals: string[], survivors: number[]}} run
|
|
84
|
+
* @returns {string}
|
|
85
|
+
*/
|
|
86
|
+
function timeoutLine(timeoutMs, run) {
|
|
87
|
+
const ladder = (run.killSignals ?? []).join('\u2192') || 'no signal sent';
|
|
88
|
+
return `gate: TIMEOUT after ${timeoutMs} ms \u2014 process group ${run.pgid} ${ladder}, `
|
|
89
|
+
+ `survivors: [${(run.survivors ?? []).join(', ')}]`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* The stdout envelope `scripts/run-quality-gate.mjs` publishes when the GATE
|
|
94
|
+
* SUB-SCRIPT itself was killed on the wall-clock ceiling.
|
|
95
|
+
*
|
|
96
|
+
* Lives here, beside the other envelope helpers, because the CLI that consumes
|
|
97
|
+
* it is a top-level script with no exports — a pure function there would be
|
|
98
|
+
* untestable without executing the CLI.
|
|
99
|
+
*
|
|
100
|
+
* Deliberately carries no `test`/`typecheck`/`lint` object: a killed gate
|
|
101
|
+
* measured nothing, and `suiteCountsFromGateStdout` must return `null` for it
|
|
102
|
+
* (absent is not zero). `error: 'gate-timeout'` is the machine-readable
|
|
103
|
+
* discriminator; the exit code is 124, the same value `spawnInGroup` reports
|
|
104
|
+
* and the same one coreutils `timeout(1)` uses.
|
|
105
|
+
*
|
|
106
|
+
* @param {object} args
|
|
107
|
+
* @param {string} args.variant The `--variant` value the run was started with.
|
|
108
|
+
* @param {number} args.timeoutMs Ceiling that fired.
|
|
109
|
+
* @param {{pgid: number, durationMs: number, killSignals: string[], survivors: number[]}} args.run
|
|
110
|
+
* The {@link spawnInGroup} result.
|
|
111
|
+
* @returns {{variant: string, error: 'gate-timeout', timeout_ms: number, duration_ms: number,
|
|
112
|
+
* pgid: number, kill_signals: string[], survivors: number[]}}
|
|
113
|
+
*/
|
|
114
|
+
export function gateTimeoutEnvelope({ variant, timeoutMs, run }) {
|
|
115
|
+
return {
|
|
116
|
+
variant,
|
|
117
|
+
error: 'gate-timeout',
|
|
118
|
+
timeout_ms: timeoutMs,
|
|
119
|
+
duration_ms: run?.durationMs ?? 0,
|
|
120
|
+
pgid: run?.pgid ?? -1,
|
|
121
|
+
kill_signals: run?.killSignals ?? [],
|
|
122
|
+
survivors: run?.survivors ?? [],
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* The INNER kill ladder of one gate command, in the gate envelope's own
|
|
128
|
+
* snake_case shape (#1457 point 1).
|
|
129
|
+
*
|
|
130
|
+
* `runCheck` knows, per command, whether its per-command ceiling fired, which
|
|
131
|
+
* signals it sent and which pids survived the SIGKILL step. Until this helper
|
|
132
|
+
* that knowledge died inside the gate sub-script, so a command killed by the
|
|
133
|
+
* INNER ceiling reached `orchestrator.quality_gate.failed` as
|
|
134
|
+
* `timed_out: false`. Spread it into the command's envelope object.
|
|
135
|
+
*
|
|
136
|
+
* Absent is not zero: a result that spawned nothing (skip, stub) carries no
|
|
137
|
+
* `timedOut` boolean, and gets NO keys — never `false` / `[]`.
|
|
138
|
+
*
|
|
139
|
+
* @param {{timedOut?: boolean, killSignals?: string[], survivors?: number[]}} result
|
|
140
|
+
* A {@link runCheck} result.
|
|
141
|
+
* @returns {{timed_out: boolean, kill_signals: string[], survivors: number[]}|{}}
|
|
142
|
+
* `survivors` are the pids, same as {@link gateTimeoutEnvelope}.
|
|
143
|
+
*/
|
|
144
|
+
export function commandKillFields(result) {
|
|
145
|
+
if (typeof result?.timedOut !== 'boolean') return {};
|
|
146
|
+
return {
|
|
147
|
+
timed_out: result.timedOut,
|
|
148
|
+
kill_signals: Array.isArray(result.killSignals) ? [...result.killSignals] : [],
|
|
149
|
+
survivors: Array.isArray(result.survivors) ? [...result.survivors] : [],
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** Envelope keys, in execution order, that may carry {@link commandKillFields}. */
|
|
154
|
+
const KILL_FIELD_COMMANDS = ['typecheck', 'test', 'lint'];
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Join the OUTER kill ladder (the gate sub-script as a whole) with the INNER
|
|
158
|
+
* per-command ladders its JSON envelope publishes (#1457 point 1), into the
|
|
159
|
+
* `timed_out` / `survivors` / `kill_signals` keys of
|
|
160
|
+
* `orchestrator.quality_gate.{passed,failed}`.
|
|
161
|
+
*
|
|
162
|
+
* - inner measured (the envelope parses and EVERY command object either carries
|
|
163
|
+
* a boolean `timed_out` or provably spawned nothing — `status: 'skip'` or
|
|
164
|
+
* listed in `stubbed`): `timed_out` = outer OR any inner; `kill_signals` =
|
|
165
|
+
* outer followed by each command's, in execution order; `survivors` = outer
|
|
166
|
+
* count plus every inner pid count.
|
|
167
|
+
* - inner NOT measured (a variant whose envelope does not publish it, an
|
|
168
|
+
* unparseable envelope, or the outer ceiling killed the sub-script before it
|
|
169
|
+
* wrote one): the outer ladder alone when it fired — a kill is a kill — and
|
|
170
|
+
* otherwise NO keys, because `false` / `0` / `[]` would claim that no
|
|
171
|
+
* command was killed when nobody looked.
|
|
172
|
+
*
|
|
173
|
+
* Never throws.
|
|
174
|
+
*
|
|
175
|
+
* @param {object} args
|
|
176
|
+
* @param {{timed_out: boolean, survivors: number, kill_signals: string[]}} args.outer
|
|
177
|
+
* `gateKillFields()` of the sub-script's `spawnInGroup` result.
|
|
178
|
+
* @param {string} args.gateStdout The sub-script's stdout envelope (`''` on an outer kill).
|
|
179
|
+
* @returns {{timed_out: boolean, survivors: number, kill_signals: string[]}|{}}
|
|
180
|
+
*/
|
|
181
|
+
export function joinGateKillFields({ outer, gateStdout }) {
|
|
182
|
+
let parsed = null;
|
|
183
|
+
if (typeof gateStdout === 'string' && gateStdout.trim()) {
|
|
184
|
+
try {
|
|
185
|
+
parsed = JSON.parse(gateStdout);
|
|
186
|
+
} catch {
|
|
187
|
+
parsed = null;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
const isObj = (v) => v !== null && typeof v === 'object' && !Array.isArray(v);
|
|
191
|
+
const names = isObj(parsed) ? KILL_FIELD_COMMANDS.filter((name) => isObj(parsed[name])) : [];
|
|
192
|
+
const stubbed = isObj(parsed?.stubbed) ? parsed.stubbed : {};
|
|
193
|
+
// A command either published its ladder, or provably spawned nothing (skip /
|
|
194
|
+
// stub) — nothing killed is then a measurement too. Any OTHER command (a
|
|
195
|
+
// variant that ran it but does not publish the ladder) makes the inner side
|
|
196
|
+
// unknown as a whole.
|
|
197
|
+
const inner = names.map((name) => parsed[name]).filter((c) => typeof c.timed_out === 'boolean');
|
|
198
|
+
const innerKnown = names.length > 0 && names.every((name) => typeof parsed[name].timed_out === 'boolean'
|
|
199
|
+
|| parsed[name].status === 'skip'
|
|
200
|
+
|| Boolean(stubbed[name]));
|
|
201
|
+
|
|
202
|
+
if (!innerKnown) return outer?.timed_out === true ? { ...outer } : {};
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
timed_out: outer?.timed_out === true || inner.some((c) => c.timed_out),
|
|
206
|
+
survivors: (outer?.survivors ?? 0)
|
|
207
|
+
+ inner.reduce((n, c) => n + (Array.isArray(c.survivors) ? c.survivors.length : 0), 0),
|
|
208
|
+
kill_signals: [
|
|
209
|
+
...(outer?.kill_signals ?? []),
|
|
210
|
+
...inner.flatMap((c) => (Array.isArray(c.kill_signals) ? c.kill_signals : [])),
|
|
211
|
+
],
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Decide WHAT a finished gate sub-script run publishes — stdout, stderr,
|
|
217
|
+
* exit code and operator warnings — without performing any of the writes.
|
|
218
|
+
*
|
|
219
|
+
* ## Why this is a pure function and not four `process.*.write` calls
|
|
220
|
+
*
|
|
221
|
+
* The timeout branch in `scripts/run-quality-gate.mjs` was unreachable by any
|
|
222
|
+
* test: reaching it required a REAL gate sub-script to exceed
|
|
223
|
+
* `resolveGateTimeoutMs() + GATE_OUTER_TIMEOUT_RESERVE_MS` (15 min + 60 s, a
|
|
224
|
+
* hard constant with no injection seam), so the four decisions it makes —
|
|
225
|
+
* suppress the partial capture on stdout, re-publish it on stderr, emit ONE
|
|
226
|
+
* complete `gate-timeout` envelope, warn about survivors — were pinned by
|
|
227
|
+
* nothing. The CLI now decides here and only WRITES there, so each decision is
|
|
228
|
+
* testable against a synthetic {@link spawnInGroup} result.
|
|
229
|
+
*
|
|
230
|
+
* ## The one-document contract on stdout
|
|
231
|
+
*
|
|
232
|
+
* A killed child never wrote its envelope, so its capture is at best a partial
|
|
233
|
+
* JSON document. Publishing that hands every stdout consumer a parse error
|
|
234
|
+
* where a named failure belongs; publishing BOTH the partial text and an
|
|
235
|
+
* envelope breaks the "one JSON document" contract. Hence: capture → stderr,
|
|
236
|
+
* envelope → stdout, and `stdout` carries EXACTLY the envelope line.
|
|
237
|
+
*
|
|
238
|
+
* @param {object} args
|
|
239
|
+
* @param {{fullOutput?: string, exitCode?: number, timedOut?: boolean, pgid?: number,
|
|
240
|
+
* durationMs?: number, killSignals?: string[], survivors?: number[]}} args.result
|
|
241
|
+
* The {@link spawnInGroup} result for the gate sub-script.
|
|
242
|
+
* @param {string} args.variant The `--variant` value the run was started with.
|
|
243
|
+
* @param {number} args.timeoutMs The OUTER ceiling that applied to the sub-script.
|
|
244
|
+
* @returns {{stdout: string, stderr: string, exitCode: number, warnings: string[]}}
|
|
245
|
+
* `stdout`/`stderr` are written verbatim (empty string = write nothing);
|
|
246
|
+
* `warnings` go through the caller's `warn()`; `exitCode` is the gate's own,
|
|
247
|
+
* which `spawnInGroup` reports as 124 on the timeout path.
|
|
248
|
+
*/
|
|
249
|
+
export function publishGateOutcome({ result, variant, timeoutMs }) {
|
|
250
|
+
const capture = String(result?.fullOutput ?? '');
|
|
251
|
+
const exitCode = result?.exitCode ?? 0;
|
|
252
|
+
|
|
253
|
+
if (!result?.timedOut) {
|
|
254
|
+
return { stdout: capture, stderr: '', exitCode, warnings: [] };
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const survivors = result.survivors ?? [];
|
|
258
|
+
return {
|
|
259
|
+
stdout: `${JSON.stringify(gateTimeoutEnvelope({ variant, timeoutMs, run: result }))}\n`,
|
|
260
|
+
stderr: capture.trim()
|
|
261
|
+
? `\n\u2500\u2500\u2500\u2500 gate TIMED OUT \u2014 captured output before the kill \u2500\u2500\u2500\u2500\n${capture}\n\u2500\u2500\u2500\u2500 end \u2500\u2500\u2500\u2500\n`
|
|
262
|
+
: '',
|
|
263
|
+
exitCode,
|
|
264
|
+
warnings: survivors.length > 0
|
|
265
|
+
? [`gate process group ${result.pgid} left survivors after SIGKILL: ${survivors.join(', ')}`]
|
|
266
|
+
: [],
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
15
270
|
// ---------------------------------------------------------------------------
|
|
16
271
|
// Internal pattern helpers
|
|
17
272
|
// ---------------------------------------------------------------------------
|
|
@@ -39,7 +294,8 @@ function isTestFile(filePath) {
|
|
|
39
294
|
// ---------------------------------------------------------------------------
|
|
40
295
|
|
|
41
296
|
/**
|
|
42
|
-
* Execute a shell command
|
|
297
|
+
* Execute a shell command as the LEADER OF ITS OWN PROCESS GROUP, under a
|
|
298
|
+
* wall-clock ceiling, and return a structured result.
|
|
43
299
|
*
|
|
44
300
|
* `output` is a bounded TAIL for humans. `fullOutput` is the complete captured
|
|
45
301
|
* text and is what every COUNT parse must read.
|
|
@@ -52,10 +308,47 @@ function isTestFile(filePath) {
|
|
|
52
308
|
* "the runner never produced results" and hides WHICH test failed. An hour was
|
|
53
309
|
* spent chasing that phantom before the real cause (one red test) was found.
|
|
54
310
|
*
|
|
311
|
+
* ## Why a process group, and why a timeout (Epic #1425 A3)
|
|
312
|
+
*
|
|
313
|
+
* This used to be `execSync(cmd, { maxBuffer })` — no timeout at all, and the
|
|
314
|
+
* shell as the only signalled process. On 2026-09-20 four `tsgo --noEmit`
|
|
315
|
+
* grandchildren of gate runs outlived their parents at PPID 1 with up to 8.0 GB
|
|
316
|
+
* RSS each and took the host to 13 % free memory. {@link spawnInGroup} spawns
|
|
317
|
+
* `detached`, so `process.kill(-pgid, …)` reaches every descendant, and it runs
|
|
318
|
+
* the SIGTERM→grace→SIGKILL ladder with a read-back verification.
|
|
319
|
+
*
|
|
320
|
+
* ## What changed for callers
|
|
321
|
+
*
|
|
322
|
+
* 1. It is ASYNC. Every call site must `await`.
|
|
323
|
+
* 2. `fullOutput` now interleaves stdout AND stderr on the PASS path too
|
|
324
|
+
* (`execSync` discarded stderr when the command succeeded). A runner that
|
|
325
|
+
* prints its summary to stderr is therefore no longer invisible to
|
|
326
|
+
* {@link extractTestCounts}.
|
|
327
|
+
* 3. Three fields are added — `timedOut`, `killSignals`, `survivors` — and they
|
|
328
|
+
* are present ONLY when a process actually ran. A skipped or stubbed command
|
|
329
|
+
* spawned nothing, so it carries no `timedOut: false`: absent is not a
|
|
330
|
+
* measured false, the same contract `counts` and `files` already keep.
|
|
331
|
+
*
|
|
332
|
+
* A timeout is a REPORTED failure: `status: 'fail'`, `exitCode: 124`, and a
|
|
333
|
+
* `gate: TIMEOUT after …` line appended to `output`/`fullOutput` naming the
|
|
334
|
+
* ceiling, the group, the signal ladder and any survivor. It is never a silent
|
|
335
|
+
* `fail`.
|
|
336
|
+
*
|
|
55
337
|
* @param {string} cmd - Shell command to run, or `"skip"` / empty to skip.
|
|
56
|
-
* @
|
|
338
|
+
* @param {object} [opts] - Forwarded verbatim to {@link spawnInGroup}, and it
|
|
339
|
+
* OVERRIDES the defaults below (`timeoutMs`, `maxOutputBytes`, `repoRoot`,
|
|
340
|
+
* `commandSignature`). This is also the seam tests inject `spawnFn` /
|
|
341
|
+
* `killFn` / `isAliveFn` through.
|
|
342
|
+
* @param {number|null} [opts.timeoutMs] - Wall-clock ceiling; defaults to
|
|
343
|
+
* {@link resolveGateTimeoutMs} (`SO_GATE_TIMEOUT_MS` or 900_000). `null`
|
|
344
|
+
* disables the clock — the byte cap still applies.
|
|
345
|
+
* @param {string} [opts.repoRoot] - Root whose gate-process ledger the spawn is
|
|
346
|
+
* recorded in; defaults to `process.cwd()`, which is the tree under test.
|
|
347
|
+
* @returns {Promise<{ status: 'pass'|'fail'|'skip', output: string, fullOutput: string,
|
|
348
|
+
* exitCode: number, timedOut?: boolean, killSignals?: string[], survivors?: number[],
|
|
349
|
+
* stubbed?: { kind: 'echo'|'noop' } }>}
|
|
57
350
|
*/
|
|
58
|
-
export function runCheck(cmd) {
|
|
351
|
+
export async function runCheck(cmd, opts = {}) {
|
|
59
352
|
if (!cmd || cmd === 'skip') {
|
|
60
353
|
return { status: 'skip', output: '', fullOutput: '', exitCode: 0 };
|
|
61
354
|
}
|
|
@@ -65,26 +358,44 @@ export function runCheck(cmd) {
|
|
|
65
358
|
return { status: 'pass', output: `(stubbed: ${stub.kind})`, fullOutput: `(stubbed: ${stub.kind})`, exitCode: 0, stubbed: { kind: stub.kind } };
|
|
66
359
|
}
|
|
67
360
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
361
|
+
const timeoutMs = opts.timeoutMs === undefined ? resolveGateTimeoutMs() : opts.timeoutMs;
|
|
362
|
+
const run = await spawnInGroup(cmd, {
|
|
363
|
+
maxOutputBytes: RUN_CHECK_MAX_BUFFER_BYTES,
|
|
364
|
+
repoRoot: process.cwd(),
|
|
365
|
+
commandSignature: buildCommandSignature(cmd),
|
|
366
|
+
...opts,
|
|
367
|
+
timeoutMs,
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
// Reported in every returned shape below, so a consumer never has to ask a
|
|
371
|
+
// second question to learn whether the group is actually gone.
|
|
372
|
+
const groupFields = { killSignals: run.killSignals, survivors: run.survivors };
|
|
373
|
+
|
|
374
|
+
if (run.timedOut) {
|
|
375
|
+
const fullOutput = `${run.fullOutput}\n${timeoutLine(timeoutMs, run)}\n`;
|
|
376
|
+
return {
|
|
377
|
+
status: 'fail',
|
|
378
|
+
output: tailOf(fullOutput),
|
|
379
|
+
fullOutput,
|
|
380
|
+
exitCode: run.exitCode,
|
|
381
|
+
timedOut: true,
|
|
382
|
+
...groupFields,
|
|
383
|
+
};
|
|
384
|
+
}
|
|
83
385
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
return { status: '
|
|
386
|
+
// Exit code 127 means command not found — treat as skip.
|
|
387
|
+
if (run.exitCode === 127) {
|
|
388
|
+
return { status: 'skip', output: 'command not found', fullOutput: '', exitCode: 127, timedOut: false, ...groupFields };
|
|
87
389
|
}
|
|
390
|
+
|
|
391
|
+
return {
|
|
392
|
+
status: run.exitCode === 0 ? 'pass' : 'fail',
|
|
393
|
+
output: tailOf(run.fullOutput),
|
|
394
|
+
fullOutput: run.fullOutput,
|
|
395
|
+
exitCode: run.exitCode,
|
|
396
|
+
timedOut: false,
|
|
397
|
+
...groupFields,
|
|
398
|
+
};
|
|
88
399
|
}
|
|
89
400
|
|
|
90
401
|
/**
|
|
@@ -392,8 +703,10 @@ export function extractFailedTestFiles(output, cwd = process.cwd()) {
|
|
|
392
703
|
*
|
|
393
704
|
* This function sees a candidate triple and nothing else. It performs no I/O and
|
|
394
705
|
* reads no files. The following are per-caller INPUT ADAPTERS and must not
|
|
395
|
-
* migrate here: JSON-envelope parsing; the `test`-
|
|
396
|
-
* variant
|
|
706
|
+
* migrate here: JSON-envelope parsing; the `test`-must-be-an-object check
|
|
707
|
+
* (every variant emits an object since #1457 pkt 12; only full-gate's carries
|
|
708
|
+
* counts, so the others fall through to this function's own finite-number
|
|
709
|
+
* refusal);
|
|
397
710
|
* `test.status ∈ {pass, fail}`; the `parsed.stubbed?.test` short-circuit; the
|
|
398
711
|
* raw-text tail parse ({@link extractTestCounts}); and the positional evidence
|
|
399
712
|
* that the test gate ran at all — which each caller expresses by handing over
|
|
@@ -6,13 +6,38 @@
|
|
|
6
6
|
// Required env: TYPECHECK_CMD, TEST_CMD
|
|
7
7
|
// Optional env: FILES (comma-separated), SESSION_START_REF
|
|
8
8
|
|
|
9
|
-
import { runCheck, resolveTestFiles, extractErrorLinesJson } from './gate-helpers.mjs';
|
|
9
|
+
import { runCheck, resolveTestFiles, extractErrorLinesJson, commandKillFields } from './gate-helpers.mjs';
|
|
10
10
|
|
|
11
11
|
const typecheckCmd = process.env.TYPECHECK_CMD;
|
|
12
12
|
const testCmd = process.env.TEST_CMD;
|
|
13
13
|
const files = process.env.FILES ?? '';
|
|
14
14
|
const sessionStartRef = process.env.SESSION_START_REF ?? '';
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Per-command wall-clock ceiling, published by `scripts/run-quality-gate.mjs`
|
|
18
|
+
* as `GATE_TIMEOUT_MS` (#1425 A3 / #1432). It carries the ALREADY-RESOLVED
|
|
19
|
+
* value — operator override `SO_GATE_TIMEOUT_MS` > Session Config
|
|
20
|
+
* `gate.timeout-path-b-ms` > 900 000 — so this script only has to read it.
|
|
21
|
+
*
|
|
22
|
+
* Absent or non-numeric (this gate invoked directly, not through the wrapper)
|
|
23
|
+
* → the option is OMITTED, and `runCheck` falls back to `resolveGateTimeoutMs()`
|
|
24
|
+
* exactly as before. An empty object is deliberate: passing `timeoutMs:
|
|
25
|
+
* undefined` would NOT trigger that fallback in every spread order.
|
|
26
|
+
*/
|
|
27
|
+
const CHECK_OPTS = (() => {
|
|
28
|
+
const raw = Number((process.env.GATE_TIMEOUT_MS || '').trim());
|
|
29
|
+
const opts = Number.isFinite(raw) && raw > 0 ? { timeoutMs: raw } : {};
|
|
30
|
+
// GATE_LEDGER_ROOT (#1425 A4, W5 fix-pass): the wrapper publishes the ledger
|
|
31
|
+
// root it resolved (--ledger-root > repo root) so the gate-process register
|
|
32
|
+
// is written where the WRAPPER decided, not where this sub-script happens to
|
|
33
|
+
// run. Without it every runCheck() here defaulted to process.cwd(), and a test
|
|
34
|
+
// that spawned this script from the checkout wrote real lines into the live
|
|
35
|
+
// .orchestrator/runtime/gate-processes.jsonl — the reaper's kill population.
|
|
36
|
+
const ledgerRoot = (process.env.GATE_LEDGER_ROOT || "").trim();
|
|
37
|
+
if (ledgerRoot && ledgerRoot.startsWith("/")) opts.repoRoot = ledgerRoot;
|
|
38
|
+
return opts;
|
|
39
|
+
})();
|
|
40
|
+
|
|
16
41
|
if (!typecheckCmd) {
|
|
17
42
|
process.stderr.write('TYPECHECK_CMD must be set\n');
|
|
18
43
|
process.exit(1);
|
|
@@ -24,41 +49,32 @@ if (!testCmd) {
|
|
|
24
49
|
|
|
25
50
|
const start = Date.now();
|
|
26
51
|
|
|
27
|
-
|
|
52
|
+
// A skipped test run spawned nothing: `{ status: 'skip' }`, no kill keys.
|
|
53
|
+
let testResult = { status: 'skip' };
|
|
28
54
|
let errors = [];
|
|
29
55
|
|
|
30
56
|
// --- typecheck (always runs unless cmd is "skip") ---
|
|
31
|
-
const tcResult = await runCheck(typecheckCmd);
|
|
32
|
-
|
|
33
|
-
if (tcStatus === 'fail') {
|
|
57
|
+
const tcResult = await runCheck(typecheckCmd, CHECK_OPTS);
|
|
58
|
+
if (tcResult.status === 'fail') {
|
|
34
59
|
errors = errors.concat(extractErrorLinesJson(tcResult.output, /error TS\d+/));
|
|
35
60
|
}
|
|
36
61
|
|
|
37
62
|
// --- test (scoped to changed/specified files) ---
|
|
38
|
-
if (testCmd
|
|
39
|
-
testStatus = 'skip';
|
|
40
|
-
} else {
|
|
63
|
+
if (testCmd !== 'skip') {
|
|
41
64
|
const testFiles = await resolveTestFiles(files, sessionStartRef);
|
|
42
65
|
if (testFiles.length > 0) {
|
|
43
66
|
const fileArgs = testFiles.join(' ');
|
|
44
|
-
|
|
45
|
-
testStatus = testResult.status;
|
|
46
|
-
if (testStatus === 'fail') {
|
|
47
|
-
const testErrors = extractErrorLinesJson(testResult.output, /(fail|error|FAIL)/i);
|
|
48
|
-
errors = errors.concat(testErrors);
|
|
49
|
-
}
|
|
67
|
+
testResult = await runCheck(`${testCmd} -- ${fileArgs}`, CHECK_OPTS);
|
|
50
68
|
} else if (!files && !sessionStartRef) {
|
|
51
69
|
// No FILES or SESSION_START_REF supplied: run the full test suite
|
|
52
|
-
|
|
53
|
-
testStatus = testResult.status;
|
|
54
|
-
if (testStatus === 'fail') {
|
|
55
|
-
const testErrors = extractErrorLinesJson(testResult.output, /(fail|error|FAIL)/i);
|
|
56
|
-
errors = errors.concat(testErrors);
|
|
57
|
-
}
|
|
70
|
+
testResult = await runCheck(testCmd, CHECK_OPTS);
|
|
58
71
|
} else {
|
|
59
72
|
// Files/ref supplied but no test files found — skip
|
|
60
73
|
process.stderr.write('warn: No test files found for incremental run; skipping tests\n');
|
|
61
|
-
|
|
74
|
+
}
|
|
75
|
+
if (testResult.status === 'fail') {
|
|
76
|
+
const testErrors = extractErrorLinesJson(testResult.output, /(fail|error|FAIL)/i);
|
|
77
|
+
errors = errors.concat(testErrors);
|
|
62
78
|
}
|
|
63
79
|
}
|
|
64
80
|
|
|
@@ -67,8 +83,18 @@ const duration_seconds = Math.round((Date.now() - start) / 1000);
|
|
|
67
83
|
const result = {
|
|
68
84
|
variant: 'incremental',
|
|
69
85
|
duration_seconds,
|
|
70
|
-
|
|
71
|
-
|
|
86
|
+
// Each command object also carries its INNER kill ladder (`timed_out`,
|
|
87
|
+
// `kill_signals`, `survivors` pids) when `runCheck` spawned it — omitted for
|
|
88
|
+
// skip/stub (#1457). `run-quality-gate.mjs` joins them into its event.
|
|
89
|
+
typecheck: { status: tcResult.status, ...commandKillFields(tcResult) },
|
|
90
|
+
test: { status: testResult.status, ...commandKillFields(testResult) },
|
|
91
|
+
// Stub map, same shape as gate-full: a command `runCheck` recognised as a
|
|
92
|
+
// stub (`echo …`/`true`) spawned nothing, so `joinGateKillFields` may count
|
|
93
|
+
// it as measured instead of declaring the whole inner ladder unknown.
|
|
94
|
+
stubbed: {
|
|
95
|
+
...(tcResult.stubbed ? { typecheck: tcResult.stubbed } : {}),
|
|
96
|
+
...(testResult.stubbed ? { test: testResult.stubbed } : {}),
|
|
97
|
+
},
|
|
72
98
|
errors,
|
|
73
99
|
};
|
|
74
100
|
|
|
@@ -9,11 +9,36 @@
|
|
|
9
9
|
* Part of v3.2 shell-helper port migration (issue #218 / #317).
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import { runCheck, csvToJsonArray } from './gate-helpers.mjs';
|
|
12
|
+
import { runCheck, csvToJsonArray, commandKillFields } from './gate-helpers.mjs';
|
|
13
13
|
|
|
14
14
|
const typecheckCmd = process.env.TYPECHECK_CMD;
|
|
15
15
|
const testCmd = process.env.TEST_CMD;
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Per-command wall-clock ceiling, published by `scripts/run-quality-gate.mjs`
|
|
19
|
+
* as `GATE_TIMEOUT_MS` (#1425 A3 / #1432). It carries the ALREADY-RESOLVED
|
|
20
|
+
* value — operator override `SO_GATE_TIMEOUT_MS` > Session Config
|
|
21
|
+
* `gate.timeout-path-b-ms` > 900 000 — so this script only has to read it.
|
|
22
|
+
*
|
|
23
|
+
* Absent or non-numeric (this gate invoked directly, not through the wrapper)
|
|
24
|
+
* → the option is OMITTED, and `runCheck` falls back to `resolveGateTimeoutMs()`
|
|
25
|
+
* exactly as before. An empty object is deliberate: passing `timeoutMs:
|
|
26
|
+
* undefined` would NOT trigger that fallback in every spread order.
|
|
27
|
+
*/
|
|
28
|
+
const CHECK_OPTS = (() => {
|
|
29
|
+
const raw = Number((process.env.GATE_TIMEOUT_MS || '').trim());
|
|
30
|
+
const opts = Number.isFinite(raw) && raw > 0 ? { timeoutMs: raw } : {};
|
|
31
|
+
// GATE_LEDGER_ROOT (#1425 A4, W5 fix-pass): the wrapper publishes the ledger
|
|
32
|
+
// root it resolved (--ledger-root > repo root) so the gate-process register
|
|
33
|
+
// is written where the WRAPPER decided, not where this sub-script happens to
|
|
34
|
+
// run. Without it every runCheck() here defaulted to process.cwd(), and a test
|
|
35
|
+
// that spawned this script from the checkout wrote real lines into the live
|
|
36
|
+
// .orchestrator/runtime/gate-processes.jsonl — the reaper's kill population.
|
|
37
|
+
const ledgerRoot = (process.env.GATE_LEDGER_ROOT || "").trim();
|
|
38
|
+
if (ledgerRoot && ledgerRoot.startsWith("/")) opts.repoRoot = ledgerRoot;
|
|
39
|
+
return opts;
|
|
40
|
+
})();
|
|
41
|
+
|
|
17
42
|
if (!typecheckCmd) {
|
|
18
43
|
process.stderr.write('gate-per-file: TYPECHECK_CMD must be set\n');
|
|
19
44
|
process.exit(1);
|
|
@@ -33,21 +58,30 @@ if (files.length === 0) {
|
|
|
33
58
|
}
|
|
34
59
|
|
|
35
60
|
// Typecheck runs on the whole project, not per-file
|
|
36
|
-
const tcResult = runCheck(typecheckCmd);
|
|
37
|
-
const tcStatus = tcResult.status;
|
|
61
|
+
const tcResult = await runCheck(typecheckCmd, CHECK_OPTS);
|
|
38
62
|
|
|
39
|
-
|
|
63
|
+
// A skipped test run spawned nothing: `{ status: 'skip' }`, no kill keys.
|
|
64
|
+
let testResult = { status: 'skip' };
|
|
40
65
|
|
|
41
66
|
if (testCmd !== 'skip' && files.length > 0) {
|
|
42
67
|
const fileArgs = files.join(' ');
|
|
43
|
-
|
|
44
|
-
testStatus = testResult.status;
|
|
68
|
+
testResult = await runCheck(`${testCmd} -- ${fileArgs}`, CHECK_OPTS);
|
|
45
69
|
}
|
|
46
70
|
|
|
47
71
|
const result = {
|
|
48
72
|
variant: 'per-file',
|
|
49
|
-
|
|
50
|
-
|
|
73
|
+
// Each command object also carries its INNER kill ladder (`timed_out`,
|
|
74
|
+
// `kill_signals`, `survivors` pids) when `runCheck` spawned it — omitted for
|
|
75
|
+
// skip/stub (#1457). `run-quality-gate.mjs` joins them into its event.
|
|
76
|
+
typecheck: { status: tcResult.status, ...commandKillFields(tcResult) },
|
|
77
|
+
test: { status: testResult.status, ...commandKillFields(testResult) },
|
|
78
|
+
// Stub map, same shape as gate-full: a command `runCheck` recognised as a
|
|
79
|
+
// stub (`echo …`/`true`) spawned nothing, so `joinGateKillFields` may count
|
|
80
|
+
// it as measured instead of declaring the whole inner ladder unknown.
|
|
81
|
+
stubbed: {
|
|
82
|
+
...(tcResult.stubbed ? { typecheck: tcResult.stubbed } : {}),
|
|
83
|
+
...(testResult.stubbed ? { test: testResult.stubbed } : {}),
|
|
84
|
+
},
|
|
51
85
|
files,
|
|
52
86
|
};
|
|
53
87
|
|
|
@@ -139,7 +139,12 @@ export function renderPortfolio(summaries, opts) {
|
|
|
139
139
|
// Frontmatter
|
|
140
140
|
lines.push('---');
|
|
141
141
|
lines.push(`_generator: ${GENERATOR_MARKER}`);
|
|
142
|
-
|
|
142
|
+
// `id` + `type` satisfy the vault frontmatter schema (skills/vault-sync/validator.mjs).
|
|
143
|
+
// `type: dashboard` was not in its enum and `id` was missing, so the generated file
|
|
144
|
+
// failed the hard gate at /close (#1144). `board` is the enum member added for
|
|
145
|
+
// generated dashboards (#738) — same shape as vault-status/board-writer.mjs.
|
|
146
|
+
lines.push('id: portfolio');
|
|
147
|
+
lines.push('type: board');
|
|
143
148
|
lines.push(`created: ${createdValue}`);
|
|
144
149
|
lines.push(`updated: ${updatedValue}`);
|
|
145
150
|
lines.push('---');
|