docguard-cli 0.18.1 → 0.21.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 +128 -34
- package/cli/commands/demo.mjs +241 -0
- package/cli/commands/guard.mjs +20 -2
- package/cli/commands/init.mjs +122 -0
- package/cli/docguard.mjs +125 -47
- package/cli/validators/canonical-sync.mjs +211 -0
- package/cli/validators/spec-kit.mjs +14 -0
- package/docs/quickstart.md +1 -1
- package/extensions/spec-kit-docguard/README.md +1 -1
- package/extensions/spec-kit-docguard/extension.yml +5 -5
- 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 +1 -1
- package/package.json +1 -1
- package/templates/demo-fixture/.docguard.json +8 -0
- package/templates/demo-fixture/.env.example +5 -0
- package/templates/demo-fixture/AGENTS.md +14 -0
- package/templates/demo-fixture/CHANGELOG.md +13 -0
- package/templates/demo-fixture/DRIFT-LOG.md +3 -0
- package/templates/demo-fixture/README.md +17 -0
- package/templates/demo-fixture/docs-canonical/API-REFERENCE.md +36 -0
- package/templates/demo-fixture/docs-canonical/ARCHITECTURE.md +30 -0
- package/templates/demo-fixture/docs-canonical/DATA-MODEL.md +30 -0
- package/templates/demo-fixture/docs-canonical/ENVIRONMENT.md +20 -0
- package/templates/demo-fixture/docs-canonical/SECURITY.md +15 -0
- package/templates/demo-fixture/docs-canonical/TEST-SPEC.md +10 -0
- package/templates/demo-fixture/package.json +10 -0
- package/templates/demo-fixture/src/api.mjs +18 -0
- package/templates/demo-fixture/src/notifier.mjs +23 -0
- package/templates/demo-fixture/src/scheduler.mjs +8 -0
- package/templates/demo-fixture/src/worker.mjs +15 -0
package/README.md
CHANGED
|
@@ -12,9 +12,18 @@
|
|
|
12
12
|
|
|
13
13
|
---
|
|
14
14
|
|
|
15
|
+
> **✨ See what DocGuard catches in 30 seconds — no install, no setup:**
|
|
16
|
+
> ```bash
|
|
17
|
+
> npx docguard-cli demo
|
|
18
|
+
> ```
|
|
19
|
+
> Runs against a baked-in sample project with intentional drift and shows you the findings + a clear path to fixing them.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
15
23
|
## Table of Contents
|
|
16
24
|
|
|
17
25
|
- [What is DocGuard?](#what-is-docguard)
|
|
26
|
+
- [What's New](#-whats-new)
|
|
18
27
|
- [Quick Start](#-quick-start)
|
|
19
28
|
- [Spec Kit Integration](#-spec-kit-integration)
|
|
20
29
|
- [Usage](#usage)
|
|
@@ -50,15 +59,15 @@ DocGuard is an official [GitHub Spec Kit](https://github.com/github/spec-kit) co
|
|
|
50
59
|
|
|
51
60
|
```mermaid
|
|
52
61
|
graph TD
|
|
53
|
-
CLI["CLI Entry<br/>docguard.mjs"] --> Commands["Commands (
|
|
62
|
+
CLI["CLI Entry<br/>docguard.mjs"] --> Commands["Commands (14)"]
|
|
54
63
|
Commands --> guard["guard"]
|
|
55
64
|
Commands --> generate["generate"]
|
|
56
65
|
Commands --> score["score"]
|
|
57
66
|
Commands --> diagnose["diagnose"]
|
|
58
67
|
Commands --> setup["setup wizard"]
|
|
59
|
-
Commands --> other["diff · init · fix · trace<br/>agents · hooks · badge · ci · watch"]
|
|
68
|
+
Commands --> other["diff · init · fix · trace · impact · sync<br/>explain · memory · upgrade · agents · hooks · badge · ci · watch"]
|
|
60
69
|
|
|
61
|
-
guard --> Validators["Validators (
|
|
70
|
+
guard --> Validators["Validators (23)"]
|
|
62
71
|
generate --> Scanners["Scanners (4)<br/>routes · schemas · doc-tools · speckit"]
|
|
63
72
|
score --> Scoring["Weighted Scoring<br/>8 categories"]
|
|
64
73
|
diagnose --> Validators
|
|
@@ -81,6 +90,37 @@ graph TD
|
|
|
81
90
|
|
|
82
91
|
---
|
|
83
92
|
|
|
93
|
+
## ✨ What's New
|
|
94
|
+
|
|
95
|
+
Recent highlights across the v0.16 → v0.19 line:
|
|
96
|
+
|
|
97
|
+
- **`docguard explain <validator>`** — `docguard explain freshness` prints purpose, rules, common
|
|
98
|
+
failures, and fix recipes for any of the 23 validators. No need to dig into source.
|
|
99
|
+
- **`docguard memory --diff`** — surface what changed in your canonical docs between two refs
|
|
100
|
+
(`HEAD~10..HEAD` by default). Great for code review and changelog drafting.
|
|
101
|
+
- **`docguard score --diff`** — see exactly which validators moved the score up or down between
|
|
102
|
+
two commits. Pinpoints regressions without re-running the full suite by hand.
|
|
103
|
+
- **`docguard upgrade --apply --pr`** — when the config schema bumps, DocGuard migrates
|
|
104
|
+
`.docguard.json` for you and (optionally) opens a PR with the change.
|
|
105
|
+
- **Language-aware patterns** — test discovery and trace mapping now understand Python, Rust, Go,
|
|
106
|
+
Java, Ruby, and PHP layouts in addition to JS/TS. Sensible defaults, override via config.
|
|
107
|
+
- **Per-validator severity overrides** — escalate `freshness` to `high` for production repos,
|
|
108
|
+
demote `doc-quality` to `low` for prototypes. Configurable per-project.
|
|
109
|
+
- **JSON Schema for `.docguard.json`** — IDE autocomplete, in-line docs, and validation via
|
|
110
|
+
`$schema`. Shipped in the package at `schemas/docguard-config.schema.json`.
|
|
111
|
+
- **Version pin (`docguardVersion` + `--pin`)** — pin the CLI version your project supports so
|
|
112
|
+
CI fails loudly if someone bumps DocGuard without re-running the suite.
|
|
113
|
+
- **Cross-process plan cache** — repeated runs reuse the validator plan across processes when
|
|
114
|
+
the working tree hasn't changed. ~30% faster guard runs on typical repos.
|
|
115
|
+
- **Headless-aware banner** — `--quiet`, `--format json`, `--write`, and `--changed-only`
|
|
116
|
+
automatically suppress the banner so JSON output stays parse-clean.
|
|
117
|
+
- **npm-pack smoke gate** — every release now extracts the actual tarball and runs the CLI
|
|
118
|
+
end-to-end before publish, catching missing-file regressions.
|
|
119
|
+
|
|
120
|
+
See [CHANGELOG.md](CHANGELOG.md) for the full history.
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
84
124
|
## ⚡ Quick Start
|
|
85
125
|
|
|
86
126
|
### Node.js (npm)
|
|
@@ -203,24 +243,49 @@ This installs DocGuard's slash commands (`/docguard.guard`, `/docguard.review`,
|
|
|
203
243
|
|
|
204
244
|
## Usage
|
|
205
245
|
|
|
206
|
-
DocGuard ships **
|
|
246
|
+
DocGuard ships **14 commands** (the "Daily 5" + 9 situational tools, including the zero-install `demo`). Six additional one-shot scaffolders are accessed via `docguard init --with <name>`. Eight v0.19 commands continue to work as deprecation aliases through v0.20.x — see [MIGRATION-v0.20.md](docs-implementation/MIGRATION-v0.20.md).
|
|
247
|
+
|
|
248
|
+
**The Daily 5** — what you'll reach for 95% of the time:
|
|
249
|
+
|
|
250
|
+
| Command | What It Does |
|
|
251
|
+
|:--------|:-------------|
|
|
252
|
+
| `init` | Bootstrap a project (`--wizard` for interactive · `--with <name>` for scaffolders) |
|
|
253
|
+
| `guard` | Validate against canonical docs — 23 validators |
|
|
254
|
+
| `diff` | Show gaps between docs and code (`--since <ref>` for impact mode) |
|
|
255
|
+
| `sync` | Refresh code-truth doc sections — keeps memory always up to date |
|
|
256
|
+
| `score` | CDD maturity score (0-100; `--diff` for delta between refs) |
|
|
257
|
+
|
|
258
|
+
**Tools (situational, but day-to-day useful):**
|
|
207
259
|
|
|
208
260
|
| Command | Purpose |
|
|
209
261
|
|:--------|:--------|
|
|
210
|
-
| `diagnose` |
|
|
211
|
-
| `
|
|
212
|
-
| `
|
|
213
|
-
| `
|
|
214
|
-
| `
|
|
215
|
-
| `
|
|
216
|
-
| `
|
|
217
|
-
| `diff` |
|
|
218
|
-
| `
|
|
219
|
-
| `trace` | Requirements traceability
|
|
220
|
-
| `
|
|
221
|
-
| `watch` | Live
|
|
222
|
-
|
|
223
|
-
|
|
262
|
+
| `diagnose` | AI orchestrator — guard → emit fix prompts in one command |
|
|
263
|
+
| `fix` | Generate AI fix instructions for specific docs (`--doc <name> --format prompt`) |
|
|
264
|
+
| `fix --write` | Apply deterministic fixes (no AI — version bumps, counts, anchors, sections) |
|
|
265
|
+
| `fix --history` | Audit log of every mechanical fix applied (from `.docguard/fixed.json`) |
|
|
266
|
+
| `generate` | Reverse-engineer docs from existing codebase (`--plan` for AI scan) |
|
|
267
|
+
| `explain <warning>` | Paste any warning — get the validator's docstring + fix path |
|
|
268
|
+
| `memory` | Per-domain accuracy headline (endpoints / entities / env / tech) |
|
|
269
|
+
| `memory --diff` | Drill into which specific claims don't match code |
|
|
270
|
+
| `score --diff` | Drill into which checks pulled each category down |
|
|
271
|
+
| `trace` / `trace --reverse <file>` | Requirements traceability — forward AND reverse |
|
|
272
|
+
| `upgrade [--apply] [--pr]` | Check + migrate `.docguard.json` schema; `--pr` opens a PR |
|
|
273
|
+
| `watch` | Live mode: re-run guard on file changes |
|
|
274
|
+
|
|
275
|
+
**`init --with <name>` scaffolders** — picked at init time:
|
|
276
|
+
|
|
277
|
+
| Scaffolder | What It Generates |
|
|
278
|
+
|:-----------|:------------------|
|
|
279
|
+
| `agents` | `AGENTS.md`, `CLAUDE.md`, `.cursor/rules/`, `.github/copilot-instructions.md` |
|
|
280
|
+
| `hooks` | Git pre-commit / pre-push hooks |
|
|
281
|
+
| `ci` | GitHub Actions / pipeline YAML |
|
|
282
|
+
| `badge` | Shields.io score badges for README |
|
|
283
|
+
| `llms` | `llms.txt` (AI-friendly summary) |
|
|
284
|
+
| `publish` | External doc-site config (Mintlify) — experimental |
|
|
285
|
+
|
|
286
|
+
Run them solo (`docguard init --with hooks`) or stacked (`docguard init --with agents,hooks,badge,ci`).
|
|
287
|
+
|
|
288
|
+
**Deprecation aliases** — `setup` · `agents` · `hooks` · `ci` · `badge` · `llms` · `publish` · `impact` keep working in v0.20.x with a yellow stderr warning. `audit → guard` is permanent (no warning). See [MIGRATION-v0.20.md](docs-implementation/MIGRATION-v0.20.md).
|
|
224
289
|
|
|
225
290
|
### CLI Flags
|
|
226
291
|
|
|
@@ -228,10 +293,22 @@ DocGuard ships **13 commands**:
|
|
|
228
293
|
|:-----|:------------|:---------|
|
|
229
294
|
| `--dir <path>` | Project directory (default: `.`) | All |
|
|
230
295
|
| `--verbose` | Show detailed output | All |
|
|
231
|
-
| `--
|
|
296
|
+
| `--quiet` / `-q` | Suppress banner — for hooks, CI loops, scripts | All |
|
|
297
|
+
| `--format json` | Machine-readable output (clean JSON, no ANSI bleed) | guard, score, diff, trace, diagnose, memory, impact, explain |
|
|
232
298
|
| `--force` | Overwrite existing files (creates `.bak` backups) | generate, agents, init |
|
|
233
|
-
| `--
|
|
234
|
-
| `--
|
|
299
|
+
| `--force-redo` | Bypass ping-pong suppression in `.docguard/fixed.json` | fix --write |
|
|
300
|
+
| `--profile <name>` | Starter / standard / enterprise | init |
|
|
301
|
+
| `--no-spec-kit` | Skip auto-init of `.specify/` / `.agent/` scaffolding | init |
|
|
302
|
+
| `--changed-only [--since <ref>]` | Pre-commit lite mode (5 fast validators on changed files only) | guard |
|
|
303
|
+
| `--timings` | Per-validator wall-time profile (slowest first) | guard |
|
|
304
|
+
| `--show-failing` | Show warnings/errors even when status is PASS | guard |
|
|
305
|
+
| `--pin` | Record running CLI version into `.docguard.json` (reproducibility) | guard |
|
|
306
|
+
| `--diff` | Per-category drill-down | score, memory |
|
|
307
|
+
| `--check-only` | Exit 1 if behind (for CI) | upgrade |
|
|
308
|
+
| `--apply` | Actually run the migration | upgrade |
|
|
309
|
+
| `--pr` | Open a PR with the migration | upgrade |
|
|
310
|
+
| `--reverse <file>` | Reverse traceability (code → docs) | trace |
|
|
311
|
+
| `--history` | Show fix audit log | fix |
|
|
235
312
|
|
|
236
313
|
### Example Output
|
|
237
314
|
|
|
@@ -266,30 +343,47 @@ $ npx docguard-cli generate
|
|
|
266
343
|
|
|
267
344
|
## 🔍 Validators
|
|
268
345
|
|
|
269
|
-
DocGuard runs **
|
|
346
|
+
DocGuard runs **23 automated validators** on every `guard` check. Every one is **language-aware** as of v0.16 — patterns for Python (`test_*.py`), Rust (`tests/*.rs`), Go (`*_test.go`), Java (`*Test.java`), Ruby (`*_spec.rb`), PHP, and JS/TS all match.
|
|
270
347
|
|
|
271
348
|
| # | Validator | What It Checks | Default |
|
|
272
349
|
|:--|:----------|:--------------|:--------|
|
|
273
350
|
| 1 | **Structure** | Required CDD files exist | ✅ On |
|
|
274
|
-
| 2 | **Doc Sections** | Canonical docs have required sections | ✅ On |
|
|
351
|
+
| 2 | **Doc Sections** | Canonical docs have required sections (or N/A markers) | ✅ On |
|
|
275
352
|
| 3 | **Docs-Sync** | Routes/services referenced in docs + OpenAPI cross-check | ✅ On |
|
|
276
|
-
| 4 | **Drift-Comments** | `// DRIFT:` comments logged in DRIFT-LOG.md | ✅ On |
|
|
353
|
+
| 4 | **Drift-Comments** | `// DRIFT:` comments logged in DRIFT-LOG.md (skips test files by default) | ✅ On |
|
|
277
354
|
| 5 | **Changelog** | CHANGELOG.md has [Unreleased] section | ✅ On |
|
|
278
355
|
| 6 | **Test-Spec** | Tests exist per TEST-SPEC.md rules | ✅ On |
|
|
279
|
-
| 7 | **Environment** | Env vars documented,
|
|
356
|
+
| 7 | **Environment** | Env vars documented, `.env.example` exists | ✅ On |
|
|
280
357
|
| 8 | **Security** | No hardcoded secrets in source code | ✅ On |
|
|
281
|
-
| 9 | **Architecture** | Imports follow layer boundaries | ✅ On |
|
|
282
|
-
| 10 | **Freshness** | Docs not stale relative to code changes | ✅ On |
|
|
358
|
+
| 9 | **Architecture** | Imports follow layer boundaries (honors `config.ignore`) | ✅ On |
|
|
359
|
+
| 10 | **Freshness** | Docs not stale relative to code changes (rename-aware via `git log --follow`) | ✅ On |
|
|
283
360
|
| 11 | **Traceability** | Requirement IDs (FR, SC, NFR, US, AC, T) trace to tests | ✅ On |
|
|
284
361
|
| 12 | **Docs-Diff** | Code artifacts match documented entities | ✅ On |
|
|
285
|
-
| 13 | **
|
|
286
|
-
| 14 | **
|
|
287
|
-
| 15 | **
|
|
288
|
-
| 16 | **Doc-Quality** | Writing quality (readability, passive voice, atomicity) | ✅ On |
|
|
289
|
-
| 17 | **TODO-Tracking** | Untracked TODOs/FIXMEs and skipped tests | ✅ On |
|
|
362
|
+
| 13 | **API-Surface** | API-REFERENCE.md endpoints match real routes (OpenAPI cross-check) | ✅ On |
|
|
363
|
+
| 14 | **Metadata-Sync** | Version refs consistent across docs | ✅ On |
|
|
364
|
+
| 15 | **Docs-Coverage** | Code features referenced in documentation | ✅ On |
|
|
365
|
+
| 16 | **Doc-Quality** | Writing quality (readability, passive voice, atomicity, IEEE 830) | ✅ On |
|
|
366
|
+
| 17 | **TODO-Tracking** | Untracked TODOs/FIXMEs and skipped tests (skips test files by default) | ✅ On |
|
|
290
367
|
| 18 | **Schema-Sync** | Database models documented in DATA-MODEL.md | ✅ On |
|
|
291
368
|
| 19 | **Spec-Kit** | Spec quality validation (FR-IDs, mandatory sections, phased tasks) | ✅ On |
|
|
292
|
-
| 20 | **
|
|
369
|
+
| 20 | **Cross-Reference** | Internal markdown links + anchors resolve (with "did you mean?" hints) | ✅ On |
|
|
370
|
+
| 21 | **Generated-Staleness** | `source=code` sections match scanner output; `status: draft` doc age | ✅ On |
|
|
371
|
+
| 22 | **Canonical-Sync** | DocGuard's own README count claims match code-truth (DocGuard repo only — N/A elsewhere) | ✅ On |
|
|
372
|
+
| 23 | **Metrics-Consistency** | Hardcoded numbers match actual counts | ✅ On |
|
|
373
|
+
|
|
374
|
+
**Per-validator controls** (in `.docguard.json`):
|
|
375
|
+
```json
|
|
376
|
+
{
|
|
377
|
+
"validators": {
|
|
378
|
+
"test-spec": false, // disable (kebab-case OR camelCase both accepted)
|
|
379
|
+
"freshness": true
|
|
380
|
+
},
|
|
381
|
+
"severity": {
|
|
382
|
+
"todoTracking": "high", // warnings fail CI
|
|
383
|
+
"freshness": "low" // warnings ignored for exit code
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
```
|
|
293
387
|
|
|
294
388
|
---
|
|
295
389
|
|
|
@@ -343,7 +437,7 @@ DocGuard provides AI agent slash commands for integrated workflows. Installed au
|
|
|
343
437
|
|
|
344
438
|
| Command | What It Does |
|
|
345
439
|
|:--------|:-------------|
|
|
346
|
-
| `/docguard.guard` | Run quality validation — check all
|
|
440
|
+
| `/docguard.guard` | Run quality validation — check all 23 validators |
|
|
347
441
|
| `/docguard.review` | Analyze doc quality and suggest improvements |
|
|
348
442
|
| `/docguard.fix` | Generate targeted fix prompts for specific issues |
|
|
349
443
|
| `/docguard.score` | Show CDD maturity score with category breakdown |
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Demo Command — v0.21.
|
|
3
|
+
*
|
|
4
|
+
* The 30-second "ah-ha" experience for devs shopping for doc tools.
|
|
5
|
+
*
|
|
6
|
+
* npx docguard-cli demo
|
|
7
|
+
*
|
|
8
|
+
* Spins up a baked-in fixture project (`templates/demo-fixture/`) — a 4-service
|
|
9
|
+
* payments API with INTENTIONAL doc drift — runs guard against it, and prints
|
|
10
|
+
* a curated narrative with real-world-impact annotations + a clear install CTA.
|
|
11
|
+
*
|
|
12
|
+
* Zero install required, zero damage to the user's environment: the fixture
|
|
13
|
+
* is copied to a temp directory, git-initialized there, and cleaned up on exit.
|
|
14
|
+
*
|
|
15
|
+
* Why this exists: per SURFACE-AUDIT v0.21 plan, the #2 friction point for
|
|
16
|
+
* adoption was "no demo path — devs have to install, init, write docs, run
|
|
17
|
+
* guard just to see what we do." This command compresses that to 30 seconds.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { mkdtempSync, rmSync, cpSync, existsSync, writeFileSync } from 'node:fs';
|
|
21
|
+
import { resolve, dirname, join } from 'node:path';
|
|
22
|
+
import { tmpdir } from 'node:os';
|
|
23
|
+
import { fileURLToPath } from 'node:url';
|
|
24
|
+
import { spawnSync } from 'node:child_process';
|
|
25
|
+
import { c } from '../shared.mjs';
|
|
26
|
+
import { runGuardInternal, classifyResult } from './guard.mjs';
|
|
27
|
+
import { runScoreInternal } from './score.mjs';
|
|
28
|
+
import { loadConfig } from '../docguard.mjs';
|
|
29
|
+
|
|
30
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
31
|
+
const __dirname = dirname(__filename);
|
|
32
|
+
const FIXTURE_SRC = resolve(__dirname, '../../templates/demo-fixture');
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Each warning pattern gets a 1-2 line "real-world impact" gloss. Keyed by
|
|
36
|
+
* a regex on the warning text; the first match wins. Falls back to the
|
|
37
|
+
* generic gloss for unrecognized warnings (so this dictionary stays
|
|
38
|
+
* resilient as validators evolve).
|
|
39
|
+
*
|
|
40
|
+
* The point: turn validator-speak ("Missing 'Setup Steps' section") into
|
|
41
|
+
* adopter-speak ("New devs spend an hour figuring out how to run this").
|
|
42
|
+
*/
|
|
43
|
+
const IMPACT_GLOSS = [
|
|
44
|
+
{
|
|
45
|
+
re: /env var.*not documented|missing.*Environment Variables/i,
|
|
46
|
+
impact: 'New devs hit cryptic "X is undefined" runtime errors at boot. CI bypasses the missing var entirely.',
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
re: /[Aa]rchitecture|service.*not (in|mentioned)|not in [Aa]rchitecture/,
|
|
50
|
+
impact: 'Your AI agent reads the architecture doc and gives wrong answers about how the system works.',
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
re: /missing.*Usage|missing.*License|README/,
|
|
54
|
+
impact: 'First-time visitors bounce. The README is the storefront — empty sections = lost trust.',
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
re: /endpoint|route|API-REFERENCE/i,
|
|
58
|
+
impact: 'Clients call a documented endpoint that no longer exists, or worse — miss a new endpoint entirely.',
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
re: /Test-Spec|test.*directory|test files/,
|
|
62
|
+
impact: 'Your TEST-SPEC doesn\'t reflect reality. New tests get written in the wrong place.',
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
re: /Unreleased.*section|Changelog/,
|
|
66
|
+
impact: 'Release automation can\'t auto-detect what\'s pending. Versioning becomes manual guesswork.',
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
re: /Spec.?Kit/i,
|
|
70
|
+
impact: 'Specs aren\'t structured for AI agents to use. You miss the multiplier on spec-driven development.',
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
re: /Config file.*not mentioned/,
|
|
74
|
+
impact: 'Devs see an unknown config file and don\'t know if it\'s safe to delete or required.',
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
re: /unlinked doc|not in your requiredFiles/,
|
|
78
|
+
impact: 'Doc lives in canonical/ but isn\'t in the manifest — guard skips it, drift accumulates silently.',
|
|
79
|
+
},
|
|
80
|
+
];
|
|
81
|
+
|
|
82
|
+
function getImpact(warning) {
|
|
83
|
+
for (const { re, impact } of IMPACT_GLOSS) {
|
|
84
|
+
if (re.test(warning)) return impact;
|
|
85
|
+
}
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Set up a temp copy of the fixture, git-init it, return the path.
|
|
91
|
+
*/
|
|
92
|
+
function setupFixture() {
|
|
93
|
+
const dir = mkdtempSync(join(tmpdir(), 'docguard-demo-'));
|
|
94
|
+
cpSync(FIXTURE_SRC, dir, { recursive: true });
|
|
95
|
+
// Initialize git so any history-aware validators (Freshness, Drift-Comments)
|
|
96
|
+
// can run without erroring. Identity is set locally so commit succeeds on
|
|
97
|
+
// CI runners that have no global git identity.
|
|
98
|
+
const opts = { cwd: dir, stdio: 'ignore' };
|
|
99
|
+
spawnSync('git', ['init', '-q', '-b', 'main'], opts);
|
|
100
|
+
spawnSync('git', ['config', 'user.email', 'demo@docguard.dev'], opts);
|
|
101
|
+
spawnSync('git', ['config', 'user.name', 'docguard-demo'], opts);
|
|
102
|
+
spawnSync('git', ['add', '-A'], opts);
|
|
103
|
+
spawnSync('git', ['commit', '-q', '-m', 'fixture'], opts);
|
|
104
|
+
return dir;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Pretty-print a curated guard run.
|
|
109
|
+
*/
|
|
110
|
+
function presentResults(guardData, scoreData) {
|
|
111
|
+
const allWarnings = [];
|
|
112
|
+
for (const v of guardData.validators) {
|
|
113
|
+
for (const w of (v.warnings || [])) {
|
|
114
|
+
allWarnings.push({ validator: v.name, message: w, severity: v.severity });
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
console.log(`\n${c.bold}🔍 What DocGuard found in your fixture:${c.reset}`);
|
|
119
|
+
console.log(`${c.dim} Validators run: ${guardData.validators.length} · Warnings: ${allWarnings.length} · Time: ~0.5s${c.reset}\n`);
|
|
120
|
+
|
|
121
|
+
// Pick up to 5 warnings showing VARIETY across validators (not 5 from the
|
|
122
|
+
// same one). Dedupe by validator name; within each validator group, pick
|
|
123
|
+
// the highest-severity warning. Then rank the picks by severity.
|
|
124
|
+
const sev = { high: 0, medium: 1, low: 2 };
|
|
125
|
+
const byValidator = new Map();
|
|
126
|
+
for (const w of allWarnings) {
|
|
127
|
+
const prev = byValidator.get(w.validator);
|
|
128
|
+
if (!prev || (sev[w.severity] ?? 1) < (sev[prev.severity] ?? 1)) {
|
|
129
|
+
byValidator.set(w.validator, w);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
const ranked = [...byValidator.values()].sort((a, b) => {
|
|
133
|
+
return (sev[a.severity] ?? 1) - (sev[b.severity] ?? 1);
|
|
134
|
+
});
|
|
135
|
+
const top = ranked.slice(0, 5);
|
|
136
|
+
|
|
137
|
+
for (let i = 0; i < top.length; i++) {
|
|
138
|
+
const w = top[i];
|
|
139
|
+
const sev = w.severity === 'high' ? `${c.red}[HIGH]${c.reset}`
|
|
140
|
+
: w.severity === 'low' ? `${c.dim}[LOW]${c.reset}`
|
|
141
|
+
: `${c.yellow}[MED]${c.reset}`;
|
|
142
|
+
console.log(` ${c.bold}${i + 1}.${c.reset} ${sev} ${c.cyan}${w.validator}${c.reset}`);
|
|
143
|
+
console.log(` ${c.dim}${w.message}${c.reset}`);
|
|
144
|
+
const impact = getImpact(w.message);
|
|
145
|
+
if (impact) {
|
|
146
|
+
console.log(` ${c.green}→${c.reset} ${impact}`);
|
|
147
|
+
}
|
|
148
|
+
console.log('');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (allWarnings.length > top.length) {
|
|
152
|
+
console.log(` ${c.dim}... and ${allWarnings.length - top.length} more. Run \`docguard guard\` in your repo to see everything.${c.reset}\n`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Score line
|
|
156
|
+
if (scoreData && typeof scoreData.score === 'number') {
|
|
157
|
+
const grade = scoreData.score >= 90 ? 'A' : scoreData.score >= 80 ? 'B' : scoreData.score >= 70 ? 'C' : scoreData.score >= 60 ? 'D' : 'F';
|
|
158
|
+
const color = scoreData.score >= 80 ? c.green : scoreData.score >= 60 ? c.yellow : c.red;
|
|
159
|
+
console.log(`${c.bold}📊 CDD Maturity Score:${c.reset} ${color}${scoreData.score}/100 (${grade})${c.reset}`);
|
|
160
|
+
console.log(`${c.dim} ↑ This is the fixture's score. Yours will hopefully be higher.${c.reset}\n`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function printCTA() {
|
|
165
|
+
console.log(`${c.bold}🛠️ Fixing drift like this:${c.reset}`);
|
|
166
|
+
console.log(` ${c.cyan}docguard fix --write${c.reset} ${c.dim}— patches the mechanical stuff (version refs, counts, anchors)${c.reset}`);
|
|
167
|
+
console.log(` ${c.cyan}docguard sync --write${c.reset} ${c.dim}— refreshes code-truth sections to match the codebase${c.reset}`);
|
|
168
|
+
console.log(` ${c.cyan}docguard diagnose${c.reset} ${c.dim}— generates an AI prompt for the prose drift (Claude/GPT/Cursor)${c.reset}\n`);
|
|
169
|
+
|
|
170
|
+
console.log(`${c.bold}🚀 Try it on YOUR project:${c.reset}`);
|
|
171
|
+
console.log(` ${c.green}npm install -g docguard-cli${c.reset}`);
|
|
172
|
+
console.log(` ${c.green}cd your-project${c.reset}`);
|
|
173
|
+
console.log(` ${c.green}docguard init${c.reset} ${c.dim}— scans existing code and proposes canonical docs${c.reset}`);
|
|
174
|
+
console.log(` ${c.green}docguard guard${c.reset} ${c.dim}— see what we catch${c.reset}\n`);
|
|
175
|
+
|
|
176
|
+
console.log(`${c.dim}Or stay zero-install:${c.reset}`);
|
|
177
|
+
console.log(` ${c.green}npx docguard-cli init${c.reset}`);
|
|
178
|
+
console.log(` ${c.green}npx docguard-cli guard${c.reset}\n`);
|
|
179
|
+
|
|
180
|
+
console.log(`${c.bold}📚 Learn more:${c.reset} ${c.cyan}https://github.com/raccioly/docguard${c.reset}`);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Public entry point — `docguard demo`.
|
|
185
|
+
*
|
|
186
|
+
* @param {string} _projectDir — ignored; demo uses its own temp fixture
|
|
187
|
+
* @param {object} _config — ignored
|
|
188
|
+
* @param {object} flags — supports --quiet (skip banner) and --keep (don't cleanup fixture)
|
|
189
|
+
*/
|
|
190
|
+
export function runDemo(_projectDir, _config, flags = {}) {
|
|
191
|
+
if (!flags.quiet) {
|
|
192
|
+
console.log(`\n${c.bold}🎬 DocGuard Demo${c.reset} ${c.dim}— see what we catch in 30 seconds${c.reset}`);
|
|
193
|
+
console.log(`${c.dim} No install. No setup. We're running against a sample 4-service payments API${c.reset}`);
|
|
194
|
+
console.log(`${c.dim} with intentional drift between code and docs.${c.reset}\n`);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (!existsSync(FIXTURE_SRC)) {
|
|
198
|
+
console.error(`${c.red}Demo fixture not found at ${FIXTURE_SRC}.${c.reset}`);
|
|
199
|
+
console.error(`${c.dim}If this is a packaging bug, please file an issue.${c.reset}`);
|
|
200
|
+
process.exit(1);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
let fixture;
|
|
204
|
+
try {
|
|
205
|
+
fixture = setupFixture();
|
|
206
|
+
if (!flags.quiet) console.log(`${c.dim} Fixture ready at ${fixture}${c.reset}\n`);
|
|
207
|
+
} catch (err) {
|
|
208
|
+
if (fixture) rmSync(fixture, { recursive: true, force: true });
|
|
209
|
+
console.error(`${c.red}Failed to set up demo fixture: ${err.message}${c.reset}`);
|
|
210
|
+
process.exit(1);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Run guard + score against the fixture
|
|
214
|
+
let guardData, scoreData;
|
|
215
|
+
try {
|
|
216
|
+
// Load full config (defaults + fixture's .docguard.json) — same path the
|
|
217
|
+
// real `docguard guard` uses. The fixture ships its own .docguard.json
|
|
218
|
+
// so this hydrates the right project name + profile.
|
|
219
|
+
const config = loadConfig(fixture);
|
|
220
|
+
guardData = runGuardInternal(fixture, config);
|
|
221
|
+
scoreData = runScoreInternal(fixture, config);
|
|
222
|
+
} catch (err) {
|
|
223
|
+
rmSync(fixture, { recursive: true, force: true });
|
|
224
|
+
console.error(`${c.red}Demo guard run failed: ${err.message}${c.reset}`);
|
|
225
|
+
process.exit(1);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
presentResults(guardData, scoreData);
|
|
229
|
+
printCTA();
|
|
230
|
+
|
|
231
|
+
// Cleanup unless --keep
|
|
232
|
+
if (!flags.keep) {
|
|
233
|
+
rmSync(fixture, { recursive: true, force: true });
|
|
234
|
+
if (!flags.quiet) console.log(`${c.dim} Fixture cleaned up.${c.reset}`);
|
|
235
|
+
} else {
|
|
236
|
+
console.log(`${c.dim} Fixture kept at ${fixture} (--keep)${c.reset}`);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Always exit 0 — the demo is informational, never a failure
|
|
240
|
+
process.exit(0);
|
|
241
|
+
}
|
package/cli/commands/guard.mjs
CHANGED
|
@@ -139,7 +139,8 @@ import { validateCrossReferences } from '../validators/cross-reference.mjs';
|
|
|
139
139
|
import { validateGeneratedStaleness } from '../validators/generated-staleness.mjs';
|
|
140
140
|
import { validateTodoTracking } from '../validators/todo-tracking.mjs';
|
|
141
141
|
import { validateSchemaSync } from '../validators/schema-sync.mjs';
|
|
142
|
-
import { validateSpecKitIntegration } from '../
|
|
142
|
+
import { validateSpecKitIntegration } from '../validators/spec-kit.mjs';
|
|
143
|
+
import { validateCanonicalSync } from '../validators/canonical-sync.mjs';
|
|
143
144
|
|
|
144
145
|
/**
|
|
145
146
|
* Internal guard — returns structured data, no console output, no process.exit.
|
|
@@ -237,6 +238,22 @@ export function runGuardInternal(projectDir, config) {
|
|
|
237
238
|
}
|
|
238
239
|
}
|
|
239
240
|
|
|
241
|
+
// ── Canonical-Sync runs AFTER main loop, BEFORE metrics-consistency.
|
|
242
|
+
// Needs the live validator results to count "real" validators that ran.
|
|
243
|
+
// (Pre-canonical-sync ordering — comes before metrics-consistency so the
|
|
244
|
+
// metrics validator sees a stable surface count.)
|
|
245
|
+
if (validators.canonicalSync !== false) {
|
|
246
|
+
const start = performance.now();
|
|
247
|
+
try {
|
|
248
|
+
const result = validateCanonicalSync(projectDir, config, results);
|
|
249
|
+
const durationMs = Math.round((performance.now() - start) * 100) / 100;
|
|
250
|
+
results.push({ ...result, name: 'Canonical-Sync', key: 'canonicalSync', durationMs, ...classifyResult(result) });
|
|
251
|
+
} catch (err) {
|
|
252
|
+
const durationMs = Math.round((performance.now() - start) * 100) / 100;
|
|
253
|
+
results.push({ name: 'Canonical-Sync', key: 'canonicalSync', status: 'fail', quality: 'LOW', errors: [err.message], warnings: [], passed: 0, total: 1, durationMs });
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
240
257
|
// ── Metrics-Consistency runs AFTER all other validators (needs their results) ──
|
|
241
258
|
if (validators.metricsConsistency !== false) {
|
|
242
259
|
const start = performance.now();
|
|
@@ -317,7 +334,8 @@ function liteValidatorsConfig() {
|
|
|
317
334
|
'structure', 'docsSync', 'drift', 'changelog', 'testSpec', 'environment',
|
|
318
335
|
'security', 'architecture', 'freshness', 'traceability', 'docsDiff',
|
|
319
336
|
'apiSurface', 'metadataSync', 'docsCoverage', 'docQuality', 'todoTracking',
|
|
320
|
-
'schemaSync', 'specKit', 'crossReference', 'generatedStaleness',
|
|
337
|
+
'schemaSync', 'specKit', 'crossReference', 'generatedStaleness',
|
|
338
|
+
'canonicalSync', 'metricsConsistency',
|
|
321
339
|
];
|
|
322
340
|
const out = {};
|
|
323
341
|
for (const k of all) out[k] = CHANGED_ONLY_VALIDATORS.includes(k);
|
package/cli/commands/init.mjs
CHANGED
|
@@ -17,6 +17,45 @@ import { execSync } from 'node:child_process';
|
|
|
17
17
|
import { c, PROFILES } from '../shared.mjs';
|
|
18
18
|
import { ensureSkills, detectAgentMode, detectAIAgent, isSpecKitAvailable, isSpecKitInitialized, getDetectedAgent } from '../ensure-skills.mjs';
|
|
19
19
|
|
|
20
|
+
// v0.20: scaffolder names that can be passed via `init --with <name>` and
|
|
21
|
+
// dispatched to the corresponding standalone runner. Each name maps to its
|
|
22
|
+
// canonical command module. Keep in sync with cli/docguard.mjs router.
|
|
23
|
+
const SCAFFOLDER_DISPATCH = {
|
|
24
|
+
agents: async (dir, cfg, flags) => (await import('./agents.mjs')).runAgents(dir, cfg, flags),
|
|
25
|
+
hooks: async (dir, cfg, flags) => (await import('./hooks.mjs')).runHooks(dir, cfg, flags),
|
|
26
|
+
ci: async (dir, cfg, flags) => (await import('./ci.mjs')).runCI(dir, cfg, flags),
|
|
27
|
+
badge: async (dir, cfg, flags) => (await import('./badge.mjs')).runBadge(dir, cfg, flags),
|
|
28
|
+
llms: async (dir, cfg, flags) => (await import('./llms.mjs')).runLlms(dir, cfg, flags),
|
|
29
|
+
publish: async (dir, cfg, flags) => (await import('./publish.mjs')).runPublish(dir, cfg, flags),
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Run one or more scaffolders after init has completed.
|
|
34
|
+
* Called when `docguard init --with agents,hooks,ci` is invoked.
|
|
35
|
+
* Each scaffolder runs in sequence; if one throws, the rest are skipped
|
|
36
|
+
* (matches the standalone command's failure semantics).
|
|
37
|
+
*/
|
|
38
|
+
async function runScaffolders(projectDir, config, flags, names) {
|
|
39
|
+
const unknown = names.filter(n => !SCAFFOLDER_DISPATCH[n]);
|
|
40
|
+
if (unknown.length > 0) {
|
|
41
|
+
console.error(`${c.red}Unknown --with target(s): ${unknown.join(', ')}${c.reset}`);
|
|
42
|
+
console.log(`${c.dim}Valid: ${Object.keys(SCAFFOLDER_DISPATCH).join(', ')}${c.reset}`);
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
console.log(`\n${c.bold}🧰 Scaffolders:${c.reset} ${c.cyan}${names.join(', ')}${c.reset}\n`);
|
|
47
|
+
|
|
48
|
+
for (const name of names) {
|
|
49
|
+
console.log(`${c.dim}── ${name} ───────────────────────────────────────${c.reset}`);
|
|
50
|
+
try {
|
|
51
|
+
await SCAFFOLDER_DISPATCH[name](projectDir, config, flags);
|
|
52
|
+
} catch (err) {
|
|
53
|
+
console.error(`${c.red}✗ ${name} failed: ${err.message}${c.reset}`);
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
20
59
|
function detectProjectType(dir) {
|
|
21
60
|
const pkgPath = resolve(dir, 'package.json');
|
|
22
61
|
if (existsSync(pkgPath)) {
|
|
@@ -53,7 +92,83 @@ function askQuestion(prompt) {
|
|
|
53
92
|
|
|
54
93
|
// ── Init Command ─────────────────────────────────────────────────────────
|
|
55
94
|
|
|
95
|
+
/**
|
|
96
|
+
* v0.21 — Smart first-run detection.
|
|
97
|
+
*
|
|
98
|
+
* Heuristic: if the user is running `docguard init` against a project that
|
|
99
|
+
* already has substantial source code (cli/, src/, lib/, app/, or 10+ source
|
|
100
|
+
* files at depth 1-2) AND has no docs-canonical/ yet, switch from the
|
|
101
|
+
* skeleton-first path to the "scan and propose" path — i.e. dispatch to
|
|
102
|
+
* `docguard generate --plan` which reverse-engineers canonical docs from
|
|
103
|
+
* existing code.
|
|
104
|
+
*
|
|
105
|
+
* Rationale: blank skeletons feel useless for existing projects (the dev
|
|
106
|
+
* has to write everything from scratch). The scan path delivers immediate
|
|
107
|
+
* value: "here's what your project actually does, mapped to canonical doc
|
|
108
|
+
* shape." That's a 30-second wow for the 80% of adopters who arrive with
|
|
109
|
+
* an existing codebase.
|
|
110
|
+
*
|
|
111
|
+
* Opt out: `docguard init --skeleton` forces the blank-template path
|
|
112
|
+
* (preserves the v0.20 behavior for greenfield projects and CI flows).
|
|
113
|
+
*
|
|
114
|
+
* @returns {boolean} true if smart-detection fired and dispatched
|
|
115
|
+
*/
|
|
116
|
+
function shouldRunGenerate(projectDir, flags) {
|
|
117
|
+
if (flags.skeleton) return false; // explicit opt-out
|
|
118
|
+
if (flags.skipPrompts) return false; // non-interactive (CI) keeps deterministic skeleton path
|
|
119
|
+
if (flags.wizard) return false; // wizard has its own scan step
|
|
120
|
+
if (flags.profile) return false; // explicit profile = user knows what they want
|
|
121
|
+
|
|
122
|
+
// If canonical docs already exist, this is a re-init, not a first-run.
|
|
123
|
+
const canonicalDir = resolve(projectDir, 'docs-canonical');
|
|
124
|
+
if (existsSync(canonicalDir)) {
|
|
125
|
+
try {
|
|
126
|
+
const entries = readdirSync(canonicalDir).filter(f => f.endsWith('.md'));
|
|
127
|
+
if (entries.length > 0) return false;
|
|
128
|
+
} catch { /* fall through */ }
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Existing-code signals: any of cli/, src/, lib/, app/ as a directory.
|
|
132
|
+
const codeDirs = ['cli', 'src', 'lib', 'app'];
|
|
133
|
+
for (const d of codeDirs) {
|
|
134
|
+
if (existsSync(resolve(projectDir, d))) return true;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Fallback: count source files at top level (Python / Rust / Go projects
|
|
138
|
+
// often don't use src/ — files live at the root).
|
|
139
|
+
try {
|
|
140
|
+
const exts = ['.py', '.rs', '.go', '.java', '.rb', '.ts', '.tsx', '.mjs', '.js'];
|
|
141
|
+
const topLevel = readdirSync(projectDir).filter(f => {
|
|
142
|
+
return exts.some(e => f.endsWith(e));
|
|
143
|
+
});
|
|
144
|
+
if (topLevel.length >= 10) return true;
|
|
145
|
+
} catch { /* fall through */ }
|
|
146
|
+
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
|
|
56
150
|
export async function runInit(projectDir, config, flags) {
|
|
151
|
+
// v0.20: `--wizard` dispatches to the full interactive onboarding (formerly
|
|
152
|
+
// `docguard setup`). Done before profile validation so the wizard can ask
|
|
153
|
+
// for the profile itself if needed.
|
|
154
|
+
if (flags.wizard) {
|
|
155
|
+
const { runSetup } = await import('./setup.mjs');
|
|
156
|
+
return runSetup(projectDir, config, flags);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// v0.21: smart first-run — for existing projects without canonical docs,
|
|
160
|
+
// dispatch to `generate --plan` (the "scan and propose" path). Opt out
|
|
161
|
+
// with --skeleton or by setting --profile/--skip-prompts/--wizard explicitly.
|
|
162
|
+
if (shouldRunGenerate(projectDir, flags)) {
|
|
163
|
+
console.log(`${c.bold}🔍 DocGuard Init — Smart Mode${c.reset}`);
|
|
164
|
+
console.log(`${c.dim} Detected existing project with code but no canonical docs.${c.reset}`);
|
|
165
|
+
console.log(`${c.dim} Switching to "scan and propose" mode — DocGuard will reverse-engineer${c.reset}`);
|
|
166
|
+
console.log(`${c.dim} canonical docs from your code instead of dumping a blank skeleton.${c.reset}`);
|
|
167
|
+
console.log(`${c.dim} (Opt out: ${c.cyan}docguard init --skeleton${c.dim} for the blank-template path.)${c.reset}\n`);
|
|
168
|
+
const { runGenerate } = await import('./generate.mjs');
|
|
169
|
+
return runGenerate(projectDir, config, { ...flags, plan: true });
|
|
170
|
+
}
|
|
171
|
+
|
|
57
172
|
const profileName = flags.profile || 'standard';
|
|
58
173
|
const profile = PROFILES[profileName];
|
|
59
174
|
|
|
@@ -369,4 +484,11 @@ poetry.lock
|
|
|
369
484
|
|
|
370
485
|
// Auto-install DocGuard skills and commands (spec-kit skills handled by specify init)
|
|
371
486
|
ensureSkills(projectDir, flags);
|
|
487
|
+
|
|
488
|
+
// v0.20: `docguard init --with agents,hooks,ci,badge,llms,publish` runs
|
|
489
|
+
// the named scaffolders after init has finished. Each one runs in sequence
|
|
490
|
+
// and uses the same flags object (so --force / --skip-prompts propagate).
|
|
491
|
+
if (Array.isArray(flags.with) && flags.with.length > 0) {
|
|
492
|
+
await runScaffolders(projectDir, config, flags, flags.with);
|
|
493
|
+
}
|
|
372
494
|
}
|