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.
- package/.forge.json +5 -0
- package/AGENTS.md +42 -0
- package/README.md +283 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +148 -0
- package/dist/cli.js.map +1 -0
- package/dist/config/loader.d.ts +2 -0
- package/dist/config/loader.js +44 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/config/schema.d.ts +57 -0
- package/dist/config/schema.js +15 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/gates/index.d.ts +11 -0
- package/dist/gates/index.js +106 -0
- package/dist/gates/index.js.map +1 -0
- package/dist/gates/lint-gate.d.ts +2 -0
- package/dist/gates/lint-gate.js +66 -0
- package/dist/gates/lint-gate.js.map +1 -0
- package/dist/gates/prd-gate.d.ts +7 -0
- package/dist/gates/prd-gate.js +193 -0
- package/dist/gates/prd-gate.js.map +1 -0
- package/dist/gates/runtime-gate.d.ts +5 -0
- package/dist/gates/runtime-gate.js +99 -0
- package/dist/gates/runtime-gate.js.map +1 -0
- package/dist/gates/tests-gate.d.ts +2 -0
- package/dist/gates/tests-gate.js +116 -0
- package/dist/gates/tests-gate.js.map +1 -0
- package/dist/gates/types-gate.d.ts +2 -0
- package/dist/gates/types-gate.js +59 -0
- package/dist/gates/types-gate.js.map +1 -0
- package/dist/gates/visual-gate.d.ts +6 -0
- package/dist/gates/visual-gate.js +118 -0
- package/dist/gates/visual-gate.js.map +1 -0
- package/dist/go/auto-chain.d.ts +107 -0
- package/dist/go/auto-chain.js +303 -0
- package/dist/go/auto-chain.js.map +1 -0
- package/dist/go/executor.d.ts +130 -0
- package/dist/go/executor.js +409 -0
- package/dist/go/executor.js.map +1 -0
- package/dist/go/finalize.d.ts +58 -0
- package/dist/go/finalize.js +200 -0
- package/dist/go/finalize.js.map +1 -0
- package/dist/go/linear-sync.d.ts +75 -0
- package/dist/go/linear-sync.js +239 -0
- package/dist/go/linear-sync.js.map +1 -0
- package/dist/go/verify-loop.d.ts +47 -0
- package/dist/go/verify-loop.js +172 -0
- package/dist/go/verify-loop.js.map +1 -0
- package/dist/hooks/pre-commit.d.ts +5 -0
- package/dist/hooks/pre-commit.js +69 -0
- package/dist/hooks/pre-commit.js.map +1 -0
- package/dist/linear/client.d.ts +108 -0
- package/dist/linear/client.js +388 -0
- package/dist/linear/client.js.map +1 -0
- package/dist/linear/issues.d.ts +20 -0
- package/dist/linear/issues.js +39 -0
- package/dist/linear/issues.js.map +1 -0
- package/dist/linear/milestones.d.ts +11 -0
- package/dist/linear/milestones.js +32 -0
- package/dist/linear/milestones.js.map +1 -0
- package/dist/linear/projects.d.ts +16 -0
- package/dist/linear/projects.js +50 -0
- package/dist/linear/projects.js.map +1 -0
- package/dist/reporter/human.d.ts +2 -0
- package/dist/reporter/human.js +63 -0
- package/dist/reporter/human.js.map +1 -0
- package/dist/reporter/json.d.ts +2 -0
- package/dist/reporter/json.js +4 -0
- package/dist/reporter/json.js.map +1 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +109 -0
- package/dist/server.js.map +1 -0
- package/dist/spec/generator.d.ts +14 -0
- package/dist/spec/generator.js +206 -0
- package/dist/spec/generator.js.map +1 -0
- package/dist/spec/interview.d.ts +104 -0
- package/dist/spec/interview.js +342 -0
- package/dist/spec/interview.js.map +1 -0
- package/dist/spec/linear-sync.d.ts +48 -0
- package/dist/spec/linear-sync.js +125 -0
- package/dist/spec/linear-sync.js.map +1 -0
- package/dist/spec/scanner.d.ts +45 -0
- package/dist/spec/scanner.js +473 -0
- package/dist/spec/scanner.js.map +1 -0
- package/dist/spec/templates.d.ts +345 -0
- package/dist/spec/templates.js +86 -0
- package/dist/spec/templates.js.map +1 -0
- package/dist/state/reader.d.ts +29 -0
- package/dist/state/reader.js +116 -0
- package/dist/state/reader.js.map +1 -0
- package/dist/state/writer.d.ts +60 -0
- package/dist/state/writer.js +222 -0
- package/dist/state/writer.js.map +1 -0
- package/dist/types.d.ts +64 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/browser.d.ts +10 -0
- package/dist/utils/browser.js +89 -0
- package/dist/utils/browser.js.map +1 -0
- package/hooks/pre-commit-verify.js +103 -0
- package/package.json +68 -0
- package/skills/README.md +33 -0
- package/skills/forge-go.md +332 -0
- package/skills/forge-spec.md +251 -0
- package/skills/forge-triage.md +133 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { runPipeline } from "../gates/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Self-healing verification loop.
|
|
4
|
+
*
|
|
5
|
+
* Runs the forge verification pipeline, and on failure either invokes the
|
|
6
|
+
* `onFixAttempt` callback (so the caller can spawn a fix agent) or simply
|
|
7
|
+
* re-runs verification. Loops until the pipeline passes or max iterations
|
|
8
|
+
* are exhausted.
|
|
9
|
+
*/
|
|
10
|
+
export async function runVerifyLoop(options) {
|
|
11
|
+
const { projectDir, config, onIteration, onFixAttempt, } = options;
|
|
12
|
+
const maxIterations = options.maxIterations ?? config.maxIterations;
|
|
13
|
+
const results = [];
|
|
14
|
+
for (let iteration = 1; iteration <= maxIterations; iteration++) {
|
|
15
|
+
const pipelineInput = {
|
|
16
|
+
projectDir,
|
|
17
|
+
gates: config.gates,
|
|
18
|
+
prdPath: config.prdPath,
|
|
19
|
+
maxIterations,
|
|
20
|
+
devServerCommand: config.devServer?.command,
|
|
21
|
+
devServerPort: config.devServer?.port,
|
|
22
|
+
};
|
|
23
|
+
const result = await runPipeline(pipelineInput);
|
|
24
|
+
// Stamp the iteration number onto the result
|
|
25
|
+
const stamped = {
|
|
26
|
+
...result,
|
|
27
|
+
iteration,
|
|
28
|
+
maxIterations,
|
|
29
|
+
};
|
|
30
|
+
results.push(stamped);
|
|
31
|
+
onIteration?.(iteration, stamped);
|
|
32
|
+
// Success — return immediately
|
|
33
|
+
if (stamped.passed) {
|
|
34
|
+
return {
|
|
35
|
+
passed: true,
|
|
36
|
+
iterations: iteration,
|
|
37
|
+
maxIterations,
|
|
38
|
+
results,
|
|
39
|
+
finalResult: stamped,
|
|
40
|
+
failedGates: [],
|
|
41
|
+
errorSummary: "",
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
// Not the last iteration — attempt a fix before retrying
|
|
45
|
+
if (iteration < maxIterations) {
|
|
46
|
+
const allErrors = collectErrors(stamped);
|
|
47
|
+
if (onFixAttempt) {
|
|
48
|
+
// Give the caller a chance to fix. Even if it returns false we still
|
|
49
|
+
// loop and re-verify (the caller may have partially fixed things, or
|
|
50
|
+
// an external process may have intervened).
|
|
51
|
+
await onFixAttempt(iteration, allErrors);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// Exhausted all iterations without passing
|
|
56
|
+
const finalResult = results[results.length - 1];
|
|
57
|
+
const failedGates = finalResult.gates
|
|
58
|
+
.filter((g) => !g.passed)
|
|
59
|
+
.map((g) => g.gate);
|
|
60
|
+
return {
|
|
61
|
+
passed: false,
|
|
62
|
+
iterations: maxIterations,
|
|
63
|
+
maxIterations,
|
|
64
|
+
results,
|
|
65
|
+
finalResult,
|
|
66
|
+
failedGates,
|
|
67
|
+
errorSummary: buildErrorSummary(maxIterations, finalResult),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
// Helpers
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
/** Collect all GateError items from failed gates in a pipeline result */
|
|
74
|
+
function collectErrors(result) {
|
|
75
|
+
return result.gates
|
|
76
|
+
.filter((g) => !g.passed)
|
|
77
|
+
.flatMap((g) => g.errors);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Build a human-readable error summary for a failed verification run.
|
|
81
|
+
*
|
|
82
|
+
* Example output:
|
|
83
|
+
* ```
|
|
84
|
+
* Verification failed after 3 iterations.
|
|
85
|
+
*
|
|
86
|
+
* Failed gates:
|
|
87
|
+
* - types: 2 errors
|
|
88
|
+
* - src/foo.ts:10: Type 'string' not assignable to 'number'
|
|
89
|
+
* - src/bar.ts:5: Property 'x' does not exist on type 'Y'
|
|
90
|
+
* - lint: 1 error
|
|
91
|
+
* - src/baz.ts:20: Unexpected any type
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
function buildErrorSummary(iterations, result) {
|
|
95
|
+
const lines = [];
|
|
96
|
+
lines.push(`Verification failed after ${iterations} iteration${iterations === 1 ? "" : "s"}.`);
|
|
97
|
+
lines.push("");
|
|
98
|
+
const failedGates = result.gates.filter((g) => !g.passed);
|
|
99
|
+
if (failedGates.length === 0) {
|
|
100
|
+
return lines.join("\n");
|
|
101
|
+
}
|
|
102
|
+
lines.push("Failed gates:");
|
|
103
|
+
for (const gate of failedGates) {
|
|
104
|
+
const errorCount = gate.errors.length;
|
|
105
|
+
lines.push(`- ${gate.gate}: ${errorCount} error${errorCount === 1 ? "" : "s"}`);
|
|
106
|
+
for (const err of gate.errors) {
|
|
107
|
+
const location = formatLocation(err);
|
|
108
|
+
const prefix = location ? `${location}: ` : "";
|
|
109
|
+
lines.push(` - ${prefix}${err.message}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return lines.join("\n");
|
|
113
|
+
}
|
|
114
|
+
/** Format file:line location string for an error */
|
|
115
|
+
function formatLocation(err) {
|
|
116
|
+
if (!err.file)
|
|
117
|
+
return "";
|
|
118
|
+
return err.line ? `${err.file}:${err.line}` : err.file;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Format gate errors into a structured prompt for a fix agent.
|
|
122
|
+
*
|
|
123
|
+
* The output is designed to be directly usable in an AI agent prompt:
|
|
124
|
+
* it includes file paths, line numbers, error messages, and remediation
|
|
125
|
+
* hints so the agent can locate and fix issues without extra searching.
|
|
126
|
+
*/
|
|
127
|
+
export function formatErrorsForAgent(result) {
|
|
128
|
+
const lines = [];
|
|
129
|
+
const failedGates = result.gates.filter((g) => !g.passed);
|
|
130
|
+
if (failedGates.length === 0) {
|
|
131
|
+
return "All gates passed. No errors to fix.";
|
|
132
|
+
}
|
|
133
|
+
lines.push("# Verification Errors to Fix");
|
|
134
|
+
lines.push("");
|
|
135
|
+
for (const gate of failedGates) {
|
|
136
|
+
lines.push(`## Gate: ${gate.gate} (${gate.errors.length} errors)`);
|
|
137
|
+
lines.push("");
|
|
138
|
+
if (gate.errors.length === 0) {
|
|
139
|
+
lines.push("Gate failed but reported no structured errors.");
|
|
140
|
+
lines.push("");
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
for (const err of gate.errors) {
|
|
144
|
+
const location = formatLocation(err);
|
|
145
|
+
if (location) {
|
|
146
|
+
lines.push(`### ${location}`);
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
lines.push("### (no file location)");
|
|
150
|
+
}
|
|
151
|
+
lines.push(`**Error:** ${err.message}`);
|
|
152
|
+
if (err.remediation) {
|
|
153
|
+
lines.push(`**Suggested fix:** ${err.remediation}`);
|
|
154
|
+
}
|
|
155
|
+
lines.push("");
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
// Add warnings as context (non-blocking but useful for agents)
|
|
159
|
+
const gatesWithWarnings = result.gates.filter((g) => g.warnings.length > 0);
|
|
160
|
+
if (gatesWithWarnings.length > 0) {
|
|
161
|
+
lines.push("## Warnings (non-blocking)");
|
|
162
|
+
lines.push("");
|
|
163
|
+
for (const gate of gatesWithWarnings) {
|
|
164
|
+
for (const warning of gate.warnings) {
|
|
165
|
+
lines.push(`- [${gate.gate}] ${warning}`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
lines.push("");
|
|
169
|
+
}
|
|
170
|
+
return lines.join("\n");
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=verify-loop.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify-loop.js","sourceRoot":"","sources":["../../src/go/verify-loop.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { execSync } from "node:child_process";
|
|
4
|
+
import { loadConfig } from "../config/loader.js";
|
|
5
|
+
export function checkPreCommit(projectDir) {
|
|
6
|
+
// Wrong branch protection
|
|
7
|
+
try {
|
|
8
|
+
const branch = execSync("git branch --show-current", {
|
|
9
|
+
cwd: projectDir,
|
|
10
|
+
encoding: "utf-8",
|
|
11
|
+
}).trim();
|
|
12
|
+
if (branch === "main" || branch === "master") {
|
|
13
|
+
return {
|
|
14
|
+
allowed: false,
|
|
15
|
+
reason: `Cannot commit directly to ${branch}. Create a feature branch first.`,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
// Can't determine branch — allow
|
|
21
|
+
}
|
|
22
|
+
// Check verify cache
|
|
23
|
+
const cachePath = join(projectDir, ".forge", "last-verify.json");
|
|
24
|
+
if (!existsSync(cachePath)) {
|
|
25
|
+
return {
|
|
26
|
+
allowed: false,
|
|
27
|
+
reason: "No verification found. Run `npx forge verify` before committing.",
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
const cache = JSON.parse(readFileSync(cachePath, "utf-8"));
|
|
32
|
+
// Validate cache structure — treat malformed cache as invalid
|
|
33
|
+
if (typeof cache.passed !== "boolean" || typeof cache.timestamp !== "string") {
|
|
34
|
+
return {
|
|
35
|
+
allowed: false,
|
|
36
|
+
reason: "Verification cache is malformed (missing or invalid fields). Run `npx forge verify`.",
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
if (!cache.passed) {
|
|
40
|
+
return {
|
|
41
|
+
allowed: false,
|
|
42
|
+
reason: "Last verification FAILED. Fix errors and run `npx forge verify` again.",
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
const config = loadConfig(projectDir);
|
|
46
|
+
const age = Date.now() - new Date(cache.timestamp).getTime();
|
|
47
|
+
if (Number.isNaN(age) || age < 0) {
|
|
48
|
+
return {
|
|
49
|
+
allowed: false,
|
|
50
|
+
reason: "Verification cache has an invalid timestamp. Run `npx forge verify`.",
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
if (age > config.verifyFreshness) {
|
|
54
|
+
const ageMin = Math.round(age / 60_000);
|
|
55
|
+
return {
|
|
56
|
+
allowed: false,
|
|
57
|
+
reason: `Verification is stale (${ageMin}min old). Run \`npx forge verify\` again.`,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
return { allowed: true };
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return {
|
|
64
|
+
allowed: false,
|
|
65
|
+
reason: "Could not read verification cache. Run `npx forge verify`.",
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=pre-commit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pre-commit.js","sourceRoot":"","sources":["../../src/hooks/pre-commit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAOjD,MAAM,UAAU,cAAc,CAAC,UAAkB;IAC/C,0BAA0B;IAC1B,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,QAAQ,CAAC,2BAA2B,EAAE;YACnD,GAAG,EAAE,UAAU;YACf,QAAQ,EAAE,OAAO;SAClB,CAAC,CAAC,IAAI,EAAE,CAAC;QACV,IAAI,MAAM,KAAK,MAAM,IAAI,MAAM,KAAK,QAAQ,EAAE,CAAC;YAC7C,OAAO;gBACL,OAAO,EAAE,KAAK;gBACd,MAAM,EAAE,6BAA6B,MAAM,kCAAkC;aAC9E,CAAC;QACJ,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,iCAAiC;IACnC,CAAC;IAED,qBAAqB;IACrB,MAAM,SAAS,GAAG,IAAI,CAAC,UAAU,EAAE,QAAQ,EAAE,kBAAkB,CAAC,CAAC;IACjE,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC3B,OAAO;YACL,OAAO,EAAE,KAAK;YACd,MAAM,EACJ,kEAAkE;SACrE,CAAC;IACJ,CAAC;IAED,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC;QAE3D,8DAA8D;QAC9D,IAAI,OAAO,KAAK,CAAC,MAAM,KAAK,SAAS,IAAI,OAAO,KAAK,CAAC,SAAS,KAAK,QAAQ,EAAE,CAAC;YAC7E,OAAO;gBACL,OAAO,EAAE,KAAK;gBACd,MAAM,EACJ,sFAAsF;aACzF,CAAC;QACJ,CAAC;QAED,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;YAClB,OAAO;gBACL,OAAO,EAAE,KAAK;gBACd,MAAM,EACJ,wEAAwE;aAC3E,CAAC;QACJ,CAAC;QAED,MAAM,MAAM,GAAG,UAAU,CAAC,UAAU,CAAC,CAAC;QACtC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,CAAC;QAC7D,IAAI,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,GAAG,GAAG,CAAC,EAAE,CAAC;YACjC,OAAO;gBACL,OAAO,EAAE,KAAK;gBACd,MAAM,EACJ,sEAAsE;aACzE,CAAC;QACJ,CAAC;QACD,IAAI,GAAG,GAAG,MAAM,CAAC,eAAe,EAAE,CAAC;YACjC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,MAAM,CAAC,CAAC;YACxC,OAAO;gBACL,OAAO,EAAE,KAAK;gBACd,MAAM,EAAE,0BAA0B,MAAM,2CAA2C;aACpF,CAAC;QACJ,CAAC;QAED,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC3B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO;YACL,OAAO,EAAE,KAAK;YACd,MAAM,EACJ,4DAA4D;SAC/D,CAAC;IACJ,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed wrapper around the Linear GraphQL API.
|
|
3
|
+
* Used by lifecycle modules (projects.ts, milestones.ts, issues.ts)
|
|
4
|
+
* and the execution engine for programmatic Linear management.
|
|
5
|
+
*/
|
|
6
|
+
export interface LinearProject {
|
|
7
|
+
id: string;
|
|
8
|
+
name: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
state: string;
|
|
11
|
+
url: string;
|
|
12
|
+
}
|
|
13
|
+
export interface LinearMilestone {
|
|
14
|
+
id: string;
|
|
15
|
+
name: string;
|
|
16
|
+
description?: string;
|
|
17
|
+
progress: number;
|
|
18
|
+
sortOrder: number;
|
|
19
|
+
}
|
|
20
|
+
export interface LinearIssue {
|
|
21
|
+
id: string;
|
|
22
|
+
identifier: string;
|
|
23
|
+
title: string;
|
|
24
|
+
description?: string;
|
|
25
|
+
state: string;
|
|
26
|
+
projectId?: string;
|
|
27
|
+
milestoneId?: string;
|
|
28
|
+
url: string;
|
|
29
|
+
}
|
|
30
|
+
export interface LinearTeam {
|
|
31
|
+
id: string;
|
|
32
|
+
name: string;
|
|
33
|
+
key: string;
|
|
34
|
+
}
|
|
35
|
+
export interface CreateProjectInput {
|
|
36
|
+
name: string;
|
|
37
|
+
description?: string;
|
|
38
|
+
teamIds: string[];
|
|
39
|
+
state?: string;
|
|
40
|
+
}
|
|
41
|
+
export interface UpdateProjectInput {
|
|
42
|
+
name?: string;
|
|
43
|
+
description?: string;
|
|
44
|
+
state?: string;
|
|
45
|
+
}
|
|
46
|
+
export interface CreateMilestoneInput {
|
|
47
|
+
projectId: string;
|
|
48
|
+
name: string;
|
|
49
|
+
description?: string;
|
|
50
|
+
targetDate?: string;
|
|
51
|
+
}
|
|
52
|
+
export interface CreateIssueInput {
|
|
53
|
+
title: string;
|
|
54
|
+
description?: string;
|
|
55
|
+
teamId: string;
|
|
56
|
+
projectId?: string;
|
|
57
|
+
milestoneId?: string;
|
|
58
|
+
priority?: number;
|
|
59
|
+
state?: string;
|
|
60
|
+
}
|
|
61
|
+
export interface UpdateIssueInput {
|
|
62
|
+
title?: string;
|
|
63
|
+
description?: string;
|
|
64
|
+
state?: string;
|
|
65
|
+
milestoneId?: string;
|
|
66
|
+
priority?: number;
|
|
67
|
+
}
|
|
68
|
+
export declare class LinearClientError extends Error {
|
|
69
|
+
readonly statusCode?: number | undefined;
|
|
70
|
+
readonly errors?: Array<{
|
|
71
|
+
message: string;
|
|
72
|
+
}> | undefined;
|
|
73
|
+
constructor(message: string, statusCode?: number | undefined, errors?: Array<{
|
|
74
|
+
message: string;
|
|
75
|
+
}> | undefined);
|
|
76
|
+
}
|
|
77
|
+
export declare class LinearClient {
|
|
78
|
+
private readonly apiKey;
|
|
79
|
+
constructor(apiKey?: string);
|
|
80
|
+
listProjects(opts?: {
|
|
81
|
+
query?: string;
|
|
82
|
+
state?: string;
|
|
83
|
+
}): Promise<LinearProject[]>;
|
|
84
|
+
createProject(input: CreateProjectInput): Promise<LinearProject>;
|
|
85
|
+
updateProject(id: string, input: UpdateProjectInput): Promise<LinearProject>;
|
|
86
|
+
listMilestones(projectId: string): Promise<LinearMilestone[]>;
|
|
87
|
+
createMilestone(input: CreateMilestoneInput): Promise<LinearMilestone>;
|
|
88
|
+
listIssues(opts?: {
|
|
89
|
+
projectId?: string;
|
|
90
|
+
milestoneId?: string;
|
|
91
|
+
state?: string;
|
|
92
|
+
}): Promise<LinearIssue[]>;
|
|
93
|
+
createIssue(input: CreateIssueInput): Promise<LinearIssue>;
|
|
94
|
+
updateIssue(id: string, input: UpdateIssueInput): Promise<LinearIssue>;
|
|
95
|
+
createComment(issueId: string, body: string): Promise<void>;
|
|
96
|
+
listTeams(): Promise<LinearTeam[]>;
|
|
97
|
+
/**
|
|
98
|
+
* Execute a GraphQL request against the Linear API with retry + backoff.
|
|
99
|
+
*/
|
|
100
|
+
private request;
|
|
101
|
+
/**
|
|
102
|
+
* Auto-paginate a connection query. The query MUST accept `$after: String`
|
|
103
|
+
* and the root field must return `{ pageInfo { hasNextPage endCursor } nodes { ... } }`.
|
|
104
|
+
*/
|
|
105
|
+
private paginate;
|
|
106
|
+
/** Normalize a raw issue node from GraphQL into our flat LinearIssue shape. */
|
|
107
|
+
private mapIssue;
|
|
108
|
+
}
|