@pugi/cli 0.1.0-beta.1 → 0.1.0-beta.100

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/CHANGELOG.md +132 -0
  2. package/LICENSE +1 -1
  3. package/README.md +53 -11
  4. package/THIRD_PARTY_NOTICES.md +40 -0
  5. package/assets/pugi-mascot.ansi +15 -40
  6. package/assets/pugi-prozr2-mascot.ansi +9 -0
  7. package/bin/run.js +33 -1
  8. package/dist/commands/deploy.js +40 -40
  9. package/dist/commands/flatten.js +191 -0
  10. package/dist/commands/jobs-watch.js +201 -0
  11. package/dist/commands/jobs.js +42 -27
  12. package/dist/commands/retro.js +210 -0
  13. package/dist/commands/smoke.js +133 -0
  14. package/dist/core/agent-progress/cleanup.js +134 -0
  15. package/dist/core/agent-progress/schema.js +144 -0
  16. package/dist/core/agent-progress/writer.js +101 -0
  17. package/dist/core/agents/adaptive-router.js +330 -0
  18. package/dist/core/agents/query-decomposer.js +297 -0
  19. package/dist/core/agents/registry.js +3 -3
  20. package/dist/core/approvals/shortcut-resolver.js +98 -0
  21. package/dist/core/artifact-chain/dispatcher.js +148 -0
  22. package/dist/core/artifact-chain/exporter.js +164 -0
  23. package/dist/core/artifact-chain/state.js +243 -0
  24. package/dist/core/artifact-chain/steps.js +169 -0
  25. package/dist/core/ask-user/question.js +92 -0
  26. package/dist/core/audit/audit-trail.js +275 -0
  27. package/dist/core/auth/ensure-authenticated.js +129 -0
  28. package/dist/core/auth/env-provider.js +238 -0
  29. package/dist/core/auto-open-browser.js +4 -4
  30. package/dist/core/auto-update/channels.js +122 -0
  31. package/dist/core/auto-update/checker.js +241 -0
  32. package/dist/core/auto-update/state.js +235 -0
  33. package/dist/core/bare-mode/index.js +107 -0
  34. package/dist/core/bash/redirect.js +281 -0
  35. package/dist/core/bash-classifier.js +436 -40
  36. package/dist/core/checkpoint/resumer.js +149 -0
  37. package/dist/core/checkpoint/rewinder.js +291 -0
  38. package/dist/core/checkpoints/shadow-git.js +670 -0
  39. package/dist/core/citations/parser.js +109 -0
  40. package/dist/core/classifier/yolo-classifier.js +88 -0
  41. package/dist/core/codegraph/db.js +506 -0
  42. package/dist/core/codegraph/decision-store.js +248 -0
  43. package/dist/core/codegraph/detect-repo.js +459 -0
  44. package/dist/core/codegraph/install.js +134 -0
  45. package/dist/core/codegraph/offer-hook.js +220 -0
  46. package/dist/core/codegraph/parser.js +71 -0
  47. package/dist/core/codegraph/types.js +34 -0
  48. package/dist/core/compact/auto-trigger.js +96 -0
  49. package/dist/core/compact/buffer-rewriter.js +115 -0
  50. package/dist/core/compact/summarizer.js +208 -0
  51. package/dist/core/compact/token-counter.js +108 -0
  52. package/dist/core/consensus/anvil-fanout.js +25 -25
  53. package/dist/core/consensus/diff-capture.js +121 -12
  54. package/dist/core/consensus/rubric.js +21 -21
  55. package/dist/core/context/builder.js +6 -6
  56. package/dist/core/context/compaction-events.js +8 -8
  57. package/dist/core/context/compaction.js +31 -31
  58. package/dist/core/context/index.js +15 -8
  59. package/dist/core/context/invariants.js +51 -51
  60. package/dist/core/context/markdown-loader.js +28 -10
  61. package/dist/core/context/markdown-traverse.js +255 -0
  62. package/dist/core/context/pugiignore.js +41 -41
  63. package/dist/core/context/repo-skeleton.js +37 -37
  64. package/dist/core/context/tool-eviction.js +55 -0
  65. package/dist/core/context/watcher.js +32 -32
  66. package/dist/core/context/working-set.js +23 -23
  67. package/dist/core/coordinator/agent-tools.js +77 -0
  68. package/dist/core/coordinator/agent-toolset.js +65 -0
  69. package/dist/core/coordinator/fsm.js +73 -0
  70. package/dist/core/coordinator/mode-fsm.js +70 -0
  71. package/dist/core/cost/rate-card.js +129 -0
  72. package/dist/core/cost/tracker.js +221 -0
  73. package/dist/core/credentials.js +13 -13
  74. package/dist/core/cron/scheduler.js +138 -0
  75. package/dist/core/denial-tracking/index.js +8 -0
  76. package/dist/core/denial-tracking/state.js +264 -0
  77. package/dist/core/diagnostics/probe-runner.js +93 -0
  78. package/dist/core/diagnostics/probes/api.js +46 -0
  79. package/dist/core/diagnostics/probes/auth.js +93 -0
  80. package/dist/core/diagnostics/probes/bare-mode.js +42 -0
  81. package/dist/core/diagnostics/probes/cli-version.js +127 -0
  82. package/dist/core/diagnostics/probes/config.js +72 -0
  83. package/dist/core/diagnostics/probes/denial-tracking.js +57 -0
  84. package/dist/core/diagnostics/probes/disk.js +81 -0
  85. package/dist/core/diagnostics/probes/engine-live.js +46 -0
  86. package/dist/core/diagnostics/probes/git.js +65 -0
  87. package/dist/core/diagnostics/probes/hooks.js +118 -0
  88. package/dist/core/diagnostics/probes/mcp.js +75 -0
  89. package/dist/core/diagnostics/probes/node.js +59 -0
  90. package/dist/core/diagnostics/probes/pnpm.js +36 -0
  91. package/dist/core/diagnostics/probes/pugi-md.js +89 -0
  92. package/dist/core/diagnostics/probes/sandbox.js +72 -0
  93. package/dist/core/diagnostics/probes/session.js +74 -0
  94. package/dist/core/diagnostics/probes/status-snapshot.js +488 -0
  95. package/dist/core/diagnostics/probes/workspace.js +63 -0
  96. package/dist/core/diagnostics/types.js +70 -0
  97. package/dist/core/dispatch/cache-cleanup.js +197 -0
  98. package/dist/core/dispatch/cache-handoff.js +295 -0
  99. package/dist/core/edits/apply-patch-layer-e.js +189 -0
  100. package/dist/core/edits/dispatch.js +333 -7
  101. package/dist/core/edits/format-detector.js +260 -0
  102. package/dist/core/edits/format-matrix.js +26 -0
  103. package/dist/core/edits/fuzzy-ladder.js +650 -0
  104. package/dist/core/edits/index.js +5 -1
  105. package/dist/core/edits/journal.js +199 -0
  106. package/dist/core/edits/layer-a-apply.js +15 -15
  107. package/dist/core/edits/layer-a-fuzzy-apply.js +198 -0
  108. package/dist/core/edits/layer-b-apply.js +9 -9
  109. package/dist/core/edits/layer-c-apply.js +6 -6
  110. package/dist/core/edits/layer-d-ast.js +557 -14
  111. package/dist/core/edits/marker-parser.js +12 -12
  112. package/dist/core/edits/security-gate.js +27 -27
  113. package/dist/core/edits/verify-hook.js +273 -0
  114. package/dist/core/edits/worktree.js +322 -0
  115. package/dist/core/engine/anvil-client.js +214 -26
  116. package/dist/core/engine/auto-compact.js +247 -0
  117. package/dist/core/engine/budgets.js +220 -0
  118. package/dist/core/engine/compact-llm-summarizer.js +124 -0
  119. package/dist/core/engine/context-prefix.js +155 -0
  120. package/dist/core/engine/index.js +1 -1
  121. package/dist/core/engine/intensity.js +163 -0
  122. package/dist/core/engine/intent.js +260 -0
  123. package/dist/core/engine/native-pugi.js +1559 -227
  124. package/dist/core/engine/prompts.js +192 -16
  125. package/dist/core/engine/strip-internal-fields.js +124 -0
  126. package/dist/core/engine/tool-bridge.js +1887 -59
  127. package/dist/core/engine/verification-patterns.js +195 -0
  128. package/dist/core/evaluation/golden-dataset.js +293 -0
  129. package/dist/core/feedback/queue.js +177 -0
  130. package/dist/core/feedback/submitter.js +145 -0
  131. package/dist/core/file-cache.js +113 -1
  132. package/dist/core/flatten/flatten-repo.js +439 -0
  133. package/dist/core/format/osc8-link.js +28 -0
  134. package/dist/core/hook-chains.js +392 -0
  135. package/dist/core/hooks/citation-verify-hook.js +138 -0
  136. package/dist/core/hooks/citation-verify.js +112 -0
  137. package/dist/core/hooks/events.js +46 -0
  138. package/dist/core/hooks/index.js +15 -0
  139. package/dist/core/hooks/registry.js +216 -0
  140. package/dist/core/hooks/runner.js +236 -0
  141. package/dist/core/hooks/v2/event-emitter.js +115 -0
  142. package/dist/core/hooks/v2/executor.js +282 -0
  143. package/dist/core/hooks/v2/index.js +25 -0
  144. package/dist/core/hooks/v2/lifecycle.js +104 -0
  145. package/dist/core/hooks/v2/loader.js +216 -0
  146. package/dist/core/hooks/v2/matcher.js +125 -0
  147. package/dist/core/hooks/v2/trust.js +143 -0
  148. package/dist/core/hooks/v2/types.js +86 -0
  149. package/dist/core/hooks/worktree-events.js +158 -0
  150. package/dist/core/image/renderer.js +71 -0
  151. package/dist/core/init/detector.js +582 -0
  152. package/dist/core/init/template-renderer.js +242 -0
  153. package/dist/core/jobs/registry.js +18 -18
  154. package/dist/core/ledger/results-tsv.js +142 -0
  155. package/dist/core/log-discipline/stdout-redirect.js +51 -0
  156. package/dist/core/lsp/cache.js +105 -0
  157. package/dist/core/lsp/client.js +1229 -0
  158. package/dist/core/lsp/language-detect.js +66 -0
  159. package/dist/core/lsp/post-edit-diagnostics.js +171 -0
  160. package/dist/core/lsp/server-detect.js +173 -0
  161. package/dist/core/lsp/symbol-cache.js +162 -0
  162. package/dist/core/lsp/symbol-tools.js +664 -0
  163. package/dist/core/mcp/client.js +97 -28
  164. package/dist/core/mcp/http-server.js +553 -0
  165. package/dist/core/mcp/orchestrator-config.js +192 -0
  166. package/dist/core/mcp/orchestrator-tools.js +806 -0
  167. package/dist/core/mcp/permission.js +190 -0
  168. package/dist/core/mcp/registry.js +39 -17
  169. package/dist/core/mcp/server-tools.js +219 -0
  170. package/dist/core/mcp/server.js +397 -0
  171. package/dist/core/mcp/trust.js +10 -10
  172. package/dist/core/memory/dual-write.js +416 -0
  173. package/dist/core/memory/passive-extract.js +130 -0
  174. package/dist/core/memory/phase1-kinds.js +20 -0
  175. package/dist/core/memory/secret-scanner.js +304 -0
  176. package/dist/core/memory-sync/queue.js +170 -0
  177. package/dist/core/metrics/extract.js +113 -0
  178. package/dist/core/modes/roo-modes.js +68 -0
  179. package/dist/core/notes/notes-paths.js +113 -0
  180. package/dist/core/notes/notes-recorder.js +140 -0
  181. package/dist/core/notes/notes-writer.js +53 -0
  182. package/dist/core/notes/renderers.js +0 -0
  183. package/dist/core/notes/slug.js +105 -0
  184. package/dist/core/onboarding/ensure-initialized.js +133 -0
  185. package/dist/core/onboarding/marker.js +111 -0
  186. package/dist/core/onboarding/telemetry-state.js +108 -0
  187. package/dist/core/output-style/presets.js +176 -0
  188. package/dist/core/output-style/state.js +185 -0
  189. package/dist/core/path-security.js +287 -5
  190. package/dist/core/permission.js +82 -22
  191. package/dist/core/permissions/auto-classifier.js +124 -0
  192. package/dist/core/permissions/bash-parser.js +371 -0
  193. package/dist/core/permissions/circuit-breaker.js +83 -0
  194. package/dist/core/permissions/constrained-edit.js +91 -0
  195. package/dist/core/permissions/gate.js +278 -0
  196. package/dist/core/permissions/index.js +20 -0
  197. package/dist/core/permissions/mode.js +174 -0
  198. package/dist/core/permissions/network-egress.js +137 -0
  199. package/dist/core/permissions/state.js +241 -0
  200. package/dist/core/permissions/tool-class.js +107 -0
  201. package/dist/core/plan-mode/ui-state.js +51 -0
  202. package/dist/core/plans/plan-artifact.js +721 -0
  203. package/dist/core/policy-limits/etag-store.js +122 -0
  204. package/dist/core/prd-check/parser.js +215 -0
  205. package/dist/core/prd-check/reporter.js +127 -0
  206. package/dist/core/prd-check/session-review.js +557 -0
  207. package/dist/core/prd-check/verifiers.js +223 -0
  208. package/dist/core/prompt-cache/client-cache.js +99 -0
  209. package/dist/core/prompts/assembly.js +29 -0
  210. package/dist/core/prompts/registry.js +364 -0
  211. package/dist/core/pugi-gitignore.js +52 -0
  212. package/dist/core/pugi-md/cc-compat-rules.js +735 -0
  213. package/dist/core/pugi-md/context-injector.js +76 -0
  214. package/dist/core/pugi-md/walk-up.js +207 -0
  215. package/dist/core/python/uv-installer.js +270 -0
  216. package/dist/core/python/uv-resolver.js +83 -0
  217. package/dist/core/rate-limit/narrator.js +146 -0
  218. package/dist/core/recipes/cli-types.js +20 -0
  219. package/dist/core/recipes/loader.js +103 -0
  220. package/dist/core/recipes/runner.js +345 -0
  221. package/dist/core/recipes/schema.js +587 -0
  222. package/dist/core/release-notes/parser.js +241 -0
  223. package/dist/core/release-notes/state.js +116 -0
  224. package/dist/core/repl/ask.js +37 -37
  225. package/dist/core/repl/cancellation.js +26 -26
  226. package/dist/core/repl/cap-warning.js +4 -4
  227. package/dist/core/repl/clipboard-read.js +11 -11
  228. package/dist/core/repl/dispatch-fsm.js +12 -12
  229. package/dist/core/repl/engine-bridge.js +303 -0
  230. package/dist/core/repl/history-search.js +15 -15
  231. package/dist/core/repl/history.js +28 -18
  232. package/dist/core/repl/kill-ring.js +5 -5
  233. package/dist/core/repl/model-pricing.js +135 -0
  234. package/dist/core/repl/privacy-banner.js +22 -22
  235. package/dist/core/repl/session.js +2714 -228
  236. package/dist/core/repl/slash-commands.js +572 -40
  237. package/dist/core/repl/store/index.js +1 -1
  238. package/dist/core/repl/store/jsonl-log.js +22 -22
  239. package/dist/core/repl/store/lockfile.js +10 -10
  240. package/dist/core/repl/store/session-store.js +136 -107
  241. package/dist/core/repl/store/types.js +15 -15
  242. package/dist/core/repl/store/uuid-v7.js +12 -12
  243. package/dist/core/repl/tool-route.js +382 -0
  244. package/dist/core/repl/workspace-context.js +43 -21
  245. package/dist/core/repo-map/build.js +125 -0
  246. package/dist/core/repo-map/cache.js +185 -0
  247. package/dist/core/repo-map/extractor.js +254 -0
  248. package/dist/core/repo-map/formatter.js +145 -0
  249. package/dist/core/repo-map/page-rank.js +105 -0
  250. package/dist/core/repo-map/scanner.js +211 -0
  251. package/dist/core/retro/git-collector.js +251 -0
  252. package/dist/core/retro/health-card.js +25 -0
  253. package/dist/core/retro/metrics.js +342 -0
  254. package/dist/core/retro/narrative.js +249 -0
  255. package/dist/core/retro/plane-collector.js +274 -0
  256. package/dist/core/retro/pr-issue-link.js +65 -0
  257. package/dist/core/retro/types.js +16 -0
  258. package/dist/core/retry-budget/budget.js +284 -0
  259. package/dist/core/retry-budget/index.js +5 -0
  260. package/dist/core/retry-budget/retry-cap.js +74 -0
  261. package/dist/core/routing/lead-worker.js +43 -0
  262. package/dist/core/routing/pre-flight-estimator.js +108 -0
  263. package/dist/core/runs/run-tree.js +103 -0
  264. package/dist/core/sandboxing/adapter.js +29 -0
  265. package/dist/core/sandboxing/index.js +49 -0
  266. package/dist/core/sandboxing/none.js +19 -0
  267. package/dist/core/sandboxing/seatbelt.js +183 -0
  268. package/dist/core/security/injection-scanner.js +367 -0
  269. package/dist/core/security/output-filter.js +418 -0
  270. package/dist/core/session/env-file.js +105 -0
  271. package/dist/core/session/section-budgets.js +140 -0
  272. package/dist/core/session.js +119 -0
  273. package/dist/core/settings.js +378 -5
  274. package/dist/core/share/formatter.js +271 -0
  275. package/dist/core/share/redactor.js +221 -0
  276. package/dist/core/share/uploader.js +267 -0
  277. package/dist/core/skills/defaults.js +457 -0
  278. package/dist/core/skills/loader.js +22 -22
  279. package/dist/core/skills/sources.js +27 -27
  280. package/dist/core/smoke/headless-driver.js +174 -0
  281. package/dist/core/smoke/orchestrator.js +194 -0
  282. package/dist/core/smoke/runner.js +238 -0
  283. package/dist/core/smoke/scenario-parser.js +316 -0
  284. package/dist/core/statusline.js +99 -0
  285. package/dist/core/subagents/dispatcher-real.js +600 -0
  286. package/dist/core/subagents/dispatcher.js +146 -52
  287. package/dist/core/subagents/index.js +19 -6
  288. package/dist/core/subagents/isolation-matrix.js +213 -0
  289. package/dist/core/subagents/spawn.js +19 -4
  290. package/dist/core/telemetry/emitter.js +229 -0
  291. package/dist/core/telemetry/queue.js +251 -0
  292. package/dist/core/theme/context.js +91 -0
  293. package/dist/core/theme/presets.js +228 -0
  294. package/dist/core/theme/state.js +181 -0
  295. package/dist/core/todos/invariant.js +10 -0
  296. package/dist/core/todos/state.js +177 -0
  297. package/dist/core/tool-schema/compressor.js +89 -0
  298. package/dist/core/transport/version-interceptor.js +166 -0
  299. package/dist/core/trust.js +2 -2
  300. package/dist/core/tui/thinking-block.js +64 -0
  301. package/dist/core/vim/keymap.js +288 -0
  302. package/dist/core/vim/state.js +92 -0
  303. package/dist/core/watch-markers/marker-watcher.js +133 -0
  304. package/dist/core/worktree/include-parser.js +249 -0
  305. package/dist/core/worktree-manager/cleanup.js +123 -0
  306. package/dist/core/worktree-manager/manager.js +303 -0
  307. package/dist/index.js +36 -0
  308. package/dist/runtime/bootstrap.js +190 -0
  309. package/dist/runtime/cli.js +4536 -477
  310. package/dist/runtime/commands/agents.js +31 -31
  311. package/dist/runtime/commands/budget.js +5 -5
  312. package/dist/runtime/commands/cancel.js +231 -0
  313. package/dist/runtime/commands/chain.js +489 -0
  314. package/dist/runtime/commands/codegraph-status.js +227 -0
  315. package/dist/runtime/commands/compact.js +297 -0
  316. package/dist/runtime/commands/config.js +74 -40
  317. package/dist/runtime/commands/cost.js +199 -0
  318. package/dist/runtime/commands/delegate.js +312 -0
  319. package/dist/runtime/commands/dispatch.js +126 -0
  320. package/dist/runtime/commands/doctor.js +579 -0
  321. package/dist/runtime/commands/feedback.js +184 -0
  322. package/dist/runtime/commands/hooks.js +187 -0
  323. package/dist/runtime/commands/index-cmd.js +353 -0
  324. package/dist/runtime/commands/init.js +254 -0
  325. package/dist/runtime/commands/lsp.js +368 -0
  326. package/dist/runtime/commands/mcp.js +935 -0
  327. package/dist/runtime/commands/memory.js +582 -0
  328. package/dist/runtime/commands/model.js +237 -0
  329. package/dist/runtime/commands/onboarding.js +275 -0
  330. package/dist/runtime/commands/patch.js +128 -0
  331. package/dist/runtime/commands/permissions.js +112 -0
  332. package/dist/runtime/commands/plan.js +143 -0
  333. package/dist/runtime/commands/prd-check.js +285 -0
  334. package/dist/runtime/commands/privacy.js +17 -17
  335. package/dist/runtime/commands/recipe.js +325 -0
  336. package/dist/runtime/commands/redo-blob-store.js +92 -0
  337. package/dist/runtime/commands/redo.js +361 -0
  338. package/dist/runtime/commands/release-notes.js +229 -0
  339. package/dist/runtime/commands/repo-map.js +95 -0
  340. package/dist/runtime/commands/report.js +299 -0
  341. package/dist/runtime/commands/resume.js +118 -0
  342. package/dist/runtime/commands/review-consensus.js +68 -53
  343. package/dist/runtime/commands/rewind.js +333 -0
  344. package/dist/runtime/commands/roster.js +117 -0
  345. package/dist/runtime/commands/servers.js +236 -0
  346. package/dist/runtime/commands/sessions.js +163 -0
  347. package/dist/runtime/commands/share.js +316 -0
  348. package/dist/runtime/commands/skills.js +31 -31
  349. package/dist/runtime/commands/status.js +186 -0
  350. package/dist/runtime/commands/stickers.js +82 -0
  351. package/dist/runtime/commands/style.js +194 -0
  352. package/dist/runtime/commands/theme.js +196 -0
  353. package/dist/runtime/commands/undo.js +54 -22
  354. package/dist/runtime/commands/update.js +289 -0
  355. package/dist/runtime/commands/vim.js +140 -0
  356. package/dist/runtime/commands/worktree.js +177 -0
  357. package/dist/runtime/commands/worktrees.js +155 -0
  358. package/dist/runtime/deprecation-warning.js +69 -0
  359. package/dist/runtime/engine-exit-code.js +50 -0
  360. package/dist/runtime/headless-repl.js +195 -0
  361. package/dist/runtime/headless.js +548 -0
  362. package/dist/runtime/load-hooks-or-exit.js +71 -0
  363. package/dist/runtime/plan-decompose.js +531 -0
  364. package/dist/runtime/sigint-guard.js +272 -0
  365. package/dist/runtime/stream-renderer.js +195 -0
  366. package/dist/runtime/update-check.js +28 -28
  367. package/dist/runtime/version.js +65 -0
  368. package/dist/runtime/worktree-bootstrap.js +579 -0
  369. package/dist/skills/bundled/batch.js +617 -0
  370. package/dist/skills/bundled/index.js +45 -0
  371. package/dist/skills/bundled/loop.js +358 -0
  372. package/dist/skills/bundled/remember.js +383 -0
  373. package/dist/skills/bundled/simplify.js +289 -0
  374. package/dist/skills/bundled/skillify.js +373 -0
  375. package/dist/skills/bundled/stuck.js +558 -0
  376. package/dist/skills/bundled/verify.js +439 -0
  377. package/dist/testing/vcr.js +486 -0
  378. package/dist/tools/agent-tool.js +229 -0
  379. package/dist/tools/apply-patch.js +556 -0
  380. package/dist/tools/ask-user-question.js +337 -0
  381. package/dist/tools/ask-user.js +115 -0
  382. package/dist/tools/bash.js +624 -46
  383. package/dist/tools/brief.js +224 -0
  384. package/dist/tools/cron.js +433 -0
  385. package/dist/tools/enter-worktree.js +250 -0
  386. package/dist/tools/exit-worktree.js +147 -0
  387. package/dist/tools/file-tools.js +161 -44
  388. package/dist/tools/http-request.js +336 -0
  389. package/dist/tools/lsp-tools.js +565 -0
  390. package/dist/tools/mcp-tool.js +260 -0
  391. package/dist/tools/multi-edit.js +361 -0
  392. package/dist/tools/powershell.js +268 -0
  393. package/dist/tools/registry.js +142 -1
  394. package/dist/tools/server-tools.js +892 -0
  395. package/dist/tools/skill-tool.js +96 -0
  396. package/dist/tools/sleep.js +99 -0
  397. package/dist/tools/synthetic-output.js +133 -0
  398. package/dist/tools/tasks.js +208 -0
  399. package/dist/tools/todo-write.js +184 -0
  400. package/dist/tools/verify-plan-execution.js +295 -0
  401. package/dist/tools/web-fetch-injection-scanner.js +207 -0
  402. package/dist/tools/web-fetch.js +195 -10
  403. package/dist/tools/web-search.js +458 -0
  404. package/dist/tui/agent-progress-card.js +111 -0
  405. package/dist/tui/agent-tree.js +22 -1
  406. package/dist/tui/ask-modal.js +14 -14
  407. package/dist/tui/ask-user-question-chips.js +315 -0
  408. package/dist/tui/ask-user-question-prompt.js +203 -0
  409. package/dist/tui/compact-banner.js +81 -0
  410. package/dist/tui/conversation-pane.js +85 -11
  411. package/dist/tui/cost-table.js +111 -0
  412. package/dist/tui/device-flow.js +2 -2
  413. package/dist/tui/doctor-table.js +46 -0
  414. package/dist/tui/feedback-prompt.js +156 -0
  415. package/dist/tui/input-box.js +247 -32
  416. package/dist/tui/login-picker.js +3 -3
  417. package/dist/tui/markdown-render.js +6 -6
  418. package/dist/tui/multi-file-diff-approval.js +375 -0
  419. package/dist/tui/onboarding-wizard.js +240 -0
  420. package/dist/tui/permissions-picker.js +86 -0
  421. package/dist/tui/render.js +36 -1
  422. package/dist/tui/repl-render.js +405 -32
  423. package/dist/tui/repl-splash-art.js +16 -16
  424. package/dist/tui/repl-splash-mascot.js +48 -24
  425. package/dist/tui/repl-splash.js +22 -22
  426. package/dist/tui/repl.js +136 -43
  427. package/dist/tui/slash-palette.js +6 -6
  428. package/dist/tui/splash.js +2 -2
  429. package/dist/tui/status-bar.js +109 -31
  430. package/dist/tui/status-table.js +7 -0
  431. package/dist/tui/stickers-art.js +136 -0
  432. package/dist/tui/style-table.js +28 -0
  433. package/dist/tui/theme-table.js +29 -0
  434. package/dist/tui/thinking-spinner.js +123 -0
  435. package/dist/tui/tool-stream-pane.js +53 -4
  436. package/dist/tui/update-banner.js +27 -2
  437. package/dist/tui/vim-input.js +267 -0
  438. package/dist/tui/welcome-banner.js +107 -0
  439. package/dist/tui/welcome-data.js +293 -0
  440. package/dist/tui/workspace-context.js +2 -2
  441. package/docs/examples/codegraph.mcp.json +10 -0
  442. package/package.json +25 -7
  443. package/test/scenarios/codegen-create-file.scenario.txt +13 -0
  444. package/test/scenarios/compact-force.scenario.txt +12 -0
  445. package/test/scenarios/identity.scenario.txt +11 -0
  446. package/test/scenarios/persona-handoff.scenario.txt +12 -0
  447. package/test/scenarios/walkback.scenario.txt +12 -0
  448. package/dist/core/engine/compaction-hook.js +0 -154
@@ -16,8 +16,127 @@ export function openSession(root) {
16
16
  pugiDir,
17
17
  eventsPath,
18
18
  enabled,
19
+ verificationLedger: [],
19
20
  };
20
21
  }
22
+ /**
23
+ * PUGI-VERIFY-GATE — append a verification ledger entry. Mutates
24
+ * `session.verificationLedger` in place AND mirrors the entry to
25
+ * the JSONL audit log when the session is enabled. Pure helper
26
+ * (no I/O on disabled sessions); safe to call from sync code
27
+ * paths.
28
+ *
29
+ * The bash tool dispatcher calls this after every shell exec whose
30
+ * head matches a `VERIFICATION_PATTERNS` entry.
31
+ */
32
+ export function recordVerificationCall(session, entry) {
33
+ session.verificationLedger.push(entry);
34
+ if (!session.enabled)
35
+ return;
36
+ appendEvent({
37
+ id: randomUUID(),
38
+ sessionId: session.id,
39
+ timestamp: now(),
40
+ type: 'verification',
41
+ name: 'call_recorded',
42
+ tool: entry.tool,
43
+ command: entry.command,
44
+ exitCode: entry.exitCode,
45
+ tailStderrLen: entry.tailStderr.length,
46
+ }, session.eventsPath);
47
+ }
48
+ /**
49
+ * MVP — fire the `SessionStart` lifecycle event for all hooks
50
+ * declared in `~/.pugi/hooks-mvp.json`. Single-call surface; the REPL
51
+ * boot path invokes this once after `openSession`. Best-effort: any
52
+ * failure (missing config, hook spawn error) is swallowed so a
53
+ * misconfigured hook can never crash the REPL.
54
+ *
55
+ * Returns the number of hooks that fired (0 when no config / no
56
+ * matching hooks). Tests assert on the return value as the
57
+ * single-call invariant.
58
+ */
59
+ export async function fireSessionStartMvp(session) {
60
+ try {
61
+ const { loadHooksConfig, fireHooks } = await import('./hooks/index.js');
62
+ // Defense-in-depth: `loadHooksConfig` is contractually non-null
63
+ // (returns `HooksConfig.empty(path)` when the file is absent), but
64
+ // the dynamic import boundary above can in principle return an
65
+ // unexpected shape if the module is mis-resolved at runtime. Guard
66
+ // the optional-chained `isEmpty()` call so a malformed loader can
67
+ // never raise `TypeError: Cannot read properties of undefined` and
68
+ // crash the REPL boot path. Belt-and-suspenders with the
69
+ // surrounding try/catch — the catch still swallows everything else.
70
+ const config = loadHooksConfig();
71
+ if (!config || config.isEmpty())
72
+ return 0;
73
+ const outcome = await fireHooks({
74
+ config,
75
+ event: 'SessionStart',
76
+ payload: {
77
+ event: 'SessionStart',
78
+ sessionId: session.id,
79
+ workspaceRoot: session.root,
80
+ startedAt: new Date().toISOString(),
81
+ },
82
+ workspaceRoot: session.root,
83
+ });
84
+ return outcome.results.length;
85
+ }
86
+ catch {
87
+ // SessionStart is never blocking — log nothing, return 0. A
88
+ // broken `hooks-mvp.json` is surfaced via `pugi hooks doctor`.
89
+ return 0;
90
+ }
91
+ }
92
+ /**
93
+ * P1 — fire the v2 `SessionStart` event from `~/.pugi/hooks.json`
94
+ * (global) + `<workspaceRoot>/.pugi/hooks.json` (project). Companion to
95
+ * `fireSessionStartMvp`; both surfaces run because they read different
96
+ * config files.
97
+ *
98
+ * Headless by default (no trust prompt) — the v2 trust ledger gates
99
+ * first-run executions. Operators with no prior trust decision will see
100
+ * the SessionStart hook skipped with a `denied by trust ledger` stderr
101
+ * note; running `pugi hooks trust allow <command>` enrolls it.
102
+ *
103
+ * Returns the number of hooks that ran (excluding trust-denied skips).
104
+ * Never throws.
105
+ */
106
+ export async function fireSessionStartV2(session) {
107
+ try {
108
+ const { fireSessionStart } = await import('./hooks/v2/index.js');
109
+ const outcome = await fireSessionStart({
110
+ sessionId: session.id,
111
+ workspaceRoot: session.root,
112
+ transcriptPath: session.eventsPath,
113
+ permissionMode: 'ask',
114
+ });
115
+ return outcome.results.filter((r) => r.exitCode !== -1).length;
116
+ }
117
+ catch {
118
+ return 0;
119
+ }
120
+ }
121
+ /**
122
+ * P1 — fire the v2 `SessionEnd` event. Called by the REPL
123
+ * teardown path. Companion to `fireSessionStartV2`.
124
+ */
125
+ export async function fireSessionEndV2(session) {
126
+ try {
127
+ const { fireSessionEnd } = await import('./hooks/v2/index.js');
128
+ const outcome = await fireSessionEnd({
129
+ sessionId: session.id,
130
+ workspaceRoot: session.root,
131
+ transcriptPath: session.eventsPath,
132
+ permissionMode: 'ask',
133
+ });
134
+ return outcome.results.filter((r) => r.exitCode !== -1).length;
135
+ }
136
+ catch {
137
+ return 0;
138
+ }
139
+ }
21
140
  export function recordCommandStarted(session, command) {
22
141
  if (!session.enabled)
23
142
  return;
@@ -20,6 +20,22 @@ const pugiSettingsSchema = z.object({
20
20
  allow: z.array(z.string()).default([]),
21
21
  deny: z.array(z.string()).default([]),
22
22
  notAutomatic: z.array(z.string()).default([]),
23
+ // task — operator-declared read-only paths. Every
24
+ // edit / write tool call whose target matches one of these
25
+ // patterns is denied with source `readonly_paths`, regardless
26
+ // of the active permission mode. Match grammar mirrors
27
+ // allow/deny: exact path OR tail-* glob. Targets are
28
+ // workspace-relative, no leading slash.
29
+ //
30
+ // Why a dedicated field вместо leaning on a generic deny rule
31
+ // like `deny: ["edit:vendor/x"]`?
32
+ // - Explicit intent surfaces в `pugi doctor` and the REPL
33
+ // ("3 paths read-only") без parsing rule strings.
34
+ // - Covers BOTH the `edit` tool AND the `write` tool with
35
+ // one entry; a generic deny needs two rules.
36
+ // - Survives a future migration of the `permissions` schema
37
+ // because the field carries its own contract.
38
+ readonlyPaths: z.array(z.string()).default([]),
23
39
  })
24
40
  .default({}),
25
41
  privacy: z
@@ -28,6 +44,17 @@ const pugiSettingsSchema = z.object({
28
44
  telemetry: z.enum(['off', 'anonymous', 'community']).default('off'),
29
45
  })
30
46
  .default({}),
47
+ // beta.13 P1 fix: ui.cyberZoo gates the cyber-zoo splash +
48
+ // ambient art in the REPL. Schema must declare the key explicitly
49
+ // because Zod's strip pass swallows unknown keys, which is how the
50
+ // initial `pugi init` write (which serialises `ui.cyberZoo`) was
51
+ // bypassed by the runtime reader — the value never made it past the
52
+ // schema gate so admin-api always saw the historical 'on' default.
53
+ ui: z
54
+ .object({
55
+ cyberZoo: z.enum(['on', 'off']).default('on'),
56
+ })
57
+ .default({}),
31
58
  artifacts: z
32
59
  .object({
33
60
  defaultPath: z.string().default('.pugi/artifacts'),
@@ -38,6 +65,12 @@ const pugiSettingsSchema = z.object({
38
65
  // fetcher. Default-off matches the spec posture; the schema must
39
66
  // declare it explicitly because Zod's strict-pass strips unknown
40
67
  // keys and would silently swallow the operator's intent.
68
+ //
69
+ // β1b T4 : added `web.search.enabled` to gate the
70
+ // Brave-Search-backed `web_search` tool. Distinct from `web.fetch`
71
+ // because search queries themselves are an egress event that can
72
+ // leak operator intent — an operator may want fetch without
73
+ // implicitly enabling search-as-egress.
41
74
  web: z
42
75
  .object({
43
76
  fetch: z
@@ -45,15 +78,355 @@ const pugiSettingsSchema = z.object({
45
78
  enabled: z.boolean().optional(),
46
79
  })
47
80
  .optional(),
81
+ search: z
82
+ .object({
83
+ enabled: z.boolean().optional(),
84
+ })
85
+ .optional(),
86
+ })
87
+ .optional(),
88
+ // β7 L9 — per-language LSP toggle. When omitted, every supported
89
+ // server is available subject to binary detection on PATH. When
90
+ // present, only languages set to `true` are launched (false silently
91
+ // skips that language even if the binary is installed). Use this in
92
+ // workspaces where a heavyweight server (rust-analyzer indexing a
93
+ // monorepo, pyright on a fresh venv) wastes resources for the
94
+ // current task. The `pugi lsp servers` subcommand surfaces the
95
+ // current toggle state per server.
96
+ //
97
+ // Schema is intentionally permissive (`optional()` on the section AND
98
+ // on every per-language flag) so a partial config keeps the
99
+ // backwards-compatible "every language enabled" default.
100
+ lsp: z
101
+ .object({
102
+ typescript: z.boolean().optional(),
103
+ javascript: z.boolean().optional(),
104
+ python: z.boolean().optional(),
105
+ go: z.boolean().optional(),
106
+ rust: z.boolean().optional(),
107
+ // post-edit auto-diagnostics. When `true`,
108
+ // a successful `edit`/`write`/`multi_edit` triggers a diagnostic
109
+ // pull on the touched file(s) and the result is appended to the
110
+ // tool envelope so the model can self-correct in the same turn.
111
+ // Off by default — the cold-start of `typescript-language-server`
112
+ // is heavy enough that we opt in explicitly until dogfood proves
113
+ // the throughput trade is worth it. Also enabled via env var
114
+ // `PUGI_LSP_POST_EDIT=1` for CI / one-off operator probes.
115
+ postEditDiagnostics: z.boolean().optional(),
116
+ })
117
+ .optional(),
118
+ // β1 Pl9 — per-command budget overrides. Optional. Partial
119
+ // overrides merge against the β1 defaults in
120
+ // `core/engine/budgets.ts::beta1DefaultBudgets`. The schema is
121
+ // intentionally loose at the leaf (positive integers) so a typo lands
122
+ // a deterministic `BudgetConfigError` at `resolveBudget()` instead of
123
+ // a Zod parse error two layers up.
124
+ // task — operator-customizable status line.
125
+ // When `command` is set, Pugi spawns it on each turn boundary with
126
+ // a single JSON document on stdin (see `statusline.ts` for the
127
+ // schema). The command's stdout (first non-empty line, trimmed) is
128
+ // displayed in the Ink Footer. Failures are non-fatal — they emit
129
+ // a structured log line and the footer falls back to the default.
130
+ // Mirrors CC's `statusLine` config so cross-tool muscle memory
131
+ // carries over для operators who lived in CC first.
132
+ statusLine: z
133
+ .object({
134
+ command: z.string().min(1),
135
+ timeoutMs: z.number().int().positive().max(5000).default(500),
136
+ })
137
+ .optional(),
138
+ // PR E (2026-06-05): auto-compact configuration surface. Mirrors
139
+ // `AutoCompactConfig` in `core/engine/budgets.ts`. The schema is
140
+ // intentionally permissive (`optional()` on every leaf) so a partial
141
+ // override merges against the defaults. `summaryMode` enum is closed
142
+ // so a typo lands a Zod parse error at load time instead of silently
143
+ // ignoring the operator's intent.
144
+ //
145
+ // Operator overrides:
146
+ // { "autoCompact": { "summaryMode": "stats" } } — disable LLM
147
+ // { "autoCompact": { "summaryModel": "claude-haiku" } } — pin model
148
+ // { "autoCompact": { "summaryFallbackToStats": false } } — strict
149
+ autoCompact: z
150
+ .object({
151
+ enabled: z.boolean().optional(),
152
+ thresholdRatio: z.number().positive().max(1).optional(),
153
+ summaryMode: z.enum(['stats', 'llm']).optional(),
154
+ summaryModel: z.string().min(1).optional(),
155
+ summaryFallbackToStats: z.boolean().optional(),
156
+ })
157
+ .optional(),
158
+ // PR F (2026-06-05): Obsidian-style notes. Three surfaces:
159
+ // - session journal `.pugi/notes/sessions/<id>.md`
160
+ // - file concept notes `.pugi/notes/files/<slug>.md`
161
+ // - daily journal `.pugi/journal/YYYY-MM-DD.md`
162
+ // Default-on so operators get notes the moment they upgrade.
163
+ // Disable per-workspace: `{ "notes": { "enabled": false } }`.
164
+ // Disable per-invocation: `PUGI_NOTES_DISABLE=1`.
165
+ notes: z
166
+ .object({
167
+ enabled: z.boolean().optional(),
168
+ })
169
+ .optional(),
170
+ budgets: z
171
+ .object({
172
+ code: z
173
+ .object({ maxTokens: z.number().int().positive().optional(), maxToolCalls: z.number().int().positive().optional() })
174
+ .optional(),
175
+ fix: z
176
+ .object({ maxTokens: z.number().int().positive().optional(), maxToolCalls: z.number().int().positive().optional() })
177
+ .optional(),
178
+ build: z
179
+ .object({ maxTokens: z.number().int().positive().optional(), maxToolCalls: z.number().int().positive().optional() })
180
+ .optional(),
181
+ plan: z
182
+ .object({ maxTokens: z.number().int().positive().optional(), maxToolCalls: z.number().int().positive().optional() })
183
+ .optional(),
184
+ explain: z
185
+ .object({ maxTokens: z.number().int().positive().optional(), maxToolCalls: z.number().int().positive().optional() })
186
+ .optional(),
187
+ review_triple: z
188
+ .object({ maxTokens: z.number().int().positive().optional(), maxToolCalls: z.number().int().positive().optional() })
189
+ .optional(),
190
+ })
191
+ .optional(),
192
+ // #24 (CEO P1) — hook chains. First-class config
193
+ // for `PostToolUseFailure` + `TaskCompleted` event chains. The shape
194
+ // is intentionally loose (`z.any()` at the leaf) because the
195
+ // canonical reader lives in `core/hook-chains.ts` where the
196
+ // matcher/run/timeoutMs grammar is validated. Declaring the key here
197
+ // keeps Zod's strip-pass from swallowing it before the chain reader
198
+ // sees it. See `hook-chains.ts` for the full schema.
199
+ hooks: z.any().optional(),
200
+ // PUGI-260 — persistent default for the 1M context tier opt-in.
201
+ // `pugi config set context.tier 1m` writes this; per-invocation
202
+ // `--context-tier=...` flags override it. When omitted, the CLI
203
+ // sends no `contextTier` field на the wire (server treats as
204
+ // `standard` routing). The closed enum mirrors the CLI flag и the
205
+ // admin-api DTO; an unrecognised value triggers a Zod parse error
206
+ // at load time rather than a silent fallback.
207
+ context: z
208
+ .object({
209
+ tier: z.enum(['1m', 'standard']).optional(),
210
+ })
211
+ .optional(),
212
+ // PUGI-487 - `pugi --worktree` flag governance.
213
+ //
214
+ // Two knobs control the user-facing --worktree flag introduced for
215
+ // parity with parallel-agent isolation patterns in other coding
216
+ // CLIs:
217
+ //
218
+ // - `baseRef`: which ref the new worktree branches FROM.
219
+ // `'fresh'` (default) resolves origin/<default-branch> so each
220
+ // parallel session starts from a clean trunk.
221
+ // `'head'` carries the operator's current local HEAD (including
222
+ // unpushed work) into the new tree.
223
+ //
224
+ // - `cleanupPeriodDays`: integer days. The daily sweep removes
225
+ // user-facing worktrees older than N days that have no
226
+ // uncommitted, untracked, or unpushed state. Default 7 mirrors
227
+ // a one-work-week window. Set to 0 to disable auto-cleanup.
228
+ //
229
+ // Both knobs are optional - the consumers (`bootstrapWorktree`,
230
+ // `runUserWorktreeCleanup`) carry their own defaults so a missing
231
+ // section produces standard behaviour.
232
+ worktree: z
233
+ .object({
234
+ baseRef: z.enum(['fresh', 'head']).optional(),
235
+ cleanupPeriodDays: z.number().int().min(0).max(365).optional(),
236
+ })
237
+ .optional(),
238
+ // Trust Sprint item 6 — bash sandbox adapter selection.
239
+ //
240
+ // `none` — passthrough (existing behaviour, default).
241
+ // `macOS-seatbelt` — wraps spawn calls in `/usr/bin/sandbox-exec`
242
+ // with a profile that allows reads anywhere,
243
+ // denies writes outside workspace + ~/.pugi,
244
+ // and permits standard network egress so
245
+ // `npm install` / `git fetch` still work.
246
+ // `docker` — Linux fallback (NOT shipped in this PR;
247
+ // accepted in the schema so settings.json
248
+ // does not error when operators forward-look
249
+ // at the keyword. Adapter throws at boot if
250
+ // selected today).
251
+ //
252
+ // The bash classifier denylist + permission FSM remain in force on
253
+ // top of the sandbox. This is defence-in-depth: the sandbox bounds
254
+ // what a tool CAN do; the classifier bounds what a tool TRIES.
255
+ bash: z
256
+ .object({
257
+ sandbox: z.enum(['none', 'macOS-seatbelt', 'docker']).optional(),
48
258
  })
49
259
  .optional(),
50
260
  });
51
- export function loadSettings(root) {
261
+ /**
262
+ * #20 — the upstream tool drop-in compat ingest.
263
+ *
264
+ * Operators migrating from the upstream tool typically keep a `.claude/`
265
+ * directory at workspace root with settings.json, slash commands,
266
+ * and ambient guidance files. We honour the existence of that
267
+ * directory and mirror the subset of keys that map cleanly onto
268
+ * Pugi's own settings surface — Pugi values ALWAYS win on conflict
269
+ * (the operator opted into Pugi as their primary), CC fills gaps.
270
+ *
271
+ * Opt-out: `PUGI_CC_COMPAT_DISABLE=1` short-circuits the merger and
272
+ * loads only `.pugi/settings.json` (or the empty default).
273
+ *
274
+ * Key mirror table:
275
+ * - `permissions.defaultMode` → `permissions.mode`
276
+ * (CC values map: `acceptEdits|plan|bypassPermissions|default` →
277
+ * `acceptEdits|plan|bypassPermissions|ask`).
278
+ * - `permissions.allow` → `permissions.allow` (concatenated, deduped).
279
+ * - `permissions.deny` → `permissions.deny` (concatenated, deduped).
280
+ * - `enabledPlugins` → ignored (CC-only concept; Pugi has its own
281
+ * plugin surface and we do not want to silently activate them).
282
+ * - `hooks` → currently ignored. Pugi's hook system is
283
+ * managed via `apps/pugi-cli/src/core/hooks/`; future work can
284
+ * wire CC hook entries through that bridge.
285
+ *
286
+ * Unknown CC keys are dropped on the floor by Zod's strip semantics
287
+ * just like the existing PUGI settings path — we never warn on
288
+ * unrecognised CC keys, because the CC surface is wider and we want
289
+ * fallthrough to be silent (operator does not need a stream of "we
290
+ * skipped this CC concept" warnings on every CLI invocation).
291
+ */
292
+ const ccPermissionsSchema = z
293
+ .object({
294
+ defaultMode: z.string().optional(),
295
+ allow: z.array(z.string()).optional(),
296
+ deny: z.array(z.string()).optional(),
297
+ })
298
+ .passthrough()
299
+ .optional();
300
+ const ccSettingsSchema = z
301
+ .object({
302
+ permissions: ccPermissionsSchema,
303
+ enabledPlugins: z.unknown().optional(),
304
+ hooks: z.unknown().optional(),
305
+ })
306
+ .passthrough();
307
+ /**
308
+ * Env var that disables ingest entirely. Useful for CI
309
+ * sandboxes where a stray `.claude/` from a parent checkout could
310
+ * otherwise leak permissions into Pugi.
311
+ */
312
+ export const CC_COMPAT_DISABLE_ENV = 'PUGI_CC_COMPAT_DISABLE';
313
+ /**
314
+ * Map a CC `permissions.defaultMode` to the closest Pugi permission
315
+ * mode. Unknown / missing values map to `undefined` so the caller
316
+ * keeps Pugi's own default.
317
+ *
318
+ * CC's `default` mode = "ask the user for each tool" which is Pugi's
319
+ * `ask` mode. `acceptEdits` / `plan` / `bypassPermissions` map 1:1.
320
+ */
321
+ export function mapCcPermissionMode(mode) {
322
+ if (typeof mode !== 'string')
323
+ return undefined;
324
+ switch (mode) {
325
+ case 'acceptEdits':
326
+ return 'acceptEdits';
327
+ case 'plan':
328
+ return 'plan';
329
+ case 'bypassPermissions':
330
+ return 'bypassPermissions';
331
+ case 'default':
332
+ return 'ask';
333
+ default:
334
+ return undefined;
335
+ }
336
+ }
337
+ /**
338
+ * Merge a parsed CC settings object into a Pugi settings object.
339
+ * Pugi ALWAYS wins on conflict; CC values fill gaps only.
340
+ */
341
+ export function mergeCcIntoPugi(pugi, cc, opts) {
342
+ const merged = {
343
+ ...pugi,
344
+ permissions: { ...pugi.permissions },
345
+ };
346
+ const pugiWroteMode = pugiPermissionKeyPresent(opts.pugiRawJson, 'mode');
347
+ if (!pugiWroteMode) {
348
+ const ccMode = mapCcPermissionMode(cc.permissions?.defaultMode);
349
+ if (ccMode)
350
+ merged.permissions.mode = ccMode;
351
+ }
352
+ if (Array.isArray(cc.permissions?.allow)) {
353
+ merged.permissions.allow = dedupeKeepFirst([
354
+ ...pugi.permissions.allow,
355
+ ...cc.permissions.allow,
356
+ ]);
357
+ }
358
+ if (Array.isArray(cc.permissions?.deny)) {
359
+ merged.permissions.deny = dedupeKeepFirst([
360
+ ...pugi.permissions.deny,
361
+ ...cc.permissions.deny,
362
+ ]);
363
+ }
364
+ // `enabledPlugins` and `hooks` are intentionally NOT mirrored. See
365
+ // the doc-block above for rationale.
366
+ void opts.pugiSettingsExisted;
367
+ return merged;
368
+ }
369
+ function pugiPermissionKeyPresent(raw, key) {
370
+ if (!raw || typeof raw !== 'object')
371
+ return false;
372
+ const permissions = raw.permissions;
373
+ if (!permissions || typeof permissions !== 'object')
374
+ return false;
375
+ return Object.prototype.hasOwnProperty.call(permissions, key);
376
+ }
377
+ function dedupeKeepFirst(items) {
378
+ const seen = new Set();
379
+ const out = [];
380
+ for (const item of items) {
381
+ if (seen.has(item))
382
+ continue;
383
+ seen.add(item);
384
+ out.push(item);
385
+ }
386
+ return out;
387
+ }
388
+ /**
389
+ * Read + parse `.claude/settings.json` at `root`. Returns `undefined`
390
+ * when the file is absent, malformed, or the operator has opted out
391
+ * via `PUGI_CC_COMPAT_DISABLE=1`. Never throws — a broken CC settings
392
+ * file degrades to "no ingest", not a Pugi boot crash.
393
+ */
394
+ export function loadCcSettings(root, env = process.env) {
395
+ if (env[CC_COMPAT_DISABLE_ENV] === '1')
396
+ return undefined;
397
+ const ccPath = resolve(root, '.claude/settings.json');
398
+ if (!existsSync(ccPath))
399
+ return undefined;
400
+ let parsed;
401
+ try {
402
+ parsed = JSON.parse(readFileSync(ccPath, 'utf8'));
403
+ }
404
+ catch {
405
+ return undefined;
406
+ }
407
+ const result = ccSettingsSchema.safeParse(parsed);
408
+ if (!result.success)
409
+ return undefined;
410
+ return result.data;
411
+ }
412
+ export function loadSettings(root, env = process.env) {
52
413
  const settingsPath = resolve(root, '.pugi/settings.json');
53
- if (!existsSync(settingsPath)) {
54
- return pugiSettingsSchema.parse({});
414
+ const pugiExists = existsSync(settingsPath);
415
+ let pugiRawJson = undefined;
416
+ let pugi;
417
+ if (pugiExists) {
418
+ pugiRawJson = JSON.parse(readFileSync(settingsPath, 'utf8'));
419
+ pugi = pugiSettingsSchema.parse(pugiRawJson);
420
+ }
421
+ else {
422
+ pugi = pugiSettingsSchema.parse({});
55
423
  }
56
- const parsed = JSON.parse(readFileSync(settingsPath, 'utf8'));
57
- return pugiSettingsSchema.parse(parsed);
424
+ const cc = loadCcSettings(root, env);
425
+ if (!cc)
426
+ return pugi;
427
+ return mergeCcIntoPugi(pugi, cc, {
428
+ pugiSettingsExisted: pugiExists,
429
+ pugiRawJson,
430
+ });
58
431
  }
59
432
  //# sourceMappingURL=settings.js.map