Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ User runs /setup
| Module | Purpose |
|--------|---------|
| `skills/readiness/SKILL.md` | Harness Readiness Report |
| `skills/setup/SKILL.md` | Setup Report |
| `skills/setup/SKILL.md` | If the current directory is empty, scaffold in place: |
| `skills/setup/scripts/generate-claude-md.js` | Generate tailored CLAUDE.md files for a project from templates. |
| `skills/setup/scripts/init-project.js` | Project scaffolding script for Node/TypeScript projects. |
| `skills/setup/scripts/install-enforcement.js` | Copies enforcement tooling into a target project. |
Expand Down Expand Up @@ -200,7 +200,6 @@ Before merging:
- **Setup has two code paths**: Node/TS uses the "fast path" (scripts do the work). All other stacks use the "adaptive path" (Claude creates files). The eval uses `conversation_must_not_mention` to catch cross-contamination (e.g., Python setup mentioning npm).
- **Squash merges leave branches dirty**: After squash-merging a PR, the branch still shows unmerged commits. Always create a fresh branch from main for follow-up work — don't reuse the old branch.
- **CLAUDE.md auto-updates on commit**: The pre-commit hook runs `repo-generate-docs.js` to regenerate AUTO markers. Don't manually edit content between `<!-- AUTO:tree -->` and `<!-- AUTO:modules -->` markers — it will be overwritten.
- **Eval prompts must say "in the current directory"**: `init-project.js --name=X` creates a subdirectory. Eval fixtures run in temp dirs, so the grader checks the temp dir root. If the prompt says "call it myapp", Claude creates `myapp/` and the grader finds nothing.

---

Expand All @@ -212,4 +211,3 @@ Before merging:
| Enforcement script patterns | `skills/setup/references/enforcement-scripts.md` |
| Node/TypeScript stack reference | `skills/setup/references/stack-node-typescript.md` |
| Eval suite documentation | `tests/evals/README.md` |
| Unit test documentation | `tests/scripts/README.md` |
6 changes: 6 additions & 0 deletions skills/setup/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,15 @@ Skip this phase entirely for existing projects.
**Node/TypeScript path (fast path — script does the work):**

```bash
# If the current directory is empty, scaffold in place:
node $SCRIPTS_DIR/init-project.js --name=<name> --framework=<framework> --in-place

# If the user wants a new subdirectory (rare — only when explicitly requested):
node $SCRIPTS_DIR/init-project.js --name=<name> --framework=<framework>
```

Default to `--in-place` unless the user explicitly asks to create a new directory.

**All other stacks (adaptive path — Claude does the work):**

1. `git init`
Expand Down
7 changes: 4 additions & 3 deletions skills/setup/scripts/init-project.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@ const childProcess = require('node:child_process');
const VALID_FRAMEWORKS = ['vite', 'nextjs', 'express', 'fastify', 'none'];

function parseArgs(argv) {
const flags = { name: null, framework: 'none', skipInstall: false };
const flags = { name: null, framework: 'none', skipInstall: false, inPlace: false };
for (const arg of argv.slice(2)) {
if (arg.startsWith('--name=')) flags.name = arg.slice('--name='.length);
else if (arg.startsWith('--framework=')) flags.framework = arg.slice('--framework='.length);
else if (arg === '--skip-install') flags.skipInstall = true;
else if (arg === '--in-place') flags.inPlace = true;
}
if (!VALID_FRAMEWORKS.includes(flags.framework)) {
console.error('Unknown framework "' + flags.framework + '". Must be one of: ' + VALID_FRAMEWORKS.join(', '));
Expand Down Expand Up @@ -86,12 +87,12 @@ function main() {

// Resolve working directory
let projectDir = process.cwd();
if (flags.name) {
if (flags.name && !flags.inPlace) {
projectDir = path.join(process.cwd(), flags.name);
fs.mkdirSync(projectDir, { recursive: true });
}

const projectName = path.basename(projectDir);
const projectName = flags.name || path.basename(projectDir);

// Git init if needed
if (!fs.existsSync(path.join(projectDir, '.git'))) {
Expand Down
20 changes: 20 additions & 0 deletions tests/scripts/init-project.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,26 @@ describe('--name flag', () => {
});
});

// ---------------------------------------------------------------------------
// --in-place: uses name for package.json but stays in current directory
// ---------------------------------------------------------------------------
describe('--in-place flag', () => {
it('scaffolds in current directory, not a subdirectory', () => {
runScript(['--name=myapp', '--framework=none', '--skip-install', '--in-place'], tmpDir);

expect(fs.existsSync(path.join(tmpDir, 'package.json'))).toBe(true);
expect(fs.existsSync(path.join(tmpDir, 'src'))).toBe(true);
expect(fs.existsSync(path.join(tmpDir, 'myapp'))).toBe(false);
});

it('uses the --name value in package.json', () => {
runScript(['--name=myapp', '--framework=none', '--skip-install', '--in-place'], tmpDir);

const pkg = JSON.parse(fs.readFileSync(path.join(tmpDir, 'package.json'), 'utf8'));
expect(pkg.name).toBe('myapp');
});
});

// ---------------------------------------------------------------------------
// No --name: initializes in current directory
// ---------------------------------------------------------------------------
Expand Down
Loading