@wairon/cli 5.1.1-dev.32 → 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/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.32";
68
+ WAIRON_VERSION = "5.1.1-dev.34";
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,92 @@ 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
+ default:
23566
+ return str(o.name) ?? str(o.id);
23567
+ }
23568
+ }
23569
+ function reportIdentityOf(field, item) {
23570
+ if (item !== null && typeof item === "object") {
23571
+ const step = item.stepNumber;
23572
+ if (field === "narrative" && typeof step === "number") return `step ${step}`;
23573
+ }
23574
+ return identityKeyOf(field, item);
23575
+ }
23576
+ function specChanges(before, after, prefix = "") {
23577
+ const at = (key) => prefix ? `${prefix}.${key}` : key;
23578
+ const isPlain = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
23579
+ if (Array.isArray(before) && Array.isArray(after)) {
23580
+ if (after.length === 0 && before.length > 0) {
23581
+ return [{ path: prefix, change: "cleared", before: summarizeValue(before) }];
23582
+ }
23583
+ const identified = [...before, ...after].every((i) => reportIdentityOf(prefix.split(".").pop() ?? prefix, i) !== null);
23584
+ if (!identified) {
23585
+ return JSON.stringify(before) === JSON.stringify(after) ? [] : [{ path: prefix, change: "set", before: summarizeValue(before), after: summarizeValue(after) }];
23586
+ }
23587
+ const field = prefix.split(".").pop() ?? prefix;
23588
+ const keyOf = (i) => String(reportIdentityOf(field, i));
23589
+ const changes = [];
23590
+ for (const item of before) {
23591
+ const match = after.find((a) => keyOf(a) === keyOf(item));
23592
+ if (match === void 0) changes.push({ path: at(keyOf(item)), change: "removed", before: summarizeValue(item) });
23593
+ else changes.push(...specChanges(item, match, at(keyOf(item))));
23594
+ }
23595
+ for (const item of after) {
23596
+ if (!before.some((b) => keyOf(b) === keyOf(item))) {
23597
+ changes.push({ path: at(keyOf(item)), change: "added", after: summarizeValue(item) });
23598
+ }
23599
+ }
23600
+ return changes;
23601
+ }
23602
+ if (isPlain(before) && isPlain(after)) {
23603
+ const changes = [];
23604
+ for (const key of /* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)])) {
23605
+ const b = before[key];
23606
+ const a = after[key];
23607
+ if (b === void 0 && a !== void 0) changes.push({ path: at(key), change: "added", after: summarizeValue(a) });
23608
+ else if (b !== void 0 && a === void 0) changes.push({ path: at(key), change: "removed", before: summarizeValue(b) });
23609
+ else changes.push(...specChanges(b, a, at(key)));
23610
+ }
23611
+ return changes;
23612
+ }
23613
+ if (JSON.stringify(before) === JSON.stringify(after)) return [];
23614
+ return [{ path: prefix, change: "set", before: summarizeValue(before), after: summarizeValue(after) }];
23615
+ }
23529
23616
  function workspaceFor(rootDir) {
23530
23617
  const key = path23.resolve(rootDir);
23531
23618
  let ws = workspaces.get(key);
@@ -25089,6 +25176,14 @@ var init_specs2 = __esm({
25089
25176
  return refs;
25090
25177
  };
25091
25178
  const normalizeIdentity = (k) => String(k ?? "").toLowerCase().replace(/[_\-\s]/g, "");
25179
+ const unsetFieldsOf = (item) => Array.isArray(item?.unset) ? item.unset.filter((f) => typeof f === "string") : [];
25180
+ const withUnset = (merged, deltaItem) => {
25181
+ const fields = unsetFieldsOf(deltaItem);
25182
+ const out = { ...merged };
25183
+ delete out.unset;
25184
+ for (const field of fields) delete out[field];
25185
+ return out;
25186
+ };
25092
25187
  const assertDeltaMarkers = (item, label) => {
25093
25188
  if (!item || typeof item !== "object") return;
25094
25189
  if ("remove" in item && typeof item.remove !== "boolean") {
@@ -25155,18 +25250,18 @@ var init_specs2 = __esm({
25155
25250
  capture
25156
25251
  ));
25157
25252
  const { action, remove: remove2, captureJumps, ...cleanStep } = deltaStep;
25158
- steps.push(cleanStep);
25253
+ steps.push(withUnset(cleanStep, deltaStep));
25159
25254
  } else {
25160
25255
  const idx = steps.findIndex((s) => s.stepNumber === stepNum);
25161
25256
  if (idx !== -1) {
25162
25257
  const { action, remove: remove2, ...cleanStep } = deltaStep;
25163
- steps[idx] = {
25258
+ steps[idx] = withUnset({
25164
25259
  ...steps[idx],
25165
25260
  ...cleanStep
25166
- };
25261
+ }, deltaStep);
25167
25262
  } else {
25168
25263
  const { action, remove: remove2, ...cleanStep } = deltaStep;
25169
- steps.push(cleanStep);
25264
+ steps.push(withUnset(cleanStep, deltaStep));
25170
25265
  }
25171
25266
  }
25172
25267
  }
@@ -25193,20 +25288,20 @@ var init_specs2 = __esm({
25193
25288
  } else {
25194
25289
  const existingMethod = merged[idx];
25195
25290
  let narrative = existingMethod.narrative ? [...existingMethod.narrative] : [];
25196
- if (deltaMethod.narrative && Array.isArray(deltaMethod.narrative)) {
25197
- narrative = mergeNarrative(narrative, deltaMethod.narrative);
25291
+ if (Array.isArray(deltaMethod.narrative)) {
25292
+ narrative = deltaMethod.narrative.length === 0 ? [] : mergeNarrative(narrative, deltaMethod.narrative);
25198
25293
  }
25199
25294
  const { narrative: _, ...cleanMethod } = deltaMethod;
25200
- merged[idx] = {
25295
+ merged[idx] = withUnset({
25201
25296
  ...existingMethod,
25202
25297
  ...cleanMethod,
25203
25298
  ...deltaMethod.ext !== void 0 ? { ext: mergeExt(existingMethod.ext, deltaMethod.ext) } : {},
25204
25299
  narrative
25205
- };
25300
+ }, deltaMethod);
25206
25301
  }
25207
25302
  } else {
25208
25303
  assertUnmatchedIsAnAddition(deltaMethod, deltaMethod.name, merged.map((m) => m.name), "method");
25209
- merged.push(deltaMethod);
25304
+ merged.push(withUnset(deltaMethod, deltaMethod));
25210
25305
  }
25211
25306
  }
25212
25307
  return merged;
@@ -25220,18 +25315,18 @@ var init_specs2 = __esm({
25220
25315
  if (deltaItem.remove === true || deltaItem.action === "delete") {
25221
25316
  merged.splice(idx, 1);
25222
25317
  } else {
25223
- merged[idx] = {
25318
+ merged[idx] = withUnset({
25224
25319
  ...merged[idx],
25225
25320
  ...deltaItem,
25226
25321
  ...deltaItem.ext !== void 0 ? { ext: mergeExt(merged[idx].ext, deltaItem.ext) } : {},
25227
25322
  // An interface method's findings upsert and delete by code, like
25228
25323
  // lint.allow; an empty list still clears them outright.
25229
25324
  ...Array.isArray(deltaItem.findings) && deltaItem.findings.length > 0 ? { findings: mergeIdentifiedArray("findings", merged[idx].findings ?? [], deltaItem.findings) } : {}
25230
- };
25325
+ }, deltaItem);
25231
25326
  }
25232
25327
  } else {
25233
25328
  assertUnmatchedIsAnAddition(deltaItem, deltaItem.name, merged.map((i) => i.name), "entry");
25234
- merged.push(deltaItem);
25329
+ merged.push(withUnset(deltaItem, deltaItem));
25235
25330
  }
25236
25331
  }
25237
25332
  return merged;
@@ -25245,11 +25340,11 @@ var init_specs2 = __esm({
25245
25340
  if (deltaItem.remove === true || deltaItem.action === "delete") {
25246
25341
  merged.splice(idx, 1);
25247
25342
  } else {
25248
- merged[idx] = { ...merged[idx], ...deltaItem };
25343
+ merged[idx] = withUnset({ ...merged[idx], ...deltaItem }, deltaItem);
25249
25344
  }
25250
25345
  } else {
25251
25346
  assertUnmatchedIsAnAddition(deltaItem, keyOf(deltaItem), merged.map(keyOf), "entry");
25252
- merged.push(deltaItem);
25347
+ merged.push(withUnset(deltaItem, deltaItem));
25253
25348
  }
25254
25349
  }
25255
25350
  return merged.map(({ action, remove: remove2, ...item }) => item);
@@ -25264,52 +25359,18 @@ var init_specs2 = __esm({
25264
25359
  if (deltaItem.remove === true || deltaItem.action === "delete") {
25265
25360
  merged.splice(idx, 1);
25266
25361
  } else {
25267
- merged[idx] = {
25362
+ merged[idx] = withUnset({
25268
25363
  ...merged[idx],
25269
25364
  ...deltaItem
25270
- };
25365
+ }, deltaItem);
25271
25366
  }
25272
25367
  } else {
25273
25368
  assertUnmatchedIsAnAddition(deltaItem, piKey(deltaItem), merged.map(piKey), "publicInterface");
25274
- merged.push(deltaItem);
25369
+ merged.push(withUnset(deltaItem, deltaItem));
25275
25370
  }
25276
25371
  }
25277
25372
  return merged;
25278
25373
  };
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
25374
  const mergeIdentifiedArray = (field, existing, delta2) => {
25314
25375
  const merged = [...existing];
25315
25376
  for (const deltaItem of delta2) {
@@ -25319,7 +25380,7 @@ var init_specs2 = __esm({
25319
25380
  const isDelete = deltaItem?.remove === true || deltaItem?.action === "delete";
25320
25381
  if (idx !== -1) {
25321
25382
  if (isDelete) merged.splice(idx, 1);
25322
- else merged[idx] = { ...merged[idx], ...deltaItem };
25383
+ else merged[idx] = withUnset({ ...merged[idx], ...deltaItem }, deltaItem);
25323
25384
  } else {
25324
25385
  if (key !== null) {
25325
25386
  assertUnmatchedIsAnAddition(
@@ -25331,7 +25392,7 @@ var init_specs2 = __esm({
25331
25392
  } else if (isDelete) {
25332
25393
  throw new Error(`Refusing to delete a ${field} entry with no resolvable identity.`);
25333
25394
  }
25334
- merged.push(deltaItem);
25395
+ merged.push(withUnset(deltaItem, deltaItem));
25335
25396
  }
25336
25397
  }
25337
25398
  return merged.map((item) => item && typeof item === "object" ? (({ action, remove: remove2, ...rest }) => rest)(item) : item);
@@ -25340,6 +25401,7 @@ var init_specs2 = __esm({
25340
25401
  const mergeDelta = (existing, delta2) => {
25341
25402
  const res = { ...existing };
25342
25403
  for (const [key, value] of Object.entries(delta2)) {
25404
+ if (key === "unset") continue;
25343
25405
  if (value === void 0 || value === null) {
25344
25406
  continue;
25345
25407
  }
@@ -25367,7 +25429,7 @@ var init_specs2 = __esm({
25367
25429
  res[key] = value;
25368
25430
  }
25369
25431
  }
25370
- return res;
25432
+ return withUnset(res, delta2);
25371
25433
  };
25372
25434
  const qualifyDeltaRefs = (d) => {
25373
25435
  if (kind === "system") return d;
@@ -25421,9 +25483,9 @@ var init_specs2 = __esm({
25421
25483
  };
25422
25484
  const unsetFields = Array.isArray(delta.unset) ? delta.unset.filter((f) => typeof f === "string") : [];
25423
25485
  const { unset: _unset, ...mergeableDelta } = delta;
25486
+ const storedBefore = JSON.parse(JSON.stringify(result));
25424
25487
  const mergedResult = mergeDelta(result, qualifyDeltaRefs(mergeableDelta));
25425
25488
  for (const field of unsetFields) delete mergedResult[field];
25426
- mergedResult.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
25427
25489
  if (kind === "implementation" && Array.isArray(mergedResult.methods)) {
25428
25490
  const labelErrors = [];
25429
25491
  for (const m of mergedResult.methods) {
@@ -25434,6 +25496,24 @@ var init_specs2 = __esm({
25434
25496
  - ${labelErrors.join("\n- ")}`);
25435
25497
  }
25436
25498
  }
25499
+ const canonical = (spec) => {
25500
+ const parsed = deltaSchema.safeParse(spec);
25501
+ const value = { ...parsed.success ? parsed.data : spec };
25502
+ delete value.updatedAt;
25503
+ return value;
25504
+ };
25505
+ const changes = specChanges(canonical(storedBefore), canonical(mergedResult));
25506
+ if (changes.length === 0) {
25507
+ return {
25508
+ kind,
25509
+ id,
25510
+ written: false,
25511
+ changes,
25512
+ notices,
25513
+ summary: `No change to ${kind} "${id}" \u2014 the delta matches what is stored, so nothing was written.`
25514
+ };
25515
+ }
25516
+ mergedResult.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
25437
25517
  const gateNotices = hooks?.gate?.(kind, mergedResult);
25438
25518
  if (gateNotices?.length) notices.push(...gateNotices);
25439
25519
  const opts = {
@@ -25459,7 +25539,14 @@ var init_specs2 = __esm({
25459
25539
  notices.push(...this.saveTypeSpec(mergedResult));
25460
25540
  break;
25461
25541
  }
25462
- return notices;
25542
+ return {
25543
+ kind,
25544
+ id,
25545
+ written: true,
25546
+ changes,
25547
+ notices,
25548
+ summary: `Updated ${kind} "${id}": ${changes.length} change${changes.length === 1 ? "" : "s"}.`
25549
+ };
25463
25550
  }
25464
25551
  };
25465
25552
  workspaces = /* @__PURE__ */ new Map();
@@ -26425,6 +26512,16 @@ function json(value) {
26425
26512
  function errText(message) {
26426
26513
  return { content: [{ type: "text", text: `Error: ${message}` }], isError: true };
26427
26514
  }
26515
+ function renderChangeReport(report2) {
26516
+ const lines = [report2.summary];
26517
+ for (const change of report2.changes) {
26518
+ const to = change.after === void 0 ? "" : `: ${JSON.stringify(change.after)}`;
26519
+ const from = change.before === void 0 ? "" : ` (was ${JSON.stringify(change.before)})`;
26520
+ lines.push(`- ${change.path} ${change.change}${to}${from}`);
26521
+ }
26522
+ if (report2.notices.length) lines.push("", "NOTICE:", ...report2.notices.map((n) => `- ${n}`));
26523
+ return lines.join("\n");
26524
+ }
26428
26525
  function captureBuildStamp(entryPath) {
26429
26526
  try {
26430
26527
  const s = fs20.statSync(entryPath);
@@ -27626,12 +27723,7 @@ NOTICE:
27626
27723
  },
27627
27724
  ({ kind, id, delta }) => {
27628
27725
  try {
27629
- const notices = updateSpecGated(kind, id, delta);
27630
- const noticeBlock = notices.length ? `
27631
-
27632
- NOTICE:
27633
- - ${notices.join("\n- ")}` : "";
27634
- return text(`Successfully updated ${kind} spec "${id}".${noticeBlock}`);
27726
+ return text(renderChangeReport(updateSpecGated(kind, id, delta)));
27635
27727
  } catch (e) {
27636
27728
  return errText(String(e));
27637
27729
  }