@north-light/crouter 0.3.162 → 0.3.164
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/builtin-memory/00-runtime-base.md +1 -0
- package/dist/builtin-memory/04-orchestration-kernel.md +1 -0
- package/dist/builtin-memory/init.md +38 -0
- package/dist/builtin-memory/internal/INDEX.md +1 -0
- package/dist/builtin-memory/internal/memory-loading.md +45 -0
- package/dist/builtin-memory/wedged-child-on-runaway-bash.md +2 -2
- package/dist/builtin-pi-packages/pi-crtr-extensions/README.md +0 -1
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/provider-rotation.test.ts +34 -2
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +14 -3
- package/dist/clients/attach/__tests__/crtr-output-coverage.test.js +1 -1
- package/dist/clients/attach/__tests__/crtr-output.test.js +14 -9
- package/dist/clients/attach/__tests__/edit-diff.test.d.ts +1 -0
- package/dist/clients/attach/__tests__/edit-diff.test.js +35 -0
- package/dist/clients/attach/__tests__/editor-frame-title.test.d.ts +1 -0
- package/dist/clients/attach/__tests__/editor-frame-title.test.js +19 -0
- package/dist/clients/attach/render/chat-view.js +54 -38
- package/dist/clients/attach/render/crtr-output.d.ts +2 -1
- package/dist/clients/attach/render/crtr-output.js +33 -4
- package/dist/clients/attach/render/edit-diff.d.ts +21 -6
- package/dist/clients/attach/render/edit-diff.js +104 -81
- package/dist/clients/attach/render/tool-calls.d.ts +21 -20
- package/dist/clients/attach/render/tool-calls.js +89 -43
- package/dist/clients/attach/session/connection.d.ts +1 -1
- package/dist/clients/attach/session/editor-frame.d.ts +5 -1
- package/dist/clients/attach/session/editor-frame.js +21 -15
- package/dist/clients/attach/session/identity.d.ts +1 -1
- package/dist/clients/attach/viewer.js +628 -624
- package/dist/commands/__tests__/api-canvas-source.test.d.ts +1 -0
- package/dist/commands/__tests__/api-canvas-source.test.js +37 -0
- package/dist/commands/__tests__/human.test.js +2 -0
- package/dist/commands/__tests__/search-contents.test.d.ts +1 -0
- package/dist/commands/__tests__/search-contents.test.js +52 -0
- package/dist/commands/api-client.js +1 -0
- package/dist/commands/human/prompts.js +2 -2
- package/dist/commands/human/shared.d.ts +1 -0
- package/dist/commands/human/shared.js +7 -4
- package/dist/commands/memory/lint.d.ts +6 -0
- package/dist/commands/memory/lint.js +177 -35
- package/dist/commands/memory/read.js +22 -1
- package/dist/commands/memory/write.js +18 -4
- package/dist/commands/memory.js +1 -1
- package/dist/commands/search/contents.js +2 -2
- package/dist/commands/sys/__tests__/sync-deps.test.js +12 -21
- package/dist/commands/sys/__tests__/sync-import.test.js +30 -29
- package/dist/commands/sys/setup-wizard.d.ts +8 -1
- package/dist/commands/sys/setup-wizard.js +209 -58
- package/dist/commands/sys/sync-deps.js +14 -18
- package/dist/commands/sys/sync-project-guidance.js +16 -19
- package/dist/core/__tests__/migration.test.js +8 -3
- package/dist/core/__tests__/on-read-crouter-home-fence.test.js +7 -10
- package/dist/core/__tests__/on-read-dedup-resume.test.js +13 -25
- package/dist/core/__tests__/on-read-identity.test.js +8 -15
- package/dist/core/__tests__/revive.test.js +2 -2
- package/dist/core/__tests__/tmux-surface.test.js +10 -4
- package/dist/core/__tests__/worktree.test.js +3 -3
- package/dist/core/canvas/extensions.d.ts +1 -1
- package/dist/core/canvas/extensions.js +18 -11
- package/dist/core/canvas/labels.d.ts +4 -5
- package/dist/core/canvas/labels.js +9 -9
- package/dist/core/command-manifests/schema.js +18 -5
- package/dist/core/command.js +17 -7
- package/dist/core/configured-clis/invoker.js +2 -0
- package/dist/core/help.d.ts +3 -1
- package/dist/core/help.js +5 -2
- package/dist/core/keybindings/__tests__/resolve.test.js +5 -2
- package/dist/core/keybindings/catalog.d.ts +3 -2
- package/dist/core/keybindings/catalog.js +34 -25
- package/dist/core/keybindings/index.d.ts +1 -1
- package/dist/core/keybindings/resolve.js +8 -0
- package/dist/core/keybindings/types.d.ts +6 -1
- package/dist/core/memory/doc-link-grammar.d.ts +20 -0
- package/dist/core/memory/doc-link-grammar.js +110 -0
- package/dist/core/memory-resolver.d.ts +5 -0
- package/dist/core/memory-resolver.js +12 -1
- package/dist/core/runtime/bearings.d.ts +8 -8
- package/dist/core/runtime/bearings.js +20 -16
- package/dist/core/runtime/broker.js +3 -13
- package/dist/core/runtime/canvas-extensions.d.ts +1 -0
- package/dist/core/runtime/canvas-extensions.js +2 -0
- package/dist/core/runtime/launch.d.ts +1 -1
- package/dist/core/runtime/launch.js +1 -1
- package/dist/core/runtime/tmux.js +136 -100
- package/dist/core/scope.js +4 -5
- package/dist/core/substrate/ceiling.d.ts +3 -6
- package/dist/core/substrate/ceiling.js +13 -15
- package/dist/core/substrate/index.d.ts +2 -3
- package/dist/core/substrate/index.js +2 -2
- package/dist/core/substrate/on-read.d.ts +4 -13
- package/dist/core/substrate/on-read.js +142 -262
- package/dist/core/substrate/render.js +4 -4
- package/dist/core/substrate/schema.d.ts +3 -2
- package/dist/core/substrate/schema.js +5 -5
- package/dist/pi-extensions/__tests__/canvas-tool-guide.test.d.ts +1 -0
- package/dist/pi-extensions/__tests__/canvas-tool-guide.test.js +96 -0
- package/dist/pi-extensions/canvas-doc-substrate.js +7 -8
- package/dist/pi-extensions/canvas-tool-guide.d.ts +14 -0
- package/dist/pi-extensions/canvas-tool-guide.js +77 -0
- package/package.json +2 -2
- package/runtime.lock.json +6 -6
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/crouter-help.ts +0 -95
|
@@ -1278,6 +1278,50 @@ class ModelsPanel {
|
|
|
1278
1278
|
return frame(this.fitBody(body, selectedLine, contentBudget));
|
|
1279
1279
|
}
|
|
1280
1280
|
}
|
|
1281
|
+
/** Collapse explicitly-paired catalog definitions into one setup action row.
|
|
1282
|
+
* Pair membership and field roles come only from catalog metadata; neither
|
|
1283
|
+
* rendering nor installation guesses from binding ID names. */
|
|
1284
|
+
function buildSetupKeybindingRows() {
|
|
1285
|
+
const byId = new Map(BINDING_CATALOG.map((definition) => [definition.id, definition]));
|
|
1286
|
+
const consumed = new Set();
|
|
1287
|
+
const rows = [];
|
|
1288
|
+
for (const definition of BINDING_CATALOG) {
|
|
1289
|
+
if (consumed.has(definition.id))
|
|
1290
|
+
continue;
|
|
1291
|
+
if (definition.pair === undefined) {
|
|
1292
|
+
rows.push({
|
|
1293
|
+
group: definition.group,
|
|
1294
|
+
name: definition.name,
|
|
1295
|
+
description: definition.description,
|
|
1296
|
+
fields: [{ definition }],
|
|
1297
|
+
});
|
|
1298
|
+
consumed.add(definition.id);
|
|
1299
|
+
continue;
|
|
1300
|
+
}
|
|
1301
|
+
const paired = byId.get(definition.pair.pairedId);
|
|
1302
|
+
if (paired?.pair?.pairedId !== definition.id || paired.pair.field === definition.pair.field) {
|
|
1303
|
+
throw new Error(`invalid keybinding pair metadata for ${definition.id}`);
|
|
1304
|
+
}
|
|
1305
|
+
const members = [definition, paired];
|
|
1306
|
+
const menu = members.find((member) => member.pair?.field === 'menu');
|
|
1307
|
+
const global = members.find((member) => member.pair?.field === 'global');
|
|
1308
|
+
if (menu === undefined || global === undefined)
|
|
1309
|
+
throw new Error(`incomplete keybinding pair for ${definition.id}`);
|
|
1310
|
+
rows.push({
|
|
1311
|
+
group: menu.group,
|
|
1312
|
+
name: menu.name,
|
|
1313
|
+
description: menu.description,
|
|
1314
|
+
fields: [
|
|
1315
|
+
{ label: 'Menu', definition: menu },
|
|
1316
|
+
{ label: 'Global', definition: global },
|
|
1317
|
+
],
|
|
1318
|
+
});
|
|
1319
|
+
consumed.add(menu.id);
|
|
1320
|
+
consumed.add(global.id);
|
|
1321
|
+
}
|
|
1322
|
+
return rows;
|
|
1323
|
+
}
|
|
1324
|
+
const SETUP_KEYBINDING_ROWS = buildSetupKeybindingRows();
|
|
1281
1325
|
export class SetupWizard {
|
|
1282
1326
|
manifest;
|
|
1283
1327
|
installedPackageIds;
|
|
@@ -1299,6 +1343,7 @@ export class SetupWizard {
|
|
|
1299
1343
|
expandedPluginId = null;
|
|
1300
1344
|
confirming = false;
|
|
1301
1345
|
keyIndex = 0;
|
|
1346
|
+
keyFieldIndex = 0;
|
|
1302
1347
|
keyScroll = 0;
|
|
1303
1348
|
keyFilter = '';
|
|
1304
1349
|
filteringKeys = false;
|
|
@@ -1539,25 +1584,38 @@ export class SetupWizard {
|
|
|
1539
1584
|
const resolution = this.keyDraft.resolution();
|
|
1540
1585
|
const needle = this.keyFilter.trim().toLowerCase();
|
|
1541
1586
|
if (needle === '')
|
|
1542
|
-
return
|
|
1543
|
-
return
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1587
|
+
return SETUP_KEYBINDING_ROWS;
|
|
1588
|
+
return SETUP_KEYBINDING_ROWS.filter((row) => [
|
|
1589
|
+
row.name,
|
|
1590
|
+
row.group,
|
|
1591
|
+
row.description,
|
|
1592
|
+
...row.fields.flatMap((field) => [
|
|
1593
|
+
field.label ?? '',
|
|
1594
|
+
field.definition.id,
|
|
1595
|
+
field.definition.name,
|
|
1596
|
+
field.definition.description,
|
|
1597
|
+
formatBinding(resolution, field.definition.id),
|
|
1598
|
+
]),
|
|
1599
|
+
].some((value) => value.toLowerCase().includes(needle)));
|
|
1600
|
+
}
|
|
1601
|
+
selectedKeyRow() {
|
|
1550
1602
|
const rows = this.filteredKeybindings();
|
|
1551
1603
|
if (rows.length === 0)
|
|
1552
1604
|
return undefined;
|
|
1553
1605
|
this.keyIndex = Math.max(0, Math.min(this.keyIndex, rows.length - 1));
|
|
1554
|
-
|
|
1606
|
+
const row = rows[this.keyIndex];
|
|
1607
|
+
this.keyFieldIndex = Math.max(0, Math.min(this.keyFieldIndex, row.fields.length - 1));
|
|
1608
|
+
return row;
|
|
1609
|
+
}
|
|
1610
|
+
selectedKeybinding() {
|
|
1611
|
+
return this.selectedKeyRow()?.fields[this.keyFieldIndex]?.definition;
|
|
1555
1612
|
}
|
|
1556
1613
|
moveKey(delta) {
|
|
1557
1614
|
const rows = this.filteredKeybindings();
|
|
1558
1615
|
if (rows.length === 0)
|
|
1559
1616
|
return;
|
|
1560
1617
|
this.keyIndex = Math.max(0, Math.min(rows.length - 1, this.keyIndex + delta));
|
|
1618
|
+
this.keyFieldIndex = Math.min(this.keyFieldIndex, rows[this.keyIndex].fields.length - 1);
|
|
1561
1619
|
const page = this.keyPageSize();
|
|
1562
1620
|
if (this.keyIndex < this.keyScroll)
|
|
1563
1621
|
this.keyScroll = this.keyIndex;
|
|
@@ -1566,6 +1624,14 @@ export class SetupWizard {
|
|
|
1566
1624
|
this.keyEditError = undefined;
|
|
1567
1625
|
this.invalidate();
|
|
1568
1626
|
}
|
|
1627
|
+
moveKeyField(delta) {
|
|
1628
|
+
const row = this.selectedKeyRow();
|
|
1629
|
+
if (row === undefined)
|
|
1630
|
+
return;
|
|
1631
|
+
this.keyFieldIndex = Math.max(0, Math.min(row.fields.length - 1, this.keyFieldIndex + delta));
|
|
1632
|
+
this.keyEditError = undefined;
|
|
1633
|
+
this.invalidate();
|
|
1634
|
+
}
|
|
1569
1635
|
// Max output lines the row region may emit before it starts clipping the
|
|
1570
1636
|
// confirm prompt / notice / footer. Budgets the fixed chrome around the list:
|
|
1571
1637
|
// header (3) + blank + tab header/count (2) + blank + footer (1) plus a small
|
|
@@ -1821,6 +1887,14 @@ export class SetupWizard {
|
|
|
1821
1887
|
this.moveKey(-1);
|
|
1822
1888
|
return;
|
|
1823
1889
|
}
|
|
1890
|
+
else if (this.matches('crtr.setup.cell-previous', data)) {
|
|
1891
|
+
this.moveKeyField(-1);
|
|
1892
|
+
return;
|
|
1893
|
+
}
|
|
1894
|
+
else if (this.matches('crtr.setup.cell-next', data)) {
|
|
1895
|
+
this.moveKeyField(1);
|
|
1896
|
+
return;
|
|
1897
|
+
}
|
|
1824
1898
|
else if (this.matches('crtr.setup.keybindings.page-up', data)) {
|
|
1825
1899
|
this.moveKey(-this.keyPageSize());
|
|
1826
1900
|
return;
|
|
@@ -2027,7 +2101,7 @@ export class SetupWizard {
|
|
|
2027
2101
|
? `type gestures separated by commas · ${this.label('crtr.setup.keybindings.commit-edit')} commit · ${this.label('crtr.setup.keybindings.cancel-edit')} cancel · ${this.label('crtr.setup.keybindings.page-up')}/${this.label('crtr.setup.keybindings.page-down')} scroll`
|
|
2028
2102
|
: this.filteringKeys
|
|
2029
2103
|
? `filter: ${this.keyFilter || 'type to search'} · ${this.label('crtr.setup.keybindings.edit')} keep · ${this.label('crtr.setup.keybindings.filter-delete')} delete · ${this.label('crtr.setup.keybindings.filter-clear')} clear`
|
|
2030
|
-
: `${this.label('crtr.setup.list-down')}/${this.label('crtr.setup.list-up')} move · ${this.label('crtr.setup.keybindings.edit')} edit · ${this.label('crtr.setup.keybindings.unbind')} disable · ${this.label('crtr.setup.keybindings.reset-row')} reset · ${this.label('crtr.setup.keybindings.reset-all')} reset all · ${this.label('crtr.setup.keybindings.filter')} filter · ${this.label('crtr.setup.keybindings.save')} save · ${this.label('crtr.setup.keybindings.discard')} ${this.keyFilter !== '' ? 'clear filter' : 'discard'}`;
|
|
2104
|
+
: `${this.label('crtr.setup.list-down')}/${this.label('crtr.setup.list-up')} move · ${this.label('crtr.setup.cell-previous')}/${this.label('crtr.setup.cell-next')} field · ${this.label('crtr.setup.keybindings.edit')} edit · ${this.label('crtr.setup.keybindings.unbind')} disable · ${this.label('crtr.setup.keybindings.reset-row')} reset · ${this.label('crtr.setup.keybindings.reset-all')} reset all · ${this.label('crtr.setup.keybindings.filter')} filter · ${this.label('crtr.setup.keybindings.save')} save · ${this.label('crtr.setup.keybindings.discard')} ${this.keyFilter !== '' ? 'clear filter' : 'discard'}`;
|
|
2031
2105
|
return [truncateToWidth(theme.fg('dim', line), width)];
|
|
2032
2106
|
}
|
|
2033
2107
|
renderProviders(width) {
|
|
@@ -2113,30 +2187,50 @@ export class SetupWizard {
|
|
|
2113
2187
|
out.push(truncateToWidth(theme.fg('dim', `${this.label('crtr.setup.confirm.accept')} confirm · ${this.label('crtr.setup.confirm.back')} go back`), width));
|
|
2114
2188
|
return out;
|
|
2115
2189
|
}
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
// and the render stay in lockstep.
|
|
2119
|
-
keyRowHasMessage(rows, index) {
|
|
2120
|
-
const definition = rows[index];
|
|
2190
|
+
keyRowMessage(rows, index) {
|
|
2191
|
+
const row = rows[index];
|
|
2121
2192
|
const selected = index === this.keyIndex;
|
|
2122
|
-
const editing =
|
|
2123
|
-
if (!selected &&
|
|
2124
|
-
return
|
|
2125
|
-
if (editing && this.keyEditError)
|
|
2126
|
-
return
|
|
2127
|
-
const diagnostic = this.keyDiagnostics.find((entry) => entry.bindingId === definition.id || entry.relatedBindingIds?.includes(definition.id));
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2193
|
+
const editing = row.fields.find((field) => field.definition.id === this.editingKey);
|
|
2194
|
+
if (!selected && editing === undefined)
|
|
2195
|
+
return undefined;
|
|
2196
|
+
if (editing !== undefined && this.keyEditError)
|
|
2197
|
+
return { label: editing.label, message: this.keyEditError };
|
|
2198
|
+
const diagnostic = this.keyDiagnostics.find((entry) => row.fields.some((field) => entry.bindingId === field.definition.id || entry.relatedBindingIds?.includes(field.definition.id)));
|
|
2199
|
+
if (diagnostic === undefined)
|
|
2200
|
+
return undefined;
|
|
2201
|
+
const responsible = row.fields.find((field) => diagnostic.bindingId === field.definition.id || diagnostic.relatedBindingIds?.includes(field.definition.id));
|
|
2202
|
+
return { label: responsible?.label, message: diagnostic.message };
|
|
2203
|
+
}
|
|
2204
|
+
/** Number of lines needed to keep the active paired field legible. A narrow
|
|
2205
|
+
* row stacks the action and field instead of sacrificing the action name,
|
|
2206
|
+
* selected-field identity, or value to fixed columns. */
|
|
2207
|
+
keyRowBodyLineCount(row, index, resolution, width) {
|
|
2208
|
+
const editingFieldIndex = row.fields.findIndex((field) => field.definition.id === this.editingKey);
|
|
2209
|
+
const active = editingFieldIndex >= 0 || index === this.keyIndex;
|
|
2210
|
+
if (!active || row.fields.length < 2)
|
|
2211
|
+
return 1;
|
|
2212
|
+
const fieldIndex = editingFieldIndex >= 0 ? editingFieldIndex : Math.min(this.keyFieldIndex, row.fields.length - 1);
|
|
2213
|
+
const field = row.fields[fieldIndex];
|
|
2214
|
+
const value = editingFieldIndex >= 0 ? `> ${this.keyInput.getValue()}` : formatBinding(resolution, field.definition.id);
|
|
2215
|
+
const bracketedLabel = `[${field.label}]`;
|
|
2216
|
+
const inlineWidth = 3 + visibleWidth(row.name) + 1 + visibleWidth(bracketedLabel) + 1 + visibleWidth(value);
|
|
2217
|
+
if (inlineWidth <= width)
|
|
2218
|
+
return 1;
|
|
2219
|
+
const compactFieldWidth = visibleWidth(field.label ?? '') + 1 + visibleWidth(value);
|
|
2220
|
+
return compactFieldWidth <= width ? 2 : 3;
|
|
2221
|
+
}
|
|
2222
|
+
// Output lines the visible rows [start..end] cost, counting group headings,
|
|
2223
|
+
// responsive row bodies, and diagnostics. Used to clamp the scroll so the
|
|
2224
|
+
// anchor row always renders.
|
|
2225
|
+
keyLineCost(rows, start, end, resolution, width) {
|
|
2134
2226
|
let lines = 0;
|
|
2135
2227
|
let previousGroup;
|
|
2136
2228
|
for (let index = start; index <= end; index += 1) {
|
|
2137
|
-
const
|
|
2138
|
-
lines += (
|
|
2139
|
-
|
|
2229
|
+
const row = rows[index];
|
|
2230
|
+
lines += (row.group !== previousGroup ? 1 : 0)
|
|
2231
|
+
+ this.keyRowBodyLineCount(row, index, resolution, width)
|
|
2232
|
+
+ (this.keyRowMessage(rows, index) ? 1 : 0);
|
|
2233
|
+
previousGroup = row.group;
|
|
2140
2234
|
}
|
|
2141
2235
|
return lines;
|
|
2142
2236
|
}
|
|
@@ -2157,12 +2251,14 @@ export class SetupWizard {
|
|
|
2157
2251
|
// Injected group-heading and diagnostic lines can make a pageSize-row window
|
|
2158
2252
|
// exceed the raw viewport, so instead of a fixed reserve, clamp the scroll
|
|
2159
2253
|
// dynamically until the anchor is guaranteed to render within `viewport`.
|
|
2160
|
-
const editIndex = this.editingKey !== null
|
|
2254
|
+
const editIndex = this.editingKey !== null
|
|
2255
|
+
? rows.findIndex((row) => row.fields.some((field) => field.definition.id === this.editingKey))
|
|
2256
|
+
: -1;
|
|
2161
2257
|
const anchor = editIndex >= 0 ? editIndex : this.keyIndex;
|
|
2162
2258
|
if (anchor >= 0) {
|
|
2163
2259
|
if (anchor < this.keyScroll)
|
|
2164
2260
|
this.keyScroll = anchor;
|
|
2165
|
-
while (this.keyScroll < anchor && this.keyLineCost(rows, this.keyScroll, anchor) > viewport) {
|
|
2261
|
+
while (this.keyScroll < anchor && this.keyLineCost(rows, this.keyScroll, anchor, resolution, width) > viewport) {
|
|
2166
2262
|
this.keyScroll += 1;
|
|
2167
2263
|
}
|
|
2168
2264
|
}
|
|
@@ -2179,40 +2275,95 @@ export class SetupWizard {
|
|
|
2179
2275
|
out.push(truncateToWidth(theme.fg('warning', 'No matching keybindings.'), width));
|
|
2180
2276
|
return out;
|
|
2181
2277
|
}
|
|
2182
|
-
const nameWidth = Math.max(
|
|
2183
|
-
const keyWidth = Math.max(18, Math.min(34, Math.floor(width * 0.34)));
|
|
2184
|
-
const showDescription = width >= 72;
|
|
2278
|
+
const nameWidth = Math.max(10, Math.min(26, Math.floor(width * 0.28)));
|
|
2185
2279
|
let emitted = 0;
|
|
2186
2280
|
let previousGroup;
|
|
2187
2281
|
for (let index = this.keyScroll; index < rows.length; index += 1) {
|
|
2188
|
-
const
|
|
2282
|
+
const row = rows[index];
|
|
2189
2283
|
const selected = index === this.keyIndex;
|
|
2190
|
-
const
|
|
2191
|
-
const
|
|
2192
|
-
?
|
|
2193
|
-
:
|
|
2194
|
-
const message =
|
|
2195
|
-
|
|
2196
|
-
//
|
|
2197
|
-
|
|
2284
|
+
const editingFieldIndex = row.fields.findIndex((field) => field.definition.id === this.editingKey);
|
|
2285
|
+
const activeFieldIndex = editingFieldIndex >= 0
|
|
2286
|
+
? editingFieldIndex
|
|
2287
|
+
: selected ? Math.min(this.keyFieldIndex, row.fields.length - 1) : 0;
|
|
2288
|
+
const message = this.keyRowMessage(rows, index);
|
|
2289
|
+
const bodyLines = this.keyRowBodyLineCount(row, index, resolution, width);
|
|
2290
|
+
// Budget output lines (heading + responsive row + optional diagnostic) so
|
|
2291
|
+
// the list never clips the confirm prompt / notice / footer below it.
|
|
2292
|
+
const cost = (row.group !== previousGroup ? 1 : 0) + bodyLines + (message ? 1 : 0);
|
|
2198
2293
|
if (emitted > 0 && emitted + cost > viewport)
|
|
2199
2294
|
break;
|
|
2200
|
-
if (
|
|
2201
|
-
out.push(truncateToWidth(theme.fg('accent',
|
|
2202
|
-
previousGroup =
|
|
2295
|
+
if (row.group !== previousGroup) {
|
|
2296
|
+
out.push(truncateToWidth(theme.fg('accent', row.group), width));
|
|
2297
|
+
previousGroup = row.group;
|
|
2203
2298
|
}
|
|
2204
2299
|
const marker = selected ? theme.fg('accent', '▶') : ' ';
|
|
2205
|
-
const sourceMark = resolution.get(definition.id).source === 'user'
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2300
|
+
const sourceMark = row.fields.some((field) => resolution.get(field.definition.id).source === 'user')
|
|
2301
|
+
? theme.fg('warning', '•')
|
|
2302
|
+
: ' ';
|
|
2303
|
+
if (bodyLines > 1) {
|
|
2304
|
+
const field = row.fields[activeFieldIndex];
|
|
2305
|
+
const editing = field.definition.id === this.editingKey;
|
|
2306
|
+
const plainValue = editing ? `> ${this.keyInput.getValue()}` : formatBinding(resolution, field.definition.id);
|
|
2307
|
+
const bracketedLabel = `[${field.label}]`;
|
|
2308
|
+
const compactLabel = field.label ?? '';
|
|
2309
|
+
out.push(truncateToWidth(`${marker}${sourceMark} ${selected ? theme.fg('accent', row.name) : row.name}`, width));
|
|
2310
|
+
if (bodyLines === 2) {
|
|
2311
|
+
const useBracketed = visibleWidth(bracketedLabel) + 1 + visibleWidth(plainValue) <= width;
|
|
2312
|
+
const plainLabel = useBracketed ? bracketedLabel : compactLabel;
|
|
2313
|
+
const label = theme.fg('accent', theme.bold(plainLabel));
|
|
2314
|
+
const indent = ' '.repeat(Math.max(0, Math.min(3, width - visibleWidth(plainLabel) - 1 - visibleWidth(plainValue))));
|
|
2315
|
+
const valueWidth = Math.max(1, width - visibleWidth(indent) - visibleWidth(label) - 1);
|
|
2316
|
+
const value = editing ? (this.keyInput.render(valueWidth)[0] ?? '') : plainValue;
|
|
2317
|
+
out.push(truncateToWidth(`${indent}${label} ${value}`, width));
|
|
2318
|
+
}
|
|
2319
|
+
else {
|
|
2320
|
+
const label = theme.fg('accent', theme.bold(bracketedLabel));
|
|
2321
|
+
const labelIndent = ' '.repeat(Math.max(0, Math.min(3, width - visibleWidth(label))));
|
|
2322
|
+
const value = editing ? (this.keyInput.render(width)[0] ?? '') : plainValue;
|
|
2323
|
+
out.push(truncateToWidth(`${labelIndent}${label}`, width));
|
|
2324
|
+
out.push(truncateToWidth(value, width));
|
|
2325
|
+
}
|
|
2326
|
+
}
|
|
2327
|
+
else {
|
|
2328
|
+
const name = padAnsi(selected ? theme.fg('accent', row.name) : row.name, nameWidth);
|
|
2329
|
+
const available = Math.max(1, width - 4 - nameWidth);
|
|
2330
|
+
const showAllFields = row.fields.length === 1 || available >= 28;
|
|
2331
|
+
const visibleFields = showAllFields ? row.fields : [row.fields[activeFieldIndex]];
|
|
2332
|
+
const fieldAreaWidth = row.fields.length > 1
|
|
2333
|
+
? Math.min(61, available)
|
|
2334
|
+
: Math.min(34, available);
|
|
2335
|
+
const gapWidth = Math.max(0, visibleFields.length - 1);
|
|
2336
|
+
const fieldWidth = Math.max(1, Math.floor((fieldAreaWidth - gapWidth) / visibleFields.length));
|
|
2337
|
+
const renderedFields = visibleFields.map((field) => {
|
|
2338
|
+
const fieldIndex = row.fields.indexOf(field);
|
|
2339
|
+
const editing = field.definition.id === this.editingKey;
|
|
2340
|
+
const active = editing || (selected && fieldIndex === activeFieldIndex);
|
|
2341
|
+
const label = field.label === undefined
|
|
2342
|
+
? ''
|
|
2343
|
+
: active
|
|
2344
|
+
? theme.fg('accent', theme.bold(`[${field.label}]`))
|
|
2345
|
+
: theme.fg('dim', `${field.label}:`);
|
|
2346
|
+
const labelWidth = visibleWidth(label);
|
|
2347
|
+
const valueWidth = Math.max(1, fieldWidth - labelWidth - (label === '' ? 0 : 1));
|
|
2348
|
+
// Render the edit field whenever THIS underlying definition is being
|
|
2349
|
+
// edited, even if paging moved row selection elsewhere.
|
|
2350
|
+
const value = editing
|
|
2351
|
+
? (this.keyInput.render(valueWidth)[0] ?? '')
|
|
2352
|
+
: formatBinding(resolution, field.definition.id);
|
|
2353
|
+
const content = label === '' ? value : `${label} ${value}`;
|
|
2354
|
+
return padAnsi(truncateToWidth(content, fieldWidth), fieldWidth);
|
|
2355
|
+
});
|
|
2356
|
+
const fieldsText = renderedFields.join(' ');
|
|
2357
|
+
const descriptionWidth = available - visibleWidth(fieldsText) - 1;
|
|
2358
|
+
const description = descriptionWidth >= 16
|
|
2359
|
+
? ` ${theme.fg('dim', truncateToWidth(row.description, descriptionWidth))}`
|
|
2360
|
+
: '';
|
|
2361
|
+
out.push(truncateToWidth(`${marker}${sourceMark} ${name} ${fieldsText}${description}`, width));
|
|
2362
|
+
}
|
|
2363
|
+
if (message) {
|
|
2364
|
+
const fieldPrefix = message.label === undefined ? '' : `${message.label}: `;
|
|
2365
|
+
out.push(truncateToWidth(theme.fg('warning', ` ${fieldPrefix}${message.message}`), width));
|
|
2366
|
+
}
|
|
2216
2367
|
emitted += cost;
|
|
2217
2368
|
}
|
|
2218
2369
|
if (this.keyConfirm !== null) {
|
|
@@ -16,16 +16,13 @@
|
|
|
16
16
|
// via the bare-dir → INDEX contract, AND the bundle's relative sidecar
|
|
17
17
|
// links (./references/x.md, ./docs/y.md) keep resolving on disk because
|
|
18
18
|
// the bundle's file layout is preserved. Sidecar .md files get minimal
|
|
19
|
-
// substrate frontmatter injected (lint-clean, name/
|
|
20
|
-
// copy verbatim.
|
|
21
|
-
// • Vendored bundle and sidecar docs sit at name/
|
|
22
|
-
//
|
|
23
|
-
// ONE generated top deps/INDEX.md sits at preview/
|
|
24
|
-
// routing catalog. The catalog is one read away, each vendored
|
|
25
|
-
//
|
|
26
|
-
// vendored doc's bare name, once per session — which also routes agents
|
|
27
|
-
// whose cwd (and boot tree) is elsewhere the moment they read a file in
|
|
28
|
-
// this project.
|
|
19
|
+
// substrate frontmatter injected (lint-clean, system name/file none); other
|
|
20
|
+
// files copy verbatim.
|
|
21
|
+
// • Vendored bundle and sidecar docs sit at system name/file none, so the boot
|
|
22
|
+
// catalog exposes their resolvable identities without loading their bodies.
|
|
23
|
+
// The ONE generated top deps/INDEX.md sits at system preview/file none and
|
|
24
|
+
// carries the routing catalog. The catalog is one read away, each vendored
|
|
25
|
+
// doc another.
|
|
29
26
|
import { copyFileSync, mkdirSync, readdirSync, rmSync } from 'node:fs';
|
|
30
27
|
import { CRTR_DIR_NAME } from '../../types.js';
|
|
31
28
|
import { basename, dirname, join, relative, resolve, sep } from 'node:path';
|
|
@@ -181,9 +178,8 @@ function condenseDescription(desc, max = 220) {
|
|
|
181
178
|
return `${cut.slice(0, lastSpace > 80 ? lastSpace : max).trimEnd()}…`;
|
|
182
179
|
}
|
|
183
180
|
/** Frontmatter fields from a source file that must never pass through into a
|
|
184
|
-
* vendored doc: identity, routing, and
|
|
185
|
-
*
|
|
186
|
-
* pierce the INDEX ceiling (on-read D6) and defeat the collapsed design. */
|
|
181
|
+
* vendored doc: this lane owns identity, routing, visibility, and gates, so a
|
|
182
|
+
* dependency bundle cannot alter how the generated project catalog surfaces. */
|
|
187
183
|
const CONTROLLED_FIELDS = [
|
|
188
184
|
'name',
|
|
189
185
|
'description',
|
|
@@ -374,7 +370,7 @@ export function runDepsSync(opts) {
|
|
|
374
370
|
? condenseDescription(entry.description)
|
|
375
371
|
: `Vendored agent doc shipped by ${entry.pkg}.`,
|
|
376
372
|
'system-prompt-visibility': 'name',
|
|
377
|
-
'file-read-visibility': '
|
|
373
|
+
'file-read-visibility': 'none',
|
|
378
374
|
'source-package': `${entry.pkg}@${entry.version}`,
|
|
379
375
|
...passthroughFm(entry.fm),
|
|
380
376
|
};
|
|
@@ -398,7 +394,7 @@ export function runDepsSync(opts) {
|
|
|
398
394
|
'when-and-why-to-read': `When ${parentName} links to this file, this knowledge should be read because it carries the linked supporting reference.`,
|
|
399
395
|
'short-form': firstHeading(body) ?? `Supporting reference vendored from ${entry.pkg}.`,
|
|
400
396
|
'system-prompt-visibility': 'name',
|
|
401
|
-
'file-read-visibility': '
|
|
397
|
+
'file-read-visibility': 'none',
|
|
402
398
|
'source-package': `${entry.pkg}@${entry.version}`,
|
|
403
399
|
...passthroughFm(sourceFm),
|
|
404
400
|
};
|
|
@@ -409,7 +405,7 @@ export function runDepsSync(opts) {
|
|
|
409
405
|
copyFileSync(entry.sourceAbs, target);
|
|
410
406
|
}
|
|
411
407
|
}
|
|
412
|
-
// The generated catalog INDEX: preview
|
|
408
|
+
// The generated catalog INDEX: boot preview + full per-package map.
|
|
413
409
|
const libNames = [...new Set(skills.map(([, skill]) => skill.pkg))];
|
|
414
410
|
const shownLibs = libNames.slice(0, 6).join(', ') + (libNames.length > 6 ? ', …' : '');
|
|
415
411
|
const byPackage = new Map();
|
|
@@ -435,7 +431,7 @@ export function runDepsSync(opts) {
|
|
|
435
431
|
? 'No dependency-shipped agent docs are currently vendored.'
|
|
436
432
|
: "Generated catalog of the agent docs shipped inside this project's npm dependencies.",
|
|
437
433
|
'system-prompt-visibility': 'preview',
|
|
438
|
-
'file-read-visibility': '
|
|
434
|
+
'file-read-visibility': 'none',
|
|
439
435
|
'generated-by': DEPS_GENERATED_MARKER,
|
|
440
436
|
};
|
|
441
437
|
const indexBody = emptyCatalog
|
|
@@ -490,7 +486,7 @@ export const sysSyncDepsLeaf = defineLeaf({
|
|
|
490
486
|
],
|
|
491
487
|
outputKind: 'object',
|
|
492
488
|
effects: [
|
|
493
|
-
"Wipes and regenerates <project>/.crouter/memory/deps/ from installed dependency skills, except bundles selected by exact {package,version,skill} entries in package.json crtr.sync.deps.exclude. Each included SKILL.md becomes a name/
|
|
489
|
+
"Wipes and regenerates <project>/.crouter/memory/deps/ from installed dependency skills, except bundles selected by exact {package,version,skill} entries in package.json crtr.sync.deps.exclude. Each included SKILL.md becomes a system-name/file-none deps/<bundle>/INDEX.md (relative sidecar links keep resolving), Markdown sidecar docs are vendored alongside at the same rungs, other sidecar assets are copied verbatim, and a system-preview/file-none deps/INDEX.md catalog plus a self-ignoring .gitignore are written. Excluded bundles and their sidecars do not enter the catalog.",
|
|
494
490
|
'With --dry-run: read-only; writes nothing.',
|
|
495
491
|
],
|
|
496
492
|
},
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// `crtr sys sync project-guidance` — one-way migration of a project's own
|
|
2
2
|
// CLAUDE.md/AGENTS.md/.claude/rules into that SAME directory's OWN
|
|
3
|
-
// `.crouter/memory
|
|
4
|
-
//
|
|
5
|
-
//
|
|
3
|
+
// `.crouter/memory/`. Each project guide becomes the root INDEX front door;
|
|
4
|
+
// rules retain their explicit path routes, or use the workspace-open `.` route
|
|
5
|
+
// when they are pathless.
|
|
6
6
|
import { existsSync, readdirSync, realpathSync, statSync } from 'node:fs';
|
|
7
7
|
import { basename, dirname, join, relative, resolve, sep } from 'node:path';
|
|
8
8
|
import { defineLeaf } from '../../core/command.js';
|
|
@@ -95,7 +95,7 @@ function synthesizeShortForm(body) {
|
|
|
95
95
|
return 'Migrated project guidance.';
|
|
96
96
|
}
|
|
97
97
|
/** `.claude/rules` frontmatter `paths:` normalizes to a glob list — a single
|
|
98
|
-
* string, an array, or absent
|
|
98
|
+
* string, an array, or absent for a workspace-wide rule. */
|
|
99
99
|
function normalizeRulePaths(v) {
|
|
100
100
|
if (typeof v === 'string')
|
|
101
101
|
return v.trim() === '' ? [] : [v.trim()];
|
|
@@ -201,12 +201,10 @@ function projectDocLabel(candidate) {
|
|
|
201
201
|
.replace(/^\.$/, '');
|
|
202
202
|
return relDir === '' ? rootLabel : normalizedName(`${rootLabel}/${relDir}`);
|
|
203
203
|
}
|
|
204
|
-
/** AGENTS.md/CLAUDE.md → `<sourceDir>/.crouter/memory/
|
|
205
|
-
*
|
|
206
|
-
*
|
|
207
|
-
*
|
|
208
|
-
* `AGENTS.md`, but the substrate identity is the explicit project label so
|
|
209
|
-
* sibling roots do not collapse on a shared `AGENTS` leaf. */
|
|
204
|
+
/** AGENTS.md/CLAUDE.md → `<sourceDir>/.crouter/memory/INDEX.md`, the
|
|
205
|
+
* workspace front door. Its explicit project identity keeps sibling roots
|
|
206
|
+
* distinct; system none/file content plus `applies-to: "."` puts the wholly
|
|
207
|
+
* important guide in first-message context when cwd/profile mounts the store. */
|
|
210
208
|
function prepareAgentsCandidate(candidate) {
|
|
211
209
|
const body = parseFrontmatterGeneric(readText(candidate.path)).body;
|
|
212
210
|
const docLabel = projectDocLabel(candidate);
|
|
@@ -215,21 +213,21 @@ function prepareAgentsCandidate(candidate) {
|
|
|
215
213
|
name: docLabel,
|
|
216
214
|
'when-and-why-to-read': `When working in ${docLabel}, this knowledge should be read because it is the project's operating guide.`,
|
|
217
215
|
'short-form': synthesizeShortForm(body),
|
|
218
|
-
'system-prompt-visibility': '
|
|
216
|
+
'system-prompt-visibility': 'none',
|
|
219
217
|
'file-read-visibility': 'content',
|
|
218
|
+
'applies-to': ['.'],
|
|
220
219
|
};
|
|
221
220
|
return {
|
|
222
221
|
candidate,
|
|
223
222
|
name: docLabel,
|
|
224
|
-
target: memoryFilePath(join(candidate.sourceDir, CRTR_DIR_NAME, 'memory'), '
|
|
223
|
+
target: memoryFilePath(join(candidate.sourceDir, CRTR_DIR_NAME, 'memory'), 'INDEX'),
|
|
225
224
|
frontmatter,
|
|
226
225
|
body,
|
|
227
226
|
};
|
|
228
227
|
}
|
|
229
228
|
/** `.claude/rules/<name>.md` → `<sourceDir>/.crouter/memory/<name>.md`. A
|
|
230
|
-
* `paths:` rule
|
|
231
|
-
*
|
|
232
|
-
* own project scope) at the same none/content rungs. */
|
|
229
|
+
* `paths:` rule keeps its `applies-to` file globs at none/content; a pathless
|
|
230
|
+
* rule receives `applies-to: "."` and surfaces when the workspace opens. */
|
|
233
231
|
function prepareRuleCandidate(candidate) {
|
|
234
232
|
const raw = readText(candidate.path);
|
|
235
233
|
const parsed = parseFrontmatterGeneric(raw);
|
|
@@ -247,8 +245,7 @@ function prepareRuleCandidate(candidate) {
|
|
|
247
245
|
'file-read-visibility': 'content',
|
|
248
246
|
};
|
|
249
247
|
const globs = normalizeRulePaths(fm['paths']);
|
|
250
|
-
|
|
251
|
-
frontmatter['applies-to'] = globs;
|
|
248
|
+
frontmatter['applies-to'] = globs.length > 0 ? globs : ['.'];
|
|
252
249
|
return {
|
|
253
250
|
candidate,
|
|
254
251
|
name,
|
|
@@ -299,7 +296,7 @@ function renderSummary(results) {
|
|
|
299
296
|
export const sysSyncProjectGuidanceLeaf = defineLeaf({
|
|
300
297
|
name: 'project-guidance',
|
|
301
298
|
description: "migrate CLAUDE.md/AGENTS.md/.claude/rules into that directory's own crouter memory docs",
|
|
302
|
-
whenToUse: "migrating per-project CLAUDE.md/AGENTS.md/.claude/rules into the crouter memory-doc model: one-way copy into that same directory's OWN .crouter/memory
|
|
299
|
+
whenToUse: "migrating per-project CLAUDE.md/AGENTS.md/.claude/rules into the crouter memory-doc model: one-way copy into that same directory's OWN .crouter/memory/. The project guide becomes its root INDEX front door; path-scoped rules keep their globs and pathless rules open with the workspace. Never exports memory docs back to these source files.",
|
|
303
300
|
help: {
|
|
304
301
|
name: 'sys sync project-guidance',
|
|
305
302
|
summary: "one-way migration: CLAUDE.md/AGENTS.md/.claude/rules → that dir's own crouter memory docs",
|
|
@@ -316,7 +313,7 @@ export const sysSyncProjectGuidanceLeaf = defineLeaf({
|
|
|
316
313
|
],
|
|
317
314
|
outputKind: 'object',
|
|
318
315
|
effects: [
|
|
319
|
-
"Copies each discovered CLAUDE.md/AGENTS.md into <dir>/.crouter/memory/
|
|
316
|
+
"Copies each discovered CLAUDE.md/AGENTS.md into <dir>/.crouter/memory/INDEX.md with an explicit project name at system-prompt-visibility none, file-read-visibility content, and applies-to `.`. Copies each .claude/rules/*.md into <dir>/.crouter/memory/<name>.md at none/content, using the source paths as applies-to globs or `.` when no paths are declared. Originals are left untouched.",
|
|
320
317
|
'Skips existing memory docs unless --overwrite is present.',
|
|
321
318
|
'With --dry-run: read-only; writes nothing.',
|
|
322
319
|
],
|
|
@@ -7,12 +7,17 @@ import { fileURLToPath } from 'node:url';
|
|
|
7
7
|
import { DatabaseSync } from 'node:sqlite';
|
|
8
8
|
import { openDb, closeDb, migrate, MIGRATIONS } from '../canvas/db.js';
|
|
9
9
|
import { canvasDbPath, ensureHome } from '../canvas/paths.js';
|
|
10
|
-
import {
|
|
10
|
+
import { customExtensionPaths } from '../canvas/extensions.js';
|
|
11
11
|
import { updateNode } from '../canvas/canvas.js';
|
|
12
12
|
import { spawn, spawnSync } from 'node:child_process';
|
|
13
13
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
14
14
|
const RUNTIME_COLUMNS = ['intent', 'pi_pid', 'window', 'tmux_session'];
|
|
15
15
|
const RACE_FIXTURE = join(HERE, 'fixtures', 'metadata-migration-race.ts');
|
|
16
|
+
const PRE_TOOL_GUIDE_CANVAS_EXTENSION_NAMES = [
|
|
17
|
+
'canvas-inbox-watcher', 'canvas-stophook', 'canvas-recap',
|
|
18
|
+
'canvas-goal-capture', 'canvas-passive-context', 'canvas-context-intro',
|
|
19
|
+
'canvas-doc-substrate', 'canvas-structured-output', 'canvas-bash-valve',
|
|
20
|
+
];
|
|
16
21
|
let home;
|
|
17
22
|
async function waitForPath(path) {
|
|
18
23
|
for (;;) {
|
|
@@ -76,7 +81,7 @@ test('a v14 canvas normalizes historical canvas paths while retaining custom lau
|
|
|
76
81
|
node_id: 'saved', name: 'saved', created: '2026-01-01T00:00:00.000Z', cwd: '/tmp',
|
|
77
82
|
kind: 'developer', mode: 'base', lifecycle: 'terminal', status: 'idle',
|
|
78
83
|
launch: { extensions: [
|
|
79
|
-
...
|
|
84
|
+
...PRE_TOOL_GUIDE_CANVAS_EXTENSION_NAMES.map((name) => `/old-generation/pi-extensions/${name}.js`),
|
|
80
85
|
'/plugins/canvas-inbox-watcher.js',
|
|
81
86
|
] },
|
|
82
87
|
}));
|
|
@@ -102,7 +107,7 @@ test('a v14 migration is protected against concurrent writeback by normal update
|
|
|
102
107
|
lifecycle: 'terminal',
|
|
103
108
|
status: 'idle',
|
|
104
109
|
launch: {
|
|
105
|
-
extensions: [...
|
|
110
|
+
extensions: [...PRE_TOOL_GUIDE_CANVAS_EXTENSION_NAMES.map((name) => `/old-generation/pi-extensions/${name}.js`), '/plugins/canvas-inbox-watcher.js'],
|
|
106
111
|
},
|
|
107
112
|
}));
|
|
108
113
|
ensureHome();
|
|
@@ -1,12 +1,8 @@
|
|
|
1
|
-
//
|
|
2
|
-
// the user scope root) as a project ancestor. Canvas runtime
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// cwd resolved its private project store there. That leaks one node's private
|
|
7
|
-
// memory into every unrelated node that reads a canvas file. This test pins the
|
|
8
|
-
// fence: reading a canvas artifact must not surface a store nested in the
|
|
9
|
-
// crouter home.
|
|
1
|
+
// Explicit file-context discovery never treats the crouter home
|
|
2
|
+
// (`~/.crouter`, the user scope root) as a project ancestor. Canvas runtime
|
|
3
|
+
// artifacts live below that root, while a nested `~/.crouter/.crouter/memory/`
|
|
4
|
+
// store can belong to a differently scoped node. The user-root fence keeps its
|
|
5
|
+
// explicitly routed docs out of unrelated canvas-file reads.
|
|
10
6
|
//
|
|
11
7
|
// Run: node --import tsx/esm --test src/core/__tests__/on-read-crouter-home-fence.test.ts
|
|
12
8
|
import { test, before, beforeEach, after } from 'node:test';
|
|
@@ -54,7 +50,8 @@ test('a store nested in the crouter home does not leak into a canvas-artifact re
|
|
|
54
50
|
mkdirSync(leakedStore, { recursive: true });
|
|
55
51
|
writeFileSync(join(leakedStore, 'private-thing.md'), '---\nkind: knowledge\n' +
|
|
56
52
|
"when-and-why-to-read: When X, read this because Y.\n" +
|
|
57
|
-
'file-read-visibility: content\n
|
|
53
|
+
'file-read-visibility: content\n' +
|
|
54
|
+
'applies-to: "**/*.md"\n---\n' +
|
|
58
55
|
'PRIVATE ASSISTANT MEMORY MUST NOT LEAK\n');
|
|
59
56
|
// A canvas runtime artifact under the crouter home — the shape of every node
|
|
60
57
|
// report/context file the harness auto-reads.
|