@wairon/cli 5.1.1-dev.75 → 5.1.1-dev.77
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/dist/cli/index.js +1632 -697
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +3590 -193
- package/dist/index.js.map +1 -1
- package/dist/templates/skills/sdd-implement.md +10 -5
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1672,15 +1672,15 @@ var require_node = __commonJS({
|
|
|
1672
1672
|
var slzh = function(d, b) {
|
|
1673
1673
|
return b + 30 + b2(d, b + 26) + b2(d, b + 28);
|
|
1674
1674
|
};
|
|
1675
|
-
var zh = function(d, b,
|
|
1675
|
+
var zh = function(d, b, z12) {
|
|
1676
1676
|
var fnl = b2(d, b + 28), efl = b2(d, b + 30), fn = strFromU8(d.subarray(b + 46, b + 46 + fnl), !(b2(d, b + 8) & 2048)), es = b + 46 + fnl;
|
|
1677
|
-
var _a2 = z64hs(d, es, efl,
|
|
1677
|
+
var _a2 = z64hs(d, es, efl, z12, b4(d, b + 20), b4(d, b + 24), b4(d, b + 42)), sc = _a2[0], su = _a2[1], off = _a2[2];
|
|
1678
1678
|
return [b2(d, b + 10), sc, su, fn, es + efl + b2(d, b + 32), off];
|
|
1679
1679
|
};
|
|
1680
|
-
var z64hs = function(d, b, l,
|
|
1680
|
+
var z64hs = function(d, b, l, z12, sc, su, off) {
|
|
1681
1681
|
var nsc = sc == 4294967295, nsu = su == 4294967295, noff = off == 4294967295, e = b + l;
|
|
1682
1682
|
var nf = nsc + nsu + noff;
|
|
1683
|
-
if (
|
|
1683
|
+
if (z12 && nf) {
|
|
1684
1684
|
for (; b + 4 < e; b += 4 + b2(d, b + 2)) {
|
|
1685
1685
|
if (b2(d, b) == 1) {
|
|
1686
1686
|
return [
|
|
@@ -1691,7 +1691,7 @@ var require_node = __commonJS({
|
|
|
1691
1691
|
];
|
|
1692
1692
|
}
|
|
1693
1693
|
}
|
|
1694
|
-
if (
|
|
1694
|
+
if (z12 < 2)
|
|
1695
1695
|
err(13);
|
|
1696
1696
|
}
|
|
1697
1697
|
return [sc, su, off, 0];
|
|
@@ -2300,18 +2300,18 @@ var require_node = __commonJS({
|
|
|
2300
2300
|
if (lft) {
|
|
2301
2301
|
var c = lft;
|
|
2302
2302
|
var o = b4(data, e + 16);
|
|
2303
|
-
var
|
|
2304
|
-
if (
|
|
2303
|
+
var z12 = b4(data, e - 20) == 117853008;
|
|
2304
|
+
if (z12) {
|
|
2305
2305
|
var ze = b4(data, e - 12);
|
|
2306
|
-
|
|
2307
|
-
if (
|
|
2306
|
+
z12 = b4(data, ze) == 101075792;
|
|
2307
|
+
if (z12) {
|
|
2308
2308
|
c = lft = b4(data, ze + 32);
|
|
2309
2309
|
o = b4(data, ze + 48);
|
|
2310
2310
|
}
|
|
2311
2311
|
}
|
|
2312
2312
|
var fltr = opts && opts.filter;
|
|
2313
2313
|
var _loop_3 = function(i3) {
|
|
2314
|
-
var _a2 = zh(data, o,
|
|
2314
|
+
var _a2 = zh(data, o, z12), c_1 = _a2[0], sc = _a2[1], su = _a2[2], fn = _a2[3], no = _a2[4], off = _a2[5], b = slzh(data, off);
|
|
2315
2315
|
o = no;
|
|
2316
2316
|
var cbl = function(e2, d) {
|
|
2317
2317
|
if (e2) {
|
|
@@ -2366,18 +2366,18 @@ var require_node = __commonJS({
|
|
|
2366
2366
|
if (!c)
|
|
2367
2367
|
return {};
|
|
2368
2368
|
var o = b4(data, e + 16);
|
|
2369
|
-
var
|
|
2370
|
-
if (
|
|
2369
|
+
var z12 = b4(data, e - 20) == 117853008;
|
|
2370
|
+
if (z12) {
|
|
2371
2371
|
var ze = b4(data, e - 12);
|
|
2372
|
-
|
|
2373
|
-
if (
|
|
2372
|
+
z12 = b4(data, ze) == 101075792;
|
|
2373
|
+
if (z12) {
|
|
2374
2374
|
c = b4(data, ze + 32);
|
|
2375
2375
|
o = b4(data, ze + 48);
|
|
2376
2376
|
}
|
|
2377
2377
|
}
|
|
2378
2378
|
var fltr = opts && opts.filter;
|
|
2379
2379
|
for (var i2 = 0; i2 < c; ++i2) {
|
|
2380
|
-
var _a2 = zh(data, o,
|
|
2380
|
+
var _a2 = zh(data, o, z12), c_2 = _a2[0], sc = _a2[1], su = _a2[2], fn = _a2[3], no = _a2[4], off = _a2[5], b = slzh(data, off);
|
|
2381
2381
|
o = no;
|
|
2382
2382
|
if (!fltr || fltr({
|
|
2383
2383
|
name: fn,
|
|
@@ -2497,7 +2497,7 @@ var require_dist = __commonJS({
|
|
|
2497
2497
|
scaffoldPack: () => scaffoldPack2
|
|
2498
2498
|
});
|
|
2499
2499
|
module2.exports = __toCommonJS2(src_exports2);
|
|
2500
|
-
var
|
|
2500
|
+
var path210 = __toESM2(require("path"));
|
|
2501
2501
|
var import_js_yaml3 = require("js-yaml");
|
|
2502
2502
|
var import_js_yaml = require("js-yaml");
|
|
2503
2503
|
var import_crypto = require("crypto");
|
|
@@ -2510,8 +2510,8 @@ var require_dist = __commonJS({
|
|
|
2510
2510
|
maxCompressionRatio: 100,
|
|
2511
2511
|
maxDepth: 16
|
|
2512
2512
|
};
|
|
2513
|
-
function parseManifest(
|
|
2514
|
-
const raw = (0, import_js_yaml.load)(
|
|
2513
|
+
function parseManifest(text2) {
|
|
2514
|
+
const raw = (0, import_js_yaml.load)(text2);
|
|
2515
2515
|
const manifest = validateEnvelope(raw);
|
|
2516
2516
|
if (Math.trunc(manifest.formatVersion) > SUPPORTED_FORMAT_VERSION) {
|
|
2517
2517
|
throw new Error("unsupported pack-archive formatVersion (newer than this wairon understands)");
|
|
@@ -2689,8 +2689,8 @@ var require_dist = __commonJS({
|
|
|
2689
2689
|
var DERIVED_ROOTS = ["generated", "docs"];
|
|
2690
2690
|
var EXECUTABLE_EXT_RE = /\.(c|m)?js$/i;
|
|
2691
2691
|
var WAI_SEGMENT = ".wai";
|
|
2692
|
-
function parseTreeManifest(
|
|
2693
|
-
const raw = (0, import_js_yaml2.load)(
|
|
2692
|
+
function parseTreeManifest(text2) {
|
|
2693
|
+
const raw = (0, import_js_yaml2.load)(text2);
|
|
2694
2694
|
const manifest = validateEnvelope2(raw);
|
|
2695
2695
|
if (Math.trunc(manifest.formatVersion) > SUPPORTED_FORMAT_VERSION2) {
|
|
2696
2696
|
throw new Error("unsupported tree-archive formatVersion (newer than this wairon understands)");
|
|
@@ -3060,8 +3060,8 @@ var require_dist = __commonJS({
|
|
|
3060
3060
|
function slug(name) {
|
|
3061
3061
|
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
3062
3062
|
}
|
|
3063
|
-
var
|
|
3064
|
-
var
|
|
3063
|
+
var fs20 = __toESM2(require("fs"));
|
|
3064
|
+
var path31 = __toESM2(require("path"));
|
|
3065
3065
|
var import_fflate = require_node();
|
|
3066
3066
|
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".hg", ".svn"]);
|
|
3067
3067
|
function listEntries(archive) {
|
|
@@ -3099,9 +3099,9 @@ var require_dist = __commonJS({
|
|
|
3099
3099
|
}
|
|
3100
3100
|
function writeTree(destDir, files) {
|
|
3101
3101
|
for (const file of files) {
|
|
3102
|
-
const absolute =
|
|
3103
|
-
|
|
3104
|
-
|
|
3102
|
+
const absolute = path31.join(destDir, file.path);
|
|
3103
|
+
fs20.mkdirSync(path31.dirname(absolute), { recursive: true });
|
|
3104
|
+
fs20.writeFileSync(absolute, file.contents);
|
|
3105
3105
|
}
|
|
3106
3106
|
}
|
|
3107
3107
|
function classifyKind(name, symlinks) {
|
|
@@ -3110,14 +3110,14 @@ var require_dist = __commonJS({
|
|
|
3110
3110
|
return "file";
|
|
3111
3111
|
}
|
|
3112
3112
|
function walkPackDir(root, current2, out) {
|
|
3113
|
-
for (const entry of
|
|
3113
|
+
for (const entry of fs20.readdirSync(current2, { withFileTypes: true })) {
|
|
3114
3114
|
if (entry.isDirectory()) {
|
|
3115
3115
|
if (SKIP_DIRS.has(entry.name)) continue;
|
|
3116
|
-
walkPackDir(root,
|
|
3116
|
+
walkPackDir(root, path31.join(current2, entry.name), out);
|
|
3117
3117
|
} else if (entry.isFile()) {
|
|
3118
|
-
const absolute =
|
|
3119
|
-
const relative22 =
|
|
3120
|
-
out.push({ path: relative22, contents:
|
|
3118
|
+
const absolute = path31.join(current2, entry.name);
|
|
3119
|
+
const relative22 = path31.relative(root, absolute).split(path31.sep).join("/");
|
|
3120
|
+
out.push({ path: relative22, contents: fs20.readFileSync(absolute) });
|
|
3121
3121
|
}
|
|
3122
3122
|
}
|
|
3123
3123
|
}
|
|
@@ -3167,7 +3167,7 @@ var require_dist = __commonJS({
|
|
|
3167
3167
|
function scaffoldPack(request) {
|
|
3168
3168
|
const files = render(request);
|
|
3169
3169
|
writeTree(request.targetDir, files);
|
|
3170
|
-
return files.map((file) =>
|
|
3170
|
+
return files.map((file) => path210.join(request.targetDir, file.path));
|
|
3171
3171
|
}
|
|
3172
3172
|
function buildPack(sourceDir) {
|
|
3173
3173
|
const files = readPackDir(sourceDir);
|
|
@@ -3224,7 +3224,7 @@ var require_dist = __commonJS({
|
|
|
3224
3224
|
}
|
|
3225
3225
|
const manifest = parseManifest(decode(envelope.contents));
|
|
3226
3226
|
verifyIntegrity2(manifest, files);
|
|
3227
|
-
const directory =
|
|
3227
|
+
const directory = path210.resolve(destDir);
|
|
3228
3228
|
writeTree(directory, files);
|
|
3229
3229
|
const result = {
|
|
3230
3230
|
directory,
|
|
@@ -3302,7 +3302,7 @@ var require_dist = __commonJS({
|
|
|
3302
3302
|
}
|
|
3303
3303
|
const manifest = parseTreeManifest(decode(envelope.contents));
|
|
3304
3304
|
verifyTreeIntegrity(manifest, files);
|
|
3305
|
-
const directory =
|
|
3305
|
+
const directory = path210.resolve(destDir);
|
|
3306
3306
|
writeTree(directory, files);
|
|
3307
3307
|
const result = {
|
|
3308
3308
|
destDir: directory,
|
|
@@ -3317,7 +3317,7 @@ var require_dist = __commonJS({
|
|
|
3317
3317
|
return normalized === "." ? ".wai/" : `${normalized}/.wai/`;
|
|
3318
3318
|
}
|
|
3319
3319
|
function normalizeRootPath(relativePath) {
|
|
3320
|
-
const posix2 = relativePath.split(
|
|
3320
|
+
const posix2 = relativePath.split(path210.sep).join("/").replace(/^\.\//, "").replace(/\/+$/, "");
|
|
3321
3321
|
return posix2 === "" || posix2 === "." ? "." : posix2;
|
|
3322
3322
|
}
|
|
3323
3323
|
function slugify(projectName) {
|
|
@@ -3616,7 +3616,7 @@ __export(src_exports, {
|
|
|
3616
3616
|
loadImplementationSpecs: () => loadImplementationSpecs,
|
|
3617
3617
|
loadInterfaceSpec: () => loadInterfaceSpec,
|
|
3618
3618
|
loadInterfaceSpecs: () => loadInterfaceSpecs,
|
|
3619
|
-
loadProjectConfig: () =>
|
|
3619
|
+
loadProjectConfig: () => loadProjectConfig3,
|
|
3620
3620
|
loadProjectExtensions: () => loadProjectExtensions,
|
|
3621
3621
|
loadProjectVariants: () => loadProjectVariants,
|
|
3622
3622
|
loadRegistry: () => loadRegistry,
|
|
@@ -3687,6 +3687,7 @@ __export(src_exports, {
|
|
|
3687
3687
|
saveSpec: () => saveSpec,
|
|
3688
3688
|
saveTopologyConfig: () => saveTopologyConfig,
|
|
3689
3689
|
scanAllSpecs: () => scanAllSpecs,
|
|
3690
|
+
schemaFingerprint: () => schemaFingerprint,
|
|
3690
3691
|
serializeYaml: () => serializeYaml,
|
|
3691
3692
|
setExecutionTier: () => setExecutionTier,
|
|
3692
3693
|
setLogLevel: () => setLogLevel,
|
|
@@ -3918,6 +3919,40 @@ var BundleNotFoundError = class extends WaironError {
|
|
|
3918
3919
|
}
|
|
3919
3920
|
};
|
|
3920
3921
|
|
|
3922
|
+
// src/models/identity-rename.ts
|
|
3923
|
+
var EDGE = " -> ";
|
|
3924
|
+
var QUALIFIED_UNIT = /^(\d+:)?([^.\s]+)\.([^.\s]+)$/;
|
|
3925
|
+
function movedComponent(rename, id) {
|
|
3926
|
+
return rename.components?.find((c) => c.from === id)?.to ?? id;
|
|
3927
|
+
}
|
|
3928
|
+
function rekeyUnit(rename, unit) {
|
|
3929
|
+
if (unit.includes(EDGE)) return unit.split(EDGE).map((side) => movedComponent(rename, side)).join(EDGE);
|
|
3930
|
+
const qualified = QUALIFIED_UNIT.exec(unit);
|
|
3931
|
+
if (!qualified) return unit;
|
|
3932
|
+
const [, step = "", component, method] = qualified;
|
|
3933
|
+
const moved = rename.methods?.find((m) => m.component === component && m.method === method);
|
|
3934
|
+
if (moved) return `${step}${moved.toComponent}.${moved.toMethod}`;
|
|
3935
|
+
return `${step}${movedComponent(rename, component)}.${method}`;
|
|
3936
|
+
}
|
|
3937
|
+
function rekeyAnchor(rename, anchor) {
|
|
3938
|
+
const { spec, at, covers } = anchor;
|
|
3939
|
+
const movedSpec = rename.specs?.find((s) => s.from === spec && (!s.sites || at !== void 0 && s.sites.includes(at)));
|
|
3940
|
+
let site = at;
|
|
3941
|
+
if (at !== void 0) {
|
|
3942
|
+
if (at.includes(EDGE)) {
|
|
3943
|
+
site = rekeyUnit(rename, at);
|
|
3944
|
+
} else {
|
|
3945
|
+
const moved = rename.methods?.find((m) => m.method === at && m.specs?.includes(spec));
|
|
3946
|
+
if (moved) site = moved.toMethod;
|
|
3947
|
+
}
|
|
3948
|
+
}
|
|
3949
|
+
return {
|
|
3950
|
+
spec: movedSpec?.to ?? spec,
|
|
3951
|
+
...site !== void 0 ? { at: site } : {},
|
|
3952
|
+
...covers !== void 0 ? { covers: covers.map((unit) => rekeyUnit(rename, unit)) } : {}
|
|
3953
|
+
};
|
|
3954
|
+
}
|
|
3955
|
+
|
|
3921
3956
|
// src/models/project.ts
|
|
3922
3957
|
var path2 = __toESM(require("path"));
|
|
3923
3958
|
var import_zod3 = require("zod");
|
|
@@ -3975,6 +4010,21 @@ var ExecutionBudgetSchema = import_zod.z.object({
|
|
|
3975
4010
|
allowNestedDelegation: import_zod.z.boolean(),
|
|
3976
4011
|
mcp: McpAccessSchema
|
|
3977
4012
|
});
|
|
4013
|
+
function summarize(budget, profile) {
|
|
4014
|
+
const lines = [
|
|
4015
|
+
`- **Work shape**: ${profile.breadth} breadth, ${profile.reasoningDepth} reasoning${profile.writes ? "" : ", read-only"}${profile.delegates ? ", delegating" : ""}`,
|
|
4016
|
+
`- **Why**: ${profile.rationale}`
|
|
4017
|
+
];
|
|
4018
|
+
if (budget.modelTier) lines.push(`- **Capability tier**: ${budget.modelTier}`);
|
|
4019
|
+
if (budget.effort) lines.push(`- **Effort**: ${budget.effort}`);
|
|
4020
|
+
if (budget.maxTurns !== void 0) {
|
|
4021
|
+
lines.push(`- **Turn ceiling**: ${budget.maxTurns} (a circuit breaker \u2014 hitting it should read as a scoping error, not a limit to work up to)`);
|
|
4022
|
+
}
|
|
4023
|
+
lines.push(`- **Tool grant**: ${budget.toolClass}`);
|
|
4024
|
+
lines.push(`- **May delegate further**: ${budget.allowNestedDelegation ? "yes" : "no"}`);
|
|
4025
|
+
lines.push(`- **MCP access**: ${budget.mcp}`);
|
|
4026
|
+
return lines;
|
|
4027
|
+
}
|
|
3978
4028
|
var BudgetTierSchema = import_zod.z.enum(["off", "free", "default", "trade", "aggressive"]);
|
|
3979
4029
|
var ExecutionConfigSchema = import_zod.z.object({
|
|
3980
4030
|
/**
|
|
@@ -4538,6 +4588,12 @@ function projectConfigFsAdapterAt(rootDir) {
|
|
|
4538
4588
|
},
|
|
4539
4589
|
documentExists() {
|
|
4540
4590
|
return fs2.existsSync(file());
|
|
4591
|
+
},
|
|
4592
|
+
readText() {
|
|
4593
|
+
return readFileOrNull(file());
|
|
4594
|
+
},
|
|
4595
|
+
writeText(text2) {
|
|
4596
|
+
writeFile(file(), text2);
|
|
4541
4597
|
}
|
|
4542
4598
|
};
|
|
4543
4599
|
}
|
|
@@ -4563,8 +4619,158 @@ function storeOver(adapter, root) {
|
|
|
4563
4619
|
},
|
|
4564
4620
|
readField(field) {
|
|
4565
4621
|
return valueAt(currentDocument(), field);
|
|
4622
|
+
},
|
|
4623
|
+
rewriteScalars(edits, dryRun = false) {
|
|
4624
|
+
if (edits.length === 0) return;
|
|
4625
|
+
const text2 = adapter.readText();
|
|
4626
|
+
if (text2 === null) throw new ProjectNotInitializedError();
|
|
4627
|
+
const next = rewriteScalarsInText(text2, edits, root);
|
|
4628
|
+
if (!dryRun) adapter.writeText(next);
|
|
4629
|
+
}
|
|
4630
|
+
};
|
|
4631
|
+
}
|
|
4632
|
+
var KEY_RE = /^('(?:[^']|'')*'|"(?:[^"\\]|\\.)*"|[^\s'"#{}[\],&*!|>%@`][^:#]*?)\s*:(?=\s|$)/;
|
|
4633
|
+
function tokenize(lines) {
|
|
4634
|
+
const tokens = [];
|
|
4635
|
+
let blockAbove = null;
|
|
4636
|
+
lines.forEach((raw, line) => {
|
|
4637
|
+
const content = raw.replace(/\r?\n$/, "");
|
|
4638
|
+
const indent = content.length - content.trimStart().length;
|
|
4639
|
+
const trimmed = content.trim();
|
|
4640
|
+
if (blockAbove !== null) {
|
|
4641
|
+
if (trimmed === "" || indent > blockAbove) return;
|
|
4642
|
+
blockAbove = null;
|
|
4643
|
+
}
|
|
4644
|
+
if (trimmed === "" || trimmed.startsWith("#")) return;
|
|
4645
|
+
let col = indent;
|
|
4646
|
+
let rest = content.slice(indent);
|
|
4647
|
+
while (rest === "-" || rest.startsWith("- ")) {
|
|
4648
|
+
tokens.push({ kind: "item", col, line });
|
|
4649
|
+
const after = rest.slice(1);
|
|
4650
|
+
const pad = after.length - after.trimStart().length;
|
|
4651
|
+
col += 1 + pad;
|
|
4652
|
+
rest = after.trimStart();
|
|
4653
|
+
}
|
|
4654
|
+
if (rest === "") return;
|
|
4655
|
+
const key = KEY_RE.exec(rest);
|
|
4656
|
+
if (key) {
|
|
4657
|
+
const name = key[1].startsWith("'") ? key[1].slice(1, -1).replace(/''/g, "'") : key[1].startsWith('"') ? JSON.parse(key[1]) : key[1].trim();
|
|
4658
|
+
const afterKey = rest.slice(key[0].length);
|
|
4659
|
+
const value = afterKey.trimStart();
|
|
4660
|
+
const token = { kind: "key", col, line, name };
|
|
4661
|
+
if (value !== "" && !value.startsWith("#")) token.valueStart = col + key[0].length + (afterKey.length - value.length);
|
|
4662
|
+
tokens.push(token);
|
|
4663
|
+
if (/^[|>][-+0-9]*\s*(#.*)?$/.test(value)) blockAbove = col;
|
|
4664
|
+
return;
|
|
4665
|
+
}
|
|
4666
|
+
tokens.push({ kind: "scalar", col, line, valueStart: col });
|
|
4667
|
+
if (/^[|>][-+0-9]*\s*(#.*)?$/.test(rest)) blockAbove = col - 2;
|
|
4668
|
+
});
|
|
4669
|
+
return tokens;
|
|
4670
|
+
}
|
|
4671
|
+
function childrenOf(tokens, index) {
|
|
4672
|
+
const parent = tokens[index];
|
|
4673
|
+
const out = [];
|
|
4674
|
+
for (let i = index + 1; i < tokens.length; i += 1) {
|
|
4675
|
+
const t = tokens[i];
|
|
4676
|
+
const nested = t.col > parent.col || parent.kind === "key" && t.kind === "item" && t.col === parent.col;
|
|
4677
|
+
if (!nested) break;
|
|
4678
|
+
out.push(t);
|
|
4679
|
+
}
|
|
4680
|
+
return out;
|
|
4681
|
+
}
|
|
4682
|
+
function locateScalar(tokens, path31) {
|
|
4683
|
+
let region = tokens;
|
|
4684
|
+
for (let k = 0; k < path31.length; k += 1) {
|
|
4685
|
+
const segment = path31[k];
|
|
4686
|
+
if (region.length === 0) return `nothing is nested at ${path31.slice(0, k).join(".")}`;
|
|
4687
|
+
const base = Math.min(...region.map((t) => t.col));
|
|
4688
|
+
const last = k === path31.length - 1;
|
|
4689
|
+
let at;
|
|
4690
|
+
if (typeof segment === "string") {
|
|
4691
|
+
at = region.findIndex((t) => t.kind === "key" && t.col === base && t.name === segment);
|
|
4692
|
+
if (at < 0) return `no block key "${segment}" at ${path31.slice(0, k).join(".") || "the document root"}`;
|
|
4693
|
+
if (last) return region[at].valueStart !== void 0 ? region[at] : `"${segment}" holds no inline scalar`;
|
|
4694
|
+
} else {
|
|
4695
|
+
const items = region.map((t, i) => ({ t, i })).filter(({ t }) => t.kind === "item" && t.col === base);
|
|
4696
|
+
if (segment >= items.length) return `no block sequence item ${segment} at ${path31.slice(0, k).join(".")}`;
|
|
4697
|
+
at = items[segment].i;
|
|
4698
|
+
if (last) {
|
|
4699
|
+
const value = region[at + 1];
|
|
4700
|
+
return value && value.kind === "scalar" && value.line === region[at].line ? value : `item ${segment} holds no inline scalar`;
|
|
4701
|
+
}
|
|
4702
|
+
}
|
|
4703
|
+
const regionStart = tokens.indexOf(region[at]);
|
|
4704
|
+
region = childrenOf(tokens, regionStart);
|
|
4705
|
+
}
|
|
4706
|
+
return "an empty path addresses no scalar";
|
|
4707
|
+
}
|
|
4708
|
+
function readScalar(text2) {
|
|
4709
|
+
if (text2.startsWith("'")) {
|
|
4710
|
+
let i = 1;
|
|
4711
|
+
let value = "";
|
|
4712
|
+
while (i < text2.length) {
|
|
4713
|
+
if (text2[i] === "'") {
|
|
4714
|
+
if (text2[i + 1] === "'") {
|
|
4715
|
+
value += "'";
|
|
4716
|
+
i += 2;
|
|
4717
|
+
continue;
|
|
4718
|
+
}
|
|
4719
|
+
return { value, style: "single", end: i + 1 };
|
|
4720
|
+
}
|
|
4721
|
+
value += text2[i];
|
|
4722
|
+
i += 1;
|
|
4723
|
+
}
|
|
4724
|
+
return null;
|
|
4725
|
+
}
|
|
4726
|
+
if (text2.startsWith('"')) {
|
|
4727
|
+
const closing = /^"(?:[^"\\]|\\.)*"/.exec(text2);
|
|
4728
|
+
if (!closing) return null;
|
|
4729
|
+
try {
|
|
4730
|
+
return { value: JSON.parse(closing[0]), style: "double", end: closing[0].length };
|
|
4731
|
+
} catch {
|
|
4732
|
+
return null;
|
|
4566
4733
|
}
|
|
4734
|
+
}
|
|
4735
|
+
const comment = text2.search(/\s#/);
|
|
4736
|
+
const body = (comment < 0 ? text2 : text2.slice(0, comment)).trimEnd();
|
|
4737
|
+
return { value: body, style: "plain", end: body.length };
|
|
4738
|
+
}
|
|
4739
|
+
function writeScalar(value, style) {
|
|
4740
|
+
if (style === "double") return JSON.stringify(value);
|
|
4741
|
+
if (style === "plain" && /^[A-Za-z0-9_][A-Za-z0-9_./-]*$/.test(value)) return value;
|
|
4742
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
4743
|
+
}
|
|
4744
|
+
function setAt(document, path31, value) {
|
|
4745
|
+
let node = document;
|
|
4746
|
+
for (const segment of path31.slice(0, -1)) node = node[segment];
|
|
4747
|
+
node[path31[path31.length - 1]] = value;
|
|
4748
|
+
}
|
|
4749
|
+
function rewriteScalarsInText(text2, edits, root) {
|
|
4750
|
+
const refuse = (why) => {
|
|
4751
|
+
throw new WaironError(`Cannot rewrite .wai/project.yaml at ${root} without disturbing its formatting: ${why}. Nothing was written.`);
|
|
4567
4752
|
};
|
|
4753
|
+
const lines = text2.split(/(?<=\n)/);
|
|
4754
|
+
const tokens = tokenize(lines);
|
|
4755
|
+
const expected = parseYaml(text2);
|
|
4756
|
+
for (const edit of edits) {
|
|
4757
|
+
const where = edit.path.join(".");
|
|
4758
|
+
const token = locateScalar(tokens, edit.path);
|
|
4759
|
+
if (typeof token === "string") refuse(`${where}: ${token}`);
|
|
4760
|
+
const { line, valueStart } = token;
|
|
4761
|
+
const content = lines[line].replace(/\r?\n$/, "");
|
|
4762
|
+
const ending = lines[line].slice(content.length);
|
|
4763
|
+
const scalar = readScalar(content.slice(valueStart));
|
|
4764
|
+
if (!scalar) refuse(`${where}: the scalar there is not written on one line`);
|
|
4765
|
+
if (scalar.value !== edit.from) refuse(`${where} holds "${scalar.value}", not "${edit.from}"`);
|
|
4766
|
+
lines[line] = content.slice(0, valueStart) + writeScalar(edit.to, scalar.style) + content.slice(valueStart + scalar.end) + ending;
|
|
4767
|
+
setAt(expected, edit.path, edit.to);
|
|
4768
|
+
}
|
|
4769
|
+
const next = lines.join("");
|
|
4770
|
+
if (JSON.stringify(parseYaml(next)) !== JSON.stringify(expected)) {
|
|
4771
|
+
refuse("the edited text does not read back as exactly the intended change");
|
|
4772
|
+
}
|
|
4773
|
+
return next;
|
|
4568
4774
|
}
|
|
4569
4775
|
function parseConfig(document, root) {
|
|
4570
4776
|
try {
|
|
@@ -4707,6 +4913,35 @@ function registryOver(store, root) {
|
|
|
4707
4913
|
...config,
|
|
4708
4914
|
extensions: { ...config.extensions, packs: [...packsOf(config), ...selections], useGlobalPacks: false }
|
|
4709
4915
|
});
|
|
4916
|
+
},
|
|
4917
|
+
rekeyCarried(rename, dryRun = false) {
|
|
4918
|
+
const groups = store.readField("rules.conformance.carried");
|
|
4919
|
+
if (!Array.isArray(groups)) return [];
|
|
4920
|
+
const edits = [];
|
|
4921
|
+
const rekeys = [];
|
|
4922
|
+
groups.forEach((group, g) => {
|
|
4923
|
+
const findings = group?.findings;
|
|
4924
|
+
if (!Array.isArray(findings)) return;
|
|
4925
|
+
findings.forEach((finding, f) => {
|
|
4926
|
+
const entry = finding;
|
|
4927
|
+
if (!entry || typeof entry.spec !== "string") return;
|
|
4928
|
+
const at = typeof entry.at === "string" ? entry.at : void 0;
|
|
4929
|
+
const covers = Array.isArray(entry.covers) ? entry.covers.filter((u) => typeof u === "string") : void 0;
|
|
4930
|
+
const next = rekeyAnchor(rename, { spec: entry.spec, at, covers });
|
|
4931
|
+
const base = ["rules", "conformance", "carried", g, "findings", f];
|
|
4932
|
+
const named = { code: String(entry.code), spec: entry.spec, at: at ?? "" };
|
|
4933
|
+
const change = (path31, field, from, to) => {
|
|
4934
|
+
if (from === to) return;
|
|
4935
|
+
edits.push({ path: [...base, ...path31], from, to });
|
|
4936
|
+
rekeys.push({ ...named, field, from, to });
|
|
4937
|
+
};
|
|
4938
|
+
change(["spec"], "spec", entry.spec, next.spec);
|
|
4939
|
+
if (at !== void 0) change(["at"], "at", at, next.at);
|
|
4940
|
+
(covers ?? []).forEach((unit, u) => change(["covers", u], "covers", unit, next.covers[u]));
|
|
4941
|
+
});
|
|
4942
|
+
});
|
|
4943
|
+
store.rewriteScalars(edits, dryRun);
|
|
4944
|
+
return rekeys;
|
|
4710
4945
|
}
|
|
4711
4946
|
};
|
|
4712
4947
|
}
|
|
@@ -4791,6 +5026,9 @@ function projectConfigRepositoryOver(adapter, rootDir) {
|
|
|
4791
5026
|
},
|
|
4792
5027
|
pinGlobalPacksAsSelections(selections) {
|
|
4793
5028
|
registry.pinGlobalPacksAsSelections(selections);
|
|
5029
|
+
},
|
|
5030
|
+
rekeyCarried(rename, dryRun) {
|
|
5031
|
+
return registry.rekeyCarried(rename, dryRun);
|
|
4794
5032
|
}
|
|
4795
5033
|
};
|
|
4796
5034
|
}
|
|
@@ -4840,6 +5078,9 @@ var projectConfigRepository = {
|
|
|
4840
5078
|
},
|
|
4841
5079
|
pinGlobalPacksAsSelections(selections) {
|
|
4842
5080
|
bound().pinGlobalPacksAsSelections(selections);
|
|
5081
|
+
},
|
|
5082
|
+
rekeyCarried(rename, dryRun) {
|
|
5083
|
+
return bound().rekeyCarried(rename, dryRun);
|
|
4843
5084
|
}
|
|
4844
5085
|
};
|
|
4845
5086
|
|
|
@@ -5609,9 +5850,9 @@ var MethodSignatureSchema = import_zod8.z.object({
|
|
|
5609
5850
|
ext: ExtDataSchema.optional()
|
|
5610
5851
|
});
|
|
5611
5852
|
var INTENT_FLOOR_MIN_CHARS = 40;
|
|
5612
|
-
function passesIntentFloor(
|
|
5613
|
-
if (!
|
|
5614
|
-
const t =
|
|
5853
|
+
function passesIntentFloor(text2, methodName) {
|
|
5854
|
+
if (!text2) return false;
|
|
5855
|
+
const t = text2.trim();
|
|
5615
5856
|
if (t.length < INTENT_FLOOR_MIN_CHARS) return false;
|
|
5616
5857
|
const norm = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
5617
5858
|
if (norm(t) === norm(methodName)) return false;
|
|
@@ -6357,10 +6598,10 @@ function stepGraph(method) {
|
|
|
6357
6598
|
const s = byNum.get(n);
|
|
6358
6599
|
if (s.type !== "parallel" || s.endStep === void 0 || !s.branches?.length) continue;
|
|
6359
6600
|
const entries = s.branches.map((b) => b.step).sort((a, b) => a - b);
|
|
6360
|
-
const
|
|
6601
|
+
const join19 = fallNext(s.endStep);
|
|
6361
6602
|
for (let i = 0; i < entries.length; i++) {
|
|
6362
6603
|
const armEnd = i + 1 < entries.length ? prevOf(entries[i + 1]) : s.endStep;
|
|
6363
|
-
if (armEnd !== void 0 && armEnd >= entries[i]) armEndJoin.set(armEnd,
|
|
6604
|
+
if (armEnd !== void 0 && armEnd >= entries[i]) armEndJoin.set(armEnd, join19);
|
|
6364
6605
|
}
|
|
6365
6606
|
}
|
|
6366
6607
|
const successorsOf = (n) => {
|
|
@@ -7060,12 +7301,12 @@ function buildExcalidrawScene(model, L) {
|
|
|
7060
7301
|
locked: false
|
|
7061
7302
|
};
|
|
7062
7303
|
}
|
|
7063
|
-
function boundLabel(rect,
|
|
7304
|
+
function boundLabel(rect, text2, fontSize, verticalAlign) {
|
|
7064
7305
|
const id = rect.id + "-label";
|
|
7065
7306
|
const label = base(id, "text", { x: rect.x + 8, y: rect.y + 6, w: rect.width - 16, h: 20 });
|
|
7066
7307
|
label.roundness = null;
|
|
7067
|
-
label.text =
|
|
7068
|
-
label.originalText =
|
|
7308
|
+
label.text = text2;
|
|
7309
|
+
label.originalText = text2;
|
|
7069
7310
|
label.fontSize = fontSize;
|
|
7070
7311
|
label.fontFamily = 1;
|
|
7071
7312
|
label.textAlign = "center";
|
|
@@ -11573,6 +11814,161 @@ var SURFACES_DIRNAME = "surfaces";
|
|
|
11573
11814
|
function surfacesDir(rootDir) {
|
|
11574
11815
|
return path9.join(rootDir, ".wai", SURFACES_DIRNAME);
|
|
11575
11816
|
}
|
|
11817
|
+
function audienceRank(audience) {
|
|
11818
|
+
const idx = SURFACE_AUDIENCES.indexOf(audience ?? "instance");
|
|
11819
|
+
return idx === -1 ? SURFACE_AUDIENCES.indexOf("instance") : idx;
|
|
11820
|
+
}
|
|
11821
|
+
function stateIdString() {
|
|
11822
|
+
const s = computeStateId();
|
|
11823
|
+
return `${s.algorithm}:${s.digest}`;
|
|
11824
|
+
}
|
|
11825
|
+
function computeTypeClosure(entries, types) {
|
|
11826
|
+
const included = /* @__PURE__ */ new Map();
|
|
11827
|
+
const queue = [];
|
|
11828
|
+
const enqueueRef = (ref) => {
|
|
11829
|
+
if (BUILTIN_TYPES.has(ref.toLowerCase())) return;
|
|
11830
|
+
for (const spec of types) {
|
|
11831
|
+
const qualifiedId = spec.subsystem && !spec.id.startsWith(`${spec.subsystem}::`) ? `${spec.subsystem}::${spec.id}` : spec.id;
|
|
11832
|
+
if (matchTypeRef(ref, qualifiedId) && !included.has(spec.id)) {
|
|
11833
|
+
included.set(spec.id, spec);
|
|
11834
|
+
queue.push(spec.id);
|
|
11835
|
+
}
|
|
11836
|
+
}
|
|
11837
|
+
};
|
|
11838
|
+
for (const entry of entries) {
|
|
11839
|
+
for (const m of entry.methods) {
|
|
11840
|
+
for (const ref of methodTypeRefs(m)) enqueueRef(ref);
|
|
11841
|
+
}
|
|
11842
|
+
}
|
|
11843
|
+
while (queue.length) {
|
|
11844
|
+
const spec = included.get(queue.shift());
|
|
11845
|
+
for (const field of spec.fields) {
|
|
11846
|
+
for (const ref of extractTypeIdentifiers(field.type)) enqueueRef(ref);
|
|
11847
|
+
}
|
|
11848
|
+
}
|
|
11849
|
+
return [...included.values()].map((t) => ({
|
|
11850
|
+
id: t.id,
|
|
11851
|
+
name: t.name,
|
|
11852
|
+
kind: t.kind,
|
|
11853
|
+
fields: t.fields.map((f) => ({
|
|
11854
|
+
name: f.name,
|
|
11855
|
+
type: f.type,
|
|
11856
|
+
...f.description ? { description: f.description } : {},
|
|
11857
|
+
...f.optional ? { optional: true } : {}
|
|
11858
|
+
}))
|
|
11859
|
+
}));
|
|
11860
|
+
}
|
|
11861
|
+
function projectOwnSurface(maxAudience) {
|
|
11862
|
+
const system = loadSystemSpec();
|
|
11863
|
+
if (!system) {
|
|
11864
|
+
throw new Error("Cannot project a surface: the L0 system spec is missing.");
|
|
11865
|
+
}
|
|
11866
|
+
const subsystems = loadSubsystemSpecs();
|
|
11867
|
+
const components = loadComponentSpecs();
|
|
11868
|
+
const interfaces = loadInterfaceSpecs();
|
|
11869
|
+
const types = loadTypeSpecs();
|
|
11870
|
+
const floor = audienceRank(maxAudience);
|
|
11871
|
+
const rawEntries = system.publicInterfaces ?? [];
|
|
11872
|
+
const entries = [];
|
|
11873
|
+
for (const raw of rawEntries) {
|
|
11874
|
+
const audience = raw.audience ?? "instance";
|
|
11875
|
+
if (audienceRank(audience) < floor) continue;
|
|
11876
|
+
if (!raw.component) continue;
|
|
11877
|
+
const comp = components.find((c) => c.id === raw.component);
|
|
11878
|
+
if (!comp) continue;
|
|
11879
|
+
const compInterfaces = interfaces.filter((i) => i.component === comp.id && (!raw.interface || i.id === raw.interface));
|
|
11880
|
+
const methods = compInterfaces.flatMap((i) => i.methods);
|
|
11881
|
+
const subsystemType = subsystems.find((s) => s.id === comp.subsystem)?.publicInterfaces.find((pi) => pi.component === comp.id)?.type;
|
|
11882
|
+
entries.push({
|
|
11883
|
+
id: raw.id ?? raw.interface ?? comp.id,
|
|
11884
|
+
name: raw.name ?? comp.name,
|
|
11885
|
+
audience,
|
|
11886
|
+
type: raw.type ?? subsystemType ?? "Custom",
|
|
11887
|
+
component: comp.id,
|
|
11888
|
+
methods,
|
|
11889
|
+
...comp.dispatch && comp.dispatch.length ? { dispatch: comp.dispatch } : {},
|
|
11890
|
+
// Project the backing Portal's auth + basePath so the codec can emit
|
|
11891
|
+
// OpenAPI security + per-portal servers self-contained from the snapshot.
|
|
11892
|
+
...comp.auth && comp.auth.scheme !== "none" ? { auth: comp.auth } : {},
|
|
11893
|
+
...comp.basePath ? { basePath: comp.basePath } : {},
|
|
11894
|
+
details: raw.details ?? "",
|
|
11895
|
+
...raw.version ? { version: raw.version } : {},
|
|
11896
|
+
...raw.stability ? { stability: raw.stability } : {}
|
|
11897
|
+
});
|
|
11898
|
+
}
|
|
11899
|
+
return SurfaceSnapshotSchema.parse({
|
|
11900
|
+
projectName: system.name,
|
|
11901
|
+
origin: "generated",
|
|
11902
|
+
stateId: stateIdString(),
|
|
11903
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11904
|
+
interfaces: entries,
|
|
11905
|
+
types: computeTypeClosure(entries, types)
|
|
11906
|
+
});
|
|
11907
|
+
}
|
|
11908
|
+
function projectChildSurface() {
|
|
11909
|
+
return projectOwnSurface("project");
|
|
11910
|
+
}
|
|
11911
|
+
function localName(id) {
|
|
11912
|
+
return id.split("::").pop();
|
|
11913
|
+
}
|
|
11914
|
+
var CROSS_BOUNDARY_TARGETS = /* @__PURE__ */ new Set(["Portal", "Observer"]);
|
|
11915
|
+
function projectSubsystemSurface(subsystemId) {
|
|
11916
|
+
const system = loadSystemSpec();
|
|
11917
|
+
if (!system) {
|
|
11918
|
+
throw new Error("Cannot project a subsystem surface: the L0 system spec is missing.");
|
|
11919
|
+
}
|
|
11920
|
+
const subsystems = loadSubsystemSpecs();
|
|
11921
|
+
const target = subsystems.find((s) => s.id === subsystemId);
|
|
11922
|
+
if (!target) {
|
|
11923
|
+
throw new Error(`Cannot project a subsystem surface: subsystem "${subsystemId}" does not exist.`);
|
|
11924
|
+
}
|
|
11925
|
+
const components = loadComponentSpecs();
|
|
11926
|
+
const interfaces = loadInterfaceSpecs();
|
|
11927
|
+
const types = loadTypeSpecs();
|
|
11928
|
+
const entries = [];
|
|
11929
|
+
const unprojectable = [];
|
|
11930
|
+
for (const pub of target.publicInterfaces ?? []) {
|
|
11931
|
+
if (!pub.component) continue;
|
|
11932
|
+
const comp = components.find((c) => c.id === pub.component || c.id === `${subsystemId}::${pub.component}`);
|
|
11933
|
+
if (!comp) continue;
|
|
11934
|
+
if (!CROSS_BOUNDARY_TARGETS.has(comp.componentType)) {
|
|
11935
|
+
unprojectable.push({ component: pub.component, componentType: comp.componentType });
|
|
11936
|
+
continue;
|
|
11937
|
+
}
|
|
11938
|
+
const compInterfaces = interfaces.filter((i) => i.component === comp.id && (!pub.interface || i.id === pub.interface || i.id === `${subsystemId}::${pub.interface}`));
|
|
11939
|
+
const methods = compInterfaces.flatMap((i) => i.methods);
|
|
11940
|
+
entries.push({
|
|
11941
|
+
id: localName(pub.interface ?? comp.id),
|
|
11942
|
+
name: comp.name,
|
|
11943
|
+
// Family ceiling: a sibling surface is consumable by the system family only.
|
|
11944
|
+
audience: "project",
|
|
11945
|
+
type: pub.type ?? "Custom",
|
|
11946
|
+
// The snapshot carries the LOCAL portal name — consumers resolve cross-tree
|
|
11947
|
+
// refs by their final segment.
|
|
11948
|
+
component: localName(comp.id),
|
|
11949
|
+
methods,
|
|
11950
|
+
...comp.dispatch && comp.dispatch.length ? { dispatch: comp.dispatch } : {},
|
|
11951
|
+
// Project the backing component's auth + basePath so the codec can emit
|
|
11952
|
+
// OpenAPI security + per-portal servers self-contained from the snapshot.
|
|
11953
|
+
...comp.auth && comp.auth.scheme !== "none" ? { auth: comp.auth } : {},
|
|
11954
|
+
...comp.basePath ? { basePath: comp.basePath } : {},
|
|
11955
|
+
details: pub.details ?? ""
|
|
11956
|
+
});
|
|
11957
|
+
}
|
|
11958
|
+
for (const skipped of unprojectable) {
|
|
11959
|
+
console.error(
|
|
11960
|
+
`[surfaces] skipped "${subsystemId}::${skipped.component}": a published ${skipped.componentType} can never serve a cross-boundary caller, so it stays out of every chained child's sibling surface \u2014 publish this surface through a Portal, or an Observer (for events).`
|
|
11961
|
+
);
|
|
11962
|
+
}
|
|
11963
|
+
return SurfaceSnapshotSchema.parse({
|
|
11964
|
+
projectName: `${system.name}::${subsystemId}`,
|
|
11965
|
+
origin: "generated",
|
|
11966
|
+
stateId: stateIdString(),
|
|
11967
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11968
|
+
interfaces: entries,
|
|
11969
|
+
types: computeTypeClosure(entries, types)
|
|
11970
|
+
});
|
|
11971
|
+
}
|
|
11576
11972
|
function listSnapshots(rootDir = getProjectRoot()) {
|
|
11577
11973
|
const dir = surfacesDir(rootDir);
|
|
11578
11974
|
if (!fs7.existsSync(dir)) return [];
|
|
@@ -11597,6 +11993,43 @@ function listMountSnapshots(mounts) {
|
|
|
11597
11993
|
}
|
|
11598
11994
|
return out;
|
|
11599
11995
|
}
|
|
11996
|
+
function projectFamilySurfaces(parent) {
|
|
11997
|
+
return runWithProjectRoot(parent.parentRoot, () => {
|
|
11998
|
+
const siblings = loadSubsystemSpecs().filter((s) => !s.id.includes("::") && s.id !== parent.subsystemId);
|
|
11999
|
+
return [projectChildSurface(), ...siblings.map((s) => projectSubsystemSurface(s.id))];
|
|
12000
|
+
});
|
|
12001
|
+
}
|
|
12002
|
+
function projectedFamilyContent(parent) {
|
|
12003
|
+
try {
|
|
12004
|
+
return new Map(projectFamilySurfaces(parent).map((s) => [s.projectName, surfaceContentKey(s)]));
|
|
12005
|
+
} catch {
|
|
12006
|
+
return null;
|
|
12007
|
+
}
|
|
12008
|
+
}
|
|
12009
|
+
function listExternalInterfaces() {
|
|
12010
|
+
const snapshots = listSnapshots();
|
|
12011
|
+
const chainingParent = resolveChainingParent();
|
|
12012
|
+
const projected = chainingParent ? projectedFamilyContent(chainingParent) : null;
|
|
12013
|
+
return snapshots.map((snapshot) => {
|
|
12014
|
+
const generated = snapshot.origin === "generated";
|
|
12015
|
+
const sourceKind = !generated ? "foreign" : snapshot.projectName.includes("::") ? "sibling" : "parent";
|
|
12016
|
+
const freshness = generated && projected ? projected.get(snapshot.projectName) === surfaceContentKey(snapshot) ? "fresh" : "stale" : "unverifiable";
|
|
12017
|
+
return {
|
|
12018
|
+
projectName: snapshot.projectName,
|
|
12019
|
+
origin: snapshot.origin,
|
|
12020
|
+
sourceKind,
|
|
12021
|
+
generatedAt: snapshot.generatedAt,
|
|
12022
|
+
...snapshot.stateId ? { stateId: snapshot.stateId } : {},
|
|
12023
|
+
...snapshot.version ? { version: snapshot.version } : {},
|
|
12024
|
+
freshness,
|
|
12025
|
+
interfaceIds: snapshot.interfaces.map((e) => e.id)
|
|
12026
|
+
};
|
|
12027
|
+
});
|
|
12028
|
+
}
|
|
12029
|
+
function surfaceContentKey(snapshot) {
|
|
12030
|
+
const { stateId, generatedAt, origin, ...content } = snapshot;
|
|
12031
|
+
return JSON.stringify(content);
|
|
12032
|
+
}
|
|
11600
12033
|
|
|
11601
12034
|
// src/core/extensions.ts
|
|
11602
12035
|
var fs9 = __toESM(require("fs"));
|
|
@@ -11650,7 +12083,7 @@ function defaultTargetConfig(type) {
|
|
|
11650
12083
|
enabled: true
|
|
11651
12084
|
};
|
|
11652
12085
|
}
|
|
11653
|
-
var WAIRON_VERSION = "5.1.1-dev.
|
|
12086
|
+
var WAIRON_VERSION = "5.1.1-dev.77";
|
|
11654
12087
|
var GITHUB_REPO = "SYW-Apps/Waffle-AIron";
|
|
11655
12088
|
var ARCHITECT_AGENT_ID = "agent-architect";
|
|
11656
12089
|
var ARCHITECT_TEMPLATE_ID = "architect";
|
|
@@ -11901,7 +12334,7 @@ var PackSkillSchema = import_zod9.z.object({
|
|
|
11901
12334
|
});
|
|
11902
12335
|
var PackInstructionBlockSchema = import_zod9.z.union([
|
|
11903
12336
|
// Scalar shorthand: `instructions: >- …` — the common case, unscoped.
|
|
11904
|
-
import_zod9.z.string().min(1).transform((
|
|
12337
|
+
import_zod9.z.string().min(1).transform((text2) => ({ text: text2 })),
|
|
11905
12338
|
import_zod9.z.object({
|
|
11906
12339
|
text: import_zod9.z.string().min(1),
|
|
11907
12340
|
profile: import_zod9.z.array(import_zod9.z.string().min(1)).min(1).optional()
|
|
@@ -12361,21 +12794,21 @@ function buildCodeIndex(model) {
|
|
|
12361
12794
|
const declarations = /* @__PURE__ */ new Map();
|
|
12362
12795
|
const anchors = /* @__PURE__ */ new Map();
|
|
12363
12796
|
const findingAnchors = /* @__PURE__ */ new Map();
|
|
12364
|
-
const namesAt = (cache,
|
|
12365
|
-
const cached = cache.get(
|
|
12797
|
+
const namesAt = (cache, path31, make) => {
|
|
12798
|
+
const cached = cache.get(path31);
|
|
12366
12799
|
if (cached) return cached;
|
|
12367
|
-
const f = facts.get(
|
|
12800
|
+
const f = facts.get(path31);
|
|
12368
12801
|
if (!f) return NO_NAMES;
|
|
12369
12802
|
const built = make(f);
|
|
12370
|
-
cache.set(
|
|
12803
|
+
cache.set(path31, built);
|
|
12371
12804
|
return built;
|
|
12372
12805
|
};
|
|
12373
12806
|
const republished = /* @__PURE__ */ new Map();
|
|
12374
|
-
const republishedFrom = (
|
|
12375
|
-
const cached = republished.get(
|
|
12807
|
+
const republishedFrom = (path31) => {
|
|
12808
|
+
const cached = republished.get(path31);
|
|
12376
12809
|
if (cached) return cached;
|
|
12377
|
-
const out = /* @__PURE__ */ new Set([
|
|
12378
|
-
const queue = [
|
|
12810
|
+
const out = /* @__PURE__ */ new Set([path31]);
|
|
12811
|
+
const queue = [path31];
|
|
12379
12812
|
while (queue.length) {
|
|
12380
12813
|
const from = queue.pop();
|
|
12381
12814
|
const f = facts.get(from);
|
|
@@ -12388,7 +12821,7 @@ function buildCodeIndex(model) {
|
|
|
12388
12821
|
}
|
|
12389
12822
|
}
|
|
12390
12823
|
}
|
|
12391
|
-
republished.set(
|
|
12824
|
+
republished.set(path31, out);
|
|
12392
12825
|
return out;
|
|
12393
12826
|
};
|
|
12394
12827
|
const NO_ORIGIN = /* @__PURE__ */ new Set();
|
|
@@ -12397,9 +12830,9 @@ function buildCodeIndex(model) {
|
|
|
12397
12830
|
return to ? republishedFrom(to) : NO_ORIGIN;
|
|
12398
12831
|
};
|
|
12399
12832
|
const scopeOf = (site, from) => {
|
|
12400
|
-
const
|
|
12401
|
-
const f = facts.get(
|
|
12402
|
-
return f?.status === "analyzed" && f.analysisGrade === "exact" ? { path:
|
|
12833
|
+
const path31 = pathKey(site.from ?? from);
|
|
12834
|
+
const f = facts.get(path31);
|
|
12835
|
+
return f?.status === "analyzed" && f.analysisGrade === "exact" ? { path: path31, facts: f } : void 0;
|
|
12403
12836
|
};
|
|
12404
12837
|
const originOf2 = (site, from) => {
|
|
12405
12838
|
const resolved = scopeOf(site, from);
|
|
@@ -12438,16 +12871,39 @@ function buildCodeIndex(model) {
|
|
|
12438
12871
|
if (site.via) for (const typeName of localTypesOf(f, site.via)) widenThroughType(typeName);
|
|
12439
12872
|
return out;
|
|
12440
12873
|
};
|
|
12441
|
-
const declarationsAt = (
|
|
12874
|
+
const declarationsAt = (path31) => namesAt(declarations, pathKey(path31), (f) => /* @__PURE__ */ new Set([...f.declaredNames, ...f.exportedNames]));
|
|
12875
|
+
const forwardsOf = (start, name) => {
|
|
12876
|
+
const out = [];
|
|
12877
|
+
const seen = /* @__PURE__ */ new Set();
|
|
12878
|
+
const queue = [{ file: pathKey(start), name }];
|
|
12879
|
+
while (queue.length) {
|
|
12880
|
+
const at = queue.shift();
|
|
12881
|
+
const key = `${at.file}#${at.name}`;
|
|
12882
|
+
if (seen.has(key)) continue;
|
|
12883
|
+
seen.add(key);
|
|
12884
|
+
out.push(at);
|
|
12885
|
+
for (const binding of facts.get(at.file)?.reexportBindings ?? []) {
|
|
12886
|
+
const star = binding.exported === "*";
|
|
12887
|
+
if (!star && binding.exported !== at.name) continue;
|
|
12888
|
+
if (star && at.name === "default") continue;
|
|
12889
|
+
const to = resolveImport(at.file, binding.from, paths);
|
|
12890
|
+
if (!to) continue;
|
|
12891
|
+
if (star && !facts.get(to)?.exportedNames.includes(at.name)) continue;
|
|
12892
|
+
queue.push({ file: to, name: star ? at.name : binding.local });
|
|
12893
|
+
}
|
|
12894
|
+
}
|
|
12895
|
+
return out;
|
|
12896
|
+
};
|
|
12442
12897
|
return {
|
|
12443
12898
|
paths,
|
|
12444
12899
|
exactPaths,
|
|
12445
|
-
factsAt: (
|
|
12900
|
+
factsAt: (path31) => facts.get(pathKey(path31)),
|
|
12446
12901
|
declarationsAt,
|
|
12447
|
-
anchorsAt: (
|
|
12448
|
-
findingAnchorsAt: (
|
|
12902
|
+
anchorsAt: (path31) => namesAt(anchors, pathKey(path31), (f) => /* @__PURE__ */ new Set([...f.declaredNames, ...f.exportedNames, ...f.anchoredNames])),
|
|
12903
|
+
findingAnchorsAt: (path31) => namesAt(findingAnchors, pathKey(path31), (f) => new Set(f.anchoredNames)),
|
|
12449
12904
|
originOf: originOf2,
|
|
12450
|
-
possibleOriginsOf
|
|
12905
|
+
possibleOriginsOf,
|
|
12906
|
+
forwardsOf
|
|
12451
12907
|
};
|
|
12452
12908
|
}
|
|
12453
12909
|
function buildRealizationIndex(ctx) {
|
|
@@ -12483,10 +12939,10 @@ function buildRealizationIndex(ctx) {
|
|
|
12483
12939
|
return {
|
|
12484
12940
|
paths,
|
|
12485
12941
|
implementationsOf: (compId) => implsByComponent.get(compId) ?? NONE_IMPL,
|
|
12486
|
-
implementationsAt: (
|
|
12942
|
+
implementationsAt: (path31) => implsByPath.get(pathKey(path31)) ?? NONE_IMPL,
|
|
12487
12943
|
filesOf: (compId) => filesByComponent.get(compId) ?? NONE_PATH,
|
|
12488
12944
|
filesIn: (subsystemId) => filesBySubsystem.get(subsystemId) ?? NONE_PATH,
|
|
12489
|
-
componentsAt: (
|
|
12945
|
+
componentsAt: (path31) => componentsByPath.get(pathKey(path31)) ?? NONE_COMP
|
|
12490
12946
|
};
|
|
12491
12947
|
}
|
|
12492
12948
|
function buildImportGraph(index, universe) {
|
|
@@ -12498,22 +12954,22 @@ function buildImportGraph(index, universe) {
|
|
|
12498
12954
|
if (to && to !== from) into.add(to);
|
|
12499
12955
|
}
|
|
12500
12956
|
};
|
|
12501
|
-
const importsOf = (
|
|
12502
|
-
const cached = imports.get(
|
|
12957
|
+
const importsOf = (path31) => {
|
|
12958
|
+
const cached = imports.get(path31);
|
|
12503
12959
|
if (cached) return cached;
|
|
12504
|
-
const facts = index.factsAt(
|
|
12960
|
+
const facts = index.factsAt(path31);
|
|
12505
12961
|
const out = /* @__PURE__ */ new Set();
|
|
12506
|
-
if (facts?.status === "analyzed") resolveAll(
|
|
12507
|
-
imports.set(
|
|
12962
|
+
if (facts?.status === "analyzed") resolveAll(path31, facts.imports, out);
|
|
12963
|
+
imports.set(path31, out);
|
|
12508
12964
|
return out;
|
|
12509
12965
|
};
|
|
12510
|
-
const tracesOf = (
|
|
12511
|
-
const cached = traces.get(
|
|
12966
|
+
const tracesOf = (path31) => {
|
|
12967
|
+
const cached = traces.get(path31);
|
|
12512
12968
|
if (cached) return cached;
|
|
12513
|
-
const facts = index.factsAt(
|
|
12514
|
-
const out = new Set(importsOf(
|
|
12515
|
-
if (facts?.status === "analyzed") resolveAll(
|
|
12516
|
-
traces.set(
|
|
12969
|
+
const facts = index.factsAt(path31);
|
|
12970
|
+
const out = new Set(importsOf(path31));
|
|
12971
|
+
if (facts?.status === "analyzed") resolveAll(path31, facts.reexports, out);
|
|
12972
|
+
traces.set(path31, out);
|
|
12517
12973
|
return out;
|
|
12518
12974
|
};
|
|
12519
12975
|
return {
|
|
@@ -13850,11 +14306,11 @@ var unconditionalCallCyclesRule = {
|
|
|
13850
14306
|
const memberEdges = keys.flatMap((k) => (adjacency.get(k) ?? []).filter((e) => inScc.has(e.toKey)));
|
|
13851
14307
|
if (memberEdges.length === 0) continue;
|
|
13852
14308
|
const anchor = [...memberEdges].sort((a, b) => byCodeUnit(a.fromKey, b.fromKey) || byCodeUnit(a.impl.id, b.impl.id) || a.stepNumber - b.stepNumber)[0];
|
|
13853
|
-
const
|
|
14309
|
+
const path31 = [...keys].sort(byCodeUnit).join(" \u2192 ");
|
|
13854
14310
|
ctx.addIssue(
|
|
13855
14311
|
"warning",
|
|
13856
14312
|
"UNCONDITIONAL_CALL_CYCLE",
|
|
13857
|
-
`Call cycle with no guard: ${
|
|
14313
|
+
`Call cycle with no guard: ${path31} \u2014 every call edge in this cycle is unavoidable on all paths of its narrative (e.g. step ${anchor.stepNumber} of "${anchor.methodName}" in "${anchor.impl.id}" always calls ${anchor.toLabel}). This recurses without a base case, by construction. Guard at least one edge with a branch/return before the call, or lint.allow with the termination argument.`,
|
|
13858
14314
|
anchor.impl.id,
|
|
13859
14315
|
memberEdges.some((e) => ctx.isImplementationDraft(e.impl))
|
|
13860
14316
|
);
|
|
@@ -15521,8 +15977,8 @@ var lifecycleRule = {
|
|
|
15521
15977
|
};
|
|
15522
15978
|
|
|
15523
15979
|
// src/core/rules/wiring/portal-mounts.ts
|
|
15524
|
-
function liesUnder(
|
|
15525
|
-
return
|
|
15980
|
+
function liesUnder(path31, prefix) {
|
|
15981
|
+
return path31 === prefix || path31.startsWith(prefix + "/");
|
|
15526
15982
|
}
|
|
15527
15983
|
var portalMountsRule = {
|
|
15528
15984
|
name: "portal-mounts",
|
|
@@ -15975,8 +16431,8 @@ function componentOfImpl(ctx, implContract) {
|
|
|
15975
16431
|
}
|
|
15976
16432
|
var CLAIM_PHRASES = /\b(persist(s|ed|ent|ing|ence)?|survives?\s+(a\s+)?restart|writ(es?|ten)\s+to\s+disk|registered\s+into|durabl[ey])\b/i;
|
|
15977
16433
|
var QUOTED_SPAN = /"[^"]*"|“[^”]*”|`[^`]*`|(?<![\p{L}\p{N}])['‘](?:[^'‘’]|(?<=[\p{L}\p{N}])['’](?=[\p{L}\p{N}]))*['’](?![\p{L}\p{N}])/gu;
|
|
15978
|
-
function withoutQuotedText(
|
|
15979
|
-
return
|
|
16434
|
+
function withoutQuotedText(text2) {
|
|
16435
|
+
return text2.replace(QUOTED_SPAN, " ");
|
|
15980
16436
|
}
|
|
15981
16437
|
var proseClaimRule = {
|
|
15982
16438
|
name: "prose-claims",
|
|
@@ -16510,6 +16966,25 @@ function resolveCallTarget(ctx, componentId, methodName) {
|
|
|
16510
16966
|
}
|
|
16511
16967
|
return { accepted, files };
|
|
16512
16968
|
}
|
|
16969
|
+
function isTarget(pairs, target) {
|
|
16970
|
+
return pairs.some((p) => target.files.has(p.file) && target.accepted.has(p.name));
|
|
16971
|
+
}
|
|
16972
|
+
function invokedAs(code, site, file) {
|
|
16973
|
+
const scope = pathKey(site.from ?? file);
|
|
16974
|
+
const facts = code.factsAt(scope);
|
|
16975
|
+
if (!facts || facts.status !== "analyzed" || facts.analysisGrade !== "exact") return [];
|
|
16976
|
+
if (!site.member) {
|
|
16977
|
+
const binding = importBindingOf(facts, site.name);
|
|
16978
|
+
if (!binding || binding.namespace) return [];
|
|
16979
|
+
const module3 = resolveImport(scope, binding.from, code.paths);
|
|
16980
|
+
return module3 ? code.forwardsOf(module3, binding.imported ?? site.name) : [];
|
|
16981
|
+
}
|
|
16982
|
+
if (!site.via) return [];
|
|
16983
|
+
const receiver = importBindingOf(facts, site.via);
|
|
16984
|
+
if (!receiver?.namespace) return [];
|
|
16985
|
+
const module2 = resolveImport(scope, receiver.from, code.paths);
|
|
16986
|
+
return module2 ? code.forwardsOf(module2, site.name) : [];
|
|
16987
|
+
}
|
|
16513
16988
|
function claimsOf(implMethod) {
|
|
16514
16989
|
const claims = [];
|
|
16515
16990
|
for (const step of implMethod.narrative) {
|
|
@@ -16575,6 +17050,7 @@ var callConformanceRule = {
|
|
|
16575
17050
|
if (!ctx.componentMap.has(claim.component)) continue;
|
|
16576
17051
|
const target = resolveCallTarget(ctx, claim.component, claim.method);
|
|
16577
17052
|
if (target.accepted.has(fnSymbol)) continue;
|
|
17053
|
+
if (isTarget(code.forwardsOf(file, fnSymbol), target)) continue;
|
|
16578
17054
|
const matching = sites.filter((s) => target.accepted.has(s.name));
|
|
16579
17055
|
const ref = `${claim.component}.${claim.method}`;
|
|
16580
17056
|
if (target.files.size === 0) {
|
|
@@ -16583,6 +17059,7 @@ var callConformanceRule = {
|
|
|
16583
17059
|
}
|
|
16584
17060
|
continue;
|
|
16585
17061
|
}
|
|
17062
|
+
if (sites.some((s) => !target.accepted.has(s.name) && isTarget(invokedAs(code, s, file), target))) continue;
|
|
16586
17063
|
const landed = /* @__PURE__ */ new Set();
|
|
16587
17064
|
let realized = false;
|
|
16588
17065
|
for (const site of matching) {
|
|
@@ -16690,11 +17167,11 @@ var hiddenStateRule = {
|
|
|
16690
17167
|
byPath.set(key, list);
|
|
16691
17168
|
}
|
|
16692
17169
|
}
|
|
16693
|
-
for (const
|
|
16694
|
-
const facts = code.factsAt(
|
|
17170
|
+
for (const path31 of code.exactPaths) {
|
|
17171
|
+
const facts = code.factsAt(path31);
|
|
16695
17172
|
const bindings = facts.topLevelMutableBindings ?? [];
|
|
16696
17173
|
if (bindings.length === 0) continue;
|
|
16697
|
-
const mapped = byPath.get(
|
|
17174
|
+
const mapped = byPath.get(path31) ?? [];
|
|
16698
17175
|
if (mapped.length === 0) continue;
|
|
16699
17176
|
if (!mapped.every((m) => isStatelessLogic(m.component))) continue;
|
|
16700
17177
|
const anchor = [...mapped].sort((a, b) => a.impl.id.localeCompare(b.impl.id))[0];
|
|
@@ -16760,7 +17237,7 @@ var dependencyConformanceRule = {
|
|
|
16760
17237
|
}
|
|
16761
17238
|
return false;
|
|
16762
17239
|
};
|
|
16763
|
-
const draftAt = (
|
|
17240
|
+
const draftAt = (path31) => realization.implementationsAt(path31).some((impl) => ctx.isImplementationDraft(impl));
|
|
16764
17241
|
for (const fromPath of mappedPaths) {
|
|
16765
17242
|
const fromComponents = realization.componentsAt(fromPath);
|
|
16766
17243
|
for (const toPath of graph.importsOf(fromPath)) {
|
|
@@ -16845,7 +17322,7 @@ var portalImportsRule = {
|
|
|
16845
17322
|
}
|
|
16846
17323
|
return files;
|
|
16847
17324
|
};
|
|
16848
|
-
const draftAt = (
|
|
17325
|
+
const draftAt = (path31) => realization.implementationsAt(path31).some((impl) => ctx.isImplementationDraft(impl));
|
|
16849
17326
|
for (const fromPath of mappedPaths) {
|
|
16850
17327
|
const fromSubsystems = new Set(realization.componentsAt(fromPath).map((c) => c.subsystem));
|
|
16851
17328
|
for (const toPath of graph.importsOf(fromPath)) {
|
|
@@ -17306,7 +17783,7 @@ var paramConformanceRule = {
|
|
|
17306
17783
|
codeNameOf.set(type.id, named);
|
|
17307
17784
|
if (type.subsystem) codeNameOf.set(`${type.subsystem}.${type.id}`, named);
|
|
17308
17785
|
}
|
|
17309
|
-
const normalize3 = (
|
|
17786
|
+
const normalize3 = (text2) => text2.replace(/\s+/g, " ").trim();
|
|
17310
17787
|
const typeAgrees = (declared, realized) => {
|
|
17311
17788
|
if (!realized.type) return false;
|
|
17312
17789
|
const stated = normalize3(declared.type);
|
|
@@ -17381,8 +17858,8 @@ var paramConformanceRule = {
|
|
|
17381
17858
|
};
|
|
17382
17859
|
|
|
17383
17860
|
// src/core/rules/conformance/route-coverage.ts
|
|
17384
|
-
function liesUnder2(
|
|
17385
|
-
return
|
|
17861
|
+
function liesUnder2(path31, prefix) {
|
|
17862
|
+
return path31 === prefix || path31.startsWith(prefix + "/");
|
|
17386
17863
|
}
|
|
17387
17864
|
function normalizeSegment(segment) {
|
|
17388
17865
|
return /^\{.+\}$/.test(segment) || segment.startsWith(":") ? "*" : segment;
|
|
@@ -18137,9 +18614,9 @@ function makeMatcher(tech) {
|
|
|
18137
18614
|
const joined = parts.join("");
|
|
18138
18615
|
if (joined.length < 3) return null;
|
|
18139
18616
|
const n = parts.length;
|
|
18140
|
-
return (
|
|
18141
|
-
if (!
|
|
18142
|
-
const words2 =
|
|
18617
|
+
return (text2) => {
|
|
18618
|
+
if (!text2) return false;
|
|
18619
|
+
const words2 = text2.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
|
|
18143
18620
|
if (words2.some((w) => w.includes(joined))) return true;
|
|
18144
18621
|
if (n < 2) return false;
|
|
18145
18622
|
for (let i = 0; i + n <= words2.length; i++) {
|
|
@@ -19077,6 +19554,9 @@ function ruleSequence() {
|
|
|
19077
19554
|
const audits = [carriedDebtRule, lintAllowsRule].filter((r) => ruleSet.includes(r));
|
|
19078
19555
|
return [...ruleSet.filter((r) => !audits.includes(r)), ...audits];
|
|
19079
19556
|
}
|
|
19557
|
+
function specScopedRules() {
|
|
19558
|
+
return ruleSequence().filter((r) => r.scope === "spec");
|
|
19559
|
+
}
|
|
19080
19560
|
function knownIssueCodes() {
|
|
19081
19561
|
return listRules().flatMap((r) => r.codes);
|
|
19082
19562
|
}
|
|
@@ -19211,8 +19691,8 @@ LANGUAGE_PATTERNS.cpp = LANGUAGE_PATTERNS.c;
|
|
|
19211
19691
|
var PATTERN_ANALYSIS_MAX_BYTES = 1e6;
|
|
19212
19692
|
var IDENTIFIER_RE = /[A-Za-z_$][\w$]*/g;
|
|
19213
19693
|
var STRING_RE = /'([^'\\\n]*(?:\\.[^'\\\n]*)*)'|"([^"\\\n]*(?:\\.[^"\\\n]*)*)"|`([^`\\]*(?:\\.[^`\\]*)*)`/g;
|
|
19214
|
-
function stripComments(
|
|
19215
|
-
let out =
|
|
19694
|
+
function stripComments(text2, patterns) {
|
|
19695
|
+
let out = text2;
|
|
19216
19696
|
for (const [open, close] of patterns.blockComments) {
|
|
19217
19697
|
const re = new RegExp(`${escapeRe(open)}[\\s\\S]*?${escapeRe(close)}`, "g");
|
|
19218
19698
|
out = out.replace(re, " ");
|
|
@@ -19238,16 +19718,16 @@ function boundNames(captured, localOnly) {
|
|
|
19238
19718
|
}
|
|
19239
19719
|
return names;
|
|
19240
19720
|
}
|
|
19241
|
-
function collectStrings(
|
|
19721
|
+
function collectStrings(text2) {
|
|
19242
19722
|
const anchors = /* @__PURE__ */ new Set();
|
|
19243
|
-
for (const m of
|
|
19723
|
+
for (const m of text2.matchAll(STRING_RE)) {
|
|
19244
19724
|
const value = m[1] ?? m[2] ?? m[3];
|
|
19245
19725
|
if (value) anchors.add(value);
|
|
19246
19726
|
}
|
|
19247
19727
|
return anchors;
|
|
19248
19728
|
}
|
|
19249
|
-
function analyzeWithPatterns(
|
|
19250
|
-
const stripped = stripComments(
|
|
19729
|
+
function analyzeWithPatterns(text2, patterns) {
|
|
19730
|
+
const stripped = stripComments(text2, patterns);
|
|
19251
19731
|
const declared = /* @__PURE__ */ new Set();
|
|
19252
19732
|
const exported = /* @__PURE__ */ new Set();
|
|
19253
19733
|
const imports = /* @__PURE__ */ new Set();
|
|
@@ -19275,7 +19755,7 @@ function analyzeWithPatterns(text, patterns) {
|
|
|
19275
19755
|
}
|
|
19276
19756
|
}
|
|
19277
19757
|
}
|
|
19278
|
-
const anchors = collectStrings(
|
|
19758
|
+
const anchors = collectStrings(text2);
|
|
19279
19759
|
for (const m of stripped.matchAll(IDENTIFIER_RE)) anchors.add(m[0]);
|
|
19280
19760
|
return {
|
|
19281
19761
|
analysisGrade: "pattern",
|
|
@@ -19286,11 +19766,11 @@ function analyzeWithPatterns(text, patterns) {
|
|
|
19286
19766
|
reexports: []
|
|
19287
19767
|
};
|
|
19288
19768
|
}
|
|
19289
|
-
function analyzeGeneric(
|
|
19769
|
+
function analyzeGeneric(text2) {
|
|
19290
19770
|
const words2 = /* @__PURE__ */ new Set();
|
|
19291
|
-
for (const m of
|
|
19771
|
+
for (const m of text2.matchAll(IDENTIFIER_RE)) words2.add(m[0]);
|
|
19292
19772
|
const anchors = new Set(words2);
|
|
19293
|
-
for (const s of collectStrings(
|
|
19773
|
+
for (const s of collectStrings(text2)) anchors.add(s);
|
|
19294
19774
|
return {
|
|
19295
19775
|
analysisGrade: "generic",
|
|
19296
19776
|
declaredNames: [...words2],
|
|
@@ -19860,14 +20340,14 @@ function buildCodeModel(implementations, types, projectRoot, sourceRoots = [], e
|
|
|
19860
20340
|
files.push({ path: sourcePath, status: "unreadable", ...empty });
|
|
19861
20341
|
continue;
|
|
19862
20342
|
}
|
|
19863
|
-
const
|
|
20343
|
+
const text2 = buffer.toString("utf8");
|
|
19864
20344
|
const language = EXTENSION_LANGUAGE[path13.extname(sourcePath).toLowerCase()];
|
|
19865
20345
|
let analyzed;
|
|
19866
20346
|
if (language === "typescript" || language === "javascript") {
|
|
19867
20347
|
const ts = resolveTypeScript(projectRoot);
|
|
19868
20348
|
if (ts) {
|
|
19869
20349
|
try {
|
|
19870
|
-
const facts = walkExact(ts,
|
|
20350
|
+
const facts = walkExact(ts, text2, absolute);
|
|
19871
20351
|
chaseReexports(ts, facts, absolute, projectRoot, /* @__PURE__ */ new Set([absolute]), exactCache);
|
|
19872
20352
|
analyzed = {
|
|
19873
20353
|
analysisGrade: "exact",
|
|
@@ -19876,6 +20356,10 @@ function buildCodeModel(implementations, types, projectRoot, sourceRoots = [], e
|
|
|
19876
20356
|
exportedNames: [...facts.exported],
|
|
19877
20357
|
imports: [...facts.imports],
|
|
19878
20358
|
reexports: [...facts.reexports],
|
|
20359
|
+
reexportBindings: [
|
|
20360
|
+
...facts.namedReexports.map(({ exported, local, from }) => ({ exported, local, from })),
|
|
20361
|
+
...facts.starExports.map((from) => ({ exported: "*", local: "*", from }))
|
|
20362
|
+
],
|
|
19879
20363
|
functionComplexity: Object.fromEntries(facts.complexity),
|
|
19880
20364
|
functionCallSites: Object.fromEntries([...facts.calls].map(([k, v]) => [k, [...v.values()]])),
|
|
19881
20365
|
importBindings: Object.fromEntries(facts.importBindings),
|
|
@@ -19889,20 +20373,159 @@ function buildCodeModel(implementations, types, projectRoot, sourceRoots = [], e
|
|
|
19889
20373
|
reexportOnly: facts.reexportOnly
|
|
19890
20374
|
};
|
|
19891
20375
|
} catch {
|
|
19892
|
-
analyzed = analyzeGeneric(
|
|
20376
|
+
analyzed = analyzeGeneric(text2);
|
|
19893
20377
|
}
|
|
19894
20378
|
} else {
|
|
19895
|
-
analyzed = analyzeWithPatterns(
|
|
20379
|
+
analyzed = analyzeWithPatterns(text2, LANGUAGE_PATTERNS[language]);
|
|
19896
20380
|
}
|
|
19897
|
-
} else if (language && LANGUAGE_PATTERNS[language] &&
|
|
19898
|
-
analyzed = analyzeWithPatterns(
|
|
20381
|
+
} else if (language && LANGUAGE_PATTERNS[language] && text2.length <= PATTERN_ANALYSIS_MAX_BYTES) {
|
|
20382
|
+
analyzed = analyzeWithPatterns(text2, LANGUAGE_PATTERNS[language]);
|
|
19899
20383
|
} else {
|
|
19900
|
-
analyzed = analyzeGeneric(
|
|
20384
|
+
analyzed = analyzeGeneric(text2);
|
|
19901
20385
|
}
|
|
19902
20386
|
files.push({ path: sourcePath, status: "analyzed", language, ...analyzed });
|
|
19903
20387
|
}
|
|
19904
20388
|
return { files, projectRoot, rootFiles };
|
|
19905
20389
|
}
|
|
20390
|
+
var IMPORT_FROM_RE = /(?:^|;)[ \t]*import\s+(?!\()([^'";]*?)\s*from\s*['"]([^'"]+)['"]/gm;
|
|
20391
|
+
var DYNAMIC_IMPORT_RE = /\{([^{}]*)\}\s*=\s*await\s+import\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
20392
|
+
var IDENTIFIER_ONLY_RE = /^[A-Za-z_$][\w$]*$/;
|
|
20393
|
+
function clauseBindings(clause, specifier) {
|
|
20394
|
+
const out = [];
|
|
20395
|
+
const body = clause.replace(/^\s*type\s+/, "");
|
|
20396
|
+
const named = /\{([^}]*)\}/.exec(body);
|
|
20397
|
+
if (named) {
|
|
20398
|
+
for (const piece of named[1].split(",")) {
|
|
20399
|
+
const sides = piece.replace(/^\s*type\s+/, "").split(/\s+as\s+/).map((s) => s.trim());
|
|
20400
|
+
const exported = sides[0];
|
|
20401
|
+
const local = sides[1] ?? exported;
|
|
20402
|
+
if (IDENTIFIER_ONLY_RE.test(exported) && IDENTIFIER_ONLY_RE.test(local)) out.push({ exported, local, specifier });
|
|
20403
|
+
}
|
|
20404
|
+
}
|
|
20405
|
+
const namespace = /\*\s*as\s+([A-Za-z_$][\w$]*)/.exec(body);
|
|
20406
|
+
if (namespace) out.push({ exported: "*", local: namespace[1], specifier });
|
|
20407
|
+
const head = body.replace(/\{[^}]*\}/, "").replace(/\*\s*as\s+[A-Za-z_$][\w$]*/, "");
|
|
20408
|
+
const defaulted = /^\s*([A-Za-z_$][\w$]*)/.exec(head);
|
|
20409
|
+
if (defaulted) out.push({ exported: "default", local: defaulted[1], specifier });
|
|
20410
|
+
return out;
|
|
20411
|
+
}
|
|
20412
|
+
function readTestFiles(testRoots, projectRoot) {
|
|
20413
|
+
const scanned = [];
|
|
20414
|
+
for (const key of walkDeclaredRoots(testRoots, [], projectRoot)) {
|
|
20415
|
+
let text2;
|
|
20416
|
+
try {
|
|
20417
|
+
const buffer = fs10.readFileSync(path13.resolve(projectRoot, key));
|
|
20418
|
+
if (looksBinary(buffer)) continue;
|
|
20419
|
+
text2 = buffer.toString("utf8");
|
|
20420
|
+
} catch {
|
|
20421
|
+
continue;
|
|
20422
|
+
}
|
|
20423
|
+
const imports = /* @__PURE__ */ new Set();
|
|
20424
|
+
for (const m of text2.matchAll(NAMED_IMPORT_BINDINGS_RE)) {
|
|
20425
|
+
for (const name of boundNames(m[1] ?? "", false)) imports.add(name);
|
|
20426
|
+
}
|
|
20427
|
+
const code = stripComments(text2, JS_PATTERNS);
|
|
20428
|
+
const bindings = [];
|
|
20429
|
+
for (const m of code.matchAll(IMPORT_FROM_RE)) bindings.push(...clauseBindings(m[1], m[2]));
|
|
20430
|
+
for (const m of code.matchAll(DYNAMIC_IMPORT_RE)) bindings.push(...clauseBindings(`{${m[1].replace(/:/g, " as ")}}`, m[2]));
|
|
20431
|
+
const words2 = /* @__PURE__ */ new Set();
|
|
20432
|
+
for (const m of text2.matchAll(IDENTIFIER_RE)) words2.add(m[0]);
|
|
20433
|
+
scanned.push({ path: key, imports, bindings, words: words2 });
|
|
20434
|
+
}
|
|
20435
|
+
return scanned;
|
|
20436
|
+
}
|
|
20437
|
+
var NAMED_REEXPORT_RE = /\bexport\s+(?:type\s+)?\{([^}]*)\}\s*from\s*['"]([^'"]+)['"]/g;
|
|
20438
|
+
var STAR_REEXPORT_RE = /\bexport\s+\*\s+from\s*['"]([^'"]+)['"]/g;
|
|
20439
|
+
function reexportReader() {
|
|
20440
|
+
const cache = /* @__PURE__ */ new Map();
|
|
20441
|
+
return (file) => {
|
|
20442
|
+
const cached = cache.get(file);
|
|
20443
|
+
if (cached) return cached;
|
|
20444
|
+
const found = { named: [], stars: [] };
|
|
20445
|
+
try {
|
|
20446
|
+
const code = stripComments(fs10.readFileSync(file, "utf8"), JS_PATTERNS);
|
|
20447
|
+
for (const m of code.matchAll(NAMED_REEXPORT_RE)) {
|
|
20448
|
+
for (const piece of m[1].split(",")) {
|
|
20449
|
+
const sides = piece.replace(/^\s*type\s+/, "").split(/\s+as\s+/).map((s) => s.trim());
|
|
20450
|
+
if (!IDENTIFIER_ONLY_RE.test(sides[0] ?? "")) continue;
|
|
20451
|
+
found.named.push({ local: sides[0], exported: sides[1] ?? sides[0], specifier: m[2] });
|
|
20452
|
+
}
|
|
20453
|
+
}
|
|
20454
|
+
for (const m of code.matchAll(STAR_REEXPORT_RE)) found.stars.push(m[1]);
|
|
20455
|
+
} catch {
|
|
20456
|
+
}
|
|
20457
|
+
cache.set(file, found);
|
|
20458
|
+
return found;
|
|
20459
|
+
};
|
|
20460
|
+
}
|
|
20461
|
+
function publishesFrom(module2, name, target, symbol, readReexports, visited = /* @__PURE__ */ new Set()) {
|
|
20462
|
+
if (path13.resolve(module2) === target && name === symbol) return true;
|
|
20463
|
+
const key = `${module2}#${name}`;
|
|
20464
|
+
if (visited.has(key)) return false;
|
|
20465
|
+
visited.add(key);
|
|
20466
|
+
const reexports = readReexports(module2);
|
|
20467
|
+
for (const re of reexports.named) {
|
|
20468
|
+
if (re.exported !== name) continue;
|
|
20469
|
+
const next = resolveRelativeModule(module2, re.specifier);
|
|
20470
|
+
if (next && publishesFrom(next, re.local, target, symbol, readReexports, visited)) return true;
|
|
20471
|
+
}
|
|
20472
|
+
if (name === "default") return false;
|
|
20473
|
+
for (const specifier of reexports.stars) {
|
|
20474
|
+
const next = resolveRelativeModule(module2, specifier);
|
|
20475
|
+
if (next && publishesFrom(next, name, target, symbol, readReexports, visited)) return true;
|
|
20476
|
+
}
|
|
20477
|
+
return false;
|
|
20478
|
+
}
|
|
20479
|
+
function findTestsReferencing(methods, projectRoot, testRoots = []) {
|
|
20480
|
+
const scanned = readTestFiles(testRoots, projectRoot);
|
|
20481
|
+
if (scanned.length === 0) return [];
|
|
20482
|
+
const indiscriminateAbove = scanned.length / 10;
|
|
20483
|
+
const searches = [];
|
|
20484
|
+
for (const method of methods) {
|
|
20485
|
+
const symbol = method.symbol ?? method.name;
|
|
20486
|
+
let search = searches.find((s) => s.method === method.name && s.symbol === symbol);
|
|
20487
|
+
if (!search) {
|
|
20488
|
+
search = { method: method.name, symbol, files: [] };
|
|
20489
|
+
searches.push(search);
|
|
20490
|
+
}
|
|
20491
|
+
const file = method.sourcePath ? path13.resolve(projectRoot, method.sourcePath) : void 0;
|
|
20492
|
+
if (file && !search.files.includes(file)) search.files.push(file);
|
|
20493
|
+
}
|
|
20494
|
+
const readReexports = reexportReader();
|
|
20495
|
+
const found = [];
|
|
20496
|
+
for (const { method, symbol, files } of searches) {
|
|
20497
|
+
const imported = [];
|
|
20498
|
+
const mentioned = [];
|
|
20499
|
+
for (const test of scanned) {
|
|
20500
|
+
if (files.length === 0) {
|
|
20501
|
+
if (test.imports.has(symbol)) imported.push(test.path);
|
|
20502
|
+
else if (test.words.has(symbol)) mentioned.push(test.path);
|
|
20503
|
+
continue;
|
|
20504
|
+
}
|
|
20505
|
+
const testFile = path13.resolve(projectRoot, test.path);
|
|
20506
|
+
const fromMethod = (binding, name) => {
|
|
20507
|
+
const module2 = resolveRelativeModule(testFile, binding.specifier);
|
|
20508
|
+
return module2 !== null && files.some((target) => publishesFrom(module2, name, target, symbol, readReexports));
|
|
20509
|
+
};
|
|
20510
|
+
const naming = test.bindings.filter((b) => b.exported === symbol || b.local === symbol);
|
|
20511
|
+
if (test.bindings.some((b) => b.exported !== "*" && fromMethod(b, b.exported)) || test.words.has(symbol) && test.bindings.some((b) => b.exported === "*" && fromMethod(b, symbol))) {
|
|
20512
|
+
imported.push(test.path);
|
|
20513
|
+
} else if (naming.length === 0 && test.words.has(symbol)) {
|
|
20514
|
+
mentioned.push(test.path);
|
|
20515
|
+
}
|
|
20516
|
+
}
|
|
20517
|
+
const indiscriminate = mentioned.length > indiscriminateAbove;
|
|
20518
|
+
if (imported.length === 0 && mentioned.length === 0) continue;
|
|
20519
|
+
found.push({
|
|
20520
|
+
method,
|
|
20521
|
+
symbol,
|
|
20522
|
+
imported,
|
|
20523
|
+
mentioned: indiscriminate ? [] : mentioned,
|
|
20524
|
+
indiscriminate
|
|
20525
|
+
});
|
|
20526
|
+
}
|
|
20527
|
+
return found;
|
|
20528
|
+
}
|
|
19906
20529
|
|
|
19907
20530
|
// src/core/rules/index.ts
|
|
19908
20531
|
function composeRuleSequence(extraRules = []) {
|
|
@@ -20275,7 +20898,7 @@ function buildRuleContext(opts) {
|
|
|
20275
20898
|
}
|
|
20276
20899
|
const severity = getRuleSeverity(code, defaultSeverity, isDraftContext, owner);
|
|
20277
20900
|
if (severity === "off") return;
|
|
20278
|
-
let
|
|
20901
|
+
let text2 = message;
|
|
20279
20902
|
if (specId) {
|
|
20280
20903
|
const key = `${specId}\0${code}`;
|
|
20281
20904
|
let seen = sitesSeen.get(key);
|
|
@@ -20293,7 +20916,7 @@ function buildRuleContext(opts) {
|
|
|
20293
20916
|
if (grew.length === 0) {
|
|
20294
20917
|
if (severity === "warning") return;
|
|
20295
20918
|
} else {
|
|
20296
|
-
|
|
20919
|
+
text2 = `${text2} A lint.allow covers this site, but not ${grew.length} part(s) of it \u2014 ${grew.map((u) => `"${u}"`).join("; ")} ${grew.length === 1 ? "is" : "are"} new. Decide on them: add them to the allow's \`covers\` with a reason that is actually true, or fix them.`;
|
|
20297
20920
|
}
|
|
20298
20921
|
}
|
|
20299
20922
|
}
|
|
@@ -20308,13 +20931,13 @@ function buildRuleContext(opts) {
|
|
|
20308
20931
|
else grew.push(unit);
|
|
20309
20932
|
}
|
|
20310
20933
|
if (grew.length === 0) return;
|
|
20311
|
-
|
|
20934
|
+
text2 = `${text2} The conformance debt register carries this finding, but not ${grew.length} part(s) of it \u2014 ${grew.map((u) => `"${u}"`).join("; ")} ${grew.length === 1 ? "is" : "are"} new. Fix them, or add them to the entry's \`covers\` with a reason that is actually true.`;
|
|
20312
20935
|
}
|
|
20313
20936
|
}
|
|
20314
20937
|
issues.push({
|
|
20315
20938
|
severity,
|
|
20316
20939
|
code,
|
|
20317
|
-
message:
|
|
20940
|
+
message: text2,
|
|
20318
20941
|
specId,
|
|
20319
20942
|
...isDraftContext ? { draftContext: true } : {},
|
|
20320
20943
|
...surfaceResolved ? { surfaceResolved: true } : {}
|
|
@@ -20399,6 +21022,54 @@ function buildRuleContext(opts) {
|
|
|
20399
21022
|
return ctx;
|
|
20400
21023
|
}
|
|
20401
21024
|
|
|
21025
|
+
// src/core/rules/candidate.ts
|
|
21026
|
+
function stubSystem() {
|
|
21027
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
21028
|
+
return {
|
|
21029
|
+
schemaVersion: "1.0.0",
|
|
21030
|
+
name: "candidate",
|
|
21031
|
+
vision: "",
|
|
21032
|
+
boundaries: [],
|
|
21033
|
+
globalRequirements: [],
|
|
21034
|
+
databases: [],
|
|
21035
|
+
createdAt: now,
|
|
21036
|
+
updatedAt: now
|
|
21037
|
+
};
|
|
21038
|
+
}
|
|
21039
|
+
function validateComponentCandidate(candidate, opts = {}) {
|
|
21040
|
+
const issues = [];
|
|
21041
|
+
registerBuiltinRules();
|
|
21042
|
+
if (opts.extensions?.rules?.length) registerPackRules(opts.extensions.rules);
|
|
21043
|
+
const knownCodes = /* @__PURE__ */ new Set([
|
|
21044
|
+
...knownIssueCodes().map((rc) => rc.code),
|
|
21045
|
+
...(opts.extensions?.assertions ?? []).map((a) => a.fullCode)
|
|
21046
|
+
]);
|
|
21047
|
+
const ctx = buildRuleContext({
|
|
21048
|
+
system: stubSystem(),
|
|
21049
|
+
subsystems: [],
|
|
21050
|
+
components: [candidate],
|
|
21051
|
+
interfaces: [],
|
|
21052
|
+
implementations: [],
|
|
21053
|
+
types: [],
|
|
21054
|
+
rules: opts.rules,
|
|
21055
|
+
projectType: opts.projectType ?? "backend",
|
|
21056
|
+
extensions: opts.extensions,
|
|
21057
|
+
// Only a tree-scoped rule reads these (roundtrip-serialization); no
|
|
21058
|
+
// spec-scoped rule does, so a candidate carries none.
|
|
21059
|
+
roundTripIssues: [],
|
|
21060
|
+
knownIssueCodes: knownCodes,
|
|
21061
|
+
issues
|
|
21062
|
+
});
|
|
21063
|
+
for (const rule of specScopedRules()) {
|
|
21064
|
+
rule.check(ctx);
|
|
21065
|
+
}
|
|
21066
|
+
const own = issues.filter((i) => !i.specId || i.specId === candidate.id);
|
|
21067
|
+
return {
|
|
21068
|
+
errors: own.filter((i) => i.severity === "error"),
|
|
21069
|
+
warnings: own.filter((i) => i.severity === "warning")
|
|
21070
|
+
};
|
|
21071
|
+
}
|
|
21072
|
+
|
|
20402
21073
|
// src/core/validation.ts
|
|
20403
21074
|
var path14 = __toESM(require("path"));
|
|
20404
21075
|
function projectPackSelections() {
|
|
@@ -20408,6 +21079,9 @@ function projectPackSelections() {
|
|
|
20408
21079
|
return [];
|
|
20409
21080
|
}
|
|
20410
21081
|
}
|
|
21082
|
+
function findTestsReferencing2(methods, projectRoot, testRoots = []) {
|
|
21083
|
+
return findTestsReferencing(methods, projectRoot, testRoots);
|
|
21084
|
+
}
|
|
20411
21085
|
var RESOLUTION_FAILURE_CODES = /* @__PURE__ */ new Set([
|
|
20412
21086
|
"UNDEFINED_TYPE_REFERENCE",
|
|
20413
21087
|
"INVALID_DEPENDENCY_REFERENCE",
|
|
@@ -20446,6 +21120,67 @@ function stricterSeverities(parent, child) {
|
|
|
20446
21120
|
function issue(severity, code, message, agentId, specId) {
|
|
20447
21121
|
return { severity, code, message, agentId, specId };
|
|
20448
21122
|
}
|
|
21123
|
+
function validateRegistry(registry, rules) {
|
|
21124
|
+
const issues = [];
|
|
21125
|
+
const idCounts = /* @__PURE__ */ new Map();
|
|
21126
|
+
for (const agent of registry.agents) {
|
|
21127
|
+
idCounts.set(agent.id, (idCounts.get(agent.id) ?? 0) + 1);
|
|
21128
|
+
}
|
|
21129
|
+
for (const [id, count] of idCounts) {
|
|
21130
|
+
if (count > 1) {
|
|
21131
|
+
issues.push(issue("error", "DUPLICATE_AGENT_ID", `Duplicate agent id: "${id}"`, id));
|
|
21132
|
+
}
|
|
21133
|
+
}
|
|
21134
|
+
for (const agent of registry.agents) {
|
|
21135
|
+
validateAgent(agent, rules, issues);
|
|
21136
|
+
}
|
|
21137
|
+
if (rules.noOverlappingOwnership) {
|
|
21138
|
+
checkOverlappingOwnership(registry.agents, issues);
|
|
21139
|
+
}
|
|
21140
|
+
return {
|
|
21141
|
+
valid: issues.every((i) => i.severity !== "error"),
|
|
21142
|
+
issues
|
|
21143
|
+
};
|
|
21144
|
+
}
|
|
21145
|
+
function validateAgent(agent, rules, issues) {
|
|
21146
|
+
const isMeta = agent.tags.some((t) => rules.metaAgentTags.includes(t));
|
|
21147
|
+
if (rules.requireOwnedPaths && !isMeta && agent.ownedPaths.length === 0) {
|
|
21148
|
+
issues.push(
|
|
21149
|
+
issue(
|
|
21150
|
+
"warning",
|
|
21151
|
+
"NO_OWNED_PATHS",
|
|
21152
|
+
`Agent "${agent.id}" has no ownedPaths. Add paths or tag as meta/guardian.`,
|
|
21153
|
+
agent.id
|
|
21154
|
+
)
|
|
21155
|
+
);
|
|
21156
|
+
}
|
|
21157
|
+
if (agent.targets.length === 0) {
|
|
21158
|
+
issues.push(
|
|
21159
|
+
issue("warning", "NO_TARGETS", `Agent "${agent.id}" has no output targets configured.`, agent.id)
|
|
21160
|
+
);
|
|
21161
|
+
}
|
|
21162
|
+
}
|
|
21163
|
+
function checkOverlappingOwnership(agents, issues) {
|
|
21164
|
+
const pathToAgents = /* @__PURE__ */ new Map();
|
|
21165
|
+
for (const agent of agents) {
|
|
21166
|
+
for (const p of agent.ownedPaths) {
|
|
21167
|
+
const owners = pathToAgents.get(p) ?? [];
|
|
21168
|
+
owners.push(agent.id);
|
|
21169
|
+
pathToAgents.set(p, owners);
|
|
21170
|
+
}
|
|
21171
|
+
}
|
|
21172
|
+
for (const [p, owners] of pathToAgents) {
|
|
21173
|
+
if (owners.length > 1) {
|
|
21174
|
+
issues.push(
|
|
21175
|
+
issue(
|
|
21176
|
+
"error",
|
|
21177
|
+
"OVERLAPPING_OWNERSHIP",
|
|
21178
|
+
`Path "${p}" is claimed by multiple agents: ${owners.join(", ")}`
|
|
21179
|
+
)
|
|
21180
|
+
);
|
|
21181
|
+
}
|
|
21182
|
+
}
|
|
21183
|
+
}
|
|
20449
21184
|
function validateSddTree(rulesOrOptions, projectType = "backend") {
|
|
20450
21185
|
let rules = rulesOrOptions;
|
|
20451
21186
|
let scopeSubsystem;
|
|
@@ -20631,16 +21366,16 @@ function resolveThroughParent(boundRoot, treatAllAsComplete, childRules) {
|
|
|
20631
21366
|
}));
|
|
20632
21367
|
return { root: top, scope, issues };
|
|
20633
21368
|
}
|
|
20634
|
-
function stripNamespace(
|
|
21369
|
+
function stripNamespace(text2, scope) {
|
|
20635
21370
|
const prefix = `${scope}::`;
|
|
20636
21371
|
let out = "";
|
|
20637
21372
|
let from = 0;
|
|
20638
|
-
for (let at =
|
|
20639
|
-
const atBoundary = at === 0 || !isIdChar(
|
|
20640
|
-
out +=
|
|
21373
|
+
for (let at = text2.indexOf(prefix); at !== -1; at = text2.indexOf(prefix, from)) {
|
|
21374
|
+
const atBoundary = at === 0 || !isIdChar(text2[at - 1]);
|
|
21375
|
+
out += text2.slice(from, at) + (atBoundary ? "" : prefix);
|
|
20641
21376
|
from = at + prefix.length;
|
|
20642
21377
|
}
|
|
20643
|
-
return out +
|
|
21378
|
+
return out + text2.slice(from);
|
|
20644
21379
|
}
|
|
20645
21380
|
function isIdChar(ch) {
|
|
20646
21381
|
return ch >= "a" && ch <= "z" || ch >= "A" && ch <= "Z" || ch >= "0" && ch <= "9" || ch === "_" || ch === "-" || ch === "." || ch === ":";
|
|
@@ -20833,11 +21568,11 @@ var IdPool = class {
|
|
|
20833
21568
|
return candidate;
|
|
20834
21569
|
}
|
|
20835
21570
|
};
|
|
20836
|
-
function escapeLabel(
|
|
20837
|
-
return
|
|
21571
|
+
function escapeLabel(text2) {
|
|
21572
|
+
return text2.replace(/"/g, "#quot;");
|
|
20838
21573
|
}
|
|
20839
|
-
function truncate(
|
|
20840
|
-
const clean =
|
|
21574
|
+
function truncate(text2, max) {
|
|
21575
|
+
const clean = text2.replace(/\s+/g, " ").trim();
|
|
20841
21576
|
return clean.length <= max ? clean : `${clean.slice(0, max - 1)}\u2026`;
|
|
20842
21577
|
}
|
|
20843
21578
|
function nodeDecl(id, label, comp) {
|
|
@@ -21684,8 +22419,8 @@ function rewriteSpecRefs(raw, remap) {
|
|
|
21684
22419
|
}
|
|
21685
22420
|
function summarizeValue(value) {
|
|
21686
22421
|
if (value === void 0) return "";
|
|
21687
|
-
const
|
|
21688
|
-
return
|
|
22422
|
+
const text2 = typeof value === "string" ? value : JSON.stringify(value) ?? String(value);
|
|
22423
|
+
return text2.length > 120 ? `${text2.slice(0, 117)}...` : text2;
|
|
21689
22424
|
}
|
|
21690
22425
|
function identityKeyOf(field, item) {
|
|
21691
22426
|
if (item === null || typeof item !== "object") return null;
|
|
@@ -21760,6 +22495,9 @@ function specChanges(before, after, prefix = "") {
|
|
|
21760
22495
|
if (after.length === 0 && before.length > 0) {
|
|
21761
22496
|
return [{ path: prefix, change: "cleared", before: summarizeValue(before) }];
|
|
21762
22497
|
}
|
|
22498
|
+
if ((prefix.split(".").pop() ?? prefix) === "narrative" && [...before, ...after].every((i) => i !== null && typeof i === "object" && !Array.isArray(i))) {
|
|
22499
|
+
return narrativeChanges(before, after, prefix);
|
|
22500
|
+
}
|
|
21763
22501
|
const identified = [...before, ...after].every((i) => reportIdentityOf(prefix.split(".").pop() ?? prefix, i) !== null);
|
|
21764
22502
|
if (!identified) {
|
|
21765
22503
|
return JSON.stringify(before) === JSON.stringify(after) ? [] : [{ path: prefix, change: "set", before: summarizeValue(before), after: summarizeValue(after) }];
|
|
@@ -21793,6 +22531,152 @@ function specChanges(before, after, prefix = "") {
|
|
|
21793
22531
|
if (JSON.stringify(before) === JSON.stringify(after)) return [];
|
|
21794
22532
|
return [{ path: prefix, change: "set", before: summarizeValue(before), after: summarizeValue(after) }];
|
|
21795
22533
|
}
|
|
22534
|
+
var STEP_JUMP_FIELDS = ["onTrueStep", "onFalseStep", "defaultStep", "endStep", "finallyStep", "toStep"];
|
|
22535
|
+
var STEP_JUMP_LISTS = [["cases", "value"], ["catches", "error"], ["branches", "name"]];
|
|
22536
|
+
function jumpsOf(step) {
|
|
22537
|
+
const jumps = /* @__PURE__ */ new Map();
|
|
22538
|
+
for (const field of STEP_JUMP_FIELDS) {
|
|
22539
|
+
if (typeof step[field] === "number") jumps.set(field, step[field]);
|
|
22540
|
+
}
|
|
22541
|
+
for (const [list, name] of STEP_JUMP_LISTS) {
|
|
22542
|
+
const entries = step[list];
|
|
22543
|
+
if (!Array.isArray(entries)) continue;
|
|
22544
|
+
entries.forEach((entry, i) => {
|
|
22545
|
+
if (entry && typeof entry.step === "number") jumps.set(`${list}.${String(entry[name] ?? i)}.step`, entry.step);
|
|
22546
|
+
});
|
|
22547
|
+
}
|
|
22548
|
+
return jumps;
|
|
22549
|
+
}
|
|
22550
|
+
function withoutNumbers(step) {
|
|
22551
|
+
const out = { ...step };
|
|
22552
|
+
delete out.stepNumber;
|
|
22553
|
+
for (const field of STEP_JUMP_FIELDS) delete out[field];
|
|
22554
|
+
for (const [list] of STEP_JUMP_LISTS) {
|
|
22555
|
+
if (Array.isArray(out[list])) {
|
|
22556
|
+
out[list] = out[list].map((entry) => {
|
|
22557
|
+
if (!entry || typeof entry !== "object") return entry;
|
|
22558
|
+
const { step: _step, ...rest } = entry;
|
|
22559
|
+
return rest;
|
|
22560
|
+
});
|
|
22561
|
+
}
|
|
22562
|
+
}
|
|
22563
|
+
return out;
|
|
22564
|
+
}
|
|
22565
|
+
function canonicalJson(value) {
|
|
22566
|
+
return JSON.stringify(value, (_key, v) => v && typeof v === "object" && !Array.isArray(v) ? Object.fromEntries(Object.keys(v).sort().map((k) => [k, v[k]])) : v);
|
|
22567
|
+
}
|
|
22568
|
+
function commonRun(a, b, same) {
|
|
22569
|
+
const rows = a.length + 1;
|
|
22570
|
+
const cols = b.length + 1;
|
|
22571
|
+
const table = new Uint32Array(rows * cols);
|
|
22572
|
+
for (let i2 = a.length - 1; i2 >= 0; i2--) {
|
|
22573
|
+
for (let j2 = b.length - 1; j2 >= 0; j2--) {
|
|
22574
|
+
table[i2 * cols + j2] = same(a[i2], b[j2]) ? table[(i2 + 1) * cols + j2 + 1] + 1 : Math.max(table[(i2 + 1) * cols + j2], table[i2 * cols + j2 + 1]);
|
|
22575
|
+
}
|
|
22576
|
+
}
|
|
22577
|
+
const pairs = [];
|
|
22578
|
+
let i = 0;
|
|
22579
|
+
let j = 0;
|
|
22580
|
+
while (i < a.length && j < b.length) {
|
|
22581
|
+
if (same(a[i], b[j])) {
|
|
22582
|
+
pairs.push([i, j]);
|
|
22583
|
+
i++;
|
|
22584
|
+
j++;
|
|
22585
|
+
} else if (table[(i + 1) * cols + j] >= table[i * cols + j + 1]) i++;
|
|
22586
|
+
else j++;
|
|
22587
|
+
}
|
|
22588
|
+
return pairs;
|
|
22589
|
+
}
|
|
22590
|
+
function recognisablySame(a, b) {
|
|
22591
|
+
if (typeof a.description === "string" && a.description === b.description) return true;
|
|
22592
|
+
if (typeof a.label === "string" && a.label === b.label) return true;
|
|
22593
|
+
return typeof a.targetComponent === "string" && a.type === b.type && a.targetComponent === b.targetComponent && a.targetMethod === b.targetMethod;
|
|
22594
|
+
}
|
|
22595
|
+
function pairSteps(before, after) {
|
|
22596
|
+
const keysBefore = before.map((s) => canonicalJson(withoutNumbers(s)));
|
|
22597
|
+
const keysAfter = after.map((s) => canonicalJson(withoutNumbers(s)));
|
|
22598
|
+
const anchors = commonRun(keysBefore, keysAfter, (x, y) => x === y);
|
|
22599
|
+
const pairs = [];
|
|
22600
|
+
const bounds = [[-1, -1], ...anchors, [before.length, after.length]];
|
|
22601
|
+
for (let g = 0; g < bounds.length - 1; g++) {
|
|
22602
|
+
const [oi, ni] = bounds[g];
|
|
22603
|
+
const [oj, nj] = bounds[g + 1];
|
|
22604
|
+
const gapBefore = before.slice(oi + 1, oj).map((step, k) => ({ step, at: oi + 1 + k }));
|
|
22605
|
+
const gapAfter = after.slice(ni + 1, nj).map((step, k) => ({ step, at: ni + 1 + k }));
|
|
22606
|
+
const similar = commonRun(gapBefore, gapAfter, (x, y) => recognisablySame(x.step, y.step)).map(([x, y]) => [gapBefore[x].at, gapAfter[y].at]);
|
|
22607
|
+
const inner = [[oi, ni], ...similar, [oj, nj]];
|
|
22608
|
+
for (let k = 0; k < inner.length - 1; k++) {
|
|
22609
|
+
const [a0, b0] = inner[k];
|
|
22610
|
+
const [a1, b1] = inner[k + 1];
|
|
22611
|
+
if (a1 - a0 === b1 - b0) for (let d = 1; d < a1 - a0; d++) pairs.push([a0 + d, b0 + d]);
|
|
22612
|
+
if (k < inner.length - 2) pairs.push(inner[k + 1]);
|
|
22613
|
+
}
|
|
22614
|
+
if (g < bounds.length - 2) pairs.push(bounds[g + 1]);
|
|
22615
|
+
}
|
|
22616
|
+
return pairs.sort((x, y) => x[0] - y[0]);
|
|
22617
|
+
}
|
|
22618
|
+
function numberOf(step, index) {
|
|
22619
|
+
return typeof step.stepNumber === "number" ? step.stepNumber : index + 1;
|
|
22620
|
+
}
|
|
22621
|
+
function renumberedRuns(moves) {
|
|
22622
|
+
const runs = [];
|
|
22623
|
+
const range = (a, b) => a === b ? `step ${a}` : `steps ${a}-${b}`;
|
|
22624
|
+
let start = 0;
|
|
22625
|
+
for (let k = 1; k <= moves.length; k++) {
|
|
22626
|
+
const continues = k < moves.length && moves[k][0] === moves[k - 1][0] + 1 && moves[k][1] - moves[k][0] === moves[start][1] - moves[start][0];
|
|
22627
|
+
if (continues) continue;
|
|
22628
|
+
const [firstOld, firstNew] = moves[start];
|
|
22629
|
+
const [lastOld, lastNew] = moves[k - 1];
|
|
22630
|
+
runs.push({ from: range(firstOld, lastOld), to: range(firstNew, lastNew) });
|
|
22631
|
+
start = k;
|
|
22632
|
+
}
|
|
22633
|
+
return runs;
|
|
22634
|
+
}
|
|
22635
|
+
function narrativeChanges(before, after, prefix) {
|
|
22636
|
+
const pairs = pairSteps(before, after);
|
|
22637
|
+
const oldToNew = /* @__PURE__ */ new Map();
|
|
22638
|
+
for (const [o, n] of pairs) oldToNew.set(numberOf(before[o], o), numberOf(after[n], n));
|
|
22639
|
+
const pairedOld = new Set(pairs.map(([o]) => o));
|
|
22640
|
+
const pairedNew = new Set(pairs.map(([, n]) => n));
|
|
22641
|
+
const removed = before.flatMap((step, o) => pairedOld.has(o) ? [] : [{
|
|
22642
|
+
path: `${prefix}.step ${numberOf(step, o)}`,
|
|
22643
|
+
change: "removed",
|
|
22644
|
+
before: summarizeValue(step)
|
|
22645
|
+
}]);
|
|
22646
|
+
const added = after.flatMap((step, n) => pairedNew.has(n) ? [] : [{
|
|
22647
|
+
path: `${prefix}.step ${numberOf(step, n)}`,
|
|
22648
|
+
change: "added",
|
|
22649
|
+
after: summarizeValue(step)
|
|
22650
|
+
}]);
|
|
22651
|
+
const edited = [];
|
|
22652
|
+
const relocated = [];
|
|
22653
|
+
const moves = [];
|
|
22654
|
+
for (const [o, n] of pairs) {
|
|
22655
|
+
const oldNumber = numberOf(before[o], o);
|
|
22656
|
+
const newNumber = numberOf(after[n], n);
|
|
22657
|
+
const at = oldNumber === newNumber ? `${prefix}.step ${newNumber}` : `${prefix}.step ${newNumber} (was ${oldNumber})`;
|
|
22658
|
+
edited.push(...specChanges(withoutNumbers(before[o]), withoutNumbers(after[n]), at));
|
|
22659
|
+
const oldJumps = jumpsOf(before[o]);
|
|
22660
|
+
const newJumps = jumpsOf(after[n]);
|
|
22661
|
+
for (const field of /* @__PURE__ */ new Set([...oldJumps.keys(), ...newJumps.keys()])) {
|
|
22662
|
+
const was = oldJumps.get(field);
|
|
22663
|
+
const now = newJumps.get(field);
|
|
22664
|
+
if (was === now) continue;
|
|
22665
|
+
if (was === void 0) edited.push({ path: `${at}.${field}`, change: "added", after: String(now) });
|
|
22666
|
+
else if (now === void 0) edited.push({ path: `${at}.${field}`, change: "removed", before: String(was) });
|
|
22667
|
+
else if (oldToNew.get(was) === now) relocated.push({ path: `${at}.${field}`, change: "relocated", before: String(was), after: String(now) });
|
|
22668
|
+
else edited.push({ path: `${at}.${field}`, change: "set", before: String(was), after: String(now) });
|
|
22669
|
+
}
|
|
22670
|
+
if (oldNumber !== newNumber && !edited.some((c) => c.path.startsWith(`${at}.`))) moves.push([oldNumber, newNumber]);
|
|
22671
|
+
}
|
|
22672
|
+
const renumbered = renumberedRuns(moves).map(({ from, to }) => ({
|
|
22673
|
+
path: prefix,
|
|
22674
|
+
change: "renumbered",
|
|
22675
|
+
before: from,
|
|
22676
|
+
after: to
|
|
22677
|
+
}));
|
|
22678
|
+
return [...removed, ...added, ...edited, ...renumbered, ...relocated];
|
|
22679
|
+
}
|
|
21796
22680
|
var DELTA_VERBS = /* @__PURE__ */ new Set(["action", "remove", "captureJumps"]);
|
|
21797
22681
|
var JUMP_LABEL_TWINS = /* @__PURE__ */ new Set([
|
|
21798
22682
|
"onTrueLabel",
|
|
@@ -21850,13 +22734,13 @@ function ineffectiveDeltaPaths(delta, merged, stored) {
|
|
|
21850
22734
|
const isPlain = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
21851
22735
|
const same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
|
21852
22736
|
const at = (prefix, key) => prefix ? `${prefix}.${key}` : key;
|
|
21853
|
-
const say = (
|
|
21854
|
-
out.push(`${
|
|
22737
|
+
const say = (path31, why) => {
|
|
22738
|
+
out.push(`${path31} \u2014 ${why}`);
|
|
21855
22739
|
};
|
|
21856
|
-
const walkValue = (d, m, s,
|
|
22740
|
+
const walkValue = (d, m, s, path31, field) => {
|
|
21857
22741
|
if (Array.isArray(d)) {
|
|
21858
22742
|
if (!Array.isArray(m)) {
|
|
21859
|
-
say(
|
|
22743
|
+
say(path31, "the level does not have this field, so the write dropped it");
|
|
21860
22744
|
return;
|
|
21861
22745
|
}
|
|
21862
22746
|
if (field === "narrative" && d.some(carriesEditMarker)) return;
|
|
@@ -21864,32 +22748,32 @@ function ineffectiveDeltaPaths(delta, merged, stored) {
|
|
|
21864
22748
|
if (carriesEditMarker(element)) continue;
|
|
21865
22749
|
const key = reportIdentityOf(field, element);
|
|
21866
22750
|
if (key === null) {
|
|
21867
|
-
if (!same(m, d)) say(
|
|
21868
|
-
else if (same(s, d)) say(
|
|
22751
|
+
if (!same(m, d)) say(path31, "the level does not keep this value, so the write dropped it");
|
|
22752
|
+
else if (same(s, d)) say(path31, "the stored spec already held this value");
|
|
21869
22753
|
return;
|
|
21870
22754
|
}
|
|
21871
22755
|
const mMatch = m.find((i) => reportIdentityOf(field, i) === key);
|
|
21872
22756
|
const sMatch = Array.isArray(s) ? s.find((i) => reportIdentityOf(field, i) === key) : void 0;
|
|
21873
22757
|
if (mMatch === void 0) {
|
|
21874
|
-
say(at(
|
|
22758
|
+
say(at(path31, key), "the write did not store this element");
|
|
21875
22759
|
continue;
|
|
21876
22760
|
}
|
|
21877
|
-
walk2(element, mMatch, sMatch, at(
|
|
22761
|
+
walk2(element, mMatch, sMatch, at(path31, key), field);
|
|
21878
22762
|
}
|
|
21879
22763
|
return;
|
|
21880
22764
|
}
|
|
21881
22765
|
if (isPlain(d)) {
|
|
21882
22766
|
if (!isPlain(m)) {
|
|
21883
|
-
say(
|
|
22767
|
+
say(path31, "the level does not have this field, so the write dropped it");
|
|
21884
22768
|
return;
|
|
21885
22769
|
}
|
|
21886
|
-
walk2(d, m, s,
|
|
22770
|
+
walk2(d, m, s, path31, field);
|
|
21887
22771
|
return;
|
|
21888
22772
|
}
|
|
21889
|
-
if (!same(m, d)) say(
|
|
21890
|
-
else if (same(s, d)) say(
|
|
22773
|
+
if (!same(m, d)) say(path31, "the write did not store it \u2014 the level's schema does not keep this value");
|
|
22774
|
+
else if (same(s, d)) say(path31, "the stored spec already held this value");
|
|
21891
22775
|
};
|
|
21892
|
-
function walk2(d, m, s,
|
|
22776
|
+
function walk2(d, m, s, path31, container) {
|
|
21893
22777
|
for (const [key, value] of Object.entries(d)) {
|
|
21894
22778
|
if (DELTA_VERBS.has(key) || JUMP_LABEL_TWINS.has(key)) continue;
|
|
21895
22779
|
if (key === "label" && LABEL_TWIN_CONTAINERS.has(container)) continue;
|
|
@@ -21899,13 +22783,13 @@ function ineffectiveDeltaPaths(delta, merged, stored) {
|
|
|
21899
22783
|
if (typeof name !== "string") continue;
|
|
21900
22784
|
const had = isPlain(s) && s[name] !== void 0;
|
|
21901
22785
|
const still = isPlain(m) && m[name] !== void 0;
|
|
21902
|
-
if (!had) say(at(
|
|
21903
|
-
else if (still) say(at(
|
|
22786
|
+
if (!had) say(at(path31, name), "unset named a field the stored spec did not have");
|
|
22787
|
+
else if (still) say(at(path31, name), "unset did not remove it");
|
|
21904
22788
|
}
|
|
21905
22789
|
continue;
|
|
21906
22790
|
}
|
|
21907
22791
|
if (value === void 0 || value === null) continue;
|
|
21908
|
-
const here = at(
|
|
22792
|
+
const here = at(path31, key);
|
|
21909
22793
|
const mValue = isPlain(m) ? m[key] : void 0;
|
|
21910
22794
|
const sValue = isPlain(s) ? s[key] : void 0;
|
|
21911
22795
|
if (mValue === void 0) {
|
|
@@ -23756,14 +24640,14 @@ var SpecWorkspace = class {
|
|
|
23756
24640
|
- ${labelErrors.join("\n- ")}`);
|
|
23757
24641
|
}
|
|
23758
24642
|
}
|
|
23759
|
-
const
|
|
24643
|
+
const canonical2 = (spec) => {
|
|
23760
24644
|
const parsed = deltaSchema.safeParse(spec);
|
|
23761
24645
|
const value = { ...parsed.success ? parsed.data : spec };
|
|
23762
24646
|
delete value.updatedAt;
|
|
23763
24647
|
return value;
|
|
23764
24648
|
};
|
|
23765
|
-
const canonicalStored =
|
|
23766
|
-
const canonicalMerged =
|
|
24649
|
+
const canonicalStored = canonical2(storedBefore);
|
|
24650
|
+
const canonicalMerged = canonical2(mergedResult);
|
|
23767
24651
|
const changes = specChanges(canonicalStored, canonicalMerged);
|
|
23768
24652
|
const ineffective = ineffectiveDeltaPaths(
|
|
23769
24653
|
{ ...qualifiedDelta, ...unsetFields.length ? { unset: unsetFields } : {} },
|
|
@@ -23859,6 +24743,8 @@ var SpecWorkspace = class {
|
|
|
23859
24743
|
const plan = this.planMethodMove(source, to, methods, contracts);
|
|
23860
24744
|
const repointed = this.repointToNewHome(from, to, methods, plan);
|
|
23861
24745
|
const mentions = this.moveMentions(from, plan);
|
|
24746
|
+
const rename = moveRename(from, to, methods, plan);
|
|
24747
|
+
this.rekeyMoveAllows(plan, rename);
|
|
23862
24748
|
if (hooks) {
|
|
23863
24749
|
const verdict = judgeMoveResult(hooks, plan);
|
|
23864
24750
|
plan.notices.push(...verdict.notices);
|
|
@@ -23876,10 +24762,13 @@ var SpecWorkspace = class {
|
|
|
23876
24762
|
notices: plan.notices,
|
|
23877
24763
|
summary: refusedMoveSummary(from, to, methods, verdict.refusals, alternatives),
|
|
23878
24764
|
dryRun,
|
|
23879
|
-
mentions
|
|
24765
|
+
mentions,
|
|
24766
|
+
created: [],
|
|
24767
|
+
carried: []
|
|
23880
24768
|
};
|
|
23881
24769
|
}
|
|
23882
24770
|
}
|
|
24771
|
+
const carried = projectConfigRepository.rekeyCarried(rename, true);
|
|
23883
24772
|
if (dryRun) {
|
|
23884
24773
|
const edits2 = moveChangeReports([...plan.edits.values()], true);
|
|
23885
24774
|
return {
|
|
@@ -23892,12 +24781,16 @@ var SpecWorkspace = class {
|
|
|
23892
24781
|
refusals: [],
|
|
23893
24782
|
alternatives: [],
|
|
23894
24783
|
notices: plan.notices,
|
|
23895
|
-
summary: `Dry run: moving ${namedMethods(methods)} from "${from}" to "${to}" would touch ${edits2.length} spec${edits2.length === 1 ? "" : "s"} and re-point ${repointed} reference${repointed === 1 ? "" : "s"}. Nothing was written.`,
|
|
24784
|
+
summary: `Dry run: moving ${namedMethods(methods)} from "${from}" to "${to}" would touch ${edits2.length} spec${edits2.length === 1 ? "" : "s"}${createdClause(plan.created, "would create")} and re-point ${repointed} reference${repointed === 1 ? "" : "s"}${registerClause(carried, "would rekey")}. Nothing was written.`,
|
|
23896
24785
|
dryRun: true,
|
|
23897
|
-
mentions
|
|
24786
|
+
mentions,
|
|
24787
|
+
created: plan.created,
|
|
24788
|
+
carried
|
|
23898
24789
|
};
|
|
23899
24790
|
}
|
|
23900
|
-
const edits = this.writeMoveEdits([...plan.edits.values()])
|
|
24791
|
+
const edits = this.writeMoveEdits([...plan.edits.values()], () => {
|
|
24792
|
+
projectConfigRepository.rekeyCarried(rename);
|
|
24793
|
+
});
|
|
23901
24794
|
return {
|
|
23902
24795
|
moved: true,
|
|
23903
24796
|
from,
|
|
@@ -23908,11 +24801,49 @@ var SpecWorkspace = class {
|
|
|
23908
24801
|
refusals: [],
|
|
23909
24802
|
alternatives: [],
|
|
23910
24803
|
notices: plan.notices,
|
|
23911
|
-
summary: `Moved ${namedMethods(methods)} from "${from}" to "${to}": ${edits.length} spec${edits.length === 1 ? "" : "s"} written, ${repointed} reference${repointed === 1 ? "" : "s"} re-pointed${mentions.length ? `, ${mentions.length} still naming the old home in prose or on the wire` : ""}.`,
|
|
24804
|
+
summary: `Moved ${namedMethods(methods)} from "${from}" to "${to}": ${edits.length} spec${edits.length === 1 ? "" : "s"} written${createdClause(plan.created, "created")}, ${repointed} reference${repointed === 1 ? "" : "s"} re-pointed${registerClause(carried, "rekeyed")}${mentions.length ? `, ${mentions.length} still naming the old home in prose or on the wire` : ""}.`,
|
|
23912
24805
|
dryRun: false,
|
|
23913
|
-
mentions
|
|
24806
|
+
mentions,
|
|
24807
|
+
created: plan.created,
|
|
24808
|
+
carried
|
|
23914
24809
|
};
|
|
23915
24810
|
}
|
|
24811
|
+
/**
|
|
24812
|
+
* Rekey every lint allow keyed on a moved method, staging each spec it
|
|
24813
|
+
* changes like any other reference the move follows. An allow anchored at a
|
|
24814
|
+
* moved method on a spec the method left moves to the spec it arrived in; a
|
|
24815
|
+
* covered `<from>.<method>` unit anywhere names `<to>.<method>`. An allow
|
|
24816
|
+
* whose new home the plan does not stage stays where it is.
|
|
24817
|
+
*/
|
|
24818
|
+
rekeyMoveAllows(plan, rename) {
|
|
24819
|
+
const subjects = [...this.everySpecInTree()];
|
|
24820
|
+
for (const edit of plan.edits.values()) if (edit.before === null) subjects.push({ kind: edit.kind, id: edit.id, spec: edit.after });
|
|
24821
|
+
const stagedAfter = (kind, id) => plan.edits.get(`${kind}:${id}`)?.after;
|
|
24822
|
+
for (const { kind, id, spec } of subjects) {
|
|
24823
|
+
if (id.includes("::")) continue;
|
|
24824
|
+
const allows = spec?.lint?.allow ?? [];
|
|
24825
|
+
if (allows.length === 0) continue;
|
|
24826
|
+
const staged = plan.edits.get(`${kind}:${id}`);
|
|
24827
|
+
const subject = staged ? staged.after : cloneSpec(spec);
|
|
24828
|
+
const kept = [];
|
|
24829
|
+
let changed = false;
|
|
24830
|
+
for (const allow of subject.lint?.allow ?? []) {
|
|
24831
|
+
const next = rekeyAnchor(rename, { spec: id, ...allow.at !== void 0 ? { at: allow.at } : {}, ...allow.covers ? { covers: allow.covers } : {} });
|
|
24832
|
+
const rekeyed = { ...allow, ...next.at !== void 0 ? { at: next.at } : {}, ...next.covers ? { covers: next.covers } : {} };
|
|
24833
|
+
if (JSON.stringify(rekeyed) !== JSON.stringify(allow)) changed = true;
|
|
24834
|
+
const home = next.spec !== id ? stagedAfter(kind, next.spec) : void 0;
|
|
24835
|
+
if (home) {
|
|
24836
|
+
home.lint = { ...home.lint ?? {}, allow: [...home.lint?.allow ?? [], rekeyed] };
|
|
24837
|
+
changed = true;
|
|
24838
|
+
continue;
|
|
24839
|
+
}
|
|
24840
|
+
kept.push(rekeyed);
|
|
24841
|
+
}
|
|
24842
|
+
if (!changed) continue;
|
|
24843
|
+
subject.lint = { ...subject.lint, allow: kept };
|
|
24844
|
+
if (!staged) this.stageMoveEdit(plan, kind, id, spec, subject);
|
|
24845
|
+
}
|
|
24846
|
+
}
|
|
23916
24847
|
/**
|
|
23917
24848
|
* Why this move cannot be attempted at all, or null when the request is
|
|
23918
24849
|
* answerable. Every check here is about what the CALLER named, so its answer
|
|
@@ -23943,15 +24874,26 @@ var SpecWorkspace = class {
|
|
|
23943
24874
|
}
|
|
23944
24875
|
const receiving = this.moveTargetContract(to, ofTarget);
|
|
23945
24876
|
if (typeof receiving === "string") return receiving;
|
|
23946
|
-
const
|
|
23947
|
-
|
|
23948
|
-
|
|
24877
|
+
const contractId = receiving?.id ?? `i${to}`;
|
|
24878
|
+
const holder = contracts.find((i) => i.id === contractId);
|
|
24879
|
+
if (!receiving && holder) {
|
|
24880
|
+
return `"${to}" declares no contract, and the one the move would create for it, "${contractId}", is already the id of "${holder.component}"'s contract \u2014 rename that contract, or give "${to}" its own first.`;
|
|
24881
|
+
}
|
|
24882
|
+
const implementations = this.loadImplementationSpecs();
|
|
24883
|
+
const travelling = implementations.some((impl) => ofSource.some((i) => i.id === impl.contract) && impl.methods.some((m) => methods.includes(m.name)));
|
|
24884
|
+
const implId = `${to}_impl`;
|
|
24885
|
+
const implHolder = implementations.find((i) => i.id === implId);
|
|
24886
|
+
if (travelling && !implementations.some((i) => i.contract === contractId) && implHolder) {
|
|
24887
|
+
return `no implementation realizes "${contractId}", and the one the move would create for it, "${implId}", is already the id of the implementation of "${implHolder.contract}" \u2014 rename it, or realize "${contractId}" first.`;
|
|
23949
24888
|
}
|
|
23950
24889
|
return null;
|
|
23951
24890
|
}
|
|
23952
|
-
/**
|
|
24891
|
+
/**
|
|
24892
|
+
* The contract of `to` that receives the methods; null when it declares none
|
|
24893
|
+
* yet, so the move creates `i<to>`; or why the choice cannot be decided.
|
|
24894
|
+
*/
|
|
23953
24895
|
moveTargetContract(to, ofTarget) {
|
|
23954
|
-
if (ofTarget.length === 0) return
|
|
24896
|
+
if (ofTarget.length === 0) return null;
|
|
23955
24897
|
if (ofTarget.length === 1) return ofTarget[0];
|
|
23956
24898
|
const named = ofTarget.find((i) => i.id === `i${to}` || i.id.endsWith(`::i${to}`));
|
|
23957
24899
|
return named ?? `"${to}" declares ${ofTarget.length} contracts and none is named "i${to}", so which one receives the methods is not decidable.`;
|
|
@@ -23964,7 +24906,7 @@ var SpecWorkspace = class {
|
|
|
23964
24906
|
*/
|
|
23965
24907
|
planMethodMove(source, to, methods, contracts) {
|
|
23966
24908
|
const target = this.loadComponentSpec(to);
|
|
23967
|
-
const
|
|
24909
|
+
const stated = this.moveTargetContract(to, contracts.filter((i) => i.component === to));
|
|
23968
24910
|
const implementations = this.loadImplementationSpecs();
|
|
23969
24911
|
const plan = {
|
|
23970
24912
|
edits: /* @__PURE__ */ new Map(),
|
|
@@ -23973,10 +24915,15 @@ var SpecWorkspace = class {
|
|
|
23973
24915
|
reach: [],
|
|
23974
24916
|
newDependencies: [],
|
|
23975
24917
|
wireBound: [],
|
|
23976
|
-
notices: []
|
|
24918
|
+
notices: [],
|
|
24919
|
+
created: [],
|
|
24920
|
+
rehomed: []
|
|
23977
24921
|
};
|
|
24922
|
+
const leftImplementations = [];
|
|
24923
|
+
const receiving = stated ?? createdContract(target, source.id, methods, contracts);
|
|
23978
24924
|
const arriving = [];
|
|
23979
24925
|
const travelling = [];
|
|
24926
|
+
const realizedIn = /* @__PURE__ */ new Map();
|
|
23980
24927
|
for (const contract of contracts.filter((i) => i.component === source.id)) {
|
|
23981
24928
|
const leaving = contract.methods.filter((m) => methods.includes(m.name));
|
|
23982
24929
|
if (leaving.length === 0) continue;
|
|
@@ -23984,6 +24931,7 @@ var SpecWorkspace = class {
|
|
|
23984
24931
|
const remaining = cloneSpec(contract);
|
|
23985
24932
|
remaining.methods = remaining.methods.filter((m) => !methods.includes(m.name));
|
|
23986
24933
|
this.stageMoveEdit(plan, "interface", contract.id, contract, remaining);
|
|
24934
|
+
plan.rehomed.push({ from: contract.id, to: receiving.id });
|
|
23987
24935
|
if (contract.methods.length === leaving.length) {
|
|
23988
24936
|
plan.notices.push(`"${contract.id}" is left with no methods \u2014 "${source.id}" no longer declares anything on it.`);
|
|
23989
24937
|
}
|
|
@@ -23991,17 +24939,20 @@ var SpecWorkspace = class {
|
|
|
23991
24939
|
const moving = impl.methods.filter((m) => methods.includes(m.name));
|
|
23992
24940
|
if (moving.length === 0) continue;
|
|
23993
24941
|
travelling.push(...cloneSpec(moving));
|
|
24942
|
+
for (const entry of moving) if (impl.sourcePath) realizedIn.set(entry.name, impl.sourcePath);
|
|
23994
24943
|
const kept = cloneSpec(impl);
|
|
23995
24944
|
kept.methods = kept.methods.filter((m) => !methods.includes(m.name));
|
|
23996
24945
|
this.stageMoveEdit(plan, "implementation", impl.id, impl, kept);
|
|
24946
|
+
leftImplementations.push(impl.id);
|
|
23997
24947
|
}
|
|
23998
24948
|
}
|
|
23999
|
-
this.stageMoveEdit(plan, "interface", receiving.id,
|
|
24949
|
+
this.stageMoveEdit(plan, "interface", receiving.id, stated, {
|
|
24000
24950
|
...cloneSpec(receiving),
|
|
24001
24951
|
methods: [...cloneSpec(receiving.methods), ...arriving]
|
|
24002
24952
|
});
|
|
24953
|
+
if (!stated) plan.created.push(`interface "${receiving.id}"`);
|
|
24003
24954
|
if (arriving.some((m) => m.endpoint)) plan.wireBound.push(receiving.id);
|
|
24004
|
-
if (receiving.status !== void 0 && receiving.status !== statusOfContracts(contracts, source.id)) {
|
|
24955
|
+
if (stated && receiving.status !== void 0 && receiving.status !== statusOfContracts(contracts, source.id)) {
|
|
24005
24956
|
plan.notices.push(`"${receiving.id}" is ${receiving.status} where the methods came from a ${statusOfContracts(contracts, source.id)} contract \u2014 the status of a moved method is the status of its new home.`);
|
|
24006
24957
|
}
|
|
24007
24958
|
for (const entry of travelling) {
|
|
@@ -24011,10 +24962,23 @@ var SpecWorkspace = class {
|
|
|
24011
24962
|
}
|
|
24012
24963
|
const targetImpl = implementations.find((i) => i.contract === receiving.id);
|
|
24013
24964
|
if (travelling.length > 0 && targetImpl) {
|
|
24965
|
+
for (const entry of travelling) {
|
|
24966
|
+
const file = realizedIn.get(entry.name);
|
|
24967
|
+
if (entry.sourcePath || !file || file === targetImpl.sourcePath) continue;
|
|
24968
|
+
entry.sourcePath = file;
|
|
24969
|
+
plan.notices.push(`"${entry.name}" keeps ${file} as its own sourcePath: it was realized there by default, and "${targetImpl.id}" names ${targetImpl.sourcePath ? targetImpl.sourcePath : "no file"} \u2014 moving a method is not moving its code.`);
|
|
24970
|
+
}
|
|
24014
24971
|
this.stageMoveEdit(plan, "implementation", targetImpl.id, targetImpl, {
|
|
24015
24972
|
...cloneSpec(targetImpl),
|
|
24016
24973
|
methods: [...cloneSpec(targetImpl.methods), ...travelling]
|
|
24017
24974
|
});
|
|
24975
|
+
for (const id of leftImplementations) plan.rehomed.push({ from: id, to: targetImpl.id });
|
|
24976
|
+
} else if (travelling.length > 0) {
|
|
24977
|
+
const created = createdImplementation(target, receiving.id, source.id, travelling, implementations, contracts);
|
|
24978
|
+
this.stageMoveEdit(plan, "implementation", created.spec.id, null, created.spec);
|
|
24979
|
+
for (const id of leftImplementations) plan.rehomed.push({ from: id, to: created.spec.id });
|
|
24980
|
+
plan.created.push(created.named);
|
|
24981
|
+
plan.notices.push(...created.notices);
|
|
24018
24982
|
}
|
|
24019
24983
|
plan.reach = reachOf(travelling, source.id, new Set(methods)).filter((id) => id !== to);
|
|
24020
24984
|
const held = /* @__PURE__ */ new Set([...target.dependsOn ?? [], ...target.owns ?? []]);
|
|
@@ -24058,6 +25022,9 @@ var SpecWorkspace = class {
|
|
|
24058
25022
|
}
|
|
24059
25023
|
this.stageMoveEdit(plan, kind, id, spec, subject);
|
|
24060
25024
|
}
|
|
25025
|
+
for (const edit of plan.edits.values()) {
|
|
25026
|
+
if (edit.before === null) rewriteSpecRefs(edit.after, remap);
|
|
25027
|
+
}
|
|
24061
25028
|
plan.notices.push(...this.callersOwingADependency(to, plan));
|
|
24062
25029
|
return repointed;
|
|
24063
25030
|
}
|
|
@@ -24139,7 +25106,7 @@ var SpecWorkspace = class {
|
|
|
24139
25106
|
* here: every save does it already, and a second call would only be a hop
|
|
24140
25107
|
* across a component boundary that no narrative claims.
|
|
24141
25108
|
*/
|
|
24142
|
-
writeMoveEdits(edits) {
|
|
25109
|
+
writeMoveEdits(edits, finish) {
|
|
24143
25110
|
const reports = moveChangeReports(edits, false);
|
|
24144
25111
|
const written = [];
|
|
24145
25112
|
try {
|
|
@@ -24148,10 +25115,12 @@ var SpecWorkspace = class {
|
|
|
24148
25115
|
report.notices.push(...this.saveMovedSpec(edit.kind, edit.after));
|
|
24149
25116
|
written.push(edit);
|
|
24150
25117
|
}
|
|
25118
|
+
finish?.();
|
|
24151
25119
|
} catch (e) {
|
|
24152
25120
|
for (const edit of [...written].reverse()) {
|
|
24153
25121
|
try {
|
|
24154
|
-
this.
|
|
25122
|
+
if (edit.before === null) this.delete(edit.kind, edit.id);
|
|
25123
|
+
else this.saveMovedSpec(edit.kind, edit.before);
|
|
24155
25124
|
} catch {
|
|
24156
25125
|
}
|
|
24157
25126
|
}
|
|
@@ -24241,7 +25210,7 @@ function weighMoveHome(hooks, candidate, reach, dependencyLimit) {
|
|
|
24241
25210
|
};
|
|
24242
25211
|
}
|
|
24243
25212
|
function moveChangeReports(edits, dryRun) {
|
|
24244
|
-
return edits.map((edit) => ({ edit, changes: specChanges(edit.before, edit.after) })).filter(({ changes }) => changes.length > 0).map(({ edit, changes }) => ({
|
|
25213
|
+
return edits.map((edit) => ({ edit, changes: specChanges(edit.before ?? {}, edit.after) })).filter(({ changes }) => changes.length > 0).map(({ edit, changes }) => ({
|
|
24245
25214
|
kind: edit.kind,
|
|
24246
25215
|
id: edit.id,
|
|
24247
25216
|
written: !dryRun,
|
|
@@ -24250,9 +25219,66 @@ function moveChangeReports(edits, dryRun) {
|
|
|
24250
25219
|
ineffective: [],
|
|
24251
25220
|
notices: [],
|
|
24252
25221
|
testsToRevisit: [],
|
|
24253
|
-
summary: `${dryRun ? "Would move" : "Moved"} into ${edit.kind} "${edit.id}": ${changes.length} change${changes.length === 1 ? "" : "s"}.`
|
|
25222
|
+
summary: edit.before === null ? `${dryRun ? "Would create" : "Created"} ${edit.kind} "${edit.id}" to receive the moved methods.` : `${dryRun ? "Would move" : "Moved"} into ${edit.kind} "${edit.id}": ${changes.length} change${changes.length === 1 ? "" : "s"}.`
|
|
24254
25223
|
}));
|
|
24255
25224
|
}
|
|
25225
|
+
function moveRename(from, to, methods, plan) {
|
|
25226
|
+
return {
|
|
25227
|
+
specs: plan.rehomed.map(({ from: left, to: arrived }) => ({ from: left, to: arrived, sites: [...methods] })),
|
|
25228
|
+
methods: methods.map((method) => ({ component: from, method, toComponent: to, toMethod: method }))
|
|
25229
|
+
};
|
|
25230
|
+
}
|
|
25231
|
+
function registerClause(carried, verb) {
|
|
25232
|
+
return carried.length ? `, ${verb} ${carried.length} debt-register key${carried.length === 1 ? "" : "s"}` : "";
|
|
25233
|
+
}
|
|
25234
|
+
function createdClause(created, verb) {
|
|
25235
|
+
return created.length ? `, ${verb} ${created.join(" and ")}` : "";
|
|
25236
|
+
}
|
|
25237
|
+
function createdContract(target, from, methods, contracts) {
|
|
25238
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
25239
|
+
const status = statusOfContracts(contracts, from);
|
|
25240
|
+
return {
|
|
25241
|
+
id: `i${target.id}`,
|
|
25242
|
+
name: `${target.name} Interface`,
|
|
25243
|
+
description: `The contract of ${target.name}, created by moving ${namedMethods(methods)} here from "${from}".`,
|
|
25244
|
+
component: target.id,
|
|
25245
|
+
methods: [],
|
|
25246
|
+
...status ? { status } : {},
|
|
25247
|
+
createdAt: now,
|
|
25248
|
+
updatedAt: now
|
|
25249
|
+
};
|
|
25250
|
+
}
|
|
25251
|
+
function createdImplementation(target, contractId, from, travelling, implementations, contracts) {
|
|
25252
|
+
const ofSource = new Set(contracts.filter((i) => i.component === from).map((i) => i.id));
|
|
25253
|
+
const leaving = implementations.filter((impl) => ofSource.has(impl.contract) && impl.methods.some((m) => travelling.some((t) => t.name === m.name)));
|
|
25254
|
+
const paths = [...new Set(leaving.map((impl) => impl.sourcePath))];
|
|
25255
|
+
const inherited = paths.length === 1 ? paths[0] : void 0;
|
|
25256
|
+
const notices = [];
|
|
25257
|
+
if (paths.length > 1) {
|
|
25258
|
+
for (const entry of travelling) {
|
|
25259
|
+
if (entry.sourcePath) continue;
|
|
25260
|
+
const home = leaving.find((impl) => impl.methods.some((m) => m.name === entry.name));
|
|
25261
|
+
if (home?.sourcePath) entry.sourcePath = home.sourcePath;
|
|
25262
|
+
}
|
|
25263
|
+
notices.push(`"${target.id}_impl" is created without a sourcePath: the moved entries come from implementations with different ones, so each keeps the file it was realized in as its own sourcePath.`);
|
|
25264
|
+
}
|
|
25265
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
25266
|
+
const status = leaving[0]?.status;
|
|
25267
|
+
const spec = {
|
|
25268
|
+
id: `${target.id}_impl`,
|
|
25269
|
+
name: `${target.name} Implementation`,
|
|
25270
|
+
description: `The implementation of ${target.name}, created by moving methods here from "${from}".`,
|
|
25271
|
+
contract: contractId,
|
|
25272
|
+
...inherited ? { sourcePath: inherited } : {},
|
|
25273
|
+
methods: travelling,
|
|
25274
|
+
...status ? { status } : {},
|
|
25275
|
+
createdAt: now,
|
|
25276
|
+
updatedAt: now
|
|
25277
|
+
};
|
|
25278
|
+
const origin = leaving.length === 1 ? ` from "${leaving[0].id}"` : "";
|
|
25279
|
+
const named = inherited ? `implementation "${spec.id}" (sourcePath ${inherited}, inherited${origin})` : `implementation "${spec.id}" (no sourcePath)`;
|
|
25280
|
+
return { spec, named, notices };
|
|
25281
|
+
}
|
|
24256
25282
|
function refusedMoveSummary(from, to, methods, refusals, alternatives) {
|
|
24257
25283
|
const cheapest = alternatives.find((c) => c.legal);
|
|
24258
25284
|
return `Refused: moving ${namedMethods(methods)} from "${from}" to "${to}" is refused by ${refusals.join(", ")}. Nothing was written. ` + (cheapest ? `The cheapest legal home is "${cheapest.component}" (${cheapest.newDependencies.length} new dependenc${cheapest.newDependencies.length === 1 ? "y" : "ies"}, ${cheapest.dependencyCount} of ${cheapest.dependencyLimit}).` : `No component of this subsystem can take them as they stand \u2014 ${alternatives.length} were weighed.`);
|
|
@@ -24894,7 +25920,8 @@ function rewriteRefFields(specsDir, remap, excludeDir) {
|
|
|
24894
25920
|
}
|
|
24895
25921
|
return rewritten;
|
|
24896
25922
|
}
|
|
24897
|
-
function
|
|
25923
|
+
function rekeyLintAllows(specsDir, rename) {
|
|
25924
|
+
const rewritten = [];
|
|
24898
25925
|
for (const file of listFilesRecursive(specsDir, ".yaml")) {
|
|
24899
25926
|
let raw;
|
|
24900
25927
|
try {
|
|
@@ -24902,13 +25929,48 @@ function rebaseImplementationPaths(specsDir, fromRoot, toRoot) {
|
|
|
24902
25929
|
} catch {
|
|
24903
25930
|
continue;
|
|
24904
25931
|
}
|
|
24905
|
-
|
|
25932
|
+
const allows = raw?.lint?.allow;
|
|
25933
|
+
const kind = raw && typeof raw === "object" ? specKind(raw) : null;
|
|
25934
|
+
if (!kind || !Array.isArray(allows)) continue;
|
|
25935
|
+
const specId = kind === "system" ? "system" : String(raw.id);
|
|
24906
25936
|
let changed = false;
|
|
24907
|
-
const
|
|
24908
|
-
|
|
24909
|
-
|
|
24910
|
-
|
|
24911
|
-
|
|
25937
|
+
for (const allow of allows) {
|
|
25938
|
+
if (!allow || typeof allow !== "object") continue;
|
|
25939
|
+
const next = rekeyAnchor(rename, {
|
|
25940
|
+
spec: specId,
|
|
25941
|
+
...typeof allow.at === "string" ? { at: allow.at } : {},
|
|
25942
|
+
...Array.isArray(allow.covers) ? { covers: allow.covers } : {}
|
|
25943
|
+
});
|
|
25944
|
+
if (next.at !== void 0 && next.at !== allow.at) {
|
|
25945
|
+
allow.at = next.at;
|
|
25946
|
+
changed = true;
|
|
25947
|
+
}
|
|
25948
|
+
if (next.covers && JSON.stringify(next.covers) !== JSON.stringify(allow.covers)) {
|
|
25949
|
+
allow.covers = next.covers;
|
|
25950
|
+
changed = true;
|
|
25951
|
+
}
|
|
25952
|
+
}
|
|
25953
|
+
if (!changed) continue;
|
|
25954
|
+
writeYamlFile(file, raw);
|
|
25955
|
+
rewritten.push({ kind, id: specId });
|
|
25956
|
+
}
|
|
25957
|
+
return rewritten;
|
|
25958
|
+
}
|
|
25959
|
+
function rebaseImplementationPaths(specsDir, fromRoot, toRoot) {
|
|
25960
|
+
for (const file of listFilesRecursive(specsDir, ".yaml")) {
|
|
25961
|
+
let raw;
|
|
25962
|
+
try {
|
|
25963
|
+
raw = readYamlFile(file);
|
|
25964
|
+
} catch {
|
|
25965
|
+
continue;
|
|
25966
|
+
}
|
|
25967
|
+
if (!raw || typeof raw !== "object" || !("contract" in raw)) continue;
|
|
25968
|
+
let changed = false;
|
|
25969
|
+
const rebase = (holder, key) => {
|
|
25970
|
+
const p = holder[key];
|
|
25971
|
+
if (typeof p !== "string" || p === "" || path17.isAbsolute(p)) return;
|
|
25972
|
+
const next = toPosixPath(path17.relative(toRoot, path17.resolve(fromRoot, p)));
|
|
25973
|
+
if (next !== p) {
|
|
24912
25974
|
holder[key] = next;
|
|
24913
25975
|
changed = true;
|
|
24914
25976
|
}
|
|
@@ -24963,24 +26025,32 @@ function renameComponent(componentId, newId) {
|
|
|
24963
26025
|
...movingInterface ? [{ kind: "interface", from: movingInterface.id, to: interfaceId }] : [],
|
|
24964
26026
|
...movingImplementation ? [{ kind: "implementation", from: movingImplementation.id, to: implementationId }] : []
|
|
24965
26027
|
];
|
|
24966
|
-
const
|
|
26028
|
+
const rename = {
|
|
26029
|
+
specs: renamed.map(({ from, to }) => ({ from, to })),
|
|
26030
|
+
components: [{ from: componentId, to: newId }]
|
|
26031
|
+
};
|
|
26032
|
+
projectConfigRepository.rekeyCarried(rename, true);
|
|
26033
|
+
const rewrittenRefs = rewriteRefFields(aiPathsAt(getProjectRoot()).specsDir(), (ref, position) => {
|
|
24967
26034
|
if (position === "component" || position === "entity-class" || position === "auth-source") {
|
|
24968
26035
|
return ref === componentId ? newId : ref;
|
|
24969
26036
|
}
|
|
24970
26037
|
if (position === "interface" && ref === movingInterface?.id) return interfaceId;
|
|
24971
26038
|
return ref;
|
|
24972
|
-
})
|
|
26039
|
+
});
|
|
26040
|
+
const allowsRekeyed = rekeyLintAllows(aiPathsAt(getProjectRoot()).specsDir(), rename);
|
|
26041
|
+
const rewritten = [...new Set([...rewrittenRefs, ...allowsRekeyed].filter((spec) => !renamed.some((moved) => moved.kind === spec.kind && moved.from === spec.id)).map((spec) => spec.id))];
|
|
24973
26042
|
invalidateSpecCache();
|
|
24974
26043
|
moveRenamedSpecs(componentId, newId, component, movingInterface, movingImplementation);
|
|
24975
|
-
|
|
26044
|
+
const carried = projectConfigRepository.rekeyCarried(rename);
|
|
26045
|
+
return { renamed, rewritten, carried };
|
|
24976
26046
|
}
|
|
24977
26047
|
function moveRenamedSpecs(componentId, newId, component, movingInterface, movingImplementation) {
|
|
24978
26048
|
const interfaceId = `i${newId}`;
|
|
24979
26049
|
const implementationId = `${newId}_impl`;
|
|
24980
|
-
const
|
|
26050
|
+
const movedComponent2 = loadComponentSpec(componentId) ?? component;
|
|
24981
26051
|
const movedInterface = movingInterface ? loadInterfaceSpec(movingInterface.id) ?? movingInterface : null;
|
|
24982
26052
|
const movedImplementation = movingImplementation ? loadImplementationSpec(movingImplementation.id) ?? movingImplementation : null;
|
|
24983
|
-
saveComponentSpec({ ...
|
|
26053
|
+
saveComponentSpec({ ...movedComponent2, id: newId });
|
|
24984
26054
|
if (movedInterface) saveInterfaceSpec({ ...movedInterface, id: interfaceId, component: newId });
|
|
24985
26055
|
if (movedImplementation) {
|
|
24986
26056
|
const oldFile = getImplementationPath(movedImplementation.id);
|
|
@@ -25045,6 +26115,19 @@ function renameMethod(componentId, methodName, newName, pinSymbol) {
|
|
|
25045
26115
|
const implementations = loadImplementationSpecs();
|
|
25046
26116
|
const movingContracts = new Set(moving.map((i) => i.id));
|
|
25047
26117
|
const renamed = [];
|
|
26118
|
+
const rename = {
|
|
26119
|
+
methods: [{
|
|
26120
|
+
component: componentId,
|
|
26121
|
+
method: methodName,
|
|
26122
|
+
toComponent: componentId,
|
|
26123
|
+
toMethod: newName,
|
|
26124
|
+
specs: [
|
|
26125
|
+
...moving.map((i) => i.id),
|
|
26126
|
+
...implementations.filter((impl) => movingContracts.has(impl.contract) && impl.methods.some((m) => m.name === methodName)).map((impl) => impl.id)
|
|
26127
|
+
]
|
|
26128
|
+
}]
|
|
26129
|
+
};
|
|
26130
|
+
projectConfigRepository.rekeyCarried(rename, true);
|
|
25048
26131
|
for (const contract of moving) {
|
|
25049
26132
|
saveInterfaceSpec({
|
|
25050
26133
|
...contract,
|
|
@@ -25067,8 +26150,11 @@ function renameMethod(componentId, methodName, newName, pinSymbol) {
|
|
|
25067
26150
|
}
|
|
25068
26151
|
invalidateSpecCache();
|
|
25069
26152
|
const specsDir = aiPathsAt(getProjectRoot()).specsDir();
|
|
25070
|
-
const
|
|
26153
|
+
const retargeted = rewriteRefFields(specsDir, (ref, position, owner) => position === "method" && owner === componentId && ref === methodName ? newName : ref);
|
|
26154
|
+
const allowsRekeyed = rekeyLintAllows(specsDir, rename);
|
|
26155
|
+
const rewritten = [...new Set([...retargeted, ...allowsRekeyed].map((spec) => spec.id))];
|
|
25071
26156
|
const mentions = collectMentions(specsDir, methodName, moving);
|
|
26157
|
+
const carried = projectConfigRepository.rekeyCarried(rename);
|
|
25072
26158
|
return {
|
|
25073
26159
|
component: componentId,
|
|
25074
26160
|
from: methodName,
|
|
@@ -25076,7 +26162,8 @@ function renameMethod(componentId, methodName, newName, pinSymbol) {
|
|
|
25076
26162
|
renamed,
|
|
25077
26163
|
rewritten,
|
|
25078
26164
|
mentions,
|
|
25079
|
-
...pinnedSymbol ? { pinnedSymbol } : {}
|
|
26165
|
+
...pinnedSymbol ? { pinnedSymbol } : {},
|
|
26166
|
+
carried
|
|
25080
26167
|
};
|
|
25081
26168
|
}
|
|
25082
26169
|
function renameInSignature(signature, from, to) {
|
|
@@ -25727,8 +26814,8 @@ function inferSourcePathForComponent(comp, subsystems) {
|
|
|
25727
26814
|
return null;
|
|
25728
26815
|
}
|
|
25729
26816
|
}
|
|
25730
|
-
function
|
|
25731
|
-
const clean =
|
|
26817
|
+
function summarize2(text2, max = 140) {
|
|
26818
|
+
const clean = text2.replace(/\s+/g, " ").trim();
|
|
25732
26819
|
const period = clean.indexOf(". ");
|
|
25733
26820
|
const firstSentence = period > 0 ? clean.slice(0, period + 1) : clean;
|
|
25734
26821
|
if (firstSentence.length <= max) return firstSentence;
|
|
@@ -25754,7 +26841,7 @@ function resolveAgentTopology() {
|
|
|
25754
26841
|
agents.push({
|
|
25755
26842
|
id: "system-architect",
|
|
25756
26843
|
name: `${system.name} Architect`,
|
|
25757
|
-
description: `Global architect for ${system.name} \u2014 owns the spec tree and topology. ${
|
|
26844
|
+
description: `Global architect for ${system.name} \u2014 owns the spec tree and topology. ${summarize2(system.vision)}`,
|
|
25758
26845
|
template: "architect",
|
|
25759
26846
|
creationReason: "Automatically inferred from L0 system spec",
|
|
25760
26847
|
ownedPaths: [".wai/specs/**"],
|
|
@@ -25834,7 +26921,7 @@ function resolveAgentTopology() {
|
|
|
25834
26921
|
agents.push({
|
|
25835
26922
|
id: `${sub.id}-owner`,
|
|
25836
26923
|
name: `${sub.name} Owner`,
|
|
25837
|
-
description: `Owns the ${sub.id} subsystem. ${
|
|
26924
|
+
description: `Owns the ${sub.id} subsystem. ${summarize2(sub.description)}`,
|
|
25838
26925
|
template: "domain-owner",
|
|
25839
26926
|
creationReason: `Automatically inferred from L1 subsystem spec: ${sub.id}`,
|
|
25840
26927
|
domainRoot: sub.id,
|
|
@@ -25899,7 +26986,7 @@ function resolveAgentTopology() {
|
|
|
25899
26986
|
agents.push({
|
|
25900
26987
|
id: `${dom.id}-owner`,
|
|
25901
26988
|
name: `${dom.name ?? dom.id} Owner`,
|
|
25902
|
-
description: dom.description ?
|
|
26989
|
+
description: dom.description ? summarize2(dom.description) : `Owner agent for the free-standing "${dom.id}" domain.`,
|
|
25903
26990
|
template: "domain-owner",
|
|
25904
26991
|
creationReason: "Inferred from a free-standing domain in .wai/topology.yaml",
|
|
25905
26992
|
domainRoot: dom.id,
|
|
@@ -26243,19 +27330,19 @@ function commitStagedTree(stagingDir, destDir, roots, backupExisting) {
|
|
|
26243
27330
|
const backupDir = path26.join(root, BACKUP_DIR, timestampSlug());
|
|
26244
27331
|
let backedUp = false;
|
|
26245
27332
|
for (const rootRel of roots) {
|
|
26246
|
-
const
|
|
26247
|
-
const stagedSpecDir = path26.join(stagingDir,
|
|
27333
|
+
const relative10 = rootRel === "." ? "" : rootRel;
|
|
27334
|
+
const stagedSpecDir = path26.join(stagingDir, relative10, ".wai");
|
|
26248
27335
|
if (!fs16.existsSync(stagedSpecDir)) continue;
|
|
26249
|
-
const liveSpecDir = liveSpecDirFor(path26.join(root,
|
|
27336
|
+
const liveSpecDir = liveSpecDirFor(path26.join(root, relative10));
|
|
26250
27337
|
if (backupExisting && liveSpecDir && fs16.existsSync(liveSpecDir)) {
|
|
26251
|
-
const backupTarget = path26.join(backupDir,
|
|
27338
|
+
const backupTarget = path26.join(backupDir, relative10, path26.basename(liveSpecDir));
|
|
26252
27339
|
fs16.mkdirSync(path26.dirname(backupTarget), { recursive: true });
|
|
26253
27340
|
fs16.renameSync(liveSpecDir, backupTarget);
|
|
26254
27341
|
backedUp = true;
|
|
26255
27342
|
} else if (liveSpecDir && fs16.existsSync(liveSpecDir)) {
|
|
26256
27343
|
fs16.rmSync(liveSpecDir, { recursive: true, force: true });
|
|
26257
27344
|
}
|
|
26258
|
-
const target = path26.join(root,
|
|
27345
|
+
const target = path26.join(root, relative10, ".wai");
|
|
26259
27346
|
fs16.mkdirSync(path26.dirname(target), { recursive: true });
|
|
26260
27347
|
if (fs16.existsSync(target)) fs16.rmSync(target, { recursive: true, force: true });
|
|
26261
27348
|
fs16.renameSync(stagedSpecDir, target);
|
|
@@ -26960,13 +28047,13 @@ function syncContextFiles() {
|
|
|
26960
28047
|
}
|
|
26961
28048
|
|
|
26962
28049
|
// src/core/status.ts
|
|
26963
|
-
var asWritten = (
|
|
28050
|
+
var asWritten = (text2) => text2;
|
|
26964
28051
|
function fillDecor(decor) {
|
|
26965
28052
|
return {
|
|
26966
28053
|
structure: decor?.structure ?? asWritten,
|
|
26967
28054
|
emphasis: decor?.emphasis ?? asWritten,
|
|
26968
|
-
layer: decor?.layer ?? ((_kind,
|
|
26969
|
-
score: decor?.score ?? ((_pct,
|
|
28055
|
+
layer: decor?.layer ?? ((_kind, text2) => text2),
|
|
28056
|
+
score: decor?.score ?? ((_pct, text2) => text2),
|
|
26970
28057
|
draft: decor?.draft ?? asWritten,
|
|
26971
28058
|
present: decor?.present ?? asWritten,
|
|
26972
28059
|
missing: decor?.missing ?? asWritten
|
|
@@ -26978,13 +28065,13 @@ function getStatusReport(options = {}, decor) {
|
|
|
26978
28065
|
const system = loadSystemSpec();
|
|
26979
28066
|
const loaderErrors = getLoaderIssues();
|
|
26980
28067
|
if (loaderErrors.length > 0) {
|
|
26981
|
-
let
|
|
28068
|
+
let errText2 = "Failed to parse specification files:\n";
|
|
26982
28069
|
for (const issue2 of loaderErrors) {
|
|
26983
28070
|
const prefix = issue2.specId ? mark.structure(`[${issue2.specId}] `) : "";
|
|
26984
|
-
|
|
28071
|
+
errText2 += `${prefix}[${issue2.code}] ${issue2.message}
|
|
26985
28072
|
`;
|
|
26986
28073
|
}
|
|
26987
|
-
return { text:
|
|
28074
|
+
return { text: errText2, failed: true };
|
|
26988
28075
|
}
|
|
26989
28076
|
if (!system) {
|
|
26990
28077
|
return { text: "L0 System specification (system.yaml) is missing.", failed: true };
|
|
@@ -27178,17 +28265,2326 @@ var logger = {
|
|
|
27178
28265
|
}
|
|
27179
28266
|
};
|
|
27180
28267
|
|
|
28268
|
+
// src/core/skills.ts
|
|
28269
|
+
var path29 = __toESM(require("path"));
|
|
28270
|
+
var fs18 = __toESM(require("fs"));
|
|
28271
|
+
|
|
28272
|
+
// src/core/instructions.ts
|
|
28273
|
+
function governingProfile() {
|
|
28274
|
+
try {
|
|
28275
|
+
return loadProjectConfig()?.projectType ?? null;
|
|
28276
|
+
} catch {
|
|
28277
|
+
return null;
|
|
28278
|
+
}
|
|
28279
|
+
}
|
|
28280
|
+
function blockApplies(block, profile) {
|
|
28281
|
+
if (!block.profile || block.profile.length === 0) return true;
|
|
28282
|
+
return profile !== null && block.profile.includes(profile);
|
|
28283
|
+
}
|
|
28284
|
+
function skillPointers(skills) {
|
|
28285
|
+
if (skills.length === 0) return "(none published by this server)";
|
|
28286
|
+
return skills.map((s) => `- \`${s.resourceUri}\` \u2014 ${s.description || s.name}`).join("\n");
|
|
28287
|
+
}
|
|
28288
|
+
function waironInstructions(profile, packNames, skills, variants) {
|
|
28289
|
+
return `# wairon \u2014 Spec-Driven Development (SDD)
|
|
28290
|
+
|
|
28291
|
+
This server owns one system's **spec tree**: its architecture, specified top-down
|
|
28292
|
+
and machine-checked. Author it ONLY through this server's \`sdd_*\` tools \u2014 never by
|
|
28293
|
+
writing spec files directly, and never via the \`wairon\` CLI (that is the human
|
|
28294
|
+
developer's tool).
|
|
28295
|
+
|
|
28296
|
+
## The tree: L0 \u2192 L5
|
|
28297
|
+
|
|
28298
|
+
- **L0 System** \u2014 vision, boundaries, global requirements, target language.
|
|
28299
|
+
- **L1 Subsystem** \u2014 an isolated service, its \`publicInterfaces\`, its trusted links.
|
|
28300
|
+
- **L2 Component** \u2014 ONE building block (\`Portal\`, \`Orchestrator\`, \`Supervisor\`,
|
|
28301
|
+
\`Actor\`, \`Store\`, \`Index\`, \`Query\`, \`Registry\`, \`Adapter\`, \`Observer\`, and
|
|
28302
|
+
\`View\` for UIs) or pattern (\`Repository\`, and \`FeatureComponent\` or
|
|
28303
|
+
\`RouterComponent\` for UIs), plus \`owns\` (a pattern's private members) and
|
|
28304
|
+
\`dependsOn\` (collaborators). Logic is an \`Orchestrator\` whose \`dependencyClass\`
|
|
28305
|
+
(\`pure\` or \`read\`; unset, a workflow) bounds what it may depend on; a gateway is
|
|
28306
|
+
a \`Portal\` with the \`gateway\` variant. \`Specialist\` and \`Gateway\` are retired.
|
|
28307
|
+
- **L3 Interface** \u2014 the contract: method signatures with structured \`params\`,
|
|
28308
|
+
plus wire \`endpoint\` bindings for a Portal's methods.
|
|
28309
|
+
- **L4 Implementation** \u2014 a concrete realization of one L3 contract: narrative
|
|
28310
|
+
detail level, bound \`technologies\` (the vendor swap seam), optional \`sourcePath\`.
|
|
28311
|
+
- **L5 Narrative** \u2014 each method's logic as a FLAT numbered step list; \`call\` steps
|
|
28312
|
+
resolve to real dependency methods, and flow steps jump by step number.
|
|
28313
|
+
|
|
28314
|
+
These stereotypes are not labels \u2014 they carry enforced dependency rules (e.g. a
|
|
28315
|
+
Portal may not depend on a Store; state lives in a Store, never as fields inside
|
|
28316
|
+
an Orchestrator). \`sdd_validate_tree\` is the gate, and it is the authority on
|
|
28317
|
+
whether a design is legal.
|
|
28318
|
+
|
|
28319
|
+
## Authoring order
|
|
28320
|
+
|
|
28321
|
+
1. \`sdd_initialize_system\` (L0), then \`sdd_add_subsystem\` (L1).
|
|
28322
|
+
2. Then, **one subsystem at a time**: \`sdd_add_component\` (L2) \u2192 \`sdd_define_interface\`
|
|
28323
|
+
(L3) \u2192 \`sdd_set_endpoints\` for Portals \u2192 \`sdd_write_narrative\` (L4+L5).
|
|
28324
|
+
3. Run \`sdd_validate_tree\` as you go \u2014 early and per subsystem, not once at the end.
|
|
28325
|
+
\`sdd_get_status\` reports completeness; \`sdd_get_spec\` reads any spec back as JSON.
|
|
28326
|
+
4. Edit granularly with \`sdd_update_spec\` (it renumbers narrative steps and
|
|
28327
|
+
relocates jump targets for you).
|
|
28328
|
+
|
|
28329
|
+
## The tool schemas are the field reference
|
|
28330
|
+
|
|
28331
|
+
Every \`sdd_*\` tool's input schema is self-describing and authoritative \u2014 read the
|
|
28332
|
+
schema rather than guessing field names, and prefer structured \`params\` over prose
|
|
28333
|
+
signatures. Specs are ${profile === null ? "validated against the project's configured profile" : `governed by the **\`${profile}\`** profile`}, which decides which stereotypes are legal
|
|
28334
|
+
here, which narrative flow constructs the target language supports, and which
|
|
28335
|
+
extra rules apply. A subsystem may override the profile for itself.
|
|
28336
|
+
|
|
28337
|
+
## Loaded extension packs
|
|
28338
|
+
|
|
28339
|
+
${packNames.length === 0 ? "None \u2014 only wairon's built-in doctrine applies." : `${packNames.map((n) => `\`${n}\``).join(", ")} \u2014 these inject profiles, language tables, patterns, and
|
|
28340
|
+
additional conformance rules. Their doctrine is enforced by the same gate.`}
|
|
28341
|
+
|
|
28342
|
+
${variants.length === 0 ? "" : `## Component variants in play
|
|
28343
|
+
|
|
28344
|
+
This project defines variants \u2014 named, base-anchored kinds carrying implementation
|
|
28345
|
+
guidance, so every component of the same kind is built alike: ${variants.map((v) => `\`${v.id}\` (a kind of ${v.base})`).join(", ")}.
|
|
28346
|
+
Tag a component with \`variant\` where it fits, and read a component's resolved
|
|
28347
|
+
guidance and its same-variant siblings from \`sdd_get_spec\` (kind: component).
|
|
28348
|
+
|
|
28349
|
+
`}## Read the skills BEFORE authoring
|
|
28350
|
+
|
|
28351
|
+
This server publishes its skills as MCP resources. **Read \`wairon-skill://sdd-architect\`
|
|
28352
|
+
before you design anything**, and the others when their turn comes \u2014 they carry the
|
|
28353
|
+
architecture standard, the narrative step reference, and the review checklist that
|
|
28354
|
+
this briefing only points at:
|
|
28355
|
+
|
|
28356
|
+
${skillPointers(skills)}
|
|
28357
|
+
|
|
28358
|
+
## Delegating implementation
|
|
28359
|
+
|
|
28360
|
+
Delegate component work from a LIVE agent brief, never a generated file: discover
|
|
28361
|
+
agents via \`listAgents\` (or the \`wairon-agent://\` resources), fetch the target's
|
|
28362
|
+
brief with \`sdd_get_agent_brief\`, and spawn a scoped subagent from it \u2014 its
|
|
28363
|
+
\`instructions\` as prompt, its \`ownedPaths\` as write fence. Briefs are composed
|
|
28364
|
+
from the current spec tree on every call, so a re-lock never requires a session
|
|
28365
|
+
restart \u2014 fetch fresh per delegation. \`wairon-skill://sdd-delegate\` carries the
|
|
28366
|
+
full flow.`;
|
|
28367
|
+
}
|
|
28368
|
+
function buildServerInstructions() {
|
|
28369
|
+
const profile = governingProfile();
|
|
28370
|
+
let skills = [];
|
|
28371
|
+
let blocks = [];
|
|
28372
|
+
let packNames = [];
|
|
28373
|
+
const variants = [];
|
|
28374
|
+
try {
|
|
28375
|
+
skills = listSkillResources();
|
|
28376
|
+
const extensions = loadProjectExtensions();
|
|
28377
|
+
blocks = extensions.instructions;
|
|
28378
|
+
packNames = extensions.packNames;
|
|
28379
|
+
} catch {
|
|
28380
|
+
}
|
|
28381
|
+
const sections = [waironInstructions(profile, packNames, skills, variants)];
|
|
28382
|
+
for (const block of blocks) {
|
|
28383
|
+
if (!blockApplies(block, profile)) continue;
|
|
28384
|
+
sections.push(`## From pack "${block.pack}"
|
|
28385
|
+
|
|
28386
|
+
${block.text.trim()}`);
|
|
28387
|
+
}
|
|
28388
|
+
return sections.join("\n\n");
|
|
28389
|
+
}
|
|
28390
|
+
|
|
27181
28391
|
// src/core/skills.ts
|
|
27182
28392
|
var SKILL_NAMES = ["sdd-architect", "sdd-narrative", "sdd-auditor", "sdd-implement", "sdd-delegate"];
|
|
28393
|
+
function packSkillId(skill) {
|
|
28394
|
+
return `${packNamespace(skill.pack)}-${skill.id}`;
|
|
28395
|
+
}
|
|
28396
|
+
function packNamespace(pack) {
|
|
28397
|
+
return pack.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
28398
|
+
}
|
|
28399
|
+
function extensionsFor(builtin, packSkills) {
|
|
28400
|
+
return packSkills.filter((s) => s.extends === builtin && fs18.existsSync(s.sourcePath));
|
|
28401
|
+
}
|
|
28402
|
+
function composeBuiltinSkill(name, packSkills) {
|
|
28403
|
+
const srcPath = skillTemplatePath(name);
|
|
28404
|
+
const base = fs18.existsSync(srcPath) ? fs18.readFileSync(srcPath, "utf-8") : "";
|
|
28405
|
+
const sections = extensionsFor(name, packSkills);
|
|
28406
|
+
if (sections.length === 0) return base;
|
|
28407
|
+
const parts = [base.trimEnd()];
|
|
28408
|
+
for (const section of sections) {
|
|
28409
|
+
const body = fs18.readFileSync(section.sourcePath, "utf-8").replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, "");
|
|
28410
|
+
parts.push(`## Platform: ${section.pack}`, body.trim());
|
|
28411
|
+
}
|
|
28412
|
+
return `${parts.join("\n\n")}
|
|
28413
|
+
`;
|
|
28414
|
+
}
|
|
28415
|
+
function readFrontmatter(raw, fallbackName) {
|
|
28416
|
+
const match = /^---\r?\n([\s\S]*?)\r?\n---/.exec(raw);
|
|
28417
|
+
const block = match ? match[1] : "";
|
|
28418
|
+
const field = (key) => {
|
|
28419
|
+
const m = new RegExp(`^${key}:\\s*(.*)$`, "m").exec(block);
|
|
28420
|
+
return m ? m[1].trim() : "";
|
|
28421
|
+
};
|
|
28422
|
+
return { name: field("name") || fallbackName, description: field("description") };
|
|
28423
|
+
}
|
|
28424
|
+
function builtinSkillsDir() {
|
|
28425
|
+
return path29.resolve(__dirname, "..", "templates", "skills");
|
|
28426
|
+
}
|
|
28427
|
+
function skillTemplatePath(name) {
|
|
28428
|
+
return path29.join(builtinSkillsDir(), `${name}.md`);
|
|
28429
|
+
}
|
|
27183
28430
|
function activeTargetTypes2() {
|
|
27184
28431
|
const config = loadProjectConfig();
|
|
27185
28432
|
if (!config) return [];
|
|
27186
28433
|
return activeTargetTypes(config);
|
|
27187
28434
|
}
|
|
28435
|
+
var SKILL_RESOURCE_SCHEME = "wairon-skill";
|
|
27188
28436
|
var RESOURCE_SKILL_IDS = [...SKILL_NAMES].sort();
|
|
28437
|
+
var SkillResourceNotFoundError = class extends Error {
|
|
28438
|
+
constructor(resourceId) {
|
|
28439
|
+
super(`Unknown SDD skill resource id: "${resourceId}".`);
|
|
28440
|
+
this.name = "SkillResourceNotFoundError";
|
|
28441
|
+
}
|
|
28442
|
+
};
|
|
28443
|
+
function readSkillFrontmatter(name) {
|
|
28444
|
+
return readFrontmatter(fs18.readFileSync(skillTemplatePath(name), "utf-8"), name);
|
|
28445
|
+
}
|
|
28446
|
+
function listSkillResources() {
|
|
28447
|
+
const builtin = RESOURCE_SKILL_IDS.map((id) => {
|
|
28448
|
+
const fm = readSkillFrontmatter(id);
|
|
28449
|
+
return {
|
|
28450
|
+
id,
|
|
28451
|
+
name: fm.name,
|
|
28452
|
+
description: fm.description,
|
|
28453
|
+
version: WAIRON_VERSION,
|
|
28454
|
+
resourceUri: `${SKILL_RESOURCE_SCHEME}://${id}`,
|
|
28455
|
+
defaultForHostedMcp: true
|
|
28456
|
+
};
|
|
28457
|
+
});
|
|
28458
|
+
const pack = loadProjectExtensions().skills.filter((s) => s.extends === void 0 && fs18.existsSync(s.sourcePath)).map((skill) => {
|
|
28459
|
+
const id = packSkillId(skill);
|
|
28460
|
+
const fm = readFrontmatter(fs18.readFileSync(skill.sourcePath, "utf-8"), id);
|
|
28461
|
+
return {
|
|
28462
|
+
id,
|
|
28463
|
+
name: fm.name,
|
|
28464
|
+
description: fm.description,
|
|
28465
|
+
version: skill.packVersion ?? WAIRON_VERSION,
|
|
28466
|
+
resourceUri: `${SKILL_RESOURCE_SCHEME}://${id}`,
|
|
28467
|
+
defaultForHostedMcp: true
|
|
28468
|
+
};
|
|
28469
|
+
});
|
|
28470
|
+
return [...builtin, ...pack];
|
|
28471
|
+
}
|
|
28472
|
+
function readSkillResource(resourceId) {
|
|
28473
|
+
const packSkills = loadProjectExtensions().skills;
|
|
28474
|
+
if (SKILL_NAMES.includes(resourceId)) return composeBuiltinSkill(resourceId, packSkills);
|
|
28475
|
+
const packSkill = packSkills.find((s) => s.extends === void 0 && packSkillId(s) === resourceId);
|
|
28476
|
+
if (packSkill) return fs18.readFileSync(packSkill.sourcePath, "utf-8");
|
|
28477
|
+
return fs18.readFileSync(skillTemplatePath(resourceId), "utf-8");
|
|
28478
|
+
}
|
|
28479
|
+
function listResources() {
|
|
28480
|
+
return listSkillResources();
|
|
28481
|
+
}
|
|
28482
|
+
function buildServerInstructions2() {
|
|
28483
|
+
return buildServerInstructions();
|
|
28484
|
+
}
|
|
28485
|
+
function readResource(resourceId) {
|
|
28486
|
+
const known = listSkillResources().some((d) => d.id === resourceId);
|
|
28487
|
+
if (!known) throw new SkillResourceNotFoundError(resourceId);
|
|
28488
|
+
return readSkillResource(resourceId);
|
|
28489
|
+
}
|
|
27189
28490
|
|
|
27190
|
-
// src/
|
|
28491
|
+
// src/mcp/server.ts
|
|
28492
|
+
var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
28493
|
+
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
28494
|
+
var import_zod11 = require("zod");
|
|
28495
|
+
var import_types4 = require("@modelcontextprotocol/sdk/types.js");
|
|
28496
|
+
var crypto4 = __toESM(require("crypto"));
|
|
28497
|
+
|
|
28498
|
+
// src/core/adapters/authoring-core.ts
|
|
27191
28499
|
function loadProjectConfig2() {
|
|
28500
|
+
try {
|
|
28501
|
+
return loadProjectConfig();
|
|
28502
|
+
} catch {
|
|
28503
|
+
return null;
|
|
28504
|
+
}
|
|
28505
|
+
}
|
|
28506
|
+
|
|
28507
|
+
// src/models/candidate.ts
|
|
28508
|
+
function formatCandidateRefusal(verdict) {
|
|
28509
|
+
const lines = verdict.errors.map((e) => `- ${e.code}: ${e.message}`);
|
|
28510
|
+
return `Refused: this component's fields do not match its componentType.
|
|
28511
|
+
${lines.join("\n")}
|
|
28512
|
+
|
|
28513
|
+
Nothing was written. Fix the fields and retry \u2014 on an EXISTING spec, remove a field with sdd_update_spec's "unset" (e.g. {"unset": ["basePath"]}); passing null leaves it in place.`;
|
|
28514
|
+
}
|
|
28515
|
+
|
|
28516
|
+
// src/core/authoring.ts
|
|
28517
|
+
function candidateOptions() {
|
|
28518
|
+
const config = loadProjectConfig2();
|
|
28519
|
+
return config ? { rules: config.rules, projectType: config.projectType } : {};
|
|
28520
|
+
}
|
|
28521
|
+
function noticesFrom(verdict) {
|
|
28522
|
+
return verdict.warnings.map((w) => `${w.code}: ${w.message}`);
|
|
28523
|
+
}
|
|
28524
|
+
function componentCandidateGate(options = candidateOptions()) {
|
|
28525
|
+
return {
|
|
28526
|
+
gate: (kind, merged) => {
|
|
28527
|
+
if (kind !== "component") return;
|
|
28528
|
+
const verdict = validateComponentCandidate(merged, options);
|
|
28529
|
+
if (verdict.errors.length) throw new Error(formatCandidateRefusal(verdict));
|
|
28530
|
+
return noticesFrom(verdict);
|
|
28531
|
+
}
|
|
28532
|
+
};
|
|
28533
|
+
}
|
|
28534
|
+
var STORE_MANAGED_FIELDS = /* @__PURE__ */ new Set(["status", "updatedAt"]);
|
|
28535
|
+
var ALWAYS_CARRIED_FIELDS = /* @__PURE__ */ new Set(["ext"]);
|
|
28536
|
+
var STATUS_ORDER = ["draft", "design", "complete"];
|
|
28537
|
+
var STATUSED_KINDS = /* @__PURE__ */ new Set(["subsystem", "component", "interface", "implementation"]);
|
|
28538
|
+
var MISSING_PARENT = {
|
|
28539
|
+
subsystem: () => "System spec must be initialized (sdd_initialize_system) first.",
|
|
28540
|
+
component: (id) => `Parent subsystem "${id}" does not exist.`,
|
|
28541
|
+
interface: (id) => `Component "${id}" does not exist.`,
|
|
28542
|
+
implementation: (id) => `Interface contract "${id}" does not exist.`,
|
|
28543
|
+
type: (id) => `Owning subsystem "${id}" does not exist \u2014 name a subsystem the tree has, or omit subsystem for a system-level value object.`
|
|
28544
|
+
};
|
|
28545
|
+
var REWRITE_LABEL = {
|
|
28546
|
+
system: "System spec",
|
|
28547
|
+
subsystem: "Subsystem",
|
|
28548
|
+
component: "Component",
|
|
28549
|
+
interface: "Interface",
|
|
28550
|
+
implementation: "Implementation",
|
|
28551
|
+
type: "Type"
|
|
28552
|
+
};
|
|
28553
|
+
function restatementParent(restatement) {
|
|
28554
|
+
const spec = restatement.spec;
|
|
28555
|
+
switch (restatement.kind) {
|
|
28556
|
+
case "subsystem":
|
|
28557
|
+
return { kind: "system", id: "system" };
|
|
28558
|
+
case "component":
|
|
28559
|
+
return { kind: "subsystem", id: spec.subsystem };
|
|
28560
|
+
case "interface":
|
|
28561
|
+
return { kind: "component", id: spec.component };
|
|
28562
|
+
case "implementation":
|
|
28563
|
+
return { kind: "interface", id: spec.contract };
|
|
28564
|
+
case "type":
|
|
28565
|
+
return typeof spec.subsystem === "string" && spec.subsystem !== "" ? { kind: "subsystem", id: spec.subsystem } : null;
|
|
28566
|
+
default:
|
|
28567
|
+
return null;
|
|
28568
|
+
}
|
|
28569
|
+
}
|
|
28570
|
+
function applyRestatement(restatement, existing, parent) {
|
|
28571
|
+
const replacedExisting = existing !== null;
|
|
28572
|
+
const parentRef = restatementParent(restatement);
|
|
28573
|
+
if (parentRef && !parent) return refused(restatement, replacedExisting, MISSING_PARENT[restatement.kind](parentRef.id));
|
|
28574
|
+
const status = statusForCreate(restatement, existing);
|
|
28575
|
+
if ("refusal" in status) return refused(restatement, replacedExisting, status.refusal);
|
|
28576
|
+
const candidate = JSON.parse(JSON.stringify(restatement.spec));
|
|
28577
|
+
if (restatement.kind === "subsystem") candidate.parentSystem = parent.name;
|
|
28578
|
+
const carried = carryInto(restatement, existing, candidate);
|
|
28579
|
+
const cleared = clearedByOmission(existing, candidate, restatement.fields);
|
|
28580
|
+
stampLifecycle(candidate, existing, status.status);
|
|
28581
|
+
const labelErrors = resolveLabelsOf(restatement.kind, candidate);
|
|
28582
|
+
if (labelErrors.length) {
|
|
28583
|
+
return refused(restatement, replacedExisting, `Unresolved narrative label references \u2014 nothing was saved:
|
|
28584
|
+
- ${labelErrors.join("\n- ")}`);
|
|
28585
|
+
}
|
|
28586
|
+
const parsed = parseCandidate(restatement.kind, candidate);
|
|
28587
|
+
if ("refusal" in parsed) return refused(restatement, replacedExisting, parsed.refusal);
|
|
28588
|
+
return {
|
|
28589
|
+
spec: parsed.spec,
|
|
28590
|
+
...status.status ? { status: status.status } : {},
|
|
28591
|
+
replacedExisting,
|
|
28592
|
+
notices: existing ? rewriteNotices(restatement.kind, existing, candidate, carried, cleared) : [],
|
|
28593
|
+
changedMethods: changedMethodsOf(restatement.kind, existing, candidate)
|
|
28594
|
+
};
|
|
28595
|
+
}
|
|
28596
|
+
var SPEC_SCHEMA = {
|
|
28597
|
+
system: SystemSpecSchema,
|
|
28598
|
+
subsystem: SubsystemSpecSchema,
|
|
28599
|
+
component: ComponentSpecSchema,
|
|
28600
|
+
interface: InterfaceSpecSchema,
|
|
28601
|
+
implementation: ImplementationSpecSchema,
|
|
28602
|
+
type: TypeSpecSchema
|
|
28603
|
+
};
|
|
28604
|
+
function parseCandidate(kind, candidate) {
|
|
28605
|
+
const masked = /* @__PURE__ */ new Map();
|
|
28606
|
+
const mask = (value) => {
|
|
28607
|
+
if (typeof value === "string") {
|
|
28608
|
+
if (!value.includes("::")) return value;
|
|
28609
|
+
const token = `nsref_${masked.size}_masked`;
|
|
28610
|
+
masked.set(token, value);
|
|
28611
|
+
return token;
|
|
28612
|
+
}
|
|
28613
|
+
if (Array.isArray(value)) return value.map(mask);
|
|
28614
|
+
if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, mask(v)]));
|
|
28615
|
+
return value;
|
|
28616
|
+
};
|
|
28617
|
+
const restore = (value) => {
|
|
28618
|
+
if (typeof value === "string") return masked.get(value) ?? value;
|
|
28619
|
+
if (Array.isArray(value)) return value.map(restore);
|
|
28620
|
+
if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, restore(v)]));
|
|
28621
|
+
return value;
|
|
28622
|
+
};
|
|
28623
|
+
const result = SPEC_SCHEMA[kind].safeParse(mask(candidate));
|
|
28624
|
+
if (result.success) return { spec: restore(result.data) };
|
|
28625
|
+
const id = kind === "system" ? "system" : String(candidate.id);
|
|
28626
|
+
const issues = result.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ");
|
|
28627
|
+
return {
|
|
28628
|
+
refusal: `Refusing to write ${kind} "${id}": the spec it would become does not parse \u2014 ${issues}. Nothing was written. State the missing or malformed field and call again.`
|
|
28629
|
+
};
|
|
28630
|
+
}
|
|
28631
|
+
function refused(restatement, replacedExisting, refusal2) {
|
|
28632
|
+
return { spec: restatement.spec, refusal: refusal2, replacedExisting, notices: [], changedMethods: [] };
|
|
28633
|
+
}
|
|
28634
|
+
function statusForCreate(restatement, existing) {
|
|
28635
|
+
if (!STATUSED_KINDS.has(restatement.kind)) return {};
|
|
28636
|
+
const stated = restatement.status;
|
|
28637
|
+
const stored = existing?.status;
|
|
28638
|
+
const held = stored !== void 0 && STATUS_ORDER.includes(stored) ? stored : void 0;
|
|
28639
|
+
if (stated === void 0) return { status: held ?? "draft" };
|
|
28640
|
+
if (held === void 0 || STATUS_ORDER.indexOf(stated) >= STATUS_ORDER.indexOf(held)) return { status: stated };
|
|
28641
|
+
const label = `${restatement.kind} "${restatement.spec.id}"`;
|
|
28642
|
+
return {
|
|
28643
|
+
refusal: `Refusing to re-author ${label} at status "${stated}": it is stored at "${held}", and a create tool never lowers a spec's status \u2014 a restatement that reopened a frozen spec would undo a lock without saying so. Nothing was written. To reopen it deliberately, use sdd_update_spec with {"status": "${stated}"}; to keep the level it has, leave status out.`
|
|
28644
|
+
};
|
|
28645
|
+
}
|
|
28646
|
+
function carryInto(restatement, existing, candidate) {
|
|
28647
|
+
if (!existing) return [];
|
|
28648
|
+
const carried = ["createdAt"];
|
|
28649
|
+
if (restatement.memberFields && Array.isArray(candidate.methods)) {
|
|
28650
|
+
const stored = existing.methods ?? [];
|
|
28651
|
+
for (const method of candidate.methods) {
|
|
28652
|
+
const prev = stored.find((p) => p.name === method.name);
|
|
28653
|
+
for (const field of carryUnexpressed(prev, method, restatement.memberFields)) carried.push(`${field} (${String(method.name)})`);
|
|
28654
|
+
}
|
|
28655
|
+
}
|
|
28656
|
+
carried.push(...carryUnexpressed(existing, candidate, [...restatement.fields, "createdAt"]));
|
|
28657
|
+
return carried;
|
|
28658
|
+
}
|
|
28659
|
+
function carryUnexpressed(existing, next, expressed) {
|
|
28660
|
+
if (!existing) return [];
|
|
28661
|
+
const carried = [];
|
|
28662
|
+
for (const [field, value] of Object.entries(existing)) {
|
|
28663
|
+
if (value === void 0) continue;
|
|
28664
|
+
if (STORE_MANAGED_FIELDS.has(field)) continue;
|
|
28665
|
+
if (expressed.includes(field) && !ALWAYS_CARRIED_FIELDS.has(field)) continue;
|
|
28666
|
+
if (next[field] !== void 0) continue;
|
|
28667
|
+
next[field] = value;
|
|
28668
|
+
carried.push(field);
|
|
28669
|
+
}
|
|
28670
|
+
return carried;
|
|
28671
|
+
}
|
|
28672
|
+
function isEmptyValue(value) {
|
|
28673
|
+
if (value === void 0 || value === null) return true;
|
|
28674
|
+
if (Array.isArray(value)) return value.length === 0;
|
|
28675
|
+
if (typeof value === "string") return value.trim() === "";
|
|
28676
|
+
if (typeof value === "object") return Object.keys(value).length === 0;
|
|
28677
|
+
return false;
|
|
28678
|
+
}
|
|
28679
|
+
function clearedByOmission(existing, next, expressed) {
|
|
28680
|
+
if (!existing) return [];
|
|
28681
|
+
const stored = existing;
|
|
28682
|
+
const cleared = [];
|
|
28683
|
+
for (const field of expressed) {
|
|
28684
|
+
const before = stored[field];
|
|
28685
|
+
if (STORE_MANAGED_FIELDS.has(field) || ALWAYS_CARRIED_FIELDS.has(field)) continue;
|
|
28686
|
+
if (isEmptyValue(before) || !isEmptyValue(next[field])) continue;
|
|
28687
|
+
cleared.push(Array.isArray(before) ? `${field} (had ${before.length})` : field);
|
|
28688
|
+
}
|
|
28689
|
+
return cleared;
|
|
28690
|
+
}
|
|
28691
|
+
function stampLifecycle(candidate, existing, status) {
|
|
28692
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
28693
|
+
candidate.createdAt = existing?.createdAt ?? now;
|
|
28694
|
+
candidate.updatedAt = now;
|
|
28695
|
+
if (status) candidate.status = status;
|
|
28696
|
+
else delete candidate.status;
|
|
28697
|
+
}
|
|
28698
|
+
function resolveLabelsOf(kind, candidate) {
|
|
28699
|
+
if (kind !== "implementation") return [];
|
|
28700
|
+
const methods = candidate.methods ?? [];
|
|
28701
|
+
return methods.flatMap((m) => resolveNarrativeLabels(m.name, m.narrative ?? []));
|
|
28702
|
+
}
|
|
28703
|
+
function removedByName(before, after) {
|
|
28704
|
+
if (!before?.length) return [];
|
|
28705
|
+
const kept = new Set((after ?? []).map((m) => m.name));
|
|
28706
|
+
return before.map((m) => m.name).filter((n) => !kept.has(n));
|
|
28707
|
+
}
|
|
28708
|
+
function rewriteNotices(kind, existing, candidate, carried, cleared) {
|
|
28709
|
+
const named = kind === "system" ? existing.name : String(candidate.id);
|
|
28710
|
+
const notices = [`${REWRITE_LABEL[kind]} "${named}" already existed \u2014 re-authored in place; this input REPLACES what it expresses.`];
|
|
28711
|
+
if (carried.length) notices.push(`Carried forward (not expressible through this tool): ${carried.join(", ")}.`);
|
|
28712
|
+
const removal = removalNotice(kind, existing, candidate);
|
|
28713
|
+
if (removal) notices.push(removal);
|
|
28714
|
+
if (cleared.length) {
|
|
28715
|
+
notices.push(
|
|
28716
|
+
`CLEARED by omission: ${cleared.join(", ")} \u2014 the argument was not repeated, so the previous value is gone. Use sdd_update_spec if you meant to leave it untouched.`
|
|
28717
|
+
);
|
|
28718
|
+
}
|
|
28719
|
+
return notices;
|
|
28720
|
+
}
|
|
28721
|
+
function removalNotice(kind, existing, candidate) {
|
|
28722
|
+
const before = existing;
|
|
28723
|
+
let removed = [];
|
|
28724
|
+
let noun = "method";
|
|
28725
|
+
let suffix = "";
|
|
28726
|
+
if (kind === "interface" || kind === "implementation") {
|
|
28727
|
+
removed = removedByName(before.methods, candidate.methods);
|
|
28728
|
+
suffix = kind === "interface" ? " (endpoint bindings included)" : " (L5 narratives included)";
|
|
28729
|
+
} else if (kind === "type") {
|
|
28730
|
+
removed = [
|
|
28731
|
+
...removedByName(before.fields, candidate.fields).map((n) => `field ${n}`),
|
|
28732
|
+
...removedByName(before.methods, candidate.methods).map((n) => `method ${n}`)
|
|
28733
|
+
];
|
|
28734
|
+
noun = "member";
|
|
28735
|
+
}
|
|
28736
|
+
if (!removed.length) return void 0;
|
|
28737
|
+
return `REMOVED by this restatement: ${removed.length === 1 ? noun : `${noun}s`} ${removed.map((n) => `"${n}"`).join(", ")}${suffix} \u2014 absent from the input, so no longer in the spec. Restate them to keep them, or use sdd_update_spec to edit one member at a time.`;
|
|
28738
|
+
}
|
|
28739
|
+
var METHOD_SCHEMA = {
|
|
28740
|
+
interface: MethodSignatureSchema,
|
|
28741
|
+
implementation: MethodImplementationSchema,
|
|
28742
|
+
type: TypeMethodSchema
|
|
28743
|
+
};
|
|
28744
|
+
function changedMethodsOf(kind, existing, candidate) {
|
|
28745
|
+
if (!existing || !(kind in METHOD_SCHEMA)) return [];
|
|
28746
|
+
const schema = METHOD_SCHEMA[kind];
|
|
28747
|
+
const stored = existing.methods ?? [];
|
|
28748
|
+
const next = candidate.methods ?? [];
|
|
28749
|
+
return stored.filter((method) => {
|
|
28750
|
+
const restated = next.find((m) => m.name === method.name);
|
|
28751
|
+
if (!restated) return true;
|
|
28752
|
+
const parsed = schema.safeParse(restated);
|
|
28753
|
+
return !parsed.success || canonical(parsed.data) !== canonical(method);
|
|
28754
|
+
}).map((method) => method.name);
|
|
28755
|
+
}
|
|
28756
|
+
function canonical(value) {
|
|
28757
|
+
return JSON.stringify(value, (_key, v) => v && typeof v === "object" && !Array.isArray(v) ? Object.fromEntries(Object.entries(v).filter(([, x]) => x !== void 0).sort(([a], [b]) => a.localeCompare(b))) : v);
|
|
28758
|
+
}
|
|
28759
|
+
function writeSpec(restatement) {
|
|
28760
|
+
const bound2 = candidateOptions();
|
|
28761
|
+
const parentRef = restatementParent(restatement);
|
|
28762
|
+
const parent = parentRef ? loadSpec(parentRef.kind, parentRef.id) : null;
|
|
28763
|
+
const id = restatement.kind === "system" ? "system" : restatement.spec.id;
|
|
28764
|
+
const existing = loadSpec(restatement.kind, id);
|
|
28765
|
+
const application = applyRestatement(restatement, existing, parent);
|
|
28766
|
+
if (application.refusal !== void 0) throw new Error(application.refusal);
|
|
28767
|
+
const gateNotices = restatement.kind === "component" ? judgeComponent(application.spec, bound2) : [];
|
|
28768
|
+
const persisted = persistCandidate(restatement.kind, application.spec);
|
|
28769
|
+
const testsToRevisit = testsInvalidatedBy(restatement.kind, id, application.changedMethods, [existing, application.spec], bound2);
|
|
28770
|
+
return {
|
|
28771
|
+
kind: restatement.kind,
|
|
28772
|
+
id,
|
|
28773
|
+
name: application.spec.name,
|
|
28774
|
+
replacedExisting: application.replacedExisting,
|
|
28775
|
+
...application.status ? { status: application.status } : {},
|
|
28776
|
+
notices: [...gateNotices, ...persisted.notices, ...application.notices],
|
|
28777
|
+
...persisted.scaffoldedProjectPath ? { scaffoldedProjectPath: persisted.scaffoldedProjectPath } : {},
|
|
28778
|
+
testsToRevisit
|
|
28779
|
+
};
|
|
28780
|
+
}
|
|
28781
|
+
function judgeComponent(candidate, options) {
|
|
28782
|
+
const verdict = validateComponentCandidate(candidate, options);
|
|
28783
|
+
if (verdict.errors.length) throw new Error(formatCandidateRefusal(verdict));
|
|
28784
|
+
return noticesFrom(verdict);
|
|
28785
|
+
}
|
|
28786
|
+
function persistCandidate(kind, spec) {
|
|
28787
|
+
const projectPath = kind === "subsystem" ? spec.projectPath : void 0;
|
|
28788
|
+
if (projectPath && projectPath.trim() !== "") {
|
|
28789
|
+
createChainedSubsystem(spec, spec.name);
|
|
28790
|
+
return { notices: [], scaffoldedProjectPath: projectPath };
|
|
28791
|
+
}
|
|
28792
|
+
return { notices: saveSpec(kind, spec) };
|
|
28793
|
+
}
|
|
28794
|
+
function testsInvalidatedBy(kind, id, methods, specs, options, written = []) {
|
|
28795
|
+
const testRoots = options.rules?.conformance?.testRoots ?? [];
|
|
28796
|
+
if (methods.length === 0 || testRoots.length === 0) return [];
|
|
28797
|
+
return findTestsReferencing2(searchedMethods(kind, id, methods, specs, written), getProjectRoot(), testRoots);
|
|
28798
|
+
}
|
|
28799
|
+
function searchedMethods(kind, id, names, specs, written) {
|
|
28800
|
+
const stated = (name) => written.find((m) => m?.name === name)?.symbol;
|
|
28801
|
+
if (kind === "interface") {
|
|
28802
|
+
const realizations = loadImplementationSpecs().filter((impl) => impl.contract === id);
|
|
28803
|
+
return names.flatMap((name) => {
|
|
28804
|
+
const found = realizations.flatMap((impl) => impl.methods.filter((m) => m.name === name).map((m) => ({ name, symbol: stated(name) ?? m.symbol, sourcePath: m.sourcePath ?? impl.sourcePath })));
|
|
28805
|
+
return found.length > 0 ? found : [{ name, symbol: stated(name) }];
|
|
28806
|
+
});
|
|
28807
|
+
}
|
|
28808
|
+
return names.map((name) => {
|
|
28809
|
+
const holders = specs.map((spec) => {
|
|
28810
|
+
const holder = spec;
|
|
28811
|
+
const method = holder?.methods?.find((m) => m.name === name);
|
|
28812
|
+
return method ? { method, file: method.sourcePath ?? holder?.sourcePath } : null;
|
|
28813
|
+
}).filter((h) => h !== null);
|
|
28814
|
+
const symbol = stated(name) ?? holders.map((h) => h.method.symbol).find((s) => s !== void 0);
|
|
28815
|
+
const sourcePath = holders.map((h) => h.file).find((f) => f !== void 0) ?? specs.map((spec) => spec?.sourcePath).find((f) => f !== void 0);
|
|
28816
|
+
return { name, symbol, sourcePath };
|
|
28817
|
+
});
|
|
28818
|
+
}
|
|
28819
|
+
function deleteSpec2(kind, id) {
|
|
28820
|
+
const bound2 = candidateOptions();
|
|
28821
|
+
const stored = loadSpec(kind, id);
|
|
28822
|
+
const deleted = deleteSpec(kind, id);
|
|
28823
|
+
const methods = deleted ? (stored?.methods ?? []).map((m) => m.name) : [];
|
|
28824
|
+
const testsToRevisit = testsInvalidatedBy(kind, id, methods, [stored], bound2);
|
|
28825
|
+
return { kind, id, deleted, testsToRevisit };
|
|
28826
|
+
}
|
|
28827
|
+
function updateSpecGated(kind, id, delta, dryRun) {
|
|
28828
|
+
const bound2 = candidateOptions();
|
|
28829
|
+
const gate = componentCandidateGate(bound2);
|
|
28830
|
+
const testRoots = bound2.rules?.conformance?.testRoots ?? [];
|
|
28831
|
+
const stored = testRoots.length > 0 ? loadSpec(kind, id) : null;
|
|
28832
|
+
const report = updateSpec(kind, id, delta, gate, dryRun);
|
|
28833
|
+
const changed = changedMethods(report);
|
|
28834
|
+
if (changed.length > 0 && testRoots.length > 0) {
|
|
28835
|
+
const written = Array.isArray(delta?.methods) ? delta.methods : [];
|
|
28836
|
+
report.testsToRevisit = testsInvalidatedBy(kind, id, changed, [stored], bound2, written);
|
|
28837
|
+
}
|
|
28838
|
+
return report;
|
|
28839
|
+
}
|
|
28840
|
+
function changedMethods(report) {
|
|
28841
|
+
const named = /* @__PURE__ */ new Set();
|
|
28842
|
+
for (const change of report.changes) {
|
|
28843
|
+
const [field, name] = change.path.split(".");
|
|
28844
|
+
if (field === "methods" && name) named.add(name);
|
|
28845
|
+
}
|
|
28846
|
+
return [...named];
|
|
28847
|
+
}
|
|
28848
|
+
function methodMoveGate(source) {
|
|
28849
|
+
const bound2 = candidateOptions();
|
|
28850
|
+
return {
|
|
28851
|
+
gate: (kind, merged) => {
|
|
28852
|
+
if (kind !== "component") return;
|
|
28853
|
+
const { codes, verdict, ceiling } = judgeMoveHome(merged, source, bound2);
|
|
28854
|
+
if (codes.length === 0) return noticesFrom(verdict);
|
|
28855
|
+
throw new Error(
|
|
28856
|
+
`${codes.join(", ")} refuses "${merged.id}" as the home for these methods. ` + (verdict.errors.length ? formatCandidateRefusal(verdict) : `It would reach ${merged.dependsOn?.length ?? 0} dependencies against a ceiling of ${ceiling}.`)
|
|
28857
|
+
);
|
|
28858
|
+
},
|
|
28859
|
+
assess: (kind, merged) => kind === "component" ? judgeMoveHome(merged, source, bound2).codes : []
|
|
28860
|
+
};
|
|
28861
|
+
}
|
|
28862
|
+
function judgeMoveHome(component, source, options) {
|
|
28863
|
+
const ceiling = options.rules?.complexity?.maxComponentDependencies;
|
|
28864
|
+
const verdict = validateComponentCandidate(component, options);
|
|
28865
|
+
const codes = verdict.errors.map((e) => e.code);
|
|
28866
|
+
if (component.id !== source && ceiling !== void 0 && (component.dependsOn?.length ?? 0) > ceiling) {
|
|
28867
|
+
codes.push("EXCESSIVE_DEPENDENCIES");
|
|
28868
|
+
}
|
|
28869
|
+
return { codes: [...new Set(codes)], verdict, ceiling };
|
|
28870
|
+
}
|
|
28871
|
+
function moveMethods2(from, to, methods, dryRun) {
|
|
28872
|
+
return moveMethods(from, to, methods, methodMoveGate(from), dryRun);
|
|
28873
|
+
}
|
|
28874
|
+
|
|
28875
|
+
// src/mcp/build.ts
|
|
28876
|
+
var fs19 = __toESM(require("fs"));
|
|
28877
|
+
var path30 = __toESM(require("path"));
|
|
28878
|
+
var import_child_process2 = require("child_process");
|
|
28879
|
+
function captureBuildStamp(entryPath) {
|
|
28880
|
+
try {
|
|
28881
|
+
const s = fs19.statSync(entryPath);
|
|
28882
|
+
return { path: entryPath, mtimeMs: s.mtimeMs, size: s.size };
|
|
28883
|
+
} catch {
|
|
28884
|
+
return null;
|
|
28885
|
+
}
|
|
28886
|
+
}
|
|
28887
|
+
function isBuildStale(stamp) {
|
|
28888
|
+
if (!stamp) return false;
|
|
28889
|
+
try {
|
|
28890
|
+
const s = fs19.statSync(stamp.path);
|
|
28891
|
+
return s.mtimeMs !== stamp.mtimeMs || s.size !== stamp.size;
|
|
28892
|
+
} catch {
|
|
28893
|
+
return false;
|
|
28894
|
+
}
|
|
28895
|
+
}
|
|
28896
|
+
var fingerprintCache = /* @__PURE__ */ new Map();
|
|
28897
|
+
function readBuildFingerprint(stamp) {
|
|
28898
|
+
const current2 = fs19.statSync(stamp.path);
|
|
28899
|
+
const key = `${stamp.path}|${current2.mtimeMs}|${current2.size}`;
|
|
28900
|
+
const cached = fingerprintCache.get(key);
|
|
28901
|
+
if (cached !== void 0) return cached;
|
|
28902
|
+
const modulePath = fingerprintModuleFor(stamp.path);
|
|
28903
|
+
const loaderFlags = modulePath.endsWith(".ts") ? process.execArgv : [];
|
|
28904
|
+
let printed;
|
|
28905
|
+
try {
|
|
28906
|
+
printed = (0, import_child_process2.execFileSync)(
|
|
28907
|
+
process.execPath,
|
|
28908
|
+
[...loaderFlags, "-e", "const m = require(process.argv[1]); process.stdout.write(String(m.schemaFingerprint()));", modulePath],
|
|
28909
|
+
{ encoding: "utf8", timeout: 6e4, stdio: ["ignore", "pipe", "pipe"], windowsHide: true }
|
|
28910
|
+
).trim();
|
|
28911
|
+
} catch (e) {
|
|
28912
|
+
const stderr = String(e.stderr ?? "").split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
|
|
28913
|
+
const cause = stderr.find((l) => /^[A-Za-z]*Error\b/.test(l)) ?? stderr[0] ?? e.message;
|
|
28914
|
+
throw new Error(`the build at ${modulePath} could not be asked for its schema fingerprint (${cause})`);
|
|
28915
|
+
}
|
|
28916
|
+
if (!printed) throw new Error(`the build at ${modulePath} answered no schema fingerprint`);
|
|
28917
|
+
fingerprintCache.set(key, printed);
|
|
28918
|
+
return printed;
|
|
28919
|
+
}
|
|
28920
|
+
function fingerprintModuleFor(entryPath) {
|
|
28921
|
+
let dir = path30.dirname(entryPath);
|
|
28922
|
+
for (; ; ) {
|
|
28923
|
+
const manifest = path30.join(dir, "package.json");
|
|
28924
|
+
if (fs19.existsSync(manifest)) {
|
|
28925
|
+
try {
|
|
28926
|
+
const pkg = JSON.parse(fs19.readFileSync(manifest, "utf8"));
|
|
28927
|
+
const inDist = path30.relative(path30.join(dir, "dist"), entryPath);
|
|
28928
|
+
if (pkg.name === "@wairon/cli" && pkg.main && !inDist.startsWith("..") && !path30.isAbsolute(inDist)) {
|
|
28929
|
+
return path30.resolve(dir, pkg.main);
|
|
28930
|
+
}
|
|
28931
|
+
} catch {
|
|
28932
|
+
}
|
|
28933
|
+
return entryPath;
|
|
28934
|
+
}
|
|
28935
|
+
const parent = path30.dirname(dir);
|
|
28936
|
+
if (parent === dir) return entryPath;
|
|
28937
|
+
dir = parent;
|
|
28938
|
+
}
|
|
28939
|
+
}
|
|
28940
|
+
|
|
28941
|
+
// src/mcp/server.ts
|
|
28942
|
+
function statusFamilyContext() {
|
|
28943
|
+
const lines = [];
|
|
28944
|
+
try {
|
|
28945
|
+
const parent = resolveChainingParent();
|
|
28946
|
+
if (parent) {
|
|
28947
|
+
let parentName;
|
|
28948
|
+
try {
|
|
28949
|
+
const system = readYamlFile(aiPathsAt(parent.parentRoot).specsSystem());
|
|
28950
|
+
if (typeof system?.name === "string") parentName = system.name;
|
|
28951
|
+
} catch {
|
|
28952
|
+
}
|
|
28953
|
+
lines.push(
|
|
28954
|
+
`Family: this project is a chained subproject, mounted as subsystem "${parent.subsystemId}" of ${parentName ? `the parent project "${parentName}"` : "its parent project"} \u2014 the surfaces it can consume are listed by sdd_list_external_interfaces.`
|
|
28955
|
+
);
|
|
28956
|
+
}
|
|
28957
|
+
const mounts = loadSubsystemSpecs().filter((s) => s.projectPath && !s.id.includes("::"));
|
|
28958
|
+
if (mounts.length > 0) {
|
|
28959
|
+
lines.push(`Family: chained subprojects mounted here \u2014 ${mounts.map((s) => `${s.id} (${s.projectPath})`).join(", ")}.`);
|
|
28960
|
+
}
|
|
28961
|
+
} catch {
|
|
28962
|
+
}
|
|
28963
|
+
return lines.length > 0 ? `${lines.join("\n")}
|
|
28964
|
+
|
|
28965
|
+
` : "";
|
|
28966
|
+
}
|
|
28967
|
+
function resolveComponentVariantGuidance(component) {
|
|
28968
|
+
if (!component.variant) return null;
|
|
28969
|
+
const byId = new Map(loadProjectVariants().map((v) => [v.id, v]));
|
|
28970
|
+
return resolveVariantGuidance(component, loadComponentSpecs(), byId);
|
|
28971
|
+
}
|
|
28972
|
+
function qualifiedComponentId(id) {
|
|
28973
|
+
const ids = loadComponentSpecs().map((c) => c.id);
|
|
28974
|
+
if (ids.includes(id)) return id;
|
|
28975
|
+
const bare = id.startsWith("::") ? id.slice(2) : id;
|
|
28976
|
+
if (ids.includes(bare)) return bare;
|
|
28977
|
+
const chained = ids.filter((known) => known.endsWith(`::${bare}`));
|
|
28978
|
+
return chained.length === 1 ? chained[0] : id;
|
|
28979
|
+
}
|
|
28980
|
+
function publicInterfacesReplacement(subsystem, stored, list) {
|
|
28981
|
+
const identity = (pi) => pi.component ? `${pi.component}${pi.interface ? `.${pi.interface}` : ""}` : `${pi.type} ${pi.details}`;
|
|
28982
|
+
const restated = /* @__PURE__ */ new Map();
|
|
28983
|
+
for (const pi of list) restated.set(identity(pi), (restated.get(identity(pi)) ?? 0) + 1);
|
|
28984
|
+
const twice = [...restated].filter(([, n]) => n > 1).map(([key]) => `"${key}"`);
|
|
28985
|
+
if (twice.length) {
|
|
28986
|
+
return `Refusing to set the public interfaces of subsystem "${subsystem}": the list names ${twice.join(", ")} more than once. An entry is identified by the component and interface it is bound to \u2014 or, not yet bound, by its type and details \u2014 so two entries sharing one are the same entry stated twice. Drop the duplicate. Nothing was written.`;
|
|
28987
|
+
}
|
|
28988
|
+
const seen = /* @__PURE__ */ new Map();
|
|
28989
|
+
const deletes = [];
|
|
28990
|
+
for (const pi of stored) {
|
|
28991
|
+
const key = identity(pi);
|
|
28992
|
+
seen.set(key, (seen.get(key) ?? 0) + 1);
|
|
28993
|
+
if (seen.get(key) > (restated.get(key) ?? 0)) {
|
|
28994
|
+
deletes.push(pi.component ? { component: pi.component, interface: pi.interface, action: "delete" } : { type: pi.type, details: pi.details, action: "delete" });
|
|
28995
|
+
}
|
|
28996
|
+
}
|
|
28997
|
+
const restatedEntries = list.map((pi) => {
|
|
28998
|
+
const prev = stored.find((s) => identity(s) === identity(pi));
|
|
28999
|
+
const dropped = prev ? Object.keys(prev).filter((f) => prev[f] !== void 0 && pi[f] === void 0) : [];
|
|
29000
|
+
return dropped.length ? { ...pi, unset: dropped } : pi;
|
|
29001
|
+
});
|
|
29002
|
+
return { publicInterfaces: [...deletes, ...restatedEntries] };
|
|
29003
|
+
}
|
|
29004
|
+
function text(content) {
|
|
29005
|
+
return { content: [{ type: "text", text: content }] };
|
|
29006
|
+
}
|
|
29007
|
+
function json(value) {
|
|
29008
|
+
return text(JSON.stringify(value, null, 2));
|
|
29009
|
+
}
|
|
29010
|
+
function errText(message) {
|
|
29011
|
+
return { content: [{ type: "text", text: `Error: ${message.replace(/^(?:\s*Error:\s*)+/, "")}` }], isError: true };
|
|
29012
|
+
}
|
|
29013
|
+
function errMessage(e) {
|
|
29014
|
+
return errText(e instanceof Error ? e.message : String(e));
|
|
29015
|
+
}
|
|
29016
|
+
var TYPE_REF_GRAMMAR = (what) => `${what}: a primitive/builtin or a defined type id (qualified across subsystems, e.g. "billing.Invoice" or "billing::Invoice"). Generics, arrays and UNIONS are all read, at any depth: "Invoice | null" (the commonest shape there is), "Invoice | undefined", "Invoice | Receipt", "Promise<Invoice | null>", "Map<string, Invoice | null>", "Invoice[] | null", "(Invoice | null)[]". Every identifier the string names must resolve \u2014 a union of two defined types means BOTH must exist. A union of string literals ("read" | "write") names no type and resolves to nothing.`;
|
|
29017
|
+
function structured(content, data) {
|
|
29018
|
+
return { content: [{ type: "text", text: content }], structuredContent: data };
|
|
29019
|
+
}
|
|
29020
|
+
function jsonStructured(value) {
|
|
29021
|
+
return structured(JSON.stringify(value, null, 2), value);
|
|
29022
|
+
}
|
|
29023
|
+
var staleServerOutput = {
|
|
29024
|
+
staleServer: import_zod11.z.boolean().optional().describe(
|
|
29025
|
+
"True when the wairon build on disk changed after this server started \u2014 the structured twin of the text answer's STALE SERVER banner. When the rebuild left the spec and tool schemas alone, writes still go through; reconnect to run the new code."
|
|
29026
|
+
),
|
|
29027
|
+
writesRefused: import_zod11.z.boolean().optional().describe(
|
|
29028
|
+
"True when the rebuild changed a spec or tool schema (or its schemas could not be compared): every spec write through this server is refused and writes nothing, because it would silently drop the fields only the new build knows. The human has to reconnect the server (/mcp reconnect wairon)."
|
|
29029
|
+
)
|
|
29030
|
+
};
|
|
29031
|
+
function renderChangeReport(report) {
|
|
29032
|
+
const lines = [report.dryRun ? `DRY RUN \u2014 nothing was written. ${report.summary}` : report.summary];
|
|
29033
|
+
for (const change of report.changes) {
|
|
29034
|
+
const to = change.after === void 0 ? "" : `: ${JSON.stringify(change.after)}`;
|
|
29035
|
+
const from = change.before === void 0 ? "" : ` (was ${JSON.stringify(change.before)})`;
|
|
29036
|
+
lines.push(`- ${change.path} ${report.dryRun ? `would be ${change.change}` : change.change}${to}${from}`);
|
|
29037
|
+
}
|
|
29038
|
+
if (report.ineffective.length) {
|
|
29039
|
+
lines.push("", "NO EFFECT:", ...report.ineffective.map((i) => `- ${i}`));
|
|
29040
|
+
}
|
|
29041
|
+
if (report.notices.length) lines.push("", "NOTICE:", ...report.notices.map((n) => `- ${n}`));
|
|
29042
|
+
lines.push(...renderTestsToRevisit(report.testsToRevisit));
|
|
29043
|
+
return lines.join("\n");
|
|
29044
|
+
}
|
|
29045
|
+
var SPEC_KINDS = ["system", "subsystem", "component", "interface", "implementation", "type"];
|
|
29046
|
+
var STATUS_VALUES = ["draft", "design", "complete"];
|
|
29047
|
+
var testsToRevisitOutput = import_zod11.z.array(import_zod11.z.object({
|
|
29048
|
+
method: import_zod11.z.string().describe("The contract method this write changed or deleted."),
|
|
29049
|
+
symbol: import_zod11.z.string().describe("The code-level name searched for: the method's `symbol` when it declares one, else its name."),
|
|
29050
|
+
imported: import_zod11.z.array(import_zod11.z.string()).describe("Test files that IMPORT that symbol \u2014 the high-confidence list, because an import is a binding and not a coincidence."),
|
|
29051
|
+
mentioned: import_zod11.z.array(import_zod11.z.string()).describe("Test files that name the symbol without importing it \u2014 usually a test driving it through a portal. Empty when `indiscriminate` is true."),
|
|
29052
|
+
indiscriminate: import_zod11.z.boolean().describe("True when the bare name matched more than a tenth of the test suite, so the mention list was withheld as noise.")
|
|
29053
|
+
})).describe(
|
|
29054
|
+
"The tests that encode a method this write changed or deleted, one entry per such method. Empty when the write touched no method, when the project declares no `rules.conformance.testRoots`, or when nothing references them. Read it before promising that existing tests still pass."
|
|
29055
|
+
);
|
|
29056
|
+
function renderTestsToRevisit(entries) {
|
|
29057
|
+
if (!entries.length) return [];
|
|
29058
|
+
const lines = ["", "TESTS TO REVISIT:"];
|
|
29059
|
+
for (const entry of entries) {
|
|
29060
|
+
lines.push(`- ${entry.method} (searched as "${entry.symbol}")`);
|
|
29061
|
+
if (entry.imported.length) lines.push(` imports it: ${entry.imported.join(", ")}`);
|
|
29062
|
+
if (entry.mentioned.length) lines.push(` names it: ${entry.mentioned.join(", ")}`);
|
|
29063
|
+
if (entry.indiscriminate) {
|
|
29064
|
+
lines.push(" names it: withheld \u2014 the bare name matches more than a tenth of the suite, so the list carries no signal.");
|
|
29065
|
+
}
|
|
29066
|
+
}
|
|
29067
|
+
return lines;
|
|
29068
|
+
}
|
|
29069
|
+
var specWriteReceiptOutput = {
|
|
29070
|
+
kind: import_zod11.z.enum(SPEC_KINDS).describe("The spec kind written."),
|
|
29071
|
+
id: import_zod11.z.string().describe('The id the spec is stored under; "system" for the L0 singleton.'),
|
|
29072
|
+
name: import_zod11.z.string().describe("The spec's display name as written."),
|
|
29073
|
+
replacedExisting: import_zod11.z.boolean().describe(
|
|
29074
|
+
"True when a spec already held this id and the call re-authored it in place rather than adding one. The notices then say what was carried forward, removed or cleared by the restatement."
|
|
29075
|
+
),
|
|
29076
|
+
status: import_zod11.z.enum(STATUS_VALUES).optional().describe(
|
|
29077
|
+
"The lifecycle status written. Absent for the L0 and a type, which carry none."
|
|
29078
|
+
),
|
|
29079
|
+
notices: import_zod11.z.array(import_zod11.z.string()).describe(
|
|
29080
|
+
"The notice lines the text answer lists, one per entry: what was carried forward, what a restatement removed, what an omission cleared, and every gate warning the write raised. Empty for a clean create."
|
|
29081
|
+
),
|
|
29082
|
+
scaffoldedProjectPath: import_zod11.z.string().optional().describe(
|
|
29083
|
+
"The child project directory a chained subsystem's create scaffolded; absent for every other write."
|
|
29084
|
+
),
|
|
29085
|
+
testsToRevisit: testsToRevisitOutput,
|
|
29086
|
+
...staleServerOutput
|
|
29087
|
+
};
|
|
29088
|
+
function writeReceipt(sentence, receipt) {
|
|
29089
|
+
const noticeBlock = receipt.notices.length ? `
|
|
29090
|
+
|
|
29091
|
+
NOTICE:
|
|
29092
|
+
- ${receipt.notices.join("\n- ")}` : "";
|
|
29093
|
+
const testsBlock = renderTestsToRevisit(receipt.testsToRevisit).join("\n");
|
|
29094
|
+
return structured(`${sentence}${noticeBlock}${testsBlock ? `
|
|
29095
|
+
${testsBlock}` : ""}`, receipt);
|
|
29096
|
+
}
|
|
29097
|
+
var specDeletionOutput = {
|
|
29098
|
+
kind: import_zod11.z.enum(SPEC_KINDS).describe("The spec kind deleted."),
|
|
29099
|
+
id: import_zod11.z.string().describe("The id the spec was stored under."),
|
|
29100
|
+
deleted: import_zod11.z.boolean().describe("True when a spec document was removed."),
|
|
29101
|
+
testsToRevisit: testsToRevisitOutput,
|
|
29102
|
+
...staleServerOutput
|
|
29103
|
+
};
|
|
29104
|
+
var specChangeOutput = {
|
|
29105
|
+
path: import_zod11.z.string().describe("The dotted path the change addresses, with names and indexes."),
|
|
29106
|
+
change: import_zod11.z.enum(["set", "added", "removed", "cleared", "renumbered", "relocated"]).describe(
|
|
29107
|
+
"What happened at that path. A narrative is compared by step identity: `renumbered` is a run of unchanged steps shifted by an insert or delete (before/after name the ranges), `relocated` a jump field that followed its target to the target's new number."
|
|
29108
|
+
),
|
|
29109
|
+
before: import_zod11.z.string().optional().describe("The previous value, summarized; absent when there was none."),
|
|
29110
|
+
after: import_zod11.z.string().optional().describe("The new value, summarized; absent when there is none.")
|
|
29111
|
+
};
|
|
29112
|
+
var specChangeReportOutput = {
|
|
29113
|
+
kind: import_zod11.z.enum(SPEC_KINDS).describe("The spec kind written."),
|
|
29114
|
+
id: import_zod11.z.string().describe("The stored spec's id."),
|
|
29115
|
+
written: import_zod11.z.boolean().describe("False when the merged spec equals what is stored: nothing reached disk."),
|
|
29116
|
+
dryRun: import_zod11.z.boolean().describe(
|
|
29117
|
+
"True when the caller asked what the delta WOULD do. `written` is false for a dry run exactly as it is for a delta that changed nothing, and only this field tells the two apart."
|
|
29118
|
+
),
|
|
29119
|
+
changes: import_zod11.z.array(import_zod11.z.object(specChangeOutput)).describe(
|
|
29120
|
+
"Every change the write made; empty exactly when `written` is false."
|
|
29121
|
+
),
|
|
29122
|
+
ineffective: import_zod11.z.array(import_zod11.z.string()).describe(
|
|
29123
|
+
"Every path the delta named that the write did not act on, each with why. Read it: a nested typo the permissive delta cannot refuse shows up here and nowhere else."
|
|
29124
|
+
),
|
|
29125
|
+
notices: import_zod11.z.array(import_zod11.z.string()).describe("Store placement notices, gate warnings and delta notices."),
|
|
29126
|
+
summary: import_zod11.z.string().describe("One line for people."),
|
|
29127
|
+
testsToRevisit: testsToRevisitOutput,
|
|
29128
|
+
...staleServerOutput
|
|
29129
|
+
};
|
|
29130
|
+
var validationIssueOutput = {
|
|
29131
|
+
severity: import_zod11.z.enum(["error", "warning"]).describe("The finding's severity after project overrides."),
|
|
29132
|
+
code: import_zod11.z.string().describe("The rule code, UPPER_SNAKE \u2014 the stable handle to filter and suppress by."),
|
|
29133
|
+
message: import_zod11.z.string().describe("What is wrong, named."),
|
|
29134
|
+
agentId: import_zod11.z.string().optional().describe("The agent the finding concerns, when it concerns one."),
|
|
29135
|
+
specId: import_zod11.z.string().optional().describe("The spec the finding concerns, when it concerns one."),
|
|
29136
|
+
draftContext: import_zod11.z.boolean().optional().describe(
|
|
29137
|
+
"True when the finding was raised against a draft/design spec \u2014 what the --ci gate waives."
|
|
29138
|
+
),
|
|
29139
|
+
surfaceResolved: import_zod11.z.boolean().optional().describe(
|
|
29140
|
+
"True when the finding was verified against a vendored surface snapshot, so it is a contract verdict rather than a resolution failure."
|
|
29141
|
+
)
|
|
29142
|
+
};
|
|
29143
|
+
var validateTreeOutput = {
|
|
29144
|
+
valid: import_zod11.z.boolean().describe("False when the tree holds at least one error."),
|
|
29145
|
+
errors: import_zod11.z.array(import_zod11.z.object(validationIssueOutput)).describe("Every finding of severity error."),
|
|
29146
|
+
warnings: import_zod11.z.array(import_zod11.z.object(validationIssueOutput)).describe("Every finding of severity warning."),
|
|
29147
|
+
resolvedThrough: import_zod11.z.object({
|
|
29148
|
+
root: import_zod11.z.string().describe("The top root that was validated."),
|
|
29149
|
+
scope: import_zod11.z.string().describe("The mount chain the verdict was scoped to.")
|
|
29150
|
+
}).optional().describe(
|
|
29151
|
+
"Present when a chained subproject's verdict was resolved through its parent; absent when the tree was validated on its own."
|
|
29152
|
+
),
|
|
29153
|
+
...staleServerOutput
|
|
29154
|
+
};
|
|
29155
|
+
var getSpecOutput = {
|
|
29156
|
+
kind: import_zod11.z.enum(SPEC_KINDS).describe("The kind of spec read."),
|
|
29157
|
+
id: import_zod11.z.string().describe("The id it was read by."),
|
|
29158
|
+
spec: import_zod11.z.record(import_zod11.z.unknown()).describe(
|
|
29159
|
+
"The stored spec, exactly as the text block renders it \u2014 minus the two derived markers below, which the text folds in and this answer keeps separate so nothing derived can be mistaken for stored."
|
|
29160
|
+
),
|
|
29161
|
+
partialResult: import_zod11.z.object({
|
|
29162
|
+
shown: import_zod11.z.array(import_zod11.z.string()).describe("The method names this answer carries."),
|
|
29163
|
+
omitted: import_zod11.z.number().describe("How many of the spec's methods are NOT in it."),
|
|
29164
|
+
warning: import_zod11.z.string().describe("Why it must never be re-authored from.")
|
|
29165
|
+
}).optional().describe(
|
|
29166
|
+
"Present only when `methods` filtered the read. A filtered spec is a partial one: sdd_define_interface and sdd_write_narrative REPLACE the method list, so re-authoring from it would delete every method left out."
|
|
29167
|
+
),
|
|
29168
|
+
variantGuidance: import_zod11.z.object({
|
|
29169
|
+
variant: import_zod11.z.string().describe("The variant id the component declares."),
|
|
29170
|
+
base: import_zod11.z.string().describe("The core stereotype it specializes."),
|
|
29171
|
+
guidance: import_zod11.z.string().describe("How to implement a component of this variant."),
|
|
29172
|
+
siblings: import_zod11.z.array(import_zod11.z.string()).describe("Other components of the same variant, to implement alike."),
|
|
29173
|
+
target: import_zod11.z.string().optional().describe("The target language the variant scopes itself to, if any."),
|
|
29174
|
+
profile: import_zod11.z.string().optional().describe("The architectural profile it scopes itself to, if any.")
|
|
29175
|
+
}).optional().describe(
|
|
29176
|
+
"Derived, read-only guidance for a variant-tagged component \u2014 resolved from the variant registry, not part of the spec. Never write it back."
|
|
29177
|
+
),
|
|
29178
|
+
...staleServerOutput
|
|
29179
|
+
};
|
|
29180
|
+
var FINGERPRINT_VERSION = 1;
|
|
29181
|
+
function schemaShape(schema, seen = /* @__PURE__ */ new Map()) {
|
|
29182
|
+
const def = schema?._def;
|
|
29183
|
+
if (!def) return null;
|
|
29184
|
+
const known = seen.get(schema);
|
|
29185
|
+
if (known !== void 0) return { ref: known };
|
|
29186
|
+
seen.set(schema, seen.size);
|
|
29187
|
+
const inner = (s) => schemaShape(s, seen);
|
|
29188
|
+
const kind = String(def.typeName);
|
|
29189
|
+
switch (kind) {
|
|
29190
|
+
case "ZodObject": {
|
|
29191
|
+
const shape = typeof def.shape === "function" ? def.shape() : def.shape;
|
|
29192
|
+
return {
|
|
29193
|
+
kind,
|
|
29194
|
+
keys: Object.keys(shape).sort().map((key) => [key, inner(shape[key])]),
|
|
29195
|
+
unknownKeys: def.unknownKeys,
|
|
29196
|
+
catchall: inner(def.catchall)
|
|
29197
|
+
};
|
|
29198
|
+
}
|
|
29199
|
+
case "ZodArray":
|
|
29200
|
+
return { kind, of: inner(def.type), min: def.minLength?.value, max: def.maxLength?.value };
|
|
29201
|
+
case "ZodOptional":
|
|
29202
|
+
case "ZodNullable":
|
|
29203
|
+
case "ZodReadonly":
|
|
29204
|
+
case "ZodBranded":
|
|
29205
|
+
case "ZodCatch":
|
|
29206
|
+
return { kind, of: inner(def.innerType ?? def.type) };
|
|
29207
|
+
case "ZodDefault":
|
|
29208
|
+
return { kind, of: inner(def.innerType), default: JSON.stringify(def.defaultValue?.()) };
|
|
29209
|
+
case "ZodEffects":
|
|
29210
|
+
return { kind, effect: def.effect?.type, of: inner(def.schema) };
|
|
29211
|
+
case "ZodLazy":
|
|
29212
|
+
return { kind, of: inner(def.getter()) };
|
|
29213
|
+
case "ZodUnion":
|
|
29214
|
+
case "ZodDiscriminatedUnion": {
|
|
29215
|
+
const options = def.options instanceof Map ? [...def.options.values()] : def.options;
|
|
29216
|
+
return { kind, discriminator: def.discriminator, of: options.map(inner) };
|
|
29217
|
+
}
|
|
29218
|
+
case "ZodIntersection":
|
|
29219
|
+
return { kind, left: inner(def.left), right: inner(def.right) };
|
|
29220
|
+
case "ZodTuple":
|
|
29221
|
+
return { kind, of: def.items.map(inner), rest: inner(def.rest) };
|
|
29222
|
+
case "ZodRecord":
|
|
29223
|
+
case "ZodMap":
|
|
29224
|
+
return { kind, key: inner(def.keyType), value: inner(def.valueType) };
|
|
29225
|
+
case "ZodSet":
|
|
29226
|
+
return { kind, of: inner(def.valueType) };
|
|
29227
|
+
case "ZodEnum":
|
|
29228
|
+
return { kind, values: def.values };
|
|
29229
|
+
case "ZodNativeEnum":
|
|
29230
|
+
return { kind, values: def.values };
|
|
29231
|
+
case "ZodLiteral":
|
|
29232
|
+
return { kind, value: def.value };
|
|
29233
|
+
case "ZodPipeline":
|
|
29234
|
+
return { kind, in: inner(def.in), out: inner(def.out) };
|
|
29235
|
+
default:
|
|
29236
|
+
return { kind, checks: def.checks?.map((c) => [c.kind, c.value ?? null]) ?? [] };
|
|
29237
|
+
}
|
|
29238
|
+
}
|
|
29239
|
+
var runningFingerprint;
|
|
29240
|
+
function schemaFingerprint() {
|
|
29241
|
+
if (runningFingerprint !== void 0) return runningFingerprint;
|
|
29242
|
+
const specs = {
|
|
29243
|
+
system: schemaShape(SystemSpecSchema),
|
|
29244
|
+
subsystem: schemaShape(SubsystemSpecSchema),
|
|
29245
|
+
component: schemaShape(ComponentSpecSchema),
|
|
29246
|
+
interface: schemaShape(InterfaceSpecSchema),
|
|
29247
|
+
implementation: schemaShape(ImplementationSpecSchema),
|
|
29248
|
+
type: schemaShape(TypeSpecSchema)
|
|
29249
|
+
};
|
|
29250
|
+
const registered = createMcpServer()._registeredTools;
|
|
29251
|
+
const tools = [...SPEC_WRITE_TOOLS].sort().map((name) => [name, schemaShape(registered[name]?.inputSchema)]);
|
|
29252
|
+
runningFingerprint = crypto4.createHash("sha256").update(JSON.stringify({ version: FINGERPRINT_VERSION, specs, tools })).digest("hex");
|
|
29253
|
+
return runningFingerprint;
|
|
29254
|
+
}
|
|
29255
|
+
var RECONNECT = "The human has to reconnect this MCP server (in Claude Code: /mcp reconnect wairon) \u2014 an agent cannot.";
|
|
29256
|
+
function assessBuildFreshness(stamp) {
|
|
29257
|
+
if (!isBuildStale(stamp)) return { state: "fresh", writesRefused: false, reason: "" };
|
|
29258
|
+
const running = schemaFingerprint();
|
|
29259
|
+
let onDisk;
|
|
29260
|
+
try {
|
|
29261
|
+
onDisk = readBuildFingerprint(stamp);
|
|
29262
|
+
} catch (e) {
|
|
29263
|
+
return {
|
|
29264
|
+
state: "unreadable",
|
|
29265
|
+
writesRefused: true,
|
|
29266
|
+
reason: `the wairon build on disk changed after this MCP server started, and its schemas could not be compared (${e instanceof Error ? e.message : String(e)}), so a schema change cannot be ruled out. Every spec write through this server is REFUSED and writes nothing. ${RECONNECT}`
|
|
29267
|
+
};
|
|
29268
|
+
}
|
|
29269
|
+
if (onDisk === running) {
|
|
29270
|
+
return {
|
|
29271
|
+
state: "build-changed",
|
|
29272
|
+
writesRefused: false,
|
|
29273
|
+
reason: "the wairon build on disk changed after this MCP server started. Its spec and tool schemas did not change, so spec writes still go through; reconnect the server (/mcp reconnect wairon) to run the new code."
|
|
29274
|
+
};
|
|
29275
|
+
}
|
|
29276
|
+
return {
|
|
29277
|
+
state: "schema-changed",
|
|
29278
|
+
writesRefused: true,
|
|
29279
|
+
reason: `the wairon build on disk changed after this MCP server started, and its spec or tool schemas changed with it. A write through this server would silently drop the fields only the new build knows, so every spec write is REFUSED and writes nothing. ${RECONNECT}`
|
|
29280
|
+
};
|
|
29281
|
+
}
|
|
29282
|
+
function markStale(result, freshness = assessedAsChanged, placement = "trail") {
|
|
29283
|
+
const marked = result.structuredContent ? {
|
|
29284
|
+
...result,
|
|
29285
|
+
structuredContent: {
|
|
29286
|
+
...result.structuredContent,
|
|
29287
|
+
staleServer: true,
|
|
29288
|
+
...freshness.writesRefused ? { writesRefused: true } : {}
|
|
29289
|
+
}
|
|
29290
|
+
} : result;
|
|
29291
|
+
const banner = `\u26A0 STALE SERVER: ${freshness.reason}`;
|
|
29292
|
+
const first = marked.content?.[0];
|
|
29293
|
+
if (first && first.type === "text") {
|
|
29294
|
+
const textWithBanner = placement === "lead" ? `${banner}
|
|
29295
|
+
|
|
29296
|
+
${first.text}` : `${first.text}
|
|
29297
|
+
|
|
29298
|
+
${banner}`;
|
|
29299
|
+
return { ...marked, content: [{ ...first, text: textWithBanner }, ...marked.content.slice(1)] };
|
|
29300
|
+
}
|
|
29301
|
+
return marked;
|
|
29302
|
+
}
|
|
29303
|
+
var assessedAsChanged = {
|
|
29304
|
+
state: "build-changed",
|
|
29305
|
+
writesRefused: false,
|
|
29306
|
+
reason: "the wairon build on disk changed after this MCP server started. Reconnect the server (/mcp reconnect wairon) before further spec edits \u2014 a write through a stale server can silently drop fields a newer schema introduced."
|
|
29307
|
+
};
|
|
29308
|
+
var serverBuildStamps = /* @__PURE__ */ new WeakMap();
|
|
29309
|
+
var SERVER_BUILD_STAMP = captureBuildStamp(__filename);
|
|
29310
|
+
var SPEC_WRITE_TOOLS = /* @__PURE__ */ new Set([
|
|
29311
|
+
"sdd_initialize_system",
|
|
29312
|
+
"sdd_add_subsystem",
|
|
29313
|
+
"sdd_set_public_interfaces",
|
|
29314
|
+
"sdd_set_subsystem_project_path",
|
|
29315
|
+
"sdd_move_subsystem_project",
|
|
29316
|
+
"sdd_externalize_subsystem",
|
|
29317
|
+
"sdd_internalize_subsystem",
|
|
29318
|
+
"sdd_rename_component",
|
|
29319
|
+
"sdd_rename_method",
|
|
29320
|
+
"sdd_add_component",
|
|
29321
|
+
"sdd_define_interface",
|
|
29322
|
+
"sdd_set_endpoints",
|
|
29323
|
+
"sdd_write_narrative",
|
|
29324
|
+
"sdd_add_type",
|
|
29325
|
+
"sdd_delete_spec",
|
|
29326
|
+
"sdd_update_spec",
|
|
29327
|
+
"sdd_move_methods"
|
|
29328
|
+
]);
|
|
29329
|
+
var listChangedEmitters = /* @__PURE__ */ new WeakMap();
|
|
29330
|
+
function reg(server, name, config, cb) {
|
|
29331
|
+
const guarded = (args) => {
|
|
29332
|
+
const freshness = assessBuildFreshness(serverBuildStamps.get(server) ?? null);
|
|
29333
|
+
if (freshness.writesRefused && SPEC_WRITE_TOOLS.has(name)) {
|
|
29334
|
+
return errText(`${name} refused \u2014 ${freshness.reason}`);
|
|
29335
|
+
}
|
|
29336
|
+
const answered = cb(args);
|
|
29337
|
+
const result = freshness.state === "fresh" ? answered : markStale(answered, freshness, name === "sdd_get_status" ? "lead" : "trail");
|
|
29338
|
+
if (result.isError !== true && SPEC_WRITE_TOOLS.has(name)) listChangedEmitters.get(server)?.();
|
|
29339
|
+
return result;
|
|
29340
|
+
};
|
|
29341
|
+
const strictConfig = config.inputSchema ? { ...config, inputSchema: import_zod11.z.object(config.inputSchema).strict() } : config;
|
|
29342
|
+
server.registerTool(name, strictConfig, guarded);
|
|
29343
|
+
}
|
|
29344
|
+
var statusInput = import_zod11.z.enum(STATUS_VALUES).optional().describe(
|
|
29345
|
+
"The spec's lifecycle status. Omitted means draft for a NEW spec and the status already stored for a re-authoring, so a restatement never reopens a frozen spec. State it to author straight at design or complete instead of promoting afterwards. A status that would LOWER the stored one is refused \u2014 reopening a spec for revision is sdd_update_spec's job, which sets the demotion deliberately."
|
|
29346
|
+
);
|
|
29347
|
+
var SKILL_RESOURCE_MIME = "text/markdown";
|
|
29348
|
+
function skillIdFromResourceUri(uri) {
|
|
29349
|
+
try {
|
|
29350
|
+
const u = new URL(uri);
|
|
29351
|
+
return u.host || u.pathname.replace(/^\/+/, "") || uri;
|
|
29352
|
+
} catch {
|
|
29353
|
+
return uri;
|
|
29354
|
+
}
|
|
29355
|
+
}
|
|
29356
|
+
var AGENT_BRIEF_SCHEME = "wairon-agent://";
|
|
29357
|
+
function listAgentBriefResources() {
|
|
29358
|
+
try {
|
|
29359
|
+
return resolveAgentTopology().map((a) => ({
|
|
29360
|
+
uri: `${AGENT_BRIEF_SCHEME}${a.id}`,
|
|
29361
|
+
name: a.name,
|
|
29362
|
+
description: a.description,
|
|
29363
|
+
mimeType: SKILL_RESOURCE_MIME
|
|
29364
|
+
}));
|
|
29365
|
+
} catch {
|
|
29366
|
+
return [];
|
|
29367
|
+
}
|
|
29368
|
+
}
|
|
29369
|
+
function renderAgentBriefMarkdown(brief) {
|
|
29370
|
+
const lines = [
|
|
29371
|
+
`# Agent brief: ${brief.agentId}`,
|
|
29372
|
+
"",
|
|
29373
|
+
`- **Name**: ${brief.name}`,
|
|
29374
|
+
`- **Template**: ${brief.template}`,
|
|
29375
|
+
...brief.domainRoot ? [`- **Domain root**: ${brief.domainRoot}`] : [],
|
|
29376
|
+
`- **Owned paths**: ${brief.ownedPaths.join(", ")}`,
|
|
29377
|
+
...brief.readPaths?.length ? [`- **Read paths**: ${brief.readPaths.join(", ")}`] : [],
|
|
29378
|
+
"",
|
|
29379
|
+
"## Instructions",
|
|
29380
|
+
"",
|
|
29381
|
+
brief.instructions
|
|
29382
|
+
];
|
|
29383
|
+
if (brief.variantGuidance && !brief.instructions.includes(brief.variantGuidance)) {
|
|
29384
|
+
lines.push("", "## Variant guidance", "", brief.variantGuidance);
|
|
29385
|
+
}
|
|
29386
|
+
if (brief.budget && brief.profile) {
|
|
29387
|
+
lines.push(
|
|
29388
|
+
"",
|
|
29389
|
+
"## Execution budget",
|
|
29390
|
+
"",
|
|
29391
|
+
...summarize(brief.budget, brief.profile),
|
|
29392
|
+
"",
|
|
29393
|
+
"Advisory \u2014 apply these when spawning. Map the capability tier onto your host tool's models; a tool that cannot express a field should ignore it rather than approximate it."
|
|
29394
|
+
);
|
|
29395
|
+
}
|
|
29396
|
+
return `${lines.join("\n")}
|
|
29397
|
+
`;
|
|
29398
|
+
}
|
|
29399
|
+
function registerSkillResources(server, listChanged) {
|
|
29400
|
+
server.server.registerCapabilities({ resources: listChanged ? { listChanged: true } : {} });
|
|
29401
|
+
server.server.setRequestHandler(import_types4.ListResourcesRequestSchema, () => ({
|
|
29402
|
+
resources: [
|
|
29403
|
+
...listResources().map((d) => ({
|
|
29404
|
+
uri: d.resourceUri,
|
|
29405
|
+
name: d.name,
|
|
29406
|
+
description: d.description,
|
|
29407
|
+
mimeType: SKILL_RESOURCE_MIME
|
|
29408
|
+
})),
|
|
29409
|
+
...listAgentBriefResources()
|
|
29410
|
+
]
|
|
29411
|
+
}));
|
|
29412
|
+
server.server.setRequestHandler(import_types4.ReadResourceRequestSchema, (request) => {
|
|
29413
|
+
const uri = request.params.uri;
|
|
29414
|
+
if (uri.startsWith(AGENT_BRIEF_SCHEME)) {
|
|
29415
|
+
try {
|
|
29416
|
+
const brief = composeAgentBrief(uri.slice(AGENT_BRIEF_SCHEME.length));
|
|
29417
|
+
return { contents: [{ uri, mimeType: SKILL_RESOURCE_MIME, text: renderAgentBriefMarkdown(brief) }] };
|
|
29418
|
+
} catch (e) {
|
|
29419
|
+
throw new import_types4.McpError(import_types4.ErrorCode.InvalidParams, e instanceof Error ? e.message : String(e));
|
|
29420
|
+
}
|
|
29421
|
+
}
|
|
29422
|
+
try {
|
|
29423
|
+
const content = readResource(skillIdFromResourceUri(uri));
|
|
29424
|
+
return { contents: [{ uri, mimeType: SKILL_RESOURCE_MIME, text: content }] };
|
|
29425
|
+
} catch (e) {
|
|
29426
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
29427
|
+
throw new import_types4.McpError(import_types4.ErrorCode.InvalidParams, message);
|
|
29428
|
+
}
|
|
29429
|
+
});
|
|
29430
|
+
}
|
|
29431
|
+
function registerSkillPrompts(server, listChanged) {
|
|
29432
|
+
server.server.registerCapabilities({ prompts: listChanged ? { listChanged: true } : {} });
|
|
29433
|
+
server.server.setRequestHandler(import_types4.ListPromptsRequestSchema, () => ({
|
|
29434
|
+
prompts: listResources().map((d) => ({
|
|
29435
|
+
name: d.id,
|
|
29436
|
+
title: d.name,
|
|
29437
|
+
description: d.description
|
|
29438
|
+
}))
|
|
29439
|
+
}));
|
|
29440
|
+
server.server.setRequestHandler(import_types4.GetPromptRequestSchema, (request) => {
|
|
29441
|
+
const name = request.params.name;
|
|
29442
|
+
try {
|
|
29443
|
+
const content = readResource(name);
|
|
29444
|
+
return {
|
|
29445
|
+
description: `The wairon "${name}" skill.`,
|
|
29446
|
+
messages: [{ role: "user", content: { type: "text", text: content } }]
|
|
29447
|
+
};
|
|
29448
|
+
} catch (e) {
|
|
29449
|
+
throw new import_types4.McpError(import_types4.ErrorCode.InvalidParams, e instanceof Error ? e.message : String(e));
|
|
29450
|
+
}
|
|
29451
|
+
});
|
|
29452
|
+
}
|
|
29453
|
+
function createMcpServer(options = {}) {
|
|
29454
|
+
const server = new import_mcp.McpServer({
|
|
29455
|
+
name: "wairon",
|
|
29456
|
+
version: WAIRON_VERSION
|
|
29457
|
+
}, {
|
|
29458
|
+
instructions: buildServerInstructions2()
|
|
29459
|
+
});
|
|
29460
|
+
serverBuildStamps.set(server, options.buildStamp === void 0 ? SERVER_BUILD_STAMP : options.buildStamp);
|
|
29461
|
+
if (!options.hostedTools) {
|
|
29462
|
+
listChangedEmitters.set(server, () => {
|
|
29463
|
+
try {
|
|
29464
|
+
void server.server.sendResourceListChanged().catch(() => {
|
|
29465
|
+
});
|
|
29466
|
+
void server.server.sendPromptListChanged().catch(() => {
|
|
29467
|
+
});
|
|
29468
|
+
} catch {
|
|
29469
|
+
}
|
|
29470
|
+
});
|
|
29471
|
+
}
|
|
29472
|
+
reg(
|
|
29473
|
+
server,
|
|
29474
|
+
"listAgents",
|
|
29475
|
+
{
|
|
29476
|
+
description: "List all AI agents registered in this project. Returns id, name, description, domainRoot, template, tags, and status for each agent. Filter by domainId to scope to one domain.",
|
|
29477
|
+
inputSchema: { domainId: import_zod11.z.string().optional() }
|
|
29478
|
+
},
|
|
29479
|
+
({ domainId }) => {
|
|
29480
|
+
try {
|
|
29481
|
+
const registry = loadRegistry();
|
|
29482
|
+
const agents = domainId ? registry.agents.filter((a) => a.domainRoot === domainId) : registry.agents;
|
|
29483
|
+
return json(agents.map((a) => ({
|
|
29484
|
+
id: a.id,
|
|
29485
|
+
name: a.name,
|
|
29486
|
+
description: a.description,
|
|
29487
|
+
domainRoot: a.domainRoot,
|
|
29488
|
+
template: a.template,
|
|
29489
|
+
tags: a.tags,
|
|
29490
|
+
status: a.status
|
|
29491
|
+
})));
|
|
29492
|
+
} catch (e) {
|
|
29493
|
+
return errText(String(e));
|
|
29494
|
+
}
|
|
29495
|
+
}
|
|
29496
|
+
);
|
|
29497
|
+
reg(
|
|
29498
|
+
server,
|
|
29499
|
+
"getAgent",
|
|
29500
|
+
{
|
|
29501
|
+
description: "Get full details of a specific agent by id, including ownership rules, context, and output targets.",
|
|
29502
|
+
inputSchema: { id: import_zod11.z.string() }
|
|
29503
|
+
},
|
|
29504
|
+
({ id }) => {
|
|
29505
|
+
try {
|
|
29506
|
+
const registry = loadRegistry();
|
|
29507
|
+
const agent = registry.agents.find((a) => a.id === id);
|
|
29508
|
+
if (!agent) return errText(`Agent "${id}" not found.`);
|
|
29509
|
+
return json(agent);
|
|
29510
|
+
} catch (e) {
|
|
29511
|
+
return errText(String(e));
|
|
29512
|
+
}
|
|
29513
|
+
}
|
|
29514
|
+
);
|
|
29515
|
+
reg(
|
|
29516
|
+
server,
|
|
29517
|
+
"listDomains",
|
|
29518
|
+
{
|
|
29519
|
+
description: "List all domains in this project: subsystem-derived (boundTo set) plus free-standing domains from .wai/topology.yaml."
|
|
29520
|
+
},
|
|
29521
|
+
() => {
|
|
29522
|
+
try {
|
|
29523
|
+
return json(resolveDomains2());
|
|
29524
|
+
} catch (e) {
|
|
29525
|
+
return errText(String(e));
|
|
29526
|
+
}
|
|
29527
|
+
}
|
|
29528
|
+
);
|
|
29529
|
+
reg(
|
|
29530
|
+
server,
|
|
29531
|
+
"validateTopology",
|
|
29532
|
+
{
|
|
29533
|
+
description: "Validate the project's agent topology. Returns errors and warnings (duplicate ids, overlapping ownership, missing paths, etc.). Supports optional subsystem scoping.",
|
|
29534
|
+
inputSchema: {
|
|
29535
|
+
subsystem: import_zod11.z.string().optional().describe("Only validate topology for agents under the specified subsystem")
|
|
29536
|
+
}
|
|
29537
|
+
},
|
|
29538
|
+
({ subsystem }) => {
|
|
29539
|
+
try {
|
|
29540
|
+
let registry = loadRegistry();
|
|
29541
|
+
if (subsystem) {
|
|
29542
|
+
registry = {
|
|
29543
|
+
...registry,
|
|
29544
|
+
agents: registry.agents.filter((a) => a.domainRoot === subsystem || a.domainRoot?.startsWith(`${subsystem}::`))
|
|
29545
|
+
};
|
|
29546
|
+
}
|
|
29547
|
+
const config = loadProjectConfig();
|
|
29548
|
+
if (!config) throw new ProjectNotInitializedError();
|
|
29549
|
+
const result = validateRegistry(registry, config.rules);
|
|
29550
|
+
return json({
|
|
29551
|
+
valid: result.issues.filter((i) => i.severity === "error").length === 0,
|
|
29552
|
+
errors: result.issues.filter((i) => i.severity === "error"),
|
|
29553
|
+
warnings: result.issues.filter((i) => i.severity === "warning")
|
|
29554
|
+
});
|
|
29555
|
+
} catch (e) {
|
|
29556
|
+
return errText(String(e));
|
|
29557
|
+
}
|
|
29558
|
+
}
|
|
29559
|
+
);
|
|
29560
|
+
reg(
|
|
29561
|
+
server,
|
|
29562
|
+
"getProjectConfig",
|
|
29563
|
+
{
|
|
29564
|
+
description: "Get the current project configuration (name, targets, rules, paths)."
|
|
29565
|
+
},
|
|
29566
|
+
() => {
|
|
29567
|
+
try {
|
|
29568
|
+
const config = loadProjectConfig();
|
|
29569
|
+
if (!config) throw new ProjectNotInitializedError();
|
|
29570
|
+
return json(config);
|
|
29571
|
+
} catch (e) {
|
|
29572
|
+
return errText(String(e));
|
|
29573
|
+
}
|
|
29574
|
+
}
|
|
29575
|
+
);
|
|
29576
|
+
const systemInput = {
|
|
29577
|
+
name: import_zod11.z.string().describe("Overarching name of the project/system"),
|
|
29578
|
+
vision: import_zod11.z.string().describe("Vision, mission, and core goals of the system"),
|
|
29579
|
+
boundaries: import_zod11.z.array(import_zod11.z.union([import_zod11.z.string(), import_zod11.z.object({ name: import_zod11.z.string(), description: import_zod11.z.string().optional() }).strict()])).optional().describe("System boundary rules or scope statements (strings or name/description objects)"),
|
|
29580
|
+
globalRequirements: import_zod11.z.array(import_zod11.z.union([import_zod11.z.string(), import_zod11.z.object({ description: import_zod11.z.string() }).strict()])).optional().describe("Global functional and non-functional requirements (strings or description objects)"),
|
|
29581
|
+
targetLanguage: import_zod11.z.string().optional().describe('Default implementation language for the system (e.g. "typescript", "rust", "python"). Subsystems may override. Enables language-aware validation.')
|
|
29582
|
+
};
|
|
29583
|
+
const systemInputFields = Object.keys(systemInput);
|
|
29584
|
+
reg(
|
|
29585
|
+
server,
|
|
29586
|
+
"sdd_initialize_system",
|
|
29587
|
+
{
|
|
29588
|
+
description: "Initialize the L0 System Specification (system.yaml). Re-running it on an existing system RE-AUTHORS it: the fields above are replaced, and everything this tool cannot express (databases, the project gateway publicInterfaces, diagram defaults) is carried forward. The answer carries a write receipt as structured content beside the sentence \u2014 what was written, and whether a system spec already existed \u2014 so a caller never has to read English to find out.",
|
|
29589
|
+
inputSchema: systemInput,
|
|
29590
|
+
outputSchema: specWriteReceiptOutput
|
|
29591
|
+
},
|
|
29592
|
+
({ name, vision, boundaries, globalRequirements, targetLanguage }) => {
|
|
29593
|
+
try {
|
|
29594
|
+
const restatement = {
|
|
29595
|
+
kind: "system",
|
|
29596
|
+
spec: {
|
|
29597
|
+
name,
|
|
29598
|
+
vision,
|
|
29599
|
+
boundaries: boundaries ?? [],
|
|
29600
|
+
globalRequirements: globalRequirements ?? [],
|
|
29601
|
+
...targetLanguage ? { targetLanguage } : {}
|
|
29602
|
+
},
|
|
29603
|
+
fields: systemInputFields
|
|
29604
|
+
};
|
|
29605
|
+
const receipt = writeSpec(restatement);
|
|
29606
|
+
return writeReceipt(
|
|
29607
|
+
`Successfully ${receipt.replacedExisting ? "re-authored" : "initialized"} L0 System Spec for "${name}".`,
|
|
29608
|
+
receipt
|
|
29609
|
+
);
|
|
29610
|
+
} catch (e) {
|
|
29611
|
+
return errMessage(e);
|
|
29612
|
+
}
|
|
29613
|
+
}
|
|
29614
|
+
);
|
|
29615
|
+
const subsystemInput = {
|
|
29616
|
+
id: import_zod11.z.string().describe("Lowercase identifier for the subsystem"),
|
|
29617
|
+
name: import_zod11.z.string().describe("Human-readable display name"),
|
|
29618
|
+
description: import_zod11.z.string().describe("Purpose and details of the subsystem"),
|
|
29619
|
+
publicInterfaces: import_zod11.z.array(import_zod11.z.object({
|
|
29620
|
+
type: import_zod11.z.enum(["REST", "GraphQL", "MessageBus", "RPC", "Custom"]),
|
|
29621
|
+
details: import_zod11.z.string(),
|
|
29622
|
+
component: import_zod11.z.string().optional().describe("The L2 component id that realizes this interface (this subsystem's published surface)"),
|
|
29623
|
+
interface: import_zod11.z.string().optional().describe("Optional L3 interface id on that component backing this entry"),
|
|
29624
|
+
consumers: import_zod11.z.array(import_zod11.z.string()).optional().describe("The subsystem ids this surface is published to. Absent, any subsystem may depend on the component; present, only these may \u2014 when every entry publishing the component names consumers, the union of the lists is the whole set, and a dependency from any other subsystem is CROSS_SUBSYSTEM_UNLISTED_CONSUMER. Every id must name a subsystem of the tree (PUBLIC_INTERFACE_UNKNOWN_CONSUMER).")
|
|
29625
|
+
}).strict()).optional().describe("Public entrypoints exposed by this subsystem, each bound to a realizing component"),
|
|
29626
|
+
projectPath: import_zod11.z.string().optional().describe("Relative path to external project root for subsystem chaining"),
|
|
29627
|
+
targetLanguage: import_zod11.z.string().optional().describe("Override of the system-level targetLanguage for this subsystem"),
|
|
29628
|
+
profile: import_zod11.z.string().optional().describe("Architectural profile override for this subsystem (built-ins: backend, frontend-reactive, frontend-controller, lowlevel-os, game-ecs, realtime-embedded, plc-cyclic; extension packs may add more \u2014 unknown names get UNKNOWN_PROFILE)"),
|
|
29629
|
+
designDepth: import_zod11.z.enum(["components", "interfaces", "implementations", "narratives"]).optional().describe("How deep THIS subsystem commits to designing (overrides project rules.designDepth; default narratives = full depth). Expectation checks below the depth are gated \u2014 soundness of authored content always applies."),
|
|
29630
|
+
trustedLinks: import_zod11.z.array(import_zod11.z.object({
|
|
29631
|
+
subsystem: import_zod11.z.string().describe("Peer subsystem id"),
|
|
29632
|
+
reason: import_zod11.z.string().describe('Why the coupling is sanctioned (e.g. "dispatch latency fast lane")')
|
|
29633
|
+
}).strict()).optional().describe("Sanctioned tight couplings with peers \u2014 required to acknowledge a mutual subsystem dependency; the Adapter \u2192 published Portal shape still applies."),
|
|
29634
|
+
lifecycle: import_zod11.z.array(import_zod11.z.object({
|
|
29635
|
+
phase: import_zod11.z.enum(["init", "shutdown", "cyclic", "interrupt", "scheduled"]).describe("Which lifecycle/execution flow this roots (cyclic = every scan/tick, interrupt = hardware/OS interrupt, scheduled = timer/cron)"),
|
|
29636
|
+
component: import_zod11.z.string().describe("Component id whose method the runtime invokes at this phase"),
|
|
29637
|
+
method: import_zod11.z.string().describe("Method name on that component's interface"),
|
|
29638
|
+
description: import_zod11.z.string().optional()
|
|
29639
|
+
}).strict()).optional().describe("Declared execution-flow roots \u2014 reachability entrypoints alongside Portals/Observers. Only init flows feed the durable-Store hydration check (MISSING_HYDRATION); cyclic/interrupt/scheduled root non-request/response execution models (PLC scan, ISR, cron)."),
|
|
29640
|
+
status: statusInput
|
|
29641
|
+
};
|
|
29642
|
+
const subsystemInputFields = [...Object.keys(subsystemInput), "parentSystem"];
|
|
29643
|
+
reg(
|
|
29644
|
+
server,
|
|
29645
|
+
"sdd_add_subsystem",
|
|
29646
|
+
{
|
|
29647
|
+
description: "Add an L1 Subsystem / Service under the system boundary. publicInterfaces should bind each entry to the component that realizes it (the subsystem's published surface); if components do not exist yet, add them later with sdd_set_public_interfaces. Re-running it on an existing id RE-AUTHORS it: the fields above are replaced, lint/ext are carried forward, and the stored status is kept unless this input states a higher one. The answer carries a write receipt as structured content beside the sentence \u2014 the status written, whether a spec already held the id, and the child project directory a chained subsystem scaffolded.",
|
|
29648
|
+
inputSchema: subsystemInput,
|
|
29649
|
+
outputSchema: specWriteReceiptOutput
|
|
29650
|
+
},
|
|
29651
|
+
({ id, name, description, publicInterfaces, projectPath, targetLanguage, profile, designDepth, trustedLinks, lifecycle, status }) => {
|
|
29652
|
+
try {
|
|
29653
|
+
const restatement = {
|
|
29654
|
+
kind: "subsystem",
|
|
29655
|
+
spec: {
|
|
29656
|
+
id,
|
|
29657
|
+
name,
|
|
29658
|
+
description,
|
|
29659
|
+
publicInterfaces: publicInterfaces ?? [],
|
|
29660
|
+
projectPath,
|
|
29661
|
+
...targetLanguage ? { targetLanguage } : {},
|
|
29662
|
+
...profile ? { profile } : {},
|
|
29663
|
+
...designDepth ? { designDepth } : {},
|
|
29664
|
+
...lifecycle ? { lifecycle } : {},
|
|
29665
|
+
trustedLinks: trustedLinks ?? []
|
|
29666
|
+
},
|
|
29667
|
+
fields: subsystemInputFields,
|
|
29668
|
+
status
|
|
29669
|
+
};
|
|
29670
|
+
const receipt = writeSpec(restatement);
|
|
29671
|
+
if (receipt.scaffoldedProjectPath) {
|
|
29672
|
+
return writeReceipt(
|
|
29673
|
+
`Successfully added external subsystem "${name}" (${id}) and scaffolded its child project at ${receipt.scaffoldedProjectPath}.`,
|
|
29674
|
+
receipt
|
|
29675
|
+
);
|
|
29676
|
+
}
|
|
29677
|
+
return writeReceipt(`Successfully added L1 Subsystem Spec "${name}" (${id}).`, receipt);
|
|
29678
|
+
} catch (e) {
|
|
29679
|
+
return errMessage(e);
|
|
29680
|
+
}
|
|
29681
|
+
}
|
|
29682
|
+
);
|
|
29683
|
+
reg(
|
|
29684
|
+
server,
|
|
29685
|
+
"sdd_set_public_interfaces",
|
|
29686
|
+
{
|
|
29687
|
+
description: "Set (replace) an existing subsystem's publicInterfaces, binding each to the component (and optional interface) that realizes it. Use this to backfill bindings once the subsystem's components exist \u2014 cross-subsystem dependencies may only target a published public component. A replacement, not a merge: an entry left out is removed. An entry's identity is its component and interface, so a list naming one identity twice is refused. Written through the authoring seam as one gated delta, and answered with the change report \u2014 every entry added, changed or removed, or that nothing changed \u2014 as structured content beside the sentence.",
|
|
29688
|
+
inputSchema: {
|
|
29689
|
+
subsystem: import_zod11.z.string().describe("The L1 subsystem id to update"),
|
|
29690
|
+
publicInterfaces: import_zod11.z.array(import_zod11.z.object({
|
|
29691
|
+
type: import_zod11.z.enum(["REST", "GraphQL", "MessageBus", "RPC", "Custom"]),
|
|
29692
|
+
details: import_zod11.z.string(),
|
|
29693
|
+
component: import_zod11.z.string().optional().describe("The L2 component id that realizes this interface"),
|
|
29694
|
+
interface: import_zod11.z.string().optional().describe("Optional L3 interface id on that component"),
|
|
29695
|
+
consumers: import_zod11.z.array(import_zod11.z.string()).optional().describe("The subsystem ids this surface is published to. Absent, any subsystem may depend on the component; present, only these may \u2014 when every entry publishing the component names consumers, the union of the lists is the whole set, and a dependency from any other subsystem is CROSS_SUBSYSTEM_UNLISTED_CONSUMER. Every id must name a subsystem of the tree (PUBLIC_INTERFACE_UNKNOWN_CONSUMER).")
|
|
29696
|
+
}).strict()).describe("The full replacement list of public interfaces for this subsystem")
|
|
29697
|
+
},
|
|
29698
|
+
outputSchema: specChangeReportOutput
|
|
29699
|
+
},
|
|
29700
|
+
({ subsystem, publicInterfaces }) => {
|
|
29701
|
+
try {
|
|
29702
|
+
const sub = loadSubsystemSpec(subsystem);
|
|
29703
|
+
if (!sub) return errText(`Subsystem "${subsystem}" does not exist.`);
|
|
29704
|
+
const delta = publicInterfacesReplacement(subsystem, sub.publicInterfaces ?? [], publicInterfaces);
|
|
29705
|
+
if (typeof delta === "string") return errText(delta);
|
|
29706
|
+
const report = updateSpecGated("subsystem", subsystem, delta);
|
|
29707
|
+
return structured(
|
|
29708
|
+
`Updated public interfaces for subsystem "${subsystem}" (${publicInterfaces.length} ${publicInterfaces.length === 1 ? "entry" : "entries"}).
|
|
29709
|
+
${renderChangeReport(report)}`,
|
|
29710
|
+
report
|
|
29711
|
+
);
|
|
29712
|
+
} catch (e) {
|
|
29713
|
+
return errMessage(e);
|
|
29714
|
+
}
|
|
29715
|
+
}
|
|
29716
|
+
);
|
|
29717
|
+
reg(
|
|
29718
|
+
server,
|
|
29719
|
+
"sdd_set_subsystem_project_path",
|
|
29720
|
+
{
|
|
29721
|
+
description: "Set (or clear) the projectPath of an L1 Subsystem for subsystem chaining. projectPath should be a relative path to the external project root directory. It rewrites the link only (sdd_move_subsystem_project relocates the directory with it). Written through the authoring seam as a gated delta, and answered with the change report as structured content beside the sentence.",
|
|
29722
|
+
inputSchema: {
|
|
29723
|
+
subsystem: import_zod11.z.string().describe("The L1 subsystem id to update"),
|
|
29724
|
+
projectPath: import_zod11.z.string().optional().describe("Relative path to external project root (or omit to clear)")
|
|
29725
|
+
},
|
|
29726
|
+
outputSchema: specChangeReportOutput
|
|
29727
|
+
},
|
|
29728
|
+
({ subsystem, projectPath }) => {
|
|
29729
|
+
try {
|
|
29730
|
+
const delta = projectPath ? { projectPath } : { unset: ["projectPath"] };
|
|
29731
|
+
const report = updateSpecGated("subsystem", subsystem, delta);
|
|
29732
|
+
return structured(
|
|
29733
|
+
`Updated projectPath for subsystem "${subsystem}" to: ${projectPath || "none (cleared)"}
|
|
29734
|
+
${renderChangeReport(report)}`,
|
|
29735
|
+
report
|
|
29736
|
+
);
|
|
29737
|
+
} catch (e) {
|
|
29738
|
+
return errMessage(e);
|
|
29739
|
+
}
|
|
29740
|
+
}
|
|
29741
|
+
);
|
|
29742
|
+
reg(
|
|
29743
|
+
server,
|
|
29744
|
+
"sdd_move_subsystem_project",
|
|
29745
|
+
{
|
|
29746
|
+
description: "Relocate an external subsystem: move its subproject directory on disk to newProjectPath and update its projectPath link in one step. Errors if the subsystem has no projectPath (not an external subproject).",
|
|
29747
|
+
inputSchema: {
|
|
29748
|
+
subsystem: import_zod11.z.string().describe("The external L1 subsystem id to relocate"),
|
|
29749
|
+
newProjectPath: import_zod11.z.string().describe("The new relative path for the subproject directory")
|
|
29750
|
+
}
|
|
29751
|
+
},
|
|
29752
|
+
({ subsystem, newProjectPath }) => {
|
|
29753
|
+
try {
|
|
29754
|
+
moveSubsystemProject(subsystem, newProjectPath);
|
|
29755
|
+
return text(`Moved subsystem "${subsystem}" to: ${newProjectPath}`);
|
|
29756
|
+
} catch (e) {
|
|
29757
|
+
return errText(String(e));
|
|
29758
|
+
}
|
|
29759
|
+
}
|
|
29760
|
+
);
|
|
29761
|
+
reg(
|
|
29762
|
+
server,
|
|
29763
|
+
"sdd_externalize_subsystem",
|
|
29764
|
+
{
|
|
29765
|
+
description: "Migrate an internal subsystem into a standalone subproject at projectPath: move its spec subtree out, mount it via projectPath, and rewrite cross-subsystem references to the new namespaced ids. Source code is not moved. Errors if the subsystem is missing or already external.",
|
|
29766
|
+
inputSchema: {
|
|
29767
|
+
subsystem: import_zod11.z.string().describe("The internal L1 subsystem id to externalize"),
|
|
29768
|
+
projectPath: import_zod11.z.string().describe("Relative destination directory for the new subproject")
|
|
29769
|
+
}
|
|
29770
|
+
},
|
|
29771
|
+
({ subsystem, projectPath }) => {
|
|
29772
|
+
try {
|
|
29773
|
+
externalizeSubsystem(subsystem, projectPath);
|
|
29774
|
+
return text(`Externalized subsystem "${subsystem}" into subproject: ${projectPath}. Move its source code there and re-validate.`);
|
|
29775
|
+
} catch (e) {
|
|
29776
|
+
return errText(String(e));
|
|
29777
|
+
}
|
|
29778
|
+
}
|
|
29779
|
+
);
|
|
29780
|
+
reg(
|
|
29781
|
+
server,
|
|
29782
|
+
"sdd_internalize_subsystem",
|
|
29783
|
+
{
|
|
29784
|
+
description: "Migrate an external subsystem back into the parent tree: move its subproject spec subtree back under the parent, drop projectPath, delete the child .wai project, and rewrite references back to bare ids. Errors if the subsystem is not external or its subproject is not a single flat subsystem.",
|
|
29785
|
+
inputSchema: {
|
|
29786
|
+
subsystem: import_zod11.z.string().describe("The external L1 subsystem id to internalize")
|
|
29787
|
+
}
|
|
29788
|
+
},
|
|
29789
|
+
({ subsystem }) => {
|
|
29790
|
+
try {
|
|
29791
|
+
internalizeSubsystem(subsystem);
|
|
29792
|
+
return text(`Internalized subsystem "${subsystem}" back into this project (child .wai removed).`);
|
|
29793
|
+
} catch (e) {
|
|
29794
|
+
return errText(String(e));
|
|
29795
|
+
}
|
|
29796
|
+
}
|
|
29797
|
+
);
|
|
29798
|
+
reg(
|
|
29799
|
+
server,
|
|
29800
|
+
"sdd_rename_component",
|
|
29801
|
+
{
|
|
29802
|
+
description: "Rename a component and rewrite every reference to it in the bound tree: dependsOn, owns and dispatch entries, lifecycle entrypoints, published interfaces at L1 and L0, narrative call/dispatch/register targets, an interface's component, an implementation's contract and an entity's componentClass. The interface named i<id> and the implementation named <id>_impl move to the new ids and files; interfaces and implementations named otherwise keep their ids. Findings keyed on the component follow it too: every lint allow naming it on an edge site or in a covered unit, and every entry of the debt register (.wai/project.yaml rules.conformance.carried) anchored on a renamed spec or naming the component in its site or units \u2014 rewritten in place, every comment and all other formatting of the register kept, so unchanged debt never reads as paid and new at once. A register that cannot be rewritten that precisely is refused before the first write. Display names are left as they are. Refuses, writing nothing: a component that does not exist (component-missing), one inside a chained subproject (chained-component \u2014 rename it from that project's own root), a new id that is not a lowercase identifier without a namespace separator (invalid-id), and a new id \u2014 or i<newId> or <newId>_impl \u2014 already in use (id-taken). Returns the specs moved, each with its old and new id, the ids of the other specs rewritten (lint allows included), and `carried`: each register edit \u2014 the entry by code, spec and site as it stood, the key changed (spec, at or covers), from and to.",
|
|
29803
|
+
inputSchema: {
|
|
29804
|
+
id: import_zod11.z.string().describe("The component to rename (namespaced if needed)"),
|
|
29805
|
+
newId: import_zod11.z.string().describe("Its new id: a lowercase identifier with no namespace separator")
|
|
29806
|
+
}
|
|
29807
|
+
},
|
|
29808
|
+
({ id, newId }) => {
|
|
29809
|
+
try {
|
|
29810
|
+
return json(renameComponent(qualifiedComponentId(id), newId));
|
|
29811
|
+
} catch (e) {
|
|
29812
|
+
return errText(String(e));
|
|
29813
|
+
}
|
|
29814
|
+
}
|
|
29815
|
+
);
|
|
29816
|
+
reg(
|
|
29817
|
+
server,
|
|
29818
|
+
"sdd_rename_method",
|
|
29819
|
+
{
|
|
29820
|
+
description: "Rename a contract method and retarget every reference to it in the bound tree. The method moves on every interface of the component that declares it \u2014 its name, and the name inside its signature \u2014 and on the implementations of those contracts, carrying narrative, sourcePath, symbol, detail, intent and findings unchanged; an implementation that declared no symbol is pinned to the old name unless pinSymbol is false, so the function it already binds to keeps binding. Narrative call, register and dispatch steps naming this component and method, dispatch-table bindings and lifecycle entrypoints are retargeted, and so are the findings keyed on it: a lint allow at the method on a spec it moved in or covering `<component>.<method>`, and every debt-register entry (.wai/project.yaml rules.conformance.carried) keyed the same way \u2014 rewritten in place with every comment and all other formatting kept (a register that cannot be rewritten that precisely is refused before the first write). Prose is never rewritten and a gRPC endpoint binding keeps its wire method \u2014 renaming a contract method must not silently rename an RPC; both are reported as mentions. Refuses, writing nothing: a component that does not exist (component-missing), one inside a chained subproject (chained-component \u2014 rename its method from that project's own root), a new name that is not a camel-case identifier (invalid-name), a method the component does not declare (method-missing), and a name a moving contract already declares (name-taken). Returns the specs the method moved in, the specs retargeted (lint allows included), the specs whose prose still names it, the pinned symbol when one was set, and `carried`: each register edit.",
|
|
29821
|
+
inputSchema: {
|
|
29822
|
+
id: import_zod11.z.string().describe("The component whose method is renamed (namespaced if needed)"),
|
|
29823
|
+
method: import_zod11.z.string().describe("The method name as it stands"),
|
|
29824
|
+
newName: import_zod11.z.string().describe("Its new name: a camel-case identifier"),
|
|
29825
|
+
pinSymbol: import_zod11.z.boolean().optional().describe("Whether an implementation that declares no symbol is pinned to the old name so its function still binds; true when omitted")
|
|
29826
|
+
}
|
|
29827
|
+
},
|
|
29828
|
+
({ id, method, newName, pinSymbol }) => {
|
|
29829
|
+
try {
|
|
29830
|
+
return json(renameMethod(qualifiedComponentId(id), method, newName, pinSymbol));
|
|
29831
|
+
} catch (e) {
|
|
29832
|
+
return errText(String(e));
|
|
29833
|
+
}
|
|
29834
|
+
}
|
|
29835
|
+
);
|
|
29836
|
+
reg(
|
|
29837
|
+
server,
|
|
29838
|
+
"sdd_move_methods",
|
|
29839
|
+
{
|
|
29840
|
+
description: "Move contract methods from one component to another, carrying each method's narrative, calls and bindings, and re-pointing every reference that named the old home: narrative call/register/dispatch steps, dispatch-table bindings, lifecycle entrypoints and `calls` entries. The target gains the dependencies the moved narratives call. Findings keyed on a moved method follow it: a lint allow anchored at it moves to the receiving spec, one covering `<from>.<method>` names `<to>.<method>`, and the debt register (.wai/project.yaml rules.conformance.carried) is rekeyed the same way in place, every comment and all other formatting kept \u2014 the report's `carried` lists each register edit, and a register that cannot be rewritten that precisely is refused before the first write. ONE all-or-nothing gated write: if a rule refuses the move nothing is written and the report names the rule plus where these methods could live instead, cheapest legal home first, with the requested target among them so its shortfall reads beside the others. Prose is never rewritten and a wire endpoint keeps its address \u2014 moving a method between components must not silently re-address an RPC; both are reported as mentions. A target that declares no contract yet receives one, i<target>, and when implementation entries travel and no implementation realizes the receiving contract, <target>_impl is created for them, inheriting the source implementation's sourcePath; the report's `created` names every spec the move created. Refuses before the first write (unmovable request): a component or method that does not exist, a method name already declared on the target, a source and target that are the same component, a target with several contracts none named i<target>, a contract or implementation to create whose id another spec already holds, and a component inside a chained subproject (move it from that project's own root). A dry run runs the whole move, gate included, and reports what it would have done.",
|
|
29841
|
+
inputSchema: {
|
|
29842
|
+
from: import_zod11.z.string().describe("The component the methods belong to (namespaced if needed)"),
|
|
29843
|
+
to: import_zod11.z.string().describe("The component that should receive them (namespaced if needed)"),
|
|
29844
|
+
methods: import_zod11.z.array(import_zod11.z.string()).describe("The method names to move; every one must exist on the source contract"),
|
|
29845
|
+
dryRun: import_zod11.z.boolean().optional().describe("Answer with what the move would do and write nothing")
|
|
29846
|
+
}
|
|
29847
|
+
},
|
|
29848
|
+
({ from, to, methods, dryRun }) => {
|
|
29849
|
+
try {
|
|
29850
|
+
const source = qualifiedComponentId(from);
|
|
29851
|
+
const target = qualifiedComponentId(to);
|
|
29852
|
+
if (!loadComponentSpec(source)) return errText(`no component has the id "${from}".`);
|
|
29853
|
+
if (!loadComponentSpec(target)) return errText(`no component has the id "${to}".`);
|
|
29854
|
+
return json(moveMethods2(source, target, methods, dryRun));
|
|
29855
|
+
} catch (e) {
|
|
29856
|
+
return errText(String(e));
|
|
29857
|
+
}
|
|
29858
|
+
}
|
|
29859
|
+
);
|
|
29860
|
+
const componentInput = {
|
|
29861
|
+
id: import_zod11.z.string().describe("Lowercase identifier for the component"),
|
|
29862
|
+
name: import_zod11.z.string().describe("Human-readable display name"),
|
|
29863
|
+
description: import_zod11.z.string().describe("Responsibility / internal architecture details"),
|
|
29864
|
+
subsystem: import_zod11.z.string().describe("The L1 subsystem ID this component belongs to"),
|
|
29865
|
+
componentType: import_zod11.z.enum(["Portal", "Orchestrator", "Supervisor", "Actor", "Store", "Index", "Query", "Registry", "Adapter", "Observer", "Repository"]).describe("The building block, or the pattern Repository. Specialist and Gateway are retired and cannot be authored: logic is an Orchestrator with a dependencyClass, a gateway is a Portal with the gateway variant"),
|
|
29866
|
+
owns: import_zod11.z.array(import_zod11.z.string()).optional().describe("Member block ids privately owned by this component (patterns only)"),
|
|
29867
|
+
dependsOn: import_zod11.z.array(import_zod11.z.string()).optional().describe("IDs of other components this collaborates with (facades or standalone blocks)"),
|
|
29868
|
+
portalType: import_zod11.z.enum(["HTTP_API", "gRPC", "GraphQL", "MessageBus", "CLI", "NamedPipe", "IPC", "Custom"]).optional().describe("Portal-only, and expected on every Portal. OMIT IT on any other componentType \u2014 passing it there is refused at the write (UNEXPECTED_PORTAL_FIELD), nothing is saved."),
|
|
29869
|
+
basePath: import_zod11.z.string().optional().describe("Portal-only: base path/prefix all the portal's endpoints mount under. OMIT IT on any other componentType \u2014 passing it there is refused at the write (UNEXPECTED_PORTAL_FIELD), nothing is saved."),
|
|
29870
|
+
dispatch: import_zod11.z.array(import_zod11.z.object({
|
|
29871
|
+
capability: import_zod11.z.string().describe('Capability name exactly as dispatched at runtime (e.g. "shadow_module.get")'),
|
|
29872
|
+
component: import_zod11.z.string().describe("Component id serving this capability (must also appear under dependsOn/owns)"),
|
|
29873
|
+
method: import_zod11.z.string().describe("Method name on the serving component's interface"),
|
|
29874
|
+
description: import_zod11.z.string().optional()
|
|
29875
|
+
}).strict()).optional().describe("Portal-only: capability \u2192 component.method dispatch table for generic-handle portals. Gives the reachability walker real edges and is validated against target interfaces (UNSERVED_CAPABILITY)."),
|
|
29876
|
+
mounts: import_zod11.z.array(import_zod11.z.object({
|
|
29877
|
+
portal: import_zod11.z.string().describe("The mounted Portal's component id"),
|
|
29878
|
+
prefixes: import_zod11.z.array(import_zod11.z.string()).describe('The path prefixes this listener routes to the portal. A path lies under a prefix when it equals it or continues it past a slash, so "/" covers only the root itself'),
|
|
29879
|
+
via: import_zod11.z.string().optional().describe("The router entry the listener calls to hand the portal its request, exported by the PORTAL's own file (held to it: UNREALIZED_EXPORT_HANDLE). Omit when the listener calls the portal's contract methods directly, route by route")
|
|
29880
|
+
}).strict()).optional().describe("Portal-only: the portals this LISTENER serves, each under its path prefixes and through its router entry. Declaring the field \u2014 even as an empty array \u2014 marks this portal as a listener, the one kind of portal nothing else needs to mount. Checked by portal-mounts: a mount must name a Portal (MOUNT_TARGET_NOT_PORTAL), every HTTP endpoint of a mounted portal must lie under one of its prefixes (ENDPOINT_OUTSIDE_MOUNT), and a portal with HTTP endpoints that is neither a listener nor mounted by one is reported (UNMOUNTED_PORTAL)."),
|
|
29881
|
+
durability: import_zod11.z.enum(["ram-projection", "durable", "read-through", "cache"]).optional().describe("Store-only \u2014 OMIT IT on any other componentType, where it is refused at the write (DURABILITY_ON_NON_STORE) and nothing is saved. Every Store should declare one (MISSING_DURABILITY): durable = persisted RAM projection (hydration read-back from a lifecycle init entrypoint required \u2014 MISSING_HYDRATION); read-through = persisted with no RAM copy (every read is the read-back, hydration exempt); ram-projection = rebuilt not restored; cache = evictable loss-safe memo state."),
|
|
29882
|
+
dependencyClass: import_zod11.z.enum(["pure", "read"]).optional().describe("Orchestrator-only \u2014 OMIT IT on any other componentType, where it is refused at the write (DEPENDENCY_CLASS_ON_NON_ORCHESTRATOR) and nothing is saved. Declares what logic may depend on, enforced as a Store's durability is (DEPENDENCY_CLASS_VIOLATION): pure = depends only on pure Orchestrators (a computation over the values it is handed, e.g. an arbiter or codec); read = also on read Orchestrators, Repositories, Indexes and Adapters, never calling their write methods; unset = a workflow."),
|
|
29883
|
+
emits: import_zod11.z.array(import_zod11.z.object({
|
|
29884
|
+
topic: import_zod11.z.string().describe("Topic/channel name exactly as used on the bus"),
|
|
29885
|
+
event: import_zod11.z.string().optional().describe("Optional event name within the topic (informational; pairing is by topic)"),
|
|
29886
|
+
description: import_zod11.z.string().optional()
|
|
29887
|
+
}).strict()).optional().describe("Topics this component publishes \u2014 every emitted topic needs a subscriber somewhere in the tree (UNCONSUMED_TOPIC)."),
|
|
29888
|
+
subscribesTo: import_zod11.z.array(import_zod11.z.object({
|
|
29889
|
+
topic: import_zod11.z.string().describe("Topic/channel name exactly as used on the bus"),
|
|
29890
|
+
event: import_zod11.z.string().optional(),
|
|
29891
|
+
description: import_zod11.z.string().optional()
|
|
29892
|
+
}).strict()).optional().describe("Topics this component consumes (typical on Observers) \u2014 every subscription needs an emitter somewhere in the tree (UNSOURCED_SUBSCRIPTION)."),
|
|
29893
|
+
ext: import_zod11.z.record(import_zod11.z.unknown()).optional().describe('Opaque pack/tool extension data (namespaced keys, e.g. "mypack:priority") \u2014 preserved verbatim, never validated or interpreted by the core'),
|
|
29894
|
+
status: statusInput
|
|
29895
|
+
};
|
|
29896
|
+
const componentInputFields = Object.keys(componentInput);
|
|
29897
|
+
reg(
|
|
29898
|
+
server,
|
|
29899
|
+
"sdd_add_component",
|
|
29900
|
+
{
|
|
29901
|
+
description: `Add an L2 Component under a subsystem. componentType is a building block (Portal, Orchestrator, Supervisor, Actor, Store, Index, Query, Registry, Adapter, Observer) or the pattern Repository. Specialist and Gateway are retired (STEREOTYPE_RETIRED) and cannot be authored: logic is an Orchestrator with a dependencyClass (pure | read; unset = a workflow), and a gateway is a Portal with the gateway variant (set variant with sdd_update_spec). Patterns set "owns" (their private member blocks); all components set "dependsOn" (collaborators \u2014 facades or standalone blocks). Held/persisted state (configs, permissions, sessions, caches): model the Repository recipe \u2014 a Store + Registry (write) + Index (read), plus a Query for computed reads over the Store, owned by a Repository facade consumers depend on; a deliberately standalone Store is the sanctioned lightweight form (workflow-layer consumers + lint.allow on UNOWNED_STORE). Never hold state as fields inside an Orchestrator because a Store link was refused. Re-running it on an existing id RE-AUTHORS it: the fields above are replaced (an omitted array is CLEARED), while lint.allow, a Portal's auth, variant, patterns and externalLinks are carried forward \u2014 edit those with sdd_update_spec. The stored status is kept unless this input states a higher one. The answer carries a write receipt as structured content beside the sentence \u2014 the status written, whether a spec already held the id, and every gate notice the write raised, each as its own entry.`,
|
|
29902
|
+
inputSchema: componentInput,
|
|
29903
|
+
outputSchema: specWriteReceiptOutput
|
|
29904
|
+
},
|
|
29905
|
+
({ id, name, description, subsystem, componentType, owns, dependsOn, portalType, basePath, dispatch, mounts, durability, dependencyClass, emits, subscribesTo, ext, status }) => {
|
|
29906
|
+
try {
|
|
29907
|
+
const restatement = {
|
|
29908
|
+
kind: "component",
|
|
29909
|
+
spec: {
|
|
29910
|
+
id,
|
|
29911
|
+
name,
|
|
29912
|
+
description,
|
|
29913
|
+
subsystem,
|
|
29914
|
+
componentType,
|
|
29915
|
+
owns: owns ?? [],
|
|
29916
|
+
dependsOn: dependsOn ?? [],
|
|
29917
|
+
...portalType ? { portalType } : {},
|
|
29918
|
+
...basePath ? { basePath } : {},
|
|
29919
|
+
...dispatch ? { dispatch } : {},
|
|
29920
|
+
// An EMPTY list is kept: declaring the field is what marks a listener.
|
|
29921
|
+
...mounts ? { mounts } : {},
|
|
29922
|
+
...durability ? { durability } : {},
|
|
29923
|
+
...dependencyClass ? { dependencyClass } : {},
|
|
29924
|
+
...emits ? { emits } : {},
|
|
29925
|
+
...subscribesTo ? { subscribesTo } : {},
|
|
29926
|
+
...ext ? { ext } : {}
|
|
29927
|
+
},
|
|
29928
|
+
fields: componentInputFields,
|
|
29929
|
+
status
|
|
29930
|
+
};
|
|
29931
|
+
const receipt = writeSpec(restatement);
|
|
29932
|
+
return writeReceipt(
|
|
29933
|
+
`Successfully ${receipt.replacedExisting ? "re-authored" : "added"} L2 Component Spec "${name}" (${id}, ${componentType}).`,
|
|
29934
|
+
receipt
|
|
29935
|
+
);
|
|
29936
|
+
} catch (e) {
|
|
29937
|
+
return errMessage(e);
|
|
29938
|
+
}
|
|
29939
|
+
}
|
|
29940
|
+
);
|
|
29941
|
+
const interfaceMethodShape = {
|
|
29942
|
+
name: import_zod11.z.string(),
|
|
29943
|
+
description: import_zod11.z.string(),
|
|
29944
|
+
signature: import_zod11.z.string(),
|
|
29945
|
+
returns: import_zod11.z.string().describe(TYPE_REF_GRAMMAR("The type the method answers with")),
|
|
29946
|
+
params: import_zod11.z.array(import_zod11.z.object({
|
|
29947
|
+
name: import_zod11.z.string(),
|
|
29948
|
+
type: import_zod11.z.string().describe(TYPE_REF_GRAMMAR("The parameter's type")),
|
|
29949
|
+
description: import_zod11.z.string().optional(),
|
|
29950
|
+
optional: import_zod11.z.boolean().optional().describe(
|
|
29951
|
+
'Whether the parameter may be OMITTED by a caller. It is not nullability: a parameter that must be passed but may be passed as nothing is a required parameter whose type is a union \u2014 "ProjectConfig | null". Say whichever is true; they are different contracts.'
|
|
29952
|
+
)
|
|
29953
|
+
}).strict()).optional().describe("Structured parameters \u2014 authoritative for type checking (the prose signature becomes display-only). Strongly preferred."),
|
|
29954
|
+
guarantees: import_zod11.z.array(import_zod11.z.string().min(1)).optional().describe("Semantic guarantees the method promises (combinable); any guarantee a narrative step asserts must be declared here. Builtin tokens: idempotent | atomic | transactional | exactly-once; extension packs may declare more (any other token is UNKNOWN_GUARANTEE)"),
|
|
29955
|
+
effect: import_zod11.z.enum(["read", "write"]).optional().describe("State-effect direction on the component's held state \u2014 required on a durable Store's contract methods so the durability round-trip rule can pair writes with hydration read-backs"),
|
|
29956
|
+
invokedBy: import_zod11.z.object({
|
|
29957
|
+
kind: import_zod11.z.enum(["runtime", "external", "sibling-subsystem"]).describe("Who owns the out-of-graph invocation: runtime (timer/signal/shutdown hook), external (a system outside this project), sibling-subsystem (a modeled sibling whose edge is not narrated here)"),
|
|
29958
|
+
caller: import_zod11.z.string().optional().describe("WHO invokes it and when, as reviewable prose \u2014 missing or placeholder-thin prose is INVOKED_BY_UNDESCRIBED")
|
|
29959
|
+
}).strict().optional().describe("Typed acknowledgment of a real caller OUTSIDE the modeled narrative graph. Unused-detection seeds the method as an entrypoint so reachability propagates through its narrative (unlike lint.allow); a method the internal walk already reaches is flagged stale (INVOKED_BY_REDUNDANT). Prefer a `register` narrative step when the wiring is internal."),
|
|
29960
|
+
findings: import_zod11.z.array(import_zod11.z.object({
|
|
29961
|
+
code: import_zod11.z.string().describe("UPPER_SNAKE finding code, unique within the method; a pack's codes carry the pack prefix (<PACK>_<CODE>)"),
|
|
29962
|
+
severity: import_zod11.z.enum(["error", "warning"]).describe("Default severity, before project severity overrides and draft-context downgrades"),
|
|
29963
|
+
summary: import_zod11.z.string().describe("One line saying what the finding means")
|
|
29964
|
+
}).strict()).optional().describe("The finding codes this method can report, each with its default severity and summary. Each declared code must be anchored in the method's source file, as a string literal or a property-access name (UNREALIZED_FINDING). A code is declared once per method; sdd_update_spec upserts and deletes findings by code."),
|
|
29965
|
+
ext: import_zod11.z.record(import_zod11.z.unknown()).optional().describe("Opaque pack/tool extension data for this method (namespaced keys) \u2014 preserved verbatim")
|
|
29966
|
+
};
|
|
29967
|
+
const interfaceMethodInputFields = Object.keys(interfaceMethodShape);
|
|
29968
|
+
const interfaceInput = {
|
|
29969
|
+
id: import_zod11.z.string().describe('Lowercase identifier prefixed with "i", e.g. "istorage"'),
|
|
29970
|
+
name: import_zod11.z.string().describe("Human-readable contract name"),
|
|
29971
|
+
description: import_zod11.z.string().describe("Contract description and obligations"),
|
|
29972
|
+
component: import_zod11.z.string().describe("The L2 component ID this interface belongs to"),
|
|
29973
|
+
methods: import_zod11.z.array(import_zod11.z.object(interfaceMethodShape).strict()).optional().describe("List of method signature contracts"),
|
|
29974
|
+
status: statusInput
|
|
29975
|
+
};
|
|
29976
|
+
const interfaceInputFields = Object.keys(interfaceInput);
|
|
29977
|
+
reg(
|
|
29978
|
+
server,
|
|
29979
|
+
"sdd_define_interface",
|
|
29980
|
+
{
|
|
29981
|
+
description: "Define an L3 Contract / Interface with method signatures for a component. Prefer supplying structured `params` per method \u2014 they are the authoritative source for type checking (the free-form signature string then becomes display-only and is never heuristically parsed). A method declares the finding codes it reports in `findings` ({code, severity, summary}); each code must be anchored in the method's source file, as a string literal or a property-access name (UNREALIZED_FINDING). Re-defining an existing id REPLACES the method list: a method left out of the input is REMOVED (and reported); spec-level lint/ext and each method's endpoint binding are carried forward, and the stored status is kept unless this input states a higher one. The answer carries a write receipt as structured content beside the sentence \u2014 the status written, whether a spec already held the id, and the notices a restatement raised, each as its own entry.",
|
|
29982
|
+
inputSchema: interfaceInput,
|
|
29983
|
+
outputSchema: specWriteReceiptOutput
|
|
29984
|
+
},
|
|
29985
|
+
({ id, name, description, component, methods, status }) => {
|
|
29986
|
+
try {
|
|
29987
|
+
const restatement = {
|
|
29988
|
+
kind: "interface",
|
|
29989
|
+
spec: { id, name, description, component, methods: methods ?? [] },
|
|
29990
|
+
fields: interfaceInputFields,
|
|
29991
|
+
memberFields: interfaceMethodInputFields,
|
|
29992
|
+
status
|
|
29993
|
+
};
|
|
29994
|
+
const receipt = writeSpec(restatement);
|
|
29995
|
+
return writeReceipt(
|
|
29996
|
+
`Successfully ${receipt.replacedExisting ? "re-authored" : "defined"} L3 Interface Contract "${name}" (${id}).`,
|
|
29997
|
+
receipt
|
|
29998
|
+
);
|
|
29999
|
+
} catch (e) {
|
|
30000
|
+
return errMessage(e);
|
|
30001
|
+
}
|
|
30002
|
+
}
|
|
30003
|
+
);
|
|
30004
|
+
reg(
|
|
30005
|
+
server,
|
|
30006
|
+
"sdd_set_endpoints",
|
|
30007
|
+
{
|
|
30008
|
+
description: "Bind concrete wire endpoints to existing L3 interface methods (required for every Portal). Pick `transport` and fill that transport's address fields. Run after sdd_define_interface. Written through the authoring seam as one gated delta, and answered with the change report \u2014 every binding set or changed, or that nothing changed \u2014 as structured content beside the sentence.",
|
|
30009
|
+
inputSchema: {
|
|
30010
|
+
interface: import_zod11.z.string().describe('The L3 interface ID (e.g. "ibilling-gateway")'),
|
|
30011
|
+
endpoints: import_zod11.z.array(import_zod11.z.object({
|
|
30012
|
+
method: import_zod11.z.string().describe("The NAME of the interface method to bind (not the HTTP verb)"),
|
|
30013
|
+
transport: import_zod11.z.enum(["HTTP", "gRPC", "GraphQL", "MessageBus", "NamedPipe", "IPC", "CLI", "Custom"]).describe("Wire protocol; must match the Portal's portalType"),
|
|
30014
|
+
httpMethod: import_zod11.z.enum(["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"]).optional().describe("HTTP: verb"),
|
|
30015
|
+
path: import_zod11.z.string().optional().describe('HTTP: route path, e.g. "/v1/checkout"'),
|
|
30016
|
+
service: import_zod11.z.string().optional().describe("gRPC: service name"),
|
|
30017
|
+
rpcMethod: import_zod11.z.string().optional().describe("gRPC: rpc method name"),
|
|
30018
|
+
operation: import_zod11.z.enum(["query", "mutation", "subscription"]).optional().describe("GraphQL: operation kind"),
|
|
30019
|
+
field: import_zod11.z.string().optional().describe("GraphQL: root field name"),
|
|
30020
|
+
topic: import_zod11.z.string().optional().describe("MessageBus: topic"),
|
|
30021
|
+
event: import_zod11.z.string().optional().describe("MessageBus: event name"),
|
|
30022
|
+
queue: import_zod11.z.string().optional().describe("MessageBus: optional queue/consumer group"),
|
|
30023
|
+
direction: import_zod11.z.enum(["subscribe", "publish"]).optional().describe("MessageBus: subscribe (default) or publish"),
|
|
30024
|
+
pipe: import_zod11.z.string().optional().describe('NamedPipe: pipe name, e.g. "\\\\.\\pipe\\gk-events"'),
|
|
30025
|
+
channel: import_zod11.z.string().optional().describe("IPC: channel name"),
|
|
30026
|
+
command: import_zod11.z.string().optional().describe("CLI: command/subcommand"),
|
|
30027
|
+
address: import_zod11.z.string().optional().describe("Custom: free-form address")
|
|
30028
|
+
}).strict()).describe("One binding per method")
|
|
30029
|
+
},
|
|
30030
|
+
outputSchema: specChangeReportOutput
|
|
30031
|
+
},
|
|
30032
|
+
({ interface: interfaceId, endpoints }) => {
|
|
30033
|
+
try {
|
|
30034
|
+
const intf = loadInterfaceSpec(interfaceId);
|
|
30035
|
+
if (!intf) return errText(`Interface "${interfaceId}" does not exist.`);
|
|
30036
|
+
const buildRaw = (e) => {
|
|
30037
|
+
switch (e.transport) {
|
|
30038
|
+
case "HTTP":
|
|
30039
|
+
return { transport: "HTTP", method: e.httpMethod, path: e.path };
|
|
30040
|
+
case "gRPC":
|
|
30041
|
+
return { transport: "gRPC", service: e.service, method: e.rpcMethod };
|
|
30042
|
+
case "GraphQL":
|
|
30043
|
+
return { transport: "GraphQL", operation: e.operation, field: e.field };
|
|
30044
|
+
case "MessageBus":
|
|
30045
|
+
return { transport: "MessageBus", topic: e.topic, event: e.event, queue: e.queue, direction: e.direction ?? "subscribe" };
|
|
30046
|
+
case "NamedPipe":
|
|
30047
|
+
return { transport: "NamedPipe", pipe: e.pipe };
|
|
30048
|
+
case "IPC":
|
|
30049
|
+
return { transport: "IPC", channel: e.channel };
|
|
30050
|
+
case "CLI":
|
|
30051
|
+
return { transport: "CLI", command: e.command };
|
|
30052
|
+
case "Custom":
|
|
30053
|
+
return { transport: "Custom", address: e.address };
|
|
30054
|
+
}
|
|
30055
|
+
};
|
|
30056
|
+
const bound2 = [];
|
|
30057
|
+
const methods = [];
|
|
30058
|
+
for (const e of endpoints) {
|
|
30059
|
+
const m = intf.methods.find((x) => x.name === e.method);
|
|
30060
|
+
if (!m) return errText(`Method "${e.method}" not found on interface "${interfaceId}". Define it via sdd_define_interface first.`);
|
|
30061
|
+
const parsed = EndpointSchema.safeParse(buildRaw(e));
|
|
30062
|
+
if (!parsed.success) {
|
|
30063
|
+
const detail = parsed.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ");
|
|
30064
|
+
return errText(`Invalid ${e.transport} endpoint for method "${e.method}": ${detail}`);
|
|
30065
|
+
}
|
|
30066
|
+
methods.push({ name: e.method, endpoint: parsed.data });
|
|
30067
|
+
bound2.push(`${e.method}\u2192${e.transport}`);
|
|
30068
|
+
}
|
|
30069
|
+
const report = updateSpecGated("interface", interfaceId, { methods });
|
|
30070
|
+
return structured(
|
|
30071
|
+
`Bound ${bound2.length} endpoint(s) on "${interfaceId}": ${bound2.join(", ")}.
|
|
30072
|
+
${renderChangeReport(report)}`,
|
|
30073
|
+
report
|
|
30074
|
+
);
|
|
30075
|
+
} catch (e) {
|
|
30076
|
+
return errMessage(e);
|
|
30077
|
+
}
|
|
30078
|
+
}
|
|
30079
|
+
);
|
|
30080
|
+
const stepNo = () => import_zod11.z.number().int().positive();
|
|
30081
|
+
const labelRef = () => import_zod11.z.string().min(1);
|
|
30082
|
+
const narrativeStepInput = import_zod11.z.object({
|
|
30083
|
+
stepNumber: stepNo().optional().describe("Defaults to the 1-based array position \u2014 jump fields reference these numbers"),
|
|
30084
|
+
label: labelRef().optional().describe("Optional symbolic anchor for this step (unique per narrative). Every jump-by-number field has a *Label twin resolved against these anchors at write time \u2014 prefer labels over hand-counted step numbers"),
|
|
30085
|
+
description: import_zod11.z.string(),
|
|
30086
|
+
type: import_zod11.z.enum(["local", "call", "dispatch", "register", "branch", "switch", "loop", "try", "parallel", "jump", "return", "throw"]),
|
|
30087
|
+
targetComponent: import_zod11.z.string().optional().describe("call/register/dispatch: L2 component id (for dispatch, the Portal routed through)"),
|
|
30088
|
+
targetMethod: import_zod11.z.string().optional().describe("call/register: method name on the target. A register step hands the target method to the runtime as a callback (timer, event listener, shutdown hook): reachability follows the edge, but it is never an invocation \u2014 exempt from call-graph conformance, call-cycle detection, and the durability boot walk"),
|
|
30089
|
+
auth: import_zod11.z.object({ from: import_zod11.z.string(), note: import_zod11.z.string().optional() }).strict().optional().describe("call/dispatch: the credential this step presents to an AUTHED callee Portal and WHERE it loads from (`from`). Opaque form (env:API_KEY, a config key, vault:path) = a design note wairon never resolves; modeled form `component:<id>` references the Adapter/Store that provides the secret and is validated (must resolve, be an Adapter/Store, and be wired to the presenter). Absence on a call into a Portal whose auth \u2260 none warns (PORTAL_AUTH_UNMET). The authenticated call itself should be made by an Adapter (AUTH_PRESENTER_NOT_ADAPTER)."),
|
|
30090
|
+
detach: import_zod11.z.boolean().optional().describe("call/dispatch: fire-and-forget \u2014 issue the call and continue without awaiting the result (no later step consumes it)"),
|
|
30091
|
+
capability: import_zod11.z.string().optional().describe("dispatch: the capability routed through the target Portal's dispatch table (validated against it \u2014 UNSERVED_CAPABILITY)"),
|
|
30092
|
+
assertsGuarantees: import_zod11.z.array(import_zod11.z.string().min(1)).optional().describe("Semantic guarantees this step relies on \u2014 each must be declared in the called method's L3 guarantees (NARRATIVE_SEMANTIC_UNBACKED otherwise). Builtin tokens: idempotent | atomic | transactional | exactly-once; extension packs may declare more (any other token is UNKNOWN_GUARANTEE)"),
|
|
30093
|
+
assertsInvariants: import_zod11.z.array(import_zod11.z.string()).optional().describe(`Declared entity invariants this step upholds, as "<type-id>.<invariant-id>" refs (UNKNOWN_INVARIANT_REF when unresolved); write-effect methods of the entity's componentClass must carry one per declared invariant (UNASSERTED_INVARIANT)`),
|
|
30094
|
+
condition: import_zod11.z.string().optional().describe("branch / while / doWhile"),
|
|
30095
|
+
onTrueStep: stepNo().optional().describe("branch: default = next step"),
|
|
30096
|
+
onTrueLabel: labelRef().optional().describe("branch: symbolic alternative to onTrueStep"),
|
|
30097
|
+
onFalseStep: stepNo().optional().describe("branch: required (or onFalseLabel)"),
|
|
30098
|
+
onFalseLabel: labelRef().optional().describe("branch: symbolic alternative to onFalseStep"),
|
|
30099
|
+
on: import_zod11.z.string().optional().describe("switch: the dispatched value"),
|
|
30100
|
+
cases: import_zod11.z.array(import_zod11.z.object({ value: import_zod11.z.string(), step: stepNo().optional(), label: labelRef().optional() }).strict()).optional().describe("switch: required; each case targets its region by step number or label"),
|
|
30101
|
+
defaultStep: stepNo().optional().describe("switch: default = next step"),
|
|
30102
|
+
defaultLabel: labelRef().optional().describe("switch: symbolic alternative to defaultStep"),
|
|
30103
|
+
loopKind: import_zod11.z.enum(["forEach", "for", "while", "doWhile"]).optional().describe('loop: default forEach when "over" is set, else while'),
|
|
30104
|
+
over: import_zod11.z.string().optional().describe("loop forEach/for: iteration source"),
|
|
30105
|
+
endStep: stepNo().optional().describe("loop/try: last step of the body region (required, or endLabel)"),
|
|
30106
|
+
endLabel: labelRef().optional().describe("loop/try: symbolic alternative to endStep"),
|
|
30107
|
+
catches: import_zod11.z.array(import_zod11.z.object({ error: import_zod11.z.string(), step: stepNo().optional(), label: labelRef().optional() }).strict()).optional().describe("try: handler regions, each targeted by step number or label"),
|
|
30108
|
+
finallyStep: stepNo().optional().describe("try: first step of the always-runs region"),
|
|
30109
|
+
finallyLabel: labelRef().optional().describe("try: symbolic alternative to finallyStep"),
|
|
30110
|
+
branches: import_zod11.z.array(import_zod11.z.object({ step: stepNo().optional(), label: labelRef().optional(), name: import_zod11.z.string().optional() }).strict()).optional().describe("parallel: >= 2 arm entries (step number or label), ascending, first = the step after the header; arms are contiguous sub-regions of body next..endStep, joining after endStep once ALL complete"),
|
|
30111
|
+
toStep: stepNo().optional().describe("jump: required (break/continue/rejoin), or toLabel"),
|
|
30112
|
+
toLabel: labelRef().optional().describe("jump: symbolic alternative to toStep \u2014 resolves to the labeled step at write time"),
|
|
30113
|
+
outcome: import_zod11.z.string().optional().describe('return: e.g. "success", "not found"'),
|
|
30114
|
+
error: import_zod11.z.string().optional().describe("throw: the raised error")
|
|
30115
|
+
}).strict();
|
|
30116
|
+
const detailEnum = import_zod11.z.enum(["full", "calls-only", "intent"]);
|
|
30117
|
+
const conformanceEnum = import_zod11.z.enum(["declared", "anchored", "off"]);
|
|
30118
|
+
const implMethodShape = {
|
|
30119
|
+
name: import_zod11.z.string(),
|
|
30120
|
+
sourcePath: import_zod11.z.string().optional().describe("Optional: the source file realizing THIS method when it is not the implementation's sourcePath (e.g. a command whose body lives in its own file); the implementation's sourcePath is the default. Relative to project root \u2014 for a chained subproject's implementation (qualified id), relative to that subproject's root; a path given relative to this root that lands inside the subproject is re-expressed for you"),
|
|
30121
|
+
detail: detailEnum.optional().describe("Detail level for this method (overrides the spec default)"),
|
|
30122
|
+
intent: import_zod11.z.string().optional().describe("detail: intent \u2014 behavioral prose (what it does and how it fails); substitute for a narrative"),
|
|
30123
|
+
conformance: conformanceEnum.optional().describe("Conformance tier for this method (overrides the spec default)"),
|
|
30124
|
+
symbol: import_zod11.z.string().optional().describe("Code-level name realizing this contract method in the sourcePath file, when it legitimately differs from the intent-language contract name (e.g. put realized by saveSnapshot)"),
|
|
30125
|
+
exportedVia: import_zod11.z.string().optional().describe("The exported binding a consumer imports to REACH this method, when the realization is published as a value that composes it rather than exported in its own right (a rule module publishes callConformanceRule, and the method is the check inside it). Not symbol, which names the function itself: this names the route to it, so the surface check reads that export as promised. A handle the method's own source file does not actually export is reported (UNREALIZED_EXPORT_HANDLE) and allows nothing"),
|
|
30126
|
+
ext: import_zod11.z.record(import_zod11.z.unknown()).optional().describe("Opaque pack/tool extension data for this method (namespaced keys) \u2014 preserved verbatim"),
|
|
30127
|
+
narrative: import_zod11.z.array(narrativeStepInput).optional(),
|
|
30128
|
+
calls: import_zod11.z.array(import_zod11.z.string().min(1)).optional().describe('The calls this method makes, when its narrative does not show them (detail: intent, or calls-only with no steps authored) \u2014 each as "<component>.<method>", the spelling the debt register and lint allows name a unit with. The narrative walk takes exactly these edges and no others, so an undeclared collaborator is NOT reached: this is what keeps unused-detection honest at a lower detail dial. Each entry is validated like a call step (the component resolves, this component declares it, the method is on its contract). Refused beside a non-empty narrative \u2014 add the call step there instead')
|
|
30129
|
+
};
|
|
30130
|
+
const implMethodInputFields = Object.keys(implMethodShape);
|
|
30131
|
+
const implInput = {
|
|
30132
|
+
id: import_zod11.z.string().describe('Lowercase identifier, e.g. "vfs_storage"'),
|
|
30133
|
+
name: import_zod11.z.string().describe("Human-readable implementation name"),
|
|
30134
|
+
description: import_zod11.z.string().describe("Implementation details"),
|
|
30135
|
+
contract: import_zod11.z.string().describe("The L3 Interface contract ID this implements"),
|
|
30136
|
+
sourcePath: import_zod11.z.string().optional().describe("Optional: target source code file path relative to project root \u2014 for a chained subproject's implementation (qualified id), relative to that subproject's root; a path given relative to this root that lands inside the subproject is re-expressed for you"),
|
|
30137
|
+
simPath: import_zod11.z.string().optional().describe("Optional: the committed integration-sim harness file (project-relative; N:1 sharing allowed). The validator proves it exists and its import graph wires the REAL modules (this component + each direct dependency; technology adapters may stay faked) \u2014 running it is CI's job. Declaring the first simPath in a subsystem activates MISSING_INTEGRATION_SIM for its other complete non-leaf implementations"),
|
|
30138
|
+
technologies: import_zod11.z.array(import_zod11.z.string()).optional().describe(`External technologies this implementation binds to (e.g. ["mysql"]) \u2014 declares this component's ownership tree as the technology's home; references outside it are flagged (TECH_LEAKAGE) and contract identifiers must stay intent-language. Only for Adapter/Store/Registry/Index components.`),
|
|
30139
|
+
injectedParams: import_zod11.z.array(import_zod11.z.string()).optional().describe("The parameter names this realization takes BEFORE the ones its contract declares \u2014 a config object, a data root, the transport handles a portal is handed. Supplied by whatever wires the component up, never by the caller the contract describes, which is why they belong here and not to the contract: another realization may hold them as fields instead. Declared rather than guessed, because a leading parameter the contract does not name cannot be told from one it named under a different name (`seed(config)` realized as `bootstrapInstance(cfg)`); a leading parameter this list does not name is reported (UNDECLARED_PARAM). Only a LEADING run is dropped \u2014 one of these names appearing after the contract's own parameters is an argument in the middle of the caller's list, not wiring"),
|
|
30140
|
+
detail: detailEnum.optional().describe("Spec-level narrative detail default for all methods"),
|
|
30141
|
+
conformance: conformanceEnum.optional().describe("Spec-level conformance tier default: declared | anchored | off (omitted = stereotype default: Portal \u2192 anchored, else declared)"),
|
|
30142
|
+
methods: import_zod11.z.array(import_zod11.z.object(implMethodShape).strict()).optional().describe("Method implementations containing L5 narratives"),
|
|
30143
|
+
status: statusInput
|
|
30144
|
+
};
|
|
30145
|
+
const implInputFields = Object.keys(implInput);
|
|
30146
|
+
reg(
|
|
30147
|
+
server,
|
|
30148
|
+
"sdd_write_narrative",
|
|
30149
|
+
{
|
|
30150
|
+
description: "Write L4 Concrete Implementation spec containing L5 method narratives. Narratives are a FLAT ordered step list; flow steps (branch/switch/loop/try/parallel/jump/return/throw) jump by step number \u2014 blocks are just skipped regions. Steps may declare a `label` anchor, and every jump field has a *Label twin (toLabel, onTrueLabel, endLabel, \u2026) resolved to step numbers at write time \u2014 prefer labels over hand-counted numbers; an unresolvable label rejects the write. Detail dial per method: full (narrative required) | calls-only (call choreography suffices) | intent (prose instead of steps); omitted = stereotype default (Portal/Observer/Adapter: calls-only, Store/Index/Registry: intent, else full). A method whose narrative shows no steps declares the calls it makes in `calls` (one \"<component>.<method>\" each): the reachability walk takes exactly those edges and no others, so an intent-level method that reaches a collaborator must name it or that collaborator is reported unused. Conformance dial per method or spec: declared | anchored | off \u2014 how strictly structural conformance requires contract methods to be realized in their source file (omitted = Portal: anchored, else declared). A method whose body lives in its own file names it in the method's sourcePath; the implementation's sourcePath is the default for every method that names none. Parameters the realization takes BEFORE its contract's own \u2014 a config object, a data root, a portal's transport handles \u2014 are wiring, and are declared once for the spec in injectedParams; a leading parameter it does not name is reported against the contract (UNDECLARED_PARAM). Re-authoring an existing id REPLACES the method list: a method left out of the input is REMOVED together with its narrative (and reported); spec-level lint/ext are carried forward, and the stored status is kept unless this input states a higher one. The answer carries a write receipt as structured content beside the sentence \u2014 the status written, whether a spec already held the id, and the notices a restatement raised, each as its own entry.",
|
|
30151
|
+
inputSchema: implInput,
|
|
30152
|
+
outputSchema: specWriteReceiptOutput
|
|
30153
|
+
},
|
|
30154
|
+
({ id, name, description, contract, sourcePath, simPath, technologies, injectedParams, detail, conformance, methods, status }) => {
|
|
30155
|
+
try {
|
|
30156
|
+
const restatement = {
|
|
30157
|
+
kind: "implementation",
|
|
30158
|
+
spec: {
|
|
30159
|
+
id,
|
|
30160
|
+
name,
|
|
30161
|
+
description,
|
|
30162
|
+
contract,
|
|
30163
|
+
sourcePath,
|
|
30164
|
+
simPath,
|
|
30165
|
+
technologies,
|
|
30166
|
+
injectedParams,
|
|
30167
|
+
detail,
|
|
30168
|
+
conformance,
|
|
30169
|
+
methods: (methods ?? []).map((m) => ({
|
|
30170
|
+
...m,
|
|
30171
|
+
narrative: (m.narrative ?? []).map((s, i) => ({ ...s, stepNumber: s.stepNumber ?? i + 1 }))
|
|
30172
|
+
}))
|
|
30173
|
+
},
|
|
30174
|
+
fields: implInputFields,
|
|
30175
|
+
memberFields: implMethodInputFields,
|
|
30176
|
+
status
|
|
30177
|
+
};
|
|
30178
|
+
const receipt = writeSpec(restatement);
|
|
30179
|
+
return writeReceipt(
|
|
30180
|
+
`Successfully saved L4 Implementation Spec "${name}" (${id}) with method narratives.`,
|
|
30181
|
+
receipt
|
|
30182
|
+
);
|
|
30183
|
+
} catch (e) {
|
|
30184
|
+
return errMessage(e);
|
|
30185
|
+
}
|
|
30186
|
+
}
|
|
30187
|
+
);
|
|
30188
|
+
const typeInput = {
|
|
30189
|
+
kind: import_zod11.z.enum(["entity", "value-object"]).describe("entity (owned by a subsystem) or value-object (often system-level shared)"),
|
|
30190
|
+
id: import_zod11.z.string().describe("Lowercase identifier"),
|
|
30191
|
+
name: import_zod11.z.string().describe("Human-readable name"),
|
|
30192
|
+
description: import_zod11.z.string().optional(),
|
|
30193
|
+
subsystem: import_zod11.z.string().optional().describe("Owning subsystem id; omit for a system-level shared value object. A subsystem the tree does not have is refused and nothing is written"),
|
|
30194
|
+
group: import_zod11.z.string().optional().describe("Optional logical group ID to organize this type in subfolders"),
|
|
30195
|
+
fields: import_zod11.z.array(import_zod11.z.object({
|
|
30196
|
+
name: import_zod11.z.string(),
|
|
30197
|
+
type: import_zod11.z.string().describe(TYPE_REF_GRAMMAR("The field's type")),
|
|
30198
|
+
description: import_zod11.z.string().optional(),
|
|
30199
|
+
optional: import_zod11.z.boolean().optional(),
|
|
30200
|
+
key: import_zod11.z.enum(["primary", "unique", "foreign"]).optional().describe("Identity marker (PK/unique/FK) for ERD and database schema derivation"),
|
|
30201
|
+
references: import_zod11.z.string().optional().describe('For foreign keys, the referenced type/table id and optional field, e.g. "invoice.id"')
|
|
30202
|
+
}).strict()).optional().describe('Data fields (type is a primitive or a qualified type id, e.g. "billing.Invoice")'),
|
|
30203
|
+
methods: import_zod11.z.array(import_zod11.z.object({
|
|
30204
|
+
name: import_zod11.z.string(),
|
|
30205
|
+
signature: import_zod11.z.string(),
|
|
30206
|
+
returns: import_zod11.z.string().describe(TYPE_REF_GRAMMAR("The type the method answers with")),
|
|
30207
|
+
description: import_zod11.z.string().optional(),
|
|
30208
|
+
sourcePath: import_zod11.z.string().optional().describe("Source file realizing this method when the type's own sourcePath does not hold it \u2014 a pure type method often lives apart from the declaration"),
|
|
30209
|
+
symbol: import_zod11.z.string().optional().describe("Code-level name realizing this method when it differs from the method name, e.g. narrative_step.foreignFields realized by narrativeStepForeignFields")
|
|
30210
|
+
}).strict()).optional().describe("Pure intrinsic methods only"),
|
|
30211
|
+
componentClass: import_zod11.z.string().optional().describe("Optional component id that implements or owns this logical entity"),
|
|
30212
|
+
invariants: import_zod11.z.array(import_zod11.z.object({
|
|
30213
|
+
id: import_zod11.z.string().describe("Stable invariant id, unique within the entity"),
|
|
30214
|
+
description: import_zod11.z.string().describe("The property that must hold, stated precisely")
|
|
30215
|
+
}).strict()).optional().describe("Declared domain invariants (entities): every write-effect method of the componentClass must carry a narrative step asserting each (assertsInvariants) \u2014 declarations checked, enforcement never proven"),
|
|
30216
|
+
database: import_zod11.z.string().optional().describe("Optional database id for table-schema types"),
|
|
30217
|
+
table: import_zod11.z.string().optional().describe("Optional database table name for table-schema types"),
|
|
30218
|
+
linkedEntity: import_zod11.z.string().optional().describe("Optional logical entity id represented by this table-schema type"),
|
|
30219
|
+
sourcePath: import_zod11.z.string().optional().describe("Source file holding the declaration of this type (project-relative). Naming one turns the type into a claim on code: the file must resolve and the declaration must be anchored in it (UNREALIZED_TYPE)"),
|
|
30220
|
+
symbol: import_zod11.z.string().optional().describe('Code-level name realizing the declaration when it differs from name, e.g. a type named "Invoice Line" declared as InvoiceLine')
|
|
30221
|
+
};
|
|
30222
|
+
const typeInputFields = Object.keys(typeInput);
|
|
30223
|
+
reg(
|
|
30224
|
+
server,
|
|
30225
|
+
"sdd_add_type",
|
|
30226
|
+
{
|
|
30227
|
+
description: "Define an entity or value-object type (the data components operate on). Entities are owned by a subsystem; shared value objects omit subsystem (system-level). Fields are data; methods are PURE intrinsic behaviour only \u2014 anything needing a collaborator belongs on a component, taking the entity as an argument. A type may also CLAIM code: sourcePath names the file holding its declaration (and each method may name its own), symbol binds the code-level name when it differs \u2014 the file must then resolve and the declaration must be anchored in it. An owning subsystem the tree does not have is refused, writing nothing, exactly as a component under an unknown subsystem is. Re-defining an existing id REPLACES fields/methods/invariants and restates sourcePath/symbol (an omitted list or path is CLEARED, and a dropped member is reported); lint/ext are carried forward. The answer carries a write receipt as structured content beside the sentence \u2014 whether a spec already held the id, and the notices a restatement raised, each as its own entry. A type carries no lifecycle status, so the receipt states none.",
|
|
30228
|
+
inputSchema: typeInput,
|
|
30229
|
+
outputSchema: specWriteReceiptOutput
|
|
30230
|
+
},
|
|
30231
|
+
({ kind, id, name, description, subsystem, group, fields, methods, componentClass, invariants, database, table, linkedEntity, sourcePath, symbol }) => {
|
|
30232
|
+
try {
|
|
30233
|
+
const spec = {
|
|
30234
|
+
kind,
|
|
30235
|
+
id,
|
|
30236
|
+
name,
|
|
30237
|
+
...description ? { description } : {},
|
|
30238
|
+
...subsystem ? { subsystem } : {},
|
|
30239
|
+
...group ? { group } : {},
|
|
30240
|
+
fields: (fields ?? []).map((f) => ({
|
|
30241
|
+
name: f.name,
|
|
30242
|
+
type: f.type,
|
|
30243
|
+
description: f.description,
|
|
30244
|
+
optional: f.optional ?? false,
|
|
30245
|
+
...f.key ? { key: f.key } : {},
|
|
30246
|
+
...f.references ? { references: f.references } : {}
|
|
30247
|
+
})),
|
|
30248
|
+
methods: methods ?? [],
|
|
30249
|
+
...componentClass ? { componentClass } : {},
|
|
30250
|
+
...invariants?.length ? { invariants } : {},
|
|
30251
|
+
...database ? { database } : {},
|
|
30252
|
+
...table ? { table } : {},
|
|
30253
|
+
...linkedEntity ? { linkedEntity } : {},
|
|
30254
|
+
...sourcePath ? { sourcePath } : {},
|
|
30255
|
+
...symbol ? { symbol } : {}
|
|
30256
|
+
};
|
|
30257
|
+
const receipt = writeSpec({ kind: "type", spec, fields: typeInputFields });
|
|
30258
|
+
return writeReceipt(
|
|
30259
|
+
`Successfully ${receipt.replacedExisting ? "re-authored" : "defined"} ${kind} type "${name}" (${id}).`,
|
|
30260
|
+
receipt
|
|
30261
|
+
);
|
|
30262
|
+
} catch (e) {
|
|
30263
|
+
return errMessage(e);
|
|
30264
|
+
}
|
|
30265
|
+
}
|
|
30266
|
+
);
|
|
30267
|
+
reg(
|
|
30268
|
+
server,
|
|
30269
|
+
"sdd_validate_tree",
|
|
30270
|
+
{
|
|
30271
|
+
description: "Validate the SDD spec tree, checking parent references, contract compatibility, narratives, and component type boundaries. Supports scoping and recursion controls. Findings come back as structured content too, under the schema this tool declares \u2014 errors and warnings already split, each with its code, severity, message and the spec it concerns \u2014 so a caller filters them as objects instead of parsing the JSON text block and hoping its shape holds.",
|
|
30272
|
+
inputSchema: {
|
|
30273
|
+
subsystem: import_zod11.z.string().optional().describe("Only validate the specified subsystem (granular)"),
|
|
30274
|
+
recursive: import_zod11.z.boolean().optional().describe("Whether to recursively validate subprojects (default: true)")
|
|
30275
|
+
},
|
|
30276
|
+
outputSchema: validateTreeOutput
|
|
30277
|
+
},
|
|
30278
|
+
({ subsystem, recursive }) => {
|
|
30279
|
+
try {
|
|
30280
|
+
const config = loadProjectConfig();
|
|
30281
|
+
if (!config) throw new ProjectNotInitializedError();
|
|
30282
|
+
const result = validateSddTree({
|
|
30283
|
+
rules: config.rules,
|
|
30284
|
+
projectType: config.projectType,
|
|
30285
|
+
scopeSubsystem: subsystem,
|
|
30286
|
+
recursive: recursive ?? true
|
|
30287
|
+
});
|
|
30288
|
+
return jsonStructured({
|
|
30289
|
+
valid: result.valid,
|
|
30290
|
+
errors: result.issues.filter((i) => i.severity === "error"),
|
|
30291
|
+
warnings: result.issues.filter((i) => i.severity === "warning"),
|
|
30292
|
+
...result.resolvedThrough ? { resolvedThrough: result.resolvedThrough } : {}
|
|
30293
|
+
});
|
|
30294
|
+
} catch (e) {
|
|
30295
|
+
return errText(String(e));
|
|
30296
|
+
}
|
|
30297
|
+
}
|
|
30298
|
+
);
|
|
30299
|
+
reg(
|
|
30300
|
+
server,
|
|
30301
|
+
"sdd_get_spec",
|
|
30302
|
+
{
|
|
30303
|
+
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. The structured content carries the same answer with the two derived markers KEPT SEPARATE from the stored spec ({kind, id, spec, partialResult?, variantGuidance?}), so nothing derived can be mistaken for something stored; the text block folds them in as it always has.`,
|
|
30304
|
+
inputSchema: {
|
|
30305
|
+
kind: import_zod11.z.enum(["system", "subsystem", "component", "interface", "implementation", "type"]).describe("The kind of specification"),
|
|
30306
|
+
id: import_zod11.z.string().describe('The identifier of the spec to fetch (the L0 system spec is a singleton \u2014 pass the system name or "system")'),
|
|
30307
|
+
methods: import_zod11.z.array(import_zod11.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.")
|
|
30308
|
+
},
|
|
30309
|
+
outputSchema: getSpecOutput
|
|
30310
|
+
},
|
|
30311
|
+
({ kind, id, methods }) => {
|
|
30312
|
+
try {
|
|
30313
|
+
let result = null;
|
|
30314
|
+
let partial = null;
|
|
30315
|
+
switch (kind) {
|
|
30316
|
+
case "system":
|
|
30317
|
+
result = loadSystemSpec();
|
|
30318
|
+
break;
|
|
30319
|
+
case "subsystem":
|
|
30320
|
+
result = loadSubsystemSpec(id);
|
|
30321
|
+
break;
|
|
30322
|
+
case "component":
|
|
30323
|
+
result = loadComponentSpec(id);
|
|
30324
|
+
break;
|
|
30325
|
+
case "interface":
|
|
30326
|
+
result = loadInterfaceSpec(id);
|
|
30327
|
+
break;
|
|
30328
|
+
case "implementation":
|
|
30329
|
+
result = loadImplementationSpec(id);
|
|
30330
|
+
break;
|
|
30331
|
+
case "type":
|
|
30332
|
+
result = loadTypeSpec(id);
|
|
30333
|
+
break;
|
|
30334
|
+
}
|
|
30335
|
+
if (!result) return errText(`Spec of kind "${kind}" with ID "${id}" does not exist.`);
|
|
30336
|
+
if (methods !== void 0) {
|
|
30337
|
+
const declared = result.methods;
|
|
30338
|
+
if (!Array.isArray(declared)) {
|
|
30339
|
+
return errText(
|
|
30340
|
+
`A ${kind} spec declares no methods, so "methods" cannot filter it. Drop the argument to read "${id}" whole; methods live on an interface, an implementation and a type.`
|
|
30341
|
+
);
|
|
30342
|
+
}
|
|
30343
|
+
const names = declared.map((m) => String(m?.name));
|
|
30344
|
+
const unknown = methods.filter((n) => !names.includes(n));
|
|
30345
|
+
if (unknown.length > 0) {
|
|
30346
|
+
return errText(
|
|
30347
|
+
`${kind} "${id}" does not declare ${unknown.map((n) => `"${n}"`).join(", ")}. It declares: ${names.join(", ")}. Nothing was returned, so a misspelled name never reads as a method with no content.`
|
|
30348
|
+
);
|
|
30349
|
+
}
|
|
30350
|
+
const kept = declared.filter((m) => methods.includes(String(m?.name)));
|
|
30351
|
+
partial = {
|
|
30352
|
+
shown: kept.map((m) => String(m?.name)),
|
|
30353
|
+
omitted: names.length - kept.length,
|
|
30354
|
+
warning: `PARTIAL: ${names.length - kept.length} of this spec's ${names.length} methods are not in this answer. Never re-author from it \u2014 sdd_define_interface and sdd_write_narrative REPLACE the method list, so every method missing here would be removed from the spec.`
|
|
30355
|
+
};
|
|
30356
|
+
result = { ...result, methods: kept };
|
|
30357
|
+
}
|
|
30358
|
+
const guidance = kind === "component" ? resolveComponentVariantGuidance(result) : null;
|
|
30359
|
+
const folded = {
|
|
30360
|
+
...result,
|
|
30361
|
+
...partial ? { partialResult: partial } : {},
|
|
30362
|
+
...guidance ? { variantGuidance: guidance } : {}
|
|
30363
|
+
};
|
|
30364
|
+
return structured(JSON.stringify(folded, null, 2), {
|
|
30365
|
+
kind,
|
|
30366
|
+
id,
|
|
30367
|
+
spec: result,
|
|
30368
|
+
...partial ? { partialResult: partial } : {},
|
|
30369
|
+
...guidance ? { variantGuidance: guidance } : {}
|
|
30370
|
+
});
|
|
30371
|
+
} catch (e) {
|
|
30372
|
+
return errText(String(e));
|
|
30373
|
+
}
|
|
30374
|
+
}
|
|
30375
|
+
);
|
|
30376
|
+
reg(
|
|
30377
|
+
server,
|
|
30378
|
+
"sdd_delete_spec",
|
|
30379
|
+
{
|
|
30380
|
+
description: "Delete a specification file from the spec tree and clean up any empty parent directories. Answers with a structured deletion beside the sentence: whether a spec was removed, and the tests that encode a method the deletion took away (when the project declares test roots).",
|
|
30381
|
+
inputSchema: {
|
|
30382
|
+
kind: import_zod11.z.enum(["subsystem", "component", "interface", "implementation", "type"]).describe("The kind of spec to delete"),
|
|
30383
|
+
id: import_zod11.z.string().describe("The ID of the spec to delete")
|
|
30384
|
+
},
|
|
30385
|
+
outputSchema: specDeletionOutput
|
|
30386
|
+
},
|
|
30387
|
+
({ kind, id }) => {
|
|
30388
|
+
try {
|
|
30389
|
+
const deletion = deleteSpec2(kind, id);
|
|
30390
|
+
if (!deletion.deleted) return errText(`Spec of kind "${kind}" with ID "${id}" could not be deleted (file may not exist).`);
|
|
30391
|
+
const testsBlock = renderTestsToRevisit(deletion.testsToRevisit).join("\n");
|
|
30392
|
+
return structured(`Successfully deleted ${kind} spec "${id}".${testsBlock ? `
|
|
30393
|
+
${testsBlock}` : ""}`, deletion);
|
|
30394
|
+
} catch (e) {
|
|
30395
|
+
return errText(String(e));
|
|
30396
|
+
}
|
|
30397
|
+
}
|
|
30398
|
+
);
|
|
30399
|
+
reg(
|
|
30400
|
+
server,
|
|
30401
|
+
"sdd_update_spec",
|
|
30402
|
+
{
|
|
30403
|
+
description: "Update/patch an existing SDD specification (subsystem, component, interface, implementation, or type) using a granular delta. Updates fields, appends/merges array elements, or inserts/deletes narrative steps. Answers with exactly what changed, and with every path the delta named that the write did not act on. Pass dryRun to be told what it would do without writing it.",
|
|
30404
|
+
inputSchema: {
|
|
30405
|
+
kind: import_zod11.z.enum(["system", "subsystem", "component", "interface", "implementation", "type"]).describe("The spec kind to update (system = the singleton L0 \u2014 vision, boundaries, globalRequirements, databases, and publicInterfaces: the project gateway surface, each entry {id, name, subsystem, component, type, details, audience: project|department|instance|partner|external}; id is informational)"),
|
|
30406
|
+
id: import_zod11.z.string().describe("The ID of the spec to update (namespaced if needed)"),
|
|
30407
|
+
delta: import_zod11.z.record(import_zod11.z.any()).describe(`The partial fields to merge into the spec. ARRAYS UPSERT, they do not replace: an array whose elements carry an identity is merged element-by-element, so a delta naming ONE element leaves the others intact. Identity is "name" or "id" by default, and per field: dispatch by "capability", a listener's mounts by "portal", lifecycle by phase+component+method, emits/subscribesTo by topic+event, trustedLinks by "subsystem", a subsystem's publicInterfaces by component+interface (or type+details for an entry not yet bound to a component), invariants and patterns by "id", lint.allow by "code" AND "at" (an allow covers one occurrence, so several may share a code on one spec), an interface method's findings by "code", boundaries by "name", globalRequirements by "description", switch cases by "value", try catches by "error". Identity merging applies at EVERY depth, including an array INSIDE an element (a method's params, a step's catches). Add "action: 'delete'" (or "remove: true") alongside that identity to REMOVE an element \u2014 including a stale lint allow. Arrays of plain STRINGS (owns, dependsOn, guarantees) carry no per-element identity and are replaced wholesale; pass [] to clear any array outright. To REMOVE an optional field entirely, list it in "unset": e.g. {"unset": ["basePath", "variant"]} \u2014 passing null/undefined means "no change" (they are skipped), and writing "" would leave the field present but empty, which is a different and usually wrong spec. Unsetting a required field is refused by schema validation, which names it. For narrative steps, match by "stepNumber" and use "action: 'insert'" (shifts subsequent steps up) or "action: 'delete'" (shifts subsequent steps down and removes it). Step entries apply in ASCENDING stepNumber order, each against the numbering the earlier entries of the SAME delta left behind \u2014 delete step 3 and step 7 becomes step 6 \u2014 so prefer labels, and restate the step's "label" or "description" on a delete to have it checked against the step actually addressed. Renumbering RELOCATES every flow jump field (onTrueStep/onFalseStep/cases.step/defaultStep/endStep/catches.step/finallyStep/toStep) in the same narrative. A delete is REJECTED when the narrative has no such step, when a jump still targets it (retarget the referrers first), when a restated label/description does not match, or when it is a loop/try/parallel header whose body would be left standing (retype the header first to dissolve the region, then delete it). Changing a step's "type" REBUILDS it for the new type: its description and label are kept and every field the new type cannot carry is dropped (returned as a NOTICE); a delta that retypes AND sets such a field is refused. A step delta is also refused when it carries a marker the merge does not recognise: a non-boolean "remove", an "action" that is neither "insert" nor "delete", a "captureJumps" outside an insert, or no "stepNumber" to address. Inserting AT a jump target relocates those jumps past the inserted step by default (a NOTICE is returned) \u2014 add "captureJumps": true on the inserted step to retarget entry jumps onto it (loop/try endStep region tails always relocate with the body and are never captured). Every jump field has a "*Label" twin (toLabel, onFalseLabel, endLabel, \u2026, and "label" on a cases/catches entry) resolved against step labels AFTER the merge, so a delta may anchor on a label only pre-existing steps carry; a label the delta supplies REPLACES the stored number it twins, while setting the number and its label together in one delta is refused as a contradiction. Reference ids in deltas may use LOCAL names \u2014 they are qualified against the spec's namespace exactly as the loader would. Per-spec lint suppression: set "lint: { allow: [{ code, at, covers, reason }] }" to silence a WARNING code on this spec only (errors always surface; stale allows are flagged). An allow covers EXACTLY the occurrence it names: "at" is the site the finding names (a contract method, an import edge "from -> to", a declared edge "component -> target") and is REQUIRED for a code whose findings report one, while a finding that reports no site is covered only by an allow that names none; "covers" lists the units an aggregating finding reports, and the allow silences it only when every one is listed. This delta is deliberately OPEN below its top level \u2014 the shapes nest further than a schema here should restate \u2014 so a key that is not a field at its depth is not refused, it is NAMED BACK under NO EFFECT in the answer, together with any value the spec already held and any "unset" that removed nothing. Read that list: it is where a nested typo shows up.`),
|
|
30408
|
+
dryRun: import_zod11.z.boolean().optional().describe("Ask what this delta WOULD do instead of doing it. The whole write runs, the candidate gate included, and the answer is the change report it would have produced \u2014 marked DRY RUN, with nothing stamped and not one byte of the stored file moved. Use it before a delta that renumbers a long narrative.")
|
|
30409
|
+
},
|
|
30410
|
+
outputSchema: specChangeReportOutput
|
|
30411
|
+
},
|
|
30412
|
+
({ kind, id, delta, dryRun }) => {
|
|
30413
|
+
try {
|
|
30414
|
+
const report = updateSpecGated(kind, id, delta, dryRun);
|
|
30415
|
+
return structured(renderChangeReport(report), report);
|
|
30416
|
+
} catch (e) {
|
|
30417
|
+
return errText(String(e));
|
|
30418
|
+
}
|
|
30419
|
+
}
|
|
30420
|
+
);
|
|
30421
|
+
reg(
|
|
30422
|
+
server,
|
|
30423
|
+
"sdd_get_status",
|
|
30424
|
+
{
|
|
30425
|
+
description: "Get the completeness status dashboard of the SDD spec tree. Supports scoping and recursion controls.",
|
|
30426
|
+
inputSchema: {
|
|
30427
|
+
subsystem: import_zod11.z.string().optional().describe("Only show status for the specified subsystem"),
|
|
30428
|
+
recursive: import_zod11.z.boolean().optional().describe("Whether to recursively load subprojects (default: true)")
|
|
30429
|
+
}
|
|
30430
|
+
},
|
|
30431
|
+
({ subsystem, recursive }) => {
|
|
30432
|
+
try {
|
|
30433
|
+
const report = getStatusReport({
|
|
30434
|
+
subsystem,
|
|
30435
|
+
recursive: recursive ?? true
|
|
30436
|
+
});
|
|
30437
|
+
const family = statusFamilyContext();
|
|
30438
|
+
const verdict = approvalVerdict();
|
|
30439
|
+
return text(`${family}${report.text}${verdict.text}`);
|
|
30440
|
+
} catch (e) {
|
|
30441
|
+
return errText(String(e));
|
|
30442
|
+
}
|
|
30443
|
+
}
|
|
30444
|
+
);
|
|
30445
|
+
reg(
|
|
30446
|
+
server,
|
|
30447
|
+
"sdd_list_external_interfaces",
|
|
30448
|
+
{
|
|
30449
|
+
description: "List the bound project's consumable external surfaces (parent family, siblings, foreign imports) as discovery entries with origin, provenance, and freshness \u2014 the tool an agent inside a subproject uses to SEE its outward world instead of discovering it by failed reference resolution. Full contracts stay in the vendored snapshots (.wai/surfaces/); each entry summarizes the interface ids it exposes."
|
|
30450
|
+
},
|
|
30451
|
+
() => {
|
|
30452
|
+
try {
|
|
30453
|
+
return json(listExternalInterfaces());
|
|
30454
|
+
} catch (e) {
|
|
30455
|
+
return errText(String(e));
|
|
30456
|
+
}
|
|
30457
|
+
}
|
|
30458
|
+
);
|
|
30459
|
+
reg(
|
|
30460
|
+
server,
|
|
30461
|
+
"sdd_get_agent_brief",
|
|
30462
|
+
{
|
|
30463
|
+
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).",
|
|
30464
|
+
inputSchema: {
|
|
30465
|
+
agentId: import_zod11.z.string().describe("The resolved agent id (list via listAgents or resources/list)")
|
|
30466
|
+
}
|
|
30467
|
+
},
|
|
30468
|
+
({ agentId }) => {
|
|
30469
|
+
try {
|
|
30470
|
+
return json(composeAgentBrief(agentId));
|
|
30471
|
+
} catch (e) {
|
|
30472
|
+
return errText(String(e));
|
|
30473
|
+
}
|
|
30474
|
+
}
|
|
30475
|
+
);
|
|
30476
|
+
registerSkillResources(server, !options.hostedTools);
|
|
30477
|
+
registerSkillPrompts(server, !options.hostedTools);
|
|
30478
|
+
try {
|
|
30479
|
+
const chainingParent = resolveChainingParent();
|
|
30480
|
+
if (chainingParent) {
|
|
30481
|
+
let externalSurfaceCount = 0;
|
|
30482
|
+
try {
|
|
30483
|
+
externalSurfaceCount = listExternalInterfaces().length;
|
|
30484
|
+
} catch {
|
|
30485
|
+
}
|
|
30486
|
+
process.stderr.write(
|
|
30487
|
+
`[wairon mcp] chained subproject: this root is mounted as subsystem "${chainingParent.subsystemId}" of the parent project at ${chainingParent.parentRoot} \u2014 ${externalSurfaceCount} vendored external surface(s) discoverable via sdd_list_external_interfaces
|
|
30488
|
+
`
|
|
30489
|
+
);
|
|
30490
|
+
}
|
|
30491
|
+
} catch {
|
|
30492
|
+
}
|
|
30493
|
+
advertiseHostedTools(server, options);
|
|
30494
|
+
return server;
|
|
30495
|
+
}
|
|
30496
|
+
function advertiseHostedTools(server, options) {
|
|
30497
|
+
if (options.hostedTools) {
|
|
30498
|
+
const hostedStub = () => errText("This hosted tool is dispatched by the hosting data plane before reaching the MCP server; it is unavailable outside a hosted request.");
|
|
30499
|
+
reg(server, "sdd_host_lock_project", {
|
|
30500
|
+
description: "Hosted project lifecycle (execute-primary): LOCK the bound project (validate-as-complete gate + commit-scoped lock record). Executes directly when your resolved permission is yes and returns the completed outcome; when it is approval, a pending approval request is created instead (await it with sdd_host_await_approval).",
|
|
30501
|
+
inputSchema: {}
|
|
30502
|
+
}, hostedStub);
|
|
30503
|
+
reg(server, "sdd_host_initialize_project", {
|
|
30504
|
+
description: "Hosted project lifecycle (execute-primary): initialize a new hosted project into its REQUIRED owner organization unit (with an optional profile selection). Executes directly when your resolved permission is yes; when it is approval, a pending approval request is created instead.",
|
|
30505
|
+
inputSchema: {
|
|
30506
|
+
id: import_zod11.z.string().describe("Requested project id"),
|
|
30507
|
+
displayName: import_zod11.z.string().optional(),
|
|
30508
|
+
description: import_zod11.z.string().optional(),
|
|
30509
|
+
ownerUnitId: import_zod11.z.string().describe("REQUIRED organization unit that owns the new project (every project is placed at creation)"),
|
|
30510
|
+
environment: import_zod11.z.string().optional()
|
|
30511
|
+
}
|
|
30512
|
+
}, hostedStub);
|
|
30513
|
+
reg(server, "sdd_host_get_approval_status", {
|
|
30514
|
+
description: "Hosted project lifecycle: read the status of one of your approval requests.",
|
|
30515
|
+
inputSchema: { requestId: import_zod11.z.string() }
|
|
30516
|
+
}, hostedStub);
|
|
30517
|
+
reg(server, "sdd_host_await_approval", {
|
|
30518
|
+
description: "Hosted project lifecycle: long-poll one of YOUR approval requests until it is decided (approved requests auto-execute, so an approval resolves as completed) or the timeout elapses. Returns the request in its current state.",
|
|
30519
|
+
inputSchema: {
|
|
30520
|
+
requestId: import_zod11.z.string(),
|
|
30521
|
+
timeoutSeconds: import_zod11.z.number().optional().describe("How long to wait server-side (clamped; 0 returns the current state immediately)")
|
|
30522
|
+
}
|
|
30523
|
+
}, hostedStub);
|
|
30524
|
+
reg(server, "sdd_landscape_list_reachable_projects", {
|
|
30525
|
+
description: "Hosted landscape: the projects reachable from the BOUND project through ACTIVE cross-project relations (directional, relations-only), each with the relation ids/kinds and target public interface ids.",
|
|
30526
|
+
inputSchema: {}
|
|
30527
|
+
}, hostedStub);
|
|
30528
|
+
reg(server, "sdd_landscape_list_reachable_project_interfaces", {
|
|
30529
|
+
description: "Hosted landscape: redacted, audience-filtered public interface summaries of one reachable target project (Forbidden outside the reachable set; private by default).",
|
|
30530
|
+
inputSchema: { projectId: import_zod11.z.string().describe("The reachable target project id") }
|
|
30531
|
+
}, hostedStub);
|
|
30532
|
+
reg(server, "sdd_landscape_list_visible_surfaces", {
|
|
30533
|
+
description: "Hosted landscape: the visibility-resolved discovery catalog for the BOUND project \u2014 every target the organization unit graph exposes to it (open-within-tenant, closed groups hidden, exposeTo grants honored), with audience distance and audience-filtered summaries. No relation required.",
|
|
30534
|
+
inputSchema: {}
|
|
30535
|
+
}, hostedStub);
|
|
30536
|
+
reg(server, "sdd_landscape_get_project_surface", {
|
|
30537
|
+
description: `Hosted landscape: fetch a visible target project's CONTRACT-GRADE surface snapshot (full method contracts, dispatch tables, type closure) at your audience-distance ceiling, origin "exchanged" \u2014 save it under .wai/surfaces/ (wairon surface import) so your adapters validate against the declared contract.`,
|
|
30538
|
+
inputSchema: { projectId: import_zod11.z.string().describe("The visible target project id") }
|
|
30539
|
+
}, hostedStub);
|
|
30540
|
+
reg(server, "sdd_host_pack_list", {
|
|
30541
|
+
description: "Hosted project ops: list the BOUND project's registered extension packs (requires project:read over it).",
|
|
30542
|
+
inputSchema: {}
|
|
30543
|
+
}, hostedStub);
|
|
30544
|
+
reg(server, "sdd_host_pack_install", {
|
|
30545
|
+
description: "Hosted project ops: install a DECLARATIVE extension pack (profiles + language/platform tables \u2014 never rule/code packs) into the BOUND project (requires project:admin over it).",
|
|
30546
|
+
inputSchema: {
|
|
30547
|
+
name: import_zod11.z.string().describe("Pack name (letters, digits, dot, underscore, hyphen)"),
|
|
30548
|
+
content: import_zod11.z.string().describe("The declarative pack YAML content")
|
|
30549
|
+
}
|
|
30550
|
+
}, hostedStub);
|
|
30551
|
+
reg(server, "sdd_host_policy_evaluate", {
|
|
30552
|
+
description: "Hosted project ops: evaluate the BOUND project's packs and profile selection against the instance pack policy \u2014 compliance without side effects (requires project:write over it).",
|
|
30553
|
+
inputSchema: {}
|
|
30554
|
+
}, hostedStub);
|
|
30555
|
+
reg(server, "sdd_host_policy_reconcile", {
|
|
30556
|
+
description: "Hosted project ops: reconcile the BOUND project against the instance pack policy \u2014 under auto_reconcile enforcement the missing required/default declarative packs are applied (requires project:write over it).",
|
|
30557
|
+
inputSchema: {}
|
|
30558
|
+
}, hostedStub);
|
|
30559
|
+
reg(server, "sdd_host_produce", {
|
|
30560
|
+
description: "Hosted project ops: run a configured producer projection (Notion/Miro) of the BOUND project to the named target (requires project:admin over it).",
|
|
30561
|
+
inputSchema: { target: import_zod11.z.string().describe("The configured producer target (e.g. notion, miro)") }
|
|
30562
|
+
}, hostedStub);
|
|
30563
|
+
reg(server, "sdd_host_commit_project", {
|
|
30564
|
+
description: "Hosted project ops: publish a DELIBERATE, .wai/-scoped commit+push of the BOUND project's specs to its bound repository (commit = local save, push = the actual backup; a clean scope publishes nothing). The data plane binds ONE project, so there is no project argument. Requires project:write over it.",
|
|
30565
|
+
inputSchema: {
|
|
30566
|
+
subsystem: import_zod11.z.string().optional().describe("Narrow staging to .wai/specs/<subsystem>/ (a convenience \u2014 git history stays per-repo)"),
|
|
30567
|
+
message: import_zod11.z.string().optional().describe("Commit message; defaults to a timestamped wairon message")
|
|
30568
|
+
}
|
|
30569
|
+
}, hostedStub);
|
|
30570
|
+
reg(server, "sdd_host_export_tree", {
|
|
30571
|
+
description: "Hosted spec-tree transfer: pack the BOUND project's WHOLE spec tree \u2014 its own .wai plus every chained subproject \u2014 into a .waitree archive, returned as base64 with its roots, file count and state id. The migration counterpart of an import: use it to take a hosted project local, or to move it to another instance. Requires project:read over the project. Bounded by the data-plane body cap; a very large tree exports through the web download route instead. Refuses when a chained mount cannot be packed (escaping, missing, cyclic, too deep, or holding no .wai), naming every one and why, unless allowPartial is set \u2014 then the archive is built anyway and the result lists what was skipped.",
|
|
30572
|
+
inputSchema: {
|
|
30573
|
+
allowPartial: import_zod11.z.boolean().optional().describe("Build the archive even when some chained mounts cannot be packed, listing them as skipped; default false (refuse and name them)")
|
|
30574
|
+
}
|
|
30575
|
+
}, hostedStub);
|
|
30576
|
+
reg(server, "sdd_host_import_tree", {
|
|
30577
|
+
description: "Hosted spec-tree transfer: REPLACE the BOUND project's spec tree from a base64 .waitree archive. Requires project:admin over the project (strictly above project:write \u2014 this replaces the whole design, not one spec). Refuses an occupied destination unless replaceExisting is set, always refuses executable entries (rule/code packs install only through the trusted filesystem), and moves the previous tree aside to a backup whose path is returned.",
|
|
30578
|
+
inputSchema: {
|
|
30579
|
+
archiveBase64: import_zod11.z.string().describe("The .waitree archive bytes, base64-encoded"),
|
|
30580
|
+
replaceExisting: import_zod11.z.boolean().optional().describe("Replace a tree already present (backed up first); without it an occupied destination is refused")
|
|
30581
|
+
}
|
|
30582
|
+
}, hostedStub);
|
|
30583
|
+
}
|
|
30584
|
+
}
|
|
30585
|
+
|
|
30586
|
+
// src/index.ts
|
|
30587
|
+
function loadProjectConfig3() {
|
|
27192
30588
|
const config = loadProjectConfig();
|
|
27193
30589
|
if (!config) throw new ProjectNotInitializedError();
|
|
27194
30590
|
return config;
|
|
@@ -27497,6 +30893,7 @@ function loadProjectConfig2() {
|
|
|
27497
30893
|
saveSpec,
|
|
27498
30894
|
saveTopologyConfig,
|
|
27499
30895
|
scanAllSpecs,
|
|
30896
|
+
schemaFingerprint,
|
|
27500
30897
|
serializeYaml,
|
|
27501
30898
|
setExecutionTier,
|
|
27502
30899
|
setLogLevel,
|