@pugi/cli 0.1.0-beta.88 → 0.1.0-beta.89

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 (37) hide show
  1. package/dist/core/auth/env-provider.js +1 -1
  2. package/dist/core/context/markdown-traverse.js +1 -1
  3. package/dist/core/credentials.js +1 -1
  4. package/dist/core/engine/anvil-client.js +63 -0
  5. package/dist/core/engine/native-pugi.js +1 -1
  6. package/dist/core/engine/tool-bridge.js +436 -0
  7. package/dist/core/hooks/events.js +3 -1
  8. package/dist/core/hooks/registry.js +3 -0
  9. package/dist/core/hooks/worktree-events.js +158 -0
  10. package/dist/core/lsp/client.js +453 -0
  11. package/dist/core/lsp/server-detect.js +173 -0
  12. package/dist/core/lsp/symbol-cache.js +162 -0
  13. package/dist/core/lsp/symbol-tools.js +296 -4
  14. package/dist/core/mcp/server.js +1 -1
  15. package/dist/core/repl/ask.js +1 -1
  16. package/dist/core/repl/session.js +3 -3
  17. package/dist/core/repl/slash-commands.js +1 -1
  18. package/dist/core/settings.js +26 -0
  19. package/dist/core/worktree/include-parser.js +249 -0
  20. package/dist/runtime/cli.js +108 -8
  21. package/dist/runtime/commands/agents.js +1 -1
  22. package/dist/runtime/commands/hooks.js +3 -0
  23. package/dist/runtime/commands/review-consensus.js +1 -1
  24. package/dist/runtime/version.js +1 -1
  25. package/dist/runtime/worktree-bootstrap.js +579 -0
  26. package/dist/tools/lsp-tools.js +377 -1
  27. package/dist/tools/registry.js +23 -0
  28. package/dist/tui/input-box.js +1 -1
  29. package/dist/tui/render.js +1 -1
  30. package/dist/tui/repl.js +1 -1
  31. package/dist/tui/status-bar.js +1 -1
  32. package/dist/tui/update-banner.js +1 -1
  33. package/package.json +3 -3
  34. package/test/scenarios/compact-force.scenario.txt +3 -2
  35. package/test/scenarios/identity.scenario.txt +6 -5
  36. package/test/scenarios/persona-handoff.scenario.txt +2 -1
  37. package/test/scenarios/walkback.scenario.txt +6 -6
@@ -1,5 +1,7 @@
1
+ import { callHierarchyAt, codeActionsAt, diagnosticsFor, findDefinition as symbolsFindDefinition, findImplementations, findReferences as symbolsFindReferences, findTypeDefinition, findWorkspaceSymbol, formatFile, hoverSymbol, listDocumentSymbols, renameSymbol, signatureAt, transportFromLspClient, } from '../core/lsp/symbol-tools.js';
1
2
  import { gateOnCancellation, OperatorAbortedError } from './file-tools.js';
2
3
  import { recordToolCall, recordToolResult } from '../core/session.js';
4
+ import { getGlobalSymbolCache, SymbolCache } from '../core/lsp/symbol-cache.js';
3
5
  /** Cap for any single LSP tool's payload size. Keeps model context lean. */
4
6
  const LSP_PAYLOAD_CAP_BYTES = 8 * 1024;
5
7
  export async function lspHover(ctx, lang, file, line, col) {
@@ -184,6 +186,380 @@ function capDiagnostics(items) {
184
186
  }
185
187
  return { value: items.slice(0, 1), truncated: true };
186
188
  }
189
+ /* ------------------------------------------------------------------------- */
190
+ /* PUGI-78 Phase 1: symbols.* namespace tools (13 categories). */
191
+ /* ------------------------------------------------------------------------- */
192
+ /**
193
+ * PUGI-78 Phase 1: symbols.find_definition.
194
+ *
195
+ * Pugi reads a whole file (~5-50 KB tokens) every time the model wants
196
+ * to know "where is foo defined?". The LSP wire here returns ~200 bytes
197
+ * (file + line + character). Two orders of magnitude reduction in token
198
+ * cost per refactor turn, hence the BIG-leverage classification in the
199
+ * spec (10-100x token savings per refactor).
200
+ *
201
+ * Failure folds to `lsp_unavailable` when no client is registered for
202
+ * the inferred language. The agent surface is expected to fall back to
203
+ * grep when this fires (the tool-preference prompt rule steers it).
204
+ */
205
+ export async function symbolsFindDefinitionTool(ctx, lang, file, line, col) {
206
+ const toolCallId = recordToolCall(ctx.session, 'symbols_find_definition', `${lang}:${file}:${line}:${col}`);
207
+ return guard(ctx, 'symbols_find_definition', toolCallId, async () => {
208
+ const client = ctx.lspClients?.get(lang);
209
+ if (!client)
210
+ return unavailable(lang);
211
+ const cache = getSymbolCacheFromCtx(ctx);
212
+ const cwd = workspaceForCache(ctx);
213
+ const key = SymbolCache.makeKey(lang, cwd, 'find_definition', { file, line, col });
214
+ const cached = cache?.get(key);
215
+ if (cached !== undefined) {
216
+ if (cached === null)
217
+ return notFound('definition');
218
+ return { ok: true, value: cached };
219
+ }
220
+ const result = await symbolsFindDefinition(transportFromLspClient(client), file, line, col);
221
+ cache?.set(key, result);
222
+ if (!result)
223
+ return notFound('definition');
224
+ return { ok: true, value: result };
225
+ });
226
+ }
227
+ /**
228
+ * PUGI-78 Phase 1: symbols.find_references.
229
+ *
230
+ * Returns the flat list of call sites for the symbol at (line, col).
231
+ * The 200-row cap from the legacy `lspReferences` carries over via
232
+ * `capSymbolReferences`.
233
+ */
234
+ export async function symbolsFindReferencesTool(ctx, lang, file, line, col) {
235
+ const toolCallId = recordToolCall(ctx.session, 'symbols_find_references', `${lang}:${file}:${line}:${col}`);
236
+ return guard(ctx, 'symbols_find_references', toolCallId, async () => {
237
+ const client = ctx.lspClients?.get(lang);
238
+ if (!client)
239
+ return unavailable(lang);
240
+ const cache = getSymbolCacheFromCtx(ctx);
241
+ const cwd = workspaceForCache(ctx);
242
+ const key = SymbolCache.makeKey(lang, cwd, 'find_references', { file, line, col });
243
+ const cached = cache?.get(key);
244
+ if (cached !== undefined) {
245
+ const capped = capSymbolReferences(cached);
246
+ return { ok: true, value: capped.value, ...(capped.truncated ? { truncated: true } : {}) };
247
+ }
248
+ const result = await symbolsFindReferences(transportFromLspClient(client), file, line, col);
249
+ cache?.set(key, result);
250
+ const capped = capSymbolReferences(result);
251
+ return { ok: true, value: capped.value, ...(capped.truncated ? { truncated: true } : {}) };
252
+ });
253
+ }
254
+ /**
255
+ * PUGI-78 Phase 1: symbols.list_in_file.
256
+ *
257
+ * Outline view — the document-symbol surface. Returns the flat list of
258
+ * top-level + nested symbols with their kind. Cache HIT on the same
259
+ * file across consecutive turns is the common case (the model usually
260
+ * lists, then dives into a single symbol).
261
+ */
262
+ export async function symbolsListInFileTool(ctx, lang, file) {
263
+ const toolCallId = recordToolCall(ctx.session, 'symbols_list_in_file', `${lang}:${file}`);
264
+ return guard(ctx, 'symbols_list_in_file', toolCallId, async () => {
265
+ const client = ctx.lspClients?.get(lang);
266
+ if (!client)
267
+ return unavailable(lang);
268
+ const cache = getSymbolCacheFromCtx(ctx);
269
+ const cwd = workspaceForCache(ctx);
270
+ const key = SymbolCache.makeKey(lang, cwd, 'list_in_file', { file });
271
+ const cached = cache?.get(key);
272
+ if (cached !== undefined) {
273
+ return { ok: true, value: [...cached] };
274
+ }
275
+ const result = await listDocumentSymbols(transportFromLspClient(client), file);
276
+ cache?.set(key, result);
277
+ return { ok: true, value: result };
278
+ });
279
+ }
280
+ /**
281
+ * PUGI-78 Phase 1: symbols.rename.
282
+ *
283
+ * Returns the workspace-edit preview the server proposes. The
284
+ * dispatcher applies via `apply_patch` in a future ticket; Phase 1 is
285
+ * read-only — the agent surface gets the affected file list + per-edit
286
+ * line/character so the model can summarize the change.
287
+ */
288
+ export async function symbolsRenameTool(ctx, lang, file, line, col, newName) {
289
+ const toolCallId = recordToolCall(ctx.session, 'symbols_rename', `${lang}:${file}:${line}:${col}:${newName}`);
290
+ return guard(ctx, 'symbols_rename', toolCallId, async () => {
291
+ const client = ctx.lspClients?.get(lang);
292
+ if (!client)
293
+ return unavailable(lang);
294
+ // No cache for rename — the edit set depends on the live source
295
+ // file state. A subsequent rename of the same symbol after the
296
+ // first applied would otherwise return stale edits.
297
+ const result = await renameSymbol(transportFromLspClient(client), file, line, col, newName);
298
+ if (!result)
299
+ return notFound('rename');
300
+ return { ok: true, value: result };
301
+ });
302
+ }
303
+ /**
304
+ * PUGI-78 Phase 1: symbols.hover.
305
+ *
306
+ * Returns the hover content (type info + docstring) at the position.
307
+ * Body capped at 4 KB so a verbose generic does not blow context.
308
+ */
309
+ export async function symbolsHoverTool(ctx, lang, file, line, col) {
310
+ const toolCallId = recordToolCall(ctx.session, 'symbols_hover', `${lang}:${file}:${line}:${col}`);
311
+ return guard(ctx, 'symbols_hover', toolCallId, async () => {
312
+ const client = ctx.lspClients?.get(lang);
313
+ if (!client)
314
+ return unavailable(lang);
315
+ const cache = getSymbolCacheFromCtx(ctx);
316
+ const cwd = workspaceForCache(ctx);
317
+ const key = SymbolCache.makeKey(lang, cwd, 'hover', { file, line, col });
318
+ const cached = cache?.get(key);
319
+ if (cached !== undefined) {
320
+ if (cached === null)
321
+ return notFound('hover');
322
+ const next = {
323
+ ok: true,
324
+ value: { content: cached.content, ...(cached.range ? { range: cached.range } : {}) },
325
+ ...(cached.truncated ? { truncated: true } : {}),
326
+ };
327
+ return next;
328
+ }
329
+ const result = await hoverSymbol(transportFromLspClient(client), file, line, col);
330
+ cache?.set(key, result);
331
+ if (!result)
332
+ return notFound('hover');
333
+ return {
334
+ ok: true,
335
+ value: { content: result.content, ...(result.range ? { range: result.range } : {}) },
336
+ ...(result.truncated ? { truncated: true } : {}),
337
+ };
338
+ });
339
+ }
340
+ /**
341
+ * PUGI-78 Phase 1: symbols.signature.
342
+ *
343
+ * Function signature at a call site. Returns the active overload's
344
+ * label, parameters, and active-parameter index.
345
+ */
346
+ export async function symbolsSignatureTool(ctx, lang, file, line, col) {
347
+ const toolCallId = recordToolCall(ctx.session, 'symbols_signature', `${lang}:${file}:${line}:${col}`);
348
+ return guard(ctx, 'symbols_signature', toolCallId, async () => {
349
+ const client = ctx.lspClients?.get(lang);
350
+ if (!client)
351
+ return unavailable(lang);
352
+ const result = await signatureAt(transportFromLspClient(client), file, line, col);
353
+ if (!result)
354
+ return notFound('signature');
355
+ return { ok: true, value: result };
356
+ });
357
+ }
358
+ /**
359
+ * PUGI-78 Phase 1: symbols.workspace_symbols.
360
+ *
361
+ * Workspace-wide fuzzy search. The server picks the matching algorithm
362
+ * (substring / fuzzy / prefix); we forward verbatim.
363
+ */
364
+ export async function symbolsWorkspaceSymbolsTool(ctx, lang, query) {
365
+ const toolCallId = recordToolCall(ctx.session, 'symbols_workspace_symbols', `${lang}:${query}`);
366
+ return guard(ctx, 'symbols_workspace_symbols', toolCallId, async () => {
367
+ const client = ctx.lspClients?.get(lang);
368
+ if (!client)
369
+ return unavailable(lang);
370
+ const cache = getSymbolCacheFromCtx(ctx);
371
+ const cwd = workspaceForCache(ctx);
372
+ const key = SymbolCache.makeKey(lang, cwd, 'workspace_symbols', { query });
373
+ const cached = cache?.get(key);
374
+ if (cached !== undefined) {
375
+ return { ok: true, value: [...cached] };
376
+ }
377
+ const result = await findWorkspaceSymbol(transportFromLspClient(client), query);
378
+ cache?.set(key, result);
379
+ return { ok: true, value: result };
380
+ });
381
+ }
382
+ /**
383
+ * PUGI-78 Phase 1: symbols.call_hierarchy.
384
+ *
385
+ * Returns the incoming + outgoing call edges at the symbol position.
386
+ * The two arrays are independent — a function may have many incoming
387
+ * callers and zero outgoing calls (a pure leaf), or vice versa.
388
+ */
389
+ export async function symbolsCallHierarchyTool(ctx, lang, file, line, col) {
390
+ const toolCallId = recordToolCall(ctx.session, 'symbols_call_hierarchy', `${lang}:${file}:${line}:${col}`);
391
+ return guard(ctx, 'symbols_call_hierarchy', toolCallId, async () => {
392
+ const client = ctx.lspClients?.get(lang);
393
+ if (!client)
394
+ return unavailable(lang);
395
+ const cache = getSymbolCacheFromCtx(ctx);
396
+ const cwd = workspaceForCache(ctx);
397
+ const key = SymbolCache.makeKey(lang, cwd, 'call_hierarchy', { file, line, col });
398
+ const cached = cache?.get(key);
399
+ if (cached !== undefined) {
400
+ return { ok: true, value: { incoming: [...cached.incoming], outgoing: [...cached.outgoing] } };
401
+ }
402
+ const result = await callHierarchyAt(transportFromLspClient(client), file, line, col);
403
+ cache?.set(key, result);
404
+ return { ok: true, value: result };
405
+ });
406
+ }
407
+ /**
408
+ * PUGI-78 Phase 1: symbols.implementations.
409
+ *
410
+ * Implementations of an interface / abstract method. Returns the flat
411
+ * list of concrete sites.
412
+ */
413
+ export async function symbolsImplementationsTool(ctx, lang, file, line, col) {
414
+ const toolCallId = recordToolCall(ctx.session, 'symbols_implementations', `${lang}:${file}:${line}:${col}`);
415
+ return guard(ctx, 'symbols_implementations', toolCallId, async () => {
416
+ const client = ctx.lspClients?.get(lang);
417
+ if (!client)
418
+ return unavailable(lang);
419
+ const cache = getSymbolCacheFromCtx(ctx);
420
+ const cwd = workspaceForCache(ctx);
421
+ const key = SymbolCache.makeKey(lang, cwd, 'implementations', { file, line, col });
422
+ const cached = cache?.get(key);
423
+ if (cached !== undefined) {
424
+ const capped = capSymbolReferences(cached);
425
+ return { ok: true, value: capped.value, ...(capped.truncated ? { truncated: true } : {}) };
426
+ }
427
+ const result = await findImplementations(transportFromLspClient(client), file, line, col);
428
+ cache?.set(key, result);
429
+ const capped = capSymbolReferences(result);
430
+ return { ok: true, value: capped.value, ...(capped.truncated ? { truncated: true } : {}) };
431
+ });
432
+ }
433
+ /**
434
+ * PUGI-78 Phase 1: symbols.type_definition.
435
+ *
436
+ * Type definition (vs value definition). Useful for "what type is foo?"
437
+ * questions when the model has a variable and wants its type declaration.
438
+ */
439
+ export async function symbolsTypeDefinitionTool(ctx, lang, file, line, col) {
440
+ const toolCallId = recordToolCall(ctx.session, 'symbols_type_definition', `${lang}:${file}:${line}:${col}`);
441
+ return guard(ctx, 'symbols_type_definition', toolCallId, async () => {
442
+ const client = ctx.lspClients?.get(lang);
443
+ if (!client)
444
+ return unavailable(lang);
445
+ const cache = getSymbolCacheFromCtx(ctx);
446
+ const cwd = workspaceForCache(ctx);
447
+ const key = SymbolCache.makeKey(lang, cwd, 'type_definition', { file, line, col });
448
+ const cached = cache?.get(key);
449
+ if (cached !== undefined) {
450
+ if (cached === null)
451
+ return notFound('type_definition');
452
+ return { ok: true, value: cached };
453
+ }
454
+ const result = await findTypeDefinition(transportFromLspClient(client), file, line, col);
455
+ cache?.set(key, result);
456
+ if (!result)
457
+ return notFound('type_definition');
458
+ return { ok: true, value: result };
459
+ });
460
+ }
461
+ /**
462
+ * PUGI-78 Phase 1: symbols.code_actions.
463
+ *
464
+ * Quick-fix list at the given range. Server-provided actions include
465
+ * auto-import, unused-import removal, refactor extractions, etc.
466
+ */
467
+ export async function symbolsCodeActionsTool(ctx, lang, file, startLine, startChar, endLine, endChar) {
468
+ const toolCallId = recordToolCall(ctx.session, 'symbols_code_actions', `${lang}:${file}:${startLine}:${startChar}-${endLine}:${endChar}`);
469
+ return guard(ctx, 'symbols_code_actions', toolCallId, async () => {
470
+ const client = ctx.lspClients?.get(lang);
471
+ if (!client)
472
+ return unavailable(lang);
473
+ const result = await codeActionsAt(transportFromLspClient(client), file, startLine, startChar, endLine, endChar);
474
+ return { ok: true, value: result };
475
+ });
476
+ }
477
+ /**
478
+ * PUGI-78 Phase 1: symbols.format.
479
+ *
480
+ * Formatter — returns the text edits the LSP server would apply for
481
+ * `textDocument/formatting`. Phase 1 is read-only (returns the edits);
482
+ * the dispatcher composes + applies in a future ticket.
483
+ */
484
+ export async function symbolsFormatTool(ctx, lang, file, options) {
485
+ const toolCallId = recordToolCall(ctx.session, 'symbols_format', `${lang}:${file}`);
486
+ return guard(ctx, 'symbols_format', toolCallId, async () => {
487
+ const client = ctx.lspClients?.get(lang);
488
+ if (!client)
489
+ return unavailable(lang);
490
+ const result = await formatFile(transportFromLspClient(client), file, options);
491
+ return { ok: true, value: result };
492
+ });
493
+ }
494
+ /**
495
+ * PUGI-78 Phase 1: symbols.diagnostics.
496
+ *
497
+ * Pulls the cached `publishDiagnostics` payload for the file. Mirrors
498
+ * the legacy `lspDiagnostics` tool but exposed via the namespaced
499
+ * `symbols.*` surface for discoverability.
500
+ */
501
+ export async function symbolsDiagnosticsTool(ctx, lang, file) {
502
+ const toolCallId = recordToolCall(ctx.session, 'symbols_diagnostics', `${lang}:${file}`);
503
+ return guard(ctx, 'symbols_diagnostics', toolCallId, async () => {
504
+ const client = ctx.lspClients?.get(lang);
505
+ if (!client)
506
+ return unavailable(lang);
507
+ const result = await diagnosticsFor(transportFromLspClient(client), file);
508
+ const capped = capDiagnostics(result);
509
+ return { ok: true, value: capped.value, ...(capped.truncated ? { truncated: true } : {}) };
510
+ });
511
+ }
512
+ function workspaceForCache(ctx) {
513
+ // ToolContext exposes `root` (the workspace root). Fall back to a
514
+ // tagged anonymous workspace when somehow absent so the cache still
515
+ // keys deterministically.
516
+ const root = ctx.root;
517
+ if (typeof root === 'string' && root.length > 0)
518
+ return root;
519
+ return '<unknown>';
520
+ }
521
+ function getSymbolCacheFromCtx(ctx) {
522
+ // The agent dispatcher injects an optional cache via the ctx — when
523
+ // present we use the dispatcher-scoped cache so test harnesses can
524
+ // inject a deterministic clock. When absent, we fall back to the
525
+ // process-global cache.
526
+ if (ctx.symbolCache)
527
+ return ctx.symbolCache;
528
+ return getGlobalSymbolCache();
529
+ }
530
+ function notFound(verb) {
531
+ return {
532
+ ok: false,
533
+ reason: 'lsp_not_found',
534
+ detail: `LSP returned no ${verb} for the requested position.`,
535
+ };
536
+ }
537
+ function capSymbolReferences(items) {
538
+ const COUNT_CAP = 200;
539
+ if (items.length === 0)
540
+ return { value: items, truncated: false };
541
+ const trimmed = items.slice(0, COUNT_CAP);
542
+ const serialized = JSON.stringify(trimmed);
543
+ if (Buffer.byteLength(serialized, 'utf8') <= LSP_PAYLOAD_CAP_BYTES && trimmed.length === items.length) {
544
+ return { value: trimmed, truncated: false };
545
+ }
546
+ let upper = trimmed.length;
547
+ while (upper > 1) {
548
+ const half = Math.floor(upper / 2);
549
+ const sub = trimmed.slice(0, half);
550
+ if (Buffer.byteLength(JSON.stringify(sub), 'utf8') <= LSP_PAYLOAD_CAP_BYTES) {
551
+ return { value: sub, truncated: true };
552
+ }
553
+ upper = half;
554
+ }
555
+ return { value: trimmed.slice(0, 1), truncated: true };
556
+ }
187
557
  /** Test-only surface so specs can poke truncation directly. */
188
- export const __test__ = { truncate, capLocations, capDiagnostics, LSP_PAYLOAD_CAP_BYTES };
558
+ export const __test__ = {
559
+ truncate,
560
+ capLocations,
561
+ capDiagnostics,
562
+ capSymbolReferences,
563
+ LSP_PAYLOAD_CAP_BYTES,
564
+ };
189
565
  //# sourceMappingURL=lsp-tools.js.map
@@ -43,6 +43,29 @@ const registry = [
43
43
  { name: 'lsp_diagnostics', permission: 'read', risk: 'low', concurrencySafe: true, m1: true },
44
44
  { name: 'lsp_hover', permission: 'read', risk: 'low', concurrencySafe: true, m1: true },
45
45
  { name: 'lsp_references', permission: 'read', risk: 'low', concurrencySafe: true, m1: true },
46
+ // PUGI-78 Phase 1 — symbols.* namespace. 13 first-class tools that
47
+ // expose the full LSP symbol-aware surface (definition, references,
48
+ // hover, signature, document/workspace symbols, rename preview, call
49
+ // hierarchy, implementations, type definition, code actions,
50
+ // formatter, diagnostics). All read-only in Phase 1 — `rename` /
51
+ // `format` / `code_actions` return PREVIEW edits the dispatcher
52
+ // applies via apply_patch in a future ticket. Permission stays
53
+ // `read` because no workspace mutation happens on dispatch; risk
54
+ // stays `low` because the LSP server is local and the payload is
55
+ // capped at 8 KB per tool.
56
+ { name: 'symbols_call_hierarchy', permission: 'read', risk: 'low', concurrencySafe: true, m1: false },
57
+ { name: 'symbols_code_actions', permission: 'read', risk: 'low', concurrencySafe: true, m1: false },
58
+ { name: 'symbols_diagnostics', permission: 'read', risk: 'low', concurrencySafe: true, m1: false },
59
+ { name: 'symbols_find_definition', permission: 'read', risk: 'low', concurrencySafe: true, m1: false },
60
+ { name: 'symbols_find_references', permission: 'read', risk: 'low', concurrencySafe: true, m1: false },
61
+ { name: 'symbols_format', permission: 'read', risk: 'low', concurrencySafe: true, m1: false },
62
+ { name: 'symbols_hover', permission: 'read', risk: 'low', concurrencySafe: true, m1: false },
63
+ { name: 'symbols_implementations', permission: 'read', risk: 'low', concurrencySafe: true, m1: false },
64
+ { name: 'symbols_list_in_file', permission: 'read', risk: 'low', concurrencySafe: true, m1: false },
65
+ { name: 'symbols_rename', permission: 'read', risk: 'low', concurrencySafe: true, m1: false },
66
+ { name: 'symbols_signature', permission: 'read', risk: 'low', concurrencySafe: true, m1: false },
67
+ { name: 'symbols_type_definition', permission: 'read', risk: 'low', concurrencySafe: true, m1: false },
68
+ { name: 'symbols_workspace_symbols', permission: 'read', risk: 'low', concurrencySafe: true, m1: false },
46
69
  // β7 L5+T11: multi_edit dispatches an ordered batch of Layer A edits
47
70
  // as a single transaction. Risk = medium (same chokepoints as `edit`).
48
71
  // concurrencySafe = false because the journal serialises one dispatch
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  /**
3
3
  * REPL input box - Sprint (REPL UX P0 wave 1).
4
4
  *
5
- * Bordered, cursor-aware input box matching the upstream tool / Codex CLI
5
+ * Bordered, cursor-aware input box matching the upstream tool / peer CLI
6
6
  * aesthetics. Layered upgrade over :
7
7
  *
8
8
  * - Top divider (full-width cyan rule) anchors the input below the
@@ -26,7 +26,7 @@ export async function renderSplash(cliVersion) {
26
26
  * Sentinel thrown when the user dismisses the login picker via Esc
27
27
  * or `q`. The CLI dispatcher catches it, prints a one-line abort
28
28
  * message, and exits 130 (the standard exit code for SIGINT-style
29
- * user cancellations — matches gh CLI, codex, claude-code).
29
+ * user cancellations — matches gh CLI and peer CLIs).
30
30
  */
31
31
  export class LoginCancelledError extends Error {
32
32
  constructor() {
package/dist/tui/repl.js CHANGED
@@ -292,7 +292,7 @@ function MainArea({ state, personaNames, nowEpochMs, hideToolStream, toolStreamC
292
292
  // The window over the transcript is small (last 12 rows) so the
293
293
  // bottom of the frame stays anchored to the input box. New agents
294
294
  // push the operator line up the screen, mirroring the upstream tool /
295
- // Codex CLI / Gemini CLI rendering.
295
+ // peer CLI / Gemini CLI rendering.
296
296
  const conversationSlice = state.transcript.slice(-CONVERSATION_WINDOW);
297
297
  return (_jsxs(Box, { flexDirection: "column", children: [_jsx(ConversationPane, { rows: conversationSlice, personaNames: personaNames }), hideToolStream ? null : (_jsx(Box, { marginTop: 1, children: _jsx(ToolStreamPane, { calls: state.toolCalls, collapsed: toolStreamCollapsed }) })), _jsx(Box, { marginTop: 1, children: _jsx(AgentTreePane, { agents: state.agents, nowEpochMs: nowEpochMs }) })] }));
298
298
  }
@@ -137,7 +137,7 @@ export function connectionLabel(connection) {
137
137
  * anomaly stands out vs the calm cyan baseline.
138
138
  * 3. `tool_running` — surfaces the tool label when available
139
139
  * (`tool: read`), falls back to `tool` when not.
140
- * 4. `awaiting_response` — `dispatching` (matches Codex CLI's verb
140
+ * 4. `awaiting_response` — `dispatching` (matches peer CLI's verb
141
141
  * for the same state).
142
142
  * 5. `completed` — `shipped` (matches the agent tree status glyph
143
143
  * so the operator's eye links the two surfaces).
@@ -14,7 +14,7 @@ export function resolveDisplayedLatest(npmLatest, serverRecommended) {
14
14
  : npmLatest;
15
15
  }
16
16
  /**
17
- * Claude-Code-style corner banner: single line, dim orange, right
17
+ * peer-CLI-style corner banner: single line, dim orange, right
18
18
  * aligned. Renders to the bottom of the REPL frame so it never
19
19
  * displaces conversation content. Only mounts when an update is
20
20
  * actually available; identical to no-op when the registry poll
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pugi/cli",
3
- "version": "0.1.0-beta.88",
3
+ "version": "0.1.0-beta.89",
4
4
  "description": "Pugi CLI - terminal-native software execution system",
5
5
  "homepage": "https://pugi.io",
6
6
  "repository": {
@@ -62,8 +62,8 @@
62
62
  "undici": "^8.3.0",
63
63
  "which": "^6.0.0",
64
64
  "zod": "^3.23.0",
65
- "@pugi/sdk": "0.1.0-beta.88",
66
- "@pugi/personas": "0.1.2"
65
+ "@pugi/personas": "0.1.2",
66
+ "@pugi/sdk": "0.1.0-beta.89"
67
67
  },
68
68
  "devDependencies": {
69
69
  "@types/node": "^22.0.0",
@@ -4,8 +4,9 @@
4
4
  # Phase 1: the stub responder echoes the directive verbatim — Phase 2
5
5
  # wires the slash command through to the live compaction loop. Until
6
6
  # then this scenario protects the contract between scenario authoring
7
- # and the harness envelope shape.
7
+ # and the harness envelope shape. The scenario covers envelope routing,
8
+ # not anti-name-chant, so the persona is free to identify itself on the
9
+ # initial turn.
8
10
 
9
11
  > "/compact --force"
10
12
  EXPECT: persona-turn contains "/compact"
11
- EXPECT_NOT: persona-turn contains "Pugi"
@@ -1,11 +1,12 @@
1
1
  # scenario: identity
2
- # title: Pugi self-identifies as Pugi (never as Pugi)
2
+ # title: Pugi self-identifies on the very first turn
3
3
 
4
4
  # CEO directive feedback_live_console_test_every_publish: every published
5
- # beta must still answer "ты кто?" with the Pugi persona, never the legacy
6
- # Pugi name. This is the single most-asked dialog in CEO dogfood and the
7
- # regression that has bitten us most often.
5
+ # beta must still answer "ты кто?" with the Pugi persona on turn 1. This
6
+ # is the single most-asked dialog in CEO dogfood and the regression that
7
+ # has bitten us most often. The intro fires once per session, so the
8
+ # initial envelope is expected to include the brand name verbatim.
8
9
 
9
10
  > "ты кто?"
10
11
  EXPECT: persona-turn contains "Pugi" OR "Пуджи"
11
- EXPECT_NOT: persona-turn contains "Pugi" OR "Мира"
12
+ EXPECT_NOT: persona-turn contains "Мира"
@@ -5,7 +5,8 @@
5
5
  # (real dispatch routing wires in Phase 2). The scenario is authored
6
6
  # against the Phase 2 contract — for now it documents the intent and
7
7
  # the harness keeps it as a soft EXPECT so the corpus stays loadable.
8
+ # The positive EXPECT already covers the brand name on turn 1, so no
9
+ # anti-name-chant assertion is needed here.
8
10
 
9
11
  > "Hiroshi, please review the authentication module"
10
12
  EXPECT: persona-turn contains "Pugi" OR "Hiroshi" OR "Хироси"
11
- EXPECT_NOT: persona-turn contains "Pugi"
@@ -1,12 +1,12 @@
1
1
  # scenario: walkback
2
2
  # title: Esc-Esc walkback / rewind directive recognized
3
3
 
4
- # CEO parity ask (): the operator must be able to walk back
5
- # the last turn. Phase 1 ships the harness contract — the live walkback
6
- # wiring lands in . The scenario asserts the stub responder
7
- # acknowledges the directive verbatim so the contract holds while the
8
- # real implementation lands behind it.
4
+ # CEO parity ask: the operator must be able to walk back the last turn.
5
+ # Phase 1 ships the harness contract — the live walkback wiring lands
6
+ # later. The scenario asserts the stub responder acknowledges the
7
+ # directive verbatim so the contract holds while the real implementation
8
+ # lands behind it. Envelope-shape only, not anti-name-chant — the persona
9
+ # is free to identify itself on the initial turn.
9
10
 
10
11
  > "/rewind 1"
11
12
  EXPECT: persona-turn contains "/rewind"
12
- EXPECT_NOT: persona-turn contains "Pugi"