@nathapp/nax-agent 0.1.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 (398) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/LICENSE +21 -0
  3. package/README.md +49 -0
  4. package/dist/coding-tools/coding-tool-bash.d.ts +46 -0
  5. package/dist/coding-tools/coding-tool-bash.js +44 -0
  6. package/dist/coding-tools/coding-tool-extras.d.ts +35 -0
  7. package/dist/coding-tools/coding-tool-extras.js +56 -0
  8. package/dist/coding-tools/coding-tool-sandbox.d.ts +74 -0
  9. package/dist/coding-tools/coding-tool-sandbox.js +215 -0
  10. package/dist/coding-tools/coding-tool-support.d.ts +132 -0
  11. package/dist/coding-tools/coding-tool-support.js +159 -0
  12. package/dist/coding-tools/universal-coding-tools.d.ts +11 -0
  13. package/dist/coding-tools/universal-coding-tools.js +5 -0
  14. package/dist/command-interceptor/index.d.ts +55 -0
  15. package/dist/command-interceptor/index.js +60 -0
  16. package/dist/command-interceptor/shell.d.ts +36 -0
  17. package/dist/command-interceptor/shell.js +137 -0
  18. package/dist/command-safety/build.d.ts +19 -0
  19. package/dist/command-safety/build.js +47 -0
  20. package/dist/command-safety/guard.d.ts +32 -0
  21. package/dist/command-safety/guard.js +179 -0
  22. package/dist/command-safety/identifiers.d.ts +24 -0
  23. package/dist/command-safety/identifiers.js +20 -0
  24. package/dist/command-safety/index.d.ts +17 -0
  25. package/dist/command-safety/index.js +11 -0
  26. package/dist/command-safety/questions.d.ts +19 -0
  27. package/dist/command-safety/questions.js +60 -0
  28. package/dist/command-safety/row.d.ts +2 -0
  29. package/dist/command-safety/row.js +14 -0
  30. package/dist/command-safety/rule-scorer.d.ts +9 -0
  31. package/dist/command-safety/rule-scorer.js +391 -0
  32. package/dist/command-safety/shadow.d.ts +33 -0
  33. package/dist/command-safety/shadow.js +188 -0
  34. package/dist/command-safety/systemone-client.d.ts +16 -0
  35. package/dist/command-safety/systemone-client.js +92 -0
  36. package/dist/command-safety/tap.d.ts +37 -0
  37. package/dist/command-safety/tap.js +77 -0
  38. package/dist/command-safety/temp-only.d.ts +6 -0
  39. package/dist/command-safety/temp-only.js +215 -0
  40. package/dist/command-safety/tmp-write.d.ts +2 -0
  41. package/dist/command-safety/tmp-write.js +112 -0
  42. package/dist/command-safety/types.d.ts +185 -0
  43. package/dist/command-safety/types.js +21 -0
  44. package/dist/config/bash-approval.d.ts +33 -0
  45. package/dist/config/bash-approval.js +30 -0
  46. package/dist/config/catalog-overrides.d.ts +94 -0
  47. package/dist/config/catalog-overrides.js +7 -0
  48. package/dist/config/native-agent/index.d.ts +8 -0
  49. package/dist/config/native-agent/index.js +8 -0
  50. package/dist/config/schemas-sandbox.d.ts +31 -0
  51. package/dist/config/schemas-sandbox.js +45 -0
  52. package/dist/cost/core/index.d.ts +8 -0
  53. package/dist/cost/core/index.js +7 -0
  54. package/dist/cost/estimate.d.ts +34 -0
  55. package/dist/cost/estimate.js +65 -0
  56. package/dist/cost/model-spec.d.ts +28 -0
  57. package/dist/cost/model-spec.js +29 -0
  58. package/dist/cost/standard-types.d.ts +9 -0
  59. package/dist/cost/standard-types.js +1 -0
  60. package/dist/cost/usage-math.d.ts +18 -0
  61. package/dist/cost/usage-math.js +46 -0
  62. package/dist/index.d.ts +26 -0
  63. package/dist/index.js +21 -0
  64. package/dist/infra/agent-logger.d.ts +22 -0
  65. package/dist/infra/agent-logger.js +19 -0
  66. package/dist/infra/credentials-config.d.ts +20 -0
  67. package/dist/infra/credentials-config.js +15 -0
  68. package/dist/infra/errors.d.ts +9 -0
  69. package/dist/infra/errors.js +11 -0
  70. package/dist/infra/index.d.ts +7 -0
  71. package/dist/infra/index.js +7 -0
  72. package/dist/infra/nax-error.d.ts +10 -0
  73. package/dist/infra/nax-error.js +16 -0
  74. package/dist/infra/spin-breaker/hash.d.ts +2 -0
  75. package/dist/infra/spin-breaker/hash.js +5 -0
  76. package/dist/infra/spin-breaker/index.d.ts +120 -0
  77. package/dist/infra/spin-breaker/index.js +340 -0
  78. package/dist/internal/agent-output-env.d.ts +44 -0
  79. package/dist/internal/agent-output-env.js +57 -0
  80. package/dist/internal/argv-exec.d.ts +58 -0
  81. package/dist/internal/argv-exec.js +224 -0
  82. package/dist/internal/bounded-io.d.ts +24 -0
  83. package/dist/internal/bounded-io.js +51 -0
  84. package/dist/internal/command-spec/index.d.ts +29 -0
  85. package/dist/internal/command-spec/index.js +36 -0
  86. package/dist/internal/describe-value-type.d.ts +8 -0
  87. package/dist/internal/describe-value-type.js +14 -0
  88. package/dist/internal/exec-framing.d.ts +18 -0
  89. package/dist/internal/exec-framing.js +22 -0
  90. package/dist/internal/file-lock.d.ts +91 -0
  91. package/dist/internal/file-lock.js +212 -0
  92. package/dist/internal/file-size.d.ts +2 -0
  93. package/dist/internal/file-size.js +12 -0
  94. package/dist/internal/git-add.d.ts +43 -0
  95. package/dist/internal/git-add.js +59 -0
  96. package/dist/internal/git-env.d.ts +59 -0
  97. package/dist/internal/git-env.js +93 -0
  98. package/dist/internal/git-exec.d.ts +58 -0
  99. package/dist/internal/git-exec.js +115 -0
  100. package/dist/internal/path-file-lock.d.ts +23 -0
  101. package/dist/internal/path-file-lock.js +25 -0
  102. package/dist/internal/process-alive.d.ts +42 -0
  103. package/dist/internal/process-alive.js +56 -0
  104. package/dist/internal/process-kill.d.ts +27 -0
  105. package/dist/internal/process-kill.js +54 -0
  106. package/dist/internal/realpath.d.ts +29 -0
  107. package/dist/internal/realpath.js +52 -0
  108. package/dist/internal/redact.d.ts +40 -0
  109. package/dist/internal/redact.js +161 -0
  110. package/dist/internal/shell-quote.d.ts +2 -0
  111. package/dist/internal/shell-quote.js +4 -0
  112. package/dist/internal/sort.d.ts +20 -0
  113. package/dist/internal/sort.js +20 -0
  114. package/dist/internal/strip-control-chars.d.ts +19 -0
  115. package/dist/internal/strip-control-chars.js +38 -0
  116. package/dist/internal/thenable.d.ts +10 -0
  117. package/dist/internal/thenable.js +15 -0
  118. package/dist/internal.d.ts +91 -0
  119. package/dist/internal.js +91 -0
  120. package/dist/native/adapter-deps.d.ts +46 -0
  121. package/dist/native/adapter-deps.js +43 -0
  122. package/dist/native/auth-types.d.ts +70 -0
  123. package/dist/native/auth-types.js +13 -0
  124. package/dist/native/auth.d.ts +133 -0
  125. package/dist/native/auth.js +362 -0
  126. package/dist/native/client.d.ts +50 -0
  127. package/dist/native/client.js +174 -0
  128. package/dist/native/complete.d.ts +33 -0
  129. package/dist/native/complete.js +88 -0
  130. package/dist/native/credentials/chained-store.d.ts +30 -0
  131. package/dist/native/credentials/chained-store.js +68 -0
  132. package/dist/native/credentials/change-guard.d.ts +51 -0
  133. package/dist/native/credentials/change-guard.js +136 -0
  134. package/dist/native/credentials/exec-source.d.ts +50 -0
  135. package/dist/native/credentials/exec-source.js +222 -0
  136. package/dist/native/credentials/fingerprint.d.ts +26 -0
  137. package/dist/native/credentials/fingerprint.js +174 -0
  138. package/dist/native/credentials/helper-process.d.ts +79 -0
  139. package/dist/native/credentials/helper-process.js +259 -0
  140. package/dist/native/credentials/helper-protocol.d.ts +36 -0
  141. package/dist/native/credentials/helper-protocol.js +79 -0
  142. package/dist/native/credentials/index.d.ts +60 -0
  143. package/dist/native/credentials/index.js +184 -0
  144. package/dist/native/errors.d.ts +34 -0
  145. package/dist/native/errors.js +109 -0
  146. package/dist/native/index.d.ts +25 -0
  147. package/dist/native/index.js +23 -0
  148. package/dist/native/model-resolver.d.ts +57 -0
  149. package/dist/native/model-resolver.js +73 -0
  150. package/dist/native/models.d.ts +116 -0
  151. package/dist/native/models.js +183 -0
  152. package/dist/native/session/ask-human.d.ts +15 -0
  153. package/dist/native/session/ask-human.js +22 -0
  154. package/dist/native/session/compaction.d.ts +94 -0
  155. package/dist/native/session/compaction.js +170 -0
  156. package/dist/native/session/handle-invalid-tool-call.d.ts +111 -0
  157. package/dist/native/session/handle-invalid-tool-call.js +159 -0
  158. package/dist/native/session/loop-events/cache-boundary.d.ts +11 -0
  159. package/dist/native/session/loop-events/cache-boundary.js +29 -0
  160. package/dist/native/session/loop-events/external-handler.d.ts +38 -0
  161. package/dist/native/session/loop-events/external-handler.js +193 -0
  162. package/dist/native/session/loop-events/index.d.ts +16 -0
  163. package/dist/native/session/loop-events/index.js +15 -0
  164. package/dist/native/session/loop-events/payload-guard.d.ts +9 -0
  165. package/dist/native/session/loop-events/payload-guard.js +31 -0
  166. package/dist/native/session/loop-events/registry.d.ts +84 -0
  167. package/dist/native/session/loop-events/registry.js +305 -0
  168. package/dist/native/session/loop-events/types.d.ts +251 -0
  169. package/dist/native/session/loop-events/types.js +14 -0
  170. package/dist/native/session/loop-handlers.d.ts +73 -0
  171. package/dist/native/session/loop-handlers.js +137 -0
  172. package/dist/native/session/nudge.d.ts +29 -0
  173. package/dist/native/session/nudge.js +35 -0
  174. package/dist/native/session/rate-provenance.d.ts +17 -0
  175. package/dist/native/session/rate-provenance.js +39 -0
  176. package/dist/native/session/session.d.ts +139 -0
  177. package/dist/native/session/session.js +208 -0
  178. package/dist/native/session/tool-input-exemplar.d.ts +2 -0
  179. package/dist/native/session/tool-input-exemplar.js +66 -0
  180. package/dist/native/session/tool-input-validation.d.ts +15 -0
  181. package/dist/native/session/tool-input-validation.js +146 -0
  182. package/dist/native/session/tool-mapping.d.ts +18 -0
  183. package/dist/native/session/tool-mapping.js +27 -0
  184. package/dist/native/session/tool-result.d.ts +37 -0
  185. package/dist/native/session/tool-result.js +25 -0
  186. package/dist/native/session/transcript-store.d.ts +77 -0
  187. package/dist/native/session/transcript-store.js +200 -0
  188. package/dist/native/session/truncation-handler.d.ts +47 -0
  189. package/dist/native/session/truncation-handler.js +83 -0
  190. package/dist/native/session/turn-accumulator.d.ts +45 -0
  191. package/dist/native/session/turn-accumulator.js +67 -0
  192. package/dist/native/session/turn-ask-human.d.ts +23 -0
  193. package/dist/native/session/turn-ask-human.js +41 -0
  194. package/dist/native/session/turn-compaction-step.d.ts +74 -0
  195. package/dist/native/session/turn-compaction-step.js +128 -0
  196. package/dist/native/session/turn-complete-step.d.ts +77 -0
  197. package/dist/native/session/turn-complete-step.js +159 -0
  198. package/dist/native/session/turn-end-event.d.ts +3 -0
  199. package/dist/native/session/turn-end-event.js +17 -0
  200. package/dist/native/session/turn-events.d.ts +63 -0
  201. package/dist/native/session/turn-events.js +48 -0
  202. package/dist/native/session/turn-loop-round-trip.d.ts +116 -0
  203. package/dist/native/session/turn-loop-round-trip.js +318 -0
  204. package/dist/native/session/turn-loop.d.ts +23 -0
  205. package/dist/native/session/turn-loop.js +250 -0
  206. package/dist/native/session/turn-result.d.ts +39 -0
  207. package/dist/native/session/turn-result.js +64 -0
  208. package/dist/native/session/turn-retry.d.ts +120 -0
  209. package/dist/native/session/turn-retry.js +182 -0
  210. package/dist/native/session/turn-tool-batch.d.ts +61 -0
  211. package/dist/native/session/turn-tool-batch.js +259 -0
  212. package/dist/native/session/turn-types.d.ts +164 -0
  213. package/dist/native/session/turn-types.js +36 -0
  214. package/dist/native/session-adapter.d.ts +56 -0
  215. package/dist/native/session-adapter.js +369 -0
  216. package/dist/native/session-affinity.d.ts +36 -0
  217. package/dist/native/session-affinity.js +43 -0
  218. package/dist/native/tier-providers.d.ts +18 -0
  219. package/dist/native/tier-providers.js +20 -0
  220. package/dist/permissions/approval-audit.d.ts +10 -0
  221. package/dist/permissions/approval-audit.js +22 -0
  222. package/dist/permissions/approvals-link.d.ts +20 -0
  223. package/dist/permissions/approvals-link.js +62 -0
  224. package/dist/permissions/approvals-store.d.ts +168 -0
  225. package/dist/permissions/approvals-store.js +259 -0
  226. package/dist/permissions/approvals-taint.d.ts +33 -0
  227. package/dist/permissions/approvals-taint.js +138 -0
  228. package/dist/permissions/ask-chain.d.ts +69 -0
  229. package/dist/permissions/ask-chain.js +32 -0
  230. package/dist/permissions/ask.d.ts +23 -0
  231. package/dist/permissions/ask.js +25 -0
  232. package/dist/permissions/bash-lex.d.ts +59 -0
  233. package/dist/permissions/bash-lex.js +279 -0
  234. package/dist/permissions/grammar.d.ts +12 -0
  235. package/dist/permissions/grammar.js +24 -0
  236. package/dist/permissions/index.d.ts +11 -0
  237. package/dist/permissions/index.js +9 -0
  238. package/dist/permissions/secret-spans.d.ts +19 -0
  239. package/dist/permissions/secret-spans.js +88 -0
  240. package/dist/permissions/types.d.ts +85 -0
  241. package/dist/permissions/types.js +1 -0
  242. package/dist/runtime/index.d.ts +3 -0
  243. package/dist/runtime/index.js +2 -0
  244. package/dist/runtime/node-glob.d.ts +3 -0
  245. package/dist/runtime/node-glob.js +83 -0
  246. package/dist/runtime/node-runtime.d.ts +3 -0
  247. package/dist/runtime/node-runtime.js +126 -0
  248. package/dist/runtime/slot.d.ts +11 -0
  249. package/dist/runtime/slot.js +17 -0
  250. package/dist/runtime/types.d.ts +51 -0
  251. package/dist/runtime/types.js +1 -0
  252. package/dist/runtime/which.d.ts +8 -0
  253. package/dist/runtime/which.js +32 -0
  254. package/dist/sandbox/argv-quote.d.ts +11 -0
  255. package/dist/sandbox/argv-quote.js +13 -0
  256. package/dist/sandbox/defaults.d.ts +27 -0
  257. package/dist/sandbox/defaults.js +47 -0
  258. package/dist/sandbox/git-guards.d.ts +64 -0
  259. package/dist/sandbox/git-guards.js +161 -0
  260. package/dist/sandbox/index.d.ts +11 -0
  261. package/dist/sandbox/index.js +10 -0
  262. package/dist/sandbox/launcher.d.ts +37 -0
  263. package/dist/sandbox/launcher.js +160 -0
  264. package/dist/sandbox/messages.d.ts +17 -0
  265. package/dist/sandbox/messages.js +46 -0
  266. package/dist/sandbox/policy-builder.d.ts +44 -0
  267. package/dist/sandbox/policy-builder.js +149 -0
  268. package/dist/sandbox/policy-inputs.d.ts +53 -0
  269. package/dist/sandbox/policy-inputs.js +76 -0
  270. package/dist/sandbox/probe.d.ts +8 -0
  271. package/dist/sandbox/probe.js +66 -0
  272. package/dist/sandbox/registry.d.ts +17 -0
  273. package/dist/sandbox/registry.js +57 -0
  274. package/dist/sandbox/session-tmp.d.ts +20 -0
  275. package/dist/sandbox/session-tmp.js +95 -0
  276. package/dist/sandbox/srt-backend.d.ts +21 -0
  277. package/dist/sandbox/srt-backend.js +99 -0
  278. package/dist/sandbox/types.d.ts +102 -0
  279. package/dist/sandbox/types.js +1 -0
  280. package/dist/session/adapter-failure.d.ts +60 -0
  281. package/dist/session/adapter-failure.js +11 -0
  282. package/dist/session/agent-stream-events.d.ts +103 -0
  283. package/dist/session/agent-stream-events.js +6 -0
  284. package/dist/session/interaction-handler.d.ts +53 -0
  285. package/dist/session/interaction-handler.js +2 -0
  286. package/dist/session/no-op-interaction-handler.d.ts +9 -0
  287. package/dist/session/no-op-interaction-handler.js +11 -0
  288. package/dist/session/protocol-types.d.ts +12 -0
  289. package/dist/session/protocol-types.js +1 -0
  290. package/dist/session/session-types.d.ts +456 -0
  291. package/dist/session/session-types.js +64 -0
  292. package/dist/session/tool-descriptor.d.ts +23 -0
  293. package/dist/session/tool-descriptor.js +5 -0
  294. package/dist/session/turn-deadline.d.ts +17 -0
  295. package/dist/session/turn-deadline.js +23 -0
  296. package/dist/tools/ask-request.d.ts +35 -0
  297. package/dist/tools/ask-request.js +57 -0
  298. package/dist/tools/bash-cwd.d.ts +82 -0
  299. package/dist/tools/bash-cwd.js +55 -0
  300. package/dist/tools/bash.d.ts +69 -0
  301. package/dist/tools/bash.js +283 -0
  302. package/dist/tools/delete.d.ts +57 -0
  303. package/dist/tools/delete.js +154 -0
  304. package/dist/tools/denial-redirect.d.ts +42 -0
  305. package/dist/tools/denial-redirect.js +266 -0
  306. package/dist/tools/deny-paths.d.ts +35 -0
  307. package/dist/tools/deny-paths.js +76 -0
  308. package/dist/tools/edit-region.d.ts +50 -0
  309. package/dist/tools/edit-region.js +90 -0
  310. package/dist/tools/edit.d.ts +25 -0
  311. package/dist/tools/edit.js +114 -0
  312. package/dist/tools/exec-allowlist-text.d.ts +18 -0
  313. package/dist/tools/exec-allowlist-text.js +24 -0
  314. package/dist/tools/exec-guard.d.ts +69 -0
  315. package/dist/tools/exec-guard.js +151 -0
  316. package/dist/tools/git-commit.d.ts +59 -0
  317. package/dist/tools/git-commit.js +220 -0
  318. package/dist/tools/git-flags/index.d.ts +10 -0
  319. package/dist/tools/git-flags/index.js +10 -0
  320. package/dist/tools/git.d.ts +88 -0
  321. package/dist/tools/git.js +397 -0
  322. package/dist/tools/glob.d.ts +29 -0
  323. package/dist/tools/glob.js +153 -0
  324. package/dist/tools/grep.d.ts +21 -0
  325. package/dist/tools/grep.js +162 -0
  326. package/dist/tools/index.d.ts +36 -0
  327. package/dist/tools/index.js +28 -0
  328. package/dist/tools/narrow-grants.d.ts +19 -0
  329. package/dist/tools/narrow-grants.js +54 -0
  330. package/dist/tools/nax-owned-writes.d.ts +135 -0
  331. package/dist/tools/nax-owned-writes.js +259 -0
  332. package/dist/tools/package-managers-table.d.ts +146 -0
  333. package/dist/tools/package-managers-table.js +325 -0
  334. package/dist/tools/package-managers-types.d.ts +36 -0
  335. package/dist/tools/package-managers-types.js +6 -0
  336. package/dist/tools/package-managers.d.ts +85 -0
  337. package/dist/tools/package-managers.js +391 -0
  338. package/dist/tools/path-list.d.ts +38 -0
  339. package/dist/tools/path-list.js +62 -0
  340. package/dist/tools/policy-bash-raw.d.ts +19 -0
  341. package/dist/tools/policy-bash-raw.js +210 -0
  342. package/dist/tools/policy-bash.d.ts +64 -0
  343. package/dist/tools/policy-bash.js +232 -0
  344. package/dist/tools/policy-command-branch.d.ts +32 -0
  345. package/dist/tools/policy-command-branch.js +63 -0
  346. package/dist/tools/policy-input.d.ts +2 -0
  347. package/dist/tools/policy-input.js +10 -0
  348. package/dist/tools/policy-match.d.ts +75 -0
  349. package/dist/tools/policy-match.js +117 -0
  350. package/dist/tools/policy-paths-branch.d.ts +97 -0
  351. package/dist/tools/policy-paths-branch.js +314 -0
  352. package/dist/tools/policy.d.ts +65 -0
  353. package/dist/tools/policy.js +232 -0
  354. package/dist/tools/protected-paths.d.ts +26 -0
  355. package/dist/tools/protected-paths.js +8 -0
  356. package/dist/tools/provider-adapt.d.ts +4 -0
  357. package/dist/tools/provider-adapt.js +32 -0
  358. package/dist/tools/provider-advertise.d.ts +39 -0
  359. package/dist/tools/provider-advertise.js +50 -0
  360. package/dist/tools/provider-grants.d.ts +36 -0
  361. package/dist/tools/provider-grants.js +72 -0
  362. package/dist/tools/provider-sanitize.d.ts +19 -0
  363. package/dist/tools/provider-sanitize.js +53 -0
  364. package/dist/tools/provider-types.d.ts +41 -0
  365. package/dist/tools/provider-types.js +26 -0
  366. package/dist/tools/read-continuation.d.ts +147 -0
  367. package/dist/tools/read-continuation.js +176 -0
  368. package/dist/tools/read-file.d.ts +32 -0
  369. package/dist/tools/read-file.js +85 -0
  370. package/dist/tools/read.d.ts +25 -0
  371. package/dist/tools/read.js +182 -0
  372. package/dist/tools/registry.d.ts +125 -0
  373. package/dist/tools/registry.js +67 -0
  374. package/dist/tools/request-capability.d.ts +13 -0
  375. package/dist/tools/request-capability.js +23 -0
  376. package/dist/tools/run-command-exec.d.ts +14 -0
  377. package/dist/tools/run-command-exec.js +161 -0
  378. package/dist/tools/run-command.d.ts +107 -0
  379. package/dist/tools/run-command.js +398 -0
  380. package/dist/tools/runtime-calltool.d.ts +146 -0
  381. package/dist/tools/runtime-calltool.js +223 -0
  382. package/dist/tools/runtime.d.ts +149 -0
  383. package/dist/tools/runtime.js +345 -0
  384. package/dist/tools/scratchpad.d.ts +20 -0
  385. package/dist/tools/scratchpad.js +197 -0
  386. package/dist/tools/spill.d.ts +81 -0
  387. package/dist/tools/spill.js +251 -0
  388. package/dist/tools/tool-audit.d.ts +171 -0
  389. package/dist/tools/tool-audit.js +183 -0
  390. package/dist/tools/truncate.d.ts +98 -0
  391. package/dist/tools/truncate.js +297 -0
  392. package/dist/tools/types.d.ts +145 -0
  393. package/dist/tools/types.js +24 -0
  394. package/dist/tools/verb-denial-argv-hint.d.ts +35 -0
  395. package/dist/tools/verb-denial-argv-hint.js +40 -0
  396. package/dist/tools/write.d.ts +9 -0
  397. package/dist/tools/write.js +45 -0
  398. package/package.json +54 -0
@@ -0,0 +1,391 @@
1
+ /**
2
+ * Classification and normalization for the install-shaped exec branch.
3
+ *
4
+ * A call is install-shaped only when `argv[0]` is a known manager AND the
5
+ * verb is one of that manager's install verbs; such calls are hardened (a
6
+ * no-scripts mechanism, when one is known) and normalized (scoped to the
7
+ * story's own workspace member) by `normalizeExec`. Everything else is
8
+ * generic: it runs exactly as given, at the target cwd, with no rewriting
9
+ * and no mechanism — reachable only through an explicit `Exec(...)` grant
10
+ * elsewhere in the policy.
11
+ *
12
+ * Both directions matter. Without the generic class, something like
13
+ * `bun x tsc --noEmit` could never be permitted by any grant. Without the
14
+ * "known manager plus install verb" test, a loosely classified `bun add`
15
+ * could reach the generic path and skip its no-scripts mechanism entirely.
16
+ * Neither class may borrow the other's treatment — this module is pure
17
+ * classification/data lookup, no spawning, no policy, no I/O.
18
+ *
19
+ * Classification is FAIL-CLOSED (fix round 1, Critical finding 1). A global
20
+ * flag ahead of the verb (`npm --loglevel=silent install x`) must not push
21
+ * a real install onto the unhardened generic path, but a flag's own VALUE
22
+ * token must not be mistaken for the verb either. Three branches, in order:
23
+ *
24
+ * 1. Find the first token after argv[0] that does not start with "-". If
25
+ * it (or the token sequence starting there, for multi-token verbs
26
+ * like `go mod download`) matches one of this manager's install verbs
27
+ * (aliases included), the call is install-shaped.
28
+ * 2. Otherwise, if ANY token anywhere in argv equals one of this
29
+ * manager's install verb tokens, DENY, naming the token. This is the
30
+ * fail-closed branch: `npm --loglevel silent install x` has "silent"
31
+ * as its first non-flag token (branch 1 misses, because a flag's
32
+ * value is not the verb), so the "install" token later in argv trips
33
+ * this branch instead. That is deliberate — a visible refusal the
34
+ * model can retry without the global flag beats a silent unhardened
35
+ * install. Do not "improve" this into a fall-through to generic.
36
+ * 3. Otherwise the call is generic.
37
+ *
38
+ * A manager invocation can also be DISGUISED rather than merely flag-led
39
+ * (addendum finding B): `npx`/`bunx`/`pnpx` run an arbitrary package outside
40
+ * this table's model entirely, and `pnpm dlx npm install x` / `yarn dlx bun
41
+ * add x` launder a real manager invocation through a dlx-style verb. Both
42
+ * shapes are denied before verb classification even runs — there is no
43
+ * argv shape here this table can normalize safely, so neither may fall
44
+ * through to generic. `argv[0]` is also case- and executable-suffix-
45
+ * normalized (`NPM`, `npm.cmd`, `npm.exe` all resolve to the `npm` entry)
46
+ * before every manager lookup; the path-separator form (`./npm`) is already
47
+ * refused upstream by `validateArgv` in `exec-guard.ts` and is not
48
+ * duplicated here.
49
+ *
50
+ * A directory-redirect flag (`--cwd`, `--dir`, `--manifest-path`, ...) is
51
+ * denied on the same basis (fix round 2): `target` is a closed enum
52
+ * precisely so no path can arrive from the model, and a flag like this
53
+ * hands that choice straight back. Manager-global redirect flags are
54
+ * screened for install-shaped AND generic calls alike — see
55
+ * `package-managers-table.ts`'s file header for the full per-manager table
56
+ * of which flags are global versus install-only.
57
+ *
58
+ * `uv pip <anything>` is also denied at classification time (fix round 3,
59
+ * finding 3) — see `isNestedPipInvocation` for why nesting is rejected in
60
+ * favor of a flat denial.
61
+ *
62
+ * None of the fix-round-3 gaps (pip's `-t` alias, yarn's `-C`, `uv pip`
63
+ * nesting) is reachable under the built-in default allowlist, which is
64
+ * install-only and contains neither `uv pip *`, a bare `pip install`, nor
65
+ * a yarn form with `-C`. They become reachable only when a project writes
66
+ * a widening `Exec(...)` grant by hand — defense-in-depth, not a live hole
67
+ * under the shipped default.
68
+ */
69
+ import { resolve } from "node:path";
70
+ import { normalizeFlagToken } from "./exec-guard.js";
71
+ import { MANAGER_TABLE } from "./package-managers-table.js";
72
+ import { resolveWithin } from "./policy.js";
73
+ /** Package runners that execute an arbitrary, model-named package outside
74
+ * this table's model entirely — always denied, never generic. */
75
+ const RUNNER_BINARIES = new Set(["npx", "bunx", "pnpx"]);
76
+ const DLX_VERB = "dlx";
77
+ /**
78
+ * Lowercases and strips a trailing executable suffix so `NPM`, `npm.cmd`
79
+ * and `npm.exe` all resolve to the same `MANAGER_TABLE` entry as `npm`.
80
+ * Path-separator forms (`./npm`, `..\npm.exe`) are refused upstream by
81
+ * `validateArgv` in `exec-guard.ts` and are not re-checked here.
82
+ */
83
+ export function normalizeManagerBinary(token) {
84
+ return token.toLowerCase().replace(/\.(cmd|exe|ps1|bat)$/, "");
85
+ }
86
+ export function isKnownManager(binary) {
87
+ return Object.hasOwn(MANAGER_TABLE, normalizeManagerBinary(binary));
88
+ }
89
+ /**
90
+ * `pnpm dlx npm install x` / `yarn dlx bun add x`: a manager plus a
91
+ * dlx-style verb whose remaining tokens name another known manager. That is
92
+ * a manager invocation wearing a disguise, not an ordinary dlx package run
93
+ * (`pnpm dlx cowsay hello` is untouched — "cowsay" is not a known manager).
94
+ * Returns the disguised manager's token, or undefined when there is none.
95
+ */
96
+ function disguisedManagerToken(argv) {
97
+ const second = argv[1];
98
+ if (second === undefined || second.toLowerCase() !== DLX_VERB)
99
+ return undefined;
100
+ return argv.slice(2).find((token) => isKnownManager(token));
101
+ }
102
+ /**
103
+ * `uv pip <anything>` (fix round 3, finding 3): uv's install verbs are
104
+ * `sync`/`add`, so `["uv","pip","install","--target","/elsewhere","x"]`
105
+ * matches no uv verb and contains no bare token equal to one — it would
106
+ * otherwise fall through to generic, with no directory screen and no
107
+ * no-scripts mechanism, even though `pip install` is exactly the
108
+ * install-shaped call this table exists to harden.
109
+ *
110
+ * Ruling: DENY, don't recognize `uv pip install` as install-shaped and
111
+ * borrow pip's screens for it. That would make one manager entry reach
112
+ * into another's semantics — a general nesting mechanism this table does
113
+ * not otherwise have — for a shape the built-in allowlist never permits
114
+ * anyway (its patterns are `uv sync*`/`uv add*`, not `uv pip *`). Denying
115
+ * costs a capability that already requires a hand-written `Exec(...)`
116
+ * grant to reach at all, and keeps every entry independent. If a project
117
+ * ever needs `uv pip` normalized instead of denied, that is the tradeoff
118
+ * to revisit — recorded here so the choice is visible.
119
+ *
120
+ * Uses the same first-non-flag-token search as branch 1 of `classify`, so
121
+ * a global flag ahead of `pip` (`uv --quiet pip install x`) does not
122
+ * defeat this either.
123
+ */
124
+ function isNestedPipInvocation(rawBinary, argv) {
125
+ if (normalizeManagerBinary(rawBinary) !== "uv")
126
+ return false;
127
+ const firstNonFlagIndex = argv.findIndex((token, i) => i > 0 && !token.startsWith("-"));
128
+ return firstNonFlagIndex !== -1 && argv[firstNonFlagIndex].toLowerCase() === "pip";
129
+ }
130
+ /** The first token after argv[0] that is not a flag — where a manager's verb
131
+ * (or the fail-closed scan's target) is expected to start. */
132
+ function firstNonFlagIndexAfterBinary(argv) {
133
+ return argv.findIndex((token, i) => i > 0 && !token.startsWith("-"));
134
+ }
135
+ /**
136
+ * Longest-sequence-first so a multi-token verb like `["mod", "download"]`
137
+ * matches as one verb rather than being pre-empted by a shorter prefix.
138
+ * Returns the matched verb (so its length is known to the caller), or
139
+ * `undefined` when nothing at `pos` matches.
140
+ */
141
+ function matchedVerb(argv, pos, installVerbs) {
142
+ const sorted = [...installVerbs].sort((a, b) => b.length - a.length);
143
+ for (const verb of sorted) {
144
+ const candidate = argv.slice(pos, pos + verb.length);
145
+ if (candidate.length === verb.length && candidate.every((token, i) => token === verb[i])) {
146
+ return verb;
147
+ }
148
+ }
149
+ return undefined;
150
+ }
151
+ function matchesVerbAt(argv, pos, installVerbs) {
152
+ return matchedVerb(argv, pos, installVerbs) !== undefined;
153
+ }
154
+ function classify(argv) {
155
+ const rawBinary = argv[0];
156
+ if (rawBinary === undefined)
157
+ return { kind: "generic" };
158
+ if (RUNNER_BINARIES.has(normalizeManagerBinary(rawBinary))) {
159
+ return {
160
+ kind: "deny",
161
+ reason: `"${rawBinary}" is a package runner that executes an arbitrary package; it cannot be classified as install-shaped or safely treated as generic`,
162
+ };
163
+ }
164
+ if (isKnownManager(rawBinary)) {
165
+ const disguised = disguisedManagerToken(argv);
166
+ if (disguised !== undefined) {
167
+ return {
168
+ kind: "deny",
169
+ reason: `"${rawBinary} ${argv[1]}" runs "${disguised}" — a disguised manager invocation via a dlx-style verb`,
170
+ };
171
+ }
172
+ if (isNestedPipInvocation(rawBinary, argv)) {
173
+ return {
174
+ kind: "deny",
175
+ reason: `"${rawBinary} pip" nests pip inside another manager; not classifiable as install-shaped (uv's install verbs are sync/add, not pip) and not safely generic (pip's own directory/scripts screens would not apply)`,
176
+ };
177
+ }
178
+ }
179
+ const normalizedBinary = normalizeManagerBinary(rawBinary);
180
+ const entry = MANAGER_TABLE[normalizedBinary];
181
+ if (!entry)
182
+ return { kind: "generic" };
183
+ // Branch 1: the first non-flag token after argv[0] (or the sequence
184
+ // starting there) matches an install verb.
185
+ const firstNonFlagIndex = firstNonFlagIndexAfterBinary(argv);
186
+ if (firstNonFlagIndex !== -1 && matchesVerbAt(argv, firstNonFlagIndex, entry.installVerbs)) {
187
+ return { kind: "install" };
188
+ }
189
+ // Branch 2 (fail-closed): an install verb token appears somewhere else in
190
+ // argv. The call looks like an install but is not in a position this
191
+ // policy can normalize safely — deny, do not guess generic.
192
+ const flatVerbTokens = new Set(entry.installVerbs.flat());
193
+ for (let i = 1; i < argv.length; i++) {
194
+ if (flatVerbTokens.has(argv[i])) {
195
+ return {
196
+ kind: "deny",
197
+ reason: `argv contains the install verb "${argv[i]}" but not in a position this policy can classify safely: ${argv.join(" ")}`,
198
+ };
199
+ }
200
+ }
201
+ // Branch 3: generic.
202
+ return { kind: "generic" };
203
+ }
204
+ export function classifyExec(argv) {
205
+ return classify(argv).kind;
206
+ }
207
+ /**
208
+ * A scripts-control flag the model added itself (`--ignore-scripts`, or its
209
+ * negation `--no-ignore-scripts`) is not trusted, in either direction: the
210
+ * no-scripts mechanism this table appends must be the only thing deciding
211
+ * that, driven by `install.allowScripts` in project config — a human
212
+ * decision — never by a flag in the model-authored argv. Addendum finding
213
+ * C. Matches case- and `=`-normalized, mirroring `deniedFlag` in
214
+ * `exec-guard.ts`.
215
+ */
216
+ const SCRIPTS_CONTROL_FLAG = /^--(no-)?ignore-scripts$/;
217
+ function scriptsControlConflict(argv) {
218
+ for (let i = 1; i < argv.length; i++) {
219
+ const token = argv[i];
220
+ if (SCRIPTS_CONTROL_FLAG.test(normalizeFlagToken(token)))
221
+ return token;
222
+ }
223
+ return undefined;
224
+ }
225
+ function applyNoScripts(result, mechanism) {
226
+ if ("error" in result)
227
+ return result;
228
+ if ("none" in mechanism)
229
+ return result;
230
+ if ("flag" in mechanism) {
231
+ return { ...result, argv: [...result.argv, mechanism.flag] };
232
+ }
233
+ return { ...result, env: { ...result.env, ...mechanism.env } };
234
+ }
235
+ /** Archive suffixes that make a pip/uv token path-shaped even without a
236
+ * leading path separator — a built sdist/wheel tarball path, e.g.
237
+ * `pip install ./dist/pkg-1.0.tar.gz` or a bare `pkg-1.0.whl` sitting in
238
+ * cwd. Not extended to every manager: an npm/bun/yarn tarball install is
239
+ * already path-shaped via a leading path segment in every case this table
240
+ * has to cover, and widening this check to every manager risks flagging a
241
+ * bare filename that merely happens to share an extension. */
242
+ const ARCHIVE_SUFFIXES = [".tgz", ".tar.gz", ".whl", ".zip"];
243
+ /**
244
+ * A token "looks like a path" when it starts with a path-relative or
245
+ * absolute prefix, or — for pip/uv only — ends in a known archive suffix.
246
+ * A bare package name (`httpx`), a version specifier (`pkg@1.2.3`,
247
+ * `pkg>=1.0`), and a Python extras spec (`httpx[http2]`) are none of these
248
+ * and must NOT be treated as path-shaped (finding 1, fix round 4).
249
+ */
250
+ function isPathShapedToken(token, manager) {
251
+ if (token.startsWith("./") || token.startsWith("../") || token.startsWith("/") || token.startsWith("~")) {
252
+ return true;
253
+ }
254
+ if ((manager === "pip" || manager === "uv") && ARCHIVE_SUFFIXES.some((suffix) => token.endsWith(suffix))) {
255
+ return true;
256
+ }
257
+ return false;
258
+ }
259
+ /**
260
+ * Finding 1 (fix round 4, Important/security): every screen in this table
261
+ * covers FLAGS only (registry/directory redirects) — a positional argument
262
+ * to an install verb was never containment-checked at all, so
263
+ * `["pip","install","/etc/passwd"]` or
264
+ * `["npm","install","../../../elsewhere/x.tgz"]` builds/installs from an
265
+ * arbitrary filesystem path. This matters most for pip: it ships in the
266
+ * default built-in allowlist and has no confirmed no-scripts mechanism (see
267
+ * `package-managers-table.ts`'s file header), so a `setup.py` from outside
268
+ * the permitted root would execute.
269
+ *
270
+ * Ruling: do not ban positional paths — a local path install
271
+ * (`bun add ./vendor/local-types`) is legitimate and the integration test
272
+ * depends on it. Containment-check any positional token that LOOKS like a
273
+ * path instead, resolved against `resolvedCwd` — the cwd this call will
274
+ * actually run from once normalized, taken from `packageForm`/`rootForm`'s
275
+ * own result rather than re-derived here, because which cwd an install
276
+ * actually runs from is manager-specific: npm/pnpm/yarn/cargo/uv always run
277
+ * from the repo root via their own scoping flag/subcommand, while
278
+ * bun/pip/go run from the package workdir when target is "package".
279
+ *
280
+ * Flags (leading `-`) are skipped outright when scanning for candidates —
281
+ * every flag that could redirect a path is already denied earlier in
282
+ * `normalizeExec` (`globalDirectoryConflict`/`installDirectoryConflict`), so
283
+ * only a boolean/enum flag or its non-path value can still be present here.
284
+ * Treating a flag's own value as a candidate anyway is harmless, not merely
285
+ * tolerated: a non-path-shaped value is ignored by `isPathShapedToken`, and
286
+ * a path-shaped one (pip's `-e ./local-pkg`, `-r ./requirements.txt`) is
287
+ * exactly the same containment concern as a bare positional path and
288
+ * deserves the same treatment.
289
+ *
290
+ * Reuses `resolveWithin` (`src/tools/policy.ts`) for the actual containment
291
+ * comparison rather than a second path-comparison implementation: this
292
+ * function only computes the absolute candidate itself (a relative token
293
+ * resolves against `resolvedCwd`, not the repo root, so `resolveWithin`
294
+ * cannot be asked to do that resolution step) and then hands it off already
295
+ * absolute, so it is `resolveWithin`'s own `isAbsolute` branch that runs the
296
+ * actual containment check.
297
+ */
298
+ function findPositionalPathConflict(argv, entry, repoRoot, resolvedCwd) {
299
+ const manager = normalizeManagerBinary(argv[0]);
300
+ const verbStart = firstNonFlagIndexAfterBinary(argv);
301
+ if (verbStart === -1)
302
+ return undefined; // unreachable for an install-shaped call
303
+ const verb = matchedVerb(argv, verbStart, entry.installVerbs);
304
+ const afterVerb = verbStart + (verb?.length ?? 0);
305
+ for (let i = afterVerb; i < argv.length; i++) {
306
+ const token = argv[i];
307
+ if (token.startsWith("-"))
308
+ continue;
309
+ if (!isPathShapedToken(token, manager))
310
+ continue;
311
+ const absolute = token.startsWith("/") ? token : resolve(resolvedCwd, token);
312
+ if (resolveWithin(repoRoot, absolute) === null) {
313
+ return `argv contains a path-shaped argument "${token}" that resolves outside the permitted root`;
314
+ }
315
+ }
316
+ return undefined;
317
+ }
318
+ export function normalizeExec(input) {
319
+ const { argv, target, packageRelPath, allowScripts } = input;
320
+ // Rule: packageRelPath === "" means the story IS the repo root — both
321
+ // targets collapse to repoRoot, and no scoping flag naming a member is
322
+ // ever added.
323
+ const effectiveTarget = packageRelPath === "" ? "repoRoot" : target;
324
+ const classification = classify(argv);
325
+ if (classification.kind === "deny") {
326
+ return { error: classification.reason };
327
+ }
328
+ const binary = argv[0];
329
+ const entry = MANAGER_TABLE[normalizeManagerBinary(binary)];
330
+ // Fix round 2: a directory-redirect flag global to the manager hands cwd
331
+ // control back to the model regardless of whether the call is
332
+ // install-shaped or generic — `target` is a closed enum precisely so no
333
+ // path can arrive from the model, and this is the containment property
334
+ // itself. Checked before branching on classification kind so a generic
335
+ // call (`bun x tsc --cwd /elsewhere`) cannot escape it either.
336
+ if (entry) {
337
+ const globalDirConflict = entry.globalDirectoryConflict(argv);
338
+ if (globalDirConflict !== undefined) {
339
+ return {
340
+ error: `argv already redirects the working directory via "${globalDirConflict}"; this policy computes and controls cwd, never the model-authored argv`,
341
+ };
342
+ }
343
+ }
344
+ if (classification.kind === "generic") {
345
+ // Generic calls get cwd from the target and nothing else — no
346
+ // mechanism, no scoping, no rewriting of any argument.
347
+ return { argv, cwd: effectiveTarget === "package" ? input.packageWorkdir : input.repoRoot };
348
+ }
349
+ if (!entry) {
350
+ // Unreachable: classify() only returns "install" for a binary that
351
+ // resolved to a MANAGER_TABLE entry. Kept as a defensive fallback
352
+ // rather than a non-null assertion (postfix `!` is banned in this repo).
353
+ return { argv, cwd: effectiveTarget === "package" ? input.packageWorkdir : input.repoRoot };
354
+ }
355
+ const installDirConflict = entry.installDirectoryConflict(argv);
356
+ if (installDirConflict !== undefined) {
357
+ return {
358
+ error: `argv already redirects the install destination via "${installDirConflict}"; this policy computes and controls cwd, never the model-authored argv`,
359
+ };
360
+ }
361
+ // Fix round 1, Critical finding 2 + addendum C: DENY rather than strip
362
+ // when the incoming argv already carries a flag that would smuggle in a
363
+ // second workspace member, or a scripts-control flag the model is not
364
+ // entitled to set. Stripping either would change what the model asked
365
+ // for while reporting success.
366
+ const scopeConflict = entry.scopingConflict(argv);
367
+ if (scopeConflict !== undefined) {
368
+ return {
369
+ error: `argv already scopes this call via "${scopeConflict}"; refusing rather than combining it with the story's own package scoping`,
370
+ };
371
+ }
372
+ const scriptsConflict = scriptsControlConflict(argv);
373
+ if (scriptsConflict !== undefined) {
374
+ return {
375
+ error: `argv already carries the scripts-control flag "${scriptsConflict}"; the no-scripts mechanism is decided by project config (install.allowScripts), never by the model-authored argv`,
376
+ };
377
+ }
378
+ if (effectiveTarget === "package" && entry.needsPackageName && !input.packageName) {
379
+ return { error: `${binary} workspace scoping requires the member package's manifest name` };
380
+ }
381
+ const base = effectiveTarget === "package" ? entry.packageForm(argv, input) : entry.rootForm(argv, input);
382
+ if ("error" in base)
383
+ return base;
384
+ const positionalPathConflict = findPositionalPathConflict(argv, entry, input.repoRoot, base.cwd);
385
+ if (positionalPathConflict !== undefined) {
386
+ return { error: positionalPathConflict };
387
+ }
388
+ if (allowScripts)
389
+ return base;
390
+ return applyNoScripts(base, entry.noScripts(input));
391
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Split one string field into the path elements it really names.
3
+ *
4
+ * Lives on its own because two layers must agree on the answer exactly: the
5
+ * policy resolves and grant-checks each element (`ToolScope.listPathFields`),
6
+ * and the tool substitutes each element as its own shell argument. Were they
7
+ * to disagree, the policy would approve one set of paths and the shell would
8
+ * receive another (nax#1998).
9
+ *
10
+ * Whitespace is the separator, but only for a value that is really a LIST.
11
+ * `{{files}}` is a project-declared placeholder and a project may mean a NAME
12
+ * filter by it (`pytest -k {{files}}`, `jest -t {{files}}`), while a path may
13
+ * itself contain a space. Splitting unconditionally would turn one filter into
14
+ * several arguments and break any repo under `/tmp/my dir/`. So a multi-token
15
+ * value decides by ordered evidence tiers (nax#1998 + US-002):
16
+ *
17
+ * 1. One token (or none) -> the whole value. Never an empty list: an empty
18
+ * list would leave a caller's per-element loop with nothing to check, and
19
+ * the field would be approved by falling off the end of it.
20
+ * 2. The WHOLE value is an existing file -> the whole value. A path under
21
+ * `/tmp/my dir/` contains a space; its tokens are not separate paths. This
22
+ * is checked before every token-level test, so a space-bearing path can
23
+ * never be re-split.
24
+ * 3. ANY token is an existing file or an existing DIRECTORY -> the tokens.
25
+ * The directory case is what gives `test/unit test/integration` two
26
+ * arguments today.
27
+ * 4. EVERY token looks like a path -- contains a path separator, or ends in
28
+ * a file extension -> the tokens. Both slash spellings count, so a path
29
+ * list keeps its meaning across operating systems. This is deliberately
30
+ * language-neutral:
31
+ * it keys on path SYNTAX, never on a test-file naming convention, so it
32
+ * admits not-yet-created `test_foo.py` / `foo_test.go` / `Foo.test.ts`
33
+ * equally, without nax knowing the host project's language. It requires
34
+ * EVERY token to qualify, so a mixed name-filter-plus-path value where
35
+ * nothing exists keeps today's whole-value behaviour rather than guessing.
36
+ * 5. Otherwise -> the whole value. A multi-token name filter lands here.
37
+ */
38
+ export declare function pathListElements(value: string, root: string): string[];
@@ -0,0 +1,62 @@
1
+ import { statSync } from "node:fs";
2
+ import { isAbsolute, resolve } from "node:path";
3
+ /**
4
+ * Split one string field into the path elements it really names.
5
+ *
6
+ * Lives on its own because two layers must agree on the answer exactly: the
7
+ * policy resolves and grant-checks each element (`ToolScope.listPathFields`),
8
+ * and the tool substitutes each element as its own shell argument. Were they
9
+ * to disagree, the policy would approve one set of paths and the shell would
10
+ * receive another (nax#1998).
11
+ *
12
+ * Whitespace is the separator, but only for a value that is really a LIST.
13
+ * `{{files}}` is a project-declared placeholder and a project may mean a NAME
14
+ * filter by it (`pytest -k {{files}}`, `jest -t {{files}}`), while a path may
15
+ * itself contain a space. Splitting unconditionally would turn one filter into
16
+ * several arguments and break any repo under `/tmp/my dir/`. So a multi-token
17
+ * value decides by ordered evidence tiers (nax#1998 + US-002):
18
+ *
19
+ * 1. One token (or none) -> the whole value. Never an empty list: an empty
20
+ * list would leave a caller's per-element loop with nothing to check, and
21
+ * the field would be approved by falling off the end of it.
22
+ * 2. The WHOLE value is an existing file -> the whole value. A path under
23
+ * `/tmp/my dir/` contains a space; its tokens are not separate paths. This
24
+ * is checked before every token-level test, so a space-bearing path can
25
+ * never be re-split.
26
+ * 3. ANY token is an existing file or an existing DIRECTORY -> the tokens.
27
+ * The directory case is what gives `test/unit test/integration` two
28
+ * arguments today.
29
+ * 4. EVERY token looks like a path -- contains a path separator, or ends in
30
+ * a file extension -> the tokens. Both slash spellings count, so a path
31
+ * list keeps its meaning across operating systems. This is deliberately
32
+ * language-neutral:
33
+ * it keys on path SYNTAX, never on a test-file naming convention, so it
34
+ * admits not-yet-created `test_foo.py` / `foo_test.go` / `Foo.test.ts`
35
+ * equally, without nax knowing the host project's language. It requires
36
+ * EVERY token to qualify, so a mixed name-filter-plus-path value where
37
+ * nothing exists keeps today's whole-value behaviour rather than guessing.
38
+ * 5. Otherwise -> the whole value. A multi-token name filter lands here.
39
+ */
40
+ export function pathListElements(value, root) {
41
+ const tokens = value.split(/\s+/).filter((token) => token.length > 0);
42
+ if (tokens.length <= 1)
43
+ return [value];
44
+ if (isExistingFile(value, root))
45
+ return [value];
46
+ if (tokens.some((token) => isExistingFile(token, root) || isExistingDir(token, root)))
47
+ return tokens;
48
+ if (tokens.every(looksLikePath))
49
+ return tokens;
50
+ return [value];
51
+ }
52
+ function isExistingFile(candidate, root) {
53
+ const absolute = isAbsolute(candidate) ? candidate : resolve(root, candidate);
54
+ return statSync(absolute, { throwIfNoEntry: false })?.isFile() === true;
55
+ }
56
+ function isExistingDir(candidate, root) {
57
+ const absolute = isAbsolute(candidate) ? candidate : resolve(root, candidate);
58
+ return statSync(absolute, { throwIfNoEntry: false })?.isDirectory() === true;
59
+ }
60
+ function looksLikePath(token) {
61
+ return /[\\/]/.test(token) || /\.[A-Za-z0-9]+$/.test(token);
62
+ }
@@ -0,0 +1,19 @@
1
+ import type { BashCheck } from "./policy-bash.ts";
2
+ export interface RawScreenArgs {
3
+ readonly tool: string;
4
+ readonly command: unknown;
5
+ /** The shell's initial working directory. */
6
+ readonly initialPath: string;
7
+ /** Resolves a candidate from an effective shell working directory. */
8
+ readonly resolvePath: (candidate: string, cwd: string) => string | null;
9
+ /** The permitted root, used to relativise a resolved path. */
10
+ readonly root: string;
11
+ /**
12
+ * US-002: true when the command runs inside the OS sandbox (an available
13
+ * launcher). A token that only NAMES a feature PRD is then no longer refused
14
+ * -- reading it through Bash costs nothing, and the sandbox still blocks a
15
+ * write. Absent or false screens exactly as before.
16
+ */
17
+ readonly sandboxWrapped?: boolean;
18
+ }
19
+ export declare function screenRawBashCommand(args: RawScreenArgs): BashCheck;