docguard-cli 0.35.0 → 0.36.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/README.md +8 -15
- package/cli/commands/agent.mjs +27 -6
- package/cli/commands/ci.mjs +3 -0
- package/cli/commands/diagnose.mjs +8 -2
- package/cli/commands/feedback.mjs +83 -89
- package/cli/commands/fix.mjs +4 -0
- package/cli/commands/generate.mjs +3 -0
- package/cli/commands/guard.mjs +37 -20
- package/cli/commands/hooks.mjs +61 -40
- package/cli/commands/init.mjs +51 -5
- package/cli/commands/memory.mjs +29 -15
- package/cli/commands/report.mjs +12 -7
- package/cli/commands/score.mjs +39 -19
- package/cli/commands/sync.mjs +2 -0
- package/cli/commands/watch.mjs +113 -70
- package/cli/config.mjs +6 -3
- package/cli/docguard.mjs +12 -4
- package/cli/findings.mjs +13 -13
- package/cli/scanners/memory-plan.mjs +279 -134
- package/cli/scanners/project-type.mjs +6 -1
- package/cli/scanners/semantic-claims.mjs +176 -26
- package/cli/shared-diff.mjs +22 -1
- package/cli/shared-doc-roles.mjs +59 -0
- package/cli/shared-ignore.mjs +15 -2
- package/cli/shared-source.mjs +223 -1
- package/cli/validator-coverage.mjs +20 -0
- package/cli/validators/api-surface.mjs +94 -70
- package/cli/validators/architecture.mjs +19 -5
- package/cli/validators/diff-suspicion.mjs +45 -9
- package/cli/validators/docs-coverage.mjs +6 -5
- package/cli/validators/docs-diff.mjs +51 -7
- package/cli/validators/environment.mjs +3 -2
- package/cli/validators/freshness.mjs +140 -83
- package/cli/validators/schema-sync.mjs +3 -2
- package/cli/validators/security.mjs +58 -23
- package/cli/validators/structure.mjs +3 -1
- package/cli/validators/test-spec.mjs +3 -2
- package/cli/validators/todo-tracking.mjs +61 -28
- package/cli/validators/traceability.mjs +152 -38
- package/docs/configuration.md +41 -0
- package/extensions/spec-kit-docguard/extension.yml +2 -3
- 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/extensions.yml +1 -2
- package/extensions/spec-kit-docguard/templates/github-workflows/docguard-guard.yml +74 -29
- package/package.json +1 -1
- package/schemas/docguard-config.schema.json +43 -1
- package/templates/ci/github-actions.yml +51 -11
package/README.md
CHANGED
|
@@ -102,20 +102,13 @@ graph TD
|
|
|
102
102
|
|
|
103
103
|
## Why DocGuard?
|
|
104
104
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
The field data backs the enforcement-over-instructions bet: an ETH Zurich
|
|
113
|
-
study across 138 repos / 5,694 agent PRs found the most popular style of
|
|
114
|
-
agent-instruction file *hurts* agent performance, and practitioners keep
|
|
115
|
-
converging on the same lesson — written rules are routinely ignored;
|
|
116
|
-
programmatic checks are what agents (and humans) actually respect. That is
|
|
117
|
-
exactly the layer DocGuard provides: not another instructions file, but the
|
|
118
|
-
validator suite that makes the instructions and docs verifiably true.
|
|
105
|
+
DocGuard checks declared documentation facts against repository evidence and gives agents structured repair tasks. Deterministic checks cover supported facts, references, and generated sections. Human-authored requirements and architectural decisions retain their authority when implementation diverges.
|
|
106
|
+
|
|
107
|
+
A guard result describes the checks performed. The CDD grade measures structural maturity. Factual accuracy stays explicitly unverified until the relevant claims have supporting evidence. Coverage and unresolved claims remain visible, so teams can choose an appropriate enforcement policy.
|
|
108
|
+
|
|
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
|
+
|
|
111
|
+
The development plan prioritizes accurate detection, reproducible evidence, and contributor-supplied regression cases. See [the trust roadmap](docs-implementation/TRUST-ROADMAP.md) for implementation status, proposed experiments, and acceptance criteria.
|
|
119
112
|
|
|
120
113
|
---
|
|
121
114
|
|
|
@@ -215,7 +208,7 @@ DocGuard splits drift into two kinds and is explicit about which is which:
|
|
|
215
208
|
|
|
216
209
|
`docguard fix --write` only touches docs marked `<!-- docguard:generated true -->` (override with `--force`), is idempotent, and prints exactly what changed. It never rewrites prose — that stays with the agent.
|
|
217
210
|
|
|
218
|
-
###
|
|
211
|
+
### Continuous documentation workflow
|
|
219
212
|
|
|
220
213
|
```
|
|
221
214
|
guard ──▶ fix --write (mechanical, auto) ──▶ guard ──▶ diagnose (agent prompts for the rest)
|
package/cli/commands/agent.mjs
CHANGED
|
@@ -23,6 +23,8 @@
|
|
|
23
23
|
|
|
24
24
|
import { buildMemoryPlan } from '../scanners/memory-plan.mjs';
|
|
25
25
|
import { c } from '../shared.mjs';
|
|
26
|
+
import { createEvidenceReader, citedSources, taskEvidence, gitEvidence, SEMANTIC_COVERAGE_LIMITATION } from '../scanners/semantic-claims.mjs';
|
|
27
|
+
import { buildScoreAssurance } from './score.mjs';
|
|
26
28
|
|
|
27
29
|
const PHASES = ['config', 'canonical-docs', 'verify'];
|
|
28
30
|
|
|
@@ -31,8 +33,8 @@ function docSlug(path) {
|
|
|
31
33
|
}
|
|
32
34
|
|
|
33
35
|
/**
|
|
34
|
-
* Transform a memory plan into the ordered agent task graph
|
|
35
|
-
*
|
|
36
|
+
* Transform a memory plan into the ordered agent task graph, with bounded
|
|
37
|
+
* read-only input snapshots. Evidence records inputs, never completed review.
|
|
36
38
|
*/
|
|
37
39
|
export function buildAgentTaskGraph(projectDir, config, plan) {
|
|
38
40
|
const profileName = config.profile || 'standard';
|
|
@@ -69,7 +71,13 @@ export function buildAgentTaskGraph(projectDir, config, plan) {
|
|
|
69
71
|
? `Insert the pre-filled "${sec.id}" content into ${doc.path} verbatim — it is extracted from your code. Only fill any \`<!-- … -->\` placeholders.`
|
|
70
72
|
: sec.task,
|
|
71
73
|
grounding: sec.grounding || null,
|
|
72
|
-
acceptance: {
|
|
74
|
+
acceptance: {
|
|
75
|
+
verify: 'docguard guard --format json',
|
|
76
|
+
expect: `no missing/stale finding for ${doc.path}; ${isCode ? 'compare generated content with current sources' : 'review prose against sources and project intent separately'}`,
|
|
77
|
+
scope: 'structural-only',
|
|
78
|
+
reviewRequired: true,
|
|
79
|
+
factualAccuracy: 'unknown',
|
|
80
|
+
},
|
|
73
81
|
confidence: isCode ? 'high' : 'requires-human',
|
|
74
82
|
});
|
|
75
83
|
}
|
|
@@ -81,15 +89,27 @@ export function buildAgentTaskGraph(projectDir, config, plan) {
|
|
|
81
89
|
phase: 'verify',
|
|
82
90
|
file: null,
|
|
83
91
|
kind: 'verify',
|
|
84
|
-
instruction: 'Run `docguard guard --format json`. Resolve every error
|
|
92
|
+
instruction: 'Run `docguard guard --format json`. Resolve every error, then re-run until there are 0 errors. Triage warnings and record unresolved warnings; warnings do not fail this acceptance gate. Run `docguard score` for structural maturity, and `docguard verify --semantic` to obtain unverified claim tasks for separate source review. Neither guard nor score verifies prose or factual accuracy.',
|
|
85
93
|
prefilled: null,
|
|
86
94
|
grounding: null,
|
|
87
|
-
acceptance: { verify: 'docguard guard --format json', expect: '0 errors' },
|
|
95
|
+
acceptance: { verify: 'docguard guard --format json', expect: '0 errors', warnings: 'triage-and-report', scope: 'structural-only', factualAccuracy: 'unknown' },
|
|
88
96
|
confidence: 'high',
|
|
89
97
|
});
|
|
90
98
|
|
|
99
|
+
const read = createEvidenceReader(projectDir);
|
|
100
|
+
for (const task of tasks) {
|
|
101
|
+
const inputs = JSON.stringify({ instruction: task.instruction, prefilled: task.prefilled, grounding: task.grounding });
|
|
102
|
+
const citations = citedSources([read(task.file).content, task.prefilled, task.instruction].filter(Boolean).join('\n'));
|
|
103
|
+
task.evidence = taskEvidence(read, task.file, citations, inputs);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const assurance = buildScoreAssurance(projectDir, config);
|
|
107
|
+
assurance.limitation += ` ${SEMANTIC_COVERAGE_LIMITATION}`;
|
|
108
|
+
|
|
91
109
|
return {
|
|
92
110
|
project: config.projectName,
|
|
111
|
+
provenance: { kind: 'snapshot', git: gitEvidence(projectDir) },
|
|
112
|
+
assurance,
|
|
93
113
|
profile: { name: profileName, kind: plan.profile.kind, languages: plan.profile.languages, frameworks: plan.profile.frameworks },
|
|
94
114
|
order: PHASES,
|
|
95
115
|
counts: {
|
|
@@ -106,7 +126,7 @@ export function runAgent(projectDir, config, flags) {
|
|
|
106
126
|
// Allow `--profile <name>` to preview a profile's plan without having to run
|
|
107
127
|
// `init` first (the field-report agent had no config yet on its first call).
|
|
108
128
|
const cfg = flags.profile ? { ...config, profile: flags.profile } : config;
|
|
109
|
-
const plan = buildMemoryPlan(projectDir, cfg);
|
|
129
|
+
const plan = buildMemoryPlan(projectDir, { ...cfg, diskCache: false });
|
|
110
130
|
const graph = buildAgentTaskGraph(projectDir, cfg, plan);
|
|
111
131
|
|
|
112
132
|
if (flags.format === 'json') {
|
|
@@ -119,6 +139,7 @@ export function runAgent(projectDir, config, flags) {
|
|
|
119
139
|
{
|
|
120
140
|
console.log(`${c.bold}🤖 DocGuard Agent Task Graph — ${graph.project}${c.reset}`);
|
|
121
141
|
console.log(`${c.dim} profile: ${graph.profile.name} · kind: ${graph.profile.kind} · ${graph.counts.tasks} tasks (${graph.counts.codeTruth} code-truth, ${graph.counts.humanJudgment} human)${c.reset}\n`);
|
|
142
|
+
console.log(` ${c.dim}Snapshot only · structural checks do not verify prose · ${graph.assurance.unverifiedClaims ?? 'unknown'} extracted claims await review.${c.reset}\n`);
|
|
122
143
|
for (const phase of graph.order) {
|
|
123
144
|
const inPhase = graph.tasks.filter(t => t.phase === phase);
|
|
124
145
|
if (!inPhase.length) continue;
|
package/cli/commands/ci.mjs
CHANGED
|
@@ -62,6 +62,7 @@ export function runCI(projectDir, config, flags) {
|
|
|
62
62
|
errors: guardData.errors,
|
|
63
63
|
warnings: guardData.warnings,
|
|
64
64
|
baselineSuppressed: guardData.baselineSuppressed || 0,
|
|
65
|
+
checkCoverage: guardData.checkCoverage,
|
|
65
66
|
passed: guardData.passed,
|
|
66
67
|
total: guardData.total,
|
|
67
68
|
status,
|
|
@@ -74,6 +75,8 @@ export function runCI(projectDir, config, flags) {
|
|
|
74
75
|
project: config.projectName,
|
|
75
76
|
profile: config.profile || 'standard',
|
|
76
77
|
projectType: config.projectType || 'unknown',
|
|
78
|
+
scoreKind: scoreData.scoreKind,
|
|
79
|
+
assurance: scoreData.assurance,
|
|
77
80
|
score: scoreData.score,
|
|
78
81
|
grade: scoreData.grade,
|
|
79
82
|
guard: {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { assertDefaultDocWrites } from '../shared-doc-roles.mjs';
|
|
1
2
|
/**
|
|
2
3
|
* Diagnose Command — The AI Orchestrator
|
|
3
4
|
*
|
|
@@ -181,6 +182,7 @@ const FIX_INSTRUCTIONS = {
|
|
|
181
182
|
};
|
|
182
183
|
|
|
183
184
|
export function runDiagnose(projectDir, config, flags) {
|
|
185
|
+
if (flags.auto) assertDefaultDocWrites(config);
|
|
184
186
|
// ── Step 0: Detect agent mode (LLM-first) ──
|
|
185
187
|
const agentMode = detectAgentMode(projectDir);
|
|
186
188
|
|
|
@@ -347,6 +349,9 @@ function outputJSON(guardData, scoreData, issues) {
|
|
|
347
349
|
status: guardData.status,
|
|
348
350
|
score: scoreData.score,
|
|
349
351
|
grade: scoreData.grade,
|
|
352
|
+
scoreKind: scoreData.scoreKind,
|
|
353
|
+
assurance: scoreData.assurance,
|
|
354
|
+
checkCoverage: guardData.checkCoverage,
|
|
350
355
|
issueCount: issues.length,
|
|
351
356
|
issues: issues.map(i => ({
|
|
352
357
|
severity: i.severity,
|
|
@@ -493,7 +498,8 @@ function outputPrompt(projectDir, guardData, scoreData, issues, flags, agentMode
|
|
|
493
498
|
lines.push('After making all fixes, run: docguard guard');
|
|
494
499
|
}
|
|
495
500
|
lines.push('Expected result: All checks pass (0 errors, 0 warnings)');
|
|
496
|
-
lines.push(`
|
|
501
|
+
lines.push(`Structural baseline: ${scoreData.score}/100. Resolve evidenced defects; verify material claims separately.`);
|
|
502
|
+
lines.push('Preserve approved requirements when implementation disagrees. A higher score is not proof of factual correctness.');
|
|
497
503
|
|
|
498
504
|
// Agent-aware: add explicit checklist for basic-tier agents
|
|
499
505
|
if (agentTier === 'basic') {
|
|
@@ -581,7 +587,7 @@ function outputDebatePrompt(projectDir, guardData, scoreData, issues, agentMode
|
|
|
581
587
|
lines.push(' c. What content to write (be specific, not vague)');
|
|
582
588
|
const verifyCmd = agentMode === 'llm' ? '/docguard.guard' : 'docguard guard';
|
|
583
589
|
lines.push(`4. After all fixes, verify with: ${verifyCmd}`);
|
|
584
|
-
lines.push(
|
|
590
|
+
lines.push('5. Verify repaired claims against their evidence and retain unresolved uncertainty. Structural score is a proxy.');
|
|
585
591
|
lines.push('');
|
|
586
592
|
lines.push('═══════════════════════════════════════════════════════');
|
|
587
593
|
lines.push('Execute all three perspectives in sequence, then implement the Synthesizer\'s plan.');
|
|
@@ -1,32 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Feedback Command —
|
|
2
|
+
* Feedback Command — prepare opt-in detection feedback.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* other finding DocGuard itself flagged as uncertain) into:
|
|
4
|
+
* Select uncertain findings by default, or challenge active findings with
|
|
5
|
+
* --code / --all. Full local records may contain private diagnostics;
|
|
6
|
+
* --preview skips feedback-record writes. Persistence outcomes are explicit.
|
|
8
7
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
8
|
+
* Shared issue and search URLs contain only allowlisted finding identity,
|
|
9
|
+
* tool metadata, and contribution instructions. Paths, source text, messages,
|
|
10
|
+
* suggestions, and redactedContext are excluded. URLs are capped; users must
|
|
11
|
+
* review and supply a synthetic reproduction before submitting an issue.
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* - The URL is CAPPED well under the limit; bulk lives in the local file.
|
|
16
|
-
* - It is REDACTED: no source code, no secret values — only a basename, a line
|
|
17
|
-
* number, and the safe `redactedContext` the validator built.
|
|
18
|
-
* - It is OPT-IN: nothing is filed automatically; the human clicks (or not).
|
|
19
|
-
*
|
|
20
|
-
* Not read-only in the strict sense — it writes its own .docguard/feedback/ —
|
|
21
|
-
* but it never scaffolds skills and never touches the user's source tree.
|
|
22
|
-
*
|
|
23
|
-
* Zero npm dependencies — pure Node.js built-ins.
|
|
13
|
+
* Nothing is transmitted automatically. This command never scaffolds skills
|
|
14
|
+
* or edits source files. Zero npm dependencies — pure Node.js built-ins.
|
|
24
15
|
*/
|
|
25
16
|
|
|
26
|
-
import { existsSync, mkdirSync,
|
|
27
|
-
import { resolve, dirname
|
|
17
|
+
import { existsSync, mkdirSync, readFileSync } from 'node:fs';
|
|
18
|
+
import { resolve, dirname } from 'node:path';
|
|
28
19
|
import { fileURLToPath } from 'node:url';
|
|
29
20
|
import { c } from '../shared.mjs';
|
|
21
|
+
import { CODES } from '../findings.mjs';
|
|
22
|
+
import { safeWrite } from '../writers/generate-io.mjs';
|
|
30
23
|
import { runGuardInternal } from './guard.mjs';
|
|
31
24
|
|
|
32
25
|
const _PKG = JSON.parse(
|
|
@@ -45,69 +38,54 @@ function shortId(str) {
|
|
|
45
38
|
return h.toString(36).slice(0, 6);
|
|
46
39
|
}
|
|
47
40
|
|
|
48
|
-
/**
|
|
49
|
-
function
|
|
50
|
-
|
|
51
|
-
const [
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
* Build a prefilled, capped issue URL. Drops optional body lines (longest-value
|
|
57
|
-
* first) until under the cap; title + code + location always survive.
|
|
58
|
-
*/
|
|
59
|
-
function buildIssueUrl(finding) {
|
|
60
|
-
const code = finding.code || 'FINDING';
|
|
61
|
-
const validator = finding.validator || 'unknown';
|
|
62
|
-
const shortMsg = (finding.message || '').replace(/\s+/g, ' ').slice(0, 70);
|
|
63
|
-
const title = `[feedback] ${code} (${validator}): ${shortMsg}`;
|
|
64
|
-
|
|
65
|
-
// Optional lines are ordered most→least droppable.
|
|
66
|
-
const required = [
|
|
67
|
-
`DocGuard v${CLI_VERSION} flagged this and it may be a false positive (or other feedback).`,
|
|
68
|
-
'',
|
|
41
|
+
/** Shared output deliberately excludes every source-derived string. */
|
|
42
|
+
export function buildIssueUrl(finding) {
|
|
43
|
+
const code = Object.hasOwn(CODES, finding.code || '') ? finding.code : 'FINDING';
|
|
44
|
+
const validator = CODES[code]?.validator || 'unknown';
|
|
45
|
+
const title = `[feedback] ${code} (${validator}): detection feedback`;
|
|
46
|
+
const confidence = ['high', 'medium', 'low'].includes(finding.confidence) ? finding.confidence : 'unknown';
|
|
47
|
+
const body = [
|
|
48
|
+
`DocGuard v${CLI_VERSION}`,
|
|
69
49
|
`- Code: ${code}`,
|
|
70
50
|
`- Validator: ${validator}`,
|
|
71
|
-
`-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
opt = opt.slice(0, -1);
|
|
87
|
-
url = compose(opt);
|
|
88
|
-
}
|
|
89
|
-
if (url.length > URL_CAP) {
|
|
90
|
-
// Even the required body is too long (pathological) — collapse to a stub.
|
|
91
|
-
url = `${ISSUES_BASE}/new?labels=${encodeURIComponent('docguard-feedback')}` +
|
|
92
|
-
`&title=${encodeURIComponent(title)}` +
|
|
93
|
-
`&body=${encodeURIComponent(`DocGuard v${CLI_VERSION} — ${code} (${validator}). Full details saved locally; please attach.`)}`;
|
|
94
|
-
}
|
|
95
|
-
return { url, title };
|
|
51
|
+
`- Confidence: ${confidence}`,
|
|
52
|
+
'',
|
|
53
|
+
'Expected behavior:',
|
|
54
|
+
'Actual behavior:',
|
|
55
|
+
'Minimal synthetic reproduction (review before attaching):',
|
|
56
|
+
'',
|
|
57
|
+
'Check existing open AND closed issues and pull requests before submitting.',
|
|
58
|
+
'A regression-test-only contribution is welcome; explain the expected behavior.',
|
|
59
|
+
'',
|
|
60
|
+
'Generated by docguard feedback. No project paths, messages, source code, or secret values are included.',
|
|
61
|
+
].join('\n');
|
|
62
|
+
const url = `${ISSUES_BASE}/new?labels=docguard-feedback&title=${encodeURIComponent(title)}&body=${encodeURIComponent(body)}`;
|
|
63
|
+
const query = `repo:raccioly/docguard ${code}`;
|
|
64
|
+
const searchUrl = `https://github.com/search?q=${encodeURIComponent(query)}&type=issues`;
|
|
65
|
+
return { url: url.length <= URL_CAP ? url : `${ISSUES_BASE}/new`, title, searchUrl };
|
|
96
66
|
}
|
|
97
67
|
|
|
98
68
|
export function runFeedback(projectDir, config, flags) {
|
|
99
69
|
const data = runGuardInternal(projectDir, config);
|
|
100
|
-
const
|
|
70
|
+
const selectedCode = typeof flags.code === 'string' ? flags.code.toUpperCase() : null;
|
|
71
|
+
if (flags.code !== undefined && (!selectedCode || !Object.hasOwn(CODES, selectedCode))) {
|
|
72
|
+
const error = 'Unknown finding code. Run docguard explain <CODE> to inspect supported findings.';
|
|
73
|
+
if (flags.format === 'json') console.log(JSON.stringify({ error, reportable: [] }));
|
|
74
|
+
else console.error(error);
|
|
75
|
+
process.exitCode = 1;
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const reportable = (data.findings || []).filter(f => selectedCode ? f.code === selectedCode : flags.all || f.reportable);
|
|
101
79
|
const isJson = flags.format === 'json';
|
|
102
80
|
|
|
103
81
|
if (reportable.length === 0) {
|
|
104
82
|
if (isJson) {
|
|
105
|
-
console.log(JSON.stringify({ reportable: [], message: 'no
|
|
83
|
+
console.log(JSON.stringify({ reportable: [], message: 'no matching findings; use --code <CODE> or --all to challenge confident findings' }, null, 2));
|
|
106
84
|
return;
|
|
107
85
|
}
|
|
108
86
|
console.log(`${c.bold}📮 DocGuard Feedback${c.reset}`);
|
|
109
|
-
console.log(`${c.green}✅
|
|
110
|
-
console.log(`${c.dim}
|
|
87
|
+
console.log(`${c.green}✅ No findings matched this selection. Confidence is not proof of correctness.${c.reset}`);
|
|
88
|
+
console.log(`${c.dim} Use --code <CODE> or --all to challenge a confident finding; --preview avoids saving records.${c.reset}\n`);
|
|
111
89
|
return;
|
|
112
90
|
}
|
|
113
91
|
|
|
@@ -115,37 +93,51 @@ export function runFeedback(projectDir, config, flags) {
|
|
|
115
93
|
const feedbackDir = resolve(projectDir, '.docguard', 'feedback');
|
|
116
94
|
const items = reportable.map((f) => {
|
|
117
95
|
const id = shortId(`${f.code}|${f.location || f.message}`);
|
|
118
|
-
const { url, title } = buildIssueUrl(f);
|
|
96
|
+
const { url, title, searchUrl } = buildIssueUrl(f);
|
|
119
97
|
const fileName = `${(f.code || 'finding').toLowerCase()}-${id}.json`;
|
|
120
98
|
const filePath = resolve(feedbackDir, fileName);
|
|
121
|
-
return { finding: f, id, url, title, fileName, filePath };
|
|
99
|
+
return { finding: f, id, url, title, searchUrl, fileName, filePath, saved: false, error: null };
|
|
122
100
|
});
|
|
123
101
|
|
|
124
|
-
if (isJson) {
|
|
125
|
-
console.log(JSON.stringify({
|
|
126
|
-
version: CLI_VERSION,
|
|
127
|
-
reportable: items.map((it) => ({ code: it.finding.code, location: it.finding.location, url: it.url, file: `.docguard/feedback/${it.fileName}` })),
|
|
128
|
-
}, null, 2));
|
|
129
|
-
// Still write the local records so the JSON path is not a dead end.
|
|
130
|
-
}
|
|
131
|
-
|
|
132
102
|
let wrote = 0;
|
|
133
|
-
for (const it of items) {
|
|
103
|
+
for (const it of flags.preview ? [] : items) {
|
|
134
104
|
try {
|
|
135
105
|
if (!existsSync(feedbackDir)) mkdirSync(feedbackDir, { recursive: true });
|
|
136
|
-
|
|
106
|
+
safeWrite(it.filePath, JSON.stringify({
|
|
137
107
|
capturedBy: `docguard feedback (v${CLI_VERSION})`,
|
|
138
108
|
finding: it.finding,
|
|
139
109
|
issueUrl: it.url,
|
|
140
|
-
|
|
110
|
+
searchUrl: it.searchUrl,
|
|
111
|
+
sharing: 'The finding record can contain private project information. Share only a reviewed synthetic reproduction.',
|
|
112
|
+
}, null, 2) + '\n');
|
|
113
|
+
it.saved = true;
|
|
141
114
|
wrote++;
|
|
142
|
-
} catch {
|
|
115
|
+
} catch (err) {
|
|
116
|
+
it.error = `Unable to save feedback record (${err.code || 'write failed'}).`;
|
|
117
|
+
}
|
|
143
118
|
}
|
|
144
119
|
|
|
145
|
-
if (
|
|
120
|
+
if (items.some(it => it.error)) process.exitCode = 1;
|
|
121
|
+
|
|
122
|
+
if (isJson) {
|
|
123
|
+
console.log(JSON.stringify({
|
|
124
|
+
version: CLI_VERSION,
|
|
125
|
+
preview: Boolean(flags.preview),
|
|
126
|
+
reportable: items.map(it => ({
|
|
127
|
+
code: it.finding.code,
|
|
128
|
+
location: it.finding.location,
|
|
129
|
+
url: it.url,
|
|
130
|
+
searchUrl: it.searchUrl,
|
|
131
|
+
file: it.saved ? `.docguard/feedback/${it.fileName}` : null,
|
|
132
|
+
saved: it.saved,
|
|
133
|
+
error: it.error,
|
|
134
|
+
})),
|
|
135
|
+
}, null, 2));
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
146
138
|
|
|
147
139
|
console.log(`${c.bold}📮 DocGuard Feedback${c.reset}`);
|
|
148
|
-
console.log(`${c.dim} ${items.length}
|
|
140
|
+
console.log(`${c.dim} ${items.length} selected finding(s). ${flags.preview ? 'Preview only; no feedback records saved.' : `${wrote} local record(s) saved to .docguard/feedback/`}\n`);
|
|
149
141
|
|
|
150
142
|
for (const it of items) {
|
|
151
143
|
const f = it.finding;
|
|
@@ -153,9 +145,11 @@ export function runFeedback(projectDir, config, flags) {
|
|
|
153
145
|
if (f.suggestion && f.suggestion.pragma) {
|
|
154
146
|
console.log(` ${c.dim}Suppress locally instead: ${f.suggestion.pragma}${c.reset}`);
|
|
155
147
|
}
|
|
156
|
-
console.log(` ${c.dim}
|
|
148
|
+
console.log(` ${c.dim}Check for duplicates (including closed work): ${it.searchUrl}${c.reset}`);
|
|
149
|
+
console.log(` ${c.dim}Report (review before submitting):${c.reset}`);
|
|
157
150
|
console.log(` ${c.cyan}${it.url}${c.reset}`);
|
|
158
|
-
console.log(` ${c.dim}Local copy: .docguard/feedback/${it.fileName}${c.reset}\n`);
|
|
151
|
+
if (it.saved) console.log(` ${c.dim}Local copy (may contain private project details): .docguard/feedback/${it.fileName}${c.reset}\n`);
|
|
152
|
+
if (it.error) console.error(` ${c.red}${it.error}${c.reset}`);
|
|
159
153
|
}
|
|
160
154
|
|
|
161
155
|
console.log(`${c.dim}These reports help DocGuard stop flagging the same false positive in a future release.${c.reset}`);
|
package/cli/commands/fix.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { assertDefaultDocWrites } from '../shared-doc-roles.mjs';
|
|
1
2
|
/**
|
|
2
3
|
* Fix Command — The AI Orchestrator
|
|
3
4
|
*
|
|
@@ -37,6 +38,7 @@ const API_DOC = 'docs-canonical/API-REFERENCE.md';
|
|
|
37
38
|
* @returns {{ applied: boolean, removed: Array<{method,path}>, skipped?: string }}
|
|
38
39
|
*/
|
|
39
40
|
export function applyApiSurfaceWrites(projectDir, config, { force = false } = {}) {
|
|
41
|
+
assertDefaultDocWrites(config);
|
|
40
42
|
const drift = computeApiSurfaceDrift(projectDir, config);
|
|
41
43
|
// Only spec-confirmed absences are safe to delete deterministically.
|
|
42
44
|
const removable = drift.confidence === 'spec' ? drift.documentedButAbsent : [];
|
|
@@ -273,6 +275,7 @@ IMPORTANT: A new contributor should be able to follow this doc and have the proj
|
|
|
273
275
|
* @returns {{ applied: object[], skipped: object[], total: number }}
|
|
274
276
|
*/
|
|
275
277
|
export function applyAllMechanicalFixes(projectDir, config, opts = {}) {
|
|
278
|
+
assertDefaultDocWrites(config);
|
|
276
279
|
const { force = false, forceRedo = false } = opts;
|
|
277
280
|
const guardData = runGuardInternal(projectDir, config);
|
|
278
281
|
const fixes = [];
|
|
@@ -372,6 +375,7 @@ function runWriteMode(projectDir, config, flags) {
|
|
|
372
375
|
// ── Main Entry ─────────────────────────────────────────────────────────────
|
|
373
376
|
|
|
374
377
|
export function runFix(projectDir, config, flags) {
|
|
378
|
+
if (flags.write) assertDefaultDocWrites(config);
|
|
375
379
|
const isJson = flags.format === 'json';
|
|
376
380
|
const isPrompt = flags.format === 'prompt';
|
|
377
381
|
const autoFix = flags.auto || false;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { assertDefaultDocWrites } from '../shared-doc-roles.mjs';
|
|
1
2
|
/**
|
|
2
3
|
* Generate Command — Reverse-engineer canonical docs from an existing codebase
|
|
3
4
|
* Scans source code and creates documentation templates pre-filled with project data.
|
|
@@ -39,6 +40,7 @@ const CODE_EXTENSIONS = new Set([
|
|
|
39
40
|
* inserted as agent-task placeholders), respecting human prose via markers.
|
|
40
41
|
*/
|
|
41
42
|
export function runGeneratePlan(projectDir, config, flags) {
|
|
43
|
+
if (flags.write) assertDefaultDocWrites(config);
|
|
42
44
|
// `--profile <name>` previews a profile's doc set without needing `init` first.
|
|
43
45
|
if (flags.profile) config = { ...config, profile: flags.profile };
|
|
44
46
|
const plan = buildMemoryPlan(projectDir, config);
|
|
@@ -137,6 +139,7 @@ export function runGeneratePlan(projectDir, config, flags) {
|
|
|
137
139
|
}
|
|
138
140
|
|
|
139
141
|
export function runGenerate(projectDir, config, flags) {
|
|
142
|
+
if (!flags.plan || flags.write) assertDefaultDocWrites(config);
|
|
140
143
|
// --plan: emit the AI-powered "memory plan" — the agent task manifest. The CLI
|
|
141
144
|
// builds the code-truth skeleton (marked sections) + tells the agent exactly
|
|
142
145
|
// what prose to write per section. This is the language-aware Generate path.
|