@pugi/cli 0.1.0-beta.8 → 0.1.0-beta.88

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 (405) hide show
  1. package/CHANGELOG.md +132 -0
  2. package/LICENSE +1 -1
  3. package/THIRD_PARTY_NOTICES.md +40 -0
  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/smoke.js +133 -0
  11. package/dist/core/agent-progress/cleanup.js +134 -0
  12. package/dist/core/agent-progress/schema.js +144 -0
  13. package/dist/core/agent-progress/writer.js +101 -0
  14. package/dist/core/agents/adaptive-router.js +330 -0
  15. package/dist/core/agents/query-decomposer.js +297 -0
  16. package/dist/core/agents/registry.js +3 -3
  17. package/dist/core/approvals/shortcut-resolver.js +98 -0
  18. package/dist/core/artifact-chain/dispatcher.js +148 -0
  19. package/dist/core/artifact-chain/exporter.js +164 -0
  20. package/dist/core/artifact-chain/state.js +243 -0
  21. package/dist/core/artifact-chain/steps.js +169 -0
  22. package/dist/core/ask-user/question.js +92 -0
  23. package/dist/core/audit/audit-trail.js +275 -0
  24. package/dist/core/auth/ensure-authenticated.js +129 -0
  25. package/dist/core/auth/env-provider.js +238 -0
  26. package/dist/core/auto-open-browser.js +4 -4
  27. package/dist/core/auto-update/channels.js +122 -0
  28. package/dist/core/auto-update/checker.js +241 -0
  29. package/dist/core/auto-update/state.js +235 -0
  30. package/dist/core/bare-mode/index.js +107 -0
  31. package/dist/core/bash/redirect.js +281 -0
  32. package/dist/core/bash-classifier.js +436 -40
  33. package/dist/core/checkpoint/resumer.js +149 -0
  34. package/dist/core/checkpoint/rewinder.js +291 -0
  35. package/dist/core/checkpoints/shadow-git.js +670 -0
  36. package/dist/core/citations/parser.js +109 -0
  37. package/dist/core/classifier/yolo-classifier.js +88 -0
  38. package/dist/core/codegraph/decision-store.js +248 -0
  39. package/dist/core/codegraph/detect-repo.js +459 -0
  40. package/dist/core/codegraph/install.js +134 -0
  41. package/dist/core/codegraph/offer-hook.js +220 -0
  42. package/dist/core/compact/auto-trigger.js +96 -0
  43. package/dist/core/compact/buffer-rewriter.js +115 -0
  44. package/dist/core/compact/summarizer.js +208 -0
  45. package/dist/core/compact/token-counter.js +108 -0
  46. package/dist/core/consensus/anvil-fanout.js +25 -25
  47. package/dist/core/consensus/diff-capture.js +121 -12
  48. package/dist/core/consensus/rubric.js +21 -21
  49. package/dist/core/context/builder.js +6 -6
  50. package/dist/core/context/compaction-events.js +8 -8
  51. package/dist/core/context/compaction.js +31 -31
  52. package/dist/core/context/index.js +15 -8
  53. package/dist/core/context/invariants.js +51 -51
  54. package/dist/core/context/markdown-loader.js +28 -10
  55. package/dist/core/context/markdown-traverse.js +255 -0
  56. package/dist/core/context/pugiignore.js +41 -41
  57. package/dist/core/context/repo-skeleton.js +37 -37
  58. package/dist/core/context/tool-eviction.js +55 -0
  59. package/dist/core/context/watcher.js +32 -32
  60. package/dist/core/context/working-set.js +23 -23
  61. package/dist/core/coordinator/agent-tools.js +77 -0
  62. package/dist/core/coordinator/agent-toolset.js +65 -0
  63. package/dist/core/coordinator/fsm.js +73 -0
  64. package/dist/core/coordinator/mode-fsm.js +70 -0
  65. package/dist/core/cost/rate-card.js +129 -0
  66. package/dist/core/cost/tracker.js +221 -0
  67. package/dist/core/credentials.js +12 -12
  68. package/dist/core/cron/scheduler.js +138 -0
  69. package/dist/core/denial-tracking/index.js +8 -0
  70. package/dist/core/denial-tracking/state.js +264 -0
  71. package/dist/core/diagnostics/probe-runner.js +93 -0
  72. package/dist/core/diagnostics/probes/api.js +46 -0
  73. package/dist/core/diagnostics/probes/auth.js +93 -0
  74. package/dist/core/diagnostics/probes/bare-mode.js +42 -0
  75. package/dist/core/diagnostics/probes/cli-version.js +127 -0
  76. package/dist/core/diagnostics/probes/config.js +72 -0
  77. package/dist/core/diagnostics/probes/denial-tracking.js +57 -0
  78. package/dist/core/diagnostics/probes/disk.js +81 -0
  79. package/dist/core/diagnostics/probes/engine-live.js +46 -0
  80. package/dist/core/diagnostics/probes/git.js +65 -0
  81. package/dist/core/diagnostics/probes/hooks.js +118 -0
  82. package/dist/core/diagnostics/probes/mcp.js +75 -0
  83. package/dist/core/diagnostics/probes/node.js +59 -0
  84. package/dist/core/diagnostics/probes/pnpm.js +36 -0
  85. package/dist/core/diagnostics/probes/pugi-md.js +89 -0
  86. package/dist/core/diagnostics/probes/sandbox.js +40 -0
  87. package/dist/core/diagnostics/probes/session.js +74 -0
  88. package/dist/core/diagnostics/probes/status-snapshot.js +488 -0
  89. package/dist/core/diagnostics/probes/workspace.js +63 -0
  90. package/dist/core/diagnostics/types.js +70 -0
  91. package/dist/core/dispatch/cache-cleanup.js +197 -0
  92. package/dist/core/dispatch/cache-handoff.js +295 -0
  93. package/dist/core/edits/apply-patch-layer-e.js +189 -0
  94. package/dist/core/edits/dispatch.js +293 -7
  95. package/dist/core/edits/format-matrix.js +26 -0
  96. package/dist/core/edits/fuzzy-ladder.js +650 -0
  97. package/dist/core/edits/index.js +3 -1
  98. package/dist/core/edits/journal.js +199 -0
  99. package/dist/core/edits/layer-a-apply.js +15 -15
  100. package/dist/core/edits/layer-a-fuzzy-apply.js +198 -0
  101. package/dist/core/edits/layer-b-apply.js +9 -9
  102. package/dist/core/edits/layer-c-apply.js +6 -6
  103. package/dist/core/edits/layer-d-ast.js +557 -14
  104. package/dist/core/edits/marker-parser.js +12 -12
  105. package/dist/core/edits/security-gate.js +27 -27
  106. package/dist/core/edits/verify-hook.js +273 -0
  107. package/dist/core/edits/worktree.js +322 -0
  108. package/dist/core/engine/anvil-client.js +151 -26
  109. package/dist/core/engine/auto-compact.js +179 -0
  110. package/dist/core/engine/budgets.js +186 -0
  111. package/dist/core/engine/context-prefix.js +155 -0
  112. package/dist/core/engine/index.js +1 -1
  113. package/dist/core/engine/intensity.js +158 -0
  114. package/dist/core/engine/intent.js +260 -0
  115. package/dist/core/engine/native-pugi.js +1295 -227
  116. package/dist/core/engine/prompts.js +134 -16
  117. package/dist/core/engine/strip-internal-fields.js +124 -0
  118. package/dist/core/engine/tool-bridge.js +1295 -59
  119. package/dist/core/evaluation/golden-dataset.js +293 -0
  120. package/dist/core/feedback/queue.js +177 -0
  121. package/dist/core/feedback/submitter.js +145 -0
  122. package/dist/core/file-cache.js +113 -1
  123. package/dist/core/flatten/flatten-repo.js +439 -0
  124. package/dist/core/format/osc8-link.js +28 -0
  125. package/dist/core/hook-chains.js +392 -0
  126. package/dist/core/hooks/citation-verify-hook.js +138 -0
  127. package/dist/core/hooks/citation-verify.js +112 -0
  128. package/dist/core/hooks/events.js +44 -0
  129. package/dist/core/hooks/index.js +15 -0
  130. package/dist/core/hooks/registry.js +213 -0
  131. package/dist/core/hooks/runner.js +236 -0
  132. package/dist/core/hooks/v2/event-emitter.js +115 -0
  133. package/dist/core/hooks/v2/executor.js +282 -0
  134. package/dist/core/hooks/v2/index.js +25 -0
  135. package/dist/core/hooks/v2/lifecycle.js +104 -0
  136. package/dist/core/hooks/v2/loader.js +216 -0
  137. package/dist/core/hooks/v2/matcher.js +125 -0
  138. package/dist/core/hooks/v2/trust.js +143 -0
  139. package/dist/core/hooks/v2/types.js +86 -0
  140. package/dist/core/image/renderer.js +71 -0
  141. package/dist/core/init/detector.js +582 -0
  142. package/dist/core/init/template-renderer.js +242 -0
  143. package/dist/core/jobs/registry.js +18 -18
  144. package/dist/core/ledger/results-tsv.js +142 -0
  145. package/dist/core/log-discipline/stdout-redirect.js +51 -0
  146. package/dist/core/lsp/cache.js +105 -0
  147. package/dist/core/lsp/client.js +776 -0
  148. package/dist/core/lsp/language-detect.js +66 -0
  149. package/dist/core/lsp/post-edit-diagnostics.js +171 -0
  150. package/dist/core/lsp/symbol-tools.js +372 -0
  151. package/dist/core/mcp/client.js +97 -28
  152. package/dist/core/mcp/http-server.js +553 -0
  153. package/dist/core/mcp/orchestrator-tools.js +662 -0
  154. package/dist/core/mcp/permission.js +190 -0
  155. package/dist/core/mcp/registry.js +39 -17
  156. package/dist/core/mcp/server-tools.js +219 -0
  157. package/dist/core/mcp/server.js +397 -0
  158. package/dist/core/mcp/trust.js +10 -10
  159. package/dist/core/memory/dual-write.js +416 -0
  160. package/dist/core/memory/passive-extract.js +130 -0
  161. package/dist/core/memory/phase1-kinds.js +20 -0
  162. package/dist/core/memory/secret-scanner.js +304 -0
  163. package/dist/core/memory-sync/queue.js +170 -0
  164. package/dist/core/metrics/extract.js +113 -0
  165. package/dist/core/modes/roo-modes.js +68 -0
  166. package/dist/core/onboarding/ensure-initialized.js +133 -0
  167. package/dist/core/onboarding/marker.js +111 -0
  168. package/dist/core/onboarding/telemetry-state.js +108 -0
  169. package/dist/core/output-style/presets.js +176 -0
  170. package/dist/core/output-style/state.js +185 -0
  171. package/dist/core/path-security.js +287 -5
  172. package/dist/core/permission.js +82 -22
  173. package/dist/core/permissions/auto-classifier.js +124 -0
  174. package/dist/core/permissions/bash-parser.js +371 -0
  175. package/dist/core/permissions/circuit-breaker.js +83 -0
  176. package/dist/core/permissions/constrained-edit.js +91 -0
  177. package/dist/core/permissions/gate.js +278 -0
  178. package/dist/core/permissions/index.js +20 -0
  179. package/dist/core/permissions/mode.js +174 -0
  180. package/dist/core/permissions/network-egress.js +137 -0
  181. package/dist/core/permissions/state.js +241 -0
  182. package/dist/core/permissions/tool-class.js +93 -0
  183. package/dist/core/plan-mode/ui-state.js +51 -0
  184. package/dist/core/plans/plan-artifact.js +721 -0
  185. package/dist/core/policy-limits/etag-store.js +122 -0
  186. package/dist/core/prd-check/parser.js +215 -0
  187. package/dist/core/prd-check/reporter.js +127 -0
  188. package/dist/core/prd-check/session-review.js +557 -0
  189. package/dist/core/prd-check/verifiers.js +223 -0
  190. package/dist/core/prompt-cache/client-cache.js +99 -0
  191. package/dist/core/prompts/assembly.js +29 -0
  192. package/dist/core/prompts/registry.js +364 -0
  193. package/dist/core/pugi-md/cc-compat-rules.js +735 -0
  194. package/dist/core/pugi-md/context-injector.js +76 -0
  195. package/dist/core/pugi-md/walk-up.js +207 -0
  196. package/dist/core/python/uv-installer.js +270 -0
  197. package/dist/core/python/uv-resolver.js +83 -0
  198. package/dist/core/rate-limit/narrator.js +146 -0
  199. package/dist/core/recipes/cli-types.js +20 -0
  200. package/dist/core/recipes/loader.js +103 -0
  201. package/dist/core/recipes/runner.js +345 -0
  202. package/dist/core/recipes/schema.js +587 -0
  203. package/dist/core/release-notes/parser.js +241 -0
  204. package/dist/core/release-notes/state.js +116 -0
  205. package/dist/core/repl/ask.js +37 -37
  206. package/dist/core/repl/cancellation.js +26 -26
  207. package/dist/core/repl/cap-warning.js +4 -4
  208. package/dist/core/repl/clipboard-read.js +11 -11
  209. package/dist/core/repl/dispatch-fsm.js +12 -12
  210. package/dist/core/repl/history-search.js +15 -15
  211. package/dist/core/repl/history.js +28 -18
  212. package/dist/core/repl/kill-ring.js +5 -5
  213. package/dist/core/repl/model-pricing.js +135 -0
  214. package/dist/core/repl/privacy-banner.js +22 -22
  215. package/dist/core/repl/session.js +2157 -214
  216. package/dist/core/repl/slash-commands.js +533 -40
  217. package/dist/core/repl/store/index.js +1 -1
  218. package/dist/core/repl/store/jsonl-log.js +22 -22
  219. package/dist/core/repl/store/lockfile.js +10 -10
  220. package/dist/core/repl/store/session-store.js +136 -107
  221. package/dist/core/repl/store/types.js +15 -15
  222. package/dist/core/repl/store/uuid-v7.js +12 -12
  223. package/dist/core/repl/workspace-context.js +43 -21
  224. package/dist/core/repo-map/build.js +125 -0
  225. package/dist/core/repo-map/cache.js +185 -0
  226. package/dist/core/repo-map/extractor.js +254 -0
  227. package/dist/core/repo-map/formatter.js +145 -0
  228. package/dist/core/repo-map/page-rank.js +105 -0
  229. package/dist/core/repo-map/scanner.js +211 -0
  230. package/dist/core/retry-budget/budget.js +284 -0
  231. package/dist/core/retry-budget/index.js +5 -0
  232. package/dist/core/retry-budget/retry-cap.js +74 -0
  233. package/dist/core/routing/lead-worker.js +43 -0
  234. package/dist/core/routing/pre-flight-estimator.js +108 -0
  235. package/dist/core/runs/run-tree.js +103 -0
  236. package/dist/core/security/injection-scanner.js +367 -0
  237. package/dist/core/security/output-filter.js +418 -0
  238. package/dist/core/session/env-file.js +105 -0
  239. package/dist/core/session/section-budgets.js +140 -0
  240. package/dist/core/session.js +92 -0
  241. package/dist/core/settings.js +298 -5
  242. package/dist/core/share/formatter.js +271 -0
  243. package/dist/core/share/redactor.js +221 -0
  244. package/dist/core/share/uploader.js +267 -0
  245. package/dist/core/skills/defaults.js +457 -0
  246. package/dist/core/skills/loader.js +22 -22
  247. package/dist/core/skills/sources.js +27 -27
  248. package/dist/core/smoke/headless-driver.js +174 -0
  249. package/dist/core/smoke/orchestrator.js +194 -0
  250. package/dist/core/smoke/runner.js +238 -0
  251. package/dist/core/smoke/scenario-parser.js +316 -0
  252. package/dist/core/statusline.js +99 -0
  253. package/dist/core/subagents/dispatcher-real.js +600 -0
  254. package/dist/core/subagents/dispatcher.js +132 -43
  255. package/dist/core/subagents/index.js +19 -6
  256. package/dist/core/subagents/isolation-matrix.js +213 -0
  257. package/dist/core/subagents/spawn.js +19 -4
  258. package/dist/core/telemetry/emitter.js +229 -0
  259. package/dist/core/telemetry/queue.js +251 -0
  260. package/dist/core/theme/context.js +91 -0
  261. package/dist/core/theme/presets.js +228 -0
  262. package/dist/core/theme/state.js +181 -0
  263. package/dist/core/todos/invariant.js +10 -0
  264. package/dist/core/todos/state.js +177 -0
  265. package/dist/core/tool-schema/compressor.js +89 -0
  266. package/dist/core/transport/version-interceptor.js +166 -0
  267. package/dist/core/trust.js +2 -2
  268. package/dist/core/tui/thinking-block.js +64 -0
  269. package/dist/core/vim/keymap.js +288 -0
  270. package/dist/core/vim/state.js +92 -0
  271. package/dist/core/watch-markers/marker-watcher.js +133 -0
  272. package/dist/core/worktree-manager/cleanup.js +123 -0
  273. package/dist/core/worktree-manager/manager.js +303 -0
  274. package/dist/index.js +36 -0
  275. package/dist/runtime/bootstrap.js +190 -0
  276. package/dist/runtime/cli.js +4203 -493
  277. package/dist/runtime/commands/agents.js +30 -30
  278. package/dist/runtime/commands/budget.js +5 -5
  279. package/dist/runtime/commands/cancel.js +231 -0
  280. package/dist/runtime/commands/chain.js +489 -0
  281. package/dist/runtime/commands/codegraph-status.js +227 -0
  282. package/dist/runtime/commands/compact.js +297 -0
  283. package/dist/runtime/commands/config.js +73 -39
  284. package/dist/runtime/commands/cost.js +199 -0
  285. package/dist/runtime/commands/delegate.js +244 -13
  286. package/dist/runtime/commands/dispatch.js +126 -0
  287. package/dist/runtime/commands/doctor.js +579 -0
  288. package/dist/runtime/commands/feedback.js +184 -0
  289. package/dist/runtime/commands/hooks.js +184 -0
  290. package/dist/runtime/commands/init.js +254 -0
  291. package/dist/runtime/commands/lsp.js +368 -0
  292. package/dist/runtime/commands/mcp.js +879 -0
  293. package/dist/runtime/commands/memory.js +582 -0
  294. package/dist/runtime/commands/model.js +237 -0
  295. package/dist/runtime/commands/onboarding.js +275 -0
  296. package/dist/runtime/commands/patch.js +128 -0
  297. package/dist/runtime/commands/permissions.js +112 -0
  298. package/dist/runtime/commands/plan.js +143 -0
  299. package/dist/runtime/commands/prd-check.js +285 -0
  300. package/dist/runtime/commands/privacy.js +17 -17
  301. package/dist/runtime/commands/recipe.js +325 -0
  302. package/dist/runtime/commands/redo-blob-store.js +92 -0
  303. package/dist/runtime/commands/redo.js +361 -0
  304. package/dist/runtime/commands/release-notes.js +229 -0
  305. package/dist/runtime/commands/repo-map.js +95 -0
  306. package/dist/runtime/commands/report.js +299 -0
  307. package/dist/runtime/commands/resume.js +118 -0
  308. package/dist/runtime/commands/review-consensus.js +68 -53
  309. package/dist/runtime/commands/rewind.js +333 -0
  310. package/dist/runtime/commands/roster.js +14 -14
  311. package/dist/runtime/commands/sessions.js +163 -0
  312. package/dist/runtime/commands/share.js +316 -0
  313. package/dist/runtime/commands/skills.js +31 -31
  314. package/dist/runtime/commands/status.js +186 -0
  315. package/dist/runtime/commands/stickers.js +82 -0
  316. package/dist/runtime/commands/style.js +194 -0
  317. package/dist/runtime/commands/theme.js +196 -0
  318. package/dist/runtime/commands/undo.js +54 -22
  319. package/dist/runtime/commands/update.js +289 -0
  320. package/dist/runtime/commands/vim.js +140 -0
  321. package/dist/runtime/commands/worktree.js +177 -0
  322. package/dist/runtime/commands/worktrees.js +155 -0
  323. package/dist/runtime/headless-repl.js +195 -0
  324. package/dist/runtime/headless.js +543 -0
  325. package/dist/runtime/load-hooks-or-exit.js +71 -0
  326. package/dist/runtime/plan-decompose.js +531 -0
  327. package/dist/runtime/sigint-guard.js +272 -0
  328. package/dist/runtime/update-check.js +28 -28
  329. package/dist/runtime/version.js +65 -0
  330. package/dist/skills/bundled/batch.js +617 -0
  331. package/dist/skills/bundled/index.js +45 -0
  332. package/dist/skills/bundled/loop.js +358 -0
  333. package/dist/skills/bundled/remember.js +383 -0
  334. package/dist/skills/bundled/simplify.js +289 -0
  335. package/dist/skills/bundled/skillify.js +373 -0
  336. package/dist/skills/bundled/stuck.js +558 -0
  337. package/dist/skills/bundled/verify.js +439 -0
  338. package/dist/testing/vcr.js +486 -0
  339. package/dist/tools/agent-tool.js +229 -0
  340. package/dist/tools/apply-patch.js +556 -0
  341. package/dist/tools/ask-user-question.js +288 -0
  342. package/dist/tools/ask-user.js +115 -0
  343. package/dist/tools/bash.js +624 -46
  344. package/dist/tools/brief.js +224 -0
  345. package/dist/tools/enter-worktree.js +250 -0
  346. package/dist/tools/exit-worktree.js +147 -0
  347. package/dist/tools/file-tools.js +161 -44
  348. package/dist/tools/lsp-tools.js +189 -0
  349. package/dist/tools/mcp-tool.js +260 -0
  350. package/dist/tools/multi-edit.js +361 -0
  351. package/dist/tools/powershell.js +268 -0
  352. package/dist/tools/registry.js +85 -0
  353. package/dist/tools/skill-tool.js +96 -0
  354. package/dist/tools/sleep.js +99 -0
  355. package/dist/tools/synthetic-output.js +133 -0
  356. package/dist/tools/tasks.js +208 -0
  357. package/dist/tools/todo-write.js +184 -0
  358. package/dist/tools/verify-plan-execution.js +295 -0
  359. package/dist/tools/web-fetch-injection-scanner.js +207 -0
  360. package/dist/tools/web-fetch.js +195 -10
  361. package/dist/tools/web-search.js +458 -0
  362. package/dist/tui/agent-progress-card.js +111 -0
  363. package/dist/tui/agent-tree.js +11 -1
  364. package/dist/tui/ask-modal.js +14 -14
  365. package/dist/tui/ask-user-question-chips.js +257 -0
  366. package/dist/tui/ask-user-question-prompt.js +203 -0
  367. package/dist/tui/compact-banner.js +81 -0
  368. package/dist/tui/conversation-pane.js +85 -11
  369. package/dist/tui/cost-table.js +111 -0
  370. package/dist/tui/device-flow.js +2 -2
  371. package/dist/tui/doctor-table.js +46 -0
  372. package/dist/tui/feedback-prompt.js +156 -0
  373. package/dist/tui/input-box.js +247 -32
  374. package/dist/tui/login-picker.js +3 -3
  375. package/dist/tui/markdown-render.js +6 -6
  376. package/dist/tui/onboarding-wizard.js +240 -0
  377. package/dist/tui/permissions-picker.js +86 -0
  378. package/dist/tui/render.js +35 -0
  379. package/dist/tui/repl-render.js +332 -54
  380. package/dist/tui/repl-splash-art.js +16 -16
  381. package/dist/tui/repl-splash-mascot.js +48 -24
  382. package/dist/tui/repl-splash.js +22 -22
  383. package/dist/tui/repl.js +124 -44
  384. package/dist/tui/slash-palette.js +6 -6
  385. package/dist/tui/splash.js +2 -2
  386. package/dist/tui/status-bar.js +109 -31
  387. package/dist/tui/status-table.js +7 -0
  388. package/dist/tui/stickers-art.js +136 -0
  389. package/dist/tui/style-table.js +28 -0
  390. package/dist/tui/theme-table.js +29 -0
  391. package/dist/tui/thinking-spinner.js +123 -0
  392. package/dist/tui/tool-stream-pane.js +53 -4
  393. package/dist/tui/update-banner.js +27 -2
  394. package/dist/tui/vim-input.js +267 -0
  395. package/dist/tui/welcome-banner.js +107 -0
  396. package/dist/tui/welcome-data.js +293 -0
  397. package/dist/tui/workspace-context.js +2 -2
  398. package/docs/examples/codegraph.mcp.json +10 -0
  399. package/package.json +25 -7
  400. package/test/scenarios/codegen-create-file.scenario.txt +13 -0
  401. package/test/scenarios/compact-force.scenario.txt +11 -0
  402. package/test/scenarios/identity.scenario.txt +11 -0
  403. package/test/scenarios/persona-handoff.scenario.txt +11 -0
  404. package/test/scenarios/walkback.scenario.txt +12 -0
  405. package/dist/core/engine/compaction-hook.js +0 -154
@@ -0,0 +1,199 @@
1
+ /**
2
+ * Edit journal — β1b Pl8 .
3
+ *
4
+ * Per-session append-only log of file-mutation INTENT before the
5
+ * dispatcher runs. Lives at `<root>/.pugi/sessions/<sessionId>/journal.jsonl`,
6
+ * one JSON entry per multi-file dispatch. On a non-zero exit / budget
7
+ * kill / OOM crash, the journal lets `git restore` / `fs.unlink` revert
8
+ * the workspace to its pre-dispatch state.
9
+ *
10
+ * Shape:
11
+ *
12
+ * ```ts
13
+ * {
14
+ * ts: number, // ms epoch — opens chronological replay
15
+ * taskId: string, // engine task id (`<kind>-<ts>`) for correlation
16
+ * files: [{
17
+ * path: string, // workspace-relative
18
+ * existed: boolean, // existed BEFORE the dispatch
19
+ * sha256_before?: string, // present iff existed === true
20
+ * }],
21
+ * }
22
+ * ```
23
+ *
24
+ * The journal records ONLY intent: it does not enumerate the post-state
25
+ * (a per-edit `applied` event already lives in `events.jsonl`). On
26
+ * rollback we need the pre-state to know:
27
+ * - whether to `git restore` (the file existed before and is git-
28
+ * tracked) OR `fs.unlink` (the file did NOT exist before — it was
29
+ * created by the failed dispatch).
30
+ * - whether to fall back to writing the cached `sha256_before` source
31
+ * when neither git nor fs can recover (untracked file that existed
32
+ * before — the dispatcher MUST have cached the pre-content; we
33
+ * surface a `partial_rollback` reason and let the operator decide).
34
+ *
35
+ * Why a separate journal vs reusing `events.jsonl`:
36
+ * - events.jsonl mixes status + tool_call + tool_result + outcome
37
+ * records from the engine loop. A crash mid-dispatch leaves the
38
+ * file in a state where reconstructing "files we MIGHT have
39
+ * touched" requires correlating multiple event types. The journal
40
+ * hoists the rollback-relevant subset into one line per dispatch
41
+ * so the recovery code is grep-able + audit-readable.
42
+ * - The journal is also forward-compatible with a future
43
+ * `pugi resume --rollback <taskId>` operator command — single
44
+ * source of truth for "what would I undo".
45
+ *
46
+ * Best-effort writes: a journal failure (disk full, .pugi unwritable)
47
+ * must NEVER block the actual edit. The dispatcher proceeds with
48
+ * `journalEntryId: null` and surfaces a warning in the session's
49
+ * status events. Rollback then degrades to apply-patch-style
50
+ * pre-existing snapshot (the dispatcher keeps an in-memory copy too).
51
+ *
52
+ * Brand voice: ASCII only, no banned words. Operator-facing tail
53
+ * (jq friendly).
54
+ */
55
+ import { appendFileSync, existsSync, mkdirSync, readFileSync, statSync, } from 'node:fs';
56
+ import { createHash } from 'node:crypto';
57
+ import { join, resolve } from 'node:path';
58
+ /**
59
+ * Compute the on-disk journal path for a session. The directory is
60
+ * created lazily by `appendEntry` so a read against a missing session
61
+ * does not silently mkdir.
62
+ */
63
+ export function journalPath(workspaceRoot, sessionId) {
64
+ return resolve(workspaceRoot, '.pugi', 'sessions', sessionId, 'journal.jsonl');
65
+ }
66
+ /**
67
+ * sha256 of a file's bytes. Used by `snapshotForDispatch` to capture
68
+ * the pre-content fingerprint so rollback can detect "file was changed
69
+ * between intent + rollback" cases (rare but real — a concurrent
70
+ * external editor + the dispatcher hitting the same path).
71
+ *
72
+ * Returns null when the file cannot be hashed (missing / unreadable);
73
+ * caller treats absent fingerprint as `existed=false`.
74
+ */
75
+ export function sha256File(absPath) {
76
+ try {
77
+ const buf = readFileSync(absPath);
78
+ const h = createHash('sha256');
79
+ h.update(buf);
80
+ return h.digest('hex');
81
+ }
82
+ catch {
83
+ return null;
84
+ }
85
+ }
86
+ /**
87
+ * Build a snapshot of the pre-dispatch state for every workspace-relative
88
+ * path the dispatcher is about to touch. Pure read; no writes.
89
+ *
90
+ * `paths` are workspace-relative. The journal records the SAME shape so
91
+ * the rollback path can re-resolve against the same `workspaceRoot`
92
+ * without ambiguity.
93
+ */
94
+ export function snapshotForDispatch(workspaceRoot, paths) {
95
+ const out = [];
96
+ for (const rel of paths) {
97
+ const abs = resolve(workspaceRoot, rel);
98
+ if (existsSync(abs)) {
99
+ // Hash only on regular files; sha256File returns null for
100
+ // directories or unreadable inodes and we degrade cleanly.
101
+ try {
102
+ const st = statSync(abs);
103
+ if (st.isFile()) {
104
+ const sha = sha256File(abs);
105
+ out.push({ path: rel, existed: true, ...(sha ? { sha256_before: sha } : {}) });
106
+ continue;
107
+ }
108
+ }
109
+ catch {
110
+ /* fall through — treat as non-existent for rollback */
111
+ }
112
+ }
113
+ out.push({ path: rel, existed: false });
114
+ }
115
+ return out;
116
+ }
117
+ /**
118
+ * Append one journal entry. Best-effort: any I/O failure returns false
119
+ * and the dispatcher proceeds without journal-backed rollback. The
120
+ * in-memory `JournalFileEntry[]` snapshot still drives the immediate
121
+ * post-crash rollback inside the same process.
122
+ */
123
+ export function appendEntry(workspaceRoot, sessionId, entry) {
124
+ const path = journalPath(workspaceRoot, sessionId);
125
+ try {
126
+ mkdirSync(join(workspaceRoot, '.pugi', 'sessions', sessionId), {
127
+ recursive: true,
128
+ mode: 0o700,
129
+ });
130
+ }
131
+ catch {
132
+ return false;
133
+ }
134
+ try {
135
+ appendFileSync(path, `${JSON.stringify(entry)}\n`, { encoding: 'utf8', mode: 0o600 });
136
+ return true;
137
+ }
138
+ catch {
139
+ return false;
140
+ }
141
+ }
142
+ /**
143
+ * Read every journal entry for a session, oldest-first. Malformed
144
+ * lines are dropped silently — a single bad line should not nuke
145
+ * recovery. Returns `[]` when the file is missing.
146
+ *
147
+ * Synchronous (mirrors `recordToolCall` etc) because the operator-
148
+ * facing rollback path needs to be deterministic; a partially-
149
+ * streamed read would race the very crash recovery it is supporting.
150
+ */
151
+ export function readEntries(workspaceRoot, sessionId) {
152
+ const path = journalPath(workspaceRoot, sessionId);
153
+ if (!existsSync(path))
154
+ return [];
155
+ let raw;
156
+ try {
157
+ raw = readFileSync(path, 'utf8');
158
+ }
159
+ catch {
160
+ return [];
161
+ }
162
+ const out = [];
163
+ for (const line of raw.split('\n')) {
164
+ const trimmed = line.trim();
165
+ if (trimmed.length === 0)
166
+ continue;
167
+ try {
168
+ const parsed = JSON.parse(trimmed);
169
+ if (!parsed ||
170
+ typeof parsed !== 'object' ||
171
+ typeof parsed.taskId !== 'string' ||
172
+ typeof parsed.ts !== 'number' ||
173
+ !Array.isArray(parsed.files)) {
174
+ continue;
175
+ }
176
+ // β1b r1 defense-in-depth: validate every `files[]` entry too.
177
+ // A malformed file entry (missing `path`, wrong `existed` type)
178
+ // would silently feed garbage into the rollback path; better to
179
+ // drop the whole journal line than to attempt restore against a
180
+ // bogus snapshot.
181
+ const candidate = parsed;
182
+ const allFilesValid = candidate.files.every((f) => f !== null &&
183
+ typeof f === 'object' &&
184
+ typeof f.path === 'string' &&
185
+ f.path.length > 0 &&
186
+ typeof f.existed === 'boolean' &&
187
+ (f.sha256_before === undefined ||
188
+ typeof f.sha256_before === 'string'));
189
+ if (!allFilesValid)
190
+ continue;
191
+ out.push(candidate);
192
+ }
193
+ catch {
194
+ /* drop malformed line */
195
+ }
196
+ }
197
+ return out;
198
+ }
199
+ //# sourceMappingURL=journal.js.map
@@ -1,24 +1,24 @@
1
1
  /**
2
- * Layer A diff applicator — α6.6 diff escalation Phase 1.
2
+ * Layer A diff applicator — diff escalation Phase 1.
3
3
  *
4
4
  * Layer A is the default and lowest-risk edit primitive: a single
5
5
  * `{file, oldString, newString}` block that must match exactly once
6
- * (whitespace-sensitive). It mirrors the Claude Code Edit-tool semantics
6
+ * (whitespace-sensitive). It mirrors the the upstream tool Edit-tool semantics
7
7
  * by design: agents emit a chunk of context guaranteed to be unique
8
8
  * inside the file, and we replace it.
9
9
  *
10
10
  * Three failure modes are surfaced LOUD (never silent partial match):
11
11
  *
12
- * - `no_match` — oldString does not appear in the file at all.
13
- * Model must re-read with broader context.
14
- * - `ambiguous_match` — oldString appears 2+ times. Model must extend
15
- * the context to disambiguate (line above /
16
- * below / function name / etc.). The result
17
- * carries the match count so the dispatcher
18
- * can surface it to the operator.
19
- * - `file_missing` — the target file does not exist on disk.
20
- * Layer A NEVER creates files — that is the
21
- * job of the write tool or Layer C.
12
+ * - `no_match` — oldString does not appear in the file at all.
13
+ * Model must re-read with broader context.
14
+ * - `ambiguous_match` — oldString appears 2+ times. Model must extend
15
+ * the context to disambiguate (line above /
16
+ * below / function name / etc.). The result
17
+ * carries the match count so the dispatcher
18
+ * can surface it to the operator.
19
+ * - `file_missing` — the target file does not exist on disk.
20
+ * Layer A NEVER creates files — that is the
21
+ * job of the write tool or Layer C.
22
22
  *
23
23
  * Cursor's silent partial-match falling back to inline rewrite is the
24
24
  * documented anti-pattern in the spec; we do not replicate it.
@@ -42,9 +42,9 @@ export async function applyLayerA(edit, opts) {
42
42
  // The gate runs three checks in order: workspace path scoping,
43
43
  // protected-basename / suffix / credential-path deny, and a
44
44
  // realpath-based symlink-escape re-check. See `security-gate.ts`
45
- // for the full rationale. Bypassing this is how PR #392's pre-fix
45
+ // for the full rationale. Bypassing this is how PR's pre-fix
46
46
  // version would have written through `+++ NEW ../../../../etc/passwd`
47
- // and `+++ NEW .env` markers (triple-review 2026-05-25 P0).
47
+ // and `+++ NEW .env` markers (triple-review P0).
48
48
  let gateResult;
49
49
  try {
50
50
  gateResult = applySecurityGate(edit.file, { cwd: opts.cwd, toolName: 'layer-a' });
@@ -108,7 +108,7 @@ export async function applyLayerA(edit, opts) {
108
108
  if (edit.oldString === edit.newString) {
109
109
  // Treat no-op edits as a failure so the model corrects itself
110
110
  // rather than silently believing the patch landed. This matches
111
- // the Claude Code Edit-tool contract.
111
+ // the the upstream tool Edit-tool contract.
112
112
  return {
113
113
  ok: false,
114
114
  bytesWritten: 0,
@@ -0,0 +1,198 @@
1
+ /**
2
+ * Layer A.5 — fuzzy SEARCH/REPLACE applicator.
3
+ *
4
+ * Wraps `tryFuzzyLadder` with the same security gate + atomic-write
5
+ * machinery Layer A uses, then escalates to disk. Sits between Layer
6
+ * A strict-exact (which already failed with `no_match`) and Layer C
7
+ * rewrite (which requires the model к re-emit the whole file).
8
+ *
9
+ * Failure semantics:
10
+ *
11
+ * - `no_match` — all three tiers failed. Operator + model
12
+ * treat this identically к Layer A's
13
+ * `no_match`: re-read with broader context.
14
+ * - `ambiguous_match` — a tier found 2+ equally-scored candidates.
15
+ * Surfaces matchCount so the dispatcher can
16
+ * tell the model "extend context".
17
+ * - `file_missing` — target file does not exist; ladder never
18
+ * creates files (parity с Layer A).
19
+ * - `identical_replacement` — search and replace are identical;
20
+ * would no-op; surfaced LOUD.
21
+ *
22
+ * Inspired by Aider editblock_coder.py (Apache-2.0). independent implementation
23
+ * implementation; no Aider source code copied.
24
+ */
25
+ import { existsSync, readFileSync, renameSync, writeFileSync, unlinkSync } from 'node:fs';
26
+ import { applySecurityGate } from './security-gate.js';
27
+ import { tryFuzzyLadder } from './fuzzy-ladder.js';
28
+ export async function applyLayerAFuzzy(edit, opts) {
29
+ // Security gate — identical к Layer A. Path scoping + protected
30
+ // basename + symlink-escape re-check. Bypassing this is how a
31
+ // fuzzy retry could rescue an otherwise-failed `.env` edit.
32
+ let gateResult;
33
+ try {
34
+ gateResult = applySecurityGate(edit.file, { cwd: opts.cwd, toolName: 'layer-a-fuzzy' });
35
+ }
36
+ catch (error) {
37
+ return {
38
+ ok: false,
39
+ bytesWritten: 0,
40
+ reason: 'write_error',
41
+ absPath: edit.file,
42
+ detail: error instanceof Error ? error.message : String(error),
43
+ };
44
+ }
45
+ if (!gateResult.ok) {
46
+ return {
47
+ ok: false,
48
+ bytesWritten: 0,
49
+ reason: gateResult.reason,
50
+ absPath: edit.file,
51
+ detail: gateResult.detail,
52
+ };
53
+ }
54
+ const absPath = gateResult.absPath;
55
+ if (!existsSync(absPath)) {
56
+ return {
57
+ ok: false,
58
+ bytesWritten: 0,
59
+ reason: 'file_missing',
60
+ absPath,
61
+ detail: `file does not exist: ${edit.file}`,
62
+ };
63
+ }
64
+ if (edit.oldString === edit.newString) {
65
+ return {
66
+ ok: false,
67
+ bytesWritten: 0,
68
+ reason: 'identical_replacement',
69
+ absPath,
70
+ detail: 'oldString and newString are identical — no-op',
71
+ };
72
+ }
73
+ const before = readFileSync(absPath, 'utf8');
74
+ const ladder = tryFuzzyLadder(before, edit.oldString, edit.newString, {
75
+ minRatio: opts.minRatio,
76
+ lengthFlex: opts.lengthFlex,
77
+ });
78
+ if (ladder.kind === 'no_match') {
79
+ return {
80
+ ok: false,
81
+ bytesWritten: 0,
82
+ reason: 'no_match',
83
+ absPath,
84
+ matchCount: 0,
85
+ detail: ladder.detail,
86
+ };
87
+ }
88
+ if (ladder.kind === 'ambiguous') {
89
+ return {
90
+ ok: false,
91
+ bytesWritten: 0,
92
+ reason: 'ambiguous_match',
93
+ absPath,
94
+ matchCount: ladder.candidateCount,
95
+ detail: ladder.detail,
96
+ };
97
+ }
98
+ if (opts.dryRun) {
99
+ return {
100
+ ok: true,
101
+ bytesWritten: 0,
102
+ absPath,
103
+ matchCount: 1,
104
+ fuzzyTier: ladder.tier,
105
+ fuzzyScore: ladder.score,
106
+ };
107
+ }
108
+ // Triple-review P1-3 — re-verify the security gate immediately
109
+ // before the destructive write. The upstream gate ran BEFORE the
110
+ // ladder; on a slow ladder (large file, tier-3 LCS scan) a symlink
111
+ // swap could have re-targeted `absPath` between the two checks. The
112
+ // gate is cheap relative to a fuzzy edit, and double-running it
113
+ // closes the TOCTOU window opened by the per-tool reuse of `absPath`.
114
+ // We pin the re-check to the same tool-name so audit logs correlate.
115
+ let gateRecheck;
116
+ try {
117
+ gateRecheck = applySecurityGate(edit.file, { cwd: opts.cwd, toolName: 'layer-a-fuzzy' });
118
+ }
119
+ catch (error) {
120
+ return {
121
+ ok: false,
122
+ bytesWritten: 0,
123
+ reason: 'write_error',
124
+ absPath,
125
+ matchCount: 1,
126
+ detail: error instanceof Error ? error.message : String(error),
127
+ };
128
+ }
129
+ if (!gateRecheck.ok) {
130
+ return {
131
+ ok: false,
132
+ bytesWritten: 0,
133
+ reason: gateRecheck.reason,
134
+ absPath,
135
+ matchCount: 1,
136
+ detail: `pre-write recheck: ${gateRecheck.detail}`,
137
+ };
138
+ }
139
+ if (gateRecheck.absPath !== absPath) {
140
+ // The gate resolved to a DIFFERENT path on re-run — a symlink swap
141
+ // or a parent-dir mutation occurred between the two gate calls.
142
+ // Refuse the write loud rather than landing on the new target.
143
+ return {
144
+ ok: false,
145
+ bytesWritten: 0,
146
+ reason: 'symlink_escape',
147
+ absPath,
148
+ matchCount: 1,
149
+ detail: `pre-write recheck resolved to a different path: ${gateRecheck.absPath} != ${absPath}`,
150
+ };
151
+ }
152
+ try {
153
+ atomicWrite(absPath, ladder.after);
154
+ }
155
+ catch (error) {
156
+ return {
157
+ ok: false,
158
+ bytesWritten: 0,
159
+ reason: 'write_error',
160
+ absPath,
161
+ matchCount: 1,
162
+ detail: error instanceof Error ? error.message : String(error),
163
+ };
164
+ }
165
+ return {
166
+ ok: true,
167
+ bytesWritten: Buffer.byteLength(ladder.after, 'utf8'),
168
+ absPath,
169
+ matchCount: 1,
170
+ fuzzyTier: ladder.tier,
171
+ fuzzyScore: ladder.score,
172
+ };
173
+ }
174
+ /**
175
+ * Atomic write — mirrors `layer-a-apply::atomicWrite`. Duplicated
176
+ * intentionally (not exported from Layer A) because the fuzzy module
177
+ * stays decoupled from Layer A's internals — if Layer A's write path
178
+ * ever diverges (e.g. mode bits for a future executable-bit support)
179
+ * the fuzzy ladder should evolve independently.
180
+ */
181
+ function atomicWrite(absPath, contents) {
182
+ const suffix = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
183
+ const tmp = `${absPath}.pugi-tmp-${suffix}`;
184
+ try {
185
+ writeFileSync(tmp, contents, { encoding: 'utf8', mode: 0o600 });
186
+ renameSync(tmp, absPath);
187
+ }
188
+ catch (error) {
189
+ try {
190
+ unlinkSync(tmp);
191
+ }
192
+ catch {
193
+ // tmp may not exist if writeFileSync itself failed.
194
+ }
195
+ throw error;
196
+ }
197
+ }
198
+ //# sourceMappingURL=layer-a-fuzzy-apply.js.map
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Layer B diff applicator — α6.6 diff escalation Phase 1.
2
+ * Layer B diff applicator — diff escalation Phase 1.
3
3
  *
4
4
  * Layer B is the multi-edit primitive: an ordered list of
5
5
  * `{oldString, newString}` blocks against a single file, applied
@@ -12,14 +12,14 @@
12
12
  *
13
13
  * Atomicity story:
14
14
  *
15
- * 1. We mutate a single in-memory string buffer.
16
- * 2. Every block runs through `applyOneToBuffer` and either advances
17
- * the buffer or returns a structured failure.
18
- * 3. On ANY block failure we discard the buffer and write nothing.
19
- * The on-disk file remains exactly as it was on entry.
20
- * 4. On total success we hand the final buffer to the same atomic
21
- * tmp+rename writer Layer A uses, so a crash mid-write can never
22
- * leave a partially-edited file.
15
+ * 1. We mutate a single in-memory string buffer.
16
+ * 2. Every block runs through `applyOneToBuffer` and either advances
17
+ * the buffer or returns a structured failure.
18
+ * 3. On ANY block failure we discard the buffer and write nothing.
19
+ * The on-disk file remains exactly as it was on entry.
20
+ * 4. On total success we hand the final buffer to the same atomic
21
+ * tmp+rename writer Layer A uses, so a crash mid-write can never
22
+ * leave a partially-edited file.
23
23
  *
24
24
  * Roll-back is therefore in-memory — no need to track per-block undo
25
25
  * because nothing touched the disk until the final write.
@@ -1,15 +1,15 @@
1
1
  /**
2
- * Layer C diff applicator — α6.6 diff escalation Phase 1.
2
+ * Layer C diff applicator — diff escalation Phase 1.
3
3
  *
4
4
  * Layer C is the full-file rewrite escape hatch. The model emits the
5
5
  * complete new file contents plus a sha256 of the file as it was when
6
6
  * the model READ it. We compute the current sha256 of the on-disk
7
7
  * file, compare, and either:
8
8
  *
9
- * - Match — atomically write the new contents.
10
- * - Mismatch — refuse with `base_sha_mismatch`. The model must
11
- * re-read the file and resubmit; the disk version changed between
12
- * read and write (filewatcher event, parallel edit, human typing).
9
+ * - Match — atomically write the new contents.
10
+ * - Mismatch — refuse with `base_sha_mismatch`. The model must
11
+ * re-read the file and resubmit; the disk version changed between
12
+ * read and write (filewatcher event, parallel edit, human typing).
13
13
  *
14
14
  * This is the "I rewrote the entire file" path. It is the heaviest
15
15
  * Layer because the wire payload is the full file body, but it is also
@@ -19,7 +19,7 @@
19
19
  *
20
20
  * The sha256 gate is the load-bearing safety. Without it, Layer C
21
21
  * silently overwrites concurrent edits; with it, the operator
22
- * (and Mira) sees a clean `mismatch` event and can resolve manually.
22
+ * (and Pugi) sees a clean `mismatch` event and can resolve manually.
23
23
  */
24
24
  import { createHash } from 'node:crypto';
25
25
  import { existsSync, readFileSync, renameSync, writeFileSync, unlinkSync } from 'node:fs';