@wairon/cli 5.1.1-dev.47 → 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/index.js CHANGED
@@ -1222,7 +1222,7 @@ var init_defaults = __esm({
1222
1222
  copilot: ".github/prompts",
1223
1223
  codex: ".codex/agents"
1224
1224
  };
1225
- WAIRON_VERSION = "5.1.1-dev.47";
1225
+ WAIRON_VERSION = "5.1.1-dev.49";
1226
1226
  GITHUB_REPO = "SYW-Apps/Waffle-AIron";
1227
1227
  ARCHITECT_AGENT_ID = "agent-architect";
1228
1228
  ARCHITECT_TEMPLATE_ID = "architect";
@@ -19332,6 +19332,99 @@ function computeSpecTreeSignature(dirs) {
19332
19332
  }
19333
19333
  return parts.join("|");
19334
19334
  }
19335
+ function specKind(raw) {
19336
+ if ("componentType" in raw) return "component";
19337
+ if ("parentSystem" in raw) return "subsystem";
19338
+ if ("vision" in raw) return "system";
19339
+ if ("component" in raw && Array.isArray(raw.methods)) return "interface";
19340
+ if ("contract" in raw && Array.isArray(raw.methods)) return "implementation";
19341
+ if ("kind" in raw && Array.isArray(raw.fields)) return "type";
19342
+ return void 0;
19343
+ }
19344
+ function rewriteSpecRefs(raw, remap) {
19345
+ if (!raw || typeof raw !== "object") return false;
19346
+ let changed = false;
19347
+ const rewrite = (holder, key, position, owner) => {
19348
+ const ref = holder?.[key];
19349
+ if (typeof ref !== "string") return;
19350
+ const next = remap(ref, position, owner);
19351
+ if (next !== ref) {
19352
+ holder[key] = next;
19353
+ changed = true;
19354
+ }
19355
+ };
19356
+ const rewriteMethod = (holder, key, owner) => {
19357
+ const name = holder?.[key];
19358
+ if (typeof name !== "string" || typeof owner !== "string") return;
19359
+ const next = remap(name, "method", owner);
19360
+ if (next !== name) {
19361
+ holder[key] = next;
19362
+ changed = true;
19363
+ }
19364
+ };
19365
+ const rewritePair = (holder, componentKey, methodKey2) => {
19366
+ const component = holder?.[componentKey];
19367
+ const method = holder?.[methodKey2];
19368
+ rewriteMethod(holder, methodKey2, component);
19369
+ rewrite(holder, componentKey, "component", typeof method === "string" ? method : void 0);
19370
+ };
19371
+ const entries = (list) => Array.isArray(list) ? list : [];
19372
+ const rewriteEach = (list, position) => {
19373
+ entries(list).forEach((_, i) => rewrite(list, i, position));
19374
+ };
19375
+ const rewritePublished = (list) => {
19376
+ for (const published of entries(list)) {
19377
+ rewrite(published, "component", "component");
19378
+ rewrite(published, "interface", "interface");
19379
+ }
19380
+ };
19381
+ const kind = specKind(raw);
19382
+ if (kind === "component") {
19383
+ rewriteEach(raw.dependsOn, "component");
19384
+ rewriteEach(raw.owns, "component");
19385
+ for (const binding of entries(raw.dispatch)) rewritePair(binding, "component", "method");
19386
+ } else if (kind === "subsystem") {
19387
+ for (const entrypoint of entries(raw.lifecycle)) rewritePair(entrypoint, "component", "method");
19388
+ rewritePublished(raw.publicInterfaces);
19389
+ } else if (kind === "system") {
19390
+ rewritePublished(raw.publicInterfaces);
19391
+ } else if (kind === "interface") {
19392
+ rewrite(raw, "component", "component");
19393
+ for (const method of entries(raw.methods)) {
19394
+ for (const param of entries(method?.params)) rewrite(param, "type", "type");
19395
+ }
19396
+ } else if (kind === "implementation") {
19397
+ rewrite(raw, "contract", "interface");
19398
+ for (const method of entries(raw.methods)) {
19399
+ for (const step of entries(method?.narrative)) {
19400
+ rewritePair(step, "targetComponent", "targetMethod");
19401
+ const source = step?.auth?.from;
19402
+ if (typeof source === "string" && source.startsWith(COMPONENT_AUTH_SOURCE)) {
19403
+ const id = source.slice(COMPONENT_AUTH_SOURCE.length);
19404
+ const next = remap(id, "auth-source");
19405
+ if (next !== id) {
19406
+ step.auth.from = `${COMPONENT_AUTH_SOURCE}${next}`;
19407
+ changed = true;
19408
+ }
19409
+ }
19410
+ }
19411
+ entries(method?.calls).forEach((entry, i) => {
19412
+ if (typeof entry !== "string") return;
19413
+ const call = parseDeclaredCall(entry);
19414
+ if (!call) return;
19415
+ const component = remap(call.compId, "component", call.methodName);
19416
+ const name = remap(call.methodName, "method", call.compId);
19417
+ if (component === call.compId && name === call.methodName) return;
19418
+ method.calls[i] = `${component}.${name}`;
19419
+ changed = true;
19420
+ });
19421
+ }
19422
+ } else if (kind === "type") {
19423
+ rewrite(raw, "componentClass", "entity-class");
19424
+ for (const field of entries(raw.fields)) rewrite(field, "type", "type");
19425
+ }
19426
+ return changed;
19427
+ }
19335
19428
  function summarizeValue(value) {
19336
19429
  if (value === void 0) return "";
19337
19430
  const text = typeof value === "string" ? value : JSON.stringify(value) ?? String(value);
@@ -19543,6 +19636,84 @@ function ineffectiveDeltaPaths(delta, merged, stored) {
19543
19636
  walk2(delta, merged, stored, "", "");
19544
19637
  return out;
19545
19638
  }
19639
+ function cloneSpec(spec) {
19640
+ return JSON.parse(JSON.stringify(spec));
19641
+ }
19642
+ function namedMethods(methods) {
19643
+ return methods.map((m) => `"${m}"`).join(", ");
19644
+ }
19645
+ function statusOfContracts(contracts, component) {
19646
+ return contracts.find((i) => i.component === component)?.status;
19647
+ }
19648
+ function reachOf(entries, from, moved) {
19649
+ const reach = /* @__PURE__ */ new Set();
19650
+ for (const entry of entries) {
19651
+ for (const step of entry.narrative ?? []) {
19652
+ const target = step.targetComponent;
19653
+ const method = step.targetMethod;
19654
+ if (!target) continue;
19655
+ if (target === from && method && moved.has(method)) continue;
19656
+ reach.add(target);
19657
+ }
19658
+ for (const call of entry.calls ?? []) {
19659
+ const parsed = parseDeclaredCall(call);
19660
+ if (!parsed) continue;
19661
+ if (parsed.compId === from && moved.has(parsed.methodName)) continue;
19662
+ reach.add(parsed.compId);
19663
+ }
19664
+ }
19665
+ return [...reach].sort();
19666
+ }
19667
+ function namesInProse(value, named) {
19668
+ if (Array.isArray(value)) return value.some((v) => namesInProse(v, named));
19669
+ if (!value || typeof value !== "object") return false;
19670
+ return Object.entries(value).some(([key, nested]) => typeof nested === "string" ? PROSE_FIELDS.has(key) && named.test(nested) : namesInProse(nested, named));
19671
+ }
19672
+ function judgeMoveResult(hooks, plan) {
19673
+ const refusals = [];
19674
+ const notices = [];
19675
+ for (const resulting of [plan.edits.get(`component:${plan.source.id}`), plan.edits.get(`component:${plan.target.id}`)]) {
19676
+ if (!resulting) continue;
19677
+ try {
19678
+ const raised = hooks.gate?.("component", resulting.after);
19679
+ if (raised?.length) notices.push(...raised);
19680
+ } catch (e) {
19681
+ const codes = hooks.assess?.("component", resulting.after) ?? [];
19682
+ refusals.push(...codes.length ? codes : [String(e?.message ?? e)]);
19683
+ }
19684
+ }
19685
+ return { refusals: [...new Set(refusals)], notices };
19686
+ }
19687
+ function weighMoveHome(hooks, candidate, reach, dependencyLimit) {
19688
+ const held = /* @__PURE__ */ new Set([...candidate.dependsOn ?? [], ...candidate.owns ?? []]);
19689
+ const newDependencies = reach.filter((id) => id !== candidate.id && !held.has(id));
19690
+ const resulting = { ...candidate, dependsOn: [...candidate.dependsOn ?? [], ...newDependencies] };
19691
+ const refusals = hooks.assess?.("component", resulting) ?? [];
19692
+ return {
19693
+ component: candidate.id,
19694
+ legal: refusals.length === 0,
19695
+ newDependencies,
19696
+ dependencyCount: resulting.dependsOn.length,
19697
+ dependencyLimit,
19698
+ refusals
19699
+ };
19700
+ }
19701
+ function moveChangeReports(edits, dryRun) {
19702
+ return edits.map((edit) => ({ edit, changes: specChanges(edit.before, edit.after) })).filter(({ changes }) => changes.length > 0).map(({ edit, changes }) => ({
19703
+ kind: edit.kind,
19704
+ id: edit.id,
19705
+ written: !dryRun,
19706
+ dryRun,
19707
+ changes,
19708
+ ineffective: [],
19709
+ notices: [],
19710
+ summary: `${dryRun ? "Would move" : "Moved"} into ${edit.kind} "${edit.id}": ${changes.length} change${changes.length === 1 ? "" : "s"}.`
19711
+ }));
19712
+ }
19713
+ function refusedMoveSummary(from, to, methods, refusals, alternatives) {
19714
+ const cheapest = alternatives.find((c) => c.legal);
19715
+ 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.`);
19716
+ }
19546
19717
  function workspaceFor(rootDir) {
19547
19718
  const key = path16.resolve(rootDir);
19548
19719
  let ws = workspaces.get(key);
@@ -19771,7 +19942,10 @@ function findLegacySpecFiles() {
19771
19942
  function updateSpec(kind, id, delta, hooks, dryRun) {
19772
19943
  return current().updateSpec(kind, id, delta, hooks, dryRun);
19773
19944
  }
19774
- var fs11, path16, NO_ROOT_SUBSYSTEMS, SIGNATURE_TTL_MS, DELTA_VERBS, JUMP_LABEL_TWINS, LABEL_TWIN_CONTAINERS, SpecWorkspace, workspaces;
19945
+ function moveMethods(from, to, methods, hooks, dryRun) {
19946
+ return current().moveMethods(from, to, methods, hooks, dryRun);
19947
+ }
19948
+ var fs11, path16, NO_ROOT_SUBSYSTEMS, SIGNATURE_TTL_MS, COMPONENT_AUTH_SOURCE, DELTA_VERBS, JUMP_LABEL_TWINS, LABEL_TWIN_CONTAINERS, SpecWorkspace, DEFAULT_DEPENDENCY_LIMIT, PROSE_FIELDS, workspaces;
19775
19949
  var init_specs2 = __esm({
19776
19950
  "src/core/specs.ts"() {
19777
19951
  "use strict";
@@ -19790,6 +19964,7 @@ var init_specs2 = __esm({
19790
19964
  init_diagram();
19791
19965
  NO_ROOT_SUBSYSTEMS = /* @__PURE__ */ new Set();
19792
19966
  SIGNATURE_TTL_MS = 2e3;
19967
+ COMPONENT_AUTH_SOURCE = "component:";
19793
19968
  DELTA_VERBS = /* @__PURE__ */ new Set(["action", "remove", "captureJumps"]);
19794
19969
  JUMP_LABEL_TWINS = /* @__PURE__ */ new Set([
19795
19970
  "onTrueLabel",
@@ -21648,7 +21823,382 @@ var init_specs2 = __esm({
21648
21823
  summary: `Updated ${kind} "${id}": ${changes.length} change${changes.length === 1 ? "" : "s"}.`
21649
21824
  };
21650
21825
  }
21826
+ // -------------------------------------------------------------------------
21827
+ // Method move (icore_orchestrator.moveMethods)
21828
+ //
21829
+ // A rename changes a NAME and can stay mechanical. A move changes which
21830
+ // component OWNS behaviour, which is exactly what the stereotype and
21831
+ // dependency rules judge — so the mechanics live here and the judgement
21832
+ // arrives as an injected hook, for the same reason `gate` does: this module
21833
+ // must not import the rule engine.
21834
+ //
21835
+ // It is several writes at once: both contracts, both implementations, the
21836
+ // target component (which gains the dependencies the moved narratives call),
21837
+ // and every caller whose steps, dispatch bindings, lifecycle entrypoints or
21838
+ // `calls` entries name the old home. Every edit is computed before the first
21839
+ // is written and a failed write restores the ones already made, because a
21840
+ // half-moved method leaves callers naming a home it no longer has.
21841
+ // -------------------------------------------------------------------------
21842
+ /**
21843
+ * Move contract methods from one component to another, and every reference
21844
+ * with them.
21845
+ *
21846
+ * Each method leaves the source component's contract and arrives on the
21847
+ * target's, carrying its signature, params, returns, guarantees and endpoint
21848
+ * binding unchanged; its implementation entry travels with it, carrying
21849
+ * narrative, sourcePath, symbol, detail, intent, calls and findings. Prose is
21850
+ * never rewritten and a wire endpoint keeps its address — moving a method
21851
+ * between components must not silently re-address an RPC — so both are
21852
+ * reported as mentions instead.
21853
+ *
21854
+ * Refuses before the first write (`unmovable request`): a component or method
21855
+ * that does not exist, a method name already declared on the target, a source
21856
+ * and target that are the same component, and a component inside a chained
21857
+ * subproject.
21858
+ */
21859
+ moveMethods(from, to, methods, hooks, dryRun = false) {
21860
+ const source = this.loadComponentSpec(from);
21861
+ const complaint = this.unmovableRequest(source, from, to, methods);
21862
+ if (complaint) throw new Error(`unmovable request: ${complaint}`);
21863
+ const contracts = this.loadInterfaceSpecs();
21864
+ const plan = this.planMethodMove(source, to, methods, contracts);
21865
+ const repointed = this.repointToNewHome(from, to, methods, plan);
21866
+ const mentions = this.moveMentions(from, plan);
21867
+ if (hooks) {
21868
+ const verdict = judgeMoveResult(hooks, plan);
21869
+ plan.notices.push(...verdict.notices);
21870
+ if (verdict.refusals.length > 0) {
21871
+ const alternatives = this.rankMoveHomes(hooks, from, to, plan);
21872
+ return {
21873
+ moved: false,
21874
+ from,
21875
+ to,
21876
+ methods,
21877
+ edits: [],
21878
+ repointed: 0,
21879
+ refusals: verdict.refusals,
21880
+ alternatives,
21881
+ notices: plan.notices,
21882
+ summary: refusedMoveSummary(from, to, methods, verdict.refusals, alternatives),
21883
+ dryRun,
21884
+ mentions
21885
+ };
21886
+ }
21887
+ }
21888
+ if (dryRun) {
21889
+ const edits2 = moveChangeReports([...plan.edits.values()], true);
21890
+ return {
21891
+ moved: false,
21892
+ from,
21893
+ to,
21894
+ methods,
21895
+ edits: edits2,
21896
+ repointed,
21897
+ refusals: [],
21898
+ alternatives: [],
21899
+ notices: plan.notices,
21900
+ 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.`,
21901
+ dryRun: true,
21902
+ mentions
21903
+ };
21904
+ }
21905
+ const edits = this.writeMoveEdits([...plan.edits.values()]);
21906
+ return {
21907
+ moved: true,
21908
+ from,
21909
+ to,
21910
+ methods,
21911
+ edits,
21912
+ repointed,
21913
+ refusals: [],
21914
+ alternatives: [],
21915
+ notices: plan.notices,
21916
+ 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` : ""}.`,
21917
+ dryRun: false,
21918
+ mentions
21919
+ };
21920
+ }
21921
+ /**
21922
+ * Why this move cannot be attempted at all, or null when the request is
21923
+ * answerable. Every check here is about what the CALLER named, so its answer
21924
+ * is a mistake to correct — never a rule refusing the design.
21925
+ */
21926
+ unmovableRequest(source, from, to, methods) {
21927
+ const chained = [from, to].find((id) => id.includes("::"));
21928
+ if (chained) {
21929
+ return `"${chained}" lives in a chained subproject; move its methods from that project's own root.`;
21930
+ }
21931
+ if (!source) return `no component has the id "${from}".`;
21932
+ if (!this.loadComponentSpec(to)) return `no component has the id "${to}".`;
21933
+ if (from === to) return `"${from}" is both the source and the target; a method cannot move to where it already lives.`;
21934
+ if (methods.length === 0) return "no method names were given.";
21935
+ const contracts = this.loadInterfaceSpecs();
21936
+ const ofSource = contracts.filter((i) => i.component === from);
21937
+ const ofTarget = contracts.filter((i) => i.component === to);
21938
+ for (const name of methods) {
21939
+ const declaring = ofSource.filter((i) => i.methods.some((m) => m.name === name));
21940
+ if (declaring.length === 0) return `no contract of "${from}" declares the method "${name}".`;
21941
+ if (declaring.length > 1) {
21942
+ 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.`;
21943
+ }
21944
+ }
21945
+ const taken = methods.filter((name) => ofTarget.some((i) => i.methods.some((m) => m.name === name)));
21946
+ if (taken.length > 0) {
21947
+ return `"${to}" already declares ${namedMethods(taken)}.`;
21948
+ }
21949
+ const receiving = this.moveTargetContract(to, ofTarget);
21950
+ if (typeof receiving === "string") return receiving;
21951
+ const travelling = this.loadImplementationSpecs().some((impl) => ofSource.some((i) => i.id === impl.contract) && impl.methods.some((m) => methods.includes(m.name)));
21952
+ if (travelling && !this.loadImplementationSpecs().some((i) => i.contract === receiving.id)) {
21953
+ return `no implementation realizes "${receiving.id}", so the moved methods' narratives would have nowhere to arrive.`;
21954
+ }
21955
+ return null;
21956
+ }
21957
+ /** The contract of `to` that receives the methods, or why that cannot be decided. */
21958
+ moveTargetContract(to, ofTarget) {
21959
+ if (ofTarget.length === 0) return `"${to}" declares no contract, so the methods would have nowhere to arrive.`;
21960
+ if (ofTarget.length === 1) return ofTarget[0];
21961
+ const named = ofTarget.find((i) => i.id === `i${to}` || i.id.endsWith(`::i${to}`));
21962
+ return named ?? `"${to}" declares ${ofTarget.length} contracts and none is named "i${to}", so which one receives the methods is not decidable.`;
21963
+ }
21964
+ /**
21965
+ * The whole move as staged edits, computed before anything is written: the
21966
+ * contract entries that leave the source and arrive on the target, the
21967
+ * implementation entries that travel with them, and the dependencies the
21968
+ * moved narratives call that the target does not have yet.
21969
+ */
21970
+ planMethodMove(source, to, methods, contracts) {
21971
+ const target = this.loadComponentSpec(to);
21972
+ const receiving = this.moveTargetContract(to, contracts.filter((i) => i.component === to));
21973
+ const implementations = this.loadImplementationSpecs();
21974
+ const plan = {
21975
+ edits: /* @__PURE__ */ new Map(),
21976
+ source,
21977
+ target,
21978
+ reach: [],
21979
+ newDependencies: [],
21980
+ wireBound: [],
21981
+ notices: []
21982
+ };
21983
+ const arriving = [];
21984
+ const travelling = [];
21985
+ for (const contract of contracts.filter((i) => i.component === source.id)) {
21986
+ const leaving = contract.methods.filter((m) => methods.includes(m.name));
21987
+ if (leaving.length === 0) continue;
21988
+ arriving.push(...cloneSpec(leaving));
21989
+ const remaining = cloneSpec(contract);
21990
+ remaining.methods = remaining.methods.filter((m) => !methods.includes(m.name));
21991
+ this.stageMoveEdit(plan, "interface", contract.id, contract, remaining);
21992
+ if (contract.methods.length === leaving.length) {
21993
+ plan.notices.push(`"${contract.id}" is left with no methods \u2014 "${source.id}" no longer declares anything on it.`);
21994
+ }
21995
+ for (const impl of implementations.filter((i) => i.contract === contract.id)) {
21996
+ const moving = impl.methods.filter((m) => methods.includes(m.name));
21997
+ if (moving.length === 0) continue;
21998
+ travelling.push(...cloneSpec(moving));
21999
+ const kept = cloneSpec(impl);
22000
+ kept.methods = kept.methods.filter((m) => !methods.includes(m.name));
22001
+ this.stageMoveEdit(plan, "implementation", impl.id, impl, kept);
22002
+ }
22003
+ }
22004
+ this.stageMoveEdit(plan, "interface", receiving.id, receiving, {
22005
+ ...cloneSpec(receiving),
22006
+ methods: [...cloneSpec(receiving.methods), ...arriving]
22007
+ });
22008
+ if (arriving.some((m) => m.endpoint)) plan.wireBound.push(receiving.id);
22009
+ if (receiving.status !== void 0 && receiving.status !== statusOfContracts(contracts, source.id)) {
22010
+ 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.`);
22011
+ }
22012
+ for (const entry of travelling) {
22013
+ if ((entry.narrative ?? []).length === 0 && !entry.intent) {
22014
+ plan.notices.push(`"${entry.name}" moves with an empty narrative and no intent \u2014 nothing followed it but its name.`);
22015
+ }
22016
+ }
22017
+ const targetImpl = implementations.find((i) => i.contract === receiving.id);
22018
+ if (travelling.length > 0 && targetImpl) {
22019
+ this.stageMoveEdit(plan, "implementation", targetImpl.id, targetImpl, {
22020
+ ...cloneSpec(targetImpl),
22021
+ methods: [...cloneSpec(targetImpl.methods), ...travelling]
22022
+ });
22023
+ }
22024
+ plan.reach = reachOf(travelling, source.id, new Set(methods)).filter((id) => id !== to);
22025
+ const held = /* @__PURE__ */ new Set([...target.dependsOn ?? [], ...target.owns ?? []]);
22026
+ plan.newDependencies = plan.reach.filter((id) => !held.has(id) && id !== to);
22027
+ this.stageMoveEdit(plan, "component", source.id, source, cloneSpec(source));
22028
+ this.stageMoveEdit(plan, "component", target.id, target, {
22029
+ ...cloneSpec(target),
22030
+ dependsOn: [...target.dependsOn ?? [], ...plan.newDependencies]
22031
+ });
22032
+ return plan;
22033
+ }
22034
+ /** Stage one spec's rewrite, keeping the version it started from as the snapshot a failed write restores. */
22035
+ stageMoveEdit(plan, kind, id, before, after) {
22036
+ plan.edits.set(`${kind}:${id}`, { kind, id, before, after });
22037
+ }
22038
+ /**
22039
+ * Re-point every reference that names the old home, through the one
22040
+ * reference-field table, and answer with how many followed.
22041
+ *
22042
+ * A reference is only re-pointed when it names BOTH the old home and a method
22043
+ * that is moving: another method of the same component, and another
22044
+ * component's method of the same name, stay exactly where they point.
22045
+ */
22046
+ repointToNewHome(from, to, methods, plan) {
22047
+ const moved = new Set(methods);
22048
+ let repointed = 0;
22049
+ const remap = (ref, position, owner) => {
22050
+ if (position !== "component" || ref !== from || !owner || !moved.has(owner)) return ref;
22051
+ repointed += 1;
22052
+ return to;
22053
+ };
22054
+ for (const { kind, id, spec } of this.everySpecInTree()) {
22055
+ const staged = plan.edits.get(`${kind}:${id}`);
22056
+ const subject = staged ? staged.after : cloneSpec(spec);
22057
+ if (!rewriteSpecRefs(subject, remap)) continue;
22058
+ if (staged) continue;
22059
+ if (id.includes("::")) {
22060
+ plan.notices.push(`"${id}" lives in a chained subproject and still names "${from}" \u2014 re-point it from that project's own root.`);
22061
+ repointed -= 1;
22062
+ continue;
22063
+ }
22064
+ this.stageMoveEdit(plan, kind, id, spec, subject);
22065
+ }
22066
+ plan.notices.push(...this.callersOwingADependency(to, plan));
22067
+ return repointed;
22068
+ }
22069
+ /**
22070
+ * The components that now reach the new home without declaring it. The move
22071
+ * re-points the reference; it never edits another component's dependency
22072
+ * list, because a dependency nobody judged is exactly what the gate exists to
22073
+ * prevent.
22074
+ */
22075
+ callersOwingADependency(to, plan) {
22076
+ const contracts = this.loadInterfaceSpecs();
22077
+ const owing = /* @__PURE__ */ new Set();
22078
+ for (const edit of plan.edits.values()) {
22079
+ const owner = edit.kind === "component" ? edit.id : contracts.find((i) => i.id === edit.after.contract)?.component;
22080
+ if (!owner || owner === to) continue;
22081
+ const component = owner === plan.target.id ? plan.target : this.loadComponentSpec(owner);
22082
+ if (!component) continue;
22083
+ const held = /* @__PURE__ */ new Set([...component.dependsOn ?? [], ...component.owns ?? []]);
22084
+ if (!held.has(to)) owing.add(owner);
22085
+ }
22086
+ return [...owing].map((id) => `"${id}" now reaches "${to}" but does not declare it \u2014 add the dependency, or the call is undeclared.`);
22087
+ }
22088
+ /**
22089
+ * Where these methods could live instead, cheapest legal home first, with the
22090
+ * requested target among them so its shortfall reads beside the others.
22091
+ *
22092
+ * The store enumerates, because the store holds the tree; the hook judges,
22093
+ * because the gate holds the judgement. `assess` answers rather than throws,
22094
+ * so this is a search and not a sequence of caught exceptions.
22095
+ */
22096
+ rankMoveHomes(hooks, from, to, plan) {
22097
+ const limit = this.dependencyLimit();
22098
+ const pool = this.loadComponentSpecs().filter((c) => c.subsystem === plan.source.subsystem && c.id !== from);
22099
+ if (!pool.some((c) => c.id === to)) pool.push(plan.target);
22100
+ 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));
22101
+ }
22102
+ /**
22103
+ * The project's `maxComponentDependencies`, so a candidate's dependency count
22104
+ * can be read without looking up the config. A project that sets none is read
22105
+ * against the same default the coupling-health rule applies.
22106
+ */
22107
+ dependencyLimit() {
22108
+ try {
22109
+ return projectConfigRepository.load()?.rules?.complexity?.maxComponentDependencies ?? DEFAULT_DEPENDENCY_LIMIT;
22110
+ } catch {
22111
+ return DEFAULT_DEPENDENCY_LIMIT;
22112
+ }
22113
+ }
22114
+ /** Every spec in the bound tree, with the kind and id a staged edit addresses it by. */
22115
+ *everySpecInTree() {
22116
+ const system = this.loadSystemSpec();
22117
+ if (system) yield { kind: "system", id: system.name, spec: system };
22118
+ for (const spec of this.loadSubsystemSpecs()) yield { kind: "subsystem", id: spec.id, spec };
22119
+ for (const spec of this.loadComponentSpecs()) yield { kind: "component", id: spec.id, spec };
22120
+ for (const spec of this.loadInterfaceSpecs()) yield { kind: "interface", id: spec.id, spec };
22121
+ for (const spec of this.loadImplementationSpecs()) yield { kind: "implementation", id: spec.id, spec };
22122
+ for (const spec of this.loadTypeSpecs()) yield { kind: "type", id: spec.id, spec };
22123
+ }
22124
+ /**
22125
+ * What still names the old home once the move is written, every one of it
22126
+ * left exactly as it was: the specs whose prose carries the id, and the
22127
+ * receiving contract whose moved methods keep their wire address. A move
22128
+ * never edits prose and never re-addresses an RPC.
22129
+ */
22130
+ moveMentions(from, plan) {
22131
+ const mentions = new Set(plan.wireBound);
22132
+ const named = new RegExp(`(?<![A-Za-z0-9_])${from}(?![A-Za-z0-9_])`);
22133
+ for (const { kind, id, spec } of this.everySpecInTree()) {
22134
+ if (namesInProse(spec, named)) mentions.add(kind === "system" ? "system" : id);
22135
+ }
22136
+ return [...mentions];
22137
+ }
22138
+ /**
22139
+ * Write every staged edit, restoring the ones already written if one fails.
22140
+ *
22141
+ * The snapshots are the versions the plan started from, so a restore puts the
22142
+ * authored content back exactly; only `updatedAt` reads as touched, because
22143
+ * the store stamps every write it accepts. Nothing invalidates the cache
22144
+ * here: every save does it already, and a second call would only be a hop
22145
+ * across a component boundary that no narrative claims.
22146
+ */
22147
+ writeMoveEdits(edits) {
22148
+ const reports = moveChangeReports(edits, false);
22149
+ const written = [];
22150
+ try {
22151
+ for (const report of reports) {
22152
+ const edit = edits.find((e) => e.kind === report.kind && e.id === report.id);
22153
+ report.notices.push(...this.saveMovedSpec(edit.kind, edit.after));
22154
+ written.push(edit);
22155
+ }
22156
+ } catch (e) {
22157
+ for (const edit of [...written].reverse()) {
22158
+ try {
22159
+ this.saveMovedSpec(edit.kind, edit.before);
22160
+ } catch {
22161
+ }
22162
+ }
22163
+ throw new Error(
22164
+ `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.`
22165
+ );
22166
+ }
22167
+ return reports;
22168
+ }
22169
+ /** Persist one moved spec through the store's own writer, and answer with its placement notices. */
22170
+ saveMovedSpec(kind, spec) {
22171
+ const opts = { allowStatusDemotion: true };
22172
+ switch (kind) {
22173
+ case "system":
22174
+ this.saveSystemSpec(spec);
22175
+ return [];
22176
+ case "subsystem":
22177
+ this.saveSubsystemSpec(spec, opts);
22178
+ return [];
22179
+ case "component":
22180
+ return this.saveComponentSpec(spec, opts);
22181
+ case "interface":
22182
+ return this.saveInterfaceSpec(spec, opts);
22183
+ case "implementation":
22184
+ return this.saveImplementationSpec(spec, opts);
22185
+ case "type":
22186
+ return this.saveTypeSpec(spec);
22187
+ }
22188
+ }
21651
22189
  };
22190
+ DEFAULT_DEPENDENCY_LIMIT = 8;
22191
+ PROSE_FIELDS = /* @__PURE__ */ new Set([
22192
+ "description",
22193
+ "intent",
22194
+ "summary",
22195
+ "condition",
22196
+ "over",
22197
+ "outcome",
22198
+ "error",
22199
+ "note",
22200
+ "caller"
22201
+ ]);
21652
22202
  workspaces = /* @__PURE__ */ new Map();
21653
22203
  }
21654
22204
  });
@@ -22549,15 +23099,6 @@ function componentIdsUnder(dir) {
22549
23099
  }
22550
23100
  return ids;
22551
23101
  }
22552
- function specKind(raw) {
22553
- if ("componentType" in raw) return "component";
22554
- if ("parentSystem" in raw) return "subsystem";
22555
- if ("vision" in raw) return "system";
22556
- if ("component" in raw && Array.isArray(raw.methods)) return "interface";
22557
- if ("contract" in raw && Array.isArray(raw.methods)) return "implementation";
22558
- if ("kind" in raw && Array.isArray(raw.fields)) return "type";
22559
- return void 0;
22560
- }
22561
23102
  function rewriteRefFields(specsDir, remap, excludeDir) {
22562
23103
  const rewritten = [];
22563
23104
  for (const file of listFilesRecursive(specsDir, ".yaml")) {
@@ -22569,81 +23110,10 @@ function rewriteRefFields(specsDir, remap, excludeDir) {
22569
23110
  continue;
22570
23111
  }
22571
23112
  if (!raw || typeof raw !== "object") continue;
22572
- let changed = false;
22573
- const rewrite = (holder, key, position) => {
22574
- const ref = holder?.[key];
22575
- if (typeof ref !== "string") return;
22576
- const next = remap(ref, position);
22577
- if (next !== ref) {
22578
- holder[key] = next;
22579
- changed = true;
22580
- }
22581
- };
22582
- const rewriteMethod = (holder, key, owner) => {
22583
- const name = holder?.[key];
22584
- if (typeof name !== "string" || typeof owner !== "string") return;
22585
- const next = remap(name, "method", owner);
22586
- if (next !== name) {
22587
- holder[key] = next;
22588
- changed = true;
22589
- }
22590
- };
22591
- const entries = (list) => Array.isArray(list) ? list : [];
22592
- const rewriteEach = (list, position) => {
22593
- entries(list).forEach((_, i) => rewrite(list, i, position));
22594
- };
22595
- const rewritePublished = (list) => {
22596
- for (const published of entries(list)) {
22597
- rewrite(published, "component", "component");
22598
- rewrite(published, "interface", "interface");
22599
- }
22600
- };
22601
23113
  const kind = specKind(raw);
22602
- if (kind === "component") {
22603
- rewriteEach(raw.dependsOn, "component");
22604
- rewriteEach(raw.owns, "component");
22605
- for (const binding of entries(raw.dispatch)) {
22606
- rewriteMethod(binding, "method", binding?.component);
22607
- rewrite(binding, "component", "component");
22608
- }
22609
- } else if (kind === "subsystem") {
22610
- for (const entrypoint of entries(raw.lifecycle)) {
22611
- rewriteMethod(entrypoint, "method", entrypoint?.component);
22612
- rewrite(entrypoint, "component", "component");
22613
- }
22614
- rewritePublished(raw.publicInterfaces);
22615
- } else if (kind === "system") {
22616
- rewritePublished(raw.publicInterfaces);
22617
- } else if (kind === "interface") {
22618
- rewrite(raw, "component", "component");
22619
- for (const method of raw.methods) {
22620
- for (const param of entries(method?.params)) rewrite(param, "type", "type");
22621
- }
22622
- } else if (kind === "implementation") {
22623
- rewrite(raw, "contract", "interface");
22624
- for (const method of raw.methods) {
22625
- for (const step of entries(method?.narrative)) {
22626
- rewriteMethod(step, "targetMethod", step?.targetComponent);
22627
- rewrite(step, "targetComponent", "component");
22628
- const source = step?.auth?.from;
22629
- if (typeof source === "string" && source.startsWith(COMPONENT_AUTH_SOURCE)) {
22630
- const id = source.slice(COMPONENT_AUTH_SOURCE.length);
22631
- const next = remap(id, "auth-source");
22632
- if (next !== id) {
22633
- step.auth.from = `${COMPONENT_AUTH_SOURCE}${next}`;
22634
- changed = true;
22635
- }
22636
- }
22637
- }
22638
- }
22639
- } else if (kind === "type") {
22640
- rewrite(raw, "componentClass", "entity-class");
22641
- for (const field of raw.fields) rewrite(field, "type", "type");
22642
- }
22643
- if (changed && kind) {
22644
- writeYamlFile(file, raw);
22645
- rewritten.push({ kind, id: kind === "system" ? "system" : String(raw.id) });
22646
- }
23114
+ if (!rewriteSpecRefs(raw, remap) || !kind) continue;
23115
+ writeYamlFile(file, raw);
23116
+ rewritten.push({ kind, id: kind === "system" ? "system" : String(raw.id) });
22647
23117
  }
22648
23118
  return rewritten;
22649
23119
  }
@@ -22836,7 +23306,7 @@ function collectMentions(specsDir, methodName, moved) {
22836
23306
  const namesMethod = (value) => {
22837
23307
  if (Array.isArray(value)) return value.some(namesMethod);
22838
23308
  if (!value || typeof value !== "object") return false;
22839
- return Object.entries(value).some(([key, nested]) => typeof nested === "string" ? PROSE_FIELDS.has(key) && named.test(nested) : namesMethod(nested));
23309
+ return Object.entries(value).some(([key, nested]) => typeof nested === "string" ? PROSE_FIELDS2.has(key) && named.test(nested) : namesMethod(nested));
22840
23310
  };
22841
23311
  for (const file of listFilesRecursive(specsDir, ".yaml")) {
22842
23312
  let raw;
@@ -22855,7 +23325,7 @@ function collectMentions(specsDir, methodName, moved) {
22855
23325
  function identifierPattern(methodName) {
22856
23326
  return new RegExp(`(?<![A-Za-z0-9_])${methodName}(?![A-Za-z0-9_])`);
22857
23327
  }
22858
- var fs14, path20, COMPONENT_AUTH_SOURCE, METHOD_NAME, PROSE_FIELDS;
23328
+ var fs14, path20, METHOD_NAME, PROSE_FIELDS2;
22859
23329
  var init_provision = __esm({
22860
23330
  "src/core/provision.ts"() {
22861
23331
  "use strict";
@@ -22868,9 +23338,8 @@ var init_provision = __esm({
22868
23338
  init_yaml();
22869
23339
  init_errors();
22870
23340
  init_models();
22871
- COMPONENT_AUTH_SOURCE = "component:";
22872
23341
  METHOD_NAME = /^[a-z][a-zA-Z0-9]*$/;
22873
- PROSE_FIELDS = /* @__PURE__ */ new Set([
23342
+ PROSE_FIELDS2 = /* @__PURE__ */ new Set([
22874
23343
  "description",
22875
23344
  "intent",
22876
23345
  "summary",
@@ -27158,6 +27627,7 @@ __export(src_exports, {
27158
27627
  BuiltinTargetConfigSchema: () => BuiltinTargetConfigSchema,
27159
27628
  BuiltinTargetSchema: () => BuiltinTargetSchema,
27160
27629
  BundleNotFoundError: () => BundleNotFoundError,
27630
+ COMPONENT_AUTH_SOURCE: () => COMPONENT_AUTH_SOURCE,
27161
27631
  CONTEXT_PATHS: () => CONTEXT_PATHS,
27162
27632
  CarriedDebtKindSchema: () => CarriedDebtKindSchema,
27163
27633
  CarriedDebtSchema: () => CarriedDebtSchema,
@@ -27462,6 +27932,7 @@ __export(src_exports, {
27462
27932
  methodGenericParameters: () => methodGenericParameters,
27463
27933
  methodSourceFile: () => methodSourceFile,
27464
27934
  methodTypeRefs: () => methodTypeRefs,
27935
+ moveMethods: () => moveMethods,
27465
27936
  moveSubsystemProject: () => moveSubsystemProject,
27466
27937
  narrativeStepForeignFields: () => narrativeStepForeignFields,
27467
27938
  normalizeApprover: () => normalizeApprover,
@@ -27521,6 +27992,7 @@ __export(src_exports, {
27521
27992
  resolveVariantGuidance: () => resolveVariantGuidance,
27522
27993
  restoreSpecFiles: () => restoreSpecFiles,
27523
27994
  retireSpecialists: () => retireSpecialists,
27995
+ rewriteSpecRefs: () => rewriteSpecRefs,
27524
27996
  runWithProjectBinding: () => runWithProjectBinding,
27525
27997
  runWithProjectRoot: () => runWithProjectRoot,
27526
27998
  sameContract: () => sameContract,
@@ -27543,6 +28015,7 @@ __export(src_exports, {
27543
28015
  skillsDirForTarget: () => skillsDirForTarget,
27544
28016
  snapshotSpecFiles: () => snapshotSpecFiles,
27545
28017
  specChanges: () => specChanges,
28018
+ specKind: () => specKind,
27546
28019
  specPathsInScope: () => specPathsInScope,
27547
28020
  splitNamespace: () => splitNamespace,
27548
28021
  stateIdEquals: () => stateIdEquals,
@@ -27898,6 +28371,7 @@ function loadProjectConfig4() {
27898
28371
  BuiltinTargetConfigSchema,
27899
28372
  BuiltinTargetSchema,
27900
28373
  BundleNotFoundError,
28374
+ COMPONENT_AUTH_SOURCE,
27901
28375
  CONTEXT_PATHS,
27902
28376
  CarriedDebtKindSchema,
27903
28377
  CarriedDebtSchema,
@@ -28202,6 +28676,7 @@ function loadProjectConfig4() {
28202
28676
  methodGenericParameters,
28203
28677
  methodSourceFile,
28204
28678
  methodTypeRefs,
28679
+ moveMethods,
28205
28680
  moveSubsystemProject,
28206
28681
  narrativeStepForeignFields,
28207
28682
  normalizeApprover,
@@ -28261,6 +28736,7 @@ function loadProjectConfig4() {
28261
28736
  resolveVariantGuidance,
28262
28737
  restoreSpecFiles,
28263
28738
  retireSpecialists,
28739
+ rewriteSpecRefs,
28264
28740
  runWithProjectBinding,
28265
28741
  runWithProjectRoot,
28266
28742
  sameContract,
@@ -28283,6 +28759,7 @@ function loadProjectConfig4() {
28283
28759
  skillsDirForTarget,
28284
28760
  snapshotSpecFiles,
28285
28761
  specChanges,
28762
+ specKind,
28286
28763
  specPathsInScope,
28287
28764
  splitNamespace,
28288
28765
  stateIdEquals,