@pugi/cli 0.1.0-beta.5 → 0.1.0-beta.51

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 (264) hide show
  1. package/THIRD_PARTY_NOTICES.md +40 -0
  2. package/assets/pugi-mascot.ansi +15 -25
  3. package/assets/pugi-prozr2-mascot.ansi +9 -0
  4. package/bin/run.js +33 -1
  5. package/dist/commands/jobs-watch.js +201 -0
  6. package/dist/commands/jobs.js +15 -0
  7. package/dist/commands/smoke.js +133 -0
  8. package/dist/core/agent-progress/cleanup.js +134 -0
  9. package/dist/core/agent-progress/schema.js +144 -0
  10. package/dist/core/agent-progress/writer.js +101 -0
  11. package/dist/core/artifact-chain/dispatcher.js +148 -0
  12. package/dist/core/artifact-chain/exporter.js +164 -0
  13. package/dist/core/artifact-chain/state.js +243 -0
  14. package/dist/core/artifact-chain/steps.js +169 -0
  15. package/dist/core/auth/ensure-authenticated.js +129 -0
  16. package/dist/core/auth/env-provider.js +238 -0
  17. package/dist/core/auto-update/channels.js +122 -0
  18. package/dist/core/auto-update/checker.js +241 -0
  19. package/dist/core/auto-update/state.js +235 -0
  20. package/dist/core/bare-mode/index.js +107 -0
  21. package/dist/core/bash-classifier.js +400 -4
  22. package/dist/core/checkpoint/resumer.js +149 -0
  23. package/dist/core/checkpoint/rewinder.js +291 -0
  24. package/dist/core/codegraph/decision-store.js +248 -0
  25. package/dist/core/codegraph/detect-repo.js +459 -0
  26. package/dist/core/codegraph/install.js +134 -0
  27. package/dist/core/codegraph/offer-hook.js +220 -0
  28. package/dist/core/compact/auto-trigger.js +96 -0
  29. package/dist/core/compact/buffer-rewriter.js +115 -0
  30. package/dist/core/compact/summarizer.js +208 -0
  31. package/dist/core/compact/token-counter.js +108 -0
  32. package/dist/core/consensus/diff-capture.js +112 -3
  33. package/dist/core/context/index.js +7 -0
  34. package/dist/core/context/markdown-traverse.js +255 -0
  35. package/dist/core/cost/rate-card.js +129 -0
  36. package/dist/core/cost/tracker.js +221 -0
  37. package/dist/core/denial-tracking/index.js +8 -0
  38. package/dist/core/denial-tracking/state.js +264 -0
  39. package/dist/core/diagnostics/probe-runner.js +93 -0
  40. package/dist/core/diagnostics/probes/api.js +46 -0
  41. package/dist/core/diagnostics/probes/auth.js +86 -0
  42. package/dist/core/diagnostics/probes/bare-mode.js +42 -0
  43. package/dist/core/diagnostics/probes/cli-version.js +127 -0
  44. package/dist/core/diagnostics/probes/config.js +72 -0
  45. package/dist/core/diagnostics/probes/denial-tracking.js +57 -0
  46. package/dist/core/diagnostics/probes/disk.js +81 -0
  47. package/dist/core/diagnostics/probes/git.js +65 -0
  48. package/dist/core/diagnostics/probes/hooks.js +118 -0
  49. package/dist/core/diagnostics/probes/mcp.js +75 -0
  50. package/dist/core/diagnostics/probes/node.js +59 -0
  51. package/dist/core/diagnostics/probes/pnpm.js +36 -0
  52. package/dist/core/diagnostics/probes/pugi-md.js +89 -0
  53. package/dist/core/diagnostics/probes/sandbox.js +40 -0
  54. package/dist/core/diagnostics/probes/session.js +74 -0
  55. package/dist/core/diagnostics/probes/status-snapshot.js +488 -0
  56. package/dist/core/diagnostics/probes/workspace.js +63 -0
  57. package/dist/core/diagnostics/types.js +70 -0
  58. package/dist/core/dispatch/cache-cleanup.js +197 -0
  59. package/dist/core/dispatch/cache-handoff.js +295 -0
  60. package/dist/core/edits/dispatch.js +218 -2
  61. package/dist/core/edits/journal.js +199 -0
  62. package/dist/core/edits/layer-d-ast.js +557 -14
  63. package/dist/core/edits/verify-hook.js +273 -0
  64. package/dist/core/edits/worktree.js +322 -0
  65. package/dist/core/engine/anvil-client.js +115 -5
  66. package/dist/core/engine/auto-compact.js +179 -0
  67. package/dist/core/engine/budgets.js +155 -0
  68. package/dist/core/engine/context-prefix.js +155 -0
  69. package/dist/core/engine/intent.js +260 -0
  70. package/dist/core/engine/native-pugi.js +897 -211
  71. package/dist/core/engine/prompts.js +88 -2
  72. package/dist/core/engine/strip-internal-fields.js +124 -0
  73. package/dist/core/engine/tool-bridge.js +1045 -36
  74. package/dist/core/feedback/queue.js +177 -0
  75. package/dist/core/feedback/submitter.js +145 -0
  76. package/dist/core/file-cache.js +113 -1
  77. package/dist/core/hooks/events.js +44 -0
  78. package/dist/core/hooks/index.js +15 -0
  79. package/dist/core/hooks/registry.js +213 -0
  80. package/dist/core/hooks/runner.js +236 -0
  81. package/dist/core/hooks/v2/event-emitter.js +115 -0
  82. package/dist/core/hooks/v2/executor.js +282 -0
  83. package/dist/core/hooks/v2/index.js +25 -0
  84. package/dist/core/hooks/v2/lifecycle.js +104 -0
  85. package/dist/core/hooks/v2/loader.js +216 -0
  86. package/dist/core/hooks/v2/matcher.js +125 -0
  87. package/dist/core/hooks/v2/trust.js +143 -0
  88. package/dist/core/hooks/v2/types.js +86 -0
  89. package/dist/core/lsp/cache.js +105 -0
  90. package/dist/core/lsp/client.js +776 -0
  91. package/dist/core/lsp/language-detect.js +66 -0
  92. package/dist/core/lsp/post-edit-diagnostics.js +171 -0
  93. package/dist/core/mcp/client.js +75 -6
  94. package/dist/core/mcp/http-server.js +553 -0
  95. package/dist/core/mcp/orchestrator-tools.js +662 -0
  96. package/dist/core/mcp/permission.js +190 -0
  97. package/dist/core/mcp/registry.js +24 -2
  98. package/dist/core/mcp/server-tools.js +219 -0
  99. package/dist/core/mcp/server.js +397 -0
  100. package/dist/core/memory/dual-write.js +416 -0
  101. package/dist/core/memory/phase1-kinds.js +20 -0
  102. package/dist/core/memory-sync/queue.js +158 -0
  103. package/dist/core/onboarding/ensure-initialized.js +133 -0
  104. package/dist/core/onboarding/marker.js +111 -0
  105. package/dist/core/onboarding/telemetry-state.js +108 -0
  106. package/dist/core/output-style/presets.js +176 -0
  107. package/dist/core/output-style/state.js +185 -0
  108. package/dist/core/path-security.js +284 -2
  109. package/dist/core/permissions/auto-classifier.js +124 -0
  110. package/dist/core/permissions/circuit-breaker.js +83 -0
  111. package/dist/core/permissions/gate.js +278 -0
  112. package/dist/core/permissions/index.js +20 -0
  113. package/dist/core/permissions/mode.js +174 -0
  114. package/dist/core/permissions/state.js +241 -0
  115. package/dist/core/permissions/tool-class.js +93 -0
  116. package/dist/core/prd-check/parser.js +215 -0
  117. package/dist/core/prd-check/reporter.js +127 -0
  118. package/dist/core/prd-check/session-review.js +557 -0
  119. package/dist/core/prd-check/verifiers.js +223 -0
  120. package/dist/core/pugi-md/context-injector.js +76 -0
  121. package/dist/core/pugi-md/walk-up.js +207 -0
  122. package/dist/core/release-notes/parser.js +241 -0
  123. package/dist/core/release-notes/state.js +116 -0
  124. package/dist/core/repl/history.js +11 -1
  125. package/dist/core/repl/model-pricing.js +135 -0
  126. package/dist/core/repl/session.js +1897 -37
  127. package/dist/core/repl/slash-commands.js +430 -15
  128. package/dist/core/repl/store/session-store.js +31 -2
  129. package/dist/core/repl/workspace-context.js +22 -0
  130. package/dist/core/repo-map/build.js +125 -0
  131. package/dist/core/repo-map/cache.js +185 -0
  132. package/dist/core/repo-map/extractor.js +254 -0
  133. package/dist/core/repo-map/formatter.js +145 -0
  134. package/dist/core/repo-map/scanner.js +211 -0
  135. package/dist/core/retry-budget/budget.js +284 -0
  136. package/dist/core/retry-budget/index.js +5 -0
  137. package/dist/core/session.js +92 -0
  138. package/dist/core/settings.js +80 -0
  139. package/dist/core/share/formatter.js +271 -0
  140. package/dist/core/share/redactor.js +221 -0
  141. package/dist/core/share/uploader.js +267 -0
  142. package/dist/core/skills/defaults.js +457 -0
  143. package/dist/core/smoke/headless-driver.js +174 -0
  144. package/dist/core/smoke/orchestrator.js +194 -0
  145. package/dist/core/smoke/runner.js +238 -0
  146. package/dist/core/smoke/scenario-parser.js +316 -0
  147. package/dist/core/subagents/dispatcher-real.js +600 -0
  148. package/dist/core/subagents/dispatcher.js +113 -24
  149. package/dist/core/subagents/index.js +18 -5
  150. package/dist/core/subagents/isolation-matrix.js +213 -0
  151. package/dist/core/subagents/spawn.js +19 -4
  152. package/dist/core/telemetry/emitter.js +229 -0
  153. package/dist/core/telemetry/queue.js +251 -0
  154. package/dist/core/theme/context.js +91 -0
  155. package/dist/core/theme/presets.js +228 -0
  156. package/dist/core/theme/state.js +181 -0
  157. package/dist/core/todos/invariant.js +10 -0
  158. package/dist/core/todos/state.js +177 -0
  159. package/dist/core/transport/version-interceptor.js +166 -0
  160. package/dist/core/vim/keymap.js +288 -0
  161. package/dist/core/vim/state.js +92 -0
  162. package/dist/core/worktree-manager/cleanup.js +123 -0
  163. package/dist/core/worktree-manager/manager.js +303 -0
  164. package/dist/index.js +28 -0
  165. package/dist/runtime/bootstrap.js +190 -0
  166. package/dist/runtime/cli.js +3241 -343
  167. package/dist/runtime/commands/cancel.js +231 -0
  168. package/dist/runtime/commands/chain.js +489 -0
  169. package/dist/runtime/commands/codegraph-status.js +227 -0
  170. package/dist/runtime/commands/compact.js +297 -0
  171. package/dist/runtime/commands/cost.js +199 -0
  172. package/dist/runtime/commands/delegate.js +242 -11
  173. package/dist/runtime/commands/dispatch.js +126 -0
  174. package/dist/runtime/commands/doctor.js +412 -0
  175. package/dist/runtime/commands/feedback.js +184 -0
  176. package/dist/runtime/commands/hooks.js +184 -0
  177. package/dist/runtime/commands/lsp.js +368 -0
  178. package/dist/runtime/commands/mcp.js +879 -0
  179. package/dist/runtime/commands/memory.js +508 -0
  180. package/dist/runtime/commands/model.js +237 -0
  181. package/dist/runtime/commands/onboarding.js +275 -0
  182. package/dist/runtime/commands/patch.js +128 -0
  183. package/dist/runtime/commands/permissions.js +112 -0
  184. package/dist/runtime/commands/plan.js +143 -0
  185. package/dist/runtime/commands/prd-check.js +285 -0
  186. package/dist/runtime/commands/redo-blob-store.js +92 -0
  187. package/dist/runtime/commands/redo.js +361 -0
  188. package/dist/runtime/commands/release-notes.js +229 -0
  189. package/dist/runtime/commands/repo-map.js +95 -0
  190. package/dist/runtime/commands/report.js +299 -0
  191. package/dist/runtime/commands/resume.js +118 -0
  192. package/dist/runtime/commands/review-consensus.js +17 -2
  193. package/dist/runtime/commands/rewind.js +333 -0
  194. package/dist/runtime/commands/sessions.js +163 -0
  195. package/dist/runtime/commands/share.js +316 -0
  196. package/dist/runtime/commands/status.js +186 -0
  197. package/dist/runtime/commands/stickers.js +82 -0
  198. package/dist/runtime/commands/style.js +194 -0
  199. package/dist/runtime/commands/theme.js +196 -0
  200. package/dist/runtime/commands/undo.js +32 -0
  201. package/dist/runtime/commands/update.js +289 -0
  202. package/dist/runtime/commands/vim.js +140 -0
  203. package/dist/runtime/commands/worktree.js +177 -0
  204. package/dist/runtime/commands/worktrees.js +155 -0
  205. package/dist/runtime/headless-repl.js +195 -0
  206. package/dist/runtime/headless.js +543 -0
  207. package/dist/runtime/load-hooks-or-exit.js +71 -0
  208. package/dist/runtime/plan-decompose.js +531 -0
  209. package/dist/runtime/version.js +65 -0
  210. package/dist/tools/agent-tool.js +229 -0
  211. package/dist/tools/apply-patch.js +556 -0
  212. package/dist/tools/ask-user-question.js +213 -0
  213. package/dist/tools/ask-user.js +115 -0
  214. package/dist/tools/bash.js +203 -4
  215. package/dist/tools/file-tools.js +85 -14
  216. package/dist/tools/lsp-tools.js +189 -0
  217. package/dist/tools/mcp-tool.js +260 -0
  218. package/dist/tools/multi-edit.js +361 -0
  219. package/dist/tools/powershell.js +268 -0
  220. package/dist/tools/registry.js +51 -0
  221. package/dist/tools/skill-tool.js +96 -0
  222. package/dist/tools/tasks.js +208 -0
  223. package/dist/tools/todo-write.js +184 -0
  224. package/dist/tools/web-fetch.js +147 -2
  225. package/dist/tools/web-search.js +458 -0
  226. package/dist/tui/agent-progress-card.js +111 -0
  227. package/dist/tui/agent-tree.js +10 -0
  228. package/dist/tui/ask-modal.js +2 -2
  229. package/dist/tui/ask-user-question-prompt.js +192 -0
  230. package/dist/tui/compact-banner.js +81 -0
  231. package/dist/tui/conversation-pane.js +82 -8
  232. package/dist/tui/cost-table.js +111 -0
  233. package/dist/tui/doctor-table.js +46 -0
  234. package/dist/tui/feedback-prompt.js +156 -0
  235. package/dist/tui/input-box.js +218 -3
  236. package/dist/tui/markdown-render.js +4 -4
  237. package/dist/tui/onboarding-wizard.js +240 -0
  238. package/dist/tui/permissions-picker.js +86 -0
  239. package/dist/tui/render.js +35 -0
  240. package/dist/tui/repl-render.js +313 -35
  241. package/dist/tui/repl-splash-art.js +1 -1
  242. package/dist/tui/repl-splash-mascot.js +32 -8
  243. package/dist/tui/repl-splash.js +2 -2
  244. package/dist/tui/repl.js +85 -5
  245. package/dist/tui/splash.js +1 -1
  246. package/dist/tui/status-bar.js +94 -16
  247. package/dist/tui/status-table.js +7 -0
  248. package/dist/tui/stickers-art.js +136 -0
  249. package/dist/tui/style-table.js +28 -0
  250. package/dist/tui/theme-table.js +29 -0
  251. package/dist/tui/thinking-spinner.js +123 -0
  252. package/dist/tui/tool-stream-pane.js +52 -3
  253. package/dist/tui/update-banner.js +27 -2
  254. package/dist/tui/vim-input.js +267 -0
  255. package/dist/tui/welcome-banner.js +107 -0
  256. package/dist/tui/welcome-data.js +293 -0
  257. package/docs/examples/codegraph.mcp.json +10 -0
  258. package/package.json +13 -7
  259. package/test/scenarios/codegen-create-file.scenario.txt +13 -0
  260. package/test/scenarios/compact-force.scenario.txt +11 -0
  261. package/test/scenarios/identity.scenario.txt +11 -0
  262. package/test/scenarios/persona-handoff.scenario.txt +11 -0
  263. package/test/scenarios/walkback.scenario.txt +12 -0
  264. package/dist/core/engine/compaction-hook.js +0 -154
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Language-from-extension detection — Leak L15.
3
+ *
4
+ * Single source of truth for "given a file path, which `LspLanguage`
5
+ * slug do we route to". The α7.7 `runtime/commands/lsp.ts` shipped its
6
+ * own inline `inferLanguage` switch; L15 (post-edit auto-diagnostics)
7
+ * needs the same lookup from `core/engine/tool-bridge.ts`, so we lift
8
+ * the table into a dedicated module to avoid a second copy drifting
9
+ * out of sync.
10
+ *
11
+ * Returning `undefined` is the calling code's signal to silently skip
12
+ * LSP — an unsupported extension is NOT an error, it just means "no
13
+ * diagnostics for this file". The tool-bridge hook treats this as a
14
+ * no-op envelope tail.
15
+ *
16
+ * Adding a new language requires THREE coordinated changes:
17
+ * 1. Add the `LspLanguage` slug + server descriptor in `client.ts`.
18
+ * 2. Map its extensions here.
19
+ * 3. Add a `lsp-language-matrix` spec row exercising the new ext.
20
+ *
21
+ * Brand voice: ASCII only, no emoji, no banned words.
22
+ */
23
+ import { extname } from 'node:path';
24
+ /**
25
+ * Lower-case extension (including the dot) → LSP language slug.
26
+ * Mirror of the switch in `runtime/commands/lsp.ts::inferLanguage`.
27
+ * The table form lets tests assert coverage and lets new languages
28
+ * land with one edit instead of two.
29
+ */
30
+ export const EXTENSION_TO_LANGUAGE = {
31
+ '.ts': 'ts',
32
+ '.tsx': 'ts',
33
+ '.mts': 'ts',
34
+ '.cts': 'ts',
35
+ '.js': 'js',
36
+ '.jsx': 'js',
37
+ '.mjs': 'js',
38
+ '.cjs': 'js',
39
+ '.py': 'py',
40
+ '.pyi': 'py',
41
+ '.go': 'go',
42
+ '.rs': 'rust',
43
+ };
44
+ /**
45
+ * Infer the `LspLanguage` for a workspace-relative or absolute path.
46
+ * Returns `undefined` for unmapped extensions — the caller decides
47
+ * whether that is silently skipped (post-edit hook) or surfaced as
48
+ * `language_unsupported` (`pugi lsp` CLI).
49
+ */
50
+ export function languageForFile(file) {
51
+ const ext = extname(file).toLowerCase();
52
+ if (!ext)
53
+ return undefined;
54
+ return EXTENSION_TO_LANGUAGE[ext];
55
+ }
56
+ /**
57
+ * Return every extension currently mapped to the given language.
58
+ * Used by the matrix spec to assert coverage without re-typing the
59
+ * extension list.
60
+ */
61
+ export function extensionsForLanguage(lang) {
62
+ return Object.entries(EXTENSION_TO_LANGUAGE)
63
+ .filter(([, value]) => value === lang)
64
+ .map(([ext]) => ext);
65
+ }
66
+ //# sourceMappingURL=language-detect.js.map
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Post-edit diagnostics — Leak L15.
3
+ *
4
+ * Claude Code's leak intel surfaced this pattern: after a `FileEdit` /
5
+ * `Write` tool call lands, an LSP diagnostic pass runs on the touched
6
+ * file and the result is appended to the tool envelope before the
7
+ * model sees it. The model then self-corrects in the same turn —
8
+ * "TS2304: Cannot find name 'undef'" comes back, the model fixes the
9
+ * typo in the next tool call, no operator round-trip needed.
10
+ *
11
+ * This module is the Pugi side of that pattern:
12
+ *
13
+ * 1. The tool-bridge calls `runPostEditDiagnostics(path, ctx)` after
14
+ * a successful `edit` / `write` / `multi_edit`.
15
+ * 2. We infer the language from the extension (`language-detect`).
16
+ * Unsupported extension → `{ skip: true }` and the bridge appends
17
+ * nothing.
18
+ * 3. We borrow (or lazily start) the per-language cached client
19
+ * from `cache.ts`. A spawn failure → `{ skip: true }` and the
20
+ * envelope stays clean. Silence on failure is intentional: an
21
+ * operator who has not installed `typescript-language-server`
22
+ * should not see an LSP nag on every edit.
23
+ * 4. We pull diagnostics with a hard 5s ceiling. A timeout logs a
24
+ * warning on stderr (gated on `PUGI_LSP_DEBUG=1`) and skips —
25
+ * the envelope is never blocked on LSP.
26
+ * 5. We format the surviving diagnostics into a readable tail
27
+ * mirroring the leak format:
28
+ *
29
+ * LSP DIAGNOSTICS (typescript):
30
+ * foo.ts:42:5 error TS2304: Cannot find name 'undef'.
31
+ * foo.ts:51:1 warn TS6133: 'unused' is declared.
32
+ *
33
+ * The bridge concatenates this tail onto its existing `wrote ...` /
34
+ * `edited ...` body with a single newline separator. When there are
35
+ * zero diagnostics we return `{ skip: true }` so the existing body
36
+ * is unchanged — the "no news is good news" path stays terse.
37
+ *
38
+ * Brand voice: ASCII only, no emoji, no banned words.
39
+ */
40
+ import { isAbsolute, relative, resolve } from 'node:path';
41
+ import { getOrStartLspClient } from './cache.js';
42
+ import { languageForFile } from './language-detect.js';
43
+ const DEFAULT_TIMEOUT_MS = 5_000;
44
+ /**
45
+ * Hard cap on how many diagnostics we surface to the model. A file
46
+ * with 200 errors after a broken bulk edit would otherwise blow the
47
+ * context window; the model can re-run `pugi lsp diagnostics` if
48
+ * it needs the full list.
49
+ */
50
+ const MAX_DIAGNOSTICS = 25;
51
+ export async function runPostEditDiagnostics(filePath, opts) {
52
+ const lang = languageForFile(filePath);
53
+ if (!lang) {
54
+ return { skip: true, reason: 'unsupported_language' };
55
+ }
56
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
57
+ const clientResult = await loadClient(lang, opts);
58
+ if (!clientResult.ok) {
59
+ return { skip: true, reason: mapStartFailure(clientResult.reason) };
60
+ }
61
+ // Run diagnostics with a hard timeout. The underlying LspClient has
62
+ // its own per-request timeout (5s default) but a slow handshake
63
+ // can blow past it; we belt-and-suspenders here so the agent loop
64
+ // never blocks on LSP.
65
+ const relPath = toWorkspaceRelative(filePath, opts.cwd);
66
+ const diagnosticsPromise = clientResult.client.diagnostics(relPath);
67
+ let timer;
68
+ const timeoutPromise = new Promise((resolveFn) => {
69
+ timer = setTimeout(() => resolveFn({ timedOut: true }), timeoutMs);
70
+ timer.unref();
71
+ });
72
+ const race = await Promise.race([
73
+ diagnosticsPromise.then((value) => ({ timedOut: false, value })),
74
+ timeoutPromise,
75
+ ]);
76
+ if (timer)
77
+ clearTimeout(timer);
78
+ if (race.timedOut) {
79
+ const writeFn = opts.debugWrite ?? ((line) => {
80
+ if (process.env.PUGI_LSP_DEBUG === '1')
81
+ process.stderr.write(`${line}\n`);
82
+ });
83
+ writeFn(`[pugi-lsp] post-edit diagnostics for ${relPath} timed out after ${timeoutMs}ms (lang=${lang})`);
84
+ return { skip: true, reason: 'timeout' };
85
+ }
86
+ const diag = race.value;
87
+ if (!diag.ok) {
88
+ return { skip: true, reason: 'lsp_error' };
89
+ }
90
+ if (diag.value.length === 0) {
91
+ return { skip: true, reason: 'no_diagnostics' };
92
+ }
93
+ const tail = formatDiagnosticsTail(relPath, lang, diag.value);
94
+ return { skip: false, tail, count: diag.value.length, language: lang };
95
+ }
96
+ async function loadClient(lang, opts) {
97
+ if (opts.clientLoader) {
98
+ return opts.clientLoader(lang);
99
+ }
100
+ const { cwd, timeoutMs, clientLoader: _ignoredA, debugWrite: _ignoredB, ...rest } = opts;
101
+ const result = await getOrStartLspClient(lang, { cwd, ...rest });
102
+ if (!result.ok) {
103
+ return { ok: false, reason: result.reason, detail: result.detail };
104
+ }
105
+ return { ok: true, client: result.client };
106
+ }
107
+ function mapStartFailure(reason) {
108
+ if (reason === 'lsp_unavailable' || reason === 'language_unsupported')
109
+ return 'lsp_unavailable';
110
+ if (reason === 'lsp_disabled')
111
+ return 'lsp_disabled';
112
+ return 'lsp_error';
113
+ }
114
+ /**
115
+ * Convert an absolute or workspace-relative path into the form the
116
+ * LSP client expects — same shape as `runtime/commands/lsp.ts` uses.
117
+ */
118
+ function toWorkspaceRelative(filePath, cwd) {
119
+ if (!isAbsolute(filePath))
120
+ return filePath;
121
+ const rel = relative(cwd, resolve(cwd, filePath));
122
+ return rel || filePath;
123
+ }
124
+ /**
125
+ * Format diagnostics into the leak-shaped envelope tail. Pure function
126
+ * exported for unit tests to assert the line format independent of
127
+ * any LSP plumbing.
128
+ */
129
+ export function formatDiagnosticsTail(relPath, lang, diagnostics) {
130
+ const visible = diagnostics.slice(0, MAX_DIAGNOSTICS);
131
+ const truncated = diagnostics.length > visible.length;
132
+ const lines = [`LSP DIAGNOSTICS (${LANGUAGE_LABELS[lang]}):`];
133
+ for (const diag of visible) {
134
+ const line = diag.range.start.line + 1; // LSP is zero-based; humans expect 1-based.
135
+ const col = diag.range.start.character + 1;
136
+ const severity = SEVERITY_LABELS[diag.severityLabel];
137
+ const code = diag.code !== undefined && diag.code !== '' ? ` ${diag.code}` : '';
138
+ const source = diag.source ? `${diag.source}` : '';
139
+ const head = source ? `${severity}${code} (${source}):` : `${severity}${code}:`;
140
+ lines.push(` ${relPath}:${line}:${col} ${head} ${diag.message}`);
141
+ }
142
+ if (truncated) {
143
+ lines.push(` ... ${diagnostics.length - visible.length} more diagnostic(s) — re-run pugi lsp diagnostics ${relPath} for the full list`);
144
+ }
145
+ return lines.join('\n');
146
+ }
147
+ const LANGUAGE_LABELS = {
148
+ ts: 'typescript',
149
+ js: 'javascript',
150
+ py: 'python',
151
+ go: 'go',
152
+ rust: 'rust',
153
+ };
154
+ /**
155
+ * Map LSP severity label → the short token the leak envelope uses.
156
+ * "warn" is shorter than "warning" and matches Claude Code's leak
157
+ * verbatim; the rest mirror LSP terminology.
158
+ */
159
+ const SEVERITY_LABELS = {
160
+ error: 'error',
161
+ warning: 'warn ',
162
+ info: 'info ',
163
+ hint: 'hint ',
164
+ };
165
+ /** Test-only surface so specs can poke the pure helpers without LSP. */
166
+ export const __test__ = {
167
+ formatDiagnosticsTail,
168
+ MAX_DIAGNOSTICS,
169
+ DEFAULT_TIMEOUT_MS,
170
+ };
171
+ //# sourceMappingURL=post-edit-diagnostics.js.map
@@ -1,4 +1,5 @@
1
1
  import { spawn } from 'node:child_process';
2
+ import { createWriteStream } from 'node:fs';
2
3
  import { z } from 'zod';
3
4
  /**
4
5
  * Minimal JSON-RPC 2.0 over stdio MCP client for Pugi CLI M1.
@@ -34,6 +35,15 @@ import { z } from 'zod';
34
35
  */
35
36
  export const mcpServerConfigSchema = z.object({
36
37
  command: z.string().min(1),
38
+ /**
39
+ * β4 r2 P1 #1 — operator's literal `command` argument as supplied to
40
+ * `pugi mcp install`. `command` above is now the RESOLVED absolute path
41
+ * we actually spawn. `originalCommand` is preserved for display/audit
42
+ * (`pugi mcp list`, audit logs) so operators can still see how they
43
+ * typed the install. Optional because pre-r2 mcp.json files do not
44
+ * carry the field — the loader treats absence as "use command directly".
45
+ */
46
+ originalCommand: z.string().min(1).optional(),
37
47
  args: z.array(z.string()).default([]),
38
48
  env: z.record(z.string()).default({}),
39
49
  /**
@@ -70,6 +80,22 @@ export async function connect(serverName, config, options = {}) {
70
80
  env: { ...process.env, ...config.env },
71
81
  stdio: ['pipe', 'pipe', 'pipe'],
72
82
  });
83
+ // L13: optional stderr → log file. We open the stream lazily so a bad
84
+ // path surfaces synchronously to the caller instead of getting buried
85
+ // in the child's stderr handler. `mkdir -p` of the parent dir is the
86
+ // caller's responsibility (the registry does it once per session).
87
+ let logStream;
88
+ if (options.logFile) {
89
+ try {
90
+ logStream = createWriteStream(options.logFile, { flags: 'a' });
91
+ }
92
+ catch {
93
+ // Log directory missing or unwritable — degrade silently rather
94
+ // than refuse the handshake. The operator still gets the same
95
+ // surface they had pre-L13 (stderr dropped on the floor).
96
+ logStream = undefined;
97
+ }
98
+ }
73
99
  const connection = {
74
100
  serverName,
75
101
  child,
@@ -77,6 +103,8 @@ export async function connect(serverName, config, options = {}) {
77
103
  pending: new Map(),
78
104
  nextId: 1,
79
105
  closed: false,
106
+ ...(logStream ? { logStream } : {}),
107
+ startedAt: Date.now(),
80
108
  };
81
109
  // Attach the spawn-error handler BEFORE the reader: ENOENT and similar
82
110
  // are emitted asynchronously on the next tick, and without this
@@ -184,11 +212,25 @@ export async function callTool(connection, name, args, options = {}) {
184
212
  * SIGKILL if the child has not exited. Safe to call multiple times.
185
213
  */
186
214
  export async function disconnect(connection) {
187
- if (connection.closed)
215
+ const closeLogStream = () => {
216
+ const stream = connection.logStream;
217
+ if (!stream)
218
+ return;
219
+ try {
220
+ stream.end();
221
+ }
222
+ catch {
223
+ // Best-effort — stream may already be destroyed.
224
+ }
225
+ };
226
+ if (connection.closed) {
227
+ closeLogStream();
188
228
  return;
229
+ }
189
230
  const { child } = connection;
190
231
  if (child.exitCode !== null || child.killed) {
191
232
  connection.closed = true;
233
+ closeLogStream();
192
234
  return;
193
235
  }
194
236
  return new Promise((resolveDone) => {
@@ -198,6 +240,7 @@ export async function disconnect(connection) {
198
240
  return;
199
241
  settled = true;
200
242
  connection.closed = true;
243
+ closeLogStream();
201
244
  resolveDone();
202
245
  };
203
246
  child.once('exit', settle);
@@ -223,6 +266,22 @@ export async function disconnect(connection) {
223
266
  }, SHUTDOWN_GRACE_MS);
224
267
  });
225
268
  }
269
+ /**
270
+ * L13: cheap liveness probe for `pugi mcp doctor`. True when the child
271
+ * process is still running AND the connection has not been torn down.
272
+ * Does NOT issue a JSON-RPC call — that would race with in-flight tool
273
+ * dispatch and the doctor surface is read-only.
274
+ */
275
+ export function isAlive(connection) {
276
+ if (connection.closed)
277
+ return false;
278
+ const { child } = connection;
279
+ if (child.killed)
280
+ return false;
281
+ if (child.exitCode !== null)
282
+ return false;
283
+ return true;
284
+ }
226
285
  function writeFrame(connection, message) {
227
286
  if (connection.closed) {
228
287
  throw new Error(`mcp server "${connection.serverName}" connection is closed`);
@@ -274,11 +333,21 @@ function attachReader(connection) {
274
333
  newlineIndex = buffer.indexOf('\n');
275
334
  }
276
335
  });
277
- // stderr is captured but never thrown. Some MCP servers log diagnostics
278
- // there even on success; we route it to nowhere so the CLI stdout stays
279
- // pure JSON envelope output. A future iteration may forward this to the
280
- // audit log.
281
- connection.child.stderr.on('data', () => { });
336
+ // stderr is captured and (when `connect({ logFile })` was set) mirrored
337
+ // to a per-server log file under `.pugi/logs/`. Operators can tail the
338
+ // file via `pugi mcp logs <server>`. Default sink remains "drop on the
339
+ // floor" so the CLI stdout stays pure JSON envelope output.
340
+ connection.child.stderr.on('data', (chunk) => {
341
+ const stream = connection.logStream;
342
+ if (!stream || stream.destroyed)
343
+ return;
344
+ try {
345
+ stream.write(typeof chunk === 'string' ? chunk : chunk);
346
+ }
347
+ catch {
348
+ // Disk full / fd revoked — drop silently. Mirroring is best-effort.
349
+ }
350
+ });
282
351
  }
283
352
  function handleLine(connection, line) {
284
353
  let parsed;