session-orchestrator 5.11.0 → 5.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (394) hide show
  1. package/.claude-plugin/marketplace.json +12 -1
  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 +180 -3
  11. package/README.md +126 -117
  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 +4 -2
  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 +102 -25
  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 +55 -21
  47. package/docs/session-config-template.md +32 -24
  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 +158 -59
  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 -3
  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 +73 -3
  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/remote-hosts.mjs +2 -2
  143. package/scripts/lib/config/section-extractor.mjs +65 -11
  144. package/scripts/lib/config/template-keys.mjs +153 -0
  145. package/scripts/lib/config/vault-integration.mjs +1 -0
  146. package/scripts/lib/config.mjs +3 -3
  147. package/scripts/lib/coordinator-snapshot.mjs +1 -1
  148. package/scripts/lib/discovery/triage-state.mjs +2 -1
  149. package/scripts/lib/dispatcher/rank.mjs +5 -7
  150. package/scripts/lib/ecosystem-health.mjs +2 -2
  151. package/scripts/lib/eval/engine.mjs +3 -2
  152. package/scripts/lib/eval/session-resolve.mjs +6 -2
  153. package/scripts/lib/eval/sink.mjs +4 -3
  154. package/scripts/lib/events-retention-banner.mjs +3 -2
  155. package/scripts/lib/events-schema.mjs +10 -0
  156. package/scripts/lib/events.mjs +23 -12
  157. package/scripts/lib/evolve/autonomy-verdict.mjs +4 -9
  158. package/scripts/lib/evolve/autopilot-effectiveness.mjs +8 -18
  159. package/scripts/lib/fleet-protocol.mjs +21 -0
  160. package/scripts/lib/gates/gate-full.mjs +172 -11
  161. package/scripts/lib/gates/gate-helpers.mjs +236 -23
  162. package/scripts/lib/git-config-drift.mjs +141 -7
  163. package/scripts/lib/github-export.mjs +447 -0
  164. package/scripts/lib/gitlab-portfolio/cli.mjs +16 -5
  165. package/scripts/lib/handover-queue-marks.mjs +159 -0
  166. package/scripts/lib/hardware-pattern-detector.mjs +103 -13
  167. package/scripts/lib/harness-audit/categories/category1.mjs +16 -4
  168. package/scripts/lib/harness-audit/categories/category4.mjs +11 -8
  169. package/scripts/lib/harness-audit/categories/category6.mjs +25 -5
  170. package/scripts/lib/harness-audit/categories/category9.mjs +10 -4
  171. package/scripts/lib/host-identity.mjs +9 -5
  172. package/scripts/lib/http-request.mjs +64 -0
  173. package/scripts/lib/instruction-budget-guard.mjs +30 -22
  174. package/scripts/lib/io.mjs +6 -3
  175. package/scripts/lib/issue-budget-reconcile.mjs +3 -2
  176. package/scripts/lib/issue-budget.mjs +36 -7
  177. package/scripts/lib/learnings/evidence-candidates.mjs +154 -0
  178. package/scripts/lib/learnings/expiry-sweep.mjs +99 -7
  179. package/scripts/lib/learnings/io.mjs +289 -15
  180. package/scripts/lib/learnings/select.mjs +53 -4
  181. package/scripts/lib/learnings/store-role.mjs +209 -0
  182. package/scripts/lib/learnings/surface.mjs +46 -7
  183. package/scripts/lib/ledger-path.mjs +113 -0
  184. package/scripts/lib/maintenance-due-banner.mjs +63 -12
  185. package/scripts/lib/memory-banner.mjs +5 -6
  186. package/scripts/lib/memory-proposals/collector.mjs +5 -2
  187. package/scripts/lib/memory-proposals/sink.mjs +7 -4
  188. package/scripts/lib/memory-proposals/store.mjs +22 -5
  189. package/scripts/lib/mode-selector-accuracy.mjs +2 -1
  190. package/scripts/lib/no-follow-open.mjs +57 -0
  191. package/scripts/lib/orphan-reaper/reaper-audit.mjs +4 -2
  192. package/scripts/lib/own-lock-root.mjs +95 -0
  193. package/scripts/lib/owner-interview.mjs +2 -1
  194. package/scripts/lib/owner-yaml.mjs +70 -7
  195. package/scripts/lib/platform.mjs +161 -9
  196. package/scripts/lib/plugin-update-banner.mjs +15 -11
  197. package/scripts/lib/pre-push-local-partial.mjs +117 -0
  198. package/scripts/lib/pre-push-remote-gate.mjs +151 -25
  199. package/scripts/lib/process-group.mjs +270 -34
  200. package/scripts/lib/project-hygiene.mjs +28 -0
  201. package/scripts/lib/quality-gate.mjs +33 -9
  202. package/scripts/lib/reconcile/backlog.mjs +29 -15
  203. package/scripts/lib/reconcile/engine.mjs +26 -2
  204. package/scripts/lib/reconcile/idempotency.mjs +65 -3
  205. package/scripts/lib/reconcile/rule-expiry-sweep.mjs +53 -12
  206. package/scripts/lib/reconcile/verdict-ledger.mjs +224 -0
  207. package/scripts/lib/reconcile-nudge-banner.mjs +40 -6
  208. package/scripts/lib/resource-probe/evaluate.mjs +32 -9
  209. package/scripts/lib/resource-probe/probe-platform.mjs +3 -1
  210. package/scripts/lib/rule-loader.mjs +20 -10
  211. package/scripts/lib/scope-baseline.mjs +123 -8
  212. package/scripts/lib/scope-echo.mjs +5 -2
  213. package/scripts/lib/scope-gate.mjs +55 -20
  214. package/scripts/lib/session-close-backfill.mjs +75 -7
  215. package/scripts/lib/session-end/close-precheck.mjs +151 -0
  216. package/scripts/lib/session-end/phase-skip.mjs +8 -7
  217. package/scripts/lib/session-end/tail-runner.mjs +7 -2
  218. package/scripts/lib/session-end/vault-orphans.mjs +205 -0
  219. package/scripts/lib/session-id.mjs +9 -1
  220. package/scripts/lib/session-identity/own-session.mjs +63 -0
  221. package/scripts/lib/session-invocation.mjs +14 -3
  222. package/scripts/lib/session-lock.mjs +114 -0
  223. package/scripts/lib/session-record-repair.mjs +245 -35
  224. package/scripts/lib/session-registry.mjs +85 -12
  225. package/scripts/lib/session-schema/constants.mjs +9 -0
  226. package/scripts/lib/session-schema/filters.mjs +56 -1
  227. package/scripts/lib/session-schema/validator.mjs +15 -5
  228. package/scripts/lib/session-start-probes.mjs +149 -22
  229. package/scripts/lib/session-token-rollup.mjs +2 -1
  230. package/scripts/lib/session-work-trees.mjs +166 -0
  231. package/scripts/lib/sessions-canonical.mjs +61 -3
  232. package/scripts/lib/sessions-integrity-banner.mjs +17 -39
  233. package/scripts/lib/sessions-staleness-banner.mjs +8 -5
  234. package/scripts/lib/sh-quote.mjs +1 -0
  235. package/scripts/lib/skill-evidence-window.mjs +5 -1
  236. package/scripts/lib/skill-health/join.mjs +10 -9
  237. package/scripts/lib/skill-invocations-schema.mjs +13 -0
  238. package/scripts/lib/skill-judgments-schema.mjs +6 -2
  239. package/scripts/lib/snapshot-probe.mjs +414 -0
  240. package/scripts/lib/so-hud/collect.mjs +174 -0
  241. package/scripts/lib/so-hud/runtime.mjs +87 -0
  242. package/scripts/lib/spiral-carryover.mjs +8 -1
  243. package/scripts/lib/state-md/frontmatter-mutators.mjs +266 -8
  244. package/scripts/lib/state-md/idle-reset.mjs +248 -0
  245. package/scripts/lib/state-md/mission-status.mjs +5 -1
  246. package/scripts/lib/state-md/recommendations.mjs +26 -4
  247. package/scripts/lib/state-md-peer-guard.mjs +2 -0
  248. package/scripts/lib/state-md.mjs +31 -8
  249. package/scripts/lib/sunset/walker.mjs +4 -3
  250. package/scripts/lib/telemetry/consent.mjs +4 -2
  251. package/scripts/lib/telemetry/queue.mjs +3 -3
  252. package/scripts/lib/telemetry/sync.mjs +45 -9
  253. package/scripts/lib/test-runner/artifact-paths.mjs +10 -2
  254. package/scripts/lib/tmux-layout/layouts.mjs +20 -3
  255. package/scripts/lib/ux-grill/paths.mjs +5 -1
  256. package/scripts/lib/validate/check-agents.mjs +37 -6
  257. package/scripts/lib/validate/check-component-paths.mjs +25 -1
  258. package/scripts/lib/validate/check-guard-requires-parity.mjs +3 -3
  259. package/scripts/lib/validate/check-hooks-symmetry.mjs +53 -32
  260. package/scripts/lib/validate/check-json-files.mjs +3 -1
  261. package/scripts/lib/validate/check-learning-provenance.mjs +3 -2
  262. package/scripts/lib/validate/check-learnings-shell-writes.mjs +10 -4
  263. package/scripts/lib/validate/check-metrics-ledger-root.mjs +234 -0
  264. package/scripts/lib/validate/check-owner-leakage.mjs +159 -11
  265. package/scripts/lib/validate/check-test-posix-shell.mjs +246 -0
  266. package/scripts/lib/validate/check-unwired-features.mjs +4 -2
  267. package/scripts/lib/validate/check-vcs-repo-flag-positional.mjs +58 -0
  268. package/scripts/lib/validate/check-vcs-repo-flag.mjs +259 -73
  269. package/scripts/lib/validate/check-workflow-continue-on-error.mjs +63 -0
  270. package/scripts/lib/validate/confidential-names.mjs +3 -1
  271. package/scripts/lib/vault-mirror/migrate-ids.mjs +321 -0
  272. package/scripts/lib/vault-mirror/namespace.mjs +22 -9
  273. package/scripts/lib/vault-mirror/process.mjs +197 -65
  274. package/scripts/lib/vault-mirror/prune.mjs +5 -2
  275. package/scripts/lib/vault-mirror/render-learnings.mjs +8 -7
  276. package/scripts/lib/vault-mirror/render-sessions.mjs +36 -53
  277. package/scripts/lib/vault-mirror/retention.mjs +2 -2
  278. package/scripts/lib/vault-mirror/utils.mjs +106 -13
  279. package/scripts/lib/vault-staleness-banner.mjs +4 -7
  280. package/scripts/lib/vault-status/board-writer.mjs +34 -5
  281. package/scripts/lib/vault-status/narrative-mirror.mjs +66 -7
  282. package/scripts/lib/wave-executor/foreign-dispatch.mjs +20 -0
  283. package/scripts/lib/win-process-tree.mjs +137 -0
  284. package/scripts/lib/workspace.mjs +1 -1
  285. package/scripts/lib/worktree/constants.mjs +1 -1
  286. package/scripts/lib/worktree/lifecycle.mjs +1 -1
  287. package/scripts/lib/worktree/listing.mjs +1 -1
  288. package/scripts/lib/zx.mjs +50 -0
  289. package/scripts/mcp-server.mjs +16 -7
  290. package/scripts/mcp-server.sh +13 -83
  291. package/scripts/measure-policy-cache-effectiveness.mjs +7 -5
  292. package/scripts/memory-propose.mjs +10 -3
  293. package/scripts/migrate-cold-start-seed.mjs +7 -0
  294. package/scripts/migrate-legacy-learnings.sh +22 -0
  295. package/scripts/migrate-sessions-jsonl.mjs +16 -2
  296. package/scripts/migrate-vault-paths.mjs +29 -13
  297. package/scripts/parse-config.mjs +71 -14
  298. package/scripts/print-learnings-index.mjs +25 -2
  299. package/scripts/release-session-lock.mjs +94 -7
  300. package/scripts/release.mjs +451 -24
  301. package/scripts/relocate-vault-corpus.mjs +11 -7
  302. package/scripts/repair-invalid-sessions.mjs +68 -3
  303. package/scripts/resolve-session-invocation.mjs +57 -8
  304. package/scripts/run-migrate-v2-cross-repo.mjs +56 -1
  305. package/scripts/run-quality-gate.mjs +33 -7
  306. package/scripts/self-update.mjs +56 -7
  307. package/scripts/session-commit-files.mjs +133 -0
  308. package/scripts/site-numbers.mjs +52 -9
  309. package/scripts/snapshot-probe.mjs +156 -0
  310. package/scripts/so-hud.mjs +68 -0
  311. package/scripts/state-md-idle-reset.mjs +82 -0
  312. package/scripts/sweep-expired-learnings.mjs +54 -6
  313. package/scripts/sweep-expired-rules.mjs +4 -1
  314. package/scripts/validate-plugin.mjs +22 -0
  315. package/scripts/vault-integration-watcher.mjs +54 -6
  316. package/scripts/vault-mirror.mjs +9 -0
  317. package/scripts/vault-orphans.mjs +171 -0
  318. package/skills/_shared/fleet-protocol.md +166 -3
  319. package/skills/_shared/instruction-file-resolution.md +1 -1
  320. package/skills/_shared/monitor-patterns.md +44 -25
  321. package/skills/_shared/platform-tools.md +7 -0
  322. package/skills/_shared/state-ownership.md +5 -4
  323. package/skills/architecture/references/domain-model.md +1 -1
  324. package/skills/autopilot/SKILL.md +3 -2
  325. package/skills/bootstrap/SKILL.md +3 -1
  326. package/skills/bootstrap/_shared-template.md +4 -4
  327. package/skills/bootstrap/fast-template.md +1 -1
  328. package/skills/bootstrap/private-contract.md +1 -1
  329. package/skills/bootstrap/references/bootstrap-retroactive-flow.md +17 -9
  330. package/skills/bootstrap/templates/agents/project-code-review.md +5 -1
  331. package/skills/bootstrap/templates/agents/project-discovery.md +5 -1
  332. package/skills/bootstrap/templates/agents/project-quality-gate.md +5 -1
  333. package/skills/claude-md-drift-check/SKILL.md +3 -3
  334. package/skills/claude-md-drift-check/checker.mjs +61 -101
  335. package/skills/close/SKILL.md +20 -7
  336. package/skills/discovery/probes/docs-staleness.mjs +7 -5
  337. package/skills/discovery/probes/frontend-slop.mjs +7 -3
  338. package/skills/discovery/probes/ssot-code-diff.mjs +5 -3
  339. package/skills/discovery/probes/supply-chain-slopcheck.mjs +10 -8
  340. package/skills/discovery/probes/vault-narrative-staleness.mjs +30 -10
  341. package/skills/discovery/probes/vault-staleness.mjs +5 -3
  342. package/skills/discovery/probes-session.md +3 -0
  343. package/skills/discovery/probes-vault.md +2 -0
  344. package/skills/dispatcher/SKILL.md +1 -1
  345. package/skills/eval/SKILL.md +3 -1
  346. package/skills/evolve/SKILL.md +2 -0
  347. package/skills/evolve/references/evolve-analyze-mode.md +3 -3
  348. package/skills/evolve/references/evolve-dialectic-mode.md +1 -1
  349. package/skills/gitlab-ops/SKILL.md +7 -6
  350. package/skills/gitlab-portfolio/SKILL.md +2 -2
  351. package/skills/go/SKILL.md +13 -1
  352. package/skills/npm-publish/SKILL.md +5 -2
  353. package/skills/plan/SKILL.md +23 -10
  354. package/skills/plan/mode-feature.md +13 -8
  355. package/skills/plan/mode-new.md +6 -14
  356. package/skills/plan/prd-feature-template.md +19 -0
  357. package/skills/plan/prd-full-template.md +12 -5
  358. package/skills/plan/prd-premortem-panel.md +48 -0
  359. package/skills/plan/prd-reviewer-prompt.md +11 -3
  360. package/skills/plan/soul.md +1 -1
  361. package/skills/portfolio/SKILL.md +5 -6
  362. package/skills/quality-gates/SKILL.md +2 -4
  363. package/skills/reconcile/SKILL.md +27 -4
  364. package/skills/release/SKILL.md +16 -8
  365. package/skills/session-end/SKILL.md +97 -13
  366. package/skills/session-end/phase-3-2-docs-verification.md +1 -0
  367. package/skills/session-end/phase-3-6-tail.md +11 -7
  368. package/skills/session-end/phase-3-7a-recommendations.md +9 -2
  369. package/skills/session-end/plan-verification.md +16 -1
  370. package/skills/session-end/references/phase-3-7e-vault-orphans.md +63 -0
  371. package/skills/session-end/references/phase-3-documentation-updates.md +25 -13
  372. package/skills/session-end/references/phase-5-issue-cleanup.md +1 -1
  373. package/skills/session-end/references/session-summary-template.md +8 -0
  374. package/skills/session-end/session-metrics-write.md +7 -4
  375. package/skills/session-plan/SKILL.md +5 -3
  376. package/skills/session-start/SKILL.md +10 -4
  377. package/skills/session-start/phase-4-5-resource-health.md +11 -5
  378. package/skills/session-start/phase-7-5-mode-selector.md +3 -1
  379. package/skills/session-start/phase-8-5-express-path.md +4 -3
  380. package/skills/session-start/references/phase-1-2-session-lock.md +7 -5
  381. package/skills/session-start/references/phase-1-5-session-continuity.md +13 -5
  382. package/skills/session-start/references/phase-1-7-vault-status-board.md +1 -1
  383. package/skills/session-start/references/phase-4-ssot-environment-check.md +3 -2
  384. package/skills/tmux-layout/SKILL.md +2 -2
  385. package/skills/vault-sync/validator.mjs +3 -2
  386. package/skills/wave-executor/SKILL.md +5 -4
  387. package/skills/wave-executor/references/wave-executor-state-init.md +3 -3
  388. package/skills/wave-executor/references/wave-loop-dispatch.md +11 -5
  389. package/skills/wave-executor/references/wave-loop-review.md +13 -5
  390. package/skills/wave-executor/references/wave-loop-scope-manifest.md +1 -1
  391. package/AGENTS.md +0 -207
  392. package/assets/og-card.png +0 -0
  393. package/assets/og-card.svg +0 -47
  394. package/assets/wave-lifecycle.svg +0 -98
@@ -120,32 +120,11 @@
120
120
  * executing the binary shows the constraint — same lesson as the `glab repo`
121
121
  * asymmetry above, in the opposite direction.
122
122
  *
123
- * ## Blind spot — a `-R` the CLI will REJECT
124
- *
125
- * Every entry in the gap list below is an OMISSION: a call site the census does
126
- * not see. This one is not, so it is named apart from them.
127
- * `repoFlagBlockedByMissingPositional()` is consulted only AFTER the
128
- * `--repo`-present branch (see its own docstring for why), so the
129
- * conditional-positional rule can only ever describe calls that LACK the flag —
130
- * the harmless half of that table. The harmful half is invisible:
131
- * `gh pr checks -R o/r --watch` carries `-R` with no positional, which gh 2.86.0
132
- * rejects at parse time (`argument required when using the --repo flag`, exit 1,
133
- * probed 2026-08-14). This census counts it as `withFlag` — as already hardened.
134
- *
135
- * Measured 2026-08-14 against this tree: **0 such call sites**. A probe copy of
136
- * this file, patched to skip the repo flag's own value before scanning the
137
- * remaining tokens for a positional, ran `inspectVcsRepoFlag(repoRoot)` and
138
- * reported an empty list — so the blind spot is inert here today, and detecting
139
- * it is ~8 lines.
140
- *
141
- * It is nonetheless NOT reported, for a contract reason rather than a detection
142
- * one. A finding here means "add `--repo`"; that call's fix is the opposite
143
- * ("name the PR, or drop the flag"), and the `kind` enum, the WARN message text
144
- * and the PASS line's "N bare" all speak only the first language. Folding a
145
- * loud, exit-1 failure into a census built for SILENT wrong-repo failures would
146
- * hand the sweep a worklist whose entries prescribe opposite edits, and would
147
- * buy nothing the command's own first run does not already say. That class wants
148
- * its own check; it is routed as a follow-up rather than patched in here.
123
+ * ## Rejected flags (issue #1024)
124
+ *
125
+ * `check-vcs-repo-flag-positional.mjs` uses the same scan engine in rejected
126
+ * mode, with its own finding kind and advice: name the PR or drop the flag.
127
+ * The default census still reports only missing repo flags.
149
128
  *
150
129
  * ## Named coverage gaps (v1) — read these before quoting the count
151
130
  *
@@ -195,6 +174,7 @@ import { existsSync, readFileSync, readdirSync } from 'node:fs';
195
174
  import path from 'node:path';
196
175
  import { SHELL_LANGS, forEachLine } from './markdown-fences.mjs';
197
176
  import { isMainModule } from '../is-main-module.mjs';
177
+ import { tokenizeCommand, redirectSpanEnd } from '../command-blocker.mjs';
198
178
 
199
179
  /** Directories whose content is scanned. Root-level `*.md` is added separately. */
200
180
  const SCAN_DIRS = Object.freeze([
@@ -309,12 +289,8 @@ function looksPositional(token) {
309
289
  * silence, because a wrongly-suppressed call site is invisible forever while a
310
290
  * wrongly-reported one is one sweep read away:
311
291
  *
312
- * 1. Only consulted AFTER the `--repo`-present branch. A call that already
313
- * carries the flag is never this function's business, and asking here would
314
- * mean parsing which flags consume a value — `gh pr merge -R o/r 123` puts
315
- * its positional at index 4, behind `-R`'s argument. What that ordering
316
- * costs is named in § Blind spot at the top of this file: the `-R`-present,
317
- * positional-absent call — the one the CLI actually rejects — is invisible.
292
+ * 1. The bare census consults this AFTER the repo-flag branch. The separate
293
+ * rejected-flag check first removes option values from the token stream.
318
294
  * 2. ANY positional-looking token past the subcommand disarms the rule. A flag
319
295
  * VALUE (`--json state` → `state`) can be mistaken for a positional; that
320
296
  * mistake yields a finding, never a suppression.
@@ -331,6 +307,55 @@ function repoFlagBlockedByMissingPositional(cli, group, sub, rest) {
331
307
  return !rest.some(looksPositional);
332
308
  }
333
309
 
310
+ /**
311
+ * Known value-taking options of the six conditional gh pr subcommands.
312
+ * Unknown options retain the census's conservative positional heuristic;
313
+ * revisit this list when gh adds a value-taking option to these commands.
314
+ */
315
+ const PR_VALUE_FLAGS = new Set([
316
+ '-R', '--repo', '--json', '-q', '--jq', '-t', '--template', '-i', '--interval',
317
+ '--color', '-A', '--author-email', '-b', '--body', '-F', '--body-file',
318
+ '--subject', '--match-head-commit',
319
+ ]);
320
+
321
+ /** @param {string[]} args @returns {boolean} a repo option, not another option's value */
322
+ function repoFlagPresent(args) {
323
+ for (let index = 0; index < args.length; index += 1) {
324
+ const arg = args[index];
325
+ if (arg === '--') break;
326
+ if (arg === '-R' || arg === '--repo' || arg.startsWith('--repo=') || /^-R.+/.test(arg)) return true;
327
+ if (PR_VALUE_FLAGS.has(arg)) index += 1;
328
+ }
329
+ return false;
330
+ }
331
+
332
+ /** @param {string[]} args @returns {string[]} args without option values */
333
+ function withoutOptionValues(args) {
334
+ const rest = [];
335
+ for (let index = 0; index < args.length; index += 1) {
336
+ const arg = args[index];
337
+ if (arg === '--') return rest.concat(args.slice(index + 1));
338
+ if (PR_VALUE_FLAGS.has(arg)) index += 1;
339
+ else if (!arg.startsWith('-')) rest.push(arg);
340
+ }
341
+ return rest;
342
+ }
343
+
344
+ /** Strip shell comments at word boundaries, retaining quoted/escaped hashes.
345
+ * @param {string} text @returns {string}
346
+ */
347
+ function stripShellComment(text) {
348
+ let quote = '';
349
+ for (let index = 0; index < text.length; index += 1) {
350
+ const char = text[index];
351
+ if (char === '\\' && quote !== "'") { index += 1; continue; }
352
+ if (quote) { if (char === quote) quote = ''; continue; }
353
+ if (char === '"' || char === "'" || char === '`') { quote = char; continue; }
354
+ if (char === '#' && (index === 0 || /[\s;|&()]/.test(text[index - 1]))) return text.slice(0, index);
355
+ }
356
+ return text;
357
+ }
358
+
334
359
  /**
335
360
  * A `--repo`/`-R` occurrence. `--repo=x`, `-R x` and `-R"$SPEC"` all count;
336
361
  * `--repository` does not (the `\b` blocks it) and neither does a `-R` glued to
@@ -366,44 +391,60 @@ const COMMAND_RE = /(?:^|[|;&(]|\$\()\s*(?:[A-Za-z_][A-Za-z0-9_]*=\S*\s+)*(gh|gl
366
391
  * outer command's own, substitution-valued or not: `-R $(cat repo.txt)`
367
392
  * counts.
368
393
  *
369
- * The outer projection replaces each substitution with a single space rather
370
- * than deleting it, so that closing the gap in `-$(x)R` cannot weld a `-R` into
371
- * existence.
372
- *
373
- * Two named ceilings, both landing on today's verdict so neither is a
374
- * regression; revisit if a call site in this corpus ever takes either shape.
375
- * `$((` arithmetic expansion reads as a substitution plus a plain paren, so a
376
- * segment containing one is cut at its second `)` — exactly where the previous
377
- * `)`-stop cut it. Quote state is not tracked: `'$(x)'` is a literal string to a
378
- * real shell and a substitution to this walk, which reaches the same verdict
379
- * (the `-R` inside is not this command's flag) by a more generous route.
394
+ * The outer projection replaces each substitution with an opaque argument
395
+ * placeholder. Only placeholders in possible PR positions suppress a finding;
396
+ * option values and redirect operands are removed first. Quote and
397
+ * escape state are evaluated before command separators can end the segment.
380
398
  *
381
399
  * @param {string} rest text following the matched CLI token
382
- * @returns {{segment: string, outer: string}} full segment, and its depth-0 projection
400
+ * @returns {{segment: string, outer: string, unresolved: boolean}} full segment, and its depth-0 projection
383
401
  */
384
402
  function sliceInvocation(rest) {
385
403
  let segment = '';
386
404
  let outer = '';
387
405
  let depth = 0;
388
406
  let inBacktick = false;
407
+ let quote = '';
408
+ let unresolved = false;
389
409
 
390
410
  for (let index = 0; index < rest.length; index += 1) {
391
411
  const char = rest[index];
392
412
  const pair = rest.slice(index, index + 2);
393
413
 
394
414
  if (depth === 0 && !inBacktick) {
395
- if (pair === '||' || pair === '&&' || char === '|' || char === ';' || char === ')') break;
396
- if (pair === '$(') {
415
+ if (char === '\\' && quote !== "'") {
416
+ segment += rest.slice(index, index + 2);
417
+ outer += rest.slice(index, index + 2);
418
+ index += 1;
419
+ continue;
420
+ }
421
+ if (char === quote) {
422
+ quote = '';
423
+ segment += char;
424
+ outer += char;
425
+ continue;
426
+ }
427
+ if (!quote && (char === '"' || char === "'")) {
428
+ quote = char;
429
+ segment += char;
430
+ outer += char;
431
+ continue;
432
+ }
433
+ if (!quote && (pair === '||' || pair === '&&' ||
434
+ (char === '|' && rest[index - 1] !== '>') || char === ';' || char === ')')) break;
435
+ if (quote !== "'" && pair === '$(') {
436
+ unresolved = true;
397
437
  depth = 1;
398
438
  segment += pair;
399
- outer += ' ';
439
+ outer += '__SO_UNRESOLVED__';
400
440
  index += 1;
401
441
  continue;
402
442
  }
403
- if (char === '`') {
443
+ if (quote !== "'" && char === '`') {
444
+ unresolved = true;
404
445
  inBacktick = true;
405
446
  segment += char;
406
- outer += ' ';
447
+ outer += '__SO_UNRESOLVED__';
407
448
  continue;
408
449
  }
409
450
  segment += char;
@@ -425,7 +466,7 @@ function sliceInvocation(rest) {
425
466
  if (char === ')') depth -= 1;
426
467
  }
427
468
 
428
- return { segment, outer };
469
+ return { segment, outer, unresolved };
429
470
  }
430
471
 
431
472
  /** Snippet budget — see § stdout budget at the CLI guard. */
@@ -433,7 +474,7 @@ const SNIPPET_MAX = 200;
433
474
 
434
475
  /**
435
476
  * @typedef {{
436
- * kind: 'missing-repo-flag-doc' | 'missing-repo-flag-code' | 'tool-error',
477
+ * kind: 'missing-repo-flag-doc' | 'missing-repo-flag-code' | 'repo-flag-missing-positional' | 'tool-error',
437
478
  * file: string,
438
479
  * line: number,
439
480
  * cli: 'gh' | 'glab' | '-',
@@ -490,9 +531,11 @@ function clampSnippet(text) {
490
531
  *
491
532
  * @param {string} text the logical command line
492
533
  * @param {Tally} tally mutated in place
534
+ * @param {boolean} [rejected] collect rejected flags instead of missing flags
493
535
  * @returns {{cli: 'gh'|'glab', command: string, segment: string}[]} findings-worthy invocations
494
536
  */
495
- export function extractBareInvocations(text, tally) {
537
+ export function extractBareInvocations(text, tally, rejected = false) {
538
+ text = stripShellComment(text);
496
539
  /** @type {{cli: 'gh'|'glab', command: string, segment: string}[]} */
497
540
  const bare = [];
498
541
  COMMAND_RE.lastIndex = 0;
@@ -521,10 +564,20 @@ export function extractBareInvocations(text, tally) {
521
564
  tally.applicable += 1;
522
565
  // `outer`, never `segment`: a `-R` inside a command substitution belongs to
523
566
  // the inner command — see `sliceInvocation` job 2.
524
- if (REPO_FLAG_RE.test(outer)) {
567
+ const argv = rejected ? tokenizeCommand(outer) : [];
568
+ const args = [];
569
+ for (let index = 0; index < argv.length; index += 1) {
570
+ if (argv[index].redirect) index = redirectSpanEnd(argv, index);
571
+ else args.push(argv[index].text);
572
+ }
573
+ if (rejected ? repoFlagPresent(args.slice(2)) : REPO_FLAG_RE.test(outer)) {
525
574
  tally.withFlag += 1;
575
+ if (rejected && repoFlagBlockedByMissingPositional(cli, head, tokens[1], withoutOptionValues(args.slice(2)))) {
576
+ bare.push({ cli, command: `${cli} ${head} ${tokens[1]}`, segment });
577
+ }
526
578
  continue;
527
579
  }
580
+ if (rejected) continue;
528
581
  if (repoFlagBlockedByMissingPositional(cli, head, tokens[1], tokens.slice(2))) {
529
582
  tally.positionalRequired += 1;
530
583
  continue;
@@ -560,6 +613,73 @@ function joinContinuations(entries) {
560
613
  return joined;
561
614
  }
562
615
 
616
+ /**
617
+ * Read the heredoc delimiter word starting at `start` and apply shell quote
618
+ * removal: `\EOF`, `'EOF'`, `"EOF"` and `E"O"F` all name the terminator `EOF`.
619
+ * @param {string} text @param {number} start
620
+ * @returns {{word: string, end: number}} `end` = index after the word
621
+ */
622
+ function readDelimiterWord(text, start) {
623
+ let word = '';
624
+ let index = start;
625
+ for (; index < text.length; index += 1) {
626
+ const char = text[index];
627
+ if (/[\s;|&<>()]/.test(char)) break;
628
+ if (char === '\\') { index += 1; word += text[index] ?? ''; continue; }
629
+ if (char === "'" || char === '"') {
630
+ const close = text.indexOf(char, index + 1);
631
+ const stop = close === -1 ? text.length : close;
632
+ const quoted = text.slice(index + 1, stop);
633
+ word += char === '"' ? quoted.replace(/\\([\\"$`])/g, '$1') : quoted;
634
+ index = stop;
635
+ continue;
636
+ }
637
+ word += char;
638
+ }
639
+ return { word, end: index };
640
+ }
641
+
642
+ /**
643
+ * Index just past the `))` closing the arithmetic `((` at `start`, so a shift
644
+ * operator inside `$(( ))` / `(( ))` is never read as a heredoc.
645
+ * @param {string} text @param {number} start @returns {number}
646
+ */
647
+ function arithmeticEnd(text, start) {
648
+ let depth = 0;
649
+ for (let index = start; index < text.length; index += 1) {
650
+ if (text[index] === '(') depth += 1;
651
+ else if (text[index] === ')' && --depth === 0) return index + 1;
652
+ }
653
+ return text.length;
654
+ }
655
+
656
+ /**
657
+ * Find heredoc delimiters outside quoted/escaped shell text and arithmetic.
658
+ * Their bodies are unresolved data for the positional check, including an
659
+ * unterminated body.
660
+ * @param {string} text
661
+ * @returns {{word: string, stripTabs: boolean}[]}
662
+ */
663
+ function heredocDelimiters(text) {
664
+ const delimiters = [];
665
+ let quote = '';
666
+ text = stripShellComment(text);
667
+ for (let index = 0; index < text.length; index += 1) {
668
+ const char = text[index];
669
+ if (char === '\\' && quote !== "'") { index += 1; continue; }
670
+ if (quote) { if (char === quote) quote = ''; continue; }
671
+ if (char === '"' || char === "'" || char === '`') { quote = char; continue; }
672
+ if (text.startsWith('((', index)) { index = arithmeticEnd(text, index) - 1; continue; }
673
+ if (text.startsWith('<<<', index)) { index += 2; continue; }
674
+ if (!text.startsWith('<<', index)) continue;
675
+ const head = /^<<(-?)[ \t]*/.exec(text.slice(index));
676
+ const { word, end } = readDelimiterWord(text, index + head[0].length);
677
+ if (word) delimiters.push({ word, stripTabs: head[1] === '-' });
678
+ index = Math.max(index + 1, end - 1);
679
+ }
680
+ return delimiters;
681
+ }
682
+
563
683
  /**
564
684
  * Scan a markdown file: fence state machine → shell fences only → drop shell
565
685
  * comments → join continuations → classify.
@@ -567,30 +687,50 @@ function joinContinuations(entries) {
567
687
  * @param {string} relative repo-relative path
568
688
  * @param {string} body file content
569
689
  * @param {Tally} tally mutated in place
690
+ * @param {boolean} [rejected] collect rejected flags instead of missing flags
570
691
  * @returns {Finding[]}
571
692
  */
572
- export function scanMarkdown(relative, body, tally) {
693
+ export function scanMarkdown(relative, body, tally, rejected = false) {
573
694
  /** @type {Finding[]} */
574
695
  const findings = [];
575
696
  /** @type {{line: number, text: string}[]} */
576
697
  const shellLines = [];
698
+ /** @type {{word: string, stripTabs: boolean}[]} */
699
+ let pendingHeredocs = [];
700
+ // Delimiters of the logical line still being continued: a body starts only
701
+ // after the line that ends the `\`-continued command (same test as joinContinuations).
702
+ /** @type {{word: string, stripTabs: boolean}[]} */
703
+ let lineHeredocs = [];
704
+ let previousLine = 0;
577
705
 
578
706
  forEachLine(body, (raw, { lineNumber, inFence, lang }) => {
579
707
  if (!inFence || !SHELL_LANGS.has(lang)) return;
708
+ // Fence markers are omitted by forEachLine; a gap resets shell context.
709
+ if (lineNumber !== previousLine + 1) { pendingHeredocs = []; lineHeredocs = []; }
710
+ previousLine = lineNumber;
711
+ if (rejected && pendingHeredocs.length) {
712
+ const pending = pendingHeredocs[0];
713
+ if ((pending.stripTabs ? raw.replace(/^\t+/, '') : raw) === pending.word) pendingHeredocs.shift();
714
+ return;
715
+ }
580
716
  const stripped = raw.replace(/^\s*[$❯>]\s+/, '');
581
717
  if (/^\s*#/.test(stripped)) return;
718
+ if (rejected) {
719
+ lineHeredocs.push(...heredocDelimiters(stripped));
720
+ if (!/\\\s*$/.test(stripped)) { pendingHeredocs = lineHeredocs; lineHeredocs = []; }
721
+ }
582
722
  shellLines.push({ line: lineNumber, text: stripped });
583
723
  });
584
724
 
585
725
  for (const entry of joinContinuations(shellLines)) {
586
- for (const hit of extractBareInvocations(entry.text, tally)) {
726
+ for (const hit of extractBareInvocations(entry.text, tally, rejected)) {
587
727
  findings.push({
588
- kind: 'missing-repo-flag-doc',
728
+ kind: rejected ? 'repo-flag-missing-positional' : 'missing-repo-flag-doc',
589
729
  file: relative,
590
730
  line: entry.line,
591
731
  cli: hit.cli,
592
732
  command: hit.command,
593
- message:
733
+ message: rejected ? rejectedFlagMessage(hit.command) :
594
734
  `\`${hit.command}\` runs without --repo/-R — the target project comes from the ambient ` +
595
735
  `cwd remote: ${clampSnippet(`${hit.cli}${hit.segment}`)}`,
596
736
  });
@@ -628,8 +768,32 @@ export function scanArgvCalls(body) {
628
768
  }
629
769
  let depth = 0;
630
770
  let end = -1;
771
+ let quote = '';
772
+ let uncommented = '';
631
773
  for (let index = 0; index < after.length; index += 1) {
632
774
  const char = after[index];
775
+ if (quote) {
776
+ uncommented += char;
777
+ if (char === '\\') { uncommented += after[index + 1] ?? ''; index += 1; }
778
+ else if (char === quote) quote = '';
779
+ continue;
780
+ }
781
+ if (after.startsWith('//', index)) {
782
+ const newline = after.indexOf('\n', index + 2);
783
+ if (newline === -1) break;
784
+ uncommented += ' ';
785
+ index = newline;
786
+ continue;
787
+ }
788
+ if (after.startsWith('/*', index)) {
789
+ const close = after.indexOf('*/', index + 2);
790
+ if (close === -1) break;
791
+ uncommented += ' ';
792
+ index = close + 1;
793
+ continue;
794
+ }
795
+ uncommented += char;
796
+ if (char === "'" || char === '"' || char === '`') { quote = char; continue; }
633
797
  if (char === '[') depth += 1;
634
798
  else if (char === ']') {
635
799
  depth -= 1;
@@ -643,12 +807,25 @@ export function scanArgvCalls(body) {
643
807
  unresolved += 1;
644
808
  continue;
645
809
  }
646
- const inner = after.slice(1, end);
810
+ const inner = uncommented.slice(1, -1);
647
811
  if (inner.includes('...') || /\$\{/.test(inner)) {
648
812
  unresolved += 1;
649
813
  continue;
650
814
  }
651
- const args = [...inner.matchAll(/['"]([^'"]*)['"]/g)].map((entry) => entry[1]);
815
+ // Consume the entire array, never just its quoted fragments. Escaped or
816
+ // computed strings are deliberately unresolved rather than partly decoded.
817
+ const literal = /\s*(?:'([^'\\]*)'|"([^"\\]*)")\s*(?:,|$)/gy;
818
+ const args = [];
819
+ let consumed = 0;
820
+ let item;
821
+ while ((item = literal.exec(inner)) !== null) {
822
+ args.push(item[1] ?? item[2]);
823
+ consumed = literal.lastIndex;
824
+ }
825
+ if (inner.slice(consumed).trim()) {
826
+ unresolved += 1;
827
+ continue;
828
+ }
652
829
  literals.push({ index: matched.index, cli: /** @type {'gh'|'glab'} */ (matched[1]), args });
653
830
  }
654
831
  return { literals, unresolved };
@@ -662,9 +839,10 @@ export function scanArgvCalls(body) {
662
839
  * @param {string} relative repo-relative path
663
840
  * @param {string} body file content
664
841
  * @param {Tally} tally mutated in place
842
+ * @param {boolean} [rejected] collect rejected flags instead of missing flags
665
843
  * @returns {{findings: Finding[], unresolvedArgv: number}}
666
844
  */
667
- export function scanCode(relative, body, tally) {
845
+ export function scanCode(relative, body, tally, rejected = false) {
668
846
  /** @type {Finding[]} */
669
847
  const findings = [];
670
848
  const lines = body.split('\n');
@@ -681,14 +859,14 @@ export function scanCode(relative, body, tally) {
681
859
  const closeAt = raw.indexOf(quote, contentStart);
682
860
  const literal = closeAt === -1 ? raw.slice(contentStart) : raw.slice(contentStart, closeAt);
683
861
  if (literal.includes('${')) continue;
684
- for (const hit of extractBareInvocations(literal.trim(), tally)) {
862
+ for (const hit of extractBareInvocations(literal.trim(), tally, rejected)) {
685
863
  findings.push({
686
- kind: 'missing-repo-flag-code',
864
+ kind: rejected ? 'repo-flag-missing-positional' : 'missing-repo-flag-code',
687
865
  file: relative,
688
866
  line: index + 1,
689
867
  cli: hit.cli,
690
868
  command: hit.command,
691
- message:
869
+ message: rejected ? rejectedFlagMessage(hit.command) :
692
870
  `\`${hit.command}\` in a shell-command string runs without --repo/-R — the target project ` +
693
871
  `comes from the ambient cwd remote: ${clampSnippet(`${hit.cli}${hit.segment}`)}`,
694
872
  });
@@ -712,11 +890,13 @@ export function scanCode(relative, body, tally) {
712
890
  continue;
713
891
  }
714
892
  tally.applicable += 1;
715
- if (call.args.some((arg) => arg === '--repo' || arg === '-R' || arg.startsWith('--repo='))) {
716
- tally.withFlag += 1;
717
- continue;
718
- }
719
- if (repoFlagBlockedByMissingPositional(call.cli, head, call.args[1], call.args.slice(2))) {
893
+ const hasRepoFlag = rejected ? repoFlagPresent(call.args.slice(2)) :
894
+ call.args.some((arg) => arg === '--repo' || arg === '-R' || arg.startsWith('--repo='));
895
+ if (hasRepoFlag) tally.withFlag += 1;
896
+ if (rejected) {
897
+ if (!hasRepoFlag || !repoFlagBlockedByMissingPositional(call.cli, head, call.args[1], withoutOptionValues(call.args.slice(2)))) continue;
898
+ } else if (hasRepoFlag) continue;
899
+ if (!rejected && repoFlagBlockedByMissingPositional(call.cli, head, call.args[1], call.args.slice(2))) {
720
900
  tally.positionalRequired += 1;
721
901
  continue;
722
902
  }
@@ -727,12 +907,12 @@ export function scanCode(relative, body, tally) {
727
907
  const line = body.slice(0, call.index).split('\n').length;
728
908
  const second = call.args[1] && !call.args[1].startsWith('-') ? ` ${call.args[1]}` : '';
729
909
  findings.push({
730
- kind: 'missing-repo-flag-code',
910
+ kind: rejected ? 'repo-flag-missing-positional' : 'missing-repo-flag-code',
731
911
  file: relative,
732
912
  line,
733
913
  cli: call.cli,
734
914
  command: `${call.cli} ${head}${second}`,
735
- message:
915
+ message: rejected ? rejectedFlagMessage(`${call.cli} ${head}${second}`) :
736
916
  `\`${call.cli} ${head}${second}\` argv array carries no --repo/-R — the target project comes ` +
737
917
  `from the ambient cwd remote: ${clampSnippet(call.args.join(' '))}`,
738
918
  });
@@ -741,10 +921,16 @@ export function scanCode(relative, body, tally) {
741
921
  return { findings, unresolvedArgv: unresolved };
742
922
  }
743
923
 
924
+ /** @param {string} command @returns {string} */
925
+ function rejectedFlagMessage(command) {
926
+ return `\`${command}\`: --repo/-R breaks this call — name the PR positional or drop the flag`;
927
+ }
928
+
744
929
  /**
745
930
  * Run the full census.
746
931
  *
747
932
  * @param {string} pluginRoot absolute plugin root
933
+ * @param {{rejected?: boolean}} [options] separate rejected-flag classification
748
934
  * @returns {{
749
935
  * ok: boolean,
750
936
  * summary: {
@@ -758,7 +944,7 @@ export function scanCode(relative, body, tally) {
758
944
  * toolError: boolean,
759
945
  * }}
760
946
  */
761
- export function inspectVcsRepoFlag(pluginRoot) {
947
+ export function inspectVcsRepoFlag(pluginRoot, { rejected = false } = {}) {
762
948
  /** @type {Finding[]} */
763
949
  const findings = [];
764
950
  /** @type {Tally} */
@@ -839,10 +1025,10 @@ export function inspectVcsRepoFlag(pluginRoot) {
839
1025
  }
840
1026
  if (DOC_EXTENSIONS.includes(path.extname(absolute))) {
841
1027
  docFiles += 1;
842
- findings.push(...scanMarkdown(relative, body, tally));
1028
+ findings.push(...scanMarkdown(relative, body, tally, rejected));
843
1029
  } else {
844
1030
  codeFiles += 1;
845
- const scanned = scanCode(relative, body, tally);
1031
+ const scanned = scanCode(relative, body, tally, rejected);
846
1032
  findings.push(...scanned.findings);
847
1033
  unresolvedArgv += scanned.unresolvedArgv;
848
1034
  }
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env node
2
+ // Usage: check-workflow-continue-on-error.mjs <plugin-root>
3
+ import { existsSync, readdirSync, readFileSync } from 'node:fs';
4
+ import { join } from 'node:path';
5
+ import yaml from 'js-yaml';
6
+ import { isMainModule } from '../is-main-module.mjs';
7
+
8
+ function inspectWorkflow(yamlText, relPath) {
9
+ const workflow = yaml.load(yamlText, { schema: yaml.CORE_SCHEMA });
10
+ const jobs = Object.entries(workflow?.jobs ?? {});
11
+ const findings = jobs
12
+ .filter(([, job]) => job !== null && job !== undefined && Object.hasOwn(job, 'continue-on-error')
13
+ && job['continue-on-error'] !== false)
14
+ .map(([job, config]) => ({ file: relPath, job, value: config['continue-on-error'] }));
15
+ return { findings, jobCount: jobs.length };
16
+ }
17
+
18
+ /** Find job-level tolerance that can leave a failed check run; invalid YAML throws. */
19
+ export function findJobLevelContinueOnError(yamlText, relPath) {
20
+ return inspectWorkflow(yamlText, relPath).findings;
21
+ }
22
+
23
+ function run(pluginRoot) {
24
+ console.log('--- Check: workflow job-level continue-on-error ---');
25
+ const workflowsDir = join(pluginRoot, '.github', 'workflows');
26
+ let passed = 0;
27
+ let failed = 0;
28
+ if (!existsSync(workflowsDir)) {
29
+ console.log(' PASS: no workflows');
30
+ passed++;
31
+ } else {
32
+ for (const entry of readdirSync(workflowsDir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
33
+ if (!entry.isFile() || !/\.ya?ml$/.test(entry.name)) continue;
34
+ const relPath = `.github/workflows/${entry.name}`;
35
+ try {
36
+ const { findings, jobCount } = inspectWorkflow(readFileSync(join(workflowsDir, entry.name), 'utf8'), relPath);
37
+ for (const { job } of findings) {
38
+ console.log(` FAIL: ${relPath}: job '${job}' sets job-level continue-on-error — its check run still concludes failure; move it to the step(s)`);
39
+ failed++;
40
+ }
41
+ if (findings.length === 0) {
42
+ console.log(` PASS: ${relPath}: no job-level continue-on-error (${jobCount} jobs)`);
43
+ passed++;
44
+ }
45
+ } catch (error) {
46
+ console.log(` FAIL: ${relPath}: ${error.message}`);
47
+ failed++;
48
+ }
49
+ }
50
+ }
51
+ console.log(`\nResults: ${passed} passed, ${failed} failed`);
52
+ return failed > 0 ? 1 : 0;
53
+ }
54
+
55
+ const isMain = isMainModule(import.meta.url);
56
+ if (isMain) {
57
+ const root = process.argv[2];
58
+ if (!root) {
59
+ console.error('Usage: check-workflow-continue-on-error.mjs <plugin-root>');
60
+ process.exit(1);
61
+ }
62
+ process.exit(run(root));
63
+ }
@@ -103,7 +103,9 @@ const DEFAULT_DEPS = {
103
103
  function parseNames(raw, label, d) {
104
104
  let parsed;
105
105
  try {
106
- parsed = JSON.parse(raw);
106
+ // #1613: Windows PowerShell 5.1 (`Set-Content -Encoding utf8`) writes a UTF-8 BOM,
107
+ // which JSON.parse rejects — and a rejected names file silently switches CP11 off.
108
+ parsed = JSON.parse(raw.charCodeAt(0) === 0xfeff ? raw.slice(1) : raw);
107
109
  } catch (err) {
108
110
  // Fix 3 (security-reviewer): NEVER embed err.message — V8's JSON.parse error
109
111
  // text echoes the first ~10 chars of the file body, which for a confidential-