eng-skills 0.1.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 +127 -0
- package/bin/cli.mjs +193 -0
- package/package.json +39 -0
- package/templates/CLAUDE.md +60 -0
- package/templates/claude/commands/decide.md +12 -0
- package/templates/claude/commands/feature.md +30 -0
- package/templates/claude/commands/fix.md +22 -0
- package/templates/claude/commands/onboard.md +35 -0
- package/templates/claude/commands/phase.md +9 -0
- package/templates/claude/hooks/guard.mjs +32 -0
- package/templates/claude/hooks/lib.mjs +91 -0
- package/templates/claude/hooks/post-edit.mjs +28 -0
- package/templates/claude/hooks/pre-edit.mjs +23 -0
- package/templates/claude/hooks/stop-gate.mjs +25 -0
- package/templates/claude/settings.json +45 -0
- package/templates/docs/CHANGELOG-AUTO.md +8 -0
- package/templates/docs/README.md +22 -0
- package/templates/docs/_templates/decision.md +17 -0
- package/templates/docs/_templates/feature.md +21 -0
- package/templates/docs/_templates/fix.md +18 -0
- package/templates/docs/_templates/phase.md +17 -0
- package/templates/docs/decisions/0001-record-architecture-decisions.md +24 -0
- package/templates/docs/phases/phase-0-foundation.md +18 -0
package/README.md
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# eng-skills
|
|
2
|
+
|
|
3
|
+
**Battle-tested Claude Code engineering conventions, scaffolded into any repo in one command.**
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npx eng-skills init
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Most Claude Code advice lives in blog posts. This package turns it into files: an
|
|
10
|
+
*enforced* documentation protocol, safety hooks, and research-first workflows —
|
|
11
|
+
extracted from a production monorepo where this exact system sustained
|
|
12
|
+
**100+ feature docs, 100+ fix docs, and a full ADR log** without the docs going stale.
|
|
13
|
+
|
|
14
|
+
## Why
|
|
15
|
+
|
|
16
|
+
Two facts about working with coding agents:
|
|
17
|
+
|
|
18
|
+
1. **CLAUDE.md instructions are advisory; hooks are deterministic.** An instruction
|
|
19
|
+
like "always document your changes" gets skipped under context pressure. A Stop
|
|
20
|
+
hook that refuses to end the turn does not.
|
|
21
|
+
2. **Sessions are ephemeral.** Whatever the agent learned — why a decision was made,
|
|
22
|
+
what a fix's root cause was — evaporates unless it's written where the next
|
|
23
|
+
session will look.
|
|
24
|
+
|
|
25
|
+
eng-skills wires those two facts together: hooks that *force* the knowledge base to
|
|
26
|
+
stay current, and a knowledge base structured so future sessions (and humans)
|
|
27
|
+
actually benefit from it.
|
|
28
|
+
|
|
29
|
+
## What you get
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
CLAUDE.md # protocol + TODO(onboard) markers Claude fills in itself
|
|
33
|
+
docs/
|
|
34
|
+
README.md # the knowledge-base map and "the rule"
|
|
35
|
+
CHANGELOG-AUTO.md # append-only machine-written edit log
|
|
36
|
+
_templates/ # feature / fix / decision / phase templates
|
|
37
|
+
features/ fixes/ # one doc per feature, one per bug fix
|
|
38
|
+
decisions/ # numbered ADRs (seeded with ADR-0001)
|
|
39
|
+
phases/ plans/ specs/ # living progress checklists + planning artifacts
|
|
40
|
+
.claude/
|
|
41
|
+
settings.json # hook wiring (merged safely into an existing file)
|
|
42
|
+
hooks/
|
|
43
|
+
pre-edit.mjs # PreToolUse: reminds about the protocol on first code edit
|
|
44
|
+
post-edit.mjs # PostToolUse: logs every edit to CHANGELOG-AUTO.md
|
|
45
|
+
stop-gate.mjs # Stop: BLOCKS ending a turn that changed code but not docs
|
|
46
|
+
guard.mjs # PreToolUse(Bash): denies rm -rf, force push, .env exfil, …
|
|
47
|
+
commands/
|
|
48
|
+
onboard.md # /onboard — Claude researches YOUR codebase, fills CLAUDE.md
|
|
49
|
+
feature.md # /feature — research → plan → implement → verify → document
|
|
50
|
+
fix.md # /fix — reproduce → root-cause → fix → prove → document
|
|
51
|
+
decide.md # /decide — record an ADR
|
|
52
|
+
phase.md # /phase — honest progress review, phase-gate closeout
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Everything is dependency-free Node (~150 lines of hooks total). No runtime, no daemon,
|
|
56
|
+
nothing to keep updated.
|
|
57
|
+
|
|
58
|
+
## How the enforcement works
|
|
59
|
+
|
|
60
|
+
- **pre-edit** (PreToolUse) — on the first code edit of a turn, injects a reminder
|
|
61
|
+
of the docs protocol into Claude's context. Never blocks.
|
|
62
|
+
- **post-edit** (PostToolUse) — appends `timestamp | session | tool | file | ±lines`
|
|
63
|
+
to `docs/CHANGELOG-AUTO.md` for every tracked edit, and records whether the turn
|
|
64
|
+
touched code, docs, or both.
|
|
65
|
+
- **stop-gate** (Stop) — when Claude tries to end the turn: if code changed but
|
|
66
|
+
`docs/` didn't, it returns `{"decision": "block"}` and Claude must write the doc
|
|
67
|
+
before finishing. Includes the `stop_hook_active` loop guard.
|
|
68
|
+
- **guard** (PreToolUse on Bash) — deny-list for destructive commands (`rm -rf` on
|
|
69
|
+
broad paths, `git push --force`, `git reset --hard`, `DROP TABLE`, `.env`
|
|
70
|
+
exfiltration, credential directories). Hooks fire even under
|
|
71
|
+
`--dangerously-skip-permissions`, so this holds in unattended runs.
|
|
72
|
+
|
|
73
|
+
The result: **you cannot end a turn that changed source code without documenting it**,
|
|
74
|
+
and you get a free forensic edit log on the side.
|
|
75
|
+
|
|
76
|
+
## Using it on an existing codebase
|
|
77
|
+
|
|
78
|
+
`init` is non-destructive and idempotent:
|
|
79
|
+
|
|
80
|
+
- Files that already exist are **skipped** (use `--force` to overwrite).
|
|
81
|
+
- An existing `.claude/settings.json` gets our hooks **merged in** — your
|
|
82
|
+
permissions and existing hooks are preserved.
|
|
83
|
+
- An existing `CLAUDE.md` gets the docs-protocol section **appended**, nothing else touched.
|
|
84
|
+
|
|
85
|
+
Then open Claude Code and run **`/onboard`**: Claude reads your manifests, CI config,
|
|
86
|
+
and source layout, fills in every `TODO(onboard)` marker in `CLAUDE.md` (stack,
|
|
87
|
+
verified commands, hard invariants), and retroactively records your existing
|
|
88
|
+
architecture as ADRs — so the knowledge base starts warm, not empty.
|
|
89
|
+
|
|
90
|
+
## The daily workflow
|
|
91
|
+
|
|
92
|
+
| Command | What it does |
|
|
93
|
+
|---|---|
|
|
94
|
+
| `/feature <desc>` | Researches the codebase (and web when useful) *before* writing code, writes a checkbox plan to `docs/plans/`, implements with TDD, verifies end-to-end, documents in `docs/features/`. |
|
|
95
|
+
| `/fix <bug>` | Reproduce first, root-cause (not symptom-patch), failing test → fix, document Symptom/Root cause/Fix/Verification/Prevention in `docs/fixes/`. |
|
|
96
|
+
| `/decide <choice>` | Numbered ADR with context, options, decision, consequences. Supersede — never delete — old decisions. |
|
|
97
|
+
| `/phase status` | Cross-checks the current phase checklist against reality (do claimed tests actually pass?). |
|
|
98
|
+
| `/phase next <name>` | Phase-gate closeout: verify acceptance criteria, get operator sign-off, open the next phase. |
|
|
99
|
+
|
|
100
|
+
## CLI reference
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
npx eng-skills init [dir] # scaffold (default: current directory)
|
|
104
|
+
--force # overwrite existing files
|
|
105
|
+
--dry-run # print what would happen, write nothing
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Design principles (and their sources)
|
|
109
|
+
|
|
110
|
+
- **"Give Claude a check it can run."** Without a runnable check, "looks done" is the
|
|
111
|
+
agent's only stop signal. The stop-gate is that check for documentation; the
|
|
112
|
+
templates' Verification sections demand evidence (real command output), not
|
|
113
|
+
assertions. — [Claude Code best practices](https://code.claude.com/docs/en/best-practices)
|
|
114
|
+
- **Hooks for zero-exception rules, CLAUDE.md for judgment calls.** Anything that
|
|
115
|
+
must happen every time is a hook; CLAUDE.md carries the judgment-dependent
|
|
116
|
+
conventions. — [Hooks reference](https://code.claude.com/docs/en/hooks)
|
|
117
|
+
- **Research before code.** `/feature` front-loads codebase + web research so the
|
|
118
|
+
plan is grounded in what exists, not what the model remembers.
|
|
119
|
+
- **Keep CLAUDE.md small.** It loads into every session; every line costs context.
|
|
120
|
+
`/onboard` targets ~120 lines. Long-form knowledge goes in `docs/` where it's
|
|
121
|
+
read on demand.
|
|
122
|
+
- **ADRs are agent memory.** Superseded decisions are marked, never deleted — the
|
|
123
|
+
decision log is how session N+1 avoids re-litigating what session N settled.
|
|
124
|
+
|
|
125
|
+
## License
|
|
126
|
+
|
|
127
|
+
MIT
|
package/bin/cli.mjs
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// eng-skills — scaffold battle-tested Claude Code engineering conventions.
|
|
3
|
+
// Usage: npx eng-skills init [dir] [--force] [--dry-run]
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
|
|
8
|
+
const TEMPLATES = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'templates');
|
|
9
|
+
|
|
10
|
+
const argv = process.argv.slice(2);
|
|
11
|
+
const flags = new Set(argv.filter((a) => a.startsWith('--')));
|
|
12
|
+
const positional = argv.filter((a) => !a.startsWith('--'));
|
|
13
|
+
const command = positional[0] || 'init';
|
|
14
|
+
const FORCE = flags.has('--force');
|
|
15
|
+
const DRY = flags.has('--dry-run');
|
|
16
|
+
|
|
17
|
+
if (flags.has('--help') || command === 'help') {
|
|
18
|
+
console.log(`
|
|
19
|
+
eng-skills — scaffold Claude Code engineering conventions into a repo
|
|
20
|
+
|
|
21
|
+
Usage:
|
|
22
|
+
npx eng-skills init [dir] scaffold into dir (default: current directory)
|
|
23
|
+
|
|
24
|
+
Options:
|
|
25
|
+
--force overwrite files that already exist (default: skip them)
|
|
26
|
+
--dry-run show what would be written without writing anything
|
|
27
|
+
--help this message
|
|
28
|
+
|
|
29
|
+
What it sets up:
|
|
30
|
+
CLAUDE.md protocol + TODO(onboard) markers Claude fills in
|
|
31
|
+
docs/ features/ fixes/ decisions/ phases/ plans/ specs/ + templates
|
|
32
|
+
.claude/hooks/ pre-edit reminder, post-edit changelog, stop-gate, bash guard
|
|
33
|
+
.claude/commands/ /onboard /feature /fix /decide /phase
|
|
34
|
+
.claude/settings.json hook wiring (merged into an existing file safely)
|
|
35
|
+
|
|
36
|
+
After installing, open Claude Code in the repo and run /onboard.
|
|
37
|
+
`);
|
|
38
|
+
process.exit(0);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (command !== 'init') {
|
|
42
|
+
console.error(`Unknown command "${command}". Try: npx eng-skills init`);
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const target = path.resolve(positional[1] || '.');
|
|
47
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
48
|
+
|
|
49
|
+
function projectName() {
|
|
50
|
+
try {
|
|
51
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(target, 'package.json'), 'utf8'));
|
|
52
|
+
if (pkg.name) return pkg.name;
|
|
53
|
+
} catch {}
|
|
54
|
+
return path.basename(target);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const VARS = { PROJECT_NAME: projectName(), DATE: today };
|
|
58
|
+
const fill = (s) => s.replace(/\{\{(\w+)\}\}/g, (m, k) => VARS[k] ?? m);
|
|
59
|
+
|
|
60
|
+
const written = [];
|
|
61
|
+
const skipped = [];
|
|
62
|
+
const merged = [];
|
|
63
|
+
|
|
64
|
+
function writeFile(rel, content) {
|
|
65
|
+
const dest = path.join(target, rel);
|
|
66
|
+
if (fs.existsSync(dest) && !FORCE) {
|
|
67
|
+
skipped.push(rel);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
written.push(rel);
|
|
71
|
+
if (DRY) return;
|
|
72
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
73
|
+
fs.writeFileSync(dest, content);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function copyTemplate(srcRel, destRel = srcRel) {
|
|
77
|
+
writeFile(destRel, fill(fs.readFileSync(path.join(TEMPLATES, srcRel), 'utf8')));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Merge our hook wiring into an existing .claude/settings.json without clobbering. */
|
|
81
|
+
function mergeSettings() {
|
|
82
|
+
const rel = path.join('.claude', 'settings.json');
|
|
83
|
+
const dest = path.join(target, rel);
|
|
84
|
+
const ours = JSON.parse(fs.readFileSync(path.join(TEMPLATES, 'claude', 'settings.json'), 'utf8'));
|
|
85
|
+
|
|
86
|
+
if (!fs.existsSync(dest)) {
|
|
87
|
+
writeFile(rel, JSON.stringify(ours, null, 2) + '\n');
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
let existing;
|
|
92
|
+
try {
|
|
93
|
+
existing = JSON.parse(fs.readFileSync(dest, 'utf8'));
|
|
94
|
+
} catch {
|
|
95
|
+
console.error(` ! ${rel} exists but is not valid JSON — leaving it alone.`);
|
|
96
|
+
console.error(` Add the hook wiring manually (see templates in the eng-skills package).`);
|
|
97
|
+
skipped.push(rel);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
existing.hooks ||= {};
|
|
102
|
+
let changed = false;
|
|
103
|
+
for (const [event, entries] of Object.entries(ours.hooks)) {
|
|
104
|
+
existing.hooks[event] ||= [];
|
|
105
|
+
for (const entry of entries) {
|
|
106
|
+
const sig = JSON.stringify(entry.hooks.map((h) => h.command));
|
|
107
|
+
const already = existing.hooks[event].some(
|
|
108
|
+
(e) => JSON.stringify((e.hooks || []).map((h) => h.command)) === sig,
|
|
109
|
+
);
|
|
110
|
+
if (!already) {
|
|
111
|
+
existing.hooks[event].push(entry);
|
|
112
|
+
changed = true;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (changed) {
|
|
117
|
+
merged.push(rel);
|
|
118
|
+
if (!DRY) fs.writeFileSync(dest, JSON.stringify(existing, null, 2) + '\n');
|
|
119
|
+
} else {
|
|
120
|
+
skipped.push(rel + ' (hooks already wired)');
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Append the enforced docs-protocol section to an existing CLAUDE.md. */
|
|
125
|
+
function installClaudeMd() {
|
|
126
|
+
const dest = path.join(target, 'CLAUDE.md');
|
|
127
|
+
const template = fill(fs.readFileSync(path.join(TEMPLATES, 'CLAUDE.md'), 'utf8'));
|
|
128
|
+
|
|
129
|
+
if (!fs.existsSync(dest)) {
|
|
130
|
+
writeFile('CLAUDE.md', template);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
const existing = fs.readFileSync(dest, 'utf8');
|
|
134
|
+
if (existing.includes('Documentation protocol (ENFORCED BY HOOKS)')) {
|
|
135
|
+
skipped.push('CLAUDE.md (protocol section already present)');
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const marker = '## 📋 Documentation protocol (ENFORCED BY HOOKS)';
|
|
139
|
+
const start = template.indexOf(marker);
|
|
140
|
+
const end = template.indexOf('## Phase roadmap');
|
|
141
|
+
const section = template.slice(start, end === -1 ? undefined : end).trimEnd();
|
|
142
|
+
merged.push('CLAUDE.md (appended docs-protocol section)');
|
|
143
|
+
if (!DRY) fs.writeFileSync(dest, existing.trimEnd() + '\n\n' + section + '\n');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function ensureGitignore() {
|
|
147
|
+
const dest = path.join(target, '.gitignore');
|
|
148
|
+
const line = '.claude/.docs-state/';
|
|
149
|
+
const existing = fs.existsSync(dest) ? fs.readFileSync(dest, 'utf8') : '';
|
|
150
|
+
if (existing.includes(line)) return;
|
|
151
|
+
merged.push('.gitignore (+ .claude/.docs-state/)');
|
|
152
|
+
if (!DRY) fs.writeFileSync(dest, existing + (existing && !existing.endsWith('\n') ? '\n' : '') + line + '\n');
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
console.log(`\neng-skills init → ${target}${DRY ? ' (dry run)' : ''}\n`);
|
|
156
|
+
|
|
157
|
+
// Hooks + commands
|
|
158
|
+
for (const f of ['lib.mjs', 'pre-edit.mjs', 'post-edit.mjs', 'stop-gate.mjs', 'guard.mjs']) {
|
|
159
|
+
copyTemplate(path.join('claude', 'hooks', f), path.join('.claude', 'hooks', f));
|
|
160
|
+
}
|
|
161
|
+
for (const f of ['onboard.md', 'feature.md', 'fix.md', 'decide.md', 'phase.md']) {
|
|
162
|
+
copyTemplate(path.join('claude', 'commands', f), path.join('.claude', 'commands', f));
|
|
163
|
+
}
|
|
164
|
+
mergeSettings();
|
|
165
|
+
|
|
166
|
+
// Knowledge base
|
|
167
|
+
copyTemplate(path.join('docs', 'README.md'));
|
|
168
|
+
copyTemplate(path.join('docs', 'CHANGELOG-AUTO.md'));
|
|
169
|
+
for (const f of ['feature.md', 'fix.md', 'decision.md', 'phase.md']) {
|
|
170
|
+
copyTemplate(path.join('docs', '_templates', f));
|
|
171
|
+
}
|
|
172
|
+
copyTemplate(path.join('docs', 'decisions', '0001-record-architecture-decisions.md'));
|
|
173
|
+
copyTemplate(path.join('docs', 'phases', 'phase-0-foundation.md'));
|
|
174
|
+
for (const d of ['features', 'fixes', 'plans', 'specs']) {
|
|
175
|
+
writeFile(path.join('docs', d, '.gitkeep'), '');
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
installClaudeMd();
|
|
179
|
+
ensureGitignore();
|
|
180
|
+
|
|
181
|
+
for (const f of written) console.log(` + ${f}`);
|
|
182
|
+
for (const f of merged) console.log(` ~ ${f}`);
|
|
183
|
+
for (const f of skipped) console.log(` = ${f} (exists, skipped)`);
|
|
184
|
+
|
|
185
|
+
console.log(`
|
|
186
|
+
Done. Next steps:
|
|
187
|
+
1. cd ${path.relative(process.cwd(), target) || '.'} && claude
|
|
188
|
+
2. Run /onboard — Claude researches this codebase and fills in CLAUDE.md.
|
|
189
|
+
3. Build things with /feature, fix things with /fix, record choices with /decide.
|
|
190
|
+
|
|
191
|
+
The stop-gate hook now blocks any turn that changes code without updating docs/.
|
|
192
|
+
Verify hooks are active with /hooks inside Claude Code.
|
|
193
|
+
`);
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "eng-skills",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Scaffold battle-tested Claude Code engineering conventions into any repo: enforced docs protocol (features/fixes/decisions/phases), pre/post/stop hooks, research-first slash commands, and a CLAUDE.md that Claude fills in by reading your codebase.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"claude",
|
|
7
|
+
"claude-code",
|
|
8
|
+
"ai",
|
|
9
|
+
"agent",
|
|
10
|
+
"scaffold",
|
|
11
|
+
"adr",
|
|
12
|
+
"documentation",
|
|
13
|
+
"hooks",
|
|
14
|
+
"best-practices"
|
|
15
|
+
],
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"author": "Vidit Parashar <viditsharma818@gmail.com>",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/viditparashar96/eng-skills.git"
|
|
21
|
+
},
|
|
22
|
+
"bugs": "https://github.com/viditparashar96/eng-skills/issues",
|
|
23
|
+
"homepage": "https://github.com/viditparashar96/eng-skills#readme",
|
|
24
|
+
"type": "module",
|
|
25
|
+
"bin": {
|
|
26
|
+
"eng-skills": "bin/cli.mjs"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"bin",
|
|
30
|
+
"templates",
|
|
31
|
+
"README.md"
|
|
32
|
+
],
|
|
33
|
+
"engines": {
|
|
34
|
+
"node": ">=18"
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"test": "node test/smoke.mjs"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# {{PROJECT_NAME}}
|
|
2
|
+
|
|
3
|
+
<!-- TODO(onboard): One-paragraph mission. State the defining constraint of this
|
|
4
|
+
project and point at the authoritative requirement sources. Run /onboard to have
|
|
5
|
+
Claude research the codebase and fill every TODO(onboard) in this file. -->
|
|
6
|
+
|
|
7
|
+
> ⚠️ <!-- TODO(onboard): the single biggest risk/constraint contributors must never violate. -->
|
|
8
|
+
|
|
9
|
+
## Stack
|
|
10
|
+
|
|
11
|
+
<!-- TODO(onboard): One bullet per package/app/module, plus an infra line
|
|
12
|
+
(DB, queues, docker). Keep it to what an agent needs to navigate, not a sales pitch. -->
|
|
13
|
+
|
|
14
|
+
## Commands
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
# TODO(onboard): the canonical dev commands — install, run, test, lint, migrate.
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Hard invariants (NEVER violate)
|
|
21
|
+
|
|
22
|
+
<!-- TODO(onboard): a numbered list of absolute rules, e.g. security constraints,
|
|
23
|
+
"never edit generated files", "all writes go through X". Keep them few and bold. -->
|
|
24
|
+
|
|
25
|
+
## 📋 Documentation protocol (ENFORCED BY HOOKS)
|
|
26
|
+
|
|
27
|
+
This repo runs three hooks (`.claude/hooks/`, configured in `.claude/settings.json`):
|
|
28
|
+
|
|
29
|
+
- **pre-edit** (PreToolUse): reminds about this protocol on the first code edit of a turn.
|
|
30
|
+
- **post-edit** (PostToolUse): appends every tracked edit to `docs/CHANGELOG-AUTO.md` (never hand-edit that file).
|
|
31
|
+
- **stop-gate** (Stop): **blocks the turn from ending** if you changed source files without updating `docs/`.
|
|
32
|
+
|
|
33
|
+
**Therefore, every turn that touches code MUST also update docs.**
|
|
34
|
+
|
|
35
|
+
| You did… | Write/Update |
|
|
36
|
+
|----------|--------------|
|
|
37
|
+
| Implemented a feature/module | `docs/features/<phase>-<name>.md` (template: `docs/_templates/feature.md`) |
|
|
38
|
+
| Fixed a bug | `docs/fixes/YYYY-MM-DD-<slug>.md` (template: `fix.md`) |
|
|
39
|
+
| Made an architectural/trade-off decision | `docs/decisions/NNNN-<title>.md` (template: `decision.md`) |
|
|
40
|
+
| Any code-touching turn | tick/append progress in the **current** `docs/phases/phase-N-*.md` |
|
|
41
|
+
|
|
42
|
+
Notes:
|
|
43
|
+
- `docs/CHANGELOG-AUTO.md` is machine-written — never edit it by hand.
|
|
44
|
+
- If the Stop hook blocks you, it's working as designed: write the missing doc, then finish.
|
|
45
|
+
- If hooks don't fire at all, the workspace may be untrusted — verify with `/hooks`.
|
|
46
|
+
|
|
47
|
+
## Phase roadmap
|
|
48
|
+
|
|
49
|
+
<!-- TODO(onboard): bullet list of phases, one marked `← CURRENT`, each pointing
|
|
50
|
+
at its docs/phases/phase-N-*.md. Pause for operator review at each phase boundary. -->
|
|
51
|
+
|
|
52
|
+
- Phase 0 — foundation ← CURRENT (`docs/phases/phase-0-foundation.md`)
|
|
53
|
+
|
|
54
|
+
## Conventions
|
|
55
|
+
|
|
56
|
+
- Research before building: for any non-trivial feature, use `/feature` — it researches the codebase (and the web when useful) and writes a short plan before writing code.
|
|
57
|
+
- TDD for logic; build/boot verification for scaffolding.
|
|
58
|
+
- Frequent, small conventional commits (`feat:` / `fix:` / `docs:` / `chore:`), one logical change each.
|
|
59
|
+
- Small focused files; generated contracts are regenerated, never hand-copied.
|
|
60
|
+
- Record trade-off decisions as ADRs in `docs/decisions/` (use `/decide`).
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Record an Architecture Decision Record (ADR) in docs/decisions/
|
|
3
|
+
argument-hint: <decision to record>
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Record an ADR for: **$ARGUMENTS**
|
|
7
|
+
|
|
8
|
+
1. Find the next number: list `docs/decisions/` and take the highest `NNNN` + 1 (zero-padded to 4 digits).
|
|
9
|
+
2. If the decision hasn't been made yet: lay out the real options with honest pros/cons, recommend one, and ask the user to choose before writing `Status: accepted`.
|
|
10
|
+
3. Write `docs/decisions/NNNN-<slug>.md` from `docs/_templates/decision.md` — Context, Options considered, Decision, Consequences.
|
|
11
|
+
4. If this supersedes an earlier ADR, update that ADR's status line to `superseded by [[NNNN]]`.
|
|
12
|
+
5. Link the ADR from the current `docs/phases/` doc's notes section.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Research-first feature workflow — research, plan, implement, verify, document
|
|
3
|
+
argument-hint: <feature description>
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Build the following feature using the research-first workflow: **$ARGUMENTS**
|
|
7
|
+
|
|
8
|
+
## 1. Research (before any code)
|
|
9
|
+
|
|
10
|
+
- Search the codebase for everything the feature touches: existing modules to reuse, conventions to follow, contracts (API schemas, types, DB models) to extend.
|
|
11
|
+
- Read the relevant docs: `docs/features/` for related features, `docs/decisions/` for constraints already decided, the current `docs/phases/` doc.
|
|
12
|
+
- If the feature involves an external library/API or a non-obvious technique, do a web search for current best practice — do not rely on memory for API details.
|
|
13
|
+
- If requirements are ambiguous in a way that changes the design, ask the user now, not after implementing.
|
|
14
|
+
|
|
15
|
+
## 2. Plan
|
|
16
|
+
|
|
17
|
+
Write a short plan to `docs/plans/YYYY-MM-DD-<slug>.md`: goal, approach, files to create/change as checkboxes (`- [ ]`), test strategy. For a small feature this can be 15 lines; don't pad it. If the plan involves a real trade-off decision, record it as an ADR (`/decide`).
|
|
18
|
+
|
|
19
|
+
## 3. Implement
|
|
20
|
+
|
|
21
|
+
Work through the plan's checkboxes, ticking them as you go. TDD for logic. Follow the codebase's existing style, not your defaults.
|
|
22
|
+
|
|
23
|
+
## 4. Verify
|
|
24
|
+
|
|
25
|
+
Run the project's tests and build/typecheck. For user-facing behavior, exercise the feature end-to-end (run the app, hit the endpoint, drive the UI) — not just tests. Paste real output as evidence.
|
|
26
|
+
|
|
27
|
+
## 5. Document (required — the Stop hook enforces this)
|
|
28
|
+
|
|
29
|
+
- Write `docs/features/<phase>-<slug>.md` from `docs/_templates/feature.md`.
|
|
30
|
+
- Tick progress in the current `docs/phases/phase-N-*.md` with a dated entry.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Root-cause a bug, fix it, prove the fix, and document it
|
|
3
|
+
argument-hint: <bug description or error output>
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Fix the following bug: **$ARGUMENTS**
|
|
7
|
+
|
|
8
|
+
## 1. Reproduce & root-cause
|
|
9
|
+
|
|
10
|
+
Reproduce the bug first (failing test, curl, script). Then find the *root cause* — trace the actual failure, don't patch the symptom. Check `docs/fixes/` for related past fixes before starting; the same class of bug may have history.
|
|
11
|
+
|
|
12
|
+
## 2. Fix
|
|
13
|
+
|
|
14
|
+
Write a failing test that captures the bug, then make it pass with the smallest correct change. If the root cause reveals a design flaw, note it — fix the bug now, record the design issue as an ADR or follow-up rather than scope-creeping.
|
|
15
|
+
|
|
16
|
+
## 3. Verify
|
|
17
|
+
|
|
18
|
+
Run the reproduction again and the full relevant test suite. Paste real output.
|
|
19
|
+
|
|
20
|
+
## 4. Document (required — the Stop hook enforces this)
|
|
21
|
+
|
|
22
|
+
Write `docs/fixes/YYYY-MM-DD-<slug>.md` from `docs/_templates/fix.md` — Symptom, Root cause, Fix, Verification, Prevention. Tick the current `docs/phases/` doc.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Research this codebase and fill in CLAUDE.md + seed the docs knowledge base
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
You are onboarding this repository into the eng-skills workflow. The scaffold left
|
|
6
|
+
`TODO(onboard)` markers in `CLAUDE.md`. Your job is to replace every one of them
|
|
7
|
+
with researched, accurate content — not guesses.
|
|
8
|
+
|
|
9
|
+
## Steps
|
|
10
|
+
|
|
11
|
+
1. **Research the codebase thoroughly.** Read the manifest files (package.json,
|
|
12
|
+
pyproject.toml, go.mod, Cargo.toml, …), lockfiles' package manager, CI config,
|
|
13
|
+
docker/compose files, existing README/docs, and the top-level source layout.
|
|
14
|
+
For monorepos, map every package/app. Identify: tech stack, how to install/run/
|
|
15
|
+
test/lint, architecture boundaries, generated-vs-source files, and anything
|
|
16
|
+
dangerous (migrations, deploy scripts, secrets handling).
|
|
17
|
+
|
|
18
|
+
2. **Fill in `CLAUDE.md`.** Replace each `TODO(onboard)` marker:
|
|
19
|
+
- Mission paragraph: what the project is and its defining constraint.
|
|
20
|
+
- Warning callout: the single biggest thing an agent must never break.
|
|
21
|
+
- Stack: one bullet per package/module + infra line.
|
|
22
|
+
- Commands: the *canonical* dev commands, verified against the manifests (don't invent scripts that don't exist).
|
|
23
|
+
- Hard invariants: 3–7 absolute rules derived from what you found (e.g. "never edit `src/generated/**`").
|
|
24
|
+
- Phase roadmap: if the project has a clear direction (roadmap file, milestones, TODOs), sketch phases; otherwise leave Phase 0 as CURRENT.
|
|
25
|
+
Keep CLAUDE.md under ~120 lines — it is loaded into every session; every line costs context.
|
|
26
|
+
|
|
27
|
+
3. **Seed the knowledge base.** If this is an existing codebase (not a fresh repo):
|
|
28
|
+
- Write `docs/decisions/0002-<slug>.md`-style ADRs for 2–4 major *existing* architectural decisions you can infer (framework choice, data layer, auth approach) — mark them `accepted`, dated today, noting they were recorded retroactively.
|
|
29
|
+
- Update `docs/phases/phase-0-foundation.md`: tick the onboard task, add a dated log entry.
|
|
30
|
+
|
|
31
|
+
4. **Verify.** Run the test/build command you documented in Commands to confirm it actually works. If it fails, document the real state honestly rather than an aspirational one.
|
|
32
|
+
|
|
33
|
+
5. **Report.** Summarize what you filled in and anything you were unsure about so the human can correct it.
|
|
34
|
+
|
|
35
|
+
Do NOT modify source code during onboarding — this is a documentation-only pass.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Review current phase progress, or close it out and open the next one
|
|
3
|
+
argument-hint: [status | next <name>]
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Phase management: **$ARGUMENTS**
|
|
7
|
+
|
|
8
|
+
- **`status`** (or no argument): read the current `docs/phases/phase-N-*.md`, cross-check its checkboxes against reality (do the features/tests it claims actually exist and pass?), and report honest progress: done, in flight, blocked.
|
|
9
|
+
- **`next <name>`**: close out the current phase — verify all acceptance criteria are ticked and true (run the tests), mark it `complete`, then create `docs/phases/phase-N+1-<name>.md` from `docs/_templates/phase.md` and update the Phase roadmap in `CLAUDE.md` to move the `← CURRENT` marker. Phase boundaries are review gates: summarize what shipped and ask the operator to confirm before starting work in the new phase.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// PreToolUse (Bash): deny obviously destructive commands and secret access.
|
|
2
|
+
// Hooks fire even under --dangerously-skip-permissions, so this holds in
|
|
3
|
+
// unattended runs. Deny-list only — everything else passes through untouched.
|
|
4
|
+
import { readEvent } from './lib.mjs';
|
|
5
|
+
|
|
6
|
+
const ev = readEvent();
|
|
7
|
+
const cmd = String(ev.tool_input?.command || '');
|
|
8
|
+
|
|
9
|
+
const RULES = [
|
|
10
|
+
[/\brm\s+(-[a-z]*[rf][a-z]*\s+)+.*(\/|\*|~|\$HOME)/i, 'recursive/forced rm on a broad path'],
|
|
11
|
+
[/\bgit\s+push\s+.*--force(?!-with-lease)/, 'force push (use --force-with-lease if truly needed)'],
|
|
12
|
+
[/\bgit\s+reset\s+--hard\b/, 'hard reset discards uncommitted work — stash first or ask the user'],
|
|
13
|
+
[/\bDROP\s+(TABLE|DATABASE|SCHEMA)\b/i, 'destructive SQL'],
|
|
14
|
+
[/(^|[\s/])\.env(\.[\w.]+)?\b.*(cat|cp|curl|nc |mail|post)|((cat|cp|curl)\s+[^|;]*\.env\b)/i, 'reading/exfiltrating .env secrets via shell'],
|
|
15
|
+
[/~\/\.(ssh|aws|gnupg)\b/, 'accessing credential directories'],
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
for (const [re, why] of RULES) {
|
|
19
|
+
if (re.test(cmd)) {
|
|
20
|
+
process.stdout.write(
|
|
21
|
+
JSON.stringify({
|
|
22
|
+
hookSpecificOutput: {
|
|
23
|
+
hookEventName: 'PreToolUse',
|
|
24
|
+
permissionDecision: 'deny',
|
|
25
|
+
permissionDecisionReason: `Blocked by eng-skills guard: ${why}. If this is genuinely needed, ask the user to run it themselves or adjust .claude/hooks/guard.mjs.`,
|
|
26
|
+
},
|
|
27
|
+
}),
|
|
28
|
+
);
|
|
29
|
+
process.exit(0);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
process.exit(0);
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// Shared helpers for eng-skills hooks. Zero dependencies — plain Node.
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { execFileSync } from 'node:child_process';
|
|
5
|
+
|
|
6
|
+
export const PROJECT_DIR = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
7
|
+
export const STATE_DIR = path.join(PROJECT_DIR, '.claude', '.docs-state');
|
|
8
|
+
|
|
9
|
+
/** Parse the hook event JSON that Claude Code pipes to stdin. */
|
|
10
|
+
export function readEvent() {
|
|
11
|
+
try {
|
|
12
|
+
return JSON.parse(fs.readFileSync(0, 'utf8'));
|
|
13
|
+
} catch {
|
|
14
|
+
return {};
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Repo-relative path for display. */
|
|
19
|
+
export function relPath(fp) {
|
|
20
|
+
if (!fp) return '';
|
|
21
|
+
const abs = path.resolve(fp);
|
|
22
|
+
const root = path.resolve(PROJECT_DIR) + path.sep;
|
|
23
|
+
return abs.startsWith(root) ? abs.slice(root.length) : fp;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Classify an edited file:
|
|
28
|
+
* - 'ignored' — infra noise that should count as neither code nor docs
|
|
29
|
+
* - 'docs' — the tracked knowledge base (docs/**, CLAUDE.md)
|
|
30
|
+
* - 'code' — everything else
|
|
31
|
+
*/
|
|
32
|
+
export function classify(fp) {
|
|
33
|
+
const rel = relPath(fp).replaceAll('\\', '/');
|
|
34
|
+
if (!rel) return 'ignored';
|
|
35
|
+
const base = path.basename(rel);
|
|
36
|
+
|
|
37
|
+
if (
|
|
38
|
+
rel.startsWith('.git/') ||
|
|
39
|
+
rel.startsWith('.claude/') ||
|
|
40
|
+
rel.includes('node_modules/') ||
|
|
41
|
+
rel === 'docs/CHANGELOG-AUTO.md' ||
|
|
42
|
+
base.startsWith('.')
|
|
43
|
+
) {
|
|
44
|
+
return 'ignored';
|
|
45
|
+
}
|
|
46
|
+
// Agent docs/config counts as documentation.
|
|
47
|
+
if (rel.startsWith('docs/') || base === 'CLAUDE.md') return 'docs';
|
|
48
|
+
return 'code';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const DEFAULT_STATE = { codeChanged: false, docsChanged: false, reminded: false };
|
|
52
|
+
|
|
53
|
+
function stateFile(sessionId) {
|
|
54
|
+
const sid = String(sessionId || 'unknown').slice(0, 16);
|
|
55
|
+
return path.join(STATE_DIR, `${sid}.json`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function readState(sessionId) {
|
|
59
|
+
try {
|
|
60
|
+
return { ...DEFAULT_STATE, ...JSON.parse(fs.readFileSync(stateFile(sessionId), 'utf8')) };
|
|
61
|
+
} catch {
|
|
62
|
+
return { ...DEFAULT_STATE };
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function writeState(sessionId, state) {
|
|
67
|
+
try {
|
|
68
|
+
fs.mkdirSync(STATE_DIR, { recursive: true });
|
|
69
|
+
fs.writeFileSync(stateFile(sessionId), JSON.stringify(state));
|
|
70
|
+
} catch {
|
|
71
|
+
// State is best-effort; never break the agent over it.
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Best-effort "+added/-deleted" git diff stat for one file. */
|
|
76
|
+
export function diffStat(fp) {
|
|
77
|
+
const rel = relPath(fp);
|
|
78
|
+
try {
|
|
79
|
+
const run = (args) =>
|
|
80
|
+
execFileSync('git', args, { cwd: PROJECT_DIR, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
|
|
81
|
+
const out = run(['diff', '--numstat', '--', rel]) || run(['diff', '--cached', '--numstat', '--', rel]);
|
|
82
|
+
const m = out.trim().split('\n')[0]?.match(/^(\d+|-)\s+(\d+|-)/);
|
|
83
|
+
if (m) return `+${m[1]}/-${m[2]}`;
|
|
84
|
+
// Untracked file — no diff yet.
|
|
85
|
+
const status = run(['status', '--porcelain', '--', rel]);
|
|
86
|
+
if (status.startsWith('??')) return 'new/?';
|
|
87
|
+
return '?/?';
|
|
88
|
+
} catch {
|
|
89
|
+
return '?/?';
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// PostToolUse (Edit|Write|MultiEdit): append every tracked edit to
|
|
2
|
+
// docs/CHANGELOG-AUTO.md and record code/docs activity for the Stop gate.
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { readEvent, readState, writeState, classify, relPath, diffStat, PROJECT_DIR } from './lib.mjs';
|
|
6
|
+
|
|
7
|
+
const ev = readEvent();
|
|
8
|
+
const fp = ev.tool_input?.file_path;
|
|
9
|
+
const kind = classify(fp);
|
|
10
|
+
|
|
11
|
+
if (kind !== 'ignored') {
|
|
12
|
+
const logFile = path.join(PROJECT_DIR, 'docs', 'CHANGELOG-AUTO.md');
|
|
13
|
+
try {
|
|
14
|
+
const row = `| ${new Date().toISOString()} | ${String(ev.session_id || '').slice(0, 8)} | ${
|
|
15
|
+
ev.tool_name || '?'
|
|
16
|
+
} | \`${relPath(fp)}\` | ${diffStat(fp)} |\n`;
|
|
17
|
+
fs.mkdirSync(path.dirname(logFile), { recursive: true });
|
|
18
|
+
fs.appendFileSync(logFile, row);
|
|
19
|
+
} catch {
|
|
20
|
+
// Logging is best-effort.
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const st = readState(ev.session_id);
|
|
24
|
+
if (kind === 'code') st.codeChanged = true;
|
|
25
|
+
if (kind === 'docs') st.docsChanged = true;
|
|
26
|
+
writeState(ev.session_id, st);
|
|
27
|
+
}
|
|
28
|
+
process.exit(0);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// PreToolUse (Edit|Write|MultiEdit): on the first code edit of a turn,
|
|
2
|
+
// remind Claude about the docs protocol. Never blocks.
|
|
3
|
+
import { readEvent, readState, writeState, classify } from './lib.mjs';
|
|
4
|
+
|
|
5
|
+
const ev = readEvent();
|
|
6
|
+
const fp = ev.tool_input?.file_path;
|
|
7
|
+
if (classify(fp) === 'code') {
|
|
8
|
+
const st = readState(ev.session_id);
|
|
9
|
+
if (!st.reminded) {
|
|
10
|
+
st.reminded = true;
|
|
11
|
+
writeState(ev.session_id, st);
|
|
12
|
+
process.stdout.write(
|
|
13
|
+
JSON.stringify({
|
|
14
|
+
hookSpecificOutput: {
|
|
15
|
+
hookEventName: 'PreToolUse',
|
|
16
|
+
additionalContext:
|
|
17
|
+
'Docs protocol: you are editing source code this turn. Before ending the turn, create/update the matching entry under docs/ (features/, fixes/, or decisions/ — templates in docs/_templates/) and tick progress in the current docs/phases/ doc. The Stop hook will block the turn otherwise.',
|
|
18
|
+
},
|
|
19
|
+
}),
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
process.exit(0);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Stop: block the turn from ending if source files changed but docs/ did not.
|
|
2
|
+
// This is the enforcement half of the docs protocol in CLAUDE.md.
|
|
3
|
+
import { readEvent, readState, writeState } from './lib.mjs';
|
|
4
|
+
|
|
5
|
+
const ev = readEvent();
|
|
6
|
+
|
|
7
|
+
// Loop guard: if we already blocked once this turn, let it end.
|
|
8
|
+
if (ev.stop_hook_active) process.exit(0);
|
|
9
|
+
|
|
10
|
+
const st = readState(ev.session_id);
|
|
11
|
+
|
|
12
|
+
if (st.codeChanged && !st.docsChanged) {
|
|
13
|
+
process.stdout.write(
|
|
14
|
+
JSON.stringify({
|
|
15
|
+
decision: 'block',
|
|
16
|
+
reason:
|
|
17
|
+
'You changed source files this turn but did not update docs/. Per the CLAUDE.md docs protocol: create or update the matching entry (docs/features/ for a feature, docs/fixes/ for a bug fix, docs/decisions/ for an architectural decision — templates in docs/_templates/) AND tick progress in the current docs/phases/ doc, then finish.',
|
|
18
|
+
}),
|
|
19
|
+
);
|
|
20
|
+
process.exit(0);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Clean turn — reset state for the next one.
|
|
24
|
+
writeState(ev.session_id, { codeChanged: false, docsChanged: false, reminded: false });
|
|
25
|
+
process.exit(0);
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"PreToolUse": [
|
|
4
|
+
{
|
|
5
|
+
"matcher": "Edit|Write|MultiEdit",
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"type": "command",
|
|
9
|
+
"command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/pre-edit.mjs\""
|
|
10
|
+
}
|
|
11
|
+
]
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"matcher": "Bash",
|
|
15
|
+
"hooks": [
|
|
16
|
+
{
|
|
17
|
+
"type": "command",
|
|
18
|
+
"command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/guard.mjs\""
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
}
|
|
22
|
+
],
|
|
23
|
+
"PostToolUse": [
|
|
24
|
+
{
|
|
25
|
+
"matcher": "Edit|Write|MultiEdit",
|
|
26
|
+
"hooks": [
|
|
27
|
+
{
|
|
28
|
+
"type": "command",
|
|
29
|
+
"command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/post-edit.mjs\""
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
],
|
|
34
|
+
"Stop": [
|
|
35
|
+
{
|
|
36
|
+
"hooks": [
|
|
37
|
+
{
|
|
38
|
+
"type": "command",
|
|
39
|
+
"command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/stop-gate.mjs\""
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
]
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# CHANGELOG-AUTO
|
|
2
|
+
|
|
3
|
+
> ⚠️ **Machine-written file.** Every row below is appended automatically by the
|
|
4
|
+
> post-edit hook (`.claude/hooks/post-edit.mjs`) whenever Claude Code edits a
|
|
5
|
+
> tracked file. Never edit this file by hand.
|
|
6
|
+
|
|
7
|
+
| timestamp (UTC) | session | tool | file | ±lines |
|
|
8
|
+
|---|---|---|---|---|
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# docs/ — the project knowledge base
|
|
2
|
+
|
|
3
|
+
This directory is the project's tracked knowledge base. It is **enforced by hooks**:
|
|
4
|
+
you cannot end a Claude Code turn that changed source code without updating
|
|
5
|
+
something here.
|
|
6
|
+
|
|
7
|
+
| Path | What it is | Who writes it |
|
|
8
|
+
|------|------------|---------------|
|
|
9
|
+
| `features/` | One doc per implemented feature (`<phase>-<name>.md`) | you (Claude) |
|
|
10
|
+
| `fixes/` | One doc per bug fix (`YYYY-MM-DD-<slug>.md`) | you (Claude) |
|
|
11
|
+
| `decisions/` | Architecture Decision Records (`NNNN-<title>.md`) | you (Claude) |
|
|
12
|
+
| `phases/` | Living per-phase progress checklists (`phase-N-<name>.md`) | you (Claude) |
|
|
13
|
+
| `plans/` | Implementation plans (`YYYY-MM-DD-<name>.md`) | planning workflow |
|
|
14
|
+
| `specs/` | Approved design specs (`YYYY-MM-DD-<name>-design.md`) | design workflow |
|
|
15
|
+
| `_templates/` | Copy-from templates for all of the above | eng-skills (don't edit casually) |
|
|
16
|
+
| `CHANGELOG-AUTO.md` | Append-only, machine-written edit log | the post-edit hook (**never by hand**) |
|
|
17
|
+
|
|
18
|
+
Cross-link docs with `[[wiki-style]]` links so phases ↔ features ↔ fixes ↔ decisions
|
|
19
|
+
stay navigable.
|
|
20
|
+
|
|
21
|
+
**The rule:** after implementing or fixing anything, write the matching doc
|
|
22
|
+
**before the turn ends.**
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# NNNN — <title>
|
|
2
|
+
|
|
3
|
+
**Date:** YYYY-MM-DD · **Status:** proposed | accepted | superseded by [[NNNN]]
|
|
4
|
+
|
|
5
|
+
## Context
|
|
6
|
+
<The forces at play: requirements, constraints, what problem this decides.>
|
|
7
|
+
|
|
8
|
+
## Options considered
|
|
9
|
+
1. **<Option A>** — pros / cons
|
|
10
|
+
2. **<Option B>** — pros / cons
|
|
11
|
+
3. **<Option C>** — pros / cons
|
|
12
|
+
|
|
13
|
+
## Decision
|
|
14
|
+
<The choice made.>
|
|
15
|
+
|
|
16
|
+
## Consequences
|
|
17
|
+
<What becomes easier/harder. Follow-ups. What this commits us to.>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Feature — <name>
|
|
2
|
+
|
|
3
|
+
**Phase:** <N> · **Date:** YYYY-MM-DD · **Status:** implemented | partial
|
|
4
|
+
|
|
5
|
+
## What it does
|
|
6
|
+
<One-paragraph summary of the capability.>
|
|
7
|
+
|
|
8
|
+
## How to use it
|
|
9
|
+
<Entry points: endpoints, events, functions, CLI. Example request/payload.>
|
|
10
|
+
|
|
11
|
+
## Contract touchpoints
|
|
12
|
+
<APIs, events, DB models, or types this feature touches.>
|
|
13
|
+
|
|
14
|
+
## Dependencies
|
|
15
|
+
<Packages/modules/services this relies on.>
|
|
16
|
+
|
|
17
|
+
## Tests
|
|
18
|
+
<Where the tests live and what they assert.>
|
|
19
|
+
|
|
20
|
+
## Notes / follow-ups
|
|
21
|
+
<Known gaps, deferred work, links to related [[docs]].>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Fix — <slug>
|
|
2
|
+
|
|
3
|
+
**Date:** YYYY-MM-DD · **Phase:** <N> · **Severity:** low | med | high
|
|
4
|
+
|
|
5
|
+
## Symptom
|
|
6
|
+
<What was observed. Error messages, repro steps.>
|
|
7
|
+
|
|
8
|
+
## Root cause
|
|
9
|
+
<The actual underlying cause, not just the surface symptom.>
|
|
10
|
+
|
|
11
|
+
## Fix
|
|
12
|
+
<What changed and why it resolves the root cause. Files touched.>
|
|
13
|
+
|
|
14
|
+
## Verification
|
|
15
|
+
<Command(s) run + observed output proving the fix works.>
|
|
16
|
+
|
|
17
|
+
## Prevention
|
|
18
|
+
<Test added / guard added so this can't regress. Links to [[docs]].>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Phase <N> — <name>
|
|
2
|
+
|
|
3
|
+
**Status:** not started | in progress | complete · **Plan:** [[docs/plans/...]]
|
|
4
|
+
|
|
5
|
+
## Goal
|
|
6
|
+
<One sentence.>
|
|
7
|
+
|
|
8
|
+
## Acceptance criteria
|
|
9
|
+
- [ ] <criterion 1>
|
|
10
|
+
- [ ] <criterion 2>
|
|
11
|
+
|
|
12
|
+
## Task progress
|
|
13
|
+
- [ ] Task 1 — <name>
|
|
14
|
+
- [ ] Task 2 — <name>
|
|
15
|
+
|
|
16
|
+
## Notes / decisions made during the phase
|
|
17
|
+
<Running log. Link [[decisions]] and [[features]].>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# 0001 — Record architecture decisions
|
|
2
|
+
|
|
3
|
+
**Date:** {{DATE}} · **Status:** accepted
|
|
4
|
+
|
|
5
|
+
## Context
|
|
6
|
+
This project is built with Claude Code. Agent sessions are ephemeral: context is
|
|
7
|
+
lost between sessions, and the reasoning behind a choice evaporates unless it is
|
|
8
|
+
written down where the next session will find it. We need a durable, low-friction
|
|
9
|
+
record of significant architectural and trade-off decisions.
|
|
10
|
+
|
|
11
|
+
## Options considered
|
|
12
|
+
1. **No records** — zero friction, but every future session (human or agent) re-litigates old decisions and repeats mistakes.
|
|
13
|
+
2. **Decisions embedded in code comments** — close to the code, but scattered, unsearchable as a set, and invisible to planning.
|
|
14
|
+
3. **Architecture Decision Records in `docs/decisions/`** — one numbered file per decision with context, options, decision, consequences. Searchable, linkable, cheap.
|
|
15
|
+
|
|
16
|
+
## Decision
|
|
17
|
+
We record every significant decision as an ADR in `docs/decisions/NNNN-<title>.md`,
|
|
18
|
+
using `docs/_templates/decision.md`. The docs-protocol hooks enforce that decisions
|
|
19
|
+
made while coding get written down in the same turn.
|
|
20
|
+
|
|
21
|
+
## Consequences
|
|
22
|
+
Future sessions can read *why*, not just *what*. Superseded decisions are marked,
|
|
23
|
+
not deleted, preserving history. The set of ADRs becomes the project's long-term
|
|
24
|
+
memory alongside `CLAUDE.md`.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Phase 0 — foundation
|
|
2
|
+
|
|
3
|
+
**Status:** in progress · **Plan:** —
|
|
4
|
+
|
|
5
|
+
## Goal
|
|
6
|
+
Establish the eng-skills workflow in this repo: hooks live, CLAUDE.md filled in, first real work documented.
|
|
7
|
+
|
|
8
|
+
## Acceptance criteria
|
|
9
|
+
- [ ] `/onboard` run — CLAUDE.md has no remaining `TODO(onboard)` markers
|
|
10
|
+
- [ ] Hooks verified firing (check `/hooks` inside Claude Code)
|
|
11
|
+
- [ ] First feature/fix documented under `docs/`
|
|
12
|
+
|
|
13
|
+
## Task progress
|
|
14
|
+
- [x] Task 0 — scaffold installed via `npx eng-skills init` ({{DATE}})
|
|
15
|
+
- [ ] Task 1 — run `/onboard`
|
|
16
|
+
|
|
17
|
+
## Notes / decisions made during the phase
|
|
18
|
+
See [[0001-record-architecture-decisions]].
|