@pugi/cli 0.1.0-beta.99 → 1.0.0-alpha.10

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 (448) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +11 -191
  3. package/bin/pugi +8 -0
  4. package/package.json +15 -71
  5. package/postinstall.mjs +31 -0
  6. package/CHANGELOG.md +0 -132
  7. package/THIRD_PARTY_NOTICES.md +0 -40
  8. package/assets/pugi-mascot.ansi +0 -16
  9. package/assets/pugi-prozr2-mascot.ansi +0 -9
  10. package/bin/run.js +0 -34
  11. package/dist/commands/deploy.js +0 -439
  12. package/dist/commands/flatten.js +0 -191
  13. package/dist/commands/jobs-watch.js +0 -201
  14. package/dist/commands/jobs.js +0 -260
  15. package/dist/commands/retro.js +0 -210
  16. package/dist/commands/smoke.js +0 -133
  17. package/dist/core/agent-progress/cleanup.js +0 -134
  18. package/dist/core/agent-progress/schema.js +0 -144
  19. package/dist/core/agent-progress/writer.js +0 -101
  20. package/dist/core/agents/adaptive-router.js +0 -330
  21. package/dist/core/agents/loader.js +0 -104
  22. package/dist/core/agents/query-decomposer.js +0 -297
  23. package/dist/core/agents/registry.js +0 -69
  24. package/dist/core/approvals/shortcut-resolver.js +0 -98
  25. package/dist/core/artifact-chain/dispatcher.js +0 -148
  26. package/dist/core/artifact-chain/exporter.js +0 -164
  27. package/dist/core/artifact-chain/state.js +0 -243
  28. package/dist/core/artifact-chain/steps.js +0 -169
  29. package/dist/core/ask-user/question.js +0 -92
  30. package/dist/core/audit/audit-trail.js +0 -275
  31. package/dist/core/auth/ensure-authenticated.js +0 -129
  32. package/dist/core/auth/env-provider.js +0 -238
  33. package/dist/core/auto-open-browser.js +0 -128
  34. package/dist/core/auto-update/channels.js +0 -122
  35. package/dist/core/auto-update/checker.js +0 -241
  36. package/dist/core/auto-update/state.js +0 -235
  37. package/dist/core/bare-mode/index.js +0 -107
  38. package/dist/core/bash/redirect.js +0 -281
  39. package/dist/core/bash-classifier.js +0 -1397
  40. package/dist/core/checkpoint/resumer.js +0 -149
  41. package/dist/core/checkpoint/rewinder.js +0 -291
  42. package/dist/core/checkpoints/shadow-git.js +0 -670
  43. package/dist/core/citations/parser.js +0 -109
  44. package/dist/core/classifier/yolo-classifier.js +0 -88
  45. package/dist/core/clipboard.js +0 -70
  46. package/dist/core/codegraph/decision-store.js +0 -248
  47. package/dist/core/codegraph/detect-repo.js +0 -459
  48. package/dist/core/codegraph/install.js +0 -134
  49. package/dist/core/codegraph/offer-hook.js +0 -220
  50. package/dist/core/compact/auto-trigger.js +0 -96
  51. package/dist/core/compact/buffer-rewriter.js +0 -115
  52. package/dist/core/compact/summarizer.js +0 -208
  53. package/dist/core/compact/token-counter.js +0 -108
  54. package/dist/core/consensus/anvil-fanout.js +0 -276
  55. package/dist/core/consensus/diff-capture.js +0 -491
  56. package/dist/core/consensus/rubric.js +0 -233
  57. package/dist/core/context/builder.js +0 -114
  58. package/dist/core/context/compaction-events.js +0 -99
  59. package/dist/core/context/compaction.js +0 -602
  60. package/dist/core/context/index.js +0 -28
  61. package/dist/core/context/invariants.js +0 -250
  62. package/dist/core/context/markdown-loader.js +0 -288
  63. package/dist/core/context/markdown-traverse.js +0 -255
  64. package/dist/core/context/pugiignore.js +0 -316
  65. package/dist/core/context/repo-skeleton.js +0 -533
  66. package/dist/core/context/tool-eviction.js +0 -55
  67. package/dist/core/context/watcher.js +0 -342
  68. package/dist/core/context/working-set.js +0 -165
  69. package/dist/core/coordinator/agent-tools.js +0 -77
  70. package/dist/core/coordinator/agent-toolset.js +0 -65
  71. package/dist/core/coordinator/fsm.js +0 -73
  72. package/dist/core/coordinator/mode-fsm.js +0 -70
  73. package/dist/core/cost/rate-card.js +0 -129
  74. package/dist/core/cost/tracker.js +0 -221
  75. package/dist/core/credentials.js +0 -355
  76. package/dist/core/cron/scheduler.js +0 -138
  77. package/dist/core/denial-tracking/index.js +0 -8
  78. package/dist/core/denial-tracking/state.js +0 -264
  79. package/dist/core/diagnostics/probe-runner.js +0 -93
  80. package/dist/core/diagnostics/probes/api.js +0 -46
  81. package/dist/core/diagnostics/probes/auth.js +0 -93
  82. package/dist/core/diagnostics/probes/bare-mode.js +0 -42
  83. package/dist/core/diagnostics/probes/cli-version.js +0 -127
  84. package/dist/core/diagnostics/probes/config.js +0 -72
  85. package/dist/core/diagnostics/probes/denial-tracking.js +0 -57
  86. package/dist/core/diagnostics/probes/disk.js +0 -81
  87. package/dist/core/diagnostics/probes/engine-live.js +0 -46
  88. package/dist/core/diagnostics/probes/git.js +0 -65
  89. package/dist/core/diagnostics/probes/hooks.js +0 -118
  90. package/dist/core/diagnostics/probes/mcp.js +0 -75
  91. package/dist/core/diagnostics/probes/node.js +0 -59
  92. package/dist/core/diagnostics/probes/pnpm.js +0 -36
  93. package/dist/core/diagnostics/probes/pugi-md.js +0 -89
  94. package/dist/core/diagnostics/probes/sandbox.js +0 -72
  95. package/dist/core/diagnostics/probes/session.js +0 -74
  96. package/dist/core/diagnostics/probes/status-snapshot.js +0 -488
  97. package/dist/core/diagnostics/probes/workspace.js +0 -63
  98. package/dist/core/diagnostics/types.js +0 -70
  99. package/dist/core/dispatch/cache-cleanup.js +0 -197
  100. package/dist/core/dispatch/cache-handoff.js +0 -295
  101. package/dist/core/edits/apply-patch-layer-e.js +0 -189
  102. package/dist/core/edits/dispatch.js +0 -511
  103. package/dist/core/edits/format-detector.js +0 -260
  104. package/dist/core/edits/format-matrix.js +0 -26
  105. package/dist/core/edits/fuzzy-ladder.js +0 -650
  106. package/dist/core/edits/index.js +0 -19
  107. package/dist/core/edits/journal.js +0 -199
  108. package/dist/core/edits/layer-a-apply.js +0 -217
  109. package/dist/core/edits/layer-a-fuzzy-apply.js +0 -198
  110. package/dist/core/edits/layer-b-apply.js +0 -211
  111. package/dist/core/edits/layer-c-apply.js +0 -160
  112. package/dist/core/edits/layer-d-ast.js +0 -572
  113. package/dist/core/edits/marker-parser.js +0 -401
  114. package/dist/core/edits/security-gate.js +0 -223
  115. package/dist/core/edits/verify-hook.js +0 -273
  116. package/dist/core/edits/worktree.js +0 -322
  117. package/dist/core/engine/adapter-runner.js +0 -8
  118. package/dist/core/engine/anvil-client.js +0 -344
  119. package/dist/core/engine/auto-compact.js +0 -179
  120. package/dist/core/engine/budgets.js +0 -195
  121. package/dist/core/engine/context-prefix.js +0 -155
  122. package/dist/core/engine/index.js +0 -12
  123. package/dist/core/engine/intensity.js +0 -163
  124. package/dist/core/engine/intent.js +0 -260
  125. package/dist/core/engine/native-pugi.js +0 -1616
  126. package/dist/core/engine/noop.js +0 -27
  127. package/dist/core/engine/prompts.js +0 -236
  128. package/dist/core/engine/strip-internal-fields.js +0 -124
  129. package/dist/core/engine/tool-bridge.js +0 -2173
  130. package/dist/core/engine/verification-patterns.js +0 -195
  131. package/dist/core/evaluation/golden-dataset.js +0 -293
  132. package/dist/core/feedback/queue.js +0 -177
  133. package/dist/core/feedback/submitter.js +0 -145
  134. package/dist/core/file-cache.js +0 -141
  135. package/dist/core/flatten/flatten-repo.js +0 -439
  136. package/dist/core/format/osc8-link.js +0 -28
  137. package/dist/core/hook-chains.js +0 -392
  138. package/dist/core/hooks/citation-verify-hook.js +0 -138
  139. package/dist/core/hooks/citation-verify.js +0 -112
  140. package/dist/core/hooks/events.js +0 -46
  141. package/dist/core/hooks/index.js +0 -15
  142. package/dist/core/hooks/registry.js +0 -216
  143. package/dist/core/hooks/runner.js +0 -236
  144. package/dist/core/hooks/v2/event-emitter.js +0 -115
  145. package/dist/core/hooks/v2/executor.js +0 -282
  146. package/dist/core/hooks/v2/index.js +0 -25
  147. package/dist/core/hooks/v2/lifecycle.js +0 -104
  148. package/dist/core/hooks/v2/loader.js +0 -216
  149. package/dist/core/hooks/v2/matcher.js +0 -125
  150. package/dist/core/hooks/v2/trust.js +0 -143
  151. package/dist/core/hooks/v2/types.js +0 -86
  152. package/dist/core/hooks/worktree-events.js +0 -158
  153. package/dist/core/hooks.js +0 -415
  154. package/dist/core/image/renderer.js +0 -71
  155. package/dist/core/index-store.js +0 -260
  156. package/dist/core/init/detector.js +0 -582
  157. package/dist/core/init/template-renderer.js +0 -242
  158. package/dist/core/jobs/registry.js +0 -462
  159. package/dist/core/ledger/results-tsv.js +0 -142
  160. package/dist/core/log-discipline/stdout-redirect.js +0 -51
  161. package/dist/core/lsp/cache.js +0 -105
  162. package/dist/core/lsp/client.js +0 -1229
  163. package/dist/core/lsp/language-detect.js +0 -66
  164. package/dist/core/lsp/post-edit-diagnostics.js +0 -171
  165. package/dist/core/lsp/server-detect.js +0 -173
  166. package/dist/core/lsp/symbol-cache.js +0 -162
  167. package/dist/core/lsp/symbol-tools.js +0 -664
  168. package/dist/core/mcp/client.js +0 -385
  169. package/dist/core/mcp/http-server.js +0 -553
  170. package/dist/core/mcp/orchestrator-config.js +0 -192
  171. package/dist/core/mcp/orchestrator-tools.js +0 -806
  172. package/dist/core/mcp/permission.js +0 -190
  173. package/dist/core/mcp/registry.js +0 -193
  174. package/dist/core/mcp/server-tools.js +0 -219
  175. package/dist/core/mcp/server.js +0 -397
  176. package/dist/core/mcp/trust.js +0 -91
  177. package/dist/core/memory/dual-write.js +0 -416
  178. package/dist/core/memory/passive-extract.js +0 -130
  179. package/dist/core/memory/phase1-kinds.js +0 -20
  180. package/dist/core/memory/secret-scanner.js +0 -304
  181. package/dist/core/memory-sync/queue.js +0 -170
  182. package/dist/core/metrics/extract.js +0 -113
  183. package/dist/core/modes/roo-modes.js +0 -68
  184. package/dist/core/onboarding/ensure-initialized.js +0 -133
  185. package/dist/core/onboarding/marker.js +0 -111
  186. package/dist/core/onboarding/telemetry-state.js +0 -108
  187. package/dist/core/output-style/presets.js +0 -176
  188. package/dist/core/output-style/state.js +0 -185
  189. package/dist/core/path-security.js +0 -345
  190. package/dist/core/permission.js +0 -369
  191. package/dist/core/permissions/auto-classifier.js +0 -124
  192. package/dist/core/permissions/bash-parser.js +0 -371
  193. package/dist/core/permissions/circuit-breaker.js +0 -83
  194. package/dist/core/permissions/constrained-edit.js +0 -91
  195. package/dist/core/permissions/gate.js +0 -278
  196. package/dist/core/permissions/index.js +0 -20
  197. package/dist/core/permissions/mode.js +0 -174
  198. package/dist/core/permissions/network-egress.js +0 -137
  199. package/dist/core/permissions/state.js +0 -241
  200. package/dist/core/permissions/tool-class.js +0 -107
  201. package/dist/core/plan-mode/ui-state.js +0 -51
  202. package/dist/core/plans/plan-artifact.js +0 -721
  203. package/dist/core/policy-limits/etag-store.js +0 -122
  204. package/dist/core/prd-check/parser.js +0 -215
  205. package/dist/core/prd-check/reporter.js +0 -127
  206. package/dist/core/prd-check/session-review.js +0 -557
  207. package/dist/core/prd-check/verifiers.js +0 -223
  208. package/dist/core/prompt-cache/client-cache.js +0 -99
  209. package/dist/core/prompts/assembly.js +0 -29
  210. package/dist/core/prompts/registry.js +0 -364
  211. package/dist/core/pugi-gitignore.js +0 -52
  212. package/dist/core/pugi-md/cc-compat-rules.js +0 -735
  213. package/dist/core/pugi-md/context-injector.js +0 -76
  214. package/dist/core/pugi-md/walk-up.js +0 -207
  215. package/dist/core/python/uv-installer.js +0 -270
  216. package/dist/core/python/uv-resolver.js +0 -83
  217. package/dist/core/rate-limit/narrator.js +0 -146
  218. package/dist/core/recipes/cli-types.js +0 -20
  219. package/dist/core/recipes/loader.js +0 -103
  220. package/dist/core/recipes/runner.js +0 -345
  221. package/dist/core/recipes/schema.js +0 -587
  222. package/dist/core/release-notes/parser.js +0 -241
  223. package/dist/core/release-notes/state.js +0 -116
  224. package/dist/core/repl/ask.js +0 -512
  225. package/dist/core/repl/cancellation.js +0 -98
  226. package/dist/core/repl/cap-warning.js +0 -91
  227. package/dist/core/repl/clipboard-read.js +0 -174
  228. package/dist/core/repl/dispatch-fsm.js +0 -220
  229. package/dist/core/repl/engine-bridge.js +0 -303
  230. package/dist/core/repl/history-search.js +0 -175
  231. package/dist/core/repl/history.js +0 -182
  232. package/dist/core/repl/kill-ring.js +0 -138
  233. package/dist/core/repl/model-pricing.js +0 -135
  234. package/dist/core/repl/privacy-banner.js +0 -71
  235. package/dist/core/repl/session.js +0 -4962
  236. package/dist/core/repl/slash-commands.js +0 -747
  237. package/dist/core/repl/store/index.js +0 -12
  238. package/dist/core/repl/store/jsonl-log.js +0 -321
  239. package/dist/core/repl/store/lockfile.js +0 -155
  240. package/dist/core/repl/store/session-store.js +0 -821
  241. package/dist/core/repl/store/types.js +0 -44
  242. package/dist/core/repl/store/uuid-v7.js +0 -68
  243. package/dist/core/repl/tool-route.js +0 -382
  244. package/dist/core/repl/workspace-context.js +0 -206
  245. package/dist/core/repo-map/build.js +0 -125
  246. package/dist/core/repo-map/cache.js +0 -185
  247. package/dist/core/repo-map/extractor.js +0 -254
  248. package/dist/core/repo-map/formatter.js +0 -145
  249. package/dist/core/repo-map/page-rank.js +0 -105
  250. package/dist/core/repo-map/scanner.js +0 -211
  251. package/dist/core/retro/git-collector.js +0 -251
  252. package/dist/core/retro/health-card.js +0 -25
  253. package/dist/core/retro/metrics.js +0 -342
  254. package/dist/core/retro/narrative.js +0 -249
  255. package/dist/core/retro/plane-collector.js +0 -274
  256. package/dist/core/retro/pr-issue-link.js +0 -65
  257. package/dist/core/retro/types.js +0 -16
  258. package/dist/core/retry-budget/budget.js +0 -284
  259. package/dist/core/retry-budget/index.js +0 -5
  260. package/dist/core/retry-budget/retry-cap.js +0 -74
  261. package/dist/core/routing/lead-worker.js +0 -43
  262. package/dist/core/routing/pre-flight-estimator.js +0 -108
  263. package/dist/core/runs/run-tree.js +0 -103
  264. package/dist/core/sandboxing/adapter.js +0 -29
  265. package/dist/core/sandboxing/index.js +0 -49
  266. package/dist/core/sandboxing/none.js +0 -19
  267. package/dist/core/sandboxing/seatbelt.js +0 -183
  268. package/dist/core/security/injection-scanner.js +0 -367
  269. package/dist/core/security/output-filter.js +0 -418
  270. package/dist/core/session/env-file.js +0 -105
  271. package/dist/core/session/section-budgets.js +0 -140
  272. package/dist/core/session.js +0 -377
  273. package/dist/core/settings.js +0 -400
  274. package/dist/core/share/formatter.js +0 -271
  275. package/dist/core/share/redactor.js +0 -221
  276. package/dist/core/share/uploader.js +0 -267
  277. package/dist/core/skills/defaults.js +0 -457
  278. package/dist/core/skills/loader.js +0 -454
  279. package/dist/core/skills/sources.js +0 -480
  280. package/dist/core/skills/trust.js +0 -172
  281. package/dist/core/smoke/headless-driver.js +0 -174
  282. package/dist/core/smoke/orchestrator.js +0 -194
  283. package/dist/core/smoke/runner.js +0 -238
  284. package/dist/core/smoke/scenario-parser.js +0 -316
  285. package/dist/core/statusline.js +0 -99
  286. package/dist/core/subagents/dispatcher-real.js +0 -600
  287. package/dist/core/subagents/dispatcher.js +0 -352
  288. package/dist/core/subagents/index.js +0 -39
  289. package/dist/core/subagents/isolation-matrix.js +0 -213
  290. package/dist/core/subagents/spawn.js +0 -101
  291. package/dist/core/telemetry/emitter.js +0 -229
  292. package/dist/core/telemetry/queue.js +0 -251
  293. package/dist/core/theme/context.js +0 -91
  294. package/dist/core/theme/presets.js +0 -228
  295. package/dist/core/theme/state.js +0 -181
  296. package/dist/core/todos/invariant.js +0 -10
  297. package/dist/core/todos/state.js +0 -177
  298. package/dist/core/tool-schema/compressor.js +0 -89
  299. package/dist/core/transport/version-interceptor.js +0 -166
  300. package/dist/core/trust.js +0 -109
  301. package/dist/core/tui/thinking-block.js +0 -64
  302. package/dist/core/vim/keymap.js +0 -288
  303. package/dist/core/vim/state.js +0 -92
  304. package/dist/core/watch-markers/marker-watcher.js +0 -133
  305. package/dist/core/worktree/include-parser.js +0 -249
  306. package/dist/core/worktree-manager/cleanup.js +0 -123
  307. package/dist/core/worktree-manager/manager.js +0 -303
  308. package/dist/index.js +0 -44
  309. package/dist/runtime/bootstrap.js +0 -190
  310. package/dist/runtime/cli.js +0 -8121
  311. package/dist/runtime/commands/agents.js +0 -385
  312. package/dist/runtime/commands/budget.js +0 -192
  313. package/dist/runtime/commands/cancel.js +0 -231
  314. package/dist/runtime/commands/chain.js +0 -489
  315. package/dist/runtime/commands/codegraph-status.js +0 -227
  316. package/dist/runtime/commands/compact.js +0 -297
  317. package/dist/runtime/commands/config.js +0 -595
  318. package/dist/runtime/commands/cost.js +0 -199
  319. package/dist/runtime/commands/delegate.js +0 -312
  320. package/dist/runtime/commands/dispatch.js +0 -126
  321. package/dist/runtime/commands/doctor.js +0 -579
  322. package/dist/runtime/commands/feedback.js +0 -184
  323. package/dist/runtime/commands/hooks.js +0 -187
  324. package/dist/runtime/commands/init.js +0 -254
  325. package/dist/runtime/commands/lsp.js +0 -368
  326. package/dist/runtime/commands/mcp.js +0 -935
  327. package/dist/runtime/commands/memory.js +0 -582
  328. package/dist/runtime/commands/model.js +0 -237
  329. package/dist/runtime/commands/onboarding.js +0 -275
  330. package/dist/runtime/commands/patch.js +0 -128
  331. package/dist/runtime/commands/permissions.js +0 -112
  332. package/dist/runtime/commands/plan.js +0 -143
  333. package/dist/runtime/commands/prd-check.js +0 -285
  334. package/dist/runtime/commands/privacy.js +0 -107
  335. package/dist/runtime/commands/recipe.js +0 -325
  336. package/dist/runtime/commands/redo-blob-store.js +0 -92
  337. package/dist/runtime/commands/redo.js +0 -361
  338. package/dist/runtime/commands/release-notes.js +0 -229
  339. package/dist/runtime/commands/repo-map.js +0 -95
  340. package/dist/runtime/commands/report.js +0 -299
  341. package/dist/runtime/commands/resume.js +0 -118
  342. package/dist/runtime/commands/review-consensus.js +0 -414
  343. package/dist/runtime/commands/rewind.js +0 -333
  344. package/dist/runtime/commands/roster.js +0 -117
  345. package/dist/runtime/commands/sessions.js +0 -163
  346. package/dist/runtime/commands/share.js +0 -316
  347. package/dist/runtime/commands/skills.js +0 -401
  348. package/dist/runtime/commands/status.js +0 -186
  349. package/dist/runtime/commands/stickers.js +0 -82
  350. package/dist/runtime/commands/style.js +0 -194
  351. package/dist/runtime/commands/theme.js +0 -196
  352. package/dist/runtime/commands/undo.js +0 -361
  353. package/dist/runtime/commands/update.js +0 -289
  354. package/dist/runtime/commands/vim.js +0 -140
  355. package/dist/runtime/commands/worktree.js +0 -177
  356. package/dist/runtime/commands/worktrees.js +0 -155
  357. package/dist/runtime/deprecation-warning.js +0 -69
  358. package/dist/runtime/engine-exit-code.js +0 -50
  359. package/dist/runtime/headless-repl.js +0 -195
  360. package/dist/runtime/headless.js +0 -548
  361. package/dist/runtime/load-hooks-or-exit.js +0 -71
  362. package/dist/runtime/plan-decompose.js +0 -531
  363. package/dist/runtime/sigint-guard.js +0 -272
  364. package/dist/runtime/stream-renderer.js +0 -195
  365. package/dist/runtime/update-check.js +0 -294
  366. package/dist/runtime/version.js +0 -65
  367. package/dist/runtime/worktree-bootstrap.js +0 -579
  368. package/dist/skills/bundled/batch.js +0 -617
  369. package/dist/skills/bundled/index.js +0 -45
  370. package/dist/skills/bundled/loop.js +0 -358
  371. package/dist/skills/bundled/remember.js +0 -383
  372. package/dist/skills/bundled/simplify.js +0 -289
  373. package/dist/skills/bundled/skillify.js +0 -373
  374. package/dist/skills/bundled/stuck.js +0 -558
  375. package/dist/skills/bundled/verify.js +0 -439
  376. package/dist/testing/vcr.js +0 -486
  377. package/dist/tools/agent-tool.js +0 -229
  378. package/dist/tools/apply-patch.js +0 -556
  379. package/dist/tools/ask-user-question.js +0 -337
  380. package/dist/tools/ask-user.js +0 -115
  381. package/dist/tools/bash.js +0 -1238
  382. package/dist/tools/brief.js +0 -224
  383. package/dist/tools/cron.js +0 -433
  384. package/dist/tools/enter-worktree.js +0 -250
  385. package/dist/tools/exit-worktree.js +0 -147
  386. package/dist/tools/file-tools.js +0 -553
  387. package/dist/tools/http-request.js +0 -336
  388. package/dist/tools/lsp-tools.js +0 -565
  389. package/dist/tools/mcp-tool.js +0 -260
  390. package/dist/tools/multi-edit.js +0 -361
  391. package/dist/tools/powershell.js +0 -268
  392. package/dist/tools/registry.js +0 -166
  393. package/dist/tools/server-tools.js +0 -892
  394. package/dist/tools/skill-tool.js +0 -96
  395. package/dist/tools/sleep.js +0 -99
  396. package/dist/tools/synthetic-output.js +0 -133
  397. package/dist/tools/tasks.js +0 -208
  398. package/dist/tools/todo-write.js +0 -184
  399. package/dist/tools/verify-plan-execution.js +0 -295
  400. package/dist/tools/web-fetch-injection-scanner.js +0 -207
  401. package/dist/tools/web-fetch.js +0 -720
  402. package/dist/tools/web-search.js +0 -458
  403. package/dist/tui/agent-progress-card.js +0 -111
  404. package/dist/tui/agent-tree-pane.js +0 -9
  405. package/dist/tui/agent-tree.js +0 -87
  406. package/dist/tui/ask-cli.js +0 -52
  407. package/dist/tui/ask-modal.js +0 -211
  408. package/dist/tui/ask-user-question-chips.js +0 -315
  409. package/dist/tui/ask-user-question-prompt.js +0 -203
  410. package/dist/tui/compact-banner.js +0 -81
  411. package/dist/tui/conversation-pane.js +0 -164
  412. package/dist/tui/cost-table.js +0 -111
  413. package/dist/tui/device-flow.js +0 -142
  414. package/dist/tui/doctor-table.js +0 -46
  415. package/dist/tui/feedback-prompt.js +0 -156
  416. package/dist/tui/input-box.js +0 -732
  417. package/dist/tui/login-picker.js +0 -69
  418. package/dist/tui/markdown-render.js +0 -266
  419. package/dist/tui/multi-file-diff-approval.js +0 -375
  420. package/dist/tui/onboarding-wizard.js +0 -240
  421. package/dist/tui/permissions-picker.js +0 -86
  422. package/dist/tui/render.js +0 -160
  423. package/dist/tui/repl-render.js +0 -770
  424. package/dist/tui/repl-splash-art.js +0 -64
  425. package/dist/tui/repl-splash-mascot.js +0 -154
  426. package/dist/tui/repl-splash.js +0 -117
  427. package/dist/tui/repl.js +0 -378
  428. package/dist/tui/slash-palette.js +0 -106
  429. package/dist/tui/splash-data.js +0 -61
  430. package/dist/tui/splash.js +0 -31
  431. package/dist/tui/status-bar.js +0 -209
  432. package/dist/tui/status-table.js +0 -7
  433. package/dist/tui/stickers-art.js +0 -136
  434. package/dist/tui/style-table.js +0 -28
  435. package/dist/tui/theme-table.js +0 -29
  436. package/dist/tui/thinking-spinner.js +0 -123
  437. package/dist/tui/tool-stream-pane.js +0 -140
  438. package/dist/tui/update-banner.js +0 -33
  439. package/dist/tui/vim-input.js +0 -267
  440. package/dist/tui/welcome-banner.js +0 -107
  441. package/dist/tui/welcome-data.js +0 -293
  442. package/dist/tui/workspace-context.js +0 -105
  443. package/docs/examples/codegraph.mcp.json +0 -10
  444. package/test/scenarios/codegen-create-file.scenario.txt +0 -13
  445. package/test/scenarios/compact-force.scenario.txt +0 -12
  446. package/test/scenarios/identity.scenario.txt +0 -11
  447. package/test/scenarios/persona-handoff.scenario.txt +0 -12
  448. package/test/scenarios/walkback.scenario.txt +0 -12
@@ -1,553 +0,0 @@
1
- /**
2
- * file-tools - Pugi CLI file/bash/glob/grep tool surface.
3
- *
4
- * Workspace-binding contract (CEO red-alert follow-up):
5
- *
6
- * Every tool dispatch path threads `ctx.root` from the operator's
7
- * `process.cwd()` through `EngineTask.workspaceRoot` ->
8
- * `native-pugi.run()` -> `toolCtx.root` -> here. Tools call
9
- * `resolveWorkspacePath(ctx.root, path)` for every on-disk operation
10
- * so a dispatched specialist (e.g. Hiroshi writing tic-tac-toe HTML)
11
- * produces files in the OPERATOR'S cwd, never in a server-side temp
12
- * space. The path-security gate refuses traversal (`../etc/passwd`,
13
- * URL-encoded variants, symlink escapes at the target).
14
- *
15
- * Wiring chain:
16
- * 1. runtime/cli.ts: workspaceRoot = process.cwd()
17
- * 2. EngineTask.workspaceRoot threads through to native-pugi.run().
18
- * 3. native-pugi: const root = task.workspaceRoot
19
- * 4. tool-bridge: passes ctx.root to file-tools / bash.
20
- * 5. file-tools: resolveWorkspacePath(ctx.root, path).
21
- *
22
- * The contract is locked by `test/tools-write-to-workspace.spec.ts`
23
- * (6 cases covering relative + nested + absolute paths + traversal
24
- * refusal). If any layer of the chain regressed silently, dispatched
25
- * files would land in `/tmp` instead of the operator's repo, which
26
- * is the same failure surface as the menu-mode anti-pattern the
27
- * sibling commits close.
28
- */
29
- import { spawnSync } from 'node:child_process';
30
- import { existsSync, readFileSync, realpathSync, renameSync, statSync, writeFileSync } from 'node:fs';
31
- import { dirname, isAbsolute, relative } from 'node:path';
32
- import { globSync } from 'node:fs';
33
- import { decidePermission } from '../core/permission.js';
34
- import { StaleReadError, createReadRecord, hashContent, } from '../core/file-cache.js';
35
- import { resolveWorkspacePath } from '../core/path-security.js';
36
- import { scanForInjection, summarizeFindings } from '../core/security/injection-scanner.js';
37
- import { recordFileMutation, recordToolCall, recordToolResult } from '../core/session.js';
38
- /**
39
- * WriteGate marker — thrown by `gateOnCancellation` when the
40
- * caller supplied a cancellation token that has already aborted. The
41
- * tool dispatch loop in `tool-bridge.ts` recognises the name and folds
42
- * the throw into a `status: 'aborted'` tool result rather than a hard
43
- * error so the loop terminates cleanly.
44
- */
45
- export class OperatorAbortedError extends Error {
46
- constructor(toolName) {
47
- super(`operator_aborted: ${toolName} refused — operator cancelled the dispatch.`);
48
- this.name = 'OperatorAbortedError';
49
- }
50
- }
51
- // Re-export StaleReadError so tool-bridge / test consumers can import
52
- // the typed error from a single file-tools surface alongside
53
- // OperatorAbortedError. Same shape as the existing OperatorAbortedError
54
- // re-surface pattern.
55
- export { StaleReadError } from '../core/file-cache.js';
56
- /**
57
- * WriteGate: refuse the tool dispatch when the active
58
- * cancellation token has aborted. Idempotent (the token's `isAborted`
59
- * is a getter, no side effects). Returns void on the happy path so the
60
- * tool can proceed; throws `OperatorAbortedError` when cancelled.
61
- *
62
- * The audit trail still gets the call: `recordToolCall` already fired
63
- * upstream of this guard so the abort + reason are persisted. The
64
- * matching `recordToolResult` is fired by the caller in its catch
65
- * block with `status: 'cancelled'` (see existing path for `error`).
66
- */
67
- export function gateOnCancellation(ctx, toolName) {
68
- if (ctx.cancellation && ctx.cancellation.isAborted) {
69
- throw new OperatorAbortedError(toolName);
70
- }
71
- }
72
- /**
73
- * Re-check the permission decision against the *resolved* real path so
74
- * a workspace-local symlink (`alias -> .env`) cannot bypass the protected
75
- * basename check. The first `decidePermission` call sees only the user
76
- * input (`alias`); this second call sees the realpath relative to root
77
- * (`.env`), which `protectedTargetReason` recognises.
78
- *
79
- * Returns the resolved absolute path. Throws when the resolved path is
80
- * gated by anything other than `allow`.
81
- */
82
- function permissionGatedResolve(ctx, inputPath, action, toolName) {
83
- const resolved = resolveWorkspacePath(ctx.root, inputPath);
84
- let realPath;
85
- try {
86
- realPath = realpathSync.native(resolved);
87
- }
88
- catch (error) {
89
- // For writes to a file that does not yet exist there is no symlink
90
- // to follow; fall back to the resolved path so the workspace check
91
- // already done in `resolveWorkspacePath` is the only gate.
92
- const code = error.code;
93
- if (code === 'ENOENT' || code === 'ENOTDIR')
94
- return resolved;
95
- throw error;
96
- }
97
- if (realPath === resolved)
98
- return resolved;
99
- const realRelative = relative(ctx.root, realPath);
100
- const realDecision = decidePermission({ tool: toolName, kind: action, target: realRelative }, ctx.settings, ctx.root);
101
- if (realDecision.decision !== 'allow') {
102
- throw new Error(`Permission ${realDecision.decision} for ${action} ${realRelative} (via symlink ${inputPath}): ${realDecision.reason}`);
103
- }
104
- return realPath;
105
- }
106
- export function readTool(ctx, path) {
107
- const toolCallId = recordToolCall(ctx.session, 'read', path);
108
- // WriteGate: fail fast on operator cancel BEFORE permission
109
- // decision so a half-second post-cancel race never lands the read.
110
- if (ctx.cancellation && ctx.cancellation.isAborted) {
111
- const reason = 'operator_aborted: read refused';
112
- recordToolResult(ctx.session, toolCallId, 'cancelled', reason);
113
- throw new OperatorAbortedError('read');
114
- }
115
- const decision = decidePermission({ tool: 'read', kind: 'read', target: path }, ctx.settings, ctx.root);
116
- if (decision.decision !== 'allow') {
117
- const reason = `Permission ${decision.decision} for read ${path}: ${decision.reason}`;
118
- recordToolResult(ctx.session, toolCallId, 'error', reason);
119
- throw new Error(reason);
120
- }
121
- let resolved;
122
- try {
123
- resolved = permissionGatedResolve(ctx, path, 'read', 'read');
124
- }
125
- catch (error) {
126
- const reason = error.message;
127
- recordToolResult(ctx.session, toolCallId, 'error', reason);
128
- throw error;
129
- }
130
- const content = readFileSync(resolved, 'utf8');
131
- ctx.readCache.set(createReadRecord(ctx.root, path, content, 'read_tool'));
132
- recordToolResult(ctx.session, toolCallId, 'success', `Read ${path}`);
133
- return content;
134
- }
135
- export function writeTool(ctx, path, content) {
136
- const toolCallId = recordToolCall(ctx.session, 'write', path);
137
- // WriteGate: refuse the write when the operator has cancelled
138
- // the dispatch. The audit log captures the cancellation reason so a
139
- // post-mortem can distinguish operator_aborted from settings-deny.
140
- if (ctx.cancellation && ctx.cancellation.isAborted) {
141
- const reason = 'operator_aborted: write refused';
142
- recordToolResult(ctx.session, toolCallId, 'cancelled', reason);
143
- throw new OperatorAbortedError('write');
144
- }
145
- const decision = decidePermission({ tool: 'write', kind: 'edit', target: path }, ctx.settings, ctx.root);
146
- if (decision.decision !== 'allow') {
147
- const reason = `Permission ${decision.decision} for write ${path}: ${decision.reason}`;
148
- recordToolResult(ctx.session, toolCallId, 'error', reason);
149
- throw new Error(reason);
150
- }
151
- let resolved;
152
- try {
153
- resolved = permissionGatedResolve(ctx, path, 'edit', 'write');
154
- }
155
- catch (error) {
156
- const reason = error.message;
157
- recordToolResult(ctx.session, toolCallId, 'error', reason);
158
- throw error;
159
- }
160
- const existed = existsSync(resolved);
161
- // stale-read gate for writeTool's update-existing path. The
162
- // model uses writeTool for two distinct intents:
163
- //
164
- // - create-new: path does not exist on disk. There is no prior
165
- // read to validate against; skip the gate. This is the
166
- // intentional escape hatch the leak spec also calls out.
167
- // - overwrite-existing: path exists. Without the gate the model
168
- // could blind-clobber an externally-modified file, losing the
169
- // concurrent change silently. Force the model to re-read first.
170
- //
171
- // We deliberately apply the SAME stale-validation primitive editTool
172
- // uses so the two write surfaces stay symmetric and a future fix to
173
- // either one cannot accidentally weaken the other.
174
- let before;
175
- if (existed) {
176
- before = readFileSync(resolved, 'utf8');
177
- const currentStat = statSync(resolved);
178
- const validation = ctx.readCache.validate(ctx.root, path, currentStat.mtimeMs, before);
179
- if (validation.stale) {
180
- const reason = `stale_read: write ${path} refused — ${validation.detail}`;
181
- recordToolResult(ctx.session, toolCallId, 'error', reason);
182
- throw new StaleReadError(path, validation.reason, validation.detail);
183
- }
184
- }
185
- const tmp = `${resolved}.pugi-tmp-${Date.now()}`;
186
- writeFileSync(tmp, content, { encoding: 'utf8', mode: 0o600 });
187
- renameSync(tmp, resolved);
188
- // Injection scan (ported an external utility,
189
- // Apache-2.0). Scan the BODY (never the path — path security is
190
- // owned by `path-security.ts`). Findings are SURFACED as an extra
191
- // line on the session tool-result, never block the write. Hard-
192
- // block requires a separate CEO-signed PR. Failure here must NOT
193
- // throw: a buggy scanner cannot rugpull the write that already
194
- // landed on disk above.
195
- surfaceInjectionWarning(ctx, toolCallId, 'write', path, content);
196
- // Refresh the cache with the post-write content so the model can
197
- // chain a follow-up read+edit on the same file without an extra
198
- // round-trip. Same pattern editTool uses below.
199
- ctx.readCache.set(createReadRecord(ctx.root, path, content, 'read_tool'));
200
- recordFileMutation(ctx.session, {
201
- toolCallId,
202
- path,
203
- operation: existed ? 'update' : 'create',
204
- beforeHash: before ? hashContent(before) : undefined,
205
- afterHash: hashContent(content),
206
- });
207
- recordToolResult(ctx.session, toolCallId, 'success', `${existed ? 'Updated' : 'Created'} ${path}`);
208
- }
209
- /**
210
- * Surface an injection-scan warning on a file write/edit BODY. The
211
- * scan never blocks — it folds findings into the session as a
212
- * `tool_result` with status `warn` so an operator (or SOC pipeline
213
- * tailing `<workspace>/.pugi/events.jsonl`) sees the signal without a
214
- * mid-dispatch rollback.
215
- *
216
- * Wrapped in try/catch so a malformed scanner never crashes the tool
217
- * loop — the write itself has already landed on disk by the time we
218
- * call this.
219
- */
220
- function surfaceInjectionWarning(ctx, triggeringToolCallId, tool, path, body) {
221
- try {
222
- const findings = scanForInjection(body);
223
- if (findings.length === 0)
224
- return;
225
- const summary = summarizeFindings(findings);
226
- const warnCallId = recordToolCall(ctx.session, 'injection_warning', path);
227
- const message = `injection_warning: ${tool} ${path} — ${summary.total} pattern(s) ` +
228
- `(score=${summary.score}, kinds=${summary.kinds.join('|')}). ` +
229
- `Triggering call: ${triggeringToolCallId}. ` +
230
- `Detector: external-injection-patterns. Write was NOT blocked.`;
231
- recordToolResult(ctx.session, warnCallId, 'success', message);
232
- }
233
- catch {
234
- // Scanner failure must NEVER throw — the write has already
235
- // landed and the tool loop has to continue. Silent no-op
236
- // mirrors the audit-trail contract.
237
- }
238
- }
239
- export function editTool(ctx, path, oldString, newString) {
240
- const toolCallId = recordToolCall(ctx.session, 'edit', path);
241
- // WriteGate: refuse the edit when the operator has cancelled
242
- // the dispatch. Edits are higher-risk than reads — surface the abort
243
- // BEFORE we even consult permissions so a cancel-during-tool-loop
244
- // never partially mutates the workspace.
245
- if (ctx.cancellation && ctx.cancellation.isAborted) {
246
- const reason = 'operator_aborted: edit refused';
247
- recordToolResult(ctx.session, toolCallId, 'cancelled', reason);
248
- throw new OperatorAbortedError('edit');
249
- }
250
- const decision = decidePermission({ tool: 'edit', kind: 'edit', target: path }, ctx.settings, ctx.root);
251
- if (decision.decision !== 'allow') {
252
- const reason = `Permission ${decision.decision} for edit ${path}: ${decision.reason}`;
253
- recordToolResult(ctx.session, toolCallId, 'error', reason);
254
- throw new Error(reason);
255
- }
256
- let resolved;
257
- try {
258
- resolved = permissionGatedResolve(ctx, path, 'edit', 'edit');
259
- }
260
- catch (error) {
261
- const reason = error.message;
262
- recordToolResult(ctx.session, toolCallId, 'error', reason);
263
- throw error;
264
- }
265
- // stale-read gate. Validate the model's read-time view of
266
- // the file against the on-disk state BEFORE applying the mutation.
267
- // We read disk content once and feed it to the validator so a single
268
- // syscall covers both the gate decision AND the oldString/newString
269
- // replacement below.
270
- const before = readFileSync(resolved, 'utf8');
271
- const currentStat = statSync(resolved);
272
- const validation = ctx.readCache.validate(ctx.root, path, currentStat.mtimeMs, before);
273
- if (validation.stale) {
274
- const reason = `stale_read: edit ${path} refused — ${validation.detail}`;
275
- recordToolResult(ctx.session, toolCallId, 'error', reason);
276
- throw new StaleReadError(path, validation.reason, validation.detail);
277
- }
278
- const currentHash = hashContent(before);
279
- const matches = before.split(oldString).length - 1;
280
- if (matches === 0) {
281
- const reason = `Cannot edit ${path}: oldString not found`;
282
- recordToolResult(ctx.session, toolCallId, 'error', reason);
283
- throw new Error(reason);
284
- }
285
- if (matches > 1) {
286
- const reason = `Cannot edit ${path}: oldString is not unique`;
287
- recordToolResult(ctx.session, toolCallId, 'error', reason);
288
- throw new Error(reason);
289
- }
290
- const after = before.replace(oldString, newString);
291
- const tmp = `${resolved}.pugi-tmp-${Date.now()}`;
292
- writeFileSync(tmp, after, { encoding: 'utf8', mode: 0o600 });
293
- renameSync(tmp, resolved);
294
- // Injection scan (ported an external utility,
295
- // Apache-2.0). We scan the NEW SUBSTRING the model is inserting,
296
- // not the full post-edit file — the rest of the file is operator-
297
- // owned content that pre-dates this dispatch. False-positive on
298
- // legitimate prose that mentions banned phrases is the worst
299
- // outcome and the warn-only contract bounds the cost.
300
- surfaceInjectionWarning(ctx, toolCallId, 'edit', path, newString);
301
- ctx.readCache.set(createReadRecord(ctx.root, path, after, 'read_tool'));
302
- recordFileMutation(ctx.session, {
303
- toolCallId,
304
- path,
305
- operation: 'update',
306
- beforeHash: currentHash,
307
- afterHash: hashContent(after),
308
- });
309
- recordToolResult(ctx.session, toolCallId, 'success', `Edited ${path}`);
310
- }
311
- export function globTool(ctx, pattern) {
312
- const toolCallId = recordToolCall(ctx.session, 'glob', pattern);
313
- // WriteGate: cancel-aware short-circuit. Glob is read-only but
314
- // can be expensive on large trees; respecting the abort here keeps
315
- // the tool loop responsive when the operator hits Ctrl+C mid-scan.
316
- if (ctx.cancellation && ctx.cancellation.isAborted) {
317
- const reason = 'operator_aborted: glob refused';
318
- recordToolResult(ctx.session, toolCallId, 'cancelled', reason);
319
- throw new OperatorAbortedError('glob');
320
- }
321
- // Pugi globs are workspace-scoped. Reject any pattern that could enumerate
322
- // outside the workspace:
323
- // 1. absolute paths (`/etc/**/*`) — globSync resolves these against `/`
324
- // regardless of `cwd`, so they fan out outside the repo.
325
- // 2. `..` as a path SEGMENT (`../*`, `src/../etc`) — parent traversal.
326
- // A substring check would over-reject legitimate names like
327
- // `src/v1..v2/*` so we split on `/` instead.
328
- if (isAbsolute(pattern)) {
329
- const reason = `Absolute glob patterns are not allowed: ${pattern}`;
330
- recordToolResult(ctx.session, toolCallId, 'error', reason);
331
- throw new Error(reason);
332
- }
333
- if (pattern.split('/').some((segment) => segment === '..')) {
334
- const reason = `Glob pattern escapes workspace via '..' segment: ${pattern}`;
335
- recordToolResult(ctx.session, toolCallId, 'error', reason);
336
- throw new Error(reason);
337
- }
338
- const results = globSync(pattern, {
339
- cwd: ctx.root,
340
- withFileTypes: false,
341
- exclude: ['node_modules/**', 'dist/**', '.git/**', '.pugi/**'],
342
- })
343
- .map((entry) => String(entry))
344
- .slice(0, 500);
345
- recordToolResult(ctx.session, toolCallId, 'success', `Glob matched ${results.length} paths`);
346
- return results;
347
- }
348
- export function grepTool(ctx, query) {
349
- const toolCallId = recordToolCall(ctx.session, 'grep', query);
350
- // WriteGate: refuse before scanning. Grep walks the whole
351
- // workspace and can take seconds on a large repo; check abort first
352
- // so a cancel mid-scan returns immediately rather than after the
353
- // full walk completes.
354
- if (ctx.cancellation && ctx.cancellation.isAborted) {
355
- const reason = 'operator_aborted: grep refused';
356
- recordToolResult(ctx.session, toolCallId, 'cancelled', reason);
357
- throw new OperatorAbortedError('grep');
358
- }
359
- const files = globTool(ctx, '**/*').filter((path) => !path.endsWith('/'));
360
- const matches = [];
361
- for (const path of files) {
362
- if (matches.length >= 200)
363
- break;
364
- // WriteGate: poll abort inside the file loop so a cancel
365
- // arriving mid-scan terminates early. The per-file branch keeps
366
- // the responsiveness bounded by the slowest single-file read.
367
- if (ctx.cancellation && ctx.cancellation.isAborted) {
368
- const reason = `operator_aborted: grep stopped mid-scan after ${matches.length} matches`;
369
- recordToolResult(ctx.session, toolCallId, 'cancelled', reason);
370
- throw new OperatorAbortedError('grep');
371
- }
372
- // Permission gate every file read individually — grep used to bypass
373
- // `decidePermission` and could surface lines from protected files
374
- // (.env, *.sql, *.pem, ~/.ssh/**) when invoked from a directory walk.
375
- const decision = decidePermission({ tool: 'grep', kind: 'read', target: path }, ctx.settings, ctx.root);
376
- if (decision.decision !== 'allow')
377
- continue;
378
- let fullPath;
379
- try {
380
- // `permissionGatedResolve` follows symlinks and re-checks the
381
- // realpath against the permission rules. Without this an attacker
382
- // could plant `alias -> .env` inside the workspace and recover
383
- // secrets through `pugi explain .` because the initial decision
384
- // only saw the unprotected basename `alias`.
385
- fullPath = permissionGatedResolve(ctx, path, 'read', 'grep');
386
- }
387
- catch {
388
- continue;
389
- }
390
- if (dirname(fullPath).includes('node_modules'))
391
- continue;
392
- try {
393
- const lines = readFileSync(fullPath, 'utf8').split('\n');
394
- lines.forEach((text, index) => {
395
- if (matches.length < 200 && text.includes(query)) {
396
- matches.push({ path: relative(ctx.root, fullPath), line: index + 1, text });
397
- }
398
- });
399
- }
400
- catch {
401
- // Binary or unreadable files are ignored by the scaffolded grep tool.
402
- }
403
- }
404
- recordToolResult(ctx.session, toolCallId, 'success', `Grep matched ${matches.length} lines`);
405
- return matches;
406
- }
407
- /**
408
- * Workspace-scoped bash tool. Sized for the M1 engine adapter:
409
- * - Runs through `/bin/sh -c <command>` so the model can use pipes,
410
- * redirection, and shell builtins (`ls | wc -l`, `git status`).
411
- * - `cwd` is pinned to the workspace root so a stray `cd /` cannot
412
- * leak commands outside the repo (the child process inherits root
413
- * filesystem visibility — destructive patterns are blocked by
414
- * `decidePermission`, not by chroot).
415
- * - Output capped at 64KB combined stdout/stderr to keep the
416
- * transcript bounded; the model gets the head + a `(...truncated)`
417
- * marker if the cap fires.
418
- * - 30s wall-clock timeout. The engine loop's per-tool error path
419
- * surfaces the timeout to the model so it can retry with a narrower
420
- * command or give up.
421
- *
422
- * Permission gating: `kind: 'bash'`. The CLI's permission module already
423
- * hard-denies the destructive-patterns list (rm -rf /, DROP DATABASE,
424
- * etc) regardless of mode. Plan-mode callers MUST gate the bash tool
425
- * out before it reaches the registry — `engine-tools.ts` does this.
426
- */
427
- export const BASH_OUTPUT_CAP = 64 * 1024;
428
- export const BASH_DEFAULT_TIMEOUT_MS = 30_000;
429
- // Child-process stdio buffer — large enough that the model-facing
430
- // truncation cap (`BASH_OUTPUT_CAP`) is always the gate, never the
431
- // child's internal buffer. Code Reviewer P2 retro flagged
432
- // `BASH_OUTPUT_CAP * 2` as too tight: real builds (`pnpm build`,
433
- // `tsc --noEmit`) routinely exceed 128 KB combined and the model
434
- // then saw a fatal `ERR_CHILD_PROCESS_STDIO_MAXBUFFER` instead of a
435
- // graceful `(...truncated at N bytes)` tail.
436
- export const BASH_CHILD_MAXBUFFER = 10 * 1024 * 1024;
437
- export function bashTool(ctx, command, options = {}) {
438
- const toolCallId = recordToolCall(ctx.session, 'bash', command);
439
- // WriteGate: bash is the highest-risk tool surface. Refuse
440
- // before the destructive-pattern classifier even runs so a
441
- // cancelled dispatch never spawns a child process. Note: this is
442
- // pre-spawn cancellation only; once the /bin/sh -c process is
443
- // running, the synchronous spawnSync wait blocks until it exits or
444
- // the 30s timeout fires. Phase 2 will wire SIGTERM forwarding via
445
- // an async wrapper.
446
- if (ctx.cancellation && ctx.cancellation.isAborted) {
447
- const reason = 'operator_aborted: bash refused';
448
- recordToolResult(ctx.session, toolCallId, 'cancelled', reason);
449
- throw new OperatorAbortedError('bash');
450
- }
451
- const decision = decidePermission({ tool: 'bash', kind: 'bash', target: command }, ctx.settings, ctx.root);
452
- if (decision.decision !== 'allow') {
453
- const reason = `Permission ${decision.decision} for bash: ${decision.reason}`;
454
- recordToolResult(ctx.session, toolCallId, 'error', reason);
455
- throw new Error(reason);
456
- }
457
- // `/bin/sh -c` is portable enough for the M1 alpha; downstream
458
- // operators on hosts without /bin/sh can override via $SHELL, but
459
- // that is an explicit opt-in for now.
460
- //
461
- // Env sanitisation strategy: build the child env from an explicit
462
- // allow-list rather than inheriting `process.env` and trying to
463
- // strip secrets after the fact. Code Reviewer P1 flagged
464
- // that the deny-list approach missed ANTHROPIC_API_KEY / GH_TOKEN
465
- // / AWS_SECRET_ACCESS_KEY / DATABASE_URL / arbitrary *_TOKEN /
466
- // *_SECRET / *_KEY variables — every CI agent rotation would risk
467
- // leaking a new secret name. Allow-listed PATH / HOME / USER /
468
- // SHELL / LANG / LC_* / TERM / TZ + Pugi-internal PUGI_ROOT for
469
- // tools that need it. Any other env variable is invisible to the
470
- // child process.
471
- const childEnv = {};
472
- const SAFE_ENV_ALLOW = new Set([
473
- 'PATH',
474
- 'HOME',
475
- 'USER',
476
- 'LOGNAME',
477
- 'SHELL',
478
- 'LANG',
479
- 'TZ',
480
- 'TERM',
481
- 'PWD',
482
- ]);
483
- for (const [key, value] of Object.entries(process.env)) {
484
- if (value === undefined)
485
- continue;
486
- if (SAFE_ENV_ALLOW.has(key) || key.startsWith('LC_')) {
487
- childEnv[key] = value;
488
- }
489
- }
490
- const timeoutMs = options.timeoutMs ?? BASH_DEFAULT_TIMEOUT_MS;
491
- // `spawnSync` (vs `execFileSync`) captures stdout AND stderr on
492
- // BOTH success and failure paths. Code Reviewer P1:
493
- // `execFileSync` returns only stdout on exit 0, silently dropping
494
- // stderr output from `tsc`, `eslint`, `pytest`, etc. — the model
495
- // would see `(no output)` for successful runs that emitted real
496
- // warnings.
497
- //
498
- // maxBuffer is generous (10 MB) so the child process is never the
499
- // truncation gate — the post-hoc `.slice(0, BASH_OUTPUT_CAP)` below
500
- // is the single source of truth for what the model sees. Code
501
- // Reviewer P2 retro: the previous `BASH_OUTPUT_CAP * 2`
502
- // (128 KB) would hard-throw `ERR_CHILD_PROCESS_STDIO_MAXBUFFER`
503
- // on noisy commands (`pnpm build`, `tsc --noEmit` on the whole
504
- // monorepo) instead of returning the truncated head.
505
- const result = spawnSync('/bin/sh', ['-c', command], {
506
- cwd: ctx.root,
507
- env: childEnv,
508
- encoding: 'utf8',
509
- stdio: ['ignore', 'pipe', 'pipe'],
510
- timeout: timeoutMs,
511
- maxBuffer: BASH_CHILD_MAXBUFFER,
512
- });
513
- if (result.error) {
514
- const err = result.error;
515
- if (err.code === 'ETIMEDOUT' || result.signal === 'SIGTERM') {
516
- const reason = `bash command timed out after ${timeoutMs}ms`;
517
- recordToolResult(ctx.session, toolCallId, 'error', reason);
518
- throw new Error(reason);
519
- }
520
- if (err.code === 'ERR_CHILD_PROCESS_STDIO_MAXBUFFER') {
521
- // maxBuffer overflow — surface as truncated rather than an
522
- // opaque Node internal code so the model sees the same
523
- // truncation marker it gets on stdout/stderr cap hits. With the
524
- // post-Code-Reviewer-P2-retro-2026-05-23 maxBuffer at 10 MB
525
- // this branch only fires on truly pathological output (>10 MB
526
- // single command).
527
- const reason = `bash output exceeded ${BASH_CHILD_MAXBUFFER} byte child-process buffer`;
528
- recordToolResult(ctx.session, toolCallId, 'error', reason);
529
- throw new Error(reason);
530
- }
531
- const reason = `bash invocation failed: ${err.message ?? String(err)}`;
532
- recordToolResult(ctx.session, toolCallId, 'error', reason);
533
- throw new Error(reason);
534
- }
535
- const stdout = (result.stdout ?? '').toString();
536
- const stderr = (result.stderr ?? '').toString();
537
- const truncatedOut = stdout.length > BASH_OUTPUT_CAP;
538
- const truncatedErr = stderr.length > BASH_OUTPUT_CAP;
539
- const truncated = truncatedOut || truncatedErr;
540
- const out = truncatedOut
541
- ? `${stdout.slice(0, BASH_OUTPUT_CAP)}\n(...truncated at ${BASH_OUTPUT_CAP} bytes)`
542
- : stdout;
543
- const err = truncatedErr
544
- ? `${stderr.slice(0, BASH_OUTPUT_CAP)}\n(...truncated at ${BASH_OUTPUT_CAP} bytes)`
545
- : stderr;
546
- const exitCode = result.status ?? 1;
547
- // Non-zero exit is a normal outcome (e.g. `grep` finding no match,
548
- // `test -f` returning 1). Surface it as a success at the audit
549
- // layer; the engine loop feeds the exit code back to the model.
550
- recordToolResult(ctx.session, toolCallId, 'success', `bash exit=${exitCode} stdout=${stdout.length} stderr=${stderr.length}`);
551
- return { stdout: out, stderr: err, exitCode, truncated };
552
- }
553
- //# sourceMappingURL=file-tools.js.map