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

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
@@ -3316,8 +3316,8 @@ var require_dist = __commonJS({
3316
3316
  return normalized === "." ? ".wai/" : `${normalized}/.wai/`;
3317
3317
  }
3318
3318
  function normalizeRootPath(relativePath) {
3319
- const posix5 = relativePath.split(path210.sep).join("/").replace(/^\.\//, "").replace(/\/+$/, "");
3320
- return posix5 === "" || posix5 === "." ? "." : posix5;
3319
+ const posix7 = relativePath.split(path210.sep).join("/").replace(/^\.\//, "").replace(/\/+$/, "");
3320
+ return posix7 === "" || posix7 === "." ? "." : posix7;
3321
3321
  }
3322
3322
  function slugify(projectName) {
3323
3323
  const slug2 = projectName.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
@@ -3709,6 +3709,7 @@ __export(src_exports, {
3709
3709
  keyIn: () => keyIn,
3710
3710
  landReference: () => landReference,
3711
3711
  languageOfSourcePath: () => languageOfSourcePath,
3712
+ lineEndingFor: () => lineEndingFor,
3712
3713
  listDirectChainedSubprojects: () => listDirectChainedSubprojects,
3713
3714
  listExternals: () => listExternals2,
3714
3715
  listFiles: () => listFiles,
@@ -3828,6 +3829,7 @@ __export(src_exports, {
3828
3829
  resolveSubsystemExports: () => resolveSubsystemExports,
3829
3830
  resolveVariantGuidance: () => resolveVariantGuidance,
3830
3831
  retireSpecialists: () => retireSpecialists,
3832
+ returnTypeOf: () => returnTypeOf,
3831
3833
  rewriteReferences: () => rewriteReferences2,
3832
3834
  runWithHostedLookup: () => runWithHostedLookup,
3833
3835
  runWithProjectBinding: () => runWithProjectBinding,
@@ -3866,6 +3868,7 @@ __export(src_exports, {
3866
3868
  typeIsMany: () => typeIsMany,
3867
3869
  typeMatchesRef: () => typeMatchesRef,
3868
3870
  typeNamedRefs: () => typeNamedRefs,
3871
+ typeProblemEnumProposal: () => typeProblemEnumProposal,
3869
3872
  typeProblemIntProposal: () => typeProblemIntProposal,
3870
3873
  typeSourceFiles: () => typeSourceFiles,
3871
3874
  typeSpellingFacts: () => typeSpellingFacts,
@@ -3879,6 +3882,7 @@ __export(src_exports, {
3879
3882
  validateProject: () => validateProject,
3880
3883
  validateProjectConfig: () => validateProjectConfig,
3881
3884
  validateRegistry: () => validateRegistry,
3885
+ withLineEndings: () => withLineEndings,
3882
3886
  withPack: () => withPack,
3883
3887
  writeFile: () => writeFile,
3884
3888
  writeFileIfChanged: () => writeFileIfChanged,
@@ -3893,8 +3897,8 @@ __export(src_exports, {
3893
3897
  module.exports = __toCommonJS(src_exports);
3894
3898
 
3895
3899
  // src/config/project-config.ts
3896
- var fs2 = __toESM(require("fs"));
3897
- var path3 = __toESM(require("path"));
3900
+ var fs3 = __toESM(require("fs"));
3901
+ var path5 = __toESM(require("path"));
3898
3902
  var YAML = __toESM(require("yaml"));
3899
3903
  var import_zod4 = require("zod");
3900
3904
 
@@ -3907,17 +3911,119 @@ function ensureDir(dirPath) {
3907
3911
  }
3908
3912
  function writeFile(filePath, content) {
3909
3913
  ensureDir(path.dirname(filePath));
3910
- fs.writeFileSync(filePath, content, "utf-8");
3914
+ fs.writeFileSync(filePath, withLineEndings(filePath, content), "utf-8");
3911
3915
  }
3912
3916
  function writeFileIfChanged(filePath, content) {
3917
+ const text3 = withLineEndings(filePath, content);
3913
3918
  if (fs.existsSync(filePath)) {
3914
3919
  const existing = fs.readFileSync(filePath, "utf-8");
3915
- if (existing === content) return false;
3920
+ if (existing === text3) return false;
3916
3921
  }
3917
3922
  ensureDir(path.dirname(filePath));
3918
- fs.writeFileSync(filePath, content, "utf-8");
3923
+ fs.writeFileSync(filePath, text3, "utf-8");
3919
3924
  return true;
3920
3925
  }
3926
+ var EOL_SAMPLE_BYTES = 64 * 1024;
3927
+ function endingsOf(text3) {
3928
+ const crlf = text3.split("\r\n").length - 1;
3929
+ const lf = text3.split("\n").length - 1 - crlf;
3930
+ if (crlf === 0 && lf === 0) return null;
3931
+ return crlf > lf ? "\r\n" : "\n";
3932
+ }
3933
+ function sampleOf(file) {
3934
+ let fd;
3935
+ try {
3936
+ fd = fs.openSync(file, "r");
3937
+ const buffer = Buffer.alloc(EOL_SAMPLE_BYTES);
3938
+ const read2 = fs.readSync(fd, buffer, 0, EOL_SAMPLE_BYTES, 0);
3939
+ return buffer.subarray(0, read2).toString("utf-8");
3940
+ } catch {
3941
+ return null;
3942
+ } finally {
3943
+ if (fd !== void 0) fs.closeSync(fd);
3944
+ }
3945
+ }
3946
+ function attributeMatches(pattern, relative20) {
3947
+ const glob = pattern.replace(/^\//, "");
3948
+ const target = glob.includes("/") ? relative20 : path.posix.basename(relative20);
3949
+ const source = glob.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*\//g, "\0").replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]").replace(/\u0000/g, "(?:.*/)?");
3950
+ return new RegExp(`^${source}$`).test(target);
3951
+ }
3952
+ function attributeEol(attributesFile, file) {
3953
+ const text3 = readFileOrNull(attributesFile);
3954
+ if (text3 === null) return null;
3955
+ const relative20 = path.relative(path.dirname(attributesFile), file).split(path.sep).join("/");
3956
+ let eol = null;
3957
+ for (const raw of text3.split(/\r?\n/)) {
3958
+ const line = raw.trim();
3959
+ if (line === "" || line.startsWith("#")) continue;
3960
+ const [pattern, ...attrs] = line.split(/\s+/);
3961
+ if (!attributeMatches(pattern, relative20)) continue;
3962
+ for (const attr of attrs) {
3963
+ if (attr === "eol=crlf") eol = "\r\n";
3964
+ else if (attr === "eol=lf") eol = "\n";
3965
+ else if (attr === "-text" || attr === "binary") eol = null;
3966
+ }
3967
+ }
3968
+ return eol;
3969
+ }
3970
+ function conventionFolders(dir) {
3971
+ const folders = [];
3972
+ let at = path.resolve(dir);
3973
+ for (let depth = 0; depth < 8; depth++) {
3974
+ folders.push(at);
3975
+ if (fs.existsSync(path.join(at, ".git")) || fs.existsSync(path.join(at, ".wai"))) return folders;
3976
+ const parent = path.dirname(at);
3977
+ if (parent === at) break;
3978
+ at = parent;
3979
+ }
3980
+ return folders.slice(0, 1);
3981
+ }
3982
+ function folderEndings(folder, ext, exclude) {
3983
+ let names;
3984
+ try {
3985
+ names = fs.readdirSync(folder);
3986
+ } catch {
3987
+ return null;
3988
+ }
3989
+ const candidates = names.filter((n) => path.join(folder, n) !== exclude && /\.(ya?ml|json|md|ts|js|txt)$/i.test(n)).sort((a, b) => Number(path.extname(b) === ext) - Number(path.extname(a) === ext)).slice(0, 24);
3990
+ let crlf = 0;
3991
+ let lf = 0;
3992
+ for (const name of candidates) {
3993
+ const full = path.join(folder, name);
3994
+ try {
3995
+ if (!fs.statSync(full).isFile()) continue;
3996
+ } catch {
3997
+ continue;
3998
+ }
3999
+ const sample = sampleOf(full);
4000
+ const endings = sample === null ? null : endingsOf(sample);
4001
+ if (endings === "\r\n") crlf++;
4002
+ else if (endings === "\n") lf++;
4003
+ }
4004
+ if (crlf === lf) return null;
4005
+ return crlf > lf ? "\r\n" : "\n";
4006
+ }
4007
+ function lineEndingFor(filePath) {
4008
+ const own = sampleOf(filePath);
4009
+ const existing = own === null ? null : endingsOf(own);
4010
+ if (existing) return existing;
4011
+ const folders = conventionFolders(path.dirname(filePath));
4012
+ for (const folder of folders) {
4013
+ const eol = attributeEol(path.join(folder, ".gitattributes"), filePath);
4014
+ if (eol) return eol;
4015
+ }
4016
+ const ext = path.extname(filePath);
4017
+ for (const folder of folders) {
4018
+ const endings = folderEndings(folder, ext, path.resolve(filePath));
4019
+ if (endings) return endings;
4020
+ }
4021
+ return "\n";
4022
+ }
4023
+ function withLineEndings(filePath, content) {
4024
+ const normalised = content.replace(/\r\n/g, "\n");
4025
+ return lineEndingFor(filePath) === "\r\n" ? normalised.replace(/\n/g, "\r\n") : normalised;
4026
+ }
3921
4027
  function readFileOrNull(filePath) {
3922
4028
  try {
3923
4029
  return fs.readFileSync(filePath, "utf-8");
@@ -4063,44 +4169,67 @@ function aiDirAt(root, ...segments) {
4063
4169
  return path.join(base, ...segments);
4064
4170
  }
4065
4171
 
4066
- // src/utils/yaml.ts
4067
- var yaml = __toESM(require("js-yaml"));
4068
- function parseYaml(content, sourcePath) {
4069
- try {
4070
- return yaml.load(content);
4071
- } catch (err) {
4072
- const loc = sourcePath ? ` (${sourcePath})` : "";
4073
- throw new Error(`Failed to parse YAML${loc}: ${String(err)}`);
4074
- }
4075
- }
4076
- function serializeYaml(value) {
4077
- return yaml.dump(value, {
4078
- indent: 2,
4079
- lineWidth: 100,
4080
- noRefs: true,
4081
- sortKeys: false
4082
- });
4083
- }
4084
- function readYamlFile(filePath) {
4085
- const content = readFileOrNull(filePath);
4086
- if (content === null) return null;
4087
- return parseYaml(content, filePath);
4088
- }
4089
- function writeYamlFile(filePath, value) {
4090
- writeFile(filePath, serializeYaml(value));
4172
+ // src/config/defaults.ts
4173
+ var os = __toESM(require("os"));
4174
+ var path2 = __toESM(require("path"));
4175
+ var DEFAULT_TARGET_DIRS = {
4176
+ claude: ".claude/agents",
4177
+ gemini: ".gemini/agents",
4178
+ agy: ".gemini/agents",
4179
+ cursor: ".cursor/rules",
4180
+ copilot: ".github/prompts",
4181
+ codex: ".codex/agents"
4182
+ };
4183
+ function defaultTargetConfig(type) {
4184
+ return {
4185
+ type,
4186
+ outputDir: DEFAULT_TARGET_DIRS[type],
4187
+ enabled: true
4188
+ };
4091
4189
  }
4092
- function readJsonFile(filePath) {
4093
- const content = readFileOrNull(filePath);
4094
- if (content === null) return null;
4095
- try {
4096
- return JSON.parse(content);
4097
- } catch (err) {
4098
- throw new Error(`Failed to parse JSON (${filePath}): ${String(err)}`);
4190
+ var WAIRON_VERSION = "5.1.1-dev.102";
4191
+ var GITHUB_REPO = "SYW-Apps/Waffle-AIron";
4192
+ var ARCHITECT_AGENT_ID = "agent-architect";
4193
+ var ARCHITECT_TEMPLATE_ID = "architect";
4194
+ function globalTemplatesDir(projectOverride) {
4195
+ if (process.env.WAIRON_TEMPLATES_DIR) {
4196
+ return process.env.WAIRON_TEMPLATES_DIR;
4197
+ }
4198
+ if (projectOverride) {
4199
+ return projectOverride;
4099
4200
  }
4201
+ return path2.join(os.homedir(), ".wairon", "templates");
4100
4202
  }
4101
- function writeJsonFile(filePath, value) {
4102
- writeFile(filePath, JSON.stringify(value, null, 2) + "\n");
4203
+ var BACKEND_COMMANDS = {
4204
+ claude: "claude",
4205
+ gemini: "gemini"
4206
+ };
4207
+ function backendCommand(backend) {
4208
+ return BACKEND_COMMANDS[backend] ?? backend;
4103
4209
  }
4210
+ var SUPPORTED_ALIASES = ["wai"];
4211
+ var SCAN_EXCLUDE_DIRS = /* @__PURE__ */ new Set([
4212
+ "node_modules",
4213
+ "dist",
4214
+ "build",
4215
+ "out",
4216
+ ".git",
4217
+ ".cache",
4218
+ "coverage",
4219
+ "__pycache__",
4220
+ ".venv",
4221
+ "venv",
4222
+ "target",
4223
+ // Rust
4224
+ "vendor"
4225
+ ]);
4226
+
4227
+ // src/core/adapters/git-source.ts
4228
+ var import_child_process = require("child_process");
4229
+ var crypto = __toESM(require("crypto"));
4230
+ var fs2 = __toESM(require("fs"));
4231
+ var os2 = __toESM(require("os"));
4232
+ var path3 = __toESM(require("path"));
4104
4233
 
4105
4234
  // src/utils/errors.ts
4106
4235
  var WaironError = class extends Error {
@@ -4150,6 +4279,167 @@ ${reasons.map((r) => ` - ${r}`).join("\n")}`);
4150
4279
  }
4151
4280
  };
4152
4281
 
4282
+ // src/core/adapters/git-source.ts
4283
+ var SourceUnavailable = class extends WaironError {
4284
+ constructor(message) {
4285
+ super(message);
4286
+ this.name = "SourceUnavailable";
4287
+ }
4288
+ };
4289
+ function osUserCache() {
4290
+ if (process.platform === "win32") return process.env.LOCALAPPDATA ?? path3.join(os2.homedir(), "AppData", "Local");
4291
+ if (process.platform === "darwin") return path3.join(os2.homedir(), "Library", "Caches");
4292
+ return process.env.XDG_CACHE_HOME ?? path3.join(os2.homedir(), ".cache");
4293
+ }
4294
+ var cacheRoots = /* @__PURE__ */ new Map();
4295
+ function cacheRoot() {
4296
+ const setting = process.env.WAIRON_CACHE_DIR ?? "";
4297
+ let root = cacheRoots.get(setting);
4298
+ if (root === void 0) {
4299
+ root = setting !== "" ? path3.resolve(setting) : path3.join(osUserCache(), "wairon");
4300
+ cacheRoots.set(setting, root);
4301
+ }
4302
+ return root;
4303
+ }
4304
+ function git(args, cwd) {
4305
+ return (0, import_child_process.execFileSync)("git", args, {
4306
+ cwd,
4307
+ encoding: "utf8",
4308
+ stdio: ["ignore", "pipe", "pipe"],
4309
+ env: { ...process.env, GIT_TERMINAL_PROMPT: "0", GIT_ASKPASS: "echo" }
4310
+ });
4311
+ }
4312
+ function reasonOf(e) {
4313
+ const stderr = e?.stderr;
4314
+ const text3 = typeof stderr === "string" && stderr.trim() !== "" ? stderr : e instanceof Error ? e.message : String(e);
4315
+ return text3.trim().split(/\r?\n/).filter((l) => l.trim() !== "").slice(-1)[0] ?? "git failed";
4316
+ }
4317
+ function cacheEntryOf(url, commit2) {
4318
+ const repo = crypto.createHash("sha256").update(url).digest("hex");
4319
+ return path3.join(cacheRoot(), "git", repo, commit2);
4320
+ }
4321
+ function within(entry, url, commit2, dir) {
4322
+ const at = dir ? path3.join(entry, dir) : entry;
4323
+ if (!fs2.existsSync(at)) {
4324
+ throw new SourceUnavailable(`the commit ${commit2} of ${url} holds no "${dir}"`);
4325
+ }
4326
+ return at;
4327
+ }
4328
+ function fetchInto(tmp, url, commit2) {
4329
+ git(["init", "-q"], tmp);
4330
+ try {
4331
+ git(["fetch", "-q", "--depth", "1", url, commit2], tmp);
4332
+ } catch {
4333
+ git(["fetch", "-q", url, "+refs/heads/*:refs/remotes/source/*", "+refs/tags/*:refs/tags/*"], tmp);
4334
+ }
4335
+ git(["-c", "advice.detachedHead=false", "checkout", "-q", commit2], tmp);
4336
+ fs2.rmSync(path3.join(tmp, ".git"), { recursive: true, force: true });
4337
+ }
4338
+ function fetch(url, commit2, dir) {
4339
+ const entry = cacheEntryOf(url, commit2);
4340
+ if (fs2.existsSync(entry)) return within(entry, url, commit2, dir);
4341
+ fs2.mkdirSync(path3.dirname(entry), { recursive: true });
4342
+ const tmp = `${entry}.tmp-${process.pid}-${crypto.randomBytes(4).toString("hex")}`;
4343
+ fs2.mkdirSync(tmp, { recursive: true });
4344
+ try {
4345
+ fetchInto(tmp, url, commit2);
4346
+ try {
4347
+ fs2.renameSync(tmp, entry);
4348
+ } catch (e) {
4349
+ if (!fs2.existsSync(entry)) throw e;
4350
+ fs2.rmSync(tmp, { recursive: true, force: true });
4351
+ }
4352
+ } catch (e) {
4353
+ fs2.rmSync(tmp, { recursive: true, force: true });
4354
+ if (fs2.readdirSync(path3.dirname(entry)).length === 0) fs2.rmdirSync(path3.dirname(entry));
4355
+ throw e instanceof SourceUnavailable ? e : new SourceUnavailable(`cannot fetch ${commit2} of ${url}: ${reasonOf(e)}`);
4356
+ }
4357
+ return within(entry, url, commit2, dir);
4358
+ }
4359
+ function resolve3(url, ref) {
4360
+ let listed;
4361
+ try {
4362
+ listed = git(["ls-remote", url, ref ?? "HEAD"]);
4363
+ } catch (e) {
4364
+ throw new SourceUnavailable(`cannot reach ${url}: ${reasonOf(e)}`);
4365
+ }
4366
+ const wanted = commitListed(listed, ref ?? "HEAD");
4367
+ if (!wanted) throw new SourceUnavailable(`${url} lists no ref "${ref ?? "HEAD"}"`);
4368
+ return wanted;
4369
+ }
4370
+ function commitListed(listed, ref) {
4371
+ const lines = listed.split(/\r?\n/).filter((l) => l.trim() !== "").map((l) => l.split(/\s+/));
4372
+ const names = [ref, `refs/heads/${ref}`, `refs/tags/${ref}^{}`, `refs/tags/${ref}`];
4373
+ for (const name of names) {
4374
+ const line = lines.find(([, listedName]) => listedName === name);
4375
+ if (line) return line[0];
4376
+ }
4377
+ return void 0;
4378
+ }
4379
+ function trackedFiles(directory, pathspecs) {
4380
+ if (pathspecs.length === 0) return [];
4381
+ try {
4382
+ return git(["ls-files", "-z", "--", ...pathspecs], directory).split("\0").filter((p) => p !== "");
4383
+ } catch {
4384
+ return null;
4385
+ }
4386
+ }
4387
+ function head(directory) {
4388
+ try {
4389
+ return git(["rev-parse", "HEAD"], directory).trim() || null;
4390
+ } catch {
4391
+ return null;
4392
+ }
4393
+ }
4394
+ function repositoryRoot(directory) {
4395
+ let at = path3.resolve(directory);
4396
+ for (; ; ) {
4397
+ if (fs2.existsSync(path3.join(at, ".git"))) return at;
4398
+ const up = path3.dirname(at);
4399
+ if (up === at) return null;
4400
+ at = up;
4401
+ }
4402
+ }
4403
+
4404
+ // src/utils/yaml.ts
4405
+ var yaml = __toESM(require("js-yaml"));
4406
+ function parseYaml(content, sourcePath) {
4407
+ try {
4408
+ return yaml.load(content);
4409
+ } catch (err) {
4410
+ const loc = sourcePath ? ` (${sourcePath})` : "";
4411
+ throw new Error(`Failed to parse YAML${loc}: ${String(err)}`);
4412
+ }
4413
+ }
4414
+ function serializeYaml(value) {
4415
+ return yaml.dump(value, {
4416
+ indent: 2,
4417
+ lineWidth: 100,
4418
+ noRefs: true,
4419
+ sortKeys: false
4420
+ });
4421
+ }
4422
+ function readYamlFile(filePath) {
4423
+ const content = readFileOrNull(filePath);
4424
+ if (content === null) return null;
4425
+ return parseYaml(content, filePath);
4426
+ }
4427
+ function writeYamlFile(filePath, value) {
4428
+ writeFile(filePath, serializeYaml(value));
4429
+ }
4430
+ function readJsonFile(filePath) {
4431
+ const content = readFileOrNull(filePath);
4432
+ if (content === null) return null;
4433
+ try {
4434
+ return JSON.parse(content);
4435
+ } catch (err) {
4436
+ throw new Error(`Failed to parse JSON (${filePath}): ${String(err)}`);
4437
+ }
4438
+ }
4439
+ function writeJsonFile(filePath, value) {
4440
+ writeFile(filePath, JSON.stringify(value, null, 2) + "\n");
4441
+ }
4442
+
4153
4443
  // src/models/identity-rename.ts
4154
4444
  var EDGE = " -> ";
4155
4445
  var QUALIFIED_UNIT = /^(\d+:)?([^.\s]+)\.([^.\s]+)$/;
@@ -4185,7 +4475,7 @@ function rekeyAnchor(rename, anchor) {
4185
4475
  }
4186
4476
 
4187
4477
  // src/models/project.ts
4188
- var path2 = __toESM(require("path"));
4478
+ var path4 = __toESM(require("path"));
4189
4479
  var import_zod3 = require("zod");
4190
4480
 
4191
4481
  // src/models/agent.ts
@@ -4735,7 +5025,7 @@ var ExternalDeclarationSchema = import_zod3.z.object({
4735
5025
  /**
4736
5026
  * The producer's public names this project imports, so its specs may name
4737
5027
  * them bare: `['*']` imports every public name the producer exports to this
4738
- * project, `[waffler-error]` only those. Plain strings: a malformed entry is
5028
+ * project, `[shared-error]` only those. Plain strings: a malformed entry is
4739
5029
  * REPORTED (the declaration's problem), never unreadable.
4740
5030
  */
4741
5031
  use: import_zod3.z.array(import_zod3.z.string()).optional(),
@@ -4938,7 +5228,7 @@ var PACK_EXT_RE = /\.(ya?ml|cjs|js)$/i;
4938
5228
  function declaredPackNames(config) {
4939
5229
  const selection = config.profileSelection;
4940
5230
  return [.../* @__PURE__ */ new Set([
4941
- ...(config.extensions?.packs ?? []).map((entry) => typeof entry === "string" ? path2.basename(entry).replace(PACK_EXT_RE, "") : entry.name),
5231
+ ...(config.extensions?.packs ?? []).map((entry) => typeof entry === "string" ? path4.basename(entry).replace(PACK_EXT_RE, "") : entry.name),
4942
5232
  ...selection?.requiredPackNames ?? [],
4943
5233
  ...selection?.defaultPackNames ?? []
4944
5234
  ])];
@@ -5236,13 +5526,13 @@ function isPart(config) {
5236
5526
 
5237
5527
  // src/config/project-config.ts
5238
5528
  function waiDirAt(root) {
5239
- const wai = path3.join(root, ".wai");
5240
- const legacy = path3.join(root, ".wairon");
5241
- return !fs2.existsSync(wai) && fs2.existsSync(legacy) ? legacy : wai;
5529
+ const wai = path5.join(root, ".wai");
5530
+ const legacy = path5.join(root, ".wairon");
5531
+ return !fs3.existsSync(wai) && fs3.existsSync(legacy) ? legacy : wai;
5242
5532
  }
5243
5533
  function projectConfigFsAdapterAt(rootDir) {
5244
- const root = path3.resolve(rootDir);
5245
- const file = () => path3.join(waiDirAt(root), "project.yaml");
5534
+ const root = path5.resolve(rootDir);
5535
+ const file = () => path5.join(waiDirAt(root), "project.yaml");
5246
5536
  return {
5247
5537
  readDocument() {
5248
5538
  return readYamlFile(file());
@@ -5256,7 +5546,7 @@ function projectConfigFsAdapterAt(rootDir) {
5256
5546
  writeFile(file(), editDocumentText(text3, document, root));
5257
5547
  },
5258
5548
  documentExists() {
5259
- return fs2.existsSync(file());
5549
+ return fs3.existsSync(file());
5260
5550
  },
5261
5551
  readText() {
5262
5552
  return readFileOrNull(file());
@@ -5278,8 +5568,9 @@ function storeOver(adapter, root) {
5278
5568
  if (!checked.success) {
5279
5569
  throw new WaironError(`Refusing to write an invalid .wai/project.yaml at ${root}: ${checked.error.message}`);
5280
5570
  }
5281
- const document = config.partOf !== void 0 ? { schemaVersion: config.schemaVersion, partOf: config.partOf } : config;
5282
- adapter.writeDocument(overlayKnownFields(ProjectConfigSchema, document, currentDocument()));
5571
+ const onDisk = currentDocument();
5572
+ const document = config.partOf !== void 0 ? { schemaVersion: config.schemaVersion, partOf: config.partOf } : withoutDefaultedMaterialization(config, onDisk);
5573
+ adapter.writeDocument(overlayKnownFields(ProjectConfigSchema, document, onDisk));
5283
5574
  },
5284
5575
  exists() {
5285
5576
  return adapter.documentExists();
@@ -5679,9 +5970,69 @@ function asReadable(document) {
5679
5970
  if (!isPlainObject(document) || document.partOf === void 0) return document;
5680
5971
  return { name: "", createdAt: PART_EPOCH, updatedAt: PART_EPOCH, ...document };
5681
5972
  }
5973
+ var MANAGED_AGENT_MARKER = "wairon:managed";
5974
+ var MANAGED_AGENT_NAME = /-(owner|implementer|architect)\.(md|ya?ml)$/;
5975
+ function holdsMaterializedAgentFiles(document, root) {
5976
+ const dirs = new Set(Object.values(DEFAULT_TARGET_DIRS));
5977
+ for (const target of Array.isArray(document.targets) ? document.targets : []) {
5978
+ const outputDir = isPlainObject(target) ? target.outputDir : void 0;
5979
+ if (typeof outputDir === "string") dirs.add(outputDir);
5980
+ }
5981
+ const base = path5.resolve(root);
5982
+ const inside = [];
5983
+ for (const dir of dirs) {
5984
+ const rel2 = path5.relative(base, path5.resolve(base, dir));
5985
+ if (rel2.startsWith("..") || path5.isAbsolute(rel2)) continue;
5986
+ let present = false;
5987
+ try {
5988
+ present = fs3.statSync(path5.join(base, rel2)).isDirectory();
5989
+ } catch {
5990
+ present = false;
5991
+ }
5992
+ if (present) inside.push(rel2 === "" ? "." : rel2.split(path5.sep).join("/"));
5993
+ }
5994
+ if (inside.length === 0) return false;
5995
+ const tracked = trackedFiles(base, inside);
5996
+ const candidates = [];
5997
+ if (tracked === null) {
5998
+ for (const dir of inside) {
5999
+ let names;
6000
+ try {
6001
+ names = fs3.readdirSync(path5.join(base, dir));
6002
+ } catch {
6003
+ continue;
6004
+ }
6005
+ for (const name of names) candidates.push(path5.posix.join(dir, name));
6006
+ }
6007
+ } else {
6008
+ const folders = new Set(inside.map((dir) => path5.posix.normalize(dir)));
6009
+ for (const file of tracked) if (folders.has(path5.posix.dirname(file))) candidates.push(file);
6010
+ }
6011
+ for (const file of candidates) {
6012
+ const head2 = readFileOrNull(path5.join(base, file));
6013
+ if (head2 === null) continue;
6014
+ if (MANAGED_AGENT_NAME.test(path5.posix.basename(file))) return true;
6015
+ if (head2.slice(0, 4096).includes(MANAGED_AGENT_MARKER)) return true;
6016
+ }
6017
+ return false;
6018
+ }
6019
+ function withInferredMaterialization(document, root) {
6020
+ if (!isPlainObject(document) || document.partOf !== void 0) return document;
6021
+ const rules = isPlainObject(document.rules) ? document.rules : void 0;
6022
+ if (rules !== void 0 && hasOwn(rules, "materializeAgentFiles")) return document;
6023
+ if (!holdsMaterializedAgentFiles(document, root)) return document;
6024
+ return { ...document, rules: { ...rules ?? {}, materializeAgentFiles: true } };
6025
+ }
6026
+ function withoutDefaultedMaterialization(config, onDisk) {
6027
+ if (!isPlainObject(onDisk) || config.rules?.materializeAgentFiles !== false) return config;
6028
+ const stated = isPlainObject(onDisk.rules) && hasOwn(onDisk.rules, "materializeAgentFiles");
6029
+ if (stated) return config;
6030
+ const { materializeAgentFiles: _defaulted, ...rules } = config.rules;
6031
+ return { ...config, rules };
6032
+ }
5682
6033
  function parseConfig(document, root) {
5683
6034
  try {
5684
- return ProjectConfigSchema.parse(asReadable(document));
6035
+ return ProjectConfigSchema.parse(asReadable(withInferredMaterialization(document, root)));
5685
6036
  } catch (e) {
5686
6037
  throw new WaironError(
5687
6038
  `Invalid .wai/project.yaml: ${e instanceof Error ? e.message : String(e)}
@@ -6122,15 +6473,15 @@ function indexOver(store, root) {
6122
6473
  specsDir() {
6123
6474
  try {
6124
6475
  const declared = store.readField("paths.specsDir");
6125
- if (declared) return path3.resolve(root, declared);
6476
+ if (declared) return path5.resolve(root, declared);
6126
6477
  } catch {
6127
6478
  }
6128
- return path3.join(waiDirAt(root), "specs");
6479
+ return path5.join(waiDirAt(root), "specs");
6129
6480
  }
6130
6481
  };
6131
6482
  }
6132
6483
  function projectConfigRepositoryOver(adapter, rootDir) {
6133
- const root = path3.resolve(rootDir);
6484
+ const root = path5.resolve(rootDir);
6134
6485
  const store = storeOver(adapter, root);
6135
6486
  const index = indexOver(store, root);
6136
6487
  const registry = registryOver(store, root);
@@ -6323,15 +6674,15 @@ var fs14 = __toESM(require("fs"));
6323
6674
  var path21 = __toESM(require("path"));
6324
6675
 
6325
6676
  // src/config/paths.ts
6326
- var fs3 = __toESM(require("fs"));
6327
- var path4 = __toESM(require("path"));
6677
+ var fs4 = __toESM(require("fs"));
6678
+ var path6 = __toESM(require("path"));
6328
6679
  function aiPathsAt(rootDir) {
6329
- const resolvedRoot = path4.resolve(rootDir);
6680
+ const resolvedRoot = path6.resolve(rootDir);
6330
6681
  const aiDirAt2 = (...segments) => {
6331
- const waiPath = path4.join(resolvedRoot, ".wai");
6332
- const waironPath = path4.join(resolvedRoot, ".wairon");
6333
- const base = !fs3.existsSync(waiPath) && fs3.existsSync(waironPath) ? waironPath : waiPath;
6334
- return path4.join(base, ...segments);
6682
+ const waiPath = path6.join(resolvedRoot, ".wai");
6683
+ const waironPath = path6.join(resolvedRoot, ".wairon");
6684
+ const base = !fs4.existsSync(waiPath) && fs4.existsSync(waironPath) ? waironPath : waiPath;
6685
+ return path6.join(base, ...segments);
6335
6686
  };
6336
6687
  const specsDir = () => projectConfigRepositoryAt(resolvedRoot).specsDir();
6337
6688
  return {
@@ -6348,12 +6699,12 @@ function aiPathsAt(rootDir) {
6348
6699
  contextDomainsMd: () => aiDirAt2("context", "domains.md"),
6349
6700
  contextWaironGuideMd: () => aiDirAt2("context", "wairon-guide.md"),
6350
6701
  specsDir,
6351
- specsSystem: () => path4.join(specsDir(), ".index.yaml"),
6352
- specsSubsystemsDir: () => path4.join(specsDir(), "subsystems"),
6353
- specsComponentsDir: () => path4.join(specsDir(), "components"),
6354
- specsInterfacesDir: () => path4.join(specsDir(), "interfaces"),
6355
- specsImplementationsDir: () => path4.join(specsDir(), "implementations"),
6356
- specsTypesDir: () => path4.join(specsDir(), "types")
6702
+ specsSystem: () => path6.join(specsDir(), ".index.yaml"),
6703
+ specsSubsystemsDir: () => path6.join(specsDir(), "subsystems"),
6704
+ specsComponentsDir: () => path6.join(specsDir(), "components"),
6705
+ specsInterfacesDir: () => path6.join(specsDir(), "interfaces"),
6706
+ specsImplementationsDir: () => path6.join(specsDir(), "implementations"),
6707
+ specsTypesDir: () => path6.join(specsDir(), "types")
6357
6708
  };
6358
6709
  }
6359
6710
  var AI_PATHS = {
@@ -6387,8 +6738,8 @@ function assertProjectInitialized() {
6387
6738
  }
6388
6739
 
6389
6740
  // src/core/statehash.ts
6390
- var crypto = __toESM(require("crypto"));
6391
- var path5 = __toESM(require("path"));
6741
+ var crypto2 = __toESM(require("crypto"));
6742
+ var path7 = __toESM(require("path"));
6392
6743
 
6393
6744
  // src/utils/canonical-json.ts
6394
6745
  function canonicalize(value) {
@@ -6427,7 +6778,7 @@ function digestTree(keep, asStored = (impl) => impl) {
6427
6778
  implementations: kind(loadImplementationSpecs().map(asStored)),
6428
6779
  types: kind(loadTypeSpecs())
6429
6780
  };
6430
- const digest3 = crypto.createHash("sha256").update(canonicalize(tree)).digest("hex");
6781
+ const digest3 = crypto2.createHash("sha256").update(canonicalize(tree)).digest("hex");
6431
6782
  return { algorithm: CONTENT_ALGORITHM, digest: digest3 };
6432
6783
  }
6433
6784
  function computeStateId() {
@@ -6447,11 +6798,11 @@ function computeOwnStateId() {
6447
6798
  return digestTree((id) => (owners.get(id) ?? "") === "", asStored);
6448
6799
  }
6449
6800
  function isInside(dir, file) {
6450
- const rel2 = path5.relative(dir, file);
6451
- return rel2 !== "" && !rel2.startsWith("..") && !path5.isAbsolute(rel2);
6801
+ const rel2 = path7.relative(dir, file);
6802
+ return rel2 !== "" && !rel2.startsWith("..") && !path7.isAbsolute(rel2);
6452
6803
  }
6453
6804
  function fromPartRoot(impl, root, partDir) {
6454
- const from = (p) => path5.isAbsolute(p) ? p : path5.relative(partDir, path5.resolve(root, p)).split(path5.sep).join("/");
6805
+ const from = (p) => path7.isAbsolute(p) ? p : path7.relative(partDir, path7.resolve(root, p)).split(path7.sep).join("/");
6455
6806
  return {
6456
6807
  ...impl,
6457
6808
  ...impl.sourcePath ? { sourcePath: from(impl.sourcePath) } : {},
@@ -6464,8 +6815,8 @@ function stateIdEquals(a, b) {
6464
6815
  }
6465
6816
 
6466
6817
  // src/core/lockfile.ts
6467
- var fs4 = __toESM(require("fs"));
6468
- var path6 = __toESM(require("path"));
6818
+ var fs5 = __toESM(require("fs"));
6819
+ var path8 = __toESM(require("path"));
6469
6820
  function lockPath() {
6470
6821
  return aiDir("lock.json");
6471
6822
  }
@@ -6479,14 +6830,14 @@ function normalizeApprover(value) {
6479
6830
  }
6480
6831
  function readLockRecord() {
6481
6832
  try {
6482
- return normalizeRecord(JSON.parse(fs4.readFileSync(lockPath(), "utf8")));
6833
+ return normalizeRecord(JSON.parse(fs5.readFileSync(lockPath(), "utf8")));
6483
6834
  } catch {
6484
6835
  return null;
6485
6836
  }
6486
6837
  }
6487
6838
  function readLockRecordAt(root) {
6488
6839
  try {
6489
- return normalizeRecord(JSON.parse(fs4.readFileSync(aiDirAt(root, "lock.json"), "utf8")));
6840
+ return normalizeRecord(JSON.parse(fs5.readFileSync(aiDirAt(root, "lock.json"), "utf8")));
6490
6841
  } catch {
6491
6842
  return null;
6492
6843
  }
@@ -6497,10 +6848,10 @@ function normalizeRecord(raw) {
6497
6848
  }
6498
6849
  function writeLockRecord(record) {
6499
6850
  const p = lockPath();
6500
- fs4.mkdirSync(path6.dirname(p), { recursive: true });
6851
+ fs5.mkdirSync(path8.dirname(p), { recursive: true });
6501
6852
  const tmp = `${p}.tmp`;
6502
- fs4.writeFileSync(tmp, JSON.stringify(withSortedMaps(record), null, 2) + "\n");
6503
- fs4.renameSync(tmp, p);
6853
+ fs5.writeFileSync(tmp, withLineEndings(p, JSON.stringify(withSortedMaps(record), null, 2) + "\n"));
6854
+ fs5.renameSync(tmp, p);
6504
6855
  }
6505
6856
  function withSortedMaps(record) {
6506
6857
  const sorted = (m) => {
@@ -6520,8 +6871,8 @@ function withSortedMaps(record) {
6520
6871
  }
6521
6872
 
6522
6873
  // src/core/spec-files.ts
6523
- var fs5 = __toESM(require("fs"));
6524
- var path7 = __toESM(require("path"));
6874
+ var fs6 = __toESM(require("fs"));
6875
+ var path9 = __toESM(require("path"));
6525
6876
  var SPEC_FILE_EXTENSION = ".yaml";
6526
6877
  function readSpecFile(specPath) {
6527
6878
  return readYamlFile(specPath);
@@ -6533,136 +6884,17 @@ function listSpecFiles(specsDir) {
6533
6884
  return listFilesRecursive(specsDir, SPEC_FILE_EXTENSION);
6534
6885
  }
6535
6886
  function removeSpecFile(specPath, specsRoot) {
6536
- if (!fs5.existsSync(specPath)) return false;
6537
- fs5.unlinkSync(specPath);
6538
- pruneEmptyDirs(path7.dirname(specPath), path7.resolve(specsRoot));
6887
+ if (!fs6.existsSync(specPath)) return false;
6888
+ fs6.unlinkSync(specPath);
6889
+ pruneEmptyDirs(path9.dirname(specPath), path9.resolve(specsRoot));
6539
6890
  return true;
6540
6891
  }
6541
6892
  function pruneEmptyDirs(dir, specsRoot) {
6542
6893
  let at = dir;
6543
6894
  while (at !== specsRoot && at.startsWith(specsRoot)) {
6544
- if (!fs5.existsSync(at) || fs5.readdirSync(at).length > 0) return;
6545
- fs5.rmdirSync(at);
6546
- at = path7.dirname(at);
6547
- }
6548
- }
6549
-
6550
- // src/core/adapters/git-source.ts
6551
- var import_child_process = require("child_process");
6552
- var crypto2 = __toESM(require("crypto"));
6553
- var fs6 = __toESM(require("fs"));
6554
- var os = __toESM(require("os"));
6555
- var path8 = __toESM(require("path"));
6556
- var SourceUnavailable = class extends WaironError {
6557
- constructor(message) {
6558
- super(message);
6559
- this.name = "SourceUnavailable";
6560
- }
6561
- };
6562
- function osUserCache() {
6563
- if (process.platform === "win32") return process.env.LOCALAPPDATA ?? path8.join(os.homedir(), "AppData", "Local");
6564
- if (process.platform === "darwin") return path8.join(os.homedir(), "Library", "Caches");
6565
- return process.env.XDG_CACHE_HOME ?? path8.join(os.homedir(), ".cache");
6566
- }
6567
- var cacheRoots = /* @__PURE__ */ new Map();
6568
- function cacheRoot() {
6569
- const setting = process.env.WAIRON_CACHE_DIR ?? "";
6570
- let root = cacheRoots.get(setting);
6571
- if (root === void 0) {
6572
- root = setting !== "" ? path8.resolve(setting) : path8.join(osUserCache(), "wairon");
6573
- cacheRoots.set(setting, root);
6574
- }
6575
- return root;
6576
- }
6577
- function git(args, cwd) {
6578
- return (0, import_child_process.execFileSync)("git", args, {
6579
- cwd,
6580
- encoding: "utf8",
6581
- stdio: ["ignore", "pipe", "pipe"],
6582
- env: { ...process.env, GIT_TERMINAL_PROMPT: "0", GIT_ASKPASS: "echo" }
6583
- });
6584
- }
6585
- function reasonOf(e) {
6586
- const stderr = e?.stderr;
6587
- const text3 = typeof stderr === "string" && stderr.trim() !== "" ? stderr : e instanceof Error ? e.message : String(e);
6588
- return text3.trim().split(/\r?\n/).filter((l) => l.trim() !== "").slice(-1)[0] ?? "git failed";
6589
- }
6590
- function cacheEntryOf(url, commit2) {
6591
- const repo = crypto2.createHash("sha256").update(url).digest("hex");
6592
- return path8.join(cacheRoot(), "git", repo, commit2);
6593
- }
6594
- function within(entry, url, commit2, dir) {
6595
- const at = dir ? path8.join(entry, dir) : entry;
6596
- if (!fs6.existsSync(at)) {
6597
- throw new SourceUnavailable(`the commit ${commit2} of ${url} holds no "${dir}"`);
6598
- }
6599
- return at;
6600
- }
6601
- function fetchInto(tmp, url, commit2) {
6602
- git(["init", "-q"], tmp);
6603
- try {
6604
- git(["fetch", "-q", "--depth", "1", url, commit2], tmp);
6605
- } catch {
6606
- git(["fetch", "-q", url, "+refs/heads/*:refs/remotes/source/*", "+refs/tags/*:refs/tags/*"], tmp);
6607
- }
6608
- git(["-c", "advice.detachedHead=false", "checkout", "-q", commit2], tmp);
6609
- fs6.rmSync(path8.join(tmp, ".git"), { recursive: true, force: true });
6610
- }
6611
- function fetch(url, commit2, dir) {
6612
- const entry = cacheEntryOf(url, commit2);
6613
- if (fs6.existsSync(entry)) return within(entry, url, commit2, dir);
6614
- fs6.mkdirSync(path8.dirname(entry), { recursive: true });
6615
- const tmp = `${entry}.tmp-${process.pid}-${crypto2.randomBytes(4).toString("hex")}`;
6616
- fs6.mkdirSync(tmp, { recursive: true });
6617
- try {
6618
- fetchInto(tmp, url, commit2);
6619
- try {
6620
- fs6.renameSync(tmp, entry);
6621
- } catch (e) {
6622
- if (!fs6.existsSync(entry)) throw e;
6623
- fs6.rmSync(tmp, { recursive: true, force: true });
6624
- }
6625
- } catch (e) {
6626
- fs6.rmSync(tmp, { recursive: true, force: true });
6627
- if (fs6.readdirSync(path8.dirname(entry)).length === 0) fs6.rmdirSync(path8.dirname(entry));
6628
- throw e instanceof SourceUnavailable ? e : new SourceUnavailable(`cannot fetch ${commit2} of ${url}: ${reasonOf(e)}`);
6629
- }
6630
- return within(entry, url, commit2, dir);
6631
- }
6632
- function resolve7(url, ref) {
6633
- let listed;
6634
- try {
6635
- listed = git(["ls-remote", url, ref ?? "HEAD"]);
6636
- } catch (e) {
6637
- throw new SourceUnavailable(`cannot reach ${url}: ${reasonOf(e)}`);
6638
- }
6639
- const wanted = commitListed(listed, ref ?? "HEAD");
6640
- if (!wanted) throw new SourceUnavailable(`${url} lists no ref "${ref ?? "HEAD"}"`);
6641
- return wanted;
6642
- }
6643
- function commitListed(listed, ref) {
6644
- const lines = listed.split(/\r?\n/).filter((l) => l.trim() !== "").map((l) => l.split(/\s+/));
6645
- const names = [ref, `refs/heads/${ref}`, `refs/tags/${ref}^{}`, `refs/tags/${ref}`];
6646
- for (const name of names) {
6647
- const line = lines.find(([, listedName]) => listedName === name);
6648
- if (line) return line[0];
6649
- }
6650
- return void 0;
6651
- }
6652
- function head(directory) {
6653
- try {
6654
- return git(["rev-parse", "HEAD"], directory).trim() || null;
6655
- } catch {
6656
- return null;
6657
- }
6658
- }
6659
- function repositoryRoot(directory) {
6660
- let at = path8.resolve(directory);
6661
- for (; ; ) {
6662
- if (fs6.existsSync(path8.join(at, ".git"))) return at;
6663
- const up = path8.dirname(at);
6664
- if (up === at) return null;
6665
- at = up;
6895
+ if (!fs6.existsSync(at) || fs6.readdirSync(at).length > 0) return;
6896
+ fs6.rmdirSync(at);
6897
+ at = path9.dirname(at);
6666
6898
  }
6667
6899
  }
6668
6900
 
@@ -8061,7 +8293,11 @@ var GENERIC_ALIASES = /* @__PURE__ */ new Map([
8061
8293
  ["HashMap", { form: "map", arity: 2 }],
8062
8294
  ["Option", { form: "optional", arity: 1 }],
8063
8295
  ["Optional", { form: "optional", arity: 1 }],
8064
- ["Promise", { form: "async", arity: 1 }]
8296
+ ["Promise", { form: "async", arity: 1 }],
8297
+ // The success-or-failure type the mainstream languages with typed failures
8298
+ // share (Rust, Swift, Kotlin, F#); Rust's spelling is its alias.
8299
+ ["result", { form: "result", arity: 2 }],
8300
+ ["Result", { form: "result", arity: 2 }]
8065
8301
  ]);
8066
8302
  var NOT_NEUTRAL = /* @__PURE__ */ new Map([
8067
8303
  ["uuid", "a named value-object holding a string (e.g. order_id)"],
@@ -8070,7 +8306,6 @@ var NOT_NEUTRAL = /* @__PURE__ */ new Map([
8070
8306
  ["byte", "int, or bytes for binary data"],
8071
8307
  ["time", "datetime for an instant, or duration for an elapsed time"],
8072
8308
  ["tuple", "a named value-object"],
8073
- ["result", "the success type as the returns (failures as findings or throws), or a named value-object"],
8074
8309
  ["error", "a named value-object"],
8075
8310
  ["never", "void"],
8076
8311
  ["box", "the inner type (ownership is an implementation detail)"],
@@ -8265,9 +8500,9 @@ var RawParser = class {
8265
8500
  }
8266
8501
  name += this.ident();
8267
8502
  while (this.isP("::") || this.isP(".")) {
8268
- const sep14 = this.peek().v;
8503
+ const sep15 = this.peek().v;
8269
8504
  this.at++;
8270
- name += sep14 + this.ident();
8505
+ name += sep15 + this.ident();
8271
8506
  }
8272
8507
  if (!this.isP("<")) return { k: "name", name };
8273
8508
  this.at++;
@@ -8294,6 +8529,10 @@ var RawParser = class {
8294
8529
  const close2 = this.closingParen();
8295
8530
  const after = close2 === -1 ? void 0 : this.tokens[close2 + 1];
8296
8531
  if (!after || after.t !== "p" || after.v !== "=>") {
8532
+ if (this.isP(")", 1)) {
8533
+ this.at += 2;
8534
+ return { k: "name", name: "void" };
8535
+ }
8297
8536
  this.expect("(");
8298
8537
  const inner2 = this.type();
8299
8538
  this.expect(")");
@@ -8402,7 +8641,7 @@ function optionalOf(inner2) {
8402
8641
  return { form: "optional", args: [inner2] };
8403
8642
  }
8404
8643
  function inner(ctx) {
8405
- return { ...ctx, top: false, voidAllowed: false, omittable: false };
8644
+ return { ...ctx, top: false, voidAllowed: false, resultAllowed: false, omittable: false };
8406
8645
  }
8407
8646
  function normalise(node, ctx, notes) {
8408
8647
  switch (node.k) {
@@ -8436,9 +8675,21 @@ function asyncOf(innerNode, ctx, notes, spelled) {
8436
8675
  if (!ctx.top || !RETURNS_POSITIONS.has(ctx.position)) {
8437
8676
  throw new TypeProblemError("TYPE_POSITION_INVALID", `${spelled} may stand only at the top of a returns, not in a ${ctx.position}`);
8438
8677
  }
8439
- const read2 = normalise(innerNode, { ...inner(ctx), voidAllowed: true }, notes);
8678
+ const read2 = normalise(innerNode, { ...inner(ctx), voidAllowed: true, resultAllowed: true }, notes);
8440
8679
  return { form: "async", args: [read2] };
8441
8680
  }
8681
+ function resultOf(args, ctx, notes, spelled) {
8682
+ if (!ctx.resultAllowed) {
8683
+ throw new TypeProblemError(
8684
+ "TYPE_POSITION_INVALID",
8685
+ `${spelled} may stand only as a whole returns (or under its async), not in a ${ctx.top ? ctx.position : "collection, union, optional or argument"}`,
8686
+ "the success type here (a failure belongs to a method's returns)"
8687
+ );
8688
+ }
8689
+ const ok = normalise(args[0], { ...inner(ctx), voidAllowed: true }, notes);
8690
+ const err = normalise(args[1], inner(ctx), notes);
8691
+ return { form: "result", args: [ok, err] };
8692
+ }
8442
8693
  function bareName(name, ctx, notes) {
8443
8694
  const lower = name.toLowerCase();
8444
8695
  if (NONES.has(name)) {
@@ -8476,6 +8727,7 @@ function appliedName(name, args, ctx, notes) {
8476
8727
  throw new TypeProblemError("TYPE_EXPRESSION_INVALID", `"${name}" takes ${alias.arity} type argument${alias.arity === 1 ? "" : "s"}, ${args.length} given`);
8477
8728
  }
8478
8729
  if (alias.form === "async") return asyncOf(args[0], ctx, notes, `${name}<...>`);
8730
+ if (alias.form === "result") return resultOf(args, ctx, notes, `${name}<...>`);
8479
8731
  if (alias.form === "optional") return optionalOf(normalise(args[0], inner(ctx), notes));
8480
8732
  const read2 = args.map((a) => normalise(a, inner(ctx), notes));
8481
8733
  if (alias.form === "map") assertMapKey(read2[0], args[0]);
@@ -8549,6 +8801,8 @@ function canonicalTypeText(expr) {
8549
8801
  return `${expr.form}<${canonicalTypeText(expr.args[0])}>`;
8550
8802
  case "map":
8551
8803
  return `map<${canonicalTypeText(expr.args[0])}, ${canonicalTypeText(expr.args[1])}>`;
8804
+ case "result":
8805
+ return `result<${canonicalTypeText(expr.args[0])}, ${canonicalTypeText(expr.args[1])}>`;
8552
8806
  case "optional": {
8553
8807
  const innerText = canonicalTypeText(expr.args[0]);
8554
8808
  return expr.args[0].form === "union" ? `(${innerText})?` : `${innerText}?`;
@@ -8568,11 +8822,12 @@ function typeNamedRefs(expr) {
8568
8822
  }
8569
8823
  function typeIsMany(expr) {
8570
8824
  if (expr.form === "list" || expr.form === "set" || expr.form === "map") return true;
8571
- if (expr.form === "optional" || expr.form === "async") return typeIsMany(expr.args[0]);
8825
+ if (expr.form === "optional" || expr.form === "async" || expr.form === "result") return typeIsMany(expr.args[0]);
8572
8826
  return false;
8573
8827
  }
8574
8828
  function contextFor(position, omittable2, looseNumber = false) {
8575
- return { position, top: true, voidAllowed: RETURNS_POSITIONS.has(position), omittable: omittable2, looseNumber };
8829
+ const returns = RETURNS_POSITIONS.has(position);
8830
+ return { position, top: true, voidAllowed: returns, resultAllowed: returns, omittable: omittable2, looseNumber };
8576
8831
  }
8577
8832
  function parseTypePosition(text3, position, omittable2 = false) {
8578
8833
  let raw;
@@ -8763,6 +9018,47 @@ function typeProblemIntProposal(problem) {
8763
9018
  if (!parse.expression || parse.problem) return null;
8764
9019
  return { specId: problem.specId, kind: problem.kind, path: problem.path, written: problem.written, stored: parse.canonical };
8765
9020
  }
9021
+ function stringLiteralValue(text3) {
9022
+ const quote = text3[0];
9023
+ if (quote !== "'" && quote !== '"' && quote !== "`" || text3[text3.length - 1] !== quote) return null;
9024
+ return text3.slice(1, -1).replace(/\\(.)/g, "$1");
9025
+ }
9026
+ function kebabOf(name) {
9027
+ return name.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/[\s_.]+/g, "-").toLowerCase();
9028
+ }
9029
+ function typeProblemEnumProposal(problem) {
9030
+ if (problem.code !== "TYPE_FORM_UNSUPPORTED") return null;
9031
+ if (problem.specId === void 0 || problem.kind === void 0 || problem.path === void 0) return null;
9032
+ let raw;
9033
+ try {
9034
+ raw = readRaw(problem.written);
9035
+ } catch (e) {
9036
+ if (e instanceof TypeSyntaxError) return null;
9037
+ throw e;
9038
+ }
9039
+ const members = raw.k === "union" ? raw.members : [raw];
9040
+ const values = [];
9041
+ let optional = false;
9042
+ for (const member of members) {
9043
+ if (member.k === "name" && member.args === void 0 && NONES.has(member.name)) {
9044
+ optional = true;
9045
+ continue;
9046
+ }
9047
+ const value = member.k === "literal" ? stringLiteralValue(member.text) : null;
9048
+ if (value === null) return null;
9049
+ if (!values.includes(value)) values.push(value);
9050
+ }
9051
+ if (values.length === 0) return null;
9052
+ return {
9053
+ specId: problem.specId,
9054
+ kind: problem.kind,
9055
+ path: problem.path,
9056
+ written: problem.written,
9057
+ enumId: kebabOf(positionNameOf(problem.path)),
9058
+ values,
9059
+ optional
9060
+ };
9061
+ }
8766
9062
  function emptyTypeSpellingFacts() {
8767
9063
  return { respellings: [], problems: [] };
8768
9064
  }
@@ -8913,6 +9209,7 @@ function lastSegment(name) {
8913
9209
  return name.split(/::|\./).filter(Boolean).pop() ?? name;
8914
9210
  }
8915
9211
  function sameType(code, spec, codeNames) {
9212
+ if (spec.form === "result" && code.form !== "result") return sameType(code, spec.args[0], codeNames);
8916
9213
  if (code.form === "primitive" && code.name === "number") return spec.form === "primitive" && (spec.name === "int" || spec.name === "float");
8917
9214
  if (code.form === "named" && (spec.form === "named" || spec.form === "applied")) {
8918
9215
  if (spec.form === "applied") return false;
@@ -8949,6 +9246,8 @@ function writeTypeScript(expr) {
8949
9246
  return expr.args.map(writeTypeScript).join(" | ");
8950
9247
  case "async":
8951
9248
  return `Promise<${writeTypeScript(expr.args[0])}>`;
9249
+ case "result":
9250
+ return writeTypeScript(expr.args[0]);
8952
9251
  case "applied":
8953
9252
  return `${expr.name}<${expr.args.map(writeTypeScript).join(", ")}>`;
8954
9253
  }
@@ -8991,6 +9290,7 @@ var typescriptDialect = {
8991
9290
  "T? \u2192 T | null",
8992
9291
  "A | B \u2192 A | B",
8993
9292
  "async T \u2192 Promise<T>",
9293
+ "result<T, E> \u2192 T, throwing E on failure (result<void, E> \u2192 void)",
8994
9294
  "an enum E \u2192 type E = 'a' | 'b' (a string-literal union alias)",
8995
9295
  "a named scalar E holding P \u2192 type E = P (an alias of the primitive)"
8996
9296
  ];
@@ -9265,7 +9565,7 @@ function jumpFieldsOf(s) {
9265
9565
  }
9266
9566
 
9267
9567
  // src/models/code-model.ts
9268
- var path9 = __toESM(require("path"));
9568
+ var path10 = __toESM(require("path"));
9269
9569
  function pathKey(sourcePath) {
9270
9570
  return sourcePath.replace(/\\/g, "/").replace(/^\.\//, "");
9271
9571
  }
@@ -9339,14 +9639,20 @@ var EXTENSION_LANGUAGE = {
9339
9639
  ".swift": "swift"
9340
9640
  };
9341
9641
  function languageOfSourcePath(sourcePath) {
9342
- return EXTENSION_LANGUAGE[path9.extname(sourcePath).toLowerCase()];
9642
+ return EXTENSION_LANGUAGE[path10.extname(sourcePath).toLowerCase()];
9343
9643
  }
9344
9644
  function dialectOf(facts) {
9345
9645
  return facts.language ? typeDialectFor(facts.language) : null;
9346
9646
  }
9347
- function resolveImport(fromFile, specifier, knownPaths) {
9348
- if (!specifier.startsWith(".")) return void 0;
9349
- const joined = pathKey(path9.posix.normalize(path9.posix.join(path9.posix.dirname(fromFile), specifier)));
9647
+ function returnTypeOf(facts, fn) {
9648
+ return ownEntry(facts.returnTypes, fn);
9649
+ }
9650
+ function resolveImport(fromFile, specifier, knownPaths, packages) {
9651
+ if (!specifier.startsWith(".")) {
9652
+ const mapped = ownEntry(packages, specifier);
9653
+ return mapped !== void 0 && knownPaths.has(mapped) ? mapped : void 0;
9654
+ }
9655
+ const joined = pathKey(path10.posix.normalize(path10.posix.join(path10.posix.dirname(fromFile), specifier)));
9350
9656
  const candidates = [
9351
9657
  joined,
9352
9658
  joined.replace(/\.js$/, ".ts"),
@@ -9493,7 +9799,7 @@ function exportTargetKey(e) {
9493
9799
  }
9494
9800
 
9495
9801
  // src/models/project-family.ts
9496
- var path10 = __toESM(require("path"));
9802
+ var path11 = __toESM(require("path"));
9497
9803
  function walkMemberPath(byKey3, start, segments) {
9498
9804
  let node = start;
9499
9805
  for (const segment of segments.slice(0, -1)) {
@@ -9555,15 +9861,15 @@ function keyIn(project, localId) {
9555
9861
  return project ? `${project}::${localId}` : localId;
9556
9862
  }
9557
9863
  function approvalKeyIn(file, root, parts) {
9558
- const resolved = path10.resolve(file);
9864
+ const resolved = path11.resolve(file);
9559
9865
  for (const part of parts) {
9560
9866
  if (part.directory === void 0) continue;
9561
- const rel2 = path10.relative(part.directory, resolved);
9562
- if (rel2 !== "" && !rel2.startsWith("..") && !path10.isAbsolute(rel2)) {
9563
- return `members/${part.alias}/${rel2.split(path10.sep).join("/")}`;
9867
+ const rel2 = path11.relative(part.directory, resolved);
9868
+ if (rel2 !== "" && !rel2.startsWith("..") && !path11.isAbsolute(rel2)) {
9869
+ return `members/${part.alias}/${rel2.split(path11.sep).join("/")}`;
9564
9870
  }
9565
9871
  }
9566
- return path10.relative(root, resolved).split(path10.sep).join("/");
9872
+ return path11.relative(root, resolved).split(path11.sep).join("/");
9567
9873
  }
9568
9874
  function familyNode(family, namespace) {
9569
9875
  return family.nodes.find((n) => n.namespace === namespace) ?? null;
@@ -9696,7 +10002,7 @@ var DesignDependencyRoleSchema = import_zod9.z.enum(DESIGN_DEPENDENCY_ROLES);
9696
10002
  var ExtSchema = import_zod9.z.record(import_zod9.z.unknown());
9697
10003
  var TypeExpressionSchema = import_zod9.z.lazy(
9698
10004
  () => import_zod9.z.object({
9699
- form: import_zod9.z.enum(["primitive", "named", "list", "set", "map", "optional", "union", "async", "applied"]),
10005
+ form: import_zod9.z.enum(["primitive", "named", "list", "set", "map", "optional", "union", "async", "applied", "result"]),
9700
10006
  name: import_zod9.z.string().optional(),
9701
10007
  args: import_zod9.z.array(TypeExpressionSchema)
9702
10008
  }).passthrough()
@@ -9933,7 +10239,7 @@ function resolveNarrativeLabels(methodName, steps) {
9933
10239
 
9934
10240
  // src/core/canvas.ts
9935
10241
  var fs7 = __toESM(require("fs"));
9936
- var path11 = __toESM(require("path"));
10242
+ var path12 = __toESM(require("path"));
9937
10243
 
9938
10244
  // src/core/canvas-layout.ts
9939
10245
  function computeLayout(model, collapsed) {
@@ -10704,9 +11010,9 @@ function embedJson(value) {
10704
11010
  }
10705
11011
  function loadCytoscapeLib() {
10706
11012
  const candidates = [
10707
- path11.resolve(__dirname, "..", "templates", "canvas", "cytoscape.min.js"),
11013
+ path12.resolve(__dirname, "..", "templates", "canvas", "cytoscape.min.js"),
10708
11014
  // src/core & dist/cli
10709
- path11.resolve(__dirname, "templates", "canvas", "cytoscape.min.js")
11015
+ path12.resolve(__dirname, "templates", "canvas", "cytoscape.min.js")
10710
11016
  // dist (library entry)
10711
11017
  ];
10712
11018
  for (const p of candidates) {
@@ -15089,17 +15395,17 @@ function safeFilenamePart(value) {
15089
15395
 
15090
15396
  // src/core/externals.ts
15091
15397
  var fs8 = __toESM(require("fs"));
15092
- var path12 = __toESM(require("path"));
15398
+ var path13 = __toESM(require("path"));
15093
15399
  function lockPath2(root) {
15094
- return path12.join(root, ".wai", "externals.lock.yaml");
15400
+ return path13.join(root, ".wai", "externals.lock.yaml");
15095
15401
  }
15096
15402
  function snapshotPath(root, alias) {
15097
- return path12.join(root, ".wai", "externals", `${alias}.yaml`);
15403
+ return path13.join(root, ".wai", "externals", `${alias}.yaml`);
15098
15404
  }
15099
15405
  function writeAtomically(file, text3) {
15100
- fs8.mkdirSync(path12.dirname(file), { recursive: true });
15406
+ fs8.mkdirSync(path13.dirname(file), { recursive: true });
15101
15407
  const tmp = `${file}.${process.pid}.tmp`;
15102
- fs8.writeFileSync(tmp, text3);
15408
+ fs8.writeFileSync(tmp, withLineEndings(file, text3));
15103
15409
  fs8.renameSync(tmp, file);
15104
15410
  }
15105
15411
  function sortedLock(lock) {
@@ -15142,7 +15448,7 @@ var externalsFileAdapter = {
15142
15448
  const file = snapshotPath(getProjectRoot(), alias);
15143
15449
  if (!fs8.existsSync(file)) return false;
15144
15450
  fs8.unlinkSync(file);
15145
- const dir = path12.dirname(file);
15451
+ const dir = path13.dirname(file);
15146
15452
  if (fs8.existsSync(dir) && fs8.readdirSync(dir).length === 0) fs8.rmdirSync(dir);
15147
15453
  return true;
15148
15454
  },
@@ -15275,6 +15581,8 @@ function schemaOfExpression(expr, closureIds) {
15275
15581
  return { oneOf: expr.args.map((member) => schemaOfExpression(member, closureIds)) };
15276
15582
  case "async":
15277
15583
  return schemaOfExpression(expr.args[0], closureIds);
15584
+ case "result":
15585
+ return schemaOfExpression(expr.args[0], closureIds);
15278
15586
  }
15279
15587
  }
15280
15588
  function schemaFor(typeRef2, closureIds) {
@@ -15284,7 +15592,8 @@ function schemaFor(typeRef2, closureIds) {
15284
15592
  }
15285
15593
  function returnsNothing(returns) {
15286
15594
  const expr = expressionOf(returns);
15287
- const inner2 = expr?.form === "async" ? expr.args[0] : expr;
15595
+ const awaited = expr?.form === "async" ? expr.args[0] : expr;
15596
+ const inner2 = awaited?.form === "result" ? awaited.args[0] : awaited;
15288
15597
  return inner2?.form === "primitive" && inner2.name === "void";
15289
15598
  }
15290
15599
  function enumDescription(values) {
@@ -15736,61 +16045,6 @@ function fromOpenApi(document, projectName) {
15736
16045
  });
15737
16046
  }
15738
16047
 
15739
- // src/config/defaults.ts
15740
- var os2 = __toESM(require("os"));
15741
- var path13 = __toESM(require("path"));
15742
- var DEFAULT_TARGET_DIRS = {
15743
- claude: ".claude/agents",
15744
- gemini: ".gemini/agents",
15745
- agy: ".gemini/agents",
15746
- cursor: ".cursor/rules",
15747
- copilot: ".github/prompts",
15748
- codex: ".codex/agents"
15749
- };
15750
- function defaultTargetConfig(type) {
15751
- return {
15752
- type,
15753
- outputDir: DEFAULT_TARGET_DIRS[type],
15754
- enabled: true
15755
- };
15756
- }
15757
- var WAIRON_VERSION = "5.1.1-dev.100";
15758
- var GITHUB_REPO = "SYW-Apps/Waffle-AIron";
15759
- var ARCHITECT_AGENT_ID = "agent-architect";
15760
- var ARCHITECT_TEMPLATE_ID = "architect";
15761
- function globalTemplatesDir(projectOverride) {
15762
- if (process.env.WAIRON_TEMPLATES_DIR) {
15763
- return process.env.WAIRON_TEMPLATES_DIR;
15764
- }
15765
- if (projectOverride) {
15766
- return projectOverride;
15767
- }
15768
- return path13.join(os2.homedir(), ".wairon", "templates");
15769
- }
15770
- var BACKEND_COMMANDS = {
15771
- claude: "claude",
15772
- gemini: "gemini"
15773
- };
15774
- function backendCommand(backend) {
15775
- return BACKEND_COMMANDS[backend] ?? backend;
15776
- }
15777
- var SUPPORTED_ALIASES = ["wai"];
15778
- var SCAN_EXCLUDE_DIRS = /* @__PURE__ */ new Set([
15779
- "node_modules",
15780
- "dist",
15781
- "build",
15782
- "out",
15783
- ".git",
15784
- ".cache",
15785
- "coverage",
15786
- "__pycache__",
15787
- ".venv",
15788
- "venv",
15789
- "target",
15790
- // Rust
15791
- "vendor"
15792
- ]);
15793
-
15794
16048
  // src/core/design-export.ts
15795
16049
  var NoSystemSpecError = class extends Error {
15796
16050
  constructor() {
@@ -17285,7 +17539,7 @@ function approvalFinding(entry, config) {
17285
17539
  return finding(
17286
17540
  config,
17287
17541
  "MEMBER_DRIFTED",
17288
- `The member ${named(entry.key)} was approved under an earlier gate identity \u2014 the gate identity gained inputs in stage 5 (members' composition subjects, \`composition\`; code conformance moved beside the claim), which says nothing about its design. Re-lock it once at its own root (\`wairon lock\`).`,
17542
+ `The member ${named(entry.key)} was approved under an earlier gate identity \u2014 the gate identity gained inputs in this release (members' composition subjects, \`composition\`; code conformance moved beside the claim), which says nothing about its design. Re-lock it once at its own root (\`wairon lock\`).`,
17289
17543
  entry.key
17290
17544
  );
17291
17545
  }
@@ -17666,12 +17920,12 @@ function packFiles(dir, prefix = "") {
17666
17920
  function computePackDigest(packDir) {
17667
17921
  const hash = crypto3.createHash("sha256");
17668
17922
  const abs = path16.resolve(packDir);
17669
- let isFile = false;
17923
+ let isFile2 = false;
17670
17924
  try {
17671
- isFile = fs11.statSync(abs).isFile();
17925
+ isFile2 = fs11.statSync(abs).isFile();
17672
17926
  } catch {
17673
17927
  }
17674
- const entries = isFile ? [[path16.basename(abs), abs]] : packFiles(abs).map((rel2) => [rel2, path16.join(abs, rel2)]);
17928
+ const entries = isFile2 ? [[path16.basename(abs), abs]] : packFiles(abs).map((rel2) => [rel2, path16.join(abs, rel2)]);
17675
17929
  for (const [rel2, full] of entries) {
17676
17930
  hash.update(rel2);
17677
17931
  hash.update("\0");
@@ -18291,6 +18545,16 @@ function verifyIntegrity(selection, packDir, where) {
18291
18545
  message: `Pack "${wanted}" resolved to content that does not match the pinned integrity: expected ${selection.integrity}, found ${actual} (${where}). The pack was modified after it was pinned \u2014 reinstall or re-bundle the pinned version, or update the pin with \`wairon pack use ${selection.name} --pin\` if the change is intended.`
18292
18546
  };
18293
18547
  }
18548
+ function bundlesSingleFile(bundleDir, storePath) {
18549
+ try {
18550
+ if (!fs12.statSync(storePath).isFile()) return false;
18551
+ const entry = packDirEntry(bundleDir);
18552
+ if (!entry || fs12.readdirSync(bundleDir).length !== 1) return false;
18553
+ return fs12.readFileSync(entry).equals(fs12.readFileSync(storePath));
18554
+ } catch {
18555
+ return false;
18556
+ }
18557
+ }
18294
18558
  function detectBundleStoreDrift(selection, bundleDir) {
18295
18559
  const bundledVersion = path17.basename(bundleDir);
18296
18560
  const installed = resolveInstalledPack(selection.name, bundledVersion);
@@ -18298,6 +18562,7 @@ function detectBundleStoreDrift(selection, bundleDir) {
18298
18562
  const bundleDigest = computePackDigest(bundleDir);
18299
18563
  const storeDigest = computePackDigest(installed.path);
18300
18564
  if (bundleDigest === storeDigest) return void 0;
18565
+ if (bundlesSingleFile(bundleDir, installed.path)) return void 0;
18301
18566
  return {
18302
18567
  code: "PACK_STORE_DRIFT",
18303
18568
  severity: "warning",
@@ -18355,6 +18620,7 @@ function appendUnique(map, key, value, same) {
18355
18620
  list.push(value);
18356
18621
  }
18357
18622
  function buildCodeIndex(model) {
18623
+ const packages = model.packages ?? {};
18358
18624
  const facts = /* @__PURE__ */ new Map();
18359
18625
  const paths = /* @__PURE__ */ new Set();
18360
18626
  const exactPaths = /* @__PURE__ */ new Set();
@@ -18387,7 +18653,7 @@ function buildCodeIndex(model) {
18387
18653
  const f = facts.get(from);
18388
18654
  if (f?.status !== "analyzed") continue;
18389
18655
  for (const specifier of f.reexports) {
18390
- const to = resolveImport(from, specifier, paths);
18656
+ const to = resolveImport(from, specifier, paths, packages);
18391
18657
  if (to && !out.has(to)) {
18392
18658
  out.add(to);
18393
18659
  queue.push(to);
@@ -18399,7 +18665,7 @@ function buildCodeIndex(model) {
18399
18665
  };
18400
18666
  const NO_ORIGIN = /* @__PURE__ */ new Set();
18401
18667
  const landingFrom = (scope, specifier) => {
18402
- const to = resolveImport(scope, specifier, paths);
18668
+ const to = resolveImport(scope, specifier, paths, packages);
18403
18669
  return to ? republishedFrom(to) : NO_ORIGIN;
18404
18670
  };
18405
18671
  const scopeOf2 = (site, from) => {
@@ -18429,7 +18695,7 @@ function buildCodeIndex(model) {
18429
18695
  };
18430
18696
  const possibleOriginsOf = (site, from) => {
18431
18697
  const proven = originOf2(site, from);
18432
- if (!site.field && !site.constructed && !site.via) return proven;
18698
+ if (!site.field && !site.constructed && !site.via && !site.returnedBy && !site.enclosingClass) return proven;
18433
18699
  const resolved = scopeOf2(site, from);
18434
18700
  if (!resolved) return proven;
18435
18701
  const { path: scope, facts: f } = resolved;
@@ -18442,24 +18708,59 @@ function buildCodeIndex(model) {
18442
18708
  if (site.field) for (const typeName of fieldTypesOf(f, site.field)) widenThroughType(typeName);
18443
18709
  if (site.constructed) widenThrough(site.constructed, importBindingOf(f, site.constructed)?.from);
18444
18710
  if (site.via) for (const typeName of localTypesOf(f, site.via)) widenThroughType(typeName);
18711
+ if (site.enclosingClass !== void 0 && holdsMemberBody(f, site.name, site.enclosingClass)) {
18712
+ for (const candidate of republishedFrom(scope)) out.add(candidate);
18713
+ }
18714
+ if (site.returnedBy !== void 0) {
18715
+ for (const fnFile of originOf2({ name: site.returnedBy, member: false }, scope)) {
18716
+ const at = facts.get(fnFile);
18717
+ if (at?.status !== "analyzed" || at.analysisGrade !== "exact") continue;
18718
+ const typeName = returnTypeOf(at, site.returnedBy);
18719
+ if (typeName === void 0) continue;
18720
+ const specifier = typeBindingOf(at, typeName);
18721
+ const landings = specifier !== void 0 ? landingFrom(fnFile, specifier) : declarationsAt(fnFile).has(typeName) ? republishedFrom(fnFile) : NO_ORIGIN;
18722
+ for (const candidate of landings) {
18723
+ const there = facts.get(candidate);
18724
+ if (there && holdsMemberBody(there, site.name, typeName)) out.add(candidate);
18725
+ }
18726
+ }
18727
+ }
18445
18728
  return out;
18446
18729
  };
18730
+ 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);
18447
18731
  const declarationsAt = (path49) => namesAt(declarations, pathKey(path49), (f) => /* @__PURE__ */ new Set([...f.declaredNames, ...f.exportedNames]));
18448
- const forwardsOf = (start, name) => {
18732
+ const forwardsOf = (start, name, container) => {
18449
18733
  const out = [];
18734
+ const listed = /* @__PURE__ */ new Set();
18450
18735
  const seen = /* @__PURE__ */ new Set();
18451
- const queue = [{ file: pathKey(start), name }];
18736
+ const queue = [{ file: pathKey(start), name, container }];
18452
18737
  while (queue.length) {
18453
18738
  const at = queue.shift();
18454
- const key = `${at.file}#${at.name}`;
18739
+ const key = `${at.scope ? "s" : "p"}|${at.container ?? ""}|${at.file}#${at.name}`;
18455
18740
  if (seen.has(key)) continue;
18456
18741
  seen.add(key);
18457
- out.push(at);
18458
- for (const binding of facts.get(at.file)?.reexportBindings ?? []) {
18742
+ if (!listed.has(`${at.file}#${at.name}`)) {
18743
+ listed.add(`${at.file}#${at.name}`);
18744
+ out.push({ file: at.file, name: at.name });
18745
+ }
18746
+ const f = facts.get(at.file);
18747
+ if (at.scope) {
18748
+ const binding = f ? importBindingOf(f, at.name) : void 0;
18749
+ if (!binding || binding.namespace) continue;
18750
+ const to = resolveImport(at.file, binding.from, paths, packages);
18751
+ if (to) queue.push({ file: to, name: binding.imported ?? at.name });
18752
+ continue;
18753
+ }
18754
+ for (const alias of f?.exportAliases ?? []) {
18755
+ if (alias.exported !== at.name || alias.container !== at.container) continue;
18756
+ queue.push({ file: at.file, name: alias.local, scope: true });
18757
+ }
18758
+ if (at.container !== void 0) continue;
18759
+ for (const binding of f?.reexportBindings ?? []) {
18459
18760
  const star = binding.exported === "*";
18460
18761
  if (!star && binding.exported !== at.name) continue;
18461
18762
  if (star && at.name === "default") continue;
18462
- const to = resolveImport(at.file, binding.from, paths);
18763
+ const to = resolveImport(at.file, binding.from, paths, packages);
18463
18764
  if (!to) continue;
18464
18765
  if (star && !facts.get(to)?.exportedNames.includes(at.name)) continue;
18465
18766
  queue.push({ file: to, name: star ? at.name : binding.local });
@@ -18470,6 +18771,7 @@ function buildCodeIndex(model) {
18470
18771
  return {
18471
18772
  paths,
18472
18773
  exactPaths,
18774
+ packages,
18473
18775
  factsAt: (path49) => facts.get(pathKey(path49)),
18474
18776
  declarationsAt,
18475
18777
  anchorsAt: (path49) => namesAt(anchors, pathKey(path49), (f) => /* @__PURE__ */ new Set([...f.declaredNames, ...f.exportedNames, ...f.anchoredNames])),
@@ -18523,7 +18825,7 @@ function buildImportGraph(index, universe) {
18523
18825
  const traces = /* @__PURE__ */ new Map();
18524
18826
  const resolveAll = (from, specifiers, into) => {
18525
18827
  for (const specifier of specifiers) {
18526
- const to = resolveImport(from, specifier, universe);
18828
+ const to = resolveImport(from, specifier, universe, index.packages);
18527
18829
  if (to && to !== from) into.add(to);
18528
18830
  }
18529
18831
  };
@@ -18819,7 +19121,7 @@ var localIdsRule = {
18819
19121
  var projectIdentityRule = {
18820
19122
  name: "project-identity",
18821
19123
  judges: "design",
18822
- description: "The bound project is keyed on its id, so the id must be declared, well-formed, stable and its own: a project that declares none answers to its display name slugified until it writes one (PROJECT_ID_DEFAULTED), a project whose name yields no id or whose declared id breaks the grammar has nothing reliable to key on (PROJECT_ID_AMBIGUOUS), an id the rename migration moved from the one the lock approved is owed a re-lock and nothing more (PROJECT_ID_RENAMED: the approved id is one of the configuration's previousIds), and any other id that differs from the one the lock approved has moved under everything that keyed on it (PROJECT_ID_CHANGED). It judges the bound project's own identity only. The family half \u2014 a member with a defaulted id (named with its alias as the id to declare), a member with no id, and two members resolving to one id \u2014 is a family check since stage 4 (family_validator.checkMembers), which reads the family root's graph.",
19124
+ description: "The bound project is keyed on its id, so the id must be declared, well-formed, stable and its own: a project that declares none answers to its display name slugified until it writes one (PROJECT_ID_DEFAULTED), a project whose name yields no id or whose declared id breaks the grammar has nothing reliable to key on (PROJECT_ID_AMBIGUOUS), an id the rename migration moved from the one the lock approved is owed a re-lock and nothing more (PROJECT_ID_RENAMED: the approved id is one of the configuration's previousIds), and any other id that differs from the one the lock approved has moved under everything that keyed on it (PROJECT_ID_CHANGED). It judges the bound project's own identity only. The family half \u2014 a member with a defaulted id (named with its alias as the id to declare), a member with no id, and two members resolving to one id \u2014 is a family check (family_validator.checkMembers), which reads the family root's graph.",
18823
19125
  codes: [
18824
19126
  { code: "PROJECT_ID_AMBIGUOUS", defaultSeverity: "warning", summary: "Project has no usable id: its name yields no slug, or its declared id breaks the grammar" },
18825
19127
  { code: "PROJECT_ID_CHANGED", defaultSeverity: "error", summary: "Project id differs from the id the lock approved" },
@@ -18837,7 +19139,7 @@ function judgeOwnIdentity(ctx, identity2) {
18837
19139
  ctx.addIssue(
18838
19140
  "notice",
18839
19141
  "PROJECT_ID_DEFAULTED",
18840
- `Project "${identity2.name}" declares no id in .wai/project.yaml, so it answers to "${identity2.id}", derived from its display name \u2014 renaming the project would move it. No save writes it for you; declare it with \`id: ${identity2.id}\` (stage 3 requires an explicit id).`
19142
+ `Project "${identity2.name}" declares no id in .wai/project.yaml, so it answers to "${identity2.id}", derived from its display name \u2014 renaming the project would move it. No save writes it for you; declare it with \`id: ${identity2.id}\` (a project is keyed on an explicit id).`
18841
19143
  );
18842
19144
  }
18843
19145
  const ambiguous = problem("ambiguous");
@@ -19226,10 +19528,10 @@ function problemMessage(problem, specId) {
19226
19528
  var typeExpressionsRule = {
19227
19529
  name: "type-expressions",
19228
19530
  judges: "design",
19229
- description: "Reports what the scan's type canonicalisation recorded (ctx.typeSpellingFacts), one finding per position, located by spec and path: a stored text that does not parse (TYPE_EXPRESSION_INVALID); one that breaks a position rule \u2014 void or async out of place, a map key that is not string, int or an enum, a named scalar's holds that is not one primitive other than void and any, `T??` (TYPE_POSITION_INVALID); one using a form the grammar leaves out \u2014 an inline object shape, an inline function type, a string-literal union, a union mixing in a primitive or a collection, an intersection, a utility type or a tuple (TYPE_FORM_UNSUPPORTED); one naming `number` or a legacy builtin with no neutral meaning (TYPE_NOT_NEUTRAL); and one that is an alias of its canonical spelling (TYPE_SPELLING_STALE, which any save or doctor --fix repairs). Every message names the replacement: `number` asks \"int or float?\" (and says when the doctor repair proposes int), a function type names a signature type, a literal union an enum, an inline object or a mixed union a named value-object. The writer refuses all but the stale spelling at write time; these findings are how a tree that held them before the grammar existed is told, while it still loads and its consumers read the position as opaque any \u2014 which is why only what no real tree holds (a text that does not parse, a broken position rule) is an error. No finding when the run carries no facts (a candidate run).",
19531
+ description: "Reports what the scan's type canonicalisation recorded (ctx.typeSpellingFacts), one finding per position, located by spec and path: a stored text that does not parse (TYPE_EXPRESSION_INVALID); one that breaks a position rule \u2014 void, async or result out of place, a map key that is not string, int or an enum, a named scalar's holds that is not one primitive other than void and any, `T??` (TYPE_POSITION_INVALID); one using a form the grammar leaves out \u2014 an inline object shape, an inline function type, a string-literal union, a union mixing in a primitive or a collection, an intersection, a utility type or a tuple (TYPE_FORM_UNSUPPORTED); one naming `number` or a legacy builtin with no neutral meaning (TYPE_NOT_NEUTRAL); and one that is an alias of its canonical spelling (TYPE_SPELLING_STALE, which any save or doctor --fix repairs). Every message names the replacement: `number` asks \"int or float?\" (and says when the doctor repair proposes int), a function type names a signature type, a literal union an enum (which the doctor repair proposes), an inline object or a mixed union a named value-object. The writer refuses all but the stale spelling at write time; on load every one of them is a warning \u2014 these findings are how a tree that held them before the grammar existed is told, while it still loads, stays lockable with its debt in view, and its consumers read the position as opaque any, which never invents a shape. No finding when the run carries no facts (a candidate run).",
19230
19532
  codes: [
19231
- { code: "TYPE_EXPRESSION_INVALID", defaultSeverity: "error", summary: "A structured type position does not parse under the type grammar" },
19232
- { code: "TYPE_POSITION_INVALID", defaultSeverity: "error", summary: "A type position breaks a position rule: void or async out of place, a non-scalar map key, or T??" },
19533
+ { code: "TYPE_EXPRESSION_INVALID", defaultSeverity: "warning", summary: "A structured type position does not parse under the type grammar" },
19534
+ { code: "TYPE_POSITION_INVALID", defaultSeverity: "warning", summary: "A type position breaks a position rule: void, async or result out of place, a non-scalar map key, or T??" },
19233
19535
  { code: "TYPE_FORM_UNSUPPORTED", defaultSeverity: "warning", summary: "A type position uses a form the grammar leaves out \u2014 inline object, inline function type, string-literal union, union mixing in a primitive or collection, intersection, utility type or tuple \u2014 and the message names its named replacement" },
19234
19536
  { code: "TYPE_NOT_NEUTRAL", defaultSeverity: "warning", summary: "A type position names number (int or float?) or a legacy builtin with no neutral meaning, and the message names the replacement" },
19235
19537
  { code: "TYPE_SPELLING_STALE", defaultSeverity: "warning", summary: "A stored type position is an alias of its canonical spelling; any save or doctor --fix rewrites it" }
@@ -19239,9 +19541,8 @@ var typeExpressionsRule = {
19239
19541
  if (!facts) return;
19240
19542
  for (const problem of facts.problems) {
19241
19543
  if (problem.specId === void 0 || !ctx.isSpecInScope(problem.specId)) continue;
19242
- const severity = problem.code === "TYPE_EXPRESSION_INVALID" || problem.code === "TYPE_POSITION_INVALID" ? "error" : "warning";
19243
19544
  ctx.addIssue(
19244
- severity,
19545
+ "warning",
19245
19546
  problem.code,
19246
19547
  problemMessage(problem, problem.specId),
19247
19548
  problem.specId,
@@ -19575,7 +19876,7 @@ var publicSurfaceBoundContractRule = {
19575
19876
  var exportTablesRule = {
19576
19877
  name: "export-tables",
19577
19878
  judges: "design",
19578
- description: "Every level's export table must resolve the way a module resolver resolves `export \u2026 from`: one public name binds one target (EXPORT_ID_DUPLICATE when two wildcards \u2014 or two explicit entries \u2014 bind a name to different targets, or two public names share one nameKey, since a bare import could not tell them apart; the name is left out), no chain leads back to itself (EXPORT_CYCLE \u2014 an error for a named chain that never reaches a component or type, a warning for a wildcard cycle, which resolves to its union), every entry names a source and an item that exist, that the source exports and, for an own type, that the level owns \u2014 a subsystem-owned type at L1, a project-level type (no subsystem) for an L0 `{ typeDef }` without `from` \u2014 under a public name of [a-z0-9-_]+ (EXPORT_INVALID), every re-exported component can serve a boundary caller \u2014 a Portal, a gateway being a Portal variant, or an Observer for events (EXPORT_UNCONSUMABLE, at L0 and for L1 re-exports; an error since stage 4), and an L0 entry that re-exports a member's or an external's export never declares a wider audience than that export has (EXPORT_WIDENS_AUDIENCE): a re-export can narrow reach, never widen it. The rule judges the facts the export resolver recorded in the scan \u2014 for the bound root's table and every contained member's \u2014 and resolves nothing itself.",
19879
+ description: "Every level's export table must resolve the way a module resolver resolves `export \u2026 from`: one public name binds one target (EXPORT_ID_DUPLICATE when two wildcards \u2014 or two explicit entries \u2014 bind a name to different targets, or two public names share one nameKey, since a bare import could not tell them apart; the name is left out), no chain leads back to itself (EXPORT_CYCLE \u2014 an error for a named chain that never reaches a component or type, a warning for a wildcard cycle, which resolves to its union), every entry names a source and an item that exist, that the source exports and, for an own type, that the level owns \u2014 a subsystem-owned type at L1, a project-level type (no subsystem) for an L0 `{ typeDef }` without `from` \u2014 under a public name of [a-z0-9-_]+ (EXPORT_INVALID), every re-exported component can serve a boundary caller \u2014 a Portal, a gateway being a Portal variant, or an Observer for events (EXPORT_UNCONSUMABLE, at L0 and for L1 re-exports), and an L0 entry that re-exports a member's or an external's export never declares a wider audience than that export has (EXPORT_WIDENS_AUDIENCE): a re-export can narrow reach, never widen it. The rule judges the facts the export resolver recorded in the scan \u2014 for the bound root's table and every contained member's \u2014 and resolves nothing itself.",
19579
19880
  codes: [
19580
19881
  { code: "EXPORT_ID_DUPLICATE", defaultSeverity: "error", summary: "One public name bound to different targets by two wildcard re-exports or two explicit entries" },
19581
19882
  { code: "EXPORT_CYCLE", defaultSeverity: "error", summary: "A re-export chain leads back to itself (a warning for a wildcard cycle, which resolves to its union)" },
@@ -19737,7 +20038,7 @@ function whyNoRewrite(ref) {
19737
20038
  var referenceFormsRule = {
19738
20039
  name: "reference-forms",
19739
20040
  judges: "design",
19740
- description: "A cross-project reference is `alias::name` (or a bare name a `use` imports): the alias is one of the referring project's members or externals, the name a public name of that project's L0 table. The older forms still bind for one release and are reported (DEPRECATED_REFERENCE_FORM), each naming the spec, where in it, the form as written and the text to write instead (the scan's rewrite: a bare local id, or `alias::publicName`): a leading `::`, which escapes to the root of whatever checkout loads it; `super::`, which climbs the containing projects \u2014 a place in one family, not a name; a member path (`desktop::shell` written inside desktop, `waffler_core::transpiler::x` written inside transpiler), a first segment the referring project does not declare, read from the bound root; and a self-prefix \u2014 the referring project's own id as the first segment, with `::` or `.` (`registry.advisory-channel` inside registry), which stage 3's migration did not rewrite and stage 4's positional step does. Wairon's own writer emits none of them. A form with no rewrite (its target is out of reach) is reported with the reason. It reads the project graph's authored references, which the scan records before binding erases the form.",
20041
+ description: "A cross-project reference is `alias::name` (or a bare name a `use` imports): the alias is one of the referring project's members or externals, the name a public name of that project's L0 table. The older forms still bind for one release and are reported (DEPRECATED_REFERENCE_FORM), each naming the spec, where in it, the form as written and the text to write instead (the scan's rewrite: a bare local id, or `alias::publicName`): a leading `::`, which escapes to the root of whatever checkout loads it; `super::`, which climbs the containing projects \u2014 a place in one family, not a name; a member path (`desktop::shell` written inside desktop, `app_core::engine::x` written inside engine), a first segment the referring project does not declare, read from the bound root; and a self-prefix \u2014 the referring project's own id as the first segment, with `::` or `.` (`registry.advisory-channel` inside registry), which `wairon doctor --fix` rewrites to the bare local id. Wairon's own writer emits none of them. A form with no rewrite (its target is out of reach) is reported with the reason. It reads the project graph's authored references, which the scan records before binding erases the form.",
19741
20042
  codes: [
19742
20043
  { code: "DEPRECATED_REFERENCE_FORM", defaultSeverity: "notice", summary: "A reference written with a leading ::, super:: or a member path where a local id or alias::name belongs" }
19743
20044
  ],
@@ -19768,9 +20069,9 @@ function carriedFields(mount) {
19768
20069
  var memberDeclarationsRule = {
19769
20070
  name: "member-declarations",
19770
20071
  judges: "design",
19771
- description: "A project declares its members in project.yaml `members`, each by one source in one grammar. A deprecated declaration form loads for one release and is reported (DEPRECATED_MOUNT_FORM, notice): the L1 form \u2014 a subsystem carrying projectPath \u2014 naming the mount, its path, the `members` entry that replaces it, and every field it carries that `doctor --fix` will move into the member (or stop on); a mount whose alias `members` also declares is reported as ignored; and (stage 8) the long-form `path` key, naming the `source` (or shorthand) `doctor --fix` rewrites it to. Since stage 8 it also judges what each member of the bound project IS against what is declared or written about it, from the scan's problems at the bound root (a member's own members are that member's gate's): what a member is follows from its content \u2014 an id, an L0 or a lock make it a project, anything else a part \u2014 and every contradiction is ONE finding, MEMBER_KIND_MISMATCH (error), naming the alias and the contradiction: an asserted `as` the content contradicts; a part whose configuration declares project fields (members, externals, rules, extensions, composition, targets) without the id that would make it a project (add the id, or move them to the parent); a non-contained part with no PartOf, or one naming another project (opened alone it could not say what it belongs to); a contained member declaring PartOf; a `use` on a member whose content is a part (its names are already this project's own). A part whose files could not be read \u2014 its directory absent, or its git commit not cached and not fetchable (offline, refused, unknown) \u2014 is PART_UNAVAILABLE (error): the project's own subsystems are missing, so its verdict cannot be a pass. The member findings name no spec: members live in configuration. It reads the project graph's nodes, their legacy mounts, the bound root's declared members and its problems.",
20072
+ description: "A project declares its members in project.yaml `members`, each by one source in one grammar. A deprecated declaration form loads for one release and is reported (DEPRECATED_MOUNT_FORM, notice): the L1 form \u2014 a subsystem carrying projectPath \u2014 naming the mount, its path, the `members` entry that replaces it, and every field it carries that `doctor --fix` will move into the member (or stop on); a mount whose alias `members` also declares is reported as ignored; and the deprecated long-form `path` key, naming the `source` (or shorthand) `doctor --fix` rewrites it to. It also judges what each member of the bound project IS against what is declared or written about it, from the scan's problems at the bound root (a member's own members are that member's gate's): what a member is follows from its content \u2014 an id, an L0 or a lock make it a project, anything else a part \u2014 and every contradiction is ONE finding, MEMBER_KIND_MISMATCH (error), naming the alias and the contradiction: an asserted `as` the content contradicts; a part whose configuration declares project fields (members, externals, rules, extensions, composition, targets) without the id that would make it a project (add the id, or move them to the parent); a non-contained part with no PartOf, or one naming another project (opened alone it could not say what it belongs to); a contained member declaring PartOf; a `use` on a member whose content is a part (its names are already this project's own). A part whose files could not be read \u2014 its directory absent, or its git commit not cached and not fetchable (offline, refused, unknown) \u2014 is PART_UNAVAILABLE (error): the project's own subsystems are missing, so its verdict cannot be a pass. The member findings name no spec: members live in configuration. It reads the project graph's nodes, their legacy mounts, the bound root's declared members and its problems.",
19772
20073
  codes: [
19773
- { code: "DEPRECATED_MOUNT_FORM", defaultSeverity: "notice", summary: "A member declared in a deprecated form \u2014 an L1 subsystem carrying projectPath, or (stage 8) the long-form `path` key \u2014 which doctor --fix rewrites to a `members` entry with one `source`." },
20074
+ { code: "DEPRECATED_MOUNT_FORM", defaultSeverity: "notice", summary: "A member declared in a deprecated form \u2014 an L1 subsystem carrying projectPath, or the long-form `path` key \u2014 which doctor --fix rewrites to a `members` entry with one `source`." },
19774
20075
  { code: "MEMBER_KIND_MISMATCH", defaultSeverity: "error", summary: "What a member's content makes it (an id, L0 or lock: a project; else a part) contradicts what is declared or written about it: an asserted `as`, project fields in a part, a missing or wrong PartOf, a `use` on a part." },
19775
20076
  { code: "PART_UNAVAILABLE", defaultSeverity: "error", summary: "A part's files could not be read (absent directory, unpinned or unfetchable git commit): the project's own subsystems are missing." }
19776
20077
  ],
@@ -19808,7 +20109,7 @@ var memberDeclarationsRule = {
19808
20109
  ctx.addIssue(
19809
20110
  "notice",
19810
20111
  "DEPRECATED_MOUNT_FORM",
19811
- `This project declares the member "${member.alias}" with the long-form \`path\` key, which stage 8 replaced by the one location key \`source\`. \`doctor --fix\` rewrites it to ${rest ? `\`source: ${written}\` in its long form` : `the shorthand \`${member.alias}: ${written}\``} \u2014 its meaning unchanged.`
20112
+ `This project declares the member "${member.alias}" with the long-form \`path\` key, which the one location key replaced: \`source\`. \`doctor --fix\` rewrites it to ${rest ? `\`source: ${written}\` in its long form` : `the shorthand \`${member.alias}: ${written}\``} \u2014 its meaning unchanged.`
19812
20113
  );
19813
20114
  }
19814
20115
  for (const problem of family.problems) {
@@ -19845,7 +20146,7 @@ function declaration(producer) {
19845
20146
  var projectBoundariesRule = {
19846
20147
  name: "project-boundaries",
19847
20148
  judges: "design",
19848
- description: "The one judge of every cross-project reference the bound project writes (decision 9 \u2014 every project is a crate of its own, reached only through its L0 exports and only as a dependency it declared). It reads each of the project's own references from the project graph \u2014 `::` forms and bare names bound through `use` imports alike \u2014 takes its resolution from resolveCrossProject, decided before any severity from the project's own files alone, and reports every outcome but resolved once, carrying the resolution: forbidden is EXTERNAL_UNDECLARED (the reference reaches a project the referrer declares neither as a member nor as an external, or names a first segment that is none of its subsystems, aliases or foreign providers); missing is EXTERNAL_NOT_EXPORTED (the producer's table \u2014 a contained member's live one, or the external's pin \u2014 has no public name covering the referrer, or not the used method, or exports the type only inside a signature's closure); an ambiguous import is IMPORT_AMBIGUOUS (two imports supply one bare name: two explicit `use` names, or two `*` with no explicit one \u2014 name it `alias::name` or narrow a `use`); unavailable is EXTERNAL_CHECK_UNAVAILABLE (no pin, an unreadable pin, a member absent on disk, or a deprecated form naming no alias \u2014 never a pass). A bare name no import supplies is not this rule's: it is the unresolved-reference finding it always was, with rule_context.importHint appended. A trustedLinks entry naming a subsystem of another project (`alias::sub`) is TRUSTED_LINK_CROSSES_PROJECT: a sanctioned fast lane stays inside one project. Stage 4 makes the three stage-2 notices errors now that the owner's gate judges them exactly; a reference is never left to a parent's verdict.",
20149
+ description: "The one judge of every cross-project reference the bound project writes (every project is a crate of its own, reached only through its L0 exports and only as a dependency it declared). It reads each of the project's own references from the project graph \u2014 `::` forms and bare names bound through `use` imports alike \u2014 takes its resolution from resolveCrossProject, decided before any severity from the project's own files alone, and reports every outcome but resolved once, carrying the resolution: forbidden is EXTERNAL_UNDECLARED (the reference reaches a project the referrer declares neither as a member nor as an external, or names a first segment that is none of its subsystems, aliases or foreign providers); missing is EXTERNAL_NOT_EXPORTED (the producer's table \u2014 a contained member's live one, or the external's pin \u2014 has no public name covering the referrer, or not the used method, or exports the type only inside a signature's closure); an ambiguous import is IMPORT_AMBIGUOUS (two imports supply one bare name: two explicit `use` names, or two `*` with no explicit one \u2014 name it `alias::name` or narrow a `use`); unavailable is EXTERNAL_CHECK_UNAVAILABLE (no pin, an unreadable pin, a member absent on disk, or a deprecated form naming no alias \u2014 never a pass). A bare name no import supplies is not this rule's: it is the unresolved-reference finding it always was, with rule_context.importHint appended. A trustedLinks entry naming a subsystem of another project (`alias::sub`) is TRUSTED_LINK_CROSSES_PROJECT: a sanctioned fast lane stays inside one project. An undeclared, unexported or ambiguous reference is an error, since the owner's gate judges it exactly; a reference is never left to a parent's verdict.",
19849
20150
  codes: [
19850
20151
  { code: "EXTERNAL_UNDECLARED", defaultSeverity: "error", summary: "A reference reaches another project the referring project declares neither as a member nor as an external, or names a first segment that is none of its subsystems, aliases or foreign providers" },
19851
20152
  { code: "EXTERNAL_NOT_EXPORTED", defaultSeverity: "error", summary: "A reference reaches another project at anything but a public name, or a member of one, whose audience covers the referrer" },
@@ -22391,7 +22692,7 @@ var reproducibilityRule = {
22391
22692
  ctx.addIssue(
22392
22693
  "warning",
22393
22694
  "PACK_SOURCE_UNFETCHABLE",
22394
- `Pack selection "${selection.name}" records no source and is not bundled, so a fresh machine or CI runner cannot obtain it \u2014 \`wairon pack sync\` has nothing to fetch. Record a URL (\`wairon pack use ${selection.name} --source <url>\`) or commit a copy (\`wairon pack bundle ${selection.name}\`).`
22695
+ `Pack selection "${selection.name}" records no source and is not bundled, so a fresh machine or CI runner cannot obtain it \u2014 \`wairon pack sync\` has nothing to fetch. Commit a copy the repository carries (\`wairon pack bundle ${selection.name}\`, or \`wairon pack bundle --all\` for every selection), or record a URL \`wairon pack sync\` fetches it from (\`wairon pack use ${selection.name} --source <url>\`). A selection \`wairon doctor --fix\` recorded from a pack installed from a local path has no URL to record: bundle it.`
22395
22696
  );
22396
22697
  }
22397
22698
  }
@@ -23378,10 +23679,22 @@ var invariantBackingRule = {
23378
23679
  };
23379
23680
 
23380
23681
  // src/core/rules/wiring/invariant-references.ts
23682
+ function unknownInvariantMessage(where, ref, types, aliases) {
23683
+ const parts = splitInvariantRef(ref);
23684
+ const qualifier = parts ? parts.typeRef.lastIndexOf("::") : -1;
23685
+ if (parts && qualifier > 0) {
23686
+ const bare = `${parts.typeRef.slice(qualifier + 2)}.${parts.invariantId}`;
23687
+ if (resolveInvariantRef(bare, types, aliases)) {
23688
+ const prefix = parts.typeRef.slice(0, qualifier);
23689
+ return `${where} asserts invariant "${ref}", whose qualifier "${prefix}::" names no project this one reaches the type through \u2014 the invariant is declared here, so write "${bare}" (a project's own alias, written from inside it, is not a qualifier it reads as its own; \`wairon doctor --fix\` rewrites it in a migrated family).`;
23690
+ }
23691
+ }
23692
+ return `${where} asserts invariant "${ref}", but no entity declares it (expected "<type-id>.<invariant-id>" naming a declared entry in that entity's invariants).`;
23693
+ }
23381
23694
  var invariantReferencesRule = {
23382
23695
  name: "invariant-references",
23383
23696
  judges: "design",
23384
- description: "Every invariant a narrative step asserts (step.assertsInvariants) must resolve to a declared entity invariant: the reference splits at its last dot into a type reference and an invariant id, and some entity matching that type reference must declare that id. A reference nothing declares is an error \u2014 the step claims a property that exists nowhere.",
23697
+ description: "Every invariant a narrative step asserts (step.assertsInvariants) must resolve to a declared entity invariant: the reference splits at its last dot into a type reference and an invariant id, and some entity matching that type reference must declare that id. A reference nothing declares is an error \u2014 the step claims a property that exists nowhere. The message says which: when the type reference carries a qualifier (`alias::type`) and the bare reference does resolve, the cause is the qualifier \u2014 typically the project's own alias written from inside it \u2014 and the message names it and the bare reference to write instead, never claiming that no entity declares the invariant.",
23385
23698
  codes: [
23386
23699
  { code: "UNKNOWN_INVARIANT_REF", defaultSeverity: "error", summary: "A narrative step asserts an invariant that no entity declares" }
23387
23700
  ],
@@ -23396,7 +23709,7 @@ var invariantReferencesRule = {
23396
23709
  ctx.addIssue(
23397
23710
  "error",
23398
23711
  "UNKNOWN_INVARIANT_REF",
23399
- `Step ${step.stepNumber} of "${method.name}" in implementation "${impl.id}" asserts invariant "${ref}", but no entity declares it (expected "<type-id>.<invariant-id>" naming a declared entry in that entity's invariants).`,
23712
+ unknownInvariantMessage(`Step ${step.stepNumber} of "${method.name}" in implementation "${impl.id}"`, ref, ctx.types, aliases),
23400
23713
  impl.id,
23401
23714
  isDraftCtx
23402
23715
  );
@@ -23741,13 +24054,13 @@ function invokedAs(code, site, file) {
23741
24054
  if (!site.member) {
23742
24055
  const binding = importBindingOf(facts, site.name);
23743
24056
  if (!binding || binding.namespace) return [];
23744
- const module3 = resolveImport(scope, binding.from, code.paths);
24057
+ const module3 = resolveImport(scope, binding.from, code.paths, code.packages);
23745
24058
  return module3 ? code.forwardsOf(module3, binding.imported ?? site.name) : [];
23746
24059
  }
23747
24060
  if (!site.via) return [];
23748
24061
  const receiver = importBindingOf(facts, site.via);
23749
24062
  if (!receiver?.namespace) return [];
23750
- const module2 = resolveImport(scope, receiver.from, code.paths);
24063
+ const module2 = resolveImport(scope, receiver.from, code.paths, code.packages);
23751
24064
  return module2 ? code.forwardsOf(module2, site.name) : [];
23752
24065
  }
23753
24066
  function claimsOf(implMethod) {
@@ -23769,6 +24082,8 @@ function describeSite(site) {
23769
24082
  if (site.via) return `${site.via}.${site.name}(\u2026)`;
23770
24083
  if (site.field) return `this.${site.field}.${site.name}(\u2026)`;
23771
24084
  if (site.constructed) return `new ${site.constructed}(\u2026).${site.name}(\u2026)`;
24085
+ if (site.returnedBy) return `${site.returnedBy}(\u2026).${site.name}(\u2026)`;
24086
+ if (site.enclosingClass) return `this.${site.name}(\u2026)`;
23772
24087
  return `<receiver>.${site.name}(\u2026)`;
23773
24088
  }
23774
24089
  function describeMiss(m) {
@@ -23781,7 +24096,7 @@ function describeMiss(m) {
23781
24096
  var callConformanceRule = {
23782
24097
  name: "call-conformance",
23783
24098
  judges: "code",
23784
- 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, and 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. 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 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.",
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.",
23785
24100
  codes: [
23786
24101
  { 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 },
23787
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 },
@@ -23824,7 +24139,7 @@ var callConformanceRule = {
23824
24139
  if (!ctx.componentMap.has(claim.component)) continue;
23825
24140
  const target = resolveCallTarget(ctx, claim.component, claim.method);
23826
24141
  if (target.accepted.has(fnSymbol)) continue;
23827
- if (isTarget(code.forwardsOf(file, fnSymbol), target)) continue;
24142
+ if (isTarget(code.forwardsOf(file, fnSymbol, implMethod.exportedVia), target)) continue;
23828
24143
  const matching = sites.filter((s) => target.accepted.has(s.name));
23829
24144
  const ref = `${claim.component}.${claim.method}`;
23830
24145
  if (target.files.size === 0) {
@@ -23867,7 +24182,7 @@ var callConformanceRule = {
23867
24182
  ctx.addIssue(
23868
24183
  "warning",
23869
24184
  "CALL_ORIGIN_UNRESOLVED",
23870
- `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 \`this.<field>\` receiver whose declared type names one, a \`new Class(\u2026)\` receiver whose class does, and a receiver NAME the file annotates with a type that does. What does not: an import from a PACKAGE specifier, 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.`,
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.`,
23871
24186
  impl.id,
23872
24187
  entry.draftContext,
23873
24188
  void 0,
@@ -23889,7 +24204,9 @@ var callConformanceRule = {
23889
24204
  reached.set(site, owner);
23890
24205
  return owner !== void 0;
23891
24206
  };
23892
- for (const site of closedCallSites(code, file, fnSymbol, stopAtModelled, implMethod.exportedVia) ?? []) {
24207
+ const forwardsToModelled = code.forwardsOf(file, fnSymbol, implMethod.exportedVia).some((p) => p.file !== here && modelledAt.get(p.file)?.byName.has(p.name));
24208
+ const conversed = forwardsToModelled ? [] : closedCallSites(code, file, fnSymbol, stopAtModelled, implMethod.exportedVia) ?? [];
24209
+ for (const site of conversed) {
23893
24210
  const owner = reached.get(site);
23894
24211
  if (!owner || owner.component === component.id) continue;
23895
24212
  const ref = `${owner.component}.${owner.method}`;
@@ -25077,7 +25394,7 @@ var exportConformanceRule = {
25077
25394
  const facts = code.factsAt(file);
25078
25395
  if (!facts || facts.status !== "analyzed" || facts.analysisGrade !== "exact") continue;
25079
25396
  for (const binding of facts.reexportBindings ?? []) {
25080
- const target = resolveImport(file, binding.from, code.paths);
25397
+ const target = resolveImport(file, binding.from, code.paths, code.packages);
25081
25398
  const methods = target ? typeMethodsAt.get(target) : void 0;
25082
25399
  if (!methods) continue;
25083
25400
  if (binding.local === "*") {
@@ -25089,7 +25406,7 @@ var exportConformanceRule = {
25089
25406
  }
25090
25407
  const takenFrom = /* @__PURE__ */ new Map();
25091
25408
  const take = (consumer, writtenIn, specifier, names) => {
25092
- const target = resolveImport(writtenIn, specifier, code.paths);
25409
+ const target = resolveImport(writtenIn, specifier, code.paths, code.packages);
25093
25410
  if (!target) return;
25094
25411
  let byName = takenFrom.get(target);
25095
25412
  if (!byName) takenFrom.set(target, byName = /* @__PURE__ */ new Map());
@@ -26823,6 +27140,9 @@ function walkExact(ts, sourceText, fileName) {
26823
27140
  const reexports = /* @__PURE__ */ new Set();
26824
27141
  const starExports = [];
26825
27142
  const namedReexports = [];
27143
+ const exportAliases = [];
27144
+ const propertyAliases = [];
27145
+ const returnTypes = /* @__PURE__ */ new Map();
26826
27146
  const complexity = /* @__PURE__ */ new Map();
26827
27147
  const calls = /* @__PURE__ */ new Map();
26828
27148
  const bodies = /* @__PURE__ */ new Map();
@@ -26839,7 +27159,7 @@ function walkExact(ts, sourceText, fileName) {
26839
27159
  const mutableBindings = /* @__PURE__ */ new Set();
26840
27160
  const schemaConstants = /* @__PURE__ */ new Map();
26841
27161
  const derivedAliases = [];
26842
- const receiverKey = (site) => site.via ?? (site.field ? `this.${site.field}` : site.constructed ? `new ${site.constructed}` : "");
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}` : "");
26843
27163
  const siteKey = (site) => `${site.member ? "m" : "b"}:${receiverKey(site)}:${site.name}`;
26844
27164
  const addBindingNames = (name) => {
26845
27165
  if (ts.isIdentifier(name)) declared.add(name.text);
@@ -26947,6 +27267,44 @@ function walkExact(ts, sourceText, fileName) {
26947
27267
  }
26948
27268
  return void 0;
26949
27269
  };
27270
+ const enclosingClassOf = (node) => {
27271
+ let at = node.parent;
27272
+ while (at) {
27273
+ if (ts.isFunctionExpression(at) || ts.isFunctionDeclaration(at) || ts.isClassExpression(at)) return void 0;
27274
+ if ((ts.isMethodDeclaration(at) || ts.isGetAccessorDeclaration(at) || ts.isSetAccessorDeclaration(at)) && at.parent && ts.isObjectLiteralExpression(at.parent)) return void 0;
27275
+ if (ts.isClassDeclaration(at)) {
27276
+ return at.name && at.parent && ts.isSourceFile(at.parent) ? at.name.text : void 0;
27277
+ }
27278
+ at = at.parent;
27279
+ }
27280
+ return void 0;
27281
+ };
27282
+ const returnedTypeOf = (fn) => {
27283
+ const annotated = fn.type;
27284
+ if (annotated) {
27285
+ if (!ts.isTypeReferenceNode(annotated)) return void 0;
27286
+ if (ts.isIdentifier(annotated.typeName) && annotated.typeName.text === "Promise" && annotated.typeArguments?.length === 1) {
27287
+ const inner2 = annotated.typeArguments[0];
27288
+ return ts.isTypeReferenceNode(inner2) && !inner2.typeArguments ? typeReferenceName(inner2) : void 0;
27289
+ }
27290
+ return annotated.typeArguments ? void 0 : typeReferenceName(annotated);
27291
+ }
27292
+ const constructedClass = (expression) => {
27293
+ let node = expression;
27294
+ while (node && (ts.isParenthesizedExpression(node) || ts.isAsExpression(node) || ts.isSatisfiesExpression(node))) node = node.expression;
27295
+ return node && ts.isNewExpression(node) && ts.isIdentifier(node.expression) ? node.expression.text : void 0;
27296
+ };
27297
+ if (!fn.body) return void 0;
27298
+ if (!ts.isBlock(fn.body)) return constructedClass(fn.body);
27299
+ const returns = [];
27300
+ const collect = (node) => {
27301
+ if (ts.isFunctionLike(node)) return;
27302
+ if (ts.isReturnStatement(node)) returns.push(node);
27303
+ ts.forEachChild(node, collect);
27304
+ };
27305
+ ts.forEachChild(fn.body, collect);
27306
+ return returns.length === 1 ? constructedClass(returns[0].expression) : void 0;
27307
+ };
26950
27308
  const isTopLevelVariable = (decl) => ts.isVariableDeclaration(decl) && ts.isIdentifier(decl.name) && !!decl.parent && ts.isVariableDeclarationList(decl.parent) && !!decl.parent.parent && ts.isVariableStatement(decl.parent.parent) && !!decl.parent.parent.parent && ts.isSourceFile(decl.parent.parent.parent);
26951
27309
  const containerOf = (owner) => {
26952
27310
  if (!owner) return void 0;
@@ -27008,6 +27366,11 @@ function walkExact(ts, sourceText, fileName) {
27008
27366
  addSite({ name, member: true, field: receiver.name.text });
27009
27367
  } else if (ts.isNewExpression(receiver) && ts.isIdentifier(receiver.expression)) {
27010
27368
  addSite({ name, member: true, constructed: receiver.expression.text });
27369
+ } else if (ts.isCallExpression(receiver) && ts.isIdentifier(receiver.expression)) {
27370
+ addSite({ name, member: true, returnedBy: receiver.expression.text });
27371
+ } else if (receiver.kind === ts.SyntaxKind.ThisKeyword) {
27372
+ const owner = enclosingClassOf(node);
27373
+ addSite(owner !== void 0 ? { name, member: true, enclosingClass: owner } : { name, member: true });
27011
27374
  } else addSite({ name, member: true });
27012
27375
  }
27013
27376
  }
@@ -27134,6 +27497,8 @@ function walkExact(ts, sourceText, fileName) {
27134
27497
  signatures.push(declaredParameters(node));
27135
27498
  functionParams.set(fnName, signatures);
27136
27499
  if (completesLater(node)) asyncFunctions.push(fnName);
27500
+ const returned = returnedTypeOf(node) ?? null;
27501
+ returnTypes.set(fnName, returnTypes.has(fnName) && returnTypes.get(fnName) !== returned ? null : returned);
27137
27502
  const routes = collectRoutes(node);
27138
27503
  if (routes.size > 0) {
27139
27504
  const known = functionRoutes.get(fnName) ?? /* @__PURE__ */ new Map();
@@ -27170,6 +27535,23 @@ function walkExact(ts, sourceText, fileName) {
27170
27535
  if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer && !schemaConstants.has(node.name.text)) {
27171
27536
  schemaConstants.set(node.name.text, node.initializer);
27172
27537
  }
27538
+ if (isTopLevelVariable(node) && node.initializer) {
27539
+ let literal = node.initializer;
27540
+ while (ts.isAsExpression(literal) || ts.isSatisfiesExpression(literal) || ts.isParenthesizedExpression(literal)) {
27541
+ literal = literal.expression;
27542
+ }
27543
+ if (ts.isObjectLiteralExpression(literal)) {
27544
+ const container = node.name.text;
27545
+ for (const property of literal.properties) {
27546
+ if (ts.isShorthandPropertyAssignment(property)) {
27547
+ propertyAliases.push({ exported: property.name.text, local: property.name.text, container });
27548
+ } else if (ts.isPropertyAssignment(property) && ts.isIdentifier(property.initializer)) {
27549
+ const key = propertyNameText(property.name);
27550
+ if (key) propertyAliases.push({ exported: key, local: property.initializer.text, container });
27551
+ }
27552
+ }
27553
+ }
27554
+ }
27173
27555
  if (ts.isFunctionDeclaration(node) || ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node) || ts.isEnumDeclaration(node) || ts.isModuleDeclaration(node)) {
27174
27556
  const name = node.name && ts.isIdentifier(node.name) ? node.name.text : void 0;
27175
27557
  if (name) {
@@ -27221,9 +27603,10 @@ function walkExact(ts, sourceText, fileName) {
27221
27603
  for (const el of node.exportClause.elements) {
27222
27604
  declared.add(el.name.text);
27223
27605
  exported.add(el.name.text);
27224
- if (spec && !node.isTypeOnly && !el.isTypeOnly) {
27225
- namedReexports.push({ exported: el.name.text, local: (el.propertyName ?? el.name).text, from: spec });
27226
- }
27606
+ if (node.isTypeOnly || el.isTypeOnly) continue;
27607
+ const local = (el.propertyName ?? el.name).text;
27608
+ if (spec) namedReexports.push({ exported: el.name.text, local, from: spec });
27609
+ else exportAliases.push({ exported: el.name.text, local });
27227
27610
  }
27228
27611
  } else if (!node.exportClause && spec) {
27229
27612
  starExports.push(spec);
@@ -27298,7 +27681,13 @@ function walkExact(ts, sourceText, fileName) {
27298
27681
  if (values && schemaConstants.has(alias.constant)) enumValues.set(alias.name, values);
27299
27682
  }
27300
27683
  const reexportOnly = sf.statements.length > 0 && sf.statements.every((st) => ts.isExportDeclaration(st) && !!st.moduleSpecifier);
27301
- return { declared, anchors, exported, imports, reexports, starExports, namedReexports, complexity, calls, bodies, importBindings, typeOnlyBindings, fieldTypes, localTypes, typeShapes, functionParams, functionRoutes, asyncFunctions, enumValues, aliasTypes, mutableBindings, reexportOnly };
27684
+ 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 };
27686
+ }
27687
+ var NO_PACKAGES = /* @__PURE__ */ new Map();
27688
+ function resolveModule(fromFile, specifier, packages) {
27689
+ if (!specifier.startsWith(".")) return packages.get(specifier) ?? null;
27690
+ return resolveRelativeModule(fromFile, specifier);
27302
27691
  }
27303
27692
  function resolveRelativeModule(fromFile, specifier) {
27304
27693
  if (!specifier.startsWith(".")) return null;
@@ -27321,8 +27710,8 @@ function resolveRelativeModule(fromFile, specifier) {
27321
27710
  }
27322
27711
  return null;
27323
27712
  }
27324
- function reexportTarget(ts, filePath, specifier, projectRoot, visited, exactCache) {
27325
- const target = resolveRelativeModule(filePath, specifier);
27713
+ function reexportTarget(ts, filePath, specifier, projectRoot, visited, exactCache, packages) {
27714
+ const target = resolveModule(filePath, specifier, packages);
27326
27715
  if (!target || path18.relative(projectRoot, target).startsWith("..")) return null;
27327
27716
  let targetFacts = exactCache.get(target);
27328
27717
  if (targetFacts === void 0) {
@@ -27336,7 +27725,7 @@ function reexportTarget(ts, filePath, specifier, projectRoot, visited, exactCach
27336
27725
  if (!targetFacts) return null;
27337
27726
  if (!visited.has(target)) {
27338
27727
  visited.add(target);
27339
- chaseReexports(ts, targetFacts, target, projectRoot, visited, exactCache);
27728
+ chaseReexports(ts, targetFacts, target, projectRoot, visited, exactCache, packages);
27340
27729
  }
27341
27730
  return { path: target, facts: targetFacts };
27342
27731
  }
@@ -27359,13 +27748,33 @@ function carryBody(facts, target, targetPath, local, exported, projectRoot) {
27359
27748
  }
27360
27749
  facts.bodies.set(exported, carried);
27361
27750
  }
27362
- function chaseReexports(ts, facts, filePath, projectRoot, visited, exactCache) {
27751
+ function carryAliasBody(facts, source, from, local, alias) {
27752
+ const sourceBodies = source.bodies.get(local) ?? [];
27753
+ if (!sourceBodies.length || sourceBodies.some((b) => b.nested)) return;
27754
+ const moduleScope = sourceBodies.filter((b) => b.container === void 0);
27755
+ if (!moduleScope.length) return;
27756
+ const score = source.complexity.get(local);
27757
+ if (score !== void 0) {
27758
+ facts.complexity.set(alias.exported, Math.max(facts.complexity.get(alias.exported) ?? 0, score));
27759
+ }
27760
+ const relocate2 = (site) => from !== void 0 ? { ...site, from: site.from ?? from } : site;
27761
+ const sites = facts.calls.get(alias.exported) ?? /* @__PURE__ */ new Map();
27762
+ const carried = facts.bodies.get(alias.exported) ?? [];
27763
+ for (const body of moduleScope) {
27764
+ const moved = body.sites.map(relocate2);
27765
+ for (const site of moved) sites.set(`${from ?? ""}|${alias.container ?? ""}|${JSON.stringify(site)}`, site);
27766
+ carried.push(alias.container !== void 0 ? { container: alias.container, sites: moved } : { sites: moved });
27767
+ }
27768
+ facts.calls.set(alias.exported, sites);
27769
+ facts.bodies.set(alias.exported, carried);
27770
+ }
27771
+ function chaseReexports(ts, facts, filePath, projectRoot, visited, exactCache, packages = NO_PACKAGES) {
27363
27772
  for (const re of facts.namedReexports) {
27364
- const target = reexportTarget(ts, filePath, re.from, projectRoot, visited, exactCache);
27773
+ const target = reexportTarget(ts, filePath, re.from, projectRoot, visited, exactCache, packages);
27365
27774
  if (target) carryBody(facts, target.facts, target.path, re.local, re.exported, projectRoot);
27366
27775
  }
27367
27776
  for (const spec of facts.starExports) {
27368
- const target = reexportTarget(ts, filePath, spec, projectRoot, visited, exactCache);
27777
+ const target = reexportTarget(ts, filePath, spec, projectRoot, visited, exactCache, packages);
27369
27778
  if (!target) continue;
27370
27779
  for (const name of target.facts.exported) {
27371
27780
  facts.exported.add(name);
@@ -27373,6 +27782,150 @@ function chaseReexports(ts, facts, filePath, projectRoot, visited, exactCache) {
27373
27782
  carryBody(facts, target.facts, target.path, name, name, projectRoot);
27374
27783
  }
27375
27784
  }
27785
+ for (const alias of facts.exportAliases) {
27786
+ const binding = facts.importBindings.get(alias.local);
27787
+ if (binding) {
27788
+ if (binding.namespace) continue;
27789
+ const target = reexportTarget(ts, filePath, binding.from, projectRoot, visited, exactCache, packages);
27790
+ if (!target) continue;
27791
+ const from = pathKey(path18.relative(projectRoot, target.path));
27792
+ carryAliasBody(facts, target.facts, from, binding.imported ?? alias.local, alias);
27793
+ } else if (alias.container !== void 0 || alias.local !== alias.exported) {
27794
+ carryAliasBody(facts, facts, void 0, alias.local, alias);
27795
+ }
27796
+ }
27797
+ }
27798
+ var BUILD_EXTENSIONS = [".d.mts", ".d.cts", ".d.ts", ".mjs", ".cjs", ".jsx", ".js"];
27799
+ var SOURCE_EXTENSIONS = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
27800
+ var ENTRY_CONDITIONS = ["types", "import", "require", "default"];
27801
+ function isOwnFile(projectRoot, absolute) {
27802
+ const rel2 = path18.relative(projectRoot, absolute);
27803
+ if (rel2.startsWith("..") || path18.isAbsolute(rel2)) return false;
27804
+ return !pathKey(rel2).split("/").includes("node_modules");
27805
+ }
27806
+ function isFile(absolute) {
27807
+ try {
27808
+ return fs13.statSync(absolute).isFile();
27809
+ } catch {
27810
+ return false;
27811
+ }
27812
+ }
27813
+ function readJsonFile2(file, ts) {
27814
+ let text3;
27815
+ try {
27816
+ text3 = fs13.readFileSync(file, "utf8");
27817
+ } catch {
27818
+ return void 0;
27819
+ }
27820
+ try {
27821
+ return JSON.parse(text3);
27822
+ } catch {
27823
+ if (!ts) return void 0;
27824
+ const parsed = ts.parseConfigFileTextToJson(file, text3);
27825
+ return parsed.error ? void 0 : parsed.config;
27826
+ }
27827
+ }
27828
+ function conditionTargets(value) {
27829
+ if (typeof value === "string") return [value];
27830
+ if (!value || typeof value !== "object" || Array.isArray(value)) return [];
27831
+ const record = value;
27832
+ const keys = [...ENTRY_CONDITIONS.filter((k) => k in record), ...Object.keys(record).filter((k) => !ENTRY_CONDITIONS.includes(k))];
27833
+ return keys.flatMap((k) => conditionTargets(record[k]));
27834
+ }
27835
+ function packageEntries(pkg) {
27836
+ const entries = /* @__PURE__ */ new Map();
27837
+ const exportsField = pkg.exports;
27838
+ if (exportsField !== void 0 && exportsField !== null) {
27839
+ const isSubpathMap = typeof exportsField === "object" && !Array.isArray(exportsField) && Object.keys(exportsField).some((k) => k.startsWith("."));
27840
+ if (isSubpathMap) {
27841
+ for (const [subpath, value] of Object.entries(exportsField)) {
27842
+ if (!subpath.startsWith(".") || subpath.includes("*")) continue;
27843
+ const targets = conditionTargets(value);
27844
+ if (targets.length) entries.set(subpath, targets);
27845
+ }
27846
+ } else {
27847
+ const targets = conditionTargets(exportsField);
27848
+ if (targets.length) entries.set(".", targets);
27849
+ }
27850
+ return entries;
27851
+ }
27852
+ const fallbacks = ["module", "main", "types"].map((k) => pkg[k]).filter((v) => typeof v === "string");
27853
+ if (fallbacks.length) entries.set(".", fallbacks);
27854
+ return entries;
27855
+ }
27856
+ function entrySource(packageDir, target, compiler, projectRoot) {
27857
+ const absolute = path18.resolve(packageDir, target);
27858
+ if (!isOwnFile(projectRoot, absolute)) return null;
27859
+ const isDeclaration = /\.d\.[cm]?ts$/.test(absolute);
27860
+ if (!isDeclaration && languageOfSourcePath(absolute) && isFile(absolute) && !(compiler.outDir && !path18.relative(compiler.outDir, absolute).startsWith(".."))) {
27861
+ return absolute;
27862
+ }
27863
+ if (!compiler.outDir || !compiler.rootDir) return null;
27864
+ const inOut = path18.relative(compiler.outDir, absolute);
27865
+ if (inOut.startsWith("..") || path18.isAbsolute(inOut)) return null;
27866
+ const extension = BUILD_EXTENSIONS.find((ext) => inOut.endsWith(ext));
27867
+ const stem = extension ? inOut.slice(0, -extension.length) : inOut;
27868
+ for (const ext of SOURCE_EXTENSIONS) {
27869
+ const candidate = path18.join(compiler.rootDir, stem + ext);
27870
+ if (isOwnFile(projectRoot, candidate) && isFile(candidate)) return candidate;
27871
+ }
27872
+ return null;
27873
+ }
27874
+ function readLocalPackages(projectRoot) {
27875
+ const out = /* @__PURE__ */ new Map();
27876
+ const ts = resolveTypeScript(projectRoot);
27877
+ const root = readJsonFile2(path18.join(projectRoot, "package.json"), null);
27878
+ if (!root || typeof root !== "object" || Array.isArray(root)) return out;
27879
+ const rootPkg = root;
27880
+ const declared = Array.isArray(rootPkg.workspaces) ? rootPkg.workspaces : rootPkg.workspaces && typeof rootPkg.workspaces === "object" && Array.isArray(rootPkg.workspaces.packages) ? rootPkg.workspaces.packages : [];
27881
+ const dirs = [projectRoot];
27882
+ for (const entry of declared) {
27883
+ if (typeof entry !== "string") continue;
27884
+ const key = pathKey(entry).replace(/\/+$/, "");
27885
+ if (!key || path18.isAbsolute(key)) continue;
27886
+ if (key.endsWith("/*")) {
27887
+ const base = path18.resolve(projectRoot, key.slice(0, -2));
27888
+ if (base.includes("*") || !isOwnFile(projectRoot, base)) continue;
27889
+ let children;
27890
+ try {
27891
+ children = fs13.readdirSync(base, { withFileTypes: true });
27892
+ } catch {
27893
+ continue;
27894
+ }
27895
+ for (const child of children.slice().sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0)) {
27896
+ if (child.isDirectory() && child.name !== "node_modules" && !child.name.startsWith(".")) {
27897
+ dirs.push(path18.join(base, child.name));
27898
+ }
27899
+ }
27900
+ } else if (!key.includes("*")) {
27901
+ const dir = path18.resolve(projectRoot, key);
27902
+ if (isOwnFile(projectRoot, dir)) dirs.push(dir);
27903
+ }
27904
+ }
27905
+ for (const dir of dirs) {
27906
+ const pkg = dir === projectRoot ? rootPkg : readJsonFile2(path18.join(dir, "package.json"), null);
27907
+ if (!pkg || typeof pkg !== "object" || Array.isArray(pkg)) continue;
27908
+ const name = pkg.name;
27909
+ if (typeof name !== "string" || !name) continue;
27910
+ const tsconfig = readJsonFile2(path18.join(dir, "tsconfig.json"), ts);
27911
+ const options = tsconfig?.compilerOptions ?? {};
27912
+ const compiler = {
27913
+ outDir: typeof options.outDir === "string" ? path18.resolve(dir, options.outDir) : void 0,
27914
+ rootDir: typeof options.rootDir === "string" ? path18.resolve(dir, options.rootDir) : void 0
27915
+ };
27916
+ for (const [subpath, targets] of packageEntries(pkg)) {
27917
+ const specifier = subpath === "." ? name : `${name}/${subpath.replace(/^\.\//, "")}`;
27918
+ if (out.has(specifier)) continue;
27919
+ for (const target of targets) {
27920
+ const source = entrySource(dir, target, compiler, projectRoot);
27921
+ if (source) {
27922
+ out.set(specifier, source);
27923
+ break;
27924
+ }
27925
+ }
27926
+ }
27927
+ }
27928
+ return out;
27376
27929
  }
27377
27930
  function looksBinary(buffer) {
27378
27931
  const probe = buffer.subarray(0, Math.min(buffer.length, 4096));
@@ -27430,6 +27983,9 @@ function buildCodeModel(implementations, types, projectRoot, sourceRoots = [], e
27430
27983
  const seen = /* @__PURE__ */ new Set();
27431
27984
  const exactCache = /* @__PURE__ */ new Map();
27432
27985
  const rootFiles = walkDeclaredRoots(sourceRoots, exclude, projectRoot);
27986
+ const localPackages = readLocalPackages(projectRoot);
27987
+ const packages = {};
27988
+ for (const [specifier, absolute] of localPackages) packages[specifier] = pathKey(path18.relative(projectRoot, absolute));
27433
27989
  const declaredPaths = [];
27434
27990
  for (const impl of implementations) {
27435
27991
  declaredPaths.push(...implementationSourceFiles(impl));
@@ -27475,7 +28031,7 @@ function buildCodeModel(implementations, types, projectRoot, sourceRoots = [], e
27475
28031
  if (ts) {
27476
28032
  try {
27477
28033
  const facts = walkExact(ts, text3, absolute);
27478
- chaseReexports(ts, facts, absolute, projectRoot, /* @__PURE__ */ new Set([absolute]), exactCache);
28034
+ chaseReexports(ts, facts, absolute, projectRoot, /* @__PURE__ */ new Set([absolute]), exactCache, localPackages);
27479
28035
  analyzed = {
27480
28036
  analysisGrade: "exact",
27481
28037
  declaredNames: [...facts.declared],
@@ -27487,6 +28043,10 @@ function buildCodeModel(implementations, types, projectRoot, sourceRoots = [], e
27487
28043
  ...facts.namedReexports.map(({ exported, local, from }) => ({ exported, local, from })),
27488
28044
  ...facts.starExports.map((from) => ({ exported: "*", local: "*", from }))
27489
28045
  ],
28046
+ exportAliases: facts.exportAliases.map((alias) => ({ ...alias })),
28047
+ returnTypes: Object.fromEntries(
28048
+ [...facts.returnTypes].filter((entry) => entry[1] !== null)
28049
+ ),
27490
28050
  functionComplexity: Object.fromEntries(facts.complexity),
27491
28051
  functionCallSites: Object.fromEntries([...facts.calls].map(([k, v]) => [k, [...v.values()]])),
27492
28052
  functionBodies: Object.fromEntries(facts.bodies),
@@ -27516,7 +28076,7 @@ function buildCodeModel(implementations, types, projectRoot, sourceRoots = [], e
27516
28076
  }
27517
28077
  files.push({ path: sourcePath, status: "analyzed", language, ...analyzed });
27518
28078
  }
27519
- return { files, projectRoot, rootFiles };
28079
+ return { files, projectRoot, rootFiles, packages };
27520
28080
  }
27521
28081
  var IMPORT_FROM_RE = /(?:^|;)[ \t]*import\s+(?!\()([^'";]*?)\s*from\s*['"]([^'"]+)['"]/gm;
27522
28082
  var DYNAMIC_IMPORT_RE = /\{([^{}]*)\}\s*=\s*await\s+import\(\s*['"]([^'"]+)['"]\s*\)/g;
@@ -29483,8 +30043,8 @@ function generateSequenceDiagram(componentId, methodName, options) {
29483
30043
  const steps = [...methodImpl.narrative].sort((a, b) => a.stepNumber - b.stepNumber);
29484
30044
  for (const step of steps) {
29485
30045
  for (const par of parallelArms) {
29486
- const sep14 = par.sepByStep.get(step.stepNumber);
29487
- if (sep14 !== void 0) lines.push(` and ${escapeLabel(sep14)}`);
30046
+ const sep15 = par.sepByStep.get(step.stepNumber);
30047
+ if (sep15 !== void 0) lines.push(` and ${escapeLabel(sep15)}`);
29488
30048
  }
29489
30049
  switch (step.type) {
29490
30050
  case "local":
@@ -30214,6 +30774,15 @@ function classifySource(world, sourceId, label7, sources) {
30214
30774
  detail: `"${label7}" re-exports from "${sourceId}", which is neither a subsystem of this project, a member nor a declared external the scan read \u2014 a project re-exports only from its subsystems, members and externals of its family`
30215
30775
  };
30216
30776
  }
30777
+ function duplicateDetail(entries, index) {
30778
+ const e = entries[index];
30779
+ if (e.from !== void 0 || e.component !== void 0 || e.interface !== void 0 || e.typeDef !== void 0) return null;
30780
+ const name = e.as ?? e.id;
30781
+ if (name === void 0) return null;
30782
+ const other = entries.findIndex((x, i) => i !== index && (x.as ?? x.id) === name);
30783
+ if (other === -1) return null;
30784
+ return `has a duplicate entry "${name}": entry #${index + 1} names no source and no item, while entry #${other + 1} declares the same id \u2014 delete the copy (entry #${index + 1}), or give it a source and an item under an id of its own`;
30785
+ }
30217
30786
  function bindOwnType(world, owner, e, problems) {
30218
30787
  const type = (world.types.get(e.typeDef) ?? []).find((t) => !t.subsystem);
30219
30788
  if (!type) {
@@ -30285,7 +30854,8 @@ function resolveProjectTable(system, subsystems, components, interfaces, types,
30285
30854
  const owner = sources.namespace ? sources.namespace : system.name;
30286
30855
  const problems = [];
30287
30856
  const candidates = [];
30288
- (system.publicInterfaces ?? []).forEach((e, index) => {
30857
+ const entries = system.publicInterfaces ?? [];
30858
+ entries.forEach((e, index) => {
30289
30859
  const label7 = e.as ?? e.id ?? e.interface ?? e.component ?? e.typeDef ?? `#${index + 1}`;
30290
30860
  const source = classifySource(world, sourceOf(world, e), label7, sources);
30291
30861
  switch (source.kind) {
@@ -30311,7 +30881,7 @@ function resolveProjectTable(system, subsystems, components, interfaces, types,
30311
30881
  return;
30312
30882
  }
30313
30883
  default:
30314
- problems.push({ kind: "invalid", owner, publicName: label7, detail: source.detail });
30884
+ problems.push({ kind: "invalid", owner, publicName: label7, detail: duplicateDetail(entries, index) ?? source.detail });
30315
30885
  }
30316
30886
  });
30317
30887
  return settle(owner, "project", candidates, problems);
@@ -30904,6 +31474,16 @@ function qualifiedTypeNames(typeStr) {
30904
31474
  }
30905
31475
  return out;
30906
31476
  }
31477
+ function invariantTypeRef(ref) {
31478
+ const at = ref.lastIndexOf(".");
31479
+ return at > 0 && at < ref.length - 1 ? ref.slice(0, at) : void 0;
31480
+ }
31481
+ function mapInvariantRef(ref, map) {
31482
+ const typeRef2 = invariantTypeRef(ref);
31483
+ if (typeRef2 === void 0 || !typeRef2.includes("::")) return ref;
31484
+ const next = map("type", typeRef2);
31485
+ return next === typeRef2 ? ref : `${next}${ref.slice(typeRef2.length)}`;
31486
+ }
30907
31487
  function rawReferences(kind, spec) {
30908
31488
  const out = [];
30909
31489
  const add2 = (position, value) => {
@@ -30926,6 +31506,7 @@ function rawReferences(kind, spec) {
30926
31506
  for (const step of m.narrative) {
30927
31507
  const source = step.auth?.from;
30928
31508
  if (typeof source === "string" && source.startsWith(COMPONENT_AUTH_SOURCE)) add2("auth", source.slice(COMPONENT_AUTH_SOURCE.length));
31509
+ for (const ref of step.assertsInvariants ?? []) add2("type", invariantTypeRef(ref));
30929
31510
  }
30930
31511
  }
30931
31512
  break;
@@ -31045,6 +31626,9 @@ function respellStoredReferences(kind, doc, map) {
31045
31626
  for (const m of list(doc.methods)) {
31046
31627
  for (const step of list(m.narrative)) {
31047
31628
  at(step, "targetComponent", "narrative");
31629
+ if (Array.isArray(step.assertsInvariants)) {
31630
+ step.assertsInvariants = step.assertsInvariants.map((ref) => typeof ref === "string" ? mapInvariantRef(ref, map) : ref);
31631
+ }
31048
31632
  const auth = step.auth;
31049
31633
  if (auth && typeof auth.from === "string" && auth.from.startsWith(COMPONENT_AUTH_SOURCE)) {
31050
31634
  auth.from = `${COMPONENT_AUTH_SOURCE}${map("auth", auth.from.slice(COMPONENT_AUTH_SOURCE.length))}`;
@@ -31104,7 +31688,8 @@ function mapRawReferences(kind, spec, map) {
31104
31688
  return call && next !== call.compId ? `${next}.${call.methodName}` : entry;
31105
31689
  })
31106
31690
  } : {},
31107
- narrative: m.narrative.map((step) => {
31691
+ narrative: m.narrative.map((authored) => {
31692
+ const step = authored.assertsInvariants ? { ...authored, assertsInvariants: authored.assertsInvariants.map((ref) => mapInvariantRef(ref, map)) } : authored;
31108
31693
  const source = step.auth?.from;
31109
31694
  if (typeof source !== "string" || !source.startsWith(COMPONENT_AUTH_SOURCE)) return step;
31110
31695
  const auth = step.auth;
@@ -33200,12 +33785,12 @@ var SpecWorkspace = class {
33200
33785
  return this.relativizeSpec("interface", stored);
33201
33786
  }
33202
33787
  prepareImplementationForWrite(spec, carry = true) {
33203
- const relative19 = this.relativizeSpec("implementation", spec, carry);
33788
+ const relative20 = this.relativizeSpec("implementation", spec, carry);
33204
33789
  const member = this.getSubprojectPrefix(spec.id);
33205
33790
  const memberRoot = member ? this.resolveSubprojectForNamespace(member) : null;
33206
- if (memberRoot) return childRelativeFilePaths(relative19, this.rootDir, memberRoot);
33791
+ if (memberRoot) return childRelativeFilePaths(relative20, this.rootDir, memberRoot);
33207
33792
  const partDir = this.partHolding(this.getImplementationPath(spec.id, spec.contract))?.part.directory;
33208
- return partDir ? partRelativeFilePaths(relative19, this.rootDir, partDir) : relative19;
33793
+ return partDir ? partRelativeFilePaths(relative20, this.rootDir, partDir) : relative20;
33209
33794
  }
33210
33795
  /**
33211
33796
  * A type written in its stored form — every type position canonical (one
@@ -35756,7 +36341,7 @@ function resolveGit(external, config) {
35756
36341
  const declared = gitDeclarationOf(config, external);
35757
36342
  if (!declared) return { ...external, problem: "git producer unavailable: its declaration names no repository" };
35758
36343
  try {
35759
- const commit2 = external.role === "member" && declared.commit ? declared.commit : resolve7(declared.url, declared.ref);
36344
+ const commit2 = external.role === "member" && declared.commit ? declared.commit : resolve3(declared.url, declared.ref);
35760
36345
  const directory = fetch(declared.url, commit2, declared.dir);
35761
36346
  return { ...external, directory: path23.resolve(directory), commit: commit2, problem: void 0 };
35762
36347
  } catch (e) {
@@ -36185,7 +36770,7 @@ function scaffoldPart(alias, memberDir, contained) {
36185
36770
  }
36186
36771
  function createGitMember(alias, source, description, as) {
36187
36772
  const url = source.git;
36188
- const commit2 = source.commit ?? resolve7(url, source.ref);
36773
+ const commit2 = source.commit ?? resolve3(url, source.ref);
36189
36774
  const dir = fetch(url, commit2, source.dir);
36190
36775
  const kind = holdsProjectContent(dir) ? "project" : "part";
36191
36776
  if (as !== void 0 && as !== kind) {
@@ -37145,7 +37730,7 @@ function advanceMember(alias, ref, commit2, dryRun) {
37145
37730
  const url = member.source.git;
37146
37731
  const from = member.source.commit ?? "";
37147
37732
  const followed = commit2 === void 0 ? ref ?? member.source.ref : void 0;
37148
- const to = commit2 ?? resolve7(url, followed);
37733
+ const to = commit2 ?? resolve3(url, followed);
37149
37734
  const after = specContents(fetch(url, to, member.source.dir));
37150
37735
  const before = specContents(from !== "" ? fetch(url, from, member.source.dir) : null);
37151
37736
  const added = [...after.keys()].filter((f) => !before.has(f)).sort();
@@ -38686,17 +39271,17 @@ function memberHop(agentId) {
38686
39271
  const alias = agentId.slice(0, at);
38687
39272
  const member = graph().nodes.find((n) => n.parent === "" && n.mountAlias === alias);
38688
39273
  if (!member) return null;
38689
- const relative19 = path29.relative(getProjectRoot(), member.directory).split(path29.sep).join("/");
38690
- return { alias, rest: agentId.slice(at + 2), directory: member.directory, relative: relative19 };
39274
+ const relative20 = path29.relative(getProjectRoot(), member.directory).split(path29.sep).join("/");
39275
+ return { alias, rest: agentId.slice(at + 2), directory: member.directory, relative: relative20 };
38691
39276
  }
38692
- function throughMount(brief, alias, relative19) {
38693
- const under = (p) => path29.posix.join(relative19, p);
39277
+ function throughMount(brief, alias, relative20) {
39278
+ const under = (p) => path29.posix.join(relative20, p);
38694
39279
  return {
38695
39280
  ...brief,
38696
39281
  agentId: `${alias}::${brief.agentId}`,
38697
39282
  ownedPaths: brief.ownedPaths.map(under),
38698
39283
  ...brief.readPaths ? { readPaths: brief.readPaths.map(under) } : {},
38699
- root: brief.root ? under(brief.root) : relative19
39284
+ root: brief.root ? under(brief.root) : relative20
38700
39285
  };
38701
39286
  }
38702
39287
  function loadRegistry() {
@@ -38975,19 +39560,19 @@ function commitStagedTree(stagingDir, destDir, roots, backupExisting) {
38975
39560
  const backupDir = path34.join(root, BACKUP_DIR, timestampSlug());
38976
39561
  let backedUp = false;
38977
39562
  for (const rootRel of roots) {
38978
- const relative19 = rootRel === "." ? "" : rootRel;
38979
- const stagedSpecDir = path34.join(stagingDir, relative19, ".wai");
39563
+ const relative20 = rootRel === "." ? "" : rootRel;
39564
+ const stagedSpecDir = path34.join(stagingDir, relative20, ".wai");
38980
39565
  if (!fs20.existsSync(stagedSpecDir)) continue;
38981
- const liveSpecDir = liveSpecDirFor(path34.join(root, relative19));
39566
+ const liveSpecDir = liveSpecDirFor(path34.join(root, relative20));
38982
39567
  if (backupExisting && liveSpecDir && fs20.existsSync(liveSpecDir)) {
38983
- const backupTarget = path34.join(backupDir, relative19, path34.basename(liveSpecDir));
39568
+ const backupTarget = path34.join(backupDir, relative20, path34.basename(liveSpecDir));
38984
39569
  fs20.mkdirSync(path34.dirname(backupTarget), { recursive: true });
38985
39570
  fs20.renameSync(liveSpecDir, backupTarget);
38986
39571
  backedUp = true;
38987
39572
  } else if (liveSpecDir && fs20.existsSync(liveSpecDir)) {
38988
39573
  fs20.rmSync(liveSpecDir, { recursive: true, force: true });
38989
39574
  }
38990
- const target = path34.join(root, relative19, ".wai");
39575
+ const target = path34.join(root, relative20, ".wai");
38991
39576
  fs20.mkdirSync(path34.dirname(target), { recursive: true });
38992
39577
  if (fs20.existsSync(target)) fs20.rmSync(target, { recursive: true, force: true });
38993
39578
  fs20.renameSync(stagedSpecDir, target);
@@ -39266,7 +39851,7 @@ function repairTypeSpellings(apply3) {
39266
39851
  const key = `${kind}:${specId}`;
39267
39852
  let repair = repairs.get(key);
39268
39853
  if (!repair) {
39269
- repair = { specId, kind, rewritten: [], proposals: [], authorNeeded: [] };
39854
+ repair = { specId, kind, rewritten: [], proposals: [], enumProposals: [], authorNeeded: [] };
39270
39855
  repairs.set(key, repair);
39271
39856
  }
39272
39857
  return repair;
@@ -39281,7 +39866,9 @@ function repairTypeSpellings(apply3) {
39281
39866
  if (problem.specId === void 0 || problem.kind === void 0 || !own(problem.specId)) continue;
39282
39867
  const repair = repairOf(problem.specId, problem.kind);
39283
39868
  const proposal = typeProblemIntProposal(problem);
39869
+ const enumProposal = proposal ? null : typeProblemEnumProposal(problem);
39284
39870
  if (proposal) repair.proposals.push(proposal);
39871
+ else if (enumProposal) repair.enumProposals.push(enumProposal);
39285
39872
  else repair.authorNeeded.push(problem);
39286
39873
  }
39287
39874
  const planned = [...repairs.values()];
@@ -39428,7 +40015,7 @@ ${headline} \u2014 approved ${approval.lockedAt} by ${by}:
39428
40015
  return quiet;
39429
40016
  }
39430
40017
  }
39431
- var GATE_UPGRADE_NOTE = "the gate identity gained inputs in stage 5 (members' composition subjects, `composition`; code conformance moved beside the claim)";
40018
+ var GATE_UPGRADE_NOTE = "the gate identity gained inputs in this release (members' composition subjects, `composition`; code conformance moved beside the claim)";
39432
40019
  function memberNotes(approvals) {
39433
40020
  const own = approvals.find((a) => a.key === "");
39434
40021
  const direct = approvals.filter((a) => a.parent === "" && a.as !== "part");
@@ -39510,16 +40097,16 @@ var GLOBAL_GUIDE_BODY = `## wairon \u2014 Spec-Driven Development (optional)
39510
40097
  If \`.wai/specs/\` exists, the wairon SDD workflow is active; otherwise ignore it. wairon does not orchestrate sessions \u2014 it equips yours.
39511
40098
 
39512
40099
  ### In SDD Projects:
39513
- - **Source of Truth**: All architecture lives in the spec tree under \`.wai/specs/\` (L0 System \u2192 L1 Subsystem \u2192 L2 Component \u2192 L3 Interface \u2192 L4 Implementation \u2192 L5 Narrative). Do not edit generated agent config files under \`.claude/agents/\` (rebuilt via \`wairon generate\`).
40100
+ - **Source of Truth**: All architecture lives in the spec tree under \`.wai/specs/\` (L0 System \u2192 L1 Subsystem \u2192 L2 Component \u2192 L3 Interface \u2192 L4 Implementation \u2192 L5 Narrative). Agent files under \`.claude/agents/\` are an opt-in generated view (\`rules.materializeAgentFiles\`) \u2014 never edit them; agents are served as live briefs.
39514
40101
  - **Validation**: Conformance checks (stereotype rules, cycle checks, reference integrity) are run via the \`sdd_validate_tree\` MCP tool.
39515
40102
  - **Operating Rules**:
39516
40103
  1. **Skills**: Use \`sdd-architect\` to design (and \`sdd-implement\`, \`sdd-narrative\`, \`sdd-auditor\`). Refer to project's local guide file for detailed constraints.
39517
40104
  2. **MCP Tools Only**: Author/validate specs *only* via \`sdd_*\` tools (e.g. \`sdd_initialize_system\`, \`sdd_validate_tree\`).
39518
40105
  3. **No CLI Exec**: Do not run the \`wairon\` CLI (human tool). Use MCP tools \`sdd_validate_tree\` and \`sdd_get_status\` instead.
39519
40106
  4. **Delegation**: Delegate implementation via the \`sdd-delegate\` skill \u2014 live agent briefs (\`sdd_get_agent_brief\` MCP tool / \`wairon-agent://\` resource) are composed per call and always current; no session restart. Generated agent files are an optional materialized view of the same topology. User-owned per-agent guidance may live in \`.wai/agents/<agent-id>.md\` (folded into every brief; scaffold via \`wairon agent customize <id>\`).
39520
- 5. **Design First**: Complete spec and pass \`sdd_validate_tree\` before writing code.
40107
+ 5. **Design First, then approval**: Complete the spec and pass \`sdd_validate_tree\`, then ask the human to approve it with \`wairon lock\` before writing code. Approval is the lock record (\`.wai/lock.json\`), not a spec's \`status\` \u2014 the lock does not rewrite statuses; \`sdd_get_status\` reports the approval state.
39521
40108
  6. **Consistency**: Code must match L3 interfaces and L5 narratives exactly. If the spec is wrong, stop and update the spec.
39522
- 7. **Members & References**: A project may contain other wairon projects as **members**, declared in its \`.wai/project.yaml\` \`members\` (create one with \`sdd_add_member\`); each member has its own spec tree, designed from its own root. Reference what another project exports as \`alias::name\` (the alias is a member or a declared external, the name a public name of its L0 export table); an id without \`::\` is local. A leading \`::\`, \`super::\`, member paths and an L1 subsystem carrying \`projectPath\` are deprecated: they still resolve for one release, are reported, and \`wairon doctor --fix\` rewrites them.`;
40109
+ 7. **Members & References**: A project may declare **members** in its \`.wai/project.yaml\` \`members\` (create one with \`sdd_add_member\`). A **part** (the default) stores some of this project's subsystems in another folder or repository: local ids, this project's lock. A **project** member is an independent boundary with its own spec tree and lock, designed from its own root. Reference what another project exports as \`alias::name\` (the alias is a member or a declared external, the name a public name of its L0 export table); an id without \`::\` is local. A leading \`::\`, \`super::\`, member paths and an L1 subsystem carrying \`projectPath\` are deprecated: they still resolve for one release, are reported, and \`wairon doctor --fix\` rewrites them.`;
39523
40110
  var LOCAL_GUIDE_BODY = `## Wairon \u2014 Spec-Driven Development (you are operating inside it)
39524
40111
 
39525
40112
  This project uses **wairon**. System specs live under \`.wai/specs/\` (L0 System \u2192 L1 Subsystem \u2192 L2 Component \u2192 L3 Interface \u2192 L4 Implementation \u2192 Narrative); agent topology and code are derived from it.
@@ -39536,7 +40123,8 @@ This project uses **wairon**. System specs live under \`.wai/specs/\` (L0 System
39536
40123
  - **\`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\`).
39537
40124
  - **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\`).
39538
40125
  - **Do not run the \`wairon\` CLI**: Use \`sdd_validate_tree\` and \`sdd_get_status\` instead of CLI commands.
39539
- - **Handoff to implementation**: Once design is complete and validates cleanly, tell the human: *"The specs are complete and validate. Please run \`wairon lock\` to confirm and freeze them."* No session restart is needed after the lock \u2014 delegate implementation right away via the \`sdd-delegate\` skill.
40126
+ - **Handoff to implementation**: Once design is complete and validates cleanly, tell the human: *"The specs are complete and validate. Please run \`wairon lock\` to approve them, and commit \`.wai/lock.json\`."* The lock records the approval; it does not rewrite spec files or their \`status\`. No session restart is needed after the lock \u2014 delegate implementation right away via the \`sdd-delegate\` skill.
40127
+ - **Approval, not status**: a design is ready to implement when it is APPROVED \u2014 \`sdd_get_status\` reports the approval state (approved, or which specs changed since), and \`wairon lock-check\` gives the same verdict in CI. A spec's \`status\` (draft/design/complete) is authoring readiness only; never wait for it to become \`complete\`.
39540
40128
  - **To implement code**: Delegate via the \`sdd-delegate\` skill: fetch the component's live brief with the \`sdd_get_agent_brief\` MCP tool (or the \`wairon-agent://\` resource) and spawn a subagent from it. Briefs are composed per call from the current spec tree, so they are always current \u2014 never wait for a restart. Implementations must match L3 interfaces and L5 narratives exactly. Generated agent files under \`.claude/agents/\` are an optional materialized view of the same topology \u2014 the live briefs are canonical.
39541
40129
 
39542
40130
  ### Rules (enforced by \`sdd_validate_tree\`)
@@ -40005,7 +40593,7 @@ function getStatusReport(options = {}, decor) {
40005
40593
  }
40006
40594
  const own = options.approvals?.find((a) => a.key === "");
40007
40595
  const hasMembers = (options.approvals ?? []).some((a) => a.key !== "" && a.as !== "part");
40008
- if (own && (own.state !== "never" || hasMembers)) output += `${mark.layer("system", "Approval:")} this project is ${own.state}${own.upgraded ? " (locked under the pre-stage-5 gate identity \u2014 re-lock once)" : ""}
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)" : ""}
40009
40597
  `;
40010
40598
  return { text: output, failed: false };
40011
40599
  }
@@ -41187,7 +41775,7 @@ var KINDS_AT = {
41187
41775
  auth: ["implementation"],
41188
41776
  subsystem: ["component", "type"],
41189
41777
  group: ["type"],
41190
- type: ["interface", "type"]
41778
+ type: ["interface", "type", "implementation"]
41191
41779
  };
41192
41780
  function kindOf(ref) {
41193
41781
  const kinds = KINDS_AT[ref.position] ?? ["component", "interface", "implementation", "type", "subsystem"];
@@ -42267,7 +42855,7 @@ function emptyPlan(family, request) {
42267
42855
  const whole = !(reach2 !== null && !reach2.parentReach);
42268
42856
  return { request, familyRoot: familyNode(family, "").directory, whole, edits: [], refusals: [], changes: [], relock: [], notes: [] };
42269
42857
  }
42270
- var posix3 = (p) => p.split(path43.sep).join("/");
42858
+ var posix5 = (p) => p.split(path43.sep).join("/");
42271
42859
  var refuse = (plan6, code, project, detail) => {
42272
42860
  plan6.refusals.push({ code, project, detail });
42273
42861
  };
@@ -42345,7 +42933,7 @@ function attachEdits(plan6, family, bound2, dir, alias, bindingConfig) {
42345
42933
  edit(plan6, key, "id", `id: ${id} (declared${approved !== void 0 ? ", the id its lock approved" : ""})`, { root: dir, call: "setId", args: [id] });
42346
42934
  }
42347
42935
  if (!attached) plan6.notes.push(`${dir} has no project.yaml of its own \u2014 \`wairon doctor --fix\` writes one declaring "${alias}" as its id`);
42348
- const relPath = posix3(path43.relative(familyNode(family, bound2).directory, dir));
42936
+ const relPath = posix5(path43.relative(familyNode(family, bound2).directory, dir));
42349
42937
  const declaration2 = { path: relPath, ...plan6.request.description !== void 0 ? { description: plan6.request.description } : {} };
42350
42938
  edit(plan6, bound2, "member", `members: ${alias} \u2192 ${relPath} (an existing project, kept as it is)`, { root: familyNode(family, bound2).directory, call: "declareMember", args: [alias, declaration2] });
42351
42939
  for (const requirement of requiredPolicies(bindingConfig)) {
@@ -42392,8 +42980,8 @@ function planDetach(family, bound2, request) {
42392
42980
  if (plan6.request.widen) planWidening(plan6, member, narrow);
42393
42981
  if (plan6.refusals.length > 0) return plan6;
42394
42982
  const boundDir = familyNode(family, bound2).directory;
42395
- const held = memberDeclarationOf(configAt3(boundDir)?.members?.[alias] ?? posix3(path43.relative(boundDir, member.directory)));
42396
- const declared = { ...held, path: memberLocationOf(held) ?? posix3(path43.relative(boundDir, member.directory)) };
42983
+ const held = memberDeclarationOf(configAt3(boundDir)?.members?.[alias] ?? posix5(path43.relative(boundDir, member.directory)));
42984
+ const declared = { ...held, path: memberLocationOf(held) ?? posix5(path43.relative(boundDir, member.directory)) };
42397
42985
  detachEdits(plan6, family, bound2, member, alias, declared, consumers, outward);
42398
42986
  return plan6;
42399
42987
  }
@@ -42433,7 +43021,7 @@ function planWidening(plan6, member, narrow) {
42433
43021
  function detachEdits(plan6, family, bound2, member, alias, declared, consumers, outward) {
42434
43022
  const boundDir = familyNode(family, bound2).directory;
42435
43023
  const relocation = plan6.request.relocation;
42436
- const sourceTo = (from, producer) => relocation ? { hosted: producer.id ?? producer.mountAlias ?? producer.namespace } : { path: posix3(path43.relative(from, producer.directory)) };
43024
+ const sourceTo = (from, producer) => relocation ? { hosted: producer.id ?? producer.mountAlias ?? producer.namespace } : { path: posix5(path43.relative(from, producer.directory)) };
42437
43025
  const where = (source) => source.hosted !== void 0 ? `hosted record ${source.hosted}` : source.path;
42438
43026
  edit(plan6, bound2, "member", `members: ${alias} removed (${declared.path} ${relocation ? `moves to ${relocation.to}` : "stays as it is"})`, { root: boundDir, call: "removeMember", args: [alias] });
42439
43027
  const boundSource = relocation ? sourceTo(boundDir, member) : { path: declared.path };
@@ -42507,7 +43095,7 @@ function adoptEdits(plan6, family, bound2, alias, external, dir, at = dir) {
42507
43095
  edit(plan6, bound2, "pin", `pin ${alias} removed (the family supplies it now)`, { root: boundDir, call: "unpin", args: [alias] });
42508
43096
  edit(plan6, bound2, "external", `externals: ${alias} removed`, { root: boundDir, call: "removeExternal", args: [alias] });
42509
43097
  const declaration2 = {
42510
- path: posix3(path43.relative(boundDir, at)),
43098
+ path: posix5(path43.relative(boundDir, at)),
42511
43099
  ...external.description !== void 0 ? { description: external.description } : {},
42512
43100
  ...external.use && external.use.length > 0 ? { use: external.use } : {}
42513
43101
  };
@@ -42618,7 +43206,7 @@ var KINDS_AT2 = {
42618
43206
  auth: ["implementation"],
42619
43207
  subsystem: ["component", "type"],
42620
43208
  group: ["type"],
42621
- type: ["interface", "type"]
43209
+ type: ["interface", "type", "implementation"]
42622
43210
  };
42623
43211
  function kindOf2(ref) {
42624
43212
  const kinds = KINDS_AT2[ref.position] ?? ["component", "interface", "implementation", "type", "subsystem"];
@@ -42819,7 +43407,7 @@ function emptyPlan3(family, request) {
42819
43407
  const whole = !(reach2 !== null && !reach2.parentReach);
42820
43408
  return { request, familyRoot: familyNode(family, "").directory, whole, edits: [], refusals: [], changes: [], relock: [], notes: [] };
42821
43409
  }
42822
- var posix4 = (p) => p.split(path45.sep).join("/");
43410
+ var posix6 = (p) => p.split(path45.sep).join("/");
42823
43411
  var label6 = (key) => key === "" ? "the top project" : `"${key}"`;
42824
43412
  var refuse3 = (plan6, code, project, detail) => {
42825
43413
  plan6.refusals.push({ code, project, detail });
@@ -42850,7 +43438,7 @@ var KINDS_AT3 = {
42850
43438
  auth: ["implementation"],
42851
43439
  subsystem: ["component", "type"],
42852
43440
  group: ["type"],
42853
- type: ["interface", "type"]
43441
+ type: ["interface", "type", "implementation"]
42854
43442
  };
42855
43443
  function kindOf3(ref) {
42856
43444
  const kinds = KINDS_AT3[ref.position] ?? ["component", "interface", "implementation", "type", "subsystem"];
@@ -43029,7 +43617,7 @@ function respellConsumer(plan6, family, c, own) {
43029
43617
  function repointConsumer(plan6, c, boundNode2) {
43030
43618
  const declared = configAt5(c.node.directory)?.externals?.[c.alias];
43031
43619
  const project = boundNode2.id !== void 0 && boundNode2.id !== c.alias ? boundNode2.id : null;
43032
- const source = declared?.source ? { path: posix4(path45.relative(c.node.directory, boundNode2.directory)) } : null;
43620
+ const source = declared?.source ? { path: posix6(path45.relative(c.node.directory, boundNode2.directory)) } : null;
43033
43621
  edit3(plan6, c.node.namespace, "external", `externals: ${c.alias} names ${boundNode2.id ?? "the top project"}${source ? ` at ${source.path}` : ""}`, { root: c.node.directory, call: "repointExternal", args: [c.alias, project, source] });
43034
43622
  plan6.notes.push(`${label6(c.node.namespace)}'s pin of "${c.alias}" is taken again against ${boundNode2.id ?? "the top project"}: the producer changed, so its lock reads drifted until it is re-locked.`);
43035
43623
  return c;
@@ -43065,8 +43653,8 @@ function planExternalize(family, bound2, request) {
43065
43653
  plan6,
43066
43654
  bound2,
43067
43655
  "move",
43068
- `externalize ${id}: its specs into ${joined ? `the part "${joined.alias}"` : `a part at ${posix4(path45.relative(node.directory, dir))}, declared as "${id}"`} \u2014 a storage move; no reference, export or pin changes`,
43069
- { root: node.directory, call: "externalizeSubsystem", args: [id, posix4(path45.relative(node.directory, dir))] }
43656
+ `externalize ${id}: its specs into ${joined ? `the part "${joined.alias}"` : `a part at ${posix6(path45.relative(node.directory, dir))}, declared as "${id}"`} \u2014 a storage move; no reference, export or pin changes`,
43657
+ { root: node.directory, call: "externalizeSubsystem", args: [id, posix6(path45.relative(node.directory, dir))] }
43070
43658
  );
43071
43659
  plan6.notes.push("Source code is not moved: move the subsystem's code yourself if it should live beside its specs.");
43072
43660
  return plan6;
@@ -43075,7 +43663,7 @@ function planExternalize(family, bound2, request) {
43075
43663
  crossingsBack(plan6, family, bound2, subKey);
43076
43664
  checkSiblings(plan6, family, bound2, id, subKey, table);
43077
43665
  if (plan6.refusals.length > 0) return plan6;
43078
- edit3(plan6, bound2, "move", `externalize ${id} as a project: its specs into a member at ${posix4(path45.relative(node.directory, dir))}, declared as "${id}", then promoted`, { root: node.directory, call: "externalizeSubsystem", args: [id, posix4(path45.relative(node.directory, dir)), "project"] });
43666
+ 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"] });
43079
43667
  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: [[]] });
43080
43668
  plan6.notes.push("Source code is not moved: move the subsystem's code into the member yourself.");
43081
43669
  return plan6;
@@ -44204,7 +44792,7 @@ function errText(message) {
44204
44792
  function errMessage(e) {
44205
44793
  return errText(e instanceof Error ? e.message : String(e));
44206
44794
  }
44207
- var TYPE_REF_GRAMMAR = (what) => `${what}, in the neutral type grammar: a primitive (string, int, float, bool, bytes, date, datetime, duration, any; void only as a whole returns), a defined type id ("billing.Invoice", "alias::name"), list<T>, set<T>, map<K, V> (K is string, int or an enum), T? (T or no value), a union of NAMED types ("Invoice | Receipt"), and on a returns only, async T ("async void"). Every named type must resolve. TypeScript spellings are accepted and respelled \u2014 "Invoice[]" \u2192 list<Invoice>, "Invoice | null" \u2192 Invoice?, boolean \u2192 bool, Record<string, V> \u2192 map<string, V>, Promise<T> \u2192 async T \u2014 and the answer lists each respelling. Refused, naming the replacement: number ("int or float?"), inline object or function types (name a value-object or a signature type), string-literal unions (name an enum), and unions mixing in a primitive or a collection.`;
44795
+ var TYPE_REF_GRAMMAR = (what) => `${what}, in the neutral type grammar: a primitive (string, int, float, bool, bytes, date, datetime, duration, any; void only as a whole returns), a defined type id ("billing.Invoice", "alias::name"), list<T>, set<T>, map<K, V> (K is string, int or an enum), T? (T or no value), a union of NAMED types ("Invoice | Receipt"), and on a returns only, async T ("async void") and result<T, E> (completes with T or fails with E; "async result<void, E>"). Every named type must resolve. TypeScript and Rust spellings are accepted and respelled \u2014 "Invoice[]" \u2192 list<Invoice>, "Invoice | null" \u2192 Invoice?, boolean \u2192 bool, Record<string, V> \u2192 map<string, V>, Promise<T> \u2192 async T, Result<(), E> \u2192 result<void, E> \u2014 and the answer lists each respelling. Refused, naming the replacement: number ("int or float?"), inline object or function types (name a value-object or a signature type), string-literal unions (name an enum), and unions mixing in a primitive or a collection.`;
44208
44796
  function structured(content, data) {
44209
44797
  return { content: [{ type: "text", text: content }], structuredContent: data };
44210
44798
  }
@@ -44304,6 +44892,28 @@ function renderChangeReport(report3) {
44304
44892
  return lines.join("\n");
44305
44893
  }
44306
44894
  var SPEC_KINDS = ["system", "subsystem", "component", "interface", "implementation", "type"];
44895
+ function specKindsHolding(id) {
44896
+ const system = loadSystemSpec();
44897
+ const held = {
44898
+ system: !!system && (id === "system" || id === system.name),
44899
+ subsystem: !!loadSubsystemSpec(id),
44900
+ component: !!loadComponentSpec(id),
44901
+ interface: !!loadInterfaceSpec(id),
44902
+ implementation: !!loadImplementationSpec(id),
44903
+ type: !!loadTypeSpec(id)
44904
+ };
44905
+ return SPEC_KINDS.filter((k) => held[k]);
44906
+ }
44907
+ var PORTAL_TYPE_TRANSPORT = {
44908
+ HTTP_API: "HTTP",
44909
+ gRPC: "gRPC",
44910
+ GraphQL: "GraphQL",
44911
+ MessageBus: "MessageBus",
44912
+ NamedPipe: "NamedPipe",
44913
+ IPC: "IPC",
44914
+ CLI: "CLI",
44915
+ Custom: void 0
44916
+ };
44307
44917
  var STATUS_VALUES = ["draft", "design", "complete"];
44308
44918
  var testsToRevisitOutput = import_zod12.z.array(import_zod12.z.object({
44309
44919
  method: import_zod12.z.string().describe("The contract method this write changed or deleted."),
@@ -44446,7 +45056,7 @@ var memberCreationOutput = {
44446
45056
  profile: import_zod12.z.string().optional()
44447
45057
  })).describe("Requirements no installed version satisfies: nothing was written for them, and the next family run reports each as POLICY_NOT_ADOPTED."),
44448
45058
  projectType: import_zod12.z.string().optional().describe("The profile written as the new member's projectType, when exactly one adopted requirement names one."),
44449
- as: import_zod12.z.string().describe("part | project: what was created (stage 8; a part by default)."),
45059
+ as: import_zod12.z.string().describe("part | project: what was created (a part by default)."),
44450
45060
  storage: import_zod12.z.string().describe("contained | path | git: where its files live."),
44451
45061
  commit: import_zod12.z.string().optional().describe("git: the commit the member was pinned at."),
44452
45062
  ...staleServerOutput
@@ -45028,7 +45638,7 @@ ${renderChangeReport(report3)}`,
45028
45638
  server,
45029
45639
  "sdd_add_member",
45030
45640
  {
45031
- description: "Create a member of the bound project at `source`, in the one members grammar: `services/scheduler` (contained), `../admin` (a sibling checkout), `git@host:acme/payments.git` (a git repository, pinned at its default branch head unless `#<commit>` is given). By default a PART (stage 8): a piece of this project stored in another folder or repository, whose subsystems are this project's own (local ids, the ordinary subsystem rules, this project's lock); it gets only a specs folder, plus a configuration naming this project when it is not contained. With `as: project` an independent PROJECT instead: its project content is created \u2014 a project.yaml declaring the alias as its id, its L0, and the bound project's required packs (composition.requirePolicies) once \u2014 and it is reached only as `alias::name` through its L0 exports. What a member is follows from that content. A git member is not scaffolded; the repository's content decides what it is. Boundaries are earned: create a part unless the piece needs its own team, release, approval or public surface; sdd_promote_member makes one later. Idempotent \u2014 the same call again writes nothing; a different member under the alias is refused.",
45641
+ description: "Create a member of the bound project at `source`, in the one members grammar: `services/scheduler` (contained), `../admin` (a sibling checkout), `git@host:acme/payments.git` (a git repository, pinned at its default branch head unless `#<commit>` is given). By default a PART: a piece of this project stored in another folder or repository, whose subsystems are this project's own (local ids, the ordinary subsystem rules, this project's lock); it gets only a specs folder, plus a configuration naming this project when it is not contained. With `as: project` an independent PROJECT instead: its project content is created \u2014 a project.yaml declaring the alias as its id, its L0, and the bound project's required packs (composition.requirePolicies) once \u2014 and it is reached only as `alias::name` through its L0 exports. What a member is follows from that content. A git member is not scaffolded; the repository's content decides what it is. Boundaries are earned: create a part unless the piece needs its own team, release, approval or public surface; sdd_promote_member makes one later. Idempotent \u2014 the same call again writes nothing; a different member under the alias is refused.",
45032
45642
  inputSchema: {
45033
45643
  alias: import_zod12.z.string().describe("The member's alias ([a-z0-9-_]+); a project's new id"),
45034
45644
  source: import_zod12.z.string().describe("A contained path, a `../` sibling path, or a git URL (optionally `#<commit>`)"),
@@ -45098,7 +45708,7 @@ ${renderChangeReport(report3)}`,
45098
45708
  server,
45099
45709
  "sdd_externalize_subsystem",
45100
45710
  {
45101
- description: "Move an internal subsystem out to the path. By default (stage 8) into a PART \u2014 a storage move: the subsystem's specs move to that directory (a new part, or the existing part there), and nothing else changes: no reference, export, external or lock entry, the same verdict. With `as: project`, into a member PROJECT instead, family-wide and all-or-nothing \u2014 the move followed by a promote: what crosses the new boundary exported and imported, references respelled `alias::name`, the new member's external for this project pinned, and every other family project's references checked to still resolve (a reference that would need a component made public refuses the plan). Source code is not moved. dryRun answers the plan and writes nothing; run it first and show the plan. Never locks: the answer names the projects to re-lock.",
45711
+ description: "Move an internal subsystem out to the path. By default into a PART \u2014 a storage move: the subsystem's specs move to that directory (a new part, or the existing part there), and nothing else changes: no reference, export, external or lock entry, the same verdict. With `as: project`, into a member PROJECT instead, family-wide and all-or-nothing \u2014 the move followed by a promote: what crosses the new boundary exported and imported, references respelled `alias::name`, the new member's external for this project pinned, and every other family project's references checked to still resolve (a reference that would need a component made public refuses the plan). Source code is not moved. dryRun answers the plan and writes nothing; run it first and show the plan. Never locks: the answer names the projects to re-lock.",
45102
45712
  inputSchema: {
45103
45713
  subsystem: import_zod12.z.string().describe("The subsystem to move out"),
45104
45714
  path: import_zod12.z.string().describe("The part's root, relative to the bound project (a new directory, or an existing part's)"),
@@ -45112,7 +45722,7 @@ ${renderChangeReport(report3)}`,
45112
45722
  server,
45113
45723
  "sdd_promote_member",
45114
45724
  {
45115
- description: "Promote a part of the bound project to an independent project in place (stage 8) \u2014 its files stay where they are; it gets an id, an L0 exporting exactly what the rest of the project uses of it, its references across the new boundary respelled `alias::name`, the parent declared as its external, and pins on both sides. Refused (each reason listed) for a part fetched from git, a trusted link that would cross the new boundary, a used component its subsystem does not publish, or an id already taken. All-or-nothing; dryRun answers the plan and writes nothing \u2014 run it first and show the plan. Never locks: the answer names the project to re-lock and the new one to lock.",
45725
+ description: "Promote a part of the bound project to an independent project in place \u2014 its files stay where they are; it gets an id, an L0 exporting exactly what the rest of the project uses of it, its references across the new boundary respelled `alias::name`, the parent declared as its external, and pins on both sides. Refused (each reason listed) for a part fetched from git, a trusted link that would cross the new boundary, a used component its subsystem does not publish, or an id already taken. All-or-nothing; dryRun answers the plan and writes nothing \u2014 run it first and show the plan. Never locks: the answer names the project to re-lock and the new one to lock.",
45116
45726
  inputSchema: {
45117
45727
  alias: import_zod12.z.string().describe("The part's alias"),
45118
45728
  id: import_zod12.z.string().optional().describe("The new project's id (default: the alias)"),
@@ -45125,7 +45735,7 @@ ${renderChangeReport(report3)}`,
45125
45735
  server,
45126
45736
  "sdd_demote_member",
45127
45737
  {
45128
- description: "Demote a project member of the bound project to a part in place (stage 8) \u2014 sdd_promote_member's inverse: its files stay where they are; its own metadata goes to the homes the destination names (as sdd_internalize_member sends it), its L0, lock and pins end, its references across the old boundary become local ids, and its configuration is reduced to naming its parent. Refused while another family project consumes it (a part has no exports \u2014 each consumer listed), for a member fetched from git, or for internalize's reasons. All-or-nothing; dryRun answers the plan and writes nothing \u2014 run it first and show the plan. Never locks.",
45738
+ description: "Demote a project member of the bound project to a part in place \u2014 sdd_promote_member's inverse: its files stay where they are; its own metadata goes to the homes the destination names (as sdd_internalize_member sends it), its L0, lock and pins end, its references across the old boundary become local ids, and its configuration is reduced to naming its parent. Refused while another family project consumes it (a part has no exports \u2014 each consumer listed), for a member fetched from git, or for internalize's reasons. All-or-nothing; dryRun answers the plan and writes nothing \u2014 run it first and show the plan. Never locks.",
45129
45739
  inputSchema: {
45130
45740
  alias: import_zod12.z.string().describe("The project member's alias"),
45131
45741
  destination: import_zod12.z.object({
@@ -45468,7 +46078,7 @@ ${renderChangeReport(report3)}`,
45468
46078
  interface: import_zod12.z.string().describe('The L3 interface ID (e.g. "ibilling-gateway")'),
45469
46079
  endpoints: import_zod12.z.array(import_zod12.z.object({
45470
46080
  method: import_zod12.z.string().describe("The NAME of the interface method to bind (not the HTTP verb)"),
45471
- transport: import_zod12.z.enum(["HTTP", "gRPC", "GraphQL", "MessageBus", "NamedPipe", "IPC", "CLI", "Custom"]).describe("Wire protocol; must match the Portal's portalType"),
46081
+ transport: import_zod12.z.enum(["HTTP", "HTTP_API", "gRPC", "GraphQL", "MessageBus", "NamedPipe", "IPC", "CLI", "Custom"]).describe("Wire protocol; must be the one the Portal's portalType implies. Either vocabulary is accepted: the endpoint's (HTTP) or the portalType's (HTTP_API), stored as HTTP; any other transport is refused, naming the one the portalType implies"),
45472
46082
  httpMethod: import_zod12.z.enum(["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"]).optional().describe("HTTP: verb"),
45473
46083
  path: import_zod12.z.string().optional().describe('HTTP: route path, e.g. "/v1/checkout"'),
45474
46084
  service: import_zod12.z.string().optional().describe("gRPC: service name"),
@@ -45491,8 +46101,10 @@ ${renderChangeReport(report3)}`,
45491
46101
  try {
45492
46102
  const intf = loadInterfaceSpec(interfaceId);
45493
46103
  if (!intf) return errText(`Interface "${interfaceId}" does not exist.`);
45494
- const buildRaw = (e) => {
45495
- switch (e.transport) {
46104
+ const owner = intf.component ? loadComponentSpec(intf.component) : null;
46105
+ const implied = owner?.componentType === "Portal" && owner.portalType ? PORTAL_TYPE_TRANSPORT[owner.portalType] : void 0;
46106
+ const buildRaw = (e, transport) => {
46107
+ switch (transport) {
45496
46108
  case "HTTP":
45497
46109
  return { transport: "HTTP", method: e.httpMethod, path: e.path };
45498
46110
  case "gRPC":
@@ -45516,13 +46128,19 @@ ${renderChangeReport(report3)}`,
45516
46128
  for (const e of endpoints) {
45517
46129
  const m = intf.methods.find((x) => x.name === e.method);
45518
46130
  if (!m) return errText(`Method "${e.method}" not found on interface "${interfaceId}". Define it via sdd_define_interface first.`);
45519
- const parsed = EndpointSchema.safeParse(buildRaw(e));
46131
+ const transport = e.transport === "HTTP_API" ? "HTTP" : e.transport;
46132
+ if (implied && transport !== implied) {
46133
+ return errText(
46134
+ `Method "${e.method}": transport "${e.transport}" does not match ${owner.id}'s portalType ${owner.portalType}, which implies transport "${implied}". Bind it as "${implied}", or change the Portal's portalType.`
46135
+ );
46136
+ }
46137
+ const parsed = EndpointSchema.safeParse(buildRaw(e, transport));
45520
46138
  if (!parsed.success) {
45521
46139
  const detail = parsed.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ");
45522
- return errText(`Invalid ${e.transport} endpoint for method "${e.method}": ${detail}`);
46140
+ return errText(`Invalid ${transport} endpoint for method "${e.method}": ${detail}`);
45523
46141
  }
45524
46142
  methods.push({ name: e.method, endpoint: parsed.data });
45525
- bound2.push(`${e.method}\u2192${e.transport}`);
46143
+ bound2.push(`${e.method}\u2192${transport}`);
45526
46144
  }
45527
46145
  const report3 = updateSpecGated("interface", interfaceId, { methods });
45528
46146
  return structured(
@@ -45783,14 +46401,24 @@ ${renderChangeReport(report3)}`,
45783
46401
  {
45784
46402
  description: `Get/read the parsed JSON contents of a specific spec from the spec tree. Returns structural contents without file system path searching. Pass "methods" to read only the named methods of a contract, an implementation or a type \u2014 a 45-method spec fetched whole to look at one of them is the read side of the same waste a restatement is on the write side; the answer then carries a "partialResult" marker naming what was left out, and must never be re-authored from. For a variant-tagged COMPONENT the result also carries a derived, read-only "variantGuidance" (the variant's base, its implementation guidance, and the same-variant sibling components to implement alike) \u2014 it is resolved from the variant registry, not part of the spec, so never write it back. A contract's methods come back in their STORED form \u2014 a method that takes its signature from a source carries its signatureFrom, not the params the loader resolves into it \u2014 so the answer can be re-authored from as it is; the resolved params, returns and text of each such method come back as a derived, read-only "resolvedSignatures" marker. The structured content carries the same answer with the derived markers KEPT SEPARATE from the stored spec ({kind, id, spec, partialResult?, variantGuidance?, resolvedSignatures?}), so nothing derived can be mistaken for something stored; the text block folds them in as it always has.`,
45785
46403
  inputSchema: {
45786
- kind: import_zod12.z.enum(["system", "subsystem", "component", "interface", "implementation", "type"]).describe("The kind of specification"),
46404
+ kind: import_zod12.z.enum(["system", "subsystem", "component", "interface", "implementation", "type"]).optional().describe("The kind of specification. Omit it to infer the kind from the id: the read is refused, naming the candidate kinds, when the id names specs at more than one level (an interface and an implementation sharing an id, say), or at none"),
45787
46405
  id: import_zod12.z.string().describe('The identifier of the spec to fetch (the L0 system spec is a singleton \u2014 pass the system name or "system")'),
45788
46406
  methods: import_zod12.z.array(import_zod12.z.string().min(1)).optional().describe("Return only these methods by name; every other field of the spec comes back unchanged. Only an interface, an implementation or a type declares methods \u2014 asking for one elsewhere is refused, as is a name the spec does not declare (the answer names the ones it does). Omit it for the whole spec.")
45789
46407
  },
45790
46408
  outputSchema: getSpecOutput
45791
46409
  },
45792
- ({ kind, id, methods }) => {
46410
+ ({ kind: askedKind, id, methods }) => {
45793
46411
  try {
46412
+ let kind;
46413
+ if (askedKind) {
46414
+ kind = askedKind;
46415
+ } else {
46416
+ const holders = specKindsHolding(id);
46417
+ if (holders.length !== 1) {
46418
+ return errText(holders.length === 0 ? `No spec of any kind has the ID "${id}".` : `The ID "${id}" names specs of more than one kind (${holders.join(", ")}). Pass "kind" to choose one.`);
46419
+ }
46420
+ kind = holders[0];
46421
+ }
45794
46422
  let result = null;
45795
46423
  let partial2 = null;
45796
46424
  switch (kind) {
@@ -46343,6 +46971,7 @@ function loadProjectConfig3() {
46343
46971
  keyIn,
46344
46972
  landReference,
46345
46973
  languageOfSourcePath,
46974
+ lineEndingFor,
46346
46975
  listDirectChainedSubprojects,
46347
46976
  listExternals,
46348
46977
  listFiles,
@@ -46462,6 +47091,7 @@ function loadProjectConfig3() {
46462
47091
  resolveSubsystemExports,
46463
47092
  resolveVariantGuidance,
46464
47093
  retireSpecialists,
47094
+ returnTypeOf,
46465
47095
  rewriteReferences,
46466
47096
  runWithHostedLookup,
46467
47097
  runWithProjectBinding,
@@ -46500,6 +47130,7 @@ function loadProjectConfig3() {
46500
47130
  typeIsMany,
46501
47131
  typeMatchesRef,
46502
47132
  typeNamedRefs,
47133
+ typeProblemEnumProposal,
46503
47134
  typeProblemIntProposal,
46504
47135
  typeSourceFiles,
46505
47136
  typeSpellingFacts,
@@ -46513,6 +47144,7 @@ function loadProjectConfig3() {
46513
47144
  validateProject,
46514
47145
  validateProjectConfig,
46515
47146
  validateRegistry,
47147
+ withLineEndings,
46516
47148
  withPack,
46517
47149
  writeFile,
46518
47150
  writeFileIfChanged,