session-orchestrator 5.12.0 → 5.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (385) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +4 -1
  3. package/.codex-plugin/agents/explorer.toml +1 -1
  4. package/.codex-plugin/agents/session-reviewer.toml +2 -2
  5. package/.codex-plugin/agents/wave-worker.toml +1 -1
  6. package/.codex-plugin/plugin.json +1 -1
  7. package/.cursor-plugin/plugin.json +1 -1
  8. package/.orchestrator/policy/blocked-commands.json +8 -0
  9. package/.orchestrator/policy/public-export.json +30 -0
  10. package/CHANGELOG.md +162 -4
  11. package/README.md +126 -127
  12. package/agents/analyst.md +7 -2
  13. package/agents/architect-reviewer.md +7 -2
  14. package/agents/code-implementer.md +4 -2
  15. package/agents/db-specialist.md +9 -2
  16. package/agents/dialectic-deriver.md +9 -3
  17. package/agents/docs-writer.md +7 -2
  18. package/agents/eval-judge.md +7 -1
  19. package/agents/qa-strategist.md +7 -2
  20. package/agents/security-reviewer.md +9 -2
  21. package/agents/session-reviewer.md +18 -2
  22. package/agents/skill-applied-judge.md +7 -1
  23. package/agents/test-writer.md +9 -2
  24. package/agents/ui-developer.md +9 -2
  25. package/agents/ux-evaluator.md +6 -1
  26. package/commands/session.md +1 -1
  27. package/commands/templates-ack.md +89 -18
  28. package/docs/agent-authoring.md +30 -11
  29. package/docs/ci-setup.md +38 -2
  30. package/docs/codex-setup.md +33 -9
  31. package/docs/components.md +13 -2
  32. package/docs/cursor-setup.md +3 -3
  33. package/docs/events-schema.md +5 -4
  34. package/docs/evidence-candidates.md +63 -0
  35. package/docs/fleet-checkin-audit.md +7 -4
  36. package/docs/github-mirror-protection.md +150 -3
  37. package/docs/install.md +90 -17
  38. package/docs/instruction-delivery.md +1 -1
  39. package/docs/migration-v4.md +2 -2
  40. package/docs/mods.md +118 -0
  41. package/docs/owner-config-schema.md +1 -0
  42. package/docs/plugin-architecture-v3.md +1 -1
  43. package/docs/pm-skills-marketplace.md +4 -3
  44. package/docs/rule-authoring.md +24 -13
  45. package/docs/scope-collision-guard.md +2 -1
  46. package/docs/session-config-reference.md +49 -15
  47. package/docs/session-config-template.md +30 -22
  48. package/docs/sessions-ledger-repair.md +137 -0
  49. package/docs/vault-docs-architecture.md +1 -1
  50. package/hooks/_lib/hook-import-set.json +406 -73
  51. package/hooks/_lib/hook-root.mjs +301 -0
  52. package/hooks/_lib/lock-bootstrap.mjs +434 -4
  53. package/hooks/_lib/orchestrator-ignore.mjs +3 -0
  54. package/hooks/_lib/subagent-transcript.mjs +2 -2
  55. package/hooks/_lib/vcs-create-matcher.mjs +13 -17
  56. package/hooks/cwd-change-restore.mjs +5 -1
  57. package/hooks/enforce-commands.mjs +4 -3
  58. package/hooks/enforce-scope.mjs +11 -1
  59. package/hooks/hooks-codex.json +48 -2
  60. package/hooks/hooks.json +31 -31
  61. package/hooks/on-session-end.mjs +38 -15
  62. package/hooks/on-session-start.mjs +139 -49
  63. package/hooks/on-stop.mjs +11 -6
  64. package/hooks/post-bash-issue-budget-refund.mjs +6 -1
  65. package/hooks/post-bash-write-verify.mjs +8 -4
  66. package/hooks/post-edit-validate.mjs +45 -52
  67. package/hooks/post-subagent-discovery-validator.mjs +2 -2
  68. package/hooks/post-tool-batch-wave-signal.mjs +8 -4
  69. package/hooks/post-tool-failure-corrective-context.mjs +5 -1
  70. package/hooks/pre-bash-destructive-guard.mjs +220 -8
  71. package/hooks/pre-bash-issue-budget.mjs +6 -1
  72. package/hooks/pre-bash-sessions-ledger-guard.mjs +691 -123
  73. package/hooks/pre-bash-staging-fence.mjs +258 -14
  74. package/hooks/pre-bash-templates-first.mjs +28 -27
  75. package/hooks/run-node.sh +1 -1
  76. package/hooks/skill-invocation-telemetry.mjs +15 -8
  77. package/hooks/subagent-telemetry.mjs +77 -14
  78. package/hooks/wave-scope-commit-guard.mjs +143 -14
  79. package/mods/so-hud/.claude-plugin/plugin.json +11 -0
  80. package/mods/so-hud/README.md +57 -0
  81. package/mods/so-hud/hooks/beacon-io.mjs +101 -0
  82. package/mods/so-hud/hooks/heredoc-scan.ts +394 -0
  83. package/mods/so-hud/hooks/hooks.json +1 -0
  84. package/mods/so-hud/hooks/logic.ts +182 -0
  85. package/mods/so-hud/hooks/register.tsx +670 -0
  86. package/mods/so-hud/hooks/rules.ts +2998 -0
  87. package/mods/so-hud/tsconfig.json +8 -0
  88. package/mods/so-hud/types/index.d.ts +36 -0
  89. package/package.json +4 -2
  90. package/rules/_index.md +1 -1
  91. package/rules/always-on/ask-via-tool.md +1 -1
  92. package/rules/always-on/loop-and-monitor.md +17 -194
  93. package/rules/always-on/parallel-sessions.md +3 -1
  94. package/rules/opt-in-stack/backend-data.md +1 -1
  95. package/rules/opt-in-stack/backend.md +1 -1
  96. package/rules/opt-in-stack/security-web.md +5 -1
  97. package/scripts/agent-usage-census.mjs +79 -0
  98. package/scripts/apply-session-learnings.mjs +34 -6
  99. package/scripts/archive-closed-prds.mjs +21 -5
  100. package/scripts/autopilot.mjs +11 -5
  101. package/scripts/backfill-abandoned-sessions.mjs +13 -3
  102. package/scripts/backfill-evidence-digest.mjs +2 -1
  103. package/scripts/backfill-learnings-from-vault.mjs +61 -6
  104. package/scripts/backfill-sessions.mjs +16 -2
  105. package/scripts/check-package-manager.mjs +26 -8
  106. package/scripts/check-sessions-integrity.mjs +4 -1
  107. package/scripts/ci/assert-vitest-green.mjs +3 -0
  108. package/scripts/ci/windows-smoke.mjs +201 -0
  109. package/scripts/close-precheck.mjs +55 -0
  110. package/scripts/dialectic-deriver.mjs +20 -12
  111. package/scripts/emit-event.mjs +2 -2
  112. package/scripts/emit-session.mjs +239 -20
  113. package/scripts/eval-session.mjs +17 -3
  114. package/scripts/export-hw-learnings.mjs +29 -2
  115. package/scripts/fleet-checkin-audit.mjs +8 -4
  116. package/scripts/fleet-checkin.mjs +39 -27
  117. package/scripts/gc-stale-worktrees.mjs +6 -1
  118. package/scripts/github-export.mjs +128 -0
  119. package/scripts/github-protection-audit.mjs +3 -1
  120. package/scripts/handover-queue-check.mjs +209 -0
  121. package/scripts/harness-audit.mjs +6 -4
  122. package/scripts/lib/agent-frontmatter.mjs +57 -10
  123. package/scripts/lib/agent-status.mjs +125 -7
  124. package/scripts/lib/agent-usage-census.mjs +394 -0
  125. package/scripts/lib/auto-dialectic.mjs +17 -10
  126. package/scripts/lib/auto-dream.mjs +22 -14
  127. package/scripts/lib/autopilot/loop.mjs +3 -1
  128. package/scripts/lib/autopilot/recent-runs.mjs +4 -2
  129. package/scripts/lib/autopilot/stall-sampler.mjs +2 -1
  130. package/scripts/lib/autopilot/telemetry.mjs +5 -1
  131. package/scripts/lib/autopilot/worktree-pipeline.mjs +5 -5
  132. package/scripts/lib/baseline-archetypes.mjs +10 -2
  133. package/scripts/lib/blocked-commands-policy.mjs +4 -3
  134. package/scripts/lib/build-live-signals.mjs +13 -9
  135. package/scripts/lib/ci-status-banner.mjs +329 -24
  136. package/scripts/lib/codex/plugin-contract.mjs +8 -0
  137. package/scripts/lib/cold-start-detector.mjs +5 -2
  138. package/scripts/lib/command-blocker.mjs +603 -43
  139. package/scripts/lib/common.mjs +3 -2
  140. package/scripts/lib/config/cross-repo.mjs +74 -7
  141. package/scripts/lib/config/host-paths.mjs +6 -2
  142. package/scripts/lib/config/section-extractor.mjs +65 -11
  143. package/scripts/lib/config/template-keys.mjs +153 -0
  144. package/scripts/lib/config/vault-integration.mjs +1 -0
  145. package/scripts/lib/config.mjs +2 -2
  146. package/scripts/lib/coordinator-snapshot.mjs +1 -1
  147. package/scripts/lib/discovery/triage-state.mjs +2 -1
  148. package/scripts/lib/dispatcher/rank.mjs +5 -7
  149. package/scripts/lib/ecosystem-health.mjs +2 -2
  150. package/scripts/lib/eval/engine.mjs +3 -2
  151. package/scripts/lib/eval/session-resolve.mjs +6 -2
  152. package/scripts/lib/eval/sink.mjs +4 -3
  153. package/scripts/lib/events-retention-banner.mjs +3 -2
  154. package/scripts/lib/events-schema.mjs +10 -0
  155. package/scripts/lib/events.mjs +23 -12
  156. package/scripts/lib/evolve/autonomy-verdict.mjs +4 -9
  157. package/scripts/lib/evolve/autopilot-effectiveness.mjs +8 -18
  158. package/scripts/lib/fleet-protocol.mjs +4 -0
  159. package/scripts/lib/gates/gate-full.mjs +172 -11
  160. package/scripts/lib/gates/gate-helpers.mjs +236 -23
  161. package/scripts/lib/git-config-drift.mjs +141 -7
  162. package/scripts/lib/github-export.mjs +447 -0
  163. package/scripts/lib/gitlab-portfolio/cli.mjs +16 -5
  164. package/scripts/lib/handover-queue-marks.mjs +159 -0
  165. package/scripts/lib/hardware-pattern-detector.mjs +103 -13
  166. package/scripts/lib/harness-audit/categories/category1.mjs +16 -4
  167. package/scripts/lib/harness-audit/categories/category4.mjs +11 -8
  168. package/scripts/lib/harness-audit/categories/category6.mjs +25 -5
  169. package/scripts/lib/harness-audit/categories/category9.mjs +10 -4
  170. package/scripts/lib/host-identity.mjs +9 -5
  171. package/scripts/lib/http-request.mjs +64 -0
  172. package/scripts/lib/instruction-budget-guard.mjs +30 -22
  173. package/scripts/lib/io.mjs +6 -3
  174. package/scripts/lib/issue-budget-reconcile.mjs +3 -2
  175. package/scripts/lib/issue-budget.mjs +36 -7
  176. package/scripts/lib/learnings/evidence-candidates.mjs +154 -0
  177. package/scripts/lib/learnings/expiry-sweep.mjs +99 -7
  178. package/scripts/lib/learnings/io.mjs +289 -15
  179. package/scripts/lib/learnings/select.mjs +53 -4
  180. package/scripts/lib/learnings/store-role.mjs +209 -0
  181. package/scripts/lib/learnings/surface.mjs +46 -7
  182. package/scripts/lib/ledger-path.mjs +113 -0
  183. package/scripts/lib/maintenance-due-banner.mjs +63 -12
  184. package/scripts/lib/memory-banner.mjs +5 -6
  185. package/scripts/lib/memory-proposals/collector.mjs +5 -2
  186. package/scripts/lib/memory-proposals/sink.mjs +7 -4
  187. package/scripts/lib/memory-proposals/store.mjs +22 -5
  188. package/scripts/lib/mode-selector-accuracy.mjs +2 -1
  189. package/scripts/lib/no-follow-open.mjs +57 -0
  190. package/scripts/lib/orphan-reaper/reaper-audit.mjs +4 -2
  191. package/scripts/lib/own-lock-root.mjs +95 -0
  192. package/scripts/lib/owner-interview.mjs +2 -1
  193. package/scripts/lib/owner-yaml.mjs +70 -7
  194. package/scripts/lib/platform.mjs +161 -9
  195. package/scripts/lib/plugin-update-banner.mjs +15 -11
  196. package/scripts/lib/pre-push-local-partial.mjs +117 -0
  197. package/scripts/lib/pre-push-remote-gate.mjs +151 -25
  198. package/scripts/lib/process-group.mjs +270 -34
  199. package/scripts/lib/project-hygiene.mjs +28 -0
  200. package/scripts/lib/quality-gate.mjs +33 -9
  201. package/scripts/lib/reconcile/backlog.mjs +29 -15
  202. package/scripts/lib/reconcile/engine.mjs +26 -2
  203. package/scripts/lib/reconcile/idempotency.mjs +65 -3
  204. package/scripts/lib/reconcile/rule-expiry-sweep.mjs +53 -12
  205. package/scripts/lib/reconcile/verdict-ledger.mjs +224 -0
  206. package/scripts/lib/reconcile-nudge-banner.mjs +40 -6
  207. package/scripts/lib/resource-probe/evaluate.mjs +32 -9
  208. package/scripts/lib/resource-probe/probe-platform.mjs +3 -1
  209. package/scripts/lib/rule-loader.mjs +20 -10
  210. package/scripts/lib/scope-baseline.mjs +123 -8
  211. package/scripts/lib/scope-echo.mjs +5 -2
  212. package/scripts/lib/scope-gate.mjs +55 -20
  213. package/scripts/lib/session-close-backfill.mjs +75 -7
  214. package/scripts/lib/session-end/close-precheck.mjs +151 -0
  215. package/scripts/lib/session-end/phase-skip.mjs +8 -7
  216. package/scripts/lib/session-end/tail-runner.mjs +7 -2
  217. package/scripts/lib/session-id.mjs +9 -1
  218. package/scripts/lib/session-identity/own-session.mjs +63 -0
  219. package/scripts/lib/session-lock.mjs +114 -0
  220. package/scripts/lib/session-record-repair.mjs +245 -35
  221. package/scripts/lib/session-registry.mjs +72 -9
  222. package/scripts/lib/session-schema/constants.mjs +9 -0
  223. package/scripts/lib/session-schema/filters.mjs +56 -1
  224. package/scripts/lib/session-schema/validator.mjs +15 -5
  225. package/scripts/lib/session-start-probes.mjs +149 -22
  226. package/scripts/lib/session-token-rollup.mjs +2 -1
  227. package/scripts/lib/session-work-trees.mjs +166 -0
  228. package/scripts/lib/sessions-canonical.mjs +61 -3
  229. package/scripts/lib/sessions-integrity-banner.mjs +17 -39
  230. package/scripts/lib/sessions-staleness-banner.mjs +8 -5
  231. package/scripts/lib/sh-quote.mjs +1 -0
  232. package/scripts/lib/skill-evidence-window.mjs +5 -1
  233. package/scripts/lib/skill-health/join.mjs +10 -9
  234. package/scripts/lib/skill-invocations-schema.mjs +13 -0
  235. package/scripts/lib/skill-judgments-schema.mjs +6 -2
  236. package/scripts/lib/snapshot-probe.mjs +414 -0
  237. package/scripts/lib/so-hud/collect.mjs +49 -5
  238. package/scripts/lib/so-hud/runtime.mjs +15 -4
  239. package/scripts/lib/spiral-carryover.mjs +8 -1
  240. package/scripts/lib/state-md/frontmatter-mutators.mjs +266 -8
  241. package/scripts/lib/state-md/idle-reset.mjs +14 -5
  242. package/scripts/lib/state-md/mission-status.mjs +5 -1
  243. package/scripts/lib/state-md-peer-guard.mjs +2 -0
  244. package/scripts/lib/state-md.mjs +31 -8
  245. package/scripts/lib/sunset/walker.mjs +4 -3
  246. package/scripts/lib/telemetry/consent.mjs +4 -2
  247. package/scripts/lib/telemetry/queue.mjs +3 -3
  248. package/scripts/lib/telemetry/sync.mjs +45 -9
  249. package/scripts/lib/test-runner/artifact-paths.mjs +10 -2
  250. package/scripts/lib/tmux-layout/layouts.mjs +20 -3
  251. package/scripts/lib/ux-grill/paths.mjs +5 -1
  252. package/scripts/lib/validate/check-agents.mjs +37 -6
  253. package/scripts/lib/validate/check-component-paths.mjs +25 -1
  254. package/scripts/lib/validate/check-guard-requires-parity.mjs +3 -3
  255. package/scripts/lib/validate/check-hooks-symmetry.mjs +53 -32
  256. package/scripts/lib/validate/check-json-files.mjs +3 -1
  257. package/scripts/lib/validate/check-learning-provenance.mjs +3 -2
  258. package/scripts/lib/validate/check-learnings-shell-writes.mjs +10 -4
  259. package/scripts/lib/validate/check-metrics-ledger-root.mjs +234 -0
  260. package/scripts/lib/validate/check-owner-leakage.mjs +159 -11
  261. package/scripts/lib/validate/check-test-posix-shell.mjs +246 -0
  262. package/scripts/lib/validate/check-unwired-features.mjs +4 -2
  263. package/scripts/lib/validate/check-vcs-repo-flag-positional.mjs +58 -0
  264. package/scripts/lib/validate/check-vcs-repo-flag.mjs +259 -73
  265. package/scripts/lib/validate/check-workflow-continue-on-error.mjs +63 -0
  266. package/scripts/lib/validate/confidential-names.mjs +3 -1
  267. package/scripts/lib/vault-mirror/migrate-ids.mjs +321 -0
  268. package/scripts/lib/vault-mirror/namespace.mjs +19 -7
  269. package/scripts/lib/vault-mirror/process.mjs +56 -27
  270. package/scripts/lib/vault-mirror/prune.mjs +5 -2
  271. package/scripts/lib/vault-mirror/render-learnings.mjs +3 -3
  272. package/scripts/lib/vault-mirror/render-sessions.mjs +36 -53
  273. package/scripts/lib/vault-mirror/retention.mjs +2 -2
  274. package/scripts/lib/vault-mirror/utils.mjs +79 -0
  275. package/scripts/lib/vault-staleness-banner.mjs +4 -7
  276. package/scripts/lib/vault-status/board-writer.mjs +2 -1
  277. package/scripts/lib/vault-status/narrative-mirror.mjs +66 -7
  278. package/scripts/lib/wave-executor/foreign-dispatch.mjs +20 -0
  279. package/scripts/lib/win-process-tree.mjs +137 -0
  280. package/scripts/lib/workspace.mjs +1 -1
  281. package/scripts/lib/worktree/constants.mjs +1 -1
  282. package/scripts/lib/worktree/lifecycle.mjs +1 -1
  283. package/scripts/lib/worktree/listing.mjs +1 -1
  284. package/scripts/lib/zx.mjs +50 -0
  285. package/scripts/mcp-server.mjs +16 -7
  286. package/scripts/mcp-server.sh +13 -83
  287. package/scripts/measure-policy-cache-effectiveness.mjs +7 -5
  288. package/scripts/memory-propose.mjs +10 -3
  289. package/scripts/migrate-cold-start-seed.mjs +7 -0
  290. package/scripts/migrate-legacy-learnings.sh +22 -0
  291. package/scripts/migrate-sessions-jsonl.mjs +16 -2
  292. package/scripts/migrate-vault-paths.mjs +29 -13
  293. package/scripts/parse-config.mjs +71 -14
  294. package/scripts/print-learnings-index.mjs +25 -2
  295. package/scripts/release-session-lock.mjs +94 -7
  296. package/scripts/release.mjs +451 -24
  297. package/scripts/relocate-vault-corpus.mjs +11 -7
  298. package/scripts/repair-invalid-sessions.mjs +68 -3
  299. package/scripts/resolve-session-invocation.mjs +10 -17
  300. package/scripts/run-migrate-v2-cross-repo.mjs +56 -1
  301. package/scripts/run-quality-gate.mjs +33 -7
  302. package/scripts/self-update.mjs +56 -7
  303. package/scripts/session-commit-files.mjs +133 -0
  304. package/scripts/site-numbers.mjs +52 -9
  305. package/scripts/snapshot-probe.mjs +156 -0
  306. package/scripts/so-hud.mjs +29 -6
  307. package/scripts/state-md-idle-reset.mjs +2 -1
  308. package/scripts/sweep-expired-learnings.mjs +54 -6
  309. package/scripts/sweep-expired-rules.mjs +4 -1
  310. package/scripts/validate-plugin.mjs +22 -0
  311. package/scripts/vault-integration-watcher.mjs +54 -6
  312. package/scripts/vault-mirror.mjs +9 -0
  313. package/skills/_shared/fleet-protocol.md +142 -1
  314. package/skills/_shared/instruction-file-resolution.md +1 -1
  315. package/skills/_shared/monitor-patterns.md +44 -25
  316. package/skills/_shared/platform-tools.md +7 -0
  317. package/skills/_shared/state-ownership.md +3 -2
  318. package/skills/architecture/references/domain-model.md +1 -1
  319. package/skills/bootstrap/SKILL.md +3 -1
  320. package/skills/bootstrap/_shared-template.md +4 -4
  321. package/skills/bootstrap/fast-template.md +1 -1
  322. package/skills/bootstrap/private-contract.md +1 -1
  323. package/skills/bootstrap/references/bootstrap-retroactive-flow.md +17 -9
  324. package/skills/bootstrap/templates/agents/project-code-review.md +5 -1
  325. package/skills/bootstrap/templates/agents/project-discovery.md +5 -1
  326. package/skills/bootstrap/templates/agents/project-quality-gate.md +5 -1
  327. package/skills/claude-md-drift-check/SKILL.md +3 -3
  328. package/skills/claude-md-drift-check/checker.mjs +61 -101
  329. package/skills/close/SKILL.md +20 -7
  330. package/skills/discovery/probes/docs-staleness.mjs +7 -5
  331. package/skills/discovery/probes/frontend-slop.mjs +7 -3
  332. package/skills/discovery/probes/ssot-code-diff.mjs +5 -3
  333. package/skills/discovery/probes/supply-chain-slopcheck.mjs +10 -8
  334. package/skills/discovery/probes/vault-narrative-staleness.mjs +30 -10
  335. package/skills/discovery/probes/vault-staleness.mjs +5 -3
  336. package/skills/discovery/probes-session.md +3 -0
  337. package/skills/discovery/probes-vault.md +2 -0
  338. package/skills/dispatcher/SKILL.md +1 -1
  339. package/skills/eval/SKILL.md +3 -1
  340. package/skills/evolve/SKILL.md +2 -0
  341. package/skills/evolve/references/evolve-analyze-mode.md +3 -3
  342. package/skills/evolve/references/evolve-dialectic-mode.md +1 -1
  343. package/skills/gitlab-ops/SKILL.md +7 -6
  344. package/skills/gitlab-portfolio/SKILL.md +2 -2
  345. package/skills/go/SKILL.md +13 -1
  346. package/skills/npm-publish/SKILL.md +5 -2
  347. package/skills/plan/SKILL.md +23 -10
  348. package/skills/plan/mode-feature.md +13 -8
  349. package/skills/plan/mode-new.md +6 -14
  350. package/skills/plan/prd-feature-template.md +19 -0
  351. package/skills/plan/prd-full-template.md +12 -5
  352. package/skills/plan/prd-premortem-panel.md +48 -0
  353. package/skills/plan/prd-reviewer-prompt.md +11 -3
  354. package/skills/plan/soul.md +1 -1
  355. package/skills/portfolio/SKILL.md +5 -6
  356. package/skills/quality-gates/SKILL.md +2 -4
  357. package/skills/reconcile/SKILL.md +27 -4
  358. package/skills/release/SKILL.md +16 -8
  359. package/skills/session-end/SKILL.md +94 -11
  360. package/skills/session-end/phase-3-2-docs-verification.md +1 -0
  361. package/skills/session-end/phase-3-6-tail.md +11 -7
  362. package/skills/session-end/phase-3-7a-recommendations.md +9 -2
  363. package/skills/session-end/plan-verification.md +16 -1
  364. package/skills/session-end/references/phase-3-documentation-updates.md +14 -12
  365. package/skills/session-end/references/phase-5-issue-cleanup.md +1 -1
  366. package/skills/session-end/references/session-summary-template.md +4 -0
  367. package/skills/session-end/session-metrics-write.md +7 -4
  368. package/skills/session-plan/SKILL.md +5 -3
  369. package/skills/session-start/SKILL.md +9 -3
  370. package/skills/session-start/phase-4-5-resource-health.md +11 -5
  371. package/skills/session-start/phase-8-5-express-path.md +4 -3
  372. package/skills/session-start/references/phase-1-2-session-lock.md +7 -5
  373. package/skills/session-start/references/phase-1-5-session-continuity.md +2 -2
  374. package/skills/session-start/references/phase-1-7-vault-status-board.md +1 -1
  375. package/skills/session-start/references/phase-4-ssot-environment-check.md +3 -2
  376. package/skills/tmux-layout/SKILL.md +2 -2
  377. package/skills/vault-sync/validator.mjs +3 -2
  378. package/skills/wave-executor/SKILL.md +5 -4
  379. package/skills/wave-executor/references/wave-executor-state-init.md +3 -3
  380. package/skills/wave-executor/references/wave-loop-dispatch.md +11 -5
  381. package/skills/wave-executor/references/wave-loop-review.md +13 -5
  382. package/skills/wave-executor/references/wave-loop-scope-manifest.md +1 -1
  383. package/assets/og-card.png +0 -0
  384. package/assets/og-card.svg +0 -47
  385. 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
- `${projectPath}/pipelines/${currentPipeline.id}/jobs`,
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
- `${projectPath}/pipelines/${currentPipeline.id}/jobs`,
1189
+ pipelineJobsPath(projectPath, currentPipeline.id),
975
1190
  repoRoot,
976
1191
  apiDeps,
977
1192
  'array',
978
1193
  );
979
1194
  if (Array.isArray(jobs)) {
980
- const failedJob = jobs.find((j) => j.status === 'failed');
981
- failingJobName = failedJob ? sanitizeApiText(failedJob.name) : undefined;
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 (v1 — red/green only; lastGreen not implemented).
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
- * `deps.sha` (#1332): when set, the check-runs query names THAT commit
1046
- * instead of the literal `HEAD` ref.
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 {{ execFile?: Function, timeoutMs?: number, repoSpec?: string, repoHost?: string, sha?: string }} deps
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
- const data = await ghApi(
1074
- `repos/${nameWithOwner}/commits/${deps.sha ?? 'HEAD'}/check-runs`,
1075
- repoRoot,
1076
- deps,
1077
- 'object',
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 failedRun = checkRuns.find(
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
- return {
1371
+ const red = {
1113
1372
  status: 'red',
1114
1373
  ok: false,
1115
1374
  failingJobName: sanitizeApiText(failedRun.name),
1116
- details: {
1117
- cliUsed: 'gh',
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: number,
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
- return countSessionsInJsonl(await readFile(filePath, 'utf8'));
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.