返回 slidev
025-skills-docs-drift-guard.md
根目录 / plans / 025-skills-docs-drift-guard.md
1 # Plan 025: Guard against `skills/` drifting from `docs/`
2
3 > **Executor instructions**: Follow this plan step by step. Run every
4 > verification command and confirm the expected result. If anything in "STOP
5 > conditions" occurs, stop and report. When done, update the status row in
6 > `plans/README.md`.
7 >
8 > **Drift check (run first)**: `git diff --stat c63cb120..HEAD -- skills/ scripts/ .github/workflows/`
9 > On a mismatch with the excerpts below, treat it as a STOP condition.
10
11 ## Status
12
13 - **Priority**: P3
14 - **Effort**: S-M
15 - **Risk**: LOW
16 - **Depends on**: none
17 - **Category**: docs / dx
18 - **Planned at**: commit `c63cb120`, 2026-07-10
19
20 ## Why this matters
21
22 `skills/slidev/**` is an agent-facing reference **generated from `docs/`** and is
23 **shipped to every user** inside `@slidev/cli` (`scripts/publish.mjs:4` copies
24 `skills` into `packages/slidev/skills`). Its provenance is recorded in
25 `skills/GENERATION.md` as SHA `9d300814` / v52.11.3 — but the repo is v52.16.0
26 with dozens of `docs/` commits since, and the only sync mechanism is a **manual**
27 human checklist. So stale guidance ships to users' agents invisibly. This plan
28 adds an automated **drift guard**: CI fails when `docs/` has advanced past the
29 SHA the skills were generated from, forcing a re-sync + provenance bump. (It does
30 NOT auto-regenerate — that generator is a separate, larger effort.)
31
32 ## Current state
33
34 - `skills/GENERATION.md` records provenance in prose:
35 - `**Short SHA**: \`9d300814\`` (`:10`)
36 - a version-history table (`:194-196`)
37 - `Current SHA: 9d300814` (final line)
38 There is no machine-readable marker and no CI check.
39 - `scripts/publish.mjs:4`: `await fs.copy('skills', 'packages/slidev/skills', { overwrite: true })`.
40 - `scripts/` already contains Node/zx scripts (e.g. `publish.mjs`,
41 `update-versions.mjs`), so adding a script is idiomatic.
42 - CI workflows live in `.github/workflows/` (see `test.yml`).
43
44 ## Commands you will need
45
46 | Purpose | Command | Expected |
47 |---------|---------|----------|
48 | Run the new check | `node scripts/check-skills-drift.mjs` | exit 0 when in sync; non-zero + message when drifted |
49 | YAML sanity | `pnpm dlx js-yaml .github/workflows/<file>.yml` | parses |
50
51 (No build/deps needed; the script uses `git` + Node fs.)
52
53 ## Scope
54
55 **In scope**:
56 - `skills/GENERATION.md` (add a machine-readable provenance marker)
57 - `scripts/check-skills-drift.mjs` (create)
58 - `.github/workflows/` (add a job/step that runs the check)
59
60 **Out of scope**:
61 - Writing the actual skills **generator** (that's a separate direction item, D2).
62 - Editing `skills/slidev/**` content (do not hand-edit generated files).
63 - Changing `publish.mjs`.
64
65 ## Git workflow
66
67 - Branch: `ci/skills-drift-guard`.
68 - Conventional commit: `ci: fail when skills drift from docs`.
69 - Do NOT push/PR unless instructed.
70
71 ## Steps
72
73 ### Step 1: Add a machine-readable provenance marker
74
75 Add a single unambiguous line to `skills/GENERATION.md` (near the top) that the
76 script parses, seeded with the currently-recorded SHA:
77 ```md
78 <!-- skills-generated-from: 9d300814 -->
79 ```
80 Keep the existing human-readable fields too; this marker is the source of truth
81 for the check.
82
83 ### Step 2: Write `scripts/check-skills-drift.mjs`
84
85 The script:
86 1. Reads the marker SHA from `skills/GENERATION.md` (regex on
87 `skills-generated-from:\s*([0-9a-f]+)`); error if absent.
88 2. Runs `git rev-list --count <sha>..HEAD -- docs/` (via `node:child_process`
89 `execFileSync('git', [...])` — argv array, no shell).
90 3. If the count is `0`, print "skills in sync with docs" and exit `0`.
91 4. If `> 0`, print the list (`git diff --name-only <sha>..HEAD -- docs/`) and a
92 remediation message ("Re-sync `skills/` per `skills/GENERATION.md` and update
93 the `skills-generated-from` marker to the current HEAD"), then exit `1`.
94 Handle the case where `<sha>` is not an ancestor (e.g. shallow CI clone):
95 detect the failure of `git rev-list` and exit `0` with a warning rather than
96 hard-failing CI on a fetch-depth issue.
97
98 Match the style of existing `scripts/*.mjs` (ESM, top-level await ok).
99
100 ### Step 3: Wire it into CI
101
102 Add a lightweight job (or a step in an existing job) that runs
103 `node scripts/check-skills-drift.mjs`. Because it needs history for
104 `git rev-list`, set the checkout to full depth:
105 ```yaml
106 skills-drift:
107 runs-on: ubuntu-latest
108 steps:
109 - uses: actions/checkout@v6
110 with:
111 fetch-depth: 0
112 - uses: actions/setup-node@v6
113 with:
114 node-version: lts/*
115 - name: Check skills/docs drift
116 run: node scripts/check-skills-drift.mjs
117 ```
118
119 **Verify**:
120 - Locally, `node scripts/check-skills-drift.mjs` runs and exits non-zero **today**
121 (docs have advanced past `9d300814`), printing the changed docs files — that is
122 the guard working. (Do NOT "fix" it by editing skills in this plan; the point is
123 to surface the drift.)
124 - The workflow YAML parses.
125
126 ## Test plan
127
128 - Manual: run the script on the current tree → it reports drift (non-zero) and
129 lists changed `docs/` files. Temporarily set the marker to `HEAD`'s short SHA
130 and re-run → exits `0`. Restore the real recorded SHA afterward.
131 - No unit test framework needed for a small CI script; the two manual runs above
132 are the verification.
133
134 ## Done criteria
135
136 - [ ] `skills/GENERATION.md` has a `skills-generated-from:` marker
137 - [ ] `scripts/check-skills-drift.mjs` exists, uses argv-based `git` (no shell), and handles the non-ancestor/shallow case gracefully
138 - [ ] A CI job runs the check with `fetch-depth: 0`
139 - [ ] Running the script locally correctly reports the current drift (non-zero)
140 - [ ] Workflow YAML parses
141 - [ ] Only in-scope files modified (`git status`)
142 - [ ] `plans/README.md` status row updated
143
144 ## STOP conditions
145
146 Stop and report if:
147
148 - The team wants the guard to also **auto-regenerate** skills (that's the
149 generator, direction item D2 — a separate plan; this one only detects drift).
150 - CI's default shallow clone can't be given full history — then base the check on
151 a different signal (e.g. compare a committed hash of `docs/` tree) and report
152 the approach change.
153
154 ## Maintenance notes
155
156 - When skills are re-synced, bump the `skills-generated-from:` marker (and the
157 human fields) to the current HEAD; the guard then goes green until docs move
158 again.
159 - Reviewer: confirm the script fails **loudly with guidance**, not silently, and
160 that it never edits generated files itself.
161
161 lines MARKDOWN