create-claude-workspace 2.3.30 → 2.3.32
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.
|
@@ -74,6 +74,14 @@ export function getPRStatus(cwd, platform, branch) {
|
|
|
74
74
|
}
|
|
75
75
|
return getGitLabMRStatus(cwd, branch);
|
|
76
76
|
}
|
|
77
|
+
/** Like getPRStatus but uses the MR/PR number directly (more reliable than branch name lookup). */
|
|
78
|
+
export function getPRStatusByNumber(cwd, platform, prNumber) {
|
|
79
|
+
const id = String(prNumber);
|
|
80
|
+
if (platform === 'github') {
|
|
81
|
+
return getGitHubPRStatus(cwd, id);
|
|
82
|
+
}
|
|
83
|
+
return getGitLabMRStatus(cwd, id);
|
|
84
|
+
}
|
|
77
85
|
function getGitHubPRStatus(cwd, branch) {
|
|
78
86
|
const output = run('gh', [
|
|
79
87
|
'pr', 'view', branch,
|
package/dist/scheduler/index.mjs
CHANGED
|
@@ -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;
|
|
@@ -190,7 +191,8 @@ Options:
|
|
|
190
191
|
--no-pull Skip auto git pull
|
|
191
192
|
--idle-poll <ms> Poll interval when idle (default: 300000)
|
|
192
193
|
--max-idle <ms> Max idle time before exit (default: 0 = unlimited)
|
|
193
|
-
--
|
|
194
|
+
--task-mode <mode> Task source: local, platform, or interactive
|
|
195
|
+
--interactive, -i Interactive mode — tasks from user input only (implies --task-mode interactive)
|
|
194
196
|
--dry-run Validate prerequisites only
|
|
195
197
|
--help Show this message
|
|
196
198
|
`);
|
|
@@ -225,6 +227,13 @@ export async function runScheduler(opts) {
|
|
|
225
227
|
const logPath = resolve(opts.projectDir, opts.logFile);
|
|
226
228
|
const tui = new TUI(logPath, opts.interactive);
|
|
227
229
|
tui.setTopAgent('scheduler');
|
|
230
|
+
// Wire TUI input → inbox (interactive mode: user text becomes tasks)
|
|
231
|
+
if (opts.interactive) {
|
|
232
|
+
tui.setInputHandler((text) => {
|
|
233
|
+
writeToInbox(opts.projectDir, [{ type: 'add-task', title: text }]);
|
|
234
|
+
tui.info(`Task queued: ${text}`);
|
|
235
|
+
});
|
|
236
|
+
}
|
|
228
237
|
const logger = {
|
|
229
238
|
info: (m) => tui.info(m),
|
|
230
239
|
warn: (m) => tui.warn(m),
|
|
@@ -304,7 +313,12 @@ export async function runScheduler(opts) {
|
|
|
304
313
|
}
|
|
305
314
|
// Task mode detection — always re-detect unless user explicitly set --task-mode
|
|
306
315
|
// (stored state may be stale if CLI was installed/authenticated since last run)
|
|
307
|
-
if (opts.taskMode) {
|
|
316
|
+
if (opts.interactive && !opts.taskMode) {
|
|
317
|
+
// Interactive flag implies interactive task mode (no issues, no TODO)
|
|
318
|
+
state.taskMode = 'interactive';
|
|
319
|
+
logger.info('Task mode: interactive (user input only)');
|
|
320
|
+
}
|
|
321
|
+
else if (opts.taskMode) {
|
|
308
322
|
state.taskMode = opts.taskMode;
|
|
309
323
|
logger.info(`Task mode (from flag): ${state.taskMode}`);
|
|
310
324
|
}
|
|
@@ -369,6 +383,15 @@ export async function runScheduler(opts) {
|
|
|
369
383
|
};
|
|
370
384
|
process.on('SIGINT', cleanup);
|
|
371
385
|
process.on('SIGTERM', cleanup);
|
|
386
|
+
// Wire TUI hotkeys → scheduler control
|
|
387
|
+
if (opts.interactive) {
|
|
388
|
+
tui.setHotkeyHandler((action) => {
|
|
389
|
+
if (action === 'stop' || action === 'quit') {
|
|
390
|
+
stopping = true;
|
|
391
|
+
stoppingRef.value = true;
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
}
|
|
372
395
|
// ─── Loop ───
|
|
373
396
|
for (let i = state.iteration; i < opts.maxIterations && !stopping; i++) {
|
|
374
397
|
tui.setIteration(i + 1, opts.maxIterations);
|
|
@@ -386,21 +409,33 @@ export async function runScheduler(opts) {
|
|
|
386
409
|
onSpawnEnd,
|
|
387
410
|
});
|
|
388
411
|
if (!workDone) {
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
412
|
+
if (state.taskMode === 'interactive') {
|
|
413
|
+
// Interactive mode: wait for user input (no external polling)
|
|
414
|
+
logger.info('Waiting for input...');
|
|
415
|
+
while (!stopping) {
|
|
416
|
+
const pending = readInbox(opts.projectDir);
|
|
417
|
+
if (pending.length > 0 || discoveredIssueStore.peek().length > 0)
|
|
418
|
+
break;
|
|
419
|
+
await sleep(500, stoppingRef);
|
|
397
420
|
}
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
421
|
+
}
|
|
422
|
+
else {
|
|
423
|
+
// No work → idle polling (platform/local)
|
|
424
|
+
logger.info('No work available. Entering idle polling...');
|
|
425
|
+
const idleStart = Date.now();
|
|
426
|
+
while (!stopping) {
|
|
427
|
+
const poll = await pollForNewWork(opts.projectDir, logger, state.taskMode, state.completedTasks);
|
|
428
|
+
if (poll.hasWork) {
|
|
429
|
+
logger.info(`New work detected (${poll.source}). Resuming...`);
|
|
430
|
+
break;
|
|
431
|
+
}
|
|
432
|
+
if (opts.maxIdleTime > 0 && Date.now() - idleStart >= opts.maxIdleTime) {
|
|
433
|
+
logger.info('Max idle time reached. Exiting.');
|
|
434
|
+
stopping = true;
|
|
435
|
+
break;
|
|
436
|
+
}
|
|
437
|
+
await sleep(opts.idlePollInterval, stoppingRef);
|
|
402
438
|
}
|
|
403
|
-
await sleep(opts.idlePollInterval, stoppingRef);
|
|
404
439
|
}
|
|
405
440
|
continue;
|
|
406
441
|
}
|
package/dist/scheduler/loop.mjs
CHANGED
|
@@ -10,7 +10,7 @@ import { buildGraph, getParallelBatches, isPhaseComplete, getNextPhase, isProjec
|
|
|
10
10
|
import { writeState, appendEvent, createEvent, rotateLog } from './state/state.mjs';
|
|
11
11
|
import { recordSession, getSession, clearSession } from './state/session.mjs';
|
|
12
12
|
import { createWorktree, commitInWorktree, getChangedFiles, cleanupWorktree, mergeToMain, syncMain, pushWorktree, forcePushWorktree, rebaseOnMain, cleanMainForMerge, popStash, getMainBranch, listWorktrees, listOrphanedWorktrees, isBranchMerged, getCurrentBranch, hasUncommittedChanges, deleteBranchRemote, cleanMergedBranches, pruneRemoteBranches, findStaleUnmergedBranches, } from './git/manager.mjs';
|
|
13
|
-
import { createPR, getPRStatus, getPRComments, mergePR, closePR } from './git/pr-manager.mjs';
|
|
13
|
+
import { createPR, getPRStatus, getPRStatusByNumber, getPRComments, mergePR, closePR } from './git/pr-manager.mjs';
|
|
14
14
|
import { scanAgents } from './agents/health-checker.mjs';
|
|
15
15
|
import { detectCIPlatform, fetchFailureLogs } from './git/ci-watcher.mjs';
|
|
16
16
|
import { createRelease } from './git/release.mjs';
|
|
@@ -79,9 +79,12 @@ export async function runIteration(deps) {
|
|
|
79
79
|
state._recoveryDone = true;
|
|
80
80
|
await recoverOrphanedWorktrees(projectDir, state, logger, deps);
|
|
81
81
|
}
|
|
82
|
-
// Load tasks (
|
|
82
|
+
// Load tasks (triple mode: interactive = inbox only, platform = issues, local = TODO.md)
|
|
83
83
|
let tasks;
|
|
84
|
-
if (state.taskMode === '
|
|
84
|
+
if (state.taskMode === 'interactive') {
|
|
85
|
+
tasks = []; // No external sources — only inbox + discovered
|
|
86
|
+
}
|
|
87
|
+
else if (state.taskMode === 'platform') {
|
|
85
88
|
const platform = detectCIPlatform(projectDir);
|
|
86
89
|
if (platform === 'none') {
|
|
87
90
|
logger.error('Platform mode requires a git remote (github/gitlab)');
|
|
@@ -1230,7 +1233,8 @@ async function checkPRWatch(taskId, pipeline, projectDir, agents, deps) {
|
|
|
1230
1233
|
return 'failed';
|
|
1231
1234
|
const branch = pipeline.branchSlug;
|
|
1232
1235
|
try {
|
|
1233
|
-
|
|
1236
|
+
// Use PR number (more reliable than branch name lookup which can fail on glab)
|
|
1237
|
+
const prStatus = getPRStatusByNumber(projectDir, ciPlatform, pipeline.prState.prNumber);
|
|
1234
1238
|
if (prStatus.status === 'merged')
|
|
1235
1239
|
return 'merged';
|
|
1236
1240
|
// PR was closed (not merged) — try local merge as fallback before giving up
|
|
@@ -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);
|