@wairon/cli 5.0.2-dev.13 → 5.0.2-dev.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -65,7 +65,7 @@ var init_defaults = __esm({
65
65
  copilot: ".github/prompts",
66
66
  codex: ".codex/agents"
67
67
  };
68
- WAIRON_VERSION = "5.0.2-dev.13";
68
+ WAIRON_VERSION = "5.0.2-dev.14";
69
69
  GITHUB_REPO = "SYW-Apps/Waffle-AIron";
70
70
  ARCHITECT_AGENT_ID = "agent-architect";
71
71
  ARCHITECT_TEMPLATE_ID = "architect";
@@ -1914,6 +1914,47 @@ var init_loader = __esm({
1914
1914
  }
1915
1915
  });
1916
1916
 
1917
+ // src/core/statehash.ts
1918
+ function computeStateId() {
1919
+ const tree = {
1920
+ system: loadSystemSpec(),
1921
+ subsystems: loadSubsystemSpecs(),
1922
+ components: loadComponentSpecs(),
1923
+ interfaces: loadInterfaceSpecs(),
1924
+ implementations: loadImplementationSpecs(),
1925
+ types: loadTypeSpecs()
1926
+ };
1927
+ const digest = crypto.createHash("sha256").update(canonicalize(tree)).digest("hex");
1928
+ return { algorithm: "sha256", digest };
1929
+ }
1930
+ function stateIdEquals(a, b) {
1931
+ return !!a && !!b && a.algorithm === b.algorithm && a.digest === b.digest;
1932
+ }
1933
+ function canonicalize(value) {
1934
+ return JSON.stringify(sortKeys(value));
1935
+ }
1936
+ function sortKeys(v) {
1937
+ if (Array.isArray(v)) return v.map(sortKeys);
1938
+ if (v && typeof v === "object") {
1939
+ const src = v;
1940
+ const out = {};
1941
+ for (const k of Object.keys(src).sort()) {
1942
+ if (k === "createdAt" || k === "updatedAt") continue;
1943
+ out[k] = sortKeys(src[k]);
1944
+ }
1945
+ return out;
1946
+ }
1947
+ return v;
1948
+ }
1949
+ var crypto;
1950
+ var init_statehash = __esm({
1951
+ "src/core/statehash.ts"() {
1952
+ "use strict";
1953
+ crypto = __toESM(require("crypto"));
1954
+ init_specs2();
1955
+ }
1956
+ });
1957
+
1917
1958
  // src/core/narrative-labels.ts
1918
1959
  function resolveNarrativeLabels(methodName, steps) {
1919
1960
  const errors = [];
@@ -7475,47 +7516,6 @@ var init_filenames = __esm({
7475
7516
  }
7476
7517
  });
7477
7518
 
7478
- // src/core/statehash.ts
7479
- function computeStateId() {
7480
- const tree = {
7481
- system: loadSystemSpec(),
7482
- subsystems: loadSubsystemSpecs(),
7483
- components: loadComponentSpecs(),
7484
- interfaces: loadInterfaceSpecs(),
7485
- implementations: loadImplementationSpecs(),
7486
- types: loadTypeSpecs()
7487
- };
7488
- const digest = crypto.createHash("sha256").update(canonicalize(tree)).digest("hex");
7489
- return { algorithm: "sha256", digest };
7490
- }
7491
- function stateIdEquals(a, b) {
7492
- return !!a && !!b && a.algorithm === b.algorithm && a.digest === b.digest;
7493
- }
7494
- function canonicalize(value) {
7495
- return JSON.stringify(sortKeys(value));
7496
- }
7497
- function sortKeys(v) {
7498
- if (Array.isArray(v)) return v.map(sortKeys);
7499
- if (v && typeof v === "object") {
7500
- const src = v;
7501
- const out = {};
7502
- for (const k of Object.keys(src).sort()) {
7503
- if (k === "createdAt" || k === "updatedAt") continue;
7504
- out[k] = sortKeys(src[k]);
7505
- }
7506
- return out;
7507
- }
7508
- return v;
7509
- }
7510
- var crypto;
7511
- var init_statehash = __esm({
7512
- "src/core/statehash.ts"() {
7513
- "use strict";
7514
- crypto = __toESM(require("crypto"));
7515
- init_specs2();
7516
- }
7517
- });
7518
-
7519
7519
  // src/core/openapi.ts
7520
7520
  function schemaFor(typeRef, closureIds) {
7521
7521
  const trimmed = typeRef.trim().replace(/^promise\s*<(.+)>$/i, "$1").trim();
@@ -7969,6 +7969,57 @@ function projectOwnSurface(maxAudience) {
7969
7969
  function projectChildSurface() {
7970
7970
  return projectOwnSurface("project");
7971
7971
  }
7972
+ function localName(id) {
7973
+ return id.split("::").pop();
7974
+ }
7975
+ function projectSubsystemSurface(subsystemId) {
7976
+ const system = loadSystemSpec();
7977
+ if (!system) {
7978
+ throw new Error("Cannot project a subsystem surface: the L0 system spec is missing.");
7979
+ }
7980
+ const subsystems = loadSubsystemSpecs();
7981
+ const target = subsystems.find((s) => s.id === subsystemId);
7982
+ if (!target) {
7983
+ throw new Error(`Cannot project a subsystem surface: subsystem "${subsystemId}" does not exist.`);
7984
+ }
7985
+ const components = loadComponentSpecs();
7986
+ const interfaces = loadInterfaceSpecs();
7987
+ const types = loadTypeSpecs();
7988
+ const entries = [];
7989
+ for (const pub of target.publicInterfaces ?? []) {
7990
+ if (!pub.component) continue;
7991
+ const comp = components.find((c) => c.id === pub.component || c.id === `${subsystemId}::${pub.component}`);
7992
+ if (!comp) continue;
7993
+ if (comp.componentType !== "Portal") continue;
7994
+ const compInterfaces = interfaces.filter((i) => i.component === comp.id && (!pub.interface || i.id === pub.interface || i.id === `${subsystemId}::${pub.interface}`));
7995
+ const methods = compInterfaces.flatMap((i) => i.methods);
7996
+ entries.push({
7997
+ id: localName(pub.interface ?? comp.id),
7998
+ name: comp.name,
7999
+ // Family ceiling: a sibling surface is consumable by the system family only.
8000
+ audience: "project",
8001
+ type: pub.type ?? "Custom",
8002
+ // The snapshot carries the LOCAL portal name — consumers resolve cross-tree
8003
+ // refs by their final segment.
8004
+ component: localName(comp.id),
8005
+ methods,
8006
+ ...comp.dispatch && comp.dispatch.length ? { dispatch: comp.dispatch } : {},
8007
+ // Project the backing Portal's auth + basePath so the codec can emit
8008
+ // OpenAPI security + per-portal servers self-contained from the snapshot.
8009
+ ...comp.auth && comp.auth.scheme !== "none" ? { auth: comp.auth } : {},
8010
+ ...comp.basePath ? { basePath: comp.basePath } : {},
8011
+ details: pub.details ?? ""
8012
+ });
8013
+ }
8014
+ return SurfaceSnapshotSchema.parse({
8015
+ projectName: `${system.name}::${subsystemId}`,
8016
+ origin: "generated",
8017
+ stateId: stateIdString(),
8018
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
8019
+ interfaces: entries,
8020
+ types: computeTypeClosure(entries, types)
8021
+ });
8022
+ }
7972
8023
  function listSnapshots(rootDir = getProjectRoot()) {
7973
8024
  const dir = surfacesDir(rootDir);
7974
8025
  if (!fs9.existsSync(dir)) return [];
@@ -7985,10 +8036,13 @@ function listSnapshots(rootDir = getProjectRoot()) {
7985
8036
  function getSnapshot(projectName, rootDir = getProjectRoot()) {
7986
8037
  return listSnapshots(rootDir).find((s) => s.projectName === projectName) ?? null;
7987
8038
  }
8039
+ function snapshotFilename(projectName) {
8040
+ return `${safeFilenamePart(projectName)}.yaml`;
8041
+ }
7988
8042
  function saveSnapshot(snapshot, rootDir = getProjectRoot()) {
7989
8043
  const dir = surfacesDir(rootDir);
7990
8044
  fs9.mkdirSync(dir, { recursive: true });
7991
- const p = path10.join(dir, `${snapshot.projectName}.yaml`);
8045
+ const p = path10.join(dir, snapshotFilename(snapshot.projectName));
7992
8046
  writeYamlFile(p, SurfaceSnapshotSchema.parse(snapshot));
7993
8047
  return p;
7994
8048
  }
@@ -8061,17 +8115,54 @@ function importSurface(sourcePath, origin) {
8061
8115
  return snapshot;
8062
8116
  }
8063
8117
  function generateChildSnapshots(rootDir = getProjectRoot()) {
8064
- const children = loadSubsystemSpecs().filter((s) => s.projectPath && !s.id.includes("::"));
8118
+ const topLevel = loadSubsystemSpecs().filter((s) => !s.id.includes("::"));
8119
+ const children = topLevel.filter((s) => s.projectPath);
8065
8120
  if (!children.length) return [];
8066
- const snapshot = projectChildSurface();
8121
+ const familySnapshot = projectChildSurface();
8122
+ const siblingSnapshots = /* @__PURE__ */ new Map();
8123
+ const siblingSurface = (subsystemId) => {
8124
+ let snap = siblingSnapshots.get(subsystemId);
8125
+ if (!snap) {
8126
+ snap = projectSubsystemSurface(subsystemId);
8127
+ siblingSnapshots.set(subsystemId, snap);
8128
+ }
8129
+ return snap;
8130
+ };
8067
8131
  const written = [];
8068
8132
  for (const child of children) {
8069
8133
  const childDir = path10.resolve(rootDir, child.projectPath);
8070
8134
  if (!fs9.existsSync(childDir)) continue;
8071
- written.push(saveSnapshot(snapshot, childDir));
8135
+ written.push(saveSnapshot(familySnapshot, childDir));
8136
+ for (const sibling of topLevel) {
8137
+ if (sibling.id === child.id) continue;
8138
+ written.push(saveSnapshot(siblingSurface(sibling.id), childDir));
8139
+ }
8072
8140
  }
8073
8141
  return written;
8074
8142
  }
8143
+ function computeParentStateId(parentRoot) {
8144
+ return computeStateIdAt(parentRoot);
8145
+ }
8146
+ function listExternalInterfaces() {
8147
+ const snapshots = listSnapshots();
8148
+ const chainingParent = resolveChainingParent();
8149
+ const parentStateId = chainingParent ? computeParentStateId(chainingParent.parentRoot) : null;
8150
+ return snapshots.map((snapshot) => {
8151
+ const generated = snapshot.origin === "generated";
8152
+ const sourceKind = !generated ? "foreign" : snapshot.projectName.includes("::") ? "sibling" : "parent";
8153
+ const freshness = generated && parentStateId ? snapshot.stateId === parentStateId ? "fresh" : "stale" : "unverifiable";
8154
+ return {
8155
+ projectName: snapshot.projectName,
8156
+ origin: snapshot.origin,
8157
+ sourceKind,
8158
+ generatedAt: snapshot.generatedAt,
8159
+ ...snapshot.stateId ? { stateId: snapshot.stateId } : {},
8160
+ ...snapshot.version ? { version: snapshot.version } : {},
8161
+ freshness,
8162
+ interfaceIds: snapshot.interfaces.map((e) => e.id)
8163
+ };
8164
+ });
8165
+ }
8075
8166
  function surfaceContentKey(snapshot) {
8076
8167
  const { stateId, generatedAt, origin, ...content } = snapshot;
8077
8168
  return JSON.stringify(content);
@@ -8280,7 +8371,8 @@ var init_contracts = __esm({
8280
8371
  "SURFACE_REF_NOT_EXPOSED",
8281
8372
  `Method "${implMethod.name}" in implementation "${impl.id}" dispatches capability "${step.capability}" through cross-tree portal "${step.targetComponent}" (step ${step.stepNumber}), but the surface snapshot of "${resolved.snapshot.projectName}" does not serve that capability on "${resolved.entry.id}".`,
8282
8373
  impl.id,
8283
- isDraftCtx
8374
+ isDraftCtx,
8375
+ true
8284
8376
  );
8285
8377
  }
8286
8378
  continue;
@@ -8337,7 +8429,8 @@ var init_contracts = __esm({
8337
8429
  "SURFACE_REF_NOT_EXPOSED",
8338
8430
  `Method "${implMethod.name}" in implementation "${impl.id}" calls "${step.targetMethod}" on cross-tree component "${step.targetComponent}" (step ${step.stepNumber}), but the surface snapshot of "${resolved.snapshot.projectName}" does not expose that method on "${resolved.entry.id}".`,
8339
8431
  impl.id,
8340
- isDraftCtx
8432
+ isDraftCtx,
8433
+ true
8341
8434
  );
8342
8435
  } else if (step.assertsGuarantees) {
8343
8436
  const declared = new Set(surfaceMethod.guarantees ?? []);
@@ -8348,7 +8441,8 @@ var init_contracts = __esm({
8348
8441
  "NARRATIVE_SEMANTIC_UNBACKED",
8349
8442
  `Step ${step.stepNumber} of "${implMethod.name}" in implementation "${impl.id}" asserts guarantee "${g}", but the surface snapshot of "${resolved.snapshot.projectName}" does not declare it on "${resolved.entry.id}.${step.targetMethod}".`,
8350
8443
  impl.id,
8351
- isDraftCtx
8444
+ isDraftCtx,
8445
+ true
8352
8446
  );
8353
8447
  }
8354
8448
  }
@@ -9836,7 +9930,8 @@ var init_stereotype_deps = __esm({
9836
9930
  "CROSS_SUBSYSTEM_NON_ADAPTER",
9837
9931
  `Boundary violation: ${comp.componentType} "${comp.id}" depends directly on "${depId}", a surface of project "${resolved.snapshot.projectName}". Only a local client Adapter may cross a project boundary \u2014 route this hop through an Adapter.`,
9838
9932
  comp.id,
9839
- isDraftCtx
9933
+ isDraftCtx,
9934
+ true
9840
9935
  );
9841
9936
  }
9842
9937
  continue;
@@ -11699,11 +11794,11 @@ var init_narrative_antipatterns = __esm({
11699
11794
  const memberEdges = keys.flatMap((k) => (adjacency.get(k) ?? []).filter((e) => inScc.has(e.toKey)));
11700
11795
  if (memberEdges.length === 0) continue;
11701
11796
  const anchor = [...memberEdges].sort((a, b) => a.fromKey.localeCompare(b.fromKey))[0];
11702
- const path62 = [...keys].sort().join(" \u2192 ");
11797
+ const path61 = [...keys].sort().join(" \u2192 ");
11703
11798
  ctx.addIssue(
11704
11799
  "warning",
11705
11800
  "UNCONDITIONAL_CALL_CYCLE",
11706
- `Call cycle with no guard: ${path62} \u2014 every call edge in this cycle is unavoidable on all paths of its narrative (e.g. step ${anchor.stepNumber} of "${anchor.methodName}" in "${anchor.impl.id}" always calls ${anchor.toLabel}). This recurses without a base case, by construction. Guard at least one edge with a branch/return before the call, or lint.allow with the termination argument.`,
11801
+ `Call cycle with no guard: ${path61} \u2014 every call edge in this cycle is unavoidable on all paths of its narrative (e.g. step ${anchor.stepNumber} of "${anchor.methodName}" in "${anchor.impl.id}" always calls ${anchor.toLabel}). This recurses without a base case, by construction. Guard at least one edge with a branch/return before the call, or lint.allow with the termination argument.`,
11707
11802
  anchor.impl.id,
11708
11803
  memberEdges.some((e) => ctx.isImplementationDraft(e.impl))
11709
11804
  );
@@ -13090,9 +13185,15 @@ function buildRuleContext(opts) {
13090
13185
  ...[...SDD_RULES, ...extensions.rules].flatMap((r) => r.codes.map((c) => c.code)),
13091
13186
  // Declarative assertions bring their own namespaced codes — lint.allow
13092
13187
  // and severity overrides treat them exactly like builtins.
13093
- ...extensions.assertions.map((a) => a.fullCode)
13188
+ ...extensions.assertions.map((a) => a.fullCode),
13189
+ // Entry-point emitted codes: validateSddTree's chained-subproject pass
13190
+ // raises these AFTER the rule run (it post-processes the aggregated issue
13191
+ // list), so no registered rule declares them — but lint.allow validation
13192
+ // must still recognize them as real codes.
13193
+ "CHAINED_SUBPROJECT_CONTEXT",
13194
+ "UNVERIFIED_EXTERNAL_REF"
13094
13195
  ]);
13095
- const addIssue = (defaultSeverity, code, message, specId, isDraftContext) => {
13196
+ const addIssue = (defaultSeverity, code, message, specId, isDraftContext, surfaceResolved) => {
13096
13197
  if (scopeSubsystem && specId && !isSpecInScope(specId)) {
13097
13198
  return;
13098
13199
  }
@@ -13110,7 +13211,14 @@ function buildRuleContext(opts) {
13110
13211
  if (severity === "warning") return;
13111
13212
  }
13112
13213
  }
13113
- issues.push({ severity, code, message, specId, ...isDraftContext ? { draftContext: true } : {} });
13214
+ issues.push({
13215
+ severity,
13216
+ code,
13217
+ message,
13218
+ specId,
13219
+ ...isDraftContext ? { draftContext: true } : {},
13220
+ ...surfaceResolved ? { surfaceResolved: true } : {}
13221
+ });
13114
13222
  };
13115
13223
  return {
13116
13224
  system,
@@ -13535,24 +13643,54 @@ function validateSddTree(rulesOrOptions, projectType = "backend") {
13535
13643
  for (const rule of ruleSequence()) {
13536
13644
  rule.check(ctx);
13537
13645
  }
13538
- const hasCrossTreeSuspects = issues.some((i) => SUBPROJECT_LENIENT_CODES.has(i.code));
13646
+ const hasCrossTreeSuspects = issues.some(
13647
+ (i) => SUBPROJECT_REFERENCE_CODES.has(i.code) || SUBPROJECT_CONFORMANCE_CODES.has(i.code)
13648
+ );
13539
13649
  const chainingParent = hasCrossTreeSuspects ? findChainingParent(getProjectRoot()) : null;
13540
13650
  if (chainingParent) {
13651
+ let unverified = 0;
13541
13652
  let downgraded = 0;
13542
- for (const iss of issues) {
13543
- if (!SUBPROJECT_LENIENT_CODES.has(iss.code)) continue;
13544
- if (iss.severity === "error") {
13545
- iss.severity = "warning";
13546
- downgraded++;
13653
+ for (let at = 0; at < issues.length; at++) {
13654
+ const iss = issues[at];
13655
+ if (SUBPROJECT_REFERENCE_CODES.has(iss.code) && !iss.surfaceResolved) {
13656
+ issues[at] = {
13657
+ severity: "warning",
13658
+ code: "UNVERIFIED_EXTERNAL_REF",
13659
+ crossTreeContext: true,
13660
+ // --ci waives it (parent root is authoritative)
13661
+ specId: iss.specId,
13662
+ ...iss.agentId ? { agentId: iss.agentId } : {},
13663
+ ...iss.draftContext ? { draftContext: true } : {},
13664
+ message: `Unverified external reference (${iss.code}): ${iss.message} No vendored surface snapshot covers this reference, so it cannot be verified from this chained subproject standalone \u2014 re-lock the parent so fresh family/sibling snapshots ship, or inspect what this project can consume via \`wairon surface externals\` / sdd_list_external_interfaces.`
13665
+ };
13666
+ unverified++;
13667
+ continue;
13668
+ }
13669
+ if (SUBPROJECT_CONFORMANCE_CODES.has(iss.code)) {
13670
+ if (iss.severity === "error") {
13671
+ iss.severity = "warning";
13672
+ downgraded++;
13673
+ }
13674
+ iss.crossTreeContext = true;
13547
13675
  }
13548
- iss.crossTreeContext = true;
13549
13676
  }
13550
- if (downgraded > 0) {
13677
+ if (unverified > 0 || downgraded > 0) {
13678
+ const notes = [];
13679
+ if (unverified > 0) {
13680
+ notes.push(
13681
+ `${unverified} cross-tree reference(s) have no vendored surface snapshot covering them and were reported as UNVERIFIED_EXTERNAL_REF warnings \u2014 re-lock the parent so fresh family/sibling snapshots ship, or inspect via \`wairon surface externals\` / sdd_list_external_interfaces.`
13682
+ );
13683
+ }
13684
+ if (downgraded > 0) {
13685
+ notes.push(
13686
+ `${downgraded} code\u2194spec conformance finding(s) (parent-root-relative source paths) were downgraded to warnings.`
13687
+ );
13688
+ }
13551
13689
  issues.unshift({
13552
13690
  severity: "warning",
13553
13691
  code: "CHAINED_SUBPROJECT_CONTEXT",
13554
13692
  crossTreeContext: true,
13555
- message: `This project is a chained subproject ("${chainingParent.subsystemId}") of the parent project at "${chainingParent.parentRoot}". ${downgraded} reference(s) resolve only in the parent tree (shared types, sibling subsystems, or cross-tree components that live above this root) and were downgraded to warnings \u2014 validating a subproject standalone cannot verify them. Run validation from the parent root for full cross-tree verification.`
13693
+ message: `This project is a chained subproject ("${chainingParent.subsystemId}") of the parent project at "${chainingParent.parentRoot}". ${notes.join(" ")} Full cross-tree verification runs from the parent root.`
13556
13694
  });
13557
13695
  }
13558
13696
  }
@@ -13569,7 +13707,7 @@ function validateSddTree(rulesOrOptions, projectType = "backend") {
13569
13707
  function validateAsComplete(options) {
13570
13708
  return validateSddTree({ ...options ?? {}, treatAllAsComplete: true });
13571
13709
  }
13572
- var SUBPROJECT_LENIENT_CODES;
13710
+ var SUBPROJECT_REFERENCE_CODES, SUBPROJECT_CONFORMANCE_CODES;
13573
13711
  var init_validation = __esm({
13574
13712
  "src/core/validation.ts"() {
13575
13713
  "use strict";
@@ -13582,8 +13720,7 @@ var init_validation = __esm({
13582
13720
  init_source_analysis();
13583
13721
  init_specs2();
13584
13722
  init_fs();
13585
- SUBPROJECT_LENIENT_CODES = /* @__PURE__ */ new Set([
13586
- // reference resolution
13723
+ SUBPROJECT_REFERENCE_CODES = /* @__PURE__ */ new Set([
13587
13724
  "UNDEFINED_TYPE_REFERENCE",
13588
13725
  "INVALID_DEPENDENCY_REFERENCE",
13589
13726
  "INVALID_TARGET_COMPONENT_REFERENCE",
@@ -13591,8 +13728,9 @@ var init_validation = __esm({
13591
13728
  "UNDECLARED_DEPENDENCY_CALL",
13592
13729
  "INVALID_TRUSTED_LINK",
13593
13730
  "CROSS_SUBSYSTEM_NON_ADAPTER",
13594
- "CROSS_TREE_REF_UNRESOLVED",
13595
- // code↔spec conformance (root-relative sourcePaths / import graph)
13731
+ "CROSS_TREE_REF_UNRESOLVED"
13732
+ ]);
13733
+ SUBPROJECT_CONFORMANCE_CODES = /* @__PURE__ */ new Set([
13596
13734
  "MISSING_SOURCE_FILE",
13597
13735
  "SOURCE_PATH_ESCAPES_ROOT",
13598
13736
  "MISSING_SOURCE_PATH",
@@ -14134,6 +14272,7 @@ __export(specs_exports, {
14134
14272
  buildProjectGraph: () => buildProjectGraph,
14135
14273
  clearLoaderIssues: () => clearLoaderIssues,
14136
14274
  collectPromotableSpecs: () => collectPromotableSpecs,
14275
+ computeStateIdAt: () => computeStateIdAt,
14137
14276
  deleteComponentSpec: () => deleteComponentSpec,
14138
14277
  deleteGroupSpec: () => deleteGroupSpec,
14139
14278
  deleteImplementationSpec: () => deleteImplementationSpec,
@@ -14166,6 +14305,7 @@ __export(specs_exports, {
14166
14305
  loadTypeSpec: () => loadTypeSpec,
14167
14306
  loadTypeSpecs: () => loadTypeSpecs,
14168
14307
  normalizeComponentLayout: () => normalizeComponentLayout,
14308
+ resolveChainingParent: () => resolveChainingParent,
14169
14309
  resolveSubprojectForNamespace: () => resolveSubprojectForNamespace,
14170
14310
  restoreSpecFiles: () => restoreSpecFiles,
14171
14311
  saveComponentSpec: () => saveComponentSpec,
@@ -14570,6 +14710,19 @@ function dryRunSerializeSpecs(include) {
14570
14710
  function buildProjectGraph(level) {
14571
14711
  return buildGraphModel(level);
14572
14712
  }
14713
+ function resolveChainingParent() {
14714
+ return findChainingParent(getProjectRoot());
14715
+ }
14716
+ function computeStateIdAt(root) {
14717
+ const resolved = path14.resolve(root);
14718
+ return runWithProjectRoot(resolved, () => {
14719
+ workspaceFor(resolved).invalidate();
14720
+ const system = loadSystemSpec();
14721
+ if (!system) return null;
14722
+ const s = computeStateId();
14723
+ return `${s.algorithm}:${s.digest}`;
14724
+ });
14725
+ }
14573
14726
  function deleteTypeSpec(id) {
14574
14727
  return current().deleteTypeSpec(id);
14575
14728
  }
@@ -14613,6 +14766,7 @@ var init_specs2 = __esm({
14613
14766
  path14 = __toESM(require("path"));
14614
14767
  init_loader();
14615
14768
  init_fs();
14769
+ init_statehash();
14616
14770
  init_yaml();
14617
14771
  init_models();
14618
14772
  init_narrative_labels();
@@ -17206,6 +17360,9 @@ function requireSpecs() {
17206
17360
  function requireProvision() {
17207
17361
  return init_provision(), __toCommonJS(provision_exports);
17208
17362
  }
17363
+ function listExternalInterfaces2() {
17364
+ return listExternalInterfaces();
17365
+ }
17209
17366
  function text(content) {
17210
17367
  return { content: [{ type: "text", text: content }] };
17211
17368
  }
@@ -18172,7 +18329,36 @@ NOTICE:
18172
18329
  }
18173
18330
  }
18174
18331
  );
18332
+ reg(
18333
+ server,
18334
+ "sdd_list_external_interfaces",
18335
+ {
18336
+ description: "List the bound project's consumable external surfaces (parent family, siblings, foreign imports) as discovery entries with origin, provenance, and freshness \u2014 the tool an agent inside a subproject uses to SEE its outward world instead of discovering it by failed reference resolution. Full contracts stay in the vendored snapshots (.wai/surfaces/); each entry summarizes the interface ids it exposes."
18337
+ },
18338
+ () => {
18339
+ try {
18340
+ return json(listExternalInterfaces2());
18341
+ } catch (e) {
18342
+ return errText(String(e));
18343
+ }
18344
+ }
18345
+ );
18175
18346
  registerSkillResources(server);
18347
+ try {
18348
+ const chainingParent = resolveChainingParent();
18349
+ if (chainingParent) {
18350
+ let externalSurfaceCount = 0;
18351
+ try {
18352
+ externalSurfaceCount = listExternalInterfaces2().length;
18353
+ } catch {
18354
+ }
18355
+ process.stderr.write(
18356
+ `[wairon mcp] chained subproject: this root is mounted as subsystem "${chainingParent.subsystemId}" of the parent project at ${chainingParent.parentRoot} \u2014 ${externalSurfaceCount} vendored external surface(s) discoverable via sdd_list_external_interfaces
18357
+ `
18358
+ );
18359
+ }
18360
+ } catch {
18361
+ }
18176
18362
  if (options.hostedTools) {
18177
18363
  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.");
18178
18364
  reg(server, "sdd_host_lock_project", {
@@ -18308,6 +18494,8 @@ var init_server = __esm({
18308
18494
  init_narrative_labels();
18309
18495
  init_specs();
18310
18496
  init_skills();
18497
+ init_specs2();
18498
+ init_surfaces();
18311
18499
  SERVER_BUILD_STAMP = captureBuildStamp(__filename);
18312
18500
  STALE_SERVER_WARNING = "\n\n\u26A0 STALE SERVER: the wairon build on disk changed after this MCP server started. Restart the MCP session (e.g. /mcp reconnect) before further spec edits \u2014 writes through a stale server can silently drop fields introduced by newer schemas.";
18313
18501
  SKILL_RESOURCE_MIME = "text/markdown";
@@ -21381,7 +21569,7 @@ var require_dist = __commonJS({
21381
21569
  return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
21382
21570
  }
21383
21571
  var fs51 = __toESM2(require("fs"));
21384
- var path62 = __toESM2(require("path"));
21572
+ var path61 = __toESM2(require("path"));
21385
21573
  var import_fflate = require_node();
21386
21574
  var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".hg", ".svn"]);
21387
21575
  function listEntries(archive) {
@@ -21419,8 +21607,8 @@ var require_dist = __commonJS({
21419
21607
  }
21420
21608
  function writeTree(destDir, files) {
21421
21609
  for (const file of files) {
21422
- const absolute = path62.join(destDir, file.path);
21423
- fs51.mkdirSync(path62.dirname(absolute), { recursive: true });
21610
+ const absolute = path61.join(destDir, file.path);
21611
+ fs51.mkdirSync(path61.dirname(absolute), { recursive: true });
21424
21612
  fs51.writeFileSync(absolute, file.contents);
21425
21613
  }
21426
21614
  }
@@ -21433,10 +21621,10 @@ var require_dist = __commonJS({
21433
21621
  for (const entry of fs51.readdirSync(current2, { withFileTypes: true })) {
21434
21622
  if (entry.isDirectory()) {
21435
21623
  if (SKIP_DIRS.has(entry.name)) continue;
21436
- walkPackDir(root, path62.join(current2, entry.name), out);
21624
+ walkPackDir(root, path61.join(current2, entry.name), out);
21437
21625
  } else if (entry.isFile()) {
21438
- const absolute = path62.join(current2, entry.name);
21439
- const relative22 = path62.relative(root, absolute).split(path62.sep).join("/");
21626
+ const absolute = path61.join(current2, entry.name);
21627
+ const relative22 = path61.relative(root, absolute).split(path61.sep).join("/");
21440
21628
  out.push({ path: relative22, contents: fs51.readFileSync(absolute) });
21441
21629
  }
21442
21630
  }
@@ -22847,86 +23035,216 @@ async function generateLayer(options = {}) {
22847
23035
  }
22848
23036
 
22849
23037
  // src/commands/lock.ts
23038
+ var os7 = __toESM(require("os"));
22850
23039
  var import_inquirer2 = __toESM(require("inquirer"));
22851
23040
  init_logger();
22852
- init_loader();
22853
- init_fs();
22854
- init_validation();
22855
- init_specs2();
22856
- async function runLock(options = {}) {
22857
- assertProjectInitialized();
22858
- if (!pathExists(AI_PATHS.specsSystem())) {
22859
- logger.error("No SDD spec tree found (.wai/specs). Nothing to lock.");
22860
- process.exit(1);
23041
+ init_defaults();
23042
+
23043
+ // src/core/detection.ts
23044
+ var fs18 = __toESM(require("fs"));
23045
+ var path27 = __toESM(require("path"));
23046
+ init_defaults();
23047
+ var PACKAGE_MARKERS = [
23048
+ "package.json",
23049
+ "pyproject.toml",
23050
+ "Cargo.toml",
23051
+ "go.mod",
23052
+ "build.gradle",
23053
+ "build.gradle.kts",
23054
+ "pom.xml"
23055
+ ];
23056
+ var MAX_SCAN_DEPTH = 5;
23057
+ function detectDomainCandidates(projectRoot2, alreadyTrackedPaths = /* @__PURE__ */ new Set(), alreadyTrackedIds = /* @__PURE__ */ new Set()) {
23058
+ const candidates = /* @__PURE__ */ new Map();
23059
+ for (const c of detectGitSubmodules(projectRoot2)) {
23060
+ candidates.set(c.path, { ...c, alreadyTracked: alreadyTrackedPaths.has(c.path) });
22861
23061
  }
22862
- const projectConfig = loadProjectConfig();
22863
- logger.info("Analyzing and validating specifications in-memory...");
22864
- const index = scanAllSpecs({ recursive: options.recursive ?? true });
22865
- const promotable = collectPromotableSpecs(options.subsystem);
22866
- const originalStatuses = /* @__PURE__ */ new Map();
22867
- const isSpecInSubsystemScope = (specSubsystem) => {
22868
- if (!options.subsystem) return true;
22869
- if (!specSubsystem) return false;
22870
- return specSubsystem === options.subsystem || specSubsystem.startsWith(`${options.subsystem}::`);
22871
- };
22872
- for (const s of index.subsystems) {
22873
- if (!options.subsystem || s.id === options.subsystem || s.id.startsWith(`${options.subsystem}::`)) {
22874
- originalStatuses.set(s, s.status);
22875
- s.status = "complete";
23062
+ for (const c of detectNestedGitRepos(projectRoot2)) {
23063
+ if (!candidates.has(c.path)) {
23064
+ candidates.set(c.path, { ...c, alreadyTracked: alreadyTrackedPaths.has(c.path) });
22876
23065
  }
22877
23066
  }
22878
- for (const c of index.components) {
22879
- if (isSpecInSubsystemScope(c.subsystem)) {
22880
- originalStatuses.set(c, c.status);
22881
- c.status = "complete";
22882
- }
23067
+ const gitPaths = new Set(
23068
+ Array.from(candidates.values()).filter((c) => c.type === "git-submodule" || c.type === "git-repo").map((c) => c.path)
23069
+ );
23070
+ for (const c of detectPackageRoots(projectRoot2)) {
23071
+ if (candidates.has(c.path)) continue;
23072
+ const insideGit = Array.from(gitPaths).some(
23073
+ (gp) => c.path === gp || c.path.startsWith(gp + "/")
23074
+ );
23075
+ if (insideGit) continue;
23076
+ candidates.set(c.path, { ...c, alreadyTracked: alreadyTrackedPaths.has(c.path) });
22883
23077
  }
22884
- for (const i of index.interfaces) {
22885
- const comp = index.components.find((c) => c.id === i.component);
22886
- if (comp && isSpecInSubsystemScope(comp.subsystem)) {
22887
- originalStatuses.set(i, i.status);
22888
- i.status = "complete";
22889
- }
23078
+ const sorted = Array.from(candidates.values()).sort((a, b) => a.path.localeCompare(b.path));
23079
+ return deduplicateIds(sorted, alreadyTrackedIds);
23080
+ }
23081
+ function deduplicateIds(candidates, existingIds = /* @__PURE__ */ new Set()) {
23082
+ const idCount = /* @__PURE__ */ new Map();
23083
+ for (const id of existingIds) {
23084
+ idCount.set(id, (idCount.get(id) ?? 0) + 1);
22890
23085
  }
22891
- for (const m of index.implementations) {
22892
- const intf = index.interfaces.find((i) => i.id === m.contract);
22893
- const comp = intf ? index.components.find((c) => c.id === intf.component) : null;
22894
- if (comp && isSpecInSubsystemScope(comp.subsystem)) {
22895
- originalStatuses.set(m, m.status);
22896
- m.status = "complete";
22897
- }
23086
+ for (const c of candidates) {
23087
+ idCount.set(c.suggestedId, (idCount.get(c.suggestedId) ?? 0) + 1);
22898
23088
  }
22899
- const dry = validateSddTree({
22900
- rules: projectConfig.rules,
22901
- projectType: projectConfig.projectType,
22902
- scopeSubsystem: options.subsystem,
22903
- recursive: options.recursive ?? true
23089
+ return candidates.map((c) => {
23090
+ if ((idCount.get(c.suggestedId) ?? 0) <= 1) return c;
23091
+ const parts = c.path.split("/");
23092
+ const qualifiedId2 = parts.length >= 2 ? pathToId(`${parts[parts.length - 2]}-${parts[parts.length - 1]}`) : c.suggestedId;
23093
+ return { ...c, suggestedId: qualifiedId2 };
22904
23094
  });
22905
- for (const [spec, status2] of originalStatuses.entries()) {
22906
- spec.status = status2;
23095
+ }
23096
+ function parseGitmodules(filePath) {
23097
+ const content = fs18.readFileSync(filePath, "utf-8");
23098
+ const entries = [];
23099
+ let current2 = {};
23100
+ for (const line2 of content.split("\n")) {
23101
+ const trimmed = line2.trim();
23102
+ const headerMatch = trimmed.match(/^\[submodule "(.+)"\]$/);
23103
+ if (headerMatch) {
23104
+ if (current2.path) entries.push(current2);
23105
+ current2 = { name: headerMatch[1] };
23106
+ continue;
23107
+ }
23108
+ const keyVal = trimmed.match(/^(\w+)\s*=\s*(.+)$/);
23109
+ if (keyVal) {
23110
+ const [, key, value] = keyVal;
23111
+ if (key === "path") current2.path = value.trim();
23112
+ if (key === "url") current2.url = value.trim();
23113
+ }
22907
23114
  }
22908
- const errors = dry.issues.filter((i) => i.severity === "error");
22909
- if (errors.length > 0) {
22910
- logger.header("Cannot lock \u2014 the spec tree does not validate as complete");
22911
- let errorCount = 0;
22912
- const MAX_PRINT = 100;
22913
- let skippedErrors = 0;
22914
- for (const i of errors) {
22915
- if (errorCount < MAX_PRINT) {
22916
- logger.error(`${i.specId ? `[${i.specId}] ` : ""}[${i.code}] ${i.message}`);
22917
- errorCount++;
22918
- } else {
22919
- skippedErrors++;
22920
- }
23115
+ if (current2.path) entries.push(current2);
23116
+ return entries;
23117
+ }
23118
+ function detectGitSubmodules(projectRoot2) {
23119
+ const gitmodulesPath = path27.join(projectRoot2, ".gitmodules");
23120
+ if (!fs18.existsSync(gitmodulesPath)) return [];
23121
+ return parseGitmodules(gitmodulesPath).map((entry) => ({
23122
+ suggestedId: pathToId(entry.path),
23123
+ suggestedName: pathToName(entry.path),
23124
+ path: normalizePath3(entry.path),
23125
+ type: "git-submodule",
23126
+ alreadyTracked: false
23127
+ }));
23128
+ }
23129
+ function detectNestedGitRepos(projectRoot2) {
23130
+ const results = [];
23131
+ walkForGit(projectRoot2, projectRoot2, 0, results);
23132
+ return results;
23133
+ }
23134
+ function walkForGit(projectRoot2, currentDir, depth, results) {
23135
+ if (depth > MAX_SCAN_DEPTH) return;
23136
+ let entries;
23137
+ try {
23138
+ entries = fs18.readdirSync(currentDir, { withFileTypes: true });
23139
+ } catch {
23140
+ return;
23141
+ }
23142
+ for (const entry of entries) {
23143
+ if (!entry.isDirectory()) continue;
23144
+ if (SCAN_EXCLUDE_DIRS.has(entry.name)) continue;
23145
+ const fullPath = path27.join(currentDir, entry.name);
23146
+ const relPath = normalizePath3(path27.relative(projectRoot2, fullPath));
23147
+ if (relPath === "" || relPath === ".") continue;
23148
+ const gitPath = path27.join(fullPath, ".git");
23149
+ if (fs18.existsSync(gitPath)) {
23150
+ results.push({
23151
+ suggestedId: pathToId(relPath),
23152
+ suggestedName: pathToName(relPath),
23153
+ path: relPath,
23154
+ type: "git-repo",
23155
+ alreadyTracked: false
23156
+ });
23157
+ continue;
22921
23158
  }
22922
- if (skippedErrors > 0) {
22923
- logger.error(`... and ${skippedErrors} more error(s) omitted.`);
23159
+ walkForGit(projectRoot2, fullPath, depth + 1, results);
23160
+ }
23161
+ }
23162
+ function detectPackageRoots(projectRoot2) {
23163
+ const results = [];
23164
+ walkForPackages(projectRoot2, projectRoot2, 0, results);
23165
+ return results;
23166
+ }
23167
+ function walkForPackages(projectRoot2, currentDir, depth, results) {
23168
+ if (depth > MAX_SCAN_DEPTH) return;
23169
+ let entries;
23170
+ try {
23171
+ entries = fs18.readdirSync(currentDir, { withFileTypes: true });
23172
+ } catch {
23173
+ return;
23174
+ }
23175
+ for (const entry of entries) {
23176
+ if (!entry.isDirectory()) continue;
23177
+ if (SCAN_EXCLUDE_DIRS.has(entry.name)) continue;
23178
+ const fullPath = path27.join(currentDir, entry.name);
23179
+ const relPath = normalizePath3(path27.relative(projectRoot2, fullPath));
23180
+ if (relPath === "" || relPath === ".") continue;
23181
+ const hasMarker = PACKAGE_MARKERS.some((m) => fs18.existsSync(path27.join(fullPath, m)));
23182
+ if (hasMarker) {
23183
+ results.push({
23184
+ suggestedId: pathToId(relPath),
23185
+ suggestedName: pathToName(relPath),
23186
+ path: relPath,
23187
+ type: "package-root",
23188
+ alreadyTracked: false
23189
+ });
22924
23190
  }
22925
- logger.blank();
22926
- logger.info("Fix the errors above, then run `wairon lock` again. Nothing was changed.");
22927
- process.exit(1);
23191
+ walkForPackages(projectRoot2, fullPath, depth + 1, results);
22928
23192
  }
22929
- logger.header("Lock SDD specs");
23193
+ }
23194
+ function pathToId(relPath) {
23195
+ const basename11 = path27.basename(relPath);
23196
+ return basename11.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
23197
+ }
23198
+ function pathToName(relPath) {
23199
+ const id = pathToId(relPath);
23200
+ return id.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
23201
+ }
23202
+ function normalizePath3(p) {
23203
+ return p.replace(/\\/g, "/");
23204
+ }
23205
+
23206
+ // src/core/index.ts
23207
+ init_domains();
23208
+ init_validation();
23209
+ init_extensions();
23210
+ init_variants();
23211
+ init_rules();
23212
+ init_specs2();
23213
+ init_provision();
23214
+ init_diagram();
23215
+
23216
+ // src/core/lockfile.ts
23217
+ var fs19 = __toESM(require("fs"));
23218
+ var path28 = __toESM(require("path"));
23219
+ init_fs();
23220
+ function lockPath() {
23221
+ return aiDir("lock.json");
23222
+ }
23223
+ function readLockRecord() {
23224
+ try {
23225
+ return JSON.parse(fs19.readFileSync(lockPath(), "utf8"));
23226
+ } catch {
23227
+ return null;
23228
+ }
23229
+ }
23230
+ function writeLockRecord(record2) {
23231
+ const p = lockPath();
23232
+ fs19.mkdirSync(path28.dirname(p), { recursive: true });
23233
+ const tmp = `${p}.tmp`;
23234
+ fs19.writeFileSync(tmp, JSON.stringify(record2, null, 2) + "\n");
23235
+ fs19.renameSync(tmp, p);
23236
+ }
23237
+
23238
+ // src/core/index.ts
23239
+ init_statehash();
23240
+ init_agent_resolver();
23241
+ init_skills();
23242
+ init_surfaces();
23243
+ init_openapi();
23244
+
23245
+ // src/commands/lock.ts
23246
+ async function runLock(options = {}, gate) {
23247
+ const promotable = collectPromotableSpecs(options.subsystem);
22930
23248
  if (promotable.length === 0) {
22931
23249
  logger.info("All specs are already complete \u2014 this will re-validate and regenerate the agent topology.");
22932
23250
  } else {
@@ -22950,23 +23268,36 @@ async function runLock(options = {}) {
22950
23268
  default: false
22951
23269
  }
22952
23270
  ]);
22953
- if (!confirmed) {
22954
- logger.info("Cancelled. Nothing was changed.");
22955
- return;
22956
- }
23271
+ if (!confirmed) return null;
23272
+ }
23273
+ if (options.subsystem) {
23274
+ for (const p of promotable) applySpecStatus(p.kind, p.id, "complete");
23275
+ invalidateSpecCache();
23276
+ } else {
23277
+ promoteAllComplete();
22957
23278
  }
22958
- for (const p of promotable) applySpecStatus(p.kind, p.id, "complete");
22959
- invalidateSpecCache();
22960
23279
  if (promotable.length > 0) {
22961
23280
  logger.success(`Locked ${promotable.length} spec(s) as complete.`);
22962
23281
  }
22963
- logger.blank();
22964
- await runGenerate({ domain: options.subsystem });
22965
- logger.blank();
22966
- logger.success("Specs locked and agent topology generated.");
22967
- logger.warn(
22968
- "Restart any running AI agent sessions (Claude Code / Antigravity / Codex) so the newly generated implementer agents load \u2014 they are not picked up mid-session."
22969
- );
23282
+ let lockedBy = "local";
23283
+ try {
23284
+ lockedBy = `local:${os7.userInfo().username}`;
23285
+ } catch {
23286
+ }
23287
+ const record2 = {
23288
+ stateId: computeStateId(),
23289
+ lockedAt: (/* @__PURE__ */ new Date()).toISOString(),
23290
+ lockedBy,
23291
+ validatorVersion: WAIRON_VERSION,
23292
+ validationResult: {
23293
+ valid: true,
23294
+ errors: 0,
23295
+ warnings: gate ? gate.issues.filter((i) => i.severity === "warning").length : 0
23296
+ },
23297
+ status: "ready"
23298
+ };
23299
+ writeLockRecord(record2);
23300
+ return record2;
22970
23301
  }
22971
23302
 
22972
23303
  // src/commands/validate.ts
@@ -23105,6 +23436,10 @@ async function runValidate(options = {}) {
23105
23436
  }
23106
23437
  }
23107
23438
 
23439
+ // src/cli/index.ts
23440
+ init_loader();
23441
+ init_fs();
23442
+
23108
23443
  // src/commands/list.ts
23109
23444
  var import_chalk7 = __toESM(require("chalk"));
23110
23445
  init_logger();
@@ -23219,9 +23554,9 @@ init_mcp();
23219
23554
  // src/commands/update.ts
23220
23555
  var https = __toESM(require("https"));
23221
23556
  var http = __toESM(require("http"));
23222
- var fs18 = __toESM(require("fs"));
23223
- var path27 = __toESM(require("path"));
23224
- var os7 = __toESM(require("os"));
23557
+ var fs20 = __toESM(require("fs"));
23558
+ var path29 = __toESM(require("path"));
23559
+ var os8 = __toESM(require("os"));
23225
23560
  var crypto2 = __toESM(require("crypto"));
23226
23561
  var import_child_process2 = require("child_process");
23227
23562
  init_logger();
@@ -23284,8 +23619,8 @@ async function runUpdate(options = {}) {
23284
23619
  logger.info(`Download manually from: ${release.html_url}`);
23285
23620
  process.exit(1);
23286
23621
  }
23287
- const tmpDir = os7.tmpdir();
23288
- const tmpFile = path27.join(tmpDir, assetName);
23622
+ const tmpDir = os8.tmpdir();
23623
+ const tmpFile = path29.join(tmpDir, assetName);
23289
23624
  logger.info(`Downloading ${assetName}...`);
23290
23625
  try {
23291
23626
  await downloadFile(asset.browser_download_url, tmpFile);
@@ -23302,16 +23637,16 @@ async function runUpdate(options = {}) {
23302
23637
  const checksumAssetName = assetName + ".sha256";
23303
23638
  const checksumAsset = release.assets.find((a) => a.name === checksumAssetName);
23304
23639
  if (checksumAsset) {
23305
- const tmpChecksum = path27.join(tmpDir, checksumAssetName);
23640
+ const tmpChecksum = path29.join(tmpDir, checksumAssetName);
23306
23641
  logger.info(`Verifying checksum...`);
23307
23642
  try {
23308
23643
  await downloadFile(checksumAsset.browser_download_url, tmpChecksum);
23309
23644
  verifyChecksum(tmpFile, tmpChecksum, assetName);
23310
- fs18.unlinkSync(tmpChecksum);
23645
+ fs20.unlinkSync(tmpChecksum);
23311
23646
  } catch (err) {
23312
23647
  logger.error(`Checksum verification failed: ${err.message}`);
23313
23648
  try {
23314
- fs18.unlinkSync(tmpFile);
23649
+ fs20.unlinkSync(tmpFile);
23315
23650
  } catch {
23316
23651
  }
23317
23652
  process.exit(1);
@@ -23377,7 +23712,7 @@ function fetchReleases(repo) {
23377
23712
  }
23378
23713
  function downloadFile(url, dest) {
23379
23714
  return new Promise((resolve24, reject) => {
23380
- const file = fs18.createWriteStream(dest);
23715
+ const file = fs20.createWriteStream(dest);
23381
23716
  const get3 = url.startsWith("https://") ? https.get : http.get;
23382
23717
  get3(url, { headers: { "User-Agent": `wairon/${WAIRON_VERSION}` }, agent: false }, (res) => {
23383
23718
  if (res.statusCode === 301 || res.statusCode === 302) {
@@ -23399,21 +23734,21 @@ function downloadFile(url, dest) {
23399
23734
  });
23400
23735
  file.on("error", (err) => {
23401
23736
  res.destroy();
23402
- fs18.unlink(dest, () => {
23737
+ fs20.unlink(dest, () => {
23403
23738
  });
23404
23739
  reject(err);
23405
23740
  });
23406
23741
  }).on("error", (err) => {
23407
- fs18.unlink(dest, () => {
23742
+ fs20.unlink(dest, () => {
23408
23743
  });
23409
23744
  reject(err);
23410
23745
  });
23411
23746
  });
23412
23747
  }
23413
23748
  function verifyChecksum(filePath, checksumFile, expectedFilename) {
23414
- const checksumContent = fs18.readFileSync(checksumFile, "utf-8").trim();
23749
+ const checksumContent = fs20.readFileSync(checksumFile, "utf-8").trim();
23415
23750
  const expectedHash = checksumContent.split(/\s+/)[0].toLowerCase();
23416
- const fileBuffer = fs18.readFileSync(filePath);
23751
+ const fileBuffer = fs20.readFileSync(filePath);
23417
23752
  const actualHash = crypto2.createHash("sha256").update(fileBuffer).digest("hex").toLowerCase();
23418
23753
  if (actualHash !== expectedHash) {
23419
23754
  throw new Error(
@@ -23444,9 +23779,9 @@ function isPkgBinary2() {
23444
23779
  function installBinary(tmpFile, destPath) {
23445
23780
  const platform = process.platform;
23446
23781
  const isZip = tmpFile.endsWith(".zip");
23447
- const extractDir = path27.join(os7.tmpdir(), "wairon-extract");
23448
- if (fs18.existsSync(extractDir)) fs18.rmSync(extractDir, { recursive: true });
23449
- fs18.mkdirSync(extractDir, { recursive: true });
23782
+ const extractDir = path29.join(os8.tmpdir(), "wairon-extract");
23783
+ if (fs20.existsSync(extractDir)) fs20.rmSync(extractDir, { recursive: true });
23784
+ fs20.mkdirSync(extractDir, { recursive: true });
23450
23785
  if (isZip) {
23451
23786
  (0, import_child_process2.execSync)(
23452
23787
  `powershell -NoProfile -NonInteractive -Command "Expand-Archive -Path '${tmpFile}' -DestinationPath '${extractDir}' -Force"`,
@@ -23456,18 +23791,18 @@ function installBinary(tmpFile, destPath) {
23456
23791
  (0, import_child_process2.execSync)(`tar -xzf "${tmpFile}" -C "${extractDir}"`, { stdio: ["ignore", "pipe", "pipe"] });
23457
23792
  }
23458
23793
  const binaryName = platform === "win32" ? "wairon.exe" : "wairon";
23459
- const extractedBinary = path27.join(extractDir, binaryName);
23460
- if (!fs18.existsSync(extractedBinary)) {
23794
+ const extractedBinary = path29.join(extractDir, binaryName);
23795
+ if (!fs20.existsSync(extractedBinary)) {
23461
23796
  throw new Error(`Extracted binary not found at ${extractedBinary}`);
23462
23797
  }
23463
23798
  if (platform === "win32") {
23464
23799
  const oldPath = destPath + ".old";
23465
23800
  try {
23466
23801
  cleanStaleBinary(oldPath);
23467
- fs18.renameSync(destPath, oldPath);
23468
- fs18.copyFileSync(extractedBinary, destPath);
23802
+ fs20.renameSync(destPath, oldPath);
23803
+ fs20.copyFileSync(extractedBinary, destPath);
23469
23804
  try {
23470
- fs18.unlinkSync(oldPath);
23805
+ fs20.unlinkSync(oldPath);
23471
23806
  } catch {
23472
23807
  }
23473
23808
  } catch (err) {
@@ -23481,25 +23816,25 @@ function installBinary(tmpFile, destPath) {
23481
23816
  }
23482
23817
  } else {
23483
23818
  const tmpDest = destPath + ".new";
23484
- fs18.copyFileSync(extractedBinary, tmpDest);
23485
- fs18.chmodSync(tmpDest, 493);
23486
- fs18.renameSync(tmpDest, destPath);
23819
+ fs20.copyFileSync(extractedBinary, tmpDest);
23820
+ fs20.chmodSync(tmpDest, 493);
23821
+ fs20.renameSync(tmpDest, destPath);
23487
23822
  }
23488
23823
  try {
23489
- fs18.unlinkSync(tmpFile);
23824
+ fs20.unlinkSync(tmpFile);
23490
23825
  } catch {
23491
23826
  }
23492
23827
  try {
23493
- fs18.rmSync(extractDir, { recursive: true });
23828
+ fs20.rmSync(extractDir, { recursive: true });
23494
23829
  } catch {
23495
23830
  }
23496
23831
  }
23497
23832
  function cleanStaleBinary(oldPath) {
23498
23833
  const target = oldPath ?? (isPkgBinary2() ? process.execPath + ".old" : null);
23499
23834
  if (!target) return;
23500
- if (fs18.existsSync(target)) {
23835
+ if (fs20.existsSync(target)) {
23501
23836
  try {
23502
- fs18.unlinkSync(target);
23837
+ fs20.unlinkSync(target);
23503
23838
  } catch {
23504
23839
  }
23505
23840
  }
@@ -23745,171 +24080,6 @@ async function filteredCheckbox(config) {
23745
24080
 
23746
24081
  // src/commands/domains.ts
23747
24082
  init_loader();
23748
-
23749
- // src/core/detection.ts
23750
- var fs19 = __toESM(require("fs"));
23751
- var path28 = __toESM(require("path"));
23752
- init_defaults();
23753
- var PACKAGE_MARKERS = [
23754
- "package.json",
23755
- "pyproject.toml",
23756
- "Cargo.toml",
23757
- "go.mod",
23758
- "build.gradle",
23759
- "build.gradle.kts",
23760
- "pom.xml"
23761
- ];
23762
- var MAX_SCAN_DEPTH = 5;
23763
- function detectDomainCandidates(projectRoot2, alreadyTrackedPaths = /* @__PURE__ */ new Set(), alreadyTrackedIds = /* @__PURE__ */ new Set()) {
23764
- const candidates = /* @__PURE__ */ new Map();
23765
- for (const c of detectGitSubmodules(projectRoot2)) {
23766
- candidates.set(c.path, { ...c, alreadyTracked: alreadyTrackedPaths.has(c.path) });
23767
- }
23768
- for (const c of detectNestedGitRepos(projectRoot2)) {
23769
- if (!candidates.has(c.path)) {
23770
- candidates.set(c.path, { ...c, alreadyTracked: alreadyTrackedPaths.has(c.path) });
23771
- }
23772
- }
23773
- const gitPaths = new Set(
23774
- Array.from(candidates.values()).filter((c) => c.type === "git-submodule" || c.type === "git-repo").map((c) => c.path)
23775
- );
23776
- for (const c of detectPackageRoots(projectRoot2)) {
23777
- if (candidates.has(c.path)) continue;
23778
- const insideGit = Array.from(gitPaths).some(
23779
- (gp) => c.path === gp || c.path.startsWith(gp + "/")
23780
- );
23781
- if (insideGit) continue;
23782
- candidates.set(c.path, { ...c, alreadyTracked: alreadyTrackedPaths.has(c.path) });
23783
- }
23784
- const sorted = Array.from(candidates.values()).sort((a, b) => a.path.localeCompare(b.path));
23785
- return deduplicateIds(sorted, alreadyTrackedIds);
23786
- }
23787
- function deduplicateIds(candidates, existingIds = /* @__PURE__ */ new Set()) {
23788
- const idCount = /* @__PURE__ */ new Map();
23789
- for (const id of existingIds) {
23790
- idCount.set(id, (idCount.get(id) ?? 0) + 1);
23791
- }
23792
- for (const c of candidates) {
23793
- idCount.set(c.suggestedId, (idCount.get(c.suggestedId) ?? 0) + 1);
23794
- }
23795
- return candidates.map((c) => {
23796
- if ((idCount.get(c.suggestedId) ?? 0) <= 1) return c;
23797
- const parts = c.path.split("/");
23798
- const qualifiedId2 = parts.length >= 2 ? pathToId(`${parts[parts.length - 2]}-${parts[parts.length - 1]}`) : c.suggestedId;
23799
- return { ...c, suggestedId: qualifiedId2 };
23800
- });
23801
- }
23802
- function parseGitmodules(filePath) {
23803
- const content = fs19.readFileSync(filePath, "utf-8");
23804
- const entries = [];
23805
- let current2 = {};
23806
- for (const line2 of content.split("\n")) {
23807
- const trimmed = line2.trim();
23808
- const headerMatch = trimmed.match(/^\[submodule "(.+)"\]$/);
23809
- if (headerMatch) {
23810
- if (current2.path) entries.push(current2);
23811
- current2 = { name: headerMatch[1] };
23812
- continue;
23813
- }
23814
- const keyVal = trimmed.match(/^(\w+)\s*=\s*(.+)$/);
23815
- if (keyVal) {
23816
- const [, key, value] = keyVal;
23817
- if (key === "path") current2.path = value.trim();
23818
- if (key === "url") current2.url = value.trim();
23819
- }
23820
- }
23821
- if (current2.path) entries.push(current2);
23822
- return entries;
23823
- }
23824
- function detectGitSubmodules(projectRoot2) {
23825
- const gitmodulesPath = path28.join(projectRoot2, ".gitmodules");
23826
- if (!fs19.existsSync(gitmodulesPath)) return [];
23827
- return parseGitmodules(gitmodulesPath).map((entry) => ({
23828
- suggestedId: pathToId(entry.path),
23829
- suggestedName: pathToName(entry.path),
23830
- path: normalizePath3(entry.path),
23831
- type: "git-submodule",
23832
- alreadyTracked: false
23833
- }));
23834
- }
23835
- function detectNestedGitRepos(projectRoot2) {
23836
- const results = [];
23837
- walkForGit(projectRoot2, projectRoot2, 0, results);
23838
- return results;
23839
- }
23840
- function walkForGit(projectRoot2, currentDir, depth, results) {
23841
- if (depth > MAX_SCAN_DEPTH) return;
23842
- let entries;
23843
- try {
23844
- entries = fs19.readdirSync(currentDir, { withFileTypes: true });
23845
- } catch {
23846
- return;
23847
- }
23848
- for (const entry of entries) {
23849
- if (!entry.isDirectory()) continue;
23850
- if (SCAN_EXCLUDE_DIRS.has(entry.name)) continue;
23851
- const fullPath = path28.join(currentDir, entry.name);
23852
- const relPath = normalizePath3(path28.relative(projectRoot2, fullPath));
23853
- if (relPath === "" || relPath === ".") continue;
23854
- const gitPath = path28.join(fullPath, ".git");
23855
- if (fs19.existsSync(gitPath)) {
23856
- results.push({
23857
- suggestedId: pathToId(relPath),
23858
- suggestedName: pathToName(relPath),
23859
- path: relPath,
23860
- type: "git-repo",
23861
- alreadyTracked: false
23862
- });
23863
- continue;
23864
- }
23865
- walkForGit(projectRoot2, fullPath, depth + 1, results);
23866
- }
23867
- }
23868
- function detectPackageRoots(projectRoot2) {
23869
- const results = [];
23870
- walkForPackages(projectRoot2, projectRoot2, 0, results);
23871
- return results;
23872
- }
23873
- function walkForPackages(projectRoot2, currentDir, depth, results) {
23874
- if (depth > MAX_SCAN_DEPTH) return;
23875
- let entries;
23876
- try {
23877
- entries = fs19.readdirSync(currentDir, { withFileTypes: true });
23878
- } catch {
23879
- return;
23880
- }
23881
- for (const entry of entries) {
23882
- if (!entry.isDirectory()) continue;
23883
- if (SCAN_EXCLUDE_DIRS.has(entry.name)) continue;
23884
- const fullPath = path28.join(currentDir, entry.name);
23885
- const relPath = normalizePath3(path28.relative(projectRoot2, fullPath));
23886
- if (relPath === "" || relPath === ".") continue;
23887
- const hasMarker = PACKAGE_MARKERS.some((m) => fs19.existsSync(path28.join(fullPath, m)));
23888
- if (hasMarker) {
23889
- results.push({
23890
- suggestedId: pathToId(relPath),
23891
- suggestedName: pathToName(relPath),
23892
- path: relPath,
23893
- type: "package-root",
23894
- alreadyTracked: false
23895
- });
23896
- }
23897
- walkForPackages(projectRoot2, fullPath, depth + 1, results);
23898
- }
23899
- }
23900
- function pathToId(relPath) {
23901
- const basename12 = path28.basename(relPath);
23902
- return basename12.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
23903
- }
23904
- function pathToName(relPath) {
23905
- const id = pathToId(relPath);
23906
- return id.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
23907
- }
23908
- function normalizePath3(p) {
23909
- return p.replace(/\\/g, "/");
23910
- }
23911
-
23912
- // src/commands/domains.ts
23913
24083
  init_domains();
23914
24084
  init_domain();
23915
24085
  async function runDomainsList() {
@@ -24099,9 +24269,9 @@ async function runSkillsInstall() {
24099
24269
  }
24100
24270
 
24101
24271
  // src/commands/doctor.ts
24102
- var fs20 = __toESM(require("fs"));
24103
- var os8 = __toESM(require("os"));
24104
- var path29 = __toESM(require("path"));
24272
+ var fs21 = __toESM(require("fs"));
24273
+ var os9 = __toESM(require("os"));
24274
+ var path30 = __toESM(require("path"));
24105
24275
  var import_chalk12 = __toESM(require("chalk"));
24106
24276
  init_logger();
24107
24277
  init_defaults();
@@ -24131,10 +24301,10 @@ function stampVerdict(content) {
24131
24301
  return { mark: "warn", note: `v${v} \u2014 stale, installed is v${WAIRON_VERSION}` };
24132
24302
  }
24133
24303
  function mcpEntryHealth(settingsPath) {
24134
- if (!fs20.existsSync(settingsPath)) return { mark: "warn", note: "not registered" };
24304
+ if (!fs21.existsSync(settingsPath)) return { mark: "warn", note: "not registered" };
24135
24305
  let entry;
24136
24306
  try {
24137
- const s = JSON.parse(fs20.readFileSync(settingsPath, "utf8"));
24307
+ const s = JSON.parse(fs21.readFileSync(settingsPath, "utf8"));
24138
24308
  entry = s.mcpServers?.["wairon"];
24139
24309
  } catch {
24140
24310
  return { mark: "error", note: "parse error" };
@@ -24142,7 +24312,7 @@ function mcpEntryHealth(settingsPath) {
24142
24312
  if (!entry) return { mark: "warn", note: "not registered" };
24143
24313
  if (entry.command === "node" && Array.isArray(entry.args) && typeof entry.args[0] === "string") {
24144
24314
  const scriptPath = entry.args[0];
24145
- if (!fs20.existsSync(scriptPath)) {
24315
+ if (!fs21.existsSync(scriptPath)) {
24146
24316
  return { mark: "error", note: `registered but the server path is missing \u2014 ${scriptPath}` };
24147
24317
  }
24148
24318
  }
@@ -24194,7 +24364,7 @@ async function runDoctor(options = {}) {
24194
24364
  const { findChainingSubprojectsMissingConfig: findChainingSubprojectsMissingConfig2 } = (init_provision(), __toCommonJS(provision_exports));
24195
24365
  const missing = findChainingSubprojectsMissingConfig2(getProjectRoot());
24196
24366
  if (missing.length > 0) {
24197
- line(tally, "warn", `${missing.length} chained subproject(s) have specs but no project.yaml (un-runnable standalone): ${missing.map((d) => path29.relative(getProjectRoot(), d) || ".").join(", ")}. Run \`wairon doctor --fix\` to initialize them.`);
24367
+ line(tally, "warn", `${missing.length} chained subproject(s) have specs but no project.yaml (un-runnable standalone): ${missing.map((d) => path30.relative(getProjectRoot(), d) || ".").join(", ")}. Run \`wairon doctor --fix\` to initialize them.`);
24198
24368
  }
24199
24369
  } catch {
24200
24370
  }
@@ -24232,7 +24402,7 @@ async function runDoctor(options = {}) {
24232
24402
  const gp = localGuideFilePath(process.cwd(), t);
24233
24403
  if (!gp || seenGuides.has(gp)) continue;
24234
24404
  seenGuides.add(gp);
24235
- const rel2 = path29.relative(process.cwd(), gp).replace(/\\/g, "/");
24405
+ const rel2 = path30.relative(process.cwd(), gp).replace(/\\/g, "/");
24236
24406
  if (!pathExists(gp)) {
24237
24407
  line(tally, "warn", `${rel2} guide \u2014 not injected (run \`wairon generate\`)`);
24238
24408
  continue;
@@ -24266,17 +24436,17 @@ async function runDoctor(options = {}) {
24266
24436
  line(tally, h.mark, `Claude (project .mcp.json): ${h.note}${h.mark === "ok" ? "" : " \u2014 run `wairon mcp install --backend claude`"}`);
24267
24437
  }
24268
24438
  if (wantGemini) {
24269
- const globalCfg = path29.join(os8.homedir(), ".gemini", "antigravity-cli", "mcp_config.json");
24439
+ const globalCfg = path30.join(os9.homedir(), ".gemini", "antigravity-cli", "mcp_config.json");
24270
24440
  const hg = mcpEntryHealth(globalCfg);
24271
24441
  line(tally, hg.mark, `Antigravity (global mcp_config.json): ${hg.note}${hg.mark === "ok" ? "" : " \u2014 run `wairon mcp install --backend gemini --global`"}`);
24272
24442
  const projPath = fromProjectRoot(".gemini", "settings.json");
24273
- if (fs20.existsSync(projPath)) {
24443
+ if (fs21.existsSync(projPath)) {
24274
24444
  const hp = mcpEntryHealth(projPath);
24275
24445
  line(tally, hp.mark === "error" ? "error" : "ok", `Gemini CLI (project): ${hp.note} ${import_chalk12.default.gray("(Antigravity ignores this file)")}`);
24276
24446
  }
24277
24447
  }
24278
- const pluginDir = path29.join(os8.homedir(), ".gemini", "config", "plugins", "wairon");
24279
- if (fs20.existsSync(pluginDir)) {
24448
+ const pluginDir = path30.join(os9.homedir(), ".gemini", "config", "plugins", "wairon");
24449
+ if (fs21.existsSync(pluginDir)) {
24280
24450
  line(tally, "warn", `Legacy Antigravity plugin present (${pluginDir}) \u2014 it collides with the wairon MCP server. Remove it with \`wairon doctor --fix\`.`);
24281
24451
  }
24282
24452
  logger.blank();
@@ -24307,7 +24477,7 @@ async function applyFixes() {
24307
24477
  const legacySpecs = findLegacySpecFiles();
24308
24478
  if (legacySpecs.length > 0) {
24309
24479
  for (const { path: oldPath, expected: newPath } of legacySpecs) {
24310
- fs20.renameSync(oldPath, newPath);
24480
+ fs21.renameSync(oldPath, newPath);
24311
24481
  }
24312
24482
  console.log(` ${icon("ok")} Migrated ${legacySpecs.length} legacy spec file(s) to the new dot-prefixed unified schema.`);
24313
24483
  }
@@ -24361,8 +24531,8 @@ function printSummary(tally) {
24361
24531
  }
24362
24532
 
24363
24533
  // src/commands/diagram.ts
24364
- var fs21 = __toESM(require("fs"));
24365
- var path30 = __toESM(require("path"));
24534
+ var fs22 = __toESM(require("fs"));
24535
+ var path31 = __toESM(require("path"));
24366
24536
  init_logger();
24367
24537
  init_loader();
24368
24538
  init_fs();
@@ -24397,8 +24567,8 @@ function collectIssues() {
24397
24567
  }
24398
24568
  function writeCanvas(dest) {
24399
24569
  const model = buildCanvasModel(collectIssues());
24400
- ensureDir(path30.dirname(path30.resolve(dest)));
24401
- fs21.writeFileSync(dest, renderCanvasHtml(model), "utf-8");
24570
+ ensureDir(path31.dirname(path31.resolve(dest)));
24571
+ fs22.writeFileSync(dest, renderCanvasHtml(model), "utf-8");
24402
24572
  }
24403
24573
  function parseSequenceRef(ref) {
24404
24574
  const sep6 = ref.includes(":") ? ref.lastIndexOf(":") : ref.lastIndexOf(".");
@@ -24413,55 +24583,55 @@ async function runDiagram(rawOptions = {}) {
24413
24583
  assertProjectInitialized();
24414
24584
  const options = applyFormat(rawOptions);
24415
24585
  if (options.canvas && !options.all) {
24416
- const dest2 = options.out ?? path30.join(AI_PATHS.docsDir(), "diagrams", "canvas.html");
24586
+ const dest2 = options.out ?? path31.join(AI_PATHS.docsDir(), "diagrams", "canvas.html");
24417
24587
  writeCanvas(dest2);
24418
24588
  logger.success(`Interactive canvas written to ${dest2}`);
24419
24589
  logger.info("Open it in a browser \u2014 fully self-contained (works offline).");
24420
24590
  return;
24421
24591
  }
24422
24592
  if (options.drawio && !options.all) {
24423
- const dest2 = options.out ?? path30.join(AI_PATHS.docsDir(), "diagrams", "architecture.drawio");
24424
- ensureDir(path30.dirname(path30.resolve(dest2)));
24425
- fs21.writeFileSync(dest2, generateDrawioXml(buildCanvasModel()), "utf-8");
24593
+ const dest2 = options.out ?? path31.join(AI_PATHS.docsDir(), "diagrams", "architecture.drawio");
24594
+ ensureDir(path31.dirname(path31.resolve(dest2)));
24595
+ fs22.writeFileSync(dest2, generateDrawioXml(buildCanvasModel()), "utf-8");
24426
24596
  logger.success(`draw.io diagram written to ${dest2}`);
24427
24597
  logger.info("Open with draw.io / diagrams.net (or import into tools that accept the format).");
24428
24598
  return;
24429
24599
  }
24430
24600
  if (options.excalidraw && !options.all) {
24431
- const dest2 = options.out ?? path30.join(AI_PATHS.docsDir(), "diagrams", "architecture.excalidraw");
24432
- ensureDir(path30.dirname(path30.resolve(dest2)));
24433
- fs21.writeFileSync(dest2, generateExcalidrawScene(buildCanvasModel()), "utf-8");
24601
+ const dest2 = options.out ?? path31.join(AI_PATHS.docsDir(), "diagrams", "architecture.excalidraw");
24602
+ ensureDir(path31.dirname(path31.resolve(dest2)));
24603
+ fs22.writeFileSync(dest2, generateExcalidrawScene(buildCanvasModel()), "utf-8");
24434
24604
  logger.success(`Excalidraw scene written to ${dest2}`);
24435
24605
  logger.info("Open with excalidraw.com or the VS Code extension.");
24436
24606
  return;
24437
24607
  }
24438
24608
  const wantsMermaid = options.format?.toLowerCase().startsWith("mermaid") || !!options.subsystem || !!options.sequence;
24439
24609
  if (!options.all && !options.sequence && !wantsMermaid) {
24440
- const dest2 = options.out ?? path30.join(AI_PATHS.docsDir(), "diagrams", "canvas.html");
24610
+ const dest2 = options.out ?? path31.join(AI_PATHS.docsDir(), "diagrams", "canvas.html");
24441
24611
  writeCanvas(dest2);
24442
24612
  logger.success(`Interactive canvas written to ${dest2}`);
24443
24613
  logger.info("Open it in a browser \u2014 fully self-contained (works offline). Other formats: --format mermaid|drawio|excalidraw.");
24444
24614
  return;
24445
24615
  }
24446
24616
  if (options.all) {
24447
- const outDir = options.out ?? path30.join(AI_PATHS.docsDir(), "diagrams");
24617
+ const outDir = options.out ?? path31.join(AI_PATHS.docsDir(), "diagrams");
24448
24618
  const files = generateDiagramSet();
24449
24619
  if (files.length === 0) {
24450
24620
  logger.warn("No diagrams to generate \u2014 the spec tree has no components yet.");
24451
24621
  return;
24452
24622
  }
24453
24623
  for (const file of files) {
24454
- const dest2 = path30.join(outDir, file.relPath);
24455
- ensureDir(path30.dirname(dest2));
24456
- fs21.writeFileSync(dest2, toMarkdown(file), "utf-8");
24624
+ const dest2 = path31.join(outDir, file.relPath);
24625
+ ensureDir(path31.dirname(dest2));
24626
+ fs22.writeFileSync(dest2, toMarkdown(file), "utf-8");
24457
24627
  }
24458
- writeCanvas(path30.join(outDir, "canvas.html"));
24628
+ writeCanvas(path31.join(outDir, "canvas.html"));
24459
24629
  const exportModel = buildCanvasModel();
24460
- fs21.writeFileSync(path30.join(outDir, "architecture.drawio"), generateDrawioXml(exportModel), "utf-8");
24461
- fs21.writeFileSync(path30.join(outDir, "architecture.excalidraw"), generateExcalidrawScene(exportModel), "utf-8");
24630
+ fs22.writeFileSync(path31.join(outDir, "architecture.drawio"), generateDrawioXml(exportModel), "utf-8");
24631
+ fs22.writeFileSync(path31.join(outDir, "architecture.excalidraw"), generateExcalidrawScene(exportModel), "utf-8");
24462
24632
  const graph = loadSpecGraph();
24463
- const indexPath = path30.join(outDir, "README.md");
24464
- fs21.writeFileSync(indexPath, diagramSetIndex(files, graph.systemName), "utf-8");
24633
+ const indexPath = path31.join(outDir, "README.md");
24634
+ fs22.writeFileSync(indexPath, diagramSetIndex(files, graph.systemName), "utf-8");
24465
24635
  logger.success(`Generated ${files.length} diagram(s) + interactive canvas.html + index into ${outDir}`);
24466
24636
  for (const file of files.slice(0, 12)) {
24467
24637
  logger.info(` ${file.relPath}`);
@@ -24472,26 +24642,26 @@ async function runDiagram(rawOptions = {}) {
24472
24642
  let mermaid;
24473
24643
  let title;
24474
24644
  let defaultDest;
24475
- const diagramsDir = path30.join(AI_PATHS.docsDir(), "diagrams");
24645
+ const diagramsDir = path31.join(AI_PATHS.docsDir(), "diagrams");
24476
24646
  if (options.sequence) {
24477
24647
  const { component, method: method2 } = parseSequenceRef(options.sequence);
24478
24648
  mermaid = generateSequenceDiagram(component, method2, { depth: options.depth });
24479
24649
  title = `${component}.${method2} \u2014 narrative sequence`;
24480
- defaultDest = path30.join(diagramsDir, "sequences", `${component.replace(/::/g, "--")}.${method2}.md`);
24650
+ defaultDest = path31.join(diagramsDir, "sequences", `${component.replace(/::/g, "--")}.${method2}.md`);
24481
24651
  } else if (options.subsystem) {
24482
24652
  mermaid = generateComponentDiagram({ subsystem: options.subsystem });
24483
24653
  title = `${options.subsystem} \u2014 components`;
24484
- defaultDest = path30.join(diagramsDir, "subsystems", `${options.subsystem.replace(/::/g, "--")}.md`);
24654
+ defaultDest = path31.join(diagramsDir, "subsystems", `${options.subsystem.replace(/::/g, "--")}.md`);
24485
24655
  } else {
24486
24656
  mermaid = generateComponentDiagram();
24487
24657
  title = "Component architecture";
24488
- defaultDest = path30.join(diagramsDir, "system.md");
24658
+ defaultDest = path31.join(diagramsDir, "system.md");
24489
24659
  }
24490
24660
  const dest = options.out ?? defaultDest;
24491
- ensureDir(path30.dirname(path30.resolve(dest)));
24661
+ ensureDir(path31.dirname(path31.resolve(dest)));
24492
24662
  const content = dest.endsWith(".mmd") ? `${mermaid}
24493
24663
  ` : toMarkdown({ relPath: dest, title, mermaid });
24494
- fs21.writeFileSync(dest, content, "utf-8");
24664
+ fs22.writeFileSync(dest, content, "utf-8");
24495
24665
  logger.success(`Mermaid diagram written to ${dest}`);
24496
24666
  logger.info("Renders on GitHub/IDE previews; use a .mmd --out path for raw Mermaid.");
24497
24667
  }
@@ -24600,8 +24770,8 @@ Component variants (${variants.length})
24600
24770
  }
24601
24771
 
24602
24772
  // src/commands/packs.ts
24603
- var fs22 = __toESM(require("fs"));
24604
- var path31 = __toESM(require("path"));
24773
+ var fs23 = __toESM(require("fs"));
24774
+ var path32 = __toESM(require("path"));
24605
24775
  var import_chalk16 = __toESM(require("chalk"));
24606
24776
  var import_sdk = __toESM(require_dist());
24607
24777
  init_logger();
@@ -24627,11 +24797,11 @@ function describe(probe2) {
24627
24797
  return parts.join(", ");
24628
24798
  }
24629
24799
  function resolveSourceUnit(source) {
24630
- const abs = path31.resolve(source);
24631
- if (!fs22.existsSync(abs)) {
24800
+ const abs = path32.resolve(source);
24801
+ if (!fs23.existsSync(abs)) {
24632
24802
  throw new Error(`Pack source "${source}" does not exist.`);
24633
24803
  }
24634
- const isDir = fs22.statSync(abs).isDirectory();
24804
+ const isDir = fs23.statSync(abs).isDirectory();
24635
24805
  if (isDir && !packDirEntry(abs)) {
24636
24806
  throw new Error(`"${source}" is a directory without a pack entry file (pack.yaml | pack.cjs | index.cjs | ...).`);
24637
24807
  }
@@ -24647,7 +24817,7 @@ async function addPack(source, options = {}) {
24647
24817
  }
24648
24818
  const { abs } = resolveSourceUnit(source);
24649
24819
  const scope = options.global ? "global" : "project";
24650
- const probe2 = probePack(abs, path31.dirname(abs), scope);
24820
+ const probe2 = probePack(abs, path32.dirname(abs), scope);
24651
24821
  if (probe2.error) {
24652
24822
  logger.error(probe2.error);
24653
24823
  process.exitCode = 1;
@@ -24655,10 +24825,10 @@ async function addPack(source, options = {}) {
24655
24825
  }
24656
24826
  if (options.global) {
24657
24827
  const destDir = globalPacksDir();
24658
- const dest2 = path31.join(destDir, path31.basename(abs));
24659
- if (path31.resolve(dest2) !== abs) {
24660
- fs22.mkdirSync(destDir, { recursive: true });
24661
- fs22.cpSync(abs, dest2, { recursive: true, force: true });
24828
+ const dest2 = path32.join(destDir, path32.basename(abs));
24829
+ if (path32.resolve(dest2) !== abs) {
24830
+ fs23.mkdirSync(destDir, { recursive: true });
24831
+ fs23.cpSync(abs, dest2, { recursive: true, force: true });
24662
24832
  }
24663
24833
  logger.success(`Installed pack "${probe2.name}" globally: ${dest2}`);
24664
24834
  logger.info(`${describe(probe2)} \u2014 auto-loaded for every project on this machine (WAIRON_PACKS_DIR / ~/.wairon/packs).`);
@@ -24671,11 +24841,11 @@ async function addPack(source, options = {}) {
24671
24841
  return;
24672
24842
  }
24673
24843
  const root = getProjectRoot();
24674
- const relRef = `.wai/packs/${path31.basename(abs)}`;
24675
- const dest = path31.join(root, ".wai", "packs", path31.basename(abs));
24676
- if (path31.resolve(dest) !== abs) {
24677
- fs22.mkdirSync(path31.dirname(dest), { recursive: true });
24678
- fs22.cpSync(abs, dest, { recursive: true, force: true });
24844
+ const relRef = `.wai/packs/${path32.basename(abs)}`;
24845
+ const dest = path32.join(root, ".wai", "packs", path32.basename(abs));
24846
+ if (path32.resolve(dest) !== abs) {
24847
+ fs23.mkdirSync(path32.dirname(dest), { recursive: true });
24848
+ fs23.cpSync(abs, dest, { recursive: true, force: true });
24679
24849
  }
24680
24850
  const config = loadProjectConfig();
24681
24851
  const packs = config.extensions?.packs ?? [];
@@ -24684,14 +24854,14 @@ async function addPack(source, options = {}) {
24684
24854
  saveProjectConfig(config);
24685
24855
  logger.success(`Vendored pack "${probe2.name}" into ${relRef} and registered it in .wai/project.yaml.`);
24686
24856
  } else {
24687
- fs22.cpSync(abs, dest, { recursive: true, force: true });
24857
+ fs23.cpSync(abs, dest, { recursive: true, force: true });
24688
24858
  logger.success(`Pack "${probe2.name}" already registered \u2014 refreshed ${relRef} from the source.`);
24689
24859
  }
24690
24860
  logger.info(`${describe(probe2)} \u2014 commit .wai/ so CI and every clone enforce it.`);
24691
24861
  }
24692
24862
  async function addPackFromArchive(source, options) {
24693
- const abs = path31.resolve(source);
24694
- if (!fs22.existsSync(abs) || !fs22.statSync(abs).isFile()) {
24863
+ const abs = path32.resolve(source);
24864
+ if (!fs23.existsSync(abs) || !fs23.statSync(abs).isFile()) {
24695
24865
  logger.error(`Pack archive "${source}" does not exist.`);
24696
24866
  process.exitCode = 1;
24697
24867
  return;
@@ -24706,27 +24876,27 @@ async function addPackFromArchive(source, options) {
24706
24876
  process.exitCode = 1;
24707
24877
  return;
24708
24878
  }
24709
- baseDir = path31.join(getProjectRoot(), ".wai", "packs");
24879
+ baseDir = path32.join(getProjectRoot(), ".wai", "packs");
24710
24880
  }
24711
- const bytes = fs22.readFileSync(abs);
24712
- fs22.mkdirSync(baseDir, { recursive: true });
24713
- const staging = fs22.mkdtempSync(path31.join(baseDir, ".wpack-staging-"));
24881
+ const bytes = fs23.readFileSync(abs);
24882
+ fs23.mkdirSync(baseDir, { recursive: true });
24883
+ const staging = fs23.mkdtempSync(path32.join(baseDir, ".wpack-staging-"));
24714
24884
  let result;
24715
24885
  try {
24716
24886
  result = (0, import_sdk.extractPack)(bytes, staging);
24717
24887
  } catch (err) {
24718
- fs22.rmSync(staging, { recursive: true, force: true });
24719
- logger.error(`Failed to extract pack archive "${path31.basename(abs)}": ${err instanceof Error ? err.message : String(err)}`);
24888
+ fs23.rmSync(staging, { recursive: true, force: true });
24889
+ logger.error(`Failed to extract pack archive "${path32.basename(abs)}": ${err instanceof Error ? err.message : String(err)}`);
24720
24890
  process.exitCode = 1;
24721
24891
  return;
24722
24892
  }
24723
24893
  const name = result.name;
24724
- const destDir = path31.join(baseDir, name);
24725
- if (fs22.existsSync(destDir)) fs22.rmSync(destDir, { recursive: true, force: true });
24726
- fs22.renameSync(staging, destDir);
24727
- const probe2 = probePack(destDir, path31.dirname(destDir), scope);
24894
+ const destDir = path32.join(baseDir, name);
24895
+ if (fs23.existsSync(destDir)) fs23.rmSync(destDir, { recursive: true, force: true });
24896
+ fs23.renameSync(staging, destDir);
24897
+ const probe2 = probePack(destDir, path32.dirname(destDir), scope);
24728
24898
  if (probe2.error) {
24729
- fs22.rmSync(destDir, { recursive: true, force: true });
24899
+ fs23.rmSync(destDir, { recursive: true, force: true });
24730
24900
  logger.error(probe2.error);
24731
24901
  process.exitCode = 1;
24732
24902
  return;
@@ -24744,7 +24914,7 @@ async function addPackFromArchive(source, options) {
24744
24914
  saveProjectConfig(config);
24745
24915
  logger.success(`Installed pack "${probe2.name ?? name}" into ${relRef} and registered it in .wai/project.yaml.`);
24746
24916
  } else {
24747
- logger.success(`Pack "${probe2.name ?? name}" already registered \u2014 refreshed ${relRef} from ${path31.basename(abs)}.`);
24917
+ logger.success(`Pack "${probe2.name ?? name}" already registered \u2014 refreshed ${relRef} from ${path32.basename(abs)}.`);
24748
24918
  }
24749
24919
  logger.info(`${describe(probe2)} \u2014 commit .wai/ so CI and every clone enforce it.`);
24750
24920
  }
@@ -24765,7 +24935,7 @@ async function buildPack(source, options = {}) {
24765
24935
  const sourceDir = source && source.length > 0 ? source : ".";
24766
24936
  const result = (0, import_sdk.buildPack)(sourceDir);
24767
24937
  const outPath = options.out ?? result.suggestedFileName;
24768
- fs22.writeFileSync(outPath, result.archive);
24938
+ fs23.writeFileSync(outPath, result.archive);
24769
24939
  logger.success(`Built pack "${result.info.name}" v${result.info.version} \u2192 ${outPath} (${result.archive.byteLength} bytes)`);
24770
24940
  logger.info(`Install it with \`wairon pack add ${outPath}\`, or upload it to a hosted instance.`);
24771
24941
  }
@@ -24779,9 +24949,9 @@ async function listPacks() {
24779
24949
  console.log(import_chalk16.default.bold.cyan(`\u25A0 Global (${globalPacksDir()})${useGlobal ? "" : import_chalk16.default.yellow(" [disabled: extensions.useGlobalPacks: false]")}`));
24780
24950
  if (globalRefs.length === 0) console.log(import_chalk16.default.dim(" (none)"));
24781
24951
  for (const ref of globalRefs) {
24782
- const probe2 = probePack(ref, path31.dirname(ref), "global");
24783
- if (probe2.error) console.log(` ${import_chalk16.default.red("\u2716")} ${path31.basename(ref)} \u2014 ${import_chalk16.default.red(probe2.error)}`);
24784
- else console.log(` ${import_chalk16.default.green("\u25CF")} ${import_chalk16.default.bold(probe2.name ?? path31.basename(ref))} ${import_chalk16.default.dim(describe(probe2))}`);
24952
+ const probe2 = probePack(ref, path32.dirname(ref), "global");
24953
+ if (probe2.error) console.log(` ${import_chalk16.default.red("\u2716")} ${path32.basename(ref)} \u2014 ${import_chalk16.default.red(probe2.error)}`);
24954
+ else console.log(` ${import_chalk16.default.green("\u25CF")} ${import_chalk16.default.bold(probe2.name ?? path32.basename(ref))} ${import_chalk16.default.dim(describe(probe2))}`);
24785
24955
  }
24786
24956
  console.log("");
24787
24957
  if (!inProject) {
@@ -24802,9 +24972,9 @@ async function listPacks() {
24802
24972
  async function removePack(name, options = {}) {
24803
24973
  if (options.global) {
24804
24974
  for (const ref of discoverPacks(globalPacksDir())) {
24805
- const probe2 = probePack(ref, path31.dirname(ref), "global");
24806
- if (probe2.name === name || path31.basename(ref) === name) {
24807
- fs22.rmSync(ref, { recursive: true, force: true });
24975
+ const probe2 = probePack(ref, path32.dirname(ref), "global");
24976
+ if (probe2.name === name || path32.basename(ref) === name) {
24977
+ fs23.rmSync(ref, { recursive: true, force: true });
24808
24978
  logger.success(`Removed global pack "${probe2.name ?? name}" (${ref}).`);
24809
24979
  return;
24810
24980
  }
@@ -24823,16 +24993,16 @@ async function removePack(name, options = {}) {
24823
24993
  const packs = config.extensions?.packs ?? [];
24824
24994
  for (const ref of packs) {
24825
24995
  const probe2 = probePack(ref, root, "project");
24826
- if (probe2.name === name || ref === name || path31.basename(ref) === name) {
24996
+ if (probe2.name === name || ref === name || path32.basename(ref) === name) {
24827
24997
  config.extensions = {
24828
24998
  packs: packs.filter((p) => p !== ref),
24829
24999
  useGlobalPacks: config.extensions?.useGlobalPacks ?? true
24830
25000
  };
24831
25001
  saveProjectConfig(config);
24832
- const resolved = path31.resolve(root, ref);
24833
- const vendorDir = path31.resolve(root, ".wai", "packs");
24834
- if (resolved.startsWith(vendorDir + path31.sep)) {
24835
- fs22.rmSync(resolved, { recursive: true, force: true });
25002
+ const resolved = path32.resolve(root, ref);
25003
+ const vendorDir = path32.resolve(root, ".wai", "packs");
25004
+ if (resolved.startsWith(vendorDir + path32.sep)) {
25005
+ fs23.rmSync(resolved, { recursive: true, force: true });
24836
25006
  logger.success(`Deregistered pack "${probe2.name ?? name}" and deleted ${ref}.`);
24837
25007
  } else {
24838
25008
  logger.success(`Deregistered pack "${probe2.name ?? name}" (files at ${ref} left in place).`);
@@ -24846,8 +25016,8 @@ async function removePack(name, options = {}) {
24846
25016
 
24847
25017
  // src/commands/host.ts
24848
25018
  var fs50 = __toESM(require("fs"));
24849
- var path60 = __toESM(require("path"));
24850
- var os9 = __toESM(require("os"));
25019
+ var path59 = __toESM(require("path"));
25020
+ var os10 = __toESM(require("os"));
24851
25021
  var crypto20 = __toESM(require("crypto"));
24852
25022
  var import_child_process5 = require("child_process");
24853
25023
  var import_chalk17 = __toESM(require("chalk"));
@@ -24874,29 +25044,29 @@ var UNAUTHENTICATED = {
24874
25044
  var WEB_SESSION_PREFIX = "ws_";
24875
25045
 
24876
25046
  // src/server/credentials.ts
24877
- var fs23 = __toESM(require("fs"));
24878
- var path32 = __toESM(require("path"));
25047
+ var fs24 = __toESM(require("fs"));
25048
+ var path33 = __toESM(require("path"));
24879
25049
  var crypto3 = __toESM(require("crypto"));
24880
25050
  var HASH_NS = "wairon:token:v1";
24881
25051
  function hashToken(token) {
24882
25052
  return crypto3.createHash("sha256").update(`${HASH_NS}:${token}`).digest("hex");
24883
25053
  }
24884
25054
  function storePath(dataDir) {
24885
- return path32.join(dataDir, "auth", "credentials.json");
25055
+ return path33.join(dataDir, "auth", "credentials.json");
24886
25056
  }
24887
25057
  function load3(dataDir) {
24888
25058
  try {
24889
- return JSON.parse(fs23.readFileSync(storePath(dataDir), "utf8"));
25059
+ return JSON.parse(fs24.readFileSync(storePath(dataDir), "utf8"));
24890
25060
  } catch {
24891
25061
  return [];
24892
25062
  }
24893
25063
  }
24894
25064
  function save(dataDir, records) {
24895
25065
  const p = storePath(dataDir);
24896
- fs23.mkdirSync(path32.dirname(p), { recursive: true });
25066
+ fs24.mkdirSync(path33.dirname(p), { recursive: true });
24897
25067
  const tmp = `${p}.tmp`;
24898
- fs23.writeFileSync(tmp, JSON.stringify(records, null, 2) + "\n");
24899
- fs23.renameSync(tmp, p);
25068
+ fs24.writeFileSync(tmp, JSON.stringify(records, null, 2) + "\n");
25069
+ fs24.renameSync(tmp, p);
24900
25070
  }
24901
25071
  function digestEquals(a, b) {
24902
25072
  const ab = Buffer.from(a, "hex");
@@ -24941,17 +25111,17 @@ function listByOwner(dataDir, ownerUserId) {
24941
25111
  }
24942
25112
 
24943
25113
  // src/server/websessions.ts
24944
- var fs24 = __toESM(require("fs"));
24945
- var path33 = __toESM(require("path"));
25114
+ var fs25 = __toESM(require("fs"));
25115
+ var path34 = __toESM(require("path"));
24946
25116
  var crypto4 = __toESM(require("crypto"));
24947
25117
  function storePath2(dataDir) {
24948
- return path33.join(dataDir, "web-sessions.json");
25118
+ return path34.join(dataDir, "web-sessions.json");
24949
25119
  }
24950
25120
  function readSessions(dataDir) {
24951
25121
  const p = storePath2(dataDir);
24952
25122
  let raw;
24953
25123
  try {
24954
- raw = fs24.readFileSync(p, "utf8");
25124
+ raw = fs25.readFileSync(p, "utf8");
24955
25125
  } catch (e) {
24956
25126
  if (e.code === "ENOENT") return [];
24957
25127
  throw new Error(`Failed to read web session store at ${p}: ${e.message}`);
@@ -24966,10 +25136,10 @@ function readSessions(dataDir) {
24966
25136
  }
24967
25137
  function persistSessions(dataDir, sessions) {
24968
25138
  const p = storePath2(dataDir);
24969
- fs24.mkdirSync(path33.dirname(p), { recursive: true });
25139
+ fs25.mkdirSync(path34.dirname(p), { recursive: true });
24970
25140
  const tmp = `${p}.tmp`;
24971
- fs24.writeFileSync(tmp, JSON.stringify(sessions, null, 2) + "\n");
24972
- fs24.renameSync(tmp, p);
25141
+ fs25.writeFileSync(tmp, JSON.stringify(sessions, null, 2) + "\n");
25142
+ fs25.renameSync(tmp, p);
24973
25143
  }
24974
25144
  function mintSessionId() {
24975
25145
  return `${WEB_SESSION_PREFIX}${crypto4.randomBytes(24).toString("hex")}`;
@@ -25130,17 +25300,17 @@ function listWebSessionsBySubject(dataDir, userId) {
25130
25300
  }
25131
25301
 
25132
25302
  // src/server/users.ts
25133
- var fs25 = __toESM(require("fs"));
25134
- var path34 = __toESM(require("path"));
25303
+ var fs26 = __toESM(require("fs"));
25304
+ var path35 = __toESM(require("path"));
25135
25305
  var VALID_STATUSES = ["active", "inactive", "suspended", "deactivated", "disabled"];
25136
25306
  function storePath3(dataDir) {
25137
- return path34.join(dataDir, "users.json");
25307
+ return path35.join(dataDir, "users.json");
25138
25308
  }
25139
25309
  function loadStore(dataDir) {
25140
25310
  const p = storePath3(dataDir);
25141
25311
  let raw;
25142
25312
  try {
25143
- raw = fs25.readFileSync(p, "utf8");
25313
+ raw = fs26.readFileSync(p, "utf8");
25144
25314
  } catch (err) {
25145
25315
  if (err.code === "ENOENT") return [];
25146
25316
  throw new Error(`Cannot read hosted-user store at ${p}: ${err.message}`);
@@ -25158,10 +25328,10 @@ function loadStore(dataDir) {
25158
25328
  }
25159
25329
  function replaceAll(dataDir, records) {
25160
25330
  const p = storePath3(dataDir);
25161
- fs25.mkdirSync(path34.dirname(p), { recursive: true });
25331
+ fs26.mkdirSync(path35.dirname(p), { recursive: true });
25162
25332
  const tmp = `${p}.tmp`;
25163
- fs25.writeFileSync(tmp, JSON.stringify(records, null, 2) + "\n");
25164
- fs25.renameSync(tmp, p);
25333
+ fs26.writeFileSync(tmp, JSON.stringify(records, null, 2) + "\n");
25334
+ fs26.renameSync(tmp, p);
25165
25335
  }
25166
25336
  function registryUpsert(dataDir, record2) {
25167
25337
  const records = loadStore(dataDir);
@@ -25263,11 +25433,11 @@ function remapUnitReferences(dataDir, remap, removedScopeIds) {
25263
25433
  }
25264
25434
 
25265
25435
  // src/server/instance.ts
25266
- var fs26 = __toESM(require("fs"));
25267
- var path35 = __toESM(require("path"));
25436
+ var fs27 = __toESM(require("fs"));
25437
+ var path36 = __toESM(require("path"));
25268
25438
  var import_crypto = require("crypto");
25269
25439
  function storePath4(dataDir) {
25270
- return path35.join(dataDir, "instance.json");
25440
+ return path36.join(dataDir, "instance.json");
25271
25441
  }
25272
25442
  var InstanceIdentityStore = class {
25273
25443
  constructor(dataDir) {
@@ -25284,7 +25454,7 @@ var InstanceIdentityStore = class {
25284
25454
  const p = storePath4(this.dataDir);
25285
25455
  let raw;
25286
25456
  try {
25287
- raw = fs26.readFileSync(p, "utf8");
25457
+ raw = fs27.readFileSync(p, "utf8");
25288
25458
  } catch (err) {
25289
25459
  if (err.code === "ENOENT") return null;
25290
25460
  throw new Error(`Cannot read instance identity at ${p}: ${err.message}`);
@@ -25307,10 +25477,10 @@ var InstanceIdentityStore = class {
25307
25477
  * never truncates the file. Only called by the registry's create-once seed. */
25308
25478
  replace(identity) {
25309
25479
  const p = storePath4(this.dataDir);
25310
- fs26.mkdirSync(path35.dirname(p), { recursive: true });
25480
+ fs27.mkdirSync(path36.dirname(p), { recursive: true });
25311
25481
  const tmp = `${p}.tmp`;
25312
- fs26.writeFileSync(tmp, JSON.stringify(identity, null, 2) + "\n");
25313
- fs26.renameSync(tmp, p);
25482
+ fs27.writeFileSync(tmp, JSON.stringify(identity, null, 2) + "\n");
25483
+ fs27.renameSync(tmp, p);
25314
25484
  }
25315
25485
  };
25316
25486
  var InstanceIdentityRegistry = class {
@@ -25366,8 +25536,8 @@ function getInstanceIdentity(dataDir) {
25366
25536
  }
25367
25537
 
25368
25538
  // src/utils/secrets.ts
25369
- var fs27 = __toESM(require("fs"));
25370
- var path36 = __toESM(require("path"));
25539
+ var fs28 = __toESM(require("fs"));
25540
+ var path37 = __toESM(require("path"));
25371
25541
  var ENV_FALLBACK = {
25372
25542
  "git-token": ["WAIRON_GIT_TOKEN"],
25373
25543
  "notion-token": ["WAIRON_NOTION_TOKEN"],
@@ -25376,13 +25546,13 @@ var ENV_FALLBACK = {
25376
25546
  };
25377
25547
  function storePath5() {
25378
25548
  const dataDir = process.env["WAIRON_DATA_DIR"];
25379
- return dataDir ? path36.join(dataDir, "auth", "secrets.json") : null;
25549
+ return dataDir ? path37.join(dataDir, "auth", "secrets.json") : null;
25380
25550
  }
25381
25551
  function readStore() {
25382
25552
  const p = storePath5();
25383
25553
  if (!p) return {};
25384
25554
  try {
25385
- return JSON.parse(fs27.readFileSync(p, "utf8"));
25555
+ return JSON.parse(fs28.readFileSync(p, "utf8"));
25386
25556
  } catch {
25387
25557
  return {};
25388
25558
  }
@@ -25407,10 +25577,10 @@ function setSecret(key, value) {
25407
25577
  if (!p) throw new Error("WAIRON_DATA_DIR is not set \u2014 a running server needs it to store secrets.");
25408
25578
  const store = readStore();
25409
25579
  store[key] = value;
25410
- fs27.mkdirSync(path36.dirname(p), { recursive: true });
25580
+ fs28.mkdirSync(path37.dirname(p), { recursive: true });
25411
25581
  const tmp = `${p}.tmp`;
25412
- fs27.writeFileSync(tmp, JSON.stringify(store, null, 2) + "\n");
25413
- fs27.renameSync(tmp, p);
25582
+ fs28.writeFileSync(tmp, JSON.stringify(store, null, 2) + "\n");
25583
+ fs28.renameSync(tmp, p);
25414
25584
  }
25415
25585
  function listSecretKeys() {
25416
25586
  return Object.keys(readStore());
@@ -25618,17 +25788,17 @@ function verifySsoState(state) {
25618
25788
  }
25619
25789
 
25620
25790
  // src/server/organization.ts
25621
- var fs28 = __toESM(require("fs"));
25622
- var path37 = __toESM(require("path"));
25791
+ var fs29 = __toESM(require("fs"));
25792
+ var path38 = __toESM(require("path"));
25623
25793
  var crypto6 = __toESM(require("crypto"));
25624
25794
  function storePath6(dataDir) {
25625
- return path37.join(dataDir, "organization.json");
25795
+ return path38.join(dataDir, "organization.json");
25626
25796
  }
25627
25797
  function readState(dataDir) {
25628
25798
  const p = storePath6(dataDir);
25629
25799
  let raw;
25630
25800
  try {
25631
- raw = fs28.readFileSync(p, "utf8");
25801
+ raw = fs29.readFileSync(p, "utf8");
25632
25802
  } catch (e) {
25633
25803
  if (e.code === "ENOENT") return { units: [], placements: [] };
25634
25804
  throw new Error(`Failed to read organization store at ${p}: ${e.message}`);
@@ -25645,10 +25815,10 @@ function readState(dataDir) {
25645
25815
  }
25646
25816
  function persistState(dataDir, state) {
25647
25817
  const p = storePath6(dataDir);
25648
- fs28.mkdirSync(path37.dirname(p), { recursive: true });
25818
+ fs29.mkdirSync(path38.dirname(p), { recursive: true });
25649
25819
  const tmp = `${p}.tmp`;
25650
- fs28.writeFileSync(tmp, JSON.stringify(state, null, 2) + "\n");
25651
- fs28.renameSync(tmp, p);
25820
+ fs29.writeFileSync(tmp, JSON.stringify(state, null, 2) + "\n");
25821
+ fs29.renameSync(tmp, p);
25652
25822
  }
25653
25823
  var SLUG_PATTERN = /^[a-z0-9-]+$/;
25654
25824
  var UNIT_KINDS = ["business_entity", "department", "team", "group"];
@@ -26017,11 +26187,11 @@ function getOrganizationUnit(dataDir, id) {
26017
26187
  }
26018
26188
 
26019
26189
  // src/server/permissions.ts
26020
- var fs29 = __toESM(require("fs"));
26021
- var path38 = __toESM(require("path"));
26190
+ var fs30 = __toESM(require("fs"));
26191
+ var path39 = __toESM(require("path"));
26022
26192
  var import_crypto2 = require("crypto");
26023
26193
  function storePath7(dataDir) {
26024
- return path38.join(dataDir, "permissions.json");
26194
+ return path39.join(dataDir, "permissions.json");
26025
26195
  }
26026
26196
  function assignmentKey(a) {
26027
26197
  return [a.subjectKind, a.subjectId ?? "", a.scopeKind, a.scopeId ?? "", a.capability].join("|");
@@ -26030,7 +26200,7 @@ function load4(dataDir) {
26030
26200
  const p = storePath7(dataDir);
26031
26201
  let raw;
26032
26202
  try {
26033
- raw = fs29.readFileSync(p, "utf8");
26203
+ raw = fs30.readFileSync(p, "utf8");
26034
26204
  } catch (err) {
26035
26205
  if (err.code === "ENOENT") return [];
26036
26206
  throw new Error(`Cannot read permission store at ${p}: ${err.message}`);
@@ -26048,10 +26218,10 @@ function load4(dataDir) {
26048
26218
  }
26049
26219
  function replaceAll2(dataDir, assignments) {
26050
26220
  const p = storePath7(dataDir);
26051
- fs29.mkdirSync(path38.dirname(p), { recursive: true });
26221
+ fs30.mkdirSync(path39.dirname(p), { recursive: true });
26052
26222
  const tmp = `${p}.tmp`;
26053
- fs29.writeFileSync(tmp, JSON.stringify(assignments, null, 2) + "\n");
26054
- fs29.renameSync(tmp, p);
26223
+ fs30.writeFileSync(tmp, JSON.stringify(assignments, null, 2) + "\n");
26224
+ fs30.renameSync(tmp, p);
26055
26225
  }
26056
26226
  function registrySet(dataDir, assignment) {
26057
26227
  const assignments = load4(dataDir);
@@ -26137,8 +26307,8 @@ function getAssignment(dataDir, assignmentId) {
26137
26307
  }
26138
26308
 
26139
26309
  // src/server/roles.ts
26140
- var fs30 = __toESM(require("fs"));
26141
- var path39 = __toESM(require("path"));
26310
+ var fs31 = __toESM(require("fs"));
26311
+ var path40 = __toESM(require("path"));
26142
26312
  var BUILTIN_ROLES = [
26143
26313
  {
26144
26314
  id: SSO_ADMIN_ROLE_ID,
@@ -26156,13 +26326,13 @@ function isBuiltinRoleId(roleId) {
26156
26326
  return BUILTIN_ROLE_IDS.has(roleId);
26157
26327
  }
26158
26328
  function storePath8(dataDir) {
26159
- return path39.join(dataDir, "roles.json");
26329
+ return path40.join(dataDir, "roles.json");
26160
26330
  }
26161
26331
  function load5(dataDir) {
26162
26332
  const p = storePath8(dataDir);
26163
26333
  let raw;
26164
26334
  try {
26165
- raw = fs30.readFileSync(p, "utf8");
26335
+ raw = fs31.readFileSync(p, "utf8");
26166
26336
  } catch (err) {
26167
26337
  if (err.code === "ENOENT") return [];
26168
26338
  throw new Error(`Cannot read role store at ${p}: ${err.message}`);
@@ -26180,10 +26350,10 @@ function load5(dataDir) {
26180
26350
  }
26181
26351
  function replaceAll3(dataDir, roles) {
26182
26352
  const p = storePath8(dataDir);
26183
- fs30.mkdirSync(path39.dirname(p), { recursive: true });
26353
+ fs31.mkdirSync(path40.dirname(p), { recursive: true });
26184
26354
  const tmp = `${p}.tmp`;
26185
- fs30.writeFileSync(tmp, JSON.stringify(roles, null, 2) + "\n");
26186
- fs30.renameSync(tmp, p);
26355
+ fs31.writeFileSync(tmp, JSON.stringify(roles, null, 2) + "\n");
26356
+ fs31.renameSync(tmp, p);
26187
26357
  }
26188
26358
  function registryCreate(dataDir, role) {
26189
26359
  if (isBuiltinRoleId(role.id)) {
@@ -26417,131 +26587,14 @@ function actionableUnitIds(scopes) {
26417
26587
  }
26418
26588
 
26419
26589
  // src/server/projects.ts
26420
- var fs31 = __toESM(require("fs"));
26421
- var path40 = __toESM(require("path"));
26422
- var ID_RE = /^[a-z0-9][a-z0-9-]{0,63}$/;
26423
- function isValidProjectId(id) {
26424
- return typeof id === "string" && ID_RE.test(id);
26425
- }
26426
- function registryPath(dataDir) {
26427
- return path40.join(dataDir, "projects.json");
26428
- }
26429
- function load6(dataDir) {
26430
- try {
26431
- return JSON.parse(fs31.readFileSync(registryPath(dataDir), "utf8"));
26432
- } catch {
26433
- return [];
26434
- }
26435
- }
26436
- function save2(dataDir, records) {
26437
- const p = registryPath(dataDir);
26438
- fs31.mkdirSync(path40.dirname(p), { recursive: true });
26439
- const tmp = `${p}.tmp`;
26440
- fs31.writeFileSync(tmp, JSON.stringify(records, null, 2) + "\n");
26441
- fs31.renameSync(tmp, p);
26442
- }
26443
- function projectRoot(dataDir, id) {
26444
- return path40.join(dataDir, "projects", id);
26445
- }
26446
- function existingProjectRoot(dataDir, id) {
26447
- if (!isValidProjectId(id)) return null;
26448
- const rec = load6(dataDir).find((r) => r.id === id);
26449
- return rec ? rec.rootPath : null;
26450
- }
26451
- function createProjectRecord(dataDir, id) {
26452
- if (!isValidProjectId(id)) {
26453
- throw new Error(`Invalid project id "${id}" (allowed: lowercase letters, digits, hyphen).`);
26454
- }
26455
- const records = load6(dataDir);
26456
- if (records.some((r) => r.id === id)) {
26457
- throw new Error(`Project "${id}" already exists.`);
26458
- }
26459
- const root = projectRoot(dataDir, id);
26460
- fs31.mkdirSync(root, { recursive: true });
26461
- const record2 = {
26462
- id,
26463
- rootPath: root,
26464
- status: "active",
26465
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
26466
- };
26467
- records.push(record2);
26468
- save2(dataDir, records);
26469
- return record2;
26470
- }
26471
- function registerLocalDevProject(dataDir, id, rootPath) {
26472
- if (!isValidProjectId(id)) {
26473
- throw new Error(`Invalid project id "${id}" (allowed: lowercase letters, digits, hyphen).`);
26474
- }
26475
- const records = load6(dataDir);
26476
- const existing = records.find((r) => r.id === id);
26477
- const record2 = {
26478
- id,
26479
- rootPath,
26480
- status: "active",
26481
- createdAt: existing?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()
26482
- };
26483
- const next = existing ? records.map((r) => r.id === id ? record2 : r) : [...records, record2];
26484
- save2(dataDir, next);
26485
- return record2;
26486
- }
26487
- function listProjectRecords(dataDir) {
26488
- return load6(dataDir);
26489
- }
26490
- function removeProjectRecord(dataDir, id) {
26491
- const records = load6(dataDir);
26492
- const rec = records.find((r) => r.id === id);
26493
- if (rec) {
26494
- try {
26495
- fs31.rmSync(rec.rootPath, { recursive: true, force: true });
26496
- } catch {
26497
- }
26498
- }
26499
- save2(dataDir, records.filter((r) => r.id !== id));
26500
- }
26501
- function resolveProjectRoot(dataDir, principal, selector) {
26502
- const authorized = principal.projects;
26503
- const wildcard = authorized.includes("*");
26504
- let target;
26505
- if (selector) {
26506
- if (!wildcard && !authorized.includes(selector)) return null;
26507
- target = selector;
26508
- } else if (!wildcard && authorized.length === 1) {
26509
- target = authorized[0];
26510
- } else {
26511
- return null;
26512
- }
26513
- if (!isValidProjectId(target)) return null;
26514
- const rec = load6(dataDir).find((r) => r.id === target);
26515
- if (!rec || rec.status !== "active") return null;
26516
- return rec.rootPath;
26517
- }
26518
-
26519
- // src/server/adapters.ts
26520
- init_statehash();
26521
-
26522
- // src/core/lockfile.ts
26523
- var fs32 = __toESM(require("fs"));
26524
- var path41 = __toESM(require("path"));
26590
+ var fs35 = __toESM(require("fs"));
26591
+ var path44 = __toESM(require("path"));
26592
+ init_loader();
26593
+ init_yaml();
26525
26594
  init_fs();
26526
- function lockPath() {
26527
- return aiDir("lock.json");
26528
- }
26529
- function readLockRecord() {
26530
- try {
26531
- return JSON.parse(fs32.readFileSync(lockPath(), "utf8"));
26532
- } catch {
26533
- return null;
26534
- }
26535
- }
26536
- function writeLockRecord(record2) {
26537
- const p = lockPath();
26538
- fs32.mkdirSync(path41.dirname(p), { recursive: true });
26539
- const tmp = `${p}.tmp`;
26540
- fs32.writeFileSync(tmp, JSON.stringify(record2, null, 2) + "\n");
26541
- fs32.renameSync(tmp, p);
26542
- }
26543
26595
 
26544
26596
  // src/server/adapters.ts
26597
+ init_statehash();
26545
26598
  init_specs2();
26546
26599
  init_provision();
26547
26600
  init_validation();
@@ -26552,37 +26605,37 @@ init_types();
26552
26605
  init_server();
26553
26606
 
26554
26607
  // src/git/config.ts
26555
- var fs33 = __toESM(require("fs"));
26556
- var path42 = __toESM(require("path"));
26608
+ var fs32 = __toESM(require("fs"));
26609
+ var path41 = __toESM(require("path"));
26557
26610
  init_fs();
26558
26611
  function configPath() {
26559
26612
  return aiDir("git.json");
26560
26613
  }
26561
26614
  function readGitConfig() {
26562
26615
  try {
26563
- return JSON.parse(fs33.readFileSync(configPath(), "utf8"));
26616
+ return JSON.parse(fs32.readFileSync(configPath(), "utf8"));
26564
26617
  } catch {
26565
26618
  return null;
26566
26619
  }
26567
26620
  }
26568
26621
  function writeGitConfig(config) {
26569
26622
  const p = configPath();
26570
- fs33.mkdirSync(path42.dirname(p), { recursive: true });
26623
+ fs32.mkdirSync(path41.dirname(p), { recursive: true });
26571
26624
  const tmp = `${p}.tmp`;
26572
- fs33.writeFileSync(tmp, JSON.stringify(config, null, 2) + "\n");
26573
- fs33.renameSync(tmp, p);
26625
+ fs32.writeFileSync(tmp, JSON.stringify(config, null, 2) + "\n");
26626
+ fs32.renameSync(tmp, p);
26574
26627
  }
26575
26628
  function clearGitConfig() {
26576
26629
  try {
26577
- fs33.rmSync(configPath(), { force: true });
26630
+ fs32.rmSync(configPath(), { force: true });
26578
26631
  } catch {
26579
26632
  }
26580
26633
  }
26581
26634
 
26582
26635
  // src/git/adapter.ts
26583
26636
  var import_child_process3 = require("child_process");
26584
- var fs34 = __toESM(require("fs"));
26585
- var path43 = __toESM(require("path"));
26637
+ var fs33 = __toESM(require("fs"));
26638
+ var path42 = __toESM(require("path"));
26586
26639
  init_fs();
26587
26640
  function git(args, cwd) {
26588
26641
  return (0, import_child_process3.execFileSync)("git", args, {
@@ -26634,10 +26687,10 @@ function compareUrl(remote, defaultBranch, workingBranch) {
26634
26687
  return `${web}/compare/${encodeURIComponent(defaultBranch)}...${encodeURIComponent(workingBranch)}`;
26635
26688
  }
26636
26689
  function excludeLocalFiles() {
26637
- const excludePath = path43.join(getProjectRoot(), ".git", "info", "exclude");
26690
+ const excludePath = path42.join(getProjectRoot(), ".git", "info", "exclude");
26638
26691
  try {
26639
- fs34.mkdirSync(path43.dirname(excludePath), { recursive: true });
26640
- fs34.appendFileSync(excludePath, "\n.wai/lock.json\n.wai/git.json\n");
26692
+ fs33.mkdirSync(path42.dirname(excludePath), { recursive: true });
26693
+ fs33.appendFileSync(excludePath, "\n.wai/lock.json\n.wai/git.json\n");
26641
26694
  } catch {
26642
26695
  }
26643
26696
  }
@@ -26702,39 +26755,39 @@ function configureSync(periodicSyncMinutes, skipIfClean) {
26702
26755
  }
26703
26756
 
26704
26757
  // src/producers/config.ts
26705
- var fs35 = __toESM(require("fs"));
26706
- var path44 = __toESM(require("path"));
26758
+ var fs34 = __toESM(require("fs"));
26759
+ var path43 = __toESM(require("path"));
26707
26760
  init_fs();
26708
26761
  function configPath2() {
26709
26762
  return aiDir("producers.json");
26710
26763
  }
26711
- function load7() {
26764
+ function load6() {
26712
26765
  try {
26713
- return JSON.parse(fs35.readFileSync(configPath2(), "utf8"));
26766
+ return JSON.parse(fs34.readFileSync(configPath2(), "utf8"));
26714
26767
  } catch {
26715
26768
  return [];
26716
26769
  }
26717
26770
  }
26718
- function save3(configs) {
26771
+ function save2(configs) {
26719
26772
  const p = configPath2();
26720
- fs35.mkdirSync(path44.dirname(p), { recursive: true });
26773
+ fs34.mkdirSync(path43.dirname(p), { recursive: true });
26721
26774
  const tmp = `${p}.tmp`;
26722
- fs35.writeFileSync(tmp, JSON.stringify(configs, null, 2) + "\n");
26723
- fs35.renameSync(tmp, p);
26775
+ fs34.writeFileSync(tmp, JSON.stringify(configs, null, 2) + "\n");
26776
+ fs34.renameSync(tmp, p);
26724
26777
  }
26725
26778
  function readProducerConfig(target) {
26726
- return load7().find((c) => c.target === target) ?? null;
26779
+ return load6().find((c) => c.target === target) ?? null;
26727
26780
  }
26728
26781
  function writeProducerConfig(config) {
26729
- const configs = load7().filter((c) => c.target !== config.target);
26782
+ const configs = load6().filter((c) => c.target !== config.target);
26730
26783
  configs.push(config);
26731
- save3(configs);
26784
+ save2(configs);
26732
26785
  }
26733
26786
  function clearProducerConfig(target) {
26734
- save3(load7().filter((c) => c.target !== target));
26787
+ save2(load6().filter((c) => c.target !== target));
26735
26788
  }
26736
26789
  function listProducerConfigs() {
26737
- return load7();
26790
+ return load6();
26738
26791
  }
26739
26792
 
26740
26793
  // src/producers/core-adapter.ts
@@ -27075,6 +27128,9 @@ var hostCore = {
27075
27128
  * read of a bundled constant. */
27076
27129
  builtinProfileIds: () => [...BUILTIN_PROFILES]
27077
27130
  };
27131
+ function resolveContainedProjectPath(projectRoot2, projectPath) {
27132
+ return assertContainedProjectPath(projectRoot2, projectPath);
27133
+ }
27078
27134
  function validateProjectAsComplete() {
27079
27135
  const config = loadProjectConfig();
27080
27136
  return validateAsComplete({ rules: config.rules, projectType: config.projectType });
@@ -27106,6 +27162,180 @@ var hostSdk = {
27106
27162
  extractArchive: (archive, destDir, limits) => sdkPortal.extractPack(archive, destDir, limits)
27107
27163
  };
27108
27164
 
27165
+ // src/server/projects.ts
27166
+ var ID_RE = /^[a-z0-9][a-z0-9-]{0,63}$/;
27167
+ function isValidProjectId(id) {
27168
+ return typeof id === "string" && ID_RE.test(id);
27169
+ }
27170
+ function registryPath(dataDir) {
27171
+ return path44.join(dataDir, "projects.json");
27172
+ }
27173
+ function load7(dataDir) {
27174
+ try {
27175
+ return JSON.parse(fs35.readFileSync(registryPath(dataDir), "utf8"));
27176
+ } catch {
27177
+ return [];
27178
+ }
27179
+ }
27180
+ function save3(dataDir, records) {
27181
+ const p = registryPath(dataDir);
27182
+ fs35.mkdirSync(path44.dirname(p), { recursive: true });
27183
+ const tmp = `${p}.tmp`;
27184
+ fs35.writeFileSync(tmp, JSON.stringify(records, null, 2) + "\n");
27185
+ fs35.renameSync(tmp, p);
27186
+ }
27187
+ function projectRoot(dataDir, id) {
27188
+ return path44.join(dataDir, "projects", id);
27189
+ }
27190
+ function existingProjectRoot(dataDir, id) {
27191
+ if (!isValidProjectId(id)) return null;
27192
+ const rec = load7(dataDir).find((r) => r.id === id);
27193
+ return rec ? rec.rootPath : null;
27194
+ }
27195
+ function createProjectRecord(dataDir, id) {
27196
+ if (!isValidProjectId(id)) {
27197
+ throw new Error(`Invalid project id "${id}" (allowed: lowercase letters, digits, hyphen).`);
27198
+ }
27199
+ const records = load7(dataDir);
27200
+ if (records.some((r) => r.id === id)) {
27201
+ throw new Error(`Project "${id}" already exists.`);
27202
+ }
27203
+ const root = projectRoot(dataDir, id);
27204
+ fs35.mkdirSync(root, { recursive: true });
27205
+ const record2 = {
27206
+ id,
27207
+ rootPath: root,
27208
+ status: "active",
27209
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
27210
+ };
27211
+ records.push(record2);
27212
+ save3(dataDir, records);
27213
+ return record2;
27214
+ }
27215
+ function registerLocalDevProject(dataDir, id, rootPath) {
27216
+ if (!isValidProjectId(id)) {
27217
+ throw new Error(`Invalid project id "${id}" (allowed: lowercase letters, digits, hyphen).`);
27218
+ }
27219
+ const records = load7(dataDir);
27220
+ const existing = records.find((r) => r.id === id);
27221
+ const record2 = {
27222
+ id,
27223
+ rootPath,
27224
+ status: "active",
27225
+ createdAt: existing?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()
27226
+ };
27227
+ const next = existing ? records.map((r) => r.id === id ? record2 : r) : [...records, record2];
27228
+ save3(dataDir, next);
27229
+ return record2;
27230
+ }
27231
+ function listProjectRecords(dataDir) {
27232
+ return load7(dataDir);
27233
+ }
27234
+ function removeProjectRecord(dataDir, id) {
27235
+ const records = load7(dataDir);
27236
+ const rec = records.find((r) => r.id === id);
27237
+ if (rec) {
27238
+ try {
27239
+ fs35.rmSync(rec.rootPath, { recursive: true, force: true });
27240
+ } catch {
27241
+ }
27242
+ }
27243
+ save3(dataDir, records.filter((r) => r.id !== id));
27244
+ }
27245
+ var SUBPROJECT_SEPARATOR = "::";
27246
+ function parseQualifiedSelector(value) {
27247
+ if (typeof value !== "string" || value.length === 0) return null;
27248
+ const [projectId, ...mounts] = value.split(SUBPROJECT_SEPARATOR);
27249
+ if (!isValidProjectId(projectId)) return null;
27250
+ if (mounts.some((m) => m.trim() === "")) return null;
27251
+ return { projectId, mounts };
27252
+ }
27253
+ function findSubsystemSpec(root, subsystemId) {
27254
+ const specsDir = aiPathsAt(root).specsDir();
27255
+ if (!fs35.existsSync(specsDir)) return null;
27256
+ for (const file of listFilesRecursive(specsDir, ".yaml")) {
27257
+ let raw;
27258
+ try {
27259
+ raw = readYamlFile(file);
27260
+ } catch {
27261
+ continue;
27262
+ }
27263
+ if (!raw || typeof raw !== "object" || !("parentSystem" in raw)) continue;
27264
+ if (raw.id !== subsystemId) continue;
27265
+ const pp = raw.projectPath;
27266
+ return typeof pp === "string" && pp.trim() !== "" ? { projectPath: pp } : {};
27267
+ }
27268
+ return null;
27269
+ }
27270
+ function resolveSubprojectMounts(projectId, projectRoot2, mounts) {
27271
+ let root = projectRoot2;
27272
+ let at = projectId;
27273
+ for (const mount of mounts) {
27274
+ const sub = findSubsystemSpec(root, mount);
27275
+ if (!sub) {
27276
+ throw new Error(
27277
+ `unknown subproject mount "${mount}" on "${at}" \u2014 no subsystem with that id exists in its spec tree`
27278
+ );
27279
+ }
27280
+ if (!sub.projectPath) {
27281
+ throw new Error(
27282
+ `subsystem "${mount}" on "${at}" is not a chained subproject (it carries no projectPath) \u2014 only a subsystem mounted via projectPath can be bound as a subproject`
27283
+ );
27284
+ }
27285
+ root = resolveContainedProjectPath(root, sub.projectPath);
27286
+ at = `${at}${SUBPROJECT_SEPARATOR}${mount}`;
27287
+ }
27288
+ return root;
27289
+ }
27290
+ function assertMintableNarrowingEntry(dataDir, entry) {
27291
+ if (entry === "*") return;
27292
+ const parsed = parseQualifiedSelector(entry);
27293
+ if (!parsed) {
27294
+ throw new Error(
27295
+ `invalid project narrowing entry "${entry}" (expected a project id, optionally subproject-qualified as projectId::subsystemId)`
27296
+ );
27297
+ }
27298
+ const rec = load7(dataDir).find((r) => r.id === parsed.projectId);
27299
+ if (!rec) throw new Error(`unknown project "${parsed.projectId}"`);
27300
+ if (parsed.mounts.length > 0) {
27301
+ resolveSubprojectMounts(parsed.projectId, rec.rootPath, parsed.mounts);
27302
+ }
27303
+ }
27304
+ function narrowingCovers(entry, target) {
27305
+ return target === entry || target.startsWith(entry + SUBPROJECT_SEPARATOR);
27306
+ }
27307
+ function resolveProjectBinding(dataDir, principal, selector) {
27308
+ const authorized = principal.projects;
27309
+ const wildcard = authorized.includes("*");
27310
+ let target;
27311
+ if (selector) {
27312
+ if (!wildcard && !authorized.some((e) => e !== "*" && narrowingCovers(e, selector))) return null;
27313
+ target = selector;
27314
+ } else if (!wildcard && authorized.length === 1) {
27315
+ target = authorized[0];
27316
+ } else {
27317
+ return null;
27318
+ }
27319
+ const parsed = parseQualifiedSelector(target);
27320
+ if (!parsed) return null;
27321
+ const rec = load7(dataDir).find((r) => r.id === parsed.projectId);
27322
+ if (!rec || rec.status !== "active") return null;
27323
+ let rootPath = rec.rootPath;
27324
+ if (parsed.mounts.length > 0) {
27325
+ try {
27326
+ rootPath = resolveSubprojectMounts(parsed.projectId, rec.rootPath, parsed.mounts);
27327
+ } catch {
27328
+ return null;
27329
+ }
27330
+ }
27331
+ const binding = { rootPath, projectId: parsed.projectId };
27332
+ if (parsed.mounts.length > 0) binding.subproject = parsed.mounts.join(SUBPROJECT_SEPARATOR);
27333
+ return binding;
27334
+ }
27335
+ function resolveProjectRoot(dataDir, principal, selector) {
27336
+ return resolveProjectBinding(dataDir, principal, selector)?.rootPath ?? null;
27337
+ }
27338
+
27109
27339
  // src/server/errors.ts
27110
27340
  var UnauthenticatedError = class extends Error {
27111
27341
  constructor() {
@@ -29131,11 +29361,8 @@ function mintToken(cfg, credential, request) {
29131
29361
  }
29132
29362
  assertNotReservedSubjectId(cfg, [request.ownerUserId]);
29133
29363
  const projects = request.projects?.length ? request.projects : ["*"];
29134
- const knownProjects = new Set(listProjectRecords(cfg.dataDir).map((p) => p.id));
29135
29364
  for (const p of projects) {
29136
- if (p !== "*" && !knownProjects.has(p)) {
29137
- throw new Error(`unknown project "${p}"`);
29138
- }
29365
+ assertMintableNarrowingEntry(cfg.dataDir, p);
29139
29366
  }
29140
29367
  const owner = findUserByRecordOrSubjectId(cfg.dataDir, request.ownerUserId);
29141
29368
  if (owner && owner.status !== "active") {
@@ -29172,12 +29399,21 @@ function revokeToken(cfg, credential, tokenId) {
29172
29399
  }
29173
29400
  function mintSelfToken(cfg, credential, projectId, write) {
29174
29401
  const principal = requirePrincipal5(cfg, credential);
29175
- if (authorize(cfg.dataDir, principal, PROJECT_READ_CAPABILITY2, "project", projectId).value !== "yes") {
29402
+ const parsed = parseQualifiedSelector(projectId);
29403
+ if (!parsed) {
29404
+ throw new Error(
29405
+ `invalid project id "${projectId}" (expected a project id, optionally subproject-qualified as projectId::subsystemId)`
29406
+ );
29407
+ }
29408
+ if (authorize(cfg.dataDir, principal, PROJECT_READ_CAPABILITY2, "project", parsed.projectId).value !== "yes") {
29176
29409
  throw new ForbiddenError("caller lacks project:read on the requested project");
29177
29410
  }
29178
- if (write && authorize(cfg.dataDir, principal, PROJECT_WRITE_CAPABILITY2, "project", projectId).value !== "yes") {
29411
+ if (write && authorize(cfg.dataDir, principal, PROJECT_WRITE_CAPABILITY2, "project", parsed.projectId).value !== "yes") {
29179
29412
  throw new ForbiddenError("caller lacks project:write on the requested project");
29180
29413
  }
29414
+ if (parsed.mounts.length > 0) {
29415
+ assertMintableNarrowingEntry(cfg.dataDir, projectId);
29416
+ }
29181
29417
  const token = "wk_" + crypto10.randomBytes(24).toString("hex");
29182
29418
  const owner = auditActor(principal);
29183
29419
  const record2 = {
@@ -29789,12 +30025,12 @@ function resolveVisibility(observerProjectId, units, placements) {
29789
30025
  const best = /* @__PURE__ */ new Map();
29790
30026
  for (const placement of placements) {
29791
30027
  if (placement.projectId === observerProjectId) continue;
29792
- const path62 = chainOf(placement.unitId, unitById);
29793
- if (!path62.length) continue;
29794
- const closedOk = path62.every((u) => effectivePosture(u, unitById) !== "closed" || observerInside(u.id) || grantedTo(u));
30028
+ const path61 = chainOf(placement.unitId, unitById);
30029
+ if (!path61.length) continue;
30030
+ const closedOk = path61.every((u) => effectivePosture(u, unitById) !== "closed" || observerInside(u.id) || grantedTo(u));
29795
30031
  if (!closedOk) continue;
29796
- const crossTenant = !tenantRoots.has(path62[path62.length - 1].id);
29797
- if (crossTenant && !path62.some(grantedTo)) continue;
30032
+ const crossTenant = !tenantRoots.has(path61[path61.length - 1].id);
30033
+ if (crossTenant && !path61.some(grantedTo)) continue;
29798
30034
  if (crossTenant && directUnits.length === 0) continue;
29799
30035
  const distance = crossTenant ? "partner" : sameBranch(placement.unitId) ? "department" : "instance";
29800
30036
  const existing = best.get(placement.projectId);
@@ -30733,10 +30969,9 @@ function migratePermissionModel(dataDir, apply) {
30733
30969
  // src/server/http.ts
30734
30970
  var http2 = __toESM(require("http"));
30735
30971
  var fs49 = __toESM(require("fs"));
30736
- var path59 = __toESM(require("path"));
30972
+ var path58 = __toESM(require("path"));
30737
30973
 
30738
30974
  // src/server/request.ts
30739
- var path58 = __toESM(require("path"));
30740
30975
  var import_streamableHttp = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
30741
30976
  init_fs();
30742
30977
 
@@ -35774,8 +36009,8 @@ var RealtimeHub = class {
35774
36009
  * complete the handshake, and register the connection. A bad path or session
35775
36010
  * destroys the socket. */
35776
36011
  handleUpgrade(cfg, req, socket) {
35777
- const path62 = (req.url ?? "/").split("?")[0];
35778
- if (path62 !== REALTIME_PATH || !isWebSocketUpgrade(req)) {
36012
+ const path61 = (req.url ?? "/").split("?")[0];
36013
+ if (path61 !== REALTIME_PATH || !isWebSocketUpgrade(req)) {
35779
36014
  socket.destroy();
35780
36015
  return;
35781
36016
  }
@@ -35920,7 +36155,7 @@ function deriveMcpOutcome(response) {
35920
36155
  if (r.result && typeof r.result === "object" && r.result.isError) return "failed";
35921
36156
  return "success";
35922
36157
  }
35923
- function auditToolCall(dataDir, principal, projectId, body, outcome) {
36158
+ function auditToolCall(dataDir, principal, projectId, body, outcome, subproject) {
35924
36159
  const target = mcpToolTarget(body);
35925
36160
  if (!target) return;
35926
36161
  const actor = principal.subject ?? {
@@ -35938,7 +36173,8 @@ function auditToolCall(dataDir, principal, projectId, body, outcome) {
35938
36173
  actor,
35939
36174
  tokenId: principal.tokenId,
35940
36175
  projectId,
35941
- target
36176
+ target,
36177
+ ...subproject ? { metadata: JSON.stringify({ subproject }) } : {}
35942
36178
  };
35943
36179
  try {
35944
36180
  appendAuditEvent(dataDir, event, DEFAULT_AUDIT_POLICY);
@@ -36142,8 +36378,8 @@ async function handleMcpRequest(cfg, req, res, body, credential) {
36142
36378
  permissionSubject: { subjectId: "anonymous", roleBindings: [], instanceAdmin: true }
36143
36379
  };
36144
36380
  }
36145
- const root = resolveProjectRoot(cfg.dataDir, principal, projectSelector(req));
36146
- if (!root) {
36381
+ const binding = resolveProjectBinding(cfg.dataDir, principal, projectSelector(req));
36382
+ if (!binding) {
36147
36383
  sendJson(res, 403, { error: "project not authorized, unknown, or not specified" });
36148
36384
  return;
36149
36385
  }
@@ -36158,19 +36394,20 @@ async function handleMcpRequest(cfg, req, res, body, credential) {
36158
36394
  });
36159
36395
  return;
36160
36396
  }
36161
- await runWithProjectRoot(root, async () => {
36162
- const projectId = path58.basename(root);
36397
+ await runWithProjectRoot(binding.rootPath, async () => {
36398
+ const projectId = binding.projectId;
36399
+ const subproject = binding.subproject;
36163
36400
  const dispatchedResponse = await dispatchProjectLifecycleTool(cfg, cred, projectId, body);
36164
36401
  if (dispatchedResponse !== void 0) {
36165
36402
  sendJson(res, 200, dispatchedResponse);
36166
- auditToolCall(cfg.dataDir, principal, projectId, body, deriveMcpOutcome(dispatchedResponse));
36403
+ auditToolCall(cfg.dataDir, principal, projectId, body, deriveMcpOutcome(dispatchedResponse), subproject);
36167
36404
  for (const ch of mcpChangeChannels(body, projectId, dispatchedResponse)) publishChange(ch);
36168
36405
  return;
36169
36406
  }
36170
36407
  const permissionError = dataPlanePermissionError(cfg, principal, projectId, body);
36171
36408
  if (permissionError !== void 0) {
36172
36409
  sendJson(res, 200, permissionError);
36173
- auditToolCall(cfg.dataDir, principal, projectId, body, deriveMcpOutcome(permissionError));
36410
+ auditToolCall(cfg.dataDir, principal, projectId, body, deriveMcpOutcome(permissionError), subproject);
36174
36411
  return;
36175
36412
  }
36176
36413
  const server = createScopedServer();
@@ -36191,7 +36428,7 @@ async function handleMcpRequest(cfg, req, res, body, credential) {
36191
36428
  };
36192
36429
  await server.connect(transport);
36193
36430
  await transport.handleRequest(req, res, body);
36194
- auditToolCall(cfg.dataDir, principal, projectId, body, deriveMcpOutcome(response));
36431
+ auditToolCall(cfg.dataDir, principal, projectId, body, deriveMcpOutcome(response), subproject);
36195
36432
  for (const ch of mcpChangeChannels(body, projectId, response)) publishChange(ch);
36196
36433
  });
36197
36434
  }
@@ -36626,7 +36863,7 @@ function routeData(cfg, req, res) {
36626
36863
  }
36627
36864
  function readExposurePolicyFile(dataDir) {
36628
36865
  try {
36629
- const raw = fs49.readFileSync(path59.join(dataDir, "exposure-policy.json"), "utf8");
36866
+ const raw = fs49.readFileSync(path58.join(dataDir, "exposure-policy.json"), "utf8");
36630
36867
  const parsed = JSON.parse(raw);
36631
36868
  return parsed && typeof parsed === "object" ? parsed : void 0;
36632
36869
  } catch {
@@ -37311,9 +37548,9 @@ function seedDemoTree() {
37311
37548
 
37312
37549
  // src/commands/host.ts
37313
37550
  function resolveHostConfig(options) {
37314
- const dataDir = options.dataDir || process.env["WAIRON_DATA_DIR"] || path60.join(os9.homedir(), ".wairon", "data");
37551
+ const dataDir = options.dataDir || process.env["WAIRON_DATA_DIR"] || path59.join(os10.homedir(), ".wairon", "data");
37315
37552
  if (!process.env["WAIRON_PACKS_DIR"]) {
37316
- process.env["WAIRON_PACKS_DIR"] = path60.join(dataDir, "packs");
37553
+ process.env["WAIRON_PACKS_DIR"] = path59.join(dataDir, "packs");
37317
37554
  }
37318
37555
  const cfg = {
37319
37556
  host: options.host || "0.0.0.0",
@@ -37438,13 +37675,13 @@ function openBrowser(url) {
37438
37675
  }
37439
37676
  async function runDev(options = {}) {
37440
37677
  const cwd = process.cwd();
37441
- if (!fs50.existsSync(path60.join(cwd, ".wai"))) {
37678
+ if (!fs50.existsSync(path59.join(cwd, ".wai"))) {
37442
37679
  throw new WaironError(
37443
37680
  "No .wai/ found in the current directory. Run `wairon dev` from a wairon project root (or run `wairon init` first)."
37444
37681
  );
37445
37682
  }
37446
37683
  const hash = crypto20.createHash("sha256").update(cwd).digest("hex").slice(0, 16);
37447
- const dataDir = path60.join(os9.tmpdir(), "wairon-dev", hash);
37684
+ const dataDir = path59.join(os10.tmpdir(), "wairon-dev", hash);
37448
37685
  fs50.mkdirSync(dataDir, { recursive: true });
37449
37686
  registerLocalDevProject(dataDir, "local", cwd);
37450
37687
  const port = options.port ? Number(options.port) : 8080;
@@ -37853,8 +38090,8 @@ async function runHostPacks(action, options = {}) {
37853
38090
  }
37854
38091
  case "install": {
37855
38092
  if (!options.file) throw new WaironError("`--file <path>` (a declarative pack YAML) is required for install.");
37856
- const name = options.name ?? path60.basename(options.file).replace(/\.(ya?ml)$/i, "");
37857
- const content = fs50.readFileSync(path60.resolve(options.file), "utf8");
38093
+ const name = options.name ?? path59.basename(options.file).replace(/\.(ya?ml)$/i, "");
38094
+ const content = fs50.readFileSync(path59.resolve(options.file), "utf8");
37858
38095
  const desc = project2 ? installProjectPack(cfg, cred, project2, name, content) : installGlobalPack(cfg, cred, name, content);
37859
38096
  logger.success(`Installed ${scope} pack "${desc.name}" (${desc.profiles} profile(s), ${desc.languages} language(s)).`);
37860
38097
  if (project2) logger.info("Committed with the project \u2014 every clone and CI will enforce it.");
@@ -38060,13 +38297,27 @@ async function runSurface(action, options = {}) {
38060
38297
  for (const p of written) logger.info(` ${p}`);
38061
38298
  return;
38062
38299
  }
38300
+ case "externals": {
38301
+ const entries = listExternalInterfaces();
38302
+ if (!entries.length) {
38303
+ logger.info("No external surfaces available (.wai/surfaces/ holds no snapshots).");
38304
+ return;
38305
+ }
38306
+ const freshness = (f) => f === "fresh" ? import_chalk19.default.green(f) : f === "stale" ? import_chalk19.default.yellow(f) : import_chalk19.default.gray(f);
38307
+ for (const e of entries) {
38308
+ logger.info(
38309
+ `${e.sourceKind.padEnd(8)} ${import_chalk19.default.cyan(e.projectName)} [${e.origin}] ${freshness(e.freshness)} \u2014 ${e.interfaceIds.length ? e.interfaceIds.join(", ") : "(no interfaces)"}`
38310
+ );
38311
+ }
38312
+ return;
38313
+ }
38063
38314
  default:
38064
- throw new WaironError(`Unknown surface action "${action}" (supported: export, import, list, generate-children).`);
38315
+ throw new WaironError(`Unknown surface action "${action}" (supported: export, import, list, generate-children, externals).`);
38065
38316
  }
38066
38317
  }
38067
38318
 
38068
38319
  // src/commands/subsystem.ts
38069
- var path61 = __toESM(require("path"));
38320
+ var path60 = __toESM(require("path"));
38070
38321
  init_logger();
38071
38322
  init_errors();
38072
38323
  init_fs();
@@ -38102,9 +38353,9 @@ async function runSubsystemAdd(id, options = {}) {
38102
38353
  updatedAt: now
38103
38354
  };
38104
38355
  createChainedSubsystem(subsystem, displayName);
38105
- const childDir = path61.resolve(getProjectRoot(), options.projectPath);
38356
+ const childDir = path60.resolve(getProjectRoot(), options.projectPath);
38106
38357
  logger.success(`Added external subsystem "${id}" \u2192 ${options.projectPath}`);
38107
- logger.info(`Scaffolded child project at ${path61.relative(process.cwd(), childDir) || "."}`);
38358
+ logger.info(`Scaffolded child project at ${path60.relative(process.cwd(), childDir) || "."}`);
38108
38359
  logger.info(`Design its spec tree from this parent using namespaced ids (e.g. ${id}::<component>).`);
38109
38360
  }
38110
38361
  async function runSubsystemMove(id, options = {}) {
@@ -38127,9 +38378,9 @@ async function runSubsystemExternalize(id, options = {}) {
38127
38378
  throw new WaironError("--project-path (the subproject destination) is required.");
38128
38379
  }
38129
38380
  externalizeSubsystem(id, options.projectPath);
38130
- const childDir = path61.resolve(getProjectRoot(), options.projectPath);
38381
+ const childDir = path60.resolve(getProjectRoot(), options.projectPath);
38131
38382
  logger.success(`Externalized subsystem "${id}" \u2192 ${options.projectPath}`);
38132
- logger.info(`Moved its specs into ${path61.relative(process.cwd(), childDir) || "."} (now a standalone subproject).`);
38383
+ logger.info(`Moved its specs into ${path60.relative(process.cwd(), childDir) || "."} (now a standalone subproject).`);
38133
38384
  logger.info("Move the source code there yourself, then run `wairon validate` to confirm the tree.");
38134
38385
  }
38135
38386
  async function runSubsystemInternalize(id) {
@@ -38168,8 +38419,64 @@ program.command("generate").description("Generate agent output files from the sp
38168
38419
  dryRun: opts.dryRun
38169
38420
  });
38170
38421
  });
38422
+ async function runLock2(options) {
38423
+ assertProjectInitialized();
38424
+ if (!pathExists(AI_PATHS.specsSystem())) {
38425
+ logger.error("No SDD spec tree found (.wai/specs). Nothing to lock.");
38426
+ process.exit(1);
38427
+ }
38428
+ const projectConfig = loadProjectConfig();
38429
+ logger.info("Analyzing and validating specifications in-memory...");
38430
+ const dry = validateAsComplete({
38431
+ rules: projectConfig.rules,
38432
+ projectType: projectConfig.projectType,
38433
+ scopeSubsystem: options.subsystem,
38434
+ recursive: options.recursive ?? true
38435
+ });
38436
+ const errors = dry.issues.filter((i) => i.severity === "error");
38437
+ if (errors.length > 0) {
38438
+ logger.header("Cannot lock \u2014 the spec tree does not validate as complete");
38439
+ let errorCount = 0;
38440
+ const MAX_PRINT = 100;
38441
+ let skippedErrors = 0;
38442
+ for (const i of errors) {
38443
+ if (errorCount < MAX_PRINT) {
38444
+ logger.error(`${i.specId ? `[${i.specId}] ` : ""}[${i.code}] ${i.message}`);
38445
+ errorCount++;
38446
+ } else {
38447
+ skippedErrors++;
38448
+ }
38449
+ }
38450
+ if (skippedErrors > 0) {
38451
+ logger.error(`... and ${skippedErrors} more error(s) omitted.`);
38452
+ }
38453
+ logger.blank();
38454
+ logger.info("Fix the errors above, then run `wairon lock` again. Nothing was changed.");
38455
+ process.exit(1);
38456
+ }
38457
+ logger.header("Lock SDD specs");
38458
+ const record2 = await runLock(options, dry);
38459
+ if (!record2) {
38460
+ logger.info("Cancelled. Nothing was changed.");
38461
+ return;
38462
+ }
38463
+ const childPaths = generateChildSnapshots();
38464
+ if (childPaths.length > 0) {
38465
+ logger.blank();
38466
+ logger.success(`Regenerated the family/sibling surfaces into ${childPaths.length} chained child snapshot(s):`);
38467
+ for (const p of childPaths) logger.info(` ${p}`);
38468
+ }
38469
+ logger.blank();
38470
+ await runGenerate({ domain: options.subsystem });
38471
+ logger.blank();
38472
+ logger.success("Specs locked and agent topology generated.");
38473
+ logger.info(`Lock record written (.wai/lock.json): stateId ${record2.stateId.algorithm}:${record2.stateId.digest} \u2014 status ${record2.status}.`);
38474
+ logger.warn(
38475
+ "Restart any running AI agent sessions (Claude Code / Antigravity / Codex) so the newly generated implementer agents load \u2014 they are not picked up mid-session."
38476
+ );
38477
+ }
38171
38478
  program.command("lock").description("Final check before implementation: validate the spec tree as complete, freeze all specs to complete, and (re)generate the agent topology \u2014 only if it validates").option("-y, --yes", "skip the confirmation prompt (for scripts / CI)").option("--subsystem <id>", "only lock specs in the specified subsystem").option("--no-recursive", "do not recursively validate subprojects").action(async (opts) => {
38172
- await runLock({ yes: opts.yes, subsystem: opts.subsystem, recursive: opts.recursive });
38479
+ await runLock2({ yes: opts.yes, subsystem: opts.subsystem, recursive: opts.recursive });
38173
38480
  });
38174
38481
  program.command("validate").description("Validate the project configuration and the SDD Spec Tree").option("--ci", "treat warnings as errors for CI pipelines").option("--subsystem <id>", "only validate the specified subsystem (granular)").option("--no-recursive", "do not recursively validate subprojects").action(async (opts) => {
38175
38482
  await runValidate({ ci: opts.ci, subsystem: opts.subsystem, recursive: opts.recursive });
@@ -38285,7 +38592,7 @@ mcpCmd.command("status").description("Show whether the wairon MCP server is regi
38285
38592
  program.command("produce <target>").description("project the local project's specs to a producer target (notion | miro)").option("--page <id>", "parent page/board id in the target").option("--token <token>", "integration token (else env, else interactive prompt)").action(async (target, opts) => {
38286
38593
  await runProduce(target, { page: opts.page, token: opts.token });
38287
38594
  });
38288
- program.command("surface <action>").description("public surface exchange: export | import | list | generate-children").option("--audience <level>", "export ceiling: project | department | instance | partner | external (default instance)").option("--format <fmt>", "export format: native | openapi (default native)").option("--out <path>", "export output path (else print)").option("--portal <id>", "export: select one portal's OpenAPI spec (a multi-portal project renders one document per portal)").option("--source <path>", "import: the surface document (native snapshot YAML or OpenAPI)").option("--origin <origin>", "import provenance: exchanged | authored (default authored)").action(async (action, opts) => {
38595
+ program.command("surface <action>").description("public surface exchange: export | import | list | generate-children | externals").option("--audience <level>", "export ceiling: project | department | instance | partner | external (default instance)").option("--format <fmt>", "export format: native | openapi (default native)").option("--out <path>", "export output path (else print)").option("--portal <id>", "export: select one portal's OpenAPI spec (a multi-portal project renders one document per portal)").option("--source <path>", "import: the surface document (native snapshot YAML or OpenAPI)").option("--origin <origin>", "import provenance: exchanged | authored (default authored)").action(async (action, opts) => {
38289
38596
  await runSurface(action, {
38290
38597
  audience: opts.audience,
38291
38598
  format: opts.format,