@wairon/cli 5.1.1-dev.35 → 5.1.1-dev.37
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 +437 -128
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +312 -79
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -65,7 +65,7 @@ var init_defaults = __esm({
|
|
|
65
65
|
copilot: ".github/prompts",
|
|
66
66
|
codex: ".codex/agents"
|
|
67
67
|
};
|
|
68
|
-
WAIRON_VERSION = "5.1.1-dev.
|
|
68
|
+
WAIRON_VERSION = "5.1.1-dev.37";
|
|
69
69
|
GITHUB_REPO = "SYW-Apps/Waffle-AIron";
|
|
70
70
|
SUPPORTED_ALIASES = ["wai"];
|
|
71
71
|
SCAN_EXCLUDE_DIRS = /* @__PURE__ */ new Set([
|
|
@@ -2243,13 +2243,34 @@ var init_step_graph = __esm({
|
|
|
2243
2243
|
});
|
|
2244
2244
|
|
|
2245
2245
|
// src/models/step-config.ts
|
|
2246
|
+
function stepFieldsFor(type) {
|
|
2247
|
+
const own = STEP_TYPE_FIELDS[type] ?? [];
|
|
2248
|
+
const allowed = /* @__PURE__ */ new Set([...STEP_COMMON_FIELDS, ...own]);
|
|
2249
|
+
for (const field of own) {
|
|
2250
|
+
const twin = STEP_LABEL_TWINS[field];
|
|
2251
|
+
if (twin) allowed.add(twin);
|
|
2252
|
+
}
|
|
2253
|
+
return allowed;
|
|
2254
|
+
}
|
|
2255
|
+
function isSet(step, field) {
|
|
2256
|
+
const v = step[field];
|
|
2257
|
+
if (v === void 0 || v === null) return false;
|
|
2258
|
+
if (Array.isArray(v) && v.length === 0) return false;
|
|
2259
|
+
return true;
|
|
2260
|
+
}
|
|
2246
2261
|
function flowConfigOn(step) {
|
|
2247
|
-
return FLOW_FIELDS.filter((f) =>
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2262
|
+
return FLOW_FIELDS.filter((f) => isSet(step, f));
|
|
2263
|
+
}
|
|
2264
|
+
function narrativeStepForeignFields(step) {
|
|
2265
|
+
const type = step.type;
|
|
2266
|
+
if (!Object.prototype.hasOwnProperty.call(STEP_TYPE_FIELDS, type)) return [];
|
|
2267
|
+
const allowed = stepFieldsFor(type);
|
|
2268
|
+
return Object.keys(step).filter((f) => !allowed.has(f) && isSet(step, f));
|
|
2269
|
+
}
|
|
2270
|
+
function listFields(fields) {
|
|
2271
|
+
const quoted = fields.map((f) => `"${f}"`);
|
|
2272
|
+
if (quoted.length === 1) return quoted[0];
|
|
2273
|
+
return `${quoted.slice(0, -1).join(", ")} and ${quoted[quoted.length - 1]}`;
|
|
2253
2274
|
}
|
|
2254
2275
|
function stepConfigVerdict(method2) {
|
|
2255
2276
|
const steps = method2.narrative ?? [];
|
|
@@ -2286,8 +2307,10 @@ function stepConfigVerdict(method2) {
|
|
|
2286
2307
|
return i !== void 0 && i + 1 < nums.length ? nums[i + 1] : void 0;
|
|
2287
2308
|
};
|
|
2288
2309
|
for (const s of steps) {
|
|
2310
|
+
const alreadyNamed = /* @__PURE__ */ new Set();
|
|
2289
2311
|
if (s.detach && s.type !== "call" && s.type !== "dispatch") {
|
|
2290
2312
|
malformed(s.stepNumber, `step ${s.stepNumber} (${s.type}) carries "detach" \u2014 fire-and-forget applies to call/dispatch steps only.`);
|
|
2313
|
+
alreadyNamed.add("detach");
|
|
2291
2314
|
}
|
|
2292
2315
|
switch (s.type) {
|
|
2293
2316
|
case "local":
|
|
@@ -2295,7 +2318,10 @@ function stepConfigVerdict(method2) {
|
|
|
2295
2318
|
case "register":
|
|
2296
2319
|
case "dispatch": {
|
|
2297
2320
|
const extra = flowConfigOn(s);
|
|
2298
|
-
if (extra.length)
|
|
2321
|
+
if (extra.length) {
|
|
2322
|
+
malformed(s.stepNumber, `step ${s.stepNumber} (${s.type}) carries flow config (${extra.join(", ")}) \u2014 use a flow step type instead.`);
|
|
2323
|
+
for (const f of extra) alreadyNamed.add(f);
|
|
2324
|
+
}
|
|
2299
2325
|
break;
|
|
2300
2326
|
}
|
|
2301
2327
|
case "branch":
|
|
@@ -2338,6 +2364,14 @@ function stepConfigVerdict(method2) {
|
|
|
2338
2364
|
if (s.toStep === void 0) malformed(s.stepNumber, `jump step ${s.stepNumber} requires "toStep".`);
|
|
2339
2365
|
break;
|
|
2340
2366
|
}
|
|
2367
|
+
const foreign = narrativeStepForeignFields(s).filter((f) => !alreadyNamed.has(f));
|
|
2368
|
+
if (foreign.length) {
|
|
2369
|
+
problems.push({
|
|
2370
|
+
stepNumber: s.stepNumber,
|
|
2371
|
+
code: "FOREIGN_STEP_FIELD",
|
|
2372
|
+
detail: `step ${s.stepNumber} (${s.type}) carries ${listFields(foreign)}, which a ${s.type} step cannot \u2014 left over from an edit that changed the step's type, configuring nothing. \`wairon doctor --fix\` drops them; give the step the type that carries them instead if that is what it does.`
|
|
2373
|
+
});
|
|
2374
|
+
}
|
|
2341
2375
|
for (const [field, target] of jumpFieldsOf(s)) {
|
|
2342
2376
|
if (byNum.has(target)) continue;
|
|
2343
2377
|
sound = false;
|
|
@@ -2363,24 +2397,43 @@ function jumpFieldsOf(s) {
|
|
|
2363
2397
|
(s.branches ?? []).forEach((b, i) => fields.push([`branches[${i}].step`, b.step]));
|
|
2364
2398
|
return fields;
|
|
2365
2399
|
}
|
|
2366
|
-
var FLOW_FIELDS;
|
|
2400
|
+
var STEP_TYPE_FIELDS, STEP_COMMON_FIELDS, STEP_LABEL_TWINS, FLOW_STEP_TYPES, FLOW_FIELDS;
|
|
2367
2401
|
var init_step_config = __esm({
|
|
2368
2402
|
"src/models/step-config.ts"() {
|
|
2369
2403
|
"use strict";
|
|
2404
|
+
STEP_TYPE_FIELDS = {
|
|
2405
|
+
local: [],
|
|
2406
|
+
call: ["targetComponent", "targetMethod", "auth", "detach"],
|
|
2407
|
+
register: ["targetComponent", "targetMethod"],
|
|
2408
|
+
dispatch: ["targetComponent", "capability", "auth", "detach"],
|
|
2409
|
+
branch: ["condition", "onTrueStep", "onFalseStep"],
|
|
2410
|
+
switch: ["on", "cases", "defaultStep"],
|
|
2411
|
+
loop: ["loopKind", "over", "condition", "endStep"],
|
|
2412
|
+
try: ["endStep", "catches", "finallyStep"],
|
|
2413
|
+
parallel: ["endStep", "branches"],
|
|
2414
|
+
jump: ["toStep"],
|
|
2415
|
+
return: ["outcome"],
|
|
2416
|
+
throw: ["error"]
|
|
2417
|
+
};
|
|
2418
|
+
STEP_COMMON_FIELDS = [
|
|
2419
|
+
"stepNumber",
|
|
2420
|
+
"label",
|
|
2421
|
+
"description",
|
|
2422
|
+
"type",
|
|
2423
|
+
"assertsGuarantees",
|
|
2424
|
+
"assertsInvariants"
|
|
2425
|
+
];
|
|
2426
|
+
STEP_LABEL_TWINS = {
|
|
2427
|
+
onTrueStep: "onTrueLabel",
|
|
2428
|
+
onFalseStep: "onFalseLabel",
|
|
2429
|
+
defaultStep: "defaultLabel",
|
|
2430
|
+
endStep: "endLabel",
|
|
2431
|
+
finallyStep: "finallyLabel",
|
|
2432
|
+
toStep: "toLabel"
|
|
2433
|
+
};
|
|
2434
|
+
FLOW_STEP_TYPES = ["branch", "switch", "loop", "try", "parallel", "jump"];
|
|
2370
2435
|
FLOW_FIELDS = [
|
|
2371
|
-
|
|
2372
|
-
"onTrueStep",
|
|
2373
|
-
"onFalseStep",
|
|
2374
|
-
"on",
|
|
2375
|
-
"cases",
|
|
2376
|
-
"defaultStep",
|
|
2377
|
-
"loopKind",
|
|
2378
|
-
"over",
|
|
2379
|
-
"endStep",
|
|
2380
|
-
"catches",
|
|
2381
|
-
"finallyStep",
|
|
2382
|
-
"branches",
|
|
2383
|
-
"toStep"
|
|
2436
|
+
...new Set(FLOW_STEP_TYPES.flatMap((t) => [...STEP_TYPE_FIELDS[t]]))
|
|
2384
2437
|
];
|
|
2385
2438
|
}
|
|
2386
2439
|
});
|
|
@@ -9798,20 +9851,22 @@ var init_narrative_step_config = __esm({
|
|
|
9798
9851
|
init_models();
|
|
9799
9852
|
narrativeStepConfigRule = {
|
|
9800
9853
|
name: "narrative-step-config",
|
|
9801
|
-
description: 'The step configuration of L5 narratives: every flow step (branch, switch, loop, try, parallel, jump) carries the config its type requires and no simple step carries flow config, "detach" (fire-and-forget) is legal on call/dispatch steps only, a parallel body is covered by contiguous, ordered arms (>= 2), no two steps share a step number or a label, and every jump field targets an existing step number in the same narrative.',
|
|
9854
|
+
description: 'The step configuration of L5 narratives: every flow step (branch, switch, loop, try, parallel, jump) carries the config its type requires and no simple step carries flow config, "detach" (fire-and-forget) is legal on call/dispatch steps only, no step carries a field foreign to its type, a parallel body is covered by contiguous, ordered arms (>= 2), no two steps share a step number or a label, and every jump field targets an existing step number in the same narrative.',
|
|
9802
9855
|
codes: [
|
|
9803
9856
|
{ code: "MALFORMED_FLOW_STEP", defaultSeverity: "error", summary: "Flow step missing required config (or flow config on a local/call step)" },
|
|
9804
9857
|
{ code: "INVALID_STEP_JUMP", defaultSeverity: "error", summary: "Jump field targets a step number that does not exist in the narrative" },
|
|
9805
|
-
{ code: "DUPLICATE_STEP_LABEL", defaultSeverity: "error", summary: "Two steps in one narrative share a label \u2014 the symbolic anchor later deltas address by" }
|
|
9858
|
+
{ code: "DUPLICATE_STEP_LABEL", defaultSeverity: "error", summary: "Two steps in one narrative share a label \u2014 the symbolic anchor later deltas address by" },
|
|
9859
|
+
{ code: "FOREIGN_STEP_FIELD", defaultSeverity: "warning", summary: "Step carries a field its own type cannot have \u2014 a leftover of an edit, meaning nothing" }
|
|
9806
9860
|
],
|
|
9807
9861
|
check(ctx) {
|
|
9862
|
+
const severityOf = new Map(narrativeStepConfigRule.codes.map((c) => [c.code, c.defaultSeverity]));
|
|
9808
9863
|
for (const impl of ctx.implementations) {
|
|
9809
9864
|
const isDraftCtx = ctx.isImplementationDraft(impl);
|
|
9810
9865
|
for (const implMethod of impl.methods) {
|
|
9811
9866
|
if (!implMethod.narrative.length) continue;
|
|
9812
9867
|
const where = `Method "${implMethod.name}" in implementation "${impl.id}": `;
|
|
9813
9868
|
for (const problem of stepConfigVerdict(implMethod).problems) {
|
|
9814
|
-
ctx.addIssue("error", problem.code, where + problem.detail, impl.id, isDraftCtx);
|
|
9869
|
+
ctx.addIssue(severityOf.get(problem.code) ?? "error", problem.code, where + problem.detail, impl.id, isDraftCtx);
|
|
9815
9870
|
}
|
|
9816
9871
|
}
|
|
9817
9872
|
}
|
|
@@ -12402,15 +12457,16 @@ var init_unused_types = __esm({
|
|
|
12402
12457
|
init_models();
|
|
12403
12458
|
unusedTypesRule = {
|
|
12404
12459
|
name: "unused-types",
|
|
12405
|
-
description: "Flags types no field of any type and no
|
|
12460
|
+
description: "Flags types no field of any type, no interface method signature and no other type's method signature references. References are matched through the type-reference grammar (generic arguments, collections, qualified ids), and the declaring type's own generic parameters are left out \u2014 a type parameter is not a reference to a type. A type named only by its OWN methods stays unused, the way a function that only calls itself is.",
|
|
12406
12461
|
codes: [
|
|
12407
|
-
{ code: "UNUSED_TYPE", defaultSeverity: "warning", summary: "Type never referenced by fields or
|
|
12462
|
+
{ code: "UNUSED_TYPE", defaultSeverity: "warning", summary: "Type never referenced by fields, signatures or type methods" }
|
|
12408
12463
|
],
|
|
12409
12464
|
check(ctx) {
|
|
12410
12465
|
const referencedTypes = /* @__PURE__ */ new Set();
|
|
12411
|
-
const markTypeReferenced = (ref) => {
|
|
12466
|
+
const markTypeReferenced = (ref, exceptId) => {
|
|
12412
12467
|
if (ctx.isBuiltinType(ref)) return;
|
|
12413
12468
|
for (const spec of ctx.types) {
|
|
12469
|
+
if (spec.id === exceptId) continue;
|
|
12414
12470
|
if (typeMatchesRef(spec, ref)) {
|
|
12415
12471
|
referencedTypes.add(spec.id);
|
|
12416
12472
|
}
|
|
@@ -12432,6 +12488,14 @@ var init_unused_types = __esm({
|
|
|
12432
12488
|
}
|
|
12433
12489
|
}
|
|
12434
12490
|
}
|
|
12491
|
+
for (const t of ctx.types) {
|
|
12492
|
+
for (const m of t.methods) {
|
|
12493
|
+
const refs = methodTypeRefs(m);
|
|
12494
|
+
for (const ref of refs) {
|
|
12495
|
+
markTypeReferenced(ref, t.id);
|
|
12496
|
+
}
|
|
12497
|
+
}
|
|
12498
|
+
}
|
|
12435
12499
|
for (const t of ctx.types) {
|
|
12436
12500
|
if (!ctx.isSpecInScope(t.id)) continue;
|
|
12437
12501
|
if (!referencedTypes.has(t.id)) {
|
|
@@ -12440,7 +12504,7 @@ var init_unused_types = __esm({
|
|
|
12440
12504
|
ctx.addIssue(
|
|
12441
12505
|
"warning",
|
|
12442
12506
|
"UNUSED_TYPE",
|
|
12443
|
-
`Type "${t.id}" is defined but never referenced by any type
|
|
12507
|
+
`Type "${t.id}" is defined but never referenced by any type field, interface method or other type's method.`,
|
|
12444
12508
|
t.id,
|
|
12445
12509
|
isDraftCtx
|
|
12446
12510
|
);
|
|
@@ -21868,6 +21932,44 @@ var init_stereotype_migration = __esm({
|
|
|
21868
21932
|
}
|
|
21869
21933
|
});
|
|
21870
21934
|
|
|
21935
|
+
// src/core/narrative-repair.ts
|
|
21936
|
+
function repairForeignStepFields(apply) {
|
|
21937
|
+
const implementations = loadImplementationSpecs();
|
|
21938
|
+
const repairs = [];
|
|
21939
|
+
const changed = [];
|
|
21940
|
+
for (const impl of implementations) {
|
|
21941
|
+
let touched = false;
|
|
21942
|
+
for (const method2 of impl.methods) {
|
|
21943
|
+
for (const step of method2.narrative) {
|
|
21944
|
+
const fields = narrativeStepForeignFields(step);
|
|
21945
|
+
if (!fields.length) continue;
|
|
21946
|
+
repairs.push({
|
|
21947
|
+
implementation: impl.id,
|
|
21948
|
+
method: method2.name,
|
|
21949
|
+
stepNumber: step.stepNumber,
|
|
21950
|
+
stepType: step.type,
|
|
21951
|
+
fields
|
|
21952
|
+
});
|
|
21953
|
+
for (const field of fields) delete step[field];
|
|
21954
|
+
touched = true;
|
|
21955
|
+
}
|
|
21956
|
+
}
|
|
21957
|
+
if (touched) changed.push(impl);
|
|
21958
|
+
}
|
|
21959
|
+
if (apply && changed.length) {
|
|
21960
|
+
for (const impl of changed) saveImplementationSpec(impl);
|
|
21961
|
+
invalidateSpecCache();
|
|
21962
|
+
}
|
|
21963
|
+
return repairs;
|
|
21964
|
+
}
|
|
21965
|
+
var init_narrative_repair = __esm({
|
|
21966
|
+
"src/core/narrative-repair.ts"() {
|
|
21967
|
+
"use strict";
|
|
21968
|
+
init_specs2();
|
|
21969
|
+
init_models();
|
|
21970
|
+
}
|
|
21971
|
+
});
|
|
21972
|
+
|
|
21871
21973
|
// src/core/approval.ts
|
|
21872
21974
|
function diffSize(d) {
|
|
21873
21975
|
return d.added.length + d.changed.length + d.removed.length;
|
|
@@ -22045,6 +22147,7 @@ var init_core = __esm({
|
|
|
22045
22147
|
init_treetransfer();
|
|
22046
22148
|
init_provision();
|
|
22047
22149
|
init_stereotype_migration();
|
|
22150
|
+
init_narrative_repair();
|
|
22048
22151
|
init_approval();
|
|
22049
22152
|
init_approver();
|
|
22050
22153
|
init_specs2();
|
|
@@ -23106,6 +23209,7 @@ __export(specs_exports, {
|
|
|
23106
23209
|
getSubprojectPrefix: () => getSubprojectPrefix,
|
|
23107
23210
|
getSubsystemPath: () => getSubsystemPath,
|
|
23108
23211
|
getTypePath: () => getTypePath,
|
|
23212
|
+
ineffectiveDeltaPaths: () => ineffectiveDeltaPaths,
|
|
23109
23213
|
inspectChainedRoots: () => inspectChainedRoots,
|
|
23110
23214
|
invalidateSpecCache: () => invalidateSpecCache,
|
|
23111
23215
|
listChainedRoots: () => listChainedRoots,
|
|
@@ -23577,15 +23681,6 @@ function identityKeyOf(field, item) {
|
|
|
23577
23681
|
return str(o.name) ?? str(o.id);
|
|
23578
23682
|
}
|
|
23579
23683
|
}
|
|
23580
|
-
function stepFieldsFor(type) {
|
|
23581
|
-
const own = STEP_TYPE_FIELDS[type] ?? [];
|
|
23582
|
-
const allowed = /* @__PURE__ */ new Set([...STEP_COMMON_FIELDS, ...own]);
|
|
23583
|
-
for (const field of own) {
|
|
23584
|
-
const twin = STEP_LABEL_TWINS[field];
|
|
23585
|
-
if (twin) allowed.add(twin);
|
|
23586
|
-
}
|
|
23587
|
-
return allowed;
|
|
23588
|
-
}
|
|
23589
23684
|
function reportIdentityOf(field, item) {
|
|
23590
23685
|
if (item !== null && typeof item === "object") {
|
|
23591
23686
|
const step = item.stepNumber;
|
|
@@ -23633,6 +23728,117 @@ function specChanges(before, after, prefix = "") {
|
|
|
23633
23728
|
if (JSON.stringify(before) === JSON.stringify(after)) return [];
|
|
23634
23729
|
return [{ path: prefix, change: "set", before: summarizeValue(before), after: summarizeValue(after) }];
|
|
23635
23730
|
}
|
|
23731
|
+
function identityFieldsOf(field) {
|
|
23732
|
+
switch (field) {
|
|
23733
|
+
case "dispatch":
|
|
23734
|
+
return ["capability"];
|
|
23735
|
+
case "lifecycle":
|
|
23736
|
+
return ["phase", "component", "method"];
|
|
23737
|
+
case "emits":
|
|
23738
|
+
case "subscribesTo":
|
|
23739
|
+
return ["topic", "event"];
|
|
23740
|
+
case "trustedLinks":
|
|
23741
|
+
return ["subsystem"];
|
|
23742
|
+
case "allow":
|
|
23743
|
+
case "findings":
|
|
23744
|
+
return ["code"];
|
|
23745
|
+
case "invariants":
|
|
23746
|
+
case "patterns":
|
|
23747
|
+
return ["id"];
|
|
23748
|
+
case "boundaries":
|
|
23749
|
+
return ["name"];
|
|
23750
|
+
case "globalRequirements":
|
|
23751
|
+
return ["description"];
|
|
23752
|
+
case "databases":
|
|
23753
|
+
return ["id", "name"];
|
|
23754
|
+
case "cases":
|
|
23755
|
+
return ["value"];
|
|
23756
|
+
case "catches":
|
|
23757
|
+
return ["error"];
|
|
23758
|
+
case "narrative":
|
|
23759
|
+
return ["stepNumber"];
|
|
23760
|
+
default:
|
|
23761
|
+
return ["name", "id"];
|
|
23762
|
+
}
|
|
23763
|
+
}
|
|
23764
|
+
function carriesEditMarker(item) {
|
|
23765
|
+
if (item === null || typeof item !== "object") return false;
|
|
23766
|
+
const o = item;
|
|
23767
|
+
return o.action !== void 0 || o.remove !== void 0;
|
|
23768
|
+
}
|
|
23769
|
+
function ineffectiveDeltaPaths(delta, merged, stored) {
|
|
23770
|
+
const out = [];
|
|
23771
|
+
const isPlain = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
23772
|
+
const same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
|
23773
|
+
const at = (prefix, key) => prefix ? `${prefix}.${key}` : key;
|
|
23774
|
+
const say = (path70, why) => {
|
|
23775
|
+
out.push(`${path70} \u2014 ${why}`);
|
|
23776
|
+
};
|
|
23777
|
+
const walkValue = (d, m, s, path70, field) => {
|
|
23778
|
+
if (Array.isArray(d)) {
|
|
23779
|
+
if (!Array.isArray(m)) {
|
|
23780
|
+
say(path70, "the level does not have this field, so the write dropped it");
|
|
23781
|
+
return;
|
|
23782
|
+
}
|
|
23783
|
+
if (field === "narrative" && d.some(carriesEditMarker)) return;
|
|
23784
|
+
for (const element of d) {
|
|
23785
|
+
if (carriesEditMarker(element)) continue;
|
|
23786
|
+
const key = reportIdentityOf(field, element);
|
|
23787
|
+
if (key === null) {
|
|
23788
|
+
if (!same(m, d)) say(path70, "the level does not keep this value, so the write dropped it");
|
|
23789
|
+
else if (same(s, d)) say(path70, "the stored spec already held this value");
|
|
23790
|
+
return;
|
|
23791
|
+
}
|
|
23792
|
+
const mMatch = m.find((i) => reportIdentityOf(field, i) === key);
|
|
23793
|
+
const sMatch = Array.isArray(s) ? s.find((i) => reportIdentityOf(field, i) === key) : void 0;
|
|
23794
|
+
if (mMatch === void 0) {
|
|
23795
|
+
say(at(path70, key), "the write did not store this element");
|
|
23796
|
+
continue;
|
|
23797
|
+
}
|
|
23798
|
+
walk2(element, mMatch, sMatch, at(path70, key), field);
|
|
23799
|
+
}
|
|
23800
|
+
return;
|
|
23801
|
+
}
|
|
23802
|
+
if (isPlain(d)) {
|
|
23803
|
+
if (!isPlain(m)) {
|
|
23804
|
+
say(path70, "the level does not have this field, so the write dropped it");
|
|
23805
|
+
return;
|
|
23806
|
+
}
|
|
23807
|
+
walk2(d, m, s, path70, field);
|
|
23808
|
+
return;
|
|
23809
|
+
}
|
|
23810
|
+
if (!same(m, d)) say(path70, "the write did not store it \u2014 the level's schema does not keep this value");
|
|
23811
|
+
else if (same(s, d)) say(path70, "the stored spec already held this value");
|
|
23812
|
+
};
|
|
23813
|
+
function walk2(d, m, s, path70, container) {
|
|
23814
|
+
for (const [key, value] of Object.entries(d)) {
|
|
23815
|
+
if (DELTA_VERBS.has(key) || JUMP_LABEL_TWINS.has(key)) continue;
|
|
23816
|
+
if (key === "label" && LABEL_TWIN_CONTAINERS.has(container)) continue;
|
|
23817
|
+
if (identityFieldsOf(container).includes(key)) continue;
|
|
23818
|
+
if (key === "unset") {
|
|
23819
|
+
for (const name of Array.isArray(value) ? value : []) {
|
|
23820
|
+
if (typeof name !== "string") continue;
|
|
23821
|
+
const had = isPlain(s) && s[name] !== void 0;
|
|
23822
|
+
const still = isPlain(m) && m[name] !== void 0;
|
|
23823
|
+
if (!had) say(at(path70, name), "unset named a field the stored spec did not have");
|
|
23824
|
+
else if (still) say(at(path70, name), "unset did not remove it");
|
|
23825
|
+
}
|
|
23826
|
+
continue;
|
|
23827
|
+
}
|
|
23828
|
+
if (value === void 0 || value === null) continue;
|
|
23829
|
+
const here = at(path70, key);
|
|
23830
|
+
const mValue = isPlain(m) ? m[key] : void 0;
|
|
23831
|
+
const sValue = isPlain(s) ? s[key] : void 0;
|
|
23832
|
+
if (mValue === void 0) {
|
|
23833
|
+
say(here, "the level does not have this field, so the write dropped it");
|
|
23834
|
+
continue;
|
|
23835
|
+
}
|
|
23836
|
+
walkValue(value, mValue, sValue, here, key);
|
|
23837
|
+
}
|
|
23838
|
+
}
|
|
23839
|
+
walk2(delta, merged, stored, "", "");
|
|
23840
|
+
return out;
|
|
23841
|
+
}
|
|
23636
23842
|
function workspaceFor(rootDir) {
|
|
23637
23843
|
const key = path23.resolve(rootDir);
|
|
23638
23844
|
let ws = workspaces.get(key);
|
|
@@ -23858,10 +24064,10 @@ function restoreSpecFiles(snapshot) {
|
|
|
23858
24064
|
function findLegacySpecFiles() {
|
|
23859
24065
|
return current().findLegacySpecFiles();
|
|
23860
24066
|
}
|
|
23861
|
-
function updateSpec(kind, id, delta, hooks) {
|
|
23862
|
-
return current().updateSpec(kind, id, delta, hooks);
|
|
24067
|
+
function updateSpec(kind, id, delta, hooks, dryRun) {
|
|
24068
|
+
return current().updateSpec(kind, id, delta, hooks, dryRun);
|
|
23863
24069
|
}
|
|
23864
|
-
var fs17, path23, NO_ROOT_SUBSYSTEMS, SIGNATURE_TTL_MS,
|
|
24070
|
+
var fs17, path23, NO_ROOT_SUBSYSTEMS, SIGNATURE_TTL_MS, DELTA_VERBS, JUMP_LABEL_TWINS, LABEL_TWIN_CONTAINERS, SpecWorkspace, workspaces;
|
|
23865
24071
|
var init_specs2 = __esm({
|
|
23866
24072
|
"src/core/specs.ts"() {
|
|
23867
24073
|
"use strict";
|
|
@@ -23879,36 +24085,16 @@ var init_specs2 = __esm({
|
|
|
23879
24085
|
init_diagram();
|
|
23880
24086
|
NO_ROOT_SUBSYSTEMS = /* @__PURE__ */ new Set();
|
|
23881
24087
|
SIGNATURE_TTL_MS = 2e3;
|
|
23882
|
-
|
|
23883
|
-
|
|
23884
|
-
|
|
23885
|
-
|
|
23886
|
-
|
|
23887
|
-
|
|
23888
|
-
|
|
23889
|
-
|
|
23890
|
-
|
|
23891
|
-
|
|
23892
|
-
jump: ["toStep"],
|
|
23893
|
-
return: ["outcome"],
|
|
23894
|
-
throw: ["error"]
|
|
23895
|
-
};
|
|
23896
|
-
STEP_COMMON_FIELDS = [
|
|
23897
|
-
"stepNumber",
|
|
23898
|
-
"label",
|
|
23899
|
-
"description",
|
|
23900
|
-
"type",
|
|
23901
|
-
"assertsGuarantees",
|
|
23902
|
-
"assertsInvariants"
|
|
23903
|
-
];
|
|
23904
|
-
STEP_LABEL_TWINS = {
|
|
23905
|
-
onTrueStep: "onTrueLabel",
|
|
23906
|
-
onFalseStep: "onFalseLabel",
|
|
23907
|
-
defaultStep: "defaultLabel",
|
|
23908
|
-
endStep: "endLabel",
|
|
23909
|
-
finallyStep: "finallyLabel",
|
|
23910
|
-
toStep: "toLabel"
|
|
23911
|
-
};
|
|
24088
|
+
DELTA_VERBS = /* @__PURE__ */ new Set(["action", "remove", "captureJumps"]);
|
|
24089
|
+
JUMP_LABEL_TWINS = /* @__PURE__ */ new Set([
|
|
24090
|
+
"onTrueLabel",
|
|
24091
|
+
"onFalseLabel",
|
|
24092
|
+
"defaultLabel",
|
|
24093
|
+
"endLabel",
|
|
24094
|
+
"finallyLabel",
|
|
24095
|
+
"toLabel"
|
|
24096
|
+
]);
|
|
24097
|
+
LABEL_TWIN_CONTAINERS = /* @__PURE__ */ new Set(["cases", "catches", "branches"]);
|
|
23912
24098
|
SpecWorkspace = class {
|
|
23913
24099
|
constructor(rootDir) {
|
|
23914
24100
|
this.cachedIndex = null;
|
|
@@ -24624,6 +24810,9 @@ var init_specs2 = __esm({
|
|
|
24624
24810
|
const existing = this.loadSubsystemSpec(spec.id);
|
|
24625
24811
|
if (existing) {
|
|
24626
24812
|
specToWrite.createdAt = existing.createdAt;
|
|
24813
|
+
if (!opts?.allowStatusDemotion && existing.status && (!spec.status || spec.status === "draft")) {
|
|
24814
|
+
specToWrite.status = existing.status;
|
|
24815
|
+
}
|
|
24627
24816
|
}
|
|
24628
24817
|
specToWrite.updatedAt = opts?.preserveUpdatedAt && existing?.updatedAt ? existing.updatedAt : (/* @__PURE__ */ new Date()).toISOString();
|
|
24629
24818
|
writeYamlFile(p, parseOrThrow(SubsystemSpecSchema, specToWrite, "subsystem", spec.id));
|
|
@@ -25172,15 +25361,22 @@ var init_specs2 = __esm({
|
|
|
25172
25361
|
* carry, and a label in the delta retargets the stored numeric jump it
|
|
25173
25362
|
* twins. An unresolved reference aborts the whole update.
|
|
25174
25363
|
* 7. COMPARE with what is stored, both read through the level's canonical
|
|
25175
|
-
* schema. Equal means nothing is written and `written` is false.
|
|
25176
|
-
*
|
|
25364
|
+
* schema. Equal means nothing is written and `written` is false. The same
|
|
25365
|
+
* comparison names every delta path the write did NOT act on — one the
|
|
25366
|
+
* schema drops at a depth the permissive delta cannot refuse, one the
|
|
25367
|
+
* stored spec already held, an `unset` that removed nothing.
|
|
25368
|
+
* 8. GATE the merged spec through the caller's hook. A DRY RUN stops here
|
|
25369
|
+
* and answers with the report it would have produced — after the gate, so
|
|
25370
|
+
* it accounts for a write that would really be accepted, and before the
|
|
25371
|
+
* stamp, so not one byte of the stored file moves.
|
|
25372
|
+
* 9. STAMP updatedAt and SAVE.
|
|
25177
25373
|
*
|
|
25178
25374
|
* Returns the change report, whose `notices` carry the non-fatal effects (an
|
|
25179
25375
|
* insert whose position was a jump target, so the relocated jumps now bypass
|
|
25180
25376
|
* the inserted step; the fields a retype dropped) — empty when the merge had
|
|
25181
25377
|
* nothing to say.
|
|
25182
25378
|
*/
|
|
25183
|
-
updateSpec(kind, id, delta, hooks) {
|
|
25379
|
+
updateSpec(kind, id, delta, hooks, dryRun = false) {
|
|
25184
25380
|
const notices = [];
|
|
25185
25381
|
const result = (() => {
|
|
25186
25382
|
switch (kind) {
|
|
@@ -25659,7 +25855,8 @@ var init_specs2 = __esm({
|
|
|
25659
25855
|
const unsetFields = Array.isArray(delta.unset) ? delta.unset.filter((f) => typeof f === "string") : [];
|
|
25660
25856
|
const { unset: _unset, ...mergeableDelta } = delta;
|
|
25661
25857
|
const storedBefore = JSON.parse(JSON.stringify(result));
|
|
25662
|
-
const
|
|
25858
|
+
const qualifiedDelta = qualifyDeltaRefs(mergeableDelta);
|
|
25859
|
+
const mergedResult = mergeDelta(result, qualifiedDelta);
|
|
25663
25860
|
for (const field of unsetFields) delete mergedResult[field];
|
|
25664
25861
|
if (kind === "implementation" && Array.isArray(mergedResult.methods)) {
|
|
25665
25862
|
const labelErrors = [];
|
|
@@ -25677,20 +25874,41 @@ var init_specs2 = __esm({
|
|
|
25677
25874
|
delete value.updatedAt;
|
|
25678
25875
|
return value;
|
|
25679
25876
|
};
|
|
25680
|
-
const
|
|
25877
|
+
const canonicalStored = canonical(storedBefore);
|
|
25878
|
+
const canonicalMerged = canonical(mergedResult);
|
|
25879
|
+
const changes = specChanges(canonicalStored, canonicalMerged);
|
|
25880
|
+
const ineffective = ineffectiveDeltaPaths(
|
|
25881
|
+
{ ...qualifiedDelta, ...unsetFields.length ? { unset: unsetFields } : {} },
|
|
25882
|
+
canonicalMerged,
|
|
25883
|
+
canonicalStored
|
|
25884
|
+
);
|
|
25681
25885
|
if (changes.length === 0) {
|
|
25682
25886
|
return {
|
|
25683
25887
|
kind,
|
|
25684
25888
|
id,
|
|
25685
25889
|
written: false,
|
|
25890
|
+
dryRun,
|
|
25686
25891
|
changes,
|
|
25892
|
+
ineffective,
|
|
25687
25893
|
notices,
|
|
25688
|
-
summary: `No change to ${kind} "${id}" \u2014 the delta matches what is stored, so nothing was written.`
|
|
25894
|
+
summary: `No change to ${kind} "${id}" \u2014 the delta matches what is stored, so nothing ${dryRun ? "would be" : "was"} written.`
|
|
25689
25895
|
};
|
|
25690
25896
|
}
|
|
25691
|
-
mergedResult.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
25692
25897
|
const gateNotices = hooks?.gate?.(kind, mergedResult);
|
|
25693
25898
|
if (gateNotices?.length) notices.push(...gateNotices);
|
|
25899
|
+
if (dryRun) {
|
|
25900
|
+
return {
|
|
25901
|
+
kind,
|
|
25902
|
+
id,
|
|
25903
|
+
written: false,
|
|
25904
|
+
dryRun: true,
|
|
25905
|
+
changes,
|
|
25906
|
+
ineffective,
|
|
25907
|
+
notices,
|
|
25908
|
+
summary: `Dry run on ${kind} "${id}": ${changes.length} change${changes.length === 1 ? "" : "s"} would be made. Nothing was written.`
|
|
25909
|
+
};
|
|
25910
|
+
}
|
|
25911
|
+
mergedResult.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
25694
25912
|
const opts = {
|
|
25695
25913
|
allowStatusDemotion: Object.prototype.hasOwnProperty.call(delta, "status")
|
|
25696
25914
|
};
|
|
@@ -25699,7 +25917,7 @@ var init_specs2 = __esm({
|
|
|
25699
25917
|
this.saveSystemSpec(mergedResult);
|
|
25700
25918
|
break;
|
|
25701
25919
|
case "subsystem":
|
|
25702
|
-
this.saveSubsystemSpec(mergedResult);
|
|
25920
|
+
this.saveSubsystemSpec(mergedResult, opts);
|
|
25703
25921
|
break;
|
|
25704
25922
|
case "component":
|
|
25705
25923
|
notices.push(...this.saveComponentSpec(mergedResult, opts));
|
|
@@ -25718,7 +25936,9 @@ var init_specs2 = __esm({
|
|
|
25718
25936
|
kind,
|
|
25719
25937
|
id,
|
|
25720
25938
|
written: true,
|
|
25939
|
+
dryRun: false,
|
|
25721
25940
|
changes,
|
|
25941
|
+
ineffective,
|
|
25722
25942
|
notices,
|
|
25723
25943
|
summary: `Updated ${kind} "${id}": ${changes.length} change${changes.length === 1 ? "" : "s"}.`
|
|
25724
25944
|
};
|
|
@@ -26588,8 +26808,8 @@ function addComponent(candidate) {
|
|
|
26588
26808
|
if (verdict.errors.length) throw new Error(formatCandidateRefusal(verdict));
|
|
26589
26809
|
return [...noticesFrom(verdict), ...saveComponentSpec(candidate)];
|
|
26590
26810
|
}
|
|
26591
|
-
function updateSpecGated(kind, id, delta) {
|
|
26592
|
-
return updateSpec(kind, id, delta, componentCandidateGate());
|
|
26811
|
+
function updateSpecGated(kind, id, delta, dryRun) {
|
|
26812
|
+
return updateSpec(kind, id, delta, componentCandidateGate(), dryRun);
|
|
26593
26813
|
}
|
|
26594
26814
|
var init_authoring = __esm({
|
|
26595
26815
|
"src/core/authoring.ts"() {
|
|
@@ -26688,11 +26908,14 @@ function errText(message) {
|
|
|
26688
26908
|
return { content: [{ type: "text", text: `Error: ${message}` }], isError: true };
|
|
26689
26909
|
}
|
|
26690
26910
|
function renderChangeReport(report2) {
|
|
26691
|
-
const lines = [report2.summary];
|
|
26911
|
+
const lines = [report2.dryRun ? `DRY RUN \u2014 nothing was written. ${report2.summary}` : report2.summary];
|
|
26692
26912
|
for (const change of report2.changes) {
|
|
26693
26913
|
const to = change.after === void 0 ? "" : `: ${JSON.stringify(change.after)}`;
|
|
26694
26914
|
const from = change.before === void 0 ? "" : ` (was ${JSON.stringify(change.before)})`;
|
|
26695
|
-
lines.push(`- ${change.path} ${change.change}${to}${from}`);
|
|
26915
|
+
lines.push(`- ${change.path} ${report2.dryRun ? `would be ${change.change}` : change.change}${to}${from}`);
|
|
26916
|
+
}
|
|
26917
|
+
if (report2.ineffective.length) {
|
|
26918
|
+
lines.push("", "NO EFFECT:", ...report2.ineffective.map((i) => `- ${i}`));
|
|
26696
26919
|
}
|
|
26697
26920
|
if (report2.notices.length) lines.push("", "NOTICE:", ...report2.notices.map((n) => `- ${n}`));
|
|
26698
26921
|
return lines.join("\n");
|
|
@@ -26734,6 +26957,18 @@ function reg(server, name, config, cb) {
|
|
|
26734
26957
|
const strictConfig = config.inputSchema ? { ...config, inputSchema: import_zod11.z.object(config.inputSchema).strict() } : config;
|
|
26735
26958
|
server.registerTool(name, strictConfig, guarded);
|
|
26736
26959
|
}
|
|
26960
|
+
function statusForCreate(label, stated, existing) {
|
|
26961
|
+
if (stated === void 0) return { status: "draft" };
|
|
26962
|
+
const held = existing?.status ?? void 0;
|
|
26963
|
+
if (held === void 0) return { status: stated };
|
|
26964
|
+
const rank = (s) => STATUS_ORDER.indexOf(s);
|
|
26965
|
+
if (rank(stated) < rank(held)) {
|
|
26966
|
+
return {
|
|
26967
|
+
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.`
|
|
26968
|
+
};
|
|
26969
|
+
}
|
|
26970
|
+
return { status: stated };
|
|
26971
|
+
}
|
|
26737
26972
|
function isEmptyValue(value) {
|
|
26738
26973
|
if (value === void 0 || value === null) return true;
|
|
26739
26974
|
if (Array.isArray(value)) return value.length === 0;
|
|
@@ -27031,8 +27266,8 @@ function createMcpServer(options = {}) {
|
|
|
27031
27266
|
const systemInput = {
|
|
27032
27267
|
name: import_zod11.z.string().describe("Overarching name of the project/system"),
|
|
27033
27268
|
vision: import_zod11.z.string().describe("Vision, mission, and core goals of the system"),
|
|
27034
|
-
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() })])).optional().describe("System boundary rules or scope statements (strings or name/description objects)"),
|
|
27035
|
-
globalRequirements: import_zod11.z.array(import_zod11.z.union([import_zod11.z.string(), import_zod11.z.object({ description: import_zod11.z.string() })])).optional().describe("Global functional and non-functional requirements (strings or description objects)"),
|
|
27269
|
+
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)"),
|
|
27270
|
+
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)"),
|
|
27036
27271
|
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.')
|
|
27037
27272
|
};
|
|
27038
27273
|
const systemInputFields = Object.keys(systemInput);
|
|
@@ -27082,7 +27317,7 @@ NOTICE:
|
|
|
27082
27317
|
details: import_zod11.z.string(),
|
|
27083
27318
|
component: import_zod11.z.string().optional().describe("The L2 component id that realizes this interface (this subsystem's published surface)"),
|
|
27084
27319
|
interface: import_zod11.z.string().optional().describe("Optional L3 interface id on that component backing this entry")
|
|
27085
|
-
})).optional().describe("Public entrypoints exposed by this subsystem, each bound to a realizing component"),
|
|
27320
|
+
}).strict()).optional().describe("Public entrypoints exposed by this subsystem, each bound to a realizing component"),
|
|
27086
27321
|
projectPath: import_zod11.z.string().optional().describe("Relative path to external project root for subsystem chaining"),
|
|
27087
27322
|
targetLanguage: import_zod11.z.string().optional().describe("Override of the system-level targetLanguage for this subsystem"),
|
|
27088
27323
|
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)"),
|
|
@@ -27090,29 +27325,32 @@ NOTICE:
|
|
|
27090
27325
|
trustedLinks: import_zod11.z.array(import_zod11.z.object({
|
|
27091
27326
|
subsystem: import_zod11.z.string().describe("Peer subsystem id"),
|
|
27092
27327
|
reason: import_zod11.z.string().describe('Why the coupling is sanctioned (e.g. "dispatch latency fast lane")')
|
|
27093
|
-
})).optional().describe("Sanctioned tight couplings with peers \u2014 required to acknowledge a mutual subsystem dependency; the Adapter \u2192 published Portal shape still applies."),
|
|
27328
|
+
}).strict()).optional().describe("Sanctioned tight couplings with peers \u2014 required to acknowledge a mutual subsystem dependency; the Adapter \u2192 published Portal shape still applies."),
|
|
27094
27329
|
lifecycle: import_zod11.z.array(import_zod11.z.object({
|
|
27095
27330
|
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)"),
|
|
27096
27331
|
component: import_zod11.z.string().describe("Component id whose method the runtime invokes at this phase"),
|
|
27097
27332
|
method: import_zod11.z.string().describe("Method name on that component's interface"),
|
|
27098
27333
|
description: import_zod11.z.string().optional()
|
|
27099
|
-
})).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).")
|
|
27334
|
+
}).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)."),
|
|
27335
|
+
status: statusInput
|
|
27100
27336
|
};
|
|
27101
27337
|
const subsystemInputFields = [...Object.keys(subsystemInput), "parentSystem"];
|
|
27102
27338
|
reg(
|
|
27103
27339
|
server,
|
|
27104
27340
|
"sdd_add_subsystem",
|
|
27105
27341
|
{
|
|
27106
|
-
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.",
|
|
27342
|
+
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.",
|
|
27107
27343
|
inputSchema: subsystemInput
|
|
27108
27344
|
},
|
|
27109
|
-
({ id, name, description, publicInterfaces, projectPath, targetLanguage, profile, designDepth, trustedLinks, lifecycle }) => {
|
|
27345
|
+
({ id, name, description, publicInterfaces, projectPath, targetLanguage, profile, designDepth, trustedLinks, lifecycle, status: status2 }) => {
|
|
27110
27346
|
try {
|
|
27111
27347
|
const { loadSystemSpec: loadSystemSpec3, loadSubsystemSpec: loadSubsystemSpec2, saveSubsystemSpec: saveSubsystemSpec2 } = requireSpecs();
|
|
27112
27348
|
const system = loadSystemSpec3();
|
|
27113
27349
|
if (!system) return errText("System spec must be initialized (sdd_initialize_system) first.");
|
|
27114
27350
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
27115
27351
|
const existing = loadSubsystemSpec2(id);
|
|
27352
|
+
const resolved = statusForCreate(`subsystem "${id}"`, status2, existing);
|
|
27353
|
+
if ("refusal" in resolved) return errText(resolved.refusal);
|
|
27116
27354
|
const spec = {
|
|
27117
27355
|
id,
|
|
27118
27356
|
name,
|
|
@@ -27125,7 +27363,7 @@ NOTICE:
|
|
|
27125
27363
|
...designDepth ? { designDepth } : {},
|
|
27126
27364
|
...lifecycle ? { lifecycle } : {},
|
|
27127
27365
|
trustedLinks: trustedLinks ?? [],
|
|
27128
|
-
status:
|
|
27366
|
+
status: resolved.status,
|
|
27129
27367
|
createdAt: now,
|
|
27130
27368
|
updatedAt: now
|
|
27131
27369
|
};
|
|
@@ -27161,7 +27399,7 @@ NOTICE:
|
|
|
27161
27399
|
details: import_zod11.z.string(),
|
|
27162
27400
|
component: import_zod11.z.string().optional().describe("The L2 component id that realizes this interface"),
|
|
27163
27401
|
interface: import_zod11.z.string().optional().describe("Optional L3 interface id on that component")
|
|
27164
|
-
})).describe("The full replacement list of public interfaces for this subsystem")
|
|
27402
|
+
}).strict()).describe("The full replacement list of public interfaces for this subsystem")
|
|
27165
27403
|
}
|
|
27166
27404
|
},
|
|
27167
27405
|
({ subsystem, publicInterfaces }) => {
|
|
@@ -27318,35 +27556,39 @@ NOTICE:
|
|
|
27318
27556
|
component: import_zod11.z.string().describe("Component id serving this capability (must also appear under dependsOn/owns)"),
|
|
27319
27557
|
method: import_zod11.z.string().describe("Method name on the serving component's interface"),
|
|
27320
27558
|
description: import_zod11.z.string().optional()
|
|
27321
|
-
})).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)."),
|
|
27559
|
+
}).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)."),
|
|
27322
27560
|
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."),
|
|
27323
27561
|
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."),
|
|
27324
27562
|
emits: import_zod11.z.array(import_zod11.z.object({
|
|
27325
27563
|
topic: import_zod11.z.string().describe("Topic/channel name exactly as used on the bus"),
|
|
27326
27564
|
event: import_zod11.z.string().optional().describe("Optional event name within the topic (informational; pairing is by topic)"),
|
|
27327
27565
|
description: import_zod11.z.string().optional()
|
|
27328
|
-
})).optional().describe("Topics this component publishes \u2014 every emitted topic needs a subscriber somewhere in the tree (UNCONSUMED_TOPIC)."),
|
|
27566
|
+
}).strict()).optional().describe("Topics this component publishes \u2014 every emitted topic needs a subscriber somewhere in the tree (UNCONSUMED_TOPIC)."),
|
|
27329
27567
|
subscribesTo: import_zod11.z.array(import_zod11.z.object({
|
|
27330
27568
|
topic: import_zod11.z.string().describe("Topic/channel name exactly as used on the bus"),
|
|
27331
27569
|
event: import_zod11.z.string().optional(),
|
|
27332
27570
|
description: import_zod11.z.string().optional()
|
|
27333
|
-
})).optional().describe("Topics this component consumes (typical on Observers) \u2014 every subscription needs an emitter somewhere in the tree (UNSOURCED_SUBSCRIPTION)."),
|
|
27334
|
-
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')
|
|
27571
|
+
}).strict()).optional().describe("Topics this component consumes (typical on Observers) \u2014 every subscription needs an emitter somewhere in the tree (UNSOURCED_SUBSCRIPTION)."),
|
|
27572
|
+
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'),
|
|
27573
|
+
status: statusInput
|
|
27335
27574
|
};
|
|
27336
27575
|
const componentInputFields = Object.keys(componentInput);
|
|
27337
27576
|
reg(
|
|
27338
27577
|
server,
|
|
27339
27578
|
"sdd_add_component",
|
|
27340
27579
|
{
|
|
27341
|
-
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.`,
|
|
27580
|
+
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.`,
|
|
27342
27581
|
inputSchema: componentInput
|
|
27343
27582
|
},
|
|
27344
|
-
({ id, name, description, subsystem, componentType, owns, dependsOn, portalType, basePath, dispatch, durability, dependencyClass, emits, subscribesTo, ext }) => {
|
|
27583
|
+
({ id, name, description, subsystem, componentType, owns, dependsOn, portalType, basePath, dispatch, durability, dependencyClass, emits, subscribesTo, ext, status: status2 }) => {
|
|
27345
27584
|
try {
|
|
27346
27585
|
const { loadSubsystemSpec: loadSubsystemSpec2, loadComponentSpec: loadComponentSpec2 } = requireSpecs();
|
|
27347
27586
|
const sub = loadSubsystemSpec2(subsystem);
|
|
27348
27587
|
if (!sub) return errText(`Parent subsystem "${subsystem}" does not exist.`);
|
|
27349
27588
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
27589
|
+
const existing = loadComponentSpec2(id);
|
|
27590
|
+
const resolved = statusForCreate(`component "${id}"`, status2, existing);
|
|
27591
|
+
if ("refusal" in resolved) return errText(resolved.refusal);
|
|
27350
27592
|
const candidate = {
|
|
27351
27593
|
id,
|
|
27352
27594
|
name,
|
|
@@ -27363,11 +27605,10 @@ NOTICE:
|
|
|
27363
27605
|
...emits ? { emits } : {},
|
|
27364
27606
|
...subscribesTo ? { subscribesTo } : {},
|
|
27365
27607
|
...ext ? { ext } : {},
|
|
27366
|
-
status:
|
|
27608
|
+
status: resolved.status,
|
|
27367
27609
|
createdAt: now,
|
|
27368
27610
|
updatedAt: now
|
|
27369
27611
|
};
|
|
27370
|
-
const existing = loadComponentSpec2(id);
|
|
27371
27612
|
const carried = carryUnexpressed(existing, candidate, componentInputFields);
|
|
27372
27613
|
const cleared = clearedByOmission(existing, candidate, componentInputFields);
|
|
27373
27614
|
if (existing) candidate.createdAt = existing.createdAt;
|
|
@@ -27400,18 +27641,18 @@ NOTICE:
|
|
|
27400
27641
|
type: import_zod11.z.string().describe('A primitive/builtin or a defined type id (e.g. "billing.Invoice")'),
|
|
27401
27642
|
description: import_zod11.z.string().optional(),
|
|
27402
27643
|
optional: import_zod11.z.boolean().optional()
|
|
27403
|
-
})).optional().describe("Structured parameters \u2014 authoritative for type checking (the prose signature becomes display-only). Strongly preferred."),
|
|
27644
|
+
}).strict()).optional().describe("Structured parameters \u2014 authoritative for type checking (the prose signature becomes display-only). Strongly preferred."),
|
|
27404
27645
|
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)"),
|
|
27405
27646
|
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"),
|
|
27406
27647
|
invokedBy: import_zod11.z.object({
|
|
27407
27648
|
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)"),
|
|
27408
27649
|
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")
|
|
27409
|
-
}).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."),
|
|
27650
|
+
}).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."),
|
|
27410
27651
|
findings: import_zod11.z.array(import_zod11.z.object({
|
|
27411
27652
|
code: import_zod11.z.string().describe("UPPER_SNAKE finding code, unique within the method; a pack's codes carry the pack prefix (<PACK>_<CODE>)"),
|
|
27412
27653
|
severity: import_zod11.z.enum(["error", "warning"]).describe("Default severity, before project severity overrides and draft-context downgrades"),
|
|
27413
27654
|
summary: import_zod11.z.string().describe("One line saying what the finding means")
|
|
27414
|
-
})).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."),
|
|
27655
|
+
}).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."),
|
|
27415
27656
|
ext: import_zod11.z.record(import_zod11.z.unknown()).optional().describe("Opaque pack/tool extension data for this method (namespaced keys) \u2014 preserved verbatim")
|
|
27416
27657
|
};
|
|
27417
27658
|
const interfaceMethodInputFields = Object.keys(interfaceMethodShape);
|
|
@@ -27420,23 +27661,26 @@ NOTICE:
|
|
|
27420
27661
|
name: import_zod11.z.string().describe("Human-readable contract name"),
|
|
27421
27662
|
description: import_zod11.z.string().describe("Contract description and obligations"),
|
|
27422
27663
|
component: import_zod11.z.string().describe("The L2 component ID this interface belongs to"),
|
|
27423
|
-
methods: import_zod11.z.array(import_zod11.z.object(interfaceMethodShape)).optional().describe("List of method signature contracts")
|
|
27664
|
+
methods: import_zod11.z.array(import_zod11.z.object(interfaceMethodShape).strict()).optional().describe("List of method signature contracts"),
|
|
27665
|
+
status: statusInput
|
|
27424
27666
|
};
|
|
27425
27667
|
const interfaceInputFields = Object.keys(interfaceInput);
|
|
27426
27668
|
reg(
|
|
27427
27669
|
server,
|
|
27428
27670
|
"sdd_define_interface",
|
|
27429
27671
|
{
|
|
27430
|
-
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.",
|
|
27672
|
+
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.",
|
|
27431
27673
|
inputSchema: interfaceInput
|
|
27432
27674
|
},
|
|
27433
|
-
({ id, name, description, component, methods }) => {
|
|
27675
|
+
({ id, name, description, component, methods, status: status2 }) => {
|
|
27434
27676
|
try {
|
|
27435
27677
|
const { loadComponentSpec: loadComponentSpec2, loadInterfaceSpec: loadInterfaceSpec2, saveInterfaceSpec: saveInterfaceSpec2 } = requireSpecs();
|
|
27436
27678
|
const comp = loadComponentSpec2(component);
|
|
27437
27679
|
if (!comp) return errText(`Component "${component}" does not exist.`);
|
|
27438
27680
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
27439
27681
|
const existing = loadInterfaceSpec2(id);
|
|
27682
|
+
const resolved = statusForCreate(`interface "${id}"`, status2, existing);
|
|
27683
|
+
if ("refusal" in resolved) return errText(resolved.refusal);
|
|
27440
27684
|
const carried = [];
|
|
27441
27685
|
const specMethods = (methods ?? []).map((m) => {
|
|
27442
27686
|
const prev = existing?.methods.find((p) => p.name === m.name);
|
|
@@ -27452,7 +27696,7 @@ NOTICE:
|
|
|
27452
27696
|
description,
|
|
27453
27697
|
component,
|
|
27454
27698
|
methods: specMethods,
|
|
27455
|
-
status:
|
|
27699
|
+
status: resolved.status,
|
|
27456
27700
|
createdAt: now,
|
|
27457
27701
|
updatedAt: now
|
|
27458
27702
|
};
|
|
@@ -27503,7 +27747,7 @@ NOTICE:
|
|
|
27503
27747
|
channel: import_zod11.z.string().optional().describe("IPC: channel name"),
|
|
27504
27748
|
command: import_zod11.z.string().optional().describe("CLI: command/subcommand"),
|
|
27505
27749
|
address: import_zod11.z.string().optional().describe("Custom: free-form address")
|
|
27506
|
-
})).describe("One binding per method")
|
|
27750
|
+
}).strict()).describe("One binding per method")
|
|
27507
27751
|
}
|
|
27508
27752
|
},
|
|
27509
27753
|
({ interface: interfaceId, endpoints }) => {
|
|
@@ -27560,7 +27804,7 @@ NOTICE:
|
|
|
27560
27804
|
type: import_zod11.z.enum(["local", "call", "dispatch", "register", "branch", "switch", "loop", "try", "parallel", "jump", "return", "throw"]),
|
|
27561
27805
|
targetComponent: import_zod11.z.string().optional().describe("call/register/dispatch: L2 component id (for dispatch, the Portal routed through)"),
|
|
27562
27806
|
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"),
|
|
27563
|
-
auth: import_zod11.z.object({ from: import_zod11.z.string(), note: import_zod11.z.string().optional() }).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)."),
|
|
27807
|
+
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)."),
|
|
27564
27808
|
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)"),
|
|
27565
27809
|
capability: import_zod11.z.string().optional().describe("dispatch: the capability routed through the target Portal's dispatch table (validated against it \u2014 UNSERVED_CAPABILITY)"),
|
|
27566
27810
|
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)"),
|
|
@@ -27571,22 +27815,22 @@ NOTICE:
|
|
|
27571
27815
|
onFalseStep: stepNo().optional().describe("branch: required (or onFalseLabel)"),
|
|
27572
27816
|
onFalseLabel: labelRef().optional().describe("branch: symbolic alternative to onFalseStep"),
|
|
27573
27817
|
on: import_zod11.z.string().optional().describe("switch: the dispatched value"),
|
|
27574
|
-
cases: import_zod11.z.array(import_zod11.z.object({ value: import_zod11.z.string(), step: stepNo().optional(), label: labelRef().optional() })).optional().describe("switch: required; each case targets its region by step number or label"),
|
|
27818
|
+
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"),
|
|
27575
27819
|
defaultStep: stepNo().optional().describe("switch: default = next step"),
|
|
27576
27820
|
defaultLabel: labelRef().optional().describe("switch: symbolic alternative to defaultStep"),
|
|
27577
27821
|
loopKind: import_zod11.z.enum(["forEach", "for", "while", "doWhile"]).optional().describe('loop: default forEach when "over" is set, else while'),
|
|
27578
27822
|
over: import_zod11.z.string().optional().describe("loop forEach/for: iteration source"),
|
|
27579
27823
|
endStep: stepNo().optional().describe("loop/try: last step of the body region (required, or endLabel)"),
|
|
27580
27824
|
endLabel: labelRef().optional().describe("loop/try: symbolic alternative to endStep"),
|
|
27581
|
-
catches: import_zod11.z.array(import_zod11.z.object({ error: import_zod11.z.string(), step: stepNo().optional(), label: labelRef().optional() })).optional().describe("try: handler regions, each targeted by step number or label"),
|
|
27825
|
+
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"),
|
|
27582
27826
|
finallyStep: stepNo().optional().describe("try: first step of the always-runs region"),
|
|
27583
27827
|
finallyLabel: labelRef().optional().describe("try: symbolic alternative to finallyStep"),
|
|
27584
|
-
branches: import_zod11.z.array(import_zod11.z.object({ step: stepNo().optional(), label: labelRef().optional(), name: import_zod11.z.string().optional() })).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"),
|
|
27828
|
+
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"),
|
|
27585
27829
|
toStep: stepNo().optional().describe("jump: required (break/continue/rejoin), or toLabel"),
|
|
27586
27830
|
toLabel: labelRef().optional().describe("jump: symbolic alternative to toStep \u2014 resolves to the labeled step at write time"),
|
|
27587
27831
|
outcome: import_zod11.z.string().optional().describe('return: e.g. "success", "not found"'),
|
|
27588
27832
|
error: import_zod11.z.string().optional().describe("throw: the raised error")
|
|
27589
|
-
});
|
|
27833
|
+
}).strict();
|
|
27590
27834
|
const detailEnum = import_zod11.z.enum(["full", "calls-only", "intent"]);
|
|
27591
27835
|
const conformanceEnum = import_zod11.z.enum(["declared", "anchored", "off"]);
|
|
27592
27836
|
const implMethodShape = {
|
|
@@ -27610,22 +27854,25 @@ NOTICE:
|
|
|
27610
27854
|
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.`),
|
|
27611
27855
|
detail: detailEnum.optional().describe("Spec-level narrative detail default for all methods"),
|
|
27612
27856
|
conformance: conformanceEnum.optional().describe("Spec-level conformance tier default: declared | anchored | off (omitted = stereotype default: Portal \u2192 anchored, else declared)"),
|
|
27613
|
-
methods: import_zod11.z.array(import_zod11.z.object(implMethodShape)).optional().describe("Method implementations containing L5 narratives")
|
|
27857
|
+
methods: import_zod11.z.array(import_zod11.z.object(implMethodShape).strict()).optional().describe("Method implementations containing L5 narratives"),
|
|
27858
|
+
status: statusInput
|
|
27614
27859
|
};
|
|
27615
27860
|
const implInputFields = Object.keys(implInput);
|
|
27616
27861
|
reg(
|
|
27617
27862
|
server,
|
|
27618
27863
|
"sdd_write_narrative",
|
|
27619
27864
|
{
|
|
27620
|
-
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). 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. 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.",
|
|
27865
|
+
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). 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. 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.",
|
|
27621
27866
|
inputSchema: implInput
|
|
27622
27867
|
},
|
|
27623
|
-
({ id, name, description, contract, sourcePath, simPath, technologies, detail, conformance, methods }) => {
|
|
27868
|
+
({ id, name, description, contract, sourcePath, simPath, technologies, detail, conformance, methods, status: status2 }) => {
|
|
27624
27869
|
try {
|
|
27625
27870
|
const { loadInterfaceSpec: loadInterfaceSpec2, loadImplementationSpec: loadImplementationSpec2, saveImplementationSpec: saveImplementationSpec2 } = requireSpecs();
|
|
27626
27871
|
const intf = loadInterfaceSpec2(contract);
|
|
27627
27872
|
if (!intf) return errText(`Interface contract "${contract}" does not exist.`);
|
|
27628
27873
|
const existing = loadImplementationSpec2(id);
|
|
27874
|
+
const resolved = statusForCreate(`implementation "${id}"`, status2, existing);
|
|
27875
|
+
if ("refusal" in resolved) return errText(resolved.refusal);
|
|
27629
27876
|
const carried = [];
|
|
27630
27877
|
const resolvedMethods = (methods ?? []).map((m) => {
|
|
27631
27878
|
const prev = existing?.methods.find((p) => p.name === m.name);
|
|
@@ -27655,7 +27902,7 @@ NOTICE:
|
|
|
27655
27902
|
// Post-resolution every cases/catches entry has its numeric step —
|
|
27656
27903
|
// the *Label twins exist only in the input type, not the spec's.
|
|
27657
27904
|
methods: resolvedMethods,
|
|
27658
|
-
status:
|
|
27905
|
+
status: resolved.status,
|
|
27659
27906
|
createdAt: now,
|
|
27660
27907
|
updatedAt: now
|
|
27661
27908
|
};
|
|
@@ -27696,13 +27943,13 @@ NOTICE:
|
|
|
27696
27943
|
optional: import_zod11.z.boolean().optional(),
|
|
27697
27944
|
key: import_zod11.z.enum(["primary", "unique", "foreign"]).optional().describe("Identity marker (PK/unique/FK) for ERD and database schema derivation"),
|
|
27698
27945
|
references: import_zod11.z.string().optional().describe('For foreign keys, the referenced type/table id and optional field, e.g. "invoice.id"')
|
|
27699
|
-
})).optional().describe('Data fields (type is a primitive or a qualified type id, e.g. "billing.Invoice")'),
|
|
27700
|
-
methods: import_zod11.z.array(import_zod11.z.object({ name: import_zod11.z.string(), signature: import_zod11.z.string(), returns: import_zod11.z.string(), description: import_zod11.z.string().optional() })).optional().describe("Pure intrinsic methods only"),
|
|
27946
|
+
}).strict()).optional().describe('Data fields (type is a primitive or a qualified type id, e.g. "billing.Invoice")'),
|
|
27947
|
+
methods: import_zod11.z.array(import_zod11.z.object({ name: import_zod11.z.string(), signature: import_zod11.z.string(), returns: import_zod11.z.string(), description: import_zod11.z.string().optional() }).strict()).optional().describe("Pure intrinsic methods only"),
|
|
27701
27948
|
componentClass: import_zod11.z.string().optional().describe("Optional component id that implements or owns this logical entity"),
|
|
27702
27949
|
invariants: import_zod11.z.array(import_zod11.z.object({
|
|
27703
27950
|
id: import_zod11.z.string().describe("Stable invariant id, unique within the entity"),
|
|
27704
27951
|
description: import_zod11.z.string().describe("The property that must hold, stated precisely")
|
|
27705
|
-
})).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"),
|
|
27952
|
+
}).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"),
|
|
27706
27953
|
database: import_zod11.z.string().optional().describe("Optional database id for table-schema types"),
|
|
27707
27954
|
table: import_zod11.z.string().optional().describe("Optional database table name for table-schema types"),
|
|
27708
27955
|
linkedEntity: import_zod11.z.string().optional().describe("Optional logical entity id represented by this table-schema type")
|
|
@@ -27806,13 +28053,14 @@ NOTICE:
|
|
|
27806
28053
|
server,
|
|
27807
28054
|
"sdd_get_spec",
|
|
27808
28055
|
{
|
|
27809
|
-
description: `Get/read the parsed JSON contents of a specific spec from the spec tree. Returns structural contents without file system path searching. 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.`,
|
|
28056
|
+
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.`,
|
|
27810
28057
|
inputSchema: {
|
|
27811
28058
|
kind: import_zod11.z.enum(["system", "subsystem", "component", "interface", "implementation", "type"]).describe("The kind of specification"),
|
|
27812
|
-
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")')
|
|
28059
|
+
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")'),
|
|
28060
|
+
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.")
|
|
27813
28061
|
}
|
|
27814
28062
|
},
|
|
27815
|
-
({ kind, id }) => {
|
|
28063
|
+
({ kind, id, methods }) => {
|
|
27816
28064
|
try {
|
|
27817
28065
|
const specs = requireSpecs();
|
|
27818
28066
|
let result = null;
|
|
@@ -27837,6 +28085,31 @@ NOTICE:
|
|
|
27837
28085
|
break;
|
|
27838
28086
|
}
|
|
27839
28087
|
if (!result) return errText(`Spec of kind "${kind}" with ID "${id}" does not exist.`);
|
|
28088
|
+
if (methods !== void 0) {
|
|
28089
|
+
const declared = result.methods;
|
|
28090
|
+
if (!Array.isArray(declared)) {
|
|
28091
|
+
return errText(
|
|
28092
|
+
`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.`
|
|
28093
|
+
);
|
|
28094
|
+
}
|
|
28095
|
+
const names = declared.map((m) => String(m?.name));
|
|
28096
|
+
const unknown = methods.filter((n) => !names.includes(n));
|
|
28097
|
+
if (unknown.length > 0) {
|
|
28098
|
+
return errText(
|
|
28099
|
+
`${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.`
|
|
28100
|
+
);
|
|
28101
|
+
}
|
|
28102
|
+
const kept = declared.filter((m) => methods.includes(String(m?.name)));
|
|
28103
|
+
result = {
|
|
28104
|
+
...result,
|
|
28105
|
+
methods: kept,
|
|
28106
|
+
partialResult: {
|
|
28107
|
+
shown: kept.map((m) => String(m?.name)),
|
|
28108
|
+
omitted: names.length - kept.length,
|
|
28109
|
+
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.`
|
|
28110
|
+
}
|
|
28111
|
+
};
|
|
28112
|
+
}
|
|
27840
28113
|
if (kind === "component") {
|
|
27841
28114
|
const guidance = resolveComponentVariantGuidance(result);
|
|
27842
28115
|
if (guidance) return json({ ...result, variantGuidance: guidance });
|
|
@@ -27889,16 +28162,17 @@ NOTICE:
|
|
|
27889
28162
|
server,
|
|
27890
28163
|
"sdd_update_spec",
|
|
27891
28164
|
{
|
|
27892
|
-
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.",
|
|
28165
|
+
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.",
|
|
27893
28166
|
inputSchema: {
|
|
27894
28167
|
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)"),
|
|
27895
28168
|
id: import_zod11.z.string().describe("The ID of the spec to update (namespaced if needed)"),
|
|
27896
|
-
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", lifecycle by phase+component+method, emits/subscribesTo by topic+event, trustedLinks by "subsystem", invariants and patterns by "id", lint.allow by "code", 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, reason }] }" to silence a WARNING code on this spec only (errors always surface; stale allows are flagged).`)
|
|
28169
|
+
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", lifecycle by phase+component+method, emits/subscribesTo by topic+event, trustedLinks by "subsystem", invariants and patterns by "id", lint.allow by "code", 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, reason }] }" to silence a WARNING code on this spec only (errors always surface; stale allows are flagged). 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.`),
|
|
28170
|
+
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.")
|
|
27897
28171
|
}
|
|
27898
28172
|
},
|
|
27899
|
-
({ kind, id, delta }) => {
|
|
28173
|
+
({ kind, id, delta, dryRun }) => {
|
|
27900
28174
|
try {
|
|
27901
|
-
return text(renderChangeReport(updateSpecGated(kind, id, delta)));
|
|
28175
|
+
return text(renderChangeReport(updateSpecGated(kind, id, delta, dryRun)));
|
|
27902
28176
|
} catch (e) {
|
|
27903
28177
|
return errText(String(e));
|
|
27904
28178
|
}
|
|
@@ -28102,7 +28376,7 @@ async function startMcpServer() {
|
|
|
28102
28376
|
} catch {
|
|
28103
28377
|
}
|
|
28104
28378
|
}
|
|
28105
|
-
var import_mcp, import_stdio, import_zod11, import_types3, fs20, path27, import_url, SERVER_BUILD_STAMP, STALE_SERVER_WARNING, SPEC_WRITE_TOOLS, listChangedEmitters, STORE_MANAGED_FIELDS, ALWAYS_CARRIED_FIELDS, SKILL_RESOURCE_MIME, AGENT_BRIEF_SCHEME;
|
|
28379
|
+
var import_mcp, import_stdio, import_zod11, import_types3, fs20, path27, import_url, SERVER_BUILD_STAMP, STALE_SERVER_WARNING, SPEC_WRITE_TOOLS, listChangedEmitters, STORE_MANAGED_FIELDS, STATUS_ORDER, statusInput, ALWAYS_CARRIED_FIELDS, SKILL_RESOURCE_MIME, AGENT_BRIEF_SCHEME;
|
|
28106
28380
|
var init_server = __esm({
|
|
28107
28381
|
"src/mcp/server.ts"() {
|
|
28108
28382
|
"use strict";
|
|
@@ -28155,6 +28429,10 @@ var init_server = __esm({
|
|
|
28155
28429
|
]);
|
|
28156
28430
|
listChangedEmitters = /* @__PURE__ */ new WeakMap();
|
|
28157
28431
|
STORE_MANAGED_FIELDS = /* @__PURE__ */ new Set(["status", "updatedAt"]);
|
|
28432
|
+
STATUS_ORDER = ["draft", "design", "complete"];
|
|
28433
|
+
statusInput = import_zod11.z.enum(STATUS_ORDER).optional().describe(
|
|
28434
|
+
"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."
|
|
28435
|
+
);
|
|
28158
28436
|
ALWAYS_CARRIED_FIELDS = /* @__PURE__ */ new Set(["ext"]);
|
|
28159
28437
|
SKILL_RESOURCE_MIME = "text/markdown";
|
|
28160
28438
|
AGENT_BRIEF_SCHEME = "wairon-agent://";
|
|
@@ -28759,6 +29037,9 @@ function listDirectChainedSubprojects2(projectRoot2) {
|
|
|
28759
29037
|
function retireSpecialists2(apply) {
|
|
28760
29038
|
return retireSpecialists(apply);
|
|
28761
29039
|
}
|
|
29040
|
+
function repairForeignStepFields2(apply) {
|
|
29041
|
+
return repairForeignStepFields(apply);
|
|
29042
|
+
}
|
|
28762
29043
|
function defaultPackSelections2() {
|
|
28763
29044
|
return defaultPackSelections();
|
|
28764
29045
|
}
|
|
@@ -30900,6 +31181,23 @@ async function runDoctor(options = {}) {
|
|
|
30900
31181
|
} catch (e) {
|
|
30901
31182
|
line(tally, "warn", `Could not plan Specialist retirement: ${e instanceof Error ? e.message : String(e)}`);
|
|
30902
31183
|
}
|
|
31184
|
+
try {
|
|
31185
|
+
const foreign = repairForeignStepFields2(false);
|
|
31186
|
+
if (foreign.length > 0) {
|
|
31187
|
+
console.log(import_chalk12.default.bold("Narrative steps"));
|
|
31188
|
+
for (const repair of foreign) {
|
|
31189
|
+
line(
|
|
31190
|
+
tally,
|
|
31191
|
+
"warn",
|
|
31192
|
+
`${repair.implementation}.${repair.method} step ${repair.stepNumber} (${repair.stepType}) carries ${repair.fields.join(", ")} \u2014 a ${repair.stepType} step cannot.`
|
|
31193
|
+
);
|
|
31194
|
+
}
|
|
31195
|
+
line(tally, "warn", "Run `wairon doctor --fix` to drop them (or give each step the type that carries them).");
|
|
31196
|
+
logger.blank();
|
|
31197
|
+
}
|
|
31198
|
+
} catch (e) {
|
|
31199
|
+
line(tally, "warn", `Could not plan the narrative-step repair: ${e instanceof Error ? e.message : String(e)}`);
|
|
31200
|
+
}
|
|
30903
31201
|
}
|
|
30904
31202
|
console.log(import_chalk12.default.bold(`Generated files ${import_chalk12.default.gray("(stale = older than installed)")}`));
|
|
30905
31203
|
for (const [label, p] of [
|
|
@@ -31092,6 +31390,17 @@ async function applyFixes() {
|
|
|
31092
31390
|
} catch (e) {
|
|
31093
31391
|
console.log(` ${icon("error")} Specialist retirement failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
31094
31392
|
}
|
|
31393
|
+
try {
|
|
31394
|
+
const repairs = repairForeignStepFields2(true);
|
|
31395
|
+
if (repairs.length > 0) {
|
|
31396
|
+
console.log(` ${icon("ok")} Dropped foreign fields from ${repairs.length} narrative step(s):`);
|
|
31397
|
+
for (const repair of repairs) {
|
|
31398
|
+
console.log(` ${repair.implementation}.${repair.method} step ${repair.stepNumber} (${repair.stepType}): ${repair.fields.join(", ")}`);
|
|
31399
|
+
}
|
|
31400
|
+
}
|
|
31401
|
+
} catch (e) {
|
|
31402
|
+
console.log(` ${icon("error")} Narrative-step repair failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
31403
|
+
}
|
|
31095
31404
|
const { runMcpInstall: runMcpInstall2 } = (init_mcp(), __toCommonJS(mcp_exports));
|
|
31096
31405
|
if (targets.includes("claude")) {
|
|
31097
31406
|
try {
|