taskplane 0.29.2 → 0.30.0
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/bin/gitignore-patterns.mjs +11 -8
- package/bin/rpc-wrapper.mjs +410 -357
- package/bin/taskplane.mjs +533 -250
- package/extensions/reviewer-extension.ts +17 -11
- package/extensions/taskplane/abort.ts +50 -18
- package/extensions/taskplane/agent-bridge-extension.ts +232 -105
- package/extensions/taskplane/agent-host.ts +224 -97
- package/extensions/taskplane/cleanup.ts +71 -42
- package/extensions/taskplane/config-loader.ts +142 -58
- package/extensions/taskplane/config-schema.ts +6 -13
- package/extensions/taskplane/config.ts +10 -2
- package/extensions/taskplane/diagnostic-reports.ts +59 -47
- package/extensions/taskplane/diagnostics.ts +13 -13
- package/extensions/taskplane/discovery.ts +35 -61
- package/extensions/taskplane/engine-worker.ts +53 -46
- package/extensions/taskplane/engine.ts +1760 -602
- package/extensions/taskplane/execution.ts +426 -206
- package/extensions/taskplane/extension.ts +1073 -598
- package/extensions/taskplane/formatting.ts +136 -124
- package/extensions/taskplane/git.ts +0 -2
- package/extensions/taskplane/lane-runner.ts +542 -311
- package/extensions/taskplane/mailbox.ts +57 -49
- package/extensions/taskplane/merge.ts +662 -383
- package/extensions/taskplane/messages.ts +109 -51
- package/extensions/taskplane/migrations.ts +1 -1
- package/extensions/taskplane/path-resolver.ts +8 -9
- package/extensions/taskplane/persistence.ts +425 -262
- package/extensions/taskplane/process-registry.ts +36 -7
- package/extensions/taskplane/quality-gate.ts +107 -55
- package/extensions/taskplane/resume.ts +774 -267
- package/extensions/taskplane/sessions.ts +1 -1
- package/extensions/taskplane/settings-tui.ts +505 -164
- package/extensions/taskplane/sidecar-telemetry.ts +25 -10
- package/extensions/taskplane/supervisor.ts +477 -270
- package/extensions/taskplane/task-executor-core.ts +178 -53
- package/extensions/taskplane/types.ts +186 -108
- package/extensions/taskplane/verification.ts +27 -22
- package/extensions/taskplane/waves.ts +59 -43
- package/extensions/taskplane/workspace.ts +14 -12
- package/extensions/taskplane/worktree.ts +218 -196
- package/package.json +14 -2
|
@@ -66,7 +66,6 @@ export const CONFIG_VERSION = 1;
|
|
|
66
66
|
*/
|
|
67
67
|
export const PROJECT_CONFIG_FILENAME = "taskplane-config.json";
|
|
68
68
|
|
|
69
|
-
|
|
70
69
|
// ── Task Runner Section Interfaces ───────────────────────────────────
|
|
71
70
|
|
|
72
71
|
/** Project metadata */
|
|
@@ -207,7 +206,6 @@ export interface QualityGateConfig {
|
|
|
207
206
|
passThreshold: PassThreshold;
|
|
208
207
|
}
|
|
209
208
|
|
|
210
|
-
|
|
211
209
|
// ── Task Runner Combined Section ─────────────────────────────────────
|
|
212
210
|
|
|
213
211
|
/**
|
|
@@ -257,7 +255,6 @@ export interface TaskRunnerSection {
|
|
|
257
255
|
modelFallback: ModelFallbackMode;
|
|
258
256
|
}
|
|
259
257
|
|
|
260
|
-
|
|
261
258
|
// ── Orchestrator Section Interfaces ──────────────────────────────────
|
|
262
259
|
|
|
263
260
|
/** Core orchestrator settings */
|
|
@@ -393,7 +390,6 @@ export interface VerificationConfig {
|
|
|
393
390
|
flakyReruns: number;
|
|
394
391
|
}
|
|
395
392
|
|
|
396
|
-
|
|
397
393
|
// ── Orchestrator Combined Section ────────────────────────────────────
|
|
398
394
|
|
|
399
395
|
/**
|
|
@@ -428,7 +424,6 @@ export interface OrchestratorSection {
|
|
|
428
424
|
supervisor: SupervisorSectionConfig;
|
|
429
425
|
}
|
|
430
426
|
|
|
431
|
-
|
|
432
427
|
// ── Workspace Section Interfaces ─────────────────────────────────────
|
|
433
428
|
|
|
434
429
|
/** Workspace repo definition (JSON config shape). */
|
|
@@ -459,7 +454,6 @@ export interface WorkspaceSectionConfig {
|
|
|
459
454
|
routing: WorkspaceRoutingSectionConfig;
|
|
460
455
|
}
|
|
461
456
|
|
|
462
|
-
|
|
463
457
|
// ── Unified Config ───────────────────────────────────────────────────
|
|
464
458
|
|
|
465
459
|
/**
|
|
@@ -491,7 +485,6 @@ export interface TaskplaneConfig {
|
|
|
491
485
|
workspace?: WorkspaceSectionConfig;
|
|
492
486
|
}
|
|
493
487
|
|
|
494
|
-
|
|
495
488
|
// ── Global Preferences (Layer 2) ─────────────────────────────────────
|
|
496
489
|
|
|
497
490
|
/**
|
|
@@ -527,11 +520,12 @@ export interface InitAgentDefaultsPreferences {
|
|
|
527
520
|
mergeThinking?: string;
|
|
528
521
|
}
|
|
529
522
|
|
|
530
|
-
export type DeepPartial<T> =
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
523
|
+
export type DeepPartial<T> =
|
|
524
|
+
T extends Array<infer U>
|
|
525
|
+
? Array<DeepPartial<U>>
|
|
526
|
+
: T extends object
|
|
527
|
+
? { [K in keyof T]?: DeepPartial<T[K]> }
|
|
528
|
+
: T;
|
|
535
529
|
|
|
536
530
|
export interface GlobalPreferences {
|
|
537
531
|
/**
|
|
@@ -590,7 +584,6 @@ export const GLOBAL_PREFERENCES_FILENAME = "preferences.json";
|
|
|
590
584
|
*/
|
|
591
585
|
export const GLOBAL_PREFERENCES_SUBDIR = "taskplane";
|
|
592
586
|
|
|
593
|
-
|
|
594
587
|
// ── Defaults ─────────────────────────────────────────────────────────
|
|
595
588
|
|
|
596
589
|
/** Default task runner section values */
|
|
@@ -11,7 +11,12 @@
|
|
|
11
11
|
* @module orch/config
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
loadProjectConfig,
|
|
16
|
+
toOrchestratorConfig,
|
|
17
|
+
toTaskRunnerConfig,
|
|
18
|
+
hasConfigFiles,
|
|
19
|
+
} from "./config-loader.ts";
|
|
15
20
|
export { hasConfigFiles, resolveConfigRoot } from "./config-loader.ts";
|
|
16
21
|
import type { OrchestratorConfig, TaskRunnerConfig } from "./types.ts";
|
|
17
22
|
import type { SupervisorConfig } from "./supervisor.ts";
|
|
@@ -31,7 +36,10 @@ import { DEFAULT_SUPERVISOR_CONFIG } from "./supervisor.ts";
|
|
|
31
36
|
*
|
|
32
37
|
* Returns the legacy `OrchestratorConfig` (snake_case) shape.
|
|
33
38
|
*/
|
|
34
|
-
export function loadOrchestratorConfig(
|
|
39
|
+
export function loadOrchestratorConfig(
|
|
40
|
+
cwd: string,
|
|
41
|
+
pointerConfigRoot?: string,
|
|
42
|
+
): OrchestratorConfig {
|
|
35
43
|
const unified = loadProjectConfig(cwd, pointerConfigRoot);
|
|
36
44
|
return toOrchestratorConfig(unified);
|
|
37
45
|
}
|
|
@@ -15,7 +15,15 @@ import { join } from "path";
|
|
|
15
15
|
|
|
16
16
|
import { execLog } from "./execution.ts";
|
|
17
17
|
import { resolveOperatorId } from "./naming.ts";
|
|
18
|
-
import type {
|
|
18
|
+
import type {
|
|
19
|
+
AllocatedLane,
|
|
20
|
+
LaneTaskOutcome,
|
|
21
|
+
OrchBatchRuntimeState,
|
|
22
|
+
OrchestratorConfig,
|
|
23
|
+
PersistedTaskRecord,
|
|
24
|
+
BatchDiagnostics,
|
|
25
|
+
PersistedTaskExitSummary,
|
|
26
|
+
} from "./types.ts";
|
|
19
27
|
import { defaultBatchDiagnostics } from "./types.ts";
|
|
20
28
|
|
|
21
29
|
// ── Types ────────────────────────────────────────────────────────────
|
|
@@ -173,7 +181,7 @@ export function buildDiagnosticEvents(input: DiagnosticReportInput): DiagnosticE
|
|
|
173
181
|
* Serialize diagnostic events to JSONL format (one JSON object per line).
|
|
174
182
|
*/
|
|
175
183
|
export function eventsToJsonl(events: DiagnosticEvent[]): string {
|
|
176
|
-
return events.map(e => JSON.stringify(e)).join("\n") + "\n";
|
|
184
|
+
return events.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
177
185
|
}
|
|
178
186
|
|
|
179
187
|
// ── Human-Readable Summary ───────────────────────────────────────────
|
|
@@ -206,7 +214,10 @@ function formatCost(cost: number): string {
|
|
|
206
214
|
/**
|
|
207
215
|
* Generate a human-readable markdown summary report.
|
|
208
216
|
*/
|
|
209
|
-
export function buildMarkdownReport(
|
|
217
|
+
export function buildMarkdownReport(
|
|
218
|
+
input: DiagnosticReportInput,
|
|
219
|
+
events: DiagnosticEvent[],
|
|
220
|
+
): string {
|
|
210
221
|
const { batchId, phase, mode, startedAt, endedAt, diagnostics } = input;
|
|
211
222
|
const { succeededTasks, failedTasks, skippedTasks, blockedTasks, totalTasks } = input;
|
|
212
223
|
|
|
@@ -248,7 +259,7 @@ export function buildMarkdownReport(input: DiagnosticReportInput, events: Diagno
|
|
|
248
259
|
lines.push(`|------|--------|---------------|------|----------|---------|`);
|
|
249
260
|
for (const evt of events) {
|
|
250
261
|
lines.push(
|
|
251
|
-
`| ${evt.taskId} | ${evt.status} | ${evt.classification} | ${formatCost(evt.cost)} | ${formatDuration(evt.durationSec)} | ${evt.retries}
|
|
262
|
+
`| ${evt.taskId} | ${evt.status} | ${evt.classification} | ${formatCost(evt.cost)} | ${formatDuration(evt.durationSec)} | ${evt.retries} |`,
|
|
252
263
|
);
|
|
253
264
|
}
|
|
254
265
|
lines.push(``);
|
|
@@ -276,8 +287,8 @@ export function buildMarkdownReport(input: DiagnosticReportInput, events: Diagno
|
|
|
276
287
|
} else {
|
|
277
288
|
for (const repoKey of repoKeys) {
|
|
278
289
|
const repoEvents = byRepo.get(repoKey)!;
|
|
279
|
-
const repoSucceeded = repoEvents.filter(e => e.status === "succeeded").length;
|
|
280
|
-
const repoFailed = repoEvents.filter(e => e.status === "failed").length;
|
|
290
|
+
const repoSucceeded = repoEvents.filter((e) => e.status === "succeeded").length;
|
|
291
|
+
const repoFailed = repoEvents.filter((e) => e.status === "failed").length;
|
|
281
292
|
const repoCost = repoEvents.reduce((sum, e) => sum + e.cost, 0);
|
|
282
293
|
|
|
283
294
|
lines.push(`### ${repoKey}`);
|
|
@@ -290,7 +301,7 @@ export function buildMarkdownReport(input: DiagnosticReportInput, events: Diagno
|
|
|
290
301
|
lines.push(`|------|--------|---------------|------|----------|`);
|
|
291
302
|
for (const evt of repoEvents) {
|
|
292
303
|
lines.push(
|
|
293
|
-
`| ${evt.taskId} | ${evt.status} | ${evt.classification} | ${formatCost(evt.cost)} | ${formatDuration(evt.durationSec)}
|
|
304
|
+
`| ${evt.taskId} | ${evt.status} | ${evt.classification} | ${formatCost(evt.cost)} | ${formatDuration(evt.durationSec)} |`,
|
|
294
305
|
);
|
|
295
306
|
}
|
|
296
307
|
lines.push(``);
|
|
@@ -377,7 +388,10 @@ export function assembleDiagnosticInput(
|
|
|
377
388
|
): DiagnosticReportInput {
|
|
378
389
|
// Build lookup maps for fast per-task enrichment (mirrors serializeBatchState logic).
|
|
379
390
|
const laneByTaskId = new Map<string, AllocatedLane>();
|
|
380
|
-
const allocatedTaskByTaskId = new Map<
|
|
391
|
+
const allocatedTaskByTaskId = new Map<
|
|
392
|
+
string,
|
|
393
|
+
{ allocatedTask: import("./types.ts").AllocatedTask; lane: AllocatedLane }
|
|
394
|
+
>();
|
|
381
395
|
for (const lane of lanes) {
|
|
382
396
|
for (const allocTask of lane.tasks) {
|
|
383
397
|
laneByTaskId.set(allocTask.taskId, lane);
|
|
@@ -401,48 +415,46 @@ export function assembleDiagnosticInput(
|
|
|
401
415
|
}
|
|
402
416
|
|
|
403
417
|
// Build task records sorted by taskId for deterministic output.
|
|
404
|
-
const tasks: PersistedTaskRecord[] = [...taskIdSet]
|
|
405
|
-
.
|
|
406
|
-
.
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
exitReason: outcome?.exitReason ?? "",
|
|
421
|
-
};
|
|
422
|
-
|
|
423
|
-
// Repo attribution from allocated task metadata (workspace mode).
|
|
424
|
-
if (allocated?.allocatedTask.task?.promptRepoId !== undefined) {
|
|
425
|
-
record.repoId = allocated.allocatedTask.task.promptRepoId;
|
|
426
|
-
}
|
|
427
|
-
if (allocated?.allocatedTask.task?.resolvedRepoId !== undefined) {
|
|
428
|
-
record.resolvedRepoId = allocated.allocatedTask.task.resolvedRepoId;
|
|
429
|
-
}
|
|
418
|
+
const tasks: PersistedTaskRecord[] = [...taskIdSet].sort().map((taskId): PersistedTaskRecord => {
|
|
419
|
+
const lane = laneByTaskId.get(taskId);
|
|
420
|
+
const outcome = outcomeByTaskId.get(taskId);
|
|
421
|
+
const allocated = allocatedTaskByTaskId.get(taskId);
|
|
422
|
+
|
|
423
|
+
const record: PersistedTaskRecord = {
|
|
424
|
+
taskId,
|
|
425
|
+
laneNumber: lane?.laneNumber ?? 0,
|
|
426
|
+
sessionName: outcome?.sessionName || lane?.laneSessionId || "",
|
|
427
|
+
status: outcome?.status ?? "pending",
|
|
428
|
+
taskFolder: "",
|
|
429
|
+
startedAt: outcome?.startTime ?? null,
|
|
430
|
+
endedAt: outcome?.endTime ?? null,
|
|
431
|
+
doneFileFound: outcome?.doneFileFound ?? false,
|
|
432
|
+
exitReason: outcome?.exitReason ?? "",
|
|
433
|
+
};
|
|
430
434
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
435
|
+
// Repo attribution from allocated task metadata (workspace mode).
|
|
436
|
+
if (allocated?.allocatedTask.task?.promptRepoId !== undefined) {
|
|
437
|
+
record.repoId = allocated.allocatedTask.task.promptRepoId;
|
|
438
|
+
}
|
|
439
|
+
if (allocated?.allocatedTask.task?.resolvedRepoId !== undefined) {
|
|
440
|
+
record.resolvedRepoId = allocated.allocatedTask.task.resolvedRepoId;
|
|
441
|
+
}
|
|
438
442
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
+
// Partial progress fields from outcome.
|
|
444
|
+
if (outcome?.partialProgressCommits !== undefined) {
|
|
445
|
+
record.partialProgressCommits = outcome.partialProgressCommits;
|
|
446
|
+
}
|
|
447
|
+
if (outcome?.partialProgressBranch !== undefined) {
|
|
448
|
+
record.partialProgressBranch = outcome.partialProgressBranch;
|
|
449
|
+
}
|
|
443
450
|
|
|
444
|
-
|
|
445
|
-
|
|
451
|
+
// v3: Exit diagnostic from outcome.
|
|
452
|
+
if (outcome?.exitDiagnostic !== undefined) {
|
|
453
|
+
record.exitDiagnostic = outcome.exitDiagnostic;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
return record;
|
|
457
|
+
});
|
|
446
458
|
|
|
447
459
|
return {
|
|
448
460
|
orchConfig,
|
|
@@ -249,20 +249,20 @@ export const CONTEXT_OVERFLOW_THRESHOLD_PCT = 90;
|
|
|
249
249
|
* @since TP-055
|
|
250
250
|
*/
|
|
251
251
|
export const MODEL_ACCESS_ERROR_PATTERNS: readonly RegExp[] = [
|
|
252
|
-
/\b(?:401|403)\b/,
|
|
253
|
-
/\b429\b/,
|
|
254
|
-
/model[_ ]not[_ ]found/i,
|
|
255
|
-
/model[_ ](?:is[_ ])?unavailable/i,
|
|
256
|
-
/model[_ ](?:has[_ ]been[_ ])?deprecated/i,
|
|
252
|
+
/\b(?:401|403)\b/, // HTTP auth/forbidden status codes
|
|
253
|
+
/\b429\b/, // HTTP rate limit
|
|
254
|
+
/model[_ ]not[_ ]found/i, // Model not found
|
|
255
|
+
/model[_ ](?:is[_ ])?unavailable/i, // Model unavailable
|
|
256
|
+
/model[_ ](?:has[_ ]been[_ ])?deprecated/i, // Model deprecated
|
|
257
257
|
/api[_ ]key[_ ](?:expired|invalid|revoked)/i, // API key issues
|
|
258
|
-
/invalid[_ ]api[_ ]key/i,
|
|
258
|
+
/invalid[_ ]api[_ ]key/i, // Invalid API key (alternate phrasing)
|
|
259
259
|
/authentication[_ ](?:failed|error|required)/i, // Auth failures
|
|
260
|
-
/authorization[_ ](?:failed|error|denied)/i,
|
|
261
|
-
/access[_ ]denied/i,
|
|
262
|
-
/permission[_ ]denied/i,
|
|
263
|
-
/quota[_ ]exceeded/i,
|
|
264
|
-
/rate[_ ]limit/i,
|
|
265
|
-
/insufficient[_ ]quota/i,
|
|
260
|
+
/authorization[_ ](?:failed|error|denied)/i, // Authz failures
|
|
261
|
+
/access[_ ]denied/i, // Generic access denied
|
|
262
|
+
/permission[_ ]denied/i, // Permission denied
|
|
263
|
+
/quota[_ ]exceeded/i, // Quota exceeded
|
|
264
|
+
/rate[_ ]limit/i, // Rate limit (phrase)
|
|
265
|
+
/insufficient[_ ]quota/i, // Insufficient quota
|
|
266
266
|
];
|
|
267
267
|
|
|
268
268
|
/**
|
|
@@ -277,7 +277,7 @@ export const MODEL_ACCESS_ERROR_PATTERNS: readonly RegExp[] = [
|
|
|
277
277
|
*/
|
|
278
278
|
export function isModelAccessError(errorMessage: string): boolean {
|
|
279
279
|
if (!errorMessage) return false;
|
|
280
|
-
return MODEL_ACCESS_ERROR_PATTERNS.some(pattern => pattern.test(errorMessage));
|
|
280
|
+
return MODEL_ACCESS_ERROR_PATTERNS.some((pattern) => pattern.test(errorMessage));
|
|
281
281
|
}
|
|
282
282
|
|
|
283
283
|
/**
|
|
@@ -6,7 +6,16 @@ import { readFileSync, writeFileSync, existsSync, readdirSync, statSync } from "
|
|
|
6
6
|
import { join, dirname, basename, resolve } from "path";
|
|
7
7
|
|
|
8
8
|
import { FATAL_DISCOVERY_CODES } from "./types.ts";
|
|
9
|
-
import type {
|
|
9
|
+
import type {
|
|
10
|
+
DiscoveryError,
|
|
11
|
+
DiscoveryResult,
|
|
12
|
+
ParsedTask,
|
|
13
|
+
PromptSegmentDagMetadata,
|
|
14
|
+
SegmentCheckboxGroup,
|
|
15
|
+
StepSegmentMapping,
|
|
16
|
+
TaskArea,
|
|
17
|
+
WorkspaceConfig,
|
|
18
|
+
} from "./types.ts";
|
|
10
19
|
|
|
11
20
|
// ── PROMPT.md Parsing ────────────────────────────────────────────────
|
|
12
21
|
|
|
@@ -233,8 +242,7 @@ function parseSegmentDagMetadata(
|
|
|
233
242
|
metadata: null,
|
|
234
243
|
error: {
|
|
235
244
|
code: "SEGMENT_DAG_INVALID",
|
|
236
|
-
message:
|
|
237
|
-
`Task ${taskId} has self-edge "${fromRepoId} -> ${toRepoId}" in ## Segment DAG at line ${baseLine + i}.`,
|
|
245
|
+
message: `Task ${taskId} has self-edge "${fromRepoId} -> ${toRepoId}" in ## Segment DAG at line ${baseLine + i}.`,
|
|
238
246
|
taskId,
|
|
239
247
|
taskPath: promptPath,
|
|
240
248
|
},
|
|
@@ -258,8 +266,7 @@ function parseSegmentDagMetadata(
|
|
|
258
266
|
metadata: null,
|
|
259
267
|
error: {
|
|
260
268
|
code: "SEGMENT_REPO_UNKNOWN",
|
|
261
|
-
message:
|
|
262
|
-
`Task ${taskId} has edge endpoint repo "${edge.fromRepoId}" in ## Segment DAG that is not declared in Repos:.`,
|
|
269
|
+
message: `Task ${taskId} has edge endpoint repo "${edge.fromRepoId}" in ## Segment DAG that is not declared in Repos:.`,
|
|
263
270
|
taskId,
|
|
264
271
|
taskPath: promptPath,
|
|
265
272
|
},
|
|
@@ -270,8 +277,7 @@ function parseSegmentDagMetadata(
|
|
|
270
277
|
metadata: null,
|
|
271
278
|
error: {
|
|
272
279
|
code: "SEGMENT_REPO_UNKNOWN",
|
|
273
|
-
message:
|
|
274
|
-
`Task ${taskId} has edge endpoint repo "${edge.toRepoId}" in ## Segment DAG that is not declared in Repos:.`,
|
|
280
|
+
message: `Task ${taskId} has edge endpoint repo "${edge.toRepoId}" in ## Segment DAG that is not declared in Repos:.`,
|
|
275
281
|
taskId,
|
|
276
282
|
taskPath: promptPath,
|
|
277
283
|
},
|
|
@@ -334,8 +340,7 @@ function parseSegmentDagMetadata(
|
|
|
334
340
|
metadata: null,
|
|
335
341
|
error: {
|
|
336
342
|
code: "SEGMENT_DAG_INVALID",
|
|
337
|
-
message:
|
|
338
|
-
`Task ${taskId} has cyclic ## Segment DAG metadata: ${cycle.join(" -> ")}.`,
|
|
343
|
+
message: `Task ${taskId} has cyclic ## Segment DAG metadata: ${cycle.join(" -> ")}.`,
|
|
339
344
|
taskId,
|
|
340
345
|
taskPath: promptPath,
|
|
341
346
|
},
|
|
@@ -516,15 +521,15 @@ export function parseStepSegmentMapping(
|
|
|
516
521
|
}
|
|
517
522
|
seenRepoIds.add(seg.repoId);
|
|
518
523
|
|
|
519
|
-
const nextSegIndex =
|
|
524
|
+
const nextSegIndex =
|
|
525
|
+
j + 1 < segmentHeaders.length ? segmentHeaders[j + 1].index : stepContent.length;
|
|
520
526
|
const segContent = stepContent.slice(seg.index, nextSegIndex);
|
|
521
527
|
const checkboxes = extractCheckboxes(segContent);
|
|
522
528
|
|
|
523
529
|
if (checkboxes.length === 0) {
|
|
524
530
|
warnings.push({
|
|
525
531
|
code: "SEGMENT_STEP_EMPTY",
|
|
526
|
-
message:
|
|
527
|
-
`Task ${taskId} Step ${header.stepNumber} has empty segment "${seg.repoId}" with no checkboxes.`,
|
|
532
|
+
message: `Task ${taskId} Step ${header.stepNumber} has empty segment "${seg.repoId}" with no checkboxes.`,
|
|
528
533
|
taskId,
|
|
529
534
|
});
|
|
530
535
|
}
|
|
@@ -650,9 +655,7 @@ export function parsePromptForOrchestrator(
|
|
|
650
655
|
|
|
651
656
|
// ── Extract dependencies ─────────────────────────────────────
|
|
652
657
|
const dependencies: string[] = [];
|
|
653
|
-
const depSectionMatch = content.match(
|
|
654
|
-
/^##\s+Dependencies\s*\n([\s\S]*?)(?=\n##\s|\n---|\n$)/m,
|
|
655
|
-
);
|
|
658
|
+
const depSectionMatch = content.match(/^##\s+Dependencies\s*\n([\s\S]*?)(?=\n##\s|\n---|\n$)/m);
|
|
656
659
|
|
|
657
660
|
if (depSectionMatch) {
|
|
658
661
|
const depBody = depSectionMatch[1].trim();
|
|
@@ -669,9 +672,7 @@ export function parsePromptForOrchestrator(
|
|
|
669
672
|
}
|
|
670
673
|
|
|
671
674
|
// Pattern 2: Bullet list "- COMP-005 ...", "- **time-off/TO-014** ..."
|
|
672
|
-
const bulletMatches = depBody.matchAll(
|
|
673
|
-
/^[\s-]*\*?\*?((?:[a-z0-9-]+\/)?[A-Z]+-\d+)\*?\*?/gim,
|
|
674
|
-
);
|
|
675
|
+
const bulletMatches = depBody.matchAll(/^[\s-]*\*?\*?((?:[a-z0-9-]+\/)?[A-Z]+-\d+)\*?\*?/gim);
|
|
675
676
|
for (const m of bulletMatches) {
|
|
676
677
|
const dep = normalizeDependencyReference(m[1]);
|
|
677
678
|
if (!dependencies.includes(dep)) dependencies.push(dep);
|
|
@@ -709,15 +710,13 @@ export function parsePromptForOrchestrator(
|
|
|
709
710
|
if (afterHeader !== -1) {
|
|
710
711
|
const rest = content.slice(afterHeader + 1);
|
|
711
712
|
const nextSectionMatch = rest.search(/^##\s|^---/m);
|
|
712
|
-
execTargetSectionBody = nextSectionMatch !== -1
|
|
713
|
-
? rest.slice(0, nextSectionMatch)
|
|
714
|
-
: rest;
|
|
713
|
+
execTargetSectionBody = nextSectionMatch !== -1 ? rest.slice(0, nextSectionMatch) : rest;
|
|
715
714
|
}
|
|
716
715
|
}
|
|
717
716
|
if (execTargetSectionBody !== null) {
|
|
718
717
|
// Match "Repo: api" or "**Repo:** api" or "Workspace: api" with whitespace
|
|
719
718
|
const repoLineMatch = execTargetSectionBody.match(
|
|
720
|
-
/^\s*\*?\*?(?:Repo|Workspace):?\*?\*?\s+(\S+)/
|
|
719
|
+
/^\s*\*?\*?(?:Repo|Workspace):?\*?\*?\s+(\S+)/im,
|
|
721
720
|
);
|
|
722
721
|
if (repoLineMatch) {
|
|
723
722
|
const candidate = repoLineMatch[1].trim().toLowerCase();
|
|
@@ -729,9 +728,7 @@ export function parsePromptForOrchestrator(
|
|
|
729
728
|
|
|
730
729
|
// Priority 2 (fallback): Inline "**Repo:** <id>" or "**Workspace:** <id>" anywhere in content
|
|
731
730
|
if (!promptRepoId) {
|
|
732
|
-
const inlineRepoMatch = content.match(
|
|
733
|
-
/^\*\*(?:Repo|Workspace):\*\*\s+(\S+)/m,
|
|
734
|
-
);
|
|
731
|
+
const inlineRepoMatch = content.match(/^\*\*(?:Repo|Workspace):\*\*\s+(\S+)/m);
|
|
735
732
|
if (inlineRepoMatch) {
|
|
736
733
|
const candidate = inlineRepoMatch[1].trim().toLowerCase();
|
|
737
734
|
if (REPO_ID_PATTERN.test(candidate)) {
|
|
@@ -742,9 +739,7 @@ export function parsePromptForOrchestrator(
|
|
|
742
739
|
|
|
743
740
|
// ── Extract file scope ───────────────────────────────────────
|
|
744
741
|
const fileScope: string[] = [];
|
|
745
|
-
const fileScopeMatch = content.match(
|
|
746
|
-
/^##\s+File Scope\s*\n([\s\S]*?)(?=\n##\s|\n---|\n$)/m,
|
|
747
|
-
);
|
|
742
|
+
const fileScopeMatch = content.match(/^##\s+File Scope\s*\n([\s\S]*?)(?=\n##\s|\n---|\n$)/m);
|
|
748
743
|
|
|
749
744
|
if (fileScopeMatch) {
|
|
750
745
|
const scopeBody = fileScopeMatch[1].trim();
|
|
@@ -812,7 +807,6 @@ export function parsePromptForOrchestrator(
|
|
|
812
807
|
};
|
|
813
808
|
}
|
|
814
809
|
|
|
815
|
-
|
|
816
810
|
// ── Area Scanning ────────────────────────────────────────────────────
|
|
817
811
|
|
|
818
812
|
/**
|
|
@@ -887,7 +881,6 @@ export function scanAreaForTasks(
|
|
|
887
881
|
return { tasks, errors };
|
|
888
882
|
}
|
|
889
883
|
|
|
890
|
-
|
|
891
884
|
// ── Completed Task Set ───────────────────────────────────────────────
|
|
892
885
|
|
|
893
886
|
/**
|
|
@@ -957,7 +950,6 @@ export function buildCompletedTaskSet(areaPaths: string[]): Set<string> {
|
|
|
957
950
|
return completed;
|
|
958
951
|
}
|
|
959
952
|
|
|
960
|
-
|
|
961
953
|
// ── Argument Resolution ──────────────────────────────────────────────
|
|
962
954
|
|
|
963
955
|
/**
|
|
@@ -995,10 +987,7 @@ export function resolveArguments(
|
|
|
995
987
|
if (!areaScanPaths.includes(fullPath)) {
|
|
996
988
|
areaScanPaths.push(fullPath);
|
|
997
989
|
}
|
|
998
|
-
} else if (
|
|
999
|
-
token.endsWith("PROMPT.md") &&
|
|
1000
|
-
existsSync(resolve(cwd, token))
|
|
1001
|
-
) {
|
|
990
|
+
} else if (token.endsWith("PROMPT.md") && existsSync(resolve(cwd, token))) {
|
|
1002
991
|
// Single PROMPT.md file
|
|
1003
992
|
directTaskFolders.push(resolve(cwd, dirname(token)));
|
|
1004
993
|
} else if (existsSync(resolve(cwd, token))) {
|
|
@@ -1129,7 +1118,6 @@ export function applyDependenciesFromCache(
|
|
|
1129
1118
|
return { applied };
|
|
1130
1119
|
}
|
|
1131
1120
|
|
|
1132
|
-
|
|
1133
1121
|
// ── Task Registry ────────────────────────────────────────────────────
|
|
1134
1122
|
|
|
1135
1123
|
/**
|
|
@@ -1238,7 +1226,6 @@ export function buildTaskRegistry(
|
|
|
1238
1226
|
return { pending, completed, errors };
|
|
1239
1227
|
}
|
|
1240
1228
|
|
|
1241
|
-
|
|
1242
1229
|
// ── Cross-Area Dependency Resolution ─────────────────────────────────
|
|
1243
1230
|
|
|
1244
1231
|
/** Candidate match for a dependency reference found in task areas. */
|
|
@@ -1407,7 +1394,6 @@ export function resolveDependencies(
|
|
|
1407
1394
|
return errors;
|
|
1408
1395
|
}
|
|
1409
1396
|
|
|
1410
|
-
|
|
1411
1397
|
// ── Task-to-Repo Routing ─────────────────────────────────────────────
|
|
1412
1398
|
|
|
1413
1399
|
/** Repo ID validation: lowercase alphanumeric + hyphens, starting with alnum */
|
|
@@ -1440,7 +1426,9 @@ export function resolveTaskRouting(
|
|
|
1440
1426
|
for (const task of discovery.pending.values()) {
|
|
1441
1427
|
// ── Explicit segment DAG repo validation (workspace IDs) ─
|
|
1442
1428
|
if (task.explicitSegmentDag) {
|
|
1443
|
-
const unknownRepos = task.explicitSegmentDag.repoIds.filter(
|
|
1429
|
+
const unknownRepos = task.explicitSegmentDag.repoIds.filter(
|
|
1430
|
+
(repoId) => !validRepoIds.has(repoId),
|
|
1431
|
+
);
|
|
1444
1432
|
if (unknownRepos.length > 0) {
|
|
1445
1433
|
errors.push({
|
|
1446
1434
|
code: "SEGMENT_REPO_UNKNOWN",
|
|
@@ -1577,9 +1565,8 @@ export function resolveTaskRouting(
|
|
|
1577
1565
|
if (!validRepoIds.has(seg.repoId)) {
|
|
1578
1566
|
const knownRepos = [...validRepoIds.keys()];
|
|
1579
1567
|
const suggestions = suggestRepoMatches(seg.repoId, knownRepos);
|
|
1580
|
-
const suggestionHint =
|
|
1581
|
-
? ` Did you mean: ${suggestions.join(", ")}?`
|
|
1582
|
-
: "";
|
|
1568
|
+
const suggestionHint =
|
|
1569
|
+
suggestions.length > 0 ? ` Did you mean: ${suggestions.join(", ")}?` : "";
|
|
1583
1570
|
errors.push({
|
|
1584
1571
|
code: "SEGMENT_STEP_REPO_INVALID",
|
|
1585
1572
|
message:
|
|
@@ -1599,7 +1586,6 @@ export function resolveTaskRouting(
|
|
|
1599
1586
|
return errors;
|
|
1600
1587
|
}
|
|
1601
1588
|
|
|
1602
|
-
|
|
1603
1589
|
// ── Discovery Pipeline (Public) ──────────────────────────────────────
|
|
1604
1590
|
|
|
1605
1591
|
/**
|
|
@@ -1721,7 +1707,7 @@ export function runDiscovery(
|
|
|
1721
1707
|
for (const task of discovery.pending.values()) {
|
|
1722
1708
|
if (!task.stepSegmentMap) continue;
|
|
1723
1709
|
for (const step of task.stepSegmentMap) {
|
|
1724
|
-
const stepRepoIds = step.segments.map(s => s.repoId);
|
|
1710
|
+
const stepRepoIds = step.segments.map((s) => s.repoId);
|
|
1725
1711
|
const seen = new Set<string>();
|
|
1726
1712
|
for (const rid of stepRepoIds) {
|
|
1727
1713
|
if (seen.has(rid)) {
|
|
@@ -1765,26 +1751,15 @@ export function formatDiscoveryResults(result: DiscoveryResult): string {
|
|
|
1765
1751
|
}
|
|
1766
1752
|
|
|
1767
1753
|
lines.push("Pending Tasks:");
|
|
1768
|
-
const sortedAreas = [...byArea.entries()].sort((a, b) =>
|
|
1769
|
-
a[0].localeCompare(b[0]),
|
|
1770
|
-
);
|
|
1754
|
+
const sortedAreas = [...byArea.entries()].sort((a, b) => a[0].localeCompare(b[0]));
|
|
1771
1755
|
for (const [area, tasks] of sortedAreas) {
|
|
1772
1756
|
lines.push(` ${area}:`);
|
|
1773
|
-
const sortedTasks = [...tasks].sort((a, b) =>
|
|
1774
|
-
a.taskId.localeCompare(b.taskId),
|
|
1775
|
-
);
|
|
1757
|
+
const sortedTasks = [...tasks].sort((a, b) => a.taskId.localeCompare(b.taskId));
|
|
1776
1758
|
for (const task of sortedTasks) {
|
|
1777
1759
|
const deps =
|
|
1778
|
-
task.dependencies.length > 0
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
const repo =
|
|
1782
|
-
task.resolvedRepoId
|
|
1783
|
-
? ` → repo: ${task.resolvedRepoId}`
|
|
1784
|
-
: "";
|
|
1785
|
-
lines.push(
|
|
1786
|
-
` ${task.taskId} [${task.size}] ${task.taskName}${deps}${repo}`,
|
|
1787
|
-
);
|
|
1760
|
+
task.dependencies.length > 0 ? ` → depends on: ${task.dependencies.join(", ")}` : "";
|
|
1761
|
+
const repo = task.resolvedRepoId ? ` → repo: ${task.resolvedRepoId}` : "";
|
|
1762
|
+
lines.push(` ${task.taskId} [${task.size}] ${task.taskName}${deps}${repo}`);
|
|
1788
1763
|
}
|
|
1789
1764
|
}
|
|
1790
1765
|
lines.push("");
|
|
@@ -1815,4 +1790,3 @@ export function formatDiscoveryResults(result: DiscoveryResult): string {
|
|
|
1815
1790
|
|
|
1816
1791
|
return lines.join("\n");
|
|
1817
1792
|
}
|
|
1818
|
-
|