chiefwiggum 1.3.48 → 1.3.52
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/dist/cli.cjs +170 -49
- package/package.json +1 -1
- package/templates/prd-template.md +26 -3
package/dist/cli.cjs
CHANGED
|
@@ -300,6 +300,54 @@ var init_config = __esm({
|
|
|
300
300
|
});
|
|
301
301
|
|
|
302
302
|
// src/lib/claude.ts
|
|
303
|
+
function getProjectBoardContext() {
|
|
304
|
+
try {
|
|
305
|
+
const owner = (0, import_node_child_process4.execSync)("gh repo view --json owner -q .owner.login", {
|
|
306
|
+
encoding: "utf-8",
|
|
307
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
308
|
+
timeout: 3e4
|
|
309
|
+
}).trim();
|
|
310
|
+
const projectNumber = (0, import_node_child_process4.execSync)(
|
|
311
|
+
"gh repo view --json projectsV2 -q '.projectsV2.Nodes[0].number'",
|
|
312
|
+
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 3e4 }
|
|
313
|
+
).trim();
|
|
314
|
+
if (!projectNumber || projectNumber === "null") return null;
|
|
315
|
+
const projectId = (0, import_node_child_process4.execSync)(
|
|
316
|
+
"gh repo view --json projectsV2 -q '.projectsV2.Nodes[0].id'",
|
|
317
|
+
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 3e4 }
|
|
318
|
+
).trim();
|
|
319
|
+
const fieldsOutput = (0, import_node_child_process4.execSync)(
|
|
320
|
+
`gh project field-list ${projectNumber} --owner ${owner} --format json`,
|
|
321
|
+
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 3e4 }
|
|
322
|
+
);
|
|
323
|
+
const fields = JSON.parse(fieldsOutput);
|
|
324
|
+
const statusField = fields.fields?.find((f) => f.name === "Status");
|
|
325
|
+
if (!statusField) return null;
|
|
326
|
+
const findOption = (names) => {
|
|
327
|
+
for (const name of names) {
|
|
328
|
+
const option = statusField.options?.find(
|
|
329
|
+
(o) => o.name.toLowerCase() === name.toLowerCase()
|
|
330
|
+
);
|
|
331
|
+
if (option) return option;
|
|
332
|
+
}
|
|
333
|
+
return null;
|
|
334
|
+
};
|
|
335
|
+
const todoOption = findOption(["Todo", "To Do", "To do", "Backlog"]);
|
|
336
|
+
const inProgressOption = findOption(["In Progress", "In progress", "Doing", "Active"]);
|
|
337
|
+
const doneOption = findOption(["Done", "Completed", "Closed", "Finished"]);
|
|
338
|
+
return {
|
|
339
|
+
owner,
|
|
340
|
+
projectNumber,
|
|
341
|
+
projectId,
|
|
342
|
+
statusFieldId: statusField.id,
|
|
343
|
+
todoOptionId: todoOption?.id || "",
|
|
344
|
+
inProgressOptionId: inProgressOption?.id || "",
|
|
345
|
+
doneOptionId: doneOption?.id || ""
|
|
346
|
+
};
|
|
347
|
+
} catch {
|
|
348
|
+
return null;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
303
351
|
async function runClaude(options) {
|
|
304
352
|
const { prompt, streaming = false, onOutput } = options;
|
|
305
353
|
const timeoutMs = options.timeoutMs || config.iterationTimeoutMinutes * 60 * 1e3;
|
|
@@ -422,50 +470,62 @@ IMPORTANT: Only use the root-level files above for project context. Do not read
|
|
|
422
470
|
Output: RALPH_COMPLETE
|
|
423
471
|
If no tasks remain: RALPH_ALL_DONE`;
|
|
424
472
|
}
|
|
425
|
-
function getGitHubTaskPrompt() {
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
## Before Starting
|
|
429
|
-
Read these ROOT-LEVEL files only (ignore subdirectories for project context):
|
|
430
|
-
- CLAUDE.md \u2014 Project context and conventions
|
|
431
|
-
- specs/prd.md \u2014 What we are building
|
|
432
|
-
- specs/technical.md \u2014 How we are building it
|
|
473
|
+
function getGitHubTaskPrompt(boardContext) {
|
|
474
|
+
const boardInstructions = boardContext && boardContext.inProgressOptionId && boardContext.doneOptionId ? `
|
|
475
|
+
## Project Board Status Updates (REQUIRED)
|
|
433
476
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
477
|
+
Pre-computed values for this project:
|
|
478
|
+
- Owner: ${boardContext.owner}
|
|
479
|
+
- Project Number: ${boardContext.projectNumber}
|
|
480
|
+
- Project ID: ${boardContext.projectId}
|
|
481
|
+
- Status Field ID: ${boardContext.statusFieldId}
|
|
482
|
+
- In Progress Option ID: ${boardContext.inProgressOptionId}
|
|
483
|
+
- Done Option ID: ${boardContext.doneOptionId}
|
|
437
484
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
6. Update project board status to "Done"
|
|
445
|
-
7. Commit: git commit -m "type(scope): description (closes #<number>)"
|
|
485
|
+
### Step 1: Get Item ID for the Issue
|
|
486
|
+
Run this to get the ITEM_ID (replace ISSUE_NUMBER with the actual issue number):
|
|
487
|
+
\`\`\`bash
|
|
488
|
+
ITEM_ID=$(gh project item-list ${boardContext.projectNumber} --owner ${boardContext.owner} --format json | jq -r '.items[] | select(.content.number == ISSUE_NUMBER) | .id')
|
|
489
|
+
echo "Item ID: $ITEM_ID"
|
|
490
|
+
\`\`\`
|
|
446
491
|
|
|
447
|
-
|
|
448
|
-
|
|
492
|
+
### Step 2: Mark In Progress (do this FIRST, before any code changes)
|
|
493
|
+
\`\`\`bash
|
|
494
|
+
gh project item-edit --project-id ${boardContext.projectId} --id $ITEM_ID --field-id ${boardContext.statusFieldId} --single-select-option-id ${boardContext.inProgressOptionId}
|
|
495
|
+
\`\`\`
|
|
449
496
|
|
|
497
|
+
### Step 3: Mark Done (do this AFTER closing the issue)
|
|
450
498
|
\`\`\`bash
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
PROJECT_NUM=$(gh repo view --json projectsV2 -q '.projectsV2.Nodes[0].number')
|
|
454
|
-
PROJECT_ID=$(gh repo view --json projectsV2 -q '.projectsV2.Nodes[0].id')
|
|
499
|
+
gh project item-edit --project-id ${boardContext.projectId} --id $ITEM_ID --field-id ${boardContext.statusFieldId} --single-select-option-id ${boardContext.doneOptionId}
|
|
500
|
+
\`\`\`
|
|
455
501
|
|
|
456
|
-
|
|
457
|
-
|
|
502
|
+
**You MUST update the board status at each step. Do not skip this.**
|
|
503
|
+
` : `
|
|
504
|
+
## Project Board Status Updates
|
|
505
|
+
No project board is linked or Status field not found. Skip board status updates.
|
|
506
|
+
`;
|
|
507
|
+
return `You are an autonomous coding agent. Complete ONE GitHub Issue.
|
|
458
508
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
509
|
+
## Before Starting (REQUIRED - Read these first)
|
|
510
|
+
1. Read specs/prd.md to understand what we're building
|
|
511
|
+
2. Read CLAUDE.md for coding conventions
|
|
512
|
+
3. List open issues: gh issue list --state open --json number,title
|
|
462
513
|
|
|
463
|
-
|
|
464
|
-
gh project item-edit --project-id $PROJECT_ID --id $ITEM_ID --field-id $FIELD_ID --single-select-option-id $STATUS_ID
|
|
465
|
-
\`\`\`
|
|
514
|
+
IMPORTANT: Only use root-level files for project context. Do not read subdirectory README files.
|
|
466
515
|
|
|
467
|
-
|
|
516
|
+
## Pick an Issue
|
|
517
|
+
Choose the issue that best aligns with the PRD priorities.
|
|
518
|
+
If unsure, pick the lowest numbered open issue.
|
|
468
519
|
|
|
520
|
+
## Workflow
|
|
521
|
+
1. Read the issue: gh issue view <number>
|
|
522
|
+
2. **Mark issue as "In Progress" on project board** (see commands below)
|
|
523
|
+
3. Implement the code following existing patterns
|
|
524
|
+
4. Test your changes work correctly
|
|
525
|
+
5. Close the issue: gh issue close <number> --comment "Completed: <brief summary>"
|
|
526
|
+
6. **Mark issue as "Done" on project board**
|
|
527
|
+
7. Commit: git add -A && git commit -m "type(scope): description (closes #<number>)"
|
|
528
|
+
${boardInstructions}
|
|
469
529
|
## Rules
|
|
470
530
|
- ONE issue per iteration
|
|
471
531
|
- Follow existing code patterns
|
|
@@ -477,8 +537,8 @@ If any step fails (no project linked, field not found), skip and continue.
|
|
|
477
537
|
Output: RALPH_COMPLETE
|
|
478
538
|
If no issues remain: RALPH_ALL_DONE`;
|
|
479
539
|
}
|
|
480
|
-
async function runTaskIteration(iteration, useGitHub = false) {
|
|
481
|
-
const taskPrompt = useGitHub ? getGitHubTaskPrompt() : getTodoTaskPrompt();
|
|
540
|
+
async function runTaskIteration(iteration, useGitHub = false, boardContext) {
|
|
541
|
+
const taskPrompt = useGitHub ? getGitHubTaskPrompt(boardContext || null) : getTodoTaskPrompt();
|
|
482
542
|
let lastResult = {
|
|
483
543
|
success: false,
|
|
484
544
|
output: "",
|
|
@@ -704,6 +764,20 @@ async function cmdLoop() {
|
|
|
704
764
|
if (isGitHub) {
|
|
705
765
|
await ensureProjectLinked();
|
|
706
766
|
}
|
|
767
|
+
let boardContext = null;
|
|
768
|
+
if (isGitHub) {
|
|
769
|
+
console.log(import_picocolors6.default.dim("Fetching project board context..."));
|
|
770
|
+
boardContext = getProjectBoardContext();
|
|
771
|
+
if (boardContext) {
|
|
772
|
+
console.log(import_picocolors6.default.green(`\u2713 Board context loaded (Project #${boardContext.projectNumber})`));
|
|
773
|
+
if (!boardContext.inProgressOptionId || !boardContext.doneOptionId) {
|
|
774
|
+
console.log(import_picocolors6.default.yellow(" \u26A0 Missing status options - status updates may not work"));
|
|
775
|
+
}
|
|
776
|
+
} else {
|
|
777
|
+
console.log(import_picocolors6.default.yellow("\u26A0 Could not fetch project board context."));
|
|
778
|
+
console.log(import_picocolors6.default.dim(" Status updates will not work. Continuing anyway..."));
|
|
779
|
+
}
|
|
780
|
+
}
|
|
707
781
|
let maxIterations;
|
|
708
782
|
let hasRemainingTasks;
|
|
709
783
|
let trackerLabel;
|
|
@@ -749,7 +823,7 @@ async function cmdLoop() {
|
|
|
749
823
|
console.log(import_picocolors6.default.yellow(`\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 Task ${i} of ${maxIterations} \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`));
|
|
750
824
|
cleanupStaleProcesses();
|
|
751
825
|
const startTime = Date.now();
|
|
752
|
-
const result = await runTaskIteration(i, isGitHub);
|
|
826
|
+
const result = await runTaskIteration(i, isGitHub, boardContext);
|
|
753
827
|
const duration = Math.round((Date.now() - startTime) / 1e3);
|
|
754
828
|
console.log();
|
|
755
829
|
console.log(`Completed in ${duration}s (exit: ${result.success ? 0 : 1})`);
|
|
@@ -865,6 +939,22 @@ function countOpenGitHubIssues() {
|
|
|
865
939
|
return 0;
|
|
866
940
|
}
|
|
867
941
|
}
|
|
942
|
+
function parsePRDFrontmatter(content) {
|
|
943
|
+
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
944
|
+
if (!frontmatterMatch) return null;
|
|
945
|
+
const frontmatter = {};
|
|
946
|
+
const lines = frontmatterMatch[1].split("\n");
|
|
947
|
+
for (const line of lines) {
|
|
948
|
+
const match = line.match(/^(\w+):\s*"?([^"]+)"?$/);
|
|
949
|
+
if (match) {
|
|
950
|
+
const [, key, value] = match;
|
|
951
|
+
if (key === "project" || key === "branch" || key === "priority" || key === "source_plan") {
|
|
952
|
+
frontmatter[key] = value.trim();
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
return Object.keys(frontmatter).length > 0 ? frontmatter : null;
|
|
957
|
+
}
|
|
868
958
|
function findMarkdownFiles(dir, basePath = "") {
|
|
869
959
|
const results = [];
|
|
870
960
|
try {
|
|
@@ -1285,6 +1375,7 @@ async function generateFromPlan(planFile) {
|
|
|
1285
1375
|
const claudeTemplate = (0, import_node_fs3.readFileSync)(`${LOCAL_TEMPLATES_DIR}/CLAUDE-template.md`, "utf-8");
|
|
1286
1376
|
console.log();
|
|
1287
1377
|
console.log(import_picocolors7.default.yellow(`[1/4] Generating ${prdPath}...`));
|
|
1378
|
+
const planFilename = planFile.split("/").pop() || planFile;
|
|
1288
1379
|
const prdPrompt = `You are filling in a PRD template based on a project plan.
|
|
1289
1380
|
|
|
1290
1381
|
Here is the plan:
|
|
@@ -1299,6 +1390,18 @@ ${prdTemplate}
|
|
|
1299
1390
|
|
|
1300
1391
|
Fill in the template with specific details from the plan.
|
|
1301
1392
|
Replace all placeholder text in [brackets] with real content.
|
|
1393
|
+
|
|
1394
|
+
IMPORTANT for YAML frontmatter at the top:
|
|
1395
|
+
- project: Use a short, descriptive project name (e.g., "User Authentication System")
|
|
1396
|
+
- branch: Suggest a git branch name (e.g., "feat/user-auth")
|
|
1397
|
+
- priority: Set to "high", "medium", or "low" based on the plan
|
|
1398
|
+
- source_plan: Set to "${planFilename}"
|
|
1399
|
+
|
|
1400
|
+
For User Stories section:
|
|
1401
|
+
- Each "### Story:" becomes a GitHub Issue
|
|
1402
|
+
- Use P0 for critical, P1 for important, P2 for nice-to-have
|
|
1403
|
+
- Include specific acceptance criteria as checkboxes
|
|
1404
|
+
|
|
1302
1405
|
Write the completed PRD directly to ${prdPath}.
|
|
1303
1406
|
Do NOT ask questions \u2014 infer everything from the plan.`;
|
|
1304
1407
|
await runClaude({ prompt: prdPrompt });
|
|
@@ -1435,8 +1538,14 @@ Create 5-15 issues.${projectName ? ` Add each to project "${projectName}".` : ""
|
|
|
1435
1538
|
} catch {
|
|
1436
1539
|
}
|
|
1437
1540
|
console.log(import_picocolors7.default.dim(" Creating epic from PRD..."));
|
|
1438
|
-
const
|
|
1439
|
-
|
|
1541
|
+
const prdFrontmatter = parsePRDFrontmatter(prdGenerated);
|
|
1542
|
+
let epicTitle;
|
|
1543
|
+
if (prdFrontmatter?.project) {
|
|
1544
|
+
epicTitle = `Epic: ${prdFrontmatter.project}`;
|
|
1545
|
+
} else {
|
|
1546
|
+
const planTitleMatch = planContent.match(/^#\s+(.+)$/m);
|
|
1547
|
+
epicTitle = planTitleMatch ? `Epic: ${planTitleMatch[1]}` : "Epic: Project Implementation";
|
|
1548
|
+
}
|
|
1440
1549
|
const epicBodyInitial = `${prdGenerated}
|
|
1441
1550
|
|
|
1442
1551
|
---
|
|
@@ -1549,14 +1658,23 @@ _Creating issues..._
|
|
|
1549
1658
|
let failed = 0;
|
|
1550
1659
|
const failedIssues = [];
|
|
1551
1660
|
const createdIssueNumbers = [];
|
|
1661
|
+
const supportsInPlace = process.stdout.isTTY && !process.env.SSH_TTY && !process.env.CI;
|
|
1552
1662
|
const loggedMilestones = /* @__PURE__ */ new Set();
|
|
1553
1663
|
for (let i = 0; i < issues.length; i++) {
|
|
1554
1664
|
const issue = issues[i];
|
|
1555
1665
|
const progress = Math.round((i + 1) / total * 100);
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1666
|
+
if (supportsInPlace) {
|
|
1667
|
+
const barWidth = 20;
|
|
1668
|
+
const filled = Math.round(progress / 100 * barWidth);
|
|
1669
|
+
const empty = barWidth - filled;
|
|
1670
|
+
const bar = import_picocolors7.default.green("\u2588".repeat(filled)) + import_picocolors7.default.dim("\u2591".repeat(empty));
|
|
1671
|
+
process.stdout.write(`\r ${bar} ${progress}% (${i + 1}/${total}) Creating: ${issue.title.slice(0, 40)}...`);
|
|
1672
|
+
} else {
|
|
1673
|
+
const milestone = Math.floor(progress / 25) * 25;
|
|
1674
|
+
if (milestone > 0 && !loggedMilestones.has(milestone) && milestone < 100) {
|
|
1675
|
+
console.log(import_picocolors7.default.dim(` ${milestone}% complete (${i + 1}/${total})...`));
|
|
1676
|
+
loggedMilestones.add(milestone);
|
|
1677
|
+
}
|
|
1560
1678
|
}
|
|
1561
1679
|
try {
|
|
1562
1680
|
const validLabels = issue.labels.filter((label) => availableLabels.size === 0 || availableLabels.has(label));
|
|
@@ -1593,20 +1711,20 @@ ${issue.body}`;
|
|
|
1593
1711
|
if (projectNumber && issueUrl) {
|
|
1594
1712
|
try {
|
|
1595
1713
|
const addOutput = (0, import_node_child_process6.execSync)(
|
|
1596
|
-
`gh project item-add ${projectNumber} --owner ${owner} --url "${issueUrl}"`,
|
|
1714
|
+
`gh project item-add ${projectNumber} --owner ${owner} --url "${issueUrl}" --format json`,
|
|
1597
1715
|
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
1598
1716
|
);
|
|
1599
1717
|
if (projectId && statusFieldId && todoOptionId) {
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1718
|
+
try {
|
|
1719
|
+
const addResult = JSON.parse(addOutput);
|
|
1720
|
+
const itemId = addResult.id;
|
|
1721
|
+
if (itemId) {
|
|
1604
1722
|
(0, import_node_child_process6.execSync)(
|
|
1605
1723
|
`gh project item-edit --project-id ${projectId} --id ${itemId} --field-id ${statusFieldId} --single-select-option-id ${todoOptionId}`,
|
|
1606
1724
|
{ stdio: ["pipe", "pipe", "pipe"] }
|
|
1607
1725
|
);
|
|
1608
|
-
} catch {
|
|
1609
1726
|
}
|
|
1727
|
+
} catch {
|
|
1610
1728
|
}
|
|
1611
1729
|
}
|
|
1612
1730
|
} catch {
|
|
@@ -1619,6 +1737,9 @@ ${issue.body}`;
|
|
|
1619
1737
|
failedIssues.push({ title: issue.title, error: errorMsg });
|
|
1620
1738
|
}
|
|
1621
1739
|
}
|
|
1740
|
+
if (supportsInPlace) {
|
|
1741
|
+
process.stdout.write("\r" + " ".repeat(80) + "\r");
|
|
1742
|
+
}
|
|
1622
1743
|
console.log(import_picocolors7.default.green(` \u2713 Created ${created} GitHub Issues`));
|
|
1623
1744
|
if (failed > 0) {
|
|
1624
1745
|
console.log(import_picocolors7.default.yellow(` \u26A0 ${failed} issues failed to create:`));
|
package/package.json
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
---
|
|
2
|
+
project: "[Project Name - short, descriptive title]"
|
|
3
|
+
branch: "[feat/feature-name or fix/bug-name]"
|
|
4
|
+
priority: "[high|medium|low]"
|
|
5
|
+
source_plan: "[filename of the input plan]"
|
|
6
|
+
---
|
|
7
|
+
|
|
1
8
|
# Product Requirements Document
|
|
2
9
|
|
|
3
10
|
## Overview
|
|
@@ -15,9 +22,25 @@
|
|
|
15
22
|
|
|
16
23
|
## User Stories
|
|
17
24
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
25
|
+
<!-- Each story becomes a GitHub Issue. Use this exact format: -->
|
|
26
|
+
|
|
27
|
+
### Story: [Short descriptive title]
|
|
28
|
+
- **Priority**: P0
|
|
29
|
+
- **As a**: [user type]
|
|
30
|
+
- **I want**: [action]
|
|
31
|
+
- **So that**: [benefit]
|
|
32
|
+
- **Acceptance Criteria**:
|
|
33
|
+
- [ ] Criterion 1
|
|
34
|
+
- [ ] Criterion 2
|
|
35
|
+
|
|
36
|
+
### Story: [Another story title]
|
|
37
|
+
- **Priority**: P1
|
|
38
|
+
- **As a**: [user type]
|
|
39
|
+
- **I want**: [action]
|
|
40
|
+
- **So that**: [benefit]
|
|
41
|
+
- **Acceptance Criteria**:
|
|
42
|
+
- [ ] Criterion 1
|
|
43
|
+
- [ ] Criterion 2
|
|
21
44
|
|
|
22
45
|
## Functional Requirements
|
|
23
46
|
|