@wairon/cli 5.1.1-dev.78 → 5.1.1-dev.79
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 +116 -35
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +56 -20
- package/dist/index.js.map +1 -1
- package/dist/templates/skills/sdd-architect.md +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.79";
|
|
69
69
|
GITHUB_REPO = "SYW-Apps/Waffle-AIron";
|
|
70
70
|
SUPPORTED_ALIASES = ["wai"];
|
|
71
71
|
SCAN_EXCLUDE_DIRS = /* @__PURE__ */ new Set([
|
|
@@ -117,6 +117,15 @@ var init_logger = __esm({
|
|
|
117
117
|
console.warn(import_chalk.default.yellow("\u26A0") + " " + import_chalk.default.yellow(message));
|
|
118
118
|
}
|
|
119
119
|
},
|
|
120
|
+
/**
|
|
121
|
+
* A finding reported at `notice` severity: listed, never a failure. Kept
|
|
122
|
+
* visually apart from warn so a reader never mistakes one for the other.
|
|
123
|
+
*/
|
|
124
|
+
notice(message) {
|
|
125
|
+
if (shouldLog("info")) {
|
|
126
|
+
console.log(import_chalk.default.blue("\u25C6") + " " + import_chalk.default.blue("notice ") + message);
|
|
127
|
+
}
|
|
128
|
+
},
|
|
120
129
|
error(message) {
|
|
121
130
|
console.error(import_chalk.default.red("\u2716") + " " + import_chalk.default.red(message));
|
|
122
131
|
},
|
|
@@ -864,9 +873,11 @@ var init_project = __esm({
|
|
|
864
873
|
materializeAgentFiles: import_zod3.z.boolean().default(false),
|
|
865
874
|
/**
|
|
866
875
|
* Severity overrides for SDD validation rules.
|
|
867
|
-
* Key: rule code (e.g. CIRCULAR_DEPENDENCY), Value: error | warning | off
|
|
876
|
+
* Key: rule code (e.g. CIRCULAR_DEPENDENCY), Value: error | warning | notice | off.
|
|
877
|
+
* A notice is still reported, but never makes the tree invalid and never
|
|
878
|
+
* fails `--ci`; off is not reported at all.
|
|
868
879
|
*/
|
|
869
|
-
sddRuleSeverity: import_zod3.z.record(import_zod3.z.enum(["error", "warning", "off"])).default({}),
|
|
880
|
+
sddRuleSeverity: import_zod3.z.record(import_zod3.z.enum(["error", "warning", "notice", "off"])).default({}),
|
|
870
881
|
/** Dynamic naming conventions and stereotype suffix rules */
|
|
871
882
|
naming: NamingRuleConfigSchema.optional(),
|
|
872
883
|
/** Dynamic metadata documentation constraints */
|
|
@@ -4412,6 +4423,8 @@ body:not(.panel-closed) #panelToggle { background:var(--accent); color:#fff; bor
|
|
|
4412
4423
|
#panel .flowbtn:hover { background:var(--hover-bg); }
|
|
4413
4424
|
#panel .issue { border-left:3px solid var(--danger); padding:6px 9px; margin:6px 0; background:var(--card); font-size:12px; border-radius:0 7px 7px 0; }
|
|
4414
4425
|
#panel .issue.warning { border-left-color:var(--warn); }
|
|
4426
|
+
#panel .issue.notice { border-left-color:var(--accent); }
|
|
4427
|
+
#panel .issue .sev { font-size:10px; color:var(--dim); text-transform:uppercase; letter-spacing:.04em; margin-left:6px; }
|
|
4415
4428
|
#panel .issue code { font-size:10.5px; color:var(--dim); }
|
|
4416
4429
|
|
|
4417
4430
|
/* Presentation mode = the canvas page, focused: the header chrome and legend
|
|
@@ -4622,7 +4635,15 @@ var MODEL = __MODEL_JSON__;
|
|
|
4622
4635
|
});
|
|
4623
4636
|
document.getElementById('issueCount').textContent =
|
|
4624
4637
|
MODEL.issues.filter(function (i) { return i.severity === 'error'; }).length + 'e/' +
|
|
4625
|
-
MODEL.issues.filter(function (i) { return i.severity === 'warning'; }).length + 'w'
|
|
4638
|
+
MODEL.issues.filter(function (i) { return i.severity === 'warning'; }).length + 'w/' +
|
|
4639
|
+
MODEL.issues.filter(function (i) { return i.severity === 'notice'; }).length + 'n';
|
|
4640
|
+
// A node is marked failing only for an error or a warning; one that holds
|
|
4641
|
+
// only notices gets its own quieter mark, never the failing one.
|
|
4642
|
+
function issueMark(id) {
|
|
4643
|
+
var list = state.showIssues && issuesBySpec[id];
|
|
4644
|
+
if (!list) return '';
|
|
4645
|
+
return list.some(function (i) { return i.severity !== 'notice'; }) ? ' hasIssue' : ' hasNotice';
|
|
4646
|
+
}
|
|
4626
4647
|
|
|
4627
4648
|
var PATTERN_TYPES = { Repository:1, FeatureComponent:1, RouterComponent:1 };
|
|
4628
4649
|
// A retired Specialist or Gateway renders as a plain box marked retired, so a
|
|
@@ -4962,6 +4983,9 @@ var MODEL = __MODEL_JSON__;
|
|
|
4962
4983
|
{ selector: 'edge.stubHover', style: { 'line-color': t.selGlow, 'target-arrow-color': t.selGlow, width: 2.6, opacity: 1, 'z-compound-depth': 'top' } },
|
|
4963
4984
|
{ selector: '.dimmed', style: { opacity: 0.13 } },
|
|
4964
4985
|
{ selector: '.hasIssue', style: { 'border-color': t.issue, 'border-style': 'dashed', 'border-width': 3 } },
|
|
4986
|
+
// Notices only: the node keeps its own border colour and width (no layout
|
|
4987
|
+
// nudge), drawn dotted so it reads as noted rather than failing.
|
|
4988
|
+
{ selector: '.hasNotice', style: { 'border-style': 'dotted' } },
|
|
4965
4989
|
// Overlay only (no border) \u2014 a border changes node geometry, which nudges
|
|
4966
4990
|
// the compound parent and makes hover flicker; overlay never affects layout.
|
|
4967
4991
|
{ selector: '.sel', style: { 'overlay-color': t.selGlow, 'overlay-opacity': 0.34, 'overlay-padding': 6 } },
|
|
@@ -5751,7 +5775,7 @@ var MODEL = __MODEL_JSON__;
|
|
|
5751
5775
|
var kindCls = t.kind === 'entity' ? 'typeEntity' : 'typeValue';
|
|
5752
5776
|
var dim = !typeMatches(t);
|
|
5753
5777
|
var extra = (dim ? ' dimmed' : '')
|
|
5754
|
-
+ (
|
|
5778
|
+
+ issueMark(t.id)
|
|
5755
5779
|
+ (state.selectedKind === 'type' && state.selected === t.id ? ' sel' : '');
|
|
5756
5780
|
if (sh.plain) {
|
|
5757
5781
|
eles.push({
|
|
@@ -6041,7 +6065,7 @@ var MODEL = __MODEL_JSON__;
|
|
|
6041
6065
|
}
|
|
6042
6066
|
classes += (e.hasKids ? ' drillable' : '') + (isPub ? ' public' : '')
|
|
6043
6067
|
+ (dim ? ' dimmed' : '')
|
|
6044
|
-
+ (
|
|
6068
|
+
+ issueMark(e.id)
|
|
6045
6069
|
+ (state.selectedKind === e.kind && state.selected === e.id ? ' sel' : '');
|
|
6046
6070
|
if (inner) {
|
|
6047
6071
|
var boxNode = { data: { id: aid, label: e.kind === 'subsystem' ? nameOf(e) : nameOf(e), w: p.w, h: p.h, tw: p.w - 16 }, classes: classes };
|
|
@@ -7986,7 +8010,7 @@ var MODEL = __MODEL_JSON__;
|
|
|
7986
8010
|
}
|
|
7987
8011
|
function issueHtml(list) {
|
|
7988
8012
|
return list.map(function (i) {
|
|
7989
|
-
return '<div class="issue ' + esc(i.severity) + '"><code>' + esc(i.code) + '</code><br>' + esc(i.message) + '</div>';
|
|
8013
|
+
return '<div class="issue ' + esc(i.severity) + '"><code>' + esc(i.code) + '</code><span class="sev">' + esc(i.severity) + '</span><br>' + esc(i.message) + '</div>';
|
|
7990
8014
|
}).join('');
|
|
7991
8015
|
}
|
|
7992
8016
|
|
|
@@ -10613,7 +10637,7 @@ var init_lint_allows = __esm({
|
|
|
10613
10637
|
"use strict";
|
|
10614
10638
|
lintAllowsRule = {
|
|
10615
10639
|
name: "lint-allows",
|
|
10616
|
-
description: "Per-spec lint suppressions (lint.allow) must name real issue codes and actually suppress a finding \u2014 unknown codes and stale allows are flagged. An allow covers exactly the occurrence it names: a finding that reports a site is silenced only by an allow whose `at` is that site, a finding that reports none only by an allow that names none, and an aggregating finding only by an allow whose `covers` lists every unit it reports \u2014 a unit nobody listed is named back as new instead of inheriting a decision taken about its neighbours. So a coarse allow left on a rule that names sites, and an allow whose site the run no longer reports, are both UNUSED_LINT_ALLOW, and the finding names the sites that did fire. Allows silence warnings
|
|
10640
|
+
description: "Per-spec lint suppressions (lint.allow) must name real issue codes and actually suppress a finding \u2014 unknown codes and stale allows are flagged. An allow covers exactly the occurrence it names: a finding that reports a site is silenced only by an allow whose `at` is that site, a finding that reports none only by an allow that names none, and an aggregating finding only by an allow whose `covers` lists every unit it reports \u2014 a unit nobody listed is named back as new instead of inheriting a decision taken about its neighbours. So a coarse allow left on a rule that names sites, and an allow whose site the run no longer reports, are both UNUSED_LINT_ALLOW, and the finding names the sites that did fire. Allows silence warnings and notices; errors always surface.",
|
|
10617
10641
|
codes: [
|
|
10618
10642
|
{ code: "UNKNOWN_LINT_ALLOW_CODE", defaultSeverity: "warning", summary: "lint.allow names an issue code no registered rule emits" },
|
|
10619
10643
|
{ code: "UNUSED_LINT_ALLOW", defaultSeverity: "warning", summary: "lint.allow entry matched no finding this run \u2014 the code never fired, or it fired at sites this allow does not name" }
|
|
@@ -18379,6 +18403,9 @@ var init_source_analysis = __esm({
|
|
|
18379
18403
|
});
|
|
18380
18404
|
|
|
18381
18405
|
// src/core/rules/index.ts
|
|
18406
|
+
function atMostWarning(severity) {
|
|
18407
|
+
return severity === "error" ? "warning" : severity;
|
|
18408
|
+
}
|
|
18382
18409
|
function makeScopeFilter(opts) {
|
|
18383
18410
|
const { components, interfaces, implementations, types, scopeSubsystem } = opts;
|
|
18384
18411
|
return (specId) => {
|
|
@@ -18614,7 +18641,7 @@ function buildRuleContext(opts) {
|
|
|
18614
18641
|
return profileSeverity;
|
|
18615
18642
|
}
|
|
18616
18643
|
if (isDraftContext && COMPLETENESS_RULES.has(ruleCode)) {
|
|
18617
|
-
return
|
|
18644
|
+
return atMostWarning(defaultSeverity);
|
|
18618
18645
|
}
|
|
18619
18646
|
return defaultSeverity;
|
|
18620
18647
|
};
|
|
@@ -18688,13 +18715,13 @@ function buildRuleContext(opts) {
|
|
|
18688
18715
|
allowClaimed = true;
|
|
18689
18716
|
const grew = (parts?.covers ?? []).filter((unit) => !(allow.covers ?? []).includes(unit));
|
|
18690
18717
|
if (grew.length === 0) {
|
|
18691
|
-
if (severity
|
|
18718
|
+
if (severity !== "error") return;
|
|
18692
18719
|
} else {
|
|
18693
18720
|
text2 = `${text2} A lint.allow covers this site, but not ${grew.length} part(s) of it \u2014 ${grew.map((u) => `"${u}"`).join("; ")} ${grew.length === 1 ? "is" : "are"} new. Decide on them: add them to the allow's \`covers\` with a reason that is actually true, or fix them.`;
|
|
18694
18721
|
}
|
|
18695
18722
|
}
|
|
18696
18723
|
}
|
|
18697
|
-
if (specId && parts && severity
|
|
18724
|
+
if (specId && parts && severity !== "error" && !allowClaimed) {
|
|
18698
18725
|
const entry = carriedLookup.get(carriedKey(specId, code, parts.at));
|
|
18699
18726
|
if (entry) {
|
|
18700
18727
|
entry.fired = true;
|
|
@@ -18927,7 +18954,8 @@ function validateComponentCandidate(candidate, opts = {}) {
|
|
|
18927
18954
|
const own = issues.filter((i) => !i.specId || i.specId === candidate.id);
|
|
18928
18955
|
return {
|
|
18929
18956
|
errors: own.filter((i) => i.severity === "error"),
|
|
18930
|
-
warnings: own.filter((i) => i.severity === "warning")
|
|
18957
|
+
warnings: own.filter((i) => i.severity === "warning"),
|
|
18958
|
+
notices: own.filter((i) => i.severity === "notice")
|
|
18931
18959
|
};
|
|
18932
18960
|
}
|
|
18933
18961
|
var init_candidate = __esm({
|
|
@@ -19414,7 +19442,7 @@ var init_validation = __esm({
|
|
|
19414
19442
|
"INVALID_TRUSTED_LINK",
|
|
19415
19443
|
"CROSS_TREE_REF_UNRESOLVED"
|
|
19416
19444
|
]);
|
|
19417
|
-
SEVERITY_RANK = { off: 0,
|
|
19445
|
+
SEVERITY_RANK = { off: 0, notice: 1, warning: 2, error: 3 };
|
|
19418
19446
|
}
|
|
19419
19447
|
});
|
|
19420
19448
|
|
|
@@ -30222,7 +30250,10 @@ function candidateOptions() {
|
|
|
30222
30250
|
return config ? { rules: config.rules, projectType: config.projectType } : {};
|
|
30223
30251
|
}
|
|
30224
30252
|
function noticesFrom(verdict) {
|
|
30225
|
-
return
|
|
30253
|
+
return [
|
|
30254
|
+
...verdict.warnings.map((w) => `${w.code}: ${w.message}`),
|
|
30255
|
+
...verdict.notices.map((n) => `${n.code} (notice): ${n.message}`)
|
|
30256
|
+
];
|
|
30226
30257
|
}
|
|
30227
30258
|
function componentCandidateGate(options = candidateOptions(), storedOwner) {
|
|
30228
30259
|
return {
|
|
@@ -31152,7 +31183,7 @@ function createMcpServer(options = {}) {
|
|
|
31152
31183
|
server,
|
|
31153
31184
|
"validateTopology",
|
|
31154
31185
|
{
|
|
31155
|
-
description: "Validate the project's agent topology. Returns errors and
|
|
31186
|
+
description: "Validate the project's agent topology. Returns errors, warnings and notices (duplicate ids, overlapping ownership, missing paths, etc.). Supports optional subsystem scoping.",
|
|
31156
31187
|
inputSchema: {
|
|
31157
31188
|
subsystem: import_zod11.z.string().optional().describe("Only validate topology for agents under the specified subsystem")
|
|
31158
31189
|
}
|
|
@@ -31172,7 +31203,8 @@ function createMcpServer(options = {}) {
|
|
|
31172
31203
|
return json({
|
|
31173
31204
|
valid: result.issues.filter((i) => i.severity === "error").length === 0,
|
|
31174
31205
|
errors: result.issues.filter((i) => i.severity === "error"),
|
|
31175
|
-
warnings: result.issues.filter((i) => i.severity === "warning")
|
|
31206
|
+
warnings: result.issues.filter((i) => i.severity === "warning"),
|
|
31207
|
+
notices: result.issues.filter((i) => i.severity === "notice")
|
|
31176
31208
|
});
|
|
31177
31209
|
} catch (e) {
|
|
31178
31210
|
return errText(String(e));
|
|
@@ -31890,7 +31922,7 @@ ${renderChangeReport(report2)}`,
|
|
|
31890
31922
|
server,
|
|
31891
31923
|
"sdd_validate_tree",
|
|
31892
31924
|
{
|
|
31893
|
-
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
|
|
31925
|
+
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, warnings and notices already split into three lists (a notice never makes the tree invalid and never fails --ci), 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.",
|
|
31894
31926
|
inputSchema: {
|
|
31895
31927
|
subsystem: import_zod11.z.string().optional().describe("Only validate the specified subsystem (granular)"),
|
|
31896
31928
|
recursive: import_zod11.z.boolean().optional().describe("Whether to recursively validate subprojects (default: true)")
|
|
@@ -31911,6 +31943,7 @@ ${renderChangeReport(report2)}`,
|
|
|
31911
31943
|
valid: result.valid,
|
|
31912
31944
|
errors: result.issues.filter((i) => i.severity === "error"),
|
|
31913
31945
|
warnings: result.issues.filter((i) => i.severity === "warning"),
|
|
31946
|
+
notices: result.issues.filter((i) => i.severity === "notice"),
|
|
31914
31947
|
...result.resolvedThrough ? { resolvedThrough: result.resolvedThrough } : {}
|
|
31915
31948
|
});
|
|
31916
31949
|
} catch (e) {
|
|
@@ -32026,7 +32059,7 @@ ${testsBlock}` : ""}`, deletion);
|
|
|
32026
32059
|
inputSchema: {
|
|
32027
32060
|
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)"),
|
|
32028
32061
|
id: import_zod11.z.string().describe("The ID of the spec to update (namespaced if needed)"),
|
|
32029
|
-
delta: import_zod11.z.record(import_zod11.z.any()).describe(`The partial fields to merge into the spec. ARRAYS UPSERT, they do not replace: an array whose elements carry an identity is merged element-by-element, so a delta naming ONE element leaves the others intact. Identity is "name" or "id" by default, and per field: dispatch by "capability", a listener's mounts by "portal", lifecycle by phase+component+method, emits/subscribesTo by topic+event, trustedLinks by "subsystem", a subsystem's publicInterfaces by component+interface (or type+details for an entry not yet bound to a component), invariants and patterns by "id", lint.allow by "code" AND "at" (an allow covers one occurrence, so several may share a code on one spec), an interface method's findings by "code", boundaries by "name", globalRequirements by "description", switch cases by "value", try catches by "error". Identity merging applies at EVERY depth, including an array INSIDE an element (a method's params, a step's catches). Add "action: 'delete'" (or "remove: true") alongside that identity to REMOVE an element \u2014 including a stale lint allow. Arrays of plain STRINGS (owns, dependsOn, guarantees) carry no per-element identity and are replaced wholesale; pass [] to clear any array outright. To REMOVE an optional field entirely, list it in "unset": e.g. {"unset": ["basePath", "variant"]} \u2014 passing null/undefined means "no change" (they are skipped), and writing "" would leave the field present but empty, which is a different and usually wrong spec. Unsetting a required field is refused by schema validation, which names it. For narrative steps, match by "stepNumber" and use "action: 'insert'" (shifts subsequent steps up) or "action: 'delete'" (shifts subsequent steps down and removes it). Step entries apply in ASCENDING stepNumber order, each against the numbering the earlier entries of the SAME delta left behind \u2014 delete step 3 and step 7 becomes step 6 \u2014 so prefer labels, and restate the step's "label" or "description" on a delete to have it checked against the step actually addressed. Renumbering RELOCATES every flow jump field (onTrueStep/onFalseStep/cases.step/defaultStep/endStep/catches.step/finallyStep/toStep) in the same narrative. A delete is REJECTED when the narrative has no such step, when a jump still targets it (retarget the referrers first), when a restated label/description does not match, or when it is a loop/try/parallel header whose body would be left standing (retype the header first to dissolve the region, then delete it). Changing a step's "type" REBUILDS it for the new type: its description and label are kept and every field the new type cannot carry is dropped (returned as a NOTICE); a delta that retypes AND sets such a field is refused. A step delta is also refused when it carries a marker the merge does not recognise: a non-boolean "remove", an "action" that is neither "insert" nor "delete", a "captureJumps" outside an insert, or no "stepNumber" to address. Inserting AT a jump target relocates those jumps past the inserted step by default (a NOTICE is returned) \u2014 add "captureJumps": true on the inserted step to retarget entry jumps onto it (loop/try endStep region tails always relocate with the body and are never captured). Every jump field has a "*Label" twin (toLabel, onFalseLabel, endLabel, \u2026, and "label" on a cases/catches entry) resolved against step labels AFTER the merge, so a delta may anchor on a label only pre-existing steps carry; a label the delta supplies REPLACES the stored number it twins, while setting the number and its label together in one delta is refused as a contradiction. Reference ids in deltas may use LOCAL names \u2014 they are qualified against the spec's namespace exactly as the loader would. Per-spec lint suppression: set "lint: { allow: [{ code, at, covers, reason }] }" to silence a WARNING code on this spec only (errors always surface; stale allows are flagged). An allow covers EXACTLY the occurrence it names: "at" is the site the finding names (a contract method, an import edge "from -> to", a declared edge "component -> target") and is REQUIRED for a code whose findings report one, while a finding that reports no site is covered only by an allow that names none; "covers" lists the units an aggregating finding reports, and the allow silences it only when every one is listed. This delta is deliberately OPEN below its top level \u2014 the shapes nest further than a schema here should restate \u2014 so a key that is not a field at its depth is not refused, it is NAMED BACK under NO EFFECT in the answer, together with any value the spec already held and any "unset" that removed nothing. Read that list: it is where a nested typo shows up.`),
|
|
32062
|
+
delta: import_zod11.z.record(import_zod11.z.any()).describe(`The partial fields to merge into the spec. ARRAYS UPSERT, they do not replace: an array whose elements carry an identity is merged element-by-element, so a delta naming ONE element leaves the others intact. Identity is "name" or "id" by default, and per field: dispatch by "capability", a listener's mounts by "portal", lifecycle by phase+component+method, emits/subscribesTo by topic+event, trustedLinks by "subsystem", a subsystem's publicInterfaces by component+interface (or type+details for an entry not yet bound to a component), invariants and patterns by "id", lint.allow by "code" AND "at" (an allow covers one occurrence, so several may share a code on one spec), an interface method's findings by "code", boundaries by "name", globalRequirements by "description", switch cases by "value", try catches by "error". Identity merging applies at EVERY depth, including an array INSIDE an element (a method's params, a step's catches). Add "action: 'delete'" (or "remove: true") alongside that identity to REMOVE an element \u2014 including a stale lint allow. Arrays of plain STRINGS (owns, dependsOn, guarantees) carry no per-element identity and are replaced wholesale; pass [] to clear any array outright. To REMOVE an optional field entirely, list it in "unset": e.g. {"unset": ["basePath", "variant"]} \u2014 passing null/undefined means "no change" (they are skipped), and writing "" would leave the field present but empty, which is a different and usually wrong spec. Unsetting a required field is refused by schema validation, which names it. For narrative steps, match by "stepNumber" and use "action: 'insert'" (shifts subsequent steps up) or "action: 'delete'" (shifts subsequent steps down and removes it). Step entries apply in ASCENDING stepNumber order, each against the numbering the earlier entries of the SAME delta left behind \u2014 delete step 3 and step 7 becomes step 6 \u2014 so prefer labels, and restate the step's "label" or "description" on a delete to have it checked against the step actually addressed. Renumbering RELOCATES every flow jump field (onTrueStep/onFalseStep/cases.step/defaultStep/endStep/catches.step/finallyStep/toStep) in the same narrative. A delete is REJECTED when the narrative has no such step, when a jump still targets it (retarget the referrers first), when a restated label/description does not match, or when it is a loop/try/parallel header whose body would be left standing (retype the header first to dissolve the region, then delete it). Changing a step's "type" REBUILDS it for the new type: its description and label are kept and every field the new type cannot carry is dropped (returned as a NOTICE); a delta that retypes AND sets such a field is refused. A step delta is also refused when it carries a marker the merge does not recognise: a non-boolean "remove", an "action" that is neither "insert" nor "delete", a "captureJumps" outside an insert, or no "stepNumber" to address. Inserting AT a jump target relocates those jumps past the inserted step by default (a NOTICE is returned) \u2014 add "captureJumps": true on the inserted step to retarget entry jumps onto it (loop/try endStep region tails always relocate with the body and are never captured). Every jump field has a "*Label" twin (toLabel, onFalseLabel, endLabel, \u2026, and "label" on a cases/catches entry) resolved against step labels AFTER the merge, so a delta may anchor on a label only pre-existing steps carry; a label the delta supplies REPLACES the stored number it twins, while setting the number and its label together in one delta is refused as a contradiction. Reference ids in deltas may use LOCAL names \u2014 they are qualified against the spec's namespace exactly as the loader would. Per-spec lint suppression: set "lint: { allow: [{ code, at, covers, reason }] }" to silence a WARNING or NOTICE code on this spec only (errors always surface; stale allows are flagged). An allow covers EXACTLY the occurrence it names: "at" is the site the finding names (a contract method, an import edge "from -> to", a declared edge "component -> target") and is REQUIRED for a code whose findings report one, while a finding that reports no site is covered only by an allow that names none; "covers" lists the units an aggregating finding reports, and the allow silences it only when every one is listed. This delta is deliberately OPEN below its top level \u2014 the shapes nest further than a schema here should restate \u2014 so a key that is not a field at its depth is not refused, it is NAMED BACK under NO EFFECT in the answer, together with any value the spec already held and any "unset" that removed nothing. Read that list: it is where a nested typo shows up.`),
|
|
32030
32063
|
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.")
|
|
32031
32064
|
},
|
|
32032
32065
|
outputSchema: specChangeReportOutput
|
|
@@ -32342,7 +32375,7 @@ var init_server = __esm({
|
|
|
32342
32375
|
...staleServerOutput
|
|
32343
32376
|
};
|
|
32344
32377
|
validationIssueOutput = {
|
|
32345
|
-
severity: import_zod11.z.enum(["error", "warning"]).describe("The finding's severity after project overrides."),
|
|
32378
|
+
severity: import_zod11.z.enum(["error", "warning", "notice"]).describe("The finding's severity after project overrides."),
|
|
32346
32379
|
code: import_zod11.z.string().describe("The rule code, UPPER_SNAKE \u2014 the stable handle to filter and suppress by."),
|
|
32347
32380
|
message: import_zod11.z.string().describe("What is wrong, named."),
|
|
32348
32381
|
agentId: import_zod11.z.string().optional().describe("The agent the finding concerns, when it concerns one."),
|
|
@@ -32355,9 +32388,12 @@ var init_server = __esm({
|
|
|
32355
32388
|
)
|
|
32356
32389
|
};
|
|
32357
32390
|
validateTreeOutput = {
|
|
32358
|
-
valid: import_zod11.z.boolean().describe("False when the tree holds at least one error."),
|
|
32391
|
+
valid: import_zod11.z.boolean().describe("False when the tree holds at least one error; warnings and notices never make it false."),
|
|
32359
32392
|
errors: import_zod11.z.array(import_zod11.z.object(validationIssueOutput)).describe("Every finding of severity error."),
|
|
32360
32393
|
warnings: import_zod11.z.array(import_zod11.z.object(validationIssueOutput)).describe("Every finding of severity warning."),
|
|
32394
|
+
notices: import_zod11.z.array(import_zod11.z.object(validationIssueOutput)).describe(
|
|
32395
|
+
"Every finding of severity notice: reported, never a failure \u2014 they never make the tree invalid and never fail `wairon validate --ci`."
|
|
32396
|
+
),
|
|
32361
32397
|
resolvedThrough: import_zod11.z.object({
|
|
32362
32398
|
root: import_zod11.z.string().describe("The top root that was validated."),
|
|
32363
32399
|
scope: import_zod11.z.string().describe("The mount chain the verdict was scoped to.")
|
|
@@ -33688,7 +33724,8 @@ async function runLock(options = {}, gate) {
|
|
|
33688
33724
|
validationResult: {
|
|
33689
33725
|
valid: true,
|
|
33690
33726
|
errors: 0,
|
|
33691
|
-
warnings: gate ? gate.issues.filter((i) => i.severity === "warning").length : 0
|
|
33727
|
+
warnings: gate ? gate.issues.filter((i) => i.severity === "warning").length : 0,
|
|
33728
|
+
notices: gate ? gate.issues.filter((i) => i.severity === "notice").length : 0
|
|
33692
33729
|
},
|
|
33693
33730
|
status: "ready",
|
|
33694
33731
|
specs: captureApprovedSpecs(root, scope),
|
|
@@ -33740,6 +33777,7 @@ async function runValidate(options = {}) {
|
|
|
33740
33777
|
let hasErrors = false;
|
|
33741
33778
|
let hasFatalWarnings = false;
|
|
33742
33779
|
let waivedWarnings = 0;
|
|
33780
|
+
let noticeTotal = 0;
|
|
33743
33781
|
const legacySpecs = findLegacySpecFiles();
|
|
33744
33782
|
if (legacySpecs.length > 0) {
|
|
33745
33783
|
logger.warn(`Warning: ${legacySpecs.length} legacy spec filename(s) detected (e.g., component.yaml). These are deprecated. Please run \`wairon doctor --fix\` to migrate them to the new unified .index.yaml schema.`);
|
|
@@ -33754,6 +33792,9 @@ async function runValidate(options = {}) {
|
|
|
33754
33792
|
if (issue2.severity === "error") {
|
|
33755
33793
|
logger.error(`[${issue2.code}] ${issue2.message}`);
|
|
33756
33794
|
hasErrors = true;
|
|
33795
|
+
} else if (issue2.severity === "notice") {
|
|
33796
|
+
logger.notice(`[${issue2.code}] ${issue2.message}`);
|
|
33797
|
+
noticeTotal++;
|
|
33757
33798
|
} else {
|
|
33758
33799
|
logger.warn(`[${issue2.code}] ${issue2.message}`);
|
|
33759
33800
|
hasFatalWarnings = true;
|
|
@@ -33771,6 +33812,9 @@ async function runValidate(options = {}) {
|
|
|
33771
33812
|
if (issue2.severity === "error") {
|
|
33772
33813
|
logger.error(`${prefix}[${issue2.code}] ${issue2.message}`);
|
|
33773
33814
|
hasErrors = true;
|
|
33815
|
+
} else if (issue2.severity === "notice") {
|
|
33816
|
+
logger.notice(`${prefix}[${issue2.code}] ${issue2.message}`);
|
|
33817
|
+
noticeTotal++;
|
|
33774
33818
|
} else {
|
|
33775
33819
|
logger.warn(`${prefix}[${issue2.code}] ${issue2.message}`);
|
|
33776
33820
|
hasFatalWarnings = true;
|
|
@@ -33797,9 +33841,11 @@ async function runValidate(options = {}) {
|
|
|
33797
33841
|
} else {
|
|
33798
33842
|
let errorCount = 0;
|
|
33799
33843
|
let warningCount = 0;
|
|
33844
|
+
let noticeCount = 0;
|
|
33800
33845
|
const MAX_PRINT = 100;
|
|
33801
33846
|
let skippedErrors = 0;
|
|
33802
33847
|
let skippedWarnings = 0;
|
|
33848
|
+
let skippedNotices = 0;
|
|
33803
33849
|
for (const issue2 of sddResult.issues) {
|
|
33804
33850
|
const prefix = issue2.specId ? import_chalk5.default.gray(`[${issue2.specId}] `) : "";
|
|
33805
33851
|
if (issue2.severity === "error") {
|
|
@@ -33810,6 +33856,14 @@ async function runValidate(options = {}) {
|
|
|
33810
33856
|
} else {
|
|
33811
33857
|
skippedErrors++;
|
|
33812
33858
|
}
|
|
33859
|
+
} else if (issue2.severity === "notice") {
|
|
33860
|
+
noticeTotal++;
|
|
33861
|
+
if (noticeCount < MAX_PRINT) {
|
|
33862
|
+
logger.notice(`${prefix}[${issue2.code}] ${issue2.message}`);
|
|
33863
|
+
noticeCount++;
|
|
33864
|
+
} else {
|
|
33865
|
+
skippedNotices++;
|
|
33866
|
+
}
|
|
33813
33867
|
} else {
|
|
33814
33868
|
if (isCiDraftWaivable(issue2)) {
|
|
33815
33869
|
waivedWarnings++;
|
|
@@ -33830,6 +33884,9 @@ async function runValidate(options = {}) {
|
|
|
33830
33884
|
if (skippedWarnings > 0) {
|
|
33831
33885
|
logger.warn(`... and ${skippedWarnings} more warning(s) omitted. Use '--subsystem <id>' to validate a specific subsystem.`);
|
|
33832
33886
|
}
|
|
33887
|
+
if (skippedNotices > 0) {
|
|
33888
|
+
logger.notice(`... and ${skippedNotices} more notice(s) omitted. Use '--subsystem <id>' to validate a specific subsystem.`);
|
|
33889
|
+
}
|
|
33833
33890
|
}
|
|
33834
33891
|
}
|
|
33835
33892
|
const summary = carriedDebtSummary(projectConfig.rules?.conformance?.carried);
|
|
@@ -33839,6 +33896,9 @@ async function runValidate(options = {}) {
|
|
|
33839
33896
|
for (const note of summary.revisits) logger.info(import_chalk5.default.gray(note));
|
|
33840
33897
|
}
|
|
33841
33898
|
logger.blank();
|
|
33899
|
+
if (noticeTotal > 0) {
|
|
33900
|
+
logger.info(import_chalk5.default.blue(`${noticeTotal} notice(s) reported \u2014 never part of the failure decision${options.ci ? " (--ci does not fail on notices)" : ""}.`));
|
|
33901
|
+
}
|
|
33842
33902
|
if (options.ci && waivedWarnings > 0) {
|
|
33843
33903
|
logger.info(import_chalk5.default.gray(`${waivedWarnings} draft-related warning(s) (non-fatal in --ci): excluded from the failure decision because the referenced specs are in draft/design status.`));
|
|
33844
33904
|
}
|
|
@@ -33853,7 +33913,7 @@ async function runValidate(options = {}) {
|
|
|
33853
33913
|
process.exit(1);
|
|
33854
33914
|
} else {
|
|
33855
33915
|
if (options.ci) {
|
|
33856
|
-
logger.success("All checks passed (CI mode \u2014 warnings treated as errors, draft-related warnings excepted).");
|
|
33916
|
+
logger.success("All checks passed (CI mode \u2014 warnings treated as errors, draft-related warnings excepted; notices never fail).");
|
|
33857
33917
|
} else {
|
|
33858
33918
|
logger.success("All checks passed.");
|
|
33859
33919
|
}
|
|
@@ -34880,8 +34940,11 @@ async function runDoctor(options = {}) {
|
|
|
34880
34940
|
const result = validateSddTree(cfg.rules, cfg.projectType);
|
|
34881
34941
|
const errs = result.issues.filter((i) => i.severity === "error").length;
|
|
34882
34942
|
const warns = result.issues.filter((i) => i.severity === "warning").length;
|
|
34883
|
-
|
|
34884
|
-
|
|
34943
|
+
const notes = result.issues.filter((i) => i.severity === "notice").length;
|
|
34944
|
+
const noted = notes > 0 ? `, ${notes} notice(s)` : "";
|
|
34945
|
+
if (errs > 0) line(tally, "error", `Conformance: ${errs} error(s), ${warns} warning(s)${noted} \u2014 see \`wairon validate\` (you: \`sdd_validate_tree\`)`);
|
|
34946
|
+
else if (warns > 0) line(tally, "warn", `Conformance: ${warns} warning(s)${noted} \u2014 see \`wairon validate\` (you: \`sdd_validate_tree\`)`);
|
|
34947
|
+
else if (notes > 0) line(tally, "ok", `Conformance: 0 errors, 0 warnings, ${notes} notice(s) \u2014 see \`wairon validate\` (you: \`sdd_validate_tree\`)`);
|
|
34885
34948
|
else line(tally, "ok", "Conformance: 0 errors, 0 warnings");
|
|
34886
34949
|
} catch (e) {
|
|
34887
34950
|
line(tally, "warn", `Could not run conformance check: ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -35371,6 +35434,7 @@ async function listRules3() {
|
|
|
35371
35434
|
const sevLabel = (sev) => {
|
|
35372
35435
|
if (sev === "error") return import_chalk13.default.red("error ");
|
|
35373
35436
|
if (sev === "warning") return import_chalk13.default.yellow("warning");
|
|
35437
|
+
if (sev === "notice") return import_chalk13.default.blue("notice ");
|
|
35374
35438
|
return import_chalk13.default.gray("off ");
|
|
35375
35439
|
};
|
|
35376
35440
|
const ext = loadProjectExtensions();
|
|
@@ -35403,7 +35467,7 @@ SDD conformance rules (${active.length})
|
|
|
35403
35467
|
for (const err of ext.errors) {
|
|
35404
35468
|
logger.error(err);
|
|
35405
35469
|
}
|
|
35406
|
-
logger.info("Override severities per project via rules.sddRuleSeverity in .wai/project.yaml (error | warning | off).");
|
|
35470
|
+
logger.info("Override severities per project via rules.sddRuleSeverity in .wai/project.yaml (error | warning | notice | off). A notice is reported but never fails the gate, --ci included.");
|
|
35407
35471
|
if (ext.packNames.length) {
|
|
35408
35472
|
logger.info(`Extension packs loaded: ${ext.packNames.join(", ")} (.wai/project.yaml \u2192 extensions.packs).`);
|
|
35409
35473
|
}
|
|
@@ -38454,7 +38518,8 @@ function executeApprovedLock(cfg, projectId, approver, subproject) {
|
|
|
38454
38518
|
validationResult: {
|
|
38455
38519
|
valid: true,
|
|
38456
38520
|
errors: 0,
|
|
38457
|
-
warnings: result.issues.filter((i) => i.severity === "warning").length
|
|
38521
|
+
warnings: result.issues.filter((i) => i.severity === "warning").length,
|
|
38522
|
+
notices: result.issues.filter((i) => i.severity === "notice").length
|
|
38458
38523
|
},
|
|
38459
38524
|
status: "ready",
|
|
38460
38525
|
specs,
|
|
@@ -44718,13 +44783,22 @@ function reshapeLandscapeGraph(model, level) {
|
|
|
44718
44783
|
}
|
|
44719
44784
|
function overlayProjectIssues(graph, issues, projectId, level) {
|
|
44720
44785
|
const counts = /* @__PURE__ */ new Map();
|
|
44786
|
+
const noticeCounts = /* @__PURE__ */ new Map();
|
|
44721
44787
|
for (const issue2 of issues) {
|
|
44722
44788
|
const id = issue2.specId;
|
|
44723
|
-
if (id)
|
|
44789
|
+
if (!id) continue;
|
|
44790
|
+
const into = issue2.severity === "notice" ? noticeCounts : counts;
|
|
44791
|
+
into.set(id, (into.get(id) ?? 0) + 1);
|
|
44724
44792
|
}
|
|
44725
44793
|
const nodes = graph.nodes.map((n) => {
|
|
44726
44794
|
const count = counts.get(n.id);
|
|
44727
|
-
|
|
44795
|
+
const notices = noticeCounts.get(n.id);
|
|
44796
|
+
if (count === void 0 && notices === void 0) return n;
|
|
44797
|
+
return {
|
|
44798
|
+
...n,
|
|
44799
|
+
...count !== void 0 ? { issueCount: count } : {},
|
|
44800
|
+
...notices !== void 0 ? { noticeCount: notices } : {}
|
|
44801
|
+
};
|
|
44728
44802
|
});
|
|
44729
44803
|
return {
|
|
44730
44804
|
...graph,
|
|
@@ -45428,7 +45502,8 @@ details.adv summary { cursor:pointer; color:var(--dim); font-size:12px; margin-b
|
|
|
45428
45502
|
nodes.forEach(function (n) {
|
|
45429
45503
|
var li = document.createElement('li');
|
|
45430
45504
|
li.innerHTML = '<span class="kind">' + esc(n.kind) + '</span><span>' + esc(n.label || n.id) + '</span>'
|
|
45431
|
-
+ (n.issueCount ? ' <span class="pill bad" title="validation
|
|
45505
|
+
+ (n.issueCount ? ' <span class="pill bad" title="validation errors and warnings">' + n.issueCount + '</span>' : '')
|
|
45506
|
+
+ (n.noticeCount ? ' <span class="pill" title="validation notices">' + n.noticeCount + '</span>' : '');
|
|
45432
45507
|
li.addEventListener('click', function () {
|
|
45433
45508
|
Array.prototype.forEach.call(list.children, function (x) { x.classList.remove('sel'); });
|
|
45434
45509
|
li.classList.add('sel');
|
|
@@ -45471,14 +45546,18 @@ details.adv summary { cursor:pointer; color:var(--dim); font-size:12px; margin-b
|
|
|
45471
45546
|
$('btnValidate').addEventListener('click', function () {
|
|
45472
45547
|
var insp = $('insp'); insp.innerHTML = '<div class="hint">Validating\u2026</div>';
|
|
45473
45548
|
mcp('sdd_validate_tree', {}).then(function (res) {
|
|
45474
|
-
|
|
45549
|
+
// sdd_validate_tree answers with three lists \u2014 errors, warnings, notices;
|
|
45550
|
+
// this view used to read an "issues" list the tool never sends, so it
|
|
45551
|
+
// reported every tree clean.
|
|
45552
|
+
var issues = (res && res.issues) || [].concat((res && res.errors) || [], (res && res.warnings) || [], (res && res.notices) || []);
|
|
45475
45553
|
var errs = issues.filter(function (i) { return i.severity === 'error'; }).length;
|
|
45476
|
-
var
|
|
45477
|
-
var
|
|
45554
|
+
var notes = issues.filter(function (i) { return i.severity === 'notice'; }).length;
|
|
45555
|
+
var warns = issues.length - errs - notes;
|
|
45556
|
+
var html = '<h2>Validation</h2><p class="meta">' + (issues.length ? (errs + ' error(s), ' + warns + ' warning(s), ' + notes + ' notice(s)') : 'clean \u2014 no findings') + '</p>';
|
|
45478
45557
|
if (issues.length) {
|
|
45479
45558
|
html += '<table class="grid"><thead><tr><th>Severity</th><th>Code</th><th>Spec</th><th>Message</th></tr></thead><tbody>';
|
|
45480
45559
|
issues.slice(0, 200).forEach(function (i) {
|
|
45481
|
-
var cls = i.severity === 'error' ? 'bad' : 'warn';
|
|
45560
|
+
var cls = i.severity === 'error' ? 'bad' : i.severity === 'notice' ? '' : 'warn';
|
|
45482
45561
|
html += '<tr><td><span class="pill ' + cls + '">' + esc(i.severity) + '</span></td><td>' + esc(i.code) + '</td><td>' + esc(i.specId || '') + '</td><td>' + esc(i.message) + '</td></tr>';
|
|
45483
45562
|
});
|
|
45484
45563
|
html += '</tbody></table>';
|
|
@@ -50455,9 +50534,11 @@ async function validateCommand(opts) {
|
|
|
50455
50534
|
const report2 = await validateAttached(target, opts.subsystem);
|
|
50456
50535
|
const errors = report2.errors ?? [];
|
|
50457
50536
|
const warnings = report2.warnings ?? [];
|
|
50458
|
-
|
|
50537
|
+
const notices = report2.notices ?? [];
|
|
50538
|
+
logger.info(`Validated "${target.projectId}" on ${target.url} \u2014 ${errors.length} error(s), ${warnings.length} warning(s), ${notices.length} notice(s).`);
|
|
50459
50539
|
for (const e of errors) logger.error(` [${e.code}] ${e.specId ? `${e.specId}: ` : ""}${e.message}`);
|
|
50460
50540
|
for (const w of warnings) logger.warn(` [${w.code}] ${w.specId ? `${w.specId}: ` : ""}${w.message}`);
|
|
50541
|
+
for (const n of notices) logger.notice(` [${n.code}] ${n.specId ? `${n.specId}: ` : ""}${n.message}`);
|
|
50461
50542
|
if (errors.length || opts.ci && warnings.length) process.exit(1);
|
|
50462
50543
|
return;
|
|
50463
50544
|
}
|
|
@@ -50487,7 +50568,7 @@ program.command("lock-check").description("Merge gate: is the design in this wor
|
|
|
50487
50568
|
program.command("lock").description("Final check before implementation: validate the spec tree as complete, freeze all specs to complete, and (re)generate the agent topology \u2014 only if it validates. In an attached checkout, locks the hosted project instead.").option("-y, --yes", "skip the confirmation prompt (for scripts / CI)").option("--subsystem <id>", "only lock specs in the specified subsystem").option("--no-recursive", "do not recursively validate subprojects").action(async (opts) => {
|
|
50488
50569
|
await lockCommand(opts);
|
|
50489
50570
|
});
|
|
50490
|
-
program.command("validate").description("Validate the project configuration and the SDD Spec Tree").option("--ci", "treat warnings as errors for CI pipelines").option("--subsystem <id>", "only validate the specified subsystem (granular)").option("--no-recursive", "do not recursively validate subprojects").action(async (opts) => {
|
|
50571
|
+
program.command("validate").description("Validate the project configuration and the SDD Spec Tree").option("--ci", "treat warnings as errors for CI pipelines (notices are printed and counted, never fatal)").option("--subsystem <id>", "only validate the specified subsystem (granular)").option("--no-recursive", "do not recursively validate subprojects").action(async (opts) => {
|
|
50491
50572
|
await validateCommand(opts);
|
|
50492
50573
|
});
|
|
50493
50574
|
program.command("status").description("Show a hierarchical completeness graph of the SDD Spec Tree").option("--subsystem <id>", "only show status for the specified subsystem").option("--no-recursive", "do not recursively show status for subprojects").action(async (opts) => {
|