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
|
@@ -80,10 +80,16 @@ export function parsePromptMd(content: string, promptPath: string): CoreParsedTa
|
|
|
80
80
|
const taskFolder = dirname(resolve(promptPath));
|
|
81
81
|
|
|
82
82
|
// Task ID and name
|
|
83
|
-
let taskId = "",
|
|
83
|
+
let taskId = "",
|
|
84
|
+
taskName = "";
|
|
84
85
|
const titleMatch = text.match(/^#\s+(?:Task:\s*)?(\S+-\d+)\s*[-–:]\s*(.+)/m);
|
|
85
|
-
if (titleMatch) {
|
|
86
|
-
|
|
86
|
+
if (titleMatch) {
|
|
87
|
+
taskId = titleMatch[1];
|
|
88
|
+
taskName = titleMatch[2].trim();
|
|
89
|
+
} else {
|
|
90
|
+
taskId = basename(taskFolder);
|
|
91
|
+
taskName = taskId;
|
|
92
|
+
}
|
|
87
93
|
|
|
88
94
|
// Review level
|
|
89
95
|
let reviewLevel = 0;
|
|
@@ -99,22 +105,27 @@ export function parsePromptMd(content: string, promptPath: string): CoreParsedTa
|
|
|
99
105
|
const steps: StepInfo[] = [];
|
|
100
106
|
const stepRegex = /###\s+Step\s+(\d+):\s*(.+)/g;
|
|
101
107
|
const positions: { number: number; name: string; start: number }[] = [];
|
|
102
|
-
let m;
|
|
108
|
+
let m: RegExpExecArray | null;
|
|
103
109
|
while ((m = stepRegex.exec(text)) !== null) {
|
|
104
110
|
positions.push({ number: parseInt(m[1]), name: m[2].trim(), start: m.index });
|
|
105
111
|
}
|
|
106
112
|
for (let i = 0; i < positions.length; i++) {
|
|
107
|
-
const section = text.slice(
|
|
113
|
+
const section = text.slice(
|
|
114
|
+
positions[i].start,
|
|
115
|
+
i + 1 < positions.length ? positions[i + 1].start : text.length,
|
|
116
|
+
);
|
|
108
117
|
const checkboxes: { text: string; checked: boolean }[] = [];
|
|
109
118
|
const cbRegex = /^\s*-\s*\[([ xX])\]\s*(.*)/gm;
|
|
110
|
-
let cb;
|
|
119
|
+
let cb: RegExpExecArray | null;
|
|
111
120
|
while ((cb = cbRegex.exec(section)) !== null) {
|
|
112
121
|
checkboxes.push({ text: cb[2].trim(), checked: cb[1].toLowerCase() === "x" });
|
|
113
122
|
}
|
|
114
123
|
steps.push({
|
|
115
|
-
number: positions[i].number,
|
|
116
|
-
|
|
117
|
-
|
|
124
|
+
number: positions[i].number,
|
|
125
|
+
name: positions[i].name,
|
|
126
|
+
status: "not-started",
|
|
127
|
+
checkboxes,
|
|
128
|
+
totalChecked: checkboxes.filter((c) => c.checked).length,
|
|
118
129
|
totalItems: checkboxes.length,
|
|
119
130
|
});
|
|
120
131
|
}
|
|
@@ -124,7 +135,7 @@ export function parsePromptMd(content: string, promptPath: string): CoreParsedTa
|
|
|
124
135
|
const ctxMatch = text.match(/##\s+Context to Read First\s*\n+([\s\S]*?)(?=\n##\s|$)/);
|
|
125
136
|
if (ctxMatch) {
|
|
126
137
|
const pathRegex = /`([^\s`]+\.(?:md|yaml|json|go|ts|js))`/g;
|
|
127
|
-
let pm;
|
|
138
|
+
let pm: RegExpExecArray | null;
|
|
128
139
|
while ((pm = pathRegex.exec(ctxMatch[1])) !== null) contextDocs.push(pm[1]);
|
|
129
140
|
}
|
|
130
141
|
|
|
@@ -145,7 +156,8 @@ export function parseStatusMd(content: string): ParsedStatus {
|
|
|
145
156
|
const text = content.replace(/\r\n/g, "\n");
|
|
146
157
|
const steps: StepInfo[] = [];
|
|
147
158
|
let currentStep: StepInfo | null = null;
|
|
148
|
-
let reviewCounter = 0,
|
|
159
|
+
let reviewCounter = 0,
|
|
160
|
+
iteration = 0;
|
|
149
161
|
|
|
150
162
|
for (const line of text.split("\n")) {
|
|
151
163
|
const rcMatch = line.match(/\*\*Review Counter:\*\*\s*(\d+)/);
|
|
@@ -156,11 +168,18 @@ export function parseStatusMd(content: string): ParsedStatus {
|
|
|
156
168
|
const stepMatch = line.match(/^###\s+Step\s+(\d+):\s*(.+)/);
|
|
157
169
|
if (stepMatch) {
|
|
158
170
|
if (currentStep) {
|
|
159
|
-
currentStep.totalChecked = currentStep.checkboxes.filter(c => c.checked).length;
|
|
171
|
+
currentStep.totalChecked = currentStep.checkboxes.filter((c) => c.checked).length;
|
|
160
172
|
currentStep.totalItems = currentStep.checkboxes.length;
|
|
161
173
|
steps.push(currentStep);
|
|
162
174
|
}
|
|
163
|
-
currentStep = {
|
|
175
|
+
currentStep = {
|
|
176
|
+
number: parseInt(stepMatch[1]),
|
|
177
|
+
name: stepMatch[2].trim(),
|
|
178
|
+
status: "not-started",
|
|
179
|
+
checkboxes: [],
|
|
180
|
+
totalChecked: 0,
|
|
181
|
+
totalItems: 0,
|
|
182
|
+
};
|
|
164
183
|
continue;
|
|
165
184
|
}
|
|
166
185
|
if (currentStep) {
|
|
@@ -168,14 +187,16 @@ export function parseStatusMd(content: string): ParsedStatus {
|
|
|
168
187
|
if (ss) {
|
|
169
188
|
const s = ss[1];
|
|
170
189
|
if (s.includes("✅") || s.toLowerCase().includes("complete")) currentStep.status = "complete";
|
|
171
|
-
else if (s.includes("🟨") || s.toLowerCase().includes("progress"))
|
|
190
|
+
else if (s.includes("🟨") || s.toLowerCase().includes("progress"))
|
|
191
|
+
currentStep.status = "in-progress";
|
|
172
192
|
}
|
|
173
193
|
const cb = line.match(/^\s*-\s*\[([ xX])\]\s*(.*)/);
|
|
174
|
-
if (cb)
|
|
194
|
+
if (cb)
|
|
195
|
+
currentStep.checkboxes.push({ text: cb[2].trim(), checked: cb[1].toLowerCase() === "x" });
|
|
175
196
|
}
|
|
176
197
|
}
|
|
177
198
|
if (currentStep) {
|
|
178
|
-
currentStep.totalChecked = currentStep.checkboxes.filter(c => c.checked).length;
|
|
199
|
+
currentStep.totalChecked = currentStep.checkboxes.filter((c) => c.checked).length;
|
|
179
200
|
currentStep.totalItems = currentStep.checkboxes.length;
|
|
180
201
|
steps.push(currentStep);
|
|
181
202
|
}
|
|
@@ -190,17 +211,27 @@ export function parseStatusMd(content: string): ParsedStatus {
|
|
|
190
211
|
* @param task - Parsed task (from parsePromptMd or orchestrator ParsedTask)
|
|
191
212
|
* @returns Complete STATUS.md content string
|
|
192
213
|
*/
|
|
193
|
-
export function generateStatusMd(task: {
|
|
214
|
+
export function generateStatusMd(task: {
|
|
215
|
+
taskId: string;
|
|
216
|
+
taskName: string;
|
|
217
|
+
reviewLevel: number;
|
|
218
|
+
size: string;
|
|
219
|
+
steps: StepInfo[];
|
|
220
|
+
}): string {
|
|
194
221
|
const now = new Date().toISOString().slice(0, 10);
|
|
195
222
|
const lines: string[] = [
|
|
196
|
-
`# ${task.taskId}: ${task.taskName} — Status`,
|
|
223
|
+
`# ${task.taskId}: ${task.taskName} — Status`,
|
|
224
|
+
"",
|
|
197
225
|
`**Current Step:** Not Started`,
|
|
198
226
|
`**Status:** 🔵 Ready for Execution`,
|
|
199
227
|
`**Last Updated:** ${now}`,
|
|
200
228
|
`**Review Level:** ${task.reviewLevel}`,
|
|
201
229
|
`**Review Counter:** 0`,
|
|
202
230
|
`**Iteration:** 0`,
|
|
203
|
-
`**Size:** ${task.size}`,
|
|
231
|
+
`**Size:** ${task.size}`,
|
|
232
|
+
"",
|
|
233
|
+
"---",
|
|
234
|
+
"",
|
|
204
235
|
];
|
|
205
236
|
for (const step of task.steps) {
|
|
206
237
|
lines.push(`### Step ${step.number}: ${step.name}`, `**Status:** ⬜ Not Started`, "");
|
|
@@ -208,11 +239,37 @@ export function generateStatusMd(task: { taskId: string; taskName: string; revie
|
|
|
208
239
|
lines.push("", "---", "");
|
|
209
240
|
}
|
|
210
241
|
lines.push(
|
|
211
|
-
"## Reviews",
|
|
212
|
-
"
|
|
213
|
-
"
|
|
214
|
-
|
|
215
|
-
"
|
|
242
|
+
"## Reviews",
|
|
243
|
+
"",
|
|
244
|
+
"| # | Type | Step | Verdict | File |",
|
|
245
|
+
"|---|------|------|---------|------|",
|
|
246
|
+
"",
|
|
247
|
+
"---",
|
|
248
|
+
"",
|
|
249
|
+
"## Discoveries",
|
|
250
|
+
"",
|
|
251
|
+
"| Discovery | Disposition | Location |",
|
|
252
|
+
"|-----------|-------------|----------|",
|
|
253
|
+
"",
|
|
254
|
+
"---",
|
|
255
|
+
"",
|
|
256
|
+
"## Execution Log",
|
|
257
|
+
"",
|
|
258
|
+
"| Timestamp | Action | Outcome |",
|
|
259
|
+
"|-----------|--------|---------|",
|
|
260
|
+
`| ${now} | Task staged | STATUS.md auto-generated by task-runner |`,
|
|
261
|
+
"",
|
|
262
|
+
"---",
|
|
263
|
+
"",
|
|
264
|
+
"## Blockers",
|
|
265
|
+
"",
|
|
266
|
+
"*None*",
|
|
267
|
+
"",
|
|
268
|
+
"---",
|
|
269
|
+
"",
|
|
270
|
+
"## Notes",
|
|
271
|
+
"",
|
|
272
|
+
"*Reserved for execution notes*",
|
|
216
273
|
);
|
|
217
274
|
return lines.join("\n");
|
|
218
275
|
}
|
|
@@ -230,7 +287,9 @@ export function generateStatusMd(task: { taskId: string; taskName: string; revie
|
|
|
230
287
|
*/
|
|
231
288
|
export function updateStatusField(statusPath: string, field: string, value: string): void {
|
|
232
289
|
let content = readFileSync(statusPath, "utf-8").replace(/\r\n/g, "\n");
|
|
233
|
-
const pattern = new RegExp(
|
|
290
|
+
const pattern = new RegExp(
|
|
291
|
+
`(\\*\\*${field.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}:\\*\\*\\s*)(.+)`,
|
|
292
|
+
);
|
|
234
293
|
if (pattern.test(content)) {
|
|
235
294
|
content = content.replace(pattern, `$1${value}`);
|
|
236
295
|
} else {
|
|
@@ -246,9 +305,18 @@ export function updateStatusField(statusPath: string, field: string, value: stri
|
|
|
246
305
|
* @param stepNum - Step number to update
|
|
247
306
|
* @param status - New status
|
|
248
307
|
*/
|
|
249
|
-
export function updateStepStatus(
|
|
308
|
+
export function updateStepStatus(
|
|
309
|
+
statusPath: string,
|
|
310
|
+
stepNum: number,
|
|
311
|
+
status: "not-started" | "in-progress" | "complete",
|
|
312
|
+
): void {
|
|
250
313
|
let content = readFileSync(statusPath, "utf-8").replace(/\r\n/g, "\n");
|
|
251
|
-
const emoji =
|
|
314
|
+
const emoji =
|
|
315
|
+
status === "complete"
|
|
316
|
+
? "✅ Complete"
|
|
317
|
+
: status === "in-progress"
|
|
318
|
+
? "🟨 In Progress"
|
|
319
|
+
: "⬜ Not Started";
|
|
252
320
|
const lines = content.split("\n");
|
|
253
321
|
let inTarget = false;
|
|
254
322
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -272,7 +340,9 @@ export function updateStepStatus(statusPath: string, stepNum: number, status: "n
|
|
|
272
340
|
export function appendTableRow(statusPath: string, sectionName: string, row: string): void {
|
|
273
341
|
let content = readFileSync(statusPath, "utf-8").replace(/\r\n/g, "\n");
|
|
274
342
|
const lines = content.split("\n");
|
|
275
|
-
let insertIdx = -1,
|
|
343
|
+
let insertIdx = -1,
|
|
344
|
+
inSection = false,
|
|
345
|
+
lastTableRow = -1;
|
|
276
346
|
for (let i = 0; i < lines.length; i++) {
|
|
277
347
|
if (lines[i].match(new RegExp(`^##\\s+${sectionName}`))) {
|
|
278
348
|
inSection = true;
|
|
@@ -306,8 +376,19 @@ export function logExecution(statusPath: string, action: string, outcome: string
|
|
|
306
376
|
/**
|
|
307
377
|
* Log a review entry to the Reviews table in STATUS.md.
|
|
308
378
|
*/
|
|
309
|
-
export function logReview(
|
|
310
|
-
|
|
379
|
+
export function logReview(
|
|
380
|
+
statusPath: string,
|
|
381
|
+
num: string,
|
|
382
|
+
type: string,
|
|
383
|
+
stepNum: number,
|
|
384
|
+
verdict: string,
|
|
385
|
+
file: string,
|
|
386
|
+
): void {
|
|
387
|
+
appendTableRow(
|
|
388
|
+
statusPath,
|
|
389
|
+
"Reviews",
|
|
390
|
+
`| ${num} | ${type} | Step ${stepNum} | ${verdict} | ${file} |`,
|
|
391
|
+
);
|
|
311
392
|
}
|
|
312
393
|
|
|
313
394
|
/**
|
|
@@ -370,8 +451,18 @@ export function extractVerdict(reviewContent: string): string {
|
|
|
370
451
|
|
|
371
452
|
// Tolerate non-standard verdict formats
|
|
372
453
|
const lower = reviewContent.toLowerCase();
|
|
373
|
-
if (
|
|
374
|
-
|
|
454
|
+
if (
|
|
455
|
+
lower.includes("changes requested") ||
|
|
456
|
+
lower.includes("request changes") ||
|
|
457
|
+
lower.includes("needs revision")
|
|
458
|
+
)
|
|
459
|
+
return "REVISE";
|
|
460
|
+
if (
|
|
461
|
+
lower.includes("approve") &&
|
|
462
|
+
!lower.includes("do not approve") &&
|
|
463
|
+
!lower.includes("cannot approve")
|
|
464
|
+
)
|
|
465
|
+
return "APPROVE";
|
|
375
466
|
if (lower.includes("rethink") || lower.includes("re-think")) return "RETHINK";
|
|
376
467
|
|
|
377
468
|
return "UNKNOWN";
|
|
@@ -409,7 +500,17 @@ export function getHeadCommitSha(): string {
|
|
|
409
500
|
*/
|
|
410
501
|
export function findStepBoundaryCommit(stepNumber: number, taskId: string, since?: string): string {
|
|
411
502
|
try {
|
|
412
|
-
const args = [
|
|
503
|
+
const args = [
|
|
504
|
+
"log",
|
|
505
|
+
"--oneline",
|
|
506
|
+
"--grep",
|
|
507
|
+
`complete Step ${stepNumber}`,
|
|
508
|
+
"--grep",
|
|
509
|
+
taskId,
|
|
510
|
+
"--all-match",
|
|
511
|
+
"-1",
|
|
512
|
+
"--format=%H",
|
|
513
|
+
];
|
|
413
514
|
if (since) args.push(`${since}..HEAD`);
|
|
414
515
|
const result = spawnSync("git", args, {
|
|
415
516
|
encoding: "utf-8",
|
|
@@ -443,7 +544,7 @@ export interface StandardsConfig {
|
|
|
443
544
|
export function resolveStandards(
|
|
444
545
|
globalStandards: StandardsConfig,
|
|
445
546
|
overrides: Record<string, Partial<StandardsConfig>>,
|
|
446
|
-
taskAreas: Record<string, { path: string;[key: string]: any }>,
|
|
547
|
+
taskAreas: Record<string, { path: string; [key: string]: any }>,
|
|
447
548
|
taskFolder: string,
|
|
448
549
|
): StandardsConfig {
|
|
449
550
|
const normalizedFolder = taskFolder.replace(/\\/g, "/");
|
|
@@ -488,44 +589,60 @@ export function generateReviewRequest(
|
|
|
488
589
|
outputPath: string,
|
|
489
590
|
stepBaselineCommit?: string,
|
|
490
591
|
): string {
|
|
491
|
-
const standardsDocs = standards.docs.map(d => ` - ${d}`).join("\n");
|
|
492
|
-
const standardsRules = standards.rules.map(r => `- ${r}`).join("\n");
|
|
592
|
+
const standardsDocs = standards.docs.map((d) => ` - ${d}`).join("\n");
|
|
593
|
+
const standardsRules = standards.rules.map((r) => `- ${r}`).join("\n");
|
|
493
594
|
const statusPath = join(taskFolder, "STATUS.md");
|
|
494
595
|
|
|
495
596
|
if (type === "plan") {
|
|
496
597
|
return [
|
|
497
|
-
`# Review Request: Plan Review`,
|
|
598
|
+
`# Review Request: Plan Review`,
|
|
599
|
+
"",
|
|
498
600
|
`You are reviewing an implementation plan for a ${projectName} task.`,
|
|
499
|
-
`You have full tool access — use \`read\` to examine files and \`bash\` to run commands.`,
|
|
500
|
-
|
|
601
|
+
`You have full tool access — use \`read\` to examine files and \`bash\` to run commands.`,
|
|
602
|
+
"",
|
|
603
|
+
`## Task Context`,
|
|
604
|
+
"",
|
|
501
605
|
`- **Task PROMPT:** ${taskPromptPath}`,
|
|
502
606
|
`- **Task STATUS:** ${statusPath}`,
|
|
503
|
-
`- **Step being planned:** Step ${stepNum}: ${stepName}`,
|
|
504
|
-
|
|
607
|
+
`- **Step being planned:** Step ${stepNum}: ${stepName}`,
|
|
608
|
+
"",
|
|
609
|
+
`## Instructions`,
|
|
610
|
+
"",
|
|
505
611
|
`1. Read the PROMPT.md for full requirements`,
|
|
506
612
|
`2. Read STATUS.md for progress so far`,
|
|
507
613
|
`3. Check relevant source files for existing patterns:`,
|
|
508
|
-
standardsDocs,
|
|
509
|
-
|
|
510
|
-
`##
|
|
614
|
+
standardsDocs,
|
|
615
|
+
"",
|
|
616
|
+
`## Project Standards`,
|
|
617
|
+
"",
|
|
618
|
+
standardsRules,
|
|
619
|
+
"",
|
|
620
|
+
`## Output`,
|
|
621
|
+
"",
|
|
511
622
|
`Write your review to: \`${outputPath}\``,
|
|
512
623
|
].join("\n");
|
|
513
624
|
}
|
|
514
625
|
|
|
515
|
-
const diffCmd = stepBaselineCommit
|
|
626
|
+
const diffCmd = stepBaselineCommit
|
|
627
|
+
? `git diff ${stepBaselineCommit}..HEAD --name-only`
|
|
628
|
+
: `git diff --name-only`;
|
|
516
629
|
const diffFullCmd = stepBaselineCommit ? `git diff ${stepBaselineCommit}..HEAD` : `git diff`;
|
|
517
630
|
|
|
518
631
|
return [
|
|
519
|
-
`# Review Request: Code Review`,
|
|
632
|
+
`# Review Request: Code Review`,
|
|
633
|
+
"",
|
|
520
634
|
`You are reviewing code changes for a ${projectName} task.`,
|
|
521
|
-
`You have full tool access — use \`read\` to examine files and \`bash\` to run commands.`,
|
|
522
|
-
|
|
635
|
+
`You have full tool access — use \`read\` to examine files and \`bash\` to run commands.`,
|
|
636
|
+
"",
|
|
637
|
+
`## Task Context`,
|
|
638
|
+
"",
|
|
523
639
|
`- **Task PROMPT:** ${taskPromptPath}`,
|
|
524
640
|
`- **Task STATUS:** ${statusPath}`,
|
|
525
641
|
`- **Step reviewed:** Step ${stepNum}: ${stepName}`,
|
|
526
642
|
...(stepBaselineCommit ? [`- **Step baseline commit:** ${stepBaselineCommit}`] : []),
|
|
527
643
|
"",
|
|
528
|
-
`## Instructions`,
|
|
644
|
+
`## Instructions`,
|
|
645
|
+
"",
|
|
529
646
|
`1. Run \`${diffCmd}\` to see files changed in this step`,
|
|
530
647
|
` Then \`${diffFullCmd}\` for the full diff`,
|
|
531
648
|
` **Important:** The worker commits code via checkpoints, so plain \`git diff\` may show nothing.`,
|
|
@@ -533,9 +650,14 @@ export function generateReviewRequest(
|
|
|
533
650
|
`2. Read changed files in full for context`,
|
|
534
651
|
`3. Check neighboring files for pattern consistency`,
|
|
535
652
|
`4. Check standards:`,
|
|
536
|
-
standardsDocs,
|
|
537
|
-
|
|
538
|
-
`##
|
|
653
|
+
standardsDocs,
|
|
654
|
+
"",
|
|
655
|
+
`## Project Standards`,
|
|
656
|
+
"",
|
|
657
|
+
standardsRules,
|
|
658
|
+
"",
|
|
659
|
+
`## Output`,
|
|
660
|
+
"",
|
|
539
661
|
`Write your review to: \`${outputPath}\``,
|
|
540
662
|
].join("\n");
|
|
541
663
|
}
|
|
@@ -546,5 +668,8 @@ export function generateReviewRequest(
|
|
|
546
668
|
* Convert a kebab-case name to Title Case for display.
|
|
547
669
|
*/
|
|
548
670
|
export function displayName(name: string): string {
|
|
549
|
-
return name
|
|
671
|
+
return name
|
|
672
|
+
.split("-")
|
|
673
|
+
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
674
|
+
.join(" ");
|
|
550
675
|
}
|