create-cadmo 0.0.1 → 0.2.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 +19 -11
- package/index.js +95 -0
- package/package.json +37 -26
- package/templates/AGENTS.md +31 -0
- package/templates/claude-commands/cadmo-done.md +17 -0
- package/templates/claude-commands/cadmo-gate.md +13 -0
- package/templates/claude-commands/cadmo-spec.md +13 -0
- package/templates/decision.md +20 -0
- package/templates/plan.md +21 -0
- package/templates/spec-drift-workflow.yml +19 -0
- package/templates/spec-drift.mjs +120 -0
- package/templates/spec.md +24 -0
- package/templates/value-gate.md +12 -0
package/README.md
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
|
-
# create-cadmo
|
|
2
|
-
|
|
3
|
-
Scaffold the **Cadmo** method into your project — a right-sized method for building software with AI as your pair.
|
|
4
|
-
|
|
5
|
-
```bash
|
|
6
|
-
npm create cadmo
|
|
7
|
-
```
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
1
|
+
# create-cadmo
|
|
2
|
+
|
|
3
|
+
Scaffold the **Cadmo** method into your project — a right-sized method for building software with AI as your pair.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm create cadmo
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Drops into the current directory, never overwriting anything:
|
|
10
|
+
|
|
11
|
+
- `AGENTS.md` — the local map your AI pair reads first (it will offer to fill it in for you)
|
|
12
|
+
- `cadmo/value-gate.md` — 5 lines before any spec: is it worth building?
|
|
13
|
+
- `cadmo/spec.md` — the rules, in the client's language, criteria first
|
|
14
|
+
- `cadmo/plan.md` — the internal route: tasks, constraints, how to validate
|
|
15
|
+
- `cadmo/decision.md` — architecture decisions with a "when to reconsider" trigger
|
|
16
|
+
- `cadmo/spec-drift.mjs` + a ready CI workflow — **specs must change with the code they watch** (the drift guard)
|
|
17
|
+
- `.claude/commands/cadmo-*.md` — `/cadmo-gate`, `/cadmo-spec`, `/cadmo-done` slash commands (when Claude Code is detected, or `--claude`)
|
|
18
|
+
|
|
19
|
+
The method, docs and protocols: **https://github.com/tiagotorres91/cadmo**
|
package/index.js
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/*
|
|
3
|
+
* create-cadmo — scaffold the Cadmo method into the current project.
|
|
4
|
+
* No dependencies. Never overwrites an existing file.
|
|
5
|
+
* https://github.com/tiagotorres91/cadmo
|
|
6
|
+
*/
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
const args = process.argv.slice(2);
|
|
11
|
+
const DRY = args.includes('--dry-run');
|
|
12
|
+
const FORCE_CLAUDE = args.includes('--claude');
|
|
13
|
+
const NO_CLAUDE = args.includes('--no-claude');
|
|
14
|
+
|
|
15
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
16
|
+
console.log(`
|
|
17
|
+
create-cadmo — drop the Cadmo method into the current project.
|
|
18
|
+
|
|
19
|
+
Usage: npm create cadmo [-- --dry-run] [-- --claude | --no-claude]
|
|
20
|
+
|
|
21
|
+
--dry-run show what would be created, create nothing
|
|
22
|
+
--claude also install Claude Code slash commands (/cadmo-gate, /cadmo-spec, /cadmo-done)
|
|
23
|
+
--no-claude skip the Claude Code commands even if CLAUDE.md/.claude is detected
|
|
24
|
+
|
|
25
|
+
Creates (never overwrites):
|
|
26
|
+
AGENTS.md the local map your AI pair reads first
|
|
27
|
+
cadmo/value-gate.md 5 lines before any spec: is it worth building?
|
|
28
|
+
cadmo/spec.md the rules, client language, criteria first
|
|
29
|
+
cadmo/plan.md the internal route: tasks, constraints, validation
|
|
30
|
+
cadmo/decision.md ADR with a "when to reconsider" trigger
|
|
31
|
+
cadmo/spec-drift.mjs the drift guard: specs must change with the code they watch
|
|
32
|
+
cadmo/spec-drift-workflow.yml ready CI workflow (move into .github/workflows/)
|
|
33
|
+
.claude/commands/cadmo-*.md (when Claude Code is detected, or --claude)
|
|
34
|
+
|
|
35
|
+
The method in one page: https://github.com/tiagotorres91/cadmo
|
|
36
|
+
`);
|
|
37
|
+
process.exit(0);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const here = (...p) => path.join(__dirname, 'templates', ...p);
|
|
41
|
+
const cwd = process.cwd();
|
|
42
|
+
|
|
43
|
+
const FILES = [
|
|
44
|
+
['AGENTS.md', 'AGENTS.md'],
|
|
45
|
+
['value-gate.md', path.join('cadmo', 'value-gate.md')],
|
|
46
|
+
['spec.md', path.join('cadmo', 'spec.md')],
|
|
47
|
+
['plan.md', path.join('cadmo', 'plan.md')],
|
|
48
|
+
['decision.md', path.join('cadmo', 'decision.md')],
|
|
49
|
+
['spec-drift.mjs', path.join('cadmo', 'spec-drift.mjs')],
|
|
50
|
+
['spec-drift-workflow.yml', path.join('cadmo', 'spec-drift-workflow.yml')],
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
const hasClaude = FORCE_CLAUDE || (!NO_CLAUDE &&
|
|
54
|
+
(fs.existsSync(path.join(cwd, 'CLAUDE.md')) || fs.existsSync(path.join(cwd, '.claude'))));
|
|
55
|
+
|
|
56
|
+
const CLAUDE_FILES = [
|
|
57
|
+
[path.join('claude-commands', 'cadmo-gate.md'), path.join('.claude', 'commands', 'cadmo-gate.md')],
|
|
58
|
+
[path.join('claude-commands', 'cadmo-spec.md'), path.join('.claude', 'commands', 'cadmo-spec.md')],
|
|
59
|
+
[path.join('claude-commands', 'cadmo-done.md'), path.join('.claude', 'commands', 'cadmo-done.md')],
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
const plan = hasClaude ? FILES.concat(CLAUDE_FILES) : FILES;
|
|
63
|
+
|
|
64
|
+
console.log(`\n Cadmo — write it down before you build it.${DRY ? ' (dry run)' : ''}\n`);
|
|
65
|
+
|
|
66
|
+
let placed = 0, skipped = 0;
|
|
67
|
+
for (const [src, dest] of plan) {
|
|
68
|
+
const target = path.join(cwd, dest);
|
|
69
|
+
if (fs.existsSync(target)) {
|
|
70
|
+
console.log(` = kept ${dest} (already exists — not touched)`);
|
|
71
|
+
skipped++;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (DRY) {
|
|
75
|
+
console.log(` ~ would create ${dest}`);
|
|
76
|
+
} else {
|
|
77
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
78
|
+
fs.copyFileSync(here(src), target);
|
|
79
|
+
console.log(` + created ${dest}`);
|
|
80
|
+
}
|
|
81
|
+
placed++;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
console.log(`\n ${placed} file(s) ${DRY ? 'would be created' : 'created'}, ${skipped} kept.\n`);
|
|
85
|
+
if (!hasClaude && !NO_CLAUDE) {
|
|
86
|
+
console.log(' (No CLAUDE.md/.claude detected — run with --claude to also install /cadmo-* slash commands.)');
|
|
87
|
+
}
|
|
88
|
+
console.log(' Next steps:');
|
|
89
|
+
console.log(' 1. Fill in AGENTS.md — the local map your AI pair reads first');
|
|
90
|
+
console.log(' (using CLAUDE.md? link or rename it — same idea).');
|
|
91
|
+
console.log(' 2. For your next relevant feature: cadmo/value-gate.md (5 lines, before any spec).');
|
|
92
|
+
console.log(' 3. For any 3+ step task: copy cadmo/plan.md — acceptance criteria before code.');
|
|
93
|
+
console.log(' 4. Turn on the drift guard: move cadmo/spec-drift-workflow.yml to .github/workflows/');
|
|
94
|
+
console.log(' and add a `watches:` front matter to your first spec.');
|
|
95
|
+
console.log('\n The method in one page: https://github.com/tiagotorres91/cadmo\n');
|
package/package.json
CHANGED
|
@@ -1,26 +1,37 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "create-cadmo",
|
|
3
|
-
"version": "0.0
|
|
4
|
-
"description": "Scaffold the Cadmo method into your project
|
|
5
|
-
"keywords": [
|
|
6
|
-
"cadmo",
|
|
7
|
-
"create-cadmo",
|
|
8
|
-
"spec-driven-development",
|
|
9
|
-
"ai",
|
|
10
|
-
"methodology",
|
|
11
|
-
"framework",
|
|
12
|
-
"agents",
|
|
13
|
-
"claude",
|
|
14
|
-
"scaffold"
|
|
15
|
-
],
|
|
16
|
-
"homepage": "https://cadmo
|
|
17
|
-
"repository": {
|
|
18
|
-
"type": "git",
|
|
19
|
-
"url": "git+https://github.com/tiagotorres91/cadmo.git"
|
|
20
|
-
},
|
|
21
|
-
"author": "Tiago Torres
|
|
22
|
-
"license": "MIT",
|
|
23
|
-
"files": [
|
|
24
|
-
"
|
|
25
|
-
|
|
26
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "create-cadmo",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Scaffold the Cadmo method into your project \u2014 a right-sized method for building software with AI as your pair.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"cadmo",
|
|
7
|
+
"create-cadmo",
|
|
8
|
+
"spec-driven-development",
|
|
9
|
+
"ai",
|
|
10
|
+
"methodology",
|
|
11
|
+
"framework",
|
|
12
|
+
"agents",
|
|
13
|
+
"claude",
|
|
14
|
+
"scaffold"
|
|
15
|
+
],
|
|
16
|
+
"homepage": "https://github.com/tiagotorres91/cadmo",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/tiagotorres91/cadmo.git"
|
|
20
|
+
},
|
|
21
|
+
"author": "Tiago Torres",
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"files": [
|
|
24
|
+
"index.js",
|
|
25
|
+
"templates/",
|
|
26
|
+
"README.md"
|
|
27
|
+
],
|
|
28
|
+
"bin": {
|
|
29
|
+
"create-cadmo": "index.js"
|
|
30
|
+
},
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=16"
|
|
33
|
+
},
|
|
34
|
+
"scripts": {
|
|
35
|
+
"test": "node --test"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# AGENTS.md — <project name>
|
|
2
|
+
|
|
3
|
+
> Drop this at the root of your project so any AI pair works the Cadmo way from the first message.
|
|
4
|
+
> This is the *local map*: what this project is, where things live, and how to verify. The method
|
|
5
|
+
> itself is in AGENTS.md at the Cadmo repo; this file is the project-specific instance.
|
|
6
|
+
>
|
|
7
|
+
> **AI pair reading this while it still has `<placeholders>`:** your first job is to fill it —
|
|
8
|
+
> scan the repository (structure, package files, scripts, CI) and propose the map to your human:
|
|
9
|
+
> what the project is, where specs/plans should live, and the actual verify commands. Then remove
|
|
10
|
+
> this paragraph.
|
|
11
|
+
|
|
12
|
+
## What this project is
|
|
13
|
+
<one paragraph: the product, the client/owner, the stack>
|
|
14
|
+
|
|
15
|
+
## Where things live
|
|
16
|
+
- Specs / decisions: <path>
|
|
17
|
+
- Plans: <path>
|
|
18
|
+
- Demands / task board: <where>
|
|
19
|
+
|
|
20
|
+
## How to verify (the harness — run before saying "done")
|
|
21
|
+
- Build/type-check: `<command>`
|
|
22
|
+
- Tests: `<command>`
|
|
23
|
+
- End-to-end / smoke: `<command>`
|
|
24
|
+
- Staging URL: <url> · Production: <url>
|
|
25
|
+
|
|
26
|
+
## Gates
|
|
27
|
+
- Plan approved before building; production ships only with explicit human approval.
|
|
28
|
+
- <any project-specific rule or gotcha>
|
|
29
|
+
|
|
30
|
+
## The method
|
|
31
|
+
Opening choreography, definition of done, the layers — see the Cadmo method (link).
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# /cadmo-done — the definition of done, checked honestly
|
|
2
|
+
|
|
3
|
+
Run the definition of done for: **$ARGUMENTS** (or the work we just finished).
|
|
4
|
+
|
|
5
|
+
Check each item **against evidence, not memory** — run the commands, don't assume:
|
|
6
|
+
|
|
7
|
+
- [ ] Build/tests pass — run them now and show me the output
|
|
8
|
+
- [ ] **Tested functionally** — the real flow (browser, curl, CLI run), not just linters
|
|
9
|
+
- [ ] Validated in staging before production (when there is one)
|
|
10
|
+
- [ ] Acceptance criteria from the spec/plan — checked one by one, quoted
|
|
11
|
+
- [ ] Spec updated if a rule changed (same commit) — run `node cadmo/spec-drift.mjs --base origin/main` if present
|
|
12
|
+
- [ ] Anything untested? Say it explicitly: "I didn't test X"
|
|
13
|
+
- [ ] Touched auth/input/data/secrets? → flag that an adversarial security pass is due before shipping
|
|
14
|
+
|
|
15
|
+
Then the **process check** (the choreography, not the code): was there a plan before execution? criteria before code? If any answer is no, say so plainly — this checklist exists to be failed honestly, not passed cosmetically.
|
|
16
|
+
|
|
17
|
+
Finish with a verdict: **ready to ship** / **ready except <items>** / **not ready because <items>**.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# /cadmo-gate — open a value gate
|
|
2
|
+
|
|
3
|
+
Open a value gate for: **$ARGUMENTS**
|
|
4
|
+
|
|
5
|
+
Work with me (the human) to fill the five lines — copy `cadmo/value-gate.md` next to the demand, then:
|
|
6
|
+
|
|
7
|
+
1. **Problem (measurable):** ask me what hurts today; push for a number (time lost, error rate, money).
|
|
8
|
+
2. **Why this solution:** make me compare against at least one cheaper alternative (a rule? a process change? doing nothing?).
|
|
9
|
+
3. **Success metric (business):** a number that will prove value after delivery — reject "it works" as a metric.
|
|
10
|
+
4. **Data readiness:** check the actual sources (tables, exports, APIs) — 🟢/🟡/🔴 with the caveat named.
|
|
11
|
+
5. **Verdict:** recommend GO / NO-GO / defer, with one sentence of reasoning — but the verdict is mine to make.
|
|
12
|
+
|
|
13
|
+
Keep it to ~5 lines of content. If the work is trivial (a fix, a tweak), tell me it doesn't need a gate and stop.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# /cadmo-spec — write the spec, criteria first
|
|
2
|
+
|
|
3
|
+
Write the spec for: **$ARGUMENTS**
|
|
4
|
+
|
|
5
|
+
Use `cadmo/spec.md` as the skeleton. Rules:
|
|
6
|
+
|
|
7
|
+
1. **Goal in the client's language** — 2-5 lines a non-developer validates. No jargon.
|
|
8
|
+
2. **Acceptance criteria before anything else** — each one observable and specific. For critical ones (money, data, integrations, security) use EARS: *"WHEN <trigger>, THE SYSTEM SHALL <behavior>"* — they become tests almost word for word.
|
|
9
|
+
3. **Out of scope** — name what this does NOT cover.
|
|
10
|
+
4. **Impact on stable specs** — which existing documented rules will absorb this on delivery. If this spec should be *watched* against code, add the `watches:` front matter (see `cadmo/spec-drift.mjs`).
|
|
11
|
+
5. Then pair it with a plan (`cadmo/plan.md`) — technical route, tasks, how to validate. Don't repeat the criteria there.
|
|
12
|
+
|
|
13
|
+
Stop after the spec and ask me to validate it **before** writing any code. If it changes money, data or an external integration, remind me the client (or their proxy) validates it first.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# ADR — <short title of the decision>
|
|
2
|
+
|
|
3
|
+
**Status:** accepted · **Date:** <YYYY-MM-DD> · **Deciders:** <who>
|
|
4
|
+
|
|
5
|
+
## Context
|
|
6
|
+
The forces at play: the problem, the constraints, what makes this decision necessary now (3–6 lines).
|
|
7
|
+
|
|
8
|
+
## Decision
|
|
9
|
+
What was decided, stated as a full sentence ("We will…").
|
|
10
|
+
|
|
11
|
+
## Alternatives considered
|
|
12
|
+
- **<Alternative A>** — why not: …
|
|
13
|
+
- **<Alternative B>** — why not: …
|
|
14
|
+
(Real alternatives you weighed — this section is what saves the re-investigation in six months.)
|
|
15
|
+
|
|
16
|
+
## Consequences
|
|
17
|
+
What becomes easier, what becomes harder, what debt is knowingly taken on.
|
|
18
|
+
|
|
19
|
+
## When to reconsider
|
|
20
|
+
The trigger that should reopen this decision (a scale threshold, a price change, a vendor event) — a decision without a reconsider-trigger quietly becomes dogma.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# <Demand name> — plan (internal)
|
|
2
|
+
|
|
3
|
+
Pairs with `spec.md` (acceptance criteria live there — don't repeat them).
|
|
4
|
+
|
|
5
|
+
## Objective
|
|
6
|
+
What changes in the world when this is done (1–3 lines).
|
|
7
|
+
|
|
8
|
+
## Investigation (optional)
|
|
9
|
+
What was tried/considered before and why it was discarded (3–6 lines) — avoids re-investigating dead ends.
|
|
10
|
+
|
|
11
|
+
## Context & constraints
|
|
12
|
+
Current state, decisions already made, what NOT to change.
|
|
13
|
+
|
|
14
|
+
## Technical plan
|
|
15
|
+
Files/tables/endpoints touched; approach; alternatives discarded.
|
|
16
|
+
|
|
17
|
+
## Tasks
|
|
18
|
+
- [ ] Small, verifiable (status kept here — any session resumes without getting lost)
|
|
19
|
+
|
|
20
|
+
## How to validate
|
|
21
|
+
Commands/flows that prove the acceptance criteria (build, tests, e2e, real manual flow).
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Example workflow — copy to .github/workflows/spec-drift.yml in your project.
|
|
2
|
+
# Fails a PR when a file watched by a spec changes and the spec doesn't change with it.
|
|
3
|
+
# Escape hatch (audited, never silent): write "spec-drift: skip — <reason>" in the PR body.
|
|
4
|
+
name: spec-drift
|
|
5
|
+
on:
|
|
6
|
+
pull_request:
|
|
7
|
+
jobs:
|
|
8
|
+
drift:
|
|
9
|
+
if: "${{ !contains(github.event.pull_request.body, 'spec-drift: skip') }}"
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
with:
|
|
14
|
+
fetch-depth: 0
|
|
15
|
+
- uses: actions/setup-node@v4
|
|
16
|
+
with:
|
|
17
|
+
node-version: "20"
|
|
18
|
+
- name: specs must change with the code they govern
|
|
19
|
+
run: node cadmo/spec-drift.mjs --base origin/${{ github.base_ref }}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/*
|
|
3
|
+
* spec-drift — the rule that keeps documentation honest:
|
|
4
|
+
* a spec declares which files implement it; if those files change and the spec
|
|
5
|
+
* doesn't change in the same diff, the check fails.
|
|
6
|
+
*
|
|
7
|
+
* No dependencies. How it works:
|
|
8
|
+
* 1. Scans the repo for .md files with a `watches:` list in their front matter.
|
|
9
|
+
* 2. Diffs the current branch against --base (default: origin/main).
|
|
10
|
+
* 3. For each spec: if a watched file changed and the spec didn't → DRIFT.
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* node spec-drift.mjs [--base origin/main] [--dir .]
|
|
14
|
+
*
|
|
15
|
+
* Spec front matter example:
|
|
16
|
+
* ---
|
|
17
|
+
* watches:
|
|
18
|
+
* - src/billing/**
|
|
19
|
+
* - api/invoices.py
|
|
20
|
+
* ---
|
|
21
|
+
*
|
|
22
|
+
* Escape hatch (audited, not silent): if a change genuinely doesn't alter any
|
|
23
|
+
* documented rule, say so where reviewers can see it — e.g. run the CI step
|
|
24
|
+
* only when the PR body does NOT contain "spec-drift: skip — <reason>".
|
|
25
|
+
*/
|
|
26
|
+
import { execFileSync } from 'node:child_process';
|
|
27
|
+
import fs from 'node:fs';
|
|
28
|
+
import path from 'node:path';
|
|
29
|
+
|
|
30
|
+
const args = process.argv.slice(2);
|
|
31
|
+
function opt(name, fallback) {
|
|
32
|
+
const i = args.indexOf(name);
|
|
33
|
+
return i >= 0 && args[i + 1] ? args[i + 1] : fallback;
|
|
34
|
+
}
|
|
35
|
+
const BASE = opt('--base', 'origin/main');
|
|
36
|
+
const ROOT = path.resolve(opt('--dir', '.'));
|
|
37
|
+
|
|
38
|
+
// --- collect specs that declare `watches:` in front matter ---
|
|
39
|
+
function* mdFiles(dir) {
|
|
40
|
+
for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
41
|
+
if (e.name.startsWith('.') || e.name === 'node_modules') continue;
|
|
42
|
+
const p = path.join(dir, e.name);
|
|
43
|
+
if (e.isDirectory()) yield* mdFiles(p);
|
|
44
|
+
else if (e.name.endsWith('.md')) yield p;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function parseWatches(file) {
|
|
49
|
+
const text = fs.readFileSync(file, 'utf8');
|
|
50
|
+
if (!text.startsWith('---')) return null;
|
|
51
|
+
const end = text.indexOf('\n---', 3);
|
|
52
|
+
if (end < 0) return null;
|
|
53
|
+
const fm = text.slice(3, end);
|
|
54
|
+
const lines = fm.split('\n');
|
|
55
|
+
const watches = [];
|
|
56
|
+
let inWatches = false;
|
|
57
|
+
for (const line of lines) {
|
|
58
|
+
if (/^watches:\s*$/.test(line)) { inWatches = true; continue; }
|
|
59
|
+
if (inWatches) {
|
|
60
|
+
const m = line.match(/^\s+-\s+(.+?)\s*$/);
|
|
61
|
+
if (m) watches.push(m[1].replace(/^["']|["']$/g, ''));
|
|
62
|
+
else if (!/^\s/.test(line)) inWatches = false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return watches.length ? watches : null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// --- minimal glob → regex (supports ** and *) ---
|
|
69
|
+
function globToRegex(glob) {
|
|
70
|
+
// zero-backslash implementation: escape specials via char classes, never via backslash
|
|
71
|
+
const BS = String.fromCharCode(92);
|
|
72
|
+
let esc = '';
|
|
73
|
+
for (const ch of glob) {
|
|
74
|
+
if (ch === '*' || ch === '/') esc += ch;
|
|
75
|
+
else if ('.+^$(){}|[]?'.includes(ch) || ch === BS) esc += BS + ch;
|
|
76
|
+
else esc += ch;
|
|
77
|
+
}
|
|
78
|
+
esc = esc.split('**').join('__DSTAR__');
|
|
79
|
+
esc = esc.split('*').join('[^/]*');
|
|
80
|
+
esc = esc.split('__DSTAR__').join('.*');
|
|
81
|
+
return new RegExp('^' + esc + '$');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// --- changed files vs base ---
|
|
85
|
+
let changed;
|
|
86
|
+
try {
|
|
87
|
+
changed = execFileSync('git', ['diff', '--name-only', `${BASE}...HEAD`], { cwd: ROOT, encoding: 'utf8' })
|
|
88
|
+
.split('\n').filter(Boolean);
|
|
89
|
+
} catch (e) {
|
|
90
|
+
console.error(`spec-drift: cannot diff against ${BASE} — ${e.message}`);
|
|
91
|
+
process.exit(2);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const specs = [];
|
|
95
|
+
for (const f of mdFiles(ROOT)) {
|
|
96
|
+
const watches = parseWatches(f);
|
|
97
|
+
if (watches) specs.push({ spec: path.relative(ROOT, f).split(String.fromCharCode(92)).join('/'), watches });
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (!specs.length) {
|
|
101
|
+
console.log('spec-drift: no specs with a `watches:` front matter found — nothing to check.');
|
|
102
|
+
process.exit(0);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const changedSet = new Set(changed);
|
|
106
|
+
let drift = 0;
|
|
107
|
+
for (const { spec, watches } of specs) {
|
|
108
|
+
const regexes = watches.map(globToRegex);
|
|
109
|
+
const hits = changed.filter(c => regexes.some(r => r.test(c)));
|
|
110
|
+
if (hits.length && !changedSet.has(spec)) {
|
|
111
|
+
drift++;
|
|
112
|
+
console.error(`DRIFT: ${hits.join(', ')} changed, but ${spec} (which watches ${watches.join(', ')}) did not change in this diff.`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (drift) {
|
|
117
|
+
console.error(`\nspec-drift: ${drift} spec(s) out of sync. Update the spec in the same change — or state why no documented rule changed (visibly, where reviewers can see it).`);
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}
|
|
120
|
+
console.log(`spec-drift: ${specs.length} spec(s) checked against ${changed.length} changed file(s) — in sync ✓`);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Spec — <name>
|
|
2
|
+
|
|
3
|
+
**Status:** draft — awaiting validation (<who>) · **Demand:** <link/id>
|
|
4
|
+
|
|
5
|
+
## Goal & context
|
|
6
|
+
The business problem and what changes in the world (2–5 lines, in the client's language).
|
|
7
|
+
(Relevant work already passed the value gate — its success metric becomes an acceptance criterion here.)
|
|
8
|
+
|
|
9
|
+
## Acceptance criteria
|
|
10
|
+
- [ ] Simple ones in free form
|
|
11
|
+
- [ ] Critical ones (money / data / integration / security) in EARS:
|
|
12
|
+
WHEN <trigger>, THE SYSTEM SHALL <behavior>
|
|
13
|
+
|
|
14
|
+
## Data (AI features only)
|
|
15
|
+
Source, owner, access, quality/bias, corpus refresh plan.
|
|
16
|
+
|
|
17
|
+
## Risks (critical or AI demands only)
|
|
18
|
+
Risk → response. For AI: corpus drift, provider API change, cost, regulation.
|
|
19
|
+
|
|
20
|
+
## Out of scope
|
|
21
|
+
What this delivery does NOT cover (controlled expectations).
|
|
22
|
+
|
|
23
|
+
## Impact on stable specs
|
|
24
|
+
Which sections of the system/domain specs will be updated on delivery (the absorption).
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Value gate — <demand name>
|
|
2
|
+
|
|
3
|
+
Fill this in *before* writing a spec, for anything relevant (a new feature, module, project).
|
|
4
|
+
Trivial work and routine fixes skip this.
|
|
5
|
+
|
|
6
|
+
**Problem (measurable):** what hurts today, in numbers when possible.
|
|
7
|
+
**Why this solution:** AI vs a rule vs a process vs doing nothing — why this path.
|
|
8
|
+
**Success metric (business):** the number that will prove value — not "it works", but "reduced X", "freed Y hours", "raised Z".
|
|
9
|
+
**Data readiness:** 🟢 data exists, accessible, reliable · 🟡 exists with caveats (which?) · 🔴 doesn't exist → resolve *before* specifying.
|
|
10
|
+
**Verdict:** GO / NO-GO / defer (why).
|
|
11
|
+
|
|
12
|
+
<!-- At close, come back and record: did the metric materialize? -->
|