create-claude-workspace 1.1.50 → 1.1.52

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.
@@ -216,9 +216,10 @@ function setupSignals(opts, log, checkpoint) {
216
216
  finalCleanup(opts, checkpoint, log);
217
217
  process.exit(1);
218
218
  }
219
- log.info(`${label}. Stopping after current iteration (Ctrl+C again to force)...`);
219
+ log.info(`${label}. Killing child process and stopping (Ctrl+C again to force exit)...`);
220
220
  stopping = true;
221
221
  stoppingRef.value = true;
222
+ currentChild?.kill();
222
223
  };
223
224
  process.on('SIGINT', () => gracefulStop('SIGINT'));
224
225
  process.on('SIGTERM', () => gracefulStop('SIGTERM'));
@@ -144,7 +144,7 @@ function parseStreamEvent(json) {
144
144
  }
145
145
  }
146
146
  // ─── Process tree kill (cross-platform) ───
147
- function killProcessTree(pid, isWin) {
147
+ export function killProcessTree(pid, isWin) {
148
148
  if (isWin) {
149
149
  try {
150
150
  execSync(`taskkill /PID ${pid} /T /F`, { stdio: 'ignore' });
@@ -1,5 +1,6 @@
1
1
  import { describe, it, expect, beforeEach } from 'vitest';
2
- import { resetStreamState, validateResult, formatStreamEvent } from './claude-runner.mjs';
2
+ import { resetStreamState, validateResult, formatStreamEvent, killProcessTree } from './claude-runner.mjs';
3
+ import { spawn, execSync } from 'node:child_process';
3
4
  describe('validateResult', () => {
4
5
  it('returns valid result for complete object', () => {
5
6
  const input = { status: 'completed', action: 'commit', message: 'Done task #1' };
@@ -236,3 +237,110 @@ describe('resetStreamState', () => {
236
237
  resetStreamState(); // should not throw
237
238
  });
238
239
  });
240
+ // ─── killProcessTree integration tests ───
241
+ // Verifies that Ctrl+C (killProcessTree) actually kills child processes.
242
+ const isWin = process.platform === 'win32';
243
+ function isProcessAlive(pid) {
244
+ try {
245
+ process.kill(pid, 0); // signal 0 = existence check
246
+ return true;
247
+ }
248
+ catch {
249
+ return false;
250
+ }
251
+ }
252
+ function spawnLongRunning() {
253
+ const script = 'setTimeout(()=>{},60000)';
254
+ if (isWin) {
255
+ return spawn(process.env.ComSpec ?? 'cmd.exe', ['/c', 'node', '-e', script], {
256
+ stdio: 'ignore',
257
+ detached: true,
258
+ windowsHide: true,
259
+ });
260
+ }
261
+ return spawn('node', ['-e', script], {
262
+ stdio: 'ignore',
263
+ detached: true,
264
+ });
265
+ }
266
+ /** Spawn a nested process tree: parent node → child node (simulates cmd.exe → claude) */
267
+ function spawnNestedTree() {
268
+ // Parent spawns a child that also sleeps — simulates cmd.exe → claude process tree
269
+ const innerScript = `
270
+ const { spawn } = require('child_process');
271
+ const child = spawn('node', ['-e', 'setTimeout(()=>{},60000)'], { stdio: 'ignore' });
272
+ child.unref();
273
+ setTimeout(()=>{},60000);
274
+ `.replace(/\n/g, ' ');
275
+ if (isWin) {
276
+ return spawn(process.env.ComSpec ?? 'cmd.exe', ['/c', 'node', '-e', innerScript], {
277
+ stdio: 'ignore',
278
+ detached: true,
279
+ windowsHide: true,
280
+ });
281
+ }
282
+ return spawn('node', ['-e', innerScript], {
283
+ stdio: 'ignore',
284
+ detached: true,
285
+ });
286
+ }
287
+ describe('killProcessTree', () => {
288
+ it('kills a simple child process', async () => {
289
+ const child = spawnLongRunning();
290
+ const pid = child.pid;
291
+ expect(pid).toBeGreaterThan(0);
292
+ // Give process time to start
293
+ await new Promise(r => setTimeout(r, 500));
294
+ expect(isProcessAlive(pid)).toBe(true);
295
+ // Kill it
296
+ killProcessTree(pid, isWin);
297
+ // Wait for process to die
298
+ await new Promise(r => setTimeout(r, isWin ? 2000 : 1000));
299
+ expect(isProcessAlive(pid)).toBe(false);
300
+ }, 10_000);
301
+ it('kills a nested process tree (simulates cmd.exe → claude)', async () => {
302
+ const parent = spawnNestedTree();
303
+ const parentPid = parent.pid;
304
+ expect(parentPid).toBeGreaterThan(0);
305
+ // Give tree time to start
306
+ await new Promise(r => setTimeout(r, 1500));
307
+ expect(isProcessAlive(parentPid)).toBe(true);
308
+ // Kill the tree
309
+ killProcessTree(parentPid, isWin);
310
+ // Wait for tree to die
311
+ await new Promise(r => setTimeout(r, isWin ? 3000 : 2000));
312
+ expect(isProcessAlive(parentPid)).toBe(false);
313
+ // On Windows, verify no orphan node.exe children from our tree
314
+ if (isWin) {
315
+ // tasklist can't reliably check by parent PID after parent is dead,
316
+ // but the /T flag in taskkill should have killed all children
317
+ try {
318
+ const taskInfo = execSync(`tasklist /FI "PID eq ${parentPid}" /NH`, {
319
+ encoding: 'utf-8', stdio: 'pipe', timeout: 3000,
320
+ });
321
+ expect(taskInfo).not.toContain(String(parentPid));
322
+ }
323
+ catch { /* process dead, expected */ }
324
+ }
325
+ }, 15_000);
326
+ it('does not throw when killing already-dead process', () => {
327
+ // PID 999999 is almost certainly not a real process
328
+ expect(() => killProcessTree(999999, isWin)).not.toThrow();
329
+ });
330
+ it('kills process fast enough for interactive use (< 3s)', async () => {
331
+ const child = spawnLongRunning();
332
+ const pid = child.pid;
333
+ await new Promise(r => setTimeout(r, 500));
334
+ const start = Date.now();
335
+ killProcessTree(pid, isWin);
336
+ // Poll until dead or timeout
337
+ let alive = true;
338
+ while (alive && Date.now() - start < 3000) {
339
+ await new Promise(r => setTimeout(r, 100));
340
+ alive = isProcessAlive(pid);
341
+ }
342
+ const elapsed = Date.now() - start;
343
+ expect(alive).toBe(false);
344
+ expect(elapsed).toBeLessThan(3000);
345
+ }, 10_000);
346
+ });
@@ -380,7 +380,7 @@ vercel rollback
380
380
  **VPS (SSH):**
381
381
  ```bash
382
382
  # If using PM2 + git:
383
- ssh deploy@host "cd /var/www/myapp && git checkout <previous-release-tag-or-hash> && npm install && pm2 restart all"
383
+ ssh deploy@host "cd /var/www/myapp && git checkout <previous-release-tag-or-hash> && [PKG] install && pm2 restart all"
384
384
  # If using Docker:
385
385
  ssh deploy@host "docker compose -f docker-compose.prod.yml pull && docker compose -f docker-compose.prod.yml up -d"
386
386
  # (redeploy previous image tag — update docker-compose.prod.yml or .env with previous tag first)
@@ -72,11 +72,20 @@ At the beginning of EVERY session (including every Ralph Loop iteration):
72
72
  ### 1. Read state
73
73
  - Read MEMORY.md — find where you left off, what is done, what is next, what phase you're in
74
74
 
75
- ### 2. Resolve app names
76
- - Read CLAUDE.md — find app name(s) from the Architecture/Tech Stack section
75
+ ### 2. Resolve app names and package manager
76
+ - Read CLAUDE.md — find app name(s) and package manager from the Architecture/Tech Stack section
77
77
  - Store app names for use in build/test/lint commands:
78
78
  - `FRONTEND_APP` = the frontend app name (e.g., `my-app`)
79
79
  - `BACKEND_APP` = the API app name (e.g., `api`) — may not exist
80
+ - **Resolve package manager** — use the FIRST match:
81
+ 1. CLAUDE.md Tech Stack `[PKG] as package manager` field (authoritative if set)
82
+ 2. Lockfile detection: `bun.lock` or `bun.lockb` → bun, `pnpm-lock.yaml` → pnpm, `yarn.lock` → yarn, `package-lock.json` → npm
83
+ 3. Default: npm
84
+ - Store as `PKG` in MEMORY.md Session Config (e.g., `PKG: bun`). **All commands in all steps MUST use this value** — never hardcode npm/pnpm/yarn/bun.
85
+ - Install: `{PKG} install` (bun: `bun install`, npm: `npm ci`, pnpm: `pnpm install --frozen-lockfile`, yarn: `yarn install --frozen-lockfile`)
86
+ - Run script: `{PKG} run [script]` (bun: `bun run`, npm: `npm run`)
87
+ - Add package: `{PKG} add [pkg]` (npm: `npm install [pkg]`)
88
+ - Execute binary: `bunx` / `npx` / `pnpm exec` / `yarn dlx`
80
89
  - Also note: workflow mode from CLAUDE.md — `Workflow: solo` or `Workflow: team` (default: solo)
81
90
  - Store these in MEMORY.md under a `## Session Config` section if not already present
82
91
  - **If Session Config has "TBD" values** (new project before Phase 0): scan `apps/` directory. If apps now exist (Phase 0 completed), update the values. If still empty, keep "TBD" — Phase 0 scaffolding will create them.
@@ -236,14 +245,13 @@ To determine if a task is frontend, backend, or fullstack, use this heuristic:
236
245
  - **Phase transition check**: If this is the FIRST task of a new phase (all previous phase tasks done):
237
246
  - **CI/CD generation (Phase 0 → Phase 1 only):** If Phase 0 just completed AND CLAUDE.md has a `## Deployment` section (deployment interview was done during setup) AND no CI/CD config files exist yet (`.gitlab-ci.yml`, `.github/workflows/`): delegate to `deployment-engineer`: "Phase 0 (workspace scaffolding) is complete — the project now has package.json and source code. Read CLAUDE.md ## Deployment section for deployment config collected during setup. Generate CI/CD config files now. [If CLAUDE.md has Distribution: npm:] Also generate CI publish jobs — read MEMORY.md NPM_REGISTRY/NPM_CI_AUTH for registry config."
238
247
  - **Dependency freshness check** (skip at Phase 0 → Phase 1 — deps were just installed during scaffolding):
239
- 1. Detect package manager from lockfile (`package-lock.json` npm, `yarn.lock` yarn, `pnpm-lock.yaml` pnpm, `bun.lock` bun). Default to npm.
248
+ 1. Use `PKG` from MEMORY.md Session Config (resolved in health check step 2).
240
249
  2. Run outdated check — capture output, then validate. Note: `npm outdated` exits with code 1 when outdated packages exist (this is normal, not an error):
241
250
  ```bash
242
- # npm
243
- OUTDATED=$(npm outdated --json 2>&1) || true
244
- # yarn: yarn outdated --json 2>&1 || true
245
- # pnpm: pnpm outdated --format json 2>&1 || true
246
- # bun: bun outdated 2>&1 || true
251
+ # npm: OUTDATED=$(npm outdated --json 2>&1) || true
252
+ # pnpm: OUTDATED=$(pnpm outdated --format json 2>&1) || true
253
+ # yarn: OUTDATED=$(yarn outdated --json 2>&1) || true
254
+ # bun: OUTDATED=$(bun outdated 2>&1) || true
247
255
  ```
248
256
  If the output is not valid JSON (network failure, corrupt lockfile), log warning in MEMORY.md Notes and skip.
249
257
  3. Skip if no outdated dependencies found (empty JSON object `{}`).
@@ -251,12 +259,12 @@ To determine if a task is frontend, backend, or fullstack, use this heuristic:
251
259
  - **Patch** (e.g., `1.2.3` → `1.2.5`): safe, auto-upgrade
252
260
  - **Minor** (e.g., `1.2.3` → `1.3.0`): likely safe, auto-upgrade
253
261
  - **Major** (e.g., `1.2.3` → `2.0.0`): breaking changes — do NOT auto-upgrade
254
- 5. **Auto-upgrade patch + minor:** Run `npm update` (respects semver ranges in package.json). If package.json pins exact versions or ranges exclude the available update, use `npm install [pkg]@[wanted]` for each outdated package where wanted ≤ latest within the semver range. For other package managers: `yarn upgrade`, `pnpm update`, `bun update`.
262
+ 5. **Auto-upgrade patch + minor:** Run `{PKG} update` (e.g., `bun update`, `npm update`, `pnpm update`, `yarn upgrade`). If package.json pins exact versions or ranges exclude the available update, use `{PKG} add [pkg]@[wanted]` (npm: `npm install [pkg]@[wanted]`) for each outdated package where wanted ≤ latest within the semver range.
255
263
  6. **Verify:** Run full workspace build, lint, and test from the project root on main (not scoped to a specific project — there is no active task or worktree at this point). If ANY fail:
256
264
  - Revert files AND restore node_modules to match:
257
265
  ```bash
258
266
  git restore package.json [lockfile]
259
- npm ci # or: yarn install --frozen-lockfile / pnpm install --frozen-lockfile / bun install --frozen-lockfile
267
+ {PKG} install # bun install / npm ci / pnpm install --frozen-lockfile / yarn install --frozen-lockfile
260
268
  ```
261
269
  - Log failed upgrade in MEMORY.md Notes: "Dependency upgrade failed at phase transition — [error summary]. Skipped."
262
270
  - Do NOT block phase transition — continue with current versions.
@@ -314,7 +322,7 @@ To determine if a task is frontend, backend, or fullstack, use this heuristic:
314
322
  - Update MEMORY.md (on main): set `Current Step: 3 — IMPLEMENT`
315
323
  - **All file operations in the worktree** (`Current Worktree` from MEMORY.md). Use absolute paths for Read/Edit/Write and `cd {worktree}` for Bash commands.
316
324
  - **CLI commands and Nx Generators FIRST**: Always prioritize using CLI commands and `nx generate` over manual file creation. NEVER manually create `project.json`, `tsconfig.*`, or configure build/test/lint targets — generators and CLI tools handle this correctly. NEVER manually install or configure build tools (tsup, esbuild, rollup, webpack) — Nx generators handle build configuration. Internal libs don't need a build step; publishable libs get `--bundler` from the generator. This includes:
317
- - **Workspace scaffolding** (Phase 0): `npx create-nx-workspace` — architect decides preset and flags
325
+ - **Workspace scaffolding** (Phase 0): `create-nx-workspace` via the detected package manager runner (`bunx` / `npx` / `pnpm dlx` / `yarn dlx`) — architect decides preset and flags. If PKG is bun, use `bunx create-nx-workspace` and pass `--packageManager=bun`.
318
326
  - **Libraries/components/apps**: `nx generate` — see CLAUDE.md "Nx Generators" section
319
327
  - **Plugins**: `nx add` (e.g., `nx add @nx/playwright`, `nx add @nx/eslint`)
320
328
  - **Code quality setup**: install via package manager, configure via CLI where possible
@@ -147,6 +147,7 @@ Create a **minimal** CLAUDE.md in the project root by filling what is KNOWN from
147
147
  - Fill in: project name, description, workflow (solo/team)
148
148
  - If user answered "yes" to npm publishing (question 6), add `- **Distribution**: npm ([registry], [public/restricted])` to Tech Stack section (details collected in Step 2b)
149
149
  - Fill in Tech Stack ONLY for what is already detected in the codebase (existing package.json, nx.json, etc.)
150
+ - **Detect package manager** from lockfile: `bun.lock` or `bun.lockb` → bun, `pnpm-lock.yaml` → pnpm, `yarn.lock` → yarn, `package-lock.json` → npm. If no lockfile, check `package.json` `packageManager` field. Fill `[PKG]` in the template with the detected value. This is critical — all agents use this value for install/run/add commands.
150
151
  - For NEW projects (empty codebase): leave Tech Stack minimal — just project type and package manager if known. Architecture decisions will be made by architect agents in TODO.md Phase 0.
151
152
  - **Remove all backend sections** if no backend is mentioned
152
153
  - Remove placeholder comments and unresolved `[PLACEHOLDER]` values
@@ -179,7 +179,7 @@ Before writing E2E tests, detect existing config:
179
179
  ```bash
180
180
  nx add @nx/playwright
181
181
  ```
182
- If not using Nx, install Playwright directly: `npm init playwright@latest`
182
+ If not using Nx, install Playwright directly: `{PKG} init playwright@latest` (bun: `bun create playwright`, npm: `npm init playwright@latest`). Read CLAUDE.md Tech Stack for the project's package manager.
183
183
  NEVER manually create `project.json` for E2E projects — the generator configures targets, config files, and directory structure correctly.
184
184
 
185
185
  4. E2E tests live in `apps/[APP]-e2e/src/` (Nx) or `e2e/` (standalone) — NOT next to unit tests
@@ -10,7 +10,7 @@
10
10
  - Repository URL:
11
11
  - Primary language: TypeScript
12
12
  - Framework(s):
13
- - Package manager: npm | pnpm | yarn | bun
13
+ - Package manager: bun | npm | pnpm | yarn
14
14
  - Monorepo: no
15
15
 
16
16
  ## Product Vision
@@ -34,7 +34,7 @@ When running in autonomous/unattended mode (via `autonomous.mjs` or non-interact
34
34
 
35
35
  [Generate based on detected project type. Include only lines relevant to this project:]
36
36
  - **Project type**: [Angular app / TS library monorepo / Fullstack app / Backend only]
37
- - **Build**: Nx monorepo, [PKG] as package manager
37
+ - **Build**: Nx monorepo, [PKG] as package manager (CRITICAL: detect from lockfile — `bun.lock`/`bun.lockb` → bun, `pnpm-lock.yaml` → pnpm, `yarn.lock` → yarn, `package-lock.json` → npm)
38
38
  - **Testing**: Vitest
39
39
  - **Deployment**: Cloudflare (Pages + Workers) — default
40
40
  - **Workflow**: solo / team (solo = direct merge to main, team = MR/PR review required)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-claude-workspace",
3
- "version": "1.1.50",
3
+ "version": "1.1.52",
4
4
  "description": "Scaffold a project with Claude Code agents for autonomous AI-driven development",
5
5
  "type": "module",
6
6
  "bin": {