@wairon/cli 5.1.1-dev.88 → 5.1.1-dev.89
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 +359 -140
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +145 -43
- 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.89";
|
|
69
69
|
GITHUB_REPO = "SYW-Apps/Waffle-AIron";
|
|
70
70
|
SUPPORTED_ALIASES = ["wai"];
|
|
71
71
|
SCAN_EXCLUDE_DIRS = /* @__PURE__ */ new Set([
|
|
@@ -201,6 +201,7 @@ var fs_exports = {};
|
|
|
201
201
|
__export(fs_exports, {
|
|
202
202
|
aiDir: () => aiDir,
|
|
203
203
|
aiDirAt: () => aiDirAt,
|
|
204
|
+
backupBeside: () => backupBeside,
|
|
204
205
|
currentRootBinding: () => currentRootBinding,
|
|
205
206
|
ensureDir: () => ensureDir,
|
|
206
207
|
findProjectRoot: () => findProjectRoot,
|
|
@@ -211,6 +212,7 @@ __export(fs_exports, {
|
|
|
211
212
|
getProjectRootOverride: () => getProjectRootOverride,
|
|
212
213
|
getRequestParentReach: () => getRequestParentReach,
|
|
213
214
|
getRequestProjectRoot: () => getRequestProjectRoot,
|
|
215
|
+
isOutsideRoot: () => isOutsideRoot,
|
|
214
216
|
listFiles: () => listFiles,
|
|
215
217
|
listFilesRecursive: () => listFilesRecursive,
|
|
216
218
|
pathExists: () => pathExists,
|
|
@@ -248,6 +250,15 @@ function readFileOrNull(filePath) {
|
|
|
248
250
|
function pathExists(targetPath) {
|
|
249
251
|
return fs3.existsSync(targetPath);
|
|
250
252
|
}
|
|
253
|
+
function isOutsideRoot(root, target) {
|
|
254
|
+
const rel2 = path4.relative(path4.resolve(root), path4.resolve(target));
|
|
255
|
+
return rel2 === ".." || rel2.startsWith(`..${path4.sep}`) || path4.isAbsolute(rel2);
|
|
256
|
+
}
|
|
257
|
+
function backupBeside(file, at = /* @__PURE__ */ new Date()) {
|
|
258
|
+
const backup = `${file}.wairon-backup-${at.toISOString().replace(/[:.]/g, "-")}`;
|
|
259
|
+
fs3.copyFileSync(file, backup);
|
|
260
|
+
return backup;
|
|
261
|
+
}
|
|
251
262
|
function listFiles(dirPath, ext) {
|
|
252
263
|
if (!fs3.existsSync(dirPath)) return [];
|
|
253
264
|
return fs3.readdirSync(dirPath).filter((f) => f.endsWith(ext)).map((f) => path4.join(dirPath, f));
|
|
@@ -2831,6 +2842,12 @@ function parseDeclaredCall(ref) {
|
|
|
2831
2842
|
if (at <= 0 || at === ref.length - 1) return null;
|
|
2832
2843
|
return { compId: ref.slice(0, at), methodName: ref.slice(at + 1) };
|
|
2833
2844
|
}
|
|
2845
|
+
function technologyName(tech) {
|
|
2846
|
+
return typeof tech === "string" ? tech : tech.name;
|
|
2847
|
+
}
|
|
2848
|
+
function technologyTokens(tech) {
|
|
2849
|
+
return typeof tech === "string" ? [tech] : [...tech.matches];
|
|
2850
|
+
}
|
|
2834
2851
|
function methodSourceFile(method2, implementationSourcePath) {
|
|
2835
2852
|
return method2.sourcePath || implementationSourcePath || void 0;
|
|
2836
2853
|
}
|
|
@@ -2890,7 +2907,7 @@ function typeSourceFiles(type) {
|
|
|
2890
2907
|
for (const method2 of type.methods ?? []) add2(method2.sourcePath);
|
|
2891
2908
|
return files;
|
|
2892
2909
|
}
|
|
2893
|
-
var import_zod8, SpecIdSchema, SpecStatusSchema, BoundaryItemSchema, RequirementItemSchema, DatabaseSpecSchema, DiagramConfigSchema, SURFACE_AUDIENCES, SurfaceAudienceSchema, SystemPublicInterfaceSchema, SystemSpecSchema, PublicInterfaceTypeSchema, PublicInterfaceSchema, TrustedLinkSchema, LintAllowSchema, LintConfigSchema, ExtDataSchema, LifecycleEntrypointSchema, SubsystemSpecSchema, ComponentTypeSchema, PATTERN_TYPES, RETIRED_STEREOTYPES, PortalTypeSchema, DispatchBindingSchema, DurabilitySchema, DependencyClassSchema, PatternRefSchema, EventBindingSchema, ExternalLinkTypeSchema, ExternalLinkSchema, PortalAuthSchemeSchema, PortalAuthSchema, PortalMountSchema, ComponentSpecSchema, BLOCK_NOUNS, HttpMethodSchema, TransportSchema, EndpointSchema, SEMANTIC_GUARANTEES, GuaranteeSchema, MethodParamSchema, FindingDeclarationSchema, MethodSignatureSchema, INTENT_FLOOR_MIN_CHARS, InterfaceSpecSchema, NarrativeStepTypeSchema, LoopKindSchema, SwitchCaseSchema, CatchClauseSchema, ParallelBranchSchema, NarrativeStepSchema, NarrativeDetailSchema, ConformanceTierSchema, MethodImplementationSchema, ImplementationSpecSchema, TypeKindSchema, TypeFieldSchema, TypeMethodSchema, InvariantSchema, TypeSpecSchema, SurfaceOriginSchema, SurfaceTypeDefSchema, SurfaceTypeExportSchema, SurfaceContractEntrySchema, SurfaceSnapshotSchema, ExternalLockEntrySchema, ExternalsLockSchema, NamedOpenApiSpecSchema, GroupSpecSchema;
|
|
2910
|
+
var import_zod8, SpecIdSchema, SpecStatusSchema, BoundaryItemSchema, RequirementItemSchema, DatabaseSpecSchema, DiagramConfigSchema, SURFACE_AUDIENCES, SurfaceAudienceSchema, SystemPublicInterfaceSchema, SystemSpecSchema, PublicInterfaceTypeSchema, PublicInterfaceSchema, TrustedLinkSchema, LintAllowSchema, LintConfigSchema, ExtDataSchema, LifecycleEntrypointSchema, SubsystemSpecSchema, ComponentTypeSchema, PATTERN_TYPES, RETIRED_STEREOTYPES, PortalTypeSchema, DispatchBindingSchema, DurabilitySchema, DependencyClassSchema, PatternRefSchema, EventBindingSchema, ExternalLinkTypeSchema, ExternalLinkSchema, PortalAuthSchemeSchema, PortalAuthSchema, PortalMountSchema, ComponentSpecSchema, BLOCK_NOUNS, HttpMethodSchema, TransportSchema, EndpointSchema, SEMANTIC_GUARANTEES, GuaranteeSchema, MethodParamSchema, FindingDeclarationSchema, MethodSignatureSchema, INTENT_FLOOR_MIN_CHARS, InterfaceSpecSchema, NarrativeStepTypeSchema, LoopKindSchema, SwitchCaseSchema, CatchClauseSchema, ParallelBranchSchema, NarrativeStepSchema, NarrativeDetailSchema, ConformanceTierSchema, MethodImplementationSchema, TechnologySchema, ImplementationSpecSchema, TypeKindSchema, TypeFieldSchema, TypeMethodSchema, InvariantSchema, TypeSpecSchema, SurfaceOriginSchema, SurfaceTypeDefSchema, SurfaceTypeExportSchema, SurfaceContractEntrySchema, SurfaceSnapshotSchema, ExternalLockEntrySchema, ExternalsLockSchema, NamedOpenApiSpecSchema, GroupSpecSchema;
|
|
2894
2911
|
var init_specs = __esm({
|
|
2895
2912
|
"src/models/specs.ts"() {
|
|
2896
2913
|
"use strict";
|
|
@@ -3564,6 +3581,13 @@ var init_specs = __esm({
|
|
|
3564
3581
|
/** Opaque pack/tool extension data (see ExtDataSchema) — preserved verbatim. */
|
|
3565
3582
|
ext: ExtDataSchema.optional()
|
|
3566
3583
|
});
|
|
3584
|
+
TechnologySchema = import_zod8.z.union([
|
|
3585
|
+
import_zod8.z.string().min(1),
|
|
3586
|
+
import_zod8.z.object({
|
|
3587
|
+
name: import_zod8.z.string().min(1),
|
|
3588
|
+
matches: import_zod8.z.array(import_zod8.z.string().min(1)).min(1)
|
|
3589
|
+
}).strict()
|
|
3590
|
+
]);
|
|
3567
3591
|
ImplementationSpecSchema = import_zod8.z.object({
|
|
3568
3592
|
id: SpecIdSchema,
|
|
3569
3593
|
name: import_zod8.z.string(),
|
|
@@ -3588,9 +3612,10 @@ var init_specs = __esm({
|
|
|
3588
3612
|
* component's ownership tree the technology's home: references anywhere
|
|
3589
3613
|
* outside it are flagged (TECH_LEAKAGE), contract identifiers must stay
|
|
3590
3614
|
* intent-language (VENDOR_NAME_IN_CONTRACT), and only data-layer
|
|
3591
|
-
* stereotypes should bind tech directly (TECH_ON_LOGIC_COMPONENT).
|
|
3615
|
+
* stereotypes should bind tech directly (TECH_ON_LOGIC_COMPONENT). An entry
|
|
3616
|
+
* is a bare name or `{ name, matches }` (see TechnologySchema).
|
|
3592
3617
|
*/
|
|
3593
|
-
technologies: import_zod8.z.array(
|
|
3618
|
+
technologies: import_zod8.z.array(TechnologySchema).optional(),
|
|
3594
3619
|
/**
|
|
3595
3620
|
* The parameter names THIS realization takes BEFORE the ones its contract
|
|
3596
3621
|
* declares — a config object, a data root, the transport handles a portal is
|
|
@@ -19143,6 +19168,7 @@ var DATA_LAYER, technologyBindingRule;
|
|
|
19143
19168
|
var init_technology_binding = __esm({
|
|
19144
19169
|
"src/core/rules/heuristic/technology-binding.ts"() {
|
|
19145
19170
|
"use strict";
|
|
19171
|
+
init_models();
|
|
19146
19172
|
DATA_LAYER = /* @__PURE__ */ new Set(["Adapter", "Store", "Registry", "Index"]);
|
|
19147
19173
|
technologyBindingRule = {
|
|
19148
19174
|
name: "technology-binding",
|
|
@@ -19159,7 +19185,7 @@ var init_technology_binding = __esm({
|
|
|
19159
19185
|
ctx.addIssue(
|
|
19160
19186
|
"warning",
|
|
19161
19187
|
"TECH_ON_LOGIC_COMPONENT",
|
|
19162
|
-
`Implementation "${impl.id}" binds technology (${impl.technologies.join(", ")}) on component "${comp.id}" (${comp.componentType}). Technology belongs behind a data-layer seam \u2014 extract an Adapter (or Store/Registry/Index) behind an intent interface and let this component depend on that.`,
|
|
19188
|
+
`Implementation "${impl.id}" binds technology (${impl.technologies.map(technologyName).join(", ")}) on component "${comp.id}" (${comp.componentType}). Technology belongs behind a data-layer seam \u2014 extract an Adapter (or Store/Registry/Index) behind an intent interface and let this component depend on that.`,
|
|
19163
19189
|
impl.id,
|
|
19164
19190
|
ctx.isImplementationDraft(impl)
|
|
19165
19191
|
);
|
|
@@ -19186,6 +19212,16 @@ function makeMatcher(tech) {
|
|
|
19186
19212
|
return false;
|
|
19187
19213
|
};
|
|
19188
19214
|
}
|
|
19215
|
+
function newHome(label7) {
|
|
19216
|
+
const home = {
|
|
19217
|
+
label: label7,
|
|
19218
|
+
matchers: [],
|
|
19219
|
+
match: (text3) => home.matchers.some((m) => m(text3)),
|
|
19220
|
+
ownerComponents: /* @__PURE__ */ new Set(),
|
|
19221
|
+
scope: /* @__PURE__ */ new Set()
|
|
19222
|
+
};
|
|
19223
|
+
return home;
|
|
19224
|
+
}
|
|
19189
19225
|
function contractIdentifiers(m) {
|
|
19190
19226
|
const parts = [m.name, m.signature, m.returns];
|
|
19191
19227
|
for (const p of m.params ?? []) parts.push(p.name, p.type);
|
|
@@ -19212,7 +19248,7 @@ var init_technology_boundaries = __esm({
|
|
|
19212
19248
|
technologyRule = {
|
|
19213
19249
|
name: "technology-boundaries",
|
|
19214
19250
|
judges: "design",
|
|
19215
|
-
description: "Technology stays behind its owning boundary: an L4 that declares `technologies` (e.g. [mysql]) makes its component's ownership tree the technology's home. References outside that tree are leakage, and L3 contract identifiers must stay intent-language \u2014 the contract is the swap seam, so the vendor name is wrong even on the owning component's own interface.",
|
|
19251
|
+
description: "Technology stays behind its owning boundary: an L4 that declares `technologies` (e.g. [mysql]) makes its component's ownership tree the technology's home. References outside that tree are leakage, and L3 contract identifiers must stay intent-language \u2014 the contract is the swap seam, so the vendor name is wrong even on the owning component's own interface. A technology is matched by its name, or \u2014 when it declares `matches` because its name is also an ordinary word of the tree (a package named after the file format it reads) \u2014 by those tokens alone.",
|
|
19216
19252
|
codes: [
|
|
19217
19253
|
{ code: "TECH_LEAKAGE", defaultSeverity: "warning", summary: "Technology referenced outside its owning boundary" },
|
|
19218
19254
|
{ code: "VENDOR_NAME_IN_CONTRACT", defaultSeverity: "warning", summary: "Technology name in L3 contract identifiers" }
|
|
@@ -19249,7 +19285,9 @@ var init_technology_boundaries = __esm({
|
|
|
19249
19285
|
const intf = ctx.interfaceMap.get(impl.contract);
|
|
19250
19286
|
const comp = intf ? ctx.componentMap.get(intf.component) : void 0;
|
|
19251
19287
|
if (!comp) continue;
|
|
19252
|
-
for (const tech of impl.technologies)
|
|
19288
|
+
for (const tech of impl.technologies) {
|
|
19289
|
+
declarations.push({ comp, name: technologyName(tech), tokens: technologyTokens(tech) });
|
|
19290
|
+
}
|
|
19253
19291
|
}
|
|
19254
19292
|
const scopes = /* @__PURE__ */ new Map();
|
|
19255
19293
|
const scopeOf3 = (comp) => {
|
|
@@ -19273,11 +19311,12 @@ var init_technology_boundaries = __esm({
|
|
|
19273
19311
|
return scope;
|
|
19274
19312
|
};
|
|
19275
19313
|
const homes = /* @__PURE__ */ new Map();
|
|
19276
|
-
for (const { comp,
|
|
19277
|
-
const
|
|
19278
|
-
if (!
|
|
19279
|
-
const key =
|
|
19280
|
-
const home = homes.get(key) ??
|
|
19314
|
+
for (const { comp, name, tokens } of declarations) {
|
|
19315
|
+
const matchers = tokens.map(makeMatcher).filter((m) => m !== null);
|
|
19316
|
+
if (!matchers.length) continue;
|
|
19317
|
+
const key = name.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean).join("");
|
|
19318
|
+
const home = homes.get(key) ?? newHome(name);
|
|
19319
|
+
home.matchers.push(...matchers);
|
|
19281
19320
|
home.ownerComponents.add(comp.id);
|
|
19282
19321
|
for (const id of scopeOf3(comp)) home.scope.add(id);
|
|
19283
19322
|
homes.set(key, home);
|
|
@@ -22936,8 +22975,8 @@ function generateSequenceDiagram(componentId, methodName, options) {
|
|
|
22936
22975
|
const steps = [...methodImpl.narrative].sort((a, b) => a.stepNumber - b.stepNumber);
|
|
22937
22976
|
for (const step of steps) {
|
|
22938
22977
|
for (const par of parallelArms) {
|
|
22939
|
-
const
|
|
22940
|
-
if (
|
|
22978
|
+
const sep17 = par.sepByStep.get(step.stepNumber);
|
|
22979
|
+
if (sep17 !== void 0) lines.push(` and ${escapeLabel(sep17)}`);
|
|
22941
22980
|
}
|
|
22942
22981
|
switch (step.type) {
|
|
22943
22982
|
case "local":
|
|
@@ -24046,7 +24085,7 @@ function mapSpecReferences(kind, spec, map) {
|
|
|
24046
24085
|
const s = spec;
|
|
24047
24086
|
return {
|
|
24048
24087
|
...s,
|
|
24049
|
-
publicInterfaces: s.publicInterfaces.map((p) => ({
|
|
24088
|
+
publicInterfaces: (s.publicInterfaces ?? []).map((p) => ({
|
|
24050
24089
|
...p,
|
|
24051
24090
|
...p.component !== void 0 ? { component: map("publicInterfaces", p.component) } : {},
|
|
24052
24091
|
...p.interface !== void 0 ? { interface: map("publicInterfaces", p.interface) } : {},
|
|
@@ -24062,8 +24101,8 @@ function mapSpecReferences(kind, spec, map) {
|
|
|
24062
24101
|
return {
|
|
24063
24102
|
...c,
|
|
24064
24103
|
subsystem: map("subsystem", c.subsystem),
|
|
24065
|
-
owns: c.owns.map((o) => map("owns", o)),
|
|
24066
|
-
dependsOn: c.dependsOn.map((d) => map("dependsOn", d)),
|
|
24104
|
+
owns: (c.owns ?? []).map((o) => map("owns", o)),
|
|
24105
|
+
dependsOn: (c.dependsOn ?? []).map((d) => map("dependsOn", d)),
|
|
24067
24106
|
...c.dispatch ? { dispatch: c.dispatch.map((b) => ({ ...b, component: map("dispatch", b.component) })) } : {},
|
|
24068
24107
|
...c.mounts ? { mounts: c.mounts.map((m) => ({ ...m, portal: map("mounts", m.portal) })) } : {}
|
|
24069
24108
|
};
|
|
@@ -24077,9 +24116,9 @@ function mapSpecReferences(kind, spec, map) {
|
|
|
24077
24116
|
return {
|
|
24078
24117
|
...impl,
|
|
24079
24118
|
contract: map("contract", impl.contract),
|
|
24080
|
-
methods: impl.methods.map((m) => ({
|
|
24119
|
+
methods: (impl.methods ?? []).map((m) => ({
|
|
24081
24120
|
...m,
|
|
24082
|
-
narrative: m.narrative.map((step) => step.targetComponent !== void 0 ? { ...step, targetComponent: map("narrative", step.targetComponent) } : step)
|
|
24121
|
+
narrative: (m.narrative ?? []).map((step) => step.targetComponent !== void 0 ? { ...step, targetComponent: map("narrative", step.targetComponent) } : step)
|
|
24083
24122
|
}))
|
|
24084
24123
|
};
|
|
24085
24124
|
}
|
|
@@ -25459,7 +25498,7 @@ function rewriteReferences(kind, id, edits) {
|
|
|
25459
25498
|
function moveMethods(from, to, methods, hooks, dryRun) {
|
|
25460
25499
|
return current().moveMethods(from, to, methods, hooks, dryRun);
|
|
25461
25500
|
}
|
|
25462
|
-
var fs14, path21, IMPORTABLE_POSITIONS, SIGNATURE_TTL_MS, COMPONENT_AUTH_SOURCE, STEP_JUMP_FIELDS, STEP_JUMP_LISTS, DELTA_VERBS, JUMP_LABEL_TWINS, LABEL_TWIN_CONTAINERS, SpecWorkspace, DEFAULT_DEPENDENCY_LIMIT, PROSE_FIELDS, workspaces, lastServedBinding;
|
|
25501
|
+
var fs14, path21, IMPORTABLE_POSITIONS, SIGNATURE_TTL_MS, COMPONENT_AUTH_SOURCE, STEP_JUMP_FIELDS, STEP_JUMP_LISTS, DELTA_VERBS, JUMP_LABEL_TWINS, LABEL_TWIN_CONTAINERS, SpecWorkspace, DEFAULT_DEPENDENCY_LIMIT, PROSE_FIELDS, DELTA_JUMP_PINS, workspaces, lastServedBinding;
|
|
25463
25502
|
var init_specs2 = __esm({
|
|
25464
25503
|
"src/core/specs.ts"() {
|
|
25465
25504
|
"use strict";
|
|
@@ -26332,10 +26371,10 @@ var init_specs2 = __esm({
|
|
|
26332
26371
|
return this.relativizeSpec("interface", spec);
|
|
26333
26372
|
}
|
|
26334
26373
|
prepareImplementationForWrite(spec, carry = true) {
|
|
26335
|
-
const
|
|
26374
|
+
const relative26 = this.relativizeSpec("implementation", spec, carry);
|
|
26336
26375
|
const member = this.getSubprojectPrefix(spec.id);
|
|
26337
26376
|
const memberRoot = member ? this.resolveSubprojectForNamespace(member) : null;
|
|
26338
|
-
return memberRoot ? childRelativeFilePaths(
|
|
26377
|
+
return memberRoot ? childRelativeFilePaths(relative26, this.rootDir, memberRoot) : relative26;
|
|
26339
26378
|
}
|
|
26340
26379
|
prepareTypeForWrite(spec) {
|
|
26341
26380
|
return this.relativizeSpec("type", spec);
|
|
@@ -27174,7 +27213,10 @@ var init_specs2 = __esm({
|
|
|
27174
27213
|
* second delete written as 7 addresses what used to be step 8. That is why
|
|
27175
27214
|
* a delete may restate the step's `label` or `description` as a guard, and
|
|
27176
27215
|
* why labels are the reliable way to name a jump target. Inserts and
|
|
27177
|
-
* deletes relocate every jump field in the same narrative around
|
|
27216
|
+
* deletes relocate every STORED jump field in the same narrative around
|
|
27217
|
+
* them; a jump the delta itself writes is already in the numbering the
|
|
27218
|
+
* whole delta leaves behind, so none of its later steps moves it. A new
|
|
27219
|
+
* implementation method with no narrative is intent-level ([]); a
|
|
27178
27220
|
* step whose `type` changes is REBUILT for its new type, keeping its
|
|
27179
27221
|
* description and label.
|
|
27180
27222
|
* 5. UNSET the fields the delta names, at the level each `unset` sits in.
|
|
@@ -27232,16 +27274,19 @@ var init_specs2 = __esm({
|
|
|
27232
27274
|
}
|
|
27233
27275
|
const JUMP_FIELDS = ["onTrueStep", "onFalseStep", "defaultStep", "endStep", "finallyStep", "toStep"];
|
|
27234
27276
|
const JUMP_LIST_FIELDS = ["cases", "catches", "branches"];
|
|
27277
|
+
const isPinned = (step, field) => step?.[DELTA_JUMP_PINS] instanceof Set && step[DELTA_JUMP_PINS].has(field);
|
|
27278
|
+
const isPinnedEntry = (entry) => entry?.[DELTA_JUMP_PINS] === true;
|
|
27235
27279
|
const relocateJumps = (step, shiftFrom, deltaN, captureInsertTarget = false) => {
|
|
27236
27280
|
const hit = (v, isRegionTail = false) => deltaN > 0 ? captureInsertTarget && !isRegionTail ? v > shiftFrom : v >= shiftFrom : v > shiftFrom;
|
|
27237
27281
|
const out = { ...step };
|
|
27238
27282
|
for (const f of JUMP_FIELDS) {
|
|
27283
|
+
if (isPinned(step, f)) continue;
|
|
27239
27284
|
if (typeof out[f] === "number" && hit(out[f], f === "endStep")) out[f] = out[f] + deltaN;
|
|
27240
27285
|
}
|
|
27241
27286
|
for (const lf of JUMP_LIST_FIELDS) {
|
|
27242
27287
|
if (Array.isArray(out[lf])) {
|
|
27243
27288
|
out[lf] = out[lf].map(
|
|
27244
|
-
(c) => typeof c?.step === "number" && hit(c.step) ? { ...c, step: c.step + deltaN } : c
|
|
27289
|
+
(c) => !isPinnedEntry(c) && typeof c?.step === "number" && hit(c.step) ? { ...c, step: c.step + deltaN } : c
|
|
27245
27290
|
);
|
|
27246
27291
|
}
|
|
27247
27292
|
}
|
|
@@ -27249,14 +27294,39 @@ var init_specs2 = __esm({
|
|
|
27249
27294
|
};
|
|
27250
27295
|
const jumpRefsTo = (step, target) => {
|
|
27251
27296
|
const refs = [];
|
|
27252
|
-
for (const f of JUMP_FIELDS) if (step[f] === target) refs.push(f);
|
|
27297
|
+
for (const f of JUMP_FIELDS) if (step[f] === target && !isPinned(step, f)) refs.push(f);
|
|
27253
27298
|
for (const lf of JUMP_LIST_FIELDS) {
|
|
27254
27299
|
(Array.isArray(step[lf]) ? step[lf] : []).forEach((c, i) => {
|
|
27255
|
-
if (c?.step === target) refs.push(`${lf}[${i}].step`);
|
|
27300
|
+
if (c?.step === target && !isPinnedEntry(c)) refs.push(`${lf}[${i}].step`);
|
|
27256
27301
|
});
|
|
27257
27302
|
}
|
|
27258
27303
|
return refs;
|
|
27259
27304
|
};
|
|
27305
|
+
const pinDeltaJumps = (deltaStep, carried) => {
|
|
27306
|
+
const pins = new Set(carried ?? []);
|
|
27307
|
+
for (const f of JUMP_FIELDS) if (typeof deltaStep[f] === "number") pins.add(f);
|
|
27308
|
+
const out = { ...deltaStep };
|
|
27309
|
+
for (const lf of JUMP_LIST_FIELDS) {
|
|
27310
|
+
if (!Array.isArray(out[lf])) continue;
|
|
27311
|
+
out[lf] = out[lf].map((c) => c && typeof c === "object" && typeof c.step === "number" ? { ...c, [DELTA_JUMP_PINS]: true } : c);
|
|
27312
|
+
}
|
|
27313
|
+
out[DELTA_JUMP_PINS] = pins;
|
|
27314
|
+
return out;
|
|
27315
|
+
};
|
|
27316
|
+
const unpinned = (step) => {
|
|
27317
|
+
const out = { ...step };
|
|
27318
|
+
delete out[DELTA_JUMP_PINS];
|
|
27319
|
+
for (const lf of JUMP_LIST_FIELDS) {
|
|
27320
|
+
if (!Array.isArray(out[lf])) continue;
|
|
27321
|
+
out[lf] = out[lf].map((c) => {
|
|
27322
|
+
if (!c || typeof c !== "object" || !(DELTA_JUMP_PINS in c)) return c;
|
|
27323
|
+
const entry = { ...c };
|
|
27324
|
+
delete entry[DELTA_JUMP_PINS];
|
|
27325
|
+
return entry;
|
|
27326
|
+
});
|
|
27327
|
+
}
|
|
27328
|
+
return out;
|
|
27329
|
+
};
|
|
27260
27330
|
const normalizeIdentity = (k) => String(k ?? "").toLowerCase().replace(/[_\-\s]/g, "");
|
|
27261
27331
|
const unsetFieldsOf = (item) => Array.isArray(item?.unset) ? item.unset.filter((f) => typeof f === "string") : [];
|
|
27262
27332
|
const withUnset = (merged, deltaItem) => {
|
|
@@ -27436,21 +27506,24 @@ var init_specs2 = __esm({
|
|
|
27436
27506
|
capture
|
|
27437
27507
|
));
|
|
27438
27508
|
const { action, remove: remove3, captureJumps, ...cleanStep } = deltaStep;
|
|
27439
|
-
steps.push(withUnset(cleanStep, deltaStep));
|
|
27509
|
+
steps.push(withUnset(pinDeltaJumps(cleanStep), deltaStep));
|
|
27440
27510
|
} else {
|
|
27441
27511
|
const idx = steps.findIndex((s) => s.stepNumber === stepNum);
|
|
27442
27512
|
if (idx !== -1) {
|
|
27443
|
-
const { action, remove: remove3, ...
|
|
27513
|
+
const { action, remove: remove3, ...rawStep } = deltaStep;
|
|
27444
27514
|
const stored = steps[idx];
|
|
27515
|
+
const cleanStep = pinDeltaJumps(rawStep, stored[DELTA_JUMP_PINS]);
|
|
27445
27516
|
const merged = withUnset(retargetByLabel(mergeElement(stored, cleanStep), cleanStep), deltaStep);
|
|
27446
|
-
|
|
27517
|
+
const rebuilt = rebuildOnRetype(stored, rawStep, merged, methodName);
|
|
27518
|
+
rebuilt[DELTA_JUMP_PINS] = cleanStep[DELTA_JUMP_PINS];
|
|
27519
|
+
steps[idx] = rebuilt;
|
|
27447
27520
|
} else {
|
|
27448
27521
|
const { action, remove: remove3, ...cleanStep } = deltaStep;
|
|
27449
|
-
steps.push(withUnset(cleanStep, deltaStep));
|
|
27522
|
+
steps.push(withUnset(pinDeltaJumps(cleanStep), deltaStep));
|
|
27450
27523
|
}
|
|
27451
27524
|
}
|
|
27452
27525
|
}
|
|
27453
|
-
return steps.sort((a, b) => a.stepNumber - b.stepNumber);
|
|
27526
|
+
return steps.map(unpinned).sort((a, b) => a.stepNumber - b.stepNumber);
|
|
27454
27527
|
};
|
|
27455
27528
|
const isPlainObject2 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
27456
27529
|
const mergeExt = (existing, delta2) => {
|
|
@@ -27473,6 +27546,11 @@ var init_specs2 = __esm({
|
|
|
27473
27546
|
} else {
|
|
27474
27547
|
const existingMethod = merged[idx];
|
|
27475
27548
|
let narrative = existingMethod.narrative ? [...existingMethod.narrative] : [];
|
|
27549
|
+
if (deltaMethod.narrative !== void 0 && deltaMethod.narrative !== null && !Array.isArray(deltaMethod.narrative)) {
|
|
27550
|
+
throw new Error(
|
|
27551
|
+
`Refusing to update implementation method "${String(deltaMethod.name)}": its "narrative" must be a list of step deltas, got ${JSON.stringify(deltaMethod.narrative)}.`
|
|
27552
|
+
);
|
|
27553
|
+
}
|
|
27476
27554
|
if (Array.isArray(deltaMethod.narrative)) {
|
|
27477
27555
|
narrative = deltaMethod.narrative.length === 0 ? [] : mergeNarrative(String(deltaMethod.name), narrative, deltaMethod.narrative);
|
|
27478
27556
|
}
|
|
@@ -27485,11 +27563,21 @@ var init_specs2 = __esm({
|
|
|
27485
27563
|
}
|
|
27486
27564
|
} else {
|
|
27487
27565
|
assertUnmatchedIsAnAddition(deltaMethod, deltaMethod.name, merged.map((m) => m.name), "method");
|
|
27488
|
-
merged.push(withUnset(deltaMethod, deltaMethod));
|
|
27566
|
+
merged.push(withUnset(newMethodOf(deltaMethod), deltaMethod));
|
|
27489
27567
|
}
|
|
27490
27568
|
}
|
|
27491
27569
|
return merged;
|
|
27492
27570
|
};
|
|
27571
|
+
const newMethodOf = (deltaMethod) => {
|
|
27572
|
+
if (kind !== "implementation") return deltaMethod;
|
|
27573
|
+
if (deltaMethod.narrative === void 0 || deltaMethod.narrative === null) return { ...deltaMethod, narrative: [] };
|
|
27574
|
+
if (!Array.isArray(deltaMethod.narrative)) {
|
|
27575
|
+
throw new Error(
|
|
27576
|
+
`Refusing to add implementation method "${String(deltaMethod.name)}": its "narrative" must be a list of steps, got ${JSON.stringify(deltaMethod.narrative)}. Leave it out (or pass []) for an intent-level method.`
|
|
27577
|
+
);
|
|
27578
|
+
}
|
|
27579
|
+
return deltaMethod;
|
|
27580
|
+
};
|
|
27493
27581
|
const mergeNamedArray = (existing, delta2) => {
|
|
27494
27582
|
const merged = [...existing];
|
|
27495
27583
|
for (const deltaItem of delta2) {
|
|
@@ -27667,6 +27755,12 @@ var init_specs2 = __esm({
|
|
|
27667
27755
|
const qualifiedDelta = qualifyDeltaRefs(mergeableDelta);
|
|
27668
27756
|
const mergedResult = mergeDelta(result, qualifiedDelta);
|
|
27669
27757
|
for (const field of unsetFields) delete mergedResult[field];
|
|
27758
|
+
const withDefaults = deltaSchema.safeParse(mergedResult);
|
|
27759
|
+
if (withDefaults.success) {
|
|
27760
|
+
for (const [key, value] of Object.entries(withDefaults.data)) {
|
|
27761
|
+
if (!(key in mergedResult)) mergedResult[key] = value;
|
|
27762
|
+
}
|
|
27763
|
+
}
|
|
27670
27764
|
if (kind === "implementation" && Array.isArray(mergedResult.methods)) {
|
|
27671
27765
|
const labelErrors = [];
|
|
27672
27766
|
for (const m of mergedResult.methods) {
|
|
@@ -28187,6 +28281,7 @@ var init_specs2 = __esm({
|
|
|
28187
28281
|
"note",
|
|
28188
28282
|
"caller"
|
|
28189
28283
|
]);
|
|
28284
|
+
DELTA_JUMP_PINS = /* @__PURE__ */ Symbol("jumps written by this delta");
|
|
28190
28285
|
workspaces = /* @__PURE__ */ new Map();
|
|
28191
28286
|
lastServedBinding = null;
|
|
28192
28287
|
}
|
|
@@ -30443,17 +30538,17 @@ function memberHop(agentId) {
|
|
|
30443
30538
|
const alias = agentId.slice(0, at);
|
|
30444
30539
|
const member = graph().nodes.find((n) => n.parent === "" && n.mountAlias === alias);
|
|
30445
30540
|
if (!member) return null;
|
|
30446
|
-
const
|
|
30447
|
-
return { alias, rest: agentId.slice(at + 2), directory: member.directory, relative:
|
|
30541
|
+
const relative26 = path28.relative(getProjectRoot(), member.directory).split(path28.sep).join("/");
|
|
30542
|
+
return { alias, rest: agentId.slice(at + 2), directory: member.directory, relative: relative26 };
|
|
30448
30543
|
}
|
|
30449
|
-
function throughMount(brief, alias,
|
|
30450
|
-
const under = (p) => path28.posix.join(
|
|
30544
|
+
function throughMount(brief, alias, relative26) {
|
|
30545
|
+
const under = (p) => path28.posix.join(relative26, p);
|
|
30451
30546
|
return {
|
|
30452
30547
|
...brief,
|
|
30453
30548
|
agentId: `${alias}::${brief.agentId}`,
|
|
30454
30549
|
ownedPaths: brief.ownedPaths.map(under),
|
|
30455
30550
|
...brief.readPaths ? { readPaths: brief.readPaths.map(under) } : {},
|
|
30456
|
-
root: brief.root ? under(brief.root) :
|
|
30551
|
+
root: brief.root ? under(brief.root) : relative26
|
|
30457
30552
|
};
|
|
30458
30553
|
}
|
|
30459
30554
|
function loadRegistry() {
|
|
@@ -33843,8 +33938,8 @@ var require_dist = __commonJS({
|
|
|
33843
33938
|
walkPackDir(root, path90.join(current2, entry.name), out);
|
|
33844
33939
|
} else if (entry.isFile()) {
|
|
33845
33940
|
const absolute = path90.join(current2, entry.name);
|
|
33846
|
-
const
|
|
33847
|
-
out.push({ path:
|
|
33941
|
+
const relative26 = path90.relative(root, absolute).split(path90.sep).join("/");
|
|
33942
|
+
out.push({ path: relative26, contents: fs66.readFileSync(absolute) });
|
|
33848
33943
|
}
|
|
33849
33944
|
}
|
|
33850
33945
|
}
|
|
@@ -34151,19 +34246,19 @@ function commitStagedTree(stagingDir, destDir, roots, backupExisting) {
|
|
|
34151
34246
|
const backupDir = path33.join(root, BACKUP_DIR, timestampSlug());
|
|
34152
34247
|
let backedUp = false;
|
|
34153
34248
|
for (const rootRel of roots) {
|
|
34154
|
-
const
|
|
34155
|
-
const stagedSpecDir = path33.join(stagingDir,
|
|
34249
|
+
const relative26 = rootRel === "." ? "" : rootRel;
|
|
34250
|
+
const stagedSpecDir = path33.join(stagingDir, relative26, ".wai");
|
|
34156
34251
|
if (!fs19.existsSync(stagedSpecDir)) continue;
|
|
34157
|
-
const liveSpecDir = liveSpecDirFor(path33.join(root,
|
|
34252
|
+
const liveSpecDir = liveSpecDirFor(path33.join(root, relative26));
|
|
34158
34253
|
if (backupExisting && liveSpecDir && fs19.existsSync(liveSpecDir)) {
|
|
34159
|
-
const backupTarget = path33.join(backupDir,
|
|
34254
|
+
const backupTarget = path33.join(backupDir, relative26, path33.basename(liveSpecDir));
|
|
34160
34255
|
fs19.mkdirSync(path33.dirname(backupTarget), { recursive: true });
|
|
34161
34256
|
fs19.renameSync(liveSpecDir, backupTarget);
|
|
34162
34257
|
backedUp = true;
|
|
34163
34258
|
} else if (liveSpecDir && fs19.existsSync(liveSpecDir)) {
|
|
34164
34259
|
fs19.rmSync(liveSpecDir, { recursive: true, force: true });
|
|
34165
34260
|
}
|
|
34166
|
-
const target = path33.join(root,
|
|
34261
|
+
const target = path33.join(root, relative26, ".wai");
|
|
34167
34262
|
fs19.mkdirSync(path33.dirname(target), { recursive: true });
|
|
34168
34263
|
if (fs19.existsSync(target)) fs19.rmSync(target, { recursive: true, force: true });
|
|
34169
34264
|
fs19.renameSync(stagedSpecDir, target);
|
|
@@ -34806,7 +34901,7 @@ function contextDir(...segments) {
|
|
|
34806
34901
|
return aiDir("context", ...segments);
|
|
34807
34902
|
}
|
|
34808
34903
|
function hasContext() {
|
|
34809
|
-
return pathExists(CONTEXT_PATHS.
|
|
34904
|
+
return pathExists(CONTEXT_PATHS.dir());
|
|
34810
34905
|
}
|
|
34811
34906
|
function readProjectContext() {
|
|
34812
34907
|
return readFileOrNull(CONTEXT_PATHS.projectMd());
|
|
@@ -40373,7 +40468,7 @@ ${renderChangeReport(report3)}`,
|
|
|
40373
40468
|
contract: import_zod11.z.string().describe("The L3 Interface contract ID this implements"),
|
|
40374
40469
|
sourcePath: import_zod11.z.string().optional().describe("Optional: target source code file path relative to project root \u2014 for a chained subproject's implementation (qualified id), relative to that subproject's root; a path given relative to this root that lands inside the subproject is re-expressed for you"),
|
|
40375
40470
|
simPath: import_zod11.z.string().optional().describe("Optional: the committed integration-sim harness file (project-relative; N:1 sharing allowed). The validator proves it exists and its import graph wires the REAL modules (this component + each direct dependency; technology adapters may stay faked) \u2014 running it is CI's job. Declaring the first simPath in a subsystem activates MISSING_INTEGRATION_SIM for its other complete non-leaf implementations"),
|
|
40376
|
-
technologies: import_zod11.z.array(import_zod11.z.string()).optional().describe(`External technologies this implementation binds to (e.g. ["mysql"]) \u2014 declares this component's ownership tree as the technology's home; references outside it are flagged (TECH_LEAKAGE) and contract identifiers must stay intent-language. Only for Adapter/Store/Registry/Index components.`),
|
|
40471
|
+
technologies: import_zod11.z.array(import_zod11.z.union([import_zod11.z.string(), import_zod11.z.object({ name: import_zod11.z.string(), matches: import_zod11.z.array(import_zod11.z.string()).min(1).describe("The tokens the leakage and contract checks match INSTEAD of the name \u2014 for a technology whose name is also an ordinary word of the tree (the yaml package, the YAML format)") }).strict()])).optional().describe(`External technologies this implementation binds to (e.g. ["mysql"], or [{ "name": "yaml", "matches": ["yaml package", "parseDocument"] }] when the bare name would match a common word) \u2014 declares this component's ownership tree as the technology's home; references outside it are flagged (TECH_LEAKAGE) and contract identifiers must stay intent-language. Only for Adapter/Store/Registry/Index components.`),
|
|
40377
40472
|
injectedParams: import_zod11.z.array(import_zod11.z.string()).optional().describe("The parameter names this realization takes BEFORE the ones its contract declares \u2014 a config object, a data root, the transport handles a portal is handed. Supplied by whatever wires the component up, never by the caller the contract describes, which is why they belong here and not to the contract: another realization may hold them as fields instead. Declared rather than guessed, because a leading parameter the contract does not name cannot be told from one it named under a different name (`seed(config)` realized as `bootstrapInstance(cfg)`); a leading parameter this list does not name is reported (UNDECLARED_PARAM). Only a LEADING run is dropped \u2014 one of these names appearing after the contract's own parameters is an argument in the middle of the caller's list, not wiring"),
|
|
40378
40473
|
detail: detailEnum.optional().describe("Spec-level narrative detail default for all methods"),
|
|
40379
40474
|
conformance: conformanceEnum.optional().describe("Spec-level conformance tier default: declared | anchored | off (omitted = stereotype default: Portal \u2192 anchored, else declared)"),
|
|
@@ -40652,7 +40747,7 @@ ${testsBlock}` : ""}`, deletion);
|
|
|
40652
40747
|
inputSchema: {
|
|
40653
40748
|
kind: import_zod11.z.enum(["system", "subsystem", "component", "interface", "implementation", "type"]).describe("The spec kind to update (system = the singleton L0 \u2014 vision, boundaries, globalRequirements, databases, and publicInterfaces: the project export table, each entry a re-export {from, component, interface, typeDef, as, name, type, details, audience: project|department|instance|partner|external} \u2014 from alone re-exports everything a subsystem exports, and a legacy {id, subsystem} reads as {as, from})"),
|
|
40654
40749
|
id: import_zod11.z.string().describe("The ID of the spec to update (namespaced if needed)"),
|
|
40655
|
-
delta: import_zod11.z.record(import_zod11.z.any()).describe(`The partial fields to merge into the spec. ARRAYS UPSERT, they do not replace: an array whose elements carry an identity is merged element-by-element, so a delta naming ONE element leaves the others intact. Identity is "name" or "id" by default, and per field: dispatch by "capability", a listener's mounts by "portal", lifecycle by phase+component+method, emits/subscribesTo by topic+event, trustedLinks by "subsystem", a subsystem's publicInterfaces by component+interface (or type+details for an entry not yet bound to a component, and by from+item+interface+as for a re-export or a type export), invariants and patterns by "id", lint.allow by "code" AND "at" (an allow covers one occurrence, so several may share a code on one spec), an interface method's findings by "code", boundaries by "name", globalRequirements by "description", switch cases by "value", try catches by "error". Identity merging applies at EVERY depth, including an array INSIDE an element (a method's params, a step's catches). Add "action: 'delete'" (or "remove: true") alongside that identity to REMOVE an element \u2014 including a stale lint allow. Arrays of plain STRINGS (owns, dependsOn, guarantees) carry no per-element identity and are replaced wholesale; pass [] to clear any array outright. To REMOVE an optional field entirely, list it in "unset": e.g. {"unset": ["basePath", "variant"]} \u2014 passing null/undefined means "no change" (they are skipped), and writing "" would leave the field present but empty, which is a different and usually wrong spec. Unsetting a required field is refused by schema validation, which names it. For narrative steps, match by "stepNumber" and use "action: 'insert'" (shifts subsequent steps up) or "action: 'delete'" (shifts subsequent steps down and removes it). Step entries apply in ASCENDING stepNumber order, each against the numbering the earlier entries of the SAME delta left behind \u2014 delete step 3 and step 7 becomes step 6 \u2014 so prefer labels, and restate the step's "label" or "description" on a delete to have it checked against the step actually addressed. Renumbering RELOCATES every flow jump field (onTrueStep/onFalseStep/cases.step/defaultStep/endStep/catches.step/finallyStep/toStep) in the same narrative. A delete is REJECTED when the narrative has no such step, when a jump still targets it (retarget the referrers first), when a restated label/description does not match, or when it is a loop/try/parallel header whose body would be left standing (retype the header first to dissolve the region, then delete it). Changing a step's "type" REBUILDS it for the new type: its description and label are kept and every field the new type cannot carry is dropped (returned as a NOTICE); a delta that retypes AND sets such a field is refused. A step delta is also refused when it carries a marker the merge does not recognise: a non-boolean "remove", an "action" that is neither "insert" nor "delete", a "captureJumps" outside an insert, or no "stepNumber" to address. Inserting AT a jump target relocates those jumps past the inserted step by default (a NOTICE is returned) \u2014 add "captureJumps": true on the inserted step to retarget entry jumps onto it (loop/try endStep region tails always relocate with the body and are never captured). Every jump field has a "*Label" twin (toLabel, onFalseLabel, endLabel, \u2026, and "label" on a cases/catches entry) resolved against step labels AFTER the merge, so a delta may anchor on a label only pre-existing steps carry; a label the delta supplies REPLACES the stored number it twins, while setting the number and its label together in one delta is refused as a contradiction. Reference ids in deltas may use LOCAL names \u2014 they are qualified against the spec's namespace exactly as the loader would. Per-spec lint suppression: set "lint: { allow: [{ code, at, covers, reason }] }" to silence a WARNING or NOTICE code on this spec only (errors always surface; stale allows are flagged). An allow covers EXACTLY the occurrence it names: "at" is the site the finding names (a contract method, an import edge "from -> to", a declared edge "component -> target") and is REQUIRED for a code whose findings report one, while a finding that reports no site is covered only by an allow that names none; "covers" lists the units an aggregating finding reports, and the allow silences it only when every one is listed. This delta is deliberately OPEN below its top level \u2014 the shapes nest further than a schema here should restate \u2014 so a key that is not a field at its depth is not refused, it is NAMED BACK under NO EFFECT in the answer, together with any value the spec already held and any "unset" that removed nothing. Read that list: it is where a nested typo shows up.`),
|
|
40750
|
+
delta: import_zod11.z.record(import_zod11.z.any()).describe(`The partial fields to merge into the spec. ARRAYS UPSERT, they do not replace: an array whose elements carry an identity is merged element-by-element, so a delta naming ONE element leaves the others intact. Identity is "name" or "id" by default, and per field: dispatch by "capability", a listener's mounts by "portal", lifecycle by phase+component+method, emits/subscribesTo by topic+event, trustedLinks by "subsystem", a subsystem's publicInterfaces by component+interface (or type+details for an entry not yet bound to a component, and by from+item+interface+as for a re-export or a type export), invariants and patterns by "id", lint.allow by "code" AND "at" (an allow covers one occurrence, so several may share a code on one spec), an interface method's findings by "code", boundaries by "name", globalRequirements by "description", switch cases by "value", try catches by "error". Identity merging applies at EVERY depth, including an array INSIDE an element (a method's params, a step's catches). Add "action: 'delete'" (or "remove: true") alongside that identity to REMOVE an element \u2014 including a stale lint allow. Arrays of plain STRINGS (owns, dependsOn, guarantees) carry no per-element identity and are replaced wholesale; pass [] to clear any array outright. To REMOVE an optional field entirely, list it in "unset": e.g. {"unset": ["basePath", "variant"]} \u2014 passing null/undefined means "no change" (they are skipped), and writing "" would leave the field present but empty, which is a different and usually wrong spec. Unsetting a required field is refused by schema validation, which names it. For narrative steps, match by "stepNumber" and use "action: 'insert'" (shifts subsequent steps up) or "action: 'delete'" (shifts subsequent steps down and removes it). Step entries apply in ASCENDING stepNumber order, each against the numbering the earlier entries of the SAME delta left behind \u2014 delete step 3 and step 7 becomes step 6 \u2014 so prefer labels, and restate the step's "label" or "description" on a delete to have it checked against the step actually addressed. Renumbering RELOCATES every flow jump field (onTrueStep/onFalseStep/cases.step/defaultStep/endStep/catches.step/finallyStep/toStep) in the same narrative. Only STORED jumps relocate: a jump the delta itself writes (on an inserted, appended or edited step) names its target in the numbering the WHOLE delta leaves behind, after all of its inserts and deletes, and no later step of the same delta moves it. A method the delta ADDS without a narrative is an intent-level method with none ([]). A delete is REJECTED when the narrative has no such step, when a jump still targets it (retarget the referrers first), when a restated label/description does not match, or when it is a loop/try/parallel header whose body would be left standing (retype the header first to dissolve the region, then delete it). Changing a step's "type" REBUILDS it for the new type: its description and label are kept and every field the new type cannot carry is dropped (returned as a NOTICE); a delta that retypes AND sets such a field is refused. A step delta is also refused when it carries a marker the merge does not recognise: a non-boolean "remove", an "action" that is neither "insert" nor "delete", a "captureJumps" outside an insert, or no "stepNumber" to address. Inserting AT a jump target relocates those jumps past the inserted step by default (a NOTICE is returned) \u2014 add "captureJumps": true on the inserted step to retarget entry jumps onto it (loop/try endStep region tails always relocate with the body and are never captured). Every jump field has a "*Label" twin (toLabel, onFalseLabel, endLabel, \u2026, and "label" on a cases/catches entry) resolved against step labels AFTER the merge, so a delta may anchor on a label only pre-existing steps carry; a label the delta supplies REPLACES the stored number it twins, while setting the number and its label together in one delta is refused as a contradiction. Reference ids in deltas may use LOCAL names \u2014 they are qualified against the spec's namespace exactly as the loader would. Per-spec lint suppression: set "lint: { allow: [{ code, at, covers, reason }] }" to silence a WARNING or NOTICE code on this spec only (errors always surface; stale allows are flagged). An allow covers EXACTLY the occurrence it names: "at" is the site the finding names (a contract method, an import edge "from -> to", a declared edge "component -> target") and is REQUIRED for a code whose findings report one, while a finding that reports no site is covered only by an allow that names none; "covers" lists the units an aggregating finding reports, and the allow silences it only when every one is listed. This delta is deliberately OPEN below its top level \u2014 the shapes nest further than a schema here should restate \u2014 so a key that is not a field at its depth is not refused, it is NAMED BACK under NO EFFECT in the answer, together with any value the spec already held and any "unset" that removed nothing. Read that list: it is where a nested typo shows up.`),
|
|
40656
40751
|
dryRun: import_zod11.z.boolean().optional().describe("Ask what this delta WOULD do instead of doing it. The whole write runs, the candidate gate included, and the answer is the change report it would have produced \u2014 marked DRY RUN, with nothing stamped and not one byte of the stored file moved. Use it before a delta that renumbers a long narrative.")
|
|
40657
40752
|
},
|
|
40658
40753
|
outputSchema: specChangeReportOutput
|
|
@@ -41202,8 +41297,10 @@ var mcp_exports = {};
|
|
|
41202
41297
|
__export(mcp_exports, {
|
|
41203
41298
|
claudeMcpConfigPath: () => claudeMcpConfigPath,
|
|
41204
41299
|
detectHostedMcpSource: () => detectHostedMcpSource,
|
|
41300
|
+
findLegacyPlugin: () => findLegacyPlugin,
|
|
41205
41301
|
normalizeBackend: () => normalizeBackend,
|
|
41206
|
-
|
|
41302
|
+
planMcpInstall: () => planMcpInstall,
|
|
41303
|
+
retireLegacyPlugin: () => retireLegacyPlugin,
|
|
41207
41304
|
runMcpInstall: () => runMcpInstall,
|
|
41208
41305
|
runMcpServe: () => runMcpServe,
|
|
41209
41306
|
runMcpStatus: () => runMcpStatus,
|
|
@@ -41335,33 +41432,27 @@ function normalizeBackend(input) {
|
|
|
41335
41432
|
`Unknown --backend "${input}". Supported: claude (Claude Code) or gemini (a.k.a. agy / antigravity \u2014 both are Gemini-based). MCP auto-registration for codex / cursor / copilot is not yet supported; configure those manually.`
|
|
41336
41433
|
);
|
|
41337
41434
|
}
|
|
41338
|
-
|
|
41435
|
+
function resolveBackends(options) {
|
|
41436
|
+
if (options.backend) return [normalizeBackend(options.backend)];
|
|
41437
|
+
const backends = [];
|
|
41438
|
+
try {
|
|
41439
|
+
const config = loadProjectConfig();
|
|
41440
|
+
const enabledTypes = (config?.targets ?? []).filter((t) => !("enabled" in t) || t.enabled).map((t) => t.type);
|
|
41441
|
+
if (enabledTypes.includes("claude")) backends.push("claude");
|
|
41442
|
+
if (enabledTypes.includes("gemini") || enabledTypes.includes("agy")) backends.push("gemini");
|
|
41443
|
+
} catch {
|
|
41444
|
+
}
|
|
41445
|
+
return backends.length ? backends : ["claude"];
|
|
41446
|
+
}
|
|
41447
|
+
function resolveInstallTargets(options) {
|
|
41339
41448
|
assertProjectInitialized();
|
|
41340
41449
|
if (options.configDir && !options.backend) {
|
|
41341
41450
|
throw new WaironError("--config-dir requires --backend (claude or gemini), since a config dir is agent-specific.");
|
|
41342
41451
|
}
|
|
41343
|
-
|
|
41344
|
-
|
|
41345
|
-
backends = [normalizeBackend(options.backend)];
|
|
41346
|
-
} else {
|
|
41347
|
-
try {
|
|
41348
|
-
const config = loadProjectConfig();
|
|
41349
|
-
const enabledTypes = (config?.targets ?? []).filter((t) => !("enabled" in t) || t.enabled).map((t) => t.type);
|
|
41350
|
-
if (enabledTypes.includes("claude")) {
|
|
41351
|
-
backends.push("claude");
|
|
41352
|
-
}
|
|
41353
|
-
if (enabledTypes.includes("gemini") || enabledTypes.includes("agy")) {
|
|
41354
|
-
backends.push("gemini");
|
|
41355
|
-
}
|
|
41356
|
-
} catch {
|
|
41357
|
-
}
|
|
41358
|
-
if (backends.length === 0) {
|
|
41359
|
-
backends = ["claude"];
|
|
41360
|
-
}
|
|
41361
|
-
}
|
|
41362
|
-
for (const backend of backends) {
|
|
41452
|
+
const root = getProjectRoot();
|
|
41453
|
+
return resolveBackends(options).map((backend) => {
|
|
41363
41454
|
if (options.configDir) validateConfigDir(options.configDir, backend);
|
|
41364
|
-
const useGlobal = options.global ||
|
|
41455
|
+
const useGlobal = !!(options.global || options.configDir);
|
|
41365
41456
|
let configBase;
|
|
41366
41457
|
let settingsPath;
|
|
41367
41458
|
if (backend === "gemini") {
|
|
@@ -41385,40 +41476,75 @@ async function runMcpInstall(options = {}) {
|
|
|
41385
41476
|
}
|
|
41386
41477
|
}
|
|
41387
41478
|
const mcpServers = settings["mcpServers"] ?? {};
|
|
41388
|
-
const agentLabel = backend === "gemini" ? "Antigravity" : "Claude";
|
|
41389
41479
|
const isPackaged = typeof process.pkg !== "undefined";
|
|
41390
41480
|
const scriptPath = process.argv[1] ? path48.resolve(process.argv[1]).replace(/\\/g, "/") : null;
|
|
41391
41481
|
const useDirectNode = !isPackaged && scriptPath && (scriptPath.endsWith(".js") || scriptPath.endsWith(".ts"));
|
|
41392
41482
|
const env = useGlobal ? {} : { WAIRON_PROJECT_DIR: process.cwd().replace(/\\/g, "/") };
|
|
41393
41483
|
const desiredEntry = options.hostedUrl ? hostedMcpEntry(options) : useDirectNode ? { command: "node", args: [scriptPath, "mcp", "serve"], env } : { command: "wairon", args: ["mcp", "serve"], env };
|
|
41394
41484
|
const existingEntry = mcpServers["wairon"];
|
|
41395
|
-
|
|
41396
|
-
|
|
41485
|
+
return {
|
|
41486
|
+
backend,
|
|
41487
|
+
path: settingsPath,
|
|
41488
|
+
outsideProject: isOutsideRoot(root, settingsPath),
|
|
41489
|
+
...existingEntry !== void 0 ? { before: existingEntry } : {},
|
|
41490
|
+
after: desiredEntry,
|
|
41491
|
+
upToDate: !!existingEntry && JSON.stringify(existingEntry) === JSON.stringify(desiredEntry),
|
|
41492
|
+
configBase,
|
|
41493
|
+
settings,
|
|
41494
|
+
useGlobal
|
|
41495
|
+
};
|
|
41496
|
+
});
|
|
41497
|
+
}
|
|
41498
|
+
function planMcpInstall(options) {
|
|
41499
|
+
return resolveInstallTargets(options).map(({ backend, path: file, outsideProject, before, after, upToDate }) => ({
|
|
41500
|
+
backend,
|
|
41501
|
+
path: file,
|
|
41502
|
+
outsideProject,
|
|
41503
|
+
...before !== void 0 ? { before } : {},
|
|
41504
|
+
after,
|
|
41505
|
+
upToDate
|
|
41506
|
+
}));
|
|
41507
|
+
}
|
|
41508
|
+
async function runMcpInstall(options = {}) {
|
|
41509
|
+
for (const target of resolveInstallTargets(options)) {
|
|
41510
|
+
const agentLabel = target.backend === "gemini" ? "Antigravity" : "Claude";
|
|
41511
|
+
if (target.upToDate) {
|
|
41512
|
+
logger.info(`wairon MCP server already registered (up to date) for ${agentLabel} in ${import_chalk3.default.gray(target.path)}.`);
|
|
41397
41513
|
continue;
|
|
41398
41514
|
}
|
|
41399
|
-
|
|
41400
|
-
|
|
41401
|
-
|
|
41402
|
-
|
|
41403
|
-
|
|
41404
|
-
|
|
41405
|
-
|
|
41406
|
-
|
|
41407
|
-
|
|
41408
|
-
|
|
41409
|
-
logger.info(
|
|
41410
|
-
logger.blank();
|
|
41411
|
-
const restartApp = backend === "gemini" ? "Antigravity CLI (agy)" : "claude";
|
|
41412
|
-
logger.info(`Restart ${import_chalk3.default.bold(restartApp)} (or reload MCP servers) to activate.`);
|
|
41413
|
-
if (backend === "claude" && !useGlobal) {
|
|
41414
|
-
logger.warn('Claude shows project .mcp.json servers as "\u23F8 Pending approval" \u2014 approve wairon once on next launch (or run `claude` and accept the trust prompt).');
|
|
41415
|
-
}
|
|
41416
|
-
if (backend === "gemini" && !useGlobal) {
|
|
41417
|
-
logger.warn("Note: Antigravity (agy) reads MCP servers from its global ~/.gemini/antigravity-cli/mcp_config.json, not this project file.");
|
|
41418
|
-
logger.warn("If the agent cannot see the sdd_* tools, run: " + import_chalk3.default.bold("wairon mcp install --backend gemini --global"));
|
|
41419
|
-
}
|
|
41420
|
-
logger.blank();
|
|
41515
|
+
writeInstallTarget(target);
|
|
41516
|
+
reportInstalled(target, agentLabel);
|
|
41517
|
+
}
|
|
41518
|
+
}
|
|
41519
|
+
function writeInstallTarget(target) {
|
|
41520
|
+
const mcpServers = target.settings["mcpServers"] ?? {};
|
|
41521
|
+
mcpServers["wairon"] = target.after;
|
|
41522
|
+
target.settings["mcpServers"] = mcpServers;
|
|
41523
|
+
if (!fs28.existsSync(target.configBase)) fs28.mkdirSync(target.configBase, { recursive: true });
|
|
41524
|
+
if (target.outsideProject && fs28.existsSync(target.path)) {
|
|
41525
|
+
logger.info(`Backed up ${import_chalk3.default.gray(target.path)} to ${import_chalk3.default.cyan(backupBeside(target.path))} before replacing it (outside the project root).`);
|
|
41421
41526
|
}
|
|
41527
|
+
fs28.writeFileSync(target.path, JSON.stringify(target.settings, null, 2) + "\n");
|
|
41528
|
+
}
|
|
41529
|
+
function reportInstalled(target, agentLabel) {
|
|
41530
|
+
const wasStale = target.before !== void 0;
|
|
41531
|
+
logger.success(`wairon MCP server ${wasStale ? "updated (was stale)" : "registered"} for ${agentLabel} in ${import_chalk3.default.cyan(target.path)}.`);
|
|
41532
|
+
logger.blank();
|
|
41533
|
+
logger.info("AI tools using this config will have access to these wairon tools:");
|
|
41534
|
+
logger.info(" listAgents \xB7 getAgent \xB7 listDomains \xB7 validateTopology \xB7 getProjectConfig");
|
|
41535
|
+
logger.info(" sdd_initialize_system \xB7 sdd_add_subsystem \xB7 sdd_add_component \xB7 sdd_define_interface");
|
|
41536
|
+
logger.info(" sdd_write_narrative \xB7 sdd_validate_tree \xB7 sdd_get_status");
|
|
41537
|
+
logger.blank();
|
|
41538
|
+
const restartApp = target.backend === "gemini" ? "Antigravity CLI (agy)" : "claude";
|
|
41539
|
+
logger.info(`Restart ${import_chalk3.default.bold(restartApp)} (or reload MCP servers) to activate.`);
|
|
41540
|
+
if (target.backend === "claude" && !target.useGlobal) {
|
|
41541
|
+
logger.warn('Claude shows project .mcp.json servers as "\u23F8 Pending approval" \u2014 approve wairon once on next launch (or run `claude` and accept the trust prompt).');
|
|
41542
|
+
}
|
|
41543
|
+
if (target.backend === "gemini" && !target.useGlobal) {
|
|
41544
|
+
logger.warn("Note: Antigravity (agy) reads MCP servers from its global ~/.gemini/antigravity-cli/mcp_config.json, not this project file.");
|
|
41545
|
+
logger.warn("If the agent cannot see the sdd_* tools, run: " + import_chalk3.default.bold("wairon mcp install --backend gemini --global"));
|
|
41546
|
+
}
|
|
41547
|
+
logger.blank();
|
|
41422
41548
|
}
|
|
41423
41549
|
async function runMcpStatus() {
|
|
41424
41550
|
assertProjectInitialized();
|
|
@@ -41475,19 +41601,25 @@ function hostedMcpEntry(options) {
|
|
|
41475
41601
|
if (options.hostedProject) headers["X-Wairon-Project"] = options.hostedProject;
|
|
41476
41602
|
return { type: "http", url: `${base}/mcp`, headers };
|
|
41477
41603
|
}
|
|
41478
|
-
function
|
|
41479
|
-
|
|
41480
|
-
|
|
41604
|
+
function legacyPluginDir() {
|
|
41605
|
+
return path48.join(geminiGlobalDir(), "config", "plugins", "wairon");
|
|
41606
|
+
}
|
|
41607
|
+
function findLegacyPlugin() {
|
|
41608
|
+
const dir = legacyPluginDir();
|
|
41609
|
+
return fs28.existsSync(dir) ? dir : null;
|
|
41610
|
+
}
|
|
41611
|
+
function retireLegacyPlugin() {
|
|
41612
|
+
const dir = findLegacyPlugin();
|
|
41613
|
+
if (!dir) return null;
|
|
41614
|
+
const backup = path48.join(path48.dirname(path48.dirname(dir)), `wairon-plugin.wairon-backup-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`);
|
|
41481
41615
|
try {
|
|
41482
|
-
|
|
41483
|
-
|
|
41484
|
-
|
|
41485
|
-
return true;
|
|
41486
|
-
}
|
|
41616
|
+
fs28.renameSync(dir, backup);
|
|
41617
|
+
logger.info(`Moved the legacy global Antigravity plugin ${import_chalk3.default.gray(dir)} aside to ${import_chalk3.default.cyan(backup)} (it collides with the wairon MCP server).`);
|
|
41618
|
+
return backup;
|
|
41487
41619
|
} catch (e) {
|
|
41488
|
-
logger.warn(`Could not
|
|
41620
|
+
logger.warn(`Could not move the legacy wairon Antigravity plugin aside: ${String(e)}`);
|
|
41621
|
+
return null;
|
|
41489
41622
|
}
|
|
41490
|
-
return false;
|
|
41491
41623
|
}
|
|
41492
41624
|
var fs28, os8, path48, import_chalk3;
|
|
41493
41625
|
var init_mcp = __esm({
|
|
@@ -42399,10 +42531,12 @@ init_base();
|
|
|
42399
42531
|
init_fs();
|
|
42400
42532
|
init_project();
|
|
42401
42533
|
var WAIRON_AGENT_FILE = /-(owner|implementer|architect)\.md$/;
|
|
42402
|
-
function pruneStaleAgents(expectedPaths, scanDirs) {
|
|
42534
|
+
function pruneStaleAgents(expectedPaths, scanDirs, reach2) {
|
|
42403
42535
|
const dirs = new Set(scanDirs ?? [...expectedPaths].map((p) => path50.dirname(p)));
|
|
42404
42536
|
let pruned = 0;
|
|
42405
42537
|
for (const dir of dirs) {
|
|
42538
|
+
const outside = reach2 !== void 0 && isOutsideRoot(reach2.root, dir);
|
|
42539
|
+
if (outside && !reach2.global) continue;
|
|
42406
42540
|
let entries;
|
|
42407
42541
|
try {
|
|
42408
42542
|
entries = fs29.readdirSync(dir);
|
|
@@ -42422,6 +42556,7 @@ function pruneStaleAgents(expectedPaths, scanDirs) {
|
|
|
42422
42556
|
}
|
|
42423
42557
|
}
|
|
42424
42558
|
if (!owned) continue;
|
|
42559
|
+
if (outside) logger.info(`Backed up ${full} to ${backupBeside(full)} before pruning it (outside the project root).`);
|
|
42425
42560
|
fs29.unlinkSync(full);
|
|
42426
42561
|
pruned++;
|
|
42427
42562
|
logger.verbose(`Pruned stale: ${full}`);
|
|
@@ -42429,6 +42564,28 @@ function pruneStaleAgents(expectedPaths, scanDirs) {
|
|
|
42429
42564
|
}
|
|
42430
42565
|
return pruned;
|
|
42431
42566
|
}
|
|
42567
|
+
function targetsOutsideRoot(projectConfig, root) {
|
|
42568
|
+
const out = [];
|
|
42569
|
+
for (const target of projectConfig.targets) {
|
|
42570
|
+
if (typeof target === "string" || !("outputDir" in target) || !target.outputDir) continue;
|
|
42571
|
+
if ("enabled" in target && target.enabled === false) continue;
|
|
42572
|
+
const dir = path50.resolve(root, target.outputDir);
|
|
42573
|
+
if (isOutsideRoot(root, dir)) out.push({ type: target.type, dir });
|
|
42574
|
+
}
|
|
42575
|
+
return out;
|
|
42576
|
+
}
|
|
42577
|
+
function announceOutsideTargets(projectConfig, reach2) {
|
|
42578
|
+
const outside = targetsOutsideRoot(projectConfig, reach2.root);
|
|
42579
|
+
if (!outside.length) return [];
|
|
42580
|
+
if (reach2.global) {
|
|
42581
|
+
for (const t of outside) logger.info(`Target "${t.type}" writes agent files outside the project root, to ${t.dir} (--global): each file replaced or pruned there is backed up beside itself first.`);
|
|
42582
|
+
return [];
|
|
42583
|
+
}
|
|
42584
|
+
for (const t of outside) {
|
|
42585
|
+
logger.warn(`Skipped target "${t.type}": its agent files would land outside the project root, in ${t.dir}. Nothing was written or pruned there \u2014 re-run with --global to write there (each replaced file is backed up beside itself).`);
|
|
42586
|
+
}
|
|
42587
|
+
return outside.map((t) => t.type);
|
|
42588
|
+
}
|
|
42432
42589
|
async function runGenerate(options = {}) {
|
|
42433
42590
|
await generateLayer(options);
|
|
42434
42591
|
const scoped = options.root || options.domain || options.domains;
|
|
@@ -42452,13 +42609,15 @@ async function generateLayer(options = {}) {
|
|
|
42452
42609
|
logger.warn("No agents resolved from the spec tree. Define subsystems and components first \u2014 see `wairon status`.");
|
|
42453
42610
|
return;
|
|
42454
42611
|
}
|
|
42612
|
+
const reach2 = { root: getProjectRoot(), global: options.global === true };
|
|
42613
|
+
const skippedTargets = announceOutsideTargets(projectConfig, reach2);
|
|
42455
42614
|
if (projectConfig.rules.materializeAgentFiles) {
|
|
42456
|
-
materializeAgentLayer(agents, projectConfig, options);
|
|
42615
|
+
materializeAgentLayer(agents, projectConfig, options, reach2, skippedTargets);
|
|
42457
42616
|
} else if (options.dryRun) {
|
|
42458
42617
|
logger.info("Dry run \u2014 rules.materializeAgentFiles is off: no agent files are written; leftover managed files would be removed.");
|
|
42459
42618
|
} else if (options.prune !== false) {
|
|
42460
42619
|
const candidateDirs = [...resolveExpectedOutputPaths(agents, projectConfig)].map((p) => path50.dirname(p));
|
|
42461
|
-
const removed = pruneStaleAgents(/* @__PURE__ */ new Set(), candidateDirs);
|
|
42620
|
+
const removed = pruneStaleAgents(/* @__PURE__ */ new Set(), candidateDirs, reach2);
|
|
42462
42621
|
if (removed > 0) {
|
|
42463
42622
|
logger.info(`Removed ${removed} previously materialized agent file(s) \u2014 agents are served as live briefs (sdd_get_agent_brief); opt back in with rules.materializeAgentFiles: true.`);
|
|
42464
42623
|
}
|
|
@@ -42483,8 +42642,17 @@ async function generateLayer(options = {}) {
|
|
|
42483
42642
|
logger.warn(`Failed to re-inject AI guides: ${String(err)}`);
|
|
42484
42643
|
}
|
|
42485
42644
|
}
|
|
42486
|
-
function
|
|
42487
|
-
const
|
|
42645
|
+
function backUpOutsideFiles(agents, projectConfig, reach2) {
|
|
42646
|
+
const backups = /* @__PURE__ */ new Map();
|
|
42647
|
+
if (!reach2.global) return backups;
|
|
42648
|
+
for (const file of resolveExpectedOutputPaths(agents, projectConfig)) {
|
|
42649
|
+
if (isOutsideRoot(reach2.root, file) && pathExists(file)) backups.set(path50.resolve(file), backupBeside(file));
|
|
42650
|
+
}
|
|
42651
|
+
return backups;
|
|
42652
|
+
}
|
|
42653
|
+
function materializeAgentLayer(agents, projectConfig, options, reach2, skippedTargets) {
|
|
42654
|
+
const configured = activeTargetTypes(projectConfig);
|
|
42655
|
+
const filterTargets = options.target ? [options.target].filter((t) => !skippedTargets.includes(t)) : skippedTargets.length ? configured.filter((t) => !skippedTargets.includes(t)) : void 0;
|
|
42488
42656
|
let filterDomainIds;
|
|
42489
42657
|
if (options.root) {
|
|
42490
42658
|
filterDomainIds = ["root"];
|
|
@@ -42500,6 +42668,7 @@ function materializeAgentLayer(agents, projectConfig, options) {
|
|
|
42500
42668
|
}
|
|
42501
42669
|
logger.info(`Generating ${agentPool.length} agent(s)...`);
|
|
42502
42670
|
logger.blank();
|
|
42671
|
+
const backups = options.dryRun ? /* @__PURE__ */ new Map() : backUpOutsideFiles(agents, projectConfig, reach2);
|
|
42503
42672
|
const summaries = generateAll(agents, projectConfig, {
|
|
42504
42673
|
filterTargets,
|
|
42505
42674
|
filterDomainIds,
|
|
@@ -42518,13 +42687,19 @@ function materializeAgentLayer(agents, projectConfig, options) {
|
|
|
42518
42687
|
} else {
|
|
42519
42688
|
logger.success(`Written: ${result.outputPath}`);
|
|
42520
42689
|
written++;
|
|
42690
|
+
const backup = backups.get(path50.resolve(result.outputPath));
|
|
42691
|
+
if (backup) {
|
|
42692
|
+
logger.info(`Backed up the replaced ${result.outputPath} to ${backup}.`);
|
|
42693
|
+
backups.delete(path50.resolve(result.outputPath));
|
|
42694
|
+
}
|
|
42521
42695
|
}
|
|
42522
42696
|
}
|
|
42523
42697
|
}
|
|
42524
42698
|
}
|
|
42699
|
+
for (const backup of backups.values()) fs29.rmSync(backup, { force: true });
|
|
42525
42700
|
let prunedCount = 0;
|
|
42526
42701
|
if (!options.dryRun && options.prune !== false) {
|
|
42527
|
-
prunedCount = pruneStaleAgents(resolveExpectedOutputPaths(agents, projectConfig));
|
|
42702
|
+
prunedCount = pruneStaleAgents(resolveExpectedOutputPaths(agents, projectConfig), void 0, reach2);
|
|
42528
42703
|
}
|
|
42529
42704
|
logger.blank();
|
|
42530
42705
|
if (options.dryRun) {
|
|
@@ -44279,9 +44454,9 @@ async function runDoctor(options = {}) {
|
|
|
44279
44454
|
line(tally, hp.mark === "error" ? "error" : "ok", `Gemini CLI (project): ${hp.note} ${import_chalk12.default.gray("(Antigravity ignores this file)")}`);
|
|
44280
44455
|
}
|
|
44281
44456
|
}
|
|
44282
|
-
const pluginDir =
|
|
44283
|
-
if (
|
|
44284
|
-
line(tally, "warn", `Legacy Antigravity plugin present (${pluginDir}) \u2014 it collides with the wairon MCP server.
|
|
44457
|
+
const pluginDir = findLegacyPlugin();
|
|
44458
|
+
if (pluginDir) {
|
|
44459
|
+
line(tally, "warn", `Legacy Antigravity plugin present (${pluginDir}) \u2014 it collides with the wairon MCP server. Move it aside with \`wairon doctor --fix --global\` (it is outside the project, so --fix asks first).`);
|
|
44285
44460
|
}
|
|
44286
44461
|
logger.blank();
|
|
44287
44462
|
printSummary(tally);
|
|
@@ -44358,28 +44533,70 @@ async function applyFixes(options, tally) {
|
|
|
44358
44533
|
console.log(` ${icon("error")} Narrative-step repair failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
44359
44534
|
}
|
|
44360
44535
|
await migrateChaining(options, tally);
|
|
44361
|
-
|
|
44362
|
-
|
|
44536
|
+
await repairMcpRegistration(options, targets);
|
|
44537
|
+
logger.blank();
|
|
44538
|
+
}
|
|
44539
|
+
async function repairMcpRegistration(options, targets) {
|
|
44540
|
+
const installs = [];
|
|
44541
|
+
if (targets.includes("claude")) installs.push({ backend: "claude", global: false });
|
|
44542
|
+
const gemini = targets.includes("gemini") || targets.includes("agy");
|
|
44543
|
+
if (gemini) installs.push({ backend: "gemini", global: targets.includes("agy") });
|
|
44544
|
+
const outside = [];
|
|
44545
|
+
for (const install of installs) {
|
|
44546
|
+
let writes;
|
|
44363
44547
|
try {
|
|
44364
|
-
|
|
44548
|
+
writes = planMcpInstall(install);
|
|
44365
44549
|
} catch (e) {
|
|
44366
|
-
console.log(` ${icon("warn")} MCP install for
|
|
44550
|
+
console.log(` ${icon("warn")} MCP install for ${install.backend} could not be planned: ${e instanceof Error ? e.message : String(e)}`);
|
|
44551
|
+
continue;
|
|
44367
44552
|
}
|
|
44553
|
+
if (writes.some((w) => w.outsideProject && !w.upToDate)) outside.push({ install, writes });
|
|
44554
|
+
else await installMcp(install);
|
|
44368
44555
|
}
|
|
44369
|
-
|
|
44370
|
-
|
|
44371
|
-
|
|
44372
|
-
|
|
44373
|
-
|
|
44374
|
-
|
|
44375
|
-
|
|
44376
|
-
|
|
44377
|
-
|
|
44378
|
-
|
|
44379
|
-
|
|
44556
|
+
const plugin = gemini ? findLegacyPlugin() : null;
|
|
44557
|
+
if (outside.length === 0 && !plugin) return;
|
|
44558
|
+
printOutsideWrites(outside, plugin);
|
|
44559
|
+
if (!await confirmOutsideWrites(options)) return;
|
|
44560
|
+
for (const planned of outside) await installMcp(planned.install);
|
|
44561
|
+
if (plugin) retireLegacyPlugin();
|
|
44562
|
+
}
|
|
44563
|
+
async function installMcp(install) {
|
|
44564
|
+
try {
|
|
44565
|
+
await runMcpInstall(install);
|
|
44566
|
+
} catch (e) {
|
|
44567
|
+
console.log(` ${icon("warn")} MCP install for ${install.backend} failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
44568
|
+
}
|
|
44569
|
+
}
|
|
44570
|
+
function printOutsideWrites(outside, plugin) {
|
|
44571
|
+
console.log(` ${icon("warn")} ${import_chalk12.default.bold("Writes outside this project")} \u2014 machine-wide state every project on this machine shares:`);
|
|
44572
|
+
for (const { writes } of outside) {
|
|
44573
|
+
for (const w of writes.filter((x) => x.outsideProject && !x.upToDate)) {
|
|
44574
|
+
console.log(` ${w.path} (${w.backend} MCP registration)`);
|
|
44575
|
+
console.log(` mcpServers.wairon now: ${describeMcpEntry(w.before)}`);
|
|
44576
|
+
console.log(` mcpServers.wairon new: ${describeMcpEntry(w.after)}`);
|
|
44577
|
+
console.log(" the file is backed up beside itself before it is replaced");
|
|
44380
44578
|
}
|
|
44381
44579
|
}
|
|
44382
|
-
|
|
44580
|
+
if (plugin) console.log(` ${plugin} (legacy Antigravity plugin) \u2014 moved aside, not deleted`);
|
|
44581
|
+
}
|
|
44582
|
+
function describeMcpEntry(entry) {
|
|
44583
|
+
if (entry === void 0 || entry === null) return "(none)";
|
|
44584
|
+
const text3 = JSON.stringify(entry, (key, value) => key === "Authorization" && typeof value === "string" ? value.replace(/^(\S+\s+).+$/, "$1***") : value);
|
|
44585
|
+
const LIMIT = 200;
|
|
44586
|
+
return text3.length <= LIMIT ? text3 : `${text3.slice(0, LIMIT)}\u2026 (${text3.length} characters)`;
|
|
44587
|
+
}
|
|
44588
|
+
async function confirmOutsideWrites(options) {
|
|
44589
|
+
if (options.yes && options.global) return true;
|
|
44590
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
44591
|
+
const why = options.global ? "no terminal to confirm them" : options.yes ? "--yes does not cover a write outside the project without --global" : "no terminal to confirm them and no --global";
|
|
44592
|
+
console.log(` ${icon("warn")} Skipped the writes outside this project: ${why}. Nothing outside the project was written \u2014 re-run with \`wairon doctor --fix --yes --global\` to apply them.`);
|
|
44593
|
+
return false;
|
|
44594
|
+
}
|
|
44595
|
+
const { confirmed } = await import_inquirer5.default.prompt([
|
|
44596
|
+
{ type: "confirm", name: "confirmed", message: "Apply the writes outside this project listed above?", default: false }
|
|
44597
|
+
]);
|
|
44598
|
+
if (!confirmed) console.log(` ${icon("warn")} Skipped the writes outside this project. Nothing outside the project was written.`);
|
|
44599
|
+
return confirmed;
|
|
44383
44600
|
}
|
|
44384
44601
|
function reportOnly(options) {
|
|
44385
44602
|
if (options.fix) {
|
|
@@ -44571,7 +44788,8 @@ function printChainingPlan(migration) {
|
|
|
44571
44788
|
}
|
|
44572
44789
|
var projectLabel2 = (project2) => project2 === "" ? "(top root)" : project2;
|
|
44573
44790
|
function printProjectMigration(p) {
|
|
44574
|
-
|
|
44791
|
+
const above = isOutsideRoot(getProjectRoot(), p.directory) ? import_chalk12.default.yellow(" (outside this project's root \u2014 the family above it)") : "";
|
|
44792
|
+
console.log(` ${import_chalk12.default.bold(projectLabel2(p.project))} ${import_chalk12.default.gray(p.directory)}${above}`);
|
|
44575
44793
|
if (p.idToWrite) console.log(` id: declare "${p.idToWrite}"`);
|
|
44576
44794
|
if (p.createsSystem) console.log(" L0: create a minimal one \u2014 the project exports and has none");
|
|
44577
44795
|
for (const e of p.exports) console.log(` L0 entry: ${describeEntry(e)}`);
|
|
@@ -44665,13 +44883,13 @@ function writeArtifact(dest, content) {
|
|
|
44665
44883
|
fs33.writeFileSync(dest, content, "utf-8");
|
|
44666
44884
|
}
|
|
44667
44885
|
function parseSequenceRef(ref) {
|
|
44668
|
-
const
|
|
44669
|
-
if (
|
|
44886
|
+
const sep17 = ref.includes(":") ? ref.lastIndexOf(":") : ref.lastIndexOf(".");
|
|
44887
|
+
if (sep17 <= 0 || sep17 === ref.length - 1) {
|
|
44670
44888
|
throw new WaironError(
|
|
44671
44889
|
`Invalid --sequence reference "${ref}". Use <componentId>:<methodName> (e.g. billing-portal:authorize).`
|
|
44672
44890
|
);
|
|
44673
44891
|
}
|
|
44674
|
-
return { component: ref.slice(0,
|
|
44892
|
+
return { component: ref.slice(0, sep17), method: ref.slice(sep17 + 1) };
|
|
44675
44893
|
}
|
|
44676
44894
|
async function runDiagram(rawOptions = {}) {
|
|
44677
44895
|
assertProjectInitialized();
|
|
@@ -45074,7 +45292,7 @@ async function installPack(source, yes) {
|
|
|
45074
45292
|
const update = await confirmFloatingUpdate(extracted.dir, yes);
|
|
45075
45293
|
if (update === "declined") return;
|
|
45076
45294
|
const installed = installPackFromDirectory(extracted.dir, source);
|
|
45077
|
-
|
|
45295
|
+
reportInstalled2(installed, source, update, yes);
|
|
45078
45296
|
} catch (e) {
|
|
45079
45297
|
logger.error(e instanceof Error ? e.message : String(e));
|
|
45080
45298
|
process.exitCode = 1;
|
|
@@ -45112,7 +45330,7 @@ async function installPack(source, yes) {
|
|
|
45112
45330
|
try {
|
|
45113
45331
|
const update = await confirmFloatingUpdate(sourceDir, yes);
|
|
45114
45332
|
if (update === "declined") return;
|
|
45115
|
-
|
|
45333
|
+
reportInstalled2(installPackFromDirectory(sourceDir, origin), origin, update, yes);
|
|
45116
45334
|
} catch (e) {
|
|
45117
45335
|
logger.error(e instanceof Error ? e.message : String(e));
|
|
45118
45336
|
process.exitCode = 1;
|
|
@@ -45141,7 +45359,7 @@ async function confirmFloatingUpdate(sourceDir, yes) {
|
|
|
45141
45359
|
logger.info("Nothing was installed.");
|
|
45142
45360
|
return "declined";
|
|
45143
45361
|
}
|
|
45144
|
-
function
|
|
45362
|
+
function reportInstalled2(installed, origin, movedFloating = false, yes) {
|
|
45145
45363
|
logger.success(`Installed ${installed.name} v${installed.version} into the pack store.`);
|
|
45146
45364
|
console.log(` ${import_chalk16.default.dim(installed.path)}`);
|
|
45147
45365
|
console.log(` ${import_chalk16.default.dim(installed.digest)}`);
|
|
@@ -61483,8 +61701,9 @@ program.command("init").description("Initialize wairon and bootstrap the SDD Spe
|
|
|
61483
61701
|
await addPack(source, false, opts.yes);
|
|
61484
61702
|
}
|
|
61485
61703
|
});
|
|
61486
|
-
program.command("generate").description("Generate agent output files from the spec tree").option("--target <type>", "limit to a specific target (claude|gemini|custom)").option("--domain <id>", "limit to agents in a single domain").option("--domains <ids>", "limit to a comma-separated list of domain ids").option("--root", "only generate root-level agents").option("--family", "also generate each member's own layer in its own root (generate never cascades otherwise)").option("--no-recurse", "accepted for one release: generating only this project's layer is now the default").option("--no-prune", "do not remove wairon-managed agent files that are no longer in the topology").option("--dry-run", "preview what would be generated without writing files").action(async (opts) => {
|
|
61704
|
+
program.command("generate").description("Generate agent output files from the spec tree").option("--target <type>", "limit to a specific target (claude|gemini|custom)").option("--domain <id>", "limit to agents in a single domain").option("--domains <ids>", "limit to a comma-separated list of domain ids").option("--root", "only generate root-level agents").option("--family", "also generate each member's own layer in its own root (generate never cascades otherwise)").option("--no-recurse", "accepted for one release: generating only this project's layer is now the default").option("--no-prune", "do not remove wairon-managed agent files that are no longer in the topology").option("--dry-run", "preview what would be generated without writing files").option("--global", "also write (and prune) a target whose output directory resolves outside the project root, backing up each file replaced there").action(async (opts) => {
|
|
61487
61705
|
await runGenerate({
|
|
61706
|
+
global: opts.global === true,
|
|
61488
61707
|
target: opts.target,
|
|
61489
61708
|
domain: opts.domain,
|
|
61490
61709
|
domains: opts.domains,
|
|
@@ -61611,8 +61830,8 @@ program.command("validate").description("Validate the project configuration and
|
|
|
61611
61830
|
program.command("status").description("Show a hierarchical completeness graph of the SDD Spec Tree").option("--subsystem <id>", "only show status for the specified subsystem").option("--no-recursive", "do not recursively show status for subprojects").action(async (opts) => {
|
|
61612
61831
|
await statusCommand(opts);
|
|
61613
61832
|
});
|
|
61614
|
-
program.command("doctor").description("Health check: flags stale generated guides/skills, an unregistered MCP server, spec-tree issues and the chaining migration still pending").option("--fix", "regenerate stale in-project guides/context/skills, register the MCP server, then apply the chaining migration once confirmed").option("--report <section>", "print one section's report and nothing else, writing nothing (chaining | composed-validation); never combines with --fix").option("-y, --yes", "answer the chaining migration's confirmation (for a non-interactive --fix)").action(async (opts) => {
|
|
61615
|
-
await runDoctor({ fix: opts.fix, report: opts.report, yes: opts.yes });
|
|
61833
|
+
program.command("doctor").description("Health check: flags stale generated guides/skills, an unregistered MCP server, spec-tree issues and the chaining migration still pending").option("--fix", "regenerate stale in-project guides/context/skills, register the MCP server, then apply the chaining migration once confirmed").option("--report <section>", "print one section's report and nothing else, writing nothing (chaining | composed-validation); never combines with --fix").option("-y, --yes", "answer the chaining migration's confirmation (for a non-interactive --fix); a write outside the project root also needs --global").option("--global", "consent to the --fix writes outside the project root (a machine-wide MCP config, the legacy global plugin); with --yes it answers their confirmation, and each replaced file is backed up beside itself").action(async (opts) => {
|
|
61834
|
+
await runDoctor({ fix: opts.fix, report: opts.report, yes: opts.yes, global: opts.global === true });
|
|
61616
61835
|
});
|
|
61617
61836
|
async function runRules() {
|
|
61618
61837
|
await listRules3();
|