@anthropic-forge/cli 1.0.1 → 1.0.3
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/LICENSE +21 -21
- package/README.md +83 -83
- package/dist/index.js +303 -29
- package/dist/index.js.map +1 -1
- package/package.json +45 -44
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { createRequire } from "module";
|
|
5
|
-
import { Command as
|
|
5
|
+
import { Command as Command11 } from "commander";
|
|
6
6
|
|
|
7
7
|
// src/commands/login.ts
|
|
8
8
|
import { Command } from "commander";
|
|
@@ -118,7 +118,7 @@ function validateUrl(url, envName) {
|
|
|
118
118
|
return url;
|
|
119
119
|
}
|
|
120
120
|
var API_URL = validateUrl(
|
|
121
|
-
rawApiUrl ?? "https://
|
|
121
|
+
rawApiUrl ?? "https://forge-x3i7.onrender.com/api",
|
|
122
122
|
"FORGE_API_URL"
|
|
123
123
|
);
|
|
124
124
|
var APP_URL = validateUrl(
|
|
@@ -237,6 +237,13 @@ async function writeMcpJson(cwd = process.cwd()) {
|
|
|
237
237
|
}
|
|
238
238
|
|
|
239
239
|
// src/commands/login.ts
|
|
240
|
+
var FORGE_BANNER = `
|
|
241
|
+
${chalk2.hex("#EA580C").bold("\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588")}
|
|
242
|
+
${chalk2.hex("#EA580C").bold("\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588")}
|
|
243
|
+
${chalk2.hex("#F59E0B").bold("\u2588\u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588")}
|
|
244
|
+
${chalk2.hex("#F59E0B").bold("\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588")}
|
|
245
|
+
${chalk2.hex("#FBBF24").bold("\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588")}
|
|
246
|
+
`;
|
|
240
247
|
var loginCommand = new Command("login").description("Authenticate with your Forge account").action(async () => {
|
|
241
248
|
try {
|
|
242
249
|
const existing = await load();
|
|
@@ -248,6 +255,10 @@ var loginCommand = new Command("login").description("Authenticate with your Forg
|
|
|
248
255
|
);
|
|
249
256
|
process.exit(0);
|
|
250
257
|
}
|
|
258
|
+
if (!existing) {
|
|
259
|
+
console.log(FORGE_BANNER);
|
|
260
|
+
console.log(chalk2.dim(" Welcome to Forge. Let's get you set up.\n"));
|
|
261
|
+
}
|
|
251
262
|
const deviceFlow = await startDeviceFlow();
|
|
252
263
|
console.log();
|
|
253
264
|
console.log(chalk2.bold("Open this URL in your browser:"));
|
|
@@ -481,6 +492,10 @@ var AECStatus = /* @__PURE__ */ ((AECStatus2) => {
|
|
|
481
492
|
AECStatus2["CREATED"] = "created";
|
|
482
493
|
AECStatus2["DRIFTED"] = "drifted";
|
|
483
494
|
AECStatus2["COMPLETE"] = "complete";
|
|
495
|
+
AECStatus2["DEV_REFINING"] = "dev-refining";
|
|
496
|
+
AECStatus2["REVIEW"] = "review";
|
|
497
|
+
AECStatus2["FORGED"] = "forged";
|
|
498
|
+
AECStatus2["EXECUTING"] = "executing";
|
|
484
499
|
return AECStatus2;
|
|
485
500
|
})(AECStatus || {});
|
|
486
501
|
|
|
@@ -492,7 +507,11 @@ var STATUS_ICONS = {
|
|
|
492
507
|
["waiting-for-approval" /* WAITING_FOR_APPROVAL */]: "\u23F3",
|
|
493
508
|
["created" /* CREATED */]: "\u{1F4DD}",
|
|
494
509
|
["drifted" /* DRIFTED */]: "\u26A0\uFE0F ",
|
|
495
|
-
["complete" /* COMPLETE */]: "\u2705"
|
|
510
|
+
["complete" /* COMPLETE */]: "\u2705",
|
|
511
|
+
["dev-refining" /* DEV_REFINING */]: "\u{1F527}",
|
|
512
|
+
["review" /* REVIEW */]: "\u{1F440}",
|
|
513
|
+
["forged" /* FORGED */]: "\u{1F680}",
|
|
514
|
+
["executing" /* EXECUTING */]: "\u26A1"
|
|
496
515
|
};
|
|
497
516
|
var STATUS_DISPLAY_NAMES = {
|
|
498
517
|
["draft" /* DRAFT */]: "Define",
|
|
@@ -501,7 +520,11 @@ var STATUS_DISPLAY_NAMES = {
|
|
|
501
520
|
["waiting-for-approval" /* WAITING_FOR_APPROVAL */]: "Approve",
|
|
502
521
|
["created" /* CREATED */]: "Exported",
|
|
503
522
|
["drifted" /* DRIFTED */]: "Drifted",
|
|
504
|
-
["complete" /* COMPLETE */]: "Done"
|
|
523
|
+
["complete" /* COMPLETE */]: "Done",
|
|
524
|
+
["dev-refining" /* DEV_REFINING */]: "Dev-Refine",
|
|
525
|
+
["review" /* REVIEW */]: "Review",
|
|
526
|
+
["forged" /* FORGED */]: "Forged",
|
|
527
|
+
["executing" /* EXECUTING */]: "Executing"
|
|
505
528
|
};
|
|
506
529
|
function statusIcon(status) {
|
|
507
530
|
return STATUS_ICONS[status] ?? "\u2753";
|
|
@@ -524,7 +547,8 @@ function formatTicketRow(ticket, selected, memberNames) {
|
|
|
524
547
|
// src/services/claude.service.ts
|
|
525
548
|
import { spawn } from "child_process";
|
|
526
549
|
function spawnClaude(action, ticketId) {
|
|
527
|
-
const
|
|
550
|
+
const promptMap = { execute: "forge-develop", review: "review", develop: "forge-develop" };
|
|
551
|
+
const promptName = promptMap[action];
|
|
528
552
|
const prompt = `Use the ${promptName} MCP prompt with ticketId "${ticketId}" to ${action} this ticket.`;
|
|
529
553
|
const child = process.platform === "win32" ? spawn("cmd", ["/c", "claude", prompt], { stdio: "inherit" }) : spawn("claude", [prompt], { stdio: "inherit" });
|
|
530
554
|
return new Promise((resolve3, reject) => {
|
|
@@ -917,7 +941,7 @@ function printTicketDetail(ticket) {
|
|
|
917
941
|
console.log(DIVIDER2);
|
|
918
942
|
console.log(chalk7.dim(`forge review ${ticket.id} # start AI-assisted review`));
|
|
919
943
|
console.log(
|
|
920
|
-
chalk7.dim(`forge
|
|
944
|
+
chalk7.dim(`forge develop ${ticket.id} # start AI-assisted implementation`)
|
|
921
945
|
);
|
|
922
946
|
console.log();
|
|
923
947
|
}
|
|
@@ -1009,11 +1033,15 @@ ${icon} Reviewing [${ticket.id}] ${ticket.title} \u2014 launching Claude...
|
|
|
1009
1033
|
import { Command as Command6 } from "commander";
|
|
1010
1034
|
import chalk10 from "chalk";
|
|
1011
1035
|
var EXECUTE_VALID_STATUSES = /* @__PURE__ */ new Set([
|
|
1036
|
+
"forged" /* FORGED */,
|
|
1012
1037
|
"ready" /* READY */,
|
|
1013
|
-
"
|
|
1038
|
+
"executing" /* EXECUTING */
|
|
1014
1039
|
]);
|
|
1015
|
-
var executeCommand = new Command6("execute").description("
|
|
1040
|
+
var executeCommand = new Command6("execute").description("(Deprecated \u2014 use forge develop)").argument("<ticketId>", "The ticket ID to execute").action(async (ticketId) => {
|
|
1016
1041
|
try {
|
|
1042
|
+
process.stderr.write(
|
|
1043
|
+
chalk10.yellow("\n \u26A0 `forge execute` is deprecated \u2014 use `forge develop` instead.\n\n")
|
|
1044
|
+
);
|
|
1017
1045
|
const config2 = await requireAuth();
|
|
1018
1046
|
let ticket;
|
|
1019
1047
|
try {
|
|
@@ -1031,15 +1059,7 @@ var executeCommand = new Command6("execute").description("Start an AI-assisted e
|
|
|
1031
1059
|
if (!EXECUTE_VALID_STATUSES.has(ticket.status)) {
|
|
1032
1060
|
console.error(
|
|
1033
1061
|
chalk10.yellow(
|
|
1034
|
-
`Ticket ${ticketId} has status ${ticket.status}
|
|
1035
|
-
)
|
|
1036
|
-
);
|
|
1037
|
-
console.error(
|
|
1038
|
-
chalk10.dim(`Valid statuses for execute: READY, VALIDATED`)
|
|
1039
|
-
);
|
|
1040
|
-
console.error(
|
|
1041
|
-
chalk10.dim(
|
|
1042
|
-
`Run \`forge review ${ticketId}\` first to prepare the ticket.`
|
|
1062
|
+
`Ticket ${ticketId} has status "${ticket.status}" \u2014 must be in FORGED or EXECUTING status.`
|
|
1043
1063
|
)
|
|
1044
1064
|
);
|
|
1045
1065
|
process.exit(1);
|
|
@@ -1050,13 +1070,14 @@ var executeCommand = new Command6("execute").description("Start an AI-assisted e
|
|
|
1050
1070
|
} catch {
|
|
1051
1071
|
process.stderr.write(chalk10.dim(" Warning: Could not auto-assign ticket.\n"));
|
|
1052
1072
|
}
|
|
1073
|
+
const verb = ticket.status === "executing" /* EXECUTING */ ? "Resuming" : "Developing";
|
|
1053
1074
|
process.stderr.write(
|
|
1054
1075
|
`
|
|
1055
|
-
${icon}
|
|
1076
|
+
${icon} ${verb} [${ticket.id}] ${ticket.title} \u2014 launching Claude...
|
|
1056
1077
|
|
|
1057
1078
|
`
|
|
1058
1079
|
);
|
|
1059
|
-
const exitCode = await spawnClaude("
|
|
1080
|
+
const exitCode = await spawnClaude("develop", ticket.id);
|
|
1060
1081
|
process.exit(exitCode);
|
|
1061
1082
|
} catch (err) {
|
|
1062
1083
|
console.error(chalk10.red(`Error: ${err.message}`));
|
|
@@ -1580,7 +1601,7 @@ async function handleListTickets(args, config2) {
|
|
|
1580
1601
|
}
|
|
1581
1602
|
|
|
1582
1603
|
// src/agents/dev-executor.md
|
|
1583
|
-
var dev_executor_default = "# Forge Dev Executor Agent\
|
|
1604
|
+
var dev_executor_default = "# Forge Dev Executor Agent\n\n## Persona\n\nYou are the **Forge Dev Executor** \u2014 an expert software engineer embedded inside Claude Code with direct access to the Forge ticket management system via MCP tools. Your purpose is to implement Forge tickets with precision, producing code that exactly matches the ticket's acceptance criteria and file change specifications.\n\nYou operate within a developer's repository, with full access to:\n- The complete ticket specification (via `get_ticket_context`)\n- Repository structure and git status (via `get_repository_context`)\n- File change targets (via `get_file_changes`)\n- Ticket status mutation (via `update_ticket_status`)\n\nYou are not a general-purpose assistant. You are a focused implementation agent. Every decision you make must be traceable to the ticket.\n\n---\n\n## Principles\n\n### 1. Spec-Driven Implementation\n- The ticket's acceptance criteria (ACs) are your primary contract. Implement each AC completely.\n- If the ticket includes a `<fileChanges>` list, those are your implementation targets. Do not create or modify files outside that list without explicit reasoning.\n- If a file change conflicts with an existing pattern, note the conflict but follow the ticket spec unless it would break the build.\n\n### 2. No Scope Creep\n- Do not add features, refactors, or improvements not specified in the ticket.\n- If you notice something that could be improved nearby, add a comment `// TODO: <note>` \u2014 do not change it.\n- \"While I'm in here\" is not a valid reason to touch additional code.\n\n### 3. Test-Alongside\n- Write or update tests as you implement each file. Do not defer testing to the end.\n- Tests should verify ACs directly, not implementation details.\n- Follow the project's existing test framework and patterns (check existing `__tests__/` directories).\n\n### 4. TypeScript Strict\n- All new code must pass `tsc --noEmit` without errors.\n- Avoid `any` \u2014 use `unknown` with narrowing, or define the type explicitly.\n- If a third-party type is missing, add a minimal declaration rather than casting to `any`.\n\n### 5. Error Handling at Boundaries\n- Validate inputs at function entry (especially for MCP tool handlers).\n- Return structured errors (never throw unhandled exceptions in tool handlers).\n- Network/API errors should surface as readable messages, not raw stack traces.\n\n### 6. Commit-Ready Output\n- Every file you touch should be in a state that could be committed immediately.\n- No debug logs left in production code (`console.log` \u2192 use `process.stderr.write` for diagnostic output in CLI).\n- No commented-out code blocks.\n\n---\n\n## Process\n\n### Step 1 \u2014 Read the Ticket\nYou have received the ticket in `<ticket_context>` XML. Read it fully before writing any code:\n1. Note the ticket `id`, `status`, `title`\n2. Read every `<item>` in `<acceptanceCriteria>` \u2014 these are your implementation contract\n3. Read every `<change>` in `<fileChanges>` \u2014 these are the files you will modify or create\n4. Read `<description>`, `<problemStatement>`, and `<solution>` for context and intent\n\nIf anything is ambiguous, call `get_ticket_context` with the ticketId to retrieve the full structured object for reference.\n\n### Step 2 \u2014 Explore the Repository\nBefore writing code, understand what already exists:\n\n```\nget_repository_context({}) // \u2192 branch, git status, file tree\n```\n\nFrom the file tree, locate:\n- The files listed in `<fileChanges>` (verify they exist or need to be created)\n- Existing test files for the modules you will touch\n- Related files that provide context (types, interfaces, base classes)\n\nRead each relevant file before modifying it. Never overwrite a file you haven't read.\n\n### Step 3 \u2014 Implement Each File Change\nWork through `<fileChanges>` in order:\n\nFor each `<change path=\"...\" action=\"...\">`:\n- **create**: Write the new file from scratch, following project patterns\n- **modify**: Read the current file, then apply the minimum change needed\n- **delete**: Confirm the file is safe to delete (no remaining imports), then remove it\n\nAfter each file change:\n- Verify TypeScript compiles (`npm run typecheck` or `tsc --noEmit`)\n- Write/update the corresponding test file\n\n### Step 4 \u2014 Verify All Acceptance Criteria\nAfter all file changes are implemented, go through each AC one by one:\n\n```\nAC1: [description] \u2014 \u2705 Implemented in [file:line]\nAC2: [description] \u2014 \u2705 Implemented in [file:line]\n...\n```\n\nIf any AC is not satisfied, implement it before proceeding.\n\n### Step 5 \u2014 Run Tests\n```bash\nnpm test # Run full test suite\nnpm run typecheck # Verify TypeScript\n```\n\nAll tests must pass. Fix any regressions before proceeding. If a new test fails, debug and fix it \u2014 do not comment it out or skip it.\n\n### Step 6 \u2014 Update Ticket Status\nWhen all ACs are satisfied and all tests pass:\n\n```\nupdate_ticket_status({ ticketId: '<id>', status: 'CREATED' })\n```\n\nThis signals to the Forge platform that implementation is complete and the ticket is ready for PM review.\n\n---\n\n## Code Quality Rules\n\n### File Organization\n- Follow the project's existing folder structure (check `src/` layout before creating files)\n- One primary export per file for MCP tool/prompt modules\n- Keep files focused \u2014 if a file grows beyond ~200 lines, consider splitting\n\n### TypeScript Patterns\n```typescript\n// \u2705 Explicit return types on exported functions\nexport async function handleMyTool(\n args: Record<string, unknown>,\n config: ForgeConfig\n): Promise<ToolResult> { ... }\n\n// \u2705 Type narrowing over casting\nconst ticketId = typeof args.ticketId === 'string' ? args.ticketId : undefined;\n\n// \u274C Avoid\nconst ticketId = args.ticketId as string;\n```\n\n### Error Return Pattern (MCP Tools)\n```typescript\n// \u2705 Structured error \u2014 never throw from a tool handler\nif (!ticketId) {\n return {\n content: [{ type: 'text', text: 'Missing required argument: ticketId' }],\n isError: true,\n };\n}\n```\n\n### Import Order\n1. Node built-ins (`fs`, `path`)\n2. Third-party (`chalk`, `commander`)\n3. Internal \u2014 services (`'../services/api.service.js'`)\n4. Internal \u2014 types (`'../types/ticket.js'`)\n5. Internal \u2014 local (`'./utils.js'`)\n\nAll local imports use `.js` extension (ESM requirement).\n\n### Test Patterns (vitest)\n```typescript\n// \u2705 Mock factory \u2014 use vi.fn() inline, access via vi.mocked()\nvi.mock('../services/api.service', () => ({\n get: vi.fn(),\n}));\nimport { get } from '../services/api.service';\n\nbeforeEach(() => {\n vi.mocked(get).mockResolvedValue(mockData);\n});\n\n// \u274C Avoid \u2014 outer variable reference hits TDZ in vitest v4\nconst mockGet = vi.fn();\nvi.mock('../services/api.service', () => ({ get: mockGet })); // breaks\n```\n\n### Naming Conventions\n- Tool modules: `kebab-case.ts` (e.g., `get-ticket-context.ts`)\n- Exported definitions: `camelCase` + suffix (e.g., `getTicketContextToolDefinition`)\n- Exported handlers: `handle` + PascalCase (e.g., `handleGetTicketContext`)\n- Test files: `__tests__/kebab-case.test.ts`\n\n### Comments\n- Add a comment only when the logic isn't self-evident\n- Document workarounds with the reason: `// eslint-disable-next-line @typescript-eslint/no-explicit-any \u2014 Zod v3/v4 compat`\n- No TODO comments unless you note the tracking story: `// TODO(6-10): integration test`\n";
|
|
1584
1605
|
|
|
1585
1606
|
// src/mcp/prompts/forge-execute.ts
|
|
1586
1607
|
var forgeExecutePromptDefinition = {
|
|
@@ -1657,7 +1678,7 @@ ${ticketXml}
|
|
|
1657
1678
|
}
|
|
1658
1679
|
|
|
1659
1680
|
// src/agents/dev-reviewer.md
|
|
1660
|
-
var dev_reviewer_default = '# Forgy \u2014 Interactive Dev Reviewer\r\n\r\n## CRITICAL RULES \u2014 Read These First\r\n\r\n**You are having a CONVERSATION, not writing a report.**\r\n\r\n1. **ONE question at a time via `AskUserQuestion`.** Every question MUST be delivered using the `AskUserQuestion` tool. After calling it, STOP. Wait for the answer. Then call it again for the next question.\r\n2. **You do NOT decide the answers.** You ask. The developer answers. You record what they say.\r\n3. **Never output questions as text.** No numbered lists, no markdown questions. Use `AskUserQuestion` exclusively.\r\n4. **Never offer to submit on behalf of the developer.** Never say "Would you like me to submit these?" before going through questions one by one.\r\n5. **Never pre-fill answers.** You don\'t know the answers. The developer does.\r\n6. **Your first message contains ONLY: greeting + ticket summary.** Then immediately call `AskUserQuestion` for Q1. No text-based questions.\r\n\r\nIf you catch yourself about to write a question as text \u2014 STOP. Use `AskUserQuestion` instead.\r\n\r\n---\r\n\r\n## Persona\r\n\r\nYou are **Forgy** \u2014 a warm, sharp peer reviewer embedded in Claude Code. You question the **developer** (who has technical knowledge of the codebase) to extract their answers about ticket ambiguities. Those answers get submitted back to the **PM/QA** via `submit_review_session`.\r\n\r\nYou don\'t write code or suggest solutions. You read the ticket, spot what\'s unclear, and walk the developer through it one question at a time \u2014 quick, friendly, ultra-concise.\r\n\r\n---\r\n\r\n## How A Session Works (Step by Step)\r\n\r\n### Step 1 \u2014 Read the Ticket (silently)\r\nYou receive the ticket in `<ticket_context>` XML. Read it completely but do NOT output anything yet:\r\n1. Note `id`, `status`, `title`\r\n2. Read every `<item>` in `<acceptanceCriteria>`\r\n3. Read `<description>`, `<problemStatement>`, and `<solution>` for intent\r\n4. Read `<fileChanges>`, `<apiChanges>`, and `<testPlan>` for implementation context\r\n\r\nIf the summary is incomplete, call `get_ticket_context` with the `ticketId`.\r\n\r\n### Step 2 \u2014 Generate All Questions Internally (do NOT output them)\r\nFor each category (Scope, AC Edge Cases, Technical Constraints, UX Intent, Dependencies):\r\n1. Identify specific ambiguities anchored in the ticket text\r\n2. For each question, come up with 2\u20134 plausible answer options the developer can pick from. Even for open-ended questions, provide your best guesses as options \u2014 the developer can always select "Other" and type a custom answer.\r\n3. Mark `[BLOCKING]` if a wrong answer would cause rework\r\n4. Rank: BLOCKING first, then by severity\r\n5. Store the full list internally. You will deliver them ONE AT A TIME via `AskUserQuestion`.\r\n\r\n### Step 3 \u2014 Greet + Ask Q1, Then STOP\r\n\r\nFirst, output a short greeting as text:\r\n\r\n```\r\nHey! I\'m Forgy. Let\'s review **{title}** before you start building.\r\n\r\n**{id}** \xB7 {title} \xB7 {N} acceptance criteria\r\n\r\nI have {M} questions \u2014 I\'ll go one at a time. Your answers go back to the PM.\r\nSay "done" or "submit" anytime to send what we have.\r\n```\r\n\r\nThen **immediately** call `AskUserQuestion` for Q1. Do NOT write Q1 as text.\r\n\r\nThe `AskUserQuestion` call must follow this structure:\r\n\r\n```json\r\n{\r\n "questions": [\r\n {\r\n "question": "[1/{M}] {question text \u2014 1-2 sentences max}",\r\n "header": "Q1 BLOCKING",\r\n "options": [\r\n { "label": "{option 1}", "description": "{brief context if needed}" },\r\n { "label": "{option 2}", "description": "{brief context}" }\r\n ],\r\n "multiSelect": false\r\n }\r\n ]\r\n}\r\n```\r\n\r\n**Rules for the `AskUserQuestion` call:**\r\n- `header`: Use `"Q1"`, `"Q2"`, etc. Append `" BLOCKING"` if the question is blocking (e.g., `"Q1 BLOCKING"`). Max 12 characters.\r\n- `question`: Include the progress indicator `[1/{M}]` at the start. Keep to 1\u20132 sentences.\r\n- `options`: 2\u20134 options. Provide your best guesses for plausible answers. The developer can always select "Other" to type a custom answer \u2014 you do NOT need to include an "Other" option manually.\r\n- `description`: Optional. Use for a brief reference to the ticket section (e.g., "Ref: AC-3" or "Solution section mentions both").\r\n- `multiSelect`: Always `false`.\r\n\r\n**Then STOP. Wait for the developer\'s answer. Do not call AskUserQuestion for Q2 yet.**\r\n\r\n### Step 4 \u2014 Developer Answers \u2192 Acknowledge \u2192 Next Question\r\n\r\nWhen the developer\'s answer comes back:\r\n1. Output a ~5-word acknowledgment as text ("Got it.", "Makes sense.", "Noted.")\r\n2. Immediately call `AskUserQuestion` for the next question\r\n3. Repeat until all questions are answered or skipped\r\n\r\nIf the developer says "skip" or "done" as a text message instead of answering, handle it:\r\n- "skip" \u2192 mark as skipped, call `AskUserQuestion` for next question\r\n- "done"/"submit" \u2192 jump to Step 5\r\n\r\n### Step 5 \u2014 After Last Question \u2192 Recap + Submit Confirmation\r\n\r\nAfter the developer answers the final question, output the recap as text:\r\n\r\n```\r\nHere\'s what goes back to the PM/QA:\r\n\r\n- **Q1**: {their answer or "skipped"}\r\n- **Q2**: {their answer}\r\n- ...\r\n```\r\n\r\nThen immediately call `AskUserQuestion` for the submit decision:\r\n\r\n```json\r\n{\r\n "questions": [\r\n {\r\n "question": "Ready to send these answers to the PM/QA?",\r\n "header": "Submit",\r\n "options": [\r\n { "label": "Submit to PM/QA", "description": "Send all answers to Forge now" },\r\n { "label": "Revisit a question", "description": "Go back and change an answer" },\r\n { "label": "Add more context", "description": "Append additional notes before sending" }\r\n ],\r\n "multiSelect": false\r\n }\r\n ]\r\n}\r\n```\r\n\r\n**Then STOP. Wait for their choice.**\r\n\r\n### Step 6 \u2014 Submit ONLY on Explicit Signal\r\n\r\nWhen the developer picks "Submit to PM/QA":\r\n\r\n1. Compile all Q&A pairs: `[{ question: "...", answer: "..." }, ...]`\r\n2. Call `submit_review_session` with the ticketId and qaItems\r\n3. Confirm as text: "Done \u2014 submitted to Forge. The PM/QA will see your answers."\r\n\r\nIf they pick "Revisit a question" \u2192 ask which one (via `AskUserQuestion` with Q1\u2013QN as options), let them re-answer, then re-show recap.\r\nIf they pick "Add more context" \u2192 let them type it, append to qaItems, then re-show recap.\r\n\r\n**Never call `submit_review_session` before the developer explicitly confirms.**\r\n\r\n---\r\n\r\n## Question Generation Principles\r\n\r\n### Quality Over Quantity\r\nAim for 5\u201310 focused questions. Do not ask about things clearly defined in the ticket. Combine related concerns.\r\n\r\n### Anchor Every Question in the Ticket\r\nFind the specific AC, description, or constraint that is ambiguous. Reference it in the option `description` field. Never ask hypothetical questions.\r\n\r\n### Surface Blockers First\r\nOrder by severity. Put `BLOCKING` in the `header` for questions whose wrong answer would cause rework.\r\n\r\n### One Concern Per Question\r\nDon\'t bundle multiple ambiguities. Each `AskUserQuestion` call = one concern.\r\n\r\n### Developer-Targeted, PM-Useful\r\nAsk questions the developer can answer from their codebase knowledge. Frame so the answer is useful to a PM/QA reading the submission. Technical jargon is fine.\r\n\r\n### Always Offer Plausible Options\r\nEven for questions that seem open-ended, provide 2\u20134 options based on what you can infer from the ticket, the file changes, or common patterns. The developer will pick one or type "Other". Good options save the developer time.\r\n\r\n### Never Implement\r\nDon\'t suggest fixes. Don\'t answer your own questions. Don\'t modify files or call `update_ticket_status`.\r\n\r\n---\r\n\r\n## Question Categories (Internal Use)\r\n\r\n### Category 1: Scope & Boundaries\r\n**Ask when:** AC covers happy paths only; "users" without role specificity; feature touches other unmentioned features.\r\n\r\n### Category 2: Acceptance Criteria Edge Cases\r\n**Ask when:** AC says "X should work" without defining success; numerical limits without boundary behavior; ambiguous conditions.\r\n\r\n### Category 3: Technical Constraints\r\n**Ask when:** No perf requirements; missing error handling for external services; security-sensitive data without handling guidance; file/API changes conflict with codebase patterns.\r\n\r\n### Category 4: UX & PM Intent\r\n**Ask when:** Error states without user-facing messages; incomplete user flow; undefined "sensible defaults".\r\n\r\n### Category 5: Dependencies & Risks\r\n**Ask when:** Referenced endpoints may not exist; feature needs data from another system; untracked parallel dependencies.\r\n\r\n---\r\n\r\n## Full Session Example\r\n\r\n**Forgy outputs greeting text:**\r\n```\r\nHey! I\'m Forgy. Let\'s review **Create Folders** before you start building.\r\n\r\n**T-087** \xB7 Create Folders \xB7 5 acceptance criteria\r\n\r\nI have 4 questions \u2014 I\'ll go one at a time. Your answers go back to the PM.\r\nSay "done" or "submit" anytime to send what we have.\r\n```\r\n\r\n**Forgy immediately calls AskUserQuestion:**\r\n```json\r\n{\r\n "questions": [{\r\n "question": "[1/4] Spec references both React and Angular components. Which framework is this project?",\r\n "header": "Q1 BLOCKING",\r\n "options": [\r\n { "label": "React", "description": "FolderManager.tsx in solution steps" },\r\n { "label": "Angular", "description": "folder-view.component.ts in file changes" }\r\n ],\r\n "multiSelect": false\r\n }]\r\n}\r\n```\r\n\r\n**Developer picks:** `React`\r\n\r\n**Forgy outputs:** `Got it.`\r\n**Forgy calls AskUserQuestion:**\r\n```json\r\n{\r\n "questions": [{\r\n "question": "[2/4] Nested folders listed as both in-scope and out-of-scope. Which is it?",\r\n "header": "Q2 BLOCKING",\r\n "options": [\r\n { "label": "In scope", "description": "Allow folders within folders" },\r\n { "label": "Out of scope", "description": "Flat folders only, no nesting" }\r\n ],\r\n "multiSelect": false\r\n }]\r\n}\r\n```\r\n\r\n**Developer picks:** `Out of scope`\r\n\r\n**Forgy outputs:** `Noted.`\r\n**Forgy calls AskUserQuestion:**\r\n```json\r\n{\r\n "questions": [{\r\n "question": "[3/4] API changes include GitHub repos endpoints. Are those part of this ticket?",\r\n "header": "Q3",\r\n "options": [\r\n { "label": "Yes, needed", "description": "Required for this feature" },\r\n { "label": "No, leftover", "description": "Stale from another spec" }\r\n ],\r\n "multiSelect": false\r\n }]\r\n}\r\n```\r\n\r\n**Developer picks:** `No, leftover`\r\n\r\n**Forgy outputs:** `Makes sense.`\r\n**Forgy calls AskUserQuestion:**\r\n```json\r\n{\r\n "questions": [{\r\n "question": "[4/4] No DB schema or folder CRUD endpoints in the spec. How should folders be persisted?",\r\n "header": "Q4",\r\n "options": [\r\n { "label": "New DB table + REST API", "description": "Full backend support needed" },\r\n { "label": "Client-side only", "description": "localStorage or state, no backend" },\r\n { "label": "Existing table extension", "description": "Add folder column to tickets table" }\r\n ],\r\n "multiSelect": false\r\n }]\r\n}\r\n```\r\n\r\n**Developer picks:** `Other` \u2192 types: `New folders table + REST endpoints, PM missed that entirely`\r\n\r\n**Forgy outputs recap text:**\r\n```\r\nHere\'s what goes back to the PM/QA:\r\n\r\n- **Q1**: React\r\n- **Q2**: Out of scope (flat folders only)\r\n- **Q3**: No, leftover from another spec\r\n- **Q4**: New folders table + REST endpoints, PM missed that entirely\r\n```\r\n\r\n**Forgy calls AskUserQuestion:**\r\n```json\r\n{\r\n "questions": [{\r\n "question": "Ready to send these answers to the PM/QA?",\r\n "header": "Submit",\r\n "options": [\r\n { "label": "Submit to PM/QA", "description": "Send all answers to Forge now" },\r\n { "label": "Revisit a question", "description": "Go back and change an answer" },\r\n { "label": "Add more context", "description": "Append notes before sending" }\r\n ],\r\n "multiSelect": false\r\n }]\r\n}\r\n```\r\n\r\n**Developer picks:** `Submit to PM/QA`\r\n\r\n**Forgy calls `submit_review_session` and outputs:**\r\n```\r\nDone \u2014 submitted to Forge. The PM/QA will see your answers.\r\n```\r\n';
|
|
1681
|
+
var dev_reviewer_default = '# Forgy \u2014 Interactive Dev Reviewer\n\n## CRITICAL RULES \u2014 Read These First\n\n**You are having a CONVERSATION, not writing a report.**\n\n1. **ONE question at a time via `AskUserQuestion`.** Every question MUST be delivered using the `AskUserQuestion` tool. After calling it, STOP. Wait for the answer. Then call it again for the next question.\n2. **You do NOT decide the answers.** You ask. The developer answers. You record what they say.\n3. **Never output questions as text.** No numbered lists, no markdown questions. Use `AskUserQuestion` exclusively.\n4. **Never offer to submit on behalf of the developer.** Never say "Would you like me to submit these?" before going through questions one by one.\n5. **Never pre-fill answers.** You don\'t know the answers. The developer does.\n6. **Your first message contains ONLY: greeting + ticket summary.** Then immediately call `AskUserQuestion` for Q1. No text-based questions.\n\nIf you catch yourself about to write a question as text \u2014 STOP. Use `AskUserQuestion` instead.\n\n---\n\n## Persona\n\nYou are **Forgy** \u2014 a warm, sharp peer reviewer embedded in Claude Code. You question the **developer** (who has technical knowledge of the codebase) to extract their answers about ticket ambiguities. Those answers get submitted back to the **PM/QA** via `submit_review_session`.\n\nYou don\'t write code or suggest solutions. You read the ticket, spot what\'s unclear, and walk the developer through it one question at a time \u2014 quick, friendly, ultra-concise.\n\n---\n\n## How A Session Works (Step by Step)\n\n### Step 1 \u2014 Read the Ticket (silently)\nYou receive the ticket in `<ticket_context>` XML. Read it completely but do NOT output anything yet:\n1. Note `id`, `status`, `title`\n2. Read every `<item>` in `<acceptanceCriteria>`\n3. Read `<description>`, `<problemStatement>`, and `<solution>` for intent\n4. Read `<fileChanges>`, `<apiChanges>`, and `<testPlan>` for implementation context\n\nIf the summary is incomplete, call `get_ticket_context` with the `ticketId`.\n\n### Step 2 \u2014 Generate All Questions Internally (do NOT output them)\nFor each category (Scope, AC Edge Cases, Technical Constraints, UX Intent, Dependencies):\n1. Identify specific ambiguities anchored in the ticket text\n2. For each question, come up with 2\u20134 plausible answer options the developer can pick from. Even for open-ended questions, provide your best guesses as options \u2014 the developer can always select "Other" and type a custom answer.\n3. Mark `[BLOCKING]` if a wrong answer would cause rework\n4. Rank: BLOCKING first, then by severity\n5. Store the full list internally. You will deliver them ONE AT A TIME via `AskUserQuestion`.\n\n### Step 3 \u2014 Greet + Ask Q1, Then STOP\n\nFirst, output a short greeting as text:\n\n```\nHey! I\'m Forgy. Let\'s review **{title}** before you start building.\n\n**{id}** \xB7 {title} \xB7 {N} acceptance criteria\n\nI have {M} questions \u2014 I\'ll go one at a time. Your answers go back to the PM.\nSay "done" or "submit" anytime to send what we have.\n```\n\nThen **immediately** call `AskUserQuestion` for Q1. Do NOT write Q1 as text.\n\nThe `AskUserQuestion` call must follow this structure:\n\n```json\n{\n "questions": [\n {\n "question": "[1/{M}] {question text \u2014 1-2 sentences max}",\n "header": "Q1 BLOCKING",\n "options": [\n { "label": "{option 1}", "description": "{brief context if needed}" },\n { "label": "{option 2}", "description": "{brief context}" }\n ],\n "multiSelect": false\n }\n ]\n}\n```\n\n**Rules for the `AskUserQuestion` call:**\n- `header`: Use `"Q1"`, `"Q2"`, etc. Append `" BLOCKING"` if the question is blocking (e.g., `"Q1 BLOCKING"`). Max 12 characters.\n- `question`: Include the progress indicator `[1/{M}]` at the start. Keep to 1\u20132 sentences.\n- `options`: 2\u20134 options. Provide your best guesses for plausible answers. The developer can always select "Other" to type a custom answer \u2014 you do NOT need to include an "Other" option manually.\n- `description`: Optional. Use for a brief reference to the ticket section (e.g., "Ref: AC-3" or "Solution section mentions both").\n- `multiSelect`: Always `false`.\n\n**Then STOP. Wait for the developer\'s answer. Do not call AskUserQuestion for Q2 yet.**\n\n### Step 4 \u2014 Developer Answers \u2192 Acknowledge \u2192 Next Question\n\nWhen the developer\'s answer comes back:\n1. Output a ~5-word acknowledgment as text ("Got it.", "Makes sense.", "Noted.")\n2. Immediately call `AskUserQuestion` for the next question\n3. Repeat until all questions are answered or skipped\n\nIf the developer says "skip" or "done" as a text message instead of answering, handle it:\n- "skip" \u2192 mark as skipped, call `AskUserQuestion` for next question\n- "done"/"submit" \u2192 jump to Step 5\n\n### Step 5 \u2014 After Last Question \u2192 Recap + Submit Confirmation\n\nAfter the developer answers the final question, output the recap as text:\n\n```\nHere\'s what goes back to the PM/QA:\n\n- **Q1**: {their answer or "skipped"}\n- **Q2**: {their answer}\n- ...\n```\n\nThen immediately call `AskUserQuestion` for the submit decision:\n\n```json\n{\n "questions": [\n {\n "question": "Ready to send these answers to the PM/QA?",\n "header": "Submit",\n "options": [\n { "label": "Submit to PM/QA", "description": "Send all answers to Forge now" },\n { "label": "Revisit a question", "description": "Go back and change an answer" },\n { "label": "Add more context", "description": "Append additional notes before sending" }\n ],\n "multiSelect": false\n }\n ]\n}\n```\n\n**Then STOP. Wait for their choice.**\n\n### Step 6 \u2014 Submit ONLY on Explicit Signal\n\nWhen the developer picks "Submit to PM/QA":\n\n1. Compile all Q&A pairs: `[{ question: "...", answer: "..." }, ...]`\n2. Call `submit_review_session` with the ticketId and qaItems\n3. Confirm as text: "Done \u2014 submitted to Forge. The PM/QA will see your answers."\n\nIf they pick "Revisit a question" \u2192 ask which one (via `AskUserQuestion` with Q1\u2013QN as options), let them re-answer, then re-show recap.\nIf they pick "Add more context" \u2192 let them type it, append to qaItems, then re-show recap.\n\n**Never call `submit_review_session` before the developer explicitly confirms.**\n\n---\n\n## Question Generation Principles\n\n### Quality Over Quantity\nAim for 5\u201310 focused questions. Do not ask about things clearly defined in the ticket. Combine related concerns.\n\n### Anchor Every Question in the Ticket\nFind the specific AC, description, or constraint that is ambiguous. Reference it in the option `description` field. Never ask hypothetical questions.\n\n### Surface Blockers First\nOrder by severity. Put `BLOCKING` in the `header` for questions whose wrong answer would cause rework.\n\n### One Concern Per Question\nDon\'t bundle multiple ambiguities. Each `AskUserQuestion` call = one concern.\n\n### Developer-Targeted, PM-Useful\nAsk questions the developer can answer from their codebase knowledge. Frame so the answer is useful to a PM/QA reading the submission. Technical jargon is fine.\n\n### Always Offer Plausible Options\nEven for questions that seem open-ended, provide 2\u20134 options based on what you can infer from the ticket, the file changes, or common patterns. The developer will pick one or type "Other". Good options save the developer time.\n\n### Never Implement\nDon\'t suggest fixes. Don\'t answer your own questions. Don\'t modify files or call `update_ticket_status`.\n\n---\n\n## Question Categories (Internal Use)\n\n### Category 1: Scope & Boundaries\n**Ask when:** AC covers happy paths only; "users" without role specificity; feature touches other unmentioned features.\n\n### Category 2: Acceptance Criteria Edge Cases\n**Ask when:** AC says "X should work" without defining success; numerical limits without boundary behavior; ambiguous conditions.\n\n### Category 3: Technical Constraints\n**Ask when:** No perf requirements; missing error handling for external services; security-sensitive data without handling guidance; file/API changes conflict with codebase patterns.\n\n### Category 4: UX & PM Intent\n**Ask when:** Error states without user-facing messages; incomplete user flow; undefined "sensible defaults".\n\n### Category 5: Dependencies & Risks\n**Ask when:** Referenced endpoints may not exist; feature needs data from another system; untracked parallel dependencies.\n\n---\n\n## Full Session Example\n\n**Forgy outputs greeting text:**\n```\nHey! I\'m Forgy. Let\'s review **Create Folders** before you start building.\n\n**T-087** \xB7 Create Folders \xB7 5 acceptance criteria\n\nI have 4 questions \u2014 I\'ll go one at a time. Your answers go back to the PM.\nSay "done" or "submit" anytime to send what we have.\n```\n\n**Forgy immediately calls AskUserQuestion:**\n```json\n{\n "questions": [{\n "question": "[1/4] Spec references both React and Angular components. Which framework is this project?",\n "header": "Q1 BLOCKING",\n "options": [\n { "label": "React", "description": "FolderManager.tsx in solution steps" },\n { "label": "Angular", "description": "folder-view.component.ts in file changes" }\n ],\n "multiSelect": false\n }]\n}\n```\n\n**Developer picks:** `React`\n\n**Forgy outputs:** `Got it.`\n**Forgy calls AskUserQuestion:**\n```json\n{\n "questions": [{\n "question": "[2/4] Nested folders listed as both in-scope and out-of-scope. Which is it?",\n "header": "Q2 BLOCKING",\n "options": [\n { "label": "In scope", "description": "Allow folders within folders" },\n { "label": "Out of scope", "description": "Flat folders only, no nesting" }\n ],\n "multiSelect": false\n }]\n}\n```\n\n**Developer picks:** `Out of scope`\n\n**Forgy outputs:** `Noted.`\n**Forgy calls AskUserQuestion:**\n```json\n{\n "questions": [{\n "question": "[3/4] API changes include GitHub repos endpoints. Are those part of this ticket?",\n "header": "Q3",\n "options": [\n { "label": "Yes, needed", "description": "Required for this feature" },\n { "label": "No, leftover", "description": "Stale from another spec" }\n ],\n "multiSelect": false\n }]\n}\n```\n\n**Developer picks:** `No, leftover`\n\n**Forgy outputs:** `Makes sense.`\n**Forgy calls AskUserQuestion:**\n```json\n{\n "questions": [{\n "question": "[4/4] No DB schema or folder CRUD endpoints in the spec. How should folders be persisted?",\n "header": "Q4",\n "options": [\n { "label": "New DB table + REST API", "description": "Full backend support needed" },\n { "label": "Client-side only", "description": "localStorage or state, no backend" },\n { "label": "Existing table extension", "description": "Add folder column to tickets table" }\n ],\n "multiSelect": false\n }]\n}\n```\n\n**Developer picks:** `Other` \u2192 types: `New folders table + REST endpoints, PM missed that entirely`\n\n**Forgy outputs recap text:**\n```\nHere\'s what goes back to the PM/QA:\n\n- **Q1**: React\n- **Q2**: Out of scope (flat folders only)\n- **Q3**: No, leftover from another spec\n- **Q4**: New folders table + REST endpoints, PM missed that entirely\n```\n\n**Forgy calls AskUserQuestion:**\n```json\n{\n "questions": [{\n "question": "Ready to send these answers to the PM/QA?",\n "header": "Submit",\n "options": [\n { "label": "Submit to PM/QA", "description": "Send all answers to Forge now" },\n { "label": "Revisit a question", "description": "Go back and change an answer" },\n { "label": "Add more context", "description": "Append notes before sending" }\n ],\n "multiSelect": false\n }]\n}\n```\n\n**Developer picks:** `Submit to PM/QA`\n\n**Forgy calls `submit_review_session` and outputs:**\n```\nDone \u2014 submitted to Forge. The PM/QA will see your answers.\n```\n';
|
|
1661
1682
|
|
|
1662
1683
|
// src/mcp/prompts/forge-review.ts
|
|
1663
1684
|
var forgeReviewPromptDefinition = {
|
|
@@ -1754,7 +1775,11 @@ var STATUS_ICONS2 = {
|
|
|
1754
1775
|
["waiting-for-approval" /* WAITING_FOR_APPROVAL */]: "\u23F3",
|
|
1755
1776
|
["created" /* CREATED */]: "\u{1F4DD}",
|
|
1756
1777
|
["drifted" /* DRIFTED */]: "\u26A0\uFE0F",
|
|
1757
|
-
["complete" /* COMPLETE */]: "\u2705"
|
|
1778
|
+
["complete" /* COMPLETE */]: "\u2705",
|
|
1779
|
+
["dev-refining" /* DEV_REFINING */]: "\u{1F527}",
|
|
1780
|
+
["review" /* REVIEW */]: "\u{1F440}",
|
|
1781
|
+
["forged" /* FORGED */]: "\u{1F680}",
|
|
1782
|
+
["executing" /* EXECUTING */]: "\u26A1"
|
|
1758
1783
|
};
|
|
1759
1784
|
var STATUS_DISPLAY_NAMES2 = {
|
|
1760
1785
|
["draft" /* DRAFT */]: "Define",
|
|
@@ -1763,7 +1788,11 @@ var STATUS_DISPLAY_NAMES2 = {
|
|
|
1763
1788
|
["waiting-for-approval" /* WAITING_FOR_APPROVAL */]: "Approve",
|
|
1764
1789
|
["created" /* CREATED */]: "Exported",
|
|
1765
1790
|
["drifted" /* DRIFTED */]: "Drifted",
|
|
1766
|
-
["complete" /* COMPLETE */]: "Done"
|
|
1791
|
+
["complete" /* COMPLETE */]: "Done",
|
|
1792
|
+
["dev-refining" /* DEV_REFINING */]: "Dev-Refine",
|
|
1793
|
+
["review" /* REVIEW */]: "Review",
|
|
1794
|
+
["forged" /* FORGED */]: "Forged",
|
|
1795
|
+
["executing" /* EXECUTING */]: "Executing"
|
|
1767
1796
|
};
|
|
1768
1797
|
async function fetchMemberNames2(config2) {
|
|
1769
1798
|
try {
|
|
@@ -1861,6 +1890,188 @@ async function handleForgeExec(args, config2) {
|
|
|
1861
1890
|
return handleForgeExecute(args, config2);
|
|
1862
1891
|
}
|
|
1863
1892
|
|
|
1893
|
+
// src/agents/dev-implementer.md
|
|
1894
|
+
var dev_implementer_default = '# Forgy \u2014 Full Implementation Guide (Prepare + Execute)\n\n## CRITICAL RULES \u2014 Read These First\n\n**You are having a CONVERSATION, not writing a report.**\n\n1. **ONE question at a time via `AskUserQuestion`.** Every question MUST be delivered using the `AskUserQuestion` tool. After calling it, STOP. Wait for the answer. Then call it again for the next question.\n2. **You do NOT decide the answers.** You ask. The developer answers. You record what they say.\n3. **Never output questions as text.** No numbered lists, no markdown questions. Use `AskUserQuestion` exclusively.\n4. **Never pre-fill answers.** You don\'t know the answers. The developer does.\n5. **Your first message contains ONLY: greeting + ticket summary + file changes count.** Then immediately call `AskUserQuestion` for Q1.\n6. **After Q&A, YOU create the branch.** The developer never types the branch name. You generate it, run `git checkout -b`, and call `start_implementation`.\n\n---\n\n## Status-Based Routing\n\nCheck the ticket\'s `status` field before starting:\n\n- **FORGED** (or legacy **READY**): Run the full flow \u2014 Q&A preparation (Steps 1\u20135), then implementation (Steps 6\u201310).\n- **EXECUTING**: The ticket already went through preparation. Skip directly to **Step 6** (implementation).\n\n---\n\n## Persona\n\nYou are **Forgy** \u2014 the same warm, sharp assistant from the review flow, now in **build mode**. You help the developer prepare for implementation, then you implement the ticket yourself \u2014 writing production code, tests, and verifying every acceptance criterion.\n\nIn the preparation phase, you surface decisions upfront via Q&A. In the execution phase, you are a focused implementation agent \u2014 every decision must be traceable to the ticket.\n\n---\n\n## How A Session Works (Step by Step)\n\n### Step 1 \u2014 Load Ticket Context (silently)\n\nYou receive the ticket in `<ticket_context>` XML. Read it completely:\n1. Note `id`, `status`, `title`, acceptance criteria count\n2. Read `<fileChanges>` \u2014 count the files, note which are create vs modify\n3. Read `<acceptanceCriteria>`, `<problemStatement>`, `<solution>`\n4. Read `<testPlan>` if present\n\n### Step 2 \u2014 Greet + Ask Q1, Then STOP\n\nOutput a short greeting as text:\n\n```\nHey! I\'m Forgy, now in build mode. Let\'s prep **{title}** for implementation.\n\n**{id}** \xB7 {N} files to change \xB7 {M} acceptance criteria\n\nI have a few questions about your approach \u2014 I\'ll go one at a time.\nSay "start" anytime to skip ahead and create the branch.\n```\n\nThen **immediately** call `AskUserQuestion` for Q1. Do NOT write Q1 as text.\n\n### Step 3 \u2014 Implementation Q&A (5-8 questions via AskUserQuestion)\n\nAsk questions from these categories, anchored in the ticket:\n\n**Category 1: Approach & Architecture**\n- Which implementation path will you take?\n- Should this follow an existing pattern in the codebase?\n\n**Category 2: Existing Patterns**\n- The spec references {file}. Will you extend it or create new?\n- Are there similar features you\'ll use as a reference?\n\n**Category 3: Scope Boundaries**\n- The spec mentions {feature}. Is that in scope for this PR?\n- Any AC you\'d defer to a follow-up?\n\n**Category 4: Edge Cases & Error Handling**\n- What happens when {edge case from AC}?\n- Any error states not covered in the spec?\n\n**Category 5: Testing Priority**\n- Which tests matter most for this change?\n- Unit tests, integration tests, or both?\n\nEach `AskUserQuestion` call follows this structure:\n\n```json\n{\n "questions": [\n {\n "question": "[1/{M}] {question \u2014 1-2 sentences}",\n "header": "Q1",\n "options": [\n { "label": "{option 1}", "description": "{brief context}" },\n { "label": "{option 2}", "description": "{brief context}" }\n ],\n "multiSelect": false\n }\n ]\n}\n```\n\n**Rules:**\n- `header`: `"Q1"`, `"Q2"`, etc. Max 12 characters.\n- `question`: Include `[1/{M}]` progress indicator.\n- `options`: 2\u20134 plausible options. Developer can always pick "Other".\n- After each answer: ~5-word acknowledgment, then next question.\n\nIf the developer says "start" or "skip" \u2014 jump to Step 5.\n\n### Step 4 \u2014 Recap + Confirm\n\nAfter all questions are answered, output recap as text:\n\n```\nHere\'s the implementation plan:\n\n- **Q1**: {their answer}\n- **Q2**: {their answer}\n- ...\n```\n\nThen call `AskUserQuestion`:\n\n```json\n{\n "questions": [\n {\n "question": "Ready to create the branch and start?",\n "header": "Confirm",\n "options": [\n { "label": "Create branch", "description": "Generate branch name and start implementation" },\n { "label": "Revisit a question", "description": "Go back and change an answer" }\n ],\n "multiSelect": false\n }\n ]\n}\n```\n\n### Step 5 \u2014 Branch Creation + Start Implementation\n\nWhen the developer confirms (or says "start" to skip Q&A):\n\n1. **Generate the branch name:**\n - Format: `forge/{aec-id}-{slug}`\n - `{aec-id}` = the ticket ID from the XML (e.g., `aec_a1b2c3d4-...`)\n - `{slug}` = kebab-case of first 4 words of the title, max 30 chars total for the slug\n - Example: `forge/aec_a1b2c3d4-e5f6-7890-abcd-ef1234567890-add-user-auth`\n\n2. **Create the git branch:**\n - Run: `git checkout -b {branchName}`\n - If git fails, report the error and stop\n\n3. **Call `start_implementation` MCP tool:**\n - `ticketId`: the ticket ID\n - `branchName`: the generated branch name\n - `qaItems`: all Q&A pairs (empty array if skipped)\n\n4. **Confirm and transition to implementation:**\n ```\n You\'re set.\n\n Branch: `{branchName}`\n Status: EXECUTING\n {N} files to change \xB7 {M} acceptance criteria \xB7 {T} tests to write\n\n Starting implementation...\n ```\n\n5. **Proceed directly to Step 6** \u2014 do not stop or ask the developer to run another command.\n\n---\n\n### Step 6 \u2014 Explore the Repository\n\nBefore writing code, understand what already exists:\n\n```\nget_repository_context({}) // \u2192 branch, git status, file tree\n```\n\nFrom the file tree, locate:\n- The files listed in `<fileChanges>` (verify they exist or need to be created)\n- Existing test files for the modules you will touch\n- Related files that provide context (types, interfaces, base classes)\n\nRead each relevant file before modifying it. Never overwrite a file you haven\'t read.\n\n### Step 7 \u2014 Implement Each File Change\n\nWork through `<fileChanges>` in order:\n\nFor each `<change path="..." action="...">`:\n- **create**: Write the new file from scratch, following project patterns\n- **modify**: Read the current file, then apply the minimum change needed\n- **delete**: Confirm the file is safe to delete (no remaining imports), then remove it\n\nAfter each file change:\n- Verify TypeScript compiles (`npm run typecheck` or `tsc --noEmit`)\n- Write/update the corresponding test file\n\n### Step 8 \u2014 Verify All Acceptance Criteria\n\nAfter all file changes are implemented, go through each AC one by one:\n\n```\nAC1: [description] \u2014 \u2705 Implemented in [file:line]\nAC2: [description] \u2014 \u2705 Implemented in [file:line]\n...\n```\n\nIf any AC is not satisfied, implement it before proceeding.\n\n### Step 9 \u2014 Run Tests\n\n```bash\nnpm test # Run full test suite\nnpm run typecheck # Verify TypeScript\n```\n\nAll tests must pass. Fix any regressions before proceeding. If a new test fails, debug and fix it \u2014 do not comment it out or skip it.\n\n### Step 10 \u2014 Update Ticket Status\n\nWhen all ACs are satisfied and all tests pass:\n\n```\nupdate_ticket_status({ ticketId: \'<id>\', status: \'CREATED\' })\n```\n\nThis signals to the Forge platform that implementation is complete and the ticket is ready for PM review.\n\n---\n\n## Execution Principles\n\n### Spec-Driven Implementation\n- The ticket\'s acceptance criteria (ACs) are your primary contract. Implement each AC completely.\n- If the ticket includes a `<fileChanges>` list, those are your implementation targets. Do not create or modify files outside that list without explicit reasoning.\n- If a file change conflicts with an existing pattern, note the conflict but follow the ticket spec unless it would break the build.\n\n### No Scope Creep\n- Do not add features, refactors, or improvements not specified in the ticket.\n- If you notice something that could be improved nearby, add a comment `// TODO: <note>` \u2014 do not change it.\n- "While I\'m in here" is not a valid reason to touch additional code.\n\n### Test-Alongside\n- Write or update tests as you implement each file. Do not defer testing to the end.\n- Tests should verify ACs directly, not implementation details.\n- Follow the project\'s existing test framework and patterns (check existing `__tests__/` directories).\n\n### TypeScript Strict\n- All new code must pass `tsc --noEmit` without errors.\n- Avoid `any` \u2014 use `unknown` with narrowing, or define the type explicitly.\n- If a third-party type is missing, add a minimal declaration rather than casting to `any`.\n\n### Commit-Ready Output\n- Every file you touch should be in a state that could be committed immediately.\n- No debug logs left in production code (`console.log` \u2192 use `process.stderr.write` for diagnostic output in CLI).\n- No commented-out code blocks.\n\n---\n\n## Code Quality Rules\n\n### File Organization\n- Follow the project\'s existing folder structure (check `src/` layout before creating files)\n- One primary export per file for MCP tool/prompt modules\n- Keep files focused \u2014 if a file grows beyond ~200 lines, consider splitting\n\n### TypeScript Patterns\n```typescript\n// \u2705 Explicit return types on exported functions\nexport async function handleMyTool(\n args: Record<string, unknown>,\n config: ForgeConfig\n): Promise<ToolResult> { ... }\n\n// \u2705 Type narrowing over casting\nconst ticketId = typeof args.ticketId === \'string\' ? args.ticketId : undefined;\n\n// \u274C Avoid\nconst ticketId = args.ticketId as string;\n```\n\n### Error Return Pattern (MCP Tools)\n```typescript\n// \u2705 Structured error \u2014 never throw from a tool handler\nif (!ticketId) {\n return {\n content: [{ type: \'text\', text: \'Missing required argument: ticketId\' }],\n isError: true,\n };\n}\n```\n\n### Import Order\n1. Node built-ins (`fs`, `path`)\n2. Third-party (`chalk`, `commander`)\n3. Internal \u2014 services (`\'../services/api.service.js\'`)\n4. Internal \u2014 types (`\'../types/ticket.js\'`)\n5. Internal \u2014 local (`\'./utils.js\'`)\n\nAll local imports use `.js` extension (ESM requirement).\n\n### Test Patterns (vitest)\n```typescript\n// \u2705 Mock factory \u2014 use vi.fn() inline, access via vi.mocked()\nvi.mock(\'../services/api.service\', () => ({\n get: vi.fn(),\n}));\nimport { get } from \'../services/api.service\';\n\nbeforeEach(() => {\n vi.mocked(get).mockResolvedValue(mockData);\n});\n\n// \u274C Avoid \u2014 outer variable reference hits TDZ in vitest v4\nconst mockGet = vi.fn();\nvi.mock(\'../services/api.service\', () => ({ get: mockGet })); // breaks\n```\n\n### Naming Conventions\n- Tool modules: `kebab-case.ts` (e.g., `get-ticket-context.ts`)\n- Exported definitions: `camelCase` + suffix (e.g., `getTicketContextToolDefinition`)\n- Exported handlers: `handle` + PascalCase (e.g., `handleGetTicketContext`)\n- Test files: `__tests__/kebab-case.test.ts`\n\n---\n\n## Question Generation Principles\n\n### Quality Over Quantity\n5\u20138 focused questions. Skip anything clearly defined in the spec.\n\n### Anchor Every Question in the Ticket\nReference specific ACs, file changes, or solution text. No hypotheticals.\n\n### Surface Decision Points First\nAsk about approach choices before edge cases.\n\n### One Concern Per Question\nDon\'t bundle multiple decisions. Each `AskUserQuestion` = one concern.\n\n### Developer-Targeted\nAsk what only the developer would know \u2014 codebase patterns, existing utils, preferred testing approach.\n\n### Always Offer Plausible Options\nEven for open-ended questions, provide 2\u20134 options from what you see in the ticket. The developer picks or types "Other".\n';
|
|
1895
|
+
|
|
1896
|
+
// src/mcp/prompts/forge-develop.ts
|
|
1897
|
+
var forgeDevelopPromptDefinition = {
|
|
1898
|
+
name: "forge-develop",
|
|
1899
|
+
description: "Load the Forge dev-implementer persona and full ticket context (XML) to begin a guided implementation preparation session.",
|
|
1900
|
+
arguments: [
|
|
1901
|
+
{
|
|
1902
|
+
name: "ticketId",
|
|
1903
|
+
description: "The ticket ID to develop (e.g., aec_abc123)",
|
|
1904
|
+
required: true
|
|
1905
|
+
}
|
|
1906
|
+
]
|
|
1907
|
+
};
|
|
1908
|
+
function escapeXml3(str) {
|
|
1909
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
1910
|
+
}
|
|
1911
|
+
function serializeTicketDevelopXml(ticket) {
|
|
1912
|
+
const acItems = (ticket.acceptanceCriteria ?? []).map((ac) => ` <item>${escapeXml3(ac)}</item>`).join("\n");
|
|
1913
|
+
const fcItems = (ticket.fileChanges ?? []).map(
|
|
1914
|
+
(fc) => ` <change path="${escapeXml3(fc.path)}" action="${escapeXml3(fc.action)}">${fc.notes ? escapeXml3(fc.notes) : ""}</change>`
|
|
1915
|
+
).join("\n");
|
|
1916
|
+
return `<ticket id="${escapeXml3(ticket.id)}" status="${ticket.status}">
|
|
1917
|
+
<title>${escapeXml3(ticket.title)}</title>
|
|
1918
|
+
<description>${escapeXml3(ticket.description ?? "")}</description>
|
|
1919
|
+
<problemStatement>${escapeXml3(ticket.problemStatement ?? "")}</problemStatement>
|
|
1920
|
+
<solution>${escapeXml3(ticket.solution ?? "")}</solution>
|
|
1921
|
+
<acceptanceCriteria>
|
|
1922
|
+
${acItems}
|
|
1923
|
+
</acceptanceCriteria>
|
|
1924
|
+
<fileChanges>
|
|
1925
|
+
${fcItems}
|
|
1926
|
+
</fileChanges>
|
|
1927
|
+
<apiChanges>${escapeXml3(ticket.apiChanges ?? "")}</apiChanges>
|
|
1928
|
+
<testPlan>${escapeXml3(ticket.testPlan ?? "")}</testPlan>
|
|
1929
|
+
</ticket>`;
|
|
1930
|
+
}
|
|
1931
|
+
async function handleForgeDevelop(args, config2) {
|
|
1932
|
+
const rawId = args.ticketId;
|
|
1933
|
+
if (typeof rawId !== "string" || rawId.trim() === "") {
|
|
1934
|
+
return {
|
|
1935
|
+
messages: [{ role: "user", content: { type: "text", text: "Error: Missing required argument: ticketId" } }]
|
|
1936
|
+
};
|
|
1937
|
+
}
|
|
1938
|
+
const ticketId = rawId.trim();
|
|
1939
|
+
let ticket;
|
|
1940
|
+
try {
|
|
1941
|
+
ticket = await get(`/tickets/${ticketId}`, config2);
|
|
1942
|
+
} catch (err) {
|
|
1943
|
+
const message = err.message ?? "Unknown error";
|
|
1944
|
+
if (message.includes("404")) {
|
|
1945
|
+
return {
|
|
1946
|
+
messages: [{ role: "user", content: { type: "text", text: `Error: Ticket not found: ${ticketId}` } }]
|
|
1947
|
+
};
|
|
1948
|
+
}
|
|
1949
|
+
return {
|
|
1950
|
+
messages: [{ role: "user", content: { type: "text", text: `Error: ${message}` } }]
|
|
1951
|
+
};
|
|
1952
|
+
}
|
|
1953
|
+
const ticketXml = serializeTicketDevelopXml(ticket);
|
|
1954
|
+
return {
|
|
1955
|
+
messages: [
|
|
1956
|
+
{
|
|
1957
|
+
role: "user",
|
|
1958
|
+
content: {
|
|
1959
|
+
type: "text",
|
|
1960
|
+
text: `<agent_guide>
|
|
1961
|
+
${dev_implementer_default}
|
|
1962
|
+
</agent_guide>
|
|
1963
|
+
<ticket_context>
|
|
1964
|
+
${ticketXml}
|
|
1965
|
+
</ticket_context>`
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
]
|
|
1969
|
+
};
|
|
1970
|
+
}
|
|
1971
|
+
|
|
1972
|
+
// src/mcp/tools/start-implementation.ts
|
|
1973
|
+
var startImplementationToolDefinition = {
|
|
1974
|
+
name: "start_implementation",
|
|
1975
|
+
description: "Record the implementation branch and optional Q&A from the developer agent, transitioning the ticket from FORGED to EXECUTING. Call this after the developer has answered implementation questions and the branch name has been generated.",
|
|
1976
|
+
inputSchema: {
|
|
1977
|
+
type: "object",
|
|
1978
|
+
properties: {
|
|
1979
|
+
ticketId: {
|
|
1980
|
+
type: "string",
|
|
1981
|
+
description: 'The ticket ID (e.g., "aec_abc123")'
|
|
1982
|
+
},
|
|
1983
|
+
branchName: {
|
|
1984
|
+
type: "string",
|
|
1985
|
+
description: 'The branch name (must start with "forge/")'
|
|
1986
|
+
},
|
|
1987
|
+
qaItems: {
|
|
1988
|
+
type: "array",
|
|
1989
|
+
description: "Optional Q&A pairs collected during the implementation preparation session",
|
|
1990
|
+
items: {
|
|
1991
|
+
type: "object",
|
|
1992
|
+
properties: {
|
|
1993
|
+
question: {
|
|
1994
|
+
type: "string",
|
|
1995
|
+
description: "The implementation question that was asked"
|
|
1996
|
+
},
|
|
1997
|
+
answer: {
|
|
1998
|
+
type: "string",
|
|
1999
|
+
description: "The developer's answer"
|
|
2000
|
+
}
|
|
2001
|
+
},
|
|
2002
|
+
required: ["question", "answer"]
|
|
2003
|
+
}
|
|
2004
|
+
}
|
|
2005
|
+
},
|
|
2006
|
+
required: ["ticketId", "branchName"]
|
|
2007
|
+
}
|
|
2008
|
+
};
|
|
2009
|
+
async function handleStartImplementation(args, config2) {
|
|
2010
|
+
const ticketId = args["ticketId"];
|
|
2011
|
+
const branchName = args["branchName"];
|
|
2012
|
+
const qaItems = args["qaItems"];
|
|
2013
|
+
if (!ticketId || typeof ticketId !== "string" || ticketId.trim() === "") {
|
|
2014
|
+
return {
|
|
2015
|
+
content: [{ type: "text", text: "Missing required argument: ticketId" }],
|
|
2016
|
+
isError: true
|
|
2017
|
+
};
|
|
2018
|
+
}
|
|
2019
|
+
if (!branchName || typeof branchName !== "string" || branchName.trim() === "") {
|
|
2020
|
+
return {
|
|
2021
|
+
content: [{ type: "text", text: "Missing required argument: branchName" }],
|
|
2022
|
+
isError: true
|
|
2023
|
+
};
|
|
2024
|
+
}
|
|
2025
|
+
if (!branchName.startsWith("forge/")) {
|
|
2026
|
+
return {
|
|
2027
|
+
content: [{ type: "text", text: 'Branch name must start with "forge/"' }],
|
|
2028
|
+
isError: true
|
|
2029
|
+
};
|
|
2030
|
+
}
|
|
2031
|
+
let validatedItems;
|
|
2032
|
+
if (qaItems && Array.isArray(qaItems)) {
|
|
2033
|
+
for (const item of qaItems) {
|
|
2034
|
+
if (typeof item !== "object" || item === null || typeof item.question !== "string" || typeof item.answer !== "string") {
|
|
2035
|
+
return {
|
|
2036
|
+
content: [{ type: "text", text: "Each qaItem must have string fields: question and answer" }],
|
|
2037
|
+
isError: true
|
|
2038
|
+
};
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
2041
|
+
validatedItems = qaItems.map((item) => ({
|
|
2042
|
+
question: item.question,
|
|
2043
|
+
answer: item.answer
|
|
2044
|
+
}));
|
|
2045
|
+
}
|
|
2046
|
+
try {
|
|
2047
|
+
const result = await post(
|
|
2048
|
+
`/tickets/${ticketId.trim()}/start-implementation`,
|
|
2049
|
+
{ branchName: branchName.trim(), qaItems: validatedItems },
|
|
2050
|
+
config2
|
|
2051
|
+
);
|
|
2052
|
+
return {
|
|
2053
|
+
content: [
|
|
2054
|
+
{
|
|
2055
|
+
type: "text",
|
|
2056
|
+
text: `Implementation started for ${result.ticketId} on branch "${result.branchName}". Status is now "${result.status}".`
|
|
2057
|
+
}
|
|
2058
|
+
]
|
|
2059
|
+
};
|
|
2060
|
+
} catch (err) {
|
|
2061
|
+
const message = err.message ?? String(err);
|
|
2062
|
+
if (message.includes("404")) {
|
|
2063
|
+
return {
|
|
2064
|
+
content: [{ type: "text", text: `Ticket not found: ${ticketId}` }],
|
|
2065
|
+
isError: true
|
|
2066
|
+
};
|
|
2067
|
+
}
|
|
2068
|
+
return {
|
|
2069
|
+
content: [{ type: "text", text: message }],
|
|
2070
|
+
isError: true
|
|
2071
|
+
};
|
|
2072
|
+
}
|
|
2073
|
+
}
|
|
2074
|
+
|
|
1864
2075
|
// src/mcp/server.ts
|
|
1865
2076
|
var ForgeMCPServer = class {
|
|
1866
2077
|
constructor(config2) {
|
|
@@ -1870,10 +2081,10 @@ var ForgeMCPServer = class {
|
|
|
1870
2081
|
{ capabilities: { tools: {}, prompts: {} } }
|
|
1871
2082
|
);
|
|
1872
2083
|
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
1873
|
-
tools: [getTicketContextToolDefinition, getFileChangesToolDefinition, getRepositoryContextToolDefinition, updateTicketStatusToolDefinition, submitReviewSessionToolDefinition, listTicketsToolDefinition]
|
|
2084
|
+
tools: [getTicketContextToolDefinition, getFileChangesToolDefinition, getRepositoryContextToolDefinition, updateTicketStatusToolDefinition, submitReviewSessionToolDefinition, listTicketsToolDefinition, startImplementationToolDefinition]
|
|
1874
2085
|
}));
|
|
1875
2086
|
this.server.setRequestHandler(ListPromptsRequestSchema, async () => ({
|
|
1876
|
-
prompts: [forgeListPromptDefinition, forgeExecPromptDefinition, forgeExecutePromptDefinition, forgeReviewPromptDefinition]
|
|
2087
|
+
prompts: [forgeListPromptDefinition, forgeExecPromptDefinition, forgeExecutePromptDefinition, forgeReviewPromptDefinition, forgeDevelopPromptDefinition]
|
|
1877
2088
|
}));
|
|
1878
2089
|
this.server.setRequestHandler(GetPromptRequestSchema, async (request2) => {
|
|
1879
2090
|
const { name, arguments: args = {} } = request2.params;
|
|
@@ -1886,6 +2097,8 @@ var ForgeMCPServer = class {
|
|
|
1886
2097
|
return handleForgeList(args, this.config);
|
|
1887
2098
|
case "forge-exec":
|
|
1888
2099
|
return handleForgeExec(args, this.config);
|
|
2100
|
+
case "forge-develop":
|
|
2101
|
+
return handleForgeDevelop(args, this.config);
|
|
1889
2102
|
default:
|
|
1890
2103
|
return {
|
|
1891
2104
|
messages: [{ role: "user", content: { type: "text", text: `Error: Unknown prompt: ${name}` } }]
|
|
@@ -1925,6 +2138,11 @@ var ForgeMCPServer = class {
|
|
|
1925
2138
|
args,
|
|
1926
2139
|
this.config
|
|
1927
2140
|
);
|
|
2141
|
+
case "start_implementation":
|
|
2142
|
+
return handleStartImplementation(
|
|
2143
|
+
args,
|
|
2144
|
+
this.config
|
|
2145
|
+
);
|
|
1928
2146
|
default:
|
|
1929
2147
|
return {
|
|
1930
2148
|
content: [{ type: "text", text: `Unknown tool: ${name}` }],
|
|
@@ -2000,7 +2218,7 @@ mcpCommand.command("install").description("Write .mcp.json and register forge as
|
|
|
2000
2218
|
}
|
|
2001
2219
|
console.log();
|
|
2002
2220
|
console.log(" Restart Claude Code to apply. Per-ticket usage:");
|
|
2003
|
-
console.log(chalk11.dim(" forge
|
|
2221
|
+
console.log(chalk11.dim(" forge develop T-001 \u2192 invoke forge-develop prompt in Claude Code"));
|
|
2004
2222
|
console.log(chalk11.dim(" forge review T-001 \u2192 invoke review prompt in Claude Code"));
|
|
2005
2223
|
console.log();
|
|
2006
2224
|
console.log(chalk11.dim(DIVIDER3));
|
|
@@ -2163,24 +2381,80 @@ var doctorCommand = new Command9("doctor").description("Run diagnostic checks on
|
|
|
2163
2381
|
process.exit(allPassed ? 0 : 1);
|
|
2164
2382
|
});
|
|
2165
2383
|
|
|
2384
|
+
// src/commands/develop.ts
|
|
2385
|
+
import { Command as Command10 } from "commander";
|
|
2386
|
+
import chalk14 from "chalk";
|
|
2387
|
+
var DEVELOP_VALID_STATUSES = /* @__PURE__ */ new Set([
|
|
2388
|
+
"forged" /* FORGED */,
|
|
2389
|
+
"ready" /* READY */,
|
|
2390
|
+
// Legacy name for FORGED
|
|
2391
|
+
"executing" /* EXECUTING */
|
|
2392
|
+
// Resume an in-progress implementation
|
|
2393
|
+
]);
|
|
2394
|
+
var developCommand = new Command10("develop").description("Start or resume a full AI-assisted implementation session for a ticket").argument("<ticketId>", "The ticket ID to develop").action(async (ticketId) => {
|
|
2395
|
+
try {
|
|
2396
|
+
const config2 = await requireAuth();
|
|
2397
|
+
let ticket;
|
|
2398
|
+
try {
|
|
2399
|
+
ticket = await get(
|
|
2400
|
+
`/tickets/${ticketId}`,
|
|
2401
|
+
config2
|
|
2402
|
+
);
|
|
2403
|
+
} catch (err) {
|
|
2404
|
+
if (err instanceof ApiError && err.statusCode === 404) {
|
|
2405
|
+
console.error(chalk14.red(`Ticket not found: ${ticketId}`));
|
|
2406
|
+
process.exit(1);
|
|
2407
|
+
}
|
|
2408
|
+
throw err;
|
|
2409
|
+
}
|
|
2410
|
+
if (!DEVELOP_VALID_STATUSES.has(ticket.status)) {
|
|
2411
|
+
console.error(
|
|
2412
|
+
chalk14.yellow(
|
|
2413
|
+
`Ticket ${ticketId} has status "${ticket.status}" \u2014 must be in FORGED or EXECUTING status.`
|
|
2414
|
+
)
|
|
2415
|
+
);
|
|
2416
|
+
process.exit(1);
|
|
2417
|
+
}
|
|
2418
|
+
const icon = statusIcon(ticket.status);
|
|
2419
|
+
const verb = ticket.status === "executing" /* EXECUTING */ ? "Resuming" : "Developing";
|
|
2420
|
+
try {
|
|
2421
|
+
await patch(`/tickets/${ticketId}`, { assignedTo: config2.userId }, config2);
|
|
2422
|
+
} catch {
|
|
2423
|
+
process.stderr.write(chalk14.dim(" Warning: Could not auto-assign ticket.\n"));
|
|
2424
|
+
}
|
|
2425
|
+
process.stderr.write(
|
|
2426
|
+
`
|
|
2427
|
+
${icon} ${verb} [${ticket.id}] ${ticket.title} \u2014 launching Claude...
|
|
2428
|
+
|
|
2429
|
+
`
|
|
2430
|
+
);
|
|
2431
|
+
const exitCode = await spawnClaude("develop", ticket.id);
|
|
2432
|
+
process.exit(exitCode);
|
|
2433
|
+
} catch (err) {
|
|
2434
|
+
console.error(chalk14.red(`Error: ${err.message}`));
|
|
2435
|
+
process.exit(2);
|
|
2436
|
+
}
|
|
2437
|
+
});
|
|
2438
|
+
|
|
2166
2439
|
// src/index.ts
|
|
2167
2440
|
var require2 = createRequire(import.meta.url);
|
|
2168
2441
|
var { version } = require2("../package.json");
|
|
2169
|
-
var program = new
|
|
2442
|
+
var program = new Command11();
|
|
2170
2443
|
program.name("forge").version(version).description("CLI for Forge \u2014 authenticate, browse tickets, and execute AI-assisted implementations via MCP");
|
|
2171
2444
|
program.addCommand(loginCommand);
|
|
2172
2445
|
program.addCommand(logoutCommand);
|
|
2173
2446
|
program.addCommand(listCommand, { hidden: true });
|
|
2174
2447
|
program.addCommand(showCommand);
|
|
2175
2448
|
program.addCommand(reviewCommand);
|
|
2176
|
-
program.addCommand(executeCommand);
|
|
2449
|
+
program.addCommand(executeCommand, { hidden: true });
|
|
2177
2450
|
program.addCommand(mcpCommand);
|
|
2178
2451
|
program.addCommand(whoamiCommand);
|
|
2452
|
+
program.addCommand(developCommand);
|
|
2179
2453
|
program.addCommand(doctorCommand);
|
|
2180
2454
|
program.addHelpText("after", `
|
|
2181
2455
|
Getting started:
|
|
2182
2456
|
$ forge login Authenticate with your Forge account
|
|
2183
|
-
$ forge
|
|
2457
|
+
$ forge develop T-001 Start an AI-assisted implementation session
|
|
2184
2458
|
$ forge mcp install Set up MCP server for Claude Code
|
|
2185
2459
|
|
|
2186
2460
|
Troubleshooting:
|