@wairon/cli 5.1.1-dev.88 → 5.1.1-dev.89

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
@@ -3503,6 +3503,7 @@ __export(src_exports, {
3503
3503
  SystemPublicInterfaceSchema: () => SystemPublicInterfaceSchema,
3504
3504
  SystemSpecSchema: () => SystemSpecSchema,
3505
3505
  TargetConfigSchema: () => TargetConfigSchema,
3506
+ TechnologySchema: () => TechnologySchema,
3506
3507
  TemplateNotFoundError: () => TemplateNotFoundError,
3507
3508
  TemplateSchema: () => TemplateSchema,
3508
3509
  TopologyConfigSchema: () => TopologyConfigSchema,
@@ -3530,6 +3531,7 @@ __export(src_exports, {
3530
3531
  assertProjectInitialized: () => assertProjectInitialized,
3531
3532
  backendCommand: () => backendCommand,
3532
3533
  backfillChainedSubprojectConfigs: () => backfillChainedSubprojectConfigs,
3534
+ backupBeside: () => backupBeside,
3533
3535
  buildCanvasDataModel: () => buildCanvasDataModel,
3534
3536
  buildProjectGraph: () => buildProjectGraph,
3535
3537
  buildRuleContext: () => buildRuleContext,
@@ -3629,6 +3631,7 @@ __export(src_exports, {
3629
3631
  internalizeMember: () => internalizeMember,
3630
3632
  isDraftSubsystem: () => isDraftSubsystem,
3631
3633
  isLogic: () => isLogic,
3634
+ isOutsideRoot: () => isOutsideRoot,
3632
3635
  isOwnComponentEntry: () => isOwnComponentEntry,
3633
3636
  isPattern: () => isPattern,
3634
3637
  isProjectInitialized: () => isProjectInitialized,
@@ -3758,6 +3761,8 @@ __export(src_exports, {
3758
3761
  stepFieldsFor: () => stepFieldsFor,
3759
3762
  stepGraph: () => stepGraph,
3760
3763
  syncContextFiles: () => syncContextFiles,
3764
+ technologyName: () => technologyName,
3765
+ technologyTokens: () => technologyTokens,
3761
3766
  toMarkdown: () => toMarkdown,
3762
3767
  typeBindingOf: () => typeBindingOf,
3763
3768
  typeGenericParameters: () => typeGenericParameters,
@@ -3814,6 +3819,15 @@ function readFileOrNull(filePath) {
3814
3819
  function pathExists(targetPath) {
3815
3820
  return fs.existsSync(targetPath);
3816
3821
  }
3822
+ function isOutsideRoot(root, target) {
3823
+ const rel2 = path.relative(path.resolve(root), path.resolve(target));
3824
+ return rel2 === ".." || rel2.startsWith(`..${path.sep}`) || path.isAbsolute(rel2);
3825
+ }
3826
+ function backupBeside(file, at = /* @__PURE__ */ new Date()) {
3827
+ const backup = `${file}.wairon-backup-${at.toISOString().replace(/[:.]/g, "-")}`;
3828
+ fs.copyFileSync(file, backup);
3829
+ return backup;
3830
+ }
3817
3831
  function listFiles(dirPath, ext) {
3818
3832
  if (!fs.existsSync(dirPath)) return [];
3819
3833
  return fs.readdirSync(dirPath).filter((f) => f.endsWith(ext)).map((f) => path.join(dirPath, f));
@@ -7028,6 +7042,19 @@ function parseDeclaredCall(ref) {
7028
7042
  if (at <= 0 || at === ref.length - 1) return null;
7029
7043
  return { compId: ref.slice(0, at), methodName: ref.slice(at + 1) };
7030
7044
  }
7045
+ var TechnologySchema = import_zod8.z.union([
7046
+ import_zod8.z.string().min(1),
7047
+ import_zod8.z.object({
7048
+ name: import_zod8.z.string().min(1),
7049
+ matches: import_zod8.z.array(import_zod8.z.string().min(1)).min(1)
7050
+ }).strict()
7051
+ ]);
7052
+ function technologyName(tech) {
7053
+ return typeof tech === "string" ? tech : tech.name;
7054
+ }
7055
+ function technologyTokens(tech) {
7056
+ return typeof tech === "string" ? [tech] : [...tech.matches];
7057
+ }
7031
7058
  var ImplementationSpecSchema = import_zod8.z.object({
7032
7059
  id: SpecIdSchema,
7033
7060
  name: import_zod8.z.string(),
@@ -7052,9 +7079,10 @@ var ImplementationSpecSchema = import_zod8.z.object({
7052
7079
  * component's ownership tree the technology's home: references anywhere
7053
7080
  * outside it are flagged (TECH_LEAKAGE), contract identifiers must stay
7054
7081
  * intent-language (VENDOR_NAME_IN_CONTRACT), and only data-layer
7055
- * stereotypes should bind tech directly (TECH_ON_LOGIC_COMPONENT).
7082
+ * stereotypes should bind tech directly (TECH_ON_LOGIC_COMPONENT). An entry
7083
+ * is a bare name or `{ name, matches }` (see TechnologySchema).
7056
7084
  */
7057
- technologies: import_zod8.z.array(import_zod8.z.string()).optional(),
7085
+ technologies: import_zod8.z.array(TechnologySchema).optional(),
7058
7086
  /**
7059
7087
  * The parameter names THIS realization takes BEFORE the ones its contract
7060
7088
  * declares — a config object, a data root, the transport handles a portal is
@@ -14282,7 +14310,7 @@ function defaultTargetConfig(type) {
14282
14310
  enabled: true
14283
14311
  };
14284
14312
  }
14285
- var WAIRON_VERSION = "5.1.1-dev.88";
14313
+ var WAIRON_VERSION = "5.1.1-dev.89";
14286
14314
  var GITHUB_REPO = "SYW-Apps/Waffle-AIron";
14287
14315
  var ARCHITECT_AGENT_ID = "agent-architect";
14288
14316
  var ARCHITECT_TEMPLATE_ID = "architect";
@@ -21413,7 +21441,7 @@ var technologyBindingRule = {
21413
21441
  ctx.addIssue(
21414
21442
  "warning",
21415
21443
  "TECH_ON_LOGIC_COMPONENT",
21416
- `Implementation "${impl.id}" binds technology (${impl.technologies.join(", ")}) on component "${comp.id}" (${comp.componentType}). Technology belongs behind a data-layer seam \u2014 extract an Adapter (or Store/Registry/Index) behind an intent interface and let this component depend on that.`,
21444
+ `Implementation "${impl.id}" binds technology (${impl.technologies.map(technologyName).join(", ")}) on component "${comp.id}" (${comp.componentType}). Technology belongs behind a data-layer seam \u2014 extract an Adapter (or Store/Registry/Index) behind an intent interface and let this component depend on that.`,
21417
21445
  impl.id,
21418
21446
  ctx.isImplementationDraft(impl)
21419
21447
  );
@@ -21438,6 +21466,16 @@ function makeMatcher(tech) {
21438
21466
  return false;
21439
21467
  };
21440
21468
  }
21469
+ function newHome(label7) {
21470
+ const home = {
21471
+ label: label7,
21472
+ matchers: [],
21473
+ match: (text3) => home.matchers.some((m) => m(text3)),
21474
+ ownerComponents: /* @__PURE__ */ new Set(),
21475
+ scope: /* @__PURE__ */ new Set()
21476
+ };
21477
+ return home;
21478
+ }
21441
21479
  function contractIdentifiers(m) {
21442
21480
  const parts = [m.name, m.signature, m.returns];
21443
21481
  for (const p of m.params ?? []) parts.push(p.name, p.type);
@@ -21447,7 +21485,7 @@ function contractIdentifiers(m) {
21447
21485
  var technologyRule = {
21448
21486
  name: "technology-boundaries",
21449
21487
  judges: "design",
21450
- description: "Technology stays behind its owning boundary: an L4 that declares `technologies` (e.g. [mysql]) makes its component's ownership tree the technology's home. References outside that tree are leakage, and L3 contract identifiers must stay intent-language \u2014 the contract is the swap seam, so the vendor name is wrong even on the owning component's own interface.",
21488
+ description: "Technology stays behind its owning boundary: an L4 that declares `technologies` (e.g. [mysql]) makes its component's ownership tree the technology's home. References outside that tree are leakage, and L3 contract identifiers must stay intent-language \u2014 the contract is the swap seam, so the vendor name is wrong even on the owning component's own interface. A technology is matched by its name, or \u2014 when it declares `matches` because its name is also an ordinary word of the tree (a package named after the file format it reads) \u2014 by those tokens alone.",
21451
21489
  codes: [
21452
21490
  { code: "TECH_LEAKAGE", defaultSeverity: "warning", summary: "Technology referenced outside its owning boundary" },
21453
21491
  { code: "VENDOR_NAME_IN_CONTRACT", defaultSeverity: "warning", summary: "Technology name in L3 contract identifiers" }
@@ -21484,7 +21522,9 @@ var technologyRule = {
21484
21522
  const intf = ctx.interfaceMap.get(impl.contract);
21485
21523
  const comp = intf ? ctx.componentMap.get(intf.component) : void 0;
21486
21524
  if (!comp) continue;
21487
- for (const tech of impl.technologies) declarations.push({ comp, tech });
21525
+ for (const tech of impl.technologies) {
21526
+ declarations.push({ comp, name: technologyName(tech), tokens: technologyTokens(tech) });
21527
+ }
21488
21528
  }
21489
21529
  const scopes = /* @__PURE__ */ new Map();
21490
21530
  const scopeOf2 = (comp) => {
@@ -21508,11 +21548,12 @@ var technologyRule = {
21508
21548
  return scope;
21509
21549
  };
21510
21550
  const homes = /* @__PURE__ */ new Map();
21511
- for (const { comp, tech } of declarations) {
21512
- const match2 = makeMatcher(tech);
21513
- if (!match2) continue;
21514
- const key = tech.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean).join("");
21515
- const home = homes.get(key) ?? { label: tech, match: match2, ownerComponents: /* @__PURE__ */ new Set(), scope: /* @__PURE__ */ new Set() };
21551
+ for (const { comp, name, tokens } of declarations) {
21552
+ const matchers = tokens.map(makeMatcher).filter((m) => m !== null);
21553
+ if (!matchers.length) continue;
21554
+ const key = name.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean).join("");
21555
+ const home = homes.get(key) ?? newHome(name);
21556
+ home.matchers.push(...matchers);
21516
21557
  home.ownerComponents.add(comp.id);
21517
21558
  for (const id of scopeOf2(comp)) home.scope.add(id);
21518
21559
  homes.set(key, home);
@@ -24936,8 +24977,8 @@ function generateSequenceDiagram(componentId, methodName, options) {
24936
24977
  const steps = [...methodImpl.narrative].sort((a, b) => a.stepNumber - b.stepNumber);
24937
24978
  for (const step of steps) {
24938
24979
  for (const par of parallelArms) {
24939
- const sep12 = par.sepByStep.get(step.stepNumber);
24940
- if (sep12 !== void 0) lines.push(` and ${escapeLabel(sep12)}`);
24980
+ const sep13 = par.sepByStep.get(step.stepNumber);
24981
+ if (sep13 !== void 0) lines.push(` and ${escapeLabel(sep13)}`);
24941
24982
  }
24942
24983
  switch (step.type) {
24943
24984
  case "local":
@@ -25987,7 +26028,7 @@ function mapSpecReferences(kind, spec, map) {
25987
26028
  const s = spec;
25988
26029
  return {
25989
26030
  ...s,
25990
- publicInterfaces: s.publicInterfaces.map((p) => ({
26031
+ publicInterfaces: (s.publicInterfaces ?? []).map((p) => ({
25991
26032
  ...p,
25992
26033
  ...p.component !== void 0 ? { component: map("publicInterfaces", p.component) } : {},
25993
26034
  ...p.interface !== void 0 ? { interface: map("publicInterfaces", p.interface) } : {},
@@ -26003,8 +26044,8 @@ function mapSpecReferences(kind, spec, map) {
26003
26044
  return {
26004
26045
  ...c,
26005
26046
  subsystem: map("subsystem", c.subsystem),
26006
- owns: c.owns.map((o) => map("owns", o)),
26007
- dependsOn: c.dependsOn.map((d) => map("dependsOn", d)),
26047
+ owns: (c.owns ?? []).map((o) => map("owns", o)),
26048
+ dependsOn: (c.dependsOn ?? []).map((d) => map("dependsOn", d)),
26008
26049
  ...c.dispatch ? { dispatch: c.dispatch.map((b) => ({ ...b, component: map("dispatch", b.component) })) } : {},
26009
26050
  ...c.mounts ? { mounts: c.mounts.map((m) => ({ ...m, portal: map("mounts", m.portal) })) } : {}
26010
26051
  };
@@ -26018,9 +26059,9 @@ function mapSpecReferences(kind, spec, map) {
26018
26059
  return {
26019
26060
  ...impl,
26020
26061
  contract: map("contract", impl.contract),
26021
- methods: impl.methods.map((m) => ({
26062
+ methods: (impl.methods ?? []).map((m) => ({
26022
26063
  ...m,
26023
- narrative: m.narrative.map((step) => step.targetComponent !== void 0 ? { ...step, targetComponent: map("narrative", step.targetComponent) } : step)
26064
+ narrative: (m.narrative ?? []).map((step) => step.targetComponent !== void 0 ? { ...step, targetComponent: map("narrative", step.targetComponent) } : step)
26024
26065
  }))
26025
26066
  };
26026
26067
  }
@@ -27868,10 +27909,10 @@ var SpecWorkspace = class {
27868
27909
  return this.relativizeSpec("interface", spec);
27869
27910
  }
27870
27911
  prepareImplementationForWrite(spec, carry = true) {
27871
- const relative18 = this.relativizeSpec("implementation", spec, carry);
27912
+ const relative19 = this.relativizeSpec("implementation", spec, carry);
27872
27913
  const member = this.getSubprojectPrefix(spec.id);
27873
27914
  const memberRoot = member ? this.resolveSubprojectForNamespace(member) : null;
27874
- return memberRoot ? childRelativeFilePaths(relative18, this.rootDir, memberRoot) : relative18;
27915
+ return memberRoot ? childRelativeFilePaths(relative19, this.rootDir, memberRoot) : relative19;
27875
27916
  }
27876
27917
  prepareTypeForWrite(spec) {
27877
27918
  return this.relativizeSpec("type", spec);
@@ -28710,7 +28751,10 @@ var SpecWorkspace = class {
28710
28751
  * second delete written as 7 addresses what used to be step 8. That is why
28711
28752
  * a delete may restate the step's `label` or `description` as a guard, and
28712
28753
  * why labels are the reliable way to name a jump target. Inserts and
28713
- * deletes relocate every jump field in the same narrative around them; a
28754
+ * deletes relocate every STORED jump field in the same narrative around
28755
+ * them; a jump the delta itself writes is already in the numbering the
28756
+ * whole delta leaves behind, so none of its later steps moves it. A new
28757
+ * implementation method with no narrative is intent-level ([]); a
28714
28758
  * step whose `type` changes is REBUILT for its new type, keeping its
28715
28759
  * description and label.
28716
28760
  * 5. UNSET the fields the delta names, at the level each `unset` sits in.
@@ -28768,16 +28812,19 @@ var SpecWorkspace = class {
28768
28812
  }
28769
28813
  const JUMP_FIELDS = ["onTrueStep", "onFalseStep", "defaultStep", "endStep", "finallyStep", "toStep"];
28770
28814
  const JUMP_LIST_FIELDS = ["cases", "catches", "branches"];
28815
+ const isPinned = (step, field) => step?.[DELTA_JUMP_PINS] instanceof Set && step[DELTA_JUMP_PINS].has(field);
28816
+ const isPinnedEntry = (entry) => entry?.[DELTA_JUMP_PINS] === true;
28771
28817
  const relocateJumps = (step, shiftFrom, deltaN, captureInsertTarget = false) => {
28772
28818
  const hit = (v, isRegionTail = false) => deltaN > 0 ? captureInsertTarget && !isRegionTail ? v > shiftFrom : v >= shiftFrom : v > shiftFrom;
28773
28819
  const out = { ...step };
28774
28820
  for (const f of JUMP_FIELDS) {
28821
+ if (isPinned(step, f)) continue;
28775
28822
  if (typeof out[f] === "number" && hit(out[f], f === "endStep")) out[f] = out[f] + deltaN;
28776
28823
  }
28777
28824
  for (const lf of JUMP_LIST_FIELDS) {
28778
28825
  if (Array.isArray(out[lf])) {
28779
28826
  out[lf] = out[lf].map(
28780
- (c) => typeof c?.step === "number" && hit(c.step) ? { ...c, step: c.step + deltaN } : c
28827
+ (c) => !isPinnedEntry(c) && typeof c?.step === "number" && hit(c.step) ? { ...c, step: c.step + deltaN } : c
28781
28828
  );
28782
28829
  }
28783
28830
  }
@@ -28785,14 +28832,39 @@ var SpecWorkspace = class {
28785
28832
  };
28786
28833
  const jumpRefsTo = (step, target) => {
28787
28834
  const refs = [];
28788
- for (const f of JUMP_FIELDS) if (step[f] === target) refs.push(f);
28835
+ for (const f of JUMP_FIELDS) if (step[f] === target && !isPinned(step, f)) refs.push(f);
28789
28836
  for (const lf of JUMP_LIST_FIELDS) {
28790
28837
  (Array.isArray(step[lf]) ? step[lf] : []).forEach((c, i) => {
28791
- if (c?.step === target) refs.push(`${lf}[${i}].step`);
28838
+ if (c?.step === target && !isPinnedEntry(c)) refs.push(`${lf}[${i}].step`);
28792
28839
  });
28793
28840
  }
28794
28841
  return refs;
28795
28842
  };
28843
+ const pinDeltaJumps = (deltaStep, carried) => {
28844
+ const pins = new Set(carried ?? []);
28845
+ for (const f of JUMP_FIELDS) if (typeof deltaStep[f] === "number") pins.add(f);
28846
+ const out = { ...deltaStep };
28847
+ for (const lf of JUMP_LIST_FIELDS) {
28848
+ if (!Array.isArray(out[lf])) continue;
28849
+ out[lf] = out[lf].map((c) => c && typeof c === "object" && typeof c.step === "number" ? { ...c, [DELTA_JUMP_PINS]: true } : c);
28850
+ }
28851
+ out[DELTA_JUMP_PINS] = pins;
28852
+ return out;
28853
+ };
28854
+ const unpinned = (step) => {
28855
+ const out = { ...step };
28856
+ delete out[DELTA_JUMP_PINS];
28857
+ for (const lf of JUMP_LIST_FIELDS) {
28858
+ if (!Array.isArray(out[lf])) continue;
28859
+ out[lf] = out[lf].map((c) => {
28860
+ if (!c || typeof c !== "object" || !(DELTA_JUMP_PINS in c)) return c;
28861
+ const entry = { ...c };
28862
+ delete entry[DELTA_JUMP_PINS];
28863
+ return entry;
28864
+ });
28865
+ }
28866
+ return out;
28867
+ };
28796
28868
  const normalizeIdentity = (k) => String(k ?? "").toLowerCase().replace(/[_\-\s]/g, "");
28797
28869
  const unsetFieldsOf = (item) => Array.isArray(item?.unset) ? item.unset.filter((f) => typeof f === "string") : [];
28798
28870
  const withUnset = (merged, deltaItem) => {
@@ -28972,21 +29044,24 @@ var SpecWorkspace = class {
28972
29044
  capture
28973
29045
  ));
28974
29046
  const { action, remove: remove2, captureJumps, ...cleanStep } = deltaStep;
28975
- steps.push(withUnset(cleanStep, deltaStep));
29047
+ steps.push(withUnset(pinDeltaJumps(cleanStep), deltaStep));
28976
29048
  } else {
28977
29049
  const idx = steps.findIndex((s) => s.stepNumber === stepNum);
28978
29050
  if (idx !== -1) {
28979
- const { action, remove: remove2, ...cleanStep } = deltaStep;
29051
+ const { action, remove: remove2, ...rawStep } = deltaStep;
28980
29052
  const stored = steps[idx];
29053
+ const cleanStep = pinDeltaJumps(rawStep, stored[DELTA_JUMP_PINS]);
28981
29054
  const merged = withUnset(retargetByLabel(mergeElement(stored, cleanStep), cleanStep), deltaStep);
28982
- steps[idx] = rebuildOnRetype(stored, cleanStep, merged, methodName);
29055
+ const rebuilt = rebuildOnRetype(stored, rawStep, merged, methodName);
29056
+ rebuilt[DELTA_JUMP_PINS] = cleanStep[DELTA_JUMP_PINS];
29057
+ steps[idx] = rebuilt;
28983
29058
  } else {
28984
29059
  const { action, remove: remove2, ...cleanStep } = deltaStep;
28985
- steps.push(withUnset(cleanStep, deltaStep));
29060
+ steps.push(withUnset(pinDeltaJumps(cleanStep), deltaStep));
28986
29061
  }
28987
29062
  }
28988
29063
  }
28989
- return steps.sort((a, b) => a.stepNumber - b.stepNumber);
29064
+ return steps.map(unpinned).sort((a, b) => a.stepNumber - b.stepNumber);
28990
29065
  };
28991
29066
  const isPlainObject2 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
28992
29067
  const mergeExt = (existing, delta2) => {
@@ -29009,6 +29084,11 @@ var SpecWorkspace = class {
29009
29084
  } else {
29010
29085
  const existingMethod = merged[idx];
29011
29086
  let narrative = existingMethod.narrative ? [...existingMethod.narrative] : [];
29087
+ if (deltaMethod.narrative !== void 0 && deltaMethod.narrative !== null && !Array.isArray(deltaMethod.narrative)) {
29088
+ throw new Error(
29089
+ `Refusing to update implementation method "${String(deltaMethod.name)}": its "narrative" must be a list of step deltas, got ${JSON.stringify(deltaMethod.narrative)}.`
29090
+ );
29091
+ }
29012
29092
  if (Array.isArray(deltaMethod.narrative)) {
29013
29093
  narrative = deltaMethod.narrative.length === 0 ? [] : mergeNarrative(String(deltaMethod.name), narrative, deltaMethod.narrative);
29014
29094
  }
@@ -29021,11 +29101,21 @@ var SpecWorkspace = class {
29021
29101
  }
29022
29102
  } else {
29023
29103
  assertUnmatchedIsAnAddition(deltaMethod, deltaMethod.name, merged.map((m) => m.name), "method");
29024
- merged.push(withUnset(deltaMethod, deltaMethod));
29104
+ merged.push(withUnset(newMethodOf(deltaMethod), deltaMethod));
29025
29105
  }
29026
29106
  }
29027
29107
  return merged;
29028
29108
  };
29109
+ const newMethodOf = (deltaMethod) => {
29110
+ if (kind !== "implementation") return deltaMethod;
29111
+ if (deltaMethod.narrative === void 0 || deltaMethod.narrative === null) return { ...deltaMethod, narrative: [] };
29112
+ if (!Array.isArray(deltaMethod.narrative)) {
29113
+ throw new Error(
29114
+ `Refusing to add implementation method "${String(deltaMethod.name)}": its "narrative" must be a list of steps, got ${JSON.stringify(deltaMethod.narrative)}. Leave it out (or pass []) for an intent-level method.`
29115
+ );
29116
+ }
29117
+ return deltaMethod;
29118
+ };
29029
29119
  const mergeNamedArray = (existing, delta2) => {
29030
29120
  const merged = [...existing];
29031
29121
  for (const deltaItem of delta2) {
@@ -29203,6 +29293,12 @@ var SpecWorkspace = class {
29203
29293
  const qualifiedDelta = qualifyDeltaRefs(mergeableDelta);
29204
29294
  const mergedResult = mergeDelta(result, qualifiedDelta);
29205
29295
  for (const field of unsetFields) delete mergedResult[field];
29296
+ const withDefaults = deltaSchema.safeParse(mergedResult);
29297
+ if (withDefaults.success) {
29298
+ for (const [key, value] of Object.entries(withDefaults.data)) {
29299
+ if (!(key in mergedResult)) mergedResult[key] = value;
29300
+ }
29301
+ }
29206
29302
  if (kind === "implementation" && Array.isArray(mergedResult.methods)) {
29207
29303
  const labelErrors = [];
29208
29304
  for (const m of mergedResult.methods) {
@@ -29723,6 +29819,7 @@ var PROSE_FIELDS = /* @__PURE__ */ new Set([
29723
29819
  "note",
29724
29820
  "caller"
29725
29821
  ]);
29822
+ var DELTA_JUMP_PINS = /* @__PURE__ */ Symbol("jumps written by this delta");
29726
29823
  function cloneSpec(spec) {
29727
29824
  return JSON.parse(JSON.stringify(spec));
29728
29825
  }
@@ -32271,17 +32368,17 @@ function memberHop(agentId) {
32271
32368
  const alias = agentId.slice(0, at);
32272
32369
  const member = graph().nodes.find((n) => n.parent === "" && n.mountAlias === alias);
32273
32370
  if (!member) return null;
32274
- const relative18 = path26.relative(getProjectRoot(), member.directory).split(path26.sep).join("/");
32275
- return { alias, rest: agentId.slice(at + 2), directory: member.directory, relative: relative18 };
32371
+ const relative19 = path26.relative(getProjectRoot(), member.directory).split(path26.sep).join("/");
32372
+ return { alias, rest: agentId.slice(at + 2), directory: member.directory, relative: relative19 };
32276
32373
  }
32277
- function throughMount(brief, alias, relative18) {
32278
- const under = (p) => path26.posix.join(relative18, p);
32374
+ function throughMount(brief, alias, relative19) {
32375
+ const under = (p) => path26.posix.join(relative19, p);
32279
32376
  return {
32280
32377
  ...brief,
32281
32378
  agentId: `${alias}::${brief.agentId}`,
32282
32379
  ownedPaths: brief.ownedPaths.map(under),
32283
32380
  ...brief.readPaths ? { readPaths: brief.readPaths.map(under) } : {},
32284
- root: brief.root ? under(brief.root) : relative18
32381
+ root: brief.root ? under(brief.root) : relative19
32285
32382
  };
32286
32383
  }
32287
32384
  function loadRegistry() {
@@ -32560,19 +32657,19 @@ function commitStagedTree(stagingDir, destDir, roots, backupExisting) {
32560
32657
  const backupDir = path31.join(root, BACKUP_DIR, timestampSlug());
32561
32658
  let backedUp = false;
32562
32659
  for (const rootRel of roots) {
32563
- const relative18 = rootRel === "." ? "" : rootRel;
32564
- const stagedSpecDir = path31.join(stagingDir, relative18, ".wai");
32660
+ const relative19 = rootRel === "." ? "" : rootRel;
32661
+ const stagedSpecDir = path31.join(stagingDir, relative19, ".wai");
32565
32662
  if (!fs17.existsSync(stagedSpecDir)) continue;
32566
- const liveSpecDir = liveSpecDirFor(path31.join(root, relative18));
32663
+ const liveSpecDir = liveSpecDirFor(path31.join(root, relative19));
32567
32664
  if (backupExisting && liveSpecDir && fs17.existsSync(liveSpecDir)) {
32568
- const backupTarget = path31.join(backupDir, relative18, path31.basename(liveSpecDir));
32665
+ const backupTarget = path31.join(backupDir, relative19, path31.basename(liveSpecDir));
32569
32666
  fs17.mkdirSync(path31.dirname(backupTarget), { recursive: true });
32570
32667
  fs17.renameSync(liveSpecDir, backupTarget);
32571
32668
  backedUp = true;
32572
32669
  } else if (liveSpecDir && fs17.existsSync(liveSpecDir)) {
32573
32670
  fs17.rmSync(liveSpecDir, { recursive: true, force: true });
32574
32671
  }
32575
- const target = path31.join(root, relative18, ".wai");
32672
+ const target = path31.join(root, relative19, ".wai");
32576
32673
  fs17.mkdirSync(path31.dirname(target), { recursive: true });
32577
32674
  if (fs17.existsSync(target)) fs17.rmSync(target, { recursive: true, force: true });
32578
32675
  fs17.renameSync(stagedSpecDir, target);
@@ -33155,7 +33252,7 @@ var CONTEXT_PATHS = {
33155
33252
  waironGuideMd: () => contextDir("wairon-guide.md")
33156
33253
  };
33157
33254
  function hasContext() {
33158
- return pathExists(CONTEXT_PATHS.projectMd());
33255
+ return pathExists(CONTEXT_PATHS.dir());
33159
33256
  }
33160
33257
  function readProjectContext() {
33161
33258
  return readFileOrNull(CONTEXT_PATHS.projectMd());
@@ -38626,7 +38723,7 @@ ${renderChangeReport(report2)}`,
38626
38723
  contract: import_zod11.z.string().describe("The L3 Interface contract ID this implements"),
38627
38724
  sourcePath: import_zod11.z.string().optional().describe("Optional: target source code file path relative to project root \u2014 for a chained subproject's implementation (qualified id), relative to that subproject's root; a path given relative to this root that lands inside the subproject is re-expressed for you"),
38628
38725
  simPath: import_zod11.z.string().optional().describe("Optional: the committed integration-sim harness file (project-relative; N:1 sharing allowed). The validator proves it exists and its import graph wires the REAL modules (this component + each direct dependency; technology adapters may stay faked) \u2014 running it is CI's job. Declaring the first simPath in a subsystem activates MISSING_INTEGRATION_SIM for its other complete non-leaf implementations"),
38629
- technologies: import_zod11.z.array(import_zod11.z.string()).optional().describe(`External technologies this implementation binds to (e.g. ["mysql"]) \u2014 declares this component's ownership tree as the technology's home; references outside it are flagged (TECH_LEAKAGE) and contract identifiers must stay intent-language. Only for Adapter/Store/Registry/Index components.`),
38726
+ technologies: import_zod11.z.array(import_zod11.z.union([import_zod11.z.string(), import_zod11.z.object({ name: import_zod11.z.string(), matches: import_zod11.z.array(import_zod11.z.string()).min(1).describe("The tokens the leakage and contract checks match INSTEAD of the name \u2014 for a technology whose name is also an ordinary word of the tree (the yaml package, the YAML format)") }).strict()])).optional().describe(`External technologies this implementation binds to (e.g. ["mysql"], or [{ "name": "yaml", "matches": ["yaml package", "parseDocument"] }] when the bare name would match a common word) \u2014 declares this component's ownership tree as the technology's home; references outside it are flagged (TECH_LEAKAGE) and contract identifiers must stay intent-language. Only for Adapter/Store/Registry/Index components.`),
38630
38727
  injectedParams: import_zod11.z.array(import_zod11.z.string()).optional().describe("The parameter names this realization takes BEFORE the ones its contract declares \u2014 a config object, a data root, the transport handles a portal is handed. Supplied by whatever wires the component up, never by the caller the contract describes, which is why they belong here and not to the contract: another realization may hold them as fields instead. Declared rather than guessed, because a leading parameter the contract does not name cannot be told from one it named under a different name (`seed(config)` realized as `bootstrapInstance(cfg)`); a leading parameter this list does not name is reported (UNDECLARED_PARAM). Only a LEADING run is dropped \u2014 one of these names appearing after the contract's own parameters is an argument in the middle of the caller's list, not wiring"),
38631
38728
  detail: detailEnum.optional().describe("Spec-level narrative detail default for all methods"),
38632
38729
  conformance: conformanceEnum.optional().describe("Spec-level conformance tier default: declared | anchored | off (omitted = stereotype default: Portal \u2192 anchored, else declared)"),
@@ -38905,7 +39002,7 @@ ${testsBlock}` : ""}`, deletion);
38905
39002
  inputSchema: {
38906
39003
  kind: import_zod11.z.enum(["system", "subsystem", "component", "interface", "implementation", "type"]).describe("The spec kind to update (system = the singleton L0 \u2014 vision, boundaries, globalRequirements, databases, and publicInterfaces: the project export table, each entry a re-export {from, component, interface, typeDef, as, name, type, details, audience: project|department|instance|partner|external} \u2014 from alone re-exports everything a subsystem exports, and a legacy {id, subsystem} reads as {as, from})"),
38907
39004
  id: import_zod11.z.string().describe("The ID of the spec to update (namespaced if needed)"),
38908
- delta: import_zod11.z.record(import_zod11.z.any()).describe(`The partial fields to merge into the spec. ARRAYS UPSERT, they do not replace: an array whose elements carry an identity is merged element-by-element, so a delta naming ONE element leaves the others intact. Identity is "name" or "id" by default, and per field: dispatch by "capability", a listener's mounts by "portal", lifecycle by phase+component+method, emits/subscribesTo by topic+event, trustedLinks by "subsystem", a subsystem's publicInterfaces by component+interface (or type+details for an entry not yet bound to a component, and by from+item+interface+as for a re-export or a type export), invariants and patterns by "id", lint.allow by "code" AND "at" (an allow covers one occurrence, so several may share a code on one spec), an interface method's findings by "code", boundaries by "name", globalRequirements by "description", switch cases by "value", try catches by "error". Identity merging applies at EVERY depth, including an array INSIDE an element (a method's params, a step's catches). Add "action: 'delete'" (or "remove: true") alongside that identity to REMOVE an element \u2014 including a stale lint allow. Arrays of plain STRINGS (owns, dependsOn, guarantees) carry no per-element identity and are replaced wholesale; pass [] to clear any array outright. To REMOVE an optional field entirely, list it in "unset": e.g. {"unset": ["basePath", "variant"]} \u2014 passing null/undefined means "no change" (they are skipped), and writing "" would leave the field present but empty, which is a different and usually wrong spec. Unsetting a required field is refused by schema validation, which names it. For narrative steps, match by "stepNumber" and use "action: 'insert'" (shifts subsequent steps up) or "action: 'delete'" (shifts subsequent steps down and removes it). Step entries apply in ASCENDING stepNumber order, each against the numbering the earlier entries of the SAME delta left behind \u2014 delete step 3 and step 7 becomes step 6 \u2014 so prefer labels, and restate the step's "label" or "description" on a delete to have it checked against the step actually addressed. Renumbering RELOCATES every flow jump field (onTrueStep/onFalseStep/cases.step/defaultStep/endStep/catches.step/finallyStep/toStep) in the same narrative. A delete is REJECTED when the narrative has no such step, when a jump still targets it (retarget the referrers first), when a restated label/description does not match, or when it is a loop/try/parallel header whose body would be left standing (retype the header first to dissolve the region, then delete it). Changing a step's "type" REBUILDS it for the new type: its description and label are kept and every field the new type cannot carry is dropped (returned as a NOTICE); a delta that retypes AND sets such a field is refused. A step delta is also refused when it carries a marker the merge does not recognise: a non-boolean "remove", an "action" that is neither "insert" nor "delete", a "captureJumps" outside an insert, or no "stepNumber" to address. Inserting AT a jump target relocates those jumps past the inserted step by default (a NOTICE is returned) \u2014 add "captureJumps": true on the inserted step to retarget entry jumps onto it (loop/try endStep region tails always relocate with the body and are never captured). Every jump field has a "*Label" twin (toLabel, onFalseLabel, endLabel, \u2026, and "label" on a cases/catches entry) resolved against step labels AFTER the merge, so a delta may anchor on a label only pre-existing steps carry; a label the delta supplies REPLACES the stored number it twins, while setting the number and its label together in one delta is refused as a contradiction. Reference ids in deltas may use LOCAL names \u2014 they are qualified against the spec's namespace exactly as the loader would. Per-spec lint suppression: set "lint: { allow: [{ code, at, covers, reason }] }" to silence a WARNING or NOTICE code on this spec only (errors always surface; stale allows are flagged). An allow covers EXACTLY the occurrence it names: "at" is the site the finding names (a contract method, an import edge "from -> to", a declared edge "component -> target") and is REQUIRED for a code whose findings report one, while a finding that reports no site is covered only by an allow that names none; "covers" lists the units an aggregating finding reports, and the allow silences it only when every one is listed. This delta is deliberately OPEN below its top level \u2014 the shapes nest further than a schema here should restate \u2014 so a key that is not a field at its depth is not refused, it is NAMED BACK under NO EFFECT in the answer, together with any value the spec already held and any "unset" that removed nothing. Read that list: it is where a nested typo shows up.`),
39005
+ delta: import_zod11.z.record(import_zod11.z.any()).describe(`The partial fields to merge into the spec. ARRAYS UPSERT, they do not replace: an array whose elements carry an identity is merged element-by-element, so a delta naming ONE element leaves the others intact. Identity is "name" or "id" by default, and per field: dispatch by "capability", a listener's mounts by "portal", lifecycle by phase+component+method, emits/subscribesTo by topic+event, trustedLinks by "subsystem", a subsystem's publicInterfaces by component+interface (or type+details for an entry not yet bound to a component, and by from+item+interface+as for a re-export or a type export), invariants and patterns by "id", lint.allow by "code" AND "at" (an allow covers one occurrence, so several may share a code on one spec), an interface method's findings by "code", boundaries by "name", globalRequirements by "description", switch cases by "value", try catches by "error". Identity merging applies at EVERY depth, including an array INSIDE an element (a method's params, a step's catches). Add "action: 'delete'" (or "remove: true") alongside that identity to REMOVE an element \u2014 including a stale lint allow. Arrays of plain STRINGS (owns, dependsOn, guarantees) carry no per-element identity and are replaced wholesale; pass [] to clear any array outright. To REMOVE an optional field entirely, list it in "unset": e.g. {"unset": ["basePath", "variant"]} \u2014 passing null/undefined means "no change" (they are skipped), and writing "" would leave the field present but empty, which is a different and usually wrong spec. Unsetting a required field is refused by schema validation, which names it. For narrative steps, match by "stepNumber" and use "action: 'insert'" (shifts subsequent steps up) or "action: 'delete'" (shifts subsequent steps down and removes it). Step entries apply in ASCENDING stepNumber order, each against the numbering the earlier entries of the SAME delta left behind \u2014 delete step 3 and step 7 becomes step 6 \u2014 so prefer labels, and restate the step's "label" or "description" on a delete to have it checked against the step actually addressed. Renumbering RELOCATES every flow jump field (onTrueStep/onFalseStep/cases.step/defaultStep/endStep/catches.step/finallyStep/toStep) in the same narrative. Only STORED jumps relocate: a jump the delta itself writes (on an inserted, appended or edited step) names its target in the numbering the WHOLE delta leaves behind, after all of its inserts and deletes, and no later step of the same delta moves it. A method the delta ADDS without a narrative is an intent-level method with none ([]). A delete is REJECTED when the narrative has no such step, when a jump still targets it (retarget the referrers first), when a restated label/description does not match, or when it is a loop/try/parallel header whose body would be left standing (retype the header first to dissolve the region, then delete it). Changing a step's "type" REBUILDS it for the new type: its description and label are kept and every field the new type cannot carry is dropped (returned as a NOTICE); a delta that retypes AND sets such a field is refused. A step delta is also refused when it carries a marker the merge does not recognise: a non-boolean "remove", an "action" that is neither "insert" nor "delete", a "captureJumps" outside an insert, or no "stepNumber" to address. Inserting AT a jump target relocates those jumps past the inserted step by default (a NOTICE is returned) \u2014 add "captureJumps": true on the inserted step to retarget entry jumps onto it (loop/try endStep region tails always relocate with the body and are never captured). Every jump field has a "*Label" twin (toLabel, onFalseLabel, endLabel, \u2026, and "label" on a cases/catches entry) resolved against step labels AFTER the merge, so a delta may anchor on a label only pre-existing steps carry; a label the delta supplies REPLACES the stored number it twins, while setting the number and its label together in one delta is refused as a contradiction. Reference ids in deltas may use LOCAL names \u2014 they are qualified against the spec's namespace exactly as the loader would. Per-spec lint suppression: set "lint: { allow: [{ code, at, covers, reason }] }" to silence a WARNING or NOTICE code on this spec only (errors always surface; stale allows are flagged). An allow covers EXACTLY the occurrence it names: "at" is the site the finding names (a contract method, an import edge "from -> to", a declared edge "component -> target") and is REQUIRED for a code whose findings report one, while a finding that reports no site is covered only by an allow that names none; "covers" lists the units an aggregating finding reports, and the allow silences it only when every one is listed. This delta is deliberately OPEN below its top level \u2014 the shapes nest further than a schema here should restate \u2014 so a key that is not a field at its depth is not refused, it is NAMED BACK under NO EFFECT in the answer, together with any value the spec already held and any "unset" that removed nothing. Read that list: it is where a nested typo shows up.`),
38909
39006
  dryRun: import_zod11.z.boolean().optional().describe("Ask what this delta WOULD do instead of doing it. The whole write runs, the candidate gate included, and the answer is the change report it would have produced \u2014 marked DRY RUN, with nothing stamped and not one byte of the stored file moved. Use it before a delta that renumbers a long narrative.")
38910
39007
  },
38911
39008
  outputSchema: specChangeReportOutput
@@ -39244,6 +39341,7 @@ function loadProjectConfig3() {
39244
39341
  SystemPublicInterfaceSchema,
39245
39342
  SystemSpecSchema,
39246
39343
  TargetConfigSchema,
39344
+ TechnologySchema,
39247
39345
  TemplateNotFoundError,
39248
39346
  TemplateSchema,
39249
39347
  TopologyConfigSchema,
@@ -39271,6 +39369,7 @@ function loadProjectConfig3() {
39271
39369
  assertProjectInitialized,
39272
39370
  backendCommand,
39273
39371
  backfillChainedSubprojectConfigs,
39372
+ backupBeside,
39274
39373
  buildCanvasDataModel,
39275
39374
  buildProjectGraph,
39276
39375
  buildRuleContext,
@@ -39370,6 +39469,7 @@ function loadProjectConfig3() {
39370
39469
  internalizeMember,
39371
39470
  isDraftSubsystem,
39372
39471
  isLogic,
39472
+ isOutsideRoot,
39373
39473
  isOwnComponentEntry,
39374
39474
  isPattern,
39375
39475
  isProjectInitialized,
@@ -39499,6 +39599,8 @@ function loadProjectConfig3() {
39499
39599
  stepFieldsFor,
39500
39600
  stepGraph,
39501
39601
  syncContextFiles,
39602
+ technologyName,
39603
+ technologyTokens,
39502
39604
  toMarkdown,
39503
39605
  typeBindingOf,
39504
39606
  typeGenericParameters,