@wairon/cli 5.1.1-dev.12 → 5.1.1-dev.13
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 +351 -99
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +245 -75
- package/dist/index.js.map +1 -1
- 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.13";
|
|
69
69
|
GITHUB_REPO = "SYW-Apps/Waffle-AIron";
|
|
70
70
|
ARCHITECT_AGENT_ID = "agent-architect";
|
|
71
71
|
ARCHITECT_TEMPLATE_ID = "architect";
|
|
@@ -3554,17 +3554,43 @@ function isExternalNamespaceRef(ctx, ref) {
|
|
|
3554
3554
|
if (sep9 === -1) return false;
|
|
3555
3555
|
return !ctx.subsystemIds.has(ref.slice(0, sep9));
|
|
3556
3556
|
}
|
|
3557
|
+
function isProvidedBy(snapshot, provider) {
|
|
3558
|
+
return snapshot.projectName === provider || snapshot.projectName.split("::").pop() === provider;
|
|
3559
|
+
}
|
|
3560
|
+
function sameContract(a, b) {
|
|
3561
|
+
return a.component.split("::").pop() === b.component.split("::").pop() && JSON.stringify(a.methods) === JSON.stringify(b.methods) && JSON.stringify(a.dispatch ?? []) === JSON.stringify(b.dispatch ?? []);
|
|
3562
|
+
}
|
|
3557
3563
|
function resolveSurfaceRef(ctx, ref, fromSubsystem) {
|
|
3558
|
-
const
|
|
3559
|
-
|
|
3564
|
+
const segments = ref.split("::").filter((seg) => seg && seg !== "super");
|
|
3565
|
+
const local = segments.pop();
|
|
3566
|
+
if (!local) return { kind: "unresolved" };
|
|
3567
|
+
const provider = segments.pop();
|
|
3560
3568
|
const mountPools = fromSubsystem ? enclosingMounts(ctx, fromSubsystem).reverse().map((ns) => ctx.mountSurfaceSnapshots.find((m) => m.namespace === ns)?.snapshots ?? []) : [];
|
|
3561
3569
|
for (const pool of [...mountPools, ctx.surfaceSnapshots]) {
|
|
3570
|
+
const candidates = [];
|
|
3562
3571
|
for (const snapshot of pool) {
|
|
3572
|
+
if (provider !== void 0 && !isProvidedBy(snapshot, provider)) continue;
|
|
3563
3573
|
const entry = snapshot.interfaces.find((e) => e.component === local || e.id === local);
|
|
3564
|
-
if (entry)
|
|
3574
|
+
if (entry) candidates.push({ snapshot, entry });
|
|
3565
3575
|
}
|
|
3576
|
+
if (candidates.length === 0) continue;
|
|
3577
|
+
const [first] = candidates;
|
|
3578
|
+
if (candidates.every((c) => sameContract(c.entry, first.entry))) {
|
|
3579
|
+
return { kind: "resolved", ...first };
|
|
3580
|
+
}
|
|
3581
|
+
return { kind: "ambiguous", providers: [...new Set(candidates.map((c) => c.snapshot.projectName))] };
|
|
3566
3582
|
}
|
|
3567
|
-
return
|
|
3583
|
+
return { kind: "unresolved" };
|
|
3584
|
+
}
|
|
3585
|
+
function reportAmbiguousSurfaceRef(ctx, subject, ref, providers, specId, isDraftContext) {
|
|
3586
|
+
const local = ref.split("::").filter((seg) => seg && seg !== "super").pop() ?? ref;
|
|
3587
|
+
ctx.addIssue(
|
|
3588
|
+
"error",
|
|
3589
|
+
"SURFACE_REF_AMBIGUOUS",
|
|
3590
|
+
`${subject} cross-tree component "${ref}", which the surface snapshots of ${providers.map((p) => `"${p}"`).join(", ")} expose with different contracts \u2014 the reference matches more than one declared contract, so none of them can judge it. Name the provider it means (super::<provider>::${local}), or remove the snapshot that no longer applies.`,
|
|
3591
|
+
specId,
|
|
3592
|
+
isDraftContext
|
|
3593
|
+
);
|
|
3568
3594
|
}
|
|
3569
3595
|
function enclosingMounts(ctx, subsystemId) {
|
|
3570
3596
|
const mounts = [];
|
|
@@ -3655,6 +3681,7 @@ var init_contracts = __esm({
|
|
|
3655
3681
|
{ code: "INVALID_TARGET_COMPONENT_REFERENCE", defaultSeverity: "error", summary: "Call/register step targets a non-existent component" },
|
|
3656
3682
|
{ code: "CROSS_TREE_REF_UNRESOLVED", defaultSeverity: "warning", summary: "Cross-tree reference (super::/:: form) with no surface snapshot covering it \u2014 only the parent project can verify it" },
|
|
3657
3683
|
{ code: "SURFACE_REF_NOT_EXPOSED", defaultSeverity: "error", summary: "Cross-tree reference resolves to a surface snapshot that does not expose the called method/capability" },
|
|
3684
|
+
{ code: "SURFACE_REF_AMBIGUOUS", defaultSeverity: "error", summary: "Cross-tree call/dispatch/register target matched by surface snapshots of several providers with different contracts" },
|
|
3658
3685
|
{ code: "UNDECLARED_DEPENDENCY_CALL", defaultSeverity: "error", summary: "Call step targets a component the caller does not depend on or own" },
|
|
3659
3686
|
{ code: "INVALID_TARGET_METHOD_REFERENCE", defaultSeverity: "error", summary: "Call step targets a method not on any target interface" },
|
|
3660
3687
|
{ code: "NARRATIVE_SEMANTIC_UNBACKED", defaultSeverity: "warning", summary: "Narrative asserts a guarantee the called contract does not declare" }
|
|
@@ -3709,7 +3736,18 @@ var init_contracts = __esm({
|
|
|
3709
3736
|
if (!dispatchTarget) {
|
|
3710
3737
|
if (isCrossTreeForm || isCollapsedForm) {
|
|
3711
3738
|
const resolved = resolveSurfaceRef(ctx, step.targetComponent, fromSubsystem);
|
|
3712
|
-
if (resolved) {
|
|
3739
|
+
if (resolved.kind === "ambiguous") {
|
|
3740
|
+
reportAmbiguousSurfaceRef(
|
|
3741
|
+
ctx,
|
|
3742
|
+
`Method "${implMethod.name}" in implementation "${impl.id}" dispatches (step ${step.stepNumber}) through`,
|
|
3743
|
+
step.targetComponent,
|
|
3744
|
+
resolved.providers,
|
|
3745
|
+
impl.id,
|
|
3746
|
+
isDraftCtx
|
|
3747
|
+
);
|
|
3748
|
+
continue;
|
|
3749
|
+
}
|
|
3750
|
+
if (resolved.kind === "resolved") {
|
|
3713
3751
|
if (step.capability && !(resolved.entry.dispatch ?? []).some((b) => b.capability === step.capability)) {
|
|
3714
3752
|
ctx.addIssue(
|
|
3715
3753
|
"error",
|
|
@@ -3769,7 +3807,18 @@ var init_contracts = __esm({
|
|
|
3769
3807
|
if (!targetComp) {
|
|
3770
3808
|
if (isCrossTreeForm || isCollapsedForm) {
|
|
3771
3809
|
const resolved = resolveSurfaceRef(ctx, step.targetComponent, fromSubsystem);
|
|
3772
|
-
if (resolved) {
|
|
3810
|
+
if (resolved.kind === "ambiguous") {
|
|
3811
|
+
reportAmbiguousSurfaceRef(
|
|
3812
|
+
ctx,
|
|
3813
|
+
`Method "${implMethod.name}" in implementation "${impl.id}" ${verb} "${step.targetMethod}" (step ${step.stepNumber}) on`,
|
|
3814
|
+
step.targetComponent,
|
|
3815
|
+
resolved.providers,
|
|
3816
|
+
impl.id,
|
|
3817
|
+
isDraftCtx
|
|
3818
|
+
);
|
|
3819
|
+
continue;
|
|
3820
|
+
}
|
|
3821
|
+
if (resolved.kind === "resolved") {
|
|
3773
3822
|
const surfaceMethod = resolved.entry.methods.find((m) => m.name === step.targetMethod);
|
|
3774
3823
|
if (!surfaceMethod) {
|
|
3775
3824
|
ctx.addIssue(
|
|
@@ -5284,6 +5333,7 @@ var init_stereotype_deps = __esm({
|
|
|
5284
5333
|
codes: [
|
|
5285
5334
|
{ code: "INVALID_DEPENDENCY_REFERENCE", defaultSeverity: "error", summary: "dependsOn names a non-existent component" },
|
|
5286
5335
|
{ code: "CROSS_TREE_REF_UNRESOLVED", defaultSeverity: "warning", summary: "Cross-tree dependsOn (super::/:: form) with no surface snapshot covering it" },
|
|
5336
|
+
{ code: "SURFACE_REF_AMBIGUOUS", defaultSeverity: "error", summary: "Cross-tree dependsOn matched by surface snapshots of several providers with different contracts" },
|
|
5287
5337
|
{ code: "CROSS_SUBSYSTEM_NON_ADAPTER", defaultSeverity: "error", summary: "Non-Adapter component crossing a subsystem boundary" },
|
|
5288
5338
|
{ code: "CROSS_SUBSYSTEM_PRIVATE_ACCESS", defaultSeverity: "error", summary: "Cross-subsystem dependency on an unpublished component" },
|
|
5289
5339
|
{ code: "CROSS_SUBSYSTEM_TARGET_NON_PORTAL", defaultSeverity: "error", summary: "Cross-subsystem hop entering through a non-Portal" },
|
|
@@ -5307,7 +5357,11 @@ var init_stereotype_deps = __esm({
|
|
|
5307
5357
|
const external = isExternalNamespaceRef(ctx, depId);
|
|
5308
5358
|
if (external || isCollapsedCrossTreeRef(ctx, depId, comp.subsystem)) {
|
|
5309
5359
|
const resolved = resolveSurfaceRef(ctx, depId, comp.subsystem);
|
|
5310
|
-
if (resolved) {
|
|
5360
|
+
if (resolved.kind === "ambiguous") {
|
|
5361
|
+
reportAmbiguousSurfaceRef(ctx, `Component "${comp.id}" depends on`, depId, resolved.providers, comp.id, isDraftCtx);
|
|
5362
|
+
continue;
|
|
5363
|
+
}
|
|
5364
|
+
if (resolved.kind === "resolved") {
|
|
5311
5365
|
if (comp.componentType !== "Adapter") {
|
|
5312
5366
|
ctx.addIssue(
|
|
5313
5367
|
"error",
|
|
@@ -9009,8 +9063,8 @@ function doctrineIdentity(doctrine, gate) {
|
|
|
9009
9063
|
)
|
|
9010
9064
|
};
|
|
9011
9065
|
}
|
|
9012
|
-
function hashGateState(doctrine, gate = {}) {
|
|
9013
|
-
const payload = { tree: loadTree(), doctrine: doctrineIdentity(doctrine, gate) };
|
|
9066
|
+
function hashGateState(doctrine, inputs, gate = {}) {
|
|
9067
|
+
const payload = { tree: loadTree(), doctrine: doctrineIdentity(doctrine, gate), inputs: [...inputs].sort() };
|
|
9014
9068
|
const digest2 = crypto2.createHash("sha256").update(canonicalize(payload)).digest("hex");
|
|
9015
9069
|
return { algorithm: GATE_ALGORITHM, digest: digest2 };
|
|
9016
9070
|
}
|
|
@@ -9041,7 +9095,7 @@ var init_statehash = __esm({
|
|
|
9041
9095
|
init_specs2();
|
|
9042
9096
|
init_rules();
|
|
9043
9097
|
CONTENT_ALGORITHM = "sha256";
|
|
9044
|
-
GATE_ALGORITHM = "sha256+doctrine";
|
|
9098
|
+
GATE_ALGORITHM = "sha256+doctrine+inputs";
|
|
9045
9099
|
}
|
|
9046
9100
|
});
|
|
9047
9101
|
|
|
@@ -14686,15 +14740,18 @@ function importSurface(sourcePath, origin) {
|
|
|
14686
14740
|
saveSnapshot(snapshot);
|
|
14687
14741
|
return snapshot;
|
|
14688
14742
|
}
|
|
14689
|
-
function
|
|
14690
|
-
|
|
14691
|
-
if (!parent) return null;
|
|
14692
|
-
const childRoot = getProjectRoot();
|
|
14693
|
-
const projected = runWithProjectRoot(parent.parentRoot, () => {
|
|
14743
|
+
function projectFamilySurfaces(parent) {
|
|
14744
|
+
return runWithProjectRoot(parent.parentRoot, () => {
|
|
14694
14745
|
invalidateSpecCache();
|
|
14695
14746
|
const siblings = loadSubsystemSpecs().filter((s) => !s.id.includes("::") && s.id !== parent.subsystemId);
|
|
14696
14747
|
return [projectChildSurface(), ...siblings.map((s) => projectSubsystemSurface(s.id))];
|
|
14697
14748
|
});
|
|
14749
|
+
}
|
|
14750
|
+
function pinFamilySurfaces() {
|
|
14751
|
+
const parent = resolveChainingParent();
|
|
14752
|
+
if (!parent) return null;
|
|
14753
|
+
const childRoot = getProjectRoot();
|
|
14754
|
+
const projected = projectFamilySurfaces(parent);
|
|
14698
14755
|
const before = new Map(listSnapshots(childRoot).map((s) => [s.projectName, surfaceContentKey(s)]));
|
|
14699
14756
|
const changed = [];
|
|
14700
14757
|
for (const snapshot of projected) {
|
|
@@ -14705,17 +14762,21 @@ function pinFamilySurfaces() {
|
|
|
14705
14762
|
}
|
|
14706
14763
|
return changed;
|
|
14707
14764
|
}
|
|
14708
|
-
function
|
|
14709
|
-
|
|
14765
|
+
function projectedFamilyContent(parent) {
|
|
14766
|
+
try {
|
|
14767
|
+
return new Map(projectFamilySurfaces(parent).map((s) => [s.projectName, surfaceContentKey(s)]));
|
|
14768
|
+
} catch {
|
|
14769
|
+
return null;
|
|
14770
|
+
}
|
|
14710
14771
|
}
|
|
14711
14772
|
function listExternalInterfaces() {
|
|
14712
14773
|
const snapshots = listSnapshots();
|
|
14713
14774
|
const chainingParent = resolveChainingParent();
|
|
14714
|
-
const
|
|
14775
|
+
const projected = chainingParent ? projectedFamilyContent(chainingParent) : null;
|
|
14715
14776
|
return snapshots.map((snapshot) => {
|
|
14716
14777
|
const generated = snapshot.origin === "generated";
|
|
14717
14778
|
const sourceKind = !generated ? "foreign" : snapshot.projectName.includes("::") ? "sibling" : "parent";
|
|
14718
|
-
const freshness = generated &&
|
|
14779
|
+
const freshness = generated && projected ? projected.get(snapshot.projectName) === surfaceContentKey(snapshot) ? "fresh" : "stale" : "unverifiable";
|
|
14719
14780
|
return {
|
|
14720
14781
|
projectName: snapshot.projectName,
|
|
14721
14782
|
origin: snapshot.origin,
|
|
@@ -15075,7 +15136,7 @@ function validateSddTree(rulesOrOptions, projectType = "backend") {
|
|
|
15075
15136
|
rule.check(ctx);
|
|
15076
15137
|
}
|
|
15077
15138
|
const unresolved = (i) => RESOLUTION_FAILURE_CODES.has(i.code) && !i.surfaceResolved;
|
|
15078
|
-
const chainingParent = crossTree !== "off" && issues.some(unresolved) ?
|
|
15139
|
+
const chainingParent = crossTree !== "off" && issues.some(unresolved) ? resolveChainingParent() : null;
|
|
15079
15140
|
const resolution = chainingParent ? resolveThroughParent(getProjectRoot(), treatAllAsComplete, rules) : null;
|
|
15080
15141
|
if (resolution) {
|
|
15081
15142
|
const judgedByParent = worstByKey(resolution.issues);
|
|
@@ -15113,7 +15174,7 @@ function resolveThroughParent(boundRoot, treatAllAsComplete, childRules) {
|
|
|
15113
15174
|
const chain = [];
|
|
15114
15175
|
let top = path18.resolve(boundRoot);
|
|
15115
15176
|
while (top !== ceiling) {
|
|
15116
|
-
const hop = findChainingParent(top);
|
|
15177
|
+
const hop = findChainingParent(top, ceiling);
|
|
15117
15178
|
if (!hop) break;
|
|
15118
15179
|
const next = path18.resolve(hop.parentRoot);
|
|
15119
15180
|
if (ceiling && !isWithinOrEqual(ceiling, next)) break;
|
|
@@ -15788,6 +15849,7 @@ __export(specs_exports, {
|
|
|
15788
15849
|
getSubprojectPrefix: () => getSubprojectPrefix,
|
|
15789
15850
|
getSubsystemPath: () => getSubsystemPath,
|
|
15790
15851
|
getTypePath: () => getTypePath,
|
|
15852
|
+
inspectChainedRoots: () => inspectChainedRoots,
|
|
15791
15853
|
invalidateSpecCache: () => invalidateSpecCache,
|
|
15792
15854
|
listChainedRoots: () => listChainedRoots,
|
|
15793
15855
|
loadComponentSpec: () => loadComponentSpec,
|
|
@@ -15805,6 +15867,7 @@ __export(specs_exports, {
|
|
|
15805
15867
|
loadTypeSpecs: () => loadTypeSpecs,
|
|
15806
15868
|
normalizeComponentLayout: () => normalizeComponentLayout,
|
|
15807
15869
|
readLockState: () => readLockState,
|
|
15870
|
+
rebaseReference: () => rebaseReference,
|
|
15808
15871
|
resolveChainingParent: () => resolveChainingParent,
|
|
15809
15872
|
resolveSubprojectForNamespace: () => resolveSubprojectForNamespace,
|
|
15810
15873
|
restoreSpecFiles: () => restoreSpecFiles,
|
|
@@ -15904,33 +15967,75 @@ function relativizeId(id, prefix) {
|
|
|
15904
15967
|
if (common === idParts.length) common--;
|
|
15905
15968
|
return `${"super::".repeat(prefixParts.length - common)}${idParts.slice(common).join("::")}`;
|
|
15906
15969
|
}
|
|
15970
|
+
function rebaseReference(ref, mount, direction, isMoved = () => false) {
|
|
15971
|
+
if (!ref || ref.startsWith("::")) return ref;
|
|
15972
|
+
const segments = ref.split("::");
|
|
15973
|
+
let hops = 0;
|
|
15974
|
+
while (segments[hops] === "super") hops++;
|
|
15975
|
+
if (hops === segments.length) return ref;
|
|
15976
|
+
const outer = Array.from({ length: hops + 1 }, (_, i) => `<n${i}>`).join("::");
|
|
15977
|
+
const inner = `${outer}::${mount}`;
|
|
15978
|
+
const [from, to] = direction === "into" ? [outer, inner] : [inner, outer];
|
|
15979
|
+
const target = qualifyId(ref, from, NO_ROOT_SUBSYSTEMS);
|
|
15980
|
+
const left = relativizeId(target, from);
|
|
15981
|
+
const travels = !left.startsWith("super::") && (direction === "outOf" || isMoved(left));
|
|
15982
|
+
return relativizeId(travels ? qualifyId(left, to, NO_ROOT_SUBSYSTEMS) : target, to);
|
|
15983
|
+
}
|
|
15907
15984
|
function isWithin(dir, file) {
|
|
15908
15985
|
const d = path19.resolve(dir);
|
|
15909
15986
|
const f = path19.resolve(file);
|
|
15910
15987
|
return f === d || f.startsWith(d + path19.sep);
|
|
15911
15988
|
}
|
|
15989
|
+
function canonicalPath(target) {
|
|
15990
|
+
let existing = path19.resolve(target);
|
|
15991
|
+
const rest = [];
|
|
15992
|
+
for (; ; ) {
|
|
15993
|
+
try {
|
|
15994
|
+
fs13.lstatSync(existing);
|
|
15995
|
+
break;
|
|
15996
|
+
} catch {
|
|
15997
|
+
const up = path19.dirname(existing);
|
|
15998
|
+
if (up === existing) return path19.resolve(target);
|
|
15999
|
+
rest.unshift(path19.basename(existing));
|
|
16000
|
+
existing = up;
|
|
16001
|
+
}
|
|
16002
|
+
}
|
|
16003
|
+
try {
|
|
16004
|
+
return path19.join(fs13.realpathSync.native(existing), ...rest);
|
|
16005
|
+
} catch {
|
|
16006
|
+
return null;
|
|
16007
|
+
}
|
|
16008
|
+
}
|
|
16009
|
+
function chainDirKey(dir) {
|
|
16010
|
+
return canonicalPath(dir) ?? path19.resolve(dir);
|
|
16011
|
+
}
|
|
15912
16012
|
function projectPathEscapesRoot(projectRoot2, projectPath, resolvedChildDir) {
|
|
15913
|
-
|
|
16013
|
+
if (path19.isAbsolute(projectPath) || !isWithin(projectRoot2, resolvedChildDir)) return true;
|
|
16014
|
+
const root = canonicalPath(projectRoot2);
|
|
16015
|
+
const child = canonicalPath(resolvedChildDir);
|
|
16016
|
+
return root === null || child === null || !isWithin(root, child);
|
|
15914
16017
|
}
|
|
15915
16018
|
function assertContainedProjectPath(projectRoot2, projectPath) {
|
|
15916
16019
|
const root = path19.resolve(projectRoot2);
|
|
15917
16020
|
const resolved = path19.resolve(root, projectPath);
|
|
15918
16021
|
if (projectPathEscapesRoot(root, projectPath, resolved)) {
|
|
15919
16022
|
throw new Error(
|
|
15920
|
-
`projectPath "${projectPath}" must resolve within the project root "${root}", but resolves to "${resolved}"; absolute
|
|
16023
|
+
`projectPath "${projectPath}" must resolve within the project root "${root}", but resolves to "${resolved}"; absolute, ../-escaping and link-escaping paths are rejected so a chained subproject is always contained by its parent.`
|
|
15921
16024
|
);
|
|
15922
16025
|
}
|
|
15923
16026
|
return resolved;
|
|
15924
16027
|
}
|
|
15925
|
-
function findChainingParent(childRoot) {
|
|
16028
|
+
function findChainingParent(childRoot, ceiling) {
|
|
15926
16029
|
let childResolved;
|
|
15927
16030
|
try {
|
|
15928
16031
|
childResolved = path19.resolve(childRoot);
|
|
15929
16032
|
} catch {
|
|
15930
16033
|
return null;
|
|
15931
16034
|
}
|
|
16035
|
+
const bound = ceiling ? path19.resolve(ceiling) : void 0;
|
|
15932
16036
|
let dir = path19.dirname(childResolved);
|
|
15933
16037
|
for (let hops = 0; hops < 32; hops++) {
|
|
16038
|
+
if (bound && !isWithin(bound, dir)) break;
|
|
15934
16039
|
const specsDir = aiPathsAt(dir).specsDir();
|
|
15935
16040
|
if (pathExists(specsDir)) {
|
|
15936
16041
|
for (const file of listFilesRecursive(specsDir, ".yaml")) {
|
|
@@ -15944,7 +16049,8 @@ function findChainingParent(childRoot) {
|
|
|
15944
16049
|
const projectPath = raw.projectPath;
|
|
15945
16050
|
if (typeof projectPath === "string" && projectPath.trim() !== "") {
|
|
15946
16051
|
try {
|
|
15947
|
-
|
|
16052
|
+
const mountDir = path19.resolve(dir, projectPath);
|
|
16053
|
+
if (mountDir === childResolved && !projectPathEscapesRoot(dir, projectPath, mountDir)) {
|
|
15948
16054
|
const id = raw.id;
|
|
15949
16055
|
return { parentRoot: dir, subsystemId: typeof id === "string" ? id : "?" };
|
|
15950
16056
|
}
|
|
@@ -15960,12 +16066,11 @@ function findChainingParent(childRoot) {
|
|
|
15960
16066
|
}
|
|
15961
16067
|
return null;
|
|
15962
16068
|
}
|
|
15963
|
-
function
|
|
16069
|
+
function inspectChainedRoots(rootDir = getProjectRoot()) {
|
|
15964
16070
|
const root = path19.resolve(rootDir);
|
|
15965
|
-
const
|
|
15966
|
-
const
|
|
15967
|
-
const walk = (projectDir, depth) => {
|
|
15968
|
-
if (depth > 32) return;
|
|
16071
|
+
const inspection = { roots: [], skipped: [] };
|
|
16072
|
+
const listed = /* @__PURE__ */ new Set();
|
|
16073
|
+
const walk = (projectDir, prefix, ancestors, depth) => {
|
|
15969
16074
|
const specsDir = aiPathsAt(projectDir).specsDir();
|
|
15970
16075
|
if (!pathExists(specsDir)) return;
|
|
15971
16076
|
for (const file of listFilesRecursive(specsDir, ".yaml")) {
|
|
@@ -15976,24 +16081,37 @@ function listChainedRoots(rootDir = getProjectRoot()) {
|
|
|
15976
16081
|
continue;
|
|
15977
16082
|
}
|
|
15978
16083
|
if (!raw || typeof raw !== "object" || !("parentSystem" in raw)) continue;
|
|
15979
|
-
const projectPath = raw
|
|
16084
|
+
const { id, projectPath } = raw;
|
|
15980
16085
|
if (typeof projectPath !== "string" || projectPath.trim() === "") continue;
|
|
16086
|
+
const localId = typeof id === "string" ? id : "?";
|
|
15981
16087
|
let childDir;
|
|
15982
16088
|
try {
|
|
15983
16089
|
childDir = path19.resolve(projectDir, projectPath);
|
|
15984
16090
|
} catch {
|
|
15985
16091
|
continue;
|
|
15986
16092
|
}
|
|
15987
|
-
|
|
15988
|
-
if (
|
|
15989
|
-
if (
|
|
15990
|
-
|
|
15991
|
-
|
|
15992
|
-
|
|
16093
|
+
let reason;
|
|
16094
|
+
if (projectPathEscapesRoot(projectDir, projectPath, childDir)) reason = "escapes";
|
|
16095
|
+
else if (ancestors.has(chainDirKey(childDir))) reason = "cyclic";
|
|
16096
|
+
else if (!fs13.existsSync(childDir)) reason = "missing";
|
|
16097
|
+
else if (depth >= 32) reason = "too-deep";
|
|
16098
|
+
if (reason) {
|
|
16099
|
+
const mount = prefix ? qualifyDeclaredId(localId, prefix, true) : localId;
|
|
16100
|
+
inspection.skipped.push({ mount, projectPath, reason });
|
|
16101
|
+
continue;
|
|
16102
|
+
}
|
|
16103
|
+
const key = chainDirKey(childDir);
|
|
16104
|
+
if (listed.has(key)) continue;
|
|
16105
|
+
listed.add(key);
|
|
16106
|
+
inspection.roots.push(path19.relative(root, childDir).split(path19.sep).join("/"));
|
|
16107
|
+
walk(childDir, prefix ? `${prefix}::${localId}` : localId, /* @__PURE__ */ new Set([...ancestors, key]), depth + 1);
|
|
15993
16108
|
}
|
|
15994
16109
|
};
|
|
15995
|
-
walk(root, 0);
|
|
15996
|
-
return
|
|
16110
|
+
walk(root, "", /* @__PURE__ */ new Set([chainDirKey(root)]), 0);
|
|
16111
|
+
return inspection;
|
|
16112
|
+
}
|
|
16113
|
+
function listChainedRoots(rootDir = getProjectRoot()) {
|
|
16114
|
+
return inspectChainedRoots(rootDir).roots;
|
|
15997
16115
|
}
|
|
15998
16116
|
function mergeMountRealizations(subs) {
|
|
15999
16117
|
const result = [];
|
|
@@ -16276,12 +16394,24 @@ function buildProjectGraph(level) {
|
|
|
16276
16394
|
function resolveChainingParent() {
|
|
16277
16395
|
const reach = getRequestParentReach();
|
|
16278
16396
|
if (reach && !reach.parentReach) return null;
|
|
16279
|
-
|
|
16280
|
-
|
|
16281
|
-
|
|
16282
|
-
|
|
16397
|
+
return findChainingParent(getProjectRoot(), reach?.topRoot);
|
|
16398
|
+
}
|
|
16399
|
+
function snapshotInputKey(snapshot) {
|
|
16400
|
+
const { stateId, generatedAt, origin, ...content } = snapshot;
|
|
16401
|
+
return canonicalize(content);
|
|
16402
|
+
}
|
|
16403
|
+
function consumedSurfaceInputsAt(rootDir) {
|
|
16404
|
+
const dir = path19.join(rootDir, ".wai", "surfaces");
|
|
16405
|
+
if (!pathExists(dir)) return [];
|
|
16406
|
+
const keys = [];
|
|
16407
|
+
for (const file of fs13.readdirSync(dir)) {
|
|
16408
|
+
if (!file.endsWith(".yaml") && !file.endsWith(".yml")) continue;
|
|
16409
|
+
try {
|
|
16410
|
+
keys.push(snapshotInputKey(SurfaceSnapshotSchema.parse(readYamlFile(path19.join(dir, file)))));
|
|
16411
|
+
} catch {
|
|
16412
|
+
}
|
|
16283
16413
|
}
|
|
16284
|
-
return
|
|
16414
|
+
return keys;
|
|
16285
16415
|
}
|
|
16286
16416
|
function computeGateStateId() {
|
|
16287
16417
|
let gate = {};
|
|
@@ -16290,7 +16420,10 @@ function computeGateStateId() {
|
|
|
16290
16420
|
gate = { projectType: config.projectType, rules: config.rules };
|
|
16291
16421
|
} catch {
|
|
16292
16422
|
}
|
|
16293
|
-
|
|
16423
|
+
const root = getProjectRoot();
|
|
16424
|
+
const roots = [root, ...listChainedRoots(root).map((rel2) => path19.join(root, rel2))];
|
|
16425
|
+
const inputs = roots.flatMap(consumedSurfaceInputsAt);
|
|
16426
|
+
return hashGateState(loadProjectExtensions(), inputs, gate);
|
|
16294
16427
|
}
|
|
16295
16428
|
function readLockState() {
|
|
16296
16429
|
const record2 = readLockRecord();
|
|
@@ -16400,7 +16533,7 @@ var init_specs2 = __esm({
|
|
|
16400
16533
|
this.rootSubsystems.clear();
|
|
16401
16534
|
this.cachedRecursive = recursive;
|
|
16402
16535
|
this.scanVisitedSpecDirs = [];
|
|
16403
|
-
const visited = /* @__PURE__ */ new Set([
|
|
16536
|
+
const visited = /* @__PURE__ */ new Set([chainDirKey(this.rootDir)]);
|
|
16404
16537
|
const maxDepth = typeof recursive === "number" ? recursive : recursive ? Infinity : 0;
|
|
16405
16538
|
this.cachedIndex = this.scanSpecsForProject(this.rootDir, "", visited, maxDepth, 0);
|
|
16406
16539
|
this.cachedSpecDirs = this.scanVisitedSpecDirs;
|
|
@@ -16600,21 +16733,22 @@ var init_specs2 = __esm({
|
|
|
16600
16733
|
if (currentDepth < maxDepth) {
|
|
16601
16734
|
for (const subproj of localSubprojects) {
|
|
16602
16735
|
const childDir = path19.resolve(projectDir, subproj.projectPath);
|
|
16603
|
-
|
|
16736
|
+
const mountId = namespacePrefix ? qualifyDeclaredId(subproj.subsystemId, namespacePrefix, true) : subproj.subsystemId;
|
|
16737
|
+
if (projectPathEscapesRoot(projectDir, subproj.projectPath, childDir)) {
|
|
16604
16738
|
this.loaderIssues.push({
|
|
16605
16739
|
severity: "error",
|
|
16606
16740
|
code: "PROJECTPATH_ESCAPE",
|
|
16607
|
-
message: `Subproject path "${subproj.projectPath}" declared by subsystem "${
|
|
16608
|
-
specId:
|
|
16741
|
+
message: `Subproject path "${subproj.projectPath}" declared by subsystem "${mountId}" escapes the root of the project declaring it, "${projectDir}" (resolves to "${childDir}"); absolute, ../-escaping and link-escaping projectPaths are rejected. Skipping this subproject.`,
|
|
16742
|
+
specId: mountId
|
|
16609
16743
|
});
|
|
16610
16744
|
continue;
|
|
16611
16745
|
}
|
|
16612
|
-
if (visitedDirs.has(childDir)) {
|
|
16746
|
+
if (visitedDirs.has(chainDirKey(childDir))) {
|
|
16613
16747
|
this.loaderIssues.push({
|
|
16614
16748
|
severity: "error",
|
|
16615
16749
|
code: "CIRCULAR_SUBPROJECT_REFERENCE",
|
|
16616
|
-
message: `Circular reference detected: Subsystem "${
|
|
16617
|
-
specId:
|
|
16750
|
+
message: `Circular reference detected: Subsystem "${mountId}" refers to subproject "${childDir}" which is already loaded.`,
|
|
16751
|
+
specId: mountId
|
|
16618
16752
|
});
|
|
16619
16753
|
continue;
|
|
16620
16754
|
}
|
|
@@ -16622,14 +16756,14 @@ var init_specs2 = __esm({
|
|
|
16622
16756
|
this.loaderIssues.push({
|
|
16623
16757
|
severity: "error",
|
|
16624
16758
|
code: "SUBPROJECT_NOT_FOUND",
|
|
16625
|
-
message: `Subproject directory "${childDir}" declared by subsystem "${
|
|
16626
|
-
specId:
|
|
16759
|
+
message: `Subproject directory "${childDir}" declared by subsystem "${mountId}" does not exist.`,
|
|
16760
|
+
specId: mountId
|
|
16627
16761
|
});
|
|
16628
16762
|
continue;
|
|
16629
16763
|
}
|
|
16630
16764
|
const childNamespace = namespacePrefix ? `${namespacePrefix}::${subproj.subsystemId}` : subproj.subsystemId;
|
|
16631
16765
|
const newVisited = new Set(visitedDirs);
|
|
16632
|
-
newVisited.add(childDir);
|
|
16766
|
+
newVisited.add(chainDirKey(childDir));
|
|
16633
16767
|
const childIndex = this.scanSpecsForProject(childDir, childNamespace, newVisited, maxDepth, currentDepth + 1);
|
|
16634
16768
|
index.subsystems.push(...childIndex.subsystems);
|
|
16635
16769
|
index.components.push(...childIndex.components);
|
|
@@ -16662,11 +16796,11 @@ var init_specs2 = __esm({
|
|
|
16662
16796
|
const sub = index.subsystems.find((s) => s.id === currentPrefix);
|
|
16663
16797
|
if (sub && sub.projectPath) {
|
|
16664
16798
|
const nextDir = path19.resolve(currentDir, sub.projectPath);
|
|
16665
|
-
if (projectPathEscapesRoot(
|
|
16799
|
+
if (projectPathEscapesRoot(currentDir, sub.projectPath, nextDir)) {
|
|
16666
16800
|
this.loaderIssues.push({
|
|
16667
16801
|
severity: "error",
|
|
16668
16802
|
code: "PROJECTPATH_ESCAPE",
|
|
16669
|
-
message: `Subproject path "${sub.projectPath}" declared by subsystem "${currentPrefix}" escapes the project
|
|
16803
|
+
message: `Subproject path "${sub.projectPath}" declared by subsystem "${currentPrefix}" escapes the root of the project declaring it, "${currentDir}" (resolves to "${nextDir}"); absolute, ../-escaping and link-escaping projectPaths are rejected. Skipping this subproject.`,
|
|
16670
16804
|
specId: currentPrefix
|
|
16671
16805
|
});
|
|
16672
16806
|
continue;
|
|
@@ -18310,6 +18444,7 @@ function externalizeSubsystem(subsystemId, projectPath) {
|
|
|
18310
18444
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
18311
18445
|
});
|
|
18312
18446
|
rewriteRefsInDir(parentSpecsDir, renameMap, fooDir);
|
|
18447
|
+
rebaseMovedRefs(childFooDir, subsystemId, "into");
|
|
18313
18448
|
invalidateSpecCache();
|
|
18314
18449
|
}
|
|
18315
18450
|
function internalizeSubsystem(subsystemId) {
|
|
@@ -18349,6 +18484,7 @@ function internalizeSubsystem(subsystemId) {
|
|
|
18349
18484
|
});
|
|
18350
18485
|
fs14.rmSync(childWai, { recursive: true, force: true });
|
|
18351
18486
|
rewriteRefsInDir(parentSpecsDir, renameMap, fooDir);
|
|
18487
|
+
rebaseMovedRefs(fooDir, subsystemId, "outOf");
|
|
18352
18488
|
invalidateSpecCache();
|
|
18353
18489
|
}
|
|
18354
18490
|
function buildRenameMap(subsystemId, externalize) {
|
|
@@ -18375,7 +18511,27 @@ function buildRenameMap(subsystemId, externalize) {
|
|
|
18375
18511
|
}
|
|
18376
18512
|
function rewriteRefsInDir(specsDir, renameMap, excludeDir) {
|
|
18377
18513
|
if (renameMap.size === 0) return;
|
|
18378
|
-
|
|
18514
|
+
rewriteRefFields(specsDir, (ref) => renameMap.get(ref) ?? ref, excludeDir);
|
|
18515
|
+
}
|
|
18516
|
+
function rebaseMovedRefs(movedDir, mount, direction) {
|
|
18517
|
+
const declared = componentIdsUnder(movedDir);
|
|
18518
|
+
rewriteRefFields(movedDir, (ref, position) => position === "component" ? rebaseReference(ref, mount, direction, (id) => declared.has(id)) : ref);
|
|
18519
|
+
}
|
|
18520
|
+
function componentIdsUnder(dir) {
|
|
18521
|
+
const ids = /* @__PURE__ */ new Set();
|
|
18522
|
+
for (const file of listFilesRecursive(dir, ".yaml")) {
|
|
18523
|
+
let raw;
|
|
18524
|
+
try {
|
|
18525
|
+
raw = readYamlFile(file);
|
|
18526
|
+
} catch {
|
|
18527
|
+
continue;
|
|
18528
|
+
}
|
|
18529
|
+
if (raw && typeof raw === "object" && "componentType" in raw && typeof raw.id === "string") ids.add(raw.id);
|
|
18530
|
+
}
|
|
18531
|
+
return ids;
|
|
18532
|
+
}
|
|
18533
|
+
function rewriteRefFields(specsDir, remap, excludeDir) {
|
|
18534
|
+
const at = (ref, position) => typeof ref === "string" ? remap(ref, position) : ref;
|
|
18379
18535
|
for (const file of listFilesRecursive(specsDir, ".yaml")) {
|
|
18380
18536
|
if (excludeDir && isWithinDir(excludeDir, file)) continue;
|
|
18381
18537
|
let raw;
|
|
@@ -18387,14 +18543,14 @@ function rewriteRefsInDir(specsDir, renameMap, excludeDir) {
|
|
|
18387
18543
|
if (!raw || typeof raw !== "object") continue;
|
|
18388
18544
|
let changed = false;
|
|
18389
18545
|
if ("componentType" in raw && Array.isArray(raw.dependsOn)) {
|
|
18390
|
-
const next = raw.dependsOn.map((d) =>
|
|
18546
|
+
const next = raw.dependsOn.map((d) => at(d, "component"));
|
|
18391
18547
|
if (next.some((v, i) => v !== raw.dependsOn[i])) {
|
|
18392
18548
|
raw.dependsOn = next;
|
|
18393
18549
|
changed = true;
|
|
18394
18550
|
}
|
|
18395
18551
|
if (Array.isArray(raw.dispatch)) {
|
|
18396
18552
|
for (const b of raw.dispatch) {
|
|
18397
|
-
const nc =
|
|
18553
|
+
const nc = at(b.component, "component");
|
|
18398
18554
|
if (nc !== b.component) {
|
|
18399
18555
|
b.component = nc;
|
|
18400
18556
|
changed = true;
|
|
@@ -18403,7 +18559,7 @@ function rewriteRefsInDir(specsDir, renameMap, excludeDir) {
|
|
|
18403
18559
|
}
|
|
18404
18560
|
} else if ("parentSystem" in raw && Array.isArray(raw.lifecycle)) {
|
|
18405
18561
|
for (const le of raw.lifecycle) {
|
|
18406
|
-
const nc =
|
|
18562
|
+
const nc = at(le.component, "component");
|
|
18407
18563
|
if (nc !== le.component) {
|
|
18408
18564
|
le.component = nc;
|
|
18409
18565
|
changed = true;
|
|
@@ -18413,7 +18569,7 @@ function rewriteRefsInDir(specsDir, renameMap, excludeDir) {
|
|
|
18413
18569
|
for (const m of raw.methods) {
|
|
18414
18570
|
if (!Array.isArray(m.params)) continue;
|
|
18415
18571
|
for (const p of m.params) {
|
|
18416
|
-
const nt =
|
|
18572
|
+
const nt = at(p.type, "type");
|
|
18417
18573
|
if (nt !== p.type) {
|
|
18418
18574
|
p.type = nt;
|
|
18419
18575
|
changed = true;
|
|
@@ -18424,7 +18580,7 @@ function rewriteRefsInDir(specsDir, renameMap, excludeDir) {
|
|
|
18424
18580
|
for (const m of raw.methods) {
|
|
18425
18581
|
if (!Array.isArray(m.narrative)) continue;
|
|
18426
18582
|
for (const step of m.narrative) {
|
|
18427
|
-
const nt =
|
|
18583
|
+
const nt = at(step.targetComponent, "component");
|
|
18428
18584
|
if (nt !== step.targetComponent) {
|
|
18429
18585
|
step.targetComponent = nt;
|
|
18430
18586
|
changed = true;
|
|
@@ -18433,7 +18589,7 @@ function rewriteRefsInDir(specsDir, renameMap, excludeDir) {
|
|
|
18433
18589
|
}
|
|
18434
18590
|
} else if ("kind" in raw && Array.isArray(raw.fields)) {
|
|
18435
18591
|
for (const f of raw.fields) {
|
|
18436
|
-
const nt =
|
|
18592
|
+
const nt = at(f.type, "type");
|
|
18437
18593
|
if (nt !== f.type) {
|
|
18438
18594
|
f.type = nt;
|
|
18439
18595
|
changed = true;
|
|
@@ -22822,8 +22978,34 @@ __export(server_exports, {
|
|
|
22822
22978
|
captureBuildStamp: () => captureBuildStamp,
|
|
22823
22979
|
createMcpServer: () => createMcpServer,
|
|
22824
22980
|
isBuildStale: () => isBuildStale,
|
|
22825
|
-
startMcpServer: () => startMcpServer
|
|
22981
|
+
startMcpServer: () => startMcpServer,
|
|
22982
|
+
statusFamilyContext: () => statusFamilyContext
|
|
22826
22983
|
});
|
|
22984
|
+
function statusFamilyContext() {
|
|
22985
|
+
const lines = [];
|
|
22986
|
+
try {
|
|
22987
|
+
const parent = resolveChainingParent();
|
|
22988
|
+
if (parent) {
|
|
22989
|
+
let parentName;
|
|
22990
|
+
try {
|
|
22991
|
+
const system = readYamlFile(aiPathsAt(parent.parentRoot).specsSystem());
|
|
22992
|
+
if (typeof system?.name === "string") parentName = system.name;
|
|
22993
|
+
} catch {
|
|
22994
|
+
}
|
|
22995
|
+
lines.push(
|
|
22996
|
+
`Family: this project is a chained subproject, mounted as subsystem "${parent.subsystemId}" of ${parentName ? `the parent project "${parentName}"` : "its parent project"} \u2014 the surfaces it can consume are listed by sdd_list_external_interfaces.`
|
|
22997
|
+
);
|
|
22998
|
+
}
|
|
22999
|
+
const mounts = loadSubsystemSpecs().filter((s) => s.projectPath && !s.id.includes("::"));
|
|
23000
|
+
if (mounts.length > 0) {
|
|
23001
|
+
lines.push(`Family: chained subprojects mounted here \u2014 ${mounts.map((s) => `${s.id} (${s.projectPath})`).join(", ")}.`);
|
|
23002
|
+
}
|
|
23003
|
+
} catch {
|
|
23004
|
+
}
|
|
23005
|
+
return lines.length > 0 ? `${lines.join("\n")}
|
|
23006
|
+
|
|
23007
|
+
` : "";
|
|
23008
|
+
}
|
|
22827
23009
|
function requireLoader() {
|
|
22828
23010
|
return loader_exports;
|
|
22829
23011
|
}
|
|
@@ -24035,10 +24217,11 @@ NOTICE:
|
|
|
24035
24217
|
},
|
|
24036
24218
|
({ subsystem, recursive }) => {
|
|
24037
24219
|
try {
|
|
24038
|
-
|
|
24220
|
+
const report2 = getStatusReport({
|
|
24039
24221
|
subsystem,
|
|
24040
24222
|
recursive: recursive ?? true
|
|
24041
|
-
})
|
|
24223
|
+
});
|
|
24224
|
+
return text(`${statusFamilyContext()}${report2}`);
|
|
24042
24225
|
} catch (e) {
|
|
24043
24226
|
return errText(String(e));
|
|
24044
24227
|
}
|
|
@@ -24166,8 +24349,10 @@ NOTICE:
|
|
|
24166
24349
|
}
|
|
24167
24350
|
}, hostedStub);
|
|
24168
24351
|
reg(server, "sdd_host_export_tree", {
|
|
24169
|
-
description: "Hosted spec-tree transfer: pack the BOUND project's WHOLE spec tree \u2014 its own .wai plus every chained subproject \u2014 into a .waitree archive, returned as base64 with its roots, file count and state id. The migration counterpart of an import: use it to take a hosted project local, or to move it to another instance. Requires project:read over the project. Bounded by the data-plane body cap; a very large tree exports through the web download route instead.",
|
|
24170
|
-
inputSchema: {
|
|
24352
|
+
description: "Hosted spec-tree transfer: pack the BOUND project's WHOLE spec tree \u2014 its own .wai plus every chained subproject \u2014 into a .waitree archive, returned as base64 with its roots, file count and state id. The migration counterpart of an import: use it to take a hosted project local, or to move it to another instance. Requires project:read over the project. Bounded by the data-plane body cap; a very large tree exports through the web download route instead. Refuses when a chained mount cannot be packed (escaping, missing, cyclic, too deep, or holding no .wai), naming every one and why, unless allowPartial is set \u2014 then the archive is built anyway and the result lists what was skipped.",
|
|
24353
|
+
inputSchema: {
|
|
24354
|
+
allowPartial: import_zod10.z.boolean().optional().describe("Build the archive even when some chained mounts cannot be packed, listing them as skipped; default false (refuse and name them)")
|
|
24355
|
+
}
|
|
24171
24356
|
}, hostedStub);
|
|
24172
24357
|
reg(server, "sdd_host_import_tree", {
|
|
24173
24358
|
description: "Hosted spec-tree transfer: REPLACE the BOUND project's spec tree from a base64 .waitree archive. Requires project:admin over the project (strictly above project:write \u2014 this replaces the whole design, not one spec). Refuses an occupied destination unless replaceExisting is set, always refuses executable entries (rule/code packs install only through the trusted filesystem), and moves the previous tree aside to a backup whose path is returned.",
|
|
@@ -24230,6 +24415,7 @@ var init_server = __esm({
|
|
|
24230
24415
|
path30 = __toESM(require("path"));
|
|
24231
24416
|
import_url = require("url");
|
|
24232
24417
|
init_fs();
|
|
24418
|
+
init_yaml();
|
|
24233
24419
|
init_status();
|
|
24234
24420
|
init_defaults();
|
|
24235
24421
|
init_narrative_labels();
|
|
@@ -25359,18 +25545,29 @@ function discardStagingDir(stagingDir) {
|
|
|
25359
25545
|
} catch {
|
|
25360
25546
|
}
|
|
25361
25547
|
}
|
|
25362
|
-
function exportSpecTree(includeDerived) {
|
|
25548
|
+
function exportSpecTree(includeDerived, allowPartial) {
|
|
25363
25549
|
const system = loadSystemSpec();
|
|
25364
25550
|
if (!system) {
|
|
25365
25551
|
throw new Error("no spec tree to export at this project root");
|
|
25366
25552
|
}
|
|
25367
|
-
const
|
|
25553
|
+
const inspection = inspectChainedRoots();
|
|
25368
25554
|
const root = getProjectRoot();
|
|
25369
|
-
const
|
|
25370
|
-
|
|
25371
|
-
|
|
25372
|
-
|
|
25373
|
-
|
|
25555
|
+
const skipped = [...inspection.skipped];
|
|
25556
|
+
const roots = [{ relativePath: ".", waiDir: aiPathsAt(root).root() }];
|
|
25557
|
+
for (const rel2 of inspection.roots) {
|
|
25558
|
+
const waiDir = aiPathsAt(path28.resolve(root, rel2)).root();
|
|
25559
|
+
if (pathExists(waiDir)) {
|
|
25560
|
+
roots.push({ relativePath: rel2, waiDir });
|
|
25561
|
+
} else {
|
|
25562
|
+
skipped.push({ mount: rel2, projectPath: rel2, reason: "no-spec-tree" });
|
|
25563
|
+
}
|
|
25564
|
+
}
|
|
25565
|
+
if (skipped.length > 0 && !allowPartial) {
|
|
25566
|
+
const detail = skipped.map((s) => `${s.mount} (${s.reason})`).join(", ");
|
|
25567
|
+
throw new Error(
|
|
25568
|
+
`cannot export the whole spec tree \u2014 skipped: ${detail}. Pass allowPartial to export the rest anyway.`
|
|
25569
|
+
);
|
|
25570
|
+
}
|
|
25374
25571
|
const stateId = computeStateId();
|
|
25375
25572
|
const built = buildTreeArchive2(roots, system.name, stateId.digest, includeDerived);
|
|
25376
25573
|
const result = {
|
|
@@ -25379,7 +25576,8 @@ function exportSpecTree(includeDerived) {
|
|
|
25379
25576
|
projectName: built.manifest.projectName,
|
|
25380
25577
|
roots: built.manifest.roots,
|
|
25381
25578
|
fileCount: built.fileCount,
|
|
25382
|
-
stateId: stateId.digest
|
|
25579
|
+
stateId: stateId.digest,
|
|
25580
|
+
skipped
|
|
25383
25581
|
};
|
|
25384
25582
|
return result;
|
|
25385
25583
|
}
|
|
@@ -30159,7 +30357,19 @@ function excludeLocalFiles() {
|
|
|
30159
30357
|
const excludePath = path48.join(getProjectRoot(), ".git", "info", "exclude");
|
|
30160
30358
|
try {
|
|
30161
30359
|
fs36.mkdirSync(path48.dirname(excludePath), { recursive: true });
|
|
30162
|
-
fs36.
|
|
30360
|
+
const existing = fs36.existsSync(excludePath) ? fs36.readFileSync(excludePath, "utf8") : "";
|
|
30361
|
+
const lines = existing.split(/\r?\n/).filter((line2) => line2.length > 0);
|
|
30362
|
+
const hadLegacyLockExclusion = lines.some((line2) => line2.trim() === ".wai/lock.json");
|
|
30363
|
+
const kept = lines.filter((line2) => line2.trim() !== ".wai/lock.json");
|
|
30364
|
+
if (!kept.some((line2) => line2.trim() === ".wai/git.json")) {
|
|
30365
|
+
kept.push(".wai/git.json");
|
|
30366
|
+
}
|
|
30367
|
+
fs36.writeFileSync(excludePath, kept.join("\n") + "\n");
|
|
30368
|
+
if (hadLegacyLockExclusion) {
|
|
30369
|
+
console.warn(
|
|
30370
|
+
"[wairon git] removed a .wai/lock.json exclusion left by an earlier version \u2014 the approval it records is committed with the rest of .wai/ now, so it reaches the remote."
|
|
30371
|
+
);
|
|
30372
|
+
}
|
|
30163
30373
|
} catch {
|
|
30164
30374
|
}
|
|
30165
30375
|
}
|
|
@@ -31131,13 +31341,13 @@ function listSecrets(_cfg, credential) {
|
|
|
31131
31341
|
requireAdmin(credential);
|
|
31132
31342
|
return listSecretKeys();
|
|
31133
31343
|
}
|
|
31134
|
-
function exportProjectTree(cfg, credential, project2, subproject, includeDerived) {
|
|
31344
|
+
function exportProjectTree(cfg, credential, project2, subproject, includeDerived, allowPartial) {
|
|
31135
31345
|
const principal = requirePrincipal(cfg, credential);
|
|
31136
31346
|
if (authorize(cfg.dataDir, principal, "project:read", "project", project2).value !== "yes") {
|
|
31137
31347
|
throw new AdminAuthError("Forbidden \u2014 exporting a project's spec tree requires project:read over it");
|
|
31138
31348
|
}
|
|
31139
31349
|
const root = boundLifecycleRoot(cfg, project2, subproject);
|
|
31140
|
-
return runWithProjectRoot(root, () => hostCore.exportSpecTree(includeDerived));
|
|
31350
|
+
return runWithProjectRoot(root, () => hostCore.exportSpecTree(includeDerived, allowPartial));
|
|
31141
31351
|
}
|
|
31142
31352
|
function importProjectTree(cfg, credential, project2, archive, subproject, replaceExisting) {
|
|
31143
31353
|
const principal = requirePrincipal(cfg, credential);
|
|
@@ -35842,8 +36052,8 @@ function runPeriodicBackingSync(cfg) {
|
|
|
35842
36052
|
}
|
|
35843
36053
|
|
|
35844
36054
|
// src/server/projectops.ts
|
|
35845
|
-
function exportProjectTree2(cfg, credential, project2, subproject, includeDerived) {
|
|
35846
|
-
return exportProjectTree(cfg, credential, project2, subproject, includeDerived);
|
|
36055
|
+
function exportProjectTree2(cfg, credential, project2, subproject, includeDerived, allowPartial) {
|
|
36056
|
+
return exportProjectTree(cfg, credential, project2, subproject, includeDerived, allowPartial);
|
|
35847
36057
|
}
|
|
35848
36058
|
function importProjectTree2(cfg, credential, project2, archive, subproject, replaceExisting) {
|
|
35849
36059
|
return importProjectTree(cfg, credential, project2, archive, subproject, replaceExisting);
|
|
@@ -39233,7 +39443,8 @@ function opsExportProjectTree(cfg, sessionId, url, res) {
|
|
|
39233
39443
|
sessionId,
|
|
39234
39444
|
q(url, "projectId") ?? "",
|
|
39235
39445
|
void 0,
|
|
39236
|
-
q(url, "includeDerived") === "1"
|
|
39446
|
+
q(url, "includeDerived") === "1",
|
|
39447
|
+
q(url, "allowPartial") === "1"
|
|
39237
39448
|
);
|
|
39238
39449
|
res.writeHead(200, {
|
|
39239
39450
|
"content-type": "application/zip",
|
|
@@ -39999,6 +40210,18 @@ function requiredDataPlaneCapability(toolName) {
|
|
|
39999
40210
|
if (WRITE_TOOL_PREFIXES.some((p) => toolName.startsWith(p))) return "project:write";
|
|
40000
40211
|
return "project:write";
|
|
40001
40212
|
}
|
|
40213
|
+
var TREE_SCOPED_HOST_TOOLS = /* @__PURE__ */ new Set([
|
|
40214
|
+
"sdd_host_lock_project",
|
|
40215
|
+
...TREE_TRANSFER_TOOLS
|
|
40216
|
+
]);
|
|
40217
|
+
function toolScope(toolName) {
|
|
40218
|
+
if (PROJECT_RECORD_TOOLS.has(toolName)) return "record";
|
|
40219
|
+
if (TREE_SCOPED_HOST_TOOLS.has(toolName)) return "tree";
|
|
40220
|
+
if (READ_TOOL_NAMES.has(toolName) || READ_TOOL_PREFIXES.some((p) => toolName.startsWith(p)) || WRITE_TOOL_PREFIXES.some((p) => toolName.startsWith(p))) {
|
|
40221
|
+
return "tree";
|
|
40222
|
+
}
|
|
40223
|
+
return void 0;
|
|
40224
|
+
}
|
|
40002
40225
|
var MUTATING_HOST_TOOLS = /* @__PURE__ */ new Set([
|
|
40003
40226
|
"sdd_host_initialize_project",
|
|
40004
40227
|
"sdd_host_lock_project",
|
|
@@ -40039,7 +40262,11 @@ function subprojectConfinementError(projectId, subproject, body) {
|
|
|
40039
40262
|
const msg = jsonRpcRequest(body);
|
|
40040
40263
|
if (!msg || msg.method !== "tools/call") return void 0;
|
|
40041
40264
|
const name = msg.params?.name;
|
|
40042
|
-
if (typeof name !== "string"
|
|
40265
|
+
if (typeof name !== "string") return void 0;
|
|
40266
|
+
const scope = toolScope(name);
|
|
40267
|
+
if (scope === "tree") return void 0;
|
|
40268
|
+
const bound = `${projectId}${SUBPROJECT_SEPARATOR}${subproject}`;
|
|
40269
|
+
const why = scope === "record" ? `${name} acts on the whole project "${projectId}", but this credential is bound to subproject "${bound}". ` : `${name} declares no scope, and this credential is bound to subproject "${bound}", which serves only tools that act on the bound tree. `;
|
|
40043
40270
|
return {
|
|
40044
40271
|
jsonrpc: "2.0",
|
|
40045
40272
|
id: msg.id ?? null,
|
|
@@ -40047,7 +40274,7 @@ function subprojectConfinementError(projectId, subproject, body) {
|
|
|
40047
40274
|
content: [
|
|
40048
40275
|
{
|
|
40049
40276
|
type: "text",
|
|
40050
|
-
text: `Refused \u2014 ${
|
|
40277
|
+
text: `Refused \u2014 ${why}An unqualified credential for "${projectId}" is required to call ${name}.`
|
|
40051
40278
|
}
|
|
40052
40279
|
],
|
|
40053
40280
|
isError: true
|
|
@@ -40132,14 +40359,22 @@ async function dispatchProjectLifecycleTool(cfg, credential, projectId, body, su
|
|
|
40132
40359
|
);
|
|
40133
40360
|
break;
|
|
40134
40361
|
case "sdd_host_export_tree": {
|
|
40135
|
-
const exported = exportProjectTree2(
|
|
40362
|
+
const exported = exportProjectTree2(
|
|
40363
|
+
cfg,
|
|
40364
|
+
credential,
|
|
40365
|
+
projectId,
|
|
40366
|
+
subproject,
|
|
40367
|
+
void 0,
|
|
40368
|
+
args.allowPartial === true
|
|
40369
|
+
);
|
|
40136
40370
|
value = {
|
|
40137
40371
|
projectName: exported.projectName,
|
|
40138
40372
|
roots: exported.roots,
|
|
40139
40373
|
fileCount: exported.fileCount,
|
|
40140
40374
|
stateId: exported.stateId,
|
|
40141
40375
|
suggestedFileName: exported.suggestedFileName,
|
|
40142
|
-
archiveBase64: Buffer.from(exported.archive).toString("base64")
|
|
40376
|
+
archiveBase64: Buffer.from(exported.archive).toString("base64"),
|
|
40377
|
+
skipped: exported.skipped
|
|
40143
40378
|
};
|
|
40144
40379
|
break;
|
|
40145
40380
|
}
|
|
@@ -40727,7 +40962,8 @@ async function routeAdmin(cfg, req, res) {
|
|
|
40727
40962
|
return sendJson(res, 200, { ok: true });
|
|
40728
40963
|
}
|
|
40729
40964
|
if (req.method === "GET" && parts.length === 4 && parts[3] === "tree") {
|
|
40730
|
-
const
|
|
40965
|
+
const allowPartial = url.searchParams.get("allowPartial") === "1";
|
|
40966
|
+
const exported = exportProjectTree(cfg, cred, parts[2], void 0, void 0, allowPartial);
|
|
40731
40967
|
res.writeHead(200, {
|
|
40732
40968
|
"content-type": "application/zip",
|
|
40733
40969
|
"content-disposition": `attachment; filename="${exported.suggestedFileName}"`
|
|
@@ -42179,8 +42415,8 @@ init_provision();
|
|
|
42179
42415
|
function composeAgentBrief3(agentId) {
|
|
42180
42416
|
return composeAgentBrief(agentId);
|
|
42181
42417
|
}
|
|
42182
|
-
function exportSpecTree2(includeDerived) {
|
|
42183
|
-
return exportSpecTree(includeDerived);
|
|
42418
|
+
function exportSpecTree2(includeDerived, allowPartial) {
|
|
42419
|
+
return exportSpecTree(includeDerived, allowPartial);
|
|
42184
42420
|
}
|
|
42185
42421
|
function importSpecTree2(archive, options) {
|
|
42186
42422
|
return importSpecTree(archive, options);
|
|
@@ -42392,8 +42628,8 @@ async function callTool(target, name, args) {
|
|
|
42392
42628
|
return raw;
|
|
42393
42629
|
}
|
|
42394
42630
|
}
|
|
42395
|
-
async function exportRemoteTree(target) {
|
|
42396
|
-
const payload = await callTool(target, "sdd_host_export_tree", {});
|
|
42631
|
+
async function exportRemoteTree(target, allowPartial) {
|
|
42632
|
+
const payload = await callTool(target, "sdd_host_export_tree", { allowPartial: allowPartial === true });
|
|
42397
42633
|
if (!payload || typeof payload.archiveBase64 !== "string" || !payload.archiveBase64) {
|
|
42398
42634
|
throw new WaironError(`The hosted export returned no archive for project "${target.projectId}".`);
|
|
42399
42635
|
}
|
|
@@ -42402,7 +42638,8 @@ async function exportRemoteTree(target) {
|
|
|
42402
42638
|
suggestedFileName: payload.suggestedFileName ?? `${target.projectId}.waitree`,
|
|
42403
42639
|
projectName: payload.projectName ?? target.projectId,
|
|
42404
42640
|
roots: payload.roots ?? ["."],
|
|
42405
|
-
fileCount: payload.fileCount ?? 0
|
|
42641
|
+
fileCount: payload.fileCount ?? 0,
|
|
42642
|
+
skipped: payload.skipped ?? []
|
|
42406
42643
|
};
|
|
42407
42644
|
if (payload.stateId) result.stateId = payload.stateId;
|
|
42408
42645
|
return result;
|
|
@@ -42462,7 +42699,7 @@ async function initializeRemoteProject(target, ownerUnitId) {
|
|
|
42462
42699
|
return record2.summary ?? `created project "${record2.id ?? topProjectId(target.projectId)}"`;
|
|
42463
42700
|
}
|
|
42464
42701
|
async function pushTree(target, options = {}) {
|
|
42465
|
-
const exported = exportSpecTree2(options.includeDerived);
|
|
42702
|
+
const exported = exportSpecTree2(options.includeDerived, options.allowPartial);
|
|
42466
42703
|
const archivePath = writeArchiveIfAsked(exported.archive, options.archivePath, exported.suggestedFileName);
|
|
42467
42704
|
let createdProject = false;
|
|
42468
42705
|
if (options.createUnitId) {
|
|
@@ -42482,10 +42719,11 @@ async function pushTree(target, options = {}) {
|
|
|
42482
42719
|
};
|
|
42483
42720
|
if (imported.backupPath) result.backupPath = imported.backupPath;
|
|
42484
42721
|
if (archivePath) result.archivePath = archivePath;
|
|
42722
|
+
if (exported.skipped.length > 0) result.skipped = exported.skipped;
|
|
42485
42723
|
return result;
|
|
42486
42724
|
}
|
|
42487
42725
|
async function pullTree(target, options = {}) {
|
|
42488
|
-
const exported = await exportRemoteTree(target);
|
|
42726
|
+
const exported = await exportRemoteTree(target, options.allowPartial);
|
|
42489
42727
|
const archivePath = writeArchiveIfAsked(exported.archive, options.archivePath, exported.suggestedFileName);
|
|
42490
42728
|
const imported = importSpecTree2(exported.archive, {
|
|
42491
42729
|
replaceExisting: options.replaceExisting === true,
|
|
@@ -42502,6 +42740,7 @@ async function pullTree(target, options = {}) {
|
|
|
42502
42740
|
};
|
|
42503
42741
|
if (imported.backupPath) result.backupPath = imported.backupPath;
|
|
42504
42742
|
if (archivePath) result.archivePath = archivePath;
|
|
42743
|
+
if (exported.skipped.length > 0) result.skipped = exported.skipped;
|
|
42505
42744
|
return result;
|
|
42506
42745
|
}
|
|
42507
42746
|
function resolveTarget(root, overrides = {}) {
|
|
@@ -42601,7 +42840,8 @@ async function runRemote(action, options = {}) {
|
|
|
42601
42840
|
assertProjectInitialized();
|
|
42602
42841
|
const transfer = {
|
|
42603
42842
|
replaceExisting: options.force === true,
|
|
42604
|
-
includeDerived: options.includeDerived === true
|
|
42843
|
+
includeDerived: options.includeDerived === true,
|
|
42844
|
+
allowPartial: options.allowPartial === true
|
|
42605
42845
|
};
|
|
42606
42846
|
if (options.unit) transfer.createUnitId = options.unit;
|
|
42607
42847
|
if (options.archive) transfer.archivePath = options.archive;
|
|
@@ -42609,7 +42849,10 @@ async function runRemote(action, options = {}) {
|
|
|
42609
42849
|
return;
|
|
42610
42850
|
}
|
|
42611
42851
|
case "pull": {
|
|
42612
|
-
const transfer = {
|
|
42852
|
+
const transfer = {
|
|
42853
|
+
replaceExisting: options.force === true,
|
|
42854
|
+
allowPartial: options.allowPartial === true
|
|
42855
|
+
};
|
|
42613
42856
|
if (options.archive) transfer.archivePath = options.archive;
|
|
42614
42857
|
if (options.dir) transfer.destDir = path68.resolve(options.dir);
|
|
42615
42858
|
report(await pullTree(target, transfer));
|
|
@@ -42718,6 +42961,11 @@ function report(outcome) {
|
|
|
42718
42961
|
if (outcome.backupPath) {
|
|
42719
42962
|
logger.info(`The replaced tree was backed up to ${import_chalk20.default.cyan(outcome.backupPath)} \u2014 restore it by moving it back.`);
|
|
42720
42963
|
}
|
|
42964
|
+
if (outcome.skipped && outcome.skipped.length > 0) {
|
|
42965
|
+
logger.warn(
|
|
42966
|
+
`Skipped ${outcome.skipped.length} mount(s) \u2014 allowPartial let this through: ` + outcome.skipped.map((s) => `${s.mount} (${s.reason})`).join(", ")
|
|
42967
|
+
);
|
|
42968
|
+
}
|
|
42721
42969
|
}
|
|
42722
42970
|
|
|
42723
42971
|
// src/cli/index.ts
|
|
@@ -43172,7 +43420,10 @@ program.command("login <url>").description("store a bearer credential for a host
|
|
|
43172
43420
|
program.command("logout [url]").description("forget a stored credential for a hosted instance (local only \u2014 does NOT revoke it), or list what is stored").action(async (url) => {
|
|
43173
43421
|
await runLogout(url, {});
|
|
43174
43422
|
});
|
|
43175
|
-
program.command("remote <action>").description("hosted instance: push | pull (migrate a spec tree) \xB7 attach | detach | status (bind this checkout)").option("--url <url>", "hosted instance base URL (else WAIRON_REMOTE_URL)").option("--project <id>", "hosted project id, optionally qualified as project::subsystem (else WAIRON_REMOTE_PROJECT)").option("--token <token>", "bearer credential (else WAIRON_REMOTE_TOKEN) \u2014 mint one in the hosted UI under Tokens").option("--unit <id>", "push: create the destination project first, owned by this organization unit").option("--force", "replace an authored spec tree at the destination (it is backed up first)").option("--include-derived", "push: pack regenerable artifacts (diagrams, generated topology) too").option(
|
|
43423
|
+
program.command("remote <action>").description("hosted instance: push | pull (migrate a spec tree) \xB7 attach | detach | status (bind this checkout)").option("--url <url>", "hosted instance base URL (else WAIRON_REMOTE_URL)").option("--project <id>", "hosted project id, optionally qualified as project::subsystem (else WAIRON_REMOTE_PROJECT)").option("--token <token>", "bearer credential (else WAIRON_REMOTE_TOKEN) \u2014 mint one in the hosted UI under Tokens").option("--unit <id>", "push: create the destination project first, owned by this organization unit").option("--force", "replace an authored spec tree at the destination (it is backed up first)").option("--include-derived", "push: pack regenerable artifacts (diagrams, generated topology) too").option(
|
|
43424
|
+
"--allow-partial",
|
|
43425
|
+
"build the archive even when some chained mounts cannot be packed, listing them as skipped (default: refuse)"
|
|
43426
|
+
).option("--archive <path>", "also write the transferred archive to this path (file or directory)").option("--dir <path>", "pull: the local project root to import into (default: this project)").action(async (action, opts) => {
|
|
43176
43427
|
await runRemote(action, {
|
|
43177
43428
|
url: opts.url,
|
|
43178
43429
|
project: opts.project,
|
|
@@ -43180,6 +43431,7 @@ program.command("remote <action>").description("hosted instance: push | pull (mi
|
|
|
43180
43431
|
unit: opts.unit,
|
|
43181
43432
|
force: opts.force,
|
|
43182
43433
|
includeDerived: opts.includeDerived,
|
|
43434
|
+
allowPartial: opts.allowPartial,
|
|
43183
43435
|
archive: opts.archive,
|
|
43184
43436
|
dir: opts.dir
|
|
43185
43437
|
});
|