@starci/hfs 4.7.31 → 4.7.33

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 (473) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +13 -6
  3. package/emit/contracts.mjs +4 -3
  4. package/emit/form-bounds-reader.ts.partial +58 -0
  5. package/emit/form-bounds.mjs +155 -0
  6. package/emit/graphql-input-bounds.mjs +97 -0
  7. package/emit/input-bounds.mjs +44 -0
  8. package/emit/operation-schemas.mjs +137 -0
  9. package/emit/operations-worker.mjs +2 -2
  10. package/emit/operations.mjs +52 -16
  11. package/emit/schema-worker.mjs +17 -4
  12. package/emit/type-schema.mjs +17 -5
  13. package/package.json +9 -2
  14. package/release/gate.mjs +18 -23
  15. package/release/index.mjs +1 -1
  16. package/release/push.mjs +4 -5
  17. package/runtime/engine/admission.mjs +331 -0
  18. package/runtime/engine/canonical-json.mjs +14 -0
  19. package/runtime/engine/db/blob.mjs +380 -0
  20. package/runtime/engine/db/event-compact.mjs +73 -0
  21. package/runtime/engine/db/ledger-open.mjs +38 -0
  22. package/runtime/engine/db/ledger-paths.mjs +87 -0
  23. package/runtime/engine/db/ledger.mjs +1068 -0
  24. package/runtime/engine/db/machine-connection.mjs +150 -0
  25. package/runtime/engine/db/machine-open.mjs +60 -0
  26. package/runtime/engine/db/machine-schema.mjs +105 -0
  27. package/runtime/engine/db/machine.mjs +1317 -0
  28. package/runtime/engine/db/provider-reservations.mjs +127 -0
  29. package/runtime/engine/db/provision-authority.mjs +76 -0
  30. package/runtime/engine/db/provision-lock.mjs +29 -0
  31. package/runtime/engine/db/provision-operations.mjs +146 -0
  32. package/runtime/engine/db/ref-value.mjs +67 -0
  33. package/runtime/engine/db/schema/machine.sql +920 -0
  34. package/runtime/engine/db/schema/runtime.sql +1113 -0
  35. package/runtime/engine/refuse.mjs +11 -0
  36. package/runtime/knowledge/hfs/canon-pins.yaml +65 -9
  37. package/runtime/knowledge/hfs/design-truths.yaml +149 -0
  38. package/runtime/knowledge/hfs/fe-stack.yaml +23 -5
  39. package/runtime/knowledge/hfs/rules.yaml +269 -48
  40. package/runtime/knowledge/hfs/slots.yaml +113 -53
  41. package/runtime/knowledge/patterns/be/api.yaml +46 -20
  42. package/runtime/knowledge/patterns/be/event-bus.yaml +19 -12
  43. package/runtime/knowledge/patterns/be/idempotency.yaml +265 -0
  44. package/runtime/knowledge/patterns/be/jobs.yaml +17 -12
  45. package/runtime/knowledge/patterns/be/projections.yaml +2 -2
  46. package/runtime/knowledge/patterns/be/queues.yaml +57 -22
  47. package/runtime/knowledge/patterns/be/reactors.yaml +1 -1
  48. package/runtime/knowledge/patterns/be/realtime.yaml +326 -27
  49. package/runtime/knowledge/patterns/be/saga.yaml +2 -2
  50. package/runtime/knowledge/sonar-gate.yaml +1 -1
  51. package/runtime/modules/kernel/command-policy.yaml +249 -0
  52. package/runtime/modules/kernel/failure-codes.yaml +211 -52
  53. package/runtime/modules/kernel/hook-input.yaml +6 -0
  54. package/runtime/modules/kernel/protected-zone.yaml +81 -0
  55. package/runtime/modules/kernel/removed-vocabulary.yaml +62 -0
  56. package/runtime/modules/kernel/roles.yaml +405 -0
  57. package/runtime/modules/models/registry.yaml +3 -1
  58. package/runtime/modules/models/runtimes.yaml +2 -0
  59. package/runtime/modules/schemas/application-stacks.schema.yaml +10 -2
  60. package/runtime/packages/cli/src/catalog.generated.mjs +46 -59
  61. package/runtime/packages/grammar/scripts/build-stamp.mjs +98 -0
  62. package/runtime/scripts/agent/codex-app-server.mjs +79 -0
  63. package/runtime/scripts/agent/codex-mirror-source.mjs +34 -0
  64. package/runtime/scripts/agent/host-agents.mjs +80 -0
  65. package/runtime/scripts/agent/launch-trust-policy.mjs +53 -0
  66. package/runtime/scripts/agent/trust-launch.mjs +191 -0
  67. package/runtime/scripts/agent/trust.mjs +523 -0
  68. package/runtime/scripts/api/fs/rename-over.mjs +21 -0
  69. package/runtime/scripts/api/fs/temp-path.mjs +8 -0
  70. package/runtime/scripts/api/fs/write-json-file.mjs +10 -0
  71. package/runtime/scripts/api/git/diff-names.mjs +8 -0
  72. package/runtime/scripts/api/git/head-time.mjs +10 -0
  73. package/runtime/scripts/api/git/index-lock.mjs +78 -0
  74. package/runtime/scripts/api/git/is-ancestor.mjs +5 -0
  75. package/runtime/scripts/api/git/log.mjs +5 -0
  76. package/runtime/scripts/api/git/merge-base-query.mjs +5 -0
  77. package/runtime/scripts/api/git/merge-tree.mjs +5 -0
  78. package/runtime/scripts/api/git/rev-list.mjs +5 -0
  79. package/runtime/scripts/api/git/rev-parse.mjs +13 -0
  80. package/runtime/scripts/api/git/status-query.mjs +5 -0
  81. package/runtime/scripts/api/git/worktree-list-query.mjs +5 -0
  82. package/runtime/scripts/api/node/spawn-node.mjs +6 -0
  83. package/runtime/scripts/api/npm/dist-tag.mjs +8 -0
  84. package/runtime/scripts/api/npm/lib.mjs +32 -0
  85. package/runtime/scripts/api/npm/pack-dry-run.mjs +9 -0
  86. package/runtime/scripts/api/npm/run-npm.mjs +6 -0
  87. package/runtime/scripts/api/npm/view.mjs +30 -0
  88. package/runtime/scripts/api/npm/whoami.mjs +8 -0
  89. package/runtime/scripts/api/process/hide-child-windows.mjs +31 -0
  90. package/runtime/scripts/api/process/kill-tree.mjs +22 -0
  91. package/runtime/scripts/api/process/lib.mjs +125 -0
  92. package/runtime/scripts/api/process/probe-guard-command.mjs +74 -0
  93. package/runtime/scripts/api/process/process-list.mjs +28 -0
  94. package/runtime/scripts/api/process/system-tool.mjs +37 -0
  95. package/runtime/scripts/gates/canon-digest.mjs +81 -0
  96. package/runtime/scripts/gates/canon-pins.mjs +16 -0
  97. package/runtime/scripts/gates/commit-proof.mjs +53 -0
  98. package/runtime/scripts/gates/gate-input.mjs +98 -0
  99. package/runtime/scripts/gates/gate.mjs +669 -0
  100. package/runtime/scripts/gates/grammar-dist.mjs +206 -0
  101. package/runtime/scripts/gates/runtime-package-selection.mjs +27 -0
  102. package/runtime/scripts/gates/test-world-run.mjs +254 -0
  103. package/runtime/scripts/gates/type-impact.mjs +277 -0
  104. package/runtime/scripts/gates/type-program.mjs +113 -0
  105. package/runtime/scripts/guards/assigned-command.mjs +12 -0
  106. package/runtime/scripts/guards/call-rights.mjs +47 -0
  107. package/runtime/scripts/guards/command-guard.mjs +468 -0
  108. package/runtime/scripts/guards/command-policy.mjs +342 -0
  109. package/runtime/scripts/guards/critic-reach.mjs +82 -0
  110. package/runtime/scripts/guards/deps-guard.mjs +150 -0
  111. package/runtime/scripts/guards/env-dump-verdict.mjs +89 -0
  112. package/runtime/scripts/guards/git-pathspec.mjs +225 -0
  113. package/runtime/scripts/guards/git-policy.mjs +355 -0
  114. package/runtime/scripts/guards/guards-root.mjs +27 -0
  115. package/runtime/scripts/guards/hook-install.mjs +343 -0
  116. package/runtime/scripts/guards/hook-io.mjs +76 -0
  117. package/runtime/scripts/guards/hook-verdict.mjs +12 -0
  118. package/runtime/scripts/guards/install-verdict.mjs +41 -0
  119. package/runtime/scripts/guards/kernel-seat.mjs +63 -0
  120. package/runtime/scripts/guards/launch-verdict.mjs +74 -0
  121. package/runtime/scripts/guards/op-starci.mjs +16 -0
  122. package/runtime/scripts/guards/op-write-scope.mjs +27 -0
  123. package/runtime/scripts/guards/orca-self-lifecycle.mjs +39 -0
  124. package/runtime/scripts/guards/process-kill-verdict.mjs +106 -0
  125. package/runtime/scripts/guards/protected-zone.mjs +56 -0
  126. package/runtime/scripts/guards/refusals.mjs +27 -0
  127. package/runtime/scripts/guards/release-definition.mjs +185 -0
  128. package/runtime/scripts/guards/release-push-gate.mjs +72 -0
  129. package/runtime/scripts/guards/release-record.mjs +58 -0
  130. package/runtime/scripts/guards/release-suite-mode.mjs +15 -0
  131. package/runtime/scripts/guards/rights.mjs +268 -0
  132. package/runtime/scripts/guards/seat-tools.mjs +47 -0
  133. package/runtime/scripts/guards/shell-commands.mjs +328 -0
  134. package/runtime/scripts/guards/supervisor-seat.mjs +80 -0
  135. package/runtime/scripts/guards/verdict-process.mjs +81 -0
  136. package/runtime/scripts/hfs/architecture/dead-exports.mjs +10 -10
  137. package/runtime/scripts/hfs/architecture/function-spec-providers.mjs +1 -1
  138. package/runtime/scripts/hfs/architecture/operation-contract-roots.mjs +53 -0
  139. package/runtime/scripts/hfs/architecture/reachability.mjs +1 -1
  140. package/runtime/scripts/hfs/architecture/sql-owner.mjs +17 -6
  141. package/runtime/scripts/hfs/architecture/sql-token-forms.mjs +105 -0
  142. package/runtime/scripts/hfs/architecture/sql-tokens.mjs +38 -52
  143. package/runtime/scripts/hfs/architecture/supabase-keys.mjs +261 -0
  144. package/runtime/scripts/hfs/architecture/transport-owner.mjs +49 -3
  145. package/runtime/scripts/hfs/architecture/typescript.mjs +1 -1
  146. package/runtime/scripts/hfs/check.mjs +6 -4
  147. package/runtime/scripts/hfs/coverage-scope.mjs +28 -1
  148. package/runtime/scripts/hfs/db-restamp-verb.mjs +54 -14
  149. package/runtime/scripts/hfs/declaration-shape.mjs +4 -4
  150. package/runtime/scripts/hfs/declaration-slots.mjs +14 -3
  151. package/runtime/scripts/hfs/manifest-shape.mjs +10 -6
  152. package/runtime/scripts/hfs/rule-params-shape.mjs +18 -6
  153. package/runtime/scripts/hfs/rules/a15-findings.mjs +4 -1
  154. package/runtime/scripts/hfs/rules/bypass-markers.mjs +22 -7
  155. package/runtime/scripts/hfs/rules/database-migrations.mjs +69 -14
  156. package/runtime/scripts/hfs/rules/database-queue-sources.mjs +9 -7
  157. package/runtime/scripts/hfs/rules/database-queues.mjs +68 -18
  158. package/runtime/scripts/hfs/rules/edition.mjs +56 -12
  159. package/runtime/scripts/hfs/rules/fe-contract-documents.mjs +1 -10
  160. package/runtime/scripts/hfs/rules/fe-stack.mjs +12 -7
  161. package/runtime/scripts/hfs/rules/lite-proof-lane.mjs +1 -1
  162. package/runtime/scripts/hfs/rules/stacks-lite-backing.mjs +75 -9
  163. package/runtime/scripts/hfs/rules/stacks-runtime.mjs +1 -1
  164. package/runtime/scripts/hfs/rules/stacks-swarm.mjs +15 -4
  165. package/runtime/scripts/hfs/rules/text-integrity.mjs +73 -0
  166. package/runtime/scripts/hfs/runtime-rules/hook-shape.mjs +74 -0
  167. package/runtime/scripts/hfs/runtime-rules/release-notes.mjs +44 -0
  168. package/runtime/scripts/hfs/sealed-members.mjs +3 -3
  169. package/runtime/scripts/hfs/slot-side-problems.mjs +3 -3
  170. package/runtime/scripts/hfs/slots.mjs +2 -1
  171. package/runtime/scripts/hfs/stack-deploy-config.mjs +22 -5
  172. package/runtime/scripts/hfs/stacks-gate.mjs +28 -7
  173. package/runtime/scripts/hfs/view.mjs +2 -1
  174. package/runtime/scripts/lib/app-data.mjs +11 -0
  175. package/runtime/scripts/lib/bypass-markers.mjs +272 -75
  176. package/runtime/scripts/lib/check-format.mjs +7 -0
  177. package/runtime/scripts/lib/cli-arg.mjs +120 -0
  178. package/runtime/scripts/lib/css-rules.mjs +220 -0
  179. package/runtime/scripts/lib/dom-tree.mjs +4 -0
  180. package/runtime/scripts/lib/escape.mjs +16 -0
  181. package/runtime/scripts/lib/event-items.mjs +22 -0
  182. package/runtime/scripts/lib/failure-code-template-errors.mjs +161 -0
  183. package/runtime/scripts/lib/git-dir.mjs +120 -0
  184. package/runtime/scripts/lib/grammar-vendor-parity.mjs +377 -0
  185. package/runtime/scripts/lib/guard-command.mjs +24 -0
  186. package/runtime/scripts/lib/hook-limits.mjs +5 -0
  187. package/runtime/scripts/lib/interrupt-cleanup.mjs +38 -0
  188. package/runtime/scripts/lib/js-lex.mjs +200 -0
  189. package/runtime/scripts/lib/json-formats.mjs +0 -1
  190. package/runtime/scripts/lib/language.mjs +94 -1
  191. package/runtime/scripts/lib/loosening-budgets.mjs +44 -0
  192. package/runtime/scripts/lib/loosening-config.mjs +361 -0
  193. package/runtime/scripts/lib/loosening-state.mjs +223 -0
  194. package/runtime/scripts/lib/menu-parts.mjs +11 -0
  195. package/runtime/scripts/lib/normalize.mjs +25 -0
  196. package/runtime/scripts/lib/op-gate.mjs +15 -0
  197. package/runtime/scripts/lib/redact.mjs +208 -0
  198. package/runtime/scripts/lib/runtime-location.mjs +21 -0
  199. package/runtime/scripts/lib/runtime-package-inventory.mjs +21 -0
  200. package/runtime/scripts/lib/runtime-package-manifests.mjs +84 -0
  201. package/runtime/scripts/lib/runtime-publication-tags.mjs +99 -0
  202. package/runtime/scripts/lib/seat-env.mjs +43 -0
  203. package/runtime/scripts/lib/secret-patterns.mjs +58 -9
  204. package/runtime/scripts/lib/shell-source.mjs +4 -0
  205. package/runtime/scripts/lib/source-phrases.mjs +34 -0
  206. package/runtime/scripts/lib/sqlite.mjs +21 -0
  207. package/runtime/scripts/lib/swarm-stack.mjs +2 -1
  208. package/runtime/scripts/lib/tar-files.mjs +39 -0
  209. package/runtime/scripts/lib/ts-analysis.mjs +10 -0
  210. package/runtime/scripts/lib/worktree-exclude.mjs +35 -0
  211. package/runtime/scripts/lib/worktree-record.mjs +14 -0
  212. package/runtime/scripts/machine/artifact-hold.mjs +52 -0
  213. package/runtime/scripts/machine/ask-receipts.mjs +113 -0
  214. package/runtime/scripts/machine/evidence-store.mjs +223 -0
  215. package/runtime/scripts/machine/home.mjs +216 -0
  216. package/runtime/scripts/machine/host-lock.mjs +184 -0
  217. package/runtime/scripts/machine/lock-recovery.mjs +55 -0
  218. package/runtime/scripts/machine/roles-contract.mjs +90 -0
  219. package/runtime/scripts/machine/runtime-change.mjs +20 -0
  220. package/runtime/scripts/machine/workflow-tree.mjs +122 -0
  221. package/runtime/scripts/reconciler/checklist-items.mjs +7 -0
  222. package/runtime/scripts/reconciler/guard-row.mjs +25 -0
  223. package/runtime/scripts/work/asset-slot.mjs +284 -0
  224. package/runtime/scripts/work/brand/brand-colour.mjs +125 -0
  225. package/runtime/scripts/work/brand/brand-tokens.mjs +167 -0
  226. package/runtime/scripts/work/brand/brand-value-source.mjs +106 -0
  227. package/runtime/scripts/work/brand/brand.mjs +892 -0
  228. package/runtime/scripts/work/draw/draw-dna-anatomy.mjs +60 -0
  229. package/runtime/scripts/work/draw/draw-dna-findings.mjs +256 -0
  230. package/runtime/scripts/work/draw/draw-dna-selector.mjs +38 -0
  231. package/runtime/scripts/work/draw/draw-dna.mjs +483 -0
  232. package/runtime/scripts/work/draw/draw-layer.mjs +305 -0
  233. package/runtime/scripts/work/draw/draw-rationale-page.mjs +237 -0
  234. package/runtime/scripts/work/draw/draw-rationale-ref.mjs +14 -0
  235. package/runtime/scripts/work/draw/draw-rationale.mjs +353 -0
  236. package/runtime/scripts/work/draw/draw-source.mjs +412 -0
  237. package/runtime/scripts/work/draw/draw-taste.mjs +278 -0
  238. package/runtime/scripts/work/draw/draw-typecheck.mjs +72 -0
  239. package/runtime/scripts/work/draw/rationale-findings.mjs +205 -0
  240. package/runtime/scripts/work/draw-grammar.mjs +175 -0
  241. package/runtime/scripts/work/draw-render-page.mjs +188 -0
  242. package/runtime/scripts/work/draw-render.mjs +626 -0
  243. package/runtime/scripts/work/grammar-proposal.mjs +279 -0
  244. package/runtime/scripts/work/png.mjs +237 -0
  245. package/runtime/scripts/work/render-tools.mjs +64 -0
  246. package/runtime/scripts/work/work-io.mjs +119 -0
  247. package/scaffold/add-inbox-lite.mjs +2 -2
  248. package/scaffold/add-plan.mjs +40 -13
  249. package/scaffold/add-queue-lite.mjs +372 -0
  250. package/scaffold/add.mjs +5 -2
  251. package/scaffold/app.mjs +1 -2
  252. package/scaffold/edition-gate.mjs +11 -7
  253. package/scaffold/lite-exports.mjs +10 -0
  254. package/scaffold/pattern-file-slots.mjs +33 -0
  255. package/scaffold/test-world.mjs +1 -1
  256. package/scaffold/yaml-block-edit.mjs +117 -0
  257. package/src/main.mjs +5 -4
  258. package/stack/deploy.mjs +7 -3
  259. package/stack/post-deploy.mjs +48 -0
  260. package/sync/index.mjs +10 -0
  261. package/templates/app/ci-workflows/github/workflows/stack-deploy.yml +1 -1
  262. package/templates/app/ci-workflows-lite/github/workflows/stack-deploy.yml +1 -1
  263. package/templates/app/codegen-graphql.mjs.partial +59 -12
  264. package/templates/app/hooks/husky/pre-commit +2 -2
  265. package/templates/app/hooks/husky/pre-push +10 -3
  266. package/templates/app/package-scripts/redis-dev.json +3 -0
  267. package/templates/app/package-scripts-lite/package.json +1 -0
  268. package/templates/app/skeleton/.starcistacks/application-stacks.yaml +36 -8
  269. package/templates/app/skeleton/.starcistacks/dev/README.md +1 -1
  270. package/templates/app/skeleton/.starcistacks/production/README.md +10 -9
  271. package/templates/app/skeleton/.starcistacks/production/infra/swarm/postgres-init.sh +21 -0
  272. package/templates/app/skeleton/.starcistacks/production/infra/swarm/recovery/restore-proof.md +1 -1
  273. package/templates/app/skeleton/.starcistacks/production/infra/swarm/releases/images.lock.yaml +1 -0
  274. package/templates/app/skeleton/.starcistacks/production/infra/swarm/stack.yaml +47 -5
  275. package/templates/app/skeleton/.starcistacks/production/runtime/env/KEYS.md +5 -3
  276. package/templates/app/skeleton/README.md +2 -0
  277. package/templates/app/skeleton-lite/.starcistacks/application-stacks.yaml +5 -0
  278. package/templates/app/skeleton-lite/.starcistacks/dev/README.md +2 -2
  279. package/templates/app/skeleton-lite/.starcistacks/production/README.md +1 -1
  280. package/templates/app/skeleton-lite/README.md +1 -1
  281. package/templates/be/inbox/platform/client-operation-outcomes.sql.tpl +18 -0
  282. package/templates/be/inbox/platform/event-bus/errors/event-bus.error.ts.tpl +16 -0
  283. package/templates/be/inbox/platform/event-bus/event-bus.contracts.ts.tpl +10 -0
  284. package/templates/be/inbox/platform/event-bus/event-bus.port.ts.tpl +1 -1
  285. package/templates/be/inbox/platform/event-bus/event-bus.service.spec.ts.tpl +15 -3
  286. package/templates/be/inbox/platform/event-bus/event-bus.service.ts.tpl +11 -3
  287. package/templates/be/inbox/platform/event-bus/index.ts.tpl +2 -0
  288. package/templates/be/inbox/platform/event-bus/messages/event-bus.messages.ts.tpl +7 -0
  289. package/templates/be/inbox/platform/event-bus/persistence/event-bus.sql.ts.tpl +2 -2
  290. package/templates/be/inbox/platform/idempotency/errors/idempotency.error.ts.tpl +19 -0
  291. package/templates/be/inbox/platform/idempotency/index.ts.tpl +2 -0
  292. package/templates/be/inbox/platform/idempotency/messages/idempotency.messages.ts.tpl +13 -0
  293. package/templates/be/inbox/platform/inbox/client-operation.policy.spec.ts.tpl +32 -0
  294. package/templates/be/inbox/platform/inbox/client-operation.policy.ts.tpl +17 -0
  295. package/templates/be/inbox/platform/inbox/errors/inbox.error.ts.tpl +22 -0
  296. package/templates/be/inbox/platform/inbox/inbox.port.ts.tpl +21 -0
  297. package/templates/be/inbox/platform/inbox/inbox.service.spec.ts.tpl +334 -11
  298. package/templates/be/inbox/platform/inbox/inbox.service.ts.tpl +55 -4
  299. package/templates/be/inbox/platform/inbox/index.ts.tpl +2 -0
  300. package/templates/be/inbox/platform/inbox/messages/inbox.messages.ts.tpl +15 -0
  301. package/templates/be/inbox/platform/inbox/persistence/inbox.rows.ts.tpl +33 -0
  302. package/templates/be/inbox/platform/inbox/persistence/inbox.sql.ts.tpl +26 -0
  303. package/templates/be/inbox/platform/inbox/request-fingerprint.policy.spec.ts.tpl +76 -0
  304. package/templates/be/inbox/platform/inbox/request-fingerprint.policy.ts.tpl +36 -0
  305. package/templates/be/inbox/platform/migration.sql.tpl +1 -0
  306. package/templates/be/inbox/service.spec.ts.tpl +4 -4
  307. package/templates/be/inbox/service.ts.tpl +3 -1
  308. package/templates/be/inbox/webhook-door.spec.ts.tpl +3 -2
  309. package/templates/be/inbox/webhook-door.ts.tpl +3 -2
  310. package/templates/be/patterns/event-bus/platform/errors/event-bus.error.ts.tpl +3 -0
  311. package/templates/be/patterns/event-bus/platform/event-bus.contracts.ts.tpl +10 -0
  312. package/templates/be/patterns/event-bus/platform/event-bus.port.ts.tpl +2 -2
  313. package/templates/be/patterns/event-bus/platform/event-bus.service.spec.ts.tpl +16 -3
  314. package/templates/be/patterns/event-bus/platform/event-bus.service.ts.tpl +11 -3
  315. package/templates/be/patterns/event-bus/platform/event-envelope.policy.spec.ts.tpl +139 -50
  316. package/templates/be/patterns/event-bus/platform/event-envelope.policy.ts.tpl +25 -2
  317. package/templates/be/patterns/event-bus/platform/event-relay.service.spec.ts.tpl +139 -78
  318. package/templates/be/patterns/event-bus/platform/event-relay.service.ts.tpl +10 -6
  319. package/templates/be/patterns/event-bus/platform/event-runner.service.spec.ts.tpl +262 -159
  320. package/templates/be/patterns/event-bus/platform/event-runner.service.ts.tpl +32 -5
  321. package/templates/be/patterns/event-bus/platform/messages/event-bus.messages.ts.tpl +2 -0
  322. package/templates/be/patterns/event-bus/platform/persistence/entities/event-outbox.entity.ts.tpl +5 -1
  323. package/templates/be/patterns/event-bus/platform/persistence/event-bus.rows.ts.tpl +2 -0
  324. package/templates/be/patterns/event-bus/platform/persistence/event-bus.sql.ts.tpl +4 -4
  325. package/templates/be/patterns/event-bus/platform/persistence/migrations/1789800009000-create-event-outbox.ts.tpl +1 -0
  326. package/templates/be/patterns/idempotency/platform/idempotency/errors/idempotency.error.ts.tpl +19 -0
  327. package/templates/be/patterns/idempotency/platform/idempotency/index.ts.tpl +2 -0
  328. package/templates/be/patterns/idempotency/platform/idempotency/messages/idempotency.messages.ts.tpl +13 -0
  329. package/templates/be/patterns/idempotency/platform/inbox/client-operation.policy.spec.ts.tpl +32 -0
  330. package/templates/be/patterns/idempotency/platform/inbox/client-operation.policy.ts.tpl +17 -0
  331. package/templates/be/patterns/idempotency/platform/inbox/client-outcome-backfill.client.spec.ts.tpl +66 -0
  332. package/templates/be/patterns/idempotency/platform/inbox/client-outcome-backfill.client.ts.tpl +24 -0
  333. package/templates/be/patterns/idempotency/platform/inbox/delivery-claim-backfill.client.spec.ts.tpl +60 -0
  334. package/templates/be/patterns/idempotency/platform/inbox/delivery-claim-backfill.client.ts.tpl +12 -0
  335. package/templates/be/patterns/idempotency/platform/inbox/delivery-claim-release.client.spec.ts.tpl +49 -0
  336. package/templates/be/patterns/idempotency/platform/inbox/delivery-claim-release.client.ts.tpl +12 -0
  337. package/templates/be/patterns/idempotency/platform/inbox/effectful.decorators.ts.tpl +7 -0
  338. package/templates/be/patterns/idempotency/platform/inbox/errors/inbox.error.ts.tpl +22 -0
  339. package/templates/be/patterns/idempotency/platform/inbox/inbox.decorators.ts.tpl +17 -0
  340. package/templates/be/patterns/idempotency/platform/inbox/inbox.module-definition.ts.tpl +7 -0
  341. package/templates/be/patterns/idempotency/platform/inbox/inbox.module.ts.tpl +28 -0
  342. package/templates/be/patterns/idempotency/platform/inbox/inbox.options.ts.tpl +7 -0
  343. package/templates/be/patterns/idempotency/platform/inbox/inbox.port.ts.tpl +44 -0
  344. package/templates/be/patterns/idempotency/platform/inbox/inbox.service.spec.ts.tpl +388 -0
  345. package/templates/be/patterns/idempotency/platform/inbox/inbox.service.ts.tpl +77 -0
  346. package/templates/be/patterns/idempotency/platform/inbox/index.ts.tpl +10 -0
  347. package/templates/be/patterns/idempotency/platform/inbox/messages/inbox.messages.ts.tpl +15 -0
  348. package/templates/be/patterns/idempotency/platform/inbox/persistence/connection.ts.tpl +9 -0
  349. package/templates/be/patterns/idempotency/platform/inbox/persistence/entities/inbox-claim.entity.ts.tpl +25 -0
  350. package/templates/be/patterns/idempotency/platform/inbox/persistence/inbox.rows.ts.tpl +33 -0
  351. package/templates/be/patterns/idempotency/platform/inbox/persistence/inbox.sql.ts.tpl +39 -0
  352. package/templates/be/patterns/idempotency/platform/inbox/persistence/migrations/1789800005000-create-inbox-claims.ts.tpl +21 -0
  353. package/templates/be/patterns/idempotency/platform/inbox/persistence/migrations/1792800000000-client-operation-outcomes.ts.tpl +21 -0
  354. package/templates/be/patterns/idempotency/platform/inbox/request-fingerprint.policy.spec.ts.tpl +76 -0
  355. package/templates/be/patterns/idempotency/platform/inbox/request-fingerprint.policy.ts.tpl +36 -0
  356. package/templates/be/patterns/jobs/platform/fenced.processor.spec.ts.tpl +1 -0
  357. package/templates/be/patterns/jobs/platform/job-claim.service.spec.ts.tpl +4 -3
  358. package/templates/be/patterns/jobs/platform/job-claim.service.ts.tpl +2 -0
  359. package/templates/be/patterns/jobs/platform/job-runner.service.spec.ts.tpl +20 -2
  360. package/templates/be/patterns/jobs/platform/job-runner.service.ts.tpl +1 -0
  361. package/templates/be/patterns/jobs/platform/jobs.contracts.ts.tpl +4 -0
  362. package/templates/be/patterns/jobs/platform-lite/jobs.index.ts.tpl +10 -0
  363. package/templates/be/patterns/jobs/platform-lite/jobs.sql.tpl +44 -0
  364. package/templates/be/patterns/jobs/platform-lite/persistence.jobs.sql.ts.tpl +33 -0
  365. package/templates/be/patterns/queues/platform/bullmq-queue-transport.client.spec.ts.tpl +203 -18
  366. package/templates/be/patterns/queues/platform/bullmq-queue-transport.client.ts.tpl +78 -22
  367. package/templates/be/patterns/queues/platform/errors/queue.error.ts.tpl +19 -0
  368. package/templates/be/patterns/queues/platform/messages/queue.messages.ts.tpl +13 -0
  369. package/templates/be/patterns/queues/platform/persistence.migration.ts.tpl +1 -0
  370. package/templates/be/patterns/queues/platform/persistence.queue-outbox.entity.ts.tpl +4 -0
  371. package/templates/be/patterns/queues/platform/persistence.queue.rows.ts.tpl +2 -0
  372. package/templates/be/patterns/queues/platform/persistence.queue.sql.ts.tpl +4 -4
  373. package/templates/be/patterns/queues/platform/queue-outbox.service.spec.ts.tpl +14 -2
  374. package/templates/be/patterns/queues/platform/queue-outbox.service.ts.tpl +5 -2
  375. package/templates/be/patterns/queues/platform/queue-relay.service.spec.ts.tpl +72 -74
  376. package/templates/be/patterns/queues/platform/queue-relay.service.ts.tpl +1 -1
  377. package/templates/be/patterns/queues/platform/queue-transport.port.ts.tpl +23 -8
  378. package/templates/be/patterns/queues/platform/queue-worker.service.spec.ts.tpl +29 -8
  379. package/templates/be/patterns/queues/platform/queue-worker.service.ts.tpl +7 -3
  380. package/templates/be/patterns/queues/platform/queue.contracts.ts.tpl +5 -0
  381. package/templates/be/patterns/queues/platform/queue.index.ts.tpl +2 -0
  382. package/templates/be/patterns/queues/platform/queue.log-events.ts.tpl +4 -0
  383. package/templates/be/patterns/queues/platform/queue.module.ts.tpl +2 -1
  384. package/templates/be/patterns/queues/platform/queue.port.ts.tpl +1 -1
  385. package/templates/be/patterns/queues/platform-lite/bullmq-queue-transport.client.spec.ts.tpl +417 -0
  386. package/templates/be/patterns/queues/platform-lite/bullmq-queue-transport.client.ts.tpl +172 -0
  387. package/templates/be/patterns/queues/platform-lite/persistence.queue.sql.ts.tpl +12 -0
  388. package/templates/be/patterns/queues/platform-lite/queue-outbox.sql.tpl +32 -0
  389. package/templates/be/patterns/queues/platform-lite/queue-relay.service.spec.ts.tpl +148 -0
  390. package/templates/be/patterns/queues/platform-lite/queue-relay.service.ts.tpl +51 -0
  391. package/templates/be/patterns/queues/platform-lite/queue-transport.port.ts.tpl +163 -0
  392. package/templates/be/patterns/queues/platform-lite/queue.config.ts.tpl +17 -0
  393. package/templates/be/patterns/queues/platform-lite/queue.error.ts.tpl +25 -0
  394. package/templates/be/patterns/queues/platform-lite/queue.index.ts.tpl +8 -0
  395. package/templates/be/patterns/queues/platform-lite/queue.messages.ts.tpl +21 -0
  396. package/templates/be/patterns/queues/platform-lite/queue.module.ts.tpl +74 -0
  397. package/templates/be/patterns/queues/platform-lite/queue.options.ts.tpl +38 -0
  398. package/templates/be/patterns/queues/queue.queue.ts.tpl +2 -2
  399. package/templates/be/patterns/reactors/application.contracts.ts.tpl +2 -0
  400. package/templates/be/patterns/reactors/consumer.ts.tpl +5 -1
  401. package/templates/be/platform/graphql/graphql.contracts.ts +2 -1
  402. package/templates/be/platform/graphql/localize-errors.mapper.spec.ts +1 -1
  403. package/templates/be/skeleton/apps/__app__/src/compose/platform.ts +6 -1
  404. package/templates/be/skeleton/apps/__app__/src/main.ts +7 -4
  405. package/templates/be/skeleton/apps/cli/src/compose/platform.ts +4 -0
  406. package/templates/be/skeleton/apps/cli/src/main.ts +7 -4
  407. package/templates/be/skeleton/src/modules/platform/database/index.ts +1 -1
  408. package/templates/be/skeleton/src/modules/platform/http/errors/http.error.ts +22 -0
  409. package/templates/be/skeleton/src/modules/platform/http/fetch-http-client.client.spec.ts +235 -0
  410. package/templates/be/skeleton/src/modules/platform/http/fetch-http-client.client.ts +85 -0
  411. package/templates/be/skeleton/src/modules/platform/http/graphql-call.mapper.spec.ts +92 -0
  412. package/templates/be/skeleton/src/modules/platform/http/graphql-call.mapper.ts +52 -0
  413. package/templates/be/skeleton/src/modules/platform/http/http.decorators.ts +9 -0
  414. package/templates/be/skeleton/src/modules/platform/http/http.module-definition.ts +7 -0
  415. package/templates/be/skeleton/src/modules/platform/http/http.module.ts +19 -0
  416. package/templates/be/skeleton/src/modules/platform/http/http.options.ts +2 -0
  417. package/templates/be/skeleton/src/modules/platform/http/http.port.ts +40 -0
  418. package/templates/be/skeleton/src/modules/platform/http/index.ts +7 -0
  419. package/templates/be/skeleton/src/modules/platform/http/messages/http.messages.ts +15 -0
  420. package/templates/be/skeleton/src/modules/platform/ids/ids.decorators.ts +9 -0
  421. package/templates/be/skeleton/src/modules/platform/ids/ids.module-definition.ts +7 -0
  422. package/templates/be/skeleton/src/modules/platform/ids/ids.module.ts +19 -0
  423. package/templates/be/skeleton/src/modules/platform/ids/ids.options.ts +2 -0
  424. package/templates/be/skeleton/src/modules/platform/ids/ids.port.ts +5 -0
  425. package/templates/be/skeleton/src/modules/platform/ids/index.ts +4 -0
  426. package/templates/be/skeleton/src/modules/platform/ids/system-ids.service.spec.ts +15 -0
  427. package/templates/be/skeleton/src/modules/platform/ids/system-ids.service.ts +10 -0
  428. package/templates/be/skeleton/src/modules/platform/logging/correlation-request.policy.spec.ts +59 -0
  429. package/templates/be/skeleton/src/modules/platform/logging/correlation-request.policy.ts +21 -0
  430. package/templates/be/skeleton/src/modules/platform/logging/correlation.contracts.ts +16 -0
  431. package/templates/be/skeleton/src/modules/platform/logging/correlation.policy.spec.ts +150 -0
  432. package/templates/be/skeleton/src/modules/platform/logging/correlation.policy.ts +42 -0
  433. package/templates/be/skeleton/src/modules/platform/logging/correlation.port.ts +15 -0
  434. package/templates/be/skeleton/src/modules/platform/logging/correlation.service.spec.ts +23 -0
  435. package/templates/be/skeleton/src/modules/platform/logging/correlation.service.ts +28 -0
  436. package/templates/be/skeleton/src/modules/platform/logging/index.ts +14 -1
  437. package/templates/be/skeleton/src/modules/platform/logging/json-logger.service.spec.ts +39 -6
  438. package/templates/be/skeleton/src/modules/platform/logging/json-logger.service.ts +15 -3
  439. package/templates/be/skeleton/src/modules/platform/logging/logging.decorators.ts +17 -2
  440. package/templates/be/skeleton/src/modules/platform/logging/logging.log-events.ts +13 -1
  441. package/templates/be/skeleton/src/modules/platform/logging/logging.module.ts +13 -3
  442. package/templates/be/skeleton/src/modules/platform/logging/nest-logger.service.spec.ts +69 -0
  443. package/templates/be/skeleton/src/modules/platform/logging/nest-logger.service.ts +48 -0
  444. package/templates/be/skeleton/src/modules/platform/primitives/index.ts +1 -0
  445. package/templates/be/skeleton/src/modules/platform/primitives/record.policy.spec.ts +12 -0
  446. package/templates/be/skeleton/src/modules/platform/primitives/record.policy.ts +3 -0
  447. package/templates/be/skeleton-lite/apps/api/src/compose/platform.ts +6 -1
  448. package/templates/be/skeleton-lite/apps/cli/src/compose/platform.ts +4 -0
  449. package/templates/be/skeleton-lite/src/modules/platform/database/index.ts +1 -0
  450. package/templates/be/skeleton-lite/src/modules/platform/logging/index.ts +14 -1
  451. package/templates/be/skeleton-lite/src/modules/platform/logging/logging.log-events.ts +12 -0
  452. package/templates/be/skeleton-lite/src/modules/platform/primitives/index.ts +2 -0
  453. package/templates/be/test-world/world/test-apps.options.ts.tpl +2 -0
  454. package/templates/be/test-world/world/test-world.config.ts.tpl +1 -0
  455. package/upgrade/check.mjs +12 -135
  456. package/upgrade/file-state.mjs +1 -16
  457. package/upgrade/index.mjs +34 -361
  458. package/runtime/scripts/lib/check-code-loosening.mjs +0 -128
  459. package/runtime/scripts/lib/gate-loosening.mjs +0 -292
  460. package/templates/app/upgrade-full/.starcistacks/application-stacks.yaml +0 -89
  461. package/templates/app/upgrade-full/.starcistacks/dev/README.md +0 -16
  462. package/templates/app/upgrade-full/.starcistacks/dev/infra/compose/api.yaml +0 -13
  463. package/templates/app/upgrade-full/.starcistacks/dev/infra/compose/compose.yaml +0 -3
  464. package/templates/app/upgrade-full/.starcistacks/dev/infra/compose/postgres.yaml +0 -12
  465. package/templates/app/upgrade-full/.starcistacks/dev/infra/metadata.json +0 -7
  466. package/templates/be/skeleton-lite/apps/api/src/main.ts +0 -26
  467. package/templates/be/upgrade-full/apps/cli/src/app.module.ts +0 -12
  468. package/templates/be/upgrade-full/apps/cli/src/cli.env.ts +0 -7
  469. package/templates/be/upgrade-full/apps/cli/src/compose/cli.ts +0 -5
  470. package/templates/be/upgrade-full/apps/cli/src/compose/platform.ts +0 -9
  471. package/templates/be/upgrade-full/apps/cli/src/main.ts +0 -29
  472. package/templates/be/upgrade-full/src/features/cli/cli.module.ts +0 -7
  473. package/templates/be/upgrade-full/src/features/cli/migrate/subs/run.cli.ts +0 -23
@@ -0,0 +1,268 @@
1
+ // rights.mjs - the ROLE RIGHTS of the PreToolUse command guard (rules R223 RIGHTS_ROLE_DENIED, R224 RIGHTS_PROTECTED_ZONE).
2
+ //
3
+ // Who may do what is decided by the caller's role, which the guard already resolves from the Orca terminal the agent
4
+ // runs in (scripts/guards/command-guard.mjs boundGuard, hook-install.mjs bindSeatGuard) and never from anything the agent
5
+ // says about itself:
6
+ // op an op worker of a workflow on an app (job guard, role op): path 3 of the source process
7
+ // supervisor the Supervisor seat (seat guard) and its [Worker] jobs (job guard of workflow 'supervisor'): path 2, the
8
+ // self-upgrade of .claude
9
+ // lead the Kernel (job guard, role kernel)
10
+ // coordinator a session launched with STARCI_ROLE=coordinator (the land seat)
11
+ // release STARCI_ROLE=release AND the host lock held live by role release (scripts/machine/host-lock.mjs): the release cut
12
+ // null no guard, no seat, no role: the owner's own session, which the rights never restrict
13
+ //
14
+ // Command admission is data in modules/kernel/command-policy.yaml and is evaluated by command-policy.mjs. This module keeps
15
+ // only role resolution and the small parsers that both the PreToolUse guard and PATH shim share. It imports nothing heavy:
16
+ // the YAML of either policy loads only after a caller is known to have a bound role.
17
+ import fs from 'node:fs';
18
+ import path from 'node:path';
19
+ import { guardsRoot } from './guards-root.mjs';
20
+ import { pathKey, sameOrUnder } from '../lib/path-key.mjs';
21
+ import { RUNTIME_CHANGE_CODE, runtimeWriteRefusal } from '../machine/runtime-change.mjs';
22
+ import { opWriteScopeRefusal } from './op-write-scope.mjs';
23
+
24
+ const ENV_ROLES = new Set(['op', 'supervisor', 'lead', 'coordinator', 'release']);
25
+ const safeName = (s) => String(s).replace(/[^A-Za-z0-9._-]/g, '_');
26
+ const RUNTIME_MARKER = path.join('knowledge', 'hfs', 'runtime-slots.yaml');
27
+ const WALK_LIMIT = 24;
28
+
29
+ /** <guards root>/seats/<handle>.json (the seat guard bound to an Orca terminal), or null. */
30
+ export function boundSeat(handle, { root, env = process.env } = {}) {
31
+ if (!handle) return null;
32
+ try { return JSON.parse(fs.readFileSync(path.join(guardsRoot(root, env), 'seats', `${safeName(handle)}.json`), 'utf8')); }
33
+ catch { return null; }
34
+ }
35
+
36
+ /** <guards root>/terminals/<handle>.json (the terminal-bound job guard), or null. */
37
+ export function boundGuard(handle, { root, env = process.env } = {}) {
38
+ if (!handle) return null;
39
+ try { return JSON.parse(fs.readFileSync(path.join(guardsRoot(root, env), 'terminals', `${safeName(handle)}.json`), 'utf8')); }
40
+ catch { return null; }
41
+ }
42
+
43
+ /** Actual terminal/seat binding: missing is absent; unreadable, linked or contradictory is unknown. */
44
+ export function boundIdentityOf(handle, { root, env = process.env } = {}) {
45
+ const read = (family) => {
46
+ const file = path.join(guardsRoot(root, env), family, `${safeName(handle)}.json`);
47
+ let stat;
48
+ try { stat = fs.lstatSync(file); } catch (error) { if (error.code === 'ENOENT') { return null; } throw error; }
49
+ if (!stat.isFile() || stat.isSymbolicLink()) throw new Error('non-regular caller binding');
50
+ const value = JSON.parse(fs.readFileSync(file, 'utf8'));
51
+ if (!value || typeof value !== 'object' || Array.isArray(value) || value.terminal !== handle) throw new Error('contradictory caller binding');
52
+ return value;
53
+ };
54
+ if (!handle) return { guard: null, seat: null };
55
+ return { guard: read('terminals'), seat: read('seats') };
56
+ }
57
+
58
+ /**
59
+ * The caller's rights role, or null (the owner's own session). `lockOwner` is the host lock's owner object (or null): the
60
+ * release role needs it, so a session cannot claim release by an environment variable alone.
61
+ */
62
+ export function rightsRoleOf({ guard = null, seat = null, env = process.env, lockOwner = null } = {}) {
63
+ const claimed = String(env?.STARCI_ROLE ?? '').toLowerCase();
64
+ if (guard?.role === 'kernel') return 'lead';
65
+ if (guard?.role === 'critic') return 'critic';
66
+ if (guard?.role === 'op') return guard.workflowId === 'supervisor' ? 'supervisor' : 'op';
67
+ if (seat?.role === 'supervisor') return 'supervisor';
68
+ // A claim never outranks a bound guard or seat; the release claim holds only while the host lock is held live by role release.
69
+ if (claimed === 'release') return lockOwner && !lockOwner.stale && lockOwner.role === 'release' ? 'release' : null;
70
+ if (ENV_ROLES.has(claimed)) return claimed;
71
+ return null;
72
+ }
73
+
74
+ /* ------------------------------------------------------------------------------------------ command-policy parsers */
75
+
76
+ const GIT_GLOBAL_WITH_VALUE = new Set(['--git-dir', '--work-tree', '--namespace', '--super-prefix', '--exec-path', '--attr-source']);
77
+
78
+ /** {sub, rest} of `git [global options] <sub> <args...>`. */
79
+ export function gitSubOf(args) {
80
+ let i = 0;
81
+ while (i < args.length) {
82
+ const a = String(args[i]);
83
+ if ((a === '-C' || a === '-c' || a === '--config-env' || GIT_GLOBAL_WITH_VALUE.has(a)) && i + 1 < args.length) { i += 2; continue; }
84
+ if (a.startsWith('-')) { i += 1; continue; }
85
+ break;
86
+ }
87
+ return { sub: args[i] == null ? null : String(args[i]), rest: args.slice(i + 1).map(String) };
88
+ }
89
+
90
+ const PUSH_VALUE_OPTIONS = new Set(['-o', '--push-option', '--repo', '--receive-pack', '--exec']);
91
+ const PUSH_ALL = /^--(?:all|mirror|tags|follow-tags|prune)$/;
92
+
93
+ /** The destination refs of one `git push` call: {remote, refs[], broad}. broad = the call pushes more than the refspecs name. */
94
+ export function pushTargets(rest) {
95
+ const words = [];
96
+ let broad = false;
97
+ for (let i = 0; i < rest.length; i += 1) {
98
+ const a = rest[i];
99
+ if (PUSH_VALUE_OPTIONS.has(a)) { i += 1; continue; }
100
+ if (a.startsWith('-')) { if (PUSH_ALL.test(a)) { broad = true; } continue; }
101
+ words.push(a);
102
+ }
103
+ const refs = words.slice(1).map((spec) => {
104
+ const s = spec.replace(/^\+/, '');
105
+ const dst = s.includes(':') ? s.slice(s.indexOf(':') + 1) : s;
106
+ return dst.replace(/^refs\/heads\//, 'refs/heads/');
107
+ });
108
+ return { remote: words[0] ?? null, refs, broad: broad || words.length < 2 };
109
+ }
110
+
111
+ const optionName = (value) => String(value).split('=', 1)[0];
112
+ const optionWords = (rest, takesValue = new Set()) => {
113
+ const options = [], words = [];
114
+ for (let i = 0; i < rest.length; i += 1) {
115
+ const value = String(rest[i]);
116
+ if (value === '--') { words.push(...rest.slice(i + 1).map(String)); break; }
117
+ if (!value.startsWith('-')) { words.push(value); continue; }
118
+ options.push(value);
119
+ if (!value.includes('=') && takesValue.has(optionName(value)) && rest[i + 1] != null) words.push(String(rest[++i]));
120
+ }
121
+ return { options, words };
122
+ };
123
+
124
+ const BRANCH_READ = new Set(['--list', '-l', '-a', '--all', '-r', '--remotes', '-v', '--verbose', '--show-current', '--contains', '--merged', '--points-at']);
125
+ const BRANCH_VALUES = new Set(['--contains', '--merged', '--points-at']);
126
+ const TAG_READ = new Set(['-l', '--list', '-v', '--verify', '--points-at', '--contains', '--merged', '-n']);
127
+ const TAG_VALUES = new Set(['--points-at', '--contains', '--merged']);
128
+ const CONFIG_READ = new Set(['--get', '--get-all', '--get-regexp', '--list', '-l', '--show-origin']);
129
+
130
+ const branchListRead = (list) => {
131
+ const { options, words } = optionWords(list, BRANCH_VALUES);
132
+ if (options.some((value) => !BRANCH_READ.has(optionName(value)))) return false;
133
+ const valueCount = options.filter((value) => BRANCH_VALUES.has(optionName(value)) && !value.includes('=')).length;
134
+ return options.some((value) => ['--list', '-l'].includes(optionName(value))) || words.length <= valueCount;
135
+ };
136
+
137
+ const tagListRead = (list) => {
138
+ if (!list.length) return true;
139
+ const { options, words } = optionWords(list, TAG_VALUES);
140
+ const normalized = options.map((value) => /^-n\d*$/.test(value) ? '-n' : optionName(value));
141
+ if (normalized.some((value) => !TAG_READ.has(value))) return false;
142
+ const valueCount = normalized.filter((value, index) => TAG_VALUES.has(value) && !options[index].includes('=')).length;
143
+ if (normalized.includes('-l') || normalized.includes('--list')) return true;
144
+ return normalized.some((value) => TAG_READ.has(value)) && (normalized.includes('-v') || normalized.includes('--verify') ? words.length <= valueCount + 1 : words.length <= valueCount);
145
+ };
146
+
147
+ /** Whether one of git's mixed read/write subcommands is in an explicitly read-only form. */
148
+ export function gitListFormRead(sub, rest) {
149
+ const list = rest.map(String);
150
+ if (sub === 'branch') return branchListRead(list);
151
+ if (sub === 'tag') return tagListRead(list);
152
+ const words = list.filter((value) => !value.startsWith('-'));
153
+ if (sub === 'stash') return ['list', 'show'].includes(words[0]) && !words.slice(1).includes('push');
154
+ if (sub === 'worktree') return words[0] === 'list';
155
+ if (sub === 'remote') {
156
+ const options = list.filter((value) => value.startsWith('-'));
157
+ if (options.some((value) => !['-v', '--verbose', '--all', '--push'].includes(optionName(value)))) return false;
158
+ return !words.length || (words[0] === 'show' && words.length <= 2) || (words[0] === 'get-url' && words.length <= 2);
159
+ }
160
+ if (sub === 'config') return list.some((value) => CONFIG_READ.has(optionName(value)))
161
+ && !list.some((value) => value.startsWith('-') && !CONFIG_READ.has(optionName(value)));
162
+ if (sub === 'reflog') return !words.length || (words[0] === 'show' && words.length <= 2);
163
+ if (sub === 'notes') return ['list', 'show'].includes(words[0]);
164
+ return false;
165
+ }
166
+
167
+ const NODE_VALUE_FLAGS = new Set(['--import', '--require', '-r', '--loader', '--experimental-loader', '--conditions', '-C', '--test-reporter', '--test-reporter-destination', '--test-concurrency', '--test-name-pattern', '--test-skip-pattern', '--test-timeout', '--eval', '-e', '--print', '-p']);
168
+ const WHOLE_TREE = [/(?:^|\/)tests\/\*\*/, /^\*\*\/\*\.(?:spec|test)\.m?js$/, /^(?:\.\/)?tests\/?$/];
169
+
170
+ /** True when a `node --test` call names no file or names the whole tests tree (a glob over tests/**, or the tests directory itself). */
171
+ export function nodeWholeSuite(args) {
172
+ const list = args.map(String);
173
+ if (!list.includes('--test')) return false;
174
+ const targets = [];
175
+ for (let i = 0; i < list.length; i += 1) {
176
+ if (NODE_VALUE_FLAGS.has(list[i])) { i += 1; continue; }
177
+ if (!list[i].startsWith('-')) targets.push(list[i].replaceAll('\\', '/'));
178
+ }
179
+ return !targets.length || targets.some((x) => WHOLE_TREE.some((re) => re.test(x)));
180
+ }
181
+
182
+ /* ----------------------------------------------------------------------------------------------------- file writes */
183
+
184
+ /** One refusal of the command guard: {code, command, reason, remedy}, plus `use` (the starci verb) when the table names one. */
185
+ export const refusal = (code, command, reason, remedy, use = null) => ({ code, command: String(command).slice(0, 200), reason, remedy, ...(use ? { use } : {}) });
186
+
187
+ /** The runtime checkout root a path lies in (the nearest ancestor holding the runtime marker), or null. */
188
+ export function runtimeRootOf(file, { exists = fs.existsSync } = {}) {
189
+ let dir = path.dirname(path.resolve(file));
190
+ for (let i = 0; i < WALK_LIMIT; i += 1) {
191
+ if (exists(path.join(dir, RUNTIME_MARKER))) return dir;
192
+ const up = path.dirname(dir);
193
+ if (up === dir) return null;
194
+ dir = up;
195
+ }
196
+ return null;
197
+ }
198
+
199
+
200
+ /**
201
+ * The refusal of one file write (an Edit/Write tool call or a shell write target), or null. `edit` is the text the call
202
+ * adds or replaces ({old, new}), read for catalog entries; `declaration` and `zoneOfPath` are injected by the guard (the
203
+ * YAML loads only here, only for a role that can be refused).
204
+ */
205
+ export function fileWriteVerdict({ role, filePath, tool = 'Edit', edit = null, guard = null, zone = null, shell = false }) {
206
+ if (role !== 'supervisor' && role !== 'op') return null;
207
+ const file = path.resolve(filePath);
208
+ if (role === 'op') {
209
+ const runtimeRoot = zone?.runtimeRoot ?? runtimeRootOf(file);
210
+ const own = [guard?.workflowWorktree, ...(guard?.owned ?? [])].filter(Boolean);
211
+ if (!runtimeRoot || own.some((dir) => sameOrUnder(pathKey(file), pathKey(dir)))) return opScopeVerdict(file, guard);
212
+ return refusal('RIGHTS_OP_RUNTIME_WRITE', file, 'an op works on its app in the workflow worktree and never writes inside the .claude runtime checkout: a defect of the harness is not the op\'s to fix',
213
+ 'record a lesson or an upgrade request in your report (kernel report: lessons / upgrade request); the owner path or the supervisor self-upgrade picks it up');
214
+ }
215
+ if (!zone?.runtimeRoot) return null;
216
+ if (zone.zone) {
217
+ return refusal('RIGHTS_PROTECTED_ZONE', zone.rel, `${zone.rel} is in the protected zone (${zone.zone.id}: ${zone.zone.why}): a supervisor self-upgrade does not edit it`,
218
+ 'file an owner proposal (what to change in the protected zone and why); only the owner path edits the guard, the release cut, the git hooks, the CI triggers, the test policy, the host lock and the permission settings');
219
+ }
220
+ if (zone.catalog) {
221
+ const verdict = catalogWriteVerdict({ zone, shell, tool, edit });
222
+ if (verdict) return verdict;
223
+ }
224
+ const change = runtimeWriteRefusal();
225
+ return refusal(RUNTIME_CHANGE_CODE, zone.rel, change.reason, change.remedy);
226
+ }
227
+
228
+ /** The refusal of an Op write outside its owned paths (op-write-scope.mjs), or null. */
229
+ function opScopeVerdict(file, guard) {
230
+ const outside = opWriteScopeRefusal({ file, guard });
231
+ return outside ? refusal(outside.code, file, outside.reason, outside.remedy) : null;
232
+ }
233
+
234
+ /** The protected-zone refusal for a catalog write, or null when no protected entry is named. */
235
+ function catalogWriteVerdict({ zone, shell, tool, edit }) {
236
+ const named = shell || tool === 'Write' ? ['whole-file write'] : (zone.catalogNames?.(zone.catalog, `${edit?.old ?? ''}\n${edit?.new ?? ''}`) ?? []);
237
+ if (!named.length) return null;
238
+ const how = shell || tool === 'Write' ? 'rewrites the whole file' : `names ${named.join(', ')}`;
239
+ return refusal('RIGHTS_PROTECTED_ZONE', zone.rel, `${zone.rel} holds catalog entries that enforce the protected zone (${zone.catalog.ids.concat(zone.catalog.codes).slice(0, 6).join(', ')}...): this write ${how}`,
240
+ 'edit other entries with the Edit tool (an edit that names no protected rule id or code passes); a change to a protected entry is an owner proposal');
241
+ }
242
+
243
+ const WRITE_LAST = new Set(['cp', 'install', 'copy', 'copy-item', 'cpi']);
244
+ const WRITE_ALL = new Set(['mv', 'move', 'move-item', 'mi', 'ren', 'rename', 'rename-item', 'rni', 'rm', 'rmdir', 'rd', 'del', 'erase', 'remove-item', 'ri', 'tee', 'touch', 'truncate', 'set-content', 'sc', 'add-content', 'ac', 'out-file', 'clear-content', 'clc', 'new-item', 'ni', 'mkdir']);
245
+ const IN_PLACE = new Set(['sed', 'perl', 'ruby']);
246
+
247
+ /** The absolute paths one parsed command writes by a known writer program (cp, mv, rm, tee, sed -i, Set-Content, ...). */
248
+ export function writeTargetsOf(command) {
249
+ const { program, args, cwd } = command;
250
+ const operands = args.map(String).filter((a) => !a.startsWith('-') && !/^[A-Za-z][A-Za-z0-9]*:$/.test(a));
251
+ let picks = [];
252
+ if (WRITE_LAST.has(program)) picks = operands.slice(-1);
253
+ else if (WRITE_ALL.has(program)) picks = operands;
254
+ else if (IN_PLACE.has(program) && args.some((a) => /^-[a-zA-Z]*i/.test(String(a)))) picks = operands.slice(1);
255
+ else if (program === 'dd') picks = args.map(String).filter((a) => a.startsWith('of=')).map((a) => a.slice(3));
256
+ return picks.filter((p) => p && !/[*?]/.test(p)).map((p) => path.resolve(cwd ?? process.cwd(), p));
257
+ }
258
+
259
+ /** The redirection targets (`> file`, `>> file`, `*> file`) of a raw command text, resolved against `cwd`. */
260
+ export function redirectTargetsOf(text, cwd = process.cwd()) {
261
+ const out = [];
262
+ for (const m of String(text ?? '').matchAll(/(?:^|[^<>&0-9])(?:\d|\*)?>{1,2}\s*("[^"\n]+"|'[^'\n]+'|[^\s;&|()<>"']+)/g)) {
263
+ const target = m[1].replace(/^["']|["']$/g, '');
264
+ if (/^(?:&|\/dev\/null$|\$null$|nul$)/i.test(target)) continue;
265
+ out.push(path.resolve(cwd, target));
266
+ }
267
+ return out;
268
+ }
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env node
2
+ // seat-tools.mjs — the Claude Code PreToolUse hook that enforces a seat's denied tools (.claude/settings.json).
3
+ // worker-start takes no provider argv, so a seat launched through it cannot carry --disallowedTools: the runtime binds
4
+ // the seat's terminal instead (scripts/guards/hook-install.mjs bindSeatGuard -> <guards root>/seats/<handle>.json) and this
5
+ // hook denies exactly those tools for exactly that terminal (ORCA_TERMINAL_HANDLE, which Orca exports into it).
6
+ // Every other session - no Orca handle, or a handle with no seat guard - passes untouched.
7
+ import path from 'node:path';
8
+ import { fileURLToPath } from 'node:url';
9
+ import { boundGuard, boundSeat } from './rights.mjs';
10
+ import { hookErrorMessage, readInput, writeHookOutput } from './hook-io.mjs';
11
+ import { isMain } from '../lib/is-main.mjs';
12
+ import { readEnv } from '../lib/env.mjs';
13
+
14
+ const skillRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
15
+
16
+ /** The deny decision for one hook input, or null to pass: {reason} when `toolName` is denied for `handle`. */
17
+ function seatToolDecision({ handle, toolName, guard }) {
18
+ if (!handle || !toolName) return null;
19
+ if (!Array.isArray(guard?.deniedTools) || !guard.deniedTools.includes(toolName)) return null;
20
+ return { reason: `${toolName} is denied for the ${guard.role ?? 'seat'} (${handle}): in-process subagents are outside the Supervisor's menu - answer the menu with starci supervisor decide, or record a runtime defect with starci supervisor actions record --item runtime-defect:<cause> (modules/supervisor/supervisor-prompt.md).` };
21
+ }
22
+
23
+ /** Exported hook entry for the published CLI's in-process guard fast path. */
24
+ export async function main({ stdin = process.stdin, stdout = process.stdout, stderr = process.stderr, env = process.env, root = skillRoot } = {}) {
25
+ const handle = readEnv('ORCA_TERMINAL_HANDLE', env);
26
+ const seat = boundSeat(handle, { root, env });
27
+ const enforced = Boolean(seat || boundGuard(handle, { root, env }) || readEnv('STARCI_ROLE', env));
28
+ let toolName = null;
29
+ try { toolName = JSON.parse(await readInput(stdin))?.tool_name ?? null; }
30
+ catch (error) {
31
+ const reason = `seat-tools error (${hookErrorMessage(error)})`;
32
+ const outcome = enforced ? 'refusing the tool' : 'passing the tool through';
33
+ await writeHookOutput(stderr, `starci guard: ${reason}; ${outcome}\n`).catch(() => false);
34
+ if (enforced) await denyTool(stdout, reason);
35
+ return enforced ? 2 : 0;
36
+ }
37
+ if (toolName) {
38
+ const decision = seatToolDecision({ handle, toolName, guard: seat });
39
+ if (decision && !await denyTool(stdout, decision.reason)) return 2;
40
+ }
41
+ return 0;
42
+ }
43
+
44
+ /** Deliver a Claude denial within the output budget; failed delivery keeps the hook's blocking exit. */
45
+ const denyTool = (stdout, reason) => writeHookOutput(stdout, `${JSON.stringify({ hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: reason } })}\n`).catch(() => false);
46
+
47
+ if (isMain(import.meta.url)) process.exit(await main());
@@ -0,0 +1,328 @@
1
+ // shell-commands.mjs - the shell text of one agent call read as commands: quotes group, separators split, redirections and comments drop,
2
+ // command substitutions, wrappers (bash -c, powershell -Command, cmd /c, env, xargs, npx, command/exec/...), assignments and cd are opened,
3
+ // so the command guard (command-guard.mjs) and its rule modules judge the commands that would run, never the text around them.
4
+ // Pure: text in, [{program, args, cwd, env, word, dialect}] out. The guard re-exports simpleCommands, programOf and commandsOf.
5
+ import fs from 'node:fs';
6
+ import os from 'node:os';
7
+ import path from 'node:path';
8
+
9
+ /* ------------------------------------------------------------ command text */
10
+
11
+ const SEPARATORS = new Set([';', '&', '|', '(', ')', '{', '}', '\n']);
12
+
13
+ /** The index of the ')' closing the '(' just before `from` in `text` (text.length when none does). */
14
+ function closingParenIn(text, from) {
15
+ let depth = 1;
16
+ for (let j = from; j < text.length; j += 1) {
17
+ if (text[j] === '(') depth += 1;
18
+ else if (text[j] === ')' && --depth === 0) return j;
19
+ }
20
+ return text.length;
21
+ }
22
+
23
+ /**
24
+ * The simple commands of one command line, in order: [[word, ...], ...]. Quotes group, separators (; & | && || ( ) { }
25
+ * newline) split, redirections and comments drop, and a command substitution $(...) is a command of its own. Bash
26
+ * and PowerShell text are both read: a backslash is a literal (Windows paths), a backtick splits in bash and escapes
27
+ * in PowerShell. $VAR, ${VAR}, $env:VAR and a leading ~ expand from `env`.
28
+ */
29
+ export function simpleCommands(text, { dialect = 'bash', env = process.env } = {}) {
30
+ const s = String(text ?? '');
31
+ const st = { out: [], words: [], word: null, nested: [], heredocs: [] };
32
+ const endWord = () => { if (st.word != null) { st.words.push(st.word); } st.word = null; };
33
+ const endCommand = () => { endWord(); if (st.words.length) { st.out.push(st.words); } st.words = []; };
34
+ const expand = (name) => env?.[name] ?? env?.[Object.keys(env ?? {}).find((k) => k.toLowerCase() === name.toLowerCase())] ?? '';
35
+ const closingParen = (from) => closingParenIn(s, from);
36
+ // $NAME, ${NAME}, $env:NAME at s[i] ('$'); returns [value, next index] or null for a lone '$'.
37
+ const variable = (i) => {
38
+ const m = /^\$(?:\{([A-Z_]\w*)\}|(env:)?([A-Z_]\w*))/i.exec(s.slice(i));
39
+ if (!m) return null;
40
+ // PowerShell's $true, $false and $null are literals, never environment variables.
41
+ if (dialect === 'powershell' && !m[2] && m[3] && /^(?:true|false|null)$/i.test(m[3])) return [m[0], i + m[0].length];
42
+ return [expand(m[1] ?? m[3]), i + m[0].length];
43
+ };
44
+ /* Each `take*` below handles one special construct at s[i] and returns the index of its last consumed
45
+ character (the loop's += 1 resumes after it), or -1 when the construct does not apply at s[i]. */
46
+
47
+ // $(...) substitution (nested command), $env:NAME = value assignment, $VAR expansion.
48
+ const takeDollar = (i) => {
49
+ if (s[i + 1] === '(') {
50
+ const end = closingParen(i + 2);
51
+ st.nested.push(s.slice(i + 2, end));
52
+ st.word = (st.word ?? '') + '$()';
53
+ return end;
54
+ }
55
+ // PowerShell's `$env:NAME = value` is an assignment (NAME=value), not an expansion.
56
+ const assign = st.word == null ? /^\$env:([A-Z_]\w*)\s*=(?!=)\s*/i.exec(s.slice(i)) : null;
57
+ if (assign) { st.word = `${assign[1]}=`; return i + assign[0].length - 1; }
58
+ const v = variable(i);
59
+ if (v) { st.word = (st.word ?? '') + v[0]; return v[1] - 1; }
60
+ return -1;
61
+ };
62
+ const isQuotedEscape = (j) => (s[j] === '\\' && dialect !== 'powershell' && (s[j + 1] === '"' || s[j + 1] === '$' || s[j + 1] === '\\'))
63
+ || (s[j] === '`' && dialect === 'powershell');
64
+ const queueQuotedSubstitution = (j) => {
65
+ const end = closingParen(j + 2);
66
+ st.nested.push(s.slice(j + 2, end));
67
+ return end;
68
+ };
69
+ const doubleQuotedText = (i) => {
70
+ let j = i + 1;
71
+ let v = '';
72
+ while (j < s.length && s[j] !== '"') {
73
+ if (isQuotedEscape(j)) { v += s[j + 1] ?? ''; j += 2; continue; }
74
+ if (s[j] === '$' && s[j + 1] === '(') { const end = queueQuotedSubstitution(j); v += '$()'; j = end + 1; continue; }
75
+ const expanded = s[j] === '$' ? variable(j) : null;
76
+ if (expanded) { v += expanded[0]; j = expanded[1]; continue; }
77
+ v += s[j];
78
+ j += 1;
79
+ }
80
+ return { value: v, end: j };
81
+ };
82
+ const takeQuote = (i) => {
83
+ if (s[i] === "'") {
84
+ const end = s.indexOf("'", i + 1);
85
+ st.word = (st.word ?? '') + s.slice(i + 1, end < 0 ? s.length : end);
86
+ return end < 0 ? s.length : end;
87
+ }
88
+ const quoted = doubleQuotedText(i);
89
+ st.word = (st.word ?? '') + quoted.value;
90
+ return quoted.end;
91
+ };
92
+ const takeBacktick = (i) => {
93
+ if (dialect === 'powershell') { st.word = (st.word ?? '') + (s[i + 1] ?? ''); return i + 1; }
94
+ endCommand();
95
+ return i;
96
+ };
97
+ const takeComment = (i) => {
98
+ if (st.word != null) return -1;
99
+ const nl = s.indexOf('\n', i);
100
+ return nl < 0 ? s.length : nl - 1;
101
+ };
102
+ // A bash heredoc mark (<<WORD, <<-WORD, <<'WORD') queues the body the next newline skips.
103
+ const takeHeredocMark = (i) => {
104
+ const heredoc = dialect !== 'powershell' && s[i + 1] === '<' && s[i + 2] !== '<'
105
+ ? /^<<(-?)[ \t]*(?:'([^'\n]*)'|"([^"\n]*)"|\\?([^\s;&|()<>'"]+))/.exec(s.slice(i)) : null;
106
+ if (!heredoc) return -1;
107
+ endWord();
108
+ st.heredocs.push({ strip: heredoc[1] === '-', delimiter: heredoc[2] ?? heredoc[3] ?? heredoc[4], expands: heredoc[4] != null && !heredoc[0].includes('\\') });
109
+ return i + heredoc[0].length - 1;
110
+ };
111
+ // At a newline, each queued heredoc body is skipped to its delimiter; an unquoted body still runs its $(...) substitutions.
112
+ const queueHeredocSubstitutions = (line) => {
113
+ for (const m of line.matchAll(/\$\(/g)) st.nested.push(line.slice(m.index + 2, closingParenIn(line, m.index + 2)));
114
+ };
115
+ const skipHeredocBody = (doc, at) => {
116
+ while (at < s.length) {
117
+ const nl = s.indexOf('\n', at);
118
+ const line = s.slice(at, nl < 0 ? s.length : nl).replace(/\r$/, '');
119
+ at = nl < 0 ? s.length : nl + 1;
120
+ if ((doc.strip ? line.replace(/^\t+/, '') : line) === doc.delimiter) break;
121
+ if (doc.expands) queueHeredocSubstitutions(line);
122
+ }
123
+ return at;
124
+ };
125
+ const takeHeredocBody = (i) => {
126
+ if (!st.heredocs.length) return -1;
127
+ endCommand();
128
+ let at = i + 1;
129
+ for (const doc of st.heredocs.splice(0)) at = skipHeredocBody(doc, at);
130
+ return at - 1;
131
+ };
132
+ // A redirection and its target are not arguments: `2>&1`, `>> log`, `*> $null`, `< in`.
133
+ const takeRedirect = (i) => {
134
+ if (st.word != null && /^(?:\d|\*)$/.test(st.word)) st.word = null;
135
+ endWord();
136
+ let j = i + 1;
137
+ while (s[j] === '>' || s[j] === '&') j += 1;
138
+ if (/\d/.test(s[j] ?? '') && s[j - 1] === '&') return j;
139
+ while (s[j] === ' ' || s[j] === '\t') j += 1;
140
+ if (s[j] === '"' || s[j] === "'") { const q = s[j]; const end = s.indexOf(q, j + 1); j = end < 0 ? s.length : end + 1; }
141
+ else while (j < s.length && !/[\s;&|()<>]/.test(s[j])) j += 1;
142
+ return j - 1;
143
+ };
144
+ const takeTilde = (i) => {
145
+ if (st.word != null || !(s[i + 1] === '/' || s[i + 1] === '\\' || s[i + 1] == null || /\s/.test(s[i + 1]))) return -1;
146
+ st.word = os.homedir();
147
+ return i;
148
+ };
149
+ // s[i] as a special construct; -1 when it is an ordinary character.
150
+ const takeSpecial = (i) => {
151
+ const c = s[i];
152
+ if (c === '$') return takeDollar(i);
153
+ if (c === "'" || c === '"') return takeQuote(i);
154
+ if (c === '`') return takeBacktick(i);
155
+ if (c === '#') return takeComment(i);
156
+ if (c === '<') { const h = takeHeredocMark(i); return h >= 0 ? h : takeRedirect(i); }
157
+ if (c === '>') return takeRedirect(i);
158
+ if (c === '\n') return takeHeredocBody(i);
159
+ if (c === '~') return takeTilde(i);
160
+ return -1;
161
+ };
162
+ for (let i = 0, step = 1; i < s.length; i += step) {
163
+ step = 1;
164
+ const n = takeSpecial(i);
165
+ if (n >= 0) { step = n - i + 1; continue; }
166
+ const c = s[i];
167
+ if (c === '\r') continue;
168
+ if (SEPARATORS.has(c)) { endCommand(); continue; }
169
+ if (c === ' ' || c === '\t') { endWord(); continue; }
170
+ st.word = (st.word ?? '') + c;
171
+ }
172
+ endCommand();
173
+ for (const inner of st.nested) st.out.push(...simpleCommands(inner, { dialect, env }));
174
+ return st.out;
175
+ }
176
+
177
+ /** A command word as the program it names: basename, lowercase, without .exe/.cmd/.bat/.ps1. */
178
+ export const programOf = (word) => path.basename(String(word ?? '').replaceAll('\\', '/')).toLowerCase().replace(/\.(?:exe|cmd|bat|ps1)$/, '');
179
+
180
+ const ASSIGNMENT = /^([A-Za-z_]\w*)=(.*)$/s;
181
+ const PREFIX_PROGRAMS = new Set(['command', 'exec', 'time', 'nohup', 'sudo', 'builtin']);
182
+ const SHELLS = new Set(['bash', 'sh', 'zsh', 'dash']);
183
+ const POWERSHELLS = new Set(['powershell', 'pwsh']);
184
+ const CD_PROGRAMS = new Set(['cd', 'set-location', 'sl', 'chdir', 'pushd', 'push-location']);
185
+
186
+ // command/exec/time/nohup/sudo/builtin <maybe one flag> <real command> - drop the wrapper word.
187
+ const unPrefix = (w) => w.slice(1).filter((a, i, all) => !(i === 0 && a.startsWith('-') && all.length > 1));
188
+ // env [-u NAME | -C dir | -S str | VAR=value]... <real command>: the options and assignments apply to what follows.
189
+ const unEnv = (w, cmdEnv) => {
190
+ w = w.slice(1);
191
+ while (w.length && (w[0].startsWith('-') || ASSIGNMENT.test(w[0]))) {
192
+ const m = ASSIGNMENT.exec(w[0]);
193
+ if (m) cmdEnv[m[1]] = m[2];
194
+ const takesValue = /^-[uCS]$/.test(w[0]);
195
+ w.shift();
196
+ if (takesValue) w.shift();
197
+ }
198
+ return w;
199
+ };
200
+ const unXargs = (w) => {
201
+ w = w.slice(1);
202
+ while (w.length && w[0].startsWith('-')) {
203
+ const takesValue = /^-(?:[IdEnLPs]|-(?:replace|delimiter|eof|max-args|max-lines|max-procs|max-chars|arg-file))$/.test(w[0]);
204
+ w.shift();
205
+ if (takesValue) w.shift();
206
+ }
207
+ return w;
208
+ };
209
+ // npx [-y] [--package <pkg>] <bin> args: the package's bin is the program (@openai/codex -> codex); corepack
210
+ // <pnpm|yarn> args runs that package manager. Same option-skip for bunx.
211
+ const unRun = (w) => {
212
+ w = w.slice(1);
213
+ while (w.length && w[0].startsWith('-')) {
214
+ const takesValue = /^(?:-p|--package)$/.test(w[0]);
215
+ w.shift();
216
+ if (takesValue) w.shift();
217
+ }
218
+ return w;
219
+ };
220
+ // The inner command a wrapper program runs: {text, dialect} to recurse on, {} for a wrapper that runs
221
+ // nothing visible (powershell -File with an unreadable body), null when the program is no open wrapper.
222
+ const shellInner = (args) => {
223
+ const at = args.findIndex((a) => /^-[a-z]+$/.test(a) && a.includes('c'));
224
+ return at >= 0 && args[at + 1] != null ? { text: args[at + 1], dialect: 'bash' } : null;
225
+ };
226
+ const powershellInner = (args, dir) => {
227
+ const flagAt = (re) => args.findIndex((a) => re.test(a));
228
+ const enc = flagAt(/^-(?:e|ec|enc|encodedcommand)$/i);
229
+ if (enc >= 0 && args[enc + 1]) return { text: Buffer.from(args[enc + 1], 'base64').toString('utf16le'), dialect: 'powershell' };
230
+ const file = flagAt(/^-(?:f|file)$/i);
231
+ if (file >= 0 && args[file + 1]) {
232
+ let body = null;
233
+ try { body = fs.readFileSync(path.resolve(dir, args[file + 1]), 'utf8'); } catch { /* an unreadable script runs nothing we can see */ }
234
+ return body == null ? {} : { text: body, dialect: 'powershell' };
235
+ }
236
+ const command = flagAt(/^-(?:c|command)$/i);
237
+ if (command >= 0) return { text: args.slice(command + 1).join(' '), dialect: 'powershell' };
238
+ const bare = args.filter((a) => !a.startsWith('-'));
239
+ return bare.length ? { text: bare.join(' '), dialect: 'powershell' } : null;
240
+ };
241
+ const cmdInner = (args) => {
242
+ // Git Bash spells cmd's switch //c (MSYS would rewrite a single /c as a path).
243
+ const at = args.findIndex((a) => /^\/\/?[ck]$/i.test(a));
244
+ return at >= 0 ? { text: args.slice(at + 1).join(' '), dialect: 'cmd' } : null;
245
+ };
246
+ const wrappedInner = (program, args, dir) => {
247
+ if (SHELLS.has(program)) return shellInner(args);
248
+ if (POWERSHELLS.has(program)) return powershellInner(args, dir);
249
+ if (program === 'cmd') return cmdInner(args);
250
+ return null;
251
+ };
252
+
253
+ /**
254
+ * The commands one agent shell call runs, flattened: [{program, args, cwd, env, word, dialect}] with wrappers opened
255
+ * (bash -c, powershell -Command/-EncodedCommand/-File, cmd /c, env, xargs, npx/bunx, command/exec/...), leading VAR=value and
256
+ * export/$env: assignments applied to the commands after them, and cd/Set-Location/pushd moving the cwd.
257
+ */
258
+ const leadingAssignments = (words) => {
259
+ const local = {};
260
+ while (words.length && ASSIGNMENT.test(words[0])) { const [, key, value] = ASSIGNMENT.exec(words[0]); local[key] = value; words.shift(); }
261
+ return local;
262
+ };
263
+
264
+ const recordEnvironmentCommand = (program, words, state) => {
265
+ if (program !== 'export' && program !== 'set') return false;
266
+ if (words.length === 1 || (words.length === 2 && words[1] === '-p'))
267
+ state.out.push({ program, args: words.slice(1), cwd: state.dir, env: state.scopeEnv, word: words[0], dialect: state.dialect });
268
+ for (const argument of words.slice(1)) {
269
+ const assignment = ASSIGNMENT.exec(argument);
270
+ if (assignment) state.scopeEnv[assignment[1]] = assignment[2];
271
+ }
272
+ return true;
273
+ };
274
+
275
+ const unwrapCommandWrappers = (words, program, cmdEnv, state) => {
276
+ for (let guard = 0; guard < 6; guard += 1) {
277
+ if (PREFIX_PROGRAMS.has(program)) words = unPrefix(words);
278
+ else if (program === 'env') {
279
+ const envWord = words[0];
280
+ words = unEnv(words, cmdEnv);
281
+ // `env` and `env -u NAME` with no command to run print the whole environment: they stay a command for ENV_DUMP.
282
+ if (!words.length) state.out.push({ program: 'env', args: [], cwd: state.dir, env: cmdEnv, word: envWord, dialect: state.dialect });
283
+ } else if (program === 'xargs') words = unXargs(words);
284
+ else if (program === 'npx' || program === 'bunx' || program === 'corepack') words = unRun(words);
285
+ else break;
286
+ if (!words.length) break;
287
+ program = programOf(words[0]);
288
+ }
289
+ return { words, program };
290
+ };
291
+
292
+ const changeDirectory = (program, args, state) => {
293
+ if (!CD_PROGRAMS.has(program)) return false;
294
+ // cmd's `cd /d <dir>` switches the drive too: /d is a switch there, not the target.
295
+ const target = args.find((a) => !a.startsWith('-') && !(state.dialect === 'cmd' && /^\/d$/i.test(a)));
296
+ if (target) state.dir = path.resolve(state.dir, target);
297
+ return true;
298
+ };
299
+
300
+ const openWrapper = (program, args, cmdEnv, state) => {
301
+ const inner = wrappedInner(program, args, state.dir);
302
+ if (!inner) return false;
303
+ if (inner.text != null) state.out.push(...commandsOf(inner.text, { cwd: state.dir, env: cmdEnv, dialect: inner.dialect, depth: state.depth + 1 }));
304
+ return true;
305
+ };
306
+
307
+ const processWords = (sourceWords, state) => {
308
+ let words = [...sourceWords];
309
+ const local = leadingAssignments(words);
310
+ if (!words.length) { Object.assign(state.scopeEnv, local); return; }
311
+ let program = programOf(words[0]);
312
+ if (recordEnvironmentCommand(program, words, state)) return;
313
+ const cmdEnv = { ...state.scopeEnv, ...local };
314
+ ({ words, program } = unwrapCommandWrappers(words, program, cmdEnv, state));
315
+ if (!words.length) return;
316
+ const args = words.slice(1);
317
+ if (changeDirectory(program, args, state)) return;
318
+ if (openWrapper(program, args, cmdEnv, state)) return;
319
+ state.out.push({ program, args, cwd: state.dir, env: cmdEnv, word: words[0], dialect: state.dialect });
320
+ };
321
+
322
+ export function commandsOf(text, { cwd, env = process.env, dialect = 'bash', depth = 0 } = {}) {
323
+ const out = [];
324
+ if (depth > 4) return out;
325
+ const state = { dir: cwd, scopeEnv: { ...env }, out, dialect, depth };
326
+ for (const words of simpleCommands(text, { dialect, env: state.scopeEnv })) processWords(words, state);
327
+ return out;
328
+ }