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

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.38";
1117
1117
  GITHUB_REPO = "SYW-Apps/Waffle-AIron";
1118
1118
  ARCHITECT_AGENT_ID = "agent-architect";
1119
1119
  ARCHITECT_TEMPLATE_ID = "architect";
@@ -1823,7 +1823,13 @@ var init_specs = __esm({
1823
1823
  GuaranteeSchema = import_zod8.z.string().min(1);
1824
1824
  MethodParamSchema = import_zod8.z.object({
1825
1825
  name: import_zod8.z.string(),
1826
- /** A primitive/builtin or a defined type id (qualified across subsystems, e.g. "billing.Invoice"). */
1826
+ /**
1827
+ * A primitive/builtin or a defined type id (qualified across subsystems, e.g.
1828
+ * "billing.Invoice"), on its own or inside a generic, an array or a UNION:
1829
+ * `Invoice | null`, `Promise<Invoice | null>`, `Invoice[] | null`. Every
1830
+ * identifier the string names has to resolve — see the grammar on
1831
+ * src/models/type-references.ts.
1832
+ */
1827
1833
  type: import_zod8.z.string(),
1828
1834
  description: import_zod8.z.string().optional(),
1829
1835
  optional: import_zod8.z.boolean().optional()
@@ -1841,8 +1847,9 @@ var init_specs = __esm({
1841
1847
  description: import_zod8.z.string(),
1842
1848
  signature: import_zod8.z.string(),
1843
1849
  // e.g. "save(key: string, data: Buffer): Promise<void>"
1850
+ // e.g. "Promise<void>", or a union: "Invoice | null" — the commonest shape in
1851
+ // any real tree. See the grammar on src/models/type-references.ts.
1844
1852
  returns: import_zod8.z.string(),
1845
- // e.g. "Promise<void>"
1846
1853
  /** Structured parameters (authoritative for type checking when present). */
1847
1854
  params: import_zod8.z.array(MethodParamSchema).optional(),
1848
1855
  /** Concrete wire binding for this method when its component is a Portal (set via sdd_set_endpoints). */
@@ -2111,8 +2118,10 @@ var init_specs = __esm({
2111
2118
  TypeKindSchema = import_zod8.z.enum(["entity", "value-object"]);
2112
2119
  TypeFieldSchema = import_zod8.z.object({
2113
2120
  name: import_zod8.z.string(),
2121
+ // A primitive, or another type id (qualified across subsystems, e.g.
2122
+ // "billing.Invoice") — on its own or inside a generic, an array or a union
2123
+ // ("Invoice[] | null"). See the grammar on src/models/type-references.ts.
2114
2124
  type: import_zod8.z.string(),
2115
- // a primitive, or another type id (qualified across subsystems, e.g. "billing.Invoice")
2116
2125
  description: import_zod8.z.string().optional(),
2117
2126
  optional: import_zod8.z.boolean().default(false),
2118
2127
  /**
@@ -18471,6 +18480,117 @@ function specChanges(before, after, prefix = "") {
18471
18480
  if (JSON.stringify(before) === JSON.stringify(after)) return [];
18472
18481
  return [{ path: prefix, change: "set", before: summarizeValue(before), after: summarizeValue(after) }];
18473
18482
  }
18483
+ function identityFieldsOf(field) {
18484
+ switch (field) {
18485
+ case "dispatch":
18486
+ return ["capability"];
18487
+ case "lifecycle":
18488
+ return ["phase", "component", "method"];
18489
+ case "emits":
18490
+ case "subscribesTo":
18491
+ return ["topic", "event"];
18492
+ case "trustedLinks":
18493
+ return ["subsystem"];
18494
+ case "allow":
18495
+ case "findings":
18496
+ return ["code"];
18497
+ case "invariants":
18498
+ case "patterns":
18499
+ return ["id"];
18500
+ case "boundaries":
18501
+ return ["name"];
18502
+ case "globalRequirements":
18503
+ return ["description"];
18504
+ case "databases":
18505
+ return ["id", "name"];
18506
+ case "cases":
18507
+ return ["value"];
18508
+ case "catches":
18509
+ return ["error"];
18510
+ case "narrative":
18511
+ return ["stepNumber"];
18512
+ default:
18513
+ return ["name", "id"];
18514
+ }
18515
+ }
18516
+ function carriesEditMarker(item) {
18517
+ if (item === null || typeof item !== "object") return false;
18518
+ const o = item;
18519
+ return o.action !== void 0 || o.remove !== void 0;
18520
+ }
18521
+ function ineffectiveDeltaPaths(delta, merged, stored) {
18522
+ const out = [];
18523
+ const isPlain = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
18524
+ const same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
18525
+ const at = (prefix, key) => prefix ? `${prefix}.${key}` : key;
18526
+ const say = (path27, why) => {
18527
+ out.push(`${path27} \u2014 ${why}`);
18528
+ };
18529
+ const walkValue = (d, m, s, path27, field) => {
18530
+ if (Array.isArray(d)) {
18531
+ if (!Array.isArray(m)) {
18532
+ say(path27, "the level does not have this field, so the write dropped it");
18533
+ return;
18534
+ }
18535
+ if (field === "narrative" && d.some(carriesEditMarker)) return;
18536
+ for (const element of d) {
18537
+ if (carriesEditMarker(element)) continue;
18538
+ const key = reportIdentityOf(field, element);
18539
+ if (key === null) {
18540
+ if (!same(m, d)) say(path27, "the level does not keep this value, so the write dropped it");
18541
+ else if (same(s, d)) say(path27, "the stored spec already held this value");
18542
+ return;
18543
+ }
18544
+ const mMatch = m.find((i) => reportIdentityOf(field, i) === key);
18545
+ const sMatch = Array.isArray(s) ? s.find((i) => reportIdentityOf(field, i) === key) : void 0;
18546
+ if (mMatch === void 0) {
18547
+ say(at(path27, key), "the write did not store this element");
18548
+ continue;
18549
+ }
18550
+ walk2(element, mMatch, sMatch, at(path27, key), field);
18551
+ }
18552
+ return;
18553
+ }
18554
+ if (isPlain(d)) {
18555
+ if (!isPlain(m)) {
18556
+ say(path27, "the level does not have this field, so the write dropped it");
18557
+ return;
18558
+ }
18559
+ walk2(d, m, s, path27, field);
18560
+ return;
18561
+ }
18562
+ if (!same(m, d)) say(path27, "the write did not store it \u2014 the level's schema does not keep this value");
18563
+ else if (same(s, d)) say(path27, "the stored spec already held this value");
18564
+ };
18565
+ function walk2(d, m, s, path27, container) {
18566
+ for (const [key, value] of Object.entries(d)) {
18567
+ if (DELTA_VERBS.has(key) || JUMP_LABEL_TWINS.has(key)) continue;
18568
+ if (key === "label" && LABEL_TWIN_CONTAINERS.has(container)) continue;
18569
+ if (identityFieldsOf(container).includes(key)) continue;
18570
+ if (key === "unset") {
18571
+ for (const name of Array.isArray(value) ? value : []) {
18572
+ if (typeof name !== "string") continue;
18573
+ const had = isPlain(s) && s[name] !== void 0;
18574
+ const still = isPlain(m) && m[name] !== void 0;
18575
+ if (!had) say(at(path27, name), "unset named a field the stored spec did not have");
18576
+ else if (still) say(at(path27, name), "unset did not remove it");
18577
+ }
18578
+ continue;
18579
+ }
18580
+ if (value === void 0 || value === null) continue;
18581
+ const here = at(path27, key);
18582
+ const mValue = isPlain(m) ? m[key] : void 0;
18583
+ const sValue = isPlain(s) ? s[key] : void 0;
18584
+ if (mValue === void 0) {
18585
+ say(here, "the level does not have this field, so the write dropped it");
18586
+ continue;
18587
+ }
18588
+ walkValue(value, mValue, sValue, here, key);
18589
+ }
18590
+ }
18591
+ walk2(delta, merged, stored, "", "");
18592
+ return out;
18593
+ }
18474
18594
  function workspaceFor(rootDir) {
18475
18595
  const key = path16.resolve(rootDir);
18476
18596
  let ws = workspaces.get(key);
@@ -18696,10 +18816,10 @@ function restoreSpecFiles(snapshot) {
18696
18816
  function findLegacySpecFiles() {
18697
18817
  return current().findLegacySpecFiles();
18698
18818
  }
18699
- function updateSpec(kind, id, delta, hooks) {
18700
- return current().updateSpec(kind, id, delta, hooks);
18819
+ function updateSpec(kind, id, delta, hooks, dryRun) {
18820
+ return current().updateSpec(kind, id, delta, hooks, dryRun);
18701
18821
  }
18702
- var fs11, path16, NO_ROOT_SUBSYSTEMS, SIGNATURE_TTL_MS, SpecWorkspace, workspaces;
18822
+ var fs11, path16, NO_ROOT_SUBSYSTEMS, SIGNATURE_TTL_MS, DELTA_VERBS, JUMP_LABEL_TWINS, LABEL_TWIN_CONTAINERS, SpecWorkspace, workspaces;
18703
18823
  var init_specs2 = __esm({
18704
18824
  "src/core/specs.ts"() {
18705
18825
  "use strict";
@@ -18717,6 +18837,16 @@ var init_specs2 = __esm({
18717
18837
  init_diagram();
18718
18838
  NO_ROOT_SUBSYSTEMS = /* @__PURE__ */ new Set();
18719
18839
  SIGNATURE_TTL_MS = 2e3;
18840
+ DELTA_VERBS = /* @__PURE__ */ new Set(["action", "remove", "captureJumps"]);
18841
+ JUMP_LABEL_TWINS = /* @__PURE__ */ new Set([
18842
+ "onTrueLabel",
18843
+ "onFalseLabel",
18844
+ "defaultLabel",
18845
+ "endLabel",
18846
+ "finallyLabel",
18847
+ "toLabel"
18848
+ ]);
18849
+ LABEL_TWIN_CONTAINERS = /* @__PURE__ */ new Set(["cases", "catches", "branches"]);
18720
18850
  SpecWorkspace = class {
18721
18851
  constructor(rootDir) {
18722
18852
  this.cachedIndex = null;
@@ -19432,6 +19562,9 @@ var init_specs2 = __esm({
19432
19562
  const existing = this.loadSubsystemSpec(spec.id);
19433
19563
  if (existing) {
19434
19564
  specToWrite.createdAt = existing.createdAt;
19565
+ if (!opts?.allowStatusDemotion && existing.status && (!spec.status || spec.status === "draft")) {
19566
+ specToWrite.status = existing.status;
19567
+ }
19435
19568
  }
19436
19569
  specToWrite.updatedAt = opts?.preserveUpdatedAt && existing?.updatedAt ? existing.updatedAt : (/* @__PURE__ */ new Date()).toISOString();
19437
19570
  writeYamlFile(p, parseOrThrow(SubsystemSpecSchema, specToWrite, "subsystem", spec.id));
@@ -19980,15 +20113,22 @@ var init_specs2 = __esm({
19980
20113
  * carry, and a label in the delta retargets the stored numeric jump it
19981
20114
  * twins. An unresolved reference aborts the whole update.
19982
20115
  * 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.
20116
+ * schema. Equal means nothing is written and `written` is false. The same
20117
+ * comparison names every delta path the write did NOT act on — one the
20118
+ * schema drops at a depth the permissive delta cannot refuse, one the
20119
+ * stored spec already held, an `unset` that removed nothing.
20120
+ * 8. GATE the merged spec through the caller's hook. A DRY RUN stops here
20121
+ * and answers with the report it would have produced — after the gate, so
20122
+ * it accounts for a write that would really be accepted, and before the
20123
+ * stamp, so not one byte of the stored file moves.
20124
+ * 9. STAMP updatedAt and SAVE.
19985
20125
  *
19986
20126
  * Returns the change report, whose `notices` carry the non-fatal effects (an
19987
20127
  * insert whose position was a jump target, so the relocated jumps now bypass
19988
20128
  * the inserted step; the fields a retype dropped) — empty when the merge had
19989
20129
  * nothing to say.
19990
20130
  */
19991
- updateSpec(kind, id, delta, hooks) {
20131
+ updateSpec(kind, id, delta, hooks, dryRun = false) {
19992
20132
  const notices = [];
19993
20133
  const result = (() => {
19994
20134
  switch (kind) {
@@ -20467,7 +20607,8 @@ var init_specs2 = __esm({
20467
20607
  const unsetFields = Array.isArray(delta.unset) ? delta.unset.filter((f) => typeof f === "string") : [];
20468
20608
  const { unset: _unset, ...mergeableDelta } = delta;
20469
20609
  const storedBefore = JSON.parse(JSON.stringify(result));
20470
- const mergedResult = mergeDelta(result, qualifyDeltaRefs(mergeableDelta));
20610
+ const qualifiedDelta = qualifyDeltaRefs(mergeableDelta);
20611
+ const mergedResult = mergeDelta(result, qualifiedDelta);
20471
20612
  for (const field of unsetFields) delete mergedResult[field];
20472
20613
  if (kind === "implementation" && Array.isArray(mergedResult.methods)) {
20473
20614
  const labelErrors = [];
@@ -20485,20 +20626,41 @@ var init_specs2 = __esm({
20485
20626
  delete value.updatedAt;
20486
20627
  return value;
20487
20628
  };
20488
- const changes = specChanges(canonical(storedBefore), canonical(mergedResult));
20629
+ const canonicalStored = canonical(storedBefore);
20630
+ const canonicalMerged = canonical(mergedResult);
20631
+ const changes = specChanges(canonicalStored, canonicalMerged);
20632
+ const ineffective = ineffectiveDeltaPaths(
20633
+ { ...qualifiedDelta, ...unsetFields.length ? { unset: unsetFields } : {} },
20634
+ canonicalMerged,
20635
+ canonicalStored
20636
+ );
20489
20637
  if (changes.length === 0) {
20490
20638
  return {
20491
20639
  kind,
20492
20640
  id,
20493
20641
  written: false,
20642
+ dryRun,
20494
20643
  changes,
20644
+ ineffective,
20495
20645
  notices,
20496
- summary: `No change to ${kind} "${id}" \u2014 the delta matches what is stored, so nothing was written.`
20646
+ summary: `No change to ${kind} "${id}" \u2014 the delta matches what is stored, so nothing ${dryRun ? "would be" : "was"} written.`
20497
20647
  };
20498
20648
  }
20499
- mergedResult.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
20500
20649
  const gateNotices = hooks?.gate?.(kind, mergedResult);
20501
20650
  if (gateNotices?.length) notices.push(...gateNotices);
20651
+ if (dryRun) {
20652
+ return {
20653
+ kind,
20654
+ id,
20655
+ written: false,
20656
+ dryRun: true,
20657
+ changes,
20658
+ ineffective,
20659
+ notices,
20660
+ summary: `Dry run on ${kind} "${id}": ${changes.length} change${changes.length === 1 ? "" : "s"} would be made. Nothing was written.`
20661
+ };
20662
+ }
20663
+ mergedResult.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
20502
20664
  const opts = {
20503
20665
  allowStatusDemotion: Object.prototype.hasOwnProperty.call(delta, "status")
20504
20666
  };
@@ -20507,7 +20669,7 @@ var init_specs2 = __esm({
20507
20669
  this.saveSystemSpec(mergedResult);
20508
20670
  break;
20509
20671
  case "subsystem":
20510
- this.saveSubsystemSpec(mergedResult);
20672
+ this.saveSubsystemSpec(mergedResult, opts);
20511
20673
  break;
20512
20674
  case "component":
20513
20675
  notices.push(...this.saveComponentSpec(mergedResult, opts));
@@ -20526,7 +20688,9 @@ var init_specs2 = __esm({
20526
20688
  kind,
20527
20689
  id,
20528
20690
  written: true,
20691
+ dryRun: false,
20529
20692
  changes,
20693
+ ineffective,
20530
20694
  notices,
20531
20695
  summary: `Updated ${kind} "${id}": ${changes.length} change${changes.length === 1 ? "" : "s"}.`
20532
20696
  };
@@ -26274,6 +26438,7 @@ __export(src_exports, {
26274
26438
  implementationSourceFiles: () => implementationSourceFiles,
26275
26439
  importSpecTree: () => importSpecTree,
26276
26440
  importSurface: () => importSurface,
26441
+ ineffectiveDeltaPaths: () => ineffectiveDeltaPaths,
26277
26442
  inspectChainedRoots: () => inspectChainedRoots,
26278
26443
  inspectTreeArchive: () => inspectTreeArchive2,
26279
26444
  installPackFromDirectory: () => installPackFromDirectory,
@@ -27001,6 +27166,7 @@ function loadProjectConfig4() {
27001
27166
  implementationSourceFiles,
27002
27167
  importSpecTree,
27003
27168
  importSurface,
27169
+ ineffectiveDeltaPaths,
27004
27170
  inspectChainedRoots,
27005
27171
  inspectTreeArchive,
27006
27172
  installPackFromDirectory,