forge-cc 0.1.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.
Files changed (105) hide show
  1. package/.forge.json +5 -0
  2. package/AGENTS.md +42 -0
  3. package/README.md +283 -0
  4. package/dist/cli.d.ts +2 -0
  5. package/dist/cli.js +148 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/config/loader.d.ts +2 -0
  8. package/dist/config/loader.js +44 -0
  9. package/dist/config/loader.js.map +1 -0
  10. package/dist/config/schema.d.ts +57 -0
  11. package/dist/config/schema.js +15 -0
  12. package/dist/config/schema.js.map +1 -0
  13. package/dist/gates/index.d.ts +11 -0
  14. package/dist/gates/index.js +106 -0
  15. package/dist/gates/index.js.map +1 -0
  16. package/dist/gates/lint-gate.d.ts +2 -0
  17. package/dist/gates/lint-gate.js +66 -0
  18. package/dist/gates/lint-gate.js.map +1 -0
  19. package/dist/gates/prd-gate.d.ts +7 -0
  20. package/dist/gates/prd-gate.js +193 -0
  21. package/dist/gates/prd-gate.js.map +1 -0
  22. package/dist/gates/runtime-gate.d.ts +5 -0
  23. package/dist/gates/runtime-gate.js +99 -0
  24. package/dist/gates/runtime-gate.js.map +1 -0
  25. package/dist/gates/tests-gate.d.ts +2 -0
  26. package/dist/gates/tests-gate.js +116 -0
  27. package/dist/gates/tests-gate.js.map +1 -0
  28. package/dist/gates/types-gate.d.ts +2 -0
  29. package/dist/gates/types-gate.js +59 -0
  30. package/dist/gates/types-gate.js.map +1 -0
  31. package/dist/gates/visual-gate.d.ts +6 -0
  32. package/dist/gates/visual-gate.js +118 -0
  33. package/dist/gates/visual-gate.js.map +1 -0
  34. package/dist/go/auto-chain.d.ts +107 -0
  35. package/dist/go/auto-chain.js +303 -0
  36. package/dist/go/auto-chain.js.map +1 -0
  37. package/dist/go/executor.d.ts +130 -0
  38. package/dist/go/executor.js +409 -0
  39. package/dist/go/executor.js.map +1 -0
  40. package/dist/go/finalize.d.ts +58 -0
  41. package/dist/go/finalize.js +200 -0
  42. package/dist/go/finalize.js.map +1 -0
  43. package/dist/go/linear-sync.d.ts +75 -0
  44. package/dist/go/linear-sync.js +239 -0
  45. package/dist/go/linear-sync.js.map +1 -0
  46. package/dist/go/verify-loop.d.ts +47 -0
  47. package/dist/go/verify-loop.js +172 -0
  48. package/dist/go/verify-loop.js.map +1 -0
  49. package/dist/hooks/pre-commit.d.ts +5 -0
  50. package/dist/hooks/pre-commit.js +69 -0
  51. package/dist/hooks/pre-commit.js.map +1 -0
  52. package/dist/linear/client.d.ts +108 -0
  53. package/dist/linear/client.js +388 -0
  54. package/dist/linear/client.js.map +1 -0
  55. package/dist/linear/issues.d.ts +20 -0
  56. package/dist/linear/issues.js +39 -0
  57. package/dist/linear/issues.js.map +1 -0
  58. package/dist/linear/milestones.d.ts +11 -0
  59. package/dist/linear/milestones.js +32 -0
  60. package/dist/linear/milestones.js.map +1 -0
  61. package/dist/linear/projects.d.ts +16 -0
  62. package/dist/linear/projects.js +50 -0
  63. package/dist/linear/projects.js.map +1 -0
  64. package/dist/reporter/human.d.ts +2 -0
  65. package/dist/reporter/human.js +63 -0
  66. package/dist/reporter/human.js.map +1 -0
  67. package/dist/reporter/json.d.ts +2 -0
  68. package/dist/reporter/json.js +4 -0
  69. package/dist/reporter/json.js.map +1 -0
  70. package/dist/server.d.ts +2 -0
  71. package/dist/server.js +109 -0
  72. package/dist/server.js.map +1 -0
  73. package/dist/spec/generator.d.ts +14 -0
  74. package/dist/spec/generator.js +206 -0
  75. package/dist/spec/generator.js.map +1 -0
  76. package/dist/spec/interview.d.ts +104 -0
  77. package/dist/spec/interview.js +342 -0
  78. package/dist/spec/interview.js.map +1 -0
  79. package/dist/spec/linear-sync.d.ts +48 -0
  80. package/dist/spec/linear-sync.js +125 -0
  81. package/dist/spec/linear-sync.js.map +1 -0
  82. package/dist/spec/scanner.d.ts +45 -0
  83. package/dist/spec/scanner.js +473 -0
  84. package/dist/spec/scanner.js.map +1 -0
  85. package/dist/spec/templates.d.ts +345 -0
  86. package/dist/spec/templates.js +86 -0
  87. package/dist/spec/templates.js.map +1 -0
  88. package/dist/state/reader.d.ts +29 -0
  89. package/dist/state/reader.js +116 -0
  90. package/dist/state/reader.js.map +1 -0
  91. package/dist/state/writer.d.ts +60 -0
  92. package/dist/state/writer.js +222 -0
  93. package/dist/state/writer.js.map +1 -0
  94. package/dist/types.d.ts +64 -0
  95. package/dist/types.js +2 -0
  96. package/dist/types.js.map +1 -0
  97. package/dist/utils/browser.d.ts +10 -0
  98. package/dist/utils/browser.js +89 -0
  99. package/dist/utils/browser.js.map +1 -0
  100. package/hooks/pre-commit-verify.js +103 -0
  101. package/package.json +68 -0
  102. package/skills/README.md +33 -0
  103. package/skills/forge-go.md +332 -0
  104. package/skills/forge-spec.md +251 -0
  105. package/skills/forge-triage.md +133 -0
@@ -0,0 +1,222 @@
1
+ import { readFile, writeFile, mkdir } from "node:fs/promises";
2
+ import { execSync } from "node:child_process";
3
+ import { join, dirname } from "node:path";
4
+ import { readRoadmapProgress } from "./reader.js";
5
+ // ---------------------------------------------------------------------------
6
+ // Helpers
7
+ // ---------------------------------------------------------------------------
8
+ async function ensureDir(filePath) {
9
+ await mkdir(dirname(filePath), { recursive: true });
10
+ }
11
+ function branchSlug(branch) {
12
+ return branch.replace(/\//g, "-").toLowerCase();
13
+ }
14
+ // ---------------------------------------------------------------------------
15
+ // writeStateFile
16
+ // ---------------------------------------------------------------------------
17
+ export async function writeStateFile(projectDir, info) {
18
+ const milestoneRows = info.milestoneTable
19
+ .map((m) => `| ${m.number} | ${m.name} | ${m.status} |`)
20
+ .join("\n");
21
+ const nextActions = info.nextActions
22
+ .map((a, i) => `${i + 1}. ${a}`)
23
+ .join("\n");
24
+ const content = `# ${info.project} — Project State
25
+
26
+ ## Current Position
27
+ - **Project:** ${info.project} (build phase)
28
+ - **Milestone:** Milestone ${info.milestone.number} — ${info.milestone.name}
29
+ - **Branch:** ${info.branch}
30
+ - **Active PRD:** \`${info.activePrd}\`
31
+ - **Last Session:** ${info.lastSession}
32
+
33
+ ## Milestone Progress
34
+ | Milestone | Name | Status |
35
+ |-----------|------|--------|
36
+ ${milestoneRows}
37
+
38
+ ## Next Actions
39
+ ${nextActions}
40
+ `;
41
+ const filePath = join(projectDir, ".planning", "STATE.md");
42
+ await ensureDir(filePath);
43
+ await writeFile(filePath, content, "utf-8");
44
+ }
45
+ // ---------------------------------------------------------------------------
46
+ // updateRoadmapMilestone
47
+ // ---------------------------------------------------------------------------
48
+ export async function updateRoadmapMilestone(projectDir, milestoneNumber, status) {
49
+ const filePath = join(projectDir, ".planning", "ROADMAP.md");
50
+ const raw = await readFile(filePath, "utf-8");
51
+ // Match the specific milestone row and replace its status
52
+ const pattern = new RegExp(`^(\\|\\s*${milestoneNumber}\\s*\\|\\s*.+?\\s*\\|)\\s*.+?\\s*\\|`, "m");
53
+ const match = raw.match(pattern);
54
+ if (!match) {
55
+ throw new Error(`Milestone ${milestoneNumber} not found in ROADMAP.md table`);
56
+ }
57
+ const updated = raw.replace(pattern, `$1 ${status} |`);
58
+ await writeFile(filePath, updated, "utf-8");
59
+ }
60
+ // ---------------------------------------------------------------------------
61
+ // writeSessionMemory
62
+ // ---------------------------------------------------------------------------
63
+ export async function writeSessionMemory(projectDir, branch, data) {
64
+ const slug = branchSlug(branch);
65
+ const filePath = join(projectDir, ".claude", "memory", `session-${slug}.md`);
66
+ const content = `# Session State
67
+ **Date:** ${data.date}
68
+ **Developer:** ${data.developer}
69
+ **Branch:** ${branch}
70
+ **Working On:** ${data.workingOn}
71
+ **Status:** ${data.status}
72
+ **Next:** ${data.next}
73
+ **Blockers:** ${data.blockers}
74
+ `;
75
+ await ensureDir(filePath);
76
+ await writeFile(filePath, content, "utf-8");
77
+ }
78
+ // ---------------------------------------------------------------------------
79
+ // commitMilestoneWork — commits and optionally pushes milestone work
80
+ // ---------------------------------------------------------------------------
81
+ export function commitMilestoneWork(options) {
82
+ const { projectDir, milestoneNumber, milestoneName, filesToStage, push, branch, } = options;
83
+ if (filesToStage.length === 0) {
84
+ throw new Error("filesToStage must contain at least one file — never use git add .");
85
+ }
86
+ // Check if git is available
87
+ try {
88
+ execSync("git --version", { cwd: projectDir, stdio: "pipe" });
89
+ }
90
+ catch {
91
+ throw new Error("git is not available on this system. Cannot commit milestone work.");
92
+ }
93
+ // Check for detached HEAD
94
+ try {
95
+ const headRef = execSync("git symbolic-ref HEAD", {
96
+ cwd: projectDir,
97
+ encoding: "utf-8",
98
+ stdio: "pipe",
99
+ }).trim();
100
+ if (!headRef) {
101
+ throw new Error("Detached HEAD detected — cannot commit. Check out a branch first.");
102
+ }
103
+ }
104
+ catch (err) {
105
+ if (err instanceof Error && err.message.includes("Detached HEAD")) {
106
+ throw err;
107
+ }
108
+ // git symbolic-ref fails on detached HEAD with exit code 128
109
+ throw new Error("Detached HEAD detected — cannot commit. Check out a branch first.");
110
+ }
111
+ // Stage only the specified files, skipping files that don't exist
112
+ for (const file of filesToStage) {
113
+ try {
114
+ execSync(`git add ${JSON.stringify(file)}`, {
115
+ cwd: projectDir,
116
+ stdio: "pipe",
117
+ });
118
+ }
119
+ catch {
120
+ // File may not exist or be outside the repo — skip and continue
121
+ console.warn(`Warning: Could not stage file "${file}" — skipping.`);
122
+ }
123
+ }
124
+ // Commit with a descriptive message
125
+ const commitMessage = `feat: ${milestoneName} (Milestone ${milestoneNumber})`;
126
+ try {
127
+ execSync(`git commit -m ${JSON.stringify(commitMessage)}`, {
128
+ cwd: projectDir,
129
+ stdio: "pipe",
130
+ });
131
+ }
132
+ catch (err) {
133
+ const msg = err instanceof Error ? err.message : String(err);
134
+ throw new Error(`git commit failed: ${msg}`);
135
+ }
136
+ // Read back the commit SHA
137
+ const commitSha = execSync("git rev-parse HEAD", {
138
+ cwd: projectDir,
139
+ encoding: "utf-8",
140
+ }).trim();
141
+ // Optionally push to remote
142
+ let pushed = false;
143
+ if (push && branch) {
144
+ try {
145
+ execSync(`git push origin ${branch}`, {
146
+ cwd: projectDir,
147
+ stdio: "pipe",
148
+ });
149
+ pushed = true;
150
+ }
151
+ catch (err) {
152
+ const msg = err instanceof Error ? err.message : String(err);
153
+ console.warn(`Warning: git push failed: ${msg}. Commit was created locally.`);
154
+ }
155
+ }
156
+ return { commitSha, pushed };
157
+ }
158
+ // ---------------------------------------------------------------------------
159
+ // isLastMilestone — detects if this is the final pending milestone
160
+ // ---------------------------------------------------------------------------
161
+ export async function isLastMilestone(projectDir, milestoneNumber) {
162
+ const roadmap = await readRoadmapProgress(projectDir);
163
+ if (!roadmap || roadmap.milestones.length === 0) {
164
+ return true; // No roadmap data — treat as last by default
165
+ }
166
+ const maxMilestone = Math.max(...roadmap.milestones.map((m) => m.number));
167
+ // If this IS the highest milestone number, it's the last
168
+ if (milestoneNumber >= maxMilestone) {
169
+ return true;
170
+ }
171
+ // If all milestones after this one are already complete, this is effectively last
172
+ const remaining = roadmap.milestones.filter((m) => m.number > milestoneNumber &&
173
+ !m.status.toLowerCase().startsWith("complete"));
174
+ return remaining.length === 0;
175
+ }
176
+ // ---------------------------------------------------------------------------
177
+ // updateMilestoneProgress — updates all state docs after milestone completion
178
+ // ---------------------------------------------------------------------------
179
+ export async function updateMilestoneProgress(options) {
180
+ const { projectDir, project, milestoneNumber, milestoneName, branch, activePrd, developer, nextMilestone, milestoneTable, } = options;
181
+ const today = new Date().toISOString().slice(0, 10);
182
+ // 1. Mark this milestone as complete in ROADMAP.md
183
+ await updateRoadmapMilestone(projectDir, milestoneNumber, `Complete (${today})`);
184
+ // 2. Build next actions based on whether there's a next milestone
185
+ const nextActions = nextMilestone
186
+ ? [
187
+ `Begin Milestone ${nextMilestone.number} — ${nextMilestone.name}`,
188
+ "Read PRD for next milestone scope",
189
+ "Spawn agent team for next milestone",
190
+ ]
191
+ : [
192
+ "All milestones complete — final review and cleanup",
193
+ "Merge feature branch to main",
194
+ "Archive planning docs",
195
+ ];
196
+ // 3. Update STATE.md with current position
197
+ const stateTarget = nextMilestone ?? {
198
+ number: milestoneNumber,
199
+ name: milestoneName,
200
+ };
201
+ await writeStateFile(projectDir, {
202
+ project,
203
+ milestone: stateTarget,
204
+ branch,
205
+ activePrd,
206
+ lastSession: today,
207
+ milestoneTable,
208
+ nextActions,
209
+ });
210
+ // 4. Write session memory for this branch
211
+ await writeSessionMemory(projectDir, branch, {
212
+ date: today,
213
+ developer,
214
+ workingOn: `Milestone ${milestoneNumber} — ${milestoneName}`,
215
+ status: "Complete",
216
+ next: nextMilestone
217
+ ? `Milestone ${nextMilestone.number} — ${nextMilestone.name}`
218
+ : "All milestones complete",
219
+ blockers: "None",
220
+ });
221
+ }
222
+ //# sourceMappingURL=writer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"writer.js","sourceRoot":"","sources":["../../src/state/writer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAyBlD,8EAA8E;AAC9E,UAAU;AACV,8EAA8E;AAE9E,KAAK,UAAU,SAAS,CAAC,QAAgB;IACvC,MAAM,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;AACtD,CAAC;AAED,SAAS,UAAU,CAAC,MAAc;IAChC,OAAO,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC;AAClD,CAAC;AAED,8EAA8E;AAC9E,iBAAiB;AACjB,8EAA8E;AAE9E,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,UAAkB,EAClB,IAAqB;IAErB,MAAM,aAAa,GAAG,IAAI,CAAC,cAAc;SACtC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,MAAM,MAAM,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,MAAM,IAAI,CAAC;SACvD,IAAI,CAAC,IAAI,CAAC,CAAC;IAEd,MAAM,WAAW,GAAG,IAAI,CAAC,WAAW;SACjC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;SAC/B,IAAI,CAAC,IAAI,CAAC,CAAC;IAEd,MAAM,OAAO,GAAG,KAAK,IAAI,CAAC,OAAO;;;iBAGlB,IAAI,CAAC,OAAO;6BACA,IAAI,CAAC,SAAS,CAAC,MAAM,MAAM,IAAI,CAAC,SAAS,CAAC,IAAI;gBAC3D,IAAI,CAAC,MAAM;sBACL,IAAI,CAAC,SAAS;sBACd,IAAI,CAAC,WAAW;;;;;EAKpC,aAAa;;;EAGb,WAAW;CACZ,CAAC;IAEA,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,EAAE,WAAW,EAAE,UAAU,CAAC,CAAC;IAC3D,MAAM,SAAS,CAAC,QAAQ,CAAC,CAAC;IAC1B,MAAM,SAAS,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;AAC9C,CAAC;AAED,8EAA8E;AAC9E,yBAAyB;AACzB,8EAA8E;AAE9E,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,UAAkB,EAClB,eAAuB,EACvB,MAAc;IAEd,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,EAAE,WAAW,EAAE,YAAY,CAAC,CAAC;IAC7D,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAE9C,0DAA0D;IAC1D,MAAM,OAAO,GAAG,IAAI,MAAM,CACxB,YAAY,eAAe,sCAAsC,EACjE,GAAG,CACJ,CAAC;IAEF,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACjC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CACb,aAAa,eAAe,gCAAgC,CAC7D,CAAC;IACJ,CAAC;IAED,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC,OAAO,EAAE,MAAM,MAAM,IAAI,CAAC,CAAC;IACvD,MAAM,SAAS,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;AAC9C,CAAC;AAED,8EAA8E;AAC9E,qBAAqB;AACrB,8EAA8E;AAE9E,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,UAAkB,EAClB,MAAc,EACd,IAAwB;IAExB,MAAM,IAAI,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;IAChC,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,WAAW,IAAI,KAAK,CAAC,CAAC;IAE7E,MAAM,OAAO,GAAG;YACN,IAAI,CAAC,IAAI;iBACJ,IAAI,CAAC,SAAS;cACjB,MAAM;kBACF,IAAI,CAAC,SAAS;cAClB,IAAI,CAAC,MAAM;YACb,IAAI,CAAC,IAAI;gBACL,IAAI,CAAC,QAAQ;CAC5B,CAAC;IAEA,MAAM,SAAS,CAAC,QAAQ,CAAC,CAAC;IAC1B,MAAM,SAAS,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;AAC9C,CAAC;AAgCD,8EAA8E;AAC9E,qEAAqE;AACrE,8EAA8E;AAE9E,MAAM,UAAU,mBAAmB,CAAC,OAAsB;IACxD,MAAM,EACJ,UAAU,EACV,eAAe,EACf,aAAa,EACb,YAAY,EACZ,IAAI,EACJ,MAAM,GACP,GAAG,OAAO,CAAC;IAEZ,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC9B,MAAM,IAAI,KAAK,CACb,mEAAmE,CACpE,CAAC;IACJ,CAAC;IAED,4BAA4B;IAC5B,IAAI,CAAC;QACH,QAAQ,CAAC,eAAe,EAAE,EAAE,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;IAChE,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,oEAAoE,CAAC,CAAC;IACxF,CAAC;IAED,0BAA0B;IAC1B,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,QAAQ,CAAC,uBAAuB,EAAE;YAChD,GAAG,EAAE,UAAU;YACf,QAAQ,EAAE,OAAO;YACjB,KAAK,EAAE,MAAM;SACd,CAAC,CAAC,IAAI,EAAE,CAAC;QACV,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CAAC,mEAAmE,CAAC,CAAC;QACvF,CAAC;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,GAAG,YAAY,KAAK,IAAI,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC;YAClE,MAAM,GAAG,CAAC;QACZ,CAAC;QACD,6DAA6D;QAC7D,MAAM,IAAI,KAAK,CAAC,mEAAmE,CAAC,CAAC;IACvF,CAAC;IAED,kEAAkE;IAClE,KAAK,MAAM,IAAI,IAAI,YAAY,EAAE,CAAC;QAChC,IAAI,CAAC;YACH,QAAQ,CAAC,WAAW,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,EAAE;gBAC1C,GAAG,EAAE,UAAU;gBACf,KAAK,EAAE,MAAM;aACd,CAAC,CAAC;QACL,CAAC;QAAC,MAAM,CAAC;YACP,gEAAgE;YAChE,OAAO,CAAC,IAAI,CAAC,kCAAkC,IAAI,eAAe,CAAC,CAAC;QACtE,CAAC;IACH,CAAC;IAED,oCAAoC;IACpC,MAAM,aAAa,GAAG,SAAS,aAAa,eAAe,eAAe,GAAG,CAAC;IAC9E,IAAI,CAAC;QACH,QAAQ,CAAC,iBAAiB,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC,EAAE,EAAE;YACzD,GAAG,EAAE,UAAU;YACf,KAAK,EAAE,MAAM;SACd,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7D,MAAM,IAAI,KAAK,CAAC,sBAAsB,GAAG,EAAE,CAAC,CAAC;IAC/C,CAAC;IAED,2BAA2B;IAC3B,MAAM,SAAS,GAAG,QAAQ,CAAC,oBAAoB,EAAE;QAC/C,GAAG,EAAE,UAAU;QACf,QAAQ,EAAE,OAAO;KAClB,CAAC,CAAC,IAAI,EAAE,CAAC;IAEV,4BAA4B;IAC5B,IAAI,MAAM,GAAG,KAAK,CAAC;IACnB,IAAI,IAAI,IAAI,MAAM,EAAE,CAAC;QACnB,IAAI,CAAC;YACH,QAAQ,CAAC,mBAAmB,MAAM,EAAE,EAAE;gBACpC,GAAG,EAAE,UAAU;gBACf,KAAK,EAAE,MAAM;aACd,CAAC,CAAC;YACH,MAAM,GAAG,IAAI,CAAC;QAChB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC7D,OAAO,CAAC,IAAI,CAAC,6BAA6B,GAAG,+BAA+B,CAAC,CAAC;QAChF,CAAC;IACH,CAAC;IAED,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC;AAC/B,CAAC;AAED,8EAA8E;AAC9E,mEAAmE;AACnE,8EAA8E;AAE9E,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,UAAkB,EAClB,eAAuB;IAEvB,MAAM,OAAO,GAAG,MAAM,mBAAmB,CAAC,UAAU,CAAC,CAAC;IACtD,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAChD,OAAO,IAAI,CAAC,CAAC,6CAA6C;IAC5D,CAAC;IAED,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;IAE1E,yDAAyD;IACzD,IAAI,eAAe,IAAI,YAAY,EAAE,CAAC;QACpC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,kFAAkF;IAClF,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,MAAM,CACzC,CAAC,CAAC,EAAE,EAAE,CACJ,CAAC,CAAC,MAAM,GAAG,eAAe;QAC1B,CAAC,CAAC,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,CACjD,CAAC;IAEF,OAAO,SAAS,CAAC,MAAM,KAAK,CAAC,CAAC;AAChC,CAAC;AAED,8EAA8E;AAC9E,8EAA8E;AAC9E,8EAA8E;AAE9E,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAC3C,OAA+B;IAE/B,MAAM,EACJ,UAAU,EACV,OAAO,EACP,eAAe,EACf,aAAa,EACb,MAAM,EACN,SAAS,EACT,SAAS,EACT,aAAa,EACb,cAAc,GACf,GAAG,OAAO,CAAC;IAEZ,MAAM,KAAK,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAEpD,mDAAmD;IACnD,MAAM,sBAAsB,CAC1B,UAAU,EACV,eAAe,EACf,aAAa,KAAK,GAAG,CACtB,CAAC;IAEF,kEAAkE;IAClE,MAAM,WAAW,GAAa,aAAa;QACzC,CAAC,CAAC;YACE,mBAAmB,aAAa,CAAC,MAAM,MAAM,aAAa,CAAC,IAAI,EAAE;YACjE,mCAAmC;YACnC,qCAAqC;SACtC;QACH,CAAC,CAAC;YACE,oDAAoD;YACpD,8BAA8B;YAC9B,uBAAuB;SACxB,CAAC;IAEN,2CAA2C;IAC3C,MAAM,WAAW,GAAG,aAAa,IAAI;QACnC,MAAM,EAAE,eAAe;QACvB,IAAI,EAAE,aAAa;KACpB,CAAC;IACF,MAAM,cAAc,CAAC,UAAU,EAAE;QAC/B,OAAO;QACP,SAAS,EAAE,WAAW;QACtB,MAAM;QACN,SAAS;QACT,WAAW,EAAE,KAAK;QAClB,cAAc;QACd,WAAW;KACZ,CAAC,CAAC;IAEH,0CAA0C;IAC1C,MAAM,kBAAkB,CAAC,UAAU,EAAE,MAAM,EAAE;QAC3C,IAAI,EAAE,KAAK;QACX,SAAS;QACT,SAAS,EAAE,aAAa,eAAe,MAAM,aAAa,EAAE;QAC5D,MAAM,EAAE,UAAU;QAClB,IAAI,EAAE,aAAa;YACjB,CAAC,CAAC,aAAa,aAAa,CAAC,MAAM,MAAM,aAAa,CAAC,IAAI,EAAE;YAC7D,CAAC,CAAC,yBAAyB;QAC7B,QAAQ,EAAE,MAAM;KACjB,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,64 @@
1
+ /** Structured error from a verification gate */
2
+ export interface GateError {
3
+ file?: string;
4
+ line?: number;
5
+ message: string;
6
+ remediation?: string;
7
+ }
8
+ /** Result from a single verification gate */
9
+ export interface GateResult {
10
+ gate: string;
11
+ passed: boolean;
12
+ errors: GateError[];
13
+ warnings: string[];
14
+ duration_ms: number;
15
+ }
16
+ /** Extended result for visual validation with screenshots */
17
+ export interface VisualResult extends GateResult {
18
+ screenshots: Array<{
19
+ page: string;
20
+ path: string;
21
+ }>;
22
+ consoleErrors: string[];
23
+ }
24
+ /** Input for the full verification pipeline */
25
+ export interface PipelineInput {
26
+ projectDir: string;
27
+ gates?: string[];
28
+ prdPath?: string;
29
+ milestoneType?: "ui" | "data" | "mixed";
30
+ pages?: string[];
31
+ apiEndpoints?: string[];
32
+ maxIterations?: number;
33
+ devServerCommand?: string;
34
+ devServerPort?: number;
35
+ baseBranch?: string;
36
+ }
37
+ /** Result from the full verification pipeline */
38
+ export interface PipelineResult {
39
+ passed: boolean;
40
+ iteration: number;
41
+ maxIterations: number;
42
+ gates: GateResult[];
43
+ report: string;
44
+ }
45
+ /** Configuration from .forge.json */
46
+ export interface ForgeConfig {
47
+ gates: string[];
48
+ maxIterations: number;
49
+ verifyFreshness: number;
50
+ devServer?: {
51
+ command: string;
52
+ port: number;
53
+ readyPattern?: string;
54
+ };
55
+ prdPath?: string;
56
+ linearProject?: string;
57
+ }
58
+ /** Verification cache written to .forge/last-verify.json */
59
+ export interface VerifyCache {
60
+ passed: boolean;
61
+ timestamp: string;
62
+ gates: GateResult[];
63
+ branch: string;
64
+ }
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,10 @@
1
+ import { type Browser } from "playwright";
2
+ import { type ChildProcess } from "node:child_process";
3
+ export declare function getBrowser(): Promise<Browser>;
4
+ export declare function closeBrowser(): Promise<void>;
5
+ export declare function startDevServer(projectDir: string, command?: string, port?: number): Promise<{
6
+ port: number;
7
+ process: ChildProcess;
8
+ }>;
9
+ export declare function stopDevServer(): Promise<void>;
10
+ export declare function waitForServer(port: number, timeoutMs?: number): Promise<boolean>;
@@ -0,0 +1,89 @@
1
+ import { chromium } from "playwright";
2
+ import { spawn, execSync } from "node:child_process";
3
+ import { setTimeout } from "node:timers/promises";
4
+ let browserInstance = null;
5
+ let devServerProcess = null;
6
+ export async function getBrowser() {
7
+ if (!browserInstance || !browserInstance.isConnected()) {
8
+ try {
9
+ browserInstance = await chromium.launch({ headless: true });
10
+ }
11
+ catch (err) {
12
+ const message = err instanceof Error ? err.message : String(err);
13
+ if (message.includes("Executable doesn't exist") || message.includes("browserType.launch")) {
14
+ throw new Error(`Playwright browsers are not installed. Run "npx playwright install chromium" to fix this. Original error: ${message}`);
15
+ }
16
+ throw err;
17
+ }
18
+ }
19
+ return browserInstance;
20
+ }
21
+ export async function closeBrowser() {
22
+ if (browserInstance) {
23
+ await browserInstance.close();
24
+ browserInstance = null;
25
+ }
26
+ }
27
+ export async function startDevServer(projectDir, command, port) {
28
+ const resolvedCommand = command ?? "npm run dev";
29
+ const resolvedPort = port ?? 3000;
30
+ // Kill any existing dev server before starting a new one
31
+ await stopDevServer();
32
+ devServerProcess = spawn(resolvedCommand, {
33
+ cwd: projectDir,
34
+ shell: true,
35
+ stdio: "pipe",
36
+ });
37
+ // Wait for the server to become reachable
38
+ const ready = await waitForServer(resolvedPort);
39
+ if (!ready) {
40
+ await stopDevServer();
41
+ throw new Error(`Dev server failed to start on port ${resolvedPort} within timeout`);
42
+ }
43
+ return { port: resolvedPort, process: devServerProcess };
44
+ }
45
+ export async function stopDevServer() {
46
+ if (devServerProcess) {
47
+ const proc = devServerProcess;
48
+ devServerProcess = null;
49
+ try {
50
+ if (process.platform === "win32" && proc.pid) {
51
+ // On Windows, proc.kill() doesn't kill the child process tree.
52
+ // Use taskkill with /T (tree) /F (force) to kill the process and its children.
53
+ try {
54
+ execSync(`taskkill /pid ${proc.pid} /T /F`, { stdio: "pipe" });
55
+ }
56
+ catch {
57
+ // taskkill may fail if the process already exited — fall back to proc.kill()
58
+ try {
59
+ proc.kill();
60
+ }
61
+ catch { /* already exited */ }
62
+ }
63
+ }
64
+ else {
65
+ proc.kill();
66
+ }
67
+ }
68
+ catch {
69
+ // Process may have already exited — ignore
70
+ }
71
+ // Brief wait for cleanup
72
+ await setTimeout(500);
73
+ }
74
+ }
75
+ export async function waitForServer(port, timeoutMs) {
76
+ const deadline = Date.now() + (timeoutMs ?? 30_000);
77
+ while (Date.now() < deadline) {
78
+ try {
79
+ await fetch(`http://localhost:${port}`);
80
+ return true;
81
+ }
82
+ catch {
83
+ // Server not ready yet — wait and retry
84
+ await setTimeout(1000);
85
+ }
86
+ }
87
+ return false;
88
+ }
89
+ //# sourceMappingURL=browser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser.js","sourceRoot":"","sources":["../../src/utils/browser.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,103 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { readFileSync, existsSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ import { execSync } from "node:child_process";
6
+
7
+ // Read hook input from stdin
8
+ let input = "";
9
+ process.stdin.setEncoding("utf-8");
10
+ process.stdin.on("data", (chunk) => {
11
+ input += chunk;
12
+ });
13
+ process.stdin.on("end", () => {
14
+ try {
15
+ const hookData = JSON.parse(input);
16
+ const result = checkPreCommit(hookData);
17
+ console.log(JSON.stringify(result));
18
+ } catch {
19
+ // On any error, allow (don't block the user's work)
20
+ console.log(JSON.stringify({ decision: "allow" }));
21
+ }
22
+ });
23
+
24
+ function checkPreCommit(hookData) {
25
+ // Only intercept Bash calls with "git commit" in the command
26
+ if (hookData.tool_name !== "Bash") {
27
+ return { decision: "allow" };
28
+ }
29
+
30
+ const command = hookData.tool_input?.command ?? "";
31
+ if (!command.includes("git commit")) {
32
+ return { decision: "allow" };
33
+ }
34
+
35
+ const projectDir = process.cwd();
36
+
37
+ // Check 1: Wrong branch protection
38
+ try {
39
+ const branch = execSync("git branch --show-current", {
40
+ encoding: "utf-8",
41
+ }).trim();
42
+ if (branch === "main" || branch === "master") {
43
+ return {
44
+ decision: "block",
45
+ reason: `Forge: Cannot commit directly to ${branch}. Create a feature branch first.`,
46
+ };
47
+ }
48
+ } catch {
49
+ // Can't determine branch — allow
50
+ }
51
+
52
+ // Check 2: Verify cache exists
53
+ const cachePath = join(projectDir, ".forge", "last-verify.json");
54
+ if (!existsSync(cachePath)) {
55
+ return {
56
+ decision: "block",
57
+ reason:
58
+ "Forge: No verification found. Run `npx forge verify` before committing.",
59
+ };
60
+ }
61
+
62
+ try {
63
+ const cache = JSON.parse(readFileSync(cachePath, "utf-8"));
64
+
65
+ // Check 3: Did verification pass?
66
+ if (!cache.passed) {
67
+ return {
68
+ decision: "block",
69
+ reason:
70
+ "Forge: Last verification FAILED. Fix errors and run `npx forge verify` again.",
71
+ };
72
+ }
73
+
74
+ // Check 4: Is it fresh? (default 10 minutes = 600000ms)
75
+ let freshness = 600_000;
76
+ const configPath = join(projectDir, ".forge.json");
77
+ if (existsSync(configPath)) {
78
+ try {
79
+ const config = JSON.parse(readFileSync(configPath, "utf-8"));
80
+ if (config.verifyFreshness) freshness = config.verifyFreshness;
81
+ } catch {
82
+ /* use default */
83
+ }
84
+ }
85
+
86
+ const age = Date.now() - new Date(cache.timestamp).getTime();
87
+ if (age > freshness) {
88
+ const ageMin = Math.round(age / 60_000);
89
+ return {
90
+ decision: "block",
91
+ reason: `Forge: Verification is stale (${ageMin}min old). Run \`npx forge verify\` again.`,
92
+ };
93
+ }
94
+
95
+ return { decision: "allow" };
96
+ } catch {
97
+ return {
98
+ decision: "block",
99
+ reason:
100
+ "Forge: Could not read verification cache. Run `npx forge verify`.",
101
+ };
102
+ }
103
+ }
package/package.json ADDED
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "forge-cc",
3
+ "version": "0.1.0",
4
+ "description": "Pre-PR verification harness for Claude Code agents — gate runner + CLI + MCP server",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": "Troy Hoffman",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/troyhoffman/forge-cc.git"
11
+ },
12
+ "keywords": [
13
+ "forge",
14
+ "verification",
15
+ "claude-code",
16
+ "mcp",
17
+ "pre-commit",
18
+ "lint",
19
+ "typescript"
20
+ ],
21
+ "engines": {
22
+ "node": ">=18"
23
+ },
24
+ "main": "dist/gates/index.js",
25
+ "exports": {
26
+ ".": "./dist/gates/index.js",
27
+ "./cli": "./dist/cli.js",
28
+ "./server": "./dist/server.js"
29
+ },
30
+ "bin": {
31
+ "forge": "dist/cli.js"
32
+ },
33
+ "files": [
34
+ "dist/",
35
+ "skills/",
36
+ "hooks/",
37
+ ".forge.json",
38
+ "README.md",
39
+ "AGENTS.md"
40
+ ],
41
+ "scripts": {
42
+ "build": "tsc",
43
+ "dev": "tsc --watch",
44
+ "start": "node dist/index.js",
45
+ "test": "vitest run",
46
+ "test:watch": "vitest",
47
+ "verify": "node dist/cli.js verify",
48
+ "prepublishOnly": "npm run build"
49
+ },
50
+ "dependencies": {
51
+ "@modelcontextprotocol/sdk": "^1.12.0",
52
+ "commander": "^13.0.0",
53
+ "zod": "^3.24.0"
54
+ },
55
+ "peerDependencies": {
56
+ "playwright": ">=1.40.0"
57
+ },
58
+ "peerDependenciesMeta": {
59
+ "playwright": {
60
+ "optional": true
61
+ }
62
+ },
63
+ "devDependencies": {
64
+ "@types/node": "^22.0.0",
65
+ "typescript": "^5.7.0",
66
+ "vitest": "^3.0.0"
67
+ }
68
+ }
@@ -0,0 +1,33 @@
1
+ # forge-cc Skills
2
+
3
+ Skills are markdown instruction files that Claude Code discovers and executes. When a user types a skill command (e.g., `/forge:triage`), Claude Code reads the corresponding markdown file and follows its instructions using available tools (MCP tools, Bash, file operations, etc.).
4
+
5
+ Skills are prompts, not code. The LLM interprets the instructions and orchestrates tool calls to execute the workflow.
6
+
7
+ ## Installation
8
+
9
+ Copy or symlink the skill files into your Claude Code skills directory:
10
+
11
+ ```bash
12
+ # Copy all skills
13
+ cp skills/forge-*.md ~/.claude/skills/
14
+
15
+ # Or symlink (updates automatically with forge-cc)
16
+ ln -s "$(pwd)/skills/forge-triage.md" ~/.claude/skills/forge-triage.md
17
+ ```
18
+
19
+ Skills are also distributed via `npm install forge-cc` and can be found in `node_modules/forge-cc/skills/`.
20
+
21
+ ## Available Skills
22
+
23
+ | Skill | Command | Description |
24
+ |-------|---------|-------------|
25
+ | Triage | `/forge:triage` | Brain dump to Linear projects. Paste unstructured ideas, get organized projects. |
26
+ | Spec | `/forge:spec` | Interview to PRD. Select a project, answer questions, get milestones + issues. *(coming soon)* |
27
+ | Go | `/forge:go` | Execute milestones. Wave-based agents, self-healing verification, auto mode. *(coming soon)* |
28
+
29
+ ## Prerequisites
30
+
31
+ - Claude Code with MCP tools enabled
32
+ - Linear MCP tools configured (`mcp__linear__*`) for triage and spec skills
33
+ - `forge-cc` installed in the project for verification gates (used by go skill)