@north-light/crouter 0.3.222 → 0.3.224

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.
@@ -1,5 +1,6 @@
1
1
  import { type ProfileProjectMemory } from '../../api/dto/profiles.js';
2
2
  import type { MemoryDoc, MemoryScope } from '../memory-resolver.js';
3
+ import type { NodeConfigSubject } from './subject-fields.js';
3
4
  export declare const KINDS: readonly ["knowledge", "preference"];
4
5
  export type DocKind = (typeof KINDS)[number];
5
6
  /** Is `v` one of the two valid document kinds? */
@@ -34,10 +35,10 @@ export type SurfaceEvent = (typeof SURFACE_EVENTS)[number];
34
35
  export declare const SURFACE_RUNGS: readonly ["name", "preview", "content"];
35
36
  export type SurfaceRung = (typeof SURFACE_RUNGS)[number];
36
37
  /** One routing entry: when event `on` exposes a subject that fits `match`
37
- * (and `matchFrontmatter`, `read` only), the doc delivers at rung `at`.
38
- * Constraints within one entry AND together (each present constraint must
39
- * fit; a `match` list is satisfied by any one glob); multiple entries per
40
- * event OR together. */
38
+ * (and `matchFrontmatter`, `read` only) and its optional node-config `gate`,
39
+ * the doc delivers at rung `at`. Constraints within one entry AND together
40
+ * (each present constraint must fit; a `match` list is satisfied by any one
41
+ * glob); multiple entries per event OR together. */
41
42
  export interface SurfaceEntry {
42
43
  on: SurfaceEvent;
43
44
  /** Globs vs the event's subject namespace. Required on read/memory-read/
@@ -47,11 +48,15 @@ export interface SurfaceEntry {
47
48
  /** Predicate over the read file's own YAML frontmatter — `read` event only.
48
49
  * Frontmatter key `match-frontmatter`. */
49
50
  matchFrontmatter?: GatePredicate;
51
+ /** Optional predicate over the current node config. Frontmatter key `gate`.
52
+ * The document-level gate remains the hard eligibility check. */
53
+ gate?: GatePredicate;
50
54
  at: SurfaceRung;
51
55
  }
52
- /** A doc's boot rung: the highest `at` over its `boot` entries, `none` when
53
- * it carries no boot entry. The boot render's per-doc selection input. */
54
- export declare function bootRung(doc: Pick<SubstrateSchema, 'surfaces'>): Rung;
56
+ /** A doc's boot rung for `subject`: the highest `at` over participating
57
+ * `boot` entries, `none` when it carries none. The boot render's per-doc
58
+ * selection input. */
59
+ export declare function bootRung(doc: Pick<SubstrateSchema, 'surfaces'>, subject: NodeConfigSubject): Rung;
55
60
  /** A gate predicate tree, evaluated by predicate.ts (`evalCondition`) against
56
61
  * the node-config subject. Typed loosely on purpose — the matcher engine owns
57
62
  * validation; structurally it is a field→matcher map with optional
@@ -8,6 +8,7 @@
8
8
  // never throw): invalid `surfaces` entries are dropped rather than crashing a
9
9
  // render, and `crtr memory lint` owns strict authoring-time enforcement.
10
10
  import { PROFILE_PROJECT_MEMORY_VALUES } from '../../api/dto/profiles.js';
11
+ import { surfaceEntryGatePasses } from './gate.js';
11
12
  // ---------------------------------------------------------------------------
12
13
  // Kinds — the two semantic kinds (design §3): `knowledge` (consult — procedural
13
14
  // playbooks + factual references merged) vs `preference` (behave — standing
@@ -95,12 +96,13 @@ export const SURFACE_EVENTS = ['boot', 'workspace-open', 'read', 'memory-read',
95
96
  * absence of an entry. (`Rung`'s `none` survives only as the internal
96
97
  * ranking floor, e.g. `bootRung` of a doc with no boot entry.) */
97
98
  export const SURFACE_RUNGS = ['name', 'preview', 'content'];
98
- /** A doc's boot rung: the highest `at` over its `boot` entries, `none` when
99
- * it carries no boot entry. The boot render's per-doc selection input. */
100
- export function bootRung(doc) {
99
+ /** A doc's boot rung for `subject`: the highest `at` over participating
100
+ * `boot` entries, `none` when it carries none. The boot render's per-doc
101
+ * selection input. */
102
+ export function bootRung(doc, subject) {
101
103
  let r = 'none';
102
104
  for (const e of doc.surfaces) {
103
- if (e.on !== 'boot')
105
+ if (e.on !== 'boot' || !surfaceEntryGatePasses(e, subject))
104
106
  continue;
105
107
  if (rungRank(e.at) > rungRank(r))
106
108
  r = e.at;
@@ -194,8 +196,8 @@ function parseGate(v) {
194
196
  * dropped (bad `on`, bad `at`, a read/memory-read/command entry left with
195
197
  * neither `match` nor — read only — `match-frontmatter`). `match-frontmatter`
196
198
  * on a non-read event is dropped from the entry, which survives iff it still
197
- * carries a `match`. Lint owns strict enforcement; the runtime parser maps
198
- * over many docs and must never throw. */
199
+ * carries a `match`; an invalid entry `gate` is dropped. Lint owns strict
200
+ * enforcement; the runtime parser maps over many docs and must never throw. */
199
201
  function parseSurfaces(v) {
200
202
  if (!Array.isArray(v))
201
203
  return [];
@@ -212,6 +214,7 @@ function parseSurfaces(v) {
212
214
  continue;
213
215
  const match = parseMatchGlobs(rec['match']);
214
216
  const matchFrontmatter = on === 'read' ? parseGate(rec['match-frontmatter']) : undefined;
217
+ const gate = parseGate(rec['gate']);
215
218
  if ((on === 'read' || on === 'memory-read' || on === 'command') &&
216
219
  match === undefined &&
217
220
  matchFrontmatter === undefined) {
@@ -222,6 +225,7 @@ function parseSurfaces(v) {
222
225
  at: at,
223
226
  ...(match !== undefined ? { match } : {}),
224
227
  ...(matchFrontmatter !== undefined ? { matchFrontmatter } : {}),
228
+ ...(gate !== undefined ? { gate } : {}),
225
229
  });
226
230
  }
227
231
  return out;
@@ -1,4 +1,5 @@
1
1
  import { type Rung, type SubstrateDoc, type SurfaceEntry } from './schema.js';
2
+ import type { NodeConfigSubject } from './subject-fields.js';
2
3
  /** The project directory owning a doc's store: the path segment stack above
3
4
  * the doc's `.crouter` dir. `null` when the doc's path carries no `.crouter`
4
5
  * segment (builtin docs). */
@@ -13,20 +14,20 @@ export declare function underOwningRoot(doc: Pick<SubstrateDoc, 'path'>, absFile
13
14
  * realpathed. `readFrontmatter` is the read file's own parsed YAML (empty
14
15
  * for non-markdown files); an empty record never satisfies
15
16
  * `matchFrontmatter`. */
16
- export declare function matchesReadEntry(entry: SurfaceEntry, doc: Pick<SubstrateDoc, 'path' | 'scope'>, absReadFile: string, readFrontmatter: Record<string, unknown>): boolean;
17
+ export declare function matchesReadEntry(entry: SurfaceEntry, doc: Pick<SubstrateDoc, 'path' | 'scope'>, subject: NodeConfigSubject, absReadFile: string, readFrontmatter: Record<string, unknown>): boolean;
17
18
  /** Does a `memory-read` entry on the doc named `carrierName` fit a resolved
18
19
  * read of a doc known by `targetNames`? */
19
- export declare function matchesMemoryReadEntry(entry: SurfaceEntry, carrierName: string, targetNames: string[]): boolean;
20
+ export declare function matchesMemoryReadEntry(entry: SurfaceEntry, carrierName: string, subject: NodeConfigSubject | null, targetNames: string[]): boolean;
20
21
  /** Does a `command` entry fit the executed command string? */
21
- export declare function matchesCommandEntry(entry: SurfaceEntry, command: string): boolean;
22
+ export declare function matchesCommandEntry(entry: SurfaceEntry, subject: NodeConfigSubject, command: string): boolean;
22
23
  /** The doc's delivery rung for a read of `absReadFile` (already realpathed). */
23
- export declare function readDeliveryRung(doc: Pick<SubstrateDoc, 'surfaces' | 'path' | 'scope'>, absReadFile: string, readFrontmatter: Record<string, unknown>): Rung;
24
+ export declare function readDeliveryRung(doc: Pick<SubstrateDoc, 'surfaces' | 'path' | 'scope'>, subject: NodeConfigSubject, absReadFile: string, readFrontmatter: Record<string, unknown>): Rung;
24
25
  /** The doc's workspace-open rung. Presence of an entry is the match; callers
25
26
  * scope the corpus to project stores mounted by the subject's cwd/profile. */
26
- export declare function workspaceOpenRung(doc: Pick<SubstrateDoc, 'surfaces'>): Rung;
27
+ export declare function workspaceOpenRung(doc: Pick<SubstrateDoc, 'surfaces'>, subject: NodeConfigSubject): Rung;
27
28
  /** The doc's delivery rung for a `crtr memory read` that resolved a doc
28
29
  * known by `targetNames` (canonical name, plus the mount-stripped name for
29
30
  * plugin-mounted docs). The doc's own `name` anchors `./` globs. */
30
- export declare function memoryReadDeliveryRung(doc: Pick<SubstrateDoc, 'surfaces' | 'name'>, targetNames: string[]): Rung;
31
+ export declare function memoryReadDeliveryRung(doc: Pick<SubstrateDoc, 'surfaces' | 'name'>, subject: NodeConfigSubject | null, targetNames: string[]): Rung;
31
32
  /** The doc's delivery rung for an executed command string. */
32
- export declare function commandDeliveryRung(doc: Pick<SubstrateDoc, 'surfaces'>, command: string): Rung;
33
+ export declare function commandDeliveryRung(doc: Pick<SubstrateDoc, 'surfaces'>, subject: NodeConfigSubject, command: string): Rung;
@@ -13,6 +13,7 @@ import { basename, isAbsolute, matchesGlob, relative, sep } from 'node:path';
13
13
  import { CRTR_DIR_NAME } from '../../types.js';
14
14
  import { realpathOrSelf } from '../fs-utils.js';
15
15
  import { evalCondition } from '../predicate.js';
16
+ import { surfaceEntryGatePasses } from './gate.js';
16
17
  import { rungRank } from './schema.js';
17
18
  /** The project directory owning a doc's store: the path segment stack above
18
19
  * the doc's `.crouter` dir. `null` when the doc's path carries no `.crouter`
@@ -62,8 +63,8 @@ function readGlobMatches(glob, doc, absReadFile) {
62
63
  * realpathed. `readFrontmatter` is the read file's own parsed YAML (empty
63
64
  * for non-markdown files); an empty record never satisfies
64
65
  * `matchFrontmatter`. */
65
- export function matchesReadEntry(entry, doc, absReadFile, readFrontmatter) {
66
- if (entry.on !== 'read')
66
+ export function matchesReadEntry(entry, doc, subject, absReadFile, readFrontmatter) {
67
+ if (entry.on !== 'read' || !surfaceEntryGatePasses(entry, subject))
67
68
  return false;
68
69
  if (entry.match !== undefined && !entry.match.some((g) => readGlobMatches(g, doc, absReadFile))) {
69
70
  return false;
@@ -97,8 +98,8 @@ function memoryReadGlobMatches(glob, carrierName, targetNames) {
97
98
  }
98
99
  /** Does a `memory-read` entry on the doc named `carrierName` fit a resolved
99
100
  * read of a doc known by `targetNames`? */
100
- export function matchesMemoryReadEntry(entry, carrierName, targetNames) {
101
- if (entry.on !== 'memory-read' || entry.match === undefined)
101
+ export function matchesMemoryReadEntry(entry, carrierName, subject, targetNames) {
102
+ if (entry.on !== 'memory-read' || entry.match === undefined || !surfaceEntryGatePasses(entry, subject))
102
103
  return false;
103
104
  return entry.match.some((g) => memoryReadGlobMatches(g, carrierName, targetNames));
104
105
  }
@@ -119,8 +120,8 @@ function commandGlobMatches(glob, command) {
119
120
  }
120
121
  }
121
122
  /** Does a `command` entry fit the executed command string? */
122
- export function matchesCommandEntry(entry, command) {
123
- if (entry.on !== 'command' || entry.match === undefined)
123
+ export function matchesCommandEntry(entry, subject, command) {
124
+ if (entry.on !== 'command' || entry.match === undefined || !surfaceEntryGatePasses(entry, subject))
124
125
  return false;
125
126
  return entry.match.some((g) => commandGlobMatches(g, command));
126
127
  }
@@ -131,10 +132,10 @@ export function matchesCommandEntry(entry, command) {
131
132
  // floor, never an authored rung).
132
133
  // ---------------------------------------------------------------------------
133
134
  /** The doc's delivery rung for a read of `absReadFile` (already realpathed). */
134
- export function readDeliveryRung(doc, absReadFile, readFrontmatter) {
135
+ export function readDeliveryRung(doc, subject, absReadFile, readFrontmatter) {
135
136
  let r = 'none';
136
137
  for (const e of doc.surfaces) {
137
- if (!matchesReadEntry(e, doc, absReadFile, readFrontmatter))
138
+ if (!matchesReadEntry(e, doc, subject, absReadFile, readFrontmatter))
138
139
  continue;
139
140
  if (rungRank(e.at) > rungRank(r))
140
141
  r = e.at;
@@ -143,10 +144,10 @@ export function readDeliveryRung(doc, absReadFile, readFrontmatter) {
143
144
  }
144
145
  /** The doc's workspace-open rung. Presence of an entry is the match; callers
145
146
  * scope the corpus to project stores mounted by the subject's cwd/profile. */
146
- export function workspaceOpenRung(doc) {
147
+ export function workspaceOpenRung(doc, subject) {
147
148
  let r = 'none';
148
149
  for (const e of doc.surfaces) {
149
- if (e.on !== 'workspace-open')
150
+ if (e.on !== 'workspace-open' || !surfaceEntryGatePasses(e, subject))
150
151
  continue;
151
152
  if (rungRank(e.at) > rungRank(r))
152
153
  r = e.at;
@@ -156,10 +157,10 @@ export function workspaceOpenRung(doc) {
156
157
  /** The doc's delivery rung for a `crtr memory read` that resolved a doc
157
158
  * known by `targetNames` (canonical name, plus the mount-stripped name for
158
159
  * plugin-mounted docs). The doc's own `name` anchors `./` globs. */
159
- export function memoryReadDeliveryRung(doc, targetNames) {
160
+ export function memoryReadDeliveryRung(doc, subject, targetNames) {
160
161
  let r = 'none';
161
162
  for (const e of doc.surfaces) {
162
- if (!matchesMemoryReadEntry(e, doc.name, targetNames))
163
+ if (!matchesMemoryReadEntry(e, doc.name, subject, targetNames))
163
164
  continue;
164
165
  if (rungRank(e.at) > rungRank(r))
165
166
  r = e.at;
@@ -167,10 +168,10 @@ export function memoryReadDeliveryRung(doc, targetNames) {
167
168
  return r;
168
169
  }
169
170
  /** The doc's delivery rung for an executed command string. */
170
- export function commandDeliveryRung(doc, command) {
171
+ export function commandDeliveryRung(doc, subject, command) {
171
172
  let r = 'none';
172
173
  for (const e of doc.surfaces) {
173
- if (!matchesCommandEntry(e, command))
174
+ if (!matchesCommandEntry(e, subject, command))
174
175
  continue;
175
176
  if (rungRank(e.at) > rungRank(r))
176
177
  r = e.at;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@north-light/crouter",
3
- "version": "0.3.222",
3
+ "version": "0.3.224",
4
4
  "description": "crtr — agent runtime with memory, plugins, and marketplaces",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/runtime.lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@north-light/crouter",
3
- "version": "0.3.222",
3
+ "version": "0.3.224",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@north-light/crouter",
9
- "version": "0.3.222",
9
+ "version": "0.3.224",
10
10
  "hasInstallScript": true,
11
11
  "license": "MIT",
12
12
  "dependencies": {