@stdd/cli 0.0.1 → 0.0.2
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 -3
- package/cli/lib.mjs +8 -0
- package/cli/stdd.mjs +43 -4
- package/method/README.md +6 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -132,10 +132,14 @@ $ npx @stdd/cli doctor
|
|
|
132
132
|
Then wire the guards into CI:
|
|
133
133
|
|
|
134
134
|
```yaml
|
|
135
|
+
- uses: actions/checkout@v4
|
|
136
|
+
with:
|
|
137
|
+
fetch-depth: 0 # check-pr --base needs history
|
|
135
138
|
- run: npx @stdd/cli check # tree invariants
|
|
136
139
|
- env:
|
|
137
140
|
PR_BODY: ${{ github.event.pull_request.body }}
|
|
138
|
-
|
|
141
|
+
# Evidence line present AND the claimed doc paths really changed in this PR
|
|
142
|
+
run: printf '%s' "$PR_BODY" | npx @stdd/cli check-pr - --base "origin/${{ github.base_ref }}"
|
|
139
143
|
```
|
|
140
144
|
|
|
141
145
|
## Commands
|
|
@@ -145,7 +149,7 @@ Then wire the guards into CI:
|
|
|
145
149
|
| `stdd init [dir] [--tools claude,codex]` | Install `.stdd/` and compile playbooks per agent |
|
|
146
150
|
| `stdd doctor [dir]` | Adoption health report: setup, canonical docs, misleading artifacts, drift — exits 1 on findings |
|
|
147
151
|
| `stdd check [dir]` | CI guard: no committed working artifacts, no temporal narrative in canonical docs, no stale or hand-edited generated files |
|
|
148
|
-
| `stdd check-pr <file
|
|
152
|
+
| `stdd check-pr <file\|-> [--base <ref>]` | CI guard: PR body carries exactly one non-empty docs evidence line; with `--base`, claimed doc paths are verified against the actual git diff |
|
|
149
153
|
|
|
150
154
|
All checks are configured in `.stdd/config.json` (glob patterns for
|
|
151
155
|
forbidden artifacts, canonical docs, temporal phrases).
|
|
@@ -173,7 +177,8 @@ forbidden artifacts, canonical docs, temporal phrases).
|
|
|
173
177
|
history → git; deferred designs → dated project-log entries.
|
|
174
178
|
5. **Evidence, not claims.** Every PR states `Docs updated first:` /
|
|
175
179
|
`Docs checked, no change needed:` / `Docs not applicable:` — naming the
|
|
176
|
-
docs or the reason. CI rejects a missing, duplicated, or bare label
|
|
180
|
+
docs or the reason. CI rejects a missing, duplicated, or bare label, and
|
|
181
|
+
with `--base` verifies the claimed doc paths against the actual diff.
|
|
177
182
|
|
|
178
183
|
The full contract: [`method/README.md`](method/README.md).
|
|
179
184
|
|
package/cli/lib.mjs
CHANGED
|
@@ -92,6 +92,14 @@ export function mergeConfig(parsed) {
|
|
|
92
92
|
return config;
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
+
/**
|
|
96
|
+
* Extract repo-relative markdown paths from an evidence line's content.
|
|
97
|
+
* Prose (reasons, dashes, backticks) around the paths is ignored.
|
|
98
|
+
*/
|
|
99
|
+
export function extractDocPaths(content) {
|
|
100
|
+
return content.match(/[A-Za-z0-9_][A-Za-z0-9_./-]*\.md\b/g) ?? [];
|
|
101
|
+
}
|
|
102
|
+
|
|
95
103
|
/**
|
|
96
104
|
* Build a temporal-phrase matcher. Word-ish boundaries on both sides so
|
|
97
105
|
* hyphenated compounds ("no longer-lived") do not match.
|
package/cli/stdd.mjs
CHANGED
|
@@ -5,6 +5,7 @@ import path from "node:path";
|
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import {
|
|
7
7
|
DEFAULT_CONFIG,
|
|
8
|
+
extractDocPaths,
|
|
8
9
|
findEvidenceLines,
|
|
9
10
|
globToRegExp,
|
|
10
11
|
mergeConfig,
|
|
@@ -295,7 +296,7 @@ function doctor(targetDir) {
|
|
|
295
296
|
if (failed) process.exit(1);
|
|
296
297
|
}
|
|
297
298
|
|
|
298
|
-
function checkPr(prBodyFile) {
|
|
299
|
+
function checkPr(prBodyFile, baseRef) {
|
|
299
300
|
const body =
|
|
300
301
|
prBodyFile === "-" || !prBodyFile ? fs.readFileSync(0, "utf8") : fs.readFileSync(prBodyFile, "utf8");
|
|
301
302
|
const matches = findEvidenceLines(body);
|
|
@@ -313,16 +314,52 @@ function checkPr(prBodyFile) {
|
|
|
313
314
|
if (matches[0].content === "") {
|
|
314
315
|
fail(`"${matches[0].label}:" names no evidence — list the docs or the reason after the colon.`);
|
|
315
316
|
}
|
|
317
|
+
if (baseRef) verifyEvidenceAgainstDiff(matches[0], baseRef);
|
|
316
318
|
console.log("stdd check-pr: OK");
|
|
317
319
|
}
|
|
318
320
|
|
|
321
|
+
/** With --base: the evidence claim must be backed by the actual git diff. */
|
|
322
|
+
function verifyEvidenceAgainstDiff({ label, content }, baseRef) {
|
|
323
|
+
const paths = extractDocPaths(content);
|
|
324
|
+
if (label === "Docs updated first") {
|
|
325
|
+
if (paths.length === 0) {
|
|
326
|
+
fail(`"Docs updated first:" names no doc paths — list the changed docs.`);
|
|
327
|
+
}
|
|
328
|
+
let changed;
|
|
329
|
+
try {
|
|
330
|
+
changed = execFileSync("git", ["diff", "--name-only", `${baseRef}...HEAD`], {
|
|
331
|
+
encoding: "utf8",
|
|
332
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
333
|
+
})
|
|
334
|
+
.split("\n")
|
|
335
|
+
.filter(Boolean);
|
|
336
|
+
} catch (err) {
|
|
337
|
+
fail(`--base ${baseRef}: git diff failed: ${err.stderr?.toString().trim() || err.message}`);
|
|
338
|
+
}
|
|
339
|
+
const missing = paths.filter((p) => !changed.includes(p));
|
|
340
|
+
if (missing.length > 0) {
|
|
341
|
+
fail(`claimed as updated but not changed against ${baseRef}: ${missing.join(", ")}`);
|
|
342
|
+
}
|
|
343
|
+
} else if (label === "Docs checked, no change needed") {
|
|
344
|
+
const absent = paths.filter((p) => !fs.existsSync(path.join(process.cwd(), ...p.split("/"))));
|
|
345
|
+
if (absent.length > 0) {
|
|
346
|
+
fail(`claimed as checked but does not exist in the tree: ${absent.join(", ")}`);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
319
351
|
// --- argument parsing (strict: unknown flags are errors) ---
|
|
320
352
|
const [, , command, ...rest] = process.argv;
|
|
321
353
|
let tools = null;
|
|
354
|
+
let baseRefArg = null;
|
|
322
355
|
const positional = [];
|
|
323
356
|
for (let i = 0; i < rest.length; i++) {
|
|
324
357
|
const arg = rest[i];
|
|
325
|
-
if (arg === "--
|
|
358
|
+
if (arg === "--base" || arg.startsWith("--base=")) {
|
|
359
|
+
if (command !== "check-pr") fail(`--base is only valid for "stdd check-pr"`);
|
|
360
|
+
baseRefArg = arg.includes("=") ? arg.slice("--base=".length) : (rest[++i] ?? "");
|
|
361
|
+
if (!baseRefArg) fail("--base requires a git ref, e.g. --base origin/main");
|
|
362
|
+
} else if (arg === "--tools" || arg.startsWith("--tools=")) {
|
|
326
363
|
if (command !== "init") fail(`--tools is only valid for "stdd init"`);
|
|
327
364
|
const value = arg.includes("=") ? arg.slice("--tools=".length) : (rest[++i] ?? "");
|
|
328
365
|
tools = value.split(",").filter(Boolean);
|
|
@@ -351,13 +388,15 @@ switch (command) {
|
|
|
351
388
|
doctor(targetDir);
|
|
352
389
|
break;
|
|
353
390
|
case "check-pr":
|
|
354
|
-
checkPr(positional[0]);
|
|
391
|
+
checkPr(positional[0], baseRefArg);
|
|
355
392
|
break;
|
|
356
393
|
case "--version":
|
|
357
394
|
case "version":
|
|
358
395
|
console.log(VERSION);
|
|
359
396
|
break;
|
|
360
397
|
default:
|
|
361
|
-
console.log(
|
|
398
|
+
console.log(
|
|
399
|
+
"Usage: stdd <init|check|check-pr|doctor> [dir|pr-body-file] [--tools claude,codex] [--base <ref>]",
|
|
400
|
+
);
|
|
362
401
|
process.exit(command ? 1 : 0);
|
|
363
402
|
}
|
package/method/README.md
CHANGED
|
@@ -55,6 +55,12 @@ classify → read docs → docs edit (the spec) → failing test → implement
|
|
|
55
55
|
starting at the beginning of a line counts (quoted templates and code
|
|
56
56
|
blocks do not).
|
|
57
57
|
|
|
58
|
+
With `--base <ref>` the claim is verified against the actual diff:
|
|
59
|
+
every doc path named after `Docs updated first:` must be a file changed
|
|
60
|
+
between the base ref and `HEAD` (and at least one path must be named);
|
|
61
|
+
paths named after `Docs checked, no change needed:` must exist in the
|
|
62
|
+
tree. Claiming a docs update the diff does not contain fails CI.
|
|
63
|
+
|
|
58
64
|
## The frontend exception: design-first
|
|
59
65
|
|
|
60
66
|
Frontend **visual** work — layout, styling, markup structure, presentation
|