forge-cc 0.1.41 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +454 -338
- package/dist/cli.js +194 -935
- package/dist/cli.js.map +1 -1
- package/dist/config/loader.d.ts +1 -1
- package/dist/config/loader.js +49 -56
- package/dist/config/loader.js.map +1 -1
- package/dist/config/schema.d.ts +37 -125
- package/dist/config/schema.js +13 -28
- package/dist/config/schema.js.map +1 -1
- package/dist/doctor.d.ts +10 -0
- package/dist/doctor.js +148 -0
- package/dist/doctor.js.map +1 -0
- package/dist/gates/index.d.ts +14 -12
- package/dist/gates/index.js +53 -105
- package/dist/gates/index.js.map +1 -1
- package/dist/gates/lint-gate.d.ts +2 -2
- package/dist/gates/lint-gate.js +60 -66
- package/dist/gates/lint-gate.js.map +1 -1
- package/dist/gates/tests-gate.d.ts +2 -4
- package/dist/gates/tests-gate.js +75 -203
- package/dist/gates/tests-gate.js.map +1 -1
- package/dist/gates/types-gate.d.ts +2 -2
- package/dist/gates/types-gate.js +53 -59
- package/dist/gates/types-gate.js.map +1 -1
- package/dist/linear/client.d.ts +31 -108
- package/dist/linear/client.js +88 -388
- package/dist/linear/client.js.map +1 -1
- package/dist/linear/sync.d.ts +15 -0
- package/dist/linear/sync.js +102 -0
- package/dist/linear/sync.js.map +1 -0
- package/dist/runner/loop.d.ts +4 -0
- package/dist/runner/loop.js +168 -0
- package/dist/runner/loop.js.map +1 -0
- package/dist/runner/prompt.d.ts +14 -0
- package/dist/runner/prompt.js +59 -0
- package/dist/runner/prompt.js.map +1 -0
- package/dist/runner/update.d.ts +1 -0
- package/dist/runner/update.js +72 -0
- package/dist/runner/update.js.map +1 -0
- package/dist/server.d.ts +6 -2
- package/dist/server.js +43 -101
- package/dist/server.js.map +1 -1
- package/dist/setup.d.ts +5 -0
- package/dist/setup.js +208 -0
- package/dist/setup.js.map +1 -0
- package/dist/state/cache.d.ts +3 -0
- package/dist/state/cache.js +23 -0
- package/dist/state/cache.js.map +1 -0
- package/dist/state/status.d.ts +66 -0
- package/dist/state/status.js +96 -0
- package/dist/state/status.js.map +1 -0
- package/dist/types.d.ts +46 -114
- package/dist/worktree/manager.d.ts +6 -103
- package/dist/worktree/manager.js +25 -296
- package/dist/worktree/manager.js.map +1 -1
- package/hooks/pre-commit-verify.js +109 -109
- package/package.json +3 -2
- package/skills/forge-go.md +20 -13
- package/skills/forge-setup.md +149 -388
- package/skills/forge-spec.md +367 -342
- package/skills/forge-triage.md +179 -133
- package/skills/forge-update.md +87 -93
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { spawn, execFile } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { loadConfig } from "../config/loader.js";
|
|
6
|
+
import { readStatus, updateMilestoneStatus, findNextPending, discoverStatuses, } from "../state/status.js";
|
|
7
|
+
import { readMilestoneSection, buildPrompt } from "./prompt.js";
|
|
8
|
+
import { createWorktree, mergeWorktree, removeWorktree, } from "../worktree/manager.js";
|
|
9
|
+
import { ForgeLinearClient } from "../linear/client.js";
|
|
10
|
+
import { syncMilestoneStart, syncMilestoneComplete, } from "../linear/sync.js";
|
|
11
|
+
const execFileAsync = promisify(execFile);
|
|
12
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
const cliPath = join(__dirname, "..", "cli.js");
|
|
14
|
+
function repoName(projectDir) {
|
|
15
|
+
return basename(resolve(projectDir));
|
|
16
|
+
}
|
|
17
|
+
function worktreePath(projectDir, slug, milestoneNumber) {
|
|
18
|
+
return resolve(projectDir, "..", ".forge-wt", repoName(projectDir), `${slug}-m${milestoneNumber}`);
|
|
19
|
+
}
|
|
20
|
+
function parseMilestoneNumber(key) {
|
|
21
|
+
const match = /(\d+)/.exec(key);
|
|
22
|
+
if (!match)
|
|
23
|
+
throw new Error(`Cannot parse milestone number from key: ${key}`);
|
|
24
|
+
return Number.parseInt(match[1], 10);
|
|
25
|
+
}
|
|
26
|
+
function parseMilestoneName(key) {
|
|
27
|
+
const colonIndex = key.indexOf(":");
|
|
28
|
+
if (colonIndex === -1)
|
|
29
|
+
return key;
|
|
30
|
+
return key.slice(colonIndex + 1).trim();
|
|
31
|
+
}
|
|
32
|
+
function spawnClaude(prompt, cwd) {
|
|
33
|
+
return new Promise((resolve, reject) => {
|
|
34
|
+
// Strip CLAUDECODE env var to allow spawning claude from within a Claude Code session
|
|
35
|
+
const env = { ...process.env };
|
|
36
|
+
delete env.CLAUDECODE;
|
|
37
|
+
const child = spawn("claude", ["-p", "-", "--dangerously-skip-permissions"], {
|
|
38
|
+
cwd,
|
|
39
|
+
env,
|
|
40
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
41
|
+
});
|
|
42
|
+
child.stdin.write(prompt);
|
|
43
|
+
child.stdin.end();
|
|
44
|
+
child.stdout.pipe(process.stdout);
|
|
45
|
+
child.stderr.pipe(process.stderr);
|
|
46
|
+
child.on("error", reject);
|
|
47
|
+
child.on("close", (code) => {
|
|
48
|
+
resolve(code ?? 1);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
async function runVerifyInWorktree(wtPath) {
|
|
53
|
+
try {
|
|
54
|
+
const { stdout } = await execFileAsync("node", [cliPath, "verify", "--json"], {
|
|
55
|
+
cwd: wtPath,
|
|
56
|
+
});
|
|
57
|
+
return JSON.parse(stdout);
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
// execFile rejects on non-zero exit, but stdout still contains the JSON result
|
|
61
|
+
if (typeof err === "object" &&
|
|
62
|
+
err !== null &&
|
|
63
|
+
"stdout" in err &&
|
|
64
|
+
typeof err.stdout === "string") {
|
|
65
|
+
const stdout = err.stdout;
|
|
66
|
+
if (stdout.trim()) {
|
|
67
|
+
return JSON.parse(stdout);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
throw err;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
export async function runRalphLoop(opts) {
|
|
74
|
+
const { slug, projectDir } = opts;
|
|
75
|
+
const config = await loadConfig(projectDir);
|
|
76
|
+
const maxIterations = config.maxIterations;
|
|
77
|
+
// Find the PRD file
|
|
78
|
+
const prdPath = join(projectDir, ".planning", "prds", `${slug}.md`);
|
|
79
|
+
// Get the status for this slug
|
|
80
|
+
const status = await readStatus(projectDir, slug);
|
|
81
|
+
const prdBranch = status.branch;
|
|
82
|
+
// Find all pending milestones and process them in order
|
|
83
|
+
const allStatuses = await discoverStatuses(projectDir);
|
|
84
|
+
let pending = findNextPending(allStatuses.filter((s) => s.slug === slug));
|
|
85
|
+
while (pending.length > 0) {
|
|
86
|
+
const { milestone: milestoneKey } = pending[0];
|
|
87
|
+
const milestoneNumber = parseMilestoneNumber(milestoneKey);
|
|
88
|
+
const milestoneName = parseMilestoneName(milestoneKey);
|
|
89
|
+
console.log(`\n[forge] Starting milestone ${milestoneNumber}: ${milestoneName}`);
|
|
90
|
+
// Mark in_progress
|
|
91
|
+
await updateMilestoneStatus(projectDir, slug, milestoneKey, "in_progress");
|
|
92
|
+
// Linear sync: start
|
|
93
|
+
const apiKey = process.env.LINEAR_API_KEY;
|
|
94
|
+
if (apiKey && status.linearTeamId) {
|
|
95
|
+
try {
|
|
96
|
+
const client = new ForgeLinearClient({ apiKey, teamId: status.linearTeamId });
|
|
97
|
+
await syncMilestoneStart(client, config, status, milestoneKey);
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
// Linear sync is best-effort
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// Read milestone section from PRD
|
|
104
|
+
const milestoneSection = await readMilestoneSection(prdPath, milestoneNumber);
|
|
105
|
+
// Create worktree
|
|
106
|
+
const wtPath = worktreePath(projectDir, slug, milestoneNumber);
|
|
107
|
+
const wtBranch = `${prdBranch}/m${milestoneNumber}`;
|
|
108
|
+
await createWorktree(wtPath, wtBranch, prdBranch, projectDir);
|
|
109
|
+
let passed = false;
|
|
110
|
+
let verifyErrors = null;
|
|
111
|
+
for (let iteration = 1; iteration <= maxIterations; iteration++) {
|
|
112
|
+
console.log(`\n[forge] Milestone ${milestoneNumber}, iteration ${iteration}/${maxIterations}`);
|
|
113
|
+
// Build prompt
|
|
114
|
+
const prompt = buildPrompt({
|
|
115
|
+
milestoneName,
|
|
116
|
+
milestoneNumber,
|
|
117
|
+
milestoneSection,
|
|
118
|
+
verifyErrors,
|
|
119
|
+
});
|
|
120
|
+
// Spawn Claude
|
|
121
|
+
await spawnClaude(prompt, wtPath);
|
|
122
|
+
// Run verify
|
|
123
|
+
try {
|
|
124
|
+
const result = await runVerifyInWorktree(wtPath);
|
|
125
|
+
if (result.result === "PASSED") {
|
|
126
|
+
passed = true;
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
verifyErrors = result;
|
|
130
|
+
console.log(`[forge] Verify failed on iteration ${iteration}. Retrying...`);
|
|
131
|
+
}
|
|
132
|
+
catch (err) {
|
|
133
|
+
// verify itself may exit non-zero; try to parse stderr/stdout
|
|
134
|
+
console.warn(`[forge] Verify execution error on iteration ${iteration}:`, err);
|
|
135
|
+
verifyErrors = null;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (!passed) {
|
|
139
|
+
console.error(`\n[forge] Milestone ${milestoneNumber} failed after ${maxIterations} iterations.`);
|
|
140
|
+
await removeWorktree(wtPath, projectDir);
|
|
141
|
+
process.exit(1);
|
|
142
|
+
}
|
|
143
|
+
// Merge back and clean up
|
|
144
|
+
console.log(`\n[forge] Milestone ${milestoneNumber} passed. Merging...`);
|
|
145
|
+
await mergeWorktree(wtBranch, prdBranch, projectDir);
|
|
146
|
+
await removeWorktree(wtPath, projectDir);
|
|
147
|
+
// Update status
|
|
148
|
+
const milestoneKeys = Object.keys(status.milestones);
|
|
149
|
+
const isLast = milestoneKeys.indexOf(milestoneKey) === milestoneKeys.length - 1;
|
|
150
|
+
await updateMilestoneStatus(projectDir, slug, milestoneKey, "complete");
|
|
151
|
+
// Linear sync: complete
|
|
152
|
+
if (apiKey && status.linearTeamId) {
|
|
153
|
+
try {
|
|
154
|
+
const client = new ForgeLinearClient({ apiKey, teamId: status.linearTeamId });
|
|
155
|
+
await syncMilestoneComplete(client, config, status, milestoneKey, isLast);
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
// Linear sync is best-effort
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
console.log(`[forge] Milestone ${milestoneNumber} complete.`);
|
|
162
|
+
// Refresh pending list for next iteration
|
|
163
|
+
const refreshed = await discoverStatuses(projectDir);
|
|
164
|
+
pending = findNextPending(refreshed.filter((s) => s.slug === slug));
|
|
165
|
+
}
|
|
166
|
+
console.log(`\n[forge] All milestones for "${slug}" complete.`);
|
|
167
|
+
}
|
|
168
|
+
//# sourceMappingURL=loop.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loop.js","sourceRoot":"","sources":["../../src/runner/loop.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { PipelineResult } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Extract a milestone section from a PRD markdown file.
|
|
4
|
+
* Looks for `### Milestone {N}:` and returns everything up to
|
|
5
|
+
* the next `### Milestone` header or end of file.
|
|
6
|
+
*/
|
|
7
|
+
export declare function readMilestoneSection(prdPath: string, milestoneNumber: number): Promise<string>;
|
|
8
|
+
/** Build the full Ralph loop prompt for a milestone iteration. */
|
|
9
|
+
export declare function buildPrompt(opts: {
|
|
10
|
+
milestoneName: string;
|
|
11
|
+
milestoneNumber: number;
|
|
12
|
+
milestoneSection: string;
|
|
13
|
+
verifyErrors?: PipelineResult | null;
|
|
14
|
+
}): string;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
/**
|
|
3
|
+
* Extract a milestone section from a PRD markdown file.
|
|
4
|
+
* Looks for `### Milestone {N}:` and returns everything up to
|
|
5
|
+
* the next `### Milestone` header or end of file.
|
|
6
|
+
*/
|
|
7
|
+
export async function readMilestoneSection(prdPath, milestoneNumber) {
|
|
8
|
+
const content = await readFile(prdPath, "utf-8");
|
|
9
|
+
const pattern = new RegExp(`^### Milestone ${milestoneNumber}\\b[^\n]*`, "m");
|
|
10
|
+
const match = pattern.exec(content);
|
|
11
|
+
if (!match) {
|
|
12
|
+
throw new Error(`Milestone ${milestoneNumber} not found in ${prdPath}`);
|
|
13
|
+
}
|
|
14
|
+
const startIndex = match.index;
|
|
15
|
+
// Find the next ### Milestone header after our match
|
|
16
|
+
const rest = content.slice(startIndex + match[0].length);
|
|
17
|
+
const nextHeader = /^### Milestone \d+/m.exec(rest);
|
|
18
|
+
const section = nextHeader
|
|
19
|
+
? content.slice(startIndex, startIndex + match[0].length + nextHeader.index)
|
|
20
|
+
: content.slice(startIndex);
|
|
21
|
+
return section.trimEnd();
|
|
22
|
+
}
|
|
23
|
+
/** Format PipelineResult errors into a human-readable string. */
|
|
24
|
+
function formatVerifyErrors(result) {
|
|
25
|
+
const lines = [`forge verify: ${result.result}`];
|
|
26
|
+
for (const gate of result.gates) {
|
|
27
|
+
if (!gate.passed) {
|
|
28
|
+
lines.push(`\nGate "${gate.gate}" FAILED:`);
|
|
29
|
+
for (const err of gate.errors) {
|
|
30
|
+
const loc = err.column
|
|
31
|
+
? `${err.file}:${err.line}:${err.column}`
|
|
32
|
+
: `${err.file}:${err.line}`;
|
|
33
|
+
const rule = err.rule ? ` [${err.rule}]` : "";
|
|
34
|
+
lines.push(` ${loc} — ${err.message}${rule}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return lines.join("\n");
|
|
39
|
+
}
|
|
40
|
+
/** Build the full Ralph loop prompt for a milestone iteration. */
|
|
41
|
+
export function buildPrompt(opts) {
|
|
42
|
+
const { milestoneName, milestoneNumber, milestoneSection, verifyErrors } = opts;
|
|
43
|
+
const currentState = verifyErrors && verifyErrors.result === "FAILED"
|
|
44
|
+
? formatVerifyErrors(verifyErrors)
|
|
45
|
+
: "First iteration — start from scratch";
|
|
46
|
+
return `# Task: Complete Milestone ${milestoneNumber} — ${milestoneName}
|
|
47
|
+
|
|
48
|
+
## What to build
|
|
49
|
+
${milestoneSection}
|
|
50
|
+
|
|
51
|
+
## Current state
|
|
52
|
+
${currentState}
|
|
53
|
+
|
|
54
|
+
## Rules
|
|
55
|
+
- Run \`npx forge verify\` before finishing. All gates must pass.
|
|
56
|
+
- Commit your work before exiting.
|
|
57
|
+
- Do NOT create tests just to make gates pass. Fix real issues only.`;
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=prompt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompt.js","sourceRoot":"","sources":["../../src/runner/prompt.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAG5C;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,OAAe,EACf,eAAuB;IAEvB,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IACjD,MAAM,OAAO,GAAG,IAAI,MAAM,CACxB,kBAAkB,eAAe,WAAW,EAC5C,GAAG,CACJ,CAAC;IACF,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACpC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CACb,aAAa,eAAe,iBAAiB,OAAO,EAAE,CACvD,CAAC;IACJ,CAAC;IAED,MAAM,UAAU,GAAG,KAAK,CAAC,KAAK,CAAC;IAC/B,qDAAqD;IACrD,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,UAAU,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IACzD,MAAM,UAAU,GAAG,qBAAqB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACpD,MAAM,OAAO,GAAG,UAAU;QACxB,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,UAAU,EAAE,UAAU,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,UAAU,CAAC,KAAK,CAAC;QAC5E,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IAE9B,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;AAC3B,CAAC;AAED,iEAAiE;AACjE,SAAS,kBAAkB,CAAC,MAAsB;IAChD,MAAM,KAAK,GAAa,CAAC,iBAAiB,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;IAC3D,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;QAChC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YACjB,KAAK,CAAC,IAAI,CAAC,WAAW,IAAI,CAAC,IAAI,WAAW,CAAC,CAAC;YAC5C,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;gBAC9B,MAAM,GAAG,GAAG,GAAG,CAAC,MAAM;oBACpB,CAAC,CAAC,GAAG,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,MAAM,EAAE;oBACzC,CAAC,CAAC,GAAG,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC;gBAC9B,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC9C,KAAK,CAAC,IAAI,CAAC,KAAK,GAAG,MAAM,GAAG,CAAC,OAAO,GAAG,IAAI,EAAE,CAAC,CAAC;YACjD,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED,kEAAkE;AAClE,MAAM,UAAU,WAAW,CAAC,IAK3B;IACC,MAAM,EAAE,aAAa,EAAE,eAAe,EAAE,gBAAgB,EAAE,YAAY,EAAE,GACtE,IAAI,CAAC;IAEP,MAAM,YAAY,GAChB,YAAY,IAAI,YAAY,CAAC,MAAM,KAAK,QAAQ;QAC9C,CAAC,CAAC,kBAAkB,CAAC,YAAY,CAAC;QAClC,CAAC,CAAC,sCAAsC,CAAC;IAE7C,OAAO,8BAA8B,eAAe,MAAM,aAAa;;;EAGvE,gBAAgB;;;EAGhB,YAAY;;;;;qEAKuD,CAAC;AACtE,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function checkForUpdate(projectDir: string): Promise<void>;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { readFile, writeFile, mkdir } from "node:fs/promises";
|
|
2
|
+
import { join, dirname } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
5
|
+
function cachePath(projectDir) {
|
|
6
|
+
return join(projectDir, ".forge", "version-check.json");
|
|
7
|
+
}
|
|
8
|
+
async function readCache(projectDir) {
|
|
9
|
+
try {
|
|
10
|
+
const raw = await readFile(cachePath(projectDir), "utf-8");
|
|
11
|
+
return JSON.parse(raw);
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
async function writeCache(projectDir, cache) {
|
|
18
|
+
const p = cachePath(projectDir);
|
|
19
|
+
await mkdir(dirname(p), { recursive: true });
|
|
20
|
+
await writeFile(p, JSON.stringify(cache, null, 2), "utf-8");
|
|
21
|
+
}
|
|
22
|
+
function isCacheFresh(cache) {
|
|
23
|
+
const cacheTime = new Date(cache.timestamp).getTime();
|
|
24
|
+
const now = Date.now();
|
|
25
|
+
const ONE_DAY_MS = 24 * 60 * 60 * 1000;
|
|
26
|
+
return now - cacheTime < ONE_DAY_MS;
|
|
27
|
+
}
|
|
28
|
+
async function getCurrentVersion() {
|
|
29
|
+
const pkgPath = join(__dirname, "..", "..", "package.json");
|
|
30
|
+
const raw = await readFile(pkgPath, "utf-8");
|
|
31
|
+
const pkg = JSON.parse(raw);
|
|
32
|
+
return pkg.version;
|
|
33
|
+
}
|
|
34
|
+
async function fetchLatestVersion() {
|
|
35
|
+
const response = await fetch("https://registry.npmjs.org/forge-cc/latest");
|
|
36
|
+
if (!response.ok) {
|
|
37
|
+
throw new Error(`npm registry returned ${response.status}`);
|
|
38
|
+
}
|
|
39
|
+
const data = (await response.json());
|
|
40
|
+
return data.version;
|
|
41
|
+
}
|
|
42
|
+
export async function checkForUpdate(projectDir) {
|
|
43
|
+
// Check cache first
|
|
44
|
+
const cache = await readCache(projectDir);
|
|
45
|
+
if (cache && isCacheFresh(cache)) {
|
|
46
|
+
const current = await getCurrentVersion();
|
|
47
|
+
if (cache.latestVersion !== current) {
|
|
48
|
+
console.log(`forge-cc v${cache.latestVersion} is available (current: v${current}). Run: npm install -g forge-cc`);
|
|
49
|
+
}
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
// Fetch from registry
|
|
53
|
+
try {
|
|
54
|
+
const latestVersion = await fetchLatestVersion();
|
|
55
|
+
const current = await getCurrentVersion();
|
|
56
|
+
// Write cache
|
|
57
|
+
await writeCache(projectDir, {
|
|
58
|
+
timestamp: new Date().toISOString(),
|
|
59
|
+
latestVersion,
|
|
60
|
+
});
|
|
61
|
+
if (latestVersion !== current) {
|
|
62
|
+
console.log(`forge-cc v${latestVersion} is available (current: v${current}). Run: npm install -g forge-cc`);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
console.log(`forge-cc v${current} is up to date.`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
console.warn("[forge] Version check failed:", err);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=update.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"update.js","sourceRoot":"","sources":["../../src/runner/update.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAO1D,SAAS,SAAS,CAAC,UAAkB;IACnC,OAAO,IAAI,CAAC,UAAU,EAAE,QAAQ,EAAE,oBAAoB,CAAC,CAAC;AAC1D,CAAC;AAED,KAAK,UAAU,SAAS,CAAC,UAAkB;IACzC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,SAAS,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC,CAAC;QAC3D,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAiB,CAAC;IACzC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,KAAK,UAAU,UAAU,CAAC,UAAkB,EAAE,KAAmB;IAC/D,MAAM,CAAC,GAAG,SAAS,CAAC,UAAU,CAAC,CAAC;IAChC,MAAM,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7C,MAAM,SAAS,CAAC,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;AAC9D,CAAC;AAED,SAAS,YAAY,CAAC,KAAmB;IACvC,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,CAAC;IACtD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACvB,MAAM,UAAU,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;IACvC,OAAO,GAAG,GAAG,SAAS,GAAG,UAAU,CAAC;AACtC,CAAC;AAED,KAAK,UAAU,iBAAiB;IAC9B,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,cAAc,CAAC,CAAC;IAC5D,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAC7C,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAwB,CAAC;IACnD,OAAO,GAAG,CAAC,OAAO,CAAC;AACrB,CAAC;AAED,KAAK,UAAU,kBAAkB;IAC/B,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,4CAA4C,CAAC,CAAC;IAC3E,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,yBAAyB,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;IAC9D,CAAC;IACD,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAwB,CAAC;IAC5D,OAAO,IAAI,CAAC,OAAO,CAAC;AACtB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,UAAkB;IACrD,oBAAoB;IACpB,MAAM,KAAK,GAAG,MAAM,SAAS,CAAC,UAAU,CAAC,CAAC;IAC1C,IAAI,KAAK,IAAI,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC;QACjC,MAAM,OAAO,GAAG,MAAM,iBAAiB,EAAE,CAAC;QAC1C,IAAI,KAAK,CAAC,aAAa,KAAK,OAAO,EAAE,CAAC;YACpC,OAAO,CAAC,GAAG,CACT,aAAa,KAAK,CAAC,aAAa,4BAA4B,OAAO,iCAAiC,CACrG,CAAC;QACJ,CAAC;QACD,OAAO;IACT,CAAC;IAED,sBAAsB;IACtB,IAAI,CAAC;QACH,MAAM,aAAa,GAAG,MAAM,kBAAkB,EAAE,CAAC;QACjD,MAAM,OAAO,GAAG,MAAM,iBAAiB,EAAE,CAAC;QAE1C,cAAc;QACd,MAAM,UAAU,CAAC,UAAU,EAAE;YAC3B,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACnC,aAAa;SACd,CAAC,CAAC;QAEH,IAAI,aAAa,KAAK,OAAO,EAAE,CAAC;YAC9B,OAAO,CAAC,GAAG,CACT,aAAa,aAAa,4BAA4B,OAAO,iCAAiC,CAC/F,CAAC;QACJ,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,GAAG,CAAC,aAAa,OAAO,iBAAiB,CAAC,CAAC;QACrD,CAAC;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,IAAI,CAAC,+BAA+B,EAAE,GAAG,CAAC,CAAC;IACrD,CAAC;AACH,CAAC"}
|
package/dist/server.d.ts
CHANGED
|
@@ -1,2 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
/**
|
|
3
|
+
* Create an MCP server instance with the forge_run_pipeline tool registered.
|
|
4
|
+
* Exported for testing — call `startServer()` to run with stdio transport.
|
|
5
|
+
*/
|
|
6
|
+
export declare function createServer(): McpServer;
|
package/dist/server.js
CHANGED
|
@@ -1,109 +1,51 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
2
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
3
|
import { z } from "zod";
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
4
|
+
import { loadConfig } from "./config/loader.js";
|
|
5
|
+
import { registerGate, runPipeline, } from "./gates/index.js";
|
|
6
|
+
import { typesGate } from "./gates/types-gate.js";
|
|
7
|
+
import { lintGate } from "./gates/lint-gate.js";
|
|
8
|
+
import { testsGate } from "./gates/tests-gate.js";
|
|
9
|
+
/**
|
|
10
|
+
* Create an MCP server instance with the forge_run_pipeline tool registered.
|
|
11
|
+
* Exported for testing — call `startServer()` to run with stdio transport.
|
|
12
|
+
*/
|
|
13
|
+
export function createServer() {
|
|
14
|
+
const server = new McpServer({ name: "forge", version: "1.0.0" }, { capabilities: { tools: {} } });
|
|
15
|
+
// Register default gates once at server creation (not per-call, to avoid race conditions)
|
|
16
|
+
registerGate(typesGate);
|
|
17
|
+
registerGate(lintGate);
|
|
18
|
+
registerGate(testsGate);
|
|
19
|
+
server.tool("forge_run_pipeline", "Run the Forge verification pipeline (types, lint, tests)", {
|
|
20
|
+
projectDir: z.string().optional().describe("Project directory (defaults to cwd)"),
|
|
21
|
+
gates: z.array(z.string()).optional().describe("Filter to specific gates"),
|
|
22
|
+
}, async ({ projectDir, gates }) => {
|
|
23
|
+
const dir = projectDir ?? process.cwd();
|
|
24
|
+
const config = await loadConfig(dir);
|
|
25
|
+
if (gates && gates.length > 0) {
|
|
26
|
+
config.gates = gates;
|
|
27
|
+
}
|
|
28
|
+
const result = await runPipeline(config, dir);
|
|
29
|
+
return {
|
|
30
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
return server;
|
|
15
34
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
20
|
-
}
|
|
21
|
-
catch (err) {
|
|
22
|
-
return errorResponse(err);
|
|
23
|
-
}
|
|
24
|
-
});
|
|
25
|
-
server.tool("forge_verify_lint", "Run Biome linting checks", { projectDir: z.string().describe("Absolute path to project root") }, async ({ projectDir }) => {
|
|
26
|
-
try {
|
|
27
|
-
const result = await verifyLint(projectDir);
|
|
28
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
29
|
-
}
|
|
30
|
-
catch (err) {
|
|
31
|
-
return errorResponse(err);
|
|
32
|
-
}
|
|
33
|
-
});
|
|
34
|
-
server.tool("forge_verify_tests", "Run project test suite", { projectDir: z.string().describe("Absolute path to project root") }, async ({ projectDir }) => {
|
|
35
|
-
try {
|
|
36
|
-
const result = await verifyTests(projectDir);
|
|
37
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
38
|
-
}
|
|
39
|
-
catch (err) {
|
|
40
|
-
return errorResponse(err);
|
|
41
|
-
}
|
|
42
|
-
});
|
|
43
|
-
server.tool("forge_verify_visual", "Take screenshots and check for console errors", {
|
|
44
|
-
projectDir: z.string().describe("Absolute path to project root"),
|
|
45
|
-
pages: z.array(z.string()).default(["/"]).describe("Page paths to check"),
|
|
46
|
-
devServerCommand: z.string().optional().describe("Dev server start command"),
|
|
47
|
-
devServerPort: z.number().optional().describe("Dev server port"),
|
|
48
|
-
}, async ({ projectDir, pages, devServerCommand, devServerPort }) => {
|
|
49
|
-
try {
|
|
50
|
-
const result = await verifyVisual(projectDir, pages, { devServerCommand, devServerPort });
|
|
51
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
52
|
-
}
|
|
53
|
-
catch (err) {
|
|
54
|
-
return errorResponse(err);
|
|
55
|
-
}
|
|
56
|
-
});
|
|
57
|
-
server.tool("forge_verify_runtime", "Validate API endpoints return expected responses", {
|
|
58
|
-
projectDir: z.string().describe("Absolute path to project root"),
|
|
59
|
-
endpoints: z.array(z.string()).describe("API endpoints to test (e.g., 'GET /api/health')"),
|
|
60
|
-
devServerCommand: z.string().optional().describe("Dev server start command"),
|
|
61
|
-
devServerPort: z.number().optional().describe("Dev server port"),
|
|
62
|
-
}, async ({ projectDir, endpoints, devServerCommand, devServerPort }) => {
|
|
63
|
-
try {
|
|
64
|
-
const result = await verifyRuntime(projectDir, endpoints, { devServerCommand, devServerPort });
|
|
65
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
66
|
-
}
|
|
67
|
-
catch (err) {
|
|
68
|
-
return errorResponse(err);
|
|
69
|
-
}
|
|
70
|
-
});
|
|
71
|
-
server.tool("forge_verify_prd", "Check code changes against PRD acceptance criteria", {
|
|
72
|
-
projectDir: z.string().describe("Absolute path to project root"),
|
|
73
|
-
prdPath: z.string().describe("Path to PRD markdown file"),
|
|
74
|
-
baseBranch: z.string().default("main").describe("Base branch for diff comparison"),
|
|
75
|
-
}, async ({ projectDir, prdPath, baseBranch }) => {
|
|
76
|
-
try {
|
|
77
|
-
const result = await verifyPrd(projectDir, prdPath, baseBranch);
|
|
78
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
79
|
-
}
|
|
80
|
-
catch (err) {
|
|
81
|
-
return errorResponse(err);
|
|
82
|
-
}
|
|
83
|
-
});
|
|
84
|
-
server.tool("forge_run_pipeline", "Run full verification pipeline (all configured gates)", {
|
|
85
|
-
projectDir: z.string().describe("Absolute path to project root"),
|
|
86
|
-
gates: z.array(z.string()).optional().describe("Gates to run (default: types,lint,tests)"),
|
|
87
|
-
prdPath: z.string().optional().describe("Path to PRD file"),
|
|
88
|
-
maxIterations: z.number().optional().describe("Max retry iterations"),
|
|
89
|
-
}, async ({ projectDir, gates, prdPath, maxIterations }) => {
|
|
90
|
-
try {
|
|
91
|
-
const result = await runPipeline({
|
|
92
|
-
projectDir,
|
|
93
|
-
gates,
|
|
94
|
-
prdPath,
|
|
95
|
-
maxIterations,
|
|
96
|
-
});
|
|
97
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
98
|
-
}
|
|
99
|
-
catch (err) {
|
|
100
|
-
return errorResponse(err);
|
|
101
|
-
}
|
|
102
|
-
});
|
|
103
|
-
// Start server
|
|
104
|
-
async function main() {
|
|
35
|
+
/** Start the MCP server on stdio transport. */
|
|
36
|
+
async function startServer() {
|
|
37
|
+
const server = createServer();
|
|
105
38
|
const transport = new StdioServerTransport();
|
|
106
39
|
await server.connect(transport);
|
|
107
40
|
}
|
|
108
|
-
|
|
41
|
+
// Run when executed directly
|
|
42
|
+
const isMain = typeof process !== "undefined" &&
|
|
43
|
+
process.argv[1] &&
|
|
44
|
+
(process.argv[1].endsWith("/server.js") || process.argv[1].endsWith("\\server.js"));
|
|
45
|
+
if (isMain) {
|
|
46
|
+
startServer().catch((err) => {
|
|
47
|
+
process.stderr.write(`Forge MCP server error: ${String(err)}\n`);
|
|
48
|
+
process.exit(1);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
109
51
|
//# sourceMappingURL=server.js.map
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EACL,YAAY,EACZ,WAAW,GACZ,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAElD;;;GAGG;AACH,MAAM,UAAU,YAAY;IAC1B,MAAM,MAAM,GAAG,IAAI,SAAS,CAC1B,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,EACnC,EAAE,YAAY,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,CAChC,CAAC;IAEF,0FAA0F;IAC1F,YAAY,CAAC,SAAS,CAAC,CAAC;IACxB,YAAY,CAAC,QAAQ,CAAC,CAAC;IACvB,YAAY,CAAC,SAAS,CAAC,CAAC;IAExB,MAAM,CAAC,IAAI,CACT,oBAAoB,EACpB,0DAA0D,EAC1D;QACE,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,qCAAqC,CAAC;QACjF,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,0BAA0B,CAAC;KAC3E,EACD,KAAK,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,EAAE,EAAE;QAC9B,MAAM,GAAG,GAAG,UAAU,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;QACxC,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,GAAG,CAAC,CAAC;QAErC,IAAI,KAAK,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC9B,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC;QACvB,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAE9C,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;SAC5E,CAAC;IACJ,CAAC,CACF,CAAC;IAEF,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,+CAA+C;AAC/C,KAAK,UAAU,WAAW;IACxB,MAAM,MAAM,GAAG,YAAY,EAAE,CAAC;IAC9B,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAClC,CAAC;AAED,6BAA6B;AAC7B,MAAM,MAAM,GACV,OAAO,OAAO,KAAK,WAAW;IAC9B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IACf,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC;AAEtF,IAAI,MAAM,EAAE,CAAC;IACX,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;QAC1B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,2BAA2B,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACjE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC"}
|