pi-supernova 0.2.0 → 0.3.1

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 (32) hide show
  1. package/README.md +196 -196
  2. package/{CHANGELOG.md → docs/CHANGELOG.md} +54 -1
  3. package/index.js +81 -68
  4. package/package.json +12 -31
  5. package/{catalog.js → src/bridge/catalog.js} +7 -5
  6. package/{host-bridge.js → src/bridge/host-bridge.js} +232 -87
  7. package/src/bridge/native-tools.js +155 -0
  8. package/src/bridge/pi-extension.ts +2 -0
  9. package/{config.js → src/config/config.js} +1 -1
  10. package/{evidence.js → src/context/evidence.js} +23 -12
  11. package/{outline.js → src/context/outline.js} +1 -1
  12. package/{repo-index.js → src/context/repo-index.js} +11 -9
  13. package/{search.js → src/context/search.js} +34 -1
  14. package/{snap.js → src/context/snap.js} +51 -31
  15. package/{surface.js → src/context/surface.js} +1 -1
  16. package/{diff.js → src/fs/diff.js} +12 -9
  17. package/{patch.js → src/fs/patch.js} +1 -1
  18. package/{vfs.js → src/fs/vfs.js} +55 -14
  19. package/{workspace.js → src/fs/workspace.js} +22 -6
  20. package/{bottleneck.js → src/output/bottleneck.js} +23 -6
  21. package/{format.js → src/output/format.js} +20 -1
  22. package/{guest-worker.js → src/runtime/guest-worker.js} +90 -21
  23. package/{parallel.js → src/runtime/parallel.js} +68 -1
  24. package/{runtime.js → src/runtime/runtime.js} +14 -5
  25. package/{omp-frame.js → src/ui/omp-frame.js} +1 -1
  26. package/{render-measure.js → src/ui/render-measure.js} +27 -1
  27. package/{render.js → src/ui/render.js} +42 -20
  28. /package/{config.default.json → src/config/config.default.json} +0 -0
  29. /package/{fuzzy.js → src/context/fuzzy.js} +0 -0
  30. /package/{ledger.js → src/context/ledger.js} +0 -0
  31. /package/{check.js → src/fs/check.js} +0 -0
  32. /package/{decode.js → src/shared/decode.js} +0 -0
@@ -4,8 +4,34 @@ import stringWidth from "string-width";
4
4
  const ELLIPSIS = "…";
5
5
  const segmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" });
6
6
 
7
+ const widthCache = new Map();
8
+ let cachedWidthChars = 0;
9
+ const MAX_WIDTH_CACHE_CHARS = 512_000;
10
+
7
11
  export function measureWidth(text) {
8
- return stringWidth(String(text ?? "").replace(/\t/g, " "));
12
+ const raw = String(text ?? "");
13
+ const cached = widthCache.get(raw);
14
+ if (cached !== undefined) return cached;
15
+ const normalized = raw.replace(/\t/g, " ");
16
+ // eslint-disable-next-line no-control-regex -- intentional ANSI SGR recognition
17
+ const plain = normalized.replace(/\x1b\[(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?m/g, "");
18
+ // ASCII and these single-column chrome glyphs need no Unicode segmentation.
19
+ // Any other character/control/escape sequence uses the full oracle.
20
+ const width = /^[\x20-\x7e\u2500-\u257f\u00b7\u00d7\u2026\u2713\u2717]*$/.test(plain)
21
+ ? plain.length
22
+ : stringWidth(normalized);
23
+ // Cache immutable text only, never host/theme/result objects. Bound both
24
+ // bookkeeping and retained text; unusually long lines bypass retention.
25
+ if (raw.length <= 4096) {
26
+ while (widthCache.size >= 4096 || cachedWidthChars + raw.length > MAX_WIDTH_CACHE_CHARS) {
27
+ const oldest = widthCache.keys().next().value;
28
+ widthCache.delete(oldest);
29
+ cachedWidthChars -= oldest.length;
30
+ }
31
+ widthCache.set(raw, width);
32
+ cachedWidthChars += raw.length;
33
+ }
34
+ return width;
9
35
  }
10
36
 
11
37
  function takePrefix(text, width) {
@@ -11,10 +11,10 @@
11
11
  */
12
12
 
13
13
  import { stripVTControlCharacters } from "node:util";
14
- import { isString, isObject, isFunction } from "./decode.js";
14
+ import { isString, isObject, isFunction } from "../shared/decode.js";
15
15
  import { measureWidth, hardTruncate, clampLine, fitPath, wrapLine } from "./render-measure.js";
16
16
  import { novaFramedBlock, novaStatusLine } from "./omp-frame.js";
17
- import { formatValue } from "./format.js";
17
+ import { formatValue } from "../output/format.js";
18
18
 
19
19
  export { measureWidth, hardTruncate, clampLine };
20
20
 
@@ -42,14 +42,10 @@ function formatDiffRows(diff, theme, maxShown = 6) {
42
42
  }
43
43
 
44
44
  function stripUnsafeControls(value) {
45
- let clean = "";
46
- for (const character of value) {
47
- const codePoint = character.codePointAt(0);
48
- const isC0 = codePoint <= 0x08 || codePoint === 0x0b || codePoint === 0x0c || (codePoint >= 0x0e && codePoint <= 0x1f);
49
- const isDeleteOrC1 = codePoint >= 0x7f && codePoint <= 0x9f;
50
- if (!isC0 && !isDeleteOrC1) clean += character;
51
- }
52
- return clean;
45
+ // Exactly the C0/DEL/C1 ranges previously filtered code point by code point.
46
+ // Native replacement avoids rebuilding every already-clean Unicode string.
47
+ // eslint-disable-next-line no-control-regex -- intentional terminal-control filtering
48
+ return value.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f-\u009f]/g, "");
53
49
  }
54
50
 
55
51
  function cleanBlockText(value) {
@@ -195,11 +191,20 @@ function parseDiffLine(rawLine) {
195
191
  return null;
196
192
  }
197
193
 
194
+ // Text diffs are immutable, even when hosts replace trace snapshots on each frame.
195
+ // Cache only parsed data, not theme, paths or mutable result objects.
196
+ const textDiffCache = new Map();
197
+ let cachedDiffChars = 0;
198
+ const MAX_CACHED_DIFF_CHARS = 1_000_000;
199
+
198
200
  function normalizeTraceDiff(item) {
199
201
  const diff = item?.diff;
200
202
  if (isObject(diff)) return diff;
201
203
  if (!isString(diff) || !diff.trim()) return undefined;
204
+ const cached = textDiffCache.get(diff);
205
+ if (cached) return cached;
202
206
  const lines = [];
207
+ let displayLineCount = 0;
203
208
  let added = 0;
204
209
  let removed = 0;
205
210
  for (const rawLine of cleanBlockText(diff).split("\n")) {
@@ -207,10 +212,21 @@ function normalizeTraceDiff(item) {
207
212
  if (!parsed) continue;
208
213
  if (parsed.type === "add") added += 1;
209
214
  else if (parsed.type === "remove") removed += 1;
210
- lines.push(parsed);
215
+ displayLineCount++;
216
+ if (lines.length < 24) lines.push(parsed);
211
217
  }
212
218
  if (lines.length === 0) return undefined;
213
- return { path: item?.args?.path || "", op: item?.name, added, removed, lines };
219
+ const parsed = { added, removed, lines, displayLineCount };
220
+ if (diff.length <= MAX_CACHED_DIFF_CHARS) {
221
+ while (textDiffCache.size >= 24 || cachedDiffChars + diff.length > MAX_CACHED_DIFF_CHARS) {
222
+ const oldest = textDiffCache.keys().next().value;
223
+ textDiffCache.delete(oldest);
224
+ cachedDiffChars -= oldest.length;
225
+ }
226
+ textDiffCache.set(diff, parsed);
227
+ cachedDiffChars += diff.length;
228
+ }
229
+ return parsed;
214
230
  }
215
231
 
216
232
  function operationsFromTrace(trace) {
@@ -305,8 +321,9 @@ function formatOpRow(theme, op, width, isPartial, isError) {
305
321
  return prefix.trimEnd();
306
322
  }
307
323
 
308
- function operationsFor(payload, context) {
309
- return operationsFromTrace(payload?.trace || context?.state?.trace || []);
324
+ function traceFor(payload, context) {
325
+ const trace = payload?.trace || context?.state?.trace;
326
+ return Array.isArray(trace) ? trace : [];
310
327
  }
311
328
 
312
329
  function resultLines(value, width) {
@@ -317,7 +334,7 @@ function resultLines(value, width) {
317
334
  function appendOps(lines, theme, ops, maxOps, maxDiffLines, width, isPartial, isError) {
318
335
  for (const op of ops.slice(0, maxOps)) {
319
336
  lines.push(formatOpRow(theme, op, width, isPartial, isError));
320
- if (!op.diff || !isObject(op.diff)) continue;
337
+ if (maxDiffLines === 0 || !op.diff || !isObject(op.diff)) continue;
321
338
  for (const row of formatDiffRows(op.diff, theme, maxDiffLines)) lines.push(" " + row);
322
339
  }
323
340
  if (ops.length > maxOps) lines.push(theme.fg("dim", ` … ${ops.length - maxOps} more calls`));
@@ -338,14 +355,19 @@ function appendTail(lines, theme, payload, expanded, isError, width) {
338
355
  }
339
356
  }
340
357
 
341
- function buildBodyLines(theme, width, { payload, context, args, expanded, isPartial, isError }) {
342
- const ops = operationsFor(payload, context, args);
358
+ function buildBodyLines(theme, width, { payload, context, expanded, isPartial, isError }) {
359
+ const trace = traceFor(payload, context);
343
360
  const maxOps = expanded ? 24 : 8;
344
- const maxDiffLines = expanded ? 24 : 8;
361
+ const maxDiffLines = expanded ? 24 : isPartial ? 0 : 8;
362
+ // Select before parsing diffs: invisible history must not consume a frame.
363
+ // While running, show current activity rather than the first completed calls.
364
+ const visible = isPartial ? trace.slice(-maxOps) : trace.slice(0, maxOps);
365
+ const ops = operationsFromTrace(visible);
345
366
  const lines = [];
346
367
  appendOps(lines, theme, ops, maxOps, maxDiffLines, width, isPartial, isError);
368
+ if (trace.length > maxOps) lines.push(theme.fg("dim", ` … ${trace.length - maxOps} ${isPartial ? "earlier" : "more"} calls`));
347
369
  appendTail(lines, theme, payload, expanded, isError, width);
348
- return { lines, opCount: ops.length };
370
+ return { lines, opCount: trace.length };
349
371
  }
350
372
 
351
373
  function describeCard(model, opCount) {
@@ -404,7 +426,7 @@ export function renderSupernovaResult(resultArg, optionsArg, themeArg, contextAr
404
426
  contextArg,
405
427
  );
406
428
 
407
- const payload = result?.details;
429
+ const payload = result?.details ?? (result?.isError ? { ok: false, error: result.content?.filter(block => block.type === "text").map(block => block.text).join("\n") } : undefined);
408
430
  syncState(context, payload);
409
431
 
410
432
  const isError = result?.isError || payload?.ok === false;
File without changes
File without changes
File without changes
File without changes