opencode-pilot 0.18.1 → 0.18.3

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/AGENTS.md CHANGED
@@ -61,13 +61,14 @@ npx opencode-pilot status
61
61
 
62
62
  ## Configuration
63
63
 
64
- Config file: `~/.config/opencode-pilot/config.yaml`
64
+ Config file: `~/.config/opencode/pilot/config.yaml`
65
65
 
66
66
  Configuration has these sections:
67
- - `tools` - field mappings for MCP servers
68
- - `sources` - polling sources with generic MCP tool references
69
- - `repos` - per-repository settings (use YAML anchors to share config)
67
+ - `defaults` - default values applied to all sources
68
+ - `repos_dir` - directory to auto-discover repos via git remotes
69
+ - `sources` - polling sources with presets, shorthand, or full MCP tool config
70
+ - `tools` - field mappings to normalize different MCP APIs
70
71
 
71
- Template files: `~/.config/opencode-pilot/templates/*.md`
72
+ Template files: `~/.config/opencode/pilot/templates/*.md`
72
73
 
73
74
  See [examples/config.yaml](examples/config.yaml) for a complete example.
package/README.md CHANGED
@@ -65,11 +65,10 @@ Three ways to configure sources, from simplest to most flexible:
65
65
 
66
66
  - `github/my-issues` - Issues assigned to me
67
67
  - `github/review-requests` - PRs needing my review
68
- - `github/my-prs-feedback` - My PRs with comments/feedback (simple trigger)
69
- - `github/my-prs-attention` - My PRs needing attention (conflicts OR feedback, with dynamic labeling)
68
+ - `github/my-prs-attention` - My PRs needing attention (conflicts OR human feedback)
70
69
  - `linear/my-issues` - Linear tickets (requires `teamId`, `assigneeId`)
71
70
 
72
- **Tip:** Use `my-prs-attention` instead of `my-prs-feedback` if you also want to detect merge conflicts. The session name will indicate which condition(s) triggered it: "Conflicts: {title}", "Feedback: {title}", or "Conflicts+Feedback: {title}".
71
+ Session names for `my-prs-attention` indicate the condition: "Conflicts: {title}", "Feedback: {title}", or "Conflicts+Feedback: {title}".
73
72
 
74
73
  ### Prompt Templates
75
74
 
@@ -27,16 +27,13 @@ sources:
27
27
 
28
28
  - preset: github/review-requests
29
29
 
30
- # PRs needing attention (conflicts OR feedback) - recommended over my-prs-feedback
30
+ # PRs needing attention (conflicts OR human feedback)
31
31
  # Session names dynamically indicate the condition: "Conflicts: ...", "Feedback: ...", or "Conflicts+Feedback: ..."
32
32
  - preset: github/my-prs-attention
33
33
  repos:
34
34
  - myorg/backend
35
35
  - myorg/frontend
36
36
 
37
- # Alternative: Simple feedback-only trigger (use my-prs-attention for conflicts too)
38
- # - preset: github/my-prs-feedback
39
-
40
37
  # Linear (requires teamId and assigneeId)
41
38
  - preset: linear/my-issues
42
39
  args:
@@ -92,4 +89,4 @@ sources:
92
89
  # ttl_days: 30
93
90
 
94
91
  # Available presets: github/my-issues, github/review-requests,
95
- # github/my-prs-feedback, github/my-prs-attention, linear/my-issues
92
+ # github/my-prs-attention, linear/my-issues
@@ -1,7 +1,7 @@
1
- Address the review feedback on this PR:
1
+ Address the review feedback or merge conflicts on this PR:
2
2
 
3
3
  {title}
4
4
 
5
5
  {html_url}
6
6
 
7
- Focus only on unresolved review comments. Read each comment, make the requested changes, and respond to the reviewer. Skip any conversations that are already resolved.
7
+ Focus on unresolved review comments and merge conflicts. Make the requested changes and respond to reviewers. Skip any conversations that are already resolved.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-pilot",
3
- "version": "0.18.1",
3
+ "version": "0.18.3",
4
4
  "type": "module",
5
5
  "main": "plugin/index.js",
6
6
  "description": "Automation daemon for OpenCode - polls for work and spawns sessions",
package/service/poller.js CHANGED
@@ -379,9 +379,7 @@ export async function pollGenericSource(source, options = {}) {
379
379
  /**
380
380
  * Fetch issue comments using gh CLI
381
381
  *
382
- * The GitHub MCP server doesn't have a tool to list issue comments,
383
- * so we use gh CLI directly. This fetches the conversation thread
384
- * where bots like Linear post their comments.
382
+ * Fetches the conversation thread where bots like Linear post their comments.
385
383
  *
386
384
  * @param {string} owner - Repository owner
387
385
  * @param {string} repo - Repository name
@@ -409,19 +407,50 @@ async function fetchIssueCommentsViaCli(owner, repo, number, timeout) {
409
407
  }
410
408
  }
411
409
 
410
+ /**
411
+ * Fetch PR review comments using gh CLI
412
+ *
413
+ * Fetches inline code review comments on a PR.
414
+ *
415
+ * @param {string} owner - Repository owner
416
+ * @param {string} repo - Repository name
417
+ * @param {number} number - PR number
418
+ * @param {number} timeout - Timeout in ms
419
+ * @returns {Promise<Array>} Array of comment objects
420
+ */
421
+ async function fetchPrReviewCommentsViaCli(owner, repo, number, timeout) {
422
+ const { exec } = await import('child_process');
423
+ const { promisify } = await import('util');
424
+ const execAsync = promisify(exec);
425
+
426
+ try {
427
+ const { stdout } = await Promise.race([
428
+ execAsync(`gh api repos/${owner}/${repo}/pulls/${number}/comments`),
429
+ createTimeout(timeout, "gh api call for PR comments"),
430
+ ]);
431
+
432
+ const comments = JSON.parse(stdout);
433
+ return Array.isArray(comments) ? comments : [];
434
+ } catch (err) {
435
+ console.error(`[poller] Error fetching PR review comments via gh: ${err.message}`);
436
+ return [];
437
+ }
438
+ }
439
+
412
440
  /**
413
441
  * Fetch comments for a GitHub issue/PR and enrich the item
414
442
  *
415
- * Fetches BOTH types of comments:
416
- * 1. PR review comments (inline code comments) via MCP github_get_pull_request_comments
417
- * 2. Issue comments (conversation thread) via gh CLI
443
+ * Fetches BOTH types of comments using gh CLI:
444
+ * 1. PR review comments (inline code comments) via gh api pulls/{number}/comments
445
+ * 2. Issue comments (conversation thread) via gh api issues/{number}/comments
418
446
  *
419
- * This is necessary because bots like Linear post to issue comments, not PR review comments.
447
+ * This is necessary because:
448
+ * - Bots like Linear post to issue comments, not PR review comments
449
+ * - Human reviewers post inline feedback as PR review comments
420
450
  *
421
- * @param {object} item - Item with owner, repo_short, and number fields
451
+ * @param {object} item - Item with repository_full_name and number fields
422
452
  * @param {object} [options] - Options
423
453
  * @param {number} [options.timeout] - Timeout in ms (default: 30000)
424
- * @param {string} [options.opencodeConfigPath] - Path to opencode.json for MCP config
425
454
  * @returns {Promise<Array>} Array of comment objects (merged from both endpoints)
426
455
  */
427
456
  export async function fetchGitHubComments(item, options = {}) {
@@ -443,61 +472,20 @@ export async function fetchGitHubComments(item, options = {}) {
443
472
  return [];
444
473
  }
445
474
 
446
- let mcpConfig;
447
475
  try {
448
- mcpConfig = getMcpConfig("github", options.opencodeConfigPath);
449
- } catch {
450
- console.error("[poller] GitHub MCP not configured, cannot fetch comments");
451
- return [];
452
- }
453
-
454
- const client = new Client({ name: "opencode-pilot", version: "1.0.0" });
455
-
456
- try {
457
- const transport = await createTransport(mcpConfig);
458
-
459
- await Promise.race([
460
- client.connect(transport),
461
- createTimeout(timeout, "MCP connection for comments"),
462
- ]);
463
-
464
- // Fetch both PR review comments (via MCP) AND issue comments (via gh CLI) in parallel
465
- const [prCommentsResult, issueComments] = await Promise.all([
466
- // PR review comments via MCP (may not be available on all MCP servers)
467
- client.callTool({
468
- name: "github_get_pull_request_comments",
469
- arguments: { owner, repo, pull_number: number }
470
- }).catch(() => null), // Gracefully handle if tool doesn't exist
471
- // Issue comments via gh CLI (conversation thread where Linear bot posts)
476
+ // Fetch both PR review comments AND issue comments in parallel via gh CLI
477
+ const [prComments, issueComments] = await Promise.all([
478
+ // PR review comments (inline code comments from reviewers)
479
+ fetchPrReviewCommentsViaCli(owner, repo, number, timeout),
480
+ // Issue comments (conversation thread where Linear bot posts)
472
481
  fetchIssueCommentsViaCli(owner, repo, number, timeout),
473
482
  ]);
474
483
 
475
- // Parse PR review comments
476
- let prComments = [];
477
- const prText = prCommentsResult?.content?.[0]?.text;
478
- if (prText) {
479
- try {
480
- const parsed = JSON.parse(prText);
481
- prComments = Array.isArray(parsed) ? parsed : [];
482
- } catch {
483
- // Ignore parse errors
484
- }
485
- }
486
-
487
484
  // Return merged comments from both sources
488
485
  return [...prComments, ...issueComments];
489
486
  } catch (err) {
490
487
  console.error(`[poller] Error fetching comments: ${err.message}`);
491
488
  return [];
492
- } finally {
493
- try {
494
- await Promise.race([
495
- client.close(),
496
- new Promise(resolve => setTimeout(resolve, 3000)),
497
- ]);
498
- } catch {
499
- // Ignore close errors
500
- }
501
489
  }
502
490
  }
503
491
 
@@ -37,23 +37,6 @@ review-requests:
37
37
  session:
38
38
  name: "Review: {title}"
39
39
 
40
- my-prs-feedback:
41
- name: my-prs-feedback
42
- tool:
43
- # comments:>0 filter ensures only PRs with feedback are returned
44
- command: ["gh", "search", "prs", "--author=@me", "--state=open", "comments:>0", "--json", "number,title,url,repository,state,body,updatedAt,commentsCount"]
45
- item:
46
- id: "{url}"
47
- repo: "{repository.nameWithOwner}"
48
- prompt: review-feedback
49
- session:
50
- name: "Feedback: {title}"
51
- # Reprocess when PR is updated (new commits pushed, new comments, etc.)
52
- # This ensures we re-trigger after addressing review feedback
53
- reprocess_on:
54
- - state
55
- - updatedAt
56
-
57
40
  my-prs-attention:
58
41
  name: my-prs-attention
59
42
  tool:
@@ -679,24 +679,6 @@ sources:
679
679
  assert.ok(sources[0].tool.command.includes('--review-requested=@me'), 'command should include review-requested filter');
680
680
  });
681
681
 
682
- test('expands github/my-prs-feedback preset', async () => {
683
- writeFileSync(configPath, `
684
- sources:
685
- - preset: github/my-prs-feedback
686
- `);
687
-
688
- const { loadRepoConfig, getSources } = await import('../../service/repo-config.js');
689
- loadRepoConfig(configPath);
690
- const sources = getSources();
691
-
692
- assert.strictEqual(sources[0].name, 'my-prs-feedback');
693
- // GitHub presets now use gh CLI instead of MCP
694
- assert.ok(sources[0].tool.command.includes('--author=@me'), 'command should include author filter');
695
- assert.ok(sources[0].tool.command.includes('comments:>0'), 'command should filter for PRs with comments');
696
- // This preset includes updatedAt in reprocess_on to catch new commits
697
- assert.deepStrictEqual(sources[0].reprocess_on, ['state', 'updatedAt']);
698
- });
699
-
700
682
  test('expands github/my-prs-attention preset', async () => {
701
683
  writeFileSync(configPath, `
702
684
  sources:
@@ -774,7 +756,6 @@ sources:
774
756
  sources:
775
757
  - preset: github/my-issues
776
758
  - preset: github/review-requests
777
- - preset: github/my-prs-feedback
778
759
  - preset: github/my-prs-attention
779
760
  `);
780
761
 
@@ -860,7 +841,6 @@ sources:
860
841
  sources:
861
842
  - preset: github/my-issues
862
843
  - preset: github/review-requests
863
- - preset: github/my-prs-feedback
864
844
  - preset: github/my-prs-attention
865
845
  `);
866
846
 
@@ -874,11 +854,8 @@ sources:
874
854
  // review-requests: "Review: {title}"
875
855
  assert.strictEqual(sources[1].session.name, 'Review: {title}', 'review-requests should prefix with Review:');
876
856
 
877
- // my-prs-feedback: "Feedback: {title}"
878
- assert.strictEqual(sources[2].session.name, 'Feedback: {title}', 'my-prs-feedback should prefix with Feedback:');
879
-
880
857
  // my-prs-attention: "{_attention_label}: {title}" (dynamic based on detected conditions)
881
- assert.ok(sources[3].session.name.includes('_attention_label'), 'my-prs-attention should use dynamic label');
858
+ assert.ok(sources[2].session.name.includes('_attention_label'), 'my-prs-attention should use dynamic label');
882
859
  });
883
860
 
884
861
  test('linear preset includes session name', async () => {