@wairon/cli 5.1.1-dev.48 → 5.1.1-dev.49

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.48";
68
+ WAIRON_VERSION = "5.1.1-dev.49";
69
69
  GITHUB_REPO = "SYW-Apps/Waffle-AIron";
70
70
  SUPPORTED_ALIASES = ["wai"];
71
71
  SCAN_EXCLUDE_DIRS = /* @__PURE__ */ new Set([
@@ -17746,15 +17746,6 @@ function componentIdsUnder(dir) {
17746
17746
  }
17747
17747
  return ids;
17748
17748
  }
17749
- function specKind(raw) {
17750
- if ("componentType" in raw) return "component";
17751
- if ("parentSystem" in raw) return "subsystem";
17752
- if ("vision" in raw) return "system";
17753
- if ("component" in raw && Array.isArray(raw.methods)) return "interface";
17754
- if ("contract" in raw && Array.isArray(raw.methods)) return "implementation";
17755
- if ("kind" in raw && Array.isArray(raw.fields)) return "type";
17756
- return void 0;
17757
- }
17758
17749
  function rewriteRefFields(specsDir, remap, excludeDir) {
17759
17750
  const rewritten = [];
17760
17751
  for (const file of listFilesRecursive(specsDir, ".yaml")) {
@@ -17766,81 +17757,10 @@ function rewriteRefFields(specsDir, remap, excludeDir) {
17766
17757
  continue;
17767
17758
  }
17768
17759
  if (!raw || typeof raw !== "object") continue;
17769
- let changed = false;
17770
- const rewrite = (holder, key, position) => {
17771
- const ref = holder?.[key];
17772
- if (typeof ref !== "string") return;
17773
- const next = remap(ref, position);
17774
- if (next !== ref) {
17775
- holder[key] = next;
17776
- changed = true;
17777
- }
17778
- };
17779
- const rewriteMethod = (holder, key, owner) => {
17780
- const name = holder?.[key];
17781
- if (typeof name !== "string" || typeof owner !== "string") return;
17782
- const next = remap(name, "method", owner);
17783
- if (next !== name) {
17784
- holder[key] = next;
17785
- changed = true;
17786
- }
17787
- };
17788
- const entries = (list2) => Array.isArray(list2) ? list2 : [];
17789
- const rewriteEach = (list2, position) => {
17790
- entries(list2).forEach((_, i) => rewrite(list2, i, position));
17791
- };
17792
- const rewritePublished = (list2) => {
17793
- for (const published of entries(list2)) {
17794
- rewrite(published, "component", "component");
17795
- rewrite(published, "interface", "interface");
17796
- }
17797
- };
17798
17760
  const kind = specKind(raw);
17799
- if (kind === "component") {
17800
- rewriteEach(raw.dependsOn, "component");
17801
- rewriteEach(raw.owns, "component");
17802
- for (const binding of entries(raw.dispatch)) {
17803
- rewriteMethod(binding, "method", binding?.component);
17804
- rewrite(binding, "component", "component");
17805
- }
17806
- } else if (kind === "subsystem") {
17807
- for (const entrypoint of entries(raw.lifecycle)) {
17808
- rewriteMethod(entrypoint, "method", entrypoint?.component);
17809
- rewrite(entrypoint, "component", "component");
17810
- }
17811
- rewritePublished(raw.publicInterfaces);
17812
- } else if (kind === "system") {
17813
- rewritePublished(raw.publicInterfaces);
17814
- } else if (kind === "interface") {
17815
- rewrite(raw, "component", "component");
17816
- for (const method2 of raw.methods) {
17817
- for (const param of entries(method2?.params)) rewrite(param, "type", "type");
17818
- }
17819
- } else if (kind === "implementation") {
17820
- rewrite(raw, "contract", "interface");
17821
- for (const method2 of raw.methods) {
17822
- for (const step of entries(method2?.narrative)) {
17823
- rewriteMethod(step, "targetMethod", step?.targetComponent);
17824
- rewrite(step, "targetComponent", "component");
17825
- const source = step?.auth?.from;
17826
- if (typeof source === "string" && source.startsWith(COMPONENT_AUTH_SOURCE)) {
17827
- const id = source.slice(COMPONENT_AUTH_SOURCE.length);
17828
- const next = remap(id, "auth-source");
17829
- if (next !== id) {
17830
- step.auth.from = `${COMPONENT_AUTH_SOURCE}${next}`;
17831
- changed = true;
17832
- }
17833
- }
17834
- }
17835
- }
17836
- } else if (kind === "type") {
17837
- rewrite(raw, "componentClass", "entity-class");
17838
- for (const field of raw.fields) rewrite(field, "type", "type");
17839
- }
17840
- if (changed && kind) {
17841
- writeYamlFile(file, raw);
17842
- rewritten.push({ kind, id: kind === "system" ? "system" : String(raw.id) });
17843
- }
17761
+ if (!rewriteSpecRefs(raw, remap) || !kind) continue;
17762
+ writeYamlFile(file, raw);
17763
+ rewritten.push({ kind, id: kind === "system" ? "system" : String(raw.id) });
17844
17764
  }
17845
17765
  return rewritten;
17846
17766
  }
@@ -18052,7 +17972,7 @@ function collectMentions(specsDir, methodName, moved) {
18052
17972
  function identifierPattern(methodName) {
18053
17973
  return new RegExp(`(?<![A-Za-z0-9_])${methodName}(?![A-Za-z0-9_])`);
18054
17974
  }
18055
- var fs13, path17, COMPONENT_AUTH_SOURCE, METHOD_NAME, PROSE_FIELDS;
17975
+ var fs13, path17, METHOD_NAME, PROSE_FIELDS;
18056
17976
  var init_provision = __esm({
18057
17977
  "src/core/provision.ts"() {
18058
17978
  "use strict";
@@ -18065,7 +17985,6 @@ var init_provision = __esm({
18065
17985
  init_yaml();
18066
17986
  init_errors();
18067
17987
  init_models();
18068
- COMPONENT_AUTH_SOURCE = "component:";
18069
17988
  METHOD_NAME = /^[a-z][a-zA-Z0-9]*$/;
18070
17989
  PROSE_FIELDS = /* @__PURE__ */ new Set([
18071
17990
  "description",
@@ -24140,6 +24059,7 @@ var init_diagram = __esm({
24140
24059
  // src/core/specs.ts
24141
24060
  var specs_exports = {};
24142
24061
  __export(specs_exports, {
24062
+ COMPONENT_AUTH_SOURCE: () => COMPONENT_AUTH_SOURCE,
24143
24063
  SpecWorkspace: () => SpecWorkspace,
24144
24064
  applySpecStatus: () => applySpecStatus,
24145
24065
  assertContainedProjectPath: () => assertContainedProjectPath,
@@ -24185,6 +24105,7 @@ __export(specs_exports, {
24185
24105
  loadTypeSpec: () => loadTypeSpec,
24186
24106
  loadTypeSpecs: () => loadTypeSpecs,
24187
24107
  markSelectionsBundled: () => markSelectionsBundled,
24108
+ moveMethods: () => moveMethods,
24188
24109
  normalizeComponentLayout: () => normalizeComponentLayout,
24189
24110
  readLockState: () => readLockState,
24190
24111
  rebaseReference: () => rebaseReference,
@@ -24194,6 +24115,7 @@ __export(specs_exports, {
24194
24115
  resolveChainingParent: () => resolveChainingParent,
24195
24116
  resolveSubprojectForNamespace: () => resolveSubprojectForNamespace,
24196
24117
  restoreSpecFiles: () => restoreSpecFiles,
24118
+ rewriteSpecRefs: () => rewriteSpecRefs,
24197
24119
  saveComponentSpec: () => saveComponentSpec,
24198
24120
  saveGroupSpec: () => saveGroupSpec,
24199
24121
  saveImplementationSpec: () => saveImplementationSpec,
@@ -24206,6 +24128,7 @@ __export(specs_exports, {
24206
24128
  setProjectType: () => setProjectType,
24207
24129
  snapshotSpecFiles: () => snapshotSpecFiles,
24208
24130
  specChanges: () => specChanges,
24131
+ specKind: () => specKind,
24209
24132
  specPathsInScope: () => specPathsInScope,
24210
24133
  updateSpec: () => updateSpec,
24211
24134
  upsertPackSelection: () => upsertPackSelection,
@@ -24589,6 +24512,99 @@ function computeSpecTreeSignature(dirs) {
24589
24512
  }
24590
24513
  return parts.join("|");
24591
24514
  }
24515
+ function specKind(raw) {
24516
+ if ("componentType" in raw) return "component";
24517
+ if ("parentSystem" in raw) return "subsystem";
24518
+ if ("vision" in raw) return "system";
24519
+ if ("component" in raw && Array.isArray(raw.methods)) return "interface";
24520
+ if ("contract" in raw && Array.isArray(raw.methods)) return "implementation";
24521
+ if ("kind" in raw && Array.isArray(raw.fields)) return "type";
24522
+ return void 0;
24523
+ }
24524
+ function rewriteSpecRefs(raw, remap) {
24525
+ if (!raw || typeof raw !== "object") return false;
24526
+ let changed = false;
24527
+ const rewrite = (holder, key, position, owner) => {
24528
+ const ref = holder?.[key];
24529
+ if (typeof ref !== "string") return;
24530
+ const next = remap(ref, position, owner);
24531
+ if (next !== ref) {
24532
+ holder[key] = next;
24533
+ changed = true;
24534
+ }
24535
+ };
24536
+ const rewriteMethod = (holder, key, owner) => {
24537
+ const name = holder?.[key];
24538
+ if (typeof name !== "string" || typeof owner !== "string") return;
24539
+ const next = remap(name, "method", owner);
24540
+ if (next !== name) {
24541
+ holder[key] = next;
24542
+ changed = true;
24543
+ }
24544
+ };
24545
+ const rewritePair = (holder, componentKey, methodKey2) => {
24546
+ const component = holder?.[componentKey];
24547
+ const method2 = holder?.[methodKey2];
24548
+ rewriteMethod(holder, methodKey2, component);
24549
+ rewrite(holder, componentKey, "component", typeof method2 === "string" ? method2 : void 0);
24550
+ };
24551
+ const entries = (list2) => Array.isArray(list2) ? list2 : [];
24552
+ const rewriteEach = (list2, position) => {
24553
+ entries(list2).forEach((_, i) => rewrite(list2, i, position));
24554
+ };
24555
+ const rewritePublished = (list2) => {
24556
+ for (const published of entries(list2)) {
24557
+ rewrite(published, "component", "component");
24558
+ rewrite(published, "interface", "interface");
24559
+ }
24560
+ };
24561
+ const kind = specKind(raw);
24562
+ if (kind === "component") {
24563
+ rewriteEach(raw.dependsOn, "component");
24564
+ rewriteEach(raw.owns, "component");
24565
+ for (const binding of entries(raw.dispatch)) rewritePair(binding, "component", "method");
24566
+ } else if (kind === "subsystem") {
24567
+ for (const entrypoint of entries(raw.lifecycle)) rewritePair(entrypoint, "component", "method");
24568
+ rewritePublished(raw.publicInterfaces);
24569
+ } else if (kind === "system") {
24570
+ rewritePublished(raw.publicInterfaces);
24571
+ } else if (kind === "interface") {
24572
+ rewrite(raw, "component", "component");
24573
+ for (const method2 of entries(raw.methods)) {
24574
+ for (const param of entries(method2?.params)) rewrite(param, "type", "type");
24575
+ }
24576
+ } else if (kind === "implementation") {
24577
+ rewrite(raw, "contract", "interface");
24578
+ for (const method2 of entries(raw.methods)) {
24579
+ for (const step of entries(method2?.narrative)) {
24580
+ rewritePair(step, "targetComponent", "targetMethod");
24581
+ const source = step?.auth?.from;
24582
+ if (typeof source === "string" && source.startsWith(COMPONENT_AUTH_SOURCE)) {
24583
+ const id = source.slice(COMPONENT_AUTH_SOURCE.length);
24584
+ const next = remap(id, "auth-source");
24585
+ if (next !== id) {
24586
+ step.auth.from = `${COMPONENT_AUTH_SOURCE}${next}`;
24587
+ changed = true;
24588
+ }
24589
+ }
24590
+ }
24591
+ entries(method2?.calls).forEach((entry, i) => {
24592
+ if (typeof entry !== "string") return;
24593
+ const call = parseDeclaredCall(entry);
24594
+ if (!call) return;
24595
+ const component = remap(call.compId, "component", call.methodName);
24596
+ const name = remap(call.methodName, "method", call.compId);
24597
+ if (component === call.compId && name === call.methodName) return;
24598
+ method2.calls[i] = `${component}.${name}`;
24599
+ changed = true;
24600
+ });
24601
+ }
24602
+ } else if (kind === "type") {
24603
+ rewrite(raw, "componentClass", "entity-class");
24604
+ for (const field of entries(raw.fields)) rewrite(field, "type", "type");
24605
+ }
24606
+ return changed;
24607
+ }
24592
24608
  function summarizeValue(value) {
24593
24609
  if (value === void 0) return "";
24594
24610
  const text2 = typeof value === "string" ? value : JSON.stringify(value) ?? String(value);
@@ -24800,6 +24816,84 @@ function ineffectiveDeltaPaths(delta, merged, stored) {
24800
24816
  walk2(delta, merged, stored, "", "");
24801
24817
  return out;
24802
24818
  }
24819
+ function cloneSpec(spec) {
24820
+ return JSON.parse(JSON.stringify(spec));
24821
+ }
24822
+ function namedMethods(methods) {
24823
+ return methods.map((m) => `"${m}"`).join(", ");
24824
+ }
24825
+ function statusOfContracts(contracts, component) {
24826
+ return contracts.find((i) => i.component === component)?.status;
24827
+ }
24828
+ function reachOf(entries, from, moved) {
24829
+ const reach = /* @__PURE__ */ new Set();
24830
+ for (const entry of entries) {
24831
+ for (const step of entry.narrative ?? []) {
24832
+ const target = step.targetComponent;
24833
+ const method2 = step.targetMethod;
24834
+ if (!target) continue;
24835
+ if (target === from && method2 && moved.has(method2)) continue;
24836
+ reach.add(target);
24837
+ }
24838
+ for (const call of entry.calls ?? []) {
24839
+ const parsed = parseDeclaredCall(call);
24840
+ if (!parsed) continue;
24841
+ if (parsed.compId === from && moved.has(parsed.methodName)) continue;
24842
+ reach.add(parsed.compId);
24843
+ }
24844
+ }
24845
+ return [...reach].sort();
24846
+ }
24847
+ function namesInProse(value, named) {
24848
+ if (Array.isArray(value)) return value.some((v) => namesInProse(v, named));
24849
+ if (!value || typeof value !== "object") return false;
24850
+ return Object.entries(value).some(([key, nested]) => typeof nested === "string" ? PROSE_FIELDS2.has(key) && named.test(nested) : namesInProse(nested, named));
24851
+ }
24852
+ function judgeMoveResult(hooks, plan) {
24853
+ const refusals = [];
24854
+ const notices = [];
24855
+ for (const resulting of [plan.edits.get(`component:${plan.source.id}`), plan.edits.get(`component:${plan.target.id}`)]) {
24856
+ if (!resulting) continue;
24857
+ try {
24858
+ const raised = hooks.gate?.("component", resulting.after);
24859
+ if (raised?.length) notices.push(...raised);
24860
+ } catch (e) {
24861
+ const codes = hooks.assess?.("component", resulting.after) ?? [];
24862
+ refusals.push(...codes.length ? codes : [String(e?.message ?? e)]);
24863
+ }
24864
+ }
24865
+ return { refusals: [...new Set(refusals)], notices };
24866
+ }
24867
+ function weighMoveHome(hooks, candidate, reach, dependencyLimit) {
24868
+ const held = /* @__PURE__ */ new Set([...candidate.dependsOn ?? [], ...candidate.owns ?? []]);
24869
+ const newDependencies = reach.filter((id) => id !== candidate.id && !held.has(id));
24870
+ const resulting = { ...candidate, dependsOn: [...candidate.dependsOn ?? [], ...newDependencies] };
24871
+ const refusals = hooks.assess?.("component", resulting) ?? [];
24872
+ return {
24873
+ component: candidate.id,
24874
+ legal: refusals.length === 0,
24875
+ newDependencies,
24876
+ dependencyCount: resulting.dependsOn.length,
24877
+ dependencyLimit,
24878
+ refusals
24879
+ };
24880
+ }
24881
+ function moveChangeReports(edits, dryRun) {
24882
+ return edits.map((edit) => ({ edit, changes: specChanges(edit.before, edit.after) })).filter(({ changes }) => changes.length > 0).map(({ edit, changes }) => ({
24883
+ kind: edit.kind,
24884
+ id: edit.id,
24885
+ written: !dryRun,
24886
+ dryRun,
24887
+ changes,
24888
+ ineffective: [],
24889
+ notices: [],
24890
+ summary: `${dryRun ? "Would move" : "Moved"} into ${edit.kind} "${edit.id}": ${changes.length} change${changes.length === 1 ? "" : "s"}.`
24891
+ }));
24892
+ }
24893
+ function refusedMoveSummary(from, to, methods, refusals, alternatives) {
24894
+ const cheapest = alternatives.find((c) => c.legal);
24895
+ return `Refused: moving ${namedMethods(methods)} from "${from}" to "${to}" is refused by ${refusals.join(", ")}. Nothing was written. ` + (cheapest ? `The cheapest legal home is "${cheapest.component}" (${cheapest.newDependencies.length} new dependenc${cheapest.newDependencies.length === 1 ? "y" : "ies"}, ${cheapest.dependencyCount} of ${cheapest.dependencyLimit}).` : `No component of this subsystem can take them as they stand \u2014 ${alternatives.length} were weighed.`);
24896
+ }
24803
24897
  function workspaceFor(rootDir) {
24804
24898
  const key = path23.resolve(rootDir);
24805
24899
  let ws = workspaces.get(key);
@@ -25028,7 +25122,10 @@ function findLegacySpecFiles() {
25028
25122
  function updateSpec(kind, id, delta, hooks, dryRun) {
25029
25123
  return current().updateSpec(kind, id, delta, hooks, dryRun);
25030
25124
  }
25031
- var fs17, path23, NO_ROOT_SUBSYSTEMS, SIGNATURE_TTL_MS, DELTA_VERBS, JUMP_LABEL_TWINS, LABEL_TWIN_CONTAINERS, SpecWorkspace, workspaces;
25125
+ function moveMethods(from, to, methods, hooks, dryRun) {
25126
+ return current().moveMethods(from, to, methods, hooks, dryRun);
25127
+ }
25128
+ var fs17, path23, NO_ROOT_SUBSYSTEMS, SIGNATURE_TTL_MS, COMPONENT_AUTH_SOURCE, DELTA_VERBS, JUMP_LABEL_TWINS, LABEL_TWIN_CONTAINERS, SpecWorkspace, DEFAULT_DEPENDENCY_LIMIT, PROSE_FIELDS2, workspaces;
25032
25129
  var init_specs2 = __esm({
25033
25130
  "src/core/specs.ts"() {
25034
25131
  "use strict";
@@ -25047,6 +25144,7 @@ var init_specs2 = __esm({
25047
25144
  init_diagram();
25048
25145
  NO_ROOT_SUBSYSTEMS = /* @__PURE__ */ new Set();
25049
25146
  SIGNATURE_TTL_MS = 2e3;
25147
+ COMPONENT_AUTH_SOURCE = "component:";
25050
25148
  DELTA_VERBS = /* @__PURE__ */ new Set(["action", "remove", "captureJumps"]);
25051
25149
  JUMP_LABEL_TWINS = /* @__PURE__ */ new Set([
25052
25150
  "onTrueLabel",
@@ -26905,7 +27003,382 @@ var init_specs2 = __esm({
26905
27003
  summary: `Updated ${kind} "${id}": ${changes.length} change${changes.length === 1 ? "" : "s"}.`
26906
27004
  };
26907
27005
  }
27006
+ // -------------------------------------------------------------------------
27007
+ // Method move (icore_orchestrator.moveMethods)
27008
+ //
27009
+ // A rename changes a NAME and can stay mechanical. A move changes which
27010
+ // component OWNS behaviour, which is exactly what the stereotype and
27011
+ // dependency rules judge — so the mechanics live here and the judgement
27012
+ // arrives as an injected hook, for the same reason `gate` does: this module
27013
+ // must not import the rule engine.
27014
+ //
27015
+ // It is several writes at once: both contracts, both implementations, the
27016
+ // target component (which gains the dependencies the moved narratives call),
27017
+ // and every caller whose steps, dispatch bindings, lifecycle entrypoints or
27018
+ // `calls` entries name the old home. Every edit is computed before the first
27019
+ // is written and a failed write restores the ones already made, because a
27020
+ // half-moved method leaves callers naming a home it no longer has.
27021
+ // -------------------------------------------------------------------------
27022
+ /**
27023
+ * Move contract methods from one component to another, and every reference
27024
+ * with them.
27025
+ *
27026
+ * Each method leaves the source component's contract and arrives on the
27027
+ * target's, carrying its signature, params, returns, guarantees and endpoint
27028
+ * binding unchanged; its implementation entry travels with it, carrying
27029
+ * narrative, sourcePath, symbol, detail, intent, calls and findings. Prose is
27030
+ * never rewritten and a wire endpoint keeps its address — moving a method
27031
+ * between components must not silently re-address an RPC — so both are
27032
+ * reported as mentions instead.
27033
+ *
27034
+ * Refuses before the first write (`unmovable request`): a component or method
27035
+ * that does not exist, a method name already declared on the target, a source
27036
+ * and target that are the same component, and a component inside a chained
27037
+ * subproject.
27038
+ */
27039
+ moveMethods(from, to, methods, hooks, dryRun = false) {
27040
+ const source = this.loadComponentSpec(from);
27041
+ const complaint = this.unmovableRequest(source, from, to, methods);
27042
+ if (complaint) throw new Error(`unmovable request: ${complaint}`);
27043
+ const contracts = this.loadInterfaceSpecs();
27044
+ const plan = this.planMethodMove(source, to, methods, contracts);
27045
+ const repointed = this.repointToNewHome(from, to, methods, plan);
27046
+ const mentions = this.moveMentions(from, plan);
27047
+ if (hooks) {
27048
+ const verdict = judgeMoveResult(hooks, plan);
27049
+ plan.notices.push(...verdict.notices);
27050
+ if (verdict.refusals.length > 0) {
27051
+ const alternatives = this.rankMoveHomes(hooks, from, to, plan);
27052
+ return {
27053
+ moved: false,
27054
+ from,
27055
+ to,
27056
+ methods,
27057
+ edits: [],
27058
+ repointed: 0,
27059
+ refusals: verdict.refusals,
27060
+ alternatives,
27061
+ notices: plan.notices,
27062
+ summary: refusedMoveSummary(from, to, methods, verdict.refusals, alternatives),
27063
+ dryRun,
27064
+ mentions
27065
+ };
27066
+ }
27067
+ }
27068
+ if (dryRun) {
27069
+ const edits2 = moveChangeReports([...plan.edits.values()], true);
27070
+ return {
27071
+ moved: false,
27072
+ from,
27073
+ to,
27074
+ methods,
27075
+ edits: edits2,
27076
+ repointed,
27077
+ refusals: [],
27078
+ alternatives: [],
27079
+ notices: plan.notices,
27080
+ summary: `Dry run: moving ${namedMethods(methods)} from "${from}" to "${to}" would touch ${edits2.length} spec${edits2.length === 1 ? "" : "s"} and re-point ${repointed} reference${repointed === 1 ? "" : "s"}. Nothing was written.`,
27081
+ dryRun: true,
27082
+ mentions
27083
+ };
27084
+ }
27085
+ const edits = this.writeMoveEdits([...plan.edits.values()]);
27086
+ return {
27087
+ moved: true,
27088
+ from,
27089
+ to,
27090
+ methods,
27091
+ edits,
27092
+ repointed,
27093
+ refusals: [],
27094
+ alternatives: [],
27095
+ notices: plan.notices,
27096
+ summary: `Moved ${namedMethods(methods)} from "${from}" to "${to}": ${edits.length} spec${edits.length === 1 ? "" : "s"} written, ${repointed} reference${repointed === 1 ? "" : "s"} re-pointed${mentions.length ? `, ${mentions.length} still naming the old home in prose or on the wire` : ""}.`,
27097
+ dryRun: false,
27098
+ mentions
27099
+ };
27100
+ }
27101
+ /**
27102
+ * Why this move cannot be attempted at all, or null when the request is
27103
+ * answerable. Every check here is about what the CALLER named, so its answer
27104
+ * is a mistake to correct — never a rule refusing the design.
27105
+ */
27106
+ unmovableRequest(source, from, to, methods) {
27107
+ const chained = [from, to].find((id) => id.includes("::"));
27108
+ if (chained) {
27109
+ return `"${chained}" lives in a chained subproject; move its methods from that project's own root.`;
27110
+ }
27111
+ if (!source) return `no component has the id "${from}".`;
27112
+ if (!this.loadComponentSpec(to)) return `no component has the id "${to}".`;
27113
+ if (from === to) return `"${from}" is both the source and the target; a method cannot move to where it already lives.`;
27114
+ if (methods.length === 0) return "no method names were given.";
27115
+ const contracts = this.loadInterfaceSpecs();
27116
+ const ofSource = contracts.filter((i) => i.component === from);
27117
+ const ofTarget = contracts.filter((i) => i.component === to);
27118
+ for (const name of methods) {
27119
+ const declaring = ofSource.filter((i) => i.methods.some((m) => m.name === name));
27120
+ if (declaring.length === 0) return `no contract of "${from}" declares the method "${name}".`;
27121
+ if (declaring.length > 1) {
27122
+ return `${declaring.map((i) => `"${i.id}"`).join(" and ")} both declare "${name}" on "${from}" \u2014 a move has to know which contract the method leaves, so split or rename them first.`;
27123
+ }
27124
+ }
27125
+ const taken = methods.filter((name) => ofTarget.some((i) => i.methods.some((m) => m.name === name)));
27126
+ if (taken.length > 0) {
27127
+ return `"${to}" already declares ${namedMethods(taken)}.`;
27128
+ }
27129
+ const receiving = this.moveTargetContract(to, ofTarget);
27130
+ if (typeof receiving === "string") return receiving;
27131
+ const travelling = this.loadImplementationSpecs().some((impl) => ofSource.some((i) => i.id === impl.contract) && impl.methods.some((m) => methods.includes(m.name)));
27132
+ if (travelling && !this.loadImplementationSpecs().some((i) => i.contract === receiving.id)) {
27133
+ return `no implementation realizes "${receiving.id}", so the moved methods' narratives would have nowhere to arrive.`;
27134
+ }
27135
+ return null;
27136
+ }
27137
+ /** The contract of `to` that receives the methods, or why that cannot be decided. */
27138
+ moveTargetContract(to, ofTarget) {
27139
+ if (ofTarget.length === 0) return `"${to}" declares no contract, so the methods would have nowhere to arrive.`;
27140
+ if (ofTarget.length === 1) return ofTarget[0];
27141
+ const named = ofTarget.find((i) => i.id === `i${to}` || i.id.endsWith(`::i${to}`));
27142
+ return named ?? `"${to}" declares ${ofTarget.length} contracts and none is named "i${to}", so which one receives the methods is not decidable.`;
27143
+ }
27144
+ /**
27145
+ * The whole move as staged edits, computed before anything is written: the
27146
+ * contract entries that leave the source and arrive on the target, the
27147
+ * implementation entries that travel with them, and the dependencies the
27148
+ * moved narratives call that the target does not have yet.
27149
+ */
27150
+ planMethodMove(source, to, methods, contracts) {
27151
+ const target = this.loadComponentSpec(to);
27152
+ const receiving = this.moveTargetContract(to, contracts.filter((i) => i.component === to));
27153
+ const implementations = this.loadImplementationSpecs();
27154
+ const plan = {
27155
+ edits: /* @__PURE__ */ new Map(),
27156
+ source,
27157
+ target,
27158
+ reach: [],
27159
+ newDependencies: [],
27160
+ wireBound: [],
27161
+ notices: []
27162
+ };
27163
+ const arriving = [];
27164
+ const travelling = [];
27165
+ for (const contract of contracts.filter((i) => i.component === source.id)) {
27166
+ const leaving = contract.methods.filter((m) => methods.includes(m.name));
27167
+ if (leaving.length === 0) continue;
27168
+ arriving.push(...cloneSpec(leaving));
27169
+ const remaining = cloneSpec(contract);
27170
+ remaining.methods = remaining.methods.filter((m) => !methods.includes(m.name));
27171
+ this.stageMoveEdit(plan, "interface", contract.id, contract, remaining);
27172
+ if (contract.methods.length === leaving.length) {
27173
+ plan.notices.push(`"${contract.id}" is left with no methods \u2014 "${source.id}" no longer declares anything on it.`);
27174
+ }
27175
+ for (const impl of implementations.filter((i) => i.contract === contract.id)) {
27176
+ const moving = impl.methods.filter((m) => methods.includes(m.name));
27177
+ if (moving.length === 0) continue;
27178
+ travelling.push(...cloneSpec(moving));
27179
+ const kept = cloneSpec(impl);
27180
+ kept.methods = kept.methods.filter((m) => !methods.includes(m.name));
27181
+ this.stageMoveEdit(plan, "implementation", impl.id, impl, kept);
27182
+ }
27183
+ }
27184
+ this.stageMoveEdit(plan, "interface", receiving.id, receiving, {
27185
+ ...cloneSpec(receiving),
27186
+ methods: [...cloneSpec(receiving.methods), ...arriving]
27187
+ });
27188
+ if (arriving.some((m) => m.endpoint)) plan.wireBound.push(receiving.id);
27189
+ if (receiving.status !== void 0 && receiving.status !== statusOfContracts(contracts, source.id)) {
27190
+ plan.notices.push(`"${receiving.id}" is ${receiving.status} where the methods came from a ${statusOfContracts(contracts, source.id)} contract \u2014 the status of a moved method is the status of its new home.`);
27191
+ }
27192
+ for (const entry of travelling) {
27193
+ if ((entry.narrative ?? []).length === 0 && !entry.intent) {
27194
+ plan.notices.push(`"${entry.name}" moves with an empty narrative and no intent \u2014 nothing followed it but its name.`);
27195
+ }
27196
+ }
27197
+ const targetImpl = implementations.find((i) => i.contract === receiving.id);
27198
+ if (travelling.length > 0 && targetImpl) {
27199
+ this.stageMoveEdit(plan, "implementation", targetImpl.id, targetImpl, {
27200
+ ...cloneSpec(targetImpl),
27201
+ methods: [...cloneSpec(targetImpl.methods), ...travelling]
27202
+ });
27203
+ }
27204
+ plan.reach = reachOf(travelling, source.id, new Set(methods)).filter((id) => id !== to);
27205
+ const held = /* @__PURE__ */ new Set([...target.dependsOn ?? [], ...target.owns ?? []]);
27206
+ plan.newDependencies = plan.reach.filter((id) => !held.has(id) && id !== to);
27207
+ this.stageMoveEdit(plan, "component", source.id, source, cloneSpec(source));
27208
+ this.stageMoveEdit(plan, "component", target.id, target, {
27209
+ ...cloneSpec(target),
27210
+ dependsOn: [...target.dependsOn ?? [], ...plan.newDependencies]
27211
+ });
27212
+ return plan;
27213
+ }
27214
+ /** Stage one spec's rewrite, keeping the version it started from as the snapshot a failed write restores. */
27215
+ stageMoveEdit(plan, kind, id, before, after) {
27216
+ plan.edits.set(`${kind}:${id}`, { kind, id, before, after });
27217
+ }
27218
+ /**
27219
+ * Re-point every reference that names the old home, through the one
27220
+ * reference-field table, and answer with how many followed.
27221
+ *
27222
+ * A reference is only re-pointed when it names BOTH the old home and a method
27223
+ * that is moving: another method of the same component, and another
27224
+ * component's method of the same name, stay exactly where they point.
27225
+ */
27226
+ repointToNewHome(from, to, methods, plan) {
27227
+ const moved = new Set(methods);
27228
+ let repointed = 0;
27229
+ const remap = (ref, position, owner) => {
27230
+ if (position !== "component" || ref !== from || !owner || !moved.has(owner)) return ref;
27231
+ repointed += 1;
27232
+ return to;
27233
+ };
27234
+ for (const { kind, id, spec } of this.everySpecInTree()) {
27235
+ const staged = plan.edits.get(`${kind}:${id}`);
27236
+ const subject = staged ? staged.after : cloneSpec(spec);
27237
+ if (!rewriteSpecRefs(subject, remap)) continue;
27238
+ if (staged) continue;
27239
+ if (id.includes("::")) {
27240
+ plan.notices.push(`"${id}" lives in a chained subproject and still names "${from}" \u2014 re-point it from that project's own root.`);
27241
+ repointed -= 1;
27242
+ continue;
27243
+ }
27244
+ this.stageMoveEdit(plan, kind, id, spec, subject);
27245
+ }
27246
+ plan.notices.push(...this.callersOwingADependency(to, plan));
27247
+ return repointed;
27248
+ }
27249
+ /**
27250
+ * The components that now reach the new home without declaring it. The move
27251
+ * re-points the reference; it never edits another component's dependency
27252
+ * list, because a dependency nobody judged is exactly what the gate exists to
27253
+ * prevent.
27254
+ */
27255
+ callersOwingADependency(to, plan) {
27256
+ const contracts = this.loadInterfaceSpecs();
27257
+ const owing = /* @__PURE__ */ new Set();
27258
+ for (const edit of plan.edits.values()) {
27259
+ const owner = edit.kind === "component" ? edit.id : contracts.find((i) => i.id === edit.after.contract)?.component;
27260
+ if (!owner || owner === to) continue;
27261
+ const component = owner === plan.target.id ? plan.target : this.loadComponentSpec(owner);
27262
+ if (!component) continue;
27263
+ const held = /* @__PURE__ */ new Set([...component.dependsOn ?? [], ...component.owns ?? []]);
27264
+ if (!held.has(to)) owing.add(owner);
27265
+ }
27266
+ return [...owing].map((id) => `"${id}" now reaches "${to}" but does not declare it \u2014 add the dependency, or the call is undeclared.`);
27267
+ }
27268
+ /**
27269
+ * Where these methods could live instead, cheapest legal home first, with the
27270
+ * requested target among them so its shortfall reads beside the others.
27271
+ *
27272
+ * The store enumerates, because the store holds the tree; the hook judges,
27273
+ * because the gate holds the judgement. `assess` answers rather than throws,
27274
+ * so this is a search and not a sequence of caught exceptions.
27275
+ */
27276
+ rankMoveHomes(hooks, from, to, plan) {
27277
+ const limit = this.dependencyLimit();
27278
+ const pool = this.loadComponentSpecs().filter((c) => c.subsystem === plan.source.subsystem && c.id !== from);
27279
+ if (!pool.some((c) => c.id === to)) pool.push(plan.target);
27280
+ return pool.map((candidate) => weighMoveHome(hooks, candidate, plan.reach, limit)).sort((a, b) => Number(b.legal) - Number(a.legal) || a.newDependencies.length - b.newDependencies.length || a.component.localeCompare(b.component));
27281
+ }
27282
+ /**
27283
+ * The project's `maxComponentDependencies`, so a candidate's dependency count
27284
+ * can be read without looking up the config. A project that sets none is read
27285
+ * against the same default the coupling-health rule applies.
27286
+ */
27287
+ dependencyLimit() {
27288
+ try {
27289
+ return projectConfigRepository.load()?.rules?.complexity?.maxComponentDependencies ?? DEFAULT_DEPENDENCY_LIMIT;
27290
+ } catch {
27291
+ return DEFAULT_DEPENDENCY_LIMIT;
27292
+ }
27293
+ }
27294
+ /** Every spec in the bound tree, with the kind and id a staged edit addresses it by. */
27295
+ *everySpecInTree() {
27296
+ const system = this.loadSystemSpec();
27297
+ if (system) yield { kind: "system", id: system.name, spec: system };
27298
+ for (const spec of this.loadSubsystemSpecs()) yield { kind: "subsystem", id: spec.id, spec };
27299
+ for (const spec of this.loadComponentSpecs()) yield { kind: "component", id: spec.id, spec };
27300
+ for (const spec of this.loadInterfaceSpecs()) yield { kind: "interface", id: spec.id, spec };
27301
+ for (const spec of this.loadImplementationSpecs()) yield { kind: "implementation", id: spec.id, spec };
27302
+ for (const spec of this.loadTypeSpecs()) yield { kind: "type", id: spec.id, spec };
27303
+ }
27304
+ /**
27305
+ * What still names the old home once the move is written, every one of it
27306
+ * left exactly as it was: the specs whose prose carries the id, and the
27307
+ * receiving contract whose moved methods keep their wire address. A move
27308
+ * never edits prose and never re-addresses an RPC.
27309
+ */
27310
+ moveMentions(from, plan) {
27311
+ const mentions = new Set(plan.wireBound);
27312
+ const named = new RegExp(`(?<![A-Za-z0-9_])${from}(?![A-Za-z0-9_])`);
27313
+ for (const { kind, id, spec } of this.everySpecInTree()) {
27314
+ if (namesInProse(spec, named)) mentions.add(kind === "system" ? "system" : id);
27315
+ }
27316
+ return [...mentions];
27317
+ }
27318
+ /**
27319
+ * Write every staged edit, restoring the ones already written if one fails.
27320
+ *
27321
+ * The snapshots are the versions the plan started from, so a restore puts the
27322
+ * authored content back exactly; only `updatedAt` reads as touched, because
27323
+ * the store stamps every write it accepts. Nothing invalidates the cache
27324
+ * here: every save does it already, and a second call would only be a hop
27325
+ * across a component boundary that no narrative claims.
27326
+ */
27327
+ writeMoveEdits(edits) {
27328
+ const reports = moveChangeReports(edits, false);
27329
+ const written = [];
27330
+ try {
27331
+ for (const report2 of reports) {
27332
+ const edit = edits.find((e) => e.kind === report2.kind && e.id === report2.id);
27333
+ report2.notices.push(...this.saveMovedSpec(edit.kind, edit.after));
27334
+ written.push(edit);
27335
+ }
27336
+ } catch (e) {
27337
+ for (const edit of [...written].reverse()) {
27338
+ try {
27339
+ this.saveMovedSpec(edit.kind, edit.before);
27340
+ } catch {
27341
+ }
27342
+ }
27343
+ throw new Error(
27344
+ `move-write-failed: ${String(e?.message ?? e)} \u2014 ${written.length} edit${written.length === 1 ? "" : "s"} already written ${written.length === 1 ? "was" : "were"} restored, so nothing moved.`
27345
+ );
27346
+ }
27347
+ return reports;
27348
+ }
27349
+ /** Persist one moved spec through the store's own writer, and answer with its placement notices. */
27350
+ saveMovedSpec(kind, spec) {
27351
+ const opts = { allowStatusDemotion: true };
27352
+ switch (kind) {
27353
+ case "system":
27354
+ this.saveSystemSpec(spec);
27355
+ return [];
27356
+ case "subsystem":
27357
+ this.saveSubsystemSpec(spec, opts);
27358
+ return [];
27359
+ case "component":
27360
+ return this.saveComponentSpec(spec, opts);
27361
+ case "interface":
27362
+ return this.saveInterfaceSpec(spec, opts);
27363
+ case "implementation":
27364
+ return this.saveImplementationSpec(spec, opts);
27365
+ case "type":
27366
+ return this.saveTypeSpec(spec);
27367
+ }
27368
+ }
26908
27369
  };
27370
+ DEFAULT_DEPENDENCY_LIMIT = 8;
27371
+ PROSE_FIELDS2 = /* @__PURE__ */ new Set([
27372
+ "description",
27373
+ "intent",
27374
+ "summary",
27375
+ "condition",
27376
+ "over",
27377
+ "outcome",
27378
+ "error",
27379
+ "note",
27380
+ "caller"
27381
+ ]);
26909
27382
  workspaces = /* @__PURE__ */ new Map();
26910
27383
  }
26911
27384
  });
@@ -27773,6 +28246,32 @@ function addComponent(candidate) {
27773
28246
  function updateSpecGated(kind, id, delta, dryRun) {
27774
28247
  return updateSpec(kind, id, delta, componentCandidateGate(), dryRun);
27775
28248
  }
28249
+ function methodMoveGate(source) {
28250
+ const bound2 = candidateOptions();
28251
+ return {
28252
+ gate: (kind, merged) => {
28253
+ if (kind !== "component") return;
28254
+ const { codes, verdict, ceiling } = judgeMoveHome(merged, source, bound2);
28255
+ if (codes.length === 0) return noticesFrom(verdict);
28256
+ throw new Error(
28257
+ `${codes.join(", ")} refuses "${merged.id}" as the home for these methods. ` + (verdict.errors.length ? formatCandidateRefusal(verdict) : `It would reach ${merged.dependsOn?.length ?? 0} dependencies against a ceiling of ${ceiling}.`)
28258
+ );
28259
+ },
28260
+ assess: (kind, merged) => kind === "component" ? judgeMoveHome(merged, source, bound2).codes : []
28261
+ };
28262
+ }
28263
+ function judgeMoveHome(component, source, options) {
28264
+ const ceiling = options.rules?.complexity?.maxComponentDependencies;
28265
+ const verdict = validateComponentCandidate(component, options);
28266
+ const codes = verdict.errors.map((e) => e.code);
28267
+ if (component.id !== source && ceiling !== void 0 && (component.dependsOn?.length ?? 0) > ceiling) {
28268
+ codes.push("EXCESSIVE_DEPENDENCIES");
28269
+ }
28270
+ return { codes: [...new Set(codes)], verdict, ceiling };
28271
+ }
28272
+ function moveMethods2(from, to, methods, dryRun) {
28273
+ return moveMethods(from, to, methods, methodMoveGate(from), dryRun);
28274
+ }
27776
28275
  var init_authoring = __esm({
27777
28276
  "src/core/authoring.ts"() {
27778
28277
  "use strict";
@@ -28529,6 +29028,31 @@ function createMcpServer(options = {}) {
28529
29028
  }
28530
29029
  }
28531
29030
  );
29031
+ reg(
29032
+ server,
29033
+ "sdd_move_methods",
29034
+ {
29035
+ description: "Move contract methods from one component to another, carrying each method's narrative, calls and bindings, and re-pointing every reference that named the old home: narrative call/register/dispatch steps, dispatch-table bindings, lifecycle entrypoints and `calls` entries. The target gains the dependencies the moved narratives call. ONE all-or-nothing gated write: if a rule refuses the move nothing is written and the report names the rule plus where these methods could live instead, cheapest legal home first, with the requested target among them so its shortfall reads beside the others. Prose is never rewritten and a wire endpoint keeps its address \u2014 moving a method between components must not silently re-address an RPC; both are reported as mentions. Refuses before the first write (unmovable request): a component or method that does not exist, a method name already declared on the target, a source and target that are the same component, and a component inside a chained subproject (move it from that project's own root). A dry run runs the whole move, gate included, and reports what it would have done.",
29036
+ inputSchema: {
29037
+ from: import_zod11.z.string().describe("The component the methods belong to (namespaced if needed)"),
29038
+ to: import_zod11.z.string().describe("The component that should receive them (namespaced if needed)"),
29039
+ methods: import_zod11.z.array(import_zod11.z.string()).describe("The method names to move; every one must exist on the source contract"),
29040
+ dryRun: import_zod11.z.boolean().optional().describe("Answer with what the move would do and write nothing")
29041
+ }
29042
+ },
29043
+ ({ from, to, methods, dryRun }) => {
29044
+ try {
29045
+ const { loadComponentSpec: loadComponentSpec2 } = requireSpecs();
29046
+ const source = qualifiedComponentId(from);
29047
+ const target = qualifiedComponentId(to);
29048
+ if (!loadComponentSpec2(source)) return errText(`no component has the id "${from}".`);
29049
+ if (!loadComponentSpec2(target)) return errText(`no component has the id "${to}".`);
29050
+ return json(moveMethods2(source, target, methods, dryRun));
29051
+ } catch (e) {
29052
+ return errText(String(e));
29053
+ }
29054
+ }
29055
+ );
28532
29056
  const componentInput = {
28533
29057
  id: import_zod11.z.string().describe("Lowercase identifier for the component"),
28534
29058
  name: import_zod11.z.string().describe("Human-readable display name"),