create-claude-workspace 1.1.41 → 1.1.44

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.
@@ -220,6 +220,37 @@ To determine if a task is frontend, backend, or fullstack, use this heuristic:
220
220
  After planner updates TODO.md -> if git integration active, delegate to `devops-integrator` to sync new tasks as issues. Then restart STEP 1 with the first sub-task.
221
221
  - **Phase transition check**: If this is the FIRST task of a new phase (all previous phase tasks done):
222
222
  - **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."
223
+ - **Dependency freshness check** (skip at Phase 0 → Phase 1 — deps were just installed during scaffolding):
224
+ 1. Detect package manager from lockfile (`package-lock.json` → npm, `yarn.lock` → yarn, `pnpm-lock.yaml` → pnpm, `bun.lock` → bun). Default to npm.
225
+ 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):
226
+ ```bash
227
+ # npm
228
+ OUTDATED=$(npm outdated --json 2>&1) || true
229
+ # yarn: yarn outdated --json 2>&1 || true
230
+ # pnpm: pnpm outdated --format json 2>&1 || true
231
+ # bun: bun outdated 2>&1 || true
232
+ ```
233
+ If the output is not valid JSON (network failure, corrupt lockfile), log warning in MEMORY.md Notes and skip.
234
+ 3. Skip if no outdated dependencies found (empty JSON object `{}`).
235
+ 4. **Categorize updates:**
236
+ - **Patch** (e.g., `1.2.3` → `1.2.5`): safe, auto-upgrade
237
+ - **Minor** (e.g., `1.2.3` → `1.3.0`): likely safe, auto-upgrade
238
+ - **Major** (e.g., `1.2.3` → `2.0.0`): breaking changes — do NOT auto-upgrade
239
+ 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`.
240
+ 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:
241
+ - Revert files AND restore node_modules to match:
242
+ ```bash
243
+ git restore package.json [lockfile]
244
+ npm ci # or: yarn install --frozen-lockfile / pnpm install --frozen-lockfile / bun install --frozen-lockfile
245
+ ```
246
+ - Log failed upgrade in MEMORY.md Notes: "Dependency upgrade failed at phase transition — [error summary]. Skipped."
247
+ - Do NOT block phase transition — continue with current versions.
248
+ 7. **If verification passes:** Commit on main: `git add package.json [lockfile] && git commit -m "chore: upgrade dependencies"`. Push if remote exists.
249
+ 8. **Major version upgrades:** For each package with a major version available, add a maintenance task to the CURRENT phase in TODO.md. Infer task type from the package's nature — devDependencies (eslint, vitest, typescript) use `Type: backend`, runtime dependencies use `Type: fullstack` or the appropriate frontend/backend type:
250
+ - `- [ ] **Upgrade [package]@[current] → [latest]** — major version, review changelog for breaking changes (Complexity: S, Type: [inferred])`
251
+ - If git integration active, delegate to `devops-integrator` to create issues for these tasks.
252
+ - Log in MEMORY.md Notes: "Major upgrades available: [list packages with current → latest]"
253
+ 9. **Monorepo / Nx workspace:** If `nx.json` exists, run outdated check from workspace root (covers all hoisted deps — do NOT run per-project). Use `nx report` to verify plugin compatibility after upgrades. For Nx plugin major upgrades, prefer `nx migrate [package]@latest` over manual update — it generates migrations that handle config changes.
223
254
  - Delegate to `product-owner` agent: "This is a PHASE RE-EVALUATION (not initial creation). Phase [N-1] is complete. Read PRODUCT.md, TODO.md, MEMORY.md, and scan the current codebase. Evaluate: are Phase [N] priorities still correct? Output a structured diff: ADD / REMOVE / REPRIORITIZE / CONFIRM. If everything looks good, just CONFIRM."
224
255
  - If product-owner recommends changes -> delegate to `technical-planner` to update TODO.md
225
256
  - CLAUDE.md refresh: attempt `/revise-claude-md` skill. If the skill is not available, manually read CLAUDE.md, append any new patterns/conventions discovered during the completed phase under `## Project-Specific Details`, and commit the update.
@@ -8,7 +8,7 @@ import { getErrorAction } from './lib/errors.mjs';
8
8
  import { createLogger } from './lib/logger.mjs';
9
9
  import { emptyCheckpoint, readCheckpoint, writeCheckpoint } from './lib/state.mjs';
10
10
  import { runClaude, currentChild } from './lib/claude-runner.mjs';
11
- import { sleep, formatDuration, acquireLock, releaseLock, readMemory, isProjectComplete, getCurrentTask, getCurrentPhase, checkClaudeInstalled, checkAuth, checkGitIdentity, checkFilesystemWritable, gitFetchAndPull, gitCheckState, notify, printSummary, promptUser, } from './lib/utils.mjs';
11
+ import { sleep, formatDuration, acquireLock, releaseLock, readMemory, isProjectComplete, getCurrentTask, getCurrentPhase, checkClaudeInstalled, checkAuth, checkGitIdentity, checkFilesystemWritable, gitFetchAndPull, gitCheckState, notify, printSummary, promptUser, parseUsageLimitResetMs, } from './lib/utils.mjs';
12
12
  import { isTokenExpiringSoon, refreshOAuthToken } from './lib/oauth-refresh.mjs';
13
13
  // ─── Args ───
14
14
  function parseArgs(argv) {
@@ -371,6 +371,28 @@ async function main() {
371
371
  consecutiveFailures: checkpoint.consecutiveFailures,
372
372
  authRetries: checkpoint.authRetries,
373
373
  });
374
+ // Handle usage limit — wait until reset time if parseable, otherwise stop
375
+ if (category === 'usage_limit') {
376
+ const waitMs = parseUsageLimitResetMs(output.stderr);
377
+ if (waitMs) {
378
+ log.warn(`Account usage limit reached. Waiting ${formatDuration(waitMs + 60_000)} until reset...`);
379
+ notify(opts.notifyCommand, 'error', `Usage limit — waiting ${formatDuration(waitMs + 60_000)}`, i);
380
+ writeCheckpoint(opts.projectDir, checkpoint, log);
381
+ await sleep(waitMs + 60_000, stoppingRef); // +1min buffer
382
+ if (stopping)
383
+ break;
384
+ checkpoint.consecutiveFailures = 0;
385
+ i--; // Retry this iteration
386
+ continue;
387
+ }
388
+ // Can't parse reset time — stop
389
+ checkpoint.consecutiveFailures++;
390
+ log.error('Account usage limit reached. Could not parse reset time from output.');
391
+ log.error('Restart the loop after your limit resets.');
392
+ notify(opts.notifyCommand, 'stopped', 'Account usage limit (unknown reset time)', i);
393
+ writeCheckpoint(opts.projectDir, checkpoint, log);
394
+ break;
395
+ }
374
396
  // Handle stop — try OAuth refresh for auth errors before giving up
375
397
  if (action.type === 'stop') {
376
398
  if (category === 'auth_expired') {
@@ -165,11 +165,12 @@ function killProcessTree(pid, isWin) {
165
165
  }, 5000).unref();
166
166
  }
167
167
  // ─── Error signal patterns ───
168
- const RATE_LIMIT_RE = /rate.?limit|429|overloaded|529/;
169
- const AUTH_ERROR_RE = /authentication_error|failed to authenticate|api error: 401|invalid authentication|403 forbidden/;
168
+ const RATE_LIMIT_RE = /rate.?limit|429|overloaded|529|at capacity|capacity reached|503 service unavailable|502 bad gateway/;
169
+ const AUTH_ERROR_RE = /authentication_error|failed to authenticate|api error: 401|invalid authentication|403 forbidden|account.{0,10}(?:suspended|disabled|deactivated)/;
170
170
  const AUTH_TYPE_RE = /authentication/;
171
171
  const AUTH_MSG_RE = /authentication|401|403/;
172
172
  const AUTH_SERVER_RE = /\b5\d\d\b/;
173
+ const USAGE_LIMIT_RE = /you.ve hit your limit|usage.?limit|plan.?limit|quota.?exceeded|daily.?limit|monthly.?limit/;
173
174
  export let currentChild = null;
174
175
  export function runClaude(opts, log, runOpts = {}) {
175
176
  const startTime = process.hrtime.bigint();
@@ -229,6 +230,7 @@ export function runClaude(opts, log, runOpts = {}) {
229
230
  let isRateLimit = false;
230
231
  let isAuthError = false;
231
232
  let isAuthServerError = false;
233
+ let isUsageLimit = false;
232
234
  let resultReceived = false;
233
235
  let buffer = '';
234
236
  let killed = false;
@@ -240,6 +242,8 @@ export function runClaude(opts, log, runOpts = {}) {
240
242
  return;
241
243
  const msg = (event.error?.message ?? (typeof event.message === 'string' ? event.message : '') ?? '').toLowerCase();
242
244
  const errType = (event.error?.type ?? '').toLowerCase();
245
+ if (USAGE_LIMIT_RE.test(msg))
246
+ isUsageLimit = true;
243
247
  if (RATE_LIMIT_RE.test(msg))
244
248
  isRateLimit = true;
245
249
  if (AUTH_TYPE_RE.test(errType) || AUTH_MSG_RE.test(msg))
@@ -248,6 +252,8 @@ export function runClaude(opts, log, runOpts = {}) {
248
252
  isAuthServerError = true;
249
253
  }
250
254
  function detectTextSignals(lower) {
255
+ if (USAGE_LIMIT_RE.test(lower))
256
+ isUsageLimit = true;
251
257
  if (RATE_LIMIT_RE.test(lower))
252
258
  isRateLimit = true;
253
259
  if (AUTH_ERROR_RE.test(lower))
@@ -405,6 +411,7 @@ export function runClaude(opts, log, runOpts = {}) {
405
411
  isRateLimit,
406
412
  isAuthError,
407
413
  isAuthServerError,
414
+ isUsageLimit,
408
415
  timedOut: killReason === 'process_timeout',
409
416
  activityTimedOut: killReason === 'activity_timeout',
410
417
  hasResult: resultReceived,
@@ -2,8 +2,9 @@
2
2
  // Pure functions, no side effects. Lookup maps instead of nested conditions.
3
3
  /** Pattern table: checked in order, first match wins. */
4
4
  const STDERR_PATTERNS = [
5
- ['auth_expired', /authentication_error|invalid authentication|api error: 401|failed to authenticate|403 forbidden/i],
6
- ['rate_limited', /rate.?limit|too many requests|429|overloaded|529/i],
5
+ ['auth_expired', /authentication_error|invalid authentication|api error: 401|failed to authenticate|403 forbidden|account.{0,10}(?:suspended|disabled|deactivated)/i],
6
+ ['usage_limit', /you.ve hit your limit|usage.?limit|plan.?limit|quota.?exceeded|daily.?limit|monthly.?limit/i],
7
+ ['rate_limited', /rate.?limit|too many requests|429|overloaded|529|at capacity|capacity reached|503 service unavailable|502 bad gateway/i],
7
8
  ['disk_full', /ENOSPC|no space left/i],
8
9
  ['oom_killed', /out of memory|heap|allocation failed/i],
9
10
  ['network_error', /ENOTFOUND|EAI_AGAIN|ECONNREFUSED|ECONNRESET|ETIMEDOUT|network|CERT_|SSL_|proxy/i],
@@ -20,6 +21,9 @@ export function classifyError(signals) {
20
21
  return 'auth_expired';
21
22
  if (signals.isAuthServerError)
22
23
  return 'auth_server_error';
24
+ // Usage limit (account quota) takes priority over rate limit — it's hours, not seconds
25
+ if (signals.isUsageLimit)
26
+ return 'usage_limit';
23
27
  if (signals.isRateLimit)
24
28
  return 'rate_limited';
25
29
  if (signals.timedOut) {
@@ -69,6 +73,12 @@ const STRATEGIES = {
69
73
  auth_server_error: (ctx) => ctx.authRetries >= MAX_AUTH_RETRIES
70
74
  ? { type: 'stop', reason: `Auth server failed ${MAX_AUTH_RETRIES}x. Try again later.` }
71
75
  : { type: 'backoff', ms: 30_000 * (ctx.authRetries + 1) },
76
+ // Note: usage_limit has custom handling in autonomous.mts (wait until reset time).
77
+ // The 'stop' here is a fallback if that custom handler can't parse the reset time.
78
+ usage_limit: () => ({
79
+ type: 'stop',
80
+ reason: 'Account usage limit reached. Loop will resume automatically if reset time is detected.',
81
+ }),
72
82
  rate_limited: () => ({ type: 'backoff', ms: 0 }), // ms overridden by checkpoint.rateLimitBackoff
73
83
  network_error: (ctx) => ctx.consecutiveFailures >= MAX_CONSECUTIVE
74
84
  ? { type: 'stop', reason: `${MAX_CONSECUTIVE} consecutive network errors.` }
@@ -6,6 +6,35 @@ import { hostname } from 'node:os';
6
6
  import { createInterface } from 'node:readline';
7
7
  // Node.js runtime accepts `true` for shell but @types expects string
8
8
  const SHELL = process.platform === 'win32' ? 'cmd.exe' : '/bin/sh';
9
+ // ─── Usage limit reset time parser ───
10
+ /** Parse "resets Xpm (UTC)" / "resets X:XX AM (UTC)" / "resets HH:MM (UTC)" from stderr text.
11
+ * Returns milliseconds to wait, or null if unparseable. */
12
+ export function parseUsageLimitResetMs(text) {
13
+ // Match patterns: "resets 6pm (UTC)", "resets 6:30pm (UTC)", "resets 18:00 (UTC)", "resets 6 PM (UTC)"
14
+ const match = text.match(/resets\s+(\d{1,2})(?::(\d{2}))?\s*(am|pm)?\s*\(UTC\)/i);
15
+ if (!match)
16
+ return null;
17
+ let hours = parseInt(match[1], 10);
18
+ const minutes = match[2] ? parseInt(match[2], 10) : 0;
19
+ const ampm = match[3]?.toLowerCase();
20
+ if (ampm === 'pm' && hours < 12)
21
+ hours += 12;
22
+ if (ampm === 'am' && hours === 12)
23
+ hours = 0;
24
+ if (hours > 23 || minutes > 59)
25
+ return null;
26
+ const now = new Date();
27
+ const reset = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate(), hours, minutes, 0, 0));
28
+ // If reset time is in the past, it's tomorrow
29
+ if (reset.getTime() <= now.getTime()) {
30
+ reset.setUTCDate(reset.getUTCDate() + 1);
31
+ }
32
+ const waitMs = reset.getTime() - now.getTime();
33
+ // Sanity: max 24 hours
34
+ if (waitMs > 24 * 60 * 60_000)
35
+ return null;
36
+ return waitMs;
37
+ }
9
38
  // ─── Sleep ───
10
39
  export function sleep(ms, stoppingRef) {
11
40
  if (!stoppingRef)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-claude-workspace",
3
- "version": "1.1.41",
3
+ "version": "1.1.44",
4
4
  "description": "Scaffold a project with Claude Code agents for autonomous AI-driven development",
5
5
  "type": "module",
6
6
  "bin": {