@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/README.md +83 -37
- package/dist/cli/index.js +1313 -430
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +37562 -0
- package/dist/index.js +1033 -401
- package/dist/index.js.map +1 -1
- package/dist/templates/architect.yaml +14 -12
- package/dist/templates/domain-owner.yaml +3 -2
- package/dist/templates/skills/sdd-architect.md +11 -5
- package/dist/templates/skills/sdd-auditor.md +5 -4
- package/dist/templates/skills/sdd-delegate.md +3 -2
- package/dist/templates/skills/sdd-implement.md +4 -4
- package/dist/templates/skills/sdd-narrative.md +1 -1
- package/dist/webapp.html +770 -0
- package/package.json +2 -1
- package/schemas/design-export-1.json +4 -4
package/dist/cli/index.js
CHANGED
|
@@ -65,7 +65,7 @@ var init_defaults = __esm({
|
|
|
65
65
|
copilot: ".github/prompts",
|
|
66
66
|
codex: ".codex/agents"
|
|
67
67
|
};
|
|
68
|
-
WAIRON_VERSION = "5.1.1-dev.
|
|
68
|
+
WAIRON_VERSION = "5.1.1-dev.102";
|
|
69
69
|
GITHUB_REPO = "SYW-Apps/Waffle-AIron";
|
|
70
70
|
SUPPORTED_ALIASES = ["wai"];
|
|
71
71
|
SCAN_EXCLUDE_DIRS = /* @__PURE__ */ new Set([
|
|
@@ -214,6 +214,7 @@ __export(fs_exports, {
|
|
|
214
214
|
getRequestProjectRoot: () => getRequestProjectRoot,
|
|
215
215
|
getWriteReach: () => getWriteReach,
|
|
216
216
|
isOutsideRoot: () => isOutsideRoot,
|
|
217
|
+
lineEndingFor: () => lineEndingFor,
|
|
217
218
|
listFiles: () => listFiles,
|
|
218
219
|
listFilesRecursive: () => listFilesRecursive,
|
|
219
220
|
pathExists: () => pathExists,
|
|
@@ -222,6 +223,7 @@ __export(fs_exports, {
|
|
|
222
223
|
runWithProjectBinding: () => runWithProjectBinding,
|
|
223
224
|
runWithProjectRoot: () => runWithProjectRoot,
|
|
224
225
|
setProjectRoot: () => setProjectRoot,
|
|
226
|
+
withLineEndings: () => withLineEndings,
|
|
225
227
|
writeFile: () => writeFile,
|
|
226
228
|
writeFileIfChanged: () => writeFileIfChanged,
|
|
227
229
|
writeReachDenials: () => writeReachDenials,
|
|
@@ -232,17 +234,118 @@ function ensureDir(dirPath) {
|
|
|
232
234
|
}
|
|
233
235
|
function writeFile(filePath, content) {
|
|
234
236
|
ensureDir(path2.dirname(filePath));
|
|
235
|
-
fs.writeFileSync(filePath, content, "utf-8");
|
|
237
|
+
fs.writeFileSync(filePath, withLineEndings(filePath, content), "utf-8");
|
|
236
238
|
}
|
|
237
239
|
function writeFileIfChanged(filePath, content) {
|
|
240
|
+
const text3 = withLineEndings(filePath, content);
|
|
238
241
|
if (fs.existsSync(filePath)) {
|
|
239
242
|
const existing = fs.readFileSync(filePath, "utf-8");
|
|
240
|
-
if (existing ===
|
|
243
|
+
if (existing === text3) return false;
|
|
241
244
|
}
|
|
242
245
|
ensureDir(path2.dirname(filePath));
|
|
243
|
-
fs.writeFileSync(filePath,
|
|
246
|
+
fs.writeFileSync(filePath, text3, "utf-8");
|
|
244
247
|
return true;
|
|
245
248
|
}
|
|
249
|
+
function endingsOf(text3) {
|
|
250
|
+
const crlf = text3.split("\r\n").length - 1;
|
|
251
|
+
const lf = text3.split("\n").length - 1 - crlf;
|
|
252
|
+
if (crlf === 0 && lf === 0) return null;
|
|
253
|
+
return crlf > lf ? "\r\n" : "\n";
|
|
254
|
+
}
|
|
255
|
+
function sampleOf(file) {
|
|
256
|
+
let fd;
|
|
257
|
+
try {
|
|
258
|
+
fd = fs.openSync(file, "r");
|
|
259
|
+
const buffer = Buffer.alloc(EOL_SAMPLE_BYTES);
|
|
260
|
+
const read2 = fs.readSync(fd, buffer, 0, EOL_SAMPLE_BYTES, 0);
|
|
261
|
+
return buffer.subarray(0, read2).toString("utf-8");
|
|
262
|
+
} catch {
|
|
263
|
+
return null;
|
|
264
|
+
} finally {
|
|
265
|
+
if (fd !== void 0) fs.closeSync(fd);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
function attributeMatches(pattern, relative28) {
|
|
269
|
+
const glob = pattern.replace(/^\//, "");
|
|
270
|
+
const target = glob.includes("/") ? relative28 : path2.posix.basename(relative28);
|
|
271
|
+
const source = glob.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*\//g, "\0").replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]").replace(/\u0000/g, "(?:.*/)?");
|
|
272
|
+
return new RegExp(`^${source}$`).test(target);
|
|
273
|
+
}
|
|
274
|
+
function attributeEol(attributesFile, file) {
|
|
275
|
+
const text3 = readFileOrNull(attributesFile);
|
|
276
|
+
if (text3 === null) return null;
|
|
277
|
+
const relative28 = path2.relative(path2.dirname(attributesFile), file).split(path2.sep).join("/");
|
|
278
|
+
let eol = null;
|
|
279
|
+
for (const raw of text3.split(/\r?\n/)) {
|
|
280
|
+
const line2 = raw.trim();
|
|
281
|
+
if (line2 === "" || line2.startsWith("#")) continue;
|
|
282
|
+
const [pattern, ...attrs] = line2.split(/\s+/);
|
|
283
|
+
if (!attributeMatches(pattern, relative28)) continue;
|
|
284
|
+
for (const attr of attrs) {
|
|
285
|
+
if (attr === "eol=crlf") eol = "\r\n";
|
|
286
|
+
else if (attr === "eol=lf") eol = "\n";
|
|
287
|
+
else if (attr === "-text" || attr === "binary") eol = null;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return eol;
|
|
291
|
+
}
|
|
292
|
+
function conventionFolders(dir) {
|
|
293
|
+
const folders = [];
|
|
294
|
+
let at = path2.resolve(dir);
|
|
295
|
+
for (let depth = 0; depth < 8; depth++) {
|
|
296
|
+
folders.push(at);
|
|
297
|
+
if (fs.existsSync(path2.join(at, ".git")) || fs.existsSync(path2.join(at, ".wai"))) return folders;
|
|
298
|
+
const parent = path2.dirname(at);
|
|
299
|
+
if (parent === at) break;
|
|
300
|
+
at = parent;
|
|
301
|
+
}
|
|
302
|
+
return folders.slice(0, 1);
|
|
303
|
+
}
|
|
304
|
+
function folderEndings(folder, ext, exclude) {
|
|
305
|
+
let names;
|
|
306
|
+
try {
|
|
307
|
+
names = fs.readdirSync(folder);
|
|
308
|
+
} catch {
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
const candidates = names.filter((n) => path2.join(folder, n) !== exclude && /\.(ya?ml|json|md|ts|js|txt)$/i.test(n)).sort((a, b) => Number(path2.extname(b) === ext) - Number(path2.extname(a) === ext)).slice(0, 24);
|
|
312
|
+
let crlf = 0;
|
|
313
|
+
let lf = 0;
|
|
314
|
+
for (const name of candidates) {
|
|
315
|
+
const full = path2.join(folder, name);
|
|
316
|
+
try {
|
|
317
|
+
if (!fs.statSync(full).isFile()) continue;
|
|
318
|
+
} catch {
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
const sample = sampleOf(full);
|
|
322
|
+
const endings = sample === null ? null : endingsOf(sample);
|
|
323
|
+
if (endings === "\r\n") crlf++;
|
|
324
|
+
else if (endings === "\n") lf++;
|
|
325
|
+
}
|
|
326
|
+
if (crlf === lf) return null;
|
|
327
|
+
return crlf > lf ? "\r\n" : "\n";
|
|
328
|
+
}
|
|
329
|
+
function lineEndingFor(filePath) {
|
|
330
|
+
const own = sampleOf(filePath);
|
|
331
|
+
const existing = own === null ? null : endingsOf(own);
|
|
332
|
+
if (existing) return existing;
|
|
333
|
+
const folders = conventionFolders(path2.dirname(filePath));
|
|
334
|
+
for (const folder of folders) {
|
|
335
|
+
const eol = attributeEol(path2.join(folder, ".gitattributes"), filePath);
|
|
336
|
+
if (eol) return eol;
|
|
337
|
+
}
|
|
338
|
+
const ext = path2.extname(filePath);
|
|
339
|
+
for (const folder of folders) {
|
|
340
|
+
const endings = folderEndings(folder, ext, path2.resolve(filePath));
|
|
341
|
+
if (endings) return endings;
|
|
342
|
+
}
|
|
343
|
+
return "\n";
|
|
344
|
+
}
|
|
345
|
+
function withLineEndings(filePath, content) {
|
|
346
|
+
const normalised = content.replace(/\r\n/g, "\n");
|
|
347
|
+
return lineEndingFor(filePath) === "\r\n" ? normalised.replace(/\n/g, "\r\n") : normalised;
|
|
348
|
+
}
|
|
246
349
|
function readFileOrNull(filePath) {
|
|
247
350
|
try {
|
|
248
351
|
return fs.readFileSync(filePath, "utf-8");
|
|
@@ -384,19 +487,154 @@ function aiDirAt(root, ...segments) {
|
|
|
384
487
|
const base = !fs.existsSync(waiPath) && fs.existsSync(waironPath) ? waironPath : waiPath;
|
|
385
488
|
return path2.join(base, ...segments);
|
|
386
489
|
}
|
|
387
|
-
var fs, path2, import_node_async_hooks, projectRootOverride, sameRoot, requestRootStore;
|
|
490
|
+
var fs, path2, import_node_async_hooks, EOL_SAMPLE_BYTES, projectRootOverride, sameRoot, requestRootStore;
|
|
388
491
|
var init_fs = __esm({
|
|
389
492
|
"src/utils/fs.ts"() {
|
|
390
493
|
"use strict";
|
|
391
494
|
fs = __toESM(require("fs"));
|
|
392
495
|
path2 = __toESM(require("path"));
|
|
393
496
|
import_node_async_hooks = require("async_hooks");
|
|
497
|
+
EOL_SAMPLE_BYTES = 64 * 1024;
|
|
394
498
|
projectRootOverride = null;
|
|
395
499
|
sameRoot = (a, b) => path2.resolve(a) === path2.resolve(b);
|
|
396
500
|
requestRootStore = new import_node_async_hooks.AsyncLocalStorage();
|
|
397
501
|
}
|
|
398
502
|
});
|
|
399
503
|
|
|
504
|
+
// src/core/adapters/git-source.ts
|
|
505
|
+
function osUserCache() {
|
|
506
|
+
if (process.platform === "win32") return process.env.LOCALAPPDATA ?? path3.join(os2.homedir(), "AppData", "Local");
|
|
507
|
+
if (process.platform === "darwin") return path3.join(os2.homedir(), "Library", "Caches");
|
|
508
|
+
return process.env.XDG_CACHE_HOME ?? path3.join(os2.homedir(), ".cache");
|
|
509
|
+
}
|
|
510
|
+
function cacheRoot() {
|
|
511
|
+
const setting = process.env.WAIRON_CACHE_DIR ?? "";
|
|
512
|
+
let root = cacheRoots.get(setting);
|
|
513
|
+
if (root === void 0) {
|
|
514
|
+
root = setting !== "" ? path3.resolve(setting) : path3.join(osUserCache(), "wairon");
|
|
515
|
+
cacheRoots.set(setting, root);
|
|
516
|
+
}
|
|
517
|
+
return root;
|
|
518
|
+
}
|
|
519
|
+
function git(args, cwd) {
|
|
520
|
+
return (0, import_child_process.execFileSync)("git", args, {
|
|
521
|
+
cwd,
|
|
522
|
+
encoding: "utf8",
|
|
523
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
524
|
+
env: { ...process.env, GIT_TERMINAL_PROMPT: "0", GIT_ASKPASS: "echo" }
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
function reasonOf(e) {
|
|
528
|
+
const stderr = e?.stderr;
|
|
529
|
+
const text3 = typeof stderr === "string" && stderr.trim() !== "" ? stderr : e instanceof Error ? e.message : String(e);
|
|
530
|
+
return text3.trim().split(/\r?\n/).filter((l) => l.trim() !== "").slice(-1)[0] ?? "git failed";
|
|
531
|
+
}
|
|
532
|
+
function cacheEntryOf(url, commit3) {
|
|
533
|
+
const repo = crypto.createHash("sha256").update(url).digest("hex");
|
|
534
|
+
return path3.join(cacheRoot(), "git", repo, commit3);
|
|
535
|
+
}
|
|
536
|
+
function within(entry, url, commit3, dir) {
|
|
537
|
+
const at = dir ? path3.join(entry, dir) : entry;
|
|
538
|
+
if (!fs2.existsSync(at)) {
|
|
539
|
+
throw new SourceUnavailable(`the commit ${commit3} of ${url} holds no "${dir}"`);
|
|
540
|
+
}
|
|
541
|
+
return at;
|
|
542
|
+
}
|
|
543
|
+
function fetchInto(tmp, url, commit3) {
|
|
544
|
+
git(["init", "-q"], tmp);
|
|
545
|
+
try {
|
|
546
|
+
git(["fetch", "-q", "--depth", "1", url, commit3], tmp);
|
|
547
|
+
} catch {
|
|
548
|
+
git(["fetch", "-q", url, "+refs/heads/*:refs/remotes/source/*", "+refs/tags/*:refs/tags/*"], tmp);
|
|
549
|
+
}
|
|
550
|
+
git(["-c", "advice.detachedHead=false", "checkout", "-q", commit3], tmp);
|
|
551
|
+
fs2.rmSync(path3.join(tmp, ".git"), { recursive: true, force: true });
|
|
552
|
+
}
|
|
553
|
+
function fetch2(url, commit3, dir) {
|
|
554
|
+
const entry = cacheEntryOf(url, commit3);
|
|
555
|
+
if (fs2.existsSync(entry)) return within(entry, url, commit3, dir);
|
|
556
|
+
fs2.mkdirSync(path3.dirname(entry), { recursive: true });
|
|
557
|
+
const tmp = `${entry}.tmp-${process.pid}-${crypto.randomBytes(4).toString("hex")}`;
|
|
558
|
+
fs2.mkdirSync(tmp, { recursive: true });
|
|
559
|
+
try {
|
|
560
|
+
fetchInto(tmp, url, commit3);
|
|
561
|
+
try {
|
|
562
|
+
fs2.renameSync(tmp, entry);
|
|
563
|
+
} catch (e) {
|
|
564
|
+
if (!fs2.existsSync(entry)) throw e;
|
|
565
|
+
fs2.rmSync(tmp, { recursive: true, force: true });
|
|
566
|
+
}
|
|
567
|
+
} catch (e) {
|
|
568
|
+
fs2.rmSync(tmp, { recursive: true, force: true });
|
|
569
|
+
if (fs2.readdirSync(path3.dirname(entry)).length === 0) fs2.rmdirSync(path3.dirname(entry));
|
|
570
|
+
throw e instanceof SourceUnavailable ? e : new SourceUnavailable(`cannot fetch ${commit3} of ${url}: ${reasonOf(e)}`);
|
|
571
|
+
}
|
|
572
|
+
return within(entry, url, commit3, dir);
|
|
573
|
+
}
|
|
574
|
+
function resolve3(url, ref) {
|
|
575
|
+
let listed;
|
|
576
|
+
try {
|
|
577
|
+
listed = git(["ls-remote", url, ref ?? "HEAD"]);
|
|
578
|
+
} catch (e) {
|
|
579
|
+
throw new SourceUnavailable(`cannot reach ${url}: ${reasonOf(e)}`);
|
|
580
|
+
}
|
|
581
|
+
const wanted = commitListed(listed, ref ?? "HEAD");
|
|
582
|
+
if (!wanted) throw new SourceUnavailable(`${url} lists no ref "${ref ?? "HEAD"}"`);
|
|
583
|
+
return wanted;
|
|
584
|
+
}
|
|
585
|
+
function commitListed(listed, ref) {
|
|
586
|
+
const lines = listed.split(/\r?\n/).filter((l) => l.trim() !== "").map((l) => l.split(/\s+/));
|
|
587
|
+
const names = [ref, `refs/heads/${ref}`, `refs/tags/${ref}^{}`, `refs/tags/${ref}`];
|
|
588
|
+
for (const name of names) {
|
|
589
|
+
const line2 = lines.find(([, listedName]) => listedName === name);
|
|
590
|
+
if (line2) return line2[0];
|
|
591
|
+
}
|
|
592
|
+
return void 0;
|
|
593
|
+
}
|
|
594
|
+
function trackedFiles(directory, pathspecs) {
|
|
595
|
+
if (pathspecs.length === 0) return [];
|
|
596
|
+
try {
|
|
597
|
+
return git(["ls-files", "-z", "--", ...pathspecs], directory).split("\0").filter((p) => p !== "");
|
|
598
|
+
} catch {
|
|
599
|
+
return null;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
function head(directory) {
|
|
603
|
+
try {
|
|
604
|
+
return git(["rev-parse", "HEAD"], directory).trim() || null;
|
|
605
|
+
} catch {
|
|
606
|
+
return null;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
function repositoryRoot(directory) {
|
|
610
|
+
let at = path3.resolve(directory);
|
|
611
|
+
for (; ; ) {
|
|
612
|
+
if (fs2.existsSync(path3.join(at, ".git"))) return at;
|
|
613
|
+
const up = path3.dirname(at);
|
|
614
|
+
if (up === at) return null;
|
|
615
|
+
at = up;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
var import_child_process, crypto, fs2, os2, path3, SourceUnavailable, cacheRoots;
|
|
619
|
+
var init_git_source = __esm({
|
|
620
|
+
"src/core/adapters/git-source.ts"() {
|
|
621
|
+
"use strict";
|
|
622
|
+
import_child_process = require("child_process");
|
|
623
|
+
crypto = __toESM(require("crypto"));
|
|
624
|
+
fs2 = __toESM(require("fs"));
|
|
625
|
+
os2 = __toESM(require("os"));
|
|
626
|
+
path3 = __toESM(require("path"));
|
|
627
|
+
init_errors();
|
|
628
|
+
SourceUnavailable = class extends WaironError {
|
|
629
|
+
constructor(message) {
|
|
630
|
+
super(message);
|
|
631
|
+
this.name = "SourceUnavailable";
|
|
632
|
+
}
|
|
633
|
+
};
|
|
634
|
+
cacheRoots = /* @__PURE__ */ new Map();
|
|
635
|
+
}
|
|
636
|
+
});
|
|
637
|
+
|
|
400
638
|
// src/utils/yaml.ts
|
|
401
639
|
function parseYaml(content, sourcePath) {
|
|
402
640
|
try {
|
|
@@ -720,7 +958,7 @@ function activeTargetTypes(config) {
|
|
|
720
958
|
function declaredPackNames(config) {
|
|
721
959
|
const selection = config.profileSelection;
|
|
722
960
|
return [.../* @__PURE__ */ new Set([
|
|
723
|
-
...(config.extensions?.packs ?? []).map((entry) => typeof entry === "string" ?
|
|
961
|
+
...(config.extensions?.packs ?? []).map((entry) => typeof entry === "string" ? path4.basename(entry).replace(PACK_EXT_RE, "") : entry.name),
|
|
724
962
|
...selection?.requiredPackNames ?? [],
|
|
725
963
|
...selection?.defaultPackNames ?? []
|
|
726
964
|
])];
|
|
@@ -999,11 +1237,11 @@ function declaredMembers(config) {
|
|
|
999
1237
|
function isPart(config) {
|
|
1000
1238
|
return config?.partOf !== void 0;
|
|
1001
1239
|
}
|
|
1002
|
-
var
|
|
1240
|
+
var path4, import_zod3, BuiltinTargetConfigSchema, CustomTargetConfigSchema, TargetConfigSchema, NamingRuleConfigSchema, DocumentationRuleConfigSchema, ComplexityRuleConfigSchema, CarriedDebtKindSchema, CarriedFindingSchema, CarriedDebtSchema, ConformanceRuleConfigSchema, DesignDepthSchema, RulesConfigSchema, PathsConfigSchema, PackSelectionSchema, ProfileSelectionSubjectSchema, ProjectProfileSelectionSchema, ExternalSourceSchema, ExternalDeclarationSchema, MemberDeclarationSchema, PartOfSchema, plainString, PackRequirementSchema, CompositionConfigSchema, ProjectConfigSchema, PACK_EXT_RE, CONCRETE_RE, PARTIAL_RE, OPERATOR_RE, text, cv, PROJECT_ID_RE, EXTERNAL_ALIAS_RE, USE_ENTRY_RE, FULL_COMMIT_RE, GIT_URL_RE;
|
|
1003
1241
|
var init_project = __esm({
|
|
1004
1242
|
"src/models/project.ts"() {
|
|
1005
1243
|
"use strict";
|
|
1006
|
-
|
|
1244
|
+
path4 = __toESM(require("path"));
|
|
1007
1245
|
import_zod3 = require("zod");
|
|
1008
1246
|
init_agent();
|
|
1009
1247
|
init_execution();
|
|
@@ -1335,7 +1573,7 @@ var init_project = __esm({
|
|
|
1335
1573
|
/**
|
|
1336
1574
|
* The producer's public names this project imports, so its specs may name
|
|
1337
1575
|
* them bare: `['*']` imports every public name the producer exports to this
|
|
1338
|
-
* project, `[
|
|
1576
|
+
* project, `[shared-error]` only those. Plain strings: a malformed entry is
|
|
1339
1577
|
* REPORTED (the declaration's problem), never unreadable.
|
|
1340
1578
|
*/
|
|
1341
1579
|
use: import_zod3.z.array(import_zod3.z.string()).optional(),
|
|
@@ -1546,13 +1784,13 @@ var init_project = __esm({
|
|
|
1546
1784
|
|
|
1547
1785
|
// src/config/project-config.ts
|
|
1548
1786
|
function waiDirAt(root) {
|
|
1549
|
-
const wai =
|
|
1550
|
-
const legacy =
|
|
1551
|
-
return !
|
|
1787
|
+
const wai = path5.join(root, ".wai");
|
|
1788
|
+
const legacy = path5.join(root, ".wairon");
|
|
1789
|
+
return !fs3.existsSync(wai) && fs3.existsSync(legacy) ? legacy : wai;
|
|
1552
1790
|
}
|
|
1553
1791
|
function projectConfigFsAdapterAt(rootDir) {
|
|
1554
|
-
const root =
|
|
1555
|
-
const file = () =>
|
|
1792
|
+
const root = path5.resolve(rootDir);
|
|
1793
|
+
const file = () => path5.join(waiDirAt(root), "project.yaml");
|
|
1556
1794
|
return {
|
|
1557
1795
|
readDocument() {
|
|
1558
1796
|
return readYamlFile(file());
|
|
@@ -1566,7 +1804,7 @@ function projectConfigFsAdapterAt(rootDir) {
|
|
|
1566
1804
|
writeFile(file(), editDocumentText(text3, document, root));
|
|
1567
1805
|
},
|
|
1568
1806
|
documentExists() {
|
|
1569
|
-
return
|
|
1807
|
+
return fs3.existsSync(file());
|
|
1570
1808
|
},
|
|
1571
1809
|
readText() {
|
|
1572
1810
|
return readFileOrNull(file());
|
|
@@ -1588,8 +1826,9 @@ function storeOver(adapter, root) {
|
|
|
1588
1826
|
if (!checked.success) {
|
|
1589
1827
|
throw new WaironError(`Refusing to write an invalid .wai/project.yaml at ${root}: ${checked.error.message}`);
|
|
1590
1828
|
}
|
|
1591
|
-
const
|
|
1592
|
-
|
|
1829
|
+
const onDisk = currentDocument();
|
|
1830
|
+
const document = config.partOf !== void 0 ? { schemaVersion: config.schemaVersion, partOf: config.partOf } : withoutDefaultedMaterialization(config, onDisk);
|
|
1831
|
+
adapter.writeDocument(overlayKnownFields(ProjectConfigSchema, document, onDisk));
|
|
1593
1832
|
},
|
|
1594
1833
|
exists() {
|
|
1595
1834
|
return adapter.documentExists();
|
|
@@ -1846,9 +2085,67 @@ function asReadable(document) {
|
|
|
1846
2085
|
if (!isPlainObject(document) || document.partOf === void 0) return document;
|
|
1847
2086
|
return { name: "", createdAt: PART_EPOCH, updatedAt: PART_EPOCH, ...document };
|
|
1848
2087
|
}
|
|
2088
|
+
function holdsMaterializedAgentFiles(document, root) {
|
|
2089
|
+
const dirs = new Set(Object.values(DEFAULT_TARGET_DIRS));
|
|
2090
|
+
for (const target of Array.isArray(document.targets) ? document.targets : []) {
|
|
2091
|
+
const outputDir = isPlainObject(target) ? target.outputDir : void 0;
|
|
2092
|
+
if (typeof outputDir === "string") dirs.add(outputDir);
|
|
2093
|
+
}
|
|
2094
|
+
const base = path5.resolve(root);
|
|
2095
|
+
const inside = [];
|
|
2096
|
+
for (const dir of dirs) {
|
|
2097
|
+
const rel2 = path5.relative(base, path5.resolve(base, dir));
|
|
2098
|
+
if (rel2.startsWith("..") || path5.isAbsolute(rel2)) continue;
|
|
2099
|
+
let present = false;
|
|
2100
|
+
try {
|
|
2101
|
+
present = fs3.statSync(path5.join(base, rel2)).isDirectory();
|
|
2102
|
+
} catch {
|
|
2103
|
+
present = false;
|
|
2104
|
+
}
|
|
2105
|
+
if (present) inside.push(rel2 === "" ? "." : rel2.split(path5.sep).join("/"));
|
|
2106
|
+
}
|
|
2107
|
+
if (inside.length === 0) return false;
|
|
2108
|
+
const tracked = trackedFiles(base, inside);
|
|
2109
|
+
const candidates = [];
|
|
2110
|
+
if (tracked === null) {
|
|
2111
|
+
for (const dir of inside) {
|
|
2112
|
+
let names;
|
|
2113
|
+
try {
|
|
2114
|
+
names = fs3.readdirSync(path5.join(base, dir));
|
|
2115
|
+
} catch {
|
|
2116
|
+
continue;
|
|
2117
|
+
}
|
|
2118
|
+
for (const name of names) candidates.push(path5.posix.join(dir, name));
|
|
2119
|
+
}
|
|
2120
|
+
} else {
|
|
2121
|
+
const folders = new Set(inside.map((dir) => path5.posix.normalize(dir)));
|
|
2122
|
+
for (const file of tracked) if (folders.has(path5.posix.dirname(file))) candidates.push(file);
|
|
2123
|
+
}
|
|
2124
|
+
for (const file of candidates) {
|
|
2125
|
+
const head2 = readFileOrNull(path5.join(base, file));
|
|
2126
|
+
if (head2 === null) continue;
|
|
2127
|
+
if (MANAGED_AGENT_NAME.test(path5.posix.basename(file))) return true;
|
|
2128
|
+
if (head2.slice(0, 4096).includes(MANAGED_AGENT_MARKER)) return true;
|
|
2129
|
+
}
|
|
2130
|
+
return false;
|
|
2131
|
+
}
|
|
2132
|
+
function withInferredMaterialization(document, root) {
|
|
2133
|
+
if (!isPlainObject(document) || document.partOf !== void 0) return document;
|
|
2134
|
+
const rules = isPlainObject(document.rules) ? document.rules : void 0;
|
|
2135
|
+
if (rules !== void 0 && hasOwn(rules, "materializeAgentFiles")) return document;
|
|
2136
|
+
if (!holdsMaterializedAgentFiles(document, root)) return document;
|
|
2137
|
+
return { ...document, rules: { ...rules ?? {}, materializeAgentFiles: true } };
|
|
2138
|
+
}
|
|
2139
|
+
function withoutDefaultedMaterialization(config, onDisk) {
|
|
2140
|
+
if (!isPlainObject(onDisk) || config.rules?.materializeAgentFiles !== false) return config;
|
|
2141
|
+
const stated = isPlainObject(onDisk.rules) && hasOwn(onDisk.rules, "materializeAgentFiles");
|
|
2142
|
+
if (stated) return config;
|
|
2143
|
+
const { materializeAgentFiles: _defaulted, ...rules } = config.rules;
|
|
2144
|
+
return { ...config, rules };
|
|
2145
|
+
}
|
|
1849
2146
|
function parseConfig(document, root) {
|
|
1850
2147
|
try {
|
|
1851
|
-
return ProjectConfigSchema.parse(asReadable(document));
|
|
2148
|
+
return ProjectConfigSchema.parse(asReadable(withInferredMaterialization(document, root)));
|
|
1852
2149
|
} catch (e) {
|
|
1853
2150
|
throw new WaironError(
|
|
1854
2151
|
`Invalid .wai/project.yaml: ${e instanceof Error ? e.message : String(e)}
|
|
@@ -2287,15 +2584,15 @@ function indexOver(store, root) {
|
|
|
2287
2584
|
specsDir() {
|
|
2288
2585
|
try {
|
|
2289
2586
|
const declared = store.readField("paths.specsDir");
|
|
2290
|
-
if (declared) return
|
|
2587
|
+
if (declared) return path5.resolve(root, declared);
|
|
2291
2588
|
} catch {
|
|
2292
2589
|
}
|
|
2293
|
-
return
|
|
2590
|
+
return path5.join(waiDirAt(root), "specs");
|
|
2294
2591
|
}
|
|
2295
2592
|
};
|
|
2296
2593
|
}
|
|
2297
2594
|
function projectConfigRepositoryOver(adapter, rootDir) {
|
|
2298
|
-
const root =
|
|
2595
|
+
const root = path5.resolve(rootDir);
|
|
2299
2596
|
const store = storeOver(adapter, root);
|
|
2300
2597
|
const index = indexOver(store, root);
|
|
2301
2598
|
const registry = registryOver(store, root);
|
|
@@ -2389,15 +2686,17 @@ function projectConfigRepositoryOver(adapter, rootDir) {
|
|
|
2389
2686
|
function projectConfigRepositoryAt(rootDir) {
|
|
2390
2687
|
return projectConfigRepositoryOver(projectConfigFsAdapterAt(rootDir), rootDir);
|
|
2391
2688
|
}
|
|
2392
|
-
var
|
|
2689
|
+
var fs3, path5, YAML, import_zod4, KEY_RE, FRESH_TEXT_OPTIONS, PARSE_OPTIONS, DocumentReconciler, PART_EPOCH, MANAGED_AGENT_MARKER, MANAGED_AGENT_NAME, USE_GLOBAL_PACKS_DEFAULT, PROJECT_FIELDS, bound, projectConfigRepository;
|
|
2393
2690
|
var init_project_config = __esm({
|
|
2394
2691
|
"src/config/project-config.ts"() {
|
|
2395
2692
|
"use strict";
|
|
2396
|
-
|
|
2397
|
-
|
|
2693
|
+
fs3 = __toESM(require("fs"));
|
|
2694
|
+
path5 = __toESM(require("path"));
|
|
2398
2695
|
YAML = __toESM(require("yaml"));
|
|
2399
2696
|
import_zod4 = require("zod");
|
|
2400
2697
|
init_fs();
|
|
2698
|
+
init_defaults();
|
|
2699
|
+
init_git_source();
|
|
2401
2700
|
init_yaml();
|
|
2402
2701
|
init_fs();
|
|
2403
2702
|
init_errors();
|
|
@@ -2546,6 +2845,8 @@ var init_project_config = __esm({
|
|
|
2546
2845
|
}
|
|
2547
2846
|
};
|
|
2548
2847
|
PART_EPOCH = "1970-01-01T00:00:00.000Z";
|
|
2848
|
+
MANAGED_AGENT_MARKER = "wairon:managed";
|
|
2849
|
+
MANAGED_AGENT_NAME = /-(owner|implementer|architect)\.(md|ya?ml)$/;
|
|
2549
2850
|
USE_GLOBAL_PACKS_DEFAULT = ProjectConfigSchema.shape.extensions.unwrap().parse({}).useGlobalPacks;
|
|
2550
2851
|
PROJECT_FIELDS = ["id", "members", "externals", "composition", "rules", "extensions", "targets"];
|
|
2551
2852
|
bound = () => {
|
|
@@ -2643,12 +2944,12 @@ var init_project_config = __esm({
|
|
|
2643
2944
|
|
|
2644
2945
|
// src/config/paths.ts
|
|
2645
2946
|
function aiPathsAt(rootDir) {
|
|
2646
|
-
const resolvedRoot =
|
|
2947
|
+
const resolvedRoot = path6.resolve(rootDir);
|
|
2647
2948
|
const aiDirAt2 = (...segments) => {
|
|
2648
|
-
const waiPath =
|
|
2649
|
-
const waironPath =
|
|
2650
|
-
const base = !
|
|
2651
|
-
return
|
|
2949
|
+
const waiPath = path6.join(resolvedRoot, ".wai");
|
|
2950
|
+
const waironPath = path6.join(resolvedRoot, ".wairon");
|
|
2951
|
+
const base = !fs4.existsSync(waiPath) && fs4.existsSync(waironPath) ? waironPath : waiPath;
|
|
2952
|
+
return path6.join(base, ...segments);
|
|
2652
2953
|
};
|
|
2653
2954
|
const specsDir = () => projectConfigRepositoryAt(resolvedRoot).specsDir();
|
|
2654
2955
|
return {
|
|
@@ -2665,12 +2966,12 @@ function aiPathsAt(rootDir) {
|
|
|
2665
2966
|
contextDomainsMd: () => aiDirAt2("context", "domains.md"),
|
|
2666
2967
|
contextWaironGuideMd: () => aiDirAt2("context", "wairon-guide.md"),
|
|
2667
2968
|
specsDir,
|
|
2668
|
-
specsSystem: () =>
|
|
2669
|
-
specsSubsystemsDir: () =>
|
|
2670
|
-
specsComponentsDir: () =>
|
|
2671
|
-
specsInterfacesDir: () =>
|
|
2672
|
-
specsImplementationsDir: () =>
|
|
2673
|
-
specsTypesDir: () =>
|
|
2969
|
+
specsSystem: () => path6.join(specsDir(), ".index.yaml"),
|
|
2970
|
+
specsSubsystemsDir: () => path6.join(specsDir(), "subsystems"),
|
|
2971
|
+
specsComponentsDir: () => path6.join(specsDir(), "components"),
|
|
2972
|
+
specsInterfacesDir: () => path6.join(specsDir(), "interfaces"),
|
|
2973
|
+
specsImplementationsDir: () => path6.join(specsDir(), "implementations"),
|
|
2974
|
+
specsTypesDir: () => path6.join(specsDir(), "types")
|
|
2674
2975
|
};
|
|
2675
2976
|
}
|
|
2676
2977
|
function isProjectInitialized() {
|
|
@@ -2681,12 +2982,12 @@ function assertProjectInitialized() {
|
|
|
2681
2982
|
throw new ProjectNotInitializedError();
|
|
2682
2983
|
}
|
|
2683
2984
|
}
|
|
2684
|
-
var
|
|
2985
|
+
var fs4, path6, AI_PATHS;
|
|
2685
2986
|
var init_paths = __esm({
|
|
2686
2987
|
"src/config/paths.ts"() {
|
|
2687
2988
|
"use strict";
|
|
2688
|
-
|
|
2689
|
-
|
|
2989
|
+
fs4 = __toESM(require("fs"));
|
|
2990
|
+
path6 = __toESM(require("path"));
|
|
2690
2991
|
init_fs();
|
|
2691
2992
|
init_errors();
|
|
2692
2993
|
init_project_config();
|
|
@@ -2755,7 +3056,7 @@ function digestTree(keep, asStored = (impl) => impl) {
|
|
|
2755
3056
|
implementations: kind(loadImplementationSpecs().map(asStored)),
|
|
2756
3057
|
types: kind(loadTypeSpecs())
|
|
2757
3058
|
};
|
|
2758
|
-
const digest3 =
|
|
3059
|
+
const digest3 = crypto2.createHash("sha256").update(canonicalize(tree)).digest("hex");
|
|
2759
3060
|
return { algorithm: CONTENT_ALGORITHM, digest: digest3 };
|
|
2760
3061
|
}
|
|
2761
3062
|
function computeStateId() {
|
|
@@ -2775,11 +3076,11 @@ function computeOwnStateId() {
|
|
|
2775
3076
|
return digestTree((id) => (owners.get(id) ?? "") === "", asStored);
|
|
2776
3077
|
}
|
|
2777
3078
|
function isInside(dir, file) {
|
|
2778
|
-
const rel2 =
|
|
2779
|
-
return rel2 !== "" && !rel2.startsWith("..") && !
|
|
3079
|
+
const rel2 = path7.relative(dir, file);
|
|
3080
|
+
return rel2 !== "" && !rel2.startsWith("..") && !path7.isAbsolute(rel2);
|
|
2780
3081
|
}
|
|
2781
3082
|
function fromPartRoot(impl, root, partDir) {
|
|
2782
|
-
const from = (p) =>
|
|
3083
|
+
const from = (p) => path7.isAbsolute(p) ? p : path7.relative(partDir, path7.resolve(root, p)).split(path7.sep).join("/");
|
|
2783
3084
|
return {
|
|
2784
3085
|
...impl,
|
|
2785
3086
|
...impl.sourcePath ? { sourcePath: from(impl.sourcePath) } : {},
|
|
@@ -2790,13 +3091,13 @@ function fromPartRoot(impl, root, partDir) {
|
|
|
2790
3091
|
function stateIdEquals(a, b) {
|
|
2791
3092
|
return !!a && !!b && a.algorithm === b.algorithm && a.digest === b.digest;
|
|
2792
3093
|
}
|
|
2793
|
-
var
|
|
3094
|
+
var crypto2, path7, CONTENT_ALGORITHM;
|
|
2794
3095
|
var init_statehash = __esm({
|
|
2795
3096
|
"src/core/statehash.ts"() {
|
|
2796
3097
|
"use strict";
|
|
2797
|
-
|
|
3098
|
+
crypto2 = __toESM(require("crypto"));
|
|
2798
3099
|
init_specs2();
|
|
2799
|
-
|
|
3100
|
+
path7 = __toESM(require("path"));
|
|
2800
3101
|
init_fs();
|
|
2801
3102
|
init_canonical_json();
|
|
2802
3103
|
CONTENT_ALGORITHM = "sha256";
|
|
@@ -2817,14 +3118,14 @@ function normalizeApprover(value) {
|
|
|
2817
3118
|
}
|
|
2818
3119
|
function readLockRecord() {
|
|
2819
3120
|
try {
|
|
2820
|
-
return normalizeRecord(JSON.parse(
|
|
3121
|
+
return normalizeRecord(JSON.parse(fs5.readFileSync(lockPath(), "utf8")));
|
|
2821
3122
|
} catch {
|
|
2822
3123
|
return null;
|
|
2823
3124
|
}
|
|
2824
3125
|
}
|
|
2825
3126
|
function readLockRecordAt(root) {
|
|
2826
3127
|
try {
|
|
2827
|
-
return normalizeRecord(JSON.parse(
|
|
3128
|
+
return normalizeRecord(JSON.parse(fs5.readFileSync(aiDirAt(root, "lock.json"), "utf8")));
|
|
2828
3129
|
} catch {
|
|
2829
3130
|
return null;
|
|
2830
3131
|
}
|
|
@@ -2835,10 +3136,10 @@ function normalizeRecord(raw) {
|
|
|
2835
3136
|
}
|
|
2836
3137
|
function writeLockRecord(record2) {
|
|
2837
3138
|
const p = lockPath();
|
|
2838
|
-
|
|
3139
|
+
fs5.mkdirSync(path8.dirname(p), { recursive: true });
|
|
2839
3140
|
const tmp = `${p}.tmp`;
|
|
2840
|
-
|
|
2841
|
-
|
|
3141
|
+
fs5.writeFileSync(tmp, withLineEndings(p, JSON.stringify(withSortedMaps(record2), null, 2) + "\n"));
|
|
3142
|
+
fs5.renameSync(tmp, p);
|
|
2842
3143
|
}
|
|
2843
3144
|
function withSortedMaps(record2) {
|
|
2844
3145
|
const sorted = (m) => {
|
|
@@ -2856,12 +3157,12 @@ function withSortedMaps(record2) {
|
|
|
2856
3157
|
...members ? { members } : {}
|
|
2857
3158
|
};
|
|
2858
3159
|
}
|
|
2859
|
-
var
|
|
3160
|
+
var fs5, path8;
|
|
2860
3161
|
var init_lockfile = __esm({
|
|
2861
3162
|
"src/core/lockfile.ts"() {
|
|
2862
3163
|
"use strict";
|
|
2863
|
-
|
|
2864
|
-
|
|
3164
|
+
fs5 = __toESM(require("fs"));
|
|
3165
|
+
path8 = __toESM(require("path"));
|
|
2865
3166
|
init_fs();
|
|
2866
3167
|
}
|
|
2867
3168
|
});
|
|
@@ -2877,157 +3178,31 @@ function listSpecFiles(specsDir) {
|
|
|
2877
3178
|
return listFilesRecursive(specsDir, SPEC_FILE_EXTENSION);
|
|
2878
3179
|
}
|
|
2879
3180
|
function removeSpecFile(specPath, specsRoot) {
|
|
2880
|
-
if (!
|
|
2881
|
-
|
|
2882
|
-
pruneEmptyDirs(
|
|
3181
|
+
if (!fs6.existsSync(specPath)) return false;
|
|
3182
|
+
fs6.unlinkSync(specPath);
|
|
3183
|
+
pruneEmptyDirs(path9.dirname(specPath), path9.resolve(specsRoot));
|
|
2883
3184
|
return true;
|
|
2884
3185
|
}
|
|
2885
3186
|
function pruneEmptyDirs(dir, specsRoot) {
|
|
2886
3187
|
let at = dir;
|
|
2887
3188
|
while (at !== specsRoot && at.startsWith(specsRoot)) {
|
|
2888
|
-
if (!
|
|
2889
|
-
|
|
2890
|
-
at =
|
|
3189
|
+
if (!fs6.existsSync(at) || fs6.readdirSync(at).length > 0) return;
|
|
3190
|
+
fs6.rmdirSync(at);
|
|
3191
|
+
at = path9.dirname(at);
|
|
2891
3192
|
}
|
|
2892
3193
|
}
|
|
2893
|
-
var
|
|
3194
|
+
var fs6, path9, SPEC_FILE_EXTENSION;
|
|
2894
3195
|
var init_spec_files = __esm({
|
|
2895
3196
|
"src/core/spec-files.ts"() {
|
|
2896
3197
|
"use strict";
|
|
2897
|
-
|
|
2898
|
-
|
|
3198
|
+
fs6 = __toESM(require("fs"));
|
|
3199
|
+
path9 = __toESM(require("path"));
|
|
2899
3200
|
init_fs();
|
|
2900
3201
|
init_yaml();
|
|
2901
3202
|
SPEC_FILE_EXTENSION = ".yaml";
|
|
2902
3203
|
}
|
|
2903
3204
|
});
|
|
2904
3205
|
|
|
2905
|
-
// src/core/adapters/git-source.ts
|
|
2906
|
-
function osUserCache() {
|
|
2907
|
-
if (process.platform === "win32") return process.env.LOCALAPPDATA ?? path9.join(os2.homedir(), "AppData", "Local");
|
|
2908
|
-
if (process.platform === "darwin") return path9.join(os2.homedir(), "Library", "Caches");
|
|
2909
|
-
return process.env.XDG_CACHE_HOME ?? path9.join(os2.homedir(), ".cache");
|
|
2910
|
-
}
|
|
2911
|
-
function cacheRoot() {
|
|
2912
|
-
const setting = process.env.WAIRON_CACHE_DIR ?? "";
|
|
2913
|
-
let root = cacheRoots.get(setting);
|
|
2914
|
-
if (root === void 0) {
|
|
2915
|
-
root = setting !== "" ? path9.resolve(setting) : path9.join(osUserCache(), "wairon");
|
|
2916
|
-
cacheRoots.set(setting, root);
|
|
2917
|
-
}
|
|
2918
|
-
return root;
|
|
2919
|
-
}
|
|
2920
|
-
function git(args, cwd) {
|
|
2921
|
-
return (0, import_child_process.execFileSync)("git", args, {
|
|
2922
|
-
cwd,
|
|
2923
|
-
encoding: "utf8",
|
|
2924
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
2925
|
-
env: { ...process.env, GIT_TERMINAL_PROMPT: "0", GIT_ASKPASS: "echo" }
|
|
2926
|
-
});
|
|
2927
|
-
}
|
|
2928
|
-
function reasonOf(e) {
|
|
2929
|
-
const stderr = e?.stderr;
|
|
2930
|
-
const text3 = typeof stderr === "string" && stderr.trim() !== "" ? stderr : e instanceof Error ? e.message : String(e);
|
|
2931
|
-
return text3.trim().split(/\r?\n/).filter((l) => l.trim() !== "").slice(-1)[0] ?? "git failed";
|
|
2932
|
-
}
|
|
2933
|
-
function cacheEntryOf(url, commit3) {
|
|
2934
|
-
const repo = crypto2.createHash("sha256").update(url).digest("hex");
|
|
2935
|
-
return path9.join(cacheRoot(), "git", repo, commit3);
|
|
2936
|
-
}
|
|
2937
|
-
function within(entry, url, commit3, dir) {
|
|
2938
|
-
const at = dir ? path9.join(entry, dir) : entry;
|
|
2939
|
-
if (!fs6.existsSync(at)) {
|
|
2940
|
-
throw new SourceUnavailable(`the commit ${commit3} of ${url} holds no "${dir}"`);
|
|
2941
|
-
}
|
|
2942
|
-
return at;
|
|
2943
|
-
}
|
|
2944
|
-
function fetchInto(tmp, url, commit3) {
|
|
2945
|
-
git(["init", "-q"], tmp);
|
|
2946
|
-
try {
|
|
2947
|
-
git(["fetch", "-q", "--depth", "1", url, commit3], tmp);
|
|
2948
|
-
} catch {
|
|
2949
|
-
git(["fetch", "-q", url, "+refs/heads/*:refs/remotes/source/*", "+refs/tags/*:refs/tags/*"], tmp);
|
|
2950
|
-
}
|
|
2951
|
-
git(["-c", "advice.detachedHead=false", "checkout", "-q", commit3], tmp);
|
|
2952
|
-
fs6.rmSync(path9.join(tmp, ".git"), { recursive: true, force: true });
|
|
2953
|
-
}
|
|
2954
|
-
function fetch2(url, commit3, dir) {
|
|
2955
|
-
const entry = cacheEntryOf(url, commit3);
|
|
2956
|
-
if (fs6.existsSync(entry)) return within(entry, url, commit3, dir);
|
|
2957
|
-
fs6.mkdirSync(path9.dirname(entry), { recursive: true });
|
|
2958
|
-
const tmp = `${entry}.tmp-${process.pid}-${crypto2.randomBytes(4).toString("hex")}`;
|
|
2959
|
-
fs6.mkdirSync(tmp, { recursive: true });
|
|
2960
|
-
try {
|
|
2961
|
-
fetchInto(tmp, url, commit3);
|
|
2962
|
-
try {
|
|
2963
|
-
fs6.renameSync(tmp, entry);
|
|
2964
|
-
} catch (e) {
|
|
2965
|
-
if (!fs6.existsSync(entry)) throw e;
|
|
2966
|
-
fs6.rmSync(tmp, { recursive: true, force: true });
|
|
2967
|
-
}
|
|
2968
|
-
} catch (e) {
|
|
2969
|
-
fs6.rmSync(tmp, { recursive: true, force: true });
|
|
2970
|
-
if (fs6.readdirSync(path9.dirname(entry)).length === 0) fs6.rmdirSync(path9.dirname(entry));
|
|
2971
|
-
throw e instanceof SourceUnavailable ? e : new SourceUnavailable(`cannot fetch ${commit3} of ${url}: ${reasonOf(e)}`);
|
|
2972
|
-
}
|
|
2973
|
-
return within(entry, url, commit3, dir);
|
|
2974
|
-
}
|
|
2975
|
-
function resolve7(url, ref) {
|
|
2976
|
-
let listed;
|
|
2977
|
-
try {
|
|
2978
|
-
listed = git(["ls-remote", url, ref ?? "HEAD"]);
|
|
2979
|
-
} catch (e) {
|
|
2980
|
-
throw new SourceUnavailable(`cannot reach ${url}: ${reasonOf(e)}`);
|
|
2981
|
-
}
|
|
2982
|
-
const wanted = commitListed(listed, ref ?? "HEAD");
|
|
2983
|
-
if (!wanted) throw new SourceUnavailable(`${url} lists no ref "${ref ?? "HEAD"}"`);
|
|
2984
|
-
return wanted;
|
|
2985
|
-
}
|
|
2986
|
-
function commitListed(listed, ref) {
|
|
2987
|
-
const lines = listed.split(/\r?\n/).filter((l) => l.trim() !== "").map((l) => l.split(/\s+/));
|
|
2988
|
-
const names = [ref, `refs/heads/${ref}`, `refs/tags/${ref}^{}`, `refs/tags/${ref}`];
|
|
2989
|
-
for (const name of names) {
|
|
2990
|
-
const line2 = lines.find(([, listedName]) => listedName === name);
|
|
2991
|
-
if (line2) return line2[0];
|
|
2992
|
-
}
|
|
2993
|
-
return void 0;
|
|
2994
|
-
}
|
|
2995
|
-
function head(directory) {
|
|
2996
|
-
try {
|
|
2997
|
-
return git(["rev-parse", "HEAD"], directory).trim() || null;
|
|
2998
|
-
} catch {
|
|
2999
|
-
return null;
|
|
3000
|
-
}
|
|
3001
|
-
}
|
|
3002
|
-
function repositoryRoot(directory) {
|
|
3003
|
-
let at = path9.resolve(directory);
|
|
3004
|
-
for (; ; ) {
|
|
3005
|
-
if (fs6.existsSync(path9.join(at, ".git"))) return at;
|
|
3006
|
-
const up = path9.dirname(at);
|
|
3007
|
-
if (up === at) return null;
|
|
3008
|
-
at = up;
|
|
3009
|
-
}
|
|
3010
|
-
}
|
|
3011
|
-
var import_child_process, crypto2, fs6, os2, path9, SourceUnavailable, cacheRoots;
|
|
3012
|
-
var init_git_source = __esm({
|
|
3013
|
-
"src/core/adapters/git-source.ts"() {
|
|
3014
|
-
"use strict";
|
|
3015
|
-
import_child_process = require("child_process");
|
|
3016
|
-
crypto2 = __toESM(require("crypto"));
|
|
3017
|
-
fs6 = __toESM(require("fs"));
|
|
3018
|
-
os2 = __toESM(require("os"));
|
|
3019
|
-
path9 = __toESM(require("path"));
|
|
3020
|
-
init_errors();
|
|
3021
|
-
SourceUnavailable = class extends WaironError {
|
|
3022
|
-
constructor(message) {
|
|
3023
|
-
super(message);
|
|
3024
|
-
this.name = "SourceUnavailable";
|
|
3025
|
-
}
|
|
3026
|
-
};
|
|
3027
|
-
cacheRoots = /* @__PURE__ */ new Map();
|
|
3028
|
-
}
|
|
3029
|
-
});
|
|
3030
|
-
|
|
3031
3206
|
// src/models/domain.ts
|
|
3032
3207
|
function createEmptyTopologyConfig() {
|
|
3033
3208
|
return { schemaVersion: "1.0.0", domains: [] };
|
|
@@ -4459,7 +4634,7 @@ function optionalOf(inner2) {
|
|
|
4459
4634
|
return { form: "optional", args: [inner2] };
|
|
4460
4635
|
}
|
|
4461
4636
|
function inner(ctx) {
|
|
4462
|
-
return { ...ctx, top: false, voidAllowed: false, omittable: false };
|
|
4637
|
+
return { ...ctx, top: false, voidAllowed: false, resultAllowed: false, omittable: false };
|
|
4463
4638
|
}
|
|
4464
4639
|
function normalise(node, ctx, notes) {
|
|
4465
4640
|
switch (node.k) {
|
|
@@ -4493,9 +4668,21 @@ function asyncOf(innerNode, ctx, notes, spelled) {
|
|
|
4493
4668
|
if (!ctx.top || !RETURNS_POSITIONS.has(ctx.position)) {
|
|
4494
4669
|
throw new TypeProblemError("TYPE_POSITION_INVALID", `${spelled} may stand only at the top of a returns, not in a ${ctx.position}`);
|
|
4495
4670
|
}
|
|
4496
|
-
const read2 = normalise(innerNode, { ...inner(ctx), voidAllowed: true }, notes);
|
|
4671
|
+
const read2 = normalise(innerNode, { ...inner(ctx), voidAllowed: true, resultAllowed: true }, notes);
|
|
4497
4672
|
return { form: "async", args: [read2] };
|
|
4498
4673
|
}
|
|
4674
|
+
function resultOf(args, ctx, notes, spelled) {
|
|
4675
|
+
if (!ctx.resultAllowed) {
|
|
4676
|
+
throw new TypeProblemError(
|
|
4677
|
+
"TYPE_POSITION_INVALID",
|
|
4678
|
+
`${spelled} may stand only as a whole returns (or under its async), not in a ${ctx.top ? ctx.position : "collection, union, optional or argument"}`,
|
|
4679
|
+
"the success type here (a failure belongs to a method's returns)"
|
|
4680
|
+
);
|
|
4681
|
+
}
|
|
4682
|
+
const ok = normalise(args[0], { ...inner(ctx), voidAllowed: true }, notes);
|
|
4683
|
+
const err = normalise(args[1], inner(ctx), notes);
|
|
4684
|
+
return { form: "result", args: [ok, err] };
|
|
4685
|
+
}
|
|
4499
4686
|
function bareName(name, ctx, notes) {
|
|
4500
4687
|
const lower = name.toLowerCase();
|
|
4501
4688
|
if (NONES.has(name)) {
|
|
@@ -4533,6 +4720,7 @@ function appliedName(name, args, ctx, notes) {
|
|
|
4533
4720
|
throw new TypeProblemError("TYPE_EXPRESSION_INVALID", `"${name}" takes ${alias.arity} type argument${alias.arity === 1 ? "" : "s"}, ${args.length} given`);
|
|
4534
4721
|
}
|
|
4535
4722
|
if (alias.form === "async") return asyncOf(args[0], ctx, notes, `${name}<...>`);
|
|
4723
|
+
if (alias.form === "result") return resultOf(args, ctx, notes, `${name}<...>`);
|
|
4536
4724
|
if (alias.form === "optional") return optionalOf(normalise(args[0], inner(ctx), notes));
|
|
4537
4725
|
const read2 = args.map((a) => normalise(a, inner(ctx), notes));
|
|
4538
4726
|
if (alias.form === "map") assertMapKey(read2[0], args[0]);
|
|
@@ -4606,6 +4794,8 @@ function canonicalTypeText(expr) {
|
|
|
4606
4794
|
return `${expr.form}<${canonicalTypeText(expr.args[0])}>`;
|
|
4607
4795
|
case "map":
|
|
4608
4796
|
return `map<${canonicalTypeText(expr.args[0])}, ${canonicalTypeText(expr.args[1])}>`;
|
|
4797
|
+
case "result":
|
|
4798
|
+
return `result<${canonicalTypeText(expr.args[0])}, ${canonicalTypeText(expr.args[1])}>`;
|
|
4609
4799
|
case "optional": {
|
|
4610
4800
|
const innerText = canonicalTypeText(expr.args[0]);
|
|
4611
4801
|
return expr.args[0].form === "union" ? `(${innerText})?` : `${innerText}?`;
|
|
@@ -4625,11 +4815,12 @@ function typeNamedRefs(expr) {
|
|
|
4625
4815
|
}
|
|
4626
4816
|
function typeIsMany(expr) {
|
|
4627
4817
|
if (expr.form === "list" || expr.form === "set" || expr.form === "map") return true;
|
|
4628
|
-
if (expr.form === "optional" || expr.form === "async") return typeIsMany(expr.args[0]);
|
|
4818
|
+
if (expr.form === "optional" || expr.form === "async" || expr.form === "result") return typeIsMany(expr.args[0]);
|
|
4629
4819
|
return false;
|
|
4630
4820
|
}
|
|
4631
4821
|
function contextFor(position, omittable2, looseNumber = false) {
|
|
4632
|
-
|
|
4822
|
+
const returns = RETURNS_POSITIONS.has(position);
|
|
4823
|
+
return { position, top: true, voidAllowed: returns, resultAllowed: returns, omittable: omittable2, looseNumber };
|
|
4633
4824
|
}
|
|
4634
4825
|
function parseTypePosition(text3, position, omittable2 = false) {
|
|
4635
4826
|
let raw;
|
|
@@ -4754,6 +4945,47 @@ function typeProblemIntProposal(problem) {
|
|
|
4754
4945
|
if (!parse.expression || parse.problem) return null;
|
|
4755
4946
|
return { specId: problem.specId, kind: problem.kind, path: problem.path, written: problem.written, stored: parse.canonical };
|
|
4756
4947
|
}
|
|
4948
|
+
function stringLiteralValue(text3) {
|
|
4949
|
+
const quote = text3[0];
|
|
4950
|
+
if (quote !== "'" && quote !== '"' && quote !== "`" || text3[text3.length - 1] !== quote) return null;
|
|
4951
|
+
return text3.slice(1, -1).replace(/\\(.)/g, "$1");
|
|
4952
|
+
}
|
|
4953
|
+
function kebabOf(name) {
|
|
4954
|
+
return name.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/[\s_.]+/g, "-").toLowerCase();
|
|
4955
|
+
}
|
|
4956
|
+
function typeProblemEnumProposal(problem) {
|
|
4957
|
+
if (problem.code !== "TYPE_FORM_UNSUPPORTED") return null;
|
|
4958
|
+
if (problem.specId === void 0 || problem.kind === void 0 || problem.path === void 0) return null;
|
|
4959
|
+
let raw;
|
|
4960
|
+
try {
|
|
4961
|
+
raw = readRaw(problem.written);
|
|
4962
|
+
} catch (e) {
|
|
4963
|
+
if (e instanceof TypeSyntaxError) return null;
|
|
4964
|
+
throw e;
|
|
4965
|
+
}
|
|
4966
|
+
const members = raw.k === "union" ? raw.members : [raw];
|
|
4967
|
+
const values = [];
|
|
4968
|
+
let optional = false;
|
|
4969
|
+
for (const member of members) {
|
|
4970
|
+
if (member.k === "name" && member.args === void 0 && NONES.has(member.name)) {
|
|
4971
|
+
optional = true;
|
|
4972
|
+
continue;
|
|
4973
|
+
}
|
|
4974
|
+
const value = member.k === "literal" ? stringLiteralValue(member.text) : null;
|
|
4975
|
+
if (value === null) return null;
|
|
4976
|
+
if (!values.includes(value)) values.push(value);
|
|
4977
|
+
}
|
|
4978
|
+
if (values.length === 0) return null;
|
|
4979
|
+
return {
|
|
4980
|
+
specId: problem.specId,
|
|
4981
|
+
kind: problem.kind,
|
|
4982
|
+
path: problem.path,
|
|
4983
|
+
written: problem.written,
|
|
4984
|
+
enumId: kebabOf(positionNameOf(problem.path)),
|
|
4985
|
+
values,
|
|
4986
|
+
optional
|
|
4987
|
+
};
|
|
4988
|
+
}
|
|
4757
4989
|
function emptyTypeSpellingFacts() {
|
|
4758
4990
|
return { respellings: [], problems: [] };
|
|
4759
4991
|
}
|
|
@@ -4812,7 +5044,11 @@ var init_type_grammar = __esm({
|
|
|
4812
5044
|
["HashMap", { form: "map", arity: 2 }],
|
|
4813
5045
|
["Option", { form: "optional", arity: 1 }],
|
|
4814
5046
|
["Optional", { form: "optional", arity: 1 }],
|
|
4815
|
-
["Promise", { form: "async", arity: 1 }]
|
|
5047
|
+
["Promise", { form: "async", arity: 1 }],
|
|
5048
|
+
// The success-or-failure type the mainstream languages with typed failures
|
|
5049
|
+
// share (Rust, Swift, Kotlin, F#); Rust's spelling is its alias.
|
|
5050
|
+
["result", { form: "result", arity: 2 }],
|
|
5051
|
+
["Result", { form: "result", arity: 2 }]
|
|
4816
5052
|
]);
|
|
4817
5053
|
NOT_NEUTRAL = /* @__PURE__ */ new Map([
|
|
4818
5054
|
["uuid", "a named value-object holding a string (e.g. order_id)"],
|
|
@@ -4821,7 +5057,6 @@ var init_type_grammar = __esm({
|
|
|
4821
5057
|
["byte", "int, or bytes for binary data"],
|
|
4822
5058
|
["time", "datetime for an instant, or duration for an elapsed time"],
|
|
4823
5059
|
["tuple", "a named value-object"],
|
|
4824
|
-
["result", "the success type as the returns (failures as findings or throws), or a named value-object"],
|
|
4825
5060
|
["error", "a named value-object"],
|
|
4826
5061
|
["never", "void"],
|
|
4827
5062
|
["box", "the inner type (ownership is an implementation detail)"],
|
|
@@ -4964,9 +5199,9 @@ var init_type_grammar = __esm({
|
|
|
4964
5199
|
}
|
|
4965
5200
|
name += this.ident();
|
|
4966
5201
|
while (this.isP("::") || this.isP(".")) {
|
|
4967
|
-
const
|
|
5202
|
+
const sep19 = this.peek().v;
|
|
4968
5203
|
this.at++;
|
|
4969
|
-
name +=
|
|
5204
|
+
name += sep19 + this.ident();
|
|
4970
5205
|
}
|
|
4971
5206
|
if (!this.isP("<")) return { k: "name", name };
|
|
4972
5207
|
this.at++;
|
|
@@ -4993,6 +5228,10 @@ var init_type_grammar = __esm({
|
|
|
4993
5228
|
const close2 = this.closingParen();
|
|
4994
5229
|
const after = close2 === -1 ? void 0 : this.tokens[close2 + 1];
|
|
4995
5230
|
if (!after || after.t !== "p" || after.v !== "=>") {
|
|
5231
|
+
if (this.isP(")", 1)) {
|
|
5232
|
+
this.at += 2;
|
|
5233
|
+
return { k: "name", name: "void" };
|
|
5234
|
+
}
|
|
4996
5235
|
this.expect("(");
|
|
4997
5236
|
const inner2 = this.type();
|
|
4998
5237
|
this.expect(")");
|
|
@@ -5309,6 +5548,7 @@ function lastSegment(name) {
|
|
|
5309
5548
|
return name.split(/::|\./).filter(Boolean).pop() ?? name;
|
|
5310
5549
|
}
|
|
5311
5550
|
function sameType(code, spec, codeNames) {
|
|
5551
|
+
if (spec.form === "result" && code.form !== "result") return sameType(code, spec.args[0], codeNames);
|
|
5312
5552
|
if (code.form === "primitive" && code.name === "number") return spec.form === "primitive" && (spec.name === "int" || spec.name === "float");
|
|
5313
5553
|
if (code.form === "named" && (spec.form === "named" || spec.form === "applied")) {
|
|
5314
5554
|
if (spec.form === "applied") return false;
|
|
@@ -5345,6 +5585,8 @@ function writeTypeScript(expr) {
|
|
|
5345
5585
|
return expr.args.map(writeTypeScript).join(" | ");
|
|
5346
5586
|
case "async":
|
|
5347
5587
|
return `Promise<${writeTypeScript(expr.args[0])}>`;
|
|
5588
|
+
case "result":
|
|
5589
|
+
return writeTypeScript(expr.args[0]);
|
|
5348
5590
|
case "applied":
|
|
5349
5591
|
return `${expr.name}<${expr.args.map(writeTypeScript).join(", ")}>`;
|
|
5350
5592
|
}
|
|
@@ -5395,6 +5637,7 @@ var init_type_dialects = __esm({
|
|
|
5395
5637
|
"T? \u2192 T | null",
|
|
5396
5638
|
"A | B \u2192 A | B",
|
|
5397
5639
|
"async T \u2192 Promise<T>",
|
|
5640
|
+
"result<T, E> \u2192 T, throwing E on failure (result<void, E> \u2192 void)",
|
|
5398
5641
|
"an enum E \u2192 type E = 'a' | 'b' (a string-literal union alias)",
|
|
5399
5642
|
"a named scalar E holding P \u2192 type E = P (an alias of the primitive)"
|
|
5400
5643
|
];
|
|
@@ -5725,8 +5968,14 @@ function languageOfSourcePath(sourcePath) {
|
|
|
5725
5968
|
function dialectOf(facts) {
|
|
5726
5969
|
return facts.language ? typeDialectFor(facts.language) : null;
|
|
5727
5970
|
}
|
|
5728
|
-
function
|
|
5729
|
-
|
|
5971
|
+
function returnTypeOf(facts, fn) {
|
|
5972
|
+
return ownEntry(facts.returnTypes, fn);
|
|
5973
|
+
}
|
|
5974
|
+
function resolveImport(fromFile, specifier, knownPaths, packages) {
|
|
5975
|
+
if (!specifier.startsWith(".")) {
|
|
5976
|
+
const mapped = ownEntry(packages, specifier);
|
|
5977
|
+
return mapped !== void 0 && knownPaths.has(mapped) ? mapped : void 0;
|
|
5978
|
+
}
|
|
5730
5979
|
const joined = pathKey(path10.posix.normalize(path10.posix.join(path10.posix.dirname(fromFile), specifier)));
|
|
5731
5980
|
const candidates = [
|
|
5732
5981
|
joined,
|
|
@@ -6141,7 +6390,7 @@ var init_design_export = __esm({
|
|
|
6141
6390
|
ExtSchema = import_zod9.z.record(import_zod9.z.unknown());
|
|
6142
6391
|
TypeExpressionSchema = import_zod9.z.lazy(
|
|
6143
6392
|
() => import_zod9.z.object({
|
|
6144
|
-
form: import_zod9.z.enum(["primitive", "named", "list", "set", "map", "optional", "union", "async", "applied"]),
|
|
6393
|
+
form: import_zod9.z.enum(["primitive", "named", "list", "set", "map", "optional", "union", "async", "applied", "result"]),
|
|
6145
6394
|
name: import_zod9.z.string().optional(),
|
|
6146
6395
|
args: import_zod9.z.array(TypeExpressionSchema)
|
|
6147
6396
|
}).passthrough()
|
|
@@ -11609,7 +11858,7 @@ function snapshotPath(root, alias) {
|
|
|
11609
11858
|
function writeAtomically(file, text3) {
|
|
11610
11859
|
fs8.mkdirSync(path13.dirname(file), { recursive: true });
|
|
11611
11860
|
const tmp = `${file}.${process.pid}.tmp`;
|
|
11612
|
-
fs8.writeFileSync(tmp, text3);
|
|
11861
|
+
fs8.writeFileSync(tmp, withLineEndings(file, text3));
|
|
11613
11862
|
fs8.renameSync(tmp, file);
|
|
11614
11863
|
}
|
|
11615
11864
|
function sortedLock(lock) {
|
|
@@ -11784,6 +12033,8 @@ function schemaOfExpression(expr, closureIds) {
|
|
|
11784
12033
|
return { oneOf: expr.args.map((member) => schemaOfExpression(member, closureIds)) };
|
|
11785
12034
|
case "async":
|
|
11786
12035
|
return schemaOfExpression(expr.args[0], closureIds);
|
|
12036
|
+
case "result":
|
|
12037
|
+
return schemaOfExpression(expr.args[0], closureIds);
|
|
11787
12038
|
}
|
|
11788
12039
|
}
|
|
11789
12040
|
function schemaFor(typeRef2, closureIds) {
|
|
@@ -11793,7 +12044,8 @@ function schemaFor(typeRef2, closureIds) {
|
|
|
11793
12044
|
}
|
|
11794
12045
|
function returnsNothing(returns) {
|
|
11795
12046
|
const expr = expressionOf(returns);
|
|
11796
|
-
const
|
|
12047
|
+
const awaited = expr?.form === "async" ? expr.args[0] : expr;
|
|
12048
|
+
const inner2 = awaited?.form === "result" ? awaited.args[0] : awaited;
|
|
11797
12049
|
return inner2?.form === "primitive" && inner2.name === "void";
|
|
11798
12050
|
}
|
|
11799
12051
|
function enumDescription(values) {
|
|
@@ -13789,7 +14041,7 @@ function approvalFinding(entry, config) {
|
|
|
13789
14041
|
return finding(
|
|
13790
14042
|
config,
|
|
13791
14043
|
"MEMBER_DRIFTED",
|
|
13792
|
-
`The member ${named(entry.key)} was approved under an earlier gate identity \u2014 the gate identity gained inputs in
|
|
14044
|
+
`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\`).`,
|
|
13793
14045
|
entry.key
|
|
13794
14046
|
);
|
|
13795
14047
|
}
|
|
@@ -14185,12 +14437,12 @@ function packFiles(dir, prefix = "") {
|
|
|
14185
14437
|
function computePackDigest(packDir) {
|
|
14186
14438
|
const hash = crypto3.createHash("sha256");
|
|
14187
14439
|
const abs = path16.resolve(packDir);
|
|
14188
|
-
let
|
|
14440
|
+
let isFile2 = false;
|
|
14189
14441
|
try {
|
|
14190
|
-
|
|
14442
|
+
isFile2 = fs11.statSync(abs).isFile();
|
|
14191
14443
|
} catch {
|
|
14192
14444
|
}
|
|
14193
|
-
const entries =
|
|
14445
|
+
const entries = isFile2 ? [[path16.basename(abs), abs]] : packFiles(abs).map((rel2) => [rel2, path16.join(abs, rel2)]);
|
|
14194
14446
|
for (const [rel2, full] of entries) {
|
|
14195
14447
|
hash.update(rel2);
|
|
14196
14448
|
hash.update("\0");
|
|
@@ -14683,6 +14935,16 @@ function verifyIntegrity(selection, packDir, where) {
|
|
|
14683
14935
|
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.`
|
|
14684
14936
|
};
|
|
14685
14937
|
}
|
|
14938
|
+
function bundlesSingleFile(bundleDir, storePath16) {
|
|
14939
|
+
try {
|
|
14940
|
+
if (!fs12.statSync(storePath16).isFile()) return false;
|
|
14941
|
+
const entry = packDirEntry(bundleDir);
|
|
14942
|
+
if (!entry || fs12.readdirSync(bundleDir).length !== 1) return false;
|
|
14943
|
+
return fs12.readFileSync(entry).equals(fs12.readFileSync(storePath16));
|
|
14944
|
+
} catch {
|
|
14945
|
+
return false;
|
|
14946
|
+
}
|
|
14947
|
+
}
|
|
14686
14948
|
function detectBundleStoreDrift(selection, bundleDir) {
|
|
14687
14949
|
const bundledVersion = path17.basename(bundleDir);
|
|
14688
14950
|
const installed = resolveInstalledPack(selection.name, bundledVersion);
|
|
@@ -14690,6 +14952,7 @@ function detectBundleStoreDrift(selection, bundleDir) {
|
|
|
14690
14952
|
const bundleDigest = computePackDigest(bundleDir);
|
|
14691
14953
|
const storeDigest = computePackDigest(installed.path);
|
|
14692
14954
|
if (bundleDigest === storeDigest) return void 0;
|
|
14955
|
+
if (bundlesSingleFile(bundleDir, installed.path)) return void 0;
|
|
14693
14956
|
return {
|
|
14694
14957
|
code: "PACK_STORE_DRIFT",
|
|
14695
14958
|
severity: "warning",
|
|
@@ -14911,6 +15174,7 @@ function appendUnique(map, key, value, same) {
|
|
|
14911
15174
|
list2.push(value);
|
|
14912
15175
|
}
|
|
14913
15176
|
function buildCodeIndex(model) {
|
|
15177
|
+
const packages = model.packages ?? {};
|
|
14914
15178
|
const facts = /* @__PURE__ */ new Map();
|
|
14915
15179
|
const paths = /* @__PURE__ */ new Set();
|
|
14916
15180
|
const exactPaths = /* @__PURE__ */ new Set();
|
|
@@ -14943,7 +15207,7 @@ function buildCodeIndex(model) {
|
|
|
14943
15207
|
const f = facts.get(from);
|
|
14944
15208
|
if (f?.status !== "analyzed") continue;
|
|
14945
15209
|
for (const specifier of f.reexports) {
|
|
14946
|
-
const to = resolveImport(from, specifier, paths);
|
|
15210
|
+
const to = resolveImport(from, specifier, paths, packages);
|
|
14947
15211
|
if (to && !out.has(to)) {
|
|
14948
15212
|
out.add(to);
|
|
14949
15213
|
queue.push(to);
|
|
@@ -14955,7 +15219,7 @@ function buildCodeIndex(model) {
|
|
|
14955
15219
|
};
|
|
14956
15220
|
const NO_ORIGIN = /* @__PURE__ */ new Set();
|
|
14957
15221
|
const landingFrom = (scope, specifier) => {
|
|
14958
|
-
const to = resolveImport(scope, specifier, paths);
|
|
15222
|
+
const to = resolveImport(scope, specifier, paths, packages);
|
|
14959
15223
|
return to ? republishedFrom(to) : NO_ORIGIN;
|
|
14960
15224
|
};
|
|
14961
15225
|
const scopeOf3 = (site, from) => {
|
|
@@ -14985,7 +15249,7 @@ function buildCodeIndex(model) {
|
|
|
14985
15249
|
};
|
|
14986
15250
|
const possibleOriginsOf = (site, from) => {
|
|
14987
15251
|
const proven = originOf2(site, from);
|
|
14988
|
-
if (!site.field && !site.constructed && !site.via) return proven;
|
|
15252
|
+
if (!site.field && !site.constructed && !site.via && !site.returnedBy && !site.enclosingClass) return proven;
|
|
14989
15253
|
const resolved = scopeOf3(site, from);
|
|
14990
15254
|
if (!resolved) return proven;
|
|
14991
15255
|
const { path: scope, facts: f } = resolved;
|
|
@@ -14998,24 +15262,59 @@ function buildCodeIndex(model) {
|
|
|
14998
15262
|
if (site.field) for (const typeName of fieldTypesOf(f, site.field)) widenThroughType(typeName);
|
|
14999
15263
|
if (site.constructed) widenThrough(site.constructed, importBindingOf(f, site.constructed)?.from);
|
|
15000
15264
|
if (site.via) for (const typeName of localTypesOf(f, site.via)) widenThroughType(typeName);
|
|
15265
|
+
if (site.enclosingClass !== void 0 && holdsMemberBody(f, site.name, site.enclosingClass)) {
|
|
15266
|
+
for (const candidate of republishedFrom(scope)) out.add(candidate);
|
|
15267
|
+
}
|
|
15268
|
+
if (site.returnedBy !== void 0) {
|
|
15269
|
+
for (const fnFile of originOf2({ name: site.returnedBy, member: false }, scope)) {
|
|
15270
|
+
const at = facts.get(fnFile);
|
|
15271
|
+
if (at?.status !== "analyzed" || at.analysisGrade !== "exact") continue;
|
|
15272
|
+
const typeName = returnTypeOf(at, site.returnedBy);
|
|
15273
|
+
if (typeName === void 0) continue;
|
|
15274
|
+
const specifier = typeBindingOf(at, typeName);
|
|
15275
|
+
const landings = specifier !== void 0 ? landingFrom(fnFile, specifier) : declarationsAt(fnFile).has(typeName) ? republishedFrom(fnFile) : NO_ORIGIN;
|
|
15276
|
+
for (const candidate of landings) {
|
|
15277
|
+
const there = facts.get(candidate);
|
|
15278
|
+
if (there && holdsMemberBody(there, site.name, typeName)) out.add(candidate);
|
|
15279
|
+
}
|
|
15280
|
+
}
|
|
15281
|
+
}
|
|
15001
15282
|
return out;
|
|
15002
15283
|
};
|
|
15284
|
+
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);
|
|
15003
15285
|
const declarationsAt = (path93) => namesAt(declarations, pathKey(path93), (f) => /* @__PURE__ */ new Set([...f.declaredNames, ...f.exportedNames]));
|
|
15004
|
-
const forwardsOf = (start2, name) => {
|
|
15286
|
+
const forwardsOf = (start2, name, container) => {
|
|
15005
15287
|
const out = [];
|
|
15288
|
+
const listed = /* @__PURE__ */ new Set();
|
|
15006
15289
|
const seen = /* @__PURE__ */ new Set();
|
|
15007
|
-
const queue = [{ file: pathKey(start2), name }];
|
|
15290
|
+
const queue = [{ file: pathKey(start2), name, container }];
|
|
15008
15291
|
while (queue.length) {
|
|
15009
15292
|
const at = queue.shift();
|
|
15010
|
-
const key = `${at.file}#${at.name}`;
|
|
15293
|
+
const key = `${at.scope ? "s" : "p"}|${at.container ?? ""}|${at.file}#${at.name}`;
|
|
15011
15294
|
if (seen.has(key)) continue;
|
|
15012
15295
|
seen.add(key);
|
|
15013
|
-
|
|
15014
|
-
|
|
15296
|
+
if (!listed.has(`${at.file}#${at.name}`)) {
|
|
15297
|
+
listed.add(`${at.file}#${at.name}`);
|
|
15298
|
+
out.push({ file: at.file, name: at.name });
|
|
15299
|
+
}
|
|
15300
|
+
const f = facts.get(at.file);
|
|
15301
|
+
if (at.scope) {
|
|
15302
|
+
const binding = f ? importBindingOf(f, at.name) : void 0;
|
|
15303
|
+
if (!binding || binding.namespace) continue;
|
|
15304
|
+
const to = resolveImport(at.file, binding.from, paths, packages);
|
|
15305
|
+
if (to) queue.push({ file: to, name: binding.imported ?? at.name });
|
|
15306
|
+
continue;
|
|
15307
|
+
}
|
|
15308
|
+
for (const alias of f?.exportAliases ?? []) {
|
|
15309
|
+
if (alias.exported !== at.name || alias.container !== at.container) continue;
|
|
15310
|
+
queue.push({ file: at.file, name: alias.local, scope: true });
|
|
15311
|
+
}
|
|
15312
|
+
if (at.container !== void 0) continue;
|
|
15313
|
+
for (const binding of f?.reexportBindings ?? []) {
|
|
15015
15314
|
const star = binding.exported === "*";
|
|
15016
15315
|
if (!star && binding.exported !== at.name) continue;
|
|
15017
15316
|
if (star && at.name === "default") continue;
|
|
15018
|
-
const to = resolveImport(at.file, binding.from, paths);
|
|
15317
|
+
const to = resolveImport(at.file, binding.from, paths, packages);
|
|
15019
15318
|
if (!to) continue;
|
|
15020
15319
|
if (star && !facts.get(to)?.exportedNames.includes(at.name)) continue;
|
|
15021
15320
|
queue.push({ file: to, name: star ? at.name : binding.local });
|
|
@@ -15026,6 +15325,7 @@ function buildCodeIndex(model) {
|
|
|
15026
15325
|
return {
|
|
15027
15326
|
paths,
|
|
15028
15327
|
exactPaths,
|
|
15328
|
+
packages,
|
|
15029
15329
|
factsAt: (path93) => facts.get(pathKey(path93)),
|
|
15030
15330
|
declarationsAt,
|
|
15031
15331
|
anchorsAt: (path93) => namesAt(anchors, pathKey(path93), (f) => /* @__PURE__ */ new Set([...f.declaredNames, ...f.exportedNames, ...f.anchoredNames])),
|
|
@@ -15079,7 +15379,7 @@ function buildImportGraph(index, universe) {
|
|
|
15079
15379
|
const traces = /* @__PURE__ */ new Map();
|
|
15080
15380
|
const resolveAll = (from, specifiers, into) => {
|
|
15081
15381
|
for (const specifier of specifiers) {
|
|
15082
|
-
const to = resolveImport(from, specifier, universe);
|
|
15382
|
+
const to = resolveImport(from, specifier, universe, index.packages);
|
|
15083
15383
|
if (to && to !== from) into.add(to);
|
|
15084
15384
|
}
|
|
15085
15385
|
};
|
|
@@ -15406,7 +15706,7 @@ function judgeOwnIdentity(ctx, identity2) {
|
|
|
15406
15706
|
ctx.addIssue(
|
|
15407
15707
|
"notice",
|
|
15408
15708
|
"PROJECT_ID_DEFAULTED",
|
|
15409
|
-
`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}\` (
|
|
15709
|
+
`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).`
|
|
15410
15710
|
);
|
|
15411
15711
|
}
|
|
15412
15712
|
const ambiguous = problem("ambiguous");
|
|
@@ -15440,7 +15740,7 @@ var init_project_identity = __esm({
|
|
|
15440
15740
|
projectIdentityRule = {
|
|
15441
15741
|
name: "project-identity",
|
|
15442
15742
|
judges: "design",
|
|
15443
|
-
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
|
|
15743
|
+
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.",
|
|
15444
15744
|
codes: [
|
|
15445
15745
|
{ code: "PROJECT_ID_AMBIGUOUS", defaultSeverity: "warning", summary: "Project has no usable id: its name yields no slug, or its declared id breaks the grammar" },
|
|
15446
15746
|
{ code: "PROJECT_ID_CHANGED", defaultSeverity: "error", summary: "Project id differs from the id the lock approved" },
|
|
@@ -15866,10 +16166,10 @@ var init_type_expressions = __esm({
|
|
|
15866
16166
|
typeExpressionsRule = {
|
|
15867
16167
|
name: "type-expressions",
|
|
15868
16168
|
judges: "design",
|
|
15869
|
-
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
|
|
16169
|
+
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).",
|
|
15870
16170
|
codes: [
|
|
15871
|
-
{ code: "TYPE_EXPRESSION_INVALID", defaultSeverity: "
|
|
15872
|
-
{ code: "TYPE_POSITION_INVALID", defaultSeverity: "
|
|
16171
|
+
{ code: "TYPE_EXPRESSION_INVALID", defaultSeverity: "warning", summary: "A structured type position does not parse under the type grammar" },
|
|
16172
|
+
{ 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??" },
|
|
15873
16173
|
{ 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" },
|
|
15874
16174
|
{ 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" },
|
|
15875
16175
|
{ 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" }
|
|
@@ -15879,9 +16179,8 @@ var init_type_expressions = __esm({
|
|
|
15879
16179
|
if (!facts) return;
|
|
15880
16180
|
for (const problem of facts.problems) {
|
|
15881
16181
|
if (problem.specId === void 0 || !ctx.isSpecInScope(problem.specId)) continue;
|
|
15882
|
-
const severity = problem.code === "TYPE_EXPRESSION_INVALID" || problem.code === "TYPE_POSITION_INVALID" ? "error" : "warning";
|
|
15883
16182
|
ctx.addIssue(
|
|
15884
|
-
|
|
16183
|
+
"warning",
|
|
15885
16184
|
problem.code,
|
|
15886
16185
|
problemMessage(problem, problem.specId),
|
|
15887
16186
|
problem.specId,
|
|
@@ -16270,7 +16569,7 @@ var init_export_tables = __esm({
|
|
|
16270
16569
|
exportTablesRule = {
|
|
16271
16570
|
name: "export-tables",
|
|
16272
16571
|
judges: "design",
|
|
16273
|
-
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
|
|
16572
|
+
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.",
|
|
16274
16573
|
codes: [
|
|
16275
16574
|
{ code: "EXPORT_ID_DUPLICATE", defaultSeverity: "error", summary: "One public name bound to different targets by two wildcard re-exports or two explicit entries" },
|
|
16276
16575
|
{ 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)" },
|
|
@@ -16446,7 +16745,7 @@ var init_reference_forms = __esm({
|
|
|
16446
16745
|
referenceFormsRule = {
|
|
16447
16746
|
name: "reference-forms",
|
|
16448
16747
|
judges: "design",
|
|
16449
|
-
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, `
|
|
16748
|
+
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.",
|
|
16450
16749
|
codes: [
|
|
16451
16750
|
{ 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" }
|
|
16452
16751
|
],
|
|
@@ -16483,9 +16782,9 @@ var init_member_declarations = __esm({
|
|
|
16483
16782
|
memberDeclarationsRule = {
|
|
16484
16783
|
name: "member-declarations",
|
|
16485
16784
|
judges: "design",
|
|
16486
|
-
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
|
|
16785
|
+
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.",
|
|
16487
16786
|
codes: [
|
|
16488
|
-
{ code: "DEPRECATED_MOUNT_FORM", defaultSeverity: "notice", summary: "A member declared in a deprecated form \u2014 an L1 subsystem carrying projectPath, or
|
|
16787
|
+
{ 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`." },
|
|
16489
16788
|
{ 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." },
|
|
16490
16789
|
{ 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." }
|
|
16491
16790
|
],
|
|
@@ -16523,7 +16822,7 @@ var init_member_declarations = __esm({
|
|
|
16523
16822
|
ctx.addIssue(
|
|
16524
16823
|
"notice",
|
|
16525
16824
|
"DEPRECATED_MOUNT_FORM",
|
|
16526
|
-
`This project declares the member "${member.alias}" with the long-form \`path\` key, which
|
|
16825
|
+
`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.`
|
|
16527
16826
|
);
|
|
16528
16827
|
}
|
|
16529
16828
|
for (const problem of family.problems) {
|
|
@@ -16567,7 +16866,7 @@ var init_project_boundaries = __esm({
|
|
|
16567
16866
|
projectBoundariesRule = {
|
|
16568
16867
|
name: "project-boundaries",
|
|
16569
16868
|
judges: "design",
|
|
16570
|
-
description: "The one judge of every cross-project reference the bound project writes (
|
|
16869
|
+
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.",
|
|
16571
16870
|
codes: [
|
|
16572
16871
|
{ 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" },
|
|
16573
16872
|
{ 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" },
|
|
@@ -19420,7 +19719,7 @@ var init_pack_reproducibility = __esm({
|
|
|
19420
19719
|
ctx.addIssue(
|
|
19421
19720
|
"warning",
|
|
19422
19721
|
"PACK_SOURCE_UNFETCHABLE",
|
|
19423
|
-
`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.
|
|
19722
|
+
`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.`
|
|
19424
19723
|
);
|
|
19425
19724
|
}
|
|
19426
19725
|
}
|
|
@@ -20522,6 +20821,18 @@ var init_invariant_backing = __esm({
|
|
|
20522
20821
|
});
|
|
20523
20822
|
|
|
20524
20823
|
// src/core/rules/wiring/invariant-references.ts
|
|
20824
|
+
function unknownInvariantMessage(where, ref, types, aliases) {
|
|
20825
|
+
const parts = splitInvariantRef(ref);
|
|
20826
|
+
const qualifier = parts ? parts.typeRef.lastIndexOf("::") : -1;
|
|
20827
|
+
if (parts && qualifier > 0) {
|
|
20828
|
+
const bare = `${parts.typeRef.slice(qualifier + 2)}.${parts.invariantId}`;
|
|
20829
|
+
if (resolveInvariantRef(bare, types, aliases)) {
|
|
20830
|
+
const prefix = parts.typeRef.slice(0, qualifier);
|
|
20831
|
+
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).`;
|
|
20832
|
+
}
|
|
20833
|
+
}
|
|
20834
|
+
return `${where} asserts invariant "${ref}", but no entity declares it (expected "<type-id>.<invariant-id>" naming a declared entry in that entity's invariants).`;
|
|
20835
|
+
}
|
|
20525
20836
|
var invariantReferencesRule;
|
|
20526
20837
|
var init_invariant_references = __esm({
|
|
20527
20838
|
"src/core/rules/wiring/invariant-references.ts"() {
|
|
@@ -20530,7 +20841,7 @@ var init_invariant_references = __esm({
|
|
|
20530
20841
|
invariantReferencesRule = {
|
|
20531
20842
|
name: "invariant-references",
|
|
20532
20843
|
judges: "design",
|
|
20533
|
-
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.",
|
|
20844
|
+
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.",
|
|
20534
20845
|
codes: [
|
|
20535
20846
|
{ code: "UNKNOWN_INVARIANT_REF", defaultSeverity: "error", summary: "A narrative step asserts an invariant that no entity declares" }
|
|
20536
20847
|
],
|
|
@@ -20545,7 +20856,7 @@ var init_invariant_references = __esm({
|
|
|
20545
20856
|
ctx.addIssue(
|
|
20546
20857
|
"error",
|
|
20547
20858
|
"UNKNOWN_INVARIANT_REF",
|
|
20548
|
-
`Step ${step.stepNumber} of "${method2.name}" in implementation "${impl.id}"
|
|
20859
|
+
unknownInvariantMessage(`Step ${step.stepNumber} of "${method2.name}" in implementation "${impl.id}"`, ref, ctx.types, aliases),
|
|
20549
20860
|
impl.id,
|
|
20550
20861
|
isDraftCtx
|
|
20551
20862
|
);
|
|
@@ -20918,13 +21229,13 @@ function invokedAs(code, site, file) {
|
|
|
20918
21229
|
if (!site.member) {
|
|
20919
21230
|
const binding = importBindingOf(facts, site.name);
|
|
20920
21231
|
if (!binding || binding.namespace) return [];
|
|
20921
|
-
const module3 = resolveImport(scope, binding.from, code.paths);
|
|
21232
|
+
const module3 = resolveImport(scope, binding.from, code.paths, code.packages);
|
|
20922
21233
|
return module3 ? code.forwardsOf(module3, binding.imported ?? site.name) : [];
|
|
20923
21234
|
}
|
|
20924
21235
|
if (!site.via) return [];
|
|
20925
21236
|
const receiver = importBindingOf(facts, site.via);
|
|
20926
21237
|
if (!receiver?.namespace) return [];
|
|
20927
|
-
const module2 = resolveImport(scope, receiver.from, code.paths);
|
|
21238
|
+
const module2 = resolveImport(scope, receiver.from, code.paths, code.packages);
|
|
20928
21239
|
return module2 ? code.forwardsOf(module2, site.name) : [];
|
|
20929
21240
|
}
|
|
20930
21241
|
function claimsOf(implMethod) {
|
|
@@ -20944,6 +21255,8 @@ function describeSite(site) {
|
|
|
20944
21255
|
if (site.via) return `${site.via}.${site.name}(\u2026)`;
|
|
20945
21256
|
if (site.field) return `this.${site.field}.${site.name}(\u2026)`;
|
|
20946
21257
|
if (site.constructed) return `new ${site.constructed}(\u2026).${site.name}(\u2026)`;
|
|
21258
|
+
if (site.returnedBy) return `${site.returnedBy}(\u2026).${site.name}(\u2026)`;
|
|
21259
|
+
if (site.enclosingClass) return `this.${site.name}(\u2026)`;
|
|
20947
21260
|
return `<receiver>.${site.name}(\u2026)`;
|
|
20948
21261
|
}
|
|
20949
21262
|
function describeMiss(m) {
|
|
@@ -20964,7 +21277,7 @@ var init_call_conformance = __esm({
|
|
|
20964
21277
|
callConformanceRule = {
|
|
20965
21278
|
name: "call-conformance",
|
|
20966
21279
|
judges: "code",
|
|
20967
|
-
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,
|
|
21280
|
+
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.",
|
|
20968
21281
|
codes: [
|
|
20969
21282
|
{ 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 },
|
|
20970
21283
|
{ 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 },
|
|
@@ -21007,7 +21320,7 @@ var init_call_conformance = __esm({
|
|
|
21007
21320
|
if (!ctx.componentMap.has(claim.component)) continue;
|
|
21008
21321
|
const target = resolveCallTarget(ctx, claim.component, claim.method);
|
|
21009
21322
|
if (target.accepted.has(fnSymbol)) continue;
|
|
21010
|
-
if (isTarget(code.forwardsOf(file, fnSymbol), target)) continue;
|
|
21323
|
+
if (isTarget(code.forwardsOf(file, fnSymbol, implMethod.exportedVia), target)) continue;
|
|
21011
21324
|
const matching = sites.filter((s) => target.accepted.has(s.name));
|
|
21012
21325
|
const ref = `${claim.component}.${claim.method}`;
|
|
21013
21326
|
if (target.files.size === 0) {
|
|
@@ -21050,7 +21363,7 @@ var init_call_conformance = __esm({
|
|
|
21050
21363
|
ctx.addIssue(
|
|
21051
21364
|
"warning",
|
|
21052
21365
|
"CALL_ORIGIN_UNRESOLVED",
|
|
21053
|
-
`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,
|
|
21366
|
+
`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.`,
|
|
21054
21367
|
impl.id,
|
|
21055
21368
|
entry.draftContext,
|
|
21056
21369
|
void 0,
|
|
@@ -21072,7 +21385,9 @@ var init_call_conformance = __esm({
|
|
|
21072
21385
|
reached.set(site, owner);
|
|
21073
21386
|
return owner !== void 0;
|
|
21074
21387
|
};
|
|
21075
|
-
|
|
21388
|
+
const forwardsToModelled = code.forwardsOf(file, fnSymbol, implMethod.exportedVia).some((p) => p.file !== here && modelledAt.get(p.file)?.byName.has(p.name));
|
|
21389
|
+
const conversed = forwardsToModelled ? [] : closedCallSites(code, file, fnSymbol, stopAtModelled, implMethod.exportedVia) ?? [];
|
|
21390
|
+
for (const site of conversed) {
|
|
21076
21391
|
const owner = reached.get(site);
|
|
21077
21392
|
if (!owner || owner.component === component.id) continue;
|
|
21078
21393
|
const ref = `${owner.component}.${owner.method}`;
|
|
@@ -22363,7 +22678,7 @@ var init_export_conformance = __esm({
|
|
|
22363
22678
|
const facts = code.factsAt(file);
|
|
22364
22679
|
if (!facts || facts.status !== "analyzed" || facts.analysisGrade !== "exact") continue;
|
|
22365
22680
|
for (const binding of facts.reexportBindings ?? []) {
|
|
22366
|
-
const target = resolveImport(file, binding.from, code.paths);
|
|
22681
|
+
const target = resolveImport(file, binding.from, code.paths, code.packages);
|
|
22367
22682
|
const methods = target ? typeMethodsAt.get(target) : void 0;
|
|
22368
22683
|
if (!methods) continue;
|
|
22369
22684
|
if (binding.local === "*") {
|
|
@@ -22375,7 +22690,7 @@ var init_export_conformance = __esm({
|
|
|
22375
22690
|
}
|
|
22376
22691
|
const takenFrom = /* @__PURE__ */ new Map();
|
|
22377
22692
|
const take = (consumer, writtenIn, specifier, names) => {
|
|
22378
|
-
const target = resolveImport(writtenIn, specifier, code.paths);
|
|
22693
|
+
const target = resolveImport(writtenIn, specifier, code.paths, code.packages);
|
|
22379
22694
|
if (!target) return;
|
|
22380
22695
|
let byName = takenFrom.get(target);
|
|
22381
22696
|
if (!byName) takenFrom.set(target, byName = /* @__PURE__ */ new Map());
|
|
@@ -24203,6 +24518,9 @@ function walkExact(ts, sourceText, fileName) {
|
|
|
24203
24518
|
const reexports = /* @__PURE__ */ new Set();
|
|
24204
24519
|
const starExports = [];
|
|
24205
24520
|
const namedReexports = [];
|
|
24521
|
+
const exportAliases = [];
|
|
24522
|
+
const propertyAliases = [];
|
|
24523
|
+
const returnTypes = /* @__PURE__ */ new Map();
|
|
24206
24524
|
const complexity = /* @__PURE__ */ new Map();
|
|
24207
24525
|
const calls = /* @__PURE__ */ new Map();
|
|
24208
24526
|
const bodies = /* @__PURE__ */ new Map();
|
|
@@ -24219,7 +24537,7 @@ function walkExact(ts, sourceText, fileName) {
|
|
|
24219
24537
|
const mutableBindings = /* @__PURE__ */ new Set();
|
|
24220
24538
|
const schemaConstants = /* @__PURE__ */ new Map();
|
|
24221
24539
|
const derivedAliases = [];
|
|
24222
|
-
const receiverKey = (site) => site.via ?? (site.field ? `this.${site.field}` : site.constructed ? `new ${site.constructed}` : "");
|
|
24540
|
+
const receiverKey = (site) => site.via ?? (site.field ? `this.${site.field}` : site.constructed ? `new ${site.constructed}` : site.returnedBy ? `${site.returnedBy}()` : site.enclosingClass ? `this:${site.enclosingClass}` : "");
|
|
24223
24541
|
const siteKey = (site) => `${site.member ? "m" : "b"}:${receiverKey(site)}:${site.name}`;
|
|
24224
24542
|
const addBindingNames = (name) => {
|
|
24225
24543
|
if (ts.isIdentifier(name)) declared.add(name.text);
|
|
@@ -24327,6 +24645,44 @@ function walkExact(ts, sourceText, fileName) {
|
|
|
24327
24645
|
}
|
|
24328
24646
|
return void 0;
|
|
24329
24647
|
};
|
|
24648
|
+
const enclosingClassOf = (node) => {
|
|
24649
|
+
let at = node.parent;
|
|
24650
|
+
while (at) {
|
|
24651
|
+
if (ts.isFunctionExpression(at) || ts.isFunctionDeclaration(at) || ts.isClassExpression(at)) return void 0;
|
|
24652
|
+
if ((ts.isMethodDeclaration(at) || ts.isGetAccessorDeclaration(at) || ts.isSetAccessorDeclaration(at)) && at.parent && ts.isObjectLiteralExpression(at.parent)) return void 0;
|
|
24653
|
+
if (ts.isClassDeclaration(at)) {
|
|
24654
|
+
return at.name && at.parent && ts.isSourceFile(at.parent) ? at.name.text : void 0;
|
|
24655
|
+
}
|
|
24656
|
+
at = at.parent;
|
|
24657
|
+
}
|
|
24658
|
+
return void 0;
|
|
24659
|
+
};
|
|
24660
|
+
const returnedTypeOf = (fn) => {
|
|
24661
|
+
const annotated = fn.type;
|
|
24662
|
+
if (annotated) {
|
|
24663
|
+
if (!ts.isTypeReferenceNode(annotated)) return void 0;
|
|
24664
|
+
if (ts.isIdentifier(annotated.typeName) && annotated.typeName.text === "Promise" && annotated.typeArguments?.length === 1) {
|
|
24665
|
+
const inner2 = annotated.typeArguments[0];
|
|
24666
|
+
return ts.isTypeReferenceNode(inner2) && !inner2.typeArguments ? typeReferenceName(inner2) : void 0;
|
|
24667
|
+
}
|
|
24668
|
+
return annotated.typeArguments ? void 0 : typeReferenceName(annotated);
|
|
24669
|
+
}
|
|
24670
|
+
const constructedClass = (expression) => {
|
|
24671
|
+
let node = expression;
|
|
24672
|
+
while (node && (ts.isParenthesizedExpression(node) || ts.isAsExpression(node) || ts.isSatisfiesExpression(node))) node = node.expression;
|
|
24673
|
+
return node && ts.isNewExpression(node) && ts.isIdentifier(node.expression) ? node.expression.text : void 0;
|
|
24674
|
+
};
|
|
24675
|
+
if (!fn.body) return void 0;
|
|
24676
|
+
if (!ts.isBlock(fn.body)) return constructedClass(fn.body);
|
|
24677
|
+
const returns = [];
|
|
24678
|
+
const collect2 = (node) => {
|
|
24679
|
+
if (ts.isFunctionLike(node)) return;
|
|
24680
|
+
if (ts.isReturnStatement(node)) returns.push(node);
|
|
24681
|
+
ts.forEachChild(node, collect2);
|
|
24682
|
+
};
|
|
24683
|
+
ts.forEachChild(fn.body, collect2);
|
|
24684
|
+
return returns.length === 1 ? constructedClass(returns[0].expression) : void 0;
|
|
24685
|
+
};
|
|
24330
24686
|
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);
|
|
24331
24687
|
const containerOf = (owner) => {
|
|
24332
24688
|
if (!owner) return void 0;
|
|
@@ -24388,6 +24744,11 @@ function walkExact(ts, sourceText, fileName) {
|
|
|
24388
24744
|
addSite({ name, member: true, field: receiver.name.text });
|
|
24389
24745
|
} else if (ts.isNewExpression(receiver) && ts.isIdentifier(receiver.expression)) {
|
|
24390
24746
|
addSite({ name, member: true, constructed: receiver.expression.text });
|
|
24747
|
+
} else if (ts.isCallExpression(receiver) && ts.isIdentifier(receiver.expression)) {
|
|
24748
|
+
addSite({ name, member: true, returnedBy: receiver.expression.text });
|
|
24749
|
+
} else if (receiver.kind === ts.SyntaxKind.ThisKeyword) {
|
|
24750
|
+
const owner = enclosingClassOf(node);
|
|
24751
|
+
addSite(owner !== void 0 ? { name, member: true, enclosingClass: owner } : { name, member: true });
|
|
24391
24752
|
} else addSite({ name, member: true });
|
|
24392
24753
|
}
|
|
24393
24754
|
}
|
|
@@ -24514,6 +24875,8 @@ function walkExact(ts, sourceText, fileName) {
|
|
|
24514
24875
|
signatures.push(declaredParameters(node));
|
|
24515
24876
|
functionParams.set(fnName, signatures);
|
|
24516
24877
|
if (completesLater(node)) asyncFunctions.push(fnName);
|
|
24878
|
+
const returned = returnedTypeOf(node) ?? null;
|
|
24879
|
+
returnTypes.set(fnName, returnTypes.has(fnName) && returnTypes.get(fnName) !== returned ? null : returned);
|
|
24517
24880
|
const routes = collectRoutes(node);
|
|
24518
24881
|
if (routes.size > 0) {
|
|
24519
24882
|
const known = functionRoutes.get(fnName) ?? /* @__PURE__ */ new Map();
|
|
@@ -24550,6 +24913,23 @@ function walkExact(ts, sourceText, fileName) {
|
|
|
24550
24913
|
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer && !schemaConstants.has(node.name.text)) {
|
|
24551
24914
|
schemaConstants.set(node.name.text, node.initializer);
|
|
24552
24915
|
}
|
|
24916
|
+
if (isTopLevelVariable(node) && node.initializer) {
|
|
24917
|
+
let literal = node.initializer;
|
|
24918
|
+
while (ts.isAsExpression(literal) || ts.isSatisfiesExpression(literal) || ts.isParenthesizedExpression(literal)) {
|
|
24919
|
+
literal = literal.expression;
|
|
24920
|
+
}
|
|
24921
|
+
if (ts.isObjectLiteralExpression(literal)) {
|
|
24922
|
+
const container = node.name.text;
|
|
24923
|
+
for (const property of literal.properties) {
|
|
24924
|
+
if (ts.isShorthandPropertyAssignment(property)) {
|
|
24925
|
+
propertyAliases.push({ exported: property.name.text, local: property.name.text, container });
|
|
24926
|
+
} else if (ts.isPropertyAssignment(property) && ts.isIdentifier(property.initializer)) {
|
|
24927
|
+
const key = propertyNameText(property.name);
|
|
24928
|
+
if (key) propertyAliases.push({ exported: key, local: property.initializer.text, container });
|
|
24929
|
+
}
|
|
24930
|
+
}
|
|
24931
|
+
}
|
|
24932
|
+
}
|
|
24553
24933
|
if (ts.isFunctionDeclaration(node) || ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node) || ts.isEnumDeclaration(node) || ts.isModuleDeclaration(node)) {
|
|
24554
24934
|
const name = node.name && ts.isIdentifier(node.name) ? node.name.text : void 0;
|
|
24555
24935
|
if (name) {
|
|
@@ -24601,9 +24981,10 @@ function walkExact(ts, sourceText, fileName) {
|
|
|
24601
24981
|
for (const el of node.exportClause.elements) {
|
|
24602
24982
|
declared.add(el.name.text);
|
|
24603
24983
|
exported.add(el.name.text);
|
|
24604
|
-
if (
|
|
24605
|
-
|
|
24606
|
-
}
|
|
24984
|
+
if (node.isTypeOnly || el.isTypeOnly) continue;
|
|
24985
|
+
const local = (el.propertyName ?? el.name).text;
|
|
24986
|
+
if (spec) namedReexports.push({ exported: el.name.text, local, from: spec });
|
|
24987
|
+
else exportAliases.push({ exported: el.name.text, local });
|
|
24607
24988
|
}
|
|
24608
24989
|
} else if (!node.exportClause && spec) {
|
|
24609
24990
|
starExports.push(spec);
|
|
@@ -24678,7 +25059,12 @@ function walkExact(ts, sourceText, fileName) {
|
|
|
24678
25059
|
if (values && schemaConstants.has(alias.constant)) enumValues.set(alias.name, values);
|
|
24679
25060
|
}
|
|
24680
25061
|
const reexportOnly = sf.statements.length > 0 && sf.statements.every((st) => ts.isExportDeclaration(st) && !!st.moduleSpecifier);
|
|
24681
|
-
|
|
25062
|
+
for (const alias of propertyAliases) if (exported.has(alias.container)) exportAliases.push(alias);
|
|
25063
|
+
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 };
|
|
25064
|
+
}
|
|
25065
|
+
function resolveModule(fromFile, specifier, packages) {
|
|
25066
|
+
if (!specifier.startsWith(".")) return packages.get(specifier) ?? null;
|
|
25067
|
+
return resolveRelativeModule(fromFile, specifier);
|
|
24682
25068
|
}
|
|
24683
25069
|
function resolveRelativeModule(fromFile, specifier) {
|
|
24684
25070
|
if (!specifier.startsWith(".")) return null;
|
|
@@ -24701,8 +25087,8 @@ function resolveRelativeModule(fromFile, specifier) {
|
|
|
24701
25087
|
}
|
|
24702
25088
|
return null;
|
|
24703
25089
|
}
|
|
24704
|
-
function reexportTarget(ts, filePath, specifier, projectRoot2, visited, exactCache) {
|
|
24705
|
-
const target =
|
|
25090
|
+
function reexportTarget(ts, filePath, specifier, projectRoot2, visited, exactCache, packages) {
|
|
25091
|
+
const target = resolveModule(filePath, specifier, packages);
|
|
24706
25092
|
if (!target || path18.relative(projectRoot2, target).startsWith("..")) return null;
|
|
24707
25093
|
let targetFacts = exactCache.get(target);
|
|
24708
25094
|
if (targetFacts === void 0) {
|
|
@@ -24716,7 +25102,7 @@ function reexportTarget(ts, filePath, specifier, projectRoot2, visited, exactCac
|
|
|
24716
25102
|
if (!targetFacts) return null;
|
|
24717
25103
|
if (!visited.has(target)) {
|
|
24718
25104
|
visited.add(target);
|
|
24719
|
-
chaseReexports(ts, targetFacts, target, projectRoot2, visited, exactCache);
|
|
25105
|
+
chaseReexports(ts, targetFacts, target, projectRoot2, visited, exactCache, packages);
|
|
24720
25106
|
}
|
|
24721
25107
|
return { path: target, facts: targetFacts };
|
|
24722
25108
|
}
|
|
@@ -24739,13 +25125,33 @@ function carryBody(facts, target, targetPath, local, exported, projectRoot2) {
|
|
|
24739
25125
|
}
|
|
24740
25126
|
facts.bodies.set(exported, carried);
|
|
24741
25127
|
}
|
|
24742
|
-
function
|
|
25128
|
+
function carryAliasBody(facts, source, from, local, alias) {
|
|
25129
|
+
const sourceBodies = source.bodies.get(local) ?? [];
|
|
25130
|
+
if (!sourceBodies.length || sourceBodies.some((b) => b.nested)) return;
|
|
25131
|
+
const moduleScope = sourceBodies.filter((b) => b.container === void 0);
|
|
25132
|
+
if (!moduleScope.length) return;
|
|
25133
|
+
const score = source.complexity.get(local);
|
|
25134
|
+
if (score !== void 0) {
|
|
25135
|
+
facts.complexity.set(alias.exported, Math.max(facts.complexity.get(alias.exported) ?? 0, score));
|
|
25136
|
+
}
|
|
25137
|
+
const relocate2 = (site) => from !== void 0 ? { ...site, from: site.from ?? from } : site;
|
|
25138
|
+
const sites = facts.calls.get(alias.exported) ?? /* @__PURE__ */ new Map();
|
|
25139
|
+
const carried = facts.bodies.get(alias.exported) ?? [];
|
|
25140
|
+
for (const body of moduleScope) {
|
|
25141
|
+
const moved = body.sites.map(relocate2);
|
|
25142
|
+
for (const site of moved) sites.set(`${from ?? ""}|${alias.container ?? ""}|${JSON.stringify(site)}`, site);
|
|
25143
|
+
carried.push(alias.container !== void 0 ? { container: alias.container, sites: moved } : { sites: moved });
|
|
25144
|
+
}
|
|
25145
|
+
facts.calls.set(alias.exported, sites);
|
|
25146
|
+
facts.bodies.set(alias.exported, carried);
|
|
25147
|
+
}
|
|
25148
|
+
function chaseReexports(ts, facts, filePath, projectRoot2, visited, exactCache, packages = NO_PACKAGES) {
|
|
24743
25149
|
for (const re of facts.namedReexports) {
|
|
24744
|
-
const target = reexportTarget(ts, filePath, re.from, projectRoot2, visited, exactCache);
|
|
25150
|
+
const target = reexportTarget(ts, filePath, re.from, projectRoot2, visited, exactCache, packages);
|
|
24745
25151
|
if (target) carryBody(facts, target.facts, target.path, re.local, re.exported, projectRoot2);
|
|
24746
25152
|
}
|
|
24747
25153
|
for (const spec of facts.starExports) {
|
|
24748
|
-
const target = reexportTarget(ts, filePath, spec, projectRoot2, visited, exactCache);
|
|
25154
|
+
const target = reexportTarget(ts, filePath, spec, projectRoot2, visited, exactCache, packages);
|
|
24749
25155
|
if (!target) continue;
|
|
24750
25156
|
for (const name of target.facts.exported) {
|
|
24751
25157
|
facts.exported.add(name);
|
|
@@ -24753,6 +25159,147 @@ function chaseReexports(ts, facts, filePath, projectRoot2, visited, exactCache)
|
|
|
24753
25159
|
carryBody(facts, target.facts, target.path, name, name, projectRoot2);
|
|
24754
25160
|
}
|
|
24755
25161
|
}
|
|
25162
|
+
for (const alias of facts.exportAliases) {
|
|
25163
|
+
const binding = facts.importBindings.get(alias.local);
|
|
25164
|
+
if (binding) {
|
|
25165
|
+
if (binding.namespace) continue;
|
|
25166
|
+
const target = reexportTarget(ts, filePath, binding.from, projectRoot2, visited, exactCache, packages);
|
|
25167
|
+
if (!target) continue;
|
|
25168
|
+
const from = pathKey(path18.relative(projectRoot2, target.path));
|
|
25169
|
+
carryAliasBody(facts, target.facts, from, binding.imported ?? alias.local, alias);
|
|
25170
|
+
} else if (alias.container !== void 0 || alias.local !== alias.exported) {
|
|
25171
|
+
carryAliasBody(facts, facts, void 0, alias.local, alias);
|
|
25172
|
+
}
|
|
25173
|
+
}
|
|
25174
|
+
}
|
|
25175
|
+
function isOwnFile(projectRoot2, absolute) {
|
|
25176
|
+
const rel2 = path18.relative(projectRoot2, absolute);
|
|
25177
|
+
if (rel2.startsWith("..") || path18.isAbsolute(rel2)) return false;
|
|
25178
|
+
return !pathKey(rel2).split("/").includes("node_modules");
|
|
25179
|
+
}
|
|
25180
|
+
function isFile(absolute) {
|
|
25181
|
+
try {
|
|
25182
|
+
return fs13.statSync(absolute).isFile();
|
|
25183
|
+
} catch {
|
|
25184
|
+
return false;
|
|
25185
|
+
}
|
|
25186
|
+
}
|
|
25187
|
+
function readJsonFile(file, ts) {
|
|
25188
|
+
let text3;
|
|
25189
|
+
try {
|
|
25190
|
+
text3 = fs13.readFileSync(file, "utf8");
|
|
25191
|
+
} catch {
|
|
25192
|
+
return void 0;
|
|
25193
|
+
}
|
|
25194
|
+
try {
|
|
25195
|
+
return JSON.parse(text3);
|
|
25196
|
+
} catch {
|
|
25197
|
+
if (!ts) return void 0;
|
|
25198
|
+
const parsed = ts.parseConfigFileTextToJson(file, text3);
|
|
25199
|
+
return parsed.error ? void 0 : parsed.config;
|
|
25200
|
+
}
|
|
25201
|
+
}
|
|
25202
|
+
function conditionTargets(value) {
|
|
25203
|
+
if (typeof value === "string") return [value];
|
|
25204
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return [];
|
|
25205
|
+
const record2 = value;
|
|
25206
|
+
const keys = [...ENTRY_CONDITIONS.filter((k) => k in record2), ...Object.keys(record2).filter((k) => !ENTRY_CONDITIONS.includes(k))];
|
|
25207
|
+
return keys.flatMap((k) => conditionTargets(record2[k]));
|
|
25208
|
+
}
|
|
25209
|
+
function packageEntries(pkg) {
|
|
25210
|
+
const entries = /* @__PURE__ */ new Map();
|
|
25211
|
+
const exportsField = pkg.exports;
|
|
25212
|
+
if (exportsField !== void 0 && exportsField !== null) {
|
|
25213
|
+
const isSubpathMap = typeof exportsField === "object" && !Array.isArray(exportsField) && Object.keys(exportsField).some((k) => k.startsWith("."));
|
|
25214
|
+
if (isSubpathMap) {
|
|
25215
|
+
for (const [subpath, value] of Object.entries(exportsField)) {
|
|
25216
|
+
if (!subpath.startsWith(".") || subpath.includes("*")) continue;
|
|
25217
|
+
const targets = conditionTargets(value);
|
|
25218
|
+
if (targets.length) entries.set(subpath, targets);
|
|
25219
|
+
}
|
|
25220
|
+
} else {
|
|
25221
|
+
const targets = conditionTargets(exportsField);
|
|
25222
|
+
if (targets.length) entries.set(".", targets);
|
|
25223
|
+
}
|
|
25224
|
+
return entries;
|
|
25225
|
+
}
|
|
25226
|
+
const fallbacks = ["module", "main", "types"].map((k) => pkg[k]).filter((v) => typeof v === "string");
|
|
25227
|
+
if (fallbacks.length) entries.set(".", fallbacks);
|
|
25228
|
+
return entries;
|
|
25229
|
+
}
|
|
25230
|
+
function entrySource(packageDir, target, compiler, projectRoot2) {
|
|
25231
|
+
const absolute = path18.resolve(packageDir, target);
|
|
25232
|
+
if (!isOwnFile(projectRoot2, absolute)) return null;
|
|
25233
|
+
const isDeclaration = /\.d\.[cm]?ts$/.test(absolute);
|
|
25234
|
+
if (!isDeclaration && languageOfSourcePath(absolute) && isFile(absolute) && !(compiler.outDir && !path18.relative(compiler.outDir, absolute).startsWith(".."))) {
|
|
25235
|
+
return absolute;
|
|
25236
|
+
}
|
|
25237
|
+
if (!compiler.outDir || !compiler.rootDir) return null;
|
|
25238
|
+
const inOut = path18.relative(compiler.outDir, absolute);
|
|
25239
|
+
if (inOut.startsWith("..") || path18.isAbsolute(inOut)) return null;
|
|
25240
|
+
const extension = BUILD_EXTENSIONS.find((ext) => inOut.endsWith(ext));
|
|
25241
|
+
const stem2 = extension ? inOut.slice(0, -extension.length) : inOut;
|
|
25242
|
+
for (const ext of SOURCE_EXTENSIONS) {
|
|
25243
|
+
const candidate = path18.join(compiler.rootDir, stem2 + ext);
|
|
25244
|
+
if (isOwnFile(projectRoot2, candidate) && isFile(candidate)) return candidate;
|
|
25245
|
+
}
|
|
25246
|
+
return null;
|
|
25247
|
+
}
|
|
25248
|
+
function readLocalPackages(projectRoot2) {
|
|
25249
|
+
const out = /* @__PURE__ */ new Map();
|
|
25250
|
+
const ts = resolveTypeScript(projectRoot2);
|
|
25251
|
+
const root = readJsonFile(path18.join(projectRoot2, "package.json"), null);
|
|
25252
|
+
if (!root || typeof root !== "object" || Array.isArray(root)) return out;
|
|
25253
|
+
const rootPkg = root;
|
|
25254
|
+
const declared = Array.isArray(rootPkg.workspaces) ? rootPkg.workspaces : rootPkg.workspaces && typeof rootPkg.workspaces === "object" && Array.isArray(rootPkg.workspaces.packages) ? rootPkg.workspaces.packages : [];
|
|
25255
|
+
const dirs = [projectRoot2];
|
|
25256
|
+
for (const entry of declared) {
|
|
25257
|
+
if (typeof entry !== "string") continue;
|
|
25258
|
+
const key = pathKey(entry).replace(/\/+$/, "");
|
|
25259
|
+
if (!key || path18.isAbsolute(key)) continue;
|
|
25260
|
+
if (key.endsWith("/*")) {
|
|
25261
|
+
const base = path18.resolve(projectRoot2, key.slice(0, -2));
|
|
25262
|
+
if (base.includes("*") || !isOwnFile(projectRoot2, base)) continue;
|
|
25263
|
+
let children;
|
|
25264
|
+
try {
|
|
25265
|
+
children = fs13.readdirSync(base, { withFileTypes: true });
|
|
25266
|
+
} catch {
|
|
25267
|
+
continue;
|
|
25268
|
+
}
|
|
25269
|
+
for (const child of children.slice().sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0)) {
|
|
25270
|
+
if (child.isDirectory() && child.name !== "node_modules" && !child.name.startsWith(".")) {
|
|
25271
|
+
dirs.push(path18.join(base, child.name));
|
|
25272
|
+
}
|
|
25273
|
+
}
|
|
25274
|
+
} else if (!key.includes("*")) {
|
|
25275
|
+
const dir = path18.resolve(projectRoot2, key);
|
|
25276
|
+
if (isOwnFile(projectRoot2, dir)) dirs.push(dir);
|
|
25277
|
+
}
|
|
25278
|
+
}
|
|
25279
|
+
for (const dir of dirs) {
|
|
25280
|
+
const pkg = dir === projectRoot2 ? rootPkg : readJsonFile(path18.join(dir, "package.json"), null);
|
|
25281
|
+
if (!pkg || typeof pkg !== "object" || Array.isArray(pkg)) continue;
|
|
25282
|
+
const name = pkg.name;
|
|
25283
|
+
if (typeof name !== "string" || !name) continue;
|
|
25284
|
+
const tsconfig = readJsonFile(path18.join(dir, "tsconfig.json"), ts);
|
|
25285
|
+
const options = tsconfig?.compilerOptions ?? {};
|
|
25286
|
+
const compiler = {
|
|
25287
|
+
outDir: typeof options.outDir === "string" ? path18.resolve(dir, options.outDir) : void 0,
|
|
25288
|
+
rootDir: typeof options.rootDir === "string" ? path18.resolve(dir, options.rootDir) : void 0
|
|
25289
|
+
};
|
|
25290
|
+
for (const [subpath, targets] of packageEntries(pkg)) {
|
|
25291
|
+
const specifier = subpath === "." ? name : `${name}/${subpath.replace(/^\.\//, "")}`;
|
|
25292
|
+
if (out.has(specifier)) continue;
|
|
25293
|
+
for (const target of targets) {
|
|
25294
|
+
const source = entrySource(dir, target, compiler, projectRoot2);
|
|
25295
|
+
if (source) {
|
|
25296
|
+
out.set(specifier, source);
|
|
25297
|
+
break;
|
|
25298
|
+
}
|
|
25299
|
+
}
|
|
25300
|
+
}
|
|
25301
|
+
}
|
|
25302
|
+
return out;
|
|
24756
25303
|
}
|
|
24757
25304
|
function looksBinary(buffer) {
|
|
24758
25305
|
const probe2 = buffer.subarray(0, Math.min(buffer.length, 4096));
|
|
@@ -24810,6 +25357,9 @@ function buildCodeModel(implementations, types, projectRoot2, sourceRoots = [],
|
|
|
24810
25357
|
const seen = /* @__PURE__ */ new Set();
|
|
24811
25358
|
const exactCache = /* @__PURE__ */ new Map();
|
|
24812
25359
|
const rootFiles = walkDeclaredRoots(sourceRoots, exclude, projectRoot2);
|
|
25360
|
+
const localPackages = readLocalPackages(projectRoot2);
|
|
25361
|
+
const packages = {};
|
|
25362
|
+
for (const [specifier, absolute] of localPackages) packages[specifier] = pathKey(path18.relative(projectRoot2, absolute));
|
|
24813
25363
|
const declaredPaths = [];
|
|
24814
25364
|
for (const impl of implementations) {
|
|
24815
25365
|
declaredPaths.push(...implementationSourceFiles(impl));
|
|
@@ -24855,7 +25405,7 @@ function buildCodeModel(implementations, types, projectRoot2, sourceRoots = [],
|
|
|
24855
25405
|
if (ts) {
|
|
24856
25406
|
try {
|
|
24857
25407
|
const facts = walkExact(ts, text3, absolute);
|
|
24858
|
-
chaseReexports(ts, facts, absolute, projectRoot2, /* @__PURE__ */ new Set([absolute]), exactCache);
|
|
25408
|
+
chaseReexports(ts, facts, absolute, projectRoot2, /* @__PURE__ */ new Set([absolute]), exactCache, localPackages);
|
|
24859
25409
|
analyzed = {
|
|
24860
25410
|
analysisGrade: "exact",
|
|
24861
25411
|
declaredNames: [...facts.declared],
|
|
@@ -24867,6 +25417,10 @@ function buildCodeModel(implementations, types, projectRoot2, sourceRoots = [],
|
|
|
24867
25417
|
...facts.namedReexports.map(({ exported, local, from }) => ({ exported, local, from })),
|
|
24868
25418
|
...facts.starExports.map((from) => ({ exported: "*", local: "*", from }))
|
|
24869
25419
|
],
|
|
25420
|
+
exportAliases: facts.exportAliases.map((alias) => ({ ...alias })),
|
|
25421
|
+
returnTypes: Object.fromEntries(
|
|
25422
|
+
[...facts.returnTypes].filter((entry) => entry[1] !== null)
|
|
25423
|
+
),
|
|
24870
25424
|
functionComplexity: Object.fromEntries(facts.complexity),
|
|
24871
25425
|
functionCallSites: Object.fromEntries([...facts.calls].map(([k, v]) => [k, [...v.values()]])),
|
|
24872
25426
|
functionBodies: Object.fromEntries(facts.bodies),
|
|
@@ -24896,7 +25450,7 @@ function buildCodeModel(implementations, types, projectRoot2, sourceRoots = [],
|
|
|
24896
25450
|
}
|
|
24897
25451
|
files.push({ path: sourcePath, status: "analyzed", language, ...analyzed });
|
|
24898
25452
|
}
|
|
24899
|
-
return { files, projectRoot: projectRoot2, rootFiles };
|
|
25453
|
+
return { files, projectRoot: projectRoot2, rootFiles, packages };
|
|
24900
25454
|
}
|
|
24901
25455
|
function clauseBindings(clause, specifier) {
|
|
24902
25456
|
const out = [];
|
|
@@ -25032,7 +25586,7 @@ function findTestsReferencing(methods, projectRoot2, testRoots) {
|
|
|
25032
25586
|
}
|
|
25033
25587
|
return found;
|
|
25034
25588
|
}
|
|
25035
|
-
var fs13, path18, import_module2, C_FAMILY_COMMENTS, NAMED_IMPORT_BINDINGS_RE, JS_PATTERNS, LANGUAGE_PATTERNS, PATTERN_ANALYSIS_MAX_BYTES, IDENTIFIER_RE, STRING_RE, tsResolutionCache, TS_RESOLUTION_RETRY_MS, IMPORT_FROM_RE, DYNAMIC_IMPORT_RE, IDENTIFIER_ONLY_RE, NAMED_REEXPORT_RE, STAR_REEXPORT_RE;
|
|
25589
|
+
var fs13, path18, import_module2, C_FAMILY_COMMENTS, NAMED_IMPORT_BINDINGS_RE, JS_PATTERNS, LANGUAGE_PATTERNS, PATTERN_ANALYSIS_MAX_BYTES, IDENTIFIER_RE, STRING_RE, tsResolutionCache, TS_RESOLUTION_RETRY_MS, NO_PACKAGES, BUILD_EXTENSIONS, SOURCE_EXTENSIONS, ENTRY_CONDITIONS, IMPORT_FROM_RE, DYNAMIC_IMPORT_RE, IDENTIFIER_ONLY_RE, NAMED_REEXPORT_RE, STAR_REEXPORT_RE;
|
|
25036
25590
|
var init_source_analysis = __esm({
|
|
25037
25591
|
"src/core/source-analysis.ts"() {
|
|
25038
25592
|
"use strict";
|
|
@@ -25141,6 +25695,10 @@ var init_source_analysis = __esm({
|
|
|
25141
25695
|
STRING_RE = /'([^'\\\n]*(?:\\.[^'\\\n]*)*)'|"([^"\\\n]*(?:\\.[^"\\\n]*)*)"|`([^`\\]*(?:\\.[^`\\]*)*)`/g;
|
|
25142
25696
|
tsResolutionCache = /* @__PURE__ */ new Map();
|
|
25143
25697
|
TS_RESOLUTION_RETRY_MS = 3e4;
|
|
25698
|
+
NO_PACKAGES = /* @__PURE__ */ new Map();
|
|
25699
|
+
BUILD_EXTENSIONS = [".d.mts", ".d.cts", ".d.ts", ".mjs", ".cjs", ".jsx", ".js"];
|
|
25700
|
+
SOURCE_EXTENSIONS = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
|
|
25701
|
+
ENTRY_CONDITIONS = ["types", "import", "require", "default"];
|
|
25144
25702
|
IMPORT_FROM_RE = /(?:^|;)[ \t]*import\s+(?!\()([^'";]*?)\s*from\s*['"]([^'"]+)['"]/gm;
|
|
25145
25703
|
DYNAMIC_IMPORT_RE = /\{([^{}]*)\}\s*=\s*await\s+import\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
25146
25704
|
IDENTIFIER_ONLY_RE = /^[A-Za-z_$][\w$]*$/;
|
|
@@ -27006,8 +27564,8 @@ function generateSequenceDiagram(componentId, methodName, options) {
|
|
|
27006
27564
|
const steps = [...methodImpl.narrative].sort((a, b) => a.stepNumber - b.stepNumber);
|
|
27007
27565
|
for (const step of steps) {
|
|
27008
27566
|
for (const par of parallelArms) {
|
|
27009
|
-
const
|
|
27010
|
-
if (
|
|
27567
|
+
const sep19 = par.sepByStep.get(step.stepNumber);
|
|
27568
|
+
if (sep19 !== void 0) lines.push(` and ${escapeLabel(sep19)}`);
|
|
27011
27569
|
}
|
|
27012
27570
|
switch (step.type) {
|
|
27013
27571
|
case "local":
|
|
@@ -27785,6 +28343,15 @@ function classifySource(world, sourceId, label7, sources) {
|
|
|
27785
28343
|
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`
|
|
27786
28344
|
};
|
|
27787
28345
|
}
|
|
28346
|
+
function duplicateDetail(entries, index) {
|
|
28347
|
+
const e = entries[index];
|
|
28348
|
+
if (e.from !== void 0 || e.component !== void 0 || e.interface !== void 0 || e.typeDef !== void 0) return null;
|
|
28349
|
+
const name = e.as ?? e.id;
|
|
28350
|
+
if (name === void 0) return null;
|
|
28351
|
+
const other = entries.findIndex((x, i) => i !== index && (x.as ?? x.id) === name);
|
|
28352
|
+
if (other === -1) return null;
|
|
28353
|
+
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`;
|
|
28354
|
+
}
|
|
27788
28355
|
function bindOwnType(world, owner, e, problems) {
|
|
27789
28356
|
const type = (world.types.get(e.typeDef) ?? []).find((t) => !t.subsystem);
|
|
27790
28357
|
if (!type) {
|
|
@@ -27856,7 +28423,8 @@ function resolveProjectTable(system, subsystems, components, interfaces, types,
|
|
|
27856
28423
|
const owner = sources.namespace ? sources.namespace : system.name;
|
|
27857
28424
|
const problems = [];
|
|
27858
28425
|
const candidates = [];
|
|
27859
|
-
|
|
28426
|
+
const entries = system.publicInterfaces ?? [];
|
|
28427
|
+
entries.forEach((e, index) => {
|
|
27860
28428
|
const label7 = e.as ?? e.id ?? e.interface ?? e.component ?? e.typeDef ?? `#${index + 1}`;
|
|
27861
28429
|
const source = classifySource(world, sourceOf(world, e), label7, sources);
|
|
27862
28430
|
switch (source.kind) {
|
|
@@ -27882,7 +28450,7 @@ function resolveProjectTable(system, subsystems, components, interfaces, types,
|
|
|
27882
28450
|
return;
|
|
27883
28451
|
}
|
|
27884
28452
|
default:
|
|
27885
|
-
problems.push({ kind: "invalid", owner, publicName: label7, detail: source.detail });
|
|
28453
|
+
problems.push({ kind: "invalid", owner, publicName: label7, detail: duplicateDetail(entries, index) ?? source.detail });
|
|
27886
28454
|
}
|
|
27887
28455
|
});
|
|
27888
28456
|
return settle(owner, "project", candidates, problems);
|
|
@@ -28479,6 +29047,16 @@ function qualifiedTypeNames(typeStr) {
|
|
|
28479
29047
|
}
|
|
28480
29048
|
return out;
|
|
28481
29049
|
}
|
|
29050
|
+
function invariantTypeRef(ref) {
|
|
29051
|
+
const at = ref.lastIndexOf(".");
|
|
29052
|
+
return at > 0 && at < ref.length - 1 ? ref.slice(0, at) : void 0;
|
|
29053
|
+
}
|
|
29054
|
+
function mapInvariantRef(ref, map) {
|
|
29055
|
+
const typeRef2 = invariantTypeRef(ref);
|
|
29056
|
+
if (typeRef2 === void 0 || !typeRef2.includes("::")) return ref;
|
|
29057
|
+
const next = map("type", typeRef2);
|
|
29058
|
+
return next === typeRef2 ? ref : `${next}${ref.slice(typeRef2.length)}`;
|
|
29059
|
+
}
|
|
28482
29060
|
function rawReferences(kind, spec) {
|
|
28483
29061
|
const out = [];
|
|
28484
29062
|
const add2 = (position, value) => {
|
|
@@ -28501,6 +29079,7 @@ function rawReferences(kind, spec) {
|
|
|
28501
29079
|
for (const step of m.narrative) {
|
|
28502
29080
|
const source = step.auth?.from;
|
|
28503
29081
|
if (typeof source === "string" && source.startsWith(COMPONENT_AUTH_SOURCE)) add2("auth", source.slice(COMPONENT_AUTH_SOURCE.length));
|
|
29082
|
+
for (const ref of step.assertsInvariants ?? []) add2("type", invariantTypeRef(ref));
|
|
28504
29083
|
}
|
|
28505
29084
|
}
|
|
28506
29085
|
break;
|
|
@@ -28619,6 +29198,9 @@ function respellStoredReferences(kind, doc, map) {
|
|
|
28619
29198
|
for (const m of list2(doc.methods)) {
|
|
28620
29199
|
for (const step of list2(m.narrative)) {
|
|
28621
29200
|
at(step, "targetComponent", "narrative");
|
|
29201
|
+
if (Array.isArray(step.assertsInvariants)) {
|
|
29202
|
+
step.assertsInvariants = step.assertsInvariants.map((ref) => typeof ref === "string" ? mapInvariantRef(ref, map) : ref);
|
|
29203
|
+
}
|
|
28622
29204
|
const auth = step.auth;
|
|
28623
29205
|
if (auth && typeof auth.from === "string" && auth.from.startsWith(COMPONENT_AUTH_SOURCE)) {
|
|
28624
29206
|
auth.from = `${COMPONENT_AUTH_SOURCE}${map("auth", auth.from.slice(COMPONENT_AUTH_SOURCE.length))}`;
|
|
@@ -28678,7 +29260,8 @@ function mapRawReferences(kind, spec, map) {
|
|
|
28678
29260
|
return call && next !== call.compId ? `${next}.${call.methodName}` : entry;
|
|
28679
29261
|
})
|
|
28680
29262
|
} : {},
|
|
28681
|
-
narrative: m.narrative.map((
|
|
29263
|
+
narrative: m.narrative.map((authored) => {
|
|
29264
|
+
const step = authored.assertsInvariants ? { ...authored, assertsInvariants: authored.assertsInvariants.map((ref) => mapInvariantRef(ref, map)) } : authored;
|
|
28682
29265
|
const source = step.auth?.from;
|
|
28683
29266
|
if (typeof source !== "string" || !source.startsWith(COMPONENT_AUTH_SOURCE)) return step;
|
|
28684
29267
|
const auth = step.auth;
|
|
@@ -28779,7 +29362,7 @@ function resolveFamilyTables(raws) {
|
|
|
28779
29362
|
const byKey3 = new Map(raws.map((r) => [r.record.namespace, r]));
|
|
28780
29363
|
const done = /* @__PURE__ */ new Set();
|
|
28781
29364
|
const resolving = /* @__PURE__ */ new Set();
|
|
28782
|
-
const
|
|
29365
|
+
const resolve56 = (raw) => {
|
|
28783
29366
|
const key = raw.record.namespace;
|
|
28784
29367
|
if (done.has(key)) return raw.record.exports;
|
|
28785
29368
|
if (resolving.has(key)) return null;
|
|
@@ -28794,7 +29377,7 @@ function resolveFamilyTables(raws) {
|
|
|
28794
29377
|
const producerKey = source !== void 0 ? raw.record.aliases.get(source) : void 0;
|
|
28795
29378
|
if (producerKey === void 0 || producerTables.has(producerKey)) continue;
|
|
28796
29379
|
const producer = byKey3.get(producerKey);
|
|
28797
|
-
producerTables.set(producerKey, producer ?
|
|
29380
|
+
producerTables.set(producerKey, producer ? resolve56(producer) : null);
|
|
28798
29381
|
audienceOf.set(producerKey, "project");
|
|
28799
29382
|
}
|
|
28800
29383
|
raw.record.subsystemExports = subsystemTables;
|
|
@@ -28810,7 +29393,7 @@ function resolveFamilyTables(raws) {
|
|
|
28810
29393
|
resolving.delete(key);
|
|
28811
29394
|
}
|
|
28812
29395
|
};
|
|
28813
|
-
raws.forEach((raw) =>
|
|
29396
|
+
raws.forEach((raw) => resolve56(raw));
|
|
28814
29397
|
}
|
|
28815
29398
|
function bindAuthored(raw, raws, readables, authored) {
|
|
28816
29399
|
const key = raw.record.namespace;
|
|
@@ -31245,12 +31828,12 @@ var init_specs2 = __esm({
|
|
|
31245
31828
|
return this.relativizeSpec("interface", stored);
|
|
31246
31829
|
}
|
|
31247
31830
|
prepareImplementationForWrite(spec, carry = true) {
|
|
31248
|
-
const
|
|
31831
|
+
const relative28 = this.relativizeSpec("implementation", spec, carry);
|
|
31249
31832
|
const member = this.getSubprojectPrefix(spec.id);
|
|
31250
31833
|
const memberRoot = member ? this.resolveSubprojectForNamespace(member) : null;
|
|
31251
|
-
if (memberRoot) return childRelativeFilePaths(
|
|
31834
|
+
if (memberRoot) return childRelativeFilePaths(relative28, this.rootDir, memberRoot);
|
|
31252
31835
|
const partDir = this.partHolding(this.getImplementationPath(spec.id, spec.contract))?.part.directory;
|
|
31253
|
-
return partDir ? partRelativeFilePaths(
|
|
31836
|
+
return partDir ? partRelativeFilePaths(relative28, this.rootDir, partDir) : relative28;
|
|
31254
31837
|
}
|
|
31255
31838
|
/**
|
|
31256
31839
|
* A type written in its stored form — every type position canonical (one
|
|
@@ -33407,7 +33990,7 @@ function resolveGit(external, config) {
|
|
|
33407
33990
|
const declared = gitDeclarationOf(config, external);
|
|
33408
33991
|
if (!declared) return { ...external, problem: "git producer unavailable: its declaration names no repository" };
|
|
33409
33992
|
try {
|
|
33410
|
-
const commit3 = external.role === "member" && declared.commit ? declared.commit :
|
|
33993
|
+
const commit3 = external.role === "member" && declared.commit ? declared.commit : resolve3(declared.url, declared.ref);
|
|
33411
33994
|
const directory = fetch2(declared.url, commit3, declared.dir);
|
|
33412
33995
|
return { ...external, directory: path23.resolve(directory), commit: commit3, problem: void 0 };
|
|
33413
33996
|
} catch (e) {
|
|
@@ -33858,7 +34441,7 @@ function scaffoldPart(alias, memberDir, contained) {
|
|
|
33858
34441
|
}
|
|
33859
34442
|
function createGitMember(alias, source, description, as) {
|
|
33860
34443
|
const url = source.git;
|
|
33861
|
-
const commit3 = source.commit ??
|
|
34444
|
+
const commit3 = source.commit ?? resolve3(url, source.ref);
|
|
33862
34445
|
const dir = fetch2(url, commit3, source.dir);
|
|
33863
34446
|
const kind = holdsProjectContent(dir) ? "project" : "part";
|
|
33864
34447
|
if (as !== void 0 && as !== kind) {
|
|
@@ -34800,7 +35383,7 @@ function advanceMember(alias, ref, commit3, dryRun) {
|
|
|
34800
35383
|
const url = member.source.git;
|
|
34801
35384
|
const from = member.source.commit ?? "";
|
|
34802
35385
|
const followed = commit3 === void 0 ? ref ?? member.source.ref : void 0;
|
|
34803
|
-
const to = commit3 ??
|
|
35386
|
+
const to = commit3 ?? resolve3(url, followed);
|
|
34804
35387
|
const after = specContents(fetch2(url, to, member.source.dir));
|
|
34805
35388
|
const before = specContents(from !== "" ? fetch2(url, from, member.source.dir) : null);
|
|
34806
35389
|
const added = [...after.keys()].filter((f) => !before.has(f)).sort();
|
|
@@ -36409,17 +36992,17 @@ function memberHop(agentId) {
|
|
|
36409
36992
|
const alias = agentId.slice(0, at);
|
|
36410
36993
|
const member = graph().nodes.find((n) => n.parent === "" && n.mountAlias === alias);
|
|
36411
36994
|
if (!member) return null;
|
|
36412
|
-
const
|
|
36413
|
-
return { alias, rest: agentId.slice(at + 2), directory: member.directory, relative:
|
|
36995
|
+
const relative28 = path29.relative(getProjectRoot(), member.directory).split(path29.sep).join("/");
|
|
36996
|
+
return { alias, rest: agentId.slice(at + 2), directory: member.directory, relative: relative28 };
|
|
36414
36997
|
}
|
|
36415
|
-
function throughMount(brief, alias,
|
|
36416
|
-
const under = (p) => path29.posix.join(
|
|
36998
|
+
function throughMount(brief, alias, relative28) {
|
|
36999
|
+
const under = (p) => path29.posix.join(relative28, p);
|
|
36417
37000
|
return {
|
|
36418
37001
|
...brief,
|
|
36419
37002
|
agentId: `${alias}::${brief.agentId}`,
|
|
36420
37003
|
ownedPaths: brief.ownedPaths.map(under),
|
|
36421
37004
|
...brief.readPaths ? { readPaths: brief.readPaths.map(under) } : {},
|
|
36422
|
-
root: brief.root ? under(brief.root) :
|
|
37005
|
+
root: brief.root ? under(brief.root) : relative28
|
|
36423
37006
|
};
|
|
36424
37007
|
}
|
|
36425
37008
|
function loadRegistry() {
|
|
@@ -39810,8 +40393,8 @@ var require_dist = __commonJS({
|
|
|
39810
40393
|
walkPackDir(root, path93.join(current2, entry.name), out);
|
|
39811
40394
|
} else if (entry.isFile()) {
|
|
39812
40395
|
const absolute = path93.join(current2, entry.name);
|
|
39813
|
-
const
|
|
39814
|
-
out.push({ path:
|
|
40396
|
+
const relative28 = path93.relative(root, absolute).split(path93.sep).join("/");
|
|
40397
|
+
out.push({ path: relative28, contents: fs69.readFileSync(absolute) });
|
|
39815
40398
|
}
|
|
39816
40399
|
}
|
|
39817
40400
|
}
|
|
@@ -40011,8 +40594,8 @@ var require_dist = __commonJS({
|
|
|
40011
40594
|
return normalized === "." ? ".wai/" : `${normalized}/.wai/`;
|
|
40012
40595
|
}
|
|
40013
40596
|
function normalizeRootPath(relativePath) {
|
|
40014
|
-
const
|
|
40015
|
-
return
|
|
40597
|
+
const posix8 = relativePath.split(path210.sep).join("/").replace(/^\.\//, "").replace(/\/+$/, "");
|
|
40598
|
+
return posix8 === "" || posix8 === "." ? "." : posix8;
|
|
40016
40599
|
}
|
|
40017
40600
|
function slugify2(projectName) {
|
|
40018
40601
|
const slug2 = projectName.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
@@ -40118,19 +40701,19 @@ function commitStagedTree(stagingDir, destDir, roots, backupExisting) {
|
|
|
40118
40701
|
const backupDir = path34.join(root, BACKUP_DIR, timestampSlug());
|
|
40119
40702
|
let backedUp = false;
|
|
40120
40703
|
for (const rootRel of roots) {
|
|
40121
|
-
const
|
|
40122
|
-
const stagedSpecDir = path34.join(stagingDir,
|
|
40704
|
+
const relative28 = rootRel === "." ? "" : rootRel;
|
|
40705
|
+
const stagedSpecDir = path34.join(stagingDir, relative28, ".wai");
|
|
40123
40706
|
if (!fs20.existsSync(stagedSpecDir)) continue;
|
|
40124
|
-
const liveSpecDir = liveSpecDirFor(path34.join(root,
|
|
40707
|
+
const liveSpecDir = liveSpecDirFor(path34.join(root, relative28));
|
|
40125
40708
|
if (backupExisting && liveSpecDir && fs20.existsSync(liveSpecDir)) {
|
|
40126
|
-
const backupTarget = path34.join(backupDir,
|
|
40709
|
+
const backupTarget = path34.join(backupDir, relative28, path34.basename(liveSpecDir));
|
|
40127
40710
|
fs20.mkdirSync(path34.dirname(backupTarget), { recursive: true });
|
|
40128
40711
|
fs20.renameSync(liveSpecDir, backupTarget);
|
|
40129
40712
|
backedUp = true;
|
|
40130
40713
|
} else if (liveSpecDir && fs20.existsSync(liveSpecDir)) {
|
|
40131
40714
|
fs20.rmSync(liveSpecDir, { recursive: true, force: true });
|
|
40132
40715
|
}
|
|
40133
|
-
const target = path34.join(root,
|
|
40716
|
+
const target = path34.join(root, relative28, ".wai");
|
|
40134
40717
|
fs20.mkdirSync(path34.dirname(target), { recursive: true });
|
|
40135
40718
|
if (fs20.existsSync(target)) fs20.rmSync(target, { recursive: true, force: true });
|
|
40136
40719
|
fs20.renameSync(stagedSpecDir, target);
|
|
@@ -40445,7 +41028,7 @@ function repairTypeSpellings(apply4) {
|
|
|
40445
41028
|
const key = `${kind}:${specId}`;
|
|
40446
41029
|
let repair = repairs.get(key);
|
|
40447
41030
|
if (!repair) {
|
|
40448
|
-
repair = { specId, kind, rewritten: [], proposals: [], authorNeeded: [] };
|
|
41031
|
+
repair = { specId, kind, rewritten: [], proposals: [], enumProposals: [], authorNeeded: [] };
|
|
40449
41032
|
repairs.set(key, repair);
|
|
40450
41033
|
}
|
|
40451
41034
|
return repair;
|
|
@@ -40460,7 +41043,9 @@ function repairTypeSpellings(apply4) {
|
|
|
40460
41043
|
if (problem.specId === void 0 || problem.kind === void 0 || !own(problem.specId)) continue;
|
|
40461
41044
|
const repair = repairOf(problem.specId, problem.kind);
|
|
40462
41045
|
const proposal = typeProblemIntProposal(problem);
|
|
41046
|
+
const enumProposal = proposal ? null : typeProblemEnumProposal(problem);
|
|
40463
41047
|
if (proposal) repair.proposals.push(proposal);
|
|
41048
|
+
else if (enumProposal) repair.enumProposals.push(enumProposal);
|
|
40464
41049
|
else repair.authorNeeded.push(problem);
|
|
40465
41050
|
}
|
|
40466
41051
|
const planned = [...repairs.values()];
|
|
@@ -40670,7 +41255,7 @@ var init_approval = __esm({
|
|
|
40670
41255
|
init_lockfile();
|
|
40671
41256
|
init_lock();
|
|
40672
41257
|
init_project_family();
|
|
40673
|
-
GATE_UPGRADE_NOTE = "the gate identity gained inputs in
|
|
41258
|
+
GATE_UPGRADE_NOTE = "the gate identity gained inputs in this release (members' composition subjects, `composition`; code conformance moved beside the claim)";
|
|
40674
41259
|
}
|
|
40675
41260
|
});
|
|
40676
41261
|
|
|
@@ -40849,16 +41434,16 @@ var init_ai_guide = __esm({
|
|
|
40849
41434
|
If \`.wai/specs/\` exists, the wairon SDD workflow is active; otherwise ignore it. wairon does not orchestrate sessions \u2014 it equips yours.
|
|
40850
41435
|
|
|
40851
41436
|
### In SDD Projects:
|
|
40852
|
-
- **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).
|
|
41437
|
+
- **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.
|
|
40853
41438
|
- **Validation**: Conformance checks (stereotype rules, cycle checks, reference integrity) are run via the \`sdd_validate_tree\` MCP tool.
|
|
40854
41439
|
- **Operating Rules**:
|
|
40855
41440
|
1. **Skills**: Use \`sdd-architect\` to design (and \`sdd-implement\`, \`sdd-narrative\`, \`sdd-auditor\`). Refer to project's local guide file for detailed constraints.
|
|
40856
41441
|
2. **MCP Tools Only**: Author/validate specs *only* via \`sdd_*\` tools (e.g. \`sdd_initialize_system\`, \`sdd_validate_tree\`).
|
|
40857
41442
|
3. **No CLI Exec**: Do not run the \`wairon\` CLI (human tool). Use MCP tools \`sdd_validate_tree\` and \`sdd_get_status\` instead.
|
|
40858
41443
|
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>\`).
|
|
40859
|
-
5. **Design First**: Complete spec and pass \`sdd_validate_tree\` before writing code.
|
|
41444
|
+
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.
|
|
40860
41445
|
6. **Consistency**: Code must match L3 interfaces and L5 narratives exactly. If the spec is wrong, stop and update the spec.
|
|
40861
|
-
7. **Members & References**: A project may
|
|
41446
|
+
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.`;
|
|
40862
41447
|
LOCAL_GUIDE_BODY = `## Wairon \u2014 Spec-Driven Development (you are operating inside it)
|
|
40863
41448
|
|
|
40864
41449
|
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.
|
|
@@ -40875,7 +41460,8 @@ This project uses **wairon**. System specs live under \`.wai/specs/\` (L0 System
|
|
|
40875
41460
|
- **\`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\`).
|
|
40876
41461
|
- **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\`).
|
|
40877
41462
|
- **Do not run the \`wairon\` CLI**: Use \`sdd_validate_tree\` and \`sdd_get_status\` instead of CLI commands.
|
|
40878
|
-
- **Handoff to implementation**: Once design is complete and validates cleanly, tell the human: *"The specs are complete and validate. Please run \`wairon lock\` to
|
|
41463
|
+
- **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.
|
|
41464
|
+
- **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\`.
|
|
40879
41465
|
- **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.
|
|
40880
41466
|
|
|
40881
41467
|
### Rules (enforced by \`sdd_validate_tree\`)
|
|
@@ -41255,7 +41841,7 @@ function getStatusReport(options = {}, decor) {
|
|
|
41255
41841
|
}
|
|
41256
41842
|
const own = options.approvals?.find((a) => a.key === "");
|
|
41257
41843
|
const hasMembers = (options.approvals ?? []).some((a) => a.key !== "" && a.as !== "part");
|
|
41258
|
-
if (own && (own.state !== "never" || hasMembers)) output += `${mark.layer("system", "Approval:")} this project is ${own.state}${own.upgraded ? " (locked under
|
|
41844
|
+
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)" : ""}
|
|
41259
41845
|
`;
|
|
41260
41846
|
return { text: output, failed: false };
|
|
41261
41847
|
}
|
|
@@ -42921,7 +43507,7 @@ var init_position_migration = __esm({
|
|
|
42921
43507
|
auth: ["implementation"],
|
|
42922
43508
|
subsystem: ["component", "type"],
|
|
42923
43509
|
group: ["type"],
|
|
42924
|
-
type: ["interface", "type"]
|
|
43510
|
+
type: ["interface", "type", "implementation"]
|
|
42925
43511
|
};
|
|
42926
43512
|
BOUND = /* @__PURE__ */ new Set(["publicInterfaces", "lifecycle", "subsystem", "owns", "dependsOn", "dispatch", "mounts", "contract", "narrative", "group"]);
|
|
42927
43513
|
compare2 = (a, b) => a < b ? -1 : a > b ? 1 : 0;
|
|
@@ -43882,7 +44468,7 @@ function attachEdits(plan7, family, bound2, dir, alias, bindingConfig) {
|
|
|
43882
44468
|
edit(plan7, key, "id", `id: ${id} (declared${approved !== void 0 ? ", the id its lock approved" : ""})`, { root: dir, call: "setId", args: [id] });
|
|
43883
44469
|
}
|
|
43884
44470
|
if (!attached) plan7.notes.push(`${dir} has no project.yaml of its own \u2014 \`wairon doctor --fix\` writes one declaring "${alias}" as its id`);
|
|
43885
|
-
const relPath =
|
|
44471
|
+
const relPath = posix5(path45.relative(familyNode(family, bound2).directory, dir));
|
|
43886
44472
|
const declaration2 = { path: relPath, ...plan7.request.description !== void 0 ? { description: plan7.request.description } : {} };
|
|
43887
44473
|
edit(plan7, bound2, "member", `members: ${alias} \u2192 ${relPath} (an existing project, kept as it is)`, { root: familyNode(family, bound2).directory, call: "declareMember", args: [alias, declaration2] });
|
|
43888
44474
|
for (const requirement of requiredPolicies(bindingConfig)) {
|
|
@@ -43929,8 +44515,8 @@ function planDetach(family, bound2, request) {
|
|
|
43929
44515
|
if (plan7.request.widen) planWidening(plan7, member, narrow);
|
|
43930
44516
|
if (plan7.refusals.length > 0) return plan7;
|
|
43931
44517
|
const boundDir = familyNode(family, bound2).directory;
|
|
43932
|
-
const held = memberDeclarationOf(configAt3(boundDir)?.members?.[alias] ??
|
|
43933
|
-
const declared = { ...held, path: memberLocationOf(held) ??
|
|
44518
|
+
const held = memberDeclarationOf(configAt3(boundDir)?.members?.[alias] ?? posix5(path45.relative(boundDir, member.directory)));
|
|
44519
|
+
const declared = { ...held, path: memberLocationOf(held) ?? posix5(path45.relative(boundDir, member.directory)) };
|
|
43934
44520
|
detachEdits(plan7, family, bound2, member, alias, declared, consumers, outward);
|
|
43935
44521
|
return plan7;
|
|
43936
44522
|
}
|
|
@@ -43970,7 +44556,7 @@ function planWidening(plan7, member, narrow) {
|
|
|
43970
44556
|
function detachEdits(plan7, family, bound2, member, alias, declared, consumers, outward) {
|
|
43971
44557
|
const boundDir = familyNode(family, bound2).directory;
|
|
43972
44558
|
const relocation = plan7.request.relocation;
|
|
43973
|
-
const sourceTo = (from, producer) => relocation ? { hosted: producer.id ?? producer.mountAlias ?? producer.namespace } : { path:
|
|
44559
|
+
const sourceTo = (from, producer) => relocation ? { hosted: producer.id ?? producer.mountAlias ?? producer.namespace } : { path: posix5(path45.relative(from, producer.directory)) };
|
|
43974
44560
|
const where = (source) => source.hosted !== void 0 ? `hosted record ${source.hosted}` : source.path;
|
|
43975
44561
|
edit(plan7, bound2, "member", `members: ${alias} removed (${declared.path} ${relocation ? `moves to ${relocation.to}` : "stays as it is"})`, { root: boundDir, call: "removeMember", args: [alias] });
|
|
43976
44562
|
const boundSource = relocation ? sourceTo(boundDir, member) : { path: declared.path };
|
|
@@ -44044,7 +44630,7 @@ function adoptEdits(plan7, family, bound2, alias, external, dir, at = dir) {
|
|
|
44044
44630
|
edit(plan7, bound2, "pin", `pin ${alias} removed (the family supplies it now)`, { root: boundDir, call: "unpin", args: [alias] });
|
|
44045
44631
|
edit(plan7, bound2, "external", `externals: ${alias} removed`, { root: boundDir, call: "removeExternal", args: [alias] });
|
|
44046
44632
|
const declaration2 = {
|
|
44047
|
-
path:
|
|
44633
|
+
path: posix5(path45.relative(boundDir, at)),
|
|
44048
44634
|
...external.description !== void 0 ? { description: external.description } : {},
|
|
44049
44635
|
...external.use && external.use.length > 0 ? { use: external.use } : {}
|
|
44050
44636
|
};
|
|
@@ -44117,7 +44703,7 @@ function writeOne(rehearsal, w) {
|
|
|
44117
44703
|
}
|
|
44118
44704
|
});
|
|
44119
44705
|
}
|
|
44120
|
-
var fs26, path45,
|
|
44706
|
+
var fs26, path45, posix5, refuse, edit, label4;
|
|
44121
44707
|
var init_membership = __esm({
|
|
44122
44708
|
"src/migrations/membership.ts"() {
|
|
44123
44709
|
"use strict";
|
|
@@ -44132,7 +44718,7 @@ var init_membership = __esm({
|
|
|
44132
44718
|
init_project_family();
|
|
44133
44719
|
init_specs();
|
|
44134
44720
|
init_types2();
|
|
44135
|
-
|
|
44721
|
+
posix5 = (p) => p.split(path45.sep).join("/");
|
|
44136
44722
|
refuse = (plan7, code, project2, detail) => {
|
|
44137
44723
|
plan7.refusals.push({ code, project: project2, detail });
|
|
44138
44724
|
};
|
|
@@ -44380,7 +44966,7 @@ var init_identity = __esm({
|
|
|
44380
44966
|
auth: ["implementation"],
|
|
44381
44967
|
subsystem: ["component", "type"],
|
|
44382
44968
|
group: ["type"],
|
|
44383
|
-
type: ["interface", "type"]
|
|
44969
|
+
type: ["interface", "type", "implementation"]
|
|
44384
44970
|
};
|
|
44385
44971
|
PHASES = ["rewriteReferences", "renameAlias", "repointExternal", "renameId", "renamePin"];
|
|
44386
44972
|
}
|
|
@@ -44578,7 +45164,7 @@ function respellConsumer(plan7, family, c, own) {
|
|
|
44578
45164
|
function repointConsumer(plan7, c, boundNode2) {
|
|
44579
45165
|
const declared = configAt5(c.node.directory)?.externals?.[c.alias];
|
|
44580
45166
|
const project2 = boundNode2.id !== void 0 && boundNode2.id !== c.alias ? boundNode2.id : null;
|
|
44581
|
-
const source = declared?.source ? { path:
|
|
45167
|
+
const source = declared?.source ? { path: posix6(path47.relative(c.node.directory, boundNode2.directory)) } : null;
|
|
44582
45168
|
edit3(plan7, 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, project2, source] });
|
|
44583
45169
|
plan7.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.`);
|
|
44584
45170
|
return c;
|
|
@@ -44614,8 +45200,8 @@ function planExternalize(family, bound2, request) {
|
|
|
44614
45200
|
plan7,
|
|
44615
45201
|
bound2,
|
|
44616
45202
|
"move",
|
|
44617
|
-
`externalize ${id}: its specs into ${joined ? `the part "${joined.alias}"` : `a part at ${
|
|
44618
|
-
{ root: node.directory, call: "externalizeSubsystem", args: [id,
|
|
45203
|
+
`externalize ${id}: its specs into ${joined ? `the part "${joined.alias}"` : `a part at ${posix6(path47.relative(node.directory, dir))}, declared as "${id}"`} \u2014 a storage move; no reference, export or pin changes`,
|
|
45204
|
+
{ root: node.directory, call: "externalizeSubsystem", args: [id, posix6(path47.relative(node.directory, dir))] }
|
|
44619
45205
|
);
|
|
44620
45206
|
plan7.notes.push("Source code is not moved: move the subsystem's code yourself if it should live beside its specs.");
|
|
44621
45207
|
return plan7;
|
|
@@ -44624,7 +45210,7 @@ function planExternalize(family, bound2, request) {
|
|
|
44624
45210
|
crossingsBack(plan7, family, bound2, subKey);
|
|
44625
45211
|
checkSiblings(plan7, family, bound2, id, subKey, table);
|
|
44626
45212
|
if (plan7.refusals.length > 0) return plan7;
|
|
44627
|
-
edit3(plan7, bound2, "move", `externalize ${id} as a project: its specs into a member at ${
|
|
45213
|
+
edit3(plan7, bound2, "move", `externalize ${id} as a project: its specs into a member at ${posix6(path47.relative(node.directory, dir))}, declared as "${id}", then promoted`, { root: node.directory, call: "externalizeSubsystem", args: [id, posix6(path47.relative(node.directory, dir)), "project"] });
|
|
44628
45214
|
edit3(plan7, 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: [[]] });
|
|
44629
45215
|
plan7.notes.push("Source code is not moved: move the subsystem's code into the member yourself.");
|
|
44630
45216
|
return plan7;
|
|
@@ -44869,7 +45455,7 @@ function writeOne3(rehearsal, w) {
|
|
|
44869
45455
|
}
|
|
44870
45456
|
});
|
|
44871
45457
|
}
|
|
44872
|
-
var path47,
|
|
45458
|
+
var path47, posix6, label6, refuse3, edit3, KINDS_AT3;
|
|
44873
45459
|
var init_boundary = __esm({
|
|
44874
45460
|
"src/migrations/boundary.ts"() {
|
|
44875
45461
|
"use strict";
|
|
@@ -44881,7 +45467,7 @@ var init_boundary = __esm({
|
|
|
44881
45467
|
init_project();
|
|
44882
45468
|
init_project_family();
|
|
44883
45469
|
init_types2();
|
|
44884
|
-
|
|
45470
|
+
posix6 = (p) => p.split(path47.sep).join("/");
|
|
44885
45471
|
label6 = (key) => key === "" ? "the top project" : `"${key}"`;
|
|
44886
45472
|
refuse3 = (plan7, code, project2, detail) => {
|
|
44887
45473
|
plan7.refusals.push({ code, project: project2, detail });
|
|
@@ -44903,7 +45489,7 @@ var init_boundary = __esm({
|
|
|
44903
45489
|
auth: ["implementation"],
|
|
44904
45490
|
subsystem: ["component", "type"],
|
|
44905
45491
|
group: ["type"],
|
|
44906
|
-
type: ["interface", "type"]
|
|
45492
|
+
type: ["interface", "type", "implementation"]
|
|
44907
45493
|
};
|
|
44908
45494
|
}
|
|
44909
45495
|
});
|
|
@@ -45932,6 +46518,18 @@ function renderChangeReport(report4) {
|
|
|
45932
46518
|
lines.push(...renderTestsToRevisit(report4.testsToRevisit));
|
|
45933
46519
|
return lines.join("\n");
|
|
45934
46520
|
}
|
|
46521
|
+
function specKindsHolding(id) {
|
|
46522
|
+
const system = loadSystemSpec();
|
|
46523
|
+
const held = {
|
|
46524
|
+
system: !!system && (id === "system" || id === system.name),
|
|
46525
|
+
subsystem: !!loadSubsystemSpec(id),
|
|
46526
|
+
component: !!loadComponentSpec(id),
|
|
46527
|
+
interface: !!loadInterfaceSpec(id),
|
|
46528
|
+
implementation: !!loadImplementationSpec(id),
|
|
46529
|
+
type: !!loadTypeSpec(id)
|
|
46530
|
+
};
|
|
46531
|
+
return SPEC_KINDS.filter((k) => held[k]);
|
|
46532
|
+
}
|
|
45935
46533
|
function renderTestsToRevisit(entries) {
|
|
45936
46534
|
if (!entries.length) return [];
|
|
45937
46535
|
const lines = ["", "TESTS TO REVISIT:"];
|
|
@@ -46516,7 +47114,7 @@ ${renderChangeReport(report4)}`,
|
|
|
46516
47114
|
server,
|
|
46517
47115
|
"sdd_add_member",
|
|
46518
47116
|
{
|
|
46519
|
-
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
|
|
47117
|
+
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.",
|
|
46520
47118
|
inputSchema: {
|
|
46521
47119
|
alias: import_zod12.z.string().describe("The member's alias ([a-z0-9-_]+); a project's new id"),
|
|
46522
47120
|
source: import_zod12.z.string().describe("A contained path, a `../` sibling path, or a git URL (optionally `#<commit>`)"),
|
|
@@ -46586,7 +47184,7 @@ ${renderChangeReport(report4)}`,
|
|
|
46586
47184
|
server,
|
|
46587
47185
|
"sdd_externalize_subsystem",
|
|
46588
47186
|
{
|
|
46589
|
-
description: "Move an internal subsystem out to the path. By default
|
|
47187
|
+
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.",
|
|
46590
47188
|
inputSchema: {
|
|
46591
47189
|
subsystem: import_zod12.z.string().describe("The subsystem to move out"),
|
|
46592
47190
|
path: import_zod12.z.string().describe("The part's root, relative to the bound project (a new directory, or an existing part's)"),
|
|
@@ -46600,7 +47198,7 @@ ${renderChangeReport(report4)}`,
|
|
|
46600
47198
|
server,
|
|
46601
47199
|
"sdd_promote_member",
|
|
46602
47200
|
{
|
|
46603
|
-
description: "Promote a part of the bound project to an independent project in place
|
|
47201
|
+
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.",
|
|
46604
47202
|
inputSchema: {
|
|
46605
47203
|
alias: import_zod12.z.string().describe("The part's alias"),
|
|
46606
47204
|
id: import_zod12.z.string().optional().describe("The new project's id (default: the alias)"),
|
|
@@ -46613,7 +47211,7 @@ ${renderChangeReport(report4)}`,
|
|
|
46613
47211
|
server,
|
|
46614
47212
|
"sdd_demote_member",
|
|
46615
47213
|
{
|
|
46616
|
-
description: "Demote a project member of the bound project to a part in place
|
|
47214
|
+
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.",
|
|
46617
47215
|
inputSchema: {
|
|
46618
47216
|
alias: import_zod12.z.string().describe("The project member's alias"),
|
|
46619
47217
|
destination: import_zod12.z.object({
|
|
@@ -46956,7 +47554,7 @@ ${renderChangeReport(report4)}`,
|
|
|
46956
47554
|
interface: import_zod12.z.string().describe('The L3 interface ID (e.g. "ibilling-gateway")'),
|
|
46957
47555
|
endpoints: import_zod12.z.array(import_zod12.z.object({
|
|
46958
47556
|
method: import_zod12.z.string().describe("The NAME of the interface method to bind (not the HTTP verb)"),
|
|
46959
|
-
transport: import_zod12.z.enum(["HTTP", "gRPC", "GraphQL", "MessageBus", "NamedPipe", "IPC", "CLI", "Custom"]).describe("Wire protocol; must
|
|
47557
|
+
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"),
|
|
46960
47558
|
httpMethod: import_zod12.z.enum(["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"]).optional().describe("HTTP: verb"),
|
|
46961
47559
|
path: import_zod12.z.string().optional().describe('HTTP: route path, e.g. "/v1/checkout"'),
|
|
46962
47560
|
service: import_zod12.z.string().optional().describe("gRPC: service name"),
|
|
@@ -46979,8 +47577,10 @@ ${renderChangeReport(report4)}`,
|
|
|
46979
47577
|
try {
|
|
46980
47578
|
const intf = loadInterfaceSpec(interfaceId);
|
|
46981
47579
|
if (!intf) return errText(`Interface "${interfaceId}" does not exist.`);
|
|
46982
|
-
const
|
|
46983
|
-
|
|
47580
|
+
const owner = intf.component ? loadComponentSpec(intf.component) : null;
|
|
47581
|
+
const implied = owner?.componentType === "Portal" && owner.portalType ? PORTAL_TYPE_TRANSPORT[owner.portalType] : void 0;
|
|
47582
|
+
const buildRaw = (e, transport) => {
|
|
47583
|
+
switch (transport) {
|
|
46984
47584
|
case "HTTP":
|
|
46985
47585
|
return { transport: "HTTP", method: e.httpMethod, path: e.path };
|
|
46986
47586
|
case "gRPC":
|
|
@@ -47004,13 +47604,19 @@ ${renderChangeReport(report4)}`,
|
|
|
47004
47604
|
for (const e of endpoints) {
|
|
47005
47605
|
const m = intf.methods.find((x) => x.name === e.method);
|
|
47006
47606
|
if (!m) return errText(`Method "${e.method}" not found on interface "${interfaceId}". Define it via sdd_define_interface first.`);
|
|
47007
|
-
const
|
|
47607
|
+
const transport = e.transport === "HTTP_API" ? "HTTP" : e.transport;
|
|
47608
|
+
if (implied && transport !== implied) {
|
|
47609
|
+
return errText(
|
|
47610
|
+
`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.`
|
|
47611
|
+
);
|
|
47612
|
+
}
|
|
47613
|
+
const parsed = EndpointSchema.safeParse(buildRaw(e, transport));
|
|
47008
47614
|
if (!parsed.success) {
|
|
47009
47615
|
const detail = parsed.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ");
|
|
47010
|
-
return errText(`Invalid ${
|
|
47616
|
+
return errText(`Invalid ${transport} endpoint for method "${e.method}": ${detail}`);
|
|
47011
47617
|
}
|
|
47012
47618
|
methods.push({ name: e.method, endpoint: parsed.data });
|
|
47013
|
-
bound2.push(`${e.method}\u2192${
|
|
47619
|
+
bound2.push(`${e.method}\u2192${transport}`);
|
|
47014
47620
|
}
|
|
47015
47621
|
const report4 = updateSpecGated("interface", interfaceId, { methods });
|
|
47016
47622
|
return structured(
|
|
@@ -47271,14 +47877,24 @@ ${renderChangeReport(report4)}`,
|
|
|
47271
47877
|
{
|
|
47272
47878
|
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.`,
|
|
47273
47879
|
inputSchema: {
|
|
47274
|
-
kind: import_zod12.z.enum(["system", "subsystem", "component", "interface", "implementation", "type"]).describe("The kind of specification"),
|
|
47880
|
+
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"),
|
|
47275
47881
|
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")'),
|
|
47276
47882
|
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.")
|
|
47277
47883
|
},
|
|
47278
47884
|
outputSchema: getSpecOutput
|
|
47279
47885
|
},
|
|
47280
|
-
({ kind, id, methods }) => {
|
|
47886
|
+
({ kind: askedKind, id, methods }) => {
|
|
47281
47887
|
try {
|
|
47888
|
+
let kind;
|
|
47889
|
+
if (askedKind) {
|
|
47890
|
+
kind = askedKind;
|
|
47891
|
+
} else {
|
|
47892
|
+
const holders = specKindsHolding(id);
|
|
47893
|
+
if (holders.length !== 1) {
|
|
47894
|
+
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.`);
|
|
47895
|
+
}
|
|
47896
|
+
kind = holders[0];
|
|
47897
|
+
}
|
|
47282
47898
|
let result = null;
|
|
47283
47899
|
let partial2 = null;
|
|
47284
47900
|
switch (kind) {
|
|
@@ -47637,7 +48253,7 @@ async function startMcpServer() {
|
|
|
47637
48253
|
} catch {
|
|
47638
48254
|
}
|
|
47639
48255
|
}
|
|
47640
|
-
var import_mcp, import_stdio, import_zod12, import_types12, fs29, path50, import_url, crypto10, TYPE_REF_GRAMMAR, staleServerOutput, externalPinsOutput, externalStatusesOutput, SPEC_KINDS, STATUS_VALUES, testsToRevisitOutput, typeRespellingOutput, specWriteReceiptOutput, specDeletionOutput, specChangeOutput, specChangeReportOutput, validationIssueOutput, memberCreationOutput, doctrineChangeOutput, impactTotalsOutput, packImpactOutput, validateTreeOutput, getSpecOutput, FINGERPRINT_VERSION, runningFingerprint, RECONNECT, assessedAsChanged, serverBuildStamps, SERVER_BUILD_STAMP, SPEC_WRITE_TOOLS, listChangedEmitters, statusInput, SKILL_RESOURCE_MIME, AGENT_BRIEF_SCHEME;
|
|
48256
|
+
var import_mcp, import_stdio, import_zod12, import_types12, fs29, path50, import_url, crypto10, TYPE_REF_GRAMMAR, staleServerOutput, externalPinsOutput, externalStatusesOutput, SPEC_KINDS, PORTAL_TYPE_TRANSPORT, STATUS_VALUES, testsToRevisitOutput, typeRespellingOutput, specWriteReceiptOutput, specDeletionOutput, specChangeOutput, specChangeReportOutput, validationIssueOutput, memberCreationOutput, doctrineChangeOutput, impactTotalsOutput, packImpactOutput, validateTreeOutput, getSpecOutput, FINGERPRINT_VERSION, runningFingerprint, RECONNECT, assessedAsChanged, serverBuildStamps, SERVER_BUILD_STAMP, SPEC_WRITE_TOOLS, listChangedEmitters, statusInput, SKILL_RESOURCE_MIME, AGENT_BRIEF_SCHEME;
|
|
47641
48257
|
var init_server = __esm({
|
|
47642
48258
|
"src/mcp/server.ts"() {
|
|
47643
48259
|
"use strict";
|
|
@@ -47666,7 +48282,7 @@ var init_server = __esm({
|
|
|
47666
48282
|
init_project();
|
|
47667
48283
|
init_validation_options();
|
|
47668
48284
|
init_migrations3();
|
|
47669
|
-
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.`;
|
|
48285
|
+
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.`;
|
|
47670
48286
|
staleServerOutput = {
|
|
47671
48287
|
staleServer: import_zod12.z.boolean().optional().describe(
|
|
47672
48288
|
"True when the wairon build on disk changed after this server started \u2014 the structured twin of the text answer's STALE SERVER banner. When the rebuild left the spec and tool schemas alone, writes still go through; reconnect to run the new code."
|
|
@@ -47706,6 +48322,16 @@ var init_server = __esm({
|
|
|
47706
48322
|
...staleServerOutput
|
|
47707
48323
|
};
|
|
47708
48324
|
SPEC_KINDS = ["system", "subsystem", "component", "interface", "implementation", "type"];
|
|
48325
|
+
PORTAL_TYPE_TRANSPORT = {
|
|
48326
|
+
HTTP_API: "HTTP",
|
|
48327
|
+
gRPC: "gRPC",
|
|
48328
|
+
GraphQL: "GraphQL",
|
|
48329
|
+
MessageBus: "MessageBus",
|
|
48330
|
+
NamedPipe: "NamedPipe",
|
|
48331
|
+
IPC: "IPC",
|
|
48332
|
+
CLI: "CLI",
|
|
48333
|
+
Custom: void 0
|
|
48334
|
+
};
|
|
47709
48335
|
STATUS_VALUES = ["draft", "design", "complete"];
|
|
47710
48336
|
testsToRevisitOutput = import_zod12.z.array(import_zod12.z.object({
|
|
47711
48337
|
method: import_zod12.z.string().describe("The contract method this write changed or deleted."),
|
|
@@ -47820,7 +48446,7 @@ var init_server = __esm({
|
|
|
47820
48446
|
profile: import_zod12.z.string().optional()
|
|
47821
48447
|
})).describe("Requirements no installed version satisfies: nothing was written for them, and the next family run reports each as POLICY_NOT_ADOPTED."),
|
|
47822
48448
|
projectType: import_zod12.z.string().optional().describe("The profile written as the new member's projectType, when exactly one adopted requirement names one."),
|
|
47823
|
-
as: import_zod12.z.string().describe("part | project: what was created (
|
|
48449
|
+
as: import_zod12.z.string().describe("part | project: what was created (a part by default)."),
|
|
47824
48450
|
storage: import_zod12.z.string().describe("contained | path | git: where its files live."),
|
|
47825
48451
|
commit: import_zod12.z.string().optional().describe("git: the commit the member was pinned at."),
|
|
47826
48452
|
...staleServerOutput
|
|
@@ -48149,8 +48775,7 @@ function resolveInstallTargets(options) {
|
|
|
48149
48775
|
const isPackaged = typeof process.pkg !== "undefined";
|
|
48150
48776
|
const scriptPath = process.argv[1] ? path51.resolve(process.argv[1]).replace(/\\/g, "/") : null;
|
|
48151
48777
|
const useDirectNode = !isPackaged && scriptPath && (scriptPath.endsWith(".js") || scriptPath.endsWith(".ts"));
|
|
48152
|
-
const
|
|
48153
|
-
const desiredEntry = options.hostedUrl ? hostedMcpEntry(options) : useDirectNode ? { command: "node", args: [scriptPath, "mcp", "serve"], env } : { command: "wairon", args: ["mcp", "serve"], env };
|
|
48778
|
+
const desiredEntry = options.hostedUrl ? hostedMcpEntry(options) : useGlobal ? machineEntry(useDirectNode ? scriptPath : null) : projectEntry(root, useDirectNode ? scriptPath : null);
|
|
48154
48779
|
const existingEntry = mcpServers["wairon"];
|
|
48155
48780
|
return {
|
|
48156
48781
|
backend,
|
|
@@ -48165,6 +48790,16 @@ function resolveInstallTargets(options) {
|
|
|
48165
48790
|
};
|
|
48166
48791
|
});
|
|
48167
48792
|
}
|
|
48793
|
+
function machineEntry(scriptPath) {
|
|
48794
|
+
return scriptPath ? { command: "node", args: [scriptPath, "mcp", "serve"], env: {} } : { command: "wairon", args: ["mcp", "serve"], env: {} };
|
|
48795
|
+
}
|
|
48796
|
+
function projectEntry(root, scriptPath) {
|
|
48797
|
+
if (scriptPath && !isOutsideRoot(root, scriptPath)) {
|
|
48798
|
+
const relative28 = path51.relative(root, scriptPath).replace(/\\/g, "/");
|
|
48799
|
+
return { command: "node", args: [`./${relative28}`, "mcp", "serve"] };
|
|
48800
|
+
}
|
|
48801
|
+
return { command: "wairon", args: ["mcp", "serve"] };
|
|
48802
|
+
}
|
|
48168
48803
|
function planMcpInstall(options) {
|
|
48169
48804
|
return resolveInstallTargets(options).map(({ backend, path: file, outsideProject, before, after, upToDate }) => ({
|
|
48170
48805
|
backend,
|
|
@@ -48200,10 +48835,8 @@ function reportInstalled(target, agentLabel) {
|
|
|
48200
48835
|
const wasStale = target.before !== void 0;
|
|
48201
48836
|
logger.success(`wairon MCP server ${wasStale ? "updated (was stale)" : "registered"} for ${agentLabel} in ${import_chalk3.default.cyan(target.path)}.`);
|
|
48202
48837
|
logger.blank();
|
|
48203
|
-
logger.info("AI tools using this config
|
|
48204
|
-
logger.info(
|
|
48205
|
-
logger.info(" sdd_initialize_system \xB7 sdd_add_subsystem \xB7 sdd_add_component \xB7 sdd_define_interface");
|
|
48206
|
-
logger.info(" sdd_write_narrative \xB7 sdd_validate_tree \xB7 sdd_get_status");
|
|
48838
|
+
logger.info("AI tools using this config get the wairon MCP server: the sdd_* spec-authoring, validation, member and");
|
|
48839
|
+
logger.info('rename tools, plus live agent briefs (sdd_get_agent_brief). The full list is in docs/cli.md ("MCP tools").');
|
|
48207
48840
|
logger.blank();
|
|
48208
48841
|
const restartApp = target.backend === "gemini" ? "Antigravity CLI (agy)" : "claude";
|
|
48209
48842
|
logger.info(`Restart ${import_chalk3.default.bold(restartApp)} (or reload MCP servers) to activate.`);
|
|
@@ -48336,7 +48969,8 @@ init_skills_portal();
|
|
|
48336
48969
|
init_base();
|
|
48337
48970
|
init_fs();
|
|
48338
48971
|
init_project();
|
|
48339
|
-
var
|
|
48972
|
+
var AGENT_FILE_EXTENSION = /\.(md|ya?ml)$/;
|
|
48973
|
+
var WAIRON_AGENT_FILE = /-(owner|implementer|architect)\.(md|ya?ml)$/;
|
|
48340
48974
|
function pruneStaleAgents(expectedPaths, scanDirs, reach2) {
|
|
48341
48975
|
const dirs = new Set(scanDirs ?? [...expectedPaths].map((p) => path39.dirname(p)));
|
|
48342
48976
|
let pruned = 0;
|
|
@@ -48350,7 +48984,7 @@ function pruneStaleAgents(expectedPaths, scanDirs, reach2) {
|
|
|
48350
48984
|
continue;
|
|
48351
48985
|
}
|
|
48352
48986
|
for (const name of entries) {
|
|
48353
|
-
if (!
|
|
48987
|
+
if (!AGENT_FILE_EXTENSION.test(name)) continue;
|
|
48354
48988
|
const full = path39.resolve(dir, name);
|
|
48355
48989
|
if (expectedPaths.has(full)) continue;
|
|
48356
48990
|
let owned = WAIRON_AGENT_FILE.test(name);
|
|
@@ -48392,6 +49026,18 @@ function announceOutsideTargets(projectConfig, reach2) {
|
|
|
48392
49026
|
}
|
|
48393
49027
|
return outside.map((t) => t.type);
|
|
48394
49028
|
}
|
|
49029
|
+
function configuredTargetTypes(projectConfig) {
|
|
49030
|
+
const targets = projectConfig.targets;
|
|
49031
|
+
return [...new Set(targets.map((t) => typeof t === "string" ? t : t.type))];
|
|
49032
|
+
}
|
|
49033
|
+
var UnknownTargetError = class extends WaironError {
|
|
49034
|
+
constructor(target, known) {
|
|
49035
|
+
super(
|
|
49036
|
+
`Unknown target "${target}". This project configures: ${known.length ? known.join(", ") : "(none)"}. Pass one of those, or add a target to .wai/project.yaml (\`targets\`).`
|
|
49037
|
+
);
|
|
49038
|
+
this.name = "UnknownTargetError";
|
|
49039
|
+
}
|
|
49040
|
+
};
|
|
48395
49041
|
async function runGenerate(options = {}) {
|
|
48396
49042
|
await generateLayer(options);
|
|
48397
49043
|
const scoped = options.root || options.domain || options.domains;
|
|
@@ -48410,6 +49056,10 @@ async function generateLayer(options = {}) {
|
|
|
48410
49056
|
assertProjectInitialized();
|
|
48411
49057
|
const projectConfig = loadProjectConfig();
|
|
48412
49058
|
if (!projectConfig) throw new ProjectNotInitializedError();
|
|
49059
|
+
if (options.target !== void 0) {
|
|
49060
|
+
const known = configuredTargetTypes(projectConfig);
|
|
49061
|
+
if (!known.includes(options.target)) throw new UnknownTargetError(options.target, known);
|
|
49062
|
+
}
|
|
48413
49063
|
const agents = resolveAgentTopology();
|
|
48414
49064
|
if (agents.length === 0) {
|
|
48415
49065
|
logger.warn("No agents resolved from the spec tree. Define subsystems and components first \u2014 see `wairon status`.");
|
|
@@ -48426,6 +49076,8 @@ async function generateLayer(options = {}) {
|
|
|
48426
49076
|
const removed = pruneStaleAgents(/* @__PURE__ */ new Set(), candidateDirs, reach2);
|
|
48427
49077
|
if (removed > 0) {
|
|
48428
49078
|
logger.info(`Removed ${removed} previously materialized agent file(s) \u2014 agents are served as live briefs (sdd_get_agent_brief); opt back in with rules.materializeAgentFiles: true.`);
|
|
49079
|
+
} else {
|
|
49080
|
+
logger.info("Agent files: off (rules.materializeAgentFiles) \u2014 agents are served as live briefs (sdd_get_agent_brief); no agent files written.");
|
|
48429
49081
|
}
|
|
48430
49082
|
}
|
|
48431
49083
|
if (options.dryRun) return;
|
|
@@ -48444,6 +49096,7 @@ async function generateLayer(options = {}) {
|
|
|
48444
49096
|
try {
|
|
48445
49097
|
reinjectLocalGuides(getProjectRoot(), activeTargetTypes(projectConfig));
|
|
48446
49098
|
logger.verbose("Local AI guides re-injected.");
|
|
49099
|
+
logger.info(`Guides, skills and context reconciled for: ${activeTargetTypes(projectConfig).join(", ") || "(no active targets)"}.`);
|
|
48447
49100
|
} catch (err) {
|
|
48448
49101
|
logger.warn(`Failed to re-inject AI guides: ${String(err)}`);
|
|
48449
49102
|
}
|
|
@@ -48553,7 +49206,7 @@ function checkApproval(strict) {
|
|
|
48553
49206
|
return {
|
|
48554
49207
|
state: "stale",
|
|
48555
49208
|
approved: false,
|
|
48556
|
-
message: `The design, or something it was approved under
|
|
49209
|
+
message: `The design, or something it was approved under, changed after it was approved (the approval on record is ${record2.lockedAt} by ${describeApprover(record2.lockedBy)}). ${whatMoved(record2)} Nothing says a human has seen what is about to merge. Fix: run \`wairon lock\` on this branch and commit the updated .wai/lock.json.`
|
|
48557
49210
|
};
|
|
48558
49211
|
default:
|
|
48559
49212
|
return {
|
|
@@ -48563,13 +49216,34 @@ function checkApproval(strict) {
|
|
|
48563
49216
|
};
|
|
48564
49217
|
}
|
|
48565
49218
|
}
|
|
49219
|
+
function whatMoved(record2) {
|
|
49220
|
+
const diff3 = diffAgainstApproval();
|
|
49221
|
+
const own = diff3 ? diffSize(diff3) : null;
|
|
49222
|
+
const members = familyApprovals(1).filter((a) => a.parent === "" && a.as !== "part");
|
|
49223
|
+
const named2 = [];
|
|
49224
|
+
for (const m of members) {
|
|
49225
|
+
const name = m.alias ?? m.key;
|
|
49226
|
+
if (m.pinned === "unpinned") named2.push(`${name} (added since the approval)`);
|
|
49227
|
+
else if (m.pinned === "moved") named2.push(`${name} (its approval moved since this one was taken${m.state === "approved" ? "" : `; now ${m.state}`})`);
|
|
49228
|
+
else if (m.state !== "approved") named2.push(`${name} (${m.state} at its own root)`);
|
|
49229
|
+
}
|
|
49230
|
+
const current2 = new Set(members.map((m) => m.alias ?? m.key));
|
|
49231
|
+
for (const alias of Object.keys(record2.members ?? {})) {
|
|
49232
|
+
if (!current2.has(alias)) named2.push(`${alias} (removed since the approval)`);
|
|
49233
|
+
}
|
|
49234
|
+
const parts = [];
|
|
49235
|
+
if (own !== null && own > 0) parts.push(`${own} own spec file(s) changed since the approval`);
|
|
49236
|
+
if (named2.length > 0) parts.push(`direct member(s): ${named2.join(", ")}`);
|
|
49237
|
+
if (parts.length > 0) return `What moved: ${parts.join("; ")}.`;
|
|
49238
|
+
return own === null ? "This approval predates per-spec digests, so which spec moved cannot be said." : "No own spec file and no direct member's approval moved, so what changed is the doctrine, a declared input or `composition`.";
|
|
49239
|
+
}
|
|
48566
49240
|
function upgradedCheck(record2) {
|
|
48567
49241
|
const diff3 = diffAgainstApproval();
|
|
48568
49242
|
const specs = !diff3 ? "This approval predates per-spec digests, so whether a spec file moved since cannot be said." : diffSize(diff3) === 0 ? "No own spec file has changed since the approval." : `${diffSize(diff3)} own spec file(s) changed since the approval.`;
|
|
48569
49243
|
return {
|
|
48570
49244
|
state: "stale",
|
|
48571
49245
|
approved: false,
|
|
48572
|
-
message: `The approval on record (${record2.lockedAt} by ${describeApprover(record2.lockedBy)}) was taken under an earlier gate identity
|
|
49246
|
+
message: `The approval on record (${record2.lockedAt} by ${describeApprover(record2.lockedBy)}) was taken under an earlier gate identity (written by wairon ${record2.validatorVersion}). Since wairon 6.0.0 the gate identity also covers members' composition subjects and \`composition\`, and code conformance is recorded beside the claim. ${specs} Fix: re-lock once (\`wairon lock\`) and commit the updated .wai/lock.json.`
|
|
48573
49247
|
};
|
|
48574
49248
|
}
|
|
48575
49249
|
var LockRefusedError = class extends WaironError {
|
|
@@ -48579,7 +49253,7 @@ var LockRefusedError = class extends WaironError {
|
|
|
48579
49253
|
}
|
|
48580
49254
|
};
|
|
48581
49255
|
function describeMemberState(entry) {
|
|
48582
|
-
return `${entry.alias ?? entry.key} (${entry.state}${entry.upgraded ? " \u2014 approved under
|
|
49256
|
+
return `${entry.alias ?? entry.key} (${entry.state}${entry.upgraded ? " \u2014 approved under an earlier gate identity, re-lock it once" : ""})`;
|
|
48583
49257
|
}
|
|
48584
49258
|
function codeLine(code) {
|
|
48585
49259
|
if (!code) return "code: no code analysis recorded";
|
|
@@ -48588,9 +49262,14 @@ function codeLine(code) {
|
|
|
48588
49262
|
function summarize3(gate, members) {
|
|
48589
49263
|
const diff3 = diffAgainstApproval();
|
|
48590
49264
|
if (!diff3) {
|
|
48591
|
-
|
|
49265
|
+
const previous = readLockRecord();
|
|
49266
|
+
if (previous) {
|
|
49267
|
+
logger.info(`Replacing the approval on record (${previous.lockedAt} by ${describeApprover(previous.lockedBy)}, wairon ${previous.validatorVersion ?? "unknown"}). It records no per-spec digests, so the whole spec tree becomes the approved baseline.`);
|
|
49268
|
+
} else {
|
|
49269
|
+
logger.info("First approval of this tree \u2014 the whole spec tree becomes the approved baseline.");
|
|
49270
|
+
}
|
|
48592
49271
|
} else if (diffSize(diff3) === 0) {
|
|
48593
|
-
logger.info("Nothing has changed since the last approval \u2014 this
|
|
49272
|
+
logger.info("Nothing has changed since the last approval \u2014 this re-records the approval and refreshes the generated outputs of this project (guides, skills, and agent files when materialized).");
|
|
48594
49273
|
} else {
|
|
48595
49274
|
logger.info(`${diffSize(diff3)} spec(s) changed since the last approval:`);
|
|
48596
49275
|
for (const p of diff3.changed) logger.info(` ~ ${p}`);
|
|
@@ -48604,7 +49283,7 @@ function summarize3(gate, members) {
|
|
|
48604
49283
|
continue;
|
|
48605
49284
|
}
|
|
48606
49285
|
const pin2 = m.pinned === "moved" ? " \u2014 its pin moved since the last approval" : "";
|
|
48607
|
-
logger.info(` member ${m.alias ?? m.key}: ${m.state}${m.upgraded ? " (
|
|
49286
|
+
logger.info(` member ${m.alias ?? m.key}: ${m.state}${m.upgraded ? " (approved under an earlier gate identity, re-lock once)" : ""}${pin2}`);
|
|
48608
49287
|
}
|
|
48609
49288
|
}
|
|
48610
49289
|
async function runLock(options, gate, captured) {
|
|
@@ -48618,7 +49297,7 @@ async function runLock(options, gate, captured) {
|
|
|
48618
49297
|
);
|
|
48619
49298
|
}
|
|
48620
49299
|
logger.blank();
|
|
48621
|
-
logger.warn("This records the current design as approved
|
|
49300
|
+
logger.warn("This records the current design as approved in .wai/lock.json (no spec file is rewritten) and refreshes the generated outputs of this project.");
|
|
48622
49301
|
if (!options.yes) {
|
|
48623
49302
|
if (!process.stdin.isTTY) {
|
|
48624
49303
|
logger.error("Non-interactive shell \u2014 re-run with --yes to confirm the lock.");
|
|
@@ -48628,7 +49307,7 @@ async function runLock(options, gate, captured) {
|
|
|
48628
49307
|
{
|
|
48629
49308
|
type: "confirm",
|
|
48630
49309
|
name: "confirmed",
|
|
48631
|
-
message: "Approve this design
|
|
49310
|
+
message: "Approve this design?",
|
|
48632
49311
|
default: false
|
|
48633
49312
|
}
|
|
48634
49313
|
]);
|
|
@@ -48652,7 +49331,7 @@ async function runLock(options, gate, captured) {
|
|
|
48652
49331
|
const design = designOnly(gate);
|
|
48653
49332
|
const count = (severity) => design.issues.filter((i) => i.severity === severity).length;
|
|
48654
49333
|
const record2 = {
|
|
48655
|
-
// The record format: 2
|
|
49334
|
+
// The record format: 2 since wairon 6.0.0 (members, code, no children).
|
|
48656
49335
|
format: 2,
|
|
48657
49336
|
stateId: captured,
|
|
48658
49337
|
lockedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -48727,12 +49406,12 @@ function projectLabel(key, result) {
|
|
|
48727
49406
|
const root = result.projects?.find((p) => p.key === "");
|
|
48728
49407
|
return `${root?.id ?? "bound project"} (bound)`;
|
|
48729
49408
|
}
|
|
48730
|
-
function renderSpecFindings(result) {
|
|
49409
|
+
function renderSpecFindings(result, all = false) {
|
|
48731
49410
|
const tally = { errors: false, fatalWarnings: false, waived: 0, notices: 0 };
|
|
48732
49411
|
if (result.issues.length === 0) {
|
|
48733
49412
|
logger.success("Spec tree is valid and component type boundaries are enforced.");
|
|
48734
49413
|
}
|
|
48735
|
-
const MAX_PRINT = 100;
|
|
49414
|
+
const MAX_PRINT = all ? Number.POSITIVE_INFINITY : 100;
|
|
48736
49415
|
const printed = { error: 0, warning: 0, notice: 0 };
|
|
48737
49416
|
const skipped = { error: 0, warning: 0, notice: 0 };
|
|
48738
49417
|
for (const issue2 of result.issues) {
|
|
@@ -48752,10 +49431,11 @@ function renderSpecFindings(result) {
|
|
|
48752
49431
|
else if (issue2.severity === "notice") logger.notice(line2);
|
|
48753
49432
|
else logger.warn(line2);
|
|
48754
49433
|
}
|
|
48755
|
-
const more = (n, what) => `... and ${n} more ${what}(s)
|
|
49434
|
+
const more = (n, what) => `... and ${n} more ${what}(s) not shown (${printed[what]} of ${printed[what] + n} printed). Run with --all to print every finding.`;
|
|
48756
49435
|
if (skipped.error > 0) logger.error(more(skipped.error, "error"));
|
|
48757
49436
|
if (skipped.warning > 0) logger.warn(more(skipped.warning, "warning"));
|
|
48758
49437
|
if (skipped.notice > 0) logger.notice(more(skipped.notice, "notice"));
|
|
49438
|
+
if (skipped.error + skipped.warning + skipped.notice > 0 || all && result.issues.length > 100) renderCodeTotals(result);
|
|
48759
49439
|
if (result.projects) {
|
|
48760
49440
|
logger.blank();
|
|
48761
49441
|
logger.info("Per project (each its own gate):");
|
|
@@ -48767,6 +49447,20 @@ function renderSpecFindings(result) {
|
|
|
48767
49447
|
if (result.hint) logger.info(result.hint);
|
|
48768
49448
|
return tally;
|
|
48769
49449
|
}
|
|
49450
|
+
function renderCodeTotals(result) {
|
|
49451
|
+
const counts = /* @__PURE__ */ new Map();
|
|
49452
|
+
for (const issue2 of result.issues) {
|
|
49453
|
+
const key = `${issue2.severity} ${issue2.code}`;
|
|
49454
|
+
const entry = counts.get(key) ?? { severity: issue2.severity, code: issue2.code, n: 0 };
|
|
49455
|
+
entry.n++;
|
|
49456
|
+
counts.set(key, entry);
|
|
49457
|
+
}
|
|
49458
|
+
const rank = { error: 0, warning: 1, notice: 2 };
|
|
49459
|
+
const rows = [...counts.values()].sort((a, b) => (rank[a.severity] ?? 3) - (rank[b.severity] ?? 3) || b.n - a.n || a.code.localeCompare(b.code));
|
|
49460
|
+
logger.blank();
|
|
49461
|
+
logger.info("Findings by code:");
|
|
49462
|
+
for (const r of rows) logger.info(` ${String(r.n).padStart(6)} ${r.severity.padEnd(7)} ${r.code}`);
|
|
49463
|
+
}
|
|
48770
49464
|
async function runValidate(options = {}) {
|
|
48771
49465
|
assertProjectInitialized();
|
|
48772
49466
|
const projectConfig = loadProjectConfig();
|
|
@@ -48838,7 +49532,7 @@ async function runValidate(options = {}) {
|
|
|
48838
49532
|
};
|
|
48839
49533
|
const family = selectsFamily(sddOptions, declaredMembers(projectConfig).length > 0);
|
|
48840
49534
|
const sddResult = family ? validateFamily2(sddOptions) : validateProject2(sddOptions);
|
|
48841
|
-
const tally = renderSpecFindings(sddResult);
|
|
49535
|
+
const tally = renderSpecFindings(sddResult, options.all === true);
|
|
48842
49536
|
hasErrors ||= tally.errors;
|
|
48843
49537
|
hasFatalWarnings ||= tally.fatalWarnings;
|
|
48844
49538
|
waivedWarnings += tally.waived;
|
|
@@ -48946,6 +49640,9 @@ async function runStatus(options = {}) {
|
|
|
48946
49640
|
logger.blank();
|
|
48947
49641
|
if (lock.drifted) logger.warn(lock.text.trim());
|
|
48948
49642
|
else logger.info(lock.text.trim());
|
|
49643
|
+
logger.info(import_chalk4.default.gray(
|
|
49644
|
+
"The percentages measure authoring readiness (each spec's draft/design/complete status), not approval: approval is the lock record above (.wai/lock.json), and `wairon lock` does not change any spec's status."
|
|
49645
|
+
));
|
|
48949
49646
|
}
|
|
48950
49647
|
logger.blank();
|
|
48951
49648
|
}
|
|
@@ -49080,14 +49777,14 @@ var http = __toESM(require("http"));
|
|
|
49080
49777
|
var https = __toESM(require("https"));
|
|
49081
49778
|
init_defaults();
|
|
49082
49779
|
function downloadFile(url, dest) {
|
|
49083
|
-
return new Promise((
|
|
49780
|
+
return new Promise((resolve56, reject) => {
|
|
49084
49781
|
const file = fs31.createWriteStream(dest);
|
|
49085
49782
|
const get4 = url.startsWith("https://") ? https.get : http.get;
|
|
49086
49783
|
get4(url, { headers: { "User-Agent": `wairon/${WAIRON_VERSION}` }, agent: false }, (res) => {
|
|
49087
49784
|
if (res.statusCode === 301 || res.statusCode === 302) {
|
|
49088
49785
|
file.close();
|
|
49089
49786
|
res.destroy();
|
|
49090
|
-
downloadFile(res.headers.location, dest).then(
|
|
49787
|
+
downloadFile(res.headers.location, dest).then(resolve56).catch(reject);
|
|
49091
49788
|
return;
|
|
49092
49789
|
}
|
|
49093
49790
|
if (res.statusCode !== 200) {
|
|
@@ -49099,7 +49796,7 @@ function downloadFile(url, dest) {
|
|
|
49099
49796
|
res.pipe(file);
|
|
49100
49797
|
file.on("finish", () => {
|
|
49101
49798
|
res.destroy();
|
|
49102
|
-
file.close(() =>
|
|
49799
|
+
file.close(() => resolve56());
|
|
49103
49800
|
});
|
|
49104
49801
|
file.on("error", (err) => {
|
|
49105
49802
|
res.destroy();
|
|
@@ -49593,7 +50290,12 @@ function writeBundle(root, resolved) {
|
|
|
49593
50290
|
const dest = path52.join(root, ".wai", "packs", resolved.name, resolved.version);
|
|
49594
50291
|
fs32.rmSync(dest, { recursive: true, force: true });
|
|
49595
50292
|
fs32.mkdirSync(path52.dirname(dest), { recursive: true });
|
|
49596
|
-
fs32.
|
|
50293
|
+
if (fs32.statSync(resolved.path).isFile()) {
|
|
50294
|
+
fs32.mkdirSync(dest, { recursive: true });
|
|
50295
|
+
fs32.copyFileSync(resolved.path, path52.join(dest, `pack${path52.extname(resolved.path).toLowerCase()}`));
|
|
50296
|
+
} else {
|
|
50297
|
+
fs32.cpSync(resolved.path, dest, { recursive: true });
|
|
50298
|
+
}
|
|
49597
50299
|
return dest;
|
|
49598
50300
|
}
|
|
49599
50301
|
function bundlePack(name, options = {}) {
|
|
@@ -50375,7 +51077,7 @@ async function runLock2(options) {
|
|
|
50375
51077
|
}
|
|
50376
51078
|
}
|
|
50377
51079
|
if (skippedErrors > 0) {
|
|
50378
|
-
logger.error(`... and ${skippedErrors} more error(s)
|
|
51080
|
+
logger.error(`... and ${skippedErrors} more error(s) not shown (${errorCount} of ${errors.length} printed). Run \`wairon validate --all\` to see every finding (it judges draft specs leniently; the lock judges them as complete).`);
|
|
50379
51081
|
}
|
|
50380
51082
|
logger.blank();
|
|
50381
51083
|
logger.info("Fix the errors above, then run `wairon lock` again. Nothing was changed.");
|
|
@@ -50427,7 +51129,7 @@ async function validateCommand(opts) {
|
|
|
50427
51129
|
for (const t of recover3(getProjectRoot(), false)) {
|
|
50428
51130
|
logger.notice(`[TRANSACTION_PENDING] an unfinished family migration (${t.verb}, transaction ${t.id}, coordinator phase ${t.phase}) \u2014 run \`wairon doctor --fix\` to roll it back`);
|
|
50429
51131
|
}
|
|
50430
|
-
await runValidate({ ci: opts.ci, subsystem: opts.subsystem, recursive: opts.recursive, family: opts.family });
|
|
51132
|
+
await runValidate({ ci: opts.ci, subsystem: opts.subsystem, recursive: opts.recursive, family: opts.family, all: opts.all });
|
|
50431
51133
|
}
|
|
50432
51134
|
async function statusCommand(opts) {
|
|
50433
51135
|
const target = resolveTarget(getProjectRoot(), {});
|
|
@@ -50874,6 +51576,10 @@ var SCREENFUL = 20;
|
|
|
50874
51576
|
function shownDir(dir) {
|
|
50875
51577
|
return nodePath3.relative(process.cwd(), dir) || ".";
|
|
50876
51578
|
}
|
|
51579
|
+
function relockLabel(dir) {
|
|
51580
|
+
const shown2 = shownDir(dir);
|
|
51581
|
+
return shown2 === "." ? "this project (.)" : shown2;
|
|
51582
|
+
}
|
|
50877
51583
|
function printPlan(planned) {
|
|
50878
51584
|
logger.header(`wairon family migration: ${planned.request.verb}`);
|
|
50879
51585
|
logger.info(`Family root: ${shownDir(planned.familyRoot)}${planned.whole ? "" : " (not the whole family: the reach stops below its top)"}`);
|
|
@@ -50884,7 +51590,7 @@ function printPlan(planned) {
|
|
|
50884
51590
|
for (const r of planned.refusals) logger.error(`[${r.code}]${r.project ? ` ${ownerLabel(r.project)}:` : ""} ${r.detail}`);
|
|
50885
51591
|
for (const note of planned.notes) logger.warn(`NOTE: ${note}`);
|
|
50886
51592
|
printFiles(planned);
|
|
50887
|
-
if (planned.relock.length > 0) logger.info(`To re-lock once applied: ${planned.relock.map(
|
|
51593
|
+
if (planned.relock.length > 0) logger.info(`To re-lock once applied: ${planned.relock.map(relockLabel).join(", ")}`);
|
|
50888
51594
|
}
|
|
50889
51595
|
function printFiles(planned) {
|
|
50890
51596
|
if (planned.changes.length === 0) return;
|
|
@@ -50913,7 +51619,7 @@ function printOutcome(report4) {
|
|
|
50913
51619
|
const verb = report4.plan.request.verb;
|
|
50914
51620
|
if (report4.applied) {
|
|
50915
51621
|
logger.success(`Applied the ${verb} migration: ${report4.plan.changes.length} file(s) across ${new Set(report4.plan.changes.map((c) => c.project)).size} project(s).`);
|
|
50916
|
-
for (const dir of report4.relock) logger.info(`Re-lock ${
|
|
51622
|
+
for (const dir of report4.relock) logger.info(`Re-lock ${relockLabel(dir)}: run \`wairon lock\` ${shownDir(dir) === "." ? "here" : "there"}.`);
|
|
50917
51623
|
return;
|
|
50918
51624
|
}
|
|
50919
51625
|
process.exitCode = 1;
|
|
@@ -50980,9 +51686,14 @@ async function runInit(options = {}) {
|
|
|
50980
51686
|
const ancestorRoot = findSystemRoot(cwd);
|
|
50981
51687
|
if (ancestorRoot && path58.resolve(ancestorRoot) === path58.resolve(cwd)) {
|
|
50982
51688
|
logger.info("Project already initialized.");
|
|
50983
|
-
logger.info("Design your spec tree with the SDD architect skill, then run `wairon
|
|
51689
|
+
logger.info("Design your spec tree with the SDD architect skill, validate it, then run `wairon lock` to approve it.");
|
|
50984
51690
|
return;
|
|
50985
51691
|
}
|
|
51692
|
+
if (!options.yes && !process.stdin.isTTY) {
|
|
51693
|
+
throw new WaironError(
|
|
51694
|
+
"Non-interactive shell: `wairon init` asks questions and there is no terminal to ask them on. Re-run with --yes (-y) to use the defaults (project name from the directory, profile backend, targets claude and agy). Nothing was written."
|
|
51695
|
+
);
|
|
51696
|
+
}
|
|
50986
51697
|
if (ancestorRoot) {
|
|
50987
51698
|
await runInitAsMember(ancestorRoot, cwd, options);
|
|
50988
51699
|
return;
|
|
@@ -51251,13 +51962,17 @@ async function executeInit(projectName, projectConfig, guidePlan) {
|
|
|
51251
51962
|
logger.warn(`Failed to automatically register MCP server for Claude: ${err}`);
|
|
51252
51963
|
}
|
|
51253
51964
|
}
|
|
51254
|
-
if (targetTypes.includes("gemini")
|
|
51965
|
+
if (targetTypes.includes("gemini")) {
|
|
51255
51966
|
try {
|
|
51256
51967
|
const { runMcpInstall: runMcpInstall2 } = (init_mcp(), __toCommonJS(mcp_exports));
|
|
51257
51968
|
runMcpInstall2({ backend: "gemini", global: false });
|
|
51258
51969
|
} catch (err) {
|
|
51259
|
-
logger.warn(`Failed to automatically register MCP server for
|
|
51970
|
+
logger.warn(`Failed to automatically register MCP server for the Gemini CLI: ${err}`);
|
|
51260
51971
|
}
|
|
51972
|
+
} else if (targetTypes.includes("agy")) {
|
|
51973
|
+
logger.info(
|
|
51974
|
+
`Antigravity (agy) reads MCP servers only from its machine-wide ~/.gemini/antigravity-cli/mcp_config.json, which init does not write. To give it the wairon tools: ${import_chalk11.default.bold("wairon mcp install --backend gemini --global")}`
|
|
51975
|
+
);
|
|
51261
51976
|
}
|
|
51262
51977
|
syncContextFiles();
|
|
51263
51978
|
logger.verbose("Context files seeded in .wai/context/");
|
|
@@ -51281,9 +51996,10 @@ async function executeInit(projectName, projectConfig, guidePlan) {
|
|
|
51281
51996
|
logger.blank();
|
|
51282
51997
|
logger.info("Next steps:");
|
|
51283
51998
|
logger.info(" Edit .wai/context/project.md \u2014 describe the project concept and stack details for the AI");
|
|
51284
|
-
logger.info(" wairon status \u2014 view the architecture completeness dashboard");
|
|
51285
|
-
logger.info(" wairon validate \u2014 check the spec tree & component boundaries");
|
|
51286
51999
|
logger.info(" Design your spec tree with the SDD architect skill or the wairon MCP sdd_* tools");
|
|
52000
|
+
logger.info(" wairon status \u2014 view the spec tree, its authoring readiness and approval state");
|
|
52001
|
+
logger.info(" wairon validate \u2014 check the spec tree & component boundaries");
|
|
52002
|
+
logger.info(" wairon lock \u2014 approve the validated design (commit .wai/lock.json); `wairon lock-check` gates CI on it");
|
|
51287
52003
|
logger.blank();
|
|
51288
52004
|
}
|
|
51289
52005
|
function buildProjectConfig(name, targets, now, projectType) {
|
|
@@ -51366,11 +52082,13 @@ Define the non-negotiable architectural guardrails here. The AI agent must follo
|
|
|
51366
52082
|
|
|
51367
52083
|
* [ ] **Primary Language & Runtime:** Node.js (TypeScript) / Python / etc.
|
|
51368
52084
|
* [ ] **Architectural Style:** Clean Architecture / Hexagonal / Domain-Driven Design (DDD).
|
|
51369
|
-
* [ ] **Data Persistence Rules:** E.g. No raw SQL in controllers;
|
|
51370
|
-
* [ ] **Stereotype Dependencies
|
|
51371
|
-
* \`
|
|
51372
|
-
* \`
|
|
51373
|
-
*
|
|
52085
|
+
* [ ] **Data Persistence Rules:** E.g. No raw SQL in controllers; held state lives in a data component \u2014 the recommended \`Repository\` (owning its \`Store\`, \`Registry\`, \`Index\`es and \`Query\`s), or a deliberately standalone \`Store\` for genuinely simple state.
|
|
52086
|
+
* [ ] **Stereotype Dependencies** (enforced by \`sdd_validate_tree\`):
|
|
52087
|
+
* Only \`Portal\` components accept external traffic; a gateway is a \`Portal\` with the \`gateway\` variant.
|
|
52088
|
+
* A \`Portal\` never depends on a \`Store\`, \`Registry\` or \`Adapter\`. Its reads may go through a \`Repository\` / \`Index\` facade; every write routes through an \`Orchestrator\`.
|
|
52089
|
+
* State never lives as fields inside an \`Orchestrator\`; logic is an \`Orchestrator\` (\`dependencyClass: pure | read\` bounds what it may depend on).
|
|
52090
|
+
* A \`Registry\` and an \`Index\` depend on the \`Store\` they read; a \`Store\` may use an \`Adapter\` as its backend.
|
|
52091
|
+
* Outbound calls to third parties go through an \`Adapter\`.
|
|
51374
52092
|
|
|
51375
52093
|
---
|
|
51376
52094
|
|
|
@@ -51383,15 +52101,16 @@ Define the non-negotiable architectural guardrails here. The AI agent must follo
|
|
|
51383
52101
|
|
|
51384
52102
|
---
|
|
51385
52103
|
|
|
51386
|
-
## Stage 3: Ingress
|
|
52104
|
+
## Stage 3: Ingress Portals & Outbound Adapters (Level 2 & Level 3)
|
|
51387
52105
|
|
|
51388
|
-
Portals are
|
|
52106
|
+
Portals are where requests enter a subsystem; Adapters are where calls leave it.
|
|
51389
52107
|
|
|
51390
52108
|
* [ ] **Define Ingress Portals (REST / gRPC / MessageBus):**
|
|
51391
52109
|
* *AI Action:* Create L2 Portal components with \`status: draft\` and map their L3 interfaces.
|
|
51392
|
-
* *Design check:*
|
|
51393
|
-
*
|
|
51394
|
-
|
|
52110
|
+
* *Design check:* Bind each method's wire endpoint with \`sdd_set_endpoints\`, and give every HTTP Portal its \`mounts\` (or a listener) \u2014 otherwise \`UNMOUNTED_PORTAL\`.
|
|
52111
|
+
* *Design check:* A subsystem another subsystem calls lists those callers in its \`publicInterfaces[].consumers\`.
|
|
52112
|
+
* [ ] **Define Outbound Adapters (third-party clients / publishers):**
|
|
52113
|
+
* *AI Action:* Declare an \`Adapter\` for each external API, database driver or event publisher the subsystem calls.
|
|
51395
52114
|
|
|
51396
52115
|
---
|
|
51397
52116
|
|
|
@@ -51416,13 +52135,13 @@ Map the behavior step-by-step.
|
|
|
51416
52135
|
|
|
51417
52136
|
---
|
|
51418
52137
|
|
|
51419
|
-
## Stage 6:
|
|
52138
|
+
## Stage 6: Approval & Implementation
|
|
51420
52139
|
|
|
51421
|
-
Once the specs
|
|
52140
|
+
Once the specs validate cleanly, the human approves the design with \`wairon lock\`; implementation starts from the approved design.
|
|
51422
52141
|
|
|
51423
52142
|
* [ ] **Validation Check (AI):** Call the \`sdd_validate_tree\` MCP tool (must return 0 errors).
|
|
51424
|
-
* [ ] **
|
|
51425
|
-
* [ ] **Code Implementation:**
|
|
52143
|
+
* [ ] **Approval (human):** The developer runs \`wairon lock\` from their terminal and commits \`.wai/lock.json\` \u2014 the AI does not run this. The lock records the approval; it does not rewrite spec files or their \`status\`. \`wairon lock-check\` gates merges on it in CI.
|
|
52144
|
+
* [ ] **Code Implementation (AI):** Confirm \`sdd_get_status\` reports the design approved, then delegate each component via the \`sdd-delegate\` skill (live briefs from \`sdd_get_agent_brief\`); code must match the L3 interfaces and L5 narratives.
|
|
51426
52145
|
`;
|
|
51427
52146
|
writeFile(fromProjectRoot(".wai", "phased_design.md"), content);
|
|
51428
52147
|
}
|
|
@@ -51723,7 +52442,7 @@ function releaseChannelLabel(tag) {
|
|
|
51723
52442
|
return version.slice(dash + 1).replace(/\.\d+$/, "") || "stable";
|
|
51724
52443
|
}
|
|
51725
52444
|
function fetchReleases(repo) {
|
|
51726
|
-
return new Promise((
|
|
52445
|
+
return new Promise((resolve56, reject) => {
|
|
51727
52446
|
const url = `https://api.github.com/repos/${repo}/releases?per_page=100`;
|
|
51728
52447
|
const options = {
|
|
51729
52448
|
headers: {
|
|
@@ -51743,7 +52462,7 @@ function fetchReleases(repo) {
|
|
|
51743
52462
|
return;
|
|
51744
52463
|
}
|
|
51745
52464
|
try {
|
|
51746
|
-
|
|
52465
|
+
resolve56(JSON.parse(data));
|
|
51747
52466
|
} catch {
|
|
51748
52467
|
reject(new Error("Failed to parse GitHub API response"));
|
|
51749
52468
|
}
|
|
@@ -51915,7 +52634,7 @@ async function filteredCheckbox(config) {
|
|
|
51915
52634
|
32,
|
|
51916
52635
|
Math.max(...items.map((i) => i.label.length))
|
|
51917
52636
|
);
|
|
51918
|
-
return new Promise((
|
|
52637
|
+
return new Promise((resolve56) => {
|
|
51919
52638
|
const checked = /* @__PURE__ */ new Set();
|
|
51920
52639
|
let cursor = 0;
|
|
51921
52640
|
let filterIdx = 0;
|
|
@@ -51982,7 +52701,7 @@ async function filteredCheckbox(config) {
|
|
|
51982
52701
|
);
|
|
51983
52702
|
teardown();
|
|
51984
52703
|
const result = items.filter((_, idx) => checked.has(idx)).map((i) => i.value);
|
|
51985
|
-
|
|
52704
|
+
resolve56(result);
|
|
51986
52705
|
}
|
|
51987
52706
|
function abort() {
|
|
51988
52707
|
process.stdout.write("\n");
|
|
@@ -52446,7 +53165,7 @@ function mcpEntryHealth(settingsPath) {
|
|
|
52446
53165
|
if (!entry) return { mark: "warn", note: "not registered" };
|
|
52447
53166
|
if (entry.command === "node" && Array.isArray(entry.args) && typeof entry.args[0] === "string") {
|
|
52448
53167
|
const scriptPath = entry.args[0];
|
|
52449
|
-
if (!fs38.existsSync(scriptPath)) {
|
|
53168
|
+
if (!fs38.existsSync(path61.resolve(getProjectRoot(), scriptPath))) {
|
|
52450
53169
|
return { mark: "error", note: `registered but the server path is missing \u2014 ${scriptPath}` };
|
|
52451
53170
|
}
|
|
52452
53171
|
}
|
|
@@ -52512,15 +53231,26 @@ async function runDoctor(options = {}) {
|
|
|
52512
53231
|
try {
|
|
52513
53232
|
const cfg = loadProjectConfig();
|
|
52514
53233
|
if (!cfg) throw new ProjectNotInitializedError();
|
|
52515
|
-
const result =
|
|
52516
|
-
const
|
|
52517
|
-
const
|
|
52518
|
-
const notes = result.issues.filter((i) => i.severity === "notice").length;
|
|
53234
|
+
const result = validateFamily2({ rules: cfg.rules, projectType: cfg.projectType });
|
|
53235
|
+
const counts = severityCounts(result.issues);
|
|
53236
|
+
const { errs, warns, notes } = counts;
|
|
52519
53237
|
const noted = notes > 0 ? `, ${notes} notice(s)` : "";
|
|
52520
53238
|
if (errs > 0) line(tally, "error", `Conformance: ${errs} error(s), ${warns} warning(s)${noted} \u2014 see \`wairon validate\` (you: \`sdd_validate_tree\`)`);
|
|
52521
53239
|
else if (warns > 0) line(tally, "warn", `Conformance: ${warns} warning(s)${noted} \u2014 see \`wairon validate\` (you: \`sdd_validate_tree\`)`);
|
|
52522
53240
|
else if (notes > 0) line(tally, "ok", `Conformance: 0 errors, 0 warnings, ${notes} notice(s) \u2014 see \`wairon validate\` (you: \`sdd_validate_tree\`)`);
|
|
52523
53241
|
else line(tally, "ok", "Conformance: 0 errors, 0 warnings");
|
|
53242
|
+
const byProject = /* @__PURE__ */ new Map();
|
|
53243
|
+
for (const issue2 of result.issues) {
|
|
53244
|
+
const key = issue2.project ?? "";
|
|
53245
|
+
byProject.set(key, [...byProject.get(key) ?? [], issue2]);
|
|
53246
|
+
}
|
|
53247
|
+
if (byProject.size > 1 || byProject.size === 1 && !byProject.has("")) {
|
|
53248
|
+
for (const [key, issues] of [...byProject].sort(([a], [b]) => a.localeCompare(b))) {
|
|
53249
|
+
const own = severityCounts(issues);
|
|
53250
|
+
if (own.errs === 0 && own.warns === 0 && own.notes === 0) continue;
|
|
53251
|
+
console.log(` ${key === "" ? "this project" : `member ${key}`}: ${own.errs} error(s), ${own.warns} warning(s), ${own.notes} notice(s)`);
|
|
53252
|
+
}
|
|
53253
|
+
}
|
|
52524
53254
|
} catch (e) {
|
|
52525
53255
|
line(tally, "warn", `Could not run conformance check: ${e instanceof Error ? e.message : String(e)}`);
|
|
52526
53256
|
}
|
|
@@ -52590,6 +53320,11 @@ async function runDoctor(options = {}) {
|
|
|
52590
53320
|
line(tally, "warn", `${proposal.kind} ${proposal.specId} ${proposal.path}: "${proposal.written}" \u2192 ${proposal.stored} (proposed)`);
|
|
52591
53321
|
}
|
|
52592
53322
|
}
|
|
53323
|
+
const enumProposals = spellings.flatMap((repair) => repair.enumProposals);
|
|
53324
|
+
if (enumProposals.length > 0) {
|
|
53325
|
+
console.log(` ${import_chalk17.default.gray("enum proposed \u2014 never written: define the enum, then write its id here")}`);
|
|
53326
|
+
for (const proposal of enumProposals) line(tally, "warn", describeEnumProposal(proposal));
|
|
53327
|
+
}
|
|
52593
53328
|
if (authorNeeded.length > 0) {
|
|
52594
53329
|
console.log(` ${import_chalk17.default.gray("needs an author \u2014 no rewrite can settle these")}`);
|
|
52595
53330
|
for (const problem of authorNeeded) {
|
|
@@ -52600,6 +53335,11 @@ async function runDoctor(options = {}) {
|
|
|
52600
53335
|
} catch (e) {
|
|
52601
53336
|
line(tally, "warn", `Could not plan the type-spelling repair: ${e instanceof Error ? e.message : String(e)}`);
|
|
52602
53337
|
}
|
|
53338
|
+
try {
|
|
53339
|
+
reportMemberRepairs(tally);
|
|
53340
|
+
} catch (e) {
|
|
53341
|
+
line(tally, "warn", `Could not plan the members' repairs: ${e instanceof Error ? e.message : String(e)}`);
|
|
53342
|
+
}
|
|
52603
53343
|
try {
|
|
52604
53344
|
const planned = plan5({ verb: "chaining", rehearse: false });
|
|
52605
53345
|
const pending4 = planned.chaining;
|
|
@@ -52668,7 +53408,7 @@ async function runDoctor(options = {}) {
|
|
|
52668
53408
|
line(
|
|
52669
53409
|
tally,
|
|
52670
53410
|
"warn",
|
|
52671
|
-
`stale \u2014 the lock of ${lock.record.lockedAt} was taken under an earlier gate identity: the gate identity gained inputs in
|
|
53411
|
+
`stale \u2014 the lock of ${lock.record.lockedAt} was taken under an earlier gate identity: the gate identity gained inputs in this release (members' composition subjects, \`composition\`; code conformance moved beside the claim). Re-lock once (\`wairon lock\`).`
|
|
52672
53412
|
);
|
|
52673
53413
|
} else if (lock.state === "stale") {
|
|
52674
53414
|
console.log(import_chalk17.default.bold("Lock"));
|
|
@@ -52733,9 +53473,13 @@ async function runDoctor(options = {}) {
|
|
|
52733
53473
|
line(tally, h.mark, `Claude (project .mcp.json): ${h.note}${h.mark === "ok" ? "" : " \u2014 run `wairon mcp install --backend claude`"}`);
|
|
52734
53474
|
}
|
|
52735
53475
|
if (wantGemini) {
|
|
52736
|
-
const
|
|
52737
|
-
|
|
52738
|
-
|
|
53476
|
+
const agyHome = path61.join(os13.homedir(), ".gemini", "antigravity-cli");
|
|
53477
|
+
if (!fs38.existsSync(agyHome)) {
|
|
53478
|
+
line(tally, "ok", `Antigravity: not installed on this machine (no ${agyHome}) \u2014 nothing to register. Once it is: \`wairon mcp install --backend gemini --global\``);
|
|
53479
|
+
} else {
|
|
53480
|
+
const hg = mcpEntryHealth(path61.join(agyHome, "mcp_config.json"));
|
|
53481
|
+
line(tally, hg.mark, `Antigravity (global mcp_config.json): ${hg.note}${hg.mark === "ok" ? "" : " \u2014 run `wairon mcp install --backend gemini --global`"}`);
|
|
53482
|
+
}
|
|
52739
53483
|
const projPath = fromProjectRoot(".gemini", "settings.json");
|
|
52740
53484
|
if (fs38.existsSync(projPath)) {
|
|
52741
53485
|
const hp = mcpEntryHealth(projPath);
|
|
@@ -52771,14 +53515,7 @@ async function applyFixes(options, tally) {
|
|
|
52771
53515
|
} catch (e) {
|
|
52772
53516
|
console.log(` ${icon("error")} Regeneration failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
52773
53517
|
}
|
|
52774
|
-
|
|
52775
|
-
const pinned = pinInstalledPacksAsSelections();
|
|
52776
|
-
if (pinned.length > 0) {
|
|
52777
|
-
console.log(` ${icon("ok")} Recorded ${pinned.length} installed pack(s) as explicit selections: ${pinned.join(", ")}.`);
|
|
52778
|
-
}
|
|
52779
|
-
} catch (e) {
|
|
52780
|
-
console.log(` ${icon("error")} Could not record pack selections: ${e instanceof Error ? e.message : String(e)}`);
|
|
52781
|
-
}
|
|
53518
|
+
pinPackSelections("");
|
|
52782
53519
|
try {
|
|
52783
53520
|
const legacySpecs = findLegacySpecFiles();
|
|
52784
53521
|
if (legacySpecs.length > 0) {
|
|
@@ -52798,63 +53535,157 @@ async function applyFixes(options, tally) {
|
|
|
52798
53535
|
} catch (e) {
|
|
52799
53536
|
console.log(` ${icon("error")} Chained-subproject backfill failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
52800
53537
|
}
|
|
53538
|
+
repairProjectSpecs("");
|
|
53539
|
+
await migrateChaining(options, tally);
|
|
53540
|
+
repairMembers();
|
|
53541
|
+
await repairMcpRegistration(options, targets);
|
|
53542
|
+
logger.blank();
|
|
53543
|
+
}
|
|
53544
|
+
function whoPrefix(who) {
|
|
53545
|
+
return who === "" ? "" : `[${who}] `;
|
|
53546
|
+
}
|
|
53547
|
+
function severityCounts(issues) {
|
|
53548
|
+
return {
|
|
53549
|
+
errs: issues.filter((i) => i.severity === "error").length,
|
|
53550
|
+
warns: issues.filter((i) => i.severity === "warning").length,
|
|
53551
|
+
notes: issues.filter((i) => i.severity === "notice").length
|
|
53552
|
+
};
|
|
53553
|
+
}
|
|
53554
|
+
function describeEnumProposal(p) {
|
|
53555
|
+
return `${p.kind} ${p.specId} ${p.path}: "${p.written}" \u2192 enum ${p.enumId} (${p.values.join(", ")}), written as ${p.enumId}${p.optional ? "?" : ""} (proposed)`;
|
|
53556
|
+
}
|
|
53557
|
+
function pinPackSelections(who) {
|
|
53558
|
+
try {
|
|
53559
|
+
const pinned = pinInstalledPacksAsSelections();
|
|
53560
|
+
if (pinned.length === 0) return;
|
|
53561
|
+
console.log(` ${icon("ok")} ${whoPrefix(who)}Recorded ${pinned.length} installed pack(s) as explicit selections: ${pinned.join(", ")}.`);
|
|
53562
|
+
const pinnedNames = new Set(pinned.map((label7) => label7.replace(/@[^@]*$/, "")));
|
|
53563
|
+
const unobtainable = (loadProjectConfig()?.extensions?.packs ?? []).filter((e) => typeof e !== "string").filter((e) => pinnedNames.has(e.name) && !e.source && !e.bundle).map((e) => e.name);
|
|
53564
|
+
if (unobtainable.length > 0) {
|
|
53565
|
+
console.log(` ${icon("warn")} ${whoPrefix(who)}${unobtainable.length} of them record no fetchable source (installed from a local path), so CI and a fresh clone cannot obtain them: ${unobtainable.join(", ")}. Run \`wairon pack bundle --all\` to commit a copy the repository carries, or \`wairon pack use <name> --source <url>\` to record where \`wairon pack sync\` fetches each.`);
|
|
53566
|
+
}
|
|
53567
|
+
} catch (e) {
|
|
53568
|
+
console.log(` ${icon("error")} ${whoPrefix(who)}Could not record pack selections: ${e instanceof Error ? e.message : String(e)}`);
|
|
53569
|
+
}
|
|
53570
|
+
}
|
|
53571
|
+
function repairProjectSpecs(who) {
|
|
53572
|
+
const at = whoPrefix(who);
|
|
52801
53573
|
try {
|
|
52802
53574
|
const retirement = retireSpecialists(true);
|
|
52803
53575
|
if (retirement.retyped.length > 0) {
|
|
52804
|
-
console.log(` ${icon("ok")} Retired ${retirement.retyped.length} Specialist(s) to Orchestrator.`);
|
|
53576
|
+
console.log(` ${icon("ok")} ${at}Retired ${retirement.retyped.length} Specialist(s) to Orchestrator.`);
|
|
52805
53577
|
}
|
|
52806
53578
|
if (retirement.rebasedVariants.length > 0) {
|
|
52807
|
-
console.log(` ${icon("ok")} Rebased ${retirement.rebasedVariants.length} variant(s) from Specialist to Orchestrator: ${retirement.rebasedVariants.join(", ")}.`);
|
|
53579
|
+
console.log(` ${icon("ok")} ${at}Rebased ${retirement.rebasedVariants.length} variant(s) from Specialist to Orchestrator: ${retirement.rebasedVariants.join(", ")}.`);
|
|
52808
53580
|
}
|
|
52809
53581
|
} catch (e) {
|
|
52810
|
-
console.log(` ${icon("error")} Specialist retirement failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
53582
|
+
console.log(` ${icon("error")} ${at}Specialist retirement failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
52811
53583
|
}
|
|
52812
53584
|
try {
|
|
52813
53585
|
const repairs = repairForeignStepFields(true);
|
|
52814
53586
|
if (repairs.length > 0) {
|
|
52815
|
-
console.log(` ${icon("ok")} Dropped foreign fields from ${repairs.length} narrative step(s):`);
|
|
53587
|
+
console.log(` ${icon("ok")} ${at}Dropped foreign fields from ${repairs.length} narrative step(s):`);
|
|
52816
53588
|
for (const repair of repairs) {
|
|
52817
53589
|
console.log(` ${repair.implementation}.${repair.method} step ${repair.stepNumber} (${repair.stepType}): ${repair.fields.join(", ")}`);
|
|
52818
53590
|
}
|
|
52819
53591
|
}
|
|
52820
53592
|
} catch (e) {
|
|
52821
|
-
console.log(` ${icon("error")} Narrative-step repair failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
53593
|
+
console.log(` ${icon("error")} ${at}Narrative-step repair failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
52822
53594
|
}
|
|
52823
53595
|
try {
|
|
52824
53596
|
const signatures = repairSignatures(true);
|
|
52825
53597
|
if (signatures.length > 0) {
|
|
52826
|
-
console.log(` ${icon("ok")} Rewrote ${signatures.length} spec(s) into their stored signature form:`);
|
|
53598
|
+
console.log(` ${icon("ok")} ${at}Rewrote ${signatures.length} spec(s) into their stored signature form:`);
|
|
52827
53599
|
for (const repair of signatures) {
|
|
52828
53600
|
console.log(` ${repair.kind} ${repair.specId}: ${describeSignatureRepair(repair)}`);
|
|
52829
53601
|
}
|
|
52830
53602
|
}
|
|
52831
53603
|
} catch (e) {
|
|
52832
|
-
console.log(` ${icon("error")} Signature repair failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
53604
|
+
console.log(` ${icon("error")} ${at}Signature repair failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
52833
53605
|
}
|
|
52834
53606
|
try {
|
|
52835
53607
|
const spellings = repairTypeSpellings(true);
|
|
52836
53608
|
const rewritten = spellings.filter((repair) => repair.rewritten.length > 0);
|
|
52837
53609
|
if (rewritten.length > 0) {
|
|
52838
53610
|
const positions = rewritten.reduce((n, repair) => n + repair.rewritten.length, 0);
|
|
52839
|
-
console.log(` ${icon("ok")} Rewrote ${positions} type position(s) in ${rewritten.length} spec(s) into their canonical spelling.`);
|
|
53611
|
+
console.log(` ${icon("ok")} ${at}Rewrote ${positions} type position(s) in ${rewritten.length} spec(s) into their canonical spelling.`);
|
|
52840
53612
|
}
|
|
52841
53613
|
const proposals = spellings.flatMap((repair) => repair.proposals);
|
|
52842
53614
|
if (proposals.length > 0) {
|
|
52843
|
-
console.log(` ${icon("warn")} ${proposals.length} number position(s) with int proposed \u2014 not written; confirm by writing int, or write float:`);
|
|
53615
|
+
console.log(` ${icon("warn")} ${at}${proposals.length} number position(s) with int proposed \u2014 not written; confirm by writing int, or write float:`);
|
|
52844
53616
|
for (const proposal of proposals) console.log(` ${proposal.kind} ${proposal.specId} ${proposal.path}: "${proposal.written}" \u2192 ${proposal.stored} (proposed)`);
|
|
52845
53617
|
}
|
|
53618
|
+
const enumProposals = spellings.flatMap((repair) => repair.enumProposals);
|
|
53619
|
+
if (enumProposals.length > 0) {
|
|
53620
|
+
console.log(` ${icon("warn")} ${at}${enumProposals.length} string-literal union(s) with an enum proposed \u2014 not written; define the enum (sdd_add_type kind enum), then write its id:`);
|
|
53621
|
+
for (const proposal of enumProposals) console.log(` ${describeEnumProposal(proposal)}`);
|
|
53622
|
+
}
|
|
52846
53623
|
const authorNeeded = spellings.flatMap((repair) => repair.authorNeeded);
|
|
52847
53624
|
if (authorNeeded.length > 0) {
|
|
52848
|
-
console.log(` ${icon("warn")} ${authorNeeded.length} type position(s) need an author \u2014 no rewrite can settle them:`);
|
|
53625
|
+
console.log(` ${icon("warn")} ${at}${authorNeeded.length} type position(s) need an author \u2014 no rewrite can settle them:`);
|
|
52849
53626
|
for (const problem of authorNeeded) {
|
|
52850
53627
|
console.log(` ${problem.kind} ${problem.specId} ${problem.path ?? ""}: "${problem.written}" \u2014 ${problem.replacement ? `write ${problem.replacement}` : problem.detail}`);
|
|
52851
53628
|
}
|
|
52852
53629
|
}
|
|
52853
53630
|
} catch (e) {
|
|
52854
|
-
console.log(` ${icon("error")} Type-spelling repair failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
53631
|
+
console.log(` ${icon("error")} ${at}Type-spelling repair failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
52855
53632
|
}
|
|
52856
|
-
|
|
52857
|
-
|
|
53633
|
+
}
|
|
53634
|
+
function memberProjects() {
|
|
53635
|
+
const root = getProjectRoot();
|
|
53636
|
+
return graph().nodes.filter((node) => node.namespace !== "" && node.directory && pathExists(node.directory)).map((node) => ({ alias: node.mountAlias ?? node.namespace, directory: node.directory, inside: !isOutsideRoot(root, node.directory) }));
|
|
53637
|
+
}
|
|
53638
|
+
function repairMembers() {
|
|
53639
|
+
let members;
|
|
53640
|
+
try {
|
|
53641
|
+
members = memberProjects();
|
|
53642
|
+
} catch (e) {
|
|
53643
|
+
console.log(` ${icon("error")} Could not read the family's members: ${e instanceof Error ? e.message : String(e)}`);
|
|
53644
|
+
return;
|
|
53645
|
+
}
|
|
53646
|
+
for (const member of members) {
|
|
53647
|
+
if (!member.inside) {
|
|
53648
|
+
console.log(` ${icon("warn")} [${member.alias}] lies outside this project (${member.directory}): run \`wairon doctor --fix\` there to upgrade it.`);
|
|
53649
|
+
continue;
|
|
53650
|
+
}
|
|
53651
|
+
runWithProjectRoot(member.directory, () => {
|
|
53652
|
+
if (!projectConfigExists()) return;
|
|
53653
|
+
pinPackSelections(member.alias);
|
|
53654
|
+
repairProjectSpecs(member.alias);
|
|
53655
|
+
});
|
|
53656
|
+
}
|
|
53657
|
+
}
|
|
53658
|
+
function reportMemberRepairs(tally) {
|
|
53659
|
+
const pending4 = [];
|
|
53660
|
+
for (const member of memberProjects()) {
|
|
53661
|
+
const counts = runWithProjectRoot(member.directory, () => {
|
|
53662
|
+
if (!projectConfigExists()) return null;
|
|
53663
|
+
const spellings = repairTypeSpellings(false);
|
|
53664
|
+
return {
|
|
53665
|
+
specialists: retireSpecialists(false).retyped.length,
|
|
53666
|
+
stepFields: repairForeignStepFields(false).length,
|
|
53667
|
+
signatures: repairSignatures(false).length,
|
|
53668
|
+
respell: spellings.reduce((n, r) => n + r.rewritten.length, 0),
|
|
53669
|
+
proposals: spellings.reduce((n, r) => n + r.proposals.length + r.enumProposals.length, 0),
|
|
53670
|
+
authorNeeded: spellings.reduce((n, r) => n + r.authorNeeded.length, 0)
|
|
53671
|
+
};
|
|
53672
|
+
});
|
|
53673
|
+
if (!counts) continue;
|
|
53674
|
+
const parts = [
|
|
53675
|
+
counts.specialists ? `${counts.specialists} Specialist(s)` : "",
|
|
53676
|
+
counts.stepFields ? `${counts.stepFields} step(s) with foreign fields` : "",
|
|
53677
|
+
counts.signatures ? `${counts.signatures} spec(s) with stale signatures` : "",
|
|
53678
|
+
counts.respell ? `${counts.respell} type position(s) to respell` : "",
|
|
53679
|
+
counts.proposals ? `${counts.proposals} proposal(s)` : "",
|
|
53680
|
+
counts.authorNeeded ? `${counts.authorNeeded} position(s) needing an author` : ""
|
|
53681
|
+
].filter(Boolean);
|
|
53682
|
+
if (parts.length === 0) continue;
|
|
53683
|
+
const where = member.inside ? "`wairon doctor --fix` here cascades into it" : `run \`wairon doctor --fix\` in ${member.directory}`;
|
|
53684
|
+
pending4.push(`member ${member.alias}: ${parts.join(", ")} \u2014 ${where}`);
|
|
53685
|
+
}
|
|
53686
|
+
if (pending4.length === 0) return;
|
|
53687
|
+
console.log(import_chalk17.default.bold("Members"));
|
|
53688
|
+
for (const text3 of pending4) line(tally, "warn", text3);
|
|
52858
53689
|
logger.blank();
|
|
52859
53690
|
}
|
|
52860
53691
|
async function repairMcpRegistration(options, targets) {
|
|
@@ -52949,11 +53780,11 @@ function reportLabel(key) {
|
|
|
52949
53780
|
return key === "" ? "(this project)" : key;
|
|
52950
53781
|
}
|
|
52951
53782
|
function printUpgradeReport(report4) {
|
|
52952
|
-
console.log(import_chalk17.default.bold("Composed validation: what
|
|
53783
|
+
console.log(import_chalk17.default.bold("Composed validation: what it changed since each lock"));
|
|
52953
53784
|
console.log(import_chalk17.default.gray(" A lock records totals only (errors, warnings, notices) and the validator version that took it \u2014 nothing per code \u2014 so this is not a per-code diff. Today's findings are classed by a reason computed now."));
|
|
52954
53785
|
for (const p of report4.projects) {
|
|
52955
53786
|
const locked = p.lockedTotals ? `locked by v${p.lockedBy}: ${p.lockedTotals}` : "never locked";
|
|
52956
|
-
const attribution = p.lockedTotals ? p.predatesStage4 ? " \u2014 the lock predates
|
|
53787
|
+
const attribution = p.lockedTotals ? p.predatesStage4 ? " \u2014 the lock predates composed validation, so a difference is the upgrade's" : " \u2014 the lock was taken under composed validation, so a difference is not the upgrade's" : "";
|
|
52957
53788
|
console.log(` ${reportLabel(p.key)}: ${locked}; today (as-complete): ${p.currentTotals}${attribution}`);
|
|
52958
53789
|
}
|
|
52959
53790
|
const byCode = /* @__PURE__ */ new Map();
|
|
@@ -52962,12 +53793,12 @@ function printUpgradeReport(report4) {
|
|
|
52962
53793
|
console.log(` ${import_chalk17.default.bold(code)} (${entries.length})`);
|
|
52963
53794
|
for (const e of entries) {
|
|
52964
53795
|
const where = `${reportLabel(e.project)}${e.specId ? ` ${e.specId}` : ""}`;
|
|
52965
|
-
const reason = e.reason === "escalated" ? "escalated: a
|
|
53796
|
+
const reason = e.reason === "escalated" ? "escalated: once a notice, an error under composed validation" : e.reason === "pinned" ? "pinned: judged against the project's own pin, no longer through the parent's live tree" : `positional: the family's top matches it${e.resolvedAs ? ` to ${e.resolvedAs}` : ""} by position, which is how it passed before${e.rewrite ? ` \u2014 the migration writes ${e.rewrite}` : ""}`;
|
|
52966
53797
|
console.log(` ${e.severity} ${where}: ${reason}`);
|
|
52967
53798
|
}
|
|
52968
53799
|
}
|
|
52969
|
-
if (report4.entries.length === 0) console.log(" No finding is new for a reason
|
|
52970
|
-
console.log(` ${report4.unclassified} finding(s) could not be attributed to
|
|
53800
|
+
if (report4.entries.length === 0) console.log(" No finding is new for a reason composed validation introduced.");
|
|
53801
|
+
console.log(` ${report4.unclassified} finding(s) could not be attributed to composed validation.`);
|
|
52971
53802
|
if (report4.unmatched.length > 0) {
|
|
52972
53803
|
console.log(` ${import_chalk17.default.bold("Positional matches no rule decides")} (${report4.unmatched.length}) \u2014 counted above; a person picks:`);
|
|
52973
53804
|
for (const m of report4.unmatched) {
|
|
@@ -53131,7 +53962,7 @@ function printProjectMigration(p) {
|
|
|
53131
53962
|
console.log(` will be removed from its stored L0 by the first write (the schema does not know them): ${p.droppedKeys.join("; ")}`);
|
|
53132
53963
|
}
|
|
53133
53964
|
for (const x of p.externals) {
|
|
53134
|
-
const why = x.reason === "legacy-pin" ? "replaces the
|
|
53965
|
+
const why = x.reason === "legacy-pin" ? "replaces the legacy family pin of" : "its references cross into";
|
|
53135
53966
|
console.log(` external: ${x.alias}: {} \u2014 ${why} ${projectLabel2(x.producer)}`);
|
|
53136
53967
|
}
|
|
53137
53968
|
for (const i of p.imports) {
|
|
@@ -53147,10 +53978,10 @@ function printProjectMigration(p) {
|
|
|
53147
53978
|
if (m.retired.length > 0) console.log(` retires with the mount: ${m.retired.join("; ")}`);
|
|
53148
53979
|
}
|
|
53149
53980
|
if (p.supersededPins.length > 0) {
|
|
53150
|
-
console.log(` superseded family pins: ${p.supersededPins.length} \u2014 deleted once the externals that replace them are pinned; nothing reads them
|
|
53981
|
+
console.log(` superseded family pins: ${p.supersededPins.length} \u2014 deleted once the externals that replace them are pinned; nothing reads them any more`);
|
|
53151
53982
|
}
|
|
53152
53983
|
for (const alias of p.locations) {
|
|
53153
|
-
console.log(` member: ${alias}: the deprecated long-form \`path\` rewritten to \`source\`
|
|
53984
|
+
console.log(` member: ${alias}: the deprecated long-form \`path\` rewritten to \`source\` \u2014 its meaning unchanged`);
|
|
53154
53985
|
}
|
|
53155
53986
|
}
|
|
53156
53987
|
function describeEntry(e) {
|
|
@@ -53220,13 +54051,13 @@ function writeArtifact(dest, content) {
|
|
|
53220
54051
|
fs39.writeFileSync(dest, content, "utf-8");
|
|
53221
54052
|
}
|
|
53222
54053
|
function parseSequenceRef(ref) {
|
|
53223
|
-
const
|
|
53224
|
-
if (
|
|
54054
|
+
const sep19 = ref.includes(":") ? ref.lastIndexOf(":") : ref.lastIndexOf(".");
|
|
54055
|
+
if (sep19 <= 0 || sep19 === ref.length - 1) {
|
|
53225
54056
|
throw new WaironError(
|
|
53226
54057
|
`Invalid --sequence reference "${ref}". Use <componentId>:<methodName> (e.g. billing-portal:authorize).`
|
|
53227
54058
|
);
|
|
53228
54059
|
}
|
|
53229
|
-
return { component: ref.slice(0,
|
|
54060
|
+
return { component: ref.slice(0, sep19), method: ref.slice(sep19 + 1) };
|
|
53230
54061
|
}
|
|
53231
54062
|
function canvasRelations(options) {
|
|
53232
54063
|
if (options.health === false) return void 0;
|
|
@@ -53378,8 +54209,8 @@ function withoutRoot(record2) {
|
|
|
53378
54209
|
return rest;
|
|
53379
54210
|
}
|
|
53380
54211
|
function normalizeMemberPath(p) {
|
|
53381
|
-
const
|
|
53382
|
-
return
|
|
54212
|
+
const posix8 = path64.posix.normalize(p.replace(/\\/g, "/"));
|
|
54213
|
+
return posix8.replace(/^\.\/+/, "").replace(/\/+$/, "");
|
|
53383
54214
|
}
|
|
53384
54215
|
function projectRoot(dataDir, id) {
|
|
53385
54216
|
return path64.join(dataDir, "projects", id);
|
|
@@ -57084,12 +57915,13 @@ function mintKey(cfg, credential, project2, role) {
|
|
|
57084
57915
|
"instance-wide super-admin (*:*) keys cannot be minted \u2014 the built-in admin account (WAIRON_ADMIN_USER) is the only super-admin"
|
|
57085
57916
|
);
|
|
57086
57917
|
}
|
|
57918
|
+
const narrowed2 = assertMintableNarrowingEntry(cfg.dataDir, project2);
|
|
57087
57919
|
const token = "wk_" + crypto17.randomBytes(24).toString("hex");
|
|
57088
57920
|
const record2 = {
|
|
57089
57921
|
id: crypto17.randomBytes(6).toString("hex"),
|
|
57090
57922
|
keyHash: hashToken(token),
|
|
57091
57923
|
role,
|
|
57092
|
-
projects:
|
|
57924
|
+
projects: [narrowed2],
|
|
57093
57925
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
57094
57926
|
};
|
|
57095
57927
|
createCredential(cfg.dataDir, record2);
|
|
@@ -57182,7 +58014,7 @@ function refuseDesignErrors(gate) {
|
|
|
57182
58014
|
function refuseUnapprovedMembers(required, members) {
|
|
57183
58015
|
const unapproved = members.filter((m) => m.as !== "part" && m.state !== "approved");
|
|
57184
58016
|
if (!required || unapproved.length === 0) return;
|
|
57185
|
-
const named2 = unapproved.map((m) => `${m.alias ?? m.key} (${m.state}${m.upgraded ? " \u2014 approved under
|
|
58017
|
+
const named2 = unapproved.map((m) => `${m.alias ?? m.key} (${m.state}${m.upgraded ? " \u2014 approved under an earlier gate identity, re-lock it once" : ""})`);
|
|
57186
58018
|
throw new LockRefusedError2(
|
|
57187
58019
|
`composition.requireApprovedMembers: direct member(s) not approved \u2014 ${named2.join(", ")}. Lock each at its own root first; a parent never approves below itself. Nothing was written.`
|
|
57188
58020
|
);
|
|
@@ -61364,7 +62196,7 @@ async function awaitApproval(cfg, credential, requestId, timeoutSeconds) {
|
|
|
61364
62196
|
let current2 = req;
|
|
61365
62197
|
while (current2.status === "pending" && Date.now() < deadline) {
|
|
61366
62198
|
const remaining = deadline - Date.now();
|
|
61367
|
-
await new Promise((
|
|
62199
|
+
await new Promise((resolve56) => setTimeout(resolve56, Math.min(AWAIT_POLL_INTERVAL_MS, remaining)));
|
|
61368
62200
|
expirePendingApprovals(cfg.dataDir, (/* @__PURE__ */ new Date()).toISOString());
|
|
61369
62201
|
current2 = getApprovalRequestById(cfg.dataDir, requestId) ?? current2;
|
|
61370
62202
|
}
|
|
@@ -66963,8 +67795,9 @@ function stop() {
|
|
|
66963
67795
|
var DEV_UNIT_ID = "local";
|
|
66964
67796
|
function bootstrapInstance(config) {
|
|
66965
67797
|
ensureInstanceIdentity(config.dataDir);
|
|
66966
|
-
if (config.devMode
|
|
66967
|
-
|
|
67798
|
+
if (!config.devMode) return;
|
|
67799
|
+
const system = { userId: "system", kind: "service", issuer: "local" };
|
|
67800
|
+
if (!getOrganizationUnit(config.dataDir, DEV_UNIT_ID)) {
|
|
66968
67801
|
createUnit(config.dataDir, {
|
|
66969
67802
|
id: DEV_UNIT_ID,
|
|
66970
67803
|
name: "Local Development",
|
|
@@ -66976,6 +67809,18 @@ function bootstrapInstance(config) {
|
|
|
66976
67809
|
createdBy: system
|
|
66977
67810
|
});
|
|
66978
67811
|
}
|
|
67812
|
+
const placed = new Set(listProjectPlacements(config.dataDir).map((p) => p.projectId));
|
|
67813
|
+
for (const record2 of listProjectRecords(config.dataDir)) {
|
|
67814
|
+
if (placed.has(record2.id) || record2.parentProjectId !== void 0) continue;
|
|
67815
|
+
placeProject(config.dataDir, {
|
|
67816
|
+
id: "",
|
|
67817
|
+
projectId: record2.id,
|
|
67818
|
+
unitId: DEV_UNIT_ID,
|
|
67819
|
+
role: "owner",
|
|
67820
|
+
createdAt: "",
|
|
67821
|
+
createdBy: system
|
|
67822
|
+
});
|
|
67823
|
+
}
|
|
66979
67824
|
}
|
|
66980
67825
|
|
|
66981
67826
|
// src/server/identity-portal.ts
|
|
@@ -68312,11 +69157,11 @@ async function runServe(options = {}) {
|
|
|
68312
69157
|
logger.info(` data dir: ${import_chalk18.default.gray(cfg.dataDir)}`);
|
|
68313
69158
|
logger.blank();
|
|
68314
69159
|
logger.info("Press Ctrl+C to stop.");
|
|
68315
|
-
await new Promise((
|
|
69160
|
+
await new Promise((resolve56) => {
|
|
68316
69161
|
const shutdown = () => {
|
|
68317
69162
|
logger.info("Shutting down\u2026");
|
|
68318
69163
|
handle.close();
|
|
68319
|
-
|
|
69164
|
+
resolve56();
|
|
68320
69165
|
};
|
|
68321
69166
|
process.on("SIGINT", shutdown);
|
|
68322
69167
|
process.on("SIGTERM", shutdown);
|
|
@@ -68381,11 +69226,11 @@ async function runDev(options = {}) {
|
|
|
68381
69226
|
logger.blank();
|
|
68382
69227
|
logger.info("An agent edits specs; refresh the page to see the live graph. Press Ctrl+C to stop.");
|
|
68383
69228
|
if (options.open) openBrowser(url);
|
|
68384
|
-
await new Promise((
|
|
69229
|
+
await new Promise((resolve56) => {
|
|
68385
69230
|
const shutdown = () => {
|
|
68386
69231
|
logger.info("Shutting down\u2026");
|
|
68387
69232
|
handle.close();
|
|
68388
|
-
|
|
69233
|
+
resolve56();
|
|
68389
69234
|
};
|
|
68390
69235
|
process.on("SIGINT", shutdown);
|
|
68391
69236
|
process.on("SIGTERM", shutdown);
|
|
@@ -68491,7 +69336,9 @@ async function runHostUnit(action, options = {}) {
|
|
|
68491
69336
|
id: qualifiedId2,
|
|
68492
69337
|
name: options.name ?? options.slug,
|
|
68493
69338
|
slug: options.slug,
|
|
68494
|
-
|
|
69339
|
+
// A top-level unit must be a business_entity (the org-unit hierarchy's
|
|
69340
|
+
// root kind), so that is the default without --parent; under a parent, team.
|
|
69341
|
+
kind: options.kind ?? (options.parent ? "team" : "business_entity"),
|
|
68495
69342
|
...options.parent ? { parentId: options.parent } : {},
|
|
68496
69343
|
status: "active",
|
|
68497
69344
|
createdAt: "",
|
|
@@ -68580,6 +69427,21 @@ function resolveScopeOptions(options) {
|
|
|
68580
69427
|
if (options.unit) return { kind: "unit", id: options.unit };
|
|
68581
69428
|
return { kind: "instance" };
|
|
68582
69429
|
}
|
|
69430
|
+
function warnIfUnreadable(cfg, cred, userId, scope, why) {
|
|
69431
|
+
const target = scope.kind === "subsystem" ? { kind: "project", id: (scope.id ?? "").split("/")[0] } : scope;
|
|
69432
|
+
let read2;
|
|
69433
|
+
try {
|
|
69434
|
+
read2 = explain3(cfg, cred, userId, "project:read", target.kind, target.id ?? "").value;
|
|
69435
|
+
} catch {
|
|
69436
|
+
return;
|
|
69437
|
+
}
|
|
69438
|
+
if (read2 === "yes") return;
|
|
69439
|
+
const where = target.kind === "instance" ? "the instance" : `${target.kind} ${target.id}`;
|
|
69440
|
+
const flag = target.kind === "instance" ? "--instance" : `--${target.kind} ${target.id}`;
|
|
69441
|
+
logger.warn(
|
|
69442
|
+
`project:read for "${userId}" at ${where} resolves to ${read2} \u2014 ${why}. Grant it with \`wairon host permission set --user ${userId} --capability project:read ${flag}\`.`
|
|
69443
|
+
);
|
|
69444
|
+
}
|
|
68583
69445
|
async function runHostPermission(action, options = {}) {
|
|
68584
69446
|
const cfg = resolveHostConfig(options);
|
|
68585
69447
|
const cred = masterCredential();
|
|
@@ -68608,6 +69470,9 @@ async function runHostPermission(action, options = {}) {
|
|
|
68608
69470
|
logger.success(
|
|
68609
69471
|
`Assignment ${stored.id}: ${options.user} ${options.capability}=${value} @ ${scope.kind}${scope.id ? ` ${scope.id}` : ""}`
|
|
68610
69472
|
);
|
|
69473
|
+
if (options.capability === "project:write" && (value === "yes" || value === "approval")) {
|
|
69474
|
+
warnIfUnreadable(cfg, cred, options.user, scope, `${options.user} can write here but cannot read it`);
|
|
69475
|
+
}
|
|
68611
69476
|
break;
|
|
68612
69477
|
}
|
|
68613
69478
|
case "list": {
|
|
@@ -68661,7 +69526,16 @@ async function runHostKey(action, options = {}) {
|
|
|
68661
69526
|
console.log(` ${import_chalk18.default.bold(minted.token)}`);
|
|
68662
69527
|
logger.blank();
|
|
68663
69528
|
for (const line2 of minted.mapped) {
|
|
68664
|
-
logger.warn(`Narrowing ${line2}: a member is a record of its own
|
|
69529
|
+
logger.warn(`Narrowing ${line2}: a member is a record of its own, so the token names it by its id (qualified entries are deprecated).`);
|
|
69530
|
+
}
|
|
69531
|
+
if (options.project !== "*") {
|
|
69532
|
+
warnIfUnreadable(
|
|
69533
|
+
cfg,
|
|
69534
|
+
cred,
|
|
69535
|
+
options.owner,
|
|
69536
|
+
{ kind: "project", id: options.project },
|
|
69537
|
+
`the token acts as "${options.owner}"'s live permission, so it cannot read the project either`
|
|
69538
|
+
);
|
|
68665
69539
|
}
|
|
68666
69540
|
break;
|
|
68667
69541
|
}
|
|
@@ -69065,14 +69939,23 @@ async function runExport(out) {
|
|
|
69065
69939
|
}
|
|
69066
69940
|
const approval = verdict.state;
|
|
69067
69941
|
const design = exportDesign3(out, approval);
|
|
69942
|
+
const members = memberNote(design);
|
|
69068
69943
|
if (out) {
|
|
69069
69944
|
logger.success(
|
|
69070
69945
|
`Design export of "${design.project.name}" written to ${path92.resolve(out)} (${design.components.length} component(s), ${design.types.length} type(s); approval: ${design.source.approval}).`
|
|
69071
69946
|
);
|
|
69947
|
+
if (members) logger.info(members);
|
|
69072
69948
|
return;
|
|
69073
69949
|
}
|
|
69074
69950
|
process.stdout.write(`${JSON.stringify(design, null, 2)}
|
|
69075
69951
|
`);
|
|
69952
|
+
if (members && design.components.length === 0 && design.types.length === 0) process.stderr.write(`${members}
|
|
69953
|
+
`);
|
|
69954
|
+
}
|
|
69955
|
+
function memberNote(design) {
|
|
69956
|
+
const members = design.dependencies.filter((d) => d.role === "member").map((d) => d.alias);
|
|
69957
|
+
if (members.length === 0) return null;
|
|
69958
|
+
return `${members.length} member project(s) are listed under \`dependencies\`, not inlined (${members.join(", ")}): run \`wairon export\` in a member's own root for its design.`;
|
|
69076
69959
|
}
|
|
69077
69960
|
|
|
69078
69961
|
// src/commands/externals.ts
|
|
@@ -69295,16 +70178,16 @@ program.command("generate").description("Generate agent output files from the sp
|
|
|
69295
70178
|
dryRun: opts.dryRun
|
|
69296
70179
|
});
|
|
69297
70180
|
});
|
|
69298
|
-
program.command("lock-check").description("Merge gate: is the design in this working tree the design that was approved? Compares the tree's gate identity against
|
|
70181
|
+
program.command("lock-check").description("Merge gate: is the design in this working tree the design that was approved? Compares the tree's gate identity against .wai/lock.json as it is in the working tree (in CI, the file committed in the checked-out revision; locally, an uncommitted record counts too). Exits 1 when the design moved past its approval; passes with a notice when the project never locked, unless --strict.").option("--strict", "also fail when nothing was ever approved, or when there is no spec tree at all").action(async (opts) => {
|
|
69299
70182
|
await lockCheckCommand(opts);
|
|
69300
70183
|
});
|
|
69301
|
-
program.command("lock").description("
|
|
70184
|
+
program.command("lock").description("Record human approval of the design: validate the spec tree as complete, write the approval to .wai/lock.json (one digest per spec; spec files are not rewritten), and refresh this project's generated outputs \u2014 only if the design validates. Commit .wai/lock.json; `wairon lock-check` gates on it. In an attached checkout, locks the hosted project instead.").option("-y, --yes", "skip the confirmation prompt (for scripts / CI)").option("--subsystem <id>", "only lock specs in the specified subsystem").option("--no-recursive", "accepted for one release and ignored: a lock approves this project only; each member locks at its own root").action(async (opts) => {
|
|
69302
70185
|
await lockCommand(opts);
|
|
69303
70186
|
});
|
|
69304
|
-
program.command("validate").description("Validate the project configuration and the SDD Spec Tree").option("--ci", "treat warnings as errors for CI pipelines (notices are printed and counted, never fatal)").option("--subsystem <id>", "only validate the specified subsystem (granular)").option("--no-recursive", "at a project that declares members: run the owner's gate alone instead of the family run").option("--family", "run the family run from here: every member's own gate, and this project's externals composed against their live producers").action(async (opts) => {
|
|
70187
|
+
program.command("validate").description("Validate the project configuration and the SDD Spec Tree").option("--ci", "treat warnings as errors for CI pipelines (notices are printed and counted, never fatal)").option("--subsystem <id>", "only validate the specified subsystem (granular)").option("--no-recursive", "at a project that declares members: run the owner's gate alone instead of the family run").option("--family", "run the family run from here: every member's own gate, and this project's externals composed against their live producers").option("--all", "print every finding (by default the first 100 per severity are printed, followed by per-code totals)").action(async (opts) => {
|
|
69305
70188
|
await validateCommand(opts);
|
|
69306
70189
|
});
|
|
69307
|
-
program.command("status").description("Show
|
|
70190
|
+
program.command("status").description("Show the spec tree with its authoring readiness (how far each spec is written out, from its draft/design/complete status) and, separately, its approval state from .wai/lock.json").option("--subsystem <id>", "only show status for the specified subsystem").option("--no-recursive", "show this project only, without its members").action(async (opts) => {
|
|
69308
70191
|
await statusCommand(opts);
|
|
69309
70192
|
});
|
|
69310
70193
|
program.command("doctor").description("Health check: flags stale generated guides/skills, an unregistered MCP server, spec-tree issues and the chaining migration still pending").option("--fix", "regenerate stale in-project guides/context/skills, register the MCP server, then apply the chaining migration once confirmed").option("--report <section>", "print one section's report and nothing else, writing nothing (chaining | composed-validation); never combines with --fix").option("-y, --yes", "answer the chaining migration's confirmation (for a non-interactive --fix); a write outside the project root also needs --global").option("--global", "consent to the --fix writes outside the project root (a machine-wide MCP config, the legacy global plugin); with --yes it answers their confirmation, and each replaced file is backed up beside itself").action(async (opts) => {
|
|
@@ -69484,7 +70367,7 @@ hostCmd.command("project <action>").description("create | list | destroy a hoste
|
|
|
69484
70367
|
hostCmd.command("demo").description("provision a project seeded with a rich example spec tree, so the canvas has content to render").option("--id <id>", "project id", "demo").option("--unit <unitId>", "owner unit id (created if absent)", "demo").option("--force", "destroy and reseed the project if it already exists").option("--data-dir <path>", "data root (default WAIRON_DATA_DIR or ~/.wairon/data)").action(async (opts) => {
|
|
69485
70368
|
await runHostDemo({ id: opts.id, unit: opts.unit, force: opts.force, dataDir: opts.dataDir });
|
|
69486
70369
|
});
|
|
69487
|
-
hostCmd.command("unit <action>").description("create an organization unit (projects are placed into units at creation)").option("--slug <slug>", "unit slug (REQUIRED for create; a root unit's id IS its slug)").option("--name <name>", "display name (defaults to the slug)").option("--kind <kind>", "unit kind (business_entity | department | team | \u2026)
|
|
70370
|
+
hostCmd.command("unit <action>").description("create an organization unit (projects are placed into units at creation)").option("--slug <slug>", "unit slug (REQUIRED for create; a root unit's id IS its slug)").option("--name <name>", "display name (defaults to the slug)").option("--kind <kind>", "unit kind (business_entity | department | team | \u2026); default business_entity for a top-level unit, team under --parent").option("--parent <unitId>", "qualified id of the parent unit (omit for a root unit)").option("--data-dir <path>", "data root").action(async (action, opts) => {
|
|
69488
70371
|
await runHostUnit(action, { slug: opts.slug, name: opts.name, kind: opts.kind, parent: opts.parent, dataDir: opts.dataDir });
|
|
69489
70372
|
});
|
|
69490
70373
|
hostCmd.command("doctor").description("migrate a hosted data dir to the permission model (grants \u2192 assignments, owners, unit ids, placements); dry-run without --fix").option("--fix", "apply the migration (REQUIRED at rollout \u2014 legacy users/tokens otherwise resolve to zero permissions)").option("--data-dir <path>", "data root").action(async (opts) => {
|