@pugi/cli 0.1.0-beta.2 → 0.1.0-beta.20

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 (130) hide show
  1. package/THIRD_PARTY_NOTICES.md +40 -0
  2. package/assets/pugi-mascot.ansi +15 -40
  3. package/bin/run.js +33 -1
  4. package/dist/commands/jobs-watch.js +201 -0
  5. package/dist/commands/jobs.js +15 -0
  6. package/dist/core/agent-progress/cleanup.js +134 -0
  7. package/dist/core/agent-progress/schema.js +144 -0
  8. package/dist/core/agent-progress/writer.js +101 -0
  9. package/dist/core/compact/auto-trigger.js +96 -0
  10. package/dist/core/compact/buffer-rewriter.js +115 -0
  11. package/dist/core/compact/summarizer.js +196 -0
  12. package/dist/core/compact/token-counter.js +108 -0
  13. package/dist/core/consensus/diff-capture.js +73 -0
  14. package/dist/core/context/index.js +7 -0
  15. package/dist/core/context/markdown-traverse.js +255 -0
  16. package/dist/core/cost/rate-card.js +129 -0
  17. package/dist/core/cost/tracker.js +221 -0
  18. package/dist/core/denial-tracking/index.js +8 -0
  19. package/dist/core/denial-tracking/state.js +264 -0
  20. package/dist/core/diagnostics/probe-runner.js +93 -0
  21. package/dist/core/diagnostics/probes/api.js +46 -0
  22. package/dist/core/diagnostics/probes/auth.js +86 -0
  23. package/dist/core/diagnostics/probes/cli-version.js +127 -0
  24. package/dist/core/diagnostics/probes/config.js +72 -0
  25. package/dist/core/diagnostics/probes/denial-tracking.js +57 -0
  26. package/dist/core/diagnostics/probes/disk.js +81 -0
  27. package/dist/core/diagnostics/probes/git.js +65 -0
  28. package/dist/core/diagnostics/probes/mcp.js +75 -0
  29. package/dist/core/diagnostics/probes/node.js +59 -0
  30. package/dist/core/diagnostics/probes/pnpm.js +36 -0
  31. package/dist/core/diagnostics/probes/session.js +74 -0
  32. package/dist/core/diagnostics/probes/status-snapshot.js +442 -0
  33. package/dist/core/diagnostics/probes/workspace.js +63 -0
  34. package/dist/core/diagnostics/types.js +70 -0
  35. package/dist/core/edits/dispatch.js +218 -2
  36. package/dist/core/edits/journal.js +199 -0
  37. package/dist/core/edits/layer-d-ast.js +557 -14
  38. package/dist/core/edits/verify-hook.js +273 -0
  39. package/dist/core/edits/worktree.js +111 -18
  40. package/dist/core/engine/anvil-client.js +115 -5
  41. package/dist/core/engine/budgets.js +89 -0
  42. package/dist/core/engine/context-prefix.js +155 -0
  43. package/dist/core/engine/intent.js +260 -0
  44. package/dist/core/engine/native-pugi.js +744 -210
  45. package/dist/core/engine/prompts.js +61 -6
  46. package/dist/core/engine/strip-internal-fields.js +124 -0
  47. package/dist/core/engine/tool-bridge.js +818 -31
  48. package/dist/core/file-cache.js +113 -1
  49. package/dist/core/init/scaffold.js +195 -0
  50. package/dist/core/lsp/client.js +174 -29
  51. package/dist/core/mcp/client.js +75 -6
  52. package/dist/core/mcp/http-server.js +553 -0
  53. package/dist/core/mcp/permission.js +190 -0
  54. package/dist/core/mcp/registry.js +24 -2
  55. package/dist/core/mcp/server-tools.js +219 -0
  56. package/dist/core/mcp/server.js +397 -0
  57. package/dist/core/permissions/gate.js +187 -0
  58. package/dist/core/permissions/index.js +18 -0
  59. package/dist/core/permissions/mode.js +102 -0
  60. package/dist/core/permissions/state.js +160 -0
  61. package/dist/core/permissions/tool-class.js +93 -0
  62. package/dist/core/repl/codebase-survey.js +308 -0
  63. package/dist/core/repl/history.js +11 -1
  64. package/dist/core/repl/init-interview.js +457 -0
  65. package/dist/core/repl/model-pricing.js +135 -0
  66. package/dist/core/repl/onboarding-state.js +297 -0
  67. package/dist/core/repl/session.js +719 -29
  68. package/dist/core/repl/slash-commands.js +133 -9
  69. package/dist/core/retry-budget/budget.js +284 -0
  70. package/dist/core/retry-budget/index.js +5 -0
  71. package/dist/core/settings.js +71 -0
  72. package/dist/core/skills/defaults.js +457 -0
  73. package/dist/core/subagents/dispatcher-real.js +600 -0
  74. package/dist/core/subagents/dispatcher.js +113 -24
  75. package/dist/core/subagents/index.js +18 -5
  76. package/dist/core/subagents/isolation-matrix.js +213 -0
  77. package/dist/core/subagents/spawn.js +19 -4
  78. package/dist/core/transport/version-interceptor.js +166 -0
  79. package/dist/index.js +28 -0
  80. package/dist/runtime/bootstrap.js +190 -0
  81. package/dist/runtime/cli.js +1588 -266
  82. package/dist/runtime/commands/compact.js +296 -0
  83. package/dist/runtime/commands/cost.js +199 -0
  84. package/dist/runtime/commands/delegate.js +289 -0
  85. package/dist/runtime/commands/doctor.js +369 -0
  86. package/dist/runtime/commands/lsp.js +187 -5
  87. package/dist/runtime/commands/mcp.js +824 -0
  88. package/dist/runtime/commands/patch.js +17 -0
  89. package/dist/runtime/commands/permissions.js +87 -0
  90. package/dist/runtime/commands/report.js +299 -0
  91. package/dist/runtime/commands/review-consensus.js +17 -2
  92. package/dist/runtime/commands/roster.js +117 -0
  93. package/dist/runtime/commands/status.js +178 -0
  94. package/dist/runtime/commands/worktree.js +50 -6
  95. package/dist/runtime/headless.js +543 -0
  96. package/dist/runtime/load-hooks-or-exit.js +71 -0
  97. package/dist/runtime/plan-decompose.js +531 -0
  98. package/dist/runtime/version.js +65 -0
  99. package/dist/tools/agent-tool.js +206 -0
  100. package/dist/tools/apply-patch.js +281 -39
  101. package/dist/tools/ask-user-question.js +213 -0
  102. package/dist/tools/ask-user.js +115 -0
  103. package/dist/tools/file-tools.js +85 -14
  104. package/dist/tools/mcp-tool.js +260 -0
  105. package/dist/tools/multi-edit.js +361 -0
  106. package/dist/tools/registry.js +22 -2
  107. package/dist/tools/skill-tool.js +96 -0
  108. package/dist/tools/tasks.js +208 -0
  109. package/dist/tools/web-fetch.js +147 -2
  110. package/dist/tools/web-search.js +458 -0
  111. package/dist/tui/agent-progress-card.js +111 -0
  112. package/dist/tui/agent-tree.js +10 -0
  113. package/dist/tui/ask-modal.js +2 -2
  114. package/dist/tui/ask-user-question-prompt.js +192 -0
  115. package/dist/tui/compact-banner.js +54 -0
  116. package/dist/tui/conversation-pane.js +69 -8
  117. package/dist/tui/cost-table.js +111 -0
  118. package/dist/tui/doctor-table.js +31 -0
  119. package/dist/tui/input-box.js +1 -1
  120. package/dist/tui/markdown-render.js +4 -4
  121. package/dist/tui/repl-render.js +276 -37
  122. package/dist/tui/repl-splash.js +2 -2
  123. package/dist/tui/repl.js +25 -6
  124. package/dist/tui/splash.js +1 -1
  125. package/dist/tui/status-bar.js +94 -16
  126. package/dist/tui/status-table.js +7 -0
  127. package/dist/tui/tool-stream-pane.js +7 -0
  128. package/dist/tui/update-banner.js +20 -2
  129. package/docs/examples/codegraph.mcp.json +10 -0
  130. package/package.json +9 -6
@@ -25,16 +25,63 @@
25
25
  * Brand voice: ASCII only, no emoji, no banned words.
26
26
  */
27
27
  import { extname } from 'node:path';
28
- import { startLspClient } from '../../core/lsp/client.js';
28
+ import { inspectLspServers, startLspClient } from '../../core/lsp/client.js';
29
+ import { loadSettings } from '../../core/settings.js';
29
30
  export async function runLspCommand(args, opts) {
30
31
  const [op, file, ...rest] = args;
31
- if (!op || !file) {
32
+ if (!op) {
32
33
  return usage();
33
34
  }
35
+ // β7 L9: introspection subcommand. `pugi lsp servers` reports the
36
+ // language matrix — binary-on-PATH + enabled-via-settings — so the
37
+ // operator can debug `lsp_unavailable` vs `lsp_disabled` without
38
+ // re-running an actual hover.
39
+ if (op === 'servers' || op === 'status') {
40
+ const settings = loadSettings(opts.cwd);
41
+ const lspSettings = settings.lsp;
42
+ const rows = inspectLspServers(lspSettings);
43
+ if (opts.json) {
44
+ return { ok: true, text: JSON.stringify(rows, null, 2), exitCode: 0 };
45
+ }
46
+ const lines = ['language\tcommand\tavailable\tenabled'];
47
+ for (const r of rows) {
48
+ lines.push(`${r.language}\t${r.command}\t${r.available ? 'yes' : 'no'}\t${r.enabled ? 'yes' : 'no'}`);
49
+ }
50
+ return { ok: true, text: lines.join('\n'), exitCode: 0 };
51
+ }
52
+ if (!file) {
53
+ return usage();
54
+ }
55
+ // β7 L6: `find_definition` convenience subcommand. Identical wire to
56
+ // `definition` but takes `<symbol>` instead of <line> <col>. We grep
57
+ // the workspace for the first hit on `<symbol>` (whole-word match) to
58
+ // recover a position, then route through LSP. Falls back cleanly when
59
+ // the symbol is not present in the file.
60
+ if (op === 'find_definition' || op === 'find-definition') {
61
+ const { lang: explicitLangFD, positional: positionalFD } = pullLangFlag(rest);
62
+ const symbol = positionalFD[0];
63
+ if (!symbol) {
64
+ return {
65
+ ok: false,
66
+ text: 'Usage: pugi lsp find_definition <file> <symbol> [--lang ts|js|py|go|rust]',
67
+ exitCode: 2,
68
+ };
69
+ }
70
+ const lang = explicitLangFD ?? inferLanguage(file);
71
+ if (!lang) {
72
+ return {
73
+ ok: false,
74
+ text: `cannot infer language from ${file}; pass --lang ts|js|py|go|rust. ` +
75
+ `Supported extensions: .ts/.tsx, .js/.jsx/.mjs, .py, .go, .rs`,
76
+ exitCode: 2,
77
+ };
78
+ }
79
+ return await findDefinition(file, symbol, lang, opts);
80
+ }
34
81
  if (!['hover', 'definition', 'references', 'diagnostics'].includes(op)) {
35
82
  return {
36
83
  ok: false,
37
- text: `unknown lsp operation: ${op}. Supported: hover, definition, references, diagnostics`,
84
+ text: `unknown lsp operation: ${op}. Supported: hover, definition, references, diagnostics, find_definition, servers`,
38
85
  exitCode: 2,
39
86
  };
40
87
  }
@@ -48,7 +95,11 @@ export async function runLspCommand(args, opts) {
48
95
  exitCode: 2,
49
96
  };
50
97
  }
51
- const clientResult = await startLspClient(lang, { cwd: opts.cwd });
98
+ const settings = loadSettings(opts.cwd);
99
+ const clientResult = await startLspClient(lang, {
100
+ cwd: opts.cwd,
101
+ ...(settings.lsp ? { lspSettings: settings.lsp } : {}),
102
+ });
52
103
  if (!clientResult.ok) {
53
104
  return {
54
105
  ok: false,
@@ -57,6 +108,20 @@ export async function runLspCommand(args, opts) {
57
108
  };
58
109
  }
59
110
  const client = clientResult.value;
111
+ // R1 fix (2026-05-26, PR #413 r1, P2 #12): propagate SIGINT/SIGTERM
112
+ // to the LSP child process. Without this, ^C in the middle of a
113
+ // hung definition request would kill the CLI but leave the spawned
114
+ // language server orphaned (especially expensive for rust-analyzer
115
+ // / pyright which hold workspace indices in memory). We register
116
+ // listeners narrowly scoped to this single command invocation and
117
+ // tear them down in the `finally` block.
118
+ let interrupted = false;
119
+ const signalHandler = () => {
120
+ interrupted = true;
121
+ void client.stop();
122
+ };
123
+ process.once('SIGINT', signalHandler);
124
+ process.once('SIGTERM', signalHandler);
60
125
  try {
61
126
  if (op === 'diagnostics') {
62
127
  const result = await client.diagnostics(file);
@@ -120,7 +185,15 @@ export async function runLspCommand(args, opts) {
120
185
  return { ok: true, text: lines.join('\n') || 'no references', exitCode: 0 };
121
186
  }
122
187
  finally {
188
+ process.removeListener('SIGINT', signalHandler);
189
+ process.removeListener('SIGTERM', signalHandler);
123
190
  await client.stop();
191
+ if (interrupted) {
192
+ // Propagate the interruption so the shell sees a sensible exit
193
+ // code on ^C rather than the last successful result code.
194
+ // 130 is the canonical "terminated by SIGINT" exit value.
195
+ return { ok: false, text: 'lsp aborted by signal', exitCode: 130 };
196
+ }
124
197
  }
125
198
  }
126
199
  function usage() {
@@ -130,10 +203,119 @@ function usage() {
130
203
  ' pugi lsp hover <file> <line> <col>\n' +
131
204
  ' pugi lsp definition <file> <line> <col>\n' +
132
205
  ' pugi lsp references <file> <line> <col>\n' +
133
- ' pugi lsp diagnostics <file>',
206
+ ' pugi lsp diagnostics <file>\n' +
207
+ ' pugi lsp find_definition <file> <symbol>\n' +
208
+ ' pugi lsp servers',
134
209
  exitCode: 2,
135
210
  };
136
211
  }
212
+ /**
213
+ * β7 L6: find_definition convenience.
214
+ *
215
+ * Reads the file, walks line-by-line looking for the first whole-word
216
+ * match of `<symbol>`, then runs `lsp definition` at that position.
217
+ * The result is a normal location array — empty when the LSP server
218
+ * can't resolve the symbol (typically because the workspace TS server
219
+ * hasn't indexed the import yet).
220
+ *
221
+ * "Whole-word match" uses the same identifier-boundary rules as Layer
222
+ * D's tokenizer: matches inside string literals / comments / partial
223
+ * identifiers are skipped. This keeps the result aligned with what the
224
+ * operator means by "where is foo defined" — not "any character sequence
225
+ * spelling foo".
226
+ */
227
+ async function findDefinition(file, symbol, lang, opts) {
228
+ const { readFileSync, existsSync } = await import('node:fs');
229
+ const { resolve } = await import('node:path');
230
+ const abs = resolve(opts.cwd, file);
231
+ if (!existsSync(abs)) {
232
+ return { ok: false, text: `file not found: ${file}`, exitCode: 1 };
233
+ }
234
+ let body;
235
+ try {
236
+ body = readFileSync(abs, 'utf8');
237
+ }
238
+ catch (error) {
239
+ const detail = error instanceof Error ? error.message : String(error);
240
+ return { ok: false, text: `cannot read ${file}: ${detail}`, exitCode: 1 };
241
+ }
242
+ const position = locateIdentifier(body, symbol);
243
+ if (!position) {
244
+ return {
245
+ ok: false,
246
+ text: `symbol '${symbol}' not found in ${file}`,
247
+ exitCode: 1,
248
+ };
249
+ }
250
+ const settings = loadSettings(opts.cwd);
251
+ const clientResult = await startLspClient(lang, {
252
+ cwd: opts.cwd,
253
+ ...(settings.lsp ? { lspSettings: settings.lsp } : {}),
254
+ });
255
+ if (!clientResult.ok) {
256
+ return { ok: false, text: `${clientResult.reason}: ${clientResult.detail}`, exitCode: 1 };
257
+ }
258
+ const client = clientResult.value;
259
+ try {
260
+ const result = await client.definition(file, position);
261
+ if (!result.ok) {
262
+ return { ok: false, text: `${result.reason}: ${result.detail}`, exitCode: 1 };
263
+ }
264
+ if (opts.json) {
265
+ return {
266
+ ok: true,
267
+ text: JSON.stringify({
268
+ symbol,
269
+ file,
270
+ sourcePosition: { line: position.line + 1, character: position.character + 1 },
271
+ definitions: result.value,
272
+ }, null, 2),
273
+ exitCode: 0,
274
+ };
275
+ }
276
+ if (result.value.length === 0) {
277
+ return { ok: true, text: `no definition for ${symbol}`, exitCode: 0 };
278
+ }
279
+ const lines = result.value.map((loc) => `${loc.path || loc.uri}:${loc.range.start.line + 1}:${loc.range.start.character + 1}`);
280
+ return { ok: true, text: lines.join('\n'), exitCode: 0 };
281
+ }
282
+ finally {
283
+ await client.stop();
284
+ }
285
+ }
286
+ /**
287
+ * Find the first whole-word occurrence of `symbol` in `body`. Returns
288
+ * the 0-based LSP position (line + character) or null when absent.
289
+ * Uses the same identifier boundary rule as Layer D's tokenizer to
290
+ * avoid matching inside larger identifiers.
291
+ */
292
+ export function locateIdentifier(body, symbol) {
293
+ const lines = body.split('\n');
294
+ // Build the boundary regex once. We can't use `\b` directly because
295
+ // it treats `$` as a word boundary and TS/JS allow `$` in identifiers;
296
+ // implement the boundary check with a manual lookaround.
297
+ const isIdent = (ch) => {
298
+ if (!ch)
299
+ return false;
300
+ return /[A-Za-z0-9_$]/.test(ch);
301
+ };
302
+ for (let line = 0; line < lines.length; line += 1) {
303
+ const text = lines[line];
304
+ let from = 0;
305
+ while (from < text.length) {
306
+ const idx = text.indexOf(symbol, from);
307
+ if (idx === -1)
308
+ break;
309
+ const before = idx > 0 ? text[idx - 1] : undefined;
310
+ const after = idx + symbol.length < text.length ? text[idx + symbol.length] : undefined;
311
+ if (!isIdent(before) && !isIdent(after)) {
312
+ return { line, character: idx };
313
+ }
314
+ from = idx + symbol.length;
315
+ }
316
+ }
317
+ return null;
318
+ }
137
319
  function pullLangFlag(args) {
138
320
  let lang;
139
321
  const positional = [];