@wairon/cli 5.1.1-dev.92 → 5.1.1-dev.94

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
@@ -3580,6 +3580,7 @@ __export(src_exports, {
3580
3580
  deregisterPackRef: () => deregisterPackRef,
3581
3581
  deriveExecutionProfile: () => deriveExecutionProfile,
3582
3582
  derivedDocPaths: () => derivedDocPaths,
3583
+ describeProject: () => describeProject,
3583
3584
  detectDomainCandidates: () => detectDomainCandidates,
3584
3585
  diagnoseProjectPacks: () => diagnoseProjectPacks,
3585
3586
  diagramSetIndex: () => diagramSetIndex,
@@ -3621,6 +3622,7 @@ __export(src_exports, {
3621
3622
  getRequestParentReach: () => getRequestParentReach,
3622
3623
  getRequestProjectRoot: () => getRequestProjectRoot,
3623
3624
  getStatusReport: () => getStatusReport,
3625
+ getWriteReach: () => getWriteReach,
3624
3626
  globalGuideFilePath: () => globalGuideFilePath,
3625
3627
  globalPacksDir: () => globalPacksDir,
3626
3628
  globalPacksEnabled: () => globalPacksEnabled,
@@ -3792,6 +3794,8 @@ __export(src_exports, {
3792
3794
  writeFileIfChanged: () => writeFileIfChanged,
3793
3795
  writeJsonFile: () => writeJsonFile,
3794
3796
  writeLockRecord: () => writeLockRecord,
3797
+ writeReachDenials: () => writeReachDenials,
3798
+ writeReachPermits: () => writeReachPermits,
3795
3799
  writeRootGuideDelegator: () => writeRootGuideDelegator,
3796
3800
  writeYamlFile: () => writeYamlFile
3797
3801
  });
@@ -3861,6 +3865,18 @@ function listFilesRecursive(dirPath, ext) {
3861
3865
  return files;
3862
3866
  }
3863
3867
  var projectRootOverride = null;
3868
+ var sameRoot = (a, b) => path.resolve(a) === path.resolve(b);
3869
+ function writeReachPermits(reach2, root, subsystemId) {
3870
+ const own = subsystemId === null ? void 0 : reach2.subsystems.find((s) => s.subsystemId === subsystemId && sameRoot(s.root, root));
3871
+ if (own) return own.writable;
3872
+ return !reach2.unwritableRoots.some((r) => sameRoot(r, root));
3873
+ }
3874
+ function writeReachDenials(reach2, root) {
3875
+ return reach2.subsystems.filter((s) => !s.writable && sameRoot(s.root, root));
3876
+ }
3877
+ function getWriteReach() {
3878
+ return requestRootStore.getStore()?.writeReach ?? null;
3879
+ }
3864
3880
  var requestRootStore = new import_node_async_hooks.AsyncLocalStorage();
3865
3881
  function currentRootBinding() {
3866
3882
  return requestRootStore.getStore();
@@ -3876,7 +3892,8 @@ function runWithProjectBinding(dir, reach2, fn) {
3876
3892
  parentReach: reach2.parentReach,
3877
3893
  ...reach2.narrowed ? { narrowed: true } : {},
3878
3894
  ...reach2.unwritableRoots ? { unwritableRoots: reach2.unwritableRoots.map((r) => path.resolve(r)) } : {},
3879
- ...reach2.hostedLookup ? { hostedLookup: reach2.hostedLookup } : {}
3895
+ ...reach2.hostedLookup ? { hostedLookup: reach2.hostedLookup } : {},
3896
+ ...reach2.writeReach ? { writeReach: reach2.writeReach } : {}
3880
3897
  },
3881
3898
  fn
3882
3899
  );
@@ -4133,21 +4150,6 @@ var ExecutionBudgetSchema = import_zod.z.object({
4133
4150
  allowNestedDelegation: import_zod.z.boolean(),
4134
4151
  mcp: McpAccessSchema
4135
4152
  });
4136
- function summarize(budget, profile) {
4137
- const lines = [
4138
- `- **Work shape**: ${profile.breadth} breadth, ${profile.reasoningDepth} reasoning${profile.writes ? "" : ", read-only"}${profile.delegates ? ", delegating" : ""}`,
4139
- `- **Why**: ${profile.rationale}`
4140
- ];
4141
- if (budget.modelTier) lines.push(`- **Capability tier**: ${budget.modelTier}`);
4142
- if (budget.effort) lines.push(`- **Effort**: ${budget.effort}`);
4143
- if (budget.maxTurns !== void 0) {
4144
- lines.push(`- **Turn ceiling**: ${budget.maxTurns} (a circuit breaker \u2014 hitting it should read as a scoping error, not a limit to work up to)`);
4145
- }
4146
- lines.push(`- **Tool grant**: ${budget.toolClass}`);
4147
- lines.push(`- **May delegate further**: ${budget.allowNestedDelegation ? "yes" : "no"}`);
4148
- lines.push(`- **MCP access**: ${budget.mcp}`);
4149
- return lines;
4150
- }
4151
4153
  var BudgetTierSchema = import_zod.z.enum(["off", "free", "default", "trade", "aggressive"]);
4152
4154
  var ExecutionConfigSchema = import_zod.z.object({
4153
4155
  /**
@@ -4825,16 +4827,6 @@ var ProjectConfigSchema = import_zod3.z.object({
4825
4827
  * Can also be set via WAIRON_TEMPLATES_DIR environment variable.
4826
4828
  */
4827
4829
  globalTemplatesDir: import_zod3.z.string().optional(),
4828
- /**
4829
- * Tracks whether the wairon usage guide has been injected into each target's
4830
- * AI tool configuration files so the tool knows how to use wairon.
4831
- */
4832
- aiGuide: import_zod3.z.object({
4833
- claudeGlobal: import_zod3.z.boolean().default(false),
4834
- claudeLocal: import_zod3.z.boolean().default(false),
4835
- geminiGlobal: import_zod3.z.boolean().default(false),
4836
- geminiLocal: import_zod3.z.boolean().default(false)
4837
- }).optional(),
4838
4830
  /** Created by wairon at init time */
4839
4831
  createdAt: import_zod3.z.string().datetime(),
4840
4832
  updatedAt: import_zod3.z.string().datetime()
@@ -5698,6 +5690,9 @@ function registryOver(store, root) {
5698
5690
  setProjectType(projectType) {
5699
5691
  save({ ...current2(), projectType });
5700
5692
  },
5693
+ describeProject(name, description) {
5694
+ save({ ...current2(), name, ...description !== void 0 ? { description } : {} });
5695
+ },
5701
5696
  recordProfileSelection(selection) {
5702
5697
  save({ ...current2(), profileSelection: selection });
5703
5698
  },
@@ -6063,6 +6058,9 @@ function projectConfigRepositoryOver(adapter, rootDir) {
6063
6058
  setProjectType(projectType) {
6064
6059
  registry.setProjectType(projectType);
6065
6060
  },
6061
+ describeProject(name, description) {
6062
+ registry.describeProject(name, description);
6063
+ },
6066
6064
  recordProfileSelection(selection) {
6067
6065
  registry.recordProfileSelection(selection);
6068
6066
  },
@@ -6125,7 +6123,10 @@ function projectConfigRepositoryOver(adapter, rootDir) {
6125
6123
  function projectConfigRepositoryAt(rootDir) {
6126
6124
  return projectConfigRepositoryOver(projectConfigFsAdapterAt(rootDir), rootDir);
6127
6125
  }
6128
- var bound = () => projectConfigRepositoryAt(getProjectRoot());
6126
+ var bound = () => {
6127
+ const rootDir = getProjectRoot();
6128
+ return projectConfigRepositoryOver(projectConfigFsAdapterAt(rootDir), rootDir);
6129
+ };
6129
6130
  var projectConfigRepository = {
6130
6131
  load() {
6131
6132
  return bound().load();
@@ -6151,6 +6152,9 @@ var projectConfigRepository = {
6151
6152
  setProjectType(projectType) {
6152
6153
  bound().setProjectType(projectType);
6153
6154
  },
6155
+ describeProject(name, description) {
6156
+ bound().describeProject(name, description);
6157
+ },
6154
6158
  recordProfileSelection(selection) {
6155
6159
  bound().recordProfileSelection(selection);
6156
6160
  },
@@ -6418,19 +6422,19 @@ function withSortedMaps(record) {
6418
6422
  var fs5 = __toESM(require("fs"));
6419
6423
  var path7 = __toESM(require("path"));
6420
6424
  var SPEC_FILE_EXTENSION = ".yaml";
6421
- function readSpecFile(filePath) {
6422
- return readYamlFile(filePath);
6425
+ function readSpecFile(specPath) {
6426
+ return readYamlFile(specPath);
6423
6427
  }
6424
- function writeSpecFile(filePath, document) {
6425
- writeYamlFile(filePath, document);
6428
+ function writeSpecFile(specPath, document) {
6429
+ writeYamlFile(specPath, document);
6426
6430
  }
6427
6431
  function listSpecFiles(specsDir) {
6428
6432
  return listFilesRecursive(specsDir, SPEC_FILE_EXTENSION);
6429
6433
  }
6430
- function removeSpecFile(filePath, specsRoot) {
6431
- if (!fs5.existsSync(filePath)) return false;
6432
- fs5.unlinkSync(filePath);
6433
- pruneEmptyDirs(path7.dirname(filePath), path7.resolve(specsRoot));
6434
+ function removeSpecFile(specPath, specsRoot) {
6435
+ if (!fs5.existsSync(specPath)) return false;
6436
+ fs5.unlinkSync(specPath);
6437
+ pruneEmptyDirs(path7.dirname(specPath), path7.resolve(specsRoot));
6434
6438
  return true;
6435
6439
  }
6436
6440
  function pruneEmptyDirs(dir, specsRoot) {
@@ -14025,8 +14029,8 @@ function canonicalReferences(snapshot) {
14025
14029
  types: snapshot.types.map((def) => ({ ...def, fields: def.fields.map((f) => ({ ...f, type: canon(f.type) })) }))
14026
14030
  };
14027
14031
  }
14028
- function listSnapshots(rootDir = getProjectRoot()) {
14029
- const dir = surfacesDir(rootDir);
14032
+ function listSnapshots() {
14033
+ const dir = surfacesDir(getProjectRoot());
14030
14034
  if (!fs9.existsSync(dir)) return [];
14031
14035
  const out = [];
14032
14036
  for (const file of fs9.readdirSync(dir)) {
@@ -14041,8 +14045,8 @@ function listSnapshots(rootDir = getProjectRoot()) {
14041
14045
  function snapshotFilename(projectName) {
14042
14046
  return `${safeFilenamePart(projectName)}.yaml`;
14043
14047
  }
14044
- function removeSnapshot(projectName, rootDir = getProjectRoot()) {
14045
- const dir = surfacesDir(rootDir);
14048
+ function removeSnapshot(projectName) {
14049
+ const dir = surfacesDir(getProjectRoot());
14046
14050
  const direct = path13.join(dir, snapshotFilename(projectName));
14047
14051
  if (fs9.existsSync(direct)) {
14048
14052
  fs9.unlinkSync(direct);
@@ -14362,11 +14366,11 @@ function listExternals() {
14362
14366
  }
14363
14367
 
14364
14368
  // src/core/surface-portal.ts
14365
- function listSnapshots2(rootDir) {
14366
- return listSnapshots(rootDir);
14369
+ function listSnapshots2() {
14370
+ return listSnapshots();
14367
14371
  }
14368
- function removeSnapshot2(projectName, rootDir) {
14369
- return removeSnapshot(projectName, rootDir);
14372
+ function removeSnapshot2(projectName) {
14373
+ return removeSnapshot(projectName);
14370
14374
  }
14371
14375
  function listFamilyPins2() {
14372
14376
  return listFamilyPins();
@@ -15081,7 +15085,7 @@ function defaultTargetConfig(type) {
15081
15085
  enabled: true
15082
15086
  };
15083
15087
  }
15084
- var WAIRON_VERSION = "5.1.1-dev.92";
15088
+ var WAIRON_VERSION = "5.1.1-dev.94";
15085
15089
  var GITHUB_REPO = "SYW-Apps/Waffle-AIron";
15086
15090
  var ARCHITECT_AGENT_ID = "agent-architect";
15087
15091
  var ARCHITECT_TEMPLATE_ID = "architect";
@@ -15528,13 +15532,13 @@ function resolvePackRef(ref, projectRoot) {
15528
15532
  }
15529
15533
  return resolved;
15530
15534
  }
15531
- function readManifest(target, projectRoot) {
15535
+ function readManifest(target, resolveFrom) {
15532
15536
  if (isYamlPath(target)) {
15533
15537
  const raw = readYamlFile(target);
15534
15538
  if (raw == null) throw new Error("file not found or empty");
15535
15539
  return { ...DeclarativePackSchema.parse(raw), rules: [] };
15536
15540
  }
15537
- const req = (0, import_module.createRequire)(path17.join(projectRoot, "package.json"));
15541
+ const req = (0, import_module.createRequire)(path17.join(resolveFrom, "package.json"));
15538
15542
  const modRaw = req(target);
15539
15543
  const mod = modRaw?.default ?? modRaw;
15540
15544
  const parsed = DeclarativePackSchema.parse({
@@ -18854,7 +18858,9 @@ var facadeForwardingRule = {
18854
18858
  "FACADE_FORWARDING",
18855
18859
  `Facade method "${method.name}" of ${comp.componentType} "${comp.id}" (implementation "${impl.id}") must be pure 1:1 forwarding \u2014 exactly one call step to an owned member (${comp.owns.join(", ") || "none declared"}) \u2014 but its narrative ${problem}. Move the logic into a member block, or lint.allow a deliberate exception.`,
18856
18860
  impl.id,
18857
- isDraftCtx
18861
+ isDraftCtx,
18862
+ void 0,
18863
+ { at: method.name }
18858
18864
  );
18859
18865
  }
18860
18866
  }
@@ -21750,11 +21756,31 @@ var exportConformanceRule = {
21750
21756
  );
21751
21757
  }
21752
21758
  }
21759
+ const typeMethodsAt = /* @__PURE__ */ new Map();
21753
21760
  for (const type of ctx.types) {
21754
21761
  if (!type.sourcePath) continue;
21755
21762
  promise(type.sourcePath, type.symbol ?? type.name);
21756
21763
  for (const method of type.methods ?? []) {
21757
- promise(method.sourcePath ?? type.sourcePath, method.symbol ?? method.name);
21764
+ const at = pathKey(method.sourcePath ?? type.sourcePath);
21765
+ const name = method.symbol ?? method.name;
21766
+ promise(at, name);
21767
+ let names = typeMethodsAt.get(at);
21768
+ if (!names) typeMethodsAt.set(at, names = /* @__PURE__ */ new Set());
21769
+ names.add(name);
21770
+ }
21771
+ }
21772
+ for (const file of realization.paths) {
21773
+ const facts = code.factsAt(file);
21774
+ if (!facts || facts.status !== "analyzed" || facts.analysisGrade !== "exact") continue;
21775
+ for (const binding of facts.reexportBindings ?? []) {
21776
+ const target = resolveImport(file, binding.from, code.paths);
21777
+ const methods = target ? typeMethodsAt.get(target) : void 0;
21778
+ if (!methods) continue;
21779
+ if (binding.local === "*") {
21780
+ for (const name of methods) promise(file, name);
21781
+ } else if (methods.has(binding.local)) {
21782
+ promise(file, binding.exported);
21783
+ }
21758
21784
  }
21759
21785
  }
21760
21786
  const takenFrom = /* @__PURE__ */ new Map();
@@ -24156,7 +24182,7 @@ function publishesFrom(module2, name, target, symbol, readReexports, visited = /
24156
24182
  }
24157
24183
  return false;
24158
24184
  }
24159
- function findTestsReferencing(methods, projectRoot, testRoots = []) {
24185
+ function findTestsReferencing(methods, projectRoot, testRoots) {
24160
24186
  const scanned = readTestFiles(testRoots, projectRoot);
24161
24187
  if (scanned.length === 0) return [];
24162
24188
  const indiscriminateAbove = scanned.length / 10;
@@ -25147,7 +25173,7 @@ function boundProjectIdentity() {
25147
25173
  const lockedProjectId = readLockRecord()?.projectId;
25148
25174
  return projectIdentity(config, lockedProjectId);
25149
25175
  }
25150
- function findTestsReferencing2(methods, projectRoot, testRoots = []) {
25176
+ function findTestsReferencing2(methods, projectRoot, testRoots) {
25151
25177
  return findTestsReferencing(methods, projectRoot, testRoots);
25152
25178
  }
25153
25179
  function issue(severity, code, message, agentId, specId) {
@@ -26846,6 +26872,34 @@ var PartReadOnly = class extends WaironError {
26846
26872
  this.name = "PartReadOnly";
26847
26873
  }
26848
26874
  };
26875
+ var SubsystemWriteDenied = class extends WaironError {
26876
+ constructor(message) {
26877
+ super(message);
26878
+ this.name = "SubsystemWriteDenied";
26879
+ }
26880
+ };
26881
+ function deniedOwners(reach2, owners) {
26882
+ const seen = /* @__PURE__ */ new Set();
26883
+ const denied = [];
26884
+ for (const o of owners) {
26885
+ const key = `${path21.resolve(o.root)}|${o.subsystemId ?? ""}`;
26886
+ if (seen.has(key)) continue;
26887
+ seen.add(key);
26888
+ if (writeReachPermits(reach2, o.root, o.subsystemId)) continue;
26889
+ const rule = o.subsystemId === null ? void 0 : reach2.subsystems.find((s) => s.subsystemId === o.subsystemId && path21.resolve(s.root) === path21.resolve(o.root));
26890
+ denied.push(o.subsystemId === null ? `project "${o.project}" itself (a spec of no subsystem, decided at the project rung)` : `subsystem "${o.subsystemId}" of project "${o.project}" (decided by ${rule?.decidedBy ?? "the project rung"})`);
26891
+ }
26892
+ return denied;
26893
+ }
26894
+ function assertSpecWritesPermitted(owners, what) {
26895
+ const reach2 = getWriteReach();
26896
+ if (!reach2) return;
26897
+ const denied = deniedOwners(reach2, owners);
26898
+ if (denied.length === 0) return;
26899
+ throw new SubsystemWriteDenied(
26900
+ `SubsystemWriteDenied: ${what} would change ${denied.length === 1 ? "a spec" : "specs"} this request may not change \u2014 ${denied.join("; ")}. Nothing was written.`
26901
+ );
26902
+ }
26849
26903
  function unknownKeysIn(raw, parsed, at, out) {
26850
26904
  if (Array.isArray(raw)) {
26851
26905
  if (!Array.isArray(parsed) || parsed.length !== raw.length) return;
@@ -28856,6 +28910,92 @@ var SpecWorkspace = class {
28856
28910
  `The spec file ${file} belongs to the part "${held.part.alias}", fetched from git (${held.source?.git ?? "its repository"} at ${held.part.commit ?? "its pinned commit"}): a git part is read-only here. Edit it in its own repository, commit it there, and take it in with \`wairon member update ${held.part.alias}\`. Nothing was written.`
28857
28911
  );
28858
28912
  }
28913
+ /**
28914
+ * spec_registry.save step 5 / delete step 2: inside a hosted request, may the
28915
+ * request change this spec? Judged against the request's write reach with the
28916
+ * home's project root and the spec's owning subsystem — the document written
28917
+ * and, when a stored document is replaced or removed, the one stored (a
28918
+ * component moved out of a subsystem changes that subsystem too). Outside a
28919
+ * hosted request nothing is judged.
28920
+ */
28921
+ assertHomeInReach(file, kind, document) {
28922
+ if (!getWriteReach()) return;
28923
+ const raw = this.homeRootOf(file);
28924
+ const owners = [];
28925
+ if (document !== null) owners.push(this.documentOwner(raw, kind, document));
28926
+ if (pathExists(file)) {
28927
+ try {
28928
+ owners.push(this.documentOwner(raw, kind, readSpecFile(file)));
28929
+ } catch {
28930
+ }
28931
+ }
28932
+ assertSpecWritesPermitted(owners, `writing ${kind} spec ${path21.basename(path21.dirname(file))}/${path21.basename(file)}`);
28933
+ }
28934
+ /** The project root a spec file is homed under: the root whose specs folder, or one of whose parts' folders, holds it (deepest wins). */
28935
+ homeRootOf(file) {
28936
+ this.scanAll();
28937
+ let best;
28938
+ let depth = -1;
28939
+ for (const raw of this.cachedRawRoots) {
28940
+ const homes = [aiPathsAt(raw.dir).specsDir(), ...raw.record.parts.map((p) => p.directory).filter((d) => d !== void 0)];
28941
+ for (const home of homes) {
28942
+ if (isWithin(home, file) && path21.resolve(home).length > depth) {
28943
+ best = raw;
28944
+ depth = path21.resolve(home).length;
28945
+ }
28946
+ }
28947
+ }
28948
+ if (!best) return { dir: this.rootDir, namespace: "", project: path21.basename(this.rootDir) };
28949
+ return { dir: best.dir, namespace: best.record.namespace, project: idOf(best) ?? (best.record.namespace || path21.basename(best.dir)) };
28950
+ }
28951
+ /**
28952
+ * The owner of one spec DOCUMENT as stored in its root (its references local
28953
+ * to that root): a subsystem is its own, a component or type names its
28954
+ * subsystem, an interface or implementation follows its component; the L0, a
28955
+ * system-level type and a type group belong to no subsystem.
28956
+ */
28957
+ documentOwner(root, kind, document) {
28958
+ const doc = document ?? {};
28959
+ const local = (v) => typeof v === "string" && v ? splitNamespace(v).localId : null;
28960
+ const key = (v) => typeof v === "string" && v ? keyIn(root.namespace, splitNamespace(v).localId) : null;
28961
+ let subsystemId = null;
28962
+ if (kind === "subsystem") subsystemId = local(doc.id);
28963
+ else if (kind === "component" || kind === "type") subsystemId = local(doc.subsystem);
28964
+ else if (kind === "interface") subsystemId = this.componentSubsystem(key(doc.component));
28965
+ else if (kind === "implementation") {
28966
+ const contract = key(doc.contract);
28967
+ subsystemId = this.componentSubsystem(contract ? this.loadInterfaceSpec(contract)?.component ?? null : null);
28968
+ }
28969
+ return { root: root.dir, project: root.project, subsystemId };
28970
+ }
28971
+ /** The local id of a component's subsystem, by the component's key; null when no such component is stored. */
28972
+ componentSubsystem(componentKey) {
28973
+ if (!componentKey) return null;
28974
+ const subsystem = this.loadComponentSpec(componentKey)?.subsystem;
28975
+ return subsystem ? splitNamespace(subsystem).localId : null;
28976
+ }
28977
+ /**
28978
+ * The owner of one IN-MEMORY spec (its references keys, as the loader binds
28979
+ * them) — what a multi-spec write (a rename, a method move) judges before its
28980
+ * first write. `staged` answers a spec the write itself creates.
28981
+ */
28982
+ specOwner(kind, spec, staged) {
28983
+ this.scanAll();
28984
+ const id = kind === "system" ? "" : String(spec?.id ?? "");
28985
+ const namespace = (kind === "system" ? "" : this.ownerKeyOf(id)) ?? "";
28986
+ const raw = this.cachedRawRoots.find((r) => r.record.namespace === namespace);
28987
+ const root = raw ? { dir: raw.dir, project: idOf(raw) ?? (namespace || path21.basename(raw.dir)) } : { dir: this.rootDir, project: path21.basename(this.rootDir) };
28988
+ const localOfKey = (v) => v ? splitNamespace(v).localId : null;
28989
+ let subsystemId = null;
28990
+ if (kind === "subsystem") subsystemId = localOfKey(id);
28991
+ else if (kind === "component" || kind === "type") subsystemId = localOfKey(spec?.subsystem);
28992
+ else if (kind === "interface") subsystemId = this.componentSubsystem(spec?.component ?? null);
28993
+ else if (kind === "implementation" && spec?.contract) {
28994
+ const component = this.loadInterfaceSpec(spec.contract)?.component ?? staged?.("interface", spec.contract)?.component ?? null;
28995
+ subsystemId = this.componentSubsystem(component);
28996
+ }
28997
+ return { root: root.dir, project: root.project, subsystemId };
28998
+ }
28859
28999
  /**
28860
29000
  * The key one spec file is approved under (stage 8): its path from the bound
28861
29001
  * root, POSIX — or, for a part's file, `members/<alias>/<path inside the
@@ -28866,13 +29006,15 @@ var SpecWorkspace = class {
28866
29006
  return approvalKeyIn(file, this.rootDir, this.cachedRoots[0]?.parts ?? []);
28867
29007
  }
28868
29008
  /** The file store's write, at a home the writer resolved — a part's folder included, a git part's refused. */
28869
- writeHome(file, document) {
29009
+ writeHome(file, document, kind) {
28870
29010
  this.assertHomeWritable(file);
29011
+ this.assertHomeInReach(file, kind, document);
28871
29012
  writeSpecFile(file, document);
28872
29013
  }
28873
29014
  /** The file store's delete, pruning up to the specs folder the file lives in (its part's, for a part's file). */
28874
- removeHome(file) {
29015
+ removeHome(file, kind) {
28875
29016
  this.assertHomeWritable(file);
29017
+ this.assertHomeInReach(file, kind, null);
28876
29018
  const part = this.partHolding(file)?.part;
28877
29019
  return removeSpecFile(file, part?.directory ? aiPathsAt(part.directory).specsDir() : this.paths.specsDir());
28878
29020
  }
@@ -29223,6 +29365,7 @@ var SpecWorkspace = class {
29223
29365
  }
29224
29366
  saveSystemSpec(spec) {
29225
29367
  const p = this.paths.specsSystem();
29368
+ this.assertHomeInReach(p, "system", spec);
29226
29369
  ensureDir(path21.dirname(p));
29227
29370
  writeSpecFile(p, parseOrThrow(SystemSpecSchema, spec, "system", spec.name));
29228
29371
  invalidateSpecCache();
@@ -29373,12 +29516,12 @@ var SpecWorkspace = class {
29373
29516
  }
29374
29517
  }
29375
29518
  specToWrite.updatedAt = opts?.preserveUpdatedAt && existing?.updatedAt ? existing.updatedAt : (/* @__PURE__ */ new Date()).toISOString();
29376
- this.writeHome(p, parseOrThrow(SubsystemSpecSchema, specToWrite, "subsystem", spec.id));
29519
+ this.writeHome(p, parseOrThrow(SubsystemSpecSchema, specToWrite, "subsystem", spec.id), "subsystem");
29377
29520
  invalidateSpecCache();
29378
29521
  }
29379
29522
  deleteSubsystemSpec(id) {
29380
29523
  const p = this.getSubsystemPath(id);
29381
- if (!this.removeHome(p)) return false;
29524
+ if (!this.removeHome(p, "subsystem")) return false;
29382
29525
  invalidateSpecCache();
29383
29526
  return true;
29384
29527
  }
@@ -29433,14 +29576,14 @@ var SpecWorkspace = class {
29433
29576
  );
29434
29577
  }
29435
29578
  specToWrite.updatedAt = opts?.preserveUpdatedAt && existing?.updatedAt ? existing.updatedAt : (/* @__PURE__ */ new Date()).toISOString();
29436
- this.writeHome(p, parseOrThrow(ComponentSpecSchema, specToWrite, "component", spec.id));
29579
+ this.writeHome(p, parseOrThrow(ComponentSpecSchema, specToWrite, "component", spec.id), "component");
29437
29580
  invalidateSpecCache();
29438
29581
  this.normalizeComponentLayout();
29439
29582
  return notices;
29440
29583
  }
29441
29584
  deleteComponentSpec(id) {
29442
29585
  const p = this.getComponentPath(id);
29443
- if (!this.removeHome(p)) return false;
29586
+ if (!this.removeHome(p, "component")) return false;
29444
29587
  invalidateSpecCache();
29445
29588
  return true;
29446
29589
  }
@@ -29562,13 +29705,13 @@ var SpecWorkspace = class {
29562
29705
  }
29563
29706
  }
29564
29707
  specToWrite.updatedAt = opts?.preserveUpdatedAt && existing?.updatedAt ? existing.updatedAt : (/* @__PURE__ */ new Date()).toISOString();
29565
- this.writeHome(p, parseOrThrow(InterfaceSpecSchema, specToWrite, "interface", spec.id));
29708
+ this.writeHome(p, parseOrThrow(InterfaceSpecSchema, specToWrite, "interface", spec.id), "interface");
29566
29709
  invalidateSpecCache();
29567
29710
  return notices;
29568
29711
  }
29569
29712
  deleteInterfaceSpec(id) {
29570
29713
  const p = this.getInterfacePath(id);
29571
- if (!this.removeHome(p)) return false;
29714
+ if (!this.removeHome(p, "interface")) return false;
29572
29715
  invalidateSpecCache();
29573
29716
  return true;
29574
29717
  }
@@ -29618,13 +29761,13 @@ var SpecWorkspace = class {
29618
29761
  }
29619
29762
  }
29620
29763
  specToWrite.updatedAt = opts?.preserveUpdatedAt && existing?.updatedAt ? existing.updatedAt : (/* @__PURE__ */ new Date()).toISOString();
29621
- this.writeHome(p, parseOrThrow(ImplementationSpecSchema, specToWrite, "implementation", spec.id));
29764
+ this.writeHome(p, parseOrThrow(ImplementationSpecSchema, specToWrite, "implementation", spec.id), "implementation");
29622
29765
  invalidateSpecCache();
29623
29766
  return notices;
29624
29767
  }
29625
29768
  deleteImplementationSpec(id) {
29626
29769
  const p = this.getImplementationPath(id);
29627
- if (!this.removeHome(p)) return false;
29770
+ if (!this.removeHome(p, "implementation")) return false;
29628
29771
  invalidateSpecCache();
29629
29772
  return true;
29630
29773
  }
@@ -29670,14 +29813,14 @@ var SpecWorkspace = class {
29670
29813
  }
29671
29814
  }
29672
29815
  specToWrite.updatedAt = opts?.preserveUpdatedAt && existing?.updatedAt ? existing.updatedAt : (/* @__PURE__ */ new Date()).toISOString();
29673
- this.writeHome(p, parseOrThrow(TypeSpecSchema, specToWrite, "type", spec.id));
29816
+ this.writeHome(p, parseOrThrow(TypeSpecSchema, specToWrite, "type", spec.id), "type");
29674
29817
  invalidateSpecCache();
29675
29818
  return notices;
29676
29819
  }
29677
29820
  deleteTypeSpec(id) {
29678
29821
  const spec = this.loadTypeSpec(id);
29679
29822
  const p = this.getTypePath(id, spec?.subsystem, spec?.group);
29680
- if (!this.removeHome(p)) return false;
29823
+ if (!this.removeHome(p, "type")) return false;
29681
29824
  invalidateSpecCache();
29682
29825
  return true;
29683
29826
  }
@@ -29700,12 +29843,12 @@ var SpecWorkspace = class {
29700
29843
  specToWrite.createdAt = existing.createdAt;
29701
29844
  }
29702
29845
  specToWrite.updatedAt = opts?.preserveUpdatedAt && existing?.updatedAt ? existing.updatedAt : (/* @__PURE__ */ new Date()).toISOString();
29703
- this.writeHome(p, parseOrThrow(GroupSpecSchema, specToWrite, "group", spec.id));
29846
+ this.writeHome(p, parseOrThrow(GroupSpecSchema, specToWrite, "group", spec.id), "group");
29704
29847
  invalidateSpecCache();
29705
29848
  }
29706
29849
  deleteGroupSpec(id) {
29707
29850
  const p = this.getGroupPath(id);
29708
- if (!this.removeHome(p)) return false;
29851
+ if (!this.removeHome(p, "group")) return false;
29709
29852
  invalidateSpecCache();
29710
29853
  return true;
29711
29854
  }
@@ -29903,7 +30046,7 @@ var SpecWorkspace = class {
29903
30046
  );
29904
30047
  }
29905
30048
  if (JSON.stringify(document) === JSON.stringify(stored)) return false;
29906
- this.writeHome(file, document);
30049
+ this.writeHome(file, document, kind);
29907
30050
  invalidateSpecCache();
29908
30051
  return true;
29909
30052
  }
@@ -30817,6 +30960,7 @@ var SpecWorkspace = class {
30817
30960
  }
30818
30961
  }
30819
30962
  const carried = projectConfigRepository.rekeyCarried(rename, true);
30963
+ this.assertMoveInReach(from, to, plan6, carried.length > 0);
30820
30964
  if (dryRun) {
30821
30965
  const edits2 = moveChangeReports([...plan6.edits.values()], true);
30822
30966
  return {
@@ -30856,6 +31000,14 @@ var SpecWorkspace = class {
30856
31000
  carried
30857
31001
  };
30858
31002
  }
31003
+ /** Refuse a method move, naming every subsystem it may not change, before anything is written. */
31004
+ assertMoveInReach(from, to, plan6, rekeysRegister) {
31005
+ if (!getWriteReach()) return;
31006
+ const staged = (kind, id) => plan6.edits.get(`${kind}:${id}`)?.after;
31007
+ const owners = [...plan6.edits.values()].flatMap((e) => [e.before, e.after].filter((s) => s !== null && s !== void 0).map((s) => this.specOwner(e.kind, s, staged)));
31008
+ if (rekeysRegister) owners.push(this.specOwner("system", null));
31009
+ assertSpecWritesPermitted(owners, `moving methods from "${from}" to "${to}"`);
31010
+ }
30859
31011
  /**
30860
31012
  * Rekey every lint allow keyed on a moved method, staging each spec it
30861
31013
  * changes like any other reference the move follows. An allow anchored at a
@@ -31504,6 +31656,9 @@ function removePackSelection(packName) {
31504
31656
  function setProjectType(projectType) {
31505
31657
  projectConfigRepository.setProjectType(projectType);
31506
31658
  }
31659
+ function describeProject(name, description) {
31660
+ projectConfigRepository.describeProject(name, description);
31661
+ }
31507
31662
  function recordProfileSelection(selection) {
31508
31663
  projectConfigRepository.recordProfileSelection(selection);
31509
31664
  }
@@ -31586,6 +31741,14 @@ function normalizeReferences(kind, id) {
31586
31741
  function rewriteReferences(kind, id, edits) {
31587
31742
  return current().respellReferences(kind, id, edits);
31588
31743
  }
31744
+ function assertSpecsInReach(targets, what, projectRung = false) {
31745
+ if (!getWriteReach()) return;
31746
+ const ws = current();
31747
+ const writable = ["system", "subsystem", "component", "interface", "implementation", "type"];
31748
+ const owners = targets.map((t) => ws.specOwner(t.kind, t.kind === "system" ? null : (writable.includes(t.kind) ? ws.load(t.kind, t.id) : null) ?? { id: t.id }));
31749
+ if (projectRung) owners.push(ws.specOwner("system", null));
31750
+ assertSpecWritesPermitted(owners, what);
31751
+ }
31589
31752
  function moveMethods(from, to, methods, hooks, dryRun) {
31590
31753
  return current().moveMethods(from, to, methods, hooks, dryRun);
31591
31754
  }
@@ -33151,7 +33314,7 @@ function restateReExports(from, to) {
33151
33314
  publicInterfaces: entries.map((e) => e.from === from ? { ...e, from: to } : e)
33152
33315
  });
33153
33316
  }
33154
- function rewriteRefFields(specsDir, remap, excludeDir) {
33317
+ function rewriteRefFields(specsDir, remap, excludeDir, dryRun = false) {
33155
33318
  const rewritten = [];
33156
33319
  for (const file of listFilesRecursive(specsDir, ".yaml")) {
33157
33320
  if (excludeDir && isWithinDir2(excludeDir, file)) continue;
@@ -33164,12 +33327,12 @@ function rewriteRefFields(specsDir, remap, excludeDir) {
33164
33327
  if (!raw || typeof raw !== "object") continue;
33165
33328
  const kind = specKind(raw);
33166
33329
  if (!rewriteSpecRefs(raw, remap) || !kind) continue;
33167
- writeYamlFile(file, raw);
33330
+ if (!dryRun) writeYamlFile(file, raw);
33168
33331
  rewritten.push({ kind, id: kind === "system" ? "system" : String(raw.id) });
33169
33332
  }
33170
33333
  return rewritten;
33171
33334
  }
33172
- function rekeyLintAllows(specsDir, rename) {
33335
+ function rekeyLintAllows(specsDir, rename, dryRun = false) {
33173
33336
  const rewritten = [];
33174
33337
  for (const file of listFilesRecursive(specsDir, ".yaml")) {
33175
33338
  let raw;
@@ -33200,7 +33363,7 @@ function rekeyLintAllows(specsDir, rename) {
33200
33363
  }
33201
33364
  }
33202
33365
  if (!changed) continue;
33203
- writeYamlFile(file, raw);
33366
+ if (!dryRun) writeYamlFile(file, raw);
33204
33367
  rewritten.push({ kind, id: specId });
33205
33368
  }
33206
33369
  return rewritten;
@@ -33278,15 +33441,22 @@ function renameComponent(componentId, newId) {
33278
33441
  specs: renamed.map(({ from, to }) => ({ from, to })),
33279
33442
  components: [{ from: componentId, to: newId }]
33280
33443
  };
33281
- projectConfigRepository.rekeyCarried(rename, true);
33282
- const rewrittenRefs = rewriteRefFields(aiPathsAt(getProjectRoot()).specsDir(), (ref, position) => {
33444
+ const registerEdits = projectConfigRepository.rekeyCarried(rename, true);
33445
+ const remap = (ref, position) => {
33283
33446
  if (position === "component" || position === "entity-class" || position === "auth-source") {
33284
33447
  return ref === componentId ? newId : ref;
33285
33448
  }
33286
33449
  if (position === "interface" && ref === movingInterface?.id) return interfaceId;
33287
33450
  return ref;
33288
- });
33289
- const allowsRekeyed = rekeyLintAllows(aiPathsAt(getProjectRoot()).specsDir(), rename);
33451
+ };
33452
+ const specsDir = aiPathsAt(getProjectRoot()).specsDir();
33453
+ assertSpecsInReach(
33454
+ [...renamed.map(({ kind, from }) => ({ kind, id: from })), ...rewriteRefFields(specsDir, remap, void 0, true), ...rekeyLintAllows(specsDir, rename, true)],
33455
+ `renaming component "${componentId}"`,
33456
+ Array.isArray(registerEdits) && registerEdits.length > 0
33457
+ );
33458
+ const rewrittenRefs = rewriteRefFields(specsDir, remap);
33459
+ const allowsRekeyed = rekeyLintAllows(specsDir, rename);
33290
33460
  const rewritten = [...new Set([...rewrittenRefs, ...allowsRekeyed].filter((spec) => !renamed.some((moved) => moved.kind === spec.kind && moved.from === spec.id)).map((spec) => spec.id))];
33291
33461
  invalidateSpecCache();
33292
33462
  moveRenamedSpecs(componentId, newId, component, movingInterface, movingImplementation);
@@ -33376,7 +33546,18 @@ function renameMethod(componentId, methodName, newName, pinSymbol) {
33376
33546
  ]
33377
33547
  }]
33378
33548
  };
33379
- projectConfigRepository.rekeyCarried(rename, true);
33549
+ const registerEdits = projectConfigRepository.rekeyCarried(rename, true);
33550
+ const methodRemap = (ref, position, owner) => position === "method" && owner === componentId && ref === methodName ? newName : ref;
33551
+ const reachDir = aiPathsAt(getProjectRoot()).specsDir();
33552
+ assertSpecsInReach(
33553
+ [
33554
+ ...(rename.methods?.[0]?.specs ?? []).map((id) => ({ kind: moving.some((i) => i.id === id) ? "interface" : "implementation", id })),
33555
+ ...rewriteRefFields(reachDir, methodRemap, void 0, true),
33556
+ ...rekeyLintAllows(reachDir, rename, true)
33557
+ ],
33558
+ `renaming method "${componentId}.${methodName}"`,
33559
+ Array.isArray(registerEdits) && registerEdits.length > 0
33560
+ );
33380
33561
  for (const contract of moving) {
33381
33562
  saveInterfaceSpec({
33382
33563
  ...contract,
@@ -33399,7 +33580,7 @@ function renameMethod(componentId, methodName, newName, pinSymbol) {
33399
33580
  }
33400
33581
  invalidateSpecCache();
33401
33582
  const specsDir = aiPathsAt(getProjectRoot()).specsDir();
33402
- const retargeted = rewriteRefFields(specsDir, (ref, position, owner) => position === "method" && owner === componentId && ref === methodName ? newName : ref);
33583
+ const retargeted = rewriteRefFields(specsDir, methodRemap);
33403
33584
  const allowsRekeyed = rekeyLintAllows(specsDir, rename);
33404
33585
  const rewritten = [...new Set([...retargeted, ...allowsRekeyed].map((spec) => spec.id))];
33405
33586
  const mentions = collectMentions(specsDir, methodName, moving);
@@ -33789,16 +33970,16 @@ var path28 = __toESM(require("path"));
33789
33970
  function builtinTemplatesDir() {
33790
33971
  return path28.resolve(__dirname, "..", "templates");
33791
33972
  }
33792
- function loadTemplate(id, globalOverride) {
33793
- const dirs = templateSearchDirs(globalOverride);
33973
+ function loadTemplate(templateName, globalTemplatesDir2) {
33974
+ const dirs = templateSearchDirs(globalTemplatesDir2);
33794
33975
  for (const dir of dirs) {
33795
- const filePath = path28.join(dir, `${id}.yaml`);
33976
+ const filePath = path28.join(dir, `${templateName}.yaml`);
33796
33977
  if (pathExists(filePath)) {
33797
33978
  const raw = readYamlFile(filePath);
33798
- return { ...TemplateSchema.parse(raw), templateName: id };
33979
+ return { ...TemplateSchema.parse(raw), templateName };
33799
33980
  }
33800
33981
  }
33801
- throw new TemplateNotFoundError(id);
33982
+ throw new TemplateNotFoundError(templateName);
33802
33983
  }
33803
33984
  function loadAgentOverride(agentId) {
33804
33985
  const filePath = path28.join(AI_PATHS.root(), "agents", `${agentId}.md`);
@@ -34081,7 +34262,7 @@ function inferSourcePathForComponent(comp, subsystems) {
34081
34262
  return null;
34082
34263
  }
34083
34264
  }
34084
- function summarize2(text3, max = 140) {
34265
+ function summarize(text3, max = 140) {
34085
34266
  const clean = text3.replace(/\s+/g, " ").trim();
34086
34267
  const period = clean.indexOf(". ");
34087
34268
  const firstSentence = period > 0 ? clean.slice(0, period + 1) : clean;
@@ -34136,7 +34317,7 @@ function resolveLayer(delegate) {
34136
34317
  agents.push({
34137
34318
  id: "system-architect",
34138
34319
  name: `${system.name} Architect`,
34139
- description: `Global architect for ${system.name} \u2014 owns the spec tree and topology. ${summarize2(system.vision)}`,
34320
+ description: `Global architect for ${system.name} \u2014 owns the spec tree and topology. ${summarize(system.vision)}`,
34140
34321
  template: "architect",
34141
34322
  creationReason: "Automatically inferred from L0 system spec",
34142
34323
  ownedPaths: [".wai/specs/**"],
@@ -34220,7 +34401,7 @@ function resolveLayer(delegate) {
34220
34401
  agents.push({
34221
34402
  id: `${sub.id}-owner`,
34222
34403
  name: `${sub.name} Owner`,
34223
- description: `Owns the ${sub.id} subsystem. ${summarize2(sub.description)}${partNote ? ` ${partNote}` : ""}`,
34404
+ description: `Owns the ${sub.id} subsystem. ${summarize(sub.description)}${partNote ? ` ${partNote}` : ""}`,
34224
34405
  template: "domain-owner",
34225
34406
  creationReason: `Automatically inferred from L1 subsystem spec: ${sub.id}`,
34226
34407
  domainRoot: sub.id,
@@ -34285,7 +34466,7 @@ function resolveLayer(delegate) {
34285
34466
  agents.push({
34286
34467
  id: `${dom.id}-owner`,
34287
34468
  name: `${dom.name ?? dom.id} Owner`,
34288
- description: dom.description ? summarize2(dom.description) : `Owner agent for the free-standing "${dom.id}" domain.`,
34469
+ description: dom.description ? summarize(dom.description) : `Owner agent for the free-standing "${dom.id}" domain.`,
34289
34470
  template: "domain-owner",
34290
34471
  creationReason: "Inferred from a free-standing domain in .wai/topology.yaml",
34291
34472
  domainRoot: dom.id,
@@ -35152,8 +35333,11 @@ function localGuideFilePath(projectRoot, targetType) {
35152
35333
  if (targetType === "gemini" || targetType === "agy") return path36.join(projectRoot, ".gemini", "GEMINI.md");
35153
35334
  return null;
35154
35335
  }
35336
+ function guideBody(scope) {
35337
+ return scope === "global" ? GLOBAL_GUIDE_BODY : LOCAL_GUIDE_BODY;
35338
+ }
35155
35339
  function injectGuide(filePath, scope) {
35156
- const body = scope === "global" ? GLOBAL_GUIDE_BODY : LOCAL_GUIDE_BODY;
35340
+ const body = guideBody(scope);
35157
35341
  const section = `
35158
35342
 
35159
35343
  ${GUIDE_MARKER_START}
@@ -35362,7 +35546,7 @@ function renderWaironGuide() {
35362
35546
  lines.push("---");
35363
35547
  lines.push("");
35364
35548
  }
35365
- lines.push(GLOBAL_GUIDE_BODY);
35549
+ lines.push(guideBody("global"));
35366
35550
  lines.push("");
35367
35551
  return lines.join("\n");
35368
35552
  }
@@ -35823,22 +36007,6 @@ function packSkillId(skill) {
35823
36007
  function packNamespace(pack) {
35824
36008
  return pack.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
35825
36009
  }
35826
- function extensionsFor(builtin, packSkills) {
35827
- return packSkills.filter((s) => s.extends === builtin && fs22.existsSync(s.sourcePath));
35828
- }
35829
- function composeBuiltinSkill(name, packSkills) {
35830
- const srcPath = skillTemplatePath(name);
35831
- const base = fs22.existsSync(srcPath) ? fs22.readFileSync(srcPath, "utf-8") : "";
35832
- const sections = extensionsFor(name, packSkills);
35833
- if (sections.length === 0) return base;
35834
- const parts = [base.trimEnd()];
35835
- for (const section of sections) {
35836
- const body = fs22.readFileSync(section.sourcePath, "utf-8").replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, "");
35837
- parts.push(`## Platform: ${section.pack}`, body.trim());
35838
- }
35839
- return `${parts.join("\n\n")}
35840
- `;
35841
- }
35842
36010
  function readFrontmatter(raw, fallbackName) {
35843
36011
  const match2 = /^---\r?\n([\s\S]*?)\r?\n---/.exec(raw);
35844
36012
  const block2 = match2 ? match2[1] : "";
@@ -35861,12 +36029,6 @@ function activeTargetTypes2() {
35861
36029
  }
35862
36030
  var SKILL_RESOURCE_SCHEME = "wairon-skill";
35863
36031
  var RESOURCE_SKILL_IDS = [...SKILL_NAMES].sort();
35864
- var SkillResourceNotFoundError = class extends Error {
35865
- constructor(resourceId) {
35866
- super(`Unknown SDD skill resource id: "${resourceId}".`);
35867
- this.name = "SkillResourceNotFoundError";
35868
- }
35869
- };
35870
36032
  function readSkillFrontmatter(name) {
35871
36033
  return readFrontmatter(fs22.readFileSync(skillTemplatePath(name), "utf-8"), name);
35872
36034
  }
@@ -35896,24 +36058,9 @@ function listSkillResources() {
35896
36058
  });
35897
36059
  return [...builtin, ...pack];
35898
36060
  }
35899
- function readSkillResource(resourceId) {
35900
- const packSkills = loadProjectExtensions().skills;
35901
- if (SKILL_NAMES.includes(resourceId)) return composeBuiltinSkill(resourceId, packSkills);
35902
- const packSkill = packSkills.find((s) => s.extends === void 0 && packSkillId(s) === resourceId);
35903
- if (packSkill) return fs22.readFileSync(packSkill.sourcePath, "utf-8");
35904
- return fs22.readFileSync(skillTemplatePath(resourceId), "utf-8");
35905
- }
35906
- function listResources() {
35907
- return listSkillResources();
35908
- }
35909
36061
  function buildServerInstructions2() {
35910
36062
  return buildServerInstructions();
35911
36063
  }
35912
- function readResource(resourceId) {
35913
- const known = listSkillResources().some((d) => d.id === resourceId);
35914
- if (!known) throw new SkillResourceNotFoundError(resourceId);
35915
- return readSkillResource(resourceId);
35916
- }
35917
36064
 
35918
36065
  // src/mcp/server.ts
35919
36066
  var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
@@ -39438,10 +39585,13 @@ function rehearse2(plan6) {
39438
39585
  return { ...plan6, refusals: [...plan6.refusals, { code: `${plan6.request.verb}-refused`, project: "", detail }] };
39439
39586
  }
39440
39587
  const changes = diff(rehearsal);
39441
- const outside = unwritableOwners(changes);
39442
- if (outside.length > 0) {
39588
+ const refused2 = [
39589
+ ...unwritableOwners(changes).map((o) => ({ code: "family-partial", project: o, detail: `the plan must write ${o}, which this request may not write` })),
39590
+ ...deniedSubsystems(changes)
39591
+ ];
39592
+ if (refused2.length > 0) {
39443
39593
  discard(rehearsal);
39444
- return { ...plan6, refusals: [...plan6.refusals, ...outside.map((o) => ({ code: "family-partial", project: o, detail: `the plan must write ${o}, which this request may not write` }))] };
39594
+ return { ...plan6, refusals: [...plan6.refusals, ...refused2] };
39445
39595
  }
39446
39596
  return { ...plan6, rehearsal, changes, relock: relockOf(plan6, changes) };
39447
39597
  }
@@ -39485,6 +39635,16 @@ function unwritableOwners(changes) {
39485
39635
  const owners = [...new Set(changes.map((c) => path47.resolve(c.project)))];
39486
39636
  return owners.filter((o) => denied.includes(o));
39487
39637
  }
39638
+ function deniedSubsystems(changes) {
39639
+ const reach2 = getWriteReach();
39640
+ if (!reach2) return [];
39641
+ const owners = [...new Set(changes.map((c) => path47.resolve(c.project)))];
39642
+ return owners.flatMap((o) => writeReachDenials(reach2, o).map((s) => ({
39643
+ code: "subsystem-denied",
39644
+ project: o,
39645
+ detail: `the plan must change ${o}, which holds subsystem "${s.subsystemId}" this request may not change (decided by ${s.decidedBy}); a family migration reshapes its projects whole, so it needs write on every subsystem of every project it changes`
39646
+ })));
39647
+ }
39488
39648
  function pathProducers(plan6, family) {
39489
39649
  const inside = (dir) => {
39490
39650
  const rel2 = path47.relative(plan6.familyRoot, dir);
@@ -40099,7 +40259,7 @@ function schemaFingerprint() {
40099
40259
  implementation: schemaShape(ImplementationSpecSchema),
40100
40260
  type: schemaShape(TypeSpecSchema)
40101
40261
  };
40102
- const registered = createMcpServer()._registeredTools;
40262
+ const registered = createBareMcpServer()._registeredTools;
40103
40263
  const tools = [...SPEC_WRITE_TOOLS].sort().map((name) => [name, schemaShape(registered[name]?.inputSchema)]);
40104
40264
  runningFingerprint = crypto10.createHash("sha256").update(JSON.stringify({ version: FINGERPRINT_VERSION, specs, tools })).digest("hex");
40105
40265
  return runningFingerprint;
@@ -40203,113 +40363,7 @@ function reg(server, name, config, cb) {
40203
40363
  var statusInput = import_zod11.z.enum(STATUS_VALUES).optional().describe(
40204
40364
  "The spec's lifecycle status. Omitted means draft for a NEW spec and the status already stored for a re-authoring, so a restatement never reopens a frozen spec. State it to author straight at design or complete instead of promoting afterwards. A status that would LOWER the stored one is refused \u2014 reopening a spec for revision is sdd_update_spec's job, which sets the demotion deliberately."
40205
40365
  );
40206
- var SKILL_RESOURCE_MIME = "text/markdown";
40207
- function skillIdFromResourceUri(uri) {
40208
- try {
40209
- const u = new URL(uri);
40210
- return u.host || u.pathname.replace(/^\/+/, "") || uri;
40211
- } catch {
40212
- return uri;
40213
- }
40214
- }
40215
- var AGENT_BRIEF_SCHEME = "wairon-agent://";
40216
- function listAgentBriefResources() {
40217
- try {
40218
- return resolveAgentTopology().map((a) => ({
40219
- uri: `${AGENT_BRIEF_SCHEME}${a.id}`,
40220
- name: a.name,
40221
- description: a.description,
40222
- mimeType: SKILL_RESOURCE_MIME
40223
- }));
40224
- } catch {
40225
- return [];
40226
- }
40227
- }
40228
- function renderAgentBriefMarkdown(brief) {
40229
- const lines = [
40230
- `# Agent brief: ${brief.agentId}`,
40231
- "",
40232
- `- **Name**: ${brief.name}`,
40233
- `- **Template**: ${brief.template}`,
40234
- ...brief.domainRoot ? [`- **Domain root**: ${brief.domainRoot}`] : [],
40235
- `- **Owned paths**: ${brief.ownedPaths.join(", ")}`,
40236
- ...brief.readPaths?.length ? [`- **Read paths**: ${brief.readPaths.join(", ")}`] : [],
40237
- "",
40238
- "## Instructions",
40239
- "",
40240
- brief.instructions
40241
- ];
40242
- if (brief.variantGuidance && !brief.instructions.includes(brief.variantGuidance)) {
40243
- lines.push("", "## Variant guidance", "", brief.variantGuidance);
40244
- }
40245
- if (brief.budget && brief.profile) {
40246
- lines.push(
40247
- "",
40248
- "## Execution budget",
40249
- "",
40250
- ...summarize(brief.budget, brief.profile),
40251
- "",
40252
- "Advisory \u2014 apply these when spawning. Map the capability tier onto your host tool's models; a tool that cannot express a field should ignore it rather than approximate it."
40253
- );
40254
- }
40255
- return `${lines.join("\n")}
40256
- `;
40257
- }
40258
- function registerSkillResources(server, listChanged) {
40259
- server.server.registerCapabilities({ resources: listChanged ? { listChanged: true } : {} });
40260
- server.server.setRequestHandler(import_types12.ListResourcesRequestSchema, () => ({
40261
- resources: [
40262
- ...listResources().map((d) => ({
40263
- uri: d.resourceUri,
40264
- name: d.name,
40265
- description: d.description,
40266
- mimeType: SKILL_RESOURCE_MIME
40267
- })),
40268
- ...listAgentBriefResources()
40269
- ]
40270
- }));
40271
- server.server.setRequestHandler(import_types12.ReadResourceRequestSchema, (request) => {
40272
- const uri = request.params.uri;
40273
- if (uri.startsWith(AGENT_BRIEF_SCHEME)) {
40274
- try {
40275
- const brief = composeAgentBrief(uri.slice(AGENT_BRIEF_SCHEME.length));
40276
- return { contents: [{ uri, mimeType: SKILL_RESOURCE_MIME, text: renderAgentBriefMarkdown(brief) }] };
40277
- } catch (e) {
40278
- throw new import_types12.McpError(import_types12.ErrorCode.InvalidParams, e instanceof Error ? e.message : String(e));
40279
- }
40280
- }
40281
- try {
40282
- const content = readResource(skillIdFromResourceUri(uri));
40283
- return { contents: [{ uri, mimeType: SKILL_RESOURCE_MIME, text: content }] };
40284
- } catch (e) {
40285
- const message = e instanceof Error ? e.message : String(e);
40286
- throw new import_types12.McpError(import_types12.ErrorCode.InvalidParams, message);
40287
- }
40288
- });
40289
- }
40290
- function registerSkillPrompts(server, listChanged) {
40291
- server.server.registerCapabilities({ prompts: listChanged ? { listChanged: true } : {} });
40292
- server.server.setRequestHandler(import_types12.ListPromptsRequestSchema, () => ({
40293
- prompts: listResources().map((d) => ({
40294
- name: d.id,
40295
- title: d.name,
40296
- description: d.description
40297
- }))
40298
- }));
40299
- server.server.setRequestHandler(import_types12.GetPromptRequestSchema, (request) => {
40300
- const name = request.params.name;
40301
- try {
40302
- const content = readResource(name);
40303
- return {
40304
- description: `The wairon "${name}" skill.`,
40305
- messages: [{ role: "user", content: { type: "text", text: content } }]
40306
- };
40307
- } catch (e) {
40308
- throw new import_types12.McpError(import_types12.ErrorCode.InvalidParams, e instanceof Error ? e.message : String(e));
40309
- }
40310
- });
40311
- }
40312
- function createMcpServer(options = {}) {
40366
+ function createBareMcpServer(options = {}) {
40313
40367
  const server = new import_mcp.McpServer({
40314
40368
  name: "wairon",
40315
40369
  version: WAIRON_VERSION
@@ -41489,8 +41543,6 @@ ${testsBlock}` : ""}`, deletion);
41489
41543
  }
41490
41544
  }
41491
41545
  );
41492
- registerSkillResources(server, !options.hostedTools);
41493
- registerSkillPrompts(server, !options.hostedTools);
41494
41546
  try {
41495
41547
  const chainingParent = resolveChainingParent();
41496
41548
  if (chainingParent) {
@@ -41501,105 +41553,8 @@ ${testsBlock}` : ""}`, deletion);
41501
41553
  }
41502
41554
  } catch {
41503
41555
  }
41504
- advertiseHostedTools(server, options);
41505
41556
  return server;
41506
41557
  }
41507
- function advertiseHostedTools(server, options) {
41508
- if (options.hostedTools) {
41509
- const hostedStub = () => errText("This hosted tool is dispatched by the hosting data plane before reaching the MCP server; it is unavailable outside a hosted request.");
41510
- reg(server, "sdd_host_lock_project", {
41511
- description: "Hosted project lifecycle (execute-primary): LOCK the bound project (validate-as-complete gate + commit-scoped lock record). Executes directly when your resolved permission is yes and returns the completed outcome; when it is approval, a pending approval request is created instead (await it with sdd_host_await_approval).",
41512
- inputSchema: {}
41513
- }, hostedStub);
41514
- reg(server, "sdd_host_initialize_project", {
41515
- description: "Hosted project lifecycle (execute-primary): initialize a new hosted project into its REQUIRED owner organization unit (with an optional profile selection). Executes directly when your resolved permission is yes; when it is approval, a pending approval request is created instead.",
41516
- inputSchema: {
41517
- id: import_zod11.z.string().describe("Requested project id"),
41518
- displayName: import_zod11.z.string().optional(),
41519
- description: import_zod11.z.string().optional(),
41520
- ownerUnitId: import_zod11.z.string().describe("REQUIRED organization unit that owns the new project (every project is placed at creation)"),
41521
- environment: import_zod11.z.string().optional()
41522
- }
41523
- }, hostedStub);
41524
- reg(server, "sdd_host_get_approval_status", {
41525
- description: "Hosted project lifecycle: read the status of one of your approval requests.",
41526
- inputSchema: { requestId: import_zod11.z.string() }
41527
- }, hostedStub);
41528
- reg(server, "sdd_host_await_approval", {
41529
- description: "Hosted project lifecycle: long-poll one of YOUR approval requests until it is decided (approved requests auto-execute, so an approval resolves as completed) or the timeout elapses. Returns the request in its current state.",
41530
- inputSchema: {
41531
- requestId: import_zod11.z.string(),
41532
- timeoutSeconds: import_zod11.z.number().optional().describe("How long to wait server-side (clamped; 0 returns the current state immediately)")
41533
- }
41534
- }, hostedStub);
41535
- reg(server, "sdd_landscape_list_reachable_projects", {
41536
- description: "Hosted landscape: the projects reachable from the BOUND project through ACTIVE cross-project relations (directional, relations-only), each with the relation ids/kinds and target public interface ids.",
41537
- inputSchema: {}
41538
- }, hostedStub);
41539
- reg(server, "sdd_landscape_list_reachable_project_interfaces", {
41540
- description: "Hosted landscape: redacted, audience-filtered public interface summaries of one reachable target project (Forbidden outside the reachable set; private by default).",
41541
- inputSchema: { projectId: import_zod11.z.string().describe("The reachable target project id") }
41542
- }, hostedStub);
41543
- reg(server, "sdd_landscape_list_visible_surfaces", {
41544
- description: "Hosted landscape: the visibility-resolved discovery catalog for the BOUND project \u2014 every target the organization unit graph exposes to it (open-within-tenant, closed groups hidden, exposeTo grants honored), with audience distance and audience-filtered summaries. No relation required.",
41545
- inputSchema: {}
41546
- }, hostedStub);
41547
- reg(server, "sdd_landscape_get_project_surface", {
41548
- description: `Hosted landscape: fetch a visible target project's CONTRACT-GRADE surface snapshot (full method contracts, dispatch tables, type closure) at your audience-distance ceiling, origin "exchanged" \u2014 save it under .wai/surfaces/ (wairon surface import) so your adapters validate against the declared contract.`,
41549
- inputSchema: { projectId: import_zod11.z.string().describe("The visible target project id") }
41550
- }, hostedStub);
41551
- reg(server, "sdd_host_pack_list", {
41552
- description: "Hosted project ops: list the BOUND project's registered extension packs (requires project:read over it).",
41553
- inputSchema: {}
41554
- }, hostedStub);
41555
- reg(server, "sdd_host_pack_install", {
41556
- description: "Hosted project ops: install a DECLARATIVE extension pack (profiles + language/platform tables \u2014 never rule/code packs) into the BOUND project (requires project:admin over it).",
41557
- inputSchema: {
41558
- name: import_zod11.z.string().describe("Pack name (letters, digits, dot, underscore, hyphen)"),
41559
- content: import_zod11.z.string().describe("The declarative pack YAML content")
41560
- }
41561
- }, hostedStub);
41562
- reg(server, "sdd_host_pack_impact", {
41563
- description: "Hosted project ops: measure, writing nothing, what installing a DECLARATIVE pack (content given) or adopting the server-global pack of that name (content omitted) would change on the BOUND project \u2014 the pack's doctrine against wairon's defaults (rules loosened, raised, turned off or added; profiles and concepts added, redefined or removed), which of its profiles would govern, and the findings that change. Show it to your human before sdd_host_pack_install, which applies without it. Nothing in it is a finding: packs exist to adjust wairon's checks. Requires project:read over the project.",
41564
- inputSchema: {
41565
- name: import_zod11.z.string().describe("Pack name (letters, digits, dot, underscore, hyphen); the server-global pack to adopt when content is omitted"),
41566
- content: import_zod11.z.string().optional().describe("The declarative pack YAML an install would upload; omitted, the server-global pack an adoption would vendor")
41567
- }
41568
- }, hostedStub);
41569
- reg(server, "sdd_host_policy_evaluate", {
41570
- description: "Hosted project ops: evaluate the BOUND project's packs and profile selection against the instance pack policy \u2014 compliance without side effects (requires project:write over it).",
41571
- inputSchema: {}
41572
- }, hostedStub);
41573
- reg(server, "sdd_host_policy_reconcile", {
41574
- description: "Hosted project ops: reconcile the BOUND project against the instance pack policy \u2014 under auto_reconcile enforcement the missing required/default declarative packs are applied (requires project:write over it).",
41575
- inputSchema: {}
41576
- }, hostedStub);
41577
- reg(server, "sdd_host_produce", {
41578
- description: "Hosted project ops: run a configured producer projection (Notion/Miro) of the BOUND project to the named target (requires project:admin over it).",
41579
- inputSchema: { target: import_zod11.z.string().describe("The configured producer target (e.g. notion, miro)") }
41580
- }, hostedStub);
41581
- reg(server, "sdd_host_commit_project", {
41582
- description: "Hosted project ops: publish a DELIBERATE, .wai/-scoped commit+push of the BOUND project's specs to its bound repository (commit = local save, push = the actual backup; a clean scope publishes nothing). The data plane binds ONE project, so there is no project argument. Requires project:write over it.",
41583
- inputSchema: {
41584
- subsystem: import_zod11.z.string().optional().describe("Narrow staging to .wai/specs/<subsystem>/ (a convenience \u2014 git history stays per-repo)"),
41585
- message: import_zod11.z.string().optional().describe("Commit message; defaults to a timestamped wairon message")
41586
- }
41587
- }, hostedStub);
41588
- reg(server, "sdd_host_export_tree", {
41589
- 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.",
41590
- inputSchema: {
41591
- allowPartial: import_zod11.z.boolean().optional().describe("Build the archive even when some members cannot be packed, listing them as skipped; default false (refuse and name them)")
41592
- }
41593
- }, hostedStub);
41594
- reg(server, "sdd_host_import_tree", {
41595
- 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.",
41596
- inputSchema: {
41597
- archiveBase64: import_zod11.z.string().describe("The .waitree archive bytes, base64-encoded"),
41598
- replaceExisting: import_zod11.z.boolean().optional().describe("Replace a tree already present (backed up first); without it an occupied destination is refused")
41599
- }
41600
- }, hostedStub);
41601
- }
41602
- }
41603
41558
 
41604
41559
  // src/index.ts
41605
41560
  function loadProjectConfig3() {
@@ -41804,6 +41759,7 @@ function loadProjectConfig3() {
41804
41759
  deregisterPackRef,
41805
41760
  deriveExecutionProfile,
41806
41761
  derivedDocPaths,
41762
+ describeProject,
41807
41763
  detectDomainCandidates,
41808
41764
  diagnoseProjectPacks,
41809
41765
  diagramSetIndex,
@@ -41845,6 +41801,7 @@ function loadProjectConfig3() {
41845
41801
  getRequestParentReach,
41846
41802
  getRequestProjectRoot,
41847
41803
  getStatusReport,
41804
+ getWriteReach,
41848
41805
  globalGuideFilePath,
41849
41806
  globalPacksDir,
41850
41807
  globalPacksEnabled,
@@ -42016,6 +41973,8 @@ function loadProjectConfig3() {
42016
41973
  writeFileIfChanged,
42017
41974
  writeJsonFile,
42018
41975
  writeLockRecord,
41976
+ writeReachDenials,
41977
+ writeReachPermits,
42019
41978
  writeRootGuideDelegator,
42020
41979
  writeYamlFile
42021
41980
  });