forge-cc 0.1.40 → 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 -906
- 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/go/linear-sync-cli.js +13 -4
- package/dist/go/linear-sync-cli.js.map +1 -1
- package/dist/go/linear-sync.d.ts +1 -0
- package/dist/go/linear-sync.js +67 -4
- package/dist/go/linear-sync.js.map +1 -1
- package/dist/linear/client.d.ts +34 -105
- package/dist/linear/client.js +85 -365
- package/dist/linear/client.js.map +1 -1
- package/dist/linear/issues.d.ts +3 -1
- package/dist/linear/issues.js +14 -2
- package/dist/linear/issues.js.map +1 -1
- package/dist/linear/projects.js +3 -2
- package/dist/linear/projects.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 +583 -575
- 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
|
@@ -1,109 +1,109 @@
|
|
|
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
|
-
let branch = "unknown";
|
|
39
|
-
try {
|
|
40
|
-
branch = execSync("git branch --show-current", {
|
|
41
|
-
encoding: "utf-8",
|
|
42
|
-
}).trim();
|
|
43
|
-
if (branch === "main" || branch === "master") {
|
|
44
|
-
return {
|
|
45
|
-
decision: "block",
|
|
46
|
-
reason: `Forge: Cannot commit directly to ${branch}. Create a feature branch first.`,
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
} catch {
|
|
50
|
-
// Can't determine branch — allow
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Check 2: Verify cache exists — per-branch first, fall back to legacy path
|
|
54
|
-
const slug = branch.replace(/\//g, "-").toLowerCase();
|
|
55
|
-
const perBranchCachePath = join(projectDir, ".forge", "verify-cache", `${slug}.json`);
|
|
56
|
-
const legacyCachePath = join(projectDir, ".forge", "last-verify.json");
|
|
57
|
-
const cachePath = existsSync(perBranchCachePath)
|
|
58
|
-
? perBranchCachePath
|
|
59
|
-
: legacyCachePath;
|
|
60
|
-
if (!existsSync(cachePath)) {
|
|
61
|
-
return {
|
|
62
|
-
decision: "block",
|
|
63
|
-
reason:
|
|
64
|
-
"Forge: No verification found. Run `npx forge verify` before committing.",
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
try {
|
|
69
|
-
const cache = JSON.parse(readFileSync(cachePath, "utf-8"));
|
|
70
|
-
|
|
71
|
-
// Check 3: Did verification pass?
|
|
72
|
-
if (
|
|
73
|
-
return {
|
|
74
|
-
decision: "block",
|
|
75
|
-
reason:
|
|
76
|
-
"Forge: Last verification FAILED. Fix errors and run `npx forge verify` again.",
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Check 4: Is it fresh? (default 10 minutes = 600000ms)
|
|
81
|
-
let freshness = 600_000;
|
|
82
|
-
const configPath = join(projectDir, ".forge.json");
|
|
83
|
-
if (existsSync(configPath)) {
|
|
84
|
-
try {
|
|
85
|
-
const config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
86
|
-
if (config.verifyFreshness) freshness = config.verifyFreshness;
|
|
87
|
-
} catch {
|
|
88
|
-
/* use default */
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const age = Date.now() - new Date(cache.timestamp).getTime();
|
|
93
|
-
if (age > freshness) {
|
|
94
|
-
const ageMin = Math.round(age / 60_000);
|
|
95
|
-
return {
|
|
96
|
-
decision: "block",
|
|
97
|
-
reason: `Forge: Verification is stale (${ageMin}min old). Run \`npx forge verify\` again.`,
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
return { decision: "allow" };
|
|
102
|
-
} catch {
|
|
103
|
-
return {
|
|
104
|
-
decision: "block",
|
|
105
|
-
reason:
|
|
106
|
-
"Forge: Could not read verification cache. Run `npx forge verify`.",
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
}
|
|
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
|
+
let branch = "unknown";
|
|
39
|
+
try {
|
|
40
|
+
branch = execSync("git branch --show-current", {
|
|
41
|
+
encoding: "utf-8",
|
|
42
|
+
}).trim();
|
|
43
|
+
if (branch === "main" || branch === "master") {
|
|
44
|
+
return {
|
|
45
|
+
decision: "block",
|
|
46
|
+
reason: `Forge: Cannot commit directly to ${branch}. Create a feature branch first.`,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
} catch {
|
|
50
|
+
// Can't determine branch — allow
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Check 2: Verify cache exists — per-branch first, fall back to legacy path
|
|
54
|
+
const slug = branch.replace(/\//g, "-").toLowerCase();
|
|
55
|
+
const perBranchCachePath = join(projectDir, ".forge", "verify-cache", `${slug}.json`);
|
|
56
|
+
const legacyCachePath = join(projectDir, ".forge", "last-verify.json");
|
|
57
|
+
const cachePath = existsSync(perBranchCachePath)
|
|
58
|
+
? perBranchCachePath
|
|
59
|
+
: legacyCachePath;
|
|
60
|
+
if (!existsSync(cachePath)) {
|
|
61
|
+
return {
|
|
62
|
+
decision: "block",
|
|
63
|
+
reason:
|
|
64
|
+
"Forge: No verification found. Run `npx forge verify` before committing.",
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
const cache = JSON.parse(readFileSync(cachePath, "utf-8"));
|
|
70
|
+
|
|
71
|
+
// Check 3: Did verification pass? (v2 format: cache.result === 'PASSED')
|
|
72
|
+
if (cache.result !== 'PASSED') {
|
|
73
|
+
return {
|
|
74
|
+
decision: "block",
|
|
75
|
+
reason:
|
|
76
|
+
"Forge: Last verification FAILED. Fix errors and run `npx forge verify` again.",
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Check 4: Is it fresh? (default 10 minutes = 600000ms)
|
|
81
|
+
let freshness = 600_000;
|
|
82
|
+
const configPath = join(projectDir, ".forge.json");
|
|
83
|
+
if (existsSync(configPath)) {
|
|
84
|
+
try {
|
|
85
|
+
const config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
86
|
+
if (config.verifyFreshness) freshness = config.verifyFreshness;
|
|
87
|
+
} catch {
|
|
88
|
+
/* use default */
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const age = Date.now() - new Date(cache.timestamp).getTime();
|
|
93
|
+
if (age > freshness) {
|
|
94
|
+
const ageMin = Math.round(age / 60_000);
|
|
95
|
+
return {
|
|
96
|
+
decision: "block",
|
|
97
|
+
reason: `Forge: Verification is stale (${ageMin}min old). Run \`npx forge verify\` again.`,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return { decision: "allow" };
|
|
102
|
+
} catch {
|
|
103
|
+
return {
|
|
104
|
+
decision: "block",
|
|
105
|
+
reason:
|
|
106
|
+
"Forge: Could not read verification cache. Run `npx forge verify`.",
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "forge-cc",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Forge — verification harness for Claude Code agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "Troy Hoffman",
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
"prepublishOnly": "npm run build"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
|
+
"@linear/sdk": "^75.0.0",
|
|
53
54
|
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
54
55
|
"commander": "^13.0.0",
|
|
55
56
|
"zod": "^3.24.0"
|