@warpmetrics/coder 0.1.4 → 0.2.2

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/bin/init.js DELETED
@@ -1,213 +0,0 @@
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
- function getExistingSecrets() {
23
- try {
24
- execSync('gh --version', { stdio: 'ignore' });
25
- } catch {
26
- return null; // gh not available
27
- }
28
- try {
29
- const output = execSync('gh secret list', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] });
30
- const names = output.split('\n').map(line => line.split('\t')[0].trim()).filter(Boolean);
31
- return new Set(names);
32
- } catch {
33
- return new Set(); // gh available but couldn't list (e.g. no repo context)
34
- }
35
- }
36
-
37
- async function main() {
38
- log('');
39
- log(' warp-coder \u2014 Agent pipeline for implementing GitHub issues');
40
- log('');
41
-
42
- const existingSecrets = getExistingSecrets();
43
- const ghAvailable = existingSecrets !== null;
44
-
45
- // 1. Anthropic API key
46
- let anthropicKey = null;
47
- if (existingSecrets?.has('ANTHROPIC_API_KEY')) {
48
- log(' \u2713 ANTHROPIC_API_KEY already set');
49
- const replace = await ask(' Replace it? (y/N): ');
50
- if (replace.toLowerCase() === 'y') {
51
- anthropicKey = await ask(' ? Anthropic API key: ');
52
- }
53
- } else {
54
- anthropicKey = await ask(' ? Anthropic API key: ');
55
- }
56
- if (anthropicKey && !anthropicKey.startsWith('sk-ant-')) {
57
- log(' \u26a0 Warning: key doesn\'t start with sk-ant- \u2014 make sure this is a valid Anthropic API key');
58
- }
59
-
60
- // 2. WarpMetrics API key
61
- let wmKey = null;
62
- if (existingSecrets?.has('WARPMETRICS_API_KEY')) {
63
- log(' \u2713 WARPMETRICS_API_KEY already set');
64
- const replace = await ask(' Replace it? (y/N): ');
65
- if (replace.toLowerCase() === 'y') {
66
- wmKey = await ask(' ? WarpMetrics API key (get one at warpmetrics.com/app/api-keys): ');
67
- }
68
- } else {
69
- wmKey = await ask(' ? WarpMetrics API key (get one at warpmetrics.com/app/api-keys): ');
70
- }
71
- if (wmKey && !wmKey.startsWith('wm_')) {
72
- log(' \u26a0 Warning: key doesn\'t start with wm_ \u2014 make sure this is a valid WarpMetrics API key');
73
- }
74
-
75
- log('');
76
-
77
- // 3. Set GitHub secrets
78
- if (ghAvailable) {
79
- if (anthropicKey) {
80
- try {
81
- execSync('gh secret set ANTHROPIC_API_KEY', { input: anthropicKey, stdio: ['pipe', 'ignore', 'ignore'] });
82
- log(' \u2713 ANTHROPIC_API_KEY set');
83
- } catch (e) {
84
- log(` \u2717 Failed to set ANTHROPIC_API_KEY: ${e.message}`);
85
- }
86
- }
87
- if (wmKey) {
88
- try {
89
- execSync('gh secret set WARPMETRICS_API_KEY', { input: wmKey, stdio: ['pipe', 'ignore', 'ignore'] });
90
- log(' \u2713 WARPMETRICS_API_KEY set');
91
- } catch (e) {
92
- log(` \u2717 Failed to set WARPMETRICS_API_KEY: ${e.message}`);
93
- }
94
- }
95
- } else {
96
- log(' gh (GitHub CLI) not found. Set these secrets manually:');
97
- log('');
98
- log(' gh secret set ANTHROPIC_API_KEY');
99
- log(' gh secret set WARPMETRICS_API_KEY');
100
- log(' (gh will prompt for the value interactively)');
101
- }
102
- log('');
103
-
104
- // 4. Copy workflows
105
- await copyWorkflow('agent-implement.yml');
106
- await copyWorkflow('agent-revise.yml');
107
-
108
- // 5. Copy scripts
109
- await copyScripts();
110
-
111
- log('');
112
-
113
- // 6. Register outcome classifications
114
- if (wmKey) {
115
- log(' Registering outcome classifications with WarpMetrics...');
116
- const classifications = [
117
- { name: 'PR Created', classification: 'success' },
118
- { name: 'Fixes Applied', classification: 'success' },
119
- { name: 'Issue Understood', classification: 'success' },
120
- { name: 'Needs Clarification', classification: 'neutral' },
121
- { name: 'Needs Human', classification: 'neutral' },
122
- { name: 'Implementation Failed', classification: 'failure' },
123
- { name: 'Tests Failed', classification: 'failure' },
124
- { name: 'Revision Failed', classification: 'failure' },
125
- { name: 'Max Retries', classification: 'failure' },
126
- ];
127
-
128
- let classOk = true;
129
- for (const { name, classification } of classifications) {
130
- try {
131
- const res = await fetch(`https://api.warpmetrics.com/v1/outcomes/classifications/${encodeURIComponent(name)}`, {
132
- method: 'PUT',
133
- headers: {
134
- Authorization: `Bearer ${wmKey}`,
135
- 'Content-Type': 'application/json',
136
- },
137
- body: JSON.stringify({ classification }),
138
- });
139
- if (!res.ok) {
140
- classOk = false;
141
- console.warn(` \u26a0 Failed to set classification ${name}: ${res.status}`);
142
- }
143
- } catch (e) {
144
- classOk = false;
145
- console.warn(` \u26a0 Failed to set classification ${name}: ${e.message}`);
146
- }
147
- }
148
- if (classOk) {
149
- log(' \u2713 Outcomes configured');
150
- } else {
151
- log(' \u26a0 Some classifications failed \u2014 you can set them manually in the WarpMetrics dashboard');
152
- }
153
- } else {
154
- log(' Skipping outcome classifications (WarpMetrics key not provided)');
155
- }
156
-
157
- // 7. Check for warp-review
158
- log('');
159
- if (!existsSync('.github/workflows/warp-review.yml')) {
160
- log(' \u26a0 warp-review not found. Install it for automated code reviews on agent PRs:');
161
- log(' npx @warpmetrics/review init');
162
- } else {
163
- log(' \u2713 warp-review detected \u2014 agent PRs will be reviewed automatically');
164
- }
165
-
166
- // 8. Print next steps
167
- log('');
168
- log(' Done! Next steps:');
169
- log(' 1. git add .github/workflows .github/scripts');
170
- log(' 2. git commit -m "Add warp-coder agent pipeline"');
171
- log(' 3. Label a GitHub issue with "agent" to trigger implementation');
172
- log(' 4. View pipeline analytics at https://app.warpmetrics.com');
173
- log('');
174
-
175
- rl.close();
176
- }
177
-
178
- async function copyWorkflow(filename) {
179
- const dest = `.github/workflows/${filename}`;
180
- if (existsSync(dest)) {
181
- const overwrite = await ask(` ${filename} already exists. Overwrite? (y/N): `);
182
- if (overwrite.toLowerCase() !== 'y') {
183
- log(` Skipping ${filename}`);
184
- return;
185
- }
186
- }
187
- mkdirSync('.github/workflows', { recursive: true });
188
- copyFileSync(join(defaultsDir, filename), dest);
189
- log(` \u2713 ${dest} created`);
190
- }
191
-
192
- async function copyScripts() {
193
- const scriptsDir = '.github/scripts';
194
- if (existsSync(scriptsDir)) {
195
- const overwrite = await ask(' .github/scripts/ already exists. Overwrite? (y/N): ');
196
- if (overwrite.toLowerCase() !== 'y') {
197
- log(' Skipping scripts');
198
- return;
199
- }
200
- }
201
- mkdirSync(scriptsDir, { recursive: true });
202
- const srcDir = join(defaultsDir, 'scripts');
203
- for (const file of readdirSync(srcDir)) {
204
- copyFileSync(join(srcDir, file), join(scriptsDir, file));
205
- }
206
- log(` \u2713 .github/scripts/ created (${readdirSync(srcDir).length} files)`);
207
- }
208
-
209
- main().catch(err => {
210
- console.error('init failed:', err.message);
211
- process.exitCode = 1;
212
- rl.close();
213
- });
@@ -1,75 +0,0 @@
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
- id-token: write
12
-
13
- jobs:
14
- implement:
15
- if: github.event.label.name == 'agent'
16
- runs-on: ubuntu-latest
17
- timeout-minutes: 30
18
-
19
- steps:
20
- - uses: actions/checkout@v4
21
-
22
- - uses: actions/setup-node@v4
23
- with:
24
- node-version: 22
25
-
26
- - name: Start pipeline
27
- run: node .github/scripts/pipeline-start.js
28
- env:
29
- WARPMETRICS_API_KEY: ${{ secrets.WARPMETRICS_API_KEY }}
30
- GITHUB_REPOSITORY: ${{ github.repository }}
31
- STEP: implement
32
- ISSUE_NUMBER: ${{ github.event.issue.number }}
33
- ISSUE_TITLE: ${{ github.event.issue.title }}
34
-
35
- - name: Implement issue
36
- id: agent
37
- uses: anthropics/claude-code-action@v1
38
- with:
39
- anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
40
- direct_prompt: |
41
- You are working on the repository ${{ github.repository }}.
42
-
43
- Implement the following GitHub issue:
44
-
45
- **#${{ github.event.issue.number }}: ${{ github.event.issue.title }}**
46
-
47
- ${{ github.event.issue.body }}
48
-
49
- Steps:
50
- 1. Read the codebase to understand relevant context
51
- 2. Create a new branch: agent/issue-${{ github.event.issue.number }}
52
- 3. Implement the changes
53
- 4. Run tests to verify nothing is broken
54
- 5. Commit with a clear message
55
- 6. Push the branch and open a pull request
56
- 7. Include "Closes #${{ github.event.issue.number }}" in the PR body
57
-
58
- If the issue is unclear or you cannot implement it, explain what is missing.
59
-
60
- - name: Record outcome
61
- if: always()
62
- run: node .github/scripts/pipeline-outcome.js
63
- env:
64
- WARPMETRICS_API_KEY: ${{ secrets.WARPMETRICS_API_KEY }}
65
- STEP: implement
66
- STATUS: ${{ steps.agent.outcome }}
67
-
68
- - name: Comment on failure
69
- if: failure()
70
- run: |
71
- gh issue comment ${{ github.event.issue.number }} \
72
- --body "Agent failed to implement this issue. See the [workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})."
73
- gh issue edit ${{ github.event.issue.number }} --add-label "agent-failed" --remove-label "agent"
74
- env:
75
- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -1,82 +0,0 @@
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
- id-token: write
11
-
12
- jobs:
13
- revise:
14
- # Only trigger when warp-review (bot) submits a review with comments
15
- # and the PR was created by the agent (has "agent" label on the linked issue)
16
- if: >
17
- github.event.review.user.login == 'github-actions[bot]' &&
18
- github.event.review.state == 'COMMENTED'
19
- runs-on: ubuntu-latest
20
- timeout-minutes: 20
21
-
22
- steps:
23
- - uses: actions/checkout@v4
24
- with:
25
- ref: ${{ github.event.pull_request.head.ref }}
26
-
27
- - uses: actions/setup-node@v4
28
- with:
29
- node-version: 22
30
-
31
- - name: Check revision limit
32
- id: check
33
- run: node .github/scripts/check-revision-limit.js
34
- env:
35
- WARPMETRICS_API_KEY: ${{ secrets.WARPMETRICS_API_KEY }}
36
- GITHUB_REPOSITORY: ${{ github.repository }}
37
- PR_NUMBER: ${{ github.event.pull_request.number }}
38
-
39
- - name: Start pipeline
40
- if: steps.check.outputs.should_revise == 'true'
41
- run: node .github/scripts/pipeline-start.js
42
- env:
43
- WARPMETRICS_API_KEY: ${{ secrets.WARPMETRICS_API_KEY }}
44
- GITHUB_REPOSITORY: ${{ github.repository }}
45
- STEP: revise
46
- PR_NUMBER: ${{ github.event.pull_request.number }}
47
-
48
- - name: Apply review feedback
49
- id: agent
50
- if: steps.check.outputs.should_revise == 'true'
51
- uses: anthropics/claude-code-action@v1
52
- with:
53
- anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
54
- direct_prompt: |
55
- You are working on PR #${{ github.event.pull_request.number }} in ${{ github.repository }}.
56
-
57
- A code review has been submitted with comments. Your job:
58
-
59
- 1. Read all review comments on this PR
60
- 2. Apply the suggested fixes
61
- 3. Run tests to make sure everything passes
62
- 4. Commit the fixes with a message like "Address review feedback"
63
- 5. Push to the same branch
64
-
65
- Do NOT open a new PR — just push to the existing branch.
66
-
67
- - name: Record outcome
68
- if: always() && steps.check.outputs.should_revise == 'true'
69
- run: node .github/scripts/pipeline-outcome.js
70
- env:
71
- WARPMETRICS_API_KEY: ${{ secrets.WARPMETRICS_API_KEY }}
72
- STEP: revise
73
- STATUS: ${{ steps.agent.outcome }}
74
-
75
- - name: Request human review
76
- if: steps.check.outputs.should_revise == 'false'
77
- run: |
78
- gh pr comment ${{ github.event.pull_request.number }} \
79
- --body "Agent has reached the revision limit (${{ steps.check.outputs.revision_count }} attempts). Requesting human review."
80
- gh pr edit ${{ github.event.pull_request.number }} --add-label "needs-human"
81
- env:
82
- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -1,47 +0,0 @@
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
- }
@@ -1,33 +0,0 @@
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
- }
@@ -1,40 +0,0 @@
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(() => {});