opencode-pilot 0.18.0 → 0.18.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/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.0",
3
+ "version": "0.18.2",
4
4
  "type": "module",
5
5
  "main": "plugin/index.js",
6
6
  "description": "Automation daemon for OpenCode - polls for work and spawns sessions",
@@ -124,7 +124,12 @@ export async function pollOnce(options = {}) {
124
124
  // Fetch items from source
125
125
  if (!skipMcp) {
126
126
  try {
127
- toolProviderConfig = getToolProviderConfig(source.tool.mcp);
127
+ // Get provider config - for MCP sources use source.tool.mcp, for CLI sources detect provider
128
+ let provider = source.tool.mcp;
129
+ if (!provider && Array.isArray(source.tool?.command) && source.tool.command[0] === 'gh') {
130
+ provider = 'github'; // CLI-based GitHub source
131
+ }
132
+ toolProviderConfig = getToolProviderConfig(provider);
128
133
  items = await pollGenericSource(source, { toolProviderConfig });
129
134
  debug(`Fetched ${items.length} items from ${sourceName}`);
130
135
 
package/service/poller.js CHANGED
@@ -12,7 +12,7 @@ import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
12
12
  import fs from "fs";
13
13
  import path from "path";
14
14
  import os from "os";
15
- import { getNestedValue } from "./utils.js";
15
+ import { getNestedValue, hasNonBotFeedback } from "./utils.js";
16
16
 
17
17
  /**
18
18
  * Expand template string with item fields
@@ -501,6 +501,22 @@ export async function fetchGitHubComments(item, options = {}) {
501
501
  }
502
502
  }
503
503
 
504
+ /**
505
+ * Check if a source is a GitHub source (MCP or CLI-based)
506
+ * @param {object} source - Source configuration
507
+ * @returns {boolean} True if this is a GitHub source
508
+ */
509
+ function isGitHubSource(source) {
510
+ // MCP-based GitHub source
511
+ if (source.tool?.mcp === "github") return true;
512
+
513
+ // CLI-based GitHub source (uses gh command)
514
+ const command = source.tool?.command;
515
+ if (Array.isArray(command) && command[0] === "gh") return true;
516
+
517
+ return false;
518
+ }
519
+
504
520
  /**
505
521
  * Enrich items with comments for bot filtering
506
522
  *
@@ -514,7 +530,7 @@ export async function fetchGitHubComments(item, options = {}) {
514
530
  */
515
531
  export async function enrichItemsWithComments(items, source, options = {}) {
516
532
  // Skip if not configured or not a GitHub source
517
- if (!source.filter_bot_comments || source.tool?.mcp !== "github") {
533
+ if (!source.filter_bot_comments || !isGitHubSource(source)) {
518
534
  return items;
519
535
  }
520
536
 
@@ -618,16 +634,11 @@ export function computeAttentionLabels(items, source) {
618
634
  reasons.push('Conflicts');
619
635
  }
620
636
 
621
- // Check for human feedback (non-bot, non-author comments)
637
+ // Check for human feedback using the shared hasNonBotFeedback utility
638
+ // This properly handles known bots like 'linear' that don't have [bot] suffix
622
639
  if (item._comments && item._comments.length > 0) {
623
640
  const authorUsername = item.user?.login || item.author?.login;
624
- const hasHumanFeedback = item._comments.some(comment => {
625
- const commenter = comment.user?.login || comment.author?.login;
626
- const isBot = commenter?.includes('[bot]') || comment.user?.type === 'Bot';
627
- const isAuthor = commenter === authorUsername;
628
- return !isBot && !isAuthor;
629
- });
630
- if (hasHumanFeedback) {
641
+ if (hasNonBotFeedback(item._comments, authorUsername)) {
631
642
  reasons.push('Feedback');
632
643
  }
633
644
  }
@@ -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:
@@ -1013,5 +1013,25 @@ describe('poller.js', () => {
1013
1013
  assert.strictEqual(result[0]._attention_label, 'PR');
1014
1014
  assert.strictEqual(result[0]._has_attention, false);
1015
1015
  });
1016
+
1017
+ test('ignores known bots without [bot] suffix (e.g., linear)', async () => {
1018
+ const { computeAttentionLabels } = await import('../../service/poller.js');
1019
+
1020
+ const items = [{
1021
+ number: 123,
1022
+ title: 'Test PR',
1023
+ user: { login: 'author' },
1024
+ _mergeable: 'MERGEABLE',
1025
+ _comments: [
1026
+ // Linear bot posts linkback comments without [bot] suffix
1027
+ { user: { login: 'linear', type: 'User' }, body: '<!-- linear-linkback -->' }
1028
+ ]
1029
+ }];
1030
+
1031
+ const result = computeAttentionLabels(items, {});
1032
+
1033
+ assert.strictEqual(result[0]._attention_label, 'PR');
1034
+ assert.strictEqual(result[0]._has_attention, false);
1035
+ });
1016
1036
  });
1017
1037
  });
@@ -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 () => {