@wairon/cli 5.1.1-dev.71 → 5.1.1-dev.72

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.1.1-dev.71";
68
+ WAIRON_VERSION = "5.1.1-dev.72";
69
69
  GITHUB_REPO = "SYW-Apps/Waffle-AIron";
70
70
  SUPPORTED_ALIASES = ["wai"];
71
71
  SCAN_EXCLUDE_DIRS = /* @__PURE__ */ new Set([
@@ -1860,7 +1860,13 @@ var init_specs = __esm({
1860
1860
  /** The L2 component that realizes this public interface (the subsystem's published surface). */
1861
1861
  component: SpecIdSchema.optional(),
1862
1862
  /** Optional L3 interface on that component backing this entry. */
1863
- interface: SpecIdSchema.optional()
1863
+ interface: SpecIdSchema.optional(),
1864
+ /**
1865
+ * The subsystems this surface is published to. Absent, anyone may depend on
1866
+ * it; present, only these subsystems may — when every entry publishing the
1867
+ * component names consumers, the union of those lists is the whole set.
1868
+ */
1869
+ consumers: import_zod8.z.array(SpecIdSchema).optional()
1864
1870
  });
1865
1871
  TrustedLinkSchema = import_zod8.z.object({
1866
1872
  /** The peer subsystem id this link sanctions tight coupling with. */
@@ -9477,6 +9483,39 @@ var init_public_surface_binding = __esm({
9477
9483
  }
9478
9484
  });
9479
9485
 
9486
+ // src/core/rules/integrity/public-surface-consumers.ts
9487
+ var publicSurfaceConsumersRule;
9488
+ var init_public_surface_consumers = __esm({
9489
+ "src/core/rules/integrity/public-surface-consumers.ts"() {
9490
+ "use strict";
9491
+ init_models();
9492
+ publicSurfaceConsumersRule = {
9493
+ name: "public-surface-consumers",
9494
+ description: "Every subsystem a published surface names as its consumer must exist in the tree. A consumers list restricts who may depend on the surface, so an id that names nothing locks the surface against a caller nobody can be \u2014 and a typo there silently locks out the caller that was meant, which the boundary rule would then report against the wrong party.",
9495
+ codes: [
9496
+ { code: "PUBLIC_INTERFACE_UNKNOWN_CONSUMER", defaultSeverity: "error", summary: "A published surface names as its consumer a subsystem the tree does not have" }
9497
+ ],
9498
+ check(ctx) {
9499
+ const known = new Set(ctx.subsystems.map((s) => s.id));
9500
+ for (const sub of ctx.subsystems) {
9501
+ for (const pi of sub.publicInterfaces) {
9502
+ for (const consumer of pi.consumers ?? []) {
9503
+ if (known.has(consumer)) continue;
9504
+ ctx.addIssue(
9505
+ "error",
9506
+ "PUBLIC_INTERFACE_UNKNOWN_CONSUMER",
9507
+ `Subsystem "${sub.id}" publishes "${pi.component ?? `${pi.type} ${pi.details}`}" to consumer "${consumer}", which names no subsystem of this tree. Name an existing subsystem, or drop the id \u2014 a consumers list admits only the subsystems it names.`,
9508
+ sub.id,
9509
+ isDraftSubsystem(sub)
9510
+ );
9511
+ }
9512
+ }
9513
+ }
9514
+ }
9515
+ };
9516
+ }
9517
+ });
9518
+
9480
9519
  // src/core/rules/integrity/public-surface-declared-type.ts
9481
9520
  var pubTypeMatches, EVENT_VOCAB, expectedFor, publicSurfaceDeclaredTypeRule;
9482
9521
  var init_public_surface_declared_type = __esm({
@@ -11009,6 +11048,11 @@ var init_auth_source_wiring = __esm({
11009
11048
  });
11010
11049
 
11011
11050
  // src/core/rules/doctrine/subsystem-boundary-dependencies.ts
11051
+ function restrictedConsumers(ctx, subsystemId, componentId) {
11052
+ const entries = (ctx.subsystems.find((s) => s.id === subsystemId)?.publicInterfaces ?? []).filter((pi) => pi.component === componentId);
11053
+ if (entries.length === 0 || entries.some((pi) => pi.consumers === void 0)) return null;
11054
+ return [...new Set(entries.flatMap((pi) => pi.consumers ?? []))];
11055
+ }
11012
11056
  var subsystemBoundaryDepsRule;
11013
11057
  var init_subsystem_boundary_dependencies = __esm({
11014
11058
  "src/core/rules/doctrine/subsystem-boundary-dependencies.ts"() {
@@ -11016,14 +11060,15 @@ var init_subsystem_boundary_dependencies = __esm({
11016
11060
  init_models();
11017
11061
  subsystemBoundaryDepsRule = {
11018
11062
  name: "subsystem-boundary-dependencies",
11019
- description: "Judges every dependsOn edge that leaves its own subsystem, and every one that resolves nowhere. Across subsystems the shape is client Adapter \u2192 published remote Portal, OR a direct in-process edge licensed by a trustedLink declared on the SOURCE subsystem (the published-Portal target requirement applies either way). A reference that names nothing in this tree is resolved against the stored surface snapshots: a hit is a DECLARED remote portal and the same Adapter requirement applies to it, snapshots of several providers that disagree make the reference ambiguous, and a reference authored to leave this root that no snapshot covers warns instead of erroring. Boundary rules are never relaxed by a pack profile.",
11063
+ description: "Judges every dependsOn edge that leaves its own subsystem, and every one that resolves nowhere. Across subsystems the shape is client Adapter \u2192 published remote Portal, OR a direct in-process edge licensed by a trustedLink declared on the SOURCE subsystem (the published-Portal target requirement applies either way). A published surface may also name the subsystems it serves (a publicInterfaces entry's consumers): a component every one of whose entries names consumers may be depended on only from those subsystems, which is how a provider keeps a surface \u2014 the raw spec writes \u2014 away from a door that must not reach it. A reference that names nothing in this tree is resolved against the stored surface snapshots: a hit is a DECLARED remote portal and the same Adapter requirement applies to it, snapshots of several providers that disagree make the reference ambiguous, and a reference authored to leave this root that no snapshot covers warns instead of erroring. Boundary rules are never relaxed by a pack profile.",
11020
11064
  codes: [
11021
11065
  { code: "INVALID_DEPENDENCY_REFERENCE", defaultSeverity: "error", summary: "dependsOn names a non-existent component" },
11022
11066
  { code: "CROSS_TREE_REF_UNRESOLVED", defaultSeverity: "warning", summary: "Cross-tree dependsOn (super::/:: form) with no surface snapshot covering it" },
11023
11067
  { code: "SURFACE_REF_AMBIGUOUS", defaultSeverity: "error", summary: "Cross-tree dependsOn matched by surface snapshots of several providers with different contracts" },
11024
11068
  { code: "CROSS_SUBSYSTEM_NON_ADAPTER", defaultSeverity: "error", summary: "Non-Adapter component crossing a subsystem boundary" },
11025
11069
  { code: "CROSS_SUBSYSTEM_PRIVATE_ACCESS", defaultSeverity: "error", summary: "Cross-subsystem dependency on an unpublished component" },
11026
- { code: "CROSS_SUBSYSTEM_TARGET_NON_PORTAL", defaultSeverity: "error", summary: "Cross-subsystem hop entering through a non-Portal" }
11070
+ { code: "CROSS_SUBSYSTEM_TARGET_NON_PORTAL", defaultSeverity: "error", summary: "Cross-subsystem hop entering through a non-Portal" },
11071
+ { code: "CROSS_SUBSYSTEM_UNLISTED_CONSUMER", defaultSeverity: "error", summary: "Cross-subsystem dependency on a surface published only to other subsystems" }
11027
11072
  ],
11028
11073
  check(ctx) {
11029
11074
  for (const edge of ctx.dependencyEdges().all) {
@@ -11106,6 +11151,16 @@ var init_subsystem_boundary_dependencies = __esm({
11106
11151
  edge.draftContext
11107
11152
  );
11108
11153
  }
11154
+ const publishedTo = restrictedConsumers(ctx, depComp.subsystem, depComp.id);
11155
+ if (publishedTo && !publishedTo.includes(comp.subsystem)) {
11156
+ ctx.addIssue(
11157
+ "error",
11158
+ "CROSS_SUBSYSTEM_UNLISTED_CONSUMER",
11159
+ `Boundary violation: "${comp.id}" (subsystem "${comp.subsystem}") depends on "${depComp.id}", which subsystem "${depComp.subsystem}" publishes only to ${publishedTo.map((s) => `"${s}"`).join(", ")}. Depend on a surface published to "${comp.subsystem}" instead, or \u2014 when "${comp.subsystem}" really is a caller the provider meant to serve \u2014 add it to the entry's consumers.`,
11160
+ comp.id,
11161
+ edge.draftContext
11162
+ );
11163
+ }
11109
11164
  }
11110
11165
  }
11111
11166
  };
@@ -15983,6 +16038,7 @@ var init_repository = __esm({
15983
16038
  init_field_type_references();
15984
16039
  init_signature_type_references();
15985
16040
  init_public_surface_binding();
16041
+ init_public_surface_consumers();
15986
16042
  init_public_surface_declared_type();
15987
16043
  init_public_surface_bound_contract();
15988
16044
  init_lint_allows();
@@ -16161,10 +16217,11 @@ var init_repository = __esm({
16161
16217
  assertionForbiddenEdgesRule,
16162
16218
  assertionRequiredFieldsRule,
16163
16219
  assertionEndpointShapesRule,
16164
- // The published surface in three questions: what backs the entry, whether
16165
- // that component's stereotype can realize the type it declares, and whether
16166
- // the contract it binds is that component's own.
16220
+ // The published surface in four questions: what backs the entry, who it is
16221
+ // published to, whether that component's stereotype can realize the type it
16222
+ // declares, and whether the contract it binds is that component's own.
16167
16223
  publicSurfaceBindingRule,
16224
+ publicSurfaceConsumersRule,
16168
16225
  publicSurfaceDeclaredTypeRule,
16169
16226
  publicSurfaceBoundContractRule,
16170
16227
  cyclesRule,
@@ -19949,7 +20006,10 @@ function stripNamespaceFromSubsystem(spec, prefix) {
19949
20006
  publicInterfaces: spec.publicInterfaces.map((pi) => ({
19950
20007
  ...pi,
19951
20008
  component: pi.component ? relMember(pi.component) : void 0,
19952
- interface: pi.interface ? relMember(pi.interface) : void 0
20009
+ interface: pi.interface ? relMember(pi.interface) : void 0,
20010
+ // consumers name SUBSYSTEMS — peers of this one, so they are relative to
20011
+ // the namespace the subsystem itself is declared in, never its members'.
20012
+ ...pi.consumers ? { consumers: pi.consumers.map((c) => c.includes("::") ? relativizeId(c, prefix) : c) } : {}
19953
20013
  })),
19954
20014
  lifecycle: spec.lifecycle?.map((le) => ({
19955
20015
  ...le,
@@ -20897,7 +20957,10 @@ var init_specs2 = __esm({
20897
20957
  publicInterfaces: sub.publicInterfaces.map((p) => ({
20898
20958
  ...p,
20899
20959
  component: p.component ? qualifyId(p.component, componentPrefix, this.rootSubsystems) : void 0,
20900
- interface: p.interface ? qualifyId(p.interface, componentPrefix, this.rootSubsystems) : void 0
20960
+ interface: p.interface ? qualifyId(p.interface, componentPrefix, this.rootSubsystems) : void 0,
20961
+ // Each consumer is a subsystem reference: qualified into the namespace
20962
+ // this subsystem is declared in, like a component's `subsystem` field.
20963
+ ...p.consumers ? { consumers: p.consumers.map((c) => qualifySubsystemRef(c, namespacePrefix, this.rootSubsystems)) } : {}
20901
20964
  })),
20902
20965
  lifecycle: sub.lifecycle?.map((le) => ({
20903
20966
  ...le,
@@ -22511,7 +22574,8 @@ var init_specs2 = __esm({
22511
22574
  out.publicInterfaces = out.publicInterfaces.map((pi) => ({
22512
22575
  ...pi,
22513
22576
  ...typeof pi?.component === "string" ? { component: qm(pi.component) } : {},
22514
- ...typeof pi?.interface === "string" ? { interface: qm(pi.interface) } : {}
22577
+ ...typeof pi?.interface === "string" ? { interface: qm(pi.interface) } : {},
22578
+ ...Array.isArray(pi?.consumers) ? { consumers: pi.consumers.map((c) => typeof c === "string" ? qualifySubsystemRef(c, prefix, this.rootSubsystems) : c) } : {}
22515
22579
  }));
22516
22580
  }
22517
22581
  if (Array.isArray(out.lifecycle)) {
@@ -29129,643 +29193,680 @@ var init_core = __esm({
29129
29193
  }
29130
29194
  });
29131
29195
 
29132
- // src/core/instructions.ts
29133
- function governingProfile() {
29196
+ // src/core/authoring.ts
29197
+ function candidateOptions() {
29134
29198
  try {
29135
- return loadProjectConfig4()?.projectType ?? null;
29199
+ const config = loadProjectConfig();
29200
+ return config ? { rules: config.rules, projectType: config.projectType } : {};
29136
29201
  } catch {
29137
- return null;
29202
+ return {};
29138
29203
  }
29139
29204
  }
29140
- function blockApplies(block, profile) {
29141
- if (!block.profile || block.profile.length === 0) return true;
29142
- return profile !== null && block.profile.includes(profile);
29143
- }
29144
- function skillPointers(skills) {
29145
- if (skills.length === 0) return "(none published by this server)";
29146
- return skills.map((s) => `- \`${s.resourceUri}\` \u2014 ${s.description || s.name}`).join("\n");
29205
+ function noticesFrom(verdict) {
29206
+ return verdict.warnings.map((w) => `${w.code}: ${w.message}`);
29147
29207
  }
29148
- function waironInstructions(profile, packNames, skills, variants) {
29149
- return `# wairon \u2014 Spec-Driven Development (SDD)
29150
-
29151
- This server owns one system's **spec tree**: its architecture, specified top-down
29152
- and machine-checked. Author it ONLY through this server's \`sdd_*\` tools \u2014 never by
29153
- writing spec files directly, and never via the \`wairon\` CLI (that is the human
29154
- developer's tool).
29155
-
29156
- ## The tree: L0 \u2192 L5
29157
-
29158
- - **L0 System** \u2014 vision, boundaries, global requirements, target language.
29159
- - **L1 Subsystem** \u2014 an isolated service, its \`publicInterfaces\`, its trusted links.
29160
- - **L2 Component** \u2014 ONE building block (\`Portal\`, \`Orchestrator\`, \`Supervisor\`,
29161
- \`Actor\`, \`Store\`, \`Index\`, \`Query\`, \`Registry\`, \`Adapter\`, \`Observer\`, and
29162
- \`View\` for UIs) or pattern (\`Repository\`, and \`FeatureComponent\` or
29163
- \`RouterComponent\` for UIs), plus \`owns\` (a pattern's private members) and
29164
- \`dependsOn\` (collaborators). Logic is an \`Orchestrator\` whose \`dependencyClass\`
29165
- (\`pure\` or \`read\`; unset, a workflow) bounds what it may depend on; a gateway is
29166
- a \`Portal\` with the \`gateway\` variant. \`Specialist\` and \`Gateway\` are retired.
29167
- - **L3 Interface** \u2014 the contract: method signatures with structured \`params\`,
29168
- plus wire \`endpoint\` bindings for a Portal's methods.
29169
- - **L4 Implementation** \u2014 a concrete realization of one L3 contract: narrative
29170
- detail level, bound \`technologies\` (the vendor swap seam), optional \`sourcePath\`.
29171
- - **L5 Narrative** \u2014 each method's logic as a FLAT numbered step list; \`call\` steps
29172
- resolve to real dependency methods, and flow steps jump by step number.
29173
-
29174
- These stereotypes are not labels \u2014 they carry enforced dependency rules (e.g. a
29175
- Portal may not depend on a Store; state lives in a Store, never as fields inside
29176
- an Orchestrator). \`sdd_validate_tree\` is the gate, and it is the authority on
29177
- whether a design is legal.
29178
-
29179
- ## Authoring order
29180
-
29181
- 1. \`sdd_initialize_system\` (L0), then \`sdd_add_subsystem\` (L1).
29182
- 2. Then, **one subsystem at a time**: \`sdd_add_component\` (L2) \u2192 \`sdd_define_interface\`
29183
- (L3) \u2192 \`sdd_set_endpoints\` for Portals \u2192 \`sdd_write_narrative\` (L4+L5).
29184
- 3. Run \`sdd_validate_tree\` as you go \u2014 early and per subsystem, not once at the end.
29185
- \`sdd_get_status\` reports completeness; \`sdd_get_spec\` reads any spec back as JSON.
29186
- 4. Edit granularly with \`sdd_update_spec\` (it renumbers narrative steps and
29187
- relocates jump targets for you).
29188
-
29189
- ## The tool schemas are the field reference
29190
-
29191
- Every \`sdd_*\` tool's input schema is self-describing and authoritative \u2014 read the
29192
- schema rather than guessing field names, and prefer structured \`params\` over prose
29193
- signatures. Specs are ${profile === null ? "validated against the project's configured profile" : `governed by the **\`${profile}\`** profile`}, which decides which stereotypes are legal
29194
- here, which narrative flow constructs the target language supports, and which
29195
- extra rules apply. A subsystem may override the profile for itself.
29196
-
29197
- ## Loaded extension packs
29198
-
29199
- ${packNames.length === 0 ? "None \u2014 only wairon's built-in doctrine applies." : `${packNames.map((n) => `\`${n}\``).join(", ")} \u2014 these inject profiles, language tables, patterns, and
29200
- additional conformance rules. Their doctrine is enforced by the same gate.`}
29201
-
29202
- ${variants.length === 0 ? "" : `## Component variants in play
29203
-
29204
- This project defines variants \u2014 named, base-anchored kinds carrying implementation
29205
- guidance, so every component of the same kind is built alike: ${variants.map((v) => `\`${v.id}\` (a kind of ${v.base})`).join(", ")}.
29206
- Tag a component with \`variant\` where it fits, and read a component's resolved
29207
- guidance and its same-variant siblings from \`sdd_get_spec\` (kind: component).
29208
-
29209
- `}## Read the skills BEFORE authoring
29210
-
29211
- This server publishes its skills as MCP resources. **Read \`wairon-skill://sdd-architect\`
29212
- before you design anything**, and the others when their turn comes \u2014 they carry the
29213
- architecture standard, the narrative step reference, and the review checklist that
29214
- this briefing only points at:
29215
-
29216
- ${skillPointers(skills)}
29217
-
29218
- ## Delegating implementation
29219
-
29220
- Delegate component work from a LIVE agent brief, never a generated file: discover
29221
- agents via \`listAgents\` (or the \`wairon-agent://\` resources), fetch the target's
29222
- brief with \`sdd_get_agent_brief\`, and spawn a scoped subagent from it \u2014 its
29223
- \`instructions\` as prompt, its \`ownedPaths\` as write fence. Briefs are composed
29224
- from the current spec tree on every call, so a re-lock never requires a session
29225
- restart \u2014 fetch fresh per delegation. \`wairon-skill://sdd-delegate\` carries the
29226
- full flow.`;
29208
+ function componentCandidateGate(options = candidateOptions()) {
29209
+ return {
29210
+ gate: (kind, merged) => {
29211
+ if (kind !== "component") return;
29212
+ const verdict = validateComponentCandidate(merged, options);
29213
+ if (verdict.errors.length) throw new Error(formatCandidateRefusal(verdict));
29214
+ return noticesFrom(verdict);
29215
+ }
29216
+ };
29227
29217
  }
29228
- function buildServerInstructions() {
29229
- const profile = governingProfile();
29230
- let skills = [];
29231
- let blocks = [];
29232
- let packNames = [];
29233
- const variants = [];
29234
- try {
29235
- skills = listSkillResources();
29236
- const extensions = loadProjectExtensions2();
29237
- blocks = extensions.instructions;
29238
- packNames = extensions.packNames;
29239
- } catch {
29240
- }
29241
- const sections = [waironInstructions(profile, packNames, skills, variants)];
29242
- for (const block of blocks) {
29243
- if (!blockApplies(block, profile)) continue;
29244
- sections.push(`## From pack "${block.pack}"
29245
-
29246
- ${block.text.trim()}`);
29218
+ function restatementParent(restatement) {
29219
+ const spec = restatement.spec;
29220
+ switch (restatement.kind) {
29221
+ case "subsystem":
29222
+ return { kind: "system", id: "system" };
29223
+ case "component":
29224
+ return { kind: "subsystem", id: spec.subsystem };
29225
+ case "interface":
29226
+ return { kind: "component", id: spec.component };
29227
+ case "implementation":
29228
+ return { kind: "interface", id: spec.contract };
29229
+ default:
29230
+ return null;
29247
29231
  }
29248
- return sections.join("\n\n");
29249
29232
  }
29250
- var init_instructions = __esm({
29251
- "src/core/instructions.ts"() {
29252
- "use strict";
29253
- init_skills();
29233
+ function applyRestatement(restatement, existing, parent) {
29234
+ const replacedExisting = existing !== null;
29235
+ const parentRef = restatementParent(restatement);
29236
+ if (parentRef && !parent) return refused(restatement, replacedExisting, MISSING_PARENT[restatement.kind](parentRef.id));
29237
+ const status2 = statusForCreate(restatement, existing);
29238
+ if ("refusal" in status2) return refused(restatement, replacedExisting, status2.refusal);
29239
+ const candidate = JSON.parse(JSON.stringify(restatement.spec));
29240
+ if (restatement.kind === "subsystem") candidate.parentSystem = parent.name;
29241
+ const carried = carryInto(restatement, existing, candidate);
29242
+ const cleared = clearedByOmission(existing, candidate, restatement.fields);
29243
+ stampLifecycle(candidate, existing, status2.status);
29244
+ const labelErrors = resolveLabelsOf(restatement.kind, candidate);
29245
+ if (labelErrors.length) {
29246
+ return refused(restatement, replacedExisting, `Unresolved narrative label references \u2014 nothing was saved:
29247
+ - ${labelErrors.join("\n- ")}`);
29254
29248
  }
29255
- });
29256
-
29257
- // src/core/skills.ts
29258
- function loadProjectExtensions2() {
29259
- return loadProjectExtensions();
29249
+ const parsed = parseCandidate(restatement.kind, candidate);
29250
+ if ("refusal" in parsed) return refused(restatement, replacedExisting, parsed.refusal);
29251
+ return {
29252
+ spec: parsed.spec,
29253
+ ...status2.status ? { status: status2.status } : {},
29254
+ replacedExisting,
29255
+ notices: existing ? rewriteNotices(restatement.kind, existing, candidate, carried, cleared) : [],
29256
+ changedMethods: changedMethodsOf(restatement.kind, existing, candidate)
29257
+ };
29260
29258
  }
29261
- function loadProjectConfig4() {
29262
- return loadProjectConfig();
29259
+ function parseCandidate(kind, candidate) {
29260
+ const masked = /* @__PURE__ */ new Map();
29261
+ const mask = (value) => {
29262
+ if (typeof value === "string") {
29263
+ if (!value.includes("::")) return value;
29264
+ const token = `nsref_${masked.size}_masked`;
29265
+ masked.set(token, value);
29266
+ return token;
29267
+ }
29268
+ if (Array.isArray(value)) return value.map(mask);
29269
+ if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, mask(v)]));
29270
+ return value;
29271
+ };
29272
+ const restore = (value) => {
29273
+ if (typeof value === "string") return masked.get(value) ?? value;
29274
+ if (Array.isArray(value)) return value.map(restore);
29275
+ if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, restore(v)]));
29276
+ return value;
29277
+ };
29278
+ const result = SPEC_SCHEMA[kind].safeParse(mask(candidate));
29279
+ if (result.success) return { spec: restore(result.data) };
29280
+ const id = kind === "system" ? "system" : String(candidate.id);
29281
+ const issues = result.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ");
29282
+ return {
29283
+ refusal: `Refusing to write ${kind} "${id}": the spec it would become does not parse \u2014 ${issues}. Nothing was written. State the missing or malformed field and call again.`
29284
+ };
29263
29285
  }
29264
- function packSkillId(skill) {
29265
- return `${packNamespace(skill.pack)}-${skill.id}`;
29286
+ function refused(restatement, replacedExisting, refusal2) {
29287
+ return { spec: restatement.spec, refusal: refusal2, replacedExisting, notices: [], changedMethods: [] };
29266
29288
  }
29267
- function packNamespace(pack) {
29268
- return pack.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
29289
+ function statusForCreate(restatement, existing) {
29290
+ if (!STATUSED_KINDS.has(restatement.kind)) return {};
29291
+ const stated = restatement.status;
29292
+ const stored = existing?.status;
29293
+ const held = stored !== void 0 && STATUS_ORDER.includes(stored) ? stored : void 0;
29294
+ if (stated === void 0) return { status: held ?? "draft" };
29295
+ if (held === void 0 || STATUS_ORDER.indexOf(stated) >= STATUS_ORDER.indexOf(held)) return { status: stated };
29296
+ const label = `${restatement.kind} "${restatement.spec.id}"`;
29297
+ return {
29298
+ refusal: `Refusing to re-author ${label} at status "${stated}": it is stored at "${held}", and a create tool never lowers a spec's status \u2014 a restatement that reopened a frozen spec would undo a lock without saying so. Nothing was written. To reopen it deliberately, use sdd_update_spec with {"status": "${stated}"}; to keep the level it has, leave status out.`
29299
+ };
29269
29300
  }
29270
- function extensionsFor(builtin, packSkills) {
29271
- return packSkills.filter((s) => s.extends === builtin && fs20.existsSync(s.sourcePath));
29301
+ function carryInto(restatement, existing, candidate) {
29302
+ if (!existing) return [];
29303
+ const carried = ["createdAt"];
29304
+ if (restatement.memberFields && Array.isArray(candidate.methods)) {
29305
+ const stored = existing.methods ?? [];
29306
+ for (const method2 of candidate.methods) {
29307
+ const prev = stored.find((p) => p.name === method2.name);
29308
+ for (const field of carryUnexpressed(prev, method2, restatement.memberFields)) carried.push(`${field} (${String(method2.name)})`);
29309
+ }
29310
+ }
29311
+ carried.push(...carryUnexpressed(existing, candidate, [...restatement.fields, "createdAt"]));
29312
+ return carried;
29272
29313
  }
29273
- function composeBuiltinSkill(name, packSkills) {
29274
- const srcPath = skillTemplatePath(name);
29275
- const base = fs20.existsSync(srcPath) ? fs20.readFileSync(srcPath, "utf-8") : "";
29276
- const sections = extensionsFor(name, packSkills);
29277
- if (sections.length === 0) return base;
29278
- const parts = [base.trimEnd()];
29279
- for (const section of sections) {
29280
- const body = fs20.readFileSync(section.sourcePath, "utf-8").replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, "");
29281
- parts.push(`## Platform: ${section.pack}`, body.trim());
29314
+ function carryUnexpressed(existing, next, expressed) {
29315
+ if (!existing) return [];
29316
+ const carried = [];
29317
+ for (const [field, value] of Object.entries(existing)) {
29318
+ if (value === void 0) continue;
29319
+ if (STORE_MANAGED_FIELDS.has(field)) continue;
29320
+ if (expressed.includes(field) && !ALWAYS_CARRIED_FIELDS.has(field)) continue;
29321
+ if (next[field] !== void 0) continue;
29322
+ next[field] = value;
29323
+ carried.push(field);
29282
29324
  }
29283
- return `${parts.join("\n\n")}
29284
- `;
29325
+ return carried;
29285
29326
  }
29286
- function readFrontmatter(raw, fallbackName) {
29287
- const match = /^---\r?\n([\s\S]*?)\r?\n---/.exec(raw);
29288
- const block = match ? match[1] : "";
29289
- const field = (key) => {
29290
- const m = new RegExp(`^${key}:\\s*(.*)$`, "m").exec(block);
29291
- return m ? m[1].trim() : "";
29292
- };
29293
- return { name: field("name") || fallbackName, description: field("description") };
29294
- }
29295
- function builtinSkillsDir() {
29296
- return path31.resolve(__dirname, "..", "templates", "skills");
29297
- }
29298
- function skillTemplatePath(name) {
29299
- return path31.join(builtinSkillsDir(), `${name}.md`);
29327
+ function isEmptyValue(value) {
29328
+ if (value === void 0 || value === null) return true;
29329
+ if (Array.isArray(value)) return value.length === 0;
29330
+ if (typeof value === "string") return value.trim() === "";
29331
+ if (typeof value === "object") return Object.keys(value).length === 0;
29332
+ return false;
29300
29333
  }
29301
- function skillDestPath(type, destDir, name) {
29302
- if (type === "claude" || type === "codex" || type === "gemini" || type === "agy") {
29303
- return path31.join(destDir, name, "SKILL.md");
29334
+ function clearedByOmission(existing, next, expressed) {
29335
+ if (!existing) return [];
29336
+ const stored = existing;
29337
+ const cleared = [];
29338
+ for (const field of expressed) {
29339
+ const before = stored[field];
29340
+ if (STORE_MANAGED_FIELDS.has(field) || ALWAYS_CARRIED_FIELDS.has(field)) continue;
29341
+ if (isEmptyValue(before) || !isEmptyValue(next[field])) continue;
29342
+ cleared.push(Array.isArray(before) ? `${field} (had ${before.length})` : field);
29304
29343
  }
29305
- return path31.join(destDir, `${name}.md`);
29344
+ return cleared;
29306
29345
  }
29307
- function skillsDirForTarget(type) {
29308
- switch (type) {
29309
- case "claude":
29310
- return fromProjectRoot(".claude", "skills");
29311
- case "gemini":
29312
- // Gemini-based Antigravity
29313
- case "agy":
29314
- case "codex":
29315
- return fromProjectRoot(".agents", "skills");
29316
- case "cursor":
29317
- return fromProjectRoot(".cursor", "skills");
29318
- default:
29319
- return null;
29320
- }
29346
+ function stampLifecycle(candidate, existing, status2) {
29347
+ const now = (/* @__PURE__ */ new Date()).toISOString();
29348
+ candidate.createdAt = existing?.createdAt ?? now;
29349
+ candidate.updatedAt = now;
29350
+ if (status2) candidate.status = status2;
29351
+ else delete candidate.status;
29321
29352
  }
29322
- function listSkillNames() {
29323
- return [...SKILL_NAMES];
29353
+ function resolveLabelsOf(kind, candidate) {
29354
+ if (kind !== "implementation") return [];
29355
+ const methods = candidate.methods ?? [];
29356
+ return methods.flatMap((m) => resolveNarrativeLabels(m.name, m.narrative ?? []));
29324
29357
  }
29325
- function exportSddSkills(targetTypes) {
29326
- const types = targetTypes ?? activeTargetTypes2();
29327
- const destinations = [];
29328
- const skipped = [];
29329
- let fileCount = 0;
29330
- const packSkills = loadProjectExtensions2().skills;
29331
- for (const type of types) {
29332
- const destDir = skillsDirForTarget(type);
29333
- if (!destDir) {
29334
- skipped.push(type);
29335
- continue;
29336
- }
29337
- ensureDir(destDir);
29338
- destinations.push(destDir);
29339
- for (const name of SKILL_NAMES) {
29340
- if (!fs20.existsSync(skillTemplatePath(name))) continue;
29341
- const content = composeBuiltinSkill(name, packSkills.filter((s) => s.targets.includes(type)));
29342
- const destPath = skillDestPath(type, destDir, name);
29343
- ensureDir(path31.dirname(destPath));
29344
- fs20.writeFileSync(destPath, content, "utf-8");
29345
- fileCount++;
29346
- }
29347
- for (const skill of packSkills) {
29348
- if (skill.extends !== void 0) continue;
29349
- if (!skill.targets.includes(type)) continue;
29350
- if (!fs20.existsSync(skill.sourcePath)) continue;
29351
- const id = packSkillId(skill);
29352
- const content = fs20.readFileSync(skill.sourcePath, "utf-8");
29353
- const destPath = skillDestPath(type, destDir, id);
29354
- ensureDir(path31.dirname(destPath));
29355
- fs20.writeFileSync(destPath, content, "utf-8");
29356
- fileCount++;
29357
- }
29358
+ function removedByName(before, after) {
29359
+ if (!before?.length) return [];
29360
+ const kept = new Set((after ?? []).map((m) => m.name));
29361
+ return before.map((m) => m.name).filter((n) => !kept.has(n));
29362
+ }
29363
+ function rewriteNotices(kind, existing, candidate, carried, cleared) {
29364
+ const named = kind === "system" ? existing.name : String(candidate.id);
29365
+ const notices = [`${REWRITE_LABEL[kind]} "${named}" already existed \u2014 re-authored in place; this input REPLACES what it expresses.`];
29366
+ if (carried.length) notices.push(`Carried forward (not expressible through this tool): ${carried.join(", ")}.`);
29367
+ const removal = removalNotice(kind, existing, candidate);
29368
+ if (removal) notices.push(removal);
29369
+ if (cleared.length) {
29370
+ notices.push(
29371
+ `CLEARED by omission: ${cleared.join(", ")} \u2014 the argument was not repeated, so the previous value is gone. Use sdd_update_spec if you meant to leave it untouched.`
29372
+ );
29358
29373
  }
29359
- return { destinations, fileCount, skipped };
29374
+ return notices;
29360
29375
  }
29361
- function checkSkillFreshness(type) {
29362
- const dir = skillsDirForTarget(type);
29363
- const result = { dir, missing: [], stale: [], ok: [] };
29364
- if (!dir) return result;
29365
- const packSkills = loadProjectExtensions2().skills.filter((s) => s.targets.includes(type));
29366
- for (const name of SKILL_NAMES) {
29367
- const destPath = skillDestPath(type, dir, name);
29368
- if (!fs20.existsSync(destPath)) {
29369
- result.missing.push(name);
29370
- continue;
29371
- }
29372
- const want = composeBuiltinSkill(name, packSkills);
29373
- const have = fs20.readFileSync(destPath, "utf-8");
29374
- if (have === want) result.ok.push(name);
29375
- else result.stale.push(name);
29376
+ function removalNotice(kind, existing, candidate) {
29377
+ const before = existing;
29378
+ let removed = [];
29379
+ let noun = "method";
29380
+ let suffix = "";
29381
+ if (kind === "interface" || kind === "implementation") {
29382
+ removed = removedByName(before.methods, candidate.methods);
29383
+ suffix = kind === "interface" ? " (endpoint bindings included)" : " (L5 narratives included)";
29384
+ } else if (kind === "type") {
29385
+ removed = [
29386
+ ...removedByName(before.fields, candidate.fields).map((n) => `field ${n}`),
29387
+ ...removedByName(before.methods, candidate.methods).map((n) => `method ${n}`)
29388
+ ];
29389
+ noun = "member";
29376
29390
  }
29377
- return result;
29391
+ if (!removed.length) return void 0;
29392
+ return `REMOVED by this restatement: ${removed.length === 1 ? noun : `${noun}s`} ${removed.map((n) => `"${n}"`).join(", ")}${suffix} \u2014 absent from the input, so no longer in the spec. Restate them to keep them, or use sdd_update_spec to edit one member at a time.`;
29378
29393
  }
29379
- function activeTargetTypes2() {
29380
- const config = loadProjectConfig4();
29381
- if (!config) return [];
29382
- return activeTargetTypes(config);
29394
+ function changedMethodsOf(kind, existing, candidate) {
29395
+ if (!existing || !(kind in METHOD_SCHEMA)) return [];
29396
+ const schema = METHOD_SCHEMA[kind];
29397
+ const stored = existing.methods ?? [];
29398
+ const next = candidate.methods ?? [];
29399
+ return stored.filter((method2) => {
29400
+ const restated = next.find((m) => m.name === method2.name);
29401
+ if (!restated) return true;
29402
+ const parsed = schema.safeParse(restated);
29403
+ return !parsed.success || canonical(parsed.data) !== canonical(method2);
29404
+ }).map((method2) => method2.name);
29383
29405
  }
29384
- function readSkillFrontmatter(name) {
29385
- return readFrontmatter(fs20.readFileSync(skillTemplatePath(name), "utf-8"), name);
29406
+ function canonical(value) {
29407
+ return JSON.stringify(value, (_key, v) => v && typeof v === "object" && !Array.isArray(v) ? Object.fromEntries(Object.entries(v).filter(([, x]) => x !== void 0).sort(([a], [b]) => a.localeCompare(b))) : v);
29386
29408
  }
29387
- function listSkillResources() {
29388
- const builtin = RESOURCE_SKILL_IDS.map((id) => {
29389
- const fm = readSkillFrontmatter(id);
29390
- return {
29391
- id,
29392
- name: fm.name,
29393
- description: fm.description,
29394
- version: WAIRON_VERSION,
29395
- resourceUri: `${SKILL_RESOURCE_SCHEME}://${id}`,
29396
- defaultForHostedMcp: true
29397
- };
29398
- });
29399
- const pack = loadProjectExtensions2().skills.filter((s) => s.extends === void 0 && fs20.existsSync(s.sourcePath)).map((skill) => {
29400
- const id = packSkillId(skill);
29401
- const fm = readFrontmatter(fs20.readFileSync(skill.sourcePath, "utf-8"), id);
29402
- return {
29403
- id,
29404
- name: fm.name,
29405
- description: fm.description,
29406
- version: skill.packVersion ?? WAIRON_VERSION,
29407
- resourceUri: `${SKILL_RESOURCE_SCHEME}://${id}`,
29408
- defaultForHostedMcp: true
29409
- };
29410
- });
29411
- return [...builtin, ...pack];
29409
+ function writeSpec(restatement) {
29410
+ const bound2 = candidateOptions();
29411
+ const parentRef = restatementParent(restatement);
29412
+ const parent = parentRef ? loadSpec(parentRef.kind, parentRef.id) : null;
29413
+ const id = restatement.kind === "system" ? "system" : restatement.spec.id;
29414
+ const existing = loadSpec(restatement.kind, id);
29415
+ const application = applyRestatement(restatement, existing, parent);
29416
+ if (application.refusal !== void 0) throw new Error(application.refusal);
29417
+ const gateNotices = restatement.kind === "component" ? judgeComponent(application.spec, bound2) : [];
29418
+ const persisted = persistCandidate(restatement.kind, application.spec);
29419
+ const testsToRevisit = testsInvalidatedBy(application.changedMethods, [existing, application.spec], bound2);
29420
+ return {
29421
+ kind: restatement.kind,
29422
+ id,
29423
+ name: application.spec.name,
29424
+ replacedExisting: application.replacedExisting,
29425
+ ...application.status ? { status: application.status } : {},
29426
+ notices: [...gateNotices, ...persisted.notices, ...application.notices],
29427
+ ...persisted.scaffoldedProjectPath ? { scaffoldedProjectPath: persisted.scaffoldedProjectPath } : {},
29428
+ testsToRevisit
29429
+ };
29412
29430
  }
29413
- function readSkillResource(resourceId) {
29414
- const packSkills = loadProjectExtensions2().skills;
29415
- if (SKILL_NAMES.includes(resourceId)) return composeBuiltinSkill(resourceId, packSkills);
29416
- const packSkill = packSkills.find((s) => s.extends === void 0 && packSkillId(s) === resourceId);
29417
- if (packSkill) return fs20.readFileSync(packSkill.sourcePath, "utf-8");
29418
- return fs20.readFileSync(skillTemplatePath(resourceId), "utf-8");
29431
+ function judgeComponent(candidate, options) {
29432
+ const verdict = validateComponentCandidate(candidate, options);
29433
+ if (verdict.errors.length) throw new Error(formatCandidateRefusal(verdict));
29434
+ return noticesFrom(verdict);
29419
29435
  }
29420
- function listResources() {
29421
- return listSkillResources();
29436
+ function persistCandidate(kind, spec) {
29437
+ const projectPath = kind === "subsystem" ? spec.projectPath : void 0;
29438
+ if (projectPath && projectPath.trim() !== "") {
29439
+ createChainedSubsystem(spec, spec.name);
29440
+ return { notices: [], scaffoldedProjectPath: projectPath };
29441
+ }
29442
+ return { notices: saveSpec(kind, spec) };
29422
29443
  }
29423
- function buildServerInstructions2() {
29424
- return buildServerInstructions();
29444
+ function testsInvalidatedBy(methods, specs, options) {
29445
+ const testRoots = options.rules?.conformance?.testRoots ?? [];
29446
+ if (methods.length === 0 || testRoots.length === 0) return [];
29447
+ const symbolOf = (name) => specs.map((s) => (s?.methods ?? []).find((m) => m.name === name)?.symbol).find((symbol) => symbol !== void 0);
29448
+ return findTestsReferencing2(methods.map((name) => ({ name, symbol: symbolOf(name) })), getProjectRoot(), testRoots);
29425
29449
  }
29426
- function readResource(resourceId) {
29427
- const known = listSkillResources().some((d) => d.id === resourceId);
29428
- if (!known) throw new SkillResourceNotFoundError(resourceId);
29429
- return readSkillResource(resourceId);
29450
+ function deleteSpec2(kind, id) {
29451
+ const bound2 = candidateOptions();
29452
+ const stored = loadSpec(kind, id);
29453
+ const deleted = deleteSpec(kind, id);
29454
+ const methods = deleted ? (stored?.methods ?? []).map((m) => m.name) : [];
29455
+ const testsToRevisit = testsInvalidatedBy(methods, [stored], bound2);
29456
+ return { kind, id, deleted, testsToRevisit };
29430
29457
  }
29431
- var path31, fs20, SKILL_NAMES, SKILL_RESOURCE_SCHEME, RESOURCE_SKILL_IDS, SkillResourceNotFoundError;
29432
- var init_skills = __esm({
29433
- "src/core/skills.ts"() {
29434
- "use strict";
29435
- path31 = __toESM(require("path"));
29436
- fs20 = __toESM(require("fs"));
29437
- init_fs();
29438
- init_defaults();
29439
- init_project();
29440
- init_extensions();
29441
- init_instructions();
29442
- init_core();
29443
- SKILL_NAMES = ["sdd-architect", "sdd-narrative", "sdd-auditor", "sdd-implement", "sdd-delegate"];
29444
- SKILL_RESOURCE_SCHEME = "wairon-skill";
29445
- RESOURCE_SKILL_IDS = [...SKILL_NAMES].sort();
29446
- SkillResourceNotFoundError = class extends Error {
29447
- constructor(resourceId) {
29448
- super(`Unknown SDD skill resource id: "${resourceId}".`);
29449
- this.name = "SkillResourceNotFoundError";
29450
- }
29451
- };
29452
- }
29453
- });
29454
-
29455
- // src/core/authoring.ts
29456
- function candidateOptions() {
29457
- try {
29458
- const config = loadProjectConfig();
29459
- return config ? { rules: config.rules, projectType: config.projectType } : {};
29460
- } catch {
29461
- return {};
29458
+ function updateSpecGated(kind, id, delta, dryRun) {
29459
+ const bound2 = candidateOptions();
29460
+ const gate = componentCandidateGate(bound2);
29461
+ const report2 = updateSpec(kind, id, delta, gate, dryRun);
29462
+ const testRoots = bound2.rules?.conformance?.testRoots ?? [];
29463
+ const changed = changedMethods(report2, delta);
29464
+ if (changed.length > 0 && testRoots.length > 0) {
29465
+ report2.testsToRevisit = findTestsReferencing2(changed, getProjectRoot(), testRoots);
29462
29466
  }
29467
+ return report2;
29463
29468
  }
29464
- function noticesFrom(verdict) {
29465
- return verdict.warnings.map((w) => `${w.code}: ${w.message}`);
29469
+ function changedMethods(report2, delta) {
29470
+ const named = /* @__PURE__ */ new Set();
29471
+ for (const change of report2.changes) {
29472
+ const [field, name] = change.path.split(".");
29473
+ if (field === "methods" && name) named.add(name);
29474
+ }
29475
+ const written = Array.isArray(delta?.methods) ? delta.methods : [];
29476
+ return [...named].map((name) => ({
29477
+ name,
29478
+ symbol: written.find((m) => m?.name === name)?.symbol
29479
+ }));
29466
29480
  }
29467
- function componentCandidateGate(options = candidateOptions()) {
29481
+ function methodMoveGate(source) {
29482
+ const bound2 = candidateOptions();
29468
29483
  return {
29469
29484
  gate: (kind, merged) => {
29470
29485
  if (kind !== "component") return;
29471
- const verdict = validateComponentCandidate(merged, options);
29472
- if (verdict.errors.length) throw new Error(formatCandidateRefusal(verdict));
29473
- return noticesFrom(verdict);
29474
- }
29486
+ const { codes, verdict, ceiling } = judgeMoveHome(merged, source, bound2);
29487
+ if (codes.length === 0) return noticesFrom(verdict);
29488
+ throw new Error(
29489
+ `${codes.join(", ")} refuses "${merged.id}" as the home for these methods. ` + (verdict.errors.length ? formatCandidateRefusal(verdict) : `It would reach ${merged.dependsOn?.length ?? 0} dependencies against a ceiling of ${ceiling}.`)
29490
+ );
29491
+ },
29492
+ assess: (kind, merged) => kind === "component" ? judgeMoveHome(merged, source, bound2).codes : []
29475
29493
  };
29476
29494
  }
29477
- function restatementParent(restatement) {
29478
- const spec = restatement.spec;
29479
- switch (restatement.kind) {
29480
- case "subsystem":
29481
- return { kind: "system", id: "system" };
29482
- case "component":
29483
- return { kind: "subsystem", id: spec.subsystem };
29484
- case "interface":
29485
- return { kind: "component", id: spec.component };
29486
- case "implementation":
29487
- return { kind: "interface", id: spec.contract };
29488
- default:
29489
- return null;
29495
+ function judgeMoveHome(component, source, options) {
29496
+ const ceiling = options.rules?.complexity?.maxComponentDependencies;
29497
+ const verdict = validateComponentCandidate(component, options);
29498
+ const codes = verdict.errors.map((e) => e.code);
29499
+ if (component.id !== source && ceiling !== void 0 && (component.dependsOn?.length ?? 0) > ceiling) {
29500
+ codes.push("EXCESSIVE_DEPENDENCIES");
29490
29501
  }
29502
+ return { codes: [...new Set(codes)], verdict, ceiling };
29491
29503
  }
29492
- function applyRestatement(restatement, existing, parent) {
29493
- const replacedExisting = existing !== null;
29494
- const parentRef = restatementParent(restatement);
29495
- if (parentRef && !parent) return refused(restatement, replacedExisting, MISSING_PARENT[restatement.kind](parentRef.id));
29496
- const status2 = statusForCreate(restatement, existing);
29497
- if ("refusal" in status2) return refused(restatement, replacedExisting, status2.refusal);
29498
- const candidate = JSON.parse(JSON.stringify(restatement.spec));
29499
- if (restatement.kind === "subsystem") candidate.parentSystem = parent.name;
29500
- const carried = carryInto(restatement, existing, candidate);
29501
- const cleared = clearedByOmission(existing, candidate, restatement.fields);
29502
- stampLifecycle(candidate, existing, status2.status);
29503
- const labelErrors = resolveLabelsOf(restatement.kind, candidate);
29504
- if (labelErrors.length) {
29505
- return refused(restatement, replacedExisting, `Unresolved narrative label references \u2014 nothing was saved:
29506
- - ${labelErrors.join("\n- ")}`);
29504
+ function moveMethods2(from, to, methods, dryRun) {
29505
+ return moveMethods(from, to, methods, methodMoveGate(from), dryRun);
29506
+ }
29507
+ var STORE_MANAGED_FIELDS, ALWAYS_CARRIED_FIELDS, STATUS_ORDER, STATUSED_KINDS, MISSING_PARENT, REWRITE_LABEL, SPEC_SCHEMA, METHOD_SCHEMA;
29508
+ var init_authoring = __esm({
29509
+ "src/core/authoring.ts"() {
29510
+ "use strict";
29511
+ init_specs();
29512
+ init_core();
29513
+ init_validation();
29514
+ init_candidate();
29515
+ init_narrative_labels();
29516
+ init_fs();
29517
+ STORE_MANAGED_FIELDS = /* @__PURE__ */ new Set(["status", "updatedAt"]);
29518
+ ALWAYS_CARRIED_FIELDS = /* @__PURE__ */ new Set(["ext"]);
29519
+ STATUS_ORDER = ["draft", "design", "complete"];
29520
+ STATUSED_KINDS = /* @__PURE__ */ new Set(["subsystem", "component", "interface", "implementation"]);
29521
+ MISSING_PARENT = {
29522
+ subsystem: () => "System spec must be initialized (sdd_initialize_system) first.",
29523
+ component: (id) => `Parent subsystem "${id}" does not exist.`,
29524
+ interface: (id) => `Component "${id}" does not exist.`,
29525
+ implementation: (id) => `Interface contract "${id}" does not exist.`
29526
+ };
29527
+ REWRITE_LABEL = {
29528
+ system: "System spec",
29529
+ subsystem: "Subsystem",
29530
+ component: "Component",
29531
+ interface: "Interface",
29532
+ implementation: "Implementation",
29533
+ type: "Type"
29534
+ };
29535
+ SPEC_SCHEMA = {
29536
+ system: SystemSpecSchema,
29537
+ subsystem: SubsystemSpecSchema,
29538
+ component: ComponentSpecSchema,
29539
+ interface: InterfaceSpecSchema,
29540
+ implementation: ImplementationSpecSchema,
29541
+ type: TypeSpecSchema
29542
+ };
29543
+ METHOD_SCHEMA = {
29544
+ interface: MethodSignatureSchema,
29545
+ implementation: MethodImplementationSchema,
29546
+ type: TypeMethodSchema
29547
+ };
29548
+ }
29549
+ });
29550
+
29551
+ // src/core/instructions.ts
29552
+ function governingProfile() {
29553
+ try {
29554
+ return loadProjectConfig4()?.projectType ?? null;
29555
+ } catch {
29556
+ return null;
29507
29557
  }
29508
- return {
29509
- spec: candidate,
29510
- ...status2.status ? { status: status2.status } : {},
29511
- replacedExisting,
29512
- notices: existing ? rewriteNotices(restatement.kind, existing, candidate, carried, cleared) : [],
29513
- changedMethods: changedMethodsOf(restatement.kind, existing, candidate)
29514
- };
29515
29558
  }
29516
- function refused(restatement, replacedExisting, refusal2) {
29517
- return { spec: restatement.spec, refusal: refusal2, replacedExisting, notices: [], changedMethods: [] };
29559
+ function blockApplies(block, profile) {
29560
+ if (!block.profile || block.profile.length === 0) return true;
29561
+ return profile !== null && block.profile.includes(profile);
29518
29562
  }
29519
- function statusForCreate(restatement, existing) {
29520
- if (!STATUSED_KINDS.has(restatement.kind)) return {};
29521
- const stated = restatement.status;
29522
- const stored = existing?.status;
29523
- const held = stored !== void 0 && STATUS_ORDER.includes(stored) ? stored : void 0;
29524
- if (stated === void 0) return { status: held ?? "draft" };
29525
- if (held === void 0 || STATUS_ORDER.indexOf(stated) >= STATUS_ORDER.indexOf(held)) return { status: stated };
29526
- const label = `${restatement.kind} "${restatement.spec.id}"`;
29527
- return {
29528
- refusal: `Refusing to re-author ${label} at status "${stated}": it is stored at "${held}", and a create tool never lowers a spec's status \u2014 a restatement that reopened a frozen spec would undo a lock without saying so. Nothing was written. To reopen it deliberately, use sdd_update_spec with {"status": "${stated}"}; to keep the level it has, leave status out.`
29529
- };
29563
+ function skillPointers(skills) {
29564
+ if (skills.length === 0) return "(none published by this server)";
29565
+ return skills.map((s) => `- \`${s.resourceUri}\` \u2014 ${s.description || s.name}`).join("\n");
29530
29566
  }
29531
- function carryInto(restatement, existing, candidate) {
29532
- if (!existing) return [];
29533
- const carried = ["createdAt"];
29534
- if (restatement.memberFields && Array.isArray(candidate.methods)) {
29535
- const stored = existing.methods ?? [];
29536
- for (const method2 of candidate.methods) {
29537
- const prev = stored.find((p) => p.name === method2.name);
29538
- for (const field of carryUnexpressed(prev, method2, restatement.memberFields)) carried.push(`${field} (${String(method2.name)})`);
29539
- }
29540
- }
29541
- carried.push(...carryUnexpressed(existing, candidate, [...restatement.fields, "createdAt"]));
29542
- return carried;
29567
+ function waironInstructions(profile, packNames, skills, variants) {
29568
+ return `# wairon \u2014 Spec-Driven Development (SDD)
29569
+
29570
+ This server owns one system's **spec tree**: its architecture, specified top-down
29571
+ and machine-checked. Author it ONLY through this server's \`sdd_*\` tools \u2014 never by
29572
+ writing spec files directly, and never via the \`wairon\` CLI (that is the human
29573
+ developer's tool).
29574
+
29575
+ ## The tree: L0 \u2192 L5
29576
+
29577
+ - **L0 System** \u2014 vision, boundaries, global requirements, target language.
29578
+ - **L1 Subsystem** \u2014 an isolated service, its \`publicInterfaces\`, its trusted links.
29579
+ - **L2 Component** \u2014 ONE building block (\`Portal\`, \`Orchestrator\`, \`Supervisor\`,
29580
+ \`Actor\`, \`Store\`, \`Index\`, \`Query\`, \`Registry\`, \`Adapter\`, \`Observer\`, and
29581
+ \`View\` for UIs) or pattern (\`Repository\`, and \`FeatureComponent\` or
29582
+ \`RouterComponent\` for UIs), plus \`owns\` (a pattern's private members) and
29583
+ \`dependsOn\` (collaborators). Logic is an \`Orchestrator\` whose \`dependencyClass\`
29584
+ (\`pure\` or \`read\`; unset, a workflow) bounds what it may depend on; a gateway is
29585
+ a \`Portal\` with the \`gateway\` variant. \`Specialist\` and \`Gateway\` are retired.
29586
+ - **L3 Interface** \u2014 the contract: method signatures with structured \`params\`,
29587
+ plus wire \`endpoint\` bindings for a Portal's methods.
29588
+ - **L4 Implementation** \u2014 a concrete realization of one L3 contract: narrative
29589
+ detail level, bound \`technologies\` (the vendor swap seam), optional \`sourcePath\`.
29590
+ - **L5 Narrative** \u2014 each method's logic as a FLAT numbered step list; \`call\` steps
29591
+ resolve to real dependency methods, and flow steps jump by step number.
29592
+
29593
+ These stereotypes are not labels \u2014 they carry enforced dependency rules (e.g. a
29594
+ Portal may not depend on a Store; state lives in a Store, never as fields inside
29595
+ an Orchestrator). \`sdd_validate_tree\` is the gate, and it is the authority on
29596
+ whether a design is legal.
29597
+
29598
+ ## Authoring order
29599
+
29600
+ 1. \`sdd_initialize_system\` (L0), then \`sdd_add_subsystem\` (L1).
29601
+ 2. Then, **one subsystem at a time**: \`sdd_add_component\` (L2) \u2192 \`sdd_define_interface\`
29602
+ (L3) \u2192 \`sdd_set_endpoints\` for Portals \u2192 \`sdd_write_narrative\` (L4+L5).
29603
+ 3. Run \`sdd_validate_tree\` as you go \u2014 early and per subsystem, not once at the end.
29604
+ \`sdd_get_status\` reports completeness; \`sdd_get_spec\` reads any spec back as JSON.
29605
+ 4. Edit granularly with \`sdd_update_spec\` (it renumbers narrative steps and
29606
+ relocates jump targets for you).
29607
+
29608
+ ## The tool schemas are the field reference
29609
+
29610
+ Every \`sdd_*\` tool's input schema is self-describing and authoritative \u2014 read the
29611
+ schema rather than guessing field names, and prefer structured \`params\` over prose
29612
+ signatures. Specs are ${profile === null ? "validated against the project's configured profile" : `governed by the **\`${profile}\`** profile`}, which decides which stereotypes are legal
29613
+ here, which narrative flow constructs the target language supports, and which
29614
+ extra rules apply. A subsystem may override the profile for itself.
29615
+
29616
+ ## Loaded extension packs
29617
+
29618
+ ${packNames.length === 0 ? "None \u2014 only wairon's built-in doctrine applies." : `${packNames.map((n) => `\`${n}\``).join(", ")} \u2014 these inject profiles, language tables, patterns, and
29619
+ additional conformance rules. Their doctrine is enforced by the same gate.`}
29620
+
29621
+ ${variants.length === 0 ? "" : `## Component variants in play
29622
+
29623
+ This project defines variants \u2014 named, base-anchored kinds carrying implementation
29624
+ guidance, so every component of the same kind is built alike: ${variants.map((v) => `\`${v.id}\` (a kind of ${v.base})`).join(", ")}.
29625
+ Tag a component with \`variant\` where it fits, and read a component's resolved
29626
+ guidance and its same-variant siblings from \`sdd_get_spec\` (kind: component).
29627
+
29628
+ `}## Read the skills BEFORE authoring
29629
+
29630
+ This server publishes its skills as MCP resources. **Read \`wairon-skill://sdd-architect\`
29631
+ before you design anything**, and the others when their turn comes \u2014 they carry the
29632
+ architecture standard, the narrative step reference, and the review checklist that
29633
+ this briefing only points at:
29634
+
29635
+ ${skillPointers(skills)}
29636
+
29637
+ ## Delegating implementation
29638
+
29639
+ Delegate component work from a LIVE agent brief, never a generated file: discover
29640
+ agents via \`listAgents\` (or the \`wairon-agent://\` resources), fetch the target's
29641
+ brief with \`sdd_get_agent_brief\`, and spawn a scoped subagent from it \u2014 its
29642
+ \`instructions\` as prompt, its \`ownedPaths\` as write fence. Briefs are composed
29643
+ from the current spec tree on every call, so a re-lock never requires a session
29644
+ restart \u2014 fetch fresh per delegation. \`wairon-skill://sdd-delegate\` carries the
29645
+ full flow.`;
29543
29646
  }
29544
- function carryUnexpressed(existing, next, expressed) {
29545
- if (!existing) return [];
29546
- const carried = [];
29547
- for (const [field, value] of Object.entries(existing)) {
29548
- if (value === void 0) continue;
29549
- if (STORE_MANAGED_FIELDS.has(field)) continue;
29550
- if (expressed.includes(field) && !ALWAYS_CARRIED_FIELDS.has(field)) continue;
29551
- if (next[field] !== void 0) continue;
29552
- next[field] = value;
29553
- carried.push(field);
29647
+ function buildServerInstructions() {
29648
+ const profile = governingProfile();
29649
+ let skills = [];
29650
+ let blocks = [];
29651
+ let packNames = [];
29652
+ const variants = [];
29653
+ try {
29654
+ skills = listSkillResources();
29655
+ const extensions = loadProjectExtensions2();
29656
+ blocks = extensions.instructions;
29657
+ packNames = extensions.packNames;
29658
+ } catch {
29554
29659
  }
29555
- return carried;
29556
- }
29557
- function isEmptyValue(value) {
29558
- if (value === void 0 || value === null) return true;
29559
- if (Array.isArray(value)) return value.length === 0;
29560
- if (typeof value === "string") return value.trim() === "";
29561
- if (typeof value === "object") return Object.keys(value).length === 0;
29562
- return false;
29660
+ const sections = [waironInstructions(profile, packNames, skills, variants)];
29661
+ for (const block of blocks) {
29662
+ if (!blockApplies(block, profile)) continue;
29663
+ sections.push(`## From pack "${block.pack}"
29664
+
29665
+ ${block.text.trim()}`);
29666
+ }
29667
+ return sections.join("\n\n");
29563
29668
  }
29564
- function clearedByOmission(existing, next, expressed) {
29565
- if (!existing) return [];
29566
- const stored = existing;
29567
- const cleared = [];
29568
- for (const field of expressed) {
29569
- const before = stored[field];
29570
- if (STORE_MANAGED_FIELDS.has(field) || ALWAYS_CARRIED_FIELDS.has(field)) continue;
29571
- if (isEmptyValue(before) || !isEmptyValue(next[field])) continue;
29572
- cleared.push(Array.isArray(before) ? `${field} (had ${before.length})` : field);
29669
+ var init_instructions = __esm({
29670
+ "src/core/instructions.ts"() {
29671
+ "use strict";
29672
+ init_skills();
29573
29673
  }
29574
- return cleared;
29674
+ });
29675
+
29676
+ // src/core/skills.ts
29677
+ function loadProjectExtensions2() {
29678
+ return loadProjectExtensions();
29575
29679
  }
29576
- function stampLifecycle(candidate, existing, status2) {
29577
- const now = (/* @__PURE__ */ new Date()).toISOString();
29578
- candidate.createdAt = existing?.createdAt ?? now;
29579
- candidate.updatedAt = now;
29580
- if (status2) candidate.status = status2;
29581
- else delete candidate.status;
29680
+ function loadProjectConfig4() {
29681
+ return loadProjectConfig();
29582
29682
  }
29583
- function resolveLabelsOf(kind, candidate) {
29584
- if (kind !== "implementation") return [];
29585
- const methods = candidate.methods ?? [];
29586
- return methods.flatMap((m) => resolveNarrativeLabels(m.name, m.narrative ?? []));
29683
+ function packSkillId(skill) {
29684
+ return `${packNamespace(skill.pack)}-${skill.id}`;
29587
29685
  }
29588
- function removedByName(before, after) {
29589
- if (!before?.length) return [];
29590
- const kept = new Set((after ?? []).map((m) => m.name));
29591
- return before.map((m) => m.name).filter((n) => !kept.has(n));
29686
+ function packNamespace(pack) {
29687
+ return pack.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
29592
29688
  }
29593
- function rewriteNotices(kind, existing, candidate, carried, cleared) {
29594
- const named = kind === "system" ? existing.name : String(candidate.id);
29595
- const notices = [`${REWRITE_LABEL[kind]} "${named}" already existed \u2014 re-authored in place; this input REPLACES what it expresses.`];
29596
- if (carried.length) notices.push(`Carried forward (not expressible through this tool): ${carried.join(", ")}.`);
29597
- const removal = removalNotice(kind, existing, candidate);
29598
- if (removal) notices.push(removal);
29599
- if (cleared.length) {
29600
- notices.push(
29601
- `CLEARED by omission: ${cleared.join(", ")} \u2014 the argument was not repeated, so the previous value is gone. Use sdd_update_spec if you meant to leave it untouched.`
29602
- );
29603
- }
29604
- return notices;
29689
+ function extensionsFor(builtin, packSkills) {
29690
+ return packSkills.filter((s) => s.extends === builtin && fs20.existsSync(s.sourcePath));
29605
29691
  }
29606
- function removalNotice(kind, existing, candidate) {
29607
- const before = existing;
29608
- let removed = [];
29609
- let noun = "method";
29610
- let suffix = "";
29611
- if (kind === "interface" || kind === "implementation") {
29612
- removed = removedByName(before.methods, candidate.methods);
29613
- suffix = kind === "interface" ? " (endpoint bindings included)" : " (L5 narratives included)";
29614
- } else if (kind === "type") {
29615
- removed = [
29616
- ...removedByName(before.fields, candidate.fields).map((n) => `field ${n}`),
29617
- ...removedByName(before.methods, candidate.methods).map((n) => `method ${n}`)
29618
- ];
29619
- noun = "member";
29692
+ function composeBuiltinSkill(name, packSkills) {
29693
+ const srcPath = skillTemplatePath(name);
29694
+ const base = fs20.existsSync(srcPath) ? fs20.readFileSync(srcPath, "utf-8") : "";
29695
+ const sections = extensionsFor(name, packSkills);
29696
+ if (sections.length === 0) return base;
29697
+ const parts = [base.trimEnd()];
29698
+ for (const section of sections) {
29699
+ const body = fs20.readFileSync(section.sourcePath, "utf-8").replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, "");
29700
+ parts.push(`## Platform: ${section.pack}`, body.trim());
29620
29701
  }
29621
- if (!removed.length) return void 0;
29622
- return `REMOVED by this restatement: ${removed.length === 1 ? noun : `${noun}s`} ${removed.map((n) => `"${n}"`).join(", ")}${suffix} \u2014 absent from the input, so no longer in the spec. Restate them to keep them, or use sdd_update_spec to edit one member at a time.`;
29623
- }
29624
- function changedMethodsOf(kind, existing, candidate) {
29625
- if (!existing || !(kind in METHOD_SCHEMA)) return [];
29626
- const schema = METHOD_SCHEMA[kind];
29627
- const stored = existing.methods ?? [];
29628
- const next = candidate.methods ?? [];
29629
- return stored.filter((method2) => {
29630
- const restated = next.find((m) => m.name === method2.name);
29631
- if (!restated) return true;
29632
- const parsed = schema.safeParse(restated);
29633
- return !parsed.success || canonical(parsed.data) !== canonical(method2);
29634
- }).map((method2) => method2.name);
29635
- }
29636
- function canonical(value) {
29637
- return JSON.stringify(value, (_key, v) => v && typeof v === "object" && !Array.isArray(v) ? Object.fromEntries(Object.entries(v).filter(([, x]) => x !== void 0).sort(([a], [b]) => a.localeCompare(b))) : v);
29702
+ return `${parts.join("\n\n")}
29703
+ `;
29638
29704
  }
29639
- function writeSpec(restatement) {
29640
- const bound2 = candidateOptions();
29641
- const parentRef = restatementParent(restatement);
29642
- const parent = parentRef ? loadSpec(parentRef.kind, parentRef.id) : null;
29643
- const id = restatement.kind === "system" ? "system" : restatement.spec.id;
29644
- const existing = loadSpec(restatement.kind, id);
29645
- const application = applyRestatement(restatement, existing, parent);
29646
- if (application.refusal !== void 0) throw new Error(application.refusal);
29647
- const gateNotices = restatement.kind === "component" ? judgeComponent(application.spec, bound2) : [];
29648
- const persisted = persistCandidate(restatement.kind, application.spec);
29649
- const testsToRevisit = testsInvalidatedBy(application.changedMethods, [existing, application.spec], bound2);
29650
- return {
29651
- kind: restatement.kind,
29652
- id,
29653
- name: application.spec.name,
29654
- replacedExisting: application.replacedExisting,
29655
- ...application.status ? { status: application.status } : {},
29656
- notices: [...gateNotices, ...persisted.notices, ...application.notices],
29657
- ...persisted.scaffoldedProjectPath ? { scaffoldedProjectPath: persisted.scaffoldedProjectPath } : {},
29658
- testsToRevisit
29705
+ function readFrontmatter(raw, fallbackName) {
29706
+ const match = /^---\r?\n([\s\S]*?)\r?\n---/.exec(raw);
29707
+ const block = match ? match[1] : "";
29708
+ const field = (key) => {
29709
+ const m = new RegExp(`^${key}:\\s*(.*)$`, "m").exec(block);
29710
+ return m ? m[1].trim() : "";
29659
29711
  };
29712
+ return { name: field("name") || fallbackName, description: field("description") };
29660
29713
  }
29661
- function judgeComponent(candidate, options) {
29662
- const verdict = validateComponentCandidate(candidate, options);
29663
- if (verdict.errors.length) throw new Error(formatCandidateRefusal(verdict));
29664
- return noticesFrom(verdict);
29714
+ function builtinSkillsDir() {
29715
+ return path31.resolve(__dirname, "..", "templates", "skills");
29665
29716
  }
29666
- function persistCandidate(kind, spec) {
29667
- const projectPath = kind === "subsystem" ? spec.projectPath : void 0;
29668
- if (projectPath && projectPath.trim() !== "") {
29669
- createChainedSubsystem(spec, spec.name);
29670
- return { notices: [], scaffoldedProjectPath: projectPath };
29717
+ function skillTemplatePath(name) {
29718
+ return path31.join(builtinSkillsDir(), `${name}.md`);
29719
+ }
29720
+ function skillDestPath(type, destDir, name) {
29721
+ if (type === "claude" || type === "codex" || type === "gemini" || type === "agy") {
29722
+ return path31.join(destDir, name, "SKILL.md");
29671
29723
  }
29672
- return { notices: saveSpec(kind, spec) };
29724
+ return path31.join(destDir, `${name}.md`);
29673
29725
  }
29674
- function testsInvalidatedBy(methods, specs, options) {
29675
- const testRoots = options.rules?.conformance?.testRoots ?? [];
29676
- if (methods.length === 0 || testRoots.length === 0) return [];
29677
- const symbolOf = (name) => specs.map((s) => (s?.methods ?? []).find((m) => m.name === name)?.symbol).find((symbol) => symbol !== void 0);
29678
- return findTestsReferencing2(methods.map((name) => ({ name, symbol: symbolOf(name) })), getProjectRoot(), testRoots);
29726
+ function skillsDirForTarget(type) {
29727
+ switch (type) {
29728
+ case "claude":
29729
+ return fromProjectRoot(".claude", "skills");
29730
+ case "gemini":
29731
+ // Gemini-based Antigravity
29732
+ case "agy":
29733
+ case "codex":
29734
+ return fromProjectRoot(".agents", "skills");
29735
+ case "cursor":
29736
+ return fromProjectRoot(".cursor", "skills");
29737
+ default:
29738
+ return null;
29739
+ }
29679
29740
  }
29680
- function deleteSpec2(kind, id) {
29681
- const bound2 = candidateOptions();
29682
- const stored = loadSpec(kind, id);
29683
- const deleted = deleteSpec(kind, id);
29684
- const methods = deleted ? (stored?.methods ?? []).map((m) => m.name) : [];
29685
- const testsToRevisit = testsInvalidatedBy(methods, [stored], bound2);
29686
- return { kind, id, deleted, testsToRevisit };
29741
+ function listSkillNames() {
29742
+ return [...SKILL_NAMES];
29687
29743
  }
29688
- function updateSpecGated(kind, id, delta, dryRun) {
29689
- const bound2 = candidateOptions();
29690
- const gate = componentCandidateGate(bound2);
29691
- const report2 = updateSpec(kind, id, delta, gate, dryRun);
29692
- const testRoots = bound2.rules?.conformance?.testRoots ?? [];
29693
- const changed = changedMethods(report2, delta);
29694
- if (changed.length > 0 && testRoots.length > 0) {
29695
- report2.testsToRevisit = findTestsReferencing2(changed, getProjectRoot(), testRoots);
29744
+ function exportSddSkills(targetTypes) {
29745
+ const types = targetTypes ?? activeTargetTypes2();
29746
+ const destinations = [];
29747
+ const skipped = [];
29748
+ let fileCount = 0;
29749
+ const packSkills = loadProjectExtensions2().skills;
29750
+ for (const type of types) {
29751
+ const destDir = skillsDirForTarget(type);
29752
+ if (!destDir) {
29753
+ skipped.push(type);
29754
+ continue;
29755
+ }
29756
+ ensureDir(destDir);
29757
+ destinations.push(destDir);
29758
+ for (const name of SKILL_NAMES) {
29759
+ if (!fs20.existsSync(skillTemplatePath(name))) continue;
29760
+ const content = composeBuiltinSkill(name, packSkills.filter((s) => s.targets.includes(type)));
29761
+ const destPath = skillDestPath(type, destDir, name);
29762
+ ensureDir(path31.dirname(destPath));
29763
+ fs20.writeFileSync(destPath, content, "utf-8");
29764
+ fileCount++;
29765
+ }
29766
+ for (const skill of packSkills) {
29767
+ if (skill.extends !== void 0) continue;
29768
+ if (!skill.targets.includes(type)) continue;
29769
+ if (!fs20.existsSync(skill.sourcePath)) continue;
29770
+ const id = packSkillId(skill);
29771
+ const content = fs20.readFileSync(skill.sourcePath, "utf-8");
29772
+ const destPath = skillDestPath(type, destDir, id);
29773
+ ensureDir(path31.dirname(destPath));
29774
+ fs20.writeFileSync(destPath, content, "utf-8");
29775
+ fileCount++;
29776
+ }
29696
29777
  }
29697
- return report2;
29778
+ return { destinations, fileCount, skipped };
29698
29779
  }
29699
- function changedMethods(report2, delta) {
29700
- const named = /* @__PURE__ */ new Set();
29701
- for (const change of report2.changes) {
29702
- const [field, name] = change.path.split(".");
29703
- if (field === "methods" && name) named.add(name);
29780
+ function checkSkillFreshness(type) {
29781
+ const dir = skillsDirForTarget(type);
29782
+ const result = { dir, missing: [], stale: [], ok: [] };
29783
+ if (!dir) return result;
29784
+ const packSkills = loadProjectExtensions2().skills.filter((s) => s.targets.includes(type));
29785
+ for (const name of SKILL_NAMES) {
29786
+ const destPath = skillDestPath(type, dir, name);
29787
+ if (!fs20.existsSync(destPath)) {
29788
+ result.missing.push(name);
29789
+ continue;
29790
+ }
29791
+ const want = composeBuiltinSkill(name, packSkills);
29792
+ const have = fs20.readFileSync(destPath, "utf-8");
29793
+ if (have === want) result.ok.push(name);
29794
+ else result.stale.push(name);
29704
29795
  }
29705
- const written = Array.isArray(delta?.methods) ? delta.methods : [];
29706
- return [...named].map((name) => ({
29707
- name,
29708
- symbol: written.find((m) => m?.name === name)?.symbol
29709
- }));
29796
+ return result;
29710
29797
  }
29711
- function methodMoveGate(source) {
29712
- const bound2 = candidateOptions();
29713
- return {
29714
- gate: (kind, merged) => {
29715
- if (kind !== "component") return;
29716
- const { codes, verdict, ceiling } = judgeMoveHome(merged, source, bound2);
29717
- if (codes.length === 0) return noticesFrom(verdict);
29718
- throw new Error(
29719
- `${codes.join(", ")} refuses "${merged.id}" as the home for these methods. ` + (verdict.errors.length ? formatCandidateRefusal(verdict) : `It would reach ${merged.dependsOn?.length ?? 0} dependencies against a ceiling of ${ceiling}.`)
29720
- );
29721
- },
29722
- assess: (kind, merged) => kind === "component" ? judgeMoveHome(merged, source, bound2).codes : []
29723
- };
29798
+ function activeTargetTypes2() {
29799
+ const config = loadProjectConfig4();
29800
+ if (!config) return [];
29801
+ return activeTargetTypes(config);
29724
29802
  }
29725
- function judgeMoveHome(component, source, options) {
29726
- const ceiling = options.rules?.complexity?.maxComponentDependencies;
29727
- const verdict = validateComponentCandidate(component, options);
29728
- const codes = verdict.errors.map((e) => e.code);
29729
- if (component.id !== source && ceiling !== void 0 && (component.dependsOn?.length ?? 0) > ceiling) {
29730
- codes.push("EXCESSIVE_DEPENDENCIES");
29731
- }
29732
- return { codes: [...new Set(codes)], verdict, ceiling };
29803
+ function readSkillFrontmatter(name) {
29804
+ return readFrontmatter(fs20.readFileSync(skillTemplatePath(name), "utf-8"), name);
29733
29805
  }
29734
- function moveMethods2(from, to, methods, dryRun) {
29735
- return moveMethods(from, to, methods, methodMoveGate(from), dryRun);
29806
+ function listSkillResources() {
29807
+ const builtin = RESOURCE_SKILL_IDS.map((id) => {
29808
+ const fm = readSkillFrontmatter(id);
29809
+ return {
29810
+ id,
29811
+ name: fm.name,
29812
+ description: fm.description,
29813
+ version: WAIRON_VERSION,
29814
+ resourceUri: `${SKILL_RESOURCE_SCHEME}://${id}`,
29815
+ defaultForHostedMcp: true
29816
+ };
29817
+ });
29818
+ const pack = loadProjectExtensions2().skills.filter((s) => s.extends === void 0 && fs20.existsSync(s.sourcePath)).map((skill) => {
29819
+ const id = packSkillId(skill);
29820
+ const fm = readFrontmatter(fs20.readFileSync(skill.sourcePath, "utf-8"), id);
29821
+ return {
29822
+ id,
29823
+ name: fm.name,
29824
+ description: fm.description,
29825
+ version: skill.packVersion ?? WAIRON_VERSION,
29826
+ resourceUri: `${SKILL_RESOURCE_SCHEME}://${id}`,
29827
+ defaultForHostedMcp: true
29828
+ };
29829
+ });
29830
+ return [...builtin, ...pack];
29736
29831
  }
29737
- var STORE_MANAGED_FIELDS, ALWAYS_CARRIED_FIELDS, STATUS_ORDER, STATUSED_KINDS, MISSING_PARENT, REWRITE_LABEL, METHOD_SCHEMA;
29738
- var init_authoring = __esm({
29739
- "src/core/authoring.ts"() {
29832
+ function readSkillResource(resourceId) {
29833
+ const packSkills = loadProjectExtensions2().skills;
29834
+ if (SKILL_NAMES.includes(resourceId)) return composeBuiltinSkill(resourceId, packSkills);
29835
+ const packSkill = packSkills.find((s) => s.extends === void 0 && packSkillId(s) === resourceId);
29836
+ if (packSkill) return fs20.readFileSync(packSkill.sourcePath, "utf-8");
29837
+ return fs20.readFileSync(skillTemplatePath(resourceId), "utf-8");
29838
+ }
29839
+ function listResources() {
29840
+ return listSkillResources();
29841
+ }
29842
+ function buildServerInstructions2() {
29843
+ return buildServerInstructions();
29844
+ }
29845
+ function readResource(resourceId) {
29846
+ const known = listSkillResources().some((d) => d.id === resourceId);
29847
+ if (!known) throw new SkillResourceNotFoundError(resourceId);
29848
+ return readSkillResource(resourceId);
29849
+ }
29850
+ var path31, fs20, SKILL_NAMES, SKILL_RESOURCE_SCHEME, RESOURCE_SKILL_IDS, SkillResourceNotFoundError;
29851
+ var init_skills = __esm({
29852
+ "src/core/skills.ts"() {
29740
29853
  "use strict";
29741
- init_specs();
29742
- init_core();
29743
- init_validation();
29744
- init_candidate();
29745
- init_narrative_labels();
29854
+ path31 = __toESM(require("path"));
29855
+ fs20 = __toESM(require("fs"));
29746
29856
  init_fs();
29747
- STORE_MANAGED_FIELDS = /* @__PURE__ */ new Set(["status", "updatedAt"]);
29748
- ALWAYS_CARRIED_FIELDS = /* @__PURE__ */ new Set(["ext"]);
29749
- STATUS_ORDER = ["draft", "design", "complete"];
29750
- STATUSED_KINDS = /* @__PURE__ */ new Set(["subsystem", "component", "interface", "implementation"]);
29751
- MISSING_PARENT = {
29752
- subsystem: () => "System spec must be initialized (sdd_initialize_system) first.",
29753
- component: (id) => `Parent subsystem "${id}" does not exist.`,
29754
- interface: (id) => `Component "${id}" does not exist.`,
29755
- implementation: (id) => `Interface contract "${id}" does not exist.`
29756
- };
29757
- REWRITE_LABEL = {
29758
- system: "System spec",
29759
- subsystem: "Subsystem",
29760
- component: "Component",
29761
- interface: "Interface",
29762
- implementation: "Implementation",
29763
- type: "Type"
29764
- };
29765
- METHOD_SCHEMA = {
29766
- interface: MethodSignatureSchema,
29767
- implementation: MethodImplementationSchema,
29768
- type: TypeMethodSchema
29857
+ init_defaults();
29858
+ init_project();
29859
+ init_extensions();
29860
+ init_instructions();
29861
+ init_core();
29862
+ SKILL_NAMES = ["sdd-architect", "sdd-narrative", "sdd-auditor", "sdd-implement", "sdd-delegate"];
29863
+ SKILL_RESOURCE_SCHEME = "wairon-skill";
29864
+ RESOURCE_SKILL_IDS = [...SKILL_NAMES].sort();
29865
+ SkillResourceNotFoundError = class extends Error {
29866
+ constructor(resourceId) {
29867
+ super(`Unknown SDD skill resource id: "${resourceId}".`);
29868
+ this.name = "SkillResourceNotFoundError";
29869
+ }
29769
29870
  };
29770
29871
  }
29771
29872
  });
@@ -29862,7 +29963,12 @@ function publicInterfacesReplacement(subsystem, stored, list2) {
29862
29963
  deletes.push(pi.component ? { component: pi.component, interface: pi.interface, action: "delete" } : { type: pi.type, details: pi.details, action: "delete" });
29863
29964
  }
29864
29965
  }
29865
- return { publicInterfaces: [...deletes, ...list2] };
29966
+ const restatedEntries = list2.map((pi) => {
29967
+ const prev = stored.find((s) => identity(s) === identity(pi));
29968
+ const dropped = prev ? Object.keys(prev).filter((f) => prev[f] !== void 0 && pi[f] === void 0) : [];
29969
+ return dropped.length ? { ...pi, unset: dropped } : pi;
29970
+ });
29971
+ return { publicInterfaces: [...deletes, ...restatedEntries] };
29866
29972
  }
29867
29973
  function text(content) {
29868
29974
  return { content: [{ type: "text", text: content }] };
@@ -30241,7 +30347,8 @@ function createMcpServer(options = {}) {
30241
30347
  type: import_zod11.z.enum(["REST", "GraphQL", "MessageBus", "RPC", "Custom"]),
30242
30348
  details: import_zod11.z.string(),
30243
30349
  component: import_zod11.z.string().optional().describe("The L2 component id that realizes this interface (this subsystem's published surface)"),
30244
- interface: import_zod11.z.string().optional().describe("Optional L3 interface id on that component backing this entry")
30350
+ interface: import_zod11.z.string().optional().describe("Optional L3 interface id on that component backing this entry"),
30351
+ consumers: import_zod11.z.array(import_zod11.z.string()).optional().describe("The subsystem ids this surface is published to. Absent, any subsystem may depend on the component; present, only these may \u2014 when every entry publishing the component names consumers, the union of the lists is the whole set, and a dependency from any other subsystem is CROSS_SUBSYSTEM_UNLISTED_CONSUMER. Every id must name a subsystem of the tree (PUBLIC_INTERFACE_UNKNOWN_CONSUMER).")
30245
30352
  }).strict()).optional().describe("Public entrypoints exposed by this subsystem, each bound to a realizing component"),
30246
30353
  projectPath: import_zod11.z.string().optional().describe("Relative path to external project root for subsystem chaining"),
30247
30354
  targetLanguage: import_zod11.z.string().optional().describe("Override of the system-level targetLanguage for this subsystem"),
@@ -30311,15 +30418,16 @@ function createMcpServer(options = {}) {
30311
30418
  type: import_zod11.z.enum(["REST", "GraphQL", "MessageBus", "RPC", "Custom"]),
30312
30419
  details: import_zod11.z.string(),
30313
30420
  component: import_zod11.z.string().optional().describe("The L2 component id that realizes this interface"),
30314
- interface: import_zod11.z.string().optional().describe("Optional L3 interface id on that component")
30421
+ interface: import_zod11.z.string().optional().describe("Optional L3 interface id on that component"),
30422
+ consumers: import_zod11.z.array(import_zod11.z.string()).optional().describe("The subsystem ids this surface is published to. Absent, any subsystem may depend on the component; present, only these may \u2014 when every entry publishing the component names consumers, the union of the lists is the whole set, and a dependency from any other subsystem is CROSS_SUBSYSTEM_UNLISTED_CONSUMER. Every id must name a subsystem of the tree (PUBLIC_INTERFACE_UNKNOWN_CONSUMER).")
30315
30423
  }).strict()).describe("The full replacement list of public interfaces for this subsystem")
30316
30424
  },
30317
30425
  outputSchema: specChangeReportOutput
30318
30426
  },
30319
30427
  ({ subsystem, publicInterfaces }) => {
30320
30428
  try {
30321
- const { loadSubsystemSpec: loadSubsystemSpec2 } = requireSpecs();
30322
- const sub = loadSubsystemSpec2(subsystem);
30429
+ const { loadSubsystemSpec: loadSubsystemSpec3 } = requireSpecs();
30430
+ const sub = loadSubsystemSpec3(subsystem);
30323
30431
  if (!sub) return errText(`Subsystem "${subsystem}" does not exist.`);
30324
30432
  const delta = publicInterfacesReplacement(subsystem, sub.publicInterfaces ?? [], publicInterfaces);
30325
30433
  if (typeof delta === "string") return errText(delta);
@@ -31973,6 +32081,7 @@ init_errors();
31973
32081
  init_fs();
31974
32082
  init_core();
31975
32083
  init_core();
32084
+ init_authoring();
31976
32085
  init_core();
31977
32086
  function composeAgentBrief2(agentId) {
31978
32087
  return composeAgentBrief(agentId);
@@ -31998,8 +32107,11 @@ function projectConfigExists2() {
31998
32107
  function loadSystemSpec2() {
31999
32108
  return loadSystemSpec();
32000
32109
  }
32001
- function createChainedSubsystem2(subsystem, projectName) {
32002
- createChainedSubsystem(subsystem, projectName);
32110
+ function loadSubsystemSpec2(id) {
32111
+ return loadSubsystemSpec(id);
32112
+ }
32113
+ function writeSpec2(restatement) {
32114
+ return writeSpec(restatement);
32003
32115
  }
32004
32116
  function resolveAgentTopology2() {
32005
32117
  return resolveAgentTopology();
@@ -32082,6 +32194,19 @@ function getStatusReport2(options, decor) {
32082
32194
  function approvalVerdict2() {
32083
32195
  return approvalVerdict();
32084
32196
  }
32197
+ function subsystemAddRestatement(id, projectPath, name, stored) {
32198
+ const spec = { id, projectPath };
32199
+ const fields = ["id", "projectPath"];
32200
+ if (name !== void 0 || !stored) {
32201
+ spec.name = name ?? id;
32202
+ fields.push("name");
32203
+ }
32204
+ if (!stored) {
32205
+ spec.description = `External subsystem ${spec.name}`;
32206
+ fields.push("description");
32207
+ }
32208
+ return { kind: "subsystem", spec, fields };
32209
+ }
32085
32210
  async function runSubsystemAdd(id, options = {}) {
32086
32211
  logger.header("wairon subsystem add");
32087
32212
  if (!projectConfigExists2()) {
@@ -32096,23 +32221,12 @@ async function runSubsystemAdd(id, options = {}) {
32096
32221
  if (!system) {
32097
32222
  throw new WaironError("System spec is missing. Run `wairon init` first.");
32098
32223
  }
32099
- const displayName = options.name ?? id;
32100
- const now = (/* @__PURE__ */ new Date()).toISOString();
32101
- const subsystem = {
32102
- id,
32103
- name: displayName,
32104
- description: `External subsystem ${displayName}`,
32105
- parentSystem: system.name,
32106
- publicInterfaces: [],
32107
- projectPath: options.projectPath,
32108
- trustedLinks: [],
32109
- status: "draft",
32110
- createdAt: now,
32111
- updatedAt: now
32112
- };
32113
- createChainedSubsystem2(subsystem, displayName);
32224
+ const stored = loadSubsystemSpec2(id);
32225
+ const restatement = subsystemAddRestatement(id, options.projectPath, options.name, stored);
32226
+ const receipt = writeSpec2(restatement);
32114
32227
  const childDir = path30.resolve(getProjectRoot(), options.projectPath);
32115
- logger.success(`Added external subsystem "${id}" \u2192 ${options.projectPath}`);
32228
+ logger.success(`${receipt.replacedExisting ? "Re-authored" : "Added"} external subsystem "${id}" \u2192 ${options.projectPath}`);
32229
+ for (const notice of receipt.notices) logger.info(notice);
32116
32230
  logger.info(`Scaffolded child project at ${path30.relative(process.cwd(), childDir) || "."}`);
32117
32231
  logger.info(`Design its spec tree from this parent using namespaced ids (e.g. ${id}::<component>).`);
32118
32232
  }
@@ -32248,20 +32362,16 @@ async function runInitAsExternalSubsystem(parentRoot, cwd, options) {
32248
32362
  if (!system) {
32249
32363
  throw new WaironError(`Parent project at ${parentRoot} has no system spec.`);
32250
32364
  }
32251
- const now = (/* @__PURE__ */ new Date()).toISOString();
32252
- const subsystem = {
32253
- id: subsystemId,
32254
- name: subsystemId,
32255
- description: `External subsystem ${subsystemId}`,
32256
- parentSystem: system.name,
32257
- publicInterfaces: [],
32258
- projectPath: relPath,
32259
- trustedLinks: [],
32260
- status: "draft",
32261
- createdAt: now,
32262
- updatedAt: now
32263
- };
32264
- createChainedSubsystem2(subsystem, subsystemId);
32365
+ const stored = loadSubsystemSpec2(subsystemId);
32366
+ const spec = { id: subsystemId, projectPath: relPath };
32367
+ const fields = ["id", "projectPath"];
32368
+ if (!stored) {
32369
+ Object.assign(spec, { name: subsystemId, description: `External subsystem ${subsystemId}` });
32370
+ fields.push("name", "description");
32371
+ }
32372
+ const restatement = { kind: "subsystem", spec, fields };
32373
+ const receipt = writeSpec2(restatement);
32374
+ for (const notice of receipt.notices) logger.info(notice);
32265
32375
  } finally {
32266
32376
  setProjectRoot(prevOverride);
32267
32377
  }