@wairon/cli 5.1.1-dev.102 → 5.1.1-dev.103

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
@@ -3566,6 +3566,7 @@ __export(src_exports, {
3566
3566
  aiDir: () => aiDir,
3567
3567
  aiDirAt: () => aiDirAt,
3568
3568
  aiPathsAt: () => aiPathsAt,
3569
+ aliasTargetOf: () => aliasTargetOf,
3569
3570
  ambiguityMessage: () => ambiguityMessage,
3570
3571
  approvalKeyIn: () => approvalKeyIn,
3571
3572
  approvalRecord: () => approvalRecord,
@@ -3751,6 +3752,7 @@ __export(src_exports, {
3751
3752
  memberDeclarationOf: () => memberDeclarationOf,
3752
3753
  memberDigest: () => memberDigest,
3753
3754
  memberLocationOf: () => memberLocationOf,
3755
+ memberTypesOf: () => memberTypesOf,
3754
3756
  methodGenericParameters: () => methodGenericParameters,
3755
3757
  methodSourceFile: () => methodSourceFile,
3756
3758
  methodTypeRefs: () => methodTypeRefs,
@@ -3809,7 +3811,7 @@ __export(src_exports, {
3809
3811
  renameId: () => renameProjectId,
3810
3812
  renameMethod: () => renameMethod,
3811
3813
  renameType: () => renameType,
3812
- renderDiagram: () => renderDiagram,
3814
+ renderDiagram: () => renderDiagram2,
3813
3815
  repairForeignStepFields: () => repairForeignStepFields,
3814
3816
  repairSignatures: () => repairSignatures,
3815
3817
  repairTypeSpellings: () => repairTypeSpellings,
@@ -3823,6 +3825,7 @@ __export(src_exports, {
3823
3825
  resolveExternals: () => resolveDeclared,
3824
3826
  resolveImport: () => resolveImport,
3825
3827
  resolveInstalledPack: () => resolveInstalledPack,
3828
+ resolveProjectBinding: () => resolveProjectBinding,
3826
3829
  resolveProjectExports: () => resolveProjectExports,
3827
3830
  resolveSignatures: () => resolveSignatures,
3828
3831
  resolveSubprojectForNamespace: () => resolveSubprojectForNamespace,
@@ -3887,7 +3890,7 @@ __export(src_exports, {
3887
3890
  writeFile: () => writeFile,
3888
3891
  writeFileIfChanged: () => writeFileIfChanged,
3889
3892
  writeJsonFile: () => writeJsonFile,
3890
- writeLockRecord: () => writeLockRecord,
3893
+ writeLockRecord: () => writeLockRecord2,
3891
3894
  writeReachDenials: () => writeReachDenials,
3892
3895
  writeReachPermits: () => writeReachPermits,
3893
3896
  writeRootGuideDelegator: () => writeRootGuideDelegator,
@@ -3906,6 +3909,7 @@ var import_zod4 = require("zod");
3906
3909
  var fs = __toESM(require("fs"));
3907
3910
  var path = __toESM(require("path"));
3908
3911
  var import_node_async_hooks = require("async_hooks");
3912
+ var yaml = __toESM(require("js-yaml"));
3909
3913
  function ensureDir(dirPath) {
3910
3914
  fs.mkdirSync(dirPath, { recursive: true });
3911
3915
  }
@@ -4122,22 +4126,74 @@ function setProjectRoot(dir) {
4122
4126
  function getProjectRootOverride() {
4123
4127
  return projectRootOverride;
4124
4128
  }
4125
- function findSystemRoot(startDir) {
4129
+ function holdsSystemSpec(dir) {
4130
+ for (const base of [".wai", ".wairon"]) {
4131
+ const specs = path.join(dir, base, "specs");
4132
+ if (fs.existsSync(path.join(specs, ".index.yaml")) || fs.existsSync(path.join(specs, "system.yaml"))) return true;
4133
+ if (base === ".wai" && fs.existsSync(path.join(dir, ".wai"))) break;
4134
+ }
4135
+ return false;
4136
+ }
4137
+ function findRepositoryRoot(startDir) {
4126
4138
  let dir = path.resolve(startDir);
4127
4139
  while (true) {
4128
- const isWai = fs.existsSync(path.join(dir, ".wai"));
4129
- const isWairon = !isWai && fs.existsSync(path.join(dir, ".wairon"));
4130
- const base = isWai ? ".wai" : isWairon ? ".wairon" : null;
4131
- if (base) {
4132
- if (fs.existsSync(path.join(dir, base, "specs", ".index.yaml")) || fs.existsSync(path.join(dir, base, "specs", "system.yaml"))) {
4133
- return dir;
4134
- }
4140
+ if (fs.existsSync(path.join(dir, ".git"))) return dir;
4141
+ const parent = path.dirname(dir);
4142
+ if (parent === dir) return null;
4143
+ dir = parent;
4144
+ }
4145
+ }
4146
+ function isAtOrBelow(outer, inner2) {
4147
+ const rel2 = path.relative(outer, inner2);
4148
+ return rel2 === "" || !rel2.startsWith("..") && !path.isAbsolute(rel2);
4149
+ }
4150
+ function declaredMemberDirs(projectRoot) {
4151
+ let parsed;
4152
+ try {
4153
+ const text3 = fs.readFileSync(path.join(projectRoot, ".wai", "project.yaml"), "utf-8");
4154
+ parsed = yaml.load(text3);
4155
+ } catch {
4156
+ return [];
4157
+ }
4158
+ const members = parsed?.members;
4159
+ if (!members || typeof members !== "object") return [];
4160
+ const dirs = [];
4161
+ for (const value of Object.values(members)) {
4162
+ const declaration2 = value;
4163
+ const source = typeof declaration2 === "string" ? declaration2 : typeof declaration2?.source === "string" ? declaration2.source : typeof declaration2?.path === "string" ? declaration2.path : void 0;
4164
+ if (!source) continue;
4165
+ const text3 = source.trim();
4166
+ if (text3.startsWith("hosted:") || /^[a-z][a-z0-9+.-]*:\/\//i.test(text3) || /^[\w.-]+@[\w.-]+:/.test(text3) || path.isAbsolute(text3)) continue;
4167
+ dirs.push(path.resolve(projectRoot, text3));
4168
+ }
4169
+ return dirs;
4170
+ }
4171
+ function resolveProjectBinding(startDir) {
4172
+ const start = path.resolve(startDir);
4173
+ const repositoryRoot2 = findRepositoryRoot(start) ?? void 0;
4174
+ const withRepo = (b) => repositoryRoot2 ? { ...b, repositoryRoot: repositoryRoot2 } : b;
4175
+ let dir = start;
4176
+ while (true) {
4177
+ if (holdsSystemSpec(dir)) return withRepo({ root: dir, via: dir === start ? "here" : "ancestor" });
4178
+ if (repositoryRoot2 && dir === repositoryRoot2) break;
4179
+ const parent = path.dirname(dir);
4180
+ if (parent === dir) return null;
4181
+ dir = parent;
4182
+ }
4183
+ dir = path.dirname(repositoryRoot2);
4184
+ while (true) {
4185
+ if (holdsSystemSpec(dir)) {
4186
+ const declares2 = declaredMemberDirs(dir).some((member) => isAtOrBelow(repositoryRoot2, member) && isAtOrBelow(member, start));
4187
+ if (declares2) return withRepo({ root: dir, via: "declared-member" });
4135
4188
  }
4136
4189
  const parent = path.dirname(dir);
4137
4190
  if (parent === dir) return null;
4138
4191
  dir = parent;
4139
4192
  }
4140
4193
  }
4194
+ function findSystemRoot(startDir) {
4195
+ return resolveProjectBinding(startDir)?.root ?? null;
4196
+ }
4141
4197
  function getProjectRoot() {
4142
4198
  const scoped = requestRootStore.getStore()?.root;
4143
4199
  if (scoped) return scoped;
@@ -4187,7 +4243,7 @@ function defaultTargetConfig(type) {
4187
4243
  enabled: true
4188
4244
  };
4189
4245
  }
4190
- var WAIRON_VERSION = "5.1.1-dev.102";
4246
+ var WAIRON_VERSION = "5.1.1-dev.103";
4191
4247
  var GITHUB_REPO = "SYW-Apps/Waffle-AIron";
4192
4248
  var ARCHITECT_AGENT_ID = "agent-architect";
4193
4249
  var ARCHITECT_TEMPLATE_ID = "architect";
@@ -4402,17 +4458,17 @@ function repositoryRoot(directory) {
4402
4458
  }
4403
4459
 
4404
4460
  // src/utils/yaml.ts
4405
- var yaml = __toESM(require("js-yaml"));
4461
+ var yaml2 = __toESM(require("js-yaml"));
4406
4462
  function parseYaml(content, sourcePath) {
4407
4463
  try {
4408
- return yaml.load(content);
4464
+ return yaml2.load(content);
4409
4465
  } catch (err) {
4410
4466
  const loc = sourcePath ? ` (${sourcePath})` : "";
4411
4467
  throw new Error(`Failed to parse YAML${loc}: ${String(err)}`);
4412
4468
  }
4413
4469
  }
4414
4470
  function serializeYaml(value) {
4415
- return yaml.dump(value, {
4471
+ return yaml2.dump(value, {
4416
4472
  indent: 2,
4417
4473
  lineWidth: 100,
4418
4474
  noRefs: true,
@@ -4635,6 +4691,14 @@ var AgentBriefSchema = import_zod2.z.object({
4635
4691
  * agent implements nothing.
4636
4692
  */
4637
4693
  typeMapping: import_zod2.z.array(import_zod2.z.string()).optional(),
4694
+ /**
4695
+ * Where an implementing agent may write CODE: each source file its
4696
+ * implementations name, plus the folder they share below the project root as
4697
+ * `<folder>/**`. Empty when nothing names a file yet (the instructions then
4698
+ * ask the spawning session to name the planned files); absent for an agent
4699
+ * that implements nothing.
4700
+ */
4701
+ codeFence: import_zod2.z.array(import_zod2.z.string()).optional(),
4638
4702
  /**
4639
4703
  * Set only on a brief for a member's agent (an id qualified `<alias>::`): the
4640
4704
  * member's directory relative to the root the brief was asked from. The
@@ -9109,7 +9173,7 @@ function extractTypesFromSignature(signature, returns) {
9109
9173
  let sigCleaned = signature.replace(/\/\/.*$/gm, "").replace(/#.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
9110
9174
  sigCleaned = sigCleaned.replace(/(?<=\S)\s*\([^)]*\)\s*$/, "");
9111
9175
  sigCleaned = sigCleaned.replace(/(?<=\S)\s+[-—:]\s+[a-z\s_-]+$/, "");
9112
- const returnsCleaned = returns.replace(/\/\/.*$/gm, "").replace(/#.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "").replace(/[a-zA-Z0-9_-]+\s*\??\s*:/g, "");
9176
+ const returnsCleaned = returns.replace(/\/\/.*$/gm, "").replace(/#.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "").replace(/(?<![:\w-])[a-zA-Z0-9_-]+\s*\??\s*:(?!:)/g, "");
9113
9177
  types.push(...extractTypeIdentifiers(returnsCleaned));
9114
9178
  const openParen = sigCleaned.indexOf("(");
9115
9179
  const closeParen = sigCleaned.lastIndexOf(")");
@@ -9140,7 +9204,7 @@ function extractTypesFromSignature(signature, returns) {
9140
9204
  const colonIndex = param.indexOf(":");
9141
9205
  if (colonIndex !== -1) {
9142
9206
  const paramType = param.slice(colonIndex + 1).trim();
9143
- const paramTypeCleaned = paramType.replace(/[a-zA-Z0-9_-]+\s*\??\s*:/g, "");
9207
+ const paramTypeCleaned = paramType.replace(/(?<![:\w-])[a-zA-Z0-9_-]+\s*\??\s*:(?!:)/g, "");
9144
9208
  types.push(...extractTypeIdentifiers(paramTypeCleaned));
9145
9209
  }
9146
9210
  }
@@ -9589,6 +9653,12 @@ function fieldTypesOf(facts, field) {
9589
9653
  function localTypesOf(facts, name) {
9590
9654
  return ownEntry(facts.localTypes, name) ?? NO_DECLARED_TYPES;
9591
9655
  }
9656
+ function memberTypesOf(facts, shape, property) {
9657
+ return ownEntry(facts.memberTypes, `${shape}.${property}`) ?? NO_DECLARED_TYPES;
9658
+ }
9659
+ function aliasTargetOf(facts, name) {
9660
+ return ownEntry(facts.aliasTargets, name);
9661
+ }
9592
9662
  function typeBindingOf(facts, name) {
9593
9663
  return ownEntry(facts.typeOnlyBindings, name) ?? ownEntry(facts.importBindings, name)?.from;
9594
9664
  }
@@ -15532,7 +15602,7 @@ function unpin(alias) {
15532
15602
  }
15533
15603
 
15534
15604
  // src/core/openapi.ts
15535
- var yaml2 = __toESM(require("js-yaml"));
15605
+ var yaml3 = __toESM(require("js-yaml"));
15536
15606
  var PRIMITIVE_SCHEMAS = {
15537
15607
  string: { type: "string" },
15538
15608
  int: { type: "integer" },
@@ -15817,7 +15887,7 @@ function toOpenApiSet(snapshot) {
15817
15887
  }
15818
15888
  function isOpenApiDocument(body) {
15819
15889
  try {
15820
- const parsed = yaml2.load(body);
15890
+ const parsed = yaml3.load(body);
15821
15891
  return !!parsed && typeof parsed === "object" && typeof parsed.openapi === "string";
15822
15892
  } catch {
15823
15893
  return false;
@@ -15928,7 +15998,7 @@ function authFromSecurityScheme(scheme) {
15928
15998
  function fromOpenApi(document, projectName) {
15929
15999
  let parsed;
15930
16000
  try {
15931
- parsed = yaml2.load(document);
16001
+ parsed = yaml3.load(document);
15932
16002
  } catch (e) {
15933
16003
  throw new Error(`Invalid surface document: not parseable as JSON/YAML (${e instanceof Error ? e.message : String(e)})`);
15934
16004
  }
@@ -16758,7 +16828,9 @@ function writeSurfaceFile(targetPath, snapshot, renderedSet) {
16758
16828
  const resolved = path14.resolve(targetPath);
16759
16829
  fs9.mkdirSync(path14.dirname(resolved), { recursive: true });
16760
16830
  if (!renderedSet || renderedSet.length === 0) {
16761
- writeYamlFile(resolved, snapshot);
16831
+ if (path14.extname(resolved).toLowerCase() === ".json") fs9.writeFileSync(resolved, `${JSON.stringify(snapshot, null, 2)}
16832
+ `);
16833
+ else writeYamlFile(resolved, snapshot);
16762
16834
  return [resolved];
16763
16835
  }
16764
16836
  if (renderedSet.length === 1) {
@@ -18695,17 +18767,47 @@ function buildCodeIndex(model) {
18695
18767
  };
18696
18768
  const possibleOriginsOf = (site, from) => {
18697
18769
  const proven = originOf2(site, from);
18698
- if (!site.field && !site.constructed && !site.via && !site.returnedBy && !site.enclosingClass) return proven;
18770
+ if (!site.field && !site.constructed && !site.via && !site.returnedBy && !site.enclosingClass && !site.receiverPath) {
18771
+ return proven;
18772
+ }
18699
18773
  const resolved = scopeOf2(site, from);
18700
18774
  if (!resolved) return proven;
18701
18775
  const { path: scope, facts: f } = resolved;
18702
18776
  const out = new Set(proven);
18703
- const widenThrough = (name, specifier) => {
18704
- const landings = specifier !== void 0 ? landingFrom(scope, specifier) : declarationsAt(scope).has(name) ? republishedFrom(scope) : NO_ORIGIN;
18777
+ const widenThroughAt = (at, name, specifier) => {
18778
+ const landings = specifier !== void 0 ? landingFrom(at, specifier) : declarationsAt(at).has(name) ? republishedFrom(at) : NO_ORIGIN;
18705
18779
  for (const candidate of landings) out.add(candidate);
18706
18780
  };
18707
- const widenThroughType = (typeName) => widenThrough(typeName, typeBindingOf(f, typeName));
18781
+ const widenThrough = (name, specifier) => widenThroughAt(scope, name, specifier);
18782
+ const widenThroughType = (typeName, at = scope, atFacts = f) => {
18783
+ widenThroughAt(at, typeName, typeBindingOf(atFacts, typeName));
18784
+ for (const decl of typeDeclarationsOf(at, atFacts, typeName)) {
18785
+ const target = aliasTargetOf(decl.facts, decl.name);
18786
+ if (target !== void 0) widenThroughAt(decl.path, target, typeBindingOf(decl.facts, target));
18787
+ }
18788
+ };
18708
18789
  if (site.field) for (const typeName of fieldTypesOf(f, site.field)) widenThroughType(typeName);
18790
+ if (site.receiverPath && site.receiverPath.length >= 2) {
18791
+ const [root, ...rest] = site.receiverPath;
18792
+ const links = [...rest];
18793
+ let current2 = [];
18794
+ if (root === "this") {
18795
+ const field = links.shift();
18796
+ if (field !== void 0) current2 = fieldTypesOf(f, field).map((type) => ({ at: scope, facts: f, type }));
18797
+ } else {
18798
+ current2 = localTypesOf(f, root).map((type) => ({ at: scope, facts: f, type }));
18799
+ }
18800
+ for (const link of links) {
18801
+ const next = [];
18802
+ for (const c of current2) {
18803
+ for (const decl of typeDeclarationsOf(c.at, c.facts, c.type, true)) {
18804
+ for (const type of memberTypesOf(decl.facts, decl.name, link)) next.push({ at: decl.path, facts: decl.facts, type });
18805
+ }
18806
+ }
18807
+ current2 = next;
18808
+ }
18809
+ for (const c of current2) widenThroughType(c.type, c.at, c.facts);
18810
+ }
18709
18811
  if (site.constructed) widenThrough(site.constructed, importBindingOf(f, site.constructed)?.from);
18710
18812
  if (site.via) for (const typeName of localTypesOf(f, site.via)) widenThroughType(typeName);
18711
18813
  if (site.enclosingClass !== void 0 && holdsMemberBody(f, site.name, site.enclosingClass)) {
@@ -18727,6 +18829,34 @@ function buildCodeIndex(model) {
18727
18829
  }
18728
18830
  return out;
18729
18831
  };
18832
+ const typeDeclarationsOf = (at, atFacts, typeName, throughAlias2 = false) => {
18833
+ const found = [];
18834
+ const exactFacts = (p) => {
18835
+ const x = facts.get(p);
18836
+ return x?.status === "analyzed" && x.analysisGrade === "exact" ? x : void 0;
18837
+ };
18838
+ const direct = (where, whereFacts, name) => {
18839
+ if (whereFacts.declaredNames.includes(name) && typeBindingOf(whereFacts, name) === void 0) {
18840
+ return [{ path: where, facts: whereFacts, name }];
18841
+ }
18842
+ const specifier = typeBindingOf(whereFacts, name);
18843
+ if (specifier === void 0) return [];
18844
+ const published = importBindingOf(whereFacts, name)?.imported ?? name;
18845
+ const out = [];
18846
+ for (const candidate of landingFrom(where, specifier)) {
18847
+ const there = exactFacts(candidate);
18848
+ if (there && there.declaredNames.includes(published)) out.push({ path: candidate, facts: there, name: published });
18849
+ }
18850
+ return out;
18851
+ };
18852
+ for (const decl of direct(at, atFacts, typeName)) {
18853
+ found.push(decl);
18854
+ if (!throughAlias2) continue;
18855
+ const target = aliasTargetOf(decl.facts, decl.name);
18856
+ if (target !== void 0) found.push(...direct(decl.path, decl.facts, target));
18857
+ }
18858
+ return found;
18859
+ };
18730
18860
  const holdsMemberBody = (f, name, container) => f.status === "analyzed" && f.analysisGrade === "exact" && !!f.functionBodies && Object.prototype.hasOwnProperty.call(f.functionBodies, name) && f.functionBodies[name].some((b) => b.container === container);
18731
18861
  const declarationsAt = (path49) => namesAt(declarations, pathKey(path49), (f) => /* @__PURE__ */ new Set([...f.declaredNames, ...f.exportedNames]));
18732
18862
  const forwardsOf = (start, name, container) => {
@@ -19236,10 +19366,11 @@ var fieldTypeReferencesRule = {
19236
19366
  for (const ref of refs) {
19237
19367
  if (!ctx.isTypeResolved(ref, /* @__PURE__ */ new Set())) {
19238
19368
  const hint = ctx.importHint(ref);
19369
+ const spelled = ctx.aliasSpelling(ref);
19239
19370
  ctx.addIssue(
19240
19371
  "error",
19241
19372
  "UNDEFINED_TYPE_REFERENCE",
19242
- `Type "${t.id}" field "${field.name}" references undefined type "${ref}" in "${field.type}".${hint ? ` A declared dependency exports it without this project importing it \u2014 add \`${hint}\`.` : ""}`,
19373
+ `Type "${t.id}" field "${field.name}" references undefined type "${ref}" in "${field.type}".${hint ? ` A declared dependency exports it without this project importing it \u2014 add \`${hint}\`.` : ""}${spelled ? ` "${ref.split(".")[0]}" is another project here, not a subsystem of this one \u2014 write \`${spelled}\`.` : ""}`,
19243
19374
  t.id,
19244
19375
  isDraftCtx
19245
19376
  );
@@ -19274,10 +19405,11 @@ var signatureTypeReferencesRule = {
19274
19405
  for (const ref of refs) {
19275
19406
  if (!ctx.isTypeResolved(ref, allGenerics)) {
19276
19407
  const hint = ctx.importHint(ref);
19408
+ const spelled = ctx.aliasSpelling(ref);
19277
19409
  ctx.addIssue(
19278
19410
  "error",
19279
19411
  "UNDEFINED_TYPE_REFERENCE",
19280
- `Method "${m.name}" on interface "${intf.id}" references undefined type "${ref}" in signature.${hint ? ` A declared dependency exports it without this project importing it \u2014 add \`${hint}\`.` : ""}`,
19412
+ `Method "${m.name}" on interface "${intf.id}" references undefined type "${ref}" in signature.${hint ? ` A declared dependency exports it without this project importing it \u2014 add \`${hint}\`.` : ""}${spelled ? ` "${ref.split(".")[0]}" is another project here, not a subsystem of this one \u2014 write \`${spelled}\`.` : ""}`,
19281
19413
  intf.id,
19282
19414
  isDraftCtx
19283
19415
  );
@@ -19296,10 +19428,11 @@ var signatureTypeReferencesRule = {
19296
19428
  for (const { ref, by } of named2) {
19297
19429
  if (ctx.isTypeResolved(ref, typeGenerics)) continue;
19298
19430
  const hint = ctx.importHint(ref);
19431
+ const spelled = ctx.aliasSpelling(ref);
19299
19432
  ctx.addIssue(
19300
19433
  "error",
19301
19434
  "UNDEFINED_TYPE_REFERENCE",
19302
- `${by} references undefined type "${ref}" in signature.${hint ? ` A declared dependency exports it without this project importing it \u2014 add \`${hint}\`.` : ""}`,
19435
+ `${by} references undefined type "${ref}" in signature.${hint ? ` A declared dependency exports it without this project importing it \u2014 add \`${hint}\`.` : ""}${spelled ? ` "${ref.split(".")[0]}" is another project here, not a subsystem of this one \u2014 write \`${spelled}\`.` : ""}`,
19303
19436
  t.id,
19304
19437
  isDraftCtx
19305
19438
  );
@@ -24030,19 +24163,40 @@ function closedCallSites(code, file, fn, stopAt, container) {
24030
24163
  return out;
24031
24164
  }
24032
24165
  var anchorKey = (symbol, exportedVia) => exportedVia !== void 0 ? `${exportedVia}.${symbol}` : symbol;
24166
+ var bodyKey = (file, symbol, exportedVia) => `${file}|${anchorKey(symbol, exportedVia)}`;
24033
24167
  function resolveCallTarget(ctx, componentId, methodName) {
24034
24168
  const accepted = /* @__PURE__ */ new Set([methodName]);
24035
24169
  const files = /* @__PURE__ */ new Set();
24170
+ const bodies = /* @__PURE__ */ new Set();
24171
+ const anchors = [];
24036
24172
  for (const intf of ctx.interfacesByComponent.get(componentId) ?? []) {
24037
24173
  for (const impl of ctx.implementationsByContract.get(intf.id) ?? []) {
24038
24174
  const method = impl.methods.find((m) => m.name === methodName);
24039
24175
  if (!method && !intf.methods.some((m) => m.name === methodName)) continue;
24040
24176
  if (method?.symbol) accepted.add(method.symbol);
24041
24177
  const file = methodSourceFile(method ?? {}, impl.sourcePath);
24042
- if (file) files.add(pathKey(file));
24178
+ if (!file) continue;
24179
+ files.add(pathKey(file));
24180
+ bodies.add(bodyKey(pathKey(file), method?.symbol ?? methodName, method?.exportedVia));
24181
+ anchors.push({ file: pathKey(file), symbol: method?.symbol ?? methodName, exportedVia: method?.exportedVia });
24043
24182
  }
24044
24183
  }
24045
- return { accepted, files };
24184
+ return { accepted, files, bodies, anchors };
24185
+ }
24186
+ function targetForwards(code, target) {
24187
+ const out = /* @__PURE__ */ new Set();
24188
+ for (const anchor of target.anchors) {
24189
+ for (const p of code.forwardsOf(anchor.file, anchor.symbol, anchor.exportedVia)) out.add(`${p.file}#${p.name}`);
24190
+ if (anchor.exportedVia !== void 0) continue;
24191
+ const facts = code.factsAt(anchor.file);
24192
+ if (!facts || facts.status !== "analyzed" || facts.analysisGrade !== "exact") continue;
24193
+ const binding = importBindingOf(facts, anchor.symbol);
24194
+ if (!binding || binding.namespace) continue;
24195
+ const module2 = resolveImport(anchor.file, binding.from, code.paths, code.packages);
24196
+ if (!module2) continue;
24197
+ for (const p of code.forwardsOf(module2, binding.imported ?? anchor.symbol)) out.add(`${p.file}#${p.name}`);
24198
+ }
24199
+ return out;
24046
24200
  }
24047
24201
  function isTarget(pairs, target) {
24048
24202
  return pairs.some((p) => target.files.has(p.file) && target.accepted.has(p.name));
@@ -24083,6 +24237,7 @@ function describeSite(site) {
24083
24237
  if (site.field) return `this.${site.field}.${site.name}(\u2026)`;
24084
24238
  if (site.constructed) return `new ${site.constructed}(\u2026).${site.name}(\u2026)`;
24085
24239
  if (site.returnedBy) return `${site.returnedBy}(\u2026).${site.name}(\u2026)`;
24240
+ if (site.receiverPath) return `${site.receiverPath.join(".")}.${site.name}(\u2026)`;
24086
24241
  if (site.enclosingClass) return `this.${site.name}(\u2026)`;
24087
24242
  return `<receiver>.${site.name}(\u2026)`;
24088
24243
  }
@@ -24096,10 +24251,10 @@ function describeMiss(m) {
24096
24251
  var callConformanceRule = {
24097
24252
  name: "call-conformance",
24098
24253
  judges: "code",
24099
- description: "Code\u2194spec Level 3: the CLAIMED call \u2194 realized call relation, judged both ways against the method's own source file (its sourcePath, else the implementation's), at exact analysis grade only. A method claims a call two ways and they face one check: a narrative `call` step, and an entry of the declared `calls` a narrative-less method reaches its collaborators through. They assert the same thing, and the step number is no part of the claim \u2014 this check never reads order, so it is only how a finding points at one. Forward: every claim must be realized by a call whose callee RESOLVES TO one of the target method's own source files \u2014 the target's contract name or a per-method `symbol` override, closed transitively over the named helpers the realized function calls, and resolved against every file the call CAN have reached: a `this.<field>.<method>()` receiver followed through the field's DECLARED TYPE, a `new Class(...).<method>()` receiver through the module its CLASS NAME came from, a plain `<name>.<method>()` receiver through the type the file ANNOTATES that name with \u2014 a parameter's, or an annotated variable's, which is how a module that wires its collaborators as closures writes every one of its calls \u2014 a `fn(...).<method>()` receiver through the type fn RETURNS where the code settles it, and a `this.<method>()` receiver through the class whose own method it sits in; a package specifier naming a package of this repository resolves like a relative one, and an export alias (`export { a as b }`, an adapter object property `{ b: a }`) forwards by identity like a re-export. A matching call whose origin a pure model cannot resolve is reported apart as CALL_ORIGIN_UNRESOLVED, which NAMES the shape it could not follow and asks for nothing \u2014 a coverage hole in the reader, reported for the reason CONFORMANCE_DEGRADED is: a silently degraded gate is worse than a degraded one. A finding names a landing only from the PROVEN tier so that widening what a call reached can accept a claim but never accuse one, and a target that names no file of its own falls back to name membership. Converse: a call that resolves to a modelled method of ANOTHER component in the SAME file crosses a component boundary while looking local, so the method must claim it \u2014 in a step or in `calls` (UNDECLARED_COLOCATED_CALL) \u2014 judged on the proven tier alone, and a same-file private helper is no modelled method and is never reported; a function forwarding by identity to a modelled method written in another file IS that method, so what it calls is that method's subject. A method that claims nothing at all is judged in neither direction: what it leaves unsaid is UNUSED_*'s subject. Order, arguments and conditions stay unverified, dispatch steps (runtime-table routed) are no claim about a call site, a malformed declared reference is MALFORMED_DECLARED_CALL's finding and names no target here, the conformance dial (off) skips, and weaker analysis grades never guess.",
24254
+ description: "Code\u2194spec Level 3: the CLAIMED call \u2194 realized call relation, judged both ways against the method's own source file (its sourcePath, else the implementation's), at exact analysis grade only. A method claims a call two ways and they face one check: a narrative `call` step, and an entry of the declared `calls` a narrative-less method reaches its collaborators through. They assert the same thing, and the step number is no part of the claim \u2014 this check never reads order, so it is only how a finding points at one. Forward: every claim must be realized by a call whose callee RESOLVES TO one of the target method's own source files \u2014 the target's contract name or a per-method `symbol` override, closed transitively over the named helpers the realized function calls, and resolved against every file the call CAN have reached: a `this.<field>.<method>()` receiver followed through the field's DECLARED TYPE, a `new Class(...).<method>()` receiver through the module its CLASS NAME came from, a plain `<name>.<method>()` receiver through the type the file ANNOTATES that name with \u2014 a parameter's, or an annotated variable's, which is how a module that wires its collaborators as closures writes every one of its calls \u2014 a `fn(...).<method>()` receiver through the type fn RETURNS where the code settles it, and a `this.<method>()` receiver through the class whose own method it sits in, and a receiver CHAIN (`this.<field>.<property>.<method>()`, `<name>.<property>.<method>()`) link by link through each link's declared type \u2014 a declared type read through a member-preserving utility (Pick, Omit, Partial, Required, Readonly) and one alias hop; a package specifier naming a package of this repository resolves like a relative one, and an export alias (`export { a as b }`, an adapter object property `{ b: a }`) forwards by identity like a re-export. A matching call whose origin a pure model cannot resolve is reported apart as CALL_ORIGIN_UNRESOLVED, which NAMES the shape it could not follow and asks for nothing \u2014 a coverage hole in the reader, reported for the reason CONFORMANCE_DEGRADED is: a silently degraded gate is worse than a degraded one. A finding names a landing only from the PROVEN tier so that widening what a call reached can accept a claim but never accuse one, and a target that names no file of its own falls back to name membership. N:1 identity \u2014 a caller that IS the target \u2014 is judged on the body, the same anchor in the same file, never on a shared name: a Portal method forwarding to a same-named Orchestrator method elsewhere must make that call. Converse: a call that resolves to a modelled method of ANOTHER component in the SAME file crosses a component boundary while looking local, so the method must claim it \u2014 in a step or in `calls` (UNDECLARED_COLOCATED_CALL) \u2014 judged on the proven tier alone, and a same-file private helper is no modelled method and is never reported; a function forwarding by identity to a modelled method written in another file IS that method, so what it calls is that method's subject. A method that claims nothing at all is judged in neither direction: what it leaves unsaid is UNUSED_*'s subject. Order, arguments and conditions stay unverified, dispatch steps (runtime-table routed) are no claim about a call site, a malformed declared reference is MALFORMED_DECLARED_CALL's finding and names no target here, the conformance dial (off) skips, and weaker analysis grades never guess.",
24100
24255
  codes: [
24101
24256
  { code: "CALL_STEP_UNREALIZED", defaultSeverity: "warning", summary: "Narrative call step or declared call realized by no call that resolves to the target method's own source file \u2014 the call is absent, or it lands in another module", carryable: true },
24102
- { code: "CALL_ORIGIN_UNRESOLVED", defaultSeverity: "warning", summary: "Narrative call step or declared call whose target name IS called, but only from call sites written in a shape this analysis cannot resolve to a file \u2014 the claim was not checked, and is neither proven realized nor accused", carryable: true },
24257
+ { code: "CALL_ORIGIN_UNRESOLVED", defaultSeverity: "warning", summary: "Narrative call step or declared call whose target name IS called, but only from call sites written in a shape this analysis cannot resolve to a file \u2014 the claim was not checked, and is neither proven realized nor accused. What resolves is a name bound to a module of THIS project: a bare or namespaced call through such an import binding, a this.<field> receiver whose declared type names one, a receiver chain (this.<field>.<property>, <name>.<property>) each of whose links the code declares a type for, a new Class(\u2026) receiver whose class does, a receiver name the file annotates with such a type, a fn(\u2026) receiver whose return the code settles, and this inside a class's own method \u2014 a declared type read through Pick/Omit/Partial/Required/Readonly and one alias hop. What does not: a third-party package import, a value the module assembled, and a receiver the file declares nothing for", carryable: true },
24103
24258
  { code: "UNDECLARED_COLOCATED_CALL", defaultSeverity: "warning", summary: "The realized function calls a modelled method of another component living in the same source file, and neither a narrative step nor a declared call names it", carryable: true }
24104
24259
  ],
24105
24260
  check(ctx) {
@@ -24127,9 +24282,9 @@ var callConformanceRule = {
24127
24282
  const here = pathKey(file);
24128
24283
  const fnSymbol = implMethod.symbol ?? implMethod.name;
24129
24284
  const colocated = modelledAt.get(here) ?? { byKey: /* @__PURE__ */ new Map(), byName: /* @__PURE__ */ new Map() };
24130
- const ownerOf4 = (site, bodyKey) => {
24285
+ const ownerOf4 = (site, bodyKey2) => {
24131
24286
  const inHere = pathKey(site.from ?? here) === here;
24132
- if (inHere && bodyKey !== void 0 && !bodyKey.startsWith("*")) return colocated.byKey.get(bodyKey);
24287
+ if (inHere && bodyKey2 !== void 0 && !bodyKey2.startsWith("*")) return colocated.byKey.get(bodyKey2);
24133
24288
  return colocated.byName.get(site.name);
24134
24289
  };
24135
24290
  const sites = closedCallSites(code, file, fnSymbol, void 0, implMethod.exportedVia);
@@ -24138,7 +24293,9 @@ var callConformanceRule = {
24138
24293
  for (const claim of claimsOf(implMethod)) {
24139
24294
  if (!ctx.componentMap.has(claim.component)) continue;
24140
24295
  const target = resolveCallTarget(ctx, claim.component, claim.method);
24141
- if (target.accepted.has(fnSymbol)) continue;
24296
+ if (target.bodies.has(bodyKey(here, fnSymbol, implMethod.exportedVia))) continue;
24297
+ const sharedBody = targetForwards(code, target);
24298
+ if (code.forwardsOf(file, fnSymbol, implMethod.exportedVia).some((p) => p.file !== here && sharedBody.has(`${p.file}#${p.name}`))) continue;
24142
24299
  if (isTarget(code.forwardsOf(file, fnSymbol, implMethod.exportedVia), target)) continue;
24143
24300
  const matching = sites.filter((s) => target.accepted.has(s.name));
24144
24301
  const ref = `${claim.component}.${claim.method}`;
@@ -24182,7 +24339,10 @@ var callConformanceRule = {
24182
24339
  ctx.addIssue(
24183
24340
  "warning",
24184
24341
  "CALL_ORIGIN_UNRESOLVED",
24185
- `Method "${implMethod.name}" in implementation "${impl.id}": ${unresolved.length} ${claimNoun(implMethod)} ARE called by name inside the function "${fnSymbol}" in "${file}", but every site calling them is written in a shape this analysis cannot resolve to a file \u2014 ${unresolved.map(describeMiss).join("; ")}. What resolves is a name bound to a module of THIS project: a bare or namespaced call through such an import binding (a package of this repository included), a \`this.<field>\` receiver whose declared type names one, a \`new Class(\u2026)\` receiver whose class does, a receiver NAME the file annotates with a type that does, a \`fn(\u2026)\` receiver whose function's return the code settles on a type with that method's body, and \`this\` inside a class's own method that has it. What does not: an import from a third-party PACKAGE, a receiver holding a value the module assembled, a receiver this model records no name for, and one the file annotates with nothing. This reports what was not checked, not what is wrong \u2014 the claim is neither proven realized nor accused of being missing, and no working call is asked to be rewritten to suit the reader.`,
24342
+ // The sites, and what the answer means — once. Which receiver
24343
+ // forms resolve is the code's summary and the rule's description
24344
+ // (`wairon rules list`), not prose every finding repeats.
24345
+ `Method "${implMethod.name}" in implementation "${impl.id}": ${unresolved.length} ${claimNoun(implMethod)} not checked \u2014 called by name inside "${fnSymbol}" in "${file}", but only through receivers this analysis cannot resolve to a file: ${unresolved.map(describeMiss).join("; ")}. Neither proven realized nor accused; the receiver forms that do resolve are listed under CALL_ORIGIN_UNRESOLVED in \`wairon rules list\`.`,
24186
24346
  impl.id,
24187
24347
  entry.draftContext,
24188
24348
  void 0,
@@ -24199,8 +24359,8 @@ var callConformanceRule = {
24199
24359
  }
24200
24360
  const crossings = /* @__PURE__ */ new Map();
24201
24361
  const reached = /* @__PURE__ */ new Map();
24202
- const stopAtModelled = (site, bodyKey) => {
24203
- const owner = ownerOf4(site, bodyKey);
24362
+ const stopAtModelled = (site, bodyKey2) => {
24363
+ const owner = ownerOf4(site, bodyKey2);
24204
24364
  reached.set(site, owner);
24205
24365
  return owner !== void 0;
24206
24366
  };
@@ -24289,7 +24449,7 @@ var hiddenStateRule = {
24289
24449
  var dependencyConformanceRule = {
24290
24450
  name: "dependency-conformance",
24291
24451
  judges: "code",
24292
- description: "Code\u2194spec Level 2: runtime import edges between component-mapped source files (a component maps to every file its implementations and their methods name) must be justified by declared relations \u2014 a direct dependsOn/owns pair, a shared component, membership in a depended-on pattern, or (across subsystems) a declared edge to the target subsystem's published surface (UNDECLARED_DEPENDENCY). Conversely, a declared dependsOn/owns edge between components realized in different files should be visible as an import between any file of the source and any file of the target (UNREALIZED_DEPENDENCY \u2014 DI indirection can defeat this, hence warning). Only exact-grade analyzed files participate; type-only imports are exempt; chained subprojects validate standalone.",
24452
+ description: "Code\u2194spec Level 2: runtime import edges between component-mapped source files (a component maps to every file its implementations and their methods name) must be justified by declared relations \u2014 a direct dependsOn/owns pair, a shared component, membership in a depended-on pattern, or (across subsystems) a declared edge to the target subsystem's published surface (UNDECLARED_DEPENDENCY). Conversely, a declared dependsOn/owns edge between components realized in different files should be visible as an import between any file of the source and any file of the target (UNREALIZED_DEPENDENCY \u2014 DI indirection can defeat this, hence warning). A type-only import never accuses, but it does realize a declared edge: a constructor-injected collaborator is reached through its type, and `import type` is what dependency injection writes down. Only exact-grade analyzed files participate; chained subprojects validate standalone.",
24293
24453
  codes: [
24294
24454
  { code: "UNDECLARED_DEPENDENCY", defaultSeverity: "warning", summary: "A runtime import between component-mapped files has no declared dependsOn/owns (or published-surface) justification" },
24295
24455
  { code: "UNREALIZED_DEPENDENCY", defaultSeverity: "warning", summary: "A declared dependsOn/owns edge between components in different files is realized by no import between any of their files" }
@@ -24357,6 +24517,23 @@ var dependencyConformanceRule = {
24357
24517
  );
24358
24518
  }
24359
24519
  }
24520
+ const typeTraces = /* @__PURE__ */ new Map();
24521
+ const typeTracesOf = (path49) => {
24522
+ let out = typeTraces.get(path49);
24523
+ if (out) return out;
24524
+ out = /* @__PURE__ */ new Set();
24525
+ const facts = code.factsAt(path49);
24526
+ for (const specifier of new Set(Object.values(facts?.typeOnlyBindings ?? {}))) {
24527
+ const to = resolveImport(path49, specifier, mappedPaths, code.packages);
24528
+ if (to && to !== path49) out.add(to);
24529
+ }
24530
+ typeTraces.set(path49, out);
24531
+ return out;
24532
+ };
24533
+ const typeConnects = (from, to) => from.some((f) => {
24534
+ const traces = typeTracesOf(f);
24535
+ return to.some((t) => traces.has(t));
24536
+ });
24360
24537
  for (const component of ctx.components) {
24361
24538
  const fromFiles = filesOf(component.id);
24362
24539
  if (fromFiles.length === 0) continue;
@@ -24370,6 +24547,7 @@ var dependencyConformanceRule = {
24370
24547
  if (toFiles.length === 0) continue;
24371
24548
  if (fromFiles.some((f) => toFiles.includes(f))) continue;
24372
24549
  if (graph2.connects(fromFiles, toFiles, isMountingDeclarer)) continue;
24550
+ if (typeConnects(fromFiles, toFiles)) continue;
24373
24551
  const impls = mappedImplsOf(component.id);
24374
24552
  const declaredDependsOn = component.dependsOn.includes(targetId);
24375
24553
  const declaredOwns = (component.owns ?? []).includes(targetId);
@@ -24378,7 +24556,7 @@ var dependencyConformanceRule = {
24378
24556
  ctx.addIssue(
24379
24557
  "warning",
24380
24558
  "UNREALIZED_DEPENDENCY",
24381
- `Component "${component.id}" declares ${relation} "${targetId}", but no runtime import connects their source files (${fromFiles.join(", ")} \u219B ${crossSubsystem ? `subsystem ${target.subsystem}` : filesOf(targetId).join(", ")}) \u2014 either the collaboration is wired indirectly (DI) or the declared edge is stale.`,
24559
+ `Component "${component.id}" declares ${relation} "${targetId}", but no import \u2014 runtime or type-only \u2014 connects their source files (${fromFiles.join(", ")} \u219B ${crossSubsystem ? `subsystem ${target.subsystem}` : filesOf(targetId).join(", ")}) \u2014 either the collaboration is wired indirectly (DI) or the declared edge is stale.`,
24382
24560
  impls[0]?.id ?? component.id,
24383
24561
  draft,
24384
24562
  void 0,
@@ -24451,11 +24629,13 @@ var portalImportsRule = {
24451
24629
  var integrationSimDeclarationRule = {
24452
24630
  name: "integration-sim-declaration",
24453
24631
  judges: "code",
24454
- description: "Once a subsystem has adopted integration sims (its first declared L4 simPath), every OTHER complete, non-leaf implementation of that subsystem must declare one too: its unit suite proves contract shape against mocks, not that the wired components run together. A leaf component's unit suite IS its sim, and a subsystem that has adopted nothing is never flooded.",
24632
+ description: "Once a subsystem has adopted integration sims (its first declared L4 simPath), every OTHER complete, non-leaf implementation of that subsystem must declare one too: its unit suite proves contract shape against mocks, not that the wired components run together. A leaf component's unit suite IS its sim, a subsystem that has adopted nothing is never flooded, and an implementation with no code yet \u2014 no source file named, or only files that are missing \u2014 has nothing to wire and is not asked for a harness.",
24455
24633
  codes: [
24456
24634
  { code: "MISSING_INTEGRATION_SIM", defaultSeverity: "warning", summary: "Complete non-leaf implementation in a sim-adopting subsystem declares no simPath" }
24457
24635
  ],
24458
24636
  check(ctx) {
24637
+ const code = ctx.codeIndex();
24638
+ const hasCode = (files) => files.some((file) => code.factsAt(pathKey(file))?.status !== "missing");
24459
24639
  const adopted = /* @__PURE__ */ new Set();
24460
24640
  for (const impl of ctx.implementations) {
24461
24641
  if (!impl.simPath) continue;
@@ -24474,6 +24654,7 @@ var integrationSimDeclarationRule = {
24474
24654
  if (ctx.isImplementationDraft(impl)) continue;
24475
24655
  const deps = [.../* @__PURE__ */ new Set([...comp.dependsOn, ...comp.owns])].filter((d) => ctx.componentMap.has(d));
24476
24656
  if (deps.length === 0) continue;
24657
+ if (!hasCode(implementationSourceFiles(impl))) continue;
24477
24658
  ctx.addIssue(
24478
24659
  "warning",
24479
24660
  "MISSING_INTEGRATION_SIM",
@@ -25294,7 +25475,7 @@ var NOTHING_PROMISED = /* @__PURE__ */ new Set();
25294
25475
  var exportConformanceRule = {
25295
25476
  name: "export-conformance",
25296
25477
  judges: "code",
25297
- description: "Code-to-contract for the SURFACE: does this file publish anything the components it realizes never promised, that another component then takes? The rest of the conformance set reads contract-to-code \u2014 it asks whether the code holds what a spec claims \u2014 so a file could export whatever it liked under a component's name and nothing looked. That is one half of the same hole as a contract promising a parameter it never passes. A method's `exportedVia` names the export a consumer imports to REACH it \u2014 the value that composes it, which `symbol` cannot name because `symbol` names the function inside \u2014 and a listener's mount names the router entry it calls to hand a portal its requests; both are declared publication, and a declared handle the file does not actually export is itself a finding, so naming one can never become a free-text suppression. Both questions are asked only of a file read at exact grade, and a taker counts only when its own import specifier resolves to the file it is accused of taking from: below that nothing separates an export from a mention, or a shared word from a shared module.",
25478
+ description: "Code-to-contract for the SURFACE: does this file publish anything the components it realizes never promised, that another component then takes? The rest of the conformance set reads contract-to-code \u2014 it asks whether the code holds what a spec claims \u2014 so a file could export whatever it liked under a component's name and nothing looked. That is one half of the same hole as a contract promising a parameter it never passes. A method's `exportedVia` names the export a consumer imports to REACH it \u2014 the value that composes it, which `symbol` cannot name because `symbol` names the function inside \u2014 and a listener's mount names the router entry it calls to hand a portal its requests; both are declared publication, and a declared handle the file does not actually export is itself a finding, so naming one can never become a free-text suppression. Both questions are asked only of a file read at exact grade, and a taker counts only when its own import specifier resolves to the file it is accused of taking from: below that nothing separates an export from a mention, or a shared word from a shared module. A taker inside the owns fence is no crossing: a pattern constructs the blocks it owns, so the owner's file, or another member of the same pattern, importing a member's class is the owns relation made real.",
25298
25479
  codes: [
25299
25480
  {
25300
25481
  code: "UNDECLARED_EXPORT",
@@ -25316,6 +25497,7 @@ var exportConformanceRule = {
25316
25497
  check(ctx) {
25317
25498
  const code = ctx.codeIndex();
25318
25499
  const realization = ctx.realizationIndex();
25500
+ const ownership = ctx.ownershipIndex();
25319
25501
  const promised = /* @__PURE__ */ new Map();
25320
25502
  const promise = (file, name) => {
25321
25503
  if (!name) return;
@@ -25442,13 +25624,20 @@ var exportConformanceRule = {
25442
25624
  const declared = promised.get(file) ?? NOTHING_PROMISED;
25443
25625
  const taken = takenFrom.get(file);
25444
25626
  const owners = new Set(realization.componentsAt(file).map((c) => c.id));
25627
+ const fence = new Set(owners);
25628
+ for (const id of owners) {
25629
+ const pattern = ownership.ownerOf(id);
25630
+ if (pattern === void 0) continue;
25631
+ fence.add(pattern);
25632
+ for (const member of ownership.ownedMembers) if (ownership.ownerOf(member) === pattern) fence.add(member);
25633
+ }
25445
25634
  const crossed = [];
25446
25635
  for (const name of [...new Set(facts.exportedNames)].sort()) {
25447
25636
  if (declared.has(name)) continue;
25448
25637
  const takers = taken?.get(name);
25449
25638
  if (!takers) continue;
25450
25639
  const crossing = [...takers].some(
25451
- (taker) => taker !== file && !realization.componentsAt(taker).some((c) => owners.has(c.id))
25640
+ (taker) => taker !== file && !realization.componentsAt(taker).some((c) => fence.has(c.id))
25452
25641
  );
25453
25642
  if (crossing) crossed.push(name);
25454
25643
  }
@@ -27150,6 +27339,8 @@ function walkExact(ts, sourceText, fileName) {
27150
27339
  const typeOnlyBindings = /* @__PURE__ */ new Map();
27151
27340
  const fieldTypes = /* @__PURE__ */ new Map();
27152
27341
  const localTypes = /* @__PURE__ */ new Map();
27342
+ const memberTypes = /* @__PURE__ */ new Map();
27343
+ const aliasTargets = /* @__PURE__ */ new Map();
27153
27344
  const typeShapes = /* @__PURE__ */ new Map();
27154
27345
  const functionParams = /* @__PURE__ */ new Map();
27155
27346
  const functionRoutes = /* @__PURE__ */ new Map();
@@ -27159,7 +27350,7 @@ function walkExact(ts, sourceText, fileName) {
27159
27350
  const mutableBindings = /* @__PURE__ */ new Set();
27160
27351
  const schemaConstants = /* @__PURE__ */ new Map();
27161
27352
  const derivedAliases = [];
27162
- const receiverKey = (site) => site.via ?? (site.field ? `this.${site.field}` : site.constructed ? `new ${site.constructed}` : site.returnedBy ? `${site.returnedBy}()` : site.enclosingClass ? `this:${site.enclosingClass}` : "");
27353
+ const receiverKey = (site) => site.via ?? (site.field ? `this.${site.field}` : site.constructed ? `new ${site.constructed}` : site.returnedBy ? `${site.returnedBy}()` : site.enclosingClass ? `this:${site.enclosingClass}` : site.receiverPath ? site.receiverPath.join(".") : "");
27163
27354
  const siteKey = (site) => `${site.member ? "m" : "b"}:${receiverKey(site)}:${site.name}`;
27164
27355
  const addBindingNames = (name) => {
27165
27356
  if (ts.isIdentifier(name)) declared.add(name.text);
@@ -27174,7 +27365,34 @@ function walkExact(ts, sourceText, fileName) {
27174
27365
  if (ts.isPrivateIdentifier(name)) return name.text;
27175
27366
  return void 0;
27176
27367
  };
27177
- const typeReferenceName = (type) => type && ts.isTypeReferenceNode(type) && ts.isIdentifier(type.typeName) ? type.typeName.text : void 0;
27368
+ const MEMBER_PRESERVING = /* @__PURE__ */ new Set(["Pick", "Omit", "Partial", "Required", "Readonly"]);
27369
+ const typeReferenceName = (type) => {
27370
+ let at = type;
27371
+ for (let depth = 0; at && depth < 4; depth++) {
27372
+ if (!ts.isTypeReferenceNode(at) || !ts.isIdentifier(at.typeName)) return void 0;
27373
+ const name = at.typeName.text;
27374
+ if (!MEMBER_PRESERVING.has(name) || !at.typeArguments?.length) return name;
27375
+ at = at.typeArguments[0];
27376
+ }
27377
+ return void 0;
27378
+ };
27379
+ const recordMemberType = (shape, name, type) => {
27380
+ if (!shape) return;
27381
+ const typeName = typeReferenceName(type);
27382
+ if (!typeName) return;
27383
+ const property = propertyNameText(name);
27384
+ if (!property) return;
27385
+ const key = `${shape}.${property}`;
27386
+ const named2 = memberTypes.get(key) ?? /* @__PURE__ */ new Set();
27387
+ named2.add(typeName);
27388
+ memberTypes.set(key, named2);
27389
+ };
27390
+ const shapeNameOf = (holder) => {
27391
+ if (!holder) return void 0;
27392
+ if ((ts.isClassDeclaration(holder) || ts.isInterfaceDeclaration(holder)) && holder.name) return holder.name.text;
27393
+ if (ts.isTypeLiteralNode(holder) && holder.parent && ts.isTypeAliasDeclaration(holder.parent)) return holder.parent.name.text;
27394
+ return void 0;
27395
+ };
27178
27396
  const isParameterProperty = (node) => !!node.modifiers?.some((m) => m.kind === ts.SyntaxKind.PublicKeyword || m.kind === ts.SyntaxKind.PrivateKeyword || m.kind === ts.SyntaxKind.ProtectedKeyword || m.kind === ts.SyntaxKind.ReadonlyKeyword);
27179
27397
  const recordFieldType = (name, type) => {
27180
27398
  const typeName = typeReferenceName(type);
@@ -27371,7 +27589,18 @@ function walkExact(ts, sourceText, fileName) {
27371
27589
  } else if (receiver.kind === ts.SyntaxKind.ThisKeyword) {
27372
27590
  const owner = enclosingClassOf(node);
27373
27591
  addSite(owner !== void 0 ? { name, member: true, enclosingClass: owner } : { name, member: true });
27374
- } else addSite({ name, member: true });
27592
+ } else {
27593
+ const chain = [];
27594
+ let at = receiver;
27595
+ while (ts.isPropertyAccessExpression(at) && !at.questionDotToken && ts.isIdentifier(at.name)) {
27596
+ chain.unshift(at.name.text);
27597
+ at = at.expression;
27598
+ }
27599
+ if (ts.isIdentifier(at)) chain.unshift(at.text);
27600
+ else if (at.kind === ts.SyntaxKind.ThisKeyword) chain.unshift("this");
27601
+ else chain.length = 0;
27602
+ addSite(chain.length >= 2 ? { name, member: true, receiverPath: chain } : { name, member: true });
27603
+ }
27375
27604
  }
27376
27605
  }
27377
27606
  ts.forEachChild(node, count);
@@ -27506,9 +27735,14 @@ function walkExact(ts, sourceText, fileName) {
27506
27735
  functionRoutes.set(fnName, known);
27507
27736
  }
27508
27737
  }
27509
- if (ts.isPropertyDeclaration(node)) recordFieldType(node.name, node.type);
27510
- else if (ts.isParameter(node) && node.parent && ts.isConstructorDeclaration(node.parent) && isParameterProperty(node)) {
27738
+ if (ts.isPropertyDeclaration(node)) {
27739
+ recordFieldType(node.name, node.type);
27740
+ recordMemberType(shapeNameOf(node.parent), node.name, node.type);
27741
+ } else if (ts.isParameter(node) && node.parent && ts.isConstructorDeclaration(node.parent) && isParameterProperty(node)) {
27511
27742
  recordFieldType(node.name, node.type);
27743
+ recordMemberType(shapeNameOf(node.parent.parent), node.name, node.type);
27744
+ } else if (ts.isPropertySignature(node)) {
27745
+ recordMemberType(shapeNameOf(node.parent), node.name, node.type);
27512
27746
  }
27513
27747
  if (ts.isParameter(node) || ts.isVariableDeclaration(node)) recordLocalType(node.name, node.type);
27514
27748
  if (ts.isInterfaceDeclaration(node)) {
@@ -27522,6 +27756,10 @@ function walkExact(ts, sourceText, fileName) {
27522
27756
  if (ts.isTypeLiteralNode(node.type)) recordDeclaredShape(node.name.text, node.type.members, false);
27523
27757
  else {
27524
27758
  if (!aliasTypes.has(node.name.text)) aliasTypes.set(node.name.text, node.type.getText(sf));
27759
+ const named2 = typeReferenceName(node.type);
27760
+ if (named2 !== void 0 && named2 !== node.name.text && !aliasTargets.has(node.name.text)) {
27761
+ aliasTargets.set(node.name.text, named2);
27762
+ }
27525
27763
  const constant = inferredSchemaConstant(node.type);
27526
27764
  if (constant) derivedAliases.push({ name: node.name.text, constant });
27527
27765
  const literals = stringLiteralUnion(node.type);
@@ -27682,7 +27920,7 @@ function walkExact(ts, sourceText, fileName) {
27682
27920
  }
27683
27921
  const reexportOnly = sf.statements.length > 0 && sf.statements.every((st) => ts.isExportDeclaration(st) && !!st.moduleSpecifier);
27684
27922
  for (const alias of propertyAliases) if (exported.has(alias.container)) exportAliases.push(alias);
27685
- return { declared, anchors, exported, imports, reexports, starExports, namedReexports, exportAliases, returnTypes, complexity, calls, bodies, importBindings, typeOnlyBindings, fieldTypes, localTypes, typeShapes, functionParams, functionRoutes, asyncFunctions, enumValues, aliasTypes, mutableBindings, reexportOnly };
27923
+ return { declared, anchors, exported, imports, reexports, starExports, namedReexports, exportAliases, returnTypes, complexity, calls, bodies, importBindings, typeOnlyBindings, fieldTypes, localTypes, memberTypes, aliasTargets, typeShapes, functionParams, functionRoutes, asyncFunctions, enumValues, aliasTypes, mutableBindings, reexportOnly };
27686
27924
  }
27687
27925
  var NO_PACKAGES = /* @__PURE__ */ new Map();
27688
27926
  function resolveModule(fromFile, specifier, packages) {
@@ -28054,6 +28292,8 @@ function buildCodeModel(implementations, types, projectRoot, sourceRoots = [], e
28054
28292
  typeOnlyBindings: Object.fromEntries(facts.typeOnlyBindings),
28055
28293
  fieldTypes: Object.fromEntries([...facts.fieldTypes].map(([k, v]) => [k, [...v]])),
28056
28294
  localTypes: Object.fromEntries([...facts.localTypes].map(([k, v]) => [k, [...v]])),
28295
+ memberTypes: Object.fromEntries([...facts.memberTypes].map(([k, v]) => [k, [...v]])),
28296
+ aliasTargets: Object.fromEntries(facts.aliasTargets),
28057
28297
  typeShapes: Object.fromEntries(facts.typeShapes),
28058
28298
  functionParams: Object.fromEntries(facts.functionParams),
28059
28299
  functionRoutes: Object.fromEntries([...facts.functionRoutes].map(([k, v]) => [k, [...v.values()]])),
@@ -28498,8 +28738,16 @@ function buildRuleContext(opts) {
28498
28738
  }
28499
28739
  if (ownerId && nameKey(first) === nameKey(ownerId)) return ownTypes.some((spec) => typeMatchesRef(spec, rest.join("::")));
28500
28740
  if (ref.includes("::") && authoredTypeRefs.has(ref)) return true;
28741
+ return false;
28742
+ };
28743
+ const aliasSpelling = (ref) => {
28744
+ const segments = ref.split(".");
28745
+ if (segments.length !== 2 || ref.includes("::")) return null;
28746
+ const [first, name] = segments;
28747
+ if (subsystems.some((s) => nameKey(s.id.split("::").pop()) === nameKey(first) && memberOf(s.id) === void 0)) return null;
28501
28748
  const via = aliasOf(first);
28502
- return via !== void 0 && rest.length === 1 && exportedBy(via, nameKey(rest[0]), "type") !== void 0;
28749
+ const publicName = via ? exportedBy(via, nameKey(name), "type") : void 0;
28750
+ return via && publicName ? `${via.alias}::${publicName}` : null;
28503
28751
  };
28504
28752
  const foreignSurfaceRef = (ref) => {
28505
28753
  const segments = ref.split("::").filter((seg) => seg && seg !== "super");
@@ -28873,6 +29121,7 @@ function buildRuleContext(opts) {
28873
29121
  isImplementationDraft,
28874
29122
  getComponentProfile,
28875
29123
  isTypeResolved,
29124
+ aliasSpelling,
28876
29125
  targetLanguageFor,
28877
29126
  isSpecInScope,
28878
29127
  isInChainedSubproject,
@@ -31404,6 +31653,28 @@ function mapSignatureSource(value, map) {
31404
31653
  const head2 = map("signatureFrom", parts.head);
31405
31654
  return head2 === parts.head ? value : `${head2}.${parts.tail}`;
31406
31655
  }
31656
+ var TYPE_EXPRESSION_PATHS = {
31657
+ interface: ["methods.params.type", "methods.returns", "methods.signature", "methods.signatureFrom"],
31658
+ type: ["fields.type", "params.type", "returns", "methods.params.type", "methods.returns", "methods.signature"]
31659
+ };
31660
+ function typeExpressionSlots(kind, doc) {
31661
+ const out = [];
31662
+ const paths = kind === "interface" || kind === "type" ? TYPE_EXPRESSION_PATHS[kind] : [];
31663
+ const walk2 = (node, segments, at, method) => {
31664
+ if (!node || typeof node !== "object") return;
31665
+ const holder = node;
31666
+ const [head2, ...rest] = segments;
31667
+ if (rest.length === 0) {
31668
+ if (typeof holder[head2] === "string") out.push({ holder, key: head2, path: at, ...method ? { method } : {} });
31669
+ return;
31670
+ }
31671
+ const next = holder[head2];
31672
+ const items = Array.isArray(next) ? next : next && typeof next === "object" ? [next] : [];
31673
+ for (const item of items) walk2(item, rest, at, head2 === "methods" ? item : method);
31674
+ };
31675
+ for (const at of paths) walk2(doc, at.split("."), at, void 0);
31676
+ return out;
31677
+ }
31407
31678
  function mapSpecReferences(kind, spec, map) {
31408
31679
  switch (kind) {
31409
31680
  case "subsystem": {
@@ -31494,11 +31765,8 @@ function rawReferences(kind, spec) {
31494
31765
  for (const link of spec.trustedLinks ?? []) add2("trustedLinks", link.subsystem);
31495
31766
  break;
31496
31767
  case "interface":
31497
- for (const m of spec.methods) {
31498
- qualifiedTypeNames(m.returns).forEach((t) => add2("type", t));
31499
- m.params?.forEach((p) => qualifiedTypeNames(p.type).forEach((t) => add2("type", t)));
31500
- if (m.signatureFrom !== void 0 && !signatureSourceParts(m.signatureFrom)) add2("type", m.signatureFrom);
31501
- }
31768
+ case "type":
31769
+ typeSlotReferences(kind, spec).forEach((t) => add2("type", t));
31502
31770
  break;
31503
31771
  case "implementation":
31504
31772
  for (const m of spec.methods) {
@@ -31510,19 +31778,36 @@ function rawReferences(kind, spec) {
31510
31778
  }
31511
31779
  }
31512
31780
  break;
31513
- case "type": {
31514
- const t = spec;
31515
- for (const f of t.fields) qualifiedTypeNames(f.type).forEach((n) => add2("type", n));
31516
- t.params?.forEach((p) => qualifiedTypeNames(p.type).forEach((n) => add2("type", n)));
31517
- qualifiedTypeNames(t.returns).forEach((n) => add2("type", n));
31518
- for (const m of t.methods ?? []) m.params?.forEach((p) => qualifiedTypeNames(p.type).forEach((n) => add2("type", n)));
31519
- break;
31520
- }
31521
31781
  default:
31522
31782
  break;
31523
31783
  }
31524
31784
  return out;
31525
31785
  }
31786
+ function typeSlotReferences(kind, spec) {
31787
+ const out = [];
31788
+ const structured2 = /* @__PURE__ */ new Set();
31789
+ const slots = typeExpressionSlots(kind, spec);
31790
+ for (const slot of slots) {
31791
+ const value = slot.holder[slot.key];
31792
+ if (slot.path === "methods.signature") continue;
31793
+ if (slot.path === "methods.signatureFrom") {
31794
+ if (!signatureSourceParts(value)) out.push(value);
31795
+ continue;
31796
+ }
31797
+ for (const name of qualifiedTypeNames(value)) {
31798
+ out.push(name);
31799
+ structured2.add(name);
31800
+ }
31801
+ }
31802
+ for (const slot of slots.filter((s) => s.path === "methods.signature")) {
31803
+ for (const name of qualifiedTypeNames(slot.holder[slot.key])) {
31804
+ if (structured2.has(name)) continue;
31805
+ structured2.add(name);
31806
+ out.push(name);
31807
+ }
31808
+ }
31809
+ return out;
31810
+ }
31526
31811
  var IMPORTABLE_POSITIONS = /* @__PURE__ */ new Set(["dependsOn", "dispatch", "mounts", "narrative"]);
31527
31812
  function bareRawReferences(kind, spec) {
31528
31813
  const out = [];
@@ -31549,7 +31834,11 @@ function bareRawReferences(kind, spec) {
31549
31834
  const t = spec;
31550
31835
  for (const f of t.fields) for (const ref of fieldTypeRefs(t, f.type)) add2("type", ref);
31551
31836
  for (const ref of signatureTypeRefs(t)) add2("type", ref);
31552
- for (const m of t.methods ?? []) for (const p of m.params ?? []) for (const ref of fieldTypeRefs(t, p.type)) add2("type", ref);
31837
+ const own = lower(typeGenericParameters(t));
31838
+ for (const m of t.methods ?? []) {
31839
+ const generics = /* @__PURE__ */ new Set([...own, ...lower(methodGenericParameters(m))]);
31840
+ for (const ref of methodTypeRefs(m)) add2("type", ref, generics);
31841
+ }
31553
31842
  break;
31554
31843
  }
31555
31844
  case "implementation":
@@ -31562,6 +31851,9 @@ function bareRawReferences(kind, spec) {
31562
31851
  }
31563
31852
  return out;
31564
31853
  }
31854
+ function respellTypeNames(typeStr, map) {
31855
+ return mapTypeNames(typeStr, (_position, name) => map(name));
31856
+ }
31565
31857
  function mapTypeNames(typeStr, map) {
31566
31858
  if (typeStr === void 0) return void 0;
31567
31859
  return typeStr.replace(
@@ -31585,12 +31877,9 @@ function respellStoredReferences(kind, doc, map) {
31585
31877
  const values = holder[key];
31586
31878
  if (Array.isArray(values)) holder[key] = values.map((v) => typeof v === "string" ? map(position, v) : v);
31587
31879
  };
31588
- const typed = (holder, key) => {
31589
- if (typeof holder[key] === "string") holder[key] = mapTypeNames(holder[key], map);
31590
- };
31591
31880
  const published = (entries) => {
31592
31881
  for (const e of list(entries)) {
31593
- for (const key of ["from", "component", "interface", "typeDef"]) at(e, key, "publicInterfaces");
31882
+ for (const key of ["from", "subsystem", "component", "interface", "typeDef"]) at(e, key, "publicInterfaces");
31594
31883
  each(e, "consumers", "publicInterfaces");
31595
31884
  }
31596
31885
  };
@@ -31612,14 +31901,7 @@ function respellStoredReferences(kind, doc, map) {
31612
31901
  break;
31613
31902
  case "interface":
31614
31903
  at(doc, "component", "contract");
31615
- for (const m of list(doc.methods)) {
31616
- typed(m, "signature");
31617
- typed(m, "returns");
31618
- for (const p of list(m.params)) typed(p, "type");
31619
- if (typeof m.signatureFrom === "string") {
31620
- m.signatureFrom = signatureSourceParts(m.signatureFrom) ? mapSignatureSource(m.signatureFrom, map) : mapTypeNames(m.signatureFrom, map);
31621
- }
31622
- }
31904
+ typedSlots("interface", doc, map);
31623
31905
  break;
31624
31906
  case "implementation":
31625
31907
  at(doc, "contract", "contract");
@@ -31646,34 +31928,33 @@ function respellStoredReferences(kind, doc, map) {
31646
31928
  case "type":
31647
31929
  at(doc, "subsystem", "subsystem");
31648
31930
  at(doc, "group", "group");
31649
- for (const f of list(doc.fields)) typed(f, "type");
31650
- for (const p of list(doc.params)) typed(p, "type");
31651
- typed(doc, "returns");
31652
- for (const m of list(doc.methods)) for (const p of list(m.params)) typed(p, "type");
31931
+ typedSlots("type", doc, map);
31653
31932
  break;
31654
31933
  default:
31655
31934
  break;
31656
31935
  }
31657
31936
  }
31937
+ function typedSlots(kind, doc, map) {
31938
+ for (const slot of typeExpressionSlots(kind, doc)) {
31939
+ const value = slot.holder[slot.key];
31940
+ slot.holder[slot.key] = slot.path === "methods.signatureFrom" && signatureSourceParts(value) ? mapSignatureSource(value, map) : mapTypeNames(value, map);
31941
+ }
31942
+ }
31658
31943
  function mapRawReferences(kind, spec, map) {
31659
31944
  switch (kind) {
31660
31945
  case "subsystem": {
31661
31946
  const s = spec;
31662
31947
  return { ...s, trustedLinks: (s.trustedLinks ?? []).map((l) => ({ ...l, subsystem: map("trustedLinks", l.subsystem) })) };
31663
31948
  }
31664
- case "interface": {
31665
- const i = spec;
31666
- return {
31667
- ...i,
31668
- methods: i.methods.map((m) => ({
31669
- ...m,
31670
- ...m.signature !== void 0 ? { signature: mapTypeNames(m.signature, map) } : {},
31671
- ...m.returns !== void 0 ? { returns: mapTypeNames(m.returns, map) } : {},
31672
- ...m.params ? { params: m.params.map((p) => ({ ...p, type: mapTypeNames(p.type, map) })) } : {},
31673
- // A type source is a raw type position; a method source's head is bound, never raw.
31674
- ...m.signatureFrom !== void 0 && !signatureSourceParts(m.signatureFrom) ? { signatureFrom: mapTypeNames(m.signatureFrom, map) } : {}
31675
- }))
31676
- };
31949
+ case "interface":
31950
+ case "type": {
31951
+ const copy = JSON.parse(JSON.stringify(spec));
31952
+ for (const slot of typeExpressionSlots(kind, copy)) {
31953
+ const value = slot.holder[slot.key];
31954
+ if (slot.path === "methods.signatureFrom" && signatureSourceParts(value)) continue;
31955
+ slot.holder[slot.key] = mapTypeNames(value, map);
31956
+ }
31957
+ return copy;
31677
31958
  }
31678
31959
  case "implementation": {
31679
31960
  const impl = spec;
@@ -31698,17 +31979,6 @@ function mapRawReferences(kind, spec, map) {
31698
31979
  }))
31699
31980
  };
31700
31981
  }
31701
- case "type": {
31702
- const t = spec;
31703
- const typedParams = (params) => params ? { params: params.map((p) => ({ ...p, type: mapTypeNames(p.type, map) })) } : {};
31704
- return {
31705
- ...t,
31706
- fields: t.fields.map((f) => ({ ...f, type: mapTypeNames(f.type, map) })),
31707
- ...typedParams(t.params),
31708
- ...t.returns !== void 0 ? { returns: mapTypeNames(t.returns, map) } : {},
31709
- methods: (t.methods ?? []).map((m) => ({ ...m, ...typedParams(m.params) }))
31710
- };
31711
- }
31712
31982
  default:
31713
31983
  return spec;
31714
31984
  }
@@ -36095,6 +36365,12 @@ function readLockState(current2) {
36095
36365
  if (!record) return { state: "unlocked", record: null, current: current2 };
36096
36366
  return { state: stateIdEquals(record.stateId, current2) ? "locked" : "stale", record, current: current2 };
36097
36367
  }
36368
+ function writeLockRecord2(record) {
36369
+ writeLockRecord(record);
36370
+ }
36371
+ function renderDiagram2(format, relations) {
36372
+ return renderDiagram(format, relations);
36373
+ }
36098
36374
  function createProjectConfig(config) {
36099
36375
  projectConfigRepository.create(config);
36100
36376
  }
@@ -36928,7 +37204,7 @@ function isWithinDir2(dir, file) {
36928
37204
  var MOVED_REF_POSITIONS = /* @__PURE__ */ new Set(["component", "interface"]);
36929
37205
  var INTO_MEMBER_POSITIONS = /* @__PURE__ */ new Set([...MOVED_REF_POSITIONS, "type"]);
36930
37206
  function externalizeSubsystem(subsystemId, path49, as) {
36931
- externalizeInto(subsystemId, path49, as);
37207
+ return externalizeInto(subsystemId, path49, as);
36932
37208
  }
36933
37209
  function externalizeInto(subsystemId, partPath, as) {
36934
37210
  if (subsystemId.includes("::")) {
@@ -36947,7 +37223,8 @@ function externalizeInto(subsystemId, partPath, as) {
36947
37223
  const parentRoot = getProjectRoot();
36948
37224
  const parentSpecsDir = aiPathsAt(parentRoot).specsDir();
36949
37225
  const fooDir = path25.join(parentSpecsDir, subsystemId);
36950
- if (!fs16.existsSync(fooDir)) throw new WaironError(`subsystem specs directory not found: ${fooDir}`);
37226
+ const owned = subsystemFiles(subsystemId, parentSpecsDir);
37227
+ if (owned.files.length === 0) throw new WaironError(`subsystem specs not found for "${subsystemId}" under ${parentSpecsDir}`);
36951
37228
  const relPath = toPosixPath(partPath);
36952
37229
  const partDir = assertContainedProjectPath(parentRoot, relPath);
36953
37230
  if (path25.resolve(partDir) === path25.resolve(parentRoot)) throw new WaironError(`cannot externalize "${subsystemId}": the path "${relPath}" is the project root itself.`);
@@ -36960,19 +37237,74 @@ function externalizeInto(subsystemId, partPath, as) {
36960
37237
  if (!joined && (declaredMembers(projectConfigRepository.load() ?? {}).some((m) => m.alias === alias) || projectConfigRepository.load()?.externals?.[alias] !== void 0)) {
36961
37238
  throw new WaironError(`cannot externalize "${subsystemId}": this project already declares the alias "${alias}".`);
36962
37239
  }
36963
- const targetDir = path25.join(aiPathsAt(partDir).specsDir(), subsystemId);
36964
- if (fs16.existsSync(targetDir)) throw new WaironError(`target already contains a "${subsystemId}" subsystem: ${targetDir}`);
37240
+ const partSpecsDir = aiPathsAt(partDir).specsDir();
37241
+ const targetDir = path25.join(partSpecsDir, subsystemId);
37242
+ if (owned.home !== void 0 && fs16.existsSync(targetDir)) throw new WaironError(`target already contains a "${subsystemId}" subsystem: ${targetDir}`);
37243
+ const destinationOf = (file) => path25.join(partSpecsDir, path25.relative(parentSpecsDir, file));
37244
+ const strays = owned.files.filter((f) => !(owned.home !== void 0 && isWithinDir2(owned.home, f)));
37245
+ const taken = strays.filter((f) => fs16.existsSync(destinationOf(f)));
37246
+ if (taken.length > 0) throw new WaironError(`cannot externalize "${subsystemId}": the part already holds ${taken.map((f) => toPosixPath(path25.relative(partDir, destinationOf(f)))).join(", ")}.`);
36965
37247
  if (as === "project") {
36966
37248
  if (joined) throw new WaironError(`cannot externalize "${subsystemId}" as a project into the existing part "${joined.alias}": externalize it into the part, then promote the part (\`wairon member promote ${joined.alias}\`).`);
36967
- const refusals = promoteRefusals(family, specFilesUnder(fooDir), ownSpecFiles(family, fooDir), alias);
37249
+ const inside = new Set(owned.files.map((f) => path25.resolve(f)));
37250
+ const refusals = promoteRefusals(family, owned.files, ownSpecFiles(family, fooDir).filter((f) => !inside.has(path25.resolve(f))), alias);
36968
37251
  if (refusals.length > 0) throw new WaironError(`cannot externalize "${subsystemId}" as a project: ${refusals.join("; ")}.`);
36969
37252
  }
36970
- ensureDir(path25.dirname(targetDir));
36971
- fs16.renameSync(fooDir, targetDir);
36972
- rebaseImplementationPaths(targetDir, parentRoot, partDir);
37253
+ const moved = [];
37254
+ if (owned.home !== void 0) {
37255
+ ensureDir(path25.dirname(targetDir));
37256
+ fs16.renameSync(owned.home, targetDir);
37257
+ moved.push(...specFilesUnder(targetDir));
37258
+ }
37259
+ for (const file of strays) {
37260
+ const to = destinationOf(file);
37261
+ ensureDir(path25.dirname(to));
37262
+ fs16.renameSync(file, to);
37263
+ moved.push(to);
37264
+ removeEmptyDirsUpTo(path25.dirname(file), parentSpecsDir);
37265
+ }
37266
+ rebaseImplementationPaths(partSpecsDir, parentRoot, partDir, moved);
36973
37267
  if (!joined) projectConfigRepository.declareMember(alias, { source: relPath });
36974
37268
  invalidateSpecCache();
36975
- if (as === "project") promoteMember(alias);
37269
+ return as === "project" ? promoteMember(alias) : void 0;
37270
+ }
37271
+ function subsystemFiles(subsystemId, specsDir) {
37272
+ const subPath = getSubsystemPath(subsystemId);
37273
+ const nested = subPath.endsWith(".index.yaml") && path25.resolve(path25.dirname(subPath)) === path25.resolve(path25.join(specsDir, subsystemId)) && fs16.existsSync(subPath);
37274
+ const home = nested ? path25.dirname(subPath) : void 0;
37275
+ const files = new Set(home !== void 0 ? specFilesUnder(home).map((f) => path25.resolve(f)) : []);
37276
+ if (!nested && fs16.existsSync(subPath)) files.add(path25.resolve(subPath));
37277
+ const l0 = path25.resolve(aiPathsAt(getProjectRoot()).specsSystem());
37278
+ const others = () => specFilesUnder(specsDir).filter((f) => path25.resolve(f) !== l0 && !files.has(path25.resolve(f)));
37279
+ const add2 = (file) => {
37280
+ if (path25.basename(file) === ".index.yaml") for (const f of specFilesUnder(path25.dirname(file))) files.add(path25.resolve(f));
37281
+ else files.add(path25.resolve(file));
37282
+ };
37283
+ for (const file of others()) {
37284
+ let raw;
37285
+ try {
37286
+ raw = readYamlFile(file);
37287
+ } catch {
37288
+ continue;
37289
+ }
37290
+ const kind = raw && typeof raw === "object" ? specKind(raw) : void 0;
37291
+ if (kind === "subsystem" || kind === "system" || raw?.subsystem !== subsystemId) continue;
37292
+ add2(file);
37293
+ }
37294
+ const owned = () => rawSpecs([...files]);
37295
+ const components = new Set(owned().filter((x) => x.kind === "component").map((x) => x.id));
37296
+ for (const spec of rawSpecs(others())) if (spec.kind === "interface" && components.has(spec.raw?.component)) add2(spec.file);
37297
+ const contracts = new Set(owned().filter((x) => x.kind === "interface").map((x) => x.id));
37298
+ for (const spec of rawSpecs(others())) if (spec.kind === "implementation" && contracts.has(spec.raw?.contract)) add2(spec.file);
37299
+ return { home, files: [...files] };
37300
+ }
37301
+ function removeEmptyDirsUpTo(dir, stop) {
37302
+ let at = path25.resolve(dir);
37303
+ const end = path25.resolve(stop);
37304
+ while (at !== end && isWithinDir2(end, at) && fs16.existsSync(at) && fs16.readdirSync(at).length === 0) {
37305
+ fs16.rmdirSync(at);
37306
+ at = path25.dirname(at);
37307
+ }
36976
37308
  }
36977
37309
  function specFilesUnder(dir) {
36978
37310
  return fs16.existsSync(dir) ? listFilesRecursive(dir, ".yaml") : [];
@@ -36998,7 +37330,8 @@ function rawSpecs(files) {
36998
37330
  return out;
36999
37331
  }
37000
37332
  function typeTokens(expression) {
37001
- return typeof expression === "string" ? [...expression.matchAll(/[A-Za-z_][A-Za-z0-9_-]*/g)].map((m) => m[0]) : [];
37333
+ if (typeof expression !== "string") return [];
37334
+ return [...expression.matchAll(/(?:::)?[A-Za-z_][A-Za-z0-9_-]*(?:(?:::|\.)[A-Za-z_][A-Za-z0-9_-]*)*/g)].map((m) => m[0]).filter((t) => !t.includes(".") && !t.includes("::"));
37002
37335
  }
37003
37336
  function referencesOf(spec, wholeParams) {
37004
37337
  const out = [];
@@ -37007,13 +37340,13 @@ function referencesOf(spec, wholeParams) {
37007
37340
  return ref;
37008
37341
  });
37009
37342
  const expressions = [];
37010
- if (spec.kind === "interface") {
37011
- for (const m of spec.raw.methods ?? []) {
37012
- expressions.push(m?.returns);
37013
- for (const p of m?.params ?? []) if (wholeParams || !(typeof p?.type === "string" && /^[A-Za-z_][A-Za-z0-9_-]*$/.test(p.type))) expressions.push(p?.type);
37014
- }
37343
+ for (const slot of typeExpressionSlots(spec.kind, spec.raw)) {
37344
+ const value = slot.holder[slot.key];
37345
+ if (slot.path === "methods.signatureFrom") continue;
37346
+ if (slot.path === "methods.signature" && Array.isArray(slot.method?.params)) continue;
37347
+ if (slot.path === "methods.params.type" && spec.kind === "interface" && !wholeParams && /^[A-Za-z_][A-Za-z0-9_-]*$/.test(String(value))) continue;
37348
+ expressions.push(slot.path === "methods.signature" ? String(value).replace(/^\s*[A-Za-z_][\w-]*\s*(?:<[^>]*>)?\s*\(/, "(").replace(/(?<![:\w-])[A-Za-z_][\w-]*\s*\??\s*:(?!:)/g, "") : value);
37015
37349
  }
37016
- if (spec.kind === "type") for (const f of spec.raw.fields ?? []) expressions.push(f?.type);
37017
37350
  for (const token of expressions.flatMap(typeTokens)) out.push({ ref: token, position: "bare-type" });
37018
37351
  return out;
37019
37352
  }
@@ -37021,6 +37354,9 @@ function crossingsOf(inside, outside) {
37021
37354
  const idsOf = (specs) => new Set(specs.filter((s) => s.kind !== "system").map((s) => s.id));
37022
37355
  const insideIds = idsOf(inside);
37023
37356
  const outsideIds = idsOf(outside);
37357
+ const targetsOf2 = (specs) => new Set(specs.filter((s) => s.kind === "component" || s.kind === "interface" || s.kind === "type").map((s) => s.id));
37358
+ const insideTargets = targetsOf2(inside);
37359
+ const outsideTargets = targetsOf2(outside);
37024
37360
  const typeIds = (specs) => new Map(specs.filter((s) => s.kind === "type").map((s) => [nameKey(s.id), s.id]));
37025
37361
  const insideTypes = typeIds(inside);
37026
37362
  const outsideTypes = typeIds(outside);
@@ -37038,13 +37374,14 @@ function crossingsOf(inside, outside) {
37038
37374
  }
37039
37375
  return found;
37040
37376
  };
37041
- return { out: collect(inside, outsideIds, outsideTypes, true), in: collect(outside, insideIds, insideTypes, false), insideIds, outsideIds };
37377
+ return { out: collect(inside, outsideTargets, outsideTypes, true), in: collect(outside, insideTargets, insideTypes, false), insideIds, outsideIds, insideTargets, outsideTargets };
37042
37378
  }
37043
37379
  function promoteRefusals(family, insideFiles, outsideFiles, id) {
37044
37380
  const out = [];
37045
37381
  const inside = rawSpecs(insideFiles);
37046
37382
  const outside = rawSpecs(outsideFiles);
37047
37383
  const crossing = crossingsOf(inside, outside);
37384
+ out.push(...unreachableUpward(inside, outside));
37048
37385
  const subsystemsIn = new Set(inside.filter((s) => s.kind === "subsystem").map((s) => s.id));
37049
37386
  for (const s of [...inside, ...outside].filter((x) => x.kind === "subsystem")) {
37050
37387
  for (const link of s.raw.trustedLinks ?? []) {
@@ -37086,13 +37423,19 @@ function climbsOf(family, ids, parentId) {
37086
37423
  return { map, refused: [...new Set(refused2)] };
37087
37424
  }
37088
37425
  var PROJECT_ID_RE_LOCAL = /^[a-z0-9](?:[a-z0-9._-]*[a-z0-9])?$/;
37089
- function rewriteRefsIn(specs, remap) {
37426
+ function rewriteRefsIn(specs, remap, record) {
37090
37427
  const rewritten = [];
37091
37428
  for (const spec of specs) {
37092
37429
  const raw = readYamlFile(spec.file);
37093
- if (!rewriteSpecRefs(raw, (ref, position) => remap(ref, position))) continue;
37430
+ const edits = [];
37431
+ if (!rewriteSpecRefs(raw, (ref, position) => {
37432
+ const to = remap(ref, position);
37433
+ if (to !== ref) edits.push({ kind: spec.kind, specId: spec.id, position, from: ref, to });
37434
+ return to;
37435
+ })) continue;
37094
37436
  writeYamlFile(spec.file, raw);
37095
37437
  rewritten.push({ kind: spec.kind, id: spec.id });
37438
+ if (record) for (const e of edits) recordRespelling(record.result, e, record.member);
37096
37439
  }
37097
37440
  return rewritten;
37098
37441
  }
@@ -37142,15 +37485,19 @@ function promoteMember(alias, id) {
37142
37485
  patchKeepingStamp(s.file, (raw) => raw.parentSystem === projectName ? false : (raw.parentSystem = projectName, true));
37143
37486
  }
37144
37487
  if (declaration2?.as === "part") projectConfigRepository.updateMember(alias, { as: "" });
37145
- const parentSide = rewriteRefsIn(crossing.in.map((c) => c.spec).filter(unique), (ref, position) => INTO_MEMBER_POSITIONS.has(position) && crossing.insideIds.has(ref) ? `${alias}::${ref}` : ref);
37488
+ const result = { respelled: [], memberRespelled: [], exported: [], imported: [], consumersDropped: [] };
37489
+ const parentSide = rewriteRefsIn(crossing.in.map((c) => c.spec).filter(unique), (ref, position) => INTO_MEMBER_POSITIONS.has(position) && crossing.insideTargets.has(ref) ? `${alias}::${ref}` : ref, { result, member: false });
37146
37490
  const climbs = climbsOf(family, crossing.insideIds, parentId).map;
37147
37491
  const climbing = inside.filter((s) => family.authoredReferences.some((r) => r.form === "super" && r.specId === s.id));
37148
- const memberSide = rewriteRefsIn([...crossing.out.map((c) => c.spec), ...climbing].filter(unique), (ref, position) => climbs.get(ref) ?? (MOVED_REF_POSITIONS.has(position) && crossing.outsideIds.has(ref) ? `${parentId}::${ref}` : ref));
37492
+ const memberSide = rewriteRefsIn([...crossing.out.map((c) => c.spec), ...climbing].filter(unique), (ref, position) => climbs.get(ref) ?? (MOVED_REF_POSITIONS.has(position) && crossing.outsideTargets.has(ref) ? `${parentId}::${ref}` : ref), { result, member: true });
37493
+ const qualified = respellQualified(inside, outside, { alias, parentId, newId }, result);
37149
37494
  invalidateSpecCache();
37150
- if (crossing.out.length > 0 || [...climbs.values()].some((t) => t.startsWith(`${parentId}::`))) runWithProjectRoot(dir, () => projectConfigRepository.declareExternal(parentId, {}));
37495
+ dropCrossingConsumers(inside, outside, result);
37496
+ if (crossing.out.length > 0 || qualified.out.length > 0 || [...climbs.values()].some((t) => t.startsWith(`${parentId}::`))) runWithProjectRoot(dir, () => projectConfigRepository.declareExternal(parentId, {}));
37151
37497
  for (const s of inside.filter((x) => x.kind === "subsystem")) restateReExports(s.id, alias);
37152
- exportCrossings(crossing.out, dir, alias, parentId, "out");
37153
- exportCrossings(crossing.in, dir, alias, parentId, "in");
37498
+ exportCrossings([...crossing.out, ...qualified.out], dir, alias, parentId, "out", result);
37499
+ exportCrossings([...crossing.in, ...qualified.in], dir, alias, parentId, "in", result);
37500
+ exportReExported(inside, dir, alias, result);
37154
37501
  invalidateSpecCache();
37155
37502
  for (const spec of parentSide) if (spec.kind !== "system") normalizeReferences(spec.kind, spec.id);
37156
37503
  const memberKey = graph().nodes.find((n) => n.parent === "" && n.mountAlias === alias)?.namespace;
@@ -37169,6 +37516,165 @@ function promoteMember(alias, id) {
37169
37516
  });
37170
37517
  }
37171
37518
  invalidateSpecCache();
37519
+ return result;
37520
+ }
37521
+ function recordRespelling(result, edit4, member) {
37522
+ if (edit4.from === edit4.to) return;
37523
+ const list = member ? result.memberRespelled : result.respelled;
37524
+ if (list.some((e) => e.kind === edit4.kind && e.specId === edit4.specId && e.position === edit4.position && e.from === edit4.from)) return;
37525
+ list.push(edit4);
37526
+ }
37527
+ function assertedTypeRef(ref) {
37528
+ const at = ref.lastIndexOf(".");
37529
+ return at > 0 && at < ref.length - 1 ? ref.slice(0, at) : void 0;
37530
+ }
37531
+ function sidedTypes(inside, outside) {
37532
+ const types = [
37533
+ ...inside.filter((s) => s.kind === "type").map((s) => ({ spec: s, side: "inside" })),
37534
+ ...outside.filter((s) => s.kind === "type").map((s) => ({ spec: s, side: "outside" }))
37535
+ ];
37536
+ const subsystems = new Set([...inside, ...outside].filter((s) => s.kind === "subsystem").map((s) => nameKey(s.id)));
37537
+ return {
37538
+ qualified: (token) => {
37539
+ const segments = token.split(/::|\./);
37540
+ if (segments.length !== 2 || segments.some((x) => x === "") || !subsystems.has(nameKey(segments[0]))) return void 0;
37541
+ const found = types.filter((t) => typeof t.spec.raw.subsystem === "string" && nameKey(t.spec.raw.subsystem) === nameKey(segments[0]) && nameKey(t.spec.id) === nameKey(segments[1]));
37542
+ return found.length === 1 ? found[0] : void 0;
37543
+ },
37544
+ bare: (token) => {
37545
+ const found = types.filter((t) => nameKey(t.spec.id) === nameKey(token));
37546
+ return found.length === 1 ? found[0] : void 0;
37547
+ }
37548
+ };
37549
+ }
37550
+ function unreachableUpward(inside, outside) {
37551
+ const out = [];
37552
+ const types = sidedTypes(inside, outside);
37553
+ const outsideComponents = new Set(outside.filter((x) => x.kind === "component").map((x) => x.id));
37554
+ const target = (ref) => ref.includes(".") || ref.includes("::") ? types.qualified(ref) : types.bare(ref);
37555
+ for (const spec of inside) {
37556
+ if (spec.kind === "interface") {
37557
+ for (const m of spec.raw.methods ?? []) {
37558
+ const source = m?.signatureFrom;
37559
+ if (typeof source !== "string") continue;
37560
+ const head2 = source.includes(".") ? source.slice(0, source.lastIndexOf(".")) : void 0;
37561
+ if (target(source)?.side === "outside" || head2 !== void 0 && outsideComponents.has(head2)) {
37562
+ out.push(`"${spec.id}" method "${m.name}" takes its signature from "${source}", which stays in this project: a signatureFrom is not read across a project boundary \u2014 state its params and returns first`);
37563
+ }
37564
+ }
37565
+ }
37566
+ if (spec.kind === "implementation") {
37567
+ for (const m of spec.raw.methods ?? []) {
37568
+ for (const step of m?.narrative ?? []) {
37569
+ for (const ref of step?.assertsInvariants ?? []) {
37570
+ const typeRef2 = typeof ref === "string" ? assertedTypeRef(ref) : void 0;
37571
+ if (typeRef2 !== void 0 && !typeRef2.includes("::") && target(typeRef2)?.side === "outside") {
37572
+ out.push(`"${spec.id}" asserts the invariant "${ref}" of an entity that stays in this project: an invariant is asserted by its own project's write paths \u2014 move the assertion first`);
37573
+ }
37574
+ }
37575
+ }
37576
+ }
37577
+ }
37578
+ }
37579
+ return out;
37580
+ }
37581
+ function respellQualified(inside, outside, names, result) {
37582
+ const crossings = { out: [], in: [] };
37583
+ const { qualified: qualifiedTarget, bare: bareTarget } = sidedTypes(inside, outside);
37584
+ const respell = (spec, side, token, target, selfPrefix2) => {
37585
+ if (!target) return token;
37586
+ if (target.side !== side) {
37587
+ const to = `${side === "outside" ? names.alias : names.parentId}::${target.spec.id}`;
37588
+ (side === "inside" ? crossings.out : crossings.in).push({ spec, target: target.spec.id, position: "type" });
37589
+ return to;
37590
+ }
37591
+ return side === "inside" && selfPrefix2 ? target.spec.id : token;
37592
+ };
37593
+ for (const [specs, side] of [[inside, "inside"], [outside, "outside"]]) {
37594
+ for (const spec of specs) {
37595
+ if (!fs16.existsSync(spec.file)) continue;
37596
+ const raw = readYamlFile(spec.file);
37597
+ if (!raw || typeof raw !== "object") continue;
37598
+ const edits = [];
37599
+ const record = (from, to) => {
37600
+ if (from !== to) edits.push({ kind: spec.kind, specId: spec.id, position: "type", from, to });
37601
+ };
37602
+ const mapToken = (token) => {
37603
+ const target = qualifiedTarget(token);
37604
+ const to = respell(spec, side, token, target, nameKey(token.split(/::|\./)[0]) === nameKey(names.newId));
37605
+ record(token, to);
37606
+ return to;
37607
+ };
37608
+ for (const slot of typeExpressionSlots(spec.kind, raw)) {
37609
+ const value = slot.holder[slot.key];
37610
+ if (slot.path === "methods.signatureFrom" && !qualifiedTarget(value)) continue;
37611
+ slot.holder[slot.key] = respellTypeNames(value, mapToken);
37612
+ }
37613
+ if (spec.kind === "implementation") {
37614
+ for (const m of raw.methods ?? []) {
37615
+ for (const step of m?.narrative ?? []) {
37616
+ if (!Array.isArray(step?.assertsInvariants)) continue;
37617
+ step.assertsInvariants = step.assertsInvariants.map((ref) => {
37618
+ const typeRef2 = typeof ref === "string" ? assertedTypeRef(ref) : void 0;
37619
+ if (typeRef2 === void 0 || typeRef2.includes("::") && !qualifiedTarget(typeRef2)) return ref;
37620
+ const target = typeRef2.includes(".") || typeRef2.includes("::") ? qualifiedTarget(typeRef2) : bareTarget(typeRef2);
37621
+ const to = respell(spec, side, typeRef2, target, typeRef2.includes(".") && nameKey(typeRef2.split(".")[0]) === nameKey(names.newId));
37622
+ if (to === typeRef2) return ref;
37623
+ record(ref, `${to}${ref.slice(typeRef2.length)}`);
37624
+ return `${to}${ref.slice(typeRef2.length)}`;
37625
+ });
37626
+ }
37627
+ }
37628
+ }
37629
+ if (edits.length === 0) continue;
37630
+ writeYamlFile(spec.file, raw);
37631
+ for (const e of edits) recordRespelling(result, e, side === "inside");
37632
+ }
37633
+ }
37634
+ return crossings;
37635
+ }
37636
+ function dropCrossingConsumers(inside, outside, result) {
37637
+ const idsOf = (specs) => new Set(specs.filter((s) => s.kind === "subsystem").map((s) => s.id));
37638
+ const sides = [[inside, idsOf(outside)], [outside, idsOf(inside)]];
37639
+ for (const [specs, across] of sides) {
37640
+ for (const spec of specs.filter((s) => s.kind === "subsystem")) {
37641
+ patchKeepingStamp(spec.file, (raw) => {
37642
+ let changed = false;
37643
+ for (const entry of raw.publicInterfaces ?? []) {
37644
+ if (!Array.isArray(entry?.consumers)) continue;
37645
+ const dropped = entry.consumers.filter((c) => typeof c === "string" && across.has(c));
37646
+ if (dropped.length === 0) continue;
37647
+ const kept = entry.consumers.filter((c) => !dropped.includes(c));
37648
+ if (kept.length > 0) entry.consumers = kept;
37649
+ else delete entry.consumers;
37650
+ changed = true;
37651
+ const what = entry.component ?? entry.typeDef ?? entry.interface ?? entry.from ?? "(entry)";
37652
+ result.consumersDropped.push(`${spec.id} ${what}: ${dropped.join(", ")}${kept.length === 0 ? " (the list is gone: the L0 export table governs who outside may use it)" : ""}`);
37653
+ }
37654
+ return changed;
37655
+ });
37656
+ }
37657
+ }
37658
+ }
37659
+ function exportReExported(inside, memberDir, alias, result) {
37660
+ invalidateSpecCache();
37661
+ const entries = (loadSystemSpec()?.publicInterfaces ?? []).filter((e) => e.from === alias);
37662
+ if (entries.length === 0) return;
37663
+ const targets = entries.map((e) => e.component ?? e.typeDef).filter((t) => t !== void 0);
37664
+ const whole = entries.some((e) => e.component === void 0 && e.typeDef === void 0);
37665
+ runWithProjectRoot(memberDir, () => {
37666
+ for (const name of exportItems(targets)) result.exported.push(`${alias} L0: ${name} (re-exported by the parent)`);
37667
+ if (!whole) return;
37668
+ invalidateSpecCache();
37669
+ const system = loadSystemSpec();
37670
+ if (!system) return;
37671
+ const own = system.publicInterfaces ?? [];
37672
+ const added = inside.filter((s) => s.kind === "subsystem" && !own.some((e) => e.from === s.id && e.component === void 0 && e.typeDef === void 0)).map((s) => ({ from: s.id, audience: "project" }));
37673
+ if (added.length === 0) return;
37674
+ saveSystemSpec({ ...system, publicInterfaces: [...own, ...added] });
37675
+ for (const e of added) result.exported.push(`${alias} L0: everything ${e.from} exports (re-exported whole by the parent)`);
37676
+ });
37677
+ invalidateSpecCache();
37172
37678
  }
37173
37679
  function promotedVision(id, parentName) {
37174
37680
  return `Project ${id}, promoted from a part of ${parentName}`;
@@ -37176,43 +37682,57 @@ function promotedVision(id, parentName) {
37176
37682
  function unique(value, index, all) {
37177
37683
  return all.indexOf(value) === index;
37178
37684
  }
37179
- function exportCrossings(crossings, memberDir, alias, parentId, direction) {
37685
+ function exportCrossings(crossings, memberDir, alias, parentId, direction, result) {
37180
37686
  const targets = [...new Set(crossings.map((c) => c.target))];
37181
37687
  const bare = [...new Set(crossings.filter((c) => c.position === "bare-type").map((c) => c.target))];
37182
37688
  if (direction === "out") {
37183
- exportItems(targets);
37184
- if (bare.length > 0) projectConfigRepositoryAt(memberDir).importNames(parentId, bare);
37689
+ for (const name of exportItems(targets)) result.exported.push(`${parentId} L0: ${name}`);
37690
+ if (bare.length > 0) {
37691
+ projectConfigRepositoryAt(memberDir).importNames(parentId, bare);
37692
+ result.imported.push(`${alias}: externals.${parentId}.use gains ${bare.join(", ")}`);
37693
+ }
37185
37694
  } else {
37186
- runWithProjectRoot(memberDir, () => exportItems(targets));
37187
- if (bare.length > 0) projectConfigRepository.importNames(alias, bare);
37695
+ for (const name of runWithProjectRoot(memberDir, () => exportItems(targets))) result.exported.push(`${alias} L0: ${name}`);
37696
+ if (bare.length > 0) {
37697
+ projectConfigRepository.importNames(alias, bare);
37698
+ result.imported.push(`${parentId}: members.${alias}.use gains ${bare.join(", ")}`);
37699
+ }
37188
37700
  }
37189
37701
  }
37190
37702
  function localIn(key, project) {
37191
37703
  return project && key.startsWith(`${project}::`) ? key.slice(project.length + 2) : key;
37192
37704
  }
37193
37705
  function exportItems(ids) {
37194
- if (ids.length === 0) return;
37706
+ if (ids.length === 0) return [];
37195
37707
  invalidateSpecCache();
37196
37708
  const system = loadSystemSpec();
37197
- if (!system) return;
37709
+ if (!system) return [];
37198
37710
  const entries = [...system.publicInterfaces ?? []];
37199
37711
  const types = loadTypeSpecs();
37712
+ const added = [];
37200
37713
  for (const id of ids) {
37201
37714
  if (!PUBLIC_NAME_RE.test(id)) continue;
37202
37715
  const type = types.find((t) => t.id === id);
37203
37716
  if (type) {
37204
37717
  if (type.subsystem) exportTypeFromSubsystem(type.subsystem, id);
37205
- if (!entries.some((e) => e.typeDef === id && e.from === void 0)) entries.push({ typeDef: id, audience: "project" });
37718
+ if (!entries.some((e) => e.typeDef === id && (e.from === void 0 || e.from === type.subsystem))) {
37719
+ entries.push({ ...type.subsystem ? { from: type.subsystem } : {}, typeDef: id, audience: "project" });
37720
+ added.push(type.subsystem ? `${id} (from ${type.subsystem})` : id);
37721
+ }
37206
37722
  continue;
37207
37723
  }
37208
37724
  const component = loadComponentSpec(id);
37209
37725
  if (!component) continue;
37210
37726
  const exported = resolveSubsystemExports(component.subsystem).entries.some((e) => e.kind === "component" && e.component === id);
37211
- if (exported && !entries.some((e) => e.component === id)) entries.push({ from: component.subsystem, component: id, audience: "project" });
37727
+ if (exported && !entries.some((e) => e.component === id)) {
37728
+ entries.push({ from: component.subsystem, component: id, audience: "project" });
37729
+ added.push(`${id} (from ${component.subsystem})`);
37730
+ }
37212
37731
  }
37213
37732
  if (entries.length !== (system.publicInterfaces ?? []).length) {
37214
37733
  saveSystemSpec({ ...system, publicInterfaces: entries });
37215
37734
  }
37735
+ return added;
37216
37736
  }
37217
37737
  function exportTypeFromSubsystem(subsystemId, typeId) {
37218
37738
  const sub = loadSubsystemSpec(subsystemId);
@@ -37256,7 +37776,7 @@ function internalizeMember(alias, destination) {
37256
37776
  if (refusals.length > 0) {
37257
37777
  throw new WaironError(`cannot internalize "${alias}": the member cannot be taken in whole \u2014 ${refusals.join("; ")}.`);
37258
37778
  }
37259
- const result = { subsystems: scan.subsystems, types: scan.types, placed: [], externals: [], deleted: [], notCarried: [] };
37779
+ const result = { subsystems: scan.subsystems, types: scan.types, placed: [], externals: [], deleted: [], notCarried: [], consumersRestored: [] };
37260
37780
  const intoMember = crossingReferences(family, "", member.namespace);
37261
37781
  const backOut = crossingReferences(family, member.namespace, "");
37262
37782
  deleteMount(alias);
@@ -37286,7 +37806,7 @@ function internalizePart(alias, part) {
37286
37806
  projectConfigRepository.removeMember(alias);
37287
37807
  const deleted = deleteMemberWai(partDir);
37288
37808
  invalidateSpecCache();
37289
- return { subsystems: part.subsystems, types, placed: [], externals: [], deleted, notCarried: [] };
37809
+ return { subsystems: part.subsystems, types, placed: [], externals: [], deleted, notCarried: [], consumersRestored: [] };
37290
37810
  }
37291
37811
  function scanMember(family, alias, member, destination) {
37292
37812
  const root = getProjectRoot();
@@ -37635,7 +38155,7 @@ function demoteMember(alias, destination) {
37635
38155
  refusals.push(`its metadata needs a home subsystem (--home) \u2014 ${home === "" ? "none was named" : `"${home}" is no subsystem of the member or of this project`}; its subsystems are ${subsystems.join(", ") || "none"}`);
37636
38156
  }
37637
38157
  if (refusals.length > 0) throw new WaironError(`demote refused: "${alias}" cannot become a part \u2014 ${refusals.join("; ")}.`);
37638
- const result = { subsystems, types, placed: [], externals: [], deleted: [], notCarried: [] };
38158
+ const result = { subsystems, types, placed: [], externals: [], deleted: [], notCarried: [], consumersRestored: [] };
37639
38159
  const intoMember = localsOf(dir);
37640
38160
  const backOut = localsOf(root);
37641
38161
  invalidateSpecCache();
@@ -37652,6 +38172,10 @@ function demoteMember(alias, destination) {
37652
38172
  const parentSide = rewriteRefsIn(ownSpecs, across(/* @__PURE__ */ new Set([alias]), intoMember, false));
37653
38173
  const memberSide = rewriteRefsIn(memberSpecs, across(parentAliases, backOut, true));
37654
38174
  for (const pa of parentAliases) for (const name of config?.externals?.[pa]?.use ?? []) if (name !== "*") crossed.add(backOut.get(name) ?? name);
38175
+ const memberNow = rawSpecs(specFilesUnder(aiPathsAt(dir).specsDir()).filter((f) => path25.resolve(f) !== path25.resolve(aiPathsAt(dir).specsSystem())));
38176
+ const parentNow = rawSpecs(ownSpecFiles(family, aiPathsAt(dir).specsDir()));
38177
+ respellQualifiedBack(parentNow, memberNow, { intoMember: (a) => a === alias, backOut: (a) => parentAliases.has(a) }, { intoMember, backOut }, crossed);
38178
+ restoreCrossingConsumers(memberNow, parentNow, result);
37655
38179
  const parentName = loadSystemSpec()?.name;
37656
38180
  if (parentName) {
37657
38181
  for (const s of memberSpecs.filter((x) => x.kind === "subsystem")) {
@@ -37676,6 +38200,79 @@ function demoteMember(alias, destination) {
37676
38200
  invalidateSpecCache();
37677
38201
  return result;
37678
38202
  }
38203
+ function respellQualifiedBack(parentSide, memberSide, aliases, tables, crossed) {
38204
+ const types = [...parentSide, ...memberSide].filter((s) => s.kind === "type");
38205
+ const localForm = (id) => {
38206
+ const found = types.filter((t) => t.id === id);
38207
+ if (found.length !== 1) return void 0;
38208
+ const sub = found[0].raw?.subsystem;
38209
+ return typeof sub === "string" && sub !== "" ? `${sub}.${id}` : id;
38210
+ };
38211
+ for (const [specs, across, table, record] of [
38212
+ [parentSide, aliases.intoMember, tables.intoMember, false],
38213
+ [memberSide, aliases.backOut, tables.backOut, true]
38214
+ ]) {
38215
+ const local = (token) => {
38216
+ const segments = token.split("::");
38217
+ if (segments.length !== 2 || !across(segments[0])) return token;
38218
+ const id = table.get(segments[1]) ?? segments[1];
38219
+ const to = localForm(id);
38220
+ if (to === void 0) return token;
38221
+ if (record) crossed.add(id);
38222
+ return to;
38223
+ };
38224
+ for (const spec of specs) {
38225
+ if (!fs16.existsSync(spec.file)) continue;
38226
+ const raw = readYamlFile(spec.file);
38227
+ if (!raw || typeof raw !== "object") continue;
38228
+ let changed = false;
38229
+ for (const slot of typeExpressionSlots(spec.kind, raw)) {
38230
+ const value = slot.holder[slot.key];
38231
+ const next = respellTypeNames(value, local);
38232
+ if (next !== value) {
38233
+ slot.holder[slot.key] = next;
38234
+ changed = true;
38235
+ }
38236
+ }
38237
+ if (spec.kind === "implementation") {
38238
+ for (const m of raw.methods ?? []) {
38239
+ for (const step of m?.narrative ?? []) {
38240
+ if (!Array.isArray(step?.assertsInvariants)) continue;
38241
+ step.assertsInvariants = step.assertsInvariants.map((ref) => {
38242
+ const typeRef2 = typeof ref === "string" ? assertedTypeRef(ref) : void 0;
38243
+ if (typeRef2 === void 0 || !typeRef2.includes("::")) return ref;
38244
+ const to = local(typeRef2);
38245
+ if (to === typeRef2) return ref;
38246
+ changed = true;
38247
+ return `${to}${ref.slice(typeRef2.length)}`;
38248
+ });
38249
+ }
38250
+ }
38251
+ }
38252
+ if (changed) writeYamlFile(spec.file, raw);
38253
+ }
38254
+ }
38255
+ }
38256
+ function restoreCrossingConsumers(memberSide, parentSide, result) {
38257
+ const sides = [[memberSide, parentSide], [parentSide, memberSide]];
38258
+ for (const [providers, consumers] of sides) {
38259
+ const dependents = consumers.filter((c) => c.kind === "component" && typeof c.raw?.subsystem === "string" && Array.isArray(c.raw?.dependsOn));
38260
+ for (const spec of providers.filter((x) => x.kind === "subsystem")) {
38261
+ patchKeepingStamp(spec.file, (raw) => {
38262
+ let changed = false;
38263
+ for (const entry of raw.publicInterfaces ?? []) {
38264
+ if (!Array.isArray(entry?.consumers) || entry.consumers.length === 0 || typeof entry.component !== "string") continue;
38265
+ const users = [...new Set(dependents.filter((c) => c.raw.dependsOn.includes(entry.component)).map((c) => c.raw.subsystem))].filter((sub) => sub !== spec.id && !entry.consumers.includes(sub));
38266
+ if (users.length === 0) continue;
38267
+ entry.consumers = [...entry.consumers, ...users];
38268
+ changed = true;
38269
+ result.consumersRestored.push(`${spec.id} ${entry.component}: ${users.join(", ")}`);
38270
+ }
38271
+ return changed;
38272
+ });
38273
+ }
38274
+ }
38275
+ }
37679
38276
  function deleteProjectContent(memberDir) {
37680
38277
  const wai = path25.join(memberDir, ".wai");
37681
38278
  const deleted = [];
@@ -37705,9 +38302,9 @@ function retireBoundaryExports(family, used, memberKey) {
37705
38302
  const { publicInterfaces: _dropped, ...rest } = system;
37706
38303
  saveSystemSpec(kept.length > 0 ? { ...rest, publicInterfaces: kept } : rest);
37707
38304
  invalidateSpecCache();
37708
- for (const e of retired.filter((x) => x.typeDef !== void 0 && x.from === void 0)) {
38305
+ for (const e of retired.filter((x) => x.typeDef !== void 0)) {
37709
38306
  const type = loadTypeSpecs().find((t) => t.id === e.typeDef);
37710
- if (!type?.subsystem) continue;
38307
+ if (!type?.subsystem || e.from !== void 0 && e.from !== type.subsystem) continue;
37711
38308
  patchKeepingStamp(getSubsystemPath(type.subsystem), (raw) => {
37712
38309
  const before = (raw.publicInterfaces ?? []).length;
37713
38310
  raw.publicInterfaces = (raw.publicInterfaces ?? []).filter((pi) => !(pi?.typeDef === e.typeDef && pi?.from === void 0 && Object.keys(pi).length === 1));
@@ -37876,7 +38473,7 @@ function renameComponent(componentId, newId) {
37876
38473
  }
37877
38474
  if (componentId.includes("::")) {
37878
38475
  throw new WaironError(
37879
- `chained-component: "${componentId}" lives in a chained subproject; rename it from that project's own root.`
38476
+ `chained-component: "${componentId}" lives in another project; rename it from that project's own root. ${memberRootWay(componentId, `sdd_rename_component ${componentId.slice(componentId.lastIndexOf("::") + 2)} <new id>`)}`
37880
38477
  );
37881
38478
  }
37882
38479
  if (!SpecIdSchema.safeParse(newId).success) {
@@ -38039,7 +38636,7 @@ function renameMethod(componentId, methodName, newName, pinSymbol) {
38039
38636
  }
38040
38637
  if (componentId.includes("::")) {
38041
38638
  throw new WaironError(
38042
- `chained-component: "${componentId}" lives in a chained subproject; rename its method from that project's own root.`
38639
+ `chained-component: "${componentId}" lives in another project; rename its method from that project's own root. ${memberRootWay(componentId, `sdd_rename_method ${componentId.slice(componentId.lastIndexOf("::") + 2)} <method> <new name>`)}`
38043
38640
  );
38044
38641
  }
38045
38642
  if (!METHOD_NAME.test(newName)) {
@@ -38197,18 +38794,29 @@ function respellLike(written, oldId, newId) {
38197
38794
  if (written.includes("-")) return kebab(newId);
38198
38795
  return /^[A-Z]/.test(written) ? pascal(newId) : newId;
38199
38796
  }
38797
+ function memberRootWay(qualifiedId, call) {
38798
+ const alias = qualifiedId.slice(0, qualifiedId.indexOf("::"));
38799
+ let folder;
38800
+ try {
38801
+ const node = graph().nodes.find((n) => n.namespace === alias || n.parent === "" && n.mountAlias === alias);
38802
+ if (node) folder = path25.relative(getProjectRoot(), node.directory).split(path25.sep).join("/") || ".";
38803
+ } catch {
38804
+ }
38805
+ const where = folder ? `the member project "${alias}" at ${folder}` : `the member project "${alias}"`;
38806
+ return `It belongs to ${where}: open a session in that folder (its own guide and .mcp.json \u2014 run \`wairon generate\` and \`wairon mcp install --backend claude\` there first if it has none) and call ${call} there.`;
38807
+ }
38200
38808
  function renameType(typeId, newId) {
38201
38809
  const all = loadTypeSpecs();
38202
38810
  const cut = typeId.lastIndexOf("::");
38203
38811
  const qualifier = cut >= 0 ? typeId.slice(0, cut) : void 0;
38204
38812
  const ownSubsystem = qualifier !== void 0 && loadSubsystemSpecs().some((s) => s.id === qualifier);
38205
38813
  if (qualifier !== void 0 && !ownSubsystem) {
38206
- throw new WaironError(`chained-type: "${typeId}" lives in another project; rename it from that project's own root.`);
38814
+ throw new WaironError(`chained-type: "${typeId}" lives in another project; rename it from that project's own root. ${memberRootWay(typeId, `sdd_rename_type ${typeId.slice(cut + 2)} <new id>`)} An exported type keeps its public name through \`as\`, so this project's references do not break.`);
38207
38815
  }
38208
38816
  const type = qualifier !== void 0 ? all.find((t) => t.id === typeId.slice(cut + 2) && t.subsystem === qualifier) ?? null : loadTypeSpec(typeId);
38209
38817
  if (!type) throw new WaironError(`type-missing: no type has the id "${typeId}".`);
38210
38818
  if (type.id.includes("::")) {
38211
- throw new WaironError(`chained-type: "${type.id}" lives in another project; rename it from that project's own root.`);
38819
+ throw new WaironError(`chained-type: "${type.id}" lives in another project; rename it from that project's own root. ${memberRootWay(type.id, `sdd_rename_type ${type.id.slice(type.id.lastIndexOf("::") + 2)} <new id>`)}`);
38212
38820
  }
38213
38821
  const oldId = type.id;
38214
38822
  if (!SpecIdSchema.safeParse(newId).success) {
@@ -38496,8 +39104,8 @@ function walkForPackages(projectRoot, currentDir, depth, results) {
38496
39104
  }
38497
39105
  }
38498
39106
  function pathToId(relPath) {
38499
- const basename12 = path26.basename(relPath);
38500
- return basename12.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
39107
+ const basename13 = path26.basename(relPath);
39108
+ return basename13.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
38501
39109
  }
38502
39110
  function pathToName(relPath) {
38503
39111
  const id = pathToId(relPath);
@@ -38981,7 +39589,7 @@ function isWithinDir3(dir, target) {
38981
39589
  const rel2 = path29.relative(path29.resolve(dir), path29.resolve(target));
38982
39590
  return rel2 === "" || !rel2.startsWith("..") && !path29.isAbsolute(rel2);
38983
39591
  }
38984
- function resolveLayer(delegate) {
39592
+ function resolveLayer(delegate, implementers = false) {
38985
39593
  projectFilesCache.clear();
38986
39594
  const system = loadSystemSpec();
38987
39595
  if (!system) return [];
@@ -39112,7 +39720,7 @@ function resolveLayer(delegate) {
39112
39720
  updatedAt: sub.updatedAt
39113
39721
  });
39114
39722
  }
39115
- if (config.rules.generateComponentImplementers) {
39723
+ if (config.rules.generateComponentImplementers || implementers) {
39116
39724
  for (const comp of components) {
39117
39725
  const compInterfaces = interfaces.filter((i) => i.component === comp.id);
39118
39726
  const compInterfaceIds = compInterfaces.map((i) => i.id);
@@ -39180,7 +39788,7 @@ function resolveLayer(delegate) {
39180
39788
  }
39181
39789
  var UnknownAgentError = class extends WaironError {
39182
39790
  constructor(agentId, knownIds) {
39183
- super(`Unknown agent id: "${agentId}". Known agent ids: ${knownIds.join(", ")}`);
39791
+ super(`Unknown agent id: "${agentId}". Known agent ids: ${knownIds.join(", ")} \u2014 or any component id of this project, which composes that component's implementer brief.`);
39184
39792
  this.name = "UnknownAgentError";
39185
39793
  }
39186
39794
  };
@@ -39191,7 +39799,7 @@ function composeAgentBrief(agentId) {
39191
39799
  return throughMount(brief, hop.alias, hop.relative);
39192
39800
  }
39193
39801
  const records = resolveAgentTopology();
39194
- const record = records.find((r) => r.id === agentId);
39802
+ const record = records.find((r) => r.id === agentId) ?? componentImplementer(agentId);
39195
39803
  if (!record) {
39196
39804
  throw new UnknownAgentError(agentId, records.map((r) => r.id));
39197
39805
  }
@@ -39210,6 +39818,17 @@ function composeAgentBrief(agentId) {
39210
39818
  domainName: "",
39211
39819
  variantGuidance: record.variantGuidance ?? ""
39212
39820
  });
39821
+ const codeFence = codeFenceOf(record);
39822
+ if (codeFence) {
39823
+ instructions = `${instructions.trimEnd()}
39824
+
39825
+ ## Code write fence
39826
+
39827
+ ${codeFence.length > 0 ? `Write code only here (and tests beside it, as the project lays tests out):
39828
+
39829
+ ${codeFence.map((p) => `- \`${p}\``).join("\n")}
39830
+ ` : "No implementation names a source file yet, so no code location is declared. The spawning session must name the file(s) this agent may create (the planned sourcePath); set `sourcePath` on the implementation once the file exists.\n"}`;
39831
+ }
39213
39832
  const guidance = loadAgentOverride(agentId);
39214
39833
  if (guidance !== null) {
39215
39834
  instructions = `${instructions.trimEnd()}
@@ -39244,10 +39863,34 @@ ${typeMapping.map((line) => `- ${line}`).join("\n")}
39244
39863
  instructions,
39245
39864
  variantGuidance: record.variantGuidance || void 0,
39246
39865
  ...typeMapping ? { typeMapping } : {},
39866
+ ...codeFence ? { codeFence } : {},
39247
39867
  profile: budget ? profile : void 0,
39248
39868
  budget
39249
39869
  };
39250
39870
  }
39871
+ function componentImplementer(agentId) {
39872
+ const componentId = agentId.endsWith("-implementer") ? agentId.slice(0, -"-implementer".length) : agentId;
39873
+ if (componentId === "" || componentId.includes("::")) return null;
39874
+ return resolveLayer(true, true).find((r) => r.id === `${componentId}-implementer`) ?? null;
39875
+ }
39876
+ function codeFenceOf(record) {
39877
+ const implementsComponents = record.template === "implementer" || record.template === "domain-owner" && record.creationReason.startsWith("Automatically inferred from L1");
39878
+ if (!implementsComponents) return null;
39879
+ const files = record.ownedPaths.filter((p) => !p.startsWith(".wai/") && !p.includes("/.wai/") && !/[*?]/.test(p));
39880
+ if (files.length === 0) return [];
39881
+ const dirs = files.map((f) => path29.posix.dirname(f.replace(/\\/g, "/")));
39882
+ let shared = dirs[0].split("/");
39883
+ for (const dir of dirs.slice(1)) {
39884
+ const parts = dir.split("/");
39885
+ let i = 0;
39886
+ while (i < shared.length && i < parts.length && shared[i] === parts[i]) i++;
39887
+ shared = shared.slice(0, i);
39888
+ }
39889
+ const folder = shared.join("/");
39890
+ const fence = [...files];
39891
+ if (folder !== "" && folder !== ".") fence.push(`${folder}/**`);
39892
+ return fence;
39893
+ }
39251
39894
  function implementationLanguage(record) {
39252
39895
  const owned = new Set(record.ownedPaths);
39253
39896
  const implementations = loadImplementationSpecs().filter((impl) => implementationSourceFiles(impl).some((file) => owned.has(file)));
@@ -39280,6 +39923,7 @@ function throughMount(brief, alias, relative20) {
39280
39923
  ...brief,
39281
39924
  agentId: `${alias}::${brief.agentId}`,
39282
39925
  ownedPaths: brief.ownedPaths.map(under),
39926
+ ...brief.codeFence ? { codeFence: brief.codeFence.map(under) } : {},
39283
39927
  ...brief.readPaths ? { readPaths: brief.readPaths.map(under) } : {},
39284
39928
  root: brief.root ? under(brief.root) : relative20
39285
39929
  };
@@ -40009,7 +40653,8 @@ Approved: ${approval.lockedAt} by ${by} \u2014 no spec has changed since.${child
40009
40653
  ${headline} \u2014 approved ${approval.lockedAt} by ${by}:
40010
40654
  ` + named2.map((p) => ` ${p}`).join("\n") + (rest > 0 ? `
40011
40655
  \u2026 and ${rest} more` : "") + childNote + "\n",
40012
- drifted: true
40656
+ drifted: true,
40657
+ moved: [...diff2.changed.map((p) => `~ ${p}`), ...diff2.added.map((p) => `+ ${p}`), ...diff2.removed.map((p) => `- ${p}`)]
40013
40658
  };
40014
40659
  } catch {
40015
40660
  return quiet;
@@ -40113,13 +40758,13 @@ This project uses **wairon**. System specs live under \`.wai/specs/\` (L0 System
40113
40758
 
40114
40759
  **Do NOT search files or read agent configs to learn about wairon or SDD. Use the context here and the \`sdd-architect\` skill to start.**
40115
40760
 
40116
- **Your first move: call the \`sdd_get_status\` MCP tool** (or \`wairon/sdd_get_status\`) to see the spec tree. Do not parse files or run CLI commands manually.
40761
+ **Your first move: call the \`sdd_get_status\` MCP tool** (or \`wairon/sdd_get_status\`) to see the spec tree. Do not parse files or run CLI commands manually. If no \`sdd_*\` tools are available, this folder is not the root of the project this guide came from \u2014 say so instead of guessing.
40117
40762
 
40118
40763
  ### How you operate
40119
40764
  - **To design/modify specs**: Use **\`sdd-architect\`** skill (in \`.claude/skills/\` or \`.gemini/skills/\`).
40120
40765
  - **Manage specs via MCP tools only**: Use \`sdd_initialize_system\`, \`sdd_add_subsystem\`, \`sdd_add_component\`, \`sdd_define_interface\`, \`sdd_write_narrative\`, \`sdd_add_type\`, \`sdd_get_spec\`, \`sdd_delete_spec\`, \`sdd_validate_tree\`, and \`sdd_get_status\` (namespaced if needed). Do not edit specs manually.
40121
40766
  - **Growing a system \u2014 subsystems \u2192 parts \u2192 projects**: boundaries are earned. Start with subsystems in one folder. A piece that needs its own folder or repository but stays the same system is a **part**: declared in \`.wai/project.yaml\` \`members\` by one location key (\`scheduler: services/scheduler\`, \`admin: ../admin\`, \`payments: git@host:acme/payments.git#<commit>\`; long form \`{ source, as, description }\`), its subsystems this project's own \u2014 local ids, this project's lock. \`sdd_add_member\` creates a part by default (\`as: project\` for a project) and \`sdd_externalize_subsystem\` moves a subsystem into one. A **project** is an independent boundary \u2014 its own id, exports, lock \u2014 reached as \`alias::name\`; make one only when it needs its own team, release, approval or public surface: \`sdd_promote_member\` (\`sdd_demote_member\` back). What a member is follows from its content (an id, an L0 or a lock make a project).
40122
- - **Members & cross-project references**: relocate a member with \`sdd_move_member\`. Every other change of the family's shape is a **family migration**, each with \`dryRun\`: promote/demote, make an existing project a member with \`sdd_attach_member\`, take one out and back with \`sdd_detach_member\` / \`sdd_adopt_member\`, rename a project's id with \`sdd_rename_project\` or an alias with \`sdd_rename_member_alias\`, move a subsystem into a part with \`sdd_externalize_subsystem\` and fold a member back in with \`sdd_internalize_member\`. Run it with \`dryRun: true\` first and show the plan; applied, it writes every project it touches or none, and never locks \u2014 it names the projects to re-lock. A project member is never a subsystem of its parent: it has its own \`.wai/\` tree and is designed from its own root.
40767
+ - **Members & cross-project references**: relocate a member with \`sdd_move_member\`. Every other change of the family's shape is a **family migration**, each with \`dryRun\`: promote/demote, make an existing project a member with \`sdd_attach_member\`, take one out and back with \`sdd_detach_member\` / \`sdd_adopt_member\`, rename a project's id with \`sdd_rename_project\` or an alias with \`sdd_rename_member_alias\`, move a subsystem into a part with \`sdd_externalize_subsystem\` and fold a member back in with \`sdd_internalize_member\`. Run it with \`dryRun: true\` first and show the plan (the human's CLI calls the same plan \`--report\`); applied, it writes every project it touches or none, and never locks \u2014 it names the projects to re-lock. A project member is never a subsystem of its parent: it has its own \`.wai/\` tree and is designed from its own root \u2014 its specs, its L0 export table and its \`project.yaml\` are written by a session opened in that member's folder (its own guide and \`.mcp.json\`), and the tools here refuse such a write naming that folder.
40123
40768
  - **\`alias::name\`**: An id without \`::\` is local to the project that writes it. Anything another project provides is referenced as \`alias::name\` \u2014 the alias is one of your members or declared \`externals\`, the name a public name in that project's L0 export table. A reference to something it does not export is reported (\`EXTERNAL_NOT_EXPORTED\`).
40124
40769
  - **Deprecated forms** (they still resolve for one release, are reported, and \`wairon doctor --fix\` rewrites them): a leading \`::\` (\`::shared::error-type\`), \`super::\` (\`super::sibling_comp\`), member paths (\`billing::invoice::invoice_portal\`), and an L1 subsystem carrying \`projectPath\` (\`DEPRECATED_MOUNT_FORM\`).
40125
40770
  - **Do not run the \`wairon\` CLI**: Use \`sdd_validate_tree\` and \`sdd_get_status\` instead of CLI commands.
@@ -40164,7 +40809,7 @@ ${GUIDE_MARKER_END}
40164
40809
  const stripped = stripGuideSection(existing);
40165
40810
  const newContent = stripped.trimEnd() + section;
40166
40811
  fs21.mkdirSync(path36.dirname(filePath), { recursive: true });
40167
- fs21.writeFileSync(filePath, newContent, "utf-8");
40812
+ writeFile(filePath, newContent);
40168
40813
  }
40169
40814
  function stripGuideSection(content) {
40170
40815
  const start = content.indexOf(GUIDE_MARKER_START);
@@ -40187,7 +40832,7 @@ To design or modify the system, invoke the **\`sdd-architect\`** skill
40187
40832
  (in \`.claude/skills/\`). Author and validate specs with the \`sdd_*\` MCP tools;
40188
40833
  the \`wairon\` CLI is the human developer's tool, not yours.
40189
40834
  `;
40190
- fs21.writeFileSync(filePath, content, "utf-8");
40835
+ writeFile(filePath, content);
40191
40836
  } else if (targetType === "gemini" || targetType === "agy") {
40192
40837
  const filePath = path36.join(projectRoot, "GEMINI.md");
40193
40838
  const content = `# Wairon SDD Project
@@ -40196,7 +40841,7 @@ ${versionStamp()}
40196
40841
  ${LOCAL_GUIDE_BODY}
40197
40842
  ${GUIDE_MARKER_END}
40198
40843
  `;
40199
- fs21.writeFileSync(filePath, content, "utf-8");
40844
+ writeFile(filePath, content);
40200
40845
  } else if (targetType === "cursor") {
40201
40846
  const filePath = path36.join(projectRoot, ".cursorrules");
40202
40847
  const content = `# Wairon SDD Project
@@ -40205,7 +40850,7 @@ This project uses the Wairon Spec-Driven Development (SDD) framework.
40205
40850
 
40206
40851
  Refer to the rules in [.cursor/rules/](.cursor/rules/) for full instructions.
40207
40852
  `;
40208
- fs21.writeFileSync(filePath, content, "utf-8");
40853
+ writeFile(filePath, content);
40209
40854
  } else if (targetType === "copilot") {
40210
40855
  const filePath = path36.join(projectRoot, ".github", "copilot-instructions.md");
40211
40856
  fs21.mkdirSync(path36.dirname(filePath), { recursive: true });
@@ -40215,14 +40860,14 @@ This project uses the Wairon Spec-Driven Development (SDD) framework.
40215
40860
 
40216
40861
  Refer to the prompts in [.github/prompts/](.github/prompts/) for instructions.
40217
40862
  `;
40218
- fs21.writeFileSync(filePath, content, "utf-8");
40863
+ writeFile(filePath, content);
40219
40864
  } else if (targetType === "codex") {
40220
40865
  const filePath = path36.join(projectRoot, ".codexrules");
40221
40866
  const content = `# Wairon SDD Project
40222
40867
 
40223
40868
  Refer to [.codex/agents/](.codex/agents/) for full instructions.
40224
40869
  `;
40225
- fs21.writeFileSync(filePath, content, "utf-8");
40870
+ writeFile(filePath, content);
40226
40871
  }
40227
40872
  }
40228
40873
  var GUIDE_TARGETS = ["claude", "gemini", "agy", "cursor", "copilot", "codex"];
@@ -40377,9 +41022,17 @@ function syncContextFiles() {
40377
41022
  var path37 = __toESM(require("path"));
40378
41023
  function approvalTag(entry) {
40379
41024
  if (!entry) return "";
40380
- const state = entry.upgraded ? `${entry.state}, re-lock once` : entry.state;
40381
- return entry.pinned ? ` [${state} \xB7 pin ${entry.pinned}]` : ` [${state}]`;
40382
- }
41025
+ const words2 = APPROVAL_WORDS[entry.state];
41026
+ const state = entry.upgraded ? `${words2}, re-lock once` : words2;
41027
+ return entry.pinned ? ` [${state} \xB7 ${PIN_WORDS[entry.pinned]}]` : ` [${state}]`;
41028
+ }
41029
+ var APPROVAL_WORDS = { approved: "approved", drifted: "drifted", never: "never approved" };
41030
+ var PIN_WORDS = { matches: "pinned", moved: "pin moved", unpinned: "not pinned" };
41031
+ var OWN_APPROVAL_WORDS = {
41032
+ approved: "approved",
41033
+ drifted: "drifted \u2014 its approval no longer covers this tree (`wairon lock` re-approves)",
41034
+ never: "never approved (`wairon lock` approves the design)"
41035
+ };
40383
41036
  var asWritten = (text3) => text3;
40384
41037
  function fillDecor(decor) {
40385
41038
  return {
@@ -40465,11 +41118,15 @@ function getStatusReport(options = {}, decor) {
40465
41118
  }
40466
41119
  componentScores.set(comp.id, score);
40467
41120
  }
41121
+ const typeOwners = new Set(loadTypeSpecs().map((t) => t.subsystem).filter((id) => typeof id === "string"));
40468
41122
  const getSubsystemScore = (subId) => {
40469
41123
  const sub = subsystems.find((s) => s.id === subId);
40470
41124
  if (!sub) return 0;
40471
41125
  const subComps = components.filter((c) => c.subsystem === subId);
40472
- if (subComps.length === 0) return 0;
41126
+ if (subComps.length === 0) {
41127
+ if (!typeOwners.has(subId)) return 0;
41128
+ return sub.status === "complete" ? 100 : 50;
41129
+ }
40473
41130
  const totalScore = subComps.reduce((acc, c) => acc + (componentScores.get(c.id) ?? 0), 0);
40474
41131
  let avg = Math.round(totalScore / subComps.length);
40475
41132
  if (sub.status === "draft" || sub.status === "design") {
@@ -40592,8 +41249,7 @@ function getStatusReport(options = {}, decor) {
40592
41249
  `;
40593
41250
  }
40594
41251
  const own = options.approvals?.find((a) => a.key === "");
40595
- const hasMembers = (options.approvals ?? []).some((a) => a.key !== "" && a.as !== "part");
40596
- if (own && (own.state !== "never" || hasMembers)) output += `${mark.layer("system", "Approval:")} this project is ${own.state}${own.upgraded ? " (locked under an earlier gate identity \u2014 re-lock once)" : ""}
41252
+ if (own) output += `${mark.layer("system", "Approval:")} this project is ${OWN_APPROVAL_WORDS[own.state]}${own.upgraded ? " (locked under an earlier gate identity \u2014 re-lock once)" : ""}
40597
41253
  `;
40598
41254
  return { text: output, failed: false };
40599
41255
  }
@@ -43354,9 +44010,33 @@ function planAliasRename(family, bound2, request) {
43354
44010
  if ([...family.owners].some(([key, owner]) => owner === bound2 && key.split("::").pop() === next)) {
43355
44011
  plan6.notes.push(`${label5(bound2)} has a spec whose id is "${next}" \u2014 a local id equal to an alias (LOCAL_ID_SHADOWS_PROJECT reports it)`);
43356
44012
  }
44013
+ repinIntactConsumers(plan6, family, bound2);
43357
44014
  return plan6;
43358
44015
  }
43359
- var PHASES = ["rewriteReferences", "renameAlias", "repointExternal", "renameId", "renamePin"];
44016
+ function repinIntactConsumers(plan6, family, bound2) {
44017
+ const boundNode2 = familyNode(family, bound2);
44018
+ for (const node of family.nodes) {
44019
+ if (path44.resolve(node.directory) === path44.resolve(boundNode2.directory)) continue;
44020
+ let statuses = [];
44021
+ try {
44022
+ statuses = runWithProjectRoot(node.directory, () => getExternalsStatus2());
44023
+ } catch {
44024
+ continue;
44025
+ }
44026
+ for (const status of statuses) {
44027
+ const producer = node.aliases.get(status.alias);
44028
+ const pinsBound = producer !== void 0 ? producer === bound2 : boundNode2.id !== void 0 && status.project === boundNode2.id;
44029
+ if (!status.pinned || status.outOfReach || !pinsBound) continue;
44030
+ const broken = status.uses.filter((u) => u.state === "changed" || u.state === "removed");
44031
+ if (broken.length > 0) {
44032
+ plan6.notes.push(`${label5(node.namespace)}'s pin of "${status.alias}" is already incompatible (${broken.map((u) => u.publicName ?? u.member ?? "?").join(", ")}): not re-pinned \u2014 adapt its uses, then run \`wairon externals pin ${status.alias}\` there.`);
44033
+ continue;
44034
+ }
44035
+ edit2(plan6, node.namespace, "pin", `pin ${status.alias} taken again: the names it uses of ${label5(bound2)} are unchanged`, { root: node.directory, call: "pinExternals", args: [[status.alias]] });
44036
+ }
44037
+ }
44038
+ }
44039
+ var PHASES = ["rewriteReferences", "renameAlias", "repointExternal", "renameId", "renamePin", "pinExternals"];
43360
44040
  function write4(plan6, rehearsal) {
43361
44041
  const planned = plan6.edits.filter((e) => e.write !== void 0);
43362
44042
  const unknown = planned.find((x) => !PHASES.includes(x.write.call));
@@ -43371,6 +44051,7 @@ function write4(plan6, rehearsal) {
43371
44051
  for (const e of planned.filter((x) => x.write.call === "renameId")) writeOne2(rehearsal, e.write);
43372
44052
  }
43373
44053
  for (const e of planned.filter((x) => x.write.call === "renamePin")) writeOne2(rehearsal, e.write);
44054
+ for (const e of planned.filter((x) => x.write.call === "pinExternals")) writeOne2(rehearsal, e.write);
43374
44055
  }
43375
44056
  function referencesBySpec(planned) {
43376
44057
  const out = /* @__PURE__ */ new Map();
@@ -43394,6 +44075,8 @@ function writeOne2(rehearsal, w) {
43394
44075
  return renameProjectId(args[0], args[1]);
43395
44076
  case "renamePin":
43396
44077
  return renamePin2(args[0], args[1], args[2]);
44078
+ case "pinExternals":
44079
+ return pinExternals2(args[0]);
43397
44080
  default:
43398
44081
  throw new Error(`identity_migration makes no "${w.call}" write`);
43399
44082
  }
@@ -43665,6 +44348,7 @@ function planExternalize(family, bound2, request) {
43665
44348
  if (plan6.refusals.length > 0) return plan6;
43666
44349
  edit3(plan6, bound2, "move", `externalize ${id} as a project: its specs into a member at ${posix6(path45.relative(node.directory, dir))}, declared as "${id}", then promoted`, { root: node.directory, call: "externalizeSubsystem", args: [id, posix6(path45.relative(node.directory, dir)), "project"] });
43667
44350
  edit3(plan6, id, "pin", `pin the new member's externals (its external for ${node.id ?? "this project"}, when its specs reference it)`, { root: dir, call: "pinExternals", args: [[]] });
44351
+ repinIntactConsumers2(plan6, family, bound2, [dir]);
43668
44352
  plan6.notes.push("Source code is not moved: move the subsystem's code into the member yourself.");
43669
44353
  return plan6;
43670
44354
  }
@@ -43793,6 +44477,7 @@ function planPromote(family, bound2, request) {
43793
44477
  );
43794
44478
  edit3(plan6, newId, "pin", `pin the new project's external for ${node.id ?? "this project"} (when its specs reference it)`, { root: part.directory, call: "pinExternals", args: [[]] });
43795
44479
  if (part.storage !== "contained") edit3(plan6, bound2, "pin", `pin ${alias} (a project stored outside ${label6(bound2)} is judged against its pin)`, { root: node.directory, call: "pinExternals", args: [[alias]] });
44480
+ repinIntactConsumers2(plan6, family, bound2, [part.directory]);
43796
44481
  plan6.notes.push(`"${newId}" has no lock yet: lock it at its own root (\`wairon lock\`) once it validates.`);
43797
44482
  return plan6;
43798
44483
  }
@@ -43859,16 +44544,19 @@ function write5(plan6, rehearsal) {
43859
44544
  return;
43860
44545
  }
43861
44546
  if (plan6.request.verb === "externalize") {
43862
- for (const e of planned.filter((x) => x.write.call === "externalizeSubsystem")) writeOne3(rehearsal, e.write);
44547
+ for (const e of planned.filter((x) => x.write.call === "externalizeSubsystem")) recordCrossing(plan6, e.project, e.write.args[0], writeOne3(rehearsal, e.write));
43863
44548
  for (const e of planned.filter((x) => x.write.call === "pinExternals")) writeOne3(rehearsal, e.write);
43864
44549
  return;
43865
44550
  }
43866
44551
  if (plan6.request.verb === "promote") {
43867
- for (const e of planned.filter((x) => x.write.call === "promoteMember")) writeOne3(rehearsal, e.write);
44552
+ for (const e of planned.filter((x) => x.write.call === "promoteMember")) recordCrossing(plan6, e.project, e.write.args[1] ?? e.write.args[0], writeOne3(rehearsal, e.write));
43868
44553
  for (const e of planned.filter((x) => x.write.call === "pinExternals")) writeOne3(rehearsal, e.write);
43869
44554
  return;
43870
44555
  }
43871
- for (const e of planned.filter((x) => x.write.call === "demoteMember")) writeOne3(rehearsal, e.write);
44556
+ for (const e of planned.filter((x) => x.write.call === "demoteMember")) {
44557
+ const demoted = writeOne3(rehearsal, e.write);
44558
+ for (const line of demoted?.consumersRestored ?? []) plan6.edits.push({ project: e.project, kind: "reference", detail: `consumers across the old boundary restored: ${line}` });
44559
+ }
43872
44560
  for (const e of planned.filter((x) => x.write.call === "unpin")) writeOne3(rehearsal, e.write);
43873
44561
  }
43874
44562
  function referencesBySpec2(planned) {
@@ -43881,9 +44569,46 @@ ${id}`) ?? [], ...edits]);
43881
44569
  }
43882
44570
  return out;
43883
44571
  }
44572
+ function repinIntactConsumers2(plan6, family, bound2, created) {
44573
+ const boundNode2 = familyNode(family, bound2);
44574
+ const skip = new Set([boundNode2.directory, ...created].map((d) => path45.resolve(d)));
44575
+ for (const node of family.nodes) {
44576
+ if (skip.has(path45.resolve(node.directory))) continue;
44577
+ let statuses = [];
44578
+ try {
44579
+ statuses = runWithProjectRoot(node.directory, () => getExternalsStatus2());
44580
+ } catch {
44581
+ continue;
44582
+ }
44583
+ for (const status of statuses) {
44584
+ const producer = node.aliases.get(status.alias);
44585
+ const pinsBound = producer !== void 0 ? producer === bound2 : boundNode2.id !== void 0 && status.project === boundNode2.id;
44586
+ if (!status.pinned || status.outOfReach || !pinsBound) continue;
44587
+ const broken = status.uses.filter((u) => u.state === "changed" || u.state === "removed");
44588
+ if (broken.length > 0) {
44589
+ plan6.notes.push(`${label6(node.namespace)}'s pin of "${status.alias}" is already incompatible (${broken.map((u) => u.publicName ?? u.member ?? "?").join(", ")}): not re-pinned \u2014 adapt its uses, then run \`wairon externals pin ${status.alias}\` there.`);
44590
+ continue;
44591
+ }
44592
+ edit3(plan6, node.namespace, "pin", `pin ${status.alias} taken again: the names it uses of ${label6(bound2)} only move with the boundary`, { root: node.directory, call: "pinExternals", args: [[status.alias]] });
44593
+ }
44594
+ }
44595
+ }
44596
+ function recordCrossing(plan6, bound2, member, result) {
44597
+ const crossed = result;
44598
+ if (!crossed) return;
44599
+ for (const r of crossed.respelled) {
44600
+ plan6.edits.push({ project: bound2, kind: "reference", detail: `${r.specId} (${r.position}): ${r.from} \u2192 ${r.to}`, reference: r });
44601
+ }
44602
+ for (const r of crossed.memberRespelled) {
44603
+ plan6.edits.push({ project: member, kind: "reference", detail: `${r.specId} (${r.position}): ${r.from} \u2192 ${r.to}`, reference: r });
44604
+ }
44605
+ for (const line of crossed.exported) plan6.edits.push({ project: bound2, kind: "export", detail: `export ${line}` });
44606
+ for (const line of crossed.imported) plan6.edits.push({ project: bound2, kind: "external", detail: `import: ${line}` });
44607
+ for (const line of crossed.consumersDropped) plan6.edits.push({ project: bound2, kind: "reference", detail: `consumers across the boundary to ${member} dropped: ${line}` });
44608
+ }
43884
44609
  function writeOne3(rehearsal, w) {
43885
44610
  const args = w.args;
43886
- runWithProjectRoot(rehearsalRoot(rehearsal, w.root), () => {
44611
+ return runWithProjectRoot(rehearsalRoot(rehearsal, w.root), () => {
43887
44612
  switch (w.call) {
43888
44613
  case "internalizeMember":
43889
44614
  return internalizeMember(args[0], args[1]);
@@ -43918,7 +44643,7 @@ var path46 = __toESM(require("path"));
43918
44643
  var fs26 = __toESM(require("fs"));
43919
44644
  var os7 = __toESM(require("os"));
43920
44645
  var nodePath2 = __toESM(require("path"));
43921
- var yaml3 = __toESM(require("js-yaml"));
44646
+ var yaml4 = __toESM(require("js-yaml"));
43922
44647
  var TRANSACTIONS2 = nodePath2.join(".wai", "transactions");
43923
44648
  var transactionsDir = (owner) => nodePath2.join(owner, TRANSACTIONS2);
43924
44649
  var transactionDir = (owner, id) => nodePath2.join(transactionsDir(owner), id);
@@ -43985,7 +44710,7 @@ function dropRehearsal(id) {
43985
44710
  function saveJournal(journal) {
43986
44711
  const ignore = nodePath2.join(transactionsDir(journal.owner), ".gitignore");
43987
44712
  if (!fs26.existsSync(ignore)) writeDurably(ignore, "*\n");
43988
- writeDurably(nodePath2.join(transactionDir(journal.owner, journal.id), "journal.yaml"), yaml3.dump(journal, { noRefs: true, lineWidth: 200 }));
44713
+ writeDurably(nodePath2.join(transactionDir(journal.owner, journal.id), "journal.yaml"), yaml4.dump(journal, { noRefs: true, lineWidth: 200 }));
43989
44714
  }
43990
44715
  function readJournal(owner, id) {
43991
44716
  const file = nodePath2.join(transactionDir(owner, id), "journal.yaml");
@@ -43997,7 +44722,7 @@ function readJournal(owner, id) {
43997
44722
  throw e;
43998
44723
  }
43999
44724
  try {
44000
- return yaml3.load(text3);
44725
+ return yaml4.load(text3);
44001
44726
  } catch (e) {
44002
44727
  throw new Error(`${file} is not a readable transaction journal: ${e instanceof Error ? e.message : String(e)}`);
44003
44728
  }
@@ -44816,10 +45541,19 @@ ${body}`);
44816
45541
  }
44817
45542
  function migrationView(report3, dryRun) {
44818
45543
  const plan6 = report3.plan;
45544
+ const request = plan6.request;
45545
+ const madeProject = request.verb === "promote" || request.verb === "externalize" && request.as === "project";
45546
+ const madeAt = madeProject && report3.applied ? plan6.changes.find((c) => c.action === "create" && c.path.replace(/\\/g, "/") === ".wai/project.yaml")?.project : void 0;
44819
45547
  return {
44820
45548
  dryRun,
44821
45549
  applied: report3.applied,
44822
45550
  relock: report3.applied ? report3.relock : plan6.relock,
45551
+ ...madeAt ? {
45552
+ newProject: {
45553
+ root: madeAt,
45554
+ next: `The new project at ${madeAt} has no guide, skills or MCP registration of its own yet. The human runs, in that folder: \`wairon generate\` and \`wairon mcp install --backend claude\` (the CLI's \`wairon member promote\` / \`subsystem externalize --as project\` do this themselves). Its own session then designs it \u2014 its specs, L0 export table and project.yaml are written from its own root.`
45555
+ }
45556
+ } : {},
44823
45557
  ...report3.outcome ? { outcome: { committed: report3.outcome.committed, restored: report3.outcome.restored, unrestored: report3.outcome.unrestored, ...report3.outcome.failure ? { failure: report3.outcome.failure } : {} } } : {},
44824
45558
  plan: {
44825
45559
  verb: plan6.request.verb,
@@ -46612,9 +47346,9 @@ ${testsBlock}` : ""}`, deletion);
46612
47346
  server,
46613
47347
  "sdd_get_agent_brief",
46614
47348
  {
46615
- description: "Compose the LIVE delegation brief for one resolved agent: the fully rendered instruction body plus the ownedPaths/readPaths scope fence \u2014 the dynamic replacement for generated per-component agent files. Fetch a brief and spawn a generic subagent with it: always fresh after a re-lock, no session restart needed. List agent ids via listAgents or resources/list (the wairon-agent:// entries).",
47349
+ description: "Compose the LIVE delegation brief for one resolved agent: the fully rendered instruction body plus the ownedPaths/readPaths scope fence and the codeFence (where the agent may write CODE: its source files and their shared folder; empty when no file is named yet \u2014 then name the planned files when spawning) \u2014 the dynamic replacement for generated per-component agent files. A COMPONENT id (or `<component>-implementer`) composes that one component's implementer brief, with no agent files and no setting needed. Fetch a brief and spawn a generic subagent with it: always fresh after a re-lock, no session restart needed. List agent ids via listAgents or resources/list (the wairon-agent:// entries).",
46616
47350
  inputSchema: {
46617
- agentId: import_zod12.z.string().describe("The resolved agent id (list via listAgents or resources/list)")
47351
+ agentId: import_zod12.z.string().describe("The resolved agent id (list via listAgents or resources/list), or a component id for that component's implementer brief")
46618
47352
  }
46619
47353
  },
46620
47354
  ({ agentId }) => {
@@ -46828,6 +47562,7 @@ function loadProjectConfig3() {
46828
47562
  aiDir,
46829
47563
  aiDirAt,
46830
47564
  aiPathsAt,
47565
+ aliasTargetOf,
46831
47566
  ambiguityMessage,
46832
47567
  approvalKeyIn,
46833
47568
  approvalRecord,
@@ -47013,6 +47748,7 @@ function loadProjectConfig3() {
47013
47748
  memberDeclarationOf,
47014
47749
  memberDigest,
47015
47750
  memberLocationOf,
47751
+ memberTypesOf,
47016
47752
  methodGenericParameters,
47017
47753
  methodSourceFile,
47018
47754
  methodTypeRefs,
@@ -47085,6 +47821,7 @@ function loadProjectConfig3() {
47085
47821
  resolveExternals,
47086
47822
  resolveImport,
47087
47823
  resolveInstalledPack,
47824
+ resolveProjectBinding,
47088
47825
  resolveProjectExports,
47089
47826
  resolveSignatures,
47090
47827
  resolveSubprojectForNamespace,