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
@@ -34,8 +34,8 @@
34
34
 
35
35
  import { execFile as _execFile } from 'node:child_process';
36
36
  import { promisify } from 'node:util';
37
- import { pathToFileURL } from 'node:url';
38
37
  import { resolveRepoSpec as _resolveRepoSpec, resolveRepoHost as _resolveRepoHost } from './lib/vcs-repo-spec.mjs';
38
+ import { isMainModule } from './lib/is-main-module.mjs';
39
39
 
40
40
  const execFileAsync = promisify(_execFile);
41
41
 
@@ -347,8 +347,7 @@ export async function auditGithubBranchProtection(opts = {}, deps = {}) {
347
347
  }
348
348
 
349
349
  // CLI entry — only when run directly, not when imported (e.g. by tests).
350
- const isMain =
351
- process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href;
350
+ const isMain =isMainModule(import.meta.url);
352
351
 
353
352
  if (isMain) {
354
353
  auditGithubBranchProtection({ repoRoot: process.cwd() }).then((result) => {
@@ -58,7 +58,12 @@ export function parseAgentFrontmatter(contents) {
58
58
  };
59
59
  }
60
60
 
61
- const match = FRONTMATTER_RE.exec(contents);
61
+ // A UTF-8 BOM before `---` makes FRONTMATTER_RE miss the block entirely, so a
62
+ // BOM-prefixed SKILL.md would parse as "no frontmatter" and every flag in it
63
+ // (`user-invocable` included) silently disappear.
64
+ const text = contents.charCodeAt(0) === 0xfeff ? contents.slice(1) : contents;
65
+
66
+ const match = FRONTMATTER_RE.exec(text);
62
67
  if (!match) {
63
68
  return {
64
69
  ok: false,
@@ -76,19 +81,36 @@ export function parseAgentFrontmatter(contents) {
76
81
  const frontmatter = /** @type {Record<string,string>} */ ({});
77
82
  const lines = fmText.split(/\r?\n/);
78
83
 
84
+ // Indented lines belong to whatever the preceding top-level key opened (a
85
+ // nested mapping, or a block scalar) — they are NOT top-level keys. Without
86
+ // this, `metadata:\n user-invocable: true` was HOISTED to top level and read
87
+ // as the slash-command marker, so the counters saw a skill no generator did.
88
+ let inNestedBlock = false;
89
+
79
90
  for (const rawLine of lines) {
80
91
  const line = rawLine.replace(/\s+$/, '');
81
92
  if (line === '' || /^\s*#/.test(line)) continue;
82
93
 
94
+ if (/^\s/.test(rawLine)) {
95
+ if (inNestedBlock) continue;
96
+ } else {
97
+ inNestedBlock = false;
98
+ }
99
+
83
100
  // Detect block-scalar indicators on a key line, e.g. `description: >` or `description: |`
84
101
  // These are valid YAML but break Claude Code's parser.
85
102
  const blockScalarMatch = /^(\w[\w-]*):\s*[>|]\s*$/.exec(line);
86
103
  if (blockScalarMatch) {
87
104
  // Store a sentinel so the validator can emit the correct rule.
88
105
  frontmatter[blockScalarMatch[1]] = '__BLOCK_SCALAR__';
106
+ inNestedBlock = true;
89
107
  continue;
90
108
  }
91
109
 
110
+ // A bare `key:` at top level opens a nested mapping (or an empty value);
111
+ // everything indented under it is that mapping's business, not ours.
112
+ if (/^\w[\w-]*:$/.test(line)) inNestedBlock = true;
113
+
92
114
  const idx = line.indexOf(':');
93
115
  if (idx === -1) continue;
94
116
  const key = line.slice(0, idx).trim();
@@ -67,6 +67,7 @@ import { basename, resolve, sep } from 'node:path';
67
67
  import { homedir } from 'node:os';
68
68
  import { resolveInstructionFile } from './common.mjs';
69
69
  import { isSessionConfigHeading } from './config/section-extractor.mjs';
70
+ import { isMainModule } from './is-main-module.mjs';
70
71
 
71
72
  /** Thrown by `lintClaudeMd()` on any infra-level failure (missing/unreadable file). */
72
73
  export class ClaudeMdLintInfraError extends Error {
@@ -536,10 +537,6 @@ function main() {
536
537
  }
537
538
 
538
539
  const __filename = fileURLToPath(import.meta.url);
539
- const isMain =
540
- typeof process !== 'undefined' &&
541
- process.argv[1] !== null &&
542
- process.argv[1] !== undefined &&
543
- resolve(process.argv[1]) === resolve(__filename);
540
+ const isMain =isMainModule(import.meta.url);
544
541
 
545
542
  if (isMain) main();
@@ -743,6 +743,31 @@ export { splitSegments as splitChainSegments };
743
743
 
744
744
  const ENV_ASSIGN_RE = /^[A-Za-z_][A-Za-z0-9_]*=/;
745
745
 
746
+ /**
747
+ * Verbs that name a WRITE TARGET as a bare operand — no redirect operator, no
748
+ * wrapper flag (#1366). A sibling of WRAPPER_UNWRAP's `fileArgFlags`: that table
749
+ * answers "does this WRAPPER flag's operand name a file the wrapper writes?",
750
+ * this one "does this VERB's operand name a file the verb writes?".
751
+ *
752
+ * Spec fields:
753
+ * - prefix: the operand's literal prefix (`of=`), matched on a token AFTER
754
+ * the resolved verb.
755
+ * - mode: the default redirect mode the write is equivalent to.
756
+ * - appendRe: operands that turn the write into an APPEND. `dd` truncates by
757
+ * default (`conv=notrunc` / `oflag=append` do not), so reporting
758
+ * `truncate` unconditionally would over-report a rule class that
759
+ * deliberately permits `>>`.
760
+ *
761
+ * Deliberately narrow — one entry, measured. `tee`/`sponge` and in-process
762
+ * writers (`node -e fs.writeFileSync`) are NOT here: they are not redirect-shaped
763
+ * and belong to a different rule class (see the `redirect-harness-memory`
764
+ * rationale in .orchestrator/policy/blocked-commands.json). REVISIT TRIGGER: a
765
+ * second `<verb> <prefix>file` writer showing up in a transcript census.
766
+ */
767
+ const VERB_FILE_OPERANDS = new Map([
768
+ ['dd', { prefix: 'of=', mode: 'truncate', appendRe: /^(?:conv=.*\bnotrunc\b|oflag=.*\bappend\b)/ }],
769
+ ]);
770
+
746
771
  /**
747
772
  * Transparent process-wrapper table for verb resolution (#982), keyed by
748
773
  * basename. Each spec describes how to skip a wrapper's own options so the
@@ -946,15 +971,37 @@ function resolveCore(segment, unknownFlagsTakeValue) {
946
971
  if (spec.splitString
947
972
  && (text === '-S' || text === '--split-string'
948
973
  || text.startsWith('--split-string=') || text.startsWith('-S'))) {
974
+ // env(1) APPENDS the remaining argv to the split string: `env -S bash
975
+ // -c '…'` runs `bash -c '…'`, not `bash` (#1366). Collecting only the
976
+ // operand made the trailing `-c '…'` execute unseen — the segment then
977
+ // resolved to `verb: 'echo x > CLAUDE.md'` (the quoted payload read as
978
+ // the verb) and neither the match surface nor the redirect surface ever
979
+ // saw it. So the payload is the operand PLUS every remaining token, and
980
+ // the wrapper CONSUMES the segment: nothing is left to resolve as a
981
+ // verb, which is why this now yields `verb: null, index: -1`.
982
+ let operand = null;
949
983
  if (text.startsWith('--split-string=')) {
950
- payloads.push(text.slice('--split-string='.length));
984
+ operand = text.slice('--split-string='.length);
985
+ i++;
951
986
  } else if (text !== '-S' && text !== '--split-string') {
952
- payloads.push(text.slice(2)); // attached form: -S'string'
953
- } else if (i + 1 < segment.length) {
954
- payloads.push(segment[i + 1].text);
987
+ operand = text.slice(2); // attached form: -S'string'
955
988
  i++;
989
+ } else if (i + 1 < segment.length) {
990
+ operand = segment[i + 1].text;
991
+ i += 2;
992
+ } else {
993
+ i++; // `env -S` with no operand at all — nothing to collect
994
+ }
995
+ if (operand !== null) {
996
+ // Raw token texts, joined by one space. Quoting is NOT re-applied:
997
+ // the tokenizer already stripped it, and re-inventing it would be a
998
+ // second quoting grammar. The direction of that loss is safe — an
999
+ // over-wide payload can only make MORE text visible to the matcher
1000
+ // (fail-closed), never hide a redirect operator.
1001
+ const rest = segment.slice(i).map((t) => t.text);
1002
+ payloads.push(rest.length > 0 ? [operand, ...rest].join(' ') : operand);
1003
+ i = segment.length;
956
1004
  }
957
- i++;
958
1005
  continue;
959
1006
  }
960
1007
  if (spec.shellFlags && spec.shellFlags.has(text)) { sawShellFlag = true; i++; continue; }
@@ -1189,15 +1236,65 @@ function dedupedSegmentPayloads(segment, resolved) {
1189
1236
  if (resolved.verb && DASH_C_SHELLS.has(resolved.verb)) {
1190
1237
  for (const p of dashCPayloads(segment, resolved.index)) payloadSet.add(p);
1191
1238
  }
1239
+ addArgJoinedPayload(segment, resolved.verb, resolved.index, payloadSet);
1192
1240
  if (resolved.alt) {
1193
1241
  for (const p of resolved.alt.payloads) payloadSet.add(p);
1194
1242
  if (resolved.alt.verb && DASH_C_SHELLS.has(resolved.alt.verb)) {
1195
1243
  for (const p of dashCPayloads(segment, resolved.alt.index)) payloadSet.add(p);
1196
1244
  }
1245
+ addArgJoinedPayload(segment, resolved.alt.verb, resolved.alt.index, payloadSet);
1197
1246
  }
1198
1247
  return [...payloadSet];
1199
1248
  }
1200
1249
 
1250
+ /**
1251
+ * Interpreters that JOIN their remaining arguments with a space and execute the
1252
+ * result — no `-c` flag, no wrapper option to key on (#1366).
1253
+ *
1254
+ * `eval` is the whole set today. It was already in SHELL_EXEC_INTERPRETERS, but
1255
+ * that set only powers the QUOTED-TOKEN match in matchSegments — it never fed
1256
+ * the payload traversal, so `eval 'echo x > CLAUDE.md'` carried a redirect no
1257
+ * redirect rule ever saw (`resolveSegmentVerb` reports `payloads: []` for it).
1258
+ *
1259
+ * NAMED CEILING (BV-004): only a LITERAL payload is reachable. `eval "$CMD"` and
1260
+ * `eval $(…)` do not exist as command text at hook time and stay in the existing
1261
+ * `unresolved` class (#641). REVISIT TRIGGER: a second arg-joining interpreter
1262
+ * showing up in a transcript census — extend the set, never special-case `eval`.
1263
+ */
1264
+ const ARG_JOINING_INTERPRETERS = new Set(['eval']);
1265
+
1266
+ /**
1267
+ * Add the joined post-verb argument text as a recursion payload when `verb` is
1268
+ * an arg-joining interpreter. No-op otherwise — every other caller shape keeps
1269
+ * the pre-#1366 payload set byte-identical.
1270
+ *
1271
+ * Quoting is not re-applied for the same reason as the `env -S` tail (see
1272
+ * resolveCore): the tokenizer stripped it, and a wider payload can only make
1273
+ * more text visible to the matcher, never hide an operator.
1274
+ *
1275
+ * @param {Array<{ text: string, quoted: boolean }>} segment
1276
+ * @param {string|null} verb
1277
+ * @param {number} index — the verb's token index (`-1` when unresolved)
1278
+ * @param {Set<string>} payloadSet — mutated in place
1279
+ */
1280
+ function addArgJoinedPayload(segment, verb, index, payloadSet) {
1281
+ if (!verb || index < 0 || !ARG_JOINING_INTERPRETERS.has(verb)) return;
1282
+ const words = [];
1283
+ for (let i = index + 1; i < segment.length; i++) {
1284
+ if (segment[i].redirect) {
1285
+ // A redirect belongs to the SEGMENT (`eval 'x' > f` redirects eval's own
1286
+ // stdout), never to the joined argv — the segment scan already reported
1287
+ // it, and re-reporting it through the payload would duplicate the entry.
1288
+ const end = redirectSpanEnd(segment, i);
1289
+ if (end > i) i = end;
1290
+ continue;
1291
+ }
1292
+ words.push(segment[i].text);
1293
+ }
1294
+ const joined = words.join(' ').trim();
1295
+ if (joined.length > 0) payloadSet.add(joined);
1296
+ }
1297
+
1201
1298
  /**
1202
1299
  * Match a blocked-pattern regex against tokenized chain segments — the shared
1203
1300
  * core of commandMatchesBlocked, recursion-capable for `-c` payloads (#982).
@@ -1347,6 +1444,37 @@ function collectRedirectTargets(segments, out, depth, budget) {
1347
1444
  out.push({ target: wa.value, mode: 'truncate', fd: null });
1348
1445
  }
1349
1446
 
1447
+ // A VERB can name its write target as a bare operand, with no redirect
1448
+ // operator and no wrapper flag: `dd of=CLAUDE.md` truncates CLAUDE.md while
1449
+ // `> CLAUDE.md` is denied (measured ALLOW pre-#1366 against the real
1450
+ // policy). Keyed on the RESOLVED verb, so `sudo dd of=X` is reached through
1451
+ // the existing wrapper unwrap; both readings contribute, as above.
1452
+ const seenVerbOperands = new Set();
1453
+ for (const reading of resolved.alt ? [resolved, resolved.alt] : [resolved]) {
1454
+ const spec = reading.verb ? VERB_FILE_OPERANDS.get(reading.verb) : null;
1455
+ if (!spec || reading.index < 0) continue;
1456
+ let operand = null;
1457
+ let append = false;
1458
+ for (let k = reading.index + 1; k < segment.length; k++) {
1459
+ const text = segment[k].text;
1460
+ if (segment[k].redirect) continue;
1461
+ if (text.startsWith(spec.prefix)) operand = text.slice(spec.prefix.length);
1462
+ else if (spec.appendRe?.test(text)) append = true;
1463
+ }
1464
+ if (operand === null || operand.length === 0) continue;
1465
+ const mode = append ? 'append' : spec.mode;
1466
+ const key = `${operand}:${mode}`;
1467
+ if (seenVerbOperands.has(key)) continue;
1468
+ seenVerbOperands.add(key);
1469
+ if (/[$`]/.test(operand)) {
1470
+ // Same fail-visible rule as a redirect operand (#983) — never guess at a
1471
+ // variable or a command substitution, never silently drop it either.
1472
+ out.push({ target: null, mode, fd: null, unresolved: true });
1473
+ continue;
1474
+ }
1475
+ out.push({ target: operand, mode, fd: null });
1476
+ }
1477
+
1350
1478
  // Both readings of an ambiguous unknown flag contribute payloads (#1000),
1351
1479
  // UNIONED and de-duped (HIGH-2). Walking parse A only let
1352
1480
  // `env -Q x bash -c 'echo pwned > CLAUDE.md'` — which resolves to the
@@ -1663,18 +1791,90 @@ export function redirectRuleMatches(rule, command, opts = {}) {
1663
1791
  const denylist = Array.isArray(rule['target-denylist']) ? rule['target-denylist'] : [];
1664
1792
  if (denylist.length === 0) return false;
1665
1793
  const modes = new Set(Array.isArray(rule.modes) && rule.modes.length > 0 ? rule.modes : ['truncate']);
1666
- const regexes = denylist.map(redirectGlobToRegExp);
1794
+
1795
+ // #1362 — TWO denylist classes, deliberately kept apart:
1796
+ //
1797
+ // REPO-RELATIVE (every pre-#1362 entry): judged against
1798
+ // `repoRelativeRedirectTarget`, which returns `null` for anything outside
1799
+ // the root. Behaviour is byte-identical to before, and that null is what
1800
+ // keeps a relative entry from ever reaching an out-of-repo path.
1801
+ //
1802
+ // HOME-ANCHORED (`~/…`, new): judged against the EXPANDED ABSOLUTE target,
1803
+ // never relativized. Needed because the artefacts worth denying are not all
1804
+ // inside the repo — the harness auto-memory directory
1805
+ // (`~/.claude/projects/<encodeProjectDir(root)>/memory/`) is auto-injected
1806
+ // as trusted project context into every later session yet lives outside
1807
+ // `git diff`, CI review and the secret scanners. Measured 2026-09-13: with
1808
+ // only the repo-relative class, NO glob spelling could match it, because
1809
+ // `relativizeAgainstRoot` discards out-of-repo targets by design.
1810
+ //
1811
+ // The split is one-directional on purpose: a home-anchored entry cannot widen
1812
+ // an existing repo-relative rule, and a repo-relative entry still cannot
1813
+ // escape the root. An unexpandable `~` (no usable `home`) matches NOTHING —
1814
+ // fail-closed for the new class, since an unexpanded `~` names no file.
1815
+ const relRegexes = [];
1816
+ const absRegexes = [];
1817
+ for (const pattern of denylist) {
1818
+ const text = String(pattern);
1819
+ if (text === '~' || text.startsWith('~/')) {
1820
+ const expanded = expandLeadingHome(text, home);
1821
+ if (expanded === text) continue; // no usable home → no match, never a guess
1822
+ absRegexes.push(redirectGlobToRegExp(foldAbsoluteTarget(expanded)));
1823
+ } else {
1824
+ relRegexes.push(redirectGlobToRegExp(text));
1825
+ }
1826
+ }
1827
+ if (relRegexes.length === 0 && absRegexes.length === 0) return false;
1667
1828
 
1668
1829
  for (const entry of extractRedirectTargets(command)) {
1669
1830
  if (entry.unresolved) continue;
1670
1831
  if (!modes.has(entry.mode)) continue;
1671
- const target = repoRelativeRedirectTarget(entry.target, repoRoot, home);
1672
- if (target === null) continue;
1673
- if (regexes.some((re) => re.test(target))) return true;
1832
+
1833
+ if (absRegexes.length > 0) {
1834
+ // A RELATIVE target is repo-root-relative here, exactly as the repo-relative
1835
+ // class already treats it (#994 R1) — otherwise `> ../../.claude/projects/
1836
+ // <enc>/memory/MEMORY.md` from the repo root names the very file the `~`
1837
+ // spelling blocks, and only the spelling would be denied (measured
1838
+ // 2026-09-13: relative `>`/`>>` ALLOW vs tilde/absolute BLOCK on one and
1839
+ // the same resolved path). This resolves TARGETS, never ENTRIES: a
1840
+ // repo-relative denylist entry keeps going through
1841
+ // `repoRelativeRedirectTarget` below and still cannot reach out of the root.
1842
+ const expandedTarget = expandLeadingHome(entry.target, home);
1843
+ const absTarget = path.isAbsolute(expandedTarget)
1844
+ ? expandedTarget
1845
+ : repoRoot && path.isAbsolute(repoRoot)
1846
+ ? path.resolve(repoRoot, expandedTarget)
1847
+ : null;
1848
+ if (absTarget !== null) {
1849
+ const folded = foldAbsoluteTarget(absTarget);
1850
+ if (absRegexes.some((re) => re.test(folded))) return true;
1851
+ }
1852
+ }
1853
+
1854
+ if (relRegexes.length > 0) {
1855
+ const target = repoRelativeRedirectTarget(entry.target, repoRoot, home);
1856
+ if (target !== null && relRegexes.some((re) => re.test(target))) return true;
1857
+ }
1674
1858
  }
1675
1859
  return false;
1676
1860
  }
1677
1861
 
1862
+ /**
1863
+ * Normalize an ABSOLUTE path for home-anchored denylist comparison (#1362):
1864
+ * same alias-collapse and case-fold `relativizeAgainstRoot` applies before its
1865
+ * containment test, so the two classes agree on what `/private/tmp` and a
1866
+ * case-insensitive volume mean. Applied to BOTH sides (pattern and target), so
1867
+ * the folding can never make a pattern match more than its literal spelling.
1868
+ *
1869
+ * @param {string} abs
1870
+ * @returns {string}
1871
+ */
1872
+ function foldAbsoluteTarget(abs) {
1873
+ return stripPathAliases(path.posix.normalize(String(abs).replace(/\\/g, '/'))).toLocaleLowerCase(
1874
+ 'en-US',
1875
+ );
1876
+ }
1877
+
1678
1878
  /**
1679
1879
  * Test whether a command string contains a blocked pattern with shell-aware
1680
1880
  * boundaries AND a quoted-payload guard (#641).
@@ -5,6 +5,25 @@ import { preprocessBlockLines } from './block-preprocess.mjs';
5
5
  * drift-check.mjs — Parser for the top-level `drift-check:` YAML block.
6
6
  *
7
7
  * Ported from config-yaml-parser.sh (v2).
8
+ *
9
+ * SCOPE NOTE — this file parses FLAGS ONLY; it implements no check. The
10
+ * `check-command-count` flag below toggles the `command-count` surface in
11
+ * `skills/claude-md-drift-check/checker.mjs`, which is where the counting lives.
12
+ *
13
+ * `command-count` STAYS on `ls commands/*.md | wc -l` on purpose. The checker is
14
+ * GENERIC over any consumer repo, where a slash command still IS a
15
+ * `commands/*.md` file. The 2026-09-16 command→skill fold (#1370) is a fact
16
+ * about THIS plugin — 24 command files folded into their same-named
17
+ * `skills/<name>/SKILL.md` behind an explicit `user-invocable: true` — so
18
+ * teaching the shared checker that union would mis-measure every repo that never
19
+ * folded. `scripts/site-numbers.mjs` `countCommands()` carries the union for this
20
+ * plugin's own tile, and names the divergence in its docblock.
21
+ *
22
+ * CONSEQUENCE for THIS repo, left deliberately visible rather than papered over:
23
+ * a prose claim of the shape "N commands" / "N slash commands" in a
24
+ * drift-check-scanned doc is compared against the `commands/` listing (2 after
25
+ * the fold), so such a claim must either be dropped or reworded off the
26
+ * `\b(\d+)\s+(?:\/)?commands?\b` pattern the checker matches.
8
27
  */
9
28
 
10
29
  /**
@@ -45,7 +45,7 @@
45
45
 
46
46
  import { existsSync, statSync, openSync, readSync, closeSync } from 'node:fs';
47
47
  import { join } from 'node:path';
48
- import { pathToFileURL } from 'node:url';
48
+ import { isMainModule } from './is-main-module.mjs';
49
49
 
50
50
  const DEFAULT_INTERVAL_S = 2;
51
51
  const EVENTS_FILE_REL = '.orchestrator/metrics/events.jsonl';
@@ -502,7 +502,7 @@ function main() {
502
502
 
503
503
  // Run the tail loop only when executed as a script. Importing the module (for
504
504
  // unit tests over `classify`) must not parse vitest's argv and exit 1.
505
- if (process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href) {
505
+ if (isMainModule(import.meta.url)) {
506
506
  main();
507
507
  }
508
508
 
@@ -18,10 +18,10 @@
18
18
  import { spawnSync } from 'node:child_process';
19
19
  import { existsSync, readFileSync } from 'node:fs';
20
20
  import path from 'node:path';
21
- import { pathToFileURL } from 'node:url';
22
21
 
23
22
  import { matcherMatches, readHookDecision } from './pi-hook-bridge.mjs';
24
23
  import { resolvePluginRoot } from './plugin-root.mjs';
24
+ import { isMainModule } from './is-main-module.mjs';
25
25
 
26
26
  export const CURSOR_TO_CANONICAL_EVENT = Object.freeze({
27
27
  sessionStart: 'SessionStart',
@@ -434,7 +434,7 @@ async function main() {
434
434
  process.stdout.write(`${JSON.stringify(toCursorHookOutput(eventName, result))}\n`);
435
435
  }
436
436
 
437
- const isMain = process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href;
437
+ const isMain =isMainModule(import.meta.url);
438
438
  if (isMain) {
439
439
  main().catch((err) => {
440
440
  process.stderr.write(`cursor-hook-bridge: ${err instanceof Error ? err.message : err}\n`);
@@ -58,6 +58,7 @@
58
58
  import { readdirSync, readFileSync, statSync } from 'node:fs';
59
59
  import { fileURLToPath } from 'node:url';
60
60
  import { join, relative, resolve } from 'node:path';
61
+ import { isMainModule } from './is-main-module.mjs';
61
62
 
62
63
  /** Block-scalar indicator on the `description:` line: '>' (folded) or '|' (literal), optional chomping '+'/'-'. */
63
64
  const BLOCK_SCALAR_RE = /^([>|])([+-]?)\s*$/;
@@ -526,10 +527,6 @@ function main() {
526
527
  }
527
528
 
528
529
  const __filename = fileURLToPath(import.meta.url);
529
- const isMain =
530
- typeof process !== 'undefined' &&
531
- process.argv[1] !== null &&
532
- process.argv[1] !== undefined &&
533
- resolve(process.argv[1]) === resolve(__filename);
530
+ const isMain =isMainModule(import.meta.url);
534
531
 
535
532
  if (isMain) main();
@@ -32,6 +32,7 @@ import { warn } from '../common.mjs';
32
32
  import { acquire } from '../session-lock.mjs';
33
33
  import { enumerateCandidates, freeCandidates } from './enumerate.mjs';
34
34
  import { rankCandidates } from './rank.mjs';
35
+ import { isMainModule } from '../is-main-module.mjs';
35
36
 
36
37
  const VERSION = '1.0.0';
37
38
 
@@ -247,7 +248,7 @@ export async function main(argv) {
247
248
  }
248
249
 
249
250
  // Run main only when executed directly, never on import.
250
- if (import.meta.url === `file://${process.argv[1]}`) {
251
+ if (isMainModule(import.meta.url)) {
251
252
  main(process.argv.slice(2))
252
253
  .then((code) => process.exit(code))
253
254
  .catch((err) => {
@@ -7,6 +7,7 @@
7
7
  */
8
8
 
9
9
  import { runEcosystemWizard } from './ecosystem-wizard/wizard-prompt.mjs';
10
+ import { isMainModule } from './is-main-module.mjs';
10
11
 
11
12
  export { detectCiProvider } from './ecosystem-wizard/ci-detector.mjs';
12
13
  export {
@@ -31,7 +32,7 @@ export { runEcosystemWizard } from './ecosystem-wizard/wizard-prompt.mjs';
31
32
  // CLI entry
32
33
  // ---------------------------------------------------------------------------
33
34
 
34
- if (process.argv[1] && process.argv[1].endsWith('ecosystem-wizard.mjs')) {
35
+ if (isMainModule(import.meta.url)) {
35
36
  const args = process.argv.slice(2);
36
37
  const repoRootIdx = args.indexOf('--repo-root');
37
38
  const dryRun = args.includes('--dry-run');
@@ -30,6 +30,8 @@ import { promises as fs } from 'node:fs';
30
30
  import path from 'node:path';
31
31
  import { execSync } from 'node:child_process';
32
32
 
33
+ import { isMainModule } from './is-main-module.mjs';
34
+
33
35
  // ---------------------------------------------------------------------------
34
36
  // Configuration defaults (mirrors the .sh defaults)
35
37
  // ---------------------------------------------------------------------------
@@ -296,14 +298,7 @@ async function _cliMain() {
296
298
  process.exit(3);
297
299
  }
298
300
 
299
- // Detect CLI invocation: import.meta.url matches the argv[1] path
300
- const isMain = process.argv[1] && (
301
- process.argv[1] === new URL(import.meta.url).pathname ||
302
- // Handle invocation without file:// prefix
303
- process.argv[1].endsWith('fetch-baseline.mjs')
304
- );
305
-
306
- if (isMain) {
301
+ if (isMainModule(import.meta.url)) {
307
302
  _cliMain().catch((err) => {
308
303
  process.stderr.write(`FATAL: ${err instanceof Error ? err.message : String(err)}\n`);
309
304
  process.exit(3);
@@ -24,6 +24,7 @@ import { promisify } from 'node:util';
24
24
 
25
25
  import { detectVcsForRepo, discoverVaultRepos } from '../gitlab-portfolio/vcs-detect.mjs';
26
26
  import { resolveRepoSpec } from '../vcs-repo-spec.mjs';
27
+ import { isMainModule } from '../is-main-module.mjs';
27
28
 
28
29
  const realExecFile = promisify(execFileCb);
29
30
 
@@ -455,7 +456,7 @@ export async function main(argv, deps = {}) {
455
456
 
456
457
  // ── CLI guard — prevent process.exit during test-time imports ─────────────────
457
458
 
458
- if (import.meta.url === `file://${process.argv[1]}`) {
459
+ if (isMainModule(import.meta.url)) {
459
460
  main(process.argv.slice(2))
460
461
  .then((result) => {
461
462
  process.exit(result.exitCode);
@@ -36,6 +36,7 @@ import { expandTilde } from '../common.mjs';
36
36
  import { discoverVaultRepos } from './vcs-detect.mjs';
37
37
  import { fetchIssuesMultiRepo, summarizeRepo } from './aggregator.mjs';
38
38
  import { renderPortfolio, writePortfolio } from './markdown-writer.mjs';
39
+ import { isMainModule } from '../is-main-module.mjs';
39
40
 
40
41
  // ── Helpers ───────────────────────────────────────────────────────────────────
41
42
 
@@ -406,7 +407,7 @@ EXIT CODES
406
407
 
407
408
  // ── CLI guard — prevent process.exit during test-time imports ─────────────────
408
409
 
409
- if (import.meta.url === `file://${process.argv[1]}`) {
410
+ if (isMainModule(import.meta.url)) {
410
411
  main(process.argv.slice(2)).then((result) => {
411
412
  process.exit(result.exitCode);
412
413
  }).catch((err) => {