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
package/dist/doctor.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { loadConfig } from "./config/loader.js";
|
|
3
|
+
import { ForgeLinearClient } from "./linear/client.js";
|
|
4
|
+
function checkNodeVersion() {
|
|
5
|
+
const raw = process.version.replace(/^v/, "");
|
|
6
|
+
const major = Number.parseInt(raw.split(".")[0], 10);
|
|
7
|
+
if (major >= 18) {
|
|
8
|
+
return { name: "Node.js", status: "ok", message: `v${raw}` };
|
|
9
|
+
}
|
|
10
|
+
return {
|
|
11
|
+
name: "Node.js",
|
|
12
|
+
status: "error",
|
|
13
|
+
message: `v${raw} (>= 18 required)`,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function checkGit() {
|
|
17
|
+
try {
|
|
18
|
+
const out = execFileSync("git", ["--version"], {
|
|
19
|
+
encoding: "utf-8",
|
|
20
|
+
timeout: 5000,
|
|
21
|
+
}).trim();
|
|
22
|
+
return { name: "git", status: "ok", message: out };
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return { name: "git", status: "error", message: "not found" };
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function checkGhCli() {
|
|
29
|
+
try {
|
|
30
|
+
const out = execFileSync("gh", ["--version"], {
|
|
31
|
+
encoding: "utf-8",
|
|
32
|
+
timeout: 5000,
|
|
33
|
+
})
|
|
34
|
+
.trim()
|
|
35
|
+
.split("\n")[0];
|
|
36
|
+
return { name: "gh CLI", status: "ok", message: out };
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return {
|
|
40
|
+
name: "gh CLI",
|
|
41
|
+
status: "warn",
|
|
42
|
+
message: "not found (optional — needed for PR workflows)",
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function checkLinearApiKey() {
|
|
47
|
+
if (process.env.LINEAR_API_KEY) {
|
|
48
|
+
return { name: "LINEAR_API_KEY", status: "ok", message: "set" };
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
name: "LINEAR_API_KEY",
|
|
52
|
+
status: "warn",
|
|
53
|
+
message: "not set (optional — needed for Linear integration)",
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
async function checkLinearApiValid() {
|
|
57
|
+
const apiKey = process.env.LINEAR_API_KEY;
|
|
58
|
+
if (!apiKey) {
|
|
59
|
+
return { name: "Linear API", status: "warn", message: "skipped (no key)" };
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
const client = new ForgeLinearClient({ apiKey });
|
|
63
|
+
const teams = await client.listTeams();
|
|
64
|
+
if (teams.length > 0) {
|
|
65
|
+
return {
|
|
66
|
+
name: "Linear API",
|
|
67
|
+
status: "ok",
|
|
68
|
+
message: `authenticated (${teams.length} team${teams.length === 1 ? "" : "s"})`,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
name: "Linear API",
|
|
73
|
+
status: "warn",
|
|
74
|
+
message: "authenticated but no teams visible",
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return {
|
|
79
|
+
name: "Linear API",
|
|
80
|
+
status: "error",
|
|
81
|
+
message: "authentication failed",
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
async function checkLinearTeam(projectDir) {
|
|
86
|
+
const apiKey = process.env.LINEAR_API_KEY;
|
|
87
|
+
if (!apiKey) {
|
|
88
|
+
return {
|
|
89
|
+
name: "Linear team",
|
|
90
|
+
status: "warn",
|
|
91
|
+
message: "skipped (no key)",
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
let config;
|
|
95
|
+
try {
|
|
96
|
+
config = await loadConfig(projectDir);
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return {
|
|
100
|
+
name: "Linear team",
|
|
101
|
+
status: "warn",
|
|
102
|
+
message: "skipped (could not load config)",
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
if (!config.linearTeam) {
|
|
106
|
+
return {
|
|
107
|
+
name: "Linear team",
|
|
108
|
+
status: "warn",
|
|
109
|
+
message: "skipped (no linearTeam in .forge.json)",
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
try {
|
|
113
|
+
const client = new ForgeLinearClient({ apiKey });
|
|
114
|
+
const teams = await client.listTeams();
|
|
115
|
+
const match = teams.find((t) => t.key === config.linearTeam || t.name === config.linearTeam);
|
|
116
|
+
if (match) {
|
|
117
|
+
return {
|
|
118
|
+
name: "Linear team",
|
|
119
|
+
status: "ok",
|
|
120
|
+
message: `"${match.name}" (${match.key})`,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
name: "Linear team",
|
|
125
|
+
status: "error",
|
|
126
|
+
message: `team "${config.linearTeam}" not found`,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
return {
|
|
131
|
+
name: "Linear team",
|
|
132
|
+
status: "error",
|
|
133
|
+
message: "failed to verify team",
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
export async function runDoctor(projectDir) {
|
|
138
|
+
const checks = [];
|
|
139
|
+
checks.push(checkNodeVersion());
|
|
140
|
+
checks.push(checkGit());
|
|
141
|
+
checks.push(checkGhCli());
|
|
142
|
+
checks.push(checkLinearApiKey());
|
|
143
|
+
checks.push(await checkLinearApiValid());
|
|
144
|
+
checks.push(await checkLinearTeam(projectDir));
|
|
145
|
+
const ok = checks.every((c) => c.status !== "error");
|
|
146
|
+
return { checks, ok };
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=doctor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor.js","sourceRoot":"","sources":["../src/doctor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAavD,SAAS,gBAAgB;IACxB,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IAC9C,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACrD,IAAI,KAAK,IAAI,EAAE,EAAE,CAAC;QACjB,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,GAAG,EAAE,EAAE,CAAC;IAC9D,CAAC;IACD,OAAO;QACN,IAAI,EAAE,SAAS;QACf,MAAM,EAAE,OAAO;QACf,OAAO,EAAE,IAAI,GAAG,mBAAmB;KACnC,CAAC;AACH,CAAC;AAED,SAAS,QAAQ;IAChB,IAAI,CAAC;QACJ,MAAM,GAAG,GAAG,YAAY,CAAC,KAAK,EAAE,CAAC,WAAW,CAAC,EAAE;YAC9C,QAAQ,EAAE,OAAO;YACjB,OAAO,EAAE,IAAI;SACb,CAAC,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC;IACpD,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC;IAC/D,CAAC;AACF,CAAC;AAED,SAAS,UAAU;IAClB,IAAI,CAAC;QACJ,MAAM,GAAG,GAAG,YAAY,CAAC,IAAI,EAAE,CAAC,WAAW,CAAC,EAAE;YAC7C,QAAQ,EAAE,OAAO;YACjB,OAAO,EAAE,IAAI;SACb,CAAC;aACA,IAAI,EAAE;aACN,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;QACjB,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC;IACvD,CAAC;IAAC,MAAM,CAAC;QACR,OAAO;YACN,IAAI,EAAE,QAAQ;YACd,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,gDAAgD;SACzD,CAAC;IACH,CAAC;AACF,CAAC;AAED,SAAS,iBAAiB;IACzB,IAAI,OAAO,CAAC,GAAG,CAAC,cAAc,EAAE,CAAC;QAChC,OAAO,EAAE,IAAI,EAAE,gBAAgB,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IACjE,CAAC;IACD,OAAO;QACN,IAAI,EAAE,gBAAgB;QACtB,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,oDAAoD;KAC7D,CAAC;AACH,CAAC;AAED,KAAK,UAAU,mBAAmB;IACjC,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC;IAC1C,IAAI,CAAC,MAAM,EAAE,CAAC;QACb,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,kBAAkB,EAAE,CAAC;IAC5E,CAAC;IACD,IAAI,CAAC;QACJ,MAAM,MAAM,GAAG,IAAI,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;QACjD,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,SAAS,EAAE,CAAC;QACvC,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACtB,OAAO;gBACN,IAAI,EAAE,YAAY;gBAClB,MAAM,EAAE,IAAI;gBACZ,OAAO,EAAE,kBAAkB,KAAK,CAAC,MAAM,QAAQ,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,GAAG;aAC/E,CAAC;QACH,CAAC;QACD,OAAO;YACN,IAAI,EAAE,YAAY;YAClB,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,oCAAoC;SAC7C,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACR,OAAO;YACN,IAAI,EAAE,YAAY;YAClB,MAAM,EAAE,OAAO;YACf,OAAO,EAAE,uBAAuB;SAChC,CAAC;IACH,CAAC;AACF,CAAC;AAED,KAAK,UAAU,eAAe,CAAC,UAAkB;IAChD,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC;IAC1C,IAAI,CAAC,MAAM,EAAE,CAAC;QACb,OAAO;YACN,IAAI,EAAE,aAAa;YACnB,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,kBAAkB;SAC3B,CAAC;IACH,CAAC;IAED,IAAI,MAAM,CAAC;IACX,IAAI,CAAC;QACJ,MAAM,GAAG,MAAM,UAAU,CAAC,UAAU,CAAC,CAAC;IACvC,CAAC;IAAC,MAAM,CAAC;QACR,OAAO;YACN,IAAI,EAAE,aAAa;YACnB,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,iCAAiC;SAC1C,CAAC;IACH,CAAC;IAED,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;QACxB,OAAO;YACN,IAAI,EAAE,aAAa;YACnB,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,wCAAwC;SACjD,CAAC;IACH,CAAC;IAED,IAAI,CAAC;QACJ,MAAM,MAAM,GAAG,IAAI,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;QACjD,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,SAAS,EAAE,CAAC;QACvC,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CACvB,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,MAAM,CAAC,UAAU,IAAI,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,UAAU,CAClE,CAAC;QACF,IAAI,KAAK,EAAE,CAAC;YACX,OAAO;gBACN,IAAI,EAAE,aAAa;gBACnB,MAAM,EAAE,IAAI;gBACZ,OAAO,EAAE,IAAI,KAAK,CAAC,IAAI,MAAM,KAAK,CAAC,GAAG,GAAG;aACzC,CAAC;QACH,CAAC;QACD,OAAO;YACN,IAAI,EAAE,aAAa;YACnB,MAAM,EAAE,OAAO;YACf,OAAO,EAAE,SAAS,MAAM,CAAC,UAAU,aAAa;SAChD,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACR,OAAO;YACN,IAAI,EAAE,aAAa;YACnB,MAAM,EAAE,OAAO;YACf,OAAO,EAAE,uBAAuB;SAChC,CAAC;IACH,CAAC;AACF,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,UAAkB;IACjD,MAAM,MAAM,GAAkB,EAAE,CAAC;IAEjC,MAAM,CAAC,IAAI,CAAC,gBAAgB,EAAE,CAAC,CAAC;IAChC,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;IACxB,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;IAC1B,MAAM,CAAC,IAAI,CAAC,iBAAiB,EAAE,CAAC,CAAC;IACjC,MAAM,CAAC,IAAI,CAAC,MAAM,mBAAmB,EAAE,CAAC,CAAC;IACzC,MAAM,CAAC,IAAI,CAAC,MAAM,eAAe,CAAC,UAAU,CAAC,CAAC,CAAC;IAE/C,MAAM,EAAE,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,OAAO,CAAC,CAAC;IACrD,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;AACvB,CAAC"}
|
package/dist/gates/index.d.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
/**
|
|
3
|
-
export
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
export
|
|
9
|
-
|
|
10
|
-
export
|
|
11
|
-
|
|
12
|
-
export
|
|
1
|
+
import type { ForgeConfig, GateResult, PipelineResult } from "../types.js";
|
|
2
|
+
/** A single verification gate. */
|
|
3
|
+
export interface Gate {
|
|
4
|
+
name: string;
|
|
5
|
+
run: (projectDir: string) => Promise<GateResult>;
|
|
6
|
+
}
|
|
7
|
+
/** Register a gate in the global registry. */
|
|
8
|
+
export declare function registerGate(gate: Gate): void;
|
|
9
|
+
/** List all registered gate names in insertion order. */
|
|
10
|
+
export declare function listGates(): string[];
|
|
11
|
+
/** Clear all registered gates (for testing). */
|
|
12
|
+
export declare function clearGates(): void;
|
|
13
|
+
/** Run the verification pipeline: execute configured gates sequentially with per-gate timeouts. */
|
|
14
|
+
export declare function runPipeline(config: ForgeConfig, projectDir: string): Promise<PipelineResult>;
|
package/dist/gates/index.js
CHANGED
|
@@ -1,115 +1,63 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}),
|
|
18
|
-
runtime: (input) => verifyRuntime(input.appDir ?? input.projectDir, input.apiEndpoints ?? [], {
|
|
19
|
-
devServerCommand: input.devServerCommand,
|
|
20
|
-
devServerPort: input.devServerPort,
|
|
21
|
-
}),
|
|
22
|
-
prd: (input) => verifyPrd(input.projectDir, input.prdPath ?? "", input.baseBranch),
|
|
23
|
-
review: (input) => verifyReview(input.projectDir, {
|
|
24
|
-
prdPath: input.prdPath,
|
|
25
|
-
baseBranch: input.baseBranch,
|
|
26
|
-
blocking: input.reviewBlocking,
|
|
27
|
-
}),
|
|
28
|
-
};
|
|
29
|
-
/** Run the full verification pipeline */
|
|
30
|
-
export async function runPipeline(input) {
|
|
31
|
-
const { gates: requestedGates, maxIterations = 3, } = input;
|
|
32
|
-
// Determine which gates to run
|
|
33
|
-
const gatesToRun = requestedGates ?? ["types", "lint", "tests"];
|
|
1
|
+
const registry = new Map();
|
|
2
|
+
/** Register a gate in the global registry. */
|
|
3
|
+
export function registerGate(gate) {
|
|
4
|
+
registry.set(gate.name, gate);
|
|
5
|
+
}
|
|
6
|
+
/** List all registered gate names in insertion order. */
|
|
7
|
+
export function listGates() {
|
|
8
|
+
return [...registry.keys()];
|
|
9
|
+
}
|
|
10
|
+
/** Clear all registered gates (for testing). */
|
|
11
|
+
export function clearGates() {
|
|
12
|
+
registry.clear();
|
|
13
|
+
}
|
|
14
|
+
/** Run the verification pipeline: execute configured gates sequentially with per-gate timeouts. */
|
|
15
|
+
export async function runPipeline(config, projectDir) {
|
|
16
|
+
const start = Date.now();
|
|
34
17
|
const results = [];
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
duration_ms: 0,
|
|
47
|
-
});
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
50
|
-
const result = await runGateSafe(gateName, () => gateFn(input));
|
|
51
|
-
results.push(result);
|
|
52
|
-
// Early exit: if all core gates (types, lint, tests) fail, skip remaining
|
|
53
|
-
const coreGates = results.filter(r => ["types", "lint", "tests"].includes(r.gate));
|
|
54
|
-
if (coreGates.length === 3 && coreGates.every(r => !r.passed)) {
|
|
55
|
-
// Add skipped gates
|
|
56
|
-
for (const remaining of gatesToRun.slice(gatesToRun.indexOf(gateName) + 1)) {
|
|
57
|
-
results.push({
|
|
58
|
-
gate: remaining,
|
|
59
|
-
passed: false,
|
|
60
|
-
errors: [],
|
|
61
|
-
warnings: ["Skipped due to core gate failures"],
|
|
62
|
-
duration_ms: 0,
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
break;
|
|
66
|
-
}
|
|
18
|
+
const defaultTimeout = 120_000;
|
|
19
|
+
for (const gateName of config.gates) {
|
|
20
|
+
const gate = registry.get(gateName);
|
|
21
|
+
if (!gate) {
|
|
22
|
+
results.push({
|
|
23
|
+
gate: gateName,
|
|
24
|
+
passed: false,
|
|
25
|
+
errors: [{ file: "", line: 0, message: `Gate "${gateName}" not registered` }],
|
|
26
|
+
durationMs: 0,
|
|
27
|
+
});
|
|
28
|
+
continue;
|
|
67
29
|
}
|
|
68
|
-
|
|
69
|
-
|
|
30
|
+
const timeout = config.gateTimeouts[gateName] ?? defaultTimeout;
|
|
31
|
+
const gateStart = Date.now();
|
|
32
|
+
let timer;
|
|
70
33
|
try {
|
|
71
|
-
await
|
|
34
|
+
const result = await Promise.race([
|
|
35
|
+
gate.run(projectDir),
|
|
36
|
+
new Promise((_resolve, reject) => {
|
|
37
|
+
timer = setTimeout(() => reject(new Error(`Gate "${gateName}" timed out after ${timeout}ms`)), timeout);
|
|
38
|
+
}),
|
|
39
|
+
]);
|
|
40
|
+
results.push(result);
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
44
|
+
results.push({
|
|
45
|
+
gate: gateName,
|
|
46
|
+
passed: false,
|
|
47
|
+
errors: [{ file: "", line: 0, message }],
|
|
48
|
+
durationMs: Date.now() - gateStart,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
if (timer !== undefined)
|
|
53
|
+
clearTimeout(timer);
|
|
72
54
|
}
|
|
73
|
-
catch { /* non-fatal */ }
|
|
74
55
|
}
|
|
75
|
-
const
|
|
56
|
+
const allPassed = results.every((r) => r.passed);
|
|
76
57
|
return {
|
|
77
|
-
|
|
78
|
-
iteration: 1,
|
|
79
|
-
maxIterations,
|
|
58
|
+
result: allPassed ? "PASSED" : "FAILED",
|
|
80
59
|
gates: results,
|
|
81
|
-
|
|
60
|
+
durationMs: Date.now() - start,
|
|
82
61
|
};
|
|
83
62
|
}
|
|
84
|
-
const GATE_TIMEOUT_MS = 120_000; // 2 minutes per gate
|
|
85
|
-
async function runGateSafe(name, fn) {
|
|
86
|
-
const start = Date.now();
|
|
87
|
-
try {
|
|
88
|
-
const result = await Promise.race([
|
|
89
|
-
fn(),
|
|
90
|
-
new Promise((_, reject) => globalThis.setTimeout(() => reject(new Error(`Gate "${name}" timed out after ${GATE_TIMEOUT_MS / 1000}s`)), GATE_TIMEOUT_MS)),
|
|
91
|
-
]);
|
|
92
|
-
return result;
|
|
93
|
-
}
|
|
94
|
-
catch (err) {
|
|
95
|
-
const duration = Date.now() - start;
|
|
96
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
97
|
-
const isTimeout = message.includes("timed out");
|
|
98
|
-
return {
|
|
99
|
-
gate: name,
|
|
100
|
-
passed: false,
|
|
101
|
-
errors: [{ message: isTimeout ? message : `Gate "${name}" crashed: ${message}` }],
|
|
102
|
-
warnings: [],
|
|
103
|
-
duration_ms: duration,
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
// Re-export individual gates for direct use
|
|
108
|
-
export { verifyTypes } from "./types-gate.js";
|
|
109
|
-
export { verifyLint } from "./lint-gate.js";
|
|
110
|
-
export { verifyTests } from "./tests-gate.js";
|
|
111
|
-
export { verifyVisual, captureBeforeSnapshots, clearBeforeSnapshots } from "./visual-gate.js";
|
|
112
|
-
export { verifyRuntime } from "./runtime-gate.js";
|
|
113
|
-
export { verifyPrd } from "./prd-gate.js";
|
|
114
|
-
export { verifyReview } from "./review-gate.js";
|
|
115
63
|
//# sourceMappingURL=index.js.map
|
package/dist/gates/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/gates/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/gates/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
export declare
|
|
1
|
+
import type { Gate } from "./index.js";
|
|
2
|
+
export declare const lintGate: Gate;
|
package/dist/gates/lint-gate.js
CHANGED
|
@@ -1,71 +1,65 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* Biome diagnostics often look like:
|
|
5
|
-
* path/to/file.ts:10:5 lint/rule ...
|
|
6
|
-
* or header lines with ━━ separators
|
|
7
|
-
*/
|
|
8
|
-
const BIOME_LOC_RE = /^(.+?):(\d+):\d+\s+(.+)$/;
|
|
9
|
-
export async function verifyLint(projectDir) {
|
|
10
|
-
const start = Date.now();
|
|
11
|
-
const errors = [];
|
|
12
|
-
const warnings = [];
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
/** Parse biome check --reporter=json output into structured errors. */
|
|
3
|
+
function parseBiomeOutput(output) {
|
|
13
4
|
try {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
5
|
+
const report = JSON.parse(output);
|
|
6
|
+
if (!report.diagnostics)
|
|
7
|
+
return [];
|
|
8
|
+
return report.diagnostics
|
|
9
|
+
.filter((d) => d.severity === "error" || d.severity === "warning")
|
|
10
|
+
.map((d) => ({
|
|
11
|
+
file: d.location?.path?.file ?? "",
|
|
12
|
+
line: 0, // JSON reporter doesn't provide line numbers directly; span offsets are byte-based
|
|
13
|
+
column: 0,
|
|
14
|
+
message: d.description ?? d.message ?? "Unknown lint error",
|
|
15
|
+
rule: d.category,
|
|
16
|
+
}));
|
|
20
17
|
}
|
|
21
|
-
catch
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
: ""
|
|
25
|
-
|
|
26
|
-
? String(err.stderr)
|
|
27
|
-
: "";
|
|
28
|
-
const output = `${stdout}\n${stderr}`;
|
|
29
|
-
const rawErrors = [];
|
|
30
|
-
for (const line of output.split("\n")) {
|
|
31
|
-
const trimmed = line.trim();
|
|
32
|
-
if (!trimmed)
|
|
33
|
-
continue;
|
|
34
|
-
if (trimmed.includes(" ━━") ||
|
|
35
|
-
trimmed.toLowerCase().includes("error") ||
|
|
36
|
-
trimmed.includes("×")) {
|
|
37
|
-
const match = BIOME_LOC_RE.exec(trimmed);
|
|
38
|
-
if (match) {
|
|
39
|
-
rawErrors.push({
|
|
40
|
-
file: match[1],
|
|
41
|
-
line: Number.parseInt(match[2], 10),
|
|
42
|
-
message: match[3],
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
else {
|
|
46
|
-
rawErrors.push({ message: trimmed });
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
// Cap at 50 errors to avoid massive output
|
|
51
|
-
const cappedErrors = rawErrors.slice(0, 50);
|
|
52
|
-
if (rawErrors.length > 50) {
|
|
53
|
-
cappedErrors.push({ message: `... and ${rawErrors.length - 50} more errors` });
|
|
54
|
-
}
|
|
55
|
-
if (cappedErrors.length === 0) {
|
|
56
|
-
cappedErrors.push({ message: "biome check exited with non-zero status but no errors were parsed" });
|
|
57
|
-
}
|
|
58
|
-
// Enrich errors with remediation hints
|
|
59
|
-
for (const error of cappedErrors) {
|
|
60
|
-
error.remediation = buildLintRemediation(error);
|
|
61
|
-
}
|
|
62
|
-
return {
|
|
63
|
-
gate: "lint",
|
|
64
|
-
passed: false,
|
|
65
|
-
errors: cappedErrors,
|
|
66
|
-
warnings,
|
|
67
|
-
duration_ms: Date.now() - start,
|
|
68
|
-
};
|
|
18
|
+
catch {
|
|
19
|
+
// If JSON parsing fails, return a single error with the raw output
|
|
20
|
+
return output.trim()
|
|
21
|
+
? [{ file: "", line: 0, message: `Biome output parse error: ${output.slice(0, 200)}` }]
|
|
22
|
+
: [];
|
|
69
23
|
}
|
|
70
24
|
}
|
|
25
|
+
function runBiome(projectDir) {
|
|
26
|
+
return new Promise((resolve) => {
|
|
27
|
+
const start = Date.now();
|
|
28
|
+
const child = spawn("npx", ["biome", "check", "--reporter=json", "."], {
|
|
29
|
+
cwd: projectDir,
|
|
30
|
+
shell: true,
|
|
31
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
32
|
+
});
|
|
33
|
+
let stdout = "";
|
|
34
|
+
let stderr = "";
|
|
35
|
+
child.stdout.on("data", (data) => {
|
|
36
|
+
stdout += data.toString();
|
|
37
|
+
});
|
|
38
|
+
child.stderr.on("data", (data) => {
|
|
39
|
+
stderr += data.toString();
|
|
40
|
+
});
|
|
41
|
+
child.on("close", (code) => {
|
|
42
|
+
const output = stdout + stderr;
|
|
43
|
+
const errors = parseBiomeOutput(output);
|
|
44
|
+
resolve({
|
|
45
|
+
gate: "lint",
|
|
46
|
+
passed: code === 0,
|
|
47
|
+
errors,
|
|
48
|
+
durationMs: Date.now() - start,
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
child.on("error", (err) => {
|
|
52
|
+
resolve({
|
|
53
|
+
gate: "lint",
|
|
54
|
+
passed: false,
|
|
55
|
+
errors: [{ file: "", line: 0, message: err.message }],
|
|
56
|
+
durationMs: Date.now() - start,
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
export const lintGate = {
|
|
62
|
+
name: "lint",
|
|
63
|
+
run: runBiome,
|
|
64
|
+
};
|
|
71
65
|
//# sourceMappingURL=lint-gate.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lint-gate.js","sourceRoot":"","sources":["../../src/gates/lint-gate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"lint-gate.js","sourceRoot":"","sources":["../../src/gates/lint-gate.ts"],"names":[],"mappings":"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"}
|