session-orchestrator 5.0.0 → 5.2.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 (298) hide show
  1. package/.agents/skills/autopilot/SKILL.md +1 -0
  2. package/.agents/skills/bootstrap/SKILL.md +2 -0
  3. package/.agents/skills/brainstorm/SKILL.md +3 -0
  4. package/.agents/skills/close/SKILL.md +17 -0
  5. package/.agents/skills/debug/SKILL.md +2 -0
  6. package/.agents/skills/discovery/SKILL.md +2 -1
  7. package/.agents/skills/dispatcher/SKILL.md +2 -0
  8. package/.agents/skills/eli5/SKILL.md +2 -0
  9. package/.agents/skills/eval/SKILL.md +1 -0
  10. package/.agents/skills/evolve/SKILL.md +2 -1
  11. package/.agents/skills/go/SKILL.md +18 -0
  12. package/.agents/skills/grill/SKILL.md +2 -0
  13. package/.agents/skills/harness-audit/SKILL.md +16 -0
  14. package/.agents/skills/memory-cleanup/SKILL.md +1 -0
  15. package/.agents/skills/persona-panel/SKILL.md +1 -0
  16. package/.agents/skills/plan/SKILL.md +3 -1
  17. package/.agents/skills/portfolio/SKILL.md +17 -0
  18. package/.agents/skills/reconcile/SKILL.md +1 -0
  19. package/.agents/skills/release/SKILL.md +18 -0
  20. package/.agents/skills/repo-audit/SKILL.md +1 -0
  21. package/.agents/skills/spinout/SKILL.md +1 -0
  22. package/.agents/skills/sunset-review/SKILL.md +2 -0
  23. package/.agents/skills/test/SKILL.md +17 -0
  24. package/.agents/skills/ux-grill/SKILL.md +2 -0
  25. package/.claude-plugin/marketplace.json +3 -3
  26. package/.claude-plugin/plugin.json +2 -2
  27. package/.codex-plugin/plugin.json +2 -2
  28. package/.codex-plugin/skills/autopilot/SKILL.md +5 -4
  29. package/.codex-plugin/skills/bootstrap/SKILL.md +8 -4
  30. package/.codex-plugin/skills/brainstorm/SKILL.md +11 -4
  31. package/.codex-plugin/skills/close/SKILL.md +3 -3
  32. package/.codex-plugin/skills/convergence-monitoring/SKILL.md +2 -0
  33. package/.codex-plugin/skills/convergence-monitoring/agents/openai.yaml +5 -0
  34. package/.codex-plugin/skills/debug/SKILL.md +11 -4
  35. package/.codex-plugin/skills/discovery/SKILL.md +8 -4
  36. package/.codex-plugin/skills/dispatcher/SKILL.md +4 -4
  37. package/.codex-plugin/skills/eli5/SKILL.md +9 -4
  38. package/.codex-plugin/skills/eval/SKILL.md +9 -4
  39. package/.codex-plugin/skills/evolve/SKILL.md +9 -4
  40. package/.codex-plugin/skills/go/SKILL.md +3 -3
  41. package/.codex-plugin/skills/grill/SKILL.md +11 -4
  42. package/.codex-plugin/skills/harness-audit/SKILL.md +4 -3
  43. package/.codex-plugin/skills/memory-cleanup/SKILL.md +9 -4
  44. package/.codex-plugin/skills/npm-publish/SKILL.md +2 -0
  45. package/.codex-plugin/skills/npm-publish/agents/openai.yaml +5 -0
  46. package/.codex-plugin/skills/persona-panel/SKILL.md +5 -5
  47. package/.codex-plugin/skills/plan/SKILL.md +8 -4
  48. package/.codex-plugin/skills/portfolio/SKILL.md +3 -3
  49. package/.codex-plugin/skills/reconcile/SKILL.md +9 -4
  50. package/.codex-plugin/skills/release/SKILL.md +3 -3
  51. package/.codex-plugin/skills/repo-audit/SKILL.md +6 -4
  52. package/.codex-plugin/skills/spinout/SKILL.md +4 -4
  53. package/.codex-plugin/skills/sunset-review/SKILL.md +5 -4
  54. package/.codex-plugin/skills/test/SKILL.md +3 -3
  55. package/.codex-plugin/skills/ux-grill/SKILL.md +11 -4
  56. package/.cursor/commands/autopilot.md +4 -4
  57. package/.cursor/commands/bootstrap.md +5 -4
  58. package/.cursor/commands/brainstorm.md +5 -4
  59. package/.cursor/commands/close.md +4 -3
  60. package/.cursor/commands/convergence-monitoring.md +13 -0
  61. package/.cursor/commands/debug.md +4 -4
  62. package/.cursor/commands/discovery.md +4 -4
  63. package/.cursor/commands/dispatcher.md +4 -4
  64. package/.cursor/commands/eli5.md +4 -4
  65. package/.cursor/commands/eval.md +4 -4
  66. package/.cursor/commands/evolve.md +4 -4
  67. package/.cursor/commands/go.md +4 -3
  68. package/.cursor/commands/grill.md +4 -4
  69. package/.cursor/commands/harness-audit.md +3 -3
  70. package/.cursor/commands/memory-cleanup.md +4 -4
  71. package/.cursor/commands/npm-publish.md +13 -0
  72. package/.cursor/commands/persona-panel.md +4 -4
  73. package/.cursor/commands/plan.md +5 -4
  74. package/.cursor/commands/portfolio.md +3 -3
  75. package/.cursor/commands/reconcile.md +4 -4
  76. package/.cursor/commands/release.md +4 -3
  77. package/.cursor/commands/repo-audit.md +4 -4
  78. package/.cursor/commands/spinout.md +4 -4
  79. package/.cursor/commands/sunset-review.md +4 -4
  80. package/.cursor/commands/test.md +3 -3
  81. package/.cursor/commands/ux-grill.md +4 -4
  82. package/.cursor/rules/010-session-workflow.mdc +2 -2
  83. package/.cursor/skills/bootstrap/SKILL.md +1 -0
  84. package/.cursor/skills/close/SKILL.md +13 -0
  85. package/.cursor/skills/debug/SKILL.md +0 -1
  86. package/.cursor/skills/discovery/SKILL.md +0 -1
  87. package/.cursor/skills/dispatcher/SKILL.md +0 -1
  88. package/.cursor/skills/eli5/SKILL.md +0 -1
  89. package/.cursor/skills/evolve/SKILL.md +0 -1
  90. package/.cursor/skills/go/SKILL.md +13 -0
  91. package/.cursor/skills/grill/SKILL.md +0 -1
  92. package/.cursor/skills/harness-audit/SKILL.md +12 -0
  93. package/.cursor/skills/portfolio/SKILL.md +12 -0
  94. package/.cursor/skills/release/SKILL.md +13 -0
  95. package/.cursor/skills/repo-audit/SKILL.md +0 -1
  96. package/.cursor/skills/sunset-review/SKILL.md +0 -1
  97. package/.cursor/skills/test/SKILL.md +12 -0
  98. package/.cursor/skills/ux-grill/SKILL.md +0 -1
  99. package/.cursor-plugin/plugin.json +2 -2
  100. package/.orchestrator/policy/blocked-commands.json +10 -0
  101. package/AGENTS.md +1 -1
  102. package/CHANGELOG.md +80 -0
  103. package/README.md +74 -235
  104. package/commands/session.md +10 -0
  105. package/docs/USER-GUIDE.md +24 -0
  106. package/docs/ci-setup.md +53 -0
  107. package/docs/codex-setup.md +1 -1
  108. package/docs/components.md +12 -5
  109. package/docs/events-schema.md +5 -1
  110. package/docs/install.md +128 -0
  111. package/docs/persona-panel.md +1 -1
  112. package/docs/pi-setup.md +1 -1
  113. package/docs/rule-authoring.md +83 -14
  114. package/docs/scope-collision-guard.md +2 -0
  115. package/docs/session-config-reference.md +6 -4
  116. package/docs/session-config-template.md +38 -0
  117. package/docs/telemetry.md +15 -0
  118. package/hooks/_lib/hook-import-set.json +46 -6
  119. package/hooks/_lib/subagent-paths.mjs +15 -0
  120. package/hooks/_lib/vcs-create-matcher.mjs +217 -62
  121. package/hooks/enforce-scope.mjs +42 -1
  122. package/hooks/hooks-codex.json +1 -1
  123. package/hooks/hooks.json +1 -1
  124. package/hooks/on-session-end.mjs +14 -2
  125. package/hooks/on-stop.mjs +43 -1
  126. package/hooks/post-bash-write-verify.mjs +3 -0
  127. package/hooks/pre-auq-clarity.mjs +3 -0
  128. package/hooks/pre-bash-issue-budget.mjs +103 -17
  129. package/hooks/pre-task-scope-disjoint.mjs +152 -3
  130. package/hooks/skill-invocation-telemetry.mjs +2 -1
  131. package/package.json +3 -2
  132. package/pi/prompts/autopilot.md +3 -3
  133. package/pi/prompts/bootstrap.md +3 -3
  134. package/pi/prompts/brainstorm.md +3 -3
  135. package/pi/prompts/close.md +2 -2
  136. package/pi/prompts/convergence-monitoring.md +11 -0
  137. package/pi/prompts/debug.md +3 -3
  138. package/pi/prompts/discovery.md +3 -3
  139. package/pi/prompts/dispatcher.md +3 -3
  140. package/pi/prompts/eli5.md +3 -3
  141. package/pi/prompts/eval.md +3 -3
  142. package/pi/prompts/evolve.md +3 -3
  143. package/pi/prompts/go.md +2 -2
  144. package/pi/prompts/grill.md +3 -3
  145. package/pi/prompts/harness-audit.md +2 -3
  146. package/pi/prompts/memory-cleanup.md +3 -3
  147. package/pi/prompts/npm-publish.md +11 -0
  148. package/pi/prompts/persona-panel.md +3 -3
  149. package/pi/prompts/plan.md +3 -3
  150. package/pi/prompts/portfolio.md +2 -2
  151. package/pi/prompts/reconcile.md +3 -3
  152. package/pi/prompts/release.md +3 -3
  153. package/pi/prompts/repo-audit.md +3 -4
  154. package/pi/prompts/session.md +1 -1
  155. package/pi/prompts/spinout.md +3 -3
  156. package/pi/prompts/sunset-review.md +3 -3
  157. package/pi/prompts/templates-ack.md +1 -1
  158. package/pi/prompts/test.md +3 -3
  159. package/pi/prompts/ux-grill.md +3 -3
  160. package/scripts/archive-closed-prds.mjs +2 -2
  161. package/scripts/auq-audit.mjs +2 -3
  162. package/scripts/backfill-abandoned-sessions.mjs +57 -3
  163. package/scripts/backfill-evidence-digest.mjs +2 -1
  164. package/scripts/backfill-learnings-from-vault.mjs +2 -2
  165. package/scripts/check-package-manager.mjs +2 -2
  166. package/scripts/ci/assert-vitest-green.mjs +2 -1
  167. package/scripts/emit-session.mjs +2 -3
  168. package/scripts/export-hw-learnings.mjs +2 -1
  169. package/scripts/express-path.mjs +1 -1
  170. package/scripts/gc-stale-worktrees.mjs +2 -1
  171. package/scripts/generate-codex-skills.mjs +48 -4
  172. package/scripts/generate-cursor-adapter.mjs +173 -9
  173. package/scripts/generate-hook-import-set.mjs +12 -27
  174. package/scripts/generate-pi-prompts.mjs +183 -13
  175. package/scripts/github-protection-audit.mjs +2 -3
  176. package/scripts/lib/agent-frontmatter.mjs +23 -1
  177. package/scripts/lib/claude-md-budget-lint.mjs +2 -5
  178. package/scripts/lib/command-blocker.mjs +209 -9
  179. package/scripts/lib/config/drift-check.mjs +19 -0
  180. package/scripts/lib/convergence-monitor.mjs +2 -2
  181. package/scripts/lib/cursor-hook-bridge.mjs +2 -2
  182. package/scripts/lib/description-surface.mjs +2 -5
  183. package/scripts/lib/dispatcher/cli.mjs +2 -1
  184. package/scripts/lib/ecosystem-wizard.mjs +2 -1
  185. package/scripts/lib/fetch-baseline.mjs +3 -8
  186. package/scripts/lib/gitlab-ops/stale-mr-sweep.mjs +2 -1
  187. package/scripts/lib/gitlab-portfolio/cli.mjs +2 -1
  188. package/scripts/lib/instruction-budget-guard.mjs +186 -46
  189. package/scripts/lib/is-main-module.mjs +82 -0
  190. package/scripts/lib/locks/index.mjs +32 -25
  191. package/scripts/lib/maintenance-due-banner.mjs +69 -3
  192. package/scripts/lib/peer-discovery.mjs +2 -5
  193. package/scripts/lib/playwright-driver/runner.mjs +63 -2
  194. package/scripts/lib/reconcile/rule-expiry-sweep.mjs +642 -0
  195. package/scripts/lib/rules-sync.mjs +2 -5
  196. package/scripts/lib/scope-echo.mjs +392 -7
  197. package/scripts/lib/session-close-backfill.mjs +58 -6
  198. package/scripts/lib/state-md.mjs +84 -3
  199. package/scripts/lib/sunset/walker.mjs +31 -4
  200. package/scripts/lib/tests-src-ratio.mjs +2 -6
  201. package/scripts/lib/tmux-layout/telemetry-stats.mjs +2 -1
  202. package/scripts/lib/user-invocable-skills.mjs +185 -0
  203. package/scripts/lib/validate/check-banner-parity.mjs +2 -2
  204. package/scripts/lib/validate/check-cursor-adapter.mjs +2 -2
  205. package/scripts/lib/validate/check-dead-bridge.mjs +2 -2
  206. package/scripts/lib/validate/check-doc-cli-commands.mjs +2 -2
  207. package/scripts/lib/validate/check-entry-guard.mjs +366 -0
  208. package/scripts/lib/validate/check-guard-requires-parity.mjs +2 -2
  209. package/scripts/lib/validate/check-hooks-emit-event-guard.mjs +2 -2
  210. package/scripts/lib/validate/check-learning-provenance.mjs +2 -2
  211. package/scripts/lib/validate/check-skill-links.mjs +27 -6
  212. package/scripts/lib/validate/check-skill-script-paths.mjs +2 -2
  213. package/scripts/lib/validate/check-test-git-config-target.mjs +2 -2
  214. package/scripts/lib/validate/check-unicode-safety.mjs +2 -2
  215. package/scripts/lib/validate/check-untracked-test-deps.mjs +2 -2
  216. package/scripts/lib/validate/check-unwired-features.mjs +266 -11
  217. package/scripts/lib/validate/check-validator-registration.mjs +2 -2
  218. package/scripts/lib/validate/check-vcs-repo-flag.mjs +2 -2
  219. package/scripts/lib/validate-vendored-rules.mjs +35 -9
  220. package/scripts/lib/wave-transcript-tail.mjs +2 -2
  221. package/scripts/lock-reaper.mjs +2 -1
  222. package/scripts/materialize-wave-scope.mjs +87 -4
  223. package/scripts/migrate-sessions-jsonl.mjs +2 -1
  224. package/scripts/migrate-vault-paths.mjs +2 -3
  225. package/scripts/release.mjs +124 -35
  226. package/scripts/relocate-vault-corpus.mjs +2 -3
  227. package/scripts/repair-invalid-sessions.mjs +2 -2
  228. package/scripts/session-shape.mjs +2 -2
  229. package/scripts/site-numbers.mjs +35 -11
  230. package/scripts/sweep-expired-rules.mjs +216 -0
  231. package/scripts/validate-plugin.mjs +9 -0
  232. package/scripts/vault-consolidate.mjs +2 -2
  233. package/scripts/vault-mirror.mjs +2 -3
  234. package/scripts/wave-scope-binding.mjs +2 -3
  235. package/skills/_shared/bootstrap-gate.md +1 -1
  236. package/skills/_shared/monitor-patterns.md +1 -1
  237. package/skills/_shared/research-evidence.md +53 -0
  238. package/skills/_shared/state-ownership.md +3 -0
  239. package/skills/autopilot/SKILL.md +58 -4
  240. package/skills/bootstrap/SKILL.md +51 -1
  241. package/skills/brainstorm/SKILL.md +16 -0
  242. package/skills/claude-md-drift-check/checker.mjs +49 -11
  243. package/{commands/close.md → skills/close/SKILL.md} +9 -3
  244. package/skills/debug/SKILL.md +10 -0
  245. package/skills/discovery/SKILL.md +24 -1
  246. package/skills/discovery/probes-session.md +2 -2
  247. package/skills/dispatcher/SKILL.md +38 -7
  248. package/skills/eli5/SKILL.md +11 -0
  249. package/skills/eval/SKILL.md +14 -0
  250. package/skills/evolve/SKILL.md +8 -1
  251. package/skills/evolve/references/evolve-dialectic-mode.md +6 -2
  252. package/{commands/go.md → skills/go/SKILL.md} +9 -1
  253. package/skills/grill/SKILL.md +19 -0
  254. package/{commands/harness-audit.md → skills/harness-audit/SKILL.md} +7 -2
  255. package/skills/hook-development/SKILL.md +46 -41
  256. package/skills/memory-cleanup/SKILL.md +7 -0
  257. package/skills/npm-publish/SKILL.md +1 -1
  258. package/skills/persona-panel/SKILL.md +56 -1
  259. package/skills/persona-panel/persona-format.md +1 -1
  260. package/skills/plan/SKILL.md +28 -1
  261. package/skills/playwright-driver/SKILL.md +7 -10
  262. package/{commands/portfolio.md → skills/portfolio/SKILL.md} +8 -2
  263. package/skills/reconcile/SKILL.md +10 -0
  264. package/{commands/release.md → skills/release/SKILL.md} +16 -2
  265. package/skills/repo-audit/SKILL.md +7 -0
  266. package/skills/session-end/plan-verification.md +2 -2
  267. package/skills/session-plan/SKILL.md +1 -1
  268. package/skills/session-start/SKILL.md +5 -4
  269. package/skills/session-start/phase-8-5-express-path.md +6 -6
  270. package/skills/session-start/references/phase-1-5-session-continuity.md +1 -1
  271. package/skills/session-start/references/phase-2-7-portfolio-snapshot.md +1 -1
  272. package/skills/session-start/references/phase-4-ssot-environment-check.md +4 -3
  273. package/skills/spinout/SKILL.md +12 -1
  274. package/skills/sunset-review/SKILL.md +13 -0
  275. package/{commands/test.md → skills/test/SKILL.md} +10 -4
  276. package/skills/ux-grill/SKILL.md +19 -1
  277. package/skills/wave-executor/SKILL.md +7 -4
  278. package/skills/wave-executor/references/wave-executor-state-init.md +13 -1
  279. package/skills/wave-executor/references/wave-loop-dispatch.md +3 -1
  280. package/skills/wave-executor/references/wave-loop-review.md +17 -1
  281. package/commands/autopilot.md +0 -80
  282. package/commands/bootstrap.md +0 -56
  283. package/commands/brainstorm.md +0 -48
  284. package/commands/debug.md +0 -36
  285. package/commands/discovery.md +0 -32
  286. package/commands/dispatcher.md +0 -59
  287. package/commands/eli5.md +0 -33
  288. package/commands/eval.md +0 -28
  289. package/commands/evolve.md +0 -10
  290. package/commands/grill.md +0 -45
  291. package/commands/memory-cleanup.md +0 -26
  292. package/commands/persona-panel.md +0 -121
  293. package/commands/plan.md +0 -15
  294. package/commands/reconcile.md +0 -23
  295. package/commands/repo-audit.md +0 -24
  296. package/commands/spinout.md +0 -15
  297. package/commands/sunset-review.md +0 -27
  298. package/commands/ux-grill.md +0 -51
@@ -23,10 +23,11 @@
23
23
  */
24
24
 
25
25
  import { existsSync, readdirSync, readFileSync, unlinkSync } from 'node:fs';
26
- import { resolve } from 'node:path';
27
- import { fileURLToPath } from 'node:url';
26
+ import { dirname, resolve } from 'node:path';
28
27
  import { writeJsonAtomicSync } from './lib/io.mjs';
28
+ import { isMainModule } from './lib/is-main-module.mjs';
29
29
  import { isPeerRecordId } from './lib/scope-gate.mjs';
30
+ import { SCOPE_MATERIALIZED_EVENT, scopeDigest } from './lib/scope-echo.mjs';
30
31
  import {
31
32
  MANIFEST_SESSION_KEYS,
32
33
  manifestSessionBinding,
@@ -479,6 +480,83 @@ export function materializeWaveScope(records, {
479
480
  return { aggregatePath, perAgentPaths, removedOrphans: removed, retainedOrphans: retained };
480
481
  }
481
482
 
483
+ /**
484
+ * Is the SEND-side half of the #1092 chain installed on this platform at all?
485
+ *
486
+ * The send-side record only exists because a `PreToolUse` hook with matcher
487
+ * `Agent` observes the dispatch. `hooks-codex.json` / `hooks-cursor.json` /
488
+ * `hooks-pi.json` deliberately carry no such entry (those platforms have no
489
+ * `Agent` dispatch tool — `DOCUMENTED_ASYMMETRIES`), so on them a MISSING
490
+ * dispatch record is not evidence of a missing injection. Recording the answer
491
+ * beside the wave is what lets `--verify` tell those two apart later instead of
492
+ * re-deriving a platform fact from an absence.
493
+ *
494
+ * Fail-CLOSED on any read error: an unreadable hooks file means the guard cannot
495
+ * be shown to be installed, and claiming observability we cannot prove is the
496
+ * direction that produces false `injection-missing` accusations.
497
+ *
498
+ * @param {string} [pluginRoot]
499
+ * @param {typeof readFileSync} [readFile]
500
+ * @returns {boolean}
501
+ */
502
+ export function transportObservable(pluginRoot = resolve(import.meta.dirname, '..'), readFile = readFileSync) {
503
+ try {
504
+ const parsed = JSON.parse(readFile(resolve(pluginRoot, 'hooks', 'hooks.json'), 'utf8'));
505
+ const entries = parsed?.hooks?.PreToolUse;
506
+ if (!Array.isArray(entries)) return false;
507
+ return entries.some((entry) => typeof entry?.matcher === 'string'
508
+ && entry.matcher.split('|').map((m) => m.trim()).includes('Agent'));
509
+ } catch {
510
+ return false;
511
+ }
512
+ }
513
+
514
+ /**
515
+ * One `orchestrator.wave_dispatch.scope_materialized` record per wave (#1092) —
516
+ * the DEGRADATION half of the chain.
517
+ *
518
+ * It runs here, and not in the hook, precisely because this command runs on
519
+ * EVERY platform while the hook runs on one. Without it a wave with zero
520
+ * `scope_checked` records is ambiguous between "the coordinator skipped the
521
+ * manifest step" (the #1020 failure this whole chain exists for) and "this
522
+ * platform has no dispatch hook".
523
+ *
524
+ * `repoRoot` is the state directory's PARENT, never `process.cwd()`: the state
525
+ * dir is the artefact this command owns, and a caller invoking it from a
526
+ * subdirectory (or a test from the repo root against a `$TMPDIR` state dir)
527
+ * must not write its record into a different repo's ledger.
528
+ *
529
+ * Best-effort in both directions and SILENT on failure: the corpus pins
530
+ * byte-empty stderr on this command's success path
531
+ * (`tests/integration/wave-scope-producer.test.mjs`), and a telemetry write must
532
+ * never be the thing that fails a materialization.
533
+ *
534
+ * @param {{stateDir: string, wave: number, records: Array<{id: string, files: string[]}>}} params
535
+ * @returns {Promise<void>}
536
+ */
537
+ async function emitScopeMaterialized({ stateDir, wave, records }) {
538
+ try {
539
+ const repoRoot = dirname(resolve(stateDir));
540
+ const agentRecords = records.filter((r) => !isPeerRecordId(r.id));
541
+ const digests = new Set();
542
+ for (const record of agentRecords) {
543
+ if (Array.isArray(record.files) && record.files.length > 0) digests.add(scopeDigest(record.files));
544
+ }
545
+ const { emitEvent, sessionAttribution } = await import('./lib/events.mjs');
546
+ await emitEvent(
547
+ SCOPE_MATERIALIZED_EVENT,
548
+ {
549
+ wave,
550
+ agent_count: agentRecords.length,
551
+ digest_count: digests.size,
552
+ transport_observable: transportObservable(),
553
+ ...sessionAttribution(repoRoot),
554
+ },
555
+ { repoRoot },
556
+ );
557
+ } catch { /* observability is best-effort — it never fails a materialization */ }
558
+ }
559
+
482
560
  /**
483
561
  * @param {string} message
484
562
  * @param {number} code
@@ -536,6 +614,12 @@ export function main() {
536
614
  ? `${JSON.stringify({ ok: true, aggregatePath, perAgentPaths, removedOrphans, retainedOrphans })}\n`
537
615
  : `${aggregatePath}\n`,
538
616
  );
617
+
618
+ // Fire-and-forget AFTER stdout, so the telemetry append can never reorder or
619
+ // delay this command's own contract. Node drains the pending append before
620
+ // exiting because nothing here calls `process.exit()`; the `.catch` keeps an
621
+ // unhandled rejection from turning observability into an exit code.
622
+ emitScopeMaterialized({ stateDir: args.stateDir, wave: args.wave, records }).catch(() => {});
539
623
  } catch (error) {
540
624
  if (error instanceof InputError) {
541
625
  fail(error.message, 1);
@@ -549,7 +633,6 @@ export function main() {
549
633
  }
550
634
  }
551
635
 
552
- const invokedPath = process.argv[1] ? resolve(process.argv[1]) : null;
553
- if (invokedPath === fileURLToPath(import.meta.url)) {
636
+ if (isMainModule(import.meta.url)) {
554
637
  main();
555
638
  }
@@ -41,6 +41,7 @@
41
41
  import { readFileSync, writeFileSync, renameSync, copyFileSync, existsSync } from 'node:fs';
42
42
  import { join, dirname, basename } from 'node:path';
43
43
  import { validateSession, normalizeSession, ValidationError } from './lib/session-schema.mjs';
44
+ import { isMainModule } from './lib/is-main-module.mjs';
44
45
 
45
46
  const DEFAULT_FILE = '.orchestrator/metrics/sessions.jsonl';
46
47
 
@@ -440,7 +441,7 @@ async function main() {
440
441
 
441
442
  // Run main() only when this file is invoked directly as a CLI, not when imported.
442
443
  // Prevents process.exit during test-time imports (#368).
443
- if (import.meta.url === `file://${process.argv[1]}`) {
444
+ if (isMainModule(import.meta.url)) {
444
445
  main().catch((err) => {
445
446
  process.stderr.write(`migrate-sessions-jsonl: unexpected error: ${err?.stack ?? err}\n`);
446
447
  process.exit(2);
@@ -50,7 +50,6 @@
50
50
 
51
51
  import { promises as fs, existsSync } from 'node:fs';
52
52
  import { spawnSync } from 'node:child_process';
53
- import { pathToFileURL } from 'node:url';
54
53
  import path from 'node:path';
55
54
  import os from 'node:os';
56
55
  import {
@@ -58,6 +57,7 @@ import {
58
57
  VAULT_MIGRATION_RULES_PATH,
59
58
  } from './lib/vault-migration-rules.mjs';
60
59
  import { parseColumnFlags, CliFlagError } from './lib/cli-flags.mjs';
60
+ import { isMainModule } from './lib/is-main-module.mjs';
61
61
 
62
62
  // ---------------------------------------------------------------------------
63
63
  // Constants
@@ -773,8 +773,7 @@ function emit(opts, rec) {
773
773
  // importing the module is never a hard error regardless of how the importer runs.
774
774
  // ---------------------------------------------------------------------------
775
775
 
776
- const invokedDirectly =
777
- process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href;
776
+ const invokedDirectly =isMainModule(import.meta.url);
778
777
 
779
778
  if (invokedDirectly) {
780
779
  main().catch((err) => {
@@ -356,7 +356,7 @@ const INTENTIONAL_TEST_ASSET_PATHS = new Set([
356
356
  'templates/python-uv/tests/test_sanity.py',
357
357
  ]);
358
358
 
359
- // `commands/release.md` quotes the `npm view` OUTPUT that proves the 3.18.0 gap,
359
+ // `skills/release/SKILL.md` quotes the `npm view` OUTPUT that proves the 3.18.0 gap,
360
360
  // dated at the line. Bumping it would destroy the evidence it exists to carry —
361
361
  // the registry state on that date is the whole point of the paragraph.
362
362
  //
@@ -384,7 +384,7 @@ const INTENTIONAL_TEST_ASSET_PATHS = new Set([
384
384
  // September 10 campaign snapshot: inputs and receipt reproduce the dated 4.3-planned
385
385
  // render made while 4.2.0 was current. Exact files only; neighboring marketing
386
386
  // remains checked. Do not bump receipt props without a newly reviewed render.
387
- export const HISTORY_ALLOWLIST = /^(CHANGELOG\.md|README\.md|CLAUDE\.md|AGENTS\.md|CONTRIBUTING\.md|NOTICE|\.husky\/pre-push|docs\/|tests\/|skills\/npm-publish\/|skills\/architecture\/references\/domain-model\.md|skills\/autopilot\/SKILL\.md|scripts\/release\.mjs|\.orchestrator\/|site\/leaderboard\.json|site\/guide\/index\.html|site\/llms-full\.txt|commands\/release\.md|marketing\/remotion\/(?:README\.md|campaign\.json|render-receipt\.json|src\/ReleaseFilm\.tsx)$)/;
387
+ export const HISTORY_ALLOWLIST = /^(CHANGELOG\.md|README\.md|CLAUDE\.md|AGENTS\.md|CONTRIBUTING\.md|NOTICE|\.husky\/pre-push|docs\/|tests\/|skills\/npm-publish\/|skills\/architecture\/references\/domain-model\.md|skills\/autopilot\/SKILL\.md|scripts\/release\.mjs|\.orchestrator\/|site\/leaderboard\.json|site\/guide\/index\.html|site\/llms-full\.txt|skills\/release\/SKILL\.md|marketing\/remotion\/(?:README\.md|campaign\.json|render-receipt\.json|src\/ReleaseFilm\.tsx)$)/;
388
388
 
389
389
  /** Pure check over packed-entry lines. Returns violations: {name, line}[]. */
390
390
  export function checkLeakage(lines) {
@@ -511,6 +511,49 @@ const LOCKFILE_BASENAMES = new Set(['package-lock.json', 'npm-shrinkwrap.json'])
511
511
  /** Code files in which a `//`, `*`, `/*` or `#` line is comment prose, never a version surface. */
512
512
  const CODE_COMMENT_EXTENSIONS = new Set(['.mjs', '.js', '.cjs', '.ts', '.sh']);
513
513
 
514
+ /** Workflow/CI files in which a SHA-pinned `uses:` carries a third-party version as a trailing comment. */
515
+ const YAML_EXTENSIONS = new Set(['.yml', '.yaml']);
516
+
517
+ /**
518
+ * Is this YAML row a SHA-pinned THIRD-PARTY action whose version lives only in the
519
+ * trailing comment?
520
+ *
521
+ * Measured on the 5.1.0 cut (2026-09-13): `.github/workflows/test.yml` carries
522
+ * `uses: actions/setup-node@a0853c2… # v5.0.0` twice. That `5.0.0` is setup-node's
523
+ * version, not ours — the sweep matched it only because our previous release happened
524
+ * to land on the same number, so the false positive is triggered by COINCIDENCE and
525
+ * would reappear for any action whose pin equals our next version.
526
+ *
527
+ * Expressed as a PREDICATE, like the three classes above, for the reason
528
+ * `.claude/rules/measurement-discipline.md` records: a per-path allowlist row fixes
529
+ * this file and leaves the class open for the next workflow.
530
+ *
531
+ * Two conditions, both required, so the predicate cannot mask a stale surface of ours:
532
+ * 1. the line pins a reference with `uses:` BEFORE the comment marker, and
533
+ * 2. EVERY occurrence of the literal sits AFTER that marker.
534
+ * A version in the pin itself (`uses: foo@v5.0.0`) fails condition 2 and is still swept.
535
+ *
536
+ * NAMED CEILING (BV-004): no SURFACES pattern targets a `.yml`/`.yaml` file today, so a
537
+ * YAML hit is never our own surface anyway. Revisit trigger: the first version surface
538
+ * added to a YAML file — then this predicate must also exclude that file's pattern.
539
+ *
540
+ * @param {string} content — the matching line's text
541
+ * @param {string} literal — the previous release literal being swept for
542
+ * @returns {boolean} true = third-party pin comment, skip the row
543
+ */
544
+ export function isPinnedActionComment(content, literal) {
545
+ const hash = content.indexOf('#');
546
+ if (hash === -1) return false;
547
+ if (!/\buses:\s*\S/.test(content.slice(0, hash))) return false;
548
+ const re = versionTokenRegex(literal);
549
+ let seen = 0;
550
+ for (let m = re.exec(content); m; m = re.exec(content)) {
551
+ seen += 1;
552
+ if (m.index < hash) return false;
553
+ }
554
+ return seen > 0;
555
+ }
556
+
514
557
  /**
515
558
  * Is this `path:line:content` row version HISTORY rather than a stale surface?
516
559
  *
@@ -546,6 +589,7 @@ function isHistoryRow(file, line, content, prevTag) {
546
589
  }
547
590
  const dot = base.lastIndexOf('.');
548
591
  const ext = dot === -1 ? '' : base.slice(dot);
592
+ if (YAML_EXTENSIONS.has(ext) && isPinnedActionComment(content, prevTag)) return true;
549
593
  if (CODE_COMMENT_EXTENSIONS.has(ext)) {
550
594
  const trimmed = content.trim();
551
595
  if (trimmed.startsWith('//') || trimmed.startsWith('/*') || trimmed.startsWith('*')) return true;
@@ -864,6 +908,11 @@ export function evaluateCiRow(ci) {
864
908
  * "we could not read the mirror" is not "the mirror is green" (the fail-closed
865
909
  * house rule at the top of this file).
866
910
  *
911
+ * This function only judges the `ci` reading it is handed — it is
912
+ * `evaluateCiPreflightRows` (the caller, see its own docblock) that fetches
913
+ * `ci` for the commit actually being released, rather than whichever HEAD the
914
+ * GitHub mirror itself reports.
915
+ *
867
916
  * @param {string|undefined} repoSpec — `resolveRepoSpec({vcs:'github'})`, undefined when no github remote resolves
868
917
  * @param {null | {status?: string, failingJobName?: string, degraded?: string}} ci
869
918
  * @returns {{ok: boolean, detail: string}}
@@ -925,6 +974,62 @@ function readPackageVersion(repoRoot) {
925
974
  return JSON.parse(readFileSync(join(repoRoot, 'package.json'), 'utf8')).version;
926
975
  }
927
976
 
977
+ /**
978
+ * Both CI preflight rows (`ci-green-on-head` + `ci-green-on-head-github`) for
979
+ * ONE commit — the commit actually about to be released — never whichever
980
+ * HEAD the queried platform happens to report.
981
+ *
982
+ * Before this function existed, neither `checkCiStatus` call below passed
983
+ * `sha`, so the GitHub branch fell back to `commits/HEAD` — the MIRROR's own
984
+ * default-branch head, which is the release commit only once `head-pushed-github`
985
+ * has ALREADY proven `github/main == local HEAD`. A release cut before that
986
+ * push landed asked GitHub about a commit that was never pushed and could read
987
+ * green for work GitHub has not seen at all (measured 2026-09-16 for
988
+ * `3ebf0e9d`). The GitLab branch was already correct by default (`deps.sha ??
989
+ * getHeadSha(repoRoot)`), so only the two call sites below needed the fix —
990
+ * see `checkCiStatus`'s own docblock in `ci-status-banner.mjs` for the `sha`
991
+ * contract (#1332).
992
+ *
993
+ * Factored out of `preflight()` as its own async, DI-testable unit:
994
+ * `checkCiStatus` reaches the network by default, and `preflight()`'s
995
+ * surrounding checks (git, npm) are not test-doubled, so this is the seam
996
+ * through which the `sha` wiring can be unit-tested without spinning up a
997
+ * full fixture release.
998
+ *
999
+ * @param {string} repoRoot
1000
+ * @param {string} head - full hex commit id of the commit being released (the
1001
+ * local HEAD `preflight()` already computed for the remote-parity rows).
1002
+ * @param {{
1003
+ * skipCi?: boolean,
1004
+ * checkCiStatus?: Function,
1005
+ * resolveRepoSpec?: (opts: { repoRoot: string, vcs: 'github' }) => string|undefined,
1006
+ * }} [deps]
1007
+ * @returns {Promise<{ gitlab: {ok: boolean, detail: string}, github: {ok: boolean, detail: string} }>}
1008
+ */
1009
+ export async function evaluateCiPreflightRows(repoRoot, head, {
1010
+ skipCi = false,
1011
+ checkCiStatus: checkCiStatusDep,
1012
+ resolveRepoSpec: resolveRepoSpecDep = resolveRepoSpec,
1013
+ } = {}) {
1014
+ if (skipCi) {
1015
+ const skipped = { ok: true, detail: 'SKIPPED via --skip-ci' };
1016
+ return { gitlab: skipped, github: skipped };
1017
+ }
1018
+ const checkCiStatusImpl = checkCiStatusDep ?? (await import('./lib/ci-status-banner.mjs')).checkCiStatus;
1019
+
1020
+ const ci = await checkCiStatusImpl({ repoRoot, timeoutMs: 15000, sha: head });
1021
+ const gitlab = evaluateCiRow(ci);
1022
+
1023
+ const githubSpec = resolveRepoSpecDep({ repoRoot, vcs: 'github' });
1024
+ let githubCi = null;
1025
+ if (githubSpec) {
1026
+ githubCi = await checkCiStatusImpl({ repoRoot, vcs: 'github', timeoutMs: 15000, sha: head });
1027
+ }
1028
+ const github = evaluateGithubCiRow(githubSpec, githubCi);
1029
+
1030
+ return { gitlab, github };
1031
+ }
1032
+
928
1033
  async function preflight(repoRoot, target, { skipCi = false } = {}) {
929
1034
  const checks = [];
930
1035
  const add = (name, ok, detail = '') => checks.push({ name, ok, detail });
@@ -1054,39 +1159,23 @@ async function preflight(repoRoot, target, { skipCi = false } = {}) {
1054
1159
  add('npm-token-live', false, err.message);
1055
1160
  }
1056
1161
 
1057
- // 6. CI green on HEAD (the repo's iron session-start rule applies to
1058
- // releases doubly: local green is not evidence — see .claude/rules).
1059
- // --skip-ci is refused under --publish upstream in validateFlags(); it can
1060
- // only reach this branch from --check.
1061
- if (skipCi) {
1062
- add('ci-green-on-head', true, 'SKIPPED via --skip-ci');
1063
- } else {
1064
- const { checkCiStatus } = await import('./lib/ci-status-banner.mjs');
1065
- const ci = await checkCiStatus({ repoRoot, timeoutMs: 15000 });
1066
- const row = evaluateCiRow(ci);
1067
- add('ci-green-on-head', row.ok, row.detail);
1068
- }
1069
-
1070
- // 6b. CI green on the GitHub mirror too. The row above asks the platform
1071
- // `detectVcsFamily` picks for `origin` (GitLab), whose pipeline is Linux-only;
1072
- // the macOS matrix leg lives exclusively in `.github/workflows/test.yml`.
1073
- // `vcs: 'github'` forces the probe onto the mirror without touching the
1074
- // detection order. The GitHub check-runs path reads `commits/HEAD`, i.e. the
1075
- // mirror's default-branch head — which is HEAD only because `head-pushed-github`
1076
- // above proves github/main == local HEAD; that row is this one's precondition,
1077
- // not a duplicate of it.
1078
- if (skipCi) {
1079
- add('ci-green-on-head-github', true, 'SKIPPED via --skip-ci');
1080
- } else {
1081
- const githubSpec = resolveRepoSpec({ repoRoot, vcs: 'github' });
1082
- let githubCi = null;
1083
- if (githubSpec) {
1084
- const { checkCiStatus } = await import('./lib/ci-status-banner.mjs');
1085
- githubCi = await checkCiStatus({ repoRoot, vcs: 'github', timeoutMs: 15000 });
1086
- }
1087
- const row = evaluateGithubCiRow(githubSpec, githubCi);
1088
- add('ci-green-on-head-github', row.ok, row.detail);
1089
- }
1162
+ // 6/6b. CI green on HEAD, both platforms — judged for the commit actually
1163
+ // being released, never whichever HEAD the queried platform reports (see
1164
+ // evaluateCiPreflightRows's docblock for the `sha`-wiring history, #1332).
1165
+ //
1166
+ // GitLab: the repo's iron session-start rule applies to releases doubly —
1167
+ // local green is not evidence — see .claude/rules. GitHub: this second row
1168
+ // exists because `detectVcsFamily` picks `origin` (GitLab) for the row
1169
+ // above, whose pipeline is Linux-only; the macOS matrix leg lives
1170
+ // exclusively in `.github/workflows/test.yml`. `vcs: 'github'` forces the
1171
+ // probe onto the mirror without touching the detection order.
1172
+ // SELF-DISABLING: a checkout with no `github` remote has no mirror to be
1173
+ // red, so `evaluateGithubCiRow` reports `skipped` rather than red — see its
1174
+ // own docblock. --skip-ci is refused under --publish upstream in
1175
+ // validateFlags(); it can only reach this branch from --check.
1176
+ const ciRows = await evaluateCiPreflightRows(repoRoot, head, { skipCi });
1177
+ add('ci-green-on-head', ciRows.gitlab.ok, ciRows.gitlab.detail);
1178
+ add('ci-green-on-head-github', ciRows.github.ok, ciRows.github.detail);
1090
1179
 
1091
1180
  // 7. Leakage gate over the actual pack file list.
1092
1181
  // `npm_config_loglevel` is INHERITED, and `npm pack --dry-run` writes its whole
@@ -40,7 +40,6 @@
40
40
 
41
41
  import { promises as fs, existsSync } from 'node:fs';
42
42
  import { spawnSync } from 'node:child_process';
43
- import { pathToFileURL } from 'node:url';
44
43
  import path from 'node:path';
45
44
  import {
46
45
  loadVaultRelocationRules,
@@ -55,6 +54,7 @@ import {
55
54
  } from './lib/vault-relocation-rules.mjs';
56
55
  import { buildBackfillIndex, parseSessionId } from './lib/vault-repo-backfill.mjs';
57
56
  import { parseColumnFlags, CliFlagError } from './lib/cli-flags.mjs';
57
+ import { isMainModule } from './lib/is-main-module.mjs';
58
58
 
59
59
  // ---------------------------------------------------------------------------
60
60
  // Constants
@@ -1158,8 +1158,7 @@ async function main() {
1158
1158
  // Entry guard (run only when invoked directly)
1159
1159
  // ---------------------------------------------------------------------------
1160
1160
 
1161
- const invokedDirectly =
1162
- process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href;
1161
+ const invokedDirectly =isMainModule(import.meta.url);
1163
1162
 
1164
1163
  if (invokedDirectly) {
1165
1164
  main().catch((err) => {
@@ -37,10 +37,10 @@
37
37
  import fs from 'node:fs';
38
38
  import { parseArgs } from 'node:util';
39
39
  import path from 'node:path';
40
- import { fileURLToPath } from 'node:url';
41
40
 
42
41
  import { repairLedger, CANONICAL_LEDGER_REL } from './lib/session-record-repair.mjs';
43
42
  import { getProjectDir } from './lib/platform.mjs';
43
+ import { isMainModule } from './lib/is-main-module.mjs';
44
44
 
45
45
  const USAGE =
46
46
  'Usage: node scripts/repair-invalid-sessions.mjs [--dry-run|--apply] [--json]\n' +
@@ -200,7 +200,7 @@ async function main() {
200
200
  process.exit(summary.ok === false ? 3 : 0);
201
201
  }
202
202
 
203
- const isDirectRun = process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1];
203
+ const isDirectRun =isMainModule(import.meta.url);
204
204
  if (isDirectRun) {
205
205
  main().catch((err) => {
206
206
  process.stderr.write(`repair-invalid-sessions: unexpected error: ${err?.stack ?? err}\n`);
@@ -42,10 +42,10 @@
42
42
  import { existsSync, readFileSync, statSync } from 'node:fs';
43
43
  import { join, resolve } from 'node:path';
44
44
  import { parseArgs } from 'node:util';
45
- import { fileURLToPath } from 'node:url';
46
45
  import { resolveInstructionFile } from './lib/common.mjs';
47
46
  import { parseSessionConfig } from './lib/config.mjs';
48
47
  import { resolveAndRecordSessionShape } from './lib/session-shape.mjs';
48
+ import { isMainModule } from './lib/is-main-module.mjs';
49
49
 
50
50
  const USAGE = [
51
51
  'Usage: node scripts/session-shape.mjs --repo-root <path> --session-type <housekeeping|feature|deep>',
@@ -258,7 +258,7 @@ async function main() {
258
258
  }
259
259
 
260
260
  // Entrypoint guard — importing this file must not run it (check-unwired-features S3).
261
- if (process.argv[1] && resolve(process.argv[1]) === resolve(fileURLToPath(import.meta.url))) {
261
+ if (isMainModule(import.meta.url)) {
262
262
  main().catch((err) => {
263
263
  process.stderr.write(`session-shape: unexpected error: ${err?.stack ?? err}\n`);
264
264
  process.exit(EXIT_CONFIG_IO);
@@ -101,10 +101,11 @@
101
101
  import { readFileSync, writeFileSync, readdirSync, existsSync, statSync } from 'node:fs';
102
102
  import { join, resolve, relative } from 'node:path';
103
103
  import { execFileSync } from 'node:child_process';
104
- import { fileURLToPath } from 'node:url';
105
104
 
106
105
  import { writeStdoutLineSync, writeJsonAtomicSync } from './lib/io.mjs';
107
106
  import { readCanonicalSessions } from './lib/sessions-canonical.mjs';
107
+ import { isMainModule } from './lib/is-main-module.mjs';
108
+ import { slashCommandNames } from './lib/user-invocable-skills.mjs';
108
109
 
109
110
  /** Machine-readable schema tag for the --json envelope. */
110
111
  export const SCHEMA = 'site-numbers/1';
@@ -169,11 +170,37 @@ export function countSkills(root) {
169
170
  return n;
170
171
  }
171
172
 
172
- /** `ls commands/*.md | wc -l` — same basis as checker.mjs `command-count`. */
173
+ /**
174
+ * The product's "slash commands" number: `commands/*.md` ∪ skills carrying an
175
+ * EXPLICIT `user-invocable: true`, deduplicated by name.
176
+ *
177
+ * NOT `ls commands/*.md | wc -l` any more, and the change is not cosmetic. After
178
+ * the 2026-09-16 fold (#1370) `commands/` holds two files — `session` and
179
+ * `templates-ack`, the only two names a skill cannot carry — while 26 operator-
180
+ * facing slash commands exist. A command file only ever resolved because a
181
+ * same-named skill existed, and the twin additionally listed the entry twice in
182
+ * the `/` picker; the skill frontmatter flag is what makes a slash command a
183
+ * slash command now. Counting the directory would have reported 2.
184
+ *
185
+ * Dedup by name is load-bearing: a name present on BOTH sides is the picker-
186
+ * duplicate defect (`tests/commands/headless-bare-command-availability.test.mjs`
187
+ * fails on it), so it must count once here rather than inflate the tile.
188
+ *
189
+ * null (→ the tile renders as unmeasured) only when NEITHER `commands/` nor
190
+ * `skills/` exists — i.e. this is not a plugin tree at all. A tree with one of
191
+ * the two counts what it has; a missing `commands/` is now the NORMAL state, not
192
+ * a missing measurement.
193
+ *
194
+ * NO divergence from `skills/claude-md-drift-check/checker.mjs` `command-count`:
195
+ * that checker unions the same user-invocable skills (`checker.mjs:435-446`,
196
+ * measured 2026-09-17), and both routes reach the same verdict through the same
197
+ * SSOT — `isUserInvocableValue` in `scripts/lib/user-invocable-skills.mjs`. The
198
+ * checker stays GENERIC over consumer repos only in the sense that a repo
199
+ * without `skills/` sees the union collapse to the directory count on its own.
200
+ */
173
201
  export function countCommands(root) {
174
- const dir = join(root, 'commands');
175
- if (!isDir(dir)) return null;
176
- return readdirSync(dir).filter((f) => f.endsWith('.md') && !f.startsWith('.')).length;
202
+ if (!isDir(join(root, 'commands')) && !isDir(join(root, 'skills'))) return null;
203
+ return slashCommandNames(root).length;
177
204
  }
178
205
 
179
206
  /**
@@ -400,7 +427,8 @@ export const METRIC_DEFS = Object.freeze([
400
427
  {
401
428
  id: 'commands',
402
429
  provenance: false,
403
- source: 'ls commands/*.md | wc -l',
430
+ source:
431
+ 'ls commands/*.md | wc -l + grep -l "^user-invocable: true" skills/*/SKILL.md | wc -l (union by name — see countCommands)',
404
432
  compute: (root) => fmtCount(countCommands(root)),
405
433
  },
406
434
  {
@@ -1398,11 +1426,7 @@ export function main(argv = process.argv.slice(2), env = {}) {
1398
1426
  return exitCode;
1399
1427
  }
1400
1428
 
1401
- const isMain =
1402
- typeof process !== 'undefined' &&
1403
- process.argv[1] !== null &&
1404
- process.argv[1] !== undefined &&
1405
- resolve(process.argv[1]) === resolve(fileURLToPath(import.meta.url));
1429
+ const isMain =isMainModule(import.meta.url);
1406
1430
 
1407
1431
  if (isMain) {
1408
1432
  const argv = process.argv.slice(2);