@wairon/cli 5.1.1-dev.33 → 5.1.1-dev.34

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.33";
1116
+ WAIRON_VERSION = "5.1.1-dev.34";
1117
1117
  GITHUB_REPO = "SYW-Apps/Waffle-AIron";
1118
1118
  ARCHITECT_AGENT_ID = "agent-architect";
1119
1119
  ARCHITECT_TEMPLATE_ID = "architect";
@@ -18310,6 +18310,92 @@ 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
+ default:
18349
+ return str(o.name) ?? str(o.id);
18350
+ }
18351
+ }
18352
+ function reportIdentityOf(field, item) {
18353
+ if (item !== null && typeof item === "object") {
18354
+ const step = item.stepNumber;
18355
+ if (field === "narrative" && typeof step === "number") return `step ${step}`;
18356
+ }
18357
+ return identityKeyOf(field, item);
18358
+ }
18359
+ function specChanges(before, after, prefix = "") {
18360
+ const at = (key) => prefix ? `${prefix}.${key}` : key;
18361
+ const isPlain = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
18362
+ if (Array.isArray(before) && Array.isArray(after)) {
18363
+ if (after.length === 0 && before.length > 0) {
18364
+ return [{ path: prefix, change: "cleared", before: summarizeValue(before) }];
18365
+ }
18366
+ const identified = [...before, ...after].every((i) => reportIdentityOf(prefix.split(".").pop() ?? prefix, i) !== null);
18367
+ if (!identified) {
18368
+ return JSON.stringify(before) === JSON.stringify(after) ? [] : [{ path: prefix, change: "set", before: summarizeValue(before), after: summarizeValue(after) }];
18369
+ }
18370
+ const field = prefix.split(".").pop() ?? prefix;
18371
+ const keyOf = (i) => String(reportIdentityOf(field, i));
18372
+ const changes = [];
18373
+ for (const item of before) {
18374
+ const match = after.find((a) => keyOf(a) === keyOf(item));
18375
+ if (match === void 0) changes.push({ path: at(keyOf(item)), change: "removed", before: summarizeValue(item) });
18376
+ else changes.push(...specChanges(item, match, at(keyOf(item))));
18377
+ }
18378
+ for (const item of after) {
18379
+ if (!before.some((b) => keyOf(b) === keyOf(item))) {
18380
+ changes.push({ path: at(keyOf(item)), change: "added", after: summarizeValue(item) });
18381
+ }
18382
+ }
18383
+ return changes;
18384
+ }
18385
+ if (isPlain(before) && isPlain(after)) {
18386
+ const changes = [];
18387
+ for (const key of /* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)])) {
18388
+ const b = before[key];
18389
+ const a = after[key];
18390
+ if (b === void 0 && a !== void 0) changes.push({ path: at(key), change: "added", after: summarizeValue(a) });
18391
+ else if (b !== void 0 && a === void 0) changes.push({ path: at(key), change: "removed", before: summarizeValue(b) });
18392
+ else changes.push(...specChanges(b, a, at(key)));
18393
+ }
18394
+ return changes;
18395
+ }
18396
+ if (JSON.stringify(before) === JSON.stringify(after)) return [];
18397
+ return [{ path: prefix, change: "set", before: summarizeValue(before), after: summarizeValue(after) }];
18398
+ }
18313
18399
  function workspaceFor(rootDir) {
18314
18400
  const key = path16.resolve(rootDir);
18315
18401
  let ws = workspaces.get(key);
@@ -19873,6 +19959,14 @@ var init_specs2 = __esm({
19873
19959
  return refs;
19874
19960
  };
19875
19961
  const normalizeIdentity = (k) => String(k ?? "").toLowerCase().replace(/[_\-\s]/g, "");
19962
+ const unsetFieldsOf = (item) => Array.isArray(item?.unset) ? item.unset.filter((f) => typeof f === "string") : [];
19963
+ const withUnset = (merged, deltaItem) => {
19964
+ const fields = unsetFieldsOf(deltaItem);
19965
+ const out = { ...merged };
19966
+ delete out.unset;
19967
+ for (const field of fields) delete out[field];
19968
+ return out;
19969
+ };
19876
19970
  const assertDeltaMarkers = (item, label) => {
19877
19971
  if (!item || typeof item !== "object") return;
19878
19972
  if ("remove" in item && typeof item.remove !== "boolean") {
@@ -19939,18 +20033,18 @@ var init_specs2 = __esm({
19939
20033
  capture
19940
20034
  ));
19941
20035
  const { action, remove, captureJumps, ...cleanStep } = deltaStep;
19942
- steps.push(cleanStep);
20036
+ steps.push(withUnset(cleanStep, deltaStep));
19943
20037
  } else {
19944
20038
  const idx = steps.findIndex((s) => s.stepNumber === stepNum);
19945
20039
  if (idx !== -1) {
19946
20040
  const { action, remove, ...cleanStep } = deltaStep;
19947
- steps[idx] = {
20041
+ steps[idx] = withUnset({
19948
20042
  ...steps[idx],
19949
20043
  ...cleanStep
19950
- };
20044
+ }, deltaStep);
19951
20045
  } else {
19952
20046
  const { action, remove, ...cleanStep } = deltaStep;
19953
- steps.push(cleanStep);
20047
+ steps.push(withUnset(cleanStep, deltaStep));
19954
20048
  }
19955
20049
  }
19956
20050
  }
@@ -19977,20 +20071,20 @@ var init_specs2 = __esm({
19977
20071
  } else {
19978
20072
  const existingMethod = merged[idx];
19979
20073
  let narrative = existingMethod.narrative ? [...existingMethod.narrative] : [];
19980
- if (deltaMethod.narrative && Array.isArray(deltaMethod.narrative)) {
19981
- narrative = mergeNarrative(narrative, deltaMethod.narrative);
20074
+ if (Array.isArray(deltaMethod.narrative)) {
20075
+ narrative = deltaMethod.narrative.length === 0 ? [] : mergeNarrative(narrative, deltaMethod.narrative);
19982
20076
  }
19983
20077
  const { narrative: _, ...cleanMethod } = deltaMethod;
19984
- merged[idx] = {
20078
+ merged[idx] = withUnset({
19985
20079
  ...existingMethod,
19986
20080
  ...cleanMethod,
19987
20081
  ...deltaMethod.ext !== void 0 ? { ext: mergeExt(existingMethod.ext, deltaMethod.ext) } : {},
19988
20082
  narrative
19989
- };
20083
+ }, deltaMethod);
19990
20084
  }
19991
20085
  } else {
19992
20086
  assertUnmatchedIsAnAddition(deltaMethod, deltaMethod.name, merged.map((m) => m.name), "method");
19993
- merged.push(deltaMethod);
20087
+ merged.push(withUnset(deltaMethod, deltaMethod));
19994
20088
  }
19995
20089
  }
19996
20090
  return merged;
@@ -20004,18 +20098,18 @@ var init_specs2 = __esm({
20004
20098
  if (deltaItem.remove === true || deltaItem.action === "delete") {
20005
20099
  merged.splice(idx, 1);
20006
20100
  } else {
20007
- merged[idx] = {
20101
+ merged[idx] = withUnset({
20008
20102
  ...merged[idx],
20009
20103
  ...deltaItem,
20010
20104
  ...deltaItem.ext !== void 0 ? { ext: mergeExt(merged[idx].ext, deltaItem.ext) } : {},
20011
20105
  // An interface method's findings upsert and delete by code, like
20012
20106
  // lint.allow; an empty list still clears them outright.
20013
20107
  ...Array.isArray(deltaItem.findings) && deltaItem.findings.length > 0 ? { findings: mergeIdentifiedArray("findings", merged[idx].findings ?? [], deltaItem.findings) } : {}
20014
- };
20108
+ }, deltaItem);
20015
20109
  }
20016
20110
  } else {
20017
20111
  assertUnmatchedIsAnAddition(deltaItem, deltaItem.name, merged.map((i) => i.name), "entry");
20018
- merged.push(deltaItem);
20112
+ merged.push(withUnset(deltaItem, deltaItem));
20019
20113
  }
20020
20114
  }
20021
20115
  return merged;
@@ -20029,11 +20123,11 @@ var init_specs2 = __esm({
20029
20123
  if (deltaItem.remove === true || deltaItem.action === "delete") {
20030
20124
  merged.splice(idx, 1);
20031
20125
  } else {
20032
- merged[idx] = { ...merged[idx], ...deltaItem };
20126
+ merged[idx] = withUnset({ ...merged[idx], ...deltaItem }, deltaItem);
20033
20127
  }
20034
20128
  } else {
20035
20129
  assertUnmatchedIsAnAddition(deltaItem, keyOf(deltaItem), merged.map(keyOf), "entry");
20036
- merged.push(deltaItem);
20130
+ merged.push(withUnset(deltaItem, deltaItem));
20037
20131
  }
20038
20132
  }
20039
20133
  return merged.map(({ action, remove, ...item }) => item);
@@ -20048,52 +20142,18 @@ var init_specs2 = __esm({
20048
20142
  if (deltaItem.remove === true || deltaItem.action === "delete") {
20049
20143
  merged.splice(idx, 1);
20050
20144
  } else {
20051
- merged[idx] = {
20145
+ merged[idx] = withUnset({
20052
20146
  ...merged[idx],
20053
20147
  ...deltaItem
20054
- };
20148
+ }, deltaItem);
20055
20149
  }
20056
20150
  } else {
20057
20151
  assertUnmatchedIsAnAddition(deltaItem, piKey(deltaItem), merged.map(piKey), "publicInterface");
20058
- merged.push(deltaItem);
20152
+ merged.push(withUnset(deltaItem, deltaItem));
20059
20153
  }
20060
20154
  }
20061
20155
  return merged;
20062
20156
  };
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
20157
  const mergeIdentifiedArray = (field, existing, delta2) => {
20098
20158
  const merged = [...existing];
20099
20159
  for (const deltaItem of delta2) {
@@ -20103,7 +20163,7 @@ var init_specs2 = __esm({
20103
20163
  const isDelete = deltaItem?.remove === true || deltaItem?.action === "delete";
20104
20164
  if (idx !== -1) {
20105
20165
  if (isDelete) merged.splice(idx, 1);
20106
- else merged[idx] = { ...merged[idx], ...deltaItem };
20166
+ else merged[idx] = withUnset({ ...merged[idx], ...deltaItem }, deltaItem);
20107
20167
  } else {
20108
20168
  if (key !== null) {
20109
20169
  assertUnmatchedIsAnAddition(
@@ -20115,7 +20175,7 @@ var init_specs2 = __esm({
20115
20175
  } else if (isDelete) {
20116
20176
  throw new Error(`Refusing to delete a ${field} entry with no resolvable identity.`);
20117
20177
  }
20118
- merged.push(deltaItem);
20178
+ merged.push(withUnset(deltaItem, deltaItem));
20119
20179
  }
20120
20180
  }
20121
20181
  return merged.map((item) => item && typeof item === "object" ? (({ action, remove, ...rest }) => rest)(item) : item);
@@ -20124,6 +20184,7 @@ var init_specs2 = __esm({
20124
20184
  const mergeDelta = (existing, delta2) => {
20125
20185
  const res = { ...existing };
20126
20186
  for (const [key, value] of Object.entries(delta2)) {
20187
+ if (key === "unset") continue;
20127
20188
  if (value === void 0 || value === null) {
20128
20189
  continue;
20129
20190
  }
@@ -20151,7 +20212,7 @@ var init_specs2 = __esm({
20151
20212
  res[key] = value;
20152
20213
  }
20153
20214
  }
20154
- return res;
20215
+ return withUnset(res, delta2);
20155
20216
  };
20156
20217
  const qualifyDeltaRefs = (d) => {
20157
20218
  if (kind === "system") return d;
@@ -20205,9 +20266,9 @@ var init_specs2 = __esm({
20205
20266
  };
20206
20267
  const unsetFields = Array.isArray(delta.unset) ? delta.unset.filter((f) => typeof f === "string") : [];
20207
20268
  const { unset: _unset, ...mergeableDelta } = delta;
20269
+ const storedBefore = JSON.parse(JSON.stringify(result));
20208
20270
  const mergedResult = mergeDelta(result, qualifyDeltaRefs(mergeableDelta));
20209
20271
  for (const field of unsetFields) delete mergedResult[field];
20210
- mergedResult.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
20211
20272
  if (kind === "implementation" && Array.isArray(mergedResult.methods)) {
20212
20273
  const labelErrors = [];
20213
20274
  for (const m of mergedResult.methods) {
@@ -20218,6 +20279,24 @@ var init_specs2 = __esm({
20218
20279
  - ${labelErrors.join("\n- ")}`);
20219
20280
  }
20220
20281
  }
20282
+ const canonical = (spec) => {
20283
+ const parsed = deltaSchema.safeParse(spec);
20284
+ const value = { ...parsed.success ? parsed.data : spec };
20285
+ delete value.updatedAt;
20286
+ return value;
20287
+ };
20288
+ const changes = specChanges(canonical(storedBefore), canonical(mergedResult));
20289
+ if (changes.length === 0) {
20290
+ return {
20291
+ kind,
20292
+ id,
20293
+ written: false,
20294
+ changes,
20295
+ notices,
20296
+ summary: `No change to ${kind} "${id}" \u2014 the delta matches what is stored, so nothing was written.`
20297
+ };
20298
+ }
20299
+ mergedResult.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
20221
20300
  const gateNotices = hooks?.gate?.(kind, mergedResult);
20222
20301
  if (gateNotices?.length) notices.push(...gateNotices);
20223
20302
  const opts = {
@@ -20243,7 +20322,14 @@ var init_specs2 = __esm({
20243
20322
  notices.push(...this.saveTypeSpec(mergedResult));
20244
20323
  break;
20245
20324
  }
20246
- return notices;
20325
+ return {
20326
+ kind,
20327
+ id,
20328
+ written: true,
20329
+ changes,
20330
+ notices,
20331
+ summary: `Updated ${kind} "${id}": ${changes.length} change${changes.length === 1 ? "" : "s"}.`
20332
+ };
20247
20333
  }
20248
20334
  };
20249
20335
  workspaces = /* @__PURE__ */ new Map();
@@ -26084,6 +26170,7 @@ __export(src_exports, {
26084
26170
  skillExtendErrors: () => skillExtendErrors,
26085
26171
  skillsDirForTarget: () => skillsDirForTarget,
26086
26172
  snapshotSpecFiles: () => snapshotSpecFiles,
26173
+ specChanges: () => specChanges,
26087
26174
  specPathsInScope: () => specPathsInScope,
26088
26175
  splitNamespace: () => splitNamespace,
26089
26176
  stateIdEquals: () => stateIdEquals,
@@ -26804,6 +26891,7 @@ function loadProjectConfig4() {
26804
26891
  skillExtendErrors,
26805
26892
  skillsDirForTarget,
26806
26893
  snapshotSpecFiles,
26894
+ specChanges,
26807
26895
  specPathsInScope,
26808
26896
  splitNamespace,
26809
26897
  stateIdEquals,