@metaobjectsdev/sdk 0.23.2 → 0.24.1

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.
Files changed (75) hide show
  1. package/agent-context/skills/metaobjects-audit/SKILL.md +20 -7
  2. package/agent-context/skills/metaobjects-audit/references/capability-checklist.md +65 -19
  3. package/agent-context/skills/metaobjects-audit/references/csharp.md +2 -1
  4. package/agent-context/skills/metaobjects-audit/references/java.md +3 -2
  5. package/agent-context/skills/metaobjects-audit/references/kotlin.md +2 -1
  6. package/agent-context/skills/metaobjects-audit/references/requirements.md +3 -4
  7. package/agent-context/skills/metaobjects-audit/references/typescript.md +1 -1
  8. package/agent-context/skills/metaobjects-authoring/SKILL.md +53 -10
  9. package/agent-context/skills/metaobjects-authoring/references/requirements.md +49 -23
  10. package/agent-context/skills/metaobjects-codegen/SKILL.md +4 -3
  11. package/agent-context/skills/metaobjects-codegen/references/csharp.md +12 -2
  12. package/agent-context/skills/metaobjects-codegen/references/java.md +3 -3
  13. package/agent-context/skills/metaobjects-codegen/references/kotlin.md +4 -4
  14. package/agent-context/skills/metaobjects-codegen/references/python.md +12 -2
  15. package/agent-context/skills/metaobjects-codegen/references/typescript.md +14 -3
  16. package/agent-context/skills/metaobjects-fit-assessment/SKILL.md +4 -4
  17. package/agent-context/skills/metaobjects-prompts/SKILL.md +53 -32
  18. package/agent-context/skills/metaobjects-prompts/references/csharp.md +26 -21
  19. package/agent-context/skills/metaobjects-prompts/references/java.md +24 -18
  20. package/agent-context/skills/metaobjects-prompts/references/kotlin.md +25 -20
  21. package/agent-context/skills/metaobjects-prompts/references/python.md +68 -26
  22. package/agent-context/skills/metaobjects-prompts/references/typescript.md +29 -23
  23. package/agent-context/skills/metaobjects-verify/references/migration.md +1 -1
  24. package/agent-context/skills/metaobjects-verify/references/requirements.md +36 -22
  25. package/agent-context/templates/always-on.md.mustache +3 -1
  26. package/dist/agent-docs/body.d.ts +1 -1
  27. package/dist/agent-docs/body.d.ts.map +1 -1
  28. package/dist/agent-docs/body.js +24 -2
  29. package/dist/agent-docs/body.js.map +1 -1
  30. package/dist/collection.d.ts +71 -0
  31. package/dist/collection.d.ts.map +1 -0
  32. package/dist/collection.js +203 -0
  33. package/dist/collection.js.map +1 -0
  34. package/dist/config.d.ts +51 -21
  35. package/dist/config.d.ts.map +1 -1
  36. package/dist/config.js +76 -10
  37. package/dist/config.js.map +1 -1
  38. package/dist/discovery.d.ts +43 -0
  39. package/dist/discovery.d.ts.map +1 -0
  40. package/dist/discovery.js +100 -0
  41. package/dist/discovery.js.map +1 -0
  42. package/dist/index.d.ts +10 -1
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js +17 -4
  45. package/dist/index.js.map +1 -1
  46. package/dist/memory.d.ts +42 -19
  47. package/dist/memory.d.ts.map +1 -1
  48. package/dist/memory.js +38 -95
  49. package/dist/memory.js.map +1 -1
  50. package/dist/metadata-files.d.ts +67 -0
  51. package/dist/metadata-files.d.ts.map +1 -0
  52. package/dist/metadata-files.js +155 -0
  53. package/dist/metadata-files.js.map +1 -0
  54. package/dist/records/any.d.ts +8 -8
  55. package/dist/records/decision.d.ts +2 -2
  56. package/dist/records/glossary.d.ts +4 -4
  57. package/dist/records/principle.d.ts +2 -2
  58. package/dist/scope.d.ts +16 -0
  59. package/dist/scope.d.ts.map +1 -0
  60. package/dist/scope.js +75 -0
  61. package/dist/scope.js.map +1 -0
  62. package/dist/sources.d.ts +86 -0
  63. package/dist/sources.d.ts.map +1 -0
  64. package/dist/sources.js +130 -0
  65. package/dist/sources.js.map +1 -0
  66. package/package.json +2 -2
  67. package/src/agent-docs/body.ts +24 -2
  68. package/src/collection.ts +268 -0
  69. package/src/config.ts +80 -12
  70. package/src/discovery.ts +110 -0
  71. package/src/index.ts +26 -9
  72. package/src/memory.ts +65 -99
  73. package/src/metadata-files.ts +158 -0
  74. package/src/scope.ts +95 -0
  75. package/src/sources.ts +160 -0
@@ -0,0 +1,155 @@
1
+ // server/typescript/packages/sdk/src/metadata-files.ts
2
+ //
3
+ // The project's default directory names, what counts as a metadata file, and
4
+ // the one walk that turns a directory into an ordered file list.
5
+ //
6
+ // **This module imports nothing from its siblings, and that is the point.**
7
+ // `resolveCollection` (`collection.ts`) is the single authority on where
8
+ // metadata lives, so `memory.ts`'s `loadMemory` must call it — while
9
+ // `collection.ts` and `sources.ts` need the constants and the walk below.
10
+ // Homing those in `memory.ts` closes an ESM cycle whose failure mode is not a
11
+ // warning but a crash: `DEFAULT_SOURCES` (`sources.ts`) reads
12
+ // `DEFAULT_METADATA_DIR` at module top level, so the cycle surfaces as
13
+ // `ReferenceError: Cannot access 'DEFAULT_METADATA_DIR' before initialization`
14
+ // on whichever module the entry point happens to reach first. A leaf both
15
+ // sides import is the fix; a lazy `await import()` inside `loadMemory` is not
16
+ // — that hides the cycle rather than removing it.
17
+ import { extname, join } from "node:path";
18
+ import { readdir, realpath, stat } from "node:fs/promises";
19
+ /**
20
+ * The DEFAULT value of `sources` — the directory scanned when
21
+ * `.metaobjects/config.json` declares no sources. Scaffold via `meta init`;
22
+ * the directory is committed to git.
23
+ *
24
+ * **A default, and nothing else.** No read path may assume a directory of this
25
+ * name exists or is where metadata lives: that question is answered by
26
+ * `resolveCollection`, which applies this constant exactly once (via
27
+ * `DEFAULT_SOURCES` in `sources.ts`) when a project declares nothing. A
28
+ * project that declares `sources` may put its metadata anywhere, and every
29
+ * command follows the config. `test/no-hardcoded-metadata-dir.test.ts` is the
30
+ * enforcer.
31
+ */
32
+ export const DEFAULT_METADATA_DIR = "metaobjects";
33
+ /**
34
+ * Default directory name (relative to project root) for MetaObjects' own
35
+ * runtime state: config.json, .gen-state/, package.meta.json, agent docs.
36
+ * Scaffold via `meta init`; most contents are committed to git.
37
+ *
38
+ * Unlike {@link DEFAULT_METADATA_DIR} this one IS a fixed convention — it is
39
+ * where the config that answers "where is the metadata?" lives, so it cannot
40
+ * itself be configured.
41
+ */
42
+ export const DEFAULT_METAOBJECTS_DIR = ".metaobjects";
43
+ /** Recognized metadata file extensions, matched case-insensitively — mirrors
44
+ * `DirectorySource` in `@metaobjectsdev/metadata`, which checks
45
+ * `extname().toLowerCase()`. The single definition every metadata-file
46
+ * walker in this package uses — and since `resolveSources` (`sources.ts`)
47
+ * calls {@link listMetadataFiles} outright rather than keeping a second
48
+ * recursive walk of its own, there is exactly one walker to keep honest. */
49
+ export const METADATA_EXTENSIONS = new Set([".json", ".yaml", ".yml"]);
50
+ export function isMetadataFile(name) {
51
+ return METADATA_EXTENSIONS.has(extname(name).toLowerCase());
52
+ }
53
+ /** Directory excluded at every level of {@link listMetadataFiles} — drafts
54
+ * that are deliberately not part of the loaded model. */
55
+ const PENDING_DIR = "_pending";
56
+ /**
57
+ * Recursively list metadata files (*.json, *.yaml, *.yml, matched
58
+ * case-insensitively — see `isMetadataFile` above) under a directory,
59
+ * excluding _pending/ at any level. Subdirectories (e.g. projections/) are
60
+ * walked depth-first. Files within a directory are sorted alphabetically for
61
+ * deterministic load order; subdirectories are visited AFTER the files at the
62
+ * same level.
63
+ *
64
+ * That per-level rule is a contract, not an implementation detail. This is the
65
+ * order production has always handed the loader, and declaration order survives
66
+ * into generated output: `codegen-ts`'s barrel emits from `root.objects()`
67
+ * order, and so do the shared `enums.ts`, `meta docs` page ordering and `meta
68
+ * export`'s `canonicalSerialize` sibling order. A flat lexicographic sort of
69
+ * absolute paths is NOT the same list — it disagrees whenever a subdirectory
70
+ * name sorts before a sibling file (`common/` before `meta.users.json`) — so
71
+ * `resolveSources` calls this function rather than re-walking and re-sorting.
72
+ * Pinned by `test/source-order.test.ts`.
73
+ *
74
+ * Exported for that gate and for `sources.ts`; not re-exported from the package
75
+ * index — `resolveCollection` is the public door.
76
+ *
77
+ * An entry whose `stat` fails (a dangling symlink, a TOCTOU removal between
78
+ * `readdir` and `stat`, an EACCES entry) is SKIPPED, matching `DirectorySource`
79
+ * in `@metaobjectsdev/metadata`, which this walk otherwise mirrors. A failure to
80
+ * read the directory itself still throws — that is the "you have no metadata
81
+ * here" case callers report.
82
+ *
83
+ * A symlink CYCLE (e.g. `metaobjects/link -> ..`) is a loud error rather than
84
+ * unbounded recursion — see {@link listMetadataFilesGuarded}.
85
+ *
86
+ * Format selection (parsing) happens downstream in `FileSource` from
87
+ * `@metaobjectsdev/metadata`, which infers the parser from file extension.
88
+ */
89
+ export async function listMetadataFiles(dir) {
90
+ return listMetadataFilesGuarded(dir, new Set());
91
+ }
92
+ /**
93
+ * {@link listMetadataFiles}'s recursive worker, carrying the REAL (symlink-
94
+ * resolved) ancestor directories already on this walk branch.
95
+ *
96
+ * This walk follows symlinked directories on purpose (`stat`, not `lstat`,
97
+ * below — matching `DirectorySource` in `@metaobjectsdev/metadata`), so an
98
+ * unguarded directory symlink that revisits an ancestor recurses forever:
99
+ * Java and Python both added this exact guard when this PR promoted
100
+ * symlink-following to a cross-port contract; the TypeScript reference itself
101
+ * did not, even though the corpus cites it as authoritative. `ancestors` is
102
+ * extended only on the recursive call (never mutated in place), so it
103
+ * reflects the current branch, not siblings visited earlier at the same
104
+ * level — a directory legitimately reachable via two different symlinked
105
+ * paths (not a cycle) is not falsely rejected.
106
+ */
107
+ async function listMetadataFilesGuarded(dir, ancestors) {
108
+ // `realpath` failing (e.g. `dir` vanished between being listed and now) is
109
+ // not this guard's problem — fall back to the given path and let `readdir`
110
+ // below raise its own coded error.
111
+ const real = await realpath(dir).catch(() => dir);
112
+ if (ancestors.has(real)) {
113
+ throw new Error(`symlink loop detected while expanding metadata directory: ${dir} revisits ${real}`);
114
+ }
115
+ const nextAncestors = new Set(ancestors);
116
+ nextAncestors.add(real);
117
+ let entries;
118
+ try {
119
+ entries = await readdir(dir);
120
+ }
121
+ catch (err) {
122
+ throw new Error(`cannot read metadata directory ${dir}: ${err.message}`);
123
+ }
124
+ const paths = [];
125
+ const subdirs = [];
126
+ // #188: sort the raw `readdir` entries so file order is deterministic across
127
+ // runtimes/filesystems (Node vs Bun return different `readdir` orders), matching
128
+ // this function's docstring and the metadata package's own `DirectorySource`.
129
+ // (Resolution is now order-INDEPENDENT — super-resolve.ts #188 — so this is the
130
+ // deterministic-enumeration FLOOR, not the fix; it keeps every derived artifact
131
+ // that preserves declaration order, e.g. serialization, stable across runtimes.)
132
+ for (const entry of [...entries].sort()) {
133
+ if (entry === PENDING_DIR)
134
+ continue;
135
+ const full = join(dir, entry);
136
+ // `stat` (not `lstat`/`Dirent.isDirectory()`) so a symlinked subdirectory is
137
+ // traversed — `DirectorySource` has always followed symlinks this way.
138
+ const s = await stat(full).catch(() => undefined);
139
+ if (s === undefined)
140
+ continue;
141
+ if (s.isDirectory()) {
142
+ subdirs.push(full);
143
+ }
144
+ else if (s.isFile() && isMetadataFile(entry)) {
145
+ paths.push(full);
146
+ }
147
+ }
148
+ // Recurse into subdirectories after collecting files at this level.
149
+ // `subdirs` is already in sorted order (built from the sorted `entries` above).
150
+ for (const sub of subdirs) {
151
+ paths.push(...(await listMetadataFilesGuarded(sub, nextAncestors)));
152
+ }
153
+ return paths;
154
+ }
155
+ //# sourceMappingURL=metadata-files.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metadata-files.js","sourceRoot":"","sources":["../src/metadata-files.ts"],"names":[],"mappings":"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"}
@@ -127,6 +127,7 @@ export declare const AnyRecord: z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
127
127
  scope: z.ZodUnion<[z.ZodLiteral<"global">, z.ZodArray<z.ZodString, "many">]>;
128
128
  }, "strip", z.ZodTypeAny, {
129
129
  type: "decision";
130
+ scope: string[] | "global";
130
131
  schema_version: 1;
131
132
  id: string;
132
133
  title: string;
@@ -141,7 +142,6 @@ export declare const AnyRecord: z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
141
142
  }[];
142
143
  rationale: string;
143
144
  alternatives_considered: string[];
144
- scope: string[] | "global";
145
145
  superseded_by?: string | undefined;
146
146
  evidence?: {
147
147
  commits?: string[] | undefined;
@@ -150,6 +150,7 @@ export declare const AnyRecord: z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
150
150
  } | undefined;
151
151
  }, {
152
152
  type: "decision";
153
+ scope: string[] | "global";
153
154
  schema_version: 1;
154
155
  id: string;
155
156
  title: string;
@@ -159,7 +160,6 @@ export declare const AnyRecord: z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
159
160
  last_validated_against_commit: string;
160
161
  rationale: string;
161
162
  alternatives_considered: string[];
162
- scope: string[] | "global";
163
163
  superseded_by?: string | undefined;
164
164
  evidence?: {
165
165
  commits?: string[] | undefined;
@@ -216,6 +216,7 @@ export declare const AnyRecord: z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
216
216
  enforcement: z.ZodDefault<z.ZodEnum<["advisory", "block"]>>;
217
217
  }, "strip", z.ZodTypeAny, {
218
218
  type: "principle";
219
+ scope: string[];
219
220
  schema_version: 1;
220
221
  id: string;
221
222
  title: string;
@@ -230,7 +231,6 @@ export declare const AnyRecord: z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
230
231
  }[];
231
232
  examples: string[];
232
233
  rationale: string;
233
- scope: string[];
234
234
  statement: string;
235
235
  counter_examples: string[];
236
236
  enforcement: "advisory" | "block";
@@ -242,6 +242,7 @@ export declare const AnyRecord: z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
242
242
  } | undefined;
243
243
  }, {
244
244
  type: "principle";
245
+ scope: string[];
245
246
  schema_version: 1;
246
247
  id: string;
247
248
  title: string;
@@ -251,7 +252,6 @@ export declare const AnyRecord: z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
251
252
  last_validated_against_commit: string;
252
253
  examples: string[];
253
254
  rationale: string;
254
- scope: string[];
255
255
  statement: string;
256
256
  counter_examples: string[];
257
257
  superseded_by?: string | undefined;
@@ -310,11 +310,11 @@ export declare const AnyRecord: z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
310
310
  entity: z.ZodOptional<z.ZodString>;
311
311
  files: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
312
312
  }, "strip", z.ZodTypeAny, {
313
- entity?: string | undefined;
314
313
  files?: string[] | undefined;
315
- }, {
316
314
  entity?: string | undefined;
315
+ }, {
317
316
  files?: string[] | undefined;
317
+ entity?: string | undefined;
318
318
  }>;
319
319
  see_also: z.ZodArray<z.ZodString, "many">;
320
320
  }, "strip", z.ZodTypeAny, {
@@ -335,8 +335,8 @@ export declare const AnyRecord: z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
335
335
  synonyms: string[];
336
336
  definition: string;
337
337
  code_anchors: {
338
- entity?: string | undefined;
339
338
  files?: string[] | undefined;
339
+ entity?: string | undefined;
340
340
  };
341
341
  see_also: string[];
342
342
  superseded_by?: string | undefined;
@@ -358,8 +358,8 @@ export declare const AnyRecord: z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
358
358
  synonyms: string[];
359
359
  definition: string;
360
360
  code_anchors: {
361
- entity?: string | undefined;
362
361
  files?: string[] | undefined;
362
+ entity?: string | undefined;
363
363
  };
364
364
  see_also: string[];
365
365
  superseded_by?: string | undefined;
@@ -41,6 +41,7 @@ export declare const DecisionRecord: z.ZodObject<{
41
41
  scope: z.ZodUnion<[z.ZodLiteral<"global">, z.ZodArray<z.ZodString, "many">]>;
42
42
  }, "strip", z.ZodTypeAny, {
43
43
  type: "decision";
44
+ scope: string[] | "global";
44
45
  schema_version: 1;
45
46
  id: string;
46
47
  title: string;
@@ -55,7 +56,6 @@ export declare const DecisionRecord: z.ZodObject<{
55
56
  }[];
56
57
  rationale: string;
57
58
  alternatives_considered: string[];
58
- scope: string[] | "global";
59
59
  superseded_by?: string | undefined;
60
60
  evidence?: {
61
61
  commits?: string[] | undefined;
@@ -64,6 +64,7 @@ export declare const DecisionRecord: z.ZodObject<{
64
64
  } | undefined;
65
65
  }, {
66
66
  type: "decision";
67
+ scope: string[] | "global";
67
68
  schema_version: 1;
68
69
  id: string;
69
70
  title: string;
@@ -73,7 +74,6 @@ export declare const DecisionRecord: z.ZodObject<{
73
74
  last_validated_against_commit: string;
74
75
  rationale: string;
75
76
  alternatives_considered: string[];
76
- scope: string[] | "global";
77
77
  superseded_by?: string | undefined;
78
78
  evidence?: {
79
79
  commits?: string[] | undefined;
@@ -43,11 +43,11 @@ export declare const GlossaryRecord: z.ZodObject<{
43
43
  entity: z.ZodOptional<z.ZodString>;
44
44
  files: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
45
45
  }, "strip", z.ZodTypeAny, {
46
- entity?: string | undefined;
47
46
  files?: string[] | undefined;
48
- }, {
49
47
  entity?: string | undefined;
48
+ }, {
50
49
  files?: string[] | undefined;
50
+ entity?: string | undefined;
51
51
  }>;
52
52
  see_also: z.ZodArray<z.ZodString, "many">;
53
53
  }, "strip", z.ZodTypeAny, {
@@ -68,8 +68,8 @@ export declare const GlossaryRecord: z.ZodObject<{
68
68
  synonyms: string[];
69
69
  definition: string;
70
70
  code_anchors: {
71
- entity?: string | undefined;
72
71
  files?: string[] | undefined;
72
+ entity?: string | undefined;
73
73
  };
74
74
  see_also: string[];
75
75
  superseded_by?: string | undefined;
@@ -91,8 +91,8 @@ export declare const GlossaryRecord: z.ZodObject<{
91
91
  synonyms: string[];
92
92
  definition: string;
93
93
  code_anchors: {
94
- entity?: string | undefined;
95
94
  files?: string[] | undefined;
95
+ entity?: string | undefined;
96
96
  };
97
97
  see_also: string[];
98
98
  superseded_by?: string | undefined;
@@ -44,6 +44,7 @@ export declare const PrincipleRecord: z.ZodObject<{
44
44
  enforcement: z.ZodDefault<z.ZodEnum<["advisory", "block"]>>;
45
45
  }, "strip", z.ZodTypeAny, {
46
46
  type: "principle";
47
+ scope: string[];
47
48
  schema_version: 1;
48
49
  id: string;
49
50
  title: string;
@@ -58,7 +59,6 @@ export declare const PrincipleRecord: z.ZodObject<{
58
59
  }[];
59
60
  examples: string[];
60
61
  rationale: string;
61
- scope: string[];
62
62
  statement: string;
63
63
  counter_examples: string[];
64
64
  enforcement: "advisory" | "block";
@@ -70,6 +70,7 @@ export declare const PrincipleRecord: z.ZodObject<{
70
70
  } | undefined;
71
71
  }, {
72
72
  type: "principle";
73
+ scope: string[];
73
74
  schema_version: 1;
74
75
  id: string;
75
76
  title: string;
@@ -79,7 +80,6 @@ export declare const PrincipleRecord: z.ZodObject<{
79
80
  last_validated_against_commit: string;
80
81
  examples: string[];
81
82
  rationale: string;
82
- scope: string[];
83
83
  statement: string;
84
84
  counter_examples: string[];
85
85
  superseded_by?: string | undefined;
@@ -0,0 +1,16 @@
1
+ /** A consumer-side output filter over fully-qualified node names. */
2
+ export interface Scope {
3
+ /** Absent or empty means "everything". */
4
+ readonly include?: readonly string[];
5
+ /** Applied after `include`. */
6
+ readonly exclude?: readonly string[];
7
+ }
8
+ export interface CompiledScope {
9
+ readonly include: readonly RegExp[];
10
+ readonly exclude: readonly RegExp[];
11
+ }
12
+ export declare function compilePattern(pattern: string): RegExp;
13
+ export declare function compileScope(scope: Scope): CompiledScope;
14
+ /** True when `fqn` is inside the scope. An empty `include` means everything. */
15
+ export declare function matchesScope(fqn: string, compiled: CompiledScope): boolean;
16
+ //# sourceMappingURL=scope.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scope.d.ts","sourceRoot":"","sources":["../src/scope.ts"],"names":[],"mappings":"AAkBA,qEAAqE;AACrE,MAAM,WAAW,KAAK;IACpB,0CAA0C;IAC1C,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,+BAA+B;IAC/B,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACtC;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC;AAuCD,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAYtD;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,KAAK,GAAG,aAAa,CAKxD;AAED,gFAAgF;AAChF,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,GAAG,OAAO,CAI1E"}
package/dist/scope.js ADDED
@@ -0,0 +1,75 @@
1
+ // server/typescript/packages/sdk/src/scope.ts
2
+ //
3
+ // Phase-1 metadata-source-resolution — the scope pattern engine.
4
+ //
5
+ // A pure, no-I/O module deciding whether a fully-qualified node name falls
6
+ // inside a consumer's declared `include`/`exclude` scope. Source resolution
7
+ // and discovery (later phase-1 tasks) build on this; a cross-language
8
+ // conformance corpus pins its semantics, so exact pattern behavior matters.
9
+ // NOTE: PACKAGE_SEPARATOR is NOT re-exported from the browser-safe
10
+ // `@metaobjectsdev/metadata/constants` barrel (that barrel only re-exports
11
+ // the per-concern `*-constants.ts` modules; `PACKAGE_SEPARATOR` lives in
12
+ // `shared/structural.ts`, exported from the package root). This package
13
+ // (`@metaobjectsdev/sdk`) is server-side, not a `client/web/**` browser
14
+ // package, so importing metamodel values from the root — the same thing
15
+ // `memory.ts` and `forge-types.ts` in this package already do — is correct.
16
+ import { PACKAGE_SEPARATOR, ParseError, codeSource } from "@metaobjectsdev/metadata";
17
+ /** One package segment: any run of characters containing no separator char. */
18
+ const SEGMENT = "[^:]+";
19
+ /** One or more segments, separator-joined — the `**` expansion. */
20
+ const SEGMENTS = `${SEGMENT}(?:${PACKAGE_SEPARATOR}${SEGMENT})*`;
21
+ function escapeLiteral(text) {
22
+ return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
23
+ }
24
+ /** Compile one segment. `**` spans segments; `*` never crosses a separator. */
25
+ function compileSegment(segment, pattern) {
26
+ if (segment.length === 0) {
27
+ throw new ParseError(`empty segment in scope pattern "${pattern}"`, {
28
+ code: "ERR_SCOPE_PATTERN_INVALID",
29
+ source: codeSource("compileSegment"),
30
+ });
31
+ }
32
+ // A segment surviving the split on the two-character PACKAGE_SEPARATOR
33
+ // ("::") can still contain a lone ":" when the pattern has an odd colon
34
+ // run — e.g. "acme:::Order".split("::") => ["acme", ":Order"]. SEGMENT
35
+ // ([^:]+) already excludes ":" from a well-formed segment, so a leftover
36
+ // ":" here means the separator was malformed, not that ":" is meant
37
+ // literally. Left unchecked, escapeLiteral treats it as a literal
38
+ // character and compiles a regex requiring three colons in a row — which
39
+ // no legal "::"-joined fully-qualified name can ever contain, so the
40
+ // pattern silently matches nothing instead of failing loud.
41
+ if (segment.includes(":")) {
42
+ throw new ParseError(`scope pattern "${pattern}" has a malformed separator (an odd run of ":") — segments are joined by "::", never a single ":"`, { code: "ERR_SCOPE_PATTERN_INVALID", source: codeSource("compileSegment") });
43
+ }
44
+ if (segment === "**")
45
+ return `(?:${SEGMENTS})`;
46
+ // `*` inside a segment matches any characters except the separator char.
47
+ return segment.split("*").map(escapeLiteral).join("[^:]*");
48
+ }
49
+ export function compilePattern(pattern) {
50
+ if (pattern.length === 0) {
51
+ throw new ParseError(`scope pattern must not be empty`, {
52
+ code: "ERR_SCOPE_PATTERN_INVALID",
53
+ source: codeSource("compilePattern"),
54
+ });
55
+ }
56
+ const body = pattern
57
+ .split(PACKAGE_SEPARATOR)
58
+ .map((segment) => compileSegment(segment, pattern))
59
+ .join(PACKAGE_SEPARATOR);
60
+ return new RegExp(`^${body}$`);
61
+ }
62
+ export function compileScope(scope) {
63
+ return {
64
+ include: (scope.include ?? []).map(compilePattern),
65
+ exclude: (scope.exclude ?? []).map(compilePattern),
66
+ };
67
+ }
68
+ /** True when `fqn` is inside the scope. An empty `include` means everything. */
69
+ export function matchesScope(fqn, compiled) {
70
+ const included = compiled.include.length === 0 || compiled.include.some((re) => re.test(fqn));
71
+ if (!included)
72
+ return false;
73
+ return !compiled.exclude.some((re) => re.test(fqn));
74
+ }
75
+ //# sourceMappingURL=scope.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scope.js","sourceRoot":"","sources":["../src/scope.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,86 @@
1
+ /** Tagged union of source kinds. `resource` and `package` are declared now so
2
+ * the config shape is stable across phases; only `path` resolves in phase 1 —
3
+ * `resource`/`package` throw `ERR_SOURCE_KIND_UNSUPPORTED`. */
4
+ export type SourceSpec = {
5
+ readonly path: string;
6
+ } | {
7
+ readonly resource: string;
8
+ } | {
9
+ readonly package: string;
10
+ };
11
+ export interface ResolvedSource {
12
+ /** Absolute path of one metadata file. */
13
+ readonly file: string;
14
+ /** The spec that contributed it — provenance for diagnostics. */
15
+ readonly spec: SourceSpec;
16
+ }
17
+ /** Used when `sources` is absent or empty in `.metaobjects/config.json`. A
18
+ * DEFAULT, never a requirement — a project that declares `sources` explicitly
19
+ * need not include the default directory at all. Built from
20
+ * `DEFAULT_METADATA_DIR` (`metadata-files.ts`'s single definition) rather than
21
+ * restating that name here: a second independent encoding of the same
22
+ * default would let `resolveCollection`'s "does the default dir exist"
23
+ * check (`collection.ts`) desync from what `resolveSources` actually
24
+ * resolves the moment the default ever changed — silently reproducing the
25
+ * "two code paths disagree about where metadata lives" class of bug this
26
+ * whole mechanism exists to eliminate. */
27
+ export declare const DEFAULT_SOURCES: readonly SourceSpec[];
28
+ /**
29
+ * The declared source SET in CANONICAL order — kind-validated, then sorted by
30
+ * spec CONTENT rather than by declaration order.
31
+ *
32
+ * This is the ONE place declaration order is discarded, and the module's "pure
33
+ * function of the SET" invariant rests on it: the emitted file order, the spec
34
+ * attributed to a file two specs both reach, and which of several unresolvable
35
+ * paths reports its `ERR_SOURCE_UNRESOLVED` first are all decided here.
36
+ * Validation runs across the WHOLE list before any sorting or filesystem I/O —
37
+ * interleaved with resolution, which error code came back would depend on
38
+ * declaration order, contradicting that same invariant.
39
+ *
40
+ * Exported because `resolveCollection` derives `sourceRoots` from the declared
41
+ * specs and must use this identical ordering; a second sort would be a second
42
+ * definition of "canonical".
43
+ */
44
+ export declare function orderedPathSpecs(specs: readonly SourceSpec[]): {
45
+ readonly path: string;
46
+ }[];
47
+ /**
48
+ * Where a declared `path` source lives on disk: absolute as written, otherwise
49
+ * relative to the DECLARING config's directory — never to ambient
50
+ * `process.cwd()`.
51
+ *
52
+ * One definition, because this expression *is* the rule for where a declared
53
+ * source lives, which is the single piece of knowledge this module exists to
54
+ * own. A caller that needs a source's root directory (rather than its files)
55
+ * calls this rather than restating it.
56
+ */
57
+ export declare function resolveSpecPath(configDir: string, spec: {
58
+ readonly path: string;
59
+ }): string;
60
+ /**
61
+ * Resolve a declared source SET to a canonically-ordered list of metadata files.
62
+ *
63
+ * The full result — each entry's `.file` AND its `.spec` — is a pure function
64
+ * of the SET of `specs`: permuting `specs` cannot change the output. One thing
65
+ * makes that hold: the specs are processed in CONTENT order
66
+ * (`JSON.stringify(spec)`, ascending) rather than declared order, so both the
67
+ * emitted file order and the spec attributed to a file overlapping two specs
68
+ * are decided by content alone. Declared order carries no information anywhere
69
+ * in this function.
70
+ *
71
+ * Within one directory spec the file order is `listMetadataFiles`'s — files at
72
+ * a level, then that level's subdirectories, depth-first. That is deliberately
73
+ * NOT a flat sort of absolute paths: see the file header, and
74
+ * `test/source-order.test.ts`.
75
+ *
76
+ * Only `path` specs resolve in phase 1: a directory is walked recursively, a
77
+ * file is taken as-is. An unresolvable `path` throws `ERR_SOURCE_UNRESOLVED`
78
+ * rather than silently contributing nothing; `resource`/`package` specs throw
79
+ * `ERR_SOURCE_KIND_UNSUPPORTED`.
80
+ *
81
+ * @param configDir absolute directory of the declaring config (the parent of
82
+ * `.metaobjects/`) — relative `path` specs resolve against it, never against
83
+ * ambient `process.cwd()`.
84
+ */
85
+ export declare function resolveSources(configDir: string, specs: readonly SourceSpec[]): Promise<ResolvedSource[]>;
86
+ //# sourceMappingURL=sources.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sources.d.ts","sourceRoot":"","sources":["../src/sources.ts"],"names":[],"mappings":"AA2BA;;gEAEgE;AAChE,MAAM,MAAM,UAAU,GAClB;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACzB;IAAE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAC7B;IAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEjC,MAAM,WAAW,cAAc;IAC7B,0CAA0C;IAC1C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,iEAAiE;IACjE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;CAC3B;AAED;;;;;;;;;2CAS2C;AAC3C,eAAO,MAAM,eAAe,EAAE,SAAS,UAAU,EAAqC,CAAC;AAgBvF;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,SAAS,UAAU,EAAE,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,CAK1F;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAE1F;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAsB,cAAc,CAClC,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,SAAS,UAAU,EAAE,GAC3B,OAAO,CAAC,cAAc,EAAE,CAAC,CA0B3B"}