depa-codument 0.4.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 (117) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +262 -0
  3. package/package.json +63 -0
  4. package/src/cli/commands/archive.ts +519 -0
  5. package/src/cli/commands/decisions.ts +123 -0
  6. package/src/cli/commands/engineering.ts +105 -0
  7. package/src/cli/commands/init.ts +54 -0
  8. package/src/cli/commands/list.ts +73 -0
  9. package/src/cli/commands/modeling.ts +105 -0
  10. package/src/cli/commands/show.ts +238 -0
  11. package/src/cli/commands/status.ts +140 -0
  12. package/src/cli/commands/upgrade-track.ts +385 -0
  13. package/src/cli/commands/upgrade-workspace.ts +138 -0
  14. package/src/cli/commands/validate.ts +330 -0
  15. package/src/cli/engineering/config.ts +68 -0
  16. package/src/cli/engineering/lint.ts +58 -0
  17. package/src/cli/engineering/merge.ts +172 -0
  18. package/src/cli/engineering/registry.ts +230 -0
  19. package/src/cli/engineering/schema.ts +126 -0
  20. package/src/cli/engineering/validate.ts +286 -0
  21. package/src/cli/index.ts +136 -0
  22. package/src/cli/modeling/config.ts +68 -0
  23. package/src/cli/modeling/lint.ts +58 -0
  24. package/src/cli/modeling/merge.ts +172 -0
  25. package/src/cli/modeling/registry.ts +229 -0
  26. package/src/cli/modeling/schema.ts +160 -0
  27. package/src/cli/modeling/validate.ts +282 -0
  28. package/src/cli/utils/index.ts +941 -0
  29. package/src/cli/utils/install.ts +291 -0
  30. package/src/cli/utils/spec-xml.ts +673 -0
  31. package/src/cli/utils/track-time.ts +75 -0
  32. package/src/cli/utils/vfs.ts +102 -0
  33. package/src/templates/codument/README.md +59 -0
  34. package/src/templates/codument/attractors/product.md +17 -0
  35. package/src/templates/codument/attractors/project.md +10 -0
  36. package/src/templates/codument/backlog/README.md +33 -0
  37. package/src/templates/codument/config/attractor-profiles.xml +31 -0
  38. package/src/templates/codument/config/engineering.xml +22 -0
  39. package/src/templates/codument/config/modeling.xml +22 -0
  40. package/src/templates/codument/config/operation-hooks.xml +55 -0
  41. package/src/templates/codument/memory/README.md +13 -0
  42. package/src/templates/codument/missions/README.md +125 -0
  43. package/src/templates/codument/sop/README.md +14 -0
  44. package/src/templates/codument/std/AGENTS.md +82 -0
  45. package/src/templates/codument/std/attractors/depa-attractor.md +572 -0
  46. package/src/templates/codument/std/attractors/knowledge-tiers.md +128 -0
  47. package/src/templates/codument/std/attractors/model-driven-docs.md +293 -0
  48. package/src/templates/codument/std/attractors/project-memory.md +48 -0
  49. package/src/templates/codument/std/docs-impl-fractal/index.md +110 -0
  50. package/src/templates/codument/std/docs-modeling-fractal/index.md +156 -0
  51. package/src/templates/codument/std/kernel-pointer.md +19 -0
  52. package/src/templates/codument/std/operations/README.md +30 -0
  53. package/src/templates/codument/std/operations/_operation-spec.md +41 -0
  54. package/src/templates/codument/std/operations/archive-mission.md +66 -0
  55. package/src/templates/codument/std/operations/archive-track.md +238 -0
  56. package/src/templates/codument/std/operations/artifact-sync.md +172 -0
  57. package/src/templates/codument/std/operations/discuss-phase.md +214 -0
  58. package/src/templates/codument/std/operations/discuss.md +87 -0
  59. package/src/templates/codument/std/operations/docs-bootstrap.md +148 -0
  60. package/src/templates/codument/std/operations/gap-loop.md +301 -0
  61. package/src/templates/codument/std/operations/impl-mission.md +167 -0
  62. package/src/templates/codument/std/operations/impl-quick.md +79 -0
  63. package/src/templates/codument/std/operations/impl-track.md +537 -0
  64. package/src/templates/codument/std/operations/migrate.md +337 -0
  65. package/src/templates/codument/std/operations/plan-mission.md +230 -0
  66. package/src/templates/codument/std/operations/plan-track-wave.md +231 -0
  67. package/src/templates/codument/std/operations/plan-track.md +579 -0
  68. package/src/templates/codument/std/operations/revise-track.md +136 -0
  69. package/src/templates/codument/std/operations/validate.md +339 -0
  70. package/src/templates/codument/std/operations/verify.md +184 -0
  71. package/src/templates/codument/std/root-agents.md +39 -0
  72. package/src/templates/codument/std/sop/questioning.md +98 -0
  73. package/src/templates/codument/std/sop/tdd.md +26 -0
  74. package/src/templates/codument/std/sop/validation.md +25 -0
  75. package/src/templates/codument/std/sop/wave-exec.md +42 -0
  76. package/src/templates/codument/std/sop/workflow.md +35 -0
  77. package/src/templates/codument/std/spec/behavior-delta.md +36 -0
  78. package/src/templates/codument/std/spec/behavior-registry.md +42 -0
  79. package/src/templates/codument/std/spec/engineering-delta.md +68 -0
  80. package/src/templates/codument/std/spec/engineering-node-schema.md +86 -0
  81. package/src/templates/codument/std/spec/engineering-registry.md +82 -0
  82. package/src/templates/codument/std/spec/flow-notation.md +93 -0
  83. package/src/templates/codument/std/spec/folder-manifest.md +99 -0
  84. package/src/templates/codument/std/spec/mission-xml-spec.md +249 -0
  85. package/src/templates/codument/std/spec/modeling-delta.md +85 -0
  86. package/src/templates/codument/std/spec/modeling-node-schema.md +183 -0
  87. package/src/templates/codument/std/spec/modeling-registry.md +49 -0
  88. package/src/templates/codument/std/spec/track-xml-spec.md +272 -0
  89. package/src/templates/codument/std/spec/xnl-format.md +301 -0
  90. package/src/templates/codument/workflows/README.md +15 -0
  91. package/src/templates/manifest.ts +177 -0
  92. package/src/templates/skills/README.md +38 -0
  93. package/src/templates/skills/codument-archive/SKILL.md +17 -0
  94. package/src/templates/skills/codument-archive-mission/SKILL.md +17 -0
  95. package/src/templates/skills/codument-archive-track/SKILL.md +17 -0
  96. package/src/templates/skills/codument-artifact-sync/SKILL.md +17 -0
  97. package/src/templates/skills/codument-code-quality-score/SKILL.md +67 -0
  98. package/src/templates/skills/codument-decision-tree/SKILL.md +40 -0
  99. package/src/templates/skills/codument-discuss/SKILL.md +17 -0
  100. package/src/templates/skills/codument-discuss-phase/SKILL.md +17 -0
  101. package/src/templates/skills/codument-docs-bootstrap/SKILL.md +17 -0
  102. package/src/templates/skills/codument-gap-loop/SKILL.md +17 -0
  103. package/src/templates/skills/codument-impl-mission/SKILL.md +17 -0
  104. package/src/templates/skills/codument-impl-quick/SKILL.md +17 -0
  105. package/src/templates/skills/codument-impl-track/SKILL.md +17 -0
  106. package/src/templates/skills/codument-implement/SKILL.md +14 -0
  107. package/src/templates/skills/codument-migrate/SKILL.md +17 -0
  108. package/src/templates/skills/codument-modeling-engineering-e2e/SKILL.md +74 -0
  109. package/src/templates/skills/codument-plan-mission/SKILL.md +17 -0
  110. package/src/templates/skills/codument-plan-track/SKILL.md +17 -0
  111. package/src/templates/skills/codument-plan-track-wave/SKILL.md +17 -0
  112. package/src/templates/skills/codument-revise-track/SKILL.md +17 -0
  113. package/src/templates/skills/codument-track/SKILL.md +14 -0
  114. package/src/templates/skills/codument-validate/SKILL.md +17 -0
  115. package/src/templates/skills/codument-verify/SKILL.md +17 -0
  116. package/src/types/text-assets.d.ts +9 -0
  117. package/src/version.ts +1 -0
@@ -0,0 +1,160 @@
1
+ import type { XnlNode, DataElementNode, TextElementNode } from 'xnl-core';
2
+ import { isDataElement, readNodeId, type ModelingRegistry } from './registry';
3
+
4
+ /**
5
+ * Node schema validation for the modeling registry: kind vocabulary, minimal
6
+ * required representations per kind, fact-source metadata, and namespaced ids.
7
+ * See std/spec/modeling-node-schema.md.
8
+ */
9
+
10
+ export const FACT_GRADES = [
11
+ 'authoritative_fact',
12
+ 'domain_canonical_event',
13
+ 'runtime_control_fact',
14
+ 'append_only_journal',
15
+ 'checkpoint_snapshot',
16
+ 'derived_projection_cache',
17
+ 'surface_view',
18
+ ] as const;
19
+
20
+ /** Kernel (cross-domain) bare-tag kinds. Shell kinds are namespaced (`plane:kind`). */
21
+ export const KERNEL_KINDS = [
22
+ 'entity',
23
+ 'object',
24
+ 'enum',
25
+ 'state-machine',
26
+ 'module',
27
+ 'capsule',
28
+ 'component',
29
+ 'port',
30
+ 'actor',
31
+ 'policy',
32
+ ] as const;
33
+
34
+ type Element = DataElementNode | TextElementNode;
35
+
36
+ function isElement(node: XnlNode | undefined): node is Element {
37
+ return Boolean(
38
+ node && typeof node === 'object' &&
39
+ ((node as Element).kind === 'DataElement' || (node as Element).kind === 'TextElement'),
40
+ );
41
+ }
42
+
43
+ function bodyChildren(node: DataElementNode): Element[] {
44
+ return (node.body ?? []).filter(isElement);
45
+ }
46
+
47
+ /** Direct child element tags (Data + Text). */
48
+ function childTags(node: DataElementNode): Set<string> {
49
+ return new Set(bodyChildren(node).map((c) => c.tag));
50
+ }
51
+
52
+ /** Whether any descendant element has the given tag. */
53
+ function hasChildDeep(node: DataElementNode, tag: string): boolean {
54
+ for (const child of bodyChildren(node)) {
55
+ if (child.tag === tag) return true;
56
+ if (isDataElement(child) && hasChildDeep(child, tag)) return true;
57
+ }
58
+ return false;
59
+ }
60
+
61
+ /**
62
+ * Whether a component declares the given IO slot, accepting either the canonical
63
+ * bare tag (`<runtime>` …) or the role-tagged compat form (`<types role="runtime">`).
64
+ * Bare tags are spec-recommended; role-tagged is accepted-but-discouraged.
65
+ */
66
+ function hasIoSlot(node: DataElementNode, slot: string): boolean {
67
+ for (const child of bodyChildren(node)) {
68
+ if (child.tag === slot) return true;
69
+ if (child.tag === 'types') {
70
+ const role = child.metadata?.['role'];
71
+ if (typeof role === 'string' && role === slot) return true;
72
+ }
73
+ }
74
+ return false;
75
+ }
76
+
77
+ function hasMeta(node: DataElementNode, key: string): boolean {
78
+ const v = node.metadata?.[key];
79
+ return v !== undefined && v !== null && v !== '';
80
+ }
81
+
82
+ function metaString(node: DataElementNode, key: string): string | undefined {
83
+ const v = node.metadata?.[key];
84
+ return typeof v === 'string' ? v : undefined;
85
+ }
86
+
87
+ export function nodeKind(node: DataElementNode): string | undefined {
88
+ return metaString(node, 'kind');
89
+ }
90
+
91
+ /** Validate one modeling node; returns a list of error messages (empty = ok). */
92
+ export function validateModelingNode(node: XnlNode): string[] {
93
+ const errors: string[] = [];
94
+ if (!isDataElement(node)) return errors; // non-element top-level content is ignored
95
+ const id = readNodeId(node);
96
+ const where = id ? `#${id}` : `<${node.tag}>`;
97
+
98
+ if (!id) {
99
+ errors.push(`${where}: node has no id (use a namespaced #<context>.<name>)`);
100
+ }
101
+
102
+ const kind = nodeKind(node);
103
+ if (!kind) {
104
+ errors.push(`${where}: missing 'kind'`);
105
+ return errors;
106
+ }
107
+ const isShell = kind.includes(':');
108
+ if (!isShell && !(KERNEL_KINDS as readonly string[]).includes(kind)) {
109
+ errors.push(`${where}: unknown kernel kind '${kind}' (use a known kernel kind or a namespaced shell kind)`);
110
+ }
111
+
112
+ const tags = childTags(node);
113
+ const req = (cond: boolean, msg: string) => {
114
+ if (!cond) errors.push(`${where} (kind=${kind}): ${msg}`);
115
+ };
116
+
117
+ switch (kind) {
118
+ case 'entity':
119
+ case 'object':
120
+ req(tags.has('types'), 'entity requires a <types> representation');
121
+ req(hasMeta(node, 'fact_grade'), 'entity requires fact_grade');
122
+ req(hasMeta(node, 'single_writer'), 'entity requires single_writer');
123
+ break;
124
+ case 'enum':
125
+ req(tags.has('types'), 'enum requires a <types> representation');
126
+ break;
127
+ case 'state-machine':
128
+ req(hasChildDeep(node, 'mermaid'), 'state-machine requires a <mermaid> diagram');
129
+ break;
130
+ case 'module':
131
+ case 'capsule':
132
+ req(hasMeta(node, 'depends_on'), 'module requires depends_on');
133
+ req(tags.has('capsule-tree'), 'module requires a <capsule-tree>');
134
+ break;
135
+ case 'component':
136
+ for (const slot of ['runtime', 'input', 'config', 'output']) {
137
+ req(hasIoSlot(node, slot), `component requires a <${slot}> block`);
138
+ }
139
+ break;
140
+ // port/actor/policy: lenient (kind valid is enough for now)
141
+ default:
142
+ break;
143
+ }
144
+
145
+ const fg = metaString(node, 'fact_grade');
146
+ if (fg !== undefined && !(FACT_GRADES as readonly string[]).includes(fg)) {
147
+ errors.push(`${where}: invalid fact_grade '${fg}'`);
148
+ }
149
+
150
+ return errors;
151
+ }
152
+
153
+ /** Validate every node in a registry. */
154
+ export function validateModelingRegistry(registry: ModelingRegistry): string[] {
155
+ const errors: string[] = [];
156
+ for (const nodes of registry.files.values()) {
157
+ for (const node of nodes) errors.push(...validateModelingNode(node));
158
+ }
159
+ return errors;
160
+ }
@@ -0,0 +1,282 @@
1
+ import * as path from 'path';
2
+ import type { XnlNode, DataElementNode, AttributeMap } from 'xnl-core';
3
+ import {
4
+ loadModelingRegistrySafe,
5
+ isDataElement,
6
+ readNodeId,
7
+ idNamespace,
8
+ nodeName,
9
+ type ModelingRegistry,
10
+ } from './registry';
11
+ import { validateModelingNode } from './schema';
12
+
13
+ /**
14
+ * Modeling validation engine. Checks every modeling `.xnl` file across three
15
+ * layers — XNL syntax, node-schema semantics, hierarchy/reference consistency —
16
+ * and returns author-friendly findings instead of throwing.
17
+ *
18
+ * See std/spec/modeling-node-schema.md (§5 id namespace, §6 references) and the
19
+ * track decisions: id↔path alignment is lenient (context must match; plane
20
+ * prefix optional but must match if present); `modeling://` dangling refs are
21
+ * errors; `behavior://` is syntax-only; a `domain` plane must exist; unknown
22
+ * derived planes are warnings; duplicate ids are findings, not throws.
23
+ */
24
+
25
+ export type ValidateLayer = 'syntax' | 'schema' | 'hierarchy';
26
+ export type ValidateSeverity = 'error' | 'warning';
27
+
28
+ export interface ValidateFinding {
29
+ /** Path relative to the validated dir. */
30
+ file: string;
31
+ /** 1-based line, when known. */
32
+ line?: number;
33
+ layer: ValidateLayer;
34
+ severity: ValidateSeverity;
35
+ message: string;
36
+ }
37
+
38
+ export type ValidateMode = 'registry' | 'deltas';
39
+
40
+ export interface ValidateOptions {
41
+ /**
42
+ * `registry` (default): files live at `<plane>/<context>/...index.xnl` — path
43
+ * context is the second path segment. `deltas`: files live at
44
+ * `<plane>/<context>.xnl` (a track's `modeling_deltas/`) — path context is the
45
+ * file basename without `.xnl`.
46
+ */
47
+ mode?: ValidateMode;
48
+ }
49
+
50
+ /** Planes with a fixed meaning; anything else is treated as an open derived plane. */
51
+ const KNOWN_PLANES = new Set(['domain', 'backend', 'surface', 'cli', 'agent']);
52
+
53
+ /** Reference scheme prefixes we recognise in metadata values. */
54
+ const MODELING_SCHEME = 'modeling://';
55
+ const BEHAVIOR_SCHEME = 'behavior://';
56
+
57
+ interface PathLoc {
58
+ plane: string;
59
+ context: string;
60
+ }
61
+
62
+ /** Derive `<plane>/<context>` from a registry / delta file path. */
63
+ function pathLoc(relFile: string, mode: ValidateMode): PathLoc {
64
+ const segs = relFile.split(path.sep).filter(Boolean);
65
+ const plane = segs[0] ?? '';
66
+ if (mode === 'deltas') {
67
+ // `<plane>/<context>.xnl`
68
+ const base = segs[1] ?? '';
69
+ const context = base.replace(/\.xnl$/i, '');
70
+ return { plane, context };
71
+ }
72
+ // registry: `<plane>/<context>/...`
73
+ const context = segs[1] ?? plane;
74
+ return { plane, context };
75
+ }
76
+
77
+ /** Collect every `modeling://` / `behavior://` string anywhere in a metadata map. */
78
+ function collectRefs(meta: AttributeMap | undefined): string[] {
79
+ const out: string[] = [];
80
+ const visit = (v: unknown): void => {
81
+ if (typeof v === 'string') {
82
+ if (v.startsWith(MODELING_SCHEME) || v.startsWith(BEHAVIOR_SCHEME)) out.push(v);
83
+ return;
84
+ }
85
+ if (Array.isArray(v)) {
86
+ for (const item of v) visit(item);
87
+ return;
88
+ }
89
+ if (v && typeof v === 'object') {
90
+ // XnlWord or nested attribute map — scan its values.
91
+ for (const item of Object.values(v as Record<string, unknown>)) visit(item);
92
+ }
93
+ };
94
+ if (meta) for (const v of Object.values(meta)) visit(v);
95
+ return out;
96
+ }
97
+
98
+ /** Validate id↔path alignment for one node; returns hierarchy findings. */
99
+ function checkIdPathAlignment(
100
+ node: DataElementNode,
101
+ relFile: string,
102
+ loc: PathLoc,
103
+ ): ValidateFinding[] {
104
+ const findings: ValidateFinding[] = [];
105
+ const id = readNodeId(node);
106
+ if (!id) return findings; // schema layer already flags a missing id
107
+ const ns = idNamespace(id);
108
+ const segs = ns ? ns.split('.') : [];
109
+
110
+ if (segs.length === 0) {
111
+ findings.push({
112
+ file: relFile,
113
+ layer: 'hierarchy',
114
+ severity: 'error',
115
+ message: `#${id}: id has no namespace context segment; expected '<context>.<name>' with context '${loc.context}'`,
116
+ });
117
+ return findings;
118
+ }
119
+
120
+ const idContext = segs[segs.length - 1];
121
+ if (idContext !== loc.context) {
122
+ findings.push({
123
+ file: relFile,
124
+ layer: 'hierarchy',
125
+ severity: 'error',
126
+ message: `#${id}: id context '${idContext}' does not match path context '${loc.context}' (${relFile})`,
127
+ });
128
+ }
129
+
130
+ // plane prefix optional; if present (namespace has >= 2 segments) it must match.
131
+ if (segs.length >= 2) {
132
+ const idPlane = segs[segs.length - 2];
133
+ if (idPlane !== loc.plane) {
134
+ findings.push({
135
+ file: relFile,
136
+ layer: 'hierarchy',
137
+ severity: 'error',
138
+ message: `#${id}: id plane prefix '${idPlane}' does not match path plane '${loc.plane}' (${relFile})`,
139
+ });
140
+ }
141
+ }
142
+
143
+ return findings;
144
+ }
145
+
146
+ /** Validate modeling:///behavior:// references for one node against the uri set. */
147
+ function checkReferences(
148
+ node: DataElementNode,
149
+ relFile: string,
150
+ knownUris: Set<string>,
151
+ ): ValidateFinding[] {
152
+ const findings: ValidateFinding[] = [];
153
+ const id = readNodeId(node);
154
+ const where = id ? `#${id}` : `<${node.tag}>`;
155
+ for (const ref of collectRefs(node.metadata)) {
156
+ if (ref.startsWith(MODELING_SCHEME)) {
157
+ if (!knownUris.has(ref)) {
158
+ findings.push({
159
+ file: relFile,
160
+ layer: 'hierarchy',
161
+ severity: 'error',
162
+ message: `${where}: dangling reference '${ref}' (no such node in the registry index)`,
163
+ });
164
+ }
165
+ } else if (ref.startsWith(BEHAVIOR_SCHEME)) {
166
+ // Syntax-only: must look like behavior://<capability>/.../<id>; existence unverified.
167
+ const rest = ref.slice(BEHAVIOR_SCHEME.length);
168
+ if (rest.split('/').filter(Boolean).length < 2) {
169
+ findings.push({
170
+ file: relFile,
171
+ layer: 'hierarchy',
172
+ severity: 'error',
173
+ message: `${where}: malformed behavior reference '${ref}' (expected behavior://<capability>/...)`,
174
+ });
175
+ }
176
+ }
177
+ }
178
+ return findings;
179
+ }
180
+
181
+ /**
182
+ * Check plane legality: domain must exist; unknown = warning. Operates over every
183
+ * discovered file path (including ones that failed to parse), so a syntax error in
184
+ * a `domain/...` file does not also produce a spurious "missing domain plane".
185
+ */
186
+ function checkPlanes(relFiles: Iterable<string>, mode: ValidateMode): ValidateFinding[] {
187
+ const findings: ValidateFinding[] = [];
188
+ const planes = new Set<string>();
189
+ for (const relFile of relFiles) {
190
+ planes.add(pathLoc(relFile, mode).plane);
191
+ }
192
+
193
+ if (!planes.has('domain')) {
194
+ findings.push({
195
+ file: '.',
196
+ layer: 'hierarchy',
197
+ severity: 'error',
198
+ message: `modeling registry must contain a 'domain' plane (found planes: ${[...planes].sort().join(', ') || '(none)'})`,
199
+ });
200
+ }
201
+
202
+ for (const plane of planes) {
203
+ if (plane && !KNOWN_PLANES.has(plane)) {
204
+ findings.push({
205
+ file: '.',
206
+ layer: 'hierarchy',
207
+ severity: 'warning',
208
+ message: `unknown derived plane '${plane}' (open derived planes are allowed; verify it is intentional)`,
209
+ });
210
+ }
211
+ }
212
+
213
+ return findings;
214
+ }
215
+
216
+ /**
217
+ * Validate a modeling tree (registry by default, or a track's `modeling_deltas/`
218
+ * tree when `mode: 'deltas'`). Returns all findings across the three layers;
219
+ * never throws on author errors.
220
+ */
221
+ export function validateModelingTree(dir: string, opts: ValidateOptions = {}): ValidateFinding[] {
222
+ const mode: ValidateMode = opts.mode ?? 'registry';
223
+ const findings: ValidateFinding[] = [];
224
+
225
+ const { registry, issues } = loadModelingRegistrySafe(dir);
226
+
227
+ // Layer 1 (syntax) + duplicate id (hierarchy), from the safe loader.
228
+ for (const issue of issues) {
229
+ if (issue.kind === 'syntax') {
230
+ findings.push({
231
+ file: issue.file,
232
+ line: issue.line,
233
+ layer: 'syntax',
234
+ severity: 'error',
235
+ message: issue.message,
236
+ });
237
+ } else {
238
+ findings.push({
239
+ file: issue.file,
240
+ layer: 'hierarchy',
241
+ severity: 'error',
242
+ message: issue.message,
243
+ });
244
+ }
245
+ }
246
+
247
+ // Known uris for reference resolution. Recompute from the path layout so delta
248
+ // mode (`<plane>/<context>.xnl`) and registry mode agree on the canonical
249
+ // `modeling://<plane>/<context>/<name>` form.
250
+ const knownUris = new Set<string>();
251
+ for (const [relFile, nodes] of registry.files) {
252
+ const loc = pathLoc(relFile, mode);
253
+ for (const node of nodes) {
254
+ const id = readNodeId(node);
255
+ if (!id || !isDataElement(node)) continue;
256
+ knownUris.add(`${MODELING_SCHEME}${loc.plane}/${loc.context}/${nodeName(id)}`);
257
+ }
258
+ }
259
+
260
+ // Layer 2 (schema) + Layer 3 per-node (alignment + references).
261
+ for (const [relFile, nodes] of registry.files) {
262
+ const loc = pathLoc(relFile, mode);
263
+ for (const node of nodes) {
264
+ if (!isDataElement(node)) continue;
265
+ for (const msg of validateModelingNode(node)) {
266
+ findings.push({ file: relFile, layer: 'schema', severity: 'error', message: msg });
267
+ }
268
+ findings.push(...checkIdPathAlignment(node, relFile, loc));
269
+ findings.push(...checkReferences(node, relFile, knownUris));
270
+ }
271
+ }
272
+
273
+ // Layer 3 cross-file: plane legality, over all discovered files (parsed ones
274
+ // plus syntax-failed ones, so a broken domain file still counts as a domain plane).
275
+ const planeFiles = new Set<string>(registry.files.keys());
276
+ for (const issue of issues) if (issue.kind === 'syntax') planeFiles.add(issue.file);
277
+ findings.push(...checkPlanes(planeFiles, mode));
278
+
279
+ return findings;
280
+ }
281
+
282
+ export { idNamespace };