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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -3060,7 +3060,7 @@ var require_dist = __commonJS({
3060
3060
  function slug(name) {
3061
3061
  return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
3062
3062
  }
3063
- var fs24 = __toESM2(require("fs"));
3063
+ var fs25 = __toESM2(require("fs"));
3064
3064
  var path46 = __toESM2(require("path"));
3065
3065
  var import_fflate = require_node();
3066
3066
  var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".hg", ".svn"]);
@@ -3100,8 +3100,8 @@ var require_dist = __commonJS({
3100
3100
  function writeTree(destDir, files) {
3101
3101
  for (const file of files) {
3102
3102
  const absolute = path46.join(destDir, file.path);
3103
- fs24.mkdirSync(path46.dirname(absolute), { recursive: true });
3104
- fs24.writeFileSync(absolute, file.contents);
3103
+ fs25.mkdirSync(path46.dirname(absolute), { recursive: true });
3104
+ fs25.writeFileSync(absolute, file.contents);
3105
3105
  }
3106
3106
  }
3107
3107
  function classifyKind(name, symlinks) {
@@ -3110,14 +3110,14 @@ var require_dist = __commonJS({
3110
3110
  return "file";
3111
3111
  }
3112
3112
  function walkPackDir(root, current2, out) {
3113
- for (const entry of fs24.readdirSync(current2, { withFileTypes: true })) {
3113
+ for (const entry of fs25.readdirSync(current2, { withFileTypes: true })) {
3114
3114
  if (entry.isDirectory()) {
3115
3115
  if (SKIP_DIRS.has(entry.name)) continue;
3116
3116
  walkPackDir(root, path46.join(current2, entry.name), out);
3117
3117
  } else if (entry.isFile()) {
3118
3118
  const absolute = path46.join(current2, entry.name);
3119
3119
  const relative22 = path46.relative(root, absolute).split(path46.sep).join("/");
3120
- out.push({ path: relative22, contents: fs24.readFileSync(absolute) });
3120
+ out.push({ path: relative22, contents: fs25.readFileSync(absolute) });
3121
3121
  }
3122
3122
  }
3123
3123
  }
@@ -3604,6 +3604,7 @@ __export(src_exports, {
3604
3604
  generateDiagramSet: () => generateDiagramSet,
3605
3605
  generateSequenceDiagram: () => generateSequenceDiagram,
3606
3606
  getExporter: () => getExporter,
3607
+ getHostedLookup: () => getHostedLookup,
3607
3608
  getLoaderIssues: () => getLoaderIssues,
3608
3609
  getProjectRoot: () => getProjectRoot,
3609
3610
  getProjectRootOverride: () => getProjectRootOverride,
@@ -3735,6 +3736,7 @@ __export(src_exports, {
3735
3736
  resolveVariantGuidance: () => resolveVariantGuidance,
3736
3737
  retireSpecialists: () => retireSpecialists,
3737
3738
  rewriteReferences: () => rewriteReferences2,
3739
+ runWithHostedLookup: () => runWithHostedLookup,
3738
3740
  runWithProjectBinding: () => runWithProjectBinding,
3739
3741
  runWithProjectRoot: () => runWithProjectRoot,
3740
3742
  sameContract: () => sameContract,
@@ -3844,18 +3846,33 @@ function runWithProjectBinding(dir, reach2, fn) {
3844
3846
  root: path.resolve(dir),
3845
3847
  topRoot: path.resolve(reach2.topRoot),
3846
3848
  parentReach: reach2.parentReach,
3847
- ...reach2.narrowed ? { narrowed: true } : {}
3849
+ ...reach2.narrowed ? { narrowed: true } : {},
3850
+ ...reach2.unwritableRoots ? { unwritableRoots: reach2.unwritableRoots.map((r) => path.resolve(r)) } : {},
3851
+ ...reach2.hostedLookup ? { hostedLookup: reach2.hostedLookup } : {}
3848
3852
  },
3849
3853
  fn
3850
3854
  );
3851
3855
  }
3856
+ function getHostedLookup() {
3857
+ return requestRootStore.getStore()?.hostedLookup ?? null;
3858
+ }
3859
+ function runWithHostedLookup(lookup2, fn) {
3860
+ const scope = requestRootStore.getStore();
3861
+ if (!scope?.hostedLookup) return fn();
3862
+ return requestRootStore.run({ ...scope, hostedLookup: lookup2 }, fn);
3863
+ }
3852
3864
  function getRequestProjectRoot() {
3853
3865
  return requestRootStore.getStore()?.root ?? null;
3854
3866
  }
3855
3867
  function getRequestParentReach() {
3856
3868
  const scope = requestRootStore.getStore();
3857
3869
  if (!scope || scope.parentReach === void 0) return null;
3858
- return { topRoot: scope.topRoot, parentReach: scope.parentReach, ...scope.narrowed ? { narrowed: true } : {} };
3870
+ return {
3871
+ topRoot: scope.topRoot,
3872
+ parentReach: scope.parentReach,
3873
+ ...scope.narrowed ? { narrowed: true } : {},
3874
+ ...scope.unwritableRoots ? { unwritableRoots: scope.unwritableRoots } : {}
3875
+ };
3859
3876
  }
3860
3877
  function setProjectRoot(dir) {
3861
3878
  projectRootOverride = dir === null ? null : path.resolve(dir);
@@ -4571,7 +4588,8 @@ var ProjectProfileSelectionSchema = import_zod3.z.object({
4571
4588
  selectedAt: import_zod3.z.string()
4572
4589
  });
4573
4590
  var ExternalSourceSchema = import_zod3.z.object({
4574
- path: import_zod3.z.string()
4591
+ path: import_zod3.z.string().optional(),
4592
+ hosted: import_zod3.z.string().optional()
4575
4593
  });
4576
4594
  var ExternalDeclarationSchema = import_zod3.z.object({
4577
4595
  /** The producer's project id; defaults to the alias. Plain string: a malformed id is REPORTED, never unreadable. */
@@ -4942,6 +4960,12 @@ function projectIdentity(config, lockedProjectId) {
4942
4960
  };
4943
4961
  }
4944
4962
  var EXTERNAL_ALIAS_RE = /^[a-z0-9_-]+$/;
4963
+ function sourceProblem(source) {
4964
+ if (source === void 0) return void 0;
4965
+ const named2 = (source.path !== void 0 ? 1 : 0) + (source.hosted !== void 0 ? 1 : 0);
4966
+ if (named2 === 1) return void 0;
4967
+ return named2 === 0 ? "its `source` names neither `path` nor `hosted` \u2014 give exactly one" : "its `source` names both `path` and `hosted` \u2014 give exactly one";
4968
+ }
4945
4969
  var USE_ENTRY_RE = /^(\*|[a-z0-9_-]+)$/;
4946
4970
  function readUse(alias, use) {
4947
4971
  const list = Array.isArray(use) ? use.map((u) => String(u)) : [];
@@ -4956,11 +4980,12 @@ function declaredExternals(config) {
4956
4980
  return Object.entries(config.externals ?? {}).map(([alias, declaration2]) => {
4957
4981
  const project = declaration2?.project ?? alias;
4958
4982
  const imports = readUse(alias, declaration2?.use);
4959
- const problem = !EXTERNAL_ALIAS_RE.test(alias) ? PROJECT_ID_RE.test(alias) ? `the alias "${alias}" is not a reference name ([a-z0-9-_]+) \u2014 a dotted producer id needs an explicit alias, e.g. \`${alias.replace(/\./g, "-")}: { project: ${alias} }\`` : `the alias "${alias}" breaks [a-z0-9-_]+` : config.members?.[alias] !== void 0 ? `the alias "${alias}" is also declared under \`members\` \u2014 one alias names one project` : !PROJECT_ID_RE.test(project) ? `the producer id "${project}" breaks the project-id grammar` : imports.problem;
4983
+ const problem = !EXTERNAL_ALIAS_RE.test(alias) ? PROJECT_ID_RE.test(alias) ? `the alias "${alias}" is not a reference name ([a-z0-9-_]+) \u2014 a dotted producer id needs an explicit alias, e.g. \`${alias.replace(/\./g, "-")}: { project: ${alias} }\`` : `the alias "${alias}" breaks [a-z0-9-_]+` : config.members?.[alias] !== void 0 ? `the alias "${alias}" is also declared under \`members\` \u2014 one alias names one project` : !PROJECT_ID_RE.test(project) ? `the producer id "${project}" breaks the project-id grammar` : sourceProblem(declaration2?.source) ?? imports.problem;
4960
4984
  return {
4961
4985
  alias,
4962
4986
  project,
4963
4987
  ...declaration2?.source?.path !== void 0 ? { sourcePath: declaration2.source.path } : {},
4988
+ ...declaration2?.source?.hosted !== void 0 ? { sourceHosted: declaration2.source.hosted } : {},
4964
4989
  ...problem ? { problem } : {},
4965
4990
  use: imports.use
4966
4991
  };
@@ -7556,10 +7581,10 @@ function stepGraph(method) {
7556
7581
  const s = byNum.get(n);
7557
7582
  if (s.type !== "parallel" || s.endStep === void 0 || !s.branches?.length) continue;
7558
7583
  const entries = s.branches.map((b) => b.step).sort((a, b) => a - b);
7559
- const join27 = fallNext(s.endStep);
7584
+ const join28 = fallNext(s.endStep);
7560
7585
  for (let i = 0; i < entries.length; i++) {
7561
7586
  const armEnd = i + 1 < entries.length ? prevOf(entries[i + 1]) : s.endStep;
7562
- if (armEnd !== void 0 && armEnd >= entries[i]) armEndJoin.set(armEnd, join27);
7587
+ if (armEnd !== void 0 && armEnd >= entries[i]) armEndJoin.set(armEnd, join28);
7563
7588
  }
7564
7589
  }
7565
7590
  const successorsOf = (n) => {
@@ -13418,10 +13443,11 @@ function isWithinDir(dir, target) {
13418
13443
  const rel2 = path10.relative(path10.resolve(dir), path10.resolve(target));
13419
13444
  return rel2 === "" || !rel2.startsWith("..") && !path10.isAbsolute(rel2);
13420
13445
  }
13421
- function producerUnreadable(directory) {
13446
+ function producerUnreadable(external) {
13447
+ const directory = external.directory;
13422
13448
  if (!directory) return "the producer has no root directory";
13423
13449
  const reach2 = getRequestParentReach();
13424
- if (reach2) {
13450
+ if (reach2 && external.sourceKind !== "hosted") {
13425
13451
  const bound2 = getProjectRoot();
13426
13452
  const ceiling = reach2.parentReach ? reach2.topRoot ?? bound2 : bound2;
13427
13453
  if (!isWithinDir(ceiling, directory)) return OUT_OF_REACH;
@@ -13450,7 +13476,7 @@ function usedDigests(usage, snapshot) {
13450
13476
  function pinBinding(binding, lock) {
13451
13477
  const { external } = binding;
13452
13478
  const unexported = binding.usage?.unexported ?? [];
13453
- const unreadable = producerUnreadable(external.directory);
13479
+ const unreadable = producerUnreadable(external);
13454
13480
  let snapshot;
13455
13481
  try {
13456
13482
  if (unreadable) throw new Error(unreadable);
@@ -13553,7 +13579,7 @@ function externalStatus(binding, lock) {
13553
13579
  const entry = lock?.externals[external.alias];
13554
13580
  const pinned = entry !== void 0 && externalsRepository.readSnapshot(external.alias) !== null;
13555
13581
  const base = { alias: external.alias, project: external.project, sourceKind: external.sourceKind, pinned };
13556
- const unreadable = external.sourceKind === "unresolved" ? `the external does not resolve: ${external.problem}` : producerUnreadable(external.directory);
13582
+ const unreadable = external.sourceKind === "unresolved" ? hostedOnly(external) ? external.problem : `the external does not resolve: ${external.problem}` : producerUnreadable(external);
13557
13583
  let live = null;
13558
13584
  let reason = unreadable;
13559
13585
  if (!reason) {
@@ -13564,7 +13590,7 @@ function externalStatus(binding, lock) {
13564
13590
  }
13565
13591
  }
13566
13592
  if (!live) {
13567
- const outOfReach = reason === OUT_OF_REACH || !binding.reachable && external.sourceKind !== "family";
13593
+ const outOfReach = reason === OUT_OF_REACH || hostedOutOfReach(external) || !binding.reachable && external.sourceKind !== "family";
13568
13594
  return {
13569
13595
  ...base,
13570
13596
  reachable: false,
@@ -13575,7 +13601,7 @@ function externalStatus(binding, lock) {
13575
13601
  };
13576
13602
  }
13577
13603
  const drifted = entry !== void 0 ? contentDigest(live) !== entry.digest : void 0;
13578
- if (external.sourceKind === "path") {
13604
+ if (external.sourceKind === "path" || external.sourceKind === "hosted") {
13579
13605
  return { ...base, reachable: true, stale: false, ...drifted !== void 0 ? { drifted } : {}, uses: [{ state: "unavailable", code: CHECK_UNAVAILABLE, detail: OUTSIDE_FAMILY }], detail: OUTSIDE_FAMILY };
13580
13606
  }
13581
13607
  const uses = compareUses(entry, binding.usage, live);
@@ -13587,6 +13613,12 @@ function externalStatus(binding, lock) {
13587
13613
  uses
13588
13614
  };
13589
13615
  }
13616
+ function hostedOnly(external) {
13617
+ return external.hosted !== void 0 && external.sourceKind === "unresolved" && getHostedLookup() === null;
13618
+ }
13619
+ function hostedOutOfReach(external) {
13620
+ return external.hosted !== void 0 && external.sourceKind === "unresolved" && getHostedLookup() !== null;
13621
+ }
13590
13622
  function getExternalsStatus() {
13591
13623
  const bindings = resolveDeclared();
13592
13624
  const lock = externalsRepository.readLock();
@@ -14250,7 +14282,7 @@ function defaultTargetConfig(type) {
14250
14282
  enabled: true
14251
14283
  };
14252
14284
  }
14253
- var WAIRON_VERSION = "5.1.1-dev.87";
14285
+ var WAIRON_VERSION = "5.1.1-dev.88";
14254
14286
  var GITHUB_REPO = "SYW-Apps/Waffle-AIron";
14255
14287
  var ARCHITECT_AGENT_ID = "agent-architect";
14256
14288
  var ARCHITECT_TEMPLATE_ID = "architect";
@@ -15047,13 +15079,13 @@ function buildCodeIndex(model) {
15047
15079
  const to = resolveImport(scope, specifier, paths);
15048
15080
  return to ? republishedFrom(to) : NO_ORIGIN;
15049
15081
  };
15050
- const scopeOf = (site, from) => {
15082
+ const scopeOf2 = (site, from) => {
15051
15083
  const path46 = pathKey(site.from ?? from);
15052
15084
  const f = facts.get(path46);
15053
15085
  return f?.status === "analyzed" && f.analysisGrade === "exact" ? { path: path46, facts: f } : void 0;
15054
15086
  };
15055
15087
  const originOf2 = (site, from) => {
15056
- const resolved = scopeOf(site, from);
15088
+ const resolved = scopeOf2(site, from);
15057
15089
  if (!resolved) return NO_ORIGIN;
15058
15090
  const { path: scope, facts: f } = resolved;
15059
15091
  const landing = (specifier) => landingFrom(scope, specifier);
@@ -15075,7 +15107,7 @@ function buildCodeIndex(model) {
15075
15107
  const possibleOriginsOf = (site, from) => {
15076
15108
  const proven = originOf2(site, from);
15077
15109
  if (!site.field && !site.constructed && !site.via) return proven;
15078
- const resolved = scopeOf(site, from);
15110
+ const resolved = scopeOf2(site, from);
15079
15111
  if (!resolved) return proven;
15080
15112
  const { path: scope, facts: f } = resolved;
15081
15113
  const out = new Set(proven);
@@ -21455,7 +21487,7 @@ var technologyRule = {
21455
21487
  for (const tech of impl.technologies) declarations.push({ comp, tech });
21456
21488
  }
21457
21489
  const scopes = /* @__PURE__ */ new Map();
21458
- const scopeOf = (comp) => {
21490
+ const scopeOf2 = (comp) => {
21459
21491
  const cached = scopes.get(comp.id);
21460
21492
  if (cached) return cached;
21461
21493
  const compSet = ownsClosure(ownershipRoot(comp.id));
@@ -21482,7 +21514,7 @@ var technologyRule = {
21482
21514
  const key = tech.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean).join("");
21483
21515
  const home = homes.get(key) ?? { label: tech, match: match2, ownerComponents: /* @__PURE__ */ new Set(), scope: /* @__PURE__ */ new Set() };
21484
21516
  home.ownerComponents.add(comp.id);
21485
- for (const id of scopeOf(comp)) home.scope.add(id);
21517
+ for (const id of scopeOf2(comp)) home.scope.add(id);
21486
21518
  homes.set(key, home);
21487
21519
  }
21488
21520
  if (homes.size === 0) return;
@@ -25167,6 +25199,7 @@ function bindExternals(node, root, nodes) {
25167
25199
  return declaredExternals(root.config).map((decl) => {
25168
25200
  const unresolved = (problem) => ({ alias: decl.alias, project: decl.project, sourceKind: "unresolved", audience: "instance", problem });
25169
25201
  if (decl.problem) return unresolved(decl.problem);
25202
+ if (decl.sourceHosted !== void 0) return { alias: decl.alias, project: decl.project, sourceKind: "hosted", hosted: decl.sourceHosted, audience: "instance" };
25170
25203
  if (decl.sourcePath !== void 0) {
25171
25204
  const directory = path18.resolve(node.directory, decl.sourcePath);
25172
25205
  const found = nodes.find((n) => dirKey(n.directory) === dirKey(directory));
@@ -30169,6 +30202,17 @@ function unregisterDomain(id) {
30169
30202
 
30170
30203
  // src/core/external-producers.ts
30171
30204
  var path21 = __toESM(require("path"));
30205
+ function hostedOnlyProducer(id) {
30206
+ return `hosted-only producer \`${id}\`: available only through the hosted server`;
30207
+ }
30208
+ function resolveHosted(external) {
30209
+ if (external.sourceKind !== "hosted" || external.hosted === void 0) return external;
30210
+ const lookup2 = getHostedLookup();
30211
+ const unresolved = (problem) => ({ ...external, sourceKind: "unresolved", problem });
30212
+ if (!lookup2) return unresolved(hostedOnlyProducer(external.hosted));
30213
+ const root = lookup2(external.hosted);
30214
+ return root === null ? unresolved(`the hosted producer \`${external.hosted}\` is unknown or outside this request's reach`) : { ...external, directory: path21.resolve(root) };
30215
+ }
30172
30216
  function dirKey2(dir) {
30173
30217
  const resolved = path21.resolve(dir);
30174
30218
  return process.platform === "win32" ? resolved.toLowerCase() : resolved;
@@ -30194,11 +30238,14 @@ function resolveDeclared() {
30194
30238
  const family = graph();
30195
30239
  const node = consumerNode(family, bound2);
30196
30240
  if (!node) return null;
30197
- return node.externals.map((external) => ({
30198
- external,
30199
- ...external.sourceKind === "family" && external.producer !== void 0 ? { usage: exportUsage(node.namespace, external.producer) } : {},
30200
- reachable: whole
30201
- }));
30241
+ return node.externals.map((declared) => {
30242
+ const external = resolveHosted(declared);
30243
+ return {
30244
+ external,
30245
+ ...external.sourceKind === "family" && external.producer !== void 0 ? { usage: exportUsage(node.namespace, external.producer) } : {},
30246
+ reachable: whole
30247
+ };
30248
+ });
30202
30249
  });
30203
30250
  return answer(top) ?? answer(bound2) ?? [];
30204
30251
  }
@@ -30881,7 +30928,7 @@ function carryDeclarations(scan, result) {
30881
30928
  const bound2 = projectConfigRepository.load() ?? {};
30882
30929
  for (const [alias, decl] of Object.entries(scan.config?.externals ?? {})) {
30883
30930
  if (carriedExternal(scan, alias, decl) === "parent") continue;
30884
- const source = decl.source && !path22.isAbsolute(decl.source.path) ? { path: toPosixPath(path22.relative(root, path22.resolve(scan.memberDir, decl.source.path))) } : decl.source;
30931
+ const source = decl.source?.path !== void 0 && !path22.isAbsolute(decl.source.path) ? { path: toPosixPath(path22.relative(root, path22.resolve(scan.memberDir, decl.source.path))) } : decl.source;
30885
30932
  if (bound2.members?.[alias] === void 0) {
30886
30933
  projectConfigRepository.declareExternal(alias, {
30887
30934
  ...decl.project !== void 0 ? { project: decl.project } : {},
@@ -32583,7 +32630,7 @@ function importSpecTree(archive, options) {
32583
32630
  throw new Error("incompatible .waitree archive (format or minimum wairon version)");
32584
32631
  }
32585
32632
  const destDir = path31.resolve(options.destDir ?? getProjectRoot());
32586
- const occupied = hasSpecTree(destDir);
32633
+ const occupied2 = hasSpecTree(destDir);
32587
32634
  const authored = hasAuthoredSpecs(destDir);
32588
32635
  if (authored && options.replaceExisting !== true) {
32589
32636
  throw new Error(
@@ -32598,7 +32645,7 @@ function importSpecTree(archive, options) {
32598
32645
  discardStagingDir(stagingDir);
32599
32646
  throw err;
32600
32647
  }
32601
- const backupPath = commitStagedTree(stagingDir, destDir, extraction.roots, occupied);
32648
+ const backupPath = commitStagedTree(stagingDir, destDir, extraction.roots, occupied2);
32602
32649
  discardStagingDir(stagingDir);
32603
32650
  invalidateSpecCache();
32604
32651
  const result = {
@@ -32606,7 +32653,7 @@ function importSpecTree(archive, options) {
32606
32653
  projectName: extraction.manifest.projectName,
32607
32654
  roots: extraction.roots,
32608
32655
  fileCount: extraction.writtenPaths.length,
32609
- replaced: occupied
32656
+ replaced: occupied2
32610
32657
  };
32611
32658
  if (backupPath) result.backupPath = backupPath;
32612
32659
  if (extraction.manifest.stateId) result.stateId = extraction.manifest.stateId;
@@ -35205,6 +35252,7 @@ function pin(p, log) {
35205
35252
  }
35206
35253
 
35207
35254
  // src/migrations/membership.ts
35255
+ var fs22 = __toESM(require("fs"));
35208
35256
  var path40 = __toESM(require("path"));
35209
35257
 
35210
35258
  // src/migrations/family-files.ts
@@ -35285,39 +35333,93 @@ function within2(root, candidate) {
35285
35333
  const rel2 = nodePath.relative(root, candidate);
35286
35334
  return rel2 === "" || !rel2.startsWith("..") && !nodePath.isAbsolute(rel2);
35287
35335
  }
35336
+ function areaFiles(owner, area) {
35337
+ const out = [];
35338
+ const walk2 = (rel2) => {
35339
+ if (rel2 === `${WAI}/${TRANSACTIONS}`) return;
35340
+ const abs = nodePath.join(owner, ...rel2.split("/"));
35341
+ let stat;
35342
+ try {
35343
+ stat = fs21.lstatSync(abs);
35344
+ } catch {
35345
+ return;
35346
+ }
35347
+ if (stat.isSymbolicLink()) return;
35348
+ if (stat.isFile()) {
35349
+ out.push(rel2);
35350
+ return;
35351
+ }
35352
+ if (!stat.isDirectory()) return;
35353
+ for (const name of fs21.readdirSync(abs)) walk2(`${rel2}/${name}`);
35354
+ };
35355
+ walk2(area.split(nodePath.sep).join("/").replace(/\/+$/, ""));
35356
+ return out.sort();
35357
+ }
35358
+ function everyFile(owner) {
35359
+ const out = [];
35360
+ const walk2 = (dir, rel2) => {
35361
+ let entries;
35362
+ try {
35363
+ entries = fs21.readdirSync(dir, { withFileTypes: true });
35364
+ } catch {
35365
+ return;
35366
+ }
35367
+ for (const entry of entries) {
35368
+ const relPath = rel2 ? `${rel2}/${entry.name}` : entry.name;
35369
+ if (relPath === `${WAI}/${TRANSACTIONS}`) continue;
35370
+ if (entry.isSymbolicLink()) continue;
35371
+ if (entry.isDirectory()) walk2(nodePath.join(dir, entry.name), relPath);
35372
+ else if (entry.isFile()) out.push(relPath);
35373
+ }
35374
+ };
35375
+ walk2(owner, "");
35376
+ return out.sort();
35377
+ }
35378
+ function insideWhole(whole, dir) {
35379
+ return whole.some((w) => within2(w, dir));
35380
+ }
35381
+ function scopedFiles(owner, areas) {
35382
+ return areas ? [...new Set(areas.flatMap((a) => areaFiles(owner, a)))].sort() : waiFiles(owner);
35383
+ }
35288
35384
  function mirror(scope, target) {
35289
35385
  const digests = /* @__PURE__ */ new Map();
35290
- for (const project of scope.projects) {
35291
- if (!within2(scope.familyRoot, project)) {
35292
- throw new Error(`${project} does not lie within the family root ${scope.familyRoot}; a rehearsal copies only the family's own trees`);
35293
- }
35294
- const at = nodePath.join(target, nodePath.relative(scope.familyRoot, project));
35295
- for (const rel2 of waiFiles(project)) {
35296
- const live = nodePath.join(project, ...rel2.split("/"));
35297
- let bytes;
35298
- try {
35299
- bytes = fs21.readFileSync(live);
35300
- } catch (e) {
35301
- throw new Error(`could not copy ${live} into the rehearsal: ${e instanceof Error ? e.message : String(e)}`);
35302
- }
35303
- const copy = nodePath.join(at, ...rel2.split("/"));
35304
- fs21.mkdirSync(nodePath.dirname(copy), { recursive: true });
35305
- fs21.writeFileSync(copy, bytes);
35306
- digests.set(live, sha2562(bytes));
35386
+ const whole = scope.whole ?? [];
35387
+ for (const owner of [...scope.projects, ...whole]) {
35388
+ if (!within2(scope.familyRoot, owner)) {
35389
+ throw new Error(`${owner} does not lie within the family root ${scope.familyRoot}; a rehearsal copies only the family's own trees`);
35307
35390
  }
35308
35391
  }
35392
+ for (const project of scope.projects.filter((p) => !insideWhole(whole, p))) {
35393
+ copyFiles(project, scopedFiles(project, scope.areas), nodePath.join(target, nodePath.relative(scope.familyRoot, project)), digests);
35394
+ }
35395
+ for (const owner of whole) {
35396
+ copyFiles(owner, everyFile(owner), nodePath.join(target, nodePath.relative(scope.familyRoot, owner)), digests);
35397
+ }
35309
35398
  return digests;
35310
35399
  }
35400
+ function copyFiles(owner, rels, at, digests) {
35401
+ fs21.mkdirSync(at, { recursive: true });
35402
+ for (const rel2 of rels) {
35403
+ const live = nodePath.join(owner, ...rel2.split("/"));
35404
+ let bytes;
35405
+ try {
35406
+ bytes = fs21.readFileSync(live);
35407
+ } catch (e) {
35408
+ throw new Error(`could not copy ${live} into the rehearsal: ${e instanceof Error ? e.message : String(e)}`);
35409
+ }
35410
+ const copy = nodePath.join(at, ...rel2.split("/"));
35411
+ fs21.mkdirSync(nodePath.dirname(copy), { recursive: true });
35412
+ fs21.writeFileSync(copy, bytes);
35413
+ digests.set(live, sha2562(bytes));
35414
+ }
35415
+ }
35311
35416
  function compare4(rehearsal) {
35312
35417
  const changes = [];
35313
35418
  const seen = /* @__PURE__ */ new Set();
35314
- for (const owner of waiOwners(rehearsal.directory)) {
35315
- const live = nodePath.join(rehearsal.familyRoot, nodePath.relative(rehearsal.directory, owner));
35316
- for (const rel2 of waiFiles(owner)) {
35317
- seen.add(nodePath.join(live, ...rel2.split("/")));
35318
- const change = changeOf(rehearsal, live, rel2, sha2562(fs21.readFileSync(nodePath.join(owner, ...rel2.split("/")))));
35319
- if (change) changes.push(change);
35320
- }
35419
+ for (const [live, copyRoot, rel2] of comparedFiles(rehearsal)) {
35420
+ seen.add(nodePath.join(live, ...rel2.split("/")));
35421
+ const change = changeOf(rehearsal, live, rel2, sha2562(fs21.readFileSync(nodePath.join(copyRoot, ...rel2.split("/")))));
35422
+ if (change) changes.push(change);
35321
35423
  }
35322
35424
  for (const [liveFile, base] of rehearsal.baseDigests) {
35323
35425
  if (seen.has(liveFile)) continue;
@@ -35326,6 +35428,16 @@ function compare4(rehearsal) {
35326
35428
  }
35327
35429
  return changes.sort(byOwnerThenPath);
35328
35430
  }
35431
+ function comparedFiles(rehearsal) {
35432
+ const whole = rehearsal.whole ?? [];
35433
+ const owners = rehearsal.areas ? [...rehearsal.roots].map(([live, copy]) => [live, copy, scopedFiles(copy, rehearsal.areas)]) : waiOwners(rehearsal.directory).map((copy) => [nodePath.join(rehearsal.familyRoot, nodePath.relative(rehearsal.directory, copy)), copy, waiFiles(copy)]);
35434
+ const imageOf = (live) => nodePath.join(rehearsal.directory, nodePath.relative(rehearsal.familyRoot, live));
35435
+ const all = [
35436
+ ...owners.filter(([live]) => !insideWhole(whole, live)),
35437
+ ...whole.map((live) => [live, imageOf(live), everyFile(imageOf(live))])
35438
+ ];
35439
+ return all.flatMap(([live, copy, rels]) => rels.map((rel2) => [live, copy, rel2]));
35440
+ }
35329
35441
  function changeOf(rehearsal, live, rel2, staged) {
35330
35442
  const base = rehearsal.baseDigests.get(nodePath.join(live, ...rel2.split("/")));
35331
35443
  if (base === staged) return null;
@@ -35334,9 +35446,23 @@ function changeOf(rehearsal, live, rel2, staged) {
35334
35446
  var order = (a, b) => a < b ? -1 : a > b ? 1 : 0;
35335
35447
  var byOwnerThenPath = (a, b) => order(a.project, b.project) || order(a.path, b.path);
35336
35448
  function ownerOf3(rehearsal, liveFile) {
35337
- const roots = [...rehearsal.roots.keys()].filter((r) => within2(nodePath.join(r, WAI), liveFile));
35449
+ const whole = (rehearsal.whole ?? []).find((w) => within2(w, liveFile));
35450
+ if (whole !== void 0) return whole;
35451
+ const roots = [...rehearsal.roots.keys()].filter((r) => within2(rehearsal.areas ? r : nodePath.join(r, WAI), liveFile));
35338
35452
  return roots.sort((a, b) => b.length - a.length)[0] ?? nodePath.dirname(nodePath.dirname(liveFile));
35339
35453
  }
35454
+ function move(from, to) {
35455
+ const rels = everyFile(from);
35456
+ const taken = rels.find((rel2) => fs21.existsSync(nodePath.join(to, ...rel2.split("/"))));
35457
+ if (taken !== void 0) throw new Error(`cannot move ${from} to ${to}: ${nodePath.join(to, ...taken.split("/"))} already exists`);
35458
+ for (const rel2 of rels) {
35459
+ const target = nodePath.join(to, ...rel2.split("/"));
35460
+ makeDirs(nodePath.dirname(target));
35461
+ replace(nodePath.join(from, ...rel2.split("/")), target);
35462
+ }
35463
+ for (const rel2 of rels) prune(nodePath.dirname(nodePath.join(from, ...rel2.split("/"))), nodePath.dirname(from));
35464
+ prune(from, nodePath.dirname(from));
35465
+ }
35340
35466
  function read(path46) {
35341
35467
  try {
35342
35468
  return fs21.readFileSync(path46);
@@ -35478,6 +35604,13 @@ function hasSystemAt(dir) {
35478
35604
  }
35479
35605
  });
35480
35606
  }
35607
+ function occupied(dir) {
35608
+ try {
35609
+ return !fs22.statSync(dir).isDirectory() || fs22.readdirSync(dir).length > 0;
35610
+ } catch {
35611
+ return false;
35612
+ }
35613
+ }
35481
35614
  function boundNode(family, bound2) {
35482
35615
  return familyNode(family, bound2);
35483
35616
  }
@@ -35551,8 +35684,11 @@ function planDetach(family, bound2, request) {
35551
35684
  const plan5 = emptyPlan(family, request);
35552
35685
  const alias = request.alias ?? "";
35553
35686
  const done = configAt3(familyNode(family, bound2).directory)?.externals?.[alias];
35554
- if (done?.source?.path && !family.nodes.some((n) => n.parent === bound2 && n.mountAlias === alias)) return plan5;
35687
+ if ((done?.source?.path || done?.source?.hosted) && !family.nodes.some((n) => n.parent === bound2 && n.mountAlias === alias)) return plan5;
35555
35688
  const member = memberUnder(family, bound2, alias, plan5);
35689
+ if (member && request.relocation && occupied(request.relocation.to)) {
35690
+ refuse(plan5, "relocation-target-exists", bound2, `the member "${alias}" cannot move to ${request.relocation.to}: it already exists`);
35691
+ }
35556
35692
  if (!member) return plan5;
35557
35693
  const table = resolveProjectExports(member.namespace || void 0);
35558
35694
  const leaving = (key) => key !== void 0 && withinMember(family, member, key);
@@ -35605,27 +35741,34 @@ function planWidening(plan5, member, narrow) {
35605
35741
  }
35606
35742
  function detachEdits(plan5, family, bound2, member, alias, declared, consumers, outward) {
35607
35743
  const boundDir = familyNode(family, bound2).directory;
35608
- edit(plan5, bound2, "member", `members: ${alias} removed (${declared.path} stays as it is)`, { root: boundDir, call: "removeMember", args: [alias] });
35744
+ const relocation = plan5.request.relocation;
35745
+ const sourceTo = (from, producer) => relocation ? { hosted: producer.id ?? producer.mountAlias ?? producer.namespace } : { path: posix3(path40.relative(from, producer.directory)) };
35746
+ const where = (source) => source.hosted !== void 0 ? `hosted record ${source.hosted}` : source.path;
35747
+ edit(plan5, bound2, "member", `members: ${alias} removed (${declared.path} ${relocation ? `moves to ${relocation.to}` : "stays as it is"})`, { root: boundDir, call: "removeMember", args: [alias] });
35748
+ const boundSource = relocation ? sourceTo(boundDir, member) : { path: declared.path };
35609
35749
  const external = {
35610
35750
  ...member.id !== void 0 && member.id !== alias ? { project: member.id } : {},
35611
- source: { path: declared.path },
35751
+ source: boundSource,
35612
35752
  ...declared.use && declared.use.length > 0 ? { use: declared.use } : {},
35613
35753
  ...declared.description !== void 0 ? { description: declared.description } : {}
35614
35754
  };
35615
- edit(plan5, bound2, "external", `externals: ${alias} \u2192 ${member.id ?? alias} at ${declared.path}`, { root: boundDir, call: "declareExternal", args: [alias, external] });
35755
+ edit(plan5, bound2, "external", `externals: ${alias} \u2192 ${member.id ?? alias} at ${where(boundSource)}`, { root: boundDir, call: "declareExternal", args: [alias, external] });
35616
35756
  for (const c of consumers) {
35617
35757
  const held = configAt3(c.node.directory)?.externals?.[c.alias];
35618
- const source = { path: posix3(path40.relative(c.node.directory, member.directory)) };
35619
- edit(plan5, c.node.namespace, "external", `externals: ${c.alias} found at ${source.path} (it leaves the family)`, { root: c.node.directory, call: "repointExternal", args: [c.alias, held?.project ?? null, source] });
35758
+ const source = sourceTo(c.node.directory, member);
35759
+ edit(plan5, c.node.namespace, "external", `externals: ${c.alias} found at ${where(source)} (it leaves the family)`, { root: c.node.directory, call: "repointExternal", args: [c.alias, held?.project ?? null, source] });
35620
35760
  }
35621
35761
  const own = configAt3(member.directory)?.externals ?? {};
35622
35762
  for (const e of outward) {
35623
35763
  const producer = familyNode(family, e.producer);
35624
- const source = { path: posix3(path40.relative(member.directory, producer.directory)) };
35625
- edit(plan5, member.namespace, "external", `externals: ${e.alias} found at ${source.path} (the member leaves the family)`, { root: member.directory, call: "repointExternal", args: [e.alias, own[e.alias]?.project ?? null, source] });
35764
+ const source = sourceTo(member.directory, producer);
35765
+ edit(plan5, member.namespace, "external", `externals: ${e.alias} found at ${where(source)} (the member leaves the family)`, { root: member.directory, call: "repointExternal", args: [e.alias, own[e.alias]?.project ?? null, source] });
35766
+ }
35767
+ if (relocation) {
35768
+ edit(plan5, member.namespace, "move", `${member.directory} moved to ${relocation.to} (its own isolated root), its own members with it`, { root: member.directory, call: "move", args: [relocation.to] });
35626
35769
  }
35627
35770
  edit(plan5, bound2, "pin", `pin ${alias} (the detached project's surface, read last)`, { root: boundDir, call: "pinExternals", args: [[alias]] });
35628
- plan5.notes.push(`Consumers outside the family are not affected: they already name ${member.id ?? alias} by path, or not at all.`);
35771
+ plan5.notes.push(`Consumers outside the family are not affected: they already name ${member.id ?? alias} by ${relocation ? "hosted record id" : "path"}, or not at all.`);
35629
35772
  }
35630
35773
  function planAdopt(family, bound2, request) {
35631
35774
  const plan5 = emptyPlan(family, request);
@@ -35636,6 +35779,7 @@ function planAdopt(family, bound2, request) {
35636
35779
  if (!external && boundConfig?.members?.[alias] !== void 0) return plan5;
35637
35780
  let dir = null;
35638
35781
  if (!external) refuse(plan5, "not-an-external", bound2, `${label4(bound2)} declares no external "${alias}"`);
35782
+ else if (request.relocation) return planRelocatedAdopt(plan5, family, bound2, alias, external, request.relocation);
35639
35783
  else {
35640
35784
  dir = resolve30(boundDir, external.source?.path ?? "");
35641
35785
  if (dir === null) refuse(plan5, "not-contained", bound2, `the external "${alias}" ${external.source?.path ? `is found at "${external.source.path}", which is not strictly within ${label4(bound2)}` : "names no source.path"}`);
@@ -35644,7 +35788,18 @@ function planAdopt(family, bound2, request) {
35644
35788
  if (plan5.refusals.length > 0) return plan5;
35645
35789
  return adoptEdits(plan5, family, bound2, alias, external, dir);
35646
35790
  }
35647
- function adoptEdits(plan5, family, bound2, alias, external, dir) {
35791
+ function planRelocatedAdopt(plan5, family, bound2, alias, external, relocation) {
35792
+ const boundDir = familyNode(family, bound2).directory;
35793
+ const target = resolve30(boundDir, plan5.request.path ?? "");
35794
+ if (!external.source?.hosted) refuse(plan5, "not-contained", bound2, `the external "${alias}" names no source.hosted \u2014 a hosted adopt moves a project named by its hosted record id`);
35795
+ else if (target === null || path40.resolve(target) !== path40.resolve(relocation.to)) refuse(plan5, "not-contained", bound2, `the member path "${plan5.request.path ?? ""}" does not resolve strictly within ${label4(bound2)} to ${relocation.to}`);
35796
+ else if (occupied(relocation.to)) refuse(plan5, "relocation-target-exists", bound2, `the external "${alias}" cannot be adopted at ${relocation.to}: it already exists and is not an empty directory`);
35797
+ const from = relocation.from ?? (external.source?.hosted !== void 0 ? getHostedLookup()?.(external.source.hosted) ?? void 0 : void 0);
35798
+ if (plan5.refusals.length === 0 && !from) refuse(plan5, "not-a-project", bound2, `the hosted record "${external.source?.hosted}" is unknown or outside this request's reach \u2014 there is no root to adopt from`);
35799
+ if (plan5.refusals.length > 0) return plan5;
35800
+ return adoptEdits(plan5, family, bound2, alias, external, from, relocation.to);
35801
+ }
35802
+ function adoptEdits(plan5, family, bound2, alias, external, dir, at = dir) {
35648
35803
  const boundDir = familyNode(family, bound2).directory;
35649
35804
  const adoptee = configAt3(dir);
35650
35805
  if (!adoptee && !hasSystemAt(dir)) {
@@ -35661,22 +35816,28 @@ function adoptEdits(plan5, family, bound2, alias, external, dir) {
35661
35816
  edit(plan5, bound2, "pin", `pin ${alias} removed (the family supplies it now)`, { root: boundDir, call: "unpin", args: [alias] });
35662
35817
  edit(plan5, bound2, "external", `externals: ${alias} removed`, { root: boundDir, call: "removeExternal", args: [alias] });
35663
35818
  const declaration2 = {
35664
- path: posix3(path40.relative(boundDir, dir)),
35819
+ path: posix3(path40.relative(boundDir, at)),
35665
35820
  ...external.description !== void 0 ? { description: external.description } : {},
35666
35821
  ...external.use && external.use.length > 0 ? { use: external.use } : {}
35667
35822
  };
35668
35823
  edit(plan5, bound2, "member", `members: ${alias} \u2192 ${declaration2.path}`, { root: boundDir, call: "declareMember", args: [alias, declaration2] });
35824
+ const namesAdoptee = (from, e) => e.source?.path !== void 0 && path40.resolve(from, e.source.path) === path40.resolve(dir) || plan5.request.relocation !== void 0 && id !== void 0 && e.source?.hosted === id;
35825
+ const sourceText = (e) => e.source?.hosted !== void 0 ? `source.hosted ${e.source.hosted}` : `source.path ${e.source?.path}`;
35669
35826
  for (const n of family.nodes) {
35670
35827
  for (const [a, e] of Object.entries(configAt3(n.directory)?.externals ?? {})) {
35671
35828
  if (n.namespace === bound2 && a === alias) continue;
35672
- if (!e.source?.path || path40.resolve(n.directory, e.source.path) !== path40.resolve(dir)) continue;
35673
- edit(plan5, n.namespace, "external", `externals: ${a} found through the family (its source.path ${e.source.path} removed)`, { root: n.directory, call: "repointExternal", args: [a, e.project ?? null, null] });
35674
- plan5.notes.push(`${label4(n.namespace)}'s external "${a}" named the adoptee by path; adopt removes it \u2014 detach-then-adopt is exact only when detach wrote that path`);
35829
+ if (!namesAdoptee(n.directory, e)) continue;
35830
+ edit(plan5, n.namespace, "external", `externals: ${a} found through the family (its ${sourceText(e)} removed)`, { root: n.directory, call: "repointExternal", args: [a, e.project ?? null, null] });
35831
+ plan5.notes.push(`${label4(n.namespace)}'s external "${a}" named the adoptee by ${e.source?.hosted !== void 0 ? "hosted record id" : "path"}; adopt removes it \u2014 detach-then-adopt is exact only when detach wrote that source`);
35675
35832
  }
35676
35833
  }
35834
+ const namesFamily = (e) => e.source?.path !== void 0 && nodeAt(family, path40.resolve(dir, e.source.path)) !== void 0 || plan5.request.relocation !== void 0 && e.source?.hosted !== void 0 && family.nodes.some((n) => n.id === e.source.hosted);
35677
35835
  for (const [a, e] of Object.entries(adoptee?.externals ?? {})) {
35678
- if (!e.source?.path || !nodeAt(family, path40.resolve(dir, e.source.path))) continue;
35679
- edit(plan5, key, "external", `externals: ${a} found through the family (its source.path ${e.source.path} removed)`, { root: dir, call: "repointExternal", args: [a, e.project ?? null, null] });
35836
+ if (!namesFamily(e)) continue;
35837
+ edit(plan5, key, "external", `externals: ${a} found through the family (its ${sourceText(e)} removed)`, { root: dir, call: "repointExternal", args: [a, e.project ?? null, null] });
35838
+ }
35839
+ if (plan5.request.relocation) {
35840
+ edit(plan5, key, "move", `${dir} moved to ${at} (it joins the family's tree), its own members with it`, { root: dir, call: "move", args: [at] });
35680
35841
  }
35681
35842
  return plan5;
35682
35843
  }
@@ -35688,11 +35849,13 @@ function write3(plan5, rehearsal) {
35688
35849
  break;
35689
35850
  case "detach":
35690
35851
  for (const e of planned.filter((x) => x.write.call === "updateSpecGated")) writeOne(rehearsal, e.write);
35691
- for (const e of planned.filter((x) => x.write.call !== "pinExternals" && x.write.call !== "updateSpecGated")) writeOne(rehearsal, e.write);
35852
+ for (const e of planned.filter((x) => !["pinExternals", "updateSpecGated", "move"].includes(x.write.call))) writeOne(rehearsal, e.write);
35853
+ for (const e of planned.filter((x) => x.write.call === "move")) writeOne(rehearsal, e.write);
35692
35854
  for (const e of planned.filter((x) => x.write.call === "pinExternals")) writeOne(rehearsal, e.write);
35693
35855
  break;
35694
35856
  case "adopt":
35695
- for (const e of planned) writeOne(rehearsal, e.write);
35857
+ for (const e of planned.filter((x) => x.write.call !== "move")) writeOne(rehearsal, e.write);
35858
+ for (const e of planned.filter((x) => x.write.call === "move")) writeOne(rehearsal, e.write);
35696
35859
  break;
35697
35860
  default:
35698
35861
  throw new Error(`membership_migration writes attach, detach and adopt, not "${plan5.request.verb}"`);
@@ -35700,6 +35863,7 @@ function write3(plan5, rehearsal) {
35700
35863
  }
35701
35864
  function writeOne(rehearsal, w) {
35702
35865
  const args = w.args;
35866
+ if (w.call === "move") return move(rehearsalRoot(rehearsal, w.root), rehearsalRoot(rehearsal, String(args[0])));
35703
35867
  runWithProjectRoot(rehearsalRoot(rehearsal, w.root), () => {
35704
35868
  switch (w.call) {
35705
35869
  case "setId":
@@ -36124,11 +36288,11 @@ function planExternalize(family, bound2, request) {
36124
36288
  const donePath = typeof done === "string" ? done : done?.path;
36125
36289
  if (donePath !== void 0 && request.path !== void 0 && path42.resolve(node.directory, donePath) === path42.resolve(node.directory, request.path)) return plan5;
36126
36290
  const dir = resolve30(node.directory, request.path ?? "");
36127
- const occupied = dir !== null && holdsProject(dir);
36291
+ const occupied2 = dir !== null && holdsProject(dir);
36128
36292
  const sub = id.includes("::") ? null : loadSpec("subsystem", subKey);
36129
36293
  if (!sub || sub.projectPath || (family.owners.get(subKey) ?? "") !== bound2) refuse3(plan5, "externalize-refused", bound2, `${label6(bound2)} has no subsystem "${id}" to externalize (it is missing, or already a member)`);
36130
36294
  if (dir === null) refuse3(plan5, "not-contained", bound2, `the path "${request.path ?? ""}" does not resolve strictly within ${label6(bound2)}`);
36131
- else if (family.nodes.some((n) => path42.resolve(n.directory) === dir) || occupied) {
36295
+ else if (family.nodes.some((n) => path42.resolve(n.directory) === dir) || occupied2) {
36132
36296
  refuse3(plan5, "not-contained", bound2, `${dir} already holds a project`);
36133
36297
  }
36134
36298
  const config = configAt5(node.directory);
@@ -36243,11 +36407,11 @@ function writeOne3(rehearsal, w) {
36243
36407
 
36244
36408
  // src/migrations/transaction.ts
36245
36409
  var crypto6 = __toESM(require("crypto"));
36246
- var fs23 = __toESM(require("fs"));
36410
+ var fs24 = __toESM(require("fs"));
36247
36411
  var path43 = __toESM(require("path"));
36248
36412
 
36249
36413
  // src/migrations/transaction-files.ts
36250
- var fs22 = __toESM(require("fs"));
36414
+ var fs23 = __toESM(require("fs"));
36251
36415
  var os6 = __toESM(require("os"));
36252
36416
  var nodePath2 = __toESM(require("path"));
36253
36417
  var yaml3 = __toESM(require("js-yaml"));
@@ -36261,7 +36425,7 @@ function pause2(ms) {
36261
36425
  function removeTree(dir) {
36262
36426
  for (let attempt = 1; ; attempt++) {
36263
36427
  try {
36264
- fs22.rmSync(dir, { recursive: true, force: true });
36428
+ fs23.rmSync(dir, { recursive: true, force: true });
36265
36429
  return;
36266
36430
  } catch (e) {
36267
36431
  const code = e.code;
@@ -36271,20 +36435,20 @@ function removeTree(dir) {
36271
36435
  }
36272
36436
  }
36273
36437
  function writeDurably(file, bytes) {
36274
- fs22.mkdirSync(nodePath2.dirname(file), { recursive: true });
36438
+ fs23.mkdirSync(nodePath2.dirname(file), { recursive: true });
36275
36439
  const tmp = `${file}.tmp`;
36276
36440
  try {
36277
- const fd = fs22.openSync(tmp, "w");
36441
+ const fd = fs23.openSync(tmp, "w");
36278
36442
  try {
36279
- fs22.writeSync(fd, typeof bytes === "string" ? Buffer.from(bytes, "utf8") : bytes);
36280
- fs22.fsyncSync(fd);
36443
+ fs23.writeSync(fd, typeof bytes === "string" ? Buffer.from(bytes, "utf8") : bytes);
36444
+ fs23.fsyncSync(fd);
36281
36445
  } finally {
36282
- fs22.closeSync(fd);
36446
+ fs23.closeSync(fd);
36283
36447
  }
36284
- fs22.renameSync(tmp, file);
36448
+ fs23.renameSync(tmp, file);
36285
36449
  } catch (e) {
36286
36450
  try {
36287
- fs22.unlinkSync(tmp);
36451
+ fs23.unlinkSync(tmp);
36288
36452
  } catch {
36289
36453
  }
36290
36454
  throw e;
@@ -36294,18 +36458,18 @@ function writeDurably(file, bytes) {
36294
36458
  function syncDirectory(dir) {
36295
36459
  if (process.platform === "win32") return;
36296
36460
  try {
36297
- const fd = fs22.openSync(dir, "r");
36461
+ const fd = fs23.openSync(dir, "r");
36298
36462
  try {
36299
- fs22.fsyncSync(fd);
36463
+ fs23.fsyncSync(fd);
36300
36464
  } finally {
36301
- fs22.closeSync(fd);
36465
+ fs23.closeSync(fd);
36302
36466
  }
36303
36467
  } catch {
36304
36468
  }
36305
36469
  }
36306
36470
  function openRehearsal(id) {
36307
36471
  const dir = rehearsalDir(id);
36308
- fs22.mkdirSync(dir);
36472
+ fs23.mkdirSync(dir);
36309
36473
  return dir;
36310
36474
  }
36311
36475
  function dropRehearsal(id) {
@@ -36316,14 +36480,14 @@ function dropRehearsal(id) {
36316
36480
  }
36317
36481
  function saveJournal(journal) {
36318
36482
  const ignore = nodePath2.join(transactionsDir(journal.owner), ".gitignore");
36319
- if (!fs22.existsSync(ignore)) writeDurably(ignore, "*\n");
36483
+ if (!fs23.existsSync(ignore)) writeDurably(ignore, "*\n");
36320
36484
  writeDurably(nodePath2.join(transactionDir(journal.owner, journal.id), "journal.yaml"), yaml3.dump(journal, { noRefs: true, lineWidth: 200 }));
36321
36485
  }
36322
36486
  function readJournal(owner, id) {
36323
36487
  const file = nodePath2.join(transactionDir(owner, id), "journal.yaml");
36324
36488
  let text3;
36325
36489
  try {
36326
- text3 = fs22.readFileSync(file, "utf8");
36490
+ text3 = fs23.readFileSync(file, "utf8");
36327
36491
  } catch (e) {
36328
36492
  if (e.code === "ENOENT") return null;
36329
36493
  throw e;
@@ -36338,7 +36502,7 @@ function listJournals(owner) {
36338
36502
  const dir = transactionsDir(owner);
36339
36503
  let names;
36340
36504
  try {
36341
- names = fs22.readdirSync(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort();
36505
+ names = fs23.readdirSync(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort();
36342
36506
  } catch {
36343
36507
  return [];
36344
36508
  }
@@ -36360,7 +36524,7 @@ function putFile(owner, id, area, path46, bytes) {
36360
36524
  }
36361
36525
  function readFile(owner, id, area, path46) {
36362
36526
  try {
36363
- return fs22.readFileSync(locate(owner, id, area, path46));
36527
+ return fs23.readFileSync(locate(owner, id, area, path46));
36364
36528
  } catch (e) {
36365
36529
  if (e.code === "ENOENT") return null;
36366
36530
  throw e;
@@ -36374,7 +36538,7 @@ function close(owner, id) {
36374
36538
  const dir = transactionsDir(owner);
36375
36539
  let left;
36376
36540
  try {
36377
- left = fs22.readdirSync(dir);
36541
+ left = fs23.readdirSync(dir);
36378
36542
  } catch {
36379
36543
  return;
36380
36544
  }
@@ -36382,7 +36546,11 @@ function close(owner, id) {
36382
36546
  removeTree(dir);
36383
36547
  const wai = nodePath2.dirname(dir);
36384
36548
  try {
36385
- if (fs22.readdirSync(wai).length === 0) fs22.rmdirSync(wai);
36549
+ if (fs23.readdirSync(wai).length === 0) fs23.rmdirSync(wai);
36550
+ } catch {
36551
+ }
36552
+ try {
36553
+ if (fs23.readdirSync(owner).length === 0) fs23.rmdirSync(owner);
36386
36554
  } catch {
36387
36555
  }
36388
36556
  }
@@ -36469,15 +36637,16 @@ function rehearse(scope) {
36469
36637
  const directory = transactionRepository.openRehearsal(id);
36470
36638
  const familyRoot = path43.resolve(scope.familyRoot);
36471
36639
  const projects = [...new Set(scope.projects.map((p) => path43.resolve(p)))];
36640
+ const whole = scope.whole ? [...new Set(scope.whole.map((p) => path43.resolve(p)))] : void 0;
36472
36641
  let baseDigests;
36473
36642
  try {
36474
- baseDigests = mirror({ familyRoot, projects }, directory);
36643
+ baseDigests = mirror({ familyRoot, projects, ...scope.areas ? { areas: scope.areas } : {}, ...whole ? { whole } : {} }, directory);
36475
36644
  } catch (e) {
36476
36645
  transactionRepository.dropRehearsal(id);
36477
36646
  throw e;
36478
36647
  }
36479
36648
  const roots = new Map(projects.map((p) => [p, path43.join(directory, path43.relative(familyRoot, p))]));
36480
- return { id, directory, familyRoot, roots, baseDigests };
36649
+ return { id, directory, familyRoot, roots, baseDigests, ...scope.areas ? { areas: [...scope.areas] } : {}, ...whole ? { whole } : {} };
36481
36650
  }
36482
36651
  function diff(rehearsal) {
36483
36652
  return compare4(rehearsal);
@@ -36564,7 +36733,7 @@ function missingDirs(owner, entries) {
36564
36733
  for (const entry of entries.filter((e) => e.action === "create")) {
36565
36734
  const missing = [];
36566
36735
  let at = path43.dirname(liveOf(owner, entry.path));
36567
- while (!fs23.existsSync(at) && at !== path43.dirname(at)) {
36736
+ while (!fs24.existsSync(at) && at !== path43.dirname(at)) {
36568
36737
  missing.unshift(at);
36569
36738
  at = path43.dirname(at);
36570
36739
  }
@@ -36598,7 +36767,7 @@ function stageBytes(rehearsal, journal, entry) {
36598
36767
  function checkVolume(journal, entry, target) {
36599
36768
  const staged = transactionRepository.locate(journal.owner, journal.id, "staged", entry.path);
36600
36769
  let nearest = path43.dirname(target);
36601
- while (!fs23.existsSync(nearest) && nearest !== path43.dirname(nearest)) nearest = path43.dirname(nearest);
36770
+ while (!fs24.existsSync(nearest) && nearest !== path43.dirname(nearest)) nearest = path43.dirname(nearest);
36602
36771
  if (!sameVolume(staged, nearest)) {
36603
36772
  throw new StagingError(`cross-volume: ${target} lies on another volume than its staged copy; a rename across volumes is not atomic`);
36604
36773
  }
@@ -36682,7 +36851,7 @@ function recover(root, fix) {
36682
36851
  var coordinatorRoot = (journal) => path43.resolve(journal.owner, journal.coordinator);
36683
36852
  function journalAt(owner, id) {
36684
36853
  try {
36685
- const journal = fs23.existsSync(owner) ? transactionRepository.readJournal(owner, id) : null;
36854
+ const journal = fs24.existsSync(owner) ? transactionRepository.readJournal(owner, id) : null;
36686
36855
  return journal ? { ...journal, owner } : null;
36687
36856
  } catch {
36688
36857
  return null;
@@ -36714,7 +36883,7 @@ function resolveOne(journal) {
36714
36883
  for (const owner of [...owners.filter((o) => o !== coordinator.owner), coordinator.owner]) transactionRepository.close(owner, journal.id);
36715
36884
  return { ...base, owners, phase, action: "cleaned", detail: "it had committed; its transaction directories were removed" };
36716
36885
  }
36717
- const absent = owners.filter((o) => !fs23.existsSync(o));
36886
+ const absent = phase === "staging" ? [] : owners.filter((o) => !fs24.existsSync(o));
36718
36887
  if (absent.length > 0) {
36719
36888
  return { ...base, owners, phase, action: "refused", detail: `owner(s) out of reach: ${absent.join(", ")}` };
36720
36889
  }
@@ -36745,14 +36914,15 @@ function plan3(request) {
36745
36914
  const { top, whole } = climb3(bound2);
36746
36915
  const family = runWithProjectRoot(top, () => graph());
36747
36916
  const boundKey = family.nodes.find((n) => path44.resolve(n.directory) === path44.resolve(bound2))?.namespace ?? "";
36748
- const pending3 = unfinished(top, bound2);
36917
+ const pending3 = unfinished(top, bound2, request.relocation?.coordinator);
36749
36918
  if (pending3.length > 0) return refusedForPending(request, top, whole, pending3);
36750
36919
  const planned = request.verb === "chaining" ? route(request, family, boundKey) : runWithProjectRoot(top, () => route(request, family, boundKey));
36751
36920
  return rehearse2(request.verb === "chaining" ? planned : { ...planned, familyRoot: top, whole });
36752
36921
  }
36753
- function unfinished(top, bound2) {
36922
+ function unfinished(top, bound2, coordinator) {
36754
36923
  const found = recover(top, false);
36755
36924
  if (path44.resolve(bound2) !== path44.resolve(top)) found.push(...recover(bound2, false));
36925
+ if (coordinator !== void 0) found.push(...recover(coordinator, false));
36756
36926
  return found.filter((t, i) => found.findIndex((u) => u.id === t.id) === i);
36757
36927
  }
36758
36928
  function refusedForPending(request, top, whole, pending3) {
@@ -36830,17 +37000,62 @@ function rehearse2(plan5) {
36830
37000
  if (blocked(plan5) || plan5.request.rehearse === false) return plan5;
36831
37001
  const family = runWithProjectRoot(plan5.familyRoot, () => graph());
36832
37002
  const projects = [...family.nodes.map((n) => n.directory), ...broughtIn(plan5, family), ...pathProducers(plan5, family)];
36833
- const rehearsal = rehearse({ familyRoot: plan5.familyRoot, projects });
37003
+ const rehearsal = rehearse(scopeOf(plan5, projects));
36834
37004
  try {
36835
- onCopy(rehearsal, () => writeVerb(plan5, rehearsal));
37005
+ onCopy(rehearsal, () => relocatedLookup(plan5, rehearsal, () => writeVerb(plan5, rehearsal)));
36836
37006
  } catch (e) {
36837
37007
  discard(rehearsal);
36838
37008
  const detail = e instanceof Error ? e.message : String(e);
36839
37009
  return { ...plan5, refusals: [...plan5.refusals, { code: `${plan5.request.verb}-refused`, project: "", detail }] };
36840
37010
  }
36841
37011
  const changes = diff(rehearsal);
37012
+ const outside = unwritableOwners(changes);
37013
+ if (outside.length > 0) {
37014
+ discard(rehearsal);
37015
+ return { ...plan5, refusals: [...plan5.refusals, ...outside.map((o) => ({ code: "family-partial", project: o, detail: `the plan must write ${o}, which this request may not write` }))] };
37016
+ }
36842
37017
  return { ...plan5, rehearsal, changes, relock: relockOf(plan5, changes) };
36843
37018
  }
37019
+ function movedOf(plan5) {
37020
+ const relocation = plan5.request.relocation;
37021
+ const move2 = plan5.edits.find((e) => e.write?.call === "move");
37022
+ if (!relocation || !move2?.write) return null;
37023
+ return { from: path44.resolve(move2.write.root), to: path44.resolve(relocation.to) };
37024
+ }
37025
+ function scopeOf(plan5, projects) {
37026
+ const relocation = plan5.request.relocation;
37027
+ const moved = movedOf(plan5);
37028
+ if (!relocation || !moved) return { familyRoot: plan5.familyRoot, projects };
37029
+ return {
37030
+ familyRoot: relocation.coordinator,
37031
+ projects: [relocation.coordinator, ...projects],
37032
+ areas: [".wai", ...relocation.areas],
37033
+ whole: [moved.from, moved.to]
37034
+ };
37035
+ }
37036
+ function relocatedLookup(plan5, rehearsal, run3) {
37037
+ const moved = movedOf(plan5);
37038
+ const live = getHostedLookup();
37039
+ if (!moved || !live) return run3();
37040
+ const copied = [...rehearsal.roots.keys(), ...rehearsal.whole ?? []].filter((r) => r !== path44.resolve(rehearsal.familyRoot));
37041
+ const inside = (root, dir) => {
37042
+ const rel2 = path44.relative(root, dir);
37043
+ return rel2 === "" || !rel2.startsWith("..") && !path44.isAbsolute(rel2);
37044
+ };
37045
+ return runWithHostedLookup((id) => {
37046
+ const root = live(id);
37047
+ if (root === null) return null;
37048
+ const at = path44.resolve(root);
37049
+ if (inside(moved.from, at)) return path44.join(rehearsalRoot(rehearsal, moved.to), path44.relative(moved.from, at));
37050
+ return copied.some((r) => inside(r, at)) ? rehearsalRoot(rehearsal, at) : at;
37051
+ }, run3);
37052
+ }
37053
+ function unwritableOwners(changes) {
37054
+ const denied = getRequestParentReach()?.unwritableRoots ?? [];
37055
+ if (denied.length === 0) return [];
37056
+ const owners = [...new Set(changes.map((c) => path44.resolve(c.project)))];
37057
+ return owners.filter((o) => denied.includes(o));
37058
+ }
36844
37059
  function pathProducers(plan5, family) {
36845
37060
  const inside = (dir) => {
36846
37061
  const rel2 = path44.relative(plan5.familyRoot, dir);
@@ -36883,7 +37098,9 @@ function onCopy(rehearsal, run3) {
36883
37098
  function relockOf(plan5, changes) {
36884
37099
  const ended = new Set(changes.filter((c) => c.action === "delete" && c.path === ".wai/project.yaml").map((c) => path44.resolve(c.project)));
36885
37100
  const owners = [...new Set(changes.map((c) => path44.resolve(c.project)))].filter((o) => !ended.has(o));
36886
- if (plan5.request.verb !== "rename") return owners.filter((owner) => approvalRecord(owner) !== null);
37101
+ const moved = movedOf(plan5);
37102
+ const relocated = moved !== null && approvalRecord(moved.from) !== null ? [moved.to] : [];
37103
+ if (plan5.request.verb !== "rename") return [...owners.filter((owner) => approvalRecord(owner) !== null), ...relocated];
36887
37104
  const renamed = plan5.edits.find((e) => e.write?.call === "renameId")?.write?.root;
36888
37105
  const first = renamed !== void 0 ? path44.resolve(renamed) : void 0;
36889
37106
  return [...owners.filter((o) => o === first), ...owners.filter((o) => o !== first)];
@@ -38060,9 +38277,10 @@ ${renderChangeReport(report2)}`,
38060
38277
  server,
38061
38278
  "sdd_adopt_member",
38062
38279
  {
38063
- description: "Make the bound project's external found by a path inside it a member \u2014 sdd_detach_member's inverse. All-or-nothing; dryRun answers the plan and writes nothing. Never locks.",
38280
+ description: "Make the bound project's external a member \u2014 sdd_detach_member's inverse. Locally the external is found by a path inside the bound project and adopted where it is; on a hosted instance it names a project by source.hosted and `path` says where to adopt it (the hosting server relocates it there). All-or-nothing; dryRun answers the plan and writes nothing. Never locks.",
38064
38281
  inputSchema: {
38065
38282
  alias: import_zod11.z.string().describe("The external's alias"),
38283
+ path: import_zod11.z.string().optional().describe("Hosted: the member path to adopt a source.hosted external at, relative to the bound project; a source.path external is adopted where it is"),
38066
38284
  dryRun: dryRunArg
38067
38285
  }
38068
38286
  },
@@ -39127,6 +39345,7 @@ function loadProjectConfig3() {
39127
39345
  generateDiagramSet,
39128
39346
  generateSequenceDiagram,
39129
39347
  getExporter,
39348
+ getHostedLookup,
39130
39349
  getLoaderIssues,
39131
39350
  getProjectRoot,
39132
39351
  getProjectRootOverride,
@@ -39258,6 +39477,7 @@ function loadProjectConfig3() {
39258
39477
  resolveVariantGuidance,
39259
39478
  retireSpecialists,
39260
39479
  rewriteReferences,
39480
+ runWithHostedLookup,
39261
39481
  runWithProjectBinding,
39262
39482
  runWithProjectRoot,
39263
39483
  sameContract,