@wairon/cli 5.1.1-dev.74 → 5.1.1-dev.76

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.
package/dist/index.js CHANGED
@@ -1672,15 +1672,15 @@ var require_node = __commonJS({
1672
1672
  var slzh = function(d, b) {
1673
1673
  return b + 30 + b2(d, b + 26) + b2(d, b + 28);
1674
1674
  };
1675
- var zh = function(d, b, z11) {
1675
+ var zh = function(d, b, z12) {
1676
1676
  var fnl = b2(d, b + 28), efl = b2(d, b + 30), fn = strFromU8(d.subarray(b + 46, b + 46 + fnl), !(b2(d, b + 8) & 2048)), es = b + 46 + fnl;
1677
- var _a2 = z64hs(d, es, efl, z11, b4(d, b + 20), b4(d, b + 24), b4(d, b + 42)), sc = _a2[0], su = _a2[1], off = _a2[2];
1677
+ var _a2 = z64hs(d, es, efl, z12, b4(d, b + 20), b4(d, b + 24), b4(d, b + 42)), sc = _a2[0], su = _a2[1], off = _a2[2];
1678
1678
  return [b2(d, b + 10), sc, su, fn, es + efl + b2(d, b + 32), off];
1679
1679
  };
1680
- var z64hs = function(d, b, l, z11, sc, su, off) {
1680
+ var z64hs = function(d, b, l, z12, sc, su, off) {
1681
1681
  var nsc = sc == 4294967295, nsu = su == 4294967295, noff = off == 4294967295, e = b + l;
1682
1682
  var nf = nsc + nsu + noff;
1683
- if (z11 && nf) {
1683
+ if (z12 && nf) {
1684
1684
  for (; b + 4 < e; b += 4 + b2(d, b + 2)) {
1685
1685
  if (b2(d, b) == 1) {
1686
1686
  return [
@@ -1691,7 +1691,7 @@ var require_node = __commonJS({
1691
1691
  ];
1692
1692
  }
1693
1693
  }
1694
- if (z11 < 2)
1694
+ if (z12 < 2)
1695
1695
  err(13);
1696
1696
  }
1697
1697
  return [sc, su, off, 0];
@@ -2300,18 +2300,18 @@ var require_node = __commonJS({
2300
2300
  if (lft) {
2301
2301
  var c = lft;
2302
2302
  var o = b4(data, e + 16);
2303
- var z11 = b4(data, e - 20) == 117853008;
2304
- if (z11) {
2303
+ var z12 = b4(data, e - 20) == 117853008;
2304
+ if (z12) {
2305
2305
  var ze = b4(data, e - 12);
2306
- z11 = b4(data, ze) == 101075792;
2307
- if (z11) {
2306
+ z12 = b4(data, ze) == 101075792;
2307
+ if (z12) {
2308
2308
  c = lft = b4(data, ze + 32);
2309
2309
  o = b4(data, ze + 48);
2310
2310
  }
2311
2311
  }
2312
2312
  var fltr = opts && opts.filter;
2313
2313
  var _loop_3 = function(i3) {
2314
- var _a2 = zh(data, o, z11), c_1 = _a2[0], sc = _a2[1], su = _a2[2], fn = _a2[3], no = _a2[4], off = _a2[5], b = slzh(data, off);
2314
+ var _a2 = zh(data, o, z12), c_1 = _a2[0], sc = _a2[1], su = _a2[2], fn = _a2[3], no = _a2[4], off = _a2[5], b = slzh(data, off);
2315
2315
  o = no;
2316
2316
  var cbl = function(e2, d) {
2317
2317
  if (e2) {
@@ -2366,18 +2366,18 @@ var require_node = __commonJS({
2366
2366
  if (!c)
2367
2367
  return {};
2368
2368
  var o = b4(data, e + 16);
2369
- var z11 = b4(data, e - 20) == 117853008;
2370
- if (z11) {
2369
+ var z12 = b4(data, e - 20) == 117853008;
2370
+ if (z12) {
2371
2371
  var ze = b4(data, e - 12);
2372
- z11 = b4(data, ze) == 101075792;
2373
- if (z11) {
2372
+ z12 = b4(data, ze) == 101075792;
2373
+ if (z12) {
2374
2374
  c = b4(data, ze + 32);
2375
2375
  o = b4(data, ze + 48);
2376
2376
  }
2377
2377
  }
2378
2378
  var fltr = opts && opts.filter;
2379
2379
  for (var i2 = 0; i2 < c; ++i2) {
2380
- var _a2 = zh(data, o, z11), c_2 = _a2[0], sc = _a2[1], su = _a2[2], fn = _a2[3], no = _a2[4], off = _a2[5], b = slzh(data, off);
2380
+ var _a2 = zh(data, o, z12), c_2 = _a2[0], sc = _a2[1], su = _a2[2], fn = _a2[3], no = _a2[4], off = _a2[5], b = slzh(data, off);
2381
2381
  o = no;
2382
2382
  if (!fltr || fltr({
2383
2383
  name: fn,
@@ -2497,7 +2497,7 @@ var require_dist = __commonJS({
2497
2497
  scaffoldPack: () => scaffoldPack2
2498
2498
  });
2499
2499
  module2.exports = __toCommonJS2(src_exports2);
2500
- var path29 = __toESM2(require("path"));
2500
+ var path210 = __toESM2(require("path"));
2501
2501
  var import_js_yaml3 = require("js-yaml");
2502
2502
  var import_js_yaml = require("js-yaml");
2503
2503
  var import_crypto = require("crypto");
@@ -2510,8 +2510,8 @@ var require_dist = __commonJS({
2510
2510
  maxCompressionRatio: 100,
2511
2511
  maxDepth: 16
2512
2512
  };
2513
- function parseManifest(text) {
2514
- const raw = (0, import_js_yaml.load)(text);
2513
+ function parseManifest(text2) {
2514
+ const raw = (0, import_js_yaml.load)(text2);
2515
2515
  const manifest = validateEnvelope(raw);
2516
2516
  if (Math.trunc(manifest.formatVersion) > SUPPORTED_FORMAT_VERSION) {
2517
2517
  throw new Error("unsupported pack-archive formatVersion (newer than this wairon understands)");
@@ -2689,8 +2689,8 @@ var require_dist = __commonJS({
2689
2689
  var DERIVED_ROOTS = ["generated", "docs"];
2690
2690
  var EXECUTABLE_EXT_RE = /\.(c|m)?js$/i;
2691
2691
  var WAI_SEGMENT = ".wai";
2692
- function parseTreeManifest(text) {
2693
- const raw = (0, import_js_yaml2.load)(text);
2692
+ function parseTreeManifest(text2) {
2693
+ const raw = (0, import_js_yaml2.load)(text2);
2694
2694
  const manifest = validateEnvelope2(raw);
2695
2695
  if (Math.trunc(manifest.formatVersion) > SUPPORTED_FORMAT_VERSION2) {
2696
2696
  throw new Error("unsupported tree-archive formatVersion (newer than this wairon understands)");
@@ -3060,8 +3060,8 @@ var require_dist = __commonJS({
3060
3060
  function slug(name) {
3061
3061
  return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
3062
3062
  }
3063
- var fs18 = __toESM2(require("fs"));
3064
- var path30 = __toESM2(require("path"));
3063
+ var fs20 = __toESM2(require("fs"));
3064
+ var path31 = __toESM2(require("path"));
3065
3065
  var import_fflate = require_node();
3066
3066
  var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".hg", ".svn"]);
3067
3067
  function listEntries(archive) {
@@ -3099,9 +3099,9 @@ var require_dist = __commonJS({
3099
3099
  }
3100
3100
  function writeTree(destDir, files) {
3101
3101
  for (const file of files) {
3102
- const absolute = path30.join(destDir, file.path);
3103
- fs18.mkdirSync(path30.dirname(absolute), { recursive: true });
3104
- fs18.writeFileSync(absolute, file.contents);
3102
+ const absolute = path31.join(destDir, file.path);
3103
+ fs20.mkdirSync(path31.dirname(absolute), { recursive: true });
3104
+ fs20.writeFileSync(absolute, file.contents);
3105
3105
  }
3106
3106
  }
3107
3107
  function classifyKind(name, symlinks) {
@@ -3110,14 +3110,14 @@ var require_dist = __commonJS({
3110
3110
  return "file";
3111
3111
  }
3112
3112
  function walkPackDir(root, current2, out) {
3113
- for (const entry of fs18.readdirSync(current2, { withFileTypes: true })) {
3113
+ for (const entry of fs20.readdirSync(current2, { withFileTypes: true })) {
3114
3114
  if (entry.isDirectory()) {
3115
3115
  if (SKIP_DIRS.has(entry.name)) continue;
3116
- walkPackDir(root, path30.join(current2, entry.name), out);
3116
+ walkPackDir(root, path31.join(current2, entry.name), out);
3117
3117
  } else if (entry.isFile()) {
3118
- const absolute = path30.join(current2, entry.name);
3119
- const relative22 = path30.relative(root, absolute).split(path30.sep).join("/");
3120
- out.push({ path: relative22, contents: fs18.readFileSync(absolute) });
3118
+ const absolute = path31.join(current2, entry.name);
3119
+ const relative22 = path31.relative(root, absolute).split(path31.sep).join("/");
3120
+ out.push({ path: relative22, contents: fs20.readFileSync(absolute) });
3121
3121
  }
3122
3122
  }
3123
3123
  }
@@ -3167,7 +3167,7 @@ var require_dist = __commonJS({
3167
3167
  function scaffoldPack(request) {
3168
3168
  const files = render(request);
3169
3169
  writeTree(request.targetDir, files);
3170
- return files.map((file) => path29.join(request.targetDir, file.path));
3170
+ return files.map((file) => path210.join(request.targetDir, file.path));
3171
3171
  }
3172
3172
  function buildPack(sourceDir) {
3173
3173
  const files = readPackDir(sourceDir);
@@ -3224,7 +3224,7 @@ var require_dist = __commonJS({
3224
3224
  }
3225
3225
  const manifest = parseManifest(decode(envelope.contents));
3226
3226
  verifyIntegrity2(manifest, files);
3227
- const directory = path29.resolve(destDir);
3227
+ const directory = path210.resolve(destDir);
3228
3228
  writeTree(directory, files);
3229
3229
  const result = {
3230
3230
  directory,
@@ -3302,7 +3302,7 @@ var require_dist = __commonJS({
3302
3302
  }
3303
3303
  const manifest = parseTreeManifest(decode(envelope.contents));
3304
3304
  verifyTreeIntegrity(manifest, files);
3305
- const directory = path29.resolve(destDir);
3305
+ const directory = path210.resolve(destDir);
3306
3306
  writeTree(directory, files);
3307
3307
  const result = {
3308
3308
  destDir: directory,
@@ -3317,7 +3317,7 @@ var require_dist = __commonJS({
3317
3317
  return normalized === "." ? ".wai/" : `${normalized}/.wai/`;
3318
3318
  }
3319
3319
  function normalizeRootPath(relativePath) {
3320
- const posix2 = relativePath.split(path29.sep).join("/").replace(/^\.\//, "").replace(/\/+$/, "");
3320
+ const posix2 = relativePath.split(path210.sep).join("/").replace(/^\.\//, "").replace(/\/+$/, "");
3321
3321
  return posix2 === "" || posix2 === "." ? "." : posix2;
3322
3322
  }
3323
3323
  function slugify(projectName) {
@@ -3616,7 +3616,7 @@ __export(src_exports, {
3616
3616
  loadImplementationSpecs: () => loadImplementationSpecs,
3617
3617
  loadInterfaceSpec: () => loadInterfaceSpec,
3618
3618
  loadInterfaceSpecs: () => loadInterfaceSpecs,
3619
- loadProjectConfig: () => loadProjectConfig2,
3619
+ loadProjectConfig: () => loadProjectConfig3,
3620
3620
  loadProjectExtensions: () => loadProjectExtensions,
3621
3621
  loadProjectVariants: () => loadProjectVariants,
3622
3622
  loadRegistry: () => loadRegistry,
@@ -3687,6 +3687,7 @@ __export(src_exports, {
3687
3687
  saveSpec: () => saveSpec,
3688
3688
  saveTopologyConfig: () => saveTopologyConfig,
3689
3689
  scanAllSpecs: () => scanAllSpecs,
3690
+ schemaFingerprint: () => schemaFingerprint,
3690
3691
  serializeYaml: () => serializeYaml,
3691
3692
  setExecutionTier: () => setExecutionTier,
3692
3693
  setLogLevel: () => setLogLevel,
@@ -3975,6 +3976,21 @@ var ExecutionBudgetSchema = import_zod.z.object({
3975
3976
  allowNestedDelegation: import_zod.z.boolean(),
3976
3977
  mcp: McpAccessSchema
3977
3978
  });
3979
+ function summarize(budget, profile) {
3980
+ const lines = [
3981
+ `- **Work shape**: ${profile.breadth} breadth, ${profile.reasoningDepth} reasoning${profile.writes ? "" : ", read-only"}${profile.delegates ? ", delegating" : ""}`,
3982
+ `- **Why**: ${profile.rationale}`
3983
+ ];
3984
+ if (budget.modelTier) lines.push(`- **Capability tier**: ${budget.modelTier}`);
3985
+ if (budget.effort) lines.push(`- **Effort**: ${budget.effort}`);
3986
+ if (budget.maxTurns !== void 0) {
3987
+ lines.push(`- **Turn ceiling**: ${budget.maxTurns} (a circuit breaker \u2014 hitting it should read as a scoping error, not a limit to work up to)`);
3988
+ }
3989
+ lines.push(`- **Tool grant**: ${budget.toolClass}`);
3990
+ lines.push(`- **May delegate further**: ${budget.allowNestedDelegation ? "yes" : "no"}`);
3991
+ lines.push(`- **MCP access**: ${budget.mcp}`);
3992
+ return lines;
3993
+ }
3978
3994
  var BudgetTierSchema = import_zod.z.enum(["off", "free", "default", "trade", "aggressive"]);
3979
3995
  var ExecutionConfigSchema = import_zod.z.object({
3980
3996
  /**
@@ -5609,9 +5625,9 @@ var MethodSignatureSchema = import_zod8.z.object({
5609
5625
  ext: ExtDataSchema.optional()
5610
5626
  });
5611
5627
  var INTENT_FLOOR_MIN_CHARS = 40;
5612
- function passesIntentFloor(text, methodName) {
5613
- if (!text) return false;
5614
- const t = text.trim();
5628
+ function passesIntentFloor(text2, methodName) {
5629
+ if (!text2) return false;
5630
+ const t = text2.trim();
5615
5631
  if (t.length < INTENT_FLOOR_MIN_CHARS) return false;
5616
5632
  const norm = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
5617
5633
  if (norm(t) === norm(methodName)) return false;
@@ -6357,10 +6373,10 @@ function stepGraph(method) {
6357
6373
  const s = byNum.get(n);
6358
6374
  if (s.type !== "parallel" || s.endStep === void 0 || !s.branches?.length) continue;
6359
6375
  const entries = s.branches.map((b) => b.step).sort((a, b) => a - b);
6360
- const join17 = fallNext(s.endStep);
6376
+ const join19 = fallNext(s.endStep);
6361
6377
  for (let i = 0; i < entries.length; i++) {
6362
6378
  const armEnd = i + 1 < entries.length ? prevOf(entries[i + 1]) : s.endStep;
6363
- if (armEnd !== void 0 && armEnd >= entries[i]) armEndJoin.set(armEnd, join17);
6379
+ if (armEnd !== void 0 && armEnd >= entries[i]) armEndJoin.set(armEnd, join19);
6364
6380
  }
6365
6381
  }
6366
6382
  const successorsOf = (n) => {
@@ -7060,12 +7076,12 @@ function buildExcalidrawScene(model, L) {
7060
7076
  locked: false
7061
7077
  };
7062
7078
  }
7063
- function boundLabel(rect, text, fontSize, verticalAlign) {
7079
+ function boundLabel(rect, text2, fontSize, verticalAlign) {
7064
7080
  const id = rect.id + "-label";
7065
7081
  const label = base(id, "text", { x: rect.x + 8, y: rect.y + 6, w: rect.width - 16, h: 20 });
7066
7082
  label.roundness = null;
7067
- label.text = text;
7068
- label.originalText = text;
7083
+ label.text = text2;
7084
+ label.originalText = text2;
7069
7085
  label.fontSize = fontSize;
7070
7086
  label.fontFamily = 1;
7071
7087
  label.textAlign = "center";
@@ -11573,6 +11589,161 @@ var SURFACES_DIRNAME = "surfaces";
11573
11589
  function surfacesDir(rootDir) {
11574
11590
  return path9.join(rootDir, ".wai", SURFACES_DIRNAME);
11575
11591
  }
11592
+ function audienceRank(audience) {
11593
+ const idx = SURFACE_AUDIENCES.indexOf(audience ?? "instance");
11594
+ return idx === -1 ? SURFACE_AUDIENCES.indexOf("instance") : idx;
11595
+ }
11596
+ function stateIdString() {
11597
+ const s = computeStateId();
11598
+ return `${s.algorithm}:${s.digest}`;
11599
+ }
11600
+ function computeTypeClosure(entries, types) {
11601
+ const included = /* @__PURE__ */ new Map();
11602
+ const queue = [];
11603
+ const enqueueRef = (ref) => {
11604
+ if (BUILTIN_TYPES.has(ref.toLowerCase())) return;
11605
+ for (const spec of types) {
11606
+ const qualifiedId = spec.subsystem && !spec.id.startsWith(`${spec.subsystem}::`) ? `${spec.subsystem}::${spec.id}` : spec.id;
11607
+ if (matchTypeRef(ref, qualifiedId) && !included.has(spec.id)) {
11608
+ included.set(spec.id, spec);
11609
+ queue.push(spec.id);
11610
+ }
11611
+ }
11612
+ };
11613
+ for (const entry of entries) {
11614
+ for (const m of entry.methods) {
11615
+ for (const ref of methodTypeRefs(m)) enqueueRef(ref);
11616
+ }
11617
+ }
11618
+ while (queue.length) {
11619
+ const spec = included.get(queue.shift());
11620
+ for (const field of spec.fields) {
11621
+ for (const ref of extractTypeIdentifiers(field.type)) enqueueRef(ref);
11622
+ }
11623
+ }
11624
+ return [...included.values()].map((t) => ({
11625
+ id: t.id,
11626
+ name: t.name,
11627
+ kind: t.kind,
11628
+ fields: t.fields.map((f) => ({
11629
+ name: f.name,
11630
+ type: f.type,
11631
+ ...f.description ? { description: f.description } : {},
11632
+ ...f.optional ? { optional: true } : {}
11633
+ }))
11634
+ }));
11635
+ }
11636
+ function projectOwnSurface(maxAudience) {
11637
+ const system = loadSystemSpec();
11638
+ if (!system) {
11639
+ throw new Error("Cannot project a surface: the L0 system spec is missing.");
11640
+ }
11641
+ const subsystems = loadSubsystemSpecs();
11642
+ const components = loadComponentSpecs();
11643
+ const interfaces = loadInterfaceSpecs();
11644
+ const types = loadTypeSpecs();
11645
+ const floor = audienceRank(maxAudience);
11646
+ const rawEntries = system.publicInterfaces ?? [];
11647
+ const entries = [];
11648
+ for (const raw of rawEntries) {
11649
+ const audience = raw.audience ?? "instance";
11650
+ if (audienceRank(audience) < floor) continue;
11651
+ if (!raw.component) continue;
11652
+ const comp = components.find((c) => c.id === raw.component);
11653
+ if (!comp) continue;
11654
+ const compInterfaces = interfaces.filter((i) => i.component === comp.id && (!raw.interface || i.id === raw.interface));
11655
+ const methods = compInterfaces.flatMap((i) => i.methods);
11656
+ const subsystemType = subsystems.find((s) => s.id === comp.subsystem)?.publicInterfaces.find((pi) => pi.component === comp.id)?.type;
11657
+ entries.push({
11658
+ id: raw.id ?? raw.interface ?? comp.id,
11659
+ name: raw.name ?? comp.name,
11660
+ audience,
11661
+ type: raw.type ?? subsystemType ?? "Custom",
11662
+ component: comp.id,
11663
+ methods,
11664
+ ...comp.dispatch && comp.dispatch.length ? { dispatch: comp.dispatch } : {},
11665
+ // Project the backing Portal's auth + basePath so the codec can emit
11666
+ // OpenAPI security + per-portal servers self-contained from the snapshot.
11667
+ ...comp.auth && comp.auth.scheme !== "none" ? { auth: comp.auth } : {},
11668
+ ...comp.basePath ? { basePath: comp.basePath } : {},
11669
+ details: raw.details ?? "",
11670
+ ...raw.version ? { version: raw.version } : {},
11671
+ ...raw.stability ? { stability: raw.stability } : {}
11672
+ });
11673
+ }
11674
+ return SurfaceSnapshotSchema.parse({
11675
+ projectName: system.name,
11676
+ origin: "generated",
11677
+ stateId: stateIdString(),
11678
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
11679
+ interfaces: entries,
11680
+ types: computeTypeClosure(entries, types)
11681
+ });
11682
+ }
11683
+ function projectChildSurface() {
11684
+ return projectOwnSurface("project");
11685
+ }
11686
+ function localName(id) {
11687
+ return id.split("::").pop();
11688
+ }
11689
+ var CROSS_BOUNDARY_TARGETS = /* @__PURE__ */ new Set(["Portal", "Observer"]);
11690
+ function projectSubsystemSurface(subsystemId) {
11691
+ const system = loadSystemSpec();
11692
+ if (!system) {
11693
+ throw new Error("Cannot project a subsystem surface: the L0 system spec is missing.");
11694
+ }
11695
+ const subsystems = loadSubsystemSpecs();
11696
+ const target = subsystems.find((s) => s.id === subsystemId);
11697
+ if (!target) {
11698
+ throw new Error(`Cannot project a subsystem surface: subsystem "${subsystemId}" does not exist.`);
11699
+ }
11700
+ const components = loadComponentSpecs();
11701
+ const interfaces = loadInterfaceSpecs();
11702
+ const types = loadTypeSpecs();
11703
+ const entries = [];
11704
+ const unprojectable = [];
11705
+ for (const pub of target.publicInterfaces ?? []) {
11706
+ if (!pub.component) continue;
11707
+ const comp = components.find((c) => c.id === pub.component || c.id === `${subsystemId}::${pub.component}`);
11708
+ if (!comp) continue;
11709
+ if (!CROSS_BOUNDARY_TARGETS.has(comp.componentType)) {
11710
+ unprojectable.push({ component: pub.component, componentType: comp.componentType });
11711
+ continue;
11712
+ }
11713
+ const compInterfaces = interfaces.filter((i) => i.component === comp.id && (!pub.interface || i.id === pub.interface || i.id === `${subsystemId}::${pub.interface}`));
11714
+ const methods = compInterfaces.flatMap((i) => i.methods);
11715
+ entries.push({
11716
+ id: localName(pub.interface ?? comp.id),
11717
+ name: comp.name,
11718
+ // Family ceiling: a sibling surface is consumable by the system family only.
11719
+ audience: "project",
11720
+ type: pub.type ?? "Custom",
11721
+ // The snapshot carries the LOCAL portal name — consumers resolve cross-tree
11722
+ // refs by their final segment.
11723
+ component: localName(comp.id),
11724
+ methods,
11725
+ ...comp.dispatch && comp.dispatch.length ? { dispatch: comp.dispatch } : {},
11726
+ // Project the backing component's auth + basePath so the codec can emit
11727
+ // OpenAPI security + per-portal servers self-contained from the snapshot.
11728
+ ...comp.auth && comp.auth.scheme !== "none" ? { auth: comp.auth } : {},
11729
+ ...comp.basePath ? { basePath: comp.basePath } : {},
11730
+ details: pub.details ?? ""
11731
+ });
11732
+ }
11733
+ for (const skipped of unprojectable) {
11734
+ console.error(
11735
+ `[surfaces] skipped "${subsystemId}::${skipped.component}": a published ${skipped.componentType} can never serve a cross-boundary caller, so it stays out of every chained child's sibling surface \u2014 publish this surface through a Portal, or an Observer (for events).`
11736
+ );
11737
+ }
11738
+ return SurfaceSnapshotSchema.parse({
11739
+ projectName: `${system.name}::${subsystemId}`,
11740
+ origin: "generated",
11741
+ stateId: stateIdString(),
11742
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
11743
+ interfaces: entries,
11744
+ types: computeTypeClosure(entries, types)
11745
+ });
11746
+ }
11576
11747
  function listSnapshots(rootDir = getProjectRoot()) {
11577
11748
  const dir = surfacesDir(rootDir);
11578
11749
  if (!fs7.existsSync(dir)) return [];
@@ -11597,6 +11768,43 @@ function listMountSnapshots(mounts) {
11597
11768
  }
11598
11769
  return out;
11599
11770
  }
11771
+ function projectFamilySurfaces(parent) {
11772
+ return runWithProjectRoot(parent.parentRoot, () => {
11773
+ const siblings = loadSubsystemSpecs().filter((s) => !s.id.includes("::") && s.id !== parent.subsystemId);
11774
+ return [projectChildSurface(), ...siblings.map((s) => projectSubsystemSurface(s.id))];
11775
+ });
11776
+ }
11777
+ function projectedFamilyContent(parent) {
11778
+ try {
11779
+ return new Map(projectFamilySurfaces(parent).map((s) => [s.projectName, surfaceContentKey(s)]));
11780
+ } catch {
11781
+ return null;
11782
+ }
11783
+ }
11784
+ function listExternalInterfaces() {
11785
+ const snapshots = listSnapshots();
11786
+ const chainingParent = resolveChainingParent();
11787
+ const projected = chainingParent ? projectedFamilyContent(chainingParent) : null;
11788
+ return snapshots.map((snapshot) => {
11789
+ const generated = snapshot.origin === "generated";
11790
+ const sourceKind = !generated ? "foreign" : snapshot.projectName.includes("::") ? "sibling" : "parent";
11791
+ const freshness = generated && projected ? projected.get(snapshot.projectName) === surfaceContentKey(snapshot) ? "fresh" : "stale" : "unverifiable";
11792
+ return {
11793
+ projectName: snapshot.projectName,
11794
+ origin: snapshot.origin,
11795
+ sourceKind,
11796
+ generatedAt: snapshot.generatedAt,
11797
+ ...snapshot.stateId ? { stateId: snapshot.stateId } : {},
11798
+ ...snapshot.version ? { version: snapshot.version } : {},
11799
+ freshness,
11800
+ interfaceIds: snapshot.interfaces.map((e) => e.id)
11801
+ };
11802
+ });
11803
+ }
11804
+ function surfaceContentKey(snapshot) {
11805
+ const { stateId, generatedAt, origin, ...content } = snapshot;
11806
+ return JSON.stringify(content);
11807
+ }
11600
11808
 
11601
11809
  // src/core/extensions.ts
11602
11810
  var fs9 = __toESM(require("fs"));
@@ -11650,7 +11858,7 @@ function defaultTargetConfig(type) {
11650
11858
  enabled: true
11651
11859
  };
11652
11860
  }
11653
- var WAIRON_VERSION = "5.1.1-dev.74";
11861
+ var WAIRON_VERSION = "5.1.1-dev.76";
11654
11862
  var GITHUB_REPO = "SYW-Apps/Waffle-AIron";
11655
11863
  var ARCHITECT_AGENT_ID = "agent-architect";
11656
11864
  var ARCHITECT_TEMPLATE_ID = "architect";
@@ -11901,7 +12109,7 @@ var PackSkillSchema = import_zod9.z.object({
11901
12109
  });
11902
12110
  var PackInstructionBlockSchema = import_zod9.z.union([
11903
12111
  // Scalar shorthand: `instructions: >- …` — the common case, unscoped.
11904
- import_zod9.z.string().min(1).transform((text) => ({ text })),
12112
+ import_zod9.z.string().min(1).transform((text2) => ({ text: text2 })),
11905
12113
  import_zod9.z.object({
11906
12114
  text: import_zod9.z.string().min(1),
11907
12115
  profile: import_zod9.z.array(import_zod9.z.string().min(1)).min(1).optional()
@@ -12361,21 +12569,21 @@ function buildCodeIndex(model) {
12361
12569
  const declarations = /* @__PURE__ */ new Map();
12362
12570
  const anchors = /* @__PURE__ */ new Map();
12363
12571
  const findingAnchors = /* @__PURE__ */ new Map();
12364
- const namesAt = (cache, path29, make) => {
12365
- const cached = cache.get(path29);
12572
+ const namesAt = (cache, path31, make) => {
12573
+ const cached = cache.get(path31);
12366
12574
  if (cached) return cached;
12367
- const f = facts.get(path29);
12575
+ const f = facts.get(path31);
12368
12576
  if (!f) return NO_NAMES;
12369
12577
  const built = make(f);
12370
- cache.set(path29, built);
12578
+ cache.set(path31, built);
12371
12579
  return built;
12372
12580
  };
12373
12581
  const republished = /* @__PURE__ */ new Map();
12374
- const republishedFrom = (path29) => {
12375
- const cached = republished.get(path29);
12582
+ const republishedFrom = (path31) => {
12583
+ const cached = republished.get(path31);
12376
12584
  if (cached) return cached;
12377
- const out = /* @__PURE__ */ new Set([path29]);
12378
- const queue = [path29];
12585
+ const out = /* @__PURE__ */ new Set([path31]);
12586
+ const queue = [path31];
12379
12587
  while (queue.length) {
12380
12588
  const from = queue.pop();
12381
12589
  const f = facts.get(from);
@@ -12388,7 +12596,7 @@ function buildCodeIndex(model) {
12388
12596
  }
12389
12597
  }
12390
12598
  }
12391
- republished.set(path29, out);
12599
+ republished.set(path31, out);
12392
12600
  return out;
12393
12601
  };
12394
12602
  const NO_ORIGIN = /* @__PURE__ */ new Set();
@@ -12397,9 +12605,9 @@ function buildCodeIndex(model) {
12397
12605
  return to ? republishedFrom(to) : NO_ORIGIN;
12398
12606
  };
12399
12607
  const scopeOf = (site, from) => {
12400
- const path29 = pathKey(site.from ?? from);
12401
- const f = facts.get(path29);
12402
- return f?.status === "analyzed" && f.analysisGrade === "exact" ? { path: path29, facts: f } : void 0;
12608
+ const path31 = pathKey(site.from ?? from);
12609
+ const f = facts.get(path31);
12610
+ return f?.status === "analyzed" && f.analysisGrade === "exact" ? { path: path31, facts: f } : void 0;
12403
12611
  };
12404
12612
  const originOf2 = (site, from) => {
12405
12613
  const resolved = scopeOf(site, from);
@@ -12438,14 +12646,14 @@ function buildCodeIndex(model) {
12438
12646
  if (site.via) for (const typeName of localTypesOf(f, site.via)) widenThroughType(typeName);
12439
12647
  return out;
12440
12648
  };
12441
- const declarationsAt = (path29) => namesAt(declarations, pathKey(path29), (f) => /* @__PURE__ */ new Set([...f.declaredNames, ...f.exportedNames]));
12649
+ const declarationsAt = (path31) => namesAt(declarations, pathKey(path31), (f) => /* @__PURE__ */ new Set([...f.declaredNames, ...f.exportedNames]));
12442
12650
  return {
12443
12651
  paths,
12444
12652
  exactPaths,
12445
- factsAt: (path29) => facts.get(pathKey(path29)),
12653
+ factsAt: (path31) => facts.get(pathKey(path31)),
12446
12654
  declarationsAt,
12447
- anchorsAt: (path29) => namesAt(anchors, pathKey(path29), (f) => /* @__PURE__ */ new Set([...f.declaredNames, ...f.exportedNames, ...f.anchoredNames])),
12448
- findingAnchorsAt: (path29) => namesAt(findingAnchors, pathKey(path29), (f) => new Set(f.anchoredNames)),
12655
+ anchorsAt: (path31) => namesAt(anchors, pathKey(path31), (f) => /* @__PURE__ */ new Set([...f.declaredNames, ...f.exportedNames, ...f.anchoredNames])),
12656
+ findingAnchorsAt: (path31) => namesAt(findingAnchors, pathKey(path31), (f) => new Set(f.anchoredNames)),
12449
12657
  originOf: originOf2,
12450
12658
  possibleOriginsOf
12451
12659
  };
@@ -12483,10 +12691,10 @@ function buildRealizationIndex(ctx) {
12483
12691
  return {
12484
12692
  paths,
12485
12693
  implementationsOf: (compId) => implsByComponent.get(compId) ?? NONE_IMPL,
12486
- implementationsAt: (path29) => implsByPath.get(pathKey(path29)) ?? NONE_IMPL,
12694
+ implementationsAt: (path31) => implsByPath.get(pathKey(path31)) ?? NONE_IMPL,
12487
12695
  filesOf: (compId) => filesByComponent.get(compId) ?? NONE_PATH,
12488
12696
  filesIn: (subsystemId) => filesBySubsystem.get(subsystemId) ?? NONE_PATH,
12489
- componentsAt: (path29) => componentsByPath.get(pathKey(path29)) ?? NONE_COMP
12697
+ componentsAt: (path31) => componentsByPath.get(pathKey(path31)) ?? NONE_COMP
12490
12698
  };
12491
12699
  }
12492
12700
  function buildImportGraph(index, universe) {
@@ -12498,22 +12706,22 @@ function buildImportGraph(index, universe) {
12498
12706
  if (to && to !== from) into.add(to);
12499
12707
  }
12500
12708
  };
12501
- const importsOf = (path29) => {
12502
- const cached = imports.get(path29);
12709
+ const importsOf = (path31) => {
12710
+ const cached = imports.get(path31);
12503
12711
  if (cached) return cached;
12504
- const facts = index.factsAt(path29);
12712
+ const facts = index.factsAt(path31);
12505
12713
  const out = /* @__PURE__ */ new Set();
12506
- if (facts?.status === "analyzed") resolveAll(path29, facts.imports, out);
12507
- imports.set(path29, out);
12714
+ if (facts?.status === "analyzed") resolveAll(path31, facts.imports, out);
12715
+ imports.set(path31, out);
12508
12716
  return out;
12509
12717
  };
12510
- const tracesOf = (path29) => {
12511
- const cached = traces.get(path29);
12718
+ const tracesOf = (path31) => {
12719
+ const cached = traces.get(path31);
12512
12720
  if (cached) return cached;
12513
- const facts = index.factsAt(path29);
12514
- const out = new Set(importsOf(path29));
12515
- if (facts?.status === "analyzed") resolveAll(path29, facts.reexports, out);
12516
- traces.set(path29, out);
12721
+ const facts = index.factsAt(path31);
12722
+ const out = new Set(importsOf(path31));
12723
+ if (facts?.status === "analyzed") resolveAll(path31, facts.reexports, out);
12724
+ traces.set(path31, out);
12517
12725
  return out;
12518
12726
  };
12519
12727
  return {
@@ -13850,11 +14058,11 @@ var unconditionalCallCyclesRule = {
13850
14058
  const memberEdges = keys.flatMap((k) => (adjacency.get(k) ?? []).filter((e) => inScc.has(e.toKey)));
13851
14059
  if (memberEdges.length === 0) continue;
13852
14060
  const anchor = [...memberEdges].sort((a, b) => byCodeUnit(a.fromKey, b.fromKey) || byCodeUnit(a.impl.id, b.impl.id) || a.stepNumber - b.stepNumber)[0];
13853
- const path29 = [...keys].sort(byCodeUnit).join(" \u2192 ");
14061
+ const path31 = [...keys].sort(byCodeUnit).join(" \u2192 ");
13854
14062
  ctx.addIssue(
13855
14063
  "warning",
13856
14064
  "UNCONDITIONAL_CALL_CYCLE",
13857
- `Call cycle with no guard: ${path29} \u2014 every call edge in this cycle is unavoidable on all paths of its narrative (e.g. step ${anchor.stepNumber} of "${anchor.methodName}" in "${anchor.impl.id}" always calls ${anchor.toLabel}). This recurses without a base case, by construction. Guard at least one edge with a branch/return before the call, or lint.allow with the termination argument.`,
14065
+ `Call cycle with no guard: ${path31} \u2014 every call edge in this cycle is unavoidable on all paths of its narrative (e.g. step ${anchor.stepNumber} of "${anchor.methodName}" in "${anchor.impl.id}" always calls ${anchor.toLabel}). This recurses without a base case, by construction. Guard at least one edge with a branch/return before the call, or lint.allow with the termination argument.`,
13858
14066
  anchor.impl.id,
13859
14067
  memberEdges.some((e) => ctx.isImplementationDraft(e.impl))
13860
14068
  );
@@ -15521,8 +15729,8 @@ var lifecycleRule = {
15521
15729
  };
15522
15730
 
15523
15731
  // src/core/rules/wiring/portal-mounts.ts
15524
- function liesUnder(path29, prefix) {
15525
- return path29 === prefix || path29.startsWith(prefix + "/");
15732
+ function liesUnder(path31, prefix) {
15733
+ return path31 === prefix || path31.startsWith(prefix + "/");
15526
15734
  }
15527
15735
  var portalMountsRule = {
15528
15736
  name: "portal-mounts",
@@ -15975,8 +16183,8 @@ function componentOfImpl(ctx, implContract) {
15975
16183
  }
15976
16184
  var CLAIM_PHRASES = /\b(persist(s|ed|ent|ing|ence)?|survives?\s+(a\s+)?restart|writ(es?|ten)\s+to\s+disk|registered\s+into|durabl[ey])\b/i;
15977
16185
  var QUOTED_SPAN = /"[^"]*"|“[^”]*”|`[^`]*`|(?<![\p{L}\p{N}])['‘](?:[^'‘’]|(?<=[\p{L}\p{N}])['’](?=[\p{L}\p{N}]))*['’](?![\p{L}\p{N}])/gu;
15978
- function withoutQuotedText(text) {
15979
- return text.replace(QUOTED_SPAN, " ");
16186
+ function withoutQuotedText(text2) {
16187
+ return text2.replace(QUOTED_SPAN, " ");
15980
16188
  }
15981
16189
  var proseClaimRule = {
15982
16190
  name: "prose-claims",
@@ -16690,11 +16898,11 @@ var hiddenStateRule = {
16690
16898
  byPath.set(key, list);
16691
16899
  }
16692
16900
  }
16693
- for (const path29 of code.exactPaths) {
16694
- const facts = code.factsAt(path29);
16901
+ for (const path31 of code.exactPaths) {
16902
+ const facts = code.factsAt(path31);
16695
16903
  const bindings = facts.topLevelMutableBindings ?? [];
16696
16904
  if (bindings.length === 0) continue;
16697
- const mapped = byPath.get(path29) ?? [];
16905
+ const mapped = byPath.get(path31) ?? [];
16698
16906
  if (mapped.length === 0) continue;
16699
16907
  if (!mapped.every((m) => isStatelessLogic(m.component))) continue;
16700
16908
  const anchor = [...mapped].sort((a, b) => a.impl.id.localeCompare(b.impl.id))[0];
@@ -16760,7 +16968,7 @@ var dependencyConformanceRule = {
16760
16968
  }
16761
16969
  return false;
16762
16970
  };
16763
- const draftAt = (path29) => realization.implementationsAt(path29).some((impl) => ctx.isImplementationDraft(impl));
16971
+ const draftAt = (path31) => realization.implementationsAt(path31).some((impl) => ctx.isImplementationDraft(impl));
16764
16972
  for (const fromPath of mappedPaths) {
16765
16973
  const fromComponents = realization.componentsAt(fromPath);
16766
16974
  for (const toPath of graph.importsOf(fromPath)) {
@@ -16845,7 +17053,7 @@ var portalImportsRule = {
16845
17053
  }
16846
17054
  return files;
16847
17055
  };
16848
- const draftAt = (path29) => realization.implementationsAt(path29).some((impl) => ctx.isImplementationDraft(impl));
17056
+ const draftAt = (path31) => realization.implementationsAt(path31).some((impl) => ctx.isImplementationDraft(impl));
16849
17057
  for (const fromPath of mappedPaths) {
16850
17058
  const fromSubsystems = new Set(realization.componentsAt(fromPath).map((c) => c.subsystem));
16851
17059
  for (const toPath of graph.importsOf(fromPath)) {
@@ -17306,7 +17514,7 @@ var paramConformanceRule = {
17306
17514
  codeNameOf.set(type.id, named);
17307
17515
  if (type.subsystem) codeNameOf.set(`${type.subsystem}.${type.id}`, named);
17308
17516
  }
17309
- const normalize3 = (text) => text.replace(/\s+/g, " ").trim();
17517
+ const normalize3 = (text2) => text2.replace(/\s+/g, " ").trim();
17310
17518
  const typeAgrees = (declared, realized) => {
17311
17519
  if (!realized.type) return false;
17312
17520
  const stated = normalize3(declared.type);
@@ -17381,8 +17589,8 @@ var paramConformanceRule = {
17381
17589
  };
17382
17590
 
17383
17591
  // src/core/rules/conformance/route-coverage.ts
17384
- function liesUnder2(path29, prefix) {
17385
- return path29 === prefix || path29.startsWith(prefix + "/");
17592
+ function liesUnder2(path31, prefix) {
17593
+ return path31 === prefix || path31.startsWith(prefix + "/");
17386
17594
  }
17387
17595
  function normalizeSegment(segment) {
17388
17596
  return /^\{.+\}$/.test(segment) || segment.startsWith(":") ? "*" : segment;
@@ -18137,9 +18345,9 @@ function makeMatcher(tech) {
18137
18345
  const joined = parts.join("");
18138
18346
  if (joined.length < 3) return null;
18139
18347
  const n = parts.length;
18140
- return (text) => {
18141
- if (!text) return false;
18142
- const words2 = text.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
18348
+ return (text2) => {
18349
+ if (!text2) return false;
18350
+ const words2 = text2.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
18143
18351
  if (words2.some((w) => w.includes(joined))) return true;
18144
18352
  if (n < 2) return false;
18145
18353
  for (let i = 0; i + n <= words2.length; i++) {
@@ -19077,6 +19285,9 @@ function ruleSequence() {
19077
19285
  const audits = [carriedDebtRule, lintAllowsRule].filter((r) => ruleSet.includes(r));
19078
19286
  return [...ruleSet.filter((r) => !audits.includes(r)), ...audits];
19079
19287
  }
19288
+ function specScopedRules() {
19289
+ return ruleSequence().filter((r) => r.scope === "spec");
19290
+ }
19080
19291
  function knownIssueCodes() {
19081
19292
  return listRules().flatMap((r) => r.codes);
19082
19293
  }
@@ -19211,8 +19422,8 @@ LANGUAGE_PATTERNS.cpp = LANGUAGE_PATTERNS.c;
19211
19422
  var PATTERN_ANALYSIS_MAX_BYTES = 1e6;
19212
19423
  var IDENTIFIER_RE = /[A-Za-z_$][\w$]*/g;
19213
19424
  var STRING_RE = /'([^'\\\n]*(?:\\.[^'\\\n]*)*)'|"([^"\\\n]*(?:\\.[^"\\\n]*)*)"|`([^`\\]*(?:\\.[^`\\]*)*)`/g;
19214
- function stripComments(text, patterns) {
19215
- let out = text;
19425
+ function stripComments(text2, patterns) {
19426
+ let out = text2;
19216
19427
  for (const [open, close] of patterns.blockComments) {
19217
19428
  const re = new RegExp(`${escapeRe(open)}[\\s\\S]*?${escapeRe(close)}`, "g");
19218
19429
  out = out.replace(re, " ");
@@ -19238,16 +19449,16 @@ function boundNames(captured, localOnly) {
19238
19449
  }
19239
19450
  return names;
19240
19451
  }
19241
- function collectStrings(text) {
19452
+ function collectStrings(text2) {
19242
19453
  const anchors = /* @__PURE__ */ new Set();
19243
- for (const m of text.matchAll(STRING_RE)) {
19454
+ for (const m of text2.matchAll(STRING_RE)) {
19244
19455
  const value = m[1] ?? m[2] ?? m[3];
19245
19456
  if (value) anchors.add(value);
19246
19457
  }
19247
19458
  return anchors;
19248
19459
  }
19249
- function analyzeWithPatterns(text, patterns) {
19250
- const stripped = stripComments(text, patterns);
19460
+ function analyzeWithPatterns(text2, patterns) {
19461
+ const stripped = stripComments(text2, patterns);
19251
19462
  const declared = /* @__PURE__ */ new Set();
19252
19463
  const exported = /* @__PURE__ */ new Set();
19253
19464
  const imports = /* @__PURE__ */ new Set();
@@ -19275,7 +19486,7 @@ function analyzeWithPatterns(text, patterns) {
19275
19486
  }
19276
19487
  }
19277
19488
  }
19278
- const anchors = collectStrings(text);
19489
+ const anchors = collectStrings(text2);
19279
19490
  for (const m of stripped.matchAll(IDENTIFIER_RE)) anchors.add(m[0]);
19280
19491
  return {
19281
19492
  analysisGrade: "pattern",
@@ -19286,11 +19497,11 @@ function analyzeWithPatterns(text, patterns) {
19286
19497
  reexports: []
19287
19498
  };
19288
19499
  }
19289
- function analyzeGeneric(text) {
19500
+ function analyzeGeneric(text2) {
19290
19501
  const words2 = /* @__PURE__ */ new Set();
19291
- for (const m of text.matchAll(IDENTIFIER_RE)) words2.add(m[0]);
19502
+ for (const m of text2.matchAll(IDENTIFIER_RE)) words2.add(m[0]);
19292
19503
  const anchors = new Set(words2);
19293
- for (const s of collectStrings(text)) anchors.add(s);
19504
+ for (const s of collectStrings(text2)) anchors.add(s);
19294
19505
  return {
19295
19506
  analysisGrade: "generic",
19296
19507
  declaredNames: [...words2],
@@ -19860,14 +20071,14 @@ function buildCodeModel(implementations, types, projectRoot, sourceRoots = [], e
19860
20071
  files.push({ path: sourcePath, status: "unreadable", ...empty });
19861
20072
  continue;
19862
20073
  }
19863
- const text = buffer.toString("utf8");
20074
+ const text2 = buffer.toString("utf8");
19864
20075
  const language = EXTENSION_LANGUAGE[path13.extname(sourcePath).toLowerCase()];
19865
20076
  let analyzed;
19866
20077
  if (language === "typescript" || language === "javascript") {
19867
20078
  const ts = resolveTypeScript(projectRoot);
19868
20079
  if (ts) {
19869
20080
  try {
19870
- const facts = walkExact(ts, text, absolute);
20081
+ const facts = walkExact(ts, text2, absolute);
19871
20082
  chaseReexports(ts, facts, absolute, projectRoot, /* @__PURE__ */ new Set([absolute]), exactCache);
19872
20083
  analyzed = {
19873
20084
  analysisGrade: "exact",
@@ -19889,20 +20100,62 @@ function buildCodeModel(implementations, types, projectRoot, sourceRoots = [], e
19889
20100
  reexportOnly: facts.reexportOnly
19890
20101
  };
19891
20102
  } catch {
19892
- analyzed = analyzeGeneric(text);
20103
+ analyzed = analyzeGeneric(text2);
19893
20104
  }
19894
20105
  } else {
19895
- analyzed = analyzeWithPatterns(text, LANGUAGE_PATTERNS[language]);
20106
+ analyzed = analyzeWithPatterns(text2, LANGUAGE_PATTERNS[language]);
19896
20107
  }
19897
- } else if (language && LANGUAGE_PATTERNS[language] && text.length <= PATTERN_ANALYSIS_MAX_BYTES) {
19898
- analyzed = analyzeWithPatterns(text, LANGUAGE_PATTERNS[language]);
20108
+ } else if (language && LANGUAGE_PATTERNS[language] && text2.length <= PATTERN_ANALYSIS_MAX_BYTES) {
20109
+ analyzed = analyzeWithPatterns(text2, LANGUAGE_PATTERNS[language]);
19899
20110
  } else {
19900
- analyzed = analyzeGeneric(text);
20111
+ analyzed = analyzeGeneric(text2);
19901
20112
  }
19902
20113
  files.push({ path: sourcePath, status: "analyzed", language, ...analyzed });
19903
20114
  }
19904
20115
  return { files, projectRoot, rootFiles };
19905
20116
  }
20117
+ function readTestFiles(testRoots, projectRoot) {
20118
+ const scanned = [];
20119
+ for (const key of walkDeclaredRoots(testRoots, [], projectRoot)) {
20120
+ let text2;
20121
+ try {
20122
+ const buffer = fs10.readFileSync(path13.resolve(projectRoot, key));
20123
+ if (looksBinary(buffer)) continue;
20124
+ text2 = buffer.toString("utf8");
20125
+ } catch {
20126
+ continue;
20127
+ }
20128
+ const imports = /* @__PURE__ */ new Set();
20129
+ for (const m of text2.matchAll(NAMED_IMPORT_BINDINGS_RE)) {
20130
+ for (const name of boundNames(m[1] ?? "", false)) imports.add(name);
20131
+ }
20132
+ const words2 = /* @__PURE__ */ new Set();
20133
+ for (const m of text2.matchAll(IDENTIFIER_RE)) words2.add(m[0]);
20134
+ scanned.push({ path: key, imports, words: words2 });
20135
+ }
20136
+ return scanned;
20137
+ }
20138
+ function findTestsReferencing(methods, projectRoot, testRoots = []) {
20139
+ const scanned = readTestFiles(testRoots, projectRoot);
20140
+ if (scanned.length === 0) return [];
20141
+ const indiscriminateAbove = scanned.length / 10;
20142
+ const found = [];
20143
+ for (const method of methods) {
20144
+ const symbol = method.symbol ?? method.name;
20145
+ const imported = scanned.filter((f) => f.imports.has(symbol)).map((f) => f.path);
20146
+ const mentioned = scanned.filter((f) => !f.imports.has(symbol) && f.words.has(symbol)).map((f) => f.path);
20147
+ const indiscriminate = mentioned.length > indiscriminateAbove;
20148
+ if (imported.length === 0 && mentioned.length === 0) continue;
20149
+ found.push({
20150
+ method: method.name,
20151
+ symbol,
20152
+ imported,
20153
+ mentioned: indiscriminate ? [] : mentioned,
20154
+ indiscriminate
20155
+ });
20156
+ }
20157
+ return found;
20158
+ }
19906
20159
 
19907
20160
  // src/core/rules/index.ts
19908
20161
  function composeRuleSequence(extraRules = []) {
@@ -20275,7 +20528,7 @@ function buildRuleContext(opts) {
20275
20528
  }
20276
20529
  const severity = getRuleSeverity(code, defaultSeverity, isDraftContext, owner);
20277
20530
  if (severity === "off") return;
20278
- let text = message;
20531
+ let text2 = message;
20279
20532
  if (specId) {
20280
20533
  const key = `${specId}\0${code}`;
20281
20534
  let seen = sitesSeen.get(key);
@@ -20293,7 +20546,7 @@ function buildRuleContext(opts) {
20293
20546
  if (grew.length === 0) {
20294
20547
  if (severity === "warning") return;
20295
20548
  } else {
20296
- text = `${text} A lint.allow covers this site, but not ${grew.length} part(s) of it \u2014 ${grew.map((u) => `"${u}"`).join("; ")} ${grew.length === 1 ? "is" : "are"} new. Decide on them: add them to the allow's \`covers\` with a reason that is actually true, or fix them.`;
20549
+ text2 = `${text2} A lint.allow covers this site, but not ${grew.length} part(s) of it \u2014 ${grew.map((u) => `"${u}"`).join("; ")} ${grew.length === 1 ? "is" : "are"} new. Decide on them: add them to the allow's \`covers\` with a reason that is actually true, or fix them.`;
20297
20550
  }
20298
20551
  }
20299
20552
  }
@@ -20308,13 +20561,13 @@ function buildRuleContext(opts) {
20308
20561
  else grew.push(unit);
20309
20562
  }
20310
20563
  if (grew.length === 0) return;
20311
- text = `${text} The conformance debt register carries this finding, but not ${grew.length} part(s) of it \u2014 ${grew.map((u) => `"${u}"`).join("; ")} ${grew.length === 1 ? "is" : "are"} new. Fix them, or add them to the entry's \`covers\` with a reason that is actually true.`;
20564
+ text2 = `${text2} The conformance debt register carries this finding, but not ${grew.length} part(s) of it \u2014 ${grew.map((u) => `"${u}"`).join("; ")} ${grew.length === 1 ? "is" : "are"} new. Fix them, or add them to the entry's \`covers\` with a reason that is actually true.`;
20312
20565
  }
20313
20566
  }
20314
20567
  issues.push({
20315
20568
  severity,
20316
20569
  code,
20317
- message: text,
20570
+ message: text2,
20318
20571
  specId,
20319
20572
  ...isDraftContext ? { draftContext: true } : {},
20320
20573
  ...surfaceResolved ? { surfaceResolved: true } : {}
@@ -20399,6 +20652,54 @@ function buildRuleContext(opts) {
20399
20652
  return ctx;
20400
20653
  }
20401
20654
 
20655
+ // src/core/rules/candidate.ts
20656
+ function stubSystem() {
20657
+ const now = (/* @__PURE__ */ new Date()).toISOString();
20658
+ return {
20659
+ schemaVersion: "1.0.0",
20660
+ name: "candidate",
20661
+ vision: "",
20662
+ boundaries: [],
20663
+ globalRequirements: [],
20664
+ databases: [],
20665
+ createdAt: now,
20666
+ updatedAt: now
20667
+ };
20668
+ }
20669
+ function validateComponentCandidate(candidate, opts = {}) {
20670
+ const issues = [];
20671
+ registerBuiltinRules();
20672
+ if (opts.extensions?.rules?.length) registerPackRules(opts.extensions.rules);
20673
+ const knownCodes = /* @__PURE__ */ new Set([
20674
+ ...knownIssueCodes().map((rc) => rc.code),
20675
+ ...(opts.extensions?.assertions ?? []).map((a) => a.fullCode)
20676
+ ]);
20677
+ const ctx = buildRuleContext({
20678
+ system: stubSystem(),
20679
+ subsystems: [],
20680
+ components: [candidate],
20681
+ interfaces: [],
20682
+ implementations: [],
20683
+ types: [],
20684
+ rules: opts.rules,
20685
+ projectType: opts.projectType ?? "backend",
20686
+ extensions: opts.extensions,
20687
+ // Only a tree-scoped rule reads these (roundtrip-serialization); no
20688
+ // spec-scoped rule does, so a candidate carries none.
20689
+ roundTripIssues: [],
20690
+ knownIssueCodes: knownCodes,
20691
+ issues
20692
+ });
20693
+ for (const rule of specScopedRules()) {
20694
+ rule.check(ctx);
20695
+ }
20696
+ const own = issues.filter((i) => !i.specId || i.specId === candidate.id);
20697
+ return {
20698
+ errors: own.filter((i) => i.severity === "error"),
20699
+ warnings: own.filter((i) => i.severity === "warning")
20700
+ };
20701
+ }
20702
+
20402
20703
  // src/core/validation.ts
20403
20704
  var path14 = __toESM(require("path"));
20404
20705
  function projectPackSelections() {
@@ -20408,6 +20709,9 @@ function projectPackSelections() {
20408
20709
  return [];
20409
20710
  }
20410
20711
  }
20712
+ function findTestsReferencing2(methods, projectRoot, testRoots = []) {
20713
+ return findTestsReferencing(methods, projectRoot, testRoots);
20714
+ }
20411
20715
  var RESOLUTION_FAILURE_CODES = /* @__PURE__ */ new Set([
20412
20716
  "UNDEFINED_TYPE_REFERENCE",
20413
20717
  "INVALID_DEPENDENCY_REFERENCE",
@@ -20446,6 +20750,67 @@ function stricterSeverities(parent, child) {
20446
20750
  function issue(severity, code, message, agentId, specId) {
20447
20751
  return { severity, code, message, agentId, specId };
20448
20752
  }
20753
+ function validateRegistry(registry, rules) {
20754
+ const issues = [];
20755
+ const idCounts = /* @__PURE__ */ new Map();
20756
+ for (const agent of registry.agents) {
20757
+ idCounts.set(agent.id, (idCounts.get(agent.id) ?? 0) + 1);
20758
+ }
20759
+ for (const [id, count] of idCounts) {
20760
+ if (count > 1) {
20761
+ issues.push(issue("error", "DUPLICATE_AGENT_ID", `Duplicate agent id: "${id}"`, id));
20762
+ }
20763
+ }
20764
+ for (const agent of registry.agents) {
20765
+ validateAgent(agent, rules, issues);
20766
+ }
20767
+ if (rules.noOverlappingOwnership) {
20768
+ checkOverlappingOwnership(registry.agents, issues);
20769
+ }
20770
+ return {
20771
+ valid: issues.every((i) => i.severity !== "error"),
20772
+ issues
20773
+ };
20774
+ }
20775
+ function validateAgent(agent, rules, issues) {
20776
+ const isMeta = agent.tags.some((t) => rules.metaAgentTags.includes(t));
20777
+ if (rules.requireOwnedPaths && !isMeta && agent.ownedPaths.length === 0) {
20778
+ issues.push(
20779
+ issue(
20780
+ "warning",
20781
+ "NO_OWNED_PATHS",
20782
+ `Agent "${agent.id}" has no ownedPaths. Add paths or tag as meta/guardian.`,
20783
+ agent.id
20784
+ )
20785
+ );
20786
+ }
20787
+ if (agent.targets.length === 0) {
20788
+ issues.push(
20789
+ issue("warning", "NO_TARGETS", `Agent "${agent.id}" has no output targets configured.`, agent.id)
20790
+ );
20791
+ }
20792
+ }
20793
+ function checkOverlappingOwnership(agents, issues) {
20794
+ const pathToAgents = /* @__PURE__ */ new Map();
20795
+ for (const agent of agents) {
20796
+ for (const p of agent.ownedPaths) {
20797
+ const owners = pathToAgents.get(p) ?? [];
20798
+ owners.push(agent.id);
20799
+ pathToAgents.set(p, owners);
20800
+ }
20801
+ }
20802
+ for (const [p, owners] of pathToAgents) {
20803
+ if (owners.length > 1) {
20804
+ issues.push(
20805
+ issue(
20806
+ "error",
20807
+ "OVERLAPPING_OWNERSHIP",
20808
+ `Path "${p}" is claimed by multiple agents: ${owners.join(", ")}`
20809
+ )
20810
+ );
20811
+ }
20812
+ }
20813
+ }
20449
20814
  function validateSddTree(rulesOrOptions, projectType = "backend") {
20450
20815
  let rules = rulesOrOptions;
20451
20816
  let scopeSubsystem;
@@ -20631,16 +20996,16 @@ function resolveThroughParent(boundRoot, treatAllAsComplete, childRules) {
20631
20996
  }));
20632
20997
  return { root: top, scope, issues };
20633
20998
  }
20634
- function stripNamespace(text, scope) {
20999
+ function stripNamespace(text2, scope) {
20635
21000
  const prefix = `${scope}::`;
20636
21001
  let out = "";
20637
21002
  let from = 0;
20638
- for (let at = text.indexOf(prefix); at !== -1; at = text.indexOf(prefix, from)) {
20639
- const atBoundary = at === 0 || !isIdChar(text[at - 1]);
20640
- out += text.slice(from, at) + (atBoundary ? "" : prefix);
21003
+ for (let at = text2.indexOf(prefix); at !== -1; at = text2.indexOf(prefix, from)) {
21004
+ const atBoundary = at === 0 || !isIdChar(text2[at - 1]);
21005
+ out += text2.slice(from, at) + (atBoundary ? "" : prefix);
20641
21006
  from = at + prefix.length;
20642
21007
  }
20643
- return out + text.slice(from);
21008
+ return out + text2.slice(from);
20644
21009
  }
20645
21010
  function isIdChar(ch) {
20646
21011
  return ch >= "a" && ch <= "z" || ch >= "A" && ch <= "Z" || ch >= "0" && ch <= "9" || ch === "_" || ch === "-" || ch === "." || ch === ":";
@@ -20833,11 +21198,11 @@ var IdPool = class {
20833
21198
  return candidate;
20834
21199
  }
20835
21200
  };
20836
- function escapeLabel(text) {
20837
- return text.replace(/"/g, "#quot;");
21201
+ function escapeLabel(text2) {
21202
+ return text2.replace(/"/g, "#quot;");
20838
21203
  }
20839
- function truncate(text, max) {
20840
- const clean = text.replace(/\s+/g, " ").trim();
21204
+ function truncate(text2, max) {
21205
+ const clean = text2.replace(/\s+/g, " ").trim();
20841
21206
  return clean.length <= max ? clean : `${clean.slice(0, max - 1)}\u2026`;
20842
21207
  }
20843
21208
  function nodeDecl(id, label, comp) {
@@ -21684,8 +22049,8 @@ function rewriteSpecRefs(raw, remap) {
21684
22049
  }
21685
22050
  function summarizeValue(value) {
21686
22051
  if (value === void 0) return "";
21687
- const text = typeof value === "string" ? value : JSON.stringify(value) ?? String(value);
21688
- return text.length > 120 ? `${text.slice(0, 117)}...` : text;
22052
+ const text2 = typeof value === "string" ? value : JSON.stringify(value) ?? String(value);
22053
+ return text2.length > 120 ? `${text2.slice(0, 117)}...` : text2;
21689
22054
  }
21690
22055
  function identityKeyOf(field, item) {
21691
22056
  if (item === null || typeof item !== "object") return null;
@@ -21760,6 +22125,9 @@ function specChanges(before, after, prefix = "") {
21760
22125
  if (after.length === 0 && before.length > 0) {
21761
22126
  return [{ path: prefix, change: "cleared", before: summarizeValue(before) }];
21762
22127
  }
22128
+ if ((prefix.split(".").pop() ?? prefix) === "narrative" && [...before, ...after].every((i) => i !== null && typeof i === "object" && !Array.isArray(i))) {
22129
+ return narrativeChanges(before, after, prefix);
22130
+ }
21763
22131
  const identified = [...before, ...after].every((i) => reportIdentityOf(prefix.split(".").pop() ?? prefix, i) !== null);
21764
22132
  if (!identified) {
21765
22133
  return JSON.stringify(before) === JSON.stringify(after) ? [] : [{ path: prefix, change: "set", before: summarizeValue(before), after: summarizeValue(after) }];
@@ -21793,6 +22161,152 @@ function specChanges(before, after, prefix = "") {
21793
22161
  if (JSON.stringify(before) === JSON.stringify(after)) return [];
21794
22162
  return [{ path: prefix, change: "set", before: summarizeValue(before), after: summarizeValue(after) }];
21795
22163
  }
22164
+ var STEP_JUMP_FIELDS = ["onTrueStep", "onFalseStep", "defaultStep", "endStep", "finallyStep", "toStep"];
22165
+ var STEP_JUMP_LISTS = [["cases", "value"], ["catches", "error"], ["branches", "name"]];
22166
+ function jumpsOf(step) {
22167
+ const jumps = /* @__PURE__ */ new Map();
22168
+ for (const field of STEP_JUMP_FIELDS) {
22169
+ if (typeof step[field] === "number") jumps.set(field, step[field]);
22170
+ }
22171
+ for (const [list, name] of STEP_JUMP_LISTS) {
22172
+ const entries = step[list];
22173
+ if (!Array.isArray(entries)) continue;
22174
+ entries.forEach((entry, i) => {
22175
+ if (entry && typeof entry.step === "number") jumps.set(`${list}.${String(entry[name] ?? i)}.step`, entry.step);
22176
+ });
22177
+ }
22178
+ return jumps;
22179
+ }
22180
+ function withoutNumbers(step) {
22181
+ const out = { ...step };
22182
+ delete out.stepNumber;
22183
+ for (const field of STEP_JUMP_FIELDS) delete out[field];
22184
+ for (const [list] of STEP_JUMP_LISTS) {
22185
+ if (Array.isArray(out[list])) {
22186
+ out[list] = out[list].map((entry) => {
22187
+ if (!entry || typeof entry !== "object") return entry;
22188
+ const { step: _step, ...rest } = entry;
22189
+ return rest;
22190
+ });
22191
+ }
22192
+ }
22193
+ return out;
22194
+ }
22195
+ function canonicalJson(value) {
22196
+ return JSON.stringify(value, (_key, v) => v && typeof v === "object" && !Array.isArray(v) ? Object.fromEntries(Object.keys(v).sort().map((k) => [k, v[k]])) : v);
22197
+ }
22198
+ function commonRun(a, b, same) {
22199
+ const rows = a.length + 1;
22200
+ const cols = b.length + 1;
22201
+ const table = new Uint32Array(rows * cols);
22202
+ for (let i2 = a.length - 1; i2 >= 0; i2--) {
22203
+ for (let j2 = b.length - 1; j2 >= 0; j2--) {
22204
+ table[i2 * cols + j2] = same(a[i2], b[j2]) ? table[(i2 + 1) * cols + j2 + 1] + 1 : Math.max(table[(i2 + 1) * cols + j2], table[i2 * cols + j2 + 1]);
22205
+ }
22206
+ }
22207
+ const pairs = [];
22208
+ let i = 0;
22209
+ let j = 0;
22210
+ while (i < a.length && j < b.length) {
22211
+ if (same(a[i], b[j])) {
22212
+ pairs.push([i, j]);
22213
+ i++;
22214
+ j++;
22215
+ } else if (table[(i + 1) * cols + j] >= table[i * cols + j + 1]) i++;
22216
+ else j++;
22217
+ }
22218
+ return pairs;
22219
+ }
22220
+ function recognisablySame(a, b) {
22221
+ if (typeof a.description === "string" && a.description === b.description) return true;
22222
+ if (typeof a.label === "string" && a.label === b.label) return true;
22223
+ return typeof a.targetComponent === "string" && a.type === b.type && a.targetComponent === b.targetComponent && a.targetMethod === b.targetMethod;
22224
+ }
22225
+ function pairSteps(before, after) {
22226
+ const keysBefore = before.map((s) => canonicalJson(withoutNumbers(s)));
22227
+ const keysAfter = after.map((s) => canonicalJson(withoutNumbers(s)));
22228
+ const anchors = commonRun(keysBefore, keysAfter, (x, y) => x === y);
22229
+ const pairs = [];
22230
+ const bounds = [[-1, -1], ...anchors, [before.length, after.length]];
22231
+ for (let g = 0; g < bounds.length - 1; g++) {
22232
+ const [oi, ni] = bounds[g];
22233
+ const [oj, nj] = bounds[g + 1];
22234
+ const gapBefore = before.slice(oi + 1, oj).map((step, k) => ({ step, at: oi + 1 + k }));
22235
+ const gapAfter = after.slice(ni + 1, nj).map((step, k) => ({ step, at: ni + 1 + k }));
22236
+ const similar = commonRun(gapBefore, gapAfter, (x, y) => recognisablySame(x.step, y.step)).map(([x, y]) => [gapBefore[x].at, gapAfter[y].at]);
22237
+ const inner = [[oi, ni], ...similar, [oj, nj]];
22238
+ for (let k = 0; k < inner.length - 1; k++) {
22239
+ const [a0, b0] = inner[k];
22240
+ const [a1, b1] = inner[k + 1];
22241
+ if (a1 - a0 === b1 - b0) for (let d = 1; d < a1 - a0; d++) pairs.push([a0 + d, b0 + d]);
22242
+ if (k < inner.length - 2) pairs.push(inner[k + 1]);
22243
+ }
22244
+ if (g < bounds.length - 2) pairs.push(bounds[g + 1]);
22245
+ }
22246
+ return pairs.sort((x, y) => x[0] - y[0]);
22247
+ }
22248
+ function numberOf(step, index) {
22249
+ return typeof step.stepNumber === "number" ? step.stepNumber : index + 1;
22250
+ }
22251
+ function renumberedRuns(moves) {
22252
+ const runs = [];
22253
+ const range = (a, b) => a === b ? `step ${a}` : `steps ${a}-${b}`;
22254
+ let start = 0;
22255
+ for (let k = 1; k <= moves.length; k++) {
22256
+ const continues = k < moves.length && moves[k][0] === moves[k - 1][0] + 1 && moves[k][1] - moves[k][0] === moves[start][1] - moves[start][0];
22257
+ if (continues) continue;
22258
+ const [firstOld, firstNew] = moves[start];
22259
+ const [lastOld, lastNew] = moves[k - 1];
22260
+ runs.push({ from: range(firstOld, lastOld), to: range(firstNew, lastNew) });
22261
+ start = k;
22262
+ }
22263
+ return runs;
22264
+ }
22265
+ function narrativeChanges(before, after, prefix) {
22266
+ const pairs = pairSteps(before, after);
22267
+ const oldToNew = /* @__PURE__ */ new Map();
22268
+ for (const [o, n] of pairs) oldToNew.set(numberOf(before[o], o), numberOf(after[n], n));
22269
+ const pairedOld = new Set(pairs.map(([o]) => o));
22270
+ const pairedNew = new Set(pairs.map(([, n]) => n));
22271
+ const removed = before.flatMap((step, o) => pairedOld.has(o) ? [] : [{
22272
+ path: `${prefix}.step ${numberOf(step, o)}`,
22273
+ change: "removed",
22274
+ before: summarizeValue(step)
22275
+ }]);
22276
+ const added = after.flatMap((step, n) => pairedNew.has(n) ? [] : [{
22277
+ path: `${prefix}.step ${numberOf(step, n)}`,
22278
+ change: "added",
22279
+ after: summarizeValue(step)
22280
+ }]);
22281
+ const edited = [];
22282
+ const relocated = [];
22283
+ const moves = [];
22284
+ for (const [o, n] of pairs) {
22285
+ const oldNumber = numberOf(before[o], o);
22286
+ const newNumber = numberOf(after[n], n);
22287
+ const at = oldNumber === newNumber ? `${prefix}.step ${newNumber}` : `${prefix}.step ${newNumber} (was ${oldNumber})`;
22288
+ edited.push(...specChanges(withoutNumbers(before[o]), withoutNumbers(after[n]), at));
22289
+ const oldJumps = jumpsOf(before[o]);
22290
+ const newJumps = jumpsOf(after[n]);
22291
+ for (const field of /* @__PURE__ */ new Set([...oldJumps.keys(), ...newJumps.keys()])) {
22292
+ const was = oldJumps.get(field);
22293
+ const now = newJumps.get(field);
22294
+ if (was === now) continue;
22295
+ if (was === void 0) edited.push({ path: `${at}.${field}`, change: "added", after: String(now) });
22296
+ else if (now === void 0) edited.push({ path: `${at}.${field}`, change: "removed", before: String(was) });
22297
+ else if (oldToNew.get(was) === now) relocated.push({ path: `${at}.${field}`, change: "relocated", before: String(was), after: String(now) });
22298
+ else edited.push({ path: `${at}.${field}`, change: "set", before: String(was), after: String(now) });
22299
+ }
22300
+ if (oldNumber !== newNumber && !edited.some((c) => c.path.startsWith(`${at}.`))) moves.push([oldNumber, newNumber]);
22301
+ }
22302
+ const renumbered = renumberedRuns(moves).map(({ from, to }) => ({
22303
+ path: prefix,
22304
+ change: "renumbered",
22305
+ before: from,
22306
+ after: to
22307
+ }));
22308
+ return [...removed, ...added, ...edited, ...renumbered, ...relocated];
22309
+ }
21796
22310
  var DELTA_VERBS = /* @__PURE__ */ new Set(["action", "remove", "captureJumps"]);
21797
22311
  var JUMP_LABEL_TWINS = /* @__PURE__ */ new Set([
21798
22312
  "onTrueLabel",
@@ -21850,13 +22364,13 @@ function ineffectiveDeltaPaths(delta, merged, stored) {
21850
22364
  const isPlain = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
21851
22365
  const same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
21852
22366
  const at = (prefix, key) => prefix ? `${prefix}.${key}` : key;
21853
- const say = (path29, why) => {
21854
- out.push(`${path29} \u2014 ${why}`);
22367
+ const say = (path31, why) => {
22368
+ out.push(`${path31} \u2014 ${why}`);
21855
22369
  };
21856
- const walkValue = (d, m, s, path29, field) => {
22370
+ const walkValue = (d, m, s, path31, field) => {
21857
22371
  if (Array.isArray(d)) {
21858
22372
  if (!Array.isArray(m)) {
21859
- say(path29, "the level does not have this field, so the write dropped it");
22373
+ say(path31, "the level does not have this field, so the write dropped it");
21860
22374
  return;
21861
22375
  }
21862
22376
  if (field === "narrative" && d.some(carriesEditMarker)) return;
@@ -21864,32 +22378,32 @@ function ineffectiveDeltaPaths(delta, merged, stored) {
21864
22378
  if (carriesEditMarker(element)) continue;
21865
22379
  const key = reportIdentityOf(field, element);
21866
22380
  if (key === null) {
21867
- if (!same(m, d)) say(path29, "the level does not keep this value, so the write dropped it");
21868
- else if (same(s, d)) say(path29, "the stored spec already held this value");
22381
+ if (!same(m, d)) say(path31, "the level does not keep this value, so the write dropped it");
22382
+ else if (same(s, d)) say(path31, "the stored spec already held this value");
21869
22383
  return;
21870
22384
  }
21871
22385
  const mMatch = m.find((i) => reportIdentityOf(field, i) === key);
21872
22386
  const sMatch = Array.isArray(s) ? s.find((i) => reportIdentityOf(field, i) === key) : void 0;
21873
22387
  if (mMatch === void 0) {
21874
- say(at(path29, key), "the write did not store this element");
22388
+ say(at(path31, key), "the write did not store this element");
21875
22389
  continue;
21876
22390
  }
21877
- walk2(element, mMatch, sMatch, at(path29, key), field);
22391
+ walk2(element, mMatch, sMatch, at(path31, key), field);
21878
22392
  }
21879
22393
  return;
21880
22394
  }
21881
22395
  if (isPlain(d)) {
21882
22396
  if (!isPlain(m)) {
21883
- say(path29, "the level does not have this field, so the write dropped it");
22397
+ say(path31, "the level does not have this field, so the write dropped it");
21884
22398
  return;
21885
22399
  }
21886
- walk2(d, m, s, path29, field);
22400
+ walk2(d, m, s, path31, field);
21887
22401
  return;
21888
22402
  }
21889
- if (!same(m, d)) say(path29, "the write did not store it \u2014 the level's schema does not keep this value");
21890
- else if (same(s, d)) say(path29, "the stored spec already held this value");
22403
+ if (!same(m, d)) say(path31, "the write did not store it \u2014 the level's schema does not keep this value");
22404
+ else if (same(s, d)) say(path31, "the stored spec already held this value");
21891
22405
  };
21892
- function walk2(d, m, s, path29, container) {
22406
+ function walk2(d, m, s, path31, container) {
21893
22407
  for (const [key, value] of Object.entries(d)) {
21894
22408
  if (DELTA_VERBS.has(key) || JUMP_LABEL_TWINS.has(key)) continue;
21895
22409
  if (key === "label" && LABEL_TWIN_CONTAINERS.has(container)) continue;
@@ -21899,13 +22413,13 @@ function ineffectiveDeltaPaths(delta, merged, stored) {
21899
22413
  if (typeof name !== "string") continue;
21900
22414
  const had = isPlain(s) && s[name] !== void 0;
21901
22415
  const still = isPlain(m) && m[name] !== void 0;
21902
- if (!had) say(at(path29, name), "unset named a field the stored spec did not have");
21903
- else if (still) say(at(path29, name), "unset did not remove it");
22416
+ if (!had) say(at(path31, name), "unset named a field the stored spec did not have");
22417
+ else if (still) say(at(path31, name), "unset did not remove it");
21904
22418
  }
21905
22419
  continue;
21906
22420
  }
21907
22421
  if (value === void 0 || value === null) continue;
21908
- const here = at(path29, key);
22422
+ const here = at(path31, key);
21909
22423
  const mValue = isPlain(m) ? m[key] : void 0;
21910
22424
  const sValue = isPlain(s) ? s[key] : void 0;
21911
22425
  if (mValue === void 0) {
@@ -23756,14 +24270,14 @@ var SpecWorkspace = class {
23756
24270
  - ${labelErrors.join("\n- ")}`);
23757
24271
  }
23758
24272
  }
23759
- const canonical = (spec) => {
24273
+ const canonical2 = (spec) => {
23760
24274
  const parsed = deltaSchema.safeParse(spec);
23761
24275
  const value = { ...parsed.success ? parsed.data : spec };
23762
24276
  delete value.updatedAt;
23763
24277
  return value;
23764
24278
  };
23765
- const canonicalStored = canonical(storedBefore);
23766
- const canonicalMerged = canonical(mergedResult);
24279
+ const canonicalStored = canonical2(storedBefore);
24280
+ const canonicalMerged = canonical2(mergedResult);
23767
24281
  const changes = specChanges(canonicalStored, canonicalMerged);
23768
24282
  const ineffective = ineffectiveDeltaPaths(
23769
24283
  { ...qualifiedDelta, ...unsetFields.length ? { unset: unsetFields } : {} },
@@ -23876,7 +24390,8 @@ var SpecWorkspace = class {
23876
24390
  notices: plan.notices,
23877
24391
  summary: refusedMoveSummary(from, to, methods, verdict.refusals, alternatives),
23878
24392
  dryRun,
23879
- mentions
24393
+ mentions,
24394
+ created: []
23880
24395
  };
23881
24396
  }
23882
24397
  }
@@ -23892,9 +24407,10 @@ var SpecWorkspace = class {
23892
24407
  refusals: [],
23893
24408
  alternatives: [],
23894
24409
  notices: plan.notices,
23895
- summary: `Dry run: moving ${namedMethods(methods)} from "${from}" to "${to}" would touch ${edits2.length} spec${edits2.length === 1 ? "" : "s"} and re-point ${repointed} reference${repointed === 1 ? "" : "s"}. Nothing was written.`,
24410
+ summary: `Dry run: moving ${namedMethods(methods)} from "${from}" to "${to}" would touch ${edits2.length} spec${edits2.length === 1 ? "" : "s"}${createdClause(plan.created, "would create")} and re-point ${repointed} reference${repointed === 1 ? "" : "s"}. Nothing was written.`,
23896
24411
  dryRun: true,
23897
- mentions
24412
+ mentions,
24413
+ created: plan.created
23898
24414
  };
23899
24415
  }
23900
24416
  const edits = this.writeMoveEdits([...plan.edits.values()]);
@@ -23908,9 +24424,10 @@ var SpecWorkspace = class {
23908
24424
  refusals: [],
23909
24425
  alternatives: [],
23910
24426
  notices: plan.notices,
23911
- summary: `Moved ${namedMethods(methods)} from "${from}" to "${to}": ${edits.length} spec${edits.length === 1 ? "" : "s"} written, ${repointed} reference${repointed === 1 ? "" : "s"} re-pointed${mentions.length ? `, ${mentions.length} still naming the old home in prose or on the wire` : ""}.`,
24427
+ summary: `Moved ${namedMethods(methods)} from "${from}" to "${to}": ${edits.length} spec${edits.length === 1 ? "" : "s"} written${createdClause(plan.created, "created")}, ${repointed} reference${repointed === 1 ? "" : "s"} re-pointed${mentions.length ? `, ${mentions.length} still naming the old home in prose or on the wire` : ""}.`,
23912
24428
  dryRun: false,
23913
- mentions
24429
+ mentions,
24430
+ created: plan.created
23914
24431
  };
23915
24432
  }
23916
24433
  /**
@@ -23943,15 +24460,26 @@ var SpecWorkspace = class {
23943
24460
  }
23944
24461
  const receiving = this.moveTargetContract(to, ofTarget);
23945
24462
  if (typeof receiving === "string") return receiving;
23946
- const travelling = this.loadImplementationSpecs().some((impl) => ofSource.some((i) => i.id === impl.contract) && impl.methods.some((m) => methods.includes(m.name)));
23947
- if (travelling && !this.loadImplementationSpecs().some((i) => i.contract === receiving.id)) {
23948
- return `no implementation realizes "${receiving.id}", so the moved methods' narratives would have nowhere to arrive.`;
24463
+ const contractId = receiving?.id ?? `i${to}`;
24464
+ const holder = contracts.find((i) => i.id === contractId);
24465
+ if (!receiving && holder) {
24466
+ return `"${to}" declares no contract, and the one the move would create for it, "${contractId}", is already the id of "${holder.component}"'s contract \u2014 rename that contract, or give "${to}" its own first.`;
24467
+ }
24468
+ const implementations = this.loadImplementationSpecs();
24469
+ const travelling = implementations.some((impl) => ofSource.some((i) => i.id === impl.contract) && impl.methods.some((m) => methods.includes(m.name)));
24470
+ const implId = `${to}_impl`;
24471
+ const implHolder = implementations.find((i) => i.id === implId);
24472
+ if (travelling && !implementations.some((i) => i.contract === contractId) && implHolder) {
24473
+ return `no implementation realizes "${contractId}", and the one the move would create for it, "${implId}", is already the id of the implementation of "${implHolder.contract}" \u2014 rename it, or realize "${contractId}" first.`;
23949
24474
  }
23950
24475
  return null;
23951
24476
  }
23952
- /** The contract of `to` that receives the methods, or why that cannot be decided. */
24477
+ /**
24478
+ * The contract of `to` that receives the methods; null when it declares none
24479
+ * yet, so the move creates `i<to>`; or why the choice cannot be decided.
24480
+ */
23953
24481
  moveTargetContract(to, ofTarget) {
23954
- if (ofTarget.length === 0) return `"${to}" declares no contract, so the methods would have nowhere to arrive.`;
24482
+ if (ofTarget.length === 0) return null;
23955
24483
  if (ofTarget.length === 1) return ofTarget[0];
23956
24484
  const named = ofTarget.find((i) => i.id === `i${to}` || i.id.endsWith(`::i${to}`));
23957
24485
  return named ?? `"${to}" declares ${ofTarget.length} contracts and none is named "i${to}", so which one receives the methods is not decidable.`;
@@ -23964,7 +24492,7 @@ var SpecWorkspace = class {
23964
24492
  */
23965
24493
  planMethodMove(source, to, methods, contracts) {
23966
24494
  const target = this.loadComponentSpec(to);
23967
- const receiving = this.moveTargetContract(to, contracts.filter((i) => i.component === to));
24495
+ const stated = this.moveTargetContract(to, contracts.filter((i) => i.component === to));
23968
24496
  const implementations = this.loadImplementationSpecs();
23969
24497
  const plan = {
23970
24498
  edits: /* @__PURE__ */ new Map(),
@@ -23973,10 +24501,13 @@ var SpecWorkspace = class {
23973
24501
  reach: [],
23974
24502
  newDependencies: [],
23975
24503
  wireBound: [],
23976
- notices: []
24504
+ notices: [],
24505
+ created: []
23977
24506
  };
24507
+ const receiving = stated ?? createdContract(target, source.id, methods, contracts);
23978
24508
  const arriving = [];
23979
24509
  const travelling = [];
24510
+ const realizedIn = /* @__PURE__ */ new Map();
23980
24511
  for (const contract of contracts.filter((i) => i.component === source.id)) {
23981
24512
  const leaving = contract.methods.filter((m) => methods.includes(m.name));
23982
24513
  if (leaving.length === 0) continue;
@@ -23991,17 +24522,19 @@ var SpecWorkspace = class {
23991
24522
  const moving = impl.methods.filter((m) => methods.includes(m.name));
23992
24523
  if (moving.length === 0) continue;
23993
24524
  travelling.push(...cloneSpec(moving));
24525
+ for (const entry of moving) if (impl.sourcePath) realizedIn.set(entry.name, impl.sourcePath);
23994
24526
  const kept = cloneSpec(impl);
23995
24527
  kept.methods = kept.methods.filter((m) => !methods.includes(m.name));
23996
24528
  this.stageMoveEdit(plan, "implementation", impl.id, impl, kept);
23997
24529
  }
23998
24530
  }
23999
- this.stageMoveEdit(plan, "interface", receiving.id, receiving, {
24531
+ this.stageMoveEdit(plan, "interface", receiving.id, stated, {
24000
24532
  ...cloneSpec(receiving),
24001
24533
  methods: [...cloneSpec(receiving.methods), ...arriving]
24002
24534
  });
24535
+ if (!stated) plan.created.push(`interface "${receiving.id}"`);
24003
24536
  if (arriving.some((m) => m.endpoint)) plan.wireBound.push(receiving.id);
24004
- if (receiving.status !== void 0 && receiving.status !== statusOfContracts(contracts, source.id)) {
24537
+ if (stated && receiving.status !== void 0 && receiving.status !== statusOfContracts(contracts, source.id)) {
24005
24538
  plan.notices.push(`"${receiving.id}" is ${receiving.status} where the methods came from a ${statusOfContracts(contracts, source.id)} contract \u2014 the status of a moved method is the status of its new home.`);
24006
24539
  }
24007
24540
  for (const entry of travelling) {
@@ -24011,10 +24544,21 @@ var SpecWorkspace = class {
24011
24544
  }
24012
24545
  const targetImpl = implementations.find((i) => i.contract === receiving.id);
24013
24546
  if (travelling.length > 0 && targetImpl) {
24547
+ for (const entry of travelling) {
24548
+ const file = realizedIn.get(entry.name);
24549
+ if (entry.sourcePath || !file || file === targetImpl.sourcePath) continue;
24550
+ entry.sourcePath = file;
24551
+ plan.notices.push(`"${entry.name}" keeps ${file} as its own sourcePath: it was realized there by default, and "${targetImpl.id}" names ${targetImpl.sourcePath ? targetImpl.sourcePath : "no file"} \u2014 moving a method is not moving its code.`);
24552
+ }
24014
24553
  this.stageMoveEdit(plan, "implementation", targetImpl.id, targetImpl, {
24015
24554
  ...cloneSpec(targetImpl),
24016
24555
  methods: [...cloneSpec(targetImpl.methods), ...travelling]
24017
24556
  });
24557
+ } else if (travelling.length > 0) {
24558
+ const created = createdImplementation(target, receiving.id, source.id, travelling, implementations, contracts);
24559
+ this.stageMoveEdit(plan, "implementation", created.spec.id, null, created.spec);
24560
+ plan.created.push(created.named);
24561
+ plan.notices.push(...created.notices);
24018
24562
  }
24019
24563
  plan.reach = reachOf(travelling, source.id, new Set(methods)).filter((id) => id !== to);
24020
24564
  const held = /* @__PURE__ */ new Set([...target.dependsOn ?? [], ...target.owns ?? []]);
@@ -24058,6 +24602,9 @@ var SpecWorkspace = class {
24058
24602
  }
24059
24603
  this.stageMoveEdit(plan, kind, id, spec, subject);
24060
24604
  }
24605
+ for (const edit of plan.edits.values()) {
24606
+ if (edit.before === null) rewriteSpecRefs(edit.after, remap);
24607
+ }
24061
24608
  plan.notices.push(...this.callersOwingADependency(to, plan));
24062
24609
  return repointed;
24063
24610
  }
@@ -24151,7 +24698,8 @@ var SpecWorkspace = class {
24151
24698
  } catch (e) {
24152
24699
  for (const edit of [...written].reverse()) {
24153
24700
  try {
24154
- this.saveMovedSpec(edit.kind, edit.before);
24701
+ if (edit.before === null) this.delete(edit.kind, edit.id);
24702
+ else this.saveMovedSpec(edit.kind, edit.before);
24155
24703
  } catch {
24156
24704
  }
24157
24705
  }
@@ -24241,7 +24789,7 @@ function weighMoveHome(hooks, candidate, reach, dependencyLimit) {
24241
24789
  };
24242
24790
  }
24243
24791
  function moveChangeReports(edits, dryRun) {
24244
- return edits.map((edit) => ({ edit, changes: specChanges(edit.before, edit.after) })).filter(({ changes }) => changes.length > 0).map(({ edit, changes }) => ({
24792
+ return edits.map((edit) => ({ edit, changes: specChanges(edit.before ?? {}, edit.after) })).filter(({ changes }) => changes.length > 0).map(({ edit, changes }) => ({
24245
24793
  kind: edit.kind,
24246
24794
  id: edit.id,
24247
24795
  written: !dryRun,
@@ -24250,9 +24798,57 @@ function moveChangeReports(edits, dryRun) {
24250
24798
  ineffective: [],
24251
24799
  notices: [],
24252
24800
  testsToRevisit: [],
24253
- summary: `${dryRun ? "Would move" : "Moved"} into ${edit.kind} "${edit.id}": ${changes.length} change${changes.length === 1 ? "" : "s"}.`
24801
+ summary: edit.before === null ? `${dryRun ? "Would create" : "Created"} ${edit.kind} "${edit.id}" to receive the moved methods.` : `${dryRun ? "Would move" : "Moved"} into ${edit.kind} "${edit.id}": ${changes.length} change${changes.length === 1 ? "" : "s"}.`
24254
24802
  }));
24255
24803
  }
24804
+ function createdClause(created, verb) {
24805
+ return created.length ? `, ${verb} ${created.join(" and ")}` : "";
24806
+ }
24807
+ function createdContract(target, from, methods, contracts) {
24808
+ const now = (/* @__PURE__ */ new Date()).toISOString();
24809
+ const status = statusOfContracts(contracts, from);
24810
+ return {
24811
+ id: `i${target.id}`,
24812
+ name: `${target.name} Interface`,
24813
+ description: `The contract of ${target.name}, created by moving ${namedMethods(methods)} here from "${from}".`,
24814
+ component: target.id,
24815
+ methods: [],
24816
+ ...status ? { status } : {},
24817
+ createdAt: now,
24818
+ updatedAt: now
24819
+ };
24820
+ }
24821
+ function createdImplementation(target, contractId, from, travelling, implementations, contracts) {
24822
+ const ofSource = new Set(contracts.filter((i) => i.component === from).map((i) => i.id));
24823
+ const leaving = implementations.filter((impl) => ofSource.has(impl.contract) && impl.methods.some((m) => travelling.some((t) => t.name === m.name)));
24824
+ const paths = [...new Set(leaving.map((impl) => impl.sourcePath))];
24825
+ const inherited = paths.length === 1 ? paths[0] : void 0;
24826
+ const notices = [];
24827
+ if (paths.length > 1) {
24828
+ for (const entry of travelling) {
24829
+ if (entry.sourcePath) continue;
24830
+ const home = leaving.find((impl) => impl.methods.some((m) => m.name === entry.name));
24831
+ if (home?.sourcePath) entry.sourcePath = home.sourcePath;
24832
+ }
24833
+ notices.push(`"${target.id}_impl" is created without a sourcePath: the moved entries come from implementations with different ones, so each keeps the file it was realized in as its own sourcePath.`);
24834
+ }
24835
+ const now = (/* @__PURE__ */ new Date()).toISOString();
24836
+ const status = leaving[0]?.status;
24837
+ const spec = {
24838
+ id: `${target.id}_impl`,
24839
+ name: `${target.name} Implementation`,
24840
+ description: `The implementation of ${target.name}, created by moving methods here from "${from}".`,
24841
+ contract: contractId,
24842
+ ...inherited ? { sourcePath: inherited } : {},
24843
+ methods: travelling,
24844
+ ...status ? { status } : {},
24845
+ createdAt: now,
24846
+ updatedAt: now
24847
+ };
24848
+ const origin = leaving.length === 1 ? ` from "${leaving[0].id}"` : "";
24849
+ const named = inherited ? `implementation "${spec.id}" (sourcePath ${inherited}, inherited${origin})` : `implementation "${spec.id}" (no sourcePath)`;
24850
+ return { spec, named, notices };
24851
+ }
24256
24852
  function refusedMoveSummary(from, to, methods, refusals, alternatives) {
24257
24853
  const cheapest = alternatives.find((c) => c.legal);
24258
24854
  return `Refused: moving ${namedMethods(methods)} from "${from}" to "${to}" is refused by ${refusals.join(", ")}. Nothing was written. ` + (cheapest ? `The cheapest legal home is "${cheapest.component}" (${cheapest.newDependencies.length} new dependenc${cheapest.newDependencies.length === 1 ? "y" : "ies"}, ${cheapest.dependencyCount} of ${cheapest.dependencyLimit}).` : `No component of this subsystem can take them as they stand \u2014 ${alternatives.length} were weighed.`);
@@ -25727,8 +26323,8 @@ function inferSourcePathForComponent(comp, subsystems) {
25727
26323
  return null;
25728
26324
  }
25729
26325
  }
25730
- function summarize(text, max = 140) {
25731
- const clean = text.replace(/\s+/g, " ").trim();
26326
+ function summarize2(text2, max = 140) {
26327
+ const clean = text2.replace(/\s+/g, " ").trim();
25732
26328
  const period = clean.indexOf(". ");
25733
26329
  const firstSentence = period > 0 ? clean.slice(0, period + 1) : clean;
25734
26330
  if (firstSentence.length <= max) return firstSentence;
@@ -25754,7 +26350,7 @@ function resolveAgentTopology() {
25754
26350
  agents.push({
25755
26351
  id: "system-architect",
25756
26352
  name: `${system.name} Architect`,
25757
- description: `Global architect for ${system.name} \u2014 owns the spec tree and topology. ${summarize(system.vision)}`,
26353
+ description: `Global architect for ${system.name} \u2014 owns the spec tree and topology. ${summarize2(system.vision)}`,
25758
26354
  template: "architect",
25759
26355
  creationReason: "Automatically inferred from L0 system spec",
25760
26356
  ownedPaths: [".wai/specs/**"],
@@ -25834,7 +26430,7 @@ function resolveAgentTopology() {
25834
26430
  agents.push({
25835
26431
  id: `${sub.id}-owner`,
25836
26432
  name: `${sub.name} Owner`,
25837
- description: `Owns the ${sub.id} subsystem. ${summarize(sub.description)}`,
26433
+ description: `Owns the ${sub.id} subsystem. ${summarize2(sub.description)}`,
25838
26434
  template: "domain-owner",
25839
26435
  creationReason: `Automatically inferred from L1 subsystem spec: ${sub.id}`,
25840
26436
  domainRoot: sub.id,
@@ -25899,7 +26495,7 @@ function resolveAgentTopology() {
25899
26495
  agents.push({
25900
26496
  id: `${dom.id}-owner`,
25901
26497
  name: `${dom.name ?? dom.id} Owner`,
25902
- description: dom.description ? summarize(dom.description) : `Owner agent for the free-standing "${dom.id}" domain.`,
26498
+ description: dom.description ? summarize2(dom.description) : `Owner agent for the free-standing "${dom.id}" domain.`,
25903
26499
  template: "domain-owner",
25904
26500
  creationReason: "Inferred from a free-standing domain in .wai/topology.yaml",
25905
26501
  domainRoot: dom.id,
@@ -26243,19 +26839,19 @@ function commitStagedTree(stagingDir, destDir, roots, backupExisting) {
26243
26839
  const backupDir = path26.join(root, BACKUP_DIR, timestampSlug());
26244
26840
  let backedUp = false;
26245
26841
  for (const rootRel of roots) {
26246
- const relative9 = rootRel === "." ? "" : rootRel;
26247
- const stagedSpecDir = path26.join(stagingDir, relative9, ".wai");
26842
+ const relative10 = rootRel === "." ? "" : rootRel;
26843
+ const stagedSpecDir = path26.join(stagingDir, relative10, ".wai");
26248
26844
  if (!fs16.existsSync(stagedSpecDir)) continue;
26249
- const liveSpecDir = liveSpecDirFor(path26.join(root, relative9));
26845
+ const liveSpecDir = liveSpecDirFor(path26.join(root, relative10));
26250
26846
  if (backupExisting && liveSpecDir && fs16.existsSync(liveSpecDir)) {
26251
- const backupTarget = path26.join(backupDir, relative9, path26.basename(liveSpecDir));
26847
+ const backupTarget = path26.join(backupDir, relative10, path26.basename(liveSpecDir));
26252
26848
  fs16.mkdirSync(path26.dirname(backupTarget), { recursive: true });
26253
26849
  fs16.renameSync(liveSpecDir, backupTarget);
26254
26850
  backedUp = true;
26255
26851
  } else if (liveSpecDir && fs16.existsSync(liveSpecDir)) {
26256
26852
  fs16.rmSync(liveSpecDir, { recursive: true, force: true });
26257
26853
  }
26258
- const target = path26.join(root, relative9, ".wai");
26854
+ const target = path26.join(root, relative10, ".wai");
26259
26855
  fs16.mkdirSync(path26.dirname(target), { recursive: true });
26260
26856
  if (fs16.existsSync(target)) fs16.rmSync(target, { recursive: true, force: true });
26261
26857
  fs16.renameSync(stagedSpecDir, target);
@@ -26960,13 +27556,13 @@ function syncContextFiles() {
26960
27556
  }
26961
27557
 
26962
27558
  // src/core/status.ts
26963
- var asWritten = (text) => text;
27559
+ var asWritten = (text2) => text2;
26964
27560
  function fillDecor(decor) {
26965
27561
  return {
26966
27562
  structure: decor?.structure ?? asWritten,
26967
27563
  emphasis: decor?.emphasis ?? asWritten,
26968
- layer: decor?.layer ?? ((_kind, text) => text),
26969
- score: decor?.score ?? ((_pct, text) => text),
27564
+ layer: decor?.layer ?? ((_kind, text2) => text2),
27565
+ score: decor?.score ?? ((_pct, text2) => text2),
26970
27566
  draft: decor?.draft ?? asWritten,
26971
27567
  present: decor?.present ?? asWritten,
26972
27568
  missing: decor?.missing ?? asWritten
@@ -26978,13 +27574,13 @@ function getStatusReport(options = {}, decor) {
26978
27574
  const system = loadSystemSpec();
26979
27575
  const loaderErrors = getLoaderIssues();
26980
27576
  if (loaderErrors.length > 0) {
26981
- let errText = "Failed to parse specification files:\n";
27577
+ let errText2 = "Failed to parse specification files:\n";
26982
27578
  for (const issue2 of loaderErrors) {
26983
27579
  const prefix = issue2.specId ? mark.structure(`[${issue2.specId}] `) : "";
26984
- errText += `${prefix}[${issue2.code}] ${issue2.message}
27580
+ errText2 += `${prefix}[${issue2.code}] ${issue2.message}
26985
27581
  `;
26986
27582
  }
26987
- return { text: errText, failed: true };
27583
+ return { text: errText2, failed: true };
26988
27584
  }
26989
27585
  if (!system) {
26990
27586
  return { text: "L0 System specification (system.yaml) is missing.", failed: true };
@@ -27178,17 +27774,2306 @@ var logger = {
27178
27774
  }
27179
27775
  };
27180
27776
 
27777
+ // src/core/skills.ts
27778
+ var path29 = __toESM(require("path"));
27779
+ var fs18 = __toESM(require("fs"));
27780
+
27781
+ // src/core/instructions.ts
27782
+ function governingProfile() {
27783
+ try {
27784
+ return loadProjectConfig()?.projectType ?? null;
27785
+ } catch {
27786
+ return null;
27787
+ }
27788
+ }
27789
+ function blockApplies(block, profile) {
27790
+ if (!block.profile || block.profile.length === 0) return true;
27791
+ return profile !== null && block.profile.includes(profile);
27792
+ }
27793
+ function skillPointers(skills) {
27794
+ if (skills.length === 0) return "(none published by this server)";
27795
+ return skills.map((s) => `- \`${s.resourceUri}\` \u2014 ${s.description || s.name}`).join("\n");
27796
+ }
27797
+ function waironInstructions(profile, packNames, skills, variants) {
27798
+ return `# wairon \u2014 Spec-Driven Development (SDD)
27799
+
27800
+ This server owns one system's **spec tree**: its architecture, specified top-down
27801
+ and machine-checked. Author it ONLY through this server's \`sdd_*\` tools \u2014 never by
27802
+ writing spec files directly, and never via the \`wairon\` CLI (that is the human
27803
+ developer's tool).
27804
+
27805
+ ## The tree: L0 \u2192 L5
27806
+
27807
+ - **L0 System** \u2014 vision, boundaries, global requirements, target language.
27808
+ - **L1 Subsystem** \u2014 an isolated service, its \`publicInterfaces\`, its trusted links.
27809
+ - **L2 Component** \u2014 ONE building block (\`Portal\`, \`Orchestrator\`, \`Supervisor\`,
27810
+ \`Actor\`, \`Store\`, \`Index\`, \`Query\`, \`Registry\`, \`Adapter\`, \`Observer\`, and
27811
+ \`View\` for UIs) or pattern (\`Repository\`, and \`FeatureComponent\` or
27812
+ \`RouterComponent\` for UIs), plus \`owns\` (a pattern's private members) and
27813
+ \`dependsOn\` (collaborators). Logic is an \`Orchestrator\` whose \`dependencyClass\`
27814
+ (\`pure\` or \`read\`; unset, a workflow) bounds what it may depend on; a gateway is
27815
+ a \`Portal\` with the \`gateway\` variant. \`Specialist\` and \`Gateway\` are retired.
27816
+ - **L3 Interface** \u2014 the contract: method signatures with structured \`params\`,
27817
+ plus wire \`endpoint\` bindings for a Portal's methods.
27818
+ - **L4 Implementation** \u2014 a concrete realization of one L3 contract: narrative
27819
+ detail level, bound \`technologies\` (the vendor swap seam), optional \`sourcePath\`.
27820
+ - **L5 Narrative** \u2014 each method's logic as a FLAT numbered step list; \`call\` steps
27821
+ resolve to real dependency methods, and flow steps jump by step number.
27822
+
27823
+ These stereotypes are not labels \u2014 they carry enforced dependency rules (e.g. a
27824
+ Portal may not depend on a Store; state lives in a Store, never as fields inside
27825
+ an Orchestrator). \`sdd_validate_tree\` is the gate, and it is the authority on
27826
+ whether a design is legal.
27827
+
27828
+ ## Authoring order
27829
+
27830
+ 1. \`sdd_initialize_system\` (L0), then \`sdd_add_subsystem\` (L1).
27831
+ 2. Then, **one subsystem at a time**: \`sdd_add_component\` (L2) \u2192 \`sdd_define_interface\`
27832
+ (L3) \u2192 \`sdd_set_endpoints\` for Portals \u2192 \`sdd_write_narrative\` (L4+L5).
27833
+ 3. Run \`sdd_validate_tree\` as you go \u2014 early and per subsystem, not once at the end.
27834
+ \`sdd_get_status\` reports completeness; \`sdd_get_spec\` reads any spec back as JSON.
27835
+ 4. Edit granularly with \`sdd_update_spec\` (it renumbers narrative steps and
27836
+ relocates jump targets for you).
27837
+
27838
+ ## The tool schemas are the field reference
27839
+
27840
+ Every \`sdd_*\` tool's input schema is self-describing and authoritative \u2014 read the
27841
+ schema rather than guessing field names, and prefer structured \`params\` over prose
27842
+ signatures. Specs are ${profile === null ? "validated against the project's configured profile" : `governed by the **\`${profile}\`** profile`}, which decides which stereotypes are legal
27843
+ here, which narrative flow constructs the target language supports, and which
27844
+ extra rules apply. A subsystem may override the profile for itself.
27845
+
27846
+ ## Loaded extension packs
27847
+
27848
+ ${packNames.length === 0 ? "None \u2014 only wairon's built-in doctrine applies." : `${packNames.map((n) => `\`${n}\``).join(", ")} \u2014 these inject profiles, language tables, patterns, and
27849
+ additional conformance rules. Their doctrine is enforced by the same gate.`}
27850
+
27851
+ ${variants.length === 0 ? "" : `## Component variants in play
27852
+
27853
+ This project defines variants \u2014 named, base-anchored kinds carrying implementation
27854
+ guidance, so every component of the same kind is built alike: ${variants.map((v) => `\`${v.id}\` (a kind of ${v.base})`).join(", ")}.
27855
+ Tag a component with \`variant\` where it fits, and read a component's resolved
27856
+ guidance and its same-variant siblings from \`sdd_get_spec\` (kind: component).
27857
+
27858
+ `}## Read the skills BEFORE authoring
27859
+
27860
+ This server publishes its skills as MCP resources. **Read \`wairon-skill://sdd-architect\`
27861
+ before you design anything**, and the others when their turn comes \u2014 they carry the
27862
+ architecture standard, the narrative step reference, and the review checklist that
27863
+ this briefing only points at:
27864
+
27865
+ ${skillPointers(skills)}
27866
+
27867
+ ## Delegating implementation
27868
+
27869
+ Delegate component work from a LIVE agent brief, never a generated file: discover
27870
+ agents via \`listAgents\` (or the \`wairon-agent://\` resources), fetch the target's
27871
+ brief with \`sdd_get_agent_brief\`, and spawn a scoped subagent from it \u2014 its
27872
+ \`instructions\` as prompt, its \`ownedPaths\` as write fence. Briefs are composed
27873
+ from the current spec tree on every call, so a re-lock never requires a session
27874
+ restart \u2014 fetch fresh per delegation. \`wairon-skill://sdd-delegate\` carries the
27875
+ full flow.`;
27876
+ }
27877
+ function buildServerInstructions() {
27878
+ const profile = governingProfile();
27879
+ let skills = [];
27880
+ let blocks = [];
27881
+ let packNames = [];
27882
+ const variants = [];
27883
+ try {
27884
+ skills = listSkillResources();
27885
+ const extensions = loadProjectExtensions();
27886
+ blocks = extensions.instructions;
27887
+ packNames = extensions.packNames;
27888
+ } catch {
27889
+ }
27890
+ const sections = [waironInstructions(profile, packNames, skills, variants)];
27891
+ for (const block of blocks) {
27892
+ if (!blockApplies(block, profile)) continue;
27893
+ sections.push(`## From pack "${block.pack}"
27894
+
27895
+ ${block.text.trim()}`);
27896
+ }
27897
+ return sections.join("\n\n");
27898
+ }
27899
+
27181
27900
  // src/core/skills.ts
27182
27901
  var SKILL_NAMES = ["sdd-architect", "sdd-narrative", "sdd-auditor", "sdd-implement", "sdd-delegate"];
27902
+ function packSkillId(skill) {
27903
+ return `${packNamespace(skill.pack)}-${skill.id}`;
27904
+ }
27905
+ function packNamespace(pack) {
27906
+ return pack.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
27907
+ }
27908
+ function extensionsFor(builtin, packSkills) {
27909
+ return packSkills.filter((s) => s.extends === builtin && fs18.existsSync(s.sourcePath));
27910
+ }
27911
+ function composeBuiltinSkill(name, packSkills) {
27912
+ const srcPath = skillTemplatePath(name);
27913
+ const base = fs18.existsSync(srcPath) ? fs18.readFileSync(srcPath, "utf-8") : "";
27914
+ const sections = extensionsFor(name, packSkills);
27915
+ if (sections.length === 0) return base;
27916
+ const parts = [base.trimEnd()];
27917
+ for (const section of sections) {
27918
+ const body = fs18.readFileSync(section.sourcePath, "utf-8").replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, "");
27919
+ parts.push(`## Platform: ${section.pack}`, body.trim());
27920
+ }
27921
+ return `${parts.join("\n\n")}
27922
+ `;
27923
+ }
27924
+ function readFrontmatter(raw, fallbackName) {
27925
+ const match = /^---\r?\n([\s\S]*?)\r?\n---/.exec(raw);
27926
+ const block = match ? match[1] : "";
27927
+ const field = (key) => {
27928
+ const m = new RegExp(`^${key}:\\s*(.*)$`, "m").exec(block);
27929
+ return m ? m[1].trim() : "";
27930
+ };
27931
+ return { name: field("name") || fallbackName, description: field("description") };
27932
+ }
27933
+ function builtinSkillsDir() {
27934
+ return path29.resolve(__dirname, "..", "templates", "skills");
27935
+ }
27936
+ function skillTemplatePath(name) {
27937
+ return path29.join(builtinSkillsDir(), `${name}.md`);
27938
+ }
27183
27939
  function activeTargetTypes2() {
27184
27940
  const config = loadProjectConfig();
27185
27941
  if (!config) return [];
27186
27942
  return activeTargetTypes(config);
27187
27943
  }
27944
+ var SKILL_RESOURCE_SCHEME = "wairon-skill";
27188
27945
  var RESOURCE_SKILL_IDS = [...SKILL_NAMES].sort();
27946
+ var SkillResourceNotFoundError = class extends Error {
27947
+ constructor(resourceId) {
27948
+ super(`Unknown SDD skill resource id: "${resourceId}".`);
27949
+ this.name = "SkillResourceNotFoundError";
27950
+ }
27951
+ };
27952
+ function readSkillFrontmatter(name) {
27953
+ return readFrontmatter(fs18.readFileSync(skillTemplatePath(name), "utf-8"), name);
27954
+ }
27955
+ function listSkillResources() {
27956
+ const builtin = RESOURCE_SKILL_IDS.map((id) => {
27957
+ const fm = readSkillFrontmatter(id);
27958
+ return {
27959
+ id,
27960
+ name: fm.name,
27961
+ description: fm.description,
27962
+ version: WAIRON_VERSION,
27963
+ resourceUri: `${SKILL_RESOURCE_SCHEME}://${id}`,
27964
+ defaultForHostedMcp: true
27965
+ };
27966
+ });
27967
+ const pack = loadProjectExtensions().skills.filter((s) => s.extends === void 0 && fs18.existsSync(s.sourcePath)).map((skill) => {
27968
+ const id = packSkillId(skill);
27969
+ const fm = readFrontmatter(fs18.readFileSync(skill.sourcePath, "utf-8"), id);
27970
+ return {
27971
+ id,
27972
+ name: fm.name,
27973
+ description: fm.description,
27974
+ version: skill.packVersion ?? WAIRON_VERSION,
27975
+ resourceUri: `${SKILL_RESOURCE_SCHEME}://${id}`,
27976
+ defaultForHostedMcp: true
27977
+ };
27978
+ });
27979
+ return [...builtin, ...pack];
27980
+ }
27981
+ function readSkillResource(resourceId) {
27982
+ const packSkills = loadProjectExtensions().skills;
27983
+ if (SKILL_NAMES.includes(resourceId)) return composeBuiltinSkill(resourceId, packSkills);
27984
+ const packSkill = packSkills.find((s) => s.extends === void 0 && packSkillId(s) === resourceId);
27985
+ if (packSkill) return fs18.readFileSync(packSkill.sourcePath, "utf-8");
27986
+ return fs18.readFileSync(skillTemplatePath(resourceId), "utf-8");
27987
+ }
27988
+ function listResources() {
27989
+ return listSkillResources();
27990
+ }
27991
+ function buildServerInstructions2() {
27992
+ return buildServerInstructions();
27993
+ }
27994
+ function readResource(resourceId) {
27995
+ const known = listSkillResources().some((d) => d.id === resourceId);
27996
+ if (!known) throw new SkillResourceNotFoundError(resourceId);
27997
+ return readSkillResource(resourceId);
27998
+ }
27189
27999
 
27190
- // src/index.ts
28000
+ // src/mcp/server.ts
28001
+ var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
28002
+ var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
28003
+ var import_zod11 = require("zod");
28004
+ var import_types4 = require("@modelcontextprotocol/sdk/types.js");
28005
+ var crypto4 = __toESM(require("crypto"));
28006
+
28007
+ // src/core/adapters/authoring-core.ts
27191
28008
  function loadProjectConfig2() {
28009
+ try {
28010
+ return loadProjectConfig();
28011
+ } catch {
28012
+ return null;
28013
+ }
28014
+ }
28015
+
28016
+ // src/models/candidate.ts
28017
+ function formatCandidateRefusal(verdict) {
28018
+ const lines = verdict.errors.map((e) => `- ${e.code}: ${e.message}`);
28019
+ return `Refused: this component's fields do not match its componentType.
28020
+ ${lines.join("\n")}
28021
+
28022
+ Nothing was written. Fix the fields and retry \u2014 on an EXISTING spec, remove a field with sdd_update_spec's "unset" (e.g. {"unset": ["basePath"]}); passing null leaves it in place.`;
28023
+ }
28024
+
28025
+ // src/core/authoring.ts
28026
+ function candidateOptions() {
28027
+ const config = loadProjectConfig2();
28028
+ return config ? { rules: config.rules, projectType: config.projectType } : {};
28029
+ }
28030
+ function noticesFrom(verdict) {
28031
+ return verdict.warnings.map((w) => `${w.code}: ${w.message}`);
28032
+ }
28033
+ function componentCandidateGate(options = candidateOptions()) {
28034
+ return {
28035
+ gate: (kind, merged) => {
28036
+ if (kind !== "component") return;
28037
+ const verdict = validateComponentCandidate(merged, options);
28038
+ if (verdict.errors.length) throw new Error(formatCandidateRefusal(verdict));
28039
+ return noticesFrom(verdict);
28040
+ }
28041
+ };
28042
+ }
28043
+ var STORE_MANAGED_FIELDS = /* @__PURE__ */ new Set(["status", "updatedAt"]);
28044
+ var ALWAYS_CARRIED_FIELDS = /* @__PURE__ */ new Set(["ext"]);
28045
+ var STATUS_ORDER = ["draft", "design", "complete"];
28046
+ var STATUSED_KINDS = /* @__PURE__ */ new Set(["subsystem", "component", "interface", "implementation"]);
28047
+ var MISSING_PARENT = {
28048
+ subsystem: () => "System spec must be initialized (sdd_initialize_system) first.",
28049
+ component: (id) => `Parent subsystem "${id}" does not exist.`,
28050
+ interface: (id) => `Component "${id}" does not exist.`,
28051
+ implementation: (id) => `Interface contract "${id}" does not exist.`
28052
+ };
28053
+ var REWRITE_LABEL = {
28054
+ system: "System spec",
28055
+ subsystem: "Subsystem",
28056
+ component: "Component",
28057
+ interface: "Interface",
28058
+ implementation: "Implementation",
28059
+ type: "Type"
28060
+ };
28061
+ function restatementParent(restatement) {
28062
+ const spec = restatement.spec;
28063
+ switch (restatement.kind) {
28064
+ case "subsystem":
28065
+ return { kind: "system", id: "system" };
28066
+ case "component":
28067
+ return { kind: "subsystem", id: spec.subsystem };
28068
+ case "interface":
28069
+ return { kind: "component", id: spec.component };
28070
+ case "implementation":
28071
+ return { kind: "interface", id: spec.contract };
28072
+ default:
28073
+ return null;
28074
+ }
28075
+ }
28076
+ function applyRestatement(restatement, existing, parent) {
28077
+ const replacedExisting = existing !== null;
28078
+ const parentRef = restatementParent(restatement);
28079
+ if (parentRef && !parent) return refused(restatement, replacedExisting, MISSING_PARENT[restatement.kind](parentRef.id));
28080
+ const status = statusForCreate(restatement, existing);
28081
+ if ("refusal" in status) return refused(restatement, replacedExisting, status.refusal);
28082
+ const candidate = JSON.parse(JSON.stringify(restatement.spec));
28083
+ if (restatement.kind === "subsystem") candidate.parentSystem = parent.name;
28084
+ const carried = carryInto(restatement, existing, candidate);
28085
+ const cleared = clearedByOmission(existing, candidate, restatement.fields);
28086
+ stampLifecycle(candidate, existing, status.status);
28087
+ const labelErrors = resolveLabelsOf(restatement.kind, candidate);
28088
+ if (labelErrors.length) {
28089
+ return refused(restatement, replacedExisting, `Unresolved narrative label references \u2014 nothing was saved:
28090
+ - ${labelErrors.join("\n- ")}`);
28091
+ }
28092
+ const parsed = parseCandidate(restatement.kind, candidate);
28093
+ if ("refusal" in parsed) return refused(restatement, replacedExisting, parsed.refusal);
28094
+ return {
28095
+ spec: parsed.spec,
28096
+ ...status.status ? { status: status.status } : {},
28097
+ replacedExisting,
28098
+ notices: existing ? rewriteNotices(restatement.kind, existing, candidate, carried, cleared) : [],
28099
+ changedMethods: changedMethodsOf(restatement.kind, existing, candidate)
28100
+ };
28101
+ }
28102
+ var SPEC_SCHEMA = {
28103
+ system: SystemSpecSchema,
28104
+ subsystem: SubsystemSpecSchema,
28105
+ component: ComponentSpecSchema,
28106
+ interface: InterfaceSpecSchema,
28107
+ implementation: ImplementationSpecSchema,
28108
+ type: TypeSpecSchema
28109
+ };
28110
+ function parseCandidate(kind, candidate) {
28111
+ const masked = /* @__PURE__ */ new Map();
28112
+ const mask = (value) => {
28113
+ if (typeof value === "string") {
28114
+ if (!value.includes("::")) return value;
28115
+ const token = `nsref_${masked.size}_masked`;
28116
+ masked.set(token, value);
28117
+ return token;
28118
+ }
28119
+ if (Array.isArray(value)) return value.map(mask);
28120
+ if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, mask(v)]));
28121
+ return value;
28122
+ };
28123
+ const restore = (value) => {
28124
+ if (typeof value === "string") return masked.get(value) ?? value;
28125
+ if (Array.isArray(value)) return value.map(restore);
28126
+ if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, restore(v)]));
28127
+ return value;
28128
+ };
28129
+ const result = SPEC_SCHEMA[kind].safeParse(mask(candidate));
28130
+ if (result.success) return { spec: restore(result.data) };
28131
+ const id = kind === "system" ? "system" : String(candidate.id);
28132
+ const issues = result.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ");
28133
+ return {
28134
+ refusal: `Refusing to write ${kind} "${id}": the spec it would become does not parse \u2014 ${issues}. Nothing was written. State the missing or malformed field and call again.`
28135
+ };
28136
+ }
28137
+ function refused(restatement, replacedExisting, refusal2) {
28138
+ return { spec: restatement.spec, refusal: refusal2, replacedExisting, notices: [], changedMethods: [] };
28139
+ }
28140
+ function statusForCreate(restatement, existing) {
28141
+ if (!STATUSED_KINDS.has(restatement.kind)) return {};
28142
+ const stated = restatement.status;
28143
+ const stored = existing?.status;
28144
+ const held = stored !== void 0 && STATUS_ORDER.includes(stored) ? stored : void 0;
28145
+ if (stated === void 0) return { status: held ?? "draft" };
28146
+ if (held === void 0 || STATUS_ORDER.indexOf(stated) >= STATUS_ORDER.indexOf(held)) return { status: stated };
28147
+ const label = `${restatement.kind} "${restatement.spec.id}"`;
28148
+ return {
28149
+ refusal: `Refusing to re-author ${label} at status "${stated}": it is stored at "${held}", and a create tool never lowers a spec's status \u2014 a restatement that reopened a frozen spec would undo a lock without saying so. Nothing was written. To reopen it deliberately, use sdd_update_spec with {"status": "${stated}"}; to keep the level it has, leave status out.`
28150
+ };
28151
+ }
28152
+ function carryInto(restatement, existing, candidate) {
28153
+ if (!existing) return [];
28154
+ const carried = ["createdAt"];
28155
+ if (restatement.memberFields && Array.isArray(candidate.methods)) {
28156
+ const stored = existing.methods ?? [];
28157
+ for (const method of candidate.methods) {
28158
+ const prev = stored.find((p) => p.name === method.name);
28159
+ for (const field of carryUnexpressed(prev, method, restatement.memberFields)) carried.push(`${field} (${String(method.name)})`);
28160
+ }
28161
+ }
28162
+ carried.push(...carryUnexpressed(existing, candidate, [...restatement.fields, "createdAt"]));
28163
+ return carried;
28164
+ }
28165
+ function carryUnexpressed(existing, next, expressed) {
28166
+ if (!existing) return [];
28167
+ const carried = [];
28168
+ for (const [field, value] of Object.entries(existing)) {
28169
+ if (value === void 0) continue;
28170
+ if (STORE_MANAGED_FIELDS.has(field)) continue;
28171
+ if (expressed.includes(field) && !ALWAYS_CARRIED_FIELDS.has(field)) continue;
28172
+ if (next[field] !== void 0) continue;
28173
+ next[field] = value;
28174
+ carried.push(field);
28175
+ }
28176
+ return carried;
28177
+ }
28178
+ function isEmptyValue(value) {
28179
+ if (value === void 0 || value === null) return true;
28180
+ if (Array.isArray(value)) return value.length === 0;
28181
+ if (typeof value === "string") return value.trim() === "";
28182
+ if (typeof value === "object") return Object.keys(value).length === 0;
28183
+ return false;
28184
+ }
28185
+ function clearedByOmission(existing, next, expressed) {
28186
+ if (!existing) return [];
28187
+ const stored = existing;
28188
+ const cleared = [];
28189
+ for (const field of expressed) {
28190
+ const before = stored[field];
28191
+ if (STORE_MANAGED_FIELDS.has(field) || ALWAYS_CARRIED_FIELDS.has(field)) continue;
28192
+ if (isEmptyValue(before) || !isEmptyValue(next[field])) continue;
28193
+ cleared.push(Array.isArray(before) ? `${field} (had ${before.length})` : field);
28194
+ }
28195
+ return cleared;
28196
+ }
28197
+ function stampLifecycle(candidate, existing, status) {
28198
+ const now = (/* @__PURE__ */ new Date()).toISOString();
28199
+ candidate.createdAt = existing?.createdAt ?? now;
28200
+ candidate.updatedAt = now;
28201
+ if (status) candidate.status = status;
28202
+ else delete candidate.status;
28203
+ }
28204
+ function resolveLabelsOf(kind, candidate) {
28205
+ if (kind !== "implementation") return [];
28206
+ const methods = candidate.methods ?? [];
28207
+ return methods.flatMap((m) => resolveNarrativeLabels(m.name, m.narrative ?? []));
28208
+ }
28209
+ function removedByName(before, after) {
28210
+ if (!before?.length) return [];
28211
+ const kept = new Set((after ?? []).map((m) => m.name));
28212
+ return before.map((m) => m.name).filter((n) => !kept.has(n));
28213
+ }
28214
+ function rewriteNotices(kind, existing, candidate, carried, cleared) {
28215
+ const named = kind === "system" ? existing.name : String(candidate.id);
28216
+ const notices = [`${REWRITE_LABEL[kind]} "${named}" already existed \u2014 re-authored in place; this input REPLACES what it expresses.`];
28217
+ if (carried.length) notices.push(`Carried forward (not expressible through this tool): ${carried.join(", ")}.`);
28218
+ const removal = removalNotice(kind, existing, candidate);
28219
+ if (removal) notices.push(removal);
28220
+ if (cleared.length) {
28221
+ notices.push(
28222
+ `CLEARED by omission: ${cleared.join(", ")} \u2014 the argument was not repeated, so the previous value is gone. Use sdd_update_spec if you meant to leave it untouched.`
28223
+ );
28224
+ }
28225
+ return notices;
28226
+ }
28227
+ function removalNotice(kind, existing, candidate) {
28228
+ const before = existing;
28229
+ let removed = [];
28230
+ let noun = "method";
28231
+ let suffix = "";
28232
+ if (kind === "interface" || kind === "implementation") {
28233
+ removed = removedByName(before.methods, candidate.methods);
28234
+ suffix = kind === "interface" ? " (endpoint bindings included)" : " (L5 narratives included)";
28235
+ } else if (kind === "type") {
28236
+ removed = [
28237
+ ...removedByName(before.fields, candidate.fields).map((n) => `field ${n}`),
28238
+ ...removedByName(before.methods, candidate.methods).map((n) => `method ${n}`)
28239
+ ];
28240
+ noun = "member";
28241
+ }
28242
+ if (!removed.length) return void 0;
28243
+ return `REMOVED by this restatement: ${removed.length === 1 ? noun : `${noun}s`} ${removed.map((n) => `"${n}"`).join(", ")}${suffix} \u2014 absent from the input, so no longer in the spec. Restate them to keep them, or use sdd_update_spec to edit one member at a time.`;
28244
+ }
28245
+ var METHOD_SCHEMA = {
28246
+ interface: MethodSignatureSchema,
28247
+ implementation: MethodImplementationSchema,
28248
+ type: TypeMethodSchema
28249
+ };
28250
+ function changedMethodsOf(kind, existing, candidate) {
28251
+ if (!existing || !(kind in METHOD_SCHEMA)) return [];
28252
+ const schema = METHOD_SCHEMA[kind];
28253
+ const stored = existing.methods ?? [];
28254
+ const next = candidate.methods ?? [];
28255
+ return stored.filter((method) => {
28256
+ const restated = next.find((m) => m.name === method.name);
28257
+ if (!restated) return true;
28258
+ const parsed = schema.safeParse(restated);
28259
+ return !parsed.success || canonical(parsed.data) !== canonical(method);
28260
+ }).map((method) => method.name);
28261
+ }
28262
+ function canonical(value) {
28263
+ return JSON.stringify(value, (_key, v) => v && typeof v === "object" && !Array.isArray(v) ? Object.fromEntries(Object.entries(v).filter(([, x]) => x !== void 0).sort(([a], [b]) => a.localeCompare(b))) : v);
28264
+ }
28265
+ function writeSpec(restatement) {
28266
+ const bound2 = candidateOptions();
28267
+ const parentRef = restatementParent(restatement);
28268
+ const parent = parentRef ? loadSpec(parentRef.kind, parentRef.id) : null;
28269
+ const id = restatement.kind === "system" ? "system" : restatement.spec.id;
28270
+ const existing = loadSpec(restatement.kind, id);
28271
+ const application = applyRestatement(restatement, existing, parent);
28272
+ if (application.refusal !== void 0) throw new Error(application.refusal);
28273
+ const gateNotices = restatement.kind === "component" ? judgeComponent(application.spec, bound2) : [];
28274
+ const persisted = persistCandidate(restatement.kind, application.spec);
28275
+ const testsToRevisit = testsInvalidatedBy(application.changedMethods, [existing, application.spec], bound2);
28276
+ return {
28277
+ kind: restatement.kind,
28278
+ id,
28279
+ name: application.spec.name,
28280
+ replacedExisting: application.replacedExisting,
28281
+ ...application.status ? { status: application.status } : {},
28282
+ notices: [...gateNotices, ...persisted.notices, ...application.notices],
28283
+ ...persisted.scaffoldedProjectPath ? { scaffoldedProjectPath: persisted.scaffoldedProjectPath } : {},
28284
+ testsToRevisit
28285
+ };
28286
+ }
28287
+ function judgeComponent(candidate, options) {
28288
+ const verdict = validateComponentCandidate(candidate, options);
28289
+ if (verdict.errors.length) throw new Error(formatCandidateRefusal(verdict));
28290
+ return noticesFrom(verdict);
28291
+ }
28292
+ function persistCandidate(kind, spec) {
28293
+ const projectPath = kind === "subsystem" ? spec.projectPath : void 0;
28294
+ if (projectPath && projectPath.trim() !== "") {
28295
+ createChainedSubsystem(spec, spec.name);
28296
+ return { notices: [], scaffoldedProjectPath: projectPath };
28297
+ }
28298
+ return { notices: saveSpec(kind, spec) };
28299
+ }
28300
+ function testsInvalidatedBy(methods, specs, options) {
28301
+ const testRoots = options.rules?.conformance?.testRoots ?? [];
28302
+ if (methods.length === 0 || testRoots.length === 0) return [];
28303
+ const symbolOf = (name) => specs.map((s) => (s?.methods ?? []).find((m) => m.name === name)?.symbol).find((symbol) => symbol !== void 0);
28304
+ return findTestsReferencing2(methods.map((name) => ({ name, symbol: symbolOf(name) })), getProjectRoot(), testRoots);
28305
+ }
28306
+ function deleteSpec2(kind, id) {
28307
+ const bound2 = candidateOptions();
28308
+ const stored = loadSpec(kind, id);
28309
+ const deleted = deleteSpec(kind, id);
28310
+ const methods = deleted ? (stored?.methods ?? []).map((m) => m.name) : [];
28311
+ const testsToRevisit = testsInvalidatedBy(methods, [stored], bound2);
28312
+ return { kind, id, deleted, testsToRevisit };
28313
+ }
28314
+ function updateSpecGated(kind, id, delta, dryRun) {
28315
+ const bound2 = candidateOptions();
28316
+ const gate = componentCandidateGate(bound2);
28317
+ const report = updateSpec(kind, id, delta, gate, dryRun);
28318
+ const testRoots = bound2.rules?.conformance?.testRoots ?? [];
28319
+ const changed = changedMethods(report, delta);
28320
+ if (changed.length > 0 && testRoots.length > 0) {
28321
+ report.testsToRevisit = findTestsReferencing2(changed, getProjectRoot(), testRoots);
28322
+ }
28323
+ return report;
28324
+ }
28325
+ function changedMethods(report, delta) {
28326
+ const named = /* @__PURE__ */ new Set();
28327
+ for (const change of report.changes) {
28328
+ const [field, name] = change.path.split(".");
28329
+ if (field === "methods" && name) named.add(name);
28330
+ }
28331
+ const written = Array.isArray(delta?.methods) ? delta.methods : [];
28332
+ return [...named].map((name) => ({
28333
+ name,
28334
+ symbol: written.find((m) => m?.name === name)?.symbol
28335
+ }));
28336
+ }
28337
+ function methodMoveGate(source) {
28338
+ const bound2 = candidateOptions();
28339
+ return {
28340
+ gate: (kind, merged) => {
28341
+ if (kind !== "component") return;
28342
+ const { codes, verdict, ceiling } = judgeMoveHome(merged, source, bound2);
28343
+ if (codes.length === 0) return noticesFrom(verdict);
28344
+ throw new Error(
28345
+ `${codes.join(", ")} refuses "${merged.id}" as the home for these methods. ` + (verdict.errors.length ? formatCandidateRefusal(verdict) : `It would reach ${merged.dependsOn?.length ?? 0} dependencies against a ceiling of ${ceiling}.`)
28346
+ );
28347
+ },
28348
+ assess: (kind, merged) => kind === "component" ? judgeMoveHome(merged, source, bound2).codes : []
28349
+ };
28350
+ }
28351
+ function judgeMoveHome(component, source, options) {
28352
+ const ceiling = options.rules?.complexity?.maxComponentDependencies;
28353
+ const verdict = validateComponentCandidate(component, options);
28354
+ const codes = verdict.errors.map((e) => e.code);
28355
+ if (component.id !== source && ceiling !== void 0 && (component.dependsOn?.length ?? 0) > ceiling) {
28356
+ codes.push("EXCESSIVE_DEPENDENCIES");
28357
+ }
28358
+ return { codes: [...new Set(codes)], verdict, ceiling };
28359
+ }
28360
+ function moveMethods2(from, to, methods, dryRun) {
28361
+ return moveMethods(from, to, methods, methodMoveGate(from), dryRun);
28362
+ }
28363
+
28364
+ // src/mcp/build.ts
28365
+ var fs19 = __toESM(require("fs"));
28366
+ var path30 = __toESM(require("path"));
28367
+ var import_child_process2 = require("child_process");
28368
+ function captureBuildStamp(entryPath) {
28369
+ try {
28370
+ const s = fs19.statSync(entryPath);
28371
+ return { path: entryPath, mtimeMs: s.mtimeMs, size: s.size };
28372
+ } catch {
28373
+ return null;
28374
+ }
28375
+ }
28376
+ function isBuildStale(stamp) {
28377
+ if (!stamp) return false;
28378
+ try {
28379
+ const s = fs19.statSync(stamp.path);
28380
+ return s.mtimeMs !== stamp.mtimeMs || s.size !== stamp.size;
28381
+ } catch {
28382
+ return false;
28383
+ }
28384
+ }
28385
+ var fingerprintCache = /* @__PURE__ */ new Map();
28386
+ function readBuildFingerprint(stamp) {
28387
+ const current2 = fs19.statSync(stamp.path);
28388
+ const key = `${stamp.path}|${current2.mtimeMs}|${current2.size}`;
28389
+ const cached = fingerprintCache.get(key);
28390
+ if (cached !== void 0) return cached;
28391
+ const modulePath = fingerprintModuleFor(stamp.path);
28392
+ const loaderFlags = modulePath.endsWith(".ts") ? process.execArgv : [];
28393
+ let printed;
28394
+ try {
28395
+ printed = (0, import_child_process2.execFileSync)(
28396
+ process.execPath,
28397
+ [...loaderFlags, "-e", "const m = require(process.argv[1]); process.stdout.write(String(m.schemaFingerprint()));", modulePath],
28398
+ { encoding: "utf8", timeout: 6e4, stdio: ["ignore", "pipe", "pipe"], windowsHide: true }
28399
+ ).trim();
28400
+ } catch (e) {
28401
+ const stderr = String(e.stderr ?? "").split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
28402
+ const cause = stderr.find((l) => /^[A-Za-z]*Error\b/.test(l)) ?? stderr[0] ?? e.message;
28403
+ throw new Error(`the build at ${modulePath} could not be asked for its schema fingerprint (${cause})`);
28404
+ }
28405
+ if (!printed) throw new Error(`the build at ${modulePath} answered no schema fingerprint`);
28406
+ fingerprintCache.set(key, printed);
28407
+ return printed;
28408
+ }
28409
+ function fingerprintModuleFor(entryPath) {
28410
+ let dir = path30.dirname(entryPath);
28411
+ for (; ; ) {
28412
+ const manifest = path30.join(dir, "package.json");
28413
+ if (fs19.existsSync(manifest)) {
28414
+ try {
28415
+ const pkg = JSON.parse(fs19.readFileSync(manifest, "utf8"));
28416
+ const inDist = path30.relative(path30.join(dir, "dist"), entryPath);
28417
+ if (pkg.name === "@wairon/cli" && pkg.main && !inDist.startsWith("..") && !path30.isAbsolute(inDist)) {
28418
+ return path30.resolve(dir, pkg.main);
28419
+ }
28420
+ } catch {
28421
+ }
28422
+ return entryPath;
28423
+ }
28424
+ const parent = path30.dirname(dir);
28425
+ if (parent === dir) return entryPath;
28426
+ dir = parent;
28427
+ }
28428
+ }
28429
+
28430
+ // src/mcp/server.ts
28431
+ function statusFamilyContext() {
28432
+ const lines = [];
28433
+ try {
28434
+ const parent = resolveChainingParent();
28435
+ if (parent) {
28436
+ let parentName;
28437
+ try {
28438
+ const system = readYamlFile(aiPathsAt(parent.parentRoot).specsSystem());
28439
+ if (typeof system?.name === "string") parentName = system.name;
28440
+ } catch {
28441
+ }
28442
+ lines.push(
28443
+ `Family: this project is a chained subproject, mounted as subsystem "${parent.subsystemId}" of ${parentName ? `the parent project "${parentName}"` : "its parent project"} \u2014 the surfaces it can consume are listed by sdd_list_external_interfaces.`
28444
+ );
28445
+ }
28446
+ const mounts = loadSubsystemSpecs().filter((s) => s.projectPath && !s.id.includes("::"));
28447
+ if (mounts.length > 0) {
28448
+ lines.push(`Family: chained subprojects mounted here \u2014 ${mounts.map((s) => `${s.id} (${s.projectPath})`).join(", ")}.`);
28449
+ }
28450
+ } catch {
28451
+ }
28452
+ return lines.length > 0 ? `${lines.join("\n")}
28453
+
28454
+ ` : "";
28455
+ }
28456
+ function resolveComponentVariantGuidance(component) {
28457
+ if (!component.variant) return null;
28458
+ const byId = new Map(loadProjectVariants().map((v) => [v.id, v]));
28459
+ return resolveVariantGuidance(component, loadComponentSpecs(), byId);
28460
+ }
28461
+ function qualifiedComponentId(id) {
28462
+ const ids = loadComponentSpecs().map((c) => c.id);
28463
+ if (ids.includes(id)) return id;
28464
+ const bare = id.startsWith("::") ? id.slice(2) : id;
28465
+ if (ids.includes(bare)) return bare;
28466
+ const chained = ids.filter((known) => known.endsWith(`::${bare}`));
28467
+ return chained.length === 1 ? chained[0] : id;
28468
+ }
28469
+ function publicInterfacesReplacement(subsystem, stored, list) {
28470
+ const identity = (pi) => pi.component ? `${pi.component}${pi.interface ? `.${pi.interface}` : ""}` : `${pi.type} ${pi.details}`;
28471
+ const restated = /* @__PURE__ */ new Map();
28472
+ for (const pi of list) restated.set(identity(pi), (restated.get(identity(pi)) ?? 0) + 1);
28473
+ const twice = [...restated].filter(([, n]) => n > 1).map(([key]) => `"${key}"`);
28474
+ if (twice.length) {
28475
+ return `Refusing to set the public interfaces of subsystem "${subsystem}": the list names ${twice.join(", ")} more than once. An entry is identified by the component and interface it is bound to \u2014 or, not yet bound, by its type and details \u2014 so two entries sharing one are the same entry stated twice. Drop the duplicate. Nothing was written.`;
28476
+ }
28477
+ const seen = /* @__PURE__ */ new Map();
28478
+ const deletes = [];
28479
+ for (const pi of stored) {
28480
+ const key = identity(pi);
28481
+ seen.set(key, (seen.get(key) ?? 0) + 1);
28482
+ if (seen.get(key) > (restated.get(key) ?? 0)) {
28483
+ deletes.push(pi.component ? { component: pi.component, interface: pi.interface, action: "delete" } : { type: pi.type, details: pi.details, action: "delete" });
28484
+ }
28485
+ }
28486
+ const restatedEntries = list.map((pi) => {
28487
+ const prev = stored.find((s) => identity(s) === identity(pi));
28488
+ const dropped = prev ? Object.keys(prev).filter((f) => prev[f] !== void 0 && pi[f] === void 0) : [];
28489
+ return dropped.length ? { ...pi, unset: dropped } : pi;
28490
+ });
28491
+ return { publicInterfaces: [...deletes, ...restatedEntries] };
28492
+ }
28493
+ function text(content) {
28494
+ return { content: [{ type: "text", text: content }] };
28495
+ }
28496
+ function json(value) {
28497
+ return text(JSON.stringify(value, null, 2));
28498
+ }
28499
+ function errText(message) {
28500
+ return { content: [{ type: "text", text: `Error: ${message.replace(/^(?:\s*Error:\s*)+/, "")}` }], isError: true };
28501
+ }
28502
+ function errMessage(e) {
28503
+ return errText(e instanceof Error ? e.message : String(e));
28504
+ }
28505
+ var TYPE_REF_GRAMMAR = (what) => `${what}: a primitive/builtin or a defined type id (qualified across subsystems, e.g. "billing.Invoice" or "billing::Invoice"). Generics, arrays and UNIONS are all read, at any depth: "Invoice | null" (the commonest shape there is), "Invoice | undefined", "Invoice | Receipt", "Promise<Invoice | null>", "Map<string, Invoice | null>", "Invoice[] | null", "(Invoice | null)[]". Every identifier the string names must resolve \u2014 a union of two defined types means BOTH must exist. A union of string literals ("read" | "write") names no type and resolves to nothing.`;
28506
+ function structured(content, data) {
28507
+ return { content: [{ type: "text", text: content }], structuredContent: data };
28508
+ }
28509
+ function jsonStructured(value) {
28510
+ return structured(JSON.stringify(value, null, 2), value);
28511
+ }
28512
+ var staleServerOutput = {
28513
+ staleServer: import_zod11.z.boolean().optional().describe(
28514
+ "True when the wairon build on disk changed after this server started \u2014 the structured twin of the text answer's STALE SERVER banner. When the rebuild left the spec and tool schemas alone, writes still go through; reconnect to run the new code."
28515
+ ),
28516
+ writesRefused: import_zod11.z.boolean().optional().describe(
28517
+ "True when the rebuild changed a spec or tool schema (or its schemas could not be compared): every spec write through this server is refused and writes nothing, because it would silently drop the fields only the new build knows. The human has to reconnect the server (/mcp reconnect wairon)."
28518
+ )
28519
+ };
28520
+ function renderChangeReport(report) {
28521
+ const lines = [report.dryRun ? `DRY RUN \u2014 nothing was written. ${report.summary}` : report.summary];
28522
+ for (const change of report.changes) {
28523
+ const to = change.after === void 0 ? "" : `: ${JSON.stringify(change.after)}`;
28524
+ const from = change.before === void 0 ? "" : ` (was ${JSON.stringify(change.before)})`;
28525
+ lines.push(`- ${change.path} ${report.dryRun ? `would be ${change.change}` : change.change}${to}${from}`);
28526
+ }
28527
+ if (report.ineffective.length) {
28528
+ lines.push("", "NO EFFECT:", ...report.ineffective.map((i) => `- ${i}`));
28529
+ }
28530
+ if (report.notices.length) lines.push("", "NOTICE:", ...report.notices.map((n) => `- ${n}`));
28531
+ lines.push(...renderTestsToRevisit(report.testsToRevisit));
28532
+ return lines.join("\n");
28533
+ }
28534
+ var SPEC_KINDS = ["system", "subsystem", "component", "interface", "implementation", "type"];
28535
+ var STATUS_VALUES = ["draft", "design", "complete"];
28536
+ var testsToRevisitOutput = import_zod11.z.array(import_zod11.z.object({
28537
+ method: import_zod11.z.string().describe("The contract method this write changed or deleted."),
28538
+ symbol: import_zod11.z.string().describe("The code-level name searched for: the method's `symbol` when it declares one, else its name."),
28539
+ imported: import_zod11.z.array(import_zod11.z.string()).describe("Test files that IMPORT that symbol \u2014 the high-confidence list, because an import is a binding and not a coincidence."),
28540
+ mentioned: import_zod11.z.array(import_zod11.z.string()).describe("Test files that name the symbol without importing it \u2014 usually a test driving it through a portal. Empty when `indiscriminate` is true."),
28541
+ indiscriminate: import_zod11.z.boolean().describe("True when the bare name matched more than a tenth of the test suite, so the mention list was withheld as noise.")
28542
+ })).describe(
28543
+ "The tests that encode a method this write changed or deleted, one entry per such method. Empty when the write touched no method, when the project declares no `rules.conformance.testRoots`, or when nothing references them. Read it before promising that existing tests still pass."
28544
+ );
28545
+ function renderTestsToRevisit(entries) {
28546
+ if (!entries.length) return [];
28547
+ const lines = ["", "TESTS TO REVISIT:"];
28548
+ for (const entry of entries) {
28549
+ lines.push(`- ${entry.method} (searched as "${entry.symbol}")`);
28550
+ if (entry.imported.length) lines.push(` imports it: ${entry.imported.join(", ")}`);
28551
+ if (entry.mentioned.length) lines.push(` names it: ${entry.mentioned.join(", ")}`);
28552
+ if (entry.indiscriminate) {
28553
+ lines.push(" names it: withheld \u2014 the bare name matches more than a tenth of the suite, so the list carries no signal.");
28554
+ }
28555
+ }
28556
+ return lines;
28557
+ }
28558
+ var specWriteReceiptOutput = {
28559
+ kind: import_zod11.z.enum(SPEC_KINDS).describe("The spec kind written."),
28560
+ id: import_zod11.z.string().describe('The id the spec is stored under; "system" for the L0 singleton.'),
28561
+ name: import_zod11.z.string().describe("The spec's display name as written."),
28562
+ replacedExisting: import_zod11.z.boolean().describe(
28563
+ "True when a spec already held this id and the call re-authored it in place rather than adding one. The notices then say what was carried forward, removed or cleared by the restatement."
28564
+ ),
28565
+ status: import_zod11.z.enum(STATUS_VALUES).optional().describe(
28566
+ "The lifecycle status written. Absent for the L0 and a type, which carry none."
28567
+ ),
28568
+ notices: import_zod11.z.array(import_zod11.z.string()).describe(
28569
+ "The notice lines the text answer lists, one per entry: what was carried forward, what a restatement removed, what an omission cleared, and every gate warning the write raised. Empty for a clean create."
28570
+ ),
28571
+ scaffoldedProjectPath: import_zod11.z.string().optional().describe(
28572
+ "The child project directory a chained subsystem's create scaffolded; absent for every other write."
28573
+ ),
28574
+ testsToRevisit: testsToRevisitOutput,
28575
+ ...staleServerOutput
28576
+ };
28577
+ function writeReceipt(sentence, receipt) {
28578
+ const noticeBlock = receipt.notices.length ? `
28579
+
28580
+ NOTICE:
28581
+ - ${receipt.notices.join("\n- ")}` : "";
28582
+ const testsBlock = renderTestsToRevisit(receipt.testsToRevisit).join("\n");
28583
+ return structured(`${sentence}${noticeBlock}${testsBlock ? `
28584
+ ${testsBlock}` : ""}`, receipt);
28585
+ }
28586
+ var specDeletionOutput = {
28587
+ kind: import_zod11.z.enum(SPEC_KINDS).describe("The spec kind deleted."),
28588
+ id: import_zod11.z.string().describe("The id the spec was stored under."),
28589
+ deleted: import_zod11.z.boolean().describe("True when a spec document was removed."),
28590
+ testsToRevisit: testsToRevisitOutput,
28591
+ ...staleServerOutput
28592
+ };
28593
+ var specChangeOutput = {
28594
+ path: import_zod11.z.string().describe("The dotted path the change addresses, with names and indexes."),
28595
+ change: import_zod11.z.enum(["set", "added", "removed", "cleared", "renumbered", "relocated"]).describe(
28596
+ "What happened at that path. A narrative is compared by step identity: `renumbered` is a run of unchanged steps shifted by an insert or delete (before/after name the ranges), `relocated` a jump field that followed its target to the target's new number."
28597
+ ),
28598
+ before: import_zod11.z.string().optional().describe("The previous value, summarized; absent when there was none."),
28599
+ after: import_zod11.z.string().optional().describe("The new value, summarized; absent when there is none.")
28600
+ };
28601
+ var specChangeReportOutput = {
28602
+ kind: import_zod11.z.enum(SPEC_KINDS).describe("The spec kind written."),
28603
+ id: import_zod11.z.string().describe("The stored spec's id."),
28604
+ written: import_zod11.z.boolean().describe("False when the merged spec equals what is stored: nothing reached disk."),
28605
+ dryRun: import_zod11.z.boolean().describe(
28606
+ "True when the caller asked what the delta WOULD do. `written` is false for a dry run exactly as it is for a delta that changed nothing, and only this field tells the two apart."
28607
+ ),
28608
+ changes: import_zod11.z.array(import_zod11.z.object(specChangeOutput)).describe(
28609
+ "Every change the write made; empty exactly when `written` is false."
28610
+ ),
28611
+ ineffective: import_zod11.z.array(import_zod11.z.string()).describe(
28612
+ "Every path the delta named that the write did not act on, each with why. Read it: a nested typo the permissive delta cannot refuse shows up here and nowhere else."
28613
+ ),
28614
+ notices: import_zod11.z.array(import_zod11.z.string()).describe("Store placement notices, gate warnings and delta notices."),
28615
+ summary: import_zod11.z.string().describe("One line for people."),
28616
+ testsToRevisit: testsToRevisitOutput,
28617
+ ...staleServerOutput
28618
+ };
28619
+ var validationIssueOutput = {
28620
+ severity: import_zod11.z.enum(["error", "warning"]).describe("The finding's severity after project overrides."),
28621
+ code: import_zod11.z.string().describe("The rule code, UPPER_SNAKE \u2014 the stable handle to filter and suppress by."),
28622
+ message: import_zod11.z.string().describe("What is wrong, named."),
28623
+ agentId: import_zod11.z.string().optional().describe("The agent the finding concerns, when it concerns one."),
28624
+ specId: import_zod11.z.string().optional().describe("The spec the finding concerns, when it concerns one."),
28625
+ draftContext: import_zod11.z.boolean().optional().describe(
28626
+ "True when the finding was raised against a draft/design spec \u2014 what the --ci gate waives."
28627
+ ),
28628
+ surfaceResolved: import_zod11.z.boolean().optional().describe(
28629
+ "True when the finding was verified against a vendored surface snapshot, so it is a contract verdict rather than a resolution failure."
28630
+ )
28631
+ };
28632
+ var validateTreeOutput = {
28633
+ valid: import_zod11.z.boolean().describe("False when the tree holds at least one error."),
28634
+ errors: import_zod11.z.array(import_zod11.z.object(validationIssueOutput)).describe("Every finding of severity error."),
28635
+ warnings: import_zod11.z.array(import_zod11.z.object(validationIssueOutput)).describe("Every finding of severity warning."),
28636
+ resolvedThrough: import_zod11.z.object({
28637
+ root: import_zod11.z.string().describe("The top root that was validated."),
28638
+ scope: import_zod11.z.string().describe("The mount chain the verdict was scoped to.")
28639
+ }).optional().describe(
28640
+ "Present when a chained subproject's verdict was resolved through its parent; absent when the tree was validated on its own."
28641
+ ),
28642
+ ...staleServerOutput
28643
+ };
28644
+ var getSpecOutput = {
28645
+ kind: import_zod11.z.enum(SPEC_KINDS).describe("The kind of spec read."),
28646
+ id: import_zod11.z.string().describe("The id it was read by."),
28647
+ spec: import_zod11.z.record(import_zod11.z.unknown()).describe(
28648
+ "The stored spec, exactly as the text block renders it \u2014 minus the two derived markers below, which the text folds in and this answer keeps separate so nothing derived can be mistaken for stored."
28649
+ ),
28650
+ partialResult: import_zod11.z.object({
28651
+ shown: import_zod11.z.array(import_zod11.z.string()).describe("The method names this answer carries."),
28652
+ omitted: import_zod11.z.number().describe("How many of the spec's methods are NOT in it."),
28653
+ warning: import_zod11.z.string().describe("Why it must never be re-authored from.")
28654
+ }).optional().describe(
28655
+ "Present only when `methods` filtered the read. A filtered spec is a partial one: sdd_define_interface and sdd_write_narrative REPLACE the method list, so re-authoring from it would delete every method left out."
28656
+ ),
28657
+ variantGuidance: import_zod11.z.object({
28658
+ variant: import_zod11.z.string().describe("The variant id the component declares."),
28659
+ base: import_zod11.z.string().describe("The core stereotype it specializes."),
28660
+ guidance: import_zod11.z.string().describe("How to implement a component of this variant."),
28661
+ siblings: import_zod11.z.array(import_zod11.z.string()).describe("Other components of the same variant, to implement alike."),
28662
+ target: import_zod11.z.string().optional().describe("The target language the variant scopes itself to, if any."),
28663
+ profile: import_zod11.z.string().optional().describe("The architectural profile it scopes itself to, if any.")
28664
+ }).optional().describe(
28665
+ "Derived, read-only guidance for a variant-tagged component \u2014 resolved from the variant registry, not part of the spec. Never write it back."
28666
+ ),
28667
+ ...staleServerOutput
28668
+ };
28669
+ var FINGERPRINT_VERSION = 1;
28670
+ function schemaShape(schema, seen = /* @__PURE__ */ new Map()) {
28671
+ const def = schema?._def;
28672
+ if (!def) return null;
28673
+ const known = seen.get(schema);
28674
+ if (known !== void 0) return { ref: known };
28675
+ seen.set(schema, seen.size);
28676
+ const inner = (s) => schemaShape(s, seen);
28677
+ const kind = String(def.typeName);
28678
+ switch (kind) {
28679
+ case "ZodObject": {
28680
+ const shape = typeof def.shape === "function" ? def.shape() : def.shape;
28681
+ return {
28682
+ kind,
28683
+ keys: Object.keys(shape).sort().map((key) => [key, inner(shape[key])]),
28684
+ unknownKeys: def.unknownKeys,
28685
+ catchall: inner(def.catchall)
28686
+ };
28687
+ }
28688
+ case "ZodArray":
28689
+ return { kind, of: inner(def.type), min: def.minLength?.value, max: def.maxLength?.value };
28690
+ case "ZodOptional":
28691
+ case "ZodNullable":
28692
+ case "ZodReadonly":
28693
+ case "ZodBranded":
28694
+ case "ZodCatch":
28695
+ return { kind, of: inner(def.innerType ?? def.type) };
28696
+ case "ZodDefault":
28697
+ return { kind, of: inner(def.innerType), default: JSON.stringify(def.defaultValue?.()) };
28698
+ case "ZodEffects":
28699
+ return { kind, effect: def.effect?.type, of: inner(def.schema) };
28700
+ case "ZodLazy":
28701
+ return { kind, of: inner(def.getter()) };
28702
+ case "ZodUnion":
28703
+ case "ZodDiscriminatedUnion": {
28704
+ const options = def.options instanceof Map ? [...def.options.values()] : def.options;
28705
+ return { kind, discriminator: def.discriminator, of: options.map(inner) };
28706
+ }
28707
+ case "ZodIntersection":
28708
+ return { kind, left: inner(def.left), right: inner(def.right) };
28709
+ case "ZodTuple":
28710
+ return { kind, of: def.items.map(inner), rest: inner(def.rest) };
28711
+ case "ZodRecord":
28712
+ case "ZodMap":
28713
+ return { kind, key: inner(def.keyType), value: inner(def.valueType) };
28714
+ case "ZodSet":
28715
+ return { kind, of: inner(def.valueType) };
28716
+ case "ZodEnum":
28717
+ return { kind, values: def.values };
28718
+ case "ZodNativeEnum":
28719
+ return { kind, values: def.values };
28720
+ case "ZodLiteral":
28721
+ return { kind, value: def.value };
28722
+ case "ZodPipeline":
28723
+ return { kind, in: inner(def.in), out: inner(def.out) };
28724
+ default:
28725
+ return { kind, checks: def.checks?.map((c) => [c.kind, c.value ?? null]) ?? [] };
28726
+ }
28727
+ }
28728
+ var runningFingerprint;
28729
+ function schemaFingerprint() {
28730
+ if (runningFingerprint !== void 0) return runningFingerprint;
28731
+ const specs = {
28732
+ system: schemaShape(SystemSpecSchema),
28733
+ subsystem: schemaShape(SubsystemSpecSchema),
28734
+ component: schemaShape(ComponentSpecSchema),
28735
+ interface: schemaShape(InterfaceSpecSchema),
28736
+ implementation: schemaShape(ImplementationSpecSchema),
28737
+ type: schemaShape(TypeSpecSchema)
28738
+ };
28739
+ const registered = createMcpServer()._registeredTools;
28740
+ const tools = [...SPEC_WRITE_TOOLS].sort().map((name) => [name, schemaShape(registered[name]?.inputSchema)]);
28741
+ runningFingerprint = crypto4.createHash("sha256").update(JSON.stringify({ version: FINGERPRINT_VERSION, specs, tools })).digest("hex");
28742
+ return runningFingerprint;
28743
+ }
28744
+ var RECONNECT = "The human has to reconnect this MCP server (in Claude Code: /mcp reconnect wairon) \u2014 an agent cannot.";
28745
+ function assessBuildFreshness(stamp) {
28746
+ if (!isBuildStale(stamp)) return { state: "fresh", writesRefused: false, reason: "" };
28747
+ const running = schemaFingerprint();
28748
+ let onDisk;
28749
+ try {
28750
+ onDisk = readBuildFingerprint(stamp);
28751
+ } catch (e) {
28752
+ return {
28753
+ state: "unreadable",
28754
+ writesRefused: true,
28755
+ reason: `the wairon build on disk changed after this MCP server started, and its schemas could not be compared (${e instanceof Error ? e.message : String(e)}), so a schema change cannot be ruled out. Every spec write through this server is REFUSED and writes nothing. ${RECONNECT}`
28756
+ };
28757
+ }
28758
+ if (onDisk === running) {
28759
+ return {
28760
+ state: "build-changed",
28761
+ writesRefused: false,
28762
+ reason: "the wairon build on disk changed after this MCP server started. Its spec and tool schemas did not change, so spec writes still go through; reconnect the server (/mcp reconnect wairon) to run the new code."
28763
+ };
28764
+ }
28765
+ return {
28766
+ state: "schema-changed",
28767
+ writesRefused: true,
28768
+ reason: `the wairon build on disk changed after this MCP server started, and its spec or tool schemas changed with it. A write through this server would silently drop the fields only the new build knows, so every spec write is REFUSED and writes nothing. ${RECONNECT}`
28769
+ };
28770
+ }
28771
+ function markStale(result, freshness = assessedAsChanged, placement = "trail") {
28772
+ const marked = result.structuredContent ? {
28773
+ ...result,
28774
+ structuredContent: {
28775
+ ...result.structuredContent,
28776
+ staleServer: true,
28777
+ ...freshness.writesRefused ? { writesRefused: true } : {}
28778
+ }
28779
+ } : result;
28780
+ const banner = `\u26A0 STALE SERVER: ${freshness.reason}`;
28781
+ const first = marked.content?.[0];
28782
+ if (first && first.type === "text") {
28783
+ const textWithBanner = placement === "lead" ? `${banner}
28784
+
28785
+ ${first.text}` : `${first.text}
28786
+
28787
+ ${banner}`;
28788
+ return { ...marked, content: [{ ...first, text: textWithBanner }, ...marked.content.slice(1)] };
28789
+ }
28790
+ return marked;
28791
+ }
28792
+ var assessedAsChanged = {
28793
+ state: "build-changed",
28794
+ writesRefused: false,
28795
+ reason: "the wairon build on disk changed after this MCP server started. Reconnect the server (/mcp reconnect wairon) before further spec edits \u2014 a write through a stale server can silently drop fields a newer schema introduced."
28796
+ };
28797
+ var serverBuildStamps = /* @__PURE__ */ new WeakMap();
28798
+ var SERVER_BUILD_STAMP = captureBuildStamp(__filename);
28799
+ var SPEC_WRITE_TOOLS = /* @__PURE__ */ new Set([
28800
+ "sdd_initialize_system",
28801
+ "sdd_add_subsystem",
28802
+ "sdd_set_public_interfaces",
28803
+ "sdd_set_subsystem_project_path",
28804
+ "sdd_move_subsystem_project",
28805
+ "sdd_externalize_subsystem",
28806
+ "sdd_internalize_subsystem",
28807
+ "sdd_rename_component",
28808
+ "sdd_rename_method",
28809
+ "sdd_add_component",
28810
+ "sdd_define_interface",
28811
+ "sdd_set_endpoints",
28812
+ "sdd_write_narrative",
28813
+ "sdd_add_type",
28814
+ "sdd_delete_spec",
28815
+ "sdd_update_spec",
28816
+ "sdd_move_methods"
28817
+ ]);
28818
+ var listChangedEmitters = /* @__PURE__ */ new WeakMap();
28819
+ function reg(server, name, config, cb) {
28820
+ const guarded = (args) => {
28821
+ const freshness = assessBuildFreshness(serverBuildStamps.get(server) ?? null);
28822
+ if (freshness.writesRefused && SPEC_WRITE_TOOLS.has(name)) {
28823
+ return errText(`${name} refused \u2014 ${freshness.reason}`);
28824
+ }
28825
+ const answered = cb(args);
28826
+ const result = freshness.state === "fresh" ? answered : markStale(answered, freshness, name === "sdd_get_status" ? "lead" : "trail");
28827
+ if (result.isError !== true && SPEC_WRITE_TOOLS.has(name)) listChangedEmitters.get(server)?.();
28828
+ return result;
28829
+ };
28830
+ const strictConfig = config.inputSchema ? { ...config, inputSchema: import_zod11.z.object(config.inputSchema).strict() } : config;
28831
+ server.registerTool(name, strictConfig, guarded);
28832
+ }
28833
+ var statusInput = import_zod11.z.enum(STATUS_VALUES).optional().describe(
28834
+ "The spec's lifecycle status. Omitted means draft for a NEW spec and the status already stored for a re-authoring, so a restatement never reopens a frozen spec. State it to author straight at design or complete instead of promoting afterwards. A status that would LOWER the stored one is refused \u2014 reopening a spec for revision is sdd_update_spec's job, which sets the demotion deliberately."
28835
+ );
28836
+ var SKILL_RESOURCE_MIME = "text/markdown";
28837
+ function skillIdFromResourceUri(uri) {
28838
+ try {
28839
+ const u = new URL(uri);
28840
+ return u.host || u.pathname.replace(/^\/+/, "") || uri;
28841
+ } catch {
28842
+ return uri;
28843
+ }
28844
+ }
28845
+ var AGENT_BRIEF_SCHEME = "wairon-agent://";
28846
+ function listAgentBriefResources() {
28847
+ try {
28848
+ return resolveAgentTopology().map((a) => ({
28849
+ uri: `${AGENT_BRIEF_SCHEME}${a.id}`,
28850
+ name: a.name,
28851
+ description: a.description,
28852
+ mimeType: SKILL_RESOURCE_MIME
28853
+ }));
28854
+ } catch {
28855
+ return [];
28856
+ }
28857
+ }
28858
+ function renderAgentBriefMarkdown(brief) {
28859
+ const lines = [
28860
+ `# Agent brief: ${brief.agentId}`,
28861
+ "",
28862
+ `- **Name**: ${brief.name}`,
28863
+ `- **Template**: ${brief.template}`,
28864
+ ...brief.domainRoot ? [`- **Domain root**: ${brief.domainRoot}`] : [],
28865
+ `- **Owned paths**: ${brief.ownedPaths.join(", ")}`,
28866
+ ...brief.readPaths?.length ? [`- **Read paths**: ${brief.readPaths.join(", ")}`] : [],
28867
+ "",
28868
+ "## Instructions",
28869
+ "",
28870
+ brief.instructions
28871
+ ];
28872
+ if (brief.variantGuidance && !brief.instructions.includes(brief.variantGuidance)) {
28873
+ lines.push("", "## Variant guidance", "", brief.variantGuidance);
28874
+ }
28875
+ if (brief.budget && brief.profile) {
28876
+ lines.push(
28877
+ "",
28878
+ "## Execution budget",
28879
+ "",
28880
+ ...summarize(brief.budget, brief.profile),
28881
+ "",
28882
+ "Advisory \u2014 apply these when spawning. Map the capability tier onto your host tool's models; a tool that cannot express a field should ignore it rather than approximate it."
28883
+ );
28884
+ }
28885
+ return `${lines.join("\n")}
28886
+ `;
28887
+ }
28888
+ function registerSkillResources(server, listChanged) {
28889
+ server.server.registerCapabilities({ resources: listChanged ? { listChanged: true } : {} });
28890
+ server.server.setRequestHandler(import_types4.ListResourcesRequestSchema, () => ({
28891
+ resources: [
28892
+ ...listResources().map((d) => ({
28893
+ uri: d.resourceUri,
28894
+ name: d.name,
28895
+ description: d.description,
28896
+ mimeType: SKILL_RESOURCE_MIME
28897
+ })),
28898
+ ...listAgentBriefResources()
28899
+ ]
28900
+ }));
28901
+ server.server.setRequestHandler(import_types4.ReadResourceRequestSchema, (request) => {
28902
+ const uri = request.params.uri;
28903
+ if (uri.startsWith(AGENT_BRIEF_SCHEME)) {
28904
+ try {
28905
+ const brief = composeAgentBrief(uri.slice(AGENT_BRIEF_SCHEME.length));
28906
+ return { contents: [{ uri, mimeType: SKILL_RESOURCE_MIME, text: renderAgentBriefMarkdown(brief) }] };
28907
+ } catch (e) {
28908
+ throw new import_types4.McpError(import_types4.ErrorCode.InvalidParams, e instanceof Error ? e.message : String(e));
28909
+ }
28910
+ }
28911
+ try {
28912
+ const content = readResource(skillIdFromResourceUri(uri));
28913
+ return { contents: [{ uri, mimeType: SKILL_RESOURCE_MIME, text: content }] };
28914
+ } catch (e) {
28915
+ const message = e instanceof Error ? e.message : String(e);
28916
+ throw new import_types4.McpError(import_types4.ErrorCode.InvalidParams, message);
28917
+ }
28918
+ });
28919
+ }
28920
+ function registerSkillPrompts(server, listChanged) {
28921
+ server.server.registerCapabilities({ prompts: listChanged ? { listChanged: true } : {} });
28922
+ server.server.setRequestHandler(import_types4.ListPromptsRequestSchema, () => ({
28923
+ prompts: listResources().map((d) => ({
28924
+ name: d.id,
28925
+ title: d.name,
28926
+ description: d.description
28927
+ }))
28928
+ }));
28929
+ server.server.setRequestHandler(import_types4.GetPromptRequestSchema, (request) => {
28930
+ const name = request.params.name;
28931
+ try {
28932
+ const content = readResource(name);
28933
+ return {
28934
+ description: `The wairon "${name}" skill.`,
28935
+ messages: [{ role: "user", content: { type: "text", text: content } }]
28936
+ };
28937
+ } catch (e) {
28938
+ throw new import_types4.McpError(import_types4.ErrorCode.InvalidParams, e instanceof Error ? e.message : String(e));
28939
+ }
28940
+ });
28941
+ }
28942
+ function createMcpServer(options = {}) {
28943
+ const server = new import_mcp.McpServer({
28944
+ name: "wairon",
28945
+ version: WAIRON_VERSION
28946
+ }, {
28947
+ instructions: buildServerInstructions2()
28948
+ });
28949
+ serverBuildStamps.set(server, options.buildStamp === void 0 ? SERVER_BUILD_STAMP : options.buildStamp);
28950
+ if (!options.hostedTools) {
28951
+ listChangedEmitters.set(server, () => {
28952
+ try {
28953
+ void server.server.sendResourceListChanged().catch(() => {
28954
+ });
28955
+ void server.server.sendPromptListChanged().catch(() => {
28956
+ });
28957
+ } catch {
28958
+ }
28959
+ });
28960
+ }
28961
+ reg(
28962
+ server,
28963
+ "listAgents",
28964
+ {
28965
+ description: "List all AI agents registered in this project. Returns id, name, description, domainRoot, template, tags, and status for each agent. Filter by domainId to scope to one domain.",
28966
+ inputSchema: { domainId: import_zod11.z.string().optional() }
28967
+ },
28968
+ ({ domainId }) => {
28969
+ try {
28970
+ const registry = loadRegistry();
28971
+ const agents = domainId ? registry.agents.filter((a) => a.domainRoot === domainId) : registry.agents;
28972
+ return json(agents.map((a) => ({
28973
+ id: a.id,
28974
+ name: a.name,
28975
+ description: a.description,
28976
+ domainRoot: a.domainRoot,
28977
+ template: a.template,
28978
+ tags: a.tags,
28979
+ status: a.status
28980
+ })));
28981
+ } catch (e) {
28982
+ return errText(String(e));
28983
+ }
28984
+ }
28985
+ );
28986
+ reg(
28987
+ server,
28988
+ "getAgent",
28989
+ {
28990
+ description: "Get full details of a specific agent by id, including ownership rules, context, and output targets.",
28991
+ inputSchema: { id: import_zod11.z.string() }
28992
+ },
28993
+ ({ id }) => {
28994
+ try {
28995
+ const registry = loadRegistry();
28996
+ const agent = registry.agents.find((a) => a.id === id);
28997
+ if (!agent) return errText(`Agent "${id}" not found.`);
28998
+ return json(agent);
28999
+ } catch (e) {
29000
+ return errText(String(e));
29001
+ }
29002
+ }
29003
+ );
29004
+ reg(
29005
+ server,
29006
+ "listDomains",
29007
+ {
29008
+ description: "List all domains in this project: subsystem-derived (boundTo set) plus free-standing domains from .wai/topology.yaml."
29009
+ },
29010
+ () => {
29011
+ try {
29012
+ return json(resolveDomains2());
29013
+ } catch (e) {
29014
+ return errText(String(e));
29015
+ }
29016
+ }
29017
+ );
29018
+ reg(
29019
+ server,
29020
+ "validateTopology",
29021
+ {
29022
+ description: "Validate the project's agent topology. Returns errors and warnings (duplicate ids, overlapping ownership, missing paths, etc.). Supports optional subsystem scoping.",
29023
+ inputSchema: {
29024
+ subsystem: import_zod11.z.string().optional().describe("Only validate topology for agents under the specified subsystem")
29025
+ }
29026
+ },
29027
+ ({ subsystem }) => {
29028
+ try {
29029
+ let registry = loadRegistry();
29030
+ if (subsystem) {
29031
+ registry = {
29032
+ ...registry,
29033
+ agents: registry.agents.filter((a) => a.domainRoot === subsystem || a.domainRoot?.startsWith(`${subsystem}::`))
29034
+ };
29035
+ }
29036
+ const config = loadProjectConfig();
29037
+ if (!config) throw new ProjectNotInitializedError();
29038
+ const result = validateRegistry(registry, config.rules);
29039
+ return json({
29040
+ valid: result.issues.filter((i) => i.severity === "error").length === 0,
29041
+ errors: result.issues.filter((i) => i.severity === "error"),
29042
+ warnings: result.issues.filter((i) => i.severity === "warning")
29043
+ });
29044
+ } catch (e) {
29045
+ return errText(String(e));
29046
+ }
29047
+ }
29048
+ );
29049
+ reg(
29050
+ server,
29051
+ "getProjectConfig",
29052
+ {
29053
+ description: "Get the current project configuration (name, targets, rules, paths)."
29054
+ },
29055
+ () => {
29056
+ try {
29057
+ const config = loadProjectConfig();
29058
+ if (!config) throw new ProjectNotInitializedError();
29059
+ return json(config);
29060
+ } catch (e) {
29061
+ return errText(String(e));
29062
+ }
29063
+ }
29064
+ );
29065
+ const systemInput = {
29066
+ name: import_zod11.z.string().describe("Overarching name of the project/system"),
29067
+ vision: import_zod11.z.string().describe("Vision, mission, and core goals of the system"),
29068
+ boundaries: import_zod11.z.array(import_zod11.z.union([import_zod11.z.string(), import_zod11.z.object({ name: import_zod11.z.string(), description: import_zod11.z.string().optional() }).strict()])).optional().describe("System boundary rules or scope statements (strings or name/description objects)"),
29069
+ globalRequirements: import_zod11.z.array(import_zod11.z.union([import_zod11.z.string(), import_zod11.z.object({ description: import_zod11.z.string() }).strict()])).optional().describe("Global functional and non-functional requirements (strings or description objects)"),
29070
+ targetLanguage: import_zod11.z.string().optional().describe('Default implementation language for the system (e.g. "typescript", "rust", "python"). Subsystems may override. Enables language-aware validation.')
29071
+ };
29072
+ const systemInputFields = Object.keys(systemInput);
29073
+ reg(
29074
+ server,
29075
+ "sdd_initialize_system",
29076
+ {
29077
+ description: "Initialize the L0 System Specification (system.yaml). Re-running it on an existing system RE-AUTHORS it: the fields above are replaced, and everything this tool cannot express (databases, the project gateway publicInterfaces, diagram defaults) is carried forward. The answer carries a write receipt as structured content beside the sentence \u2014 what was written, and whether a system spec already existed \u2014 so a caller never has to read English to find out.",
29078
+ inputSchema: systemInput,
29079
+ outputSchema: specWriteReceiptOutput
29080
+ },
29081
+ ({ name, vision, boundaries, globalRequirements, targetLanguage }) => {
29082
+ try {
29083
+ const restatement = {
29084
+ kind: "system",
29085
+ spec: {
29086
+ name,
29087
+ vision,
29088
+ boundaries: boundaries ?? [],
29089
+ globalRequirements: globalRequirements ?? [],
29090
+ ...targetLanguage ? { targetLanguage } : {}
29091
+ },
29092
+ fields: systemInputFields
29093
+ };
29094
+ const receipt = writeSpec(restatement);
29095
+ return writeReceipt(
29096
+ `Successfully ${receipt.replacedExisting ? "re-authored" : "initialized"} L0 System Spec for "${name}".`,
29097
+ receipt
29098
+ );
29099
+ } catch (e) {
29100
+ return errMessage(e);
29101
+ }
29102
+ }
29103
+ );
29104
+ const subsystemInput = {
29105
+ id: import_zod11.z.string().describe("Lowercase identifier for the subsystem"),
29106
+ name: import_zod11.z.string().describe("Human-readable display name"),
29107
+ description: import_zod11.z.string().describe("Purpose and details of the subsystem"),
29108
+ publicInterfaces: import_zod11.z.array(import_zod11.z.object({
29109
+ type: import_zod11.z.enum(["REST", "GraphQL", "MessageBus", "RPC", "Custom"]),
29110
+ details: import_zod11.z.string(),
29111
+ component: import_zod11.z.string().optional().describe("The L2 component id that realizes this interface (this subsystem's published surface)"),
29112
+ interface: import_zod11.z.string().optional().describe("Optional L3 interface id on that component backing this entry"),
29113
+ consumers: import_zod11.z.array(import_zod11.z.string()).optional().describe("The subsystem ids this surface is published to. Absent, any subsystem may depend on the component; present, only these may \u2014 when every entry publishing the component names consumers, the union of the lists is the whole set, and a dependency from any other subsystem is CROSS_SUBSYSTEM_UNLISTED_CONSUMER. Every id must name a subsystem of the tree (PUBLIC_INTERFACE_UNKNOWN_CONSUMER).")
29114
+ }).strict()).optional().describe("Public entrypoints exposed by this subsystem, each bound to a realizing component"),
29115
+ projectPath: import_zod11.z.string().optional().describe("Relative path to external project root for subsystem chaining"),
29116
+ targetLanguage: import_zod11.z.string().optional().describe("Override of the system-level targetLanguage for this subsystem"),
29117
+ profile: import_zod11.z.string().optional().describe("Architectural profile override for this subsystem (built-ins: backend, frontend-reactive, frontend-controller, lowlevel-os, game-ecs, realtime-embedded, plc-cyclic; extension packs may add more \u2014 unknown names get UNKNOWN_PROFILE)"),
29118
+ designDepth: import_zod11.z.enum(["components", "interfaces", "implementations", "narratives"]).optional().describe("How deep THIS subsystem commits to designing (overrides project rules.designDepth; default narratives = full depth). Expectation checks below the depth are gated \u2014 soundness of authored content always applies."),
29119
+ trustedLinks: import_zod11.z.array(import_zod11.z.object({
29120
+ subsystem: import_zod11.z.string().describe("Peer subsystem id"),
29121
+ reason: import_zod11.z.string().describe('Why the coupling is sanctioned (e.g. "dispatch latency fast lane")')
29122
+ }).strict()).optional().describe("Sanctioned tight couplings with peers \u2014 required to acknowledge a mutual subsystem dependency; the Adapter \u2192 published Portal shape still applies."),
29123
+ lifecycle: import_zod11.z.array(import_zod11.z.object({
29124
+ phase: import_zod11.z.enum(["init", "shutdown", "cyclic", "interrupt", "scheduled"]).describe("Which lifecycle/execution flow this roots (cyclic = every scan/tick, interrupt = hardware/OS interrupt, scheduled = timer/cron)"),
29125
+ component: import_zod11.z.string().describe("Component id whose method the runtime invokes at this phase"),
29126
+ method: import_zod11.z.string().describe("Method name on that component's interface"),
29127
+ description: import_zod11.z.string().optional()
29128
+ }).strict()).optional().describe("Declared execution-flow roots \u2014 reachability entrypoints alongside Portals/Observers. Only init flows feed the durable-Store hydration check (MISSING_HYDRATION); cyclic/interrupt/scheduled root non-request/response execution models (PLC scan, ISR, cron)."),
29129
+ status: statusInput
29130
+ };
29131
+ const subsystemInputFields = [...Object.keys(subsystemInput), "parentSystem"];
29132
+ reg(
29133
+ server,
29134
+ "sdd_add_subsystem",
29135
+ {
29136
+ description: "Add an L1 Subsystem / Service under the system boundary. publicInterfaces should bind each entry to the component that realizes it (the subsystem's published surface); if components do not exist yet, add them later with sdd_set_public_interfaces. Re-running it on an existing id RE-AUTHORS it: the fields above are replaced, lint/ext are carried forward, and the stored status is kept unless this input states a higher one. The answer carries a write receipt as structured content beside the sentence \u2014 the status written, whether a spec already held the id, and the child project directory a chained subsystem scaffolded.",
29137
+ inputSchema: subsystemInput,
29138
+ outputSchema: specWriteReceiptOutput
29139
+ },
29140
+ ({ id, name, description, publicInterfaces, projectPath, targetLanguage, profile, designDepth, trustedLinks, lifecycle, status }) => {
29141
+ try {
29142
+ const restatement = {
29143
+ kind: "subsystem",
29144
+ spec: {
29145
+ id,
29146
+ name,
29147
+ description,
29148
+ publicInterfaces: publicInterfaces ?? [],
29149
+ projectPath,
29150
+ ...targetLanguage ? { targetLanguage } : {},
29151
+ ...profile ? { profile } : {},
29152
+ ...designDepth ? { designDepth } : {},
29153
+ ...lifecycle ? { lifecycle } : {},
29154
+ trustedLinks: trustedLinks ?? []
29155
+ },
29156
+ fields: subsystemInputFields,
29157
+ status
29158
+ };
29159
+ const receipt = writeSpec(restatement);
29160
+ if (receipt.scaffoldedProjectPath) {
29161
+ return writeReceipt(
29162
+ `Successfully added external subsystem "${name}" (${id}) and scaffolded its child project at ${receipt.scaffoldedProjectPath}.`,
29163
+ receipt
29164
+ );
29165
+ }
29166
+ return writeReceipt(`Successfully added L1 Subsystem Spec "${name}" (${id}).`, receipt);
29167
+ } catch (e) {
29168
+ return errMessage(e);
29169
+ }
29170
+ }
29171
+ );
29172
+ reg(
29173
+ server,
29174
+ "sdd_set_public_interfaces",
29175
+ {
29176
+ description: "Set (replace) an existing subsystem's publicInterfaces, binding each to the component (and optional interface) that realizes it. Use this to backfill bindings once the subsystem's components exist \u2014 cross-subsystem dependencies may only target a published public component. A replacement, not a merge: an entry left out is removed. An entry's identity is its component and interface, so a list naming one identity twice is refused. Written through the authoring seam as one gated delta, and answered with the change report \u2014 every entry added, changed or removed, or that nothing changed \u2014 as structured content beside the sentence.",
29177
+ inputSchema: {
29178
+ subsystem: import_zod11.z.string().describe("The L1 subsystem id to update"),
29179
+ publicInterfaces: import_zod11.z.array(import_zod11.z.object({
29180
+ type: import_zod11.z.enum(["REST", "GraphQL", "MessageBus", "RPC", "Custom"]),
29181
+ details: import_zod11.z.string(),
29182
+ component: import_zod11.z.string().optional().describe("The L2 component id that realizes this interface"),
29183
+ interface: import_zod11.z.string().optional().describe("Optional L3 interface id on that component"),
29184
+ consumers: import_zod11.z.array(import_zod11.z.string()).optional().describe("The subsystem ids this surface is published to. Absent, any subsystem may depend on the component; present, only these may \u2014 when every entry publishing the component names consumers, the union of the lists is the whole set, and a dependency from any other subsystem is CROSS_SUBSYSTEM_UNLISTED_CONSUMER. Every id must name a subsystem of the tree (PUBLIC_INTERFACE_UNKNOWN_CONSUMER).")
29185
+ }).strict()).describe("The full replacement list of public interfaces for this subsystem")
29186
+ },
29187
+ outputSchema: specChangeReportOutput
29188
+ },
29189
+ ({ subsystem, publicInterfaces }) => {
29190
+ try {
29191
+ const sub = loadSubsystemSpec(subsystem);
29192
+ if (!sub) return errText(`Subsystem "${subsystem}" does not exist.`);
29193
+ const delta = publicInterfacesReplacement(subsystem, sub.publicInterfaces ?? [], publicInterfaces);
29194
+ if (typeof delta === "string") return errText(delta);
29195
+ const report = updateSpecGated("subsystem", subsystem, delta);
29196
+ return structured(
29197
+ `Updated public interfaces for subsystem "${subsystem}" (${publicInterfaces.length} ${publicInterfaces.length === 1 ? "entry" : "entries"}).
29198
+ ${renderChangeReport(report)}`,
29199
+ report
29200
+ );
29201
+ } catch (e) {
29202
+ return errMessage(e);
29203
+ }
29204
+ }
29205
+ );
29206
+ reg(
29207
+ server,
29208
+ "sdd_set_subsystem_project_path",
29209
+ {
29210
+ description: "Set (or clear) the projectPath of an L1 Subsystem for subsystem chaining. projectPath should be a relative path to the external project root directory. It rewrites the link only (sdd_move_subsystem_project relocates the directory with it). Written through the authoring seam as a gated delta, and answered with the change report as structured content beside the sentence.",
29211
+ inputSchema: {
29212
+ subsystem: import_zod11.z.string().describe("The L1 subsystem id to update"),
29213
+ projectPath: import_zod11.z.string().optional().describe("Relative path to external project root (or omit to clear)")
29214
+ },
29215
+ outputSchema: specChangeReportOutput
29216
+ },
29217
+ ({ subsystem, projectPath }) => {
29218
+ try {
29219
+ const delta = projectPath ? { projectPath } : { unset: ["projectPath"] };
29220
+ const report = updateSpecGated("subsystem", subsystem, delta);
29221
+ return structured(
29222
+ `Updated projectPath for subsystem "${subsystem}" to: ${projectPath || "none (cleared)"}
29223
+ ${renderChangeReport(report)}`,
29224
+ report
29225
+ );
29226
+ } catch (e) {
29227
+ return errMessage(e);
29228
+ }
29229
+ }
29230
+ );
29231
+ reg(
29232
+ server,
29233
+ "sdd_move_subsystem_project",
29234
+ {
29235
+ description: "Relocate an external subsystem: move its subproject directory on disk to newProjectPath and update its projectPath link in one step. Errors if the subsystem has no projectPath (not an external subproject).",
29236
+ inputSchema: {
29237
+ subsystem: import_zod11.z.string().describe("The external L1 subsystem id to relocate"),
29238
+ newProjectPath: import_zod11.z.string().describe("The new relative path for the subproject directory")
29239
+ }
29240
+ },
29241
+ ({ subsystem, newProjectPath }) => {
29242
+ try {
29243
+ moveSubsystemProject(subsystem, newProjectPath);
29244
+ return text(`Moved subsystem "${subsystem}" to: ${newProjectPath}`);
29245
+ } catch (e) {
29246
+ return errText(String(e));
29247
+ }
29248
+ }
29249
+ );
29250
+ reg(
29251
+ server,
29252
+ "sdd_externalize_subsystem",
29253
+ {
29254
+ description: "Migrate an internal subsystem into a standalone subproject at projectPath: move its spec subtree out, mount it via projectPath, and rewrite cross-subsystem references to the new namespaced ids. Source code is not moved. Errors if the subsystem is missing or already external.",
29255
+ inputSchema: {
29256
+ subsystem: import_zod11.z.string().describe("The internal L1 subsystem id to externalize"),
29257
+ projectPath: import_zod11.z.string().describe("Relative destination directory for the new subproject")
29258
+ }
29259
+ },
29260
+ ({ subsystem, projectPath }) => {
29261
+ try {
29262
+ externalizeSubsystem(subsystem, projectPath);
29263
+ return text(`Externalized subsystem "${subsystem}" into subproject: ${projectPath}. Move its source code there and re-validate.`);
29264
+ } catch (e) {
29265
+ return errText(String(e));
29266
+ }
29267
+ }
29268
+ );
29269
+ reg(
29270
+ server,
29271
+ "sdd_internalize_subsystem",
29272
+ {
29273
+ description: "Migrate an external subsystem back into the parent tree: move its subproject spec subtree back under the parent, drop projectPath, delete the child .wai project, and rewrite references back to bare ids. Errors if the subsystem is not external or its subproject is not a single flat subsystem.",
29274
+ inputSchema: {
29275
+ subsystem: import_zod11.z.string().describe("The external L1 subsystem id to internalize")
29276
+ }
29277
+ },
29278
+ ({ subsystem }) => {
29279
+ try {
29280
+ internalizeSubsystem(subsystem);
29281
+ return text(`Internalized subsystem "${subsystem}" back into this project (child .wai removed).`);
29282
+ } catch (e) {
29283
+ return errText(String(e));
29284
+ }
29285
+ }
29286
+ );
29287
+ reg(
29288
+ server,
29289
+ "sdd_rename_component",
29290
+ {
29291
+ description: "Rename a component and rewrite every reference to it in the bound tree: dependsOn, owns and dispatch entries, lifecycle entrypoints, published interfaces at L1 and L0, narrative call/dispatch/register targets, an interface's component, an implementation's contract and an entity's componentClass. The interface named i<id> and the implementation named <id>_impl move to the new ids and files; interfaces and implementations named otherwise keep their ids. Display names are left as they are. Refuses, writing nothing: a component that does not exist (component-missing), one inside a chained subproject (chained-component \u2014 rename it from that project's own root), a new id that is not a lowercase identifier without a namespace separator (invalid-id), and a new id \u2014 or i<newId> or <newId>_impl \u2014 already in use (id-taken). Returns the specs moved, each with its old and new id, and the ids of the other specs rewritten.",
29292
+ inputSchema: {
29293
+ id: import_zod11.z.string().describe("The component to rename (namespaced if needed)"),
29294
+ newId: import_zod11.z.string().describe("Its new id: a lowercase identifier with no namespace separator")
29295
+ }
29296
+ },
29297
+ ({ id, newId }) => {
29298
+ try {
29299
+ return json(renameComponent(qualifiedComponentId(id), newId));
29300
+ } catch (e) {
29301
+ return errText(String(e));
29302
+ }
29303
+ }
29304
+ );
29305
+ reg(
29306
+ server,
29307
+ "sdd_rename_method",
29308
+ {
29309
+ description: "Rename a contract method and retarget every reference to it in the bound tree. The method moves on every interface of the component that declares it \u2014 its name, and the name inside its signature \u2014 and on the implementations of those contracts, carrying narrative, sourcePath, symbol, detail, intent and findings unchanged; an implementation that declared no symbol is pinned to the old name unless pinSymbol is false, so the function it already binds to keeps binding. Narrative call, register and dispatch steps naming this component and method, dispatch-table bindings and lifecycle entrypoints are retargeted. Prose is never rewritten and a gRPC endpoint binding keeps its wire method \u2014 renaming a contract method must not silently rename an RPC; both are reported as mentions. Refuses, writing nothing: a component that does not exist (component-missing), one inside a chained subproject (chained-component \u2014 rename its method from that project's own root), a new name that is not a camel-case identifier (invalid-name), a method the component does not declare (method-missing), and a name a moving contract already declares (name-taken). Returns the specs the method moved in, the specs retargeted, the specs whose prose still names it, and the pinned symbol when one was set.",
29310
+ inputSchema: {
29311
+ id: import_zod11.z.string().describe("The component whose method is renamed (namespaced if needed)"),
29312
+ method: import_zod11.z.string().describe("The method name as it stands"),
29313
+ newName: import_zod11.z.string().describe("Its new name: a camel-case identifier"),
29314
+ pinSymbol: import_zod11.z.boolean().optional().describe("Whether an implementation that declares no symbol is pinned to the old name so its function still binds; true when omitted")
29315
+ }
29316
+ },
29317
+ ({ id, method, newName, pinSymbol }) => {
29318
+ try {
29319
+ return json(renameMethod(qualifiedComponentId(id), method, newName, pinSymbol));
29320
+ } catch (e) {
29321
+ return errText(String(e));
29322
+ }
29323
+ }
29324
+ );
29325
+ reg(
29326
+ server,
29327
+ "sdd_move_methods",
29328
+ {
29329
+ description: "Move contract methods from one component to another, carrying each method's narrative, calls and bindings, and re-pointing every reference that named the old home: narrative call/register/dispatch steps, dispatch-table bindings, lifecycle entrypoints and `calls` entries. The target gains the dependencies the moved narratives call. ONE all-or-nothing gated write: if a rule refuses the move nothing is written and the report names the rule plus where these methods could live instead, cheapest legal home first, with the requested target among them so its shortfall reads beside the others. Prose is never rewritten and a wire endpoint keeps its address \u2014 moving a method between components must not silently re-address an RPC; both are reported as mentions. A target that declares no contract yet receives one, i<target>, and when implementation entries travel and no implementation realizes the receiving contract, <target>_impl is created for them, inheriting the source implementation's sourcePath; the report's `created` names every spec the move created. Refuses before the first write (unmovable request): a component or method that does not exist, a method name already declared on the target, a source and target that are the same component, a target with several contracts none named i<target>, a contract or implementation to create whose id another spec already holds, and a component inside a chained subproject (move it from that project's own root). A dry run runs the whole move, gate included, and reports what it would have done.",
29330
+ inputSchema: {
29331
+ from: import_zod11.z.string().describe("The component the methods belong to (namespaced if needed)"),
29332
+ to: import_zod11.z.string().describe("The component that should receive them (namespaced if needed)"),
29333
+ methods: import_zod11.z.array(import_zod11.z.string()).describe("The method names to move; every one must exist on the source contract"),
29334
+ dryRun: import_zod11.z.boolean().optional().describe("Answer with what the move would do and write nothing")
29335
+ }
29336
+ },
29337
+ ({ from, to, methods, dryRun }) => {
29338
+ try {
29339
+ const source = qualifiedComponentId(from);
29340
+ const target = qualifiedComponentId(to);
29341
+ if (!loadComponentSpec(source)) return errText(`no component has the id "${from}".`);
29342
+ if (!loadComponentSpec(target)) return errText(`no component has the id "${to}".`);
29343
+ return json(moveMethods2(source, target, methods, dryRun));
29344
+ } catch (e) {
29345
+ return errText(String(e));
29346
+ }
29347
+ }
29348
+ );
29349
+ const componentInput = {
29350
+ id: import_zod11.z.string().describe("Lowercase identifier for the component"),
29351
+ name: import_zod11.z.string().describe("Human-readable display name"),
29352
+ description: import_zod11.z.string().describe("Responsibility / internal architecture details"),
29353
+ subsystem: import_zod11.z.string().describe("The L1 subsystem ID this component belongs to"),
29354
+ componentType: import_zod11.z.enum(["Portal", "Orchestrator", "Supervisor", "Actor", "Store", "Index", "Query", "Registry", "Adapter", "Observer", "Repository"]).describe("The building block, or the pattern Repository. Specialist and Gateway are retired and cannot be authored: logic is an Orchestrator with a dependencyClass, a gateway is a Portal with the gateway variant"),
29355
+ owns: import_zod11.z.array(import_zod11.z.string()).optional().describe("Member block ids privately owned by this component (patterns only)"),
29356
+ dependsOn: import_zod11.z.array(import_zod11.z.string()).optional().describe("IDs of other components this collaborates with (facades or standalone blocks)"),
29357
+ portalType: import_zod11.z.enum(["HTTP_API", "gRPC", "GraphQL", "MessageBus", "CLI", "NamedPipe", "IPC", "Custom"]).optional().describe("Portal-only, and expected on every Portal. OMIT IT on any other componentType \u2014 passing it there is refused at the write (UNEXPECTED_PORTAL_FIELD), nothing is saved."),
29358
+ basePath: import_zod11.z.string().optional().describe("Portal-only: base path/prefix all the portal's endpoints mount under. OMIT IT on any other componentType \u2014 passing it there is refused at the write (UNEXPECTED_PORTAL_FIELD), nothing is saved."),
29359
+ dispatch: import_zod11.z.array(import_zod11.z.object({
29360
+ capability: import_zod11.z.string().describe('Capability name exactly as dispatched at runtime (e.g. "shadow_module.get")'),
29361
+ component: import_zod11.z.string().describe("Component id serving this capability (must also appear under dependsOn/owns)"),
29362
+ method: import_zod11.z.string().describe("Method name on the serving component's interface"),
29363
+ description: import_zod11.z.string().optional()
29364
+ }).strict()).optional().describe("Portal-only: capability \u2192 component.method dispatch table for generic-handle portals. Gives the reachability walker real edges and is validated against target interfaces (UNSERVED_CAPABILITY)."),
29365
+ mounts: import_zod11.z.array(import_zod11.z.object({
29366
+ portal: import_zod11.z.string().describe("The mounted Portal's component id"),
29367
+ prefixes: import_zod11.z.array(import_zod11.z.string()).describe('The path prefixes this listener routes to the portal. A path lies under a prefix when it equals it or continues it past a slash, so "/" covers only the root itself'),
29368
+ via: import_zod11.z.string().optional().describe("The router entry the listener calls to hand the portal its request, exported by the PORTAL's own file (held to it: UNREALIZED_EXPORT_HANDLE). Omit when the listener calls the portal's contract methods directly, route by route")
29369
+ }).strict()).optional().describe("Portal-only: the portals this LISTENER serves, each under its path prefixes and through its router entry. Declaring the field \u2014 even as an empty array \u2014 marks this portal as a listener, the one kind of portal nothing else needs to mount. Checked by portal-mounts: a mount must name a Portal (MOUNT_TARGET_NOT_PORTAL), every HTTP endpoint of a mounted portal must lie under one of its prefixes (ENDPOINT_OUTSIDE_MOUNT), and a portal with HTTP endpoints that is neither a listener nor mounted by one is reported (UNMOUNTED_PORTAL)."),
29370
+ durability: import_zod11.z.enum(["ram-projection", "durable", "read-through", "cache"]).optional().describe("Store-only \u2014 OMIT IT on any other componentType, where it is refused at the write (DURABILITY_ON_NON_STORE) and nothing is saved. Every Store should declare one (MISSING_DURABILITY): durable = persisted RAM projection (hydration read-back from a lifecycle init entrypoint required \u2014 MISSING_HYDRATION); read-through = persisted with no RAM copy (every read is the read-back, hydration exempt); ram-projection = rebuilt not restored; cache = evictable loss-safe memo state."),
29371
+ dependencyClass: import_zod11.z.enum(["pure", "read"]).optional().describe("Orchestrator-only \u2014 OMIT IT on any other componentType, where it is refused at the write (DEPENDENCY_CLASS_ON_NON_ORCHESTRATOR) and nothing is saved. Declares what logic may depend on, enforced as a Store's durability is (DEPENDENCY_CLASS_VIOLATION): pure = depends only on pure Orchestrators (a computation over the values it is handed, e.g. an arbiter or codec); read = also on read Orchestrators, Repositories, Indexes and Adapters, never calling their write methods; unset = a workflow."),
29372
+ emits: import_zod11.z.array(import_zod11.z.object({
29373
+ topic: import_zod11.z.string().describe("Topic/channel name exactly as used on the bus"),
29374
+ event: import_zod11.z.string().optional().describe("Optional event name within the topic (informational; pairing is by topic)"),
29375
+ description: import_zod11.z.string().optional()
29376
+ }).strict()).optional().describe("Topics this component publishes \u2014 every emitted topic needs a subscriber somewhere in the tree (UNCONSUMED_TOPIC)."),
29377
+ subscribesTo: import_zod11.z.array(import_zod11.z.object({
29378
+ topic: import_zod11.z.string().describe("Topic/channel name exactly as used on the bus"),
29379
+ event: import_zod11.z.string().optional(),
29380
+ description: import_zod11.z.string().optional()
29381
+ }).strict()).optional().describe("Topics this component consumes (typical on Observers) \u2014 every subscription needs an emitter somewhere in the tree (UNSOURCED_SUBSCRIPTION)."),
29382
+ ext: import_zod11.z.record(import_zod11.z.unknown()).optional().describe('Opaque pack/tool extension data (namespaced keys, e.g. "mypack:priority") \u2014 preserved verbatim, never validated or interpreted by the core'),
29383
+ status: statusInput
29384
+ };
29385
+ const componentInputFields = Object.keys(componentInput);
29386
+ reg(
29387
+ server,
29388
+ "sdd_add_component",
29389
+ {
29390
+ description: `Add an L2 Component under a subsystem. componentType is a building block (Portal, Orchestrator, Supervisor, Actor, Store, Index, Query, Registry, Adapter, Observer) or the pattern Repository. Specialist and Gateway are retired (STEREOTYPE_RETIRED) and cannot be authored: logic is an Orchestrator with a dependencyClass (pure | read; unset = a workflow), and a gateway is a Portal with the gateway variant (set variant with sdd_update_spec). Patterns set "owns" (their private member blocks); all components set "dependsOn" (collaborators \u2014 facades or standalone blocks). Held/persisted state (configs, permissions, sessions, caches): model the Repository recipe \u2014 a Store + Registry (write) + Index (read), plus a Query for computed reads over the Store, owned by a Repository facade consumers depend on; a deliberately standalone Store is the sanctioned lightweight form (workflow-layer consumers + lint.allow on UNOWNED_STORE). Never hold state as fields inside an Orchestrator because a Store link was refused. Re-running it on an existing id RE-AUTHORS it: the fields above are replaced (an omitted array is CLEARED), while lint.allow, a Portal's auth, variant, patterns and externalLinks are carried forward \u2014 edit those with sdd_update_spec. The stored status is kept unless this input states a higher one. The answer carries a write receipt as structured content beside the sentence \u2014 the status written, whether a spec already held the id, and every gate notice the write raised, each as its own entry.`,
29391
+ inputSchema: componentInput,
29392
+ outputSchema: specWriteReceiptOutput
29393
+ },
29394
+ ({ id, name, description, subsystem, componentType, owns, dependsOn, portalType, basePath, dispatch, mounts, durability, dependencyClass, emits, subscribesTo, ext, status }) => {
29395
+ try {
29396
+ const restatement = {
29397
+ kind: "component",
29398
+ spec: {
29399
+ id,
29400
+ name,
29401
+ description,
29402
+ subsystem,
29403
+ componentType,
29404
+ owns: owns ?? [],
29405
+ dependsOn: dependsOn ?? [],
29406
+ ...portalType ? { portalType } : {},
29407
+ ...basePath ? { basePath } : {},
29408
+ ...dispatch ? { dispatch } : {},
29409
+ // An EMPTY list is kept: declaring the field is what marks a listener.
29410
+ ...mounts ? { mounts } : {},
29411
+ ...durability ? { durability } : {},
29412
+ ...dependencyClass ? { dependencyClass } : {},
29413
+ ...emits ? { emits } : {},
29414
+ ...subscribesTo ? { subscribesTo } : {},
29415
+ ...ext ? { ext } : {}
29416
+ },
29417
+ fields: componentInputFields,
29418
+ status
29419
+ };
29420
+ const receipt = writeSpec(restatement);
29421
+ return writeReceipt(
29422
+ `Successfully ${receipt.replacedExisting ? "re-authored" : "added"} L2 Component Spec "${name}" (${id}, ${componentType}).`,
29423
+ receipt
29424
+ );
29425
+ } catch (e) {
29426
+ return errMessage(e);
29427
+ }
29428
+ }
29429
+ );
29430
+ const interfaceMethodShape = {
29431
+ name: import_zod11.z.string(),
29432
+ description: import_zod11.z.string(),
29433
+ signature: import_zod11.z.string(),
29434
+ returns: import_zod11.z.string().describe(TYPE_REF_GRAMMAR("The type the method answers with")),
29435
+ params: import_zod11.z.array(import_zod11.z.object({
29436
+ name: import_zod11.z.string(),
29437
+ type: import_zod11.z.string().describe(TYPE_REF_GRAMMAR("The parameter's type")),
29438
+ description: import_zod11.z.string().optional(),
29439
+ optional: import_zod11.z.boolean().optional().describe(
29440
+ 'Whether the parameter may be OMITTED by a caller. It is not nullability: a parameter that must be passed but may be passed as nothing is a required parameter whose type is a union \u2014 "ProjectConfig | null". Say whichever is true; they are different contracts.'
29441
+ )
29442
+ }).strict()).optional().describe("Structured parameters \u2014 authoritative for type checking (the prose signature becomes display-only). Strongly preferred."),
29443
+ guarantees: import_zod11.z.array(import_zod11.z.string().min(1)).optional().describe("Semantic guarantees the method promises (combinable); any guarantee a narrative step asserts must be declared here. Builtin tokens: idempotent | atomic | transactional | exactly-once; extension packs may declare more (any other token is UNKNOWN_GUARANTEE)"),
29444
+ effect: import_zod11.z.enum(["read", "write"]).optional().describe("State-effect direction on the component's held state \u2014 required on a durable Store's contract methods so the durability round-trip rule can pair writes with hydration read-backs"),
29445
+ invokedBy: import_zod11.z.object({
29446
+ kind: import_zod11.z.enum(["runtime", "external", "sibling-subsystem"]).describe("Who owns the out-of-graph invocation: runtime (timer/signal/shutdown hook), external (a system outside this project), sibling-subsystem (a modeled sibling whose edge is not narrated here)"),
29447
+ caller: import_zod11.z.string().optional().describe("WHO invokes it and when, as reviewable prose \u2014 missing or placeholder-thin prose is INVOKED_BY_UNDESCRIBED")
29448
+ }).strict().optional().describe("Typed acknowledgment of a real caller OUTSIDE the modeled narrative graph. Unused-detection seeds the method as an entrypoint so reachability propagates through its narrative (unlike lint.allow); a method the internal walk already reaches is flagged stale (INVOKED_BY_REDUNDANT). Prefer a `register` narrative step when the wiring is internal."),
29449
+ findings: import_zod11.z.array(import_zod11.z.object({
29450
+ code: import_zod11.z.string().describe("UPPER_SNAKE finding code, unique within the method; a pack's codes carry the pack prefix (<PACK>_<CODE>)"),
29451
+ severity: import_zod11.z.enum(["error", "warning"]).describe("Default severity, before project severity overrides and draft-context downgrades"),
29452
+ summary: import_zod11.z.string().describe("One line saying what the finding means")
29453
+ }).strict()).optional().describe("The finding codes this method can report, each with its default severity and summary. Each declared code must be anchored in the method's source file, as a string literal or a property-access name (UNREALIZED_FINDING). A code is declared once per method; sdd_update_spec upserts and deletes findings by code."),
29454
+ ext: import_zod11.z.record(import_zod11.z.unknown()).optional().describe("Opaque pack/tool extension data for this method (namespaced keys) \u2014 preserved verbatim")
29455
+ };
29456
+ const interfaceMethodInputFields = Object.keys(interfaceMethodShape);
29457
+ const interfaceInput = {
29458
+ id: import_zod11.z.string().describe('Lowercase identifier prefixed with "i", e.g. "istorage"'),
29459
+ name: import_zod11.z.string().describe("Human-readable contract name"),
29460
+ description: import_zod11.z.string().describe("Contract description and obligations"),
29461
+ component: import_zod11.z.string().describe("The L2 component ID this interface belongs to"),
29462
+ methods: import_zod11.z.array(import_zod11.z.object(interfaceMethodShape).strict()).optional().describe("List of method signature contracts"),
29463
+ status: statusInput
29464
+ };
29465
+ const interfaceInputFields = Object.keys(interfaceInput);
29466
+ reg(
29467
+ server,
29468
+ "sdd_define_interface",
29469
+ {
29470
+ description: "Define an L3 Contract / Interface with method signatures for a component. Prefer supplying structured `params` per method \u2014 they are the authoritative source for type checking (the free-form signature string then becomes display-only and is never heuristically parsed). A method declares the finding codes it reports in `findings` ({code, severity, summary}); each code must be anchored in the method's source file, as a string literal or a property-access name (UNREALIZED_FINDING). Re-defining an existing id REPLACES the method list: a method left out of the input is REMOVED (and reported); spec-level lint/ext and each method's endpoint binding are carried forward, and the stored status is kept unless this input states a higher one. The answer carries a write receipt as structured content beside the sentence \u2014 the status written, whether a spec already held the id, and the notices a restatement raised, each as its own entry.",
29471
+ inputSchema: interfaceInput,
29472
+ outputSchema: specWriteReceiptOutput
29473
+ },
29474
+ ({ id, name, description, component, methods, status }) => {
29475
+ try {
29476
+ const restatement = {
29477
+ kind: "interface",
29478
+ spec: { id, name, description, component, methods: methods ?? [] },
29479
+ fields: interfaceInputFields,
29480
+ memberFields: interfaceMethodInputFields,
29481
+ status
29482
+ };
29483
+ const receipt = writeSpec(restatement);
29484
+ return writeReceipt(
29485
+ `Successfully ${receipt.replacedExisting ? "re-authored" : "defined"} L3 Interface Contract "${name}" (${id}).`,
29486
+ receipt
29487
+ );
29488
+ } catch (e) {
29489
+ return errMessage(e);
29490
+ }
29491
+ }
29492
+ );
29493
+ reg(
29494
+ server,
29495
+ "sdd_set_endpoints",
29496
+ {
29497
+ description: "Bind concrete wire endpoints to existing L3 interface methods (required for every Portal). Pick `transport` and fill that transport's address fields. Run after sdd_define_interface. Written through the authoring seam as one gated delta, and answered with the change report \u2014 every binding set or changed, or that nothing changed \u2014 as structured content beside the sentence.",
29498
+ inputSchema: {
29499
+ interface: import_zod11.z.string().describe('The L3 interface ID (e.g. "ibilling-gateway")'),
29500
+ endpoints: import_zod11.z.array(import_zod11.z.object({
29501
+ method: import_zod11.z.string().describe("The NAME of the interface method to bind (not the HTTP verb)"),
29502
+ transport: import_zod11.z.enum(["HTTP", "gRPC", "GraphQL", "MessageBus", "NamedPipe", "IPC", "CLI", "Custom"]).describe("Wire protocol; must match the Portal's portalType"),
29503
+ httpMethod: import_zod11.z.enum(["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"]).optional().describe("HTTP: verb"),
29504
+ path: import_zod11.z.string().optional().describe('HTTP: route path, e.g. "/v1/checkout"'),
29505
+ service: import_zod11.z.string().optional().describe("gRPC: service name"),
29506
+ rpcMethod: import_zod11.z.string().optional().describe("gRPC: rpc method name"),
29507
+ operation: import_zod11.z.enum(["query", "mutation", "subscription"]).optional().describe("GraphQL: operation kind"),
29508
+ field: import_zod11.z.string().optional().describe("GraphQL: root field name"),
29509
+ topic: import_zod11.z.string().optional().describe("MessageBus: topic"),
29510
+ event: import_zod11.z.string().optional().describe("MessageBus: event name"),
29511
+ queue: import_zod11.z.string().optional().describe("MessageBus: optional queue/consumer group"),
29512
+ direction: import_zod11.z.enum(["subscribe", "publish"]).optional().describe("MessageBus: subscribe (default) or publish"),
29513
+ pipe: import_zod11.z.string().optional().describe('NamedPipe: pipe name, e.g. "\\\\.\\pipe\\gk-events"'),
29514
+ channel: import_zod11.z.string().optional().describe("IPC: channel name"),
29515
+ command: import_zod11.z.string().optional().describe("CLI: command/subcommand"),
29516
+ address: import_zod11.z.string().optional().describe("Custom: free-form address")
29517
+ }).strict()).describe("One binding per method")
29518
+ },
29519
+ outputSchema: specChangeReportOutput
29520
+ },
29521
+ ({ interface: interfaceId, endpoints }) => {
29522
+ try {
29523
+ const intf = loadInterfaceSpec(interfaceId);
29524
+ if (!intf) return errText(`Interface "${interfaceId}" does not exist.`);
29525
+ const buildRaw = (e) => {
29526
+ switch (e.transport) {
29527
+ case "HTTP":
29528
+ return { transport: "HTTP", method: e.httpMethod, path: e.path };
29529
+ case "gRPC":
29530
+ return { transport: "gRPC", service: e.service, method: e.rpcMethod };
29531
+ case "GraphQL":
29532
+ return { transport: "GraphQL", operation: e.operation, field: e.field };
29533
+ case "MessageBus":
29534
+ return { transport: "MessageBus", topic: e.topic, event: e.event, queue: e.queue, direction: e.direction ?? "subscribe" };
29535
+ case "NamedPipe":
29536
+ return { transport: "NamedPipe", pipe: e.pipe };
29537
+ case "IPC":
29538
+ return { transport: "IPC", channel: e.channel };
29539
+ case "CLI":
29540
+ return { transport: "CLI", command: e.command };
29541
+ case "Custom":
29542
+ return { transport: "Custom", address: e.address };
29543
+ }
29544
+ };
29545
+ const bound2 = [];
29546
+ const methods = [];
29547
+ for (const e of endpoints) {
29548
+ const m = intf.methods.find((x) => x.name === e.method);
29549
+ if (!m) return errText(`Method "${e.method}" not found on interface "${interfaceId}". Define it via sdd_define_interface first.`);
29550
+ const parsed = EndpointSchema.safeParse(buildRaw(e));
29551
+ if (!parsed.success) {
29552
+ const detail = parsed.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ");
29553
+ return errText(`Invalid ${e.transport} endpoint for method "${e.method}": ${detail}`);
29554
+ }
29555
+ methods.push({ name: e.method, endpoint: parsed.data });
29556
+ bound2.push(`${e.method}\u2192${e.transport}`);
29557
+ }
29558
+ const report = updateSpecGated("interface", interfaceId, { methods });
29559
+ return structured(
29560
+ `Bound ${bound2.length} endpoint(s) on "${interfaceId}": ${bound2.join(", ")}.
29561
+ ${renderChangeReport(report)}`,
29562
+ report
29563
+ );
29564
+ } catch (e) {
29565
+ return errMessage(e);
29566
+ }
29567
+ }
29568
+ );
29569
+ const stepNo = () => import_zod11.z.number().int().positive();
29570
+ const labelRef = () => import_zod11.z.string().min(1);
29571
+ const narrativeStepInput = import_zod11.z.object({
29572
+ stepNumber: stepNo().optional().describe("Defaults to the 1-based array position \u2014 jump fields reference these numbers"),
29573
+ label: labelRef().optional().describe("Optional symbolic anchor for this step (unique per narrative). Every jump-by-number field has a *Label twin resolved against these anchors at write time \u2014 prefer labels over hand-counted step numbers"),
29574
+ description: import_zod11.z.string(),
29575
+ type: import_zod11.z.enum(["local", "call", "dispatch", "register", "branch", "switch", "loop", "try", "parallel", "jump", "return", "throw"]),
29576
+ targetComponent: import_zod11.z.string().optional().describe("call/register/dispatch: L2 component id (for dispatch, the Portal routed through)"),
29577
+ targetMethod: import_zod11.z.string().optional().describe("call/register: method name on the target. A register step hands the target method to the runtime as a callback (timer, event listener, shutdown hook): reachability follows the edge, but it is never an invocation \u2014 exempt from call-graph conformance, call-cycle detection, and the durability boot walk"),
29578
+ auth: import_zod11.z.object({ from: import_zod11.z.string(), note: import_zod11.z.string().optional() }).strict().optional().describe("call/dispatch: the credential this step presents to an AUTHED callee Portal and WHERE it loads from (`from`). Opaque form (env:API_KEY, a config key, vault:path) = a design note wairon never resolves; modeled form `component:<id>` references the Adapter/Store that provides the secret and is validated (must resolve, be an Adapter/Store, and be wired to the presenter). Absence on a call into a Portal whose auth \u2260 none warns (PORTAL_AUTH_UNMET). The authenticated call itself should be made by an Adapter (AUTH_PRESENTER_NOT_ADAPTER)."),
29579
+ detach: import_zod11.z.boolean().optional().describe("call/dispatch: fire-and-forget \u2014 issue the call and continue without awaiting the result (no later step consumes it)"),
29580
+ capability: import_zod11.z.string().optional().describe("dispatch: the capability routed through the target Portal's dispatch table (validated against it \u2014 UNSERVED_CAPABILITY)"),
29581
+ assertsGuarantees: import_zod11.z.array(import_zod11.z.string().min(1)).optional().describe("Semantic guarantees this step relies on \u2014 each must be declared in the called method's L3 guarantees (NARRATIVE_SEMANTIC_UNBACKED otherwise). Builtin tokens: idempotent | atomic | transactional | exactly-once; extension packs may declare more (any other token is UNKNOWN_GUARANTEE)"),
29582
+ assertsInvariants: import_zod11.z.array(import_zod11.z.string()).optional().describe(`Declared entity invariants this step upholds, as "<type-id>.<invariant-id>" refs (UNKNOWN_INVARIANT_REF when unresolved); write-effect methods of the entity's componentClass must carry one per declared invariant (UNASSERTED_INVARIANT)`),
29583
+ condition: import_zod11.z.string().optional().describe("branch / while / doWhile"),
29584
+ onTrueStep: stepNo().optional().describe("branch: default = next step"),
29585
+ onTrueLabel: labelRef().optional().describe("branch: symbolic alternative to onTrueStep"),
29586
+ onFalseStep: stepNo().optional().describe("branch: required (or onFalseLabel)"),
29587
+ onFalseLabel: labelRef().optional().describe("branch: symbolic alternative to onFalseStep"),
29588
+ on: import_zod11.z.string().optional().describe("switch: the dispatched value"),
29589
+ cases: import_zod11.z.array(import_zod11.z.object({ value: import_zod11.z.string(), step: stepNo().optional(), label: labelRef().optional() }).strict()).optional().describe("switch: required; each case targets its region by step number or label"),
29590
+ defaultStep: stepNo().optional().describe("switch: default = next step"),
29591
+ defaultLabel: labelRef().optional().describe("switch: symbolic alternative to defaultStep"),
29592
+ loopKind: import_zod11.z.enum(["forEach", "for", "while", "doWhile"]).optional().describe('loop: default forEach when "over" is set, else while'),
29593
+ over: import_zod11.z.string().optional().describe("loop forEach/for: iteration source"),
29594
+ endStep: stepNo().optional().describe("loop/try: last step of the body region (required, or endLabel)"),
29595
+ endLabel: labelRef().optional().describe("loop/try: symbolic alternative to endStep"),
29596
+ catches: import_zod11.z.array(import_zod11.z.object({ error: import_zod11.z.string(), step: stepNo().optional(), label: labelRef().optional() }).strict()).optional().describe("try: handler regions, each targeted by step number or label"),
29597
+ finallyStep: stepNo().optional().describe("try: first step of the always-runs region"),
29598
+ finallyLabel: labelRef().optional().describe("try: symbolic alternative to finallyStep"),
29599
+ branches: import_zod11.z.array(import_zod11.z.object({ step: stepNo().optional(), label: labelRef().optional(), name: import_zod11.z.string().optional() }).strict()).optional().describe("parallel: >= 2 arm entries (step number or label), ascending, first = the step after the header; arms are contiguous sub-regions of body next..endStep, joining after endStep once ALL complete"),
29600
+ toStep: stepNo().optional().describe("jump: required (break/continue/rejoin), or toLabel"),
29601
+ toLabel: labelRef().optional().describe("jump: symbolic alternative to toStep \u2014 resolves to the labeled step at write time"),
29602
+ outcome: import_zod11.z.string().optional().describe('return: e.g. "success", "not found"'),
29603
+ error: import_zod11.z.string().optional().describe("throw: the raised error")
29604
+ }).strict();
29605
+ const detailEnum = import_zod11.z.enum(["full", "calls-only", "intent"]);
29606
+ const conformanceEnum = import_zod11.z.enum(["declared", "anchored", "off"]);
29607
+ const implMethodShape = {
29608
+ name: import_zod11.z.string(),
29609
+ sourcePath: import_zod11.z.string().optional().describe("Optional: the source file realizing THIS method when it is not the implementation's sourcePath (e.g. a command whose body lives in its own file); the implementation's sourcePath is the default. Relative to project root \u2014 for a chained subproject's implementation (qualified id), relative to that subproject's root; a path given relative to this root that lands inside the subproject is re-expressed for you"),
29610
+ detail: detailEnum.optional().describe("Detail level for this method (overrides the spec default)"),
29611
+ intent: import_zod11.z.string().optional().describe("detail: intent \u2014 behavioral prose (what it does and how it fails); substitute for a narrative"),
29612
+ conformance: conformanceEnum.optional().describe("Conformance tier for this method (overrides the spec default)"),
29613
+ symbol: import_zod11.z.string().optional().describe("Code-level name realizing this contract method in the sourcePath file, when it legitimately differs from the intent-language contract name (e.g. put realized by saveSnapshot)"),
29614
+ exportedVia: import_zod11.z.string().optional().describe("The exported binding a consumer imports to REACH this method, when the realization is published as a value that composes it rather than exported in its own right (a rule module publishes callConformanceRule, and the method is the check inside it). Not symbol, which names the function itself: this names the route to it, so the surface check reads that export as promised. A handle the method's own source file does not actually export is reported (UNREALIZED_EXPORT_HANDLE) and allows nothing"),
29615
+ ext: import_zod11.z.record(import_zod11.z.unknown()).optional().describe("Opaque pack/tool extension data for this method (namespaced keys) \u2014 preserved verbatim"),
29616
+ narrative: import_zod11.z.array(narrativeStepInput).optional(),
29617
+ calls: import_zod11.z.array(import_zod11.z.string().min(1)).optional().describe('The calls this method makes, when its narrative does not show them (detail: intent, or calls-only with no steps authored) \u2014 each as "<component>.<method>", the spelling the debt register and lint allows name a unit with. The narrative walk takes exactly these edges and no others, so an undeclared collaborator is NOT reached: this is what keeps unused-detection honest at a lower detail dial. Each entry is validated like a call step (the component resolves, this component declares it, the method is on its contract). Refused beside a non-empty narrative \u2014 add the call step there instead')
29618
+ };
29619
+ const implMethodInputFields = Object.keys(implMethodShape);
29620
+ const implInput = {
29621
+ id: import_zod11.z.string().describe('Lowercase identifier, e.g. "vfs_storage"'),
29622
+ name: import_zod11.z.string().describe("Human-readable implementation name"),
29623
+ description: import_zod11.z.string().describe("Implementation details"),
29624
+ contract: import_zod11.z.string().describe("The L3 Interface contract ID this implements"),
29625
+ sourcePath: import_zod11.z.string().optional().describe("Optional: target source code file path relative to project root \u2014 for a chained subproject's implementation (qualified id), relative to that subproject's root; a path given relative to this root that lands inside the subproject is re-expressed for you"),
29626
+ simPath: import_zod11.z.string().optional().describe("Optional: the committed integration-sim harness file (project-relative; N:1 sharing allowed). The validator proves it exists and its import graph wires the REAL modules (this component + each direct dependency; technology adapters may stay faked) \u2014 running it is CI's job. Declaring the first simPath in a subsystem activates MISSING_INTEGRATION_SIM for its other complete non-leaf implementations"),
29627
+ technologies: import_zod11.z.array(import_zod11.z.string()).optional().describe(`External technologies this implementation binds to (e.g. ["mysql"]) \u2014 declares this component's ownership tree as the technology's home; references outside it are flagged (TECH_LEAKAGE) and contract identifiers must stay intent-language. Only for Adapter/Store/Registry/Index components.`),
29628
+ injectedParams: import_zod11.z.array(import_zod11.z.string()).optional().describe("The parameter names this realization takes BEFORE the ones its contract declares \u2014 a config object, a data root, the transport handles a portal is handed. Supplied by whatever wires the component up, never by the caller the contract describes, which is why they belong here and not to the contract: another realization may hold them as fields instead. Declared rather than guessed, because a leading parameter the contract does not name cannot be told from one it named under a different name (`seed(config)` realized as `bootstrapInstance(cfg)`); a leading parameter this list does not name is reported (UNDECLARED_PARAM). Only a LEADING run is dropped \u2014 one of these names appearing after the contract's own parameters is an argument in the middle of the caller's list, not wiring"),
29629
+ detail: detailEnum.optional().describe("Spec-level narrative detail default for all methods"),
29630
+ conformance: conformanceEnum.optional().describe("Spec-level conformance tier default: declared | anchored | off (omitted = stereotype default: Portal \u2192 anchored, else declared)"),
29631
+ methods: import_zod11.z.array(import_zod11.z.object(implMethodShape).strict()).optional().describe("Method implementations containing L5 narratives"),
29632
+ status: statusInput
29633
+ };
29634
+ const implInputFields = Object.keys(implInput);
29635
+ reg(
29636
+ server,
29637
+ "sdd_write_narrative",
29638
+ {
29639
+ description: "Write L4 Concrete Implementation spec containing L5 method narratives. Narratives are a FLAT ordered step list; flow steps (branch/switch/loop/try/parallel/jump/return/throw) jump by step number \u2014 blocks are just skipped regions. Steps may declare a `label` anchor, and every jump field has a *Label twin (toLabel, onTrueLabel, endLabel, \u2026) resolved to step numbers at write time \u2014 prefer labels over hand-counted numbers; an unresolvable label rejects the write. Detail dial per method: full (narrative required) | calls-only (call choreography suffices) | intent (prose instead of steps); omitted = stereotype default (Portal/Observer/Adapter: calls-only, Store/Index/Registry: intent, else full). A method whose narrative shows no steps declares the calls it makes in `calls` (one \"<component>.<method>\" each): the reachability walk takes exactly those edges and no others, so an intent-level method that reaches a collaborator must name it or that collaborator is reported unused. Conformance dial per method or spec: declared | anchored | off \u2014 how strictly structural conformance requires contract methods to be realized in their source file (omitted = Portal: anchored, else declared). A method whose body lives in its own file names it in the method's sourcePath; the implementation's sourcePath is the default for every method that names none. Parameters the realization takes BEFORE its contract's own \u2014 a config object, a data root, a portal's transport handles \u2014 are wiring, and are declared once for the spec in injectedParams; a leading parameter it does not name is reported against the contract (UNDECLARED_PARAM). Re-authoring an existing id REPLACES the method list: a method left out of the input is REMOVED together with its narrative (and reported); spec-level lint/ext are carried forward, and the stored status is kept unless this input states a higher one. The answer carries a write receipt as structured content beside the sentence \u2014 the status written, whether a spec already held the id, and the notices a restatement raised, each as its own entry.",
29640
+ inputSchema: implInput,
29641
+ outputSchema: specWriteReceiptOutput
29642
+ },
29643
+ ({ id, name, description, contract, sourcePath, simPath, technologies, injectedParams, detail, conformance, methods, status }) => {
29644
+ try {
29645
+ const restatement = {
29646
+ kind: "implementation",
29647
+ spec: {
29648
+ id,
29649
+ name,
29650
+ description,
29651
+ contract,
29652
+ sourcePath,
29653
+ simPath,
29654
+ technologies,
29655
+ injectedParams,
29656
+ detail,
29657
+ conformance,
29658
+ methods: (methods ?? []).map((m) => ({
29659
+ ...m,
29660
+ narrative: (m.narrative ?? []).map((s, i) => ({ ...s, stepNumber: s.stepNumber ?? i + 1 }))
29661
+ }))
29662
+ },
29663
+ fields: implInputFields,
29664
+ memberFields: implMethodInputFields,
29665
+ status
29666
+ };
29667
+ const receipt = writeSpec(restatement);
29668
+ return writeReceipt(
29669
+ `Successfully saved L4 Implementation Spec "${name}" (${id}) with method narratives.`,
29670
+ receipt
29671
+ );
29672
+ } catch (e) {
29673
+ return errMessage(e);
29674
+ }
29675
+ }
29676
+ );
29677
+ const typeInput = {
29678
+ kind: import_zod11.z.enum(["entity", "value-object"]).describe("entity (owned by a subsystem) or value-object (often system-level shared)"),
29679
+ id: import_zod11.z.string().describe("Lowercase identifier"),
29680
+ name: import_zod11.z.string().describe("Human-readable name"),
29681
+ description: import_zod11.z.string().optional(),
29682
+ subsystem: import_zod11.z.string().optional().describe("Owning subsystem id; omit for a system-level shared value object"),
29683
+ group: import_zod11.z.string().optional().describe("Optional logical group ID to organize this type in subfolders"),
29684
+ fields: import_zod11.z.array(import_zod11.z.object({
29685
+ name: import_zod11.z.string(),
29686
+ type: import_zod11.z.string().describe(TYPE_REF_GRAMMAR("The field's type")),
29687
+ description: import_zod11.z.string().optional(),
29688
+ optional: import_zod11.z.boolean().optional(),
29689
+ key: import_zod11.z.enum(["primary", "unique", "foreign"]).optional().describe("Identity marker (PK/unique/FK) for ERD and database schema derivation"),
29690
+ references: import_zod11.z.string().optional().describe('For foreign keys, the referenced type/table id and optional field, e.g. "invoice.id"')
29691
+ }).strict()).optional().describe('Data fields (type is a primitive or a qualified type id, e.g. "billing.Invoice")'),
29692
+ methods: import_zod11.z.array(import_zod11.z.object({
29693
+ name: import_zod11.z.string(),
29694
+ signature: import_zod11.z.string(),
29695
+ returns: import_zod11.z.string().describe(TYPE_REF_GRAMMAR("The type the method answers with")),
29696
+ description: import_zod11.z.string().optional(),
29697
+ sourcePath: import_zod11.z.string().optional().describe("Source file realizing this method when the type's own sourcePath does not hold it \u2014 a pure type method often lives apart from the declaration"),
29698
+ symbol: import_zod11.z.string().optional().describe("Code-level name realizing this method when it differs from the method name, e.g. narrative_step.foreignFields realized by narrativeStepForeignFields")
29699
+ }).strict()).optional().describe("Pure intrinsic methods only"),
29700
+ componentClass: import_zod11.z.string().optional().describe("Optional component id that implements or owns this logical entity"),
29701
+ invariants: import_zod11.z.array(import_zod11.z.object({
29702
+ id: import_zod11.z.string().describe("Stable invariant id, unique within the entity"),
29703
+ description: import_zod11.z.string().describe("The property that must hold, stated precisely")
29704
+ }).strict()).optional().describe("Declared domain invariants (entities): every write-effect method of the componentClass must carry a narrative step asserting each (assertsInvariants) \u2014 declarations checked, enforcement never proven"),
29705
+ database: import_zod11.z.string().optional().describe("Optional database id for table-schema types"),
29706
+ table: import_zod11.z.string().optional().describe("Optional database table name for table-schema types"),
29707
+ linkedEntity: import_zod11.z.string().optional().describe("Optional logical entity id represented by this table-schema type"),
29708
+ sourcePath: import_zod11.z.string().optional().describe("Source file holding the declaration of this type (project-relative). Naming one turns the type into a claim on code: the file must resolve and the declaration must be anchored in it (UNREALIZED_TYPE)"),
29709
+ symbol: import_zod11.z.string().optional().describe('Code-level name realizing the declaration when it differs from name, e.g. a type named "Invoice Line" declared as InvoiceLine')
29710
+ };
29711
+ const typeInputFields = Object.keys(typeInput);
29712
+ reg(
29713
+ server,
29714
+ "sdd_add_type",
29715
+ {
29716
+ description: "Define an entity or value-object type (the data components operate on). Entities are owned by a subsystem; shared value objects omit subsystem (system-level). Fields are data; methods are PURE intrinsic behaviour only \u2014 anything needing a collaborator belongs on a component, taking the entity as an argument. A type may also CLAIM code: sourcePath names the file holding its declaration (and each method may name its own), symbol binds the code-level name when it differs \u2014 the file must then resolve and the declaration must be anchored in it. Re-defining an existing id REPLACES fields/methods/invariants and restates sourcePath/symbol (an omitted list or path is CLEARED, and a dropped member is reported); lint/ext are carried forward. The answer carries a write receipt as structured content beside the sentence \u2014 whether a spec already held the id, and the notices a restatement raised, each as its own entry. A type carries no lifecycle status, so the receipt states none.",
29717
+ inputSchema: typeInput,
29718
+ outputSchema: specWriteReceiptOutput
29719
+ },
29720
+ ({ kind, id, name, description, subsystem, group, fields, methods, componentClass, invariants, database, table, linkedEntity, sourcePath, symbol }) => {
29721
+ try {
29722
+ const spec = {
29723
+ kind,
29724
+ id,
29725
+ name,
29726
+ ...description ? { description } : {},
29727
+ ...subsystem ? { subsystem } : {},
29728
+ ...group ? { group } : {},
29729
+ fields: (fields ?? []).map((f) => ({
29730
+ name: f.name,
29731
+ type: f.type,
29732
+ description: f.description,
29733
+ optional: f.optional ?? false,
29734
+ ...f.key ? { key: f.key } : {},
29735
+ ...f.references ? { references: f.references } : {}
29736
+ })),
29737
+ methods: methods ?? [],
29738
+ ...componentClass ? { componentClass } : {},
29739
+ ...invariants?.length ? { invariants } : {},
29740
+ ...database ? { database } : {},
29741
+ ...table ? { table } : {},
29742
+ ...linkedEntity ? { linkedEntity } : {},
29743
+ ...sourcePath ? { sourcePath } : {},
29744
+ ...symbol ? { symbol } : {}
29745
+ };
29746
+ const receipt = writeSpec({ kind: "type", spec, fields: typeInputFields });
29747
+ return writeReceipt(
29748
+ `Successfully ${receipt.replacedExisting ? "re-authored" : "defined"} ${kind} type "${name}" (${id}).`,
29749
+ receipt
29750
+ );
29751
+ } catch (e) {
29752
+ return errMessage(e);
29753
+ }
29754
+ }
29755
+ );
29756
+ reg(
29757
+ server,
29758
+ "sdd_validate_tree",
29759
+ {
29760
+ description: "Validate the SDD spec tree, checking parent references, contract compatibility, narratives, and component type boundaries. Supports scoping and recursion controls. Findings come back as structured content too, under the schema this tool declares \u2014 errors and warnings already split, each with its code, severity, message and the spec it concerns \u2014 so a caller filters them as objects instead of parsing the JSON text block and hoping its shape holds.",
29761
+ inputSchema: {
29762
+ subsystem: import_zod11.z.string().optional().describe("Only validate the specified subsystem (granular)"),
29763
+ recursive: import_zod11.z.boolean().optional().describe("Whether to recursively validate subprojects (default: true)")
29764
+ },
29765
+ outputSchema: validateTreeOutput
29766
+ },
29767
+ ({ subsystem, recursive }) => {
29768
+ try {
29769
+ const config = loadProjectConfig();
29770
+ if (!config) throw new ProjectNotInitializedError();
29771
+ const result = validateSddTree({
29772
+ rules: config.rules,
29773
+ projectType: config.projectType,
29774
+ scopeSubsystem: subsystem,
29775
+ recursive: recursive ?? true
29776
+ });
29777
+ return jsonStructured({
29778
+ valid: result.valid,
29779
+ errors: result.issues.filter((i) => i.severity === "error"),
29780
+ warnings: result.issues.filter((i) => i.severity === "warning"),
29781
+ ...result.resolvedThrough ? { resolvedThrough: result.resolvedThrough } : {}
29782
+ });
29783
+ } catch (e) {
29784
+ return errText(String(e));
29785
+ }
29786
+ }
29787
+ );
29788
+ reg(
29789
+ server,
29790
+ "sdd_get_spec",
29791
+ {
29792
+ description: `Get/read the parsed JSON contents of a specific spec from the spec tree. Returns structural contents without file system path searching. Pass "methods" to read only the named methods of a contract, an implementation or a type \u2014 a 45-method spec fetched whole to look at one of them is the read side of the same waste a restatement is on the write side; the answer then carries a "partialResult" marker naming what was left out, and must never be re-authored from. For a variant-tagged COMPONENT the result also carries a derived, read-only "variantGuidance" (the variant's base, its implementation guidance, and the same-variant sibling components to implement alike) \u2014 it is resolved from the variant registry, not part of the spec, so never write it back. The structured content carries the same answer with the two derived markers KEPT SEPARATE from the stored spec ({kind, id, spec, partialResult?, variantGuidance?}), so nothing derived can be mistaken for something stored; the text block folds them in as it always has.`,
29793
+ inputSchema: {
29794
+ kind: import_zod11.z.enum(["system", "subsystem", "component", "interface", "implementation", "type"]).describe("The kind of specification"),
29795
+ id: import_zod11.z.string().describe('The identifier of the spec to fetch (the L0 system spec is a singleton \u2014 pass the system name or "system")'),
29796
+ methods: import_zod11.z.array(import_zod11.z.string().min(1)).optional().describe("Return only these methods by name; every other field of the spec comes back unchanged. Only an interface, an implementation or a type declares methods \u2014 asking for one elsewhere is refused, as is a name the spec does not declare (the answer names the ones it does). Omit it for the whole spec.")
29797
+ },
29798
+ outputSchema: getSpecOutput
29799
+ },
29800
+ ({ kind, id, methods }) => {
29801
+ try {
29802
+ let result = null;
29803
+ let partial = null;
29804
+ switch (kind) {
29805
+ case "system":
29806
+ result = loadSystemSpec();
29807
+ break;
29808
+ case "subsystem":
29809
+ result = loadSubsystemSpec(id);
29810
+ break;
29811
+ case "component":
29812
+ result = loadComponentSpec(id);
29813
+ break;
29814
+ case "interface":
29815
+ result = loadInterfaceSpec(id);
29816
+ break;
29817
+ case "implementation":
29818
+ result = loadImplementationSpec(id);
29819
+ break;
29820
+ case "type":
29821
+ result = loadTypeSpec(id);
29822
+ break;
29823
+ }
29824
+ if (!result) return errText(`Spec of kind "${kind}" with ID "${id}" does not exist.`);
29825
+ if (methods !== void 0) {
29826
+ const declared = result.methods;
29827
+ if (!Array.isArray(declared)) {
29828
+ return errText(
29829
+ `A ${kind} spec declares no methods, so "methods" cannot filter it. Drop the argument to read "${id}" whole; methods live on an interface, an implementation and a type.`
29830
+ );
29831
+ }
29832
+ const names = declared.map((m) => String(m?.name));
29833
+ const unknown = methods.filter((n) => !names.includes(n));
29834
+ if (unknown.length > 0) {
29835
+ return errText(
29836
+ `${kind} "${id}" does not declare ${unknown.map((n) => `"${n}"`).join(", ")}. It declares: ${names.join(", ")}. Nothing was returned, so a misspelled name never reads as a method with no content.`
29837
+ );
29838
+ }
29839
+ const kept = declared.filter((m) => methods.includes(String(m?.name)));
29840
+ partial = {
29841
+ shown: kept.map((m) => String(m?.name)),
29842
+ omitted: names.length - kept.length,
29843
+ warning: `PARTIAL: ${names.length - kept.length} of this spec's ${names.length} methods are not in this answer. Never re-author from it \u2014 sdd_define_interface and sdd_write_narrative REPLACE the method list, so every method missing here would be removed from the spec.`
29844
+ };
29845
+ result = { ...result, methods: kept };
29846
+ }
29847
+ const guidance = kind === "component" ? resolveComponentVariantGuidance(result) : null;
29848
+ const folded = {
29849
+ ...result,
29850
+ ...partial ? { partialResult: partial } : {},
29851
+ ...guidance ? { variantGuidance: guidance } : {}
29852
+ };
29853
+ return structured(JSON.stringify(folded, null, 2), {
29854
+ kind,
29855
+ id,
29856
+ spec: result,
29857
+ ...partial ? { partialResult: partial } : {},
29858
+ ...guidance ? { variantGuidance: guidance } : {}
29859
+ });
29860
+ } catch (e) {
29861
+ return errText(String(e));
29862
+ }
29863
+ }
29864
+ );
29865
+ reg(
29866
+ server,
29867
+ "sdd_delete_spec",
29868
+ {
29869
+ description: "Delete a specification file from the spec tree and clean up any empty parent directories. Answers with a structured deletion beside the sentence: whether a spec was removed, and the tests that encode a method the deletion took away (when the project declares test roots).",
29870
+ inputSchema: {
29871
+ kind: import_zod11.z.enum(["subsystem", "component", "interface", "implementation", "type"]).describe("The kind of spec to delete"),
29872
+ id: import_zod11.z.string().describe("The ID of the spec to delete")
29873
+ },
29874
+ outputSchema: specDeletionOutput
29875
+ },
29876
+ ({ kind, id }) => {
29877
+ try {
29878
+ const deletion = deleteSpec2(kind, id);
29879
+ if (!deletion.deleted) return errText(`Spec of kind "${kind}" with ID "${id}" could not be deleted (file may not exist).`);
29880
+ const testsBlock = renderTestsToRevisit(deletion.testsToRevisit).join("\n");
29881
+ return structured(`Successfully deleted ${kind} spec "${id}".${testsBlock ? `
29882
+ ${testsBlock}` : ""}`, deletion);
29883
+ } catch (e) {
29884
+ return errText(String(e));
29885
+ }
29886
+ }
29887
+ );
29888
+ reg(
29889
+ server,
29890
+ "sdd_update_spec",
29891
+ {
29892
+ description: "Update/patch an existing SDD specification (subsystem, component, interface, implementation, or type) using a granular delta. Updates fields, appends/merges array elements, or inserts/deletes narrative steps. Answers with exactly what changed, and with every path the delta named that the write did not act on. Pass dryRun to be told what it would do without writing it.",
29893
+ inputSchema: {
29894
+ kind: import_zod11.z.enum(["system", "subsystem", "component", "interface", "implementation", "type"]).describe("The spec kind to update (system = the singleton L0 \u2014 vision, boundaries, globalRequirements, databases, and publicInterfaces: the project gateway surface, each entry {id, name, subsystem, component, type, details, audience: project|department|instance|partner|external}; id is informational)"),
29895
+ id: import_zod11.z.string().describe("The ID of the spec to update (namespaced if needed)"),
29896
+ delta: import_zod11.z.record(import_zod11.z.any()).describe(`The partial fields to merge into the spec. ARRAYS UPSERT, they do not replace: an array whose elements carry an identity is merged element-by-element, so a delta naming ONE element leaves the others intact. Identity is "name" or "id" by default, and per field: dispatch by "capability", a listener's mounts by "portal", lifecycle by phase+component+method, emits/subscribesTo by topic+event, trustedLinks by "subsystem", a subsystem's publicInterfaces by component+interface (or type+details for an entry not yet bound to a component), invariants and patterns by "id", lint.allow by "code" AND "at" (an allow covers one occurrence, so several may share a code on one spec), an interface method's findings by "code", boundaries by "name", globalRequirements by "description", switch cases by "value", try catches by "error". Identity merging applies at EVERY depth, including an array INSIDE an element (a method's params, a step's catches). Add "action: 'delete'" (or "remove: true") alongside that identity to REMOVE an element \u2014 including a stale lint allow. Arrays of plain STRINGS (owns, dependsOn, guarantees) carry no per-element identity and are replaced wholesale; pass [] to clear any array outright. To REMOVE an optional field entirely, list it in "unset": e.g. {"unset": ["basePath", "variant"]} \u2014 passing null/undefined means "no change" (they are skipped), and writing "" would leave the field present but empty, which is a different and usually wrong spec. Unsetting a required field is refused by schema validation, which names it. For narrative steps, match by "stepNumber" and use "action: 'insert'" (shifts subsequent steps up) or "action: 'delete'" (shifts subsequent steps down and removes it). Step entries apply in ASCENDING stepNumber order, each against the numbering the earlier entries of the SAME delta left behind \u2014 delete step 3 and step 7 becomes step 6 \u2014 so prefer labels, and restate the step's "label" or "description" on a delete to have it checked against the step actually addressed. Renumbering RELOCATES every flow jump field (onTrueStep/onFalseStep/cases.step/defaultStep/endStep/catches.step/finallyStep/toStep) in the same narrative. A delete is REJECTED when the narrative has no such step, when a jump still targets it (retarget the referrers first), when a restated label/description does not match, or when it is a loop/try/parallel header whose body would be left standing (retype the header first to dissolve the region, then delete it). Changing a step's "type" REBUILDS it for the new type: its description and label are kept and every field the new type cannot carry is dropped (returned as a NOTICE); a delta that retypes AND sets such a field is refused. A step delta is also refused when it carries a marker the merge does not recognise: a non-boolean "remove", an "action" that is neither "insert" nor "delete", a "captureJumps" outside an insert, or no "stepNumber" to address. Inserting AT a jump target relocates those jumps past the inserted step by default (a NOTICE is returned) \u2014 add "captureJumps": true on the inserted step to retarget entry jumps onto it (loop/try endStep region tails always relocate with the body and are never captured). Every jump field has a "*Label" twin (toLabel, onFalseLabel, endLabel, \u2026, and "label" on a cases/catches entry) resolved against step labels AFTER the merge, so a delta may anchor on a label only pre-existing steps carry; a label the delta supplies REPLACES the stored number it twins, while setting the number and its label together in one delta is refused as a contradiction. Reference ids in deltas may use LOCAL names \u2014 they are qualified against the spec's namespace exactly as the loader would. Per-spec lint suppression: set "lint: { allow: [{ code, at, covers, reason }] }" to silence a WARNING code on this spec only (errors always surface; stale allows are flagged). An allow covers EXACTLY the occurrence it names: "at" is the site the finding names (a contract method, an import edge "from -> to", a declared edge "component -> target") and is REQUIRED for a code whose findings report one, while a finding that reports no site is covered only by an allow that names none; "covers" lists the units an aggregating finding reports, and the allow silences it only when every one is listed. This delta is deliberately OPEN below its top level \u2014 the shapes nest further than a schema here should restate \u2014 so a key that is not a field at its depth is not refused, it is NAMED BACK under NO EFFECT in the answer, together with any value the spec already held and any "unset" that removed nothing. Read that list: it is where a nested typo shows up.`),
29897
+ dryRun: import_zod11.z.boolean().optional().describe("Ask what this delta WOULD do instead of doing it. The whole write runs, the candidate gate included, and the answer is the change report it would have produced \u2014 marked DRY RUN, with nothing stamped and not one byte of the stored file moved. Use it before a delta that renumbers a long narrative.")
29898
+ },
29899
+ outputSchema: specChangeReportOutput
29900
+ },
29901
+ ({ kind, id, delta, dryRun }) => {
29902
+ try {
29903
+ const report = updateSpecGated(kind, id, delta, dryRun);
29904
+ return structured(renderChangeReport(report), report);
29905
+ } catch (e) {
29906
+ return errText(String(e));
29907
+ }
29908
+ }
29909
+ );
29910
+ reg(
29911
+ server,
29912
+ "sdd_get_status",
29913
+ {
29914
+ description: "Get the completeness status dashboard of the SDD spec tree. Supports scoping and recursion controls.",
29915
+ inputSchema: {
29916
+ subsystem: import_zod11.z.string().optional().describe("Only show status for the specified subsystem"),
29917
+ recursive: import_zod11.z.boolean().optional().describe("Whether to recursively load subprojects (default: true)")
29918
+ }
29919
+ },
29920
+ ({ subsystem, recursive }) => {
29921
+ try {
29922
+ const report = getStatusReport({
29923
+ subsystem,
29924
+ recursive: recursive ?? true
29925
+ });
29926
+ const family = statusFamilyContext();
29927
+ const verdict = approvalVerdict();
29928
+ return text(`${family}${report.text}${verdict.text}`);
29929
+ } catch (e) {
29930
+ return errText(String(e));
29931
+ }
29932
+ }
29933
+ );
29934
+ reg(
29935
+ server,
29936
+ "sdd_list_external_interfaces",
29937
+ {
29938
+ description: "List the bound project's consumable external surfaces (parent family, siblings, foreign imports) as discovery entries with origin, provenance, and freshness \u2014 the tool an agent inside a subproject uses to SEE its outward world instead of discovering it by failed reference resolution. Full contracts stay in the vendored snapshots (.wai/surfaces/); each entry summarizes the interface ids it exposes."
29939
+ },
29940
+ () => {
29941
+ try {
29942
+ return json(listExternalInterfaces());
29943
+ } catch (e) {
29944
+ return errText(String(e));
29945
+ }
29946
+ }
29947
+ );
29948
+ reg(
29949
+ server,
29950
+ "sdd_get_agent_brief",
29951
+ {
29952
+ description: "Compose the LIVE delegation brief for one resolved agent: the fully rendered instruction body plus the ownedPaths/readPaths scope fence \u2014 the dynamic replacement for generated per-component agent files. Fetch a brief and spawn a generic subagent with it: always fresh after a re-lock, no session restart needed. List agent ids via listAgents or resources/list (the wairon-agent:// entries).",
29953
+ inputSchema: {
29954
+ agentId: import_zod11.z.string().describe("The resolved agent id (list via listAgents or resources/list)")
29955
+ }
29956
+ },
29957
+ ({ agentId }) => {
29958
+ try {
29959
+ return json(composeAgentBrief(agentId));
29960
+ } catch (e) {
29961
+ return errText(String(e));
29962
+ }
29963
+ }
29964
+ );
29965
+ registerSkillResources(server, !options.hostedTools);
29966
+ registerSkillPrompts(server, !options.hostedTools);
29967
+ try {
29968
+ const chainingParent = resolveChainingParent();
29969
+ if (chainingParent) {
29970
+ let externalSurfaceCount = 0;
29971
+ try {
29972
+ externalSurfaceCount = listExternalInterfaces().length;
29973
+ } catch {
29974
+ }
29975
+ process.stderr.write(
29976
+ `[wairon mcp] chained subproject: this root is mounted as subsystem "${chainingParent.subsystemId}" of the parent project at ${chainingParent.parentRoot} \u2014 ${externalSurfaceCount} vendored external surface(s) discoverable via sdd_list_external_interfaces
29977
+ `
29978
+ );
29979
+ }
29980
+ } catch {
29981
+ }
29982
+ advertiseHostedTools(server, options);
29983
+ return server;
29984
+ }
29985
+ function advertiseHostedTools(server, options) {
29986
+ if (options.hostedTools) {
29987
+ const hostedStub = () => errText("This hosted tool is dispatched by the hosting data plane before reaching the MCP server; it is unavailable outside a hosted request.");
29988
+ reg(server, "sdd_host_lock_project", {
29989
+ description: "Hosted project lifecycle (execute-primary): LOCK the bound project (validate-as-complete gate + commit-scoped lock record). Executes directly when your resolved permission is yes and returns the completed outcome; when it is approval, a pending approval request is created instead (await it with sdd_host_await_approval).",
29990
+ inputSchema: {}
29991
+ }, hostedStub);
29992
+ reg(server, "sdd_host_initialize_project", {
29993
+ description: "Hosted project lifecycle (execute-primary): initialize a new hosted project into its REQUIRED owner organization unit (with an optional profile selection). Executes directly when your resolved permission is yes; when it is approval, a pending approval request is created instead.",
29994
+ inputSchema: {
29995
+ id: import_zod11.z.string().describe("Requested project id"),
29996
+ displayName: import_zod11.z.string().optional(),
29997
+ description: import_zod11.z.string().optional(),
29998
+ ownerUnitId: import_zod11.z.string().describe("REQUIRED organization unit that owns the new project (every project is placed at creation)"),
29999
+ environment: import_zod11.z.string().optional()
30000
+ }
30001
+ }, hostedStub);
30002
+ reg(server, "sdd_host_get_approval_status", {
30003
+ description: "Hosted project lifecycle: read the status of one of your approval requests.",
30004
+ inputSchema: { requestId: import_zod11.z.string() }
30005
+ }, hostedStub);
30006
+ reg(server, "sdd_host_await_approval", {
30007
+ description: "Hosted project lifecycle: long-poll one of YOUR approval requests until it is decided (approved requests auto-execute, so an approval resolves as completed) or the timeout elapses. Returns the request in its current state.",
30008
+ inputSchema: {
30009
+ requestId: import_zod11.z.string(),
30010
+ timeoutSeconds: import_zod11.z.number().optional().describe("How long to wait server-side (clamped; 0 returns the current state immediately)")
30011
+ }
30012
+ }, hostedStub);
30013
+ reg(server, "sdd_landscape_list_reachable_projects", {
30014
+ description: "Hosted landscape: the projects reachable from the BOUND project through ACTIVE cross-project relations (directional, relations-only), each with the relation ids/kinds and target public interface ids.",
30015
+ inputSchema: {}
30016
+ }, hostedStub);
30017
+ reg(server, "sdd_landscape_list_reachable_project_interfaces", {
30018
+ description: "Hosted landscape: redacted, audience-filtered public interface summaries of one reachable target project (Forbidden outside the reachable set; private by default).",
30019
+ inputSchema: { projectId: import_zod11.z.string().describe("The reachable target project id") }
30020
+ }, hostedStub);
30021
+ reg(server, "sdd_landscape_list_visible_surfaces", {
30022
+ description: "Hosted landscape: the visibility-resolved discovery catalog for the BOUND project \u2014 every target the organization unit graph exposes to it (open-within-tenant, closed groups hidden, exposeTo grants honored), with audience distance and audience-filtered summaries. No relation required.",
30023
+ inputSchema: {}
30024
+ }, hostedStub);
30025
+ reg(server, "sdd_landscape_get_project_surface", {
30026
+ description: `Hosted landscape: fetch a visible target project's CONTRACT-GRADE surface snapshot (full method contracts, dispatch tables, type closure) at your audience-distance ceiling, origin "exchanged" \u2014 save it under .wai/surfaces/ (wairon surface import) so your adapters validate against the declared contract.`,
30027
+ inputSchema: { projectId: import_zod11.z.string().describe("The visible target project id") }
30028
+ }, hostedStub);
30029
+ reg(server, "sdd_host_pack_list", {
30030
+ description: "Hosted project ops: list the BOUND project's registered extension packs (requires project:read over it).",
30031
+ inputSchema: {}
30032
+ }, hostedStub);
30033
+ reg(server, "sdd_host_pack_install", {
30034
+ description: "Hosted project ops: install a DECLARATIVE extension pack (profiles + language/platform tables \u2014 never rule/code packs) into the BOUND project (requires project:admin over it).",
30035
+ inputSchema: {
30036
+ name: import_zod11.z.string().describe("Pack name (letters, digits, dot, underscore, hyphen)"),
30037
+ content: import_zod11.z.string().describe("The declarative pack YAML content")
30038
+ }
30039
+ }, hostedStub);
30040
+ reg(server, "sdd_host_policy_evaluate", {
30041
+ description: "Hosted project ops: evaluate the BOUND project's packs and profile selection against the instance pack policy \u2014 compliance without side effects (requires project:write over it).",
30042
+ inputSchema: {}
30043
+ }, hostedStub);
30044
+ reg(server, "sdd_host_policy_reconcile", {
30045
+ description: "Hosted project ops: reconcile the BOUND project against the instance pack policy \u2014 under auto_reconcile enforcement the missing required/default declarative packs are applied (requires project:write over it).",
30046
+ inputSchema: {}
30047
+ }, hostedStub);
30048
+ reg(server, "sdd_host_produce", {
30049
+ description: "Hosted project ops: run a configured producer projection (Notion/Miro) of the BOUND project to the named target (requires project:admin over it).",
30050
+ inputSchema: { target: import_zod11.z.string().describe("The configured producer target (e.g. notion, miro)") }
30051
+ }, hostedStub);
30052
+ reg(server, "sdd_host_commit_project", {
30053
+ description: "Hosted project ops: publish a DELIBERATE, .wai/-scoped commit+push of the BOUND project's specs to its bound repository (commit = local save, push = the actual backup; a clean scope publishes nothing). The data plane binds ONE project, so there is no project argument. Requires project:write over it.",
30054
+ inputSchema: {
30055
+ subsystem: import_zod11.z.string().optional().describe("Narrow staging to .wai/specs/<subsystem>/ (a convenience \u2014 git history stays per-repo)"),
30056
+ message: import_zod11.z.string().optional().describe("Commit message; defaults to a timestamped wairon message")
30057
+ }
30058
+ }, hostedStub);
30059
+ reg(server, "sdd_host_export_tree", {
30060
+ description: "Hosted spec-tree transfer: pack the BOUND project's WHOLE spec tree \u2014 its own .wai plus every chained subproject \u2014 into a .waitree archive, returned as base64 with its roots, file count and state id. The migration counterpart of an import: use it to take a hosted project local, or to move it to another instance. Requires project:read over the project. Bounded by the data-plane body cap; a very large tree exports through the web download route instead. Refuses when a chained mount cannot be packed (escaping, missing, cyclic, too deep, or holding no .wai), naming every one and why, unless allowPartial is set \u2014 then the archive is built anyway and the result lists what was skipped.",
30061
+ inputSchema: {
30062
+ allowPartial: import_zod11.z.boolean().optional().describe("Build the archive even when some chained mounts cannot be packed, listing them as skipped; default false (refuse and name them)")
30063
+ }
30064
+ }, hostedStub);
30065
+ reg(server, "sdd_host_import_tree", {
30066
+ description: "Hosted spec-tree transfer: REPLACE the BOUND project's spec tree from a base64 .waitree archive. Requires project:admin over the project (strictly above project:write \u2014 this replaces the whole design, not one spec). Refuses an occupied destination unless replaceExisting is set, always refuses executable entries (rule/code packs install only through the trusted filesystem), and moves the previous tree aside to a backup whose path is returned.",
30067
+ inputSchema: {
30068
+ archiveBase64: import_zod11.z.string().describe("The .waitree archive bytes, base64-encoded"),
30069
+ replaceExisting: import_zod11.z.boolean().optional().describe("Replace a tree already present (backed up first); without it an occupied destination is refused")
30070
+ }
30071
+ }, hostedStub);
30072
+ }
30073
+ }
30074
+
30075
+ // src/index.ts
30076
+ function loadProjectConfig3() {
27192
30077
  const config = loadProjectConfig();
27193
30078
  if (!config) throw new ProjectNotInitializedError();
27194
30079
  return config;
@@ -27497,6 +30382,7 @@ function loadProjectConfig2() {
27497
30382
  saveSpec,
27498
30383
  saveTopologyConfig,
27499
30384
  scanAllSpecs,
30385
+ schemaFingerprint,
27500
30386
  serializeYaml,
27501
30387
  setExecutionTier,
27502
30388
  setLogLevel,