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

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/cli/index.js CHANGED
@@ -65,7 +65,7 @@ var init_defaults = __esm({
65
65
  copilot: ".github/prompts",
66
66
  codex: ".codex/agents"
67
67
  };
68
- WAIRON_VERSION = "5.1.1-dev.76";
68
+ WAIRON_VERSION = "5.1.1-dev.78";
69
69
  GITHUB_REPO = "SYW-Apps/Waffle-AIron";
70
70
  SUPPORTED_ALIASES = ["wai"];
71
71
  SCAN_EXCLUDE_DIRS = /* @__PURE__ */ new Set([
@@ -359,6 +359,46 @@ var init_yaml = __esm({
359
359
  }
360
360
  });
361
361
 
362
+ // src/models/identity-rename.ts
363
+ function movedComponent(rename, id) {
364
+ return rename.components?.find((c) => c.from === id)?.to ?? id;
365
+ }
366
+ function rekeyUnit(rename, unit) {
367
+ if (unit.includes(EDGE)) return unit.split(EDGE).map((side) => movedComponent(rename, side)).join(EDGE);
368
+ const qualified = QUALIFIED_UNIT.exec(unit);
369
+ if (!qualified) return unit;
370
+ const [, step = "", component, method2] = qualified;
371
+ const moved = rename.methods?.find((m) => m.component === component && m.method === method2);
372
+ if (moved) return `${step}${moved.toComponent}.${moved.toMethod}`;
373
+ return `${step}${movedComponent(rename, component)}.${method2}`;
374
+ }
375
+ function rekeyAnchor(rename, anchor) {
376
+ const { spec, at, covers } = anchor;
377
+ const movedSpec = rename.specs?.find((s) => s.from === spec && (!s.sites || at !== void 0 && s.sites.includes(at)));
378
+ let site = at;
379
+ if (at !== void 0) {
380
+ if (at.includes(EDGE)) {
381
+ site = rekeyUnit(rename, at);
382
+ } else {
383
+ const moved = rename.methods?.find((m) => m.method === at && m.specs?.includes(spec));
384
+ if (moved) site = moved.toMethod;
385
+ }
386
+ }
387
+ return {
388
+ spec: movedSpec?.to ?? spec,
389
+ ...site !== void 0 ? { at: site } : {},
390
+ ...covers !== void 0 ? { covers: covers.map((unit) => rekeyUnit(rename, unit)) } : {}
391
+ };
392
+ }
393
+ var EDGE, QUALIFIED_UNIT;
394
+ var init_identity_rename = __esm({
395
+ "src/models/identity-rename.ts"() {
396
+ "use strict";
397
+ EDGE = " -> ";
398
+ QUALIFIED_UNIT = /^(\d+:)?([^.\s]+)\.([^.\s]+)$/;
399
+ }
400
+ });
401
+
362
402
  // src/models/execution.ts
363
403
  function summarize(budget, profile) {
364
404
  const lines = [
@@ -1002,6 +1042,12 @@ function projectConfigFsAdapterAt(rootDir) {
1002
1042
  },
1003
1043
  documentExists() {
1004
1044
  return fs4.existsSync(file());
1045
+ },
1046
+ readText() {
1047
+ return readFileOrNull(file());
1048
+ },
1049
+ writeText(text2) {
1050
+ writeFile(file(), text2);
1005
1051
  }
1006
1052
  };
1007
1053
  }
@@ -1027,8 +1073,157 @@ function storeOver(adapter, root) {
1027
1073
  },
1028
1074
  readField(field) {
1029
1075
  return valueAt(currentDocument(), field);
1076
+ },
1077
+ rewriteScalars(edits, dryRun = false) {
1078
+ if (edits.length === 0) return;
1079
+ const text2 = adapter.readText();
1080
+ if (text2 === null) throw new ProjectNotInitializedError();
1081
+ const next = rewriteScalarsInText(text2, edits, root);
1082
+ if (!dryRun) adapter.writeText(next);
1083
+ }
1084
+ };
1085
+ }
1086
+ function tokenize(lines) {
1087
+ const tokens = [];
1088
+ let blockAbove = null;
1089
+ lines.forEach((raw, line2) => {
1090
+ const content = raw.replace(/\r?\n$/, "");
1091
+ const indent = content.length - content.trimStart().length;
1092
+ const trimmed = content.trim();
1093
+ if (blockAbove !== null) {
1094
+ if (trimmed === "" || indent > blockAbove) return;
1095
+ blockAbove = null;
1096
+ }
1097
+ if (trimmed === "" || trimmed.startsWith("#")) return;
1098
+ let col = indent;
1099
+ let rest = content.slice(indent);
1100
+ while (rest === "-" || rest.startsWith("- ")) {
1101
+ tokens.push({ kind: "item", col, line: line2 });
1102
+ const after = rest.slice(1);
1103
+ const pad = after.length - after.trimStart().length;
1104
+ col += 1 + pad;
1105
+ rest = after.trimStart();
1106
+ }
1107
+ if (rest === "") return;
1108
+ const key = KEY_RE.exec(rest);
1109
+ if (key) {
1110
+ const name = key[1].startsWith("'") ? key[1].slice(1, -1).replace(/''/g, "'") : key[1].startsWith('"') ? JSON.parse(key[1]) : key[1].trim();
1111
+ const afterKey = rest.slice(key[0].length);
1112
+ const value = afterKey.trimStart();
1113
+ const token = { kind: "key", col, line: line2, name };
1114
+ if (value !== "" && !value.startsWith("#")) token.valueStart = col + key[0].length + (afterKey.length - value.length);
1115
+ tokens.push(token);
1116
+ if (/^[|>][-+0-9]*\s*(#.*)?$/.test(value)) blockAbove = col;
1117
+ return;
1030
1118
  }
1031
- };
1119
+ tokens.push({ kind: "scalar", col, line: line2, valueStart: col });
1120
+ if (/^[|>][-+0-9]*\s*(#.*)?$/.test(rest)) blockAbove = col - 2;
1121
+ });
1122
+ return tokens;
1123
+ }
1124
+ function childrenOf(tokens, index) {
1125
+ const parent = tokens[index];
1126
+ const out = [];
1127
+ for (let i = index + 1; i < tokens.length; i += 1) {
1128
+ const t = tokens[i];
1129
+ const nested = t.col > parent.col || parent.kind === "key" && t.kind === "item" && t.col === parent.col;
1130
+ if (!nested) break;
1131
+ out.push(t);
1132
+ }
1133
+ return out;
1134
+ }
1135
+ function locateScalar(tokens, path73) {
1136
+ let region = tokens;
1137
+ for (let k = 0; k < path73.length; k += 1) {
1138
+ const segment = path73[k];
1139
+ if (region.length === 0) return `nothing is nested at ${path73.slice(0, k).join(".")}`;
1140
+ const base = Math.min(...region.map((t) => t.col));
1141
+ const last = k === path73.length - 1;
1142
+ let at;
1143
+ if (typeof segment === "string") {
1144
+ at = region.findIndex((t) => t.kind === "key" && t.col === base && t.name === segment);
1145
+ if (at < 0) return `no block key "${segment}" at ${path73.slice(0, k).join(".") || "the document root"}`;
1146
+ if (last) return region[at].valueStart !== void 0 ? region[at] : `"${segment}" holds no inline scalar`;
1147
+ } else {
1148
+ const items = region.map((t, i) => ({ t, i })).filter(({ t }) => t.kind === "item" && t.col === base);
1149
+ if (segment >= items.length) return `no block sequence item ${segment} at ${path73.slice(0, k).join(".")}`;
1150
+ at = items[segment].i;
1151
+ if (last) {
1152
+ const value = region[at + 1];
1153
+ return value && value.kind === "scalar" && value.line === region[at].line ? value : `item ${segment} holds no inline scalar`;
1154
+ }
1155
+ }
1156
+ const regionStart = tokens.indexOf(region[at]);
1157
+ region = childrenOf(tokens, regionStart);
1158
+ }
1159
+ return "an empty path addresses no scalar";
1160
+ }
1161
+ function readScalar(text2) {
1162
+ if (text2.startsWith("'")) {
1163
+ let i = 1;
1164
+ let value = "";
1165
+ while (i < text2.length) {
1166
+ if (text2[i] === "'") {
1167
+ if (text2[i + 1] === "'") {
1168
+ value += "'";
1169
+ i += 2;
1170
+ continue;
1171
+ }
1172
+ return { value, style: "single", end: i + 1 };
1173
+ }
1174
+ value += text2[i];
1175
+ i += 1;
1176
+ }
1177
+ return null;
1178
+ }
1179
+ if (text2.startsWith('"')) {
1180
+ const closing = /^"(?:[^"\\]|\\.)*"/.exec(text2);
1181
+ if (!closing) return null;
1182
+ try {
1183
+ return { value: JSON.parse(closing[0]), style: "double", end: closing[0].length };
1184
+ } catch {
1185
+ return null;
1186
+ }
1187
+ }
1188
+ const comment = text2.search(/\s#/);
1189
+ const body = (comment < 0 ? text2 : text2.slice(0, comment)).trimEnd();
1190
+ return { value: body, style: "plain", end: body.length };
1191
+ }
1192
+ function writeScalar(value, style) {
1193
+ if (style === "double") return JSON.stringify(value);
1194
+ if (style === "plain" && /^[A-Za-z0-9_][A-Za-z0-9_./-]*$/.test(value)) return value;
1195
+ return `'${value.replace(/'/g, "''")}'`;
1196
+ }
1197
+ function setAt(document, path73, value) {
1198
+ let node = document;
1199
+ for (const segment of path73.slice(0, -1)) node = node[segment];
1200
+ node[path73[path73.length - 1]] = value;
1201
+ }
1202
+ function rewriteScalarsInText(text2, edits, root) {
1203
+ const refuse = (why) => {
1204
+ throw new WaironError(`Cannot rewrite .wai/project.yaml at ${root} without disturbing its formatting: ${why}. Nothing was written.`);
1205
+ };
1206
+ const lines = text2.split(/(?<=\n)/);
1207
+ const tokens = tokenize(lines);
1208
+ const expected = parseYaml(text2);
1209
+ for (const edit of edits) {
1210
+ const where = edit.path.join(".");
1211
+ const token = locateScalar(tokens, edit.path);
1212
+ if (typeof token === "string") refuse(`${where}: ${token}`);
1213
+ const { line: line2, valueStart } = token;
1214
+ const content = lines[line2].replace(/\r?\n$/, "");
1215
+ const ending = lines[line2].slice(content.length);
1216
+ const scalar = readScalar(content.slice(valueStart));
1217
+ if (!scalar) refuse(`${where}: the scalar there is not written on one line`);
1218
+ if (scalar.value !== edit.from) refuse(`${where} holds "${scalar.value}", not "${edit.from}"`);
1219
+ lines[line2] = content.slice(0, valueStart) + writeScalar(edit.to, scalar.style) + content.slice(valueStart + scalar.end) + ending;
1220
+ setAt(expected, edit.path, edit.to);
1221
+ }
1222
+ const next = lines.join("");
1223
+ if (JSON.stringify(parseYaml(next)) !== JSON.stringify(expected)) {
1224
+ refuse("the edited text does not read back as exactly the intended change");
1225
+ }
1226
+ return next;
1032
1227
  }
1033
1228
  function parseConfig(document, root) {
1034
1229
  try {
@@ -1170,6 +1365,35 @@ function registryOver(store, root) {
1170
1365
  ...config,
1171
1366
  extensions: { ...config.extensions, packs: [...packsOf(config), ...selections], useGlobalPacks: false }
1172
1367
  });
1368
+ },
1369
+ rekeyCarried(rename, dryRun = false) {
1370
+ const groups = store.readField("rules.conformance.carried");
1371
+ if (!Array.isArray(groups)) return [];
1372
+ const edits = [];
1373
+ const rekeys = [];
1374
+ groups.forEach((group, g) => {
1375
+ const findings = group?.findings;
1376
+ if (!Array.isArray(findings)) return;
1377
+ findings.forEach((finding, f) => {
1378
+ const entry = finding;
1379
+ if (!entry || typeof entry.spec !== "string") return;
1380
+ const at = typeof entry.at === "string" ? entry.at : void 0;
1381
+ const covers = Array.isArray(entry.covers) ? entry.covers.filter((u) => typeof u === "string") : void 0;
1382
+ const next = rekeyAnchor(rename, { spec: entry.spec, at, covers });
1383
+ const base = ["rules", "conformance", "carried", g, "findings", f];
1384
+ const named = { code: String(entry.code), spec: entry.spec, at: at ?? "" };
1385
+ const change = (path73, field, from, to) => {
1386
+ if (from === to) return;
1387
+ edits.push({ path: [...base, ...path73], from, to });
1388
+ rekeys.push({ ...named, field, from, to });
1389
+ };
1390
+ change(["spec"], "spec", entry.spec, next.spec);
1391
+ if (at !== void 0) change(["at"], "at", at, next.at);
1392
+ (covers ?? []).forEach((unit, u) => change(["covers", u], "covers", unit, next.covers[u]));
1393
+ });
1394
+ });
1395
+ store.rewriteScalars(edits, dryRun);
1396
+ return rekeys;
1173
1397
  }
1174
1398
  };
1175
1399
  }
@@ -1254,13 +1478,16 @@ function projectConfigRepositoryOver(adapter, rootDir) {
1254
1478
  },
1255
1479
  pinGlobalPacksAsSelections(selections) {
1256
1480
  registry.pinGlobalPacksAsSelections(selections);
1481
+ },
1482
+ rekeyCarried(rename, dryRun) {
1483
+ return registry.rekeyCarried(rename, dryRun);
1257
1484
  }
1258
1485
  };
1259
1486
  }
1260
1487
  function projectConfigRepositoryAt(rootDir) {
1261
1488
  return projectConfigRepositoryOver(projectConfigFsAdapterAt(rootDir), rootDir);
1262
1489
  }
1263
- var fs4, path6, import_zod4, USE_GLOBAL_PACKS_DEFAULT, bound, projectConfigRepository;
1490
+ var fs4, path6, import_zod4, KEY_RE, USE_GLOBAL_PACKS_DEFAULT, bound, projectConfigRepository;
1264
1491
  var init_project_config = __esm({
1265
1492
  "src/config/project-config.ts"() {
1266
1493
  "use strict";
@@ -1269,8 +1496,11 @@ var init_project_config = __esm({
1269
1496
  import_zod4 = require("zod");
1270
1497
  init_fs();
1271
1498
  init_yaml();
1499
+ init_fs();
1272
1500
  init_errors();
1501
+ init_identity_rename();
1273
1502
  init_project();
1503
+ KEY_RE = /^('(?:[^']|'')*'|"(?:[^"\\]|\\.)*"|[^\s'"#{}[\],&*!|>%@`][^:#]*?)\s*:(?=\s|$)/;
1274
1504
  USE_GLOBAL_PACKS_DEFAULT = ProjectConfigSchema.shape.extensions.unwrap().parse({}).useGlobalPacks;
1275
1505
  bound = () => projectConfigRepositoryAt(getProjectRoot());
1276
1506
  projectConfigRepository = {
@@ -1315,6 +1545,9 @@ var init_project_config = __esm({
1315
1545
  },
1316
1546
  pinGlobalPacksAsSelections(selections) {
1317
1547
  bound().pinGlobalPacksAsSelections(selections);
1548
+ },
1549
+ rekeyCarried(rename, dryRun) {
1550
+ return bound().rekeyCarried(rename, dryRun);
1318
1551
  }
1319
1552
  };
1320
1553
  }
@@ -9702,6 +9935,28 @@ function buildCodeIndex(model) {
9702
9935
  return out;
9703
9936
  };
9704
9937
  const declarationsAt = (path73) => namesAt(declarations, pathKey(path73), (f) => /* @__PURE__ */ new Set([...f.declaredNames, ...f.exportedNames]));
9938
+ const forwardsOf = (start2, name) => {
9939
+ const out = [];
9940
+ const seen = /* @__PURE__ */ new Set();
9941
+ const queue = [{ file: pathKey(start2), name }];
9942
+ while (queue.length) {
9943
+ const at = queue.shift();
9944
+ const key = `${at.file}#${at.name}`;
9945
+ if (seen.has(key)) continue;
9946
+ seen.add(key);
9947
+ out.push(at);
9948
+ for (const binding of facts.get(at.file)?.reexportBindings ?? []) {
9949
+ const star = binding.exported === "*";
9950
+ if (!star && binding.exported !== at.name) continue;
9951
+ if (star && at.name === "default") continue;
9952
+ const to = resolveImport(at.file, binding.from, paths);
9953
+ if (!to) continue;
9954
+ if (star && !facts.get(to)?.exportedNames.includes(at.name)) continue;
9955
+ queue.push({ file: to, name: star ? at.name : binding.local });
9956
+ }
9957
+ }
9958
+ return out;
9959
+ };
9705
9960
  return {
9706
9961
  paths,
9707
9962
  exactPaths,
@@ -9710,7 +9965,8 @@ function buildCodeIndex(model) {
9710
9965
  anchorsAt: (path73) => namesAt(anchors, pathKey(path73), (f) => /* @__PURE__ */ new Set([...f.declaredNames, ...f.exportedNames, ...f.anchoredNames])),
9711
9966
  findingAnchorsAt: (path73) => namesAt(findingAnchors, pathKey(path73), (f) => new Set(f.anchoredNames)),
9712
9967
  originOf: originOf2,
9713
- possibleOriginsOf
9968
+ possibleOriginsOf,
9969
+ forwardsOf
9714
9970
  };
9715
9971
  }
9716
9972
  function buildRealizationIndex(ctx) {
@@ -14281,6 +14537,25 @@ function resolveCallTarget(ctx, componentId, methodName) {
14281
14537
  }
14282
14538
  return { accepted, files };
14283
14539
  }
14540
+ function isTarget(pairs, target) {
14541
+ return pairs.some((p) => target.files.has(p.file) && target.accepted.has(p.name));
14542
+ }
14543
+ function invokedAs(code, site, file) {
14544
+ const scope = pathKey(site.from ?? file);
14545
+ const facts = code.factsAt(scope);
14546
+ if (!facts || facts.status !== "analyzed" || facts.analysisGrade !== "exact") return [];
14547
+ if (!site.member) {
14548
+ const binding = importBindingOf(facts, site.name);
14549
+ if (!binding || binding.namespace) return [];
14550
+ const module3 = resolveImport(scope, binding.from, code.paths);
14551
+ return module3 ? code.forwardsOf(module3, binding.imported ?? site.name) : [];
14552
+ }
14553
+ if (!site.via) return [];
14554
+ const receiver = importBindingOf(facts, site.via);
14555
+ if (!receiver?.namespace) return [];
14556
+ const module2 = resolveImport(scope, receiver.from, code.paths);
14557
+ return module2 ? code.forwardsOf(module2, site.name) : [];
14558
+ }
14284
14559
  function claimsOf(implMethod) {
14285
14560
  const claims = [];
14286
14561
  for (const step of implMethod.narrative) {
@@ -14351,6 +14626,7 @@ var init_call_conformance = __esm({
14351
14626
  if (!ctx.componentMap.has(claim.component)) continue;
14352
14627
  const target = resolveCallTarget(ctx, claim.component, claim.method);
14353
14628
  if (target.accepted.has(fnSymbol)) continue;
14629
+ if (isTarget(code.forwardsOf(file, fnSymbol), target)) continue;
14354
14630
  const matching = sites.filter((s) => target.accepted.has(s.name));
14355
14631
  const ref = `${claim.component}.${claim.method}`;
14356
14632
  if (target.files.size === 0) {
@@ -14359,6 +14635,7 @@ var init_call_conformance = __esm({
14359
14635
  }
14360
14636
  continue;
14361
14637
  }
14638
+ if (sites.some((s) => !target.accepted.has(s.name) && isTarget(invokedAs(code, s, file), target))) continue;
14362
14639
  const landed = /* @__PURE__ */ new Set();
14363
14640
  let realized = false;
14364
14641
  for (const site of matching) {
@@ -17795,6 +18072,10 @@ function buildCodeModel(implementations, types, projectRoot2, sourceRoots = [],
17795
18072
  exportedNames: [...facts.exported],
17796
18073
  imports: [...facts.imports],
17797
18074
  reexports: [...facts.reexports],
18075
+ reexportBindings: [
18076
+ ...facts.namedReexports.map(({ exported, local, from }) => ({ exported, local, from })),
18077
+ ...facts.starExports.map((from) => ({ exported: "*", local: "*", from }))
18078
+ ],
17798
18079
  functionComplexity: Object.fromEntries(facts.complexity),
17799
18080
  functionCallSites: Object.fromEntries([...facts.calls].map(([k, v]) => [k, [...v.values()]])),
17800
18081
  importBindings: Object.fromEntries(facts.importBindings),
@@ -17822,6 +18103,25 @@ function buildCodeModel(implementations, types, projectRoot2, sourceRoots = [],
17822
18103
  }
17823
18104
  return { files, projectRoot: projectRoot2, rootFiles };
17824
18105
  }
18106
+ function clauseBindings(clause, specifier) {
18107
+ const out = [];
18108
+ const body = clause.replace(/^\s*type\s+/, "");
18109
+ const named = /\{([^}]*)\}/.exec(body);
18110
+ if (named) {
18111
+ for (const piece of named[1].split(",")) {
18112
+ const sides = piece.replace(/^\s*type\s+/, "").split(/\s+as\s+/).map((s) => s.trim());
18113
+ const exported = sides[0];
18114
+ const local = sides[1] ?? exported;
18115
+ if (IDENTIFIER_ONLY_RE.test(exported) && IDENTIFIER_ONLY_RE.test(local)) out.push({ exported, local, specifier });
18116
+ }
18117
+ }
18118
+ const namespace = /\*\s*as\s+([A-Za-z_$][\w$]*)/.exec(body);
18119
+ if (namespace) out.push({ exported: "*", local: namespace[1], specifier });
18120
+ const head = body.replace(/\{[^}]*\}/, "").replace(/\*\s*as\s+[A-Za-z_$][\w$]*/, "");
18121
+ const defaulted = /^\s*([A-Za-z_$][\w$]*)/.exec(head);
18122
+ if (defaulted) out.push({ exported: "default", local: defaulted[1], specifier });
18123
+ return out;
18124
+ }
17825
18125
  function readTestFiles(testRoots, projectRoot2) {
17826
18126
  const scanned = [];
17827
18127
  for (const key of walkDeclaredRoots(testRoots, [], projectRoot2)) {
@@ -17837,25 +18137,98 @@ function readTestFiles(testRoots, projectRoot2) {
17837
18137
  for (const m of text2.matchAll(NAMED_IMPORT_BINDINGS_RE)) {
17838
18138
  for (const name of boundNames(m[1] ?? "", false)) imports.add(name);
17839
18139
  }
18140
+ const code = stripComments(text2, JS_PATTERNS);
18141
+ const bindings = [];
18142
+ for (const m of code.matchAll(IMPORT_FROM_RE)) bindings.push(...clauseBindings(m[1], m[2]));
18143
+ for (const m of code.matchAll(DYNAMIC_IMPORT_RE)) bindings.push(...clauseBindings(`{${m[1].replace(/:/g, " as ")}}`, m[2]));
17840
18144
  const words2 = /* @__PURE__ */ new Set();
17841
18145
  for (const m of text2.matchAll(IDENTIFIER_RE)) words2.add(m[0]);
17842
- scanned.push({ path: key, imports, words: words2 });
18146
+ scanned.push({ path: key, imports, bindings, words: words2 });
17843
18147
  }
17844
18148
  return scanned;
17845
18149
  }
18150
+ function reexportReader() {
18151
+ const cache2 = /* @__PURE__ */ new Map();
18152
+ return (file) => {
18153
+ const cached = cache2.get(file);
18154
+ if (cached) return cached;
18155
+ const found = { named: [], stars: [] };
18156
+ try {
18157
+ const code = stripComments(fs12.readFileSync(file, "utf8"), JS_PATTERNS);
18158
+ for (const m of code.matchAll(NAMED_REEXPORT_RE)) {
18159
+ for (const piece of m[1].split(",")) {
18160
+ const sides = piece.replace(/^\s*type\s+/, "").split(/\s+as\s+/).map((s) => s.trim());
18161
+ if (!IDENTIFIER_ONLY_RE.test(sides[0] ?? "")) continue;
18162
+ found.named.push({ local: sides[0], exported: sides[1] ?? sides[0], specifier: m[2] });
18163
+ }
18164
+ }
18165
+ for (const m of code.matchAll(STAR_REEXPORT_RE)) found.stars.push(m[1]);
18166
+ } catch {
18167
+ }
18168
+ cache2.set(file, found);
18169
+ return found;
18170
+ };
18171
+ }
18172
+ function publishesFrom(module2, name, target, symbol, readReexports, visited = /* @__PURE__ */ new Set()) {
18173
+ if (path15.resolve(module2) === target && name === symbol) return true;
18174
+ const key = `${module2}#${name}`;
18175
+ if (visited.has(key)) return false;
18176
+ visited.add(key);
18177
+ const reexports = readReexports(module2);
18178
+ for (const re of reexports.named) {
18179
+ if (re.exported !== name) continue;
18180
+ const next = resolveRelativeModule(module2, re.specifier);
18181
+ if (next && publishesFrom(next, re.local, target, symbol, readReexports, visited)) return true;
18182
+ }
18183
+ if (name === "default") return false;
18184
+ for (const specifier of reexports.stars) {
18185
+ const next = resolveRelativeModule(module2, specifier);
18186
+ if (next && publishesFrom(next, name, target, symbol, readReexports, visited)) return true;
18187
+ }
18188
+ return false;
18189
+ }
17846
18190
  function findTestsReferencing(methods, projectRoot2, testRoots = []) {
17847
18191
  const scanned = readTestFiles(testRoots, projectRoot2);
17848
18192
  if (scanned.length === 0) return [];
17849
18193
  const indiscriminateAbove = scanned.length / 10;
17850
- const found = [];
18194
+ const searches = [];
17851
18195
  for (const method2 of methods) {
17852
18196
  const symbol = method2.symbol ?? method2.name;
17853
- const imported = scanned.filter((f) => f.imports.has(symbol)).map((f) => f.path);
17854
- const mentioned = scanned.filter((f) => !f.imports.has(symbol) && f.words.has(symbol)).map((f) => f.path);
18197
+ let search = searches.find((s) => s.method === method2.name && s.symbol === symbol);
18198
+ if (!search) {
18199
+ search = { method: method2.name, symbol, files: [] };
18200
+ searches.push(search);
18201
+ }
18202
+ const file = method2.sourcePath ? path15.resolve(projectRoot2, method2.sourcePath) : void 0;
18203
+ if (file && !search.files.includes(file)) search.files.push(file);
18204
+ }
18205
+ const readReexports = reexportReader();
18206
+ const found = [];
18207
+ for (const { method: method2, symbol, files } of searches) {
18208
+ const imported = [];
18209
+ const mentioned = [];
18210
+ for (const test of scanned) {
18211
+ if (files.length === 0) {
18212
+ if (test.imports.has(symbol)) imported.push(test.path);
18213
+ else if (test.words.has(symbol)) mentioned.push(test.path);
18214
+ continue;
18215
+ }
18216
+ const testFile = path15.resolve(projectRoot2, test.path);
18217
+ const fromMethod = (binding, name) => {
18218
+ const module2 = resolveRelativeModule(testFile, binding.specifier);
18219
+ return module2 !== null && files.some((target) => publishesFrom(module2, name, target, symbol, readReexports));
18220
+ };
18221
+ const naming = test.bindings.filter((b) => b.exported === symbol || b.local === symbol);
18222
+ if (test.bindings.some((b) => b.exported !== "*" && fromMethod(b, b.exported)) || test.words.has(symbol) && test.bindings.some((b) => b.exported === "*" && fromMethod(b, symbol))) {
18223
+ imported.push(test.path);
18224
+ } else if (naming.length === 0 && test.words.has(symbol)) {
18225
+ mentioned.push(test.path);
18226
+ }
18227
+ }
17855
18228
  const indiscriminate = mentioned.length > indiscriminateAbove;
17856
18229
  if (imported.length === 0 && mentioned.length === 0) continue;
17857
18230
  found.push({
17858
- method: method2.name,
18231
+ method: method2,
17859
18232
  symbol,
17860
18233
  imported,
17861
18234
  mentioned: indiscriminate ? [] : mentioned,
@@ -17864,7 +18237,7 @@ function findTestsReferencing(methods, projectRoot2, testRoots = []) {
17864
18237
  }
17865
18238
  return found;
17866
18239
  }
17867
- var fs12, path15, import_module2, EXTENSION_LANGUAGE, C_FAMILY_COMMENTS, NAMED_IMPORT_BINDINGS_RE, JS_PATTERNS, LANGUAGE_PATTERNS, PATTERN_ANALYSIS_MAX_BYTES, IDENTIFIER_RE, STRING_RE, tsResolutionCache, TS_RESOLUTION_RETRY_MS;
18240
+ var fs12, path15, import_module2, EXTENSION_LANGUAGE, C_FAMILY_COMMENTS, NAMED_IMPORT_BINDINGS_RE, JS_PATTERNS, LANGUAGE_PATTERNS, PATTERN_ANALYSIS_MAX_BYTES, IDENTIFIER_RE, STRING_RE, tsResolutionCache, TS_RESOLUTION_RETRY_MS, IMPORT_FROM_RE, DYNAMIC_IMPORT_RE, IDENTIFIER_ONLY_RE, NAMED_REEXPORT_RE, STAR_REEXPORT_RE;
17868
18241
  var init_source_analysis = __esm({
17869
18242
  "src/core/source-analysis.ts"() {
17870
18243
  "use strict";
@@ -17997,6 +18370,11 @@ var init_source_analysis = __esm({
17997
18370
  STRING_RE = /'([^'\\\n]*(?:\\.[^'\\\n]*)*)'|"([^"\\\n]*(?:\\.[^"\\\n]*)*)"|`([^`\\]*(?:\\.[^`\\]*)*)`/g;
17998
18371
  tsResolutionCache = /* @__PURE__ */ new Map();
17999
18372
  TS_RESOLUTION_RETRY_MS = 3e4;
18373
+ IMPORT_FROM_RE = /(?:^|;)[ \t]*import\s+(?!\()([^'";]*?)\s*from\s*['"]([^'"]+)['"]/gm;
18374
+ DYNAMIC_IMPORT_RE = /\{([^{}]*)\}\s*=\s*await\s+import\(\s*['"]([^'"]+)['"]\s*\)/g;
18375
+ IDENTIFIER_ONLY_RE = /^[A-Za-z_$][\w$]*$/;
18376
+ NAMED_REEXPORT_RE = /\bexport\s+(?:type\s+)?\{([^}]*)\}\s*from\s*['"]([^'"]+)['"]/g;
18377
+ STAR_REEXPORT_RE = /\bexport\s+\*\s+from\s*['"]([^'"]+)['"]/g;
18000
18378
  }
18001
18379
  });
18002
18380
 
@@ -20496,6 +20874,15 @@ function moveChangeReports(edits, dryRun) {
20496
20874
  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"}.`
20497
20875
  }));
20498
20876
  }
20877
+ function moveRename(from, to, methods, plan) {
20878
+ return {
20879
+ specs: plan.rehomed.map(({ from: left, to: arrived }) => ({ from: left, to: arrived, sites: [...methods] })),
20880
+ methods: methods.map((method2) => ({ component: from, method: method2, toComponent: to, toMethod: method2 }))
20881
+ };
20882
+ }
20883
+ function registerClause(carried, verb) {
20884
+ return carried.length ? `, ${verb} ${carried.length} debt-register key${carried.length === 1 ? "" : "s"}` : "";
20885
+ }
20499
20886
  function createdClause(created, verb) {
20500
20887
  return created.length ? `, ${verb} ${created.join(" and ")}` : "";
20501
20888
  }
@@ -20751,6 +21138,7 @@ var init_specs2 = __esm({
20751
21138
  init_lockfile();
20752
21139
  init_yaml();
20753
21140
  init_spec_files();
21141
+ init_identity_rename();
20754
21142
  init_models();
20755
21143
  init_narrative_labels();
20756
21144
  init_diagram();
@@ -22710,6 +23098,8 @@ var init_specs2 = __esm({
22710
23098
  const plan = this.planMethodMove(source, to, methods, contracts);
22711
23099
  const repointed = this.repointToNewHome(from, to, methods, plan);
22712
23100
  const mentions = this.moveMentions(from, plan);
23101
+ const rename = moveRename(from, to, methods, plan);
23102
+ this.rekeyMoveAllows(plan, rename);
22713
23103
  if (hooks) {
22714
23104
  const verdict = judgeMoveResult(hooks, plan);
22715
23105
  plan.notices.push(...verdict.notices);
@@ -22728,10 +23118,12 @@ var init_specs2 = __esm({
22728
23118
  summary: refusedMoveSummary(from, to, methods, verdict.refusals, alternatives),
22729
23119
  dryRun,
22730
23120
  mentions,
22731
- created: []
23121
+ created: [],
23122
+ carried: []
22732
23123
  };
22733
23124
  }
22734
23125
  }
23126
+ const carried = projectConfigRepository.rekeyCarried(rename, true);
22735
23127
  if (dryRun) {
22736
23128
  const edits2 = moveChangeReports([...plan.edits.values()], true);
22737
23129
  return {
@@ -22744,13 +23136,16 @@ var init_specs2 = __esm({
22744
23136
  refusals: [],
22745
23137
  alternatives: [],
22746
23138
  notices: plan.notices,
22747
- 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.`,
23139
+ 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.`,
22748
23140
  dryRun: true,
22749
23141
  mentions,
22750
- created: plan.created
23142
+ created: plan.created,
23143
+ carried
22751
23144
  };
22752
23145
  }
22753
- const edits = this.writeMoveEdits([...plan.edits.values()]);
23146
+ const edits = this.writeMoveEdits([...plan.edits.values()], () => {
23147
+ projectConfigRepository.rekeyCarried(rename);
23148
+ });
22754
23149
  return {
22755
23150
  moved: true,
22756
23151
  from,
@@ -22761,12 +23156,49 @@ var init_specs2 = __esm({
22761
23156
  refusals: [],
22762
23157
  alternatives: [],
22763
23158
  notices: plan.notices,
22764
- 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` : ""}.`,
23159
+ 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` : ""}.`,
22765
23160
  dryRun: false,
22766
23161
  mentions,
22767
- created: plan.created
23162
+ created: plan.created,
23163
+ carried
22768
23164
  };
22769
23165
  }
23166
+ /**
23167
+ * Rekey every lint allow keyed on a moved method, staging each spec it
23168
+ * changes like any other reference the move follows. An allow anchored at a
23169
+ * moved method on a spec the method left moves to the spec it arrived in; a
23170
+ * covered `<from>.<method>` unit anywhere names `<to>.<method>`. An allow
23171
+ * whose new home the plan does not stage stays where it is.
23172
+ */
23173
+ rekeyMoveAllows(plan, rename) {
23174
+ const subjects = [...this.everySpecInTree()];
23175
+ for (const edit of plan.edits.values()) if (edit.before === null) subjects.push({ kind: edit.kind, id: edit.id, spec: edit.after });
23176
+ const stagedAfter = (kind, id) => plan.edits.get(`${kind}:${id}`)?.after;
23177
+ for (const { kind, id, spec } of subjects) {
23178
+ if (id.includes("::")) continue;
23179
+ const allows = spec?.lint?.allow ?? [];
23180
+ if (allows.length === 0) continue;
23181
+ const staged = plan.edits.get(`${kind}:${id}`);
23182
+ const subject = staged ? staged.after : cloneSpec(spec);
23183
+ const kept = [];
23184
+ let changed = false;
23185
+ for (const allow of subject.lint?.allow ?? []) {
23186
+ const next = rekeyAnchor(rename, { spec: id, ...allow.at !== void 0 ? { at: allow.at } : {}, ...allow.covers ? { covers: allow.covers } : {} });
23187
+ const rekeyed = { ...allow, ...next.at !== void 0 ? { at: next.at } : {}, ...next.covers ? { covers: next.covers } : {} };
23188
+ if (JSON.stringify(rekeyed) !== JSON.stringify(allow)) changed = true;
23189
+ const home = next.spec !== id ? stagedAfter(kind, next.spec) : void 0;
23190
+ if (home) {
23191
+ home.lint = { ...home.lint ?? {}, allow: [...home.lint?.allow ?? [], rekeyed] };
23192
+ changed = true;
23193
+ continue;
23194
+ }
23195
+ kept.push(rekeyed);
23196
+ }
23197
+ if (!changed) continue;
23198
+ subject.lint = { ...subject.lint, allow: kept };
23199
+ if (!staged) this.stageMoveEdit(plan, kind, id, spec, subject);
23200
+ }
23201
+ }
22770
23202
  /**
22771
23203
  * Why this move cannot be attempted at all, or null when the request is
22772
23204
  * answerable. Every check here is about what the CALLER named, so its answer
@@ -22839,8 +23271,10 @@ var init_specs2 = __esm({
22839
23271
  newDependencies: [],
22840
23272
  wireBound: [],
22841
23273
  notices: [],
22842
- created: []
23274
+ created: [],
23275
+ rehomed: []
22843
23276
  };
23277
+ const leftImplementations = [];
22844
23278
  const receiving = stated ?? createdContract(target, source.id, methods, contracts);
22845
23279
  const arriving = [];
22846
23280
  const travelling = [];
@@ -22852,6 +23286,7 @@ var init_specs2 = __esm({
22852
23286
  const remaining = cloneSpec(contract);
22853
23287
  remaining.methods = remaining.methods.filter((m) => !methods.includes(m.name));
22854
23288
  this.stageMoveEdit(plan, "interface", contract.id, contract, remaining);
23289
+ plan.rehomed.push({ from: contract.id, to: receiving.id });
22855
23290
  if (contract.methods.length === leaving.length) {
22856
23291
  plan.notices.push(`"${contract.id}" is left with no methods \u2014 "${source.id}" no longer declares anything on it.`);
22857
23292
  }
@@ -22863,6 +23298,7 @@ var init_specs2 = __esm({
22863
23298
  const kept = cloneSpec(impl);
22864
23299
  kept.methods = kept.methods.filter((m) => !methods.includes(m.name));
22865
23300
  this.stageMoveEdit(plan, "implementation", impl.id, impl, kept);
23301
+ leftImplementations.push(impl.id);
22866
23302
  }
22867
23303
  }
22868
23304
  this.stageMoveEdit(plan, "interface", receiving.id, stated, {
@@ -22891,9 +23327,11 @@ var init_specs2 = __esm({
22891
23327
  ...cloneSpec(targetImpl),
22892
23328
  methods: [...cloneSpec(targetImpl.methods), ...travelling]
22893
23329
  });
23330
+ for (const id of leftImplementations) plan.rehomed.push({ from: id, to: targetImpl.id });
22894
23331
  } else if (travelling.length > 0) {
22895
23332
  const created = createdImplementation(target, receiving.id, source.id, travelling, implementations, contracts);
22896
23333
  this.stageMoveEdit(plan, "implementation", created.spec.id, null, created.spec);
23334
+ for (const id of leftImplementations) plan.rehomed.push({ from: id, to: created.spec.id });
22897
23335
  plan.created.push(created.named);
22898
23336
  plan.notices.push(...created.notices);
22899
23337
  }
@@ -23023,7 +23461,7 @@ var init_specs2 = __esm({
23023
23461
  * here: every save does it already, and a second call would only be a hop
23024
23462
  * across a component boundary that no narrative claims.
23025
23463
  */
23026
- writeMoveEdits(edits) {
23464
+ writeMoveEdits(edits, finish) {
23027
23465
  const reports = moveChangeReports(edits, false);
23028
23466
  const written = [];
23029
23467
  try {
@@ -23032,6 +23470,7 @@ var init_specs2 = __esm({
23032
23470
  report2.notices.push(...this.saveMovedSpec(edit.kind, edit.after));
23033
23471
  written.push(edit);
23034
23472
  }
23473
+ finish?.();
23035
23474
  } catch (e) {
23036
23475
  for (const edit of [...written].reverse()) {
23037
23476
  try {
@@ -23544,6 +23983,42 @@ function rewriteRefFields(specsDir, remap, excludeDir) {
23544
23983
  }
23545
23984
  return rewritten;
23546
23985
  }
23986
+ function rekeyLintAllows(specsDir, rename) {
23987
+ const rewritten = [];
23988
+ for (const file of listFilesRecursive(specsDir, ".yaml")) {
23989
+ let raw;
23990
+ try {
23991
+ raw = readYamlFile(file);
23992
+ } catch {
23993
+ continue;
23994
+ }
23995
+ const allows = raw?.lint?.allow;
23996
+ const kind = raw && typeof raw === "object" ? specKind(raw) : null;
23997
+ if (!kind || !Array.isArray(allows)) continue;
23998
+ const specId = kind === "system" ? "system" : String(raw.id);
23999
+ let changed = false;
24000
+ for (const allow of allows) {
24001
+ if (!allow || typeof allow !== "object") continue;
24002
+ const next = rekeyAnchor(rename, {
24003
+ spec: specId,
24004
+ ...typeof allow.at === "string" ? { at: allow.at } : {},
24005
+ ...Array.isArray(allow.covers) ? { covers: allow.covers } : {}
24006
+ });
24007
+ if (next.at !== void 0 && next.at !== allow.at) {
24008
+ allow.at = next.at;
24009
+ changed = true;
24010
+ }
24011
+ if (next.covers && JSON.stringify(next.covers) !== JSON.stringify(allow.covers)) {
24012
+ allow.covers = next.covers;
24013
+ changed = true;
24014
+ }
24015
+ }
24016
+ if (!changed) continue;
24017
+ writeYamlFile(file, raw);
24018
+ rewritten.push({ kind, id: specId });
24019
+ }
24020
+ return rewritten;
24021
+ }
23547
24022
  function rebaseImplementationPaths(specsDir, fromRoot, toRoot) {
23548
24023
  for (const file of listFilesRecursive(specsDir, ".yaml")) {
23549
24024
  let raw;
@@ -23613,24 +24088,32 @@ function renameComponent(componentId, newId) {
23613
24088
  ...movingInterface ? [{ kind: "interface", from: movingInterface.id, to: interfaceId }] : [],
23614
24089
  ...movingImplementation ? [{ kind: "implementation", from: movingImplementation.id, to: implementationId }] : []
23615
24090
  ];
23616
- const rewritten = rewriteRefFields(aiPathsAt(getProjectRoot()).specsDir(), (ref, position) => {
24091
+ const rename = {
24092
+ specs: renamed.map(({ from, to }) => ({ from, to })),
24093
+ components: [{ from: componentId, to: newId }]
24094
+ };
24095
+ projectConfigRepository.rekeyCarried(rename, true);
24096
+ const rewrittenRefs = rewriteRefFields(aiPathsAt(getProjectRoot()).specsDir(), (ref, position) => {
23617
24097
  if (position === "component" || position === "entity-class" || position === "auth-source") {
23618
24098
  return ref === componentId ? newId : ref;
23619
24099
  }
23620
24100
  if (position === "interface" && ref === movingInterface?.id) return interfaceId;
23621
24101
  return ref;
23622
- }).filter((spec) => !renamed.some((moved) => moved.kind === spec.kind && moved.from === spec.id)).map((spec) => spec.id);
24102
+ });
24103
+ const allowsRekeyed = rekeyLintAllows(aiPathsAt(getProjectRoot()).specsDir(), rename);
24104
+ const rewritten = [...new Set([...rewrittenRefs, ...allowsRekeyed].filter((spec) => !renamed.some((moved) => moved.kind === spec.kind && moved.from === spec.id)).map((spec) => spec.id))];
23623
24105
  invalidateSpecCache();
23624
24106
  moveRenamedSpecs(componentId, newId, component, movingInterface, movingImplementation);
23625
- return { renamed, rewritten };
24107
+ const carried = projectConfigRepository.rekeyCarried(rename);
24108
+ return { renamed, rewritten, carried };
23626
24109
  }
23627
24110
  function moveRenamedSpecs(componentId, newId, component, movingInterface, movingImplementation) {
23628
24111
  const interfaceId = `i${newId}`;
23629
24112
  const implementationId = `${newId}_impl`;
23630
- const movedComponent = loadComponentSpec(componentId) ?? component;
24113
+ const movedComponent2 = loadComponentSpec(componentId) ?? component;
23631
24114
  const movedInterface = movingInterface ? loadInterfaceSpec(movingInterface.id) ?? movingInterface : null;
23632
24115
  const movedImplementation = movingImplementation ? loadImplementationSpec(movingImplementation.id) ?? movingImplementation : null;
23633
- saveComponentSpec({ ...movedComponent, id: newId });
24116
+ saveComponentSpec({ ...movedComponent2, id: newId });
23634
24117
  if (movedInterface) saveInterfaceSpec({ ...movedInterface, id: interfaceId, component: newId });
23635
24118
  if (movedImplementation) {
23636
24119
  const oldFile = getImplementationPath(movedImplementation.id);
@@ -23683,6 +24166,19 @@ function renameMethod(componentId, methodName, newName, pinSymbol) {
23683
24166
  const implementations = loadImplementationSpecs();
23684
24167
  const movingContracts = new Set(moving.map((i) => i.id));
23685
24168
  const renamed = [];
24169
+ const rename = {
24170
+ methods: [{
24171
+ component: componentId,
24172
+ method: methodName,
24173
+ toComponent: componentId,
24174
+ toMethod: newName,
24175
+ specs: [
24176
+ ...moving.map((i) => i.id),
24177
+ ...implementations.filter((impl) => movingContracts.has(impl.contract) && impl.methods.some((m) => m.name === methodName)).map((impl) => impl.id)
24178
+ ]
24179
+ }]
24180
+ };
24181
+ projectConfigRepository.rekeyCarried(rename, true);
23686
24182
  for (const contract of moving) {
23687
24183
  saveInterfaceSpec({
23688
24184
  ...contract,
@@ -23705,8 +24201,11 @@ function renameMethod(componentId, methodName, newName, pinSymbol) {
23705
24201
  }
23706
24202
  invalidateSpecCache();
23707
24203
  const specsDir = aiPathsAt(getProjectRoot()).specsDir();
23708
- const rewritten = rewriteRefFields(specsDir, (ref, position, owner) => position === "method" && owner === componentId && ref === methodName ? newName : ref).map((spec) => spec.id);
24204
+ const retargeted = rewriteRefFields(specsDir, (ref, position, owner) => position === "method" && owner === componentId && ref === methodName ? newName : ref);
24205
+ const allowsRekeyed = rekeyLintAllows(specsDir, rename);
24206
+ const rewritten = [...new Set([...retargeted, ...allowsRekeyed].map((spec) => spec.id))];
23709
24207
  const mentions = collectMentions(specsDir, methodName, moving);
24208
+ const carried = projectConfigRepository.rekeyCarried(rename);
23710
24209
  return {
23711
24210
  component: componentId,
23712
24211
  from: methodName,
@@ -23714,7 +24213,8 @@ function renameMethod(componentId, methodName, newName, pinSymbol) {
23714
24213
  renamed,
23715
24214
  rewritten,
23716
24215
  mentions,
23717
- ...pinnedSymbol ? { pinnedSymbol } : {}
24216
+ ...pinnedSymbol ? { pinnedSymbol } : {},
24217
+ carried
23718
24218
  };
23719
24219
  }
23720
24220
  function renameInSignature(signature, from, to) {
@@ -23764,6 +24264,7 @@ var init_provision = __esm({
23764
24264
  init_fs();
23765
24265
  init_yaml();
23766
24266
  init_errors();
24267
+ init_identity_rename();
23767
24268
  init_models();
23768
24269
  METHOD_NAME = /^[a-z][a-zA-Z0-9]*$/;
23769
24270
  PROSE_FIELDS2 = /* @__PURE__ */ new Set([
@@ -29723,9 +30224,10 @@ function candidateOptions() {
29723
30224
  function noticesFrom(verdict) {
29724
30225
  return verdict.warnings.map((w) => `${w.code}: ${w.message}`);
29725
30226
  }
29726
- function componentCandidateGate(options = candidateOptions()) {
30227
+ function componentCandidateGate(options = candidateOptions(), storedOwner) {
29727
30228
  return {
29728
30229
  gate: (kind, merged) => {
30230
+ refuseUnknownOwner(kind, merged, storedOwner);
29729
30231
  if (kind !== "component") return;
29730
30232
  const verdict = validateComponentCandidate(merged, options);
29731
30233
  if (verdict.errors.length) throw new Error(formatCandidateRefusal(verdict));
@@ -29733,6 +30235,14 @@ function componentCandidateGate(options = candidateOptions()) {
29733
30235
  }
29734
30236
  };
29735
30237
  }
30238
+ function refuseUnknownOwner(kind, merged, storedOwner) {
30239
+ if (kind !== "component" && kind !== "type") return;
30240
+ const owner = merged.subsystem;
30241
+ if (!owner || owner === storedOwner) return;
30242
+ if (loadSpec("subsystem", owner)) return;
30243
+ const sentence = MISSING_PARENT[kind](owner);
30244
+ throw new Error(`${sentence} Nothing was written.`);
30245
+ }
29736
30246
  function restatementParent(restatement) {
29737
30247
  const spec = restatement.spec;
29738
30248
  switch (restatement.kind) {
@@ -29744,6 +30254,8 @@ function restatementParent(restatement) {
29744
30254
  return { kind: "component", id: spec.component };
29745
30255
  case "implementation":
29746
30256
  return { kind: "interface", id: spec.contract };
30257
+ case "type":
30258
+ return typeof spec.subsystem === "string" && spec.subsystem !== "" ? { kind: "subsystem", id: spec.subsystem } : null;
29747
30259
  default:
29748
30260
  return null;
29749
30261
  }
@@ -29934,7 +30446,7 @@ function writeSpec(restatement) {
29934
30446
  if (application.refusal !== void 0) throw new Error(application.refusal);
29935
30447
  const gateNotices = restatement.kind === "component" ? judgeComponent(application.spec, bound2) : [];
29936
30448
  const persisted = persistCandidate(restatement.kind, application.spec);
29937
- const testsToRevisit = testsInvalidatedBy(application.changedMethods, [existing, application.spec], bound2);
30449
+ const testsToRevisit = testsInvalidatedBy(restatement.kind, id, application.changedMethods, [existing, application.spec], bound2);
29938
30450
  return {
29939
30451
  kind: restatement.kind,
29940
30452
  id,
@@ -29959,42 +30471,59 @@ function persistCandidate(kind, spec) {
29959
30471
  }
29960
30472
  return { notices: saveSpec(kind, spec) };
29961
30473
  }
29962
- function testsInvalidatedBy(methods, specs, options) {
30474
+ function testsInvalidatedBy(kind, id, methods, specs, options, written = []) {
29963
30475
  const testRoots = options.rules?.conformance?.testRoots ?? [];
29964
30476
  if (methods.length === 0 || testRoots.length === 0) return [];
29965
- const symbolOf = (name) => specs.map((s) => (s?.methods ?? []).find((m) => m.name === name)?.symbol).find((symbol) => symbol !== void 0);
29966
- return findTestsReferencing2(methods.map((name) => ({ name, symbol: symbolOf(name) })), getProjectRoot(), testRoots);
30477
+ return findTestsReferencing2(searchedMethods(kind, id, methods, specs, written), getProjectRoot(), testRoots);
30478
+ }
30479
+ function searchedMethods(kind, id, names, specs, written) {
30480
+ const stated = (name) => written.find((m) => m?.name === name)?.symbol;
30481
+ if (kind === "interface") {
30482
+ const realizations = loadImplementationSpecs().filter((impl) => impl.contract === id);
30483
+ return names.flatMap((name) => {
30484
+ 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 })));
30485
+ return found.length > 0 ? found : [{ name, symbol: stated(name) }];
30486
+ });
30487
+ }
30488
+ return names.map((name) => {
30489
+ const holders = specs.map((spec) => {
30490
+ const holder = spec;
30491
+ const method2 = holder?.methods?.find((m) => m.name === name);
30492
+ return method2 ? { method: method2, file: method2.sourcePath ?? holder?.sourcePath } : null;
30493
+ }).filter((h) => h !== null);
30494
+ const symbol = stated(name) ?? holders.map((h) => h.method.symbol).find((s) => s !== void 0);
30495
+ const sourcePath = holders.map((h) => h.file).find((f) => f !== void 0) ?? specs.map((spec) => spec?.sourcePath).find((f) => f !== void 0);
30496
+ return { name, symbol, sourcePath };
30497
+ });
29967
30498
  }
29968
30499
  function deleteSpec2(kind, id) {
29969
30500
  const bound2 = candidateOptions();
29970
30501
  const stored = loadSpec(kind, id);
29971
30502
  const deleted = deleteSpec(kind, id);
29972
30503
  const methods = deleted ? (stored?.methods ?? []).map((m) => m.name) : [];
29973
- const testsToRevisit = testsInvalidatedBy(methods, [stored], bound2);
30504
+ const testsToRevisit = testsInvalidatedBy(kind, id, methods, [stored], bound2);
29974
30505
  return { kind, id, deleted, testsToRevisit };
29975
30506
  }
29976
30507
  function updateSpecGated(kind, id, delta, dryRun) {
29977
30508
  const bound2 = candidateOptions();
29978
- const gate = componentCandidateGate(bound2);
29979
- const report2 = updateSpec(kind, id, delta, gate, dryRun);
29980
30509
  const testRoots = bound2.rules?.conformance?.testRoots ?? [];
29981
- const changed = changedMethods(report2, delta);
30510
+ const stored = loadSpec(kind, id);
30511
+ const gate = componentCandidateGate(bound2, stored?.subsystem);
30512
+ const report2 = updateSpec(kind, id, delta, gate, dryRun);
30513
+ const changed = changedMethods(report2);
29982
30514
  if (changed.length > 0 && testRoots.length > 0) {
29983
- report2.testsToRevisit = findTestsReferencing2(changed, getProjectRoot(), testRoots);
30515
+ const written = Array.isArray(delta?.methods) ? delta.methods : [];
30516
+ report2.testsToRevisit = testsInvalidatedBy(kind, id, changed, [stored], bound2, written);
29984
30517
  }
29985
30518
  return report2;
29986
30519
  }
29987
- function changedMethods(report2, delta) {
30520
+ function changedMethods(report2) {
29988
30521
  const named = /* @__PURE__ */ new Set();
29989
30522
  for (const change of report2.changes) {
29990
30523
  const [field, name] = change.path.split(".");
29991
30524
  if (field === "methods" && name) named.add(name);
29992
30525
  }
29993
- const written = Array.isArray(delta?.methods) ? delta.methods : [];
29994
- return [...named].map((name) => ({
29995
- name,
29996
- symbol: written.find((m) => m?.name === name)?.symbol
29997
- }));
30526
+ return [...named];
29998
30527
  }
29999
30528
  function methodMoveGate(source) {
30000
30529
  const bound2 = candidateOptions();
@@ -30040,7 +30569,8 @@ var init_authoring = __esm({
30040
30569
  subsystem: () => "System spec must be initialized (sdd_initialize_system) first.",
30041
30570
  component: (id) => `Parent subsystem "${id}" does not exist.`,
30042
30571
  interface: (id) => `Component "${id}" does not exist.`,
30043
- implementation: (id) => `Interface contract "${id}" does not exist.`
30572
+ implementation: (id) => `Interface contract "${id}" does not exist.`,
30573
+ type: (id) => `Owning subsystem "${id}" does not exist \u2014 name a subsystem the tree has, or omit subsystem for a system-level value object.`
30044
30574
  };
30045
30575
  REWRITE_LABEL = {
30046
30576
  system: "System spec",
@@ -30891,7 +31421,7 @@ ${renderChangeReport(report2)}`,
30891
31421
  server,
30892
31422
  "sdd_rename_component",
30893
31423
  {
30894
- 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.",
31424
+ 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.",
30895
31425
  inputSchema: {
30896
31426
  id: import_zod11.z.string().describe("The component to rename (namespaced if needed)"),
30897
31427
  newId: import_zod11.z.string().describe("Its new id: a lowercase identifier with no namespace separator")
@@ -30909,7 +31439,7 @@ ${renderChangeReport(report2)}`,
30909
31439
  server,
30910
31440
  "sdd_rename_method",
30911
31441
  {
30912
- 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.",
31442
+ 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.",
30913
31443
  inputSchema: {
30914
31444
  id: import_zod11.z.string().describe("The component whose method is renamed (namespaced if needed)"),
30915
31445
  method: import_zod11.z.string().describe("The method name as it stands"),
@@ -30929,7 +31459,7 @@ ${renderChangeReport(report2)}`,
30929
31459
  server,
30930
31460
  "sdd_move_methods",
30931
31461
  {
30932
- 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.",
31462
+ 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.",
30933
31463
  inputSchema: {
30934
31464
  from: import_zod11.z.string().describe("The component the methods belong to (namespaced if needed)"),
30935
31465
  to: import_zod11.z.string().describe("The component that should receive them (namespaced if needed)"),
@@ -31282,7 +31812,7 @@ ${renderChangeReport(report2)}`,
31282
31812
  id: import_zod11.z.string().describe("Lowercase identifier"),
31283
31813
  name: import_zod11.z.string().describe("Human-readable name"),
31284
31814
  description: import_zod11.z.string().optional(),
31285
- subsystem: import_zod11.z.string().optional().describe("Owning subsystem id; omit for a system-level shared value object"),
31815
+ 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"),
31286
31816
  group: import_zod11.z.string().optional().describe("Optional logical group ID to organize this type in subfolders"),
31287
31817
  fields: import_zod11.z.array(import_zod11.z.object({
31288
31818
  name: import_zod11.z.string(),
@@ -31316,7 +31846,7 @@ ${renderChangeReport(report2)}`,
31316
31846
  server,
31317
31847
  "sdd_add_type",
31318
31848
  {
31319
- 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.",
31849
+ 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.",
31320
31850
  inputSchema: typeInput,
31321
31851
  outputSchema: specWriteReceiptOutput
31322
31852
  },