create-claude-workspace 2.3.31 → 2.3.33
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/README.md +24 -7
- package/dist/index.js +7 -0
- package/dist/scheduler/index.mjs +76 -18
- package/dist/scheduler/loop.mjs +6 -2
- package/dist/scheduler/util/idle-poll.mjs +4 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -28,9 +28,11 @@ Answer the discovery questions (non-technical). Claude generates the full pipeli
|
|
|
28
28
|
### Start Autonomous Development
|
|
29
29
|
|
|
30
30
|
```bash
|
|
31
|
-
npx create-claude-workspace
|
|
32
|
-
npx create-claude-workspace
|
|
33
|
-
npx create-claude-workspace
|
|
31
|
+
npx create-claude-workspace run # start (1 agent)
|
|
32
|
+
npx create-claude-workspace run --concurrency 3 # 3 parallel agents
|
|
33
|
+
npx create-claude-workspace run -i # interactive mode (user input only)
|
|
34
|
+
npx create-claude-workspace run --task-mode local # force local task mode
|
|
35
|
+
npx create-claude-workspace run --resume # resume from saved state
|
|
34
36
|
```
|
|
35
37
|
|
|
36
38
|
Or interactively: `claude --agent orchestrator`, then `/ralph-loop:ralph-loop Continue autonomous development according to CLAUDE.md`
|
|
@@ -45,12 +47,27 @@ The scheduler replaces the single-orchestrator model with parallel agent executi
|
|
|
45
47
|
- **N agents in parallel** — independent tasks run concurrently in separate git worktrees
|
|
46
48
|
|
|
47
49
|
```bash
|
|
48
|
-
npx create-claude-workspace
|
|
49
|
-
npx create-claude-workspace
|
|
50
|
-
npx create-claude-workspace
|
|
51
|
-
npx create-claude-workspace
|
|
50
|
+
npx create-claude-workspace run # start (1 agent)
|
|
51
|
+
npx create-claude-workspace run --concurrency 3 # 3 parallel agents
|
|
52
|
+
npx create-claude-workspace run -i # interactive mode
|
|
53
|
+
npx create-claude-workspace run --task-mode local # force local mode
|
|
54
|
+
npx create-claude-workspace run --help # all options
|
|
52
55
|
```
|
|
53
56
|
|
|
57
|
+
#### Task modes
|
|
58
|
+
|
|
59
|
+
The scheduler supports three task modes (`--task-mode`):
|
|
60
|
+
|
|
61
|
+
| Mode | Source | Use case |
|
|
62
|
+
|------|--------|----------|
|
|
63
|
+
| **local** | `TODO.md` / `tasks.json` | Solo development, no issue tracker |
|
|
64
|
+
| **platform** | GitHub/GitLab issues | Team workflows with issue tracking |
|
|
65
|
+
| **interactive** | User TUI input only | Ad-hoc tasks, exploration, prototyping |
|
|
66
|
+
|
|
67
|
+
Auto-detected at startup: if the repo has a remote with `status::` labeled issues → `platform`, otherwise → `local`. Override with `--task-mode` flag or set `Task Platform` in CLAUDE.md via `project-initializer`.
|
|
68
|
+
|
|
69
|
+
The `--interactive` (`-i`) flag implies `--task-mode interactive`. Type task descriptions directly in the TUI — each becomes a task that goes through the full pipeline (plan → implement → test → review → commit). Pending tasks are shown in the status bar queue.
|
|
70
|
+
|
|
54
71
|
#### Communicate at runtime
|
|
55
72
|
|
|
56
73
|
Write to `.claude/scheduler/inbox.json` while the scheduler is running:
|
package/dist/index.js
CHANGED
|
@@ -70,6 +70,8 @@ ${C.b}Scheduler options:${C.n}
|
|
|
70
70
|
--concurrency <n> Parallel agents (default: 1)
|
|
71
71
|
--max-iterations <n> Max iterations (default: 50)
|
|
72
72
|
--max-turns <n> Max turns per agent invocation (default: 50)
|
|
73
|
+
--task-mode <mode> Task source: local, platform, or interactive
|
|
74
|
+
--interactive, -i Interactive mode — tasks from user input only
|
|
73
75
|
--resume Resume from saved state
|
|
74
76
|
--dry-run Validate prerequisites only
|
|
75
77
|
See 'npx create-claude-workspace run --help' for all options.
|
|
@@ -78,6 +80,8 @@ ${C.b}Examples:${C.n}
|
|
|
78
80
|
npx create-claude-workspace # scaffold in current directory
|
|
79
81
|
npx create-claude-workspace run # start scheduler
|
|
80
82
|
npx create-claude-workspace run --concurrency 3 # 3 parallel agents
|
|
83
|
+
npx create-claude-workspace run -i # interactive mode (user input only)
|
|
84
|
+
npx create-claude-workspace run --task-mode local # force local task mode
|
|
81
85
|
npx create-claude-workspace run --resume # resume from state
|
|
82
86
|
npx create-claude-workspace validate # check prerequisites
|
|
83
87
|
|
|
@@ -244,6 +248,9 @@ async function main() {
|
|
|
244
248
|
console.log(` ${C.d}# Multi-agent scheduler${C.n}`);
|
|
245
249
|
console.log(` npx create-claude-workspace run`);
|
|
246
250
|
console.log('');
|
|
251
|
+
console.log(` ${C.d}# Interactive mode (tasks from user input)${C.n}`);
|
|
252
|
+
console.log(` npx create-claude-workspace run -i`);
|
|
253
|
+
console.log('');
|
|
247
254
|
console.log(` ${C.d}# With parallel agents${C.n}`);
|
|
248
255
|
console.log(` npx create-claude-workspace run --concurrency 3`);
|
|
249
256
|
console.log('');
|
package/dist/scheduler/index.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// ─── Multi-agent scheduler entry point ───
|
|
3
3
|
// Replaces the single-orchestrator autonomous loop with parallel agent execution.
|
|
4
4
|
import { resolve } from 'node:path';
|
|
5
|
-
import { existsSync, mkdirSync } from 'node:fs';
|
|
5
|
+
import { existsSync, mkdirSync, readFileSync } from 'node:fs';
|
|
6
6
|
import { config as dotenvConfig } from '@dotenvx/dotenvx';
|
|
7
7
|
import { SCHEDULER_DEFAULTS } from './types.mjs';
|
|
8
8
|
import { detectCIPlatform, checkPlatformCLI, installPlatformCLI } from './git/ci-watcher.mjs';
|
|
@@ -15,6 +15,7 @@ import { checkAuth } from './agents/health-checker.mjs';
|
|
|
15
15
|
import { pollForNewWork } from './util/idle-poll.mjs';
|
|
16
16
|
import { TUI } from './ui/tui.mjs';
|
|
17
17
|
import { DiscoveredIssueStore } from './tools/report-issue.mjs';
|
|
18
|
+
import { readInbox, writeToInbox } from './tasks/inbox.mjs';
|
|
18
19
|
// ─── Args ───
|
|
19
20
|
export function parseSchedulerArgs(argv) {
|
|
20
21
|
const opts = { ...SCHEDULER_DEFAULTS };
|
|
@@ -131,8 +132,8 @@ export function parseSchedulerArgs(argv) {
|
|
|
131
132
|
}
|
|
132
133
|
if (arg === '--task-mode') {
|
|
133
134
|
const val = strFlag(arg, i);
|
|
134
|
-
if (val !== 'local' && val !== 'platform') {
|
|
135
|
-
console.error(`--task-mode must be 'local' or '
|
|
135
|
+
if (val !== 'local' && val !== 'platform' && val !== 'interactive') {
|
|
136
|
+
console.error(`--task-mode must be 'local', 'platform', or 'interactive'`);
|
|
136
137
|
process.exit(1);
|
|
137
138
|
}
|
|
138
139
|
mutable.taskMode = val;
|
|
@@ -146,10 +147,32 @@ export function parseSchedulerArgs(argv) {
|
|
|
146
147
|
}
|
|
147
148
|
return opts;
|
|
148
149
|
}
|
|
150
|
+
function readTaskPlatformFromClaudeMd(projectDir) {
|
|
151
|
+
for (const p of ['CLAUDE.md', '.claude/CLAUDE.md']) {
|
|
152
|
+
const fp = resolve(projectDir, p);
|
|
153
|
+
if (!existsSync(fp))
|
|
154
|
+
continue;
|
|
155
|
+
try {
|
|
156
|
+
const content = readFileSync(fp, 'utf-8');
|
|
157
|
+
const match = content.match(/^\s*-\s*\*\*Task Platform\*\*:\s*(\S+)/m);
|
|
158
|
+
if (match)
|
|
159
|
+
return match[1].toLowerCase();
|
|
160
|
+
}
|
|
161
|
+
catch { /* ignore */ }
|
|
162
|
+
}
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
149
165
|
function detectTaskMode(projectDir) {
|
|
166
|
+
// Respect explicit Task Platform setting from CLAUDE.md
|
|
167
|
+
const claudeMdPlatform = readTaskPlatformFromClaudeMd(projectDir);
|
|
168
|
+
if (claudeMdPlatform === 'local')
|
|
169
|
+
return 'local';
|
|
150
170
|
const platform = detectCIPlatform(projectDir);
|
|
151
171
|
if (platform === 'none')
|
|
152
172
|
return 'local';
|
|
173
|
+
// CLAUDE.md says github/gitlab → platform mode
|
|
174
|
+
if (claudeMdPlatform === 'github' || claudeMdPlatform === 'gitlab')
|
|
175
|
+
return 'platform';
|
|
153
176
|
try {
|
|
154
177
|
const issues = fetchOpenIssues(projectDir, platform);
|
|
155
178
|
if (issues.length === 0)
|
|
@@ -190,7 +213,8 @@ Options:
|
|
|
190
213
|
--no-pull Skip auto git pull
|
|
191
214
|
--idle-poll <ms> Poll interval when idle (default: 300000)
|
|
192
215
|
--max-idle <ms> Max idle time before exit (default: 0 = unlimited)
|
|
193
|
-
--
|
|
216
|
+
--task-mode <mode> Task source: local, platform, or interactive
|
|
217
|
+
--interactive, -i Interactive mode — tasks from user input only (implies --task-mode interactive)
|
|
194
218
|
--dry-run Validate prerequisites only
|
|
195
219
|
--help Show this message
|
|
196
220
|
`);
|
|
@@ -225,6 +249,13 @@ export async function runScheduler(opts) {
|
|
|
225
249
|
const logPath = resolve(opts.projectDir, opts.logFile);
|
|
226
250
|
const tui = new TUI(logPath, opts.interactive);
|
|
227
251
|
tui.setTopAgent('scheduler');
|
|
252
|
+
// Wire TUI input → inbox (interactive mode: user text becomes tasks)
|
|
253
|
+
if (opts.interactive) {
|
|
254
|
+
tui.setInputHandler((text) => {
|
|
255
|
+
writeToInbox(opts.projectDir, [{ type: 'add-task', title: text }]);
|
|
256
|
+
tui.addPendingInput(text); // tracks in status bar queue + logs "Queued: text"
|
|
257
|
+
});
|
|
258
|
+
}
|
|
228
259
|
const logger = {
|
|
229
260
|
info: (m) => tui.info(m),
|
|
230
261
|
warn: (m) => tui.warn(m),
|
|
@@ -304,7 +335,12 @@ export async function runScheduler(opts) {
|
|
|
304
335
|
}
|
|
305
336
|
// Task mode detection — always re-detect unless user explicitly set --task-mode
|
|
306
337
|
// (stored state may be stale if CLI was installed/authenticated since last run)
|
|
307
|
-
if (opts.taskMode) {
|
|
338
|
+
if (opts.interactive && !opts.taskMode) {
|
|
339
|
+
// Interactive flag implies interactive task mode (no issues, no TODO)
|
|
340
|
+
state.taskMode = 'interactive';
|
|
341
|
+
logger.info('Task mode: interactive (user input only)');
|
|
342
|
+
}
|
|
343
|
+
else if (opts.taskMode) {
|
|
308
344
|
state.taskMode = opts.taskMode;
|
|
309
345
|
logger.info(`Task mode (from flag): ${state.taskMode}`);
|
|
310
346
|
}
|
|
@@ -369,6 +405,15 @@ export async function runScheduler(opts) {
|
|
|
369
405
|
};
|
|
370
406
|
process.on('SIGINT', cleanup);
|
|
371
407
|
process.on('SIGTERM', cleanup);
|
|
408
|
+
// Wire TUI hotkeys → scheduler control
|
|
409
|
+
if (opts.interactive) {
|
|
410
|
+
tui.setHotkeyHandler((action) => {
|
|
411
|
+
if (action === 'stop' || action === 'quit') {
|
|
412
|
+
stopping = true;
|
|
413
|
+
stoppingRef.value = true;
|
|
414
|
+
}
|
|
415
|
+
});
|
|
416
|
+
}
|
|
372
417
|
// ─── Loop ───
|
|
373
418
|
for (let i = state.iteration; i < opts.maxIterations && !stopping; i++) {
|
|
374
419
|
tui.setIteration(i + 1, opts.maxIterations);
|
|
@@ -384,23 +429,36 @@ export async function runScheduler(opts) {
|
|
|
384
429
|
onMessage,
|
|
385
430
|
onSpawnStart,
|
|
386
431
|
onSpawnEnd,
|
|
432
|
+
onInboxTaskPicked: opts.interactive ? () => tui.consumePendingInput() : undefined,
|
|
387
433
|
});
|
|
388
434
|
if (!workDone) {
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
435
|
+
if (state.taskMode === 'interactive') {
|
|
436
|
+
// Interactive mode: wait for user input (no external polling)
|
|
437
|
+
logger.info('Waiting for input...');
|
|
438
|
+
while (!stopping) {
|
|
439
|
+
const pending = readInbox(opts.projectDir);
|
|
440
|
+
if (pending.length > 0 || discoveredIssueStore.peek().length > 0)
|
|
441
|
+
break;
|
|
442
|
+
await sleep(500, stoppingRef);
|
|
397
443
|
}
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
444
|
+
}
|
|
445
|
+
else {
|
|
446
|
+
// No work → idle polling (platform/local)
|
|
447
|
+
logger.info('No work available. Entering idle polling...');
|
|
448
|
+
const idleStart = Date.now();
|
|
449
|
+
while (!stopping) {
|
|
450
|
+
const poll = await pollForNewWork(opts.projectDir, logger, state.taskMode, state.completedTasks);
|
|
451
|
+
if (poll.hasWork) {
|
|
452
|
+
logger.info(`New work detected (${poll.source}). Resuming...`);
|
|
453
|
+
break;
|
|
454
|
+
}
|
|
455
|
+
if (opts.maxIdleTime > 0 && Date.now() - idleStart >= opts.maxIdleTime) {
|
|
456
|
+
logger.info('Max idle time reached. Exiting.');
|
|
457
|
+
stopping = true;
|
|
458
|
+
break;
|
|
459
|
+
}
|
|
460
|
+
await sleep(opts.idlePollInterval, stoppingRef);
|
|
402
461
|
}
|
|
403
|
-
await sleep(opts.idlePollInterval, stoppingRef);
|
|
404
462
|
}
|
|
405
463
|
continue;
|
|
406
464
|
}
|
package/dist/scheduler/loop.mjs
CHANGED
|
@@ -40,6 +40,7 @@ export async function runIteration(deps) {
|
|
|
40
40
|
const newTask = addTaskMessageToTask(addMsg, state.currentPhase, nextId);
|
|
41
41
|
inboxTasks.push(newTask);
|
|
42
42
|
logger.info(`[inbox] New task: ${newTask.title}`);
|
|
43
|
+
deps.onInboxTaskPicked?.(newTask.title);
|
|
43
44
|
appendEvent(projectDir, createEvent('task_started', { taskId: nextId, detail: `inbox: ${newTask.title}` }));
|
|
44
45
|
}
|
|
45
46
|
else if (msg.type === 'message') {
|
|
@@ -79,9 +80,12 @@ export async function runIteration(deps) {
|
|
|
79
80
|
state._recoveryDone = true;
|
|
80
81
|
await recoverOrphanedWorktrees(projectDir, state, logger, deps);
|
|
81
82
|
}
|
|
82
|
-
// Load tasks (
|
|
83
|
+
// Load tasks (triple mode: interactive = inbox only, platform = issues, local = TODO.md)
|
|
83
84
|
let tasks;
|
|
84
|
-
if (state.taskMode === '
|
|
85
|
+
if (state.taskMode === 'interactive') {
|
|
86
|
+
tasks = []; // No external sources — only inbox + discovered
|
|
87
|
+
}
|
|
88
|
+
else if (state.taskMode === 'platform') {
|
|
85
89
|
const platform = detectCIPlatform(projectDir);
|
|
86
90
|
if (platform === 'none') {
|
|
87
91
|
logger.error('Platform mode requires a git remote (github/gitlab)');
|
|
@@ -171,6 +171,10 @@ function readTodoSafe(projectDir) {
|
|
|
171
171
|
}
|
|
172
172
|
// ─── Main poll function ───
|
|
173
173
|
export async function pollForNewWork(projectDir, log, taskMode = 'local', completedTasks = []) {
|
|
174
|
+
// Interactive mode: no external sources to poll — caller handles inbox directly
|
|
175
|
+
if (taskMode === 'interactive') {
|
|
176
|
+
return { hasWork: false, source: null, issueCount: 0 };
|
|
177
|
+
}
|
|
174
178
|
// Platform mode: check issues directly via CLI
|
|
175
179
|
if (taskMode === 'platform') {
|
|
176
180
|
return pollPlatformIssues(projectDir, log, completedTasks);
|