@pugi/cli 0.1.0-beta.10 → 0.1.0-beta.101

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 (464) hide show
  1. package/CHANGELOG.md +132 -0
  2. package/LICENSE +1 -1
  3. package/README.md +55 -11
  4. package/assets/pugi-prozr2-mascot.ansi +9 -0
  5. package/bin/run.js +33 -1
  6. package/dist/commands/deploy.js +40 -40
  7. package/dist/commands/flatten.js +191 -0
  8. package/dist/commands/jobs-watch.js +201 -0
  9. package/dist/commands/jobs.js +42 -27
  10. package/dist/commands/retro.js +210 -0
  11. package/dist/commands/smoke.js +133 -0
  12. package/dist/core/agent-progress/cleanup.js +134 -0
  13. package/dist/core/agent-progress/schema.js +144 -0
  14. package/dist/core/agent-progress/writer.js +101 -0
  15. package/dist/core/agents/adaptive-router.js +330 -0
  16. package/dist/core/agents/query-decomposer.js +297 -0
  17. package/dist/core/agents/registry.js +3 -3
  18. package/dist/core/approvals/shortcut-resolver.js +98 -0
  19. package/dist/core/artifact-chain/dispatcher.js +148 -0
  20. package/dist/core/artifact-chain/exporter.js +164 -0
  21. package/dist/core/artifact-chain/state.js +243 -0
  22. package/dist/core/artifact-chain/steps.js +169 -0
  23. package/dist/core/ask-user/question.js +92 -0
  24. package/dist/core/audit/audit-trail.js +275 -0
  25. package/dist/core/auth/ensure-authenticated.js +129 -0
  26. package/dist/core/auth/env-provider.js +238 -0
  27. package/dist/core/auto-open-browser.js +4 -4
  28. package/dist/core/auto-update/channels.js +122 -0
  29. package/dist/core/auto-update/checker.js +241 -0
  30. package/dist/core/auto-update/state.js +235 -0
  31. package/dist/core/bare-mode/index.js +107 -0
  32. package/dist/core/bash/redirect.js +281 -0
  33. package/dist/core/bash-classifier.js +436 -40
  34. package/dist/core/checkpoint/resumer.js +149 -0
  35. package/dist/core/checkpoint/rewinder.js +291 -0
  36. package/dist/core/checkpoints/shadow-git.js +670 -0
  37. package/dist/core/citations/parser.js +109 -0
  38. package/dist/core/classifier/yolo-classifier.js +88 -0
  39. package/dist/core/codegraph/db.js +506 -0
  40. package/dist/core/codegraph/decision-store.js +248 -0
  41. package/dist/core/codegraph/detect-repo.js +459 -0
  42. package/dist/core/codegraph/install.js +134 -0
  43. package/dist/core/codegraph/offer-hook.js +220 -0
  44. package/dist/core/codegraph/parser.js +598 -0
  45. package/dist/core/codegraph/queries/go.scm +57 -0
  46. package/dist/core/codegraph/queries/javascript.scm +56 -0
  47. package/dist/core/codegraph/queries/python.scm +55 -0
  48. package/dist/core/codegraph/queries/rust.scm +63 -0
  49. package/dist/core/codegraph/queries/typescript.scm +91 -0
  50. package/dist/core/codegraph/reindex.js +218 -0
  51. package/dist/core/codegraph/resolve-edges.js +107 -0
  52. package/dist/core/codegraph/types.js +34 -0
  53. package/dist/core/codegraph/watcher.js +440 -0
  54. package/dist/core/compact/auto-trigger.js +96 -0
  55. package/dist/core/compact/buffer-rewriter.js +115 -0
  56. package/dist/core/compact/summarizer.js +208 -0
  57. package/dist/core/compact/token-counter.js +108 -0
  58. package/dist/core/consensus/anvil-fanout.js +25 -25
  59. package/dist/core/consensus/diff-capture.js +121 -12
  60. package/dist/core/consensus/rubric.js +21 -21
  61. package/dist/core/context/builder.js +6 -6
  62. package/dist/core/context/compaction-events.js +8 -8
  63. package/dist/core/context/compaction.js +31 -31
  64. package/dist/core/context/index.js +15 -8
  65. package/dist/core/context/invariants.js +51 -51
  66. package/dist/core/context/markdown-loader.js +28 -10
  67. package/dist/core/context/markdown-traverse.js +255 -0
  68. package/dist/core/context/pugiignore.js +41 -41
  69. package/dist/core/context/repo-skeleton.js +37 -37
  70. package/dist/core/context/tool-eviction.js +55 -0
  71. package/dist/core/context/watcher.js +32 -32
  72. package/dist/core/context/working-set.js +23 -23
  73. package/dist/core/coordinator/agent-tools.js +77 -0
  74. package/dist/core/coordinator/agent-toolset.js +65 -0
  75. package/dist/core/coordinator/fsm.js +73 -0
  76. package/dist/core/coordinator/mode-fsm.js +70 -0
  77. package/dist/core/cost/rate-card.js +129 -0
  78. package/dist/core/cost/tracker.js +221 -0
  79. package/dist/core/credentials.js +13 -13
  80. package/dist/core/cron/scheduler.js +138 -0
  81. package/dist/core/denial-tracking/index.js +8 -0
  82. package/dist/core/denial-tracking/state.js +264 -0
  83. package/dist/core/diagnostics/probe-runner.js +93 -0
  84. package/dist/core/diagnostics/probes/api.js +46 -0
  85. package/dist/core/diagnostics/probes/auth.js +93 -0
  86. package/dist/core/diagnostics/probes/bare-mode.js +42 -0
  87. package/dist/core/diagnostics/probes/cli-version.js +127 -0
  88. package/dist/core/diagnostics/probes/config.js +72 -0
  89. package/dist/core/diagnostics/probes/denial-tracking.js +57 -0
  90. package/dist/core/diagnostics/probes/disk.js +81 -0
  91. package/dist/core/diagnostics/probes/engine-live.js +46 -0
  92. package/dist/core/diagnostics/probes/git.js +65 -0
  93. package/dist/core/diagnostics/probes/hooks.js +118 -0
  94. package/dist/core/diagnostics/probes/mcp.js +75 -0
  95. package/dist/core/diagnostics/probes/node.js +59 -0
  96. package/dist/core/diagnostics/probes/pnpm.js +36 -0
  97. package/dist/core/diagnostics/probes/pugi-md.js +89 -0
  98. package/dist/core/diagnostics/probes/sandbox.js +67 -0
  99. package/dist/core/diagnostics/probes/session.js +74 -0
  100. package/dist/core/diagnostics/probes/status-snapshot.js +488 -0
  101. package/dist/core/diagnostics/probes/workspace.js +63 -0
  102. package/dist/core/diagnostics/types.js +70 -0
  103. package/dist/core/dispatch/cache-cleanup.js +197 -0
  104. package/dist/core/dispatch/cache-handoff.js +295 -0
  105. package/dist/core/edits/apply-patch-layer-e.js +189 -0
  106. package/dist/core/edits/dispatch.js +333 -7
  107. package/dist/core/edits/format-detector.js +260 -0
  108. package/dist/core/edits/format-matrix.js +26 -0
  109. package/dist/core/edits/fuzzy-ladder.js +650 -0
  110. package/dist/core/edits/index.js +5 -1
  111. package/dist/core/edits/journal.js +199 -0
  112. package/dist/core/edits/layer-a-apply.js +15 -15
  113. package/dist/core/edits/layer-a-fuzzy-apply.js +198 -0
  114. package/dist/core/edits/layer-b-apply.js +9 -9
  115. package/dist/core/edits/layer-c-apply.js +6 -6
  116. package/dist/core/edits/layer-d-ast.js +557 -14
  117. package/dist/core/edits/marker-parser.js +12 -12
  118. package/dist/core/edits/security-gate.js +27 -27
  119. package/dist/core/edits/verify-hook.js +273 -0
  120. package/dist/core/edits/worktree.js +29 -29
  121. package/dist/core/engine/anvil-client.js +214 -26
  122. package/dist/core/engine/auto-compact.js +247 -0
  123. package/dist/core/engine/budgets.js +220 -0
  124. package/dist/core/engine/compact-llm-summarizer.js +124 -0
  125. package/dist/core/engine/context-prefix.js +155 -0
  126. package/dist/core/engine/index.js +1 -1
  127. package/dist/core/engine/intensity.js +163 -0
  128. package/dist/core/engine/intent.js +260 -0
  129. package/dist/core/engine/native-pugi.js +1559 -227
  130. package/dist/core/engine/prompts.js +219 -19
  131. package/dist/core/engine/strip-internal-fields.js +124 -0
  132. package/dist/core/engine/tool-bridge.js +1887 -59
  133. package/dist/core/engine/verification-patterns.js +195 -0
  134. package/dist/core/eval/v1/ledger.js +83 -0
  135. package/dist/core/eval/v1/runner.js +280 -0
  136. package/dist/core/eval/v1/scoring.js +68 -0
  137. package/dist/core/eval/v1/task-loader.js +191 -0
  138. package/dist/core/eval/v1/types.js +14 -0
  139. package/dist/core/eval/v1/verifier.js +176 -0
  140. package/dist/core/eval/v1/yaml-parser.js +250 -0
  141. package/dist/core/evaluation/golden-dataset.js +293 -0
  142. package/dist/core/feedback/queue.js +177 -0
  143. package/dist/core/feedback/submitter.js +145 -0
  144. package/dist/core/file-cache.js +113 -1
  145. package/dist/core/flatten/flatten-repo.js +439 -0
  146. package/dist/core/format/osc8-link.js +28 -0
  147. package/dist/core/hook-chains.js +392 -0
  148. package/dist/core/hooks/citation-verify-hook.js +138 -0
  149. package/dist/core/hooks/citation-verify.js +112 -0
  150. package/dist/core/hooks/events.js +46 -0
  151. package/dist/core/hooks/index.js +15 -0
  152. package/dist/core/hooks/registry.js +216 -0
  153. package/dist/core/hooks/runner.js +236 -0
  154. package/dist/core/hooks/v2/event-emitter.js +115 -0
  155. package/dist/core/hooks/v2/executor.js +282 -0
  156. package/dist/core/hooks/v2/index.js +25 -0
  157. package/dist/core/hooks/v2/lifecycle.js +104 -0
  158. package/dist/core/hooks/v2/loader.js +216 -0
  159. package/dist/core/hooks/v2/matcher.js +125 -0
  160. package/dist/core/hooks/v2/trust.js +143 -0
  161. package/dist/core/hooks/v2/types.js +86 -0
  162. package/dist/core/hooks/worktree-events.js +158 -0
  163. package/dist/core/image/renderer.js +71 -0
  164. package/dist/core/init/detector.js +582 -0
  165. package/dist/core/init/template-renderer.js +242 -0
  166. package/dist/core/jobs/registry.js +18 -18
  167. package/dist/core/ledger/results-tsv.js +142 -0
  168. package/dist/core/log-discipline/stdout-redirect.js +51 -0
  169. package/dist/core/lsp/cache.js +105 -0
  170. package/dist/core/lsp/client.js +551 -41
  171. package/dist/core/lsp/language-detect.js +66 -0
  172. package/dist/core/lsp/post-edit-diagnostics.js +171 -0
  173. package/dist/core/lsp/server-detect.js +173 -0
  174. package/dist/core/lsp/symbol-cache.js +162 -0
  175. package/dist/core/lsp/symbol-tools.js +664 -0
  176. package/dist/core/mcp/client.js +97 -28
  177. package/dist/core/mcp/http-server.js +553 -0
  178. package/dist/core/mcp/orchestrator-config.js +192 -0
  179. package/dist/core/mcp/orchestrator-tools.js +806 -0
  180. package/dist/core/mcp/permission.js +190 -0
  181. package/dist/core/mcp/registry.js +39 -17
  182. package/dist/core/mcp/server-tools.js +219 -0
  183. package/dist/core/mcp/server.js +397 -0
  184. package/dist/core/mcp/trust.js +10 -10
  185. package/dist/core/memory/dual-write.js +416 -0
  186. package/dist/core/memory/passive-extract.js +130 -0
  187. package/dist/core/memory/phase1-kinds.js +20 -0
  188. package/dist/core/memory/secret-scanner.js +304 -0
  189. package/dist/core/memory-sync/queue.js +170 -0
  190. package/dist/core/metrics/extract.js +113 -0
  191. package/dist/core/modes/roo-modes.js +68 -0
  192. package/dist/core/notes/notes-paths.js +113 -0
  193. package/dist/core/notes/notes-recorder.js +140 -0
  194. package/dist/core/notes/notes-writer.js +53 -0
  195. package/dist/core/notes/renderers.js +0 -0
  196. package/dist/core/notes/slug.js +105 -0
  197. package/dist/core/onboarding/ensure-initialized.js +133 -0
  198. package/dist/core/onboarding/marker.js +111 -0
  199. package/dist/core/onboarding/telemetry-state.js +108 -0
  200. package/dist/core/output-style/presets.js +176 -0
  201. package/dist/core/output-style/state.js +185 -0
  202. package/dist/core/path-security.js +287 -5
  203. package/dist/core/permission.js +82 -22
  204. package/dist/core/permissions/auto-classifier.js +124 -0
  205. package/dist/core/permissions/bash-parser.js +371 -0
  206. package/dist/core/permissions/circuit-breaker.js +83 -0
  207. package/dist/core/permissions/constrained-edit.js +91 -0
  208. package/dist/core/permissions/gate.js +278 -0
  209. package/dist/core/permissions/index.js +20 -0
  210. package/dist/core/permissions/mode.js +174 -0
  211. package/dist/core/permissions/network-egress.js +137 -0
  212. package/dist/core/permissions/state.js +241 -0
  213. package/dist/core/permissions/tool-class.js +107 -0
  214. package/dist/core/plan-mode/ui-state.js +51 -0
  215. package/dist/core/plans/plan-artifact.js +721 -0
  216. package/dist/core/policy-limits/etag-store.js +122 -0
  217. package/dist/core/prd-check/parser.js +215 -0
  218. package/dist/core/prd-check/reporter.js +127 -0
  219. package/dist/core/prd-check/session-review.js +557 -0
  220. package/dist/core/prd-check/verifiers.js +223 -0
  221. package/dist/core/prompt-cache/client-cache.js +99 -0
  222. package/dist/core/prompts/assembly.js +29 -0
  223. package/dist/core/prompts/registry.js +364 -0
  224. package/dist/core/pugi-gitignore.js +52 -0
  225. package/dist/core/pugi-md/cc-compat-rules.js +735 -0
  226. package/dist/core/pugi-md/context-injector.js +76 -0
  227. package/dist/core/pugi-md/walk-up.js +207 -0
  228. package/dist/core/python/uv-installer.js +270 -0
  229. package/dist/core/python/uv-resolver.js +83 -0
  230. package/dist/core/rate-limit/narrator.js +146 -0
  231. package/dist/core/recipes/cli-types.js +20 -0
  232. package/dist/core/recipes/loader.js +103 -0
  233. package/dist/core/recipes/runner.js +345 -0
  234. package/dist/core/recipes/schema.js +587 -0
  235. package/dist/core/release-notes/parser.js +241 -0
  236. package/dist/core/release-notes/state.js +116 -0
  237. package/dist/core/repl/ask.js +37 -37
  238. package/dist/core/repl/cancellation.js +26 -26
  239. package/dist/core/repl/cap-warning.js +4 -4
  240. package/dist/core/repl/clipboard-read.js +11 -11
  241. package/dist/core/repl/dispatch-fsm.js +12 -12
  242. package/dist/core/repl/engine-bridge.js +303 -0
  243. package/dist/core/repl/history-search.js +15 -15
  244. package/dist/core/repl/history.js +28 -18
  245. package/dist/core/repl/kill-ring.js +5 -5
  246. package/dist/core/repl/model-pricing.js +135 -0
  247. package/dist/core/repl/privacy-banner.js +22 -22
  248. package/dist/core/repl/session.js +2690 -229
  249. package/dist/core/repl/slash-commands.js +540 -41
  250. package/dist/core/repl/store/index.js +1 -1
  251. package/dist/core/repl/store/jsonl-log.js +22 -22
  252. package/dist/core/repl/store/lockfile.js +10 -10
  253. package/dist/core/repl/store/session-store.js +136 -107
  254. package/dist/core/repl/store/types.js +15 -15
  255. package/dist/core/repl/store/uuid-v7.js +12 -12
  256. package/dist/core/repl/tool-route.js +382 -0
  257. package/dist/core/repl/workspace-context.js +43 -21
  258. package/dist/core/repo-map/build.js +125 -0
  259. package/dist/core/repo-map/cache.js +185 -0
  260. package/dist/core/repo-map/extractor.js +254 -0
  261. package/dist/core/repo-map/formatter.js +145 -0
  262. package/dist/core/repo-map/page-rank.js +105 -0
  263. package/dist/core/repo-map/scanner.js +211 -0
  264. package/dist/core/retro/git-collector.js +251 -0
  265. package/dist/core/retro/health-card.js +25 -0
  266. package/dist/core/retro/metrics.js +342 -0
  267. package/dist/core/retro/narrative.js +249 -0
  268. package/dist/core/retro/plane-collector.js +274 -0
  269. package/dist/core/retro/pr-issue-link.js +65 -0
  270. package/dist/core/retro/types.js +16 -0
  271. package/dist/core/retry-budget/budget.js +284 -0
  272. package/dist/core/retry-budget/index.js +5 -0
  273. package/dist/core/retry-budget/retry-cap.js +74 -0
  274. package/dist/core/routing/lead-worker.js +43 -0
  275. package/dist/core/routing/pre-flight-estimator.js +108 -0
  276. package/dist/core/runs/run-tree.js +103 -0
  277. package/dist/core/sandboxing/adapter.js +43 -0
  278. package/dist/core/sandboxing/bubblewrap.js +209 -0
  279. package/dist/core/sandboxing/index.js +78 -0
  280. package/dist/core/sandboxing/none.js +19 -0
  281. package/dist/core/sandboxing/policy.js +97 -0
  282. package/dist/core/sandboxing/seatbelt.js +231 -0
  283. package/dist/core/security/injection-scanner.js +367 -0
  284. package/dist/core/security/output-filter.js +418 -0
  285. package/dist/core/session/env-file.js +105 -0
  286. package/dist/core/session/section-budgets.js +140 -0
  287. package/dist/core/session.js +119 -0
  288. package/dist/core/settings.js +402 -5
  289. package/dist/core/share/formatter.js +271 -0
  290. package/dist/core/share/redactor.js +221 -0
  291. package/dist/core/share/uploader.js +267 -0
  292. package/dist/core/skills/defaults.js +30 -30
  293. package/dist/core/skills/loader.js +22 -22
  294. package/dist/core/skills/sources.js +27 -27
  295. package/dist/core/smoke/headless-driver.js +174 -0
  296. package/dist/core/smoke/orchestrator.js +194 -0
  297. package/dist/core/smoke/runner.js +238 -0
  298. package/dist/core/smoke/scenario-parser.js +316 -0
  299. package/dist/core/statusline.js +99 -0
  300. package/dist/core/subagents/dispatcher-real.js +600 -0
  301. package/dist/core/subagents/dispatcher.js +146 -52
  302. package/dist/core/subagents/index.js +19 -6
  303. package/dist/core/subagents/isolation-matrix.js +213 -0
  304. package/dist/core/subagents/spawn.js +19 -4
  305. package/dist/core/telemetry/emitter.js +229 -0
  306. package/dist/core/telemetry/queue.js +251 -0
  307. package/dist/core/theme/context.js +91 -0
  308. package/dist/core/theme/presets.js +228 -0
  309. package/dist/core/theme/state.js +181 -0
  310. package/dist/core/todos/invariant.js +10 -0
  311. package/dist/core/todos/state.js +177 -0
  312. package/dist/core/tool-schema/compressor.js +89 -0
  313. package/dist/core/transport/version-interceptor.js +166 -0
  314. package/dist/core/trust.js +2 -2
  315. package/dist/core/tui/thinking-block.js +64 -0
  316. package/dist/core/vim/keymap.js +288 -0
  317. package/dist/core/vim/state.js +92 -0
  318. package/dist/core/watch-markers/marker-watcher.js +133 -0
  319. package/dist/core/worktree/include-parser.js +249 -0
  320. package/dist/core/worktree-manager/cleanup.js +123 -0
  321. package/dist/core/worktree-manager/manager.js +303 -0
  322. package/dist/index.js +36 -0
  323. package/dist/runtime/bootstrap.js +190 -0
  324. package/dist/runtime/cli.js +4403 -561
  325. package/dist/runtime/commands/agents.js +31 -31
  326. package/dist/runtime/commands/budget.js +5 -5
  327. package/dist/runtime/commands/cancel.js +231 -0
  328. package/dist/runtime/commands/chain.js +489 -0
  329. package/dist/runtime/commands/codegraph-status.js +227 -0
  330. package/dist/runtime/commands/compact.js +297 -0
  331. package/dist/runtime/commands/config.js +74 -40
  332. package/dist/runtime/commands/cost.js +199 -0
  333. package/dist/runtime/commands/delegate.js +27 -4
  334. package/dist/runtime/commands/dispatch.js +126 -0
  335. package/dist/runtime/commands/doctor.js +579 -0
  336. package/dist/runtime/commands/eval-v1.js +266 -0
  337. package/dist/runtime/commands/feedback.js +184 -0
  338. package/dist/runtime/commands/hooks.js +187 -0
  339. package/dist/runtime/commands/index-cmd.js +459 -0
  340. package/dist/runtime/commands/init.js +254 -0
  341. package/dist/runtime/commands/lsp.js +200 -38
  342. package/dist/runtime/commands/mcp.js +935 -0
  343. package/dist/runtime/commands/memory.js +582 -0
  344. package/dist/runtime/commands/model.js +237 -0
  345. package/dist/runtime/commands/onboarding.js +275 -0
  346. package/dist/runtime/commands/patch.js +12 -12
  347. package/dist/runtime/commands/permissions.js +112 -0
  348. package/dist/runtime/commands/plan.js +143 -0
  349. package/dist/runtime/commands/prd-check.js +285 -0
  350. package/dist/runtime/commands/privacy.js +17 -17
  351. package/dist/runtime/commands/recipe.js +325 -0
  352. package/dist/runtime/commands/redo-blob-store.js +92 -0
  353. package/dist/runtime/commands/redo.js +361 -0
  354. package/dist/runtime/commands/release-notes.js +229 -0
  355. package/dist/runtime/commands/repo-map.js +95 -0
  356. package/dist/runtime/commands/report.js +299 -0
  357. package/dist/runtime/commands/resume.js +118 -0
  358. package/dist/runtime/commands/review-consensus.js +68 -53
  359. package/dist/runtime/commands/rewind.js +333 -0
  360. package/dist/runtime/commands/roster.js +14 -14
  361. package/dist/runtime/commands/servers-cli.js +182 -0
  362. package/dist/runtime/commands/servers.js +236 -0
  363. package/dist/runtime/commands/sessions.js +163 -0
  364. package/dist/runtime/commands/share.js +316 -0
  365. package/dist/runtime/commands/skills.js +31 -31
  366. package/dist/runtime/commands/status.js +186 -0
  367. package/dist/runtime/commands/stickers.js +82 -0
  368. package/dist/runtime/commands/style.js +194 -0
  369. package/dist/runtime/commands/theme.js +196 -0
  370. package/dist/runtime/commands/undo.js +54 -22
  371. package/dist/runtime/commands/update.js +289 -0
  372. package/dist/runtime/commands/vim.js +140 -0
  373. package/dist/runtime/commands/worktree.js +8 -8
  374. package/dist/runtime/commands/worktrees.js +155 -0
  375. package/dist/runtime/deprecation-warning.js +69 -0
  376. package/dist/runtime/engine-exit-code.js +50 -0
  377. package/dist/runtime/headless-repl.js +195 -0
  378. package/dist/runtime/headless.js +548 -0
  379. package/dist/runtime/load-hooks-or-exit.js +71 -0
  380. package/dist/runtime/plan-decompose.js +22 -22
  381. package/dist/runtime/sigint-guard.js +272 -0
  382. package/dist/runtime/stream-renderer.js +195 -0
  383. package/dist/runtime/update-check.js +28 -28
  384. package/dist/runtime/version.js +65 -0
  385. package/dist/runtime/worktree-bootstrap.js +579 -0
  386. package/dist/skills/bundled/batch.js +617 -0
  387. package/dist/skills/bundled/index.js +45 -0
  388. package/dist/skills/bundled/loop.js +358 -0
  389. package/dist/skills/bundled/remember.js +383 -0
  390. package/dist/skills/bundled/simplify.js +289 -0
  391. package/dist/skills/bundled/skillify.js +373 -0
  392. package/dist/skills/bundled/stuck.js +558 -0
  393. package/dist/skills/bundled/verify.js +439 -0
  394. package/dist/testing/vcr.js +486 -0
  395. package/dist/tools/agent-tool.js +229 -0
  396. package/dist/tools/apply-patch.js +89 -28
  397. package/dist/tools/ask-user-question.js +337 -0
  398. package/dist/tools/ask-user.js +115 -0
  399. package/dist/tools/bash.js +811 -49
  400. package/dist/tools/brief.js +224 -0
  401. package/dist/tools/cron.js +433 -0
  402. package/dist/tools/enter-worktree.js +250 -0
  403. package/dist/tools/exit-worktree.js +147 -0
  404. package/dist/tools/file-tools.js +161 -44
  405. package/dist/tools/http-request.js +336 -0
  406. package/dist/tools/lsp-tools.js +377 -1
  407. package/dist/tools/mcp-tool.js +260 -0
  408. package/dist/tools/multi-edit.js +361 -0
  409. package/dist/tools/powershell.js +268 -0
  410. package/dist/tools/registry.js +120 -5
  411. package/dist/tools/server-tools.js +892 -0
  412. package/dist/tools/skill-tool.js +96 -0
  413. package/dist/tools/sleep.js +99 -0
  414. package/dist/tools/synthetic-output.js +133 -0
  415. package/dist/tools/tasks.js +208 -0
  416. package/dist/tools/todo-write.js +184 -0
  417. package/dist/tools/verify-plan-execution.js +295 -0
  418. package/dist/tools/web-fetch-injection-scanner.js +207 -0
  419. package/dist/tools/web-fetch.js +195 -10
  420. package/dist/tools/web-search.js +458 -0
  421. package/dist/tui/agent-progress-card.js +111 -0
  422. package/dist/tui/agent-tree.js +22 -1
  423. package/dist/tui/ask-modal.js +14 -14
  424. package/dist/tui/ask-user-question-chips.js +315 -0
  425. package/dist/tui/ask-user-question-prompt.js +203 -0
  426. package/dist/tui/compact-banner.js +81 -0
  427. package/dist/tui/conversation-pane.js +85 -11
  428. package/dist/tui/cost-table.js +111 -0
  429. package/dist/tui/device-flow.js +2 -2
  430. package/dist/tui/doctor-table.js +46 -0
  431. package/dist/tui/feedback-prompt.js +156 -0
  432. package/dist/tui/input-box.js +247 -32
  433. package/dist/tui/login-picker.js +3 -3
  434. package/dist/tui/markdown-render.js +6 -6
  435. package/dist/tui/multi-file-diff-approval.js +375 -0
  436. package/dist/tui/onboarding-wizard.js +240 -0
  437. package/dist/tui/permissions-picker.js +86 -0
  438. package/dist/tui/render.js +36 -1
  439. package/dist/tui/repl-render.js +239 -25
  440. package/dist/tui/repl-splash-art.js +16 -16
  441. package/dist/tui/repl-splash-mascot.js +48 -24
  442. package/dist/tui/repl-splash.js +22 -22
  443. package/dist/tui/repl.js +125 -45
  444. package/dist/tui/slash-palette.js +6 -6
  445. package/dist/tui/splash.js +2 -2
  446. package/dist/tui/status-bar.js +109 -31
  447. package/dist/tui/status-table.js +7 -0
  448. package/dist/tui/stickers-art.js +136 -0
  449. package/dist/tui/style-table.js +28 -0
  450. package/dist/tui/theme-table.js +29 -0
  451. package/dist/tui/thinking-spinner.js +123 -0
  452. package/dist/tui/tool-stream-pane.js +53 -4
  453. package/dist/tui/update-banner.js +27 -2
  454. package/dist/tui/vim-input.js +267 -0
  455. package/dist/tui/welcome-banner.js +107 -0
  456. package/dist/tui/welcome-data.js +293 -0
  457. package/dist/tui/workspace-context.js +2 -2
  458. package/package.json +29 -6
  459. package/test/scenarios/codegen-create-file.scenario.txt +13 -0
  460. package/test/scenarios/compact-force.scenario.txt +12 -0
  461. package/test/scenarios/identity.scenario.txt +11 -0
  462. package/test/scenarios/persona-handoff.scenario.txt +12 -0
  463. package/test/scenarios/walkback.scenario.txt +12 -0
  464. package/dist/core/engine/compaction-hook.js +0 -154
@@ -1,12 +1,48 @@
1
- import { realpathSync } from 'node:fs';
2
- import { basename, relative, resolve } from 'node:path';
1
+ /**
2
+ * Path-security gate for Pugi CLI file operations.
3
+ *
4
+ * The original `resolveWorkspacePath` (preserved below) is the read-path
5
+ * gate: it stops relative-path traversal, URL-encoded traversal, and
6
+ * symlink escapes at the leaf. It is kept as the existing call surface
7
+ * for `read` and other non-mutating tools.
8
+ *
9
+ * `assertSafeWritePath` is a stricter, write-only gate ported from
10
+ * external's Rust `path_guard.rs` (Apache-2.0). It adds:
11
+ *
12
+ * 1. `..` segment rejection BEFORE any filesystem touch.
13
+ * 2. Walk-up canonicalize: finds the deepest existing ancestor,
14
+ * canonicalizes it (resolving every symlink in the existing
15
+ * prefix), then reattaches the non-existent tail. This closes
16
+ * the "parent's parent is a symlink" bypass that catches naive
17
+ * canonicalize-the-parent implementations.
18
+ * 3. Leaf-symlink reject: refuse to write through a symlink even
19
+ * when the file already exists. Covers dangling symlinks too.
20
+ * 4. Fail-CLOSED on empty allowed roots: an empty PUGI_ALLOWED_ROOTS
21
+ * means "no writes anywhere" rather than "writes allowed
22
+ * everywhere".
23
+ * 5. Containment check against canonicalized allowed roots.
24
+ * 6. Denylist of system, credential, and shell-init paths even
25
+ * when they sit inside an allowed root (defense in depth — a
26
+ * misconfigured root pointing at `$HOME` still cannot clobber
27
+ * `~/.ssh/id_ed25519`).
28
+ *
29
+ * ---
30
+ * Portions of `assertSafeWritePath` and `canonicalizeWithWalkUp` are
31
+ * derived from external `kei-mcp/src/handlers/safe_tools/
32
+ * path_guard.rs` (Apache-2.0). See `licenses/EXTERNAL-LICENSE-NOTICE.md`
33
+ * at the repo root for attribution and the full upstream license
34
+ * reference.
35
+ */
36
+ import { existsSync, lstatSync, realpathSync, } from 'node:fs';
37
+ import { homedir } from 'node:os';
38
+ import { basename, dirname, isAbsolute, relative, resolve, sep, } from 'node:path';
3
39
  /**
4
40
  * Resolve and validate that an inputPath stays within the workspace.
5
41
  *
6
42
  * Defends against:
7
- * 1. relative-path traversal (`../etc/passwd`)
8
- * 2. URL-encoded traversal (`..%2Fetc%2Fpasswd`)
9
- * 3. symlink escapes at the target itself (`alias-link -> /etc/passwd`)
43
+ * 1. relative-path traversal (`../etc/passwd`)
44
+ * 2. URL-encoded traversal (`..%2Fetc%2Fpasswd`)
45
+ * 3. symlink escapes at the target itself (`alias-link -> /etc/passwd`)
10
46
  *
11
47
  * The previous implementation also resolved the parent's realpath and
12
48
  * compared to the workspace root. That broke `pugi explain .` on macOS
@@ -60,4 +96,250 @@ function isInsideWorkspace(child, workspaceRoot) {
60
96
  const rel = relative(workspaceRoot, child);
61
97
  return Boolean(rel) && !rel.startsWith('..') && rel !== '..';
62
98
  }
99
+ /**
100
+ * Apply the strict write-path guard. Returns the canonical
101
+ * absolute path on success, throws on rejection.
102
+ *
103
+ * Use this for any operation that mutates the filesystem (`write`,
104
+ * `edit`, atomic-rename targets). The error message is safe to surface
105
+ * to the operator — it never echoes filesystem secrets.
106
+ */
107
+ export function assertSafeWritePath(inputPath, options = {}) {
108
+ if (typeof inputPath !== 'string' || inputPath.length === 0) {
109
+ throw new Error('file_path: empty');
110
+ }
111
+ if (inputPath.includes('\0')) {
112
+ throw new Error('file_path: null byte rejected');
113
+ }
114
+ // step: reject `..` segments before any FS work. We split on
115
+ // both `/` and the platform separator so a Windows-style `..\foo`
116
+ // input cannot smuggle a traversal through on macOS test paths.
117
+ const decoded = decodeURIComponent(inputPath);
118
+ const segments = decoded.split(/[/\\]/);
119
+ if (segments.some((seg) => seg === '..')) {
120
+ throw new Error(`file_path: '..' segment not allowed in ${inputPath}`);
121
+ }
122
+ // step: refuse to write through a symlink leaf. We must
123
+ // check the LITERAL input path (pre-canonicalize) because `realpath`
124
+ // unconditionally resolves symlinks — so by the time we have the
125
+ // canonical path the symlink fingerprint is already gone. `lstatSync`
126
+ // does NOT follow symlinks, which is exactly what we need.
127
+ const cwdRoot = options.root ?? process.cwd();
128
+ const literalAbsolute = isAbsolute(decoded) ? decoded : resolve(cwdRoot, decoded);
129
+ try {
130
+ const meta = lstatSync(literalAbsolute);
131
+ if (meta.isSymbolicLink()) {
132
+ throw new Error(`file_path: leaf is a symlink (refusing to follow): ${literalAbsolute}`);
133
+ }
134
+ }
135
+ catch (error) {
136
+ const code = error.code;
137
+ if (code !== 'ENOENT' && code !== 'ENOTDIR')
138
+ throw error;
139
+ // ENOENT/ENOTDIR is fine — the leaf simply does not exist yet
140
+ // (write-create path). The walk-up canonicalize below handles it.
141
+ }
142
+ const canonical = canonicalizeWithWalkUp(decoded, cwdRoot);
143
+ // step: fail-CLOSED on empty allowed roots.
144
+ const roots = computeAllowedRoots(options);
145
+ if (roots.length === 0) {
146
+ throw new Error("file_path: allowed_roots is empty — refusing all writes " +
147
+ '(set PUGI_ALLOWED_ROOTS to a non-empty value or run from a real cwd)');
148
+ }
149
+ // step: containment check against canonicalized allowed
150
+ // roots. Each root is normalized to end in `sep` so `/tmp/wsX` does
151
+ // not accidentally pass containment for `/tmp/ws`.
152
+ const inAllowedRoot = roots.some((r) => isContainedIn(canonical, r));
153
+ if (!inAllowedRoot) {
154
+ throw new Error(`file_path: outside allowed roots ${JSON.stringify(roots)}: ${canonical}`);
155
+ }
156
+ // step: denylist. Applied AFTER containment so a
157
+ // misconfigured root pointing at `/` or `$HOME` still cannot clobber
158
+ // sensitive files. We canonicalize HOME so a $HOME that lives under
159
+ // a symlinked prefix (macOS /var → /private/var, Linux /home →
160
+ // /usr/home on some FreeBSD-style mounts) still matches the
161
+ // canonical write target.
162
+ assertNotDenylisted(canonical, canonicalizeHomeDir(options.homeDir ?? homedir()));
163
+ return canonical;
164
+ }
165
+ function canonicalizeHomeDir(raw) {
166
+ if (!raw)
167
+ return raw;
168
+ try {
169
+ return realpathSync.native(raw);
170
+ }
171
+ catch {
172
+ return raw;
173
+ }
174
+ }
175
+ /**
176
+ * Ported from an external utility. Finds the deepest
177
+ * existing ancestor, canonicalizes it (resolving every symlink in the
178
+ * existing prefix), then reattaches the non-existent tail components.
179
+ *
180
+ * This closes the "parent's parent is a symlink" bypass where naive
181
+ * implementations canonicalize only the immediate parent.
182
+ */
183
+ export function canonicalizeWithWalkUp(inputPath, cwd) {
184
+ const absolute = isAbsolute(inputPath) ? inputPath : resolve(cwd, inputPath);
185
+ let current = absolute;
186
+ const tail = [];
187
+ // Hard cap on walk-up iterations — defense against pathological
188
+ // inputs that somehow evade `dirname` termination.
189
+ const maxIterations = 4096;
190
+ let iterations = 0;
191
+ while (true) {
192
+ iterations += 1;
193
+ if (iterations > maxIterations) {
194
+ throw new Error(`file_path: walk-up exceeded ${maxIterations} iterations: ${absolute}`);
195
+ }
196
+ if (existsSync(current)) {
197
+ let canonicalExisting;
198
+ try {
199
+ canonicalExisting = realpathSync.native(current);
200
+ }
201
+ catch (error) {
202
+ throw new Error(`file_path: canonicalize ${current}: ${error.message}`);
203
+ }
204
+ // Reattach tail in original order (we pushed leaf-first).
205
+ let result = canonicalExisting;
206
+ for (let i = tail.length - 1; i >= 0; i -= 1) {
207
+ result = resolve(result, tail[i]);
208
+ }
209
+ return result;
210
+ }
211
+ const name = basename(current);
212
+ const parent = dirname(current);
213
+ if (!name || parent === current) {
214
+ throw new Error(`file_path: walked to root without finding existing dir: ${absolute}`);
215
+ }
216
+ tail.push(name);
217
+ current = parent;
218
+ }
219
+ }
220
+ function computeAllowedRoots(options) {
221
+ const envValue = options.allowedRootsEnv ?? process.env.PUGI_ALLOWED_ROOTS;
222
+ if (envValue !== undefined) {
223
+ return envValue
224
+ .split(':')
225
+ .filter((s) => s.length > 0)
226
+ .map(canonicalizeRoot)
227
+ .filter((s) => s !== null);
228
+ }
229
+ // No env override: implicit root is the caller-supplied cwd (defaults
230
+ // to `process.cwd()`).
231
+ const cwd = options.root ?? process.cwd();
232
+ const canon = canonicalizeRoot(cwd);
233
+ return canon ? [canon] : [];
234
+ }
235
+ function canonicalizeRoot(raw) {
236
+ if (!raw)
237
+ return null;
238
+ let canon;
239
+ try {
240
+ canon = realpathSync.native(raw);
241
+ }
242
+ catch {
243
+ canon = resolve(raw);
244
+ }
245
+ if (!canon)
246
+ return null;
247
+ return canon.endsWith(sep) ? canon : canon + sep;
248
+ }
249
+ function isContainedIn(canonicalChild, rootWithSep) {
250
+ const rootNoSep = rootWithSep.endsWith(sep)
251
+ ? rootWithSep.slice(0, -1)
252
+ : rootWithSep;
253
+ if (canonicalChild === rootNoSep)
254
+ return true;
255
+ return canonicalChild.startsWith(rootWithSep);
256
+ }
257
+ function assertNotDenylisted(canonical, homeDir) {
258
+ // System paths — match as prefix-with-separator so `/etc-fake/` is
259
+ // not denied while `/etc/passwd` is.
260
+ const systemDenyPrefixes = [
261
+ '/etc/',
262
+ '/usr/',
263
+ '/System/',
264
+ '/Library/Application Support/',
265
+ '/var/db/',
266
+ '/var/log/',
267
+ '/var/root/',
268
+ '/private/etc/',
269
+ '/private/usr/',
270
+ '/private/var/db/',
271
+ '/private/var/log/',
272
+ '/private/var/root/',
273
+ '/root/',
274
+ '/bin/',
275
+ '/sbin/',
276
+ ];
277
+ for (const prefix of systemDenyPrefixes) {
278
+ if (canonical.startsWith(prefix)) {
279
+ throw new Error(`file_path: denied (system dir): ${canonical}`);
280
+ }
281
+ }
282
+ if (!homeDir)
283
+ return;
284
+ // Credential and substrate directories.
285
+ const homeDirSecrets = [
286
+ '.ssh/',
287
+ '.aws/',
288
+ '.gnupg/',
289
+ '.config/gcloud/',
290
+ '.kube/',
291
+ '.docker/',
292
+ '.claude/',
293
+ '.grok/',
294
+ '.gemini/',
295
+ '.copilot/',
296
+ '.kimi/',
297
+ '.pugi/credentials/',
298
+ ];
299
+ for (const secret of homeDirSecrets) {
300
+ const full = joinHome(homeDir, secret);
301
+ if (canonical.startsWith(full)) {
302
+ throw new Error(`file_path: denied (secret/substrate dir): ${canonical}`);
303
+ }
304
+ }
305
+ // Credential files (exact match).
306
+ const homeFileSecrets = [
307
+ '.npmrc',
308
+ '.cargo/credentials',
309
+ '.cargo/credentials.toml',
310
+ '.docker/config.json',
311
+ '.netrc',
312
+ '.pgpass',
313
+ ];
314
+ for (const secret of homeFileSecrets) {
315
+ const full = joinHome(homeDir, secret);
316
+ if (canonical === full) {
317
+ throw new Error(`file_path: denied (credential file): ${canonical}`);
318
+ }
319
+ }
320
+ // Shell-init files (exact match).
321
+ const initFiles = [
322
+ '.zshrc',
323
+ '.bashrc',
324
+ '.profile',
325
+ '.bash_profile',
326
+ '.zprofile',
327
+ '.zshenv',
328
+ '.bash_login',
329
+ '.bash_logout',
330
+ '.inputrc',
331
+ '.gitconfig',
332
+ '.config/fish/config.fish',
333
+ ];
334
+ for (const file of initFiles) {
335
+ const full = joinHome(homeDir, file);
336
+ if (canonical === full) {
337
+ throw new Error(`file_path: denied (shell-init file): ${canonical}`);
338
+ }
339
+ }
340
+ }
341
+ function joinHome(homeDir, rest) {
342
+ const trimmedHome = homeDir.endsWith(sep) ? homeDir.slice(0, -1) : homeDir;
343
+ return `${trimmedHome}${sep}${rest}`;
344
+ }
63
345
  //# sourceMappingURL=path-security.js.map
@@ -68,8 +68,8 @@ const protectedSuffixes = ['.pem', '.key', '.crt', '.p12', '.dump', '.sql'];
68
68
  * it as a list for callers (doctor, debug tooling) that need to
69
69
  * audit the rule set without re-running `classifyBash`.
70
70
  *
71
- * Code Reviewer P2 retro 2026-05-23: previously this list was
72
- * duplicated here as `destructiveBashPatterns`. Sprint α5.2 moves it
71
+ * Code Reviewer P2 retro: previously this list was
72
+ * duplicated here as `destructiveBashPatterns`. Sprint moves it
73
73
  * into the classifier so the permission engine and the doctor surface
74
74
  * cannot drift.
75
75
  */
@@ -79,23 +79,23 @@ export function destructiveBashPatternsList() {
79
79
  /**
80
80
  * Class-aware bash permission decision. The matrix:
81
81
  *
82
- * | plan | ask | acceptEdits | auto | dontAsk | bypass
83
- * read | allow| allow| allow | allow | allow | allow
84
- * build_test | deny | ask | ask | allow | allow* | allow
85
- * network | deny | ask | ask | ask | allow* | allow
86
- * write_workspace | deny | ask | allow | allow | allow* | allow
87
- * write_protected | deny | ask | ask | ask | deny | ask
88
- * destructive | deny | deny | deny | deny | deny | deny**
89
- * unknown | deny | ask | ask | ask | deny | ask
82
+ * | plan | ask | acceptEdits | auto | dontAsk | bypass
83
+ * read | allow| allow| allow | allow | allow | allow
84
+ * build_test | deny | ask | ask | allow | allow* | allow
85
+ * network | deny | ask | ask | ask | allow* | allow
86
+ * write_workspace | deny | ask | allow | allow | allow* | allow
87
+ * write_protected | deny | ask | ask | ask | deny | ask
88
+ * destructive | deny | deny | deny | deny | deny | deny**
89
+ * unknown | deny | ask | ask | ask | deny | ask
90
90
  *
91
- * * dontAsk allows non-destructive classes when no settings rule
92
- * contradicts; the bare-mode policy is encoded in the table below.
93
- * ** destructive can be unlocked ONLY when ALL three hold:
94
- * - mode === 'bypassPermissions'
95
- * - PUGI_DESTRUCTIVE_OVERRIDE === '1'
96
- * - source === 'human'
97
- * The agent loop never sets `source: 'human'`, so even a runaway
98
- * agent in bypass mode cannot trigger a destructive deletion.
91
+ * * dontAsk allows non-destructive classes when no settings rule
92
+ * contradicts; the bare-mode policy is encoded in the table below.
93
+ * ** destructive can be unlocked ONLY when ALL three hold:
94
+ * - mode === 'bypassPermissions'
95
+ * - PUGI_DESTRUCTIVE_OVERRIDE === '1'
96
+ * - source === 'human'
97
+ * The agent loop never sets `source: 'human'`, so even a runaway
98
+ * agent in bypass mode cannot trigger a destructive deletion.
99
99
  */
100
100
  export function evaluateBashPermission(cmd, mode, ctx) {
101
101
  const classification = classifyBash(cmd, {
@@ -236,6 +236,18 @@ export function decidePermission(action, settings, root) {
236
236
  if (protectedReason) {
237
237
  return decisionForMode(settings.permissions.mode, protectedReason, 'protected_file', 'high');
238
238
  }
239
+ // task — operator-declared read-only paths gate edits
240
+ // and writes ahead of the generic deny check so the audit trail
241
+ // shows `source: 'readonly_paths'` (operator intent), not
242
+ // `settings.deny` (generic rule). Reads always pass through; the
243
+ // contract is "you can look but не touch".
244
+ if (action.kind === 'edit' && matchesAny(action.target, settings.permissions.readonlyPaths)) {
245
+ return {
246
+ decision: 'deny',
247
+ reason: `Read-only path: ${action.target}`,
248
+ source: 'readonly_paths',
249
+ };
250
+ }
239
251
  const signature = `${action.kind}:${action.target}`;
240
252
  if (matchesAny(signature, settings.permissions.deny)) {
241
253
  return { decision: 'deny', reason: `Denied by rule: ${signature}`, source: 'settings.deny' };
@@ -297,13 +309,61 @@ function riskForAction(action) {
297
309
  return 'medium';
298
310
  return 'medium';
299
311
  }
312
+ /**
313
+ * Expand `${VAR}` and `$VAR` references in a rule string against
314
+ * `process.env`. Unknown variables expand к the empty string so a
315
+ * typo'd `${HMOE}` cannot accidentally match every signature via a
316
+ * literal `${HMOE}` substring; the expanded `mcp:fs/` is innocuous.
317
+ *
318
+ * task #23 — operator wants `mcp:secrets/${USER}-*`
319
+ * style rules so a single settings file fits multiple machines.
320
+ *
321
+ * The implementation deliberately avoids shell-style features like
322
+ * `$()` or default-value `${VAR:-x}`; permission rules should be
323
+ * easy to reason about at audit time.
324
+ */
325
+ export function expandEnvVars(rule, env = process.env) {
326
+ // Single combined regex. Sequential `${VAR}` then `$VAR` passes are
327
+ // unsafe — if `${X}` expands to a value containing `$Y`, the second
328
+ // pass would also expand that. An attacker controlling one env var
329
+ // could rewrite permission rules. Substitute each variable exactly
330
+ // once by matching both forms in a single sweep.
331
+ return rule.replace(/\$(?:\{([A-Za-z_][A-Za-z0-9_]*)\}|([A-Za-z_][A-Za-z0-9_]*))/g, (_m, braced, bare) => {
332
+ const name = braced ?? bare ?? '';
333
+ return env[name] ?? '';
334
+ });
335
+ }
336
+ /**
337
+ * Convert a glob-style rule to a RegExp anchored full-string.
338
+ * Supports: `*` (greedy any), `?` (single char), all other regex
339
+ * metacharacters escaped. The simpler tail-`*` form remains valid —
340
+ * `mcp:fs/ *` works the same way as before, just via regex instead of
341
+ * the old `startsWith` branch. Power users get `mcp:* / write*` style
342
+ * cross-server denies.
343
+ */
344
+ const REGEX_META = new Set(['\\', '^', '$', '.', '|', '+', '(', ')', '[', ']', '{', '}']);
345
+ function ruleToRegExp(rule) {
346
+ let out = '';
347
+ for (const ch of rule) {
348
+ if (ch === '*')
349
+ out += '.*';
350
+ else if (ch === '?')
351
+ out += '.';
352
+ else if (REGEX_META.has(ch))
353
+ out += '\\' + ch;
354
+ else
355
+ out += ch;
356
+ }
357
+ return new RegExp('^' + out + '$');
358
+ }
300
359
  function matchesAny(value, rules) {
301
360
  return rules.some((rule) => {
302
- if (rule === value)
361
+ const expanded = expandEnvVars(rule);
362
+ if (expanded === value)
303
363
  return true;
304
- if (rule.endsWith('*'))
305
- return value.startsWith(rule.slice(0, -1));
306
- return false;
364
+ if (!expanded.includes('*') && !expanded.includes('?'))
365
+ return false;
366
+ return ruleToRegExp(expanded).test(value);
307
367
  });
308
368
  }
309
369
  //# sourceMappingURL=permission.js.map
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Auto-mode classifier — Phase 1 (regex allowlist + denylist).
3
+ *
4
+ * `permissionMode === 'auto'` is the the upstream tool parity mode where the
5
+ * classifier decides safe-vs-unsafe per call. Phase 1 ships without
6
+ * ML — just two curated regex lists covering the 80% of "obviously
7
+ * safe" and "obviously catastrophic" patterns. Anything that doesn't
8
+ * match either list returns `ask`, so the operator stays in the loop
9
+ * для the ambiguous middle.
10
+ *
11
+ * Phase 2 (deferred, NOT in this PR): semantic classifier consulting
12
+ * the model with a tight system prompt. The interface (`AutoVerdict`)
13
+ * is stable so we can swap implementations without touching the gate.
14
+ *
15
+ * Design notes:
16
+ *
17
+ * - Patterns are conservative: a read-only command is only
18
+ * allow-listed когда its argv shape is unambiguous (no `-exec`,
19
+ * no `--delete`, no `|` к shell). When в doubt, fall back to ask.
20
+ * - The denylist matches catastrophic patterns even в auto-mode so
21
+ * a misclick can't shred the workspace. The circuit-breaker
22
+ * (`circuit-breaker.ts`) covers the same surface для bypass-mode;
23
+ * this denylist is the auto-mode equivalent.
24
+ * - All matches operate on the FULL command string, not parsed
25
+ * argv. This is deliberately permissive on whitespace but strict
26
+ * on operator characters (`|`, `&`, `;`, `>`, backticks) — a
27
+ * pipe-into-shell или command-chain forces fallback к ask.
28
+ */
29
+ /**
30
+ * Catastrophic patterns — the auto-mode regex denylist. Each entry
31
+ * carries a human-readable reason surfaced в the deny payload so the
32
+ * operator + audit log see why the gate refused. Order matters: most-
33
+ * specific первой так "rm -rf /" reports as that, не the generic
34
+ * "rm -rf".
35
+ */
36
+ const AUTO_DENY_PATTERNS = [
37
+ { pattern: /\brm\s+(-[a-z]*r[a-z]*f|-[a-z]*f[a-z]*r)\b/i, reason: 'rm -rf (recursive force-delete)' },
38
+ { pattern: /\bgit\s+push\s+(-{1,2}force\b|\-f\b)/i, reason: 'git push --force (history rewrite)' },
39
+ { pattern: /\bgit\s+reset\s+--hard\b/i, reason: 'git reset --hard (uncommitted-work loss)' },
40
+ { pattern: /\bdd\s+if=\/(dev|)/i, reason: 'dd if=/dev/* (raw device read/write)' },
41
+ { pattern: /\bmkfs(\.|\s|$)/i, reason: 'mkfs (filesystem format)' },
42
+ { pattern: /\bchmod\s+-R\s+777\b/i, reason: 'chmod -R 777 (world-writable recursive)' },
43
+ { pattern: /\bchown\s+-R\b/i, reason: 'chown -R (recursive ownership change)' },
44
+ { pattern: /:\(\)\s*\{\s*:\s*\|\s*:\s*&\s*\}\s*;?\s*:/, reason: 'fork bomb signature' },
45
+ { pattern: /\bsudo\b/i, reason: 'sudo (privilege escalation)' },
46
+ { pattern: /\b(npm|pnpm|yarn)\s+publish\b/i, reason: 'package publish (irreversible npm release)' },
47
+ { pattern: /\b(curl|wget)\b[^|;&]*\|\s*(sh|bash|zsh)\b/i, reason: 'pipe-to-shell installer (curl … | sh)' },
48
+ ];
49
+ /**
50
+ * Safe-by-default patterns — auto-mode regex allowlist. Each regex
51
+ * must match the FULL command (with `^…$` anchors) so a leading
52
+ * `sudo ls` или a trailing `; rm -rf /` does NOT slip through. The
53
+ * caller passes the trimmed command string; whitespace around argv
54
+ * tokens is tolerated.
55
+ */
56
+ const AUTO_ALLOW_PATTERNS = [
57
+ { pattern: /^ls(\s+-[a-zA-Z]+)*(\s+[^|;&`>$()\\]+)?$/, reason: 'ls (directory listing)' },
58
+ { pattern: /^pwd\s*$/, reason: 'pwd (working directory)' },
59
+ { pattern: /^cat\s+[^|;&`>$()\\]+$/, reason: 'cat (file read)' },
60
+ { pattern: /^head(\s+-n?\s*\d+)?\s+[^|;&`>$()\\]+$/, reason: 'head (file preview)' },
61
+ { pattern: /^tail(\s+-n?\s*\d+)?\s+[^|;&`>$()\\]+$/, reason: 'tail (file preview)' },
62
+ { pattern: /^wc(\s+-[a-z]+)?\s+[^|;&`>$()\\]+$/, reason: 'wc (line/word count)' },
63
+ { pattern: /^du\s+-sh?\s+[^|;&`>$()\\]+$/, reason: 'du -sh (disk usage summary)' },
64
+ { pattern: /^df\s+-h\s*$/, reason: 'df -h (filesystem free space)' },
65
+ { pattern: /^git\s+status(\s+--short|\s+-s)?\s*$/, reason: 'git status' },
66
+ { pattern: /^git\s+diff(\s+[a-zA-Z0-9_./~^-]+)*\s*$/, reason: 'git diff (read-only)' },
67
+ { pattern: /^git\s+log(\s+[a-zA-Z0-9_./~^-]+)*\s*$/, reason: 'git log (read-only)' },
68
+ { pattern: /^git\s+branch(\s+-[a-z]+)?\s*$/, reason: 'git branch (read-only)' },
69
+ { pattern: /^git\s+remote\s+-v\s*$/, reason: 'git remote -v (read-only)' },
70
+ { pattern: /^pnpm\s+(typecheck|lint|test\s+--run|test\s+--watch=false)\s*$/, reason: 'pnpm read-only build check' },
71
+ { pattern: /^npm\s+(--version|-v|run\s+typecheck|run\s+lint)\s*$/, reason: 'npm read-only check' },
72
+ { pattern: /^node\s+--version\s*$/, reason: 'node --version' },
73
+ { pattern: /^pnpm\s+--version\s*$/, reason: 'pnpm --version' },
74
+ { pattern: /^which\s+[a-zA-Z0-9_-]+\s*$/, reason: 'which (command lookup)' },
75
+ { pattern: /^find\s+\.\s+-type\s+f(\s+-name\s+[^|;&`>$()\\]+)?\s*$/, reason: 'find -type f (read-only)' },
76
+ { pattern: /^(rg|ripgrep|grep)\s+(-[a-z]+\s+)*[^|;&`>$()\\]+(\s+[^|;&`>$()\\]+)?\s*$/, reason: 'grep/ripgrep (read-only search)' },
77
+ ];
78
+ /**
79
+ * Classify an auto-mode command. Order:
80
+ * 1. Catastrophic deny patterns — surface the explicit deny reason.
81
+ * 2. Safe allow patterns — surface the matched reason.
82
+ * 3. Fallback к ask.
83
+ *
84
+ * The order matters: a destructive pattern that ALSO looks like a
85
+ * read-only token (e.g. `git diff ; rm -rf .`) hits deny first because
86
+ * the allow patterns require `^…$` anchors that the chained command
87
+ * fails to satisfy. Belt + suspenders.
88
+ */
89
+ export function classifyAutoMode(command) {
90
+ const trimmed = command.trim();
91
+ if (trimmed.length === 0)
92
+ return { verdict: 'ask' };
93
+ for (const entry of AUTO_DENY_PATTERNS) {
94
+ if (entry.pattern.test(trimmed)) {
95
+ return {
96
+ verdict: 'deny',
97
+ reason: entry.reason,
98
+ pattern: entry.pattern.source,
99
+ };
100
+ }
101
+ }
102
+ for (const entry of AUTO_ALLOW_PATTERNS) {
103
+ if (entry.pattern.test(trimmed)) {
104
+ return {
105
+ verdict: 'allow',
106
+ reason: entry.reason,
107
+ pattern: entry.pattern.source,
108
+ };
109
+ }
110
+ }
111
+ return { verdict: 'ask' };
112
+ }
113
+ /**
114
+ * Diagnostic accessors — exposed для doctor surfaces + spec coverage.
115
+ * The arrays are frozen at module load so callers can iterate without
116
+ * mutating the source-of-truth.
117
+ */
118
+ export function listAutoAllowPatterns() {
119
+ return AUTO_ALLOW_PATTERNS.map((e) => ({ pattern: e.pattern.source, reason: e.reason }));
120
+ }
121
+ export function listAutoDenyPatterns() {
122
+ return AUTO_DENY_PATTERNS.map((e) => ({ pattern: e.pattern.source, reason: e.reason }));
123
+ }
124
+ //# sourceMappingURL=auto-classifier.js.map