@wairon/cli 5.1.1-dev.34 → 5.1.1-dev.36
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 +325 -55
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +303 -52
- 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.36";
|
|
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();
|
|
@@ -23562,6 +23665,17 @@ function identityKeyOf(field, item) {
|
|
|
23562
23665
|
return str(o.description);
|
|
23563
23666
|
case "databases":
|
|
23564
23667
|
return str(o.id) ?? str(o.name);
|
|
23668
|
+
// A flow step's own arrays, addressed by the thing the author named them
|
|
23669
|
+
// for. Their `step` is a RELOCATABLE number, never an identity — a switch
|
|
23670
|
+
// case is the value it matches and a catch clause is the error it catches,
|
|
23671
|
+
// and both survive the renumbering that moves their target. (A parallel
|
|
23672
|
+
// arm falls to the default: named, it merges by name; unnamed, it carries
|
|
23673
|
+
// no identity at all and the list replaces wholesale, which is the same
|
|
23674
|
+
// rule everywhere else.)
|
|
23675
|
+
case "cases":
|
|
23676
|
+
return str(o.value);
|
|
23677
|
+
case "catches":
|
|
23678
|
+
return str(o.error);
|
|
23565
23679
|
default:
|
|
23566
23680
|
return str(o.name) ?? str(o.id);
|
|
23567
23681
|
}
|
|
@@ -25096,9 +25210,39 @@ var init_specs2 = __esm({
|
|
|
25096
25210
|
// Granular delta updates (sdd_update_spec)
|
|
25097
25211
|
// -------------------------------------------------------------------------
|
|
25098
25212
|
/**
|
|
25099
|
-
*
|
|
25100
|
-
*
|
|
25101
|
-
*
|
|
25213
|
+
* Apply a delta to one stored spec, in this ORDER — the order is the contract,
|
|
25214
|
+
* because every stage below can see what the stage above left:
|
|
25215
|
+
*
|
|
25216
|
+
* 1. LOAD the stored spec for the addressed kind and id; a spec that does
|
|
25217
|
+
* not exist refuses, and a mis-selected `system` kind refuses by name.
|
|
25218
|
+
* 2. REFUSE unknown delta keys, whole. A key the level's schema does not
|
|
25219
|
+
* know is dropped on write, so the edit would report a success it never
|
|
25220
|
+
* performed.
|
|
25221
|
+
* 3. QUALIFY the delta's id-bearing references against the spec's namespace,
|
|
25222
|
+
* BEFORE the merge — post-merge a bare id is ambiguous.
|
|
25223
|
+
* 4. MERGE. Identity-keyed arrays upsert element by element and honour their
|
|
25224
|
+
* delete markers, at every depth. Narrative step deltas apply in ASCENDING
|
|
25225
|
+
* stepNumber order, each against the numbering the earlier entries of the
|
|
25226
|
+
* same delta left behind — delete step 3 and step 7 becomes step 6, so a
|
|
25227
|
+
* second delete written as 7 addresses what used to be step 8. That is why
|
|
25228
|
+
* a delete may restate the step's `label` or `description` as a guard, and
|
|
25229
|
+
* why labels are the reliable way to name a jump target. Inserts and
|
|
25230
|
+
* deletes relocate every jump field in the same narrative around them; a
|
|
25231
|
+
* step whose `type` changes is REBUILT for its new type, keeping its
|
|
25232
|
+
* description and label.
|
|
25233
|
+
* 5. UNSET the fields the delta names, at the level each `unset` sits in.
|
|
25234
|
+
* 6. RESOLVE symbolic `*Label` references against the MERGED numbering — after
|
|
25235
|
+
* the merge, so a delta may anchor on a label that only pre-existing steps
|
|
25236
|
+
* carry, and a label in the delta retargets the stored numeric jump it
|
|
25237
|
+
* twins. An unresolved reference aborts the whole update.
|
|
25238
|
+
* 7. COMPARE with what is stored, both read through the level's canonical
|
|
25239
|
+
* schema. Equal means nothing is written and `written` is false.
|
|
25240
|
+
* 8. GATE the merged spec through the caller's hook, then SAVE.
|
|
25241
|
+
*
|
|
25242
|
+
* Returns the change report, whose `notices` carry the non-fatal effects (an
|
|
25243
|
+
* insert whose position was a jump target, so the relocated jumps now bypass
|
|
25244
|
+
* the inserted step; the fields a retype dropped) — empty when the merge had
|
|
25245
|
+
* nothing to say.
|
|
25102
25246
|
*/
|
|
25103
25247
|
updateSpec(kind, id, delta, hooks) {
|
|
25104
25248
|
const notices = [];
|
|
@@ -25212,14 +25356,113 @@ var init_specs2 = __esm({
|
|
|
25212
25356
|
);
|
|
25213
25357
|
}
|
|
25214
25358
|
};
|
|
25215
|
-
const
|
|
25359
|
+
const NO_NESTED_MERGE = /* @__PURE__ */ new Set(["ext", "narrative", "unset"]);
|
|
25360
|
+
const mergeElement = (existing, deltaItem) => {
|
|
25361
|
+
const out = { ...existing, ...deltaItem };
|
|
25362
|
+
if (!existing || typeof existing !== "object" || !deltaItem || typeof deltaItem !== "object") return out;
|
|
25363
|
+
for (const [key, value] of Object.entries(deltaItem)) {
|
|
25364
|
+
if (NO_NESTED_MERGE.has(key) || !Array.isArray(value) || value.length === 0) continue;
|
|
25365
|
+
const before = existing[key];
|
|
25366
|
+
if (before !== void 0 && !Array.isArray(before)) continue;
|
|
25367
|
+
const stored = Array.isArray(before) ? before : [];
|
|
25368
|
+
if (!isIdentifiedArray(key, stored, value)) continue;
|
|
25369
|
+
out[key] = mergeIdentifiedArray(key, stored, value);
|
|
25370
|
+
}
|
|
25371
|
+
return out;
|
|
25372
|
+
};
|
|
25373
|
+
const retargetByLabel = (merged, deltaStep) => {
|
|
25374
|
+
const out = { ...merged };
|
|
25375
|
+
for (const [stepField, labelField] of Object.entries(STEP_LABEL_TWINS)) {
|
|
25376
|
+
if (deltaStep?.[labelField] !== void 0 && deltaStep?.[stepField] === void 0) delete out[stepField];
|
|
25377
|
+
}
|
|
25378
|
+
for (const key of ["cases", "catches"]) {
|
|
25379
|
+
const deltaEntries = deltaStep?.[key];
|
|
25380
|
+
if (!Array.isArray(deltaEntries) || !Array.isArray(out[key])) continue;
|
|
25381
|
+
const retargeted = new Set(
|
|
25382
|
+
deltaEntries.filter((e) => e?.label !== void 0 && e?.step === void 0).map((e) => identityKeyOf(key, e)).filter((k) => k !== null)
|
|
25383
|
+
);
|
|
25384
|
+
if (!retargeted.size) continue;
|
|
25385
|
+
out[key] = out[key].map((e) => {
|
|
25386
|
+
const k = identityKeyOf(key, e);
|
|
25387
|
+
if (k === null || !retargeted.has(k)) return e;
|
|
25388
|
+
const { step, ...rest } = e;
|
|
25389
|
+
return rest;
|
|
25390
|
+
});
|
|
25391
|
+
}
|
|
25392
|
+
return out;
|
|
25393
|
+
};
|
|
25394
|
+
const rebuildOnRetype = (stored, deltaStep, merged, methodName) => {
|
|
25395
|
+
const newType = merged?.type;
|
|
25396
|
+
if (typeof newType !== "string" || stored?.type === newType) return merged;
|
|
25397
|
+
if (!Object.prototype.hasOwnProperty.call(STEP_TYPE_FIELDS, newType)) return merged;
|
|
25398
|
+
const allowed = stepFieldsFor(newType);
|
|
25399
|
+
const stated = Object.keys(deltaStep).filter(
|
|
25400
|
+
(k) => k !== "type" && deltaStep[k] !== void 0 && !allowed.has(k)
|
|
25401
|
+
);
|
|
25402
|
+
if (stated.length) {
|
|
25403
|
+
throw new Error(
|
|
25404
|
+
`Refusing to retype narrative step ${stored.stepNumber} of "${methodName}" from "${stored.type}" to "${newType}": the same delta sets ${stated.map((f) => `"${f}"`).join(", ")}, which a ${newType} step cannot carry. Drop those keys, or retype to a step type that carries them.`
|
|
25405
|
+
);
|
|
25406
|
+
}
|
|
25407
|
+
const out = {};
|
|
25408
|
+
const dropped = [];
|
|
25409
|
+
for (const [key, value] of Object.entries(merged)) {
|
|
25410
|
+
if (allowed.has(key)) out[key] = value;
|
|
25411
|
+
else dropped.push(key);
|
|
25412
|
+
}
|
|
25413
|
+
if (dropped.length) {
|
|
25414
|
+
notices.push(
|
|
25415
|
+
`Narrative step ${stored.stepNumber} of "${methodName}" was retyped ${stored.type} -> ${newType}: ${dropped.join(", ")} dropped \u2014 a ${newType} step cannot carry ${dropped.length === 1 ? "it" : "them"}. Its description and label are kept.`
|
|
25416
|
+
);
|
|
25417
|
+
}
|
|
25418
|
+
return out;
|
|
25419
|
+
};
|
|
25420
|
+
const mergeNarrative = (methodName, existingSteps, deltaSteps) => {
|
|
25216
25421
|
let steps = [...existingSteps];
|
|
25217
25422
|
const sortedDeltas = [...deltaSteps].sort((a, b) => a.stepNumber - b.stepNumber);
|
|
25218
25423
|
for (const deltaStep of sortedDeltas) {
|
|
25219
25424
|
const stepNum = deltaStep.stepNumber;
|
|
25425
|
+
if (typeof stepNum !== "number") {
|
|
25426
|
+
throw new Error(
|
|
25427
|
+
`Refusing to update the narrative of "${methodName}": a step delta must carry the "stepNumber" it addresses, got ${JSON.stringify(stepNum ?? null)}.`
|
|
25428
|
+
);
|
|
25429
|
+
}
|
|
25430
|
+
if ("remove" in deltaStep && typeof deltaStep.remove !== "boolean") {
|
|
25431
|
+
throw new Error(
|
|
25432
|
+
`Refusing to update narrative step ${stepNum} of "${methodName}": "remove" must be the boolean true, got ${JSON.stringify(deltaStep.remove)}. A non-boolean is ignored, which would leave the step in place while reporting success.`
|
|
25433
|
+
);
|
|
25434
|
+
}
|
|
25435
|
+
if ("action" in deltaStep && deltaStep.action !== "insert" && deltaStep.action !== "delete") {
|
|
25436
|
+
throw new Error(
|
|
25437
|
+
`Refusing to update narrative step ${stepNum} of "${methodName}": unknown action ${JSON.stringify(deltaStep.action)} \u2014 expected "insert" or "delete", or no action at all to edit the step in place. An unknown verb is ignored, which would report success for an edit never made.`
|
|
25438
|
+
);
|
|
25439
|
+
}
|
|
25440
|
+
if ("captureJumps" in deltaStep && deltaStep.action !== "insert") {
|
|
25441
|
+
throw new Error(
|
|
25442
|
+
`Refusing to update narrative step ${stepNum} of "${methodName}": "captureJumps" only means anything on an inserted step \u2014 it decides whether jumps aimed at that number follow the shifted original or land on the new step. On any other delta it is ignored.`
|
|
25443
|
+
);
|
|
25444
|
+
}
|
|
25220
25445
|
if (deltaStep.action === "delete" || deltaStep.remove === true) {
|
|
25221
25446
|
const idx = steps.findIndex((s) => s.stepNumber === stepNum);
|
|
25222
25447
|
if (idx !== -1) {
|
|
25448
|
+
const victim = steps[idx];
|
|
25449
|
+
for (const field of ["label", "description"]) {
|
|
25450
|
+
if (deltaStep[field] === void 0) continue;
|
|
25451
|
+
if (victim[field] !== deltaStep[field]) {
|
|
25452
|
+
throw new Error(
|
|
25453
|
+
`Refusing to delete narrative step ${stepNum}: the delta states ${field} ${JSON.stringify(deltaStep[field])}, but step ${stepNum} holds ${JSON.stringify(victim[field] ?? null)}. Step deltas apply in ascending order against the numbering earlier entries of the same delta left behind, so a later delete can address a step that has already moved.`
|
|
25454
|
+
);
|
|
25455
|
+
}
|
|
25456
|
+
}
|
|
25457
|
+
if (typeof victim.endStep === "number") {
|
|
25458
|
+
const body = steps.filter((s) => s.stepNumber > stepNum && s.stepNumber <= victim.endStep);
|
|
25459
|
+
if (body.length) {
|
|
25460
|
+
const span = body.length === 1 ? `step ${body[0].stepNumber}` : `steps ${body[0].stepNumber}-${body[body.length - 1].stepNumber}`;
|
|
25461
|
+
throw new Error(
|
|
25462
|
+
`Cannot delete narrative step ${stepNum}: it is a ${victim.type} header and ${span} ${body.length === 1 ? "is" : "are"} still its body. Deleting it would leave them in place with no ${victim.type} around them \u2014 a change of meaning nothing later reports. Dissolve the region first: retype step ${stepNum} to a type that carries no region (the region fields are dropped and reported), then delete it.`
|
|
25463
|
+
);
|
|
25464
|
+
}
|
|
25465
|
+
}
|
|
25223
25466
|
const referrers = steps.filter((s) => s.stepNumber !== stepNum).map((s) => ({ n: s.stepNumber, refs: jumpRefsTo(s, stepNum) })).filter((r) => r.refs.length > 0);
|
|
25224
25467
|
if (referrers.length) {
|
|
25225
25468
|
throw new Error(
|
|
@@ -25232,6 +25475,11 @@ var init_specs2 = __esm({
|
|
|
25232
25475
|
stepNum,
|
|
25233
25476
|
-1
|
|
25234
25477
|
));
|
|
25478
|
+
} else {
|
|
25479
|
+
const present = steps.length ? ` \u2014 it has steps ${steps[0].stepNumber}-${steps[steps.length - 1].stepNumber}` : " \u2014 it has no steps";
|
|
25480
|
+
throw new Error(
|
|
25481
|
+
`Refusing to delete narrative step ${stepNum}: this narrative has no step ${stepNum}${present}. Step deltas apply in ascending order against the numbering earlier entries of the same delta left behind, so a later delete can address a step that has already moved.`
|
|
25482
|
+
);
|
|
25235
25483
|
}
|
|
25236
25484
|
} else if (deltaStep.action === "insert") {
|
|
25237
25485
|
const capture = deltaStep.captureJumps === true;
|
|
@@ -25255,10 +25503,9 @@ var init_specs2 = __esm({
|
|
|
25255
25503
|
const idx = steps.findIndex((s) => s.stepNumber === stepNum);
|
|
25256
25504
|
if (idx !== -1) {
|
|
25257
25505
|
const { action, remove: remove2, ...cleanStep } = deltaStep;
|
|
25258
|
-
steps[idx]
|
|
25259
|
-
|
|
25260
|
-
|
|
25261
|
-
}, deltaStep);
|
|
25506
|
+
const stored = steps[idx];
|
|
25507
|
+
const merged = withUnset(retargetByLabel(mergeElement(stored, cleanStep), cleanStep), deltaStep);
|
|
25508
|
+
steps[idx] = rebuildOnRetype(stored, cleanStep, merged, methodName);
|
|
25262
25509
|
} else {
|
|
25263
25510
|
const { action, remove: remove2, ...cleanStep } = deltaStep;
|
|
25264
25511
|
steps.push(withUnset(cleanStep, deltaStep));
|
|
@@ -25289,12 +25536,11 @@ var init_specs2 = __esm({
|
|
|
25289
25536
|
const existingMethod = merged[idx];
|
|
25290
25537
|
let narrative = existingMethod.narrative ? [...existingMethod.narrative] : [];
|
|
25291
25538
|
if (Array.isArray(deltaMethod.narrative)) {
|
|
25292
|
-
narrative = deltaMethod.narrative.length === 0 ? [] : mergeNarrative(narrative, deltaMethod.narrative);
|
|
25539
|
+
narrative = deltaMethod.narrative.length === 0 ? [] : mergeNarrative(String(deltaMethod.name), narrative, deltaMethod.narrative);
|
|
25293
25540
|
}
|
|
25294
25541
|
const { narrative: _, ...cleanMethod } = deltaMethod;
|
|
25295
25542
|
merged[idx] = withUnset({
|
|
25296
|
-
...existingMethod,
|
|
25297
|
-
...cleanMethod,
|
|
25543
|
+
...mergeElement(existingMethod, cleanMethod),
|
|
25298
25544
|
...deltaMethod.ext !== void 0 ? { ext: mergeExt(existingMethod.ext, deltaMethod.ext) } : {},
|
|
25299
25545
|
narrative
|
|
25300
25546
|
}, deltaMethod);
|
|
@@ -25316,12 +25562,8 @@ var init_specs2 = __esm({
|
|
|
25316
25562
|
merged.splice(idx, 1);
|
|
25317
25563
|
} else {
|
|
25318
25564
|
merged[idx] = withUnset({
|
|
25319
|
-
...merged[idx],
|
|
25320
|
-
...deltaItem,
|
|
25321
|
-
...deltaItem.ext !== void 0 ? { ext: mergeExt(merged[idx].ext, deltaItem.ext) } : {},
|
|
25322
|
-
// An interface method's findings upsert and delete by code, like
|
|
25323
|
-
// lint.allow; an empty list still clears them outright.
|
|
25324
|
-
...Array.isArray(deltaItem.findings) && deltaItem.findings.length > 0 ? { findings: mergeIdentifiedArray("findings", merged[idx].findings ?? [], deltaItem.findings) } : {}
|
|
25565
|
+
...mergeElement(merged[idx], deltaItem),
|
|
25566
|
+
...deltaItem.ext !== void 0 ? { ext: mergeExt(merged[idx].ext, deltaItem.ext) } : {}
|
|
25325
25567
|
}, deltaItem);
|
|
25326
25568
|
}
|
|
25327
25569
|
} else {
|
|
@@ -25340,7 +25582,7 @@ var init_specs2 = __esm({
|
|
|
25340
25582
|
if (deltaItem.remove === true || deltaItem.action === "delete") {
|
|
25341
25583
|
merged.splice(idx, 1);
|
|
25342
25584
|
} else {
|
|
25343
|
-
merged[idx] = withUnset(
|
|
25585
|
+
merged[idx] = withUnset(mergeElement(merged[idx], deltaItem), deltaItem);
|
|
25344
25586
|
}
|
|
25345
25587
|
} else {
|
|
25346
25588
|
assertUnmatchedIsAnAddition(deltaItem, keyOf(deltaItem), merged.map(keyOf), "entry");
|
|
@@ -25359,10 +25601,7 @@ var init_specs2 = __esm({
|
|
|
25359
25601
|
if (deltaItem.remove === true || deltaItem.action === "delete") {
|
|
25360
25602
|
merged.splice(idx, 1);
|
|
25361
25603
|
} else {
|
|
25362
|
-
merged[idx] = withUnset(
|
|
25363
|
-
...merged[idx],
|
|
25364
|
-
...deltaItem
|
|
25365
|
-
}, deltaItem);
|
|
25604
|
+
merged[idx] = withUnset(mergeElement(merged[idx], deltaItem), deltaItem);
|
|
25366
25605
|
}
|
|
25367
25606
|
} else {
|
|
25368
25607
|
assertUnmatchedIsAnAddition(deltaItem, piKey(deltaItem), merged.map(piKey), "publicInterface");
|
|
@@ -25380,7 +25619,7 @@ var init_specs2 = __esm({
|
|
|
25380
25619
|
const isDelete = deltaItem?.remove === true || deltaItem?.action === "delete";
|
|
25381
25620
|
if (idx !== -1) {
|
|
25382
25621
|
if (isDelete) merged.splice(idx, 1);
|
|
25383
|
-
else merged[idx] = withUnset(
|
|
25622
|
+
else merged[idx] = withUnset(mergeElement(merged[idx], deltaItem), deltaItem);
|
|
25384
25623
|
} else {
|
|
25385
25624
|
if (key !== null) {
|
|
25386
25625
|
assertUnmatchedIsAnAddition(
|
|
@@ -26389,7 +26628,7 @@ var init_status = __esm({
|
|
|
26389
26628
|
// src/core/authoring.ts
|
|
26390
26629
|
function candidateOptions() {
|
|
26391
26630
|
try {
|
|
26392
|
-
const config =
|
|
26631
|
+
const config = loadProjectConfig2();
|
|
26393
26632
|
return config ? { rules: config.rules, projectType: config.projectType } : {};
|
|
26394
26633
|
} catch {
|
|
26395
26634
|
return {};
|
|
@@ -26422,7 +26661,7 @@ var init_authoring = __esm({
|
|
|
26422
26661
|
init_specs2();
|
|
26423
26662
|
init_validation();
|
|
26424
26663
|
init_candidate();
|
|
26425
|
-
|
|
26664
|
+
init_core();
|
|
26426
26665
|
}
|
|
26427
26666
|
});
|
|
26428
26667
|
|
|
@@ -27718,7 +27957,7 @@ NOTICE:
|
|
|
27718
27957
|
inputSchema: {
|
|
27719
27958
|
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)"),
|
|
27720
27959
|
id: import_zod11.z.string().describe("The ID of the spec to update (namespaced if needed)"),
|
|
27721
|
-
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". 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). Renumbering RELOCATES every flow jump field (onTrueStep/onFalseStep/cases.step/defaultStep/endStep/catches.step/finallyStep/toStep) in the same narrative
|
|
27960
|
+
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).`)
|
|
27722
27961
|
}
|
|
27723
27962
|
},
|
|
27724
27963
|
({ kind, id, delta }) => {
|
|
@@ -28584,6 +28823,9 @@ function listDirectChainedSubprojects2(projectRoot2) {
|
|
|
28584
28823
|
function retireSpecialists2(apply) {
|
|
28585
28824
|
return retireSpecialists(apply);
|
|
28586
28825
|
}
|
|
28826
|
+
function repairForeignStepFields2(apply) {
|
|
28827
|
+
return repairForeignStepFields(apply);
|
|
28828
|
+
}
|
|
28587
28829
|
function defaultPackSelections2() {
|
|
28588
28830
|
return defaultPackSelections();
|
|
28589
28831
|
}
|
|
@@ -30725,6 +30967,23 @@ async function runDoctor(options = {}) {
|
|
|
30725
30967
|
} catch (e) {
|
|
30726
30968
|
line(tally, "warn", `Could not plan Specialist retirement: ${e instanceof Error ? e.message : String(e)}`);
|
|
30727
30969
|
}
|
|
30970
|
+
try {
|
|
30971
|
+
const foreign = repairForeignStepFields2(false);
|
|
30972
|
+
if (foreign.length > 0) {
|
|
30973
|
+
console.log(import_chalk12.default.bold("Narrative steps"));
|
|
30974
|
+
for (const repair of foreign) {
|
|
30975
|
+
line(
|
|
30976
|
+
tally,
|
|
30977
|
+
"warn",
|
|
30978
|
+
`${repair.implementation}.${repair.method} step ${repair.stepNumber} (${repair.stepType}) carries ${repair.fields.join(", ")} \u2014 a ${repair.stepType} step cannot.`
|
|
30979
|
+
);
|
|
30980
|
+
}
|
|
30981
|
+
line(tally, "warn", "Run `wairon doctor --fix` to drop them (or give each step the type that carries them).");
|
|
30982
|
+
logger.blank();
|
|
30983
|
+
}
|
|
30984
|
+
} catch (e) {
|
|
30985
|
+
line(tally, "warn", `Could not plan the narrative-step repair: ${e instanceof Error ? e.message : String(e)}`);
|
|
30986
|
+
}
|
|
30728
30987
|
}
|
|
30729
30988
|
console.log(import_chalk12.default.bold(`Generated files ${import_chalk12.default.gray("(stale = older than installed)")}`));
|
|
30730
30989
|
for (const [label, p] of [
|
|
@@ -30917,6 +31176,17 @@ async function applyFixes() {
|
|
|
30917
31176
|
} catch (e) {
|
|
30918
31177
|
console.log(` ${icon("error")} Specialist retirement failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
30919
31178
|
}
|
|
31179
|
+
try {
|
|
31180
|
+
const repairs = repairForeignStepFields2(true);
|
|
31181
|
+
if (repairs.length > 0) {
|
|
31182
|
+
console.log(` ${icon("ok")} Dropped foreign fields from ${repairs.length} narrative step(s):`);
|
|
31183
|
+
for (const repair of repairs) {
|
|
31184
|
+
console.log(` ${repair.implementation}.${repair.method} step ${repair.stepNumber} (${repair.stepType}): ${repair.fields.join(", ")}`);
|
|
31185
|
+
}
|
|
31186
|
+
}
|
|
31187
|
+
} catch (e) {
|
|
31188
|
+
console.log(` ${icon("error")} Narrative-step repair failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
31189
|
+
}
|
|
30920
31190
|
const { runMcpInstall: runMcpInstall2 } = (init_mcp(), __toCommonJS(mcp_exports));
|
|
30921
31191
|
if (targets.includes("claude")) {
|
|
30922
31192
|
try {
|