@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/README.md +6 -0
- package/dist/cli/index.js +611 -87
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +566 -89
- package/dist/index.js.map +1 -1
- package/dist/templates/skills/sdd-delegate.md +32 -0
- package/dist/templates/skills/sdd-implement.md +57 -0
- package/package.json +1 -1
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.
|
|
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 (
|
|
17800
|
-
|
|
17801
|
-
|
|
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,
|
|
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
|
-
|
|
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"),
|