@wairon/cli 5.1.1-dev.101 → 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 +93 -45
- package/dist/cli/index.js +2186 -687
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +37667 -0
- package/dist/index.js +1613 -536
- package/dist/index.js.map +1 -1
- package/dist/templates/architect.yaml +14 -12
- package/dist/templates/domain-owner.yaml +3 -2
- package/dist/templates/skills/sdd-architect.md +11 -5
- package/dist/templates/skills/sdd-auditor.md +5 -4
- package/dist/templates/skills/sdd-delegate.md +3 -2
- package/dist/templates/skills/sdd-implement.md +4 -4
- package/dist/templates/skills/sdd-narrative.md +1 -1
- package/dist/webapp.html +770 -0
- package/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 +5 -2
- package/schemas/design-export-1.json +4 -4
package/dist/index.js
CHANGED
|
@@ -3316,8 +3316,8 @@ var require_dist = __commonJS({
|
|
|
3316
3316
|
return normalized === "." ? ".wai/" : `${normalized}/.wai/`;
|
|
3317
3317
|
}
|
|
3318
3318
|
function normalizeRootPath(relativePath) {
|
|
3319
|
-
const
|
|
3320
|
-
return
|
|
3319
|
+
const posix7 = relativePath.split(path210.sep).join("/").replace(/^\.\//, "").replace(/\/+$/, "");
|
|
3320
|
+
return posix7 === "" || posix7 === "." ? "." : posix7;
|
|
3321
3321
|
}
|
|
3322
3322
|
function slugify(projectName) {
|
|
3323
3323
|
const slug2 = projectName.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
@@ -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,
|
|
@@ -3709,6 +3710,7 @@ __export(src_exports, {
|
|
|
3709
3710
|
keyIn: () => keyIn,
|
|
3710
3711
|
landReference: () => landReference,
|
|
3711
3712
|
languageOfSourcePath: () => languageOfSourcePath,
|
|
3713
|
+
lineEndingFor: () => lineEndingFor,
|
|
3712
3714
|
listDirectChainedSubprojects: () => listDirectChainedSubprojects,
|
|
3713
3715
|
listExternals: () => listExternals2,
|
|
3714
3716
|
listFiles: () => listFiles,
|
|
@@ -3750,6 +3752,7 @@ __export(src_exports, {
|
|
|
3750
3752
|
memberDeclarationOf: () => memberDeclarationOf,
|
|
3751
3753
|
memberDigest: () => memberDigest,
|
|
3752
3754
|
memberLocationOf: () => memberLocationOf,
|
|
3755
|
+
memberTypesOf: () => memberTypesOf,
|
|
3753
3756
|
methodGenericParameters: () => methodGenericParameters,
|
|
3754
3757
|
methodSourceFile: () => methodSourceFile,
|
|
3755
3758
|
methodTypeRefs: () => methodTypeRefs,
|
|
@@ -3808,7 +3811,7 @@ __export(src_exports, {
|
|
|
3808
3811
|
renameId: () => renameProjectId,
|
|
3809
3812
|
renameMethod: () => renameMethod,
|
|
3810
3813
|
renameType: () => renameType,
|
|
3811
|
-
renderDiagram: () =>
|
|
3814
|
+
renderDiagram: () => renderDiagram2,
|
|
3812
3815
|
repairForeignStepFields: () => repairForeignStepFields,
|
|
3813
3816
|
repairSignatures: () => repairSignatures,
|
|
3814
3817
|
repairTypeSpellings: () => repairTypeSpellings,
|
|
@@ -3822,6 +3825,7 @@ __export(src_exports, {
|
|
|
3822
3825
|
resolveExternals: () => resolveDeclared,
|
|
3823
3826
|
resolveImport: () => resolveImport,
|
|
3824
3827
|
resolveInstalledPack: () => resolveInstalledPack,
|
|
3828
|
+
resolveProjectBinding: () => resolveProjectBinding,
|
|
3825
3829
|
resolveProjectExports: () => resolveProjectExports,
|
|
3826
3830
|
resolveSignatures: () => resolveSignatures,
|
|
3827
3831
|
resolveSubprojectForNamespace: () => resolveSubprojectForNamespace,
|
|
@@ -3867,6 +3871,7 @@ __export(src_exports, {
|
|
|
3867
3871
|
typeIsMany: () => typeIsMany,
|
|
3868
3872
|
typeMatchesRef: () => typeMatchesRef,
|
|
3869
3873
|
typeNamedRefs: () => typeNamedRefs,
|
|
3874
|
+
typeProblemEnumProposal: () => typeProblemEnumProposal,
|
|
3870
3875
|
typeProblemIntProposal: () => typeProblemIntProposal,
|
|
3871
3876
|
typeSourceFiles: () => typeSourceFiles,
|
|
3872
3877
|
typeSpellingFacts: () => typeSpellingFacts,
|
|
@@ -3880,11 +3885,12 @@ __export(src_exports, {
|
|
|
3880
3885
|
validateProject: () => validateProject,
|
|
3881
3886
|
validateProjectConfig: () => validateProjectConfig,
|
|
3882
3887
|
validateRegistry: () => validateRegistry,
|
|
3888
|
+
withLineEndings: () => withLineEndings,
|
|
3883
3889
|
withPack: () => withPack,
|
|
3884
3890
|
writeFile: () => writeFile,
|
|
3885
3891
|
writeFileIfChanged: () => writeFileIfChanged,
|
|
3886
3892
|
writeJsonFile: () => writeJsonFile,
|
|
3887
|
-
writeLockRecord: () =>
|
|
3893
|
+
writeLockRecord: () => writeLockRecord2,
|
|
3888
3894
|
writeReachDenials: () => writeReachDenials,
|
|
3889
3895
|
writeReachPermits: () => writeReachPermits,
|
|
3890
3896
|
writeRootGuideDelegator: () => writeRootGuideDelegator,
|
|
@@ -3894,8 +3900,8 @@ __export(src_exports, {
|
|
|
3894
3900
|
module.exports = __toCommonJS(src_exports);
|
|
3895
3901
|
|
|
3896
3902
|
// src/config/project-config.ts
|
|
3897
|
-
var
|
|
3898
|
-
var
|
|
3903
|
+
var fs3 = __toESM(require("fs"));
|
|
3904
|
+
var path5 = __toESM(require("path"));
|
|
3899
3905
|
var YAML = __toESM(require("yaml"));
|
|
3900
3906
|
var import_zod4 = require("zod");
|
|
3901
3907
|
|
|
@@ -3903,22 +3909,125 @@ var import_zod4 = require("zod");
|
|
|
3903
3909
|
var fs = __toESM(require("fs"));
|
|
3904
3910
|
var path = __toESM(require("path"));
|
|
3905
3911
|
var import_node_async_hooks = require("async_hooks");
|
|
3912
|
+
var yaml = __toESM(require("js-yaml"));
|
|
3906
3913
|
function ensureDir(dirPath) {
|
|
3907
3914
|
fs.mkdirSync(dirPath, { recursive: true });
|
|
3908
3915
|
}
|
|
3909
3916
|
function writeFile(filePath, content) {
|
|
3910
3917
|
ensureDir(path.dirname(filePath));
|
|
3911
|
-
fs.writeFileSync(filePath, content, "utf-8");
|
|
3918
|
+
fs.writeFileSync(filePath, withLineEndings(filePath, content), "utf-8");
|
|
3912
3919
|
}
|
|
3913
3920
|
function writeFileIfChanged(filePath, content) {
|
|
3921
|
+
const text3 = withLineEndings(filePath, content);
|
|
3914
3922
|
if (fs.existsSync(filePath)) {
|
|
3915
3923
|
const existing = fs.readFileSync(filePath, "utf-8");
|
|
3916
|
-
if (existing ===
|
|
3924
|
+
if (existing === text3) return false;
|
|
3917
3925
|
}
|
|
3918
3926
|
ensureDir(path.dirname(filePath));
|
|
3919
|
-
fs.writeFileSync(filePath,
|
|
3927
|
+
fs.writeFileSync(filePath, text3, "utf-8");
|
|
3920
3928
|
return true;
|
|
3921
3929
|
}
|
|
3930
|
+
var EOL_SAMPLE_BYTES = 64 * 1024;
|
|
3931
|
+
function endingsOf(text3) {
|
|
3932
|
+
const crlf = text3.split("\r\n").length - 1;
|
|
3933
|
+
const lf = text3.split("\n").length - 1 - crlf;
|
|
3934
|
+
if (crlf === 0 && lf === 0) return null;
|
|
3935
|
+
return crlf > lf ? "\r\n" : "\n";
|
|
3936
|
+
}
|
|
3937
|
+
function sampleOf(file) {
|
|
3938
|
+
let fd;
|
|
3939
|
+
try {
|
|
3940
|
+
fd = fs.openSync(file, "r");
|
|
3941
|
+
const buffer = Buffer.alloc(EOL_SAMPLE_BYTES);
|
|
3942
|
+
const read2 = fs.readSync(fd, buffer, 0, EOL_SAMPLE_BYTES, 0);
|
|
3943
|
+
return buffer.subarray(0, read2).toString("utf-8");
|
|
3944
|
+
} catch {
|
|
3945
|
+
return null;
|
|
3946
|
+
} finally {
|
|
3947
|
+
if (fd !== void 0) fs.closeSync(fd);
|
|
3948
|
+
}
|
|
3949
|
+
}
|
|
3950
|
+
function attributeMatches(pattern, relative20) {
|
|
3951
|
+
const glob = pattern.replace(/^\//, "");
|
|
3952
|
+
const target = glob.includes("/") ? relative20 : path.posix.basename(relative20);
|
|
3953
|
+
const source = glob.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*\//g, "\0").replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]").replace(/\u0000/g, "(?:.*/)?");
|
|
3954
|
+
return new RegExp(`^${source}$`).test(target);
|
|
3955
|
+
}
|
|
3956
|
+
function attributeEol(attributesFile, file) {
|
|
3957
|
+
const text3 = readFileOrNull(attributesFile);
|
|
3958
|
+
if (text3 === null) return null;
|
|
3959
|
+
const relative20 = path.relative(path.dirname(attributesFile), file).split(path.sep).join("/");
|
|
3960
|
+
let eol = null;
|
|
3961
|
+
for (const raw of text3.split(/\r?\n/)) {
|
|
3962
|
+
const line = raw.trim();
|
|
3963
|
+
if (line === "" || line.startsWith("#")) continue;
|
|
3964
|
+
const [pattern, ...attrs] = line.split(/\s+/);
|
|
3965
|
+
if (!attributeMatches(pattern, relative20)) continue;
|
|
3966
|
+
for (const attr of attrs) {
|
|
3967
|
+
if (attr === "eol=crlf") eol = "\r\n";
|
|
3968
|
+
else if (attr === "eol=lf") eol = "\n";
|
|
3969
|
+
else if (attr === "-text" || attr === "binary") eol = null;
|
|
3970
|
+
}
|
|
3971
|
+
}
|
|
3972
|
+
return eol;
|
|
3973
|
+
}
|
|
3974
|
+
function conventionFolders(dir) {
|
|
3975
|
+
const folders = [];
|
|
3976
|
+
let at = path.resolve(dir);
|
|
3977
|
+
for (let depth = 0; depth < 8; depth++) {
|
|
3978
|
+
folders.push(at);
|
|
3979
|
+
if (fs.existsSync(path.join(at, ".git")) || fs.existsSync(path.join(at, ".wai"))) return folders;
|
|
3980
|
+
const parent = path.dirname(at);
|
|
3981
|
+
if (parent === at) break;
|
|
3982
|
+
at = parent;
|
|
3983
|
+
}
|
|
3984
|
+
return folders.slice(0, 1);
|
|
3985
|
+
}
|
|
3986
|
+
function folderEndings(folder, ext, exclude) {
|
|
3987
|
+
let names;
|
|
3988
|
+
try {
|
|
3989
|
+
names = fs.readdirSync(folder);
|
|
3990
|
+
} catch {
|
|
3991
|
+
return null;
|
|
3992
|
+
}
|
|
3993
|
+
const candidates = names.filter((n) => path.join(folder, n) !== exclude && /\.(ya?ml|json|md|ts|js|txt)$/i.test(n)).sort((a, b) => Number(path.extname(b) === ext) - Number(path.extname(a) === ext)).slice(0, 24);
|
|
3994
|
+
let crlf = 0;
|
|
3995
|
+
let lf = 0;
|
|
3996
|
+
for (const name of candidates) {
|
|
3997
|
+
const full = path.join(folder, name);
|
|
3998
|
+
try {
|
|
3999
|
+
if (!fs.statSync(full).isFile()) continue;
|
|
4000
|
+
} catch {
|
|
4001
|
+
continue;
|
|
4002
|
+
}
|
|
4003
|
+
const sample = sampleOf(full);
|
|
4004
|
+
const endings = sample === null ? null : endingsOf(sample);
|
|
4005
|
+
if (endings === "\r\n") crlf++;
|
|
4006
|
+
else if (endings === "\n") lf++;
|
|
4007
|
+
}
|
|
4008
|
+
if (crlf === lf) return null;
|
|
4009
|
+
return crlf > lf ? "\r\n" : "\n";
|
|
4010
|
+
}
|
|
4011
|
+
function lineEndingFor(filePath) {
|
|
4012
|
+
const own = sampleOf(filePath);
|
|
4013
|
+
const existing = own === null ? null : endingsOf(own);
|
|
4014
|
+
if (existing) return existing;
|
|
4015
|
+
const folders = conventionFolders(path.dirname(filePath));
|
|
4016
|
+
for (const folder of folders) {
|
|
4017
|
+
const eol = attributeEol(path.join(folder, ".gitattributes"), filePath);
|
|
4018
|
+
if (eol) return eol;
|
|
4019
|
+
}
|
|
4020
|
+
const ext = path.extname(filePath);
|
|
4021
|
+
for (const folder of folders) {
|
|
4022
|
+
const endings = folderEndings(folder, ext, path.resolve(filePath));
|
|
4023
|
+
if (endings) return endings;
|
|
4024
|
+
}
|
|
4025
|
+
return "\n";
|
|
4026
|
+
}
|
|
4027
|
+
function withLineEndings(filePath, content) {
|
|
4028
|
+
const normalised = content.replace(/\r\n/g, "\n");
|
|
4029
|
+
return lineEndingFor(filePath) === "\r\n" ? normalised.replace(/\n/g, "\r\n") : normalised;
|
|
4030
|
+
}
|
|
3922
4031
|
function readFileOrNull(filePath) {
|
|
3923
4032
|
try {
|
|
3924
4033
|
return fs.readFileSync(filePath, "utf-8");
|
|
@@ -4017,22 +4126,74 @@ function setProjectRoot(dir) {
|
|
|
4017
4126
|
function getProjectRootOverride() {
|
|
4018
4127
|
return projectRootOverride;
|
|
4019
4128
|
}
|
|
4020
|
-
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) {
|
|
4021
4138
|
let dir = path.resolve(startDir);
|
|
4022
4139
|
while (true) {
|
|
4023
|
-
|
|
4024
|
-
const
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
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" });
|
|
4030
4188
|
}
|
|
4031
4189
|
const parent = path.dirname(dir);
|
|
4032
4190
|
if (parent === dir) return null;
|
|
4033
4191
|
dir = parent;
|
|
4034
4192
|
}
|
|
4035
4193
|
}
|
|
4194
|
+
function findSystemRoot(startDir) {
|
|
4195
|
+
return resolveProjectBinding(startDir)?.root ?? null;
|
|
4196
|
+
}
|
|
4036
4197
|
function getProjectRoot() {
|
|
4037
4198
|
const scoped = requestRootStore.getStore()?.root;
|
|
4038
4199
|
if (scoped) return scoped;
|
|
@@ -4064,44 +4225,67 @@ function aiDirAt(root, ...segments) {
|
|
|
4064
4225
|
return path.join(base, ...segments);
|
|
4065
4226
|
}
|
|
4066
4227
|
|
|
4067
|
-
// src/
|
|
4068
|
-
var
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
}
|
|
4084
|
-
}
|
|
4085
|
-
function readYamlFile(filePath) {
|
|
4086
|
-
const content = readFileOrNull(filePath);
|
|
4087
|
-
if (content === null) return null;
|
|
4088
|
-
return parseYaml(content, filePath);
|
|
4089
|
-
}
|
|
4090
|
-
function writeYamlFile(filePath, value) {
|
|
4091
|
-
writeFile(filePath, serializeYaml(value));
|
|
4228
|
+
// src/config/defaults.ts
|
|
4229
|
+
var os = __toESM(require("os"));
|
|
4230
|
+
var path2 = __toESM(require("path"));
|
|
4231
|
+
var DEFAULT_TARGET_DIRS = {
|
|
4232
|
+
claude: ".claude/agents",
|
|
4233
|
+
gemini: ".gemini/agents",
|
|
4234
|
+
agy: ".gemini/agents",
|
|
4235
|
+
cursor: ".cursor/rules",
|
|
4236
|
+
copilot: ".github/prompts",
|
|
4237
|
+
codex: ".codex/agents"
|
|
4238
|
+
};
|
|
4239
|
+
function defaultTargetConfig(type) {
|
|
4240
|
+
return {
|
|
4241
|
+
type,
|
|
4242
|
+
outputDir: DEFAULT_TARGET_DIRS[type],
|
|
4243
|
+
enabled: true
|
|
4244
|
+
};
|
|
4092
4245
|
}
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
4246
|
+
var WAIRON_VERSION = "5.1.1-dev.103";
|
|
4247
|
+
var GITHUB_REPO = "SYW-Apps/Waffle-AIron";
|
|
4248
|
+
var ARCHITECT_AGENT_ID = "agent-architect";
|
|
4249
|
+
var ARCHITECT_TEMPLATE_ID = "architect";
|
|
4250
|
+
function globalTemplatesDir(projectOverride) {
|
|
4251
|
+
if (process.env.WAIRON_TEMPLATES_DIR) {
|
|
4252
|
+
return process.env.WAIRON_TEMPLATES_DIR;
|
|
4100
4253
|
}
|
|
4254
|
+
if (projectOverride) {
|
|
4255
|
+
return projectOverride;
|
|
4256
|
+
}
|
|
4257
|
+
return path2.join(os.homedir(), ".wairon", "templates");
|
|
4101
4258
|
}
|
|
4102
|
-
|
|
4103
|
-
|
|
4259
|
+
var BACKEND_COMMANDS = {
|
|
4260
|
+
claude: "claude",
|
|
4261
|
+
gemini: "gemini"
|
|
4262
|
+
};
|
|
4263
|
+
function backendCommand(backend) {
|
|
4264
|
+
return BACKEND_COMMANDS[backend] ?? backend;
|
|
4104
4265
|
}
|
|
4266
|
+
var SUPPORTED_ALIASES = ["wai"];
|
|
4267
|
+
var SCAN_EXCLUDE_DIRS = /* @__PURE__ */ new Set([
|
|
4268
|
+
"node_modules",
|
|
4269
|
+
"dist",
|
|
4270
|
+
"build",
|
|
4271
|
+
"out",
|
|
4272
|
+
".git",
|
|
4273
|
+
".cache",
|
|
4274
|
+
"coverage",
|
|
4275
|
+
"__pycache__",
|
|
4276
|
+
".venv",
|
|
4277
|
+
"venv",
|
|
4278
|
+
"target",
|
|
4279
|
+
// Rust
|
|
4280
|
+
"vendor"
|
|
4281
|
+
]);
|
|
4282
|
+
|
|
4283
|
+
// src/core/adapters/git-source.ts
|
|
4284
|
+
var import_child_process = require("child_process");
|
|
4285
|
+
var crypto = __toESM(require("crypto"));
|
|
4286
|
+
var fs2 = __toESM(require("fs"));
|
|
4287
|
+
var os2 = __toESM(require("os"));
|
|
4288
|
+
var path3 = __toESM(require("path"));
|
|
4105
4289
|
|
|
4106
4290
|
// src/utils/errors.ts
|
|
4107
4291
|
var WaironError = class extends Error {
|
|
@@ -4151,6 +4335,167 @@ ${reasons.map((r) => ` - ${r}`).join("\n")}`);
|
|
|
4151
4335
|
}
|
|
4152
4336
|
};
|
|
4153
4337
|
|
|
4338
|
+
// src/core/adapters/git-source.ts
|
|
4339
|
+
var SourceUnavailable = class extends WaironError {
|
|
4340
|
+
constructor(message) {
|
|
4341
|
+
super(message);
|
|
4342
|
+
this.name = "SourceUnavailable";
|
|
4343
|
+
}
|
|
4344
|
+
};
|
|
4345
|
+
function osUserCache() {
|
|
4346
|
+
if (process.platform === "win32") return process.env.LOCALAPPDATA ?? path3.join(os2.homedir(), "AppData", "Local");
|
|
4347
|
+
if (process.platform === "darwin") return path3.join(os2.homedir(), "Library", "Caches");
|
|
4348
|
+
return process.env.XDG_CACHE_HOME ?? path3.join(os2.homedir(), ".cache");
|
|
4349
|
+
}
|
|
4350
|
+
var cacheRoots = /* @__PURE__ */ new Map();
|
|
4351
|
+
function cacheRoot() {
|
|
4352
|
+
const setting = process.env.WAIRON_CACHE_DIR ?? "";
|
|
4353
|
+
let root = cacheRoots.get(setting);
|
|
4354
|
+
if (root === void 0) {
|
|
4355
|
+
root = setting !== "" ? path3.resolve(setting) : path3.join(osUserCache(), "wairon");
|
|
4356
|
+
cacheRoots.set(setting, root);
|
|
4357
|
+
}
|
|
4358
|
+
return root;
|
|
4359
|
+
}
|
|
4360
|
+
function git(args, cwd) {
|
|
4361
|
+
return (0, import_child_process.execFileSync)("git", args, {
|
|
4362
|
+
cwd,
|
|
4363
|
+
encoding: "utf8",
|
|
4364
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
4365
|
+
env: { ...process.env, GIT_TERMINAL_PROMPT: "0", GIT_ASKPASS: "echo" }
|
|
4366
|
+
});
|
|
4367
|
+
}
|
|
4368
|
+
function reasonOf(e) {
|
|
4369
|
+
const stderr = e?.stderr;
|
|
4370
|
+
const text3 = typeof stderr === "string" && stderr.trim() !== "" ? stderr : e instanceof Error ? e.message : String(e);
|
|
4371
|
+
return text3.trim().split(/\r?\n/).filter((l) => l.trim() !== "").slice(-1)[0] ?? "git failed";
|
|
4372
|
+
}
|
|
4373
|
+
function cacheEntryOf(url, commit2) {
|
|
4374
|
+
const repo = crypto.createHash("sha256").update(url).digest("hex");
|
|
4375
|
+
return path3.join(cacheRoot(), "git", repo, commit2);
|
|
4376
|
+
}
|
|
4377
|
+
function within(entry, url, commit2, dir) {
|
|
4378
|
+
const at = dir ? path3.join(entry, dir) : entry;
|
|
4379
|
+
if (!fs2.existsSync(at)) {
|
|
4380
|
+
throw new SourceUnavailable(`the commit ${commit2} of ${url} holds no "${dir}"`);
|
|
4381
|
+
}
|
|
4382
|
+
return at;
|
|
4383
|
+
}
|
|
4384
|
+
function fetchInto(tmp, url, commit2) {
|
|
4385
|
+
git(["init", "-q"], tmp);
|
|
4386
|
+
try {
|
|
4387
|
+
git(["fetch", "-q", "--depth", "1", url, commit2], tmp);
|
|
4388
|
+
} catch {
|
|
4389
|
+
git(["fetch", "-q", url, "+refs/heads/*:refs/remotes/source/*", "+refs/tags/*:refs/tags/*"], tmp);
|
|
4390
|
+
}
|
|
4391
|
+
git(["-c", "advice.detachedHead=false", "checkout", "-q", commit2], tmp);
|
|
4392
|
+
fs2.rmSync(path3.join(tmp, ".git"), { recursive: true, force: true });
|
|
4393
|
+
}
|
|
4394
|
+
function fetch(url, commit2, dir) {
|
|
4395
|
+
const entry = cacheEntryOf(url, commit2);
|
|
4396
|
+
if (fs2.existsSync(entry)) return within(entry, url, commit2, dir);
|
|
4397
|
+
fs2.mkdirSync(path3.dirname(entry), { recursive: true });
|
|
4398
|
+
const tmp = `${entry}.tmp-${process.pid}-${crypto.randomBytes(4).toString("hex")}`;
|
|
4399
|
+
fs2.mkdirSync(tmp, { recursive: true });
|
|
4400
|
+
try {
|
|
4401
|
+
fetchInto(tmp, url, commit2);
|
|
4402
|
+
try {
|
|
4403
|
+
fs2.renameSync(tmp, entry);
|
|
4404
|
+
} catch (e) {
|
|
4405
|
+
if (!fs2.existsSync(entry)) throw e;
|
|
4406
|
+
fs2.rmSync(tmp, { recursive: true, force: true });
|
|
4407
|
+
}
|
|
4408
|
+
} catch (e) {
|
|
4409
|
+
fs2.rmSync(tmp, { recursive: true, force: true });
|
|
4410
|
+
if (fs2.readdirSync(path3.dirname(entry)).length === 0) fs2.rmdirSync(path3.dirname(entry));
|
|
4411
|
+
throw e instanceof SourceUnavailable ? e : new SourceUnavailable(`cannot fetch ${commit2} of ${url}: ${reasonOf(e)}`);
|
|
4412
|
+
}
|
|
4413
|
+
return within(entry, url, commit2, dir);
|
|
4414
|
+
}
|
|
4415
|
+
function resolve3(url, ref) {
|
|
4416
|
+
let listed;
|
|
4417
|
+
try {
|
|
4418
|
+
listed = git(["ls-remote", url, ref ?? "HEAD"]);
|
|
4419
|
+
} catch (e) {
|
|
4420
|
+
throw new SourceUnavailable(`cannot reach ${url}: ${reasonOf(e)}`);
|
|
4421
|
+
}
|
|
4422
|
+
const wanted = commitListed(listed, ref ?? "HEAD");
|
|
4423
|
+
if (!wanted) throw new SourceUnavailable(`${url} lists no ref "${ref ?? "HEAD"}"`);
|
|
4424
|
+
return wanted;
|
|
4425
|
+
}
|
|
4426
|
+
function commitListed(listed, ref) {
|
|
4427
|
+
const lines = listed.split(/\r?\n/).filter((l) => l.trim() !== "").map((l) => l.split(/\s+/));
|
|
4428
|
+
const names = [ref, `refs/heads/${ref}`, `refs/tags/${ref}^{}`, `refs/tags/${ref}`];
|
|
4429
|
+
for (const name of names) {
|
|
4430
|
+
const line = lines.find(([, listedName]) => listedName === name);
|
|
4431
|
+
if (line) return line[0];
|
|
4432
|
+
}
|
|
4433
|
+
return void 0;
|
|
4434
|
+
}
|
|
4435
|
+
function trackedFiles(directory, pathspecs) {
|
|
4436
|
+
if (pathspecs.length === 0) return [];
|
|
4437
|
+
try {
|
|
4438
|
+
return git(["ls-files", "-z", "--", ...pathspecs], directory).split("\0").filter((p) => p !== "");
|
|
4439
|
+
} catch {
|
|
4440
|
+
return null;
|
|
4441
|
+
}
|
|
4442
|
+
}
|
|
4443
|
+
function head(directory) {
|
|
4444
|
+
try {
|
|
4445
|
+
return git(["rev-parse", "HEAD"], directory).trim() || null;
|
|
4446
|
+
} catch {
|
|
4447
|
+
return null;
|
|
4448
|
+
}
|
|
4449
|
+
}
|
|
4450
|
+
function repositoryRoot(directory) {
|
|
4451
|
+
let at = path3.resolve(directory);
|
|
4452
|
+
for (; ; ) {
|
|
4453
|
+
if (fs2.existsSync(path3.join(at, ".git"))) return at;
|
|
4454
|
+
const up = path3.dirname(at);
|
|
4455
|
+
if (up === at) return null;
|
|
4456
|
+
at = up;
|
|
4457
|
+
}
|
|
4458
|
+
}
|
|
4459
|
+
|
|
4460
|
+
// src/utils/yaml.ts
|
|
4461
|
+
var yaml2 = __toESM(require("js-yaml"));
|
|
4462
|
+
function parseYaml(content, sourcePath) {
|
|
4463
|
+
try {
|
|
4464
|
+
return yaml2.load(content);
|
|
4465
|
+
} catch (err) {
|
|
4466
|
+
const loc = sourcePath ? ` (${sourcePath})` : "";
|
|
4467
|
+
throw new Error(`Failed to parse YAML${loc}: ${String(err)}`);
|
|
4468
|
+
}
|
|
4469
|
+
}
|
|
4470
|
+
function serializeYaml(value) {
|
|
4471
|
+
return yaml2.dump(value, {
|
|
4472
|
+
indent: 2,
|
|
4473
|
+
lineWidth: 100,
|
|
4474
|
+
noRefs: true,
|
|
4475
|
+
sortKeys: false
|
|
4476
|
+
});
|
|
4477
|
+
}
|
|
4478
|
+
function readYamlFile(filePath) {
|
|
4479
|
+
const content = readFileOrNull(filePath);
|
|
4480
|
+
if (content === null) return null;
|
|
4481
|
+
return parseYaml(content, filePath);
|
|
4482
|
+
}
|
|
4483
|
+
function writeYamlFile(filePath, value) {
|
|
4484
|
+
writeFile(filePath, serializeYaml(value));
|
|
4485
|
+
}
|
|
4486
|
+
function readJsonFile(filePath) {
|
|
4487
|
+
const content = readFileOrNull(filePath);
|
|
4488
|
+
if (content === null) return null;
|
|
4489
|
+
try {
|
|
4490
|
+
return JSON.parse(content);
|
|
4491
|
+
} catch (err) {
|
|
4492
|
+
throw new Error(`Failed to parse JSON (${filePath}): ${String(err)}`);
|
|
4493
|
+
}
|
|
4494
|
+
}
|
|
4495
|
+
function writeJsonFile(filePath, value) {
|
|
4496
|
+
writeFile(filePath, JSON.stringify(value, null, 2) + "\n");
|
|
4497
|
+
}
|
|
4498
|
+
|
|
4154
4499
|
// src/models/identity-rename.ts
|
|
4155
4500
|
var EDGE = " -> ";
|
|
4156
4501
|
var QUALIFIED_UNIT = /^(\d+:)?([^.\s]+)\.([^.\s]+)$/;
|
|
@@ -4186,7 +4531,7 @@ function rekeyAnchor(rename, anchor) {
|
|
|
4186
4531
|
}
|
|
4187
4532
|
|
|
4188
4533
|
// src/models/project.ts
|
|
4189
|
-
var
|
|
4534
|
+
var path4 = __toESM(require("path"));
|
|
4190
4535
|
var import_zod3 = require("zod");
|
|
4191
4536
|
|
|
4192
4537
|
// src/models/agent.ts
|
|
@@ -4346,6 +4691,14 @@ var AgentBriefSchema = import_zod2.z.object({
|
|
|
4346
4691
|
* agent implements nothing.
|
|
4347
4692
|
*/
|
|
4348
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(),
|
|
4349
4702
|
/**
|
|
4350
4703
|
* Set only on a brief for a member's agent (an id qualified `<alias>::`): the
|
|
4351
4704
|
* member's directory relative to the root the brief was asked from. The
|
|
@@ -4736,7 +5089,7 @@ var ExternalDeclarationSchema = import_zod3.z.object({
|
|
|
4736
5089
|
/**
|
|
4737
5090
|
* The producer's public names this project imports, so its specs may name
|
|
4738
5091
|
* them bare: `['*']` imports every public name the producer exports to this
|
|
4739
|
-
* project, `[
|
|
5092
|
+
* project, `[shared-error]` only those. Plain strings: a malformed entry is
|
|
4740
5093
|
* REPORTED (the declaration's problem), never unreadable.
|
|
4741
5094
|
*/
|
|
4742
5095
|
use: import_zod3.z.array(import_zod3.z.string()).optional(),
|
|
@@ -4939,7 +5292,7 @@ var PACK_EXT_RE = /\.(ya?ml|cjs|js)$/i;
|
|
|
4939
5292
|
function declaredPackNames(config) {
|
|
4940
5293
|
const selection = config.profileSelection;
|
|
4941
5294
|
return [.../* @__PURE__ */ new Set([
|
|
4942
|
-
...(config.extensions?.packs ?? []).map((entry) => typeof entry === "string" ?
|
|
5295
|
+
...(config.extensions?.packs ?? []).map((entry) => typeof entry === "string" ? path4.basename(entry).replace(PACK_EXT_RE, "") : entry.name),
|
|
4943
5296
|
...selection?.requiredPackNames ?? [],
|
|
4944
5297
|
...selection?.defaultPackNames ?? []
|
|
4945
5298
|
])];
|
|
@@ -5237,13 +5590,13 @@ function isPart(config) {
|
|
|
5237
5590
|
|
|
5238
5591
|
// src/config/project-config.ts
|
|
5239
5592
|
function waiDirAt(root) {
|
|
5240
|
-
const wai =
|
|
5241
|
-
const legacy =
|
|
5242
|
-
return !
|
|
5593
|
+
const wai = path5.join(root, ".wai");
|
|
5594
|
+
const legacy = path5.join(root, ".wairon");
|
|
5595
|
+
return !fs3.existsSync(wai) && fs3.existsSync(legacy) ? legacy : wai;
|
|
5243
5596
|
}
|
|
5244
5597
|
function projectConfigFsAdapterAt(rootDir) {
|
|
5245
|
-
const root =
|
|
5246
|
-
const file = () =>
|
|
5598
|
+
const root = path5.resolve(rootDir);
|
|
5599
|
+
const file = () => path5.join(waiDirAt(root), "project.yaml");
|
|
5247
5600
|
return {
|
|
5248
5601
|
readDocument() {
|
|
5249
5602
|
return readYamlFile(file());
|
|
@@ -5257,7 +5610,7 @@ function projectConfigFsAdapterAt(rootDir) {
|
|
|
5257
5610
|
writeFile(file(), editDocumentText(text3, document, root));
|
|
5258
5611
|
},
|
|
5259
5612
|
documentExists() {
|
|
5260
|
-
return
|
|
5613
|
+
return fs3.existsSync(file());
|
|
5261
5614
|
},
|
|
5262
5615
|
readText() {
|
|
5263
5616
|
return readFileOrNull(file());
|
|
@@ -5279,8 +5632,9 @@ function storeOver(adapter, root) {
|
|
|
5279
5632
|
if (!checked.success) {
|
|
5280
5633
|
throw new WaironError(`Refusing to write an invalid .wai/project.yaml at ${root}: ${checked.error.message}`);
|
|
5281
5634
|
}
|
|
5282
|
-
const
|
|
5283
|
-
|
|
5635
|
+
const onDisk = currentDocument();
|
|
5636
|
+
const document = config.partOf !== void 0 ? { schemaVersion: config.schemaVersion, partOf: config.partOf } : withoutDefaultedMaterialization(config, onDisk);
|
|
5637
|
+
adapter.writeDocument(overlayKnownFields(ProjectConfigSchema, document, onDisk));
|
|
5284
5638
|
},
|
|
5285
5639
|
exists() {
|
|
5286
5640
|
return adapter.documentExists();
|
|
@@ -5680,9 +6034,69 @@ function asReadable(document) {
|
|
|
5680
6034
|
if (!isPlainObject(document) || document.partOf === void 0) return document;
|
|
5681
6035
|
return { name: "", createdAt: PART_EPOCH, updatedAt: PART_EPOCH, ...document };
|
|
5682
6036
|
}
|
|
6037
|
+
var MANAGED_AGENT_MARKER = "wairon:managed";
|
|
6038
|
+
var MANAGED_AGENT_NAME = /-(owner|implementer|architect)\.(md|ya?ml)$/;
|
|
6039
|
+
function holdsMaterializedAgentFiles(document, root) {
|
|
6040
|
+
const dirs = new Set(Object.values(DEFAULT_TARGET_DIRS));
|
|
6041
|
+
for (const target of Array.isArray(document.targets) ? document.targets : []) {
|
|
6042
|
+
const outputDir = isPlainObject(target) ? target.outputDir : void 0;
|
|
6043
|
+
if (typeof outputDir === "string") dirs.add(outputDir);
|
|
6044
|
+
}
|
|
6045
|
+
const base = path5.resolve(root);
|
|
6046
|
+
const inside = [];
|
|
6047
|
+
for (const dir of dirs) {
|
|
6048
|
+
const rel2 = path5.relative(base, path5.resolve(base, dir));
|
|
6049
|
+
if (rel2.startsWith("..") || path5.isAbsolute(rel2)) continue;
|
|
6050
|
+
let present = false;
|
|
6051
|
+
try {
|
|
6052
|
+
present = fs3.statSync(path5.join(base, rel2)).isDirectory();
|
|
6053
|
+
} catch {
|
|
6054
|
+
present = false;
|
|
6055
|
+
}
|
|
6056
|
+
if (present) inside.push(rel2 === "" ? "." : rel2.split(path5.sep).join("/"));
|
|
6057
|
+
}
|
|
6058
|
+
if (inside.length === 0) return false;
|
|
6059
|
+
const tracked = trackedFiles(base, inside);
|
|
6060
|
+
const candidates = [];
|
|
6061
|
+
if (tracked === null) {
|
|
6062
|
+
for (const dir of inside) {
|
|
6063
|
+
let names;
|
|
6064
|
+
try {
|
|
6065
|
+
names = fs3.readdirSync(path5.join(base, dir));
|
|
6066
|
+
} catch {
|
|
6067
|
+
continue;
|
|
6068
|
+
}
|
|
6069
|
+
for (const name of names) candidates.push(path5.posix.join(dir, name));
|
|
6070
|
+
}
|
|
6071
|
+
} else {
|
|
6072
|
+
const folders = new Set(inside.map((dir) => path5.posix.normalize(dir)));
|
|
6073
|
+
for (const file of tracked) if (folders.has(path5.posix.dirname(file))) candidates.push(file);
|
|
6074
|
+
}
|
|
6075
|
+
for (const file of candidates) {
|
|
6076
|
+
const head2 = readFileOrNull(path5.join(base, file));
|
|
6077
|
+
if (head2 === null) continue;
|
|
6078
|
+
if (MANAGED_AGENT_NAME.test(path5.posix.basename(file))) return true;
|
|
6079
|
+
if (head2.slice(0, 4096).includes(MANAGED_AGENT_MARKER)) return true;
|
|
6080
|
+
}
|
|
6081
|
+
return false;
|
|
6082
|
+
}
|
|
6083
|
+
function withInferredMaterialization(document, root) {
|
|
6084
|
+
if (!isPlainObject(document) || document.partOf !== void 0) return document;
|
|
6085
|
+
const rules = isPlainObject(document.rules) ? document.rules : void 0;
|
|
6086
|
+
if (rules !== void 0 && hasOwn(rules, "materializeAgentFiles")) return document;
|
|
6087
|
+
if (!holdsMaterializedAgentFiles(document, root)) return document;
|
|
6088
|
+
return { ...document, rules: { ...rules ?? {}, materializeAgentFiles: true } };
|
|
6089
|
+
}
|
|
6090
|
+
function withoutDefaultedMaterialization(config, onDisk) {
|
|
6091
|
+
if (!isPlainObject(onDisk) || config.rules?.materializeAgentFiles !== false) return config;
|
|
6092
|
+
const stated = isPlainObject(onDisk.rules) && hasOwn(onDisk.rules, "materializeAgentFiles");
|
|
6093
|
+
if (stated) return config;
|
|
6094
|
+
const { materializeAgentFiles: _defaulted, ...rules } = config.rules;
|
|
6095
|
+
return { ...config, rules };
|
|
6096
|
+
}
|
|
5683
6097
|
function parseConfig(document, root) {
|
|
5684
6098
|
try {
|
|
5685
|
-
return ProjectConfigSchema.parse(asReadable(document));
|
|
6099
|
+
return ProjectConfigSchema.parse(asReadable(withInferredMaterialization(document, root)));
|
|
5686
6100
|
} catch (e) {
|
|
5687
6101
|
throw new WaironError(
|
|
5688
6102
|
`Invalid .wai/project.yaml: ${e instanceof Error ? e.message : String(e)}
|
|
@@ -6123,15 +6537,15 @@ function indexOver(store, root) {
|
|
|
6123
6537
|
specsDir() {
|
|
6124
6538
|
try {
|
|
6125
6539
|
const declared = store.readField("paths.specsDir");
|
|
6126
|
-
if (declared) return
|
|
6540
|
+
if (declared) return path5.resolve(root, declared);
|
|
6127
6541
|
} catch {
|
|
6128
6542
|
}
|
|
6129
|
-
return
|
|
6543
|
+
return path5.join(waiDirAt(root), "specs");
|
|
6130
6544
|
}
|
|
6131
6545
|
};
|
|
6132
6546
|
}
|
|
6133
6547
|
function projectConfigRepositoryOver(adapter, rootDir) {
|
|
6134
|
-
const root =
|
|
6548
|
+
const root = path5.resolve(rootDir);
|
|
6135
6549
|
const store = storeOver(adapter, root);
|
|
6136
6550
|
const index = indexOver(store, root);
|
|
6137
6551
|
const registry = registryOver(store, root);
|
|
@@ -6324,15 +6738,15 @@ var fs14 = __toESM(require("fs"));
|
|
|
6324
6738
|
var path21 = __toESM(require("path"));
|
|
6325
6739
|
|
|
6326
6740
|
// src/config/paths.ts
|
|
6327
|
-
var
|
|
6328
|
-
var
|
|
6741
|
+
var fs4 = __toESM(require("fs"));
|
|
6742
|
+
var path6 = __toESM(require("path"));
|
|
6329
6743
|
function aiPathsAt(rootDir) {
|
|
6330
|
-
const resolvedRoot =
|
|
6744
|
+
const resolvedRoot = path6.resolve(rootDir);
|
|
6331
6745
|
const aiDirAt2 = (...segments) => {
|
|
6332
|
-
const waiPath =
|
|
6333
|
-
const waironPath =
|
|
6334
|
-
const base = !
|
|
6335
|
-
return
|
|
6746
|
+
const waiPath = path6.join(resolvedRoot, ".wai");
|
|
6747
|
+
const waironPath = path6.join(resolvedRoot, ".wairon");
|
|
6748
|
+
const base = !fs4.existsSync(waiPath) && fs4.existsSync(waironPath) ? waironPath : waiPath;
|
|
6749
|
+
return path6.join(base, ...segments);
|
|
6336
6750
|
};
|
|
6337
6751
|
const specsDir = () => projectConfigRepositoryAt(resolvedRoot).specsDir();
|
|
6338
6752
|
return {
|
|
@@ -6349,12 +6763,12 @@ function aiPathsAt(rootDir) {
|
|
|
6349
6763
|
contextDomainsMd: () => aiDirAt2("context", "domains.md"),
|
|
6350
6764
|
contextWaironGuideMd: () => aiDirAt2("context", "wairon-guide.md"),
|
|
6351
6765
|
specsDir,
|
|
6352
|
-
specsSystem: () =>
|
|
6353
|
-
specsSubsystemsDir: () =>
|
|
6354
|
-
specsComponentsDir: () =>
|
|
6355
|
-
specsInterfacesDir: () =>
|
|
6356
|
-
specsImplementationsDir: () =>
|
|
6357
|
-
specsTypesDir: () =>
|
|
6766
|
+
specsSystem: () => path6.join(specsDir(), ".index.yaml"),
|
|
6767
|
+
specsSubsystemsDir: () => path6.join(specsDir(), "subsystems"),
|
|
6768
|
+
specsComponentsDir: () => path6.join(specsDir(), "components"),
|
|
6769
|
+
specsInterfacesDir: () => path6.join(specsDir(), "interfaces"),
|
|
6770
|
+
specsImplementationsDir: () => path6.join(specsDir(), "implementations"),
|
|
6771
|
+
specsTypesDir: () => path6.join(specsDir(), "types")
|
|
6358
6772
|
};
|
|
6359
6773
|
}
|
|
6360
6774
|
var AI_PATHS = {
|
|
@@ -6388,8 +6802,8 @@ function assertProjectInitialized() {
|
|
|
6388
6802
|
}
|
|
6389
6803
|
|
|
6390
6804
|
// src/core/statehash.ts
|
|
6391
|
-
var
|
|
6392
|
-
var
|
|
6805
|
+
var crypto2 = __toESM(require("crypto"));
|
|
6806
|
+
var path7 = __toESM(require("path"));
|
|
6393
6807
|
|
|
6394
6808
|
// src/utils/canonical-json.ts
|
|
6395
6809
|
function canonicalize(value) {
|
|
@@ -6428,7 +6842,7 @@ function digestTree(keep, asStored = (impl) => impl) {
|
|
|
6428
6842
|
implementations: kind(loadImplementationSpecs().map(asStored)),
|
|
6429
6843
|
types: kind(loadTypeSpecs())
|
|
6430
6844
|
};
|
|
6431
|
-
const digest3 =
|
|
6845
|
+
const digest3 = crypto2.createHash("sha256").update(canonicalize(tree)).digest("hex");
|
|
6432
6846
|
return { algorithm: CONTENT_ALGORITHM, digest: digest3 };
|
|
6433
6847
|
}
|
|
6434
6848
|
function computeStateId() {
|
|
@@ -6448,11 +6862,11 @@ function computeOwnStateId() {
|
|
|
6448
6862
|
return digestTree((id) => (owners.get(id) ?? "") === "", asStored);
|
|
6449
6863
|
}
|
|
6450
6864
|
function isInside(dir, file) {
|
|
6451
|
-
const rel2 =
|
|
6452
|
-
return rel2 !== "" && !rel2.startsWith("..") && !
|
|
6865
|
+
const rel2 = path7.relative(dir, file);
|
|
6866
|
+
return rel2 !== "" && !rel2.startsWith("..") && !path7.isAbsolute(rel2);
|
|
6453
6867
|
}
|
|
6454
6868
|
function fromPartRoot(impl, root, partDir) {
|
|
6455
|
-
const from = (p) =>
|
|
6869
|
+
const from = (p) => path7.isAbsolute(p) ? p : path7.relative(partDir, path7.resolve(root, p)).split(path7.sep).join("/");
|
|
6456
6870
|
return {
|
|
6457
6871
|
...impl,
|
|
6458
6872
|
...impl.sourcePath ? { sourcePath: from(impl.sourcePath) } : {},
|
|
@@ -6465,8 +6879,8 @@ function stateIdEquals(a, b) {
|
|
|
6465
6879
|
}
|
|
6466
6880
|
|
|
6467
6881
|
// src/core/lockfile.ts
|
|
6468
|
-
var
|
|
6469
|
-
var
|
|
6882
|
+
var fs5 = __toESM(require("fs"));
|
|
6883
|
+
var path8 = __toESM(require("path"));
|
|
6470
6884
|
function lockPath() {
|
|
6471
6885
|
return aiDir("lock.json");
|
|
6472
6886
|
}
|
|
@@ -6480,14 +6894,14 @@ function normalizeApprover(value) {
|
|
|
6480
6894
|
}
|
|
6481
6895
|
function readLockRecord() {
|
|
6482
6896
|
try {
|
|
6483
|
-
return normalizeRecord(JSON.parse(
|
|
6897
|
+
return normalizeRecord(JSON.parse(fs5.readFileSync(lockPath(), "utf8")));
|
|
6484
6898
|
} catch {
|
|
6485
6899
|
return null;
|
|
6486
6900
|
}
|
|
6487
6901
|
}
|
|
6488
6902
|
function readLockRecordAt(root) {
|
|
6489
6903
|
try {
|
|
6490
|
-
return normalizeRecord(JSON.parse(
|
|
6904
|
+
return normalizeRecord(JSON.parse(fs5.readFileSync(aiDirAt(root, "lock.json"), "utf8")));
|
|
6491
6905
|
} catch {
|
|
6492
6906
|
return null;
|
|
6493
6907
|
}
|
|
@@ -6498,10 +6912,10 @@ function normalizeRecord(raw) {
|
|
|
6498
6912
|
}
|
|
6499
6913
|
function writeLockRecord(record) {
|
|
6500
6914
|
const p = lockPath();
|
|
6501
|
-
|
|
6915
|
+
fs5.mkdirSync(path8.dirname(p), { recursive: true });
|
|
6502
6916
|
const tmp = `${p}.tmp`;
|
|
6503
|
-
|
|
6504
|
-
|
|
6917
|
+
fs5.writeFileSync(tmp, withLineEndings(p, JSON.stringify(withSortedMaps(record), null, 2) + "\n"));
|
|
6918
|
+
fs5.renameSync(tmp, p);
|
|
6505
6919
|
}
|
|
6506
6920
|
function withSortedMaps(record) {
|
|
6507
6921
|
const sorted = (m) => {
|
|
@@ -6521,8 +6935,8 @@ function withSortedMaps(record) {
|
|
|
6521
6935
|
}
|
|
6522
6936
|
|
|
6523
6937
|
// src/core/spec-files.ts
|
|
6524
|
-
var
|
|
6525
|
-
var
|
|
6938
|
+
var fs6 = __toESM(require("fs"));
|
|
6939
|
+
var path9 = __toESM(require("path"));
|
|
6526
6940
|
var SPEC_FILE_EXTENSION = ".yaml";
|
|
6527
6941
|
function readSpecFile(specPath) {
|
|
6528
6942
|
return readYamlFile(specPath);
|
|
@@ -6534,136 +6948,17 @@ function listSpecFiles(specsDir) {
|
|
|
6534
6948
|
return listFilesRecursive(specsDir, SPEC_FILE_EXTENSION);
|
|
6535
6949
|
}
|
|
6536
6950
|
function removeSpecFile(specPath, specsRoot) {
|
|
6537
|
-
if (!
|
|
6538
|
-
|
|
6539
|
-
pruneEmptyDirs(
|
|
6951
|
+
if (!fs6.existsSync(specPath)) return false;
|
|
6952
|
+
fs6.unlinkSync(specPath);
|
|
6953
|
+
pruneEmptyDirs(path9.dirname(specPath), path9.resolve(specsRoot));
|
|
6540
6954
|
return true;
|
|
6541
6955
|
}
|
|
6542
6956
|
function pruneEmptyDirs(dir, specsRoot) {
|
|
6543
6957
|
let at = dir;
|
|
6544
6958
|
while (at !== specsRoot && at.startsWith(specsRoot)) {
|
|
6545
|
-
if (!
|
|
6546
|
-
|
|
6547
|
-
at =
|
|
6548
|
-
}
|
|
6549
|
-
}
|
|
6550
|
-
|
|
6551
|
-
// src/core/adapters/git-source.ts
|
|
6552
|
-
var import_child_process = require("child_process");
|
|
6553
|
-
var crypto2 = __toESM(require("crypto"));
|
|
6554
|
-
var fs6 = __toESM(require("fs"));
|
|
6555
|
-
var os = __toESM(require("os"));
|
|
6556
|
-
var path8 = __toESM(require("path"));
|
|
6557
|
-
var SourceUnavailable = class extends WaironError {
|
|
6558
|
-
constructor(message) {
|
|
6559
|
-
super(message);
|
|
6560
|
-
this.name = "SourceUnavailable";
|
|
6561
|
-
}
|
|
6562
|
-
};
|
|
6563
|
-
function osUserCache() {
|
|
6564
|
-
if (process.platform === "win32") return process.env.LOCALAPPDATA ?? path8.join(os.homedir(), "AppData", "Local");
|
|
6565
|
-
if (process.platform === "darwin") return path8.join(os.homedir(), "Library", "Caches");
|
|
6566
|
-
return process.env.XDG_CACHE_HOME ?? path8.join(os.homedir(), ".cache");
|
|
6567
|
-
}
|
|
6568
|
-
var cacheRoots = /* @__PURE__ */ new Map();
|
|
6569
|
-
function cacheRoot() {
|
|
6570
|
-
const setting = process.env.WAIRON_CACHE_DIR ?? "";
|
|
6571
|
-
let root = cacheRoots.get(setting);
|
|
6572
|
-
if (root === void 0) {
|
|
6573
|
-
root = setting !== "" ? path8.resolve(setting) : path8.join(osUserCache(), "wairon");
|
|
6574
|
-
cacheRoots.set(setting, root);
|
|
6575
|
-
}
|
|
6576
|
-
return root;
|
|
6577
|
-
}
|
|
6578
|
-
function git(args, cwd) {
|
|
6579
|
-
return (0, import_child_process.execFileSync)("git", args, {
|
|
6580
|
-
cwd,
|
|
6581
|
-
encoding: "utf8",
|
|
6582
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
6583
|
-
env: { ...process.env, GIT_TERMINAL_PROMPT: "0", GIT_ASKPASS: "echo" }
|
|
6584
|
-
});
|
|
6585
|
-
}
|
|
6586
|
-
function reasonOf(e) {
|
|
6587
|
-
const stderr = e?.stderr;
|
|
6588
|
-
const text3 = typeof stderr === "string" && stderr.trim() !== "" ? stderr : e instanceof Error ? e.message : String(e);
|
|
6589
|
-
return text3.trim().split(/\r?\n/).filter((l) => l.trim() !== "").slice(-1)[0] ?? "git failed";
|
|
6590
|
-
}
|
|
6591
|
-
function cacheEntryOf(url, commit2) {
|
|
6592
|
-
const repo = crypto2.createHash("sha256").update(url).digest("hex");
|
|
6593
|
-
return path8.join(cacheRoot(), "git", repo, commit2);
|
|
6594
|
-
}
|
|
6595
|
-
function within(entry, url, commit2, dir) {
|
|
6596
|
-
const at = dir ? path8.join(entry, dir) : entry;
|
|
6597
|
-
if (!fs6.existsSync(at)) {
|
|
6598
|
-
throw new SourceUnavailable(`the commit ${commit2} of ${url} holds no "${dir}"`);
|
|
6599
|
-
}
|
|
6600
|
-
return at;
|
|
6601
|
-
}
|
|
6602
|
-
function fetchInto(tmp, url, commit2) {
|
|
6603
|
-
git(["init", "-q"], tmp);
|
|
6604
|
-
try {
|
|
6605
|
-
git(["fetch", "-q", "--depth", "1", url, commit2], tmp);
|
|
6606
|
-
} catch {
|
|
6607
|
-
git(["fetch", "-q", url, "+refs/heads/*:refs/remotes/source/*", "+refs/tags/*:refs/tags/*"], tmp);
|
|
6608
|
-
}
|
|
6609
|
-
git(["-c", "advice.detachedHead=false", "checkout", "-q", commit2], tmp);
|
|
6610
|
-
fs6.rmSync(path8.join(tmp, ".git"), { recursive: true, force: true });
|
|
6611
|
-
}
|
|
6612
|
-
function fetch(url, commit2, dir) {
|
|
6613
|
-
const entry = cacheEntryOf(url, commit2);
|
|
6614
|
-
if (fs6.existsSync(entry)) return within(entry, url, commit2, dir);
|
|
6615
|
-
fs6.mkdirSync(path8.dirname(entry), { recursive: true });
|
|
6616
|
-
const tmp = `${entry}.tmp-${process.pid}-${crypto2.randomBytes(4).toString("hex")}`;
|
|
6617
|
-
fs6.mkdirSync(tmp, { recursive: true });
|
|
6618
|
-
try {
|
|
6619
|
-
fetchInto(tmp, url, commit2);
|
|
6620
|
-
try {
|
|
6621
|
-
fs6.renameSync(tmp, entry);
|
|
6622
|
-
} catch (e) {
|
|
6623
|
-
if (!fs6.existsSync(entry)) throw e;
|
|
6624
|
-
fs6.rmSync(tmp, { recursive: true, force: true });
|
|
6625
|
-
}
|
|
6626
|
-
} catch (e) {
|
|
6627
|
-
fs6.rmSync(tmp, { recursive: true, force: true });
|
|
6628
|
-
if (fs6.readdirSync(path8.dirname(entry)).length === 0) fs6.rmdirSync(path8.dirname(entry));
|
|
6629
|
-
throw e instanceof SourceUnavailable ? e : new SourceUnavailable(`cannot fetch ${commit2} of ${url}: ${reasonOf(e)}`);
|
|
6630
|
-
}
|
|
6631
|
-
return within(entry, url, commit2, dir);
|
|
6632
|
-
}
|
|
6633
|
-
function resolve7(url, ref) {
|
|
6634
|
-
let listed;
|
|
6635
|
-
try {
|
|
6636
|
-
listed = git(["ls-remote", url, ref ?? "HEAD"]);
|
|
6637
|
-
} catch (e) {
|
|
6638
|
-
throw new SourceUnavailable(`cannot reach ${url}: ${reasonOf(e)}`);
|
|
6639
|
-
}
|
|
6640
|
-
const wanted = commitListed(listed, ref ?? "HEAD");
|
|
6641
|
-
if (!wanted) throw new SourceUnavailable(`${url} lists no ref "${ref ?? "HEAD"}"`);
|
|
6642
|
-
return wanted;
|
|
6643
|
-
}
|
|
6644
|
-
function commitListed(listed, ref) {
|
|
6645
|
-
const lines = listed.split(/\r?\n/).filter((l) => l.trim() !== "").map((l) => l.split(/\s+/));
|
|
6646
|
-
const names = [ref, `refs/heads/${ref}`, `refs/tags/${ref}^{}`, `refs/tags/${ref}`];
|
|
6647
|
-
for (const name of names) {
|
|
6648
|
-
const line = lines.find(([, listedName]) => listedName === name);
|
|
6649
|
-
if (line) return line[0];
|
|
6650
|
-
}
|
|
6651
|
-
return void 0;
|
|
6652
|
-
}
|
|
6653
|
-
function head(directory) {
|
|
6654
|
-
try {
|
|
6655
|
-
return git(["rev-parse", "HEAD"], directory).trim() || null;
|
|
6656
|
-
} catch {
|
|
6657
|
-
return null;
|
|
6658
|
-
}
|
|
6659
|
-
}
|
|
6660
|
-
function repositoryRoot(directory) {
|
|
6661
|
-
let at = path8.resolve(directory);
|
|
6662
|
-
for (; ; ) {
|
|
6663
|
-
if (fs6.existsSync(path8.join(at, ".git"))) return at;
|
|
6664
|
-
const up = path8.dirname(at);
|
|
6665
|
-
if (up === at) return null;
|
|
6666
|
-
at = up;
|
|
6959
|
+
if (!fs6.existsSync(at) || fs6.readdirSync(at).length > 0) return;
|
|
6960
|
+
fs6.rmdirSync(at);
|
|
6961
|
+
at = path9.dirname(at);
|
|
6667
6962
|
}
|
|
6668
6963
|
}
|
|
6669
6964
|
|
|
@@ -8062,7 +8357,11 @@ var GENERIC_ALIASES = /* @__PURE__ */ new Map([
|
|
|
8062
8357
|
["HashMap", { form: "map", arity: 2 }],
|
|
8063
8358
|
["Option", { form: "optional", arity: 1 }],
|
|
8064
8359
|
["Optional", { form: "optional", arity: 1 }],
|
|
8065
|
-
["Promise", { form: "async", arity: 1 }]
|
|
8360
|
+
["Promise", { form: "async", arity: 1 }],
|
|
8361
|
+
// The success-or-failure type the mainstream languages with typed failures
|
|
8362
|
+
// share (Rust, Swift, Kotlin, F#); Rust's spelling is its alias.
|
|
8363
|
+
["result", { form: "result", arity: 2 }],
|
|
8364
|
+
["Result", { form: "result", arity: 2 }]
|
|
8066
8365
|
]);
|
|
8067
8366
|
var NOT_NEUTRAL = /* @__PURE__ */ new Map([
|
|
8068
8367
|
["uuid", "a named value-object holding a string (e.g. order_id)"],
|
|
@@ -8071,7 +8370,6 @@ var NOT_NEUTRAL = /* @__PURE__ */ new Map([
|
|
|
8071
8370
|
["byte", "int, or bytes for binary data"],
|
|
8072
8371
|
["time", "datetime for an instant, or duration for an elapsed time"],
|
|
8073
8372
|
["tuple", "a named value-object"],
|
|
8074
|
-
["result", "the success type as the returns (failures as findings or throws), or a named value-object"],
|
|
8075
8373
|
["error", "a named value-object"],
|
|
8076
8374
|
["never", "void"],
|
|
8077
8375
|
["box", "the inner type (ownership is an implementation detail)"],
|
|
@@ -8266,9 +8564,9 @@ var RawParser = class {
|
|
|
8266
8564
|
}
|
|
8267
8565
|
name += this.ident();
|
|
8268
8566
|
while (this.isP("::") || this.isP(".")) {
|
|
8269
|
-
const
|
|
8567
|
+
const sep15 = this.peek().v;
|
|
8270
8568
|
this.at++;
|
|
8271
|
-
name +=
|
|
8569
|
+
name += sep15 + this.ident();
|
|
8272
8570
|
}
|
|
8273
8571
|
if (!this.isP("<")) return { k: "name", name };
|
|
8274
8572
|
this.at++;
|
|
@@ -8295,6 +8593,10 @@ var RawParser = class {
|
|
|
8295
8593
|
const close2 = this.closingParen();
|
|
8296
8594
|
const after = close2 === -1 ? void 0 : this.tokens[close2 + 1];
|
|
8297
8595
|
if (!after || after.t !== "p" || after.v !== "=>") {
|
|
8596
|
+
if (this.isP(")", 1)) {
|
|
8597
|
+
this.at += 2;
|
|
8598
|
+
return { k: "name", name: "void" };
|
|
8599
|
+
}
|
|
8298
8600
|
this.expect("(");
|
|
8299
8601
|
const inner2 = this.type();
|
|
8300
8602
|
this.expect(")");
|
|
@@ -8403,7 +8705,7 @@ function optionalOf(inner2) {
|
|
|
8403
8705
|
return { form: "optional", args: [inner2] };
|
|
8404
8706
|
}
|
|
8405
8707
|
function inner(ctx) {
|
|
8406
|
-
return { ...ctx, top: false, voidAllowed: false, omittable: false };
|
|
8708
|
+
return { ...ctx, top: false, voidAllowed: false, resultAllowed: false, omittable: false };
|
|
8407
8709
|
}
|
|
8408
8710
|
function normalise(node, ctx, notes) {
|
|
8409
8711
|
switch (node.k) {
|
|
@@ -8437,9 +8739,21 @@ function asyncOf(innerNode, ctx, notes, spelled) {
|
|
|
8437
8739
|
if (!ctx.top || !RETURNS_POSITIONS.has(ctx.position)) {
|
|
8438
8740
|
throw new TypeProblemError("TYPE_POSITION_INVALID", `${spelled} may stand only at the top of a returns, not in a ${ctx.position}`);
|
|
8439
8741
|
}
|
|
8440
|
-
const read2 = normalise(innerNode, { ...inner(ctx), voidAllowed: true }, notes);
|
|
8742
|
+
const read2 = normalise(innerNode, { ...inner(ctx), voidAllowed: true, resultAllowed: true }, notes);
|
|
8441
8743
|
return { form: "async", args: [read2] };
|
|
8442
8744
|
}
|
|
8745
|
+
function resultOf(args, ctx, notes, spelled) {
|
|
8746
|
+
if (!ctx.resultAllowed) {
|
|
8747
|
+
throw new TypeProblemError(
|
|
8748
|
+
"TYPE_POSITION_INVALID",
|
|
8749
|
+
`${spelled} may stand only as a whole returns (or under its async), not in a ${ctx.top ? ctx.position : "collection, union, optional or argument"}`,
|
|
8750
|
+
"the success type here (a failure belongs to a method's returns)"
|
|
8751
|
+
);
|
|
8752
|
+
}
|
|
8753
|
+
const ok = normalise(args[0], { ...inner(ctx), voidAllowed: true }, notes);
|
|
8754
|
+
const err = normalise(args[1], inner(ctx), notes);
|
|
8755
|
+
return { form: "result", args: [ok, err] };
|
|
8756
|
+
}
|
|
8443
8757
|
function bareName(name, ctx, notes) {
|
|
8444
8758
|
const lower = name.toLowerCase();
|
|
8445
8759
|
if (NONES.has(name)) {
|
|
@@ -8477,6 +8791,7 @@ function appliedName(name, args, ctx, notes) {
|
|
|
8477
8791
|
throw new TypeProblemError("TYPE_EXPRESSION_INVALID", `"${name}" takes ${alias.arity} type argument${alias.arity === 1 ? "" : "s"}, ${args.length} given`);
|
|
8478
8792
|
}
|
|
8479
8793
|
if (alias.form === "async") return asyncOf(args[0], ctx, notes, `${name}<...>`);
|
|
8794
|
+
if (alias.form === "result") return resultOf(args, ctx, notes, `${name}<...>`);
|
|
8480
8795
|
if (alias.form === "optional") return optionalOf(normalise(args[0], inner(ctx), notes));
|
|
8481
8796
|
const read2 = args.map((a) => normalise(a, inner(ctx), notes));
|
|
8482
8797
|
if (alias.form === "map") assertMapKey(read2[0], args[0]);
|
|
@@ -8550,6 +8865,8 @@ function canonicalTypeText(expr) {
|
|
|
8550
8865
|
return `${expr.form}<${canonicalTypeText(expr.args[0])}>`;
|
|
8551
8866
|
case "map":
|
|
8552
8867
|
return `map<${canonicalTypeText(expr.args[0])}, ${canonicalTypeText(expr.args[1])}>`;
|
|
8868
|
+
case "result":
|
|
8869
|
+
return `result<${canonicalTypeText(expr.args[0])}, ${canonicalTypeText(expr.args[1])}>`;
|
|
8553
8870
|
case "optional": {
|
|
8554
8871
|
const innerText = canonicalTypeText(expr.args[0]);
|
|
8555
8872
|
return expr.args[0].form === "union" ? `(${innerText})?` : `${innerText}?`;
|
|
@@ -8569,11 +8886,12 @@ function typeNamedRefs(expr) {
|
|
|
8569
8886
|
}
|
|
8570
8887
|
function typeIsMany(expr) {
|
|
8571
8888
|
if (expr.form === "list" || expr.form === "set" || expr.form === "map") return true;
|
|
8572
|
-
if (expr.form === "optional" || expr.form === "async") return typeIsMany(expr.args[0]);
|
|
8889
|
+
if (expr.form === "optional" || expr.form === "async" || expr.form === "result") return typeIsMany(expr.args[0]);
|
|
8573
8890
|
return false;
|
|
8574
8891
|
}
|
|
8575
8892
|
function contextFor(position, omittable2, looseNumber = false) {
|
|
8576
|
-
|
|
8893
|
+
const returns = RETURNS_POSITIONS.has(position);
|
|
8894
|
+
return { position, top: true, voidAllowed: returns, resultAllowed: returns, omittable: omittable2, looseNumber };
|
|
8577
8895
|
}
|
|
8578
8896
|
function parseTypePosition(text3, position, omittable2 = false) {
|
|
8579
8897
|
let raw;
|
|
@@ -8764,6 +9082,47 @@ function typeProblemIntProposal(problem) {
|
|
|
8764
9082
|
if (!parse.expression || parse.problem) return null;
|
|
8765
9083
|
return { specId: problem.specId, kind: problem.kind, path: problem.path, written: problem.written, stored: parse.canonical };
|
|
8766
9084
|
}
|
|
9085
|
+
function stringLiteralValue(text3) {
|
|
9086
|
+
const quote = text3[0];
|
|
9087
|
+
if (quote !== "'" && quote !== '"' && quote !== "`" || text3[text3.length - 1] !== quote) return null;
|
|
9088
|
+
return text3.slice(1, -1).replace(/\\(.)/g, "$1");
|
|
9089
|
+
}
|
|
9090
|
+
function kebabOf(name) {
|
|
9091
|
+
return name.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/[\s_.]+/g, "-").toLowerCase();
|
|
9092
|
+
}
|
|
9093
|
+
function typeProblemEnumProposal(problem) {
|
|
9094
|
+
if (problem.code !== "TYPE_FORM_UNSUPPORTED") return null;
|
|
9095
|
+
if (problem.specId === void 0 || problem.kind === void 0 || problem.path === void 0) return null;
|
|
9096
|
+
let raw;
|
|
9097
|
+
try {
|
|
9098
|
+
raw = readRaw(problem.written);
|
|
9099
|
+
} catch (e) {
|
|
9100
|
+
if (e instanceof TypeSyntaxError) return null;
|
|
9101
|
+
throw e;
|
|
9102
|
+
}
|
|
9103
|
+
const members = raw.k === "union" ? raw.members : [raw];
|
|
9104
|
+
const values = [];
|
|
9105
|
+
let optional = false;
|
|
9106
|
+
for (const member of members) {
|
|
9107
|
+
if (member.k === "name" && member.args === void 0 && NONES.has(member.name)) {
|
|
9108
|
+
optional = true;
|
|
9109
|
+
continue;
|
|
9110
|
+
}
|
|
9111
|
+
const value = member.k === "literal" ? stringLiteralValue(member.text) : null;
|
|
9112
|
+
if (value === null) return null;
|
|
9113
|
+
if (!values.includes(value)) values.push(value);
|
|
9114
|
+
}
|
|
9115
|
+
if (values.length === 0) return null;
|
|
9116
|
+
return {
|
|
9117
|
+
specId: problem.specId,
|
|
9118
|
+
kind: problem.kind,
|
|
9119
|
+
path: problem.path,
|
|
9120
|
+
written: problem.written,
|
|
9121
|
+
enumId: kebabOf(positionNameOf(problem.path)),
|
|
9122
|
+
values,
|
|
9123
|
+
optional
|
|
9124
|
+
};
|
|
9125
|
+
}
|
|
8767
9126
|
function emptyTypeSpellingFacts() {
|
|
8768
9127
|
return { respellings: [], problems: [] };
|
|
8769
9128
|
}
|
|
@@ -8814,7 +9173,7 @@ function extractTypesFromSignature(signature, returns) {
|
|
|
8814
9173
|
let sigCleaned = signature.replace(/\/\/.*$/gm, "").replace(/#.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
|
|
8815
9174
|
sigCleaned = sigCleaned.replace(/(?<=\S)\s*\([^)]*\)\s*$/, "");
|
|
8816
9175
|
sigCleaned = sigCleaned.replace(/(?<=\S)\s+[-—:]\s+[a-z\s_-]+$/, "");
|
|
8817
|
-
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, "");
|
|
8818
9177
|
types.push(...extractTypeIdentifiers(returnsCleaned));
|
|
8819
9178
|
const openParen = sigCleaned.indexOf("(");
|
|
8820
9179
|
const closeParen = sigCleaned.lastIndexOf(")");
|
|
@@ -8845,7 +9204,7 @@ function extractTypesFromSignature(signature, returns) {
|
|
|
8845
9204
|
const colonIndex = param.indexOf(":");
|
|
8846
9205
|
if (colonIndex !== -1) {
|
|
8847
9206
|
const paramType = param.slice(colonIndex + 1).trim();
|
|
8848
|
-
const paramTypeCleaned = paramType.replace(/[a-zA-Z0-9_-]+\s*\??\s
|
|
9207
|
+
const paramTypeCleaned = paramType.replace(/(?<![:\w-])[a-zA-Z0-9_-]+\s*\??\s*:(?!:)/g, "");
|
|
8849
9208
|
types.push(...extractTypeIdentifiers(paramTypeCleaned));
|
|
8850
9209
|
}
|
|
8851
9210
|
}
|
|
@@ -8914,6 +9273,7 @@ function lastSegment(name) {
|
|
|
8914
9273
|
return name.split(/::|\./).filter(Boolean).pop() ?? name;
|
|
8915
9274
|
}
|
|
8916
9275
|
function sameType(code, spec, codeNames) {
|
|
9276
|
+
if (spec.form === "result" && code.form !== "result") return sameType(code, spec.args[0], codeNames);
|
|
8917
9277
|
if (code.form === "primitive" && code.name === "number") return spec.form === "primitive" && (spec.name === "int" || spec.name === "float");
|
|
8918
9278
|
if (code.form === "named" && (spec.form === "named" || spec.form === "applied")) {
|
|
8919
9279
|
if (spec.form === "applied") return false;
|
|
@@ -8950,6 +9310,8 @@ function writeTypeScript(expr) {
|
|
|
8950
9310
|
return expr.args.map(writeTypeScript).join(" | ");
|
|
8951
9311
|
case "async":
|
|
8952
9312
|
return `Promise<${writeTypeScript(expr.args[0])}>`;
|
|
9313
|
+
case "result":
|
|
9314
|
+
return writeTypeScript(expr.args[0]);
|
|
8953
9315
|
case "applied":
|
|
8954
9316
|
return `${expr.name}<${expr.args.map(writeTypeScript).join(", ")}>`;
|
|
8955
9317
|
}
|
|
@@ -8992,6 +9354,7 @@ var typescriptDialect = {
|
|
|
8992
9354
|
"T? \u2192 T | null",
|
|
8993
9355
|
"A | B \u2192 A | B",
|
|
8994
9356
|
"async T \u2192 Promise<T>",
|
|
9357
|
+
"result<T, E> \u2192 T, throwing E on failure (result<void, E> \u2192 void)",
|
|
8995
9358
|
"an enum E \u2192 type E = 'a' | 'b' (a string-literal union alias)",
|
|
8996
9359
|
"a named scalar E holding P \u2192 type E = P (an alias of the primitive)"
|
|
8997
9360
|
];
|
|
@@ -9266,7 +9629,7 @@ function jumpFieldsOf(s) {
|
|
|
9266
9629
|
}
|
|
9267
9630
|
|
|
9268
9631
|
// src/models/code-model.ts
|
|
9269
|
-
var
|
|
9632
|
+
var path10 = __toESM(require("path"));
|
|
9270
9633
|
function pathKey(sourcePath) {
|
|
9271
9634
|
return sourcePath.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
9272
9635
|
}
|
|
@@ -9290,6 +9653,12 @@ function fieldTypesOf(facts, field) {
|
|
|
9290
9653
|
function localTypesOf(facts, name) {
|
|
9291
9654
|
return ownEntry(facts.localTypes, name) ?? NO_DECLARED_TYPES;
|
|
9292
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
|
+
}
|
|
9293
9662
|
function typeBindingOf(facts, name) {
|
|
9294
9663
|
return ownEntry(facts.typeOnlyBindings, name) ?? ownEntry(facts.importBindings, name)?.from;
|
|
9295
9664
|
}
|
|
@@ -9340,7 +9709,7 @@ var EXTENSION_LANGUAGE = {
|
|
|
9340
9709
|
".swift": "swift"
|
|
9341
9710
|
};
|
|
9342
9711
|
function languageOfSourcePath(sourcePath) {
|
|
9343
|
-
return EXTENSION_LANGUAGE[
|
|
9712
|
+
return EXTENSION_LANGUAGE[path10.extname(sourcePath).toLowerCase()];
|
|
9344
9713
|
}
|
|
9345
9714
|
function dialectOf(facts) {
|
|
9346
9715
|
return facts.language ? typeDialectFor(facts.language) : null;
|
|
@@ -9353,7 +9722,7 @@ function resolveImport(fromFile, specifier, knownPaths, packages) {
|
|
|
9353
9722
|
const mapped = ownEntry(packages, specifier);
|
|
9354
9723
|
return mapped !== void 0 && knownPaths.has(mapped) ? mapped : void 0;
|
|
9355
9724
|
}
|
|
9356
|
-
const joined = pathKey(
|
|
9725
|
+
const joined = pathKey(path10.posix.normalize(path10.posix.join(path10.posix.dirname(fromFile), specifier)));
|
|
9357
9726
|
const candidates = [
|
|
9358
9727
|
joined,
|
|
9359
9728
|
joined.replace(/\.js$/, ".ts"),
|
|
@@ -9500,7 +9869,7 @@ function exportTargetKey(e) {
|
|
|
9500
9869
|
}
|
|
9501
9870
|
|
|
9502
9871
|
// src/models/project-family.ts
|
|
9503
|
-
var
|
|
9872
|
+
var path11 = __toESM(require("path"));
|
|
9504
9873
|
function walkMemberPath(byKey3, start, segments) {
|
|
9505
9874
|
let node = start;
|
|
9506
9875
|
for (const segment of segments.slice(0, -1)) {
|
|
@@ -9562,15 +9931,15 @@ function keyIn(project, localId) {
|
|
|
9562
9931
|
return project ? `${project}::${localId}` : localId;
|
|
9563
9932
|
}
|
|
9564
9933
|
function approvalKeyIn(file, root, parts) {
|
|
9565
|
-
const resolved =
|
|
9934
|
+
const resolved = path11.resolve(file);
|
|
9566
9935
|
for (const part of parts) {
|
|
9567
9936
|
if (part.directory === void 0) continue;
|
|
9568
|
-
const rel2 =
|
|
9569
|
-
if (rel2 !== "" && !rel2.startsWith("..") && !
|
|
9570
|
-
return `members/${part.alias}/${rel2.split(
|
|
9937
|
+
const rel2 = path11.relative(part.directory, resolved);
|
|
9938
|
+
if (rel2 !== "" && !rel2.startsWith("..") && !path11.isAbsolute(rel2)) {
|
|
9939
|
+
return `members/${part.alias}/${rel2.split(path11.sep).join("/")}`;
|
|
9571
9940
|
}
|
|
9572
9941
|
}
|
|
9573
|
-
return
|
|
9942
|
+
return path11.relative(root, resolved).split(path11.sep).join("/");
|
|
9574
9943
|
}
|
|
9575
9944
|
function familyNode(family, namespace) {
|
|
9576
9945
|
return family.nodes.find((n) => n.namespace === namespace) ?? null;
|
|
@@ -9703,7 +10072,7 @@ var DesignDependencyRoleSchema = import_zod9.z.enum(DESIGN_DEPENDENCY_ROLES);
|
|
|
9703
10072
|
var ExtSchema = import_zod9.z.record(import_zod9.z.unknown());
|
|
9704
10073
|
var TypeExpressionSchema = import_zod9.z.lazy(
|
|
9705
10074
|
() => import_zod9.z.object({
|
|
9706
|
-
form: import_zod9.z.enum(["primitive", "named", "list", "set", "map", "optional", "union", "async", "applied"]),
|
|
10075
|
+
form: import_zod9.z.enum(["primitive", "named", "list", "set", "map", "optional", "union", "async", "applied", "result"]),
|
|
9707
10076
|
name: import_zod9.z.string().optional(),
|
|
9708
10077
|
args: import_zod9.z.array(TypeExpressionSchema)
|
|
9709
10078
|
}).passthrough()
|
|
@@ -9940,7 +10309,7 @@ function resolveNarrativeLabels(methodName, steps) {
|
|
|
9940
10309
|
|
|
9941
10310
|
// src/core/canvas.ts
|
|
9942
10311
|
var fs7 = __toESM(require("fs"));
|
|
9943
|
-
var
|
|
10312
|
+
var path12 = __toESM(require("path"));
|
|
9944
10313
|
|
|
9945
10314
|
// src/core/canvas-layout.ts
|
|
9946
10315
|
function computeLayout(model, collapsed) {
|
|
@@ -10711,9 +11080,9 @@ function embedJson(value) {
|
|
|
10711
11080
|
}
|
|
10712
11081
|
function loadCytoscapeLib() {
|
|
10713
11082
|
const candidates = [
|
|
10714
|
-
|
|
11083
|
+
path12.resolve(__dirname, "..", "templates", "canvas", "cytoscape.min.js"),
|
|
10715
11084
|
// src/core & dist/cli
|
|
10716
|
-
|
|
11085
|
+
path12.resolve(__dirname, "templates", "canvas", "cytoscape.min.js")
|
|
10717
11086
|
// dist (library entry)
|
|
10718
11087
|
];
|
|
10719
11088
|
for (const p of candidates) {
|
|
@@ -15096,17 +15465,17 @@ function safeFilenamePart(value) {
|
|
|
15096
15465
|
|
|
15097
15466
|
// src/core/externals.ts
|
|
15098
15467
|
var fs8 = __toESM(require("fs"));
|
|
15099
|
-
var
|
|
15468
|
+
var path13 = __toESM(require("path"));
|
|
15100
15469
|
function lockPath2(root) {
|
|
15101
|
-
return
|
|
15470
|
+
return path13.join(root, ".wai", "externals.lock.yaml");
|
|
15102
15471
|
}
|
|
15103
15472
|
function snapshotPath(root, alias) {
|
|
15104
|
-
return
|
|
15473
|
+
return path13.join(root, ".wai", "externals", `${alias}.yaml`);
|
|
15105
15474
|
}
|
|
15106
15475
|
function writeAtomically(file, text3) {
|
|
15107
|
-
fs8.mkdirSync(
|
|
15476
|
+
fs8.mkdirSync(path13.dirname(file), { recursive: true });
|
|
15108
15477
|
const tmp = `${file}.${process.pid}.tmp`;
|
|
15109
|
-
fs8.writeFileSync(tmp, text3);
|
|
15478
|
+
fs8.writeFileSync(tmp, withLineEndings(file, text3));
|
|
15110
15479
|
fs8.renameSync(tmp, file);
|
|
15111
15480
|
}
|
|
15112
15481
|
function sortedLock(lock) {
|
|
@@ -15149,7 +15518,7 @@ var externalsFileAdapter = {
|
|
|
15149
15518
|
const file = snapshotPath(getProjectRoot(), alias);
|
|
15150
15519
|
if (!fs8.existsSync(file)) return false;
|
|
15151
15520
|
fs8.unlinkSync(file);
|
|
15152
|
-
const dir =
|
|
15521
|
+
const dir = path13.dirname(file);
|
|
15153
15522
|
if (fs8.existsSync(dir) && fs8.readdirSync(dir).length === 0) fs8.rmdirSync(dir);
|
|
15154
15523
|
return true;
|
|
15155
15524
|
},
|
|
@@ -15233,7 +15602,7 @@ function unpin(alias) {
|
|
|
15233
15602
|
}
|
|
15234
15603
|
|
|
15235
15604
|
// src/core/openapi.ts
|
|
15236
|
-
var
|
|
15605
|
+
var yaml3 = __toESM(require("js-yaml"));
|
|
15237
15606
|
var PRIMITIVE_SCHEMAS = {
|
|
15238
15607
|
string: { type: "string" },
|
|
15239
15608
|
int: { type: "integer" },
|
|
@@ -15282,6 +15651,8 @@ function schemaOfExpression(expr, closureIds) {
|
|
|
15282
15651
|
return { oneOf: expr.args.map((member) => schemaOfExpression(member, closureIds)) };
|
|
15283
15652
|
case "async":
|
|
15284
15653
|
return schemaOfExpression(expr.args[0], closureIds);
|
|
15654
|
+
case "result":
|
|
15655
|
+
return schemaOfExpression(expr.args[0], closureIds);
|
|
15285
15656
|
}
|
|
15286
15657
|
}
|
|
15287
15658
|
function schemaFor(typeRef2, closureIds) {
|
|
@@ -15291,7 +15662,8 @@ function schemaFor(typeRef2, closureIds) {
|
|
|
15291
15662
|
}
|
|
15292
15663
|
function returnsNothing(returns) {
|
|
15293
15664
|
const expr = expressionOf(returns);
|
|
15294
|
-
const
|
|
15665
|
+
const awaited = expr?.form === "async" ? expr.args[0] : expr;
|
|
15666
|
+
const inner2 = awaited?.form === "result" ? awaited.args[0] : awaited;
|
|
15295
15667
|
return inner2?.form === "primitive" && inner2.name === "void";
|
|
15296
15668
|
}
|
|
15297
15669
|
function enumDescription(values) {
|
|
@@ -15515,7 +15887,7 @@ function toOpenApiSet(snapshot) {
|
|
|
15515
15887
|
}
|
|
15516
15888
|
function isOpenApiDocument(body) {
|
|
15517
15889
|
try {
|
|
15518
|
-
const parsed =
|
|
15890
|
+
const parsed = yaml3.load(body);
|
|
15519
15891
|
return !!parsed && typeof parsed === "object" && typeof parsed.openapi === "string";
|
|
15520
15892
|
} catch {
|
|
15521
15893
|
return false;
|
|
@@ -15626,7 +15998,7 @@ function authFromSecurityScheme(scheme) {
|
|
|
15626
15998
|
function fromOpenApi(document, projectName) {
|
|
15627
15999
|
let parsed;
|
|
15628
16000
|
try {
|
|
15629
|
-
parsed =
|
|
16001
|
+
parsed = yaml3.load(document);
|
|
15630
16002
|
} catch (e) {
|
|
15631
16003
|
throw new Error(`Invalid surface document: not parseable as JSON/YAML (${e instanceof Error ? e.message : String(e)})`);
|
|
15632
16004
|
}
|
|
@@ -15743,61 +16115,6 @@ function fromOpenApi(document, projectName) {
|
|
|
15743
16115
|
});
|
|
15744
16116
|
}
|
|
15745
16117
|
|
|
15746
|
-
// src/config/defaults.ts
|
|
15747
|
-
var os2 = __toESM(require("os"));
|
|
15748
|
-
var path13 = __toESM(require("path"));
|
|
15749
|
-
var DEFAULT_TARGET_DIRS = {
|
|
15750
|
-
claude: ".claude/agents",
|
|
15751
|
-
gemini: ".gemini/agents",
|
|
15752
|
-
agy: ".gemini/agents",
|
|
15753
|
-
cursor: ".cursor/rules",
|
|
15754
|
-
copilot: ".github/prompts",
|
|
15755
|
-
codex: ".codex/agents"
|
|
15756
|
-
};
|
|
15757
|
-
function defaultTargetConfig(type) {
|
|
15758
|
-
return {
|
|
15759
|
-
type,
|
|
15760
|
-
outputDir: DEFAULT_TARGET_DIRS[type],
|
|
15761
|
-
enabled: true
|
|
15762
|
-
};
|
|
15763
|
-
}
|
|
15764
|
-
var WAIRON_VERSION = "5.1.1-dev.101";
|
|
15765
|
-
var GITHUB_REPO = "SYW-Apps/Waffle-AIron";
|
|
15766
|
-
var ARCHITECT_AGENT_ID = "agent-architect";
|
|
15767
|
-
var ARCHITECT_TEMPLATE_ID = "architect";
|
|
15768
|
-
function globalTemplatesDir(projectOverride) {
|
|
15769
|
-
if (process.env.WAIRON_TEMPLATES_DIR) {
|
|
15770
|
-
return process.env.WAIRON_TEMPLATES_DIR;
|
|
15771
|
-
}
|
|
15772
|
-
if (projectOverride) {
|
|
15773
|
-
return projectOverride;
|
|
15774
|
-
}
|
|
15775
|
-
return path13.join(os2.homedir(), ".wairon", "templates");
|
|
15776
|
-
}
|
|
15777
|
-
var BACKEND_COMMANDS = {
|
|
15778
|
-
claude: "claude",
|
|
15779
|
-
gemini: "gemini"
|
|
15780
|
-
};
|
|
15781
|
-
function backendCommand(backend) {
|
|
15782
|
-
return BACKEND_COMMANDS[backend] ?? backend;
|
|
15783
|
-
}
|
|
15784
|
-
var SUPPORTED_ALIASES = ["wai"];
|
|
15785
|
-
var SCAN_EXCLUDE_DIRS = /* @__PURE__ */ new Set([
|
|
15786
|
-
"node_modules",
|
|
15787
|
-
"dist",
|
|
15788
|
-
"build",
|
|
15789
|
-
"out",
|
|
15790
|
-
".git",
|
|
15791
|
-
".cache",
|
|
15792
|
-
"coverage",
|
|
15793
|
-
"__pycache__",
|
|
15794
|
-
".venv",
|
|
15795
|
-
"venv",
|
|
15796
|
-
"target",
|
|
15797
|
-
// Rust
|
|
15798
|
-
"vendor"
|
|
15799
|
-
]);
|
|
15800
|
-
|
|
15801
16118
|
// src/core/design-export.ts
|
|
15802
16119
|
var NoSystemSpecError = class extends Error {
|
|
15803
16120
|
constructor() {
|
|
@@ -16511,7 +16828,9 @@ function writeSurfaceFile(targetPath, snapshot, renderedSet) {
|
|
|
16511
16828
|
const resolved = path14.resolve(targetPath);
|
|
16512
16829
|
fs9.mkdirSync(path14.dirname(resolved), { recursive: true });
|
|
16513
16830
|
if (!renderedSet || renderedSet.length === 0) {
|
|
16514
|
-
|
|
16831
|
+
if (path14.extname(resolved).toLowerCase() === ".json") fs9.writeFileSync(resolved, `${JSON.stringify(snapshot, null, 2)}
|
|
16832
|
+
`);
|
|
16833
|
+
else writeYamlFile(resolved, snapshot);
|
|
16515
16834
|
return [resolved];
|
|
16516
16835
|
}
|
|
16517
16836
|
if (renderedSet.length === 1) {
|
|
@@ -17292,7 +17611,7 @@ function approvalFinding(entry, config) {
|
|
|
17292
17611
|
return finding(
|
|
17293
17612
|
config,
|
|
17294
17613
|
"MEMBER_DRIFTED",
|
|
17295
|
-
`The member ${named(entry.key)} was approved under an earlier gate identity \u2014 the gate identity gained inputs in
|
|
17614
|
+
`The member ${named(entry.key)} was approved under an earlier gate identity \u2014 the gate identity gained inputs in this release (members' composition subjects, \`composition\`; code conformance moved beside the claim), which says nothing about its design. Re-lock it once at its own root (\`wairon lock\`).`,
|
|
17296
17615
|
entry.key
|
|
17297
17616
|
);
|
|
17298
17617
|
}
|
|
@@ -18298,6 +18617,16 @@ function verifyIntegrity(selection, packDir, where) {
|
|
|
18298
18617
|
message: `Pack "${wanted}" resolved to content that does not match the pinned integrity: expected ${selection.integrity}, found ${actual} (${where}). The pack was modified after it was pinned \u2014 reinstall or re-bundle the pinned version, or update the pin with \`wairon pack use ${selection.name} --pin\` if the change is intended.`
|
|
18299
18618
|
};
|
|
18300
18619
|
}
|
|
18620
|
+
function bundlesSingleFile(bundleDir, storePath) {
|
|
18621
|
+
try {
|
|
18622
|
+
if (!fs12.statSync(storePath).isFile()) return false;
|
|
18623
|
+
const entry = packDirEntry(bundleDir);
|
|
18624
|
+
if (!entry || fs12.readdirSync(bundleDir).length !== 1) return false;
|
|
18625
|
+
return fs12.readFileSync(entry).equals(fs12.readFileSync(storePath));
|
|
18626
|
+
} catch {
|
|
18627
|
+
return false;
|
|
18628
|
+
}
|
|
18629
|
+
}
|
|
18301
18630
|
function detectBundleStoreDrift(selection, bundleDir) {
|
|
18302
18631
|
const bundledVersion = path17.basename(bundleDir);
|
|
18303
18632
|
const installed = resolveInstalledPack(selection.name, bundledVersion);
|
|
@@ -18305,6 +18634,7 @@ function detectBundleStoreDrift(selection, bundleDir) {
|
|
|
18305
18634
|
const bundleDigest = computePackDigest(bundleDir);
|
|
18306
18635
|
const storeDigest = computePackDigest(installed.path);
|
|
18307
18636
|
if (bundleDigest === storeDigest) return void 0;
|
|
18637
|
+
if (bundlesSingleFile(bundleDir, installed.path)) return void 0;
|
|
18308
18638
|
return {
|
|
18309
18639
|
code: "PACK_STORE_DRIFT",
|
|
18310
18640
|
severity: "warning",
|
|
@@ -18437,17 +18767,47 @@ function buildCodeIndex(model) {
|
|
|
18437
18767
|
};
|
|
18438
18768
|
const possibleOriginsOf = (site, from) => {
|
|
18439
18769
|
const proven = originOf2(site, from);
|
|
18440
|
-
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
|
+
}
|
|
18441
18773
|
const resolved = scopeOf2(site, from);
|
|
18442
18774
|
if (!resolved) return proven;
|
|
18443
18775
|
const { path: scope, facts: f } = resolved;
|
|
18444
18776
|
const out = new Set(proven);
|
|
18445
|
-
const
|
|
18446
|
-
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;
|
|
18447
18779
|
for (const candidate of landings) out.add(candidate);
|
|
18448
18780
|
};
|
|
18449
|
-
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
|
+
};
|
|
18450
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
|
+
}
|
|
18451
18811
|
if (site.constructed) widenThrough(site.constructed, importBindingOf(f, site.constructed)?.from);
|
|
18452
18812
|
if (site.via) for (const typeName of localTypesOf(f, site.via)) widenThroughType(typeName);
|
|
18453
18813
|
if (site.enclosingClass !== void 0 && holdsMemberBody(f, site.name, site.enclosingClass)) {
|
|
@@ -18469,6 +18829,34 @@ function buildCodeIndex(model) {
|
|
|
18469
18829
|
}
|
|
18470
18830
|
return out;
|
|
18471
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
|
+
};
|
|
18472
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);
|
|
18473
18861
|
const declarationsAt = (path49) => namesAt(declarations, pathKey(path49), (f) => /* @__PURE__ */ new Set([...f.declaredNames, ...f.exportedNames]));
|
|
18474
18862
|
const forwardsOf = (start, name, container) => {
|
|
@@ -18863,7 +19251,7 @@ var localIdsRule = {
|
|
|
18863
19251
|
var projectIdentityRule = {
|
|
18864
19252
|
name: "project-identity",
|
|
18865
19253
|
judges: "design",
|
|
18866
|
-
description: "The bound project is keyed on its id, so the id must be declared, well-formed, stable and its own: a project that declares none answers to its display name slugified until it writes one (PROJECT_ID_DEFAULTED), a project whose name yields no id or whose declared id breaks the grammar has nothing reliable to key on (PROJECT_ID_AMBIGUOUS), an id the rename migration moved from the one the lock approved is owed a re-lock and nothing more (PROJECT_ID_RENAMED: the approved id is one of the configuration's previousIds), and any other id that differs from the one the lock approved has moved under everything that keyed on it (PROJECT_ID_CHANGED). It judges the bound project's own identity only. The family half \u2014 a member with a defaulted id (named with its alias as the id to declare), a member with no id, and two members resolving to one id \u2014 is a family check
|
|
19254
|
+
description: "The bound project is keyed on its id, so the id must be declared, well-formed, stable and its own: a project that declares none answers to its display name slugified until it writes one (PROJECT_ID_DEFAULTED), a project whose name yields no id or whose declared id breaks the grammar has nothing reliable to key on (PROJECT_ID_AMBIGUOUS), an id the rename migration moved from the one the lock approved is owed a re-lock and nothing more (PROJECT_ID_RENAMED: the approved id is one of the configuration's previousIds), and any other id that differs from the one the lock approved has moved under everything that keyed on it (PROJECT_ID_CHANGED). It judges the bound project's own identity only. The family half \u2014 a member with a defaulted id (named with its alias as the id to declare), a member with no id, and two members resolving to one id \u2014 is a family check (family_validator.checkMembers), which reads the family root's graph.",
|
|
18867
19255
|
codes: [
|
|
18868
19256
|
{ code: "PROJECT_ID_AMBIGUOUS", defaultSeverity: "warning", summary: "Project has no usable id: its name yields no slug, or its declared id breaks the grammar" },
|
|
18869
19257
|
{ code: "PROJECT_ID_CHANGED", defaultSeverity: "error", summary: "Project id differs from the id the lock approved" },
|
|
@@ -18881,7 +19269,7 @@ function judgeOwnIdentity(ctx, identity2) {
|
|
|
18881
19269
|
ctx.addIssue(
|
|
18882
19270
|
"notice",
|
|
18883
19271
|
"PROJECT_ID_DEFAULTED",
|
|
18884
|
-
`Project "${identity2.name}" declares no id in .wai/project.yaml, so it answers to "${identity2.id}", derived from its display name \u2014 renaming the project would move it. No save writes it for you; declare it with \`id: ${identity2.id}\` (
|
|
19272
|
+
`Project "${identity2.name}" declares no id in .wai/project.yaml, so it answers to "${identity2.id}", derived from its display name \u2014 renaming the project would move it. No save writes it for you; declare it with \`id: ${identity2.id}\` (a project is keyed on an explicit id).`
|
|
18885
19273
|
);
|
|
18886
19274
|
}
|
|
18887
19275
|
const ambiguous = problem("ambiguous");
|
|
@@ -18978,10 +19366,11 @@ var fieldTypeReferencesRule = {
|
|
|
18978
19366
|
for (const ref of refs) {
|
|
18979
19367
|
if (!ctx.isTypeResolved(ref, /* @__PURE__ */ new Set())) {
|
|
18980
19368
|
const hint = ctx.importHint(ref);
|
|
19369
|
+
const spelled = ctx.aliasSpelling(ref);
|
|
18981
19370
|
ctx.addIssue(
|
|
18982
19371
|
"error",
|
|
18983
19372
|
"UNDEFINED_TYPE_REFERENCE",
|
|
18984
|
-
`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}\`.` : ""}`,
|
|
18985
19374
|
t.id,
|
|
18986
19375
|
isDraftCtx
|
|
18987
19376
|
);
|
|
@@ -19016,10 +19405,11 @@ var signatureTypeReferencesRule = {
|
|
|
19016
19405
|
for (const ref of refs) {
|
|
19017
19406
|
if (!ctx.isTypeResolved(ref, allGenerics)) {
|
|
19018
19407
|
const hint = ctx.importHint(ref);
|
|
19408
|
+
const spelled = ctx.aliasSpelling(ref);
|
|
19019
19409
|
ctx.addIssue(
|
|
19020
19410
|
"error",
|
|
19021
19411
|
"UNDEFINED_TYPE_REFERENCE",
|
|
19022
|
-
`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}\`.` : ""}`,
|
|
19023
19413
|
intf.id,
|
|
19024
19414
|
isDraftCtx
|
|
19025
19415
|
);
|
|
@@ -19038,10 +19428,11 @@ var signatureTypeReferencesRule = {
|
|
|
19038
19428
|
for (const { ref, by } of named2) {
|
|
19039
19429
|
if (ctx.isTypeResolved(ref, typeGenerics)) continue;
|
|
19040
19430
|
const hint = ctx.importHint(ref);
|
|
19431
|
+
const spelled = ctx.aliasSpelling(ref);
|
|
19041
19432
|
ctx.addIssue(
|
|
19042
19433
|
"error",
|
|
19043
19434
|
"UNDEFINED_TYPE_REFERENCE",
|
|
19044
|
-
`${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}\`.` : ""}`,
|
|
19045
19436
|
t.id,
|
|
19046
19437
|
isDraftCtx
|
|
19047
19438
|
);
|
|
@@ -19270,10 +19661,10 @@ function problemMessage(problem, specId) {
|
|
|
19270
19661
|
var typeExpressionsRule = {
|
|
19271
19662
|
name: "type-expressions",
|
|
19272
19663
|
judges: "design",
|
|
19273
|
-
description: "Reports what the scan's type canonicalisation recorded (ctx.typeSpellingFacts), one finding per position, located by spec and path: a stored text that does not parse (TYPE_EXPRESSION_INVALID); one that breaks a position rule \u2014 void or
|
|
19664
|
+
description: "Reports what the scan's type canonicalisation recorded (ctx.typeSpellingFacts), one finding per position, located by spec and path: a stored text that does not parse (TYPE_EXPRESSION_INVALID); one that breaks a position rule \u2014 void, async or result out of place, a map key that is not string, int or an enum, a named scalar's holds that is not one primitive other than void and any, `T??` (TYPE_POSITION_INVALID); one using a form the grammar leaves out \u2014 an inline object shape, an inline function type, a string-literal union, a union mixing in a primitive or a collection, an intersection, a utility type or a tuple (TYPE_FORM_UNSUPPORTED); one naming `number` or a legacy builtin with no neutral meaning (TYPE_NOT_NEUTRAL); and one that is an alias of its canonical spelling (TYPE_SPELLING_STALE, which any save or doctor --fix repairs). Every message names the replacement: `number` asks \"int or float?\" (and says when the doctor repair proposes int), a function type names a signature type, a literal union an enum (which the doctor repair proposes), an inline object or a mixed union a named value-object. The writer refuses all but the stale spelling at write time; on load every one of them is a warning \u2014 these findings are how a tree that held them before the grammar existed is told, while it still loads, stays lockable with its debt in view, and its consumers read the position as opaque any, which never invents a shape. No finding when the run carries no facts (a candidate run).",
|
|
19274
19665
|
codes: [
|
|
19275
|
-
{ code: "TYPE_EXPRESSION_INVALID", defaultSeverity: "
|
|
19276
|
-
{ code: "TYPE_POSITION_INVALID", defaultSeverity: "
|
|
19666
|
+
{ code: "TYPE_EXPRESSION_INVALID", defaultSeverity: "warning", summary: "A structured type position does not parse under the type grammar" },
|
|
19667
|
+
{ code: "TYPE_POSITION_INVALID", defaultSeverity: "warning", summary: "A type position breaks a position rule: void, async or result out of place, a non-scalar map key, or T??" },
|
|
19277
19668
|
{ code: "TYPE_FORM_UNSUPPORTED", defaultSeverity: "warning", summary: "A type position uses a form the grammar leaves out \u2014 inline object, inline function type, string-literal union, union mixing in a primitive or collection, intersection, utility type or tuple \u2014 and the message names its named replacement" },
|
|
19278
19669
|
{ code: "TYPE_NOT_NEUTRAL", defaultSeverity: "warning", summary: "A type position names number (int or float?) or a legacy builtin with no neutral meaning, and the message names the replacement" },
|
|
19279
19670
|
{ code: "TYPE_SPELLING_STALE", defaultSeverity: "warning", summary: "A stored type position is an alias of its canonical spelling; any save or doctor --fix rewrites it" }
|
|
@@ -19283,9 +19674,8 @@ var typeExpressionsRule = {
|
|
|
19283
19674
|
if (!facts) return;
|
|
19284
19675
|
for (const problem of facts.problems) {
|
|
19285
19676
|
if (problem.specId === void 0 || !ctx.isSpecInScope(problem.specId)) continue;
|
|
19286
|
-
const severity = problem.code === "TYPE_EXPRESSION_INVALID" || problem.code === "TYPE_POSITION_INVALID" ? "error" : "warning";
|
|
19287
19677
|
ctx.addIssue(
|
|
19288
|
-
|
|
19678
|
+
"warning",
|
|
19289
19679
|
problem.code,
|
|
19290
19680
|
problemMessage(problem, problem.specId),
|
|
19291
19681
|
problem.specId,
|
|
@@ -19619,7 +20009,7 @@ var publicSurfaceBoundContractRule = {
|
|
|
19619
20009
|
var exportTablesRule = {
|
|
19620
20010
|
name: "export-tables",
|
|
19621
20011
|
judges: "design",
|
|
19622
|
-
description: "Every level's export table must resolve the way a module resolver resolves `export \u2026 from`: one public name binds one target (EXPORT_ID_DUPLICATE when two wildcards \u2014 or two explicit entries \u2014 bind a name to different targets, or two public names share one nameKey, since a bare import could not tell them apart; the name is left out), no chain leads back to itself (EXPORT_CYCLE \u2014 an error for a named chain that never reaches a component or type, a warning for a wildcard cycle, which resolves to its union), every entry names a source and an item that exist, that the source exports and, for an own type, that the level owns \u2014 a subsystem-owned type at L1, a project-level type (no subsystem) for an L0 `{ typeDef }` without `from` \u2014 under a public name of [a-z0-9-_]+ (EXPORT_INVALID), every re-exported component can serve a boundary caller \u2014 a Portal, a gateway being a Portal variant, or an Observer for events (EXPORT_UNCONSUMABLE, at L0 and for L1 re-exports
|
|
20012
|
+
description: "Every level's export table must resolve the way a module resolver resolves `export \u2026 from`: one public name binds one target (EXPORT_ID_DUPLICATE when two wildcards \u2014 or two explicit entries \u2014 bind a name to different targets, or two public names share one nameKey, since a bare import could not tell them apart; the name is left out), no chain leads back to itself (EXPORT_CYCLE \u2014 an error for a named chain that never reaches a component or type, a warning for a wildcard cycle, which resolves to its union), every entry names a source and an item that exist, that the source exports and, for an own type, that the level owns \u2014 a subsystem-owned type at L1, a project-level type (no subsystem) for an L0 `{ typeDef }` without `from` \u2014 under a public name of [a-z0-9-_]+ (EXPORT_INVALID), every re-exported component can serve a boundary caller \u2014 a Portal, a gateway being a Portal variant, or an Observer for events (EXPORT_UNCONSUMABLE, at L0 and for L1 re-exports), and an L0 entry that re-exports a member's or an external's export never declares a wider audience than that export has (EXPORT_WIDENS_AUDIENCE): a re-export can narrow reach, never widen it. The rule judges the facts the export resolver recorded in the scan \u2014 for the bound root's table and every contained member's \u2014 and resolves nothing itself.",
|
|
19623
20013
|
codes: [
|
|
19624
20014
|
{ code: "EXPORT_ID_DUPLICATE", defaultSeverity: "error", summary: "One public name bound to different targets by two wildcard re-exports or two explicit entries" },
|
|
19625
20015
|
{ code: "EXPORT_CYCLE", defaultSeverity: "error", summary: "A re-export chain leads back to itself (a warning for a wildcard cycle, which resolves to its union)" },
|
|
@@ -19781,7 +20171,7 @@ function whyNoRewrite(ref) {
|
|
|
19781
20171
|
var referenceFormsRule = {
|
|
19782
20172
|
name: "reference-forms",
|
|
19783
20173
|
judges: "design",
|
|
19784
|
-
description: "A cross-project reference is `alias::name` (or a bare name a `use` imports): the alias is one of the referring project's members or externals, the name a public name of that project's L0 table. The older forms still bind for one release and are reported (DEPRECATED_REFERENCE_FORM), each naming the spec, where in it, the form as written and the text to write instead (the scan's rewrite: a bare local id, or `alias::publicName`): a leading `::`, which escapes to the root of whatever checkout loads it; `super::`, which climbs the containing projects \u2014 a place in one family, not a name; a member path (`desktop::shell` written inside desktop, `
|
|
20174
|
+
description: "A cross-project reference is `alias::name` (or a bare name a `use` imports): the alias is one of the referring project's members or externals, the name a public name of that project's L0 table. The older forms still bind for one release and are reported (DEPRECATED_REFERENCE_FORM), each naming the spec, where in it, the form as written and the text to write instead (the scan's rewrite: a bare local id, or `alias::publicName`): a leading `::`, which escapes to the root of whatever checkout loads it; `super::`, which climbs the containing projects \u2014 a place in one family, not a name; a member path (`desktop::shell` written inside desktop, `app_core::engine::x` written inside engine), a first segment the referring project does not declare, read from the bound root; and a self-prefix \u2014 the referring project's own id as the first segment, with `::` or `.` (`registry.advisory-channel` inside registry), which `wairon doctor --fix` rewrites to the bare local id. Wairon's own writer emits none of them. A form with no rewrite (its target is out of reach) is reported with the reason. It reads the project graph's authored references, which the scan records before binding erases the form.",
|
|
19785
20175
|
codes: [
|
|
19786
20176
|
{ code: "DEPRECATED_REFERENCE_FORM", defaultSeverity: "notice", summary: "A reference written with a leading ::, super:: or a member path where a local id or alias::name belongs" }
|
|
19787
20177
|
],
|
|
@@ -19812,9 +20202,9 @@ function carriedFields(mount) {
|
|
|
19812
20202
|
var memberDeclarationsRule = {
|
|
19813
20203
|
name: "member-declarations",
|
|
19814
20204
|
judges: "design",
|
|
19815
|
-
description: "A project declares its members in project.yaml `members`, each by one source in one grammar. A deprecated declaration form loads for one release and is reported (DEPRECATED_MOUNT_FORM, notice): the L1 form \u2014 a subsystem carrying projectPath \u2014 naming the mount, its path, the `members` entry that replaces it, and every field it carries that `doctor --fix` will move into the member (or stop on); a mount whose alias `members` also declares is reported as ignored; and
|
|
20205
|
+
description: "A project declares its members in project.yaml `members`, each by one source in one grammar. A deprecated declaration form loads for one release and is reported (DEPRECATED_MOUNT_FORM, notice): the L1 form \u2014 a subsystem carrying projectPath \u2014 naming the mount, its path, the `members` entry that replaces it, and every field it carries that `doctor --fix` will move into the member (or stop on); a mount whose alias `members` also declares is reported as ignored; and the deprecated long-form `path` key, naming the `source` (or shorthand) `doctor --fix` rewrites it to. It also judges what each member of the bound project IS against what is declared or written about it, from the scan's problems at the bound root (a member's own members are that member's gate's): what a member is follows from its content \u2014 an id, an L0 or a lock make it a project, anything else a part \u2014 and every contradiction is ONE finding, MEMBER_KIND_MISMATCH (error), naming the alias and the contradiction: an asserted `as` the content contradicts; a part whose configuration declares project fields (members, externals, rules, extensions, composition, targets) without the id that would make it a project (add the id, or move them to the parent); a non-contained part with no PartOf, or one naming another project (opened alone it could not say what it belongs to); a contained member declaring PartOf; a `use` on a member whose content is a part (its names are already this project's own). A part whose files could not be read \u2014 its directory absent, or its git commit not cached and not fetchable (offline, refused, unknown) \u2014 is PART_UNAVAILABLE (error): the project's own subsystems are missing, so its verdict cannot be a pass. The member findings name no spec: members live in configuration. It reads the project graph's nodes, their legacy mounts, the bound root's declared members and its problems.",
|
|
19816
20206
|
codes: [
|
|
19817
|
-
{ code: "DEPRECATED_MOUNT_FORM", defaultSeverity: "notice", summary: "A member declared in a deprecated form \u2014 an L1 subsystem carrying projectPath, or
|
|
20207
|
+
{ code: "DEPRECATED_MOUNT_FORM", defaultSeverity: "notice", summary: "A member declared in a deprecated form \u2014 an L1 subsystem carrying projectPath, or the long-form `path` key \u2014 which doctor --fix rewrites to a `members` entry with one `source`." },
|
|
19818
20208
|
{ code: "MEMBER_KIND_MISMATCH", defaultSeverity: "error", summary: "What a member's content makes it (an id, L0 or lock: a project; else a part) contradicts what is declared or written about it: an asserted `as`, project fields in a part, a missing or wrong PartOf, a `use` on a part." },
|
|
19819
20209
|
{ code: "PART_UNAVAILABLE", defaultSeverity: "error", summary: "A part's files could not be read (absent directory, unpinned or unfetchable git commit): the project's own subsystems are missing." }
|
|
19820
20210
|
],
|
|
@@ -19852,7 +20242,7 @@ var memberDeclarationsRule = {
|
|
|
19852
20242
|
ctx.addIssue(
|
|
19853
20243
|
"notice",
|
|
19854
20244
|
"DEPRECATED_MOUNT_FORM",
|
|
19855
|
-
`This project declares the member "${member.alias}" with the long-form \`path\` key, which
|
|
20245
|
+
`This project declares the member "${member.alias}" with the long-form \`path\` key, which the one location key replaced: \`source\`. \`doctor --fix\` rewrites it to ${rest ? `\`source: ${written}\` in its long form` : `the shorthand \`${member.alias}: ${written}\``} \u2014 its meaning unchanged.`
|
|
19856
20246
|
);
|
|
19857
20247
|
}
|
|
19858
20248
|
for (const problem of family.problems) {
|
|
@@ -19889,7 +20279,7 @@ function declaration(producer) {
|
|
|
19889
20279
|
var projectBoundariesRule = {
|
|
19890
20280
|
name: "project-boundaries",
|
|
19891
20281
|
judges: "design",
|
|
19892
|
-
description: "The one judge of every cross-project reference the bound project writes (
|
|
20282
|
+
description: "The one judge of every cross-project reference the bound project writes (every project is a crate of its own, reached only through its L0 exports and only as a dependency it declared). It reads each of the project's own references from the project graph \u2014 `::` forms and bare names bound through `use` imports alike \u2014 takes its resolution from resolveCrossProject, decided before any severity from the project's own files alone, and reports every outcome but resolved once, carrying the resolution: forbidden is EXTERNAL_UNDECLARED (the reference reaches a project the referrer declares neither as a member nor as an external, or names a first segment that is none of its subsystems, aliases or foreign providers); missing is EXTERNAL_NOT_EXPORTED (the producer's table \u2014 a contained member's live one, or the external's pin \u2014 has no public name covering the referrer, or not the used method, or exports the type only inside a signature's closure); an ambiguous import is IMPORT_AMBIGUOUS (two imports supply one bare name: two explicit `use` names, or two `*` with no explicit one \u2014 name it `alias::name` or narrow a `use`); unavailable is EXTERNAL_CHECK_UNAVAILABLE (no pin, an unreadable pin, a member absent on disk, or a deprecated form naming no alias \u2014 never a pass). A bare name no import supplies is not this rule's: it is the unresolved-reference finding it always was, with rule_context.importHint appended. A trustedLinks entry naming a subsystem of another project (`alias::sub`) is TRUSTED_LINK_CROSSES_PROJECT: a sanctioned fast lane stays inside one project. An undeclared, unexported or ambiguous reference is an error, since the owner's gate judges it exactly; a reference is never left to a parent's verdict.",
|
|
19893
20283
|
codes: [
|
|
19894
20284
|
{ code: "EXTERNAL_UNDECLARED", defaultSeverity: "error", summary: "A reference reaches another project the referring project declares neither as a member nor as an external, or names a first segment that is none of its subsystems, aliases or foreign providers" },
|
|
19895
20285
|
{ code: "EXTERNAL_NOT_EXPORTED", defaultSeverity: "error", summary: "A reference reaches another project at anything but a public name, or a member of one, whose audience covers the referrer" },
|
|
@@ -22435,7 +22825,7 @@ var reproducibilityRule = {
|
|
|
22435
22825
|
ctx.addIssue(
|
|
22436
22826
|
"warning",
|
|
22437
22827
|
"PACK_SOURCE_UNFETCHABLE",
|
|
22438
|
-
`Pack selection "${selection.name}" records no source and is not bundled, so a fresh machine or CI runner cannot obtain it \u2014 \`wairon pack sync\` has nothing to fetch.
|
|
22828
|
+
`Pack selection "${selection.name}" records no source and is not bundled, so a fresh machine or CI runner cannot obtain it \u2014 \`wairon pack sync\` has nothing to fetch. Commit a copy the repository carries (\`wairon pack bundle ${selection.name}\`, or \`wairon pack bundle --all\` for every selection), or record a URL \`wairon pack sync\` fetches it from (\`wairon pack use ${selection.name} --source <url>\`). A selection \`wairon doctor --fix\` recorded from a pack installed from a local path has no URL to record: bundle it.`
|
|
22439
22829
|
);
|
|
22440
22830
|
}
|
|
22441
22831
|
}
|
|
@@ -23422,10 +23812,22 @@ var invariantBackingRule = {
|
|
|
23422
23812
|
};
|
|
23423
23813
|
|
|
23424
23814
|
// src/core/rules/wiring/invariant-references.ts
|
|
23815
|
+
function unknownInvariantMessage(where, ref, types, aliases) {
|
|
23816
|
+
const parts = splitInvariantRef(ref);
|
|
23817
|
+
const qualifier = parts ? parts.typeRef.lastIndexOf("::") : -1;
|
|
23818
|
+
if (parts && qualifier > 0) {
|
|
23819
|
+
const bare = `${parts.typeRef.slice(qualifier + 2)}.${parts.invariantId}`;
|
|
23820
|
+
if (resolveInvariantRef(bare, types, aliases)) {
|
|
23821
|
+
const prefix = parts.typeRef.slice(0, qualifier);
|
|
23822
|
+
return `${where} asserts invariant "${ref}", whose qualifier "${prefix}::" names no project this one reaches the type through \u2014 the invariant is declared here, so write "${bare}" (a project's own alias, written from inside it, is not a qualifier it reads as its own; \`wairon doctor --fix\` rewrites it in a migrated family).`;
|
|
23823
|
+
}
|
|
23824
|
+
}
|
|
23825
|
+
return `${where} asserts invariant "${ref}", but no entity declares it (expected "<type-id>.<invariant-id>" naming a declared entry in that entity's invariants).`;
|
|
23826
|
+
}
|
|
23425
23827
|
var invariantReferencesRule = {
|
|
23426
23828
|
name: "invariant-references",
|
|
23427
23829
|
judges: "design",
|
|
23428
|
-
description: "Every invariant a narrative step asserts (step.assertsInvariants) must resolve to a declared entity invariant: the reference splits at its last dot into a type reference and an invariant id, and some entity matching that type reference must declare that id. A reference nothing declares is an error \u2014 the step claims a property that exists nowhere.",
|
|
23830
|
+
description: "Every invariant a narrative step asserts (step.assertsInvariants) must resolve to a declared entity invariant: the reference splits at its last dot into a type reference and an invariant id, and some entity matching that type reference must declare that id. A reference nothing declares is an error \u2014 the step claims a property that exists nowhere. The message says which: when the type reference carries a qualifier (`alias::type`) and the bare reference does resolve, the cause is the qualifier \u2014 typically the project's own alias written from inside it \u2014 and the message names it and the bare reference to write instead, never claiming that no entity declares the invariant.",
|
|
23429
23831
|
codes: [
|
|
23430
23832
|
{ code: "UNKNOWN_INVARIANT_REF", defaultSeverity: "error", summary: "A narrative step asserts an invariant that no entity declares" }
|
|
23431
23833
|
],
|
|
@@ -23440,7 +23842,7 @@ var invariantReferencesRule = {
|
|
|
23440
23842
|
ctx.addIssue(
|
|
23441
23843
|
"error",
|
|
23442
23844
|
"UNKNOWN_INVARIANT_REF",
|
|
23443
|
-
`Step ${step.stepNumber} of "${method.name}" in implementation "${impl.id}"
|
|
23845
|
+
unknownInvariantMessage(`Step ${step.stepNumber} of "${method.name}" in implementation "${impl.id}"`, ref, ctx.types, aliases),
|
|
23444
23846
|
impl.id,
|
|
23445
23847
|
isDraftCtx
|
|
23446
23848
|
);
|
|
@@ -23761,19 +24163,40 @@ function closedCallSites(code, file, fn, stopAt, container) {
|
|
|
23761
24163
|
return out;
|
|
23762
24164
|
}
|
|
23763
24165
|
var anchorKey = (symbol, exportedVia) => exportedVia !== void 0 ? `${exportedVia}.${symbol}` : symbol;
|
|
24166
|
+
var bodyKey = (file, symbol, exportedVia) => `${file}|${anchorKey(symbol, exportedVia)}`;
|
|
23764
24167
|
function resolveCallTarget(ctx, componentId, methodName) {
|
|
23765
24168
|
const accepted = /* @__PURE__ */ new Set([methodName]);
|
|
23766
24169
|
const files = /* @__PURE__ */ new Set();
|
|
24170
|
+
const bodies = /* @__PURE__ */ new Set();
|
|
24171
|
+
const anchors = [];
|
|
23767
24172
|
for (const intf of ctx.interfacesByComponent.get(componentId) ?? []) {
|
|
23768
24173
|
for (const impl of ctx.implementationsByContract.get(intf.id) ?? []) {
|
|
23769
24174
|
const method = impl.methods.find((m) => m.name === methodName);
|
|
23770
24175
|
if (!method && !intf.methods.some((m) => m.name === methodName)) continue;
|
|
23771
24176
|
if (method?.symbol) accepted.add(method.symbol);
|
|
23772
24177
|
const file = methodSourceFile(method ?? {}, impl.sourcePath);
|
|
23773
|
-
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 });
|
|
23774
24182
|
}
|
|
23775
24183
|
}
|
|
23776
|
-
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;
|
|
23777
24200
|
}
|
|
23778
24201
|
function isTarget(pairs, target) {
|
|
23779
24202
|
return pairs.some((p) => target.files.has(p.file) && target.accepted.has(p.name));
|
|
@@ -23814,6 +24237,7 @@ function describeSite(site) {
|
|
|
23814
24237
|
if (site.field) return `this.${site.field}.${site.name}(\u2026)`;
|
|
23815
24238
|
if (site.constructed) return `new ${site.constructed}(\u2026).${site.name}(\u2026)`;
|
|
23816
24239
|
if (site.returnedBy) return `${site.returnedBy}(\u2026).${site.name}(\u2026)`;
|
|
24240
|
+
if (site.receiverPath) return `${site.receiverPath.join(".")}.${site.name}(\u2026)`;
|
|
23817
24241
|
if (site.enclosingClass) return `this.${site.name}(\u2026)`;
|
|
23818
24242
|
return `<receiver>.${site.name}(\u2026)`;
|
|
23819
24243
|
}
|
|
@@ -23827,10 +24251,10 @@ function describeMiss(m) {
|
|
|
23827
24251
|
var callConformanceRule = {
|
|
23828
24252
|
name: "call-conformance",
|
|
23829
24253
|
judges: "code",
|
|
23830
|
-
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.",
|
|
23831
24255
|
codes: [
|
|
23832
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 },
|
|
23833
|
-
{ 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 },
|
|
23834
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 }
|
|
23835
24259
|
],
|
|
23836
24260
|
check(ctx) {
|
|
@@ -23858,9 +24282,9 @@ var callConformanceRule = {
|
|
|
23858
24282
|
const here = pathKey(file);
|
|
23859
24283
|
const fnSymbol = implMethod.symbol ?? implMethod.name;
|
|
23860
24284
|
const colocated = modelledAt.get(here) ?? { byKey: /* @__PURE__ */ new Map(), byName: /* @__PURE__ */ new Map() };
|
|
23861
|
-
const ownerOf4 = (site,
|
|
24285
|
+
const ownerOf4 = (site, bodyKey2) => {
|
|
23862
24286
|
const inHere = pathKey(site.from ?? here) === here;
|
|
23863
|
-
if (inHere &&
|
|
24287
|
+
if (inHere && bodyKey2 !== void 0 && !bodyKey2.startsWith("*")) return colocated.byKey.get(bodyKey2);
|
|
23864
24288
|
return colocated.byName.get(site.name);
|
|
23865
24289
|
};
|
|
23866
24290
|
const sites = closedCallSites(code, file, fnSymbol, void 0, implMethod.exportedVia);
|
|
@@ -23869,7 +24293,9 @@ var callConformanceRule = {
|
|
|
23869
24293
|
for (const claim of claimsOf(implMethod)) {
|
|
23870
24294
|
if (!ctx.componentMap.has(claim.component)) continue;
|
|
23871
24295
|
const target = resolveCallTarget(ctx, claim.component, claim.method);
|
|
23872
|
-
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;
|
|
23873
24299
|
if (isTarget(code.forwardsOf(file, fnSymbol, implMethod.exportedVia), target)) continue;
|
|
23874
24300
|
const matching = sites.filter((s) => target.accepted.has(s.name));
|
|
23875
24301
|
const ref = `${claim.component}.${claim.method}`;
|
|
@@ -23913,7 +24339,10 @@ var callConformanceRule = {
|
|
|
23913
24339
|
ctx.addIssue(
|
|
23914
24340
|
"warning",
|
|
23915
24341
|
"CALL_ORIGIN_UNRESOLVED",
|
|
23916
|
-
|
|
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\`.`,
|
|
23917
24346
|
impl.id,
|
|
23918
24347
|
entry.draftContext,
|
|
23919
24348
|
void 0,
|
|
@@ -23930,8 +24359,8 @@ var callConformanceRule = {
|
|
|
23930
24359
|
}
|
|
23931
24360
|
const crossings = /* @__PURE__ */ new Map();
|
|
23932
24361
|
const reached = /* @__PURE__ */ new Map();
|
|
23933
|
-
const stopAtModelled = (site,
|
|
23934
|
-
const owner = ownerOf4(site,
|
|
24362
|
+
const stopAtModelled = (site, bodyKey2) => {
|
|
24363
|
+
const owner = ownerOf4(site, bodyKey2);
|
|
23935
24364
|
reached.set(site, owner);
|
|
23936
24365
|
return owner !== void 0;
|
|
23937
24366
|
};
|
|
@@ -24020,7 +24449,7 @@ var hiddenStateRule = {
|
|
|
24020
24449
|
var dependencyConformanceRule = {
|
|
24021
24450
|
name: "dependency-conformance",
|
|
24022
24451
|
judges: "code",
|
|
24023
|
-
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.",
|
|
24024
24453
|
codes: [
|
|
24025
24454
|
{ code: "UNDECLARED_DEPENDENCY", defaultSeverity: "warning", summary: "A runtime import between component-mapped files has no declared dependsOn/owns (or published-surface) justification" },
|
|
24026
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" }
|
|
@@ -24088,6 +24517,23 @@ var dependencyConformanceRule = {
|
|
|
24088
24517
|
);
|
|
24089
24518
|
}
|
|
24090
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
|
+
});
|
|
24091
24537
|
for (const component of ctx.components) {
|
|
24092
24538
|
const fromFiles = filesOf(component.id);
|
|
24093
24539
|
if (fromFiles.length === 0) continue;
|
|
@@ -24101,6 +24547,7 @@ var dependencyConformanceRule = {
|
|
|
24101
24547
|
if (toFiles.length === 0) continue;
|
|
24102
24548
|
if (fromFiles.some((f) => toFiles.includes(f))) continue;
|
|
24103
24549
|
if (graph2.connects(fromFiles, toFiles, isMountingDeclarer)) continue;
|
|
24550
|
+
if (typeConnects(fromFiles, toFiles)) continue;
|
|
24104
24551
|
const impls = mappedImplsOf(component.id);
|
|
24105
24552
|
const declaredDependsOn = component.dependsOn.includes(targetId);
|
|
24106
24553
|
const declaredOwns = (component.owns ?? []).includes(targetId);
|
|
@@ -24109,7 +24556,7 @@ var dependencyConformanceRule = {
|
|
|
24109
24556
|
ctx.addIssue(
|
|
24110
24557
|
"warning",
|
|
24111
24558
|
"UNREALIZED_DEPENDENCY",
|
|
24112
|
-
`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.`,
|
|
24113
24560
|
impls[0]?.id ?? component.id,
|
|
24114
24561
|
draft,
|
|
24115
24562
|
void 0,
|
|
@@ -24182,11 +24629,13 @@ var portalImportsRule = {
|
|
|
24182
24629
|
var integrationSimDeclarationRule = {
|
|
24183
24630
|
name: "integration-sim-declaration",
|
|
24184
24631
|
judges: "code",
|
|
24185
|
-
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.",
|
|
24186
24633
|
codes: [
|
|
24187
24634
|
{ code: "MISSING_INTEGRATION_SIM", defaultSeverity: "warning", summary: "Complete non-leaf implementation in a sim-adopting subsystem declares no simPath" }
|
|
24188
24635
|
],
|
|
24189
24636
|
check(ctx) {
|
|
24637
|
+
const code = ctx.codeIndex();
|
|
24638
|
+
const hasCode = (files) => files.some((file) => code.factsAt(pathKey(file))?.status !== "missing");
|
|
24190
24639
|
const adopted = /* @__PURE__ */ new Set();
|
|
24191
24640
|
for (const impl of ctx.implementations) {
|
|
24192
24641
|
if (!impl.simPath) continue;
|
|
@@ -24205,6 +24654,7 @@ var integrationSimDeclarationRule = {
|
|
|
24205
24654
|
if (ctx.isImplementationDraft(impl)) continue;
|
|
24206
24655
|
const deps = [.../* @__PURE__ */ new Set([...comp.dependsOn, ...comp.owns])].filter((d) => ctx.componentMap.has(d));
|
|
24207
24656
|
if (deps.length === 0) continue;
|
|
24657
|
+
if (!hasCode(implementationSourceFiles(impl))) continue;
|
|
24208
24658
|
ctx.addIssue(
|
|
24209
24659
|
"warning",
|
|
24210
24660
|
"MISSING_INTEGRATION_SIM",
|
|
@@ -25025,7 +25475,7 @@ var NOTHING_PROMISED = /* @__PURE__ */ new Set();
|
|
|
25025
25475
|
var exportConformanceRule = {
|
|
25026
25476
|
name: "export-conformance",
|
|
25027
25477
|
judges: "code",
|
|
25028
|
-
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.",
|
|
25029
25479
|
codes: [
|
|
25030
25480
|
{
|
|
25031
25481
|
code: "UNDECLARED_EXPORT",
|
|
@@ -25047,6 +25497,7 @@ var exportConformanceRule = {
|
|
|
25047
25497
|
check(ctx) {
|
|
25048
25498
|
const code = ctx.codeIndex();
|
|
25049
25499
|
const realization = ctx.realizationIndex();
|
|
25500
|
+
const ownership = ctx.ownershipIndex();
|
|
25050
25501
|
const promised = /* @__PURE__ */ new Map();
|
|
25051
25502
|
const promise = (file, name) => {
|
|
25052
25503
|
if (!name) return;
|
|
@@ -25173,13 +25624,20 @@ var exportConformanceRule = {
|
|
|
25173
25624
|
const declared = promised.get(file) ?? NOTHING_PROMISED;
|
|
25174
25625
|
const taken = takenFrom.get(file);
|
|
25175
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
|
+
}
|
|
25176
25634
|
const crossed = [];
|
|
25177
25635
|
for (const name of [...new Set(facts.exportedNames)].sort()) {
|
|
25178
25636
|
if (declared.has(name)) continue;
|
|
25179
25637
|
const takers = taken?.get(name);
|
|
25180
25638
|
if (!takers) continue;
|
|
25181
25639
|
const crossing = [...takers].some(
|
|
25182
|
-
(taker) => taker !== file && !realization.componentsAt(taker).some((c) =>
|
|
25640
|
+
(taker) => taker !== file && !realization.componentsAt(taker).some((c) => fence.has(c.id))
|
|
25183
25641
|
);
|
|
25184
25642
|
if (crossing) crossed.push(name);
|
|
25185
25643
|
}
|
|
@@ -26881,6 +27339,8 @@ function walkExact(ts, sourceText, fileName) {
|
|
|
26881
27339
|
const typeOnlyBindings = /* @__PURE__ */ new Map();
|
|
26882
27340
|
const fieldTypes = /* @__PURE__ */ new Map();
|
|
26883
27341
|
const localTypes = /* @__PURE__ */ new Map();
|
|
27342
|
+
const memberTypes = /* @__PURE__ */ new Map();
|
|
27343
|
+
const aliasTargets = /* @__PURE__ */ new Map();
|
|
26884
27344
|
const typeShapes = /* @__PURE__ */ new Map();
|
|
26885
27345
|
const functionParams = /* @__PURE__ */ new Map();
|
|
26886
27346
|
const functionRoutes = /* @__PURE__ */ new Map();
|
|
@@ -26890,7 +27350,7 @@ function walkExact(ts, sourceText, fileName) {
|
|
|
26890
27350
|
const mutableBindings = /* @__PURE__ */ new Set();
|
|
26891
27351
|
const schemaConstants = /* @__PURE__ */ new Map();
|
|
26892
27352
|
const derivedAliases = [];
|
|
26893
|
-
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(".") : "");
|
|
26894
27354
|
const siteKey = (site) => `${site.member ? "m" : "b"}:${receiverKey(site)}:${site.name}`;
|
|
26895
27355
|
const addBindingNames = (name) => {
|
|
26896
27356
|
if (ts.isIdentifier(name)) declared.add(name.text);
|
|
@@ -26905,7 +27365,34 @@ function walkExact(ts, sourceText, fileName) {
|
|
|
26905
27365
|
if (ts.isPrivateIdentifier(name)) return name.text;
|
|
26906
27366
|
return void 0;
|
|
26907
27367
|
};
|
|
26908
|
-
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
|
+
};
|
|
26909
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);
|
|
26910
27397
|
const recordFieldType = (name, type) => {
|
|
26911
27398
|
const typeName = typeReferenceName(type);
|
|
@@ -27102,7 +27589,18 @@ function walkExact(ts, sourceText, fileName) {
|
|
|
27102
27589
|
} else if (receiver.kind === ts.SyntaxKind.ThisKeyword) {
|
|
27103
27590
|
const owner = enclosingClassOf(node);
|
|
27104
27591
|
addSite(owner !== void 0 ? { name, member: true, enclosingClass: owner } : { name, member: true });
|
|
27105
|
-
} 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
|
+
}
|
|
27106
27604
|
}
|
|
27107
27605
|
}
|
|
27108
27606
|
ts.forEachChild(node, count);
|
|
@@ -27237,9 +27735,14 @@ function walkExact(ts, sourceText, fileName) {
|
|
|
27237
27735
|
functionRoutes.set(fnName, known);
|
|
27238
27736
|
}
|
|
27239
27737
|
}
|
|
27240
|
-
if (ts.isPropertyDeclaration(node))
|
|
27241
|
-
|
|
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)) {
|
|
27242
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);
|
|
27243
27746
|
}
|
|
27244
27747
|
if (ts.isParameter(node) || ts.isVariableDeclaration(node)) recordLocalType(node.name, node.type);
|
|
27245
27748
|
if (ts.isInterfaceDeclaration(node)) {
|
|
@@ -27253,6 +27756,10 @@ function walkExact(ts, sourceText, fileName) {
|
|
|
27253
27756
|
if (ts.isTypeLiteralNode(node.type)) recordDeclaredShape(node.name.text, node.type.members, false);
|
|
27254
27757
|
else {
|
|
27255
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
|
+
}
|
|
27256
27763
|
const constant = inferredSchemaConstant(node.type);
|
|
27257
27764
|
if (constant) derivedAliases.push({ name: node.name.text, constant });
|
|
27258
27765
|
const literals = stringLiteralUnion(node.type);
|
|
@@ -27413,7 +27920,7 @@ function walkExact(ts, sourceText, fileName) {
|
|
|
27413
27920
|
}
|
|
27414
27921
|
const reexportOnly = sf.statements.length > 0 && sf.statements.every((st) => ts.isExportDeclaration(st) && !!st.moduleSpecifier);
|
|
27415
27922
|
for (const alias of propertyAliases) if (exported.has(alias.container)) exportAliases.push(alias);
|
|
27416
|
-
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 };
|
|
27417
27924
|
}
|
|
27418
27925
|
var NO_PACKAGES = /* @__PURE__ */ new Map();
|
|
27419
27926
|
function resolveModule(fromFile, specifier, packages) {
|
|
@@ -27785,6 +28292,8 @@ function buildCodeModel(implementations, types, projectRoot, sourceRoots = [], e
|
|
|
27785
28292
|
typeOnlyBindings: Object.fromEntries(facts.typeOnlyBindings),
|
|
27786
28293
|
fieldTypes: Object.fromEntries([...facts.fieldTypes].map(([k, v]) => [k, [...v]])),
|
|
27787
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),
|
|
27788
28297
|
typeShapes: Object.fromEntries(facts.typeShapes),
|
|
27789
28298
|
functionParams: Object.fromEntries(facts.functionParams),
|
|
27790
28299
|
functionRoutes: Object.fromEntries([...facts.functionRoutes].map(([k, v]) => [k, [...v.values()]])),
|
|
@@ -28229,8 +28738,16 @@ function buildRuleContext(opts) {
|
|
|
28229
28738
|
}
|
|
28230
28739
|
if (ownerId && nameKey(first) === nameKey(ownerId)) return ownTypes.some((spec) => typeMatchesRef(spec, rest.join("::")));
|
|
28231
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;
|
|
28232
28748
|
const via = aliasOf(first);
|
|
28233
|
-
|
|
28749
|
+
const publicName = via ? exportedBy(via, nameKey(name), "type") : void 0;
|
|
28750
|
+
return via && publicName ? `${via.alias}::${publicName}` : null;
|
|
28234
28751
|
};
|
|
28235
28752
|
const foreignSurfaceRef = (ref) => {
|
|
28236
28753
|
const segments = ref.split("::").filter((seg) => seg && seg !== "super");
|
|
@@ -28604,6 +29121,7 @@ function buildRuleContext(opts) {
|
|
|
28604
29121
|
isImplementationDraft,
|
|
28605
29122
|
getComponentProfile,
|
|
28606
29123
|
isTypeResolved,
|
|
29124
|
+
aliasSpelling,
|
|
28607
29125
|
targetLanguageFor,
|
|
28608
29126
|
isSpecInScope,
|
|
28609
29127
|
isInChainedSubproject,
|
|
@@ -29774,8 +30292,8 @@ function generateSequenceDiagram(componentId, methodName, options) {
|
|
|
29774
30292
|
const steps = [...methodImpl.narrative].sort((a, b) => a.stepNumber - b.stepNumber);
|
|
29775
30293
|
for (const step of steps) {
|
|
29776
30294
|
for (const par of parallelArms) {
|
|
29777
|
-
const
|
|
29778
|
-
if (
|
|
30295
|
+
const sep15 = par.sepByStep.get(step.stepNumber);
|
|
30296
|
+
if (sep15 !== void 0) lines.push(` and ${escapeLabel(sep15)}`);
|
|
29779
30297
|
}
|
|
29780
30298
|
switch (step.type) {
|
|
29781
30299
|
case "local":
|
|
@@ -30505,6 +31023,15 @@ function classifySource(world, sourceId, label7, sources) {
|
|
|
30505
31023
|
detail: `"${label7}" re-exports from "${sourceId}", which is neither a subsystem of this project, a member nor a declared external the scan read \u2014 a project re-exports only from its subsystems, members and externals of its family`
|
|
30506
31024
|
};
|
|
30507
31025
|
}
|
|
31026
|
+
function duplicateDetail(entries, index) {
|
|
31027
|
+
const e = entries[index];
|
|
31028
|
+
if (e.from !== void 0 || e.component !== void 0 || e.interface !== void 0 || e.typeDef !== void 0) return null;
|
|
31029
|
+
const name = e.as ?? e.id;
|
|
31030
|
+
if (name === void 0) return null;
|
|
31031
|
+
const other = entries.findIndex((x, i) => i !== index && (x.as ?? x.id) === name);
|
|
31032
|
+
if (other === -1) return null;
|
|
31033
|
+
return `has a duplicate entry "${name}": entry #${index + 1} names no source and no item, while entry #${other + 1} declares the same id \u2014 delete the copy (entry #${index + 1}), or give it a source and an item under an id of its own`;
|
|
31034
|
+
}
|
|
30508
31035
|
function bindOwnType(world, owner, e, problems) {
|
|
30509
31036
|
const type = (world.types.get(e.typeDef) ?? []).find((t) => !t.subsystem);
|
|
30510
31037
|
if (!type) {
|
|
@@ -30576,7 +31103,8 @@ function resolveProjectTable(system, subsystems, components, interfaces, types,
|
|
|
30576
31103
|
const owner = sources.namespace ? sources.namespace : system.name;
|
|
30577
31104
|
const problems = [];
|
|
30578
31105
|
const candidates = [];
|
|
30579
|
-
|
|
31106
|
+
const entries = system.publicInterfaces ?? [];
|
|
31107
|
+
entries.forEach((e, index) => {
|
|
30580
31108
|
const label7 = e.as ?? e.id ?? e.interface ?? e.component ?? e.typeDef ?? `#${index + 1}`;
|
|
30581
31109
|
const source = classifySource(world, sourceOf(world, e), label7, sources);
|
|
30582
31110
|
switch (source.kind) {
|
|
@@ -30602,7 +31130,7 @@ function resolveProjectTable(system, subsystems, components, interfaces, types,
|
|
|
30602
31130
|
return;
|
|
30603
31131
|
}
|
|
30604
31132
|
default:
|
|
30605
|
-
problems.push({ kind: "invalid", owner, publicName: label7, detail: source.detail });
|
|
31133
|
+
problems.push({ kind: "invalid", owner, publicName: label7, detail: duplicateDetail(entries, index) ?? source.detail });
|
|
30606
31134
|
}
|
|
30607
31135
|
});
|
|
30608
31136
|
return settle(owner, "project", candidates, problems);
|
|
@@ -31125,6 +31653,28 @@ function mapSignatureSource(value, map) {
|
|
|
31125
31653
|
const head2 = map("signatureFrom", parts.head);
|
|
31126
31654
|
return head2 === parts.head ? value : `${head2}.${parts.tail}`;
|
|
31127
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
|
+
}
|
|
31128
31678
|
function mapSpecReferences(kind, spec, map) {
|
|
31129
31679
|
switch (kind) {
|
|
31130
31680
|
case "subsystem": {
|
|
@@ -31195,6 +31745,16 @@ function qualifiedTypeNames(typeStr) {
|
|
|
31195
31745
|
}
|
|
31196
31746
|
return out;
|
|
31197
31747
|
}
|
|
31748
|
+
function invariantTypeRef(ref) {
|
|
31749
|
+
const at = ref.lastIndexOf(".");
|
|
31750
|
+
return at > 0 && at < ref.length - 1 ? ref.slice(0, at) : void 0;
|
|
31751
|
+
}
|
|
31752
|
+
function mapInvariantRef(ref, map) {
|
|
31753
|
+
const typeRef2 = invariantTypeRef(ref);
|
|
31754
|
+
if (typeRef2 === void 0 || !typeRef2.includes("::")) return ref;
|
|
31755
|
+
const next = map("type", typeRef2);
|
|
31756
|
+
return next === typeRef2 ? ref : `${next}${ref.slice(typeRef2.length)}`;
|
|
31757
|
+
}
|
|
31198
31758
|
function rawReferences(kind, spec) {
|
|
31199
31759
|
const out = [];
|
|
31200
31760
|
const add2 = (position, value) => {
|
|
@@ -31205,11 +31765,8 @@ function rawReferences(kind, spec) {
|
|
|
31205
31765
|
for (const link of spec.trustedLinks ?? []) add2("trustedLinks", link.subsystem);
|
|
31206
31766
|
break;
|
|
31207
31767
|
case "interface":
|
|
31208
|
-
|
|
31209
|
-
|
|
31210
|
-
m.params?.forEach((p) => qualifiedTypeNames(p.type).forEach((t) => add2("type", t)));
|
|
31211
|
-
if (m.signatureFrom !== void 0 && !signatureSourceParts(m.signatureFrom)) add2("type", m.signatureFrom);
|
|
31212
|
-
}
|
|
31768
|
+
case "type":
|
|
31769
|
+
typeSlotReferences(kind, spec).forEach((t) => add2("type", t));
|
|
31213
31770
|
break;
|
|
31214
31771
|
case "implementation":
|
|
31215
31772
|
for (const m of spec.methods) {
|
|
@@ -31217,22 +31774,40 @@ function rawReferences(kind, spec) {
|
|
|
31217
31774
|
for (const step of m.narrative) {
|
|
31218
31775
|
const source = step.auth?.from;
|
|
31219
31776
|
if (typeof source === "string" && source.startsWith(COMPONENT_AUTH_SOURCE)) add2("auth", source.slice(COMPONENT_AUTH_SOURCE.length));
|
|
31777
|
+
for (const ref of step.assertsInvariants ?? []) add2("type", invariantTypeRef(ref));
|
|
31220
31778
|
}
|
|
31221
31779
|
}
|
|
31222
31780
|
break;
|
|
31223
|
-
case "type": {
|
|
31224
|
-
const t = spec;
|
|
31225
|
-
for (const f of t.fields) qualifiedTypeNames(f.type).forEach((n) => add2("type", n));
|
|
31226
|
-
t.params?.forEach((p) => qualifiedTypeNames(p.type).forEach((n) => add2("type", n)));
|
|
31227
|
-
qualifiedTypeNames(t.returns).forEach((n) => add2("type", n));
|
|
31228
|
-
for (const m of t.methods ?? []) m.params?.forEach((p) => qualifiedTypeNames(p.type).forEach((n) => add2("type", n)));
|
|
31229
|
-
break;
|
|
31230
|
-
}
|
|
31231
31781
|
default:
|
|
31232
31782
|
break;
|
|
31233
31783
|
}
|
|
31234
31784
|
return out;
|
|
31235
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
|
+
}
|
|
31236
31811
|
var IMPORTABLE_POSITIONS = /* @__PURE__ */ new Set(["dependsOn", "dispatch", "mounts", "narrative"]);
|
|
31237
31812
|
function bareRawReferences(kind, spec) {
|
|
31238
31813
|
const out = [];
|
|
@@ -31259,7 +31834,11 @@ function bareRawReferences(kind, spec) {
|
|
|
31259
31834
|
const t = spec;
|
|
31260
31835
|
for (const f of t.fields) for (const ref of fieldTypeRefs(t, f.type)) add2("type", ref);
|
|
31261
31836
|
for (const ref of signatureTypeRefs(t)) add2("type", ref);
|
|
31262
|
-
|
|
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
|
+
}
|
|
31263
31842
|
break;
|
|
31264
31843
|
}
|
|
31265
31844
|
case "implementation":
|
|
@@ -31272,6 +31851,9 @@ function bareRawReferences(kind, spec) {
|
|
|
31272
31851
|
}
|
|
31273
31852
|
return out;
|
|
31274
31853
|
}
|
|
31854
|
+
function respellTypeNames(typeStr, map) {
|
|
31855
|
+
return mapTypeNames(typeStr, (_position, name) => map(name));
|
|
31856
|
+
}
|
|
31275
31857
|
function mapTypeNames(typeStr, map) {
|
|
31276
31858
|
if (typeStr === void 0) return void 0;
|
|
31277
31859
|
return typeStr.replace(
|
|
@@ -31295,12 +31877,9 @@ function respellStoredReferences(kind, doc, map) {
|
|
|
31295
31877
|
const values = holder[key];
|
|
31296
31878
|
if (Array.isArray(values)) holder[key] = values.map((v) => typeof v === "string" ? map(position, v) : v);
|
|
31297
31879
|
};
|
|
31298
|
-
const typed = (holder, key) => {
|
|
31299
|
-
if (typeof holder[key] === "string") holder[key] = mapTypeNames(holder[key], map);
|
|
31300
|
-
};
|
|
31301
31880
|
const published = (entries) => {
|
|
31302
31881
|
for (const e of list(entries)) {
|
|
31303
|
-
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");
|
|
31304
31883
|
each(e, "consumers", "publicInterfaces");
|
|
31305
31884
|
}
|
|
31306
31885
|
};
|
|
@@ -31322,20 +31901,16 @@ function respellStoredReferences(kind, doc, map) {
|
|
|
31322
31901
|
break;
|
|
31323
31902
|
case "interface":
|
|
31324
31903
|
at(doc, "component", "contract");
|
|
31325
|
-
|
|
31326
|
-
typed(m, "signature");
|
|
31327
|
-
typed(m, "returns");
|
|
31328
|
-
for (const p of list(m.params)) typed(p, "type");
|
|
31329
|
-
if (typeof m.signatureFrom === "string") {
|
|
31330
|
-
m.signatureFrom = signatureSourceParts(m.signatureFrom) ? mapSignatureSource(m.signatureFrom, map) : mapTypeNames(m.signatureFrom, map);
|
|
31331
|
-
}
|
|
31332
|
-
}
|
|
31904
|
+
typedSlots("interface", doc, map);
|
|
31333
31905
|
break;
|
|
31334
31906
|
case "implementation":
|
|
31335
31907
|
at(doc, "contract", "contract");
|
|
31336
31908
|
for (const m of list(doc.methods)) {
|
|
31337
31909
|
for (const step of list(m.narrative)) {
|
|
31338
31910
|
at(step, "targetComponent", "narrative");
|
|
31911
|
+
if (Array.isArray(step.assertsInvariants)) {
|
|
31912
|
+
step.assertsInvariants = step.assertsInvariants.map((ref) => typeof ref === "string" ? mapInvariantRef(ref, map) : ref);
|
|
31913
|
+
}
|
|
31339
31914
|
const auth = step.auth;
|
|
31340
31915
|
if (auth && typeof auth.from === "string" && auth.from.startsWith(COMPONENT_AUTH_SOURCE)) {
|
|
31341
31916
|
auth.from = `${COMPONENT_AUTH_SOURCE}${map("auth", auth.from.slice(COMPONENT_AUTH_SOURCE.length))}`;
|
|
@@ -31353,34 +31928,33 @@ function respellStoredReferences(kind, doc, map) {
|
|
|
31353
31928
|
case "type":
|
|
31354
31929
|
at(doc, "subsystem", "subsystem");
|
|
31355
31930
|
at(doc, "group", "group");
|
|
31356
|
-
|
|
31357
|
-
for (const p of list(doc.params)) typed(p, "type");
|
|
31358
|
-
typed(doc, "returns");
|
|
31359
|
-
for (const m of list(doc.methods)) for (const p of list(m.params)) typed(p, "type");
|
|
31931
|
+
typedSlots("type", doc, map);
|
|
31360
31932
|
break;
|
|
31361
31933
|
default:
|
|
31362
31934
|
break;
|
|
31363
31935
|
}
|
|
31364
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
|
+
}
|
|
31365
31943
|
function mapRawReferences(kind, spec, map) {
|
|
31366
31944
|
switch (kind) {
|
|
31367
31945
|
case "subsystem": {
|
|
31368
31946
|
const s = spec;
|
|
31369
31947
|
return { ...s, trustedLinks: (s.trustedLinks ?? []).map((l) => ({ ...l, subsystem: map("trustedLinks", l.subsystem) })) };
|
|
31370
31948
|
}
|
|
31371
|
-
case "interface":
|
|
31372
|
-
|
|
31373
|
-
|
|
31374
|
-
|
|
31375
|
-
|
|
31376
|
-
|
|
31377
|
-
|
|
31378
|
-
|
|
31379
|
-
|
|
31380
|
-
// A type source is a raw type position; a method source's head is bound, never raw.
|
|
31381
|
-
...m.signatureFrom !== void 0 && !signatureSourceParts(m.signatureFrom) ? { signatureFrom: mapTypeNames(m.signatureFrom, map) } : {}
|
|
31382
|
-
}))
|
|
31383
|
-
};
|
|
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;
|
|
31384
31958
|
}
|
|
31385
31959
|
case "implementation": {
|
|
31386
31960
|
const impl = spec;
|
|
@@ -31395,7 +31969,8 @@ function mapRawReferences(kind, spec, map) {
|
|
|
31395
31969
|
return call && next !== call.compId ? `${next}.${call.methodName}` : entry;
|
|
31396
31970
|
})
|
|
31397
31971
|
} : {},
|
|
31398
|
-
narrative: m.narrative.map((
|
|
31972
|
+
narrative: m.narrative.map((authored) => {
|
|
31973
|
+
const step = authored.assertsInvariants ? { ...authored, assertsInvariants: authored.assertsInvariants.map((ref) => mapInvariantRef(ref, map)) } : authored;
|
|
31399
31974
|
const source = step.auth?.from;
|
|
31400
31975
|
if (typeof source !== "string" || !source.startsWith(COMPONENT_AUTH_SOURCE)) return step;
|
|
31401
31976
|
const auth = step.auth;
|
|
@@ -31404,17 +31979,6 @@ function mapRawReferences(kind, spec, map) {
|
|
|
31404
31979
|
}))
|
|
31405
31980
|
};
|
|
31406
31981
|
}
|
|
31407
|
-
case "type": {
|
|
31408
|
-
const t = spec;
|
|
31409
|
-
const typedParams = (params) => params ? { params: params.map((p) => ({ ...p, type: mapTypeNames(p.type, map) })) } : {};
|
|
31410
|
-
return {
|
|
31411
|
-
...t,
|
|
31412
|
-
fields: t.fields.map((f) => ({ ...f, type: mapTypeNames(f.type, map) })),
|
|
31413
|
-
...typedParams(t.params),
|
|
31414
|
-
...t.returns !== void 0 ? { returns: mapTypeNames(t.returns, map) } : {},
|
|
31415
|
-
methods: (t.methods ?? []).map((m) => ({ ...m, ...typedParams(m.params) }))
|
|
31416
|
-
};
|
|
31417
|
-
}
|
|
31418
31982
|
default:
|
|
31419
31983
|
return spec;
|
|
31420
31984
|
}
|
|
@@ -33491,12 +34055,12 @@ var SpecWorkspace = class {
|
|
|
33491
34055
|
return this.relativizeSpec("interface", stored);
|
|
33492
34056
|
}
|
|
33493
34057
|
prepareImplementationForWrite(spec, carry = true) {
|
|
33494
|
-
const
|
|
34058
|
+
const relative20 = this.relativizeSpec("implementation", spec, carry);
|
|
33495
34059
|
const member = this.getSubprojectPrefix(spec.id);
|
|
33496
34060
|
const memberRoot = member ? this.resolveSubprojectForNamespace(member) : null;
|
|
33497
|
-
if (memberRoot) return childRelativeFilePaths(
|
|
34061
|
+
if (memberRoot) return childRelativeFilePaths(relative20, this.rootDir, memberRoot);
|
|
33498
34062
|
const partDir = this.partHolding(this.getImplementationPath(spec.id, spec.contract))?.part.directory;
|
|
33499
|
-
return partDir ? partRelativeFilePaths(
|
|
34063
|
+
return partDir ? partRelativeFilePaths(relative20, this.rootDir, partDir) : relative20;
|
|
33500
34064
|
}
|
|
33501
34065
|
/**
|
|
33502
34066
|
* A type written in its stored form — every type position canonical (one
|
|
@@ -35801,6 +36365,12 @@ function readLockState(current2) {
|
|
|
35801
36365
|
if (!record) return { state: "unlocked", record: null, current: current2 };
|
|
35802
36366
|
return { state: stateIdEquals(record.stateId, current2) ? "locked" : "stale", record, current: current2 };
|
|
35803
36367
|
}
|
|
36368
|
+
function writeLockRecord2(record) {
|
|
36369
|
+
writeLockRecord(record);
|
|
36370
|
+
}
|
|
36371
|
+
function renderDiagram2(format, relations) {
|
|
36372
|
+
return renderDiagram(format, relations);
|
|
36373
|
+
}
|
|
35804
36374
|
function createProjectConfig(config) {
|
|
35805
36375
|
projectConfigRepository.create(config);
|
|
35806
36376
|
}
|
|
@@ -36047,7 +36617,7 @@ function resolveGit(external, config) {
|
|
|
36047
36617
|
const declared = gitDeclarationOf(config, external);
|
|
36048
36618
|
if (!declared) return { ...external, problem: "git producer unavailable: its declaration names no repository" };
|
|
36049
36619
|
try {
|
|
36050
|
-
const commit2 = external.role === "member" && declared.commit ? declared.commit :
|
|
36620
|
+
const commit2 = external.role === "member" && declared.commit ? declared.commit : resolve3(declared.url, declared.ref);
|
|
36051
36621
|
const directory = fetch(declared.url, commit2, declared.dir);
|
|
36052
36622
|
return { ...external, directory: path23.resolve(directory), commit: commit2, problem: void 0 };
|
|
36053
36623
|
} catch (e) {
|
|
@@ -36476,7 +37046,7 @@ function scaffoldPart(alias, memberDir, contained) {
|
|
|
36476
37046
|
}
|
|
36477
37047
|
function createGitMember(alias, source, description, as) {
|
|
36478
37048
|
const url = source.git;
|
|
36479
|
-
const commit2 = source.commit ??
|
|
37049
|
+
const commit2 = source.commit ?? resolve3(url, source.ref);
|
|
36480
37050
|
const dir = fetch(url, commit2, source.dir);
|
|
36481
37051
|
const kind = holdsProjectContent(dir) ? "project" : "part";
|
|
36482
37052
|
if (as !== void 0 && as !== kind) {
|
|
@@ -36634,7 +37204,7 @@ function isWithinDir2(dir, file) {
|
|
|
36634
37204
|
var MOVED_REF_POSITIONS = /* @__PURE__ */ new Set(["component", "interface"]);
|
|
36635
37205
|
var INTO_MEMBER_POSITIONS = /* @__PURE__ */ new Set([...MOVED_REF_POSITIONS, "type"]);
|
|
36636
37206
|
function externalizeSubsystem(subsystemId, path49, as) {
|
|
36637
|
-
externalizeInto(subsystemId, path49, as);
|
|
37207
|
+
return externalizeInto(subsystemId, path49, as);
|
|
36638
37208
|
}
|
|
36639
37209
|
function externalizeInto(subsystemId, partPath, as) {
|
|
36640
37210
|
if (subsystemId.includes("::")) {
|
|
@@ -36653,7 +37223,8 @@ function externalizeInto(subsystemId, partPath, as) {
|
|
|
36653
37223
|
const parentRoot = getProjectRoot();
|
|
36654
37224
|
const parentSpecsDir = aiPathsAt(parentRoot).specsDir();
|
|
36655
37225
|
const fooDir = path25.join(parentSpecsDir, subsystemId);
|
|
36656
|
-
|
|
37226
|
+
const owned = subsystemFiles(subsystemId, parentSpecsDir);
|
|
37227
|
+
if (owned.files.length === 0) throw new WaironError(`subsystem specs not found for "${subsystemId}" under ${parentSpecsDir}`);
|
|
36657
37228
|
const relPath = toPosixPath(partPath);
|
|
36658
37229
|
const partDir = assertContainedProjectPath(parentRoot, relPath);
|
|
36659
37230
|
if (path25.resolve(partDir) === path25.resolve(parentRoot)) throw new WaironError(`cannot externalize "${subsystemId}": the path "${relPath}" is the project root itself.`);
|
|
@@ -36666,19 +37237,74 @@ function externalizeInto(subsystemId, partPath, as) {
|
|
|
36666
37237
|
if (!joined && (declaredMembers(projectConfigRepository.load() ?? {}).some((m) => m.alias === alias) || projectConfigRepository.load()?.externals?.[alias] !== void 0)) {
|
|
36667
37238
|
throw new WaironError(`cannot externalize "${subsystemId}": this project already declares the alias "${alias}".`);
|
|
36668
37239
|
}
|
|
36669
|
-
const
|
|
36670
|
-
|
|
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(", ")}.`);
|
|
36671
37247
|
if (as === "project") {
|
|
36672
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}\`).`);
|
|
36673
|
-
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);
|
|
36674
37251
|
if (refusals.length > 0) throw new WaironError(`cannot externalize "${subsystemId}" as a project: ${refusals.join("; ")}.`);
|
|
36675
37252
|
}
|
|
36676
|
-
|
|
36677
|
-
|
|
36678
|
-
|
|
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);
|
|
36679
37267
|
if (!joined) projectConfigRepository.declareMember(alias, { source: relPath });
|
|
36680
37268
|
invalidateSpecCache();
|
|
36681
|
-
|
|
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
|
+
}
|
|
36682
37308
|
}
|
|
36683
37309
|
function specFilesUnder(dir) {
|
|
36684
37310
|
return fs16.existsSync(dir) ? listFilesRecursive(dir, ".yaml") : [];
|
|
@@ -36704,7 +37330,8 @@ function rawSpecs(files) {
|
|
|
36704
37330
|
return out;
|
|
36705
37331
|
}
|
|
36706
37332
|
function typeTokens(expression) {
|
|
36707
|
-
|
|
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("::"));
|
|
36708
37335
|
}
|
|
36709
37336
|
function referencesOf(spec, wholeParams) {
|
|
36710
37337
|
const out = [];
|
|
@@ -36713,13 +37340,13 @@ function referencesOf(spec, wholeParams) {
|
|
|
36713
37340
|
return ref;
|
|
36714
37341
|
});
|
|
36715
37342
|
const expressions = [];
|
|
36716
|
-
|
|
36717
|
-
|
|
36718
|
-
|
|
36719
|
-
|
|
36720
|
-
|
|
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);
|
|
36721
37349
|
}
|
|
36722
|
-
if (spec.kind === "type") for (const f of spec.raw.fields ?? []) expressions.push(f?.type);
|
|
36723
37350
|
for (const token of expressions.flatMap(typeTokens)) out.push({ ref: token, position: "bare-type" });
|
|
36724
37351
|
return out;
|
|
36725
37352
|
}
|
|
@@ -36727,6 +37354,9 @@ function crossingsOf(inside, outside) {
|
|
|
36727
37354
|
const idsOf = (specs) => new Set(specs.filter((s) => s.kind !== "system").map((s) => s.id));
|
|
36728
37355
|
const insideIds = idsOf(inside);
|
|
36729
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);
|
|
36730
37360
|
const typeIds = (specs) => new Map(specs.filter((s) => s.kind === "type").map((s) => [nameKey(s.id), s.id]));
|
|
36731
37361
|
const insideTypes = typeIds(inside);
|
|
36732
37362
|
const outsideTypes = typeIds(outside);
|
|
@@ -36744,13 +37374,14 @@ function crossingsOf(inside, outside) {
|
|
|
36744
37374
|
}
|
|
36745
37375
|
return found;
|
|
36746
37376
|
};
|
|
36747
|
-
return { out: collect(inside,
|
|
37377
|
+
return { out: collect(inside, outsideTargets, outsideTypes, true), in: collect(outside, insideTargets, insideTypes, false), insideIds, outsideIds, insideTargets, outsideTargets };
|
|
36748
37378
|
}
|
|
36749
37379
|
function promoteRefusals(family, insideFiles, outsideFiles, id) {
|
|
36750
37380
|
const out = [];
|
|
36751
37381
|
const inside = rawSpecs(insideFiles);
|
|
36752
37382
|
const outside = rawSpecs(outsideFiles);
|
|
36753
37383
|
const crossing = crossingsOf(inside, outside);
|
|
37384
|
+
out.push(...unreachableUpward(inside, outside));
|
|
36754
37385
|
const subsystemsIn = new Set(inside.filter((s) => s.kind === "subsystem").map((s) => s.id));
|
|
36755
37386
|
for (const s of [...inside, ...outside].filter((x) => x.kind === "subsystem")) {
|
|
36756
37387
|
for (const link of s.raw.trustedLinks ?? []) {
|
|
@@ -36792,13 +37423,19 @@ function climbsOf(family, ids, parentId) {
|
|
|
36792
37423
|
return { map, refused: [...new Set(refused2)] };
|
|
36793
37424
|
}
|
|
36794
37425
|
var PROJECT_ID_RE_LOCAL = /^[a-z0-9](?:[a-z0-9._-]*[a-z0-9])?$/;
|
|
36795
|
-
function rewriteRefsIn(specs, remap) {
|
|
37426
|
+
function rewriteRefsIn(specs, remap, record) {
|
|
36796
37427
|
const rewritten = [];
|
|
36797
37428
|
for (const spec of specs) {
|
|
36798
37429
|
const raw = readYamlFile(spec.file);
|
|
36799
|
-
|
|
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;
|
|
36800
37436
|
writeYamlFile(spec.file, raw);
|
|
36801
37437
|
rewritten.push({ kind: spec.kind, id: spec.id });
|
|
37438
|
+
if (record) for (const e of edits) recordRespelling(record.result, e, record.member);
|
|
36802
37439
|
}
|
|
36803
37440
|
return rewritten;
|
|
36804
37441
|
}
|
|
@@ -36848,15 +37485,19 @@ function promoteMember(alias, id) {
|
|
|
36848
37485
|
patchKeepingStamp(s.file, (raw) => raw.parentSystem === projectName ? false : (raw.parentSystem = projectName, true));
|
|
36849
37486
|
}
|
|
36850
37487
|
if (declaration2?.as === "part") projectConfigRepository.updateMember(alias, { as: "" });
|
|
36851
|
-
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 });
|
|
36852
37490
|
const climbs = climbsOf(family, crossing.insideIds, parentId).map;
|
|
36853
37491
|
const climbing = inside.filter((s) => family.authoredReferences.some((r) => r.form === "super" && r.specId === s.id));
|
|
36854
|
-
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);
|
|
36855
37494
|
invalidateSpecCache();
|
|
36856
|
-
|
|
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, {}));
|
|
36857
37497
|
for (const s of inside.filter((x) => x.kind === "subsystem")) restateReExports(s.id, alias);
|
|
36858
|
-
exportCrossings(crossing.out, dir, alias, parentId, "out");
|
|
36859
|
-
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);
|
|
36860
37501
|
invalidateSpecCache();
|
|
36861
37502
|
for (const spec of parentSide) if (spec.kind !== "system") normalizeReferences(spec.kind, spec.id);
|
|
36862
37503
|
const memberKey = graph().nodes.find((n) => n.parent === "" && n.mountAlias === alias)?.namespace;
|
|
@@ -36875,6 +37516,165 @@ function promoteMember(alias, id) {
|
|
|
36875
37516
|
});
|
|
36876
37517
|
}
|
|
36877
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();
|
|
36878
37678
|
}
|
|
36879
37679
|
function promotedVision(id, parentName) {
|
|
36880
37680
|
return `Project ${id}, promoted from a part of ${parentName}`;
|
|
@@ -36882,43 +37682,57 @@ function promotedVision(id, parentName) {
|
|
|
36882
37682
|
function unique(value, index, all) {
|
|
36883
37683
|
return all.indexOf(value) === index;
|
|
36884
37684
|
}
|
|
36885
|
-
function exportCrossings(crossings, memberDir, alias, parentId, direction) {
|
|
37685
|
+
function exportCrossings(crossings, memberDir, alias, parentId, direction, result) {
|
|
36886
37686
|
const targets = [...new Set(crossings.map((c) => c.target))];
|
|
36887
37687
|
const bare = [...new Set(crossings.filter((c) => c.position === "bare-type").map((c) => c.target))];
|
|
36888
37688
|
if (direction === "out") {
|
|
36889
|
-
exportItems(targets);
|
|
36890
|
-
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
|
+
}
|
|
36891
37694
|
} else {
|
|
36892
|
-
runWithProjectRoot(memberDir, () => exportItems(targets));
|
|
36893
|
-
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
|
+
}
|
|
36894
37700
|
}
|
|
36895
37701
|
}
|
|
36896
37702
|
function localIn(key, project) {
|
|
36897
37703
|
return project && key.startsWith(`${project}::`) ? key.slice(project.length + 2) : key;
|
|
36898
37704
|
}
|
|
36899
37705
|
function exportItems(ids) {
|
|
36900
|
-
if (ids.length === 0) return;
|
|
37706
|
+
if (ids.length === 0) return [];
|
|
36901
37707
|
invalidateSpecCache();
|
|
36902
37708
|
const system = loadSystemSpec();
|
|
36903
|
-
if (!system) return;
|
|
37709
|
+
if (!system) return [];
|
|
36904
37710
|
const entries = [...system.publicInterfaces ?? []];
|
|
36905
37711
|
const types = loadTypeSpecs();
|
|
37712
|
+
const added = [];
|
|
36906
37713
|
for (const id of ids) {
|
|
36907
37714
|
if (!PUBLIC_NAME_RE.test(id)) continue;
|
|
36908
37715
|
const type = types.find((t) => t.id === id);
|
|
36909
37716
|
if (type) {
|
|
36910
37717
|
if (type.subsystem) exportTypeFromSubsystem(type.subsystem, id);
|
|
36911
|
-
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
|
+
}
|
|
36912
37722
|
continue;
|
|
36913
37723
|
}
|
|
36914
37724
|
const component = loadComponentSpec(id);
|
|
36915
37725
|
if (!component) continue;
|
|
36916
37726
|
const exported = resolveSubsystemExports(component.subsystem).entries.some((e) => e.kind === "component" && e.component === id);
|
|
36917
|
-
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
|
+
}
|
|
36918
37731
|
}
|
|
36919
37732
|
if (entries.length !== (system.publicInterfaces ?? []).length) {
|
|
36920
37733
|
saveSystemSpec({ ...system, publicInterfaces: entries });
|
|
36921
37734
|
}
|
|
37735
|
+
return added;
|
|
36922
37736
|
}
|
|
36923
37737
|
function exportTypeFromSubsystem(subsystemId, typeId) {
|
|
36924
37738
|
const sub = loadSubsystemSpec(subsystemId);
|
|
@@ -36962,7 +37776,7 @@ function internalizeMember(alias, destination) {
|
|
|
36962
37776
|
if (refusals.length > 0) {
|
|
36963
37777
|
throw new WaironError(`cannot internalize "${alias}": the member cannot be taken in whole \u2014 ${refusals.join("; ")}.`);
|
|
36964
37778
|
}
|
|
36965
|
-
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: [] };
|
|
36966
37780
|
const intoMember = crossingReferences(family, "", member.namespace);
|
|
36967
37781
|
const backOut = crossingReferences(family, member.namespace, "");
|
|
36968
37782
|
deleteMount(alias);
|
|
@@ -36992,7 +37806,7 @@ function internalizePart(alias, part) {
|
|
|
36992
37806
|
projectConfigRepository.removeMember(alias);
|
|
36993
37807
|
const deleted = deleteMemberWai(partDir);
|
|
36994
37808
|
invalidateSpecCache();
|
|
36995
|
-
return { subsystems: part.subsystems, types, placed: [], externals: [], deleted, notCarried: [] };
|
|
37809
|
+
return { subsystems: part.subsystems, types, placed: [], externals: [], deleted, notCarried: [], consumersRestored: [] };
|
|
36996
37810
|
}
|
|
36997
37811
|
function scanMember(family, alias, member, destination) {
|
|
36998
37812
|
const root = getProjectRoot();
|
|
@@ -37341,7 +38155,7 @@ function demoteMember(alias, destination) {
|
|
|
37341
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"}`);
|
|
37342
38156
|
}
|
|
37343
38157
|
if (refusals.length > 0) throw new WaironError(`demote refused: "${alias}" cannot become a part \u2014 ${refusals.join("; ")}.`);
|
|
37344
|
-
const result = { subsystems, types, placed: [], externals: [], deleted: [], notCarried: [] };
|
|
38158
|
+
const result = { subsystems, types, placed: [], externals: [], deleted: [], notCarried: [], consumersRestored: [] };
|
|
37345
38159
|
const intoMember = localsOf(dir);
|
|
37346
38160
|
const backOut = localsOf(root);
|
|
37347
38161
|
invalidateSpecCache();
|
|
@@ -37358,6 +38172,10 @@ function demoteMember(alias, destination) {
|
|
|
37358
38172
|
const parentSide = rewriteRefsIn(ownSpecs, across(/* @__PURE__ */ new Set([alias]), intoMember, false));
|
|
37359
38173
|
const memberSide = rewriteRefsIn(memberSpecs, across(parentAliases, backOut, true));
|
|
37360
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);
|
|
37361
38179
|
const parentName = loadSystemSpec()?.name;
|
|
37362
38180
|
if (parentName) {
|
|
37363
38181
|
for (const s of memberSpecs.filter((x) => x.kind === "subsystem")) {
|
|
@@ -37382,6 +38200,79 @@ function demoteMember(alias, destination) {
|
|
|
37382
38200
|
invalidateSpecCache();
|
|
37383
38201
|
return result;
|
|
37384
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
|
+
}
|
|
37385
38276
|
function deleteProjectContent(memberDir) {
|
|
37386
38277
|
const wai = path25.join(memberDir, ".wai");
|
|
37387
38278
|
const deleted = [];
|
|
@@ -37411,9 +38302,9 @@ function retireBoundaryExports(family, used, memberKey) {
|
|
|
37411
38302
|
const { publicInterfaces: _dropped, ...rest } = system;
|
|
37412
38303
|
saveSystemSpec(kept.length > 0 ? { ...rest, publicInterfaces: kept } : rest);
|
|
37413
38304
|
invalidateSpecCache();
|
|
37414
|
-
for (const e of retired.filter((x) => x.typeDef !== void 0
|
|
38305
|
+
for (const e of retired.filter((x) => x.typeDef !== void 0)) {
|
|
37415
38306
|
const type = loadTypeSpecs().find((t) => t.id === e.typeDef);
|
|
37416
|
-
if (!type?.subsystem) continue;
|
|
38307
|
+
if (!type?.subsystem || e.from !== void 0 && e.from !== type.subsystem) continue;
|
|
37417
38308
|
patchKeepingStamp(getSubsystemPath(type.subsystem), (raw) => {
|
|
37418
38309
|
const before = (raw.publicInterfaces ?? []).length;
|
|
37419
38310
|
raw.publicInterfaces = (raw.publicInterfaces ?? []).filter((pi) => !(pi?.typeDef === e.typeDef && pi?.from === void 0 && Object.keys(pi).length === 1));
|
|
@@ -37436,7 +38327,7 @@ function advanceMember(alias, ref, commit2, dryRun) {
|
|
|
37436
38327
|
const url = member.source.git;
|
|
37437
38328
|
const from = member.source.commit ?? "";
|
|
37438
38329
|
const followed = commit2 === void 0 ? ref ?? member.source.ref : void 0;
|
|
37439
|
-
const to = commit2 ??
|
|
38330
|
+
const to = commit2 ?? resolve3(url, followed);
|
|
37440
38331
|
const after = specContents(fetch(url, to, member.source.dir));
|
|
37441
38332
|
const before = specContents(from !== "" ? fetch(url, from, member.source.dir) : null);
|
|
37442
38333
|
const added = [...after.keys()].filter((f) => !before.has(f)).sort();
|
|
@@ -37582,7 +38473,7 @@ function renameComponent(componentId, newId) {
|
|
|
37582
38473
|
}
|
|
37583
38474
|
if (componentId.includes("::")) {
|
|
37584
38475
|
throw new WaironError(
|
|
37585
|
-
`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>`)}`
|
|
37586
38477
|
);
|
|
37587
38478
|
}
|
|
37588
38479
|
if (!SpecIdSchema.safeParse(newId).success) {
|
|
@@ -37745,7 +38636,7 @@ function renameMethod(componentId, methodName, newName, pinSymbol) {
|
|
|
37745
38636
|
}
|
|
37746
38637
|
if (componentId.includes("::")) {
|
|
37747
38638
|
throw new WaironError(
|
|
37748
|
-
`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>`)}`
|
|
37749
38640
|
);
|
|
37750
38641
|
}
|
|
37751
38642
|
if (!METHOD_NAME.test(newName)) {
|
|
@@ -37903,18 +38794,29 @@ function respellLike(written, oldId, newId) {
|
|
|
37903
38794
|
if (written.includes("-")) return kebab(newId);
|
|
37904
38795
|
return /^[A-Z]/.test(written) ? pascal(newId) : newId;
|
|
37905
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
|
+
}
|
|
37906
38808
|
function renameType(typeId, newId) {
|
|
37907
38809
|
const all = loadTypeSpecs();
|
|
37908
38810
|
const cut = typeId.lastIndexOf("::");
|
|
37909
38811
|
const qualifier = cut >= 0 ? typeId.slice(0, cut) : void 0;
|
|
37910
38812
|
const ownSubsystem = qualifier !== void 0 && loadSubsystemSpecs().some((s) => s.id === qualifier);
|
|
37911
38813
|
if (qualifier !== void 0 && !ownSubsystem) {
|
|
37912
|
-
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.`);
|
|
37913
38815
|
}
|
|
37914
38816
|
const type = qualifier !== void 0 ? all.find((t) => t.id === typeId.slice(cut + 2) && t.subsystem === qualifier) ?? null : loadTypeSpec(typeId);
|
|
37915
38817
|
if (!type) throw new WaironError(`type-missing: no type has the id "${typeId}".`);
|
|
37916
38818
|
if (type.id.includes("::")) {
|
|
37917
|
-
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>`)}`);
|
|
37918
38820
|
}
|
|
37919
38821
|
const oldId = type.id;
|
|
37920
38822
|
if (!SpecIdSchema.safeParse(newId).success) {
|
|
@@ -38202,8 +39104,8 @@ function walkForPackages(projectRoot, currentDir, depth, results) {
|
|
|
38202
39104
|
}
|
|
38203
39105
|
}
|
|
38204
39106
|
function pathToId(relPath) {
|
|
38205
|
-
const
|
|
38206
|
-
return
|
|
39107
|
+
const basename13 = path26.basename(relPath);
|
|
39108
|
+
return basename13.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
38207
39109
|
}
|
|
38208
39110
|
function pathToName(relPath) {
|
|
38209
39111
|
const id = pathToId(relPath);
|
|
@@ -38687,7 +39589,7 @@ function isWithinDir3(dir, target) {
|
|
|
38687
39589
|
const rel2 = path29.relative(path29.resolve(dir), path29.resolve(target));
|
|
38688
39590
|
return rel2 === "" || !rel2.startsWith("..") && !path29.isAbsolute(rel2);
|
|
38689
39591
|
}
|
|
38690
|
-
function resolveLayer(delegate) {
|
|
39592
|
+
function resolveLayer(delegate, implementers = false) {
|
|
38691
39593
|
projectFilesCache.clear();
|
|
38692
39594
|
const system = loadSystemSpec();
|
|
38693
39595
|
if (!system) return [];
|
|
@@ -38818,7 +39720,7 @@ function resolveLayer(delegate) {
|
|
|
38818
39720
|
updatedAt: sub.updatedAt
|
|
38819
39721
|
});
|
|
38820
39722
|
}
|
|
38821
|
-
if (config.rules.generateComponentImplementers) {
|
|
39723
|
+
if (config.rules.generateComponentImplementers || implementers) {
|
|
38822
39724
|
for (const comp of components) {
|
|
38823
39725
|
const compInterfaces = interfaces.filter((i) => i.component === comp.id);
|
|
38824
39726
|
const compInterfaceIds = compInterfaces.map((i) => i.id);
|
|
@@ -38886,7 +39788,7 @@ function resolveLayer(delegate) {
|
|
|
38886
39788
|
}
|
|
38887
39789
|
var UnknownAgentError = class extends WaironError {
|
|
38888
39790
|
constructor(agentId, knownIds) {
|
|
38889
|
-
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.`);
|
|
38890
39792
|
this.name = "UnknownAgentError";
|
|
38891
39793
|
}
|
|
38892
39794
|
};
|
|
@@ -38897,7 +39799,7 @@ function composeAgentBrief(agentId) {
|
|
|
38897
39799
|
return throughMount(brief, hop.alias, hop.relative);
|
|
38898
39800
|
}
|
|
38899
39801
|
const records = resolveAgentTopology();
|
|
38900
|
-
const record = records.find((r) => r.id === agentId);
|
|
39802
|
+
const record = records.find((r) => r.id === agentId) ?? componentImplementer(agentId);
|
|
38901
39803
|
if (!record) {
|
|
38902
39804
|
throw new UnknownAgentError(agentId, records.map((r) => r.id));
|
|
38903
39805
|
}
|
|
@@ -38916,6 +39818,17 @@ function composeAgentBrief(agentId) {
|
|
|
38916
39818
|
domainName: "",
|
|
38917
39819
|
variantGuidance: record.variantGuidance ?? ""
|
|
38918
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
|
+
}
|
|
38919
39832
|
const guidance = loadAgentOverride(agentId);
|
|
38920
39833
|
if (guidance !== null) {
|
|
38921
39834
|
instructions = `${instructions.trimEnd()}
|
|
@@ -38950,10 +39863,34 @@ ${typeMapping.map((line) => `- ${line}`).join("\n")}
|
|
|
38950
39863
|
instructions,
|
|
38951
39864
|
variantGuidance: record.variantGuidance || void 0,
|
|
38952
39865
|
...typeMapping ? { typeMapping } : {},
|
|
39866
|
+
...codeFence ? { codeFence } : {},
|
|
38953
39867
|
profile: budget ? profile : void 0,
|
|
38954
39868
|
budget
|
|
38955
39869
|
};
|
|
38956
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
|
+
}
|
|
38957
39894
|
function implementationLanguage(record) {
|
|
38958
39895
|
const owned = new Set(record.ownedPaths);
|
|
38959
39896
|
const implementations = loadImplementationSpecs().filter((impl) => implementationSourceFiles(impl).some((file) => owned.has(file)));
|
|
@@ -38977,17 +39914,18 @@ function memberHop(agentId) {
|
|
|
38977
39914
|
const alias = agentId.slice(0, at);
|
|
38978
39915
|
const member = graph().nodes.find((n) => n.parent === "" && n.mountAlias === alias);
|
|
38979
39916
|
if (!member) return null;
|
|
38980
|
-
const
|
|
38981
|
-
return { alias, rest: agentId.slice(at + 2), directory: member.directory, relative:
|
|
39917
|
+
const relative20 = path29.relative(getProjectRoot(), member.directory).split(path29.sep).join("/");
|
|
39918
|
+
return { alias, rest: agentId.slice(at + 2), directory: member.directory, relative: relative20 };
|
|
38982
39919
|
}
|
|
38983
|
-
function throughMount(brief, alias,
|
|
38984
|
-
const under = (p) => path29.posix.join(
|
|
39920
|
+
function throughMount(brief, alias, relative20) {
|
|
39921
|
+
const under = (p) => path29.posix.join(relative20, p);
|
|
38985
39922
|
return {
|
|
38986
39923
|
...brief,
|
|
38987
39924
|
agentId: `${alias}::${brief.agentId}`,
|
|
38988
39925
|
ownedPaths: brief.ownedPaths.map(under),
|
|
39926
|
+
...brief.codeFence ? { codeFence: brief.codeFence.map(under) } : {},
|
|
38989
39927
|
...brief.readPaths ? { readPaths: brief.readPaths.map(under) } : {},
|
|
38990
|
-
root: brief.root ? under(brief.root) :
|
|
39928
|
+
root: brief.root ? under(brief.root) : relative20
|
|
38991
39929
|
};
|
|
38992
39930
|
}
|
|
38993
39931
|
function loadRegistry() {
|
|
@@ -39266,19 +40204,19 @@ function commitStagedTree(stagingDir, destDir, roots, backupExisting) {
|
|
|
39266
40204
|
const backupDir = path34.join(root, BACKUP_DIR, timestampSlug());
|
|
39267
40205
|
let backedUp = false;
|
|
39268
40206
|
for (const rootRel of roots) {
|
|
39269
|
-
const
|
|
39270
|
-
const stagedSpecDir = path34.join(stagingDir,
|
|
40207
|
+
const relative20 = rootRel === "." ? "" : rootRel;
|
|
40208
|
+
const stagedSpecDir = path34.join(stagingDir, relative20, ".wai");
|
|
39271
40209
|
if (!fs20.existsSync(stagedSpecDir)) continue;
|
|
39272
|
-
const liveSpecDir = liveSpecDirFor(path34.join(root,
|
|
40210
|
+
const liveSpecDir = liveSpecDirFor(path34.join(root, relative20));
|
|
39273
40211
|
if (backupExisting && liveSpecDir && fs20.existsSync(liveSpecDir)) {
|
|
39274
|
-
const backupTarget = path34.join(backupDir,
|
|
40212
|
+
const backupTarget = path34.join(backupDir, relative20, path34.basename(liveSpecDir));
|
|
39275
40213
|
fs20.mkdirSync(path34.dirname(backupTarget), { recursive: true });
|
|
39276
40214
|
fs20.renameSync(liveSpecDir, backupTarget);
|
|
39277
40215
|
backedUp = true;
|
|
39278
40216
|
} else if (liveSpecDir && fs20.existsSync(liveSpecDir)) {
|
|
39279
40217
|
fs20.rmSync(liveSpecDir, { recursive: true, force: true });
|
|
39280
40218
|
}
|
|
39281
|
-
const target = path34.join(root,
|
|
40219
|
+
const target = path34.join(root, relative20, ".wai");
|
|
39282
40220
|
fs20.mkdirSync(path34.dirname(target), { recursive: true });
|
|
39283
40221
|
if (fs20.existsSync(target)) fs20.rmSync(target, { recursive: true, force: true });
|
|
39284
40222
|
fs20.renameSync(stagedSpecDir, target);
|
|
@@ -39557,7 +40495,7 @@ function repairTypeSpellings(apply3) {
|
|
|
39557
40495
|
const key = `${kind}:${specId}`;
|
|
39558
40496
|
let repair = repairs.get(key);
|
|
39559
40497
|
if (!repair) {
|
|
39560
|
-
repair = { specId, kind, rewritten: [], proposals: [], authorNeeded: [] };
|
|
40498
|
+
repair = { specId, kind, rewritten: [], proposals: [], enumProposals: [], authorNeeded: [] };
|
|
39561
40499
|
repairs.set(key, repair);
|
|
39562
40500
|
}
|
|
39563
40501
|
return repair;
|
|
@@ -39572,7 +40510,9 @@ function repairTypeSpellings(apply3) {
|
|
|
39572
40510
|
if (problem.specId === void 0 || problem.kind === void 0 || !own(problem.specId)) continue;
|
|
39573
40511
|
const repair = repairOf(problem.specId, problem.kind);
|
|
39574
40512
|
const proposal = typeProblemIntProposal(problem);
|
|
40513
|
+
const enumProposal = proposal ? null : typeProblemEnumProposal(problem);
|
|
39575
40514
|
if (proposal) repair.proposals.push(proposal);
|
|
40515
|
+
else if (enumProposal) repair.enumProposals.push(enumProposal);
|
|
39576
40516
|
else repair.authorNeeded.push(problem);
|
|
39577
40517
|
}
|
|
39578
40518
|
const planned = [...repairs.values()];
|
|
@@ -39713,13 +40653,14 @@ Approved: ${approval.lockedAt} by ${by} \u2014 no spec has changed since.${child
|
|
|
39713
40653
|
${headline} \u2014 approved ${approval.lockedAt} by ${by}:
|
|
39714
40654
|
` + named2.map((p) => ` ${p}`).join("\n") + (rest > 0 ? `
|
|
39715
40655
|
\u2026 and ${rest} more` : "") + childNote + "\n",
|
|
39716
|
-
drifted: true
|
|
40656
|
+
drifted: true,
|
|
40657
|
+
moved: [...diff2.changed.map((p) => `~ ${p}`), ...diff2.added.map((p) => `+ ${p}`), ...diff2.removed.map((p) => `- ${p}`)]
|
|
39717
40658
|
};
|
|
39718
40659
|
} catch {
|
|
39719
40660
|
return quiet;
|
|
39720
40661
|
}
|
|
39721
40662
|
}
|
|
39722
|
-
var GATE_UPGRADE_NOTE = "the gate identity gained inputs in
|
|
40663
|
+
var GATE_UPGRADE_NOTE = "the gate identity gained inputs in this release (members' composition subjects, `composition`; code conformance moved beside the claim)";
|
|
39723
40664
|
function memberNotes(approvals) {
|
|
39724
40665
|
const own = approvals.find((a) => a.key === "");
|
|
39725
40666
|
const direct = approvals.filter((a) => a.parent === "" && a.as !== "part");
|
|
@@ -39801,33 +40742,34 @@ var GLOBAL_GUIDE_BODY = `## wairon \u2014 Spec-Driven Development (optional)
|
|
|
39801
40742
|
If \`.wai/specs/\` exists, the wairon SDD workflow is active; otherwise ignore it. wairon does not orchestrate sessions \u2014 it equips yours.
|
|
39802
40743
|
|
|
39803
40744
|
### In SDD Projects:
|
|
39804
|
-
- **Source of Truth**: All architecture lives in the spec tree under \`.wai/specs/\` (L0 System \u2192 L1 Subsystem \u2192 L2 Component \u2192 L3 Interface \u2192 L4 Implementation \u2192 L5 Narrative).
|
|
40745
|
+
- **Source of Truth**: All architecture lives in the spec tree under \`.wai/specs/\` (L0 System \u2192 L1 Subsystem \u2192 L2 Component \u2192 L3 Interface \u2192 L4 Implementation \u2192 L5 Narrative). Agent files under \`.claude/agents/\` are an opt-in generated view (\`rules.materializeAgentFiles\`) \u2014 never edit them; agents are served as live briefs.
|
|
39805
40746
|
- **Validation**: Conformance checks (stereotype rules, cycle checks, reference integrity) are run via the \`sdd_validate_tree\` MCP tool.
|
|
39806
40747
|
- **Operating Rules**:
|
|
39807
40748
|
1. **Skills**: Use \`sdd-architect\` to design (and \`sdd-implement\`, \`sdd-narrative\`, \`sdd-auditor\`). Refer to project's local guide file for detailed constraints.
|
|
39808
40749
|
2. **MCP Tools Only**: Author/validate specs *only* via \`sdd_*\` tools (e.g. \`sdd_initialize_system\`, \`sdd_validate_tree\`).
|
|
39809
40750
|
3. **No CLI Exec**: Do not run the \`wairon\` CLI (human tool). Use MCP tools \`sdd_validate_tree\` and \`sdd_get_status\` instead.
|
|
39810
40751
|
4. **Delegation**: Delegate implementation via the \`sdd-delegate\` skill \u2014 live agent briefs (\`sdd_get_agent_brief\` MCP tool / \`wairon-agent://\` resource) are composed per call and always current; no session restart. Generated agent files are an optional materialized view of the same topology. User-owned per-agent guidance may live in \`.wai/agents/<agent-id>.md\` (folded into every brief; scaffold via \`wairon agent customize <id>\`).
|
|
39811
|
-
5. **Design First**: Complete spec and pass \`sdd_validate_tree\` before writing code.
|
|
40752
|
+
5. **Design First, then approval**: Complete the spec and pass \`sdd_validate_tree\`, then ask the human to approve it with \`wairon lock\` before writing code. Approval is the lock record (\`.wai/lock.json\`), not a spec's \`status\` \u2014 the lock does not rewrite statuses; \`sdd_get_status\` reports the approval state.
|
|
39812
40753
|
6. **Consistency**: Code must match L3 interfaces and L5 narratives exactly. If the spec is wrong, stop and update the spec.
|
|
39813
|
-
7. **Members & References**: A project may
|
|
40754
|
+
7. **Members & References**: A project may declare **members** in its \`.wai/project.yaml\` \`members\` (create one with \`sdd_add_member\`). A **part** (the default) stores some of this project's subsystems in another folder or repository: local ids, this project's lock. A **project** member is an independent boundary with its own spec tree and lock, designed from its own root. Reference what another project exports as \`alias::name\` (the alias is a member or a declared external, the name a public name of its L0 export table); an id without \`::\` is local. A leading \`::\`, \`super::\`, member paths and an L1 subsystem carrying \`projectPath\` are deprecated: they still resolve for one release, are reported, and \`wairon doctor --fix\` rewrites them.`;
|
|
39814
40755
|
var LOCAL_GUIDE_BODY = `## Wairon \u2014 Spec-Driven Development (you are operating inside it)
|
|
39815
40756
|
|
|
39816
40757
|
This project uses **wairon**. System specs live under \`.wai/specs/\` (L0 System \u2192 L1 Subsystem \u2192 L2 Component \u2192 L3 Interface \u2192 L4 Implementation \u2192 Narrative); agent topology and code are derived from it.
|
|
39817
40758
|
|
|
39818
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.**
|
|
39819
40760
|
|
|
39820
|
-
**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.
|
|
39821
40762
|
|
|
39822
40763
|
### How you operate
|
|
39823
40764
|
- **To design/modify specs**: Use **\`sdd-architect\`** skill (in \`.claude/skills/\` or \`.gemini/skills/\`).
|
|
39824
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.
|
|
39825
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).
|
|
39826
|
-
- **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.
|
|
39827
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\`).
|
|
39828
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\`).
|
|
39829
40770
|
- **Do not run the \`wairon\` CLI**: Use \`sdd_validate_tree\` and \`sdd_get_status\` instead of CLI commands.
|
|
39830
|
-
- **Handoff to implementation**: Once design is complete and validates cleanly, tell the human: *"The specs are complete and validate. Please run \`wairon lock\` to
|
|
40771
|
+
- **Handoff to implementation**: Once design is complete and validates cleanly, tell the human: *"The specs are complete and validate. Please run \`wairon lock\` to approve them, and commit \`.wai/lock.json\`."* The lock records the approval; it does not rewrite spec files or their \`status\`. No session restart is needed after the lock \u2014 delegate implementation right away via the \`sdd-delegate\` skill.
|
|
40772
|
+
- **Approval, not status**: a design is ready to implement when it is APPROVED \u2014 \`sdd_get_status\` reports the approval state (approved, or which specs changed since), and \`wairon lock-check\` gives the same verdict in CI. A spec's \`status\` (draft/design/complete) is authoring readiness only; never wait for it to become \`complete\`.
|
|
39831
40773
|
- **To implement code**: Delegate via the \`sdd-delegate\` skill: fetch the component's live brief with the \`sdd_get_agent_brief\` MCP tool (or the \`wairon-agent://\` resource) and spawn a subagent from it. Briefs are composed per call from the current spec tree, so they are always current \u2014 never wait for a restart. Implementations must match L3 interfaces and L5 narratives exactly. Generated agent files under \`.claude/agents/\` are an optional materialized view of the same topology \u2014 the live briefs are canonical.
|
|
39832
40774
|
|
|
39833
40775
|
### Rules (enforced by \`sdd_validate_tree\`)
|
|
@@ -39867,7 +40809,7 @@ ${GUIDE_MARKER_END}
|
|
|
39867
40809
|
const stripped = stripGuideSection(existing);
|
|
39868
40810
|
const newContent = stripped.trimEnd() + section;
|
|
39869
40811
|
fs21.mkdirSync(path36.dirname(filePath), { recursive: true });
|
|
39870
|
-
|
|
40812
|
+
writeFile(filePath, newContent);
|
|
39871
40813
|
}
|
|
39872
40814
|
function stripGuideSection(content) {
|
|
39873
40815
|
const start = content.indexOf(GUIDE_MARKER_START);
|
|
@@ -39890,7 +40832,7 @@ To design or modify the system, invoke the **\`sdd-architect\`** skill
|
|
|
39890
40832
|
(in \`.claude/skills/\`). Author and validate specs with the \`sdd_*\` MCP tools;
|
|
39891
40833
|
the \`wairon\` CLI is the human developer's tool, not yours.
|
|
39892
40834
|
`;
|
|
39893
|
-
|
|
40835
|
+
writeFile(filePath, content);
|
|
39894
40836
|
} else if (targetType === "gemini" || targetType === "agy") {
|
|
39895
40837
|
const filePath = path36.join(projectRoot, "GEMINI.md");
|
|
39896
40838
|
const content = `# Wairon SDD Project
|
|
@@ -39899,7 +40841,7 @@ ${versionStamp()}
|
|
|
39899
40841
|
${LOCAL_GUIDE_BODY}
|
|
39900
40842
|
${GUIDE_MARKER_END}
|
|
39901
40843
|
`;
|
|
39902
|
-
|
|
40844
|
+
writeFile(filePath, content);
|
|
39903
40845
|
} else if (targetType === "cursor") {
|
|
39904
40846
|
const filePath = path36.join(projectRoot, ".cursorrules");
|
|
39905
40847
|
const content = `# Wairon SDD Project
|
|
@@ -39908,7 +40850,7 @@ This project uses the Wairon Spec-Driven Development (SDD) framework.
|
|
|
39908
40850
|
|
|
39909
40851
|
Refer to the rules in [.cursor/rules/](.cursor/rules/) for full instructions.
|
|
39910
40852
|
`;
|
|
39911
|
-
|
|
40853
|
+
writeFile(filePath, content);
|
|
39912
40854
|
} else if (targetType === "copilot") {
|
|
39913
40855
|
const filePath = path36.join(projectRoot, ".github", "copilot-instructions.md");
|
|
39914
40856
|
fs21.mkdirSync(path36.dirname(filePath), { recursive: true });
|
|
@@ -39918,14 +40860,14 @@ This project uses the Wairon Spec-Driven Development (SDD) framework.
|
|
|
39918
40860
|
|
|
39919
40861
|
Refer to the prompts in [.github/prompts/](.github/prompts/) for instructions.
|
|
39920
40862
|
`;
|
|
39921
|
-
|
|
40863
|
+
writeFile(filePath, content);
|
|
39922
40864
|
} else if (targetType === "codex") {
|
|
39923
40865
|
const filePath = path36.join(projectRoot, ".codexrules");
|
|
39924
40866
|
const content = `# Wairon SDD Project
|
|
39925
40867
|
|
|
39926
40868
|
Refer to [.codex/agents/](.codex/agents/) for full instructions.
|
|
39927
40869
|
`;
|
|
39928
|
-
|
|
40870
|
+
writeFile(filePath, content);
|
|
39929
40871
|
}
|
|
39930
40872
|
}
|
|
39931
40873
|
var GUIDE_TARGETS = ["claude", "gemini", "agy", "cursor", "copilot", "codex"];
|
|
@@ -40080,9 +41022,17 @@ function syncContextFiles() {
|
|
|
40080
41022
|
var path37 = __toESM(require("path"));
|
|
40081
41023
|
function approvalTag(entry) {
|
|
40082
41024
|
if (!entry) return "";
|
|
40083
|
-
const
|
|
40084
|
-
|
|
40085
|
-
}
|
|
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
|
+
};
|
|
40086
41036
|
var asWritten = (text3) => text3;
|
|
40087
41037
|
function fillDecor(decor) {
|
|
40088
41038
|
return {
|
|
@@ -40168,11 +41118,15 @@ function getStatusReport(options = {}, decor) {
|
|
|
40168
41118
|
}
|
|
40169
41119
|
componentScores.set(comp.id, score);
|
|
40170
41120
|
}
|
|
41121
|
+
const typeOwners = new Set(loadTypeSpecs().map((t) => t.subsystem).filter((id) => typeof id === "string"));
|
|
40171
41122
|
const getSubsystemScore = (subId) => {
|
|
40172
41123
|
const sub = subsystems.find((s) => s.id === subId);
|
|
40173
41124
|
if (!sub) return 0;
|
|
40174
41125
|
const subComps = components.filter((c) => c.subsystem === subId);
|
|
40175
|
-
if (subComps.length === 0)
|
|
41126
|
+
if (subComps.length === 0) {
|
|
41127
|
+
if (!typeOwners.has(subId)) return 0;
|
|
41128
|
+
return sub.status === "complete" ? 100 : 50;
|
|
41129
|
+
}
|
|
40176
41130
|
const totalScore = subComps.reduce((acc, c) => acc + (componentScores.get(c.id) ?? 0), 0);
|
|
40177
41131
|
let avg = Math.round(totalScore / subComps.length);
|
|
40178
41132
|
if (sub.status === "draft" || sub.status === "design") {
|
|
@@ -40295,8 +41249,7 @@ function getStatusReport(options = {}, decor) {
|
|
|
40295
41249
|
`;
|
|
40296
41250
|
}
|
|
40297
41251
|
const own = options.approvals?.find((a) => a.key === "");
|
|
40298
|
-
|
|
40299
|
-
if (own && (own.state !== "never" || hasMembers)) output += `${mark.layer("system", "Approval:")} this project is ${own.state}${own.upgraded ? " (locked under the pre-stage-5 gate identity \u2014 re-lock once)" : ""}
|
|
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)" : ""}
|
|
40300
41253
|
`;
|
|
40301
41254
|
return { text: output, failed: false };
|
|
40302
41255
|
}
|
|
@@ -41478,7 +42431,7 @@ var KINDS_AT = {
|
|
|
41478
42431
|
auth: ["implementation"],
|
|
41479
42432
|
subsystem: ["component", "type"],
|
|
41480
42433
|
group: ["type"],
|
|
41481
|
-
type: ["interface", "type"]
|
|
42434
|
+
type: ["interface", "type", "implementation"]
|
|
41482
42435
|
};
|
|
41483
42436
|
function kindOf(ref) {
|
|
41484
42437
|
const kinds = KINDS_AT[ref.position] ?? ["component", "interface", "implementation", "type", "subsystem"];
|
|
@@ -42558,7 +43511,7 @@ function emptyPlan(family, request) {
|
|
|
42558
43511
|
const whole = !(reach2 !== null && !reach2.parentReach);
|
|
42559
43512
|
return { request, familyRoot: familyNode(family, "").directory, whole, edits: [], refusals: [], changes: [], relock: [], notes: [] };
|
|
42560
43513
|
}
|
|
42561
|
-
var
|
|
43514
|
+
var posix5 = (p) => p.split(path43.sep).join("/");
|
|
42562
43515
|
var refuse = (plan6, code, project, detail) => {
|
|
42563
43516
|
plan6.refusals.push({ code, project, detail });
|
|
42564
43517
|
};
|
|
@@ -42636,7 +43589,7 @@ function attachEdits(plan6, family, bound2, dir, alias, bindingConfig) {
|
|
|
42636
43589
|
edit(plan6, key, "id", `id: ${id} (declared${approved !== void 0 ? ", the id its lock approved" : ""})`, { root: dir, call: "setId", args: [id] });
|
|
42637
43590
|
}
|
|
42638
43591
|
if (!attached) plan6.notes.push(`${dir} has no project.yaml of its own \u2014 \`wairon doctor --fix\` writes one declaring "${alias}" as its id`);
|
|
42639
|
-
const relPath =
|
|
43592
|
+
const relPath = posix5(path43.relative(familyNode(family, bound2).directory, dir));
|
|
42640
43593
|
const declaration2 = { path: relPath, ...plan6.request.description !== void 0 ? { description: plan6.request.description } : {} };
|
|
42641
43594
|
edit(plan6, bound2, "member", `members: ${alias} \u2192 ${relPath} (an existing project, kept as it is)`, { root: familyNode(family, bound2).directory, call: "declareMember", args: [alias, declaration2] });
|
|
42642
43595
|
for (const requirement of requiredPolicies(bindingConfig)) {
|
|
@@ -42683,8 +43636,8 @@ function planDetach(family, bound2, request) {
|
|
|
42683
43636
|
if (plan6.request.widen) planWidening(plan6, member, narrow);
|
|
42684
43637
|
if (plan6.refusals.length > 0) return plan6;
|
|
42685
43638
|
const boundDir = familyNode(family, bound2).directory;
|
|
42686
|
-
const held = memberDeclarationOf(configAt3(boundDir)?.members?.[alias] ??
|
|
42687
|
-
const declared = { ...held, path: memberLocationOf(held) ??
|
|
43639
|
+
const held = memberDeclarationOf(configAt3(boundDir)?.members?.[alias] ?? posix5(path43.relative(boundDir, member.directory)));
|
|
43640
|
+
const declared = { ...held, path: memberLocationOf(held) ?? posix5(path43.relative(boundDir, member.directory)) };
|
|
42688
43641
|
detachEdits(plan6, family, bound2, member, alias, declared, consumers, outward);
|
|
42689
43642
|
return plan6;
|
|
42690
43643
|
}
|
|
@@ -42724,7 +43677,7 @@ function planWidening(plan6, member, narrow) {
|
|
|
42724
43677
|
function detachEdits(plan6, family, bound2, member, alias, declared, consumers, outward) {
|
|
42725
43678
|
const boundDir = familyNode(family, bound2).directory;
|
|
42726
43679
|
const relocation = plan6.request.relocation;
|
|
42727
|
-
const sourceTo = (from, producer) => relocation ? { hosted: producer.id ?? producer.mountAlias ?? producer.namespace } : { path:
|
|
43680
|
+
const sourceTo = (from, producer) => relocation ? { hosted: producer.id ?? producer.mountAlias ?? producer.namespace } : { path: posix5(path43.relative(from, producer.directory)) };
|
|
42728
43681
|
const where = (source) => source.hosted !== void 0 ? `hosted record ${source.hosted}` : source.path;
|
|
42729
43682
|
edit(plan6, bound2, "member", `members: ${alias} removed (${declared.path} ${relocation ? `moves to ${relocation.to}` : "stays as it is"})`, { root: boundDir, call: "removeMember", args: [alias] });
|
|
42730
43683
|
const boundSource = relocation ? sourceTo(boundDir, member) : { path: declared.path };
|
|
@@ -42798,7 +43751,7 @@ function adoptEdits(plan6, family, bound2, alias, external, dir, at = dir) {
|
|
|
42798
43751
|
edit(plan6, bound2, "pin", `pin ${alias} removed (the family supplies it now)`, { root: boundDir, call: "unpin", args: [alias] });
|
|
42799
43752
|
edit(plan6, bound2, "external", `externals: ${alias} removed`, { root: boundDir, call: "removeExternal", args: [alias] });
|
|
42800
43753
|
const declaration2 = {
|
|
42801
|
-
path:
|
|
43754
|
+
path: posix5(path43.relative(boundDir, at)),
|
|
42802
43755
|
...external.description !== void 0 ? { description: external.description } : {},
|
|
42803
43756
|
...external.use && external.use.length > 0 ? { use: external.use } : {}
|
|
42804
43757
|
};
|
|
@@ -42909,7 +43862,7 @@ var KINDS_AT2 = {
|
|
|
42909
43862
|
auth: ["implementation"],
|
|
42910
43863
|
subsystem: ["component", "type"],
|
|
42911
43864
|
group: ["type"],
|
|
42912
|
-
type: ["interface", "type"]
|
|
43865
|
+
type: ["interface", "type", "implementation"]
|
|
42913
43866
|
};
|
|
42914
43867
|
function kindOf2(ref) {
|
|
42915
43868
|
const kinds = KINDS_AT2[ref.position] ?? ["component", "interface", "implementation", "type", "subsystem"];
|
|
@@ -43057,9 +44010,33 @@ function planAliasRename(family, bound2, request) {
|
|
|
43057
44010
|
if ([...family.owners].some(([key, owner]) => owner === bound2 && key.split("::").pop() === next)) {
|
|
43058
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)`);
|
|
43059
44012
|
}
|
|
44013
|
+
repinIntactConsumers(plan6, family, bound2);
|
|
43060
44014
|
return plan6;
|
|
43061
44015
|
}
|
|
43062
|
-
|
|
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"];
|
|
43063
44040
|
function write4(plan6, rehearsal) {
|
|
43064
44041
|
const planned = plan6.edits.filter((e) => e.write !== void 0);
|
|
43065
44042
|
const unknown = planned.find((x) => !PHASES.includes(x.write.call));
|
|
@@ -43074,6 +44051,7 @@ function write4(plan6, rehearsal) {
|
|
|
43074
44051
|
for (const e of planned.filter((x) => x.write.call === "renameId")) writeOne2(rehearsal, e.write);
|
|
43075
44052
|
}
|
|
43076
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);
|
|
43077
44055
|
}
|
|
43078
44056
|
function referencesBySpec(planned) {
|
|
43079
44057
|
const out = /* @__PURE__ */ new Map();
|
|
@@ -43097,6 +44075,8 @@ function writeOne2(rehearsal, w) {
|
|
|
43097
44075
|
return renameProjectId(args[0], args[1]);
|
|
43098
44076
|
case "renamePin":
|
|
43099
44077
|
return renamePin2(args[0], args[1], args[2]);
|
|
44078
|
+
case "pinExternals":
|
|
44079
|
+
return pinExternals2(args[0]);
|
|
43100
44080
|
default:
|
|
43101
44081
|
throw new Error(`identity_migration makes no "${w.call}" write`);
|
|
43102
44082
|
}
|
|
@@ -43110,7 +44090,7 @@ function emptyPlan3(family, request) {
|
|
|
43110
44090
|
const whole = !(reach2 !== null && !reach2.parentReach);
|
|
43111
44091
|
return { request, familyRoot: familyNode(family, "").directory, whole, edits: [], refusals: [], changes: [], relock: [], notes: [] };
|
|
43112
44092
|
}
|
|
43113
|
-
var
|
|
44093
|
+
var posix6 = (p) => p.split(path45.sep).join("/");
|
|
43114
44094
|
var label6 = (key) => key === "" ? "the top project" : `"${key}"`;
|
|
43115
44095
|
var refuse3 = (plan6, code, project, detail) => {
|
|
43116
44096
|
plan6.refusals.push({ code, project, detail });
|
|
@@ -43141,7 +44121,7 @@ var KINDS_AT3 = {
|
|
|
43141
44121
|
auth: ["implementation"],
|
|
43142
44122
|
subsystem: ["component", "type"],
|
|
43143
44123
|
group: ["type"],
|
|
43144
|
-
type: ["interface", "type"]
|
|
44124
|
+
type: ["interface", "type", "implementation"]
|
|
43145
44125
|
};
|
|
43146
44126
|
function kindOf3(ref) {
|
|
43147
44127
|
const kinds = KINDS_AT3[ref.position] ?? ["component", "interface", "implementation", "type", "subsystem"];
|
|
@@ -43320,7 +44300,7 @@ function respellConsumer(plan6, family, c, own) {
|
|
|
43320
44300
|
function repointConsumer(plan6, c, boundNode2) {
|
|
43321
44301
|
const declared = configAt5(c.node.directory)?.externals?.[c.alias];
|
|
43322
44302
|
const project = boundNode2.id !== void 0 && boundNode2.id !== c.alias ? boundNode2.id : null;
|
|
43323
|
-
const source = declared?.source ? { path:
|
|
44303
|
+
const source = declared?.source ? { path: posix6(path45.relative(c.node.directory, boundNode2.directory)) } : null;
|
|
43324
44304
|
edit3(plan6, c.node.namespace, "external", `externals: ${c.alias} names ${boundNode2.id ?? "the top project"}${source ? ` at ${source.path}` : ""}`, { root: c.node.directory, call: "repointExternal", args: [c.alias, project, source] });
|
|
43325
44305
|
plan6.notes.push(`${label6(c.node.namespace)}'s pin of "${c.alias}" is taken again against ${boundNode2.id ?? "the top project"}: the producer changed, so its lock reads drifted until it is re-locked.`);
|
|
43326
44306
|
return c;
|
|
@@ -43356,8 +44336,8 @@ function planExternalize(family, bound2, request) {
|
|
|
43356
44336
|
plan6,
|
|
43357
44337
|
bound2,
|
|
43358
44338
|
"move",
|
|
43359
|
-
`externalize ${id}: its specs into ${joined ? `the part "${joined.alias}"` : `a part at ${
|
|
43360
|
-
{ root: node.directory, call: "externalizeSubsystem", args: [id,
|
|
44339
|
+
`externalize ${id}: its specs into ${joined ? `the part "${joined.alias}"` : `a part at ${posix6(path45.relative(node.directory, dir))}, declared as "${id}"`} \u2014 a storage move; no reference, export or pin changes`,
|
|
44340
|
+
{ root: node.directory, call: "externalizeSubsystem", args: [id, posix6(path45.relative(node.directory, dir))] }
|
|
43361
44341
|
);
|
|
43362
44342
|
plan6.notes.push("Source code is not moved: move the subsystem's code yourself if it should live beside its specs.");
|
|
43363
44343
|
return plan6;
|
|
@@ -43366,8 +44346,9 @@ function planExternalize(family, bound2, request) {
|
|
|
43366
44346
|
crossingsBack(plan6, family, bound2, subKey);
|
|
43367
44347
|
checkSiblings(plan6, family, bound2, id, subKey, table);
|
|
43368
44348
|
if (plan6.refusals.length > 0) return plan6;
|
|
43369
|
-
edit3(plan6, bound2, "move", `externalize ${id} as a project: its specs into a member at ${
|
|
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"] });
|
|
43370
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]);
|
|
43371
44352
|
plan6.notes.push("Source code is not moved: move the subsystem's code into the member yourself.");
|
|
43372
44353
|
return plan6;
|
|
43373
44354
|
}
|
|
@@ -43496,6 +44477,7 @@ function planPromote(family, bound2, request) {
|
|
|
43496
44477
|
);
|
|
43497
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: [[]] });
|
|
43498
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]);
|
|
43499
44481
|
plan6.notes.push(`"${newId}" has no lock yet: lock it at its own root (\`wairon lock\`) once it validates.`);
|
|
43500
44482
|
return plan6;
|
|
43501
44483
|
}
|
|
@@ -43562,16 +44544,19 @@ function write5(plan6, rehearsal) {
|
|
|
43562
44544
|
return;
|
|
43563
44545
|
}
|
|
43564
44546
|
if (plan6.request.verb === "externalize") {
|
|
43565
|
-
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));
|
|
43566
44548
|
for (const e of planned.filter((x) => x.write.call === "pinExternals")) writeOne3(rehearsal, e.write);
|
|
43567
44549
|
return;
|
|
43568
44550
|
}
|
|
43569
44551
|
if (plan6.request.verb === "promote") {
|
|
43570
|
-
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));
|
|
43571
44553
|
for (const e of planned.filter((x) => x.write.call === "pinExternals")) writeOne3(rehearsal, e.write);
|
|
43572
44554
|
return;
|
|
43573
44555
|
}
|
|
43574
|
-
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
|
+
}
|
|
43575
44560
|
for (const e of planned.filter((x) => x.write.call === "unpin")) writeOne3(rehearsal, e.write);
|
|
43576
44561
|
}
|
|
43577
44562
|
function referencesBySpec2(planned) {
|
|
@@ -43584,9 +44569,46 @@ ${id}`) ?? [], ...edits]);
|
|
|
43584
44569
|
}
|
|
43585
44570
|
return out;
|
|
43586
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
|
+
}
|
|
43587
44609
|
function writeOne3(rehearsal, w) {
|
|
43588
44610
|
const args = w.args;
|
|
43589
|
-
runWithProjectRoot(rehearsalRoot(rehearsal, w.root), () => {
|
|
44611
|
+
return runWithProjectRoot(rehearsalRoot(rehearsal, w.root), () => {
|
|
43590
44612
|
switch (w.call) {
|
|
43591
44613
|
case "internalizeMember":
|
|
43592
44614
|
return internalizeMember(args[0], args[1]);
|
|
@@ -43621,7 +44643,7 @@ var path46 = __toESM(require("path"));
|
|
|
43621
44643
|
var fs26 = __toESM(require("fs"));
|
|
43622
44644
|
var os7 = __toESM(require("os"));
|
|
43623
44645
|
var nodePath2 = __toESM(require("path"));
|
|
43624
|
-
var
|
|
44646
|
+
var yaml4 = __toESM(require("js-yaml"));
|
|
43625
44647
|
var TRANSACTIONS2 = nodePath2.join(".wai", "transactions");
|
|
43626
44648
|
var transactionsDir = (owner) => nodePath2.join(owner, TRANSACTIONS2);
|
|
43627
44649
|
var transactionDir = (owner, id) => nodePath2.join(transactionsDir(owner), id);
|
|
@@ -43688,7 +44710,7 @@ function dropRehearsal(id) {
|
|
|
43688
44710
|
function saveJournal(journal) {
|
|
43689
44711
|
const ignore = nodePath2.join(transactionsDir(journal.owner), ".gitignore");
|
|
43690
44712
|
if (!fs26.existsSync(ignore)) writeDurably(ignore, "*\n");
|
|
43691
|
-
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 }));
|
|
43692
44714
|
}
|
|
43693
44715
|
function readJournal(owner, id) {
|
|
43694
44716
|
const file = nodePath2.join(transactionDir(owner, id), "journal.yaml");
|
|
@@ -43700,7 +44722,7 @@ function readJournal(owner, id) {
|
|
|
43700
44722
|
throw e;
|
|
43701
44723
|
}
|
|
43702
44724
|
try {
|
|
43703
|
-
return
|
|
44725
|
+
return yaml4.load(text3);
|
|
43704
44726
|
} catch (e) {
|
|
43705
44727
|
throw new Error(`${file} is not a readable transaction journal: ${e instanceof Error ? e.message : String(e)}`);
|
|
43706
44728
|
}
|
|
@@ -44495,7 +45517,7 @@ function errText(message) {
|
|
|
44495
45517
|
function errMessage(e) {
|
|
44496
45518
|
return errText(e instanceof Error ? e.message : String(e));
|
|
44497
45519
|
}
|
|
44498
|
-
var TYPE_REF_GRAMMAR = (what) => `${what}, in the neutral type grammar: a primitive (string, int, float, bool, bytes, date, datetime, duration, any; void only as a whole returns), a defined type id ("billing.Invoice", "alias::name"), list<T>, set<T>, map<K, V> (K is string, int or an enum), T? (T or no value), a union of NAMED types ("Invoice | Receipt"), and on a returns only, async T ("async void"). Every named type must resolve. TypeScript spellings are accepted and respelled \u2014 "Invoice[]" \u2192 list<Invoice>, "Invoice | null" \u2192 Invoice?, boolean \u2192 bool, Record<string, V> \u2192 map<string, V>, Promise<T> \u2192 async T \u2014 and the answer lists each respelling. Refused, naming the replacement: number ("int or float?"), inline object or function types (name a value-object or a signature type), string-literal unions (name an enum), and unions mixing in a primitive or a collection.`;
|
|
45520
|
+
var TYPE_REF_GRAMMAR = (what) => `${what}, in the neutral type grammar: a primitive (string, int, float, bool, bytes, date, datetime, duration, any; void only as a whole returns), a defined type id ("billing.Invoice", "alias::name"), list<T>, set<T>, map<K, V> (K is string, int or an enum), T? (T or no value), a union of NAMED types ("Invoice | Receipt"), and on a returns only, async T ("async void") and result<T, E> (completes with T or fails with E; "async result<void, E>"). Every named type must resolve. TypeScript and Rust spellings are accepted and respelled \u2014 "Invoice[]" \u2192 list<Invoice>, "Invoice | null" \u2192 Invoice?, boolean \u2192 bool, Record<string, V> \u2192 map<string, V>, Promise<T> \u2192 async T, Result<(), E> \u2192 result<void, E> \u2014 and the answer lists each respelling. Refused, naming the replacement: number ("int or float?"), inline object or function types (name a value-object or a signature type), string-literal unions (name an enum), and unions mixing in a primitive or a collection.`;
|
|
44499
45521
|
function structured(content, data) {
|
|
44500
45522
|
return { content: [{ type: "text", text: content }], structuredContent: data };
|
|
44501
45523
|
}
|
|
@@ -44519,10 +45541,19 @@ ${body}`);
|
|
|
44519
45541
|
}
|
|
44520
45542
|
function migrationView(report3, dryRun) {
|
|
44521
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;
|
|
44522
45547
|
return {
|
|
44523
45548
|
dryRun,
|
|
44524
45549
|
applied: report3.applied,
|
|
44525
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
|
+
} : {},
|
|
44526
45557
|
...report3.outcome ? { outcome: { committed: report3.outcome.committed, restored: report3.outcome.restored, unrestored: report3.outcome.unrestored, ...report3.outcome.failure ? { failure: report3.outcome.failure } : {} } } : {},
|
|
44527
45558
|
plan: {
|
|
44528
45559
|
verb: plan6.request.verb,
|
|
@@ -44595,6 +45626,28 @@ function renderChangeReport(report3) {
|
|
|
44595
45626
|
return lines.join("\n");
|
|
44596
45627
|
}
|
|
44597
45628
|
var SPEC_KINDS = ["system", "subsystem", "component", "interface", "implementation", "type"];
|
|
45629
|
+
function specKindsHolding(id) {
|
|
45630
|
+
const system = loadSystemSpec();
|
|
45631
|
+
const held = {
|
|
45632
|
+
system: !!system && (id === "system" || id === system.name),
|
|
45633
|
+
subsystem: !!loadSubsystemSpec(id),
|
|
45634
|
+
component: !!loadComponentSpec(id),
|
|
45635
|
+
interface: !!loadInterfaceSpec(id),
|
|
45636
|
+
implementation: !!loadImplementationSpec(id),
|
|
45637
|
+
type: !!loadTypeSpec(id)
|
|
45638
|
+
};
|
|
45639
|
+
return SPEC_KINDS.filter((k) => held[k]);
|
|
45640
|
+
}
|
|
45641
|
+
var PORTAL_TYPE_TRANSPORT = {
|
|
45642
|
+
HTTP_API: "HTTP",
|
|
45643
|
+
gRPC: "gRPC",
|
|
45644
|
+
GraphQL: "GraphQL",
|
|
45645
|
+
MessageBus: "MessageBus",
|
|
45646
|
+
NamedPipe: "NamedPipe",
|
|
45647
|
+
IPC: "IPC",
|
|
45648
|
+
CLI: "CLI",
|
|
45649
|
+
Custom: void 0
|
|
45650
|
+
};
|
|
44598
45651
|
var STATUS_VALUES = ["draft", "design", "complete"];
|
|
44599
45652
|
var testsToRevisitOutput = import_zod12.z.array(import_zod12.z.object({
|
|
44600
45653
|
method: import_zod12.z.string().describe("The contract method this write changed or deleted."),
|
|
@@ -44737,7 +45790,7 @@ var memberCreationOutput = {
|
|
|
44737
45790
|
profile: import_zod12.z.string().optional()
|
|
44738
45791
|
})).describe("Requirements no installed version satisfies: nothing was written for them, and the next family run reports each as POLICY_NOT_ADOPTED."),
|
|
44739
45792
|
projectType: import_zod12.z.string().optional().describe("The profile written as the new member's projectType, when exactly one adopted requirement names one."),
|
|
44740
|
-
as: import_zod12.z.string().describe("part | project: what was created (
|
|
45793
|
+
as: import_zod12.z.string().describe("part | project: what was created (a part by default)."),
|
|
44741
45794
|
storage: import_zod12.z.string().describe("contained | path | git: where its files live."),
|
|
44742
45795
|
commit: import_zod12.z.string().optional().describe("git: the commit the member was pinned at."),
|
|
44743
45796
|
...staleServerOutput
|
|
@@ -45319,7 +46372,7 @@ ${renderChangeReport(report3)}`,
|
|
|
45319
46372
|
server,
|
|
45320
46373
|
"sdd_add_member",
|
|
45321
46374
|
{
|
|
45322
|
-
description: "Create a member of the bound project at `source`, in the one members grammar: `services/scheduler` (contained), `../admin` (a sibling checkout), `git@host:acme/payments.git` (a git repository, pinned at its default branch head unless `#<commit>` is given). By default a PART
|
|
46375
|
+
description: "Create a member of the bound project at `source`, in the one members grammar: `services/scheduler` (contained), `../admin` (a sibling checkout), `git@host:acme/payments.git` (a git repository, pinned at its default branch head unless `#<commit>` is given). By default a PART: a piece of this project stored in another folder or repository, whose subsystems are this project's own (local ids, the ordinary subsystem rules, this project's lock); it gets only a specs folder, plus a configuration naming this project when it is not contained. With `as: project` an independent PROJECT instead: its project content is created \u2014 a project.yaml declaring the alias as its id, its L0, and the bound project's required packs (composition.requirePolicies) once \u2014 and it is reached only as `alias::name` through its L0 exports. What a member is follows from that content. A git member is not scaffolded; the repository's content decides what it is. Boundaries are earned: create a part unless the piece needs its own team, release, approval or public surface; sdd_promote_member makes one later. Idempotent \u2014 the same call again writes nothing; a different member under the alias is refused.",
|
|
45323
46376
|
inputSchema: {
|
|
45324
46377
|
alias: import_zod12.z.string().describe("The member's alias ([a-z0-9-_]+); a project's new id"),
|
|
45325
46378
|
source: import_zod12.z.string().describe("A contained path, a `../` sibling path, or a git URL (optionally `#<commit>`)"),
|
|
@@ -45389,7 +46442,7 @@ ${renderChangeReport(report3)}`,
|
|
|
45389
46442
|
server,
|
|
45390
46443
|
"sdd_externalize_subsystem",
|
|
45391
46444
|
{
|
|
45392
|
-
description: "Move an internal subsystem out to the path. By default
|
|
46445
|
+
description: "Move an internal subsystem out to the path. By default into a PART \u2014 a storage move: the subsystem's specs move to that directory (a new part, or the existing part there), and nothing else changes: no reference, export, external or lock entry, the same verdict. With `as: project`, into a member PROJECT instead, family-wide and all-or-nothing \u2014 the move followed by a promote: what crosses the new boundary exported and imported, references respelled `alias::name`, the new member's external for this project pinned, and every other family project's references checked to still resolve (a reference that would need a component made public refuses the plan). Source code is not moved. dryRun answers the plan and writes nothing; run it first and show the plan. Never locks: the answer names the projects to re-lock.",
|
|
45393
46446
|
inputSchema: {
|
|
45394
46447
|
subsystem: import_zod12.z.string().describe("The subsystem to move out"),
|
|
45395
46448
|
path: import_zod12.z.string().describe("The part's root, relative to the bound project (a new directory, or an existing part's)"),
|
|
@@ -45403,7 +46456,7 @@ ${renderChangeReport(report3)}`,
|
|
|
45403
46456
|
server,
|
|
45404
46457
|
"sdd_promote_member",
|
|
45405
46458
|
{
|
|
45406
|
-
description: "Promote a part of the bound project to an independent project in place
|
|
46459
|
+
description: "Promote a part of the bound project to an independent project in place \u2014 its files stay where they are; it gets an id, an L0 exporting exactly what the rest of the project uses of it, its references across the new boundary respelled `alias::name`, the parent declared as its external, and pins on both sides. Refused (each reason listed) for a part fetched from git, a trusted link that would cross the new boundary, a used component its subsystem does not publish, or an id already taken. All-or-nothing; dryRun answers the plan and writes nothing \u2014 run it first and show the plan. Never locks: the answer names the project to re-lock and the new one to lock.",
|
|
45407
46460
|
inputSchema: {
|
|
45408
46461
|
alias: import_zod12.z.string().describe("The part's alias"),
|
|
45409
46462
|
id: import_zod12.z.string().optional().describe("The new project's id (default: the alias)"),
|
|
@@ -45416,7 +46469,7 @@ ${renderChangeReport(report3)}`,
|
|
|
45416
46469
|
server,
|
|
45417
46470
|
"sdd_demote_member",
|
|
45418
46471
|
{
|
|
45419
|
-
description: "Demote a project member of the bound project to a part in place
|
|
46472
|
+
description: "Demote a project member of the bound project to a part in place \u2014 sdd_promote_member's inverse: its files stay where they are; its own metadata goes to the homes the destination names (as sdd_internalize_member sends it), its L0, lock and pins end, its references across the old boundary become local ids, and its configuration is reduced to naming its parent. Refused while another family project consumes it (a part has no exports \u2014 each consumer listed), for a member fetched from git, or for internalize's reasons. All-or-nothing; dryRun answers the plan and writes nothing \u2014 run it first and show the plan. Never locks.",
|
|
45420
46473
|
inputSchema: {
|
|
45421
46474
|
alias: import_zod12.z.string().describe("The project member's alias"),
|
|
45422
46475
|
destination: import_zod12.z.object({
|
|
@@ -45759,7 +46812,7 @@ ${renderChangeReport(report3)}`,
|
|
|
45759
46812
|
interface: import_zod12.z.string().describe('The L3 interface ID (e.g. "ibilling-gateway")'),
|
|
45760
46813
|
endpoints: import_zod12.z.array(import_zod12.z.object({
|
|
45761
46814
|
method: import_zod12.z.string().describe("The NAME of the interface method to bind (not the HTTP verb)"),
|
|
45762
|
-
transport: import_zod12.z.enum(["HTTP", "gRPC", "GraphQL", "MessageBus", "NamedPipe", "IPC", "CLI", "Custom"]).describe("Wire protocol; must
|
|
46815
|
+
transport: import_zod12.z.enum(["HTTP", "HTTP_API", "gRPC", "GraphQL", "MessageBus", "NamedPipe", "IPC", "CLI", "Custom"]).describe("Wire protocol; must be the one the Portal's portalType implies. Either vocabulary is accepted: the endpoint's (HTTP) or the portalType's (HTTP_API), stored as HTTP; any other transport is refused, naming the one the portalType implies"),
|
|
45763
46816
|
httpMethod: import_zod12.z.enum(["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"]).optional().describe("HTTP: verb"),
|
|
45764
46817
|
path: import_zod12.z.string().optional().describe('HTTP: route path, e.g. "/v1/checkout"'),
|
|
45765
46818
|
service: import_zod12.z.string().optional().describe("gRPC: service name"),
|
|
@@ -45782,8 +46835,10 @@ ${renderChangeReport(report3)}`,
|
|
|
45782
46835
|
try {
|
|
45783
46836
|
const intf = loadInterfaceSpec(interfaceId);
|
|
45784
46837
|
if (!intf) return errText(`Interface "${interfaceId}" does not exist.`);
|
|
45785
|
-
const
|
|
45786
|
-
|
|
46838
|
+
const owner = intf.component ? loadComponentSpec(intf.component) : null;
|
|
46839
|
+
const implied = owner?.componentType === "Portal" && owner.portalType ? PORTAL_TYPE_TRANSPORT[owner.portalType] : void 0;
|
|
46840
|
+
const buildRaw = (e, transport) => {
|
|
46841
|
+
switch (transport) {
|
|
45787
46842
|
case "HTTP":
|
|
45788
46843
|
return { transport: "HTTP", method: e.httpMethod, path: e.path };
|
|
45789
46844
|
case "gRPC":
|
|
@@ -45807,13 +46862,19 @@ ${renderChangeReport(report3)}`,
|
|
|
45807
46862
|
for (const e of endpoints) {
|
|
45808
46863
|
const m = intf.methods.find((x) => x.name === e.method);
|
|
45809
46864
|
if (!m) return errText(`Method "${e.method}" not found on interface "${interfaceId}". Define it via sdd_define_interface first.`);
|
|
45810
|
-
const
|
|
46865
|
+
const transport = e.transport === "HTTP_API" ? "HTTP" : e.transport;
|
|
46866
|
+
if (implied && transport !== implied) {
|
|
46867
|
+
return errText(
|
|
46868
|
+
`Method "${e.method}": transport "${e.transport}" does not match ${owner.id}'s portalType ${owner.portalType}, which implies transport "${implied}". Bind it as "${implied}", or change the Portal's portalType.`
|
|
46869
|
+
);
|
|
46870
|
+
}
|
|
46871
|
+
const parsed = EndpointSchema.safeParse(buildRaw(e, transport));
|
|
45811
46872
|
if (!parsed.success) {
|
|
45812
46873
|
const detail = parsed.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ");
|
|
45813
|
-
return errText(`Invalid ${
|
|
46874
|
+
return errText(`Invalid ${transport} endpoint for method "${e.method}": ${detail}`);
|
|
45814
46875
|
}
|
|
45815
46876
|
methods.push({ name: e.method, endpoint: parsed.data });
|
|
45816
|
-
bound2.push(`${e.method}\u2192${
|
|
46877
|
+
bound2.push(`${e.method}\u2192${transport}`);
|
|
45817
46878
|
}
|
|
45818
46879
|
const report3 = updateSpecGated("interface", interfaceId, { methods });
|
|
45819
46880
|
return structured(
|
|
@@ -46074,14 +47135,24 @@ ${renderChangeReport(report3)}`,
|
|
|
46074
47135
|
{
|
|
46075
47136
|
description: `Get/read the parsed JSON contents of a specific spec from the spec tree. Returns structural contents without file system path searching. Pass "methods" to read only the named methods of a contract, an implementation or a type \u2014 a 45-method spec fetched whole to look at one of them is the read side of the same waste a restatement is on the write side; the answer then carries a "partialResult" marker naming what was left out, and must never be re-authored from. For a variant-tagged COMPONENT the result also carries a derived, read-only "variantGuidance" (the variant's base, its implementation guidance, and the same-variant sibling components to implement alike) \u2014 it is resolved from the variant registry, not part of the spec, so never write it back. A contract's methods come back in their STORED form \u2014 a method that takes its signature from a source carries its signatureFrom, not the params the loader resolves into it \u2014 so the answer can be re-authored from as it is; the resolved params, returns and text of each such method come back as a derived, read-only "resolvedSignatures" marker. The structured content carries the same answer with the derived markers KEPT SEPARATE from the stored spec ({kind, id, spec, partialResult?, variantGuidance?, resolvedSignatures?}), so nothing derived can be mistaken for something stored; the text block folds them in as it always has.`,
|
|
46076
47137
|
inputSchema: {
|
|
46077
|
-
kind: import_zod12.z.enum(["system", "subsystem", "component", "interface", "implementation", "type"]).describe("The kind of specification"),
|
|
47138
|
+
kind: import_zod12.z.enum(["system", "subsystem", "component", "interface", "implementation", "type"]).optional().describe("The kind of specification. Omit it to infer the kind from the id: the read is refused, naming the candidate kinds, when the id names specs at more than one level (an interface and an implementation sharing an id, say), or at none"),
|
|
46078
47139
|
id: import_zod12.z.string().describe('The identifier of the spec to fetch (the L0 system spec is a singleton \u2014 pass the system name or "system")'),
|
|
46079
47140
|
methods: import_zod12.z.array(import_zod12.z.string().min(1)).optional().describe("Return only these methods by name; every other field of the spec comes back unchanged. Only an interface, an implementation or a type declares methods \u2014 asking for one elsewhere is refused, as is a name the spec does not declare (the answer names the ones it does). Omit it for the whole spec.")
|
|
46080
47141
|
},
|
|
46081
47142
|
outputSchema: getSpecOutput
|
|
46082
47143
|
},
|
|
46083
|
-
({ kind, id, methods }) => {
|
|
47144
|
+
({ kind: askedKind, id, methods }) => {
|
|
46084
47145
|
try {
|
|
47146
|
+
let kind;
|
|
47147
|
+
if (askedKind) {
|
|
47148
|
+
kind = askedKind;
|
|
47149
|
+
} else {
|
|
47150
|
+
const holders = specKindsHolding(id);
|
|
47151
|
+
if (holders.length !== 1) {
|
|
47152
|
+
return errText(holders.length === 0 ? `No spec of any kind has the ID "${id}".` : `The ID "${id}" names specs of more than one kind (${holders.join(", ")}). Pass "kind" to choose one.`);
|
|
47153
|
+
}
|
|
47154
|
+
kind = holders[0];
|
|
47155
|
+
}
|
|
46085
47156
|
let result = null;
|
|
46086
47157
|
let partial2 = null;
|
|
46087
47158
|
switch (kind) {
|
|
@@ -46275,9 +47346,9 @@ ${testsBlock}` : ""}`, deletion);
|
|
|
46275
47346
|
server,
|
|
46276
47347
|
"sdd_get_agent_brief",
|
|
46277
47348
|
{
|
|
46278
|
-
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).",
|
|
46279
47350
|
inputSchema: {
|
|
46280
|
-
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")
|
|
46281
47352
|
}
|
|
46282
47353
|
},
|
|
46283
47354
|
({ agentId }) => {
|
|
@@ -46491,6 +47562,7 @@ function loadProjectConfig3() {
|
|
|
46491
47562
|
aiDir,
|
|
46492
47563
|
aiDirAt,
|
|
46493
47564
|
aiPathsAt,
|
|
47565
|
+
aliasTargetOf,
|
|
46494
47566
|
ambiguityMessage,
|
|
46495
47567
|
approvalKeyIn,
|
|
46496
47568
|
approvalRecord,
|
|
@@ -46634,6 +47706,7 @@ function loadProjectConfig3() {
|
|
|
46634
47706
|
keyIn,
|
|
46635
47707
|
landReference,
|
|
46636
47708
|
languageOfSourcePath,
|
|
47709
|
+
lineEndingFor,
|
|
46637
47710
|
listDirectChainedSubprojects,
|
|
46638
47711
|
listExternals,
|
|
46639
47712
|
listFiles,
|
|
@@ -46675,6 +47748,7 @@ function loadProjectConfig3() {
|
|
|
46675
47748
|
memberDeclarationOf,
|
|
46676
47749
|
memberDigest,
|
|
46677
47750
|
memberLocationOf,
|
|
47751
|
+
memberTypesOf,
|
|
46678
47752
|
methodGenericParameters,
|
|
46679
47753
|
methodSourceFile,
|
|
46680
47754
|
methodTypeRefs,
|
|
@@ -46747,6 +47821,7 @@ function loadProjectConfig3() {
|
|
|
46747
47821
|
resolveExternals,
|
|
46748
47822
|
resolveImport,
|
|
46749
47823
|
resolveInstalledPack,
|
|
47824
|
+
resolveProjectBinding,
|
|
46750
47825
|
resolveProjectExports,
|
|
46751
47826
|
resolveSignatures,
|
|
46752
47827
|
resolveSubprojectForNamespace,
|
|
@@ -46792,6 +47867,7 @@ function loadProjectConfig3() {
|
|
|
46792
47867
|
typeIsMany,
|
|
46793
47868
|
typeMatchesRef,
|
|
46794
47869
|
typeNamedRefs,
|
|
47870
|
+
typeProblemEnumProposal,
|
|
46795
47871
|
typeProblemIntProposal,
|
|
46796
47872
|
typeSourceFiles,
|
|
46797
47873
|
typeSpellingFacts,
|
|
@@ -46805,6 +47881,7 @@ function loadProjectConfig3() {
|
|
|
46805
47881
|
validateProject,
|
|
46806
47882
|
validateProjectConfig,
|
|
46807
47883
|
validateRegistry,
|
|
47884
|
+
withLineEndings,
|
|
46808
47885
|
withPack,
|
|
46809
47886
|
writeFile,
|
|
46810
47887
|
writeFileIfChanged,
|