@crouton-kit/crouter 0.3.49 → 0.3.50

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.
@@ -54,7 +54,7 @@ import { parseFrontmatterGeneric } from '../frontmatter.js';
54
54
  import { pathExists, readText, walkFiles } from '../fs-utils.js';
55
55
  import { memoryDir } from '../runtime/memory.js';
56
56
  import { projectScopeRoots } from '../scope.js';
57
- import { assembleNodeSubject, buildCeilingIndex, effectiveRung, gatePasses, indexDirOf, isIndexName, normalizeDocName, parseSubstrateDoc, parseSubstrateFrontmatter, previewLine, rungRank, } from './index.js';
57
+ import { assembleNodeSubject, buildCeilingIndex, effectiveRung, gatePasses, indexDirOf, isIndexName, normalizeDocName, parseSubstrateDoc, parseSubstrateFrontmatter, previewLine, resolveDocName, rungRank, } from './index.js';
58
58
  import { cachedNodeSubject, cachedSubstrateDocs } from './session-cache.js';
59
59
  // ---------------------------------------------------------------------------
60
60
  // Step 1 — winner selection (ceiling + gate + first-wins dedup).
@@ -209,14 +209,18 @@ function nodeLocalDocs(nodeId, subject) {
209
209
  const out = [];
210
210
  for (const file of walkFiles(dir, (n) => n.endsWith('.md'))) {
211
211
  const raw = relative(dir, file).replace(/\.md$/i, '').split(sep).join('/');
212
- const name = normalizeDocName(raw);
213
- if (!name)
212
+ const fallbackName = normalizeDocName(raw);
213
+ if (!fallbackName)
214
214
  continue;
215
215
  try {
216
216
  const { data, body } = parseFrontmatterGeneric(readText(file));
217
217
  const schema = parseSubstrateFrontmatter(data);
218
218
  if (schema === null)
219
219
  continue;
220
+ // ONE substrate-identity rule (schema.ts's resolveDocName), shared with
221
+ // the resolver and the on-read positional loader: explicit frontmatter
222
+ // `name` wins over the physical-path-derived fallback.
223
+ const name = resolveDocName(data, fallbackName);
220
224
  // node-local is NOT a resolver scope; `scope` is a placeholder never read
221
225
  // by gate eval (keyed off the NODE subject, not the doc) nor by the
222
226
  // renderers (keyed off name / body / rung).
@@ -20,6 +20,10 @@ export declare function normalizeNameSegment(segment: string): string;
20
20
  * `spine/has-manager`. This is the identity a doc displays, dedups, and
21
21
  * resolves under — distinct from its physical path, which keeps the prefix. */
22
22
  export declare function normalizeDocName(name: string): string;
23
+ /** Resolve a doc's identity from its raw frontmatter record: an explicit
24
+ * `name` field wins (trimmed + normalized), else `fallbackName` (the
25
+ * normalized path-derived name). */
26
+ export declare function resolveDocName(fm: Record<string, unknown> | null | undefined, fallbackName: string): string;
23
27
  /** A gate predicate tree, evaluated by predicate.ts (`evalCondition`) against
24
28
  * the node-config subject. Typed loosely on purpose — the matcher engine owns
25
29
  * validation; structurally it is a field→matcher map with optional
@@ -75,10 +79,12 @@ export interface SubstrateSchema {
75
79
  rationale?: string;
76
80
  }
77
81
  /** A fully-resolved substrate document: the parsed schema PLUS the resolver's
78
- * path-derived identity and body. This single object flows through the whole
82
+ * identity (explicit frontmatter `name` when present, otherwise the normalized
83
+ * path-derived fallback) and body. This single object flows through the whole
79
84
  * pipeline (gate eval → boot/on-read render), so a renderer never re-parses. */
80
85
  export interface SubstrateDoc extends SubstrateSchema {
81
- /** Path-derived identity, e.g. `taste/document-substrate` (resolver-supplied). */
86
+ /** Resolver-supplied identity, e.g. `taste/document-substrate` or an explicit
87
+ * frontmatter name when one is present. */
82
88
  name: string;
83
89
  /** The scope this doc resolved from. */
84
90
  scope: MemoryScope;
@@ -51,8 +51,9 @@ export const FALLBACK_RUNG = 'none';
51
51
  // prefix is NEVER part of the doc's identity: every identity-facing surface —
52
52
  // MemoryDoc.name, direct lookup, leaf fallback, `crtr memory read`/`list`,
53
53
  // prompt render, on-read display — derives from the NORMALIZED segments, so
54
- // `00-runtime-base` displays/dedups/resolves as `runtime-base`. Only the
55
- // physical path keeps the prefix.
54
+ // `00-runtime-base` displays/dedups/resolves as `runtime-base`. When a doc
55
+ // sets an explicit frontmatter `name`, that normalized name wins instead of
56
+ // the path-derived fallback. Only the physical path keeps the prefix.
56
57
  // ---------------------------------------------------------------------------
57
58
  const NUMERIC_PREFIX_RE = /^\d{2}-/;
58
59
  /** Strip an optional `NN-` ordering prefix from ONE path segment (file or
@@ -69,6 +70,22 @@ export function normalizeDocName(name) {
69
70
  return name.split('/').map(normalizeNameSegment).join('/');
70
71
  }
71
72
  // ---------------------------------------------------------------------------
73
+ // Substrate identity — the ONE explicit-name-then-path-fallback rule. A doc's
74
+ // resolver identity is its explicit frontmatter `name` when present (normalized),
75
+ // otherwise the caller-supplied path-derived fallback. Every loader that turns a
76
+ // physical .md file into a named doc (the resolver's `listMemoryDocsInDir`, the
77
+ // on-read positional loader) calls this ONE helper, so a doc's identity never
78
+ // depends on which path loaded it.
79
+ // ---------------------------------------------------------------------------
80
+ /** Resolve a doc's identity from its raw frontmatter record: an explicit
81
+ * `name` field wins (trimmed + normalized), else `fallbackName` (the
82
+ * normalized path-derived name). */
83
+ export function resolveDocName(fm, fallbackName) {
84
+ const raw = fm?.['name'];
85
+ const explicit = typeof raw === 'string' && raw.trim() !== '' ? normalizeDocName(raw.trim()) : '';
86
+ return explicit !== '' ? explicit : fallbackName;
87
+ }
88
+ // ---------------------------------------------------------------------------
72
89
  // Parse / validate.
73
90
  // ---------------------------------------------------------------------------
74
91
  /** Parse a raw frontmatter record (from `parseFrontmatterGeneric`, via the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crouton-kit/crouter",
3
- "version": "0.3.49",
3
+ "version": "0.3.50",
4
4
  "description": "crtr — agent runtime with memory, plugins, and marketplaces",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -33,7 +33,7 @@
33
33
  "prebuild": "rm -rf node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-tui node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-ai node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-agent-core",
34
34
  "build": "rm -rf dist && tsc && cp -R src/builtin-views dist/builtin-views && cp -R src/builtin-memory dist/builtin-memory && cp -R src/builtin-pi-packages dist/builtin-pi-packages && npm run build:attach && vite build --config src/clients/web/web-client/vite.config.ts",
35
35
  "lint:web-px": "node scripts/lint-web-px.mjs",
36
- "build:attach": "esbuild src/clients/attach/attach-cmd.ts --bundle --minify --format=esm --platform=node --target=node22 --alias:@earendil-works/pi-tui=$PWD/node_modules/@earendil-works/pi-tui --outfile=dist/clients/attach/attach-cmd.js --log-level=warning --banner:js=\"import{createRequire as __cr}from'node:module';const require=__cr(import.meta.url);\"",
36
+ "build:attach": "esbuild src/clients/attach/attach-cmd.ts --bundle --minify --format=esm --platform=node --target=node22 --alias:@earendil-works/pi-tui=$PWD/node_modules/@earendil-works/pi-tui --external:@crouton-kit/humanloop --outfile=dist/clients/attach/attach-cmd.js --log-level=warning --banner:js=\"import{createRequire as __cr}from'node:module';const require=__cr(import.meta.url);\"",
37
37
  "postinstall": "node scripts/postinstall.mjs",
38
38
  "dev": "tsx src/cli.ts",
39
39
  "link": "npm link",
@@ -47,7 +47,7 @@
47
47
  },
48
48
  "license": "MIT",
49
49
  "dependencies": {
50
- "@crouton-kit/humanloop": "^0.3.27",
50
+ "@crouton-kit/humanloop": "^0.3.29",
51
51
  "@earendil-works/pi-agent-core": "0.80.2",
52
52
  "@earendil-works/pi-ai": "0.80.3",
53
53
  "@earendil-works/pi-coding-agent": "0.80.2",