atom-agent 1.3.0 → 1.4.0
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/CHANGELOG.md +22 -0
- package/dist/ui/diff-panel.js +5 -5
- package/dist/ui/diff-view.js +3 -2
- package/dist/ui/diff.js +7 -52
- package/dist/ui/modals.js +5 -5
- package/dist/ui/side-by-side.js +7 -5
- package/dist/ui/transcript.js +3 -3
- package/documentation/extensions.md +1 -1
- package/examples/extensions/01-audit-gate.js +2 -2
- package/examples/extensions/02-notes-tool.js +2 -2
- package/examples/extensions/03-custom-command.js +2 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.4.0 — 2026-09-11
|
|
4
|
+
|
|
5
|
+
- Uncapped TUI diffs (`src/ui/diff.ts`, `src/ui/side-by-side.tsx`,
|
|
6
|
+
`src/ui/diff-view.tsx`, `src/ui/transcript.tsx`, `src/ui/modals.tsx`,
|
|
7
|
+
`src/ui/diff-panel.tsx`): the diff engine no longer truncates at 400
|
|
8
|
+
changed lines — `computeDiff` and `computeSideBySide` return the full
|
|
9
|
+
hunk/row list with `truncated: false`, and the transcript, approval
|
|
10
|
+
preview, and `/diff` panel render it whole (no more `… N more rows` or
|
|
11
|
+
`(diff truncated at 400 changed lines)` trailers). A 500-line write now
|
|
12
|
+
shows its complete before/after instead of a capped head. Smoothness is
|
|
13
|
+
preserved by the existing per-mount memoization (`SideBySideInner`,
|
|
14
|
+
`DiffViewInner`, `LineBody`, `TranscriptRow`), append-once `<Static>`
|
|
15
|
+
commits, and the engine's linear-time fallbacks (Myers prefix/suffix past
|
|
16
|
+
1000 lines, flat word runs past 200 tokens/line). Safety caps stay
|
|
17
|
+
enforced: binary detect and the 1MB file skip. `MAX_CHANGED_LINES`,
|
|
18
|
+
`TRANSCRIPT_DIFF_MAX_LINES`, `APPROVAL_DIFF_MAX_LINES`, and
|
|
19
|
+
`DIFF_PANEL_MAX_LINES` are retained as compatibility exports (the row
|
|
20
|
+
caps now read `Infinity`); `maxRows`/`maxLines` remain as opt-in windows
|
|
21
|
+
for callers that want a collapsed tail. Known tradeoff: large approvals
|
|
22
|
+
grow the permission modal, pushing the `y/a/t/n` options further down —
|
|
23
|
+
the file on disk was and remains the whole truth
|
|
24
|
+
|
|
3
25
|
## 1.3.0 — 2026-09-11
|
|
4
26
|
|
|
5
27
|
- Session goals (`src/goal.ts`, `src/App.tsx`, `src/agent/loop.ts`,
|
package/dist/ui/diff-panel.js
CHANGED
|
@@ -4,10 +4,10 @@ import { computeDiff } from "./diff.js";
|
|
|
4
4
|
import { SideBySideDiffView } from "./side-by-side.js";
|
|
5
5
|
import { theme } from "./theme.js";
|
|
6
6
|
import { LIST_WINDOW, windowedList } from "./tool-inspector.js";
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
export const DIFF_PANEL_MAX_LINES =
|
|
7
|
+
// Retained for compatibility (no longer applied — the panel renders the
|
|
8
|
+
// full preview; smoothness comes from the per-mount memo + word fallbacks,
|
|
9
|
+
// not from a row cap).
|
|
10
|
+
export const DIFF_PANEL_MAX_LINES = Infinity;
|
|
11
11
|
// Group committed session previews by file: latest preview per path
|
|
12
12
|
// wins (a later edit supersedes the earlier view of the same file),
|
|
13
13
|
// first-seen path order kept. Previews without a path are ungroupable
|
|
@@ -51,5 +51,5 @@ export function DiffPanel({ files, index, expanded }) {
|
|
|
51
51
|
return (_jsxs(Text, { color: hi ? theme.color.selection : undefined, children: [hi ? `${theme.symbol.select} ` : theme.spacing.rowIndent, "+", r.adds, " \u2212", r.dels, " ", r.path] }, r.path));
|
|
52
52
|
}), win.below > 0 ? (_jsxs(Text, { dimColor: true, children: [theme.symbol.moreBelow, " ", win.below, " more"] })) : null, _jsxs(Text, { dimColor: true, children: [theme.symbol.moreAbove, "/", theme.symbol.moreBelow, " move \u00B7 Enter expands \u00B7 Esc closes"] })] }));
|
|
53
53
|
}
|
|
54
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { bold: true, children: [rec.path, _jsxs(Text, { dimColor: true, children: [" ", theme.symbol.separator, " +", rec.adds, " \u2212", rec.dels] })] }), _jsx(Text, { dimColor: true, children: theme.symbol.rule.repeat(32) }), _jsx(SideBySideDiffView, { oldText: rec.oldText, newText: rec.newText, lang: rec.lang
|
|
54
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { bold: true, children: [rec.path, _jsxs(Text, { dimColor: true, children: [" ", theme.symbol.separator, " +", rec.adds, " \u2212", rec.dels] })] }), _jsx(Text, { dimColor: true, children: theme.symbol.rule.repeat(32) }), _jsx(SideBySideDiffView, { oldText: rec.oldText, newText: rec.newText, lang: rec.lang }), _jsx(Text, { dimColor: true, children: theme.symbol.rule.repeat(32) }), _jsxs(Text, { dimColor: true, children: [theme.symbol.moreAbove, "/", theme.symbol.moreBelow, " prev/next file \u00B7 Enter collapses \u00B7 Esc closes"] })] }));
|
|
55
55
|
}
|
package/dist/ui/diff-view.js
CHANGED
|
@@ -6,8 +6,9 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
6
6
|
// changed-word backgrounds + syntax foregrounds (ui/highlight, zero-dep),
|
|
7
7
|
// one <Text> per run (no per-character nodes), word diff + highlighting
|
|
8
8
|
// memoized/cached so the 1s busy tick never recomputes. Long lines are
|
|
9
|
-
// never truncated (Ink wraps; copy/paste stays intact)
|
|
10
|
-
//
|
|
9
|
+
// never truncated (Ink wraps; copy/paste stays intact); the full hunk list
|
|
10
|
+
// renders by default (an explicit maxLines windows it when a caller passes
|
|
11
|
+
// one). All paint comes from
|
|
11
12
|
// ui/theme tokens (Ink supports color + backgroundColor on Text — verified
|
|
12
13
|
// against the installed Ink 7 typings).
|
|
13
14
|
import React from "react";
|
package/dist/ui/diff.js
CHANGED
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
export const CHANGE_THRESHOLD = 0.4;
|
|
5
5
|
// Mirrors Claude's DiffDetailView guards.
|
|
6
6
|
export const MAX_FILE_BYTES = 1_000_000;
|
|
7
|
+
// Retained for compatibility (no longer applied — the engine returns the
|
|
8
|
+
// full hunk list; views render it whole). Safety caps that remain enforced:
|
|
9
|
+
// MAX_FILE_BYTES, binary detect, MAX_MYERS_LINES fallback, MAX_WORD_TOKENS
|
|
10
|
+
// fallback.
|
|
7
11
|
export const MAX_CHANGED_LINES = 400;
|
|
8
12
|
export const DIFF_CONTEXT = 3;
|
|
9
13
|
// Worst-case guards for the O(ND) Myers pass + O(w1*w2) word pass.
|
|
@@ -183,7 +187,7 @@ export function computeDiff(oldText, newText) {
|
|
|
183
187
|
const hunks = [];
|
|
184
188
|
let adds = 0;
|
|
185
189
|
let dels = 0;
|
|
186
|
-
|
|
190
|
+
const truncated = false;
|
|
187
191
|
let hunkStart = Math.max(0, changeIdx[0] - DIFF_CONTEXT);
|
|
188
192
|
let hunkEnd = Math.min(raw.length, changeIdx[0] + DIFF_CONTEXT + 1);
|
|
189
193
|
const flush = (s, e) => {
|
|
@@ -242,7 +246,6 @@ export function computeDiff(oldText, newText) {
|
|
|
242
246
|
}
|
|
243
247
|
hunks.push({ oldStart, oldLines: oCount, newStart, newLines: nCount, lines });
|
|
244
248
|
};
|
|
245
|
-
let changed = 0;
|
|
246
249
|
for (let c = 1; c < changeIdx.length; c++) {
|
|
247
250
|
const prev = changeIdx[c - 1];
|
|
248
251
|
const cur = changeIdx[c];
|
|
@@ -251,32 +254,11 @@ export function computeDiff(oldText, newText) {
|
|
|
251
254
|
}
|
|
252
255
|
else {
|
|
253
256
|
flush(hunkStart, hunkEnd);
|
|
254
|
-
changed = hunks.reduce((t, h) => t + h.lines.filter((l) => l.kind !== "context").length, 0);
|
|
255
|
-
if (changed >= MAX_CHANGED_LINES) {
|
|
256
|
-
truncated = true;
|
|
257
|
-
return { hunks, adds, dels, truncated, skipped: null, isNewFile: oldText === null };
|
|
258
|
-
}
|
|
259
257
|
hunkStart = Math.max(0, cur - DIFF_CONTEXT);
|
|
260
258
|
hunkEnd = Math.min(raw.length, cur + DIFF_CONTEXT + 1);
|
|
261
259
|
}
|
|
262
260
|
}
|
|
263
261
|
flush(hunkStart, hunkEnd);
|
|
264
|
-
changed = hunks.reduce((t, h) => t + h.lines.filter((l) => l.kind !== "context").length, 0);
|
|
265
|
-
if (changed > MAX_CHANGED_LINES) {
|
|
266
|
-
// Trim trailing hunks past the budget (keep the head — the user
|
|
267
|
-
// reviews top-down; the notice names the remainder).
|
|
268
|
-
let kept = 0;
|
|
269
|
-
const out = [];
|
|
270
|
-
for (const h of hunks) {
|
|
271
|
-
const n = h.lines.filter((l) => l.kind !== "context").length;
|
|
272
|
-
if (kept + n > MAX_CHANGED_LINES)
|
|
273
|
-
break;
|
|
274
|
-
out.push(h);
|
|
275
|
-
kept += n;
|
|
276
|
-
}
|
|
277
|
-
truncated = true;
|
|
278
|
-
return { hunks: out, adds, dels, truncated, skipped: null, isNewFile: oldText === null };
|
|
279
|
-
}
|
|
280
262
|
return { hunks, adds, dels, truncated, skipped: null, isNewFile: oldText === null };
|
|
281
263
|
}
|
|
282
264
|
export function computeSideBySide(oldText, newText) {
|
|
@@ -316,9 +298,7 @@ export function computeSideBySide(oldText, newText) {
|
|
|
316
298
|
const rows = [];
|
|
317
299
|
let adds = 0;
|
|
318
300
|
let dels = 0;
|
|
319
|
-
|
|
320
|
-
const changedSoFar = () => dels + adds;
|
|
321
|
-
// Returns false when the change budget is exhausted (stop windowing).
|
|
301
|
+
const truncated = false;
|
|
322
302
|
const flushSlice = (s, e) => {
|
|
323
303
|
const slice = raw.slice(s, e);
|
|
324
304
|
let k = 0;
|
|
@@ -376,12 +356,7 @@ export function computeSideBySide(oldText, newText) {
|
|
|
376
356
|
}
|
|
377
357
|
dels += delRun.length;
|
|
378
358
|
adds += addRun.length;
|
|
379
|
-
if (changedSoFar() >= MAX_CHANGED_LINES) {
|
|
380
|
-
truncated = true;
|
|
381
|
-
return false;
|
|
382
|
-
}
|
|
383
359
|
}
|
|
384
|
-
return true;
|
|
385
360
|
};
|
|
386
361
|
let hunkStart = Math.max(0, changeIdx[0] - DIFF_CONTEXT);
|
|
387
362
|
let hunkEnd = Math.min(raw.length, changeIdx[0] + DIFF_CONTEXT + 1);
|
|
@@ -392,31 +367,11 @@ export function computeSideBySide(oldText, newText) {
|
|
|
392
367
|
hunkEnd = Math.min(raw.length, cur + DIFF_CONTEXT + 1);
|
|
393
368
|
}
|
|
394
369
|
else {
|
|
395
|
-
|
|
396
|
-
return { kind: "diff", rows, adds, dels, truncated, isNewFile: oldText === null };
|
|
397
|
-
}
|
|
370
|
+
flushSlice(hunkStart, hunkEnd);
|
|
398
371
|
hunkStart = Math.max(0, cur - DIFF_CONTEXT);
|
|
399
372
|
hunkEnd = Math.min(raw.length, cur + DIFF_CONTEXT + 1);
|
|
400
373
|
}
|
|
401
374
|
}
|
|
402
375
|
flushSlice(hunkStart, hunkEnd);
|
|
403
|
-
if (dels + adds > MAX_CHANGED_LINES) {
|
|
404
|
-
// Single-hunk overflow: the slice already pushed past the budget —
|
|
405
|
-
// trim trailing rows past 400 changes (keep the head; the notice
|
|
406
|
-
// names the remainder). Mirrors computeDiff's trailing-hunk trim.
|
|
407
|
-
let kept = 0;
|
|
408
|
-
let cut = rows.length;
|
|
409
|
-
for (let i = 0; i < rows.length; i++) {
|
|
410
|
-
if (rows[i].kind === "change") {
|
|
411
|
-
kept += 1;
|
|
412
|
-
if (kept > MAX_CHANGED_LINES) {
|
|
413
|
-
cut = i;
|
|
414
|
-
break;
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
rows.length = cut;
|
|
419
|
-
truncated = true;
|
|
420
|
-
}
|
|
421
376
|
return { kind: "diff", rows, adds, dels, truncated, isNewFile: oldText === null };
|
|
422
377
|
}
|
package/dist/ui/modals.js
CHANGED
|
@@ -7,10 +7,10 @@ import React from "react";
|
|
|
7
7
|
import { Box, Text } from "ink";
|
|
8
8
|
import { SideBySideDiffView } from "./side-by-side.js";
|
|
9
9
|
import { theme } from "./theme.js";
|
|
10
|
-
//
|
|
11
|
-
// the
|
|
12
|
-
//
|
|
13
|
-
export const APPROVAL_DIFF_MAX_LINES =
|
|
10
|
+
// Retained for compatibility (no longer applied — the approval preview
|
|
11
|
+
// renders the full diff; smoothness comes from the per-mount memo + word
|
|
12
|
+
// fallbacks, not from a row cap).
|
|
13
|
+
export const APPROVAL_DIFF_MAX_LINES = Infinity;
|
|
14
14
|
export const APPROVAL_OPTIONS = ["once", "always", "trustAll", "no"];
|
|
15
15
|
// Command/file preview: the audit description minus its `⚙ name` prefix
|
|
16
16
|
// (the tool name already headlines above). Falls back to the full text
|
|
@@ -39,7 +39,7 @@ export const ApprovalBox = React.memo(function ApprovalBox({ toolName, descripti
|
|
|
39
39
|
{ label: "[t]rust all write/edit/bash this session", option: "trustAll" },
|
|
40
40
|
{ label: "[n]o — deny this call", option: "no" },
|
|
41
41
|
];
|
|
42
|
-
return (_jsxs(Box, { flexDirection: "column", borderStyle: theme.border.style, borderColor: theme.border.approval, paddingX: theme.spacing.pickerPadX, children: [_jsxs(Text, { bold: true, color: theme.color.warning, children: [theme.symbol.warningMark, " Atom permission \u2014 allow this tool?"] }), _jsx(Text, { bold: true, children: approvalTitle(toolName) }), _jsx(Text, { color: theme.color.code, children: approvalPreview(toolName, description) }), diff ? _jsx(SideBySideDiffView, { oldText: diff.oldText, newText: diff.newText, lang: diff.lang
|
|
42
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: theme.border.style, borderColor: theme.border.approval, paddingX: theme.spacing.pickerPadX, children: [_jsxs(Text, { bold: true, color: theme.color.warning, children: [theme.symbol.warningMark, " Atom permission \u2014 allow this tool?"] }), _jsx(Text, { bold: true, children: approvalTitle(toolName) }), _jsx(Text, { color: theme.color.code, children: approvalPreview(toolName, description) }), diff ? _jsx(SideBySideDiffView, { oldText: diff.oldText, newText: diff.newText, lang: diff.lang }) : null, rows.map((r, i) => (_jsxs(Text, { color: i === selected ? theme.color.selection : undefined, children: [i === selected ? `${theme.symbol.select} ` : theme.spacing.rowIndent, r.label] }, r.option))), _jsx(Text, { dimColor: true, children: "\u2191/\u2193 + Enter selects \u00B7 y/a/t/n shortcuts \u00B7 Esc denies" })] }));
|
|
43
43
|
});
|
|
44
44
|
export const QuestionBox = React.memo(function QuestionBox({ question, options, allowCustom, askCustom, askSelIndex }) {
|
|
45
45
|
questionRenderProbe.count += 1;
|
package/dist/ui/side-by-side.js
CHANGED
|
@@ -14,7 +14,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
14
14
|
// (code-point safe); below NARROW_COLUMNS the view degrades to the
|
|
15
15
|
// stacked unified DiffView instead of destroying the layout.
|
|
16
16
|
// - computed once per mount (useMemo, keyed on inputs + pane width) and
|
|
17
|
-
//
|
|
17
|
+
// rendered whole (an explicit maxRows windows it when a caller passes one)
|
|
18
|
+
// — never recomputed per tick, never floods via re-computation.
|
|
18
19
|
// All paint comes from ui/theme tokens.
|
|
19
20
|
import React from "react";
|
|
20
21
|
import { Box, Text } from "ink";
|
|
@@ -24,10 +25,11 @@ import { DiffView, LineBody } from "./diff-view.js";
|
|
|
24
25
|
import { theme } from "./theme.js";
|
|
25
26
|
// Below this width two panes cannot breathe — stack unified instead.
|
|
26
27
|
export const SBS_NARROW_COLUMNS = 70;
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
|
|
28
|
+
// Uncapped: views render the full row list (smooth via per-mount useMemo +
|
|
29
|
+
// append-once Static + word-token/Myers fallbacks in the engine). maxRows
|
|
30
|
+
// remains as an opt-in window for callers/tests that want a collapsed tail.
|
|
31
|
+
// Retained for compatibility.
|
|
32
|
+
export const TRANSCRIPT_DIFF_MAX_LINES = Infinity;
|
|
31
33
|
function truncateTo(s, width) {
|
|
32
34
|
const chars = [...s];
|
|
33
35
|
if (chars.length <= width)
|
package/dist/ui/transcript.js
CHANGED
|
@@ -5,7 +5,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
5
5
|
// All paint comes from ui/theme tokens — no literal colors or glyphs here.
|
|
6
6
|
import React from "react";
|
|
7
7
|
import { Box, Static, Text } from "ink";
|
|
8
|
-
import { SideBySideDiffView
|
|
8
|
+
import { SideBySideDiffView } from "./side-by-side.js";
|
|
9
9
|
import { ErrorCard, classifyToolError } from "./errors.js";
|
|
10
10
|
import { MarkdownText, ToolLine } from "./markdown.js";
|
|
11
11
|
import { theme } from "./theme.js";
|
|
@@ -73,9 +73,9 @@ export function renderTranscriptItem(item) {
|
|
|
73
73
|
// write/edit label swallowed by pairing (success line immediately
|
|
74
74
|
// followed by an error line) keeps its committed diff above the card.
|
|
75
75
|
const labelDiff = item.label?.diff;
|
|
76
|
-
return (_jsxs(React.Fragment, { children: [item.label ? _jsx(ToolLine, { content: item.label.content, ms: item.label.ms }) : null, labelDiff && !item.label?.error ? (_jsx(SideBySideDiffView, { oldText: labelDiff.oldText, newText: labelDiff.newText, lang: labelDiff.lang
|
|
76
|
+
return (_jsxs(React.Fragment, { children: [item.label ? _jsx(ToolLine, { content: item.label.content, ms: item.label.ms }) : null, labelDiff && !item.label?.error ? (_jsx(SideBySideDiffView, { oldText: labelDiff.oldText, newText: labelDiff.newText, lang: labelDiff.lang })) : null, _jsx(ErrorCard, { classified: classified })] }, i));
|
|
77
77
|
}
|
|
78
|
-
return (_jsxs(React.Fragment, { children: [_jsx(ToolLine, { content: t.content, error: t.error, ms: t.ms }), t.diff && !t.error ? (_jsx(SideBySideDiffView, { oldText: t.diff.oldText, newText: t.diff.newText, lang: t.diff.lang
|
|
78
|
+
return (_jsxs(React.Fragment, { children: [_jsx(ToolLine, { content: t.content, error: t.error, ms: t.ms }), t.diff && !t.error ? (_jsx(SideBySideDiffView, { oldText: t.diff.oldText, newText: t.diff.newText, lang: t.diff.lang })) : null] }, i));
|
|
79
79
|
}
|
|
80
80
|
return (_jsxs(Box, { flexDirection: "column", marginBottom: theme.spacing.turnGap, children: [_jsx(Text, { children: _jsxs(Text, { color: theme.color.assistant, bold: true, children: [theme.symbol.speakerAssistant, " "] }) }), _jsx(MarkdownText, { text: t.content })] }, i));
|
|
81
81
|
}
|
|
@@ -146,7 +146,7 @@ Exact `ExtensionAPI` methods — nothing else exists:
|
|
|
146
146
|
| `isProjectTrusted()` | Whether this load is trusted (degrade gracefully when false) |
|
|
147
147
|
| `setStatusSegment(text)` | One status-bar slot per extension (upsert by owner) |
|
|
148
148
|
| `setWidget(def)` | Panel widget (`placement: "panel"`, keyed by owner + id) |
|
|
149
|
-
| `notify(message)` | Transient `(name) message` transcript line |
|
|
149
|
+
| `notify(message)` | Transient `(name) message` transcript line (staged queue caps at 100, drop-oldest) |
|
|
150
150
|
| `promptUser(question, options?, allowCustom?)` | Modal dialog; rejects headless, during activation, or while one is open |
|
|
151
151
|
|
|
152
152
|
Stores behind the API: `src/tools/custom.ts`, `intercept.ts`, `overrides.ts`, `provider-hooks.ts`, `compaction-hooks.ts`, `src/extension-commands.ts`, `src/extension-ui.ts`, `src/project-trust.ts`.
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
// as the normal model-visible result (approval is skipped, the turn
|
|
5
5
|
// continues); anything this hook does not block runs untouched.
|
|
6
6
|
//
|
|
7
|
-
// Gallery sample for documentation/extensions.md — that guide
|
|
8
|
-
// this file
|
|
7
|
+
// Gallery sample for documentation/extensions.md — that guide excerpts
|
|
8
|
+
// this file and references it by name. Covered by
|
|
9
9
|
// tests/extension-gallery.test.ts, which loads this exact file through the
|
|
10
10
|
// real loadExtensions path.
|
|
11
11
|
module.exports = function auditGate(api) {
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
// `Error: invalid call: ...` result and the implementation never runs), and
|
|
6
6
|
// dispatches through the shared loop.
|
|
7
7
|
//
|
|
8
|
-
// Gallery sample for documentation/extensions.md — that guide
|
|
9
|
-
// this file
|
|
8
|
+
// Gallery sample for documentation/extensions.md — that guide excerpts
|
|
9
|
+
// this file and references it by name. Covered by
|
|
10
10
|
// tests/extension-gallery.test.ts, which loads this exact file through the
|
|
11
11
|
// real loadExtensions path.
|
|
12
12
|
const notes = [];
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
// transcript) so the dialog flow is testable headless — tests drive the
|
|
7
7
|
// command with a stub askUser and unit-test the helper directly.
|
|
8
8
|
//
|
|
9
|
-
// Gallery sample for documentation/extensions.md — that guide
|
|
10
|
-
// this file
|
|
9
|
+
// Gallery sample for documentation/extensions.md — that guide excerpts
|
|
10
|
+
// this file and references it by name. Covered by
|
|
11
11
|
// tests/extension-gallery.test.ts, which loads this exact file through the
|
|
12
12
|
// real loadExtensions path.
|
|
13
13
|
function summarizeChoice(choice, extra) {
|
package/package.json
CHANGED