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