@devshop/crew 0.4.1

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.
@@ -0,0 +1,28 @@
1
+ const crypto = require('crypto');
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+
5
+ function walkSorted(folder) {
6
+ const out = [];
7
+ function walk(dir, rel) {
8
+ for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
9
+ const r = rel ? path.join(rel, e.name) : e.name;
10
+ if (e.isDirectory()) walk(path.join(dir, e.name), r);
11
+ else if (e.isFile()) out.push(r);
12
+ }
13
+ }
14
+ walk(folder, '');
15
+ return out.sort();
16
+ }
17
+
18
+ function hashSkill(folder) {
19
+ const h = crypto.createHash('sha256');
20
+ for (const rel of walkSorted(folder)) {
21
+ h.update(rel + '\n');
22
+ h.update(fs.readFileSync(path.join(folder, rel)));
23
+ h.update('\n');
24
+ }
25
+ return 'sha256-' + h.digest('hex');
26
+ }
27
+
28
+ module.exports = { hashSkill, walkSorted };
@@ -0,0 +1,19 @@
1
+ const isTty = process.stdout.isTTY;
2
+ const color = (code, s) => isTty ? `\x1b[${code}m${s}\x1b[0m` : s;
3
+ const dim = s => color('2', s);
4
+ const red = s => color('31', s);
5
+ const yellow = s => color('33', s);
6
+ const green = s => color('32', s);
7
+
8
+ const pad = s => String(s).padEnd(6);
9
+
10
+ const log = {
11
+ action(verb, target) { console.log(` ${green(pad(verb))} ${target}`); },
12
+ info(msg) { console.log(` ${pad('')} ${msg}`); },
13
+ warn(msg) { console.log(` ${yellow(pad('warn'))} ${msg}`); },
14
+ error(msg) { console.error(` ${red(pad('error'))} ${msg}`); },
15
+ dryRun(verb, target) { console.log(` ${dim('[dry]')} ${pad(verb)} ${target}`); },
16
+ plain(msg) { console.log(msg); }
17
+ };
18
+
19
+ module.exports = log;
@@ -0,0 +1,79 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const { hashSkill } = require('./hash');
4
+
5
+ const PACKAGE_NAME = '@devshop/crew';
6
+ const SCHEMA_VERSION = 1;
7
+
8
+ function manifestPath(skillsDir) {
9
+ return path.join(skillsDir, '.skills-manifest.json');
10
+ }
11
+
12
+ function readManifest(skillsDir) {
13
+ const p = manifestPath(skillsDir);
14
+ if (!fs.existsSync(p)) return null;
15
+ try {
16
+ return JSON.parse(fs.readFileSync(p, 'utf8'));
17
+ } catch (e) {
18
+ const err = new Error(`Manifest corrupt: ${p}`);
19
+ err.exitCode = 4;
20
+ throw err;
21
+ }
22
+ }
23
+
24
+ function writeManifest(skillsDir, manifest) {
25
+ fs.mkdirSync(skillsDir, { recursive: true });
26
+ fs.writeFileSync(manifestPath(skillsDir), JSON.stringify(manifest, null, 2) + '\n');
27
+ }
28
+
29
+ function emptyManifest(scope, packageVersion) {
30
+ const now = new Date().toISOString();
31
+ return {
32
+ schema_version: SCHEMA_VERSION,
33
+ package: PACKAGE_NAME,
34
+ package_version: packageVersion,
35
+ scope,
36
+ installed_at: now,
37
+ updated_at: now,
38
+ skills: {}
39
+ };
40
+ }
41
+
42
+ function listDirSkills(dir, { includeHidden = false } = {}) {
43
+ if (!fs.existsSync(dir)) return [];
44
+ return fs.readdirSync(dir, { withFileTypes: true })
45
+ .filter(e => e.isDirectory() && (includeHidden || !e.name.startsWith('.')))
46
+ .map(e => e.name)
47
+ .sort();
48
+ }
49
+
50
+ function diffSkills(packageSkillsDir, target, manifest) {
51
+ const pkgSkills = new Set(listDirSkills(packageSkillsDir));
52
+ const diskSkills = new Set(listDirSkills(target));
53
+ const mfSkills = new Set(Object.keys(manifest.skills || {}));
54
+ const all = [...new Set([...pkgSkills, ...diskSkills, ...mfSkills])].sort();
55
+ return all.map(name => {
56
+ const inPkg = pkgSkills.has(name);
57
+ const inDisk = diskSkills.has(name);
58
+ const mfEntry = manifest.skills[name];
59
+ const pkgHash = inPkg ? hashSkill(path.join(packageSkillsDir, name)) : null;
60
+ const diskHash = inDisk ? hashSkill(path.join(target, name)) : null;
61
+ const mfHash = mfEntry ? mfEntry.hash : null;
62
+ let state;
63
+ if (!inDisk) state = 'missing';
64
+ else if (!mfEntry) state = 'foreign';
65
+ else if (diskHash === mfHash) state = 'managed-unchanged';
66
+ else state = 'managed-edited';
67
+ return { name, state, pkgHash, diskHash, mfHash, inPkg, inDisk };
68
+ });
69
+ }
70
+
71
+ module.exports = {
72
+ PACKAGE_NAME,
73
+ SCHEMA_VERSION,
74
+ manifestPath,
75
+ readManifest,
76
+ writeManifest,
77
+ emptyManifest,
78
+ diffSkills
79
+ };
@@ -0,0 +1,35 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const os = require('os');
4
+
5
+ const MARKERS = ['package.json', '.git', 'CLAUDE.md', 'pyproject.toml', 'Cargo.toml', 'go.mod'];
6
+
7
+ function hasProjectMarkers(dir) {
8
+ return MARKERS.some(m => fs.existsSync(path.join(dir, m)));
9
+ }
10
+
11
+ function resolveTarget(flags) {
12
+ if (flags.global) {
13
+ return {
14
+ skillsDir: path.join(os.homedir(), '.claude', 'skills'),
15
+ claudeMdPath: null,
16
+ scope: 'global'
17
+ };
18
+ }
19
+ const cwd = process.cwd();
20
+ if (cwd === os.homedir() || !hasProjectMarkers(cwd)) {
21
+ const err = new Error(
22
+ 'Refusing project install: no project markers in cwd. ' +
23
+ 'Pass --global to install into ~/.claude/skills, or run inside a project.'
24
+ );
25
+ err.exitCode = 2;
26
+ throw err;
27
+ }
28
+ return {
29
+ skillsDir: path.join(cwd, '.claude', 'skills'),
30
+ claudeMdPath: path.join(cwd, 'CLAUDE.md'),
31
+ scope: 'project'
32
+ };
33
+ }
34
+
35
+ module.exports = { resolveTarget, hasProjectMarkers, MARKERS };
@@ -0,0 +1,40 @@
1
+ const readline = require('readline');
2
+
3
+ function chooseEditAction(skillName) {
4
+ return new Promise((resolve) => {
5
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
6
+ const ask = () => {
7
+ console.log(`The skill '${skillName}' has local edits.`);
8
+ console.log(' (b) backup edits and replace [default]');
9
+ console.log(' (k) keep edits, skip update');
10
+ console.log(' (r) replace, discard edits');
11
+ rl.question('> ', (answer) => {
12
+ const a = (answer || '').trim().toLowerCase();
13
+ if (a === '' || a === 'b') { rl.close(); resolve('backup'); }
14
+ else if (a === 'k') { rl.close(); resolve('keep'); }
15
+ else if (a === 'r') { rl.close(); resolve('replace'); }
16
+ else ask();
17
+ });
18
+ };
19
+ ask();
20
+ });
21
+ }
22
+
23
+ function confirmAbsorbForeign(skillNames) {
24
+ return new Promise((resolve) => {
25
+ console.log('');
26
+ console.log('The following skills already exist in .claude/skills/ but are not tracked by crew:');
27
+ console.log('');
28
+ for (const n of skillNames) console.log(` - ${n}`);
29
+ console.log('');
30
+ console.log('Override and absorb them? Originals will be backed up to .claude/skills/.bak/<utc>/.');
31
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
32
+ rl.question('[y/N] > ', (answer) => {
33
+ rl.close();
34
+ const a = (answer || '').trim().toLowerCase();
35
+ resolve(a === 'y' || a === 'yes');
36
+ });
37
+ });
38
+ }
39
+
40
+ module.exports = { chooseEditAction, confirmAbsorbForeign };
@@ -0,0 +1,353 @@
1
+ ---
2
+ name: adjust
3
+ description: Onboards a project for the agentic workflow — scans the project structure, detects tools and commands, validates them, and writes the Workflow Config section in CLAUDE.md. Use when the user invokes /adjust.
4
+ ---
5
+
6
+ # Adjust
7
+
8
+ ## Role
9
+
10
+ You are a project onboarding engineer. You scan a project's structure, detect its toolchain, validate the commands work, and write the Workflow Config section into CLAUDE.md so all downstream skills can operate.
11
+
12
+ You detect what's there. You don't assume.
13
+
14
+ ## When to Apply
15
+
16
+ Activate when called from the `/adjust` command. Otherwise ignore.
17
+
18
+ ---
19
+
20
+ ## Input Handling
21
+
22
+ `$ARGUMENTS` may be:
23
+
24
+ - **Empty** (most common) — scan the current project and set up config
25
+ - **`update`** — re-scan and update an existing Workflow Config
26
+ - **A specific key** (e.g. `test-cmd`) — update just that config value
27
+
28
+ ---
29
+
30
+ ## Step 1 — Check for Existing Config
31
+
32
+ 1. Read the project's `CLAUDE.md` (if it exists)
33
+ 2. Look for `## Workflow Config` section
34
+ 3. If it exists and `$ARGUMENTS` is empty, ask: "Workflow Config already exists. Do you want to update it or start fresh?"
35
+ 4. If `$ARGUMENTS` is `update` or a specific key, proceed to update mode (see Update Mode below)
36
+
37
+ ---
38
+
39
+ ## Step 2 — Scan the Project
40
+
41
+ Explore the project to detect its toolchain.
42
+
43
+ **Package managers and build tools:**
44
+ - `package.json` → npm/yarn/pnpm (check `packageManager` field and lockfiles: `package-lock.json`, `yarn.lock`, `pnpm-lock.yaml`)
45
+ - `Makefile` / `Justfile` → make/just targets
46
+ - `Cargo.toml` → Rust/cargo
47
+ - `go.mod` → Go
48
+ - `pyproject.toml` / `setup.py` / `requirements.txt` → Python
49
+ - `*.csproj` / `*.sln` → .NET
50
+ - `build.gradle` / `pom.xml` → Java/Kotlin
51
+
52
+ **Test frameworks:**
53
+ - `package.json` scripts: `test`, `test:unit`, `test:e2e`, `test:integration`
54
+ - `vitest.config.*`, `jest.config.*` → Vitest/Jest
55
+ - `playwright.config.*` → Playwright
56
+ - `cypress.config.*` / `cypress/` → Cypress
57
+ - `pytest.ini` / `conftest.py` → pytest
58
+ - `*_test.go` → Go testing
59
+ - `*.test.rs` → Rust testing
60
+
61
+ **Lint/format tools:**
62
+ - `package.json` scripts: `lint`, `format`, `lint:fix`
63
+ - `.eslintrc*` → ESLint
64
+ - `.prettierrc*` → Prettier
65
+ - `biome.json` → Biome
66
+ - `ruff.toml` / `pyproject.toml [tool.ruff]` → Ruff
67
+
68
+ **Build commands:**
69
+ - `package.json` scripts: `build`, `compile`, `typecheck`
70
+ - `tsconfig.json` → TypeScript
71
+ - `next.config.*` → Next.js
72
+ - `vite.config.*` → Vite
73
+
74
+ **E2E frameworks:**
75
+ - `playwright.config.*` → Playwright (`npx playwright test`)
76
+ - `cypress.config.*` → Cypress (`npx cypress run`)
77
+ - `e2e/` or `tests/e2e/` directories
78
+
79
+ ---
80
+
81
+ ## Step 3 — Detect Commands
82
+
83
+ For each Workflow Config key, determine the best command:
84
+
85
+ | Key | Detection Strategy |
86
+ |-----|-------------------|
87
+ | `workflow-dir` | Default `_workflow`. Check if a different workflow directory already exists. |
88
+ | `test-cmd` | Read `package.json` scripts → `test` or `test:unit`. Other ecosystems: `cargo test`, `go test ./...`, `pytest`, `just test`, etc. |
89
+ | `lint-cmd` | Read `package.json` scripts → `lint`. Fall back to `eslint .`, `ruff check`, etc. |
90
+ | `build-cmd` | Read `package.json` scripts → `build` or `typecheck`. Compiled languages: `cargo build`, `go build`, `just build`, etc. |
91
+ | `e2e-cmd` | Detect e2e framework → `npx playwright test`, `npx cypress run`, `pytest tests/e2e/`, etc. |
92
+ | `e2e-framework` | Detect from config files → `playwright`, `cypress`, `jest`, `pytest`, etc. |
93
+ | `tdd` | Default `true` |
94
+ | `branch-prefix` | Default `feature/` |
95
+ | `base-branch` | Detect from git: `git symbolic-ref refs/remotes/origin/HEAD` → extract `main` or `master` |
96
+ | `worktree-layout` | `bare-clone` or `standard`. Default `standard`. If `bare-clone`, the repo uses a bare-clone worktree structure (see Step 6W). |
97
+
98
+ ---
99
+
100
+ ## Step 4 — Validate Commands
101
+
102
+ For each detected command, run it to verify it works:
103
+
104
+ 1. Run `test-cmd` — does it execute? (It's OK if tests fail — we're checking the command works, not the tests)
105
+ 2. Run `lint-cmd` — does it execute?
106
+ 3. Run `build-cmd` — does it execute?
107
+ 4. If `e2e-cmd` is detected, try a dry run if possible (e.g. `npx playwright test --list`)
108
+
109
+ Report results: "Detected and validated: [list]. Failed to validate: [list]."
110
+
111
+ For any command that fails to execute, ask the user: "What command should I use for [purpose]?"
112
+
113
+ ---
114
+
115
+ ## Step 5 — Present Config for Confirmation
116
+
117
+ Present the detected Workflow Config:
118
+
119
+ ```markdown
120
+ ## Workflow Config
121
+
122
+ | Key | Value |
123
+ |-----|-------|
124
+ | workflow-dir | `_workflow` |
125
+ | test-cmd | `npm test` |
126
+ | lint-cmd | `npm run lint` |
127
+ | build-cmd | `npm run build` |
128
+ | e2e-cmd | `npx playwright test` |
129
+ | e2e-framework | `playwright` |
130
+ | tdd | `true` |
131
+ | branch-prefix | `feature/` |
132
+ | base-branch | `main` |
133
+ | worktree-layout | `standard` |
134
+ ```
135
+
136
+ Ask: **"Does this look right? I can change any value."**
137
+
138
+ Wait for user confirmation or adjustments.
139
+
140
+ ---
141
+
142
+ ## Step 6 — Write Config to CLAUDE.md
143
+
144
+ 1. If `CLAUDE.md` doesn't exist, create it with the Workflow Config section
145
+ 2. If `CLAUDE.md` exists but has no Workflow Config, append the section
146
+ 3. If `CLAUDE.md` exists and has a Workflow Config, replace the section (leave all other content untouched)
147
+
148
+ Also ensure the workflow directory exists:
149
+ - If `workflow-dir` doesn't exist, create it
150
+ - If it exists, leave it as-is
151
+
152
+ ---
153
+
154
+ ## Step 7 — Worktree Layout Setup (if `worktree-layout: bare-clone`)
155
+
156
+ If `worktree-layout` is `standard`, skip this step entirely.
157
+
158
+ This step converts a standard git clone into a bare-clone worktree layout, or validates an existing one.
159
+
160
+ **Target structure:**
161
+
162
+ ```
163
+ <project>/
164
+ .bare/ ← bare git repo
165
+ CLAUDE.md ← real file at root (not a symlink)
166
+ .claude/ ← real dir at root (not a symlink)
167
+ .mcp.json ← shared across worktrees
168
+ main/ ← worktree for the base branch
169
+ wt/ ← feature worktrees (created by /indie)
170
+ <feature-name>/ ← short, scannable names
171
+ ```
172
+
173
+ ### 7a — Detect Current State
174
+
175
+ Determine what we're working with:
176
+
177
+ 1. **Already bare-clone** — `../.bare/` exists relative to the current working directory, and the current directory is a worktree (`.git` is a file, not a directory). → Jump to 7d (validate).
178
+ 2. **Standard clone** — `.git` is a directory in the current working directory. → Proceed to 7b (migrate).
179
+ 3. **Root of a bare-clone layout** — `.bare/` exists in the current directory and `main/` exists. → Jump to 7d (validate from root).
180
+
181
+ ### 7b — Migrate to Bare-Clone
182
+
183
+ 1. **Capture state:**
184
+ - `REMOTE_URL` ← `git remote get-url origin`
185
+ - `BASE_BRANCH` ← the `base-branch` from Workflow Config
186
+ - Identify local-only files to preserve: `.env`, `.env.local`, `.claude/settings.local.json`, `.mcp.json` — any file that exists, is gitignored or untracked, and contains configuration
187
+ - Check for uncommitted changes — if dirty, stop: "You have uncommitted changes. Commit or stash them before migrating to a bare-clone layout."
188
+ - Check for existing worktrees — `git worktree list`. If worktrees exist outside the repo directory, warn: "Existing worktrees found at [paths]. Remove them first with `git worktree remove <path>`, or they'll have stale references after migration."
189
+
190
+ 2. **Create the new structure** in a temporary sibling directory `<project>-worktree-setup/`:
191
+ - `git clone --bare $REMOTE_URL <project>-worktree-setup/.bare`
192
+ - Configure fetch refspec: `git -C <project>-worktree-setup/.bare config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"`
193
+ - Create the main worktree: `git -C <project>-worktree-setup/.bare worktree add ../main $BASE_BRANCH`
194
+
195
+ 3. **Copy local files** from the old clone into the new `main/` worktree — only files identified in step 1.
196
+
197
+ 4. **Copy root-level files:** Copy `CLAUDE.md` and `.claude/` from `main/` to the project root as real files (not symlinks). Claude Code resolves these by walking up from the CWD, so they'll be found from any worktree.
198
+
199
+ 5. **Create the `wt/` directory:** `mkdir -p wt` in the new root — feature worktrees go here.
200
+
201
+ 6. **Swap directories:**
202
+ - `mv <project> <project>-old`
203
+ - `mv <project>-worktree-setup <project>`
204
+
205
+ 7. **Report:** "Migrated to bare-clone worktree layout. Old repo preserved at `<project>-old/`. Once you've verified everything works, delete it with `rm -rf <project>-old`."
206
+
207
+ Do NOT delete the old repo automatically — let the user verify first.
208
+
209
+ ### 7c — Post-Migration Setup
210
+
211
+ 1. Run dependency installation in the new `main/` worktree (detected package manager: `pnpm install`, `npm install`, etc.)
212
+ 2. Verify git operations work: `git -C main log --oneline -1`, `git -C main fetch origin`
213
+
214
+ ### 7d — Validate Existing Bare-Clone Layout
215
+
216
+ If the layout already exists, validate it:
217
+
218
+ 1. **`.bare/` exists** and is a bare git repo (`git -C .bare rev-parse --is-bare-repository` returns `true`)
219
+ 2. **`main/` exists** and is a valid worktree (`.git` file points to `.bare/worktrees/main`)
220
+ 3. **Worktree paths are correct** — run `git -C .bare worktree list` and verify paths match the current filesystem location. If stale (e.g. after a directory rename), run `git -C main worktree repair` to fix them.
221
+ 4. **Root-level files exist:**
222
+ - `CLAUDE.md` exists at the project root as a real file (not a symlink)
223
+ - `.claude/` exists at the project root as a real directory (not a symlink)
224
+ - If missing, copy them from `main/`. If they are symlinks, replace with real copies.
225
+ 5. **`wt/` directory exists** — if missing, create it: `mkdir -p wt`
226
+ 6. **Fetch refspec is configured** — `git -C .bare config remote.origin.fetch` returns `+refs/heads/*:refs/remotes/origin/*`
227
+
228
+ Report any issues found and fixed. If everything is valid: "Bare-clone worktree layout is healthy."
229
+
230
+ ---
231
+
232
+ ## Step 7W — Document Worktree Layout in CLAUDE.md
233
+
234
+ If `worktree-layout` is `bare-clone`, ensure CLAUDE.md has a `## Repository Layout` section (above `## Workflow Config`). If it doesn't exist, add it:
235
+
236
+ ```markdown
237
+ ## Repository Layout
238
+
239
+ This project uses a **bare-clone worktree layout**. The repo root is not a working copy — it contains:
240
+
241
+ \`\`\`
242
+ <project>/
243
+ .bare/ ← bare git repo (the actual .git data)
244
+ CLAUDE.md ← real file at root (not a symlink)
245
+ .claude/ ← real dir at root (not a symlink)
246
+ .mcp.json ← shared across worktrees
247
+ main/ ← worktree for the main branch (primary working copy)
248
+ wt/ ← feature worktrees created by /indie
249
+ <feature-name>/ ← short, scannable names
250
+ \`\`\`
251
+
252
+ - **Always work from `main/`** (or a feature worktree under `wt/`), never from the repo root.
253
+ - Feature worktrees live under `wt/` with short, scannable names (no timestamp prefix in the directory name — the timestamp is in the branch name).
254
+ - `CLAUDE.md` and `.claude/` at the root are real files, not symlinks. Claude Code finds them by walking up from any worktree's CWD.
255
+ - After a feature branch is merged, clean up with: `git worktree remove <path> && rm -rf <path>`
256
+ ```
257
+
258
+ If it already exists, leave it as-is.
259
+
260
+ ---
261
+
262
+ ## Step 8 — Set Up Playwright MCP (if applicable)
263
+
264
+ If `e2e-framework` is `playwright`, set up the Playwright MCP server so the qa-engineer skill can use a live browser when writing and debugging e2e tests.
265
+
266
+ 1. **Check if already configured** — read `.mcp.json` in the project root. If a `playwright` server entry exists, skip to validation.
267
+
268
+ 2. **Create or update `.mcp.json`** — add the Playwright MCP server:
269
+
270
+ ```json
271
+ {
272
+ "mcpServers": {
273
+ "playwright": {
274
+ "command": "npx",
275
+ "args": ["@playwright/mcp@latest", "--headless"]
276
+ }
277
+ }
278
+ }
279
+ ```
280
+
281
+ If `.mcp.json` already exists with other servers, merge — don't overwrite.
282
+
283
+ 3. **Validate** — the MCP server starts on demand (Claude Code launches it when a tool is called), so there's no process to check. Instead, verify the prerequisite: run `npx @playwright/mcp@latest --help` to confirm the package resolves and Node.js 18+ is available.
284
+
285
+ 4. **Report** — "Playwright MCP server configured in `.mcp.json`. The `/qa` skill will use it to interact with a live browser when writing e2e tests."
286
+
287
+ If `e2e-framework` is not `playwright`, skip this step entirely.
288
+
289
+ ---
290
+
291
+ ## Step 9 — Setup Recommendations
292
+
293
+ After writing the config, provide optional recommendations:
294
+
295
+ **Missing test infrastructure:**
296
+ - If no e2e framework is detected: "No e2e framework detected. The `/qa` skill won't work without one. Consider adding Playwright or Cypress."
297
+ - If no lint command is detected: "No linting detected. The implementation skill will skip lint checks."
298
+
299
+ **Hooks (optional):**
300
+ - If the project would benefit from scope-limiting hooks, suggest them. Don't install automatically — explain and let the user decide.
301
+
302
+ ---
303
+
304
+ ## Step 10 — Report
305
+
306
+ Present a summary:
307
+
308
+ 1. Config written to CLAUDE.md
309
+ 2. Workflow directory status (created or already exists)
310
+ 3. Recommendations (gitignore, missing tools)
311
+ 4. "Your project is now set up for the agentic workflow. Start with the spec-writer skill to plan a feature."
312
+
313
+ ---
314
+
315
+ ## Update Mode
316
+
317
+ When invoked with `update` or a specific key:
318
+
319
+ 1. Read the existing Workflow Config
320
+ 2. If updating everything: re-scan the project, detect changes, present the diff between old and new values
321
+ 3. If updating a specific key: ask for the new value, or re-detect just that key
322
+ 4. Update CLAUDE.md in place — only the Workflow Config section
323
+ 5. Re-validate the updated commands
324
+
325
+ ---
326
+
327
+ ## Constraints
328
+
329
+ **DO:**
330
+ - Detect commands from actual project files, not assumptions
331
+ - Validate detected commands by running them
332
+ - Present config for user confirmation before writing
333
+ - Create the workflow directory if it doesn't exist
334
+ - Recommend .gitignore additions and note missing infrastructure
335
+
336
+ **DON'T:**
337
+ - Overwrite existing CLAUDE.md content outside the Workflow Config section
338
+ - Install hooks without user consent
339
+ - Assume a specific ecosystem — detect what's there
340
+ - Write config without validating the commands work
341
+ - Skip the confirmation step — the user should review before config is written
342
+
343
+ ---
344
+
345
+ ## Red Flags
346
+
347
+ If you catch yourself thinking any of these, stop:
348
+
349
+ - "This looks like a Node.js project, I'll assume npm" — STOP. Check the lockfile. It might be pnpm or yarn.
350
+ - "The test command is obviously `npm test`" — STOP. Read `package.json` scripts. It might be `vitest`, `jest`, or something custom.
351
+ - "I'll skip validation, the commands look right" — STOP. Run them. A command that looks right but fails will break every downstream skill.
352
+ - "I'll write the config and the user can fix it later" — STOP. Present it for confirmation first.
353
+ - "This project doesn't have e2e tests, I'll leave that blank" — STOP. Leave it blank but warn that `/qa` won't work without it.