@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/index.js
CHANGED
|
@@ -3503,6 +3503,7 @@ __export(src_exports, {
|
|
|
3503
3503
|
SystemPublicInterfaceSchema: () => SystemPublicInterfaceSchema,
|
|
3504
3504
|
SystemSpecSchema: () => SystemSpecSchema,
|
|
3505
3505
|
TargetConfigSchema: () => TargetConfigSchema,
|
|
3506
|
+
TechnologySchema: () => TechnologySchema,
|
|
3506
3507
|
TemplateNotFoundError: () => TemplateNotFoundError,
|
|
3507
3508
|
TemplateSchema: () => TemplateSchema,
|
|
3508
3509
|
TopologyConfigSchema: () => TopologyConfigSchema,
|
|
@@ -3530,6 +3531,7 @@ __export(src_exports, {
|
|
|
3530
3531
|
assertProjectInitialized: () => assertProjectInitialized,
|
|
3531
3532
|
backendCommand: () => backendCommand,
|
|
3532
3533
|
backfillChainedSubprojectConfigs: () => backfillChainedSubprojectConfigs,
|
|
3534
|
+
backupBeside: () => backupBeside,
|
|
3533
3535
|
buildCanvasDataModel: () => buildCanvasDataModel,
|
|
3534
3536
|
buildProjectGraph: () => buildProjectGraph,
|
|
3535
3537
|
buildRuleContext: () => buildRuleContext,
|
|
@@ -3629,6 +3631,7 @@ __export(src_exports, {
|
|
|
3629
3631
|
internalizeMember: () => internalizeMember,
|
|
3630
3632
|
isDraftSubsystem: () => isDraftSubsystem,
|
|
3631
3633
|
isLogic: () => isLogic,
|
|
3634
|
+
isOutsideRoot: () => isOutsideRoot,
|
|
3632
3635
|
isOwnComponentEntry: () => isOwnComponentEntry,
|
|
3633
3636
|
isPattern: () => isPattern,
|
|
3634
3637
|
isProjectInitialized: () => isProjectInitialized,
|
|
@@ -3758,6 +3761,8 @@ __export(src_exports, {
|
|
|
3758
3761
|
stepFieldsFor: () => stepFieldsFor,
|
|
3759
3762
|
stepGraph: () => stepGraph,
|
|
3760
3763
|
syncContextFiles: () => syncContextFiles,
|
|
3764
|
+
technologyName: () => technologyName,
|
|
3765
|
+
technologyTokens: () => technologyTokens,
|
|
3761
3766
|
toMarkdown: () => toMarkdown,
|
|
3762
3767
|
typeBindingOf: () => typeBindingOf,
|
|
3763
3768
|
typeGenericParameters: () => typeGenericParameters,
|
|
@@ -3814,6 +3819,15 @@ function readFileOrNull(filePath) {
|
|
|
3814
3819
|
function pathExists(targetPath) {
|
|
3815
3820
|
return fs.existsSync(targetPath);
|
|
3816
3821
|
}
|
|
3822
|
+
function isOutsideRoot(root, target) {
|
|
3823
|
+
const rel2 = path.relative(path.resolve(root), path.resolve(target));
|
|
3824
|
+
return rel2 === ".." || rel2.startsWith(`..${path.sep}`) || path.isAbsolute(rel2);
|
|
3825
|
+
}
|
|
3826
|
+
function backupBeside(file, at = /* @__PURE__ */ new Date()) {
|
|
3827
|
+
const backup = `${file}.wairon-backup-${at.toISOString().replace(/[:.]/g, "-")}`;
|
|
3828
|
+
fs.copyFileSync(file, backup);
|
|
3829
|
+
return backup;
|
|
3830
|
+
}
|
|
3817
3831
|
function listFiles(dirPath, ext) {
|
|
3818
3832
|
if (!fs.existsSync(dirPath)) return [];
|
|
3819
3833
|
return fs.readdirSync(dirPath).filter((f) => f.endsWith(ext)).map((f) => path.join(dirPath, f));
|
|
@@ -7028,6 +7042,19 @@ function parseDeclaredCall(ref) {
|
|
|
7028
7042
|
if (at <= 0 || at === ref.length - 1) return null;
|
|
7029
7043
|
return { compId: ref.slice(0, at), methodName: ref.slice(at + 1) };
|
|
7030
7044
|
}
|
|
7045
|
+
var TechnologySchema = import_zod8.z.union([
|
|
7046
|
+
import_zod8.z.string().min(1),
|
|
7047
|
+
import_zod8.z.object({
|
|
7048
|
+
name: import_zod8.z.string().min(1),
|
|
7049
|
+
matches: import_zod8.z.array(import_zod8.z.string().min(1)).min(1)
|
|
7050
|
+
}).strict()
|
|
7051
|
+
]);
|
|
7052
|
+
function technologyName(tech) {
|
|
7053
|
+
return typeof tech === "string" ? tech : tech.name;
|
|
7054
|
+
}
|
|
7055
|
+
function technologyTokens(tech) {
|
|
7056
|
+
return typeof tech === "string" ? [tech] : [...tech.matches];
|
|
7057
|
+
}
|
|
7031
7058
|
var ImplementationSpecSchema = import_zod8.z.object({
|
|
7032
7059
|
id: SpecIdSchema,
|
|
7033
7060
|
name: import_zod8.z.string(),
|
|
@@ -7052,9 +7079,10 @@ var ImplementationSpecSchema = import_zod8.z.object({
|
|
|
7052
7079
|
* component's ownership tree the technology's home: references anywhere
|
|
7053
7080
|
* outside it are flagged (TECH_LEAKAGE), contract identifiers must stay
|
|
7054
7081
|
* intent-language (VENDOR_NAME_IN_CONTRACT), and only data-layer
|
|
7055
|
-
* stereotypes should bind tech directly (TECH_ON_LOGIC_COMPONENT).
|
|
7082
|
+
* stereotypes should bind tech directly (TECH_ON_LOGIC_COMPONENT). An entry
|
|
7083
|
+
* is a bare name or `{ name, matches }` (see TechnologySchema).
|
|
7056
7084
|
*/
|
|
7057
|
-
technologies: import_zod8.z.array(
|
|
7085
|
+
technologies: import_zod8.z.array(TechnologySchema).optional(),
|
|
7058
7086
|
/**
|
|
7059
7087
|
* The parameter names THIS realization takes BEFORE the ones its contract
|
|
7060
7088
|
* declares — a config object, a data root, the transport handles a portal is
|
|
@@ -14282,7 +14310,7 @@ function defaultTargetConfig(type) {
|
|
|
14282
14310
|
enabled: true
|
|
14283
14311
|
};
|
|
14284
14312
|
}
|
|
14285
|
-
var WAIRON_VERSION = "5.1.1-dev.
|
|
14313
|
+
var WAIRON_VERSION = "5.1.1-dev.89";
|
|
14286
14314
|
var GITHUB_REPO = "SYW-Apps/Waffle-AIron";
|
|
14287
14315
|
var ARCHITECT_AGENT_ID = "agent-architect";
|
|
14288
14316
|
var ARCHITECT_TEMPLATE_ID = "architect";
|
|
@@ -21413,7 +21441,7 @@ var technologyBindingRule = {
|
|
|
21413
21441
|
ctx.addIssue(
|
|
21414
21442
|
"warning",
|
|
21415
21443
|
"TECH_ON_LOGIC_COMPONENT",
|
|
21416
|
-
`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.`,
|
|
21444
|
+
`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.`,
|
|
21417
21445
|
impl.id,
|
|
21418
21446
|
ctx.isImplementationDraft(impl)
|
|
21419
21447
|
);
|
|
@@ -21438,6 +21466,16 @@ function makeMatcher(tech) {
|
|
|
21438
21466
|
return false;
|
|
21439
21467
|
};
|
|
21440
21468
|
}
|
|
21469
|
+
function newHome(label7) {
|
|
21470
|
+
const home = {
|
|
21471
|
+
label: label7,
|
|
21472
|
+
matchers: [],
|
|
21473
|
+
match: (text3) => home.matchers.some((m) => m(text3)),
|
|
21474
|
+
ownerComponents: /* @__PURE__ */ new Set(),
|
|
21475
|
+
scope: /* @__PURE__ */ new Set()
|
|
21476
|
+
};
|
|
21477
|
+
return home;
|
|
21478
|
+
}
|
|
21441
21479
|
function contractIdentifiers(m) {
|
|
21442
21480
|
const parts = [m.name, m.signature, m.returns];
|
|
21443
21481
|
for (const p of m.params ?? []) parts.push(p.name, p.type);
|
|
@@ -21447,7 +21485,7 @@ function contractIdentifiers(m) {
|
|
|
21447
21485
|
var technologyRule = {
|
|
21448
21486
|
name: "technology-boundaries",
|
|
21449
21487
|
judges: "design",
|
|
21450
|
-
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.",
|
|
21488
|
+
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.",
|
|
21451
21489
|
codes: [
|
|
21452
21490
|
{ code: "TECH_LEAKAGE", defaultSeverity: "warning", summary: "Technology referenced outside its owning boundary" },
|
|
21453
21491
|
{ code: "VENDOR_NAME_IN_CONTRACT", defaultSeverity: "warning", summary: "Technology name in L3 contract identifiers" }
|
|
@@ -21484,7 +21522,9 @@ var technologyRule = {
|
|
|
21484
21522
|
const intf = ctx.interfaceMap.get(impl.contract);
|
|
21485
21523
|
const comp = intf ? ctx.componentMap.get(intf.component) : void 0;
|
|
21486
21524
|
if (!comp) continue;
|
|
21487
|
-
for (const tech of impl.technologies)
|
|
21525
|
+
for (const tech of impl.technologies) {
|
|
21526
|
+
declarations.push({ comp, name: technologyName(tech), tokens: technologyTokens(tech) });
|
|
21527
|
+
}
|
|
21488
21528
|
}
|
|
21489
21529
|
const scopes = /* @__PURE__ */ new Map();
|
|
21490
21530
|
const scopeOf2 = (comp) => {
|
|
@@ -21508,11 +21548,12 @@ var technologyRule = {
|
|
|
21508
21548
|
return scope;
|
|
21509
21549
|
};
|
|
21510
21550
|
const homes = /* @__PURE__ */ new Map();
|
|
21511
|
-
for (const { comp,
|
|
21512
|
-
const
|
|
21513
|
-
if (!
|
|
21514
|
-
const key =
|
|
21515
|
-
const home = homes.get(key) ??
|
|
21551
|
+
for (const { comp, name, tokens } of declarations) {
|
|
21552
|
+
const matchers = tokens.map(makeMatcher).filter((m) => m !== null);
|
|
21553
|
+
if (!matchers.length) continue;
|
|
21554
|
+
const key = name.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean).join("");
|
|
21555
|
+
const home = homes.get(key) ?? newHome(name);
|
|
21556
|
+
home.matchers.push(...matchers);
|
|
21516
21557
|
home.ownerComponents.add(comp.id);
|
|
21517
21558
|
for (const id of scopeOf2(comp)) home.scope.add(id);
|
|
21518
21559
|
homes.set(key, home);
|
|
@@ -24936,8 +24977,8 @@ function generateSequenceDiagram(componentId, methodName, options) {
|
|
|
24936
24977
|
const steps = [...methodImpl.narrative].sort((a, b) => a.stepNumber - b.stepNumber);
|
|
24937
24978
|
for (const step of steps) {
|
|
24938
24979
|
for (const par of parallelArms) {
|
|
24939
|
-
const
|
|
24940
|
-
if (
|
|
24980
|
+
const sep13 = par.sepByStep.get(step.stepNumber);
|
|
24981
|
+
if (sep13 !== void 0) lines.push(` and ${escapeLabel(sep13)}`);
|
|
24941
24982
|
}
|
|
24942
24983
|
switch (step.type) {
|
|
24943
24984
|
case "local":
|
|
@@ -25987,7 +26028,7 @@ function mapSpecReferences(kind, spec, map) {
|
|
|
25987
26028
|
const s = spec;
|
|
25988
26029
|
return {
|
|
25989
26030
|
...s,
|
|
25990
|
-
publicInterfaces: s.publicInterfaces.map((p) => ({
|
|
26031
|
+
publicInterfaces: (s.publicInterfaces ?? []).map((p) => ({
|
|
25991
26032
|
...p,
|
|
25992
26033
|
...p.component !== void 0 ? { component: map("publicInterfaces", p.component) } : {},
|
|
25993
26034
|
...p.interface !== void 0 ? { interface: map("publicInterfaces", p.interface) } : {},
|
|
@@ -26003,8 +26044,8 @@ function mapSpecReferences(kind, spec, map) {
|
|
|
26003
26044
|
return {
|
|
26004
26045
|
...c,
|
|
26005
26046
|
subsystem: map("subsystem", c.subsystem),
|
|
26006
|
-
owns: c.owns.map((o) => map("owns", o)),
|
|
26007
|
-
dependsOn: c.dependsOn.map((d) => map("dependsOn", d)),
|
|
26047
|
+
owns: (c.owns ?? []).map((o) => map("owns", o)),
|
|
26048
|
+
dependsOn: (c.dependsOn ?? []).map((d) => map("dependsOn", d)),
|
|
26008
26049
|
...c.dispatch ? { dispatch: c.dispatch.map((b) => ({ ...b, component: map("dispatch", b.component) })) } : {},
|
|
26009
26050
|
...c.mounts ? { mounts: c.mounts.map((m) => ({ ...m, portal: map("mounts", m.portal) })) } : {}
|
|
26010
26051
|
};
|
|
@@ -26018,9 +26059,9 @@ function mapSpecReferences(kind, spec, map) {
|
|
|
26018
26059
|
return {
|
|
26019
26060
|
...impl,
|
|
26020
26061
|
contract: map("contract", impl.contract),
|
|
26021
|
-
methods: impl.methods.map((m) => ({
|
|
26062
|
+
methods: (impl.methods ?? []).map((m) => ({
|
|
26022
26063
|
...m,
|
|
26023
|
-
narrative: m.narrative.map((step) => step.targetComponent !== void 0 ? { ...step, targetComponent: map("narrative", step.targetComponent) } : step)
|
|
26064
|
+
narrative: (m.narrative ?? []).map((step) => step.targetComponent !== void 0 ? { ...step, targetComponent: map("narrative", step.targetComponent) } : step)
|
|
26024
26065
|
}))
|
|
26025
26066
|
};
|
|
26026
26067
|
}
|
|
@@ -27868,10 +27909,10 @@ var SpecWorkspace = class {
|
|
|
27868
27909
|
return this.relativizeSpec("interface", spec);
|
|
27869
27910
|
}
|
|
27870
27911
|
prepareImplementationForWrite(spec, carry = true) {
|
|
27871
|
-
const
|
|
27912
|
+
const relative19 = this.relativizeSpec("implementation", spec, carry);
|
|
27872
27913
|
const member = this.getSubprojectPrefix(spec.id);
|
|
27873
27914
|
const memberRoot = member ? this.resolveSubprojectForNamespace(member) : null;
|
|
27874
|
-
return memberRoot ? childRelativeFilePaths(
|
|
27915
|
+
return memberRoot ? childRelativeFilePaths(relative19, this.rootDir, memberRoot) : relative19;
|
|
27875
27916
|
}
|
|
27876
27917
|
prepareTypeForWrite(spec) {
|
|
27877
27918
|
return this.relativizeSpec("type", spec);
|
|
@@ -28710,7 +28751,10 @@ var SpecWorkspace = class {
|
|
|
28710
28751
|
* second delete written as 7 addresses what used to be step 8. That is why
|
|
28711
28752
|
* a delete may restate the step's `label` or `description` as a guard, and
|
|
28712
28753
|
* why labels are the reliable way to name a jump target. Inserts and
|
|
28713
|
-
* deletes relocate every jump field in the same narrative around
|
|
28754
|
+
* deletes relocate every STORED jump field in the same narrative around
|
|
28755
|
+
* them; a jump the delta itself writes is already in the numbering the
|
|
28756
|
+
* whole delta leaves behind, so none of its later steps moves it. A new
|
|
28757
|
+
* implementation method with no narrative is intent-level ([]); a
|
|
28714
28758
|
* step whose `type` changes is REBUILT for its new type, keeping its
|
|
28715
28759
|
* description and label.
|
|
28716
28760
|
* 5. UNSET the fields the delta names, at the level each `unset` sits in.
|
|
@@ -28768,16 +28812,19 @@ var SpecWorkspace = class {
|
|
|
28768
28812
|
}
|
|
28769
28813
|
const JUMP_FIELDS = ["onTrueStep", "onFalseStep", "defaultStep", "endStep", "finallyStep", "toStep"];
|
|
28770
28814
|
const JUMP_LIST_FIELDS = ["cases", "catches", "branches"];
|
|
28815
|
+
const isPinned = (step, field) => step?.[DELTA_JUMP_PINS] instanceof Set && step[DELTA_JUMP_PINS].has(field);
|
|
28816
|
+
const isPinnedEntry = (entry) => entry?.[DELTA_JUMP_PINS] === true;
|
|
28771
28817
|
const relocateJumps = (step, shiftFrom, deltaN, captureInsertTarget = false) => {
|
|
28772
28818
|
const hit = (v, isRegionTail = false) => deltaN > 0 ? captureInsertTarget && !isRegionTail ? v > shiftFrom : v >= shiftFrom : v > shiftFrom;
|
|
28773
28819
|
const out = { ...step };
|
|
28774
28820
|
for (const f of JUMP_FIELDS) {
|
|
28821
|
+
if (isPinned(step, f)) continue;
|
|
28775
28822
|
if (typeof out[f] === "number" && hit(out[f], f === "endStep")) out[f] = out[f] + deltaN;
|
|
28776
28823
|
}
|
|
28777
28824
|
for (const lf of JUMP_LIST_FIELDS) {
|
|
28778
28825
|
if (Array.isArray(out[lf])) {
|
|
28779
28826
|
out[lf] = out[lf].map(
|
|
28780
|
-
(c) => typeof c?.step === "number" && hit(c.step) ? { ...c, step: c.step + deltaN } : c
|
|
28827
|
+
(c) => !isPinnedEntry(c) && typeof c?.step === "number" && hit(c.step) ? { ...c, step: c.step + deltaN } : c
|
|
28781
28828
|
);
|
|
28782
28829
|
}
|
|
28783
28830
|
}
|
|
@@ -28785,14 +28832,39 @@ var SpecWorkspace = class {
|
|
|
28785
28832
|
};
|
|
28786
28833
|
const jumpRefsTo = (step, target) => {
|
|
28787
28834
|
const refs = [];
|
|
28788
|
-
for (const f of JUMP_FIELDS) if (step[f] === target) refs.push(f);
|
|
28835
|
+
for (const f of JUMP_FIELDS) if (step[f] === target && !isPinned(step, f)) refs.push(f);
|
|
28789
28836
|
for (const lf of JUMP_LIST_FIELDS) {
|
|
28790
28837
|
(Array.isArray(step[lf]) ? step[lf] : []).forEach((c, i) => {
|
|
28791
|
-
if (c?.step === target) refs.push(`${lf}[${i}].step`);
|
|
28838
|
+
if (c?.step === target && !isPinnedEntry(c)) refs.push(`${lf}[${i}].step`);
|
|
28792
28839
|
});
|
|
28793
28840
|
}
|
|
28794
28841
|
return refs;
|
|
28795
28842
|
};
|
|
28843
|
+
const pinDeltaJumps = (deltaStep, carried) => {
|
|
28844
|
+
const pins = new Set(carried ?? []);
|
|
28845
|
+
for (const f of JUMP_FIELDS) if (typeof deltaStep[f] === "number") pins.add(f);
|
|
28846
|
+
const out = { ...deltaStep };
|
|
28847
|
+
for (const lf of JUMP_LIST_FIELDS) {
|
|
28848
|
+
if (!Array.isArray(out[lf])) continue;
|
|
28849
|
+
out[lf] = out[lf].map((c) => c && typeof c === "object" && typeof c.step === "number" ? { ...c, [DELTA_JUMP_PINS]: true } : c);
|
|
28850
|
+
}
|
|
28851
|
+
out[DELTA_JUMP_PINS] = pins;
|
|
28852
|
+
return out;
|
|
28853
|
+
};
|
|
28854
|
+
const unpinned = (step) => {
|
|
28855
|
+
const out = { ...step };
|
|
28856
|
+
delete out[DELTA_JUMP_PINS];
|
|
28857
|
+
for (const lf of JUMP_LIST_FIELDS) {
|
|
28858
|
+
if (!Array.isArray(out[lf])) continue;
|
|
28859
|
+
out[lf] = out[lf].map((c) => {
|
|
28860
|
+
if (!c || typeof c !== "object" || !(DELTA_JUMP_PINS in c)) return c;
|
|
28861
|
+
const entry = { ...c };
|
|
28862
|
+
delete entry[DELTA_JUMP_PINS];
|
|
28863
|
+
return entry;
|
|
28864
|
+
});
|
|
28865
|
+
}
|
|
28866
|
+
return out;
|
|
28867
|
+
};
|
|
28796
28868
|
const normalizeIdentity = (k) => String(k ?? "").toLowerCase().replace(/[_\-\s]/g, "");
|
|
28797
28869
|
const unsetFieldsOf = (item) => Array.isArray(item?.unset) ? item.unset.filter((f) => typeof f === "string") : [];
|
|
28798
28870
|
const withUnset = (merged, deltaItem) => {
|
|
@@ -28972,21 +29044,24 @@ var SpecWorkspace = class {
|
|
|
28972
29044
|
capture
|
|
28973
29045
|
));
|
|
28974
29046
|
const { action, remove: remove2, captureJumps, ...cleanStep } = deltaStep;
|
|
28975
|
-
steps.push(withUnset(cleanStep, deltaStep));
|
|
29047
|
+
steps.push(withUnset(pinDeltaJumps(cleanStep), deltaStep));
|
|
28976
29048
|
} else {
|
|
28977
29049
|
const idx = steps.findIndex((s) => s.stepNumber === stepNum);
|
|
28978
29050
|
if (idx !== -1) {
|
|
28979
|
-
const { action, remove: remove2, ...
|
|
29051
|
+
const { action, remove: remove2, ...rawStep } = deltaStep;
|
|
28980
29052
|
const stored = steps[idx];
|
|
29053
|
+
const cleanStep = pinDeltaJumps(rawStep, stored[DELTA_JUMP_PINS]);
|
|
28981
29054
|
const merged = withUnset(retargetByLabel(mergeElement(stored, cleanStep), cleanStep), deltaStep);
|
|
28982
|
-
|
|
29055
|
+
const rebuilt = rebuildOnRetype(stored, rawStep, merged, methodName);
|
|
29056
|
+
rebuilt[DELTA_JUMP_PINS] = cleanStep[DELTA_JUMP_PINS];
|
|
29057
|
+
steps[idx] = rebuilt;
|
|
28983
29058
|
} else {
|
|
28984
29059
|
const { action, remove: remove2, ...cleanStep } = deltaStep;
|
|
28985
|
-
steps.push(withUnset(cleanStep, deltaStep));
|
|
29060
|
+
steps.push(withUnset(pinDeltaJumps(cleanStep), deltaStep));
|
|
28986
29061
|
}
|
|
28987
29062
|
}
|
|
28988
29063
|
}
|
|
28989
|
-
return steps.sort((a, b) => a.stepNumber - b.stepNumber);
|
|
29064
|
+
return steps.map(unpinned).sort((a, b) => a.stepNumber - b.stepNumber);
|
|
28990
29065
|
};
|
|
28991
29066
|
const isPlainObject2 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
28992
29067
|
const mergeExt = (existing, delta2) => {
|
|
@@ -29009,6 +29084,11 @@ var SpecWorkspace = class {
|
|
|
29009
29084
|
} else {
|
|
29010
29085
|
const existingMethod = merged[idx];
|
|
29011
29086
|
let narrative = existingMethod.narrative ? [...existingMethod.narrative] : [];
|
|
29087
|
+
if (deltaMethod.narrative !== void 0 && deltaMethod.narrative !== null && !Array.isArray(deltaMethod.narrative)) {
|
|
29088
|
+
throw new Error(
|
|
29089
|
+
`Refusing to update implementation method "${String(deltaMethod.name)}": its "narrative" must be a list of step deltas, got ${JSON.stringify(deltaMethod.narrative)}.`
|
|
29090
|
+
);
|
|
29091
|
+
}
|
|
29012
29092
|
if (Array.isArray(deltaMethod.narrative)) {
|
|
29013
29093
|
narrative = deltaMethod.narrative.length === 0 ? [] : mergeNarrative(String(deltaMethod.name), narrative, deltaMethod.narrative);
|
|
29014
29094
|
}
|
|
@@ -29021,11 +29101,21 @@ var SpecWorkspace = class {
|
|
|
29021
29101
|
}
|
|
29022
29102
|
} else {
|
|
29023
29103
|
assertUnmatchedIsAnAddition(deltaMethod, deltaMethod.name, merged.map((m) => m.name), "method");
|
|
29024
|
-
merged.push(withUnset(deltaMethod, deltaMethod));
|
|
29104
|
+
merged.push(withUnset(newMethodOf(deltaMethod), deltaMethod));
|
|
29025
29105
|
}
|
|
29026
29106
|
}
|
|
29027
29107
|
return merged;
|
|
29028
29108
|
};
|
|
29109
|
+
const newMethodOf = (deltaMethod) => {
|
|
29110
|
+
if (kind !== "implementation") return deltaMethod;
|
|
29111
|
+
if (deltaMethod.narrative === void 0 || deltaMethod.narrative === null) return { ...deltaMethod, narrative: [] };
|
|
29112
|
+
if (!Array.isArray(deltaMethod.narrative)) {
|
|
29113
|
+
throw new Error(
|
|
29114
|
+
`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.`
|
|
29115
|
+
);
|
|
29116
|
+
}
|
|
29117
|
+
return deltaMethod;
|
|
29118
|
+
};
|
|
29029
29119
|
const mergeNamedArray = (existing, delta2) => {
|
|
29030
29120
|
const merged = [...existing];
|
|
29031
29121
|
for (const deltaItem of delta2) {
|
|
@@ -29203,6 +29293,12 @@ var SpecWorkspace = class {
|
|
|
29203
29293
|
const qualifiedDelta = qualifyDeltaRefs(mergeableDelta);
|
|
29204
29294
|
const mergedResult = mergeDelta(result, qualifiedDelta);
|
|
29205
29295
|
for (const field of unsetFields) delete mergedResult[field];
|
|
29296
|
+
const withDefaults = deltaSchema.safeParse(mergedResult);
|
|
29297
|
+
if (withDefaults.success) {
|
|
29298
|
+
for (const [key, value] of Object.entries(withDefaults.data)) {
|
|
29299
|
+
if (!(key in mergedResult)) mergedResult[key] = value;
|
|
29300
|
+
}
|
|
29301
|
+
}
|
|
29206
29302
|
if (kind === "implementation" && Array.isArray(mergedResult.methods)) {
|
|
29207
29303
|
const labelErrors = [];
|
|
29208
29304
|
for (const m of mergedResult.methods) {
|
|
@@ -29723,6 +29819,7 @@ var PROSE_FIELDS = /* @__PURE__ */ new Set([
|
|
|
29723
29819
|
"note",
|
|
29724
29820
|
"caller"
|
|
29725
29821
|
]);
|
|
29822
|
+
var DELTA_JUMP_PINS = /* @__PURE__ */ Symbol("jumps written by this delta");
|
|
29726
29823
|
function cloneSpec(spec) {
|
|
29727
29824
|
return JSON.parse(JSON.stringify(spec));
|
|
29728
29825
|
}
|
|
@@ -32271,17 +32368,17 @@ function memberHop(agentId) {
|
|
|
32271
32368
|
const alias = agentId.slice(0, at);
|
|
32272
32369
|
const member = graph().nodes.find((n) => n.parent === "" && n.mountAlias === alias);
|
|
32273
32370
|
if (!member) return null;
|
|
32274
|
-
const
|
|
32275
|
-
return { alias, rest: agentId.slice(at + 2), directory: member.directory, relative:
|
|
32371
|
+
const relative19 = path26.relative(getProjectRoot(), member.directory).split(path26.sep).join("/");
|
|
32372
|
+
return { alias, rest: agentId.slice(at + 2), directory: member.directory, relative: relative19 };
|
|
32276
32373
|
}
|
|
32277
|
-
function throughMount(brief, alias,
|
|
32278
|
-
const under = (p) => path26.posix.join(
|
|
32374
|
+
function throughMount(brief, alias, relative19) {
|
|
32375
|
+
const under = (p) => path26.posix.join(relative19, p);
|
|
32279
32376
|
return {
|
|
32280
32377
|
...brief,
|
|
32281
32378
|
agentId: `${alias}::${brief.agentId}`,
|
|
32282
32379
|
ownedPaths: brief.ownedPaths.map(under),
|
|
32283
32380
|
...brief.readPaths ? { readPaths: brief.readPaths.map(under) } : {},
|
|
32284
|
-
root: brief.root ? under(brief.root) :
|
|
32381
|
+
root: brief.root ? under(brief.root) : relative19
|
|
32285
32382
|
};
|
|
32286
32383
|
}
|
|
32287
32384
|
function loadRegistry() {
|
|
@@ -32560,19 +32657,19 @@ function commitStagedTree(stagingDir, destDir, roots, backupExisting) {
|
|
|
32560
32657
|
const backupDir = path31.join(root, BACKUP_DIR, timestampSlug());
|
|
32561
32658
|
let backedUp = false;
|
|
32562
32659
|
for (const rootRel of roots) {
|
|
32563
|
-
const
|
|
32564
|
-
const stagedSpecDir = path31.join(stagingDir,
|
|
32660
|
+
const relative19 = rootRel === "." ? "" : rootRel;
|
|
32661
|
+
const stagedSpecDir = path31.join(stagingDir, relative19, ".wai");
|
|
32565
32662
|
if (!fs17.existsSync(stagedSpecDir)) continue;
|
|
32566
|
-
const liveSpecDir = liveSpecDirFor(path31.join(root,
|
|
32663
|
+
const liveSpecDir = liveSpecDirFor(path31.join(root, relative19));
|
|
32567
32664
|
if (backupExisting && liveSpecDir && fs17.existsSync(liveSpecDir)) {
|
|
32568
|
-
const backupTarget = path31.join(backupDir,
|
|
32665
|
+
const backupTarget = path31.join(backupDir, relative19, path31.basename(liveSpecDir));
|
|
32569
32666
|
fs17.mkdirSync(path31.dirname(backupTarget), { recursive: true });
|
|
32570
32667
|
fs17.renameSync(liveSpecDir, backupTarget);
|
|
32571
32668
|
backedUp = true;
|
|
32572
32669
|
} else if (liveSpecDir && fs17.existsSync(liveSpecDir)) {
|
|
32573
32670
|
fs17.rmSync(liveSpecDir, { recursive: true, force: true });
|
|
32574
32671
|
}
|
|
32575
|
-
const target = path31.join(root,
|
|
32672
|
+
const target = path31.join(root, relative19, ".wai");
|
|
32576
32673
|
fs17.mkdirSync(path31.dirname(target), { recursive: true });
|
|
32577
32674
|
if (fs17.existsSync(target)) fs17.rmSync(target, { recursive: true, force: true });
|
|
32578
32675
|
fs17.renameSync(stagedSpecDir, target);
|
|
@@ -33155,7 +33252,7 @@ var CONTEXT_PATHS = {
|
|
|
33155
33252
|
waironGuideMd: () => contextDir("wairon-guide.md")
|
|
33156
33253
|
};
|
|
33157
33254
|
function hasContext() {
|
|
33158
|
-
return pathExists(CONTEXT_PATHS.
|
|
33255
|
+
return pathExists(CONTEXT_PATHS.dir());
|
|
33159
33256
|
}
|
|
33160
33257
|
function readProjectContext() {
|
|
33161
33258
|
return readFileOrNull(CONTEXT_PATHS.projectMd());
|
|
@@ -38626,7 +38723,7 @@ ${renderChangeReport(report2)}`,
|
|
|
38626
38723
|
contract: import_zod11.z.string().describe("The L3 Interface contract ID this implements"),
|
|
38627
38724
|
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"),
|
|
38628
38725
|
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"),
|
|
38629
|
-
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.`),
|
|
38726
|
+
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.`),
|
|
38630
38727
|
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"),
|
|
38631
38728
|
detail: detailEnum.optional().describe("Spec-level narrative detail default for all methods"),
|
|
38632
38729
|
conformance: conformanceEnum.optional().describe("Spec-level conformance tier default: declared | anchored | off (omitted = stereotype default: Portal \u2192 anchored, else declared)"),
|
|
@@ -38905,7 +39002,7 @@ ${testsBlock}` : ""}`, deletion);
|
|
|
38905
39002
|
inputSchema: {
|
|
38906
39003
|
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})"),
|
|
38907
39004
|
id: import_zod11.z.string().describe("The ID of the spec to update (namespaced if needed)"),
|
|
38908
|
-
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.`),
|
|
39005
|
+
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.`),
|
|
38909
39006
|
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.")
|
|
38910
39007
|
},
|
|
38911
39008
|
outputSchema: specChangeReportOutput
|
|
@@ -39244,6 +39341,7 @@ function loadProjectConfig3() {
|
|
|
39244
39341
|
SystemPublicInterfaceSchema,
|
|
39245
39342
|
SystemSpecSchema,
|
|
39246
39343
|
TargetConfigSchema,
|
|
39344
|
+
TechnologySchema,
|
|
39247
39345
|
TemplateNotFoundError,
|
|
39248
39346
|
TemplateSchema,
|
|
39249
39347
|
TopologyConfigSchema,
|
|
@@ -39271,6 +39369,7 @@ function loadProjectConfig3() {
|
|
|
39271
39369
|
assertProjectInitialized,
|
|
39272
39370
|
backendCommand,
|
|
39273
39371
|
backfillChainedSubprojectConfigs,
|
|
39372
|
+
backupBeside,
|
|
39274
39373
|
buildCanvasDataModel,
|
|
39275
39374
|
buildProjectGraph,
|
|
39276
39375
|
buildRuleContext,
|
|
@@ -39370,6 +39469,7 @@ function loadProjectConfig3() {
|
|
|
39370
39469
|
internalizeMember,
|
|
39371
39470
|
isDraftSubsystem,
|
|
39372
39471
|
isLogic,
|
|
39472
|
+
isOutsideRoot,
|
|
39373
39473
|
isOwnComponentEntry,
|
|
39374
39474
|
isPattern,
|
|
39375
39475
|
isProjectInitialized,
|
|
@@ -39499,6 +39599,8 @@ function loadProjectConfig3() {
|
|
|
39499
39599
|
stepFieldsFor,
|
|
39500
39600
|
stepGraph,
|
|
39501
39601
|
syncContextFiles,
|
|
39602
|
+
technologyName,
|
|
39603
|
+
technologyTokens,
|
|
39502
39604
|
toMarkdown,
|
|
39503
39605
|
typeBindingOf,
|
|
39504
39606
|
typeGenericParameters,
|