@orkestrel/scaffold 0.0.86 → 0.0.87

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.
@@ -57,7 +57,7 @@ so network-controlled descriptions never enter agent instruction context.
57
57
  | `@orkestrel/database` | `0.0.16` | L2 | `@orkestrel/sqlite` `^0.0.13`, `@orkestrel/emitter` `^0.0.11`, `@orkestrel/contract` `^0.0.18`, `@orkestrel/indexeddb` `^0.0.13` | |
58
58
  | `@orkestrel/emitter` | `0.0.11` | L1 | `@orkestrel/contract` `^0.0.18` | |
59
59
  | `@orkestrel/form` | `0.0.8` | L2 | `@orkestrel/emitter` `^0.0.11`, `@orkestrel/contract` `^0.0.18` | |
60
- | `@orkestrel/guide` | `0.0.22` | L3 | `@orkestrel/contract` `^0.0.18`, `@orkestrel/markdown` `^0.0.16` | |
60
+ | `@orkestrel/guide` | `0.0.24` | L3 | `@orkestrel/contract` `^0.0.19`, `@orkestrel/markdown` `^0.0.17` | |
61
61
  | `@orkestrel/html` | `0.0.12` | L1 | `@orkestrel/contract` `^0.0.19` | |
62
62
  | `@orkestrel/indexeddb` | `0.0.13` | L1 | `@orkestrel/contract` `^0.0.18` | |
63
63
  | `@orkestrel/interpret` | `0.0.15` | L3 | `@orkestrel/reason` `^0.0.12`, `@orkestrel/emitter` `^0.0.11`, `@orkestrel/contract` `^0.0.18`, `@orkestrel/template` `^0.0.9` | |
@@ -78,7 +78,7 @@ so network-controlled descriptions never enter agent instruction context.
78
78
  | `@orkestrel/reason` | `0.0.12` | L2 | `@orkestrel/emitter` `^0.0.11`, `@orkestrel/contract` `^0.0.18` | |
79
79
  | `@orkestrel/relation` | `0.0.14` | L3 | `@orkestrel/emitter` `^0.0.11`, `@orkestrel/contract` `^0.0.18`, `@orkestrel/database` `^0.0.16` | |
80
80
  | `@orkestrel/router` | `0.0.16` | L2 | `@orkestrel/abort` `^0.0.12`, `@orkestrel/emitter` `^0.0.11`, `@orkestrel/contract` `^0.0.18` | |
81
- | `@orkestrel/scaffold` | `0.0.85` | L3 | `@orkestrel/console` `^0.0.15`, `@orkestrel/emitter` `^0.0.11`, `@orkestrel/process` `^0.0.14`, `@orkestrel/contract` `^0.0.19`, `@orkestrel/markdown` `^0.0.17`, `@orkestrel/template` `^0.0.9` | |
81
+ | `@orkestrel/scaffold` | `0.0.86` | L3 | `@orkestrel/console` `^0.0.15`, `@orkestrel/emitter` `^0.0.11`, `@orkestrel/process` `^0.0.14`, `@orkestrel/contract` `^0.0.19`, `@orkestrel/markdown` `^0.0.17`, `@orkestrel/template` `^0.0.9` | |
82
82
  | `@orkestrel/sea` | `0.0.18` | L3 | `@orkestrel/emitter` `^0.0.11`, `@orkestrel/process` `^0.0.14`, `@orkestrel/contract` `^0.0.18` | |
83
83
  | `@orkestrel/server` | `0.0.22` | L3 | `@orkestrel/abort` `^0.0.12`, `@orkestrel/codec` `^0.0.5`, `@orkestrel/router` `^0.0.16`, `@orkestrel/emitter` `^0.0.11`, `@orkestrel/timeout` `^0.0.12`, `@orkestrel/contract` `^0.0.19` | |
84
84
  | `@orkestrel/sqlite` | `0.0.13` | L1 | `@orkestrel/contract` `^0.0.18` | |
@@ -28,6 +28,7 @@ paths:
28
28
  | Shape/algorithm compilers | `*/compilers.ts` |
29
29
  | Entity/value factories | `*/factories.ts` |
30
30
  | Middleware factories | `*/middlewares.ts` |
31
+ | Plugin factories | `*/plugins.ts` |
31
32
  | Request handlers | `*/handlers.ts` |
32
33
  | Route tables | `*/routes.ts` |
33
34
  | Seeders | `*/seeders.ts` |
@@ -63,7 +64,8 @@ Use only the centralized files an environment needs.
63
64
  - non-trivial or reusable → extract, export, unit-test, and route every duplicate through it.
64
65
  - `factories.ts`, `compilers.ts`, and `parsers.ts` are centralized files, not hiding places. Factory glue extracts to `helpers.ts`; pure compiler/parser recursion remains exported in its own kind file.
65
66
  - Every exported function in `parsers.ts` is named `parse*`. Every exported function in
66
- `factories.ts` is named `create*`.
67
+ `factories.ts` is named `create*`. Every exported function in `plugins.ts` is named
68
+ `create{Entity}Plugin`, or `create{Name}Plugins` when it returns a collection.
67
69
  - Those name forms are one-directional. A name does not place a function: `createWriteDirectory`
68
70
  creates a directory rather than an entity and `isVacant` is a predicate rather than a `Guard<T>`,
69
71
  so both stay in `helpers.ts`. Placement follows what the function is; the name form follows
@@ -86,7 +88,7 @@ Use only the centralized files an environment needs.
86
88
  - Keep the leaf pair class-free. `helpers.ts` and `validators.ts` sit at the bottom of a module's
87
89
  graph: they import types, constants, errors, and each other, and they import no implementation
88
90
  class. Every file that constructs or drives a class — `cloners.ts`, `compilers.ts`, `factories.ts`,
89
- `shapers.ts` — sits above them, consumes them, and is never consumed by them. One cycle between
91
+ `plugins.ts`, `shapers.ts` — sits above them, consumes them, and is never consumed by them. One cycle between
90
92
  the leaves is the shape this produces and is acceptable; an edge running downward from a
91
93
  class-importing file into the leaf pair is not.
92
94
  - `templates.ts` and `contracts.ts` hold data only — shipped template definitions and compiled
@@ -126,10 +128,10 @@ neither reads meaning.
126
128
  freeze obligation in the earlier kind-purity rules binds regardless; only the bare literal is
127
129
  mechanical.
128
130
  - The plugin does not tell one function kind from another. Every centralized file that permits
129
- functions reads the same to it apart from the `parse*` and `create*` name forms: `cloners.ts`,
130
- `combinators.ts`, `compilers.ts`, `errors.ts`, `factories.ts`, `handlers.ts`, `helpers.ts`,
131
- `inferers.ts`, `middlewares.ts`, `parsers.ts`, `relations.ts`, `schemas.ts`, `seeders.ts`,
132
- `shapers.ts`, and `validators.ts`. That list is exhaustive, a new function kind joins it, and no
131
+ functions reads the same to it apart from the `parse*`, `create*`, `create*Plugin`, and `create*Plugins` name
132
+ forms: `cloners.ts`, `combinators.ts`, `compilers.ts`, `errors.ts`, `factories.ts`, `handlers.ts`,
133
+ `helpers.ts`, `inferers.ts`, `middlewares.ts`, `parsers.ts`, `plugins.ts`, `relations.ts`,
134
+ `schemas.ts`, `seeders.ts`, `shapers.ts`, and `validators.ts`. That list is exhaustive, a new function kind joins it, and no
133
135
  later version of the plugin claims more.
134
136
  - The plugin reports no `data` violation in `helpers.ts`. The kind rules place a camelCase
135
137
  namespace of functions there, and a namespace of callables is not separable from a data table by
@@ -202,6 +204,13 @@ Store child managers in `#` fields and expose readonly getters typed as their in
202
204
  - Extract reusable cross-middleware machinery to helpers/classes.
203
205
  - `MiddlewareManager` is the sole manager of the middleware composition chain; do not create one manager per middleware.
204
206
 
207
+ ## Plugins
208
+
209
+ - A plugin is a value a host composes at creation: the binding of one entity class to the host's extension seam. The entity class stays unaware of it.
210
+ - Place plugin factories in `plugins.ts` as `create{Entity}Plugin(options?)`, and a factory of a host's collection as `create{Name}Plugins()`; keep them distinct from the entity factories in `factories.ts`.
211
+ - A plugin factory builds and returns the value; it never registers, installs, or boots anything. The host's creation options take the plugin list, and registration happens there.
212
+ - A plugin drives its entity through the entity's public interface. When the host needs behavior the interface lacks, add it to the interface as a real entity capability; never reach a private member from the plugin.
213
+
205
214
  ## Environment/module placement
206
215
 
207
216
  - Shared cross-environment logic belongs in the central core/shared layer. Other environments import core; core imports neither browser nor server.
@@ -169,17 +169,18 @@ Keep canonical case:
169
169
 
170
170
  ## Value-level identifiers
171
171
 
172
- | Kind | Required form |
173
- | -------------- | ------------------------------------- |
174
- | Class | PascalCase `{Entity}` |
175
- | Manager class | PascalCase `{Entity}Manager` |
176
- | Factory | camelCase `create{Entity}` |
177
- | Guard | camelCase `is{Condition}` |
178
- | Helper | camelCase `{verb}{Noun}` |
179
- | Constant | UPPER_SNAKE_CASE `{QUALIFIER}_{NOUN}` |
180
- | Property/field | camelCase bare noun |
181
- | Method | camelCase bare verb |
182
- | Boolean | camelCase adjective/past participle |
172
+ | Kind | Required form |
173
+ | -------------- | -------------------------------------------------------------------- |
174
+ | Class | PascalCase `{Entity}` |
175
+ | Manager class | PascalCase `{Entity}Manager` |
176
+ | Factory | camelCase `create{Entity}` |
177
+ | Plugin factory | camelCase `create{Entity}Plugin`; a collection `create{Name}Plugins` |
178
+ | Guard | camelCase `is{Condition}` |
179
+ | Helper | camelCase `{verb}{Noun}` |
180
+ | Constant | UPPER_SNAKE_CASE `{QUALIFIER}_{NOUN}` |
181
+ | Property/field | camelCase bare noun |
182
+ | Method | camelCase bare verb |
183
+ | Boolean | camelCase adjective/past participle |
183
184
 
184
185
  ## Fixed derivation/construction forms
185
186
 
@@ -187,6 +188,7 @@ Keep canonical case:
187
188
  - `is*`: total `Guard<T>`; never throws; returns false off-shape.
188
189
  - `parse*`: coercion producing `T | undefined`; cross-type conversion never belongs in a guard.
189
190
  - `create*`: the factory form; `.claude/rules/architecture.md` § Kind purity states what a factory is and where it lives.
191
+ - `create*Plugin` and `create*Plugins`: the plugin-factory forms; `.claude/rules/architecture.md` § Plugins owns what a plugin is and what its factory does. Never name a plugin factory `register*` or `install*`.
190
192
  - `*Of`: combinator named for its constituents, combining them into a container/guard/value, such as `arrayOf(guard)` or `boundsOf(min, max)`.
191
193
  - `{noun}To{Noun}`: projection from a whole to a derived view, such as `definitionToSnapshot`.
192
194
  - `*Shape`: `ContractShape` value/JSON-Schema blueprint, not a function or type.
@@ -31,6 +31,13 @@ export interface PolicyExpression extends PolicyNode {
31
31
  readonly source?: PolicyExpression
32
32
  readonly specifiers?: readonly PolicyExpression[]
33
33
  readonly imported?: PolicyExpression
34
+ readonly exported?: PolicyExpression
35
+ }
36
+
37
+ /** Pairs a name-form kind file's name pattern with the message id a misnamed export reports. */
38
+ export interface PolicyNameForm {
39
+ readonly pattern: RegExp
40
+ readonly messageId: string
34
41
  }
35
42
 
36
43
  /** Pairs one declared module function with the name a prefix rule reads. */
@@ -145,6 +152,7 @@ export const CENTRAL_SOURCE_FILES: readonly string[] = Object.freeze([
145
152
  'inferers.ts',
146
153
  'middlewares.ts',
147
154
  'parsers.ts',
155
+ 'plugins.ts',
148
156
  'relations.ts',
149
157
  'routes.ts',
150
158
  'schemas.ts',
@@ -167,6 +175,7 @@ export const FUNCTION_SOURCE_FILES: readonly string[] = Object.freeze([
167
175
  'inferers.ts',
168
176
  'middlewares.ts',
169
177
  'parsers.ts',
178
+ 'plugins.ts',
170
179
  'relations.ts',
171
180
  'schemas.ts',
172
181
  'seeders.ts',
@@ -274,6 +283,13 @@ export const POLICY_TAG_PATTERN = /\{@(?:linkcode|linkplain|link|inheritDoc)\b[^
274
283
  /** Matches a URL, whose segments are an address rather than prose. */
275
284
  export const POLICY_URL_PATTERN = /https?:\/\/\S+/gu
276
285
 
286
+ /** Maps each name-form kind file to the form every function it exports must take. */
287
+ export const POLICY_NAME_FORMS: Readonly<Record<string, PolicyNameForm>> = Object.freeze({
288
+ 'parsers.ts': Object.freeze({ pattern: /^parse/u, messageId: 'parser' }),
289
+ 'factories.ts': Object.freeze({ pattern: /^create/u, messageId: 'factory' }),
290
+ 'plugins.ts': Object.freeze({ pattern: /^create[A-Z]\w*Plugins?$/u, messageId: 'plugin' }),
291
+ })
292
+
277
293
  /**
278
294
  * Lists the words ending in `s` that open a sentence without being a third-person verb.
279
295
  *
@@ -992,24 +1008,37 @@ export function reportConstant(context: PolicyContext, node: PolicyExpression):
992
1008
  }
993
1009
  }
994
1010
 
995
- /** Reports a parsers.ts function whose name lacks the parse prefix. */
996
- export function reportParser(context: PolicyContext, node: PolicyExpression): void {
997
- if (isPolicyAmbient(context.filename) || !isPolicyTop(node)) return
998
- if (pathToPolicyFile(context.filename) !== 'parsers.ts') return
999
- for (const binding of statementToPolicyBindings(node)) {
1000
- if (binding.name === undefined || !binding.name.startsWith('parse')) {
1001
- context.report({ node: binding.node, messageId: 'parser' })
1011
+ /**
1012
+ * Reports a function in the named kind file whose name breaks that file's form, an export specifier
1013
+ * there that publishes a name outside it, and a star re-export there, whose names the form cannot
1014
+ * read.
1015
+ */
1016
+ export function reportName(context: PolicyContext, node: PolicyExpression, file: string): void {
1017
+ const form = POLICY_NAME_FORMS[file]
1018
+ if (form === undefined || pathToPolicyFile(context.filename) !== file) return
1019
+ if (node.type === 'ExportAllDeclaration') {
1020
+ context.report({ node, messageId: form.messageId })
1021
+ return
1022
+ }
1023
+ if (node.type === 'ExportNamedDeclaration') {
1024
+ for (const specifier of node.specifiers ?? []) {
1025
+ const exported = specifier.exported
1026
+ const name =
1027
+ typeof exported?.name === 'string'
1028
+ ? exported.name
1029
+ : typeof exported?.value === 'string'
1030
+ ? exported.value
1031
+ : undefined
1032
+ if (name === undefined || !form.pattern.test(name)) {
1033
+ context.report({ node: specifier, messageId: form.messageId })
1034
+ }
1002
1035
  }
1036
+ return
1003
1037
  }
1004
- }
1005
-
1006
- /** Reports a factories.ts function whose name lacks the create prefix. */
1007
- export function reportFactory(context: PolicyContext, node: PolicyExpression): void {
1008
- if (isPolicyAmbient(context.filename) || !isPolicyTop(node)) return
1009
- if (pathToPolicyFile(context.filename) !== 'factories.ts') return
1038
+ if (!isPolicyTop(node)) return
1010
1039
  for (const binding of statementToPolicyBindings(node)) {
1011
- if (binding.name === undefined || !binding.name.startsWith('create')) {
1012
- context.report({ node: binding.node, messageId: 'factory' })
1040
+ if (binding.name === undefined || !form.pattern.test(binding.name)) {
1041
+ context.report({ node: binding.node, messageId: form.messageId })
1013
1042
  }
1014
1043
  }
1015
1044
  }
@@ -1252,44 +1281,73 @@ export const CONSTANT_RULE: PolicyRuleInterface = {
1252
1281
  },
1253
1282
  }
1254
1283
 
1255
- /** Bans a parsers.ts function whose name lacks the parse prefix. */
1284
+ /** Bans a parsers.ts function or export whose name lacks the parse prefix. */
1256
1285
  export const PARSER_RULE: PolicyRuleInterface = {
1257
1286
  meta: {
1258
1287
  type: 'problem',
1259
1288
  docs: {
1260
- description: 'Disallow a parsers.ts function whose name does not start with parse.',
1289
+ description: 'Disallow a parsers.ts function or export whose name does not start with parse.',
1261
1290
  },
1262
1291
  messages: {
1263
1292
  parser:
1264
- 'Name this parsers.ts function with the parse prefix, or move it to its own kind file.',
1293
+ 'Name this parsers.ts function or export with the parse prefix, or move it to its own kind file.',
1265
1294
  },
1266
1295
  },
1267
1296
  create(context) {
1268
1297
  return {
1269
- FunctionDeclaration: (node) => reportParser(context, node),
1270
- TSDeclareFunction: (node) => reportParser(context, node),
1271
- VariableDeclaration: (node) => reportParser(context, node),
1298
+ ExportAllDeclaration: (node) => reportName(context, node, 'parsers.ts'),
1299
+ ExportNamedDeclaration: (node) => reportName(context, node, 'parsers.ts'),
1300
+ FunctionDeclaration: (node) => reportName(context, node, 'parsers.ts'),
1301
+ TSDeclareFunction: (node) => reportName(context, node, 'parsers.ts'),
1302
+ VariableDeclaration: (node) => reportName(context, node, 'parsers.ts'),
1272
1303
  }
1273
1304
  },
1274
1305
  }
1275
1306
 
1276
- /** Bans a factories.ts function whose name lacks the create prefix. */
1307
+ /** Bans a factories.ts function or export whose name lacks the create prefix. */
1277
1308
  export const FACTORY_RULE: PolicyRuleInterface = {
1278
1309
  meta: {
1279
1310
  type: 'problem',
1280
1311
  docs: {
1281
- description: 'Disallow a factories.ts function whose name does not start with create.',
1312
+ description:
1313
+ 'Disallow a factories.ts function or export whose name does not start with create.',
1282
1314
  },
1283
1315
  messages: {
1284
1316
  factory:
1285
- 'Name this factories.ts function with the create prefix, or move it to its own kind file.',
1317
+ 'Name this factories.ts function or export with the create prefix, or move it to its own kind file.',
1318
+ },
1319
+ },
1320
+ create(context) {
1321
+ return {
1322
+ ExportAllDeclaration: (node) => reportName(context, node, 'factories.ts'),
1323
+ ExportNamedDeclaration: (node) => reportName(context, node, 'factories.ts'),
1324
+ FunctionDeclaration: (node) => reportName(context, node, 'factories.ts'),
1325
+ TSDeclareFunction: (node) => reportName(context, node, 'factories.ts'),
1326
+ VariableDeclaration: (node) => reportName(context, node, 'factories.ts'),
1327
+ }
1328
+ },
1329
+ }
1330
+
1331
+ /** Bans a plugins.ts function or export whose name is not a create-prefixed plugin or plugins form. */
1332
+ export const PLUGIN_RULE: PolicyRuleInterface = {
1333
+ meta: {
1334
+ type: 'problem',
1335
+ docs: {
1336
+ description:
1337
+ 'Disallow a plugins.ts function or export whose name is not create…Plugin or create…Plugins.',
1338
+ },
1339
+ messages: {
1340
+ plugin:
1341
+ 'Name this plugins.ts function or export create…Plugin, or create…Plugins for a collection, or move it to its own kind file.',
1286
1342
  },
1287
1343
  },
1288
1344
  create(context) {
1289
1345
  return {
1290
- FunctionDeclaration: (node) => reportFactory(context, node),
1291
- TSDeclareFunction: (node) => reportFactory(context, node),
1292
- VariableDeclaration: (node) => reportFactory(context, node),
1346
+ ExportAllDeclaration: (node) => reportName(context, node, 'plugins.ts'),
1347
+ ExportNamedDeclaration: (node) => reportName(context, node, 'plugins.ts'),
1348
+ FunctionDeclaration: (node) => reportName(context, node, 'plugins.ts'),
1349
+ TSDeclareFunction: (node) => reportName(context, node, 'plugins.ts'),
1350
+ VariableDeclaration: (node) => reportName(context, node, 'plugins.ts'),
1293
1351
  }
1294
1352
  },
1295
1353
  }
@@ -1392,6 +1450,7 @@ export default {
1392
1450
  'no-malformed-constant': CONSTANT_RULE,
1393
1451
  'no-misnamed-parser': PARSER_RULE,
1394
1452
  'no-misnamed-factory': FACTORY_RULE,
1453
+ 'no-misnamed-plugin': PLUGIN_RULE,
1395
1454
  'no-malformed-domain': DOMAIN_RULE,
1396
1455
  'no-host-line-endings': ENDING_RULE,
1397
1456
  'no-malformed-summary': VOICE_RULE,
@@ -109,6 +109,7 @@
109
109
  "policy/no-malformed-constant": "error",
110
110
  "policy/no-misnamed-parser": "error",
111
111
  "policy/no-misnamed-factory": "error",
112
+ "policy/no-misnamed-plugin": "error",
112
113
  "policy/no-malformed-domain": "error"
113
114
  }
114
115
  },
@@ -129,7 +129,7 @@ directly.
129
129
  | `extractSourceLines` | function | `(source: string) => readonly SourceLine[]` | Extracts aligned physical source-line records in one character traversal. Real line and block comments and complete template tokens become spaces in `SourceLine.code`, while ordinary code, quoted strings, and recognized regex literals retain their columns. Genuine JSDoc opened from reflection code is retained span by span at its exact physical column in `SourceLine.jsdoc`; faux openers in comments and templates are excluded. Membership remains each consumer's separate anchored grammar. |
130
130
  | `extractExports` | function | `(source: string) => readonly SurfaceSymbol[]` | Extracts the module-scope exports declared in one file's source text — the declaration keys `collectKeys` reports, each split back into the keyword and name that built it, deduped by (keyword, name). |
131
131
  | `extractHidden` | function | `(source: string) => readonly SurfaceSymbol[]` | Extracts the module-scope declarations lacking the `export` keyword in one file's source text — the mirror image of `extractExports`'s grammar, anchored the same way (column 0, so an indented inner declaration never matches). |
132
- | `joinHead` | function | `(lines: readonly string[], start: number) => DeclarationHead \| undefined` | Joins the declaration head starting at `start` into one space-separated line, consuming lines until the first that ends with `{`. |
132
+ | `joinHead` | function | `(lines: readonly string[], start: number) => DeclarationHead \| undefined` | Joins the declaration head starting at `start` into one space-separated line, consuming lines until the first that opens a body: one that ends with `{`, or one that closes an empty body with `{}` or `{ }`. |
133
133
  | `escapeRegExp` | function | `(value: string) => string` | Escapes every regex metacharacter in a literal string so it reads as text inside a larger `RegExp` source rather than as syntax. |
134
134
  | `collectDeclarations` | function | `(source: string) => ReadonlyMap<string, Declaration>` | Collects every `export class` / `export interface` declaration one file's source text declares, each keyed `${keyword} ${name}` and carrying the body lines and the base identifiers read from its own head, so a body and a heritage clause always come from the same declaration. |
135
135
  | `extractDeclaration` | function | `(source: string, keyword: DeclarationKeyword, name: string) => Declaration \| undefined` | Locates the named `export class` / `export interface` declaration in one file's source text and returns its body lines and its base identifiers read from that one head, so a body and a heritage clause always come from the same declaration, or `undefined` when the file declares no such head. |
@@ -577,7 +577,9 @@ keyed `${keyword} ${name}`. One collector is what keeps a body and a heritage cl
577
577
  declaration. The identifier is the head's own run up to its generic parameter list or its heritage
578
578
  clause, so it enters the key as literal text: a name carrying a regex metacharacter reaches no
579
579
  `RegExp`, and a lookup of that name matches the character rather than a wildcard. A head that opens
580
- no column-zero close records nothing, and a later head of a key already collected adds nothing.
580
+ no column-zero close records nothing, a head whose last line closes its own body with `{}` or
581
+ `{ }` records an empty body, and a later head of a key already collected adds nothing unless the
582
+ earlier head recorded neither a body nor bases, which the later head then replaces.
581
583
  `extractDeclaration` is the named lookup over that map: it spells the `${keyword} ${name}` key so a
582
584
  consumer reading one name never writes that convention, and it collects the file afresh on every
583
585
  call. A consumer reading many names from one file calls `collectDeclarations` once and reads the
@@ -592,12 +594,15 @@ reads as `Base`, and a class's `implements` clause is excluded. `Source.methods(
592
594
  located declaration's own members with those of every declaration it extends, following each base
593
595
  through the same module scope and keeping the keyword it started from — an interface chain resolves
594
596
  through interfaces, a class chain through classes, so an interface extending a name only a class
595
- declares gets nothing from it. One declaration answers for a name: the module scope's files are read
596
- in sorted key order, and the first one whose located head has a body or has bases supplies both the
597
- members and the bases; a head with neither a body nor bases does not count as declared, so an empty
598
- `export interface X {}` is skipped and the scan continues to a later file or falls through to a
599
- same-named class, and a second file declaring the same name after one is found adds nothing. The
600
- inventory is the further bound: a base the selected directories do not declare, whether it is
597
+ declares gets nothing from it. One declaration answers under each keyword: the module scope's files
598
+ are read in sorted key order, and the first one whose located head has a body or has bases supplies
599
+ both the members and the bases; a head with neither a body nor bases does not count as declared, so
600
+ an empty `export interface X {}` is skipped and the scan continues to a later file, and a second
601
+ file declaring the same name under that keyword adds nothing. The interface answers for a name, and
602
+ the class answers when no interface declares it. Where one file declares a name as both an interface
603
+ and a class, which TypeScript merges into one type, both answer: each shape's own members come ahead
604
+ of inherited ones, the interface's own first. Shapes from two files stay separate declarations, and
605
+ Parity lets the class of a merge the barrel exports answer for its interface half. The inventory is the further bound: a base the selected directories do not declare, whether it is
601
606
  imported from another package or written as a qualified name such as `external.Store`, contributes
602
607
  no members and is not an error, and one visited set per call collapses a cycle and a diamond to a
603
608
  single visit. `Source.examples(name)` is deliberately asymmetric with it — it reads only the named
@@ -496,7 +496,7 @@
496
496
  "storage": "claude/agents/orkestrel.md",
497
497
  "destination": ".claude/agents/orkestrel.md",
498
498
  "executable": false,
499
- "digest": "6b3af62e323ff6725680f6a13c3505904154e1d2c0afcb8ba7151a00ff5875c2"
499
+ "digest": "02c8bfcfa26ee23e91cef48771e5a3038bdb8d4018f0510bef5cd17db82f5893"
500
500
  },
501
501
  {
502
502
  "storage": "claude/agents/planner.md",
@@ -538,7 +538,7 @@
538
538
  "storage": "claude/rules/architecture.md",
539
539
  "destination": ".claude/rules/architecture.md",
540
540
  "executable": false,
541
- "digest": "e30f034c8de38ce7c86506dfaa51844201d745181caed45ef427d0325e80afd3"
541
+ "digest": "6d8936e85dd3c01e915a91a5b7bb9aaafa9b09a899b0cf8ff6bb8f9e8ff8e629"
542
542
  },
543
543
  {
544
544
  "storage": "claude/rules/browser.md",
@@ -556,7 +556,7 @@
556
556
  "storage": "claude/rules/names.md",
557
557
  "destination": ".claude/rules/names.md",
558
558
  "executable": false,
559
- "digest": "1c9aeda4a47d94ef174e1f513da7b2710fe2a162e56a450270572ad942c95a7d"
559
+ "digest": "6606f4f8b219c6631029235f4081f29b055952a635e76477dfd6febdbe61afe2"
560
560
  },
561
561
  {
562
562
  "storage": "claude/rules/patterns.md",
@@ -772,7 +772,7 @@
772
772
  "storage": "configs/policy.ts",
773
773
  "destination": "configs/policy.ts",
774
774
  "executable": false,
775
- "digest": "2ba8395040b41b30245a0e09de3a87b10546f29eab359a2884b5cde76aff4fc8"
775
+ "digest": "de238996682d2eb35a40941214dc5c1d6427c57159afc3055f243bf93a5e8f83"
776
776
  },
777
777
  {
778
778
  "storage": "cursor/mcp.json",
@@ -826,7 +826,7 @@
826
826
  "storage": "dotfiles/oxlintrc.json",
827
827
  "destination": ".oxlintrc.json",
828
828
  "executable": false,
829
- "digest": "8deab17f02176cb061f4c80a068eead4ac44ffc62cbb388587a962dc6afd8431"
829
+ "digest": "043a5c3eb0fb52d6cd841590e77c94ce03797b4b6a742fd57127bc9b9242153c"
830
830
  },
831
831
  {
832
832
  "storage": "dotfiles/prettierignore",
@@ -916,7 +916,7 @@
916
916
  "storage": "guides/guide.md",
917
917
  "destination": "guides/guide.md",
918
918
  "executable": false,
919
- "digest": "1cdcd2e03b005cd8e7e12319f083ef31847a96682b4f9e33a084ea249c6a857c"
919
+ "digest": "4749d6a2a7da1d8d89a8c3ee3b60c5f21fcfb112074db80d7b1a2edcdef93afd"
920
920
  },
921
921
  {
922
922
  "storage": "guides/html.md",
@@ -1168,7 +1168,7 @@
1168
1168
  "storage": "tests/config.test.ts",
1169
1169
  "destination": "tests/config.test.ts",
1170
1170
  "executable": false,
1171
- "digest": "6550d3edb31603777ce713985b6648063e105c1ce2bee82c66570382faed7c67"
1171
+ "digest": "395ac8782ae05c678c13e460de654e32140125f159fcb882053340ab193bb5f3"
1172
1172
  },
1173
1173
  {
1174
1174
  "storage": "tests/policy.test.ts",
@@ -2144,5 +2144,5 @@
2144
2144
  ]
2145
2145
  }
2146
2146
  ],
2147
- "digest": "ccbd92a2462e0a17fb972b7582bf05e6deec0c1f11cf4d2d17e6ebec5cc431bf"
2147
+ "digest": "5c7e45000c69dd7340b13478851fcfaeaa972f3f25311e230def9b740dc82e15"
2148
2148
  }
File without changes
File without changes
File without changes
File without changes