create-cadmo 0.1.0 → 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 CHANGED
@@ -13,5 +13,7 @@ Drops into the current directory, never overwriting anything:
13
13
  - `cadmo/spec.md` — the rules, in the client's language, criteria first
14
14
  - `cadmo/plan.md` — the internal route: tasks, constraints, how to validate
15
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`)
16
18
 
17
- The method, docs and protocols: **https://github.com/tiagotorres91/cadmo** · **https://cadmo.dev**
19
+ The method, docs and protocols: **https://github.com/tiagotorres91/cadmo**
package/index.js CHANGED
@@ -1,44 +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 here = (...p) => path.join(__dirname, 'templates', ...p);
11
- const cwd = process.cwd();
12
-
13
- const FILES = [
14
- // [source in package, destination in project]
15
- ['AGENTS.md', 'AGENTS.md'],
16
- ['value-gate.md', path.join('cadmo', 'value-gate.md')],
17
- ['spec.md', path.join('cadmo', 'spec.md')],
18
- ['plan.md', path.join('cadmo', 'plan.md')],
19
- ['decision.md', path.join('cadmo', 'decision.md')],
20
- ];
21
-
22
- console.log('\n Cadmo write it down before you build it.\n');
23
-
24
- let placed = 0, skipped = 0;
25
- for (const [src, dest] of FILES) {
26
- const target = path.join(cwd, dest);
27
- if (fs.existsSync(target)) {
28
- console.log(` = kept ${dest} (already exists not touched)`);
29
- skipped++;
30
- continue;
31
- }
32
- fs.mkdirSync(path.dirname(target), { recursive: true });
33
- fs.copyFileSync(here(src), target);
34
- console.log(` + created ${dest}`);
35
- placed++;
36
- }
37
-
38
- console.log(`\n ${placed} file(s) created, ${skipped} kept.\n`);
39
- console.log(' Next steps:');
40
- console.log(' 1. Fill in AGENTS.md the local map your AI pair reads first');
41
- console.log(' (using CLAUDE.md? link or rename it — same idea).');
42
- console.log(' 2. For your next relevant feature: cadmo/value-gate.md (5 lines, before any spec).');
43
- console.log(' 3. For any 3+ step task: copy cadmo/plan.md — acceptance criteria before code.');
44
- console.log('\n The method in one page: https://github.com/tiagotorres91/cadmo\n');
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,6 +1,6 @@
1
1
  {
2
2
  "name": "create-cadmo",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Scaffold the Cadmo method into your project \u2014 a right-sized method for building software with AI as your pair.",
5
5
  "keywords": [
6
6
  "cadmo",
@@ -13,7 +13,7 @@
13
13
  "claude",
14
14
  "scaffold"
15
15
  ],
16
- "homepage": "https://cadmo.dev",
16
+ "homepage": "https://github.com/tiagotorres91/cadmo",
17
17
  "repository": {
18
18
  "type": "git",
19
19
  "url": "git+https://github.com/tiagotorres91/cadmo.git"
@@ -30,5 +30,8 @@
30
30
  },
31
31
  "engines": {
32
32
  "node": ">=16"
33
+ },
34
+ "scripts": {
35
+ "test": "node --test"
33
36
  }
34
37
  }
@@ -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,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 ✓`);