@pugi/cli 0.1.0-beta.12 → 0.1.0-beta.14
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.
- package/dist/core/consensus/diff-capture.js +73 -0
- package/dist/core/context/index.js +7 -0
- package/dist/core/context/markdown-traverse.js +255 -0
- package/dist/core/edits/dispatch.js +218 -2
- package/dist/core/edits/journal.js +199 -0
- package/dist/core/edits/layer-d-ast.js +557 -14
- package/dist/core/edits/verify-hook.js +273 -0
- package/dist/core/engine/anvil-client.js +99 -5
- package/dist/core/engine/context-prefix.js +155 -0
- package/dist/core/engine/intent.js +260 -0
- package/dist/core/engine/native-pugi.js +663 -249
- package/dist/core/engine/prompts.js +52 -2
- package/dist/core/engine/tool-bridge.js +311 -9
- package/dist/core/lsp/client.js +57 -0
- package/dist/core/mcp/client.js +9 -0
- package/dist/core/mcp/http-server.js +553 -0
- package/dist/core/mcp/permission.js +190 -0
- package/dist/core/mcp/server-tools.js +219 -0
- package/dist/core/mcp/server.js +397 -0
- package/dist/core/repl/history.js +11 -1
- package/dist/core/repl/model-pricing.js +135 -0
- package/dist/core/repl/session.js +328 -12
- package/dist/core/repl/slash-commands.js +18 -4
- package/dist/core/settings.js +43 -0
- package/dist/core/subagents/dispatcher-real.js +600 -0
- package/dist/core/subagents/dispatcher.js +113 -24
- package/dist/core/subagents/index.js +18 -5
- package/dist/core/subagents/isolation-matrix.js +213 -0
- package/dist/core/subagents/spawn.js +19 -4
- package/dist/core/transport/version-interceptor.js +166 -0
- package/dist/index.js +28 -0
- package/dist/runtime/bootstrap.js +190 -0
- package/dist/runtime/cli.js +859 -269
- package/dist/runtime/commands/lsp.js +165 -5
- package/dist/runtime/commands/mcp.js +537 -0
- package/dist/runtime/commands/review-consensus.js +17 -2
- package/dist/runtime/headless.js +543 -0
- package/dist/runtime/load-hooks-or-exit.js +71 -0
- package/dist/runtime/version.js +65 -0
- package/dist/tools/agent-tool.js +192 -0
- package/dist/tools/apply-patch.js +62 -1
- package/dist/tools/mcp-tool.js +260 -0
- package/dist/tools/multi-edit.js +361 -0
- package/dist/tools/registry.js +5 -0
- package/dist/tools/web-fetch.js +147 -2
- package/dist/tools/web-search.js +458 -0
- package/dist/tui/agent-tree.js +10 -0
- package/dist/tui/ask-modal.js +2 -2
- package/dist/tui/conversation-pane.js +1 -1
- package/dist/tui/input-box.js +1 -1
- package/dist/tui/markdown-render.js +4 -4
- package/dist/tui/repl-render.js +105 -15
- package/dist/tui/repl-splash.js +2 -2
- package/dist/tui/repl.js +10 -4
- package/dist/tui/splash.js +1 -1
- package/dist/tui/status-bar.js +94 -16
- package/dist/tui/update-banner.js +20 -2
- package/package.json +5 -4
|
@@ -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
|
|
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
|
|
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,
|
|
@@ -152,10 +203,119 @@ function usage() {
|
|
|
152
203
|
' pugi lsp hover <file> <line> <col>\n' +
|
|
153
204
|
' pugi lsp definition <file> <line> <col>\n' +
|
|
154
205
|
' pugi lsp references <file> <line> <col>\n' +
|
|
155
|
-
' pugi lsp diagnostics <file
|
|
206
|
+
' pugi lsp diagnostics <file>\n' +
|
|
207
|
+
' pugi lsp find_definition <file> <symbol>\n' +
|
|
208
|
+
' pugi lsp servers',
|
|
156
209
|
exitCode: 2,
|
|
157
210
|
};
|
|
158
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
|
+
}
|
|
159
319
|
function pullLangFlag(args) {
|
|
160
320
|
let lang;
|
|
161
321
|
const positional = [];
|