@wairon/cli 5.1.1-dev.33 → 5.1.1-dev.35
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 +350 -83
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +337 -74
- 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.35";
|
|
69
69
|
GITHUB_REPO = "SYW-Apps/Waffle-AIron";
|
|
70
70
|
SUPPORTED_ALIASES = ["wai"];
|
|
71
71
|
SCAN_EXCLUDE_DIRS = /* @__PURE__ */ new Set([
|
|
@@ -23143,6 +23143,7 @@ __export(specs_exports, {
|
|
|
23143
23143
|
setExecutionTier: () => setExecutionTier,
|
|
23144
23144
|
setProjectType: () => setProjectType,
|
|
23145
23145
|
snapshotSpecFiles: () => snapshotSpecFiles,
|
|
23146
|
+
specChanges: () => specChanges,
|
|
23146
23147
|
specPathsInScope: () => specPathsInScope,
|
|
23147
23148
|
updateSpec: () => updateSpec,
|
|
23148
23149
|
upsertPackSelection: () => upsertPackSelection,
|
|
@@ -23526,6 +23527,112 @@ function computeSpecTreeSignature(dirs) {
|
|
|
23526
23527
|
}
|
|
23527
23528
|
return parts.join("|");
|
|
23528
23529
|
}
|
|
23530
|
+
function summarizeValue(value) {
|
|
23531
|
+
if (value === void 0) return "";
|
|
23532
|
+
const text2 = typeof value === "string" ? value : JSON.stringify(value) ?? String(value);
|
|
23533
|
+
return text2.length > 120 ? `${text2.slice(0, 117)}...` : text2;
|
|
23534
|
+
}
|
|
23535
|
+
function identityKeyOf(field, item) {
|
|
23536
|
+
if (item === null || typeof item !== "object") return null;
|
|
23537
|
+
const o = item;
|
|
23538
|
+
const str = (v) => typeof v === "string" && v.length > 0 ? v : null;
|
|
23539
|
+
switch (field) {
|
|
23540
|
+
case "dispatch":
|
|
23541
|
+
return str(o.capability);
|
|
23542
|
+
case "lifecycle":
|
|
23543
|
+
return `${String(o.phase)} ${String(o.component)} ${String(o.method)}`;
|
|
23544
|
+
case "emits":
|
|
23545
|
+
case "subscribesTo":
|
|
23546
|
+
return `${String(o.topic)} ${o.event === void 0 ? "" : String(o.event)}`;
|
|
23547
|
+
case "trustedLinks":
|
|
23548
|
+
return str(o.subsystem);
|
|
23549
|
+
case "allow":
|
|
23550
|
+
return str(o.code);
|
|
23551
|
+
// lint.allow
|
|
23552
|
+
case "findings":
|
|
23553
|
+
return str(o.code);
|
|
23554
|
+
// an interface method's findings
|
|
23555
|
+
case "invariants":
|
|
23556
|
+
return str(o.id);
|
|
23557
|
+
case "patterns":
|
|
23558
|
+
return str(o.id);
|
|
23559
|
+
case "boundaries":
|
|
23560
|
+
return str(o.name);
|
|
23561
|
+
case "globalRequirements":
|
|
23562
|
+
return str(o.description);
|
|
23563
|
+
case "databases":
|
|
23564
|
+
return str(o.id) ?? str(o.name);
|
|
23565
|
+
// A flow step's own arrays, addressed by the thing the author named them
|
|
23566
|
+
// for. Their `step` is a RELOCATABLE number, never an identity — a switch
|
|
23567
|
+
// case is the value it matches and a catch clause is the error it catches,
|
|
23568
|
+
// and both survive the renumbering that moves their target. (A parallel
|
|
23569
|
+
// arm falls to the default: named, it merges by name; unnamed, it carries
|
|
23570
|
+
// no identity at all and the list replaces wholesale, which is the same
|
|
23571
|
+
// rule everywhere else.)
|
|
23572
|
+
case "cases":
|
|
23573
|
+
return str(o.value);
|
|
23574
|
+
case "catches":
|
|
23575
|
+
return str(o.error);
|
|
23576
|
+
default:
|
|
23577
|
+
return str(o.name) ?? str(o.id);
|
|
23578
|
+
}
|
|
23579
|
+
}
|
|
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
|
+
function reportIdentityOf(field, item) {
|
|
23590
|
+
if (item !== null && typeof item === "object") {
|
|
23591
|
+
const step = item.stepNumber;
|
|
23592
|
+
if (field === "narrative" && typeof step === "number") return `step ${step}`;
|
|
23593
|
+
}
|
|
23594
|
+
return identityKeyOf(field, item);
|
|
23595
|
+
}
|
|
23596
|
+
function specChanges(before, after, prefix = "") {
|
|
23597
|
+
const at = (key) => prefix ? `${prefix}.${key}` : key;
|
|
23598
|
+
const isPlain = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
23599
|
+
if (Array.isArray(before) && Array.isArray(after)) {
|
|
23600
|
+
if (after.length === 0 && before.length > 0) {
|
|
23601
|
+
return [{ path: prefix, change: "cleared", before: summarizeValue(before) }];
|
|
23602
|
+
}
|
|
23603
|
+
const identified = [...before, ...after].every((i) => reportIdentityOf(prefix.split(".").pop() ?? prefix, i) !== null);
|
|
23604
|
+
if (!identified) {
|
|
23605
|
+
return JSON.stringify(before) === JSON.stringify(after) ? [] : [{ path: prefix, change: "set", before: summarizeValue(before), after: summarizeValue(after) }];
|
|
23606
|
+
}
|
|
23607
|
+
const field = prefix.split(".").pop() ?? prefix;
|
|
23608
|
+
const keyOf = (i) => String(reportIdentityOf(field, i));
|
|
23609
|
+
const changes = [];
|
|
23610
|
+
for (const item of before) {
|
|
23611
|
+
const match = after.find((a) => keyOf(a) === keyOf(item));
|
|
23612
|
+
if (match === void 0) changes.push({ path: at(keyOf(item)), change: "removed", before: summarizeValue(item) });
|
|
23613
|
+
else changes.push(...specChanges(item, match, at(keyOf(item))));
|
|
23614
|
+
}
|
|
23615
|
+
for (const item of after) {
|
|
23616
|
+
if (!before.some((b) => keyOf(b) === keyOf(item))) {
|
|
23617
|
+
changes.push({ path: at(keyOf(item)), change: "added", after: summarizeValue(item) });
|
|
23618
|
+
}
|
|
23619
|
+
}
|
|
23620
|
+
return changes;
|
|
23621
|
+
}
|
|
23622
|
+
if (isPlain(before) && isPlain(after)) {
|
|
23623
|
+
const changes = [];
|
|
23624
|
+
for (const key of /* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)])) {
|
|
23625
|
+
const b = before[key];
|
|
23626
|
+
const a = after[key];
|
|
23627
|
+
if (b === void 0 && a !== void 0) changes.push({ path: at(key), change: "added", after: summarizeValue(a) });
|
|
23628
|
+
else if (b !== void 0 && a === void 0) changes.push({ path: at(key), change: "removed", before: summarizeValue(b) });
|
|
23629
|
+
else changes.push(...specChanges(b, a, at(key)));
|
|
23630
|
+
}
|
|
23631
|
+
return changes;
|
|
23632
|
+
}
|
|
23633
|
+
if (JSON.stringify(before) === JSON.stringify(after)) return [];
|
|
23634
|
+
return [{ path: prefix, change: "set", before: summarizeValue(before), after: summarizeValue(after) }];
|
|
23635
|
+
}
|
|
23529
23636
|
function workspaceFor(rootDir) {
|
|
23530
23637
|
const key = path23.resolve(rootDir);
|
|
23531
23638
|
let ws = workspaces.get(key);
|
|
@@ -23754,7 +23861,7 @@ function findLegacySpecFiles() {
|
|
|
23754
23861
|
function updateSpec(kind, id, delta, hooks) {
|
|
23755
23862
|
return current().updateSpec(kind, id, delta, hooks);
|
|
23756
23863
|
}
|
|
23757
|
-
var fs17, path23, NO_ROOT_SUBSYSTEMS, SIGNATURE_TTL_MS, SpecWorkspace, workspaces;
|
|
23864
|
+
var fs17, path23, NO_ROOT_SUBSYSTEMS, SIGNATURE_TTL_MS, STEP_TYPE_FIELDS, STEP_COMMON_FIELDS, STEP_LABEL_TWINS, SpecWorkspace, workspaces;
|
|
23758
23865
|
var init_specs2 = __esm({
|
|
23759
23866
|
"src/core/specs.ts"() {
|
|
23760
23867
|
"use strict";
|
|
@@ -23772,6 +23879,36 @@ var init_specs2 = __esm({
|
|
|
23772
23879
|
init_diagram();
|
|
23773
23880
|
NO_ROOT_SUBSYSTEMS = /* @__PURE__ */ new Set();
|
|
23774
23881
|
SIGNATURE_TTL_MS = 2e3;
|
|
23882
|
+
STEP_TYPE_FIELDS = {
|
|
23883
|
+
local: [],
|
|
23884
|
+
call: ["targetComponent", "targetMethod", "auth", "detach"],
|
|
23885
|
+
register: ["targetComponent", "targetMethod"],
|
|
23886
|
+
dispatch: ["targetComponent", "capability", "auth", "detach"],
|
|
23887
|
+
branch: ["condition", "onTrueStep", "onFalseStep"],
|
|
23888
|
+
switch: ["on", "cases", "defaultStep"],
|
|
23889
|
+
loop: ["loopKind", "over", "condition", "endStep"],
|
|
23890
|
+
try: ["endStep", "catches", "finallyStep"],
|
|
23891
|
+
parallel: ["endStep", "branches"],
|
|
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
|
+
};
|
|
23775
23912
|
SpecWorkspace = class {
|
|
23776
23913
|
constructor(rootDir) {
|
|
23777
23914
|
this.cachedIndex = null;
|
|
@@ -25009,9 +25146,39 @@ var init_specs2 = __esm({
|
|
|
25009
25146
|
// Granular delta updates (sdd_update_spec)
|
|
25010
25147
|
// -------------------------------------------------------------------------
|
|
25011
25148
|
/**
|
|
25012
|
-
*
|
|
25013
|
-
*
|
|
25014
|
-
*
|
|
25149
|
+
* Apply a delta to one stored spec, in this ORDER — the order is the contract,
|
|
25150
|
+
* because every stage below can see what the stage above left:
|
|
25151
|
+
*
|
|
25152
|
+
* 1. LOAD the stored spec for the addressed kind and id; a spec that does
|
|
25153
|
+
* not exist refuses, and a mis-selected `system` kind refuses by name.
|
|
25154
|
+
* 2. REFUSE unknown delta keys, whole. A key the level's schema does not
|
|
25155
|
+
* know is dropped on write, so the edit would report a success it never
|
|
25156
|
+
* performed.
|
|
25157
|
+
* 3. QUALIFY the delta's id-bearing references against the spec's namespace,
|
|
25158
|
+
* BEFORE the merge — post-merge a bare id is ambiguous.
|
|
25159
|
+
* 4. MERGE. Identity-keyed arrays upsert element by element and honour their
|
|
25160
|
+
* delete markers, at every depth. Narrative step deltas apply in ASCENDING
|
|
25161
|
+
* stepNumber order, each against the numbering the earlier entries of the
|
|
25162
|
+
* same delta left behind — delete step 3 and step 7 becomes step 6, so a
|
|
25163
|
+
* second delete written as 7 addresses what used to be step 8. That is why
|
|
25164
|
+
* a delete may restate the step's `label` or `description` as a guard, and
|
|
25165
|
+
* why labels are the reliable way to name a jump target. Inserts and
|
|
25166
|
+
* deletes relocate every jump field in the same narrative around them; a
|
|
25167
|
+
* step whose `type` changes is REBUILT for its new type, keeping its
|
|
25168
|
+
* description and label.
|
|
25169
|
+
* 5. UNSET the fields the delta names, at the level each `unset` sits in.
|
|
25170
|
+
* 6. RESOLVE symbolic `*Label` references against the MERGED numbering — after
|
|
25171
|
+
* the merge, so a delta may anchor on a label that only pre-existing steps
|
|
25172
|
+
* carry, and a label in the delta retargets the stored numeric jump it
|
|
25173
|
+
* twins. An unresolved reference aborts the whole update.
|
|
25174
|
+
* 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
|
+
* 8. GATE the merged spec through the caller's hook, then SAVE.
|
|
25177
|
+
*
|
|
25178
|
+
* Returns the change report, whose `notices` carry the non-fatal effects (an
|
|
25179
|
+
* insert whose position was a jump target, so the relocated jumps now bypass
|
|
25180
|
+
* the inserted step; the fields a retype dropped) — empty when the merge had
|
|
25181
|
+
* nothing to say.
|
|
25015
25182
|
*/
|
|
25016
25183
|
updateSpec(kind, id, delta, hooks) {
|
|
25017
25184
|
const notices = [];
|
|
@@ -25089,6 +25256,14 @@ var init_specs2 = __esm({
|
|
|
25089
25256
|
return refs;
|
|
25090
25257
|
};
|
|
25091
25258
|
const normalizeIdentity = (k) => String(k ?? "").toLowerCase().replace(/[_\-\s]/g, "");
|
|
25259
|
+
const unsetFieldsOf = (item) => Array.isArray(item?.unset) ? item.unset.filter((f) => typeof f === "string") : [];
|
|
25260
|
+
const withUnset = (merged, deltaItem) => {
|
|
25261
|
+
const fields = unsetFieldsOf(deltaItem);
|
|
25262
|
+
const out = { ...merged };
|
|
25263
|
+
delete out.unset;
|
|
25264
|
+
for (const field of fields) delete out[field];
|
|
25265
|
+
return out;
|
|
25266
|
+
};
|
|
25092
25267
|
const assertDeltaMarkers = (item, label) => {
|
|
25093
25268
|
if (!item || typeof item !== "object") return;
|
|
25094
25269
|
if ("remove" in item && typeof item.remove !== "boolean") {
|
|
@@ -25117,14 +25292,113 @@ var init_specs2 = __esm({
|
|
|
25117
25292
|
);
|
|
25118
25293
|
}
|
|
25119
25294
|
};
|
|
25120
|
-
const
|
|
25295
|
+
const NO_NESTED_MERGE = /* @__PURE__ */ new Set(["ext", "narrative", "unset"]);
|
|
25296
|
+
const mergeElement = (existing, deltaItem) => {
|
|
25297
|
+
const out = { ...existing, ...deltaItem };
|
|
25298
|
+
if (!existing || typeof existing !== "object" || !deltaItem || typeof deltaItem !== "object") return out;
|
|
25299
|
+
for (const [key, value] of Object.entries(deltaItem)) {
|
|
25300
|
+
if (NO_NESTED_MERGE.has(key) || !Array.isArray(value) || value.length === 0) continue;
|
|
25301
|
+
const before = existing[key];
|
|
25302
|
+
if (before !== void 0 && !Array.isArray(before)) continue;
|
|
25303
|
+
const stored = Array.isArray(before) ? before : [];
|
|
25304
|
+
if (!isIdentifiedArray(key, stored, value)) continue;
|
|
25305
|
+
out[key] = mergeIdentifiedArray(key, stored, value);
|
|
25306
|
+
}
|
|
25307
|
+
return out;
|
|
25308
|
+
};
|
|
25309
|
+
const retargetByLabel = (merged, deltaStep) => {
|
|
25310
|
+
const out = { ...merged };
|
|
25311
|
+
for (const [stepField, labelField] of Object.entries(STEP_LABEL_TWINS)) {
|
|
25312
|
+
if (deltaStep?.[labelField] !== void 0 && deltaStep?.[stepField] === void 0) delete out[stepField];
|
|
25313
|
+
}
|
|
25314
|
+
for (const key of ["cases", "catches"]) {
|
|
25315
|
+
const deltaEntries = deltaStep?.[key];
|
|
25316
|
+
if (!Array.isArray(deltaEntries) || !Array.isArray(out[key])) continue;
|
|
25317
|
+
const retargeted = new Set(
|
|
25318
|
+
deltaEntries.filter((e) => e?.label !== void 0 && e?.step === void 0).map((e) => identityKeyOf(key, e)).filter((k) => k !== null)
|
|
25319
|
+
);
|
|
25320
|
+
if (!retargeted.size) continue;
|
|
25321
|
+
out[key] = out[key].map((e) => {
|
|
25322
|
+
const k = identityKeyOf(key, e);
|
|
25323
|
+
if (k === null || !retargeted.has(k)) return e;
|
|
25324
|
+
const { step, ...rest } = e;
|
|
25325
|
+
return rest;
|
|
25326
|
+
});
|
|
25327
|
+
}
|
|
25328
|
+
return out;
|
|
25329
|
+
};
|
|
25330
|
+
const rebuildOnRetype = (stored, deltaStep, merged, methodName) => {
|
|
25331
|
+
const newType = merged?.type;
|
|
25332
|
+
if (typeof newType !== "string" || stored?.type === newType) return merged;
|
|
25333
|
+
if (!Object.prototype.hasOwnProperty.call(STEP_TYPE_FIELDS, newType)) return merged;
|
|
25334
|
+
const allowed = stepFieldsFor(newType);
|
|
25335
|
+
const stated = Object.keys(deltaStep).filter(
|
|
25336
|
+
(k) => k !== "type" && deltaStep[k] !== void 0 && !allowed.has(k)
|
|
25337
|
+
);
|
|
25338
|
+
if (stated.length) {
|
|
25339
|
+
throw new Error(
|
|
25340
|
+
`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.`
|
|
25341
|
+
);
|
|
25342
|
+
}
|
|
25343
|
+
const out = {};
|
|
25344
|
+
const dropped = [];
|
|
25345
|
+
for (const [key, value] of Object.entries(merged)) {
|
|
25346
|
+
if (allowed.has(key)) out[key] = value;
|
|
25347
|
+
else dropped.push(key);
|
|
25348
|
+
}
|
|
25349
|
+
if (dropped.length) {
|
|
25350
|
+
notices.push(
|
|
25351
|
+
`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.`
|
|
25352
|
+
);
|
|
25353
|
+
}
|
|
25354
|
+
return out;
|
|
25355
|
+
};
|
|
25356
|
+
const mergeNarrative = (methodName, existingSteps, deltaSteps) => {
|
|
25121
25357
|
let steps = [...existingSteps];
|
|
25122
25358
|
const sortedDeltas = [...deltaSteps].sort((a, b) => a.stepNumber - b.stepNumber);
|
|
25123
25359
|
for (const deltaStep of sortedDeltas) {
|
|
25124
25360
|
const stepNum = deltaStep.stepNumber;
|
|
25361
|
+
if (typeof stepNum !== "number") {
|
|
25362
|
+
throw new Error(
|
|
25363
|
+
`Refusing to update the narrative of "${methodName}": a step delta must carry the "stepNumber" it addresses, got ${JSON.stringify(stepNum ?? null)}.`
|
|
25364
|
+
);
|
|
25365
|
+
}
|
|
25366
|
+
if ("remove" in deltaStep && typeof deltaStep.remove !== "boolean") {
|
|
25367
|
+
throw new Error(
|
|
25368
|
+
`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.`
|
|
25369
|
+
);
|
|
25370
|
+
}
|
|
25371
|
+
if ("action" in deltaStep && deltaStep.action !== "insert" && deltaStep.action !== "delete") {
|
|
25372
|
+
throw new Error(
|
|
25373
|
+
`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.`
|
|
25374
|
+
);
|
|
25375
|
+
}
|
|
25376
|
+
if ("captureJumps" in deltaStep && deltaStep.action !== "insert") {
|
|
25377
|
+
throw new Error(
|
|
25378
|
+
`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.`
|
|
25379
|
+
);
|
|
25380
|
+
}
|
|
25125
25381
|
if (deltaStep.action === "delete" || deltaStep.remove === true) {
|
|
25126
25382
|
const idx = steps.findIndex((s) => s.stepNumber === stepNum);
|
|
25127
25383
|
if (idx !== -1) {
|
|
25384
|
+
const victim = steps[idx];
|
|
25385
|
+
for (const field of ["label", "description"]) {
|
|
25386
|
+
if (deltaStep[field] === void 0) continue;
|
|
25387
|
+
if (victim[field] !== deltaStep[field]) {
|
|
25388
|
+
throw new Error(
|
|
25389
|
+
`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.`
|
|
25390
|
+
);
|
|
25391
|
+
}
|
|
25392
|
+
}
|
|
25393
|
+
if (typeof victim.endStep === "number") {
|
|
25394
|
+
const body = steps.filter((s) => s.stepNumber > stepNum && s.stepNumber <= victim.endStep);
|
|
25395
|
+
if (body.length) {
|
|
25396
|
+
const span = body.length === 1 ? `step ${body[0].stepNumber}` : `steps ${body[0].stepNumber}-${body[body.length - 1].stepNumber}`;
|
|
25397
|
+
throw new Error(
|
|
25398
|
+
`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.`
|
|
25399
|
+
);
|
|
25400
|
+
}
|
|
25401
|
+
}
|
|
25128
25402
|
const referrers = steps.filter((s) => s.stepNumber !== stepNum).map((s) => ({ n: s.stepNumber, refs: jumpRefsTo(s, stepNum) })).filter((r) => r.refs.length > 0);
|
|
25129
25403
|
if (referrers.length) {
|
|
25130
25404
|
throw new Error(
|
|
@@ -25137,6 +25411,11 @@ var init_specs2 = __esm({
|
|
|
25137
25411
|
stepNum,
|
|
25138
25412
|
-1
|
|
25139
25413
|
));
|
|
25414
|
+
} else {
|
|
25415
|
+
const present = steps.length ? ` \u2014 it has steps ${steps[0].stepNumber}-${steps[steps.length - 1].stepNumber}` : " \u2014 it has no steps";
|
|
25416
|
+
throw new Error(
|
|
25417
|
+
`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.`
|
|
25418
|
+
);
|
|
25140
25419
|
}
|
|
25141
25420
|
} else if (deltaStep.action === "insert") {
|
|
25142
25421
|
const capture = deltaStep.captureJumps === true;
|
|
@@ -25155,18 +25434,17 @@ var init_specs2 = __esm({
|
|
|
25155
25434
|
capture
|
|
25156
25435
|
));
|
|
25157
25436
|
const { action, remove: remove2, captureJumps, ...cleanStep } = deltaStep;
|
|
25158
|
-
steps.push(cleanStep);
|
|
25437
|
+
steps.push(withUnset(cleanStep, deltaStep));
|
|
25159
25438
|
} else {
|
|
25160
25439
|
const idx = steps.findIndex((s) => s.stepNumber === stepNum);
|
|
25161
25440
|
if (idx !== -1) {
|
|
25162
25441
|
const { action, remove: remove2, ...cleanStep } = deltaStep;
|
|
25163
|
-
steps[idx]
|
|
25164
|
-
|
|
25165
|
-
|
|
25166
|
-
};
|
|
25442
|
+
const stored = steps[idx];
|
|
25443
|
+
const merged = withUnset(retargetByLabel(mergeElement(stored, cleanStep), cleanStep), deltaStep);
|
|
25444
|
+
steps[idx] = rebuildOnRetype(stored, cleanStep, merged, methodName);
|
|
25167
25445
|
} else {
|
|
25168
25446
|
const { action, remove: remove2, ...cleanStep } = deltaStep;
|
|
25169
|
-
steps.push(cleanStep);
|
|
25447
|
+
steps.push(withUnset(cleanStep, deltaStep));
|
|
25170
25448
|
}
|
|
25171
25449
|
}
|
|
25172
25450
|
}
|
|
@@ -25193,20 +25471,19 @@ var init_specs2 = __esm({
|
|
|
25193
25471
|
} else {
|
|
25194
25472
|
const existingMethod = merged[idx];
|
|
25195
25473
|
let narrative = existingMethod.narrative ? [...existingMethod.narrative] : [];
|
|
25196
|
-
if (
|
|
25197
|
-
narrative = mergeNarrative(narrative, deltaMethod.narrative);
|
|
25474
|
+
if (Array.isArray(deltaMethod.narrative)) {
|
|
25475
|
+
narrative = deltaMethod.narrative.length === 0 ? [] : mergeNarrative(String(deltaMethod.name), narrative, deltaMethod.narrative);
|
|
25198
25476
|
}
|
|
25199
25477
|
const { narrative: _, ...cleanMethod } = deltaMethod;
|
|
25200
|
-
merged[idx] = {
|
|
25201
|
-
...existingMethod,
|
|
25202
|
-
...cleanMethod,
|
|
25478
|
+
merged[idx] = withUnset({
|
|
25479
|
+
...mergeElement(existingMethod, cleanMethod),
|
|
25203
25480
|
...deltaMethod.ext !== void 0 ? { ext: mergeExt(existingMethod.ext, deltaMethod.ext) } : {},
|
|
25204
25481
|
narrative
|
|
25205
|
-
};
|
|
25482
|
+
}, deltaMethod);
|
|
25206
25483
|
}
|
|
25207
25484
|
} else {
|
|
25208
25485
|
assertUnmatchedIsAnAddition(deltaMethod, deltaMethod.name, merged.map((m) => m.name), "method");
|
|
25209
|
-
merged.push(deltaMethod);
|
|
25486
|
+
merged.push(withUnset(deltaMethod, deltaMethod));
|
|
25210
25487
|
}
|
|
25211
25488
|
}
|
|
25212
25489
|
return merged;
|
|
@@ -25220,18 +25497,14 @@ var init_specs2 = __esm({
|
|
|
25220
25497
|
if (deltaItem.remove === true || deltaItem.action === "delete") {
|
|
25221
25498
|
merged.splice(idx, 1);
|
|
25222
25499
|
} else {
|
|
25223
|
-
merged[idx] = {
|
|
25224
|
-
...merged[idx],
|
|
25225
|
-
...deltaItem,
|
|
25226
|
-
|
|
25227
|
-
// An interface method's findings upsert and delete by code, like
|
|
25228
|
-
// lint.allow; an empty list still clears them outright.
|
|
25229
|
-
...Array.isArray(deltaItem.findings) && deltaItem.findings.length > 0 ? { findings: mergeIdentifiedArray("findings", merged[idx].findings ?? [], deltaItem.findings) } : {}
|
|
25230
|
-
};
|
|
25500
|
+
merged[idx] = withUnset({
|
|
25501
|
+
...mergeElement(merged[idx], deltaItem),
|
|
25502
|
+
...deltaItem.ext !== void 0 ? { ext: mergeExt(merged[idx].ext, deltaItem.ext) } : {}
|
|
25503
|
+
}, deltaItem);
|
|
25231
25504
|
}
|
|
25232
25505
|
} else {
|
|
25233
25506
|
assertUnmatchedIsAnAddition(deltaItem, deltaItem.name, merged.map((i) => i.name), "entry");
|
|
25234
|
-
merged.push(deltaItem);
|
|
25507
|
+
merged.push(withUnset(deltaItem, deltaItem));
|
|
25235
25508
|
}
|
|
25236
25509
|
}
|
|
25237
25510
|
return merged;
|
|
@@ -25245,11 +25518,11 @@ var init_specs2 = __esm({
|
|
|
25245
25518
|
if (deltaItem.remove === true || deltaItem.action === "delete") {
|
|
25246
25519
|
merged.splice(idx, 1);
|
|
25247
25520
|
} else {
|
|
25248
|
-
merged[idx] =
|
|
25521
|
+
merged[idx] = withUnset(mergeElement(merged[idx], deltaItem), deltaItem);
|
|
25249
25522
|
}
|
|
25250
25523
|
} else {
|
|
25251
25524
|
assertUnmatchedIsAnAddition(deltaItem, keyOf(deltaItem), merged.map(keyOf), "entry");
|
|
25252
|
-
merged.push(deltaItem);
|
|
25525
|
+
merged.push(withUnset(deltaItem, deltaItem));
|
|
25253
25526
|
}
|
|
25254
25527
|
}
|
|
25255
25528
|
return merged.map(({ action, remove: remove2, ...item }) => item);
|
|
@@ -25264,52 +25537,15 @@ var init_specs2 = __esm({
|
|
|
25264
25537
|
if (deltaItem.remove === true || deltaItem.action === "delete") {
|
|
25265
25538
|
merged.splice(idx, 1);
|
|
25266
25539
|
} else {
|
|
25267
|
-
merged[idx] =
|
|
25268
|
-
...merged[idx],
|
|
25269
|
-
...deltaItem
|
|
25270
|
-
};
|
|
25540
|
+
merged[idx] = withUnset(mergeElement(merged[idx], deltaItem), deltaItem);
|
|
25271
25541
|
}
|
|
25272
25542
|
} else {
|
|
25273
25543
|
assertUnmatchedIsAnAddition(deltaItem, piKey(deltaItem), merged.map(piKey), "publicInterface");
|
|
25274
|
-
merged.push(deltaItem);
|
|
25544
|
+
merged.push(withUnset(deltaItem, deltaItem));
|
|
25275
25545
|
}
|
|
25276
25546
|
}
|
|
25277
25547
|
return merged;
|
|
25278
25548
|
};
|
|
25279
|
-
const identityKeyOf = (field, item) => {
|
|
25280
|
-
if (item === null || typeof item !== "object") return null;
|
|
25281
|
-
const o = item;
|
|
25282
|
-
const str = (v) => typeof v === "string" && v.length > 0 ? v : null;
|
|
25283
|
-
switch (field) {
|
|
25284
|
-
case "dispatch":
|
|
25285
|
-
return str(o.capability);
|
|
25286
|
-
case "lifecycle":
|
|
25287
|
-
return `${String(o.phase)} ${String(o.component)} ${String(o.method)}`;
|
|
25288
|
-
case "emits":
|
|
25289
|
-
case "subscribesTo":
|
|
25290
|
-
return `${String(o.topic)} ${o.event === void 0 ? "" : String(o.event)}`;
|
|
25291
|
-
case "trustedLinks":
|
|
25292
|
-
return str(o.subsystem);
|
|
25293
|
-
case "allow":
|
|
25294
|
-
return str(o.code);
|
|
25295
|
-
// lint.allow
|
|
25296
|
-
case "findings":
|
|
25297
|
-
return str(o.code);
|
|
25298
|
-
// an interface method's findings
|
|
25299
|
-
case "invariants":
|
|
25300
|
-
return str(o.id);
|
|
25301
|
-
case "patterns":
|
|
25302
|
-
return str(o.id);
|
|
25303
|
-
case "boundaries":
|
|
25304
|
-
return str(o.name);
|
|
25305
|
-
case "globalRequirements":
|
|
25306
|
-
return str(o.description);
|
|
25307
|
-
case "databases":
|
|
25308
|
-
return str(o.id) ?? str(o.name);
|
|
25309
|
-
default:
|
|
25310
|
-
return str(o.name) ?? str(o.id);
|
|
25311
|
-
}
|
|
25312
|
-
};
|
|
25313
25549
|
const mergeIdentifiedArray = (field, existing, delta2) => {
|
|
25314
25550
|
const merged = [...existing];
|
|
25315
25551
|
for (const deltaItem of delta2) {
|
|
@@ -25319,7 +25555,7 @@ var init_specs2 = __esm({
|
|
|
25319
25555
|
const isDelete = deltaItem?.remove === true || deltaItem?.action === "delete";
|
|
25320
25556
|
if (idx !== -1) {
|
|
25321
25557
|
if (isDelete) merged.splice(idx, 1);
|
|
25322
|
-
else merged[idx] =
|
|
25558
|
+
else merged[idx] = withUnset(mergeElement(merged[idx], deltaItem), deltaItem);
|
|
25323
25559
|
} else {
|
|
25324
25560
|
if (key !== null) {
|
|
25325
25561
|
assertUnmatchedIsAnAddition(
|
|
@@ -25331,7 +25567,7 @@ var init_specs2 = __esm({
|
|
|
25331
25567
|
} else if (isDelete) {
|
|
25332
25568
|
throw new Error(`Refusing to delete a ${field} entry with no resolvable identity.`);
|
|
25333
25569
|
}
|
|
25334
|
-
merged.push(deltaItem);
|
|
25570
|
+
merged.push(withUnset(deltaItem, deltaItem));
|
|
25335
25571
|
}
|
|
25336
25572
|
}
|
|
25337
25573
|
return merged.map((item) => item && typeof item === "object" ? (({ action, remove: remove2, ...rest }) => rest)(item) : item);
|
|
@@ -25340,6 +25576,7 @@ var init_specs2 = __esm({
|
|
|
25340
25576
|
const mergeDelta = (existing, delta2) => {
|
|
25341
25577
|
const res = { ...existing };
|
|
25342
25578
|
for (const [key, value] of Object.entries(delta2)) {
|
|
25579
|
+
if (key === "unset") continue;
|
|
25343
25580
|
if (value === void 0 || value === null) {
|
|
25344
25581
|
continue;
|
|
25345
25582
|
}
|
|
@@ -25367,7 +25604,7 @@ var init_specs2 = __esm({
|
|
|
25367
25604
|
res[key] = value;
|
|
25368
25605
|
}
|
|
25369
25606
|
}
|
|
25370
|
-
return res;
|
|
25607
|
+
return withUnset(res, delta2);
|
|
25371
25608
|
};
|
|
25372
25609
|
const qualifyDeltaRefs = (d) => {
|
|
25373
25610
|
if (kind === "system") return d;
|
|
@@ -25421,9 +25658,9 @@ var init_specs2 = __esm({
|
|
|
25421
25658
|
};
|
|
25422
25659
|
const unsetFields = Array.isArray(delta.unset) ? delta.unset.filter((f) => typeof f === "string") : [];
|
|
25423
25660
|
const { unset: _unset, ...mergeableDelta } = delta;
|
|
25661
|
+
const storedBefore = JSON.parse(JSON.stringify(result));
|
|
25424
25662
|
const mergedResult = mergeDelta(result, qualifyDeltaRefs(mergeableDelta));
|
|
25425
25663
|
for (const field of unsetFields) delete mergedResult[field];
|
|
25426
|
-
mergedResult.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
25427
25664
|
if (kind === "implementation" && Array.isArray(mergedResult.methods)) {
|
|
25428
25665
|
const labelErrors = [];
|
|
25429
25666
|
for (const m of mergedResult.methods) {
|
|
@@ -25434,6 +25671,24 @@ var init_specs2 = __esm({
|
|
|
25434
25671
|
- ${labelErrors.join("\n- ")}`);
|
|
25435
25672
|
}
|
|
25436
25673
|
}
|
|
25674
|
+
const canonical = (spec) => {
|
|
25675
|
+
const parsed = deltaSchema.safeParse(spec);
|
|
25676
|
+
const value = { ...parsed.success ? parsed.data : spec };
|
|
25677
|
+
delete value.updatedAt;
|
|
25678
|
+
return value;
|
|
25679
|
+
};
|
|
25680
|
+
const changes = specChanges(canonical(storedBefore), canonical(mergedResult));
|
|
25681
|
+
if (changes.length === 0) {
|
|
25682
|
+
return {
|
|
25683
|
+
kind,
|
|
25684
|
+
id,
|
|
25685
|
+
written: false,
|
|
25686
|
+
changes,
|
|
25687
|
+
notices,
|
|
25688
|
+
summary: `No change to ${kind} "${id}" \u2014 the delta matches what is stored, so nothing was written.`
|
|
25689
|
+
};
|
|
25690
|
+
}
|
|
25691
|
+
mergedResult.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
25437
25692
|
const gateNotices = hooks?.gate?.(kind, mergedResult);
|
|
25438
25693
|
if (gateNotices?.length) notices.push(...gateNotices);
|
|
25439
25694
|
const opts = {
|
|
@@ -25459,7 +25714,14 @@ var init_specs2 = __esm({
|
|
|
25459
25714
|
notices.push(...this.saveTypeSpec(mergedResult));
|
|
25460
25715
|
break;
|
|
25461
25716
|
}
|
|
25462
|
-
return
|
|
25717
|
+
return {
|
|
25718
|
+
kind,
|
|
25719
|
+
id,
|
|
25720
|
+
written: true,
|
|
25721
|
+
changes,
|
|
25722
|
+
notices,
|
|
25723
|
+
summary: `Updated ${kind} "${id}": ${changes.length} change${changes.length === 1 ? "" : "s"}.`
|
|
25724
|
+
};
|
|
25463
25725
|
}
|
|
25464
25726
|
};
|
|
25465
25727
|
workspaces = /* @__PURE__ */ new Map();
|
|
@@ -26302,7 +26564,7 @@ var init_status = __esm({
|
|
|
26302
26564
|
// src/core/authoring.ts
|
|
26303
26565
|
function candidateOptions() {
|
|
26304
26566
|
try {
|
|
26305
|
-
const config =
|
|
26567
|
+
const config = loadProjectConfig2();
|
|
26306
26568
|
return config ? { rules: config.rules, projectType: config.projectType } : {};
|
|
26307
26569
|
} catch {
|
|
26308
26570
|
return {};
|
|
@@ -26335,7 +26597,7 @@ var init_authoring = __esm({
|
|
|
26335
26597
|
init_specs2();
|
|
26336
26598
|
init_validation();
|
|
26337
26599
|
init_candidate();
|
|
26338
|
-
|
|
26600
|
+
init_core();
|
|
26339
26601
|
}
|
|
26340
26602
|
});
|
|
26341
26603
|
|
|
@@ -26425,6 +26687,16 @@ function json(value) {
|
|
|
26425
26687
|
function errText(message) {
|
|
26426
26688
|
return { content: [{ type: "text", text: `Error: ${message}` }], isError: true };
|
|
26427
26689
|
}
|
|
26690
|
+
function renderChangeReport(report2) {
|
|
26691
|
+
const lines = [report2.summary];
|
|
26692
|
+
for (const change of report2.changes) {
|
|
26693
|
+
const to = change.after === void 0 ? "" : `: ${JSON.stringify(change.after)}`;
|
|
26694
|
+
const from = change.before === void 0 ? "" : ` (was ${JSON.stringify(change.before)})`;
|
|
26695
|
+
lines.push(`- ${change.path} ${change.change}${to}${from}`);
|
|
26696
|
+
}
|
|
26697
|
+
if (report2.notices.length) lines.push("", "NOTICE:", ...report2.notices.map((n) => `- ${n}`));
|
|
26698
|
+
return lines.join("\n");
|
|
26699
|
+
}
|
|
26428
26700
|
function captureBuildStamp(entryPath) {
|
|
26429
26701
|
try {
|
|
26430
26702
|
const s = fs20.statSync(entryPath);
|
|
@@ -27621,17 +27893,12 @@ NOTICE:
|
|
|
27621
27893
|
inputSchema: {
|
|
27622
27894
|
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)"),
|
|
27623
27895
|
id: import_zod11.z.string().describe("The ID of the spec to update (namespaced if needed)"),
|
|
27624
|
-
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
|
|
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).`)
|
|
27625
27897
|
}
|
|
27626
27898
|
},
|
|
27627
27899
|
({ kind, id, delta }) => {
|
|
27628
27900
|
try {
|
|
27629
|
-
|
|
27630
|
-
const noticeBlock = notices.length ? `
|
|
27631
|
-
|
|
27632
|
-
NOTICE:
|
|
27633
|
-
- ${notices.join("\n- ")}` : "";
|
|
27634
|
-
return text(`Successfully updated ${kind} spec "${id}".${noticeBlock}`);
|
|
27901
|
+
return text(renderChangeReport(updateSpecGated(kind, id, delta)));
|
|
27635
27902
|
} catch (e) {
|
|
27636
27903
|
return errText(String(e));
|
|
27637
27904
|
}
|