flanders 0.6.1 → 0.8.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/README.md +241 -0
- package/lib/Flanders.d.ts +2 -1
- package/lib/Flanders.js +7 -0
- package/lib/cli.js +10 -1
- package/lib/commands/Implement.d.ts +3 -1
- package/lib/commands/Implement.js +32 -23
- package/lib/commands/Install.d.ts +0 -1
- package/lib/commands/Install.js +131 -114
- package/lib/commands/Update.d.ts +19 -0
- package/lib/commands/Update.js +90 -0
- package/lib/commands/skillArtifacts.d.ts +18 -0
- package/lib/commands/skillArtifacts.js +93 -0
- package/lib/contexts.d.ts +1 -0
- package/lib/plan/PlanFile.d.ts +8 -5
- package/lib/plan/PlanFile.js +85 -16
- package/lib/prompts/prompts.d.ts +11 -1
- package/lib/prompts/prompts.js +42 -5
- package/lib/prompts/skills.d.ts +1 -1
- package/lib/prompts/skills.js +43 -17
- package/lib/ui/BottomBlock.d.ts +7 -3
- package/lib/ui/BottomBlock.js +47 -11
- package/lib/ui/PromptHelper.d.ts +2 -0
- package/lib/ui/PromptHelper.js +11 -2
- package/lib/ui/formatters.d.ts +8 -3
- package/lib/ui/formatters.js +67 -39
- package/lib/voiceVariants.d.ts +15 -0
- package/lib/voiceVariants.js +141 -0
- package/lib/workspace/FlandersConfig.d.ts +4 -3
- package/lib/workspace/FlandersConfig.js +18 -0
- package/lib/workspace/Workspace.d.ts +2 -0
- package/lib/workspace/Workspace.js +3 -1
- package/package.json +1 -1
package/lib/ui/formatters.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.SEPARATOR_GLYPH = exports.RESET = exports.ORANGE = exports.DIM = exports.BLUE = exports.GREEN = exports.MAGENTA = exports.YELLOW = exports.CYAN = void 0;
|
|
3
|
+
exports.SEPARATOR_GLYPH = exports.RESET = exports.ORANGE = exports.DIM = exports.BLUE = exports.RED = exports.GREEN = exports.MAGENTA = exports.YELLOW = exports.CYAN = void 0;
|
|
4
4
|
exports.colorize = colorize;
|
|
5
5
|
exports.stripAnsi = stripAnsi;
|
|
6
6
|
exports.renderSegments = renderSegments;
|
|
7
|
+
exports.segmentsWidth = segmentsWidth;
|
|
7
8
|
exports.renderSegmentsToWidth = renderSegmentsToWidth;
|
|
8
9
|
exports.formatCountdown = formatCountdown;
|
|
10
|
+
exports.formatCompactCountdown = formatCompactCountdown;
|
|
9
11
|
exports.formatDateTime = formatDateTime;
|
|
10
12
|
exports.truncateToWidth = truncateToWidth;
|
|
11
13
|
exports.formatTokens = formatTokens;
|
|
@@ -17,11 +19,13 @@ exports.formatSnapshotMetrics = formatSnapshotMetrics;
|
|
|
17
19
|
exports.formatSnapshotBlock = formatSnapshotBlock;
|
|
18
20
|
exports.formatReviewingFooter = formatReviewingFooter;
|
|
19
21
|
exports.formatWorkingFooter = formatWorkingFooter;
|
|
22
|
+
exports.formatTerminalFooter = formatTerminalFooter;
|
|
20
23
|
exports.formatWaitingFooter = formatWaitingFooter;
|
|
21
24
|
exports.CYAN = "\x1b[36m";
|
|
22
25
|
exports.YELLOW = "\x1b[33m";
|
|
23
26
|
exports.MAGENTA = "\x1b[35m";
|
|
24
27
|
exports.GREEN = "\x1b[32m";
|
|
28
|
+
exports.RED = "\x1b[31m";
|
|
25
29
|
exports.BLUE = "\x1b[34m";
|
|
26
30
|
exports.DIM = "\x1b[2m";
|
|
27
31
|
exports.ORANGE = "\x1b[38;5;208m";
|
|
@@ -45,11 +49,14 @@ function renderSegments(segments) {
|
|
|
45
49
|
}
|
|
46
50
|
return result;
|
|
47
51
|
}
|
|
48
|
-
function
|
|
49
|
-
let
|
|
52
|
+
function segmentsWidth(segments) {
|
|
53
|
+
let width = 0;
|
|
50
54
|
for (const seg of segments)
|
|
51
|
-
|
|
52
|
-
|
|
55
|
+
width += seg.text.length;
|
|
56
|
+
return width;
|
|
57
|
+
}
|
|
58
|
+
function renderSegmentsToWidth(segments, cols) {
|
|
59
|
+
if (segmentsWidth(segments) <= cols)
|
|
53
60
|
return renderSegments(segments);
|
|
54
61
|
let result = "";
|
|
55
62
|
let remaining = Math.max(0, cols - 1);
|
|
@@ -68,21 +75,41 @@ function renderSegmentsToWidth(segments, cols) {
|
|
|
68
75
|
result += "…";
|
|
69
76
|
return result;
|
|
70
77
|
}
|
|
71
|
-
function
|
|
78
|
+
function decomposeCountdown(remainingMs) {
|
|
72
79
|
const totalMinutes = Math.max(1, Math.ceil(remainingMs / 60000));
|
|
73
80
|
if (totalMinutes >= 24 * 60) {
|
|
74
81
|
const days = Math.floor(totalMinutes / (24 * 60));
|
|
75
82
|
const remainder = totalMinutes - days * 24 * 60;
|
|
76
83
|
const hours = Math.floor(remainder / 60);
|
|
77
84
|
const minutes = remainder % 60;
|
|
78
|
-
return
|
|
85
|
+
return { tier: "days", days, hours, minutes };
|
|
79
86
|
}
|
|
80
87
|
if (totalMinutes >= 60) {
|
|
81
88
|
const hours = Math.floor(totalMinutes / 60);
|
|
82
89
|
const minutes = totalMinutes % 60;
|
|
90
|
+
return { tier: "hours", days: 0, hours, minutes };
|
|
91
|
+
}
|
|
92
|
+
return { tier: "minutes", days: 0, hours: 0, minutes: totalMinutes };
|
|
93
|
+
}
|
|
94
|
+
function formatCountdown(remainingMs) {
|
|
95
|
+
const { tier, days, hours, minutes } = decomposeCountdown(remainingMs);
|
|
96
|
+
if (tier === "days") {
|
|
97
|
+
return `${days} days, ${hours} hours, ${minutes} minutes`;
|
|
98
|
+
}
|
|
99
|
+
if (tier === "hours") {
|
|
83
100
|
return `${hours} hours ${minutes} minutes`;
|
|
84
101
|
}
|
|
85
|
-
return `${
|
|
102
|
+
return `${minutes} minutes`;
|
|
103
|
+
}
|
|
104
|
+
function formatCompactCountdown(remainingMs) {
|
|
105
|
+
const { tier, days, hours, minutes } = decomposeCountdown(remainingMs);
|
|
106
|
+
if (tier === "days") {
|
|
107
|
+
return `${days}d${hours}h${minutes}m`;
|
|
108
|
+
}
|
|
109
|
+
if (tier === "hours") {
|
|
110
|
+
return `${hours}h${minutes}m`;
|
|
111
|
+
}
|
|
112
|
+
return `${minutes}m`;
|
|
86
113
|
}
|
|
87
114
|
function formatDateTime(date) {
|
|
88
115
|
const pad = (n) => String(n).padStart(2, "0");
|
|
@@ -192,10 +219,7 @@ function formatMetricsLine(task, plan, cols) {
|
|
|
192
219
|
fullSegments.push({ text: " " }, { text: "│", color: exports.DIM }, { text: " " });
|
|
193
220
|
if (plan)
|
|
194
221
|
fullSegments.push(...buildPairFullSegments("plan", pTok, pTime));
|
|
195
|
-
|
|
196
|
-
for (const seg of fullSegments)
|
|
197
|
-
fullPlainLen += seg.text.length;
|
|
198
|
-
if (fullPlainLen <= cols)
|
|
222
|
+
if (segmentsWidth(fullSegments) <= cols)
|
|
199
223
|
return renderSegments(fullSegments);
|
|
200
224
|
const compactSegments = [];
|
|
201
225
|
if (task)
|
|
@@ -204,10 +228,7 @@ function formatMetricsLine(task, plan, cols) {
|
|
|
204
228
|
compactSegments.push({ text: "│", color: exports.DIM });
|
|
205
229
|
if (plan)
|
|
206
230
|
compactSegments.push(...buildPairCompactSegments("p:", pTok, pTime));
|
|
207
|
-
|
|
208
|
-
for (const seg of compactSegments)
|
|
209
|
-
compactPlainLen += seg.text.length;
|
|
210
|
-
if (compactPlainLen <= cols)
|
|
231
|
+
if (segmentsWidth(compactSegments) <= cols)
|
|
211
232
|
return renderSegments(compactSegments);
|
|
212
233
|
return renderSegmentsToWidth(compactSegments, cols);
|
|
213
234
|
}
|
|
@@ -235,36 +256,40 @@ function reviewerEntryDescriptor(model, effort) {
|
|
|
235
256
|
const effortToken = effort === "" ? "default" : effort;
|
|
236
257
|
return `(${modelToken} ${effortToken})`;
|
|
237
258
|
}
|
|
238
|
-
function
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
const r = reviewers[i];
|
|
242
|
-
if (i > 0)
|
|
243
|
-
line += ", ";
|
|
244
|
-
line += `${r.tool} ${reviewerEntryDescriptor(r.model, r.effort)}: ${r.state}`;
|
|
259
|
+
function reviewerStateText(r, nowMs) {
|
|
260
|
+
if (r.state === "waiting" && r.endTime != null) {
|
|
261
|
+
return `waiting ${formatCompactCountdown(Math.max(0, r.endTime - nowMs))}`;
|
|
245
262
|
}
|
|
246
|
-
return
|
|
263
|
+
return r.state;
|
|
247
264
|
}
|
|
248
|
-
function
|
|
249
|
-
|
|
265
|
+
function reviewerEntryColor(state) {
|
|
266
|
+
if (state === "pass")
|
|
267
|
+
return exports.GREEN;
|
|
268
|
+
if (state === "fail")
|
|
269
|
+
return exports.RED;
|
|
270
|
+
return exports.ORANGE;
|
|
271
|
+
}
|
|
272
|
+
function buildReviewingSegments(frame, reviewers, nowMs, compact) {
|
|
273
|
+
const segments = [{ text: `${frame} review: `, color: exports.ORANGE }];
|
|
250
274
|
for (let i = 0; i < reviewers.length; i++) {
|
|
251
275
|
const r = reviewers[i];
|
|
252
276
|
if (i > 0)
|
|
253
|
-
|
|
254
|
-
|
|
277
|
+
segments.push({ text: ", ", color: exports.ORANGE });
|
|
278
|
+
const descriptor = compact ? "" : ` ${reviewerEntryDescriptor(r.model, r.effort)}`;
|
|
279
|
+
segments.push({ text: `${r.tool}${descriptor}: ${reviewerStateText(r, nowMs)}`, color: reviewerEntryColor(r.state) });
|
|
255
280
|
}
|
|
256
|
-
return
|
|
281
|
+
return segments;
|
|
257
282
|
}
|
|
258
|
-
function formatReviewingFooter(reviewers, cols) {
|
|
259
|
-
const
|
|
260
|
-
if (
|
|
261
|
-
return renderSegments(
|
|
283
|
+
function formatReviewingFooter(frame, reviewers, cols, nowMs) {
|
|
284
|
+
const full = buildReviewingSegments(frame, reviewers, nowMs, false);
|
|
285
|
+
if (segmentsWidth(full) <= cols) {
|
|
286
|
+
return renderSegments(full);
|
|
262
287
|
}
|
|
263
|
-
const
|
|
264
|
-
if (
|
|
265
|
-
return renderSegments(
|
|
288
|
+
const compact = buildReviewingSegments(frame, reviewers, nowMs, true);
|
|
289
|
+
if (segmentsWidth(compact) <= cols) {
|
|
290
|
+
return renderSegments(compact);
|
|
266
291
|
}
|
|
267
|
-
return renderSegmentsToWidth(
|
|
292
|
+
return renderSegmentsToWidth(compact, cols);
|
|
268
293
|
}
|
|
269
294
|
function fitOrangeFooterLine(text, cols) {
|
|
270
295
|
if (text.length <= cols) {
|
|
@@ -272,8 +297,11 @@ function fitOrangeFooterLine(text, cols) {
|
|
|
272
297
|
}
|
|
273
298
|
return renderSegmentsToWidth([{ text, color: exports.ORANGE }], cols);
|
|
274
299
|
}
|
|
275
|
-
function formatWorkingFooter(frame, cols) {
|
|
276
|
-
return fitOrangeFooterLine(`${frame}
|
|
300
|
+
function formatWorkingFooter(frame, label, cols) {
|
|
301
|
+
return fitOrangeFooterLine(`${frame} ${label}`, cols);
|
|
302
|
+
}
|
|
303
|
+
function formatTerminalFooter(label, cols) {
|
|
304
|
+
return fitOrangeFooterLine(label, cols);
|
|
277
305
|
}
|
|
278
306
|
function formatWaitingFooter(heading, dateTime, countdown, cols) {
|
|
279
307
|
return fitOrangeFooterLine(`${heading} — ${dateTime} — ${countdown}`, cols);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { RandomContext } from "./contexts";
|
|
2
|
+
export declare const workingPool: readonly string[];
|
|
3
|
+
export declare const successPool: readonly string[];
|
|
4
|
+
export declare const hardStopPool: readonly string[];
|
|
5
|
+
export declare const interruptionPool: readonly string[];
|
|
6
|
+
export declare const failurePool: readonly string[];
|
|
7
|
+
export declare const tasksCompletedPool: readonly string[];
|
|
8
|
+
export declare const allTasksCompletedPool: readonly string[];
|
|
9
|
+
export declare const terminalPools: {
|
|
10
|
+
Done: readonly string[];
|
|
11
|
+
"Hard stop": readonly string[];
|
|
12
|
+
Interrupted: readonly string[];
|
|
13
|
+
Failed: readonly string[];
|
|
14
|
+
};
|
|
15
|
+
export declare function pickVariant(pool: readonly string[], random: RandomContext, exclude?: string): string;
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.terminalPools = exports.allTasksCompletedPool = exports.tasksCompletedPool = exports.failurePool = exports.interruptionPool = exports.hardStopPool = exports.successPool = exports.workingPool = void 0;
|
|
4
|
+
exports.pickVariant = pickVariant;
|
|
5
|
+
exports.workingPool = [
|
|
6
|
+
"Workin'-diddly",
|
|
7
|
+
"Toilin' away",
|
|
8
|
+
"Okely-workin'",
|
|
9
|
+
"Pluggin' away",
|
|
10
|
+
"Diddly-doin'",
|
|
11
|
+
"Beaverin' away",
|
|
12
|
+
"Tinkerin'-diddly",
|
|
13
|
+
"Hammerin' away",
|
|
14
|
+
"Whittlin' away",
|
|
15
|
+
"Doodly-doin'",
|
|
16
|
+
"Scribblin'-diddly",
|
|
17
|
+
"Codin'-aroo",
|
|
18
|
+
"Buildin'-diddly",
|
|
19
|
+
"Fixin'-diddly",
|
|
20
|
+
"Noodlin' away",
|
|
21
|
+
"Tappity-tappin'",
|
|
22
|
+
"Diddly-dabblin'",
|
|
23
|
+
"Plowin' ahead",
|
|
24
|
+
"Chuggin' along",
|
|
25
|
+
"Pressin' on-aroo",
|
|
26
|
+
"Diligent-diddly",
|
|
27
|
+
"Steady-diddly",
|
|
28
|
+
"Crunchin'-diddly",
|
|
29
|
+
"Diddly-developin'",
|
|
30
|
+
"Wranglin' code",
|
|
31
|
+
"Pokin' at it",
|
|
32
|
+
"Diddly-debuggin'",
|
|
33
|
+
"Cookin'-diddly",
|
|
34
|
+
"Trundlin' along",
|
|
35
|
+
"Diddly-draftin'",
|
|
36
|
+
"Bustlin'-aroo",
|
|
37
|
+
"Hummin' along",
|
|
38
|
+
"Choppin'-diddly",
|
|
39
|
+
"Sweatin'-diddly",
|
|
40
|
+
"Diddly-deliverin'",
|
|
41
|
+
"Craftin'-aroo",
|
|
42
|
+
"Workin'-aroo",
|
|
43
|
+
"Toilin'-diddly",
|
|
44
|
+
"Diddly-drudgin'",
|
|
45
|
+
"Gettin' it done",
|
|
46
|
+
"Grindin'-diddly",
|
|
47
|
+
"Diddly-graftin'",
|
|
48
|
+
"Peggin' away",
|
|
49
|
+
"Diddly-doodlin'",
|
|
50
|
+
"Crankin'-diddly",
|
|
51
|
+
"Hustlin'-diddly",
|
|
52
|
+
"Diddly-diggin'",
|
|
53
|
+
"Pluggin'-diddly",
|
|
54
|
+
"Tappin' keys",
|
|
55
|
+
"Dilly-workin'",
|
|
56
|
+
];
|
|
57
|
+
exports.successPool = [
|
|
58
|
+
"Done-diddly",
|
|
59
|
+
"Done-diddly-done",
|
|
60
|
+
"All wrapped up, neighbor",
|
|
61
|
+
"Okely-dokely — done",
|
|
62
|
+
"Done-aroo",
|
|
63
|
+
"Hi-diddly-done",
|
|
64
|
+
"All done, neighborino",
|
|
65
|
+
"Done and done-diddly",
|
|
66
|
+
"That's a wrap, neighbor",
|
|
67
|
+
"Mission accomplished-diddly",
|
|
68
|
+
];
|
|
69
|
+
exports.hardStopPool = [
|
|
70
|
+
"Whoopsie, hard stop",
|
|
71
|
+
"Dilly of a pickle — hard stop",
|
|
72
|
+
"Hard stop, neighbor",
|
|
73
|
+
"Hard stop-aroo",
|
|
74
|
+
"Well, heck — hard stop",
|
|
75
|
+
"Hard stop-diddly",
|
|
76
|
+
"Fiddlesticks — hard stop",
|
|
77
|
+
"Gotta call it — hard stop",
|
|
78
|
+
"Pumpin' the brakes — hard stop",
|
|
79
|
+
"Hard stop, neighborino",
|
|
80
|
+
];
|
|
81
|
+
exports.interruptionPool = [
|
|
82
|
+
"Interrupted-aroo",
|
|
83
|
+
"Well, heck — interrupted",
|
|
84
|
+
"Interrupted, neighbor",
|
|
85
|
+
"Stopped short-diddly",
|
|
86
|
+
"Cut off-aroo",
|
|
87
|
+
"Hold the phone — interrupted",
|
|
88
|
+
"Interrupted-diddly",
|
|
89
|
+
"Toodle-oo — interrupted",
|
|
90
|
+
"Interrupted, neighborino",
|
|
91
|
+
"Halted-aroo",
|
|
92
|
+
];
|
|
93
|
+
exports.failurePool = [
|
|
94
|
+
"Aw, fiddlesticks — failed",
|
|
95
|
+
"Dad-gummit — failed",
|
|
96
|
+
"Failed, neighbor",
|
|
97
|
+
"Failed-aroo",
|
|
98
|
+
"Gosh-darn it — failed",
|
|
99
|
+
"Heavens to Betsy — failed",
|
|
100
|
+
"Failed-diddly",
|
|
101
|
+
"Well, shucks — failed",
|
|
102
|
+
"That's a no-go — failed",
|
|
103
|
+
"Failed, neighborino",
|
|
104
|
+
];
|
|
105
|
+
exports.tasksCompletedPool = [
|
|
106
|
+
"tasks completed — nothin' to do-diddly-do, neighbor!",
|
|
107
|
+
"all done already, neighbor — okely-dokely!",
|
|
108
|
+
"nothin' left to do here, neighbor!",
|
|
109
|
+
"tasks completed — already shipshape, neighborino!",
|
|
110
|
+
"nothin' to do-diddly-do — all set, neighbor!",
|
|
111
|
+
"tasks completed — couldn't be tidier, neighbor!",
|
|
112
|
+
"all squared away already — okely-dokely!",
|
|
113
|
+
"nothin' doin' here — all done, neighbor!",
|
|
114
|
+
"tasks completed — easy-peasy, neighborino!",
|
|
115
|
+
"all wrapped up already, neighbor — toodle-oo!",
|
|
116
|
+
];
|
|
117
|
+
exports.allTasksCompletedPool = [
|
|
118
|
+
"all tasks completed — okely-dokely-doo, neighbor!",
|
|
119
|
+
"all tasks completed — hi-diddly-done, neighbor!",
|
|
120
|
+
"every last task done-diddly-done, neighbor!",
|
|
121
|
+
"all tasks completed — that's a wrap, neighborino!",
|
|
122
|
+
"all tasks completed — mission accomplished-diddly!",
|
|
123
|
+
"all tasks done and dusted, neighbor — okely-dokely!",
|
|
124
|
+
"all tasks completed — nailed it-aroo, neighbor!",
|
|
125
|
+
"every task done-diddly-done — toodle-oo, neighbor!",
|
|
126
|
+
"all tasks completed — what a humdinger, neighbor!",
|
|
127
|
+
"all tasks completed — done and done, neighborino!",
|
|
128
|
+
];
|
|
129
|
+
exports.terminalPools = {
|
|
130
|
+
"Done": exports.successPool,
|
|
131
|
+
"Hard stop": exports.hardStopPool,
|
|
132
|
+
"Interrupted": exports.interruptionPool,
|
|
133
|
+
"Failed": exports.failurePool,
|
|
134
|
+
};
|
|
135
|
+
function pickVariant(pool, random, exclude) {
|
|
136
|
+
const candidates = exclude !== undefined && pool.length > 1
|
|
137
|
+
? pool.filter(variant => variant !== exclude)
|
|
138
|
+
: pool;
|
|
139
|
+
const index = Math.floor(random.random() * candidates.length);
|
|
140
|
+
return candidates[index];
|
|
141
|
+
}
|
|
@@ -16,12 +16,13 @@ type ReadArgs = Readonly<{
|
|
|
16
16
|
projectRoot: string;
|
|
17
17
|
homeDir: string;
|
|
18
18
|
}>;
|
|
19
|
-
type
|
|
19
|
+
type ScopedConfigArgs = ReadArgs & Readonly<{
|
|
20
20
|
scope: "project" | "global";
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
}>;
|
|
22
|
+
type WriteArgs = ScopedConfigArgs & Readonly<{
|
|
23
23
|
config: FlandersConfig;
|
|
24
24
|
}>;
|
|
25
25
|
export declare function read(fs: FsContext, args: ReadArgs): Promise<FlandersConfig | null>;
|
|
26
|
+
export declare function readScope(fs: FsContext, args: ScopedConfigArgs): Promise<FlandersConfig | null>;
|
|
26
27
|
export declare function write(fs: FsContext, args: WriteArgs): Promise<string>;
|
|
27
28
|
export {};
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.read = read;
|
|
4
|
+
exports.readScope = readScope;
|
|
4
5
|
exports.write = write;
|
|
5
6
|
const fsUtils_1 = require("../system/fsUtils");
|
|
6
7
|
const CONFIG_PATH = ".flanders/config.json";
|
|
7
8
|
const CONFIG_DIR = ".flanders";
|
|
9
|
+
const ALLOWED_TOP_LEVEL_KEYS = ["worker", "reviewers", "minimumReviews"];
|
|
8
10
|
function configPath(base) {
|
|
9
11
|
return (0, fsUtils_1.joinPath)(base, CONFIG_PATH);
|
|
10
12
|
}
|
|
@@ -42,6 +44,11 @@ function validate(raw, filePath) {
|
|
|
42
44
|
if (typeof minimumReviews !== "number" || !Number.isInteger(minimumReviews) || minimumReviews < 1 || minimumReviews > reviewers.length) {
|
|
43
45
|
throw new Error(`Malformed config at ${filePath}: field "minimumReviews" must be an integer in [1, ${reviewers.length}]`);
|
|
44
46
|
}
|
|
47
|
+
for (const key of Object.keys(obj)) {
|
|
48
|
+
if (!ALLOWED_TOP_LEVEL_KEYS.includes(key)) {
|
|
49
|
+
throw new Error(`Malformed config at ${filePath}: unexpected top-level key "${key}"`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
45
52
|
return raw;
|
|
46
53
|
}
|
|
47
54
|
function validateRole(role, name, filePath) {
|
|
@@ -85,6 +92,17 @@ async function read(fs, args) {
|
|
|
85
92
|
}
|
|
86
93
|
return null;
|
|
87
94
|
}
|
|
95
|
+
async function readScope(fs, args) {
|
|
96
|
+
const base = args.scope === "project" ? args.projectRoot : args.homeDir;
|
|
97
|
+
const target = configPath(base);
|
|
98
|
+
try {
|
|
99
|
+
const content = await fs.readFile(target);
|
|
100
|
+
return validate(JSON.parse(content), target);
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
88
106
|
async function write(fs, args) {
|
|
89
107
|
const base = args.scope === "project" ? args.projectRoot : args.homeDir;
|
|
90
108
|
const dir = configDir(base);
|
|
@@ -4,6 +4,7 @@ export type WorkspacePaths = Readonly<{
|
|
|
4
4
|
buildScript: string;
|
|
5
5
|
testScript: string;
|
|
6
6
|
errorLog: string;
|
|
7
|
+
specFile: string;
|
|
7
8
|
prepLog(taskIndex: number): string;
|
|
8
9
|
workerLog(iter: number): string;
|
|
9
10
|
buildLog(iter: number): string;
|
|
@@ -11,6 +12,7 @@ export type WorkspacePaths = Readonly<{
|
|
|
11
12
|
reviewerLog(iter: number): string;
|
|
12
13
|
reviewerOutputLog(iter: number, reviewerIndex: number): string;
|
|
13
14
|
reviewerErrorLog(reviewerIndex: number): string;
|
|
15
|
+
reviewerSpecFile(reviewerIndex: number): string;
|
|
14
16
|
}>;
|
|
15
17
|
export interface PlatformContext {
|
|
16
18
|
isWindows(): boolean;
|
|
@@ -44,13 +44,15 @@ class Workspace {
|
|
|
44
44
|
buildScript: (0, fsUtils_1.joinPath)(root, isWindows ? "build.bat" : "build.sh"),
|
|
45
45
|
testScript: (0, fsUtils_1.joinPath)(root, isWindows ? "test.bat" : "test.sh"),
|
|
46
46
|
errorLog: (0, fsUtils_1.joinPath)(root, "error.log"),
|
|
47
|
+
specFile: (0, fsUtils_1.joinPath)(root, "spec.md"),
|
|
47
48
|
prepLog(taskIndex) { return (0, fsUtils_1.joinPath)(root, `prep.${taskIndex}.log`); },
|
|
48
49
|
workerLog(iter) { return (0, fsUtils_1.joinPath)(root, `worker.${iter}.log`); },
|
|
49
50
|
buildLog(iter) { return (0, fsUtils_1.joinPath)(root, `build.${iter}.log`); },
|
|
50
51
|
testLog(iter) { return (0, fsUtils_1.joinPath)(root, `test.${iter}.log`); },
|
|
51
52
|
reviewerLog(iter) { return (0, fsUtils_1.joinPath)(root, `reviewer.${iter}.log`); },
|
|
52
53
|
reviewerOutputLog(iter, reviewerIndex) { return (0, fsUtils_1.joinPath)(root, `reviewer.${iter}.${reviewerIndex}.log`); },
|
|
53
|
-
reviewerErrorLog(reviewerIndex) { return (0, fsUtils_1.joinPath)(reviewerRoots[reviewerIndex - 1], "error.log"); }
|
|
54
|
+
reviewerErrorLog(reviewerIndex) { return (0, fsUtils_1.joinPath)(reviewerRoots[reviewerIndex - 1], "error.log"); },
|
|
55
|
+
reviewerSpecFile(reviewerIndex) { return (0, fsUtils_1.joinPath)(reviewerRoots[reviewerIndex - 1], "spec.md"); }
|
|
54
56
|
};
|
|
55
57
|
}
|
|
56
58
|
async errorLogExists() {
|