create-claude-workspace 1.1.39 → 1.1.41
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.
|
@@ -126,7 +126,39 @@ If no remote, skip this step.
|
|
|
126
126
|
- `git log --oneline -10`
|
|
127
127
|
- Scan current file structure
|
|
128
128
|
|
|
129
|
-
### 8.
|
|
129
|
+
### 8. Detect PLAN.md changes
|
|
130
|
+
If `PLAN.md` exists in the project root, check whether it changed since the last session. This runs AFTER git sync (step 6) to avoid commit conflicts with remote changes.
|
|
131
|
+
|
|
132
|
+
1. Read PLAN.md content
|
|
133
|
+
2. Read MEMORY.md `PLAN_MD_Hash` value (if present)
|
|
134
|
+
3. Compute a hash of PLAN.md content: take first 8 chars of hex SHA-256 (`node -e "const c=require('crypto');process.stdout.write(c.createHash('sha256').update(require('fs').readFileSync('PLAN.md','utf-8')).digest('hex').slice(0,8))"`)
|
|
135
|
+
4. If hash matches → skip (no changes). If `PLAN_MD_Hash` is absent (first run) → treat as "changed" to seed the state.
|
|
136
|
+
5. If changed — read PLAN.md and diff against current project state. Delegate updates based on WHICH sections changed:
|
|
137
|
+
|
|
138
|
+
| Changed section | Action |
|
|
139
|
+
|---|---|
|
|
140
|
+
| **Product Vision** (Target users, Core problem, MVP features, Non-goals) | Delegate to `product-owner`: "PLAN.md Product Vision was updated. Read the new PLAN.md and current PRODUCT.md. Update PRODUCT.md to reflect the new vision. Output what changed." |
|
|
141
|
+
| **Technical Decisions** (Framework, Backend, Database, Deployment, CI/CD, Testing) | Delegate to `technical-planner`: "PLAN.md Technical Decisions were updated. Read new PLAN.md, current CLAUDE.md, and TODO.md. Update TODO.md tasks if the tech stack changed (e.g., framework switch may invalidate existing tasks). Update CLAUDE.md Tech Stack section to match." If only Deployment/CI/CD changed, also delegate to `deployment-engineer`: "PLAN.md deployment config changed. Update CI/CD pipeline config to match new settings." |
|
|
142
|
+
| **Workflow** (Mode, Branch strategy, Auto-push, Auto-merge, Review required) | Update CLAUDE.md `Workflow:` field directly. If mode changed from solo→team or team→solo, log in MEMORY.md Notes. |
|
|
143
|
+
| **Constraints** (Max lines per file, Coverage threshold, Forbidden patterns) | Update CLAUDE.md constraints section directly (Code Quality & Linting, coverage thresholds). |
|
|
144
|
+
| **Project Info** (Name, Description, Framework, Package manager, Monorepo) | Delegate to `technical-planner`: "PLAN.md Project Info changed. Update CLAUDE.md header and Tech Stack to match." |
|
|
145
|
+
| **Access & Credentials** | Validate new credentials (run auth checks). Log status in MEMORY.md Session Config. Do NOT store credential values. |
|
|
146
|
+
| **Initial State / Runtime** | Read values but do NOT delegate — these are consumed by `autonomous.mjs`, not agents. |
|
|
147
|
+
|
|
148
|
+
6. After all delegations complete, update MEMORY.md: set `PLAN_MD_Hash: [new-hash]`
|
|
149
|
+
7. If any files were modified (CLAUDE.md, PRODUCT.md, TODO.md), commit: `git add CLAUDE.md PRODUCT.md TODO.md MEMORY.md && git commit -m "chore: propagate PLAN.md changes to project files"`
|
|
150
|
+
8. Push if remote exists: `git push origin HEAD`
|
|
151
|
+
9. Skip this entire step if PLAN.md does not exist.
|
|
152
|
+
|
|
153
|
+
**How to detect which sections changed** (without storing the old file):
|
|
154
|
+
- Compare PLAN.md values against current project files:
|
|
155
|
+
- `Product Vision` → compare MVP features list against PRODUCT.md features
|
|
156
|
+
- `Technical Decisions` → compare framework/backend/database against CLAUDE.md Tech Stack
|
|
157
|
+
- `Workflow` → compare mode against CLAUDE.md `Workflow:` field
|
|
158
|
+
- `Constraints` → compare coverage threshold against CLAUDE.md code quality section
|
|
159
|
+
- If a PLAN.md value differs from the corresponding project file value, that section "changed"
|
|
160
|
+
|
|
161
|
+
### 9. Retroactive git integration sync
|
|
130
162
|
- **Trigger condition**: remote exists (`git remote -v | grep origin`) AND TODO.md exists with tasks BUT no `<!-- #N -->` markers found in TODO.md AND CLI is available (`gh --version` or `glab --version`) AND auth is valid (`gh auth status` or `glab auth status`)
|
|
131
163
|
- This means work was done locally without git integration (token was missing or platform wasn't set up), and now it's available
|
|
132
164
|
- Delegate to `devops-integrator` agent:
|
|
@@ -142,13 +174,13 @@ If no remote, skip this step.
|
|
|
142
174
|
- Log in MEMORY.md Notes: "Retroactive git sync completed — [N] issues created, [M] marked as done"
|
|
143
175
|
- Skip if any trigger condition is not met (no remote, no TODO.md, markers already present, CLI missing, auth invalid)
|
|
144
176
|
|
|
145
|
-
###
|
|
177
|
+
### 10. Ingest external issues (if git integration active)
|
|
146
178
|
- **Only run at phase transitions** — not every invocation
|
|
147
179
|
- Delegate to `devops-integrator` agent: "Run external issue intake — check for new issues on GitHub/GitLab that are not yet in TODO.md. Ingest bugs and feature requests."
|
|
148
180
|
- If new bugs are found with `critical` or `priority::high` label, they become the next task
|
|
149
181
|
- Skip if no git integration or platform is unreachable
|
|
150
182
|
|
|
151
|
-
###
|
|
183
|
+
### 11. One task per invocation
|
|
152
184
|
- Complete exactly ONE task per invocation, then end the session cleanly.
|
|
153
185
|
- The external loop (`autonomous.mjs` or ralph-loop) handles iteration control — you do NOT need to manage capacity, iteration counters, or session bounds.
|
|
154
186
|
- After committing the task (STEP 11) and post-merge (STEP 12), EXIT. Do NOT update MEMORY.md after merge — it was already committed in STEP 11 and arrived on main via merge. Do NOT ask whether to continue, do NOT wait for confirmation, do NOT start another task. Do NOT create any commits after merge.
|
|
@@ -333,7 +333,9 @@ async function main() {
|
|
|
333
333
|
const task = getCurrentTask(memory);
|
|
334
334
|
log.info(`Iteration ${i}/${opts.maxIterations} | Phase: ${phase} | Task: ${task}`);
|
|
335
335
|
// Run Claude
|
|
336
|
+
log.debug(`Spawning claude process...`);
|
|
336
337
|
const output = await runClaude(opts, log, { resumeSessionId, resumePrompt });
|
|
338
|
+
log.debug(`Claude process exited (code=${output.code}, category=${output.errorCategory}, duration=${formatDuration(output.durationMs)})`);
|
|
337
339
|
resumeSessionId = null;
|
|
338
340
|
resumePrompt = null;
|
|
339
341
|
if (stopping)
|
|
@@ -441,8 +443,10 @@ async function main() {
|
|
|
441
443
|
log.info(`Iteration ${i} done (no structured result, exit ${output.code}).`);
|
|
442
444
|
checkpoint.lastTaskSeen = task;
|
|
443
445
|
writeCheckpoint(opts.projectDir, checkpoint, log);
|
|
444
|
-
if (i < opts.maxIterations)
|
|
446
|
+
if (i < opts.maxIterations) {
|
|
447
|
+
log.info(`Next iteration in ${formatDuration(opts.delay)}...`);
|
|
445
448
|
await sleep(opts.delay, stoppingRef);
|
|
449
|
+
}
|
|
446
450
|
continue;
|
|
447
451
|
}
|
|
448
452
|
log.info(`Status: ${result.status} | Action: ${result.action}`);
|
|
@@ -496,6 +500,7 @@ async function main() {
|
|
|
496
500
|
}
|
|
497
501
|
writeCheckpoint(opts.projectDir, checkpoint, log);
|
|
498
502
|
if (i < opts.maxIterations && !stopping && result.status !== 'needs_input') {
|
|
503
|
+
log.info(`Next iteration in ${formatDuration(opts.delay)}...`);
|
|
499
504
|
await sleep(opts.delay, stoppingRef);
|
|
500
505
|
}
|
|
501
506
|
}
|
|
@@ -19,6 +19,9 @@ const TC = {
|
|
|
19
19
|
dim: '\x1b[2m', reset: '\x1b[0m',
|
|
20
20
|
cyan: '\x1b[36m', yellow: '\x1b[33m', green: '\x1b[32m', red: '\x1b[31m',
|
|
21
21
|
};
|
|
22
|
+
function ts() {
|
|
23
|
+
return `[${new Date().toLocaleTimeString('en-GB', { hour12: false })}] `;
|
|
24
|
+
}
|
|
22
25
|
const MAX_BUFFER = 1024 * 1024; // 1MB stdout buffer cap
|
|
23
26
|
const MAX_STDERR = 64 * 1024; // 64KB stderr cap
|
|
24
27
|
// ─── Stream event formatting ───
|
|
@@ -30,11 +33,11 @@ export function resetStreamState() {
|
|
|
30
33
|
const SILENT_EVENTS = new Set(['system', 'ping', 'result']);
|
|
31
34
|
function formatRateLimit(event) {
|
|
32
35
|
const resets = event.rate_limit_info?.resets_at ?? event.resets_at ?? '';
|
|
33
|
-
return `${TC.yellow}⏳ Rate limited${resets ? ` (resets: ${resets})` : ''}${TC.reset}\n`;
|
|
36
|
+
return `${ts()}${TC.yellow}⏳ Rate limited${resets ? ` (resets: ${resets})` : ''}${TC.reset}\n`;
|
|
34
37
|
}
|
|
35
38
|
function formatError(event) {
|
|
36
39
|
const msg = event.error?.message ?? (typeof event.message === 'string' ? event.message : '') ?? JSON.stringify(event);
|
|
37
|
-
return `${TC.red}✗ Error: ${msg}${TC.reset}\n`;
|
|
40
|
+
return `${ts()}${TC.red}✗ Error: ${msg}${TC.reset}\n`;
|
|
38
41
|
}
|
|
39
42
|
function getContentBlocks(event) {
|
|
40
43
|
const msg = event.message;
|
|
@@ -46,7 +49,7 @@ function formatToolUse(block) {
|
|
|
46
49
|
const input = block.input ?? {};
|
|
47
50
|
const detail = input.command ?? input.file_path ?? input.pattern ?? input.query ?? '';
|
|
48
51
|
const short = detail.slice(0, 120);
|
|
49
|
-
return `${TC.cyan}▶ ${block.name ?? 'tool'}${TC.reset}${short ? ` ${TC.dim}${short}${TC.reset}` : ''}`;
|
|
52
|
+
return `${ts()}${TC.cyan}▶ ${block.name ?? 'tool'}${TC.reset}${short ? ` ${TC.dim}${short}${TC.reset}` : ''}`;
|
|
50
53
|
}
|
|
51
54
|
function formatTextBlock(block) {
|
|
52
55
|
if (!block.text)
|
|
@@ -58,14 +61,15 @@ function formatTextBlock(block) {
|
|
|
58
61
|
if (!text)
|
|
59
62
|
return null;
|
|
60
63
|
_lastAssistantText = block.text;
|
|
61
|
-
|
|
64
|
+
// Timestamp on first line only (multi-line text shouldn't get per-line stamps)
|
|
65
|
+
return `${ts()}${text}`;
|
|
62
66
|
}
|
|
63
67
|
function formatThinking(block) {
|
|
64
68
|
if (!block.thinking)
|
|
65
69
|
return null;
|
|
66
70
|
const preview = block.thinking.slice(0, 200).replace(/\n/g, ' ');
|
|
67
71
|
const ellipsis = preview.length < block.thinking.length ? '...' : '';
|
|
68
|
-
return `${TC.dim}💭 ${preview}${ellipsis}${TC.reset}`;
|
|
72
|
+
return `${ts()}${TC.dim}💭 ${preview}${ellipsis}${TC.reset}`;
|
|
69
73
|
}
|
|
70
74
|
const BLOCK_FORMATTERS = {
|
|
71
75
|
tool_use: formatToolUse,
|
|
@@ -96,7 +100,7 @@ function formatUser(event) {
|
|
|
96
100
|
const icon = block.is_error ? `${TC.red}✗` : `${TC.green}✓`;
|
|
97
101
|
const text = typeof block.content === 'string' ? block.content : '';
|
|
98
102
|
const preview = text.slice(0, 150).replace(/\n/g, ' ');
|
|
99
|
-
return `${icon} ${TC.dim}${preview}${TC.reset}\n`;
|
|
103
|
+
return `${ts()}${icon} ${TC.dim}${preview}${TC.reset}\n`;
|
|
100
104
|
}
|
|
101
105
|
return null;
|
|
102
106
|
}
|
|
@@ -214,6 +218,10 @@ export function runClaude(opts, log, runOpts = {}) {
|
|
|
214
218
|
const pid = child.pid;
|
|
215
219
|
currentChild = { kill: () => { if (pid)
|
|
216
220
|
killProcessTree(pid, isWin); } };
|
|
221
|
+
// Prevent uncaught exception from stdin write errors (EPIPE on Windows)
|
|
222
|
+
if (child.stdin) {
|
|
223
|
+
child.stdin.on('error', (err) => { log.debug(`stdin error (expected if child exited): ${err.message}`); });
|
|
224
|
+
}
|
|
217
225
|
// ─── State ───
|
|
218
226
|
let stderr = '';
|
|
219
227
|
let sessionId = null;
|
|
@@ -363,6 +371,18 @@ export function runClaude(opts, log, runOpts = {}) {
|
|
|
363
371
|
// ─── Close ───
|
|
364
372
|
child.on('close', (code, signal) => {
|
|
365
373
|
cleanup();
|
|
374
|
+
// Windows: kill orphan child processes that may hold Claude locks.
|
|
375
|
+
// Only kill if the process tree is still alive (avoid PID reuse killing unrelated processes).
|
|
376
|
+
if (isWin && pid && !killed) {
|
|
377
|
+
try {
|
|
378
|
+
const taskInfo = execSync(`tasklist /FI "PID eq ${pid}" /NH`, { encoding: 'utf-8', stdio: 'pipe', timeout: 3000 });
|
|
379
|
+
// tasklist always exits 0; parse stdout — if PID is dead it prints "INFO: No tasks..."
|
|
380
|
+
if (taskInfo.includes(String(pid))) {
|
|
381
|
+
execSync(`taskkill /PID ${pid} /T /F`, { stdio: 'ignore', timeout: 5000 });
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
catch { /* already dead — expected */ }
|
|
385
|
+
}
|
|
366
386
|
// Flush remaining buffer
|
|
367
387
|
if (buffer.trim()) {
|
|
368
388
|
const event = parseStreamEvent(buffer.replace(/\r/g, ''));
|