@sdd-method/author-cli 0.6.0 → 0.8.0

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 (98) hide show
  1. package/dist/author-program.js +1 -1
  2. package/dist/lib/agent-harness.js +2 -2
  3. package/dist/lib/briefing/index.js +1 -1
  4. package/dist/lib/briefing/render.js +1 -1
  5. package/dist/lib/briefing/variables.js +1 -1
  6. package/dist/lib/catalogue/application-archetype/vocabulary.js +2 -2
  7. package/dist/lib/catalogue/archetype/maps/android.yaml +2 -2
  8. package/dist/lib/catalogue/archetype/maps/swiftui.yaml +2 -2
  9. package/dist/lib/catalogue/archetype/maps/uikit.yaml +2 -2
  10. package/dist/lib/catalogue/archetype/maps/web-generic.yaml +2 -2
  11. package/dist/lib/catalogue/archetype/openui-archetype-vocabulary.yaml +3 -3
  12. package/dist/lib/catalogue/archetype/resolver.js +4 -4
  13. package/dist/lib/catalogue/archetype/wireframe.js +1 -1
  14. package/dist/lib/catalogue/build.js +33 -33
  15. package/dist/lib/catalogue/builders/adr-supersedes.js +3 -3
  16. package/dist/lib/catalogue/builders/adrs.js +3 -3
  17. package/dist/lib/catalogue/builders/c4-containers.js +2 -2
  18. package/dist/lib/catalogue/builders/c4-entity-rollups.js +1 -1
  19. package/dist/lib/catalogue/builders/c4-external-systems.js +2 -2
  20. package/dist/lib/catalogue/builders/c4-systems.js +1 -1
  21. package/dist/lib/catalogue/builders/capabilities.js +1 -1
  22. package/dist/lib/catalogue/builders/capability-domain-model.js +1 -1
  23. package/dist/lib/catalogue/builders/capability-spec-walker.js +2 -2
  24. package/dist/lib/catalogue/builders/capability-spec.js +4 -4
  25. package/dist/lib/catalogue/builders/contracts.js +12 -6
  26. package/dist/lib/catalogue/builders/data-store-mapping.js +2 -2
  27. package/dist/lib/catalogue/builders/domain-models.js +2 -2
  28. package/dist/lib/catalogue/builders/domains.js +3 -3
  29. package/dist/lib/catalogue/builders/entities-manifest.js +8 -8
  30. package/dist/lib/catalogue/builders/entities-mermaid.js +4 -4
  31. package/dist/lib/catalogue/builders/entity-persistence.js +1 -1
  32. package/dist/lib/catalogue/builders/entity-slug.js +3 -3
  33. package/dist/lib/catalogue/builders/event-catalog-walker.js +6 -6
  34. package/dist/lib/catalogue/builders/event-edges.js +6 -6
  35. package/dist/lib/catalogue/builders/events.js +3 -3
  36. package/dist/lib/catalogue/builders/evidence-class.js +1 -1
  37. package/dist/lib/catalogue/builders/feature-domain-model.js +1 -1
  38. package/dist/lib/catalogue/builders/feature-grounding-walker.js +2 -2
  39. package/dist/lib/catalogue/builders/feature-grounding.js +5 -5
  40. package/dist/lib/catalogue/builders/feature-subscription-channels.js +3 -3
  41. package/dist/lib/catalogue/builders/features.js +1 -1
  42. package/dist/lib/catalogue/builders/glossary-xref.js +4 -4
  43. package/dist/lib/catalogue/builders/integrations.js +4 -4
  44. package/dist/lib/catalogue/builders/persona-c4system.js +1 -1
  45. package/dist/lib/catalogue/builders/product-edges.js +1 -1
  46. package/dist/lib/catalogue/builders/product-platform-dependencies.js +2 -2
  47. package/dist/lib/catalogue/builders/products.js +5 -5
  48. package/dist/lib/catalogue/builders/repo-edges.js +12 -12
  49. package/dist/lib/catalogue/builders/repositories.js +16 -11
  50. package/dist/lib/catalogue/builders/screen-tree.js +6 -6
  51. package/dist/lib/catalogue/builders/screens.js +18 -18
  52. package/dist/lib/catalogue/builders/service-contract-consumes.js +1 -1
  53. package/dist/lib/catalogue/builders/service-entity-tables.js +3 -3
  54. package/dist/lib/catalogue/builders/services.js +5 -5
  55. package/dist/lib/catalogue/builders/system-external-dependency.js +2 -2
  56. package/dist/lib/catalogue/canonical-schema.yaml +3 -2
  57. package/dist/lib/catalogue/contract-id.js +1 -1
  58. package/dist/lib/catalogue/coverage-render.js +15 -15
  59. package/dist/lib/catalogue/csv-writer.js +1 -1
  60. package/dist/lib/catalogue/feature-grounding-aliases-loader.js +1 -1
  61. package/dist/lib/catalogue/findings.js +1 -1
  62. package/dist/lib/catalogue/fk-validator.js +2 -2
  63. package/dist/lib/catalogue/manifest-loader.js +3 -3
  64. package/dist/lib/catalogue/schema-compat.js +2 -2
  65. package/dist/lib/catalogue/schema-loader.js +1 -1
  66. package/dist/lib/catalogue/screen-validation-loader.js +2 -2
  67. package/dist/lib/catalogue/service-architecture-parser.js +1 -1
  68. package/dist/lib/catalogue/traceability-loader.js +9 -4
  69. package/dist/lib/conformance/finding.js +1 -1
  70. package/dist/lib/conformance/index.js +5 -5
  71. package/dist/lib/contracts-availability/index.js +8 -2
  72. package/dist/lib/generate-briefing/index.js +2 -2
  73. package/dist/lib/metamodel/tql-subset-parser.js +280 -0
  74. package/dist/lib/pack/schema.js +2 -2
  75. package/dist/lib/pack/seed.js +2 -2
  76. package/dist/lib/pack/validate.js +1 -1
  77. package/dist/lib/plan/read-sdd.js +3 -2
  78. package/dist/lib/repo-kind.js +3 -3
  79. package/dist/lib/scaffold/archetype-behaviour.js +171 -0
  80. package/dist/lib/scaffold/domain.js +24 -6
  81. package/dist/lib/scaffold/registry.js +1 -1
  82. package/dist/lib/sdd-index/repo-config.js +1 -0
  83. package/dist/lib/sync/briefing-merge.js +2 -2
  84. package/dist/lib/sync/bundle-cache.js +4 -4
  85. package/dist/lib/sync/yaml-block-merge.js +1 -1
  86. package/dist/lib/validate/archetype-citations.js +134 -0
  87. package/dist/lib/validate/behaviour-store.js +25 -1
  88. package/dist/lib/validate/dispatch.js +8 -1
  89. package/dist/maintain-version.json +2 -2
  90. package/dist/verbs/conformance.js +3 -3
  91. package/dist/verbs/list.js +1 -1
  92. package/dist/verbs/render.js +1 -1
  93. package/dist/verbs/scaffold.js +7 -4
  94. package/dist/verify-program.js +2 -2
  95. package/dist/verify-version.json +2 -2
  96. package/package.json +2 -2
  97. package/scripts/validate-app-dependencies.sh +1 -1
  98. package/scripts/validate-integration-docs.sh +1 -1
@@ -223,8 +223,8 @@ const ManifestSchema = z.object({
223
223
  // shape here. Whether the value resolves against the active vocabulary
224
224
  // (method ∪ RA-bundle ∪ adopter) is checked in the build step, which has
225
225
  // the bundle cache. Absent reads as "unclassified" (with a finding). Named
226
- // application_archetype to stay distinct from the ADR 0173 domain archetype
227
- // and the ADR 0169 openui archetype.
226
+ // application_archetype to stay distinct from the M-ADR 0173 domain archetype
227
+ // and the M-ADR 0169 openui archetype.
228
228
  application_archetype: z
229
229
  .string()
230
230
  .regex(/^[a-z][a-z0-9-]*$/, {
@@ -298,7 +298,7 @@ export async function loadSddManifest(sddPath, options) {
298
298
  const code = err.code;
299
299
  if (code === "ENOENT") {
300
300
  throw new Error(`SDD manifest not found at ${sourcePath}\n\n` +
301
- `Every SDD repo MUST declare an .sdd/sdd-manifest.yaml per ADR 0134 §4.6.\n` +
301
+ `Every SDD repo MUST declare an .sdd/sdd-manifest.yaml per M-ADR 0134 §4.6.\n` +
302
302
  `Schema: docs/method/methods/sdd-manifest-schema.yaml in sdd-method.\n` +
303
303
  `Bootstrap: \`sdd-cli init\` (when migrated to support manifest authoring).`);
304
304
  }
@@ -23,8 +23,8 @@
23
23
  * release; migration tooling (`sdd-cli migrate <schema> --to <v>`,
24
24
  * planned) covers downgrade paths.
25
25
  *
26
- * See ADR 0132 (catalogue-integrity gate's schema-drift digest hook) +
27
- * ADR 0134 §4.6 (canonical builder + aggregator).
26
+ * See M-ADR 0132 (catalogue-integrity gate's schema-drift digest hook) +
27
+ * M-ADR 0134 §4.6 (canonical builder + aggregator).
28
28
  */
29
29
  export const SCHEMA_COMPAT = {
30
30
  catalogue_schema: "^4.0.0",
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Loader for the canonical catalogue-schema.yaml vendored from sdd-method.
3
3
  *
4
- * Per ADR 0134 §4.6 the canonical builder reads the schema rather than
4
+ * Per M-ADR 0134 §4.6 the canonical builder reads the schema rather than
5
5
  * hardcoding column shapes or FK rules. This loader is the only place in
6
6
  * the CLI that knows the schema file's location.
7
7
  */
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Loader for the optional `.sdd/screen-validation.yaml` config — the
3
3
  * adopter-tunable parameters for screen-spec composition-depth validation
4
- * (ADR 0168 §4.5) and the archetype overlay (ADR 0169 §4.3).
4
+ * (M-ADR 0168 §4.5) and the archetype overlay (M-ADR 0169 §4.3).
5
5
  *
6
6
  * Every field is optional and carries a method default; an absent file means
7
7
  * "method defaults only" (the vocabulary-free checks all still run). Follows
@@ -13,7 +13,7 @@
13
13
  * decorative_primitives: # unlocks screen-styling-primitive-as-structure
14
14
  * - Spacer
15
15
  * - Divider
16
- * archetype_overlays: # per-platform overlay (ADR 0169 §4.3)
16
+ * archetype_overlays: # per-platform overlay (M-ADR 0169 §4.3)
17
17
  * web-generic:
18
18
  * strip_prefixes: [Nwc, Nwe] # render-only: stripped when humanising labels
19
19
  * overrides:
@@ -3,7 +3,7 @@
3
3
  // Extracted from builders/services.ts so two callers can share it:
4
4
  // - the per-SDD services builder (buildServices), and
5
5
  // - the aggregator's federated reverse-metadata enrichment pass
6
- // (ADR 0163 P2), which reaches member SDDs' arch docs that the
6
+ // (M-ADR 0163 P2), which reaches member SDDs' arch docs that the
7
7
  // platform-root's own build never sees.
8
8
  //
9
9
  // The metadata is read faithfully — not inferred — from three blocks:
@@ -8,7 +8,7 @@
8
8
  // `docs/domains/{domain}/product/{capability}.traceability.yaml`.
9
9
  //
10
10
  // Schema reference: docs/method/methods/capability-traceability-manifest-template.yaml
11
- // in sdd-method (per ADR 0047 / capability-spec-template.md).
11
+ // in sdd-method (per M-ADR 0047 / capability-spec-template.md).
12
12
  import { readdir, readFile, stat } from "node:fs/promises";
13
13
  import { join, relative, sep } from "node:path";
14
14
  import { parse as parseYaml } from "yaml";
@@ -47,6 +47,7 @@ export const CONTRACT_LIST_KEYS = [
47
47
  "avro",
48
48
  "spicedb",
49
49
  "graphql",
50
+ "cli",
50
51
  ];
51
52
  /**
52
53
  * Every contract path a contracts block declares, across all kinds and both
@@ -83,6 +84,8 @@ export async function loadTraceabilityManifests(opts) {
83
84
  }
84
85
  throw err;
85
86
  }
87
+ // A manifest dropped whole takes its capability's traceability with it, so
88
+ // each drop below is an error that fails the build (as for repo-configs).
86
89
  const manifests = [];
87
90
  for (const path of yamlPaths.sort()) {
88
91
  const text = await readFile(path, "utf-8");
@@ -91,18 +94,18 @@ export async function loadTraceabilityManifests(opts) {
91
94
  raw = parseYaml(text);
92
95
  }
93
96
  catch (err) {
94
- opts.findings.add("convention", `failed to parse YAML at ${relative(opts.sddPath, path)}: ${err.message}`);
97
+ opts.findings.add("convention", `failed to parse YAML at ${relative(opts.sddPath, path)}: ${err.message} — manifest dropped`, "error", relative(opts.sddPath, path));
95
98
  continue;
96
99
  }
97
100
  if (!raw || typeof raw !== "object") {
98
- opts.findings.add("convention", `${relative(opts.sddPath, path)}: not an object — skipped`);
101
+ opts.findings.add("convention", `${relative(opts.sddPath, path)}: not an object — manifest dropped`, "error", relative(opts.sddPath, path));
99
102
  continue;
100
103
  }
101
104
  const obj = raw;
102
105
  const cap = obj["capability"] ?? {};
103
106
  const capId = String(cap["id"] ?? "").trim();
104
107
  if (capId === "") {
105
- opts.findings.add("convention", `${relative(opts.sddPath, path)}: missing capability.id`);
108
+ opts.findings.add("convention", `${relative(opts.sddPath, path)}: missing capability.id — manifest dropped`, "error", relative(opts.sddPath, path));
106
109
  continue;
107
110
  }
108
111
  const relPath = relative(opts.sddPath, path);
@@ -279,6 +282,7 @@ function extractContracts(raw) {
279
282
  const avro = arr("avro");
280
283
  const spicedb = arr("spicedb");
281
284
  const graphql = arr("graphql");
285
+ const cli = arr("cli");
282
286
  // `provenance` (3.5.0) MUST be extracted here or the builder that reads it
283
287
  // sees nothing — this function reconstructs the block key-by-key rather than
284
288
  // passing the parsed object through, which is precisely the shape the
@@ -295,6 +299,7 @@ function extractContracts(raw) {
295
299
  ...(avro ? { avro } : {}),
296
300
  ...(spicedb ? { spicedb } : {}),
297
301
  ...(graphql ? { graphql } : {}),
302
+ ...(cli ? { cli } : {}),
298
303
  ...(provenance ? { provenance } : {}),
299
304
  };
300
305
  }
@@ -53,7 +53,7 @@ export function findingRows(results, runDate) {
53
53
  observed_not_declared: r.observedNotDeclared.length,
54
54
  needs_triage: r.needsTriage,
55
55
  run_date: runDate,
56
- source_sdd_id: "", // populated by the aggregator per ADR 0134
56
+ source_sdd_id: "", // populated by the aggregator per M-ADR 0134
57
57
  platform_sdd_id: "", // stamped at organisation tier per M-ADR 0208
58
58
  };
59
59
  lines.push(FINDING_COLUMNS.map((c) => csvCell(row[c])).join(","));
@@ -17,7 +17,7 @@ function resolveSource(targetRepo, source, codeRoot) {
17
17
  }
18
18
  const markdownProviderSignals = async (path, spec) => {
19
19
  const out = new Set();
20
- // `section` (ADR 0180 delta-pilot extension) scopes the scan to one heading block and accepts a plain
20
+ // `section` (M-ADR 0180 delta-pilot extension) scopes the scan to one heading block and accepts a plain
21
21
  // "Field" header, so a check can read ONE entity table inside a multi-table data-model doc (e.g.
22
22
  // data-model.mermaid.md "### Collection: orders") without duplicating the field list into a bespoke
23
23
  // surface file. With no `section` the behaviour is byte-for-byte the legacy one (whole file, strict
@@ -499,7 +499,7 @@ function printReconcile(chk, r, schemaDir, out) {
499
499
  if (!chk.reconcile || r.target !== "binding-vs-reality")
500
500
  return;
501
501
  const rc = chk.reconcile;
502
- out(`\n--- reconcile ${r.id} -> regenerate \`${rc.block}\` in ${rc.binding} (ADR 0181 §2.3) ---\n`);
502
+ out(`\n--- reconcile ${r.id} -> regenerate \`${rc.block}\` in ${rc.binding} (M-ADR 0181 §2.3) ---\n`);
503
503
  out(`# generated from the observed-set (${r.observed} schemas); the binding cannot drift if generated.\n`);
504
504
  if (rc.note)
505
505
  out(`# NOTE: ${rc.note}\n`);
@@ -513,7 +513,7 @@ function printReconcile(chk, r, schemaDir, out) {
513
513
  }
514
514
  }
515
515
  async function planChecks(opts, stdout) {
516
- // Resolution order: explicit --config wins; else the ADR 0181 binding (checks derived from it);
516
+ // Resolution order: explicit --config wins; else the M-ADR 0181 binding (checks derived from it);
517
517
  // else the conventional .sdd/conformance-checks.yaml; else nothing to do.
518
518
  if (!opts.config && hasBinding(opts.targetRepo)) {
519
519
  const derived = await deriveChecksFromBinding(opts.targetRepo);
@@ -523,7 +523,7 @@ async function planChecks(opts, stdout) {
523
523
  ? resolveSource(opts.targetRepo, opts.config, opts.codeRoot)
524
524
  : resolve(opts.targetRepo, ".sdd/conformance-checks.yaml");
525
525
  if (!existsSync(configPath)) {
526
- stdout(`[skip] no spec↔implementation binding (.sdd/implementation-binding.yaml or .sdd/implementation-bindings/, ADR 0181) and no ` +
526
+ stdout(`[skip] no spec↔implementation binding (.sdd/implementation-binding.yaml or .sdd/implementation-bindings/, M-ADR 0181) and no ` +
527
527
  `checks file at ${configPath}\n`);
528
528
  return null;
529
529
  }
@@ -562,7 +562,7 @@ export async function runConformance(opts, stdout = (line) => process.stdout.wri
562
562
  stdout(JSON.stringify({ checks: results, skipped }, null, 2) + "\n");
563
563
  }
564
564
  else {
565
- stdout("Conformance-reverse (advisory; disposition is triage — ADR 0180 §2.3).\n");
565
+ stdout("Conformance-reverse (advisory; disposition is triage — M-ADR 0180 §2.3).\n");
566
566
  for (const r of results) {
567
567
  printHuman(r, stdout);
568
568
  if (opts.reconcile) {
@@ -116,7 +116,7 @@ async function collectReferences(repo) {
116
116
  const block = m.traceability.contracts;
117
117
  if (!block)
118
118
  continue;
119
- for (const list of [block.openapi, block.proto, block.avro, block.spicedb]) {
119
+ for (const list of [block.openapi, block.proto, block.avro, block.spicedb, block.graphql, block.cli]) {
120
120
  if (!Array.isArray(list))
121
121
  continue;
122
122
  for (const entry of list) {
@@ -186,15 +186,21 @@ async function sourceCoordinate(abs) {
186
186
  return "unversioned";
187
187
  }
188
188
  }
189
- function inferKind(ref) {
189
+ export function inferKind(ref) {
190
190
  if (ref.endsWith(".avsc"))
191
191
  return "event";
192
192
  if (ref.endsWith(".proto"))
193
193
  return "rpc";
194
194
  if (ref.endsWith(".zed"))
195
195
  return "authz";
196
+ if (/\.graphqls?$/.test(ref))
197
+ return "graphql";
196
198
  if (/openapi/.test(ref) && /\.(ya?ml|json)$/.test(ref))
197
199
  return "rest";
200
+ // A command manifest lives under a `cli/` contracts folder. Tested after rest, so an OpenAPI file in a
201
+ // repository whose own name ends in -cli stays rest.
202
+ if (/(^|\/)cli\/.*\.ya?ml$/.test(ref))
203
+ return "cli";
198
204
  return undefined;
199
205
  }
200
206
  async function readManifest(abs) {
@@ -6,7 +6,7 @@
6
6
  * source of truth).
7
7
  *
8
8
  * Replaces the v0.3.0 `generate-claude-md` verb whose rich-content
9
- * output diverges from the ADR 0148 thin-pointer shape.
9
+ * output diverges from the M-ADR 0148 thin-pointer shape.
10
10
  */
11
11
  import { access } from "node:fs/promises";
12
12
  import { join } from "node:path";
@@ -36,7 +36,7 @@ export async function runGenerateBriefing(opts, stdout = (line) => process.stdou
36
36
  // graceful skip downstream.
37
37
  const templatesPresent = await fileExists(join(opts.targetDir, BRIEFING_TEMPLATE_ROOT, "agent-instructions.md.hbs"));
38
38
  if (!templatesPresent) {
39
- stdout(fail(`Briefing templates not found under ${BRIEFING_TEMPLATE_ROOT}/ — sync against a baseline that ships ADR 0148 templates first.`));
39
+ stdout(fail(`Briefing templates not found under ${BRIEFING_TEMPLATE_ROOT}/ — sync against a baseline that ships M-ADR 0148 templates first.`));
40
40
  return { exitCode: 1 };
41
41
  }
42
42
  const result = await syncBriefingFiles({
@@ -0,0 +1,280 @@
1
+ // The documented TypeQL subset — PARSER (M-ADR 0210 §4.2).
2
+ //
3
+ // This module reads the authored form of a fact-based model and nothing else: it knows
4
+ // the subset's grammar and refuses, with a located error, anything outside it. It knows
5
+ // nothing about catalogues, YAML or projections — that is `schema-projection.ts`. The split
6
+ // is deliberate: a store extending the subset extends the parser; the method's projection
7
+ // is one consumer of the parsed model among possible others.
8
+ //
9
+ // Fail-closed (M-ADR 0200 §4.3): a construct the parser does not recognise is a ParseError,
10
+ // never a skip. The five blindnesses measured in the prototype this replaces — `@values`,
11
+ // an `@card` form outside the subset, a role with two players, a value-type/family
12
+ // contradiction, a content change that must be visible — are each refused here or made
13
+ // visible to the projection, and the round-trip gate proves that with mutation controls
14
+ // before every verdict.
15
+ //
16
+ // Grammar (one declaration per line; `#` outside a string literal starts a comment):
17
+ // define
18
+ // attribute <label>, value <string|integer|double|boolean|date|datetime>;
19
+ // entity <label>[, owns <attr> [@key] [@card(1..1)|@card(0..1)|@card(0..)]][, plays <rel>:<role> [...]] ...;
20
+ // relation <label>[, relates <role> [@card(1..1)|@card(0..1)|@card(0..)]][, owns ...][, plays ...] ...;
21
+ // Meta rows (the second file):
22
+ // insert $m isa <meta-type>, has <key> <"string"|int|true|false>[, has ...];
23
+ export class ParseError extends Error {
24
+ constructor(message) {
25
+ super(message);
26
+ this.name = "ParseError";
27
+ }
28
+ }
29
+ export const VALUE_TYPES = new Set(["string", "integer", "double", "boolean", "date", "datetime"]);
30
+ export const META_TYPES = new Set([
31
+ "schema-meta",
32
+ "catalogue-meta",
33
+ "column-meta",
34
+ "fk-meta",
35
+ "column-enum-meta",
36
+ "enum-meta",
37
+ "enum-value-meta",
38
+ "enum-value-property",
39
+ "coverage-meta",
40
+ "relationship-meta",
41
+ "vocabulary-meta",
42
+ "rule-meta",
43
+ ]);
44
+ /** Meta keys whose values are lists (repeated `has` on one row). */
45
+ const LIST_KEYS = new Set(["m-external-for-profile", "m-reads"]);
46
+ function unlit(s, where) {
47
+ let out = "";
48
+ for (let i = 0; i < s.length; i++) {
49
+ const c = s[i];
50
+ if (c === "\\" && i + 1 < s.length) {
51
+ const n = s[i + 1];
52
+ const map = { n: "\n", t: "\t", '"': '"', "\\": "\\" };
53
+ const r = map[n];
54
+ if (r === undefined)
55
+ throw new ParseError(`${where}: bad escape \\${n}`);
56
+ out += r;
57
+ i++;
58
+ }
59
+ else {
60
+ out += c;
61
+ }
62
+ }
63
+ return out;
64
+ }
65
+ /** A `#` outside a string literal starts a comment. */
66
+ export function stripComment(line) {
67
+ let out = "";
68
+ let q = false;
69
+ for (let i = 0; i < line.length; i++) {
70
+ const c = line[i];
71
+ if (c === '"' && (i === 0 || line[i - 1] !== "\\"))
72
+ q = !q;
73
+ if (c === "#" && !q)
74
+ break;
75
+ out += c;
76
+ }
77
+ return out.trim();
78
+ }
79
+ const CARD_RE = /^@card\((\d+)\.\.(\d*)\)$/;
80
+ function parseAnnotations(tokens, where) {
81
+ let key = false;
82
+ let required = false;
83
+ let multi = false;
84
+ let optional = false;
85
+ for (const t of tokens) {
86
+ if (t === "@key") {
87
+ key = true;
88
+ }
89
+ else if (t.startsWith("@card(")) {
90
+ const m = CARD_RE.exec(t);
91
+ if (!m)
92
+ throw new ParseError(`${where}: unparseable cardinality ${JSON.stringify(t)}`);
93
+ const lo = m[1];
94
+ const hi = m[2];
95
+ if (lo === "1" && hi === "1")
96
+ required = true;
97
+ else if (lo === "0" && hi === "")
98
+ multi = true;
99
+ else if (lo === "0" && hi === "1")
100
+ optional = true;
101
+ else
102
+ throw new ParseError(`${where}: cardinality ${t} cannot be projected onto the catalogue schema (only @card(1..1), @card(0..1) or @card(0..) are in the subset)`);
103
+ }
104
+ else if (t.startsWith("@values")) {
105
+ throw new ParseError(`${where}: @values(...) is not admitted in this projection — vocabularies bind through column_enums meta rows or a recorded absence (M-ADR 0210 §4.5)`);
106
+ }
107
+ else {
108
+ throw new ParseError(`${where}: unknown token ${JSON.stringify(t)}`);
109
+ }
110
+ }
111
+ return { key, required, multi, optional };
112
+ }
113
+ function splitClauses(body, where) {
114
+ // refuse before the naive split can be fooled by a comma inside the parenthesis
115
+ if (body.includes("@values")) {
116
+ throw new ParseError(`${where}: @values(...) is not admitted in this projection (M-ADR 0210 §4.5)`);
117
+ }
118
+ return body.split(",").map((c) => c.trim());
119
+ }
120
+ const ATTR_RE = /^attribute ([a-z][a-z0-9-]*), value ([a-z]+)$/;
121
+ const TYPE_RE = /^(entity|relation) ([a-z][a-z0-9-]*)(?:, (.*))?$/;
122
+ /** Parse the type-system file. `fileLabel` names the file in error locations. */
123
+ export function parseTypes(text, fileLabel = "catalogue-schema.tql", options = {}) {
124
+ const singlePlayer = options.singlePlayer ?? true;
125
+ const attrs = new Map();
126
+ const entities = new Map();
127
+ const relations = new Map();
128
+ const seen = new Set();
129
+ const lines = text.split(/\r?\n/);
130
+ for (let ln = 1; ln <= lines.length; ln++) {
131
+ let line = stripComment(lines[ln - 1]);
132
+ if (line === "" || line === "define")
133
+ continue;
134
+ const where = `${fileLabel}:${ln}`;
135
+ if (!line.endsWith(";"))
136
+ throw new ParseError(`${where}: declaration must end with ';' — one declaration per line`);
137
+ line = line.slice(0, -1);
138
+ const am = ATTR_RE.exec(line);
139
+ if (am) {
140
+ const lab = am[1];
141
+ const vt = am[2];
142
+ if (!VALUE_TYPES.has(vt))
143
+ throw new ParseError(`${where}: unknown value type ${JSON.stringify(vt)}`);
144
+ if (seen.has(lab))
145
+ throw new ParseError(`${where}: duplicate declaration of ${JSON.stringify(lab)}`);
146
+ seen.add(lab);
147
+ attrs.set(lab, vt);
148
+ continue;
149
+ }
150
+ const tm = TYPE_RE.exec(line);
151
+ if (!tm)
152
+ throw new ParseError(`${where}: not in the subset: ${JSON.stringify(line.slice(0, 100))}`);
153
+ const kind = tm[1];
154
+ const lab = tm[2];
155
+ const body = tm[3];
156
+ if (seen.has(lab))
157
+ throw new ParseError(`${where}: duplicate declaration of ${JSON.stringify(lab)}`);
158
+ seen.add(lab);
159
+ const decl = { owns: new Map(), plays: new Map(), relates: new Map() };
160
+ for (const clause of body !== undefined ? splitClauses(body, where) : []) {
161
+ const w = clause.split(/\s+/).filter((x) => x !== "");
162
+ if (w.length === 0)
163
+ throw new ParseError(`${where}: empty clause`);
164
+ const head = w[0];
165
+ if (head === "owns") {
166
+ if (w.length < 2)
167
+ throw new ParseError(`${where}: owns without attribute`);
168
+ if (decl.owns.has(w[1]))
169
+ throw new ParseError(`${where}: ${lab} owns ${w[1]} twice`);
170
+ decl.owns.set(w[1], parseAnnotations(w.slice(2), where));
171
+ }
172
+ else if (head === "plays") {
173
+ if (w.length < 2 || !w[1].includes(":"))
174
+ throw new ParseError(`${where}: plays needs <relation>:<role>`);
175
+ if (decl.plays.has(w[1]))
176
+ throw new ParseError(`${where}: ${lab} plays ${w[1]} twice`);
177
+ decl.plays.set(w[1], parseAnnotations(w.slice(2), where));
178
+ }
179
+ else if (head === "relates") {
180
+ if (kind !== "relation")
181
+ throw new ParseError(`${where}: only a relation relates`);
182
+ if (w.length < 2)
183
+ throw new ParseError(`${where}: relates without role`);
184
+ if (decl.relates.has(w[1]))
185
+ throw new ParseError(`${where}: ${lab} relates ${w[1]} twice`);
186
+ decl.relates.set(w[1], parseAnnotations(w.slice(2), where));
187
+ }
188
+ else {
189
+ throw new ParseError(`${where}: unknown clause ${JSON.stringify(head)}`);
190
+ }
191
+ }
192
+ (kind === "entity" ? entities : relations).set(lab, decl);
193
+ }
194
+ // every owned attribute must be declared; every role must exist; a role has at most ONE player
195
+ const playersAll = new Map();
196
+ for (const [lab, d] of [...entities, ...relations]) {
197
+ for (const a of d.owns.keys()) {
198
+ if (!attrs.has(a))
199
+ throw new ParseError(`${lab} owns undeclared attribute ${JSON.stringify(a)}`);
200
+ }
201
+ for (const rr of d.plays.keys()) {
202
+ const idx = rr.indexOf(":");
203
+ const rel = rr.slice(0, idx);
204
+ const role = rr.slice(idx + 1);
205
+ const r = relations.get(rel);
206
+ if (!r)
207
+ throw new ParseError(`${lab} plays role of undeclared relation ${JSON.stringify(rel)}`);
208
+ if (!r.relates.has(role))
209
+ throw new ParseError(`${lab} plays undeclared role ${JSON.stringify(rr)}`);
210
+ const list = playersAll.get(rr) ?? [];
211
+ list.push(lab);
212
+ playersAll.set(rr, list);
213
+ }
214
+ }
215
+ const players = new Map();
216
+ for (const [rr, ps] of playersAll) {
217
+ if (ps.length > 1 && !singlePlayer)
218
+ continue;
219
+ if (ps.length > 1) {
220
+ throw new ParseError(`role ${rr} has ${ps.length} players (${ps.join(", ")}); the projection can carry exactly one and refuses to pick`);
221
+ }
222
+ players.set(rr, ps[0]);
223
+ }
224
+ return { attrs, entities, relations, players, playersAll };
225
+ }
226
+ const ROW_RE = /^insert \$m isa ([a-z][a-z0-9-]*), (.*);$/;
227
+ const TOK_RE = /has ([a-z][a-z0-9-]*) ("(?:[^"\\]|\\.)*"|-?\d+|true|false)/y;
228
+ export function parseMeta(text, fileLabel = "catalogue-schema-meta.tql", options = {}) {
229
+ const metaTypes = options.metaTypes ?? META_TYPES;
230
+ const rows = new Map();
231
+ const lines = text.split(/\r?\n/);
232
+ for (let ln = 1; ln <= lines.length; ln++) {
233
+ const line = stripComment(lines[ln - 1]);
234
+ if (line === "")
235
+ continue;
236
+ const where = `${fileLabel}:${ln}`;
237
+ const m = ROW_RE.exec(line);
238
+ if (!m)
239
+ throw new ParseError(`${where}: not in the subset: ${JSON.stringify(line.slice(0, 100))}`);
240
+ const typ = m[1];
241
+ const body = m[2];
242
+ if (!metaTypes.has(typ))
243
+ throw new ParseError(`${where}: unknown meta type ${JSON.stringify(typ)}`);
244
+ const acc = new Map();
245
+ let i = 0;
246
+ for (;;) {
247
+ TOK_RE.lastIndex = i;
248
+ const t = TOK_RE.exec(body);
249
+ if (!t)
250
+ throw new ParseError(`${where}: meta row body not in the subset at offset ${i}: ${JSON.stringify(body.slice(i, i + 60))}`);
251
+ const k = t[1];
252
+ const v = t[2];
253
+ let val;
254
+ if (v.startsWith('"'))
255
+ val = unlit(v.slice(1, -1), where);
256
+ else if (v === "true" || v === "false")
257
+ val = v === "true";
258
+ else
259
+ val = Number.parseInt(v, 10);
260
+ const list = acc.get(k) ?? [];
261
+ list.push(val);
262
+ acc.set(k, list);
263
+ i = TOK_RE.lastIndex;
264
+ if (i === body.length)
265
+ break;
266
+ if (body.startsWith(", ", i))
267
+ i += 2;
268
+ else
269
+ throw new ParseError(`${where}: expected ', has ...' at offset ${i}: ${JSON.stringify(body.slice(i, i + 40))}`);
270
+ }
271
+ const row = {};
272
+ for (const [k, vs] of acc)
273
+ row[k] = LIST_KEYS.has(k) ? vs : vs[0];
274
+ const list = rows.get(typ) ?? [];
275
+ list.push(row);
276
+ rows.set(typ, list);
277
+ }
278
+ return rows;
279
+ }
280
+ //# sourceMappingURL=tql-subset-parser.js.map
@@ -26,7 +26,7 @@ export const PackSeedSchema = z.object({
26
26
  scope: z
27
27
  .string()
28
28
  .regex(/^[a-z0-9-]+$/, "scope must be lower-kebab-case slug"),
29
- // Optional per ADR 0176 §4.6: absent = profile-agnostic pack.
29
+ // Optional per M-ADR 0176 §4.6: absent = profile-agnostic pack.
30
30
  forwardBase: z
31
31
  .object({
32
32
  bundle: z.enum(KNOWN_FORWARD_BASES),
@@ -66,7 +66,7 @@ export const PackSeedSchema = z.object({
66
66
  .array(z.string().min(1))
67
67
  .min(1, "managed_paths must have at least one entry"),
68
68
  forbiddenPaths: z.array(z.string()).optional(),
69
- // ADR 0176 §4.3: scaffold-source path prefixes; each must cover at
69
+ // M-ADR 0176 §4.3: scaffold-source path prefixes; each must cover at
70
70
  // least one managed_paths entry (checked in the superRefine below).
71
71
  scaffoldSourcePaths: z.array(z.string().min(1)).optional(),
72
72
  requiresPack: z
@@ -2,7 +2,7 @@ import { readFile } from "node:fs/promises";
2
2
  import { dirname, isAbsolute, resolve } from "node:path";
3
3
  import { parse as parseYaml } from "yaml";
4
4
  /**
5
- * Adopter pack seed file. Per ADR 0151 + context-pack-seed-schema.yaml.
5
+ * Adopter pack seed file. Per M-ADR 0151 + context-pack-seed-schema.yaml.
6
6
  *
7
7
  * Loaded by `sdd-cli pack build` and `sdd-cli pack validate` from
8
8
  * `orchestration/distribution/{pack-name}-seed.yaml` in adopter repos.
@@ -120,7 +120,7 @@ async function loadSeedRecursive(absPath, visited) {
120
120
  const seed = {
121
121
  version: asString(own.version, "version", absPath),
122
122
  scope: asString(own.scope, "scope", absPath),
123
- // forward_base optional per ADR 0176 §4.6 (profile-agnostic packs omit it)
123
+ // forward_base optional per M-ADR 0176 §4.6 (profile-agnostic packs omit it)
124
124
  ...(own.forward_base && {
125
125
  forwardBase: {
126
126
  bundle: asString(own.forward_base.bundle, "forward_base.bundle", absPath),
@@ -36,7 +36,7 @@ export async function runPackValidate(opts, stdout = (s) => process.stdout.write
36
36
  stdout(pass(`Seed valid: ${seed.scope} v${seed.version}`));
37
37
  stdout(info(seed.forwardBase
38
38
  ? `Forward base: ${seed.forwardBase.bundle} >= ${seed.forwardBase.minimumVersion}`
39
- : "Forward base: none (profile-agnostic pack, ADR 0176 §4.6)"));
39
+ : "Forward base: none (profile-agnostic pack, M-ADR 0176 §4.6)"));
40
40
  if (seed.scaffoldSourcePaths) {
41
41
  stdout(info(`Scaffold-source prefixes: ${seed.scaffoldSourcePaths.join(", ")}`));
42
42
  }
@@ -10,7 +10,7 @@ import { parseRepoConfigFile } from "../sdd-index/repo-config.js";
10
10
  *
11
11
  * Repo-config parsing delegates to the shared sdd-index module so the
12
12
  * `plan --pilot` path and the MCP dependency-graph tools see the same
13
- * view. The richer ADR 0110 shape (package/version/stability) is
13
+ * view. The richer M-ADR 0110 shape (package/version/stability) is
14
14
  * flattened back to path-strings here to preserve the legacy
15
15
  * `RepoConfig` contract.
16
16
  */
@@ -44,7 +44,7 @@ async function readRepoConfigFor(sddRoot, manifestAbsPath) {
44
44
  path: manifestAbsPath,
45
45
  aggregates: [],
46
46
  internalDependencies: [],
47
- contracts: { openapi: [], proto: [], avro: [], spicedb: [], graphql: [] },
47
+ contracts: { openapi: [], proto: [], avro: [], spicedb: [], graphql: [], cli: [] },
48
48
  };
49
49
  if (!(await fileExists(manifestAbsPath)))
50
50
  return empty;
@@ -62,6 +62,7 @@ function legacyShape(node, sourcePath) {
62
62
  proto: [],
63
63
  avro: [],
64
64
  spicedb: [],
65
+ cli: [],
65
66
  };
66
67
  for (const ref of node.contracts)
67
68
  byKind[ref.kind].push(ref.path);
@@ -2,7 +2,7 @@ import { readFile, writeFile } from "node:fs/promises";
2
2
  import { join } from "node:path";
3
3
  /**
4
4
  * Forward-base bundles set `.sdd-repo-kind` and qualify as the base
5
- * layer that additive extensions can compose on top of (per ADR 0135).
5
+ * layer that additive extensions can compose on top of (per M-ADR 0135).
6
6
  * The legacy self-contained `reverse-baseline` is retained as a
7
7
  * forward base for compat with adopters who haven't migrated to the
8
8
  * `reverse-extension-baseline` additive shape yet.
@@ -59,7 +59,7 @@ export function repoKindFromProfile(profile) {
59
59
  /**
60
60
  * Returns the profile a bundle authoritatively claims, or null for
61
61
  * additive bundles that don't claim a profile of their own. Accepts
62
- * arbitrary strings (adopter pack names per ADR 0151) and returns null
62
+ * arbitrary strings (adopter pack names per M-ADR 0151) and returns null
63
63
  * for any name not in the closed sdd-method bundle set.
64
64
  */
65
65
  export function profileFromBundleName(name) {
@@ -73,7 +73,7 @@ export function bundleNameFromProfile(profile) {
73
73
  /**
74
74
  * Forward-base bundles set `.sdd-repo-kind` and qualify as the base
75
75
  * layer that additive extensions compose on top of. Additive bundles
76
- * (sdd-method's `reverse-extension-baseline` and ADR 0151 adopter
76
+ * (sdd-method's `reverse-extension-baseline` and M-ADR 0151 adopter
77
77
  * packs) return false. Accepts arbitrary strings; unknown names return
78
78
  * false.
79
79
  */