@wairon/cli 5.1.1-dev.34 → 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/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.34";
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";
@@ -18345,10 +18345,30 @@ function identityKeyOf(field, item) {
18345
18345
  return str(o.description);
18346
18346
  case "databases":
18347
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);
18348
18359
  default:
18349
18360
  return str(o.name) ?? str(o.id);
18350
18361
  }
18351
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
+ }
18352
18372
  function reportIdentityOf(field, item) {
18353
18373
  if (item !== null && typeof item === "object") {
18354
18374
  const step = item.stepNumber;
@@ -18624,7 +18644,7 @@ function findLegacySpecFiles() {
18624
18644
  function updateSpec(kind, id, delta, hooks) {
18625
18645
  return current().updateSpec(kind, id, delta, hooks);
18626
18646
  }
18627
- 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;
18628
18648
  var init_specs2 = __esm({
18629
18649
  "src/core/specs.ts"() {
18630
18650
  "use strict";
@@ -18642,6 +18662,36 @@ var init_specs2 = __esm({
18642
18662
  init_diagram();
18643
18663
  NO_ROOT_SUBSYSTEMS = /* @__PURE__ */ new Set();
18644
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
+ };
18645
18695
  SpecWorkspace = class {
18646
18696
  constructor(rootDir) {
18647
18697
  this.cachedIndex = null;
@@ -19879,9 +19929,39 @@ var init_specs2 = __esm({
19879
19929
  // Granular delta updates (sdd_update_spec)
19880
19930
  // -------------------------------------------------------------------------
19881
19931
  /**
19882
- * Returns non-fatal notices about the merge (e.g. an insert whose position
19883
- * was a jump target, so the relocated jumps now bypass the inserted step) —
19884
- * empty when the merge had nothing to warn about.
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.
19885
19965
  */
19886
19966
  updateSpec(kind, id, delta, hooks) {
19887
19967
  const notices = [];
@@ -19995,14 +20075,113 @@ var init_specs2 = __esm({
19995
20075
  );
19996
20076
  }
19997
20077
  };
19998
- const mergeNarrative = (existingSteps, deltaSteps) => {
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) => {
19999
20140
  let steps = [...existingSteps];
20000
20141
  const sortedDeltas = [...deltaSteps].sort((a, b) => a.stepNumber - b.stepNumber);
20001
20142
  for (const deltaStep of sortedDeltas) {
20002
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
+ }
20003
20164
  if (deltaStep.action === "delete" || deltaStep.remove === true) {
20004
20165
  const idx = steps.findIndex((s) => s.stepNumber === stepNum);
20005
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
+ }
20006
20185
  const referrers = steps.filter((s) => s.stepNumber !== stepNum).map((s) => ({ n: s.stepNumber, refs: jumpRefsTo(s, stepNum) })).filter((r) => r.refs.length > 0);
20007
20186
  if (referrers.length) {
20008
20187
  throw new Error(
@@ -20015,6 +20194,11 @@ var init_specs2 = __esm({
20015
20194
  stepNum,
20016
20195
  -1
20017
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
+ );
20018
20202
  }
20019
20203
  } else if (deltaStep.action === "insert") {
20020
20204
  const capture = deltaStep.captureJumps === true;
@@ -20038,10 +20222,9 @@ var init_specs2 = __esm({
20038
20222
  const idx = steps.findIndex((s) => s.stepNumber === stepNum);
20039
20223
  if (idx !== -1) {
20040
20224
  const { action, remove, ...cleanStep } = deltaStep;
20041
- steps[idx] = withUnset({
20042
- ...steps[idx],
20043
- ...cleanStep
20044
- }, deltaStep);
20225
+ const stored = steps[idx];
20226
+ const merged = withUnset(retargetByLabel(mergeElement(stored, cleanStep), cleanStep), deltaStep);
20227
+ steps[idx] = rebuildOnRetype(stored, cleanStep, merged, methodName);
20045
20228
  } else {
20046
20229
  const { action, remove, ...cleanStep } = deltaStep;
20047
20230
  steps.push(withUnset(cleanStep, deltaStep));
@@ -20072,12 +20255,11 @@ var init_specs2 = __esm({
20072
20255
  const existingMethod = merged[idx];
20073
20256
  let narrative = existingMethod.narrative ? [...existingMethod.narrative] : [];
20074
20257
  if (Array.isArray(deltaMethod.narrative)) {
20075
- narrative = deltaMethod.narrative.length === 0 ? [] : mergeNarrative(narrative, deltaMethod.narrative);
20258
+ narrative = deltaMethod.narrative.length === 0 ? [] : mergeNarrative(String(deltaMethod.name), narrative, deltaMethod.narrative);
20076
20259
  }
20077
20260
  const { narrative: _, ...cleanMethod } = deltaMethod;
20078
20261
  merged[idx] = withUnset({
20079
- ...existingMethod,
20080
- ...cleanMethod,
20262
+ ...mergeElement(existingMethod, cleanMethod),
20081
20263
  ...deltaMethod.ext !== void 0 ? { ext: mergeExt(existingMethod.ext, deltaMethod.ext) } : {},
20082
20264
  narrative
20083
20265
  }, deltaMethod);
@@ -20099,12 +20281,8 @@ var init_specs2 = __esm({
20099
20281
  merged.splice(idx, 1);
20100
20282
  } else {
20101
20283
  merged[idx] = withUnset({
20102
- ...merged[idx],
20103
- ...deltaItem,
20104
- ...deltaItem.ext !== void 0 ? { ext: mergeExt(merged[idx].ext, deltaItem.ext) } : {},
20105
- // An interface method's findings upsert and delete by code, like
20106
- // lint.allow; an empty list still clears them outright.
20107
- ...Array.isArray(deltaItem.findings) && deltaItem.findings.length > 0 ? { findings: mergeIdentifiedArray("findings", merged[idx].findings ?? [], deltaItem.findings) } : {}
20284
+ ...mergeElement(merged[idx], deltaItem),
20285
+ ...deltaItem.ext !== void 0 ? { ext: mergeExt(merged[idx].ext, deltaItem.ext) } : {}
20108
20286
  }, deltaItem);
20109
20287
  }
20110
20288
  } else {
@@ -20123,7 +20301,7 @@ var init_specs2 = __esm({
20123
20301
  if (deltaItem.remove === true || deltaItem.action === "delete") {
20124
20302
  merged.splice(idx, 1);
20125
20303
  } else {
20126
- merged[idx] = withUnset({ ...merged[idx], ...deltaItem }, deltaItem);
20304
+ merged[idx] = withUnset(mergeElement(merged[idx], deltaItem), deltaItem);
20127
20305
  }
20128
20306
  } else {
20129
20307
  assertUnmatchedIsAnAddition(deltaItem, keyOf(deltaItem), merged.map(keyOf), "entry");
@@ -20142,10 +20320,7 @@ var init_specs2 = __esm({
20142
20320
  if (deltaItem.remove === true || deltaItem.action === "delete") {
20143
20321
  merged.splice(idx, 1);
20144
20322
  } else {
20145
- merged[idx] = withUnset({
20146
- ...merged[idx],
20147
- ...deltaItem
20148
- }, deltaItem);
20323
+ merged[idx] = withUnset(mergeElement(merged[idx], deltaItem), deltaItem);
20149
20324
  }
20150
20325
  } else {
20151
20326
  assertUnmatchedIsAnAddition(deltaItem, piKey(deltaItem), merged.map(piKey), "publicInterface");
@@ -20163,7 +20338,7 @@ var init_specs2 = __esm({
20163
20338
  const isDelete = deltaItem?.remove === true || deltaItem?.action === "delete";
20164
20339
  if (idx !== -1) {
20165
20340
  if (isDelete) merged.splice(idx, 1);
20166
- else merged[idx] = withUnset({ ...merged[idx], ...deltaItem }, deltaItem);
20341
+ else merged[idx] = withUnset(mergeElement(merged[idx], deltaItem), deltaItem);
20167
20342
  } else {
20168
20343
  if (key !== null) {
20169
20344
  assertUnmatchedIsAnAddition(