@pugi/cli 0.1.0-beta.87 → 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 (68) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/LICENSE +1 -1
  3. package/dist/core/agents/registry.js +1 -1
  4. package/dist/core/auth/env-provider.js +1 -1
  5. package/dist/core/checkpoints/shadow-git.js +1 -1
  6. package/dist/core/context/compaction.js +1 -1
  7. package/dist/core/context/markdown-traverse.js +1 -1
  8. package/dist/core/credentials.js +1 -1
  9. package/dist/core/denial-tracking/state.js +1 -1
  10. package/dist/core/edits/fuzzy-ladder.js +1 -1
  11. package/dist/core/edits/layer-a-fuzzy-apply.js +1 -1
  12. package/dist/core/engine/anvil-client.js +76 -2
  13. package/dist/core/engine/native-pugi.js +1 -1
  14. package/dist/core/engine/tool-bridge.js +436 -0
  15. package/dist/core/hooks/events.js +3 -1
  16. package/dist/core/hooks/registry.js +3 -0
  17. package/dist/core/hooks/worktree-events.js +158 -0
  18. package/dist/core/lsp/client.js +453 -0
  19. package/dist/core/lsp/server-detect.js +173 -0
  20. package/dist/core/lsp/symbol-cache.js +162 -0
  21. package/dist/core/lsp/symbol-tools.js +296 -4
  22. package/dist/core/mcp/server-tools.js +1 -1
  23. package/dist/core/mcp/server.js +1 -1
  24. package/dist/core/memory/secret-scanner.js +6 -6
  25. package/dist/core/onboarding/ensure-initialized.js +1 -1
  26. package/dist/core/plans/plan-artifact.js +2 -2
  27. package/dist/core/repl/ask.js +1 -1
  28. package/dist/core/repl/cap-warning.js +1 -1
  29. package/dist/core/repl/session.js +3 -3
  30. package/dist/core/repl/slash-commands.js +1 -1
  31. package/dist/core/routing/pre-flight-estimator.js +1 -1
  32. package/dist/core/settings.js +38 -0
  33. package/dist/core/worktree/include-parser.js +249 -0
  34. package/dist/index.js +8 -0
  35. package/dist/runtime/cli.js +176 -28
  36. package/dist/runtime/commands/agents.js +1 -1
  37. package/dist/runtime/commands/config.js +41 -7
  38. package/dist/runtime/commands/hooks.js +3 -0
  39. package/dist/runtime/commands/review-consensus.js +1 -1
  40. package/dist/runtime/sigint-guard.js +272 -0
  41. package/dist/runtime/version.js +1 -1
  42. package/dist/runtime/worktree-bootstrap.js +579 -0
  43. package/dist/skills/bundled/batch.js +2 -2
  44. package/dist/skills/bundled/index.js +3 -3
  45. package/dist/skills/bundled/loop.js +2 -2
  46. package/dist/skills/bundled/remember.js +1 -1
  47. package/dist/skills/bundled/simplify.js +1 -1
  48. package/dist/skills/bundled/skillify.js +2 -2
  49. package/dist/skills/bundled/stuck.js +1 -1
  50. package/dist/skills/bundled/verify.js +2 -2
  51. package/dist/testing/vcr.js +2 -2
  52. package/dist/tools/ask-user-question.js +66 -0
  53. package/dist/tools/bash.js +2 -2
  54. package/dist/tools/lsp-tools.js +377 -1
  55. package/dist/tools/powershell.js +1 -1
  56. package/dist/tools/registry.js +23 -0
  57. package/dist/tui/ask-user-question-chips.js +257 -0
  58. package/dist/tui/input-box.js +1 -1
  59. package/dist/tui/render.js +1 -1
  60. package/dist/tui/repl.js +1 -1
  61. package/dist/tui/status-bar.js +1 -1
  62. package/dist/tui/update-banner.js +1 -1
  63. package/dist/tui/welcome-data.js +4 -4
  64. package/package.json +4 -3
  65. package/test/scenarios/compact-force.scenario.txt +3 -2
  66. package/test/scenarios/identity.scenario.txt +6 -5
  67. package/test/scenarios/persona-handoff.scenario.txt +2 -1
  68. package/test/scenarios/walkback.scenario.txt +6 -6
@@ -0,0 +1,162 @@
1
+ /** Default TTL for cached entries. 5 minutes matches the spec target. */
2
+ export const DEFAULT_TTL_MS = 5 * 60 * 1000;
3
+ /** Default size cap. Empirically large enough for a heavy navigate session. */
4
+ export const DEFAULT_MAX_ENTRIES = 1000;
5
+ /**
6
+ * Generic TTL cache. The unknown value type is intentional — the cache
7
+ * stores serialized result objects produced by the 13 symbol tools; we
8
+ * surface them back to the caller as the SAME unknown so the cache
9
+ * does not encode the result schema.
10
+ *
11
+ * Thread-safety: Node single-threaded, no concurrent writes possible
12
+ * inside one `pugi` invocation. The cache is NOT shared across
13
+ * spawned subagents — each subagent runs its own LspClient + cache.
14
+ */
15
+ export class SymbolCache {
16
+ store = new Map();
17
+ ttlMs;
18
+ maxEntries;
19
+ now;
20
+ metrics = { hits: 0, misses: 0, evictions: 0, size: 0 };
21
+ constructor(options) {
22
+ this.ttlMs = options?.ttlMs ?? DEFAULT_TTL_MS;
23
+ this.maxEntries = options?.maxEntries ?? DEFAULT_MAX_ENTRIES;
24
+ this.now = options?.now ?? (() => Date.now());
25
+ }
26
+ /**
27
+ * Build a canonical key from the dispatch coordinates. Workspace +
28
+ * language scopes the cache so two different repos opened in the same
29
+ * REPL session do not pollute each other.
30
+ */
31
+ static makeKey(lang, workspace, verb, args) {
32
+ // Sort keys deterministically so `{file: 'a', line: 1}` and
33
+ // `{line: 1, file: 'a'}` map to the same cache entry.
34
+ const sorted = {};
35
+ for (const key of Object.keys(args).sort()) {
36
+ sorted[key] = args[key];
37
+ }
38
+ return `${lang}::${workspace}::${verb}::${JSON.stringify(sorted)}`;
39
+ }
40
+ /**
41
+ * Look up a cached value. Returns undefined when:
42
+ *
43
+ * - the key is not in the cache
44
+ * - the entry has aged past `ttlMs` (the entry is deleted in this
45
+ * case so the eviction counter is accurate)
46
+ *
47
+ * Hit updates the entry's `lastAccessed` so the LRU policy keeps
48
+ * frequently-touched entries warm.
49
+ */
50
+ get(key) {
51
+ const entry = this.store.get(key);
52
+ if (!entry) {
53
+ this.metrics.misses++;
54
+ return undefined;
55
+ }
56
+ const now = this.now();
57
+ if (now - entry.storedAt > this.ttlMs) {
58
+ this.store.delete(key);
59
+ this.metrics.evictions++;
60
+ this.metrics.misses++;
61
+ this.metrics.size = this.store.size;
62
+ return undefined;
63
+ }
64
+ entry.lastAccessed = now;
65
+ this.metrics.hits++;
66
+ return entry.result;
67
+ }
68
+ /**
69
+ * Store a value. Evicts the LRU entry when the cache hits `maxEntries`.
70
+ * The eviction counter increments for each LRU drop AND each TTL drop
71
+ * so the operator surface can distinguish "cache too small" from "TTL
72
+ * too short".
73
+ */
74
+ set(key, value) {
75
+ const now = this.now();
76
+ if (this.store.size >= this.maxEntries && !this.store.has(key)) {
77
+ // Find the LRU entry.
78
+ let oldestKey;
79
+ let oldestAccess = Number.POSITIVE_INFINITY;
80
+ for (const [k, entry] of this.store) {
81
+ if (entry.lastAccessed < oldestAccess) {
82
+ oldestAccess = entry.lastAccessed;
83
+ oldestKey = k;
84
+ }
85
+ }
86
+ if (oldestKey !== undefined) {
87
+ this.store.delete(oldestKey);
88
+ this.metrics.evictions++;
89
+ }
90
+ }
91
+ this.store.set(key, { result: value, storedAt: now, lastAccessed: now });
92
+ this.metrics.size = this.store.size;
93
+ }
94
+ /**
95
+ * Invalidate every entry for a workspace. Called by the post-edit
96
+ * hook so a successful `edit`/`write` clears stale symbol locations.
97
+ * Returns the number of entries dropped.
98
+ */
99
+ invalidateWorkspace(workspace) {
100
+ const prefix = `${workspace}::`;
101
+ // Two prefix forms: `<lang>::<workspace>::...` — we scan the full
102
+ // store and drop entries whose key contains the workspace literal.
103
+ let dropped = 0;
104
+ for (const key of Array.from(this.store.keys())) {
105
+ // The key shape is `lang::workspace::verb::args` — split on `::`
106
+ // and check the second segment. Avoid substring match because a
107
+ // workspace path could legally contain `::` (e.g. a Windows
108
+ // network path).
109
+ const parts = key.split('::');
110
+ if (parts.length >= 2 && parts[1] === workspace) {
111
+ this.store.delete(key);
112
+ dropped++;
113
+ }
114
+ else if (parts.length >= 2 && parts[1]?.startsWith(prefix)) {
115
+ this.store.delete(key);
116
+ dropped++;
117
+ }
118
+ }
119
+ if (dropped > 0) {
120
+ this.metrics.evictions += dropped;
121
+ this.metrics.size = this.store.size;
122
+ }
123
+ return dropped;
124
+ }
125
+ /** Drop every cached entry. */
126
+ clear() {
127
+ this.metrics.evictions += this.store.size;
128
+ this.store.clear();
129
+ this.metrics.size = 0;
130
+ }
131
+ /** Snapshot of the current metrics. The returned record is a copy. */
132
+ snapshot() {
133
+ return { ...this.metrics, size: this.store.size };
134
+ }
135
+ /** Test-only: reset metrics counters without dropping entries. */
136
+ resetMetrics() {
137
+ this.metrics = { hits: 0, misses: 0, evictions: 0, size: this.store.size };
138
+ }
139
+ /** Test-only: number of entries currently stored. */
140
+ size() {
141
+ return this.store.size;
142
+ }
143
+ }
144
+ /**
145
+ * Process-global symbol cache, lazily constructed on first access. The
146
+ * CLI surface picks this up via `getGlobalSymbolCache()` so every
147
+ * subagent / tool invocation in the same process shares the same cache.
148
+ * Subagents spawned in a fresh child process get a fresh cache (by
149
+ * design — the cache is process-scoped, not user-scoped).
150
+ */
151
+ let globalCache;
152
+ export function getGlobalSymbolCache(options) {
153
+ if (!globalCache) {
154
+ globalCache = new SymbolCache(options);
155
+ }
156
+ return globalCache;
157
+ }
158
+ /** Test-only: drop the singleton so a spec starts with a fresh cache. */
159
+ export function __resetGlobalSymbolCache() {
160
+ globalCache = undefined;
161
+ }
162
+ //# sourceMappingURL=symbol-cache.js.map
@@ -67,15 +67,28 @@ export function symbolKindLabel(kind) {
67
67
  return SYMBOL_KIND_LABELS[kind] ?? 'unknown';
68
68
  }
69
69
  /**
70
- * Build a `SymbolToolsTransport` from a real `LspClient`. Production
71
- * wire-up. `documentSymbols` / `workspaceSymbols` are omitted on
72
- * purpose — the `LspClient` does not expose them, so the Phase 1
73
- * surface degrades to `[]`. Phase 2 follows.
70
+ * Build a `SymbolToolsTransport` from a real `LspClient`. PUGI-78 Phase
71
+ * 1: wires every method the client exposes (13 total). Methods the
72
+ * `LspClient` does NOT support yet stay omitted on the returned
73
+ * transport so the symbol-tools surface degrades to null / [] without
74
+ * forcing the caller to wrap.
74
75
  */
75
76
  export function transportFromLspClient(client) {
76
77
  return {
77
78
  definition: (file, pos) => client.definition(file, pos),
78
79
  references: (file, pos) => client.references(file, pos),
80
+ documentSymbols: (file) => client.documentSymbols(file),
81
+ workspaceSymbols: (query) => client.workspaceSymbols(query),
82
+ hover: (file, pos) => client.hover(file, pos),
83
+ signatureHelp: (file, pos) => client.signatureHelp(file, pos),
84
+ implementations: (file, pos) => client.implementations(file, pos),
85
+ typeDefinition: (file, pos) => client.typeDefinition(file, pos),
86
+ rename: (file, pos, newName) => client.rename(file, pos, newName),
87
+ prepareRename: (file, pos) => client.prepareRename(file, pos),
88
+ codeActions: (file, range) => client.codeActions(file, range),
89
+ formatting: (file, options) => client.formatting(file, undefined, options),
90
+ diagnostics: (file) => client.diagnostics(file),
91
+ callHierarchy: (file, pos) => client.callHierarchy(file, pos),
79
92
  };
80
93
  }
81
94
  /**
@@ -222,6 +235,284 @@ export async function findWorkspaceSymbol(transport, query) {
222
235
  }
223
236
  return out;
224
237
  }
238
+ /**
239
+ * PUGI-78 Phase 1: hover (type info + docstring). Returns null when:
240
+ *
241
+ * - `filePath` is empty / whitespace
242
+ * - `line` / `character` non-finite or negative
243
+ * - the transport does not implement `hover`
244
+ * - `ok: false` from the transport
245
+ * - the server reports no hover (cursor not on a symbol)
246
+ * - the transport throws
247
+ *
248
+ * Caps the body at 4 KB so a verbose generic type signature does not
249
+ * blow the agent's context window. The cap mirrors the `lspHover`
250
+ * tool's 8 KB cap halved — the symbol surface is closer to "give me
251
+ * one sentence" than the full hover blob.
252
+ */
253
+ export async function hoverSymbol(transport, filePath, line, character) {
254
+ if (!isNonEmptyString(filePath))
255
+ return null;
256
+ if (!isFiniteNonNegativeInt(line) || !isFiniteNonNegativeInt(character))
257
+ return null;
258
+ if (!transport.hover)
259
+ return null;
260
+ let result;
261
+ try {
262
+ result = await transport.hover(filePath, { line, character });
263
+ }
264
+ catch {
265
+ return null;
266
+ }
267
+ if (!result.ok)
268
+ return null;
269
+ if (!result.value)
270
+ return null;
271
+ const HOVER_CAP_BYTES = 4 * 1024;
272
+ const raw = result.value.content;
273
+ const bytes = Buffer.byteLength(raw, 'utf8');
274
+ const truncated = bytes > HOVER_CAP_BYTES;
275
+ const content = truncated
276
+ ? Buffer.from(raw, 'utf8').subarray(0, HOVER_CAP_BYTES).toString('utf8') + '\n... [truncated]'
277
+ : raw;
278
+ return {
279
+ content,
280
+ ...(result.value.range ? { range: result.value.range } : {}),
281
+ ...(truncated ? { truncated: true } : {}),
282
+ };
283
+ }
284
+ /**
285
+ * PUGI-78 Phase 1: function signature at a call site. Returns null when
286
+ * the transport does not implement `signatureHelp` OR the server
287
+ * reports no signature OR the transport fails / throws.
288
+ */
289
+ export async function signatureAt(transport, filePath, line, character) {
290
+ if (!isNonEmptyString(filePath))
291
+ return null;
292
+ if (!isFiniteNonNegativeInt(line) || !isFiniteNonNegativeInt(character))
293
+ return null;
294
+ if (!transport.signatureHelp)
295
+ return null;
296
+ let result;
297
+ try {
298
+ result = await transport.signatureHelp(filePath, { line, character });
299
+ }
300
+ catch {
301
+ return null;
302
+ }
303
+ if (!result.ok)
304
+ return null;
305
+ if (!result.value)
306
+ return null;
307
+ const out = { label: result.value.label, parameters: result.value.parameters };
308
+ if (result.value.documentation)
309
+ out.documentation = result.value.documentation;
310
+ if (typeof result.value.activeParameter === 'number')
311
+ out.activeParameter = result.value.activeParameter;
312
+ return out;
313
+ }
314
+ /**
315
+ * PUGI-78 Phase 1: implementations of an interface / abstract method.
316
+ * Returns `[]` on any failure mode (parity with `findReferences`).
317
+ */
318
+ export async function findImplementations(transport, filePath, line, character) {
319
+ if (!isNonEmptyString(filePath))
320
+ return [];
321
+ if (!isFiniteNonNegativeInt(line) || !isFiniteNonNegativeInt(character))
322
+ return [];
323
+ if (!transport.implementations)
324
+ return [];
325
+ let result;
326
+ try {
327
+ result = await transport.implementations(filePath, { line, character });
328
+ }
329
+ catch {
330
+ return [];
331
+ }
332
+ if (!result.ok)
333
+ return [];
334
+ return collapseLocations(result.value);
335
+ }
336
+ /**
337
+ * PUGI-78 Phase 1: type-definition (vs value-definition). Returns null
338
+ * matching `findDefinition`'s contract.
339
+ */
340
+ export async function findTypeDefinition(transport, filePath, line, character) {
341
+ if (!isNonEmptyString(filePath))
342
+ return null;
343
+ if (!isFiniteNonNegativeInt(line) || !isFiniteNonNegativeInt(character))
344
+ return null;
345
+ if (!transport.typeDefinition)
346
+ return null;
347
+ let result;
348
+ try {
349
+ result = await transport.typeDefinition(filePath, { line, character });
350
+ }
351
+ catch {
352
+ return null;
353
+ }
354
+ if (!result.ok)
355
+ return null;
356
+ const first = result.value[0];
357
+ if (!first)
358
+ return null;
359
+ const file = pickFilePath(first);
360
+ if (!isNonEmptyString(file))
361
+ return null;
362
+ return { file, line: first.range.start.line, character: first.range.start.character };
363
+ }
364
+ export async function renameSymbol(transport, filePath, line, character, newName) {
365
+ if (!isNonEmptyString(filePath))
366
+ return null;
367
+ if (!isFiniteNonNegativeInt(line) || !isFiniteNonNegativeInt(character))
368
+ return null;
369
+ if (!isNonEmptyString(newName))
370
+ return null;
371
+ if (!transport.rename)
372
+ return null;
373
+ // Best-effort prepare-rename precheck. If the server says the cursor
374
+ // is not on a renameable token, we return null without dispatching
375
+ // the full rename — saves a wasted round-trip + avoids returning a
376
+ // misleading "0 edits" preview.
377
+ if (transport.prepareRename) {
378
+ try {
379
+ const prep = await transport.prepareRename(filePath, { line, character });
380
+ if (prep.ok && prep.value === null)
381
+ return null;
382
+ }
383
+ catch {
384
+ // Continue — some servers fail prepare-rename but still execute
385
+ // the full rename. Phase 1 honors the looser fallback.
386
+ }
387
+ }
388
+ let result;
389
+ try {
390
+ result = await transport.rename(filePath, { line, character }, newName);
391
+ }
392
+ catch {
393
+ return null;
394
+ }
395
+ if (!result.ok)
396
+ return null;
397
+ if (!result.value)
398
+ return null;
399
+ if (result.value.edits.length === 0)
400
+ return null;
401
+ return { files: result.value.files, edits: result.value.edits };
402
+ }
403
+ /**
404
+ * PUGI-78 Phase 1: code-action / quick-fix list at a range. Returns
405
+ * `[]` on any failure mode.
406
+ */
407
+ export async function codeActionsAt(transport, filePath, startLine, startChar, endLine, endChar) {
408
+ if (!isNonEmptyString(filePath))
409
+ return [];
410
+ if (!isFiniteNonNegativeInt(startLine) ||
411
+ !isFiniteNonNegativeInt(startChar) ||
412
+ !isFiniteNonNegativeInt(endLine) ||
413
+ !isFiniteNonNegativeInt(endChar)) {
414
+ return [];
415
+ }
416
+ if (!transport.codeActions)
417
+ return [];
418
+ const range = {
419
+ start: { line: startLine, character: startChar },
420
+ end: { line: endLine, character: endChar },
421
+ };
422
+ let result;
423
+ try {
424
+ result = await transport.codeActions(filePath, range);
425
+ }
426
+ catch {
427
+ return [];
428
+ }
429
+ if (!result.ok)
430
+ return [];
431
+ return [...result.value];
432
+ }
433
+ /**
434
+ * PUGI-78 Phase 1: formatter — returns the text edits the server would
435
+ * apply. Returns `[]` on any failure mode.
436
+ */
437
+ export async function formatFile(transport, filePath, options) {
438
+ if (!isNonEmptyString(filePath))
439
+ return [];
440
+ if (!transport.formatting)
441
+ return [];
442
+ let result;
443
+ try {
444
+ result = await transport.formatting(filePath, options);
445
+ }
446
+ catch {
447
+ return [];
448
+ }
449
+ if (!result.ok)
450
+ return [];
451
+ return [...result.value];
452
+ }
453
+ /**
454
+ * PUGI-78 Phase 1: diagnostics for `filePath`. Returns `[]` on any
455
+ * failure mode. Mirrors the agent surface of `lspDiagnostics` but
456
+ * folded into the symbols.* namespace for ergonomic discoverability.
457
+ */
458
+ export async function diagnosticsFor(transport, filePath) {
459
+ if (!isNonEmptyString(filePath))
460
+ return [];
461
+ if (!transport.diagnostics)
462
+ return [];
463
+ let result;
464
+ try {
465
+ result = await transport.diagnostics(filePath);
466
+ }
467
+ catch {
468
+ return [];
469
+ }
470
+ if (!result.ok)
471
+ return [];
472
+ return [...result.value];
473
+ }
474
+ /**
475
+ * PUGI-78 Phase 1: call hierarchy at a symbol position. Returns the
476
+ * incoming and outgoing edges. Empty arrays on any failure mode.
477
+ */
478
+ export async function callHierarchyAt(transport, filePath, line, character) {
479
+ const empty = { incoming: [], outgoing: [] };
480
+ if (!isNonEmptyString(filePath))
481
+ return empty;
482
+ if (!isFiniteNonNegativeInt(line) || !isFiniteNonNegativeInt(character))
483
+ return empty;
484
+ if (!transport.callHierarchy)
485
+ return empty;
486
+ let result;
487
+ try {
488
+ result = await transport.callHierarchy(filePath, { line, character });
489
+ }
490
+ catch {
491
+ return empty;
492
+ }
493
+ if (!result.ok)
494
+ return empty;
495
+ return { incoming: [...result.value.incoming], outgoing: [...result.value.outgoing] };
496
+ }
497
+ /**
498
+ * Internal: collapse `LspLocation[]` to flat references the agent
499
+ * surface ships. Identical to the `findReferences` loop body — lifted
500
+ * to avoid a second copy.
501
+ */
502
+ function collapseLocations(locations) {
503
+ const out = [];
504
+ for (const loc of locations) {
505
+ const file = pickFilePath(loc);
506
+ if (!isNonEmptyString(file))
507
+ continue;
508
+ out.push({
509
+ file,
510
+ line: loc.range.start.line,
511
+ character: loc.range.start.character,
512
+ });
513
+ }
514
+ return out;
515
+ }
225
516
  // -- internals ---------------------------------------------------------------
226
517
  function isNonEmptyString(value) {
227
518
  return typeof value === 'string' && value.trim().length > 0;
@@ -368,5 +659,6 @@ export const __test__ = {
368
659
  pickRangeStart,
369
660
  symbolKindLabel,
370
661
  walkDocumentSymbol,
662
+ collapseLocations,
371
663
  };
372
664
  //# sourceMappingURL=symbol-tools.js.map
@@ -6,7 +6,7 @@ import { bashToolSync } from '../../tools/bash.js';
6
6
  * The shapes intentionally mirror the engine-loop tool schemas in
7
7
  * `core/engine/tool-bridge.ts` so an MCP client and the Pugi engine see
8
8
  * the same parameter contracts. This is the "Pugi as MCP server"
9
- * surface — other agents (the upstream tool, Codex, OpenCode) call these to
9
+ * surface — other agents (the upstream tool, Codex, peer tooling) call these to
10
10
  * read / mutate the workspace through us, with all our security gates
11
11
  * (path containment, plan-mode refusal, bash classifier, settings) in
12
12
  * the loop.
@@ -1,7 +1,7 @@
1
1
  import { EventEmitter } from 'node:events';
2
2
  /**
3
3
  * Pugi MCP server (β4 M2) — exposes Pugi's native tool surface to other
4
- * agents (the upstream tool, OpenCode, Codex CLI, any client that speaks
4
+ * agents (the upstream tool, peer tooling, peer CLI, any client that speaks
5
5
  * MCP).
6
6
  *
7
7
  * Transport-agnostic core. The stdio entry-point lives at the bottom of
@@ -1,10 +1,10 @@
1
1
  /**
2
- * Persona-memory secret scanner (backlog, ADR-0063 hardening).
2
+ * Persona-memory secret scanner (backlog, hardening).
3
3
  *
4
4
  * Defense against API keys / credentials accidentally landing in shared
5
5
  * persona memory. A naive operator typing `pugi memory write fact "Use
6
6
  * API key sk-ant-..."` would silently persist that secret to the
7
- * `cf_admin.persona_memory` table — visible to every persona, every
7
+ * `the platform database.persona_memory` table — visible to every persona, every
8
8
  * recall query, every dual-write sink. This module is the chokepoint
9
9
  * that refuses such writes.
10
10
  *
@@ -25,7 +25,7 @@
25
25
  * 3. GitHub PAT / installation tokens (ghp_/ghs_/gho_/ghu_)
26
26
  * high
27
27
  * 4. AWS access key id AKIA… high
28
- * 5. Plane API token plane_api_… high
28
+ * 5. Plane API token plane_api_… high [pugi-leak-ok]
29
29
  * 6. npm token npm_… high
30
30
  * 7. Slack token xox[bpoars]-… high
31
31
  * 8. Stripe secret key sk_(live|test)_… high
@@ -59,10 +59,10 @@
59
59
  * instead of reject. The scanner exposes `redactSecrets` for that
60
60
  * caller — see `runtime/commands/memory.ts`.
61
61
  *
62
- * # Clean-room provenance
62
+ * # independent implementation provenance
63
63
  *
64
64
  * Inspired by the the upstream tool teamMemorySync.secretScanner pattern
65
- * (intel from leak-research memos). Clean-room TypeScript
65
+ * (intel from leak-research memos). independent implementation TypeScript
66
66
  * implementation — no upstream code reused. Pattern vocabulary was
67
67
  * cross-referenced against the existing
68
68
  * `apps/pugi-cli/scripts/secret-scanner.mjs` tarball gate so a single
@@ -124,7 +124,7 @@ const SECRET_RULES = [
124
124
  pattern: 'plane-api-token',
125
125
  // Plane (project management) personal API tokens. Bounded to 20+
126
126
  // url-safe chars after the prefix.
127
- regex: /\bplane_api_[A-Za-z0-9]{20,}(?![A-Za-z0-9])/g,
127
+ regex: /\bplane_api_[A-Za-z0-9]{20,}(?![A-Za-z0-9])/g, // [pugi-leak-ok]
128
128
  confidence: 'high',
129
129
  },
130
130
  {
@@ -119,7 +119,7 @@ export async function ensureInitialized(opts) {
119
119
  write(`No Pugi workspace found at ${root}.\n`);
120
120
  const answer = (await opts.prompt('Initialize a new Pugi workspace here? (Y/n) ')).trim().toLowerCase();
121
121
  // Default = yes (empty input OR leading 'y'). Anything else = no.
122
- // Mirrors the gh CLI / claude code prompt convention where the upper-
122
+ // Mirrors the gh CLI / the upstream prompt convention where the upper-
123
123
  // case option in `(Y/n)` is the default-on-Enter answer.
124
124
  const acceptedShort = answer === '' || answer === 'y' || answer === 'yes';
125
125
  if (!acceptedShort) {
@@ -2,7 +2,7 @@
2
2
  * Plan-as-FILE artifact store (Pugi backlog).
3
3
  *
4
4
  * Pattern absorbed from the the upstream tool `ExitPlanMode` leak intel
5
- * (clean-room TypeScript reimplementation — no source was copied; only
5
+ * (independent implementation TypeScript reimplementation — no source was copied; only
6
6
  * the file-as-artifact concept). When Pugi enters plan-mode the engine
7
7
  * routes the plan body to `.pugi/plans/<plan-id>.md` instead of the
8
8
  * message stream so it survives `/compact`, becomes diffable across
@@ -205,7 +205,7 @@ function yamlScalar(value) {
205
205
  // Empty string is also disambiguated by quoting.
206
206
  if (value.length === 0)
207
207
  return '""';
208
- if (/[-"]/.test(value)) {
208
+ if (/[-"]/.test(value)) {
209
209
  return JSON.stringify(value);
210
210
  }
211
211
  if (/[:#\n\t]/.test(value)) {
@@ -4,7 +4,7 @@
4
4
  * Pugi's persona prompt teaches Pugi to emit two structured XML envelopes
5
5
  * when she would otherwise have to guess. Operator chat then pauses on a
6
6
  * modal until the operator answers, eliminating the "fabricate a default
7
- * silently" failure mode that Codex CLI, the upstream tool, and Gemini CLI all
7
+ * silently" failure mode that peer CLI, the upstream tool, and Gemini CLI all
8
8
  * trip on with low-confidence intents.
9
9
  *
10
10
  * <pugi-ask>
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Client-side concurrent-subagent cap - Sprint (ADR-0056
2
+ * Client-side concurrent-subagent cap - Sprint (
3
3
  * acceptance #6, Mac safety memo
4
4
  * `feedback_max_3_parallel_agents_mac_safety.md`).
5
5
  *
@@ -2019,7 +2019,7 @@ export class ReplSession {
2019
2019
  // Surface the operator's choice as a transcript row so the
2020
2020
  // conversation reads linearly. The label of the chosen option
2021
2021
  // (or the literal custom input) is more readable than the bare
2022
- // value - Codex CLI's "you chose: Vercel" pattern.
2022
+ // value - peer CLI's "you chose: Vercel" pattern.
2023
2023
  const humanLabel = humanLabelForVerdict(tag, sanitisedVerdict);
2024
2024
  this.appendOperatorLine(humanLabel);
2025
2025
  // Local-origin modals (operator typed `/ask`) never need an
@@ -4105,7 +4105,7 @@ export function colorizeQuotaRow(row, pct) {
4105
4105
  * string and emit a synthesised `ToolCallEntry`. Returns null when no
4106
4106
  * known tool pattern matches.
4107
4107
  *
4108
- * The grammar mirrors the way the upstream tool, Codex CLI, and Gemini CLI
4108
+ * The grammar mirrors the way the upstream tool, peer CLI, and Gemini CLI
4109
4109
  * display tool calls in their tool stream panes:
4110
4110
  *
4111
4111
  * Read(path)
@@ -4293,7 +4293,7 @@ function encodePlanReviewVerdictLocal(result) {
4293
4293
  }
4294
4294
  /**
4295
4295
  * Compose the human-readable transcript line that records the
4296
- * operator's ask verdict. Mirrors Codex CLI's "you chose: <label>"
4296
+ * operator's ask verdict. Mirrors peer CLI's "you chose: <label>"
4297
4297
  * pattern so the conversation reads linearly.
4298
4298
  */
4299
4299
  function humanLabelForVerdict(tag, verdict) {
@@ -4,7 +4,7 @@
4
4
  * The REPL input box surfaces a palette of slash commands the operator
5
5
  * can run from inside a persistent session. The wave-2 expansion (CEO
6
6
  *) grows the surface from 6 to 20 commands so the `/help`
7
- * overlay matches the breadth the upstream tool / Codex CLI operators expect.
7
+ * overlay matches the breadth the upstream tool / peer CLI operators expect.
8
8
  *
9
9
  * The registry is pure: each `parseSlashCommand` call returns a
10
10
  * `SlashCommandResult` describing what the REPL session should do next.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Pre-flight token estimator — Nuekkis tokenEstimation port,
2
+ * Pre-flight token estimator — external tokenEstimation port,
3
3
  * adapted for Anvil's 3-tier routing.
4
4
  *
5
5
  * The auto-compact gate counts tokens AFTER a turn lands. This module