@wairon/cli 5.1.1-dev.102 → 5.1.1-dev.103
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -11
- package/dist/cli/index.js +1226 -320
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +406 -301
- package/dist/index.js +913 -176
- package/dist/index.js.map +1 -1
- package/dist/templates/skills/sdd-architect.md +1 -1
- package/docs/architecture.md +83 -0
- package/docs/cli.md +771 -0
- package/docs/design-export.md +308 -0
- package/docs/extending-wairon.md +615 -0
- package/docs/requirements.md +65 -0
- package/docs/roadmap.md +133 -0
- package/docs/standards/INDEX.md +23 -0
- package/docs/standards/architecture.md +953 -0
- package/docs/standards/language-bindings.md +120 -0
- package/docs/templates.md +61 -0
- package/docs/vision.md +59 -0
- package/docs/wairon-feature-requests-hosted-mcp.md +74 -0
- package/package.json +4 -2
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.103";
|
|
69
69
|
GITHUB_REPO = "SYW-Apps/Waffle-AIron";
|
|
70
70
|
SUPPORTED_ALIASES = ["wai"];
|
|
71
71
|
SCAN_EXCLUDE_DIRS = /* @__PURE__ */ new Set([
|
|
@@ -219,6 +219,7 @@ __export(fs_exports, {
|
|
|
219
219
|
listFilesRecursive: () => listFilesRecursive,
|
|
220
220
|
pathExists: () => pathExists,
|
|
221
221
|
readFileOrNull: () => readFileOrNull,
|
|
222
|
+
resolveProjectBinding: () => resolveProjectBinding,
|
|
222
223
|
runWithHostedLookup: () => runWithHostedLookup,
|
|
223
224
|
runWithProjectBinding: () => runWithProjectBinding,
|
|
224
225
|
runWithProjectRoot: () => runWithProjectRoot,
|
|
@@ -441,22 +442,74 @@ function setProjectRoot(dir) {
|
|
|
441
442
|
function getProjectRootOverride() {
|
|
442
443
|
return projectRootOverride;
|
|
443
444
|
}
|
|
444
|
-
function
|
|
445
|
+
function holdsSystemSpec(dir) {
|
|
446
|
+
for (const base of [".wai", ".wairon"]) {
|
|
447
|
+
const specs = path2.join(dir, base, "specs");
|
|
448
|
+
if (fs.existsSync(path2.join(specs, ".index.yaml")) || fs.existsSync(path2.join(specs, "system.yaml"))) return true;
|
|
449
|
+
if (base === ".wai" && fs.existsSync(path2.join(dir, ".wai"))) break;
|
|
450
|
+
}
|
|
451
|
+
return false;
|
|
452
|
+
}
|
|
453
|
+
function findRepositoryRoot(startDir) {
|
|
445
454
|
let dir = path2.resolve(startDir);
|
|
446
455
|
while (true) {
|
|
447
|
-
|
|
448
|
-
const
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
456
|
+
if (fs.existsSync(path2.join(dir, ".git"))) return dir;
|
|
457
|
+
const parent = path2.dirname(dir);
|
|
458
|
+
if (parent === dir) return null;
|
|
459
|
+
dir = parent;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
function isAtOrBelow(outer, inner2) {
|
|
463
|
+
const rel2 = path2.relative(outer, inner2);
|
|
464
|
+
return rel2 === "" || !rel2.startsWith("..") && !path2.isAbsolute(rel2);
|
|
465
|
+
}
|
|
466
|
+
function declaredMemberDirs(projectRoot2) {
|
|
467
|
+
let parsed;
|
|
468
|
+
try {
|
|
469
|
+
const text3 = fs.readFileSync(path2.join(projectRoot2, ".wai", "project.yaml"), "utf-8");
|
|
470
|
+
parsed = yaml.load(text3);
|
|
471
|
+
} catch {
|
|
472
|
+
return [];
|
|
473
|
+
}
|
|
474
|
+
const members = parsed?.members;
|
|
475
|
+
if (!members || typeof members !== "object") return [];
|
|
476
|
+
const dirs = [];
|
|
477
|
+
for (const value of Object.values(members)) {
|
|
478
|
+
const declaration2 = value;
|
|
479
|
+
const source = typeof declaration2 === "string" ? declaration2 : typeof declaration2?.source === "string" ? declaration2.source : typeof declaration2?.path === "string" ? declaration2.path : void 0;
|
|
480
|
+
if (!source) continue;
|
|
481
|
+
const text3 = source.trim();
|
|
482
|
+
if (text3.startsWith("hosted:") || /^[a-z][a-z0-9+.-]*:\/\//i.test(text3) || /^[\w.-]+@[\w.-]+:/.test(text3) || path2.isAbsolute(text3)) continue;
|
|
483
|
+
dirs.push(path2.resolve(projectRoot2, text3));
|
|
484
|
+
}
|
|
485
|
+
return dirs;
|
|
486
|
+
}
|
|
487
|
+
function resolveProjectBinding(startDir) {
|
|
488
|
+
const start2 = path2.resolve(startDir);
|
|
489
|
+
const repositoryRoot2 = findRepositoryRoot(start2) ?? void 0;
|
|
490
|
+
const withRepo = (b) => repositoryRoot2 ? { ...b, repositoryRoot: repositoryRoot2 } : b;
|
|
491
|
+
let dir = start2;
|
|
492
|
+
while (true) {
|
|
493
|
+
if (holdsSystemSpec(dir)) return withRepo({ root: dir, via: dir === start2 ? "here" : "ancestor" });
|
|
494
|
+
if (repositoryRoot2 && dir === repositoryRoot2) break;
|
|
495
|
+
const parent = path2.dirname(dir);
|
|
496
|
+
if (parent === dir) return null;
|
|
497
|
+
dir = parent;
|
|
498
|
+
}
|
|
499
|
+
dir = path2.dirname(repositoryRoot2);
|
|
500
|
+
while (true) {
|
|
501
|
+
if (holdsSystemSpec(dir)) {
|
|
502
|
+
const declares2 = declaredMemberDirs(dir).some((member) => isAtOrBelow(repositoryRoot2, member) && isAtOrBelow(member, start2));
|
|
503
|
+
if (declares2) return withRepo({ root: dir, via: "declared-member" });
|
|
454
504
|
}
|
|
455
505
|
const parent = path2.dirname(dir);
|
|
456
506
|
if (parent === dir) return null;
|
|
457
507
|
dir = parent;
|
|
458
508
|
}
|
|
459
509
|
}
|
|
510
|
+
function findSystemRoot(startDir) {
|
|
511
|
+
return resolveProjectBinding(startDir)?.root ?? null;
|
|
512
|
+
}
|
|
460
513
|
function getProjectRoot() {
|
|
461
514
|
const scoped = requestRootStore.getStore()?.root;
|
|
462
515
|
if (scoped) return scoped;
|
|
@@ -487,13 +540,14 @@ function aiDirAt(root, ...segments) {
|
|
|
487
540
|
const base = !fs.existsSync(waiPath) && fs.existsSync(waironPath) ? waironPath : waiPath;
|
|
488
541
|
return path2.join(base, ...segments);
|
|
489
542
|
}
|
|
490
|
-
var fs, path2, import_node_async_hooks, EOL_SAMPLE_BYTES, projectRootOverride, sameRoot, requestRootStore;
|
|
543
|
+
var fs, path2, import_node_async_hooks, yaml, EOL_SAMPLE_BYTES, projectRootOverride, sameRoot, requestRootStore;
|
|
491
544
|
var init_fs = __esm({
|
|
492
545
|
"src/utils/fs.ts"() {
|
|
493
546
|
"use strict";
|
|
494
547
|
fs = __toESM(require("fs"));
|
|
495
548
|
path2 = __toESM(require("path"));
|
|
496
549
|
import_node_async_hooks = require("async_hooks");
|
|
550
|
+
yaml = __toESM(require("js-yaml"));
|
|
497
551
|
EOL_SAMPLE_BYTES = 64 * 1024;
|
|
498
552
|
projectRootOverride = null;
|
|
499
553
|
sameRoot = (a, b) => path2.resolve(a) === path2.resolve(b);
|
|
@@ -638,14 +692,14 @@ var init_git_source = __esm({
|
|
|
638
692
|
// src/utils/yaml.ts
|
|
639
693
|
function parseYaml(content, sourcePath) {
|
|
640
694
|
try {
|
|
641
|
-
return
|
|
695
|
+
return yaml2.load(content);
|
|
642
696
|
} catch (err) {
|
|
643
697
|
const loc = sourcePath ? ` (${sourcePath})` : "";
|
|
644
698
|
throw new Error(`Failed to parse YAML${loc}: ${String(err)}`);
|
|
645
699
|
}
|
|
646
700
|
}
|
|
647
701
|
function serializeYaml(value) {
|
|
648
|
-
return
|
|
702
|
+
return yaml2.dump(value, {
|
|
649
703
|
indent: 2,
|
|
650
704
|
lineWidth: 100,
|
|
651
705
|
noRefs: true,
|
|
@@ -660,11 +714,11 @@ function readYamlFile(filePath) {
|
|
|
660
714
|
function writeYamlFile(filePath, value) {
|
|
661
715
|
writeFile(filePath, serializeYaml(value));
|
|
662
716
|
}
|
|
663
|
-
var
|
|
717
|
+
var yaml2;
|
|
664
718
|
var init_yaml = __esm({
|
|
665
719
|
"src/utils/yaml.ts"() {
|
|
666
720
|
"use strict";
|
|
667
|
-
|
|
721
|
+
yaml2 = __toESM(require("js-yaml"));
|
|
668
722
|
init_fs();
|
|
669
723
|
}
|
|
670
724
|
});
|
|
@@ -897,6 +951,14 @@ var init_agent = __esm({
|
|
|
897
951
|
* agent implements nothing.
|
|
898
952
|
*/
|
|
899
953
|
typeMapping: import_zod2.z.array(import_zod2.z.string()).optional(),
|
|
954
|
+
/**
|
|
955
|
+
* Where an implementing agent may write CODE: each source file its
|
|
956
|
+
* implementations name, plus the folder they share below the project root as
|
|
957
|
+
* `<folder>/**`. Empty when nothing names a file yet (the instructions then
|
|
958
|
+
* ask the spawning session to name the planned files); absent for an agent
|
|
959
|
+
* that implements nothing.
|
|
960
|
+
*/
|
|
961
|
+
codeFence: import_zod2.z.array(import_zod2.z.string()).optional(),
|
|
900
962
|
/**
|
|
901
963
|
* Set only on a brief for a member's agent (an id qualified `<alias>::`): the
|
|
902
964
|
* member's directory relative to the root the brief was asked from. The
|
|
@@ -5442,7 +5504,7 @@ function extractTypesFromSignature(signature, returns) {
|
|
|
5442
5504
|
let sigCleaned = signature.replace(/\/\/.*$/gm, "").replace(/#.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
|
|
5443
5505
|
sigCleaned = sigCleaned.replace(/(?<=\S)\s*\([^)]*\)\s*$/, "");
|
|
5444
5506
|
sigCleaned = sigCleaned.replace(/(?<=\S)\s+[-—:]\s+[a-z\s_-]+$/, "");
|
|
5445
|
-
const returnsCleaned = returns.replace(/\/\/.*$/gm, "").replace(/#.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "").replace(/[a-zA-Z0-9_-]+\s*\??\s
|
|
5507
|
+
const returnsCleaned = returns.replace(/\/\/.*$/gm, "").replace(/#.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "").replace(/(?<![:\w-])[a-zA-Z0-9_-]+\s*\??\s*:(?!:)/g, "");
|
|
5446
5508
|
types.push(...extractTypeIdentifiers(returnsCleaned));
|
|
5447
5509
|
const openParen = sigCleaned.indexOf("(");
|
|
5448
5510
|
const closeParen = sigCleaned.lastIndexOf(")");
|
|
@@ -5473,7 +5535,7 @@ function extractTypesFromSignature(signature, returns) {
|
|
|
5473
5535
|
const colonIndex = param.indexOf(":");
|
|
5474
5536
|
if (colonIndex !== -1) {
|
|
5475
5537
|
const paramType = param.slice(colonIndex + 1).trim();
|
|
5476
|
-
const paramTypeCleaned = paramType.replace(/[a-zA-Z0-9_-]+\s*\??\s
|
|
5538
|
+
const paramTypeCleaned = paramType.replace(/(?<![:\w-])[a-zA-Z0-9_-]+\s*\??\s*:(?!:)/g, "");
|
|
5477
5539
|
types.push(...extractTypeIdentifiers(paramTypeCleaned));
|
|
5478
5540
|
}
|
|
5479
5541
|
}
|
|
@@ -5671,10 +5733,10 @@ function stepGraph(method2) {
|
|
|
5671
5733
|
const s = byNum.get(n);
|
|
5672
5734
|
if (s.type !== "parallel" || s.endStep === void 0 || !s.branches?.length) continue;
|
|
5673
5735
|
const entries = s.branches.map((b) => b.step).sort((a, b) => a - b);
|
|
5674
|
-
const
|
|
5736
|
+
const join66 = fallNext(s.endStep);
|
|
5675
5737
|
for (let i = 0; i < entries.length; i++) {
|
|
5676
5738
|
const armEnd = i + 1 < entries.length ? prevOf(entries[i + 1]) : s.endStep;
|
|
5677
|
-
if (armEnd !== void 0 && armEnd >= entries[i]) armEndJoin.set(armEnd,
|
|
5739
|
+
if (armEnd !== void 0 && armEnd >= entries[i]) armEndJoin.set(armEnd, join66);
|
|
5678
5740
|
}
|
|
5679
5741
|
}
|
|
5680
5742
|
const successorsOf = (n) => {
|
|
@@ -5937,6 +5999,12 @@ function fieldTypesOf(facts, field) {
|
|
|
5937
5999
|
function localTypesOf(facts, name) {
|
|
5938
6000
|
return ownEntry(facts.localTypes, name) ?? NO_DECLARED_TYPES;
|
|
5939
6001
|
}
|
|
6002
|
+
function memberTypesOf(facts, shape, property) {
|
|
6003
|
+
return ownEntry(facts.memberTypes, `${shape}.${property}`) ?? NO_DECLARED_TYPES;
|
|
6004
|
+
}
|
|
6005
|
+
function aliasTargetOf(facts, name) {
|
|
6006
|
+
return ownEntry(facts.aliasTargets, name);
|
|
6007
|
+
}
|
|
5940
6008
|
function typeBindingOf(facts, name) {
|
|
5941
6009
|
return ownEntry(facts.typeOnlyBindings, name) ?? ownEntry(facts.importBindings, name)?.from;
|
|
5942
6010
|
}
|
|
@@ -12268,7 +12336,7 @@ function toOpenApiSet(snapshot) {
|
|
|
12268
12336
|
}
|
|
12269
12337
|
function isOpenApiDocument(body) {
|
|
12270
12338
|
try {
|
|
12271
|
-
const parsed =
|
|
12339
|
+
const parsed = yaml3.load(body);
|
|
12272
12340
|
return !!parsed && typeof parsed === "object" && typeof parsed.openapi === "string";
|
|
12273
12341
|
} catch {
|
|
12274
12342
|
return false;
|
|
@@ -12378,7 +12446,7 @@ function authFromSecurityScheme(scheme) {
|
|
|
12378
12446
|
function fromOpenApi(document, projectName) {
|
|
12379
12447
|
let parsed;
|
|
12380
12448
|
try {
|
|
12381
|
-
parsed =
|
|
12449
|
+
parsed = yaml3.load(document);
|
|
12382
12450
|
} catch (e) {
|
|
12383
12451
|
throw new Error(`Invalid surface document: not parseable as JSON/YAML (${e instanceof Error ? e.message : String(e)})`);
|
|
12384
12452
|
}
|
|
@@ -12494,11 +12562,11 @@ function fromOpenApi(document, projectName) {
|
|
|
12494
12562
|
types
|
|
12495
12563
|
});
|
|
12496
12564
|
}
|
|
12497
|
-
var
|
|
12565
|
+
var yaml3, PRIMITIVE_SCHEMAS, SIGNATURE_EXTENSION, SCALAR_SCHEMA_TYPES;
|
|
12498
12566
|
var init_openapi = __esm({
|
|
12499
12567
|
"src/core/openapi.ts"() {
|
|
12500
12568
|
"use strict";
|
|
12501
|
-
|
|
12569
|
+
yaml3 = __toESM(require("js-yaml"));
|
|
12502
12570
|
init_models();
|
|
12503
12571
|
PRIMITIVE_SCHEMAS = {
|
|
12504
12572
|
string: { type: "string" },
|
|
@@ -13169,7 +13237,9 @@ function writeSurfaceFile(targetPath, snapshot, renderedSet) {
|
|
|
13169
13237
|
const resolved = path14.resolve(targetPath);
|
|
13170
13238
|
fs9.mkdirSync(path14.dirname(resolved), { recursive: true });
|
|
13171
13239
|
if (!renderedSet || renderedSet.length === 0) {
|
|
13172
|
-
|
|
13240
|
+
if (path14.extname(resolved).toLowerCase() === ".json") fs9.writeFileSync(resolved, `${JSON.stringify(snapshot, null, 2)}
|
|
13241
|
+
`);
|
|
13242
|
+
else writeYamlFile(resolved, snapshot);
|
|
13173
13243
|
return [resolved];
|
|
13174
13244
|
}
|
|
13175
13245
|
if (renderedSet.length === 1) {
|
|
@@ -15249,17 +15319,47 @@ function buildCodeIndex(model) {
|
|
|
15249
15319
|
};
|
|
15250
15320
|
const possibleOriginsOf = (site, from) => {
|
|
15251
15321
|
const proven = originOf2(site, from);
|
|
15252
|
-
if (!site.field && !site.constructed && !site.via && !site.returnedBy && !site.enclosingClass)
|
|
15322
|
+
if (!site.field && !site.constructed && !site.via && !site.returnedBy && !site.enclosingClass && !site.receiverPath) {
|
|
15323
|
+
return proven;
|
|
15324
|
+
}
|
|
15253
15325
|
const resolved = scopeOf3(site, from);
|
|
15254
15326
|
if (!resolved) return proven;
|
|
15255
15327
|
const { path: scope, facts: f } = resolved;
|
|
15256
15328
|
const out = new Set(proven);
|
|
15257
|
-
const
|
|
15258
|
-
const landings = specifier !== void 0 ? landingFrom(
|
|
15329
|
+
const widenThroughAt = (at, name, specifier) => {
|
|
15330
|
+
const landings = specifier !== void 0 ? landingFrom(at, specifier) : declarationsAt(at).has(name) ? republishedFrom(at) : NO_ORIGIN;
|
|
15259
15331
|
for (const candidate of landings) out.add(candidate);
|
|
15260
15332
|
};
|
|
15261
|
-
const
|
|
15333
|
+
const widenThrough = (name, specifier) => widenThroughAt(scope, name, specifier);
|
|
15334
|
+
const widenThroughType = (typeName, at = scope, atFacts = f) => {
|
|
15335
|
+
widenThroughAt(at, typeName, typeBindingOf(atFacts, typeName));
|
|
15336
|
+
for (const decl of typeDeclarationsOf(at, atFacts, typeName)) {
|
|
15337
|
+
const target = aliasTargetOf(decl.facts, decl.name);
|
|
15338
|
+
if (target !== void 0) widenThroughAt(decl.path, target, typeBindingOf(decl.facts, target));
|
|
15339
|
+
}
|
|
15340
|
+
};
|
|
15262
15341
|
if (site.field) for (const typeName of fieldTypesOf(f, site.field)) widenThroughType(typeName);
|
|
15342
|
+
if (site.receiverPath && site.receiverPath.length >= 2) {
|
|
15343
|
+
const [root, ...rest] = site.receiverPath;
|
|
15344
|
+
const links = [...rest];
|
|
15345
|
+
let current2 = [];
|
|
15346
|
+
if (root === "this") {
|
|
15347
|
+
const field = links.shift();
|
|
15348
|
+
if (field !== void 0) current2 = fieldTypesOf(f, field).map((type) => ({ at: scope, facts: f, type }));
|
|
15349
|
+
} else {
|
|
15350
|
+
current2 = localTypesOf(f, root).map((type) => ({ at: scope, facts: f, type }));
|
|
15351
|
+
}
|
|
15352
|
+
for (const link of links) {
|
|
15353
|
+
const next = [];
|
|
15354
|
+
for (const c of current2) {
|
|
15355
|
+
for (const decl of typeDeclarationsOf(c.at, c.facts, c.type, true)) {
|
|
15356
|
+
for (const type of memberTypesOf(decl.facts, decl.name, link)) next.push({ at: decl.path, facts: decl.facts, type });
|
|
15357
|
+
}
|
|
15358
|
+
}
|
|
15359
|
+
current2 = next;
|
|
15360
|
+
}
|
|
15361
|
+
for (const c of current2) widenThroughType(c.type, c.at, c.facts);
|
|
15362
|
+
}
|
|
15263
15363
|
if (site.constructed) widenThrough(site.constructed, importBindingOf(f, site.constructed)?.from);
|
|
15264
15364
|
if (site.via) for (const typeName of localTypesOf(f, site.via)) widenThroughType(typeName);
|
|
15265
15365
|
if (site.enclosingClass !== void 0 && holdsMemberBody(f, site.name, site.enclosingClass)) {
|
|
@@ -15281,6 +15381,34 @@ function buildCodeIndex(model) {
|
|
|
15281
15381
|
}
|
|
15282
15382
|
return out;
|
|
15283
15383
|
};
|
|
15384
|
+
const typeDeclarationsOf = (at, atFacts, typeName, throughAlias2 = false) => {
|
|
15385
|
+
const found = [];
|
|
15386
|
+
const exactFacts = (p) => {
|
|
15387
|
+
const x = facts.get(p);
|
|
15388
|
+
return x?.status === "analyzed" && x.analysisGrade === "exact" ? x : void 0;
|
|
15389
|
+
};
|
|
15390
|
+
const direct = (where, whereFacts, name) => {
|
|
15391
|
+
if (whereFacts.declaredNames.includes(name) && typeBindingOf(whereFacts, name) === void 0) {
|
|
15392
|
+
return [{ path: where, facts: whereFacts, name }];
|
|
15393
|
+
}
|
|
15394
|
+
const specifier = typeBindingOf(whereFacts, name);
|
|
15395
|
+
if (specifier === void 0) return [];
|
|
15396
|
+
const published = importBindingOf(whereFacts, name)?.imported ?? name;
|
|
15397
|
+
const out = [];
|
|
15398
|
+
for (const candidate of landingFrom(where, specifier)) {
|
|
15399
|
+
const there = exactFacts(candidate);
|
|
15400
|
+
if (there && there.declaredNames.includes(published)) out.push({ path: candidate, facts: there, name: published });
|
|
15401
|
+
}
|
|
15402
|
+
return out;
|
|
15403
|
+
};
|
|
15404
|
+
for (const decl of direct(at, atFacts, typeName)) {
|
|
15405
|
+
found.push(decl);
|
|
15406
|
+
if (!throughAlias2) continue;
|
|
15407
|
+
const target = aliasTargetOf(decl.facts, decl.name);
|
|
15408
|
+
if (target !== void 0) found.push(...direct(decl.path, decl.facts, target));
|
|
15409
|
+
}
|
|
15410
|
+
return found;
|
|
15411
|
+
};
|
|
15284
15412
|
const holdsMemberBody = (f, name, container) => f.status === "analyzed" && f.analysisGrade === "exact" && !!f.functionBodies && Object.prototype.hasOwnProperty.call(f.functionBodies, name) && f.functionBodies[name].some((b) => b.container === container);
|
|
15285
15413
|
const declarationsAt = (path93) => namesAt(declarations, pathKey(path93), (f) => /* @__PURE__ */ new Set([...f.declaredNames, ...f.exportedNames]));
|
|
15286
15414
|
const forwardsOf = (start2, name, container) => {
|
|
@@ -15841,10 +15969,11 @@ var init_field_type_references = __esm({
|
|
|
15841
15969
|
for (const ref of refs) {
|
|
15842
15970
|
if (!ctx.isTypeResolved(ref, /* @__PURE__ */ new Set())) {
|
|
15843
15971
|
const hint = ctx.importHint(ref);
|
|
15972
|
+
const spelled = ctx.aliasSpelling(ref);
|
|
15844
15973
|
ctx.addIssue(
|
|
15845
15974
|
"error",
|
|
15846
15975
|
"UNDEFINED_TYPE_REFERENCE",
|
|
15847
|
-
`Type "${t.id}" field "${field.name}" references undefined type "${ref}" in "${field.type}".${hint ? ` A declared dependency exports it without this project importing it \u2014 add \`${hint}\`.` : ""}`,
|
|
15976
|
+
`Type "${t.id}" field "${field.name}" references undefined type "${ref}" in "${field.type}".${hint ? ` A declared dependency exports it without this project importing it \u2014 add \`${hint}\`.` : ""}${spelled ? ` "${ref.split(".")[0]}" is another project here, not a subsystem of this one \u2014 write \`${spelled}\`.` : ""}`,
|
|
15848
15977
|
t.id,
|
|
15849
15978
|
isDraftCtx
|
|
15850
15979
|
);
|
|
@@ -15886,10 +16015,11 @@ var init_signature_type_references = __esm({
|
|
|
15886
16015
|
for (const ref of refs) {
|
|
15887
16016
|
if (!ctx.isTypeResolved(ref, allGenerics)) {
|
|
15888
16017
|
const hint = ctx.importHint(ref);
|
|
16018
|
+
const spelled = ctx.aliasSpelling(ref);
|
|
15889
16019
|
ctx.addIssue(
|
|
15890
16020
|
"error",
|
|
15891
16021
|
"UNDEFINED_TYPE_REFERENCE",
|
|
15892
|
-
`Method "${m.name}" on interface "${intf.id}" references undefined type "${ref}" in signature.${hint ? ` A declared dependency exports it without this project importing it \u2014 add \`${hint}\`.` : ""}`,
|
|
16022
|
+
`Method "${m.name}" on interface "${intf.id}" references undefined type "${ref}" in signature.${hint ? ` A declared dependency exports it without this project importing it \u2014 add \`${hint}\`.` : ""}${spelled ? ` "${ref.split(".")[0]}" is another project here, not a subsystem of this one \u2014 write \`${spelled}\`.` : ""}`,
|
|
15893
16023
|
intf.id,
|
|
15894
16024
|
isDraftCtx
|
|
15895
16025
|
);
|
|
@@ -15908,10 +16038,11 @@ var init_signature_type_references = __esm({
|
|
|
15908
16038
|
for (const { ref, by } of named2) {
|
|
15909
16039
|
if (ctx.isTypeResolved(ref, typeGenerics)) continue;
|
|
15910
16040
|
const hint = ctx.importHint(ref);
|
|
16041
|
+
const spelled = ctx.aliasSpelling(ref);
|
|
15911
16042
|
ctx.addIssue(
|
|
15912
16043
|
"error",
|
|
15913
16044
|
"UNDEFINED_TYPE_REFERENCE",
|
|
15914
|
-
`${by} references undefined type "${ref}" in signature.${hint ? ` A declared dependency exports it without this project importing it \u2014 add \`${hint}\`.` : ""}`,
|
|
16045
|
+
`${by} references undefined type "${ref}" in signature.${hint ? ` A declared dependency exports it without this project importing it \u2014 add \`${hint}\`.` : ""}${spelled ? ` "${ref.split(".")[0]}" is another project here, not a subsystem of this one \u2014 write \`${spelled}\`.` : ""}`,
|
|
15915
16046
|
t.id,
|
|
15916
16047
|
isDraftCtx
|
|
15917
16048
|
);
|
|
@@ -21208,16 +21339,36 @@ function closedCallSites(code, file, fn, stopAt, container) {
|
|
|
21208
21339
|
function resolveCallTarget(ctx, componentId, methodName) {
|
|
21209
21340
|
const accepted = /* @__PURE__ */ new Set([methodName]);
|
|
21210
21341
|
const files = /* @__PURE__ */ new Set();
|
|
21342
|
+
const bodies = /* @__PURE__ */ new Set();
|
|
21343
|
+
const anchors = [];
|
|
21211
21344
|
for (const intf of ctx.interfacesByComponent.get(componentId) ?? []) {
|
|
21212
21345
|
for (const impl of ctx.implementationsByContract.get(intf.id) ?? []) {
|
|
21213
21346
|
const method2 = impl.methods.find((m) => m.name === methodName);
|
|
21214
21347
|
if (!method2 && !intf.methods.some((m) => m.name === methodName)) continue;
|
|
21215
21348
|
if (method2?.symbol) accepted.add(method2.symbol);
|
|
21216
21349
|
const file = methodSourceFile(method2 ?? {}, impl.sourcePath);
|
|
21217
|
-
if (file)
|
|
21350
|
+
if (!file) continue;
|
|
21351
|
+
files.add(pathKey(file));
|
|
21352
|
+
bodies.add(bodyKey(pathKey(file), method2?.symbol ?? methodName, method2?.exportedVia));
|
|
21353
|
+
anchors.push({ file: pathKey(file), symbol: method2?.symbol ?? methodName, exportedVia: method2?.exportedVia });
|
|
21218
21354
|
}
|
|
21219
21355
|
}
|
|
21220
|
-
return { accepted, files };
|
|
21356
|
+
return { accepted, files, bodies, anchors };
|
|
21357
|
+
}
|
|
21358
|
+
function targetForwards(code, target) {
|
|
21359
|
+
const out = /* @__PURE__ */ new Set();
|
|
21360
|
+
for (const anchor of target.anchors) {
|
|
21361
|
+
for (const p of code.forwardsOf(anchor.file, anchor.symbol, anchor.exportedVia)) out.add(`${p.file}#${p.name}`);
|
|
21362
|
+
if (anchor.exportedVia !== void 0) continue;
|
|
21363
|
+
const facts = code.factsAt(anchor.file);
|
|
21364
|
+
if (!facts || facts.status !== "analyzed" || facts.analysisGrade !== "exact") continue;
|
|
21365
|
+
const binding = importBindingOf(facts, anchor.symbol);
|
|
21366
|
+
if (!binding || binding.namespace) continue;
|
|
21367
|
+
const module2 = resolveImport(anchor.file, binding.from, code.paths, code.packages);
|
|
21368
|
+
if (!module2) continue;
|
|
21369
|
+
for (const p of code.forwardsOf(module2, binding.imported ?? anchor.symbol)) out.add(`${p.file}#${p.name}`);
|
|
21370
|
+
}
|
|
21371
|
+
return out;
|
|
21221
21372
|
}
|
|
21222
21373
|
function isTarget(pairs, target) {
|
|
21223
21374
|
return pairs.some((p) => target.files.has(p.file) && target.accepted.has(p.name));
|
|
@@ -21256,6 +21407,7 @@ function describeSite(site) {
|
|
|
21256
21407
|
if (site.field) return `this.${site.field}.${site.name}(\u2026)`;
|
|
21257
21408
|
if (site.constructed) return `new ${site.constructed}(\u2026).${site.name}(\u2026)`;
|
|
21258
21409
|
if (site.returnedBy) return `${site.returnedBy}(\u2026).${site.name}(\u2026)`;
|
|
21410
|
+
if (site.receiverPath) return `${site.receiverPath.join(".")}.${site.name}(\u2026)`;
|
|
21259
21411
|
if (site.enclosingClass) return `this.${site.name}(\u2026)`;
|
|
21260
21412
|
return `<receiver>.${site.name}(\u2026)`;
|
|
21261
21413
|
}
|
|
@@ -21266,21 +21418,22 @@ function describeMiss(m) {
|
|
|
21266
21418
|
if (m.miss.kind === "unresolved") return `${head2}, written as ${m.miss.shapes.map((s) => `\`${s}\``).join(" / ")})`;
|
|
21267
21419
|
return `${head2})`;
|
|
21268
21420
|
}
|
|
21269
|
-
var anchorKey, unitOf, claimNoun, callConformanceRule;
|
|
21421
|
+
var anchorKey, bodyKey, unitOf, claimNoun, callConformanceRule;
|
|
21270
21422
|
var init_call_conformance = __esm({
|
|
21271
21423
|
"src/core/rules/conformance/call-conformance.ts"() {
|
|
21272
21424
|
"use strict";
|
|
21273
21425
|
init_models();
|
|
21274
21426
|
anchorKey = (symbol, exportedVia) => exportedVia !== void 0 ? `${exportedVia}.${symbol}` : symbol;
|
|
21427
|
+
bodyKey = (file, symbol, exportedVia) => `${file}|${anchorKey(symbol, exportedVia)}`;
|
|
21275
21428
|
unitOf = (m) => m.step === void 0 ? m.target : `${m.step}:${m.target}`;
|
|
21276
21429
|
claimNoun = (implMethod) => implMethod.narrative.length ? "narrative call step(s)" : "declared call(s)";
|
|
21277
21430
|
callConformanceRule = {
|
|
21278
21431
|
name: "call-conformance",
|
|
21279
21432
|
judges: "code",
|
|
21280
|
-
description: "Code\u2194spec Level 3: the CLAIMED call \u2194 realized call relation, judged both ways against the method's own source file (its sourcePath, else the implementation's), at exact analysis grade only. A method claims a call two ways and they face one check: a narrative `call` step, and an entry of the declared `calls` a narrative-less method reaches its collaborators through. They assert the same thing, and the step number is no part of the claim \u2014 this check never reads order, so it is only how a finding points at one. Forward: every claim must be realized by a call whose callee RESOLVES TO one of the target method's own source files \u2014 the target's contract name or a per-method `symbol` override, closed transitively over the named helpers the realized function calls, and resolved against every file the call CAN have reached: a `this.<field>.<method>()` receiver followed through the field's DECLARED TYPE, a `new Class(...).<method>()` receiver through the module its CLASS NAME came from, a plain `<name>.<method>()` receiver through the type the file ANNOTATES that name with \u2014 a parameter's, or an annotated variable's, which is how a module that wires its collaborators as closures writes every one of its calls \u2014 a `fn(...).<method>()` receiver through the type fn RETURNS where the code settles it, and a `this.<method>()` receiver through the class whose own method it sits in; a package specifier naming a package of this repository resolves like a relative one, and an export alias (`export { a as b }`, an adapter object property `{ b: a }`) forwards by identity like a re-export. A matching call whose origin a pure model cannot resolve is reported apart as CALL_ORIGIN_UNRESOLVED, which NAMES the shape it could not follow and asks for nothing \u2014 a coverage hole in the reader, reported for the reason CONFORMANCE_DEGRADED is: a silently degraded gate is worse than a degraded one. A finding names a landing only from the PROVEN tier so that widening what a call reached can accept a claim but never accuse one, and a target that names no file of its own falls back to name membership. Converse: a call that resolves to a modelled method of ANOTHER component in the SAME file crosses a component boundary while looking local, so the method must claim it \u2014 in a step or in `calls` (UNDECLARED_COLOCATED_CALL) \u2014 judged on the proven tier alone, and a same-file private helper is no modelled method and is never reported; a function forwarding by identity to a modelled method written in another file IS that method, so what it calls is that method's subject. A method that claims nothing at all is judged in neither direction: what it leaves unsaid is UNUSED_*'s subject. Order, arguments and conditions stay unverified, dispatch steps (runtime-table routed) are no claim about a call site, a malformed declared reference is MALFORMED_DECLARED_CALL's finding and names no target here, the conformance dial (off) skips, and weaker analysis grades never guess.",
|
|
21433
|
+
description: "Code\u2194spec Level 3: the CLAIMED call \u2194 realized call relation, judged both ways against the method's own source file (its sourcePath, else the implementation's), at exact analysis grade only. A method claims a call two ways and they face one check: a narrative `call` step, and an entry of the declared `calls` a narrative-less method reaches its collaborators through. They assert the same thing, and the step number is no part of the claim \u2014 this check never reads order, so it is only how a finding points at one. Forward: every claim must be realized by a call whose callee RESOLVES TO one of the target method's own source files \u2014 the target's contract name or a per-method `symbol` override, closed transitively over the named helpers the realized function calls, and resolved against every file the call CAN have reached: a `this.<field>.<method>()` receiver followed through the field's DECLARED TYPE, a `new Class(...).<method>()` receiver through the module its CLASS NAME came from, a plain `<name>.<method>()` receiver through the type the file ANNOTATES that name with \u2014 a parameter's, or an annotated variable's, which is how a module that wires its collaborators as closures writes every one of its calls \u2014 a `fn(...).<method>()` receiver through the type fn RETURNS where the code settles it, and a `this.<method>()` receiver through the class whose own method it sits in, and a receiver CHAIN (`this.<field>.<property>.<method>()`, `<name>.<property>.<method>()`) link by link through each link's declared type \u2014 a declared type read through a member-preserving utility (Pick, Omit, Partial, Required, Readonly) and one alias hop; a package specifier naming a package of this repository resolves like a relative one, and an export alias (`export { a as b }`, an adapter object property `{ b: a }`) forwards by identity like a re-export. A matching call whose origin a pure model cannot resolve is reported apart as CALL_ORIGIN_UNRESOLVED, which NAMES the shape it could not follow and asks for nothing \u2014 a coverage hole in the reader, reported for the reason CONFORMANCE_DEGRADED is: a silently degraded gate is worse than a degraded one. A finding names a landing only from the PROVEN tier so that widening what a call reached can accept a claim but never accuse one, and a target that names no file of its own falls back to name membership. N:1 identity \u2014 a caller that IS the target \u2014 is judged on the body, the same anchor in the same file, never on a shared name: a Portal method forwarding to a same-named Orchestrator method elsewhere must make that call. Converse: a call that resolves to a modelled method of ANOTHER component in the SAME file crosses a component boundary while looking local, so the method must claim it \u2014 in a step or in `calls` (UNDECLARED_COLOCATED_CALL) \u2014 judged on the proven tier alone, and a same-file private helper is no modelled method and is never reported; a function forwarding by identity to a modelled method written in another file IS that method, so what it calls is that method's subject. A method that claims nothing at all is judged in neither direction: what it leaves unsaid is UNUSED_*'s subject. Order, arguments and conditions stay unverified, dispatch steps (runtime-table routed) are no claim about a call site, a malformed declared reference is MALFORMED_DECLARED_CALL's finding and names no target here, the conformance dial (off) skips, and weaker analysis grades never guess.",
|
|
21281
21434
|
codes: [
|
|
21282
21435
|
{ code: "CALL_STEP_UNREALIZED", defaultSeverity: "warning", summary: "Narrative call step or declared call realized by no call that resolves to the target method's own source file \u2014 the call is absent, or it lands in another module", carryable: true },
|
|
21283
|
-
{ code: "CALL_ORIGIN_UNRESOLVED", defaultSeverity: "warning", summary: "Narrative call step or declared call whose target name IS called, but only from call sites written in a shape this analysis cannot resolve to a file \u2014 the claim was not checked, and is neither proven realized nor accused", carryable: true },
|
|
21436
|
+
{ code: "CALL_ORIGIN_UNRESOLVED", defaultSeverity: "warning", summary: "Narrative call step or declared call whose target name IS called, but only from call sites written in a shape this analysis cannot resolve to a file \u2014 the claim was not checked, and is neither proven realized nor accused. What resolves is a name bound to a module of THIS project: a bare or namespaced call through such an import binding, a this.<field> receiver whose declared type names one, a receiver chain (this.<field>.<property>, <name>.<property>) each of whose links the code declares a type for, a new Class(\u2026) receiver whose class does, a receiver name the file annotates with such a type, a fn(\u2026) receiver whose return the code settles, and this inside a class's own method \u2014 a declared type read through Pick/Omit/Partial/Required/Readonly and one alias hop. What does not: a third-party package import, a value the module assembled, and a receiver the file declares nothing for", carryable: true },
|
|
21284
21437
|
{ code: "UNDECLARED_COLOCATED_CALL", defaultSeverity: "warning", summary: "The realized function calls a modelled method of another component living in the same source file, and neither a narrative step nor a declared call names it", carryable: true }
|
|
21285
21438
|
],
|
|
21286
21439
|
check(ctx) {
|
|
@@ -21308,9 +21461,9 @@ var init_call_conformance = __esm({
|
|
|
21308
21461
|
const here = pathKey(file);
|
|
21309
21462
|
const fnSymbol = implMethod.symbol ?? implMethod.name;
|
|
21310
21463
|
const colocated = modelledAt.get(here) ?? { byKey: /* @__PURE__ */ new Map(), byName: /* @__PURE__ */ new Map() };
|
|
21311
|
-
const ownerOf4 = (site,
|
|
21464
|
+
const ownerOf4 = (site, bodyKey2) => {
|
|
21312
21465
|
const inHere = pathKey(site.from ?? here) === here;
|
|
21313
|
-
if (inHere &&
|
|
21466
|
+
if (inHere && bodyKey2 !== void 0 && !bodyKey2.startsWith("*")) return colocated.byKey.get(bodyKey2);
|
|
21314
21467
|
return colocated.byName.get(site.name);
|
|
21315
21468
|
};
|
|
21316
21469
|
const sites = closedCallSites(code, file, fnSymbol, void 0, implMethod.exportedVia);
|
|
@@ -21319,7 +21472,9 @@ var init_call_conformance = __esm({
|
|
|
21319
21472
|
for (const claim of claimsOf(implMethod)) {
|
|
21320
21473
|
if (!ctx.componentMap.has(claim.component)) continue;
|
|
21321
21474
|
const target = resolveCallTarget(ctx, claim.component, claim.method);
|
|
21322
|
-
if (target.
|
|
21475
|
+
if (target.bodies.has(bodyKey(here, fnSymbol, implMethod.exportedVia))) continue;
|
|
21476
|
+
const sharedBody = targetForwards(code, target);
|
|
21477
|
+
if (code.forwardsOf(file, fnSymbol, implMethod.exportedVia).some((p) => p.file !== here && sharedBody.has(`${p.file}#${p.name}`))) continue;
|
|
21323
21478
|
if (isTarget(code.forwardsOf(file, fnSymbol, implMethod.exportedVia), target)) continue;
|
|
21324
21479
|
const matching = sites.filter((s) => target.accepted.has(s.name));
|
|
21325
21480
|
const ref = `${claim.component}.${claim.method}`;
|
|
@@ -21363,7 +21518,10 @@ var init_call_conformance = __esm({
|
|
|
21363
21518
|
ctx.addIssue(
|
|
21364
21519
|
"warning",
|
|
21365
21520
|
"CALL_ORIGIN_UNRESOLVED",
|
|
21366
|
-
|
|
21521
|
+
// The sites, and what the answer means — once. Which receiver
|
|
21522
|
+
// forms resolve is the code's summary and the rule's description
|
|
21523
|
+
// (`wairon rules list`), not prose every finding repeats.
|
|
21524
|
+
`Method "${implMethod.name}" in implementation "${impl.id}": ${unresolved.length} ${claimNoun(implMethod)} not checked \u2014 called by name inside "${fnSymbol}" in "${file}", but only through receivers this analysis cannot resolve to a file: ${unresolved.map(describeMiss).join("; ")}. Neither proven realized nor accused; the receiver forms that do resolve are listed under CALL_ORIGIN_UNRESOLVED in \`wairon rules list\`.`,
|
|
21367
21525
|
impl.id,
|
|
21368
21526
|
entry.draftContext,
|
|
21369
21527
|
void 0,
|
|
@@ -21380,8 +21538,8 @@ var init_call_conformance = __esm({
|
|
|
21380
21538
|
}
|
|
21381
21539
|
const crossings = /* @__PURE__ */ new Map();
|
|
21382
21540
|
const reached = /* @__PURE__ */ new Map();
|
|
21383
|
-
const stopAtModelled = (site,
|
|
21384
|
-
const owner = ownerOf4(site,
|
|
21541
|
+
const stopAtModelled = (site, bodyKey2) => {
|
|
21542
|
+
const owner = ownerOf4(site, bodyKey2);
|
|
21385
21543
|
reached.set(site, owner);
|
|
21386
21544
|
return owner !== void 0;
|
|
21387
21545
|
};
|
|
@@ -21484,7 +21642,7 @@ var init_dependency_conformance = __esm({
|
|
|
21484
21642
|
dependencyConformanceRule = {
|
|
21485
21643
|
name: "dependency-conformance",
|
|
21486
21644
|
judges: "code",
|
|
21487
|
-
description: "Code\u2194spec Level 2: runtime import edges between component-mapped source files (a component maps to every file its implementations and their methods name) must be justified by declared relations \u2014 a direct dependsOn/owns pair, a shared component, membership in a depended-on pattern, or (across subsystems) a declared edge to the target subsystem's published surface (UNDECLARED_DEPENDENCY). Conversely, a declared dependsOn/owns edge between components realized in different files should be visible as an import between any file of the source and any file of the target (UNREALIZED_DEPENDENCY \u2014 DI indirection can defeat this, hence warning). Only exact-grade analyzed files participate;
|
|
21645
|
+
description: "Code\u2194spec Level 2: runtime import edges between component-mapped source files (a component maps to every file its implementations and their methods name) must be justified by declared relations \u2014 a direct dependsOn/owns pair, a shared component, membership in a depended-on pattern, or (across subsystems) a declared edge to the target subsystem's published surface (UNDECLARED_DEPENDENCY). Conversely, a declared dependsOn/owns edge between components realized in different files should be visible as an import between any file of the source and any file of the target (UNREALIZED_DEPENDENCY \u2014 DI indirection can defeat this, hence warning). A type-only import never accuses, but it does realize a declared edge: a constructor-injected collaborator is reached through its type, and `import type` is what dependency injection writes down. Only exact-grade analyzed files participate; chained subprojects validate standalone.",
|
|
21488
21646
|
codes: [
|
|
21489
21647
|
{ code: "UNDECLARED_DEPENDENCY", defaultSeverity: "warning", summary: "A runtime import between component-mapped files has no declared dependsOn/owns (or published-surface) justification" },
|
|
21490
21648
|
{ code: "UNREALIZED_DEPENDENCY", defaultSeverity: "warning", summary: "A declared dependsOn/owns edge between components in different files is realized by no import between any of their files" }
|
|
@@ -21552,6 +21710,23 @@ var init_dependency_conformance = __esm({
|
|
|
21552
21710
|
);
|
|
21553
21711
|
}
|
|
21554
21712
|
}
|
|
21713
|
+
const typeTraces = /* @__PURE__ */ new Map();
|
|
21714
|
+
const typeTracesOf = (path93) => {
|
|
21715
|
+
let out = typeTraces.get(path93);
|
|
21716
|
+
if (out) return out;
|
|
21717
|
+
out = /* @__PURE__ */ new Set();
|
|
21718
|
+
const facts = code.factsAt(path93);
|
|
21719
|
+
for (const specifier of new Set(Object.values(facts?.typeOnlyBindings ?? {}))) {
|
|
21720
|
+
const to = resolveImport(path93, specifier, mappedPaths, code.packages);
|
|
21721
|
+
if (to && to !== path93) out.add(to);
|
|
21722
|
+
}
|
|
21723
|
+
typeTraces.set(path93, out);
|
|
21724
|
+
return out;
|
|
21725
|
+
};
|
|
21726
|
+
const typeConnects = (from, to) => from.some((f) => {
|
|
21727
|
+
const traces = typeTracesOf(f);
|
|
21728
|
+
return to.some((t) => traces.has(t));
|
|
21729
|
+
});
|
|
21555
21730
|
for (const component of ctx.components) {
|
|
21556
21731
|
const fromFiles = filesOf(component.id);
|
|
21557
21732
|
if (fromFiles.length === 0) continue;
|
|
@@ -21565,6 +21740,7 @@ var init_dependency_conformance = __esm({
|
|
|
21565
21740
|
if (toFiles.length === 0) continue;
|
|
21566
21741
|
if (fromFiles.some((f) => toFiles.includes(f))) continue;
|
|
21567
21742
|
if (graph2.connects(fromFiles, toFiles, isMountingDeclarer)) continue;
|
|
21743
|
+
if (typeConnects(fromFiles, toFiles)) continue;
|
|
21568
21744
|
const impls = mappedImplsOf(component.id);
|
|
21569
21745
|
const declaredDependsOn = component.dependsOn.includes(targetId);
|
|
21570
21746
|
const declaredOwns = (component.owns ?? []).includes(targetId);
|
|
@@ -21573,7 +21749,7 @@ var init_dependency_conformance = __esm({
|
|
|
21573
21749
|
ctx.addIssue(
|
|
21574
21750
|
"warning",
|
|
21575
21751
|
"UNREALIZED_DEPENDENCY",
|
|
21576
|
-
`Component "${component.id}" declares ${relation} "${targetId}", but no runtime
|
|
21752
|
+
`Component "${component.id}" declares ${relation} "${targetId}", but no import \u2014 runtime or type-only \u2014 connects their source files (${fromFiles.join(", ")} \u219B ${crossSubsystem ? `subsystem ${target.subsystem}` : filesOf(targetId).join(", ")}) \u2014 either the collaboration is wired indirectly (DI) or the declared edge is stale.`,
|
|
21577
21753
|
impls[0]?.id ?? component.id,
|
|
21578
21754
|
draft,
|
|
21579
21755
|
void 0,
|
|
@@ -21655,14 +21831,17 @@ var integrationSimDeclarationRule;
|
|
|
21655
21831
|
var init_integration_sim_declaration = __esm({
|
|
21656
21832
|
"src/core/rules/conformance/integration-sim-declaration.ts"() {
|
|
21657
21833
|
"use strict";
|
|
21834
|
+
init_models();
|
|
21658
21835
|
integrationSimDeclarationRule = {
|
|
21659
21836
|
name: "integration-sim-declaration",
|
|
21660
21837
|
judges: "code",
|
|
21661
|
-
description: "Once a subsystem has adopted integration sims (its first declared L4 simPath), every OTHER complete, non-leaf implementation of that subsystem must declare one too: its unit suite proves contract shape against mocks, not that the wired components run together. A leaf component's unit suite IS its sim,
|
|
21838
|
+
description: "Once a subsystem has adopted integration sims (its first declared L4 simPath), every OTHER complete, non-leaf implementation of that subsystem must declare one too: its unit suite proves contract shape against mocks, not that the wired components run together. A leaf component's unit suite IS its sim, a subsystem that has adopted nothing is never flooded, and an implementation with no code yet \u2014 no source file named, or only files that are missing \u2014 has nothing to wire and is not asked for a harness.",
|
|
21662
21839
|
codes: [
|
|
21663
21840
|
{ code: "MISSING_INTEGRATION_SIM", defaultSeverity: "warning", summary: "Complete non-leaf implementation in a sim-adopting subsystem declares no simPath" }
|
|
21664
21841
|
],
|
|
21665
21842
|
check(ctx) {
|
|
21843
|
+
const code = ctx.codeIndex();
|
|
21844
|
+
const hasCode = (files) => files.some((file) => code.factsAt(pathKey(file))?.status !== "missing");
|
|
21666
21845
|
const adopted = /* @__PURE__ */ new Set();
|
|
21667
21846
|
for (const impl of ctx.implementations) {
|
|
21668
21847
|
if (!impl.simPath) continue;
|
|
@@ -21681,6 +21860,7 @@ var init_integration_sim_declaration = __esm({
|
|
|
21681
21860
|
if (ctx.isImplementationDraft(impl)) continue;
|
|
21682
21861
|
const deps = [.../* @__PURE__ */ new Set([...comp.dependsOn, ...comp.owns])].filter((d) => ctx.componentMap.has(d));
|
|
21683
21862
|
if (deps.length === 0) continue;
|
|
21863
|
+
if (!hasCode(implementationSourceFiles(impl))) continue;
|
|
21684
21864
|
ctx.addIssue(
|
|
21685
21865
|
"warning",
|
|
21686
21866
|
"MISSING_INTEGRATION_SIM",
|
|
@@ -22578,7 +22758,7 @@ var init_export_conformance = __esm({
|
|
|
22578
22758
|
exportConformanceRule = {
|
|
22579
22759
|
name: "export-conformance",
|
|
22580
22760
|
judges: "code",
|
|
22581
|
-
description: "Code-to-contract for the SURFACE: does this file publish anything the components it realizes never promised, that another component then takes? The rest of the conformance set reads contract-to-code \u2014 it asks whether the code holds what a spec claims \u2014 so a file could export whatever it liked under a component's name and nothing looked. That is one half of the same hole as a contract promising a parameter it never passes. A method's `exportedVia` names the export a consumer imports to REACH it \u2014 the value that composes it, which `symbol` cannot name because `symbol` names the function inside \u2014 and a listener's mount names the router entry it calls to hand a portal its requests; both are declared publication, and a declared handle the file does not actually export is itself a finding, so naming one can never become a free-text suppression. Both questions are asked only of a file read at exact grade, and a taker counts only when its own import specifier resolves to the file it is accused of taking from: below that nothing separates an export from a mention, or a shared word from a shared module.",
|
|
22761
|
+
description: "Code-to-contract for the SURFACE: does this file publish anything the components it realizes never promised, that another component then takes? The rest of the conformance set reads contract-to-code \u2014 it asks whether the code holds what a spec claims \u2014 so a file could export whatever it liked under a component's name and nothing looked. That is one half of the same hole as a contract promising a parameter it never passes. A method's `exportedVia` names the export a consumer imports to REACH it \u2014 the value that composes it, which `symbol` cannot name because `symbol` names the function inside \u2014 and a listener's mount names the router entry it calls to hand a portal its requests; both are declared publication, and a declared handle the file does not actually export is itself a finding, so naming one can never become a free-text suppression. Both questions are asked only of a file read at exact grade, and a taker counts only when its own import specifier resolves to the file it is accused of taking from: below that nothing separates an export from a mention, or a shared word from a shared module. A taker inside the owns fence is no crossing: a pattern constructs the blocks it owns, so the owner's file, or another member of the same pattern, importing a member's class is the owns relation made real.",
|
|
22582
22762
|
codes: [
|
|
22583
22763
|
{
|
|
22584
22764
|
code: "UNDECLARED_EXPORT",
|
|
@@ -22600,6 +22780,7 @@ var init_export_conformance = __esm({
|
|
|
22600
22780
|
check(ctx) {
|
|
22601
22781
|
const code = ctx.codeIndex();
|
|
22602
22782
|
const realization = ctx.realizationIndex();
|
|
22783
|
+
const ownership = ctx.ownershipIndex();
|
|
22603
22784
|
const promised = /* @__PURE__ */ new Map();
|
|
22604
22785
|
const promise = (file, name) => {
|
|
22605
22786
|
if (!name) return;
|
|
@@ -22726,13 +22907,20 @@ var init_export_conformance = __esm({
|
|
|
22726
22907
|
const declared = promised.get(file) ?? NOTHING_PROMISED;
|
|
22727
22908
|
const taken = takenFrom.get(file);
|
|
22728
22909
|
const owners = new Set(realization.componentsAt(file).map((c) => c.id));
|
|
22910
|
+
const fence = new Set(owners);
|
|
22911
|
+
for (const id of owners) {
|
|
22912
|
+
const pattern = ownership.ownerOf(id);
|
|
22913
|
+
if (pattern === void 0) continue;
|
|
22914
|
+
fence.add(pattern);
|
|
22915
|
+
for (const member of ownership.ownedMembers) if (ownership.ownerOf(member) === pattern) fence.add(member);
|
|
22916
|
+
}
|
|
22729
22917
|
const crossed = [];
|
|
22730
22918
|
for (const name of [...new Set(facts.exportedNames)].sort()) {
|
|
22731
22919
|
if (declared.has(name)) continue;
|
|
22732
22920
|
const takers = taken?.get(name);
|
|
22733
22921
|
if (!takers) continue;
|
|
22734
22922
|
const crossing = [...takers].some(
|
|
22735
|
-
(taker) => taker !== file && !realization.componentsAt(taker).some((c) =>
|
|
22923
|
+
(taker) => taker !== file && !realization.componentsAt(taker).some((c) => fence.has(c.id))
|
|
22736
22924
|
);
|
|
22737
22925
|
if (crossing) crossed.push(name);
|
|
22738
22926
|
}
|
|
@@ -24528,6 +24716,8 @@ function walkExact(ts, sourceText, fileName) {
|
|
|
24528
24716
|
const typeOnlyBindings = /* @__PURE__ */ new Map();
|
|
24529
24717
|
const fieldTypes = /* @__PURE__ */ new Map();
|
|
24530
24718
|
const localTypes = /* @__PURE__ */ new Map();
|
|
24719
|
+
const memberTypes = /* @__PURE__ */ new Map();
|
|
24720
|
+
const aliasTargets = /* @__PURE__ */ new Map();
|
|
24531
24721
|
const typeShapes = /* @__PURE__ */ new Map();
|
|
24532
24722
|
const functionParams = /* @__PURE__ */ new Map();
|
|
24533
24723
|
const functionRoutes = /* @__PURE__ */ new Map();
|
|
@@ -24537,7 +24727,7 @@ function walkExact(ts, sourceText, fileName) {
|
|
|
24537
24727
|
const mutableBindings = /* @__PURE__ */ new Set();
|
|
24538
24728
|
const schemaConstants = /* @__PURE__ */ new Map();
|
|
24539
24729
|
const derivedAliases = [];
|
|
24540
|
-
const receiverKey = (site) => site.via ?? (site.field ? `this.${site.field}` : site.constructed ? `new ${site.constructed}` : site.returnedBy ? `${site.returnedBy}()` : site.enclosingClass ? `this:${site.enclosingClass}` : "");
|
|
24730
|
+
const receiverKey = (site) => site.via ?? (site.field ? `this.${site.field}` : site.constructed ? `new ${site.constructed}` : site.returnedBy ? `${site.returnedBy}()` : site.enclosingClass ? `this:${site.enclosingClass}` : site.receiverPath ? site.receiverPath.join(".") : "");
|
|
24541
24731
|
const siteKey = (site) => `${site.member ? "m" : "b"}:${receiverKey(site)}:${site.name}`;
|
|
24542
24732
|
const addBindingNames = (name) => {
|
|
24543
24733
|
if (ts.isIdentifier(name)) declared.add(name.text);
|
|
@@ -24552,7 +24742,34 @@ function walkExact(ts, sourceText, fileName) {
|
|
|
24552
24742
|
if (ts.isPrivateIdentifier(name)) return name.text;
|
|
24553
24743
|
return void 0;
|
|
24554
24744
|
};
|
|
24555
|
-
const
|
|
24745
|
+
const MEMBER_PRESERVING = /* @__PURE__ */ new Set(["Pick", "Omit", "Partial", "Required", "Readonly"]);
|
|
24746
|
+
const typeReferenceName = (type) => {
|
|
24747
|
+
let at = type;
|
|
24748
|
+
for (let depth = 0; at && depth < 4; depth++) {
|
|
24749
|
+
if (!ts.isTypeReferenceNode(at) || !ts.isIdentifier(at.typeName)) return void 0;
|
|
24750
|
+
const name = at.typeName.text;
|
|
24751
|
+
if (!MEMBER_PRESERVING.has(name) || !at.typeArguments?.length) return name;
|
|
24752
|
+
at = at.typeArguments[0];
|
|
24753
|
+
}
|
|
24754
|
+
return void 0;
|
|
24755
|
+
};
|
|
24756
|
+
const recordMemberType = (shape, name, type) => {
|
|
24757
|
+
if (!shape) return;
|
|
24758
|
+
const typeName = typeReferenceName(type);
|
|
24759
|
+
if (!typeName) return;
|
|
24760
|
+
const property = propertyNameText(name);
|
|
24761
|
+
if (!property) return;
|
|
24762
|
+
const key = `${shape}.${property}`;
|
|
24763
|
+
const named2 = memberTypes.get(key) ?? /* @__PURE__ */ new Set();
|
|
24764
|
+
named2.add(typeName);
|
|
24765
|
+
memberTypes.set(key, named2);
|
|
24766
|
+
};
|
|
24767
|
+
const shapeNameOf = (holder) => {
|
|
24768
|
+
if (!holder) return void 0;
|
|
24769
|
+
if ((ts.isClassDeclaration(holder) || ts.isInterfaceDeclaration(holder)) && holder.name) return holder.name.text;
|
|
24770
|
+
if (ts.isTypeLiteralNode(holder) && holder.parent && ts.isTypeAliasDeclaration(holder.parent)) return holder.parent.name.text;
|
|
24771
|
+
return void 0;
|
|
24772
|
+
};
|
|
24556
24773
|
const isParameterProperty = (node) => !!node.modifiers?.some((m) => m.kind === ts.SyntaxKind.PublicKeyword || m.kind === ts.SyntaxKind.PrivateKeyword || m.kind === ts.SyntaxKind.ProtectedKeyword || m.kind === ts.SyntaxKind.ReadonlyKeyword);
|
|
24557
24774
|
const recordFieldType = (name, type) => {
|
|
24558
24775
|
const typeName = typeReferenceName(type);
|
|
@@ -24749,7 +24966,18 @@ function walkExact(ts, sourceText, fileName) {
|
|
|
24749
24966
|
} else if (receiver.kind === ts.SyntaxKind.ThisKeyword) {
|
|
24750
24967
|
const owner = enclosingClassOf(node);
|
|
24751
24968
|
addSite(owner !== void 0 ? { name, member: true, enclosingClass: owner } : { name, member: true });
|
|
24752
|
-
} else
|
|
24969
|
+
} else {
|
|
24970
|
+
const chain = [];
|
|
24971
|
+
let at = receiver;
|
|
24972
|
+
while (ts.isPropertyAccessExpression(at) && !at.questionDotToken && ts.isIdentifier(at.name)) {
|
|
24973
|
+
chain.unshift(at.name.text);
|
|
24974
|
+
at = at.expression;
|
|
24975
|
+
}
|
|
24976
|
+
if (ts.isIdentifier(at)) chain.unshift(at.text);
|
|
24977
|
+
else if (at.kind === ts.SyntaxKind.ThisKeyword) chain.unshift("this");
|
|
24978
|
+
else chain.length = 0;
|
|
24979
|
+
addSite(chain.length >= 2 ? { name, member: true, receiverPath: chain } : { name, member: true });
|
|
24980
|
+
}
|
|
24753
24981
|
}
|
|
24754
24982
|
}
|
|
24755
24983
|
ts.forEachChild(node, count);
|
|
@@ -24884,9 +25112,14 @@ function walkExact(ts, sourceText, fileName) {
|
|
|
24884
25112
|
functionRoutes.set(fnName, known);
|
|
24885
25113
|
}
|
|
24886
25114
|
}
|
|
24887
|
-
if (ts.isPropertyDeclaration(node))
|
|
24888
|
-
|
|
25115
|
+
if (ts.isPropertyDeclaration(node)) {
|
|
25116
|
+
recordFieldType(node.name, node.type);
|
|
25117
|
+
recordMemberType(shapeNameOf(node.parent), node.name, node.type);
|
|
25118
|
+
} else if (ts.isParameter(node) && node.parent && ts.isConstructorDeclaration(node.parent) && isParameterProperty(node)) {
|
|
24889
25119
|
recordFieldType(node.name, node.type);
|
|
25120
|
+
recordMemberType(shapeNameOf(node.parent.parent), node.name, node.type);
|
|
25121
|
+
} else if (ts.isPropertySignature(node)) {
|
|
25122
|
+
recordMemberType(shapeNameOf(node.parent), node.name, node.type);
|
|
24890
25123
|
}
|
|
24891
25124
|
if (ts.isParameter(node) || ts.isVariableDeclaration(node)) recordLocalType(node.name, node.type);
|
|
24892
25125
|
if (ts.isInterfaceDeclaration(node)) {
|
|
@@ -24900,6 +25133,10 @@ function walkExact(ts, sourceText, fileName) {
|
|
|
24900
25133
|
if (ts.isTypeLiteralNode(node.type)) recordDeclaredShape(node.name.text, node.type.members, false);
|
|
24901
25134
|
else {
|
|
24902
25135
|
if (!aliasTypes.has(node.name.text)) aliasTypes.set(node.name.text, node.type.getText(sf));
|
|
25136
|
+
const named2 = typeReferenceName(node.type);
|
|
25137
|
+
if (named2 !== void 0 && named2 !== node.name.text && !aliasTargets.has(node.name.text)) {
|
|
25138
|
+
aliasTargets.set(node.name.text, named2);
|
|
25139
|
+
}
|
|
24903
25140
|
const constant = inferredSchemaConstant(node.type);
|
|
24904
25141
|
if (constant) derivedAliases.push({ name: node.name.text, constant });
|
|
24905
25142
|
const literals = stringLiteralUnion(node.type);
|
|
@@ -25060,7 +25297,7 @@ function walkExact(ts, sourceText, fileName) {
|
|
|
25060
25297
|
}
|
|
25061
25298
|
const reexportOnly = sf.statements.length > 0 && sf.statements.every((st) => ts.isExportDeclaration(st) && !!st.moduleSpecifier);
|
|
25062
25299
|
for (const alias of propertyAliases) if (exported.has(alias.container)) exportAliases.push(alias);
|
|
25063
|
-
return { declared, anchors, exported, imports, reexports, starExports, namedReexports, exportAliases, returnTypes, complexity, calls, bodies, importBindings, typeOnlyBindings, fieldTypes, localTypes, typeShapes, functionParams, functionRoutes, asyncFunctions, enumValues, aliasTypes, mutableBindings, reexportOnly };
|
|
25300
|
+
return { declared, anchors, exported, imports, reexports, starExports, namedReexports, exportAliases, returnTypes, complexity, calls, bodies, importBindings, typeOnlyBindings, fieldTypes, localTypes, memberTypes, aliasTargets, typeShapes, functionParams, functionRoutes, asyncFunctions, enumValues, aliasTypes, mutableBindings, reexportOnly };
|
|
25064
25301
|
}
|
|
25065
25302
|
function resolveModule(fromFile, specifier, packages) {
|
|
25066
25303
|
if (!specifier.startsWith(".")) return packages.get(specifier) ?? null;
|
|
@@ -25428,6 +25665,8 @@ function buildCodeModel(implementations, types, projectRoot2, sourceRoots = [],
|
|
|
25428
25665
|
typeOnlyBindings: Object.fromEntries(facts.typeOnlyBindings),
|
|
25429
25666
|
fieldTypes: Object.fromEntries([...facts.fieldTypes].map(([k, v]) => [k, [...v]])),
|
|
25430
25667
|
localTypes: Object.fromEntries([...facts.localTypes].map(([k, v]) => [k, [...v]])),
|
|
25668
|
+
memberTypes: Object.fromEntries([...facts.memberTypes].map(([k, v]) => [k, [...v]])),
|
|
25669
|
+
aliasTargets: Object.fromEntries(facts.aliasTargets),
|
|
25431
25670
|
typeShapes: Object.fromEntries(facts.typeShapes),
|
|
25432
25671
|
functionParams: Object.fromEntries(facts.functionParams),
|
|
25433
25672
|
functionRoutes: Object.fromEntries([...facts.functionRoutes].map(([k, v]) => [k, [...v.values()]])),
|
|
@@ -25910,8 +26149,16 @@ function buildRuleContext(opts) {
|
|
|
25910
26149
|
}
|
|
25911
26150
|
if (ownerId && nameKey(first) === nameKey(ownerId)) return ownTypes.some((spec) => typeMatchesRef(spec, rest.join("::")));
|
|
25912
26151
|
if (ref.includes("::") && authoredTypeRefs.has(ref)) return true;
|
|
26152
|
+
return false;
|
|
26153
|
+
};
|
|
26154
|
+
const aliasSpelling = (ref) => {
|
|
26155
|
+
const segments = ref.split(".");
|
|
26156
|
+
if (segments.length !== 2 || ref.includes("::")) return null;
|
|
26157
|
+
const [first, name] = segments;
|
|
26158
|
+
if (subsystems.some((s) => nameKey(s.id.split("::").pop()) === nameKey(first) && memberOf(s.id) === void 0)) return null;
|
|
25913
26159
|
const via = aliasOf(first);
|
|
25914
|
-
|
|
26160
|
+
const publicName = via ? exportedBy(via, nameKey(name), "type") : void 0;
|
|
26161
|
+
return via && publicName ? `${via.alias}::${publicName}` : null;
|
|
25915
26162
|
};
|
|
25916
26163
|
const foreignSurfaceRef = (ref) => {
|
|
25917
26164
|
const segments = ref.split("::").filter((seg) => seg && seg !== "super");
|
|
@@ -26285,6 +26532,7 @@ function buildRuleContext(opts) {
|
|
|
26285
26532
|
isImplementationDraft,
|
|
26286
26533
|
getComponentProfile,
|
|
26287
26534
|
isTypeResolved,
|
|
26535
|
+
aliasSpelling,
|
|
26288
26536
|
targetLanguageFor,
|
|
26289
26537
|
isSpecInScope,
|
|
26290
26538
|
isInChainedSubproject,
|
|
@@ -28977,6 +29225,24 @@ function mapSignatureSource(value, map) {
|
|
|
28977
29225
|
const head2 = map("signatureFrom", parts.head);
|
|
28978
29226
|
return head2 === parts.head ? value : `${head2}.${parts.tail}`;
|
|
28979
29227
|
}
|
|
29228
|
+
function typeExpressionSlots(kind, doc) {
|
|
29229
|
+
const out = [];
|
|
29230
|
+
const paths = kind === "interface" || kind === "type" ? TYPE_EXPRESSION_PATHS[kind] : [];
|
|
29231
|
+
const walk2 = (node, segments, at, method2) => {
|
|
29232
|
+
if (!node || typeof node !== "object") return;
|
|
29233
|
+
const holder = node;
|
|
29234
|
+
const [head2, ...rest] = segments;
|
|
29235
|
+
if (rest.length === 0) {
|
|
29236
|
+
if (typeof holder[head2] === "string") out.push({ holder, key: head2, path: at, ...method2 ? { method: method2 } : {} });
|
|
29237
|
+
return;
|
|
29238
|
+
}
|
|
29239
|
+
const next = holder[head2];
|
|
29240
|
+
const items = Array.isArray(next) ? next : next && typeof next === "object" ? [next] : [];
|
|
29241
|
+
for (const item of items) walk2(item, rest, at, head2 === "methods" ? item : method2);
|
|
29242
|
+
};
|
|
29243
|
+
for (const at of paths) walk2(doc, at.split("."), at, void 0);
|
|
29244
|
+
return out;
|
|
29245
|
+
}
|
|
28980
29246
|
function mapSpecReferences(kind, spec, map) {
|
|
28981
29247
|
switch (kind) {
|
|
28982
29248
|
case "subsystem": {
|
|
@@ -29067,11 +29333,8 @@ function rawReferences(kind, spec) {
|
|
|
29067
29333
|
for (const link of spec.trustedLinks ?? []) add2("trustedLinks", link.subsystem);
|
|
29068
29334
|
break;
|
|
29069
29335
|
case "interface":
|
|
29070
|
-
|
|
29071
|
-
|
|
29072
|
-
m.params?.forEach((p) => qualifiedTypeNames(p.type).forEach((t) => add2("type", t)));
|
|
29073
|
-
if (m.signatureFrom !== void 0 && !signatureSourceParts(m.signatureFrom)) add2("type", m.signatureFrom);
|
|
29074
|
-
}
|
|
29336
|
+
case "type":
|
|
29337
|
+
typeSlotReferences(kind, spec).forEach((t) => add2("type", t));
|
|
29075
29338
|
break;
|
|
29076
29339
|
case "implementation":
|
|
29077
29340
|
for (const m of spec.methods) {
|
|
@@ -29083,19 +29346,36 @@ function rawReferences(kind, spec) {
|
|
|
29083
29346
|
}
|
|
29084
29347
|
}
|
|
29085
29348
|
break;
|
|
29086
|
-
case "type": {
|
|
29087
|
-
const t = spec;
|
|
29088
|
-
for (const f of t.fields) qualifiedTypeNames(f.type).forEach((n) => add2("type", n));
|
|
29089
|
-
t.params?.forEach((p) => qualifiedTypeNames(p.type).forEach((n) => add2("type", n)));
|
|
29090
|
-
qualifiedTypeNames(t.returns).forEach((n) => add2("type", n));
|
|
29091
|
-
for (const m of t.methods ?? []) m.params?.forEach((p) => qualifiedTypeNames(p.type).forEach((n) => add2("type", n)));
|
|
29092
|
-
break;
|
|
29093
|
-
}
|
|
29094
29349
|
default:
|
|
29095
29350
|
break;
|
|
29096
29351
|
}
|
|
29097
29352
|
return out;
|
|
29098
29353
|
}
|
|
29354
|
+
function typeSlotReferences(kind, spec) {
|
|
29355
|
+
const out = [];
|
|
29356
|
+
const structured2 = /* @__PURE__ */ new Set();
|
|
29357
|
+
const slots = typeExpressionSlots(kind, spec);
|
|
29358
|
+
for (const slot of slots) {
|
|
29359
|
+
const value = slot.holder[slot.key];
|
|
29360
|
+
if (slot.path === "methods.signature") continue;
|
|
29361
|
+
if (slot.path === "methods.signatureFrom") {
|
|
29362
|
+
if (!signatureSourceParts(value)) out.push(value);
|
|
29363
|
+
continue;
|
|
29364
|
+
}
|
|
29365
|
+
for (const name of qualifiedTypeNames(value)) {
|
|
29366
|
+
out.push(name);
|
|
29367
|
+
structured2.add(name);
|
|
29368
|
+
}
|
|
29369
|
+
}
|
|
29370
|
+
for (const slot of slots.filter((s) => s.path === "methods.signature")) {
|
|
29371
|
+
for (const name of qualifiedTypeNames(slot.holder[slot.key])) {
|
|
29372
|
+
if (structured2.has(name)) continue;
|
|
29373
|
+
structured2.add(name);
|
|
29374
|
+
out.push(name);
|
|
29375
|
+
}
|
|
29376
|
+
}
|
|
29377
|
+
return out;
|
|
29378
|
+
}
|
|
29099
29379
|
function bareRawReferences(kind, spec) {
|
|
29100
29380
|
const out = [];
|
|
29101
29381
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -29121,7 +29401,11 @@ function bareRawReferences(kind, spec) {
|
|
|
29121
29401
|
const t = spec;
|
|
29122
29402
|
for (const f of t.fields) for (const ref of fieldTypeRefs(t, f.type)) add2("type", ref);
|
|
29123
29403
|
for (const ref of signatureTypeRefs(t)) add2("type", ref);
|
|
29124
|
-
|
|
29404
|
+
const own = lower(typeGenericParameters(t));
|
|
29405
|
+
for (const m of t.methods ?? []) {
|
|
29406
|
+
const generics = /* @__PURE__ */ new Set([...own, ...lower(methodGenericParameters(m))]);
|
|
29407
|
+
for (const ref of methodTypeRefs(m)) add2("type", ref, generics);
|
|
29408
|
+
}
|
|
29125
29409
|
break;
|
|
29126
29410
|
}
|
|
29127
29411
|
case "implementation":
|
|
@@ -29134,6 +29418,9 @@ function bareRawReferences(kind, spec) {
|
|
|
29134
29418
|
}
|
|
29135
29419
|
return out;
|
|
29136
29420
|
}
|
|
29421
|
+
function respellTypeNames(typeStr, map) {
|
|
29422
|
+
return mapTypeNames(typeStr, (_position, name) => map(name));
|
|
29423
|
+
}
|
|
29137
29424
|
function mapTypeNames(typeStr, map) {
|
|
29138
29425
|
if (typeStr === void 0) return void 0;
|
|
29139
29426
|
return typeStr.replace(
|
|
@@ -29157,12 +29444,9 @@ function respellStoredReferences(kind, doc, map) {
|
|
|
29157
29444
|
const values = holder[key];
|
|
29158
29445
|
if (Array.isArray(values)) holder[key] = values.map((v) => typeof v === "string" ? map(position, v) : v);
|
|
29159
29446
|
};
|
|
29160
|
-
const typed = (holder, key) => {
|
|
29161
|
-
if (typeof holder[key] === "string") holder[key] = mapTypeNames(holder[key], map);
|
|
29162
|
-
};
|
|
29163
29447
|
const published = (entries) => {
|
|
29164
29448
|
for (const e of list2(entries)) {
|
|
29165
|
-
for (const key of ["from", "component", "interface", "typeDef"]) at(e, key, "publicInterfaces");
|
|
29449
|
+
for (const key of ["from", "subsystem", "component", "interface", "typeDef"]) at(e, key, "publicInterfaces");
|
|
29166
29450
|
each(e, "consumers", "publicInterfaces");
|
|
29167
29451
|
}
|
|
29168
29452
|
};
|
|
@@ -29184,14 +29468,7 @@ function respellStoredReferences(kind, doc, map) {
|
|
|
29184
29468
|
break;
|
|
29185
29469
|
case "interface":
|
|
29186
29470
|
at(doc, "component", "contract");
|
|
29187
|
-
|
|
29188
|
-
typed(m, "signature");
|
|
29189
|
-
typed(m, "returns");
|
|
29190
|
-
for (const p of list2(m.params)) typed(p, "type");
|
|
29191
|
-
if (typeof m.signatureFrom === "string") {
|
|
29192
|
-
m.signatureFrom = signatureSourceParts(m.signatureFrom) ? mapSignatureSource(m.signatureFrom, map) : mapTypeNames(m.signatureFrom, map);
|
|
29193
|
-
}
|
|
29194
|
-
}
|
|
29471
|
+
typedSlots("interface", doc, map);
|
|
29195
29472
|
break;
|
|
29196
29473
|
case "implementation":
|
|
29197
29474
|
at(doc, "contract", "contract");
|
|
@@ -29218,34 +29495,33 @@ function respellStoredReferences(kind, doc, map) {
|
|
|
29218
29495
|
case "type":
|
|
29219
29496
|
at(doc, "subsystem", "subsystem");
|
|
29220
29497
|
at(doc, "group", "group");
|
|
29221
|
-
|
|
29222
|
-
for (const p of list2(doc.params)) typed(p, "type");
|
|
29223
|
-
typed(doc, "returns");
|
|
29224
|
-
for (const m of list2(doc.methods)) for (const p of list2(m.params)) typed(p, "type");
|
|
29498
|
+
typedSlots("type", doc, map);
|
|
29225
29499
|
break;
|
|
29226
29500
|
default:
|
|
29227
29501
|
break;
|
|
29228
29502
|
}
|
|
29229
29503
|
}
|
|
29504
|
+
function typedSlots(kind, doc, map) {
|
|
29505
|
+
for (const slot of typeExpressionSlots(kind, doc)) {
|
|
29506
|
+
const value = slot.holder[slot.key];
|
|
29507
|
+
slot.holder[slot.key] = slot.path === "methods.signatureFrom" && signatureSourceParts(value) ? mapSignatureSource(value, map) : mapTypeNames(value, map);
|
|
29508
|
+
}
|
|
29509
|
+
}
|
|
29230
29510
|
function mapRawReferences(kind, spec, map) {
|
|
29231
29511
|
switch (kind) {
|
|
29232
29512
|
case "subsystem": {
|
|
29233
29513
|
const s = spec;
|
|
29234
29514
|
return { ...s, trustedLinks: (s.trustedLinks ?? []).map((l) => ({ ...l, subsystem: map("trustedLinks", l.subsystem) })) };
|
|
29235
29515
|
}
|
|
29236
|
-
case "interface":
|
|
29237
|
-
|
|
29238
|
-
|
|
29239
|
-
|
|
29240
|
-
|
|
29241
|
-
|
|
29242
|
-
|
|
29243
|
-
|
|
29244
|
-
|
|
29245
|
-
// A type source is a raw type position; a method source's head is bound, never raw.
|
|
29246
|
-
...m.signatureFrom !== void 0 && !signatureSourceParts(m.signatureFrom) ? { signatureFrom: mapTypeNames(m.signatureFrom, map) } : {}
|
|
29247
|
-
}))
|
|
29248
|
-
};
|
|
29516
|
+
case "interface":
|
|
29517
|
+
case "type": {
|
|
29518
|
+
const copy = JSON.parse(JSON.stringify(spec));
|
|
29519
|
+
for (const slot of typeExpressionSlots(kind, copy)) {
|
|
29520
|
+
const value = slot.holder[slot.key];
|
|
29521
|
+
if (slot.path === "methods.signatureFrom" && signatureSourceParts(value)) continue;
|
|
29522
|
+
slot.holder[slot.key] = mapTypeNames(value, map);
|
|
29523
|
+
}
|
|
29524
|
+
return copy;
|
|
29249
29525
|
}
|
|
29250
29526
|
case "implementation": {
|
|
29251
29527
|
const impl = spec;
|
|
@@ -29270,17 +29546,6 @@ function mapRawReferences(kind, spec, map) {
|
|
|
29270
29546
|
}))
|
|
29271
29547
|
};
|
|
29272
29548
|
}
|
|
29273
|
-
case "type": {
|
|
29274
|
-
const t = spec;
|
|
29275
|
-
const typedParams = (params) => params ? { params: params.map((p) => ({ ...p, type: mapTypeNames(p.type, map) })) } : {};
|
|
29276
|
-
return {
|
|
29277
|
-
...t,
|
|
29278
|
-
fields: t.fields.map((f) => ({ ...f, type: mapTypeNames(f.type, map) })),
|
|
29279
|
-
...typedParams(t.params),
|
|
29280
|
-
...t.returns !== void 0 ? { returns: mapTypeNames(t.returns, map) } : {},
|
|
29281
|
-
methods: (t.methods ?? []).map((m) => ({ ...m, ...typedParams(m.params) }))
|
|
29282
|
-
};
|
|
29283
|
-
}
|
|
29284
29549
|
default:
|
|
29285
29550
|
return spec;
|
|
29286
29551
|
}
|
|
@@ -29362,7 +29627,7 @@ function resolveFamilyTables(raws) {
|
|
|
29362
29627
|
const byKey3 = new Map(raws.map((r) => [r.record.namespace, r]));
|
|
29363
29628
|
const done = /* @__PURE__ */ new Set();
|
|
29364
29629
|
const resolving = /* @__PURE__ */ new Set();
|
|
29365
|
-
const
|
|
29630
|
+
const resolve57 = (raw) => {
|
|
29366
29631
|
const key = raw.record.namespace;
|
|
29367
29632
|
if (done.has(key)) return raw.record.exports;
|
|
29368
29633
|
if (resolving.has(key)) return null;
|
|
@@ -29377,7 +29642,7 @@ function resolveFamilyTables(raws) {
|
|
|
29377
29642
|
const producerKey = source !== void 0 ? raw.record.aliases.get(source) : void 0;
|
|
29378
29643
|
if (producerKey === void 0 || producerTables.has(producerKey)) continue;
|
|
29379
29644
|
const producer = byKey3.get(producerKey);
|
|
29380
|
-
producerTables.set(producerKey, producer ?
|
|
29645
|
+
producerTables.set(producerKey, producer ? resolve57(producer) : null);
|
|
29381
29646
|
audienceOf.set(producerKey, "project");
|
|
29382
29647
|
}
|
|
29383
29648
|
raw.record.subsystemExports = subsystemTables;
|
|
@@ -29393,7 +29658,7 @@ function resolveFamilyTables(raws) {
|
|
|
29393
29658
|
resolving.delete(key);
|
|
29394
29659
|
}
|
|
29395
29660
|
};
|
|
29396
|
-
raws.forEach((raw) =>
|
|
29661
|
+
raws.forEach((raw) => resolve57(raw));
|
|
29397
29662
|
}
|
|
29398
29663
|
function bindAuthored(raw, raws, readables, authored) {
|
|
29399
29664
|
const key = raw.record.namespace;
|
|
@@ -30479,6 +30744,12 @@ function readLockState(current2) {
|
|
|
30479
30744
|
if (!record2) return { state: "unlocked", record: null, current: current2 };
|
|
30480
30745
|
return { state: stateIdEquals(record2.stateId, current2) ? "locked" : "stale", record: record2, current: current2 };
|
|
30481
30746
|
}
|
|
30747
|
+
function writeLockRecord2(record2) {
|
|
30748
|
+
writeLockRecord(record2);
|
|
30749
|
+
}
|
|
30750
|
+
function renderDiagram2(format, relations) {
|
|
30751
|
+
return renderDiagram(format, relations);
|
|
30752
|
+
}
|
|
30482
30753
|
function createProjectConfig(config) {
|
|
30483
30754
|
projectConfigRepository.create(config);
|
|
30484
30755
|
}
|
|
@@ -30583,7 +30854,7 @@ function assertSpecsInReach(targets, what, projectRung = false) {
|
|
|
30583
30854
|
function moveMethods(from, to, methods, hooks, dryRun) {
|
|
30584
30855
|
return current().moveMethods(from, to, methods, hooks, dryRun);
|
|
30585
30856
|
}
|
|
30586
|
-
var fs14, path21, crypto5, PartReadOnly, SubsystemWriteDenied, PART_FORBIDDEN_FIELDS, IMPORTABLE_POSITIONS, SIGNATURE_TTL_MS, COMPONENT_AUTH_SOURCE, STEP_JUMP_FIELDS, STEP_JUMP_LISTS, DELTA_VERBS, JUMP_LABEL_TWINS, LABEL_TWIN_CONTAINERS, SpecWorkspace, DEFAULT_DEPENDENCY_LIMIT, PROSE_FIELDS, DELTA_JUMP_PINS, workspaces, lastServedBinding;
|
|
30857
|
+
var fs14, path21, crypto5, PartReadOnly, SubsystemWriteDenied, PART_FORBIDDEN_FIELDS, TYPE_EXPRESSION_PATHS, IMPORTABLE_POSITIONS, SIGNATURE_TTL_MS, COMPONENT_AUTH_SOURCE, STEP_JUMP_FIELDS, STEP_JUMP_LISTS, DELTA_VERBS, JUMP_LABEL_TWINS, LABEL_TWIN_CONTAINERS, SpecWorkspace, DEFAULT_DEPENDENCY_LIMIT, PROSE_FIELDS, DELTA_JUMP_PINS, workspaces, lastServedBinding;
|
|
30587
30858
|
var init_specs2 = __esm({
|
|
30588
30859
|
"src/core/specs.ts"() {
|
|
30589
30860
|
"use strict";
|
|
@@ -30625,6 +30896,10 @@ var init_specs2 = __esm({
|
|
|
30625
30896
|
}
|
|
30626
30897
|
};
|
|
30627
30898
|
PART_FORBIDDEN_FIELDS = ["members", "externals", "rules", "extensions", "composition", "targets"];
|
|
30899
|
+
TYPE_EXPRESSION_PATHS = {
|
|
30900
|
+
interface: ["methods.params.type", "methods.returns", "methods.signature", "methods.signatureFrom"],
|
|
30901
|
+
type: ["fields.type", "params.type", "returns", "methods.params.type", "methods.returns", "methods.signature"]
|
|
30902
|
+
};
|
|
30628
30903
|
IMPORTABLE_POSITIONS = /* @__PURE__ */ new Set(["dependsOn", "dispatch", "mounts", "narrative"]);
|
|
30629
30904
|
SIGNATURE_TTL_MS = 2e3;
|
|
30630
30905
|
COMPONENT_AUTH_SOURCE = "component:";
|
|
@@ -34597,7 +34872,7 @@ function isWithinDir2(dir, file) {
|
|
|
34597
34872
|
return f === d || f.startsWith(d + path25.sep);
|
|
34598
34873
|
}
|
|
34599
34874
|
function externalizeSubsystem(subsystemId, path93, as) {
|
|
34600
|
-
externalizeInto(subsystemId, path93, as);
|
|
34875
|
+
return externalizeInto(subsystemId, path93, as);
|
|
34601
34876
|
}
|
|
34602
34877
|
function externalizeInto(subsystemId, partPath, as) {
|
|
34603
34878
|
if (subsystemId.includes("::")) {
|
|
@@ -34616,7 +34891,8 @@ function externalizeInto(subsystemId, partPath, as) {
|
|
|
34616
34891
|
const parentRoot = getProjectRoot();
|
|
34617
34892
|
const parentSpecsDir = aiPathsAt(parentRoot).specsDir();
|
|
34618
34893
|
const fooDir = path25.join(parentSpecsDir, subsystemId);
|
|
34619
|
-
|
|
34894
|
+
const owned = subsystemFiles(subsystemId, parentSpecsDir);
|
|
34895
|
+
if (owned.files.length === 0) throw new WaironError(`subsystem specs not found for "${subsystemId}" under ${parentSpecsDir}`);
|
|
34620
34896
|
const relPath = toPosixPath(partPath);
|
|
34621
34897
|
const partDir = assertContainedProjectPath(parentRoot, relPath);
|
|
34622
34898
|
if (path25.resolve(partDir) === path25.resolve(parentRoot)) throw new WaironError(`cannot externalize "${subsystemId}": the path "${relPath}" is the project root itself.`);
|
|
@@ -34629,19 +34905,74 @@ function externalizeInto(subsystemId, partPath, as) {
|
|
|
34629
34905
|
if (!joined && (declaredMembers(projectConfigRepository.load() ?? {}).some((m) => m.alias === alias) || projectConfigRepository.load()?.externals?.[alias] !== void 0)) {
|
|
34630
34906
|
throw new WaironError(`cannot externalize "${subsystemId}": this project already declares the alias "${alias}".`);
|
|
34631
34907
|
}
|
|
34632
|
-
const
|
|
34633
|
-
|
|
34908
|
+
const partSpecsDir = aiPathsAt(partDir).specsDir();
|
|
34909
|
+
const targetDir = path25.join(partSpecsDir, subsystemId);
|
|
34910
|
+
if (owned.home !== void 0 && fs16.existsSync(targetDir)) throw new WaironError(`target already contains a "${subsystemId}" subsystem: ${targetDir}`);
|
|
34911
|
+
const destinationOf = (file) => path25.join(partSpecsDir, path25.relative(parentSpecsDir, file));
|
|
34912
|
+
const strays = owned.files.filter((f) => !(owned.home !== void 0 && isWithinDir2(owned.home, f)));
|
|
34913
|
+
const taken = strays.filter((f) => fs16.existsSync(destinationOf(f)));
|
|
34914
|
+
if (taken.length > 0) throw new WaironError(`cannot externalize "${subsystemId}": the part already holds ${taken.map((f) => toPosixPath(path25.relative(partDir, destinationOf(f)))).join(", ")}.`);
|
|
34634
34915
|
if (as === "project") {
|
|
34635
34916
|
if (joined) throw new WaironError(`cannot externalize "${subsystemId}" as a project into the existing part "${joined.alias}": externalize it into the part, then promote the part (\`wairon member promote ${joined.alias}\`).`);
|
|
34636
|
-
const
|
|
34917
|
+
const inside = new Set(owned.files.map((f) => path25.resolve(f)));
|
|
34918
|
+
const refusals = promoteRefusals(family, owned.files, ownSpecFiles(family, fooDir).filter((f) => !inside.has(path25.resolve(f))), alias);
|
|
34637
34919
|
if (refusals.length > 0) throw new WaironError(`cannot externalize "${subsystemId}" as a project: ${refusals.join("; ")}.`);
|
|
34638
34920
|
}
|
|
34639
|
-
|
|
34640
|
-
|
|
34641
|
-
|
|
34921
|
+
const moved = [];
|
|
34922
|
+
if (owned.home !== void 0) {
|
|
34923
|
+
ensureDir(path25.dirname(targetDir));
|
|
34924
|
+
fs16.renameSync(owned.home, targetDir);
|
|
34925
|
+
moved.push(...specFilesUnder(targetDir));
|
|
34926
|
+
}
|
|
34927
|
+
for (const file of strays) {
|
|
34928
|
+
const to = destinationOf(file);
|
|
34929
|
+
ensureDir(path25.dirname(to));
|
|
34930
|
+
fs16.renameSync(file, to);
|
|
34931
|
+
moved.push(to);
|
|
34932
|
+
removeEmptyDirsUpTo(path25.dirname(file), parentSpecsDir);
|
|
34933
|
+
}
|
|
34934
|
+
rebaseImplementationPaths(partSpecsDir, parentRoot, partDir, moved);
|
|
34642
34935
|
if (!joined) projectConfigRepository.declareMember(alias, { source: relPath });
|
|
34643
34936
|
invalidateSpecCache();
|
|
34644
|
-
|
|
34937
|
+
return as === "project" ? promoteMember(alias) : void 0;
|
|
34938
|
+
}
|
|
34939
|
+
function subsystemFiles(subsystemId, specsDir) {
|
|
34940
|
+
const subPath = getSubsystemPath(subsystemId);
|
|
34941
|
+
const nested = subPath.endsWith(".index.yaml") && path25.resolve(path25.dirname(subPath)) === path25.resolve(path25.join(specsDir, subsystemId)) && fs16.existsSync(subPath);
|
|
34942
|
+
const home = nested ? path25.dirname(subPath) : void 0;
|
|
34943
|
+
const files = new Set(home !== void 0 ? specFilesUnder(home).map((f) => path25.resolve(f)) : []);
|
|
34944
|
+
if (!nested && fs16.existsSync(subPath)) files.add(path25.resolve(subPath));
|
|
34945
|
+
const l0 = path25.resolve(aiPathsAt(getProjectRoot()).specsSystem());
|
|
34946
|
+
const others = () => specFilesUnder(specsDir).filter((f) => path25.resolve(f) !== l0 && !files.has(path25.resolve(f)));
|
|
34947
|
+
const add2 = (file) => {
|
|
34948
|
+
if (path25.basename(file) === ".index.yaml") for (const f of specFilesUnder(path25.dirname(file))) files.add(path25.resolve(f));
|
|
34949
|
+
else files.add(path25.resolve(file));
|
|
34950
|
+
};
|
|
34951
|
+
for (const file of others()) {
|
|
34952
|
+
let raw;
|
|
34953
|
+
try {
|
|
34954
|
+
raw = readYamlFile(file);
|
|
34955
|
+
} catch {
|
|
34956
|
+
continue;
|
|
34957
|
+
}
|
|
34958
|
+
const kind = raw && typeof raw === "object" ? specKind(raw) : void 0;
|
|
34959
|
+
if (kind === "subsystem" || kind === "system" || raw?.subsystem !== subsystemId) continue;
|
|
34960
|
+
add2(file);
|
|
34961
|
+
}
|
|
34962
|
+
const owned = () => rawSpecs([...files]);
|
|
34963
|
+
const components = new Set(owned().filter((x) => x.kind === "component").map((x) => x.id));
|
|
34964
|
+
for (const spec of rawSpecs(others())) if (spec.kind === "interface" && components.has(spec.raw?.component)) add2(spec.file);
|
|
34965
|
+
const contracts = new Set(owned().filter((x) => x.kind === "interface").map((x) => x.id));
|
|
34966
|
+
for (const spec of rawSpecs(others())) if (spec.kind === "implementation" && contracts.has(spec.raw?.contract)) add2(spec.file);
|
|
34967
|
+
return { home, files: [...files] };
|
|
34968
|
+
}
|
|
34969
|
+
function removeEmptyDirsUpTo(dir, stop2) {
|
|
34970
|
+
let at = path25.resolve(dir);
|
|
34971
|
+
const end = path25.resolve(stop2);
|
|
34972
|
+
while (at !== end && isWithinDir2(end, at) && fs16.existsSync(at) && fs16.readdirSync(at).length === 0) {
|
|
34973
|
+
fs16.rmdirSync(at);
|
|
34974
|
+
at = path25.dirname(at);
|
|
34975
|
+
}
|
|
34645
34976
|
}
|
|
34646
34977
|
function specFilesUnder(dir) {
|
|
34647
34978
|
return fs16.existsSync(dir) ? listFilesRecursive(dir, ".yaml") : [];
|
|
@@ -34667,7 +34998,8 @@ function rawSpecs(files) {
|
|
|
34667
34998
|
return out;
|
|
34668
34999
|
}
|
|
34669
35000
|
function typeTokens(expression) {
|
|
34670
|
-
|
|
35001
|
+
if (typeof expression !== "string") return [];
|
|
35002
|
+
return [...expression.matchAll(/(?:::)?[A-Za-z_][A-Za-z0-9_-]*(?:(?:::|\.)[A-Za-z_][A-Za-z0-9_-]*)*/g)].map((m) => m[0]).filter((t) => !t.includes(".") && !t.includes("::"));
|
|
34671
35003
|
}
|
|
34672
35004
|
function referencesOf(spec, wholeParams) {
|
|
34673
35005
|
const out = [];
|
|
@@ -34676,13 +35008,13 @@ function referencesOf(spec, wholeParams) {
|
|
|
34676
35008
|
return ref;
|
|
34677
35009
|
});
|
|
34678
35010
|
const expressions = [];
|
|
34679
|
-
|
|
34680
|
-
|
|
34681
|
-
|
|
34682
|
-
|
|
34683
|
-
|
|
35011
|
+
for (const slot of typeExpressionSlots(spec.kind, spec.raw)) {
|
|
35012
|
+
const value = slot.holder[slot.key];
|
|
35013
|
+
if (slot.path === "methods.signatureFrom") continue;
|
|
35014
|
+
if (slot.path === "methods.signature" && Array.isArray(slot.method?.params)) continue;
|
|
35015
|
+
if (slot.path === "methods.params.type" && spec.kind === "interface" && !wholeParams && /^[A-Za-z_][A-Za-z0-9_-]*$/.test(String(value))) continue;
|
|
35016
|
+
expressions.push(slot.path === "methods.signature" ? String(value).replace(/^\s*[A-Za-z_][\w-]*\s*(?:<[^>]*>)?\s*\(/, "(").replace(/(?<![:\w-])[A-Za-z_][\w-]*\s*\??\s*:(?!:)/g, "") : value);
|
|
34684
35017
|
}
|
|
34685
|
-
if (spec.kind === "type") for (const f of spec.raw.fields ?? []) expressions.push(f?.type);
|
|
34686
35018
|
for (const token of expressions.flatMap(typeTokens)) out.push({ ref: token, position: "bare-type" });
|
|
34687
35019
|
return out;
|
|
34688
35020
|
}
|
|
@@ -34690,6 +35022,9 @@ function crossingsOf(inside, outside) {
|
|
|
34690
35022
|
const idsOf = (specs) => new Set(specs.filter((s) => s.kind !== "system").map((s) => s.id));
|
|
34691
35023
|
const insideIds = idsOf(inside);
|
|
34692
35024
|
const outsideIds = idsOf(outside);
|
|
35025
|
+
const targetsOf2 = (specs) => new Set(specs.filter((s) => s.kind === "component" || s.kind === "interface" || s.kind === "type").map((s) => s.id));
|
|
35026
|
+
const insideTargets = targetsOf2(inside);
|
|
35027
|
+
const outsideTargets = targetsOf2(outside);
|
|
34693
35028
|
const typeIds = (specs) => new Map(specs.filter((s) => s.kind === "type").map((s) => [nameKey(s.id), s.id]));
|
|
34694
35029
|
const insideTypes = typeIds(inside);
|
|
34695
35030
|
const outsideTypes = typeIds(outside);
|
|
@@ -34707,13 +35042,14 @@ function crossingsOf(inside, outside) {
|
|
|
34707
35042
|
}
|
|
34708
35043
|
return found;
|
|
34709
35044
|
};
|
|
34710
|
-
return { out: collect2(inside,
|
|
35045
|
+
return { out: collect2(inside, outsideTargets, outsideTypes, true), in: collect2(outside, insideTargets, insideTypes, false), insideIds, outsideIds, insideTargets, outsideTargets };
|
|
34711
35046
|
}
|
|
34712
35047
|
function promoteRefusals(family, insideFiles, outsideFiles, id) {
|
|
34713
35048
|
const out = [];
|
|
34714
35049
|
const inside = rawSpecs(insideFiles);
|
|
34715
35050
|
const outside = rawSpecs(outsideFiles);
|
|
34716
35051
|
const crossing = crossingsOf(inside, outside);
|
|
35052
|
+
out.push(...unreachableUpward(inside, outside));
|
|
34717
35053
|
const subsystemsIn = new Set(inside.filter((s) => s.kind === "subsystem").map((s) => s.id));
|
|
34718
35054
|
for (const s of [...inside, ...outside].filter((x) => x.kind === "subsystem")) {
|
|
34719
35055
|
for (const link of s.raw.trustedLinks ?? []) {
|
|
@@ -34754,13 +35090,19 @@ function climbsOf(family, ids, parentId) {
|
|
|
34754
35090
|
}
|
|
34755
35091
|
return { map, refused: [...new Set(refused2)] };
|
|
34756
35092
|
}
|
|
34757
|
-
function rewriteRefsIn(specs, remap) {
|
|
35093
|
+
function rewriteRefsIn(specs, remap, record2) {
|
|
34758
35094
|
const rewritten = [];
|
|
34759
35095
|
for (const spec of specs) {
|
|
34760
35096
|
const raw = readYamlFile(spec.file);
|
|
34761
|
-
|
|
35097
|
+
const edits = [];
|
|
35098
|
+
if (!rewriteSpecRefs(raw, (ref, position) => {
|
|
35099
|
+
const to = remap(ref, position);
|
|
35100
|
+
if (to !== ref) edits.push({ kind: spec.kind, specId: spec.id, position, from: ref, to });
|
|
35101
|
+
return to;
|
|
35102
|
+
})) continue;
|
|
34762
35103
|
writeYamlFile(spec.file, raw);
|
|
34763
35104
|
rewritten.push({ kind: spec.kind, id: spec.id });
|
|
35105
|
+
if (record2) for (const e of edits) recordRespelling(record2.result, e, record2.member);
|
|
34764
35106
|
}
|
|
34765
35107
|
return rewritten;
|
|
34766
35108
|
}
|
|
@@ -34810,15 +35152,19 @@ function promoteMember(alias, id) {
|
|
|
34810
35152
|
patchKeepingStamp(s.file, (raw) => raw.parentSystem === projectName ? false : (raw.parentSystem = projectName, true));
|
|
34811
35153
|
}
|
|
34812
35154
|
if (declaration2?.as === "part") projectConfigRepository.updateMember(alias, { as: "" });
|
|
34813
|
-
const
|
|
35155
|
+
const result = { respelled: [], memberRespelled: [], exported: [], imported: [], consumersDropped: [] };
|
|
35156
|
+
const parentSide = rewriteRefsIn(crossing.in.map((c) => c.spec).filter(unique), (ref, position) => INTO_MEMBER_POSITIONS.has(position) && crossing.insideTargets.has(ref) ? `${alias}::${ref}` : ref, { result, member: false });
|
|
34814
35157
|
const climbs = climbsOf(family, crossing.insideIds, parentId).map;
|
|
34815
35158
|
const climbing = inside.filter((s) => family.authoredReferences.some((r) => r.form === "super" && r.specId === s.id));
|
|
34816
|
-
const memberSide = rewriteRefsIn([...crossing.out.map((c) => c.spec), ...climbing].filter(unique), (ref, position) => climbs.get(ref) ?? (MOVED_REF_POSITIONS.has(position) && crossing.
|
|
35159
|
+
const memberSide = rewriteRefsIn([...crossing.out.map((c) => c.spec), ...climbing].filter(unique), (ref, position) => climbs.get(ref) ?? (MOVED_REF_POSITIONS.has(position) && crossing.outsideTargets.has(ref) ? `${parentId}::${ref}` : ref), { result, member: true });
|
|
35160
|
+
const qualified = respellQualified(inside, outside, { alias, parentId, newId }, result);
|
|
34817
35161
|
invalidateSpecCache();
|
|
34818
|
-
|
|
35162
|
+
dropCrossingConsumers(inside, outside, result);
|
|
35163
|
+
if (crossing.out.length > 0 || qualified.out.length > 0 || [...climbs.values()].some((t) => t.startsWith(`${parentId}::`))) runWithProjectRoot(dir, () => projectConfigRepository.declareExternal(parentId, {}));
|
|
34819
35164
|
for (const s of inside.filter((x) => x.kind === "subsystem")) restateReExports(s.id, alias);
|
|
34820
|
-
exportCrossings(crossing.out, dir, alias, parentId, "out");
|
|
34821
|
-
exportCrossings(crossing.in, dir, alias, parentId, "in");
|
|
35165
|
+
exportCrossings([...crossing.out, ...qualified.out], dir, alias, parentId, "out", result);
|
|
35166
|
+
exportCrossings([...crossing.in, ...qualified.in], dir, alias, parentId, "in", result);
|
|
35167
|
+
exportReExported(inside, dir, alias, result);
|
|
34822
35168
|
invalidateSpecCache();
|
|
34823
35169
|
for (const spec of parentSide) if (spec.kind !== "system") normalizeReferences(spec.kind, spec.id);
|
|
34824
35170
|
const memberKey = graph().nodes.find((n) => n.parent === "" && n.mountAlias === alias)?.namespace;
|
|
@@ -34837,6 +35183,165 @@ function promoteMember(alias, id) {
|
|
|
34837
35183
|
});
|
|
34838
35184
|
}
|
|
34839
35185
|
invalidateSpecCache();
|
|
35186
|
+
return result;
|
|
35187
|
+
}
|
|
35188
|
+
function recordRespelling(result, edit4, member) {
|
|
35189
|
+
if (edit4.from === edit4.to) return;
|
|
35190
|
+
const list2 = member ? result.memberRespelled : result.respelled;
|
|
35191
|
+
if (list2.some((e) => e.kind === edit4.kind && e.specId === edit4.specId && e.position === edit4.position && e.from === edit4.from)) return;
|
|
35192
|
+
list2.push(edit4);
|
|
35193
|
+
}
|
|
35194
|
+
function assertedTypeRef(ref) {
|
|
35195
|
+
const at = ref.lastIndexOf(".");
|
|
35196
|
+
return at > 0 && at < ref.length - 1 ? ref.slice(0, at) : void 0;
|
|
35197
|
+
}
|
|
35198
|
+
function sidedTypes(inside, outside) {
|
|
35199
|
+
const types = [
|
|
35200
|
+
...inside.filter((s) => s.kind === "type").map((s) => ({ spec: s, side: "inside" })),
|
|
35201
|
+
...outside.filter((s) => s.kind === "type").map((s) => ({ spec: s, side: "outside" }))
|
|
35202
|
+
];
|
|
35203
|
+
const subsystems = new Set([...inside, ...outside].filter((s) => s.kind === "subsystem").map((s) => nameKey(s.id)));
|
|
35204
|
+
return {
|
|
35205
|
+
qualified: (token) => {
|
|
35206
|
+
const segments = token.split(/::|\./);
|
|
35207
|
+
if (segments.length !== 2 || segments.some((x) => x === "") || !subsystems.has(nameKey(segments[0]))) return void 0;
|
|
35208
|
+
const found = types.filter((t) => typeof t.spec.raw.subsystem === "string" && nameKey(t.spec.raw.subsystem) === nameKey(segments[0]) && nameKey(t.spec.id) === nameKey(segments[1]));
|
|
35209
|
+
return found.length === 1 ? found[0] : void 0;
|
|
35210
|
+
},
|
|
35211
|
+
bare: (token) => {
|
|
35212
|
+
const found = types.filter((t) => nameKey(t.spec.id) === nameKey(token));
|
|
35213
|
+
return found.length === 1 ? found[0] : void 0;
|
|
35214
|
+
}
|
|
35215
|
+
};
|
|
35216
|
+
}
|
|
35217
|
+
function unreachableUpward(inside, outside) {
|
|
35218
|
+
const out = [];
|
|
35219
|
+
const types = sidedTypes(inside, outside);
|
|
35220
|
+
const outsideComponents = new Set(outside.filter((x) => x.kind === "component").map((x) => x.id));
|
|
35221
|
+
const target = (ref) => ref.includes(".") || ref.includes("::") ? types.qualified(ref) : types.bare(ref);
|
|
35222
|
+
for (const spec of inside) {
|
|
35223
|
+
if (spec.kind === "interface") {
|
|
35224
|
+
for (const m of spec.raw.methods ?? []) {
|
|
35225
|
+
const source = m?.signatureFrom;
|
|
35226
|
+
if (typeof source !== "string") continue;
|
|
35227
|
+
const head2 = source.includes(".") ? source.slice(0, source.lastIndexOf(".")) : void 0;
|
|
35228
|
+
if (target(source)?.side === "outside" || head2 !== void 0 && outsideComponents.has(head2)) {
|
|
35229
|
+
out.push(`"${spec.id}" method "${m.name}" takes its signature from "${source}", which stays in this project: a signatureFrom is not read across a project boundary \u2014 state its params and returns first`);
|
|
35230
|
+
}
|
|
35231
|
+
}
|
|
35232
|
+
}
|
|
35233
|
+
if (spec.kind === "implementation") {
|
|
35234
|
+
for (const m of spec.raw.methods ?? []) {
|
|
35235
|
+
for (const step of m?.narrative ?? []) {
|
|
35236
|
+
for (const ref of step?.assertsInvariants ?? []) {
|
|
35237
|
+
const typeRef2 = typeof ref === "string" ? assertedTypeRef(ref) : void 0;
|
|
35238
|
+
if (typeRef2 !== void 0 && !typeRef2.includes("::") && target(typeRef2)?.side === "outside") {
|
|
35239
|
+
out.push(`"${spec.id}" asserts the invariant "${ref}" of an entity that stays in this project: an invariant is asserted by its own project's write paths \u2014 move the assertion first`);
|
|
35240
|
+
}
|
|
35241
|
+
}
|
|
35242
|
+
}
|
|
35243
|
+
}
|
|
35244
|
+
}
|
|
35245
|
+
}
|
|
35246
|
+
return out;
|
|
35247
|
+
}
|
|
35248
|
+
function respellQualified(inside, outside, names, result) {
|
|
35249
|
+
const crossings = { out: [], in: [] };
|
|
35250
|
+
const { qualified: qualifiedTarget, bare: bareTarget } = sidedTypes(inside, outside);
|
|
35251
|
+
const respell = (spec, side, token, target, selfPrefix2) => {
|
|
35252
|
+
if (!target) return token;
|
|
35253
|
+
if (target.side !== side) {
|
|
35254
|
+
const to = `${side === "outside" ? names.alias : names.parentId}::${target.spec.id}`;
|
|
35255
|
+
(side === "inside" ? crossings.out : crossings.in).push({ spec, target: target.spec.id, position: "type" });
|
|
35256
|
+
return to;
|
|
35257
|
+
}
|
|
35258
|
+
return side === "inside" && selfPrefix2 ? target.spec.id : token;
|
|
35259
|
+
};
|
|
35260
|
+
for (const [specs, side] of [[inside, "inside"], [outside, "outside"]]) {
|
|
35261
|
+
for (const spec of specs) {
|
|
35262
|
+
if (!fs16.existsSync(spec.file)) continue;
|
|
35263
|
+
const raw = readYamlFile(spec.file);
|
|
35264
|
+
if (!raw || typeof raw !== "object") continue;
|
|
35265
|
+
const edits = [];
|
|
35266
|
+
const record2 = (from, to) => {
|
|
35267
|
+
if (from !== to) edits.push({ kind: spec.kind, specId: spec.id, position: "type", from, to });
|
|
35268
|
+
};
|
|
35269
|
+
const mapToken = (token) => {
|
|
35270
|
+
const target = qualifiedTarget(token);
|
|
35271
|
+
const to = respell(spec, side, token, target, nameKey(token.split(/::|\./)[0]) === nameKey(names.newId));
|
|
35272
|
+
record2(token, to);
|
|
35273
|
+
return to;
|
|
35274
|
+
};
|
|
35275
|
+
for (const slot of typeExpressionSlots(spec.kind, raw)) {
|
|
35276
|
+
const value = slot.holder[slot.key];
|
|
35277
|
+
if (slot.path === "methods.signatureFrom" && !qualifiedTarget(value)) continue;
|
|
35278
|
+
slot.holder[slot.key] = respellTypeNames(value, mapToken);
|
|
35279
|
+
}
|
|
35280
|
+
if (spec.kind === "implementation") {
|
|
35281
|
+
for (const m of raw.methods ?? []) {
|
|
35282
|
+
for (const step of m?.narrative ?? []) {
|
|
35283
|
+
if (!Array.isArray(step?.assertsInvariants)) continue;
|
|
35284
|
+
step.assertsInvariants = step.assertsInvariants.map((ref) => {
|
|
35285
|
+
const typeRef2 = typeof ref === "string" ? assertedTypeRef(ref) : void 0;
|
|
35286
|
+
if (typeRef2 === void 0 || typeRef2.includes("::") && !qualifiedTarget(typeRef2)) return ref;
|
|
35287
|
+
const target = typeRef2.includes(".") || typeRef2.includes("::") ? qualifiedTarget(typeRef2) : bareTarget(typeRef2);
|
|
35288
|
+
const to = respell(spec, side, typeRef2, target, typeRef2.includes(".") && nameKey(typeRef2.split(".")[0]) === nameKey(names.newId));
|
|
35289
|
+
if (to === typeRef2) return ref;
|
|
35290
|
+
record2(ref, `${to}${ref.slice(typeRef2.length)}`);
|
|
35291
|
+
return `${to}${ref.slice(typeRef2.length)}`;
|
|
35292
|
+
});
|
|
35293
|
+
}
|
|
35294
|
+
}
|
|
35295
|
+
}
|
|
35296
|
+
if (edits.length === 0) continue;
|
|
35297
|
+
writeYamlFile(spec.file, raw);
|
|
35298
|
+
for (const e of edits) recordRespelling(result, e, side === "inside");
|
|
35299
|
+
}
|
|
35300
|
+
}
|
|
35301
|
+
return crossings;
|
|
35302
|
+
}
|
|
35303
|
+
function dropCrossingConsumers(inside, outside, result) {
|
|
35304
|
+
const idsOf = (specs) => new Set(specs.filter((s) => s.kind === "subsystem").map((s) => s.id));
|
|
35305
|
+
const sides = [[inside, idsOf(outside)], [outside, idsOf(inside)]];
|
|
35306
|
+
for (const [specs, across] of sides) {
|
|
35307
|
+
for (const spec of specs.filter((s) => s.kind === "subsystem")) {
|
|
35308
|
+
patchKeepingStamp(spec.file, (raw) => {
|
|
35309
|
+
let changed = false;
|
|
35310
|
+
for (const entry of raw.publicInterfaces ?? []) {
|
|
35311
|
+
if (!Array.isArray(entry?.consumers)) continue;
|
|
35312
|
+
const dropped = entry.consumers.filter((c) => typeof c === "string" && across.has(c));
|
|
35313
|
+
if (dropped.length === 0) continue;
|
|
35314
|
+
const kept = entry.consumers.filter((c) => !dropped.includes(c));
|
|
35315
|
+
if (kept.length > 0) entry.consumers = kept;
|
|
35316
|
+
else delete entry.consumers;
|
|
35317
|
+
changed = true;
|
|
35318
|
+
const what = entry.component ?? entry.typeDef ?? entry.interface ?? entry.from ?? "(entry)";
|
|
35319
|
+
result.consumersDropped.push(`${spec.id} ${what}: ${dropped.join(", ")}${kept.length === 0 ? " (the list is gone: the L0 export table governs who outside may use it)" : ""}`);
|
|
35320
|
+
}
|
|
35321
|
+
return changed;
|
|
35322
|
+
});
|
|
35323
|
+
}
|
|
35324
|
+
}
|
|
35325
|
+
}
|
|
35326
|
+
function exportReExported(inside, memberDir, alias, result) {
|
|
35327
|
+
invalidateSpecCache();
|
|
35328
|
+
const entries = (loadSystemSpec()?.publicInterfaces ?? []).filter((e) => e.from === alias);
|
|
35329
|
+
if (entries.length === 0) return;
|
|
35330
|
+
const targets = entries.map((e) => e.component ?? e.typeDef).filter((t) => t !== void 0);
|
|
35331
|
+
const whole = entries.some((e) => e.component === void 0 && e.typeDef === void 0);
|
|
35332
|
+
runWithProjectRoot(memberDir, () => {
|
|
35333
|
+
for (const name of exportItems(targets)) result.exported.push(`${alias} L0: ${name} (re-exported by the parent)`);
|
|
35334
|
+
if (!whole) return;
|
|
35335
|
+
invalidateSpecCache();
|
|
35336
|
+
const system = loadSystemSpec();
|
|
35337
|
+
if (!system) return;
|
|
35338
|
+
const own = system.publicInterfaces ?? [];
|
|
35339
|
+
const added = inside.filter((s) => s.kind === "subsystem" && !own.some((e) => e.from === s.id && e.component === void 0 && e.typeDef === void 0)).map((s) => ({ from: s.id, audience: "project" }));
|
|
35340
|
+
if (added.length === 0) return;
|
|
35341
|
+
saveSystemSpec({ ...system, publicInterfaces: [...own, ...added] });
|
|
35342
|
+
for (const e of added) result.exported.push(`${alias} L0: everything ${e.from} exports (re-exported whole by the parent)`);
|
|
35343
|
+
});
|
|
35344
|
+
invalidateSpecCache();
|
|
34840
35345
|
}
|
|
34841
35346
|
function promotedVision(id, parentName) {
|
|
34842
35347
|
return `Project ${id}, promoted from a part of ${parentName}`;
|
|
@@ -34844,43 +35349,57 @@ function promotedVision(id, parentName) {
|
|
|
34844
35349
|
function unique(value, index, all) {
|
|
34845
35350
|
return all.indexOf(value) === index;
|
|
34846
35351
|
}
|
|
34847
|
-
function exportCrossings(crossings, memberDir, alias, parentId, direction) {
|
|
35352
|
+
function exportCrossings(crossings, memberDir, alias, parentId, direction, result) {
|
|
34848
35353
|
const targets = [...new Set(crossings.map((c) => c.target))];
|
|
34849
35354
|
const bare = [...new Set(crossings.filter((c) => c.position === "bare-type").map((c) => c.target))];
|
|
34850
35355
|
if (direction === "out") {
|
|
34851
|
-
exportItems(targets);
|
|
34852
|
-
if (bare.length > 0)
|
|
35356
|
+
for (const name of exportItems(targets)) result.exported.push(`${parentId} L0: ${name}`);
|
|
35357
|
+
if (bare.length > 0) {
|
|
35358
|
+
projectConfigRepositoryAt(memberDir).importNames(parentId, bare);
|
|
35359
|
+
result.imported.push(`${alias}: externals.${parentId}.use gains ${bare.join(", ")}`);
|
|
35360
|
+
}
|
|
34853
35361
|
} else {
|
|
34854
|
-
runWithProjectRoot(memberDir, () => exportItems(targets));
|
|
34855
|
-
if (bare.length > 0)
|
|
35362
|
+
for (const name of runWithProjectRoot(memberDir, () => exportItems(targets))) result.exported.push(`${alias} L0: ${name}`);
|
|
35363
|
+
if (bare.length > 0) {
|
|
35364
|
+
projectConfigRepository.importNames(alias, bare);
|
|
35365
|
+
result.imported.push(`${parentId}: members.${alias}.use gains ${bare.join(", ")}`);
|
|
35366
|
+
}
|
|
34856
35367
|
}
|
|
34857
35368
|
}
|
|
34858
35369
|
function localIn(key, project2) {
|
|
34859
35370
|
return project2 && key.startsWith(`${project2}::`) ? key.slice(project2.length + 2) : key;
|
|
34860
35371
|
}
|
|
34861
35372
|
function exportItems(ids) {
|
|
34862
|
-
if (ids.length === 0) return;
|
|
35373
|
+
if (ids.length === 0) return [];
|
|
34863
35374
|
invalidateSpecCache();
|
|
34864
35375
|
const system = loadSystemSpec();
|
|
34865
|
-
if (!system) return;
|
|
35376
|
+
if (!system) return [];
|
|
34866
35377
|
const entries = [...system.publicInterfaces ?? []];
|
|
34867
35378
|
const types = loadTypeSpecs();
|
|
35379
|
+
const added = [];
|
|
34868
35380
|
for (const id of ids) {
|
|
34869
35381
|
if (!PUBLIC_NAME_RE.test(id)) continue;
|
|
34870
35382
|
const type = types.find((t) => t.id === id);
|
|
34871
35383
|
if (type) {
|
|
34872
35384
|
if (type.subsystem) exportTypeFromSubsystem(type.subsystem, id);
|
|
34873
|
-
if (!entries.some((e) => e.typeDef === id && e.from === void 0
|
|
35385
|
+
if (!entries.some((e) => e.typeDef === id && (e.from === void 0 || e.from === type.subsystem))) {
|
|
35386
|
+
entries.push({ ...type.subsystem ? { from: type.subsystem } : {}, typeDef: id, audience: "project" });
|
|
35387
|
+
added.push(type.subsystem ? `${id} (from ${type.subsystem})` : id);
|
|
35388
|
+
}
|
|
34874
35389
|
continue;
|
|
34875
35390
|
}
|
|
34876
35391
|
const component = loadComponentSpec(id);
|
|
34877
35392
|
if (!component) continue;
|
|
34878
35393
|
const exported = resolveSubsystemExports(component.subsystem).entries.some((e) => e.kind === "component" && e.component === id);
|
|
34879
|
-
if (exported && !entries.some((e) => e.component === id))
|
|
35394
|
+
if (exported && !entries.some((e) => e.component === id)) {
|
|
35395
|
+
entries.push({ from: component.subsystem, component: id, audience: "project" });
|
|
35396
|
+
added.push(`${id} (from ${component.subsystem})`);
|
|
35397
|
+
}
|
|
34880
35398
|
}
|
|
34881
35399
|
if (entries.length !== (system.publicInterfaces ?? []).length) {
|
|
34882
35400
|
saveSystemSpec({ ...system, publicInterfaces: entries });
|
|
34883
35401
|
}
|
|
35402
|
+
return added;
|
|
34884
35403
|
}
|
|
34885
35404
|
function exportTypeFromSubsystem(subsystemId, typeId) {
|
|
34886
35405
|
const sub = loadSubsystemSpec(subsystemId);
|
|
@@ -34910,7 +35429,7 @@ function internalizeMember(alias, destination) {
|
|
|
34910
35429
|
if (refusals.length > 0) {
|
|
34911
35430
|
throw new WaironError(`cannot internalize "${alias}": the member cannot be taken in whole \u2014 ${refusals.join("; ")}.`);
|
|
34912
35431
|
}
|
|
34913
|
-
const result = { subsystems: scan.subsystems, types: scan.types, placed: [], externals: [], deleted: [], notCarried: [] };
|
|
35432
|
+
const result = { subsystems: scan.subsystems, types: scan.types, placed: [], externals: [], deleted: [], notCarried: [], consumersRestored: [] };
|
|
34914
35433
|
const intoMember = crossingReferences(family, "", member.namespace);
|
|
34915
35434
|
const backOut = crossingReferences(family, member.namespace, "");
|
|
34916
35435
|
deleteMount(alias);
|
|
@@ -34940,7 +35459,7 @@ function internalizePart(alias, part) {
|
|
|
34940
35459
|
projectConfigRepository.removeMember(alias);
|
|
34941
35460
|
const deleted = deleteMemberWai(partDir);
|
|
34942
35461
|
invalidateSpecCache();
|
|
34943
|
-
return { subsystems: part.subsystems, types, placed: [], externals: [], deleted, notCarried: [] };
|
|
35462
|
+
return { subsystems: part.subsystems, types, placed: [], externals: [], deleted, notCarried: [], consumersRestored: [] };
|
|
34944
35463
|
}
|
|
34945
35464
|
function scanMember(family, alias, member, destination) {
|
|
34946
35465
|
const root = getProjectRoot();
|
|
@@ -35288,7 +35807,7 @@ function demoteMember(alias, destination) {
|
|
|
35288
35807
|
refusals.push(`its metadata needs a home subsystem (--home) \u2014 ${home === "" ? "none was named" : `"${home}" is no subsystem of the member or of this project`}; its subsystems are ${subsystems.join(", ") || "none"}`);
|
|
35289
35808
|
}
|
|
35290
35809
|
if (refusals.length > 0) throw new WaironError(`demote refused: "${alias}" cannot become a part \u2014 ${refusals.join("; ")}.`);
|
|
35291
|
-
const result = { subsystems, types, placed: [], externals: [], deleted: [], notCarried: [] };
|
|
35810
|
+
const result = { subsystems, types, placed: [], externals: [], deleted: [], notCarried: [], consumersRestored: [] };
|
|
35292
35811
|
const intoMember = localsOf(dir);
|
|
35293
35812
|
const backOut = localsOf(root);
|
|
35294
35813
|
invalidateSpecCache();
|
|
@@ -35305,6 +35824,10 @@ function demoteMember(alias, destination) {
|
|
|
35305
35824
|
const parentSide = rewriteRefsIn(ownSpecs, across(/* @__PURE__ */ new Set([alias]), intoMember, false));
|
|
35306
35825
|
const memberSide = rewriteRefsIn(memberSpecs, across(parentAliases, backOut, true));
|
|
35307
35826
|
for (const pa of parentAliases) for (const name of config?.externals?.[pa]?.use ?? []) if (name !== "*") crossed.add(backOut.get(name) ?? name);
|
|
35827
|
+
const memberNow = rawSpecs(specFilesUnder(aiPathsAt(dir).specsDir()).filter((f) => path25.resolve(f) !== path25.resolve(aiPathsAt(dir).specsSystem())));
|
|
35828
|
+
const parentNow = rawSpecs(ownSpecFiles(family, aiPathsAt(dir).specsDir()));
|
|
35829
|
+
respellQualifiedBack(parentNow, memberNow, { intoMember: (a) => a === alias, backOut: (a) => parentAliases.has(a) }, { intoMember, backOut }, crossed);
|
|
35830
|
+
restoreCrossingConsumers(memberNow, parentNow, result);
|
|
35308
35831
|
const parentName = loadSystemSpec()?.name;
|
|
35309
35832
|
if (parentName) {
|
|
35310
35833
|
for (const s of memberSpecs.filter((x) => x.kind === "subsystem")) {
|
|
@@ -35329,6 +35852,79 @@ function demoteMember(alias, destination) {
|
|
|
35329
35852
|
invalidateSpecCache();
|
|
35330
35853
|
return result;
|
|
35331
35854
|
}
|
|
35855
|
+
function respellQualifiedBack(parentSide, memberSide, aliases, tables, crossed) {
|
|
35856
|
+
const types = [...parentSide, ...memberSide].filter((s) => s.kind === "type");
|
|
35857
|
+
const localForm = (id) => {
|
|
35858
|
+
const found = types.filter((t) => t.id === id);
|
|
35859
|
+
if (found.length !== 1) return void 0;
|
|
35860
|
+
const sub = found[0].raw?.subsystem;
|
|
35861
|
+
return typeof sub === "string" && sub !== "" ? `${sub}.${id}` : id;
|
|
35862
|
+
};
|
|
35863
|
+
for (const [specs, across, table, record2] of [
|
|
35864
|
+
[parentSide, aliases.intoMember, tables.intoMember, false],
|
|
35865
|
+
[memberSide, aliases.backOut, tables.backOut, true]
|
|
35866
|
+
]) {
|
|
35867
|
+
const local = (token) => {
|
|
35868
|
+
const segments = token.split("::");
|
|
35869
|
+
if (segments.length !== 2 || !across(segments[0])) return token;
|
|
35870
|
+
const id = table.get(segments[1]) ?? segments[1];
|
|
35871
|
+
const to = localForm(id);
|
|
35872
|
+
if (to === void 0) return token;
|
|
35873
|
+
if (record2) crossed.add(id);
|
|
35874
|
+
return to;
|
|
35875
|
+
};
|
|
35876
|
+
for (const spec of specs) {
|
|
35877
|
+
if (!fs16.existsSync(spec.file)) continue;
|
|
35878
|
+
const raw = readYamlFile(spec.file);
|
|
35879
|
+
if (!raw || typeof raw !== "object") continue;
|
|
35880
|
+
let changed = false;
|
|
35881
|
+
for (const slot of typeExpressionSlots(spec.kind, raw)) {
|
|
35882
|
+
const value = slot.holder[slot.key];
|
|
35883
|
+
const next = respellTypeNames(value, local);
|
|
35884
|
+
if (next !== value) {
|
|
35885
|
+
slot.holder[slot.key] = next;
|
|
35886
|
+
changed = true;
|
|
35887
|
+
}
|
|
35888
|
+
}
|
|
35889
|
+
if (spec.kind === "implementation") {
|
|
35890
|
+
for (const m of raw.methods ?? []) {
|
|
35891
|
+
for (const step of m?.narrative ?? []) {
|
|
35892
|
+
if (!Array.isArray(step?.assertsInvariants)) continue;
|
|
35893
|
+
step.assertsInvariants = step.assertsInvariants.map((ref) => {
|
|
35894
|
+
const typeRef2 = typeof ref === "string" ? assertedTypeRef(ref) : void 0;
|
|
35895
|
+
if (typeRef2 === void 0 || !typeRef2.includes("::")) return ref;
|
|
35896
|
+
const to = local(typeRef2);
|
|
35897
|
+
if (to === typeRef2) return ref;
|
|
35898
|
+
changed = true;
|
|
35899
|
+
return `${to}${ref.slice(typeRef2.length)}`;
|
|
35900
|
+
});
|
|
35901
|
+
}
|
|
35902
|
+
}
|
|
35903
|
+
}
|
|
35904
|
+
if (changed) writeYamlFile(spec.file, raw);
|
|
35905
|
+
}
|
|
35906
|
+
}
|
|
35907
|
+
}
|
|
35908
|
+
function restoreCrossingConsumers(memberSide, parentSide, result) {
|
|
35909
|
+
const sides = [[memberSide, parentSide], [parentSide, memberSide]];
|
|
35910
|
+
for (const [providers, consumers] of sides) {
|
|
35911
|
+
const dependents = consumers.filter((c) => c.kind === "component" && typeof c.raw?.subsystem === "string" && Array.isArray(c.raw?.dependsOn));
|
|
35912
|
+
for (const spec of providers.filter((x) => x.kind === "subsystem")) {
|
|
35913
|
+
patchKeepingStamp(spec.file, (raw) => {
|
|
35914
|
+
let changed = false;
|
|
35915
|
+
for (const entry of raw.publicInterfaces ?? []) {
|
|
35916
|
+
if (!Array.isArray(entry?.consumers) || entry.consumers.length === 0 || typeof entry.component !== "string") continue;
|
|
35917
|
+
const users = [...new Set(dependents.filter((c) => c.raw.dependsOn.includes(entry.component)).map((c) => c.raw.subsystem))].filter((sub) => sub !== spec.id && !entry.consumers.includes(sub));
|
|
35918
|
+
if (users.length === 0) continue;
|
|
35919
|
+
entry.consumers = [...entry.consumers, ...users];
|
|
35920
|
+
changed = true;
|
|
35921
|
+
result.consumersRestored.push(`${spec.id} ${entry.component}: ${users.join(", ")}`);
|
|
35922
|
+
}
|
|
35923
|
+
return changed;
|
|
35924
|
+
});
|
|
35925
|
+
}
|
|
35926
|
+
}
|
|
35927
|
+
}
|
|
35332
35928
|
function deleteProjectContent(memberDir) {
|
|
35333
35929
|
const wai = path25.join(memberDir, ".wai");
|
|
35334
35930
|
const deleted = [];
|
|
@@ -35358,9 +35954,9 @@ function retireBoundaryExports(family, used, memberKey) {
|
|
|
35358
35954
|
const { publicInterfaces: _dropped, ...rest } = system;
|
|
35359
35955
|
saveSystemSpec(kept.length > 0 ? { ...rest, publicInterfaces: kept } : rest);
|
|
35360
35956
|
invalidateSpecCache();
|
|
35361
|
-
for (const e of retired.filter((x) => x.typeDef !== void 0
|
|
35957
|
+
for (const e of retired.filter((x) => x.typeDef !== void 0)) {
|
|
35362
35958
|
const type = loadTypeSpecs().find((t) => t.id === e.typeDef);
|
|
35363
|
-
if (!type?.subsystem) continue;
|
|
35959
|
+
if (!type?.subsystem || e.from !== void 0 && e.from !== type.subsystem) continue;
|
|
35364
35960
|
patchKeepingStamp(getSubsystemPath(type.subsystem), (raw) => {
|
|
35365
35961
|
const before = (raw.publicInterfaces ?? []).length;
|
|
35366
35962
|
raw.publicInterfaces = (raw.publicInterfaces ?? []).filter((pi) => !(pi?.typeDef === e.typeDef && pi?.from === void 0 && Object.keys(pi).length === 1));
|
|
@@ -35529,7 +36125,7 @@ function renameComponent(componentId, newId) {
|
|
|
35529
36125
|
}
|
|
35530
36126
|
if (componentId.includes("::")) {
|
|
35531
36127
|
throw new WaironError(
|
|
35532
|
-
`chained-component: "${componentId}" lives in
|
|
36128
|
+
`chained-component: "${componentId}" lives in another project; rename it from that project's own root. ${memberRootWay(componentId, `sdd_rename_component ${componentId.slice(componentId.lastIndexOf("::") + 2)} <new id>`)}`
|
|
35533
36129
|
);
|
|
35534
36130
|
}
|
|
35535
36131
|
if (!SpecIdSchema.safeParse(newId).success) {
|
|
@@ -35680,7 +36276,7 @@ function renameMethod(componentId, methodName, newName, pinSymbol) {
|
|
|
35680
36276
|
}
|
|
35681
36277
|
if (componentId.includes("::")) {
|
|
35682
36278
|
throw new WaironError(
|
|
35683
|
-
`chained-component: "${componentId}" lives in
|
|
36279
|
+
`chained-component: "${componentId}" lives in another project; rename its method from that project's own root. ${memberRootWay(componentId, `sdd_rename_method ${componentId.slice(componentId.lastIndexOf("::") + 2)} <method> <new name>`)}`
|
|
35684
36280
|
);
|
|
35685
36281
|
}
|
|
35686
36282
|
if (!METHOD_NAME.test(newName)) {
|
|
@@ -35838,18 +36434,29 @@ function respellLike(written, oldId, newId) {
|
|
|
35838
36434
|
if (written.includes("-")) return kebab(newId);
|
|
35839
36435
|
return /^[A-Z]/.test(written) ? pascal(newId) : newId;
|
|
35840
36436
|
}
|
|
36437
|
+
function memberRootWay(qualifiedId2, call) {
|
|
36438
|
+
const alias = qualifiedId2.slice(0, qualifiedId2.indexOf("::"));
|
|
36439
|
+
let folder;
|
|
36440
|
+
try {
|
|
36441
|
+
const node = graph().nodes.find((n) => n.namespace === alias || n.parent === "" && n.mountAlias === alias);
|
|
36442
|
+
if (node) folder = path25.relative(getProjectRoot(), node.directory).split(path25.sep).join("/") || ".";
|
|
36443
|
+
} catch {
|
|
36444
|
+
}
|
|
36445
|
+
const where = folder ? `the member project "${alias}" at ${folder}` : `the member project "${alias}"`;
|
|
36446
|
+
return `It belongs to ${where}: open a session in that folder (its own guide and .mcp.json \u2014 run \`wairon generate\` and \`wairon mcp install --backend claude\` there first if it has none) and call ${call} there.`;
|
|
36447
|
+
}
|
|
35841
36448
|
function renameType(typeId, newId) {
|
|
35842
36449
|
const all = loadTypeSpecs();
|
|
35843
36450
|
const cut = typeId.lastIndexOf("::");
|
|
35844
36451
|
const qualifier = cut >= 0 ? typeId.slice(0, cut) : void 0;
|
|
35845
36452
|
const ownSubsystem = qualifier !== void 0 && loadSubsystemSpecs().some((s) => s.id === qualifier);
|
|
35846
36453
|
if (qualifier !== void 0 && !ownSubsystem) {
|
|
35847
|
-
throw new WaironError(`chained-type: "${typeId}" lives in another project; rename it from that project's own root.`);
|
|
36454
|
+
throw new WaironError(`chained-type: "${typeId}" lives in another project; rename it from that project's own root. ${memberRootWay(typeId, `sdd_rename_type ${typeId.slice(cut + 2)} <new id>`)} An exported type keeps its public name through \`as\`, so this project's references do not break.`);
|
|
35848
36455
|
}
|
|
35849
36456
|
const type = qualifier !== void 0 ? all.find((t) => t.id === typeId.slice(cut + 2) && t.subsystem === qualifier) ?? null : loadTypeSpec(typeId);
|
|
35850
36457
|
if (!type) throw new WaironError(`type-missing: no type has the id "${typeId}".`);
|
|
35851
36458
|
if (type.id.includes("::")) {
|
|
35852
|
-
throw new WaironError(`chained-type: "${type.id}" lives in another project; rename it from that project's own root
|
|
36459
|
+
throw new WaironError(`chained-type: "${type.id}" lives in another project; rename it from that project's own root. ${memberRootWay(type.id, `sdd_rename_type ${type.id.slice(type.id.lastIndexOf("::") + 2)} <new id>`)}`);
|
|
35853
36460
|
}
|
|
35854
36461
|
const oldId = type.id;
|
|
35855
36462
|
if (!SpecIdSchema.safeParse(newId).success) {
|
|
@@ -36177,8 +36784,8 @@ function walkForPackages(projectRoot2, currentDir, depth, results) {
|
|
|
36177
36784
|
}
|
|
36178
36785
|
}
|
|
36179
36786
|
function pathToId(relPath) {
|
|
36180
|
-
const
|
|
36181
|
-
return
|
|
36787
|
+
const basename19 = path26.basename(relPath);
|
|
36788
|
+
return basename19.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
36182
36789
|
}
|
|
36183
36790
|
function pathToName(relPath) {
|
|
36184
36791
|
const id = pathToId(relPath);
|
|
@@ -36708,7 +37315,7 @@ function isWithinDir3(dir, target) {
|
|
|
36708
37315
|
const rel2 = path29.relative(path29.resolve(dir), path29.resolve(target));
|
|
36709
37316
|
return rel2 === "" || !rel2.startsWith("..") && !path29.isAbsolute(rel2);
|
|
36710
37317
|
}
|
|
36711
|
-
function resolveLayer(delegate) {
|
|
37318
|
+
function resolveLayer(delegate, implementers = false) {
|
|
36712
37319
|
projectFilesCache.clear();
|
|
36713
37320
|
const system = loadSystemSpec();
|
|
36714
37321
|
if (!system) return [];
|
|
@@ -36839,7 +37446,7 @@ function resolveLayer(delegate) {
|
|
|
36839
37446
|
updatedAt: sub.updatedAt
|
|
36840
37447
|
});
|
|
36841
37448
|
}
|
|
36842
|
-
if (config.rules.generateComponentImplementers) {
|
|
37449
|
+
if (config.rules.generateComponentImplementers || implementers) {
|
|
36843
37450
|
for (const comp of components) {
|
|
36844
37451
|
const compInterfaces = interfaces.filter((i) => i.component === comp.id);
|
|
36845
37452
|
const compInterfaceIds = compInterfaces.map((i) => i.id);
|
|
@@ -36912,7 +37519,7 @@ function composeAgentBrief(agentId) {
|
|
|
36912
37519
|
return throughMount(brief, hop.alias, hop.relative);
|
|
36913
37520
|
}
|
|
36914
37521
|
const records = resolveAgentTopology();
|
|
36915
|
-
const record2 = records.find((r) => r.id === agentId);
|
|
37522
|
+
const record2 = records.find((r) => r.id === agentId) ?? componentImplementer(agentId);
|
|
36916
37523
|
if (!record2) {
|
|
36917
37524
|
throw new UnknownAgentError(agentId, records.map((r) => r.id));
|
|
36918
37525
|
}
|
|
@@ -36931,6 +37538,17 @@ function composeAgentBrief(agentId) {
|
|
|
36931
37538
|
domainName: "",
|
|
36932
37539
|
variantGuidance: record2.variantGuidance ?? ""
|
|
36933
37540
|
});
|
|
37541
|
+
const codeFence = codeFenceOf(record2);
|
|
37542
|
+
if (codeFence) {
|
|
37543
|
+
instructions = `${instructions.trimEnd()}
|
|
37544
|
+
|
|
37545
|
+
## Code write fence
|
|
37546
|
+
|
|
37547
|
+
${codeFence.length > 0 ? `Write code only here (and tests beside it, as the project lays tests out):
|
|
37548
|
+
|
|
37549
|
+
${codeFence.map((p) => `- \`${p}\``).join("\n")}
|
|
37550
|
+
` : "No implementation names a source file yet, so no code location is declared. The spawning session must name the file(s) this agent may create (the planned sourcePath); set `sourcePath` on the implementation once the file exists.\n"}`;
|
|
37551
|
+
}
|
|
36934
37552
|
const guidance = loadAgentOverride(agentId);
|
|
36935
37553
|
if (guidance !== null) {
|
|
36936
37554
|
instructions = `${instructions.trimEnd()}
|
|
@@ -36965,10 +37583,34 @@ ${typeMapping.map((line2) => `- ${line2}`).join("\n")}
|
|
|
36965
37583
|
instructions,
|
|
36966
37584
|
variantGuidance: record2.variantGuidance || void 0,
|
|
36967
37585
|
...typeMapping ? { typeMapping } : {},
|
|
37586
|
+
...codeFence ? { codeFence } : {},
|
|
36968
37587
|
profile: budget ? profile : void 0,
|
|
36969
37588
|
budget
|
|
36970
37589
|
};
|
|
36971
37590
|
}
|
|
37591
|
+
function componentImplementer(agentId) {
|
|
37592
|
+
const componentId = agentId.endsWith("-implementer") ? agentId.slice(0, -"-implementer".length) : agentId;
|
|
37593
|
+
if (componentId === "" || componentId.includes("::")) return null;
|
|
37594
|
+
return resolveLayer(true, true).find((r) => r.id === `${componentId}-implementer`) ?? null;
|
|
37595
|
+
}
|
|
37596
|
+
function codeFenceOf(record2) {
|
|
37597
|
+
const implementsComponents = record2.template === "implementer" || record2.template === "domain-owner" && record2.creationReason.startsWith("Automatically inferred from L1");
|
|
37598
|
+
if (!implementsComponents) return null;
|
|
37599
|
+
const files = record2.ownedPaths.filter((p) => !p.startsWith(".wai/") && !p.includes("/.wai/") && !/[*?]/.test(p));
|
|
37600
|
+
if (files.length === 0) return [];
|
|
37601
|
+
const dirs = files.map((f) => path29.posix.dirname(f.replace(/\\/g, "/")));
|
|
37602
|
+
let shared = dirs[0].split("/");
|
|
37603
|
+
for (const dir of dirs.slice(1)) {
|
|
37604
|
+
const parts = dir.split("/");
|
|
37605
|
+
let i = 0;
|
|
37606
|
+
while (i < shared.length && i < parts.length && shared[i] === parts[i]) i++;
|
|
37607
|
+
shared = shared.slice(0, i);
|
|
37608
|
+
}
|
|
37609
|
+
const folder = shared.join("/");
|
|
37610
|
+
const fence = [...files];
|
|
37611
|
+
if (folder !== "" && folder !== ".") fence.push(`${folder}/**`);
|
|
37612
|
+
return fence;
|
|
37613
|
+
}
|
|
36972
37614
|
function implementationLanguage(record2) {
|
|
36973
37615
|
const owned = new Set(record2.ownedPaths);
|
|
36974
37616
|
const implementations = loadImplementationSpecs().filter((impl) => implementationSourceFiles(impl).some((file) => owned.has(file)));
|
|
@@ -37001,6 +37643,7 @@ function throughMount(brief, alias, relative28) {
|
|
|
37001
37643
|
...brief,
|
|
37002
37644
|
agentId: `${alias}::${brief.agentId}`,
|
|
37003
37645
|
ownedPaths: brief.ownedPaths.map(under),
|
|
37646
|
+
...brief.codeFence ? { codeFence: brief.codeFence.map(under) } : {},
|
|
37004
37647
|
...brief.readPaths ? { readPaths: brief.readPaths.map(under) } : {},
|
|
37005
37648
|
root: brief.root ? under(brief.root) : relative28
|
|
37006
37649
|
};
|
|
@@ -37037,7 +37680,7 @@ var init_agent_resolver = __esm({
|
|
|
37037
37680
|
projectFilesCache = /* @__PURE__ */ new Map();
|
|
37038
37681
|
UnknownAgentError = class extends WaironError {
|
|
37039
37682
|
constructor(agentId, knownIds) {
|
|
37040
|
-
super(`Unknown agent id: "${agentId}". Known agent ids: ${knownIds.join(", ")}
|
|
37683
|
+
super(`Unknown agent id: "${agentId}". Known agent ids: ${knownIds.join(", ")} \u2014 or any component id of this project, which composes that component's implementer brief.`);
|
|
37041
37684
|
this.name = "UnknownAgentError";
|
|
37042
37685
|
}
|
|
37043
37686
|
};
|
|
@@ -41217,7 +41860,8 @@ Approved: ${approval.lockedAt} by ${by} \u2014 no spec has changed since.${child
|
|
|
41217
41860
|
${headline} \u2014 approved ${approval.lockedAt} by ${by}:
|
|
41218
41861
|
` + named2.map((p) => ` ${p}`).join("\n") + (rest > 0 ? `
|
|
41219
41862
|
\u2026 and ${rest} more` : "") + childNote + "\n",
|
|
41220
|
-
drifted: true
|
|
41863
|
+
drifted: true,
|
|
41864
|
+
moved: [...diff3.changed.map((p) => `~ ${p}`), ...diff3.added.map((p) => `+ ${p}`), ...diff3.removed.map((p) => `- ${p}`)]
|
|
41221
41865
|
};
|
|
41222
41866
|
} catch {
|
|
41223
41867
|
return quiet;
|
|
@@ -41345,7 +41989,7 @@ ${GUIDE_MARKER_END}
|
|
|
41345
41989
|
const stripped = stripGuideSection(existing);
|
|
41346
41990
|
const newContent = stripped.trimEnd() + section;
|
|
41347
41991
|
fs21.mkdirSync(path36.dirname(filePath), { recursive: true });
|
|
41348
|
-
|
|
41992
|
+
writeFile(filePath, newContent);
|
|
41349
41993
|
}
|
|
41350
41994
|
function stripGuideSection(content) {
|
|
41351
41995
|
const start2 = content.indexOf(GUIDE_MARKER_START);
|
|
@@ -41368,7 +42012,7 @@ To design or modify the system, invoke the **\`sdd-architect\`** skill
|
|
|
41368
42012
|
(in \`.claude/skills/\`). Author and validate specs with the \`sdd_*\` MCP tools;
|
|
41369
42013
|
the \`wairon\` CLI is the human developer's tool, not yours.
|
|
41370
42014
|
`;
|
|
41371
|
-
|
|
42015
|
+
writeFile(filePath, content);
|
|
41372
42016
|
} else if (targetType === "gemini" || targetType === "agy") {
|
|
41373
42017
|
const filePath = path36.join(projectRoot2, "GEMINI.md");
|
|
41374
42018
|
const content = `# Wairon SDD Project
|
|
@@ -41377,7 +42021,7 @@ ${versionStamp()}
|
|
|
41377
42021
|
${LOCAL_GUIDE_BODY}
|
|
41378
42022
|
${GUIDE_MARKER_END}
|
|
41379
42023
|
`;
|
|
41380
|
-
|
|
42024
|
+
writeFile(filePath, content);
|
|
41381
42025
|
} else if (targetType === "cursor") {
|
|
41382
42026
|
const filePath = path36.join(projectRoot2, ".cursorrules");
|
|
41383
42027
|
const content = `# Wairon SDD Project
|
|
@@ -41386,7 +42030,7 @@ This project uses the Wairon Spec-Driven Development (SDD) framework.
|
|
|
41386
42030
|
|
|
41387
42031
|
Refer to the rules in [.cursor/rules/](.cursor/rules/) for full instructions.
|
|
41388
42032
|
`;
|
|
41389
|
-
|
|
42033
|
+
writeFile(filePath, content);
|
|
41390
42034
|
} else if (targetType === "copilot") {
|
|
41391
42035
|
const filePath = path36.join(projectRoot2, ".github", "copilot-instructions.md");
|
|
41392
42036
|
fs21.mkdirSync(path36.dirname(filePath), { recursive: true });
|
|
@@ -41396,14 +42040,14 @@ This project uses the Wairon Spec-Driven Development (SDD) framework.
|
|
|
41396
42040
|
|
|
41397
42041
|
Refer to the prompts in [.github/prompts/](.github/prompts/) for instructions.
|
|
41398
42042
|
`;
|
|
41399
|
-
|
|
42043
|
+
writeFile(filePath, content);
|
|
41400
42044
|
} else if (targetType === "codex") {
|
|
41401
42045
|
const filePath = path36.join(projectRoot2, ".codexrules");
|
|
41402
42046
|
const content = `# Wairon SDD Project
|
|
41403
42047
|
|
|
41404
42048
|
Refer to [.codex/agents/](.codex/agents/) for full instructions.
|
|
41405
42049
|
`;
|
|
41406
|
-
|
|
42050
|
+
writeFile(filePath, content);
|
|
41407
42051
|
}
|
|
41408
42052
|
}
|
|
41409
42053
|
function reinjectLocalGuides(projectRoot2, targetTypes) {
|
|
@@ -41427,6 +42071,7 @@ var init_ai_guide = __esm({
|
|
|
41427
42071
|
os6 = __toESM(require("os"));
|
|
41428
42072
|
path36 = __toESM(require("path"));
|
|
41429
42073
|
init_stamp();
|
|
42074
|
+
init_fs();
|
|
41430
42075
|
GUIDE_MARKER_START = "<!-- wairon-guide-start -->";
|
|
41431
42076
|
GUIDE_MARKER_END = "<!-- wairon-guide-end -->";
|
|
41432
42077
|
GLOBAL_GUIDE_BODY = `## wairon \u2014 Spec-Driven Development (optional)
|
|
@@ -41450,13 +42095,13 @@ This project uses **wairon**. System specs live under \`.wai/specs/\` (L0 System
|
|
|
41450
42095
|
|
|
41451
42096
|
**Do NOT search files or read agent configs to learn about wairon or SDD. Use the context here and the \`sdd-architect\` skill to start.**
|
|
41452
42097
|
|
|
41453
|
-
**Your first move: call the \`sdd_get_status\` MCP tool** (or \`wairon/sdd_get_status\`) to see the spec tree. Do not parse files or run CLI commands manually.
|
|
42098
|
+
**Your first move: call the \`sdd_get_status\` MCP tool** (or \`wairon/sdd_get_status\`) to see the spec tree. Do not parse files or run CLI commands manually. If no \`sdd_*\` tools are available, this folder is not the root of the project this guide came from \u2014 say so instead of guessing.
|
|
41454
42099
|
|
|
41455
42100
|
### How you operate
|
|
41456
42101
|
- **To design/modify specs**: Use **\`sdd-architect\`** skill (in \`.claude/skills/\` or \`.gemini/skills/\`).
|
|
41457
42102
|
- **Manage specs via MCP tools only**: Use \`sdd_initialize_system\`, \`sdd_add_subsystem\`, \`sdd_add_component\`, \`sdd_define_interface\`, \`sdd_write_narrative\`, \`sdd_add_type\`, \`sdd_get_spec\`, \`sdd_delete_spec\`, \`sdd_validate_tree\`, and \`sdd_get_status\` (namespaced if needed). Do not edit specs manually.
|
|
41458
42103
|
- **Growing a system \u2014 subsystems \u2192 parts \u2192 projects**: boundaries are earned. Start with subsystems in one folder. A piece that needs its own folder or repository but stays the same system is a **part**: declared in \`.wai/project.yaml\` \`members\` by one location key (\`scheduler: services/scheduler\`, \`admin: ../admin\`, \`payments: git@host:acme/payments.git#<commit>\`; long form \`{ source, as, description }\`), its subsystems this project's own \u2014 local ids, this project's lock. \`sdd_add_member\` creates a part by default (\`as: project\` for a project) and \`sdd_externalize_subsystem\` moves a subsystem into one. A **project** is an independent boundary \u2014 its own id, exports, lock \u2014 reached as \`alias::name\`; make one only when it needs its own team, release, approval or public surface: \`sdd_promote_member\` (\`sdd_demote_member\` back). What a member is follows from its content (an id, an L0 or a lock make a project).
|
|
41459
|
-
- **Members & cross-project references**: relocate a member with \`sdd_move_member\`. Every other change of the family's shape is a **family migration**, each with \`dryRun\`: promote/demote, make an existing project a member with \`sdd_attach_member\`, take one out and back with \`sdd_detach_member\` / \`sdd_adopt_member\`, rename a project's id with \`sdd_rename_project\` or an alias with \`sdd_rename_member_alias\`, move a subsystem into a part with \`sdd_externalize_subsystem\` and fold a member back in with \`sdd_internalize_member\`. Run it with \`dryRun: true\` first and show the plan; applied, it writes every project it touches or none, and never locks \u2014 it names the projects to re-lock. A project member is never a subsystem of its parent: it has its own \`.wai/\` tree and is designed from its own root.
|
|
42104
|
+
- **Members & cross-project references**: relocate a member with \`sdd_move_member\`. Every other change of the family's shape is a **family migration**, each with \`dryRun\`: promote/demote, make an existing project a member with \`sdd_attach_member\`, take one out and back with \`sdd_detach_member\` / \`sdd_adopt_member\`, rename a project's id with \`sdd_rename_project\` or an alias with \`sdd_rename_member_alias\`, move a subsystem into a part with \`sdd_externalize_subsystem\` and fold a member back in with \`sdd_internalize_member\`. Run it with \`dryRun: true\` first and show the plan (the human's CLI calls the same plan \`--report\`); applied, it writes every project it touches or none, and never locks \u2014 it names the projects to re-lock. A project member is never a subsystem of its parent: it has its own \`.wai/\` tree and is designed from its own root \u2014 its specs, its L0 export table and its \`project.yaml\` are written by a session opened in that member's folder (its own guide and \`.mcp.json\`), and the tools here refuse such a write naming that folder.
|
|
41460
42105
|
- **\`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\`).
|
|
41461
42106
|
- **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\`).
|
|
41462
42107
|
- **Do not run the \`wairon\` CLI**: Use \`sdd_validate_tree\` and \`sdd_get_status\` instead of CLI commands.
|
|
@@ -41626,8 +42271,9 @@ var init_context = __esm({
|
|
|
41626
42271
|
// src/core/status.ts
|
|
41627
42272
|
function approvalTag(entry) {
|
|
41628
42273
|
if (!entry) return "";
|
|
41629
|
-
const
|
|
41630
|
-
|
|
42274
|
+
const words2 = APPROVAL_WORDS[entry.state];
|
|
42275
|
+
const state = entry.upgraded ? `${words2}, re-lock once` : words2;
|
|
42276
|
+
return entry.pinned ? ` [${state} \xB7 ${PIN_WORDS[entry.pinned]}]` : ` [${state}]`;
|
|
41631
42277
|
}
|
|
41632
42278
|
function fillDecor(decor) {
|
|
41633
42279
|
return {
|
|
@@ -41713,11 +42359,15 @@ function getStatusReport(options = {}, decor) {
|
|
|
41713
42359
|
}
|
|
41714
42360
|
componentScores.set(comp.id, score);
|
|
41715
42361
|
}
|
|
42362
|
+
const typeOwners = new Set(loadTypeSpecs().map((t) => t.subsystem).filter((id) => typeof id === "string"));
|
|
41716
42363
|
const getSubsystemScore = (subId) => {
|
|
41717
42364
|
const sub = subsystems.find((s) => s.id === subId);
|
|
41718
42365
|
if (!sub) return 0;
|
|
41719
42366
|
const subComps = components.filter((c) => c.subsystem === subId);
|
|
41720
|
-
if (subComps.length === 0)
|
|
42367
|
+
if (subComps.length === 0) {
|
|
42368
|
+
if (!typeOwners.has(subId)) return 0;
|
|
42369
|
+
return sub.status === "complete" ? 100 : 50;
|
|
42370
|
+
}
|
|
41721
42371
|
const totalScore = subComps.reduce((acc, c) => acc + (componentScores.get(c.id) ?? 0), 0);
|
|
41722
42372
|
let avg = Math.round(totalScore / subComps.length);
|
|
41723
42373
|
if (sub.status === "draft" || sub.status === "design") {
|
|
@@ -41840,12 +42490,11 @@ function getStatusReport(options = {}, decor) {
|
|
|
41840
42490
|
`;
|
|
41841
42491
|
}
|
|
41842
42492
|
const own = options.approvals?.find((a) => a.key === "");
|
|
41843
|
-
|
|
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)" : ""}
|
|
42493
|
+
if (own) output += `${mark.layer("system", "Approval:")} this project is ${OWN_APPROVAL_WORDS[own.state]}${own.upgraded ? " (locked under an earlier gate identity \u2014 re-lock once)" : ""}
|
|
41845
42494
|
`;
|
|
41846
42495
|
return { text: output, failed: false };
|
|
41847
42496
|
}
|
|
41848
|
-
var path37, asWritten;
|
|
42497
|
+
var path37, APPROVAL_WORDS, PIN_WORDS, OWN_APPROVAL_WORDS, asWritten;
|
|
41849
42498
|
var init_status = __esm({
|
|
41850
42499
|
"src/core/status.ts"() {
|
|
41851
42500
|
"use strict";
|
|
@@ -41854,6 +42503,13 @@ var init_status = __esm({
|
|
|
41854
42503
|
init_specs();
|
|
41855
42504
|
init_fs();
|
|
41856
42505
|
init_yaml();
|
|
42506
|
+
APPROVAL_WORDS = { approved: "approved", drifted: "drifted", never: "never approved" };
|
|
42507
|
+
PIN_WORDS = { matches: "pinned", moved: "pin moved", unpinned: "not pinned" };
|
|
42508
|
+
OWN_APPROVAL_WORDS = {
|
|
42509
|
+
approved: "approved",
|
|
42510
|
+
drifted: "drifted \u2014 its approval no longer covers this tree (`wairon lock` re-approves)",
|
|
42511
|
+
never: "never approved (`wairon lock` approves the design)"
|
|
42512
|
+
};
|
|
41857
42513
|
asWritten = (text3) => text3;
|
|
41858
42514
|
}
|
|
41859
42515
|
});
|
|
@@ -41894,7 +42550,8 @@ var init_core = __esm({
|
|
|
41894
42550
|
init_provision();
|
|
41895
42551
|
init_statehash();
|
|
41896
42552
|
init_lockfile();
|
|
41897
|
-
|
|
42553
|
+
init_specs2();
|
|
42554
|
+
init_specs2();
|
|
41898
42555
|
init_diagram();
|
|
41899
42556
|
init_diagram();
|
|
41900
42557
|
init_detection();
|
|
@@ -42150,7 +42807,7 @@ function exportSddSkills(targetTypes) {
|
|
|
42150
42807
|
const content = composeBuiltinSkill(name, packSkills.filter((s) => s.targets.includes(type)));
|
|
42151
42808
|
const destPath = skillDestPath(type, destDir, name);
|
|
42152
42809
|
ensureDir(path38.dirname(destPath));
|
|
42153
|
-
|
|
42810
|
+
writeFileIfChanged(destPath, content);
|
|
42154
42811
|
fileCount++;
|
|
42155
42812
|
}
|
|
42156
42813
|
for (const skill of packSkills) {
|
|
@@ -42161,7 +42818,7 @@ function exportSddSkills(targetTypes) {
|
|
|
42161
42818
|
const content = fs22.readFileSync(skill.sourcePath, "utf-8");
|
|
42162
42819
|
const destPath = skillDestPath(type, destDir, id);
|
|
42163
42820
|
ensureDir(path38.dirname(destPath));
|
|
42164
|
-
|
|
42821
|
+
writeFileIfChanged(destPath, content);
|
|
42165
42822
|
fileCount++;
|
|
42166
42823
|
}
|
|
42167
42824
|
}
|
|
@@ -42180,7 +42837,7 @@ function checkSkillFreshness(type) {
|
|
|
42180
42837
|
}
|
|
42181
42838
|
const want = composeBuiltinSkill(name, packSkills);
|
|
42182
42839
|
const have = fs22.readFileSync(destPath, "utf-8");
|
|
42183
|
-
if (have === want) result.ok.push(name);
|
|
42840
|
+
if (have.replace(/\r\n/g, "\n") === want.replace(/\r\n/g, "\n")) result.ok.push(name);
|
|
42184
42841
|
else result.stale.push(name);
|
|
42185
42842
|
}
|
|
42186
42843
|
return result;
|
|
@@ -44890,8 +45547,32 @@ function planAliasRename(family, bound2, request) {
|
|
|
44890
45547
|
if ([...family.owners].some(([key, owner]) => owner === bound2 && key.split("::").pop() === next)) {
|
|
44891
45548
|
plan7.notes.push(`${label5(bound2)} has a spec whose id is "${next}" \u2014 a local id equal to an alias (LOCAL_ID_SHADOWS_PROJECT reports it)`);
|
|
44892
45549
|
}
|
|
45550
|
+
repinIntactConsumers(plan7, family, bound2);
|
|
44893
45551
|
return plan7;
|
|
44894
45552
|
}
|
|
45553
|
+
function repinIntactConsumers(plan7, family, bound2) {
|
|
45554
|
+
const boundNode2 = familyNode(family, bound2);
|
|
45555
|
+
for (const node of family.nodes) {
|
|
45556
|
+
if (path46.resolve(node.directory) === path46.resolve(boundNode2.directory)) continue;
|
|
45557
|
+
let statuses = [];
|
|
45558
|
+
try {
|
|
45559
|
+
statuses = runWithProjectRoot(node.directory, () => getExternalsStatus2());
|
|
45560
|
+
} catch {
|
|
45561
|
+
continue;
|
|
45562
|
+
}
|
|
45563
|
+
for (const status2 of statuses) {
|
|
45564
|
+
const producer = node.aliases.get(status2.alias);
|
|
45565
|
+
const pinsBound = producer !== void 0 ? producer === bound2 : boundNode2.id !== void 0 && status2.project === boundNode2.id;
|
|
45566
|
+
if (!status2.pinned || status2.outOfReach || !pinsBound) continue;
|
|
45567
|
+
const broken = status2.uses.filter((u) => u.state === "changed" || u.state === "removed");
|
|
45568
|
+
if (broken.length > 0) {
|
|
45569
|
+
plan7.notes.push(`${label5(node.namespace)}'s pin of "${status2.alias}" is already incompatible (${broken.map((u) => u.publicName ?? u.member ?? "?").join(", ")}): not re-pinned \u2014 adapt its uses, then run \`wairon externals pin ${status2.alias}\` there.`);
|
|
45570
|
+
continue;
|
|
45571
|
+
}
|
|
45572
|
+
edit2(plan7, node.namespace, "pin", `pin ${status2.alias} taken again: the names it uses of ${label5(bound2)} are unchanged`, { root: node.directory, call: "pinExternals", args: [[status2.alias]] });
|
|
45573
|
+
}
|
|
45574
|
+
}
|
|
45575
|
+
}
|
|
44895
45576
|
function write4(plan7, rehearsal) {
|
|
44896
45577
|
const planned = plan7.edits.filter((e) => e.write !== void 0);
|
|
44897
45578
|
const unknown = planned.find((x) => !PHASES.includes(x.write.call));
|
|
@@ -44906,6 +45587,7 @@ function write4(plan7, rehearsal) {
|
|
|
44906
45587
|
for (const e of planned.filter((x) => x.write.call === "renameId")) writeOne2(rehearsal, e.write);
|
|
44907
45588
|
}
|
|
44908
45589
|
for (const e of planned.filter((x) => x.write.call === "renamePin")) writeOne2(rehearsal, e.write);
|
|
45590
|
+
for (const e of planned.filter((x) => x.write.call === "pinExternals")) writeOne2(rehearsal, e.write);
|
|
44909
45591
|
}
|
|
44910
45592
|
function referencesBySpec(planned) {
|
|
44911
45593
|
const out = /* @__PURE__ */ new Map();
|
|
@@ -44929,6 +45611,8 @@ function writeOne2(rehearsal, w) {
|
|
|
44929
45611
|
return renameProjectId(args[0], args[1]);
|
|
44930
45612
|
case "renamePin":
|
|
44931
45613
|
return renamePin2(args[0], args[1], args[2]);
|
|
45614
|
+
case "pinExternals":
|
|
45615
|
+
return pinExternals2(args[0]);
|
|
44932
45616
|
default:
|
|
44933
45617
|
throw new Error(`identity_migration makes no "${w.call}" write`);
|
|
44934
45618
|
}
|
|
@@ -44968,7 +45652,7 @@ var init_identity = __esm({
|
|
|
44968
45652
|
group: ["type"],
|
|
44969
45653
|
type: ["interface", "type", "implementation"]
|
|
44970
45654
|
};
|
|
44971
|
-
PHASES = ["rewriteReferences", "renameAlias", "repointExternal", "renameId", "renamePin"];
|
|
45655
|
+
PHASES = ["rewriteReferences", "renameAlias", "repointExternal", "renameId", "renamePin", "pinExternals"];
|
|
44972
45656
|
}
|
|
44973
45657
|
});
|
|
44974
45658
|
|
|
@@ -45212,6 +45896,7 @@ function planExternalize(family, bound2, request) {
|
|
|
45212
45896
|
if (plan7.refusals.length > 0) return plan7;
|
|
45213
45897
|
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"] });
|
|
45214
45898
|
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: [[]] });
|
|
45899
|
+
repinIntactConsumers2(plan7, family, bound2, [dir]);
|
|
45215
45900
|
plan7.notes.push("Source code is not moved: move the subsystem's code into the member yourself.");
|
|
45216
45901
|
return plan7;
|
|
45217
45902
|
}
|
|
@@ -45340,6 +46025,7 @@ function planPromote(family, bound2, request) {
|
|
|
45340
46025
|
);
|
|
45341
46026
|
edit3(plan7, newId, "pin", `pin the new project's external for ${node.id ?? "this project"} (when its specs reference it)`, { root: part.directory, call: "pinExternals", args: [[]] });
|
|
45342
46027
|
if (part.storage !== "contained") edit3(plan7, bound2, "pin", `pin ${alias} (a project stored outside ${label6(bound2)} is judged against its pin)`, { root: node.directory, call: "pinExternals", args: [[alias]] });
|
|
46028
|
+
repinIntactConsumers2(plan7, family, bound2, [part.directory]);
|
|
45343
46029
|
plan7.notes.push(`"${newId}" has no lock yet: lock it at its own root (\`wairon lock\`) once it validates.`);
|
|
45344
46030
|
return plan7;
|
|
45345
46031
|
}
|
|
@@ -45406,16 +46092,19 @@ function write5(plan7, rehearsal) {
|
|
|
45406
46092
|
return;
|
|
45407
46093
|
}
|
|
45408
46094
|
if (plan7.request.verb === "externalize") {
|
|
45409
|
-
for (const e of planned.filter((x) => x.write.call === "externalizeSubsystem")) writeOne3(rehearsal, e.write);
|
|
46095
|
+
for (const e of planned.filter((x) => x.write.call === "externalizeSubsystem")) recordCrossing(plan7, e.project, e.write.args[0], writeOne3(rehearsal, e.write));
|
|
45410
46096
|
for (const e of planned.filter((x) => x.write.call === "pinExternals")) writeOne3(rehearsal, e.write);
|
|
45411
46097
|
return;
|
|
45412
46098
|
}
|
|
45413
46099
|
if (plan7.request.verb === "promote") {
|
|
45414
|
-
for (const e of planned.filter((x) => x.write.call === "promoteMember")) writeOne3(rehearsal, e.write);
|
|
46100
|
+
for (const e of planned.filter((x) => x.write.call === "promoteMember")) recordCrossing(plan7, e.project, e.write.args[1] ?? e.write.args[0], writeOne3(rehearsal, e.write));
|
|
45415
46101
|
for (const e of planned.filter((x) => x.write.call === "pinExternals")) writeOne3(rehearsal, e.write);
|
|
45416
46102
|
return;
|
|
45417
46103
|
}
|
|
45418
|
-
for (const e of planned.filter((x) => x.write.call === "demoteMember"))
|
|
46104
|
+
for (const e of planned.filter((x) => x.write.call === "demoteMember")) {
|
|
46105
|
+
const demoted = writeOne3(rehearsal, e.write);
|
|
46106
|
+
for (const line2 of demoted?.consumersRestored ?? []) plan7.edits.push({ project: e.project, kind: "reference", detail: `consumers across the old boundary restored: ${line2}` });
|
|
46107
|
+
}
|
|
45419
46108
|
for (const e of planned.filter((x) => x.write.call === "unpin")) writeOne3(rehearsal, e.write);
|
|
45420
46109
|
}
|
|
45421
46110
|
function referencesBySpec2(planned) {
|
|
@@ -45428,9 +46117,46 @@ ${id}`) ?? [], ...edits]);
|
|
|
45428
46117
|
}
|
|
45429
46118
|
return out;
|
|
45430
46119
|
}
|
|
46120
|
+
function repinIntactConsumers2(plan7, family, bound2, created) {
|
|
46121
|
+
const boundNode2 = familyNode(family, bound2);
|
|
46122
|
+
const skip = new Set([boundNode2.directory, ...created].map((d) => path47.resolve(d)));
|
|
46123
|
+
for (const node of family.nodes) {
|
|
46124
|
+
if (skip.has(path47.resolve(node.directory))) continue;
|
|
46125
|
+
let statuses = [];
|
|
46126
|
+
try {
|
|
46127
|
+
statuses = runWithProjectRoot(node.directory, () => getExternalsStatus2());
|
|
46128
|
+
} catch {
|
|
46129
|
+
continue;
|
|
46130
|
+
}
|
|
46131
|
+
for (const status2 of statuses) {
|
|
46132
|
+
const producer = node.aliases.get(status2.alias);
|
|
46133
|
+
const pinsBound = producer !== void 0 ? producer === bound2 : boundNode2.id !== void 0 && status2.project === boundNode2.id;
|
|
46134
|
+
if (!status2.pinned || status2.outOfReach || !pinsBound) continue;
|
|
46135
|
+
const broken = status2.uses.filter((u) => u.state === "changed" || u.state === "removed");
|
|
46136
|
+
if (broken.length > 0) {
|
|
46137
|
+
plan7.notes.push(`${label6(node.namespace)}'s pin of "${status2.alias}" is already incompatible (${broken.map((u) => u.publicName ?? u.member ?? "?").join(", ")}): not re-pinned \u2014 adapt its uses, then run \`wairon externals pin ${status2.alias}\` there.`);
|
|
46138
|
+
continue;
|
|
46139
|
+
}
|
|
46140
|
+
edit3(plan7, node.namespace, "pin", `pin ${status2.alias} taken again: the names it uses of ${label6(bound2)} only move with the boundary`, { root: node.directory, call: "pinExternals", args: [[status2.alias]] });
|
|
46141
|
+
}
|
|
46142
|
+
}
|
|
46143
|
+
}
|
|
46144
|
+
function recordCrossing(plan7, bound2, member, result) {
|
|
46145
|
+
const crossed = result;
|
|
46146
|
+
if (!crossed) return;
|
|
46147
|
+
for (const r of crossed.respelled) {
|
|
46148
|
+
plan7.edits.push({ project: bound2, kind: "reference", detail: `${r.specId} (${r.position}): ${r.from} \u2192 ${r.to}`, reference: r });
|
|
46149
|
+
}
|
|
46150
|
+
for (const r of crossed.memberRespelled) {
|
|
46151
|
+
plan7.edits.push({ project: member, kind: "reference", detail: `${r.specId} (${r.position}): ${r.from} \u2192 ${r.to}`, reference: r });
|
|
46152
|
+
}
|
|
46153
|
+
for (const line2 of crossed.exported) plan7.edits.push({ project: bound2, kind: "export", detail: `export ${line2}` });
|
|
46154
|
+
for (const line2 of crossed.imported) plan7.edits.push({ project: bound2, kind: "external", detail: `import: ${line2}` });
|
|
46155
|
+
for (const line2 of crossed.consumersDropped) plan7.edits.push({ project: bound2, kind: "reference", detail: `consumers across the boundary to ${member} dropped: ${line2}` });
|
|
46156
|
+
}
|
|
45431
46157
|
function writeOne3(rehearsal, w) {
|
|
45432
46158
|
const args = w.args;
|
|
45433
|
-
runWithProjectRoot(rehearsalRoot(rehearsal, w.root), () => {
|
|
46159
|
+
return runWithProjectRoot(rehearsalRoot(rehearsal, w.root), () => {
|
|
45434
46160
|
switch (w.call) {
|
|
45435
46161
|
case "internalizeMember":
|
|
45436
46162
|
return internalizeMember(args[0], args[1]);
|
|
@@ -45557,7 +46283,7 @@ function dropRehearsal(id) {
|
|
|
45557
46283
|
function saveJournal(journal) {
|
|
45558
46284
|
const ignore = nodePath2.join(transactionsDir(journal.owner), ".gitignore");
|
|
45559
46285
|
if (!fs27.existsSync(ignore)) writeDurably(ignore, "*\n");
|
|
45560
|
-
writeDurably(nodePath2.join(transactionDir(journal.owner, journal.id), "journal.yaml"),
|
|
46286
|
+
writeDurably(nodePath2.join(transactionDir(journal.owner, journal.id), "journal.yaml"), yaml4.dump(journal, { noRefs: true, lineWidth: 200 }));
|
|
45561
46287
|
}
|
|
45562
46288
|
function readJournal(owner, id) {
|
|
45563
46289
|
const file = nodePath2.join(transactionDir(owner, id), "journal.yaml");
|
|
@@ -45569,7 +46295,7 @@ function readJournal(owner, id) {
|
|
|
45569
46295
|
throw e;
|
|
45570
46296
|
}
|
|
45571
46297
|
try {
|
|
45572
|
-
return
|
|
46298
|
+
return yaml4.load(text3);
|
|
45573
46299
|
} catch (e) {
|
|
45574
46300
|
throw new Error(`${file} is not a readable transaction journal: ${e instanceof Error ? e.message : String(e)}`);
|
|
45575
46301
|
}
|
|
@@ -45630,14 +46356,14 @@ function close(owner, id) {
|
|
|
45630
46356
|
} catch {
|
|
45631
46357
|
}
|
|
45632
46358
|
}
|
|
45633
|
-
var fs27, os7, nodePath2,
|
|
46359
|
+
var fs27, os7, nodePath2, yaml4, TRANSACTIONS2, transactionsDir, transactionDir, rehearsalDir;
|
|
45634
46360
|
var init_transaction_files = __esm({
|
|
45635
46361
|
"src/migrations/transaction-files.ts"() {
|
|
45636
46362
|
"use strict";
|
|
45637
46363
|
fs27 = __toESM(require("fs"));
|
|
45638
46364
|
os7 = __toESM(require("os"));
|
|
45639
46365
|
nodePath2 = __toESM(require("path"));
|
|
45640
|
-
|
|
46366
|
+
yaml4 = __toESM(require("js-yaml"));
|
|
45641
46367
|
TRANSACTIONS2 = nodePath2.join(".wai", "transactions");
|
|
45642
46368
|
transactionsDir = (owner) => nodePath2.join(owner, TRANSACTIONS2);
|
|
45643
46369
|
transactionDir = (owner, id) => nodePath2.join(transactionsDir(owner), id);
|
|
@@ -46481,10 +47207,19 @@ ${body}`);
|
|
|
46481
47207
|
}
|
|
46482
47208
|
function migrationView(report4, dryRun) {
|
|
46483
47209
|
const plan7 = report4.plan;
|
|
47210
|
+
const request = plan7.request;
|
|
47211
|
+
const madeProject = request.verb === "promote" || request.verb === "externalize" && request.as === "project";
|
|
47212
|
+
const madeAt = madeProject && report4.applied ? plan7.changes.find((c) => c.action === "create" && c.path.replace(/\\/g, "/") === ".wai/project.yaml")?.project : void 0;
|
|
46484
47213
|
return {
|
|
46485
47214
|
dryRun,
|
|
46486
47215
|
applied: report4.applied,
|
|
46487
47216
|
relock: report4.applied ? report4.relock : plan7.relock,
|
|
47217
|
+
...madeAt ? {
|
|
47218
|
+
newProject: {
|
|
47219
|
+
root: madeAt,
|
|
47220
|
+
next: `The new project at ${madeAt} has no guide, skills or MCP registration of its own yet. The human runs, in that folder: \`wairon generate\` and \`wairon mcp install --backend claude\` (the CLI's \`wairon member promote\` / \`subsystem externalize --as project\` do this themselves). Its own session then designs it \u2014 its specs, L0 export table and project.yaml are written from its own root.`
|
|
47221
|
+
}
|
|
47222
|
+
} : {},
|
|
46488
47223
|
...report4.outcome ? { outcome: { committed: report4.outcome.committed, restored: report4.outcome.restored, unrestored: report4.outcome.unrestored, ...report4.outcome.failure ? { failure: report4.outcome.failure } : {} } } : {},
|
|
46489
47224
|
plan: {
|
|
46490
47225
|
verb: plan7.request.verb,
|
|
@@ -48088,9 +48823,9 @@ ${testsBlock}` : ""}`, deletion);
|
|
|
48088
48823
|
server,
|
|
48089
48824
|
"sdd_get_agent_brief",
|
|
48090
48825
|
{
|
|
48091
|
-
description: "Compose the LIVE delegation brief for one resolved agent: the fully rendered instruction body plus the ownedPaths/readPaths scope fence \u2014 the dynamic replacement for generated per-component agent files. Fetch a brief and spawn a generic subagent with it: always fresh after a re-lock, no session restart needed. List agent ids via listAgents or resources/list (the wairon-agent:// entries).",
|
|
48826
|
+
description: "Compose the LIVE delegation brief for one resolved agent: the fully rendered instruction body plus the ownedPaths/readPaths scope fence and the codeFence (where the agent may write CODE: its source files and their shared folder; empty when no file is named yet \u2014 then name the planned files when spawning) \u2014 the dynamic replacement for generated per-component agent files. A COMPONENT id (or `<component>-implementer`) composes that one component's implementer brief, with no agent files and no setting needed. Fetch a brief and spawn a generic subagent with it: always fresh after a re-lock, no session restart needed. List agent ids via listAgents or resources/list (the wairon-agent:// entries).",
|
|
48092
48827
|
inputSchema: {
|
|
48093
|
-
agentId: import_zod12.z.string().describe("The resolved agent id (list via listAgents or resources/list)")
|
|
48828
|
+
agentId: import_zod12.z.string().describe("The resolved agent id (list via listAgents or resources/list), or a component id for that component's implementer brief")
|
|
48094
48829
|
}
|
|
48095
48830
|
},
|
|
48096
48831
|
({ agentId }) => {
|
|
@@ -48606,7 +49341,7 @@ function geminiGlobalDir(override) {
|
|
|
48606
49341
|
return override || process.env["GEMINI_CONFIG_DIR"] || path51.join(os8.homedir(), ".gemini");
|
|
48607
49342
|
}
|
|
48608
49343
|
function claudeMcpConfigPath(useGlobal, override) {
|
|
48609
|
-
if (!useGlobal) return path51.join(
|
|
49344
|
+
if (!useGlobal) return path51.join(getProjectRoot(), ".mcp.json");
|
|
48610
49345
|
const dir = override || process.env["CLAUDE_CONFIG_DIR"];
|
|
48611
49346
|
return dir ? path51.join(dir, ".claude.json") : path51.join(os8.homedir(), ".claude.json");
|
|
48612
49347
|
}
|
|
@@ -48636,32 +49371,33 @@ function validateConfigDir(dir, backend) {
|
|
|
48636
49371
|
);
|
|
48637
49372
|
}
|
|
48638
49373
|
}
|
|
49374
|
+
function bindingLine(root) {
|
|
49375
|
+
let id = null;
|
|
49376
|
+
try {
|
|
49377
|
+
const config = loadProjectConfig();
|
|
49378
|
+
if (config) id = effectiveProjectId(config);
|
|
49379
|
+
} catch {
|
|
49380
|
+
}
|
|
49381
|
+
return `project ${id ?? path51.basename(root)} at ${root}`;
|
|
49382
|
+
}
|
|
49383
|
+
function serveRoot(cwd, envDir) {
|
|
49384
|
+
if (envDir && fs30.existsSync(path51.join(envDir, ".wai"))) return { root: path51.resolve(envDir), how: "WAIRON_PROJECT_DIR (pinned at install)" };
|
|
49385
|
+
const binding = resolveProjectBinding(cwd);
|
|
49386
|
+
if (binding?.via === "here") return { root: binding.root, how: "cwd" };
|
|
49387
|
+
if (binding?.via === "ancestor") return { root: binding.root, how: `walked up to ancestor inside the repository \u2014 cwd (${cwd}) holds no project` };
|
|
49388
|
+
if (binding) return { root: binding.root, how: `walked up past the repository root to the project that declares it as a member \u2014 cwd (${cwd})` };
|
|
49389
|
+
if (fs30.existsSync(path51.join(cwd, ".wai"))) return { root: cwd, how: "cwd (its .wai holds no L0 yet)" };
|
|
49390
|
+
return { root: cwd, how: "cwd (no project binds this folder)" };
|
|
49391
|
+
}
|
|
48639
49392
|
async function runMcpServe() {
|
|
48640
|
-
const { setProjectRoot: setProjectRoot2, getProjectRoot: getProjectRoot2
|
|
49393
|
+
const { setProjectRoot: setProjectRoot2, getProjectRoot: getProjectRoot2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
|
|
48641
49394
|
const cwd = path51.resolve(process.cwd());
|
|
48642
49395
|
const envDir = process.env["WAIRON_PROJECT_DIR"];
|
|
48643
|
-
|
|
48644
|
-
let how;
|
|
48645
|
-
if (envDir && fs30.existsSync(path51.join(envDir, ".wai"))) {
|
|
48646
|
-
resolved = path51.resolve(envDir);
|
|
48647
|
-
how = "WAIRON_PROJECT_DIR (pinned at install)";
|
|
48648
|
-
} else {
|
|
48649
|
-
const found = findProjectRoot2(cwd);
|
|
48650
|
-
if (found && found === cwd) {
|
|
48651
|
-
resolved = found;
|
|
48652
|
-
how = "cwd";
|
|
48653
|
-
} else if (found) {
|
|
48654
|
-
resolved = found;
|
|
48655
|
-
how = `walked up to ancestor \u2014 cwd (${cwd}) has no .wai`;
|
|
48656
|
-
} else {
|
|
48657
|
-
resolved = cwd;
|
|
48658
|
-
how = "cwd (no .wai found anywhere above)";
|
|
48659
|
-
}
|
|
48660
|
-
}
|
|
49396
|
+
const { root: resolved, how } = serveRoot(cwd, envDir);
|
|
48661
49397
|
setProjectRoot2(resolved);
|
|
48662
49398
|
const initialized = isProjectInitialized();
|
|
48663
49399
|
process.stderr.write(
|
|
48664
|
-
`[wairon mcp] v${WAIRON_VERSION} \u2014
|
|
49400
|
+
`[wairon mcp] v${WAIRON_VERSION} \u2014 ${bindingLine(getProjectRoot2())} [via ${how}] (.wai ${initialized ? "found" : "NOT found \u2014 sdd_* tools will report no project"})
|
|
48665
49401
|
`
|
|
48666
49402
|
);
|
|
48667
49403
|
const { startMcpServer: startMcpServer2 } = (init_server(), __toCommonJS(server_exports));
|
|
@@ -48756,7 +49492,7 @@ function resolveInstallTargets(options) {
|
|
|
48756
49492
|
configBase = path51.join(geminiGlobalDir(options.configDir), "antigravity-cli");
|
|
48757
49493
|
settingsPath = path51.join(configBase, "mcp_config.json");
|
|
48758
49494
|
} else {
|
|
48759
|
-
configBase = path51.join(
|
|
49495
|
+
configBase = path51.join(root, ".gemini");
|
|
48760
49496
|
settingsPath = path51.join(configBase, "settings.json");
|
|
48761
49497
|
}
|
|
48762
49498
|
} else {
|
|
@@ -48829,14 +49565,14 @@ function writeInstallTarget(target) {
|
|
|
48829
49565
|
if (target.outsideProject && fs30.existsSync(target.path)) {
|
|
48830
49566
|
logger.info(`Backed up ${import_chalk3.default.gray(target.path)} to ${import_chalk3.default.cyan(backupBeside(target.path))} before replacing it (outside the project root).`);
|
|
48831
49567
|
}
|
|
48832
|
-
fs30.writeFileSync(target.path, JSON.stringify(target.settings, null, 2) + "\n");
|
|
49568
|
+
fs30.writeFileSync(target.path, withLineEndings(target.path, JSON.stringify(target.settings, null, 2) + "\n"));
|
|
48833
49569
|
}
|
|
48834
49570
|
function reportInstalled(target, agentLabel) {
|
|
48835
49571
|
const wasStale = target.before !== void 0;
|
|
48836
49572
|
logger.success(`wairon MCP server ${wasStale ? "updated (was stale)" : "registered"} for ${agentLabel} in ${import_chalk3.default.cyan(target.path)}.`);
|
|
48837
49573
|
logger.blank();
|
|
48838
49574
|
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").');
|
|
49575
|
+
logger.info('rename tools, plus live agent briefs (sdd_get_agent_brief). The full list is in docs/cli.md ("MCP tools"), shipped with the package (node_modules/@wairon/cli/docs/cli.md) and at https://github.com/SYW-Apps/Waffle-AIron/blob/main/docs/cli.md');
|
|
48840
49576
|
logger.blank();
|
|
48841
49577
|
const restartApp = target.backend === "gemini" ? "Antigravity CLI (agy)" : "claude";
|
|
48842
49578
|
logger.info(`Restart ${import_chalk3.default.bold(restartApp)} (or reload MCP servers) to activate.`);
|
|
@@ -48855,7 +49591,7 @@ function runMcpStatus() {
|
|
|
48855
49591
|
if (!projectConfig) throw new ProjectNotInitializedError();
|
|
48856
49592
|
const claudeProject = claudeMcpConfigPath(false);
|
|
48857
49593
|
const claudeGlobal = claudeMcpConfigPath(true);
|
|
48858
|
-
const geminiProject = path51.join(
|
|
49594
|
+
const geminiProject = path51.join(getProjectRoot(), ".gemini", "settings.json");
|
|
48859
49595
|
const geminiGlobal = path51.join(geminiGlobalDir(), "antigravity-cli", "mcp_config.json");
|
|
48860
49596
|
logger.blank();
|
|
48861
49597
|
logger.info(`${import_chalk3.default.bold("wairon MCP Server")}`);
|
|
@@ -48938,6 +49674,7 @@ var init_mcp = __esm({
|
|
|
48938
49674
|
init_fs();
|
|
48939
49675
|
init_errors();
|
|
48940
49676
|
init_defaults();
|
|
49677
|
+
init_project();
|
|
48941
49678
|
}
|
|
48942
49679
|
});
|
|
48943
49680
|
|
|
@@ -49194,32 +49931,99 @@ function checkApproval(strict) {
|
|
|
49194
49931
|
}
|
|
49195
49932
|
const lock = readLockState(computeGateStateId());
|
|
49196
49933
|
const record2 = lock.record;
|
|
49934
|
+
const members = familyApprovals().filter((a) => a.key !== "" && a.as !== "part");
|
|
49935
|
+
const drifted = members.filter((a) => a.state === "drifted");
|
|
49936
|
+
const never = members.filter((a) => a.state === "never");
|
|
49937
|
+
if (drifted.length > 0) {
|
|
49938
|
+
const where = memberFolders();
|
|
49939
|
+
const own = lock.state === "locked" ? "This project's own approval still covers its design." : lock.state === "stale" ? "This project's own approval is stale too \u2014 run `wairon lock` here afterwards." : "This project itself has no approval on record.";
|
|
49940
|
+
return {
|
|
49941
|
+
state: lock.state === "unlocked" ? "unlocked" : "stale",
|
|
49942
|
+
approved: false,
|
|
49943
|
+
message: `Member project(s) carry spec changes nobody approved: ${drifted.map((m) => describeMemberAt(m, where)).join("; ")}. Nothing says a human has seen what is about to merge in them. Fix: run \`wairon lock\` in each member's own folder and commit its .wai/lock.json. ${own}`
|
|
49944
|
+
};
|
|
49945
|
+
}
|
|
49197
49946
|
if (lock.state === "stale" && record2.stateId.algorithm !== lock.current.algorithm) return upgradedCheck(record2);
|
|
49198
49947
|
switch (lock.state) {
|
|
49199
|
-
case "locked":
|
|
49948
|
+
case "locked": {
|
|
49949
|
+
if (strict && never.length > 0) {
|
|
49950
|
+
const where = memberFolders();
|
|
49951
|
+
return {
|
|
49952
|
+
state: "locked",
|
|
49953
|
+
approved: false,
|
|
49954
|
+
message: `This project's own design is approved, but --strict asks for an approved family and member project(s) were never approved by anyone: ${never.map((m) => describeMemberAt(m, where)).join("; ")}. Fix: run \`wairon lock\` in each member's own folder and commit its .wai/lock.json.`
|
|
49955
|
+
};
|
|
49956
|
+
}
|
|
49957
|
+
const approvedMembers = members.length - never.length;
|
|
49958
|
+
const family = members.length === 0 ? "" : ` ${approvedMembers} member project(s) approved at their own roots.` + (never.length > 0 ? ` NOT gated: ${never.map((m) => m.alias ?? m.key).join(", ")} \u2014 never approved by anyone (\`--strict\` fails here).` : "");
|
|
49200
49959
|
return {
|
|
49201
49960
|
state: "locked",
|
|
49202
49961
|
approved: true,
|
|
49203
|
-
message:
|
|
49962
|
+
message: (never.length > 0 ? "This project's own design is the approved design \u2014 " : "The design in this tree is the approved design \u2014 ") + `approved ${record2.lockedAt} by ${describeApprover(record2.lockedBy)}.${family}`
|
|
49204
49963
|
};
|
|
49205
|
-
|
|
49964
|
+
}
|
|
49965
|
+
case "stale": {
|
|
49966
|
+
const repinned = repinOnly(record2);
|
|
49967
|
+
if (repinned) {
|
|
49968
|
+
return {
|
|
49969
|
+
state: "stale",
|
|
49970
|
+
approved: false,
|
|
49971
|
+
message: `Member project(s) were re-approved at their own roots since this project's approval was taken: ${repinned.join(", ")}. That is how a member's approval is meant to move \u2014 its own approver signed its new design off \u2014 so the member is not in question. What is stale is this project's pin of it: the approval on record (${record2.lockedAt} by ${describeApprover(record2.lockedBy)}) still names the member's earlier approval, so it does not cover the member design about to merge. No own spec file changed. Fix: run \`wairon lock\` here to pin the member(s)' new approval (the same lock records anything else the gate identity covers that moved alongside), and commit the updated .wai/lock.json.`
|
|
49972
|
+
};
|
|
49973
|
+
}
|
|
49206
49974
|
return {
|
|
49207
49975
|
state: "stale",
|
|
49208
49976
|
approved: false,
|
|
49209
49977
|
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.`
|
|
49210
49978
|
};
|
|
49979
|
+
}
|
|
49211
49980
|
default:
|
|
49212
49981
|
return {
|
|
49213
49982
|
state: "unlocked",
|
|
49214
49983
|
approved: !strict,
|
|
49215
|
-
message: strict ? "No approval on record (.wai/lock.json is absent), and --strict asks for one. Run `wairon lock` and commit the record." : "No approval on record (.wai/lock.json is absent) \u2014
|
|
49984
|
+
message: strict ? "No approval on record (.wai/lock.json is absent), and --strict asks for one. Run `wairon lock` and commit the record." : "No approval on record (.wai/lock.json is absent), so nothing is gated: plain `wairon lock-check` fails only a committed approval that no longer matches the design, and a project with no .wai/lock.json \u2014 never locked, or the record deleted \u2014 reads the same. CI should run `wairon lock-check --strict`, which fails here. Run `wairon lock` and commit the record to approve the design."
|
|
49216
49985
|
};
|
|
49217
49986
|
}
|
|
49218
49987
|
}
|
|
49988
|
+
function memberFolders() {
|
|
49989
|
+
const folders = /* @__PURE__ */ new Map();
|
|
49990
|
+
try {
|
|
49991
|
+
const family = graph();
|
|
49992
|
+
for (const node of family.nodes) if (node.namespace !== "") folders.set(node.namespace, node.directory);
|
|
49993
|
+
for (const external of family.nodes.find((n) => n.namespace === "")?.externals ?? []) {
|
|
49994
|
+
if (external.role === "member" && external.directory) folders.set(external.alias, external.directory);
|
|
49995
|
+
}
|
|
49996
|
+
} catch {
|
|
49997
|
+
}
|
|
49998
|
+
return folders;
|
|
49999
|
+
}
|
|
50000
|
+
function describeMemberAt(member, folders) {
|
|
50001
|
+
const dir = folders.get(member.key);
|
|
50002
|
+
const shown2 = dir ? path40.relative(process.cwd(), dir) || "." : void 0;
|
|
50003
|
+
const state = member.state === "drifted" ? member.upgraded ? "approved under an earlier gate identity \u2014 re-lock it once" : "changed since its own approval" : "never approved";
|
|
50004
|
+
return `${member.alias ?? member.key} (${state}${shown2 ? `, in ${shown2}` : ""})`;
|
|
50005
|
+
}
|
|
50006
|
+
function repinOnly(record2) {
|
|
50007
|
+
const diff3 = diffAgainstApproval();
|
|
50008
|
+
if (!diff3 || diffSize(diff3) > 0) return null;
|
|
50009
|
+
const direct = familyApprovals(1).filter((a) => a.parent === "");
|
|
50010
|
+
const members = direct.filter((a) => a.as !== "part");
|
|
50011
|
+
const current2 = new Set(direct.map((m) => m.alias ?? m.key));
|
|
50012
|
+
if (Object.keys(record2.members ?? {}).some((alias) => !current2.has(alias))) return null;
|
|
50013
|
+
const moved = [];
|
|
50014
|
+
for (const m of members) {
|
|
50015
|
+
if (m.pinned === "unpinned") return null;
|
|
50016
|
+
if (m.pinned !== "moved") continue;
|
|
50017
|
+
if (m.state !== "approved") return null;
|
|
50018
|
+
moved.push(m.alias ?? m.key);
|
|
50019
|
+
}
|
|
50020
|
+
return moved.length > 0 ? moved : null;
|
|
50021
|
+
}
|
|
49219
50022
|
function whatMoved(record2) {
|
|
49220
50023
|
const diff3 = diffAgainstApproval();
|
|
49221
50024
|
const own = diff3 ? diffSize(diff3) : null;
|
|
49222
|
-
const
|
|
50025
|
+
const direct = familyApprovals(1).filter((a) => a.parent === "");
|
|
50026
|
+
const members = direct.filter((a) => a.as !== "part");
|
|
49223
50027
|
const named2 = [];
|
|
49224
50028
|
for (const m of members) {
|
|
49225
50029
|
const name = m.alias ?? m.key;
|
|
@@ -49227,7 +50031,7 @@ function whatMoved(record2) {
|
|
|
49227
50031
|
else if (m.pinned === "moved") named2.push(`${name} (its approval moved since this one was taken${m.state === "approved" ? "" : `; now ${m.state}`})`);
|
|
49228
50032
|
else if (m.state !== "approved") named2.push(`${name} (${m.state} at its own root)`);
|
|
49229
50033
|
}
|
|
49230
|
-
const current2 = new Set(
|
|
50034
|
+
const current2 = new Set(direct.map((m) => m.alias ?? m.key));
|
|
49231
50035
|
for (const alias of Object.keys(record2.members ?? {})) {
|
|
49232
50036
|
if (!current2.has(alias)) named2.push(`${alias} (removed since the approval)`);
|
|
49233
50037
|
}
|
|
@@ -49344,7 +50148,7 @@ async function runLock(options, gate, captured) {
|
|
|
49344
50148
|
members: memberPins(members),
|
|
49345
50149
|
...gate.analysis ? { code: withoutCodes(gate.analysis) } : {}
|
|
49346
50150
|
};
|
|
49347
|
-
|
|
50151
|
+
writeLockRecord2(record2);
|
|
49348
50152
|
return record2;
|
|
49349
50153
|
}
|
|
49350
50154
|
function memberPins(members) {
|
|
@@ -49573,6 +50377,7 @@ async function runValidate(options = {}) {
|
|
|
49573
50377
|
init_lock();
|
|
49574
50378
|
init_paths();
|
|
49575
50379
|
init_fs();
|
|
50380
|
+
init_project();
|
|
49576
50381
|
init_mcp();
|
|
49577
50382
|
|
|
49578
50383
|
// src/commands/status.ts
|
|
@@ -49619,7 +50424,7 @@ function refuseUnreadableTree(explanation) {
|
|
|
49619
50424
|
function withoutTrailingNewline(text3) {
|
|
49620
50425
|
return text3.endsWith("\n") ? text3.slice(0, -1) : text3;
|
|
49621
50426
|
}
|
|
49622
|
-
async function runStatus(options = {}) {
|
|
50427
|
+
async function runStatus(options = {}, listAll = false) {
|
|
49623
50428
|
assertProjectInitialized();
|
|
49624
50429
|
printPendingTransactions();
|
|
49625
50430
|
let approvals;
|
|
@@ -49638,12 +50443,15 @@ async function runStatus(options = {}) {
|
|
|
49638
50443
|
const lock = approvalVerdict(approvals);
|
|
49639
50444
|
if (lock.text.trim()) {
|
|
49640
50445
|
logger.blank();
|
|
49641
|
-
|
|
49642
|
-
|
|
49643
|
-
|
|
49644
|
-
|
|
49645
|
-
));
|
|
49646
|
-
}
|
|
50446
|
+
const lines = lock.text.trim().split("\n");
|
|
50447
|
+
const text3 = listAll && lock.moved && lock.moved.length > 0 ? [lines[0], ...lock.moved.map((p) => ` ${p}`), ...lines.slice(1).filter((l) => !l.startsWith(" "))].join("\n") : lock.text.trim();
|
|
50448
|
+
if (lock.drifted) console.log(import_chalk4.default.yellow(`\u26A0 ${text3}`));
|
|
50449
|
+
else logger.info(text3);
|
|
50450
|
+
if (!listAll && lock.moved && lock.moved.length > 5) logger.info(import_chalk4.default.gray("`wairon status --all` lists every spec that moved."));
|
|
50451
|
+
}
|
|
50452
|
+
logger.info(import_chalk4.default.gray(
|
|
50453
|
+
"The percentages measure authoring progress, not approval: 80% once a component's component, contract and implementation specs are written, 100% once its implementation names source files that exist (capped at 50% while any of them is draft or design). Approval is the lock record (.wai/lock.json); `wairon lock` changes no spec."
|
|
50454
|
+
));
|
|
49647
50455
|
logger.blank();
|
|
49648
50456
|
}
|
|
49649
50457
|
function printPendingTransactions() {
|
|
@@ -49777,14 +50585,14 @@ var http = __toESM(require("http"));
|
|
|
49777
50585
|
var https = __toESM(require("https"));
|
|
49778
50586
|
init_defaults();
|
|
49779
50587
|
function downloadFile(url, dest) {
|
|
49780
|
-
return new Promise((
|
|
50588
|
+
return new Promise((resolve57, reject) => {
|
|
49781
50589
|
const file = fs31.createWriteStream(dest);
|
|
49782
50590
|
const get4 = url.startsWith("https://") ? https.get : http.get;
|
|
49783
50591
|
get4(url, { headers: { "User-Agent": `wairon/${WAIRON_VERSION}` }, agent: false }, (res) => {
|
|
49784
50592
|
if (res.statusCode === 301 || res.statusCode === 302) {
|
|
49785
50593
|
file.close();
|
|
49786
50594
|
res.destroy();
|
|
49787
|
-
downloadFile(res.headers.location, dest).then(
|
|
50595
|
+
downloadFile(res.headers.location, dest).then(resolve57).catch(reject);
|
|
49788
50596
|
return;
|
|
49789
50597
|
}
|
|
49790
50598
|
if (res.statusCode !== 200) {
|
|
@@ -49796,7 +50604,7 @@ function downloadFile(url, dest) {
|
|
|
49796
50604
|
res.pipe(file);
|
|
49797
50605
|
file.on("finish", () => {
|
|
49798
50606
|
res.destroy();
|
|
49799
|
-
file.close(() =>
|
|
50607
|
+
file.close(() => resolve57());
|
|
49800
50608
|
});
|
|
49801
50609
|
file.on("error", (err) => {
|
|
49802
50610
|
res.destroy();
|
|
@@ -50138,6 +50946,13 @@ function uninstallStorePack(spec) {
|
|
|
50138
50946
|
function whichPack(spec) {
|
|
50139
50947
|
const { name, version } = parseNameAtVersion(spec);
|
|
50140
50948
|
const resolved = resolveInstalledPack(name, version);
|
|
50949
|
+
const bundled = bundledSelectionRef(name, version);
|
|
50950
|
+
if (bundled) {
|
|
50951
|
+
console.log(`${import_chalk8.default.bold(name)} ${import_chalk8.default.cyan(`v${bundled.version ?? "?"}`)} ${import_chalk8.default.dim("(bundled in this project \u2014 .wai/packs/)")}`);
|
|
50952
|
+
console.log(` path ${import_chalk8.default.dim(bundled.ref)}`);
|
|
50953
|
+
console.log(` store ${resolved ? import_chalk8.default.dim(`also installed: v${resolved.version} at ${resolved.path} (the bundle applies here)`) : import_chalk8.default.dim("not installed in the store \u2014 the bundle is all this project needs")}`);
|
|
50954
|
+
return;
|
|
50955
|
+
}
|
|
50141
50956
|
if (!resolved) {
|
|
50142
50957
|
logger.error(`No pack "${spec}" is installed in the store (${packStoreDir()}).`);
|
|
50143
50958
|
const names = [...new Set(listInstalledPacks().map((p) => p.name))];
|
|
@@ -50147,6 +50962,24 @@ function whichPack(spec) {
|
|
|
50147
50962
|
}
|
|
50148
50963
|
printInstalled(resolved, version === void 0);
|
|
50149
50964
|
}
|
|
50965
|
+
function bundledSelectionRef(name, version) {
|
|
50966
|
+
try {
|
|
50967
|
+
if (!projectConfigExists()) return null;
|
|
50968
|
+
const root = getProjectRoot();
|
|
50969
|
+
for (const entry of loadProjectConfig()?.extensions?.packs ?? []) {
|
|
50970
|
+
if (typeof entry === "string" || entry.name !== name) continue;
|
|
50971
|
+
if (version !== void 0 && entry.version !== void 0 && entry.version !== version) continue;
|
|
50972
|
+
const ref = packEntryRef(entry, root);
|
|
50973
|
+
if (!ref) continue;
|
|
50974
|
+
const rel2 = path52.relative(path52.join(root, ".wai", "packs"), path52.resolve(root, ref));
|
|
50975
|
+
if (rel2.startsWith("..") || path52.isAbsolute(rel2)) continue;
|
|
50976
|
+
const segments = rel2.split(path52.sep);
|
|
50977
|
+
return { ref, version: entry.version ?? (segments.length > 1 ? segments[1] : void 0) };
|
|
50978
|
+
}
|
|
50979
|
+
} catch {
|
|
50980
|
+
}
|
|
50981
|
+
return null;
|
|
50982
|
+
}
|
|
50150
50983
|
function printInstalled(pack, latestByDefault) {
|
|
50151
50984
|
console.log(`${import_chalk8.default.bold(pack.name)} ${import_chalk8.default.cyan(`v${pack.version}`)}${latestByDefault ? import_chalk8.default.dim(" (latest installed)") : ""}`);
|
|
50152
50985
|
console.log(` path ${import_chalk8.default.dim(pack.path)}`);
|
|
@@ -51139,7 +51972,22 @@ async function statusCommand(opts) {
|
|
|
51139
51972
|
`);
|
|
51140
51973
|
return;
|
|
51141
51974
|
}
|
|
51142
|
-
await runStatus({ subsystem: opts.subsystem, ...opts.recursive === false ? { memberDepth: 0 } : {} });
|
|
51975
|
+
await runStatus({ subsystem: opts.subsystem, ...opts.recursive === false ? { memberDepth: 0 } : {} }, opts.all === true);
|
|
51976
|
+
}
|
|
51977
|
+
var ANNOUNCE_SILENT = /* @__PURE__ */ new Set(["init", "host", "remote", "login", "logout", "serve", "dev", "update", "aliases", "help", "mcp serve"]);
|
|
51978
|
+
function announceBinding(commandPath) {
|
|
51979
|
+
const first = commandPath.split(" ")[0];
|
|
51980
|
+
if (ANNOUNCE_SILENT.has(first) || ANNOUNCE_SILENT.has(commandPath)) return;
|
|
51981
|
+
const root = getProjectRootOverride() ?? resolveProjectBinding(process.cwd())?.root;
|
|
51982
|
+
if (!root) return;
|
|
51983
|
+
let id = null;
|
|
51984
|
+
try {
|
|
51985
|
+
const config = runWithProjectRoot(root, () => loadProjectConfig());
|
|
51986
|
+
if (config) id = effectiveProjectId(config);
|
|
51987
|
+
} catch {
|
|
51988
|
+
}
|
|
51989
|
+
process.stderr.write(`project ${id ?? path55.basename(root)} at ${root}
|
|
51990
|
+
`);
|
|
51143
51991
|
}
|
|
51144
51992
|
async function lockCheckCommand(options) {
|
|
51145
51993
|
const verdict = checkApproval(options.strict === true);
|
|
@@ -51196,6 +52044,10 @@ async function runAgent(action, id) {
|
|
|
51196
52044
|
logger.info("Owned paths:");
|
|
51197
52045
|
for (const p of brief.ownedPaths) logger.info(` ${p}`);
|
|
51198
52046
|
}
|
|
52047
|
+
if (brief.codeFence) {
|
|
52048
|
+
logger.info(brief.codeFence.length > 0 ? "Code write fence:" : "Code write fence: none declared yet (name the planned files when spawning)");
|
|
52049
|
+
for (const p of brief.codeFence) logger.info(` ${p}`);
|
|
52050
|
+
}
|
|
51199
52051
|
if (brief.readPaths && brief.readPaths.length > 0) {
|
|
51200
52052
|
logger.info("Read paths:");
|
|
51201
52053
|
for (const p of brief.readPaths) logger.info(` ${p}`);
|
|
@@ -51478,6 +52330,8 @@ var nodePath3 = __toESM(require("path"));
|
|
|
51478
52330
|
init_logger();
|
|
51479
52331
|
init_errors();
|
|
51480
52332
|
var import_inquirer3 = __toESM(require("inquirer"));
|
|
52333
|
+
init_mcp();
|
|
52334
|
+
init_fs();
|
|
51481
52335
|
init_core();
|
|
51482
52336
|
function requireProject() {
|
|
51483
52337
|
if (!projectConfigExists()) {
|
|
@@ -51570,6 +52424,28 @@ async function runMigration(request, options) {
|
|
|
51570
52424
|
}
|
|
51571
52425
|
const report4 = apply2(planned);
|
|
51572
52426
|
printOutcome(report4);
|
|
52427
|
+
if (report4.applied) await provisionMadeProjects(request);
|
|
52428
|
+
}
|
|
52429
|
+
async function provisionMadeProjects(request) {
|
|
52430
|
+
const madeProject = request.verb === "promote" || request.verb === "externalize" && request.as === "project";
|
|
52431
|
+
if (!madeProject) return;
|
|
52432
|
+
const root = nodePath3.resolve(getProjectRoot());
|
|
52433
|
+
let made;
|
|
52434
|
+
if (request.verb === "externalize") made = request.path ? nodePath3.resolve(root, request.path) : void 0;
|
|
52435
|
+
else made = graph().nodes.find((n) => n.parent === "" && n.mountAlias === request.alias)?.directory;
|
|
52436
|
+
for (const dir of made ? [nodePath3.resolve(made)] : []) {
|
|
52437
|
+
if (dir === root || !pathExists(nodePath3.join(dir, ".wai", "project.yaml"))) continue;
|
|
52438
|
+
const shown2 = shownDir(dir);
|
|
52439
|
+
try {
|
|
52440
|
+
await runWithProjectRoot(dir, async () => {
|
|
52441
|
+
await runGenerate({});
|
|
52442
|
+
runMcpInstall({ global: false, backend: "claude" });
|
|
52443
|
+
});
|
|
52444
|
+
logger.success(`Set ${shown2} up for its own sessions (guide, skills, .mcp.json): a teammate opens that folder in their AI tool.`);
|
|
52445
|
+
} catch (e) {
|
|
52446
|
+
logger.warn(`Could not set ${shown2} up for its own sessions (${e instanceof Error ? e.message : String(e)}). Run there: \`wairon generate\` and \`wairon mcp install --backend claude\`.`);
|
|
52447
|
+
}
|
|
52448
|
+
}
|
|
51573
52449
|
}
|
|
51574
52450
|
var ownerLabel = (key) => key === "" ? "the top project" : key;
|
|
51575
52451
|
var SCREENFUL = 20;
|
|
@@ -51691,10 +52567,11 @@ async function runInit(options = {}) {
|
|
|
51691
52567
|
}
|
|
51692
52568
|
if (!options.yes && !process.stdin.isTTY) {
|
|
51693
52569
|
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,
|
|
52570
|
+
"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, target claude). Nothing was written."
|
|
51695
52571
|
);
|
|
51696
52572
|
}
|
|
51697
52573
|
if (ancestorRoot) {
|
|
52574
|
+
if (options.yes) throw new WaironError(boundByParentMessage(ancestorRoot, cwd));
|
|
51698
52575
|
await runInitAsMember(ancestorRoot, cwd, options);
|
|
51699
52576
|
return;
|
|
51700
52577
|
}
|
|
@@ -51707,23 +52584,35 @@ async function runInit(options = {}) {
|
|
|
51707
52584
|
function memberAliasOf(name) {
|
|
51708
52585
|
return name.toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "") || "member";
|
|
51709
52586
|
}
|
|
52587
|
+
function boundByParentCommands(parentRoot, cwd) {
|
|
52588
|
+
const relPath = (path58.relative(parentRoot, cwd) || ".").split(path58.sep).join("/");
|
|
52589
|
+
const alias = memberAliasOf(path58.basename(cwd));
|
|
52590
|
+
return [
|
|
52591
|
+
`make it a member project of that parent: cd "${parentRoot}" && wairon member add ${alias} ${relPath} --project`,
|
|
52592
|
+
"or start an independent project here: make this folder its own repository (`git init`), then re-run `wairon init`"
|
|
52593
|
+
];
|
|
52594
|
+
}
|
|
52595
|
+
function boundByParentMessage(parentRoot, cwd) {
|
|
52596
|
+
const [member, independent] = boundByParentCommands(parentRoot, cwd);
|
|
52597
|
+
return `This folder is bound by the wairon project at ${parentRoot}, and --yes cannot answer whether it should join it \u2014 init never edits a project.yaml outside this folder without asking. Nothing was written. To ${member}; ${independent}.`;
|
|
52598
|
+
}
|
|
51710
52599
|
async function runInitAsMember(parentRoot, cwd, options) {
|
|
51711
52600
|
const relPath = (path58.relative(parentRoot, cwd) || ".").split(path58.sep).join("/");
|
|
51712
52601
|
const defaultAlias = memberAliasOf(path58.basename(cwd));
|
|
51713
|
-
logger.info(`Detected a parent wairon project at ${parentRoot}
|
|
52602
|
+
logger.info(`Detected a parent wairon project at ${parentRoot} \u2014 it binds this folder.`);
|
|
51714
52603
|
logger.info(`This directory ("${relPath}") is not yet a wairon project.`);
|
|
51715
52604
|
if (!options.yes) {
|
|
51716
52605
|
const { proceed } = await import_inquirer4.default.prompt([
|
|
51717
52606
|
{
|
|
51718
52607
|
type: "confirm",
|
|
51719
52608
|
name: "proceed",
|
|
51720
|
-
message: `Create "${relPath}" as a member project of the parent project
|
|
51721
|
-
default:
|
|
52609
|
+
message: `Create "${relPath}" as a member project of the parent project? (edits ${path58.join(parentRoot, ".wai", "project.yaml")})`,
|
|
52610
|
+
default: false
|
|
51722
52611
|
}
|
|
51723
52612
|
]);
|
|
51724
52613
|
if (!proceed) {
|
|
51725
52614
|
logger.info("Cancelled \u2014 no changes made.");
|
|
51726
|
-
|
|
52615
|
+
for (const line2 of boundByParentCommands(parentRoot, cwd)) logger.info(`To ${line2}.`);
|
|
51727
52616
|
return;
|
|
51728
52617
|
}
|
|
51729
52618
|
}
|
|
@@ -51763,13 +52652,13 @@ async function runInitNonInteractive() {
|
|
|
51763
52652
|
const cwd = process.cwd();
|
|
51764
52653
|
const projectName = path58.basename(cwd);
|
|
51765
52654
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
51766
|
-
const targets = [defaultTargetConfig("claude")
|
|
52655
|
+
const targets = [defaultTargetConfig("claude")];
|
|
51767
52656
|
const projectConfig = buildProjectConfig(projectName, targets, now, "backend");
|
|
51768
52657
|
const guidePlan = {
|
|
51769
52658
|
claudeGlobal: false,
|
|
51770
52659
|
claudeLocal: true,
|
|
51771
52660
|
geminiGlobal: false,
|
|
51772
|
-
geminiLocal:
|
|
52661
|
+
geminiLocal: false
|
|
51773
52662
|
};
|
|
51774
52663
|
await executeInit(
|
|
51775
52664
|
projectName,
|
|
@@ -52442,7 +53331,7 @@ function releaseChannelLabel(tag) {
|
|
|
52442
53331
|
return version.slice(dash + 1).replace(/\.\d+$/, "") || "stable";
|
|
52443
53332
|
}
|
|
52444
53333
|
function fetchReleases(repo) {
|
|
52445
|
-
return new Promise((
|
|
53334
|
+
return new Promise((resolve57, reject) => {
|
|
52446
53335
|
const url = `https://api.github.com/repos/${repo}/releases?per_page=100`;
|
|
52447
53336
|
const options = {
|
|
52448
53337
|
headers: {
|
|
@@ -52462,7 +53351,7 @@ function fetchReleases(repo) {
|
|
|
52462
53351
|
return;
|
|
52463
53352
|
}
|
|
52464
53353
|
try {
|
|
52465
|
-
|
|
53354
|
+
resolve57(JSON.parse(data));
|
|
52466
53355
|
} catch {
|
|
52467
53356
|
reject(new Error("Failed to parse GitHub API response"));
|
|
52468
53357
|
}
|
|
@@ -52634,7 +53523,7 @@ async function filteredCheckbox(config) {
|
|
|
52634
53523
|
32,
|
|
52635
53524
|
Math.max(...items.map((i) => i.label.length))
|
|
52636
53525
|
);
|
|
52637
|
-
return new Promise((
|
|
53526
|
+
return new Promise((resolve57) => {
|
|
52638
53527
|
const checked = /* @__PURE__ */ new Set();
|
|
52639
53528
|
let cursor = 0;
|
|
52640
53529
|
let filterIdx = 0;
|
|
@@ -52701,7 +53590,7 @@ async function filteredCheckbox(config) {
|
|
|
52701
53590
|
);
|
|
52702
53591
|
teardown();
|
|
52703
53592
|
const result = items.filter((_, idx) => checked.has(idx)).map((i) => i.value);
|
|
52704
|
-
|
|
53593
|
+
resolve57(result);
|
|
52705
53594
|
}
|
|
52706
53595
|
function abort() {
|
|
52707
53596
|
process.stdout.write("\n");
|
|
@@ -53398,7 +54287,7 @@ async function runDoctor(options = {}) {
|
|
|
53398
54287
|
const lock = readLockState(computeGateStateId());
|
|
53399
54288
|
if (lock.state === "locked") {
|
|
53400
54289
|
console.log(import_chalk17.default.bold("Lock"));
|
|
53401
|
-
line(tally, "ok", `
|
|
54290
|
+
line(tally, "ok", `approved at ${lock.record.lockedAt} by ${describeApprover(lock.record.lockedBy)}`);
|
|
53402
54291
|
if (!lock.record.specs) {
|
|
53403
54292
|
line(tally, "warn", "this lock predates per-spec approval \u2014 re-lock so `wairon status` can name what drifts");
|
|
53404
54293
|
}
|
|
@@ -53415,7 +54304,7 @@ async function runDoctor(options = {}) {
|
|
|
53415
54304
|
line(
|
|
53416
54305
|
tally,
|
|
53417
54306
|
"warn",
|
|
53418
|
-
`stale \u2014 the design or something it was approved under changed since ${lock.record.lockedAt}, so
|
|
54307
|
+
`stale \u2014 the design or something it was approved under changed since ${lock.record.lockedAt}, so the approval no longer covers it. Run \`wairon lock\` to approve the current design (\`wairon status\` names the specs that moved).`
|
|
53419
54308
|
);
|
|
53420
54309
|
logger.blank();
|
|
53421
54310
|
}
|
|
@@ -54068,21 +54957,21 @@ async function runDiagram(options = {}) {
|
|
|
54068
54957
|
const formatted = applyFormat(options);
|
|
54069
54958
|
if (formatted.canvas && !formatted.all) {
|
|
54070
54959
|
const dest2 = formatted.out ?? path62.join(AI_PATHS.docsDir(), "diagrams", "canvas.html");
|
|
54071
|
-
writeArtifact(dest2,
|
|
54960
|
+
writeArtifact(dest2, renderDiagram2("canvas", canvasRelations(formatted)));
|
|
54072
54961
|
logger.success(`Interactive canvas written to ${dest2}`);
|
|
54073
54962
|
logger.info("Open it in a browser \u2014 fully self-contained (works offline).");
|
|
54074
54963
|
return;
|
|
54075
54964
|
}
|
|
54076
54965
|
if (formatted.drawio && !formatted.all) {
|
|
54077
54966
|
const dest2 = formatted.out ?? path62.join(AI_PATHS.docsDir(), "diagrams", "architecture.drawio");
|
|
54078
|
-
writeArtifact(dest2,
|
|
54967
|
+
writeArtifact(dest2, renderDiagram2("drawio"));
|
|
54079
54968
|
logger.success(`draw.io diagram written to ${dest2}`);
|
|
54080
54969
|
logger.info("Open with draw.io / diagrams.net (or import into tools that accept the format).");
|
|
54081
54970
|
return;
|
|
54082
54971
|
}
|
|
54083
54972
|
if (formatted.excalidraw && !formatted.all) {
|
|
54084
54973
|
const dest2 = formatted.out ?? path62.join(AI_PATHS.docsDir(), "diagrams", "architecture.excalidraw");
|
|
54085
|
-
writeArtifact(dest2,
|
|
54974
|
+
writeArtifact(dest2, renderDiagram2("excalidraw"));
|
|
54086
54975
|
logger.success(`Excalidraw scene written to ${dest2}`);
|
|
54087
54976
|
logger.info("Open with excalidraw.com or the VS Code extension.");
|
|
54088
54977
|
return;
|
|
@@ -54090,7 +54979,7 @@ async function runDiagram(options = {}) {
|
|
|
54090
54979
|
const wantsMermaid = formatted.format?.toLowerCase().startsWith("mermaid") || !!formatted.subsystem || !!formatted.sequence;
|
|
54091
54980
|
if (!formatted.all && !formatted.sequence && !wantsMermaid) {
|
|
54092
54981
|
const dest2 = formatted.out ?? path62.join(AI_PATHS.docsDir(), "diagrams", "canvas.html");
|
|
54093
|
-
writeArtifact(dest2,
|
|
54982
|
+
writeArtifact(dest2, renderDiagram2("canvas", canvasRelations(formatted)));
|
|
54094
54983
|
logger.success(`Interactive canvas written to ${dest2}`);
|
|
54095
54984
|
logger.info("Open it in a browser \u2014 fully self-contained (works offline). Other formats: --format mermaid|drawio|excalidraw.");
|
|
54096
54985
|
return;
|
|
@@ -54105,9 +54994,9 @@ async function runDiagram(options = {}) {
|
|
|
54105
54994
|
for (const file of files) {
|
|
54106
54995
|
writeArtifact(path62.join(outDir, file.relPath), toMarkdown(file));
|
|
54107
54996
|
}
|
|
54108
|
-
writeArtifact(path62.join(outDir, "canvas.html"),
|
|
54109
|
-
writeArtifact(path62.join(outDir, "architecture.drawio"),
|
|
54110
|
-
writeArtifact(path62.join(outDir, "architecture.excalidraw"),
|
|
54997
|
+
writeArtifact(path62.join(outDir, "canvas.html"), renderDiagram2("canvas", canvasRelations(formatted)));
|
|
54998
|
+
writeArtifact(path62.join(outDir, "architecture.drawio"), renderDiagram2("drawio"));
|
|
54999
|
+
writeArtifact(path62.join(outDir, "architecture.excalidraw"), renderDiagram2("excalidraw"));
|
|
54111
55000
|
const graph2 = loadSpecGraph();
|
|
54112
55001
|
const indexPath = path62.join(outDir, "README.md");
|
|
54113
55002
|
writeArtifact(indexPath, diagramSetIndex(files, graph2.systemName));
|
|
@@ -54132,7 +55021,7 @@ async function runDiagram(options = {}) {
|
|
|
54132
55021
|
title = `${formatted.subsystem} \u2014 components`;
|
|
54133
55022
|
defaultDest = path62.join(diagramsDir, "subsystems", `${formatted.subsystem.replace(/::/g, "--")}.md`);
|
|
54134
55023
|
} else {
|
|
54135
|
-
mermaid =
|
|
55024
|
+
mermaid = renderDiagram2("mermaid");
|
|
54136
55025
|
title = "Component architecture";
|
|
54137
55026
|
defaultDest = path62.join(diagramsDir, "system.md");
|
|
54138
55027
|
}
|
|
@@ -54178,7 +55067,7 @@ var path63 = __toESM(require("path"));
|
|
|
54178
55067
|
function registryPath(dataDir) {
|
|
54179
55068
|
return path63.join(dataDir, "projects.json");
|
|
54180
55069
|
}
|
|
54181
|
-
function
|
|
55070
|
+
function load5(dataDir) {
|
|
54182
55071
|
try {
|
|
54183
55072
|
return JSON.parse(fs40.readFileSync(registryPath(dataDir), "utf8"));
|
|
54184
55073
|
} catch {
|
|
@@ -54198,8 +55087,8 @@ var ID_RE = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
|
|
54198
55087
|
function isValidProjectId(id) {
|
|
54199
55088
|
return typeof id === "string" && ID_RE.test(id);
|
|
54200
55089
|
}
|
|
54201
|
-
function
|
|
54202
|
-
return
|
|
55090
|
+
function load6(dataDir) {
|
|
55091
|
+
return load5(dataDir);
|
|
54203
55092
|
}
|
|
54204
55093
|
function save2(dataDir, records) {
|
|
54205
55094
|
save(dataDir, records.map((r) => r.parentProjectId ? withoutRoot(r) : r));
|
|
@@ -54246,7 +55135,7 @@ function familyRootOf(records, record2) {
|
|
|
54246
55135
|
}
|
|
54247
55136
|
function existingProjectRoot(dataDir, id) {
|
|
54248
55137
|
if (!isValidProjectId(id)) return null;
|
|
54249
|
-
const records =
|
|
55138
|
+
const records = load6(dataDir);
|
|
54250
55139
|
const rec = records.find((r) => r.id === id);
|
|
54251
55140
|
return rec ? derivedRoot(records, rec) : null;
|
|
54252
55141
|
}
|
|
@@ -54256,7 +55145,7 @@ function declaredSubsystemIds(dataDir, id) {
|
|
|
54256
55145
|
}
|
|
54257
55146
|
function rootOnDisk(dataDir, id) {
|
|
54258
55147
|
if (!isValidProjectId(id)) return null;
|
|
54259
|
-
const records =
|
|
55148
|
+
const records = load6(dataDir);
|
|
54260
55149
|
const rec = records.find((r) => r.id === id);
|
|
54261
55150
|
const root = rec ? derivedRoot(records, rec) : null;
|
|
54262
55151
|
return root && fs41.existsSync(root) ? root : null;
|
|
@@ -54275,7 +55164,7 @@ function createProjectRecord(dataDir, id) {
|
|
|
54275
55164
|
if (!isValidProjectId(id)) {
|
|
54276
55165
|
throw new Error(`Invalid project id "${id}" (allowed: lowercase letters, digits, hyphen).`);
|
|
54277
55166
|
}
|
|
54278
|
-
const records =
|
|
55167
|
+
const records = load6(dataDir);
|
|
54279
55168
|
if (records.some((r) => r.id === id)) {
|
|
54280
55169
|
throw new Error(`Project "${id}" already exists.`);
|
|
54281
55170
|
}
|
|
@@ -54295,7 +55184,7 @@ function registerProjectRecord(dataDir, id, rootPath) {
|
|
|
54295
55184
|
if (!isValidProjectId(id)) {
|
|
54296
55185
|
throw new Error(`Invalid project id "${id}" (allowed: lowercase letters, digits, hyphen).`);
|
|
54297
55186
|
}
|
|
54298
|
-
const records =
|
|
55187
|
+
const records = load6(dataDir);
|
|
54299
55188
|
const existing = records.find((r) => r.id === id);
|
|
54300
55189
|
const record2 = {
|
|
54301
55190
|
id,
|
|
@@ -54309,7 +55198,7 @@ function registerProjectRecord(dataDir, id, rootPath) {
|
|
|
54309
55198
|
}
|
|
54310
55199
|
function registerMemberRecord(dataDir, record2) {
|
|
54311
55200
|
const memberPath = validMemberShape(record2);
|
|
54312
|
-
const records =
|
|
55201
|
+
const records = load6(dataDir);
|
|
54313
55202
|
refuseParent(records, record2);
|
|
54314
55203
|
const existing = records.find((r) => r.id === record2.id);
|
|
54315
55204
|
const member = {
|
|
@@ -54363,7 +55252,7 @@ function setProjectRecordStatus(dataDir, id, status2, reason) {
|
|
|
54363
55252
|
if (status2 !== "active" && status2 !== "disabled") {
|
|
54364
55253
|
throw new Error(`Invalid project status "${status2}" (allowed: active, disabled).`);
|
|
54365
55254
|
}
|
|
54366
|
-
const records =
|
|
55255
|
+
const records = load6(dataDir);
|
|
54367
55256
|
const existing = records.find((r) => r.id === id);
|
|
54368
55257
|
if (!existing) throw new Error(`Unknown project "${id}".`);
|
|
54369
55258
|
const { disabledReason: _was, ...rest } = existing;
|
|
@@ -54373,11 +55262,11 @@ function setProjectRecordStatus(dataDir, id, status2, reason) {
|
|
|
54373
55262
|
return withDerivedRoot(next, record2);
|
|
54374
55263
|
}
|
|
54375
55264
|
function listProjectRecords(dataDir) {
|
|
54376
|
-
const records =
|
|
55265
|
+
const records = load6(dataDir);
|
|
54377
55266
|
return records.map((r) => withDerivedRoot(records, r));
|
|
54378
55267
|
}
|
|
54379
55268
|
function removeProjectRecord(dataDir, id) {
|
|
54380
|
-
const records =
|
|
55269
|
+
const records = load6(dataDir);
|
|
54381
55270
|
const rec = records.find((r) => r.id === id);
|
|
54382
55271
|
if (!rec) return;
|
|
54383
55272
|
if (!rec.parentProjectId) {
|
|
@@ -54393,7 +55282,7 @@ function removeProjectRecord(dataDir, id) {
|
|
|
54393
55282
|
save2(dataDir, records.filter((r) => r.id !== id));
|
|
54394
55283
|
}
|
|
54395
55284
|
function listFamilyRecords(dataDir, id) {
|
|
54396
|
-
const records =
|
|
55285
|
+
const records = load6(dataDir);
|
|
54397
55286
|
const rec = records.find((r) => r.id === id);
|
|
54398
55287
|
if (!rec) return [];
|
|
54399
55288
|
const root = familyRootOf(records, rec);
|
|
@@ -54408,7 +55297,7 @@ function listFamilyRecords(dataDir, id) {
|
|
|
54408
55297
|
return out;
|
|
54409
55298
|
}
|
|
54410
55299
|
function projectRepositoryScope(dataDir, id) {
|
|
54411
|
-
const records =
|
|
55300
|
+
const records = load6(dataDir);
|
|
54412
55301
|
const rec = records.find((r) => r.id === id);
|
|
54413
55302
|
if (!rec) return null;
|
|
54414
55303
|
const segments = [];
|
|
@@ -54495,7 +55384,7 @@ function resolveQualifiedMember(dataDir, qualifier) {
|
|
|
54495
55384
|
if (!parsed || parsed.mounts.length === 0) {
|
|
54496
55385
|
throw new Error(`invalid member qualifier "${qualifier}" (expected projectId::alias, one alias per hop)`);
|
|
54497
55386
|
}
|
|
54498
|
-
const records =
|
|
55387
|
+
const records = load6(dataDir);
|
|
54499
55388
|
const top = records.find((r) => r.id === parsed.projectId);
|
|
54500
55389
|
const topRoot = top ? derivedRoot(records, top) : null;
|
|
54501
55390
|
if (!top || top.status !== "active" || topRoot === null) throw new Error(`unknown project "${parsed.projectId}"`);
|
|
@@ -54536,7 +55425,7 @@ function memberIdAt2(dir) {
|
|
|
54536
55425
|
return { id };
|
|
54537
55426
|
}
|
|
54538
55427
|
function planMemberRecords(dataDir, id) {
|
|
54539
|
-
const records =
|
|
55428
|
+
const records = load6(dataDir);
|
|
54540
55429
|
const rec = records.find((r) => r.id === id);
|
|
54541
55430
|
if (!rec) return [];
|
|
54542
55431
|
const root = familyRootOf(records, rec);
|
|
@@ -54610,7 +55499,7 @@ function assertMintableNarrowingEntry(dataDir, entry) {
|
|
|
54610
55499
|
);
|
|
54611
55500
|
}
|
|
54612
55501
|
if (parsed.mounts.length > 0) return resolveQualifiedMember(dataDir, entry).id;
|
|
54613
|
-
if (!
|
|
55502
|
+
if (!load6(dataDir).some((r) => r.id === parsed.projectId)) throw new Error(`unknown project "${parsed.projectId}"`);
|
|
54614
55503
|
return parsed.projectId;
|
|
54615
55504
|
}
|
|
54616
55505
|
function entryRecordId(dataDir, entry) {
|
|
@@ -54638,7 +55527,7 @@ function indexResolveBinding(dataDir, principal, selector) {
|
|
|
54638
55527
|
if (targetId === null) return null;
|
|
54639
55528
|
via = target;
|
|
54640
55529
|
}
|
|
54641
|
-
const records =
|
|
55530
|
+
const records = load6(dataDir);
|
|
54642
55531
|
const rec = records.find((r) => r.id === targetId);
|
|
54643
55532
|
if (!rec || !isValidProjectId(rec.id)) return null;
|
|
54644
55533
|
if (!wildcard && !authorized.some((e) => {
|
|
@@ -54656,7 +55545,7 @@ function indexResolveBinding(dataDir, principal, selector) {
|
|
|
54656
55545
|
function indexResolveRoot(dataDir, principal, selector) {
|
|
54657
55546
|
return indexResolveBinding(dataDir, principal, selector)?.rootPath ?? null;
|
|
54658
55547
|
}
|
|
54659
|
-
function
|
|
55548
|
+
function resolveProjectBinding2(dataDir, principal, selector) {
|
|
54660
55549
|
return indexResolveBinding(dataDir, principal, selector);
|
|
54661
55550
|
}
|
|
54662
55551
|
function resolveProjectRoot(dataDir, principal, selector) {
|
|
@@ -55079,7 +55968,7 @@ function storePath2(dataDir) {
|
|
|
55079
55968
|
function assignmentKey(a) {
|
|
55080
55969
|
return [a.subjectKind, a.subjectId ?? "", a.scopeKind, a.scopeId ?? "", a.capability].join("|");
|
|
55081
55970
|
}
|
|
55082
|
-
function
|
|
55971
|
+
function load7(dataDir) {
|
|
55083
55972
|
const p = storePath2(dataDir);
|
|
55084
55973
|
let raw;
|
|
55085
55974
|
try {
|
|
@@ -55107,7 +55996,7 @@ function replaceAll(dataDir, assignments) {
|
|
|
55107
55996
|
fs43.renameSync(tmp, p);
|
|
55108
55997
|
}
|
|
55109
55998
|
function registrySet(dataDir, assignment) {
|
|
55110
|
-
const assignments =
|
|
55999
|
+
const assignments = load7(dataDir);
|
|
55111
56000
|
const key = assignmentKey(assignment);
|
|
55112
56001
|
const idx = assignments.findIndex((a) => assignmentKey(a) === key);
|
|
55113
56002
|
if (assignment.value === "inherit") {
|
|
@@ -55129,7 +56018,7 @@ function registrySet(dataDir, assignment) {
|
|
|
55129
56018
|
return stored;
|
|
55130
56019
|
}
|
|
55131
56020
|
function registryRemove(dataDir, assignmentId) {
|
|
55132
|
-
const assignments =
|
|
56021
|
+
const assignments = load7(dataDir);
|
|
55133
56022
|
const remaining = assignments.filter((a) => a.id !== assignmentId);
|
|
55134
56023
|
if (remaining.length === assignments.length) return;
|
|
55135
56024
|
replaceAll(dataDir, remaining);
|
|
@@ -55137,7 +56026,7 @@ function registryRemove(dataDir, assignmentId) {
|
|
|
55137
56026
|
function registryRemapScope(dataDir, remap) {
|
|
55138
56027
|
if (remap.length === 0) return;
|
|
55139
56028
|
const lookup2 = new Map(remap.map((r) => [r.oldId, r.newId]));
|
|
55140
|
-
const assignments =
|
|
56029
|
+
const assignments = load7(dataDir);
|
|
55141
56030
|
let changed = false;
|
|
55142
56031
|
const rewritten = assignments.map((a) => {
|
|
55143
56032
|
const next = a.scopeId ? lookup2.get(a.scopeId) : void 0;
|
|
@@ -55151,13 +56040,13 @@ function registryRemapScope(dataDir, remap) {
|
|
|
55151
56040
|
function registryRemoveForScopes(dataDir, scopeIds) {
|
|
55152
56041
|
if (scopeIds.length === 0) return;
|
|
55153
56042
|
const doomed = new Set(scopeIds);
|
|
55154
|
-
const assignments =
|
|
56043
|
+
const assignments = load7(dataDir);
|
|
55155
56044
|
const remaining = assignments.filter((a) => !(a.scopeId && doomed.has(a.scopeId)));
|
|
55156
56045
|
if (remaining.length === assignments.length) return;
|
|
55157
56046
|
replaceAll(dataDir, remaining);
|
|
55158
56047
|
}
|
|
55159
56048
|
function indexList(dataDir, scopeIds, subjectKind, subjectId, capability) {
|
|
55160
|
-
let assignments =
|
|
56049
|
+
let assignments = load7(dataDir);
|
|
55161
56050
|
if (scopeIds && scopeIds.length > 0) {
|
|
55162
56051
|
const wanted = new Set(scopeIds);
|
|
55163
56052
|
assignments = assignments.filter((a) => a.scopeId !== void 0 && wanted.has(a.scopeId));
|
|
@@ -55168,7 +56057,7 @@ function indexList(dataDir, scopeIds, subjectKind, subjectId, capability) {
|
|
|
55168
56057
|
return assignments;
|
|
55169
56058
|
}
|
|
55170
56059
|
function indexGet(dataDir, assignmentId) {
|
|
55171
|
-
return
|
|
56060
|
+
return load7(dataDir).find((a) => a.id === assignmentId) ?? null;
|
|
55172
56061
|
}
|
|
55173
56062
|
function setAssignment(dataDir, assignment) {
|
|
55174
56063
|
return registrySet(dataDir, assignment);
|
|
@@ -55240,7 +56129,7 @@ function isBuiltinRoleId(roleId) {
|
|
|
55240
56129
|
function storePath3(dataDir) {
|
|
55241
56130
|
return path67.join(dataDir, "roles.json");
|
|
55242
56131
|
}
|
|
55243
|
-
function
|
|
56132
|
+
function load8(dataDir) {
|
|
55244
56133
|
const p = storePath3(dataDir);
|
|
55245
56134
|
let raw;
|
|
55246
56135
|
try {
|
|
@@ -55271,7 +56160,7 @@ function registryCreate(dataDir, role) {
|
|
|
55271
56160
|
if (isBuiltinRoleId(role.id)) {
|
|
55272
56161
|
throw new Error(`Role "${role.id}" is a reserved built-in role and cannot be created.`);
|
|
55273
56162
|
}
|
|
55274
|
-
const roles =
|
|
56163
|
+
const roles = load8(dataDir);
|
|
55275
56164
|
if (roles.some((r) => r.id === role.id)) {
|
|
55276
56165
|
throw new Error(`Role "${role.id}" already exists.`);
|
|
55277
56166
|
}
|
|
@@ -55284,7 +56173,7 @@ function registryUpdate(dataDir, role) {
|
|
|
55284
56173
|
if (isBuiltinRoleId(role.id)) {
|
|
55285
56174
|
throw new Error(`Role "${role.id}" is a reserved built-in role and cannot be modified.`);
|
|
55286
56175
|
}
|
|
55287
|
-
const roles =
|
|
56176
|
+
const roles = load8(dataDir);
|
|
55288
56177
|
const idx = roles.findIndex((r) => r.id === role.id);
|
|
55289
56178
|
if (idx === -1) {
|
|
55290
56179
|
throw new Error(`Role "${role.id}" not found.`);
|
|
@@ -55298,7 +56187,7 @@ function registryDelete(dataDir, roleId) {
|
|
|
55298
56187
|
if (isBuiltinRoleId(roleId)) {
|
|
55299
56188
|
throw new Error(`Role "${roleId}" is a reserved built-in role and cannot be deleted.`);
|
|
55300
56189
|
}
|
|
55301
|
-
const roles =
|
|
56190
|
+
const roles = load8(dataDir);
|
|
55302
56191
|
const remaining = roles.filter((r) => r.id !== roleId);
|
|
55303
56192
|
if (remaining.length === roles.length) return;
|
|
55304
56193
|
replaceAll2(dataDir, remaining);
|
|
@@ -55307,7 +56196,7 @@ function indexListEffectiveRoles(dataDir) {
|
|
|
55307
56196
|
return [...indexListRoles(dataDir).filter((r) => !isBuiltinRoleId(r.id)), ...BUILTIN_ROLES];
|
|
55308
56197
|
}
|
|
55309
56198
|
function indexListRoles(dataDir) {
|
|
55310
|
-
return
|
|
56199
|
+
return load8(dataDir).sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
|
|
55311
56200
|
}
|
|
55312
56201
|
function createRole(dataDir, role) {
|
|
55313
56202
|
return registryCreate(dataDir, role);
|
|
@@ -55735,7 +56624,7 @@ var path69 = __toESM(require("path"));
|
|
|
55735
56624
|
function storePath5(dataDir) {
|
|
55736
56625
|
return path69.join(dataDir, "auth", "credentials.json");
|
|
55737
56626
|
}
|
|
55738
|
-
function
|
|
56627
|
+
function load9(dataDir) {
|
|
55739
56628
|
try {
|
|
55740
56629
|
return JSON.parse(fs46.readFileSync(storePath5(dataDir), "utf8"));
|
|
55741
56630
|
} catch {
|
|
@@ -55762,10 +56651,10 @@ function digestEquals(a, b) {
|
|
|
55762
56651
|
return crypto13.timingSafeEqual(ab, bb);
|
|
55763
56652
|
}
|
|
55764
56653
|
function findByTokenHash(dataDir, tokenHash) {
|
|
55765
|
-
return
|
|
56654
|
+
return load9(dataDir).find((r) => digestEquals(r.keyHash, tokenHash)) ?? null;
|
|
55766
56655
|
}
|
|
55767
56656
|
function createCredential(dataDir, record2) {
|
|
55768
|
-
const records =
|
|
56657
|
+
const records = load9(dataDir);
|
|
55769
56658
|
if (records.some((r) => r.id === record2.id)) {
|
|
55770
56659
|
throw new Error(`Credential "${record2.id}" already exists.`);
|
|
55771
56660
|
}
|
|
@@ -55773,10 +56662,10 @@ function createCredential(dataDir, record2) {
|
|
|
55773
56662
|
save3(dataDir, records);
|
|
55774
56663
|
}
|
|
55775
56664
|
function revokeCredential(dataDir, id) {
|
|
55776
|
-
save3(dataDir,
|
|
56665
|
+
save3(dataDir, load9(dataDir).filter((r) => r.id !== id));
|
|
55777
56666
|
}
|
|
55778
56667
|
function revokeAllForOwner(dataDir, ownerUserId) {
|
|
55779
|
-
const records =
|
|
56668
|
+
const records = load9(dataDir);
|
|
55780
56669
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
55781
56670
|
let revoked = 0;
|
|
55782
56671
|
for (const rec of records) {
|
|
@@ -55789,16 +56678,16 @@ function revokeAllForOwner(dataDir, ownerUserId) {
|
|
|
55789
56678
|
return revoked;
|
|
55790
56679
|
}
|
|
55791
56680
|
function listCredentials(dataDir, project2) {
|
|
55792
|
-
return
|
|
56681
|
+
return load9(dataDir).filter(
|
|
55793
56682
|
(r) => project2 === "*" || r.projects.includes("*") || r.projects.includes(project2)
|
|
55794
56683
|
);
|
|
55795
56684
|
}
|
|
55796
56685
|
function listByOwner(dataDir, ownerUserId) {
|
|
55797
|
-
return
|
|
56686
|
+
return load9(dataDir).filter((r) => r.ownerSubject?.userId === ownerUserId);
|
|
55798
56687
|
}
|
|
55799
56688
|
function renarrowCredential(dataDir, id, projects) {
|
|
55800
56689
|
if (projects.length === 0) throw new Error(`Credential "${id}": a narrowing cannot be empty.`);
|
|
55801
|
-
const records =
|
|
56690
|
+
const records = load9(dataDir);
|
|
55802
56691
|
const rec = records.find((r) => r.id === id);
|
|
55803
56692
|
if (!rec) throw new Error(`Unknown credential "${id}".`);
|
|
55804
56693
|
if (rec.revokedAt) throw new Error(`Credential "${id}" is revoked.`);
|
|
@@ -57451,7 +58340,7 @@ init_fs();
|
|
|
57451
58340
|
function configPath2() {
|
|
57452
58341
|
return aiDir("producers.json");
|
|
57453
58342
|
}
|
|
57454
|
-
function
|
|
58343
|
+
function load10() {
|
|
57455
58344
|
try {
|
|
57456
58345
|
return JSON.parse(fs54.readFileSync(configPath2(), "utf8"));
|
|
57457
58346
|
} catch {
|
|
@@ -57466,18 +58355,18 @@ function save4(configs) {
|
|
|
57466
58355
|
fs54.renameSync(tmp, p);
|
|
57467
58356
|
}
|
|
57468
58357
|
function readProducerConfig(target) {
|
|
57469
|
-
return
|
|
58358
|
+
return load10().find((c) => c.target === target) ?? null;
|
|
57470
58359
|
}
|
|
57471
58360
|
function writeProducerConfig(config) {
|
|
57472
|
-
const configs =
|
|
58361
|
+
const configs = load10().filter((c) => c.target !== config.target);
|
|
57473
58362
|
configs.push(config);
|
|
57474
58363
|
save4(configs);
|
|
57475
58364
|
}
|
|
57476
58365
|
function clearProducerConfig(target) {
|
|
57477
|
-
save4(
|
|
58366
|
+
save4(load10().filter((c) => c.target !== target));
|
|
57478
58367
|
}
|
|
57479
58368
|
function listProducerConfigs() {
|
|
57480
|
-
return
|
|
58369
|
+
return load10();
|
|
57481
58370
|
}
|
|
57482
58371
|
|
|
57483
58372
|
// src/producers/core-adapter.ts
|
|
@@ -57493,7 +58382,7 @@ function project(diagramUrl) {
|
|
|
57493
58382
|
const types = loadTypeSpecs();
|
|
57494
58383
|
let mermaid = "";
|
|
57495
58384
|
try {
|
|
57496
|
-
mermaid =
|
|
58385
|
+
mermaid = renderDiagram2("mermaid");
|
|
57497
58386
|
} catch {
|
|
57498
58387
|
}
|
|
57499
58388
|
return {
|
|
@@ -57995,7 +58884,7 @@ function executeApprovedLock(cfg, projectId, approver, expected) {
|
|
|
57995
58884
|
const specs = captureApprovedSpecs();
|
|
57996
58885
|
refuseMovedInputs(captured, computeGateStateId());
|
|
57997
58886
|
const record2 = lockRecordOf(captured, approver, design, gate, config, specs, members);
|
|
57998
|
-
|
|
58887
|
+
writeLockRecord2(record2);
|
|
57999
58888
|
return publishLock(record2, scope);
|
|
58000
58889
|
});
|
|
58001
58890
|
}
|
|
@@ -58056,7 +58945,7 @@ function publishLock(record2, scope) {
|
|
|
58056
58945
|
const publish2 = runWithProjectRoot(scope.repositoryRoot, () => publish(message, scope.pathspecs));
|
|
58057
58946
|
if (!publish2.published) return record2;
|
|
58058
58947
|
const published = { ...record2, commitSha: publish2.commitSha, compareUrl: publish2.compareUrl };
|
|
58059
|
-
|
|
58948
|
+
writeLockRecord2(published);
|
|
58060
58949
|
return published;
|
|
58061
58950
|
}
|
|
58062
58951
|
function projectGitCredentialKey(project2) {
|
|
@@ -58221,7 +59110,7 @@ function generateDiagram(cfg, credential, project2, format) {
|
|
|
58221
59110
|
const root = existingProjectRoot(cfg.dataDir, project2);
|
|
58222
59111
|
if (!root) throw new Error(`Unknown project "${project2}".`);
|
|
58223
59112
|
const relations = format === "canvas" ? instanceRelations(cfg, project2, root) : void 0;
|
|
58224
|
-
return runWithProjectRoot(root, () =>
|
|
59113
|
+
return runWithProjectRoot(root, () => renderDiagram2(format, relations));
|
|
58225
59114
|
}
|
|
58226
59115
|
function instanceRelations(cfg, project2, root) {
|
|
58227
59116
|
const familyRoot = listFamilyRecords(cfg.dataDir, project2)[0];
|
|
@@ -62006,7 +62895,7 @@ function lockTargetOf(cfg, req) {
|
|
|
62006
62895
|
`Approved ${req.kind} request "${req.id}" carries a ${SUBPROJECT_SCOPE_PAYLOAD} payload with no usable member qualifier \u2014 refusing to execute, because falling back to the whole project would widen the scope the requester was confined to.`
|
|
62007
62896
|
);
|
|
62008
62897
|
}
|
|
62009
|
-
const member =
|
|
62898
|
+
const member = resolveProjectBinding2(cfg.dataDir, RECORD_LOOKUP, `${projectId}::${parsed.subproject}`);
|
|
62010
62899
|
if (!member) {
|
|
62011
62900
|
throw new Error(
|
|
62012
62901
|
`Approved ${req.kind} request "${req.id}" names member "${parsed.subproject}" of "${projectId}", which holds no hosted record \u2014 refusing to execute rather than widen to the whole project; run \`wairon host doctor --fix\`.`
|
|
@@ -62196,7 +63085,7 @@ async function awaitApproval(cfg, credential, requestId, timeoutSeconds) {
|
|
|
62196
63085
|
let current2 = req;
|
|
62197
63086
|
while (current2.status === "pending" && Date.now() < deadline) {
|
|
62198
63087
|
const remaining = deadline - Date.now();
|
|
62199
|
-
await new Promise((
|
|
63088
|
+
await new Promise((resolve57) => setTimeout(resolve57, Math.min(AWAIT_POLL_INTERVAL_MS, remaining)));
|
|
62200
63089
|
expirePendingApprovals(cfg.dataDir, (/* @__PURE__ */ new Date()).toISOString());
|
|
62201
63090
|
current2 = getApprovalRequestById(cfg.dataDir, requestId) ?? current2;
|
|
62202
63091
|
}
|
|
@@ -63608,7 +64497,7 @@ async function handleMcpRequest(cfg, req, res, body, credential) {
|
|
|
63608
64497
|
permissionSubject: { subjectId: "anonymous", roleBindings: [], instanceAdmin: true }
|
|
63609
64498
|
};
|
|
63610
64499
|
}
|
|
63611
|
-
const binding =
|
|
64500
|
+
const binding = resolveProjectBinding2(cfg.dataDir, principal, projectSelector(req));
|
|
63612
64501
|
if (!binding) {
|
|
63613
64502
|
sendJson(res, 403, { error: "project not authorized, unknown, or not specified" });
|
|
63614
64503
|
return;
|
|
@@ -63800,7 +64689,7 @@ function writtenOwners(cfg, binding, response) {
|
|
|
63800
64689
|
function familyReachOf(cfg, principal, binding) {
|
|
63801
64690
|
const family = listFamilyRecords(cfg.dataDir, binding.projectId);
|
|
63802
64691
|
const reach2 = resolveFamilyReach(cfg.dataDir, principal, family.map((r) => r.id));
|
|
63803
|
-
const covered = (id) =>
|
|
64692
|
+
const covered = (id) => resolveProjectBinding2(cfg.dataDir, principal, id) !== null;
|
|
63804
64693
|
const topRoot = existingProjectRoot(cfg.dataDir, binding.familyRootId) ?? binding.rootPath;
|
|
63805
64694
|
const parentReach = reach2.readable.includes(binding.familyRootId) && covered(binding.familyRootId);
|
|
63806
64695
|
const unwritableRoots = family.filter((r) => r.rootPath && !isRetiredPart(r) && !(reach2.writable.includes(r.id) && covered(r.id))).map((r) => r.rootPath);
|
|
@@ -63815,7 +64704,7 @@ function familyReachOf(cfg, principal, binding) {
|
|
|
63815
64704
|
}
|
|
63816
64705
|
function hostedRecordLookup(cfg, principal) {
|
|
63817
64706
|
return (recordId) => {
|
|
63818
|
-
const bound2 =
|
|
64707
|
+
const bound2 = resolveProjectBinding2(cfg.dataDir, principal, recordId);
|
|
63819
64708
|
if (!bound2 || recordId.includes(SUBPROJECT_SEPARATOR)) return null;
|
|
63820
64709
|
return authorize(cfg.dataDir, principal, "project:read", "project", bound2.projectId).value === "yes" ? bound2.rootPath : null;
|
|
63821
64710
|
};
|
|
@@ -63841,7 +64730,7 @@ function handleViewDiagram(cfg, req, res) {
|
|
|
63841
64730
|
sendJson(res, 404, { error: "project not found" });
|
|
63842
64731
|
return;
|
|
63843
64732
|
}
|
|
63844
|
-
const html = runWithProjectRoot(root, () =>
|
|
64733
|
+
const html = runWithProjectRoot(root, () => renderDiagram2(grant.format));
|
|
63845
64734
|
res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
63846
64735
|
res.end(html);
|
|
63847
64736
|
}
|
|
@@ -64224,7 +65113,7 @@ function captureSnapshot(dataDir, principal, projectId, view, artifacts) {
|
|
|
64224
65113
|
canvasModel: JSON.stringify(buildCanvasDataModel(relations))
|
|
64225
65114
|
};
|
|
64226
65115
|
if (artifacts.includes("html")) {
|
|
64227
|
-
snapshot.html =
|
|
65116
|
+
snapshot.html = renderDiagram2("canvas", relations);
|
|
64228
65117
|
}
|
|
64229
65118
|
if (artifacts.includes("openapi")) {
|
|
64230
65119
|
const result = exportSurface2("project", "openapi");
|
|
@@ -64780,7 +65669,7 @@ function getWebProjectCanvas(cfg, sessionId, projectId) {
|
|
|
64780
65669
|
}
|
|
64781
65670
|
const listed = listProjects2(cfg, sessionId);
|
|
64782
65671
|
const relations = runWithProjectBinding(root, readReach(cfg, projectId, root, listed), () => familyRelations2());
|
|
64783
|
-
return runWithProjectRoot(root, () =>
|
|
65672
|
+
return runWithProjectRoot(root, () => renderDiagram2("canvas", relations));
|
|
64784
65673
|
}
|
|
64785
65674
|
function getWebProjectCanvasModel(cfg, sessionId, projectId) {
|
|
64786
65675
|
const principal = authenticateSession(cfg.dataDir, sessionId);
|
|
@@ -68551,6 +69440,7 @@ function upgradeMemberRecords(cfg, apply4) {
|
|
|
68551
69440
|
|
|
68552
69441
|
// src/commands/host.ts
|
|
68553
69442
|
init_fs();
|
|
69443
|
+
init_project();
|
|
68554
69444
|
|
|
68555
69445
|
// src/core/demo-seed.ts
|
|
68556
69446
|
init_specs2();
|
|
@@ -69157,11 +70047,11 @@ async function runServe(options = {}) {
|
|
|
69157
70047
|
logger.info(` data dir: ${import_chalk18.default.gray(cfg.dataDir)}`);
|
|
69158
70048
|
logger.blank();
|
|
69159
70049
|
logger.info("Press Ctrl+C to stop.");
|
|
69160
|
-
await new Promise((
|
|
70050
|
+
await new Promise((resolve57) => {
|
|
69161
70051
|
const shutdown = () => {
|
|
69162
70052
|
logger.info("Shutting down\u2026");
|
|
69163
70053
|
handle.close();
|
|
69164
|
-
|
|
70054
|
+
resolve57();
|
|
69165
70055
|
};
|
|
69166
70056
|
process.on("SIGINT", shutdown);
|
|
69167
70057
|
process.on("SIGTERM", shutdown);
|
|
@@ -69180,12 +70070,19 @@ function openBrowser(url) {
|
|
|
69180
70070
|
}
|
|
69181
70071
|
}
|
|
69182
70072
|
async function runDev(options = {}) {
|
|
69183
|
-
const
|
|
69184
|
-
if (!
|
|
70073
|
+
const binding = resolveProjectBinding(process.cwd());
|
|
70074
|
+
if (!binding) {
|
|
69185
70075
|
throw new WaironError(
|
|
69186
|
-
"No
|
|
70076
|
+
"No wairon project binds this folder. Run `wairon dev` inside a wairon project (or run `wairon init` first)."
|
|
69187
70077
|
);
|
|
69188
70078
|
}
|
|
70079
|
+
const cwd = binding.root;
|
|
70080
|
+
let projectId = null;
|
|
70081
|
+
try {
|
|
70082
|
+
const config = runWithProjectRoot(cwd, () => loadProjectConfig());
|
|
70083
|
+
if (config) projectId = effectiveProjectId(config);
|
|
70084
|
+
} catch {
|
|
70085
|
+
}
|
|
69189
70086
|
const hash = crypto29.createHash("sha256").update(cwd).digest("hex").slice(0, 16);
|
|
69190
70087
|
const dataDir = path91.join(os14.tmpdir(), "wairon-dev", hash);
|
|
69191
70088
|
fs68.mkdirSync(dataDir, { recursive: true });
|
|
@@ -69221,16 +70118,16 @@ async function runDev(options = {}) {
|
|
|
69221
70118
|
const url = `http://127.0.0.1:${port}`;
|
|
69222
70119
|
logger.success("wairon dev server started (local, single-project, no login).");
|
|
69223
70120
|
logger.info(` open: ${import_chalk18.default.cyan(url)}`);
|
|
69224
|
-
logger.info(` project: ${import_chalk18.default.gray(cwd)} ${import_chalk18.default.gray('(served as "local")')}`);
|
|
70121
|
+
logger.info(` project: ${projectId ?? path91.basename(cwd)} at ${import_chalk18.default.gray(cwd)} ${import_chalk18.default.gray('(served as "local")')}`);
|
|
69225
70122
|
logger.info(` data dir: ${import_chalk18.default.gray(dataDir)} ${import_chalk18.default.gray("(ephemeral)")}`);
|
|
69226
70123
|
logger.blank();
|
|
69227
70124
|
logger.info("An agent edits specs; refresh the page to see the live graph. Press Ctrl+C to stop.");
|
|
69228
70125
|
if (options.open) openBrowser(url);
|
|
69229
|
-
await new Promise((
|
|
70126
|
+
await new Promise((resolve57) => {
|
|
69230
70127
|
const shutdown = () => {
|
|
69231
70128
|
logger.info("Shutting down\u2026");
|
|
69232
70129
|
handle.close();
|
|
69233
|
-
|
|
70130
|
+
resolve57();
|
|
69234
70131
|
};
|
|
69235
70132
|
process.on("SIGINT", shutdown);
|
|
69236
70133
|
process.on("SIGTERM", shutdown);
|
|
@@ -70002,7 +70899,8 @@ function printStatuses(statuses) {
|
|
|
70002
70899
|
s.sourceKind,
|
|
70003
70900
|
s.pinned ? "pinned" : import_chalk21.default.yellow("not pinned"),
|
|
70004
70901
|
s.reachable ? "reachable" : import_chalk21.default.yellow("unreachable"),
|
|
70005
|
-
|
|
70902
|
+
// Never "current" beside "unreachable": nothing was compared.
|
|
70903
|
+
s.stale ? import_chalk21.default.red("stale") : s.reachable ? "current" : import_chalk21.default.yellow("not compared"),
|
|
70006
70904
|
...s.drifted ? [import_chalk21.default.yellow("drifted")] : []
|
|
70007
70905
|
];
|
|
70008
70906
|
logger.info(`${import_chalk21.default.cyan(s.alias)} \u2192 ${s.project}: ${flags.join(", ")}${s.detail ? ` \u2014 ${s.detail}` : ""}`);
|
|
@@ -70011,6 +70909,9 @@ function printStatuses(statuses) {
|
|
|
70011
70909
|
logger.info(` ${name}: ${u.state}${u.code ? ` ${u.code}` : ""}${u.detail ? ` \u2014 ${u.detail}` : ""}`);
|
|
70012
70910
|
}
|
|
70013
70911
|
}
|
|
70912
|
+
logger.info(import_chalk21.default.gray(
|
|
70913
|
+
"Words: stale \u2014 a name this project uses changed or was removed since the pin (adapt the design, then re-pin); current \u2014 every used name matches the pin; not compared \u2014 the producer could not be read; drifted \u2014 the producer moved since the pin (re-pin to record it; alone it means nothing used changed). Per name: unchanged; changed (its signature moved); removed (gone from the producer's export table); unlocked (used but not in the pin yet \u2014 re-pin); unavailable (not compared \u2014 never a pass)."
|
|
70914
|
+
));
|
|
70014
70915
|
}
|
|
70015
70916
|
function printListings(rows) {
|
|
70016
70917
|
if (!rows.length) {
|
|
@@ -70153,10 +71054,15 @@ async function setExecutionTier2(tier) {
|
|
|
70153
71054
|
// src/cli/index.ts
|
|
70154
71055
|
cleanStaleBinary();
|
|
70155
71056
|
var program = new import_commander.Command();
|
|
70156
|
-
program.name("wairon").description("SYW Waffle AIron \u2014 Spec-Driven Development (SDD) & Agent Topology Orchestration").version(WAIRON_VERSION, "-v, --version").option("--verbose", "enable verbose output").option("--silent", "suppress all output except errors").hook("preAction", (thisCommand) => {
|
|
71057
|
+
program.name("wairon").description("SYW Waffle AIron \u2014 Spec-Driven Development (SDD) & Agent Topology Orchestration").version(WAIRON_VERSION, "-v, --version").option("--verbose", "enable verbose output").option("--silent", "suppress all output except errors").hook("preAction", (thisCommand, actionCommand) => {
|
|
70157
71058
|
const opts = thisCommand.opts();
|
|
70158
71059
|
if (opts.verbose) setLogLevel("verbose");
|
|
70159
71060
|
else if (opts.silent) setLogLevel("silent");
|
|
71061
|
+
if (!opts.silent) {
|
|
71062
|
+
const names = [];
|
|
71063
|
+
for (let c = actionCommand; c && c.parent; c = c.parent) names.unshift(c.name());
|
|
71064
|
+
announceBinding(names.join(" "));
|
|
71065
|
+
}
|
|
70160
71066
|
});
|
|
70161
71067
|
program.command("init").description("Initialize wairon and bootstrap the SDD Spec Tree (.wai/specs/) in the current project").option("-y, --yes", "use defaults without interactive prompts").option("--pack <source>", "vendor + register an extension pack right after init (repeatable)", (v, all) => [...all, v], []).action(async (opts) => {
|
|
70162
71068
|
await runInit({ yes: opts.yes });
|
|
@@ -70187,7 +71093,7 @@ program.command("lock").description("Record human approval of the design: valida
|
|
|
70187
71093
|
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) => {
|
|
70188
71094
|
await validateCommand(opts);
|
|
70189
71095
|
});
|
|
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) => {
|
|
71096
|
+
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").option("--all", "list every spec that moved since the approval, not the first few").action(async (opts) => {
|
|
70191
71097
|
await statusCommand(opts);
|
|
70192
71098
|
});
|
|
70193
71099
|
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) => {
|
|
@@ -70265,7 +71171,7 @@ var variantsCmd = program.command("variants").description("Component variants \u
|
|
|
70265
71171
|
variantsCmd.command("list").alias("ls").description("List the component variants in the registry (global + project) with their base and guidance").action(async () => {
|
|
70266
71172
|
await runVariants();
|
|
70267
71173
|
});
|
|
70268
|
-
program.command("diagram").description("Generate architecture diagrams from the spec tree \u2014 interactive canvas by default; Mermaid / draw.io / Excalidraw via --format").option("--subsystem <id>", "scope the component diagram to one subsystem (plus its external neighbors)").option("--sequence <component:method>", "emit a sequence diagram derived from the method's L5 narrative").option("--depth <n>", "max call-expansion depth for sequence diagrams (default 3)", (v) => parseInt(v, 10)).option("--all", "write the full diagram set: system, per-subsystem, entrypoint sequences, and the interactive canvas").option("--canvas", "emit the interactive self-contained HTML canvas (pan/zoom, collapse boundaries, detail panel, issue overlay)").option("--drawio", "emit an editable draw.io / diagrams.net file (same layout as the canvas)").option("--excalidraw", "emit an editable Excalidraw scene (same layout as the canvas)").option("--format <fmt>", "alias for the flags above: mermaid | canvas | drawio | excalidraw").option("--out <path>", "
|
|
71174
|
+
program.command("diagram").description("Generate architecture diagrams from the spec tree \u2014 interactive canvas by default; Mermaid / draw.io / Excalidraw via --format").option("--subsystem <id>", "scope the component diagram to one subsystem (plus its external neighbors)").option("--sequence <component:method>", "emit a sequence diagram derived from the method's L5 narrative").option("--depth <n>", "max call-expansion depth for sequence diagrams (default 3)", (v) => parseInt(v, 10)).option("--all", "write the full diagram set: system, per-subsystem, entrypoint sequences, and the interactive canvas").option("--canvas", "emit the interactive self-contained HTML canvas (pan/zoom, collapse boundaries, detail panel, issue overlay)").option("--drawio", "emit an editable draw.io / diagrams.net file (same layout as the canvas)").option("--excalidraw", "emit an editable Excalidraw scene (same layout as the canvas)").option("--format <fmt>", "alias for the flags above: mermaid | canvas | drawio | excalidraw").option("--out <path>", "the file to write (a directory with --all); every format writes a file \u2014 default under .wai/docs/diagrams/ (system.md for Mermaid, canvas.html for the canvas); a .mmd path writes raw Mermaid").option("--no-health", "skip comparing each consumption relation with its live producer: the canvas draws those edges as not checked").action(async (opts) => {
|
|
70269
71175
|
await runDiagram({
|
|
70270
71176
|
subsystem: opts.subsystem,
|
|
70271
71177
|
sequence: opts.sequence,
|