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
|
@@ -112,14 +112,19 @@ export interface FingerprintDiff {
|
|
|
112
112
|
fixed: TestFingerprint[];
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
|
|
116
115
|
// ── Normalization Helpers ────────────────────────────────────────────
|
|
117
116
|
|
|
118
117
|
/** Max length for normalized message strings */
|
|
119
118
|
const MESSAGE_NORM_MAX_LENGTH = 512;
|
|
120
119
|
|
|
121
|
-
//
|
|
122
|
-
|
|
120
|
+
// Built via `new RegExp` so Biome's noControlCharactersInRegex (which only
|
|
121
|
+
// inspects regex literals) does not flag the \u001b/\u009b escapes that are
|
|
122
|
+
// fundamental to ANSI sequence detection. Runtime behavior is identical to
|
|
123
|
+
// the prior literal regex; this is a static-analysis adjustment only.
|
|
124
|
+
const ANSI_REGEX = new RegExp(
|
|
125
|
+
"[\\u001b\\u009b]\\[[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><~]",
|
|
126
|
+
"g",
|
|
127
|
+
);
|
|
123
128
|
|
|
124
129
|
/** Match duration strings like (42ms), (1.2s), (3m 12s), 42 ms, 1200ms */
|
|
125
130
|
const DURATION_REGEX = /\(?\d+(?:\.\d+)?\s*(?:ms|s|m)\s*(?:\d+(?:\.\d+)?\s*(?:ms|s))?\)?/g;
|
|
@@ -172,7 +177,6 @@ export function fingerprintKey(fp: TestFingerprint): string {
|
|
|
172
177
|
return `${fp.commandId}\0${fp.file}\0${fp.case}\0${fp.kind}\0${fp.messageNorm}`;
|
|
173
178
|
}
|
|
174
179
|
|
|
175
|
-
|
|
176
180
|
// ── Command Runner ───────────────────────────────────────────────────
|
|
177
181
|
|
|
178
182
|
/** Default timeout for verification commands: 5 minutes */
|
|
@@ -255,7 +259,6 @@ export function runVerificationCommands(
|
|
|
255
259
|
return results;
|
|
256
260
|
}
|
|
257
261
|
|
|
258
|
-
|
|
259
262
|
// ── Test Output Parsers ──────────────────────────────────────────────
|
|
260
263
|
|
|
261
264
|
/**
|
|
@@ -371,7 +374,7 @@ export function parseVitestOutput(commandId: string, stdout: string): TestFinger
|
|
|
371
374
|
// This covers setup/import/runtime-at-file-load errors where Vitest marks the file as
|
|
372
375
|
// failed but produces no assertionResults (or only non-failed ones).
|
|
373
376
|
if (testFile.status === "failed") {
|
|
374
|
-
const hasFailedAssertions = hasAssertions && assertions!.some(a => a.status === "failed");
|
|
377
|
+
const hasFailedAssertions = hasAssertions && assertions!.some((a) => a.status === "failed");
|
|
375
378
|
if (!hasFailedAssertions) {
|
|
376
379
|
// No assertion-level failures captured — emit suite-level runtime_error fingerprint
|
|
377
380
|
const suiteMessage = testFile.message || "Suite failed with no message";
|
|
@@ -407,13 +410,15 @@ export function parseTestOutput(commandResult: CommandResult): TestFingerprint[]
|
|
|
407
410
|
|
|
408
411
|
// If command had a spawn/timeout error, produce a command_error fingerprint
|
|
409
412
|
if (error) {
|
|
410
|
-
return [
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
413
|
+
return [
|
|
414
|
+
{
|
|
415
|
+
commandId,
|
|
416
|
+
file: "",
|
|
417
|
+
case: "",
|
|
418
|
+
kind: "command_error",
|
|
419
|
+
messageNorm: normalizeMessage(error),
|
|
420
|
+
},
|
|
421
|
+
];
|
|
417
422
|
}
|
|
418
423
|
|
|
419
424
|
// If exit code is 0, no failures to fingerprint
|
|
@@ -433,16 +438,17 @@ export function parseTestOutput(commandResult: CommandResult): TestFingerprint[]
|
|
|
433
438
|
|
|
434
439
|
// Fallback: command_error fingerprint with stderr (or stdout if stderr is empty)
|
|
435
440
|
const fallbackMessage = stderr.trim() || stdout.trim() || "Command failed with no output";
|
|
436
|
-
return [
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
441
|
+
return [
|
|
442
|
+
{
|
|
443
|
+
commandId,
|
|
444
|
+
file: "",
|
|
445
|
+
case: "",
|
|
446
|
+
kind: "command_error",
|
|
447
|
+
messageNorm: normalizeMessage(fallbackMessage),
|
|
448
|
+
},
|
|
449
|
+
];
|
|
443
450
|
}
|
|
444
451
|
|
|
445
|
-
|
|
446
452
|
// ── Fingerprint Diffing ──────────────────────────────────────────────
|
|
447
453
|
|
|
448
454
|
/**
|
|
@@ -509,7 +515,6 @@ export function diffFingerprints(
|
|
|
509
515
|
return { newFailures, preExisting, fixed };
|
|
510
516
|
}
|
|
511
517
|
|
|
512
|
-
|
|
513
518
|
// ── Baseline Capture ─────────────────────────────────────────────────
|
|
514
519
|
|
|
515
520
|
/**
|
|
@@ -7,7 +7,23 @@ import { join } from "path";
|
|
|
7
7
|
import { parseDependencyReference } from "./discovery.ts";
|
|
8
8
|
import { resolveOperatorId } from "./naming.ts";
|
|
9
9
|
import { AllocationError, buildSegmentId, getTaskDurationMinutes } from "./types.ts";
|
|
10
|
-
import type {
|
|
10
|
+
import type {
|
|
11
|
+
AllocatedLane,
|
|
12
|
+
AllocatedTask,
|
|
13
|
+
AllocationErrorCode,
|
|
14
|
+
DependencyGraph,
|
|
15
|
+
DiscoveryError,
|
|
16
|
+
GraphValidationResult,
|
|
17
|
+
LaneAssignment,
|
|
18
|
+
OrchestratorConfig,
|
|
19
|
+
ParsedTask,
|
|
20
|
+
TaskSegmentPlan,
|
|
21
|
+
TaskSegmentPlanMap,
|
|
22
|
+
WaveAssignment,
|
|
23
|
+
WaveComputationResult,
|
|
24
|
+
WorkspaceConfig,
|
|
25
|
+
WorktreeInfo,
|
|
26
|
+
} from "./types.ts";
|
|
11
27
|
import { getCurrentBranch, runGit } from "./git.ts";
|
|
12
28
|
import { ensureLaneWorktrees, removeAllWorktrees, removeWorktree } from "./worktree.ts";
|
|
13
29
|
|
|
@@ -56,7 +72,6 @@ export function buildDependencyGraph(
|
|
|
56
72
|
return { dependencies, dependents, nodes };
|
|
57
73
|
}
|
|
58
74
|
|
|
59
|
-
|
|
60
75
|
// ── Graph Validation ─────────────────────────────────────────────────
|
|
61
76
|
|
|
62
77
|
/**
|
|
@@ -182,7 +197,6 @@ export function validateGraph(
|
|
|
182
197
|
};
|
|
183
198
|
}
|
|
184
199
|
|
|
185
|
-
|
|
186
200
|
// ── Wave Computation (Topological Sort) ──────────────────────────────
|
|
187
201
|
|
|
188
202
|
/**
|
|
@@ -259,7 +273,6 @@ export function computeWaves(
|
|
|
259
273
|
return { waves, errors };
|
|
260
274
|
}
|
|
261
275
|
|
|
262
|
-
|
|
263
276
|
// ── File Scope Affinity ──────────────────────────────────────────────
|
|
264
277
|
|
|
265
278
|
/**
|
|
@@ -403,7 +416,6 @@ export function applyFileScopeAffinity(
|
|
|
403
416
|
return result;
|
|
404
417
|
}
|
|
405
418
|
|
|
406
|
-
|
|
407
419
|
// ── Repo-Scoped Lane Helpers ─────────────────────────────────────────
|
|
408
420
|
|
|
409
421
|
/**
|
|
@@ -505,14 +517,18 @@ export function generateLaneId(laneLocalNumber: number, repoId?: string): string
|
|
|
505
517
|
* @param opId - Operator identifier (sanitized, e.g., "henrylach")
|
|
506
518
|
* @param repoId - Repo identifier (undefined in repo mode)
|
|
507
519
|
*/
|
|
508
|
-
export function generateLaneSessionId(
|
|
520
|
+
export function generateLaneSessionId(
|
|
521
|
+
sessionPrefix: string,
|
|
522
|
+
laneLocalNumber: number,
|
|
523
|
+
opId: string,
|
|
524
|
+
repoId?: string,
|
|
525
|
+
): string {
|
|
509
526
|
if (repoId) {
|
|
510
527
|
return `${sessionPrefix}-${opId}-${repoId}-lane-${laneLocalNumber}`;
|
|
511
528
|
}
|
|
512
529
|
return `${sessionPrefix}-${opId}-lane-${laneLocalNumber}`;
|
|
513
530
|
}
|
|
514
531
|
|
|
515
|
-
|
|
516
532
|
// ── Repo-Scoped Worktree Resolution ─────────────────────────────────
|
|
517
533
|
|
|
518
534
|
/**
|
|
@@ -583,7 +599,7 @@ export function resolveBaseBranch(
|
|
|
583
599
|
// instead of the orch branch, bypassing batch isolation.
|
|
584
600
|
console.error(
|
|
585
601
|
`[taskplane] resolveBaseBranch WARNING: orch branch "${batchBaseBranch}" not found in repo "${repoId}" at ${repoRoot} — falling back to repo HEAD. ` +
|
|
586
|
-
|
|
602
|
+
`This bypasses orch branch isolation. Ensure the orch branch was created in all workspace repos.`,
|
|
587
603
|
);
|
|
588
604
|
} catch (err) {
|
|
589
605
|
console.error(
|
|
@@ -621,16 +637,15 @@ export function resolveBaseBranch(
|
|
|
621
637
|
if (repoId && batchBaseBranch.startsWith("orch/")) {
|
|
622
638
|
throw new Error(
|
|
623
639
|
`Cannot resolve base branch for repo "${repoId}" at ${repoRoot}: ` +
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
640
|
+
`HEAD is detached and no defaultBranch is configured. ` +
|
|
641
|
+
`The batch base branch "${batchBaseBranch}" is an orch branch that does not exist in this repo. ` +
|
|
642
|
+
`Configure a defaultBranch for this repo in task-orchestrator.yaml workspace settings.`,
|
|
627
643
|
);
|
|
628
644
|
}
|
|
629
645
|
|
|
630
646
|
return batchBaseBranch;
|
|
631
647
|
}
|
|
632
648
|
|
|
633
|
-
|
|
634
649
|
// ── Segment Planning (TP-080) ───────────────────────────────────────
|
|
635
650
|
|
|
636
651
|
const SEGMENT_REPO_ID_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
|
|
@@ -778,7 +793,7 @@ function buildSegmentNodes(taskId: string, repoIds: string[]) {
|
|
|
778
793
|
repoId,
|
|
779
794
|
order,
|
|
780
795
|
}));
|
|
781
|
-
return nodes.sort((a, b) =>
|
|
796
|
+
return nodes.sort((a, b) => a.order - b.order || a.repoId.localeCompare(b.repoId));
|
|
782
797
|
}
|
|
783
798
|
|
|
784
799
|
export function buildSegmentPlanForTask(
|
|
@@ -839,7 +854,6 @@ export function buildTaskSegmentPlans(
|
|
|
839
854
|
return plans;
|
|
840
855
|
}
|
|
841
856
|
|
|
842
|
-
|
|
843
857
|
// ── Lane Assignment ──────────────────────────────────────────────────
|
|
844
858
|
|
|
845
859
|
/**
|
|
@@ -877,9 +891,7 @@ export function assignTasksToLanes(
|
|
|
877
891
|
|
|
878
892
|
// Step 3: Initialize lane weights (for load-balanced assignment)
|
|
879
893
|
const laneWeights: number[] = new Array(laneCount).fill(0);
|
|
880
|
-
const laneAssignments: LaneAssignment[][] = new Array(laneCount)
|
|
881
|
-
.fill(null)
|
|
882
|
-
.map(() => []);
|
|
894
|
+
const laneAssignments: LaneAssignment[][] = new Array(laneCount).fill(null).map(() => []);
|
|
883
895
|
|
|
884
896
|
function getWeight(taskId: string): number {
|
|
885
897
|
const task = pending.get(taskId);
|
|
@@ -970,7 +982,6 @@ export function assignTasksToLanes(
|
|
|
970
982
|
return result;
|
|
971
983
|
}
|
|
972
984
|
|
|
973
|
-
|
|
974
985
|
// ── Global Lane Cap (TP-148) ─────────────────────────────────────────
|
|
975
986
|
|
|
976
987
|
/**
|
|
@@ -1044,8 +1055,8 @@ export function enforceGlobalLaneCap(
|
|
|
1044
1055
|
if (finalTotal > maxLanes) {
|
|
1045
1056
|
console.error(
|
|
1046
1057
|
`[taskplane] warning: global maxLanes=${maxLanes} could not be enforced — ` +
|
|
1047
|
-
|
|
1048
|
-
|
|
1058
|
+
`${byRepo.size} repos each need at least 1 lane (total: ${finalTotal}). ` +
|
|
1059
|
+
`Increase maxLanes to at least ${byRepo.size} to avoid this.`,
|
|
1049
1060
|
);
|
|
1050
1061
|
}
|
|
1051
1062
|
|
|
@@ -1061,7 +1072,6 @@ export function enforceGlobalLaneCap(
|
|
|
1061
1072
|
}
|
|
1062
1073
|
}
|
|
1063
1074
|
|
|
1064
|
-
|
|
1065
1075
|
/**
|
|
1066
1076
|
* Result of `allocateLanes()`.
|
|
1067
1077
|
*
|
|
@@ -1145,16 +1155,13 @@ export function validateAllocationInputs(
|
|
|
1145
1155
|
return new AllocationError(
|
|
1146
1156
|
"ALLOC_INVALID_CONFIG",
|
|
1147
1157
|
`Unknown assignment strategy: "${config.assignment.strategy}". ` +
|
|
1148
|
-
|
|
1158
|
+
`Valid strategies: ${validStrategies.join(", ")}`,
|
|
1149
1159
|
);
|
|
1150
1160
|
}
|
|
1151
1161
|
|
|
1152
1162
|
// Validate worktree prefix is non-empty
|
|
1153
1163
|
if (!config.orchestrator.worktree_prefix?.trim()) {
|
|
1154
|
-
return new AllocationError(
|
|
1155
|
-
"ALLOC_INVALID_CONFIG",
|
|
1156
|
-
`worktree_prefix must be a non-empty string`,
|
|
1157
|
-
);
|
|
1164
|
+
return new AllocationError("ALLOC_INVALID_CONFIG", `worktree_prefix must be a non-empty string`);
|
|
1158
1165
|
}
|
|
1159
1166
|
|
|
1160
1167
|
return null;
|
|
@@ -1336,7 +1343,12 @@ export function allocateLanes(
|
|
|
1336
1343
|
const groupLaneNumbers = repoLaneGroups.get(groupKey)!;
|
|
1337
1344
|
const groupRepoId = repoIdForGroup.get(groupKey);
|
|
1338
1345
|
const groupRepoRoot = resolveRepoRoot(groupRepoId, repoRoot, workspaceConfig);
|
|
1339
|
-
const groupBaseBranch = resolveBaseBranch(
|
|
1346
|
+
const groupBaseBranch = resolveBaseBranch(
|
|
1347
|
+
groupRepoId,
|
|
1348
|
+
groupRepoRoot,
|
|
1349
|
+
baseBranch,
|
|
1350
|
+
workspaceConfig,
|
|
1351
|
+
);
|
|
1340
1352
|
|
|
1341
1353
|
const worktreeResult = ensureLaneWorktrees(
|
|
1342
1354
|
groupLaneNumbers,
|
|
@@ -1370,16 +1382,17 @@ export function allocateLanes(
|
|
|
1370
1382
|
const failedLanes = worktreeResult.errors
|
|
1371
1383
|
.map((e) => `Lane ${e.laneNumber}: [${e.code}] ${e.message}`)
|
|
1372
1384
|
.join("\n");
|
|
1373
|
-
const withinGroupRollbackIssues =
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1385
|
+
const withinGroupRollbackIssues =
|
|
1386
|
+
worktreeResult.rollbackErrors.length > 0
|
|
1387
|
+
? "\nWithin-group rollback issues:\n" +
|
|
1388
|
+
worktreeResult.rollbackErrors
|
|
1389
|
+
.map((e) => ` Lane ${e.laneNumber}: [${e.code}] ${e.message}`)
|
|
1390
|
+
.join("\n")
|
|
1391
|
+
: "";
|
|
1392
|
+
const crossRepoRollbackIssues =
|
|
1393
|
+
rollbackErrors.length > 0
|
|
1394
|
+
? "\nCross-repo rollback issues:\n" + rollbackErrors.map((e) => ` ${e}`).join("\n")
|
|
1395
|
+
: "";
|
|
1383
1396
|
|
|
1384
1397
|
return {
|
|
1385
1398
|
success: false,
|
|
@@ -1420,7 +1433,14 @@ export function allocateLanes(
|
|
|
1420
1433
|
for (const groupKey of createdGroupKeys) {
|
|
1421
1434
|
const groupRepoId = repoIdForGroup.get(groupKey);
|
|
1422
1435
|
const groupRepoRoot = resolveRepoRoot(groupRepoId, repoRoot, workspaceConfig);
|
|
1423
|
-
removeAllWorktrees(
|
|
1436
|
+
removeAllWorktrees(
|
|
1437
|
+
config.orchestrator.worktree_prefix,
|
|
1438
|
+
groupRepoRoot,
|
|
1439
|
+
opId,
|
|
1440
|
+
undefined,
|
|
1441
|
+
batchId,
|
|
1442
|
+
config,
|
|
1443
|
+
);
|
|
1424
1444
|
}
|
|
1425
1445
|
return {
|
|
1426
1446
|
success: false,
|
|
@@ -1447,10 +1467,7 @@ export function allocateLanes(
|
|
|
1447
1467
|
(sum, t) => sum + (sizeWeights[t.task.size] || sizeWeights["M"] || 2),
|
|
1448
1468
|
0,
|
|
1449
1469
|
);
|
|
1450
|
-
const estimatedMinutes = allocatedTasks.reduce(
|
|
1451
|
-
(sum, t) => sum + t.estimatedMinutes,
|
|
1452
|
-
0,
|
|
1453
|
-
);
|
|
1470
|
+
const estimatedMinutes = allocatedTasks.reduce((sum, t) => sum + t.estimatedMinutes, 0);
|
|
1454
1471
|
|
|
1455
1472
|
const laneSessionId = generateLaneSessionId(sessionPrefix, entry.localLane, opId, entry.repoId);
|
|
1456
1473
|
allocatedLanes.push({
|
|
@@ -1480,7 +1497,6 @@ export function allocateLanes(
|
|
|
1480
1497
|
};
|
|
1481
1498
|
}
|
|
1482
1499
|
|
|
1483
|
-
|
|
1484
1500
|
// ── Full Wave Pipeline ───────────────────────────────────────────────
|
|
1485
1501
|
|
|
1486
1502
|
/**
|
|
@@ -54,7 +54,6 @@ import {
|
|
|
54
54
|
type PointerResolution,
|
|
55
55
|
} from "./types.ts";
|
|
56
56
|
|
|
57
|
-
|
|
58
57
|
// ── Path Canonicalization ────────────────────────────────────────────
|
|
59
58
|
|
|
60
59
|
/**
|
|
@@ -108,7 +107,6 @@ function isPathWithinContainer(childPath: string, parentPath: string): boolean {
|
|
|
108
107
|
return child === parent || child.startsWith(`${parent}/`);
|
|
109
108
|
}
|
|
110
109
|
|
|
111
|
-
|
|
112
110
|
// ── Pointer Resolution ───────────────────────────────────────────────
|
|
113
111
|
|
|
114
112
|
/**
|
|
@@ -287,7 +285,6 @@ export function resolvePointer(
|
|
|
287
285
|
};
|
|
288
286
|
}
|
|
289
287
|
|
|
290
|
-
|
|
291
288
|
// ── Workspace Config Loading ─────────────────────────────────────────
|
|
292
289
|
|
|
293
290
|
/**
|
|
@@ -453,9 +450,10 @@ export function loadWorkspaceConfig(workspaceRoot: string): WorkspaceConfig | nu
|
|
|
453
450
|
normalizedPaths.set(normalizedPath, repoId);
|
|
454
451
|
|
|
455
452
|
// Build repo config
|
|
456
|
-
const defaultBranch =
|
|
457
|
-
|
|
458
|
-
|
|
453
|
+
const defaultBranch =
|
|
454
|
+
typeof repoEntry.default_branch === "string" && repoEntry.default_branch.trim()
|
|
455
|
+
? repoEntry.default_branch.trim()
|
|
456
|
+
: undefined;
|
|
459
457
|
|
|
460
458
|
repos.set(repoId, {
|
|
461
459
|
id: repoId,
|
|
@@ -587,7 +585,6 @@ export function loadWorkspaceConfig(workspaceRoot: string): WorkspaceConfig | nu
|
|
|
587
585
|
};
|
|
588
586
|
}
|
|
589
587
|
|
|
590
|
-
|
|
591
588
|
// ── Cross-Config Validation ─────────────────────────────────────────
|
|
592
589
|
|
|
593
590
|
/**
|
|
@@ -603,7 +600,7 @@ export function validateTaskAreasWithinTasksRoot(
|
|
|
603
600
|
): void {
|
|
604
601
|
const tasksRoot = workspaceConfig.routing.tasksRoot;
|
|
605
602
|
const areaEntries = Object.entries(taskRunnerConfig.task_areas ?? {}).sort((a, b) =>
|
|
606
|
-
a[0].localeCompare(b[0])
|
|
603
|
+
a[0].localeCompare(b[0]),
|
|
607
604
|
);
|
|
608
605
|
|
|
609
606
|
for (const [areaName, area] of areaEntries) {
|
|
@@ -620,7 +617,6 @@ export function validateTaskAreasWithinTasksRoot(
|
|
|
620
617
|
}
|
|
621
618
|
}
|
|
622
619
|
|
|
623
|
-
|
|
624
620
|
// ── Execution Context Builder ────────────────────────────────────────
|
|
625
621
|
|
|
626
622
|
/**
|
|
@@ -643,8 +639,14 @@ function isInsideGitRepo(cwd: string): boolean {
|
|
|
643
639
|
|
|
644
640
|
export function buildExecutionContext(
|
|
645
641
|
cwd: string,
|
|
646
|
-
loadOrchConfig: (
|
|
647
|
-
|
|
642
|
+
loadOrchConfig: (
|
|
643
|
+
root: string,
|
|
644
|
+
pointerConfigRoot?: string,
|
|
645
|
+
) => import("./types.ts").OrchestratorConfig,
|
|
646
|
+
loadTaskConfig: (
|
|
647
|
+
root: string,
|
|
648
|
+
pointerConfigRoot?: string,
|
|
649
|
+
) => import("./types.ts").TaskRunnerConfig,
|
|
648
650
|
): import("./types.ts").ExecutionContext {
|
|
649
651
|
const workspaceConfig = loadWorkspaceConfig(cwd);
|
|
650
652
|
|
|
@@ -656,7 +658,7 @@ export function buildExecutionContext(
|
|
|
656
658
|
throw new WorkspaceConfigError(
|
|
657
659
|
"WORKSPACE_SETUP_REQUIRED",
|
|
658
660
|
`No workspace config found at ${wsConfigFile}, and current directory is not a git repository: ${cwd}. ` +
|
|
659
|
-
|
|
661
|
+
`Run Taskplane from a git repository, or create ${wsConfigFile} (taskplane init) to use workspace mode.`,
|
|
660
662
|
undefined,
|
|
661
663
|
cwd,
|
|
662
664
|
);
|