@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 +721 -611
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +66 -50
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -65,7 +65,7 @@ var init_defaults = __esm({
|
|
|
65
65
|
copilot: ".github/prompts",
|
|
66
66
|
codex: ".codex/agents"
|
|
67
67
|
};
|
|
68
|
-
WAIRON_VERSION = "5.1.1-dev.
|
|
68
|
+
WAIRON_VERSION = "5.1.1-dev.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
|
|
16165
|
-
// that component's stereotype can realize the type it
|
|
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/
|
|
29133
|
-
function
|
|
29196
|
+
// src/core/authoring.ts
|
|
29197
|
+
function candidateOptions() {
|
|
29134
29198
|
try {
|
|
29135
|
-
|
|
29199
|
+
const config = loadProjectConfig();
|
|
29200
|
+
return config ? { rules: config.rules, projectType: config.projectType } : {};
|
|
29136
29201
|
} catch {
|
|
29137
|
-
return
|
|
29202
|
+
return {};
|
|
29138
29203
|
}
|
|
29139
29204
|
}
|
|
29140
|
-
function
|
|
29141
|
-
|
|
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
|
|
29149
|
-
return
|
|
29150
|
-
|
|
29151
|
-
|
|
29152
|
-
|
|
29153
|
-
|
|
29154
|
-
|
|
29155
|
-
|
|
29156
|
-
|
|
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
|
|
29229
|
-
const
|
|
29230
|
-
|
|
29231
|
-
|
|
29232
|
-
|
|
29233
|
-
|
|
29234
|
-
|
|
29235
|
-
|
|
29236
|
-
|
|
29237
|
-
|
|
29238
|
-
|
|
29239
|
-
|
|
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
|
-
|
|
29251
|
-
|
|
29252
|
-
|
|
29253
|
-
|
|
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
|
-
|
|
29258
|
-
|
|
29259
|
-
|
|
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
|
|
29262
|
-
|
|
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
|
|
29265
|
-
return
|
|
29286
|
+
function refused(restatement, replacedExisting, refusal2) {
|
|
29287
|
+
return { spec: restatement.spec, refusal: refusal2, replacedExisting, notices: [], changedMethods: [] };
|
|
29266
29288
|
}
|
|
29267
|
-
function
|
|
29268
|
-
|
|
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
|
|
29271
|
-
|
|
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
|
|
29274
|
-
|
|
29275
|
-
const
|
|
29276
|
-
const
|
|
29277
|
-
|
|
29278
|
-
|
|
29279
|
-
|
|
29280
|
-
|
|
29281
|
-
|
|
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
|
|
29284
|
-
`;
|
|
29325
|
+
return carried;
|
|
29285
29326
|
}
|
|
29286
|
-
function
|
|
29287
|
-
|
|
29288
|
-
|
|
29289
|
-
|
|
29290
|
-
|
|
29291
|
-
|
|
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
|
|
29302
|
-
if (
|
|
29303
|
-
|
|
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
|
|
29344
|
+
return cleared;
|
|
29306
29345
|
}
|
|
29307
|
-
function
|
|
29308
|
-
|
|
29309
|
-
|
|
29310
|
-
|
|
29311
|
-
|
|
29312
|
-
|
|
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
|
|
29323
|
-
return [
|
|
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
|
|
29326
|
-
|
|
29327
|
-
const
|
|
29328
|
-
|
|
29329
|
-
|
|
29330
|
-
|
|
29331
|
-
|
|
29332
|
-
|
|
29333
|
-
|
|
29334
|
-
|
|
29335
|
-
|
|
29336
|
-
|
|
29337
|
-
|
|
29338
|
-
|
|
29339
|
-
|
|
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
|
|
29374
|
+
return notices;
|
|
29360
29375
|
}
|
|
29361
|
-
function
|
|
29362
|
-
const
|
|
29363
|
-
|
|
29364
|
-
|
|
29365
|
-
|
|
29366
|
-
|
|
29367
|
-
|
|
29368
|
-
|
|
29369
|
-
|
|
29370
|
-
|
|
29371
|
-
|
|
29372
|
-
|
|
29373
|
-
|
|
29374
|
-
|
|
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
|
|
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
|
|
29380
|
-
|
|
29381
|
-
|
|
29382
|
-
|
|
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
|
|
29385
|
-
return
|
|
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
|
|
29388
|
-
const
|
|
29389
|
-
|
|
29390
|
-
|
|
29391
|
-
|
|
29392
|
-
|
|
29393
|
-
|
|
29394
|
-
|
|
29395
|
-
|
|
29396
|
-
|
|
29397
|
-
|
|
29398
|
-
|
|
29399
|
-
|
|
29400
|
-
|
|
29401
|
-
|
|
29402
|
-
|
|
29403
|
-
|
|
29404
|
-
|
|
29405
|
-
|
|
29406
|
-
|
|
29407
|
-
|
|
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
|
|
29414
|
-
const
|
|
29415
|
-
if (
|
|
29416
|
-
|
|
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
|
|
29421
|
-
|
|
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
|
|
29424
|
-
|
|
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
|
|
29427
|
-
const
|
|
29428
|
-
|
|
29429
|
-
|
|
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
|
-
|
|
29432
|
-
|
|
29433
|
-
|
|
29434
|
-
|
|
29435
|
-
|
|
29436
|
-
|
|
29437
|
-
|
|
29438
|
-
|
|
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
|
|
29465
|
-
|
|
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
|
|
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 =
|
|
29472
|
-
if (
|
|
29473
|
-
|
|
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
|
|
29478
|
-
const
|
|
29479
|
-
|
|
29480
|
-
|
|
29481
|
-
|
|
29482
|
-
|
|
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
|
|
29493
|
-
|
|
29494
|
-
|
|
29495
|
-
|
|
29496
|
-
|
|
29497
|
-
|
|
29498
|
-
|
|
29499
|
-
|
|
29500
|
-
|
|
29501
|
-
|
|
29502
|
-
|
|
29503
|
-
|
|
29504
|
-
|
|
29505
|
-
|
|
29506
|
-
|
|
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
|
|
29517
|
-
|
|
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
|
|
29520
|
-
if (
|
|
29521
|
-
|
|
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
|
|
29532
|
-
|
|
29533
|
-
|
|
29534
|
-
|
|
29535
|
-
|
|
29536
|
-
|
|
29537
|
-
|
|
29538
|
-
|
|
29539
|
-
|
|
29540
|
-
|
|
29541
|
-
|
|
29542
|
-
|
|
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
|
|
29545
|
-
|
|
29546
|
-
|
|
29547
|
-
|
|
29548
|
-
|
|
29549
|
-
|
|
29550
|
-
|
|
29551
|
-
|
|
29552
|
-
|
|
29553
|
-
|
|
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
|
-
|
|
29556
|
-
|
|
29557
|
-
|
|
29558
|
-
|
|
29559
|
-
|
|
29560
|
-
|
|
29561
|
-
|
|
29562
|
-
return
|
|
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
|
-
|
|
29565
|
-
|
|
29566
|
-
|
|
29567
|
-
|
|
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
|
-
|
|
29674
|
+
});
|
|
29675
|
+
|
|
29676
|
+
// src/core/skills.ts
|
|
29677
|
+
function loadProjectExtensions2() {
|
|
29678
|
+
return loadProjectExtensions();
|
|
29575
29679
|
}
|
|
29576
|
-
function
|
|
29577
|
-
|
|
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
|
|
29584
|
-
|
|
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
|
|
29589
|
-
|
|
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
|
|
29594
|
-
|
|
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
|
|
29607
|
-
const
|
|
29608
|
-
|
|
29609
|
-
|
|
29610
|
-
|
|
29611
|
-
|
|
29612
|
-
|
|
29613
|
-
|
|
29614
|
-
|
|
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
|
-
|
|
29622
|
-
|
|
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
|
|
29640
|
-
const
|
|
29641
|
-
const
|
|
29642
|
-
const
|
|
29643
|
-
|
|
29644
|
-
|
|
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
|
|
29662
|
-
|
|
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
|
|
29667
|
-
|
|
29668
|
-
|
|
29669
|
-
|
|
29670
|
-
|
|
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
|
|
29724
|
+
return path31.join(destDir, `${name}.md`);
|
|
29673
29725
|
}
|
|
29674
|
-
function
|
|
29675
|
-
|
|
29676
|
-
|
|
29677
|
-
|
|
29678
|
-
|
|
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
|
|
29681
|
-
|
|
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
|
|
29689
|
-
const
|
|
29690
|
-
const
|
|
29691
|
-
const
|
|
29692
|
-
|
|
29693
|
-
const
|
|
29694
|
-
|
|
29695
|
-
|
|
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
|
|
29778
|
+
return { destinations, fileCount, skipped };
|
|
29698
29779
|
}
|
|
29699
|
-
function
|
|
29700
|
-
const
|
|
29701
|
-
|
|
29702
|
-
|
|
29703
|
-
|
|
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
|
-
|
|
29706
|
-
return [...named].map((name) => ({
|
|
29707
|
-
name,
|
|
29708
|
-
symbol: written.find((m) => m?.name === name)?.symbol
|
|
29709
|
-
}));
|
|
29796
|
+
return result;
|
|
29710
29797
|
}
|
|
29711
|
-
function
|
|
29712
|
-
const
|
|
29713
|
-
return
|
|
29714
|
-
|
|
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
|
|
29726
|
-
|
|
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
|
|
29735
|
-
|
|
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
|
-
|
|
29738
|
-
|
|
29739
|
-
|
|
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
|
-
|
|
29742
|
-
|
|
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
|
-
|
|
29748
|
-
|
|
29749
|
-
|
|
29750
|
-
|
|
29751
|
-
|
|
29752
|
-
|
|
29753
|
-
|
|
29754
|
-
|
|
29755
|
-
|
|
29756
|
-
|
|
29757
|
-
|
|
29758
|
-
|
|
29759
|
-
|
|
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
|
-
|
|
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:
|
|
30322
|
-
const sub =
|
|
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
|
|
32002
|
-
|
|
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
|
|
32100
|
-
const
|
|
32101
|
-
const
|
|
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(
|
|
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
|
|
32252
|
-
const
|
|
32253
|
-
|
|
32254
|
-
|
|
32255
|
-
description: `External subsystem ${subsystemId}
|
|
32256
|
-
|
|
32257
|
-
|
|
32258
|
-
|
|
32259
|
-
|
|
32260
|
-
|
|
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
|
}
|