Skip to content

Commit 8c8cf48

Browse files
committed
docs: add docs-watch protocol guardrails
1 parent c4fab8d commit 8c8cf48

File tree

5 files changed

+124
-9
lines changed

5 files changed

+124
-9
lines changed

docs/MAINTAINERS.md

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -101,11 +101,15 @@ The docs-watch process is local-only and intended for maintainers.
101101
- Monitored metadata lives in `docs/github-documentation/watch-list.json`.
102102
- Local private state defaults to `.tmp/docs-watch/state` and stores snapshots
103103
used for diffing.
104+
- For recurring runs, prefer a stable private path outside the worktree via
105+
`DOC_WATCH_STATE_DIR` so snapshots survive `.tmp/` cleanup and worktree churn.
104106

105107
### Local commands
106108

107109
- Seed or refresh baseline snapshots:
108110
- `npm run sync:github-docs-state`
111+
- Bootstrap-aware review entrypoint (recommended):
112+
- `npm run docs-watch:review`
109113
- Run check + diff + report (standard weekly run):
110114
- `npm run docs-watch:local`
111115
- Individual steps:
@@ -122,13 +126,25 @@ The docs-watch process is local-only and intended for maintainers.
122126

123127
### Maintainer review loop
124128

125-
1. Run `npm run docs-watch:local` (or let weekly Codex automation run it).
129+
1. Prefer `npm run docs-watch:review` (or let weekly Codex automation use the
130+
same protocol).
126131
2. Review `.tmp/docs-watch/docs-watch-report.md` for correctness.
127-
3. If docs changed, validate impact classification and decide if project updates
128-
are required.
129-
4. If required, open/update a draft fix PR with project and metadata changes
132+
3. If the report status is `bootstrap-required`, do not open or update a PR from
133+
that run. Seed private state with `npm run sync:github-docs-state`, then rerun
134+
the exact review.
135+
4. Only if the synced exact review still reports `changed: true`, validate impact
136+
classification and decide if project updates are required.
137+
5. If required, open/update a draft fix PR with project and metadata changes
130138
only.
131139

140+
### Recommended protocol
141+
142+
1. Set a stable private state directory, for example:
143+
- `export DOC_WATCH_STATE_DIR="$HOME/.local/state/github-api-usage-monitor/docs-watch"`
144+
2. Run `npm run docs-watch:review`.
145+
3. Treat the first seeded run as bootstrap, not as a source of patch review.
146+
4. Only create PRs from an exact synced run with `changed: true`.
147+
132148
## Notes
133149

134150
- `CHANGELOG.md` is generated by Release Please; avoid manual edits.

docs/github-documentation/README.md

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,27 @@ Readable diffs require a private local state directory that stores snapshot bodi
1616
Example local state location:
1717

1818
- `.tmp/docs-watch/state` (default for local scripts)
19+
- For recurring local or automated runs, prefer a stable external path via
20+
`DOC_WATCH_STATE_DIR`, for example
21+
`$HOME/.local/state/github-api-usage-monitor/docs-watch`
1922

2023
## Local workflow
2124

22-
1. (Optional, first run) `npm run sync:github-docs-state`
23-
2. `npm run check:github-docs`
24-
3. `npm run diff:github-docs`
25-
4. `npm run report:github-docs`
26-
5. Ask Codex to review `.tmp/docs-watch/docs-watch-report.md` and classify project impact.
25+
1. Recommended entrypoint: `npm run docs-watch:review`
26+
2. If you are running manually and want explicit control:
27+
- first run or missing snapshots: `npm run sync:github-docs-state`
28+
- exact review run: `npm run docs-watch:local`
29+
3. Ask Codex to review `.tmp/docs-watch/docs-watch-report.md` and classify project impact.
30+
31+
## Protocol notes
32+
33+
- If a run reports `status: bootstrap-required`, do not open or update a PR from
34+
that run alone.
35+
- `bootstrap-required` means the manifest differs from current upstream docs, but
36+
there is no private baseline snapshot for at least one changed page, so the run
37+
cannot produce a reliable readable diff.
38+
- After seeding private state with `npm run sync:github-docs-state`, rerun the
39+
exact review. Only act on a PR if that synced exact review still reports
40+
`changed: true`.
2741

2842
Outputs are written under `.tmp/docs-watch/` by default.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"diff:github-docs": "node scripts/render-github-doc-diff.mjs --check .tmp/docs-watch/docs-check.json --output .tmp/docs-watch/docs-diff.md --patch-output .tmp/docs-watch/docs-diff.patch --write-summary",
2828
"report:github-docs": "node scripts/write-docs-watch-report.mjs --check .tmp/docs-watch/docs-check.json --diff .tmp/docs-watch/docs-diff.patch --diff-markdown .tmp/docs-watch/docs-diff.md --output .tmp/docs-watch/docs-watch-report.md --write-summary",
2929
"docs-watch:local": "npm run check:github-docs && npm run diff:github-docs && npm run report:github-docs",
30+
"docs-watch:review": "node scripts/run-docs-watch-review.mjs",
3031
"sync:github-docs-state": "node scripts/check-github-docs.mjs --state-dir \"${DOC_WATCH_STATE_DIR:-.tmp/docs-watch/state}\" --write-state --update-manifest --output .tmp/docs-watch/docs-check-sync.json --write-summary",
3132
"clean": "rm -rf dist",
3233
"prepare": "husky",

scripts/run-docs-watch-review.mjs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
#!/usr/bin/env node
2+
3+
import fs from 'fs';
4+
import path from 'path';
5+
import { spawnSync } from 'child_process';
6+
7+
function resolveStateDir() {
8+
return process.env.DOC_WATCH_STATE_DIR || '.tmp/docs-watch/state';
9+
}
10+
11+
function hasSnapshotFiles(stateDir) {
12+
const snapshotsDir = path.join(stateDir, 'snapshots');
13+
if (!fs.existsSync(snapshotsDir)) {
14+
return false;
15+
}
16+
17+
const entries = fs.readdirSync(snapshotsDir, { withFileTypes: true });
18+
return entries.some((entry) => entry.isFile());
19+
}
20+
21+
function npmCommand() {
22+
return process.platform === 'win32' ? 'npm.cmd' : 'npm';
23+
}
24+
25+
function runNpmScript(scriptName) {
26+
const result = spawnSync(npmCommand(), ['run', scriptName], {
27+
stdio: 'inherit',
28+
env: process.env,
29+
});
30+
31+
if (result.error) {
32+
throw result.error;
33+
}
34+
35+
if (result.status !== 0) {
36+
process.exit(result.status ?? 1);
37+
}
38+
}
39+
40+
async function main() {
41+
const stateDir = resolveStateDir();
42+
43+
console.log(`# Docs watch review entrypoint`);
44+
console.log(`- state_dir: ${stateDir}`);
45+
46+
if (!hasSnapshotFiles(stateDir)) {
47+
console.log(
48+
'- no private snapshots found; bootstrapping current upstream docs into private state first',
49+
);
50+
runNpmScript('sync:github-docs-state');
51+
} else {
52+
console.log('- existing private snapshots found; running exact local review');
53+
}
54+
55+
runNpmScript('docs-watch:local');
56+
}
57+
58+
main().catch((error) => {
59+
console.error(error);
60+
process.exit(1);
61+
});

scripts/write-docs-watch-report.mjs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,23 @@ function parsePatchStats(patchText) {
6969
}
7070

7171
function defaultAssessment(payload) {
72+
const changedWithoutBaseline = payload.results.some(
73+
(item) => item.changed && item.baseline_source === 'none',
74+
);
75+
76+
if (changedWithoutBaseline) {
77+
return {
78+
status: 'bootstrap-required',
79+
impactLevel: 'unknown',
80+
requiresProjectChanges: 'unknown',
81+
confidence: 'n/a',
82+
rationale:
83+
'Changes were detected relative to the committed manifest, but at least one changed file has no private baseline snapshot. This run cannot produce a reliable readable diff or justify a project-impact review on its own.',
84+
recommendedAction:
85+
'Run `npm run sync:github-docs-state`, then rerun `npm run docs-watch:local` (or `npm run docs-watch:review`). Do not open or update a PR from this run alone.',
86+
};
87+
}
88+
7289
if (!payload.changed) {
7390
return {
7491
status: 'completed',
@@ -123,6 +140,7 @@ async function main() {
123140
lines.push(`- changed: ${checkPayload.changed}`);
124141
lines.push(`- changed_count: ${checkPayload.changed_count}`);
125142
lines.push(`- state_dir: ${checkPayload.state_dir ?? 'none'}`);
143+
lines.push(`- bootstrap_required: ${assessment.status === 'bootstrap-required'}`);
126144
lines.push('');
127145

128146
lines.push('## Monitored Files');
@@ -185,6 +203,10 @@ async function main() {
185203
fs.appendFileSync(process.env.GITHUB_OUTPUT, `changed=${checkPayload.changed}\n`);
186204
fs.appendFileSync(process.env.GITHUB_OUTPUT, `changed_count=${checkPayload.changed_count}\n`);
187205
fs.appendFileSync(process.env.GITHUB_OUTPUT, `assessment_status=${assessment.status}\n`);
206+
fs.appendFileSync(
207+
process.env.GITHUB_OUTPUT,
208+
`bootstrap_required=${assessment.status === 'bootstrap-required'}\n`,
209+
);
188210
}
189211

190212
if (writeSummary) {
@@ -195,6 +217,7 @@ async function main() {
195217
`- changed: ${checkPayload.changed}`,
196218
`- changed_count: ${checkPayload.changed_count}`,
197219
`- assessment_status: ${assessment.status}`,
220+
`- bootstrap_required: ${assessment.status === 'bootstrap-required'}`,
198221
];
199222

200223
if (process.env.GITHUB_STEP_SUMMARY) {

0 commit comments

Comments
 (0)