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

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 (411) 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 +1792 -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/cron.js +433 -0
  351. package/dist/tools/enter-worktree.js +250 -0
  352. package/dist/tools/exit-worktree.js +147 -0
  353. package/dist/tools/file-tools.js +161 -44
  354. package/dist/tools/lsp-tools.js +377 -1
  355. package/dist/tools/mcp-tool.js +260 -0
  356. package/dist/tools/multi-edit.js +361 -0
  357. package/dist/tools/powershell.js +268 -0
  358. package/dist/tools/registry.js +99 -4
  359. package/dist/tools/skill-tool.js +96 -0
  360. package/dist/tools/sleep.js +99 -0
  361. package/dist/tools/synthetic-output.js +133 -0
  362. package/dist/tools/tasks.js +208 -0
  363. package/dist/tools/todo-write.js +184 -0
  364. package/dist/tools/verify-plan-execution.js +295 -0
  365. package/dist/tools/web-fetch-injection-scanner.js +207 -0
  366. package/dist/tools/web-fetch.js +195 -10
  367. package/dist/tools/web-search.js +458 -0
  368. package/dist/tui/agent-progress-card.js +111 -0
  369. package/dist/tui/agent-tree.js +11 -1
  370. package/dist/tui/ask-modal.js +14 -14
  371. package/dist/tui/ask-user-question-chips.js +315 -0
  372. package/dist/tui/ask-user-question-prompt.js +203 -0
  373. package/dist/tui/compact-banner.js +81 -0
  374. package/dist/tui/conversation-pane.js +85 -11
  375. package/dist/tui/cost-table.js +111 -0
  376. package/dist/tui/device-flow.js +2 -2
  377. package/dist/tui/doctor-table.js +46 -0
  378. package/dist/tui/feedback-prompt.js +156 -0
  379. package/dist/tui/input-box.js +247 -32
  380. package/dist/tui/login-picker.js +3 -3
  381. package/dist/tui/markdown-render.js +6 -6
  382. package/dist/tui/multi-file-diff-approval.js +375 -0
  383. package/dist/tui/onboarding-wizard.js +240 -0
  384. package/dist/tui/permissions-picker.js +86 -0
  385. package/dist/tui/render.js +36 -1
  386. package/dist/tui/repl-render.js +176 -25
  387. package/dist/tui/repl-splash-art.js +16 -16
  388. package/dist/tui/repl-splash-mascot.js +48 -24
  389. package/dist/tui/repl-splash.js +22 -22
  390. package/dist/tui/repl.js +125 -45
  391. package/dist/tui/slash-palette.js +6 -6
  392. package/dist/tui/splash.js +2 -2
  393. package/dist/tui/status-bar.js +109 -31
  394. package/dist/tui/status-table.js +7 -0
  395. package/dist/tui/stickers-art.js +136 -0
  396. package/dist/tui/style-table.js +28 -0
  397. package/dist/tui/theme-table.js +29 -0
  398. package/dist/tui/thinking-spinner.js +123 -0
  399. package/dist/tui/tool-stream-pane.js +53 -4
  400. package/dist/tui/update-banner.js +27 -2
  401. package/dist/tui/vim-input.js +267 -0
  402. package/dist/tui/welcome-banner.js +107 -0
  403. package/dist/tui/welcome-data.js +293 -0
  404. package/dist/tui/workspace-context.js +2 -2
  405. package/package.json +31 -16
  406. package/test/scenarios/codegen-create-file.scenario.txt +13 -0
  407. package/test/scenarios/compact-force.scenario.txt +12 -0
  408. package/test/scenarios/identity.scenario.txt +12 -0
  409. package/test/scenarios/persona-handoff.scenario.txt +12 -0
  410. package/test/scenarios/walkback.scenario.txt +12 -0
  411. package/dist/core/engine/compaction-hook.js +0 -154
@@ -0,0 +1,242 @@
1
+ export const STYLE_WORD_CAPS = Object.freeze({
2
+ minimal: 500,
3
+ standard: 1500,
4
+ detailed: 3500,
5
+ });
6
+ /** Render PUGI.md content for the requested tier. */
7
+ export function renderPugiMd(detection, options) {
8
+ const sections = [];
9
+ const style = options.style;
10
+ const now = options.now ?? (() => new Date());
11
+ sections.push(renderHeader(detection, now()));
12
+ sections.push(renderStack(detection));
13
+ sections.push(renderFrameworks(detection, style));
14
+ if (style !== 'minimal') {
15
+ sections.push(renderTesting(detection));
16
+ sections.push(renderCommitConvention(detection));
17
+ sections.push(renderMonorepo(detection));
18
+ sections.push(renderAiConfigs(detection, style));
19
+ sections.push(renderCommands(detection));
20
+ sections.push(renderProjectLayout(detection));
21
+ }
22
+ if (style === 'detailed') {
23
+ sections.push(renderContributorChecklist());
24
+ }
25
+ sections.push(renderFooter());
26
+ const body = sections.filter((s) => s.length > 0).join('\n\n');
27
+ const trimmed = enforceWordCap(body, STYLE_WORD_CAPS[style]);
28
+ return Object.freeze({
29
+ content: trimmed.endsWith('\n') ? trimmed : `${trimmed}\n`,
30
+ wordCount: countWords(trimmed),
31
+ style,
32
+ });
33
+ }
34
+ /* ------------------------------------------------------------------ */
35
+ /* Section renderers */
36
+ /* ------------------------------------------------------------------ */
37
+ function renderHeader(detection, now) {
38
+ const name = detection.stack.nodeName ?? deriveName(detection.cwd);
39
+ const date = now.toISOString().slice(0, 10);
40
+ const lines = [
41
+ `# ${name} — AI Context (PUGI.md)`,
42
+ '',
43
+ `Generated by \`pugi init\` on ${date}.`,
44
+ 'Pugi reads this file on every session start. Keep it short and current —',
45
+ 'if a section conflicts with the code, the code wins. Edit freely.',
46
+ ];
47
+ return lines.join('\n');
48
+ }
49
+ function renderStack(detection) {
50
+ const lines = ['## Stack', ''];
51
+ const stack = detection.stack;
52
+ if (stack.kind === 'unknown') {
53
+ lines.push('No language manifest detected at the workspace root.');
54
+ if (detection.languages.length > 0) {
55
+ lines.push('');
56
+ lines.push(`Primary file extensions: ${detection.languages.join(', ')}.`);
57
+ }
58
+ return lines.join('\n');
59
+ }
60
+ const labelMap = {
61
+ node: 'Node.js / JavaScript / TypeScript',
62
+ python: 'Python',
63
+ rust: 'Rust',
64
+ go: 'Go',
65
+ ruby: 'Ruby',
66
+ php: 'PHP',
67
+ java: 'Java / Kotlin (JVM)',
68
+ };
69
+ lines.push(`- Primary: ${labelMap[stack.kind] ?? stack.kind}`);
70
+ if (stack.packageManager) {
71
+ lines.push(`- Package manager: ${stack.packageManager}`);
72
+ }
73
+ if (stack.manifests.length > 0) {
74
+ lines.push(`- Manifests: ${stack.manifests.join(', ')}`);
75
+ }
76
+ if (detection.languages.length > 0) {
77
+ lines.push(`- Languages observed: ${detection.languages.join(', ')}`);
78
+ }
79
+ return lines.join('\n');
80
+ }
81
+ function renderFrameworks(detection, style) {
82
+ if (detection.frameworks.length === 0)
83
+ return '';
84
+ const lines = ['## Frameworks', ''];
85
+ for (const fw of detection.frameworks) {
86
+ if (style === 'detailed') {
87
+ lines.push(`- **${fw.name}** — ${fw.evidence}`);
88
+ }
89
+ else {
90
+ lines.push(`- ${fw.name}`);
91
+ }
92
+ }
93
+ return lines.join('\n');
94
+ }
95
+ function renderTesting(detection) {
96
+ if (detection.testFrameworks.length === 0)
97
+ return '';
98
+ const lines = ['## Testing', ''];
99
+ lines.push(`Detected: ${detection.testFrameworks.join(', ')}.`);
100
+ return lines.join('\n');
101
+ }
102
+ function renderCommitConvention(detection) {
103
+ const lines = ['## Commit conventions', ''];
104
+ switch (detection.commitConvention) {
105
+ case 'conventional':
106
+ lines.push('Recent history follows Conventional Commits (`feat:`, `fix:`, `chore:`...).');
107
+ lines.push('Keep new commits in that style — auto-changelog tooling parses the prefix.');
108
+ break;
109
+ case 'gitmoji':
110
+ lines.push('Recent history uses gitmoji prefixes (`:sparkles:`, `:bug:`...).');
111
+ lines.push('Keep new commits in that style for visual consistency.');
112
+ break;
113
+ case 'freeform':
114
+ lines.push('Commits are freeform — no detected prefix convention.');
115
+ lines.push('Pick a short imperative subject line and keep body wrapped at 72 cols.');
116
+ break;
117
+ case 'unknown':
118
+ default:
119
+ lines.push('No commit history detected yet. Pick a convention (recommended: Conventional Commits).');
120
+ break;
121
+ }
122
+ return lines.join('\n');
123
+ }
124
+ function renderMonorepo(detection) {
125
+ if (!detection.monorepo)
126
+ return '';
127
+ const lines = ['## Monorepo', ''];
128
+ const kindLabel = {
129
+ pnpm: 'pnpm workspaces',
130
+ lerna: 'Lerna',
131
+ turborepo: 'Turborepo',
132
+ nx: 'Nx',
133
+ 'yarn-workspaces': 'Yarn / npm workspaces',
134
+ };
135
+ lines.push(`- Layout: ${kindLabel[detection.monorepo.kind] ?? detection.monorepo.kind} (${detection.monorepo.file})`);
136
+ return lines.join('\n');
137
+ }
138
+ function renderAiConfigs(detection, style) {
139
+ if (detection.aiConfigs.length === 0)
140
+ return '';
141
+ const lines = ['## Existing AI configs', ''];
142
+ for (const cfg of detection.aiConfigs) {
143
+ lines.push(`- \`${cfg.path}\``);
144
+ }
145
+ if (style === 'detailed') {
146
+ for (const cfg of detection.aiConfigs) {
147
+ if (!cfg.excerpt)
148
+ continue;
149
+ lines.push('');
150
+ lines.push(`### ${cfg.name} excerpt`);
151
+ lines.push('');
152
+ lines.push('```');
153
+ const trimmed = cfg.excerpt.split('\n').slice(0, 20).join('\n');
154
+ lines.push(trimmed);
155
+ lines.push('```');
156
+ }
157
+ }
158
+ return lines.join('\n');
159
+ }
160
+ function renderCommands(detection) {
161
+ if (detection.scripts.length === 0)
162
+ return '';
163
+ const lines = ['## Commands', ''];
164
+ const pm = detection.stack.packageManager ?? 'npm';
165
+ lines.push('```bash');
166
+ for (const entry of detection.scripts) {
167
+ lines.push(commandLine(entry, pm));
168
+ }
169
+ lines.push('```');
170
+ return lines.join('\n');
171
+ }
172
+ function commandLine(entry, pm) {
173
+ const runner = pm === 'pnpm' || pm === 'yarn' || pm === 'bun' ? pm : 'npm run';
174
+ if (runner === 'npm run')
175
+ return `npm run ${entry.name}`;
176
+ return `${runner} ${entry.name}`;
177
+ }
178
+ function renderProjectLayout(detection) {
179
+ if (detection.topLevelDirs.length === 0)
180
+ return '';
181
+ const lines = ['## Project layout', ''];
182
+ for (const dir of detection.topLevelDirs) {
183
+ lines.push(`- \`${dir}/\``);
184
+ }
185
+ return lines.join('\n');
186
+ }
187
+ function renderContributorChecklist() {
188
+ return [
189
+ '## Contributor checklist',
190
+ '',
191
+ '- Read this file + any in-repo ARCHITECTURE / DESIGN docs before code changes.',
192
+ '- Run the project test suite before opening a PR.',
193
+ '- Keep secrets in `.env` (never commit).',
194
+ '- Update this file when stack / framework / commands change.',
195
+ ].join('\n');
196
+ }
197
+ function renderFooter() {
198
+ return [
199
+ '## How Pugi uses this file',
200
+ '',
201
+ 'On every session, Pugi loads this file as ambient context.',
202
+ 'Edit it like a README aimed at an AI engineer joining your team.',
203
+ ].join('\n');
204
+ }
205
+ /* ------------------------------------------------------------------ */
206
+ /* Word-cap enforcement */
207
+ /* ------------------------------------------------------------------ */
208
+ function countWords(text) {
209
+ const trimmed = text.trim();
210
+ if (trimmed.length === 0)
211
+ return 0;
212
+ return trimmed.split(/\s+/).length;
213
+ }
214
+ function enforceWordCap(body, cap) {
215
+ const current = countWords(body);
216
+ if (current <= cap)
217
+ return body;
218
+ // Greedy truncation: keep paragraphs in order until cap is reached,
219
+ // then append a truncation marker so the reader understands the file
220
+ // was clipped (deterministic).
221
+ const paragraphs = body.split(/\n{2,}/);
222
+ const kept = [];
223
+ let used = 0;
224
+ for (const paragraph of paragraphs) {
225
+ const words = countWords(paragraph);
226
+ if (used + words > cap)
227
+ break;
228
+ kept.push(paragraph);
229
+ used += words;
230
+ }
231
+ kept.push(`<!-- truncated to fit ${cap}-word cap -->`);
232
+ return kept.join('\n\n');
233
+ }
234
+ /* ------------------------------------------------------------------ */
235
+ /* Misc */
236
+ /* ------------------------------------------------------------------ */
237
+ function deriveName(cwd) {
238
+ const trimmed = cwd.replace(/\/+$/, '');
239
+ const last = trimmed.split('/').pop();
240
+ return last && last.length > 0 ? last : 'project';
241
+ }
242
+ //# sourceMappingURL=template-renderer.js.map
@@ -1,23 +1,23 @@
1
1
  /**
2
- * JobRegistry — Sprint α5.9 (ADR-0056 PR-PUGI-CLI-M1-GAP-J).
2
+ * JobRegistry — Sprint .
3
3
  *
4
4
  * First-class persistent registry for background bash jobs. Lifts the
5
5
  * inline `~/.pugi/jobs.json` management from `src/tools/bash.ts` and
6
6
  * extends it with:
7
7
  *
8
- * - Atomic write (tempfile + rename) so concurrent CLI invocations
9
- * cannot corrupt the ledger (Code Reviewer P2 retro on PR #306).
10
- * - Stale-job reaping on every `list()` call: entries whose pid no
11
- * longer corresponds to a live process are marked `abandoned`.
12
- * - 24h retention for finished / killed / abandoned entries; older
13
- * records are dropped silently.
14
- * - Lifecycle status field (`running` / `finished` / `killed` /
15
- * `failed` / `abandoned`) so the `pugi jobs` CLI can render an
16
- * accurate snapshot regardless of which CLI invocation observed
17
- * the exit.
8
+ * - Atomic write (tempfile + rename) so concurrent CLI invocations
9
+ * cannot corrupt the ledger (Code Reviewer P2 retro on PR).
10
+ * - Stale-job reaping on every `list()` call: entries whose pid no
11
+ * longer corresponds to a live process are marked `abandoned`.
12
+ * - 24h retention for finished / killed / abandoned entries; older
13
+ * records are dropped silently.
14
+ * - Lifecycle status field (`running` / `finished` / `killed` /
15
+ * `failed` / `abandoned`) so the `pugi jobs` CLI can render an
16
+ * accurate snapshot regardless of which CLI invocation observed
17
+ * the exit.
18
18
  *
19
19
  * Storage stays at `~/.pugi/jobs.json` as a JSON array (not JSONL) so
20
- * α5.2 readers continue to work. The schema is additive: legacy
20
+ * readers continue to work. The schema is additive: legacy
21
21
  * entries lacking `id` / `status` / etc. are upgraded on read.
22
22
  */
23
23
  import { randomUUID } from 'node:crypto';
@@ -267,7 +267,7 @@ class FileJobRegistry {
267
267
  }
268
268
  }
269
269
  /**
270
- * Normalize legacy `~/.pugi/jobs.json` entries (Sprint α5.2 shape with
270
+ * Normalize legacy `~/.pugi/jobs.json` entries (Sprint shape with
271
271
  * `jobId` / `class` / no status) into the JobEntry shape used here.
272
272
  * Returns undefined for entries that cannot be migrated so the loop
273
273
  * silently skips garbage.
@@ -387,7 +387,7 @@ export function summarizeJobsForPrompt(entries) {
387
387
  .slice(-5);
388
388
  const lines = ['BACKGROUND JOBS:'];
389
389
  if (running.length === 0) {
390
- lines.push(' - Running: 0');
390
+ lines.push(' - Running: 0');
391
391
  }
392
392
  else {
393
393
  const oldest = running.reduce((min, entry) => {
@@ -395,7 +395,7 @@ export function summarizeJobsForPrompt(entries) {
395
395
  ? entry
396
396
  : min;
397
397
  }, running[0]);
398
- lines.push(` - Running: ${running.length} (oldest started ${relativeAge(oldest.startedAt)} ago)`);
398
+ lines.push(` - Running: ${running.length} (oldest started ${relativeAge(oldest.startedAt)} ago)`);
399
399
  const counts = new Map();
400
400
  for (const entry of running) {
401
401
  counts.set(entry.bashClass, (counts.get(entry.bashClass) ?? 0) + 1);
@@ -403,15 +403,15 @@ export function summarizeJobsForPrompt(entries) {
403
403
  const classSummary = [...counts.entries()]
404
404
  .map(([cls, count]) => `${cls} (${count})`)
405
405
  .join(', ');
406
- lines.push(` - Classes in flight: ${classSummary}`);
406
+ lines.push(` - Classes in flight: ${classSummary}`);
407
407
  }
408
408
  if (finished.length > 0) {
409
409
  const compact = finished
410
410
  .map((entry) => `${entry.id.slice(0, 8)} ${entry.status}${entry.exitCode !== undefined ? ` exit=${entry.exitCode}` : ''}`)
411
411
  .join('; ');
412
- lines.push(` - Recent finished (last ${finished.length}): ${compact}`);
412
+ lines.push(` - Recent finished (last ${finished.length}): ${compact}`);
413
413
  }
414
- lines.push(' Use `pugi jobs tail <id>` to inspect output. Do not spawn another long-running background job while 3+ are already on watch.');
414
+ lines.push(' Use `pugi jobs tail <id>` to inspect output. Do not spawn another long-running background job while 3+ are already on watch.');
415
415
  return lines.join('\n');
416
416
  }
417
417
  /**
@@ -0,0 +1,142 @@
1
+ import { promises as fs } from 'node:fs';
2
+ const HEADER = 'ts\trun_id\tscope\tkey\tvalue\tmodel\tprompt_hash\tnotes\n';
3
+ const appendLocks = new Map();
4
+ export class ResultsLedger {
5
+ path;
6
+ constructor(path) {
7
+ this.path = path;
8
+ }
9
+ async append(row) {
10
+ const previous = appendLocks.get(this.path) ?? Promise.resolve();
11
+ const next = previous
12
+ .catch(() => undefined)
13
+ .then(async () => {
14
+ if (await this.needsHeader()) {
15
+ await fs.appendFile(this.path, HEADER, 'utf8');
16
+ }
17
+ await fs.appendFile(this.path, `${this.formatRow(row)}\n`, 'utf8');
18
+ });
19
+ appendLocks.set(this.path, next);
20
+ try {
21
+ await next;
22
+ }
23
+ finally {
24
+ if (appendLocks.get(this.path) === next) {
25
+ appendLocks.delete(this.path);
26
+ }
27
+ }
28
+ }
29
+ async query(filter) {
30
+ const rows = await this.readRows();
31
+ return rows.filter((row) => {
32
+ return Object.entries(filter).every(([field, expected]) => {
33
+ if (expected === undefined) {
34
+ return true;
35
+ }
36
+ const value = row[field];
37
+ return value !== undefined && String(value) === String(expected);
38
+ });
39
+ });
40
+ }
41
+ async aggregate(scope, key, op) {
42
+ const rows = await this.query({ scope, key });
43
+ if (op === 'count') {
44
+ return rows.length;
45
+ }
46
+ const values = rows
47
+ .map((row) => Number(row.value))
48
+ .filter((value) => Number.isFinite(value));
49
+ if (values.length === 0) {
50
+ return 0;
51
+ }
52
+ if (op === 'sum') {
53
+ return values.reduce((sum, value) => sum + value, 0);
54
+ }
55
+ if (op === 'avg') {
56
+ return values.reduce((sum, value) => sum + value, 0) / values.length;
57
+ }
58
+ if (op === 'min') {
59
+ return Math.min(...values);
60
+ }
61
+ return Math.max(...values);
62
+ }
63
+ async needsHeader() {
64
+ try {
65
+ const stat = await fs.stat(this.path);
66
+ return stat.size === 0;
67
+ }
68
+ catch (error) {
69
+ if (isNodeError(error) && error.code === 'ENOENT') {
70
+ return true;
71
+ }
72
+ throw error;
73
+ }
74
+ }
75
+ async readRows() {
76
+ let data;
77
+ try {
78
+ data = await fs.readFile(this.path, 'utf8');
79
+ }
80
+ catch (error) {
81
+ if (isNodeError(error) && error.code === 'ENOENT') {
82
+ return [];
83
+ }
84
+ throw error;
85
+ }
86
+ if (!data.endsWith('\n')) {
87
+ data = data.slice(0, data.lastIndexOf('\n') + 1);
88
+ }
89
+ const lines = data.split('\n').filter((line) => line.length > 0);
90
+ const dataLines = lines[0] === HEADER.trimEnd() ? lines.slice(1) : lines;
91
+ return dataLines.flatMap((line) => {
92
+ const cells = line.split('\t');
93
+ if (cells.length !== 8) {
94
+ return [];
95
+ }
96
+ const ts = cells[0];
97
+ const run_id = cells[1];
98
+ const scope = cells[2];
99
+ const key = cells[3];
100
+ const value = cells[4];
101
+ const model = cells[5];
102
+ const prompt_hash = cells[6];
103
+ const notes = cells[7];
104
+ if (!ts || !run_id || !scope || !key || value === undefined) {
105
+ return [];
106
+ }
107
+ return [
108
+ {
109
+ ts,
110
+ run_id,
111
+ scope,
112
+ key,
113
+ value,
114
+ ...(model ? { model } : {}),
115
+ ...(prompt_hash ? { prompt_hash } : {}),
116
+ ...(notes ? { notes } : {}),
117
+ },
118
+ ];
119
+ });
120
+ }
121
+ formatRow(row) {
122
+ return [
123
+ row.ts,
124
+ row.run_id,
125
+ row.scope,
126
+ row.key,
127
+ row.value,
128
+ row.model ?? '',
129
+ row.prompt_hash ?? '',
130
+ row.notes ?? '',
131
+ ]
132
+ .map((value) => cleanCell(value))
133
+ .join('\t');
134
+ }
135
+ }
136
+ function cleanCell(value) {
137
+ return String(value).replace(/\r?\n/g, ' ').replace(/\t/g, ' ');
138
+ }
139
+ function isNodeError(error) {
140
+ return error instanceof Error && 'code' in error;
141
+ }
142
+ //# sourceMappingURL=results-tsv.js.map
@@ -0,0 +1,51 @@
1
+ import { randomBytes } from 'node:crypto';
2
+ import { join } from 'node:path';
3
+ function stripQuotes(s) {
4
+ return s
5
+ .replace(/"(?:\\.|[^"\\])*"/g, '""')
6
+ .replace(/'(?:\\.|[^'\\])*'/g, "''");
7
+ }
8
+ function hasRedirect(stripped) {
9
+ const noLogical = stripped.replace(/\|\|/g, ' ').replace(/&&/g, ' ');
10
+ if (/>\s*\S/.test(noLogical))
11
+ return true;
12
+ if (/2>&1/.test(noLogical))
13
+ return true;
14
+ if (/\|\s*tee\b/.test(noLogical))
15
+ return true;
16
+ if (/\|/.test(noLogical))
17
+ return true;
18
+ return false;
19
+ }
20
+ function isCompound(stripped) {
21
+ return /&&|\|\||;|\|/.test(stripped);
22
+ }
23
+ function shellQuote(s) {
24
+ return `'${s.replace(/'/g, `'\\''`)}'`;
25
+ }
26
+ function genLogPath(logDir) {
27
+ const rand = randomBytes(6).toString('hex');
28
+ return join(logDir, `cmd-${rand}.log`);
29
+ }
30
+ export function redirectCommand(command, options) {
31
+ if (!options.workspaceRoot || typeof options.workspaceRoot !== 'string') {
32
+ throw new Error('redirectCommand: workspaceRoot is required');
33
+ }
34
+ const logDir = options.logDir ?? join(options.workspaceRoot, '.pugi', 'logs');
35
+ const logPath = genLogPath(logDir);
36
+ const trimmed = command.trim();
37
+ if (trimmed.length === 0) {
38
+ return { command: trimmed, logPath, alreadyRedirected: false };
39
+ }
40
+ const stripped = stripQuotes(trimmed);
41
+ const already = hasRedirect(stripped);
42
+ if (already && !options.forceRedirect) {
43
+ return { command, logPath, alreadyRedirected: true };
44
+ }
45
+ const compound = isCompound(stripped);
46
+ const transformed = compound
47
+ ? `sh -c ${shellQuote(trimmed)} > ${logPath} 2>&1`
48
+ : `${trimmed} > ${logPath} 2>&1`;
49
+ return { command: transformed, logPath, alreadyRedirected: already };
50
+ }
51
+ //# sourceMappingURL=stdout-redirect.js.map
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Per-process LSP client cache — .
3
+ *
4
+ * The `runtime/commands/lsp.ts` CLI surface spawns one LSP server
5
+ * per invocation and stops it at the end. That is correct for the
6
+ * one-shot `pugi lsp hover ...` shape but wrong for L15's
7
+ * post-edit auto-diagnostics: every successful `edit`/`write` would
8
+ * otherwise pay the ~2-3s cold-start of `typescript-language-server`,
9
+ * which is unusable inside an agent loop.
10
+ *
11
+ * This module owns a singleton map keyed by `LspLanguage` with lazy
12
+ * initialization (`getOrStart`). The first edit of a TS file in a
13
+ * session pays cold-start; every subsequent edit of any TS/TSX file
14
+ * in the same workspace reuses the warm client.
15
+ *
16
+ * Lifecycle:
17
+ * - `getOrStart(lang, cwd)` — spawn if missing, return cached otherwise.
18
+ * - `stopAll()` — graceful shutdown of every cached client. Called from
19
+ * `runCli` exit so a Ctrl-C never leaves zombie LSP processes behind.
20
+ * - `reset()` — test-only escape hatch, drops the cache without
21
+ * touching child processes (specs inject stubs that own their own
22
+ * lifecycle).
23
+ *
24
+ * Failure handling: a startup failure is NOT cached. The next call
25
+ * tries again. This keeps the cache from poisoning a session when the
26
+ * operator installs the missing LSP binary mid-session and re-edits.
27
+ *
28
+ * Brand voice: ASCII only, no emoji, no banned words.
29
+ */
30
+ import { isLspLanguageDisabled, startLspClient, } from './client.js';
31
+ const cache = new Map();
32
+ /**
33
+ * Return a warm client for `lang`, starting one if needed. The
34
+ * workspace `cwd` is captured at cache-insert time; if a subsequent
35
+ * call asks for the same language with a different `cwd` we tear
36
+ * down the old client and start a fresh one. This handles the
37
+ * agent-worktree case where the same process hops between workspace
38
+ * roots inside one Node lifetime.
39
+ */
40
+ export async function getOrStartLspClient(lang, opts) {
41
+ // β7 L9: respect the per-language disable toggle BEFORE we attempt to
42
+ // spawn. The check is cheap and keeps the disabled path from paying
43
+ // the `npx --yes` warmup cost on first use.
44
+ if (isLspLanguageDisabled(lang, opts.lspSettings)) {
45
+ return {
46
+ ok: false,
47
+ reason: 'lsp_disabled',
48
+ detail: `${lang} is disabled via .pugi/settings.json::lsp`,
49
+ };
50
+ }
51
+ const existing = cache.get(lang);
52
+ if (existing && existing.cwd === opts.cwd) {
53
+ return { ok: true, client: existing.client };
54
+ }
55
+ if (existing && existing.cwd !== opts.cwd) {
56
+ // Workspace switched — stop the old client and fall through to spawn.
57
+ try {
58
+ await existing.client.stop();
59
+ }
60
+ catch {
61
+ // best effort; stop() is idempotent + swallow-safe
62
+ }
63
+ cache.delete(lang);
64
+ }
65
+ const result = await startLspClient(lang, opts);
66
+ if (!result.ok) {
67
+ return { ok: false, reason: result.reason, detail: result.detail };
68
+ }
69
+ cache.set(lang, { client: result.value, cwd: opts.cwd });
70
+ return { ok: true, client: result.value };
71
+ }
72
+ /** Look up the cached client without starting one. Returns undefined when missing. */
73
+ export function peekLspClient(lang) {
74
+ return cache.get(lang)?.client;
75
+ }
76
+ /** Snapshot of currently-cached languages — used by `pugi lsp status` debug output. */
77
+ export function listCachedLanguages() {
78
+ return Array.from(cache.keys());
79
+ }
80
+ /**
81
+ * Stop every cached client and clear the cache. Called from `runCli`
82
+ * exit and from specs that own the lifecycle of their stub servers.
83
+ */
84
+ export async function stopAllLspClients() {
85
+ const snapshot = Array.from(cache.values());
86
+ cache.clear();
87
+ await Promise.all(snapshot.map(async (entry) => {
88
+ try {
89
+ await entry.client.stop();
90
+ }
91
+ catch {
92
+ // best effort — shutting down anyway
93
+ }
94
+ }));
95
+ }
96
+ /**
97
+ * Test-only: drop the cache map WITHOUT calling stop on the children.
98
+ * Specs that inject stub servers manage the stub lifecycle themselves;
99
+ * this lets a spec swap a stub mid-test without the cache holding a
100
+ * stale reference to a torn-down process.
101
+ */
102
+ export function __resetLspCacheForTests() {
103
+ cache.clear();
104
+ }
105
+ //# sourceMappingURL=cache.js.map