@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.
- package/CHANGELOG.md +40 -0
- package/LICENSE +21 -0
- package/README.md +73 -0
- package/package.json +42 -0
- package/scripts/cli.js +67 -0
- package/scripts/commands/doctor.js +51 -0
- package/scripts/commands/init.js +131 -0
- package/scripts/commands/list.js +33 -0
- package/scripts/commands/uninstall.js +57 -0
- package/scripts/commands/update.js +92 -0
- package/scripts/lib/claude-md.js +18 -0
- package/scripts/lib/fsx.js +33 -0
- package/scripts/lib/hash.js +28 -0
- package/scripts/lib/log.js +19 -0
- package/scripts/lib/manifest.js +79 -0
- package/scripts/lib/paths.js +35 -0
- package/scripts/lib/prompt.js +40 -0
- package/skills/adjust/SKILL.md +353 -0
- package/skills/codebase-review/SKILL.md +219 -0
- package/skills/docs/SKILL.md +329 -0
- package/skills/implementation/SKILL.md +344 -0
- package/skills/indie/SKILL.md +337 -0
- package/skills/indie-agent/SKILL.md +518 -0
- package/skills/patterns-refactor/SKILL.md +291 -0
- package/skills/prep/SKILL.md +244 -0
- package/skills/qa-engineer/SKILL.md +246 -0
- package/skills/review/SKILL.md +309 -0
- package/skills/ship/SKILL.md +201 -0
- package/skills/spec-writer/SKILL.md +259 -0
- package/templates/workflow-config.md +11 -0
|
@@ -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.
|