session-orchestrator 5.11.0 → 5.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +12 -1
- package/.claude-plugin/plugin.json +4 -1
- package/.codex-plugin/agents/explorer.toml +1 -1
- package/.codex-plugin/agents/session-reviewer.toml +2 -2
- package/.codex-plugin/agents/wave-worker.toml +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/.orchestrator/policy/blocked-commands.json +8 -0
- package/.orchestrator/policy/public-export.json +30 -0
- package/CHANGELOG.md +180 -3
- package/README.md +126 -117
- package/agents/analyst.md +7 -2
- package/agents/architect-reviewer.md +7 -2
- package/agents/code-implementer.md +4 -2
- package/agents/db-specialist.md +9 -2
- package/agents/dialectic-deriver.md +9 -3
- package/agents/docs-writer.md +7 -2
- package/agents/eval-judge.md +7 -1
- package/agents/qa-strategist.md +7 -2
- package/agents/security-reviewer.md +9 -2
- package/agents/session-reviewer.md +18 -2
- package/agents/skill-applied-judge.md +7 -1
- package/agents/test-writer.md +9 -2
- package/agents/ui-developer.md +9 -2
- package/agents/ux-evaluator.md +6 -1
- package/commands/session.md +4 -2
- package/commands/templates-ack.md +89 -18
- package/docs/agent-authoring.md +30 -11
- package/docs/ci-setup.md +38 -2
- package/docs/codex-setup.md +33 -9
- package/docs/components.md +13 -2
- package/docs/cursor-setup.md +3 -3
- package/docs/events-schema.md +5 -4
- package/docs/evidence-candidates.md +63 -0
- package/docs/fleet-checkin-audit.md +7 -4
- package/docs/github-mirror-protection.md +150 -3
- package/docs/install.md +102 -25
- package/docs/instruction-delivery.md +1 -1
- package/docs/migration-v4.md +2 -2
- package/docs/mods.md +118 -0
- package/docs/owner-config-schema.md +1 -0
- package/docs/plugin-architecture-v3.md +1 -1
- package/docs/pm-skills-marketplace.md +4 -3
- package/docs/rule-authoring.md +24 -13
- package/docs/scope-collision-guard.md +2 -1
- package/docs/session-config-reference.md +55 -21
- package/docs/session-config-template.md +32 -24
- package/docs/sessions-ledger-repair.md +137 -0
- package/docs/vault-docs-architecture.md +1 -1
- package/hooks/_lib/hook-import-set.json +406 -73
- package/hooks/_lib/hook-root.mjs +301 -0
- package/hooks/_lib/lock-bootstrap.mjs +434 -4
- package/hooks/_lib/orchestrator-ignore.mjs +3 -0
- package/hooks/_lib/subagent-transcript.mjs +2 -2
- package/hooks/_lib/vcs-create-matcher.mjs +13 -17
- package/hooks/cwd-change-restore.mjs +5 -1
- package/hooks/enforce-commands.mjs +4 -3
- package/hooks/enforce-scope.mjs +11 -1
- package/hooks/hooks-codex.json +48 -2
- package/hooks/hooks.json +31 -31
- package/hooks/on-session-end.mjs +38 -15
- package/hooks/on-session-start.mjs +158 -59
- package/hooks/on-stop.mjs +11 -6
- package/hooks/post-bash-issue-budget-refund.mjs +6 -1
- package/hooks/post-bash-write-verify.mjs +8 -4
- package/hooks/post-edit-validate.mjs +45 -52
- package/hooks/post-subagent-discovery-validator.mjs +2 -2
- package/hooks/post-tool-batch-wave-signal.mjs +8 -4
- package/hooks/post-tool-failure-corrective-context.mjs +5 -1
- package/hooks/pre-bash-destructive-guard.mjs +220 -8
- package/hooks/pre-bash-issue-budget.mjs +6 -1
- package/hooks/pre-bash-sessions-ledger-guard.mjs +691 -123
- package/hooks/pre-bash-staging-fence.mjs +258 -14
- package/hooks/pre-bash-templates-first.mjs +28 -27
- package/hooks/run-node.sh +1 -1
- package/hooks/skill-invocation-telemetry.mjs +15 -8
- package/hooks/subagent-telemetry.mjs +77 -14
- package/hooks/wave-scope-commit-guard.mjs +143 -14
- package/mods/so-hud/.claude-plugin/plugin.json +11 -0
- package/mods/so-hud/README.md +57 -0
- package/mods/so-hud/hooks/beacon-io.mjs +101 -0
- package/mods/so-hud/hooks/heredoc-scan.ts +394 -0
- package/mods/so-hud/hooks/hooks.json +1 -0
- package/mods/so-hud/hooks/logic.ts +182 -0
- package/mods/so-hud/hooks/register.tsx +670 -0
- package/mods/so-hud/hooks/rules.ts +2998 -0
- package/mods/so-hud/tsconfig.json +8 -0
- package/mods/so-hud/types/index.d.ts +36 -0
- package/package.json +4 -3
- package/rules/_index.md +1 -1
- package/rules/always-on/ask-via-tool.md +1 -1
- package/rules/always-on/loop-and-monitor.md +17 -194
- package/rules/always-on/parallel-sessions.md +3 -1
- package/rules/opt-in-stack/backend-data.md +1 -1
- package/rules/opt-in-stack/backend.md +1 -1
- package/rules/opt-in-stack/security-web.md +5 -1
- package/scripts/agent-usage-census.mjs +79 -0
- package/scripts/apply-session-learnings.mjs +34 -6
- package/scripts/archive-closed-prds.mjs +21 -5
- package/scripts/autopilot.mjs +11 -5
- package/scripts/backfill-abandoned-sessions.mjs +13 -3
- package/scripts/backfill-evidence-digest.mjs +2 -1
- package/scripts/backfill-learnings-from-vault.mjs +61 -6
- package/scripts/backfill-sessions.mjs +16 -2
- package/scripts/check-package-manager.mjs +26 -8
- package/scripts/check-sessions-integrity.mjs +4 -1
- package/scripts/ci/assert-vitest-green.mjs +3 -0
- package/scripts/ci/windows-smoke.mjs +201 -0
- package/scripts/close-precheck.mjs +55 -0
- package/scripts/dialectic-deriver.mjs +20 -12
- package/scripts/emit-event.mjs +2 -2
- package/scripts/emit-session.mjs +239 -20
- package/scripts/eval-session.mjs +17 -3
- package/scripts/export-hw-learnings.mjs +29 -2
- package/scripts/fleet-checkin-audit.mjs +8 -4
- package/scripts/fleet-checkin.mjs +73 -3
- package/scripts/gc-stale-worktrees.mjs +6 -1
- package/scripts/github-export.mjs +128 -0
- package/scripts/github-protection-audit.mjs +3 -1
- package/scripts/handover-queue-check.mjs +209 -0
- package/scripts/harness-audit.mjs +6 -4
- package/scripts/lib/agent-frontmatter.mjs +57 -10
- package/scripts/lib/agent-status.mjs +125 -7
- package/scripts/lib/agent-usage-census.mjs +394 -0
- package/scripts/lib/auto-dialectic.mjs +17 -10
- package/scripts/lib/auto-dream.mjs +22 -14
- package/scripts/lib/autopilot/loop.mjs +3 -1
- package/scripts/lib/autopilot/recent-runs.mjs +4 -2
- package/scripts/lib/autopilot/stall-sampler.mjs +2 -1
- package/scripts/lib/autopilot/telemetry.mjs +5 -1
- package/scripts/lib/autopilot/worktree-pipeline.mjs +5 -5
- package/scripts/lib/baseline-archetypes.mjs +10 -2
- package/scripts/lib/blocked-commands-policy.mjs +4 -3
- package/scripts/lib/build-live-signals.mjs +13 -9
- package/scripts/lib/ci-status-banner.mjs +329 -24
- package/scripts/lib/codex/plugin-contract.mjs +8 -0
- package/scripts/lib/cold-start-detector.mjs +5 -2
- package/scripts/lib/command-blocker.mjs +603 -43
- package/scripts/lib/common.mjs +3 -2
- package/scripts/lib/config/cross-repo.mjs +74 -7
- package/scripts/lib/config/host-paths.mjs +6 -2
- package/scripts/lib/config/remote-hosts.mjs +2 -2
- package/scripts/lib/config/section-extractor.mjs +65 -11
- package/scripts/lib/config/template-keys.mjs +153 -0
- package/scripts/lib/config/vault-integration.mjs +1 -0
- package/scripts/lib/config.mjs +3 -3
- package/scripts/lib/coordinator-snapshot.mjs +1 -1
- package/scripts/lib/discovery/triage-state.mjs +2 -1
- package/scripts/lib/dispatcher/rank.mjs +5 -7
- package/scripts/lib/ecosystem-health.mjs +2 -2
- package/scripts/lib/eval/engine.mjs +3 -2
- package/scripts/lib/eval/session-resolve.mjs +6 -2
- package/scripts/lib/eval/sink.mjs +4 -3
- package/scripts/lib/events-retention-banner.mjs +3 -2
- package/scripts/lib/events-schema.mjs +10 -0
- package/scripts/lib/events.mjs +23 -12
- package/scripts/lib/evolve/autonomy-verdict.mjs +4 -9
- package/scripts/lib/evolve/autopilot-effectiveness.mjs +8 -18
- package/scripts/lib/fleet-protocol.mjs +21 -0
- package/scripts/lib/gates/gate-full.mjs +172 -11
- package/scripts/lib/gates/gate-helpers.mjs +236 -23
- package/scripts/lib/git-config-drift.mjs +141 -7
- package/scripts/lib/github-export.mjs +447 -0
- package/scripts/lib/gitlab-portfolio/cli.mjs +16 -5
- package/scripts/lib/handover-queue-marks.mjs +159 -0
- package/scripts/lib/hardware-pattern-detector.mjs +103 -13
- package/scripts/lib/harness-audit/categories/category1.mjs +16 -4
- package/scripts/lib/harness-audit/categories/category4.mjs +11 -8
- package/scripts/lib/harness-audit/categories/category6.mjs +25 -5
- package/scripts/lib/harness-audit/categories/category9.mjs +10 -4
- package/scripts/lib/host-identity.mjs +9 -5
- package/scripts/lib/http-request.mjs +64 -0
- package/scripts/lib/instruction-budget-guard.mjs +30 -22
- package/scripts/lib/io.mjs +6 -3
- package/scripts/lib/issue-budget-reconcile.mjs +3 -2
- package/scripts/lib/issue-budget.mjs +36 -7
- package/scripts/lib/learnings/evidence-candidates.mjs +154 -0
- package/scripts/lib/learnings/expiry-sweep.mjs +99 -7
- package/scripts/lib/learnings/io.mjs +289 -15
- package/scripts/lib/learnings/select.mjs +53 -4
- package/scripts/lib/learnings/store-role.mjs +209 -0
- package/scripts/lib/learnings/surface.mjs +46 -7
- package/scripts/lib/ledger-path.mjs +113 -0
- package/scripts/lib/maintenance-due-banner.mjs +63 -12
- package/scripts/lib/memory-banner.mjs +5 -6
- package/scripts/lib/memory-proposals/collector.mjs +5 -2
- package/scripts/lib/memory-proposals/sink.mjs +7 -4
- package/scripts/lib/memory-proposals/store.mjs +22 -5
- package/scripts/lib/mode-selector-accuracy.mjs +2 -1
- package/scripts/lib/no-follow-open.mjs +57 -0
- package/scripts/lib/orphan-reaper/reaper-audit.mjs +4 -2
- package/scripts/lib/own-lock-root.mjs +95 -0
- package/scripts/lib/owner-interview.mjs +2 -1
- package/scripts/lib/owner-yaml.mjs +70 -7
- package/scripts/lib/platform.mjs +161 -9
- package/scripts/lib/plugin-update-banner.mjs +15 -11
- package/scripts/lib/pre-push-local-partial.mjs +117 -0
- package/scripts/lib/pre-push-remote-gate.mjs +151 -25
- package/scripts/lib/process-group.mjs +270 -34
- package/scripts/lib/project-hygiene.mjs +28 -0
- package/scripts/lib/quality-gate.mjs +33 -9
- package/scripts/lib/reconcile/backlog.mjs +29 -15
- package/scripts/lib/reconcile/engine.mjs +26 -2
- package/scripts/lib/reconcile/idempotency.mjs +65 -3
- package/scripts/lib/reconcile/rule-expiry-sweep.mjs +53 -12
- package/scripts/lib/reconcile/verdict-ledger.mjs +224 -0
- package/scripts/lib/reconcile-nudge-banner.mjs +40 -6
- package/scripts/lib/resource-probe/evaluate.mjs +32 -9
- package/scripts/lib/resource-probe/probe-platform.mjs +3 -1
- package/scripts/lib/rule-loader.mjs +20 -10
- package/scripts/lib/scope-baseline.mjs +123 -8
- package/scripts/lib/scope-echo.mjs +5 -2
- package/scripts/lib/scope-gate.mjs +55 -20
- package/scripts/lib/session-close-backfill.mjs +75 -7
- package/scripts/lib/session-end/close-precheck.mjs +151 -0
- package/scripts/lib/session-end/phase-skip.mjs +8 -7
- package/scripts/lib/session-end/tail-runner.mjs +7 -2
- package/scripts/lib/session-end/vault-orphans.mjs +205 -0
- package/scripts/lib/session-id.mjs +9 -1
- package/scripts/lib/session-identity/own-session.mjs +63 -0
- package/scripts/lib/session-invocation.mjs +14 -3
- package/scripts/lib/session-lock.mjs +114 -0
- package/scripts/lib/session-record-repair.mjs +245 -35
- package/scripts/lib/session-registry.mjs +85 -12
- package/scripts/lib/session-schema/constants.mjs +9 -0
- package/scripts/lib/session-schema/filters.mjs +56 -1
- package/scripts/lib/session-schema/validator.mjs +15 -5
- package/scripts/lib/session-start-probes.mjs +149 -22
- package/scripts/lib/session-token-rollup.mjs +2 -1
- package/scripts/lib/session-work-trees.mjs +166 -0
- package/scripts/lib/sessions-canonical.mjs +61 -3
- package/scripts/lib/sessions-integrity-banner.mjs +17 -39
- package/scripts/lib/sessions-staleness-banner.mjs +8 -5
- package/scripts/lib/sh-quote.mjs +1 -0
- package/scripts/lib/skill-evidence-window.mjs +5 -1
- package/scripts/lib/skill-health/join.mjs +10 -9
- package/scripts/lib/skill-invocations-schema.mjs +13 -0
- package/scripts/lib/skill-judgments-schema.mjs +6 -2
- package/scripts/lib/snapshot-probe.mjs +414 -0
- package/scripts/lib/so-hud/collect.mjs +174 -0
- package/scripts/lib/so-hud/runtime.mjs +87 -0
- package/scripts/lib/spiral-carryover.mjs +8 -1
- package/scripts/lib/state-md/frontmatter-mutators.mjs +266 -8
- package/scripts/lib/state-md/idle-reset.mjs +248 -0
- package/scripts/lib/state-md/mission-status.mjs +5 -1
- package/scripts/lib/state-md/recommendations.mjs +26 -4
- package/scripts/lib/state-md-peer-guard.mjs +2 -0
- package/scripts/lib/state-md.mjs +31 -8
- package/scripts/lib/sunset/walker.mjs +4 -3
- package/scripts/lib/telemetry/consent.mjs +4 -2
- package/scripts/lib/telemetry/queue.mjs +3 -3
- package/scripts/lib/telemetry/sync.mjs +45 -9
- package/scripts/lib/test-runner/artifact-paths.mjs +10 -2
- package/scripts/lib/tmux-layout/layouts.mjs +20 -3
- package/scripts/lib/ux-grill/paths.mjs +5 -1
- package/scripts/lib/validate/check-agents.mjs +37 -6
- package/scripts/lib/validate/check-component-paths.mjs +25 -1
- package/scripts/lib/validate/check-guard-requires-parity.mjs +3 -3
- package/scripts/lib/validate/check-hooks-symmetry.mjs +53 -32
- package/scripts/lib/validate/check-json-files.mjs +3 -1
- package/scripts/lib/validate/check-learning-provenance.mjs +3 -2
- package/scripts/lib/validate/check-learnings-shell-writes.mjs +10 -4
- package/scripts/lib/validate/check-metrics-ledger-root.mjs +234 -0
- package/scripts/lib/validate/check-owner-leakage.mjs +159 -11
- package/scripts/lib/validate/check-test-posix-shell.mjs +246 -0
- package/scripts/lib/validate/check-unwired-features.mjs +4 -2
- package/scripts/lib/validate/check-vcs-repo-flag-positional.mjs +58 -0
- package/scripts/lib/validate/check-vcs-repo-flag.mjs +259 -73
- package/scripts/lib/validate/check-workflow-continue-on-error.mjs +63 -0
- package/scripts/lib/validate/confidential-names.mjs +3 -1
- package/scripts/lib/vault-mirror/migrate-ids.mjs +321 -0
- package/scripts/lib/vault-mirror/namespace.mjs +22 -9
- package/scripts/lib/vault-mirror/process.mjs +197 -65
- package/scripts/lib/vault-mirror/prune.mjs +5 -2
- package/scripts/lib/vault-mirror/render-learnings.mjs +8 -7
- package/scripts/lib/vault-mirror/render-sessions.mjs +36 -53
- package/scripts/lib/vault-mirror/retention.mjs +2 -2
- package/scripts/lib/vault-mirror/utils.mjs +106 -13
- package/scripts/lib/vault-staleness-banner.mjs +4 -7
- package/scripts/lib/vault-status/board-writer.mjs +34 -5
- package/scripts/lib/vault-status/narrative-mirror.mjs +66 -7
- package/scripts/lib/wave-executor/foreign-dispatch.mjs +20 -0
- package/scripts/lib/win-process-tree.mjs +137 -0
- package/scripts/lib/workspace.mjs +1 -1
- package/scripts/lib/worktree/constants.mjs +1 -1
- package/scripts/lib/worktree/lifecycle.mjs +1 -1
- package/scripts/lib/worktree/listing.mjs +1 -1
- package/scripts/lib/zx.mjs +50 -0
- package/scripts/mcp-server.mjs +16 -7
- package/scripts/mcp-server.sh +13 -83
- package/scripts/measure-policy-cache-effectiveness.mjs +7 -5
- package/scripts/memory-propose.mjs +10 -3
- package/scripts/migrate-cold-start-seed.mjs +7 -0
- package/scripts/migrate-legacy-learnings.sh +22 -0
- package/scripts/migrate-sessions-jsonl.mjs +16 -2
- package/scripts/migrate-vault-paths.mjs +29 -13
- package/scripts/parse-config.mjs +71 -14
- package/scripts/print-learnings-index.mjs +25 -2
- package/scripts/release-session-lock.mjs +94 -7
- package/scripts/release.mjs +451 -24
- package/scripts/relocate-vault-corpus.mjs +11 -7
- package/scripts/repair-invalid-sessions.mjs +68 -3
- package/scripts/resolve-session-invocation.mjs +57 -8
- package/scripts/run-migrate-v2-cross-repo.mjs +56 -1
- package/scripts/run-quality-gate.mjs +33 -7
- package/scripts/self-update.mjs +56 -7
- package/scripts/session-commit-files.mjs +133 -0
- package/scripts/site-numbers.mjs +52 -9
- package/scripts/snapshot-probe.mjs +156 -0
- package/scripts/so-hud.mjs +68 -0
- package/scripts/state-md-idle-reset.mjs +82 -0
- package/scripts/sweep-expired-learnings.mjs +54 -6
- package/scripts/sweep-expired-rules.mjs +4 -1
- package/scripts/validate-plugin.mjs +22 -0
- package/scripts/vault-integration-watcher.mjs +54 -6
- package/scripts/vault-mirror.mjs +9 -0
- package/scripts/vault-orphans.mjs +171 -0
- package/skills/_shared/fleet-protocol.md +166 -3
- package/skills/_shared/instruction-file-resolution.md +1 -1
- package/skills/_shared/monitor-patterns.md +44 -25
- package/skills/_shared/platform-tools.md +7 -0
- package/skills/_shared/state-ownership.md +5 -4
- package/skills/architecture/references/domain-model.md +1 -1
- package/skills/autopilot/SKILL.md +3 -2
- package/skills/bootstrap/SKILL.md +3 -1
- package/skills/bootstrap/_shared-template.md +4 -4
- package/skills/bootstrap/fast-template.md +1 -1
- package/skills/bootstrap/private-contract.md +1 -1
- package/skills/bootstrap/references/bootstrap-retroactive-flow.md +17 -9
- package/skills/bootstrap/templates/agents/project-code-review.md +5 -1
- package/skills/bootstrap/templates/agents/project-discovery.md +5 -1
- package/skills/bootstrap/templates/agents/project-quality-gate.md +5 -1
- package/skills/claude-md-drift-check/SKILL.md +3 -3
- package/skills/claude-md-drift-check/checker.mjs +61 -101
- package/skills/close/SKILL.md +20 -7
- package/skills/discovery/probes/docs-staleness.mjs +7 -5
- package/skills/discovery/probes/frontend-slop.mjs +7 -3
- package/skills/discovery/probes/ssot-code-diff.mjs +5 -3
- package/skills/discovery/probes/supply-chain-slopcheck.mjs +10 -8
- package/skills/discovery/probes/vault-narrative-staleness.mjs +30 -10
- package/skills/discovery/probes/vault-staleness.mjs +5 -3
- package/skills/discovery/probes-session.md +3 -0
- package/skills/discovery/probes-vault.md +2 -0
- package/skills/dispatcher/SKILL.md +1 -1
- package/skills/eval/SKILL.md +3 -1
- package/skills/evolve/SKILL.md +2 -0
- package/skills/evolve/references/evolve-analyze-mode.md +3 -3
- package/skills/evolve/references/evolve-dialectic-mode.md +1 -1
- package/skills/gitlab-ops/SKILL.md +7 -6
- package/skills/gitlab-portfolio/SKILL.md +2 -2
- package/skills/go/SKILL.md +13 -1
- package/skills/npm-publish/SKILL.md +5 -2
- package/skills/plan/SKILL.md +23 -10
- package/skills/plan/mode-feature.md +13 -8
- package/skills/plan/mode-new.md +6 -14
- package/skills/plan/prd-feature-template.md +19 -0
- package/skills/plan/prd-full-template.md +12 -5
- package/skills/plan/prd-premortem-panel.md +48 -0
- package/skills/plan/prd-reviewer-prompt.md +11 -3
- package/skills/plan/soul.md +1 -1
- package/skills/portfolio/SKILL.md +5 -6
- package/skills/quality-gates/SKILL.md +2 -4
- package/skills/reconcile/SKILL.md +27 -4
- package/skills/release/SKILL.md +16 -8
- package/skills/session-end/SKILL.md +97 -13
- package/skills/session-end/phase-3-2-docs-verification.md +1 -0
- package/skills/session-end/phase-3-6-tail.md +11 -7
- package/skills/session-end/phase-3-7a-recommendations.md +9 -2
- package/skills/session-end/plan-verification.md +16 -1
- package/skills/session-end/references/phase-3-7e-vault-orphans.md +63 -0
- package/skills/session-end/references/phase-3-documentation-updates.md +25 -13
- package/skills/session-end/references/phase-5-issue-cleanup.md +1 -1
- package/skills/session-end/references/session-summary-template.md +8 -0
- package/skills/session-end/session-metrics-write.md +7 -4
- package/skills/session-plan/SKILL.md +5 -3
- package/skills/session-start/SKILL.md +10 -4
- package/skills/session-start/phase-4-5-resource-health.md +11 -5
- package/skills/session-start/phase-7-5-mode-selector.md +3 -1
- package/skills/session-start/phase-8-5-express-path.md +4 -3
- package/skills/session-start/references/phase-1-2-session-lock.md +7 -5
- package/skills/session-start/references/phase-1-5-session-continuity.md +13 -5
- package/skills/session-start/references/phase-1-7-vault-status-board.md +1 -1
- package/skills/session-start/references/phase-4-ssot-environment-check.md +3 -2
- package/skills/tmux-layout/SKILL.md +2 -2
- package/skills/vault-sync/validator.mjs +3 -2
- package/skills/wave-executor/SKILL.md +5 -4
- package/skills/wave-executor/references/wave-executor-state-init.md +3 -3
- package/skills/wave-executor/references/wave-loop-dispatch.md +11 -5
- package/skills/wave-executor/references/wave-loop-review.md +13 -5
- package/skills/wave-executor/references/wave-loop-scope-manifest.md +1 -1
- package/AGENTS.md +0 -207
- package/assets/og-card.png +0 -0
- package/assets/og-card.svg +0 -47
- package/assets/wave-lifecycle.svg +0 -98
|
@@ -513,6 +513,45 @@ async function glabApi(apiPath, repoRoot, deps = {}, expect = undefined) {
|
|
|
513
513
|
return parseCliJson(result.stdout, `glab api ${apiPath}`, expect);
|
|
514
514
|
}
|
|
515
515
|
|
|
516
|
+
/**
|
|
517
|
+
* Jobs endpoint of one GitLab pipeline, shared by the green (allow-failure)
|
|
518
|
+
* and red (failing-job) branches of `checkCiStatus()`.
|
|
519
|
+
*
|
|
520
|
+
* `per_page=100` (#1716): the API default page is 20, so a failed job at
|
|
521
|
+
* position 21+ was never named. Ceiling: one page of 100; a pipeline with
|
|
522
|
+
* more jobs still truncates. Revisit trigger: a pipeline with > 100 jobs
|
|
523
|
+
* (then paginate via `glab api --paginate`).
|
|
524
|
+
*
|
|
525
|
+
* @param {string} projectPath - `projects/<url-encoded path>`
|
|
526
|
+
* @param {number|string} pipelineId
|
|
527
|
+
* @returns {string}
|
|
528
|
+
*/
|
|
529
|
+
function pipelineJobsPath(projectPath, pipelineId) {
|
|
530
|
+
return `${projectPath}/pipelines/${pipelineId}/jobs?per_page=100`;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/** Upper bound on `failingJobNames` — a row listing 40 jobs is unreadable. */
|
|
534
|
+
const MAX_FAILING_JOB_NAMES = 10;
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* Every failing job/check-run name, sanitised, deduplicated (a re-run job
|
|
538
|
+
* appears twice) and capped (#1712). Additive companion to `failingJobName`,
|
|
539
|
+
* which keeps naming only the first: release 5.13.0 had a red informational
|
|
540
|
+
* Windows run beside the real cause, and naming one of two hid the other.
|
|
541
|
+
*
|
|
542
|
+
* @param {Array<{name?: unknown}>} failed
|
|
543
|
+
* @returns {string[]}
|
|
544
|
+
*/
|
|
545
|
+
function collectFailingJobNames(failed) {
|
|
546
|
+
const names = [];
|
|
547
|
+
for (const item of failed) {
|
|
548
|
+
const name = sanitizeApiText(item?.name);
|
|
549
|
+
if (name && !names.includes(name)) names.push(name);
|
|
550
|
+
if (names.length >= MAX_FAILING_JOB_NAMES) break;
|
|
551
|
+
}
|
|
552
|
+
return names;
|
|
553
|
+
}
|
|
554
|
+
|
|
516
555
|
/**
|
|
517
556
|
* Run `gh api <path>` and return parsed JSON.
|
|
518
557
|
*
|
|
@@ -536,6 +575,181 @@ async function ghApi(apiPath, repoRoot, deps = {}, expect = undefined) {
|
|
|
536
575
|
return parseCliJson(result.stdout, `gh api ${apiPath}`, expect);
|
|
537
576
|
}
|
|
538
577
|
|
|
578
|
+
/**
|
|
579
|
+
* Run `gh api graphql` with string variables and return the parsed object.
|
|
580
|
+
*
|
|
581
|
+
* Variables go through `-f` (raw string), NEVER `-F`: `-F` type-converts
|
|
582
|
+
* values ("true", numbers) and reads `@file` operands, so a value starting
|
|
583
|
+
* with `@` would make gh read a local file into the request. The argv order
|
|
584
|
+
* is fixed (query, then the variables in the caller's insertion order, then
|
|
585
|
+
* `--hostname`) so the exact-argv test mock can pin it. Host-pinning mirrors
|
|
586
|
+
* {@link ghApi} (#872).
|
|
587
|
+
*
|
|
588
|
+
* @param {string} query
|
|
589
|
+
* @param {Record<string, string>} vars
|
|
590
|
+
* @param {string} repoRoot
|
|
591
|
+
* @param {{ execFile?: Function, timeoutMs?: number, repoHost?: string }} deps
|
|
592
|
+
* @returns {Promise<unknown>}
|
|
593
|
+
*/
|
|
594
|
+
async function ghGraphql(query, vars, repoRoot, deps = {}) {
|
|
595
|
+
const args = ['api', 'graphql', '-f', `query=${query}`];
|
|
596
|
+
for (const [key, value] of Object.entries(vars)) args.push('-f', `${key}=${value}`);
|
|
597
|
+
if (deps.repoHost) args.push('--hostname', deps.repoHost);
|
|
598
|
+
const result = await execWithTimeout(
|
|
599
|
+
'gh',
|
|
600
|
+
args,
|
|
601
|
+
{ cwd: repoRoot, timeoutMs: deps.timeoutMs ?? DEFAULT_TIMEOUT_MS, execFile: deps.execFile },
|
|
602
|
+
);
|
|
603
|
+
return parseCliJson(result.stdout, 'gh api graphql', 'object');
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
* Commits the GitHub red branch reads back from HEAD to find the last green
|
|
608
|
+
* (#856): HEAD itself plus 15 ancestors. Ceiling: a red run longer than the
|
|
609
|
+
* window reports `redCount` without `lastGreen` although a green commit
|
|
610
|
+
* exists further back. Revisit trigger: a GitHub red reading with
|
|
611
|
+
* `lastGreen` absent while an older green commit is known (then page via
|
|
612
|
+
* `history(after:)`).
|
|
613
|
+
*/
|
|
614
|
+
const GITHUB_HISTORY_WINDOW = 16;
|
|
615
|
+
|
|
616
|
+
/**
|
|
617
|
+
* One GraphQL round trip: the commit history from `expr` with each commit's
|
|
618
|
+
* CI rollup. A commit with no CI at all has `statusCheckRollup: null`.
|
|
619
|
+
*/
|
|
620
|
+
const GITHUB_HISTORY_QUERY =
|
|
621
|
+
'query($owner: String!, $name: String!, $expr: String!) { repository(owner: $owner, name: $name) { ' +
|
|
622
|
+
'object(expression: $expr) { ... on Commit { ' +
|
|
623
|
+
`history(first: ${GITHUB_HISTORY_WINDOW}) { nodes { oid committedDate statusCheckRollup { state } } } ` +
|
|
624
|
+
'} } } }';
|
|
625
|
+
|
|
626
|
+
/**
|
|
627
|
+
* Walk the GitHub history from HEAD and derive `redCount` / `lastGreen` (#856).
|
|
628
|
+
*
|
|
629
|
+
* Returns `null` on ANY failure (spawn error, timeout, parse error,
|
|
630
|
+
* unexpected shape, unusable `nameWithOwner`) — the caller then reports the
|
|
631
|
+
* red reading without both fields. Never throws: like the GitLab job-list
|
|
632
|
+
* query, one failed side query must not degrade a red reading.
|
|
633
|
+
*
|
|
634
|
+
* Rules: every node whose `oid` equals `headSha` is skipped (HEAD already is
|
|
635
|
+
* the first member of the red run; its index is not relied on). A node
|
|
636
|
+
* without `statusCheckRollup` carried no CI — neither green nor red, like a
|
|
637
|
+
* GitLab commit without a pipeline row — and is skipped. `SUCCESS` is the
|
|
638
|
+
* last green; every other state (FAILURE, ERROR, PENDING, EXPECTED, …)
|
|
639
|
+
* extends the red run.
|
|
640
|
+
*
|
|
641
|
+
* Ceiling: `statusCheckRollup` also counts legacy commit statuses, while the
|
|
642
|
+
* HEAD verdict counts check runs only — a commit can read green in the rollup
|
|
643
|
+
* and still have a red check run elsewhere, and vice versa. `history`
|
|
644
|
+
* follows all parents in date order, so commits of merged branches
|
|
645
|
+
* interleave with the mainline. Revisit trigger: a reported `lastGreen` whose
|
|
646
|
+
* check runs are red, or a `redCount` someone reads as mainline-only.
|
|
647
|
+
*
|
|
648
|
+
* @param {string} nameWithOwner
|
|
649
|
+
* @param {string} headSha
|
|
650
|
+
* @param {number} now Unix epoch ms
|
|
651
|
+
* @param {string} repoRoot
|
|
652
|
+
* @param {{ execFile?: Function, timeoutMs?: number, repoHost?: string }} deps
|
|
653
|
+
* @returns {Promise<{ redCount: number, lastGreen?: { sha: string, ageCommits: number, ageDays: number|null } }|null>}
|
|
654
|
+
*/
|
|
655
|
+
async function readGithubLastGreen(nameWithOwner, headSha, now, repoRoot, deps) {
|
|
656
|
+
const slash = typeof nameWithOwner === 'string' ? nameWithOwner.indexOf('/') : -1;
|
|
657
|
+
const owner = slash > 0 ? nameWithOwner.slice(0, slash) : '';
|
|
658
|
+
const name = slash > 0 ? nameWithOwner.slice(slash + 1) : '';
|
|
659
|
+
if (!owner || !name) return null;
|
|
660
|
+
try {
|
|
661
|
+
const data = await ghGraphql(
|
|
662
|
+
GITHUB_HISTORY_QUERY,
|
|
663
|
+
{ owner, name, expr: headSha },
|
|
664
|
+
repoRoot,
|
|
665
|
+
deps,
|
|
666
|
+
);
|
|
667
|
+
const nodes = data?.data?.repository?.object?.history?.nodes;
|
|
668
|
+
if (!Array.isArray(nodes)) return null;
|
|
669
|
+
let redCount = 1; // HEAD itself — its check runs just read red.
|
|
670
|
+
for (const node of nodes) {
|
|
671
|
+
if (!node || typeof node !== 'object' || typeof node.oid !== 'string') return null;
|
|
672
|
+
if (node.oid === headSha) continue;
|
|
673
|
+
const rollup = node.statusCheckRollup;
|
|
674
|
+
if (rollup === null || rollup === undefined) continue;
|
|
675
|
+
if (rollup.state === 'SUCCESS') {
|
|
676
|
+
return {
|
|
677
|
+
redCount,
|
|
678
|
+
lastGreen: {
|
|
679
|
+
sha: node.oid,
|
|
680
|
+
ageCommits: redCount,
|
|
681
|
+
ageDays: ageDaysFrom(node.committedDate, now),
|
|
682
|
+
},
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
redCount++;
|
|
686
|
+
}
|
|
687
|
+
return { redCount };
|
|
688
|
+
} catch {
|
|
689
|
+
return null;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
/**
|
|
694
|
+
* Fold a {@link readGithubLastGreen} answer into a GitHub red reading — the
|
|
695
|
+
* one shape the inline (#856) and the deferred (#1728) history paths share.
|
|
696
|
+
* `history: null` (the query failed) keeps the red verdict and adds
|
|
697
|
+
* `details.reason: 'last-green-query-failed'`; any earlier `reason` and the
|
|
698
|
+
* `deferredHistory` marker are dropped.
|
|
699
|
+
*
|
|
700
|
+
* @param {{ status: 'red', ok: false, failingJobName: string, failingJobNames: string[], details: { cliUsed: 'gh', checkRunCounts: object } }} red
|
|
701
|
+
* @param {{ redCount: number, lastGreen?: object }|null} history
|
|
702
|
+
* @returns {object}
|
|
703
|
+
*/
|
|
704
|
+
function withGithubHistory(red, history) {
|
|
705
|
+
return {
|
|
706
|
+
status: red.status,
|
|
707
|
+
ok: red.ok,
|
|
708
|
+
failingJobName: red.failingJobName,
|
|
709
|
+
failingJobNames: red.failingJobNames,
|
|
710
|
+
...(history ? { redCount: history.redCount } : {}),
|
|
711
|
+
...(history?.lastGreen ? { lastGreen: history.lastGreen } : {}),
|
|
712
|
+
details: {
|
|
713
|
+
cliUsed: red.details.cliUsed,
|
|
714
|
+
...(history ? {} : { reason: 'last-green-query-failed' }),
|
|
715
|
+
checkRunCounts: red.details.checkRunCounts,
|
|
716
|
+
},
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
/**
|
|
721
|
+
* Run ONLY the #856 history query for a red reading that
|
|
722
|
+
* `checkCiStatus({ deferHistory: true })` returned with
|
|
723
|
+
* `details.reason: 'last-green-deferred'` (#1728), and return the enriched
|
|
724
|
+
* reading — the same `redCount` / `lastGreen` / `last-green-query-failed`
|
|
725
|
+
* fields the inline path produces. It repeats neither `gh repo view` nor the
|
|
726
|
+
* check-runs query: the reading carries `deferredHistory` (`nameWithOwner`,
|
|
727
|
+
* `headSha`, `repoHost`), which is everything the query needs.
|
|
728
|
+
*
|
|
729
|
+
* Never throws (the history query is non-fatal, as inline). A reading without
|
|
730
|
+
* `deferredHistory` — green, GitLab, explicit-sha, or not deferred — comes
|
|
731
|
+
* back unchanged.
|
|
732
|
+
*
|
|
733
|
+
* @param {*} result A `checkCiStatus` result
|
|
734
|
+
* @param {{ repoRoot?: string, timeoutMs?: number, now?: number }} [opts]
|
|
735
|
+
* @param {{ execFile?: Function }} [deps] Same callback-style seam as `checkCiStatus`
|
|
736
|
+
* @returns {Promise<*>}
|
|
737
|
+
*/
|
|
738
|
+
export async function readDeferredGithubHistory(result, opts = {}, deps = {}) {
|
|
739
|
+
const pending = result?.deferredHistory;
|
|
740
|
+
if (!pending || typeof pending !== 'object') return result;
|
|
741
|
+
const { repoRoot = process.cwd(), timeoutMs = DEFAULT_TIMEOUT_MS, now = Date.now() } = opts;
|
|
742
|
+
const execFile = deps.execFile ? promisify(deps.execFile) : execFileAsync;
|
|
743
|
+
const history = await readGithubLastGreen(
|
|
744
|
+
pending.nameWithOwner,
|
|
745
|
+
pending.headSha,
|
|
746
|
+
now,
|
|
747
|
+
repoRoot,
|
|
748
|
+
{ execFile, timeoutMs, repoHost: pending.repoHost },
|
|
749
|
+
);
|
|
750
|
+
return withGithubHistory(result, history);
|
|
751
|
+
}
|
|
752
|
+
|
|
539
753
|
/**
|
|
540
754
|
* Look up the branch HEAD currently sits on, for pipeline-REF preference.
|
|
541
755
|
*
|
|
@@ -885,7 +1099,7 @@ async function checkGitlab(repoRoot, now, deps = {}) {
|
|
|
885
1099
|
let allowFailureJobs;
|
|
886
1100
|
try {
|
|
887
1101
|
const jobs = await glabApi(
|
|
888
|
-
|
|
1102
|
+
pipelineJobsPath(projectPath, currentPipeline.id),
|
|
889
1103
|
repoRoot,
|
|
890
1104
|
apiDeps,
|
|
891
1105
|
'array',
|
|
@@ -969,16 +1183,18 @@ async function checkGitlab(repoRoot, now, deps = {}) {
|
|
|
969
1183
|
|
|
970
1184
|
// Get the name of the first failing job on the current pipeline.
|
|
971
1185
|
let failingJobName;
|
|
1186
|
+
let failingJobNames;
|
|
972
1187
|
try {
|
|
973
1188
|
const jobs = await glabApi(
|
|
974
|
-
|
|
1189
|
+
pipelineJobsPath(projectPath, currentPipeline.id),
|
|
975
1190
|
repoRoot,
|
|
976
1191
|
apiDeps,
|
|
977
1192
|
'array',
|
|
978
1193
|
);
|
|
979
1194
|
if (Array.isArray(jobs)) {
|
|
980
|
-
const
|
|
981
|
-
failingJobName =
|
|
1195
|
+
const failedJobs = jobs.filter((j) => j?.status === 'failed');
|
|
1196
|
+
failingJobName = failedJobs[0] ? sanitizeApiText(failedJobs[0].name) : undefined;
|
|
1197
|
+
failingJobNames = failedJobs.length ? collectFailingJobNames(failedJobs) : undefined;
|
|
982
1198
|
}
|
|
983
1199
|
} catch {
|
|
984
1200
|
// Non-fatal — we still report red status without job name.
|
|
@@ -990,6 +1206,7 @@ async function checkGitlab(repoRoot, now, deps = {}) {
|
|
|
990
1206
|
...(lastGreen ? { lastGreen } : {}),
|
|
991
1207
|
redCount,
|
|
992
1208
|
...(failingJobName !== undefined ? { failingJobName } : {}),
|
|
1209
|
+
...(failingJobNames !== undefined ? { failingJobNames } : {}),
|
|
993
1210
|
details: {
|
|
994
1211
|
currentPipelineId: currentPipeline.id,
|
|
995
1212
|
cliUsed: 'glab',
|
|
@@ -1020,7 +1237,7 @@ async function checkGitlab(repoRoot, now, deps = {}) {
|
|
|
1020
1237
|
}
|
|
1021
1238
|
|
|
1022
1239
|
/**
|
|
1023
|
-
* GitHub CI status check
|
|
1240
|
+
* GitHub CI status check.
|
|
1024
1241
|
*
|
|
1025
1242
|
* #872/#1022: pins the `gh repo view` lookup to `deps.repoSpec` when resolved
|
|
1026
1243
|
* (host-pinning — `gh repo view` otherwise falls back to the ambient
|
|
@@ -1042,14 +1259,31 @@ async function checkGitlab(repoRoot, now, deps = {}) {
|
|
|
1042
1259
|
* (cross-family guard, unsafe-argv guard), and `normalizeGithubSpec` falls
|
|
1043
1260
|
* back to the raw URL on an unrecognised remote shape.
|
|
1044
1261
|
*
|
|
1045
|
-
*
|
|
1046
|
-
*
|
|
1262
|
+
* LOCAL HEAD (#1727): the check-runs query names the commit `git rev-parse
|
|
1263
|
+
* HEAD` returns, never the literal `HEAD` ref — on the GitHub API `HEAD`
|
|
1264
|
+
* resolves to the tip of the REMOTE DEFAULT BRANCH, so a feature branch or an
|
|
1265
|
+
* unpushed commit used to read main's verdict, green included. `deps.sha`
|
|
1266
|
+
* (#1332), when set, names THAT commit instead. A commit the remote does not
|
|
1267
|
+
* know (HTTP 422 "No commit found for SHA") is `unknown` with reason
|
|
1268
|
+
* `head-sha-not-on-remote` — a measured answer, not a query failure.
|
|
1269
|
+
*
|
|
1270
|
+
* Red (#856): for the LOCAL-HEAD reading only (no `deps.sha`), one GraphQL
|
|
1271
|
+
* history query adds `redCount` and, when a green commit lies inside the
|
|
1272
|
+
* window, `lastGreen` (`sha`, `ageCommits`, `ageDays`; no
|
|
1273
|
+
* `pipelineId`/`agePipelines` — GitHub has no pipelines). The query is
|
|
1274
|
+
* non-fatal: on failure the red reading stays and carries
|
|
1275
|
+
* `details.reason: 'last-green-query-failed'`. An explicit-sha red reading
|
|
1276
|
+
* skips the query and carries none of the three. See {@link readGithubLastGreen}.
|
|
1277
|
+
* `deps.deferHistory` (#1728) also skips it, but marks the red reading
|
|
1278
|
+
* `details.reason: 'last-green-deferred'` with a `deferredHistory` field for
|
|
1279
|
+
* {@link readDeferredGithubHistory}.
|
|
1047
1280
|
*
|
|
1048
1281
|
* @param {string} repoRoot
|
|
1049
|
-
* @param {
|
|
1282
|
+
* @param {number} now Unix epoch ms, for `lastGreen.ageDays`
|
|
1283
|
+
* @param {{ execFile?: Function, timeoutMs?: number, repoSpec?: string, repoHost?: string, sha?: string, deferHistory?: boolean }} deps
|
|
1050
1284
|
* @returns {Promise<object|null>}
|
|
1051
1285
|
*/
|
|
1052
|
-
async function checkGithub(repoRoot, deps = {}) {
|
|
1286
|
+
async function checkGithub(repoRoot, now, deps = {}) {
|
|
1053
1287
|
// Resolve owner/repo from gh to keep the API path generic.
|
|
1054
1288
|
const repoViewArgs = ['repo', 'view'];
|
|
1055
1289
|
if (deps.repoSpec) repoViewArgs.push(deps.repoSpec);
|
|
@@ -1070,12 +1304,36 @@ async function checkGithub(repoRoot, deps = {}) {
|
|
|
1070
1304
|
'object',
|
|
1071
1305
|
);
|
|
1072
1306
|
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1307
|
+
// `per_page=100` (#1712): the API default page is 30, so a larger matrix
|
|
1308
|
+
// silently dropped runs — a red one included. Ceiling: one page of 100; a
|
|
1309
|
+
// commit with more check runs still truncates. Revisit trigger: a workflow
|
|
1310
|
+
// set whose `total_count` exceeds 100 (then paginate via `--paginate`).
|
|
1311
|
+
// A failing `git rev-parse` throws into checkCiStatus's outer catch — the
|
|
1312
|
+
// same degraded semantics the GitLab path has.
|
|
1313
|
+
const headSha = deps.sha ?? (await getHeadSha(repoRoot, deps));
|
|
1314
|
+
let data;
|
|
1315
|
+
try {
|
|
1316
|
+
data = await ghApi(
|
|
1317
|
+
`repos/${nameWithOwner}/commits/${headSha}/check-runs?per_page=100`,
|
|
1318
|
+
repoRoot,
|
|
1319
|
+
deps,
|
|
1320
|
+
'object',
|
|
1321
|
+
);
|
|
1322
|
+
} catch (err) {
|
|
1323
|
+
// Only the remote's "this commit does not exist here" answer is caught:
|
|
1324
|
+
// the commit is unpushed (or was never on this remote), so there is no CI
|
|
1325
|
+
// verdict for it — unknown, not degraded. Every other error, timeout and
|
|
1326
|
+
// ENOENT included, keeps the outer catch's handling.
|
|
1327
|
+
const text = `${String(err?.stderr ?? '')} ${String(err?.message ?? '')}`;
|
|
1328
|
+
if (/No commit found for SHA/i.test(text)) {
|
|
1329
|
+
return {
|
|
1330
|
+
status: 'unknown',
|
|
1331
|
+
ok: false,
|
|
1332
|
+
details: { cliUsed: 'gh', reason: 'head-sha-not-on-remote' },
|
|
1333
|
+
};
|
|
1334
|
+
}
|
|
1335
|
+
throw err;
|
|
1336
|
+
}
|
|
1079
1337
|
|
|
1080
1338
|
const checkRuns = data.check_runs;
|
|
1081
1339
|
if (!Array.isArray(checkRuns) || checkRuns.length === 0) {
|
|
@@ -1104,21 +1362,48 @@ async function checkGithub(repoRoot, deps = {}) {
|
|
|
1104
1362
|
else if (run?.status === 'queued' && run?.conclusion === null) checkRunCounts.queued++;
|
|
1105
1363
|
else checkRunCounts.other++;
|
|
1106
1364
|
}
|
|
1107
|
-
const
|
|
1365
|
+
const failedRuns = checkRuns.filter(
|
|
1108
1366
|
(r) => r?.conclusion === 'failure' || r?.conclusion === 'action_required',
|
|
1109
1367
|
);
|
|
1368
|
+
const failedRun = failedRuns[0];
|
|
1110
1369
|
|
|
1111
1370
|
if (failedRun) {
|
|
1112
|
-
|
|
1371
|
+
const red = {
|
|
1113
1372
|
status: 'red',
|
|
1114
1373
|
ok: false,
|
|
1115
1374
|
failingJobName: sanitizeApiText(failedRun.name),
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
reason: 'lastGreen-not-implemented-for-github',
|
|
1119
|
-
checkRunCounts,
|
|
1120
|
-
},
|
|
1375
|
+
failingJobNames: collectFailingJobNames(failedRuns),
|
|
1376
|
+
details: { cliUsed: 'gh', checkRunCounts },
|
|
1121
1377
|
};
|
|
1378
|
+
// #856: the history query runs only for the LOCAL-HEAD reading. Explicit-sha
|
|
1379
|
+
// readings (session-start pushed-commit follow-up, release preflight) read
|
|
1380
|
+
// none of `redCount`/`lastGreen`, and the extra ~0.5–0.7 s pushed the
|
|
1381
|
+
// follow-up past its shared PROBE_BUDGET_MS, degrading a red pushed commit
|
|
1382
|
+
// to "pipeline not checked". Not attempted means no
|
|
1383
|
+
// `last-green-query-failed` reason either. Ceiling: a `{ sha }` caller gets
|
|
1384
|
+
// no `lastGreen`; revisit trigger: a consumer that reads `lastGreen` from a
|
|
1385
|
+
// `{ sha }` reading (then add an explicit opt-in).
|
|
1386
|
+
if (deps.sha) return red;
|
|
1387
|
+
// #1728: `deferHistory` returns the red verdict NOW and leaves the history
|
|
1388
|
+
// to {@link readDeferredGithubHistory}, so a caller with a hard budget (the
|
|
1389
|
+
// session-start probe) holds the red reading before the ~0.5 s query runs.
|
|
1390
|
+
if (deps.deferHistory) {
|
|
1391
|
+
return {
|
|
1392
|
+
...red,
|
|
1393
|
+
details: { cliUsed: 'gh', reason: 'last-green-deferred', checkRunCounts },
|
|
1394
|
+
deferredHistory: {
|
|
1395
|
+
nameWithOwner,
|
|
1396
|
+
headSha,
|
|
1397
|
+
...(deps.repoHost ? { repoHost: deps.repoHost } : {}),
|
|
1398
|
+
},
|
|
1399
|
+
};
|
|
1400
|
+
}
|
|
1401
|
+
// Ceiling (#1728): every other local-HEAD caller still pays the query on
|
|
1402
|
+
// red, even one that discards the fields — today `dispatcher/rank.mjs`
|
|
1403
|
+
// (`normalizeCiSignal` keeps status only). Revisit trigger: rank latency on
|
|
1404
|
+
// red GitHub repos (then pass `deferHistory: true` there too).
|
|
1405
|
+
const history = await readGithubLastGreen(nameWithOwner, headSha, now, repoRoot, deps);
|
|
1406
|
+
return withGithubHistory(red, history);
|
|
1122
1407
|
}
|
|
1123
1408
|
|
|
1124
1409
|
const allSuccess = checkRunCounts.success === checkRuns.length;
|
|
@@ -1180,11 +1465,19 @@ async function checkGithub(repoRoot, deps = {}) {
|
|
|
1180
1465
|
* timeoutMs?: number,
|
|
1181
1466
|
* now?: number,
|
|
1182
1467
|
* sha?: string,
|
|
1468
|
+
* deferHistory?: boolean,
|
|
1183
1469
|
* }} opts `sha` (#1332): query the verdict for THIS commit instead of the
|
|
1184
1470
|
* local HEAD. Must be a full hex object id (40 or 64 chars) — it is matched
|
|
1185
1471
|
* against GitLab's full pipeline SHAs and interpolated into a `gh api` path,
|
|
1186
1472
|
* so anything else is refused as `query-failed` before any spawn. Absent →
|
|
1187
1473
|
* behaviour identical to before the option existed.
|
|
1474
|
+
* `deferHistory` (#1728, opt-in, only `true` counts): a GitHub LOCAL-HEAD red
|
|
1475
|
+
* reading returns WITHOUT the #856 history query, marked
|
|
1476
|
+
* `details.reason: 'last-green-deferred'` and carrying `deferredHistory`;
|
|
1477
|
+
* {@link readDeferredGithubHistory} runs the query later. Only the
|
|
1478
|
+
* session-start probe opts in — its budget would otherwise drop the red
|
|
1479
|
+
* verdict with the query. No effect on GitLab, on `sha` readings, or on
|
|
1480
|
+
* non-red readings.
|
|
1188
1481
|
* @param {{
|
|
1189
1482
|
* execFile?: Function,
|
|
1190
1483
|
* resolveRepoSpec?: (opts: { repoRoot: string, vcs: 'gitlab'|'github' }) => string|undefined,
|
|
@@ -1204,7 +1497,7 @@ async function checkGithub(repoRoot, deps = {}) {
|
|
|
1204
1497
|
* ok: boolean,
|
|
1205
1498
|
* lastGreen?: {
|
|
1206
1499
|
* sha: string,
|
|
1207
|
-
* pipelineId
|
|
1500
|
+
* pipelineId?: number,
|
|
1208
1501
|
* ageCommits: number,
|
|
1209
1502
|
* agePipelines?: number,
|
|
1210
1503
|
* ageDays: number|null,
|
|
@@ -1238,6 +1531,16 @@ async function checkGithub(repoRoot, deps = {}) {
|
|
|
1238
1531
|
* check-runs-unrecognised, or check-runs-mixed (multiple non-success kinds),
|
|
1239
1532
|
* never as a new status value — consumers fail open on an unknown status string
|
|
1240
1533
|
* and the session-start renderer prints nothing for one.
|
|
1534
|
+
*
|
|
1535
|
+
* GitHub-specific (#1727/#856): `lastGreen.pipelineId` and
|
|
1536
|
+
* `lastGreen.agePipelines` are GitLab-only — GitHub has no pipelines, so its
|
|
1537
|
+
* `lastGreen` carries `sha`, `ageCommits` and `ageDays` only, and
|
|
1538
|
+
* `details.currentPipelineId` stays absent. Reasons: `head-sha-not-on-remote`
|
|
1539
|
+
* (unknown — the local HEAD is not on the remote, e.g. unpushed) and
|
|
1540
|
+
* `last-green-query-failed` (red — the history query failed, so `redCount`
|
|
1541
|
+
* and `lastGreen` are absent) and `last-green-deferred` (red under
|
|
1542
|
+
* `deferHistory` — the history was not queried yet; `deferredHistory`
|
|
1543
|
+
* carries what {@link readDeferredGithubHistory} needs).
|
|
1241
1544
|
*/
|
|
1242
1545
|
export async function checkCiStatus(opts = {}, deps = {}) {
|
|
1243
1546
|
const {
|
|
@@ -1246,7 +1549,9 @@ export async function checkCiStatus(opts = {}, deps = {}) {
|
|
|
1246
1549
|
timeoutMs = DEFAULT_TIMEOUT_MS,
|
|
1247
1550
|
now = Date.now(),
|
|
1248
1551
|
sha: rawSha,
|
|
1552
|
+
deferHistory: rawDeferHistory,
|
|
1249
1553
|
} = opts;
|
|
1554
|
+
const deferHistory = rawDeferHistory === true;
|
|
1250
1555
|
|
|
1251
1556
|
// Validate at the boundary (#1332): the value reaches an API path and an
|
|
1252
1557
|
// equality match against full SHAs. A short or non-hex SHA would silently
|
|
@@ -1336,7 +1641,7 @@ export async function checkCiStatus(opts = {}, deps = {}) {
|
|
|
1336
1641
|
if (vcs === 'github') {
|
|
1337
1642
|
const repoSpec = resolveRepoSpecDep({ repoRoot, vcs });
|
|
1338
1643
|
const repoHost = resolveRepoHostDep({ repoRoot, vcs });
|
|
1339
|
-
return await checkGithub(repoRoot, { ...depsWithExec, repoSpec, repoHost, sha });
|
|
1644
|
+
return await checkGithub(repoRoot, now, { ...depsWithExec, repoSpec, repoHost, sha, deferHistory });
|
|
1340
1645
|
}
|
|
1341
1646
|
|
|
1342
1647
|
// Unknown VCS value — silent no-op.
|
|
@@ -110,6 +110,14 @@ const FORBIDDEN_CODEX_EVENTS = new Set(
|
|
|
110
110
|
*
|
|
111
111
|
* The adapter this actually needs is a tool-name MAP (shell/exec_command/
|
|
112
112
|
* unified_exec -> Bash, apply_patch -> Edit/Write), not a payload bridge.
|
|
113
|
+
*
|
|
114
|
+
* PARTLY REFUTED for the shell tool (#1669), measured 2026-10-20 on
|
|
115
|
+
* codex-cli 0.160.1 with a logging PreToolUse hook under `codex exec`: the
|
|
116
|
+
* hook payload of a shell call carries `"tool_name":"Bash"` and
|
|
117
|
+
* `"tool_input":{"command":"<string>"}` — Codex already maps its shell tool to
|
|
118
|
+
* the Claude name at the hook boundary. These names are the MODEL-facing tool
|
|
119
|
+
* vocabulary, not the hook `tool_name` values. Bash-gated handlers need no
|
|
120
|
+
* map; the apply_patch -> Edit/Write side was not re-measured.
|
|
113
121
|
*/
|
|
114
122
|
export const CODEX_TOOL_NAMES = Object.freeze([
|
|
115
123
|
'shell',
|
|
@@ -36,6 +36,7 @@ import path from 'node:path';
|
|
|
36
36
|
|
|
37
37
|
import { parseBootstrapLock } from './bootstrap-lock-freshness.mjs';
|
|
38
38
|
import { countSessionsInJsonl } from './sessions-canonical.mjs';
|
|
39
|
+
import { resolveEventsLedgerRoot } from './platform.mjs';
|
|
39
40
|
|
|
40
41
|
/** Milliseconds in one hour — exported for testability. */
|
|
41
42
|
export const MS_PER_HOUR = 60 * 60 * 1000;
|
|
@@ -103,7 +104,9 @@ async function countSessions(filePath) {
|
|
|
103
104
|
try {
|
|
104
105
|
// Counting rule (blank/malformed lines, id-less records) lives in
|
|
105
106
|
// `countSessionsInJsonl`; only the async read is local.
|
|
106
|
-
|
|
107
|
+
// `measuredOnly` (audit E1): a ledger of synthetic backfill stubs is not
|
|
108
|
+
// session history — the operator never ran a real session yet.
|
|
109
|
+
return countSessionsInJsonl(await readFile(filePath, 'utf8'), { measuredOnly: true });
|
|
107
110
|
} catch {
|
|
108
111
|
return 0;
|
|
109
112
|
}
|
|
@@ -164,7 +167,7 @@ export async function detectColdStart(opts = {}) {
|
|
|
164
167
|
}
|
|
165
168
|
|
|
166
169
|
const lockPath = path.join(repoRoot, BOOTSTRAP_LOCK_REL);
|
|
167
|
-
const sessionsPath = path.join(repoRoot, SESSIONS_JSONL_REL);
|
|
170
|
+
const sessionsPath = path.join(resolveEventsLedgerRoot(repoRoot), SESSIONS_JSONL_REL);
|
|
168
171
|
const markerPath = path.join(repoRoot, WELCOME_MARKER_REL);
|
|
169
172
|
|
|
170
173
|
// PRD §F1.3 Unwanted behaviour: if bootstrap.lock missing, do NOT emit.
|