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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (448) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +11 -191
  3. package/bin/pugi +8 -0
  4. package/package.json +15 -71
  5. package/postinstall.mjs +31 -0
  6. package/CHANGELOG.md +0 -132
  7. package/THIRD_PARTY_NOTICES.md +0 -40
  8. package/assets/pugi-mascot.ansi +0 -16
  9. package/assets/pugi-prozr2-mascot.ansi +0 -9
  10. package/bin/run.js +0 -34
  11. package/dist/commands/deploy.js +0 -439
  12. package/dist/commands/flatten.js +0 -191
  13. package/dist/commands/jobs-watch.js +0 -201
  14. package/dist/commands/jobs.js +0 -260
  15. package/dist/commands/retro.js +0 -210
  16. package/dist/commands/smoke.js +0 -133
  17. package/dist/core/agent-progress/cleanup.js +0 -134
  18. package/dist/core/agent-progress/schema.js +0 -144
  19. package/dist/core/agent-progress/writer.js +0 -101
  20. package/dist/core/agents/adaptive-router.js +0 -330
  21. package/dist/core/agents/loader.js +0 -104
  22. package/dist/core/agents/query-decomposer.js +0 -297
  23. package/dist/core/agents/registry.js +0 -69
  24. package/dist/core/approvals/shortcut-resolver.js +0 -98
  25. package/dist/core/artifact-chain/dispatcher.js +0 -148
  26. package/dist/core/artifact-chain/exporter.js +0 -164
  27. package/dist/core/artifact-chain/state.js +0 -243
  28. package/dist/core/artifact-chain/steps.js +0 -169
  29. package/dist/core/ask-user/question.js +0 -92
  30. package/dist/core/audit/audit-trail.js +0 -275
  31. package/dist/core/auth/ensure-authenticated.js +0 -129
  32. package/dist/core/auth/env-provider.js +0 -238
  33. package/dist/core/auto-open-browser.js +0 -128
  34. package/dist/core/auto-update/channels.js +0 -122
  35. package/dist/core/auto-update/checker.js +0 -241
  36. package/dist/core/auto-update/state.js +0 -235
  37. package/dist/core/bare-mode/index.js +0 -107
  38. package/dist/core/bash/redirect.js +0 -281
  39. package/dist/core/bash-classifier.js +0 -1397
  40. package/dist/core/checkpoint/resumer.js +0 -149
  41. package/dist/core/checkpoint/rewinder.js +0 -291
  42. package/dist/core/checkpoints/shadow-git.js +0 -670
  43. package/dist/core/citations/parser.js +0 -109
  44. package/dist/core/classifier/yolo-classifier.js +0 -88
  45. package/dist/core/clipboard.js +0 -70
  46. package/dist/core/codegraph/decision-store.js +0 -248
  47. package/dist/core/codegraph/detect-repo.js +0 -459
  48. package/dist/core/codegraph/install.js +0 -134
  49. package/dist/core/codegraph/offer-hook.js +0 -220
  50. package/dist/core/compact/auto-trigger.js +0 -96
  51. package/dist/core/compact/buffer-rewriter.js +0 -115
  52. package/dist/core/compact/summarizer.js +0 -208
  53. package/dist/core/compact/token-counter.js +0 -108
  54. package/dist/core/consensus/anvil-fanout.js +0 -276
  55. package/dist/core/consensus/diff-capture.js +0 -491
  56. package/dist/core/consensus/rubric.js +0 -233
  57. package/dist/core/context/builder.js +0 -114
  58. package/dist/core/context/compaction-events.js +0 -99
  59. package/dist/core/context/compaction.js +0 -602
  60. package/dist/core/context/index.js +0 -28
  61. package/dist/core/context/invariants.js +0 -250
  62. package/dist/core/context/markdown-loader.js +0 -288
  63. package/dist/core/context/markdown-traverse.js +0 -255
  64. package/dist/core/context/pugiignore.js +0 -316
  65. package/dist/core/context/repo-skeleton.js +0 -533
  66. package/dist/core/context/tool-eviction.js +0 -55
  67. package/dist/core/context/watcher.js +0 -342
  68. package/dist/core/context/working-set.js +0 -165
  69. package/dist/core/coordinator/agent-tools.js +0 -77
  70. package/dist/core/coordinator/agent-toolset.js +0 -65
  71. package/dist/core/coordinator/fsm.js +0 -73
  72. package/dist/core/coordinator/mode-fsm.js +0 -70
  73. package/dist/core/cost/rate-card.js +0 -129
  74. package/dist/core/cost/tracker.js +0 -221
  75. package/dist/core/credentials.js +0 -355
  76. package/dist/core/cron/scheduler.js +0 -138
  77. package/dist/core/denial-tracking/index.js +0 -8
  78. package/dist/core/denial-tracking/state.js +0 -264
  79. package/dist/core/diagnostics/probe-runner.js +0 -93
  80. package/dist/core/diagnostics/probes/api.js +0 -46
  81. package/dist/core/diagnostics/probes/auth.js +0 -93
  82. package/dist/core/diagnostics/probes/bare-mode.js +0 -42
  83. package/dist/core/diagnostics/probes/cli-version.js +0 -127
  84. package/dist/core/diagnostics/probes/config.js +0 -72
  85. package/dist/core/diagnostics/probes/denial-tracking.js +0 -57
  86. package/dist/core/diagnostics/probes/disk.js +0 -81
  87. package/dist/core/diagnostics/probes/engine-live.js +0 -46
  88. package/dist/core/diagnostics/probes/git.js +0 -65
  89. package/dist/core/diagnostics/probes/hooks.js +0 -118
  90. package/dist/core/diagnostics/probes/mcp.js +0 -75
  91. package/dist/core/diagnostics/probes/node.js +0 -59
  92. package/dist/core/diagnostics/probes/pnpm.js +0 -36
  93. package/dist/core/diagnostics/probes/pugi-md.js +0 -89
  94. package/dist/core/diagnostics/probes/sandbox.js +0 -72
  95. package/dist/core/diagnostics/probes/session.js +0 -74
  96. package/dist/core/diagnostics/probes/status-snapshot.js +0 -488
  97. package/dist/core/diagnostics/probes/workspace.js +0 -63
  98. package/dist/core/diagnostics/types.js +0 -70
  99. package/dist/core/dispatch/cache-cleanup.js +0 -197
  100. package/dist/core/dispatch/cache-handoff.js +0 -295
  101. package/dist/core/edits/apply-patch-layer-e.js +0 -189
  102. package/dist/core/edits/dispatch.js +0 -511
  103. package/dist/core/edits/format-detector.js +0 -260
  104. package/dist/core/edits/format-matrix.js +0 -26
  105. package/dist/core/edits/fuzzy-ladder.js +0 -650
  106. package/dist/core/edits/index.js +0 -19
  107. package/dist/core/edits/journal.js +0 -199
  108. package/dist/core/edits/layer-a-apply.js +0 -217
  109. package/dist/core/edits/layer-a-fuzzy-apply.js +0 -198
  110. package/dist/core/edits/layer-b-apply.js +0 -211
  111. package/dist/core/edits/layer-c-apply.js +0 -160
  112. package/dist/core/edits/layer-d-ast.js +0 -572
  113. package/dist/core/edits/marker-parser.js +0 -401
  114. package/dist/core/edits/security-gate.js +0 -223
  115. package/dist/core/edits/verify-hook.js +0 -273
  116. package/dist/core/edits/worktree.js +0 -322
  117. package/dist/core/engine/adapter-runner.js +0 -8
  118. package/dist/core/engine/anvil-client.js +0 -344
  119. package/dist/core/engine/auto-compact.js +0 -179
  120. package/dist/core/engine/budgets.js +0 -195
  121. package/dist/core/engine/context-prefix.js +0 -155
  122. package/dist/core/engine/index.js +0 -12
  123. package/dist/core/engine/intensity.js +0 -163
  124. package/dist/core/engine/intent.js +0 -260
  125. package/dist/core/engine/native-pugi.js +0 -1616
  126. package/dist/core/engine/noop.js +0 -27
  127. package/dist/core/engine/prompts.js +0 -236
  128. package/dist/core/engine/strip-internal-fields.js +0 -124
  129. package/dist/core/engine/tool-bridge.js +0 -2173
  130. package/dist/core/engine/verification-patterns.js +0 -195
  131. package/dist/core/evaluation/golden-dataset.js +0 -293
  132. package/dist/core/feedback/queue.js +0 -177
  133. package/dist/core/feedback/submitter.js +0 -145
  134. package/dist/core/file-cache.js +0 -141
  135. package/dist/core/flatten/flatten-repo.js +0 -439
  136. package/dist/core/format/osc8-link.js +0 -28
  137. package/dist/core/hook-chains.js +0 -392
  138. package/dist/core/hooks/citation-verify-hook.js +0 -138
  139. package/dist/core/hooks/citation-verify.js +0 -112
  140. package/dist/core/hooks/events.js +0 -46
  141. package/dist/core/hooks/index.js +0 -15
  142. package/dist/core/hooks/registry.js +0 -216
  143. package/dist/core/hooks/runner.js +0 -236
  144. package/dist/core/hooks/v2/event-emitter.js +0 -115
  145. package/dist/core/hooks/v2/executor.js +0 -282
  146. package/dist/core/hooks/v2/index.js +0 -25
  147. package/dist/core/hooks/v2/lifecycle.js +0 -104
  148. package/dist/core/hooks/v2/loader.js +0 -216
  149. package/dist/core/hooks/v2/matcher.js +0 -125
  150. package/dist/core/hooks/v2/trust.js +0 -143
  151. package/dist/core/hooks/v2/types.js +0 -86
  152. package/dist/core/hooks/worktree-events.js +0 -158
  153. package/dist/core/hooks.js +0 -415
  154. package/dist/core/image/renderer.js +0 -71
  155. package/dist/core/index-store.js +0 -260
  156. package/dist/core/init/detector.js +0 -582
  157. package/dist/core/init/template-renderer.js +0 -242
  158. package/dist/core/jobs/registry.js +0 -462
  159. package/dist/core/ledger/results-tsv.js +0 -142
  160. package/dist/core/log-discipline/stdout-redirect.js +0 -51
  161. package/dist/core/lsp/cache.js +0 -105
  162. package/dist/core/lsp/client.js +0 -1229
  163. package/dist/core/lsp/language-detect.js +0 -66
  164. package/dist/core/lsp/post-edit-diagnostics.js +0 -171
  165. package/dist/core/lsp/server-detect.js +0 -173
  166. package/dist/core/lsp/symbol-cache.js +0 -162
  167. package/dist/core/lsp/symbol-tools.js +0 -664
  168. package/dist/core/mcp/client.js +0 -385
  169. package/dist/core/mcp/http-server.js +0 -553
  170. package/dist/core/mcp/orchestrator-config.js +0 -192
  171. package/dist/core/mcp/orchestrator-tools.js +0 -806
  172. package/dist/core/mcp/permission.js +0 -190
  173. package/dist/core/mcp/registry.js +0 -193
  174. package/dist/core/mcp/server-tools.js +0 -219
  175. package/dist/core/mcp/server.js +0 -397
  176. package/dist/core/mcp/trust.js +0 -91
  177. package/dist/core/memory/dual-write.js +0 -416
  178. package/dist/core/memory/passive-extract.js +0 -130
  179. package/dist/core/memory/phase1-kinds.js +0 -20
  180. package/dist/core/memory/secret-scanner.js +0 -304
  181. package/dist/core/memory-sync/queue.js +0 -170
  182. package/dist/core/metrics/extract.js +0 -113
  183. package/dist/core/modes/roo-modes.js +0 -68
  184. package/dist/core/onboarding/ensure-initialized.js +0 -133
  185. package/dist/core/onboarding/marker.js +0 -111
  186. package/dist/core/onboarding/telemetry-state.js +0 -108
  187. package/dist/core/output-style/presets.js +0 -176
  188. package/dist/core/output-style/state.js +0 -185
  189. package/dist/core/path-security.js +0 -345
  190. package/dist/core/permission.js +0 -369
  191. package/dist/core/permissions/auto-classifier.js +0 -124
  192. package/dist/core/permissions/bash-parser.js +0 -371
  193. package/dist/core/permissions/circuit-breaker.js +0 -83
  194. package/dist/core/permissions/constrained-edit.js +0 -91
  195. package/dist/core/permissions/gate.js +0 -278
  196. package/dist/core/permissions/index.js +0 -20
  197. package/dist/core/permissions/mode.js +0 -174
  198. package/dist/core/permissions/network-egress.js +0 -137
  199. package/dist/core/permissions/state.js +0 -241
  200. package/dist/core/permissions/tool-class.js +0 -107
  201. package/dist/core/plan-mode/ui-state.js +0 -51
  202. package/dist/core/plans/plan-artifact.js +0 -721
  203. package/dist/core/policy-limits/etag-store.js +0 -122
  204. package/dist/core/prd-check/parser.js +0 -215
  205. package/dist/core/prd-check/reporter.js +0 -127
  206. package/dist/core/prd-check/session-review.js +0 -557
  207. package/dist/core/prd-check/verifiers.js +0 -223
  208. package/dist/core/prompt-cache/client-cache.js +0 -99
  209. package/dist/core/prompts/assembly.js +0 -29
  210. package/dist/core/prompts/registry.js +0 -364
  211. package/dist/core/pugi-gitignore.js +0 -52
  212. package/dist/core/pugi-md/cc-compat-rules.js +0 -735
  213. package/dist/core/pugi-md/context-injector.js +0 -76
  214. package/dist/core/pugi-md/walk-up.js +0 -207
  215. package/dist/core/python/uv-installer.js +0 -270
  216. package/dist/core/python/uv-resolver.js +0 -83
  217. package/dist/core/rate-limit/narrator.js +0 -146
  218. package/dist/core/recipes/cli-types.js +0 -20
  219. package/dist/core/recipes/loader.js +0 -103
  220. package/dist/core/recipes/runner.js +0 -345
  221. package/dist/core/recipes/schema.js +0 -587
  222. package/dist/core/release-notes/parser.js +0 -241
  223. package/dist/core/release-notes/state.js +0 -116
  224. package/dist/core/repl/ask.js +0 -512
  225. package/dist/core/repl/cancellation.js +0 -98
  226. package/dist/core/repl/cap-warning.js +0 -91
  227. package/dist/core/repl/clipboard-read.js +0 -174
  228. package/dist/core/repl/dispatch-fsm.js +0 -220
  229. package/dist/core/repl/engine-bridge.js +0 -303
  230. package/dist/core/repl/history-search.js +0 -175
  231. package/dist/core/repl/history.js +0 -182
  232. package/dist/core/repl/kill-ring.js +0 -138
  233. package/dist/core/repl/model-pricing.js +0 -135
  234. package/dist/core/repl/privacy-banner.js +0 -71
  235. package/dist/core/repl/session.js +0 -4962
  236. package/dist/core/repl/slash-commands.js +0 -747
  237. package/dist/core/repl/store/index.js +0 -12
  238. package/dist/core/repl/store/jsonl-log.js +0 -321
  239. package/dist/core/repl/store/lockfile.js +0 -155
  240. package/dist/core/repl/store/session-store.js +0 -821
  241. package/dist/core/repl/store/types.js +0 -44
  242. package/dist/core/repl/store/uuid-v7.js +0 -68
  243. package/dist/core/repl/tool-route.js +0 -382
  244. package/dist/core/repl/workspace-context.js +0 -206
  245. package/dist/core/repo-map/build.js +0 -125
  246. package/dist/core/repo-map/cache.js +0 -185
  247. package/dist/core/repo-map/extractor.js +0 -254
  248. package/dist/core/repo-map/formatter.js +0 -145
  249. package/dist/core/repo-map/page-rank.js +0 -105
  250. package/dist/core/repo-map/scanner.js +0 -211
  251. package/dist/core/retro/git-collector.js +0 -251
  252. package/dist/core/retro/health-card.js +0 -25
  253. package/dist/core/retro/metrics.js +0 -342
  254. package/dist/core/retro/narrative.js +0 -249
  255. package/dist/core/retro/plane-collector.js +0 -274
  256. package/dist/core/retro/pr-issue-link.js +0 -65
  257. package/dist/core/retro/types.js +0 -16
  258. package/dist/core/retry-budget/budget.js +0 -284
  259. package/dist/core/retry-budget/index.js +0 -5
  260. package/dist/core/retry-budget/retry-cap.js +0 -74
  261. package/dist/core/routing/lead-worker.js +0 -43
  262. package/dist/core/routing/pre-flight-estimator.js +0 -108
  263. package/dist/core/runs/run-tree.js +0 -103
  264. package/dist/core/sandboxing/adapter.js +0 -29
  265. package/dist/core/sandboxing/index.js +0 -49
  266. package/dist/core/sandboxing/none.js +0 -19
  267. package/dist/core/sandboxing/seatbelt.js +0 -183
  268. package/dist/core/security/injection-scanner.js +0 -367
  269. package/dist/core/security/output-filter.js +0 -418
  270. package/dist/core/session/env-file.js +0 -105
  271. package/dist/core/session/section-budgets.js +0 -140
  272. package/dist/core/session.js +0 -377
  273. package/dist/core/settings.js +0 -400
  274. package/dist/core/share/formatter.js +0 -271
  275. package/dist/core/share/redactor.js +0 -221
  276. package/dist/core/share/uploader.js +0 -267
  277. package/dist/core/skills/defaults.js +0 -457
  278. package/dist/core/skills/loader.js +0 -454
  279. package/dist/core/skills/sources.js +0 -480
  280. package/dist/core/skills/trust.js +0 -172
  281. package/dist/core/smoke/headless-driver.js +0 -174
  282. package/dist/core/smoke/orchestrator.js +0 -194
  283. package/dist/core/smoke/runner.js +0 -238
  284. package/dist/core/smoke/scenario-parser.js +0 -316
  285. package/dist/core/statusline.js +0 -99
  286. package/dist/core/subagents/dispatcher-real.js +0 -600
  287. package/dist/core/subagents/dispatcher.js +0 -352
  288. package/dist/core/subagents/index.js +0 -39
  289. package/dist/core/subagents/isolation-matrix.js +0 -213
  290. package/dist/core/subagents/spawn.js +0 -101
  291. package/dist/core/telemetry/emitter.js +0 -229
  292. package/dist/core/telemetry/queue.js +0 -251
  293. package/dist/core/theme/context.js +0 -91
  294. package/dist/core/theme/presets.js +0 -228
  295. package/dist/core/theme/state.js +0 -181
  296. package/dist/core/todos/invariant.js +0 -10
  297. package/dist/core/todos/state.js +0 -177
  298. package/dist/core/tool-schema/compressor.js +0 -89
  299. package/dist/core/transport/version-interceptor.js +0 -166
  300. package/dist/core/trust.js +0 -109
  301. package/dist/core/tui/thinking-block.js +0 -64
  302. package/dist/core/vim/keymap.js +0 -288
  303. package/dist/core/vim/state.js +0 -92
  304. package/dist/core/watch-markers/marker-watcher.js +0 -133
  305. package/dist/core/worktree/include-parser.js +0 -249
  306. package/dist/core/worktree-manager/cleanup.js +0 -123
  307. package/dist/core/worktree-manager/manager.js +0 -303
  308. package/dist/index.js +0 -44
  309. package/dist/runtime/bootstrap.js +0 -190
  310. package/dist/runtime/cli.js +0 -8121
  311. package/dist/runtime/commands/agents.js +0 -385
  312. package/dist/runtime/commands/budget.js +0 -192
  313. package/dist/runtime/commands/cancel.js +0 -231
  314. package/dist/runtime/commands/chain.js +0 -489
  315. package/dist/runtime/commands/codegraph-status.js +0 -227
  316. package/dist/runtime/commands/compact.js +0 -297
  317. package/dist/runtime/commands/config.js +0 -595
  318. package/dist/runtime/commands/cost.js +0 -199
  319. package/dist/runtime/commands/delegate.js +0 -312
  320. package/dist/runtime/commands/dispatch.js +0 -126
  321. package/dist/runtime/commands/doctor.js +0 -579
  322. package/dist/runtime/commands/feedback.js +0 -184
  323. package/dist/runtime/commands/hooks.js +0 -187
  324. package/dist/runtime/commands/init.js +0 -254
  325. package/dist/runtime/commands/lsp.js +0 -368
  326. package/dist/runtime/commands/mcp.js +0 -935
  327. package/dist/runtime/commands/memory.js +0 -582
  328. package/dist/runtime/commands/model.js +0 -237
  329. package/dist/runtime/commands/onboarding.js +0 -275
  330. package/dist/runtime/commands/patch.js +0 -128
  331. package/dist/runtime/commands/permissions.js +0 -112
  332. package/dist/runtime/commands/plan.js +0 -143
  333. package/dist/runtime/commands/prd-check.js +0 -285
  334. package/dist/runtime/commands/privacy.js +0 -107
  335. package/dist/runtime/commands/recipe.js +0 -325
  336. package/dist/runtime/commands/redo-blob-store.js +0 -92
  337. package/dist/runtime/commands/redo.js +0 -361
  338. package/dist/runtime/commands/release-notes.js +0 -229
  339. package/dist/runtime/commands/repo-map.js +0 -95
  340. package/dist/runtime/commands/report.js +0 -299
  341. package/dist/runtime/commands/resume.js +0 -118
  342. package/dist/runtime/commands/review-consensus.js +0 -414
  343. package/dist/runtime/commands/rewind.js +0 -333
  344. package/dist/runtime/commands/roster.js +0 -117
  345. package/dist/runtime/commands/sessions.js +0 -163
  346. package/dist/runtime/commands/share.js +0 -316
  347. package/dist/runtime/commands/skills.js +0 -401
  348. package/dist/runtime/commands/status.js +0 -186
  349. package/dist/runtime/commands/stickers.js +0 -82
  350. package/dist/runtime/commands/style.js +0 -194
  351. package/dist/runtime/commands/theme.js +0 -196
  352. package/dist/runtime/commands/undo.js +0 -361
  353. package/dist/runtime/commands/update.js +0 -289
  354. package/dist/runtime/commands/vim.js +0 -140
  355. package/dist/runtime/commands/worktree.js +0 -177
  356. package/dist/runtime/commands/worktrees.js +0 -155
  357. package/dist/runtime/deprecation-warning.js +0 -69
  358. package/dist/runtime/engine-exit-code.js +0 -50
  359. package/dist/runtime/headless-repl.js +0 -195
  360. package/dist/runtime/headless.js +0 -548
  361. package/dist/runtime/load-hooks-or-exit.js +0 -71
  362. package/dist/runtime/plan-decompose.js +0 -531
  363. package/dist/runtime/sigint-guard.js +0 -272
  364. package/dist/runtime/stream-renderer.js +0 -195
  365. package/dist/runtime/update-check.js +0 -294
  366. package/dist/runtime/version.js +0 -65
  367. package/dist/runtime/worktree-bootstrap.js +0 -579
  368. package/dist/skills/bundled/batch.js +0 -617
  369. package/dist/skills/bundled/index.js +0 -45
  370. package/dist/skills/bundled/loop.js +0 -358
  371. package/dist/skills/bundled/remember.js +0 -383
  372. package/dist/skills/bundled/simplify.js +0 -289
  373. package/dist/skills/bundled/skillify.js +0 -373
  374. package/dist/skills/bundled/stuck.js +0 -558
  375. package/dist/skills/bundled/verify.js +0 -439
  376. package/dist/testing/vcr.js +0 -486
  377. package/dist/tools/agent-tool.js +0 -229
  378. package/dist/tools/apply-patch.js +0 -556
  379. package/dist/tools/ask-user-question.js +0 -337
  380. package/dist/tools/ask-user.js +0 -115
  381. package/dist/tools/bash.js +0 -1238
  382. package/dist/tools/brief.js +0 -224
  383. package/dist/tools/cron.js +0 -433
  384. package/dist/tools/enter-worktree.js +0 -250
  385. package/dist/tools/exit-worktree.js +0 -147
  386. package/dist/tools/file-tools.js +0 -553
  387. package/dist/tools/http-request.js +0 -336
  388. package/dist/tools/lsp-tools.js +0 -565
  389. package/dist/tools/mcp-tool.js +0 -260
  390. package/dist/tools/multi-edit.js +0 -361
  391. package/dist/tools/powershell.js +0 -268
  392. package/dist/tools/registry.js +0 -166
  393. package/dist/tools/server-tools.js +0 -892
  394. package/dist/tools/skill-tool.js +0 -96
  395. package/dist/tools/sleep.js +0 -99
  396. package/dist/tools/synthetic-output.js +0 -133
  397. package/dist/tools/tasks.js +0 -208
  398. package/dist/tools/todo-write.js +0 -184
  399. package/dist/tools/verify-plan-execution.js +0 -295
  400. package/dist/tools/web-fetch-injection-scanner.js +0 -207
  401. package/dist/tools/web-fetch.js +0 -720
  402. package/dist/tools/web-search.js +0 -458
  403. package/dist/tui/agent-progress-card.js +0 -111
  404. package/dist/tui/agent-tree-pane.js +0 -9
  405. package/dist/tui/agent-tree.js +0 -87
  406. package/dist/tui/ask-cli.js +0 -52
  407. package/dist/tui/ask-modal.js +0 -211
  408. package/dist/tui/ask-user-question-chips.js +0 -315
  409. package/dist/tui/ask-user-question-prompt.js +0 -203
  410. package/dist/tui/compact-banner.js +0 -81
  411. package/dist/tui/conversation-pane.js +0 -164
  412. package/dist/tui/cost-table.js +0 -111
  413. package/dist/tui/device-flow.js +0 -142
  414. package/dist/tui/doctor-table.js +0 -46
  415. package/dist/tui/feedback-prompt.js +0 -156
  416. package/dist/tui/input-box.js +0 -732
  417. package/dist/tui/login-picker.js +0 -69
  418. package/dist/tui/markdown-render.js +0 -266
  419. package/dist/tui/multi-file-diff-approval.js +0 -375
  420. package/dist/tui/onboarding-wizard.js +0 -240
  421. package/dist/tui/permissions-picker.js +0 -86
  422. package/dist/tui/render.js +0 -160
  423. package/dist/tui/repl-render.js +0 -770
  424. package/dist/tui/repl-splash-art.js +0 -64
  425. package/dist/tui/repl-splash-mascot.js +0 -154
  426. package/dist/tui/repl-splash.js +0 -117
  427. package/dist/tui/repl.js +0 -378
  428. package/dist/tui/slash-palette.js +0 -106
  429. package/dist/tui/splash-data.js +0 -61
  430. package/dist/tui/splash.js +0 -31
  431. package/dist/tui/status-bar.js +0 -209
  432. package/dist/tui/status-table.js +0 -7
  433. package/dist/tui/stickers-art.js +0 -136
  434. package/dist/tui/style-table.js +0 -28
  435. package/dist/tui/theme-table.js +0 -29
  436. package/dist/tui/thinking-spinner.js +0 -123
  437. package/dist/tui/tool-stream-pane.js +0 -140
  438. package/dist/tui/update-banner.js +0 -33
  439. package/dist/tui/vim-input.js +0 -267
  440. package/dist/tui/welcome-banner.js +0 -107
  441. package/dist/tui/welcome-data.js +0 -293
  442. package/dist/tui/workspace-context.js +0 -105
  443. package/docs/examples/codegraph.mcp.json +0 -10
  444. package/test/scenarios/codegen-create-file.scenario.txt +0 -13
  445. package/test/scenarios/compact-force.scenario.txt +0 -12
  446. package/test/scenarios/identity.scenario.txt +0 -11
  447. package/test/scenarios/persona-handoff.scenario.txt +0 -12
  448. package/test/scenarios/walkback.scenario.txt +0 -12
@@ -1,670 +0,0 @@
1
- /**
2
- * Per-task shadow git repo — file-state checkpoint surface.
3
- *
4
- * Inspired by Cline `CheckpointTracker.ts` (Apache-2.0).
5
- * independent implementation TypeScript implementation following Pugi conventions.
6
- *
7
- * Goal: every Pugi-orchestrated file mutation lands in a per-task
8
- * shadow git history kept entirely separate from the user's real
9
- * `.git/`. The operator can list / diff / restore checkpoints with
10
- * zero pollution of their own commit graph.
11
- *
12
- * Design choices:
13
- *
14
- * - Shadow `.git` lives at `<cwd>/.pugi/checkpoints/<task-id>/.git`
15
- * (a regular non-bare repo whose `objects/` + `refs/` stay there).
16
- * The "shadow repo" is the dir containing `.git`; that parent dir
17
- * itself is empty — we drive git via `--git-dir=<shadow>/.git
18
- * --work-tree=<cwd>` so the shadow tracks the user's working tree
19
- * in-place WITHOUT a separate clone of the file contents.
20
- *
21
- * - We never `git init` inside `<cwd>`. The shadow's `.git` dir is
22
- * under `.pugi/checkpoints/<task-id>/` so the user's real repo
23
- * (if any) is untouched. Auto-ignore `.pugi/` so the shadow does
24
- * not recurse into its own metadata.
25
- *
26
- * - All git invocations go through `spawnSync` with explicit argv
27
- * (no shell, no string interpolation). The shadow `.git` dir is
28
- * chmod 700 so file-content snapshots do not leak to other Unix
29
- * accounts on shared boxes (memory rule: "shadow `.git` MUST be
30
- * chmod 700").
31
- *
32
- * - Operations are atomic-or-no-op: `initShadowRepo` writes a tmp
33
- * `.gitignore` and only flips the canonical `.gitignore` once the
34
- * `git init` + initial commit complete, so a crash between steps
35
- * leaves a clean "not initialised yet" state on retry.
36
- *
37
- * - The commit author / committer is pinned to
38
- * `Pugi Shadow <shadow@pugi.local>` and `GIT_*_DATE` env vars are
39
- * set per-commit so the shadow log is reproducible regardless of
40
- * the host's git config.
41
- *
42
- * - Errors that originate from a missing `git` binary surface as
43
- * `ShadowGitUnavailableError`; the dispatcher's hook treats that
44
- * as best-effort (logs but does not block the edit).
45
- */
46
- import { spawnSync } from 'node:child_process';
47
- import { chmodSync, existsSync, mkdirSync, readdirSync, rmSync, statSync, writeFileSync, } from 'node:fs';
48
- import { createInterface } from 'node:readline';
49
- import { join, relative, resolve } from 'node:path';
50
- /**
51
- * Reserved task-id used for ad-hoc / "no active task" checkpoints when
52
- * the dispatcher cannot supply a real task id. Kept as an exported
53
- * constant so callers in tests and integration glue agree on the
54
- * fallback bucket.
55
- */
56
- export const DEFAULT_TASK_ID = 'default';
57
- /**
58
- * Default git author for shadow commits. Kept deterministic so the
59
- * shadow log is stable across hosts and reproducible in tests.
60
- */
61
- const SHADOW_AUTHOR_NAME = 'Pugi Shadow';
62
- const SHADOW_AUTHOR_EMAIL = 'shadow@pugi.local';
63
- /**
64
- * Default ignore template applied to the shadow on init. Excludes the
65
- * `.pugi/` metadata directory (so shadow operations cannot recurse
66
- * into their own state) along with the heavy build-output directories
67
- * that would otherwise turn every dispatch commit into a multi-MB
68
- * blob churn.
69
- *
70
- * Lives as a constant rather than a fixture file so the contents are
71
- * inspectable from the tests without IO.
72
- */
73
- export const SHADOW_GITIGNORE_TEMPLATE = [
74
- '# Pugi shadow git ignore — managed by core/checkpoints/shadow-git.ts',
75
- '# Do not edit by hand; reset via `pugi checkpoint reset` once that ships.',
76
- '.pugi/',
77
- 'node_modules/',
78
- '.next/',
79
- 'dist/',
80
- 'build/',
81
- 'coverage/',
82
- '.turbo/',
83
- '.cache/',
84
- '*.log',
85
- '',
86
- ].join('\n');
87
- /**
88
- * Thrown when `git` is missing or the spawn fails in a way that cannot
89
- * be recovered. Best-effort callers (the dispatch hook) catch + log
90
- * + continue; explicit callers (REPL slash commands) surface the
91
- * message to the operator.
92
- */
93
- export class ShadowGitUnavailableError extends Error {
94
- constructor(message) {
95
- super(message);
96
- this.name = 'ShadowGitUnavailableError';
97
- }
98
- }
99
- /** Resolve the on-disk shadow root for a (cwd, taskId) pair. */
100
- export function shadowRoot(cwd, taskId) {
101
- return resolve(cwd, '.pugi', 'checkpoints', sanitizeTaskId(taskId));
102
- }
103
- /**
104
- * Resolve the shadow `.git` directory. Convention: regular (non-bare)
105
- * repo so `git -C <root>` would Just Work if the operator ever needed
106
- * to introspect it directly.
107
- */
108
- export function shadowGitDir(cwd, taskId) {
109
- return join(shadowRoot(cwd, taskId), '.git');
110
- }
111
- /**
112
- * Idempotent init. When the shadow `.git` already exists this is a
113
- * no-op (returns `{ created: false }`). On first call:
114
- *
115
- * 1. mkdir parent dir tree.
116
- * 2. `git init` the shadow `.git` (no working tree at the shadow
117
- * root; we drive every later command with `--work-tree=<cwd>`).
118
- * 3. Write the canonical `.gitignore` to the shadow root + chmod
119
- * the shadow `.git` to 0o700 to keep snapshot content owner-only.
120
- * 4. Stage `.gitignore` + commit an initial "shadow init" so later
121
- * `git log` always has at least one entry to compare against.
122
- *
123
- * Throws `ShadowGitUnavailableError` when `git` is missing.
124
- */
125
- export function initShadowRepo(cwd, taskId) {
126
- const root = shadowRoot(cwd, taskId);
127
- const gitDir = shadowGitDir(cwd, taskId);
128
- if (existsSync(gitDir)) {
129
- return { created: false, root, gitDir };
130
- }
131
- mkdirSync(root, { recursive: true });
132
- // `git init <root>` creates `<root>/.git/` as the git dir. We can't
133
- // pass `gitDir` directly because that would nest as
134
- // `<root>/.git/.git/`. Init from `root` then verify `gitDir` exists.
135
- const initRes = spawnGit(['init', '--quiet', root], { cwd: root });
136
- if (initRes.error || initRes.status !== 0) {
137
- throw new ShadowGitUnavailableError(`git init failed for ${root}: ${describeSpawn(initRes)}`);
138
- }
139
- try {
140
- chmodSync(gitDir, 0o700);
141
- }
142
- catch {
143
- // Non-fatal on platforms (Windows / restrictive sandboxes) where
144
- // chmod does not have effect. The bare path is still under the
145
- // user's $HOME so the practical exposure is unchanged.
146
- }
147
- // Write the exclude template at the shadow root. This file is
148
- // referenced via `core.excludesFile` on every shadow command — it
149
- // is NOT committed into the shadow's tree, so it never appears as
150
- // a phantom delete when `--work-tree` points at the user's `cwd`.
151
- writeFileSync(join(root, '.gitignore'), SHADOW_GITIGNORE_TEMPLATE);
152
- // Empty initial commit so `git log` always has at least one anchor.
153
- // We point `--work-tree` at the user's `cwd` even for the init —
154
- // `--allow-empty` + nothing staged means no files are touched.
155
- const commitInit = spawnGit([
156
- '--git-dir',
157
- gitDir,
158
- '--work-tree',
159
- cwd,
160
- 'commit',
161
- '--quiet',
162
- '--allow-empty',
163
- '-m',
164
- 'pugi shadow init',
165
- ], { cwd, env: shadowEnv() });
166
- if (commitInit.status !== 0) {
167
- throw new ShadowGitUnavailableError(`shadow init commit failed: ${describeSpawn(commitInit)}`);
168
- }
169
- return { created: true, root, gitDir };
170
- }
171
- /**
172
- * Stage everything in `cwd` (respecting `.gitignore`) and commit a
173
- * snapshot to the shadow. Returns the new commit SHA, or `null` when
174
- * the snapshot was a no-op (working tree clean vs. last shadow head).
175
- *
176
- * The dispatcher hook calls this AFTER every successful edit. We
177
- * deliberately tolerate a no-op (no SHA) so a tool that ran but did
178
- * not actually touch a tracked file does not litter the shadow log
179
- * with empty commits.
180
- *
181
- * `message` is the operator-facing commit body. Convention:
182
- *
183
- * pugi <task-id> step <n>: <tool-name> <relative-path>
184
- *
185
- * The dispatcher composes this; the shadow module stores it verbatim.
186
- */
187
- export function commitCheckpoint(cwd, taskId, message) {
188
- const gitDir = shadowGitDir(cwd, taskId);
189
- if (!existsSync(gitDir)) {
190
- initShadowRepo(cwd, taskId);
191
- }
192
- // Apply our exclude template through `core.excludesFile` so the
193
- // shadow does not pull in `.pugi/`, `node_modules/`, `dist/`, etc.
194
- // Cannot just place a `.gitignore` in the working tree — that would
195
- // pollute the user's repo. The exclude file lives under the shadow
196
- // root and is referenced per-command.
197
- const excludeFile = join(shadowRoot(cwd, taskId), '.gitignore');
198
- const add = spawnGit([
199
- '-c',
200
- `core.excludesFile=${excludeFile}`,
201
- '--git-dir',
202
- gitDir,
203
- '--work-tree',
204
- cwd,
205
- 'add',
206
- '-A',
207
- ], { cwd, env: shadowEnv() });
208
- if (add.status !== 0) {
209
- throw new ShadowGitUnavailableError(`shadow add failed: ${describeSpawn(add)}`);
210
- }
211
- // Check whether there is anything staged. `git diff --cached
212
- // --quiet` exits 0 iff the index matches HEAD; non-zero (specifically
213
- // 1) means we have content to commit. Any other exit is an error.
214
- const diff = spawnGit(['--git-dir', gitDir, '--work-tree', cwd, 'diff', '--cached', '--quiet'], { cwd, env: shadowEnv() });
215
- if (diff.status === 0) {
216
- return null;
217
- }
218
- if (diff.status !== 1) {
219
- throw new ShadowGitUnavailableError(`shadow diff probe failed: ${describeSpawn(diff)}`);
220
- }
221
- const commit = spawnGit([
222
- '--git-dir',
223
- gitDir,
224
- '--work-tree',
225
- cwd,
226
- 'commit',
227
- '--quiet',
228
- '-m',
229
- message,
230
- ], { cwd, env: shadowEnv() });
231
- if (commit.status !== 0) {
232
- throw new ShadowGitUnavailableError(`shadow commit failed: ${describeSpawn(commit)}`);
233
- }
234
- const sha = spawnGit(['--git-dir', gitDir, '--work-tree', cwd, 'rev-parse', 'HEAD'], { cwd, env: shadowEnv() });
235
- if (sha.status !== 0) {
236
- throw new ShadowGitUnavailableError(`shadow rev-parse HEAD failed: ${describeSpawn(sha)}`);
237
- }
238
- return sha.stdout.trim();
239
- }
240
- /**
241
- * List recent checkpoints for `taskId`, newest-first. Returns an
242
- * empty array when the shadow does not exist (no edits committed
243
- * yet) — the operator hint ("no checkpoints recorded") lives in the
244
- * REPL renderer, not here.
245
- *
246
- * `limit` is clamped to [1, 200].
247
- */
248
- export function listCheckpoints(cwd, taskId, limit = 20) {
249
- const gitDir = shadowGitDir(cwd, taskId);
250
- if (!existsSync(gitDir))
251
- return [];
252
- const clamped = Math.max(1, Math.min(200, Math.floor(limit)));
253
- const log = spawnGit([
254
- '--git-dir',
255
- gitDir,
256
- '--work-tree',
257
- cwd,
258
- 'log',
259
- `--max-count=${clamped}`,
260
- // Tab-delimited so we can split on a char that does not appear
261
- // in commit messages (we sanitise tabs out when composing the
262
- // dispatcher message).
263
- '--pretty=format:%H%x09%ct%x09%s',
264
- ], { cwd, env: shadowEnv() });
265
- if (log.status !== 0) {
266
- // The shadow exists but `git log` failed (likely corrupt). Return
267
- // empty — the REPL renderer surfaces "no checkpoints" rather than
268
- // tearing through error UX. Callers that need to know about the
269
- // corruption should use `verifyShadow` (future).
270
- return [];
271
- }
272
- const out = [];
273
- for (const line of log.stdout.split('\n')) {
274
- if (line.length === 0)
275
- continue;
276
- const [sha, ctRaw, ...rest] = line.split('\t');
277
- if (!sha || !ctRaw)
278
- continue;
279
- const ct = Number.parseInt(ctRaw, 10);
280
- if (!Number.isFinite(ct))
281
- continue;
282
- out.push({
283
- sha,
284
- shortSha: sha.slice(0, 7),
285
- message: rest.join('\t'),
286
- committedAt: ct * 1000,
287
- });
288
- }
289
- return out;
290
- }
291
- /**
292
- * Thrown when the operator declines the confirmation prompt OR when
293
- * the prompt times out. Distinct from `ShadowGitUnavailableError` so
294
- * callers can differentiate "operator-said-no" from "git-broke".
295
- */
296
- export class CheckpointRestoreCancelledError extends Error {
297
- constructor(message) {
298
- super(message);
299
- this.name = 'CheckpointRestoreCancelledError';
300
- }
301
- }
302
- const DEFAULT_RESTORE_PROMPT_TIMEOUT_MS = 30_000;
303
- /**
304
- * Restore the working tree to the snapshot at `sha`. Destructive:
305
- * runs `git checkout --` against every tracked path, overwriting
306
- * local changes.
307
- *
308
- * Triple-review P1-3 — before this fix `restoreCheckpoint` ran
309
- * without any confirmation. The slash-handler enforced UX out-of-band,
310
- * but a direct API caller (a future `pugi-mcp` tool, a test, an agent
311
- * toolcall) would happily overwrite the work tree. The gate here keeps
312
- * the function safe-by-default while still letting callers opt out
313
- * with `--yes` or by piping stdin (non-TTY auto-yes — matches every
314
- * Unix `rm -i` convention).
315
- *
316
- * Implementation note: we use `git checkout <sha> -- :/` (path pattern
317
- * matching the whole work tree) rather than `reset --hard` because
318
- * `reset --hard` would move the shadow's HEAD, breaking the
319
- * "checkpoint <sha>" anchor for any later `restoreCheckpoint` call.
320
- * Operators expect к be able to restore checkpoint A, then checkpoint
321
- * B, then checkpoint A again — `checkout` preserves that property
322
- * because HEAD stays put.
323
- */
324
- export async function restoreCheckpoint(cwd, taskId, sha, options = {}) {
325
- const gitDir = shadowGitDir(cwd, taskId);
326
- if (!existsSync(gitDir)) {
327
- throw new ShadowGitUnavailableError(`no shadow repo for task ${sanitizeTaskId(taskId)}`);
328
- }
329
- if (!/^[0-9a-f]{4,40}$/i.test(sha)) {
330
- throw new ShadowGitUnavailableError(`refuse to restore: '${sha}' does not look like a git SHA`);
331
- }
332
- // Confirmation gate. `--yes` skips. Non-TTY stdin auto-confirms
333
- // (scripted contexts must not block indefinitely on a prompt). When
334
- // stdin is a TTY we ask y/N with a 30s timeout — default-N to
335
- // "did nothing destructive" rather than "operator presumed agreement".
336
- if (!options.yes && process.stdin.isTTY) {
337
- const timeoutMs = options.timeoutMs ?? DEFAULT_RESTORE_PROMPT_TIMEOUT_MS;
338
- const question = `restore shadow checkpoint ${sha.slice(0, 7)}? destroys local changes in ${cwd}. [y/N]: `;
339
- const reply = options.prompt
340
- ? await options.prompt(question, timeoutMs)
341
- : await defaultPrompt(question, timeoutMs);
342
- if (!/^y(es)?$/i.test(reply.trim())) {
343
- throw new CheckpointRestoreCancelledError(`restore cancelled by operator (answered '${reply.trim()}')`);
344
- }
345
- }
346
- // Verify the SHA resolves to a real commit before we touch the work
347
- // tree. `git cat-file -t <sha>` returns "commit" for a commit;
348
- // anything else (or non-zero exit) we refuse.
349
- const probe = spawnGit(['--git-dir', gitDir, '--work-tree', cwd, 'cat-file', '-t', sha], { cwd, env: shadowEnv() });
350
- if (probe.status !== 0 || probe.stdout.trim() !== 'commit') {
351
- throw new ShadowGitUnavailableError(`refuse to restore: ${sha} is not a commit in shadow repo`);
352
- }
353
- const checkout = spawnGit([
354
- '--git-dir',
355
- gitDir,
356
- '--work-tree',
357
- cwd,
358
- 'checkout',
359
- sha,
360
- '--',
361
- ':/',
362
- ], { cwd, env: shadowEnv() });
363
- if (checkout.status !== 0) {
364
- throw new ShadowGitUnavailableError(`shadow restore failed: ${describeSpawn(checkout)}`);
365
- }
366
- }
367
- /**
368
- * Return the diff between `sha` and the current working tree (HEAD
369
- * of shadow). Format is unified diff text; the REPL renders it
370
- * verbatim with monospace + colour gates. Empty string when there
371
- * are no differences.
372
- */
373
- export function diffCheckpoint(cwd, taskId, sha) {
374
- const gitDir = shadowGitDir(cwd, taskId);
375
- if (!existsSync(gitDir)) {
376
- throw new ShadowGitUnavailableError(`no shadow repo for task ${sanitizeTaskId(taskId)}`);
377
- }
378
- if (!/^[0-9a-f]{4,40}$/i.test(sha)) {
379
- throw new ShadowGitUnavailableError(`refuse to diff: '${sha}' does not look like a git SHA`);
380
- }
381
- const diff = spawnGit([
382
- '--git-dir',
383
- gitDir,
384
- '--work-tree',
385
- cwd,
386
- 'diff',
387
- '--no-color',
388
- sha,
389
- '--',
390
- ], { cwd, env: shadowEnv() });
391
- if (diff.status !== 0 && diff.status !== 1) {
392
- // Git diff exit 0 = no diff, 1 = diff present; anything else is an
393
- // error.
394
- throw new ShadowGitUnavailableError(`shadow diff failed: ${describeSpawn(diff)}`);
395
- }
396
- return diff.stdout;
397
- }
398
- /**
399
- * Garbage-collect old shadow repos under `<cwd>/.pugi/checkpoints/`.
400
- * Removes a directory iff:
401
- *
402
- * - Its mtime is older than `maxAgeDays` days, OR
403
- * - The aggregate disk usage of the checkpoints root exceeds
404
- * `maxSizeMB` (oldest entries removed first until back under).
405
- *
406
- * Returns a `PruneReport`. Best-effort: per-entry failures are
407
- * swallowed and excluded from `removedIds`, but the scan continues.
408
- */
409
- export function pruneOldShadows(cwd, maxAgeDays = 7, maxSizeMB = 100) {
410
- const root = resolve(cwd, '.pugi', 'checkpoints');
411
- if (!existsSync(root)) {
412
- return { scanned: 0, removed: 0, removedIds: [], totalBytesFreed: 0 };
413
- }
414
- const rawEntries = readdirSync(root, { withFileTypes: true });
415
- const entries = rawEntries
416
- .filter((d) => d.isDirectory())
417
- .map((d) => {
418
- const name = d.name;
419
- const p = join(root, name);
420
- const s = safeStat(p);
421
- const size = computeDirSize(p);
422
- return {
423
- id: name,
424
- path: p,
425
- mtimeMs: s ? Number(s.mtimeMs) : 0,
426
- sizeBytes: size,
427
- };
428
- });
429
- // Triple-review P1-2 — explicit mtime ascending sort so the
430
- // oldest entries land at index 0 and both passes (age + size) prune
431
- // the right ones. The sort key is `statSync(p).mtimeMs` (via
432
- // `safeStat` for the directory itself); stat-failed entries get
433
- // mtime=0 and naturally sort first (preferred — broken checkpoints
434
- // should be cleaned up before well-formed ones). Tie-breaker by id
435
- // makes the sweep order deterministic for tests. We re-sort
436
- // defensively before the size pass to keep the invariant if a
437
- // future refactor reorders the array between passes.
438
- const mtimeSort = (a, b) => a.mtimeMs - b.mtimeMs || a.id.localeCompare(b.id);
439
- entries.sort(mtimeSort);
440
- const ageCutoffMs = Date.now() - maxAgeDays * 24 * 60 * 60 * 1000;
441
- const removed = [];
442
- let freed = 0;
443
- const remove = (idx) => {
444
- const entry = entries[idx];
445
- if (!entry)
446
- return;
447
- try {
448
- rmSync(entry.path, { recursive: true, force: true });
449
- removed.push(entry.id);
450
- freed += entry.sizeBytes;
451
- entries.splice(idx, 1);
452
- }
453
- catch {
454
- // Skip — operator can clean up by hand.
455
- }
456
- };
457
- // Pass 1 — age. Walk forwards-with-splice so indices stay valid.
458
- let i = 0;
459
- while (i < entries.length) {
460
- const entry = entries[i];
461
- if (entry && entry.mtimeMs > 0 && entry.mtimeMs < ageCutoffMs) {
462
- remove(i);
463
- }
464
- else {
465
- i += 1;
466
- }
467
- }
468
- // Pass 2 — size cap. Defensive re-sort: pass 1 used splice к remove
469
- // entries which preserves relative order, but a future refactor
470
- // (parallel-scan, async stat) might reorder. Re-sorting before the
471
- // size pass guarantees `entries[0]` remains the oldest survivor.
472
- entries.sort(mtimeSort);
473
- const maxBytes = maxSizeMB * 1024 * 1024;
474
- let total = entries.reduce((sum, e) => sum + e.sizeBytes, 0);
475
- while (total > maxBytes && entries.length > 0) {
476
- const before = entries[0]?.sizeBytes ?? 0;
477
- remove(0);
478
- total -= before;
479
- }
480
- return {
481
- scanned: removed.length + entries.length,
482
- removed: removed.length,
483
- removedIds: removed,
484
- totalBytesFreed: freed,
485
- };
486
- }
487
- /**
488
- * Compute on-disk usage for a directory tree. Pure utility — exposed
489
- * so `pruneOldShadows` can be tested deterministically and so future
490
- * `pugi doctor` surfaces can read the same number.
491
- */
492
- export function computeDirSize(dir) {
493
- if (!existsSync(dir))
494
- return 0;
495
- let total = 0;
496
- const stack = [dir];
497
- while (stack.length > 0) {
498
- const next = stack.pop();
499
- if (!next)
500
- break;
501
- let kids = [];
502
- try {
503
- kids = readdirSync(next, { withFileTypes: true });
504
- }
505
- catch {
506
- continue;
507
- }
508
- for (const k of kids) {
509
- const name = k.name;
510
- const p = join(next, name);
511
- if (k.isDirectory()) {
512
- stack.push(p);
513
- continue;
514
- }
515
- const s = safeStat(p);
516
- if (s)
517
- total += Number(s.size);
518
- }
519
- }
520
- return total;
521
- }
522
- /**
523
- * Sanitise an operator-supplied taskId so it cannot escape the
524
- * checkpoints directory tree. Reject anything containing path
525
- * separators or `..` segments; fall back to `DEFAULT_TASK_ID`.
526
- */
527
- export function sanitizeTaskId(taskId) {
528
- const trimmed = (taskId ?? '').trim();
529
- if (trimmed.length === 0)
530
- return DEFAULT_TASK_ID;
531
- if (trimmed.includes('/') || trimmed.includes('\\') || trimmed.includes('..')) {
532
- return DEFAULT_TASK_ID;
533
- }
534
- // Whitelist: alphanumerics, dot, dash, underscore.
535
- if (!/^[A-Za-z0-9._-]+$/.test(trimmed)) {
536
- return DEFAULT_TASK_ID;
537
- }
538
- return trimmed;
539
- }
540
- /**
541
- * Compose the canonical commit message for a dispatcher hook step.
542
- * Exposed as a helper so the dispatcher and tests agree on the
543
- * exact format the slash list renderer can parse back.
544
- *
545
- * Format: `pugi <task-id> step <n>: <tool-name> <relative-path>`
546
- *
547
- * `relativePath` is normalised against `cwd` so absolute paths from
548
- * the applicator turn into workspace-relative entries.
549
- */
550
- export function formatCheckpointMessage(input) {
551
- const safeTask = sanitizeTaskId(input.taskId);
552
- const rel = relative(input.cwd, input.absPath) || input.absPath;
553
- // Sanitise control chars (tabs / newlines) so the `git log` parser
554
- // can rely on tab-delimited output (see `listCheckpoints`).
555
- const safeTool = input.toolName.replace(/[\s\t\r\n]+/g, '_');
556
- const safeRel = rel.replace(/[\t\r\n]+/g, ' ');
557
- return `pugi ${safeTask} step ${input.step}: ${safeTool} ${safeRel}`;
558
- }
559
- /* ------------------------------ internals ------------------------------ */
560
- /**
561
- * Per-call env for shadow git invocations. Pins author + committer +
562
- * dates к deterministic values so the shadow log is reproducible and
563
- * never leaks the operator's host identity. We do NOT inherit the
564
- * user's `process.env` GIT_* overrides; the spawn wrapper merges this
565
- * onto the parent env explicitly.
566
- *
567
- * Triple-review P1-1 — `/dev/null` is the Unix bit-bucket; Windows
568
- * uses `NUL`. Passing `/dev/null` к `GIT_CONFIG_*` on Windows makes
569
- * git try to open a literal file named `/dev/null`, which either
570
- * silently fails or, worse, on a misconfigured CI box succeeds and
571
- * gets created as a file under the shadow root. The fix routes by
572
- * `process.platform`.
573
- *
574
- * Triple-review P1-4 — pin `GIT_AUTHOR_DATE` + `GIT_COMMITTER_DATE`
575
- * to the supplied timestamp (ISO 8601) so `git log` output is
576
- * reproducible across hosts. When no timestamp is supplied the caller
577
- * is expected to be `initShadowRepo` whose commit is bootstrap-only;
578
- * we still emit a stable ISO date so test corpora hash-stable.
579
- */
580
- function shadowEnv(now = Date.now()) {
581
- const nullDevice = process.platform === 'win32' ? 'NUL' : '/dev/null';
582
- const isoDate = new Date(now).toISOString();
583
- return {
584
- ...process.env,
585
- GIT_AUTHOR_NAME: SHADOW_AUTHOR_NAME,
586
- GIT_AUTHOR_EMAIL: SHADOW_AUTHOR_EMAIL,
587
- GIT_COMMITTER_NAME: SHADOW_AUTHOR_NAME,
588
- GIT_COMMITTER_EMAIL: SHADOW_AUTHOR_EMAIL,
589
- // Pin commit dates for reproducible shadow logs. ISO 8601 is the
590
- // format git natively accepts; the GIT_*_DATE pair propagates к
591
- // both `git commit -m` and `git --git-dir … commit` calls.
592
- GIT_AUTHOR_DATE: isoDate,
593
- GIT_COMMITTER_DATE: isoDate,
594
- // Disable any global hook config so the shadow can't fire the
595
- // operator's commit-msg / pre-commit hooks from the user's repo.
596
- GIT_CONFIG_GLOBAL: nullDevice,
597
- GIT_CONFIG_SYSTEM: nullDevice,
598
- };
599
- }
600
- function spawnGit(args, opts) {
601
- const res = spawnSync('git', args, {
602
- cwd: opts.cwd,
603
- env: opts.env ?? process.env,
604
- encoding: 'utf8',
605
- });
606
- return {
607
- status: res.status,
608
- stdout: typeof res.stdout === 'string' ? res.stdout : String(res.stdout ?? ''),
609
- stderr: typeof res.stderr === 'string' ? res.stderr : String(res.stderr ?? ''),
610
- error: res.error,
611
- };
612
- }
613
- function describeSpawn(res) {
614
- const stderr = res.stderr.trim();
615
- const stdout = res.stdout.trim();
616
- if (res.error)
617
- return `${res.error.message}${stderr ? ` | ${stderr}` : ''}`;
618
- const status = res.status ?? 'n/a';
619
- return [
620
- `exit=${status}`,
621
- stderr ? `stderr=${stderr}` : '',
622
- stdout && !stderr ? `stdout=${stdout}` : '',
623
- ]
624
- .filter((s) => s.length > 0)
625
- .join(' ');
626
- }
627
- function safeStat(p) {
628
- try {
629
- return statSync(p);
630
- }
631
- catch {
632
- return null;
633
- }
634
- }
635
- /**
636
- * Default readline-based prompt with a hard timeout. Resolves к the
637
- * empty string on timeout, which the caller interprets as "operator
638
- * declined". We deliberately do NOT use `process.stdin.once('data')`
639
- * directly because that breaks Ctrl-C: readline gives us the standard
640
- * signal handlers for free.
641
- *
642
- * Lifted out as a module-level helper so the spec can inject a stub
643
- * via `RestoreCheckpointOptions.prompt` without touching real stdin.
644
- */
645
- function defaultPrompt(question, timeoutMs) {
646
- return new Promise((resolveOuter) => {
647
- const rl = createInterface({ input: process.stdin, output: process.stdout });
648
- let settled = false;
649
- const finish = (value) => {
650
- if (settled)
651
- return;
652
- settled = true;
653
- try {
654
- rl.close();
655
- }
656
- catch {
657
- /* ignore */
658
- }
659
- resolveOuter(value);
660
- };
661
- const timer = setTimeout(() => finish(''), Math.max(100, timeoutMs));
662
- if (typeof timer.unref === 'function')
663
- timer.unref();
664
- rl.question(question, (answer) => {
665
- clearTimeout(timer);
666
- finish(answer);
667
- });
668
- });
669
- }
670
- //# sourceMappingURL=shadow-git.js.map