@pugi/cli 0.1.0-beta.9 → 0.1.0-beta.90

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 (409) hide show
  1. package/CHANGELOG.md +132 -0
  2. package/LICENSE +1 -1
  3. package/assets/pugi-prozr2-mascot.ansi +9 -0
  4. package/bin/run.js +33 -1
  5. package/dist/commands/deploy.js +40 -40
  6. package/dist/commands/flatten.js +191 -0
  7. package/dist/commands/jobs-watch.js +201 -0
  8. package/dist/commands/jobs.js +42 -27
  9. package/dist/commands/smoke.js +133 -0
  10. package/dist/core/agent-progress/cleanup.js +134 -0
  11. package/dist/core/agent-progress/schema.js +144 -0
  12. package/dist/core/agent-progress/writer.js +101 -0
  13. package/dist/core/agents/adaptive-router.js +330 -0
  14. package/dist/core/agents/query-decomposer.js +297 -0
  15. package/dist/core/agents/registry.js +3 -3
  16. package/dist/core/approvals/shortcut-resolver.js +98 -0
  17. package/dist/core/artifact-chain/dispatcher.js +148 -0
  18. package/dist/core/artifact-chain/exporter.js +164 -0
  19. package/dist/core/artifact-chain/state.js +243 -0
  20. package/dist/core/artifact-chain/steps.js +169 -0
  21. package/dist/core/ask-user/question.js +92 -0
  22. package/dist/core/audit/audit-trail.js +275 -0
  23. package/dist/core/auth/ensure-authenticated.js +129 -0
  24. package/dist/core/auth/env-provider.js +238 -0
  25. package/dist/core/auto-open-browser.js +4 -4
  26. package/dist/core/auto-update/channels.js +122 -0
  27. package/dist/core/auto-update/checker.js +241 -0
  28. package/dist/core/auto-update/state.js +235 -0
  29. package/dist/core/bare-mode/index.js +107 -0
  30. package/dist/core/bash/redirect.js +281 -0
  31. package/dist/core/bash-classifier.js +436 -40
  32. package/dist/core/checkpoint/resumer.js +149 -0
  33. package/dist/core/checkpoint/rewinder.js +291 -0
  34. package/dist/core/checkpoints/shadow-git.js +670 -0
  35. package/dist/core/citations/parser.js +109 -0
  36. package/dist/core/classifier/yolo-classifier.js +88 -0
  37. package/dist/core/codegraph/decision-store.js +248 -0
  38. package/dist/core/codegraph/detect-repo.js +459 -0
  39. package/dist/core/codegraph/install.js +134 -0
  40. package/dist/core/codegraph/offer-hook.js +220 -0
  41. package/dist/core/compact/auto-trigger.js +96 -0
  42. package/dist/core/compact/buffer-rewriter.js +115 -0
  43. package/dist/core/compact/summarizer.js +208 -0
  44. package/dist/core/compact/token-counter.js +108 -0
  45. package/dist/core/consensus/anvil-fanout.js +25 -25
  46. package/dist/core/consensus/diff-capture.js +121 -12
  47. package/dist/core/consensus/rubric.js +21 -21
  48. package/dist/core/context/builder.js +6 -6
  49. package/dist/core/context/compaction-events.js +8 -8
  50. package/dist/core/context/compaction.js +31 -31
  51. package/dist/core/context/index.js +15 -8
  52. package/dist/core/context/invariants.js +51 -51
  53. package/dist/core/context/markdown-loader.js +28 -10
  54. package/dist/core/context/markdown-traverse.js +255 -0
  55. package/dist/core/context/pugiignore.js +41 -41
  56. package/dist/core/context/repo-skeleton.js +37 -37
  57. package/dist/core/context/tool-eviction.js +55 -0
  58. package/dist/core/context/watcher.js +32 -32
  59. package/dist/core/context/working-set.js +23 -23
  60. package/dist/core/coordinator/agent-tools.js +77 -0
  61. package/dist/core/coordinator/agent-toolset.js +65 -0
  62. package/dist/core/coordinator/fsm.js +73 -0
  63. package/dist/core/coordinator/mode-fsm.js +70 -0
  64. package/dist/core/cost/rate-card.js +129 -0
  65. package/dist/core/cost/tracker.js +221 -0
  66. package/dist/core/credentials.js +13 -13
  67. package/dist/core/cron/scheduler.js +138 -0
  68. package/dist/core/denial-tracking/index.js +8 -0
  69. package/dist/core/denial-tracking/state.js +264 -0
  70. package/dist/core/diagnostics/probe-runner.js +93 -0
  71. package/dist/core/diagnostics/probes/api.js +46 -0
  72. package/dist/core/diagnostics/probes/auth.js +93 -0
  73. package/dist/core/diagnostics/probes/bare-mode.js +42 -0
  74. package/dist/core/diagnostics/probes/cli-version.js +127 -0
  75. package/dist/core/diagnostics/probes/config.js +72 -0
  76. package/dist/core/diagnostics/probes/denial-tracking.js +57 -0
  77. package/dist/core/diagnostics/probes/disk.js +81 -0
  78. package/dist/core/diagnostics/probes/engine-live.js +46 -0
  79. package/dist/core/diagnostics/probes/git.js +65 -0
  80. package/dist/core/diagnostics/probes/hooks.js +118 -0
  81. package/dist/core/diagnostics/probes/mcp.js +75 -0
  82. package/dist/core/diagnostics/probes/node.js +59 -0
  83. package/dist/core/diagnostics/probes/pnpm.js +36 -0
  84. package/dist/core/diagnostics/probes/pugi-md.js +89 -0
  85. package/dist/core/diagnostics/probes/sandbox.js +40 -0
  86. package/dist/core/diagnostics/probes/session.js +74 -0
  87. package/dist/core/diagnostics/probes/status-snapshot.js +488 -0
  88. package/dist/core/diagnostics/probes/workspace.js +63 -0
  89. package/dist/core/diagnostics/types.js +70 -0
  90. package/dist/core/dispatch/cache-cleanup.js +197 -0
  91. package/dist/core/dispatch/cache-handoff.js +295 -0
  92. package/dist/core/edits/apply-patch-layer-e.js +189 -0
  93. package/dist/core/edits/dispatch.js +333 -7
  94. package/dist/core/edits/format-detector.js +260 -0
  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 +5 -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 +29 -29
  108. package/dist/core/engine/anvil-client.js +214 -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 +129 -19
  117. package/dist/core/engine/strip-internal-fields.js +124 -0
  118. package/dist/core/engine/tool-bridge.js +1731 -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 +46 -0
  129. package/dist/core/hooks/index.js +15 -0
  130. package/dist/core/hooks/registry.js +216 -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/hooks/worktree-events.js +158 -0
  141. package/dist/core/image/renderer.js +71 -0
  142. package/dist/core/init/detector.js +582 -0
  143. package/dist/core/init/template-renderer.js +242 -0
  144. package/dist/core/jobs/registry.js +18 -18
  145. package/dist/core/ledger/results-tsv.js +142 -0
  146. package/dist/core/log-discipline/stdout-redirect.js +51 -0
  147. package/dist/core/lsp/cache.js +105 -0
  148. package/dist/core/lsp/client.js +551 -41
  149. package/dist/core/lsp/language-detect.js +66 -0
  150. package/dist/core/lsp/post-edit-diagnostics.js +171 -0
  151. package/dist/core/lsp/server-detect.js +173 -0
  152. package/dist/core/lsp/symbol-cache.js +162 -0
  153. package/dist/core/lsp/symbol-tools.js +664 -0
  154. package/dist/core/mcp/client.js +97 -28
  155. package/dist/core/mcp/http-server.js +553 -0
  156. package/dist/core/mcp/orchestrator-tools.js +662 -0
  157. package/dist/core/mcp/permission.js +190 -0
  158. package/dist/core/mcp/registry.js +39 -17
  159. package/dist/core/mcp/server-tools.js +219 -0
  160. package/dist/core/mcp/server.js +397 -0
  161. package/dist/core/mcp/trust.js +10 -10
  162. package/dist/core/memory/dual-write.js +416 -0
  163. package/dist/core/memory/passive-extract.js +130 -0
  164. package/dist/core/memory/phase1-kinds.js +20 -0
  165. package/dist/core/memory/secret-scanner.js +304 -0
  166. package/dist/core/memory-sync/queue.js +170 -0
  167. package/dist/core/metrics/extract.js +113 -0
  168. package/dist/core/modes/roo-modes.js +68 -0
  169. package/dist/core/onboarding/ensure-initialized.js +133 -0
  170. package/dist/core/onboarding/marker.js +111 -0
  171. package/dist/core/onboarding/telemetry-state.js +108 -0
  172. package/dist/core/output-style/presets.js +176 -0
  173. package/dist/core/output-style/state.js +185 -0
  174. package/dist/core/path-security.js +287 -5
  175. package/dist/core/permission.js +82 -22
  176. package/dist/core/permissions/auto-classifier.js +124 -0
  177. package/dist/core/permissions/bash-parser.js +371 -0
  178. package/dist/core/permissions/circuit-breaker.js +83 -0
  179. package/dist/core/permissions/constrained-edit.js +91 -0
  180. package/dist/core/permissions/gate.js +278 -0
  181. package/dist/core/permissions/index.js +20 -0
  182. package/dist/core/permissions/mode.js +174 -0
  183. package/dist/core/permissions/network-egress.js +137 -0
  184. package/dist/core/permissions/state.js +241 -0
  185. package/dist/core/permissions/tool-class.js +93 -0
  186. package/dist/core/plan-mode/ui-state.js +51 -0
  187. package/dist/core/plans/plan-artifact.js +721 -0
  188. package/dist/core/policy-limits/etag-store.js +122 -0
  189. package/dist/core/prd-check/parser.js +215 -0
  190. package/dist/core/prd-check/reporter.js +127 -0
  191. package/dist/core/prd-check/session-review.js +557 -0
  192. package/dist/core/prd-check/verifiers.js +223 -0
  193. package/dist/core/prompt-cache/client-cache.js +99 -0
  194. package/dist/core/prompts/assembly.js +29 -0
  195. package/dist/core/prompts/registry.js +364 -0
  196. package/dist/core/pugi-md/cc-compat-rules.js +735 -0
  197. package/dist/core/pugi-md/context-injector.js +76 -0
  198. package/dist/core/pugi-md/walk-up.js +207 -0
  199. package/dist/core/python/uv-installer.js +270 -0
  200. package/dist/core/python/uv-resolver.js +83 -0
  201. package/dist/core/rate-limit/narrator.js +146 -0
  202. package/dist/core/recipes/cli-types.js +20 -0
  203. package/dist/core/recipes/loader.js +103 -0
  204. package/dist/core/recipes/runner.js +345 -0
  205. package/dist/core/recipes/schema.js +587 -0
  206. package/dist/core/release-notes/parser.js +241 -0
  207. package/dist/core/release-notes/state.js +116 -0
  208. package/dist/core/repl/ask.js +37 -37
  209. package/dist/core/repl/cancellation.js +26 -26
  210. package/dist/core/repl/cap-warning.js +4 -4
  211. package/dist/core/repl/clipboard-read.js +11 -11
  212. package/dist/core/repl/dispatch-fsm.js +12 -12
  213. package/dist/core/repl/history-search.js +15 -15
  214. package/dist/core/repl/history.js +28 -18
  215. package/dist/core/repl/kill-ring.js +5 -5
  216. package/dist/core/repl/model-pricing.js +135 -0
  217. package/dist/core/repl/privacy-banner.js +22 -22
  218. package/dist/core/repl/session.js +2148 -217
  219. package/dist/core/repl/slash-commands.js +501 -41
  220. package/dist/core/repl/store/index.js +1 -1
  221. package/dist/core/repl/store/jsonl-log.js +22 -22
  222. package/dist/core/repl/store/lockfile.js +10 -10
  223. package/dist/core/repl/store/session-store.js +136 -107
  224. package/dist/core/repl/store/types.js +15 -15
  225. package/dist/core/repl/store/uuid-v7.js +12 -12
  226. package/dist/core/repl/workspace-context.js +43 -21
  227. package/dist/core/repo-map/build.js +125 -0
  228. package/dist/core/repo-map/cache.js +185 -0
  229. package/dist/core/repo-map/extractor.js +254 -0
  230. package/dist/core/repo-map/formatter.js +145 -0
  231. package/dist/core/repo-map/page-rank.js +105 -0
  232. package/dist/core/repo-map/scanner.js +211 -0
  233. package/dist/core/retry-budget/budget.js +284 -0
  234. package/dist/core/retry-budget/index.js +5 -0
  235. package/dist/core/retry-budget/retry-cap.js +74 -0
  236. package/dist/core/routing/lead-worker.js +43 -0
  237. package/dist/core/routing/pre-flight-estimator.js +108 -0
  238. package/dist/core/runs/run-tree.js +103 -0
  239. package/dist/core/security/injection-scanner.js +367 -0
  240. package/dist/core/security/output-filter.js +418 -0
  241. package/dist/core/session/env-file.js +105 -0
  242. package/dist/core/session/section-budgets.js +140 -0
  243. package/dist/core/session.js +92 -0
  244. package/dist/core/settings.js +324 -5
  245. package/dist/core/share/formatter.js +271 -0
  246. package/dist/core/share/redactor.js +221 -0
  247. package/dist/core/share/uploader.js +267 -0
  248. package/dist/core/skills/defaults.js +30 -30
  249. package/dist/core/skills/loader.js +22 -22
  250. package/dist/core/skills/sources.js +27 -27
  251. package/dist/core/smoke/headless-driver.js +174 -0
  252. package/dist/core/smoke/orchestrator.js +194 -0
  253. package/dist/core/smoke/runner.js +238 -0
  254. package/dist/core/smoke/scenario-parser.js +316 -0
  255. package/dist/core/statusline.js +99 -0
  256. package/dist/core/subagents/dispatcher-real.js +600 -0
  257. package/dist/core/subagents/dispatcher.js +132 -43
  258. package/dist/core/subagents/index.js +19 -6
  259. package/dist/core/subagents/isolation-matrix.js +213 -0
  260. package/dist/core/subagents/spawn.js +19 -4
  261. package/dist/core/telemetry/emitter.js +229 -0
  262. package/dist/core/telemetry/queue.js +251 -0
  263. package/dist/core/theme/context.js +91 -0
  264. package/dist/core/theme/presets.js +228 -0
  265. package/dist/core/theme/state.js +181 -0
  266. package/dist/core/todos/invariant.js +10 -0
  267. package/dist/core/todos/state.js +177 -0
  268. package/dist/core/tool-schema/compressor.js +89 -0
  269. package/dist/core/transport/version-interceptor.js +166 -0
  270. package/dist/core/trust.js +2 -2
  271. package/dist/core/tui/thinking-block.js +64 -0
  272. package/dist/core/vim/keymap.js +288 -0
  273. package/dist/core/vim/state.js +92 -0
  274. package/dist/core/watch-markers/marker-watcher.js +133 -0
  275. package/dist/core/worktree/include-parser.js +249 -0
  276. package/dist/core/worktree-manager/cleanup.js +123 -0
  277. package/dist/core/worktree-manager/manager.js +303 -0
  278. package/dist/index.js +36 -0
  279. package/dist/runtime/bootstrap.js +190 -0
  280. package/dist/runtime/cli.js +4185 -549
  281. package/dist/runtime/commands/agents.js +31 -31
  282. package/dist/runtime/commands/budget.js +5 -5
  283. package/dist/runtime/commands/cancel.js +231 -0
  284. package/dist/runtime/commands/chain.js +489 -0
  285. package/dist/runtime/commands/codegraph-status.js +227 -0
  286. package/dist/runtime/commands/compact.js +297 -0
  287. package/dist/runtime/commands/config.js +73 -39
  288. package/dist/runtime/commands/cost.js +199 -0
  289. package/dist/runtime/commands/delegate.js +27 -4
  290. package/dist/runtime/commands/dispatch.js +126 -0
  291. package/dist/runtime/commands/doctor.js +579 -0
  292. package/dist/runtime/commands/feedback.js +184 -0
  293. package/dist/runtime/commands/hooks.js +187 -0
  294. package/dist/runtime/commands/init.js +254 -0
  295. package/dist/runtime/commands/lsp.js +200 -38
  296. package/dist/runtime/commands/mcp.js +879 -0
  297. package/dist/runtime/commands/memory.js +582 -0
  298. package/dist/runtime/commands/model.js +237 -0
  299. package/dist/runtime/commands/onboarding.js +275 -0
  300. package/dist/runtime/commands/patch.js +12 -12
  301. package/dist/runtime/commands/permissions.js +112 -0
  302. package/dist/runtime/commands/plan.js +143 -0
  303. package/dist/runtime/commands/prd-check.js +285 -0
  304. package/dist/runtime/commands/privacy.js +17 -17
  305. package/dist/runtime/commands/recipe.js +325 -0
  306. package/dist/runtime/commands/redo-blob-store.js +92 -0
  307. package/dist/runtime/commands/redo.js +361 -0
  308. package/dist/runtime/commands/release-notes.js +229 -0
  309. package/dist/runtime/commands/repo-map.js +95 -0
  310. package/dist/runtime/commands/report.js +299 -0
  311. package/dist/runtime/commands/resume.js +118 -0
  312. package/dist/runtime/commands/review-consensus.js +68 -53
  313. package/dist/runtime/commands/rewind.js +333 -0
  314. package/dist/runtime/commands/roster.js +14 -14
  315. package/dist/runtime/commands/sessions.js +163 -0
  316. package/dist/runtime/commands/share.js +316 -0
  317. package/dist/runtime/commands/skills.js +31 -31
  318. package/dist/runtime/commands/status.js +186 -0
  319. package/dist/runtime/commands/stickers.js +82 -0
  320. package/dist/runtime/commands/style.js +194 -0
  321. package/dist/runtime/commands/theme.js +196 -0
  322. package/dist/runtime/commands/undo.js +54 -22
  323. package/dist/runtime/commands/update.js +289 -0
  324. package/dist/runtime/commands/vim.js +140 -0
  325. package/dist/runtime/commands/worktree.js +8 -8
  326. package/dist/runtime/commands/worktrees.js +155 -0
  327. package/dist/runtime/headless-repl.js +195 -0
  328. package/dist/runtime/headless.js +543 -0
  329. package/dist/runtime/load-hooks-or-exit.js +71 -0
  330. package/dist/runtime/plan-decompose.js +22 -22
  331. package/dist/runtime/sigint-guard.js +272 -0
  332. package/dist/runtime/update-check.js +28 -28
  333. package/dist/runtime/version.js +65 -0
  334. package/dist/runtime/worktree-bootstrap.js +579 -0
  335. package/dist/skills/bundled/batch.js +617 -0
  336. package/dist/skills/bundled/index.js +45 -0
  337. package/dist/skills/bundled/loop.js +358 -0
  338. package/dist/skills/bundled/remember.js +383 -0
  339. package/dist/skills/bundled/simplify.js +289 -0
  340. package/dist/skills/bundled/skillify.js +373 -0
  341. package/dist/skills/bundled/stuck.js +558 -0
  342. package/dist/skills/bundled/verify.js +439 -0
  343. package/dist/testing/vcr.js +486 -0
  344. package/dist/tools/agent-tool.js +229 -0
  345. package/dist/tools/apply-patch.js +89 -28
  346. package/dist/tools/ask-user-question.js +337 -0
  347. package/dist/tools/ask-user.js +115 -0
  348. package/dist/tools/bash.js +624 -46
  349. package/dist/tools/brief.js +224 -0
  350. package/dist/tools/enter-worktree.js +250 -0
  351. package/dist/tools/exit-worktree.js +147 -0
  352. package/dist/tools/file-tools.js +161 -44
  353. package/dist/tools/lsp-tools.js +377 -1
  354. package/dist/tools/mcp-tool.js +260 -0
  355. package/dist/tools/multi-edit.js +361 -0
  356. package/dist/tools/powershell.js +268 -0
  357. package/dist/tools/registry.js +86 -4
  358. package/dist/tools/skill-tool.js +96 -0
  359. package/dist/tools/sleep.js +99 -0
  360. package/dist/tools/synthetic-output.js +133 -0
  361. package/dist/tools/tasks.js +208 -0
  362. package/dist/tools/todo-write.js +184 -0
  363. package/dist/tools/verify-plan-execution.js +295 -0
  364. package/dist/tools/web-fetch-injection-scanner.js +207 -0
  365. package/dist/tools/web-fetch.js +195 -10
  366. package/dist/tools/web-search.js +458 -0
  367. package/dist/tui/agent-progress-card.js +111 -0
  368. package/dist/tui/agent-tree.js +11 -1
  369. package/dist/tui/ask-modal.js +14 -14
  370. package/dist/tui/ask-user-question-chips.js +315 -0
  371. package/dist/tui/ask-user-question-prompt.js +203 -0
  372. package/dist/tui/compact-banner.js +81 -0
  373. package/dist/tui/conversation-pane.js +85 -11
  374. package/dist/tui/cost-table.js +111 -0
  375. package/dist/tui/device-flow.js +2 -2
  376. package/dist/tui/doctor-table.js +46 -0
  377. package/dist/tui/feedback-prompt.js +156 -0
  378. package/dist/tui/input-box.js +247 -32
  379. package/dist/tui/login-picker.js +3 -3
  380. package/dist/tui/markdown-render.js +6 -6
  381. package/dist/tui/onboarding-wizard.js +240 -0
  382. package/dist/tui/permissions-picker.js +86 -0
  383. package/dist/tui/render.js +36 -1
  384. package/dist/tui/repl-render.js +176 -25
  385. package/dist/tui/repl-splash-art.js +16 -16
  386. package/dist/tui/repl-splash-mascot.js +48 -24
  387. package/dist/tui/repl-splash.js +22 -22
  388. package/dist/tui/repl.js +125 -45
  389. package/dist/tui/slash-palette.js +6 -6
  390. package/dist/tui/splash.js +2 -2
  391. package/dist/tui/status-bar.js +109 -31
  392. package/dist/tui/status-table.js +7 -0
  393. package/dist/tui/stickers-art.js +136 -0
  394. package/dist/tui/style-table.js +28 -0
  395. package/dist/tui/theme-table.js +29 -0
  396. package/dist/tui/thinking-spinner.js +123 -0
  397. package/dist/tui/tool-stream-pane.js +53 -4
  398. package/dist/tui/update-banner.js +27 -2
  399. package/dist/tui/vim-input.js +267 -0
  400. package/dist/tui/welcome-banner.js +107 -0
  401. package/dist/tui/welcome-data.js +293 -0
  402. package/dist/tui/workspace-context.js +2 -2
  403. package/package.json +31 -16
  404. package/test/scenarios/codegen-create-file.scenario.txt +13 -0
  405. package/test/scenarios/compact-force.scenario.txt +12 -0
  406. package/test/scenarios/identity.scenario.txt +12 -0
  407. package/test/scenarios/persona-handoff.scenario.txt +12 -0
  408. package/test/scenarios/walkback.scenario.txt +12 -0
  409. package/dist/core/engine/compaction-hook.js +0 -154
@@ -0,0 +1,392 @@
1
+ /**
2
+ * Hook chains — `PostToolUseFailure` + `TaskCompleted` first-class events.
3
+ *
4
+ * #24 (CEO P1). Pugi already had primitives for
5
+ * `PostToolUseFailure` in the legacy `core/hooks.ts` registry but no
6
+ * way to declare a **fallback chain** that fires automatically when a
7
+ * tool dispatch fails or when an entire `pugi <command>` dispatch
8
+ * completes. the upstream tool exposes both events as first-class hook
9
+ * sources; this module wires them on the Pugi side.
10
+ *
11
+ * Why a fresh module rather than extending `core/hooks.ts`:
12
+ * - The legacy registry reads flat `hooks: [{event, match, run}]`
13
+ * arrays from `~/.pugi/hooks.json` + `.pugi/hooks.json`. Chains
14
+ * are declared in a nested `hooks: { EventName: [{matcher, run}] }`
15
+ * shape from `.pugi/settings.json` — a different file, different
16
+ * shape, and importantly a different opt-out grammar (per-chain
17
+ * `enabled: false`). Mixing the two readers would force every
18
+ * legacy caller to learn the chain shape.
19
+ * - Chains have richer payloads (TaskCompleted ships durationMs,
20
+ * toolCalls, filesChanged). Stuffing them through the legacy
21
+ * stdin/env contract would silently break v1 scripts that key on
22
+ * the existing payload shape.
23
+ * - Chain failures MUST NOT crash the dispatch (the model already
24
+ * finished). The legacy registry's `onFailure: 'block'` semantics
25
+ * would propagate the error — chains explicitly swallow it and
26
+ * log instead.
27
+ *
28
+ * The chain runner is intentionally self-contained: no dependency on
29
+ * `HookRegistry`, no trust-ledger gating (project settings are already
30
+ * trusted by virtue of being in the workspace's `.pugi/` dir, same as
31
+ * persona prompts), and a single `firePostToolUseFailureChain` /
32
+ * `fireTaskCompletedChain` entry point per event.
33
+ *
34
+ * Brand voice: ASCII only, no emoji, no em-dashes.
35
+ */
36
+ import { spawn } from 'node:child_process';
37
+ import { z } from 'zod';
38
+ import { loadSettings } from './settings.js';
39
+ /**
40
+ * Per-hook matcher. Both keys are optional and AND together — a hook
41
+ * with `matcher: { tool: 'write' }` fires for every PostToolUseFailure
42
+ * whose tool is `write`, regardless of command. A hook with no matcher
43
+ * at all (or `matcher: {}`) fires on every event of its kind.
44
+ */
45
+ const chainMatcherSchema = z
46
+ .object({
47
+ /** Compare against the failing tool name (PostToolUseFailure). */
48
+ tool: z.string().min(1).optional(),
49
+ /** Compare against the completed command name (TaskCompleted). */
50
+ command: z.string().min(1).optional(),
51
+ })
52
+ .strict();
53
+ /**
54
+ * Single chain entry. `run` is an array so chain authors can express
55
+ * "do A, then B, then C" without needing shell `&&` chaining — the
56
+ * runner executes the list sequentially.
57
+ *
58
+ * Hard-coded constants:
59
+ * - Default timeout per command: 10s.
60
+ * - Max timeout: 60s (matches legacy hooks.ts cap).
61
+ * - Stream cap: 256KB per stream (smaller than legacy 1MB because
62
+ * chain hooks are post-hoc notifications, not blocking gates).
63
+ */
64
+ const chainEntrySchema = z
65
+ .object({
66
+ matcher: chainMatcherSchema.optional(),
67
+ run: z.array(z.string().min(1)).min(1),
68
+ timeoutMs: z.number().int().positive().max(60_000).optional(),
69
+ })
70
+ .strict();
71
+ /**
72
+ * Per-event chain config. `enabled: false` short-circuits the whole
73
+ * chain (opt-out switch the operator can flip without deleting the
74
+ * entries). `entries` defaults to empty so the operator can declare
75
+ * `{ enabled: false }` to suppress a chain inherited from a parent
76
+ * config layer later.
77
+ */
78
+ const chainConfigSchema = z
79
+ .object({
80
+ enabled: z.boolean().default(true),
81
+ entries: z.array(chainEntrySchema).default([]),
82
+ })
83
+ .strict();
84
+ /**
85
+ * Settings-level `hooks` block. We accept BOTH:
86
+ * 1. The canonical nested form
87
+ * `hooks: { PostToolUseFailure: { enabled, entries: [...] } }`
88
+ * 2. The shorthand array form
89
+ * `hooks: { PostToolUseFailure: [...entries] }`
90
+ * — which the CEO spec uses as the example. The reader normalises
91
+ * it to the canonical form before handing back to callers.
92
+ */
93
+ const settingsHooksShape = z
94
+ .object({
95
+ PostToolUseFailure: z
96
+ .union([chainConfigSchema, z.array(chainEntrySchema)])
97
+ .optional(),
98
+ TaskCompleted: z
99
+ .union([chainConfigSchema, z.array(chainEntrySchema)])
100
+ .optional(),
101
+ })
102
+ .strict()
103
+ .optional();
104
+ const DEFAULT_TIMEOUT_MS = 10_000;
105
+ const SIGKILL_GRACE_MS = 2_000;
106
+ const STREAM_CAP_BYTES = 256 * 1024;
107
+ /**
108
+ * Resolve chain config from settings. Accepts both the canonical
109
+ * `{ enabled, entries }` shape and the shorthand array form the CEO
110
+ * spec example uses.
111
+ */
112
+ export function resolveChain(settings, event) {
113
+ const rawHooks = settings.hooks;
114
+ const parsed = settingsHooksShape.safeParse(rawHooks);
115
+ if (!parsed.success || !parsed.data) {
116
+ return { enabled: true, entries: [] };
117
+ }
118
+ const raw = parsed.data[event];
119
+ if (!raw)
120
+ return { enabled: true, entries: [] };
121
+ if (Array.isArray(raw)) {
122
+ return { enabled: true, entries: raw };
123
+ }
124
+ return raw;
125
+ }
126
+ /**
127
+ * Parse the `hooks` section out of a raw settings JSON value. Exposed
128
+ * for tests and the settings reload path. Returns the normalised shape
129
+ * (both events present, canonical `{enabled, entries}` form) so the
130
+ * caller can assert structure without re-running Zod.
131
+ */
132
+ export function parseHookChains(rawHooks) {
133
+ const parsed = settingsHooksShape.safeParse(rawHooks);
134
+ const out = {
135
+ PostToolUseFailure: { enabled: true, entries: [] },
136
+ TaskCompleted: { enabled: true, entries: [] },
137
+ };
138
+ if (!parsed.success || !parsed.data)
139
+ return out;
140
+ const pf = parsed.data.PostToolUseFailure;
141
+ if (pf) {
142
+ out.PostToolUseFailure = Array.isArray(pf) ? { enabled: true, entries: pf } : pf;
143
+ }
144
+ const tc = parsed.data.TaskCompleted;
145
+ if (tc) {
146
+ out.TaskCompleted = Array.isArray(tc) ? { enabled: true, entries: tc } : tc;
147
+ }
148
+ return out;
149
+ }
150
+ /**
151
+ * Decide whether a chain entry matches a `PostToolUseFailure` payload.
152
+ * An entry with no matcher (or only the empty object) fires on every
153
+ * failure of its event kind.
154
+ */
155
+ function matchesPostToolUseFailure(entry, payload) {
156
+ const m = entry.matcher;
157
+ if (!m || (!m.tool && !m.command))
158
+ return true;
159
+ if (m.tool !== undefined && m.tool !== payload.toolName)
160
+ return false;
161
+ return true;
162
+ }
163
+ /**
164
+ * Decide whether a chain entry matches a `TaskCompleted` payload.
165
+ */
166
+ function matchesTaskCompleted(entry, payload) {
167
+ const m = entry.matcher;
168
+ if (!m || (!m.tool && !m.command))
169
+ return true;
170
+ if (m.command !== undefined && m.command !== payload.command)
171
+ return false;
172
+ return true;
173
+ }
174
+ /**
175
+ * Fire the `PostToolUseFailure` chain. Best-effort: a hook crash, a
176
+ * spawn error, or a timeout never propagates back to the caller.
177
+ *
178
+ * Caller responsibility: the engine's tool-bridge invokes this AFTER
179
+ * the existing legacy `PostToolUseFailure` registry fire so legacy
180
+ * scripts and chain entries both run on the same failure (the legacy
181
+ * registry is the strict per-tool gate; chains are the fallback hook).
182
+ */
183
+ export async function firePostToolUseFailureChain(workspaceRoot, payload, settingsOverride) {
184
+ const settings = settingsOverride ?? safeLoadSettings(workspaceRoot);
185
+ const config = resolveChain(settings, 'PostToolUseFailure');
186
+ if (!config.enabled) {
187
+ return { event: 'PostToolUseFailure', enabled: false, entries: [] };
188
+ }
189
+ const out = {
190
+ event: 'PostToolUseFailure',
191
+ enabled: true,
192
+ entries: [],
193
+ };
194
+ for (const entry of config.entries) {
195
+ const matched = matchesPostToolUseFailure(entry, payload);
196
+ if (!matched) {
197
+ out.entries.push({ matched: false, commands: [] });
198
+ continue;
199
+ }
200
+ const commands = await runChainEntry(entry, {
201
+ PUGI_HOOK_EVENT: 'PostToolUseFailure',
202
+ PUGI_HOOK_PAYLOAD: JSON.stringify(payload),
203
+ PUGI_HOOK_TOOL: payload.toolName,
204
+ PUGI_HOOK_EXIT_CODE: String(payload.exitCode),
205
+ });
206
+ out.entries.push({ matched: true, commands });
207
+ }
208
+ return out;
209
+ }
210
+ /**
211
+ * Fire the `TaskCompleted` chain. Same best-effort semantics as the
212
+ * PostToolUseFailure chain above. Caller invokes this at the dispatch
213
+ * exit in `native-pugi.ts` regardless of completion status (the
214
+ * payload carries `exitCode` so the hook can branch on success vs
215
+ * failure).
216
+ */
217
+ export async function fireTaskCompletedChain(workspaceRoot, payload, settingsOverride) {
218
+ const settings = settingsOverride ?? safeLoadSettings(workspaceRoot);
219
+ const config = resolveChain(settings, 'TaskCompleted');
220
+ if (!config.enabled) {
221
+ return { event: 'TaskCompleted', enabled: false, entries: [] };
222
+ }
223
+ const out = {
224
+ event: 'TaskCompleted',
225
+ enabled: true,
226
+ entries: [],
227
+ };
228
+ for (const entry of config.entries) {
229
+ const matched = matchesTaskCompleted(entry, payload);
230
+ if (!matched) {
231
+ out.entries.push({ matched: false, commands: [] });
232
+ continue;
233
+ }
234
+ const commands = await runChainEntry(entry, {
235
+ PUGI_HOOK_EVENT: 'TaskCompleted',
236
+ PUGI_HOOK_PAYLOAD: JSON.stringify(payload),
237
+ PUGI_HOOK_COMMAND: payload.command,
238
+ PUGI_HOOK_EXIT_CODE: String(payload.exitCode),
239
+ PUGI_HOOK_DURATION_MS: String(payload.durationMs),
240
+ PUGI_HOOK_TOOL_CALLS: String(payload.toolCalls),
241
+ });
242
+ out.entries.push({ matched: true, commands });
243
+ }
244
+ return out;
245
+ }
246
+ /** Run every command in one chain entry sequentially. */
247
+ async function runChainEntry(entry, baseEnv) {
248
+ const timeoutMs = entry.timeoutMs ?? DEFAULT_TIMEOUT_MS;
249
+ const results = [];
250
+ for (const command of entry.run) {
251
+ try {
252
+ const result = await executeOne(command, timeoutMs, baseEnv);
253
+ results.push(result);
254
+ }
255
+ catch (error) {
256
+ // Spawn failure (binary missing, fork limit hit, etc). Swallow
257
+ // and record so the chain marches on to the next command.
258
+ results.push({
259
+ command,
260
+ exitCode: -1,
261
+ durationMs: 0,
262
+ stdout: '',
263
+ stderr: `chain spawn error: ${error.message}`,
264
+ timedOut: false,
265
+ });
266
+ }
267
+ }
268
+ return results;
269
+ }
270
+ /** Spawn ONE shell command and capture the result. */
271
+ function executeOne(command, timeoutMs, baseEnv) {
272
+ return new Promise((resolvePromise) => {
273
+ const startedAt = Date.now();
274
+ const child = spawn('/bin/sh', ['-c', command], {
275
+ env: { ...process.env, ...baseEnv },
276
+ stdio: ['pipe', 'pipe', 'pipe'],
277
+ });
278
+ let stdout = '';
279
+ let stderr = '';
280
+ let killedForTimeout = false;
281
+ let killedForStreamCap = false;
282
+ let sigKillTimer;
283
+ const enforceStreamCap = () => {
284
+ if (killedForStreamCap)
285
+ return;
286
+ if (stdout.length + stderr.length <= STREAM_CAP_BYTES)
287
+ return;
288
+ killedForStreamCap = true;
289
+ child.kill('SIGTERM');
290
+ if (!sigKillTimer) {
291
+ sigKillTimer = setTimeout(() => {
292
+ if (!child.killed)
293
+ child.kill('SIGKILL');
294
+ }, SIGKILL_GRACE_MS);
295
+ if (sigKillTimer.unref)
296
+ sigKillTimer.unref();
297
+ }
298
+ };
299
+ child.stdout?.on('data', (chunk) => {
300
+ if (killedForStreamCap)
301
+ return;
302
+ stdout += chunk.toString('utf8');
303
+ enforceStreamCap();
304
+ });
305
+ child.stderr?.on('data', (chunk) => {
306
+ if (killedForStreamCap)
307
+ return;
308
+ stderr += chunk.toString('utf8');
309
+ enforceStreamCap();
310
+ });
311
+ // Close stdin so commands that block on read (cat, jq) do not hang
312
+ // the chain. Errors are swallowed because the child may have
313
+ // already exited before we write.
314
+ if (child.stdin) {
315
+ child.stdin.on('error', () => {
316
+ /* ignore EPIPE */
317
+ });
318
+ child.stdin.end();
319
+ }
320
+ const timer = setTimeout(() => {
321
+ killedForTimeout = true;
322
+ child.kill('SIGTERM');
323
+ sigKillTimer = setTimeout(() => {
324
+ if (!child.killed)
325
+ child.kill('SIGKILL');
326
+ }, SIGKILL_GRACE_MS);
327
+ if (sigKillTimer.unref)
328
+ sigKillTimer.unref();
329
+ }, timeoutMs);
330
+ if (timer.unref)
331
+ timer.unref();
332
+ child.on('error', (error) => {
333
+ clearTimeout(timer);
334
+ if (sigKillTimer)
335
+ clearTimeout(sigKillTimer);
336
+ resolvePromise({
337
+ command,
338
+ exitCode: -1,
339
+ durationMs: Date.now() - startedAt,
340
+ stdout,
341
+ stderr: stderr || `hook spawn error: ${error.message}`,
342
+ timedOut: false,
343
+ });
344
+ });
345
+ child.on('close', (code, signal) => {
346
+ clearTimeout(timer);
347
+ if (sigKillTimer)
348
+ clearTimeout(sigKillTimer);
349
+ const durationMs = Date.now() - startedAt;
350
+ let exitCode;
351
+ if (code !== null) {
352
+ exitCode = code;
353
+ }
354
+ else if (signal === 'SIGTERM') {
355
+ exitCode = -15;
356
+ }
357
+ else if (signal === 'SIGKILL') {
358
+ exitCode = -9;
359
+ }
360
+ else {
361
+ exitCode = -1;
362
+ }
363
+ resolvePromise({
364
+ command,
365
+ exitCode,
366
+ durationMs,
367
+ stdout,
368
+ stderr,
369
+ timedOut: killedForTimeout || killedForStreamCap,
370
+ });
371
+ });
372
+ });
373
+ }
374
+ /**
375
+ * `loadSettings` throws on schema-invalid configs. Chains MUST NOT
376
+ * crash the dispatch — swallow + log to stderr so the caller proceeds
377
+ * with a default (no-hooks) settings object.
378
+ */
379
+ function safeLoadSettings(root) {
380
+ try {
381
+ return loadSettings(root);
382
+ }
383
+ catch (error) {
384
+ process.stderr.write(`[pugi hook-chains] settings load failed: ${error.message}\n`);
385
+ // Build a minimal settings object that parses through the Zod
386
+ // schema. The shape is built by re-parsing an empty settings dir
387
+ // which `loadSettings` already does for missing files (returns
388
+ // the schema's default-applied object).
389
+ return loadSettings('/this-path-does-not-exist-fallback');
390
+ }
391
+ }
392
+ //# sourceMappingURL=hook-chains.js.map
@@ -0,0 +1,138 @@
1
+ import { promises as fs } from 'node:fs';
2
+ import * as path from 'node:path';
3
+ const DEFAULT_DESTRUCTIVE_RULES = [
4
+ {
5
+ pattern: /\bgit\s+push\s+(?:--force-with-lease|--force|-f)(?=\s|$)/i,
6
+ kind: 'force-push',
7
+ label: 'git push --force',
8
+ },
9
+ {
10
+ pattern: /\bgit\s+push\s+origin\s+(?:main|master)(?=\s|$)/i,
11
+ kind: 'origin-main-push',
12
+ label: 'git push origin main',
13
+ },
14
+ { pattern: /\brm\s+-rf\b/i, kind: 'destructive-command', label: 'rm -rf' },
15
+ { pattern: /\bsudo\s+rm\b/i, kind: 'destructive-command', label: 'sudo rm' },
16
+ { pattern: /\bDROP\s+TABLE\b/i, kind: 'destructive-command', label: 'DROP TABLE' },
17
+ { pattern: /\bDROP\s+DATABASE\b/i, kind: 'destructive-command', label: 'DROP DATABASE' },
18
+ ];
19
+ const CITATION_RE = /\[([^\[\]\s:]+):(\d+)\]/g;
20
+ function lineColumnOf(text, index) {
21
+ let line = 1;
22
+ let column = 1;
23
+ for (let i = 0; i < index; i++) {
24
+ if (text.charCodeAt(i) === 10) {
25
+ line++;
26
+ column = 1;
27
+ }
28
+ else {
29
+ column++;
30
+ }
31
+ }
32
+ return { line, column };
33
+ }
34
+ async function countLines(filePath) {
35
+ const data = await fs.readFile(filePath, 'utf8');
36
+ if (data.length === 0)
37
+ return 0;
38
+ const parts = data.split('\n');
39
+ if (parts[parts.length - 1] === '')
40
+ parts.pop();
41
+ return parts.length;
42
+ }
43
+ function withinWorkspace(workspaceRoot, candidate) {
44
+ if (path.isAbsolute(candidate))
45
+ return false;
46
+ const root = path.resolve(workspaceRoot);
47
+ const target = path.resolve(root, candidate);
48
+ const rel = path.relative(root, target);
49
+ if (rel === '')
50
+ return true;
51
+ return !rel.startsWith('..') && !path.isAbsolute(rel);
52
+ }
53
+ function ensureGlobal(pattern) {
54
+ if (pattern.flags.includes('g'))
55
+ return pattern;
56
+ return new RegExp(pattern.source, pattern.flags + 'g');
57
+ }
58
+ export async function verifyOutput(text, options) {
59
+ const signals = [];
60
+ const validateCitations = options.validateCitations !== false;
61
+ if (validateCitations) {
62
+ const citations = [];
63
+ for (const m of text.matchAll(CITATION_RE)) {
64
+ if (m.index === undefined)
65
+ continue;
66
+ citations.push({
67
+ raw: m[0],
68
+ filePath: m[1],
69
+ line: Number(m[2]),
70
+ index: m.index,
71
+ });
72
+ }
73
+ const invalidSignals = [];
74
+ for (const c of citations) {
75
+ const loc = lineColumnOf(text, c.index);
76
+ if (!withinWorkspace(options.workspaceRoot, c.filePath)) {
77
+ invalidSignals.push({
78
+ kind: 'invalid-citation',
79
+ detail: `${c.raw} resolves outside workspace`,
80
+ location: loc,
81
+ severity: 'warn',
82
+ });
83
+ continue;
84
+ }
85
+ const abs = path.resolve(options.workspaceRoot, c.filePath);
86
+ let lineCount;
87
+ try {
88
+ lineCount = await countLines(abs);
89
+ }
90
+ catch {
91
+ invalidSignals.push({
92
+ kind: 'invalid-citation',
93
+ detail: `${c.raw} file not found`,
94
+ location: loc,
95
+ severity: 'warn',
96
+ });
97
+ continue;
98
+ }
99
+ if (c.line < 1 || c.line > lineCount) {
100
+ invalidSignals.push({
101
+ kind: 'invalid-citation',
102
+ detail: `${c.raw} line ${c.line} out of range (file has ${lineCount} lines)`,
103
+ location: loc,
104
+ severity: 'warn',
105
+ });
106
+ }
107
+ }
108
+ if (citations.length > 0 && invalidSignals.length === citations.length) {
109
+ for (const s of invalidSignals)
110
+ s.severity = 'block';
111
+ }
112
+ signals.push(...invalidSignals);
113
+ }
114
+ const rules = options.allowedDestructivePatterns
115
+ ? options.allowedDestructivePatterns.map((p) => ({
116
+ pattern: p,
117
+ kind: 'destructive-command',
118
+ label: p.source,
119
+ }))
120
+ : DEFAULT_DESTRUCTIVE_RULES;
121
+ for (const rule of rules) {
122
+ const re = ensureGlobal(rule.pattern);
123
+ for (const m of text.matchAll(re)) {
124
+ if (m.index === undefined)
125
+ continue;
126
+ const loc = lineColumnOf(text, m.index);
127
+ signals.push({
128
+ kind: rule.kind,
129
+ detail: `${rule.label}: ${m[0]}`,
130
+ location: loc,
131
+ severity: 'block',
132
+ });
133
+ }
134
+ }
135
+ const allowed = !signals.some((s) => s.severity === 'block');
136
+ return { allowed, signals };
137
+ }
138
+ //# sourceMappingURL=citation-verify-hook.js.map
@@ -0,0 +1,112 @@
1
+ import * as fs from 'node:fs/promises';
2
+ import * as path from 'node:path';
3
+ const CITATION_REGEX = /\[([^\]\s]+\.[a-zA-Z0-9]+):(\d+)(?:-(\d+))?(?:#([\w]+))?\]/g;
4
+ /**
5
+ * Parses citation-like strings from a block of text.
6
+ * @param text The text to parse.
7
+ * @returns An array of parsed CitationCheck objects.
8
+ */
9
+ export function parseCitations(text) {
10
+ const matches = text.matchAll(CITATION_REGEX);
11
+ const citations = [];
12
+ for (const match of matches) {
13
+ const raw = match[0];
14
+ const path = match[1];
15
+ const lineStart = match[2];
16
+ const lineEnd = match[3];
17
+ const symbol = match[4];
18
+ if (!path || !lineStart)
19
+ continue;
20
+ citations.push({
21
+ raw,
22
+ path,
23
+ lineStart: parseInt(lineStart, 10),
24
+ lineEnd: lineEnd ? parseInt(lineEnd, 10) : undefined,
25
+ symbol: symbol || undefined,
26
+ status: 'valid',
27
+ });
28
+ }
29
+ return citations;
30
+ }
31
+ /**
32
+ * Parses citations from text and validates them against the filesystem.
33
+ * @param text The text containing citations.
34
+ * @param repoRoot The absolute path to the root of the repository.
35
+ * @returns A promise that resolves to an object containing valid and invalid citations,
36
+ * and the original text with all citation markers removed.
37
+ */
38
+ export async function validateCitations(text, repoRoot) {
39
+ const citations = parseCitations(text);
40
+ const valid = [];
41
+ const invalid = [];
42
+ for (const citation of citations) {
43
+ const fullPath = path.join(repoRoot, citation.path);
44
+ let fileContent;
45
+ try {
46
+ await fs.stat(fullPath);
47
+ }
48
+ catch (error) {
49
+ if (error.code === 'ENOENT') {
50
+ citation.status = 'file_missing';
51
+ invalid.push(citation);
52
+ continue;
53
+ }
54
+ // Re-throw other errors
55
+ throw error;
56
+ }
57
+ try {
58
+ fileContent = await fs.readFile(fullPath, 'utf-8');
59
+ }
60
+ catch (e) {
61
+ citation.status = 'file_missing';
62
+ invalid.push(citation);
63
+ continue;
64
+ }
65
+ const lines = fileContent.split('\n');
66
+ const maxLines = lines.length;
67
+ if (citation.lineStart <= 0 || citation.lineStart > maxLines) {
68
+ citation.status = 'line_out_of_range';
69
+ invalid.push(citation);
70
+ continue;
71
+ }
72
+ if (citation.lineEnd && (citation.lineEnd > maxLines || citation.lineEnd < citation.lineStart)) {
73
+ citation.status = 'line_out_of_range';
74
+ invalid.push(citation);
75
+ continue;
76
+ }
77
+ // Symbol validation is not implemented in this version.
78
+ // We can add it here if needed by parsing the file content (e.g. with AST).
79
+ if (citation.symbol) {
80
+ citation.status = 'symbol_not_found'; // Placeholder for future implementation
81
+ }
82
+ // If we are here, file exists and line numbers are in range.
83
+ citation.status = 'valid';
84
+ valid.push(citation);
85
+ }
86
+ const cleaned = text.replace(CITATION_REGEX, '').trim();
87
+ return { valid, invalid, cleaned };
88
+ }
89
+ /**
90
+ * Checks if a given command string is potentially destructive.
91
+ * This is a safeguard and not exhaustive.
92
+ * @param cmd The command string to check.
93
+ * @returns An object indicating if the command is dangerous and why.
94
+ */
95
+ export function isDestructiveGitCommand(cmd) {
96
+ const checks = [
97
+ { re: /\bgit\s+push\s+(--force|-f)\b/, reason: '`git push --force` can overwrite remote history.' },
98
+ { re: /\bgit\s+push\s+.*--force-with-lease.*\s(main|master)\b/, reason: '`git push --force-with-lease` on a main branch is risky.' },
99
+ { re: /\bgit\s+reset\s+--hard\b/, reason: '`git reset --hard` discards all local changes and commits.' },
100
+ { re: /\bgit\s+clean\s+-[dfx]+/, reason: '`git clean -fd` permanently deletes untracked files and directories.' },
101
+ { re: /\brm\s+-rf\b/, reason: '`rm -rf` can recursively delete files and directories forcefully.' },
102
+ { re: /\bDROP\s+DATABASE\b/i, reason: '`DROP DATABASE` permanently deletes an entire database.' },
103
+ { re: /\bDROP\s+TABLE\b/i, reason: '`DROP TABLE` permanently deletes a table.' },
104
+ ];
105
+ for (const check of checks) {
106
+ if (check.re.test(cmd)) {
107
+ return { dangerous: true, reason: check.reason };
108
+ }
109
+ }
110
+ return { dangerous: false };
111
+ }
112
+ //# sourceMappingURL=citation-verify.js.map
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Pugi hooks MVP — typed event payloads ().
3
+ *
4
+ * This module ships the MINIMAL FIRST PASS of the user-config hook
5
+ * matrix. Two lifecycle events out of the eventual 8 land here:
6
+ *
7
+ * - `SessionStart` — fired once when the REPL boots (`session.ts`).
8
+ * - `PreToolUse` — fired before each tool dispatch
9
+ * (`engine/tool-bridge.ts`). Non-zero exit from a
10
+ * hook with `blocking: true` aborts the dispatch.
11
+ *
12
+ * The remaining 6 events (`PostToolUse`, `UserPromptSubmit`, `Stop`,
13
+ * `SubagentStop`, `PreCompact`, `Notification`) are deferred to a
14
+ * fast-follow PR. The pattern established here — discriminated union
15
+ * payloads + registry-driven dispatch — is the reusable template.
16
+ *
17
+ * Design note (parallel surface): an older surface lives at
18
+ * `apps/pugi-cli/src/core/hooks.ts` and uses a flat-array `hooks: [{
19
+ * event, match, run }]` config shape with per-hook events. THIS module
20
+ * adopts the the upstream tool-style nested `hooks: { EventName: [{ matcher,
21
+ * command }] }` config shape. The two surfaces co-exist intentionally
22
+ * for the MVP — they read different files (`~/.pugi/hooks.json` vs.
23
+ * `~/.pugi/hooks-mvp.json`) so operator configs do not collide. The
24
+ * fast-follow PR consolidates the two readers.
25
+ *
26
+ * Brand voice: ASCII only, no emoji, no em-dashes, no marketing prose.
27
+ */
28
+ /** Events the MVP actually fires. The remaining events live in the */
29
+ /** type but no integration point emits them yet. */
30
+ export const MVP_HOOK_EVENTS = [
31
+ 'SessionStart',
32
+ 'PreToolUse',
33
+ ];
34
+ export const ALL_HOOK_EVENTS_V2 = [
35
+ 'SessionStart',
36
+ 'PreToolUse',
37
+ 'PostToolUse',
38
+ 'UserPromptSubmit',
39
+ 'Stop',
40
+ 'SubagentStop',
41
+ 'PreCompact',
42
+ 'Notification',
43
+ 'WorktreeCreate',
44
+ 'WorktreeRemove',
45
+ ];
46
+ //# sourceMappingURL=events.js.map