docguard-cli 0.40.5 β 0.41.0
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/CHANGELOG.md +3218 -0
- package/README.md +25 -16
- package/cli/assessment.mjs +94 -0
- package/cli/commands/ci.mjs +15 -5
- package/cli/commands/diagnose.mjs +20 -13
- package/cli/commands/fix.mjs +14 -45
- package/cli/commands/guard.mjs +53 -25
- package/cli/commands/hooks.mjs +51 -10
- package/cli/commands/init.mjs +15 -0
- package/cli/commands/reconcile.mjs +10 -3
- package/cli/commands/report.mjs +5 -1
- package/cli/commands/score.mjs +2 -1
- package/cli/commands/upgrade.mjs +4 -1
- package/cli/commands/verify.mjs +9 -2
- package/cli/commands/watch.mjs +3 -2
- package/cli/config.mjs +23 -0
- package/cli/evidence/adapters.mjs +14 -0
- package/cli/evidence/manifest.mjs +15 -0
- package/cli/evidence/python-literal.mjs +304 -0
- package/cli/findings.mjs +17 -3
- package/cli/scanners/instruction-audit.mjs +88 -11
- package/cli/scanners/js-ast.mjs +156 -18
- package/cli/scanners/reconciliation.mjs +56 -6
- package/cli/scanners/routes.mjs +84 -9
- package/cli/scanners/spec-registry.mjs +29 -0
- package/cli/shared-git.mjs +98 -0
- package/cli/shared-ignore.mjs +1 -1
- package/cli/shared.mjs +30 -1
- package/cli/validators/api-doc-smells.mjs +2 -2
- package/cli/validators/api-surface.mjs +4 -9
- package/cli/validators/diff-suspicion.mjs +3 -2
- package/cli/validators/docs-sync.mjs +45 -29
- package/cli/validators/environment.mjs +64 -6
- package/cli/validators/metrics-consistency.mjs +52 -11
- package/cli/validators/reference-existence.mjs +4 -2
- package/cli/validators/security.mjs +37 -12
- package/cli/validators/spec-registry.mjs +10 -7
- package/cli/validators/todo-tracking.mjs +31 -11
- package/cli/validators/traceability.mjs +29 -4
- package/cli/writers/junit.mjs +3 -3
- package/cli/writers/sarif.mjs +13 -9
- package/docs/configuration.md +12 -1
- package/extensions/spec-kit-docguard/extension.yml +1 -1
- package/extensions/spec-kit-docguard/skills/docguard-fix/SKILL.md +2 -2
- package/extensions/spec-kit-docguard/skills/docguard-guard/SKILL.md +2 -2
- package/extensions/spec-kit-docguard/skills/docguard-review/SKILL.md +2 -2
- package/extensions/spec-kit-docguard/skills/docguard-score/SKILL.md +2 -2
- package/extensions/spec-kit-docguard/skills/docguard-sync/SKILL.md +2 -2
- package/extensions/spec-kit-docguard/templates/github-workflows/docguard-autofix.yml +1 -1
- package/extensions/spec-kit-docguard/templates/github-workflows/docguard-guard.yml +1 -1
- package/package.json +2 -1
- package/schemas/docguard-config.schema.json +15 -1
- package/schemas/docguard-evidence.schema.json +12 -0
- package/templates/ci/github-actions.yml +1 -1
- package/templates/evidence-manifest.json +16 -0
package/README.md
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
> ```
|
|
25
25
|
> Runs against a baked-in sample project with intentional drift and shows you the findings + a clear path to fixing them.
|
|
26
26
|
|
|
27
|
-

|
|
27
|
+

|
|
28
28
|
|
|
29
29
|
---
|
|
30
30
|
|
|
@@ -63,7 +63,7 @@ DocGuard enforces **Canonical-Driven Development (CDD)** β a methodology where
|
|
|
63
63
|
|
|
64
64
|
DocGuard is an official [GitHub Spec Kit](https://github.com/github/spec-kit) community extension. It validates the artifacts that Spec Kit creates, ensuring your specs stay high-quality throughout the development lifecycle.
|
|
65
65
|
|
|
66
|
-
π **[Philosophy](PHILOSOPHY.md)** Β· π **[CDD Standard](STANDARD.md)** Β· βοΈ **[Comparisons](COMPARISONS.md)** Β· π¬ **[Validation](VALIDATION.md)** Β· πΊοΈ **[Roadmap](ROADMAP.md)**
|
|
66
|
+
π **[Philosophy](PHILOSOPHY.md)** Β· π **[CDD Standard](STANDARD.md)** Β· βοΈ **[Comparisons](https://github.com/raccioly/docguard/blob/main/COMPARISONS.md)** Β· π¬ **[Validation](https://github.com/raccioly/docguard/blob/main/VALIDATION.md)** Β· πΊοΈ **[Roadmap](https://github.com/raccioly/docguard/blob/main/ROADMAP.md)**
|
|
67
67
|
|
|
68
68
|
### Architecture
|
|
69
69
|
|
|
@@ -108,7 +108,7 @@ A guard result describes the checks performed. The CDD grade measures structural
|
|
|
108
108
|
|
|
109
109
|
Research motivates evaluation of this approach. A 2026 study found that repository context files did not generally improve task success and increased inference cost in its evaluated settings. It also found agents generally followed the instructions. These results support testing concise, relevant context and measuring actual task outcomes; they do not establish DocGuard's effectiveness. [Evaluating AGENTS.md, revised June 2026](https://arxiv.org/abs/2602.11988v2).
|
|
110
110
|
|
|
111
|
-
The [current roadmap](ROADMAP.md) prioritizes accurate detection, reproducible evidence, document lifecycle management, and contributor-supplied regression cases. Released plans and superseded specifications are removed from active AI context and remain recoverable from Git.
|
|
111
|
+
The [current roadmap](https://github.com/raccioly/docguard/blob/main/ROADMAP.md) prioritizes accurate detection, reproducible evidence, document lifecycle management, and contributor-supplied regression cases. Released plans and superseded specifications are removed from active AI context and remain recoverable from Git.
|
|
112
112
|
|
|
113
113
|
---
|
|
114
114
|
|
|
@@ -166,7 +166,7 @@ The server is **read-only** β it never writes to the mounted project.
|
|
|
166
166
|
```
|
|
167
167
|
- **MCP** (Claude, Cursor, any MCP client) β `claude mcp add docguard -- npx -y docguard-cli mcp`; 5 read-only tools (guard, score, explain, verify-claims, diagnose). Registry manifest ships in-repo (`server.json`, Smithery-ready).
|
|
168
168
|
- **GitLab CI** β component staged at [`templates/ci/gitlab-component.yml`](templates/ci/gitlab-component.yml) (guard/score/ci job with a SARIF artifact).
|
|
169
|
-
- **Homebrew** β `brew install raccioly/tap/docguard` (formula in [`packaging/homebrew/`](packaging/homebrew
|
|
169
|
+
- **Homebrew** β `brew install raccioly/tap/docguard` (formula in [`packaging/homebrew/`](https://github.com/raccioly/docguard/tree/main/packaging/homebrew)).
|
|
170
170
|
|
|
171
171
|
### Core Workflow
|
|
172
172
|
|
|
@@ -278,7 +278,7 @@ DocGuard ships **23 commands** (the "Daily 5" + 18 situational tools, including
|
|
|
278
278
|
| `guard` | Validate against canonical docs β 30 validators |
|
|
279
279
|
| `diff` | Show gaps between docs and code (`--since <ref>` for impact mode) |
|
|
280
280
|
| `sync` | Refresh code-truth doc sections β keeps memory always up to date |
|
|
281
|
-
| `score` | CDD maturity score (0-100; `--diff` for delta between refs) |
|
|
281
|
+
| `score` | Structural CDD maturity score (0-100; not a guard verdict; `--diff` for delta between refs) |
|
|
282
282
|
|
|
283
283
|
**Tools (situational, but day-to-day useful):**
|
|
284
284
|
|
|
@@ -302,8 +302,8 @@ DocGuard ships **23 commands** (the "Daily 5" + 18 situational tools, including
|
|
|
302
302
|
| `specs --check` / `specs --write` | Validate or refresh `.docguard-specs.json`, the byte-stable index of immutable spec IDs, reviewed lifecycle/lineage/scope, artifact digests, task state, explicitly scoped test evidence, and archive tombstones. Refreshes preserve the reviewed block. |
|
|
303
303
|
| `specs preflight [--path <spec>]` | Before specification, print current spec lifecycle and evidence. Before planning, check the generated draft for structural blockers and report semantic overlap as review-only evidence. |
|
|
304
304
|
| `mcp` | MCP server β exposes guard/score/explain/verify/report/diagnose as native tools for Claude, Cursor, and any MCP client. Stdio: `claude mcp add docguard -- npx docguard-cli mcp`. Team-shared HTTP: `docguard mcp --transport http --port 8585` (loopback by default; non-loopback binds require `--api-key`) |
|
|
305
|
-
| `report` | Compliance-evidence bundle for audits β guard verdict
|
|
306
|
-
| `ci` | Pipeline gate: guard +
|
|
305
|
+
| `report` | Compliance-evidence bundle for audits β combined readiness, guard verdict, structural maturity, ALCOA+ attributes, and fix history, stamped with git commit and a tamper-evident sha256 integrity hash (`--format json`, `--out <file>`). Evidence, not a gate: always exits 0 |
|
|
306
|
+
| `ci` | Pipeline gate: guard + structural maturity in one command with READY/ATTENTION/BLOCKED assessment β never scaffolds or touches source; its only write is its own `.docguard/history.jsonl` (opt out: `--no-history`). `--threshold <n>` fails below a score, `--fail-on-warning` for strict mode, `--format json` for parsers |
|
|
307
307
|
| `score --trend` | Score trajectory from recorded `ci` runs β sparkline, delta, and the last 10 runs with commit stamps |
|
|
308
308
|
| `memory` | Per-domain accuracy headline (endpoints / entities / env / tech) |
|
|
309
309
|
| `memory --diff` | Drill into which specific claims don't match code |
|
|
@@ -456,10 +456,19 @@ DocGuard runs **30 automated validators** on every `guard` check. Source-facing
|
|
|
456
456
|
"severity": {
|
|
457
457
|
"todoTracking": "high", // warnings fail CI
|
|
458
458
|
"freshness": "low" // warnings ignored for exit code
|
|
459
|
+
},
|
|
460
|
+
"findingSeverity": {
|
|
461
|
+
"TRC004": "low", // only this finding becomes informational
|
|
462
|
+
"SEC001": "high" // this exact code always blocks
|
|
459
463
|
}
|
|
460
464
|
}
|
|
461
465
|
```
|
|
462
466
|
|
|
467
|
+
Exact `findingSeverity` entries take precedence over validator severity. Guard
|
|
468
|
+
JSON, SARIF, and JUnit retain the detector's intrinsic severity and add the
|
|
469
|
+
effective severity plus the policy source. Intrinsic errors stay blocking unless
|
|
470
|
+
their exact stable code is explicitly configured.
|
|
471
|
+
|
|
463
472
|
---
|
|
464
473
|
|
|
465
474
|
## π Templates
|
|
@@ -591,11 +600,11 @@ Three real-world projects to see DocGuard in action:
|
|
|
591
600
|
|
|
592
601
|
| Example | Scenario | What You'll See |
|
|
593
602
|
|---------|----------|----------------|
|
|
594
|
-
| [01-express-api](examples/01-express-api
|
|
595
|
-
| [02-python-flask](examples/02-python-flask
|
|
596
|
-
| [03-spec-kit-project](examples/03-spec-kit-project
|
|
603
|
+
| [01-express-api](https://github.com/raccioly/docguard/tree/main/examples/01-express-api) | Node.js API with **zero docs** | Cold-start: `generate` β instant coverage |
|
|
604
|
+
| [02-python-flask](https://github.com/raccioly/docguard/tree/main/examples/02-python-flask) | Python app with **drifted docs** | Drift detection: catch when docs lie |
|
|
605
|
+
| [03-spec-kit-project](https://github.com/raccioly/docguard/tree/main/examples/03-spec-kit-project) | Full CDD + Spec Kit | Gold standard: what maturity looks like |
|
|
597
606
|
|
|
598
|
-
See [examples/README.md](examples/README.md) for step-by-step instructions.
|
|
607
|
+
See [examples/README.md](https://github.com/raccioly/docguard/blob/main/examples/README.md) for step-by-step instructions.
|
|
599
608
|
|
|
600
609
|
---
|
|
601
610
|
|
|
@@ -639,7 +648,7 @@ The pieces that matter at company scale:
|
|
|
639
648
|
|
|
640
649
|
## βοΈ CI/CD Integration
|
|
641
650
|
|
|
642
|
-
> **Full recipes:** see [`docs-canonical/CI-RECIPES.md`](
|
|
651
|
+
> **Full recipes:** see [`docs-canonical/CI-RECIPES.md`](https://github.com/raccioly/docguard/blob/main/docs-canonical/CI-RECIPES.md) for guard, auto-fix (commits mechanical fixes back to PRs), nightly sync, score-on-PR, and pre-commit configs.
|
|
643
652
|
|
|
644
653
|
### GitHub Actions β Guard (most common)
|
|
645
654
|
|
|
@@ -705,7 +714,7 @@ Two ready-to-use templates ship with the Spec Kit extension and as standalone fi
|
|
|
705
714
|
|
|
706
715
|
## β¨ What's New
|
|
707
716
|
|
|
708
|
-
Highlights
|
|
717
|
+
Highlights through the current v0.40 release line:
|
|
709
718
|
|
|
710
719
|
- **Adoption baseline** β `guard --update-baseline` freezes a legacy repo's existing findings
|
|
711
720
|
into a committed `.docguard.baseline.json`; guard/ci then gate only NEW drift, with suppression
|
|
@@ -805,12 +814,12 @@ See [Configuration Guide](docs/configuration.md) for all options.
|
|
|
805
814
|
|
|
806
815
|
DocGuard's quality evaluation and documentation generation patterns are informed by peer-reviewed research from the University of Arizona and the Joint Interoperability Test Command (JITC), U.S. Department of Defense:
|
|
807
816
|
|
|
808
|
-
- **AITPG** β AI-driven Test Plan Generator using Multi-Agent Debate and RAG ([Lopez et al., IEEE TSE 2026](Research/AITPG.pdf))
|
|
809
|
-
- **TRACE** β Telecom Root Cause Analysis through Calibrated Explainability ([Lopez et al., IEEE TMLCN 2026](Research/TRACE.pdf))
|
|
817
|
+
- **AITPG** β AI-driven Test Plan Generator using Multi-Agent Debate and RAG ([Lopez et al., IEEE TSE 2026](https://github.com/raccioly/docguard/blob/main/Research/AITPG.pdf))
|
|
818
|
+
- **TRACE** β Telecom Root Cause Analysis through Calibrated Explainability ([Lopez et al., IEEE TMLCN 2026](https://github.com/raccioly/docguard/blob/main/Research/TRACE.pdf))
|
|
810
819
|
|
|
811
820
|
Lead researcher: **[Martin Manuel Lopez](https://github.com/martinmanuel9)** Β· [ORCID 0009-0002-7652-2385](https://orcid.org/0009-0002-7652-2385)
|
|
812
821
|
|
|
813
|
-
See [CONTRIBUTING.md](CONTRIBUTING.md#research--academic-credits) for full citations.
|
|
822
|
+
See [CONTRIBUTING.md](https://github.com/raccioly/docguard/blob/main/CONTRIBUTING.md#research--academic-credits) for full citations.
|
|
814
823
|
|
|
815
824
|
---
|
|
816
825
|
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Combine the guard verdict and optional CI policy into one readiness result.
|
|
3
|
+
* @implements docguard.adoption-workflow-integrity#FR-009
|
|
4
|
+
*
|
|
5
|
+
* Structural maturity remains an independent 0-100 measure. It can explain
|
|
6
|
+
* the quality of the documentation system, but it cannot override a failed
|
|
7
|
+
* guard check or a configured CI gate.
|
|
8
|
+
*/
|
|
9
|
+
export function buildReadinessAssessment(guardData, scoreData, options = {}) {
|
|
10
|
+
const blockingFindings = finiteCount(guardData?.effectiveErrors, guardData?.errors);
|
|
11
|
+
const advisoryFindings = finiteCount(guardData?.effectiveWarnings, guardData?.warnings);
|
|
12
|
+
const guardStatus = normalizeGuardStatus(guardData?.status, blockingFindings, advisoryFindings);
|
|
13
|
+
|
|
14
|
+
const threshold = Number.isFinite(options.threshold) ? options.threshold : null;
|
|
15
|
+
const thresholdConfigured = options.thresholdConfigured ?? (threshold !== null && threshold > 0);
|
|
16
|
+
const thresholdMet = thresholdConfigured
|
|
17
|
+
? Boolean(options.thresholdMet ?? (threshold !== null && scoreData.score >= threshold))
|
|
18
|
+
: true;
|
|
19
|
+
const warningsBlockCI = Boolean(options.failOnWarning) && guardStatus === 'WARN';
|
|
20
|
+
|
|
21
|
+
const reasons = [];
|
|
22
|
+
if (guardStatus === 'FAIL') reasons.push('GUARD_FAILED');
|
|
23
|
+
if (guardStatus === 'UNKNOWN') reasons.push('GUARD_STATUS_UNKNOWN');
|
|
24
|
+
if (!thresholdMet) reasons.push('CI_THRESHOLD_NOT_MET');
|
|
25
|
+
if (warningsBlockCI) reasons.push('CI_WARNINGS_BLOCKED');
|
|
26
|
+
else if (guardStatus === 'WARN') reasons.push('GUARD_WARNINGS');
|
|
27
|
+
|
|
28
|
+
let status;
|
|
29
|
+
if (guardStatus === 'FAIL' || guardStatus === 'UNKNOWN' || !thresholdMet || warningsBlockCI) status = 'BLOCKED';
|
|
30
|
+
else if (guardStatus === 'WARN') status = 'ATTENTION';
|
|
31
|
+
else status = 'READY';
|
|
32
|
+
|
|
33
|
+
const assessment = {
|
|
34
|
+
status,
|
|
35
|
+
reasons,
|
|
36
|
+
guardStatus,
|
|
37
|
+
blockingFindings,
|
|
38
|
+
advisoryFindings,
|
|
39
|
+
structuralMaturity: {
|
|
40
|
+
score: scoreData.score,
|
|
41
|
+
grade: scoreData.grade,
|
|
42
|
+
scoreKind: scoreData.scoreKind || 'structural-maturity',
|
|
43
|
+
},
|
|
44
|
+
threshold: {
|
|
45
|
+
configured: thresholdConfigured,
|
|
46
|
+
value: thresholdConfigured ? threshold : null,
|
|
47
|
+
met: thresholdMet,
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
return { ...assessment, summary: summarizeAssessment(assessment) };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function finiteCount(primary, fallback) {
|
|
55
|
+
if (Number.isFinite(primary)) return primary;
|
|
56
|
+
return Number.isFinite(fallback) ? fallback : 0;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function normalizeGuardStatus(value, blockingFindings, advisoryFindings) {
|
|
60
|
+
const status = typeof value === 'string' ? value.toUpperCase() : '';
|
|
61
|
+
if (status === 'FAIL' || blockingFindings > 0) return 'FAIL';
|
|
62
|
+
if (status === 'WARN' || advisoryFindings > 0) return 'WARN';
|
|
63
|
+
if (status === 'PASS') return 'PASS';
|
|
64
|
+
return 'UNKNOWN';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function summarizeAssessment(assessment) {
|
|
68
|
+
const clauses = [];
|
|
69
|
+
|
|
70
|
+
if (assessment.guardStatus === 'FAIL') {
|
|
71
|
+
clauses.push(`guard failed with ${formatCount(assessment.blockingFindings, 'blocking finding')}`);
|
|
72
|
+
} else if (assessment.guardStatus === 'UNKNOWN') {
|
|
73
|
+
clauses.push('guard status is unavailable');
|
|
74
|
+
} else if (assessment.reasons.includes('CI_WARNINGS_BLOCKED')) {
|
|
75
|
+
clauses.push(`${formatCount(assessment.advisoryFindings, 'guard warning')} configured to block CI`);
|
|
76
|
+
} else if (assessment.guardStatus === 'WARN') {
|
|
77
|
+
clauses.push(`guard reported ${formatCount(assessment.advisoryFindings, 'advisory warning')}`);
|
|
78
|
+
} else {
|
|
79
|
+
clauses.push('guard passed');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (assessment.reasons.includes('CI_THRESHOLD_NOT_MET')) {
|
|
83
|
+
const threshold = assessment.threshold.value === null ? 'the configured value' : assessment.threshold.value;
|
|
84
|
+
clauses.push(`Structural Maturity ${assessment.structuralMaturity.score}/100 is below CI threshold ${threshold}`);
|
|
85
|
+
} else {
|
|
86
|
+
clauses.push(`Structural Maturity is ${assessment.structuralMaturity.score}/100 (${assessment.structuralMaturity.grade})`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return clauses.join('; ') + '.';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function formatCount(count, noun) {
|
|
93
|
+
return `${count} ${noun}${count === 1 ? '' : 's'}`;
|
|
94
|
+
}
|
package/cli/commands/ci.mjs
CHANGED
|
@@ -18,6 +18,7 @@ import { runGuardInternal } from './guard.mjs';
|
|
|
18
18
|
import { runScoreInternal } from './score.mjs';
|
|
19
19
|
import { appendHistory } from '../writers/history.mjs';
|
|
20
20
|
import { getHeadInfo, isGitRepo } from '../shared-git.mjs';
|
|
21
|
+
import { buildReadinessAssessment } from '../assessment.mjs';
|
|
21
22
|
|
|
22
23
|
export function runCI(projectDir, config, flags) {
|
|
23
24
|
const threshold = parseInt(flags.threshold || '0', 10);
|
|
@@ -46,10 +47,14 @@ export function runCI(projectDir, config, flags) {
|
|
|
46
47
|
// --fail-on-warning failure exits 1 and must not be recorded as PASS in
|
|
47
48
|
// history or the JSON consumers parse.
|
|
48
49
|
const thresholdMet = threshold <= 0 || scoreData.score >= threshold;
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
const assessment = buildReadinessAssessment(guardData, scoreData, {
|
|
51
|
+
threshold,
|
|
52
|
+
thresholdMet,
|
|
53
|
+
failOnWarning,
|
|
54
|
+
});
|
|
55
|
+
const status = assessment.status === 'BLOCKED'
|
|
56
|
+
? 'FAIL'
|
|
57
|
+
: assessment.status === 'ATTENTION' ? 'WARN' : 'PASS';
|
|
53
58
|
|
|
54
59
|
// ββ Record history (unless opted out) ββ
|
|
55
60
|
if (!flags.noHistory) {
|
|
@@ -89,6 +94,7 @@ export function runCI(projectDir, config, flags) {
|
|
|
89
94
|
threshold,
|
|
90
95
|
thresholdMet,
|
|
91
96
|
status,
|
|
97
|
+
assessment,
|
|
92
98
|
timestamp: new Date().toISOString(),
|
|
93
99
|
};
|
|
94
100
|
// Machine output must survive a pipe: stdout.write + natural exit, never
|
|
@@ -107,12 +113,16 @@ export function runCI(projectDir, config, flags) {
|
|
|
107
113
|
if (guardData.baselineSuppressed > 0) {
|
|
108
114
|
console.log(` ${c.dim}π ${guardData.baselineSuppressed} pre-existing finding(s) suppressed by the committed baseline${c.reset}`);
|
|
109
115
|
}
|
|
110
|
-
console.log(` ${c.bold}
|
|
116
|
+
console.log(` ${c.bold}Structural Maturity:${c.reset} ${scoreData.score}/100 (${scoreData.grade})`);
|
|
111
117
|
|
|
112
118
|
if (threshold > 0) {
|
|
113
119
|
console.log(` ${c.bold}Threshold:${c.reset} ${thresholdMet ? `${c.green}β
β₯${threshold}` : `${c.red}β <${threshold}`}${c.reset}`);
|
|
114
120
|
}
|
|
115
121
|
|
|
122
|
+
const readinessColor = assessment.status === 'READY' ? c.green : assessment.status === 'ATTENTION' ? c.yellow : c.red;
|
|
123
|
+
console.log(` ${c.bold}Readiness:${c.reset} ${readinessColor}${c.bold}${assessment.status}${c.reset}`);
|
|
124
|
+
console.log(` ${c.dim}${assessment.summary}${c.reset}`);
|
|
125
|
+
|
|
116
126
|
console.log('');
|
|
117
127
|
}
|
|
118
128
|
|
|
@@ -23,6 +23,7 @@ import { resolve, dirname } from 'node:path';
|
|
|
23
23
|
import { fileURLToPath } from 'node:url';
|
|
24
24
|
import { execSync, execFileSync } from 'node:child_process';
|
|
25
25
|
import { applyAllMechanicalFixes } from './fix.mjs';
|
|
26
|
+
import { buildReadinessAssessment } from '../assessment.mjs';
|
|
26
27
|
|
|
27
28
|
// Map validator failures to the right fix --doc target
|
|
28
29
|
const VALIDATOR_TO_DOC = {
|
|
@@ -186,7 +187,7 @@ export function runDiagnose(projectDir, config, flags) {
|
|
|
186
187
|
|
|
187
188
|
// ββ Step 1: Run guard internally ββ
|
|
188
189
|
let guardData = runGuardInternal(projectDir, config);
|
|
189
|
-
|
|
190
|
+
let scoreData = runScoreInternal(projectDir, config);
|
|
190
191
|
|
|
191
192
|
// ββ Step 2: Collect issues ββ
|
|
192
193
|
let issues = collectIssues(guardData);
|
|
@@ -223,6 +224,7 @@ export function runDiagnose(projectDir, config, flags) {
|
|
|
223
224
|
|
|
224
225
|
// Re-run guard to see what's still broken
|
|
225
226
|
guardData = runGuardInternal(projectDir, config);
|
|
227
|
+
scoreData = runScoreInternal(projectDir, config);
|
|
226
228
|
issues = collectIssues(guardData);
|
|
227
229
|
|
|
228
230
|
if (!flags.format || flags.format === 'text') {
|
|
@@ -256,12 +258,13 @@ export function runDiagnose(projectDir, config, flags) {
|
|
|
256
258
|
}
|
|
257
259
|
|
|
258
260
|
// ββ Step 4: Output ββ
|
|
261
|
+
const assessment = buildReadinessAssessment(guardData, scoreData);
|
|
259
262
|
if (flags.format === 'json') {
|
|
260
|
-
outputJSON(guardData, scoreData, issues);
|
|
263
|
+
outputJSON(guardData, scoreData, issues, assessment);
|
|
261
264
|
} else if (flags.format === 'prompt') {
|
|
262
|
-
outputPrompt(projectDir, guardData, scoreData, issues, flags, agentMode);
|
|
265
|
+
outputPrompt(projectDir, guardData, scoreData, issues, flags, agentMode, assessment);
|
|
263
266
|
} else {
|
|
264
|
-
outputText(projectDir, guardData, scoreData, issues, flags, agentMode);
|
|
267
|
+
outputText(projectDir, guardData, scoreData, issues, flags, agentMode, assessment);
|
|
265
268
|
}
|
|
266
269
|
}
|
|
267
270
|
|
|
@@ -315,7 +318,7 @@ function collectIssues(guardData) {
|
|
|
315
318
|
return issues;
|
|
316
319
|
}
|
|
317
320
|
|
|
318
|
-
function outputJSON(guardData, scoreData, issues) {
|
|
321
|
+
function outputJSON(guardData, scoreData, issues, assessment) {
|
|
319
322
|
// Structured, deterministic fix actions an agent or `--write` can apply directly.
|
|
320
323
|
const mechanicalFixes = [];
|
|
321
324
|
for (const v of guardData.validators) {
|
|
@@ -332,6 +335,7 @@ function outputJSON(guardData, scoreData, issues) {
|
|
|
332
335
|
grade: scoreData.grade,
|
|
333
336
|
scoreKind: scoreData.scoreKind,
|
|
334
337
|
assurance: scoreData.assurance,
|
|
338
|
+
assessment,
|
|
335
339
|
checkCoverage: guardData.checkCoverage,
|
|
336
340
|
issueCount: issues.length,
|
|
337
341
|
issues: issues.map(i => ({
|
|
@@ -354,10 +358,12 @@ function outputJSON(guardData, scoreData, issues) {
|
|
|
354
358
|
console.log(JSON.stringify(result, null, 2));
|
|
355
359
|
}
|
|
356
360
|
|
|
357
|
-
function outputText(projectDir, guardData, scoreData, issues, flags, agentMode = 'llm') {
|
|
361
|
+
function outputText(projectDir, guardData, scoreData, issues, flags, agentMode = 'llm', assessment) {
|
|
358
362
|
console.log(`${c.bold}π DocGuard Diagnose β ${guardData.project}${c.reset}`);
|
|
359
|
-
console.log(`${c.dim} Profile: ${guardData.profile} |
|
|
363
|
+
console.log(`${c.dim} Profile: ${guardData.profile} | Structural Maturity: ${scoreData.score}/100 (${scoreData.grade}) | Mode: ${agentMode.toUpperCase()}${c.reset}`);
|
|
360
364
|
console.log(`${c.dim} Guard: ${guardData.passed}/${guardData.total} passed | Status: ${guardData.status}${c.reset}\n`);
|
|
365
|
+
const readinessColor = assessment.status === 'READY' ? c.green : assessment.status === 'ATTENTION' ? c.yellow : c.red;
|
|
366
|
+
console.log(` ${c.bold}Readiness:${c.reset} ${readinessColor}${c.bold}${assessment.status}${c.reset} ${c.dim}β ${assessment.summary}${c.reset}\n`);
|
|
361
367
|
|
|
362
368
|
if (issues.length === 0) {
|
|
363
369
|
console.log(` ${c.green}${c.bold}β
All clear!${c.reset} No issues found.\n`);
|
|
@@ -410,15 +416,15 @@ function outputText(projectDir, guardData, scoreData, issues, flags, agentMode =
|
|
|
410
416
|
// Multi-perspective debate prompts (AITPG/TRACE-inspired)
|
|
411
417
|
console.log(` ${c.bold}π€ Multi-Perspective AI Debate Prompt:${c.reset}`);
|
|
412
418
|
console.log(` ${c.dim}Copy everything below and paste to your AI agent:${c.reset}\n`);
|
|
413
|
-
outputDebatePrompt(projectDir, guardData, scoreData, issues, agentMode);
|
|
419
|
+
outputDebatePrompt(projectDir, guardData, scoreData, issues, agentMode, assessment);
|
|
414
420
|
} else {
|
|
415
421
|
console.log(` ${c.bold}π€ AI-Ready Prompt:${c.reset}`);
|
|
416
422
|
console.log(` ${c.dim}Copy everything below and paste to your AI agent:${c.reset}\n`);
|
|
417
|
-
outputPrompt(undefined, guardData, scoreData, issues, flags, agentMode);
|
|
423
|
+
outputPrompt(undefined, guardData, scoreData, issues, flags, agentMode, assessment);
|
|
418
424
|
}
|
|
419
425
|
}
|
|
420
426
|
|
|
421
|
-
function outputPrompt(projectDir, guardData, scoreData, issues, flags, agentMode = 'llm') {
|
|
427
|
+
function outputPrompt(projectDir, guardData, scoreData, issues, flags, agentMode = 'llm', assessment) {
|
|
422
428
|
if (issues.length === 0) {
|
|
423
429
|
console.log('No issues to fix. Documentation is healthy.');
|
|
424
430
|
return;
|
|
@@ -429,7 +435,8 @@ function outputPrompt(projectDir, guardData, scoreData, issues, flags, agentMode
|
|
|
429
435
|
|
|
430
436
|
const lines = [];
|
|
431
437
|
lines.push(`TASK: Fix ${issues.length} documentation issue(s) in project "${guardData.project}"`);
|
|
432
|
-
lines.push(`
|
|
438
|
+
lines.push(`Readiness: ${assessment.status} | Guard: ${guardData.status} | Structural Maturity: ${scoreData.score}/100 (${scoreData.grade})`);
|
|
439
|
+
lines.push(assessment.summary);
|
|
433
440
|
lines.push('');
|
|
434
441
|
lines.push('ISSUES FOUND:');
|
|
435
442
|
|
|
@@ -507,12 +514,12 @@ function outputPrompt(projectDir, guardData, scoreData, issues, flags, agentMode
|
|
|
507
514
|
* and TRACE adversarial debate (Advocate/Challenger/Mediator/Explainer).
|
|
508
515
|
* Lopez et al., IEEE TSE/TMLCN 2026.
|
|
509
516
|
*/
|
|
510
|
-
function outputDebatePrompt(projectDir, guardData, scoreData, issues, agentMode = 'llm') {
|
|
517
|
+
function outputDebatePrompt(projectDir, guardData, scoreData, issues, agentMode = 'llm', assessment) {
|
|
511
518
|
const lines = [];
|
|
512
519
|
|
|
513
520
|
lines.push('βββββββββββββββββββββββββββββββββββββββββββββββββββββββ');
|
|
514
521
|
lines.push('MULTI-PERSPECTIVE DOCUMENTATION ANALYSIS');
|
|
515
|
-
lines.push(`Project: "${guardData.project}" |
|
|
522
|
+
lines.push(`Project: "${guardData.project}" | Readiness: ${assessment.status} | Guard: ${guardData.status} | Structural Maturity: ${scoreData.score}/100 (${scoreData.grade}) | Issues: ${issues.length}`);
|
|
516
523
|
lines.push('Methodology: Multi-agent debate (Lopez et al., AITPG/TRACE, IEEE 2026)');
|
|
517
524
|
lines.push('βββββββββββββββββββββββββββββββββββββββββββββββββββββββ');
|
|
518
525
|
lines.push('');
|
package/cli/commands/fix.mjs
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
* @implements docguard.language-repository-coverage#FR-010
|
|
3
3
|
* @implements docguard.language-repository-coverage#FR-011
|
|
4
4
|
*/
|
|
5
|
-
import { assertMappedFullDocumentWrites, docRolePath, isMappedDocPath, resolveDocRole } from '../shared-doc-roles.mjs';
|
|
6
5
|
/**
|
|
7
6
|
* Fix Command β The AI Orchestrator
|
|
8
7
|
*
|
|
@@ -24,61 +23,31 @@ import { execSync, execFileSync } from 'node:child_process';
|
|
|
24
23
|
import { fileURLToPath } from 'node:url';
|
|
25
24
|
import { c } from '../shared.mjs';
|
|
26
25
|
import { computeApiSurfaceDrift } from '../validators/api-surface.mjs';
|
|
27
|
-
import { removeEndpoints, hasGeneratedMarker } from '../writers/api-reference.mjs';
|
|
28
26
|
import { applyMechanicalFixes } from '../writers/mechanical.mjs';
|
|
29
27
|
import { loadFixMemory } from '../writers/fix-memory.mjs';
|
|
30
28
|
import { safeWrite } from '../writers/generate-io.mjs';
|
|
31
29
|
import { runGuardInternal } from './guard.mjs';
|
|
32
30
|
|
|
33
31
|
/**
|
|
34
|
-
*
|
|
35
|
-
* API-REFERENCE.md that the OpenAPI spec confirms no longer exist. Removes the
|
|
36
|
-
* summary-table row and the detail block. Never rewrites prose.
|
|
32
|
+
* Compatibility entry point for the former API deletion writer.
|
|
37
33
|
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
34
|
+
* OpenAPI can prove a contract omission, while bounded route discovery cannot
|
|
35
|
+
* prove runtime absence and canonical documentation may represent intended
|
|
36
|
+
* behavior. Contract mismatches therefore require review and this entry point
|
|
37
|
+
* is deliberately read-only, including under `--force`.
|
|
40
38
|
*
|
|
41
39
|
* @returns {{ applied: boolean, removed: Array<{method,path}>, skipped?: string }}
|
|
42
40
|
*/
|
|
43
|
-
export function applyApiSurfaceWrites(projectDir, config
|
|
41
|
+
export function applyApiSurfaceWrites(projectDir, config) {
|
|
44
42
|
const drift = computeApiSurfaceDrift(projectDir, config);
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const content = readFileSync(apiDocPath, 'utf-8');
|
|
54
|
-
if (!hasGeneratedMarker(content) && !force) {
|
|
55
|
-
return {
|
|
56
|
-
applied: false,
|
|
57
|
-
removed: [],
|
|
58
|
-
skipped: `${apiDoc} is not marked '<!-- docguard:generated true -->'. ` +
|
|
59
|
-
`Re-run with --force to edit it, or fix it via an AI agent (/docguard.fix --doc api-reference).`,
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
if (isMappedDocPath(config, apiDoc)) assertMappedFullDocumentWrites(projectDir, config, ['apiReference']);
|
|
63
|
-
|
|
64
|
-
const { content: newContent, removed } = removeEndpoints(content, removable);
|
|
65
|
-
if (removed.length === 0 || newContent === content) {
|
|
66
|
-
return { applied: false, removed: [] }; // idempotent no-op
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
safeWrite(apiDocPath, newContent);
|
|
70
|
-
// Map removed keys back to {method,path} for reporting.
|
|
71
|
-
const removedEndpoints = removable.filter(e => removed.includes(`${e.method.toUpperCase()} ${normalizeForKey(e.path)}`));
|
|
72
|
-
return { applied: true, removed: removedEndpoints.length ? removedEndpoints : removable };
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Local mirror of api-doc normalizePath for matching removed keys (avoids an
|
|
76
|
-
// extra import cycle); only used for display reconciliation.
|
|
77
|
-
function normalizeForKey(p) {
|
|
78
|
-
let s = String(p).trim().replace(/^[|`'"\s]+/, '').replace(/[|`'"\s]+$/, '').split(/[?#]/)[0];
|
|
79
|
-
s = s.replace(/\{[^}/]+\}/g, '{}').replace(/:[^/]+/g, '{}');
|
|
80
|
-
if (s.length > 1) s = s.replace(/\/+$/, '');
|
|
81
|
-
return s;
|
|
43
|
+
const reviewCount = drift.confidence === 'spec' ? drift.contractMismatches.length : 0;
|
|
44
|
+
return {
|
|
45
|
+
applied: false,
|
|
46
|
+
removed: [],
|
|
47
|
+
...(reviewCount > 0 ? {
|
|
48
|
+
skipped: `${reviewCount} endpoint contract mismatch(es) require review; negative route-scan evidence cannot authorize documentation deletion.`,
|
|
49
|
+
} : {}),
|
|
50
|
+
};
|
|
82
51
|
}
|
|
83
52
|
|
|
84
53
|
// ββ Document Quality Definitions βββββββββββββββββββββββββββββββββββββββββββ
|
package/cli/commands/guard.mjs
CHANGED
|
@@ -10,9 +10,9 @@ import { applyDocRoles } from '../shared-doc-roles.mjs';
|
|
|
10
10
|
* runGuardInternal() β returns data, no side effects (for diagnose, ci)
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import { c, resolveSeverity, loadIgnorePatterns, resolveDocDirs } from '../shared.mjs';
|
|
13
|
+
import { c, resolveSeverity, resolveFindingEnforcement, loadIgnorePatterns, resolveDocDirs } from '../shared.mjs';
|
|
14
14
|
import { walkFiles, buildIgnoreFilter } from '../shared-ignore.mjs';
|
|
15
|
-
import { mkFinding, resultFromFindings } from '../findings.mjs';
|
|
15
|
+
import { CODES, mkFinding, resultFromFindings } from '../findings.mjs';
|
|
16
16
|
import { loadValidatorSuppressions } from '../validator-markers.mjs';
|
|
17
17
|
import { detectAgentMode, isSpecKitInitialized } from '../ensure-skills.mjs';
|
|
18
18
|
import { checkUpgradeStatus } from './upgrade.mjs';
|
|
@@ -206,6 +206,7 @@ function renderableItems(v) {
|
|
|
206
206
|
if (Array.isArray(v.findings) && v.findings.length > 0) {
|
|
207
207
|
return v.findings.map((f) => ({
|
|
208
208
|
severity: f.severity,
|
|
209
|
+
effectiveSeverity: f.effectiveSeverity || f.severity,
|
|
209
210
|
message: f.message,
|
|
210
211
|
code: f.code,
|
|
211
212
|
confidence: f.confidence,
|
|
@@ -453,24 +454,35 @@ export function runGuardInternal(projectDir, config) {
|
|
|
453
454
|
const totalPassed = activeResults.reduce((sum, r) => sum + r.passed, 0);
|
|
454
455
|
const totalChecks = activeResults.reduce((sum, r) => sum + r.total, 0);
|
|
455
456
|
|
|
456
|
-
//
|
|
457
|
-
//
|
|
458
|
-
//
|
|
459
|
-
// - high β validator's warnings get promoted to "effective errors"
|
|
460
|
-
// - low β validator's warnings are demoted (ignored for exit code)
|
|
461
|
-
// - medium (default) β warnings stay as-is
|
|
457
|
+
// Compute enforcement after baseline suppression. Structured findings can
|
|
458
|
+
// be reweighted by exact stable code; legacy validators retain the existing
|
|
459
|
+
// per-validator behavior. Preserve intrinsic severity for auditability.
|
|
462
460
|
for (const v of activeResults) {
|
|
463
461
|
v.severity = resolveSeverity(config, v.key);
|
|
462
|
+
let errors = 0, warnings = 0, infos = 0;
|
|
463
|
+
if (Array.isArray(v.findings) && v.findings.length > 0) {
|
|
464
|
+
for (const finding of v.findings) {
|
|
465
|
+
const enforcement = resolveFindingEnforcement(config, finding, v.key);
|
|
466
|
+
finding.effectiveSeverity = enforcement.level;
|
|
467
|
+
finding.enforcement = enforcement;
|
|
468
|
+
if (enforcement.level === 'error') errors++;
|
|
469
|
+
else if (enforcement.level === 'warn') warnings++;
|
|
470
|
+
else infos++;
|
|
471
|
+
}
|
|
472
|
+
} else {
|
|
473
|
+
errors = v.errors.length;
|
|
474
|
+
if (v.severity === 'high') errors += v.warnings.length;
|
|
475
|
+
else if (v.severity === 'medium') warnings = v.warnings.length;
|
|
476
|
+
else infos = v.warnings.length;
|
|
477
|
+
}
|
|
478
|
+
v.effectiveErrors = errors;
|
|
479
|
+
v.effectiveWarnings = warnings;
|
|
480
|
+
v.effectiveInfos = infos;
|
|
481
|
+
v.effectiveStatus = errors > 0 ? 'fail' : warnings > 0 ? 'warn' : v.status === 'na' ? 'na' : 'pass';
|
|
464
482
|
}
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
const wCount = v.warnings.length;
|
|
469
|
-
if (wCount === 0) continue;
|
|
470
|
-
if (v.severity === 'high') effectiveErrors += wCount;
|
|
471
|
-
else if (v.severity === 'low') { /* ignored for exit */ }
|
|
472
|
-
else effectiveWarnings += wCount;
|
|
473
|
-
}
|
|
483
|
+
const effectiveErrors = activeResults.reduce((sum, v) => sum + v.effectiveErrors, 0);
|
|
484
|
+
const effectiveWarnings = activeResults.reduce((sum, v) => sum + v.effectiveWarnings, 0);
|
|
485
|
+
const effectiveInfos = activeResults.reduce((sum, v) => sum + v.effectiveInfos, 0);
|
|
474
486
|
|
|
475
487
|
// The headline status word MUST agree with the exit code, which is
|
|
476
488
|
// severity-aware (effectiveErrors/effectiveWarnings, computed above).
|
|
@@ -523,6 +535,7 @@ export function runGuardInternal(projectDir, config) {
|
|
|
523
535
|
// things they've marked as high-severity.
|
|
524
536
|
effectiveErrors,
|
|
525
537
|
effectiveWarnings,
|
|
538
|
+
effectiveInfos,
|
|
526
539
|
baselineSuppressed,
|
|
527
540
|
coverage,
|
|
528
541
|
checkCoverage,
|
|
@@ -569,14 +582,21 @@ export function liteValidatorsConfig(config = {}) {
|
|
|
569
582
|
'security', 'architecture', 'freshness', 'traceability', 'docsDiff',
|
|
570
583
|
'apiSurface', 'metadataSync', 'docsCoverage', 'docQuality', 'todoTracking',
|
|
571
584
|
'schemaSync', 'specKit', 'crossReference', 'generatedStaleness',
|
|
572
|
-
'canonicalSync', 'metricsConsistency',
|
|
585
|
+
'canonicalSync', 'surfaceSync', 'metricsConsistency', 'documentLifecycle',
|
|
586
|
+
'specRegistry', 'diffSuspicion', 'referenceExistence', 'apiDocSmells',
|
|
573
587
|
'evidence',
|
|
574
588
|
];
|
|
575
589
|
const userValidators = (config && config.validators) || {};
|
|
590
|
+
const exactHighValidators = new Set(
|
|
591
|
+
Object.entries(config.findingSeverity || {})
|
|
592
|
+
.filter(([, level]) => typeof level === 'string' && level.toLowerCase() === 'high')
|
|
593
|
+
.map(([code]) => CODES[code.toUpperCase()]?.validator)
|
|
594
|
+
.filter(Boolean)
|
|
595
|
+
);
|
|
576
596
|
const out = {};
|
|
577
597
|
for (const k of all) {
|
|
578
598
|
let enabled = CHANGED_ONLY_VALIDATORS.includes(k);
|
|
579
|
-
if (!enabled && resolveSeverity(config, k) === 'high' && userValidators[k] !== false) {
|
|
599
|
+
if (!enabled && (resolveSeverity(config, k) === 'high' || exactHighValidators.has(k)) && userValidators[k] !== false) {
|
|
580
600
|
enabled = true;
|
|
581
601
|
}
|
|
582
602
|
out[k] = enabled;
|
|
@@ -712,6 +732,9 @@ export function runGuard(projectDir, config, flags) {
|
|
|
712
732
|
} else {
|
|
713
733
|
console.log(` ${c.yellow}β οΈ ${v.name}${c.reset} ${qBadge}${c.dim} ${v.passed}/${v.total} checks passed${c.reset}`);
|
|
714
734
|
}
|
|
735
|
+
if (v.requirementCoverage?.deferred > 0) {
|
|
736
|
+
console.log(` ${c.dim}${v.requirementCoverage.deferred} planned requirement(s) deferred by reviewed lifecycle${c.reset}`);
|
|
737
|
+
}
|
|
715
738
|
|
|
716
739
|
// --show-failing forces enumeration of every error/warning regardless of
|
|
717
740
|
// overall validator status β useful when a validator passes overall
|
|
@@ -725,12 +748,13 @@ export function runGuard(projectDir, config, flags) {
|
|
|
725
748
|
for (const item of renderableItems(v)) {
|
|
726
749
|
if (item.severity === 'error' && !showErr) continue;
|
|
727
750
|
if (item.severity === 'warn' && !showWarn) continue;
|
|
728
|
-
const
|
|
751
|
+
const effective = item.effectiveSeverity || item.severity;
|
|
752
|
+
const mark = effective === 'error' ? `${c.red}β` : effective === 'info' ? `${c.cyan}β’` : `${c.yellow}β `;
|
|
729
753
|
const codeTag = item.code ? `${c.dim}[${item.code}]${c.reset} ` : '';
|
|
730
754
|
const conf = item.confidence === 'low'
|
|
731
755
|
? ` ${c.dim}(low confidence β possible false positive)${c.reset}` : '';
|
|
732
756
|
console.log(` ${mark} ${codeTag}${item.message}${c.reset}${conf}`);
|
|
733
|
-
if (item.suggestion) {
|
|
757
|
+
if (item.suggestion?.text) {
|
|
734
758
|
console.log(` ${c.cyan}β${c.reset} ${c.dim}${item.suggestion.text}${c.reset}`);
|
|
735
759
|
if (item.suggestion.command) {
|
|
736
760
|
console.log(` ${c.cyan}${item.suggestion.command}${c.reset}`);
|
|
@@ -927,13 +951,17 @@ export function runGuard(projectDir, config, flags) {
|
|
|
927
951
|
// "high"), show a one-line note so the user knows the exit code may not
|
|
928
952
|
// match what they expected from reading the warning count.
|
|
929
953
|
const severityShifted =
|
|
930
|
-
data.effectiveErrors !== data.errors || data.effectiveWarnings !== data.warnings;
|
|
954
|
+
data.effectiveErrors !== data.errors || data.effectiveWarnings !== data.warnings || data.effectiveInfos > 0;
|
|
931
955
|
if (severityShifted) {
|
|
932
|
-
const upgraded = data.
|
|
933
|
-
|
|
956
|
+
const upgraded = data.findings.filter(f => f.severity === 'warn' && f.effectiveSeverity === 'error').length
|
|
957
|
+
+ data.validators.filter(v => !Array.isArray(v.findings) || v.findings.length === 0)
|
|
958
|
+
.reduce((sum, v) => sum + (v.severity === 'high' ? v.warnings.length : 0), 0);
|
|
959
|
+
const demotedErrors = data.findings.filter(f => f.severity === 'error' && f.effectiveSeverity !== 'error').length;
|
|
960
|
+
const informational = data.effectiveInfos;
|
|
934
961
|
const parts = [];
|
|
935
962
|
if (upgraded > 0) parts.push(`${upgraded} warning(s) escalated to fail (severity=high)`);
|
|
936
|
-
if (
|
|
963
|
+
if (demotedErrors > 0) parts.push(`${demotedErrors} intrinsic error(s) explicitly demoted by finding code`);
|
|
964
|
+
if (informational > 0) parts.push(`${informational} finding(s) informational for exit code (severity=low)`);
|
|
937
965
|
if (parts.length > 0) {
|
|
938
966
|
console.log(`\n ${c.dim}Severity override: ${parts.join('; ')}.${c.reset}`);
|
|
939
967
|
}
|