@wairon/cli 5.1.1-dev.36 → 5.1.1-dev.37

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.36";
1116
+ WAIRON_VERSION = "5.1.1-dev.37";
1117
1117
  GITHUB_REPO = "SYW-Apps/Waffle-AIron";
1118
1118
  ARCHITECT_AGENT_ID = "agent-architect";
1119
1119
  ARCHITECT_TEMPLATE_ID = "architect";
@@ -18471,6 +18471,117 @@ function specChanges(before, after, prefix = "") {
18471
18471
  if (JSON.stringify(before) === JSON.stringify(after)) return [];
18472
18472
  return [{ path: prefix, change: "set", before: summarizeValue(before), after: summarizeValue(after) }];
18473
18473
  }
18474
+ function identityFieldsOf(field) {
18475
+ switch (field) {
18476
+ case "dispatch":
18477
+ return ["capability"];
18478
+ case "lifecycle":
18479
+ return ["phase", "component", "method"];
18480
+ case "emits":
18481
+ case "subscribesTo":
18482
+ return ["topic", "event"];
18483
+ case "trustedLinks":
18484
+ return ["subsystem"];
18485
+ case "allow":
18486
+ case "findings":
18487
+ return ["code"];
18488
+ case "invariants":
18489
+ case "patterns":
18490
+ return ["id"];
18491
+ case "boundaries":
18492
+ return ["name"];
18493
+ case "globalRequirements":
18494
+ return ["description"];
18495
+ case "databases":
18496
+ return ["id", "name"];
18497
+ case "cases":
18498
+ return ["value"];
18499
+ case "catches":
18500
+ return ["error"];
18501
+ case "narrative":
18502
+ return ["stepNumber"];
18503
+ default:
18504
+ return ["name", "id"];
18505
+ }
18506
+ }
18507
+ function carriesEditMarker(item) {
18508
+ if (item === null || typeof item !== "object") return false;
18509
+ const o = item;
18510
+ return o.action !== void 0 || o.remove !== void 0;
18511
+ }
18512
+ function ineffectiveDeltaPaths(delta, merged, stored) {
18513
+ const out = [];
18514
+ const isPlain = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
18515
+ const same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
18516
+ const at = (prefix, key) => prefix ? `${prefix}.${key}` : key;
18517
+ const say = (path27, why) => {
18518
+ out.push(`${path27} \u2014 ${why}`);
18519
+ };
18520
+ const walkValue = (d, m, s, path27, field) => {
18521
+ if (Array.isArray(d)) {
18522
+ if (!Array.isArray(m)) {
18523
+ say(path27, "the level does not have this field, so the write dropped it");
18524
+ return;
18525
+ }
18526
+ if (field === "narrative" && d.some(carriesEditMarker)) return;
18527
+ for (const element of d) {
18528
+ if (carriesEditMarker(element)) continue;
18529
+ const key = reportIdentityOf(field, element);
18530
+ if (key === null) {
18531
+ if (!same(m, d)) say(path27, "the level does not keep this value, so the write dropped it");
18532
+ else if (same(s, d)) say(path27, "the stored spec already held this value");
18533
+ return;
18534
+ }
18535
+ const mMatch = m.find((i) => reportIdentityOf(field, i) === key);
18536
+ const sMatch = Array.isArray(s) ? s.find((i) => reportIdentityOf(field, i) === key) : void 0;
18537
+ if (mMatch === void 0) {
18538
+ say(at(path27, key), "the write did not store this element");
18539
+ continue;
18540
+ }
18541
+ walk2(element, mMatch, sMatch, at(path27, key), field);
18542
+ }
18543
+ return;
18544
+ }
18545
+ if (isPlain(d)) {
18546
+ if (!isPlain(m)) {
18547
+ say(path27, "the level does not have this field, so the write dropped it");
18548
+ return;
18549
+ }
18550
+ walk2(d, m, s, path27, field);
18551
+ return;
18552
+ }
18553
+ if (!same(m, d)) say(path27, "the write did not store it \u2014 the level's schema does not keep this value");
18554
+ else if (same(s, d)) say(path27, "the stored spec already held this value");
18555
+ };
18556
+ function walk2(d, m, s, path27, container) {
18557
+ for (const [key, value] of Object.entries(d)) {
18558
+ if (DELTA_VERBS.has(key) || JUMP_LABEL_TWINS.has(key)) continue;
18559
+ if (key === "label" && LABEL_TWIN_CONTAINERS.has(container)) continue;
18560
+ if (identityFieldsOf(container).includes(key)) continue;
18561
+ if (key === "unset") {
18562
+ for (const name of Array.isArray(value) ? value : []) {
18563
+ if (typeof name !== "string") continue;
18564
+ const had = isPlain(s) && s[name] !== void 0;
18565
+ const still = isPlain(m) && m[name] !== void 0;
18566
+ if (!had) say(at(path27, name), "unset named a field the stored spec did not have");
18567
+ else if (still) say(at(path27, name), "unset did not remove it");
18568
+ }
18569
+ continue;
18570
+ }
18571
+ if (value === void 0 || value === null) continue;
18572
+ const here = at(path27, key);
18573
+ const mValue = isPlain(m) ? m[key] : void 0;
18574
+ const sValue = isPlain(s) ? s[key] : void 0;
18575
+ if (mValue === void 0) {
18576
+ say(here, "the level does not have this field, so the write dropped it");
18577
+ continue;
18578
+ }
18579
+ walkValue(value, mValue, sValue, here, key);
18580
+ }
18581
+ }
18582
+ walk2(delta, merged, stored, "", "");
18583
+ return out;
18584
+ }
18474
18585
  function workspaceFor(rootDir) {
18475
18586
  const key = path16.resolve(rootDir);
18476
18587
  let ws = workspaces.get(key);
@@ -18696,10 +18807,10 @@ function restoreSpecFiles(snapshot) {
18696
18807
  function findLegacySpecFiles() {
18697
18808
  return current().findLegacySpecFiles();
18698
18809
  }
18699
- function updateSpec(kind, id, delta, hooks) {
18700
- return current().updateSpec(kind, id, delta, hooks);
18810
+ function updateSpec(kind, id, delta, hooks, dryRun) {
18811
+ return current().updateSpec(kind, id, delta, hooks, dryRun);
18701
18812
  }
18702
- var fs11, path16, NO_ROOT_SUBSYSTEMS, SIGNATURE_TTL_MS, SpecWorkspace, workspaces;
18813
+ var fs11, path16, NO_ROOT_SUBSYSTEMS, SIGNATURE_TTL_MS, DELTA_VERBS, JUMP_LABEL_TWINS, LABEL_TWIN_CONTAINERS, SpecWorkspace, workspaces;
18703
18814
  var init_specs2 = __esm({
18704
18815
  "src/core/specs.ts"() {
18705
18816
  "use strict";
@@ -18717,6 +18828,16 @@ var init_specs2 = __esm({
18717
18828
  init_diagram();
18718
18829
  NO_ROOT_SUBSYSTEMS = /* @__PURE__ */ new Set();
18719
18830
  SIGNATURE_TTL_MS = 2e3;
18831
+ DELTA_VERBS = /* @__PURE__ */ new Set(["action", "remove", "captureJumps"]);
18832
+ JUMP_LABEL_TWINS = /* @__PURE__ */ new Set([
18833
+ "onTrueLabel",
18834
+ "onFalseLabel",
18835
+ "defaultLabel",
18836
+ "endLabel",
18837
+ "finallyLabel",
18838
+ "toLabel"
18839
+ ]);
18840
+ LABEL_TWIN_CONTAINERS = /* @__PURE__ */ new Set(["cases", "catches", "branches"]);
18720
18841
  SpecWorkspace = class {
18721
18842
  constructor(rootDir) {
18722
18843
  this.cachedIndex = null;
@@ -19432,6 +19553,9 @@ var init_specs2 = __esm({
19432
19553
  const existing = this.loadSubsystemSpec(spec.id);
19433
19554
  if (existing) {
19434
19555
  specToWrite.createdAt = existing.createdAt;
19556
+ if (!opts?.allowStatusDemotion && existing.status && (!spec.status || spec.status === "draft")) {
19557
+ specToWrite.status = existing.status;
19558
+ }
19435
19559
  }
19436
19560
  specToWrite.updatedAt = opts?.preserveUpdatedAt && existing?.updatedAt ? existing.updatedAt : (/* @__PURE__ */ new Date()).toISOString();
19437
19561
  writeYamlFile(p, parseOrThrow(SubsystemSpecSchema, specToWrite, "subsystem", spec.id));
@@ -19980,15 +20104,22 @@ var init_specs2 = __esm({
19980
20104
  * carry, and a label in the delta retargets the stored numeric jump it
19981
20105
  * twins. An unresolved reference aborts the whole update.
19982
20106
  * 7. COMPARE with what is stored, both read through the level's canonical
19983
- * schema. Equal means nothing is written and `written` is false.
19984
- * 8. GATE the merged spec through the caller's hook, then SAVE.
20107
+ * schema. Equal means nothing is written and `written` is false. The same
20108
+ * comparison names every delta path the write did NOT act on — one the
20109
+ * schema drops at a depth the permissive delta cannot refuse, one the
20110
+ * stored spec already held, an `unset` that removed nothing.
20111
+ * 8. GATE the merged spec through the caller's hook. A DRY RUN stops here
20112
+ * and answers with the report it would have produced — after the gate, so
20113
+ * it accounts for a write that would really be accepted, and before the
20114
+ * stamp, so not one byte of the stored file moves.
20115
+ * 9. STAMP updatedAt and SAVE.
19985
20116
  *
19986
20117
  * Returns the change report, whose `notices` carry the non-fatal effects (an
19987
20118
  * insert whose position was a jump target, so the relocated jumps now bypass
19988
20119
  * the inserted step; the fields a retype dropped) — empty when the merge had
19989
20120
  * nothing to say.
19990
20121
  */
19991
- updateSpec(kind, id, delta, hooks) {
20122
+ updateSpec(kind, id, delta, hooks, dryRun = false) {
19992
20123
  const notices = [];
19993
20124
  const result = (() => {
19994
20125
  switch (kind) {
@@ -20467,7 +20598,8 @@ var init_specs2 = __esm({
20467
20598
  const unsetFields = Array.isArray(delta.unset) ? delta.unset.filter((f) => typeof f === "string") : [];
20468
20599
  const { unset: _unset, ...mergeableDelta } = delta;
20469
20600
  const storedBefore = JSON.parse(JSON.stringify(result));
20470
- const mergedResult = mergeDelta(result, qualifyDeltaRefs(mergeableDelta));
20601
+ const qualifiedDelta = qualifyDeltaRefs(mergeableDelta);
20602
+ const mergedResult = mergeDelta(result, qualifiedDelta);
20471
20603
  for (const field of unsetFields) delete mergedResult[field];
20472
20604
  if (kind === "implementation" && Array.isArray(mergedResult.methods)) {
20473
20605
  const labelErrors = [];
@@ -20485,20 +20617,41 @@ var init_specs2 = __esm({
20485
20617
  delete value.updatedAt;
20486
20618
  return value;
20487
20619
  };
20488
- const changes = specChanges(canonical(storedBefore), canonical(mergedResult));
20620
+ const canonicalStored = canonical(storedBefore);
20621
+ const canonicalMerged = canonical(mergedResult);
20622
+ const changes = specChanges(canonicalStored, canonicalMerged);
20623
+ const ineffective = ineffectiveDeltaPaths(
20624
+ { ...qualifiedDelta, ...unsetFields.length ? { unset: unsetFields } : {} },
20625
+ canonicalMerged,
20626
+ canonicalStored
20627
+ );
20489
20628
  if (changes.length === 0) {
20490
20629
  return {
20491
20630
  kind,
20492
20631
  id,
20493
20632
  written: false,
20633
+ dryRun,
20494
20634
  changes,
20635
+ ineffective,
20495
20636
  notices,
20496
- summary: `No change to ${kind} "${id}" \u2014 the delta matches what is stored, so nothing was written.`
20637
+ summary: `No change to ${kind} "${id}" \u2014 the delta matches what is stored, so nothing ${dryRun ? "would be" : "was"} written.`
20497
20638
  };
20498
20639
  }
20499
- mergedResult.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
20500
20640
  const gateNotices = hooks?.gate?.(kind, mergedResult);
20501
20641
  if (gateNotices?.length) notices.push(...gateNotices);
20642
+ if (dryRun) {
20643
+ return {
20644
+ kind,
20645
+ id,
20646
+ written: false,
20647
+ dryRun: true,
20648
+ changes,
20649
+ ineffective,
20650
+ notices,
20651
+ summary: `Dry run on ${kind} "${id}": ${changes.length} change${changes.length === 1 ? "" : "s"} would be made. Nothing was written.`
20652
+ };
20653
+ }
20654
+ mergedResult.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
20502
20655
  const opts = {
20503
20656
  allowStatusDemotion: Object.prototype.hasOwnProperty.call(delta, "status")
20504
20657
  };
@@ -20507,7 +20660,7 @@ var init_specs2 = __esm({
20507
20660
  this.saveSystemSpec(mergedResult);
20508
20661
  break;
20509
20662
  case "subsystem":
20510
- this.saveSubsystemSpec(mergedResult);
20663
+ this.saveSubsystemSpec(mergedResult, opts);
20511
20664
  break;
20512
20665
  case "component":
20513
20666
  notices.push(...this.saveComponentSpec(mergedResult, opts));
@@ -20526,7 +20679,9 @@ var init_specs2 = __esm({
20526
20679
  kind,
20527
20680
  id,
20528
20681
  written: true,
20682
+ dryRun: false,
20529
20683
  changes,
20684
+ ineffective,
20530
20685
  notices,
20531
20686
  summary: `Updated ${kind} "${id}": ${changes.length} change${changes.length === 1 ? "" : "s"}.`
20532
20687
  };
@@ -26274,6 +26429,7 @@ __export(src_exports, {
26274
26429
  implementationSourceFiles: () => implementationSourceFiles,
26275
26430
  importSpecTree: () => importSpecTree,
26276
26431
  importSurface: () => importSurface,
26432
+ ineffectiveDeltaPaths: () => ineffectiveDeltaPaths,
26277
26433
  inspectChainedRoots: () => inspectChainedRoots,
26278
26434
  inspectTreeArchive: () => inspectTreeArchive2,
26279
26435
  installPackFromDirectory: () => installPackFromDirectory,
@@ -27001,6 +27157,7 @@ function loadProjectConfig4() {
27001
27157
  implementationSourceFiles,
27002
27158
  importSpecTree,
27003
27159
  importSurface,
27160
+ ineffectiveDeltaPaths,
27004
27161
  inspectChainedRoots,
27005
27162
  inspectTreeArchive,
27006
27163
  installPackFromDirectory,