@north-light/crouter 0.3.208 → 0.3.209
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/api/client.d.ts +3 -1
- package/dist/api/client.js +4 -0
- package/dist/api/dto/broker-ops.d.ts +2 -12
- package/dist/api/dto/nodes.d.ts +16 -0
- package/dist/api/routes.d.ts +1 -0
- package/dist/api/routes.js +1 -0
- package/dist/builtin-memory/00-runtime-base.md +3 -2
- package/dist/builtin-memory/01-spine/00-has-manager.md +3 -2
- package/dist/builtin-memory/01-spine/01-no-manager.md +3 -2
- package/dist/builtin-memory/02-lifecycle/00-terminal.md +3 -2
- package/dist/builtin-memory/02-lifecycle/01-resident.md +3 -2
- package/dist/builtin-memory/04-base-worker.md +3 -2
- package/dist/builtin-memory/04-orchestration-kernel.md +3 -2
- package/dist/builtin-memory/05-kinds/advisor/00-base.md +3 -2
- package/dist/builtin-memory/05-kinds/advisor/01-orchestrator.md +3 -2
- package/dist/builtin-memory/05-kinds/design/00-base.md +3 -2
- package/dist/builtin-memory/05-kinds/design/01-orchestrator.md +3 -2
- package/dist/builtin-memory/05-kinds/developer/00-base.md +3 -2
- package/dist/builtin-memory/05-kinds/developer/01-orchestrator.md +3 -2
- package/dist/builtin-memory/05-kinds/explore/00-base.md +3 -2
- package/dist/builtin-memory/05-kinds/explore/01-orchestrator.md +3 -2
- package/dist/builtin-memory/05-kinds/general/00-base.md +3 -2
- package/dist/builtin-memory/05-kinds/general/01-orchestrator.md +3 -2
- package/dist/builtin-memory/05-kinds/plan/00-base.md +3 -2
- package/dist/builtin-memory/05-kinds/plan/01-orchestrator.md +3 -2
- package/dist/builtin-memory/05-kinds/plan/reviewers/00-base.md +3 -2
- package/dist/builtin-memory/05-kinds/plan/reviewers/architecture-fit.md +3 -2
- package/dist/builtin-memory/05-kinds/plan/reviewers/code-smells.md +3 -2
- package/dist/builtin-memory/05-kinds/plan/reviewers/pattern-consistency.md +3 -2
- package/dist/builtin-memory/05-kinds/plan/reviewers/requirements-coverage.md +3 -2
- package/dist/builtin-memory/05-kinds/plan/reviewers/security.md +3 -2
- package/dist/builtin-memory/05-kinds/review/00-base.md +3 -2
- package/dist/builtin-memory/05-kinds/review/01-orchestrator.md +3 -2
- package/dist/builtin-memory/05-kinds/review/companion/00-base.md +3 -2
- package/dist/builtin-memory/05-kinds/spec/00-base.md +3 -2
- package/dist/builtin-memory/05-kinds/spec/01-orchestrator.md +3 -2
- package/dist/builtin-memory/05-kinds/spec/requirements.md +3 -2
- package/dist/builtin-memory/advisor/council.md +0 -2
- package/dist/builtin-memory/design.md +3 -2
- package/dist/builtin-memory/development.md +3 -2
- package/dist/builtin-memory/insights/capture.md +1 -3
- package/dist/builtin-memory/insights/init.md +1 -3
- package/dist/builtin-memory/insights/listen.md +3 -2
- package/dist/builtin-memory/internal/INDEX.md +5 -4
- package/dist/builtin-memory/internal/agent-shaping.md +5 -4
- package/dist/builtin-memory/internal/examples/INDEX.md +3 -2
- package/dist/builtin-memory/internal/examples/imessage-assistant.md +3 -2
- package/dist/builtin-memory/internal/marketplaces.md +3 -2
- package/dist/builtin-memory/internal/memory-loading.md +31 -20
- package/dist/builtin-memory/internal/nodes-and-canvas.md +3 -2
- package/dist/builtin-memory/internal/plugins.md +7 -6
- package/dist/builtin-memory/internal/storage-tiers.md +3 -2
- package/dist/builtin-memory/plan/roadmap.md +3 -2
- package/dist/builtin-memory/spec/guide.md +0 -2
- package/dist/builtin-memory/spec/requirements.md +0 -2
- package/dist/builtin-memory/spec/roadmap.md +3 -2
- package/dist/builtin-memory/testing.md +1 -3
- package/dist/builtin-memory/wedged-child-on-runaway-bash.md +3 -2
- package/dist/builtin-pi-packages/pi-crtr-extensions/README.md +1 -1
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/frontmatter-rules/index.ts +3 -3
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/memory-slash-commands.ts +1 -4
- package/dist/clients/attach/viewer.js +366 -366
- package/dist/commands/memory/delete.js +3 -18
- package/dist/commands/memory/edit.js +23 -30
- package/dist/commands/memory/history.js +1 -1
- package/dist/commands/memory/lint.d.ts +7 -8
- package/dist/commands/memory/lint.js +178 -132
- package/dist/commands/memory/list.d.ts +0 -1
- package/dist/commands/memory/list.js +2 -12
- package/dist/commands/memory/move.d.ts +1 -0
- package/dist/commands/memory/move.js +195 -0
- package/dist/commands/memory/read.js +135 -141
- package/dist/commands/memory/shared.d.ts +18 -17
- package/dist/commands/memory/shared.js +93 -39
- package/dist/commands/memory/write.js +23 -33
- package/dist/commands/memory.js +5 -4
- package/dist/commands/pkg/browse/catalog.js +2 -4
- package/dist/commands/pkg/browse/doc-view.js +17 -11
- package/dist/commands/pkg/browse/model.d.ts +7 -9
- package/dist/commands/sys/migrate.d.ts +1 -0
- package/dist/commands/sys/migrate.js +106 -0
- package/dist/commands/sys/sync-deps.js +5 -10
- package/dist/commands/sys/sync-project-guidance.js +36 -16
- package/dist/commands/sys/sync-skills.js +8 -4
- package/dist/commands/sys.js +3 -2
- package/dist/core/__tests__/inline-memory-refs.test.js +8 -5
- package/dist/core/__tests__/memory-resolver-precedence.test.js +5 -4
- package/dist/core/__tests__/nested-store-discovery.test.js +1 -3
- package/dist/core/__tests__/on-read-crouter-home-fence.test.js +3 -3
- package/dist/core/__tests__/on-read-dedup-resume.test.js +12 -12
- package/dist/core/__tests__/on-read-nested-store.test.js +23 -20
- package/dist/core/canvas/db.d.ts +3 -1
- package/dist/core/canvas/db.js +12 -2
- package/dist/core/memory/history.d.ts +4 -1
- package/dist/core/memory/history.js +1 -0
- package/dist/core/memory/inline-ref-inventory.d.ts +5 -4
- package/dist/core/memory/inline-ref-inventory.js +23 -19
- package/dist/core/memory-resolver.d.ts +4 -4
- package/dist/core/memory-resolver.js +16 -43
- package/dist/core/runtime/bearings.d.ts +4 -3
- package/dist/core/runtime/bearings.js +4 -4
- package/dist/core/runtime/broker-extension-render.d.ts +3 -2
- package/dist/core/runtime/broker-extension-render.js +3 -3
- package/dist/core/runtime/memory.js +2 -3
- package/dist/core/substrate/index.d.ts +7 -4
- package/dist/core/substrate/index.js +6 -4
- package/dist/core/substrate/injected-store.d.ts +24 -12
- package/dist/core/substrate/injected-store.js +80 -33
- package/dist/core/substrate/listings.d.ts +21 -0
- package/dist/core/substrate/listings.js +88 -0
- package/dist/core/substrate/on-read-node.d.ts +5 -5
- package/dist/core/substrate/on-read-node.js +4 -5
- package/dist/core/substrate/on-read.d.ts +25 -4
- package/dist/core/substrate/on-read.js +81 -102
- package/dist/core/substrate/render-node.d.ts +5 -2
- package/dist/core/substrate/render-node.js +5 -3
- package/dist/core/substrate/render.d.ts +9 -8
- package/dist/core/substrate/render.js +104 -96
- package/dist/core/substrate/schema.d.ts +34 -18
- package/dist/core/substrate/schema.js +75 -32
- package/dist/core/substrate/surface-match.d.ts +32 -0
- package/dist/core/substrate/surface-match.js +179 -0
- package/dist/daemon/api/handlers/nodes.js +9 -0
- package/dist/migrations/001-surfaces-frontmatter.d.ts +2 -0
- package/dist/migrations/001-surfaces-frontmatter.js +276 -0
- package/dist/migrations/convergent.d.ts +31 -0
- package/dist/migrations/convergent.js +71 -0
- package/dist/migrations/registry.d.ts +2 -0
- package/dist/migrations/registry.js +19 -0
- package/dist/migrations/types.d.ts +40 -0
- package/dist/migrations/types.js +11 -0
- package/dist/pi-extensions/canvas-context-intro.d.ts +2 -1
- package/dist/pi-extensions/canvas-doc-substrate.d.ts +2 -1
- package/dist/pi-extensions/canvas-doc-substrate.js +57 -34
- package/package.json +1 -1
- package/runtime.lock.json +2 -2
- package/dist/core/substrate/ceiling.d.ts +0 -17
- package/dist/core/substrate/ceiling.js +0 -67
|
@@ -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/`. Each project guide becomes the
|
|
4
|
-
// rules
|
|
5
|
-
//
|
|
3
|
+
// `.crouter/memory/`. Each project guide becomes the workspace front door
|
|
4
|
+
// (workspace-open + ./** read entries); rules keep their explicit path routes
|
|
5
|
+
// as read entries, or take the front-door pair when they are pathless.
|
|
6
6
|
import { existsSync, readdirSync, statSync } from 'node:fs';
|
|
7
7
|
import { basename, dirname, join, relative, resolve, sep } from 'node:path';
|
|
8
8
|
import { defineLeaf } from '../../core/command.js';
|
|
@@ -218,9 +218,30 @@ function projectDocLabel(candidate) {
|
|
|
218
218
|
.replace(/^\.$/, '');
|
|
219
219
|
return relDir === '' ? rootLabel : normalizedName(`${rootLabel}/${relDir}`);
|
|
220
220
|
}
|
|
221
|
+
/** The surfaces pair every workspace front door carries: whole-doc delivery
|
|
222
|
+
* when cwd/profile mounts the store, plus on any file read beneath the
|
|
223
|
+
* store's owning dir. */
|
|
224
|
+
function frontDoorSurfaces() {
|
|
225
|
+
return [
|
|
226
|
+
{ on: 'workspace-open', at: 'content' },
|
|
227
|
+
{ on: 'read', match: './**', at: 'content' },
|
|
228
|
+
];
|
|
229
|
+
}
|
|
230
|
+
/** `./`-anchor a rule glob that could only match repo-relatively: any glob
|
|
231
|
+
* containing `/` that starts with neither `/`, `**`, nor `./`. Mirrors the
|
|
232
|
+
* surfaces migration's anchoring rule — this leaf MINTS new read globs, so it
|
|
233
|
+
* must anchor them the same way or the routes it writes silently never fire. */
|
|
234
|
+
function anchorReadGlob(g) {
|
|
235
|
+
const t = g.trim();
|
|
236
|
+
if (!t.includes('/'))
|
|
237
|
+
return t;
|
|
238
|
+
if (t.startsWith('/') || t.startsWith('**') || t.startsWith('./'))
|
|
239
|
+
return t;
|
|
240
|
+
return `./${t}`;
|
|
241
|
+
}
|
|
221
242
|
/** AGENTS.md/CLAUDE.md → `<sourceDir>/.crouter/memory/INDEX.md`, the
|
|
222
243
|
* workspace front door. Its explicit project identity keeps sibling roots
|
|
223
|
-
* distinct;
|
|
244
|
+
* distinct; the workspace-open + ./** read entry pair puts the wholly
|
|
224
245
|
* important guide in first-message context when cwd/profile mounts the store,
|
|
225
246
|
* and surfaces it on any file read beneath the store's owning dir. */
|
|
226
247
|
function prepareAgentsCandidate(candidate) {
|
|
@@ -231,9 +252,7 @@ function prepareAgentsCandidate(candidate) {
|
|
|
231
252
|
name: docLabel,
|
|
232
253
|
'when-and-why-to-read': `When working in ${docLabel}, this knowledge should be read because it is the project's operating guide.`,
|
|
233
254
|
'short-form': synthesizeShortForm(body),
|
|
234
|
-
|
|
235
|
-
'file-read-visibility': 'content',
|
|
236
|
-
'applies-to': ['.'],
|
|
255
|
+
surfaces: frontDoorSurfaces(),
|
|
237
256
|
};
|
|
238
257
|
return {
|
|
239
258
|
candidate,
|
|
@@ -244,9 +263,10 @@ function prepareAgentsCandidate(candidate) {
|
|
|
244
263
|
};
|
|
245
264
|
}
|
|
246
265
|
/** `.claude/rules/<name>.md` → `<sourceDir>/.crouter/memory/<name>.md`. A
|
|
247
|
-
* `paths:` rule keeps its
|
|
248
|
-
*
|
|
249
|
-
* file beneath the store's owning dir is read.
|
|
266
|
+
* `paths:` rule keeps its file globs as a `content` read entry (`./`-anchored
|
|
267
|
+
* when repo-relative); a pathless rule takes the front-door pair and surfaces
|
|
268
|
+
* when the workspace opens or a file beneath the store's owning dir is read.
|
|
269
|
+
* Like the INDEX docs, each rule
|
|
250
270
|
* carries an explicit namespaced substrate name (`<root>/<subdir>/<leaf>`):
|
|
251
271
|
* without it the resolver falls back to the store-relative leaf, and two
|
|
252
272
|
* nested stores sharing a rule filename (e.g. apps/core and apps/gateway both
|
|
@@ -266,11 +286,11 @@ function prepareRuleCandidate(candidate) {
|
|
|
266
286
|
? routeFromDescription(description, 'knowledge')
|
|
267
287
|
: `When working under ${dirLabel}, this knowledge should be read because it is migrated project rule guidance.`,
|
|
268
288
|
'short-form': description !== '' ? description.replace(/\s+/g, ' ').trim() : synthesizeShortForm(parsed.body),
|
|
269
|
-
'system-prompt-visibility': 'none',
|
|
270
|
-
'file-read-visibility': 'content',
|
|
271
289
|
};
|
|
272
|
-
const globs = normalizeRulePaths(fm['paths']);
|
|
273
|
-
frontmatter['
|
|
290
|
+
const globs = normalizeRulePaths(fm['paths']).map(anchorReadGlob);
|
|
291
|
+
frontmatter['surfaces'] = globs.length > 0
|
|
292
|
+
? [{ on: 'read', match: globs.length === 1 ? globs[0] : globs, at: 'content' }]
|
|
293
|
+
: frontDoorSurfaces();
|
|
274
294
|
return {
|
|
275
295
|
candidate,
|
|
276
296
|
name,
|
|
@@ -321,7 +341,7 @@ function renderSummary(results) {
|
|
|
321
341
|
export const sysSyncProjectGuidanceLeaf = defineLeaf({
|
|
322
342
|
name: 'project-guidance',
|
|
323
343
|
description: "migrate CLAUDE.md/AGENTS.md/.claude/rules into that directory's own crouter memory docs",
|
|
324
|
-
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
|
|
344
|
+
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 workspace front door; path-scoped rules keep their globs as read entries and pathless rules open with the workspace. Never exports memory docs back to these source files.",
|
|
325
345
|
help: {
|
|
326
346
|
name: 'sys sync project-guidance',
|
|
327
347
|
summary: "one-way migration: CLAUDE.md/AGENTS.md/.claude/rules → that dir's own crouter memory docs",
|
|
@@ -338,7 +358,7 @@ export const sysSyncProjectGuidanceLeaf = defineLeaf({
|
|
|
338
358
|
],
|
|
339
359
|
outputKind: 'object',
|
|
340
360
|
effects: [
|
|
341
|
-
"Copies each discovered CLAUDE.md/AGENTS.md into <dir>/.crouter/memory/INDEX.md with an explicit project name
|
|
361
|
+
"Copies each discovered CLAUDE.md/AGENTS.md into <dir>/.crouter/memory/INDEX.md with an explicit project name and the workspace front-door surfaces pair ({on: workspace-open, at: content} + {on: read, match: './**', at: content}). Copies each .claude/rules/*.md into <dir>/.crouter/memory/<name>.md with a content read entry over the source paths (‘./’-anchored when repo-relative), or the same front-door pair when no paths are declared. Originals are left untouched.",
|
|
342
362
|
'Skips existing memory docs unless --overwrite is present.',
|
|
343
363
|
'With --dry-run: read-only; writes nothing.',
|
|
344
364
|
],
|
|
@@ -167,6 +167,12 @@ function convertPrepared(prepared, opts) {
|
|
|
167
167
|
delete frontmatter.description;
|
|
168
168
|
delete frontmatter.type;
|
|
169
169
|
delete frontmatter.keywords;
|
|
170
|
+
// Retired visibility-axis fields a stale source may still carry — they must
|
|
171
|
+
// never pass through into a substrate doc.
|
|
172
|
+
delete frontmatter['system-prompt-visibility'];
|
|
173
|
+
delete frontmatter['file-read-visibility'];
|
|
174
|
+
delete frontmatter['applies-to'];
|
|
175
|
+
delete frontmatter['read-when'];
|
|
170
176
|
frontmatter.kind = prepared.kind;
|
|
171
177
|
if (typeof frontmatter['when-and-why-to-read'] !== 'string' || frontmatter['when-and-why-to-read'].trim() === '') {
|
|
172
178
|
frontmatter['when-and-why-to-read'] = routeFromDescription(description, prepared.kind);
|
|
@@ -174,10 +180,8 @@ function convertPrepared(prepared, opts) {
|
|
|
174
180
|
if (typeof frontmatter['short-form'] !== 'string' || frontmatter['short-form'].trim() === '') {
|
|
175
181
|
frontmatter['short-form'] = description.replace(/\s+/g, ' ').trim();
|
|
176
182
|
}
|
|
177
|
-
if (
|
|
178
|
-
frontmatter['
|
|
179
|
-
if (typeof frontmatter['file-read-visibility'] !== 'string')
|
|
180
|
-
frontmatter['file-read-visibility'] = 'none';
|
|
183
|
+
if (!Array.isArray(frontmatter['surfaces']))
|
|
184
|
+
frontmatter['surfaces'] = [{ on: 'boot', at: 'preview' }];
|
|
181
185
|
if (!opts.dryRun)
|
|
182
186
|
writeText(prepared.target, serializeMemoryDoc(frontmatter, prepared.parsed.body));
|
|
183
187
|
return {
|
package/dist/commands/sys.js
CHANGED
|
@@ -11,19 +11,20 @@ import { supportBranch } from './sys/support.js';
|
|
|
11
11
|
import { sysSyspromptLeaf } from './sys/sysprompt.js';
|
|
12
12
|
import { promptReviewLeaf } from './sys/prompt-review.js';
|
|
13
13
|
import { sysSyncBranch } from './sys/sync.js';
|
|
14
|
+
import { sysMigrateLeaf } from './sys/migrate.js';
|
|
14
15
|
import { sysUpdateLeaf, sysVersionLeaf } from './sys/update.js';
|
|
15
16
|
export function registerSys() {
|
|
16
17
|
return defineBranch({
|
|
17
18
|
name: 'sys',
|
|
18
19
|
rootEntry: {
|
|
19
20
|
concept: 'crtr configuration, diagnostics, and self-management',
|
|
20
|
-
desc: 'settings, config, setup, doctor, sysprompt, prompt-review, sync, update, version, feedback, logs, support, daemon',
|
|
21
|
+
desc: 'settings, config, setup, doctor, sysprompt, prompt-review, sync, migrate, update, version, feedback, logs, support, daemon',
|
|
21
22
|
useWhen: 'managing the crtr installation, inspecting diagnostics, or operating the crtrd supervisor process',
|
|
22
23
|
},
|
|
23
24
|
help: {
|
|
24
25
|
name: 'sys',
|
|
25
26
|
summary: 'crtr system configuration, settings, diagnostics, and self-management',
|
|
26
27
|
},
|
|
27
|
-
children: [sysSettingsLeaf, configBranch, sysSetupLeaf, sysDoctorLeaf, sysSyspromptLeaf, promptReviewLeaf, sysFeedbackLeaf, sysLogsLeaf, supportBranch, sysUpdateLeaf, sysVersionLeaf, sysSyncBranch, daemonBranch],
|
|
28
|
+
children: [sysSettingsLeaf, configBranch, sysSetupLeaf, sysDoctorLeaf, sysSyspromptLeaf, promptReviewLeaf, sysFeedbackLeaf, sysLogsLeaf, supportBranch, sysUpdateLeaf, sysVersionLeaf, sysMigrateLeaf, sysSyncBranch, daemonBranch],
|
|
28
29
|
});
|
|
29
30
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// Load-bearing corpus regression for `buildRefInventory` — the authoritative
|
|
4
4
|
// inventory the broker and terminal attach viewer both resolve inline memory
|
|
5
5
|
// references against. Covers scope precedence dedup, referenceability
|
|
6
|
-
// (both kinds, gated and slash docs),
|
|
6
|
+
// (both kinds, gated and slash docs), directory browse names, malformed-doc omission,
|
|
7
7
|
// metadata-only rows, and stable ordering, all over real on-disk fixtures.
|
|
8
8
|
import { test, describe, before, beforeEach, after } from 'node:test';
|
|
9
9
|
import assert from 'node:assert/strict';
|
|
@@ -17,7 +17,7 @@ import { buildRefInventory } from '../memory/inline-ref-inventory.js';
|
|
|
17
17
|
// ---------------------------------------------------------------------------
|
|
18
18
|
// Wave C4 — buildRefInventory: the authoritative corpus builder. Fixture-
|
|
19
19
|
// backed (node/project/user scope docs on disk), since precedence dedup,
|
|
20
|
-
// referenceability-ignores-gate/slash, and
|
|
20
|
+
// referenceability-ignores-gate/slash, and directory browse names are all corpus-shape
|
|
21
21
|
// facts that a pure in-memory test can't exercise. Fixture names are prefixed
|
|
22
22
|
// to avoid any collision with the real shipped builtin/user corpus that
|
|
23
23
|
// listAllMemoryDocs() also picks up in this environment.
|
|
@@ -128,12 +128,15 @@ describe('buildRefInventory (fixture-backed corpus)', () => {
|
|
|
128
128
|
assert.equal(refs.find((r) => r.name === 'crtr-inline-ref-fixture-knowledge')?.kind, 'knowledge');
|
|
129
129
|
assert.equal(refs.find((r) => r.name === 'crtr-inline-ref-fixture-preference')?.kind, 'preference');
|
|
130
130
|
});
|
|
131
|
-
test('an INDEX doc
|
|
131
|
+
test('an INDEX doc keeps its canonical name; the bare dir name is a browse link, not a ref row', () => {
|
|
132
132
|
writeDoc(join(projectMemoryDir(), 'crtr-inline-ref-fixture-taste'), 'INDEX.md', "kind: knowledge\nshort-form: 'taste index'");
|
|
133
133
|
const { refs, names } = buildRefInventory();
|
|
134
|
+
assert.ok(names.has('crtr-inline-ref-fixture-taste/INDEX'));
|
|
135
|
+
// The directory name resolves as a listing link — in `names` for linkify,
|
|
136
|
+
// never a RefMeta row (no doc answers for a directory).
|
|
134
137
|
assert.ok(names.has('crtr-inline-ref-fixture-taste'));
|
|
135
|
-
assert.ok(!
|
|
136
|
-
const winner = refs.find((r) => r.name === 'crtr-inline-ref-fixture-taste');
|
|
138
|
+
assert.ok(!refs.some((r) => r.name === 'crtr-inline-ref-fixture-taste'));
|
|
139
|
+
const winner = refs.find((r) => r.name === 'crtr-inline-ref-fixture-taste/INDEX');
|
|
137
140
|
assert.equal(winner?.shortForm, 'taste index');
|
|
138
141
|
});
|
|
139
142
|
test('a root INDEX doc is omitted, never exposed as an empty ref name', () => {
|
|
@@ -128,13 +128,14 @@ test('a plain path-named doc with no explicit name resolves to the nearest copy'
|
|
|
128
128
|
process.chdir(prevCwd);
|
|
129
129
|
}
|
|
130
130
|
});
|
|
131
|
-
test('a bare-directory query
|
|
131
|
+
test('a bare-directory query does not resolve to a doc — directories answer with listings, not INDEX.md', () => {
|
|
132
132
|
process.chdir(child);
|
|
133
133
|
resetScopeCache();
|
|
134
134
|
try {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
135
|
+
assert.throws(() => resolveMemoryDoc('taste'), (err) => {
|
|
136
|
+
assert.equal(err.code, 'not_found');
|
|
137
|
+
return true;
|
|
138
|
+
});
|
|
138
139
|
}
|
|
139
140
|
finally {
|
|
140
141
|
process.chdir(prevCwd);
|
|
@@ -76,9 +76,7 @@ test('resolveMemoryDocForTarget resolves a nested doc only with includeDescendan
|
|
|
76
76
|
mkdirSync(nestedMemory, { recursive: true });
|
|
77
77
|
writeFileSync(join(nestedMemory, 'guide.md'), '---\nkind: knowledge\n' +
|
|
78
78
|
'name: nested-guide\n' +
|
|
79
|
-
'when-and-why-to-read: When working in sub, this knowledge should be read because it carries the subsystem constraints.\n' +
|
|
80
|
-
'system-prompt-visibility: none\n' +
|
|
81
|
-
'file-read-visibility: none\n---\n' +
|
|
79
|
+
'when-and-why-to-read: When working in sub, this knowledge should be read because it carries the subsystem constraints.\n---\n' +
|
|
82
80
|
'NESTED GUIDE BODY\n');
|
|
83
81
|
const target = { cwd: proj, profileId: null, nodeId: null };
|
|
84
82
|
assert.throws(() => resolveMemoryDocForTarget('nested-guide', target, {}), (e) => e instanceof CrtrError && e.code === 'not_found', 'without the opt-in the flat ancestor stack does not see the nested store');
|
|
@@ -50,15 +50,15 @@ test('a store nested in the crouter home does not leak into a canvas-artifact re
|
|
|
50
50
|
mkdirSync(leakedStore, { recursive: true });
|
|
51
51
|
writeFileSync(join(leakedStore, 'private-thing.md'), '---\nkind: knowledge\n' +
|
|
52
52
|
"when-and-why-to-read: When X, read this because Y.\n" +
|
|
53
|
-
'
|
|
54
|
-
'
|
|
53
|
+
'surfaces:\n' +
|
|
54
|
+
' - {on: read, match: "**/*.md", at: content}\n---\n' +
|
|
55
55
|
'PRIVATE ASSISTANT MEMORY MUST NOT LEAK\n');
|
|
56
56
|
// A canvas runtime artifact under the crouter home — the shape of every node
|
|
57
57
|
// report/context file the harness auto-reads.
|
|
58
58
|
const reportFile = join(fakeHome, '.crouter', 'canvas', 'nodes', 'somenode', 'reports', 'r.md');
|
|
59
59
|
mkdirSync(join(fakeHome, '.crouter', 'canvas', 'nodes', 'somenode', 'reports'), { recursive: true });
|
|
60
60
|
writeFileSync(reportFile, '# a node report\n');
|
|
61
|
-
const rendered = renderOnReadDocs(node, reportFile, new
|
|
61
|
+
const rendered = renderOnReadDocs(node, reportFile, new Map());
|
|
62
62
|
assert.ok(!rendered.includes('PRIVATE ASSISTANT MEMORY MUST NOT LEAK'), `crouter-home-nested store must not surface on a canvas read; got: ${rendered}`);
|
|
63
63
|
assert.ok(!rendered.includes('private-thing'), `crouter-home-nested store name must not surface on a canvas read; got: ${rendered}`);
|
|
64
64
|
});
|
|
@@ -45,7 +45,7 @@ after(() => {
|
|
|
45
45
|
else
|
|
46
46
|
process.env['HOME'] = prevHomeEnv;
|
|
47
47
|
});
|
|
48
|
-
test('project and user memory docs without
|
|
48
|
+
test('project and user memory docs without read surfaces do not fire on file reads', () => {
|
|
49
49
|
const fakeHome = mkdtempSync(join(tmpdir(), 'crtr-onread-user-home-'));
|
|
50
50
|
process.env['HOME'] = fakeHome;
|
|
51
51
|
resetScopeCache();
|
|
@@ -53,18 +53,16 @@ test('project and user memory docs without applies-to do not fire on file reads'
|
|
|
53
53
|
const userMemDir = join(fakeHome, '.crouter', 'memory');
|
|
54
54
|
mkdirSync(userMemDir, { recursive: true });
|
|
55
55
|
writeFileSync(join(userMemDir, 'noisy-global.md'), '---\nkind: preference\n' +
|
|
56
|
-
'when-and-why-to-read: When reading any file, this preference should be read because this fixture must not fire without an explicit route\n' +
|
|
57
|
-
'file-read-visibility: content\n---\n' +
|
|
56
|
+
'when-and-why-to-read: When reading any file, this preference should be read because this fixture must not fire without an explicit route\n---\n' +
|
|
58
57
|
'NOISY USER BODY\n');
|
|
59
58
|
const projectMemDir = join(fakeHome, 'work', '.crouter', 'memory');
|
|
60
59
|
mkdirSync(projectMemDir, { recursive: true });
|
|
61
60
|
writeFileSync(join(projectMemDir, 'local.md'), '---\nkind: knowledge\n' +
|
|
62
|
-
'when-and-why-to-read: When reading project files, this reference should be read because this fixture proves explicit file routing\n' +
|
|
63
|
-
'file-read-visibility: content\n---\n' +
|
|
61
|
+
'when-and-why-to-read: When reading project files, this reference should be read because this fixture proves explicit file routing\n---\n' +
|
|
64
62
|
'LOCAL PROJECT BODY\n');
|
|
65
63
|
const readFile = join(fakeHome, 'work', 'ideas.md');
|
|
66
64
|
writeFileSync(readFile, 'turn off xml closer\n');
|
|
67
|
-
const rendered = renderOnReadDocs(node, readFile, new
|
|
65
|
+
const rendered = renderOnReadDocs(node, readFile, new Map());
|
|
68
66
|
assert.ok(!rendered.includes('NOISY USER BODY'), 'user-global doc has no explicit file route');
|
|
69
67
|
assert.ok(!rendered.includes('LOCAL PROJECT BODY'), 'project doc also has no implicit file route');
|
|
70
68
|
assert.equal(rendered, '', 'no file context renders without an explicit trigger');
|
|
@@ -72,13 +70,15 @@ test('project and user memory docs without applies-to do not fire on file reads'
|
|
|
72
70
|
});
|
|
73
71
|
test('on-read doc surfaces once, stays deduped across a revive(resume), re-surfaces after a fresh launch', () => {
|
|
74
72
|
const node = spawnNode({ kind: 'general', cwd: work, parent: null }).node_id;
|
|
75
|
-
// A file-routed substrate doc in a `.crouter/memory/` ancestor.
|
|
73
|
+
// A file-routed substrate doc in a `.crouter/memory/` ancestor. The
|
|
74
|
+
// `./`-anchored glob pins the repo-relative anchor: `src/file.ts` matches
|
|
75
|
+
// only relative to the doc's owning repo dir, never absolutely.
|
|
76
76
|
const memDir = join(work, '.crouter', 'memory');
|
|
77
77
|
mkdirSync(memDir, { recursive: true });
|
|
78
78
|
writeFileSync(join(memDir, 'onread-fixture.md'), '---\nkind: knowledge\n' +
|
|
79
79
|
'when-and-why-to-read: When reading work files, this reference should be read because it is the on-read regression fixture\n' +
|
|
80
|
-
'
|
|
81
|
-
'
|
|
80
|
+
'surfaces:\n' +
|
|
81
|
+
' - {on: read, match: "./src/**", at: content}\n---\n' +
|
|
82
82
|
`${FIXTURE_BODY}\n`);
|
|
83
83
|
const readFile = join(work, 'src', 'file.ts');
|
|
84
84
|
mkdirSync(join(work, 'src'), { recursive: true });
|
|
@@ -106,13 +106,13 @@ test('an explicitly routed doc injects on-read under its frontmatter name, not t
|
|
|
106
106
|
writeFileSync(join(memDir, 'physical-guide.md'), '---\nkind: knowledge\n' +
|
|
107
107
|
'name: acme-project\n' +
|
|
108
108
|
'when-and-why-to-read: When reading acme source, this knowledge should be read because it carries the project-specific implementation constraints.\n' +
|
|
109
|
-
'
|
|
110
|
-
'
|
|
109
|
+
'surfaces:\n' +
|
|
110
|
+
' - {on: read, match: "./src/**", at: content}\n---\n' +
|
|
111
111
|
'ACME PROJECT OPERATING GUIDE\n');
|
|
112
112
|
const readFile = join(work, 'src', 'file.ts');
|
|
113
113
|
mkdirSync(join(work, 'src'), { recursive: true });
|
|
114
114
|
writeFileSync(readFile, 'export const x = 1;\n');
|
|
115
|
-
const rendered = renderOnReadDocs(node, readFile, new
|
|
115
|
+
const rendered = renderOnReadDocs(node, readFile, new Map());
|
|
116
116
|
assert.ok(rendered.includes('ACME PROJECT OPERATING GUIDE'), 'the matching explicit route fires');
|
|
117
117
|
assert.ok(rendered.includes('<memory kind="knowledge" name="acme-project"'), `expected explicit frontmatter name "acme-project" in the injected envelope, got: ${rendered}`);
|
|
118
118
|
assert.ok(!rendered.includes('name="physical-guide"'), 'must not fall back to the path-derived name when frontmatter sets one');
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// A `.crouter/memory` store nested BELOW a mounted project root is delivered
|
|
2
|
-
// by the read path, not by boot: its
|
|
3
|
-
// beneath the store's owning dir, deduplicated against the
|
|
4
|
-
// render through the shared transcript dedup set. The
|
|
5
|
-
// to project-store docs — a hand-authored
|
|
6
|
-
// match a read.
|
|
2
|
+
// by the read path, not by boot: its `./**`-routed docs surface on the first
|
|
3
|
+
// read beneath the store's owning dir, deduplicated against the
|
|
4
|
+
// workspace-open render through the shared transcript dedup set. The `./`
|
|
5
|
+
// anchor stays fenced to project-store docs — a hand-authored `./` glob in a
|
|
6
|
+
// user-scope doc must never match a read.
|
|
7
7
|
//
|
|
8
8
|
// Run: node --import tsx/esm --test src/core/__tests__/on-read-nested-store.test.ts
|
|
9
9
|
import { test, before, beforeEach, after } from 'node:test';
|
|
@@ -45,22 +45,25 @@ after(() => {
|
|
|
45
45
|
process.env['HOME'] = prevHomeEnv;
|
|
46
46
|
});
|
|
47
47
|
/** A work tree with a root front door and a nested store front door, both
|
|
48
|
-
*
|
|
48
|
+
* carrying the explicit front-door pair (workspace-open + `./**` read).
|
|
49
|
+
* Returns the work dir. */
|
|
49
50
|
function makeWorkTree() {
|
|
50
51
|
const work = join(fakeHome, 'work');
|
|
51
52
|
const rootStore = join(work, '.crouter', 'memory');
|
|
52
53
|
mkdirSync(rootStore, { recursive: true });
|
|
53
54
|
writeFileSync(join(rootStore, 'INDEX.md'), '---\nkind: knowledge\n' +
|
|
54
55
|
'when-and-why-to-read: When working in this workspace, this knowledge should be read because it is the operating guide.\n' +
|
|
55
|
-
'
|
|
56
|
-
'
|
|
56
|
+
'surfaces:\n' +
|
|
57
|
+
' - {on: workspace-open, at: content}\n' +
|
|
58
|
+
' - {on: read, match: "./**", at: content}\n---\n' +
|
|
57
59
|
`${ROOT_BODY}\n`);
|
|
58
60
|
const nestedStore = join(work, 'pkg', '.crouter', 'memory');
|
|
59
61
|
mkdirSync(nestedStore, { recursive: true });
|
|
60
62
|
writeFileSync(join(nestedStore, 'INDEX.md'), '---\nkind: knowledge\n' +
|
|
61
63
|
'when-and-why-to-read: When working in pkg, this knowledge should be read because it carries the package constraints.\n' +
|
|
62
|
-
'
|
|
63
|
-
'
|
|
64
|
+
'surfaces:\n' +
|
|
65
|
+
' - {on: workspace-open, at: content}\n' +
|
|
66
|
+
' - {on: read, match: "./**", at: content}\n---\n' +
|
|
64
67
|
`${NESTED_BODY}\n`);
|
|
65
68
|
return work;
|
|
66
69
|
}
|
|
@@ -70,18 +73,18 @@ test('a nested store front door surfaces on a read beneath its owning dir, not o
|
|
|
70
73
|
const inside = join(work, 'pkg', 'lib', 'file.ts');
|
|
71
74
|
mkdirSync(join(work, 'pkg', 'lib'), { recursive: true });
|
|
72
75
|
writeFileSync(inside, 'export const x = 1;\n');
|
|
73
|
-
const rendered = renderOnReadDocs(node, inside, new
|
|
76
|
+
const rendered = renderOnReadDocs(node, inside, new Map());
|
|
74
77
|
assert.ok(rendered.includes(NESTED_BODY), `read beneath pkg surfaces the nested front door; got: ${rendered}`);
|
|
75
78
|
const outside = join(work, 'docs', 'notes.ts');
|
|
76
79
|
mkdirSync(join(work, 'docs'), { recursive: true });
|
|
77
80
|
writeFileSync(outside, 'export const y = 2;\n');
|
|
78
|
-
const renderedOutside = renderOnReadDocs(node, outside, new
|
|
81
|
+
const renderedOutside = renderOnReadDocs(node, outside, new Map());
|
|
79
82
|
assert.ok(!renderedOutside.includes(NESTED_BODY), `read outside pkg must not surface the nested front door; got: ${renderedOutside}`);
|
|
80
83
|
});
|
|
81
84
|
test('workspace-open and on-read share one dedup set: root front door once, nested store on first read beneath it', () => {
|
|
82
85
|
const work = makeWorkTree();
|
|
83
86
|
const node = spawnNode({ kind: 'general', cwd: work, parent: null }).node_id;
|
|
84
|
-
const seen = new
|
|
87
|
+
const seen = new Map();
|
|
85
88
|
const opened = renderWorkspaceOpenDocs(node, seen);
|
|
86
89
|
assert.ok(opened.includes(ROOT_BODY), 'workspace open delivers the mounted root front door');
|
|
87
90
|
assert.ok(!opened.includes(NESTED_BODY), 'boot stays flat: the nested store is not in the mounted stack');
|
|
@@ -92,18 +95,18 @@ test('workspace-open and on-read share one dedup set: root front door once, nest
|
|
|
92
95
|
assert.ok(onRead.includes(NESTED_BODY), 'the nested front door delivers on the first read beneath it');
|
|
93
96
|
assert.ok(!onRead.includes(ROOT_BODY), 'the root front door already delivered at workspace open — never twice');
|
|
94
97
|
});
|
|
95
|
-
test('a hand-authored
|
|
98
|
+
test('a hand-authored `./`-anchored read glob in a user-scope doc never matches a read', () => {
|
|
96
99
|
const userStore = join(fakeHome, '.crouter', 'memory');
|
|
97
100
|
mkdirSync(userStore, { recursive: true });
|
|
98
101
|
writeFileSync(join(userStore, 'control.md'), '---\nkind: knowledge\n' +
|
|
99
102
|
'when-and-why-to-read: When reading markdown, this knowledge should be read because it is the corpus-loaded control fixture.\n' +
|
|
100
|
-
'
|
|
101
|
-
'
|
|
103
|
+
'surfaces:\n' +
|
|
104
|
+
' - {on: read, match: "**/*.md", at: content}\n---\n' +
|
|
102
105
|
'USER CONTROL BODY\n');
|
|
103
106
|
writeFileSync(join(userStore, 'dot-routed.md'), '---\nkind: knowledge\n' +
|
|
104
107
|
'when-and-why-to-read: When anything, this knowledge should be read because it must never fire.\n' +
|
|
105
|
-
'
|
|
106
|
-
'
|
|
108
|
+
'surfaces:\n' +
|
|
109
|
+
' - {on: read, match: "./**", at: content}\n---\n' +
|
|
107
110
|
'USER DOT BODY MUST NOT FIRE\n');
|
|
108
111
|
const work = join(fakeHome, 'work');
|
|
109
112
|
mkdirSync(work, { recursive: true });
|
|
@@ -111,7 +114,7 @@ test('a hand-authored `.` route in a user-scope doc never matches a read', () =>
|
|
|
111
114
|
const readFile = join(fakeHome, 'notes', 'thing.md');
|
|
112
115
|
mkdirSync(join(fakeHome, 'notes'), { recursive: true });
|
|
113
116
|
writeFileSync(readFile, '# a note\n');
|
|
114
|
-
const rendered = renderOnReadDocs(node, readFile, new
|
|
117
|
+
const rendered = renderOnReadDocs(node, readFile, new Map());
|
|
115
118
|
assert.ok(rendered.includes('USER CONTROL BODY'), 'the glob-routed user doc proves the user store is in the corpus');
|
|
116
|
-
assert.ok(!rendered.includes('USER DOT BODY MUST NOT FIRE'), `the
|
|
119
|
+
assert.ok(!rendered.includes('USER DOT BODY MUST NOT FIRE'), `the \`./\` anchor is project-store only — a user doc's owning root is ~; got: ${rendered}`);
|
|
117
120
|
});
|
package/dist/core/canvas/db.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { DatabaseSync } from 'node:sqlite';
|
|
2
2
|
/** The ordered migration list. Index `i` is migration version `i + 1`; the db's
|
|
3
|
-
* `user_version` tracks how many have been applied. Append only.
|
|
3
|
+
* `user_version` tracks how many have been applied. Append only. Journaled
|
|
4
|
+
* entries from `src/migrations/registry.ts` follow the local steps, so a new
|
|
5
|
+
* registry migration lands as the next version. */
|
|
4
6
|
export declare const MIGRATIONS: ReadonlyArray<(db: DatabaseSync) => void>;
|
|
5
7
|
/** Bring `db` up to the latest schema version. Reads `user_version`, runs each
|
|
6
8
|
* pending migration in order, and bumps `user_version` after each so the work
|
package/dist/core/canvas/db.js
CHANGED
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
import { DatabaseSync } from 'node:sqlite';
|
|
8
8
|
import { existsSync, readFileSync, readdirSync, appendFileSync } from 'node:fs';
|
|
9
9
|
import { basename, join, resolve } from 'node:path';
|
|
10
|
-
import { canvasDbPath, ensureHome, isSafeNodeId, nodesRoot, nodeMetaPath } from './paths.js';
|
|
10
|
+
import { canvasDbPath, crtrHome, ensureHome, isSafeNodeId, nodesRoot, nodeMetaPath } from './paths.js';
|
|
11
|
+
import { STATE_MIGRATIONS } from '../../migrations/registry.js';
|
|
11
12
|
import { customExtensionPaths } from './extensions.js';
|
|
12
13
|
import { updateJsonFileDurably } from './meta-file.js';
|
|
13
14
|
// --- Schema as a forward-only migration list ------------------------------
|
|
@@ -1152,8 +1153,16 @@ ALTER TABLE crons ADD COLUMN held INTEGER NOT NULL DEFAULT 0;
|
|
|
1152
1153
|
ALTER TABLE crons ADD COLUMN last_poke_at TEXT;
|
|
1153
1154
|
`);
|
|
1154
1155
|
}
|
|
1156
|
+
/** The journaled lane of the state-migration registry, adapted to this chain's
|
|
1157
|
+
* step shape. Registry order is preserved, so these versions stay stable as
|
|
1158
|
+
* long as the registry is append-only (same contract as the local list). */
|
|
1159
|
+
const JOURNALED_STATE_MIGRATIONS = STATE_MIGRATIONS
|
|
1160
|
+
.filter((m) => m.lane === 'journaled')
|
|
1161
|
+
.map((m) => (db) => m.apply(db, crtrHome()));
|
|
1155
1162
|
/** The ordered migration list. Index `i` is migration version `i + 1`; the db's
|
|
1156
|
-
* `user_version` tracks how many have been applied. Append only.
|
|
1163
|
+
* `user_version` tracks how many have been applied. Append only. Journaled
|
|
1164
|
+
* entries from `src/migrations/registry.ts` follow the local steps, so a new
|
|
1165
|
+
* registry migration lands as the next version. */
|
|
1157
1166
|
export const MIGRATIONS = [
|
|
1158
1167
|
/* v1 */ baselineSchema,
|
|
1159
1168
|
/* v2 */ addRuntimeColumns,
|
|
@@ -1189,6 +1198,7 @@ export const MIGRATIONS = [
|
|
|
1189
1198
|
/* v30 */ dropConsultOutbox,
|
|
1190
1199
|
/* v31 */ addReviewSubmitRequested,
|
|
1191
1200
|
/* v32 */ addCronHeldColumns,
|
|
1201
|
+
...JOURNALED_STATE_MIGRATIONS,
|
|
1192
1202
|
];
|
|
1193
1203
|
/** Migration indexes that manage their OWN transaction and therefore must not
|
|
1194
1204
|
* be wrapped by `migrate()` — `node:sqlite` rejects a nested BEGIN. Index 3
|
|
@@ -4,13 +4,15 @@
|
|
|
4
4
|
* rule of its own. */
|
|
5
5
|
export declare const HISTORY_DIR = ".history";
|
|
6
6
|
/** File operations only — deliberately not a review lifecycle. */
|
|
7
|
-
export type HistoryOp = 'create' | 'edit' | 'delete';
|
|
7
|
+
export type HistoryOp = 'create' | 'edit' | 'delete' | 'move';
|
|
8
8
|
export interface HistoryRecord {
|
|
9
9
|
op: HistoryOp;
|
|
10
10
|
at: string;
|
|
11
11
|
/** Authoring node id; absent for a human at a bare CLI. */
|
|
12
12
|
node?: string;
|
|
13
13
|
cwd: string;
|
|
14
|
+
/** The canonical name this document answered to before a `move`; absent otherwise. */
|
|
15
|
+
from?: string;
|
|
14
16
|
/** Why this revision happened. Required on `edit`, absent otherwise. */
|
|
15
17
|
rationale?: string;
|
|
16
18
|
/** True when the body was declared a verbatim payload from the principal. */
|
|
@@ -29,6 +31,7 @@ export declare function historyLogPathFor(memoryRoot: string, docPath: string):
|
|
|
29
31
|
* serialized key order — the on-disk shape consumers read. */
|
|
30
32
|
export declare function buildHistoryRecord(input: {
|
|
31
33
|
op: HistoryOp;
|
|
34
|
+
from?: string;
|
|
32
35
|
rationale?: string;
|
|
33
36
|
verbatim?: boolean;
|
|
34
37
|
before: string;
|
|
@@ -40,6 +40,7 @@ export function buildHistoryRecord(input) {
|
|
|
40
40
|
at: new Date().toISOString(),
|
|
41
41
|
...(env['CRTR_NODE_ID'] ? { node: env['CRTR_NODE_ID'] } : {}),
|
|
42
42
|
cwd: env['CRTR_NODE_CWD'] ?? process.cwd(),
|
|
43
|
+
...(input.from !== undefined ? { from: input.from } : {}),
|
|
43
44
|
...(input.rationale !== undefined ? { rationale: input.rationale } : {}),
|
|
44
45
|
...(input.verbatim === true ? { verbatim: true } : {}),
|
|
45
46
|
before: input.before,
|
|
@@ -2,10 +2,11 @@ import type { RefMeta } from '../runtime/broker-protocol.js';
|
|
|
2
2
|
/**
|
|
3
3
|
* Build the inline memory-reference inventory: every referenceable document
|
|
4
4
|
* in this node's corpus, precedence-deduped first-wins by canonical name,
|
|
5
|
-
* both kinds, no gate/rung/`slash` filtering
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* both kinds, no gate/rung/`slash` filtering. Returns display metadata
|
|
6
|
+
* (`refs`, stably sorted by winning scope then name) plus the resolution set
|
|
7
|
+
* (`names` — doc names plus every proper directory prefix, because a bare-dir
|
|
8
|
+
* ref is a legal listing link) that both the broker's per-submission resolver
|
|
9
|
+
* and `buildGuidance` test tokens against.
|
|
9
10
|
*/
|
|
10
11
|
export declare function buildRefInventory(): {
|
|
11
12
|
refs: RefMeta[];
|
|
@@ -18,8 +18,10 @@
|
|
|
18
18
|
// automatic boot/on-read surfacing only (spec §7.1), and `slash` governs
|
|
19
19
|
// command registration only (spec §4.3). An explicit human reference
|
|
20
20
|
// outranks both.
|
|
21
|
-
// •
|
|
22
|
-
//
|
|
21
|
+
// • Directory names are referenceable too: a bare-dir `[[ref]]` opens the
|
|
22
|
+
// directory's listing through `crtr memory read <dir>`, so every proper
|
|
23
|
+
// name-prefix of a winning doc joins the resolution set — resolution
|
|
24
|
+
// only, never a display row.
|
|
23
25
|
// • A malformed doc (no valid `kind`) is already dropped by
|
|
24
26
|
// `parseSubstrateDoc` returning null, so it never reaches the winners map.
|
|
25
27
|
//
|
|
@@ -29,7 +31,6 @@
|
|
|
29
31
|
// cache it reads through.
|
|
30
32
|
import { listAllMemoryDocs } from '../memory-resolver.js';
|
|
31
33
|
import { parseSubstrateDoc } from '../substrate/schema.js';
|
|
32
|
-
import { displayName } from '../substrate/ceiling.js';
|
|
33
34
|
import { cachedSubstrateDocsInclusive } from '../substrate/session-cache.js';
|
|
34
35
|
// Display-sort weight matching resolution precedence (node > project >
|
|
35
36
|
// profile > user > builtin) — display-only; resolution itself is
|
|
@@ -44,26 +45,21 @@ const SCOPE_SORT_RANK = {
|
|
|
44
45
|
/**
|
|
45
46
|
* Build the inline memory-reference inventory: every referenceable document
|
|
46
47
|
* in this node's corpus, precedence-deduped first-wins by canonical name,
|
|
47
|
-
* both kinds, no gate/rung/`slash` filtering
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
48
|
+
* both kinds, no gate/rung/`slash` filtering. Returns display metadata
|
|
49
|
+
* (`refs`, stably sorted by winning scope then name) plus the resolution set
|
|
50
|
+
* (`names` — doc names plus every proper directory prefix, because a bare-dir
|
|
51
|
+
* ref is a legal listing link) that both the broker's per-submission resolver
|
|
52
|
+
* and `buildGuidance` test tokens against.
|
|
51
53
|
*/
|
|
52
54
|
export function buildRefInventory() {
|
|
53
55
|
const docs = cachedSubstrateDocsInclusive(listAllMemoryDocs, parseSubstrateDoc);
|
|
54
|
-
// First-wins by canonical
|
|
55
|
-
//
|
|
56
|
-
//
|
|
56
|
+
// First-wins by canonical name: docs arrive already in precedence order
|
|
57
|
+
// (listAllMemoryDocs' nearest-first source ordering), so the first doc seen
|
|
58
|
+
// for a name is the winner.
|
|
57
59
|
const winners = new Map();
|
|
58
60
|
for (const doc of docs) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
// has no other nonempty canonical identity in this corpus, so it has
|
|
62
|
-
// no inline token to offer — omit it rather than expose `name: ''`.
|
|
63
|
-
if (name === '')
|
|
64
|
-
continue;
|
|
65
|
-
if (!winners.has(name))
|
|
66
|
-
winners.set(name, doc);
|
|
61
|
+
if (!winners.has(doc.name))
|
|
62
|
+
winners.set(doc.name, doc);
|
|
67
63
|
}
|
|
68
64
|
const refs = [];
|
|
69
65
|
for (const [name, doc] of winners) {
|
|
@@ -75,5 +71,13 @@ export function buildRefInventory() {
|
|
|
75
71
|
return scopeDelta;
|
|
76
72
|
return a.name.localeCompare(b.name);
|
|
77
73
|
});
|
|
78
|
-
|
|
74
|
+
const names = new Set(refs.map((r) => r.name));
|
|
75
|
+
// Directory names resolve as listing links, so they join the set — but not
|
|
76
|
+
// the display rows: a dir has no kind/short-form to show.
|
|
77
|
+
for (const name of winners.keys()) {
|
|
78
|
+
const segs = name.split('/');
|
|
79
|
+
for (let i = 1; i < segs.length; i++)
|
|
80
|
+
names.add(segs.slice(0, i).join('/'));
|
|
81
|
+
}
|
|
82
|
+
return { refs, names };
|
|
79
83
|
}
|
|
@@ -5,8 +5,8 @@ import { type InstalledPlugin, type Scope } from '../types.js';
|
|
|
5
5
|
* stack nearest > ... > profile > user > builtin) for strong matches: (1) exact
|
|
6
6
|
* substrate identity (`doc.name === query` — the explicit frontmatter `name`,
|
|
7
7
|
* or its path-derived fallback), then (2) direct `memory/<name>.md` physical
|
|
8
|
-
* path
|
|
9
|
-
*
|
|
8
|
+
* path. A directory name resolves to NO doc — the read leaf answers it with
|
|
9
|
+
* the directory's listing. Only when no source has a strong
|
|
10
10
|
* match does a second nearest-first pass try bare leaf-name fallback (final
|
|
11
11
|
* path segment only). Thus scope precedence resolves competing strong matches,
|
|
12
12
|
* while a nearer nested doc's coincidental leaf can never shadow a farther
|
|
@@ -123,8 +123,8 @@ export declare function listAllMemoryDocs(scope?: MemoryScope, quiet?: boolean,
|
|
|
123
123
|
* tree segment for segment with `.jsonl` in place of `.md`. Reuses the doc
|
|
124
124
|
* resolution rules so a log outlives its doc under the same name the doc had:
|
|
125
125
|
* numeric prefixes stay prefix-blind (`00-topic.md` logged at
|
|
126
|
-
* `.history/00-topic.jsonl` still answers to `topic`)
|
|
127
|
-
*
|
|
126
|
+
* `.history/00-topic.jsonl` still answers to `topic`). Returns null when
|
|
127
|
+
* nothing resolves. */
|
|
128
128
|
export declare function resolveHistoryLogPath(historyDir: string, segments: string[]): string | null;
|
|
129
129
|
export interface MemoryDocSnapshot {
|
|
130
130
|
/** Every document in default scope precedence order, loaded once. */
|