@wairon/cli 5.1.1-dev.101 → 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 +986 -393
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +37562 -0
- package/dist/index.js +705 -365
- 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
|
];
|
|
@@ -6147,7 +6390,7 @@ var init_design_export = __esm({
|
|
|
6147
6390
|
ExtSchema = import_zod9.z.record(import_zod9.z.unknown());
|
|
6148
6391
|
TypeExpressionSchema = import_zod9.z.lazy(
|
|
6149
6392
|
() => import_zod9.z.object({
|
|
6150
|
-
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"]),
|
|
6151
6394
|
name: import_zod9.z.string().optional(),
|
|
6152
6395
|
args: import_zod9.z.array(TypeExpressionSchema)
|
|
6153
6396
|
}).passthrough()
|
|
@@ -11615,7 +11858,7 @@ function snapshotPath(root, alias) {
|
|
|
11615
11858
|
function writeAtomically(file, text3) {
|
|
11616
11859
|
fs8.mkdirSync(path13.dirname(file), { recursive: true });
|
|
11617
11860
|
const tmp = `${file}.${process.pid}.tmp`;
|
|
11618
|
-
fs8.writeFileSync(tmp, text3);
|
|
11861
|
+
fs8.writeFileSync(tmp, withLineEndings(file, text3));
|
|
11619
11862
|
fs8.renameSync(tmp, file);
|
|
11620
11863
|
}
|
|
11621
11864
|
function sortedLock(lock) {
|
|
@@ -11790,6 +12033,8 @@ function schemaOfExpression(expr, closureIds) {
|
|
|
11790
12033
|
return { oneOf: expr.args.map((member) => schemaOfExpression(member, closureIds)) };
|
|
11791
12034
|
case "async":
|
|
11792
12035
|
return schemaOfExpression(expr.args[0], closureIds);
|
|
12036
|
+
case "result":
|
|
12037
|
+
return schemaOfExpression(expr.args[0], closureIds);
|
|
11793
12038
|
}
|
|
11794
12039
|
}
|
|
11795
12040
|
function schemaFor(typeRef2, closureIds) {
|
|
@@ -11799,7 +12044,8 @@ function schemaFor(typeRef2, closureIds) {
|
|
|
11799
12044
|
}
|
|
11800
12045
|
function returnsNothing(returns) {
|
|
11801
12046
|
const expr = expressionOf(returns);
|
|
11802
|
-
const
|
|
12047
|
+
const awaited = expr?.form === "async" ? expr.args[0] : expr;
|
|
12048
|
+
const inner2 = awaited?.form === "result" ? awaited.args[0] : awaited;
|
|
11803
12049
|
return inner2?.form === "primitive" && inner2.name === "void";
|
|
11804
12050
|
}
|
|
11805
12051
|
function enumDescription(values) {
|
|
@@ -13795,7 +14041,7 @@ function approvalFinding(entry, config) {
|
|
|
13795
14041
|
return finding(
|
|
13796
14042
|
config,
|
|
13797
14043
|
"MEMBER_DRIFTED",
|
|
13798
|
-
`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\`).`,
|
|
13799
14045
|
entry.key
|
|
13800
14046
|
);
|
|
13801
14047
|
}
|
|
@@ -14689,6 +14935,16 @@ function verifyIntegrity(selection, packDir, where) {
|
|
|
14689
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.`
|
|
14690
14936
|
};
|
|
14691
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
|
+
}
|
|
14692
14948
|
function detectBundleStoreDrift(selection, bundleDir) {
|
|
14693
14949
|
const bundledVersion = path17.basename(bundleDir);
|
|
14694
14950
|
const installed = resolveInstalledPack(selection.name, bundledVersion);
|
|
@@ -14696,6 +14952,7 @@ function detectBundleStoreDrift(selection, bundleDir) {
|
|
|
14696
14952
|
const bundleDigest = computePackDigest(bundleDir);
|
|
14697
14953
|
const storeDigest = computePackDigest(installed.path);
|
|
14698
14954
|
if (bundleDigest === storeDigest) return void 0;
|
|
14955
|
+
if (bundlesSingleFile(bundleDir, installed.path)) return void 0;
|
|
14699
14956
|
return {
|
|
14700
14957
|
code: "PACK_STORE_DRIFT",
|
|
14701
14958
|
severity: "warning",
|
|
@@ -15449,7 +15706,7 @@ function judgeOwnIdentity(ctx, identity2) {
|
|
|
15449
15706
|
ctx.addIssue(
|
|
15450
15707
|
"notice",
|
|
15451
15708
|
"PROJECT_ID_DEFAULTED",
|
|
15452
|
-
`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).`
|
|
15453
15710
|
);
|
|
15454
15711
|
}
|
|
15455
15712
|
const ambiguous = problem("ambiguous");
|
|
@@ -15483,7 +15740,7 @@ var init_project_identity = __esm({
|
|
|
15483
15740
|
projectIdentityRule = {
|
|
15484
15741
|
name: "project-identity",
|
|
15485
15742
|
judges: "design",
|
|
15486
|
-
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.",
|
|
15487
15744
|
codes: [
|
|
15488
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" },
|
|
15489
15746
|
{ code: "PROJECT_ID_CHANGED", defaultSeverity: "error", summary: "Project id differs from the id the lock approved" },
|
|
@@ -15909,10 +16166,10 @@ var init_type_expressions = __esm({
|
|
|
15909
16166
|
typeExpressionsRule = {
|
|
15910
16167
|
name: "type-expressions",
|
|
15911
16168
|
judges: "design",
|
|
15912
|
-
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).",
|
|
15913
16170
|
codes: [
|
|
15914
|
-
{ code: "TYPE_EXPRESSION_INVALID", defaultSeverity: "
|
|
15915
|
-
{ 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??" },
|
|
15916
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" },
|
|
15917
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" },
|
|
15918
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" }
|
|
@@ -15922,9 +16179,8 @@ var init_type_expressions = __esm({
|
|
|
15922
16179
|
if (!facts) return;
|
|
15923
16180
|
for (const problem of facts.problems) {
|
|
15924
16181
|
if (problem.specId === void 0 || !ctx.isSpecInScope(problem.specId)) continue;
|
|
15925
|
-
const severity = problem.code === "TYPE_EXPRESSION_INVALID" || problem.code === "TYPE_POSITION_INVALID" ? "error" : "warning";
|
|
15926
16182
|
ctx.addIssue(
|
|
15927
|
-
|
|
16183
|
+
"warning",
|
|
15928
16184
|
problem.code,
|
|
15929
16185
|
problemMessage(problem, problem.specId),
|
|
15930
16186
|
problem.specId,
|
|
@@ -16313,7 +16569,7 @@ var init_export_tables = __esm({
|
|
|
16313
16569
|
exportTablesRule = {
|
|
16314
16570
|
name: "export-tables",
|
|
16315
16571
|
judges: "design",
|
|
16316
|
-
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.",
|
|
16317
16573
|
codes: [
|
|
16318
16574
|
{ code: "EXPORT_ID_DUPLICATE", defaultSeverity: "error", summary: "One public name bound to different targets by two wildcard re-exports or two explicit entries" },
|
|
16319
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)" },
|
|
@@ -16489,7 +16745,7 @@ var init_reference_forms = __esm({
|
|
|
16489
16745
|
referenceFormsRule = {
|
|
16490
16746
|
name: "reference-forms",
|
|
16491
16747
|
judges: "design",
|
|
16492
|
-
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.",
|
|
16493
16749
|
codes: [
|
|
16494
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" }
|
|
16495
16751
|
],
|
|
@@ -16526,9 +16782,9 @@ var init_member_declarations = __esm({
|
|
|
16526
16782
|
memberDeclarationsRule = {
|
|
16527
16783
|
name: "member-declarations",
|
|
16528
16784
|
judges: "design",
|
|
16529
|
-
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.",
|
|
16530
16786
|
codes: [
|
|
16531
|
-
{ 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`." },
|
|
16532
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." },
|
|
16533
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." }
|
|
16534
16790
|
],
|
|
@@ -16566,7 +16822,7 @@ var init_member_declarations = __esm({
|
|
|
16566
16822
|
ctx.addIssue(
|
|
16567
16823
|
"notice",
|
|
16568
16824
|
"DEPRECATED_MOUNT_FORM",
|
|
16569
|
-
`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.`
|
|
16570
16826
|
);
|
|
16571
16827
|
}
|
|
16572
16828
|
for (const problem of family.problems) {
|
|
@@ -16610,7 +16866,7 @@ var init_project_boundaries = __esm({
|
|
|
16610
16866
|
projectBoundariesRule = {
|
|
16611
16867
|
name: "project-boundaries",
|
|
16612
16868
|
judges: "design",
|
|
16613
|
-
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.",
|
|
16614
16870
|
codes: [
|
|
16615
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" },
|
|
16616
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" },
|
|
@@ -19463,7 +19719,7 @@ var init_pack_reproducibility = __esm({
|
|
|
19463
19719
|
ctx.addIssue(
|
|
19464
19720
|
"warning",
|
|
19465
19721
|
"PACK_SOURCE_UNFETCHABLE",
|
|
19466
|
-
`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.`
|
|
19467
19723
|
);
|
|
19468
19724
|
}
|
|
19469
19725
|
}
|
|
@@ -20565,6 +20821,18 @@ var init_invariant_backing = __esm({
|
|
|
20565
20821
|
});
|
|
20566
20822
|
|
|
20567
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
|
+
}
|
|
20568
20836
|
var invariantReferencesRule;
|
|
20569
20837
|
var init_invariant_references = __esm({
|
|
20570
20838
|
"src/core/rules/wiring/invariant-references.ts"() {
|
|
@@ -20573,7 +20841,7 @@ var init_invariant_references = __esm({
|
|
|
20573
20841
|
invariantReferencesRule = {
|
|
20574
20842
|
name: "invariant-references",
|
|
20575
20843
|
judges: "design",
|
|
20576
|
-
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.",
|
|
20577
20845
|
codes: [
|
|
20578
20846
|
{ code: "UNKNOWN_INVARIANT_REF", defaultSeverity: "error", summary: "A narrative step asserts an invariant that no entity declares" }
|
|
20579
20847
|
],
|
|
@@ -20588,7 +20856,7 @@ var init_invariant_references = __esm({
|
|
|
20588
20856
|
ctx.addIssue(
|
|
20589
20857
|
"error",
|
|
20590
20858
|
"UNKNOWN_INVARIANT_REF",
|
|
20591
|
-
`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),
|
|
20592
20860
|
impl.id,
|
|
20593
20861
|
isDraftCtx
|
|
20594
20862
|
);
|
|
@@ -27296,8 +27564,8 @@ function generateSequenceDiagram(componentId, methodName, options) {
|
|
|
27296
27564
|
const steps = [...methodImpl.narrative].sort((a, b) => a.stepNumber - b.stepNumber);
|
|
27297
27565
|
for (const step of steps) {
|
|
27298
27566
|
for (const par of parallelArms) {
|
|
27299
|
-
const
|
|
27300
|
-
if (
|
|
27567
|
+
const sep19 = par.sepByStep.get(step.stepNumber);
|
|
27568
|
+
if (sep19 !== void 0) lines.push(` and ${escapeLabel(sep19)}`);
|
|
27301
27569
|
}
|
|
27302
27570
|
switch (step.type) {
|
|
27303
27571
|
case "local":
|
|
@@ -28075,6 +28343,15 @@ function classifySource(world, sourceId, label7, sources) {
|
|
|
28075
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`
|
|
28076
28344
|
};
|
|
28077
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
|
+
}
|
|
28078
28355
|
function bindOwnType(world, owner, e, problems) {
|
|
28079
28356
|
const type = (world.types.get(e.typeDef) ?? []).find((t) => !t.subsystem);
|
|
28080
28357
|
if (!type) {
|
|
@@ -28146,7 +28423,8 @@ function resolveProjectTable(system, subsystems, components, interfaces, types,
|
|
|
28146
28423
|
const owner = sources.namespace ? sources.namespace : system.name;
|
|
28147
28424
|
const problems = [];
|
|
28148
28425
|
const candidates = [];
|
|
28149
|
-
|
|
28426
|
+
const entries = system.publicInterfaces ?? [];
|
|
28427
|
+
entries.forEach((e, index) => {
|
|
28150
28428
|
const label7 = e.as ?? e.id ?? e.interface ?? e.component ?? e.typeDef ?? `#${index + 1}`;
|
|
28151
28429
|
const source = classifySource(world, sourceOf(world, e), label7, sources);
|
|
28152
28430
|
switch (source.kind) {
|
|
@@ -28172,7 +28450,7 @@ function resolveProjectTable(system, subsystems, components, interfaces, types,
|
|
|
28172
28450
|
return;
|
|
28173
28451
|
}
|
|
28174
28452
|
default:
|
|
28175
|
-
problems.push({ kind: "invalid", owner, publicName: label7, detail: source.detail });
|
|
28453
|
+
problems.push({ kind: "invalid", owner, publicName: label7, detail: duplicateDetail(entries, index) ?? source.detail });
|
|
28176
28454
|
}
|
|
28177
28455
|
});
|
|
28178
28456
|
return settle(owner, "project", candidates, problems);
|
|
@@ -28769,6 +29047,16 @@ function qualifiedTypeNames(typeStr) {
|
|
|
28769
29047
|
}
|
|
28770
29048
|
return out;
|
|
28771
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
|
+
}
|
|
28772
29060
|
function rawReferences(kind, spec) {
|
|
28773
29061
|
const out = [];
|
|
28774
29062
|
const add2 = (position, value) => {
|
|
@@ -28791,6 +29079,7 @@ function rawReferences(kind, spec) {
|
|
|
28791
29079
|
for (const step of m.narrative) {
|
|
28792
29080
|
const source = step.auth?.from;
|
|
28793
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));
|
|
28794
29083
|
}
|
|
28795
29084
|
}
|
|
28796
29085
|
break;
|
|
@@ -28909,6 +29198,9 @@ function respellStoredReferences(kind, doc, map) {
|
|
|
28909
29198
|
for (const m of list2(doc.methods)) {
|
|
28910
29199
|
for (const step of list2(m.narrative)) {
|
|
28911
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
|
+
}
|
|
28912
29204
|
const auth = step.auth;
|
|
28913
29205
|
if (auth && typeof auth.from === "string" && auth.from.startsWith(COMPONENT_AUTH_SOURCE)) {
|
|
28914
29206
|
auth.from = `${COMPONENT_AUTH_SOURCE}${map("auth", auth.from.slice(COMPONENT_AUTH_SOURCE.length))}`;
|
|
@@ -28968,7 +29260,8 @@ function mapRawReferences(kind, spec, map) {
|
|
|
28968
29260
|
return call && next !== call.compId ? `${next}.${call.methodName}` : entry;
|
|
28969
29261
|
})
|
|
28970
29262
|
} : {},
|
|
28971
|
-
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;
|
|
28972
29265
|
const source = step.auth?.from;
|
|
28973
29266
|
if (typeof source !== "string" || !source.startsWith(COMPONENT_AUTH_SOURCE)) return step;
|
|
28974
29267
|
const auth = step.auth;
|
|
@@ -29069,7 +29362,7 @@ function resolveFamilyTables(raws) {
|
|
|
29069
29362
|
const byKey3 = new Map(raws.map((r) => [r.record.namespace, r]));
|
|
29070
29363
|
const done = /* @__PURE__ */ new Set();
|
|
29071
29364
|
const resolving = /* @__PURE__ */ new Set();
|
|
29072
|
-
const
|
|
29365
|
+
const resolve56 = (raw) => {
|
|
29073
29366
|
const key = raw.record.namespace;
|
|
29074
29367
|
if (done.has(key)) return raw.record.exports;
|
|
29075
29368
|
if (resolving.has(key)) return null;
|
|
@@ -29084,7 +29377,7 @@ function resolveFamilyTables(raws) {
|
|
|
29084
29377
|
const producerKey = source !== void 0 ? raw.record.aliases.get(source) : void 0;
|
|
29085
29378
|
if (producerKey === void 0 || producerTables.has(producerKey)) continue;
|
|
29086
29379
|
const producer = byKey3.get(producerKey);
|
|
29087
|
-
producerTables.set(producerKey, producer ?
|
|
29380
|
+
producerTables.set(producerKey, producer ? resolve56(producer) : null);
|
|
29088
29381
|
audienceOf.set(producerKey, "project");
|
|
29089
29382
|
}
|
|
29090
29383
|
raw.record.subsystemExports = subsystemTables;
|
|
@@ -29100,7 +29393,7 @@ function resolveFamilyTables(raws) {
|
|
|
29100
29393
|
resolving.delete(key);
|
|
29101
29394
|
}
|
|
29102
29395
|
};
|
|
29103
|
-
raws.forEach((raw) =>
|
|
29396
|
+
raws.forEach((raw) => resolve56(raw));
|
|
29104
29397
|
}
|
|
29105
29398
|
function bindAuthored(raw, raws, readables, authored) {
|
|
29106
29399
|
const key = raw.record.namespace;
|
|
@@ -31535,12 +31828,12 @@ var init_specs2 = __esm({
|
|
|
31535
31828
|
return this.relativizeSpec("interface", stored);
|
|
31536
31829
|
}
|
|
31537
31830
|
prepareImplementationForWrite(spec, carry = true) {
|
|
31538
|
-
const
|
|
31831
|
+
const relative28 = this.relativizeSpec("implementation", spec, carry);
|
|
31539
31832
|
const member = this.getSubprojectPrefix(spec.id);
|
|
31540
31833
|
const memberRoot = member ? this.resolveSubprojectForNamespace(member) : null;
|
|
31541
|
-
if (memberRoot) return childRelativeFilePaths(
|
|
31834
|
+
if (memberRoot) return childRelativeFilePaths(relative28, this.rootDir, memberRoot);
|
|
31542
31835
|
const partDir = this.partHolding(this.getImplementationPath(spec.id, spec.contract))?.part.directory;
|
|
31543
|
-
return partDir ? partRelativeFilePaths(
|
|
31836
|
+
return partDir ? partRelativeFilePaths(relative28, this.rootDir, partDir) : relative28;
|
|
31544
31837
|
}
|
|
31545
31838
|
/**
|
|
31546
31839
|
* A type written in its stored form — every type position canonical (one
|
|
@@ -33697,7 +33990,7 @@ function resolveGit(external, config) {
|
|
|
33697
33990
|
const declared = gitDeclarationOf(config, external);
|
|
33698
33991
|
if (!declared) return { ...external, problem: "git producer unavailable: its declaration names no repository" };
|
|
33699
33992
|
try {
|
|
33700
|
-
const commit3 = external.role === "member" && declared.commit ? declared.commit :
|
|
33993
|
+
const commit3 = external.role === "member" && declared.commit ? declared.commit : resolve3(declared.url, declared.ref);
|
|
33701
33994
|
const directory = fetch2(declared.url, commit3, declared.dir);
|
|
33702
33995
|
return { ...external, directory: path23.resolve(directory), commit: commit3, problem: void 0 };
|
|
33703
33996
|
} catch (e) {
|
|
@@ -34148,7 +34441,7 @@ function scaffoldPart(alias, memberDir, contained) {
|
|
|
34148
34441
|
}
|
|
34149
34442
|
function createGitMember(alias, source, description, as) {
|
|
34150
34443
|
const url = source.git;
|
|
34151
|
-
const commit3 = source.commit ??
|
|
34444
|
+
const commit3 = source.commit ?? resolve3(url, source.ref);
|
|
34152
34445
|
const dir = fetch2(url, commit3, source.dir);
|
|
34153
34446
|
const kind = holdsProjectContent(dir) ? "project" : "part";
|
|
34154
34447
|
if (as !== void 0 && as !== kind) {
|
|
@@ -35090,7 +35383,7 @@ function advanceMember(alias, ref, commit3, dryRun) {
|
|
|
35090
35383
|
const url = member.source.git;
|
|
35091
35384
|
const from = member.source.commit ?? "";
|
|
35092
35385
|
const followed = commit3 === void 0 ? ref ?? member.source.ref : void 0;
|
|
35093
|
-
const to = commit3 ??
|
|
35386
|
+
const to = commit3 ?? resolve3(url, followed);
|
|
35094
35387
|
const after = specContents(fetch2(url, to, member.source.dir));
|
|
35095
35388
|
const before = specContents(from !== "" ? fetch2(url, from, member.source.dir) : null);
|
|
35096
35389
|
const added = [...after.keys()].filter((f) => !before.has(f)).sort();
|
|
@@ -36699,17 +36992,17 @@ function memberHop(agentId) {
|
|
|
36699
36992
|
const alias = agentId.slice(0, at);
|
|
36700
36993
|
const member = graph().nodes.find((n) => n.parent === "" && n.mountAlias === alias);
|
|
36701
36994
|
if (!member) return null;
|
|
36702
|
-
const
|
|
36703
|
-
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 };
|
|
36704
36997
|
}
|
|
36705
|
-
function throughMount(brief, alias,
|
|
36706
|
-
const under = (p) => path29.posix.join(
|
|
36998
|
+
function throughMount(brief, alias, relative28) {
|
|
36999
|
+
const under = (p) => path29.posix.join(relative28, p);
|
|
36707
37000
|
return {
|
|
36708
37001
|
...brief,
|
|
36709
37002
|
agentId: `${alias}::${brief.agentId}`,
|
|
36710
37003
|
ownedPaths: brief.ownedPaths.map(under),
|
|
36711
37004
|
...brief.readPaths ? { readPaths: brief.readPaths.map(under) } : {},
|
|
36712
|
-
root: brief.root ? under(brief.root) :
|
|
37005
|
+
root: brief.root ? under(brief.root) : relative28
|
|
36713
37006
|
};
|
|
36714
37007
|
}
|
|
36715
37008
|
function loadRegistry() {
|
|
@@ -40100,8 +40393,8 @@ var require_dist = __commonJS({
|
|
|
40100
40393
|
walkPackDir(root, path93.join(current2, entry.name), out);
|
|
40101
40394
|
} else if (entry.isFile()) {
|
|
40102
40395
|
const absolute = path93.join(current2, entry.name);
|
|
40103
|
-
const
|
|
40104
|
-
out.push({ path:
|
|
40396
|
+
const relative28 = path93.relative(root, absolute).split(path93.sep).join("/");
|
|
40397
|
+
out.push({ path: relative28, contents: fs69.readFileSync(absolute) });
|
|
40105
40398
|
}
|
|
40106
40399
|
}
|
|
40107
40400
|
}
|
|
@@ -40301,8 +40594,8 @@ var require_dist = __commonJS({
|
|
|
40301
40594
|
return normalized === "." ? ".wai/" : `${normalized}/.wai/`;
|
|
40302
40595
|
}
|
|
40303
40596
|
function normalizeRootPath(relativePath) {
|
|
40304
|
-
const
|
|
40305
|
-
return
|
|
40597
|
+
const posix8 = relativePath.split(path210.sep).join("/").replace(/^\.\//, "").replace(/\/+$/, "");
|
|
40598
|
+
return posix8 === "" || posix8 === "." ? "." : posix8;
|
|
40306
40599
|
}
|
|
40307
40600
|
function slugify2(projectName) {
|
|
40308
40601
|
const slug2 = projectName.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
@@ -40408,19 +40701,19 @@ function commitStagedTree(stagingDir, destDir, roots, backupExisting) {
|
|
|
40408
40701
|
const backupDir = path34.join(root, BACKUP_DIR, timestampSlug());
|
|
40409
40702
|
let backedUp = false;
|
|
40410
40703
|
for (const rootRel of roots) {
|
|
40411
|
-
const
|
|
40412
|
-
const stagedSpecDir = path34.join(stagingDir,
|
|
40704
|
+
const relative28 = rootRel === "." ? "" : rootRel;
|
|
40705
|
+
const stagedSpecDir = path34.join(stagingDir, relative28, ".wai");
|
|
40413
40706
|
if (!fs20.existsSync(stagedSpecDir)) continue;
|
|
40414
|
-
const liveSpecDir = liveSpecDirFor(path34.join(root,
|
|
40707
|
+
const liveSpecDir = liveSpecDirFor(path34.join(root, relative28));
|
|
40415
40708
|
if (backupExisting && liveSpecDir && fs20.existsSync(liveSpecDir)) {
|
|
40416
|
-
const backupTarget = path34.join(backupDir,
|
|
40709
|
+
const backupTarget = path34.join(backupDir, relative28, path34.basename(liveSpecDir));
|
|
40417
40710
|
fs20.mkdirSync(path34.dirname(backupTarget), { recursive: true });
|
|
40418
40711
|
fs20.renameSync(liveSpecDir, backupTarget);
|
|
40419
40712
|
backedUp = true;
|
|
40420
40713
|
} else if (liveSpecDir && fs20.existsSync(liveSpecDir)) {
|
|
40421
40714
|
fs20.rmSync(liveSpecDir, { recursive: true, force: true });
|
|
40422
40715
|
}
|
|
40423
|
-
const target = path34.join(root,
|
|
40716
|
+
const target = path34.join(root, relative28, ".wai");
|
|
40424
40717
|
fs20.mkdirSync(path34.dirname(target), { recursive: true });
|
|
40425
40718
|
if (fs20.existsSync(target)) fs20.rmSync(target, { recursive: true, force: true });
|
|
40426
40719
|
fs20.renameSync(stagedSpecDir, target);
|
|
@@ -40735,7 +41028,7 @@ function repairTypeSpellings(apply4) {
|
|
|
40735
41028
|
const key = `${kind}:${specId}`;
|
|
40736
41029
|
let repair = repairs.get(key);
|
|
40737
41030
|
if (!repair) {
|
|
40738
|
-
repair = { specId, kind, rewritten: [], proposals: [], authorNeeded: [] };
|
|
41031
|
+
repair = { specId, kind, rewritten: [], proposals: [], enumProposals: [], authorNeeded: [] };
|
|
40739
41032
|
repairs.set(key, repair);
|
|
40740
41033
|
}
|
|
40741
41034
|
return repair;
|
|
@@ -40750,7 +41043,9 @@ function repairTypeSpellings(apply4) {
|
|
|
40750
41043
|
if (problem.specId === void 0 || problem.kind === void 0 || !own(problem.specId)) continue;
|
|
40751
41044
|
const repair = repairOf(problem.specId, problem.kind);
|
|
40752
41045
|
const proposal = typeProblemIntProposal(problem);
|
|
41046
|
+
const enumProposal = proposal ? null : typeProblemEnumProposal(problem);
|
|
40753
41047
|
if (proposal) repair.proposals.push(proposal);
|
|
41048
|
+
else if (enumProposal) repair.enumProposals.push(enumProposal);
|
|
40754
41049
|
else repair.authorNeeded.push(problem);
|
|
40755
41050
|
}
|
|
40756
41051
|
const planned = [...repairs.values()];
|
|
@@ -40960,7 +41255,7 @@ var init_approval = __esm({
|
|
|
40960
41255
|
init_lockfile();
|
|
40961
41256
|
init_lock();
|
|
40962
41257
|
init_project_family();
|
|
40963
|
-
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)";
|
|
40964
41259
|
}
|
|
40965
41260
|
});
|
|
40966
41261
|
|
|
@@ -41139,16 +41434,16 @@ var init_ai_guide = __esm({
|
|
|
41139
41434
|
If \`.wai/specs/\` exists, the wairon SDD workflow is active; otherwise ignore it. wairon does not orchestrate sessions \u2014 it equips yours.
|
|
41140
41435
|
|
|
41141
41436
|
### In SDD Projects:
|
|
41142
|
-
- **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.
|
|
41143
41438
|
- **Validation**: Conformance checks (stereotype rules, cycle checks, reference integrity) are run via the \`sdd_validate_tree\` MCP tool.
|
|
41144
41439
|
- **Operating Rules**:
|
|
41145
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.
|
|
41146
41441
|
2. **MCP Tools Only**: Author/validate specs *only* via \`sdd_*\` tools (e.g. \`sdd_initialize_system\`, \`sdd_validate_tree\`).
|
|
41147
41442
|
3. **No CLI Exec**: Do not run the \`wairon\` CLI (human tool). Use MCP tools \`sdd_validate_tree\` and \`sdd_get_status\` instead.
|
|
41148
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>\`).
|
|
41149
|
-
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.
|
|
41150
41445
|
6. **Consistency**: Code must match L3 interfaces and L5 narratives exactly. If the spec is wrong, stop and update the spec.
|
|
41151
|
-
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.`;
|
|
41152
41447
|
LOCAL_GUIDE_BODY = `## Wairon \u2014 Spec-Driven Development (you are operating inside it)
|
|
41153
41448
|
|
|
41154
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.
|
|
@@ -41165,7 +41460,8 @@ This project uses **wairon**. System specs live under \`.wai/specs/\` (L0 System
|
|
|
41165
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\`).
|
|
41166
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\`).
|
|
41167
41462
|
- **Do not run the \`wairon\` CLI**: Use \`sdd_validate_tree\` and \`sdd_get_status\` instead of CLI commands.
|
|
41168
|
-
- **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\`.
|
|
41169
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.
|
|
41170
41466
|
|
|
41171
41467
|
### Rules (enforced by \`sdd_validate_tree\`)
|
|
@@ -41545,7 +41841,7 @@ function getStatusReport(options = {}, decor) {
|
|
|
41545
41841
|
}
|
|
41546
41842
|
const own = options.approvals?.find((a) => a.key === "");
|
|
41547
41843
|
const hasMembers = (options.approvals ?? []).some((a) => a.key !== "" && a.as !== "part");
|
|
41548
|
-
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)" : ""}
|
|
41549
41845
|
`;
|
|
41550
41846
|
return { text: output, failed: false };
|
|
41551
41847
|
}
|
|
@@ -43211,7 +43507,7 @@ var init_position_migration = __esm({
|
|
|
43211
43507
|
auth: ["implementation"],
|
|
43212
43508
|
subsystem: ["component", "type"],
|
|
43213
43509
|
group: ["type"],
|
|
43214
|
-
type: ["interface", "type"]
|
|
43510
|
+
type: ["interface", "type", "implementation"]
|
|
43215
43511
|
};
|
|
43216
43512
|
BOUND = /* @__PURE__ */ new Set(["publicInterfaces", "lifecycle", "subsystem", "owns", "dependsOn", "dispatch", "mounts", "contract", "narrative", "group"]);
|
|
43217
43513
|
compare2 = (a, b) => a < b ? -1 : a > b ? 1 : 0;
|
|
@@ -44172,7 +44468,7 @@ function attachEdits(plan7, family, bound2, dir, alias, bindingConfig) {
|
|
|
44172
44468
|
edit(plan7, key, "id", `id: ${id} (declared${approved !== void 0 ? ", the id its lock approved" : ""})`, { root: dir, call: "setId", args: [id] });
|
|
44173
44469
|
}
|
|
44174
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`);
|
|
44175
|
-
const relPath =
|
|
44471
|
+
const relPath = posix5(path45.relative(familyNode(family, bound2).directory, dir));
|
|
44176
44472
|
const declaration2 = { path: relPath, ...plan7.request.description !== void 0 ? { description: plan7.request.description } : {} };
|
|
44177
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] });
|
|
44178
44474
|
for (const requirement of requiredPolicies(bindingConfig)) {
|
|
@@ -44219,8 +44515,8 @@ function planDetach(family, bound2, request) {
|
|
|
44219
44515
|
if (plan7.request.widen) planWidening(plan7, member, narrow);
|
|
44220
44516
|
if (plan7.refusals.length > 0) return plan7;
|
|
44221
44517
|
const boundDir = familyNode(family, bound2).directory;
|
|
44222
|
-
const held = memberDeclarationOf(configAt3(boundDir)?.members?.[alias] ??
|
|
44223
|
-
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)) };
|
|
44224
44520
|
detachEdits(plan7, family, bound2, member, alias, declared, consumers, outward);
|
|
44225
44521
|
return plan7;
|
|
44226
44522
|
}
|
|
@@ -44260,7 +44556,7 @@ function planWidening(plan7, member, narrow) {
|
|
|
44260
44556
|
function detachEdits(plan7, family, bound2, member, alias, declared, consumers, outward) {
|
|
44261
44557
|
const boundDir = familyNode(family, bound2).directory;
|
|
44262
44558
|
const relocation = plan7.request.relocation;
|
|
44263
|
-
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)) };
|
|
44264
44560
|
const where = (source) => source.hosted !== void 0 ? `hosted record ${source.hosted}` : source.path;
|
|
44265
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] });
|
|
44266
44562
|
const boundSource = relocation ? sourceTo(boundDir, member) : { path: declared.path };
|
|
@@ -44334,7 +44630,7 @@ function adoptEdits(plan7, family, bound2, alias, external, dir, at = dir) {
|
|
|
44334
44630
|
edit(plan7, bound2, "pin", `pin ${alias} removed (the family supplies it now)`, { root: boundDir, call: "unpin", args: [alias] });
|
|
44335
44631
|
edit(plan7, bound2, "external", `externals: ${alias} removed`, { root: boundDir, call: "removeExternal", args: [alias] });
|
|
44336
44632
|
const declaration2 = {
|
|
44337
|
-
path:
|
|
44633
|
+
path: posix5(path45.relative(boundDir, at)),
|
|
44338
44634
|
...external.description !== void 0 ? { description: external.description } : {},
|
|
44339
44635
|
...external.use && external.use.length > 0 ? { use: external.use } : {}
|
|
44340
44636
|
};
|
|
@@ -44407,7 +44703,7 @@ function writeOne(rehearsal, w) {
|
|
|
44407
44703
|
}
|
|
44408
44704
|
});
|
|
44409
44705
|
}
|
|
44410
|
-
var fs26, path45,
|
|
44706
|
+
var fs26, path45, posix5, refuse, edit, label4;
|
|
44411
44707
|
var init_membership = __esm({
|
|
44412
44708
|
"src/migrations/membership.ts"() {
|
|
44413
44709
|
"use strict";
|
|
@@ -44422,7 +44718,7 @@ var init_membership = __esm({
|
|
|
44422
44718
|
init_project_family();
|
|
44423
44719
|
init_specs();
|
|
44424
44720
|
init_types2();
|
|
44425
|
-
|
|
44721
|
+
posix5 = (p) => p.split(path45.sep).join("/");
|
|
44426
44722
|
refuse = (plan7, code, project2, detail) => {
|
|
44427
44723
|
plan7.refusals.push({ code, project: project2, detail });
|
|
44428
44724
|
};
|
|
@@ -44670,7 +44966,7 @@ var init_identity = __esm({
|
|
|
44670
44966
|
auth: ["implementation"],
|
|
44671
44967
|
subsystem: ["component", "type"],
|
|
44672
44968
|
group: ["type"],
|
|
44673
|
-
type: ["interface", "type"]
|
|
44969
|
+
type: ["interface", "type", "implementation"]
|
|
44674
44970
|
};
|
|
44675
44971
|
PHASES = ["rewriteReferences", "renameAlias", "repointExternal", "renameId", "renamePin"];
|
|
44676
44972
|
}
|
|
@@ -44868,7 +45164,7 @@ function respellConsumer(plan7, family, c, own) {
|
|
|
44868
45164
|
function repointConsumer(plan7, c, boundNode2) {
|
|
44869
45165
|
const declared = configAt5(c.node.directory)?.externals?.[c.alias];
|
|
44870
45166
|
const project2 = boundNode2.id !== void 0 && boundNode2.id !== c.alias ? boundNode2.id : null;
|
|
44871
|
-
const source = declared?.source ? { path:
|
|
45167
|
+
const source = declared?.source ? { path: posix6(path47.relative(c.node.directory, boundNode2.directory)) } : null;
|
|
44872
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] });
|
|
44873
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.`);
|
|
44874
45170
|
return c;
|
|
@@ -44904,8 +45200,8 @@ function planExternalize(family, bound2, request) {
|
|
|
44904
45200
|
plan7,
|
|
44905
45201
|
bound2,
|
|
44906
45202
|
"move",
|
|
44907
|
-
`externalize ${id}: its specs into ${joined ? `the part "${joined.alias}"` : `a part at ${
|
|
44908
|
-
{ 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))] }
|
|
44909
45205
|
);
|
|
44910
45206
|
plan7.notes.push("Source code is not moved: move the subsystem's code yourself if it should live beside its specs.");
|
|
44911
45207
|
return plan7;
|
|
@@ -44914,7 +45210,7 @@ function planExternalize(family, bound2, request) {
|
|
|
44914
45210
|
crossingsBack(plan7, family, bound2, subKey);
|
|
44915
45211
|
checkSiblings(plan7, family, bound2, id, subKey, table);
|
|
44916
45212
|
if (plan7.refusals.length > 0) return plan7;
|
|
44917
|
-
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"] });
|
|
44918
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: [[]] });
|
|
44919
45215
|
plan7.notes.push("Source code is not moved: move the subsystem's code into the member yourself.");
|
|
44920
45216
|
return plan7;
|
|
@@ -45159,7 +45455,7 @@ function writeOne3(rehearsal, w) {
|
|
|
45159
45455
|
}
|
|
45160
45456
|
});
|
|
45161
45457
|
}
|
|
45162
|
-
var path47,
|
|
45458
|
+
var path47, posix6, label6, refuse3, edit3, KINDS_AT3;
|
|
45163
45459
|
var init_boundary = __esm({
|
|
45164
45460
|
"src/migrations/boundary.ts"() {
|
|
45165
45461
|
"use strict";
|
|
@@ -45171,7 +45467,7 @@ var init_boundary = __esm({
|
|
|
45171
45467
|
init_project();
|
|
45172
45468
|
init_project_family();
|
|
45173
45469
|
init_types2();
|
|
45174
|
-
|
|
45470
|
+
posix6 = (p) => p.split(path47.sep).join("/");
|
|
45175
45471
|
label6 = (key) => key === "" ? "the top project" : `"${key}"`;
|
|
45176
45472
|
refuse3 = (plan7, code, project2, detail) => {
|
|
45177
45473
|
plan7.refusals.push({ code, project: project2, detail });
|
|
@@ -45193,7 +45489,7 @@ var init_boundary = __esm({
|
|
|
45193
45489
|
auth: ["implementation"],
|
|
45194
45490
|
subsystem: ["component", "type"],
|
|
45195
45491
|
group: ["type"],
|
|
45196
|
-
type: ["interface", "type"]
|
|
45492
|
+
type: ["interface", "type", "implementation"]
|
|
45197
45493
|
};
|
|
45198
45494
|
}
|
|
45199
45495
|
});
|
|
@@ -46222,6 +46518,18 @@ function renderChangeReport(report4) {
|
|
|
46222
46518
|
lines.push(...renderTestsToRevisit(report4.testsToRevisit));
|
|
46223
46519
|
return lines.join("\n");
|
|
46224
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
|
+
}
|
|
46225
46533
|
function renderTestsToRevisit(entries) {
|
|
46226
46534
|
if (!entries.length) return [];
|
|
46227
46535
|
const lines = ["", "TESTS TO REVISIT:"];
|
|
@@ -46806,7 +47114,7 @@ ${renderChangeReport(report4)}`,
|
|
|
46806
47114
|
server,
|
|
46807
47115
|
"sdd_add_member",
|
|
46808
47116
|
{
|
|
46809
|
-
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.",
|
|
46810
47118
|
inputSchema: {
|
|
46811
47119
|
alias: import_zod12.z.string().describe("The member's alias ([a-z0-9-_]+); a project's new id"),
|
|
46812
47120
|
source: import_zod12.z.string().describe("A contained path, a `../` sibling path, or a git URL (optionally `#<commit>`)"),
|
|
@@ -46876,7 +47184,7 @@ ${renderChangeReport(report4)}`,
|
|
|
46876
47184
|
server,
|
|
46877
47185
|
"sdd_externalize_subsystem",
|
|
46878
47186
|
{
|
|
46879
|
-
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.",
|
|
46880
47188
|
inputSchema: {
|
|
46881
47189
|
subsystem: import_zod12.z.string().describe("The subsystem to move out"),
|
|
46882
47190
|
path: import_zod12.z.string().describe("The part's root, relative to the bound project (a new directory, or an existing part's)"),
|
|
@@ -46890,7 +47198,7 @@ ${renderChangeReport(report4)}`,
|
|
|
46890
47198
|
server,
|
|
46891
47199
|
"sdd_promote_member",
|
|
46892
47200
|
{
|
|
46893
|
-
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.",
|
|
46894
47202
|
inputSchema: {
|
|
46895
47203
|
alias: import_zod12.z.string().describe("The part's alias"),
|
|
46896
47204
|
id: import_zod12.z.string().optional().describe("The new project's id (default: the alias)"),
|
|
@@ -46903,7 +47211,7 @@ ${renderChangeReport(report4)}`,
|
|
|
46903
47211
|
server,
|
|
46904
47212
|
"sdd_demote_member",
|
|
46905
47213
|
{
|
|
46906
|
-
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.",
|
|
46907
47215
|
inputSchema: {
|
|
46908
47216
|
alias: import_zod12.z.string().describe("The project member's alias"),
|
|
46909
47217
|
destination: import_zod12.z.object({
|
|
@@ -47246,7 +47554,7 @@ ${renderChangeReport(report4)}`,
|
|
|
47246
47554
|
interface: import_zod12.z.string().describe('The L3 interface ID (e.g. "ibilling-gateway")'),
|
|
47247
47555
|
endpoints: import_zod12.z.array(import_zod12.z.object({
|
|
47248
47556
|
method: import_zod12.z.string().describe("The NAME of the interface method to bind (not the HTTP verb)"),
|
|
47249
|
-
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"),
|
|
47250
47558
|
httpMethod: import_zod12.z.enum(["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"]).optional().describe("HTTP: verb"),
|
|
47251
47559
|
path: import_zod12.z.string().optional().describe('HTTP: route path, e.g. "/v1/checkout"'),
|
|
47252
47560
|
service: import_zod12.z.string().optional().describe("gRPC: service name"),
|
|
@@ -47269,8 +47577,10 @@ ${renderChangeReport(report4)}`,
|
|
|
47269
47577
|
try {
|
|
47270
47578
|
const intf = loadInterfaceSpec(interfaceId);
|
|
47271
47579
|
if (!intf) return errText(`Interface "${interfaceId}" does not exist.`);
|
|
47272
|
-
const
|
|
47273
|
-
|
|
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) {
|
|
47274
47584
|
case "HTTP":
|
|
47275
47585
|
return { transport: "HTTP", method: e.httpMethod, path: e.path };
|
|
47276
47586
|
case "gRPC":
|
|
@@ -47294,13 +47604,19 @@ ${renderChangeReport(report4)}`,
|
|
|
47294
47604
|
for (const e of endpoints) {
|
|
47295
47605
|
const m = intf.methods.find((x) => x.name === e.method);
|
|
47296
47606
|
if (!m) return errText(`Method "${e.method}" not found on interface "${interfaceId}". Define it via sdd_define_interface first.`);
|
|
47297
|
-
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));
|
|
47298
47614
|
if (!parsed.success) {
|
|
47299
47615
|
const detail = parsed.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ");
|
|
47300
|
-
return errText(`Invalid ${
|
|
47616
|
+
return errText(`Invalid ${transport} endpoint for method "${e.method}": ${detail}`);
|
|
47301
47617
|
}
|
|
47302
47618
|
methods.push({ name: e.method, endpoint: parsed.data });
|
|
47303
|
-
bound2.push(`${e.method}\u2192${
|
|
47619
|
+
bound2.push(`${e.method}\u2192${transport}`);
|
|
47304
47620
|
}
|
|
47305
47621
|
const report4 = updateSpecGated("interface", interfaceId, { methods });
|
|
47306
47622
|
return structured(
|
|
@@ -47561,14 +47877,24 @@ ${renderChangeReport(report4)}`,
|
|
|
47561
47877
|
{
|
|
47562
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.`,
|
|
47563
47879
|
inputSchema: {
|
|
47564
|
-
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"),
|
|
47565
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")'),
|
|
47566
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.")
|
|
47567
47883
|
},
|
|
47568
47884
|
outputSchema: getSpecOutput
|
|
47569
47885
|
},
|
|
47570
|
-
({ kind, id, methods }) => {
|
|
47886
|
+
({ kind: askedKind, id, methods }) => {
|
|
47571
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
|
+
}
|
|
47572
47898
|
let result = null;
|
|
47573
47899
|
let partial2 = null;
|
|
47574
47900
|
switch (kind) {
|
|
@@ -47927,7 +48253,7 @@ async function startMcpServer() {
|
|
|
47927
48253
|
} catch {
|
|
47928
48254
|
}
|
|
47929
48255
|
}
|
|
47930
|
-
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;
|
|
47931
48257
|
var init_server = __esm({
|
|
47932
48258
|
"src/mcp/server.ts"() {
|
|
47933
48259
|
"use strict";
|
|
@@ -47956,7 +48282,7 @@ var init_server = __esm({
|
|
|
47956
48282
|
init_project();
|
|
47957
48283
|
init_validation_options();
|
|
47958
48284
|
init_migrations3();
|
|
47959
|
-
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.`;
|
|
47960
48286
|
staleServerOutput = {
|
|
47961
48287
|
staleServer: import_zod12.z.boolean().optional().describe(
|
|
47962
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."
|
|
@@ -47996,6 +48322,16 @@ var init_server = __esm({
|
|
|
47996
48322
|
...staleServerOutput
|
|
47997
48323
|
};
|
|
47998
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
|
+
};
|
|
47999
48335
|
STATUS_VALUES = ["draft", "design", "complete"];
|
|
48000
48336
|
testsToRevisitOutput = import_zod12.z.array(import_zod12.z.object({
|
|
48001
48337
|
method: import_zod12.z.string().describe("The contract method this write changed or deleted."),
|
|
@@ -48110,7 +48446,7 @@ var init_server = __esm({
|
|
|
48110
48446
|
profile: import_zod12.z.string().optional()
|
|
48111
48447
|
})).describe("Requirements no installed version satisfies: nothing was written for them, and the next family run reports each as POLICY_NOT_ADOPTED."),
|
|
48112
48448
|
projectType: import_zod12.z.string().optional().describe("The profile written as the new member's projectType, when exactly one adopted requirement names one."),
|
|
48113
|
-
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)."),
|
|
48114
48450
|
storage: import_zod12.z.string().describe("contained | path | git: where its files live."),
|
|
48115
48451
|
commit: import_zod12.z.string().optional().describe("git: the commit the member was pinned at."),
|
|
48116
48452
|
...staleServerOutput
|
|
@@ -48439,8 +48775,7 @@ function resolveInstallTargets(options) {
|
|
|
48439
48775
|
const isPackaged = typeof process.pkg !== "undefined";
|
|
48440
48776
|
const scriptPath = process.argv[1] ? path51.resolve(process.argv[1]).replace(/\\/g, "/") : null;
|
|
48441
48777
|
const useDirectNode = !isPackaged && scriptPath && (scriptPath.endsWith(".js") || scriptPath.endsWith(".ts"));
|
|
48442
|
-
const
|
|
48443
|
-
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);
|
|
48444
48779
|
const existingEntry = mcpServers["wairon"];
|
|
48445
48780
|
return {
|
|
48446
48781
|
backend,
|
|
@@ -48455,6 +48790,16 @@ function resolveInstallTargets(options) {
|
|
|
48455
48790
|
};
|
|
48456
48791
|
});
|
|
48457
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
|
+
}
|
|
48458
48803
|
function planMcpInstall(options) {
|
|
48459
48804
|
return resolveInstallTargets(options).map(({ backend, path: file, outsideProject, before, after, upToDate }) => ({
|
|
48460
48805
|
backend,
|
|
@@ -48490,10 +48835,8 @@ function reportInstalled(target, agentLabel) {
|
|
|
48490
48835
|
const wasStale = target.before !== void 0;
|
|
48491
48836
|
logger.success(`wairon MCP server ${wasStale ? "updated (was stale)" : "registered"} for ${agentLabel} in ${import_chalk3.default.cyan(target.path)}.`);
|
|
48492
48837
|
logger.blank();
|
|
48493
|
-
logger.info("AI tools using this config
|
|
48494
|
-
logger.info(
|
|
48495
|
-
logger.info(" sdd_initialize_system \xB7 sdd_add_subsystem \xB7 sdd_add_component \xB7 sdd_define_interface");
|
|
48496
|
-
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").');
|
|
48497
48840
|
logger.blank();
|
|
48498
48841
|
const restartApp = target.backend === "gemini" ? "Antigravity CLI (agy)" : "claude";
|
|
48499
48842
|
logger.info(`Restart ${import_chalk3.default.bold(restartApp)} (or reload MCP servers) to activate.`);
|
|
@@ -48626,7 +48969,8 @@ init_skills_portal();
|
|
|
48626
48969
|
init_base();
|
|
48627
48970
|
init_fs();
|
|
48628
48971
|
init_project();
|
|
48629
|
-
var
|
|
48972
|
+
var AGENT_FILE_EXTENSION = /\.(md|ya?ml)$/;
|
|
48973
|
+
var WAIRON_AGENT_FILE = /-(owner|implementer|architect)\.(md|ya?ml)$/;
|
|
48630
48974
|
function pruneStaleAgents(expectedPaths, scanDirs, reach2) {
|
|
48631
48975
|
const dirs = new Set(scanDirs ?? [...expectedPaths].map((p) => path39.dirname(p)));
|
|
48632
48976
|
let pruned = 0;
|
|
@@ -48640,7 +48984,7 @@ function pruneStaleAgents(expectedPaths, scanDirs, reach2) {
|
|
|
48640
48984
|
continue;
|
|
48641
48985
|
}
|
|
48642
48986
|
for (const name of entries) {
|
|
48643
|
-
if (!
|
|
48987
|
+
if (!AGENT_FILE_EXTENSION.test(name)) continue;
|
|
48644
48988
|
const full = path39.resolve(dir, name);
|
|
48645
48989
|
if (expectedPaths.has(full)) continue;
|
|
48646
48990
|
let owned = WAIRON_AGENT_FILE.test(name);
|
|
@@ -48682,6 +49026,18 @@ function announceOutsideTargets(projectConfig, reach2) {
|
|
|
48682
49026
|
}
|
|
48683
49027
|
return outside.map((t) => t.type);
|
|
48684
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
|
+
};
|
|
48685
49041
|
async function runGenerate(options = {}) {
|
|
48686
49042
|
await generateLayer(options);
|
|
48687
49043
|
const scoped = options.root || options.domain || options.domains;
|
|
@@ -48700,6 +49056,10 @@ async function generateLayer(options = {}) {
|
|
|
48700
49056
|
assertProjectInitialized();
|
|
48701
49057
|
const projectConfig = loadProjectConfig();
|
|
48702
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
|
+
}
|
|
48703
49063
|
const agents = resolveAgentTopology();
|
|
48704
49064
|
if (agents.length === 0) {
|
|
48705
49065
|
logger.warn("No agents resolved from the spec tree. Define subsystems and components first \u2014 see `wairon status`.");
|
|
@@ -48716,6 +49076,8 @@ async function generateLayer(options = {}) {
|
|
|
48716
49076
|
const removed = pruneStaleAgents(/* @__PURE__ */ new Set(), candidateDirs, reach2);
|
|
48717
49077
|
if (removed > 0) {
|
|
48718
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.");
|
|
48719
49081
|
}
|
|
48720
49082
|
}
|
|
48721
49083
|
if (options.dryRun) return;
|
|
@@ -48734,6 +49096,7 @@ async function generateLayer(options = {}) {
|
|
|
48734
49096
|
try {
|
|
48735
49097
|
reinjectLocalGuides(getProjectRoot(), activeTargetTypes(projectConfig));
|
|
48736
49098
|
logger.verbose("Local AI guides re-injected.");
|
|
49099
|
+
logger.info(`Guides, skills and context reconciled for: ${activeTargetTypes(projectConfig).join(", ") || "(no active targets)"}.`);
|
|
48737
49100
|
} catch (err) {
|
|
48738
49101
|
logger.warn(`Failed to re-inject AI guides: ${String(err)}`);
|
|
48739
49102
|
}
|
|
@@ -48843,7 +49206,7 @@ function checkApproval(strict) {
|
|
|
48843
49206
|
return {
|
|
48844
49207
|
state: "stale",
|
|
48845
49208
|
approved: false,
|
|
48846
|
-
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.`
|
|
48847
49210
|
};
|
|
48848
49211
|
default:
|
|
48849
49212
|
return {
|
|
@@ -48853,13 +49216,34 @@ function checkApproval(strict) {
|
|
|
48853
49216
|
};
|
|
48854
49217
|
}
|
|
48855
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
|
+
}
|
|
48856
49240
|
function upgradedCheck(record2) {
|
|
48857
49241
|
const diff3 = diffAgainstApproval();
|
|
48858
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.`;
|
|
48859
49243
|
return {
|
|
48860
49244
|
state: "stale",
|
|
48861
49245
|
approved: false,
|
|
48862
|
-
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.`
|
|
48863
49247
|
};
|
|
48864
49248
|
}
|
|
48865
49249
|
var LockRefusedError = class extends WaironError {
|
|
@@ -48869,7 +49253,7 @@ var LockRefusedError = class extends WaironError {
|
|
|
48869
49253
|
}
|
|
48870
49254
|
};
|
|
48871
49255
|
function describeMemberState(entry) {
|
|
48872
|
-
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" : ""})`;
|
|
48873
49257
|
}
|
|
48874
49258
|
function codeLine(code) {
|
|
48875
49259
|
if (!code) return "code: no code analysis recorded";
|
|
@@ -48878,9 +49262,14 @@ function codeLine(code) {
|
|
|
48878
49262
|
function summarize3(gate, members) {
|
|
48879
49263
|
const diff3 = diffAgainstApproval();
|
|
48880
49264
|
if (!diff3) {
|
|
48881
|
-
|
|
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
|
+
}
|
|
48882
49271
|
} else if (diffSize(diff3) === 0) {
|
|
48883
|
-
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).");
|
|
48884
49273
|
} else {
|
|
48885
49274
|
logger.info(`${diffSize(diff3)} spec(s) changed since the last approval:`);
|
|
48886
49275
|
for (const p of diff3.changed) logger.info(` ~ ${p}`);
|
|
@@ -48894,7 +49283,7 @@ function summarize3(gate, members) {
|
|
|
48894
49283
|
continue;
|
|
48895
49284
|
}
|
|
48896
49285
|
const pin2 = m.pinned === "moved" ? " \u2014 its pin moved since the last approval" : "";
|
|
48897
|
-
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}`);
|
|
48898
49287
|
}
|
|
48899
49288
|
}
|
|
48900
49289
|
async function runLock(options, gate, captured) {
|
|
@@ -48908,7 +49297,7 @@ async function runLock(options, gate, captured) {
|
|
|
48908
49297
|
);
|
|
48909
49298
|
}
|
|
48910
49299
|
logger.blank();
|
|
48911
|
-
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.");
|
|
48912
49301
|
if (!options.yes) {
|
|
48913
49302
|
if (!process.stdin.isTTY) {
|
|
48914
49303
|
logger.error("Non-interactive shell \u2014 re-run with --yes to confirm the lock.");
|
|
@@ -48918,7 +49307,7 @@ async function runLock(options, gate, captured) {
|
|
|
48918
49307
|
{
|
|
48919
49308
|
type: "confirm",
|
|
48920
49309
|
name: "confirmed",
|
|
48921
|
-
message: "Approve this design
|
|
49310
|
+
message: "Approve this design?",
|
|
48922
49311
|
default: false
|
|
48923
49312
|
}
|
|
48924
49313
|
]);
|
|
@@ -48942,7 +49331,7 @@ async function runLock(options, gate, captured) {
|
|
|
48942
49331
|
const design = designOnly(gate);
|
|
48943
49332
|
const count = (severity) => design.issues.filter((i) => i.severity === severity).length;
|
|
48944
49333
|
const record2 = {
|
|
48945
|
-
// The record format: 2
|
|
49334
|
+
// The record format: 2 since wairon 6.0.0 (members, code, no children).
|
|
48946
49335
|
format: 2,
|
|
48947
49336
|
stateId: captured,
|
|
48948
49337
|
lockedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -49017,12 +49406,12 @@ function projectLabel(key, result) {
|
|
|
49017
49406
|
const root = result.projects?.find((p) => p.key === "");
|
|
49018
49407
|
return `${root?.id ?? "bound project"} (bound)`;
|
|
49019
49408
|
}
|
|
49020
|
-
function renderSpecFindings(result) {
|
|
49409
|
+
function renderSpecFindings(result, all = false) {
|
|
49021
49410
|
const tally = { errors: false, fatalWarnings: false, waived: 0, notices: 0 };
|
|
49022
49411
|
if (result.issues.length === 0) {
|
|
49023
49412
|
logger.success("Spec tree is valid and component type boundaries are enforced.");
|
|
49024
49413
|
}
|
|
49025
|
-
const MAX_PRINT = 100;
|
|
49414
|
+
const MAX_PRINT = all ? Number.POSITIVE_INFINITY : 100;
|
|
49026
49415
|
const printed = { error: 0, warning: 0, notice: 0 };
|
|
49027
49416
|
const skipped = { error: 0, warning: 0, notice: 0 };
|
|
49028
49417
|
for (const issue2 of result.issues) {
|
|
@@ -49042,10 +49431,11 @@ function renderSpecFindings(result) {
|
|
|
49042
49431
|
else if (issue2.severity === "notice") logger.notice(line2);
|
|
49043
49432
|
else logger.warn(line2);
|
|
49044
49433
|
}
|
|
49045
|
-
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.`;
|
|
49046
49435
|
if (skipped.error > 0) logger.error(more(skipped.error, "error"));
|
|
49047
49436
|
if (skipped.warning > 0) logger.warn(more(skipped.warning, "warning"));
|
|
49048
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);
|
|
49049
49439
|
if (result.projects) {
|
|
49050
49440
|
logger.blank();
|
|
49051
49441
|
logger.info("Per project (each its own gate):");
|
|
@@ -49057,6 +49447,20 @@ function renderSpecFindings(result) {
|
|
|
49057
49447
|
if (result.hint) logger.info(result.hint);
|
|
49058
49448
|
return tally;
|
|
49059
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
|
+
}
|
|
49060
49464
|
async function runValidate(options = {}) {
|
|
49061
49465
|
assertProjectInitialized();
|
|
49062
49466
|
const projectConfig = loadProjectConfig();
|
|
@@ -49128,7 +49532,7 @@ async function runValidate(options = {}) {
|
|
|
49128
49532
|
};
|
|
49129
49533
|
const family = selectsFamily(sddOptions, declaredMembers(projectConfig).length > 0);
|
|
49130
49534
|
const sddResult = family ? validateFamily2(sddOptions) : validateProject2(sddOptions);
|
|
49131
|
-
const tally = renderSpecFindings(sddResult);
|
|
49535
|
+
const tally = renderSpecFindings(sddResult, options.all === true);
|
|
49132
49536
|
hasErrors ||= tally.errors;
|
|
49133
49537
|
hasFatalWarnings ||= tally.fatalWarnings;
|
|
49134
49538
|
waivedWarnings += tally.waived;
|
|
@@ -49236,6 +49640,9 @@ async function runStatus(options = {}) {
|
|
|
49236
49640
|
logger.blank();
|
|
49237
49641
|
if (lock.drifted) logger.warn(lock.text.trim());
|
|
49238
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
|
+
));
|
|
49239
49646
|
}
|
|
49240
49647
|
logger.blank();
|
|
49241
49648
|
}
|
|
@@ -49370,14 +49777,14 @@ var http = __toESM(require("http"));
|
|
|
49370
49777
|
var https = __toESM(require("https"));
|
|
49371
49778
|
init_defaults();
|
|
49372
49779
|
function downloadFile(url, dest) {
|
|
49373
|
-
return new Promise((
|
|
49780
|
+
return new Promise((resolve56, reject) => {
|
|
49374
49781
|
const file = fs31.createWriteStream(dest);
|
|
49375
49782
|
const get4 = url.startsWith("https://") ? https.get : http.get;
|
|
49376
49783
|
get4(url, { headers: { "User-Agent": `wairon/${WAIRON_VERSION}` }, agent: false }, (res) => {
|
|
49377
49784
|
if (res.statusCode === 301 || res.statusCode === 302) {
|
|
49378
49785
|
file.close();
|
|
49379
49786
|
res.destroy();
|
|
49380
|
-
downloadFile(res.headers.location, dest).then(
|
|
49787
|
+
downloadFile(res.headers.location, dest).then(resolve56).catch(reject);
|
|
49381
49788
|
return;
|
|
49382
49789
|
}
|
|
49383
49790
|
if (res.statusCode !== 200) {
|
|
@@ -49389,7 +49796,7 @@ function downloadFile(url, dest) {
|
|
|
49389
49796
|
res.pipe(file);
|
|
49390
49797
|
file.on("finish", () => {
|
|
49391
49798
|
res.destroy();
|
|
49392
|
-
file.close(() =>
|
|
49799
|
+
file.close(() => resolve56());
|
|
49393
49800
|
});
|
|
49394
49801
|
file.on("error", (err) => {
|
|
49395
49802
|
res.destroy();
|
|
@@ -49883,7 +50290,12 @@ function writeBundle(root, resolved) {
|
|
|
49883
50290
|
const dest = path52.join(root, ".wai", "packs", resolved.name, resolved.version);
|
|
49884
50291
|
fs32.rmSync(dest, { recursive: true, force: true });
|
|
49885
50292
|
fs32.mkdirSync(path52.dirname(dest), { recursive: true });
|
|
49886
|
-
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
|
+
}
|
|
49887
50299
|
return dest;
|
|
49888
50300
|
}
|
|
49889
50301
|
function bundlePack(name, options = {}) {
|
|
@@ -50665,7 +51077,7 @@ async function runLock2(options) {
|
|
|
50665
51077
|
}
|
|
50666
51078
|
}
|
|
50667
51079
|
if (skippedErrors > 0) {
|
|
50668
|
-
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).`);
|
|
50669
51081
|
}
|
|
50670
51082
|
logger.blank();
|
|
50671
51083
|
logger.info("Fix the errors above, then run `wairon lock` again. Nothing was changed.");
|
|
@@ -50717,7 +51129,7 @@ async function validateCommand(opts) {
|
|
|
50717
51129
|
for (const t of recover3(getProjectRoot(), false)) {
|
|
50718
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`);
|
|
50719
51131
|
}
|
|
50720
|
-
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 });
|
|
50721
51133
|
}
|
|
50722
51134
|
async function statusCommand(opts) {
|
|
50723
51135
|
const target = resolveTarget(getProjectRoot(), {});
|
|
@@ -51164,6 +51576,10 @@ var SCREENFUL = 20;
|
|
|
51164
51576
|
function shownDir(dir) {
|
|
51165
51577
|
return nodePath3.relative(process.cwd(), dir) || ".";
|
|
51166
51578
|
}
|
|
51579
|
+
function relockLabel(dir) {
|
|
51580
|
+
const shown2 = shownDir(dir);
|
|
51581
|
+
return shown2 === "." ? "this project (.)" : shown2;
|
|
51582
|
+
}
|
|
51167
51583
|
function printPlan(planned) {
|
|
51168
51584
|
logger.header(`wairon family migration: ${planned.request.verb}`);
|
|
51169
51585
|
logger.info(`Family root: ${shownDir(planned.familyRoot)}${planned.whole ? "" : " (not the whole family: the reach stops below its top)"}`);
|
|
@@ -51174,7 +51590,7 @@ function printPlan(planned) {
|
|
|
51174
51590
|
for (const r of planned.refusals) logger.error(`[${r.code}]${r.project ? ` ${ownerLabel(r.project)}:` : ""} ${r.detail}`);
|
|
51175
51591
|
for (const note of planned.notes) logger.warn(`NOTE: ${note}`);
|
|
51176
51592
|
printFiles(planned);
|
|
51177
|
-
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(", ")}`);
|
|
51178
51594
|
}
|
|
51179
51595
|
function printFiles(planned) {
|
|
51180
51596
|
if (planned.changes.length === 0) return;
|
|
@@ -51203,7 +51619,7 @@ function printOutcome(report4) {
|
|
|
51203
51619
|
const verb = report4.plan.request.verb;
|
|
51204
51620
|
if (report4.applied) {
|
|
51205
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).`);
|
|
51206
|
-
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"}.`);
|
|
51207
51623
|
return;
|
|
51208
51624
|
}
|
|
51209
51625
|
process.exitCode = 1;
|
|
@@ -51270,9 +51686,14 @@ async function runInit(options = {}) {
|
|
|
51270
51686
|
const ancestorRoot = findSystemRoot(cwd);
|
|
51271
51687
|
if (ancestorRoot && path58.resolve(ancestorRoot) === path58.resolve(cwd)) {
|
|
51272
51688
|
logger.info("Project already initialized.");
|
|
51273
|
-
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.");
|
|
51274
51690
|
return;
|
|
51275
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
|
+
}
|
|
51276
51697
|
if (ancestorRoot) {
|
|
51277
51698
|
await runInitAsMember(ancestorRoot, cwd, options);
|
|
51278
51699
|
return;
|
|
@@ -51541,13 +51962,17 @@ async function executeInit(projectName, projectConfig, guidePlan) {
|
|
|
51541
51962
|
logger.warn(`Failed to automatically register MCP server for Claude: ${err}`);
|
|
51542
51963
|
}
|
|
51543
51964
|
}
|
|
51544
|
-
if (targetTypes.includes("gemini")
|
|
51965
|
+
if (targetTypes.includes("gemini")) {
|
|
51545
51966
|
try {
|
|
51546
51967
|
const { runMcpInstall: runMcpInstall2 } = (init_mcp(), __toCommonJS(mcp_exports));
|
|
51547
51968
|
runMcpInstall2({ backend: "gemini", global: false });
|
|
51548
51969
|
} catch (err) {
|
|
51549
|
-
logger.warn(`Failed to automatically register MCP server for
|
|
51970
|
+
logger.warn(`Failed to automatically register MCP server for the Gemini CLI: ${err}`);
|
|
51550
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
|
+
);
|
|
51551
51976
|
}
|
|
51552
51977
|
syncContextFiles();
|
|
51553
51978
|
logger.verbose("Context files seeded in .wai/context/");
|
|
@@ -51571,9 +51996,10 @@ async function executeInit(projectName, projectConfig, guidePlan) {
|
|
|
51571
51996
|
logger.blank();
|
|
51572
51997
|
logger.info("Next steps:");
|
|
51573
51998
|
logger.info(" Edit .wai/context/project.md \u2014 describe the project concept and stack details for the AI");
|
|
51574
|
-
logger.info(" wairon status \u2014 view the architecture completeness dashboard");
|
|
51575
|
-
logger.info(" wairon validate \u2014 check the spec tree & component boundaries");
|
|
51576
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");
|
|
51577
52003
|
logger.blank();
|
|
51578
52004
|
}
|
|
51579
52005
|
function buildProjectConfig(name, targets, now, projectType) {
|
|
@@ -51656,11 +52082,13 @@ Define the non-negotiable architectural guardrails here. The AI agent must follo
|
|
|
51656
52082
|
|
|
51657
52083
|
* [ ] **Primary Language & Runtime:** Node.js (TypeScript) / Python / etc.
|
|
51658
52084
|
* [ ] **Architectural Style:** Clean Architecture / Hexagonal / Domain-Driven Design (DDD).
|
|
51659
|
-
* [ ] **Data Persistence Rules:** E.g. No raw SQL in controllers;
|
|
51660
|
-
* [ ] **Stereotype Dependencies
|
|
51661
|
-
* \`
|
|
51662
|
-
* \`
|
|
51663
|
-
*
|
|
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\`.
|
|
51664
52092
|
|
|
51665
52093
|
---
|
|
51666
52094
|
|
|
@@ -51673,15 +52101,16 @@ Define the non-negotiable architectural guardrails here. The AI agent must follo
|
|
|
51673
52101
|
|
|
51674
52102
|
---
|
|
51675
52103
|
|
|
51676
|
-
## Stage 3: Ingress
|
|
52104
|
+
## Stage 3: Ingress Portals & Outbound Adapters (Level 2 & Level 3)
|
|
51677
52105
|
|
|
51678
|
-
Portals are
|
|
52106
|
+
Portals are where requests enter a subsystem; Adapters are where calls leave it.
|
|
51679
52107
|
|
|
51680
52108
|
* [ ] **Define Ingress Portals (REST / gRPC / MessageBus):**
|
|
51681
52109
|
* *AI Action:* Create L2 Portal components with \`status: draft\` and map their L3 interfaces.
|
|
51682
|
-
* *Design check:*
|
|
51683
|
-
*
|
|
51684
|
-
|
|
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.
|
|
51685
52114
|
|
|
51686
52115
|
---
|
|
51687
52116
|
|
|
@@ -51706,13 +52135,13 @@ Map the behavior step-by-step.
|
|
|
51706
52135
|
|
|
51707
52136
|
---
|
|
51708
52137
|
|
|
51709
|
-
## Stage 6:
|
|
52138
|
+
## Stage 6: Approval & Implementation
|
|
51710
52139
|
|
|
51711
|
-
Once the specs
|
|
52140
|
+
Once the specs validate cleanly, the human approves the design with \`wairon lock\`; implementation starts from the approved design.
|
|
51712
52141
|
|
|
51713
52142
|
* [ ] **Validation Check (AI):** Call the \`sdd_validate_tree\` MCP tool (must return 0 errors).
|
|
51714
|
-
* [ ] **
|
|
51715
|
-
* [ ] **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.
|
|
51716
52145
|
`;
|
|
51717
52146
|
writeFile(fromProjectRoot(".wai", "phased_design.md"), content);
|
|
51718
52147
|
}
|
|
@@ -52013,7 +52442,7 @@ function releaseChannelLabel(tag) {
|
|
|
52013
52442
|
return version.slice(dash + 1).replace(/\.\d+$/, "") || "stable";
|
|
52014
52443
|
}
|
|
52015
52444
|
function fetchReleases(repo) {
|
|
52016
|
-
return new Promise((
|
|
52445
|
+
return new Promise((resolve56, reject) => {
|
|
52017
52446
|
const url = `https://api.github.com/repos/${repo}/releases?per_page=100`;
|
|
52018
52447
|
const options = {
|
|
52019
52448
|
headers: {
|
|
@@ -52033,7 +52462,7 @@ function fetchReleases(repo) {
|
|
|
52033
52462
|
return;
|
|
52034
52463
|
}
|
|
52035
52464
|
try {
|
|
52036
|
-
|
|
52465
|
+
resolve56(JSON.parse(data));
|
|
52037
52466
|
} catch {
|
|
52038
52467
|
reject(new Error("Failed to parse GitHub API response"));
|
|
52039
52468
|
}
|
|
@@ -52205,7 +52634,7 @@ async function filteredCheckbox(config) {
|
|
|
52205
52634
|
32,
|
|
52206
52635
|
Math.max(...items.map((i) => i.label.length))
|
|
52207
52636
|
);
|
|
52208
|
-
return new Promise((
|
|
52637
|
+
return new Promise((resolve56) => {
|
|
52209
52638
|
const checked = /* @__PURE__ */ new Set();
|
|
52210
52639
|
let cursor = 0;
|
|
52211
52640
|
let filterIdx = 0;
|
|
@@ -52272,7 +52701,7 @@ async function filteredCheckbox(config) {
|
|
|
52272
52701
|
);
|
|
52273
52702
|
teardown();
|
|
52274
52703
|
const result = items.filter((_, idx) => checked.has(idx)).map((i) => i.value);
|
|
52275
|
-
|
|
52704
|
+
resolve56(result);
|
|
52276
52705
|
}
|
|
52277
52706
|
function abort() {
|
|
52278
52707
|
process.stdout.write("\n");
|
|
@@ -52736,7 +53165,7 @@ function mcpEntryHealth(settingsPath) {
|
|
|
52736
53165
|
if (!entry) return { mark: "warn", note: "not registered" };
|
|
52737
53166
|
if (entry.command === "node" && Array.isArray(entry.args) && typeof entry.args[0] === "string") {
|
|
52738
53167
|
const scriptPath = entry.args[0];
|
|
52739
|
-
if (!fs38.existsSync(scriptPath)) {
|
|
53168
|
+
if (!fs38.existsSync(path61.resolve(getProjectRoot(), scriptPath))) {
|
|
52740
53169
|
return { mark: "error", note: `registered but the server path is missing \u2014 ${scriptPath}` };
|
|
52741
53170
|
}
|
|
52742
53171
|
}
|
|
@@ -52802,15 +53231,26 @@ async function runDoctor(options = {}) {
|
|
|
52802
53231
|
try {
|
|
52803
53232
|
const cfg = loadProjectConfig();
|
|
52804
53233
|
if (!cfg) throw new ProjectNotInitializedError();
|
|
52805
|
-
const result =
|
|
52806
|
-
const
|
|
52807
|
-
const
|
|
52808
|
-
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;
|
|
52809
53237
|
const noted = notes > 0 ? `, ${notes} notice(s)` : "";
|
|
52810
53238
|
if (errs > 0) line(tally, "error", `Conformance: ${errs} error(s), ${warns} warning(s)${noted} \u2014 see \`wairon validate\` (you: \`sdd_validate_tree\`)`);
|
|
52811
53239
|
else if (warns > 0) line(tally, "warn", `Conformance: ${warns} warning(s)${noted} \u2014 see \`wairon validate\` (you: \`sdd_validate_tree\`)`);
|
|
52812
53240
|
else if (notes > 0) line(tally, "ok", `Conformance: 0 errors, 0 warnings, ${notes} notice(s) \u2014 see \`wairon validate\` (you: \`sdd_validate_tree\`)`);
|
|
52813
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
|
+
}
|
|
52814
53254
|
} catch (e) {
|
|
52815
53255
|
line(tally, "warn", `Could not run conformance check: ${e instanceof Error ? e.message : String(e)}`);
|
|
52816
53256
|
}
|
|
@@ -52880,6 +53320,11 @@ async function runDoctor(options = {}) {
|
|
|
52880
53320
|
line(tally, "warn", `${proposal.kind} ${proposal.specId} ${proposal.path}: "${proposal.written}" \u2192 ${proposal.stored} (proposed)`);
|
|
52881
53321
|
}
|
|
52882
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
|
+
}
|
|
52883
53328
|
if (authorNeeded.length > 0) {
|
|
52884
53329
|
console.log(` ${import_chalk17.default.gray("needs an author \u2014 no rewrite can settle these")}`);
|
|
52885
53330
|
for (const problem of authorNeeded) {
|
|
@@ -52890,6 +53335,11 @@ async function runDoctor(options = {}) {
|
|
|
52890
53335
|
} catch (e) {
|
|
52891
53336
|
line(tally, "warn", `Could not plan the type-spelling repair: ${e instanceof Error ? e.message : String(e)}`);
|
|
52892
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
|
+
}
|
|
52893
53343
|
try {
|
|
52894
53344
|
const planned = plan5({ verb: "chaining", rehearse: false });
|
|
52895
53345
|
const pending4 = planned.chaining;
|
|
@@ -52958,7 +53408,7 @@ async function runDoctor(options = {}) {
|
|
|
52958
53408
|
line(
|
|
52959
53409
|
tally,
|
|
52960
53410
|
"warn",
|
|
52961
|
-
`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\`).`
|
|
52962
53412
|
);
|
|
52963
53413
|
} else if (lock.state === "stale") {
|
|
52964
53414
|
console.log(import_chalk17.default.bold("Lock"));
|
|
@@ -53023,9 +53473,13 @@ async function runDoctor(options = {}) {
|
|
|
53023
53473
|
line(tally, h.mark, `Claude (project .mcp.json): ${h.note}${h.mark === "ok" ? "" : " \u2014 run `wairon mcp install --backend claude`"}`);
|
|
53024
53474
|
}
|
|
53025
53475
|
if (wantGemini) {
|
|
53026
|
-
const
|
|
53027
|
-
|
|
53028
|
-
|
|
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
|
+
}
|
|
53029
53483
|
const projPath = fromProjectRoot(".gemini", "settings.json");
|
|
53030
53484
|
if (fs38.existsSync(projPath)) {
|
|
53031
53485
|
const hp = mcpEntryHealth(projPath);
|
|
@@ -53061,14 +53515,7 @@ async function applyFixes(options, tally) {
|
|
|
53061
53515
|
} catch (e) {
|
|
53062
53516
|
console.log(` ${icon("error")} Regeneration failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
53063
53517
|
}
|
|
53064
|
-
|
|
53065
|
-
const pinned = pinInstalledPacksAsSelections();
|
|
53066
|
-
if (pinned.length > 0) {
|
|
53067
|
-
console.log(` ${icon("ok")} Recorded ${pinned.length} installed pack(s) as explicit selections: ${pinned.join(", ")}.`);
|
|
53068
|
-
}
|
|
53069
|
-
} catch (e) {
|
|
53070
|
-
console.log(` ${icon("error")} Could not record pack selections: ${e instanceof Error ? e.message : String(e)}`);
|
|
53071
|
-
}
|
|
53518
|
+
pinPackSelections("");
|
|
53072
53519
|
try {
|
|
53073
53520
|
const legacySpecs = findLegacySpecFiles();
|
|
53074
53521
|
if (legacySpecs.length > 0) {
|
|
@@ -53088,63 +53535,157 @@ async function applyFixes(options, tally) {
|
|
|
53088
53535
|
} catch (e) {
|
|
53089
53536
|
console.log(` ${icon("error")} Chained-subproject backfill failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
53090
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);
|
|
53091
53573
|
try {
|
|
53092
53574
|
const retirement = retireSpecialists(true);
|
|
53093
53575
|
if (retirement.retyped.length > 0) {
|
|
53094
|
-
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.`);
|
|
53095
53577
|
}
|
|
53096
53578
|
if (retirement.rebasedVariants.length > 0) {
|
|
53097
|
-
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(", ")}.`);
|
|
53098
53580
|
}
|
|
53099
53581
|
} catch (e) {
|
|
53100
|
-
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)}`);
|
|
53101
53583
|
}
|
|
53102
53584
|
try {
|
|
53103
53585
|
const repairs = repairForeignStepFields(true);
|
|
53104
53586
|
if (repairs.length > 0) {
|
|
53105
|
-
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):`);
|
|
53106
53588
|
for (const repair of repairs) {
|
|
53107
53589
|
console.log(` ${repair.implementation}.${repair.method} step ${repair.stepNumber} (${repair.stepType}): ${repair.fields.join(", ")}`);
|
|
53108
53590
|
}
|
|
53109
53591
|
}
|
|
53110
53592
|
} catch (e) {
|
|
53111
|
-
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)}`);
|
|
53112
53594
|
}
|
|
53113
53595
|
try {
|
|
53114
53596
|
const signatures = repairSignatures(true);
|
|
53115
53597
|
if (signatures.length > 0) {
|
|
53116
|
-
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:`);
|
|
53117
53599
|
for (const repair of signatures) {
|
|
53118
53600
|
console.log(` ${repair.kind} ${repair.specId}: ${describeSignatureRepair(repair)}`);
|
|
53119
53601
|
}
|
|
53120
53602
|
}
|
|
53121
53603
|
} catch (e) {
|
|
53122
|
-
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)}`);
|
|
53123
53605
|
}
|
|
53124
53606
|
try {
|
|
53125
53607
|
const spellings = repairTypeSpellings(true);
|
|
53126
53608
|
const rewritten = spellings.filter((repair) => repair.rewritten.length > 0);
|
|
53127
53609
|
if (rewritten.length > 0) {
|
|
53128
53610
|
const positions = rewritten.reduce((n, repair) => n + repair.rewritten.length, 0);
|
|
53129
|
-
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.`);
|
|
53130
53612
|
}
|
|
53131
53613
|
const proposals = spellings.flatMap((repair) => repair.proposals);
|
|
53132
53614
|
if (proposals.length > 0) {
|
|
53133
|
-
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:`);
|
|
53134
53616
|
for (const proposal of proposals) console.log(` ${proposal.kind} ${proposal.specId} ${proposal.path}: "${proposal.written}" \u2192 ${proposal.stored} (proposed)`);
|
|
53135
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
|
+
}
|
|
53136
53623
|
const authorNeeded = spellings.flatMap((repair) => repair.authorNeeded);
|
|
53137
53624
|
if (authorNeeded.length > 0) {
|
|
53138
|
-
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:`);
|
|
53139
53626
|
for (const problem of authorNeeded) {
|
|
53140
53627
|
console.log(` ${problem.kind} ${problem.specId} ${problem.path ?? ""}: "${problem.written}" \u2014 ${problem.replacement ? `write ${problem.replacement}` : problem.detail}`);
|
|
53141
53628
|
}
|
|
53142
53629
|
}
|
|
53143
53630
|
} catch (e) {
|
|
53144
|
-
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)}`);
|
|
53145
53632
|
}
|
|
53146
|
-
|
|
53147
|
-
|
|
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);
|
|
53148
53689
|
logger.blank();
|
|
53149
53690
|
}
|
|
53150
53691
|
async function repairMcpRegistration(options, targets) {
|
|
@@ -53239,11 +53780,11 @@ function reportLabel(key) {
|
|
|
53239
53780
|
return key === "" ? "(this project)" : key;
|
|
53240
53781
|
}
|
|
53241
53782
|
function printUpgradeReport(report4) {
|
|
53242
|
-
console.log(import_chalk17.default.bold("Composed validation: what
|
|
53783
|
+
console.log(import_chalk17.default.bold("Composed validation: what it changed since each lock"));
|
|
53243
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."));
|
|
53244
53785
|
for (const p of report4.projects) {
|
|
53245
53786
|
const locked = p.lockedTotals ? `locked by v${p.lockedBy}: ${p.lockedTotals}` : "never locked";
|
|
53246
|
-
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" : "";
|
|
53247
53788
|
console.log(` ${reportLabel(p.key)}: ${locked}; today (as-complete): ${p.currentTotals}${attribution}`);
|
|
53248
53789
|
}
|
|
53249
53790
|
const byCode = /* @__PURE__ */ new Map();
|
|
@@ -53252,12 +53793,12 @@ function printUpgradeReport(report4) {
|
|
|
53252
53793
|
console.log(` ${import_chalk17.default.bold(code)} (${entries.length})`);
|
|
53253
53794
|
for (const e of entries) {
|
|
53254
53795
|
const where = `${reportLabel(e.project)}${e.specId ? ` ${e.specId}` : ""}`;
|
|
53255
|
-
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}` : ""}`;
|
|
53256
53797
|
console.log(` ${e.severity} ${where}: ${reason}`);
|
|
53257
53798
|
}
|
|
53258
53799
|
}
|
|
53259
|
-
if (report4.entries.length === 0) console.log(" No finding is new for a reason
|
|
53260
|
-
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.`);
|
|
53261
53802
|
if (report4.unmatched.length > 0) {
|
|
53262
53803
|
console.log(` ${import_chalk17.default.bold("Positional matches no rule decides")} (${report4.unmatched.length}) \u2014 counted above; a person picks:`);
|
|
53263
53804
|
for (const m of report4.unmatched) {
|
|
@@ -53421,7 +53962,7 @@ function printProjectMigration(p) {
|
|
|
53421
53962
|
console.log(` will be removed from its stored L0 by the first write (the schema does not know them): ${p.droppedKeys.join("; ")}`);
|
|
53422
53963
|
}
|
|
53423
53964
|
for (const x of p.externals) {
|
|
53424
|
-
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";
|
|
53425
53966
|
console.log(` external: ${x.alias}: {} \u2014 ${why} ${projectLabel2(x.producer)}`);
|
|
53426
53967
|
}
|
|
53427
53968
|
for (const i of p.imports) {
|
|
@@ -53437,10 +53978,10 @@ function printProjectMigration(p) {
|
|
|
53437
53978
|
if (m.retired.length > 0) console.log(` retires with the mount: ${m.retired.join("; ")}`);
|
|
53438
53979
|
}
|
|
53439
53980
|
if (p.supersededPins.length > 0) {
|
|
53440
|
-
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`);
|
|
53441
53982
|
}
|
|
53442
53983
|
for (const alias of p.locations) {
|
|
53443
|
-
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`);
|
|
53444
53985
|
}
|
|
53445
53986
|
}
|
|
53446
53987
|
function describeEntry(e) {
|
|
@@ -53510,13 +54051,13 @@ function writeArtifact(dest, content) {
|
|
|
53510
54051
|
fs39.writeFileSync(dest, content, "utf-8");
|
|
53511
54052
|
}
|
|
53512
54053
|
function parseSequenceRef(ref) {
|
|
53513
|
-
const
|
|
53514
|
-
if (
|
|
54054
|
+
const sep19 = ref.includes(":") ? ref.lastIndexOf(":") : ref.lastIndexOf(".");
|
|
54055
|
+
if (sep19 <= 0 || sep19 === ref.length - 1) {
|
|
53515
54056
|
throw new WaironError(
|
|
53516
54057
|
`Invalid --sequence reference "${ref}". Use <componentId>:<methodName> (e.g. billing-portal:authorize).`
|
|
53517
54058
|
);
|
|
53518
54059
|
}
|
|
53519
|
-
return { component: ref.slice(0,
|
|
54060
|
+
return { component: ref.slice(0, sep19), method: ref.slice(sep19 + 1) };
|
|
53520
54061
|
}
|
|
53521
54062
|
function canvasRelations(options) {
|
|
53522
54063
|
if (options.health === false) return void 0;
|
|
@@ -53668,8 +54209,8 @@ function withoutRoot(record2) {
|
|
|
53668
54209
|
return rest;
|
|
53669
54210
|
}
|
|
53670
54211
|
function normalizeMemberPath(p) {
|
|
53671
|
-
const
|
|
53672
|
-
return
|
|
54212
|
+
const posix8 = path64.posix.normalize(p.replace(/\\/g, "/"));
|
|
54213
|
+
return posix8.replace(/^\.\/+/, "").replace(/\/+$/, "");
|
|
53673
54214
|
}
|
|
53674
54215
|
function projectRoot(dataDir, id) {
|
|
53675
54216
|
return path64.join(dataDir, "projects", id);
|
|
@@ -57374,12 +57915,13 @@ function mintKey(cfg, credential, project2, role) {
|
|
|
57374
57915
|
"instance-wide super-admin (*:*) keys cannot be minted \u2014 the built-in admin account (WAIRON_ADMIN_USER) is the only super-admin"
|
|
57375
57916
|
);
|
|
57376
57917
|
}
|
|
57918
|
+
const narrowed2 = assertMintableNarrowingEntry(cfg.dataDir, project2);
|
|
57377
57919
|
const token = "wk_" + crypto17.randomBytes(24).toString("hex");
|
|
57378
57920
|
const record2 = {
|
|
57379
57921
|
id: crypto17.randomBytes(6).toString("hex"),
|
|
57380
57922
|
keyHash: hashToken(token),
|
|
57381
57923
|
role,
|
|
57382
|
-
projects:
|
|
57924
|
+
projects: [narrowed2],
|
|
57383
57925
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
57384
57926
|
};
|
|
57385
57927
|
createCredential(cfg.dataDir, record2);
|
|
@@ -57472,7 +58014,7 @@ function refuseDesignErrors(gate) {
|
|
|
57472
58014
|
function refuseUnapprovedMembers(required, members) {
|
|
57473
58015
|
const unapproved = members.filter((m) => m.as !== "part" && m.state !== "approved");
|
|
57474
58016
|
if (!required || unapproved.length === 0) return;
|
|
57475
|
-
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" : ""})`);
|
|
57476
58018
|
throw new LockRefusedError2(
|
|
57477
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.`
|
|
57478
58020
|
);
|
|
@@ -61654,7 +62196,7 @@ async function awaitApproval(cfg, credential, requestId, timeoutSeconds) {
|
|
|
61654
62196
|
let current2 = req;
|
|
61655
62197
|
while (current2.status === "pending" && Date.now() < deadline) {
|
|
61656
62198
|
const remaining = deadline - Date.now();
|
|
61657
|
-
await new Promise((
|
|
62199
|
+
await new Promise((resolve56) => setTimeout(resolve56, Math.min(AWAIT_POLL_INTERVAL_MS, remaining)));
|
|
61658
62200
|
expirePendingApprovals(cfg.dataDir, (/* @__PURE__ */ new Date()).toISOString());
|
|
61659
62201
|
current2 = getApprovalRequestById(cfg.dataDir, requestId) ?? current2;
|
|
61660
62202
|
}
|
|
@@ -67253,8 +67795,9 @@ function stop() {
|
|
|
67253
67795
|
var DEV_UNIT_ID = "local";
|
|
67254
67796
|
function bootstrapInstance(config) {
|
|
67255
67797
|
ensureInstanceIdentity(config.dataDir);
|
|
67256
|
-
if (config.devMode
|
|
67257
|
-
|
|
67798
|
+
if (!config.devMode) return;
|
|
67799
|
+
const system = { userId: "system", kind: "service", issuer: "local" };
|
|
67800
|
+
if (!getOrganizationUnit(config.dataDir, DEV_UNIT_ID)) {
|
|
67258
67801
|
createUnit(config.dataDir, {
|
|
67259
67802
|
id: DEV_UNIT_ID,
|
|
67260
67803
|
name: "Local Development",
|
|
@@ -67266,6 +67809,18 @@ function bootstrapInstance(config) {
|
|
|
67266
67809
|
createdBy: system
|
|
67267
67810
|
});
|
|
67268
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
|
+
}
|
|
67269
67824
|
}
|
|
67270
67825
|
|
|
67271
67826
|
// src/server/identity-portal.ts
|
|
@@ -68602,11 +69157,11 @@ async function runServe(options = {}) {
|
|
|
68602
69157
|
logger.info(` data dir: ${import_chalk18.default.gray(cfg.dataDir)}`);
|
|
68603
69158
|
logger.blank();
|
|
68604
69159
|
logger.info("Press Ctrl+C to stop.");
|
|
68605
|
-
await new Promise((
|
|
69160
|
+
await new Promise((resolve56) => {
|
|
68606
69161
|
const shutdown = () => {
|
|
68607
69162
|
logger.info("Shutting down\u2026");
|
|
68608
69163
|
handle.close();
|
|
68609
|
-
|
|
69164
|
+
resolve56();
|
|
68610
69165
|
};
|
|
68611
69166
|
process.on("SIGINT", shutdown);
|
|
68612
69167
|
process.on("SIGTERM", shutdown);
|
|
@@ -68671,11 +69226,11 @@ async function runDev(options = {}) {
|
|
|
68671
69226
|
logger.blank();
|
|
68672
69227
|
logger.info("An agent edits specs; refresh the page to see the live graph. Press Ctrl+C to stop.");
|
|
68673
69228
|
if (options.open) openBrowser(url);
|
|
68674
|
-
await new Promise((
|
|
69229
|
+
await new Promise((resolve56) => {
|
|
68675
69230
|
const shutdown = () => {
|
|
68676
69231
|
logger.info("Shutting down\u2026");
|
|
68677
69232
|
handle.close();
|
|
68678
|
-
|
|
69233
|
+
resolve56();
|
|
68679
69234
|
};
|
|
68680
69235
|
process.on("SIGINT", shutdown);
|
|
68681
69236
|
process.on("SIGTERM", shutdown);
|
|
@@ -68781,7 +69336,9 @@ async function runHostUnit(action, options = {}) {
|
|
|
68781
69336
|
id: qualifiedId2,
|
|
68782
69337
|
name: options.name ?? options.slug,
|
|
68783
69338
|
slug: options.slug,
|
|
68784
|
-
|
|
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"),
|
|
68785
69342
|
...options.parent ? { parentId: options.parent } : {},
|
|
68786
69343
|
status: "active",
|
|
68787
69344
|
createdAt: "",
|
|
@@ -68870,6 +69427,21 @@ function resolveScopeOptions(options) {
|
|
|
68870
69427
|
if (options.unit) return { kind: "unit", id: options.unit };
|
|
68871
69428
|
return { kind: "instance" };
|
|
68872
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
|
+
}
|
|
68873
69445
|
async function runHostPermission(action, options = {}) {
|
|
68874
69446
|
const cfg = resolveHostConfig(options);
|
|
68875
69447
|
const cred = masterCredential();
|
|
@@ -68898,6 +69470,9 @@ async function runHostPermission(action, options = {}) {
|
|
|
68898
69470
|
logger.success(
|
|
68899
69471
|
`Assignment ${stored.id}: ${options.user} ${options.capability}=${value} @ ${scope.kind}${scope.id ? ` ${scope.id}` : ""}`
|
|
68900
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
|
+
}
|
|
68901
69476
|
break;
|
|
68902
69477
|
}
|
|
68903
69478
|
case "list": {
|
|
@@ -68951,7 +69526,16 @@ async function runHostKey(action, options = {}) {
|
|
|
68951
69526
|
console.log(` ${import_chalk18.default.bold(minted.token)}`);
|
|
68952
69527
|
logger.blank();
|
|
68953
69528
|
for (const line2 of minted.mapped) {
|
|
68954
|
-
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
|
+
);
|
|
68955
69539
|
}
|
|
68956
69540
|
break;
|
|
68957
69541
|
}
|
|
@@ -69355,14 +69939,23 @@ async function runExport(out) {
|
|
|
69355
69939
|
}
|
|
69356
69940
|
const approval = verdict.state;
|
|
69357
69941
|
const design = exportDesign3(out, approval);
|
|
69942
|
+
const members = memberNote(design);
|
|
69358
69943
|
if (out) {
|
|
69359
69944
|
logger.success(
|
|
69360
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}).`
|
|
69361
69946
|
);
|
|
69947
|
+
if (members) logger.info(members);
|
|
69362
69948
|
return;
|
|
69363
69949
|
}
|
|
69364
69950
|
process.stdout.write(`${JSON.stringify(design, null, 2)}
|
|
69365
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.`;
|
|
69366
69959
|
}
|
|
69367
69960
|
|
|
69368
69961
|
// src/commands/externals.ts
|
|
@@ -69585,16 +70178,16 @@ program.command("generate").description("Generate agent output files from the sp
|
|
|
69585
70178
|
dryRun: opts.dryRun
|
|
69586
70179
|
});
|
|
69587
70180
|
});
|
|
69588
|
-
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) => {
|
|
69589
70182
|
await lockCheckCommand(opts);
|
|
69590
70183
|
});
|
|
69591
|
-
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) => {
|
|
69592
70185
|
await lockCommand(opts);
|
|
69593
70186
|
});
|
|
69594
|
-
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) => {
|
|
69595
70188
|
await validateCommand(opts);
|
|
69596
70189
|
});
|
|
69597
|
-
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) => {
|
|
69598
70191
|
await statusCommand(opts);
|
|
69599
70192
|
});
|
|
69600
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) => {
|
|
@@ -69774,7 +70367,7 @@ hostCmd.command("project <action>").description("create | list | destroy a hoste
|
|
|
69774
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) => {
|
|
69775
70368
|
await runHostDemo({ id: opts.id, unit: opts.unit, force: opts.force, dataDir: opts.dataDir });
|
|
69776
70369
|
});
|
|
69777
|
-
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) => {
|
|
69778
70371
|
await runHostUnit(action, { slug: opts.slug, name: opts.name, kind: opts.kind, parent: opts.parent, dataDir: opts.dataDir });
|
|
69779
70372
|
});
|
|
69780
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) => {
|