@warpmetrics/coder 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 WarpMetrics
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,64 @@
1
+ # @warpmetrics/coder
2
+
3
+ Agent pipeline for implementing GitHub issues with Claude Code. Label an issue with `agent` and it gets implemented, reviewed, and revised automatically.
4
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ npx @warpmetrics/coder init
9
+ ```
10
+
11
+ This will:
12
+ 1. Set up `ANTHROPIC_API_KEY` and `WARPMETRICS_API_KEY` as GitHub secrets
13
+ 2. Add two workflow files to `.github/workflows/`
14
+ 3. Add pipeline scripts to `.github/scripts/`
15
+ 4. Register outcome classifications with WarpMetrics
16
+
17
+ ## How It Works
18
+
19
+ ```
20
+ Issue labeled "agent"
21
+ → agent-implement.yml runs Claude Code Action
22
+ → Claude reads the issue, creates a branch, implements, opens PR
23
+ → warp-review reviews the PR (if installed)
24
+ → agent-revise.yml applies feedback and pushes fixes
25
+ → Loop until approved or revision limit (3) reached
26
+ ```
27
+
28
+ Every step is instrumented with [WarpMetrics](https://warpmetrics.com) — you get runs, groups, and outcomes tracking the full pipeline.
29
+
30
+ ## Workflows
31
+
32
+ ### agent-implement.yml
33
+
34
+ Triggered when an issue is labeled `agent`. Creates a branch `agent/issue-{number}`, implements the issue, and opens a PR.
35
+
36
+ ### agent-revise.yml
37
+
38
+ Triggered when `github-actions[bot]` submits a review with comments (i.e., warp-review feedback). Applies the review feedback and pushes to the same branch. Stops after 3 revision attempts.
39
+
40
+ ## Outcome Classifications
41
+
42
+ | Name | Classification |
43
+ |------|---------------|
44
+ | PR Created | success |
45
+ | Fixes Applied | success |
46
+ | Issue Understood | success |
47
+ | Needs Clarification | neutral |
48
+ | Needs Human | neutral |
49
+ | Implementation Failed | failure |
50
+ | Tests Failed | failure |
51
+ | Revision Failed | failure |
52
+ | Max Retries | failure |
53
+
54
+ ## Pairing with warp-review
55
+
56
+ For the full implement → review → revise loop, install [warp-review](https://github.com/warpmetrics/warp-review):
57
+
58
+ ```bash
59
+ npx @warpmetrics/review init
60
+ ```
61
+
62
+ ## License
63
+
64
+ MIT
package/bin/init.js ADDED
@@ -0,0 +1,178 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { createInterface } from 'readline';
4
+ import { existsSync, mkdirSync, copyFileSync, readdirSync } from 'fs';
5
+ import { execSync } from 'child_process';
6
+ import { fileURLToPath } from 'url';
7
+ import { dirname, join } from 'path';
8
+
9
+ const __dirname = dirname(fileURLToPath(import.meta.url));
10
+ const defaultsDir = join(__dirname, '..', 'defaults');
11
+
12
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
13
+
14
+ function ask(question) {
15
+ return new Promise(resolve => rl.question(question, resolve));
16
+ }
17
+
18
+ function log(msg) {
19
+ console.log(msg);
20
+ }
21
+
22
+ async function main() {
23
+ log('');
24
+ log(' warp-coder \u2014 Agent pipeline for implementing GitHub issues');
25
+ log('');
26
+
27
+ // 1. Anthropic API key
28
+ const anthropicKey = await ask(' ? Anthropic API key: ');
29
+ if (!anthropicKey.startsWith('sk-ant-')) {
30
+ log(' \u26a0 Warning: key doesn\'t start with sk-ant- \u2014 make sure this is a valid Anthropic API key');
31
+ }
32
+
33
+ // 2. WarpMetrics API key
34
+ const wmKey = await ask(' ? WarpMetrics API key (get one at warpmetrics.com/app/api-keys): ');
35
+ if (!wmKey.startsWith('wm_')) {
36
+ log(' \u26a0 Warning: key doesn\'t start with wm_ \u2014 make sure this is a valid WarpMetrics API key');
37
+ }
38
+
39
+ log('');
40
+
41
+ // 3. Set GitHub secrets
42
+ let ghAvailable = false;
43
+ try {
44
+ execSync('gh --version', { stdio: 'ignore' });
45
+ ghAvailable = true;
46
+ } catch {
47
+ ghAvailable = false;
48
+ }
49
+
50
+ if (ghAvailable) {
51
+ log(' Setting GitHub secrets...');
52
+ try {
53
+ execSync('gh secret set ANTHROPIC_API_KEY', { input: anthropicKey, stdio: ['pipe', 'ignore', 'ignore'] });
54
+ log(' \u2713 ANTHROPIC_API_KEY set');
55
+ } catch (e) {
56
+ log(` \u2717 Failed to set ANTHROPIC_API_KEY: ${e.message}`);
57
+ }
58
+ try {
59
+ execSync('gh secret set WARPMETRICS_API_KEY', { input: wmKey, stdio: ['pipe', 'ignore', 'ignore'] });
60
+ log(' \u2713 WARPMETRICS_API_KEY set');
61
+ } catch (e) {
62
+ log(` \u2717 Failed to set WARPMETRICS_API_KEY: ${e.message}`);
63
+ }
64
+ } else {
65
+ log(' gh (GitHub CLI) not found. Set these secrets manually:');
66
+ log('');
67
+ log(' gh secret set ANTHROPIC_API_KEY');
68
+ log(' gh secret set WARPMETRICS_API_KEY');
69
+ log(' (gh will prompt for the value interactively)');
70
+ }
71
+ log('');
72
+
73
+ // 4. Copy workflows
74
+ await copyWorkflow('agent-implement.yml');
75
+ await copyWorkflow('agent-revise.yml');
76
+
77
+ // 5. Copy scripts
78
+ await copyScripts();
79
+
80
+ log('');
81
+
82
+ // 6. Register outcome classifications
83
+ log(' Registering outcome classifications with WarpMetrics...');
84
+ const classifications = [
85
+ { name: 'PR Created', classification: 'success' },
86
+ { name: 'Fixes Applied', classification: 'success' },
87
+ { name: 'Issue Understood', classification: 'success' },
88
+ { name: 'Needs Clarification', classification: 'neutral' },
89
+ { name: 'Needs Human', classification: 'neutral' },
90
+ { name: 'Implementation Failed', classification: 'failure' },
91
+ { name: 'Tests Failed', classification: 'failure' },
92
+ { name: 'Revision Failed', classification: 'failure' },
93
+ { name: 'Max Retries', classification: 'failure' },
94
+ ];
95
+
96
+ let classOk = true;
97
+ for (const { name, classification } of classifications) {
98
+ try {
99
+ const res = await fetch(`https://api.warpmetrics.com/v1/outcomes/classifications/${encodeURIComponent(name)}`, {
100
+ method: 'PUT',
101
+ headers: {
102
+ Authorization: `Bearer ${wmKey}`,
103
+ 'Content-Type': 'application/json',
104
+ },
105
+ body: JSON.stringify({ classification }),
106
+ });
107
+ if (!res.ok) {
108
+ classOk = false;
109
+ console.warn(` \u26a0 Failed to set classification ${name}: ${res.status}`);
110
+ }
111
+ } catch (e) {
112
+ classOk = false;
113
+ console.warn(` \u26a0 Failed to set classification ${name}: ${e.message}`);
114
+ }
115
+ }
116
+ if (classOk) {
117
+ log(' \u2713 Outcomes configured');
118
+ } else {
119
+ log(' \u26a0 Some classifications failed \u2014 you can set them manually in the WarpMetrics dashboard');
120
+ }
121
+
122
+ // 7. Check for warp-review
123
+ log('');
124
+ if (!existsSync('.github/workflows/warp-review.yml')) {
125
+ log(' \u26a0 warp-review not found. Install it for automated code reviews on agent PRs:');
126
+ log(' npx @warpmetrics/review init');
127
+ } else {
128
+ log(' \u2713 warp-review detected \u2014 agent PRs will be reviewed automatically');
129
+ }
130
+
131
+ // 8. Print next steps
132
+ log('');
133
+ log(' Done! Next steps:');
134
+ log(' 1. git add .github/workflows .github/scripts');
135
+ log(' 2. git commit -m "Add warp-coder agent pipeline"');
136
+ log(' 3. Label a GitHub issue with "agent" to trigger implementation');
137
+ log(' 4. View pipeline analytics at https://app.warpmetrics.com');
138
+ log('');
139
+
140
+ rl.close();
141
+ }
142
+
143
+ async function copyWorkflow(filename) {
144
+ const dest = `.github/workflows/${filename}`;
145
+ if (existsSync(dest)) {
146
+ const overwrite = await ask(` ${filename} already exists. Overwrite? (y/N): `);
147
+ if (overwrite.toLowerCase() !== 'y') {
148
+ log(` Skipping ${filename}`);
149
+ return;
150
+ }
151
+ }
152
+ mkdirSync('.github/workflows', { recursive: true });
153
+ copyFileSync(join(defaultsDir, filename), dest);
154
+ log(` \u2713 ${dest} created`);
155
+ }
156
+
157
+ async function copyScripts() {
158
+ const scriptsDir = '.github/scripts';
159
+ if (existsSync(scriptsDir)) {
160
+ const overwrite = await ask(' .github/scripts/ already exists. Overwrite? (y/N): ');
161
+ if (overwrite.toLowerCase() !== 'y') {
162
+ log(' Skipping scripts');
163
+ return;
164
+ }
165
+ }
166
+ mkdirSync(scriptsDir, { recursive: true });
167
+ const srcDir = join(defaultsDir, 'scripts');
168
+ for (const file of readdirSync(srcDir)) {
169
+ copyFileSync(join(srcDir, file), join(scriptsDir, file));
170
+ }
171
+ log(` \u2713 .github/scripts/ created (${readdirSync(srcDir).length} files)`);
172
+ }
173
+
174
+ main().catch(err => {
175
+ console.error('init failed:', err.message);
176
+ process.exitCode = 1;
177
+ rl.close();
178
+ });
@@ -0,0 +1,74 @@
1
+ name: Agent — Implement Issue
2
+
3
+ on:
4
+ issues:
5
+ types: [labeled]
6
+
7
+ permissions:
8
+ contents: write
9
+ pull-requests: write
10
+ issues: write
11
+
12
+ jobs:
13
+ implement:
14
+ if: github.event.label.name == 'agent'
15
+ runs-on: ubuntu-latest
16
+ timeout-minutes: 30
17
+
18
+ steps:
19
+ - uses: actions/checkout@v4
20
+
21
+ - uses: actions/setup-node@v4
22
+ with:
23
+ node-version: 22
24
+
25
+ - name: Start pipeline
26
+ run: node .github/scripts/pipeline-start.js
27
+ env:
28
+ WARPMETRICS_API_KEY: ${{ secrets.WARPMETRICS_API_KEY }}
29
+ GITHUB_REPOSITORY: ${{ github.repository }}
30
+ STEP: implement
31
+ ISSUE_NUMBER: ${{ github.event.issue.number }}
32
+ ISSUE_TITLE: ${{ github.event.issue.title }}
33
+
34
+ - name: Implement issue
35
+ id: agent
36
+ uses: anthropics/claude-code-action@v1
37
+ with:
38
+ anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
39
+ direct_prompt: |
40
+ You are working on the repository ${{ github.repository }}.
41
+
42
+ Implement the following GitHub issue:
43
+
44
+ **#${{ github.event.issue.number }}: ${{ github.event.issue.title }}**
45
+
46
+ ${{ github.event.issue.body }}
47
+
48
+ Steps:
49
+ 1. Read the codebase to understand relevant context
50
+ 2. Create a new branch: agent/issue-${{ github.event.issue.number }}
51
+ 3. Implement the changes
52
+ 4. Run tests to verify nothing is broken
53
+ 5. Commit with a clear message
54
+ 6. Push the branch and open a pull request
55
+ 7. Include "Closes #${{ github.event.issue.number }}" in the PR body
56
+
57
+ If the issue is unclear or you cannot implement it, explain what is missing.
58
+
59
+ - name: Record outcome
60
+ if: always()
61
+ run: node .github/scripts/pipeline-outcome.js
62
+ env:
63
+ WARPMETRICS_API_KEY: ${{ secrets.WARPMETRICS_API_KEY }}
64
+ STEP: implement
65
+ STATUS: ${{ steps.agent.outcome }}
66
+
67
+ - name: Comment on failure
68
+ if: failure()
69
+ run: |
70
+ gh issue comment ${{ github.event.issue.number }} \
71
+ --body "Agent failed to implement this issue. See the [workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})."
72
+ gh issue edit ${{ github.event.issue.number }} --add-label "agent-failed" --remove-label "agent"
73
+ env:
74
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -0,0 +1,81 @@
1
+ name: Agent — Revise PR
2
+
3
+ on:
4
+ pull_request_review:
5
+ types: [submitted]
6
+
7
+ permissions:
8
+ contents: write
9
+ pull-requests: write
10
+
11
+ jobs:
12
+ revise:
13
+ # Only trigger when warp-review (bot) submits a review with comments
14
+ # and the PR was created by the agent (has "agent" label on the linked issue)
15
+ if: >
16
+ github.event.review.user.login == 'github-actions[bot]' &&
17
+ github.event.review.state == 'COMMENTED'
18
+ runs-on: ubuntu-latest
19
+ timeout-minutes: 20
20
+
21
+ steps:
22
+ - uses: actions/checkout@v4
23
+ with:
24
+ ref: ${{ github.event.pull_request.head.ref }}
25
+
26
+ - uses: actions/setup-node@v4
27
+ with:
28
+ node-version: 22
29
+
30
+ - name: Check revision limit
31
+ id: check
32
+ run: node .github/scripts/check-revision-limit.js
33
+ env:
34
+ WARPMETRICS_API_KEY: ${{ secrets.WARPMETRICS_API_KEY }}
35
+ GITHUB_REPOSITORY: ${{ github.repository }}
36
+ PR_NUMBER: ${{ github.event.pull_request.number }}
37
+
38
+ - name: Start pipeline
39
+ if: steps.check.outputs.should_revise == 'true'
40
+ run: node .github/scripts/pipeline-start.js
41
+ env:
42
+ WARPMETRICS_API_KEY: ${{ secrets.WARPMETRICS_API_KEY }}
43
+ GITHUB_REPOSITORY: ${{ github.repository }}
44
+ STEP: revise
45
+ PR_NUMBER: ${{ github.event.pull_request.number }}
46
+
47
+ - name: Apply review feedback
48
+ id: agent
49
+ if: steps.check.outputs.should_revise == 'true'
50
+ uses: anthropics/claude-code-action@v1
51
+ with:
52
+ anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
53
+ direct_prompt: |
54
+ You are working on PR #${{ github.event.pull_request.number }} in ${{ github.repository }}.
55
+
56
+ A code review has been submitted with comments. Your job:
57
+
58
+ 1. Read all review comments on this PR
59
+ 2. Apply the suggested fixes
60
+ 3. Run tests to make sure everything passes
61
+ 4. Commit the fixes with a message like "Address review feedback"
62
+ 5. Push to the same branch
63
+
64
+ Do NOT open a new PR — just push to the existing branch.
65
+
66
+ - name: Record outcome
67
+ if: always() && steps.check.outputs.should_revise == 'true'
68
+ run: node .github/scripts/pipeline-outcome.js
69
+ env:
70
+ WARPMETRICS_API_KEY: ${{ secrets.WARPMETRICS_API_KEY }}
71
+ STEP: revise
72
+ STATUS: ${{ steps.agent.outcome }}
73
+
74
+ - name: Request human review
75
+ if: steps.check.outputs.should_revise == 'false'
76
+ run: |
77
+ gh pr comment ${{ github.event.pull_request.number }} \
78
+ --body "Agent has reached the revision limit (${{ steps.check.outputs.revision_count }} attempts). Requesting human review."
79
+ gh pr edit ${{ github.event.pull_request.number }} --add-label "needs-human"
80
+ env:
81
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -0,0 +1,47 @@
1
+ // Agent pipeline — check if we should attempt another revision.
2
+ // Queries WarpMetrics for previous "revise" runs on this PR.
3
+ // Sets GitHub Actions outputs: should_revise, revision_count.
4
+
5
+ import { appendFileSync } from 'fs';
6
+ import { findRuns } from './pipeline.js';
7
+
8
+ const apiKey = process.env.WARPMETRICS_API_KEY;
9
+ const prNumber = process.env.PR_NUMBER;
10
+ const repo = process.env.GITHUB_REPOSITORY;
11
+ const MAX_REVISIONS = 3;
12
+
13
+ function setOutput(key, value) {
14
+ if (process.env.GITHUB_OUTPUT) {
15
+ appendFileSync(process.env.GITHUB_OUTPUT, `${key}=${value}\n`);
16
+ }
17
+ console.log(`${key}=${value}`);
18
+ }
19
+
20
+ if (!apiKey) {
21
+ setOutput('should_revise', 'true');
22
+ setOutput('revision_count', '0');
23
+ process.exit(0);
24
+ }
25
+
26
+ try {
27
+ const runs = await findRuns(apiKey, 'agent-pipeline');
28
+ const revisions = runs.filter(r =>
29
+ r.opts?.step === 'revise' &&
30
+ r.opts?.pr_number === String(prNumber) &&
31
+ r.opts?.repo === repo
32
+ );
33
+
34
+ const count = revisions.length;
35
+ const shouldRevise = count < MAX_REVISIONS;
36
+
37
+ setOutput('should_revise', String(shouldRevise));
38
+ setOutput('revision_count', String(count));
39
+
40
+ console.log(shouldRevise
41
+ ? `Revision ${count + 1}/${MAX_REVISIONS}`
42
+ : `Revision limit reached (${count}/${MAX_REVISIONS})`);
43
+ } catch (err) {
44
+ console.warn(`Revision check failed: ${err.message} — allowing revision`);
45
+ setOutput('should_revise', 'true');
46
+ setOutput('revision_count', '0');
47
+ }
@@ -0,0 +1,33 @@
1
+ // Agent pipeline — record an outcome after the agent step.
2
+
3
+ import { generateId, sendEvents, loadState } from './pipeline.js';
4
+
5
+ const apiKey = process.env.WARPMETRICS_API_KEY;
6
+ const status = process.env.STATUS; // "success" or "failure"
7
+ const step = process.env.STEP;
8
+
9
+ if (!apiKey) process.exit(0);
10
+
11
+ const state = loadState();
12
+ if (!state) {
13
+ console.warn('No pipeline state — skipping outcome');
14
+ process.exit(0);
15
+ }
16
+
17
+ const names = {
18
+ implement: { success: 'PR Created', failure: 'Implementation Failed' },
19
+ revise: { success: 'Fixes Applied', failure: 'Revision Failed' },
20
+ };
21
+
22
+ const name = names[step]?.[status] || `${step}: ${status}`;
23
+ const id = generateId('oc');
24
+ const now = new Date().toISOString();
25
+
26
+ try {
27
+ await sendEvents(apiKey, {
28
+ outcomes: [{ id, refId: state.groupId, name, opts: { status, step }, timestamp: now }],
29
+ });
30
+ console.log(`Outcome: ${name} (${id})`);
31
+ } catch (err) {
32
+ console.warn(`Failed to record outcome: ${err.message}`);
33
+ }
@@ -0,0 +1,40 @@
1
+ // Agent pipeline — create a WarpMetrics run + group before the agent step.
2
+
3
+ import { generateId, sendEvents, registerClassifications, saveState } from './pipeline.js';
4
+
5
+ const apiKey = process.env.WARPMETRICS_API_KEY;
6
+ const repo = process.env.GITHUB_REPOSITORY;
7
+ const step = process.env.STEP; // "implement" or "revise"
8
+ const issueNumber = process.env.ISSUE_NUMBER || null;
9
+ const issueTitle = process.env.ISSUE_TITLE || null;
10
+ const prNumber = process.env.PR_NUMBER || null;
11
+
12
+ if (!apiKey) {
13
+ console.log('WARPMETRICS_API_KEY not set — skipping');
14
+ process.exit(0);
15
+ }
16
+
17
+ const runId = generateId('run');
18
+ const groupId = generateId('grp');
19
+ const now = new Date().toISOString();
20
+
21
+ const opts = { repo, step };
22
+ if (issueNumber) opts.issue = issueNumber;
23
+ if (issueTitle) opts.title = issueTitle;
24
+ if (prNumber) opts.pr_number = prNumber;
25
+
26
+ try {
27
+ await sendEvents(apiKey, {
28
+ runs: [{ id: runId, label: 'agent-pipeline', opts, refId: null, timestamp: now }],
29
+ groups: [{ id: groupId, label: step, opts: { triggered_at: now }, timestamp: now }],
30
+ links: [{ parentId: runId, childId: groupId, type: 'group', timestamp: now }],
31
+ });
32
+ console.log(`Pipeline run=${runId} group=${groupId} step=${step}`);
33
+ } catch (err) {
34
+ console.warn(`Failed to start pipeline: ${err.message}`);
35
+ }
36
+
37
+ saveState({ runId, groupId, step });
38
+
39
+ // Register outcome classifications on first run (idempotent)
40
+ await registerClassifications(apiKey).catch(() => {});
@@ -0,0 +1,110 @@
1
+ // Agent pipeline — shared helpers for WarpMetrics instrumentation.
2
+ // Zero external dependencies — uses Node built-ins + global fetch.
3
+
4
+ import crypto from 'crypto';
5
+ import { writeFileSync, readFileSync, existsSync } from 'fs';
6
+
7
+ const API_URL = 'https://api.warpmetrics.com';
8
+ const STATE_FILE = '.pipeline-state.json';
9
+
10
+ // ---------------------------------------------------------------------------
11
+ // ID generation
12
+ // ---------------------------------------------------------------------------
13
+
14
+ export function generateId(prefix) {
15
+ const t = Date.now().toString(36).padStart(10, '0');
16
+ const r = crypto.randomUUID().replace(/-/g, '').slice(0, 16);
17
+ return `wm_${prefix}_${t}${r}`;
18
+ }
19
+
20
+ // ---------------------------------------------------------------------------
21
+ // WarpMetrics Events API (same wire format as @warpmetrics/warp)
22
+ // ---------------------------------------------------------------------------
23
+
24
+ export async function sendEvents(apiKey, batch) {
25
+ const full = {
26
+ runs: batch.runs || [],
27
+ groups: batch.groups || [],
28
+ calls: batch.calls || [],
29
+ links: batch.links || [],
30
+ outcomes: batch.outcomes || [],
31
+ acts: batch.acts || [],
32
+ };
33
+
34
+ const raw = JSON.stringify(full);
35
+ const body = JSON.stringify({ d: Buffer.from(raw, 'utf-8').toString('base64') });
36
+
37
+ const res = await fetch(`${API_URL}/v1/events`, {
38
+ method: 'POST',
39
+ headers: {
40
+ 'Content-Type': 'application/json',
41
+ Authorization: `Bearer ${apiKey}`,
42
+ },
43
+ body,
44
+ });
45
+
46
+ if (!res.ok) {
47
+ const text = await res.text().catch(() => '');
48
+ throw new Error(`Events API ${res.status}: ${text}`);
49
+ }
50
+ }
51
+
52
+ // ---------------------------------------------------------------------------
53
+ // WarpMetrics Query API
54
+ // ---------------------------------------------------------------------------
55
+
56
+ export async function findRuns(apiKey, label, { limit = 20 } = {}) {
57
+ const params = new URLSearchParams({ label, limit: String(limit) });
58
+ const res = await fetch(`${API_URL}/v1/runs?${params}`, {
59
+ headers: { Authorization: `Bearer ${apiKey}` },
60
+ });
61
+ if (!res.ok) return [];
62
+ const data = await res.json();
63
+ return data.data || [];
64
+ }
65
+
66
+ // ---------------------------------------------------------------------------
67
+ // Outcome classifications (idempotent PUT)
68
+ // ---------------------------------------------------------------------------
69
+
70
+ export async function registerClassifications(apiKey) {
71
+ const items = [
72
+ { name: 'PR Created', classification: 'success' },
73
+ { name: 'Fixes Applied', classification: 'success' },
74
+ { name: 'Issue Understood', classification: 'success' },
75
+ { name: 'Needs Clarification', classification: 'neutral' },
76
+ { name: 'Needs Human', classification: 'neutral' },
77
+ { name: 'Implementation Failed', classification: 'failure' },
78
+ { name: 'Tests Failed', classification: 'failure' },
79
+ { name: 'Revision Failed', classification: 'failure' },
80
+ { name: 'Max Retries', classification: 'failure' },
81
+ ];
82
+
83
+ for (const { name, classification } of items) {
84
+ try {
85
+ await fetch(`${API_URL}/v1/outcomes/classifications/${encodeURIComponent(name)}`, {
86
+ method: 'PUT',
87
+ headers: {
88
+ Authorization: `Bearer ${apiKey}`,
89
+ 'Content-Type': 'application/json',
90
+ },
91
+ body: JSON.stringify({ classification }),
92
+ });
93
+ } catch {
94
+ // Best-effort
95
+ }
96
+ }
97
+ }
98
+
99
+ // ---------------------------------------------------------------------------
100
+ // Cross-step state
101
+ // ---------------------------------------------------------------------------
102
+
103
+ export function saveState(state) {
104
+ writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
105
+ }
106
+
107
+ export function loadState() {
108
+ if (!existsSync(STATE_FILE)) return null;
109
+ return JSON.parse(readFileSync(STATE_FILE, 'utf-8'));
110
+ }
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@warpmetrics/coder",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "Agent pipeline for implementing GitHub issues with Claude Code. Powered by WarpMetrics.",
6
+ "bin": {
7
+ "warp-coder": "./bin/init.js"
8
+ },
9
+ "scripts": {
10
+ "release:patch": "npm version patch && git push origin main --tags",
11
+ "release:minor": "npm version minor && git push origin main --tags"
12
+ },
13
+ "files": [
14
+ "bin/",
15
+ "defaults/",
16
+ "README.md",
17
+ "LICENSE"
18
+ ],
19
+ "license": "MIT",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "https://github.com/warpmetrics/warp-coder"
23
+ },
24
+ "keywords": [
25
+ "agent",
26
+ "claude-code",
27
+ "github-action",
28
+ "warpmetrics",
29
+ "llm",
30
+ "code-generation"
31
+ ]
32
+ }