@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/index.js
CHANGED
|
@@ -1113,7 +1113,7 @@ var init_defaults = __esm({
|
|
|
1113
1113
|
copilot: ".github/prompts",
|
|
1114
1114
|
codex: ".codex/agents"
|
|
1115
1115
|
};
|
|
1116
|
-
WAIRON_VERSION = "5.1.1-dev.
|
|
1116
|
+
WAIRON_VERSION = "5.1.1-dev.35";
|
|
1117
1117
|
GITHUB_REPO = "SYW-Apps/Waffle-AIron";
|
|
1118
1118
|
ARCHITECT_AGENT_ID = "agent-architect";
|
|
1119
1119
|
ARCHITECT_TEMPLATE_ID = "architect";
|
|
@@ -18310,6 +18310,112 @@ function computeSpecTreeSignature(dirs) {
|
|
|
18310
18310
|
}
|
|
18311
18311
|
return parts.join("|");
|
|
18312
18312
|
}
|
|
18313
|
+
function summarizeValue(value) {
|
|
18314
|
+
if (value === void 0) return "";
|
|
18315
|
+
const text = typeof value === "string" ? value : JSON.stringify(value) ?? String(value);
|
|
18316
|
+
return text.length > 120 ? `${text.slice(0, 117)}...` : text;
|
|
18317
|
+
}
|
|
18318
|
+
function identityKeyOf(field, item) {
|
|
18319
|
+
if (item === null || typeof item !== "object") return null;
|
|
18320
|
+
const o = item;
|
|
18321
|
+
const str = (v) => typeof v === "string" && v.length > 0 ? v : null;
|
|
18322
|
+
switch (field) {
|
|
18323
|
+
case "dispatch":
|
|
18324
|
+
return str(o.capability);
|
|
18325
|
+
case "lifecycle":
|
|
18326
|
+
return `${String(o.phase)} ${String(o.component)} ${String(o.method)}`;
|
|
18327
|
+
case "emits":
|
|
18328
|
+
case "subscribesTo":
|
|
18329
|
+
return `${String(o.topic)} ${o.event === void 0 ? "" : String(o.event)}`;
|
|
18330
|
+
case "trustedLinks":
|
|
18331
|
+
return str(o.subsystem);
|
|
18332
|
+
case "allow":
|
|
18333
|
+
return str(o.code);
|
|
18334
|
+
// lint.allow
|
|
18335
|
+
case "findings":
|
|
18336
|
+
return str(o.code);
|
|
18337
|
+
// an interface method's findings
|
|
18338
|
+
case "invariants":
|
|
18339
|
+
return str(o.id);
|
|
18340
|
+
case "patterns":
|
|
18341
|
+
return str(o.id);
|
|
18342
|
+
case "boundaries":
|
|
18343
|
+
return str(o.name);
|
|
18344
|
+
case "globalRequirements":
|
|
18345
|
+
return str(o.description);
|
|
18346
|
+
case "databases":
|
|
18347
|
+
return str(o.id) ?? str(o.name);
|
|
18348
|
+
// A flow step's own arrays, addressed by the thing the author named them
|
|
18349
|
+
// for. Their `step` is a RELOCATABLE number, never an identity — a switch
|
|
18350
|
+
// case is the value it matches and a catch clause is the error it catches,
|
|
18351
|
+
// and both survive the renumbering that moves their target. (A parallel
|
|
18352
|
+
// arm falls to the default: named, it merges by name; unnamed, it carries
|
|
18353
|
+
// no identity at all and the list replaces wholesale, which is the same
|
|
18354
|
+
// rule everywhere else.)
|
|
18355
|
+
case "cases":
|
|
18356
|
+
return str(o.value);
|
|
18357
|
+
case "catches":
|
|
18358
|
+
return str(o.error);
|
|
18359
|
+
default:
|
|
18360
|
+
return str(o.name) ?? str(o.id);
|
|
18361
|
+
}
|
|
18362
|
+
}
|
|
18363
|
+
function stepFieldsFor(type) {
|
|
18364
|
+
const own = STEP_TYPE_FIELDS[type] ?? [];
|
|
18365
|
+
const allowed = /* @__PURE__ */ new Set([...STEP_COMMON_FIELDS, ...own]);
|
|
18366
|
+
for (const field of own) {
|
|
18367
|
+
const twin = STEP_LABEL_TWINS[field];
|
|
18368
|
+
if (twin) allowed.add(twin);
|
|
18369
|
+
}
|
|
18370
|
+
return allowed;
|
|
18371
|
+
}
|
|
18372
|
+
function reportIdentityOf(field, item) {
|
|
18373
|
+
if (item !== null && typeof item === "object") {
|
|
18374
|
+
const step = item.stepNumber;
|
|
18375
|
+
if (field === "narrative" && typeof step === "number") return `step ${step}`;
|
|
18376
|
+
}
|
|
18377
|
+
return identityKeyOf(field, item);
|
|
18378
|
+
}
|
|
18379
|
+
function specChanges(before, after, prefix = "") {
|
|
18380
|
+
const at = (key) => prefix ? `${prefix}.${key}` : key;
|
|
18381
|
+
const isPlain = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
18382
|
+
if (Array.isArray(before) && Array.isArray(after)) {
|
|
18383
|
+
if (after.length === 0 && before.length > 0) {
|
|
18384
|
+
return [{ path: prefix, change: "cleared", before: summarizeValue(before) }];
|
|
18385
|
+
}
|
|
18386
|
+
const identified = [...before, ...after].every((i) => reportIdentityOf(prefix.split(".").pop() ?? prefix, i) !== null);
|
|
18387
|
+
if (!identified) {
|
|
18388
|
+
return JSON.stringify(before) === JSON.stringify(after) ? [] : [{ path: prefix, change: "set", before: summarizeValue(before), after: summarizeValue(after) }];
|
|
18389
|
+
}
|
|
18390
|
+
const field = prefix.split(".").pop() ?? prefix;
|
|
18391
|
+
const keyOf = (i) => String(reportIdentityOf(field, i));
|
|
18392
|
+
const changes = [];
|
|
18393
|
+
for (const item of before) {
|
|
18394
|
+
const match = after.find((a) => keyOf(a) === keyOf(item));
|
|
18395
|
+
if (match === void 0) changes.push({ path: at(keyOf(item)), change: "removed", before: summarizeValue(item) });
|
|
18396
|
+
else changes.push(...specChanges(item, match, at(keyOf(item))));
|
|
18397
|
+
}
|
|
18398
|
+
for (const item of after) {
|
|
18399
|
+
if (!before.some((b) => keyOf(b) === keyOf(item))) {
|
|
18400
|
+
changes.push({ path: at(keyOf(item)), change: "added", after: summarizeValue(item) });
|
|
18401
|
+
}
|
|
18402
|
+
}
|
|
18403
|
+
return changes;
|
|
18404
|
+
}
|
|
18405
|
+
if (isPlain(before) && isPlain(after)) {
|
|
18406
|
+
const changes = [];
|
|
18407
|
+
for (const key of /* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)])) {
|
|
18408
|
+
const b = before[key];
|
|
18409
|
+
const a = after[key];
|
|
18410
|
+
if (b === void 0 && a !== void 0) changes.push({ path: at(key), change: "added", after: summarizeValue(a) });
|
|
18411
|
+
else if (b !== void 0 && a === void 0) changes.push({ path: at(key), change: "removed", before: summarizeValue(b) });
|
|
18412
|
+
else changes.push(...specChanges(b, a, at(key)));
|
|
18413
|
+
}
|
|
18414
|
+
return changes;
|
|
18415
|
+
}
|
|
18416
|
+
if (JSON.stringify(before) === JSON.stringify(after)) return [];
|
|
18417
|
+
return [{ path: prefix, change: "set", before: summarizeValue(before), after: summarizeValue(after) }];
|
|
18418
|
+
}
|
|
18313
18419
|
function workspaceFor(rootDir) {
|
|
18314
18420
|
const key = path16.resolve(rootDir);
|
|
18315
18421
|
let ws = workspaces.get(key);
|
|
@@ -18538,7 +18644,7 @@ function findLegacySpecFiles() {
|
|
|
18538
18644
|
function updateSpec(kind, id, delta, hooks) {
|
|
18539
18645
|
return current().updateSpec(kind, id, delta, hooks);
|
|
18540
18646
|
}
|
|
18541
|
-
var fs11, path16, NO_ROOT_SUBSYSTEMS, SIGNATURE_TTL_MS, SpecWorkspace, workspaces;
|
|
18647
|
+
var fs11, path16, NO_ROOT_SUBSYSTEMS, SIGNATURE_TTL_MS, STEP_TYPE_FIELDS, STEP_COMMON_FIELDS, STEP_LABEL_TWINS, SpecWorkspace, workspaces;
|
|
18542
18648
|
var init_specs2 = __esm({
|
|
18543
18649
|
"src/core/specs.ts"() {
|
|
18544
18650
|
"use strict";
|
|
@@ -18556,6 +18662,36 @@ var init_specs2 = __esm({
|
|
|
18556
18662
|
init_diagram();
|
|
18557
18663
|
NO_ROOT_SUBSYSTEMS = /* @__PURE__ */ new Set();
|
|
18558
18664
|
SIGNATURE_TTL_MS = 2e3;
|
|
18665
|
+
STEP_TYPE_FIELDS = {
|
|
18666
|
+
local: [],
|
|
18667
|
+
call: ["targetComponent", "targetMethod", "auth", "detach"],
|
|
18668
|
+
register: ["targetComponent", "targetMethod"],
|
|
18669
|
+
dispatch: ["targetComponent", "capability", "auth", "detach"],
|
|
18670
|
+
branch: ["condition", "onTrueStep", "onFalseStep"],
|
|
18671
|
+
switch: ["on", "cases", "defaultStep"],
|
|
18672
|
+
loop: ["loopKind", "over", "condition", "endStep"],
|
|
18673
|
+
try: ["endStep", "catches", "finallyStep"],
|
|
18674
|
+
parallel: ["endStep", "branches"],
|
|
18675
|
+
jump: ["toStep"],
|
|
18676
|
+
return: ["outcome"],
|
|
18677
|
+
throw: ["error"]
|
|
18678
|
+
};
|
|
18679
|
+
STEP_COMMON_FIELDS = [
|
|
18680
|
+
"stepNumber",
|
|
18681
|
+
"label",
|
|
18682
|
+
"description",
|
|
18683
|
+
"type",
|
|
18684
|
+
"assertsGuarantees",
|
|
18685
|
+
"assertsInvariants"
|
|
18686
|
+
];
|
|
18687
|
+
STEP_LABEL_TWINS = {
|
|
18688
|
+
onTrueStep: "onTrueLabel",
|
|
18689
|
+
onFalseStep: "onFalseLabel",
|
|
18690
|
+
defaultStep: "defaultLabel",
|
|
18691
|
+
endStep: "endLabel",
|
|
18692
|
+
finallyStep: "finallyLabel",
|
|
18693
|
+
toStep: "toLabel"
|
|
18694
|
+
};
|
|
18559
18695
|
SpecWorkspace = class {
|
|
18560
18696
|
constructor(rootDir) {
|
|
18561
18697
|
this.cachedIndex = null;
|
|
@@ -19793,9 +19929,39 @@ var init_specs2 = __esm({
|
|
|
19793
19929
|
// Granular delta updates (sdd_update_spec)
|
|
19794
19930
|
// -------------------------------------------------------------------------
|
|
19795
19931
|
/**
|
|
19796
|
-
*
|
|
19797
|
-
*
|
|
19798
|
-
*
|
|
19932
|
+
* Apply a delta to one stored spec, in this ORDER — the order is the contract,
|
|
19933
|
+
* because every stage below can see what the stage above left:
|
|
19934
|
+
*
|
|
19935
|
+
* 1. LOAD the stored spec for the addressed kind and id; a spec that does
|
|
19936
|
+
* not exist refuses, and a mis-selected `system` kind refuses by name.
|
|
19937
|
+
* 2. REFUSE unknown delta keys, whole. A key the level's schema does not
|
|
19938
|
+
* know is dropped on write, so the edit would report a success it never
|
|
19939
|
+
* performed.
|
|
19940
|
+
* 3. QUALIFY the delta's id-bearing references against the spec's namespace,
|
|
19941
|
+
* BEFORE the merge — post-merge a bare id is ambiguous.
|
|
19942
|
+
* 4. MERGE. Identity-keyed arrays upsert element by element and honour their
|
|
19943
|
+
* delete markers, at every depth. Narrative step deltas apply in ASCENDING
|
|
19944
|
+
* stepNumber order, each against the numbering the earlier entries of the
|
|
19945
|
+
* same delta left behind — delete step 3 and step 7 becomes step 6, so a
|
|
19946
|
+
* second delete written as 7 addresses what used to be step 8. That is why
|
|
19947
|
+
* a delete may restate the step's `label` or `description` as a guard, and
|
|
19948
|
+
* why labels are the reliable way to name a jump target. Inserts and
|
|
19949
|
+
* deletes relocate every jump field in the same narrative around them; a
|
|
19950
|
+
* step whose `type` changes is REBUILT for its new type, keeping its
|
|
19951
|
+
* description and label.
|
|
19952
|
+
* 5. UNSET the fields the delta names, at the level each `unset` sits in.
|
|
19953
|
+
* 6. RESOLVE symbolic `*Label` references against the MERGED numbering — after
|
|
19954
|
+
* the merge, so a delta may anchor on a label that only pre-existing steps
|
|
19955
|
+
* carry, and a label in the delta retargets the stored numeric jump it
|
|
19956
|
+
* twins. An unresolved reference aborts the whole update.
|
|
19957
|
+
* 7. COMPARE with what is stored, both read through the level's canonical
|
|
19958
|
+
* schema. Equal means nothing is written and `written` is false.
|
|
19959
|
+
* 8. GATE the merged spec through the caller's hook, then SAVE.
|
|
19960
|
+
*
|
|
19961
|
+
* Returns the change report, whose `notices` carry the non-fatal effects (an
|
|
19962
|
+
* insert whose position was a jump target, so the relocated jumps now bypass
|
|
19963
|
+
* the inserted step; the fields a retype dropped) — empty when the merge had
|
|
19964
|
+
* nothing to say.
|
|
19799
19965
|
*/
|
|
19800
19966
|
updateSpec(kind, id, delta, hooks) {
|
|
19801
19967
|
const notices = [];
|
|
@@ -19873,6 +20039,14 @@ var init_specs2 = __esm({
|
|
|
19873
20039
|
return refs;
|
|
19874
20040
|
};
|
|
19875
20041
|
const normalizeIdentity = (k) => String(k ?? "").toLowerCase().replace(/[_\-\s]/g, "");
|
|
20042
|
+
const unsetFieldsOf = (item) => Array.isArray(item?.unset) ? item.unset.filter((f) => typeof f === "string") : [];
|
|
20043
|
+
const withUnset = (merged, deltaItem) => {
|
|
20044
|
+
const fields = unsetFieldsOf(deltaItem);
|
|
20045
|
+
const out = { ...merged };
|
|
20046
|
+
delete out.unset;
|
|
20047
|
+
for (const field of fields) delete out[field];
|
|
20048
|
+
return out;
|
|
20049
|
+
};
|
|
19876
20050
|
const assertDeltaMarkers = (item, label) => {
|
|
19877
20051
|
if (!item || typeof item !== "object") return;
|
|
19878
20052
|
if ("remove" in item && typeof item.remove !== "boolean") {
|
|
@@ -19901,14 +20075,113 @@ var init_specs2 = __esm({
|
|
|
19901
20075
|
);
|
|
19902
20076
|
}
|
|
19903
20077
|
};
|
|
19904
|
-
const
|
|
20078
|
+
const NO_NESTED_MERGE = /* @__PURE__ */ new Set(["ext", "narrative", "unset"]);
|
|
20079
|
+
const mergeElement = (existing, deltaItem) => {
|
|
20080
|
+
const out = { ...existing, ...deltaItem };
|
|
20081
|
+
if (!existing || typeof existing !== "object" || !deltaItem || typeof deltaItem !== "object") return out;
|
|
20082
|
+
for (const [key, value] of Object.entries(deltaItem)) {
|
|
20083
|
+
if (NO_NESTED_MERGE.has(key) || !Array.isArray(value) || value.length === 0) continue;
|
|
20084
|
+
const before = existing[key];
|
|
20085
|
+
if (before !== void 0 && !Array.isArray(before)) continue;
|
|
20086
|
+
const stored = Array.isArray(before) ? before : [];
|
|
20087
|
+
if (!isIdentifiedArray(key, stored, value)) continue;
|
|
20088
|
+
out[key] = mergeIdentifiedArray(key, stored, value);
|
|
20089
|
+
}
|
|
20090
|
+
return out;
|
|
20091
|
+
};
|
|
20092
|
+
const retargetByLabel = (merged, deltaStep) => {
|
|
20093
|
+
const out = { ...merged };
|
|
20094
|
+
for (const [stepField, labelField] of Object.entries(STEP_LABEL_TWINS)) {
|
|
20095
|
+
if (deltaStep?.[labelField] !== void 0 && deltaStep?.[stepField] === void 0) delete out[stepField];
|
|
20096
|
+
}
|
|
20097
|
+
for (const key of ["cases", "catches"]) {
|
|
20098
|
+
const deltaEntries = deltaStep?.[key];
|
|
20099
|
+
if (!Array.isArray(deltaEntries) || !Array.isArray(out[key])) continue;
|
|
20100
|
+
const retargeted = new Set(
|
|
20101
|
+
deltaEntries.filter((e) => e?.label !== void 0 && e?.step === void 0).map((e) => identityKeyOf(key, e)).filter((k) => k !== null)
|
|
20102
|
+
);
|
|
20103
|
+
if (!retargeted.size) continue;
|
|
20104
|
+
out[key] = out[key].map((e) => {
|
|
20105
|
+
const k = identityKeyOf(key, e);
|
|
20106
|
+
if (k === null || !retargeted.has(k)) return e;
|
|
20107
|
+
const { step, ...rest } = e;
|
|
20108
|
+
return rest;
|
|
20109
|
+
});
|
|
20110
|
+
}
|
|
20111
|
+
return out;
|
|
20112
|
+
};
|
|
20113
|
+
const rebuildOnRetype = (stored, deltaStep, merged, methodName) => {
|
|
20114
|
+
const newType = merged?.type;
|
|
20115
|
+
if (typeof newType !== "string" || stored?.type === newType) return merged;
|
|
20116
|
+
if (!Object.prototype.hasOwnProperty.call(STEP_TYPE_FIELDS, newType)) return merged;
|
|
20117
|
+
const allowed = stepFieldsFor(newType);
|
|
20118
|
+
const stated = Object.keys(deltaStep).filter(
|
|
20119
|
+
(k) => k !== "type" && deltaStep[k] !== void 0 && !allowed.has(k)
|
|
20120
|
+
);
|
|
20121
|
+
if (stated.length) {
|
|
20122
|
+
throw new Error(
|
|
20123
|
+
`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.`
|
|
20124
|
+
);
|
|
20125
|
+
}
|
|
20126
|
+
const out = {};
|
|
20127
|
+
const dropped = [];
|
|
20128
|
+
for (const [key, value] of Object.entries(merged)) {
|
|
20129
|
+
if (allowed.has(key)) out[key] = value;
|
|
20130
|
+
else dropped.push(key);
|
|
20131
|
+
}
|
|
20132
|
+
if (dropped.length) {
|
|
20133
|
+
notices.push(
|
|
20134
|
+
`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.`
|
|
20135
|
+
);
|
|
20136
|
+
}
|
|
20137
|
+
return out;
|
|
20138
|
+
};
|
|
20139
|
+
const mergeNarrative = (methodName, existingSteps, deltaSteps) => {
|
|
19905
20140
|
let steps = [...existingSteps];
|
|
19906
20141
|
const sortedDeltas = [...deltaSteps].sort((a, b) => a.stepNumber - b.stepNumber);
|
|
19907
20142
|
for (const deltaStep of sortedDeltas) {
|
|
19908
20143
|
const stepNum = deltaStep.stepNumber;
|
|
20144
|
+
if (typeof stepNum !== "number") {
|
|
20145
|
+
throw new Error(
|
|
20146
|
+
`Refusing to update the narrative of "${methodName}": a step delta must carry the "stepNumber" it addresses, got ${JSON.stringify(stepNum ?? null)}.`
|
|
20147
|
+
);
|
|
20148
|
+
}
|
|
20149
|
+
if ("remove" in deltaStep && typeof deltaStep.remove !== "boolean") {
|
|
20150
|
+
throw new Error(
|
|
20151
|
+
`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.`
|
|
20152
|
+
);
|
|
20153
|
+
}
|
|
20154
|
+
if ("action" in deltaStep && deltaStep.action !== "insert" && deltaStep.action !== "delete") {
|
|
20155
|
+
throw new Error(
|
|
20156
|
+
`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.`
|
|
20157
|
+
);
|
|
20158
|
+
}
|
|
20159
|
+
if ("captureJumps" in deltaStep && deltaStep.action !== "insert") {
|
|
20160
|
+
throw new Error(
|
|
20161
|
+
`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.`
|
|
20162
|
+
);
|
|
20163
|
+
}
|
|
19909
20164
|
if (deltaStep.action === "delete" || deltaStep.remove === true) {
|
|
19910
20165
|
const idx = steps.findIndex((s) => s.stepNumber === stepNum);
|
|
19911
20166
|
if (idx !== -1) {
|
|
20167
|
+
const victim = steps[idx];
|
|
20168
|
+
for (const field of ["label", "description"]) {
|
|
20169
|
+
if (deltaStep[field] === void 0) continue;
|
|
20170
|
+
if (victim[field] !== deltaStep[field]) {
|
|
20171
|
+
throw new Error(
|
|
20172
|
+
`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.`
|
|
20173
|
+
);
|
|
20174
|
+
}
|
|
20175
|
+
}
|
|
20176
|
+
if (typeof victim.endStep === "number") {
|
|
20177
|
+
const body = steps.filter((s) => s.stepNumber > stepNum && s.stepNumber <= victim.endStep);
|
|
20178
|
+
if (body.length) {
|
|
20179
|
+
const span = body.length === 1 ? `step ${body[0].stepNumber}` : `steps ${body[0].stepNumber}-${body[body.length - 1].stepNumber}`;
|
|
20180
|
+
throw new Error(
|
|
20181
|
+
`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.`
|
|
20182
|
+
);
|
|
20183
|
+
}
|
|
20184
|
+
}
|
|
19912
20185
|
const referrers = steps.filter((s) => s.stepNumber !== stepNum).map((s) => ({ n: s.stepNumber, refs: jumpRefsTo(s, stepNum) })).filter((r) => r.refs.length > 0);
|
|
19913
20186
|
if (referrers.length) {
|
|
19914
20187
|
throw new Error(
|
|
@@ -19921,6 +20194,11 @@ var init_specs2 = __esm({
|
|
|
19921
20194
|
stepNum,
|
|
19922
20195
|
-1
|
|
19923
20196
|
));
|
|
20197
|
+
} else {
|
|
20198
|
+
const present = steps.length ? ` \u2014 it has steps ${steps[0].stepNumber}-${steps[steps.length - 1].stepNumber}` : " \u2014 it has no steps";
|
|
20199
|
+
throw new Error(
|
|
20200
|
+
`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.`
|
|
20201
|
+
);
|
|
19924
20202
|
}
|
|
19925
20203
|
} else if (deltaStep.action === "insert") {
|
|
19926
20204
|
const capture = deltaStep.captureJumps === true;
|
|
@@ -19939,18 +20217,17 @@ var init_specs2 = __esm({
|
|
|
19939
20217
|
capture
|
|
19940
20218
|
));
|
|
19941
20219
|
const { action, remove, captureJumps, ...cleanStep } = deltaStep;
|
|
19942
|
-
steps.push(cleanStep);
|
|
20220
|
+
steps.push(withUnset(cleanStep, deltaStep));
|
|
19943
20221
|
} else {
|
|
19944
20222
|
const idx = steps.findIndex((s) => s.stepNumber === stepNum);
|
|
19945
20223
|
if (idx !== -1) {
|
|
19946
20224
|
const { action, remove, ...cleanStep } = deltaStep;
|
|
19947
|
-
steps[idx]
|
|
19948
|
-
|
|
19949
|
-
|
|
19950
|
-
};
|
|
20225
|
+
const stored = steps[idx];
|
|
20226
|
+
const merged = withUnset(retargetByLabel(mergeElement(stored, cleanStep), cleanStep), deltaStep);
|
|
20227
|
+
steps[idx] = rebuildOnRetype(stored, cleanStep, merged, methodName);
|
|
19951
20228
|
} else {
|
|
19952
20229
|
const { action, remove, ...cleanStep } = deltaStep;
|
|
19953
|
-
steps.push(cleanStep);
|
|
20230
|
+
steps.push(withUnset(cleanStep, deltaStep));
|
|
19954
20231
|
}
|
|
19955
20232
|
}
|
|
19956
20233
|
}
|
|
@@ -19977,20 +20254,19 @@ var init_specs2 = __esm({
|
|
|
19977
20254
|
} else {
|
|
19978
20255
|
const existingMethod = merged[idx];
|
|
19979
20256
|
let narrative = existingMethod.narrative ? [...existingMethod.narrative] : [];
|
|
19980
|
-
if (
|
|
19981
|
-
narrative = mergeNarrative(narrative, deltaMethod.narrative);
|
|
20257
|
+
if (Array.isArray(deltaMethod.narrative)) {
|
|
20258
|
+
narrative = deltaMethod.narrative.length === 0 ? [] : mergeNarrative(String(deltaMethod.name), narrative, deltaMethod.narrative);
|
|
19982
20259
|
}
|
|
19983
20260
|
const { narrative: _, ...cleanMethod } = deltaMethod;
|
|
19984
|
-
merged[idx] = {
|
|
19985
|
-
...existingMethod,
|
|
19986
|
-
...cleanMethod,
|
|
20261
|
+
merged[idx] = withUnset({
|
|
20262
|
+
...mergeElement(existingMethod, cleanMethod),
|
|
19987
20263
|
...deltaMethod.ext !== void 0 ? { ext: mergeExt(existingMethod.ext, deltaMethod.ext) } : {},
|
|
19988
20264
|
narrative
|
|
19989
|
-
};
|
|
20265
|
+
}, deltaMethod);
|
|
19990
20266
|
}
|
|
19991
20267
|
} else {
|
|
19992
20268
|
assertUnmatchedIsAnAddition(deltaMethod, deltaMethod.name, merged.map((m) => m.name), "method");
|
|
19993
|
-
merged.push(deltaMethod);
|
|
20269
|
+
merged.push(withUnset(deltaMethod, deltaMethod));
|
|
19994
20270
|
}
|
|
19995
20271
|
}
|
|
19996
20272
|
return merged;
|
|
@@ -20004,18 +20280,14 @@ var init_specs2 = __esm({
|
|
|
20004
20280
|
if (deltaItem.remove === true || deltaItem.action === "delete") {
|
|
20005
20281
|
merged.splice(idx, 1);
|
|
20006
20282
|
} else {
|
|
20007
|
-
merged[idx] = {
|
|
20008
|
-
...merged[idx],
|
|
20009
|
-
...deltaItem,
|
|
20010
|
-
|
|
20011
|
-
// An interface method's findings upsert and delete by code, like
|
|
20012
|
-
// lint.allow; an empty list still clears them outright.
|
|
20013
|
-
...Array.isArray(deltaItem.findings) && deltaItem.findings.length > 0 ? { findings: mergeIdentifiedArray("findings", merged[idx].findings ?? [], deltaItem.findings) } : {}
|
|
20014
|
-
};
|
|
20283
|
+
merged[idx] = withUnset({
|
|
20284
|
+
...mergeElement(merged[idx], deltaItem),
|
|
20285
|
+
...deltaItem.ext !== void 0 ? { ext: mergeExt(merged[idx].ext, deltaItem.ext) } : {}
|
|
20286
|
+
}, deltaItem);
|
|
20015
20287
|
}
|
|
20016
20288
|
} else {
|
|
20017
20289
|
assertUnmatchedIsAnAddition(deltaItem, deltaItem.name, merged.map((i) => i.name), "entry");
|
|
20018
|
-
merged.push(deltaItem);
|
|
20290
|
+
merged.push(withUnset(deltaItem, deltaItem));
|
|
20019
20291
|
}
|
|
20020
20292
|
}
|
|
20021
20293
|
return merged;
|
|
@@ -20029,11 +20301,11 @@ var init_specs2 = __esm({
|
|
|
20029
20301
|
if (deltaItem.remove === true || deltaItem.action === "delete") {
|
|
20030
20302
|
merged.splice(idx, 1);
|
|
20031
20303
|
} else {
|
|
20032
|
-
merged[idx] =
|
|
20304
|
+
merged[idx] = withUnset(mergeElement(merged[idx], deltaItem), deltaItem);
|
|
20033
20305
|
}
|
|
20034
20306
|
} else {
|
|
20035
20307
|
assertUnmatchedIsAnAddition(deltaItem, keyOf(deltaItem), merged.map(keyOf), "entry");
|
|
20036
|
-
merged.push(deltaItem);
|
|
20308
|
+
merged.push(withUnset(deltaItem, deltaItem));
|
|
20037
20309
|
}
|
|
20038
20310
|
}
|
|
20039
20311
|
return merged.map(({ action, remove, ...item }) => item);
|
|
@@ -20048,52 +20320,15 @@ var init_specs2 = __esm({
|
|
|
20048
20320
|
if (deltaItem.remove === true || deltaItem.action === "delete") {
|
|
20049
20321
|
merged.splice(idx, 1);
|
|
20050
20322
|
} else {
|
|
20051
|
-
merged[idx] =
|
|
20052
|
-
...merged[idx],
|
|
20053
|
-
...deltaItem
|
|
20054
|
-
};
|
|
20323
|
+
merged[idx] = withUnset(mergeElement(merged[idx], deltaItem), deltaItem);
|
|
20055
20324
|
}
|
|
20056
20325
|
} else {
|
|
20057
20326
|
assertUnmatchedIsAnAddition(deltaItem, piKey(deltaItem), merged.map(piKey), "publicInterface");
|
|
20058
|
-
merged.push(deltaItem);
|
|
20327
|
+
merged.push(withUnset(deltaItem, deltaItem));
|
|
20059
20328
|
}
|
|
20060
20329
|
}
|
|
20061
20330
|
return merged;
|
|
20062
20331
|
};
|
|
20063
|
-
const identityKeyOf = (field, item) => {
|
|
20064
|
-
if (item === null || typeof item !== "object") return null;
|
|
20065
|
-
const o = item;
|
|
20066
|
-
const str = (v) => typeof v === "string" && v.length > 0 ? v : null;
|
|
20067
|
-
switch (field) {
|
|
20068
|
-
case "dispatch":
|
|
20069
|
-
return str(o.capability);
|
|
20070
|
-
case "lifecycle":
|
|
20071
|
-
return `${String(o.phase)} ${String(o.component)} ${String(o.method)}`;
|
|
20072
|
-
case "emits":
|
|
20073
|
-
case "subscribesTo":
|
|
20074
|
-
return `${String(o.topic)} ${o.event === void 0 ? "" : String(o.event)}`;
|
|
20075
|
-
case "trustedLinks":
|
|
20076
|
-
return str(o.subsystem);
|
|
20077
|
-
case "allow":
|
|
20078
|
-
return str(o.code);
|
|
20079
|
-
// lint.allow
|
|
20080
|
-
case "findings":
|
|
20081
|
-
return str(o.code);
|
|
20082
|
-
// an interface method's findings
|
|
20083
|
-
case "invariants":
|
|
20084
|
-
return str(o.id);
|
|
20085
|
-
case "patterns":
|
|
20086
|
-
return str(o.id);
|
|
20087
|
-
case "boundaries":
|
|
20088
|
-
return str(o.name);
|
|
20089
|
-
case "globalRequirements":
|
|
20090
|
-
return str(o.description);
|
|
20091
|
-
case "databases":
|
|
20092
|
-
return str(o.id) ?? str(o.name);
|
|
20093
|
-
default:
|
|
20094
|
-
return str(o.name) ?? str(o.id);
|
|
20095
|
-
}
|
|
20096
|
-
};
|
|
20097
20332
|
const mergeIdentifiedArray = (field, existing, delta2) => {
|
|
20098
20333
|
const merged = [...existing];
|
|
20099
20334
|
for (const deltaItem of delta2) {
|
|
@@ -20103,7 +20338,7 @@ var init_specs2 = __esm({
|
|
|
20103
20338
|
const isDelete = deltaItem?.remove === true || deltaItem?.action === "delete";
|
|
20104
20339
|
if (idx !== -1) {
|
|
20105
20340
|
if (isDelete) merged.splice(idx, 1);
|
|
20106
|
-
else merged[idx] =
|
|
20341
|
+
else merged[idx] = withUnset(mergeElement(merged[idx], deltaItem), deltaItem);
|
|
20107
20342
|
} else {
|
|
20108
20343
|
if (key !== null) {
|
|
20109
20344
|
assertUnmatchedIsAnAddition(
|
|
@@ -20115,7 +20350,7 @@ var init_specs2 = __esm({
|
|
|
20115
20350
|
} else if (isDelete) {
|
|
20116
20351
|
throw new Error(`Refusing to delete a ${field} entry with no resolvable identity.`);
|
|
20117
20352
|
}
|
|
20118
|
-
merged.push(deltaItem);
|
|
20353
|
+
merged.push(withUnset(deltaItem, deltaItem));
|
|
20119
20354
|
}
|
|
20120
20355
|
}
|
|
20121
20356
|
return merged.map((item) => item && typeof item === "object" ? (({ action, remove, ...rest }) => rest)(item) : item);
|
|
@@ -20124,6 +20359,7 @@ var init_specs2 = __esm({
|
|
|
20124
20359
|
const mergeDelta = (existing, delta2) => {
|
|
20125
20360
|
const res = { ...existing };
|
|
20126
20361
|
for (const [key, value] of Object.entries(delta2)) {
|
|
20362
|
+
if (key === "unset") continue;
|
|
20127
20363
|
if (value === void 0 || value === null) {
|
|
20128
20364
|
continue;
|
|
20129
20365
|
}
|
|
@@ -20151,7 +20387,7 @@ var init_specs2 = __esm({
|
|
|
20151
20387
|
res[key] = value;
|
|
20152
20388
|
}
|
|
20153
20389
|
}
|
|
20154
|
-
return res;
|
|
20390
|
+
return withUnset(res, delta2);
|
|
20155
20391
|
};
|
|
20156
20392
|
const qualifyDeltaRefs = (d) => {
|
|
20157
20393
|
if (kind === "system") return d;
|
|
@@ -20205,9 +20441,9 @@ var init_specs2 = __esm({
|
|
|
20205
20441
|
};
|
|
20206
20442
|
const unsetFields = Array.isArray(delta.unset) ? delta.unset.filter((f) => typeof f === "string") : [];
|
|
20207
20443
|
const { unset: _unset, ...mergeableDelta } = delta;
|
|
20444
|
+
const storedBefore = JSON.parse(JSON.stringify(result));
|
|
20208
20445
|
const mergedResult = mergeDelta(result, qualifyDeltaRefs(mergeableDelta));
|
|
20209
20446
|
for (const field of unsetFields) delete mergedResult[field];
|
|
20210
|
-
mergedResult.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
20211
20447
|
if (kind === "implementation" && Array.isArray(mergedResult.methods)) {
|
|
20212
20448
|
const labelErrors = [];
|
|
20213
20449
|
for (const m of mergedResult.methods) {
|
|
@@ -20218,6 +20454,24 @@ var init_specs2 = __esm({
|
|
|
20218
20454
|
- ${labelErrors.join("\n- ")}`);
|
|
20219
20455
|
}
|
|
20220
20456
|
}
|
|
20457
|
+
const canonical = (spec) => {
|
|
20458
|
+
const parsed = deltaSchema.safeParse(spec);
|
|
20459
|
+
const value = { ...parsed.success ? parsed.data : spec };
|
|
20460
|
+
delete value.updatedAt;
|
|
20461
|
+
return value;
|
|
20462
|
+
};
|
|
20463
|
+
const changes = specChanges(canonical(storedBefore), canonical(mergedResult));
|
|
20464
|
+
if (changes.length === 0) {
|
|
20465
|
+
return {
|
|
20466
|
+
kind,
|
|
20467
|
+
id,
|
|
20468
|
+
written: false,
|
|
20469
|
+
changes,
|
|
20470
|
+
notices,
|
|
20471
|
+
summary: `No change to ${kind} "${id}" \u2014 the delta matches what is stored, so nothing was written.`
|
|
20472
|
+
};
|
|
20473
|
+
}
|
|
20474
|
+
mergedResult.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
20221
20475
|
const gateNotices = hooks?.gate?.(kind, mergedResult);
|
|
20222
20476
|
if (gateNotices?.length) notices.push(...gateNotices);
|
|
20223
20477
|
const opts = {
|
|
@@ -20243,7 +20497,14 @@ var init_specs2 = __esm({
|
|
|
20243
20497
|
notices.push(...this.saveTypeSpec(mergedResult));
|
|
20244
20498
|
break;
|
|
20245
20499
|
}
|
|
20246
|
-
return
|
|
20500
|
+
return {
|
|
20501
|
+
kind,
|
|
20502
|
+
id,
|
|
20503
|
+
written: true,
|
|
20504
|
+
changes,
|
|
20505
|
+
notices,
|
|
20506
|
+
summary: `Updated ${kind} "${id}": ${changes.length} change${changes.length === 1 ? "" : "s"}.`
|
|
20507
|
+
};
|
|
20247
20508
|
}
|
|
20248
20509
|
};
|
|
20249
20510
|
workspaces = /* @__PURE__ */ new Map();
|
|
@@ -26084,6 +26345,7 @@ __export(src_exports, {
|
|
|
26084
26345
|
skillExtendErrors: () => skillExtendErrors,
|
|
26085
26346
|
skillsDirForTarget: () => skillsDirForTarget,
|
|
26086
26347
|
snapshotSpecFiles: () => snapshotSpecFiles,
|
|
26348
|
+
specChanges: () => specChanges,
|
|
26087
26349
|
specPathsInScope: () => specPathsInScope,
|
|
26088
26350
|
splitNamespace: () => splitNamespace,
|
|
26089
26351
|
stateIdEquals: () => stateIdEquals,
|
|
@@ -26804,6 +27066,7 @@ function loadProjectConfig4() {
|
|
|
26804
27066
|
skillExtendErrors,
|
|
26805
27067
|
skillsDirForTarget,
|
|
26806
27068
|
snapshotSpecFiles,
|
|
27069
|
+
specChanges,
|
|
26807
27070
|
specPathsInScope,
|
|
26808
27071
|
splitNamespace,
|
|
26809
27072
|
stateIdEquals,
|