@wairon/cli 5.1.1-dev.77 → 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 +128 -38
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +68 -23
- 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,11 +30250,15 @@ 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
|
-
function componentCandidateGate(options = candidateOptions()) {
|
|
30258
|
+
function componentCandidateGate(options = candidateOptions(), storedOwner) {
|
|
30228
30259
|
return {
|
|
30229
30260
|
gate: (kind, merged) => {
|
|
30261
|
+
refuseUnknownOwner(kind, merged, storedOwner);
|
|
30230
30262
|
if (kind !== "component") return;
|
|
30231
30263
|
const verdict = validateComponentCandidate(merged, options);
|
|
30232
30264
|
if (verdict.errors.length) throw new Error(formatCandidateRefusal(verdict));
|
|
@@ -30234,6 +30266,14 @@ function componentCandidateGate(options = candidateOptions()) {
|
|
|
30234
30266
|
}
|
|
30235
30267
|
};
|
|
30236
30268
|
}
|
|
30269
|
+
function refuseUnknownOwner(kind, merged, storedOwner) {
|
|
30270
|
+
if (kind !== "component" && kind !== "type") return;
|
|
30271
|
+
const owner = merged.subsystem;
|
|
30272
|
+
if (!owner || owner === storedOwner) return;
|
|
30273
|
+
if (loadSpec("subsystem", owner)) return;
|
|
30274
|
+
const sentence = MISSING_PARENT[kind](owner);
|
|
30275
|
+
throw new Error(`${sentence} Nothing was written.`);
|
|
30276
|
+
}
|
|
30237
30277
|
function restatementParent(restatement) {
|
|
30238
30278
|
const spec = restatement.spec;
|
|
30239
30279
|
switch (restatement.kind) {
|
|
@@ -30497,9 +30537,9 @@ function deleteSpec2(kind, id) {
|
|
|
30497
30537
|
}
|
|
30498
30538
|
function updateSpecGated(kind, id, delta, dryRun) {
|
|
30499
30539
|
const bound2 = candidateOptions();
|
|
30500
|
-
const gate = componentCandidateGate(bound2);
|
|
30501
30540
|
const testRoots = bound2.rules?.conformance?.testRoots ?? [];
|
|
30502
|
-
const stored =
|
|
30541
|
+
const stored = loadSpec(kind, id);
|
|
30542
|
+
const gate = componentCandidateGate(bound2, stored?.subsystem);
|
|
30503
30543
|
const report2 = updateSpec(kind, id, delta, gate, dryRun);
|
|
30504
30544
|
const changed = changedMethods(report2);
|
|
30505
30545
|
if (changed.length > 0 && testRoots.length > 0) {
|
|
@@ -31143,7 +31183,7 @@ function createMcpServer(options = {}) {
|
|
|
31143
31183
|
server,
|
|
31144
31184
|
"validateTopology",
|
|
31145
31185
|
{
|
|
31146
|
-
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.",
|
|
31147
31187
|
inputSchema: {
|
|
31148
31188
|
subsystem: import_zod11.z.string().optional().describe("Only validate topology for agents under the specified subsystem")
|
|
31149
31189
|
}
|
|
@@ -31163,7 +31203,8 @@ function createMcpServer(options = {}) {
|
|
|
31163
31203
|
return json({
|
|
31164
31204
|
valid: result.issues.filter((i) => i.severity === "error").length === 0,
|
|
31165
31205
|
errors: result.issues.filter((i) => i.severity === "error"),
|
|
31166
|
-
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")
|
|
31167
31208
|
});
|
|
31168
31209
|
} catch (e) {
|
|
31169
31210
|
return errText(String(e));
|
|
@@ -31881,7 +31922,7 @@ ${renderChangeReport(report2)}`,
|
|
|
31881
31922
|
server,
|
|
31882
31923
|
"sdd_validate_tree",
|
|
31883
31924
|
{
|
|
31884
|
-
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.",
|
|
31885
31926
|
inputSchema: {
|
|
31886
31927
|
subsystem: import_zod11.z.string().optional().describe("Only validate the specified subsystem (granular)"),
|
|
31887
31928
|
recursive: import_zod11.z.boolean().optional().describe("Whether to recursively validate subprojects (default: true)")
|
|
@@ -31902,6 +31943,7 @@ ${renderChangeReport(report2)}`,
|
|
|
31902
31943
|
valid: result.valid,
|
|
31903
31944
|
errors: result.issues.filter((i) => i.severity === "error"),
|
|
31904
31945
|
warnings: result.issues.filter((i) => i.severity === "warning"),
|
|
31946
|
+
notices: result.issues.filter((i) => i.severity === "notice"),
|
|
31905
31947
|
...result.resolvedThrough ? { resolvedThrough: result.resolvedThrough } : {}
|
|
31906
31948
|
});
|
|
31907
31949
|
} catch (e) {
|
|
@@ -32017,7 +32059,7 @@ ${testsBlock}` : ""}`, deletion);
|
|
|
32017
32059
|
inputSchema: {
|
|
32018
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)"),
|
|
32019
32061
|
id: import_zod11.z.string().describe("The ID of the spec to update (namespaced if needed)"),
|
|
32020
|
-
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.`),
|
|
32021
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.")
|
|
32022
32064
|
},
|
|
32023
32065
|
outputSchema: specChangeReportOutput
|
|
@@ -32333,7 +32375,7 @@ var init_server = __esm({
|
|
|
32333
32375
|
...staleServerOutput
|
|
32334
32376
|
};
|
|
32335
32377
|
validationIssueOutput = {
|
|
32336
|
-
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."),
|
|
32337
32379
|
code: import_zod11.z.string().describe("The rule code, UPPER_SNAKE \u2014 the stable handle to filter and suppress by."),
|
|
32338
32380
|
message: import_zod11.z.string().describe("What is wrong, named."),
|
|
32339
32381
|
agentId: import_zod11.z.string().optional().describe("The agent the finding concerns, when it concerns one."),
|
|
@@ -32346,9 +32388,12 @@ var init_server = __esm({
|
|
|
32346
32388
|
)
|
|
32347
32389
|
};
|
|
32348
32390
|
validateTreeOutput = {
|
|
32349
|
-
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."),
|
|
32350
32392
|
errors: import_zod11.z.array(import_zod11.z.object(validationIssueOutput)).describe("Every finding of severity error."),
|
|
32351
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
|
+
),
|
|
32352
32397
|
resolvedThrough: import_zod11.z.object({
|
|
32353
32398
|
root: import_zod11.z.string().describe("The top root that was validated."),
|
|
32354
32399
|
scope: import_zod11.z.string().describe("The mount chain the verdict was scoped to.")
|
|
@@ -33679,7 +33724,8 @@ async function runLock(options = {}, gate) {
|
|
|
33679
33724
|
validationResult: {
|
|
33680
33725
|
valid: true,
|
|
33681
33726
|
errors: 0,
|
|
33682
|
-
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
|
|
33683
33729
|
},
|
|
33684
33730
|
status: "ready",
|
|
33685
33731
|
specs: captureApprovedSpecs(root, scope),
|
|
@@ -33731,6 +33777,7 @@ async function runValidate(options = {}) {
|
|
|
33731
33777
|
let hasErrors = false;
|
|
33732
33778
|
let hasFatalWarnings = false;
|
|
33733
33779
|
let waivedWarnings = 0;
|
|
33780
|
+
let noticeTotal = 0;
|
|
33734
33781
|
const legacySpecs = findLegacySpecFiles();
|
|
33735
33782
|
if (legacySpecs.length > 0) {
|
|
33736
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.`);
|
|
@@ -33745,6 +33792,9 @@ async function runValidate(options = {}) {
|
|
|
33745
33792
|
if (issue2.severity === "error") {
|
|
33746
33793
|
logger.error(`[${issue2.code}] ${issue2.message}`);
|
|
33747
33794
|
hasErrors = true;
|
|
33795
|
+
} else if (issue2.severity === "notice") {
|
|
33796
|
+
logger.notice(`[${issue2.code}] ${issue2.message}`);
|
|
33797
|
+
noticeTotal++;
|
|
33748
33798
|
} else {
|
|
33749
33799
|
logger.warn(`[${issue2.code}] ${issue2.message}`);
|
|
33750
33800
|
hasFatalWarnings = true;
|
|
@@ -33762,6 +33812,9 @@ async function runValidate(options = {}) {
|
|
|
33762
33812
|
if (issue2.severity === "error") {
|
|
33763
33813
|
logger.error(`${prefix}[${issue2.code}] ${issue2.message}`);
|
|
33764
33814
|
hasErrors = true;
|
|
33815
|
+
} else if (issue2.severity === "notice") {
|
|
33816
|
+
logger.notice(`${prefix}[${issue2.code}] ${issue2.message}`);
|
|
33817
|
+
noticeTotal++;
|
|
33765
33818
|
} else {
|
|
33766
33819
|
logger.warn(`${prefix}[${issue2.code}] ${issue2.message}`);
|
|
33767
33820
|
hasFatalWarnings = true;
|
|
@@ -33788,9 +33841,11 @@ async function runValidate(options = {}) {
|
|
|
33788
33841
|
} else {
|
|
33789
33842
|
let errorCount = 0;
|
|
33790
33843
|
let warningCount = 0;
|
|
33844
|
+
let noticeCount = 0;
|
|
33791
33845
|
const MAX_PRINT = 100;
|
|
33792
33846
|
let skippedErrors = 0;
|
|
33793
33847
|
let skippedWarnings = 0;
|
|
33848
|
+
let skippedNotices = 0;
|
|
33794
33849
|
for (const issue2 of sddResult.issues) {
|
|
33795
33850
|
const prefix = issue2.specId ? import_chalk5.default.gray(`[${issue2.specId}] `) : "";
|
|
33796
33851
|
if (issue2.severity === "error") {
|
|
@@ -33801,6 +33856,14 @@ async function runValidate(options = {}) {
|
|
|
33801
33856
|
} else {
|
|
33802
33857
|
skippedErrors++;
|
|
33803
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
|
+
}
|
|
33804
33867
|
} else {
|
|
33805
33868
|
if (isCiDraftWaivable(issue2)) {
|
|
33806
33869
|
waivedWarnings++;
|
|
@@ -33821,6 +33884,9 @@ async function runValidate(options = {}) {
|
|
|
33821
33884
|
if (skippedWarnings > 0) {
|
|
33822
33885
|
logger.warn(`... and ${skippedWarnings} more warning(s) omitted. Use '--subsystem <id>' to validate a specific subsystem.`);
|
|
33823
33886
|
}
|
|
33887
|
+
if (skippedNotices > 0) {
|
|
33888
|
+
logger.notice(`... and ${skippedNotices} more notice(s) omitted. Use '--subsystem <id>' to validate a specific subsystem.`);
|
|
33889
|
+
}
|
|
33824
33890
|
}
|
|
33825
33891
|
}
|
|
33826
33892
|
const summary = carriedDebtSummary(projectConfig.rules?.conformance?.carried);
|
|
@@ -33830,6 +33896,9 @@ async function runValidate(options = {}) {
|
|
|
33830
33896
|
for (const note of summary.revisits) logger.info(import_chalk5.default.gray(note));
|
|
33831
33897
|
}
|
|
33832
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
|
+
}
|
|
33833
33902
|
if (options.ci && waivedWarnings > 0) {
|
|
33834
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.`));
|
|
33835
33904
|
}
|
|
@@ -33844,7 +33913,7 @@ async function runValidate(options = {}) {
|
|
|
33844
33913
|
process.exit(1);
|
|
33845
33914
|
} else {
|
|
33846
33915
|
if (options.ci) {
|
|
33847
|
-
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).");
|
|
33848
33917
|
} else {
|
|
33849
33918
|
logger.success("All checks passed.");
|
|
33850
33919
|
}
|
|
@@ -34871,8 +34940,11 @@ async function runDoctor(options = {}) {
|
|
|
34871
34940
|
const result = validateSddTree(cfg.rules, cfg.projectType);
|
|
34872
34941
|
const errs = result.issues.filter((i) => i.severity === "error").length;
|
|
34873
34942
|
const warns = result.issues.filter((i) => i.severity === "warning").length;
|
|
34874
|
-
|
|
34875
|
-
|
|
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\`)`);
|
|
34876
34948
|
else line(tally, "ok", "Conformance: 0 errors, 0 warnings");
|
|
34877
34949
|
} catch (e) {
|
|
34878
34950
|
line(tally, "warn", `Could not run conformance check: ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -35362,6 +35434,7 @@ async function listRules3() {
|
|
|
35362
35434
|
const sevLabel = (sev) => {
|
|
35363
35435
|
if (sev === "error") return import_chalk13.default.red("error ");
|
|
35364
35436
|
if (sev === "warning") return import_chalk13.default.yellow("warning");
|
|
35437
|
+
if (sev === "notice") return import_chalk13.default.blue("notice ");
|
|
35365
35438
|
return import_chalk13.default.gray("off ");
|
|
35366
35439
|
};
|
|
35367
35440
|
const ext = loadProjectExtensions();
|
|
@@ -35394,7 +35467,7 @@ SDD conformance rules (${active.length})
|
|
|
35394
35467
|
for (const err of ext.errors) {
|
|
35395
35468
|
logger.error(err);
|
|
35396
35469
|
}
|
|
35397
|
-
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.");
|
|
35398
35471
|
if (ext.packNames.length) {
|
|
35399
35472
|
logger.info(`Extension packs loaded: ${ext.packNames.join(", ")} (.wai/project.yaml \u2192 extensions.packs).`);
|
|
35400
35473
|
}
|
|
@@ -38445,7 +38518,8 @@ function executeApprovedLock(cfg, projectId, approver, subproject) {
|
|
|
38445
38518
|
validationResult: {
|
|
38446
38519
|
valid: true,
|
|
38447
38520
|
errors: 0,
|
|
38448
|
-
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
|
|
38449
38523
|
},
|
|
38450
38524
|
status: "ready",
|
|
38451
38525
|
specs,
|
|
@@ -44709,13 +44783,22 @@ function reshapeLandscapeGraph(model, level) {
|
|
|
44709
44783
|
}
|
|
44710
44784
|
function overlayProjectIssues(graph, issues, projectId, level) {
|
|
44711
44785
|
const counts = /* @__PURE__ */ new Map();
|
|
44786
|
+
const noticeCounts = /* @__PURE__ */ new Map();
|
|
44712
44787
|
for (const issue2 of issues) {
|
|
44713
44788
|
const id = issue2.specId;
|
|
44714
|
-
if (id)
|
|
44789
|
+
if (!id) continue;
|
|
44790
|
+
const into = issue2.severity === "notice" ? noticeCounts : counts;
|
|
44791
|
+
into.set(id, (into.get(id) ?? 0) + 1);
|
|
44715
44792
|
}
|
|
44716
44793
|
const nodes = graph.nodes.map((n) => {
|
|
44717
44794
|
const count = counts.get(n.id);
|
|
44718
|
-
|
|
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
|
+
};
|
|
44719
44802
|
});
|
|
44720
44803
|
return {
|
|
44721
44804
|
...graph,
|
|
@@ -45419,7 +45502,8 @@ details.adv summary { cursor:pointer; color:var(--dim); font-size:12px; margin-b
|
|
|
45419
45502
|
nodes.forEach(function (n) {
|
|
45420
45503
|
var li = document.createElement('li');
|
|
45421
45504
|
li.innerHTML = '<span class="kind">' + esc(n.kind) + '</span><span>' + esc(n.label || n.id) + '</span>'
|
|
45422
|
-
+ (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>' : '');
|
|
45423
45507
|
li.addEventListener('click', function () {
|
|
45424
45508
|
Array.prototype.forEach.call(list.children, function (x) { x.classList.remove('sel'); });
|
|
45425
45509
|
li.classList.add('sel');
|
|
@@ -45462,14 +45546,18 @@ details.adv summary { cursor:pointer; color:var(--dim); font-size:12px; margin-b
|
|
|
45462
45546
|
$('btnValidate').addEventListener('click', function () {
|
|
45463
45547
|
var insp = $('insp'); insp.innerHTML = '<div class="hint">Validating\u2026</div>';
|
|
45464
45548
|
mcp('sdd_validate_tree', {}).then(function (res) {
|
|
45465
|
-
|
|
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) || []);
|
|
45466
45553
|
var errs = issues.filter(function (i) { return i.severity === 'error'; }).length;
|
|
45467
|
-
var
|
|
45468
|
-
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>';
|
|
45469
45557
|
if (issues.length) {
|
|
45470
45558
|
html += '<table class="grid"><thead><tr><th>Severity</th><th>Code</th><th>Spec</th><th>Message</th></tr></thead><tbody>';
|
|
45471
45559
|
issues.slice(0, 200).forEach(function (i) {
|
|
45472
|
-
var cls = i.severity === 'error' ? 'bad' : 'warn';
|
|
45560
|
+
var cls = i.severity === 'error' ? 'bad' : i.severity === 'notice' ? '' : 'warn';
|
|
45473
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>';
|
|
45474
45562
|
});
|
|
45475
45563
|
html += '</tbody></table>';
|
|
@@ -50446,9 +50534,11 @@ async function validateCommand(opts) {
|
|
|
50446
50534
|
const report2 = await validateAttached(target, opts.subsystem);
|
|
50447
50535
|
const errors = report2.errors ?? [];
|
|
50448
50536
|
const warnings = report2.warnings ?? [];
|
|
50449
|
-
|
|
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).`);
|
|
50450
50539
|
for (const e of errors) logger.error(` [${e.code}] ${e.specId ? `${e.specId}: ` : ""}${e.message}`);
|
|
50451
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}`);
|
|
50452
50542
|
if (errors.length || opts.ci && warnings.length) process.exit(1);
|
|
50453
50543
|
return;
|
|
50454
50544
|
}
|
|
@@ -50478,7 +50568,7 @@ program.command("lock-check").description("Merge gate: is the design in this wor
|
|
|
50478
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) => {
|
|
50479
50569
|
await lockCommand(opts);
|
|
50480
50570
|
});
|
|
50481
|
-
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) => {
|
|
50482
50572
|
await validateCommand(opts);
|
|
50483
50573
|
});
|
|
50484
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) => {
|