@wairon/cli 5.1.1-dev.36 → 5.1.1-dev.38
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 +486 -122
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +181 -15
- 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.38";
|
|
69
69
|
GITHUB_REPO = "SYW-Apps/Waffle-AIron";
|
|
70
70
|
SUPPORTED_ALIASES = ["wai"];
|
|
71
71
|
SCAN_EXCLUDE_DIRS = /* @__PURE__ */ new Set([
|
|
@@ -1536,7 +1536,13 @@ var init_specs = __esm({
|
|
|
1536
1536
|
GuaranteeSchema = import_zod7.z.string().min(1);
|
|
1537
1537
|
MethodParamSchema = import_zod7.z.object({
|
|
1538
1538
|
name: import_zod7.z.string(),
|
|
1539
|
-
/**
|
|
1539
|
+
/**
|
|
1540
|
+
* A primitive/builtin or a defined type id (qualified across subsystems, e.g.
|
|
1541
|
+
* "billing.Invoice"), on its own or inside a generic, an array or a UNION:
|
|
1542
|
+
* `Invoice | null`, `Promise<Invoice | null>`, `Invoice[] | null`. Every
|
|
1543
|
+
* identifier the string names has to resolve — see the grammar on
|
|
1544
|
+
* src/models/type-references.ts.
|
|
1545
|
+
*/
|
|
1540
1546
|
type: import_zod7.z.string(),
|
|
1541
1547
|
description: import_zod7.z.string().optional(),
|
|
1542
1548
|
optional: import_zod7.z.boolean().optional()
|
|
@@ -1554,8 +1560,9 @@ var init_specs = __esm({
|
|
|
1554
1560
|
description: import_zod7.z.string(),
|
|
1555
1561
|
signature: import_zod7.z.string(),
|
|
1556
1562
|
// e.g. "save(key: string, data: Buffer): Promise<void>"
|
|
1563
|
+
// e.g. "Promise<void>", or a union: "Invoice | null" — the commonest shape in
|
|
1564
|
+
// any real tree. See the grammar on src/models/type-references.ts.
|
|
1557
1565
|
returns: import_zod7.z.string(),
|
|
1558
|
-
// e.g. "Promise<void>"
|
|
1559
1566
|
/** Structured parameters (authoritative for type checking when present). */
|
|
1560
1567
|
params: import_zod7.z.array(MethodParamSchema).optional(),
|
|
1561
1568
|
/** Concrete wire binding for this method when its component is a Portal (set via sdd_set_endpoints). */
|
|
@@ -1824,8 +1831,10 @@ var init_specs = __esm({
|
|
|
1824
1831
|
TypeKindSchema = import_zod7.z.enum(["entity", "value-object"]);
|
|
1825
1832
|
TypeFieldSchema = import_zod7.z.object({
|
|
1826
1833
|
name: import_zod7.z.string(),
|
|
1834
|
+
// A primitive, or another type id (qualified across subsystems, e.g.
|
|
1835
|
+
// "billing.Invoice") — on its own or inside a generic, an array or a union
|
|
1836
|
+
// ("Invoice[] | null"). See the grammar on src/models/type-references.ts.
|
|
1827
1837
|
type: import_zod7.z.string(),
|
|
1828
|
-
// a primitive, or another type id (qualified across subsystems, e.g. "billing.Invoice")
|
|
1829
1838
|
description: import_zod7.z.string().optional(),
|
|
1830
1839
|
optional: import_zod7.z.boolean().default(false),
|
|
1831
1840
|
/**
|
|
@@ -23209,6 +23218,7 @@ __export(specs_exports, {
|
|
|
23209
23218
|
getSubprojectPrefix: () => getSubprojectPrefix,
|
|
23210
23219
|
getSubsystemPath: () => getSubsystemPath,
|
|
23211
23220
|
getTypePath: () => getTypePath,
|
|
23221
|
+
ineffectiveDeltaPaths: () => ineffectiveDeltaPaths,
|
|
23212
23222
|
inspectChainedRoots: () => inspectChainedRoots,
|
|
23213
23223
|
invalidateSpecCache: () => invalidateSpecCache,
|
|
23214
23224
|
listChainedRoots: () => listChainedRoots,
|
|
@@ -23727,6 +23737,117 @@ function specChanges(before, after, prefix = "") {
|
|
|
23727
23737
|
if (JSON.stringify(before) === JSON.stringify(after)) return [];
|
|
23728
23738
|
return [{ path: prefix, change: "set", before: summarizeValue(before), after: summarizeValue(after) }];
|
|
23729
23739
|
}
|
|
23740
|
+
function identityFieldsOf(field) {
|
|
23741
|
+
switch (field) {
|
|
23742
|
+
case "dispatch":
|
|
23743
|
+
return ["capability"];
|
|
23744
|
+
case "lifecycle":
|
|
23745
|
+
return ["phase", "component", "method"];
|
|
23746
|
+
case "emits":
|
|
23747
|
+
case "subscribesTo":
|
|
23748
|
+
return ["topic", "event"];
|
|
23749
|
+
case "trustedLinks":
|
|
23750
|
+
return ["subsystem"];
|
|
23751
|
+
case "allow":
|
|
23752
|
+
case "findings":
|
|
23753
|
+
return ["code"];
|
|
23754
|
+
case "invariants":
|
|
23755
|
+
case "patterns":
|
|
23756
|
+
return ["id"];
|
|
23757
|
+
case "boundaries":
|
|
23758
|
+
return ["name"];
|
|
23759
|
+
case "globalRequirements":
|
|
23760
|
+
return ["description"];
|
|
23761
|
+
case "databases":
|
|
23762
|
+
return ["id", "name"];
|
|
23763
|
+
case "cases":
|
|
23764
|
+
return ["value"];
|
|
23765
|
+
case "catches":
|
|
23766
|
+
return ["error"];
|
|
23767
|
+
case "narrative":
|
|
23768
|
+
return ["stepNumber"];
|
|
23769
|
+
default:
|
|
23770
|
+
return ["name", "id"];
|
|
23771
|
+
}
|
|
23772
|
+
}
|
|
23773
|
+
function carriesEditMarker(item) {
|
|
23774
|
+
if (item === null || typeof item !== "object") return false;
|
|
23775
|
+
const o = item;
|
|
23776
|
+
return o.action !== void 0 || o.remove !== void 0;
|
|
23777
|
+
}
|
|
23778
|
+
function ineffectiveDeltaPaths(delta, merged, stored) {
|
|
23779
|
+
const out = [];
|
|
23780
|
+
const isPlain = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
23781
|
+
const same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
|
23782
|
+
const at = (prefix, key) => prefix ? `${prefix}.${key}` : key;
|
|
23783
|
+
const say = (path70, why) => {
|
|
23784
|
+
out.push(`${path70} \u2014 ${why}`);
|
|
23785
|
+
};
|
|
23786
|
+
const walkValue = (d, m, s, path70, field) => {
|
|
23787
|
+
if (Array.isArray(d)) {
|
|
23788
|
+
if (!Array.isArray(m)) {
|
|
23789
|
+
say(path70, "the level does not have this field, so the write dropped it");
|
|
23790
|
+
return;
|
|
23791
|
+
}
|
|
23792
|
+
if (field === "narrative" && d.some(carriesEditMarker)) return;
|
|
23793
|
+
for (const element of d) {
|
|
23794
|
+
if (carriesEditMarker(element)) continue;
|
|
23795
|
+
const key = reportIdentityOf(field, element);
|
|
23796
|
+
if (key === null) {
|
|
23797
|
+
if (!same(m, d)) say(path70, "the level does not keep this value, so the write dropped it");
|
|
23798
|
+
else if (same(s, d)) say(path70, "the stored spec already held this value");
|
|
23799
|
+
return;
|
|
23800
|
+
}
|
|
23801
|
+
const mMatch = m.find((i) => reportIdentityOf(field, i) === key);
|
|
23802
|
+
const sMatch = Array.isArray(s) ? s.find((i) => reportIdentityOf(field, i) === key) : void 0;
|
|
23803
|
+
if (mMatch === void 0) {
|
|
23804
|
+
say(at(path70, key), "the write did not store this element");
|
|
23805
|
+
continue;
|
|
23806
|
+
}
|
|
23807
|
+
walk2(element, mMatch, sMatch, at(path70, key), field);
|
|
23808
|
+
}
|
|
23809
|
+
return;
|
|
23810
|
+
}
|
|
23811
|
+
if (isPlain(d)) {
|
|
23812
|
+
if (!isPlain(m)) {
|
|
23813
|
+
say(path70, "the level does not have this field, so the write dropped it");
|
|
23814
|
+
return;
|
|
23815
|
+
}
|
|
23816
|
+
walk2(d, m, s, path70, field);
|
|
23817
|
+
return;
|
|
23818
|
+
}
|
|
23819
|
+
if (!same(m, d)) say(path70, "the write did not store it \u2014 the level's schema does not keep this value");
|
|
23820
|
+
else if (same(s, d)) say(path70, "the stored spec already held this value");
|
|
23821
|
+
};
|
|
23822
|
+
function walk2(d, m, s, path70, container) {
|
|
23823
|
+
for (const [key, value] of Object.entries(d)) {
|
|
23824
|
+
if (DELTA_VERBS.has(key) || JUMP_LABEL_TWINS.has(key)) continue;
|
|
23825
|
+
if (key === "label" && LABEL_TWIN_CONTAINERS.has(container)) continue;
|
|
23826
|
+
if (identityFieldsOf(container).includes(key)) continue;
|
|
23827
|
+
if (key === "unset") {
|
|
23828
|
+
for (const name of Array.isArray(value) ? value : []) {
|
|
23829
|
+
if (typeof name !== "string") continue;
|
|
23830
|
+
const had = isPlain(s) && s[name] !== void 0;
|
|
23831
|
+
const still = isPlain(m) && m[name] !== void 0;
|
|
23832
|
+
if (!had) say(at(path70, name), "unset named a field the stored spec did not have");
|
|
23833
|
+
else if (still) say(at(path70, name), "unset did not remove it");
|
|
23834
|
+
}
|
|
23835
|
+
continue;
|
|
23836
|
+
}
|
|
23837
|
+
if (value === void 0 || value === null) continue;
|
|
23838
|
+
const here = at(path70, key);
|
|
23839
|
+
const mValue = isPlain(m) ? m[key] : void 0;
|
|
23840
|
+
const sValue = isPlain(s) ? s[key] : void 0;
|
|
23841
|
+
if (mValue === void 0) {
|
|
23842
|
+
say(here, "the level does not have this field, so the write dropped it");
|
|
23843
|
+
continue;
|
|
23844
|
+
}
|
|
23845
|
+
walkValue(value, mValue, sValue, here, key);
|
|
23846
|
+
}
|
|
23847
|
+
}
|
|
23848
|
+
walk2(delta, merged, stored, "", "");
|
|
23849
|
+
return out;
|
|
23850
|
+
}
|
|
23730
23851
|
function workspaceFor(rootDir) {
|
|
23731
23852
|
const key = path23.resolve(rootDir);
|
|
23732
23853
|
let ws = workspaces.get(key);
|
|
@@ -23952,10 +24073,10 @@ function restoreSpecFiles(snapshot) {
|
|
|
23952
24073
|
function findLegacySpecFiles() {
|
|
23953
24074
|
return current().findLegacySpecFiles();
|
|
23954
24075
|
}
|
|
23955
|
-
function updateSpec(kind, id, delta, hooks) {
|
|
23956
|
-
return current().updateSpec(kind, id, delta, hooks);
|
|
24076
|
+
function updateSpec(kind, id, delta, hooks, dryRun) {
|
|
24077
|
+
return current().updateSpec(kind, id, delta, hooks, dryRun);
|
|
23957
24078
|
}
|
|
23958
|
-
var fs17, path23, NO_ROOT_SUBSYSTEMS, SIGNATURE_TTL_MS, SpecWorkspace, workspaces;
|
|
24079
|
+
var fs17, path23, NO_ROOT_SUBSYSTEMS, SIGNATURE_TTL_MS, DELTA_VERBS, JUMP_LABEL_TWINS, LABEL_TWIN_CONTAINERS, SpecWorkspace, workspaces;
|
|
23959
24080
|
var init_specs2 = __esm({
|
|
23960
24081
|
"src/core/specs.ts"() {
|
|
23961
24082
|
"use strict";
|
|
@@ -23973,6 +24094,16 @@ var init_specs2 = __esm({
|
|
|
23973
24094
|
init_diagram();
|
|
23974
24095
|
NO_ROOT_SUBSYSTEMS = /* @__PURE__ */ new Set();
|
|
23975
24096
|
SIGNATURE_TTL_MS = 2e3;
|
|
24097
|
+
DELTA_VERBS = /* @__PURE__ */ new Set(["action", "remove", "captureJumps"]);
|
|
24098
|
+
JUMP_LABEL_TWINS = /* @__PURE__ */ new Set([
|
|
24099
|
+
"onTrueLabel",
|
|
24100
|
+
"onFalseLabel",
|
|
24101
|
+
"defaultLabel",
|
|
24102
|
+
"endLabel",
|
|
24103
|
+
"finallyLabel",
|
|
24104
|
+
"toLabel"
|
|
24105
|
+
]);
|
|
24106
|
+
LABEL_TWIN_CONTAINERS = /* @__PURE__ */ new Set(["cases", "catches", "branches"]);
|
|
23976
24107
|
SpecWorkspace = class {
|
|
23977
24108
|
constructor(rootDir) {
|
|
23978
24109
|
this.cachedIndex = null;
|
|
@@ -24688,6 +24819,9 @@ var init_specs2 = __esm({
|
|
|
24688
24819
|
const existing = this.loadSubsystemSpec(spec.id);
|
|
24689
24820
|
if (existing) {
|
|
24690
24821
|
specToWrite.createdAt = existing.createdAt;
|
|
24822
|
+
if (!opts?.allowStatusDemotion && existing.status && (!spec.status || spec.status === "draft")) {
|
|
24823
|
+
specToWrite.status = existing.status;
|
|
24824
|
+
}
|
|
24691
24825
|
}
|
|
24692
24826
|
specToWrite.updatedAt = opts?.preserveUpdatedAt && existing?.updatedAt ? existing.updatedAt : (/* @__PURE__ */ new Date()).toISOString();
|
|
24693
24827
|
writeYamlFile(p, parseOrThrow(SubsystemSpecSchema, specToWrite, "subsystem", spec.id));
|
|
@@ -25236,15 +25370,22 @@ var init_specs2 = __esm({
|
|
|
25236
25370
|
* carry, and a label in the delta retargets the stored numeric jump it
|
|
25237
25371
|
* twins. An unresolved reference aborts the whole update.
|
|
25238
25372
|
* 7. COMPARE with what is stored, both read through the level's canonical
|
|
25239
|
-
* schema. Equal means nothing is written and `written` is false.
|
|
25240
|
-
*
|
|
25373
|
+
* schema. Equal means nothing is written and `written` is false. The same
|
|
25374
|
+
* comparison names every delta path the write did NOT act on — one the
|
|
25375
|
+
* schema drops at a depth the permissive delta cannot refuse, one the
|
|
25376
|
+
* stored spec already held, an `unset` that removed nothing.
|
|
25377
|
+
* 8. GATE the merged spec through the caller's hook. A DRY RUN stops here
|
|
25378
|
+
* and answers with the report it would have produced — after the gate, so
|
|
25379
|
+
* it accounts for a write that would really be accepted, and before the
|
|
25380
|
+
* stamp, so not one byte of the stored file moves.
|
|
25381
|
+
* 9. STAMP updatedAt and SAVE.
|
|
25241
25382
|
*
|
|
25242
25383
|
* Returns the change report, whose `notices` carry the non-fatal effects (an
|
|
25243
25384
|
* insert whose position was a jump target, so the relocated jumps now bypass
|
|
25244
25385
|
* the inserted step; the fields a retype dropped) — empty when the merge had
|
|
25245
25386
|
* nothing to say.
|
|
25246
25387
|
*/
|
|
25247
|
-
updateSpec(kind, id, delta, hooks) {
|
|
25388
|
+
updateSpec(kind, id, delta, hooks, dryRun = false) {
|
|
25248
25389
|
const notices = [];
|
|
25249
25390
|
const result = (() => {
|
|
25250
25391
|
switch (kind) {
|
|
@@ -25723,7 +25864,8 @@ var init_specs2 = __esm({
|
|
|
25723
25864
|
const unsetFields = Array.isArray(delta.unset) ? delta.unset.filter((f) => typeof f === "string") : [];
|
|
25724
25865
|
const { unset: _unset, ...mergeableDelta } = delta;
|
|
25725
25866
|
const storedBefore = JSON.parse(JSON.stringify(result));
|
|
25726
|
-
const
|
|
25867
|
+
const qualifiedDelta = qualifyDeltaRefs(mergeableDelta);
|
|
25868
|
+
const mergedResult = mergeDelta(result, qualifiedDelta);
|
|
25727
25869
|
for (const field of unsetFields) delete mergedResult[field];
|
|
25728
25870
|
if (kind === "implementation" && Array.isArray(mergedResult.methods)) {
|
|
25729
25871
|
const labelErrors = [];
|
|
@@ -25741,20 +25883,41 @@ var init_specs2 = __esm({
|
|
|
25741
25883
|
delete value.updatedAt;
|
|
25742
25884
|
return value;
|
|
25743
25885
|
};
|
|
25744
|
-
const
|
|
25886
|
+
const canonicalStored = canonical(storedBefore);
|
|
25887
|
+
const canonicalMerged = canonical(mergedResult);
|
|
25888
|
+
const changes = specChanges(canonicalStored, canonicalMerged);
|
|
25889
|
+
const ineffective = ineffectiveDeltaPaths(
|
|
25890
|
+
{ ...qualifiedDelta, ...unsetFields.length ? { unset: unsetFields } : {} },
|
|
25891
|
+
canonicalMerged,
|
|
25892
|
+
canonicalStored
|
|
25893
|
+
);
|
|
25745
25894
|
if (changes.length === 0) {
|
|
25746
25895
|
return {
|
|
25747
25896
|
kind,
|
|
25748
25897
|
id,
|
|
25749
25898
|
written: false,
|
|
25899
|
+
dryRun,
|
|
25750
25900
|
changes,
|
|
25901
|
+
ineffective,
|
|
25751
25902
|
notices,
|
|
25752
|
-
summary: `No change to ${kind} "${id}" \u2014 the delta matches what is stored, so nothing was written.`
|
|
25903
|
+
summary: `No change to ${kind} "${id}" \u2014 the delta matches what is stored, so nothing ${dryRun ? "would be" : "was"} written.`
|
|
25753
25904
|
};
|
|
25754
25905
|
}
|
|
25755
|
-
mergedResult.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
25756
25906
|
const gateNotices = hooks?.gate?.(kind, mergedResult);
|
|
25757
25907
|
if (gateNotices?.length) notices.push(...gateNotices);
|
|
25908
|
+
if (dryRun) {
|
|
25909
|
+
return {
|
|
25910
|
+
kind,
|
|
25911
|
+
id,
|
|
25912
|
+
written: false,
|
|
25913
|
+
dryRun: true,
|
|
25914
|
+
changes,
|
|
25915
|
+
ineffective,
|
|
25916
|
+
notices,
|
|
25917
|
+
summary: `Dry run on ${kind} "${id}": ${changes.length} change${changes.length === 1 ? "" : "s"} would be made. Nothing was written.`
|
|
25918
|
+
};
|
|
25919
|
+
}
|
|
25920
|
+
mergedResult.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
25758
25921
|
const opts = {
|
|
25759
25922
|
allowStatusDemotion: Object.prototype.hasOwnProperty.call(delta, "status")
|
|
25760
25923
|
};
|
|
@@ -25763,7 +25926,7 @@ var init_specs2 = __esm({
|
|
|
25763
25926
|
this.saveSystemSpec(mergedResult);
|
|
25764
25927
|
break;
|
|
25765
25928
|
case "subsystem":
|
|
25766
|
-
this.saveSubsystemSpec(mergedResult);
|
|
25929
|
+
this.saveSubsystemSpec(mergedResult, opts);
|
|
25767
25930
|
break;
|
|
25768
25931
|
case "component":
|
|
25769
25932
|
notices.push(...this.saveComponentSpec(mergedResult, opts));
|
|
@@ -25782,7 +25945,9 @@ var init_specs2 = __esm({
|
|
|
25782
25945
|
kind,
|
|
25783
25946
|
id,
|
|
25784
25947
|
written: true,
|
|
25948
|
+
dryRun: false,
|
|
25785
25949
|
changes,
|
|
25950
|
+
ineffective,
|
|
25786
25951
|
notices,
|
|
25787
25952
|
summary: `Updated ${kind} "${id}": ${changes.length} change${changes.length === 1 ? "" : "s"}.`
|
|
25788
25953
|
};
|
|
@@ -26652,8 +26817,8 @@ function addComponent(candidate) {
|
|
|
26652
26817
|
if (verdict.errors.length) throw new Error(formatCandidateRefusal(verdict));
|
|
26653
26818
|
return [...noticesFrom(verdict), ...saveComponentSpec(candidate)];
|
|
26654
26819
|
}
|
|
26655
|
-
function updateSpecGated(kind, id, delta) {
|
|
26656
|
-
return updateSpec(kind, id, delta, componentCandidateGate());
|
|
26820
|
+
function updateSpecGated(kind, id, delta, dryRun) {
|
|
26821
|
+
return updateSpec(kind, id, delta, componentCandidateGate(), dryRun);
|
|
26657
26822
|
}
|
|
26658
26823
|
var init_authoring = __esm({
|
|
26659
26824
|
"src/core/authoring.ts"() {
|
|
@@ -26671,6 +26836,7 @@ __export(server_exports, {
|
|
|
26671
26836
|
captureBuildStamp: () => captureBuildStamp,
|
|
26672
26837
|
createMcpServer: () => createMcpServer,
|
|
26673
26838
|
isBuildStale: () => isBuildStale,
|
|
26839
|
+
markStale: () => markStale,
|
|
26674
26840
|
startMcpServer: () => startMcpServer,
|
|
26675
26841
|
statusFamilyContext: () => statusFamilyContext
|
|
26676
26842
|
});
|
|
@@ -26751,16 +26917,32 @@ function json(value) {
|
|
|
26751
26917
|
function errText(message) {
|
|
26752
26918
|
return { content: [{ type: "text", text: `Error: ${message}` }], isError: true };
|
|
26753
26919
|
}
|
|
26920
|
+
function structured(content, data) {
|
|
26921
|
+
return { content: [{ type: "text", text: content }], structuredContent: data };
|
|
26922
|
+
}
|
|
26923
|
+
function jsonStructured(value) {
|
|
26924
|
+
return structured(JSON.stringify(value, null, 2), value);
|
|
26925
|
+
}
|
|
26754
26926
|
function renderChangeReport(report2) {
|
|
26755
|
-
const lines = [report2.summary];
|
|
26927
|
+
const lines = [report2.dryRun ? `DRY RUN \u2014 nothing was written. ${report2.summary}` : report2.summary];
|
|
26756
26928
|
for (const change of report2.changes) {
|
|
26757
26929
|
const to = change.after === void 0 ? "" : `: ${JSON.stringify(change.after)}`;
|
|
26758
26930
|
const from = change.before === void 0 ? "" : ` (was ${JSON.stringify(change.before)})`;
|
|
26759
|
-
lines.push(`- ${change.path} ${change.change}${to}${from}`);
|
|
26931
|
+
lines.push(`- ${change.path} ${report2.dryRun ? `would be ${change.change}` : change.change}${to}${from}`);
|
|
26932
|
+
}
|
|
26933
|
+
if (report2.ineffective.length) {
|
|
26934
|
+
lines.push("", "NO EFFECT:", ...report2.ineffective.map((i) => `- ${i}`));
|
|
26760
26935
|
}
|
|
26761
26936
|
if (report2.notices.length) lines.push("", "NOTICE:", ...report2.notices.map((n) => `- ${n}`));
|
|
26762
26937
|
return lines.join("\n");
|
|
26763
26938
|
}
|
|
26939
|
+
function writeReceipt(sentence, receipt) {
|
|
26940
|
+
const noticeBlock = receipt.notices.length ? `
|
|
26941
|
+
|
|
26942
|
+
NOTICE:
|
|
26943
|
+
- ${receipt.notices.join("\n- ")}` : "";
|
|
26944
|
+
return structured(`${sentence}${noticeBlock}`, receipt);
|
|
26945
|
+
}
|
|
26764
26946
|
function captureBuildStamp(entryPath) {
|
|
26765
26947
|
try {
|
|
26766
26948
|
const s = fs20.statSync(entryPath);
|
|
@@ -26778,16 +26960,19 @@ function isBuildStale(stamp) {
|
|
|
26778
26960
|
return false;
|
|
26779
26961
|
}
|
|
26780
26962
|
}
|
|
26781
|
-
function
|
|
26782
|
-
|
|
26783
|
-
const first =
|
|
26963
|
+
function markStale(result) {
|
|
26964
|
+
const marked = result.structuredContent ? { ...result, structuredContent: { ...result.structuredContent, staleServer: true } } : result;
|
|
26965
|
+
const first = marked.content?.[0];
|
|
26784
26966
|
if (first && first.type === "text") {
|
|
26785
26967
|
return {
|
|
26786
|
-
...
|
|
26787
|
-
content: [{ ...first, text: `${first.text}${STALE_SERVER_WARNING}` }, ...
|
|
26968
|
+
...marked,
|
|
26969
|
+
content: [{ ...first, text: `${first.text}${STALE_SERVER_WARNING}` }, ...marked.content.slice(1)]
|
|
26788
26970
|
};
|
|
26789
26971
|
}
|
|
26790
|
-
return
|
|
26972
|
+
return marked;
|
|
26973
|
+
}
|
|
26974
|
+
function withStaleWarning(result) {
|
|
26975
|
+
return isBuildStale(SERVER_BUILD_STAMP) ? markStale(result) : result;
|
|
26791
26976
|
}
|
|
26792
26977
|
function reg(server, name, config, cb) {
|
|
26793
26978
|
const guarded = (args) => {
|
|
@@ -26798,6 +26983,19 @@ function reg(server, name, config, cb) {
|
|
|
26798
26983
|
const strictConfig = config.inputSchema ? { ...config, inputSchema: import_zod11.z.object(config.inputSchema).strict() } : config;
|
|
26799
26984
|
server.registerTool(name, strictConfig, guarded);
|
|
26800
26985
|
}
|
|
26986
|
+
function statusForCreate(label, stated, existing) {
|
|
26987
|
+
const stored = existing?.status;
|
|
26988
|
+
const held = stored !== void 0 && STATUS_ORDER.includes(stored) ? stored : void 0;
|
|
26989
|
+
if (stated === void 0) return { status: held ?? "draft" };
|
|
26990
|
+
if (held === void 0) return { status: stated };
|
|
26991
|
+
const rank = (s) => STATUS_ORDER.indexOf(s);
|
|
26992
|
+
if (rank(stated) < rank(held)) {
|
|
26993
|
+
return {
|
|
26994
|
+
refusal: `Refusing to re-author ${label} at status "${stated}": it is stored at "${held}", and a create tool never lowers a spec's status \u2014 a restatement that reopened a frozen spec would undo a lock without saying so. Nothing was written. To reopen it deliberately, use sdd_update_spec with {"status": "${stated}"}; to keep the level it has, leave status out.`
|
|
26995
|
+
};
|
|
26996
|
+
}
|
|
26997
|
+
return { status: stated };
|
|
26998
|
+
}
|
|
26801
26999
|
function isEmptyValue(value) {
|
|
26802
27000
|
if (value === void 0 || value === null) return true;
|
|
26803
27001
|
if (Array.isArray(value)) return value.length === 0;
|
|
@@ -27095,8 +27293,8 @@ function createMcpServer(options = {}) {
|
|
|
27095
27293
|
const systemInput = {
|
|
27096
27294
|
name: import_zod11.z.string().describe("Overarching name of the project/system"),
|
|
27097
27295
|
vision: import_zod11.z.string().describe("Vision, mission, and core goals of the system"),
|
|
27098
|
-
boundaries: import_zod11.z.array(import_zod11.z.union([import_zod11.z.string(), import_zod11.z.object({ name: import_zod11.z.string(), description: import_zod11.z.string().optional() })])).optional().describe("System boundary rules or scope statements (strings or name/description objects)"),
|
|
27099
|
-
globalRequirements: import_zod11.z.array(import_zod11.z.union([import_zod11.z.string(), import_zod11.z.object({ description: import_zod11.z.string() })])).optional().describe("Global functional and non-functional requirements (strings or description objects)"),
|
|
27296
|
+
boundaries: import_zod11.z.array(import_zod11.z.union([import_zod11.z.string(), import_zod11.z.object({ name: import_zod11.z.string(), description: import_zod11.z.string().optional() }).strict()])).optional().describe("System boundary rules or scope statements (strings or name/description objects)"),
|
|
27297
|
+
globalRequirements: import_zod11.z.array(import_zod11.z.union([import_zod11.z.string(), import_zod11.z.object({ description: import_zod11.z.string() }).strict()])).optional().describe("Global functional and non-functional requirements (strings or description objects)"),
|
|
27100
27298
|
targetLanguage: import_zod11.z.string().optional().describe('Default implementation language for the system (e.g. "typescript", "rust", "python"). Subsystems may override. Enables language-aware validation.')
|
|
27101
27299
|
};
|
|
27102
27300
|
const systemInputFields = Object.keys(systemInput);
|
|
@@ -27104,8 +27302,9 @@ function createMcpServer(options = {}) {
|
|
|
27104
27302
|
server,
|
|
27105
27303
|
"sdd_initialize_system",
|
|
27106
27304
|
{
|
|
27107
|
-
description: "Initialize the L0 System Specification (system.yaml). Re-running it on an existing system RE-AUTHORS it: the fields above are replaced, and everything this tool cannot express (databases, the project gateway publicInterfaces, diagram defaults) is carried forward.",
|
|
27108
|
-
inputSchema: systemInput
|
|
27305
|
+
description: "Initialize the L0 System Specification (system.yaml). Re-running it on an existing system RE-AUTHORS it: the fields above are replaced, and everything this tool cannot express (databases, the project gateway publicInterfaces, diagram defaults) is carried forward. The answer carries a write receipt as structured content beside the sentence \u2014 what was written, and whether a system spec already existed \u2014 so a caller never has to read English to find out.",
|
|
27306
|
+
inputSchema: systemInput,
|
|
27307
|
+
outputSchema: specWriteReceiptOutput
|
|
27109
27308
|
},
|
|
27110
27309
|
({ name, vision, boundaries, globalRequirements, targetLanguage }) => {
|
|
27111
27310
|
try {
|
|
@@ -27127,11 +27326,10 @@ function createMcpServer(options = {}) {
|
|
|
27127
27326
|
next.updatedAt = now;
|
|
27128
27327
|
saveSystemSpec2(next);
|
|
27129
27328
|
const notices = existing ? rewriteNotices({ label: `System spec "${existing.name}"`, carried, cleared, removed: [] }) : [];
|
|
27130
|
-
|
|
27131
|
-
|
|
27132
|
-
|
|
27133
|
-
|
|
27134
|
-
return text(`Successfully ${existing ? "re-authored" : "initialized"} L0 System Spec for "${name}".${noticeBlock}`);
|
|
27329
|
+
return writeReceipt(
|
|
27330
|
+
`Successfully ${existing ? "re-authored" : "initialized"} L0 System Spec for "${name}".`,
|
|
27331
|
+
{ kind: "system", id: "system", name, replacedExisting: Boolean(existing), notices }
|
|
27332
|
+
);
|
|
27135
27333
|
} catch (e) {
|
|
27136
27334
|
return errText(String(e));
|
|
27137
27335
|
}
|
|
@@ -27146,7 +27344,7 @@ NOTICE:
|
|
|
27146
27344
|
details: import_zod11.z.string(),
|
|
27147
27345
|
component: import_zod11.z.string().optional().describe("The L2 component id that realizes this interface (this subsystem's published surface)"),
|
|
27148
27346
|
interface: import_zod11.z.string().optional().describe("Optional L3 interface id on that component backing this entry")
|
|
27149
|
-
})).optional().describe("Public entrypoints exposed by this subsystem, each bound to a realizing component"),
|
|
27347
|
+
}).strict()).optional().describe("Public entrypoints exposed by this subsystem, each bound to a realizing component"),
|
|
27150
27348
|
projectPath: import_zod11.z.string().optional().describe("Relative path to external project root for subsystem chaining"),
|
|
27151
27349
|
targetLanguage: import_zod11.z.string().optional().describe("Override of the system-level targetLanguage for this subsystem"),
|
|
27152
27350
|
profile: import_zod11.z.string().optional().describe("Architectural profile override for this subsystem (built-ins: backend, frontend-reactive, frontend-controller, lowlevel-os, game-ecs, realtime-embedded, plc-cyclic; extension packs may add more \u2014 unknown names get UNKNOWN_PROFILE)"),
|
|
@@ -27154,29 +27352,33 @@ NOTICE:
|
|
|
27154
27352
|
trustedLinks: import_zod11.z.array(import_zod11.z.object({
|
|
27155
27353
|
subsystem: import_zod11.z.string().describe("Peer subsystem id"),
|
|
27156
27354
|
reason: import_zod11.z.string().describe('Why the coupling is sanctioned (e.g. "dispatch latency fast lane")')
|
|
27157
|
-
})).optional().describe("Sanctioned tight couplings with peers \u2014 required to acknowledge a mutual subsystem dependency; the Adapter \u2192 published Portal shape still applies."),
|
|
27355
|
+
}).strict()).optional().describe("Sanctioned tight couplings with peers \u2014 required to acknowledge a mutual subsystem dependency; the Adapter \u2192 published Portal shape still applies."),
|
|
27158
27356
|
lifecycle: import_zod11.z.array(import_zod11.z.object({
|
|
27159
27357
|
phase: import_zod11.z.enum(["init", "shutdown", "cyclic", "interrupt", "scheduled"]).describe("Which lifecycle/execution flow this roots (cyclic = every scan/tick, interrupt = hardware/OS interrupt, scheduled = timer/cron)"),
|
|
27160
27358
|
component: import_zod11.z.string().describe("Component id whose method the runtime invokes at this phase"),
|
|
27161
27359
|
method: import_zod11.z.string().describe("Method name on that component's interface"),
|
|
27162
27360
|
description: import_zod11.z.string().optional()
|
|
27163
|
-
})).optional().describe("Declared execution-flow roots \u2014 reachability entrypoints alongside Portals/Observers. Only init flows feed the durable-Store hydration check (MISSING_HYDRATION); cyclic/interrupt/scheduled root non-request/response execution models (PLC scan, ISR, cron).")
|
|
27361
|
+
}).strict()).optional().describe("Declared execution-flow roots \u2014 reachability entrypoints alongside Portals/Observers. Only init flows feed the durable-Store hydration check (MISSING_HYDRATION); cyclic/interrupt/scheduled root non-request/response execution models (PLC scan, ISR, cron)."),
|
|
27362
|
+
status: statusInput
|
|
27164
27363
|
};
|
|
27165
27364
|
const subsystemInputFields = [...Object.keys(subsystemInput), "parentSystem"];
|
|
27166
27365
|
reg(
|
|
27167
27366
|
server,
|
|
27168
27367
|
"sdd_add_subsystem",
|
|
27169
27368
|
{
|
|
27170
|
-
description: "Add an L1 Subsystem / Service under the system boundary. publicInterfaces should bind each entry to the component that realizes it (the subsystem's published surface); if components do not exist yet, add them later with sdd_set_public_interfaces. Re-running it on an existing id RE-AUTHORS it: the fields above are replaced, lint/ext are carried forward.",
|
|
27171
|
-
inputSchema: subsystemInput
|
|
27369
|
+
description: "Add an L1 Subsystem / Service under the system boundary. publicInterfaces should bind each entry to the component that realizes it (the subsystem's published surface); if components do not exist yet, add them later with sdd_set_public_interfaces. Re-running it on an existing id RE-AUTHORS it: the fields above are replaced, lint/ext are carried forward, and the stored status is kept unless this input states a higher one. The answer carries a write receipt as structured content beside the sentence \u2014 the status written, whether a spec already held the id, and the child project directory a chained subsystem scaffolded.",
|
|
27370
|
+
inputSchema: subsystemInput,
|
|
27371
|
+
outputSchema: specWriteReceiptOutput
|
|
27172
27372
|
},
|
|
27173
|
-
({ id, name, description, publicInterfaces, projectPath, targetLanguage, profile, designDepth, trustedLinks, lifecycle }) => {
|
|
27373
|
+
({ id, name, description, publicInterfaces, projectPath, targetLanguage, profile, designDepth, trustedLinks, lifecycle, status: status2 }) => {
|
|
27174
27374
|
try {
|
|
27175
27375
|
const { loadSystemSpec: loadSystemSpec3, loadSubsystemSpec: loadSubsystemSpec2, saveSubsystemSpec: saveSubsystemSpec2 } = requireSpecs();
|
|
27176
27376
|
const system = loadSystemSpec3();
|
|
27177
27377
|
if (!system) return errText("System spec must be initialized (sdd_initialize_system) first.");
|
|
27178
27378
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
27179
27379
|
const existing = loadSubsystemSpec2(id);
|
|
27380
|
+
const resolved = statusForCreate(`subsystem "${id}"`, status2, existing);
|
|
27381
|
+
if ("refusal" in resolved) return errText(resolved.refusal);
|
|
27180
27382
|
const spec = {
|
|
27181
27383
|
id,
|
|
27182
27384
|
name,
|
|
@@ -27189,7 +27391,7 @@ NOTICE:
|
|
|
27189
27391
|
...designDepth ? { designDepth } : {},
|
|
27190
27392
|
...lifecycle ? { lifecycle } : {},
|
|
27191
27393
|
trustedLinks: trustedLinks ?? [],
|
|
27192
|
-
status:
|
|
27394
|
+
status: resolved.status,
|
|
27193
27395
|
createdAt: now,
|
|
27194
27396
|
updatedAt: now
|
|
27195
27397
|
};
|
|
@@ -27197,17 +27399,24 @@ NOTICE:
|
|
|
27197
27399
|
const cleared = clearedByOmission(existing, spec, subsystemInputFields);
|
|
27198
27400
|
if (existing) spec.createdAt = existing.createdAt;
|
|
27199
27401
|
const notices = existing ? rewriteNotices({ label: `Subsystem "${id}"`, carried: ["createdAt", ...carried], cleared, removed: [] }) : [];
|
|
27200
|
-
const
|
|
27201
|
-
|
|
27202
|
-
|
|
27203
|
-
|
|
27402
|
+
const receipt = {
|
|
27403
|
+
kind: "subsystem",
|
|
27404
|
+
id,
|
|
27405
|
+
name,
|
|
27406
|
+
replacedExisting: Boolean(existing),
|
|
27407
|
+
status: resolved.status,
|
|
27408
|
+
notices
|
|
27409
|
+
};
|
|
27204
27410
|
if (projectPath && projectPath.trim() !== "") {
|
|
27205
27411
|
const { createChainedSubsystem: createChainedSubsystem3 } = requireProvision();
|
|
27206
27412
|
createChainedSubsystem3(spec, name);
|
|
27207
|
-
return
|
|
27413
|
+
return writeReceipt(
|
|
27414
|
+
`Successfully added external subsystem "${name}" (${id}) and scaffolded its child project at ${projectPath}.`,
|
|
27415
|
+
{ ...receipt, scaffoldedProjectPath: projectPath }
|
|
27416
|
+
);
|
|
27208
27417
|
}
|
|
27209
27418
|
saveSubsystemSpec2(spec);
|
|
27210
|
-
return
|
|
27419
|
+
return writeReceipt(`Successfully added L1 Subsystem Spec "${name}" (${id}).`, receipt);
|
|
27211
27420
|
} catch (e) {
|
|
27212
27421
|
return errText(String(e));
|
|
27213
27422
|
}
|
|
@@ -27225,7 +27434,7 @@ NOTICE:
|
|
|
27225
27434
|
details: import_zod11.z.string(),
|
|
27226
27435
|
component: import_zod11.z.string().optional().describe("The L2 component id that realizes this interface"),
|
|
27227
27436
|
interface: import_zod11.z.string().optional().describe("Optional L3 interface id on that component")
|
|
27228
|
-
})).describe("The full replacement list of public interfaces for this subsystem")
|
|
27437
|
+
}).strict()).describe("The full replacement list of public interfaces for this subsystem")
|
|
27229
27438
|
}
|
|
27230
27439
|
},
|
|
27231
27440
|
({ subsystem, publicInterfaces }) => {
|
|
@@ -27382,35 +27591,40 @@ NOTICE:
|
|
|
27382
27591
|
component: import_zod11.z.string().describe("Component id serving this capability (must also appear under dependsOn/owns)"),
|
|
27383
27592
|
method: import_zod11.z.string().describe("Method name on the serving component's interface"),
|
|
27384
27593
|
description: import_zod11.z.string().optional()
|
|
27385
|
-
})).optional().describe("Portal-only: capability \u2192 component.method dispatch table for generic-handle portals. Gives the reachability walker real edges and is validated against target interfaces (UNSERVED_CAPABILITY)."),
|
|
27594
|
+
}).strict()).optional().describe("Portal-only: capability \u2192 component.method dispatch table for generic-handle portals. Gives the reachability walker real edges and is validated against target interfaces (UNSERVED_CAPABILITY)."),
|
|
27386
27595
|
durability: import_zod11.z.enum(["ram-projection", "durable", "read-through", "cache"]).optional().describe("Store-only \u2014 OMIT IT on any other componentType, where it is refused at the write (DURABILITY_ON_NON_STORE) and nothing is saved. Every Store should declare one (MISSING_DURABILITY): durable = persisted RAM projection (hydration read-back from a lifecycle init entrypoint required \u2014 MISSING_HYDRATION); read-through = persisted with no RAM copy (every read is the read-back, hydration exempt); ram-projection = rebuilt not restored; cache = evictable loss-safe memo state."),
|
|
27387
27596
|
dependencyClass: import_zod11.z.enum(["pure", "read"]).optional().describe("Orchestrator-only \u2014 OMIT IT on any other componentType, where it is refused at the write (DEPENDENCY_CLASS_ON_NON_ORCHESTRATOR) and nothing is saved. Declares what logic may depend on, enforced as a Store's durability is (DEPENDENCY_CLASS_VIOLATION): pure = depends only on pure Orchestrators (a computation over the values it is handed, e.g. an arbiter or codec); read = also on read Orchestrators, Repositories, Indexes and Adapters, never calling their write methods; unset = a workflow."),
|
|
27388
27597
|
emits: import_zod11.z.array(import_zod11.z.object({
|
|
27389
27598
|
topic: import_zod11.z.string().describe("Topic/channel name exactly as used on the bus"),
|
|
27390
27599
|
event: import_zod11.z.string().optional().describe("Optional event name within the topic (informational; pairing is by topic)"),
|
|
27391
27600
|
description: import_zod11.z.string().optional()
|
|
27392
|
-
})).optional().describe("Topics this component publishes \u2014 every emitted topic needs a subscriber somewhere in the tree (UNCONSUMED_TOPIC)."),
|
|
27601
|
+
}).strict()).optional().describe("Topics this component publishes \u2014 every emitted topic needs a subscriber somewhere in the tree (UNCONSUMED_TOPIC)."),
|
|
27393
27602
|
subscribesTo: import_zod11.z.array(import_zod11.z.object({
|
|
27394
27603
|
topic: import_zod11.z.string().describe("Topic/channel name exactly as used on the bus"),
|
|
27395
27604
|
event: import_zod11.z.string().optional(),
|
|
27396
27605
|
description: import_zod11.z.string().optional()
|
|
27397
|
-
})).optional().describe("Topics this component consumes (typical on Observers) \u2014 every subscription needs an emitter somewhere in the tree (UNSOURCED_SUBSCRIPTION)."),
|
|
27398
|
-
ext: import_zod11.z.record(import_zod11.z.unknown()).optional().describe('Opaque pack/tool extension data (namespaced keys, e.g. "mypack:priority") \u2014 preserved verbatim, never validated or interpreted by the core')
|
|
27606
|
+
}).strict()).optional().describe("Topics this component consumes (typical on Observers) \u2014 every subscription needs an emitter somewhere in the tree (UNSOURCED_SUBSCRIPTION)."),
|
|
27607
|
+
ext: import_zod11.z.record(import_zod11.z.unknown()).optional().describe('Opaque pack/tool extension data (namespaced keys, e.g. "mypack:priority") \u2014 preserved verbatim, never validated or interpreted by the core'),
|
|
27608
|
+
status: statusInput
|
|
27399
27609
|
};
|
|
27400
27610
|
const componentInputFields = Object.keys(componentInput);
|
|
27401
27611
|
reg(
|
|
27402
27612
|
server,
|
|
27403
27613
|
"sdd_add_component",
|
|
27404
27614
|
{
|
|
27405
|
-
description: `Add an L2 Component under a subsystem. componentType is a building block (Portal, Orchestrator, Supervisor, Actor, Store, Index, Query, Registry, Adapter, Observer) or the pattern Repository. Specialist and Gateway are retired (STEREOTYPE_RETIRED) and cannot be authored: logic is an Orchestrator with a dependencyClass (pure | read; unset = a workflow), and a gateway is a Portal with the gateway variant (set variant with sdd_update_spec). Patterns set "owns" (their private member blocks); all components set "dependsOn" (collaborators \u2014 facades or standalone blocks). Held/persisted state (configs, permissions, sessions, caches): model the Repository recipe \u2014 a Store + Registry (write) + Index (read), plus a Query for computed reads over the Store, owned by a Repository facade consumers depend on; a deliberately standalone Store is the sanctioned lightweight form (workflow-layer consumers + lint.allow on UNOWNED_STORE). Never hold state as fields inside an Orchestrator because a Store link was refused. Re-running it on an existing id RE-AUTHORS it: the fields above are replaced (an omitted array is CLEARED), while lint.allow, a Portal's auth, variant, patterns and externalLinks are carried forward \u2014 edit those with sdd_update_spec.`,
|
|
27406
|
-
inputSchema: componentInput
|
|
27615
|
+
description: `Add an L2 Component under a subsystem. componentType is a building block (Portal, Orchestrator, Supervisor, Actor, Store, Index, Query, Registry, Adapter, Observer) or the pattern Repository. Specialist and Gateway are retired (STEREOTYPE_RETIRED) and cannot be authored: logic is an Orchestrator with a dependencyClass (pure | read; unset = a workflow), and a gateway is a Portal with the gateway variant (set variant with sdd_update_spec). Patterns set "owns" (their private member blocks); all components set "dependsOn" (collaborators \u2014 facades or standalone blocks). Held/persisted state (configs, permissions, sessions, caches): model the Repository recipe \u2014 a Store + Registry (write) + Index (read), plus a Query for computed reads over the Store, owned by a Repository facade consumers depend on; a deliberately standalone Store is the sanctioned lightweight form (workflow-layer consumers + lint.allow on UNOWNED_STORE). Never hold state as fields inside an Orchestrator because a Store link was refused. Re-running it on an existing id RE-AUTHORS it: the fields above are replaced (an omitted array is CLEARED), while lint.allow, a Portal's auth, variant, patterns and externalLinks are carried forward \u2014 edit those with sdd_update_spec. The stored status is kept unless this input states a higher one. The answer carries a write receipt as structured content beside the sentence \u2014 the status written, whether a spec already held the id, and every gate notice the write raised, each as its own entry.`,
|
|
27616
|
+
inputSchema: componentInput,
|
|
27617
|
+
outputSchema: specWriteReceiptOutput
|
|
27407
27618
|
},
|
|
27408
|
-
({ id, name, description, subsystem, componentType, owns, dependsOn, portalType, basePath, dispatch, durability, dependencyClass, emits, subscribesTo, ext }) => {
|
|
27619
|
+
({ id, name, description, subsystem, componentType, owns, dependsOn, portalType, basePath, dispatch, durability, dependencyClass, emits, subscribesTo, ext, status: status2 }) => {
|
|
27409
27620
|
try {
|
|
27410
27621
|
const { loadSubsystemSpec: loadSubsystemSpec2, loadComponentSpec: loadComponentSpec2 } = requireSpecs();
|
|
27411
27622
|
const sub = loadSubsystemSpec2(subsystem);
|
|
27412
27623
|
if (!sub) return errText(`Parent subsystem "${subsystem}" does not exist.`);
|
|
27413
27624
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
27625
|
+
const existing = loadComponentSpec2(id);
|
|
27626
|
+
const resolved = statusForCreate(`component "${id}"`, status2, existing);
|
|
27627
|
+
if ("refusal" in resolved) return errText(resolved.refusal);
|
|
27414
27628
|
const candidate = {
|
|
27415
27629
|
id,
|
|
27416
27630
|
name,
|
|
@@ -27427,11 +27641,10 @@ NOTICE:
|
|
|
27427
27641
|
...emits ? { emits } : {},
|
|
27428
27642
|
...subscribesTo ? { subscribesTo } : {},
|
|
27429
27643
|
...ext ? { ext } : {},
|
|
27430
|
-
status:
|
|
27644
|
+
status: resolved.status,
|
|
27431
27645
|
createdAt: now,
|
|
27432
27646
|
updatedAt: now
|
|
27433
27647
|
};
|
|
27434
|
-
const existing = loadComponentSpec2(id);
|
|
27435
27648
|
const carried = carryUnexpressed(existing, candidate, componentInputFields);
|
|
27436
27649
|
const cleared = clearedByOmission(existing, candidate, componentInputFields);
|
|
27437
27650
|
if (existing) candidate.createdAt = existing.createdAt;
|
|
@@ -27444,11 +27657,10 @@ NOTICE:
|
|
|
27444
27657
|
removed: []
|
|
27445
27658
|
}));
|
|
27446
27659
|
}
|
|
27447
|
-
|
|
27448
|
-
|
|
27449
|
-
|
|
27450
|
-
|
|
27451
|
-
return text(`Successfully ${existing ? "re-authored" : "added"} L2 Component Spec "${name}" (${id}, ${componentType}).${noticeBlock}`);
|
|
27660
|
+
return writeReceipt(
|
|
27661
|
+
`Successfully ${existing ? "re-authored" : "added"} L2 Component Spec "${name}" (${id}, ${componentType}).`,
|
|
27662
|
+
{ kind: "component", id, name, replacedExisting: Boolean(existing), status: resolved.status, notices }
|
|
27663
|
+
);
|
|
27452
27664
|
} catch (e) {
|
|
27453
27665
|
return errText(String(e));
|
|
27454
27666
|
}
|
|
@@ -27458,24 +27670,26 @@ NOTICE:
|
|
|
27458
27670
|
name: import_zod11.z.string(),
|
|
27459
27671
|
description: import_zod11.z.string(),
|
|
27460
27672
|
signature: import_zod11.z.string(),
|
|
27461
|
-
returns: import_zod11.z.string(),
|
|
27673
|
+
returns: import_zod11.z.string().describe(TYPE_REF_GRAMMAR("The type the method answers with")),
|
|
27462
27674
|
params: import_zod11.z.array(import_zod11.z.object({
|
|
27463
27675
|
name: import_zod11.z.string(),
|
|
27464
|
-
type: import_zod11.z.string().describe('
|
|
27676
|
+
type: import_zod11.z.string().describe(TYPE_REF_GRAMMAR("The parameter's type")),
|
|
27465
27677
|
description: import_zod11.z.string().optional(),
|
|
27466
|
-
optional: import_zod11.z.boolean().optional()
|
|
27467
|
-
|
|
27678
|
+
optional: import_zod11.z.boolean().optional().describe(
|
|
27679
|
+
'Whether the parameter may be OMITTED by a caller. It is not nullability: a parameter that must be passed but may be passed as nothing is a required parameter whose type is a union \u2014 "ProjectConfig | null". Say whichever is true; they are different contracts.'
|
|
27680
|
+
)
|
|
27681
|
+
}).strict()).optional().describe("Structured parameters \u2014 authoritative for type checking (the prose signature becomes display-only). Strongly preferred."),
|
|
27468
27682
|
guarantees: import_zod11.z.array(import_zod11.z.string().min(1)).optional().describe("Semantic guarantees the method promises (combinable); any guarantee a narrative step asserts must be declared here. Builtin tokens: idempotent | atomic | transactional | exactly-once; extension packs may declare more (any other token is UNKNOWN_GUARANTEE)"),
|
|
27469
27683
|
effect: import_zod11.z.enum(["read", "write"]).optional().describe("State-effect direction on the component's held state \u2014 required on a durable Store's contract methods so the durability round-trip rule can pair writes with hydration read-backs"),
|
|
27470
27684
|
invokedBy: import_zod11.z.object({
|
|
27471
27685
|
kind: import_zod11.z.enum(["runtime", "external", "sibling-subsystem"]).describe("Who owns the out-of-graph invocation: runtime (timer/signal/shutdown hook), external (a system outside this project), sibling-subsystem (a modeled sibling whose edge is not narrated here)"),
|
|
27472
27686
|
caller: import_zod11.z.string().optional().describe("WHO invokes it and when, as reviewable prose \u2014 missing or placeholder-thin prose is INVOKED_BY_UNDESCRIBED")
|
|
27473
|
-
}).optional().describe("Typed acknowledgment of a real caller OUTSIDE the modeled narrative graph. Unused-detection seeds the method as an entrypoint so reachability propagates through its narrative (unlike lint.allow); a method the internal walk already reaches is flagged stale (INVOKED_BY_REDUNDANT). Prefer a `register` narrative step when the wiring is internal."),
|
|
27687
|
+
}).strict().optional().describe("Typed acknowledgment of a real caller OUTSIDE the modeled narrative graph. Unused-detection seeds the method as an entrypoint so reachability propagates through its narrative (unlike lint.allow); a method the internal walk already reaches is flagged stale (INVOKED_BY_REDUNDANT). Prefer a `register` narrative step when the wiring is internal."),
|
|
27474
27688
|
findings: import_zod11.z.array(import_zod11.z.object({
|
|
27475
27689
|
code: import_zod11.z.string().describe("UPPER_SNAKE finding code, unique within the method; a pack's codes carry the pack prefix (<PACK>_<CODE>)"),
|
|
27476
27690
|
severity: import_zod11.z.enum(["error", "warning"]).describe("Default severity, before project severity overrides and draft-context downgrades"),
|
|
27477
27691
|
summary: import_zod11.z.string().describe("One line saying what the finding means")
|
|
27478
|
-
})).optional().describe("The finding codes this method can report, each with its default severity and summary. Each declared code must be anchored in the method's source file, as a string literal or a property-access name (UNREALIZED_FINDING). A code is declared once per method; sdd_update_spec upserts and deletes findings by code."),
|
|
27692
|
+
}).strict()).optional().describe("The finding codes this method can report, each with its default severity and summary. Each declared code must be anchored in the method's source file, as a string literal or a property-access name (UNREALIZED_FINDING). A code is declared once per method; sdd_update_spec upserts and deletes findings by code."),
|
|
27479
27693
|
ext: import_zod11.z.record(import_zod11.z.unknown()).optional().describe("Opaque pack/tool extension data for this method (namespaced keys) \u2014 preserved verbatim")
|
|
27480
27694
|
};
|
|
27481
27695
|
const interfaceMethodInputFields = Object.keys(interfaceMethodShape);
|
|
@@ -27484,23 +27698,27 @@ NOTICE:
|
|
|
27484
27698
|
name: import_zod11.z.string().describe("Human-readable contract name"),
|
|
27485
27699
|
description: import_zod11.z.string().describe("Contract description and obligations"),
|
|
27486
27700
|
component: import_zod11.z.string().describe("The L2 component ID this interface belongs to"),
|
|
27487
|
-
methods: import_zod11.z.array(import_zod11.z.object(interfaceMethodShape)).optional().describe("List of method signature contracts")
|
|
27701
|
+
methods: import_zod11.z.array(import_zod11.z.object(interfaceMethodShape).strict()).optional().describe("List of method signature contracts"),
|
|
27702
|
+
status: statusInput
|
|
27488
27703
|
};
|
|
27489
27704
|
const interfaceInputFields = Object.keys(interfaceInput);
|
|
27490
27705
|
reg(
|
|
27491
27706
|
server,
|
|
27492
27707
|
"sdd_define_interface",
|
|
27493
27708
|
{
|
|
27494
|
-
description: "Define an L3 Contract / Interface with method signatures for a component. Prefer supplying structured `params` per method \u2014 they are the authoritative source for type checking (the free-form signature string then becomes display-only and is never heuristically parsed). A method declares the finding codes it reports in `findings` ({code, severity, summary}); each code must be anchored in the method's source file, as a string literal or a property-access name (UNREALIZED_FINDING). Re-defining an existing id REPLACES the method list: a method left out of the input is REMOVED (and reported); spec-level lint/ext and each method's endpoint binding are carried forward.",
|
|
27495
|
-
inputSchema: interfaceInput
|
|
27709
|
+
description: "Define an L3 Contract / Interface with method signatures for a component. Prefer supplying structured `params` per method \u2014 they are the authoritative source for type checking (the free-form signature string then becomes display-only and is never heuristically parsed). A method declares the finding codes it reports in `findings` ({code, severity, summary}); each code must be anchored in the method's source file, as a string literal or a property-access name (UNREALIZED_FINDING). Re-defining an existing id REPLACES the method list: a method left out of the input is REMOVED (and reported); spec-level lint/ext and each method's endpoint binding are carried forward, and the stored status is kept unless this input states a higher one. The answer carries a write receipt as structured content beside the sentence \u2014 the status written, whether a spec already held the id, and the notices a restatement raised, each as its own entry.",
|
|
27710
|
+
inputSchema: interfaceInput,
|
|
27711
|
+
outputSchema: specWriteReceiptOutput
|
|
27496
27712
|
},
|
|
27497
|
-
({ id, name, description, component, methods }) => {
|
|
27713
|
+
({ id, name, description, component, methods, status: status2 }) => {
|
|
27498
27714
|
try {
|
|
27499
27715
|
const { loadComponentSpec: loadComponentSpec2, loadInterfaceSpec: loadInterfaceSpec2, saveInterfaceSpec: saveInterfaceSpec2 } = requireSpecs();
|
|
27500
27716
|
const comp = loadComponentSpec2(component);
|
|
27501
27717
|
if (!comp) return errText(`Component "${component}" does not exist.`);
|
|
27502
27718
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
27503
27719
|
const existing = loadInterfaceSpec2(id);
|
|
27720
|
+
const resolved = statusForCreate(`interface "${id}"`, status2, existing);
|
|
27721
|
+
if ("refusal" in resolved) return errText(resolved.refusal);
|
|
27504
27722
|
const carried = [];
|
|
27505
27723
|
const specMethods = (methods ?? []).map((m) => {
|
|
27506
27724
|
const prev = existing?.methods.find((p) => p.name === m.name);
|
|
@@ -27516,7 +27734,7 @@ NOTICE:
|
|
|
27516
27734
|
description,
|
|
27517
27735
|
component,
|
|
27518
27736
|
methods: specMethods,
|
|
27519
|
-
status:
|
|
27737
|
+
status: resolved.status,
|
|
27520
27738
|
createdAt: now,
|
|
27521
27739
|
updatedAt: now
|
|
27522
27740
|
};
|
|
@@ -27533,11 +27751,10 @@ NOTICE:
|
|
|
27533
27751
|
removedSuffix: " (endpoint bindings included)"
|
|
27534
27752
|
}));
|
|
27535
27753
|
}
|
|
27536
|
-
|
|
27537
|
-
|
|
27538
|
-
|
|
27539
|
-
|
|
27540
|
-
return text(`Successfully ${existing ? "re-authored" : "defined"} L3 Interface Contract "${name}" (${id}).${noticeBlock}`);
|
|
27754
|
+
return writeReceipt(
|
|
27755
|
+
`Successfully ${existing ? "re-authored" : "defined"} L3 Interface Contract "${name}" (${id}).`,
|
|
27756
|
+
{ kind: "interface", id, name, replacedExisting: Boolean(existing), status: resolved.status, notices }
|
|
27757
|
+
);
|
|
27541
27758
|
} catch (e) {
|
|
27542
27759
|
return errText(String(e));
|
|
27543
27760
|
}
|
|
@@ -27567,7 +27784,7 @@ NOTICE:
|
|
|
27567
27784
|
channel: import_zod11.z.string().optional().describe("IPC: channel name"),
|
|
27568
27785
|
command: import_zod11.z.string().optional().describe("CLI: command/subcommand"),
|
|
27569
27786
|
address: import_zod11.z.string().optional().describe("Custom: free-form address")
|
|
27570
|
-
})).describe("One binding per method")
|
|
27787
|
+
}).strict()).describe("One binding per method")
|
|
27571
27788
|
}
|
|
27572
27789
|
},
|
|
27573
27790
|
({ interface: interfaceId, endpoints }) => {
|
|
@@ -27624,7 +27841,7 @@ NOTICE:
|
|
|
27624
27841
|
type: import_zod11.z.enum(["local", "call", "dispatch", "register", "branch", "switch", "loop", "try", "parallel", "jump", "return", "throw"]),
|
|
27625
27842
|
targetComponent: import_zod11.z.string().optional().describe("call/register/dispatch: L2 component id (for dispatch, the Portal routed through)"),
|
|
27626
27843
|
targetMethod: import_zod11.z.string().optional().describe("call/register: method name on the target. A register step hands the target method to the runtime as a callback (timer, event listener, shutdown hook): reachability follows the edge, but it is never an invocation \u2014 exempt from call-graph conformance, call-cycle detection, and the durability boot walk"),
|
|
27627
|
-
auth: import_zod11.z.object({ from: import_zod11.z.string(), note: import_zod11.z.string().optional() }).optional().describe("call/dispatch: the credential this step presents to an AUTHED callee Portal and WHERE it loads from (`from`). Opaque form (env:API_KEY, a config key, vault:path) = a design note wairon never resolves; modeled form `component:<id>` references the Adapter/Store that provides the secret and is validated (must resolve, be an Adapter/Store, and be wired to the presenter). Absence on a call into a Portal whose auth \u2260 none warns (PORTAL_AUTH_UNMET). The authenticated call itself should be made by an Adapter (AUTH_PRESENTER_NOT_ADAPTER)."),
|
|
27844
|
+
auth: import_zod11.z.object({ from: import_zod11.z.string(), note: import_zod11.z.string().optional() }).strict().optional().describe("call/dispatch: the credential this step presents to an AUTHED callee Portal and WHERE it loads from (`from`). Opaque form (env:API_KEY, a config key, vault:path) = a design note wairon never resolves; modeled form `component:<id>` references the Adapter/Store that provides the secret and is validated (must resolve, be an Adapter/Store, and be wired to the presenter). Absence on a call into a Portal whose auth \u2260 none warns (PORTAL_AUTH_UNMET). The authenticated call itself should be made by an Adapter (AUTH_PRESENTER_NOT_ADAPTER)."),
|
|
27628
27845
|
detach: import_zod11.z.boolean().optional().describe("call/dispatch: fire-and-forget \u2014 issue the call and continue without awaiting the result (no later step consumes it)"),
|
|
27629
27846
|
capability: import_zod11.z.string().optional().describe("dispatch: the capability routed through the target Portal's dispatch table (validated against it \u2014 UNSERVED_CAPABILITY)"),
|
|
27630
27847
|
assertsGuarantees: import_zod11.z.array(import_zod11.z.string().min(1)).optional().describe("Semantic guarantees this step relies on \u2014 each must be declared in the called method's L3 guarantees (NARRATIVE_SEMANTIC_UNBACKED otherwise). Builtin tokens: idempotent | atomic | transactional | exactly-once; extension packs may declare more (any other token is UNKNOWN_GUARANTEE)"),
|
|
@@ -27635,22 +27852,22 @@ NOTICE:
|
|
|
27635
27852
|
onFalseStep: stepNo().optional().describe("branch: required (or onFalseLabel)"),
|
|
27636
27853
|
onFalseLabel: labelRef().optional().describe("branch: symbolic alternative to onFalseStep"),
|
|
27637
27854
|
on: import_zod11.z.string().optional().describe("switch: the dispatched value"),
|
|
27638
|
-
cases: import_zod11.z.array(import_zod11.z.object({ value: import_zod11.z.string(), step: stepNo().optional(), label: labelRef().optional() })).optional().describe("switch: required; each case targets its region by step number or label"),
|
|
27855
|
+
cases: import_zod11.z.array(import_zod11.z.object({ value: import_zod11.z.string(), step: stepNo().optional(), label: labelRef().optional() }).strict()).optional().describe("switch: required; each case targets its region by step number or label"),
|
|
27639
27856
|
defaultStep: stepNo().optional().describe("switch: default = next step"),
|
|
27640
27857
|
defaultLabel: labelRef().optional().describe("switch: symbolic alternative to defaultStep"),
|
|
27641
27858
|
loopKind: import_zod11.z.enum(["forEach", "for", "while", "doWhile"]).optional().describe('loop: default forEach when "over" is set, else while'),
|
|
27642
27859
|
over: import_zod11.z.string().optional().describe("loop forEach/for: iteration source"),
|
|
27643
27860
|
endStep: stepNo().optional().describe("loop/try: last step of the body region (required, or endLabel)"),
|
|
27644
27861
|
endLabel: labelRef().optional().describe("loop/try: symbolic alternative to endStep"),
|
|
27645
|
-
catches: import_zod11.z.array(import_zod11.z.object({ error: import_zod11.z.string(), step: stepNo().optional(), label: labelRef().optional() })).optional().describe("try: handler regions, each targeted by step number or label"),
|
|
27862
|
+
catches: import_zod11.z.array(import_zod11.z.object({ error: import_zod11.z.string(), step: stepNo().optional(), label: labelRef().optional() }).strict()).optional().describe("try: handler regions, each targeted by step number or label"),
|
|
27646
27863
|
finallyStep: stepNo().optional().describe("try: first step of the always-runs region"),
|
|
27647
27864
|
finallyLabel: labelRef().optional().describe("try: symbolic alternative to finallyStep"),
|
|
27648
|
-
branches: import_zod11.z.array(import_zod11.z.object({ step: stepNo().optional(), label: labelRef().optional(), name: import_zod11.z.string().optional() })).optional().describe("parallel: >= 2 arm entries (step number or label), ascending, first = the step after the header; arms are contiguous sub-regions of body next..endStep, joining after endStep once ALL complete"),
|
|
27865
|
+
branches: import_zod11.z.array(import_zod11.z.object({ step: stepNo().optional(), label: labelRef().optional(), name: import_zod11.z.string().optional() }).strict()).optional().describe("parallel: >= 2 arm entries (step number or label), ascending, first = the step after the header; arms are contiguous sub-regions of body next..endStep, joining after endStep once ALL complete"),
|
|
27649
27866
|
toStep: stepNo().optional().describe("jump: required (break/continue/rejoin), or toLabel"),
|
|
27650
27867
|
toLabel: labelRef().optional().describe("jump: symbolic alternative to toStep \u2014 resolves to the labeled step at write time"),
|
|
27651
27868
|
outcome: import_zod11.z.string().optional().describe('return: e.g. "success", "not found"'),
|
|
27652
27869
|
error: import_zod11.z.string().optional().describe("throw: the raised error")
|
|
27653
|
-
});
|
|
27870
|
+
}).strict();
|
|
27654
27871
|
const detailEnum = import_zod11.z.enum(["full", "calls-only", "intent"]);
|
|
27655
27872
|
const conformanceEnum = import_zod11.z.enum(["declared", "anchored", "off"]);
|
|
27656
27873
|
const implMethodShape = {
|
|
@@ -27674,22 +27891,26 @@ NOTICE:
|
|
|
27674
27891
|
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.`),
|
|
27675
27892
|
detail: detailEnum.optional().describe("Spec-level narrative detail default for all methods"),
|
|
27676
27893
|
conformance: conformanceEnum.optional().describe("Spec-level conformance tier default: declared | anchored | off (omitted = stereotype default: Portal \u2192 anchored, else declared)"),
|
|
27677
|
-
methods: import_zod11.z.array(import_zod11.z.object(implMethodShape)).optional().describe("Method implementations containing L5 narratives")
|
|
27894
|
+
methods: import_zod11.z.array(import_zod11.z.object(implMethodShape).strict()).optional().describe("Method implementations containing L5 narratives"),
|
|
27895
|
+
status: statusInput
|
|
27678
27896
|
};
|
|
27679
27897
|
const implInputFields = Object.keys(implInput);
|
|
27680
27898
|
reg(
|
|
27681
27899
|
server,
|
|
27682
27900
|
"sdd_write_narrative",
|
|
27683
27901
|
{
|
|
27684
|
-
description: "Write L4 Concrete Implementation spec containing L5 method narratives. Narratives are a FLAT ordered step list; flow steps (branch/switch/loop/try/parallel/jump/return/throw) jump by step number \u2014 blocks are just skipped regions. Steps may declare a `label` anchor, and every jump field has a *Label twin (toLabel, onTrueLabel, endLabel, \u2026) resolved to step numbers at write time \u2014 prefer labels over hand-counted numbers; an unresolvable label rejects the write. Detail dial per method: full (narrative required) | calls-only (call choreography suffices) | intent (prose instead of steps); omitted = stereotype default (Portal/Observer/Adapter: calls-only, Store/Index/Registry: intent, else full). Conformance dial per method or spec: declared | anchored | off \u2014 how strictly structural conformance requires contract methods to be realized in their source file (omitted = Portal: anchored, else declared). A method whose body lives in its own file names it in the method's sourcePath; the implementation's sourcePath is the default for every method that names none. Re-authoring an existing id REPLACES the method list: a method left out of the input is REMOVED together with its narrative (and reported); spec-level lint/ext are carried forward.",
|
|
27685
|
-
inputSchema: implInput
|
|
27902
|
+
description: "Write L4 Concrete Implementation spec containing L5 method narratives. Narratives are a FLAT ordered step list; flow steps (branch/switch/loop/try/parallel/jump/return/throw) jump by step number \u2014 blocks are just skipped regions. Steps may declare a `label` anchor, and every jump field has a *Label twin (toLabel, onTrueLabel, endLabel, \u2026) resolved to step numbers at write time \u2014 prefer labels over hand-counted numbers; an unresolvable label rejects the write. Detail dial per method: full (narrative required) | calls-only (call choreography suffices) | intent (prose instead of steps); omitted = stereotype default (Portal/Observer/Adapter: calls-only, Store/Index/Registry: intent, else full). Conformance dial per method or spec: declared | anchored | off \u2014 how strictly structural conformance requires contract methods to be realized in their source file (omitted = Portal: anchored, else declared). A method whose body lives in its own file names it in the method's sourcePath; the implementation's sourcePath is the default for every method that names none. Re-authoring an existing id REPLACES the method list: a method left out of the input is REMOVED together with its narrative (and reported); spec-level lint/ext are carried forward, and the stored status is kept unless this input states a higher one. The answer carries a write receipt as structured content beside the sentence \u2014 the status written, whether a spec already held the id, and the notices a restatement raised, each as its own entry.",
|
|
27903
|
+
inputSchema: implInput,
|
|
27904
|
+
outputSchema: specWriteReceiptOutput
|
|
27686
27905
|
},
|
|
27687
|
-
({ id, name, description, contract, sourcePath, simPath, technologies, detail, conformance, methods }) => {
|
|
27906
|
+
({ id, name, description, contract, sourcePath, simPath, technologies, detail, conformance, methods, status: status2 }) => {
|
|
27688
27907
|
try {
|
|
27689
27908
|
const { loadInterfaceSpec: loadInterfaceSpec2, loadImplementationSpec: loadImplementationSpec2, saveImplementationSpec: saveImplementationSpec2 } = requireSpecs();
|
|
27690
27909
|
const intf = loadInterfaceSpec2(contract);
|
|
27691
27910
|
if (!intf) return errText(`Interface contract "${contract}" does not exist.`);
|
|
27692
27911
|
const existing = loadImplementationSpec2(id);
|
|
27912
|
+
const resolved = statusForCreate(`implementation "${id}"`, status2, existing);
|
|
27913
|
+
if ("refusal" in resolved) return errText(resolved.refusal);
|
|
27693
27914
|
const carried = [];
|
|
27694
27915
|
const resolvedMethods = (methods ?? []).map((m) => {
|
|
27695
27916
|
const prev = existing?.methods.find((p) => p.name === m.name);
|
|
@@ -27719,7 +27940,7 @@ NOTICE:
|
|
|
27719
27940
|
// Post-resolution every cases/catches entry has its numeric step —
|
|
27720
27941
|
// the *Label twins exist only in the input type, not the spec's.
|
|
27721
27942
|
methods: resolvedMethods,
|
|
27722
|
-
status:
|
|
27943
|
+
status: resolved.status,
|
|
27723
27944
|
createdAt: now,
|
|
27724
27945
|
updatedAt: now
|
|
27725
27946
|
};
|
|
@@ -27736,11 +27957,10 @@ NOTICE:
|
|
|
27736
27957
|
removedSuffix: " (L5 narratives included)"
|
|
27737
27958
|
}));
|
|
27738
27959
|
}
|
|
27739
|
-
|
|
27740
|
-
|
|
27741
|
-
|
|
27742
|
-
|
|
27743
|
-
return text(`Successfully saved L4 Implementation Spec "${name}" (${id}) with method narratives.${noticeBlock}`);
|
|
27960
|
+
return writeReceipt(
|
|
27961
|
+
`Successfully saved L4 Implementation Spec "${name}" (${id}) with method narratives.`,
|
|
27962
|
+
{ kind: "implementation", id, name, replacedExisting: Boolean(existing), status: resolved.status, notices }
|
|
27963
|
+
);
|
|
27744
27964
|
} catch (e) {
|
|
27745
27965
|
return errText(String(e));
|
|
27746
27966
|
}
|
|
@@ -27755,18 +27975,23 @@ NOTICE:
|
|
|
27755
27975
|
group: import_zod11.z.string().optional().describe("Optional logical group ID to organize this type in subfolders"),
|
|
27756
27976
|
fields: import_zod11.z.array(import_zod11.z.object({
|
|
27757
27977
|
name: import_zod11.z.string(),
|
|
27758
|
-
type: import_zod11.z.string(),
|
|
27978
|
+
type: import_zod11.z.string().describe(TYPE_REF_GRAMMAR("The field's type")),
|
|
27759
27979
|
description: import_zod11.z.string().optional(),
|
|
27760
27980
|
optional: import_zod11.z.boolean().optional(),
|
|
27761
27981
|
key: import_zod11.z.enum(["primary", "unique", "foreign"]).optional().describe("Identity marker (PK/unique/FK) for ERD and database schema derivation"),
|
|
27762
27982
|
references: import_zod11.z.string().optional().describe('For foreign keys, the referenced type/table id and optional field, e.g. "invoice.id"')
|
|
27763
|
-
})).optional().describe('Data fields (type is a primitive or a qualified type id, e.g. "billing.Invoice")'),
|
|
27764
|
-
methods: import_zod11.z.array(import_zod11.z.object({
|
|
27983
|
+
}).strict()).optional().describe('Data fields (type is a primitive or a qualified type id, e.g. "billing.Invoice")'),
|
|
27984
|
+
methods: import_zod11.z.array(import_zod11.z.object({
|
|
27985
|
+
name: import_zod11.z.string(),
|
|
27986
|
+
signature: import_zod11.z.string(),
|
|
27987
|
+
returns: import_zod11.z.string().describe(TYPE_REF_GRAMMAR("The type the method answers with")),
|
|
27988
|
+
description: import_zod11.z.string().optional()
|
|
27989
|
+
}).strict()).optional().describe("Pure intrinsic methods only"),
|
|
27765
27990
|
componentClass: import_zod11.z.string().optional().describe("Optional component id that implements or owns this logical entity"),
|
|
27766
27991
|
invariants: import_zod11.z.array(import_zod11.z.object({
|
|
27767
27992
|
id: import_zod11.z.string().describe("Stable invariant id, unique within the entity"),
|
|
27768
27993
|
description: import_zod11.z.string().describe("The property that must hold, stated precisely")
|
|
27769
|
-
})).optional().describe("Declared domain invariants (entities): every write-effect method of the componentClass must carry a narrative step asserting each (assertsInvariants) \u2014 declarations checked, enforcement never proven"),
|
|
27994
|
+
}).strict()).optional().describe("Declared domain invariants (entities): every write-effect method of the componentClass must carry a narrative step asserting each (assertsInvariants) \u2014 declarations checked, enforcement never proven"),
|
|
27770
27995
|
database: import_zod11.z.string().optional().describe("Optional database id for table-schema types"),
|
|
27771
27996
|
table: import_zod11.z.string().optional().describe("Optional database table name for table-schema types"),
|
|
27772
27997
|
linkedEntity: import_zod11.z.string().optional().describe("Optional logical entity id represented by this table-schema type")
|
|
@@ -27776,8 +28001,9 @@ NOTICE:
|
|
|
27776
28001
|
server,
|
|
27777
28002
|
"sdd_add_type",
|
|
27778
28003
|
{
|
|
27779
|
-
description: "Define an entity or value-object type (the data components operate on). Entities are owned by a subsystem; shared value objects omit subsystem (system-level). Fields are data; methods are PURE intrinsic behaviour only \u2014 anything needing a collaborator belongs on a component, taking the entity as an argument. Re-defining an existing id REPLACES fields/methods/invariants (an omitted list is CLEARED, and a dropped member is reported); lint/ext are carried forward.",
|
|
27780
|
-
inputSchema: typeInput
|
|
28004
|
+
description: "Define an entity or value-object type (the data components operate on). Entities are owned by a subsystem; shared value objects omit subsystem (system-level). Fields are data; methods are PURE intrinsic behaviour only \u2014 anything needing a collaborator belongs on a component, taking the entity as an argument. Re-defining an existing id REPLACES fields/methods/invariants (an omitted list is CLEARED, and a dropped member is reported); lint/ext are carried forward. The answer carries a write receipt as structured content beside the sentence \u2014 whether a spec already held the id, and the notices a restatement raised, each as its own entry. A type carries no lifecycle status, so the receipt states none.",
|
|
28005
|
+
inputSchema: typeInput,
|
|
28006
|
+
outputSchema: specWriteReceiptOutput
|
|
27781
28007
|
},
|
|
27782
28008
|
({ kind, id, name, description, subsystem, group, fields, methods, componentClass, invariants, database, table, linkedEntity }) => {
|
|
27783
28009
|
try {
|
|
@@ -27824,11 +28050,10 @@ NOTICE:
|
|
|
27824
28050
|
removedNoun: "member"
|
|
27825
28051
|
}));
|
|
27826
28052
|
}
|
|
27827
|
-
|
|
27828
|
-
|
|
27829
|
-
|
|
27830
|
-
|
|
27831
|
-
return text(`Successfully ${existing ? "re-authored" : "defined"} ${kind} type "${name}" (${id}).${noticeBlock}`);
|
|
28053
|
+
return writeReceipt(
|
|
28054
|
+
`Successfully ${existing ? "re-authored" : "defined"} ${kind} type "${name}" (${id}).`,
|
|
28055
|
+
{ kind: "type", id, name, replacedExisting: Boolean(existing), notices }
|
|
28056
|
+
);
|
|
27832
28057
|
} catch (e) {
|
|
27833
28058
|
return errText(String(e));
|
|
27834
28059
|
}
|
|
@@ -27838,11 +28063,12 @@ NOTICE:
|
|
|
27838
28063
|
server,
|
|
27839
28064
|
"sdd_validate_tree",
|
|
27840
28065
|
{
|
|
27841
|
-
description: "Validate the SDD spec tree, checking parent references, contract compatibility, narratives, and component type boundaries. Supports scoping and recursion controls.",
|
|
28066
|
+
description: "Validate the SDD spec tree, checking parent references, contract compatibility, narratives, and component type boundaries. Supports scoping and recursion controls. Findings come back as structured content too, under the schema this tool declares \u2014 errors and warnings already split, each with its code, severity, message and the spec it concerns \u2014 so a caller filters them as objects instead of parsing the JSON text block and hoping its shape holds.",
|
|
27842
28067
|
inputSchema: {
|
|
27843
28068
|
subsystem: import_zod11.z.string().optional().describe("Only validate the specified subsystem (granular)"),
|
|
27844
28069
|
recursive: import_zod11.z.boolean().optional().describe("Whether to recursively validate subprojects (default: true)")
|
|
27845
|
-
}
|
|
28070
|
+
},
|
|
28071
|
+
outputSchema: validateTreeOutput
|
|
27846
28072
|
},
|
|
27847
28073
|
({ subsystem, recursive }) => {
|
|
27848
28074
|
try {
|
|
@@ -27855,7 +28081,7 @@ NOTICE:
|
|
|
27855
28081
|
scopeSubsystem: subsystem,
|
|
27856
28082
|
recursive: recursive ?? true
|
|
27857
28083
|
});
|
|
27858
|
-
return
|
|
28084
|
+
return jsonStructured({
|
|
27859
28085
|
valid: result.valid,
|
|
27860
28086
|
errors: result.issues.filter((i) => i.severity === "error"),
|
|
27861
28087
|
warnings: result.issues.filter((i) => i.severity === "warning"),
|
|
@@ -27870,16 +28096,19 @@ NOTICE:
|
|
|
27870
28096
|
server,
|
|
27871
28097
|
"sdd_get_spec",
|
|
27872
28098
|
{
|
|
27873
|
-
description: `Get/read the parsed JSON contents of a specific spec from the spec tree. Returns structural contents without file system path searching. For a variant-tagged COMPONENT the result also carries a derived, read-only "variantGuidance" (the variant's base, its implementation guidance, and the same-variant sibling components to implement alike) \u2014 it is resolved from the variant registry, not part of the spec, so never write it back.`,
|
|
28099
|
+
description: `Get/read the parsed JSON contents of a specific spec from the spec tree. Returns structural contents without file system path searching. Pass "methods" to read only the named methods of a contract, an implementation or a type \u2014 a 45-method spec fetched whole to look at one of them is the read side of the same waste a restatement is on the write side; the answer then carries a "partialResult" marker naming what was left out, and must never be re-authored from. For a variant-tagged COMPONENT the result also carries a derived, read-only "variantGuidance" (the variant's base, its implementation guidance, and the same-variant sibling components to implement alike) \u2014 it is resolved from the variant registry, not part of the spec, so never write it back. The structured content carries the same answer with the two derived markers KEPT SEPARATE from the stored spec ({kind, id, spec, partialResult?, variantGuidance?}), so nothing derived can be mistaken for something stored; the text block folds them in as it always has.`,
|
|
27874
28100
|
inputSchema: {
|
|
27875
28101
|
kind: import_zod11.z.enum(["system", "subsystem", "component", "interface", "implementation", "type"]).describe("The kind of specification"),
|
|
27876
|
-
id: import_zod11.z.string().describe('The identifier of the spec to fetch (the L0 system spec is a singleton \u2014 pass the system name or "system")')
|
|
27877
|
-
|
|
28102
|
+
id: import_zod11.z.string().describe('The identifier of the spec to fetch (the L0 system spec is a singleton \u2014 pass the system name or "system")'),
|
|
28103
|
+
methods: import_zod11.z.array(import_zod11.z.string().min(1)).optional().describe("Return only these methods by name; every other field of the spec comes back unchanged. Only an interface, an implementation or a type declares methods \u2014 asking for one elsewhere is refused, as is a name the spec does not declare (the answer names the ones it does). Omit it for the whole spec.")
|
|
28104
|
+
},
|
|
28105
|
+
outputSchema: getSpecOutput
|
|
27878
28106
|
},
|
|
27879
|
-
({ kind, id }) => {
|
|
28107
|
+
({ kind, id, methods }) => {
|
|
27880
28108
|
try {
|
|
27881
28109
|
const specs = requireSpecs();
|
|
27882
28110
|
let result = null;
|
|
28111
|
+
let partial = null;
|
|
27883
28112
|
switch (kind) {
|
|
27884
28113
|
case "system":
|
|
27885
28114
|
result = specs.loadSystemSpec();
|
|
@@ -27901,11 +28130,41 @@ NOTICE:
|
|
|
27901
28130
|
break;
|
|
27902
28131
|
}
|
|
27903
28132
|
if (!result) return errText(`Spec of kind "${kind}" with ID "${id}" does not exist.`);
|
|
27904
|
-
if (
|
|
27905
|
-
const
|
|
27906
|
-
if (
|
|
28133
|
+
if (methods !== void 0) {
|
|
28134
|
+
const declared = result.methods;
|
|
28135
|
+
if (!Array.isArray(declared)) {
|
|
28136
|
+
return errText(
|
|
28137
|
+
`A ${kind} spec declares no methods, so "methods" cannot filter it. Drop the argument to read "${id}" whole; methods live on an interface, an implementation and a type.`
|
|
28138
|
+
);
|
|
28139
|
+
}
|
|
28140
|
+
const names = declared.map((m) => String(m?.name));
|
|
28141
|
+
const unknown = methods.filter((n) => !names.includes(n));
|
|
28142
|
+
if (unknown.length > 0) {
|
|
28143
|
+
return errText(
|
|
28144
|
+
`${kind} "${id}" does not declare ${unknown.map((n) => `"${n}"`).join(", ")}. It declares: ${names.join(", ")}. Nothing was returned, so a misspelled name never reads as a method with no content.`
|
|
28145
|
+
);
|
|
28146
|
+
}
|
|
28147
|
+
const kept = declared.filter((m) => methods.includes(String(m?.name)));
|
|
28148
|
+
partial = {
|
|
28149
|
+
shown: kept.map((m) => String(m?.name)),
|
|
28150
|
+
omitted: names.length - kept.length,
|
|
28151
|
+
warning: `PARTIAL: ${names.length - kept.length} of this spec's ${names.length} methods are not in this answer. Never re-author from it \u2014 sdd_define_interface and sdd_write_narrative REPLACE the method list, so every method missing here would be removed from the spec.`
|
|
28152
|
+
};
|
|
28153
|
+
result = { ...result, methods: kept };
|
|
27907
28154
|
}
|
|
27908
|
-
|
|
28155
|
+
const guidance = kind === "component" ? resolveComponentVariantGuidance(result) : null;
|
|
28156
|
+
const folded = {
|
|
28157
|
+
...result,
|
|
28158
|
+
...partial ? { partialResult: partial } : {},
|
|
28159
|
+
...guidance ? { variantGuidance: guidance } : {}
|
|
28160
|
+
};
|
|
28161
|
+
return structured(JSON.stringify(folded, null, 2), {
|
|
28162
|
+
kind,
|
|
28163
|
+
id,
|
|
28164
|
+
spec: result,
|
|
28165
|
+
...partial ? { partialResult: partial } : {},
|
|
28166
|
+
...guidance ? { variantGuidance: guidance } : {}
|
|
28167
|
+
});
|
|
27909
28168
|
} catch (e) {
|
|
27910
28169
|
return errText(String(e));
|
|
27911
28170
|
}
|
|
@@ -27953,16 +28212,19 @@ NOTICE:
|
|
|
27953
28212
|
server,
|
|
27954
28213
|
"sdd_update_spec",
|
|
27955
28214
|
{
|
|
27956
|
-
description: "Update/patch an existing SDD specification (subsystem, component, interface, implementation, or type) using a granular delta. Updates fields, appends/merges array elements, or inserts/deletes narrative steps.",
|
|
28215
|
+
description: "Update/patch an existing SDD specification (subsystem, component, interface, implementation, or type) using a granular delta. Updates fields, appends/merges array elements, or inserts/deletes narrative steps. Answers with exactly what changed, and with every path the delta named that the write did not act on. Pass dryRun to be told what it would do without writing it.",
|
|
27957
28216
|
inputSchema: {
|
|
27958
28217
|
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 gateway surface, each entry {id, name, subsystem, component, type, details, audience: project|department|instance|partner|external}; id is informational)"),
|
|
27959
28218
|
id: import_zod11.z.string().describe("The ID of the spec to update (namespaced if needed)"),
|
|
27960
|
-
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", lifecycle by phase+component+method, emits/subscribesTo by topic+event, trustedLinks by "subsystem", invariants and patterns by "id", lint.allow by "code", 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, reason }] }" to silence a WARNING code on this spec only (errors always surface; stale allows are flagged).`)
|
|
27961
|
-
|
|
28219
|
+
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", lifecycle by phase+component+method, emits/subscribesTo by topic+event, trustedLinks by "subsystem", invariants and patterns by "id", lint.allow by "code", 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, reason }] }" to silence a WARNING code on this spec only (errors always surface; stale allows are flagged). 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.`),
|
|
28220
|
+
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.")
|
|
28221
|
+
},
|
|
28222
|
+
outputSchema: specChangeReportOutput
|
|
27962
28223
|
},
|
|
27963
|
-
({ kind, id, delta }) => {
|
|
28224
|
+
({ kind, id, delta, dryRun }) => {
|
|
27964
28225
|
try {
|
|
27965
|
-
|
|
28226
|
+
const report2 = updateSpecGated(kind, id, delta, dryRun);
|
|
28227
|
+
return structured(renderChangeReport(report2), report2);
|
|
27966
28228
|
} catch (e) {
|
|
27967
28229
|
return errText(String(e));
|
|
27968
28230
|
}
|
|
@@ -28166,7 +28428,7 @@ async function startMcpServer() {
|
|
|
28166
28428
|
} catch {
|
|
28167
28429
|
}
|
|
28168
28430
|
}
|
|
28169
|
-
var import_mcp, import_stdio, import_zod11, import_types3, fs20, path27, import_url, SERVER_BUILD_STAMP, STALE_SERVER_WARNING, SPEC_WRITE_TOOLS, listChangedEmitters, STORE_MANAGED_FIELDS, ALWAYS_CARRIED_FIELDS, SKILL_RESOURCE_MIME, AGENT_BRIEF_SCHEME;
|
|
28431
|
+
var import_mcp, import_stdio, import_zod11, import_types3, fs20, path27, import_url, TYPE_REF_GRAMMAR, staleServerOutput, SPEC_KINDS, STATUS_ORDER, specWriteReceiptOutput, specChangeOutput, specChangeReportOutput, validationIssueOutput, validateTreeOutput, getSpecOutput, SERVER_BUILD_STAMP, STALE_SERVER_WARNING, SPEC_WRITE_TOOLS, listChangedEmitters, STORE_MANAGED_FIELDS, statusInput, ALWAYS_CARRIED_FIELDS, SKILL_RESOURCE_MIME, AGENT_BRIEF_SCHEME;
|
|
28170
28432
|
var init_server = __esm({
|
|
28171
28433
|
"src/mcp/server.ts"() {
|
|
28172
28434
|
"use strict";
|
|
@@ -28197,6 +28459,105 @@ var init_server = __esm({
|
|
|
28197
28459
|
init_agent_resolver();
|
|
28198
28460
|
init_budget_policy();
|
|
28199
28461
|
init_surfaces();
|
|
28462
|
+
TYPE_REF_GRAMMAR = (what) => `${what}: a primitive/builtin or a defined type id (qualified across subsystems, e.g. "billing.Invoice" or "billing::Invoice"). Generics, arrays and UNIONS are all read, at any depth: "Invoice | null" (the commonest shape there is), "Invoice | undefined", "Invoice | Receipt", "Promise<Invoice | null>", "Map<string, Invoice | null>", "Invoice[] | null", "(Invoice | null)[]". Every identifier the string names must resolve \u2014 a union of two defined types means BOTH must exist. A union of string literals ("read" | "write") names no type and resolves to nothing.`;
|
|
28463
|
+
staleServerOutput = {
|
|
28464
|
+
staleServer: import_zod11.z.boolean().optional().describe(
|
|
28465
|
+
"True when the wairon build on disk changed after this server started \u2014 the structured twin of the text answer's STALE SERVER banner. Writes through a stale process can silently drop fields a newer schema introduced: restart the MCP session before editing further."
|
|
28466
|
+
)
|
|
28467
|
+
};
|
|
28468
|
+
SPEC_KINDS = ["system", "subsystem", "component", "interface", "implementation", "type"];
|
|
28469
|
+
STATUS_ORDER = ["draft", "design", "complete"];
|
|
28470
|
+
specWriteReceiptOutput = {
|
|
28471
|
+
kind: import_zod11.z.enum(SPEC_KINDS).describe("The spec kind written."),
|
|
28472
|
+
id: import_zod11.z.string().describe('The id the spec is stored under; "system" for the L0 singleton.'),
|
|
28473
|
+
name: import_zod11.z.string().describe("The spec's display name as written."),
|
|
28474
|
+
replacedExisting: import_zod11.z.boolean().describe(
|
|
28475
|
+
"True when a spec already held this id and the call re-authored it in place rather than adding one. The notices then say what was carried forward, removed or cleared by the restatement."
|
|
28476
|
+
),
|
|
28477
|
+
status: import_zod11.z.enum(STATUS_ORDER).optional().describe(
|
|
28478
|
+
"The lifecycle status written. Absent for a type, which carries none."
|
|
28479
|
+
),
|
|
28480
|
+
notices: import_zod11.z.array(import_zod11.z.string()).describe(
|
|
28481
|
+
"The notice lines the text answer lists, one per entry: what was carried forward, what a restatement removed, what an omission cleared, and every gate warning the write raised. Empty for a clean create."
|
|
28482
|
+
),
|
|
28483
|
+
scaffoldedProjectPath: import_zod11.z.string().optional().describe(
|
|
28484
|
+
"The child project directory a chained subsystem's create scaffolded; absent for every other write."
|
|
28485
|
+
),
|
|
28486
|
+
...staleServerOutput
|
|
28487
|
+
};
|
|
28488
|
+
specChangeOutput = {
|
|
28489
|
+
path: import_zod11.z.string().describe("The dotted path the change addresses, with names and indexes."),
|
|
28490
|
+
change: import_zod11.z.enum(["set", "added", "removed", "cleared"]).describe("What happened at that path."),
|
|
28491
|
+
before: import_zod11.z.string().optional().describe("The previous value, summarized; absent when there was none."),
|
|
28492
|
+
after: import_zod11.z.string().optional().describe("The new value, summarized; absent when there is none.")
|
|
28493
|
+
};
|
|
28494
|
+
specChangeReportOutput = {
|
|
28495
|
+
kind: import_zod11.z.enum(SPEC_KINDS).describe("The spec kind written."),
|
|
28496
|
+
id: import_zod11.z.string().describe("The stored spec's id."),
|
|
28497
|
+
written: import_zod11.z.boolean().describe("False when the merged spec equals what is stored: nothing reached disk."),
|
|
28498
|
+
dryRun: import_zod11.z.boolean().describe(
|
|
28499
|
+
"True when the caller asked what the delta WOULD do. `written` is false for a dry run exactly as it is for a delta that changed nothing, and only this field tells the two apart."
|
|
28500
|
+
),
|
|
28501
|
+
changes: import_zod11.z.array(import_zod11.z.object(specChangeOutput)).describe(
|
|
28502
|
+
"Every change the write made; empty exactly when `written` is false."
|
|
28503
|
+
),
|
|
28504
|
+
ineffective: import_zod11.z.array(import_zod11.z.string()).describe(
|
|
28505
|
+
"Every path the delta named that the write did not act on, each with why. Read it: a nested typo the permissive delta cannot refuse shows up here and nowhere else."
|
|
28506
|
+
),
|
|
28507
|
+
notices: import_zod11.z.array(import_zod11.z.string()).describe("Store placement notices, gate warnings and delta notices."),
|
|
28508
|
+
summary: import_zod11.z.string().describe("One line for people."),
|
|
28509
|
+
...staleServerOutput
|
|
28510
|
+
};
|
|
28511
|
+
validationIssueOutput = {
|
|
28512
|
+
severity: import_zod11.z.enum(["error", "warning"]).describe("The finding's severity after project overrides."),
|
|
28513
|
+
code: import_zod11.z.string().describe("The rule code, UPPER_SNAKE \u2014 the stable handle to filter and suppress by."),
|
|
28514
|
+
message: import_zod11.z.string().describe("What is wrong, named."),
|
|
28515
|
+
agentId: import_zod11.z.string().optional().describe("The agent the finding concerns, when it concerns one."),
|
|
28516
|
+
specId: import_zod11.z.string().optional().describe("The spec the finding concerns, when it concerns one."),
|
|
28517
|
+
draftContext: import_zod11.z.boolean().optional().describe(
|
|
28518
|
+
"True when the finding was raised against a draft/design spec \u2014 what the --ci gate waives."
|
|
28519
|
+
),
|
|
28520
|
+
surfaceResolved: import_zod11.z.boolean().optional().describe(
|
|
28521
|
+
"True when the finding was verified against a vendored surface snapshot, so it is a contract verdict rather than a resolution failure."
|
|
28522
|
+
)
|
|
28523
|
+
};
|
|
28524
|
+
validateTreeOutput = {
|
|
28525
|
+
valid: import_zod11.z.boolean().describe("False when the tree holds at least one error."),
|
|
28526
|
+
errors: import_zod11.z.array(import_zod11.z.object(validationIssueOutput)).describe("Every finding of severity error."),
|
|
28527
|
+
warnings: import_zod11.z.array(import_zod11.z.object(validationIssueOutput)).describe("Every finding of severity warning."),
|
|
28528
|
+
resolvedThrough: import_zod11.z.object({
|
|
28529
|
+
root: import_zod11.z.string().describe("The top root that was validated."),
|
|
28530
|
+
scope: import_zod11.z.string().describe("The mount chain the verdict was scoped to.")
|
|
28531
|
+
}).optional().describe(
|
|
28532
|
+
"Present when a chained subproject's verdict was resolved through its parent; absent when the tree was validated on its own."
|
|
28533
|
+
),
|
|
28534
|
+
...staleServerOutput
|
|
28535
|
+
};
|
|
28536
|
+
getSpecOutput = {
|
|
28537
|
+
kind: import_zod11.z.enum(SPEC_KINDS).describe("The kind of spec read."),
|
|
28538
|
+
id: import_zod11.z.string().describe("The id it was read by."),
|
|
28539
|
+
spec: import_zod11.z.record(import_zod11.z.unknown()).describe(
|
|
28540
|
+
"The stored spec, exactly as the text block renders it \u2014 minus the two derived markers below, which the text folds in and this answer keeps separate so nothing derived can be mistaken for stored."
|
|
28541
|
+
),
|
|
28542
|
+
partialResult: import_zod11.z.object({
|
|
28543
|
+
shown: import_zod11.z.array(import_zod11.z.string()).describe("The method names this answer carries."),
|
|
28544
|
+
omitted: import_zod11.z.number().describe("How many of the spec's methods are NOT in it."),
|
|
28545
|
+
warning: import_zod11.z.string().describe("Why it must never be re-authored from.")
|
|
28546
|
+
}).optional().describe(
|
|
28547
|
+
"Present only when `methods` filtered the read. A filtered spec is a partial one: sdd_define_interface and sdd_write_narrative REPLACE the method list, so re-authoring from it would delete every method left out."
|
|
28548
|
+
),
|
|
28549
|
+
variantGuidance: import_zod11.z.object({
|
|
28550
|
+
variant: import_zod11.z.string().describe("The variant id the component declares."),
|
|
28551
|
+
base: import_zod11.z.string().describe("The core stereotype it specializes."),
|
|
28552
|
+
guidance: import_zod11.z.string().describe("How to implement a component of this variant."),
|
|
28553
|
+
siblings: import_zod11.z.array(import_zod11.z.string()).describe("Other components of the same variant, to implement alike."),
|
|
28554
|
+
target: import_zod11.z.string().optional().describe("The target language the variant scopes itself to, if any."),
|
|
28555
|
+
profile: import_zod11.z.string().optional().describe("The architectural profile it scopes itself to, if any.")
|
|
28556
|
+
}).optional().describe(
|
|
28557
|
+
"Derived, read-only guidance for a variant-tagged component \u2014 resolved from the variant registry, not part of the spec. Never write it back."
|
|
28558
|
+
),
|
|
28559
|
+
...staleServerOutput
|
|
28560
|
+
};
|
|
28200
28561
|
SERVER_BUILD_STAMP = captureBuildStamp(__filename);
|
|
28201
28562
|
STALE_SERVER_WARNING = "\n\n\u26A0 STALE SERVER: the wairon build on disk changed after this MCP server started. Restart the MCP session (e.g. /mcp reconnect) before further spec edits \u2014 writes through a stale server can silently drop fields introduced by newer schemas.";
|
|
28202
28563
|
SPEC_WRITE_TOOLS = /* @__PURE__ */ new Set([
|
|
@@ -28219,6 +28580,9 @@ var init_server = __esm({
|
|
|
28219
28580
|
]);
|
|
28220
28581
|
listChangedEmitters = /* @__PURE__ */ new WeakMap();
|
|
28221
28582
|
STORE_MANAGED_FIELDS = /* @__PURE__ */ new Set(["status", "updatedAt"]);
|
|
28583
|
+
statusInput = import_zod11.z.enum(STATUS_ORDER).optional().describe(
|
|
28584
|
+
"The spec's lifecycle status. Omitted means draft for a NEW spec and the status already stored for a re-authoring, so a restatement never reopens a frozen spec. State it to author straight at design or complete instead of promoting afterwards. A status that would LOWER the stored one is refused \u2014 reopening a spec for revision is sdd_update_spec's job, which sets the demotion deliberately."
|
|
28585
|
+
);
|
|
28222
28586
|
ALWAYS_CARRIED_FIELDS = /* @__PURE__ */ new Set(["ext"]);
|
|
28223
28587
|
SKILL_RESOURCE_MIME = "text/markdown";
|
|
28224
28588
|
AGENT_BRIEF_SCHEME = "wairon-agent://";
|