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

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
@@ -3919,6 +3919,40 @@ var BundleNotFoundError = class extends WaironError {
3919
3919
  }
3920
3920
  };
3921
3921
 
3922
+ // src/models/identity-rename.ts
3923
+ var EDGE = " -> ";
3924
+ var QUALIFIED_UNIT = /^(\d+:)?([^.\s]+)\.([^.\s]+)$/;
3925
+ function movedComponent(rename, id) {
3926
+ return rename.components?.find((c) => c.from === id)?.to ?? id;
3927
+ }
3928
+ function rekeyUnit(rename, unit) {
3929
+ if (unit.includes(EDGE)) return unit.split(EDGE).map((side) => movedComponent(rename, side)).join(EDGE);
3930
+ const qualified = QUALIFIED_UNIT.exec(unit);
3931
+ if (!qualified) return unit;
3932
+ const [, step = "", component, method] = qualified;
3933
+ const moved = rename.methods?.find((m) => m.component === component && m.method === method);
3934
+ if (moved) return `${step}${moved.toComponent}.${moved.toMethod}`;
3935
+ return `${step}${movedComponent(rename, component)}.${method}`;
3936
+ }
3937
+ function rekeyAnchor(rename, anchor) {
3938
+ const { spec, at, covers } = anchor;
3939
+ const movedSpec = rename.specs?.find((s) => s.from === spec && (!s.sites || at !== void 0 && s.sites.includes(at)));
3940
+ let site = at;
3941
+ if (at !== void 0) {
3942
+ if (at.includes(EDGE)) {
3943
+ site = rekeyUnit(rename, at);
3944
+ } else {
3945
+ const moved = rename.methods?.find((m) => m.method === at && m.specs?.includes(spec));
3946
+ if (moved) site = moved.toMethod;
3947
+ }
3948
+ }
3949
+ return {
3950
+ spec: movedSpec?.to ?? spec,
3951
+ ...site !== void 0 ? { at: site } : {},
3952
+ ...covers !== void 0 ? { covers: covers.map((unit) => rekeyUnit(rename, unit)) } : {}
3953
+ };
3954
+ }
3955
+
3922
3956
  // src/models/project.ts
3923
3957
  var path2 = __toESM(require("path"));
3924
3958
  var import_zod3 = require("zod");
@@ -4554,6 +4588,12 @@ function projectConfigFsAdapterAt(rootDir) {
4554
4588
  },
4555
4589
  documentExists() {
4556
4590
  return fs2.existsSync(file());
4591
+ },
4592
+ readText() {
4593
+ return readFileOrNull(file());
4594
+ },
4595
+ writeText(text2) {
4596
+ writeFile(file(), text2);
4557
4597
  }
4558
4598
  };
4559
4599
  }
@@ -4579,9 +4619,159 @@ function storeOver(adapter, root) {
4579
4619
  },
4580
4620
  readField(field) {
4581
4621
  return valueAt(currentDocument(), field);
4622
+ },
4623
+ rewriteScalars(edits, dryRun = false) {
4624
+ if (edits.length === 0) return;
4625
+ const text2 = adapter.readText();
4626
+ if (text2 === null) throw new ProjectNotInitializedError();
4627
+ const next = rewriteScalarsInText(text2, edits, root);
4628
+ if (!dryRun) adapter.writeText(next);
4582
4629
  }
4583
4630
  };
4584
4631
  }
4632
+ var KEY_RE = /^('(?:[^']|'')*'|"(?:[^"\\]|\\.)*"|[^\s'"#{}[\],&*!|>%@`][^:#]*?)\s*:(?=\s|$)/;
4633
+ function tokenize(lines) {
4634
+ const tokens = [];
4635
+ let blockAbove = null;
4636
+ lines.forEach((raw, line) => {
4637
+ const content = raw.replace(/\r?\n$/, "");
4638
+ const indent = content.length - content.trimStart().length;
4639
+ const trimmed = content.trim();
4640
+ if (blockAbove !== null) {
4641
+ if (trimmed === "" || indent > blockAbove) return;
4642
+ blockAbove = null;
4643
+ }
4644
+ if (trimmed === "" || trimmed.startsWith("#")) return;
4645
+ let col = indent;
4646
+ let rest = content.slice(indent);
4647
+ while (rest === "-" || rest.startsWith("- ")) {
4648
+ tokens.push({ kind: "item", col, line });
4649
+ const after = rest.slice(1);
4650
+ const pad = after.length - after.trimStart().length;
4651
+ col += 1 + pad;
4652
+ rest = after.trimStart();
4653
+ }
4654
+ if (rest === "") return;
4655
+ const key = KEY_RE.exec(rest);
4656
+ if (key) {
4657
+ const name = key[1].startsWith("'") ? key[1].slice(1, -1).replace(/''/g, "'") : key[1].startsWith('"') ? JSON.parse(key[1]) : key[1].trim();
4658
+ const afterKey = rest.slice(key[0].length);
4659
+ const value = afterKey.trimStart();
4660
+ const token = { kind: "key", col, line, name };
4661
+ if (value !== "" && !value.startsWith("#")) token.valueStart = col + key[0].length + (afterKey.length - value.length);
4662
+ tokens.push(token);
4663
+ if (/^[|>][-+0-9]*\s*(#.*)?$/.test(value)) blockAbove = col;
4664
+ return;
4665
+ }
4666
+ tokens.push({ kind: "scalar", col, line, valueStart: col });
4667
+ if (/^[|>][-+0-9]*\s*(#.*)?$/.test(rest)) blockAbove = col - 2;
4668
+ });
4669
+ return tokens;
4670
+ }
4671
+ function childrenOf(tokens, index) {
4672
+ const parent = tokens[index];
4673
+ const out = [];
4674
+ for (let i = index + 1; i < tokens.length; i += 1) {
4675
+ const t = tokens[i];
4676
+ const nested = t.col > parent.col || parent.kind === "key" && t.kind === "item" && t.col === parent.col;
4677
+ if (!nested) break;
4678
+ out.push(t);
4679
+ }
4680
+ return out;
4681
+ }
4682
+ function locateScalar(tokens, path31) {
4683
+ let region = tokens;
4684
+ for (let k = 0; k < path31.length; k += 1) {
4685
+ const segment = path31[k];
4686
+ if (region.length === 0) return `nothing is nested at ${path31.slice(0, k).join(".")}`;
4687
+ const base = Math.min(...region.map((t) => t.col));
4688
+ const last = k === path31.length - 1;
4689
+ let at;
4690
+ if (typeof segment === "string") {
4691
+ at = region.findIndex((t) => t.kind === "key" && t.col === base && t.name === segment);
4692
+ if (at < 0) return `no block key "${segment}" at ${path31.slice(0, k).join(".") || "the document root"}`;
4693
+ if (last) return region[at].valueStart !== void 0 ? region[at] : `"${segment}" holds no inline scalar`;
4694
+ } else {
4695
+ const items = region.map((t, i) => ({ t, i })).filter(({ t }) => t.kind === "item" && t.col === base);
4696
+ if (segment >= items.length) return `no block sequence item ${segment} at ${path31.slice(0, k).join(".")}`;
4697
+ at = items[segment].i;
4698
+ if (last) {
4699
+ const value = region[at + 1];
4700
+ return value && value.kind === "scalar" && value.line === region[at].line ? value : `item ${segment} holds no inline scalar`;
4701
+ }
4702
+ }
4703
+ const regionStart = tokens.indexOf(region[at]);
4704
+ region = childrenOf(tokens, regionStart);
4705
+ }
4706
+ return "an empty path addresses no scalar";
4707
+ }
4708
+ function readScalar(text2) {
4709
+ if (text2.startsWith("'")) {
4710
+ let i = 1;
4711
+ let value = "";
4712
+ while (i < text2.length) {
4713
+ if (text2[i] === "'") {
4714
+ if (text2[i + 1] === "'") {
4715
+ value += "'";
4716
+ i += 2;
4717
+ continue;
4718
+ }
4719
+ return { value, style: "single", end: i + 1 };
4720
+ }
4721
+ value += text2[i];
4722
+ i += 1;
4723
+ }
4724
+ return null;
4725
+ }
4726
+ if (text2.startsWith('"')) {
4727
+ const closing = /^"(?:[^"\\]|\\.)*"/.exec(text2);
4728
+ if (!closing) return null;
4729
+ try {
4730
+ return { value: JSON.parse(closing[0]), style: "double", end: closing[0].length };
4731
+ } catch {
4732
+ return null;
4733
+ }
4734
+ }
4735
+ const comment = text2.search(/\s#/);
4736
+ const body = (comment < 0 ? text2 : text2.slice(0, comment)).trimEnd();
4737
+ return { value: body, style: "plain", end: body.length };
4738
+ }
4739
+ function writeScalar(value, style) {
4740
+ if (style === "double") return JSON.stringify(value);
4741
+ if (style === "plain" && /^[A-Za-z0-9_][A-Za-z0-9_./-]*$/.test(value)) return value;
4742
+ return `'${value.replace(/'/g, "''")}'`;
4743
+ }
4744
+ function setAt(document, path31, value) {
4745
+ let node = document;
4746
+ for (const segment of path31.slice(0, -1)) node = node[segment];
4747
+ node[path31[path31.length - 1]] = value;
4748
+ }
4749
+ function rewriteScalarsInText(text2, edits, root) {
4750
+ const refuse = (why) => {
4751
+ throw new WaironError(`Cannot rewrite .wai/project.yaml at ${root} without disturbing its formatting: ${why}. Nothing was written.`);
4752
+ };
4753
+ const lines = text2.split(/(?<=\n)/);
4754
+ const tokens = tokenize(lines);
4755
+ const expected = parseYaml(text2);
4756
+ for (const edit of edits) {
4757
+ const where = edit.path.join(".");
4758
+ const token = locateScalar(tokens, edit.path);
4759
+ if (typeof token === "string") refuse(`${where}: ${token}`);
4760
+ const { line, valueStart } = token;
4761
+ const content = lines[line].replace(/\r?\n$/, "");
4762
+ const ending = lines[line].slice(content.length);
4763
+ const scalar = readScalar(content.slice(valueStart));
4764
+ if (!scalar) refuse(`${where}: the scalar there is not written on one line`);
4765
+ if (scalar.value !== edit.from) refuse(`${where} holds "${scalar.value}", not "${edit.from}"`);
4766
+ lines[line] = content.slice(0, valueStart) + writeScalar(edit.to, scalar.style) + content.slice(valueStart + scalar.end) + ending;
4767
+ setAt(expected, edit.path, edit.to);
4768
+ }
4769
+ const next = lines.join("");
4770
+ if (JSON.stringify(parseYaml(next)) !== JSON.stringify(expected)) {
4771
+ refuse("the edited text does not read back as exactly the intended change");
4772
+ }
4773
+ return next;
4774
+ }
4585
4775
  function parseConfig(document, root) {
4586
4776
  try {
4587
4777
  return ProjectConfigSchema.parse(document);
@@ -4723,6 +4913,35 @@ function registryOver(store, root) {
4723
4913
  ...config,
4724
4914
  extensions: { ...config.extensions, packs: [...packsOf(config), ...selections], useGlobalPacks: false }
4725
4915
  });
4916
+ },
4917
+ rekeyCarried(rename, dryRun = false) {
4918
+ const groups = store.readField("rules.conformance.carried");
4919
+ if (!Array.isArray(groups)) return [];
4920
+ const edits = [];
4921
+ const rekeys = [];
4922
+ groups.forEach((group, g) => {
4923
+ const findings = group?.findings;
4924
+ if (!Array.isArray(findings)) return;
4925
+ findings.forEach((finding, f) => {
4926
+ const entry = finding;
4927
+ if (!entry || typeof entry.spec !== "string") return;
4928
+ const at = typeof entry.at === "string" ? entry.at : void 0;
4929
+ const covers = Array.isArray(entry.covers) ? entry.covers.filter((u) => typeof u === "string") : void 0;
4930
+ const next = rekeyAnchor(rename, { spec: entry.spec, at, covers });
4931
+ const base = ["rules", "conformance", "carried", g, "findings", f];
4932
+ const named = { code: String(entry.code), spec: entry.spec, at: at ?? "" };
4933
+ const change = (path31, field, from, to) => {
4934
+ if (from === to) return;
4935
+ edits.push({ path: [...base, ...path31], from, to });
4936
+ rekeys.push({ ...named, field, from, to });
4937
+ };
4938
+ change(["spec"], "spec", entry.spec, next.spec);
4939
+ if (at !== void 0) change(["at"], "at", at, next.at);
4940
+ (covers ?? []).forEach((unit, u) => change(["covers", u], "covers", unit, next.covers[u]));
4941
+ });
4942
+ });
4943
+ store.rewriteScalars(edits, dryRun);
4944
+ return rekeys;
4726
4945
  }
4727
4946
  };
4728
4947
  }
@@ -4807,6 +5026,9 @@ function projectConfigRepositoryOver(adapter, rootDir) {
4807
5026
  },
4808
5027
  pinGlobalPacksAsSelections(selections) {
4809
5028
  registry.pinGlobalPacksAsSelections(selections);
5029
+ },
5030
+ rekeyCarried(rename, dryRun) {
5031
+ return registry.rekeyCarried(rename, dryRun);
4810
5032
  }
4811
5033
  };
4812
5034
  }
@@ -4856,6 +5078,9 @@ var projectConfigRepository = {
4856
5078
  },
4857
5079
  pinGlobalPacksAsSelections(selections) {
4858
5080
  bound().pinGlobalPacksAsSelections(selections);
5081
+ },
5082
+ rekeyCarried(rename, dryRun) {
5083
+ return bound().rekeyCarried(rename, dryRun);
4859
5084
  }
4860
5085
  };
4861
5086
 
@@ -11858,7 +12083,7 @@ function defaultTargetConfig(type) {
11858
12083
  enabled: true
11859
12084
  };
11860
12085
  }
11861
- var WAIRON_VERSION = "5.1.1-dev.76";
12086
+ var WAIRON_VERSION = "5.1.1-dev.77";
11862
12087
  var GITHUB_REPO = "SYW-Apps/Waffle-AIron";
11863
12088
  var ARCHITECT_AGENT_ID = "agent-architect";
11864
12089
  var ARCHITECT_TEMPLATE_ID = "architect";
@@ -12647,6 +12872,28 @@ function buildCodeIndex(model) {
12647
12872
  return out;
12648
12873
  };
12649
12874
  const declarationsAt = (path31) => namesAt(declarations, pathKey(path31), (f) => /* @__PURE__ */ new Set([...f.declaredNames, ...f.exportedNames]));
12875
+ const forwardsOf = (start, name) => {
12876
+ const out = [];
12877
+ const seen = /* @__PURE__ */ new Set();
12878
+ const queue = [{ file: pathKey(start), name }];
12879
+ while (queue.length) {
12880
+ const at = queue.shift();
12881
+ const key = `${at.file}#${at.name}`;
12882
+ if (seen.has(key)) continue;
12883
+ seen.add(key);
12884
+ out.push(at);
12885
+ for (const binding of facts.get(at.file)?.reexportBindings ?? []) {
12886
+ const star = binding.exported === "*";
12887
+ if (!star && binding.exported !== at.name) continue;
12888
+ if (star && at.name === "default") continue;
12889
+ const to = resolveImport(at.file, binding.from, paths);
12890
+ if (!to) continue;
12891
+ if (star && !facts.get(to)?.exportedNames.includes(at.name)) continue;
12892
+ queue.push({ file: to, name: star ? at.name : binding.local });
12893
+ }
12894
+ }
12895
+ return out;
12896
+ };
12650
12897
  return {
12651
12898
  paths,
12652
12899
  exactPaths,
@@ -12655,7 +12902,8 @@ function buildCodeIndex(model) {
12655
12902
  anchorsAt: (path31) => namesAt(anchors, pathKey(path31), (f) => /* @__PURE__ */ new Set([...f.declaredNames, ...f.exportedNames, ...f.anchoredNames])),
12656
12903
  findingAnchorsAt: (path31) => namesAt(findingAnchors, pathKey(path31), (f) => new Set(f.anchoredNames)),
12657
12904
  originOf: originOf2,
12658
- possibleOriginsOf
12905
+ possibleOriginsOf,
12906
+ forwardsOf
12659
12907
  };
12660
12908
  }
12661
12909
  function buildRealizationIndex(ctx) {
@@ -16718,6 +16966,25 @@ function resolveCallTarget(ctx, componentId, methodName) {
16718
16966
  }
16719
16967
  return { accepted, files };
16720
16968
  }
16969
+ function isTarget(pairs, target) {
16970
+ return pairs.some((p) => target.files.has(p.file) && target.accepted.has(p.name));
16971
+ }
16972
+ function invokedAs(code, site, file) {
16973
+ const scope = pathKey(site.from ?? file);
16974
+ const facts = code.factsAt(scope);
16975
+ if (!facts || facts.status !== "analyzed" || facts.analysisGrade !== "exact") return [];
16976
+ if (!site.member) {
16977
+ const binding = importBindingOf(facts, site.name);
16978
+ if (!binding || binding.namespace) return [];
16979
+ const module3 = resolveImport(scope, binding.from, code.paths);
16980
+ return module3 ? code.forwardsOf(module3, binding.imported ?? site.name) : [];
16981
+ }
16982
+ if (!site.via) return [];
16983
+ const receiver = importBindingOf(facts, site.via);
16984
+ if (!receiver?.namespace) return [];
16985
+ const module2 = resolveImport(scope, receiver.from, code.paths);
16986
+ return module2 ? code.forwardsOf(module2, site.name) : [];
16987
+ }
16721
16988
  function claimsOf(implMethod) {
16722
16989
  const claims = [];
16723
16990
  for (const step of implMethod.narrative) {
@@ -16783,6 +17050,7 @@ var callConformanceRule = {
16783
17050
  if (!ctx.componentMap.has(claim.component)) continue;
16784
17051
  const target = resolveCallTarget(ctx, claim.component, claim.method);
16785
17052
  if (target.accepted.has(fnSymbol)) continue;
17053
+ if (isTarget(code.forwardsOf(file, fnSymbol), target)) continue;
16786
17054
  const matching = sites.filter((s) => target.accepted.has(s.name));
16787
17055
  const ref = `${claim.component}.${claim.method}`;
16788
17056
  if (target.files.size === 0) {
@@ -16791,6 +17059,7 @@ var callConformanceRule = {
16791
17059
  }
16792
17060
  continue;
16793
17061
  }
17062
+ if (sites.some((s) => !target.accepted.has(s.name) && isTarget(invokedAs(code, s, file), target))) continue;
16794
17063
  const landed = /* @__PURE__ */ new Set();
16795
17064
  let realized = false;
16796
17065
  for (const site of matching) {
@@ -20087,6 +20356,10 @@ function buildCodeModel(implementations, types, projectRoot, sourceRoots = [], e
20087
20356
  exportedNames: [...facts.exported],
20088
20357
  imports: [...facts.imports],
20089
20358
  reexports: [...facts.reexports],
20359
+ reexportBindings: [
20360
+ ...facts.namedReexports.map(({ exported, local, from }) => ({ exported, local, from })),
20361
+ ...facts.starExports.map((from) => ({ exported: "*", local: "*", from }))
20362
+ ],
20090
20363
  functionComplexity: Object.fromEntries(facts.complexity),
20091
20364
  functionCallSites: Object.fromEntries([...facts.calls].map(([k, v]) => [k, [...v.values()]])),
20092
20365
  importBindings: Object.fromEntries(facts.importBindings),
@@ -20114,6 +20387,28 @@ function buildCodeModel(implementations, types, projectRoot, sourceRoots = [], e
20114
20387
  }
20115
20388
  return { files, projectRoot, rootFiles };
20116
20389
  }
20390
+ var IMPORT_FROM_RE = /(?:^|;)[ \t]*import\s+(?!\()([^'";]*?)\s*from\s*['"]([^'"]+)['"]/gm;
20391
+ var DYNAMIC_IMPORT_RE = /\{([^{}]*)\}\s*=\s*await\s+import\(\s*['"]([^'"]+)['"]\s*\)/g;
20392
+ var IDENTIFIER_ONLY_RE = /^[A-Za-z_$][\w$]*$/;
20393
+ function clauseBindings(clause, specifier) {
20394
+ const out = [];
20395
+ const body = clause.replace(/^\s*type\s+/, "");
20396
+ const named = /\{([^}]*)\}/.exec(body);
20397
+ if (named) {
20398
+ for (const piece of named[1].split(",")) {
20399
+ const sides = piece.replace(/^\s*type\s+/, "").split(/\s+as\s+/).map((s) => s.trim());
20400
+ const exported = sides[0];
20401
+ const local = sides[1] ?? exported;
20402
+ if (IDENTIFIER_ONLY_RE.test(exported) && IDENTIFIER_ONLY_RE.test(local)) out.push({ exported, local, specifier });
20403
+ }
20404
+ }
20405
+ const namespace = /\*\s*as\s+([A-Za-z_$][\w$]*)/.exec(body);
20406
+ if (namespace) out.push({ exported: "*", local: namespace[1], specifier });
20407
+ const head = body.replace(/\{[^}]*\}/, "").replace(/\*\s*as\s+[A-Za-z_$][\w$]*/, "");
20408
+ const defaulted = /^\s*([A-Za-z_$][\w$]*)/.exec(head);
20409
+ if (defaulted) out.push({ exported: "default", local: defaulted[1], specifier });
20410
+ return out;
20411
+ }
20117
20412
  function readTestFiles(testRoots, projectRoot) {
20118
20413
  const scanned = [];
20119
20414
  for (const key of walkDeclaredRoots(testRoots, [], projectRoot)) {
@@ -20129,25 +20424,100 @@ function readTestFiles(testRoots, projectRoot) {
20129
20424
  for (const m of text2.matchAll(NAMED_IMPORT_BINDINGS_RE)) {
20130
20425
  for (const name of boundNames(m[1] ?? "", false)) imports.add(name);
20131
20426
  }
20427
+ const code = stripComments(text2, JS_PATTERNS);
20428
+ const bindings = [];
20429
+ for (const m of code.matchAll(IMPORT_FROM_RE)) bindings.push(...clauseBindings(m[1], m[2]));
20430
+ for (const m of code.matchAll(DYNAMIC_IMPORT_RE)) bindings.push(...clauseBindings(`{${m[1].replace(/:/g, " as ")}}`, m[2]));
20132
20431
  const words2 = /* @__PURE__ */ new Set();
20133
20432
  for (const m of text2.matchAll(IDENTIFIER_RE)) words2.add(m[0]);
20134
- scanned.push({ path: key, imports, words: words2 });
20433
+ scanned.push({ path: key, imports, bindings, words: words2 });
20135
20434
  }
20136
20435
  return scanned;
20137
20436
  }
20437
+ var NAMED_REEXPORT_RE = /\bexport\s+(?:type\s+)?\{([^}]*)\}\s*from\s*['"]([^'"]+)['"]/g;
20438
+ var STAR_REEXPORT_RE = /\bexport\s+\*\s+from\s*['"]([^'"]+)['"]/g;
20439
+ function reexportReader() {
20440
+ const cache = /* @__PURE__ */ new Map();
20441
+ return (file) => {
20442
+ const cached = cache.get(file);
20443
+ if (cached) return cached;
20444
+ const found = { named: [], stars: [] };
20445
+ try {
20446
+ const code = stripComments(fs10.readFileSync(file, "utf8"), JS_PATTERNS);
20447
+ for (const m of code.matchAll(NAMED_REEXPORT_RE)) {
20448
+ for (const piece of m[1].split(",")) {
20449
+ const sides = piece.replace(/^\s*type\s+/, "").split(/\s+as\s+/).map((s) => s.trim());
20450
+ if (!IDENTIFIER_ONLY_RE.test(sides[0] ?? "")) continue;
20451
+ found.named.push({ local: sides[0], exported: sides[1] ?? sides[0], specifier: m[2] });
20452
+ }
20453
+ }
20454
+ for (const m of code.matchAll(STAR_REEXPORT_RE)) found.stars.push(m[1]);
20455
+ } catch {
20456
+ }
20457
+ cache.set(file, found);
20458
+ return found;
20459
+ };
20460
+ }
20461
+ function publishesFrom(module2, name, target, symbol, readReexports, visited = /* @__PURE__ */ new Set()) {
20462
+ if (path13.resolve(module2) === target && name === symbol) return true;
20463
+ const key = `${module2}#${name}`;
20464
+ if (visited.has(key)) return false;
20465
+ visited.add(key);
20466
+ const reexports = readReexports(module2);
20467
+ for (const re of reexports.named) {
20468
+ if (re.exported !== name) continue;
20469
+ const next = resolveRelativeModule(module2, re.specifier);
20470
+ if (next && publishesFrom(next, re.local, target, symbol, readReexports, visited)) return true;
20471
+ }
20472
+ if (name === "default") return false;
20473
+ for (const specifier of reexports.stars) {
20474
+ const next = resolveRelativeModule(module2, specifier);
20475
+ if (next && publishesFrom(next, name, target, symbol, readReexports, visited)) return true;
20476
+ }
20477
+ return false;
20478
+ }
20138
20479
  function findTestsReferencing(methods, projectRoot, testRoots = []) {
20139
20480
  const scanned = readTestFiles(testRoots, projectRoot);
20140
20481
  if (scanned.length === 0) return [];
20141
20482
  const indiscriminateAbove = scanned.length / 10;
20142
- const found = [];
20483
+ const searches = [];
20143
20484
  for (const method of methods) {
20144
20485
  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);
20486
+ let search = searches.find((s) => s.method === method.name && s.symbol === symbol);
20487
+ if (!search) {
20488
+ search = { method: method.name, symbol, files: [] };
20489
+ searches.push(search);
20490
+ }
20491
+ const file = method.sourcePath ? path13.resolve(projectRoot, method.sourcePath) : void 0;
20492
+ if (file && !search.files.includes(file)) search.files.push(file);
20493
+ }
20494
+ const readReexports = reexportReader();
20495
+ const found = [];
20496
+ for (const { method, symbol, files } of searches) {
20497
+ const imported = [];
20498
+ const mentioned = [];
20499
+ for (const test of scanned) {
20500
+ if (files.length === 0) {
20501
+ if (test.imports.has(symbol)) imported.push(test.path);
20502
+ else if (test.words.has(symbol)) mentioned.push(test.path);
20503
+ continue;
20504
+ }
20505
+ const testFile = path13.resolve(projectRoot, test.path);
20506
+ const fromMethod = (binding, name) => {
20507
+ const module2 = resolveRelativeModule(testFile, binding.specifier);
20508
+ return module2 !== null && files.some((target) => publishesFrom(module2, name, target, symbol, readReexports));
20509
+ };
20510
+ const naming = test.bindings.filter((b) => b.exported === symbol || b.local === symbol);
20511
+ if (test.bindings.some((b) => b.exported !== "*" && fromMethod(b, b.exported)) || test.words.has(symbol) && test.bindings.some((b) => b.exported === "*" && fromMethod(b, symbol))) {
20512
+ imported.push(test.path);
20513
+ } else if (naming.length === 0 && test.words.has(symbol)) {
20514
+ mentioned.push(test.path);
20515
+ }
20516
+ }
20147
20517
  const indiscriminate = mentioned.length > indiscriminateAbove;
20148
20518
  if (imported.length === 0 && mentioned.length === 0) continue;
20149
20519
  found.push({
20150
- method: method.name,
20520
+ method,
20151
20521
  symbol,
20152
20522
  imported,
20153
20523
  mentioned: indiscriminate ? [] : mentioned,
@@ -24373,6 +24743,8 @@ var SpecWorkspace = class {
24373
24743
  const plan = this.planMethodMove(source, to, methods, contracts);
24374
24744
  const repointed = this.repointToNewHome(from, to, methods, plan);
24375
24745
  const mentions = this.moveMentions(from, plan);
24746
+ const rename = moveRename(from, to, methods, plan);
24747
+ this.rekeyMoveAllows(plan, rename);
24376
24748
  if (hooks) {
24377
24749
  const verdict = judgeMoveResult(hooks, plan);
24378
24750
  plan.notices.push(...verdict.notices);
@@ -24391,10 +24763,12 @@ var SpecWorkspace = class {
24391
24763
  summary: refusedMoveSummary(from, to, methods, verdict.refusals, alternatives),
24392
24764
  dryRun,
24393
24765
  mentions,
24394
- created: []
24766
+ created: [],
24767
+ carried: []
24395
24768
  };
24396
24769
  }
24397
24770
  }
24771
+ const carried = projectConfigRepository.rekeyCarried(rename, true);
24398
24772
  if (dryRun) {
24399
24773
  const edits2 = moveChangeReports([...plan.edits.values()], true);
24400
24774
  return {
@@ -24407,13 +24781,16 @@ var SpecWorkspace = class {
24407
24781
  refusals: [],
24408
24782
  alternatives: [],
24409
24783
  notices: plan.notices,
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.`,
24784
+ 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"}${registerClause(carried, "would rekey")}. Nothing was written.`,
24411
24785
  dryRun: true,
24412
24786
  mentions,
24413
- created: plan.created
24787
+ created: plan.created,
24788
+ carried
24414
24789
  };
24415
24790
  }
24416
- const edits = this.writeMoveEdits([...plan.edits.values()]);
24791
+ const edits = this.writeMoveEdits([...plan.edits.values()], () => {
24792
+ projectConfigRepository.rekeyCarried(rename);
24793
+ });
24417
24794
  return {
24418
24795
  moved: true,
24419
24796
  from,
@@ -24424,12 +24801,49 @@ var SpecWorkspace = class {
24424
24801
  refusals: [],
24425
24802
  alternatives: [],
24426
24803
  notices: plan.notices,
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` : ""}.`,
24804
+ 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${registerClause(carried, "rekeyed")}${mentions.length ? `, ${mentions.length} still naming the old home in prose or on the wire` : ""}.`,
24428
24805
  dryRun: false,
24429
24806
  mentions,
24430
- created: plan.created
24807
+ created: plan.created,
24808
+ carried
24431
24809
  };
24432
24810
  }
24811
+ /**
24812
+ * Rekey every lint allow keyed on a moved method, staging each spec it
24813
+ * changes like any other reference the move follows. An allow anchored at a
24814
+ * moved method on a spec the method left moves to the spec it arrived in; a
24815
+ * covered `<from>.<method>` unit anywhere names `<to>.<method>`. An allow
24816
+ * whose new home the plan does not stage stays where it is.
24817
+ */
24818
+ rekeyMoveAllows(plan, rename) {
24819
+ const subjects = [...this.everySpecInTree()];
24820
+ for (const edit of plan.edits.values()) if (edit.before === null) subjects.push({ kind: edit.kind, id: edit.id, spec: edit.after });
24821
+ const stagedAfter = (kind, id) => plan.edits.get(`${kind}:${id}`)?.after;
24822
+ for (const { kind, id, spec } of subjects) {
24823
+ if (id.includes("::")) continue;
24824
+ const allows = spec?.lint?.allow ?? [];
24825
+ if (allows.length === 0) continue;
24826
+ const staged = plan.edits.get(`${kind}:${id}`);
24827
+ const subject = staged ? staged.after : cloneSpec(spec);
24828
+ const kept = [];
24829
+ let changed = false;
24830
+ for (const allow of subject.lint?.allow ?? []) {
24831
+ const next = rekeyAnchor(rename, { spec: id, ...allow.at !== void 0 ? { at: allow.at } : {}, ...allow.covers ? { covers: allow.covers } : {} });
24832
+ const rekeyed = { ...allow, ...next.at !== void 0 ? { at: next.at } : {}, ...next.covers ? { covers: next.covers } : {} };
24833
+ if (JSON.stringify(rekeyed) !== JSON.stringify(allow)) changed = true;
24834
+ const home = next.spec !== id ? stagedAfter(kind, next.spec) : void 0;
24835
+ if (home) {
24836
+ home.lint = { ...home.lint ?? {}, allow: [...home.lint?.allow ?? [], rekeyed] };
24837
+ changed = true;
24838
+ continue;
24839
+ }
24840
+ kept.push(rekeyed);
24841
+ }
24842
+ if (!changed) continue;
24843
+ subject.lint = { ...subject.lint, allow: kept };
24844
+ if (!staged) this.stageMoveEdit(plan, kind, id, spec, subject);
24845
+ }
24846
+ }
24433
24847
  /**
24434
24848
  * Why this move cannot be attempted at all, or null when the request is
24435
24849
  * answerable. Every check here is about what the CALLER named, so its answer
@@ -24502,8 +24916,10 @@ var SpecWorkspace = class {
24502
24916
  newDependencies: [],
24503
24917
  wireBound: [],
24504
24918
  notices: [],
24505
- created: []
24919
+ created: [],
24920
+ rehomed: []
24506
24921
  };
24922
+ const leftImplementations = [];
24507
24923
  const receiving = stated ?? createdContract(target, source.id, methods, contracts);
24508
24924
  const arriving = [];
24509
24925
  const travelling = [];
@@ -24515,6 +24931,7 @@ var SpecWorkspace = class {
24515
24931
  const remaining = cloneSpec(contract);
24516
24932
  remaining.methods = remaining.methods.filter((m) => !methods.includes(m.name));
24517
24933
  this.stageMoveEdit(plan, "interface", contract.id, contract, remaining);
24934
+ plan.rehomed.push({ from: contract.id, to: receiving.id });
24518
24935
  if (contract.methods.length === leaving.length) {
24519
24936
  plan.notices.push(`"${contract.id}" is left with no methods \u2014 "${source.id}" no longer declares anything on it.`);
24520
24937
  }
@@ -24526,6 +24943,7 @@ var SpecWorkspace = class {
24526
24943
  const kept = cloneSpec(impl);
24527
24944
  kept.methods = kept.methods.filter((m) => !methods.includes(m.name));
24528
24945
  this.stageMoveEdit(plan, "implementation", impl.id, impl, kept);
24946
+ leftImplementations.push(impl.id);
24529
24947
  }
24530
24948
  }
24531
24949
  this.stageMoveEdit(plan, "interface", receiving.id, stated, {
@@ -24554,9 +24972,11 @@ var SpecWorkspace = class {
24554
24972
  ...cloneSpec(targetImpl),
24555
24973
  methods: [...cloneSpec(targetImpl.methods), ...travelling]
24556
24974
  });
24975
+ for (const id of leftImplementations) plan.rehomed.push({ from: id, to: targetImpl.id });
24557
24976
  } else if (travelling.length > 0) {
24558
24977
  const created = createdImplementation(target, receiving.id, source.id, travelling, implementations, contracts);
24559
24978
  this.stageMoveEdit(plan, "implementation", created.spec.id, null, created.spec);
24979
+ for (const id of leftImplementations) plan.rehomed.push({ from: id, to: created.spec.id });
24560
24980
  plan.created.push(created.named);
24561
24981
  plan.notices.push(...created.notices);
24562
24982
  }
@@ -24686,7 +25106,7 @@ var SpecWorkspace = class {
24686
25106
  * here: every save does it already, and a second call would only be a hop
24687
25107
  * across a component boundary that no narrative claims.
24688
25108
  */
24689
- writeMoveEdits(edits) {
25109
+ writeMoveEdits(edits, finish) {
24690
25110
  const reports = moveChangeReports(edits, false);
24691
25111
  const written = [];
24692
25112
  try {
@@ -24695,6 +25115,7 @@ var SpecWorkspace = class {
24695
25115
  report.notices.push(...this.saveMovedSpec(edit.kind, edit.after));
24696
25116
  written.push(edit);
24697
25117
  }
25118
+ finish?.();
24698
25119
  } catch (e) {
24699
25120
  for (const edit of [...written].reverse()) {
24700
25121
  try {
@@ -24801,6 +25222,15 @@ function moveChangeReports(edits, dryRun) {
24801
25222
  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"}.`
24802
25223
  }));
24803
25224
  }
25225
+ function moveRename(from, to, methods, plan) {
25226
+ return {
25227
+ specs: plan.rehomed.map(({ from: left, to: arrived }) => ({ from: left, to: arrived, sites: [...methods] })),
25228
+ methods: methods.map((method) => ({ component: from, method, toComponent: to, toMethod: method }))
25229
+ };
25230
+ }
25231
+ function registerClause(carried, verb) {
25232
+ return carried.length ? `, ${verb} ${carried.length} debt-register key${carried.length === 1 ? "" : "s"}` : "";
25233
+ }
24804
25234
  function createdClause(created, verb) {
24805
25235
  return created.length ? `, ${verb} ${created.join(" and ")}` : "";
24806
25236
  }
@@ -25490,6 +25920,42 @@ function rewriteRefFields(specsDir, remap, excludeDir) {
25490
25920
  }
25491
25921
  return rewritten;
25492
25922
  }
25923
+ function rekeyLintAllows(specsDir, rename) {
25924
+ const rewritten = [];
25925
+ for (const file of listFilesRecursive(specsDir, ".yaml")) {
25926
+ let raw;
25927
+ try {
25928
+ raw = readYamlFile(file);
25929
+ } catch {
25930
+ continue;
25931
+ }
25932
+ const allows = raw?.lint?.allow;
25933
+ const kind = raw && typeof raw === "object" ? specKind(raw) : null;
25934
+ if (!kind || !Array.isArray(allows)) continue;
25935
+ const specId = kind === "system" ? "system" : String(raw.id);
25936
+ let changed = false;
25937
+ for (const allow of allows) {
25938
+ if (!allow || typeof allow !== "object") continue;
25939
+ const next = rekeyAnchor(rename, {
25940
+ spec: specId,
25941
+ ...typeof allow.at === "string" ? { at: allow.at } : {},
25942
+ ...Array.isArray(allow.covers) ? { covers: allow.covers } : {}
25943
+ });
25944
+ if (next.at !== void 0 && next.at !== allow.at) {
25945
+ allow.at = next.at;
25946
+ changed = true;
25947
+ }
25948
+ if (next.covers && JSON.stringify(next.covers) !== JSON.stringify(allow.covers)) {
25949
+ allow.covers = next.covers;
25950
+ changed = true;
25951
+ }
25952
+ }
25953
+ if (!changed) continue;
25954
+ writeYamlFile(file, raw);
25955
+ rewritten.push({ kind, id: specId });
25956
+ }
25957
+ return rewritten;
25958
+ }
25493
25959
  function rebaseImplementationPaths(specsDir, fromRoot, toRoot) {
25494
25960
  for (const file of listFilesRecursive(specsDir, ".yaml")) {
25495
25961
  let raw;
@@ -25559,24 +26025,32 @@ function renameComponent(componentId, newId) {
25559
26025
  ...movingInterface ? [{ kind: "interface", from: movingInterface.id, to: interfaceId }] : [],
25560
26026
  ...movingImplementation ? [{ kind: "implementation", from: movingImplementation.id, to: implementationId }] : []
25561
26027
  ];
25562
- const rewritten = rewriteRefFields(aiPathsAt(getProjectRoot()).specsDir(), (ref, position) => {
26028
+ const rename = {
26029
+ specs: renamed.map(({ from, to }) => ({ from, to })),
26030
+ components: [{ from: componentId, to: newId }]
26031
+ };
26032
+ projectConfigRepository.rekeyCarried(rename, true);
26033
+ const rewrittenRefs = rewriteRefFields(aiPathsAt(getProjectRoot()).specsDir(), (ref, position) => {
25563
26034
  if (position === "component" || position === "entity-class" || position === "auth-source") {
25564
26035
  return ref === componentId ? newId : ref;
25565
26036
  }
25566
26037
  if (position === "interface" && ref === movingInterface?.id) return interfaceId;
25567
26038
  return ref;
25568
- }).filter((spec) => !renamed.some((moved) => moved.kind === spec.kind && moved.from === spec.id)).map((spec) => spec.id);
26039
+ });
26040
+ const allowsRekeyed = rekeyLintAllows(aiPathsAt(getProjectRoot()).specsDir(), rename);
26041
+ const rewritten = [...new Set([...rewrittenRefs, ...allowsRekeyed].filter((spec) => !renamed.some((moved) => moved.kind === spec.kind && moved.from === spec.id)).map((spec) => spec.id))];
25569
26042
  invalidateSpecCache();
25570
26043
  moveRenamedSpecs(componentId, newId, component, movingInterface, movingImplementation);
25571
- return { renamed, rewritten };
26044
+ const carried = projectConfigRepository.rekeyCarried(rename);
26045
+ return { renamed, rewritten, carried };
25572
26046
  }
25573
26047
  function moveRenamedSpecs(componentId, newId, component, movingInterface, movingImplementation) {
25574
26048
  const interfaceId = `i${newId}`;
25575
26049
  const implementationId = `${newId}_impl`;
25576
- const movedComponent = loadComponentSpec(componentId) ?? component;
26050
+ const movedComponent2 = loadComponentSpec(componentId) ?? component;
25577
26051
  const movedInterface = movingInterface ? loadInterfaceSpec(movingInterface.id) ?? movingInterface : null;
25578
26052
  const movedImplementation = movingImplementation ? loadImplementationSpec(movingImplementation.id) ?? movingImplementation : null;
25579
- saveComponentSpec({ ...movedComponent, id: newId });
26053
+ saveComponentSpec({ ...movedComponent2, id: newId });
25580
26054
  if (movedInterface) saveInterfaceSpec({ ...movedInterface, id: interfaceId, component: newId });
25581
26055
  if (movedImplementation) {
25582
26056
  const oldFile = getImplementationPath(movedImplementation.id);
@@ -25641,6 +26115,19 @@ function renameMethod(componentId, methodName, newName, pinSymbol) {
25641
26115
  const implementations = loadImplementationSpecs();
25642
26116
  const movingContracts = new Set(moving.map((i) => i.id));
25643
26117
  const renamed = [];
26118
+ const rename = {
26119
+ methods: [{
26120
+ component: componentId,
26121
+ method: methodName,
26122
+ toComponent: componentId,
26123
+ toMethod: newName,
26124
+ specs: [
26125
+ ...moving.map((i) => i.id),
26126
+ ...implementations.filter((impl) => movingContracts.has(impl.contract) && impl.methods.some((m) => m.name === methodName)).map((impl) => impl.id)
26127
+ ]
26128
+ }]
26129
+ };
26130
+ projectConfigRepository.rekeyCarried(rename, true);
25644
26131
  for (const contract of moving) {
25645
26132
  saveInterfaceSpec({
25646
26133
  ...contract,
@@ -25663,8 +26150,11 @@ function renameMethod(componentId, methodName, newName, pinSymbol) {
25663
26150
  }
25664
26151
  invalidateSpecCache();
25665
26152
  const specsDir = aiPathsAt(getProjectRoot()).specsDir();
25666
- const rewritten = rewriteRefFields(specsDir, (ref, position, owner) => position === "method" && owner === componentId && ref === methodName ? newName : ref).map((spec) => spec.id);
26153
+ const retargeted = rewriteRefFields(specsDir, (ref, position, owner) => position === "method" && owner === componentId && ref === methodName ? newName : ref);
26154
+ const allowsRekeyed = rekeyLintAllows(specsDir, rename);
26155
+ const rewritten = [...new Set([...retargeted, ...allowsRekeyed].map((spec) => spec.id))];
25667
26156
  const mentions = collectMentions(specsDir, methodName, moving);
26157
+ const carried = projectConfigRepository.rekeyCarried(rename);
25668
26158
  return {
25669
26159
  component: componentId,
25670
26160
  from: methodName,
@@ -25672,7 +26162,8 @@ function renameMethod(componentId, methodName, newName, pinSymbol) {
25672
26162
  renamed,
25673
26163
  rewritten,
25674
26164
  mentions,
25675
- ...pinnedSymbol ? { pinnedSymbol } : {}
26165
+ ...pinnedSymbol ? { pinnedSymbol } : {},
26166
+ carried
25676
26167
  };
25677
26168
  }
25678
26169
  function renameInSignature(signature, from, to) {
@@ -28048,7 +28539,8 @@ var MISSING_PARENT = {
28048
28539
  subsystem: () => "System spec must be initialized (sdd_initialize_system) first.",
28049
28540
  component: (id) => `Parent subsystem "${id}" does not exist.`,
28050
28541
  interface: (id) => `Component "${id}" does not exist.`,
28051
- implementation: (id) => `Interface contract "${id}" does not exist.`
28542
+ implementation: (id) => `Interface contract "${id}" does not exist.`,
28543
+ type: (id) => `Owning subsystem "${id}" does not exist \u2014 name a subsystem the tree has, or omit subsystem for a system-level value object.`
28052
28544
  };
28053
28545
  var REWRITE_LABEL = {
28054
28546
  system: "System spec",
@@ -28069,6 +28561,8 @@ function restatementParent(restatement) {
28069
28561
  return { kind: "component", id: spec.component };
28070
28562
  case "implementation":
28071
28563
  return { kind: "interface", id: spec.contract };
28564
+ case "type":
28565
+ return typeof spec.subsystem === "string" && spec.subsystem !== "" ? { kind: "subsystem", id: spec.subsystem } : null;
28072
28566
  default:
28073
28567
  return null;
28074
28568
  }
@@ -28272,7 +28766,7 @@ function writeSpec(restatement) {
28272
28766
  if (application.refusal !== void 0) throw new Error(application.refusal);
28273
28767
  const gateNotices = restatement.kind === "component" ? judgeComponent(application.spec, bound2) : [];
28274
28768
  const persisted = persistCandidate(restatement.kind, application.spec);
28275
- const testsToRevisit = testsInvalidatedBy(application.changedMethods, [existing, application.spec], bound2);
28769
+ const testsToRevisit = testsInvalidatedBy(restatement.kind, id, application.changedMethods, [existing, application.spec], bound2);
28276
28770
  return {
28277
28771
  kind: restatement.kind,
28278
28772
  id,
@@ -28297,42 +28791,59 @@ function persistCandidate(kind, spec) {
28297
28791
  }
28298
28792
  return { notices: saveSpec(kind, spec) };
28299
28793
  }
28300
- function testsInvalidatedBy(methods, specs, options) {
28794
+ function testsInvalidatedBy(kind, id, methods, specs, options, written = []) {
28301
28795
  const testRoots = options.rules?.conformance?.testRoots ?? [];
28302
28796
  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);
28797
+ return findTestsReferencing2(searchedMethods(kind, id, methods, specs, written), getProjectRoot(), testRoots);
28798
+ }
28799
+ function searchedMethods(kind, id, names, specs, written) {
28800
+ const stated = (name) => written.find((m) => m?.name === name)?.symbol;
28801
+ if (kind === "interface") {
28802
+ const realizations = loadImplementationSpecs().filter((impl) => impl.contract === id);
28803
+ return names.flatMap((name) => {
28804
+ const found = realizations.flatMap((impl) => impl.methods.filter((m) => m.name === name).map((m) => ({ name, symbol: stated(name) ?? m.symbol, sourcePath: m.sourcePath ?? impl.sourcePath })));
28805
+ return found.length > 0 ? found : [{ name, symbol: stated(name) }];
28806
+ });
28807
+ }
28808
+ return names.map((name) => {
28809
+ const holders = specs.map((spec) => {
28810
+ const holder = spec;
28811
+ const method = holder?.methods?.find((m) => m.name === name);
28812
+ return method ? { method, file: method.sourcePath ?? holder?.sourcePath } : null;
28813
+ }).filter((h) => h !== null);
28814
+ const symbol = stated(name) ?? holders.map((h) => h.method.symbol).find((s) => s !== void 0);
28815
+ const sourcePath = holders.map((h) => h.file).find((f) => f !== void 0) ?? specs.map((spec) => spec?.sourcePath).find((f) => f !== void 0);
28816
+ return { name, symbol, sourcePath };
28817
+ });
28305
28818
  }
28306
28819
  function deleteSpec2(kind, id) {
28307
28820
  const bound2 = candidateOptions();
28308
28821
  const stored = loadSpec(kind, id);
28309
28822
  const deleted = deleteSpec(kind, id);
28310
28823
  const methods = deleted ? (stored?.methods ?? []).map((m) => m.name) : [];
28311
- const testsToRevisit = testsInvalidatedBy(methods, [stored], bound2);
28824
+ const testsToRevisit = testsInvalidatedBy(kind, id, methods, [stored], bound2);
28312
28825
  return { kind, id, deleted, testsToRevisit };
28313
28826
  }
28314
28827
  function updateSpecGated(kind, id, delta, dryRun) {
28315
28828
  const bound2 = candidateOptions();
28316
28829
  const gate = componentCandidateGate(bound2);
28317
- const report = updateSpec(kind, id, delta, gate, dryRun);
28318
28830
  const testRoots = bound2.rules?.conformance?.testRoots ?? [];
28319
- const changed = changedMethods(report, delta);
28831
+ const stored = testRoots.length > 0 ? loadSpec(kind, id) : null;
28832
+ const report = updateSpec(kind, id, delta, gate, dryRun);
28833
+ const changed = changedMethods(report);
28320
28834
  if (changed.length > 0 && testRoots.length > 0) {
28321
- report.testsToRevisit = findTestsReferencing2(changed, getProjectRoot(), testRoots);
28835
+ const written = Array.isArray(delta?.methods) ? delta.methods : [];
28836
+ report.testsToRevisit = testsInvalidatedBy(kind, id, changed, [stored], bound2, written);
28322
28837
  }
28323
28838
  return report;
28324
28839
  }
28325
- function changedMethods(report, delta) {
28840
+ function changedMethods(report) {
28326
28841
  const named = /* @__PURE__ */ new Set();
28327
28842
  for (const change of report.changes) {
28328
28843
  const [field, name] = change.path.split(".");
28329
28844
  if (field === "methods" && name) named.add(name);
28330
28845
  }
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
- }));
28846
+ return [...named];
28336
28847
  }
28337
28848
  function methodMoveGate(source) {
28338
28849
  const bound2 = candidateOptions();
@@ -29288,7 +29799,7 @@ ${renderChangeReport(report)}`,
29288
29799
  server,
29289
29800
  "sdd_rename_component",
29290
29801
  {
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.",
29802
+ 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. Findings keyed on the component follow it too: every lint allow naming it on an edge site or in a covered unit, and every entry of the debt register (.wai/project.yaml rules.conformance.carried) anchored on a renamed spec or naming the component in its site or units \u2014 rewritten in place, every comment and all other formatting of the register kept, so unchanged debt never reads as paid and new at once. A register that cannot be rewritten that precisely is refused before the first write. 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, the ids of the other specs rewritten (lint allows included), and `carried`: each register edit \u2014 the entry by code, spec and site as it stood, the key changed (spec, at or covers), from and to.",
29292
29803
  inputSchema: {
29293
29804
  id: import_zod11.z.string().describe("The component to rename (namespaced if needed)"),
29294
29805
  newId: import_zod11.z.string().describe("Its new id: a lowercase identifier with no namespace separator")
@@ -29306,7 +29817,7 @@ ${renderChangeReport(report)}`,
29306
29817
  server,
29307
29818
  "sdd_rename_method",
29308
29819
  {
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.",
29820
+ 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, and so are the findings keyed on it: a lint allow at the method on a spec it moved in or covering `<component>.<method>`, and every debt-register entry (.wai/project.yaml rules.conformance.carried) keyed the same way \u2014 rewritten in place with every comment and all other formatting kept (a register that cannot be rewritten that precisely is refused before the first write). 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 (lint allows included), the specs whose prose still names it, the pinned symbol when one was set, and `carried`: each register edit.",
29310
29821
  inputSchema: {
29311
29822
  id: import_zod11.z.string().describe("The component whose method is renamed (namespaced if needed)"),
29312
29823
  method: import_zod11.z.string().describe("The method name as it stands"),
@@ -29326,7 +29837,7 @@ ${renderChangeReport(report)}`,
29326
29837
  server,
29327
29838
  "sdd_move_methods",
29328
29839
  {
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.",
29840
+ 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. Findings keyed on a moved method follow it: a lint allow anchored at it moves to the receiving spec, one covering `<from>.<method>` names `<to>.<method>`, and the debt register (.wai/project.yaml rules.conformance.carried) is rekeyed the same way in place, every comment and all other formatting kept \u2014 the report's `carried` lists each register edit, and a register that cannot be rewritten that precisely is refused before the first write. 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
29841
  inputSchema: {
29331
29842
  from: import_zod11.z.string().describe("The component the methods belong to (namespaced if needed)"),
29332
29843
  to: import_zod11.z.string().describe("The component that should receive them (namespaced if needed)"),
@@ -29679,7 +30190,7 @@ ${renderChangeReport(report)}`,
29679
30190
  id: import_zod11.z.string().describe("Lowercase identifier"),
29680
30191
  name: import_zod11.z.string().describe("Human-readable name"),
29681
30192
  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"),
30193
+ subsystem: import_zod11.z.string().optional().describe("Owning subsystem id; omit for a system-level shared value object. A subsystem the tree does not have is refused and nothing is written"),
29683
30194
  group: import_zod11.z.string().optional().describe("Optional logical group ID to organize this type in subfolders"),
29684
30195
  fields: import_zod11.z.array(import_zod11.z.object({
29685
30196
  name: import_zod11.z.string(),
@@ -29713,7 +30224,7 @@ ${renderChangeReport(report)}`,
29713
30224
  server,
29714
30225
  "sdd_add_type",
29715
30226
  {
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.",
30227
+ 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. An owning subsystem the tree does not have is refused, writing nothing, exactly as a component under an unknown subsystem is. 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
30228
  inputSchema: typeInput,
29718
30229
  outputSchema: specWriteReceiptOutput
29719
30230
  },