opencode-pilot 0.11.1 → 0.11.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-pilot",
3
- "version": "0.11.1",
3
+ "version": "0.11.2",
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/utils.js CHANGED
@@ -20,6 +20,26 @@ export function getNestedValue(obj, path) {
20
20
  return value;
21
21
  }
22
22
 
23
+ /**
24
+ * Check if a comment/review is an approval-only (no actionable feedback)
25
+ *
26
+ * PR reviews have a state field (APPROVED, CHANGES_REQUESTED, COMMENTED).
27
+ * An approval without substantive body text doesn't require action from the author.
28
+ *
29
+ * @param {object} comment - Comment or review object with optional state and body
30
+ * @returns {boolean} True if this is a pure approval with no actionable feedback
31
+ */
32
+ export function isApprovalOnly(comment) {
33
+ // Only applies to PR reviews with APPROVED state
34
+ if (comment.state !== 'APPROVED') return false;
35
+
36
+ // If there's substantive body text, it might contain feedback
37
+ const body = comment.body || '';
38
+ if (body.trim().length > 0) return false;
39
+
40
+ return true;
41
+ }
42
+
23
43
  /**
24
44
  * Check if a username represents a bot account
25
45
  *
@@ -50,9 +70,12 @@ export function isBot(username, type) {
50
70
  * Used to filter out PRs where only bots have commented, since those don't
51
71
  * require the author's attention for human feedback.
52
72
  *
73
+ * Also skips approval-only reviews (APPROVED state with no body text) since
74
+ * approvals don't require action from the author.
75
+ *
53
76
  * @param {Array} comments - Array of comment objects with user.login and user.type
54
77
  * @param {string} authorUsername - Username of the PR/issue author
55
- * @returns {boolean} True if there's at least one non-bot, non-author comment
78
+ * @returns {boolean} True if there's at least one non-bot, non-author, actionable comment
56
79
  */
57
80
  export function hasNonBotFeedback(comments, authorUsername) {
58
81
  // Handle null/undefined/empty
@@ -75,7 +98,10 @@ export function hasNonBotFeedback(comments, authorUsername) {
75
98
  // Skip if it's the author themselves
76
99
  if (authorLower && username?.toLowerCase() === authorLower) continue;
77
100
 
78
- // Found a non-bot, non-author comment
101
+ // Skip approval-only reviews (no actionable feedback)
102
+ if (isApprovalOnly(comment)) continue;
103
+
104
+ // Found a non-bot, non-author, actionable comment
79
105
  return true;
80
106
  }
81
107
 
@@ -100,6 +100,77 @@ describe('utils.js', () => {
100
100
 
101
101
  assert.strictEqual(hasNonBotFeedback(comments, 'contributor'), true);
102
102
  });
103
+
104
+ test('returns false when only approval-only reviews (no feedback body)', async () => {
105
+ const { hasNonBotFeedback } = await import('../../service/utils.js');
106
+
107
+ // PR reviews with APPROVED state but no body should not trigger feedback
108
+ const comments = [
109
+ { user: { login: 'github-actions[bot]', type: 'Bot' }, body: 'CI passed' },
110
+ { user: { login: 'reviewer', type: 'User' }, state: 'APPROVED', body: '' },
111
+ ];
112
+
113
+ assert.strictEqual(hasNonBotFeedback(comments, 'author'), false);
114
+ });
115
+
116
+ test('returns true when approval includes feedback body', async () => {
117
+ const { hasNonBotFeedback } = await import('../../service/utils.js');
118
+
119
+ // If someone approves but leaves feedback, we should consider it actionable
120
+ const comments = [
121
+ { user: { login: 'reviewer', type: 'User' }, state: 'APPROVED', body: 'LGTM but consider adding a test for edge cases' },
122
+ ];
123
+
124
+ assert.strictEqual(hasNonBotFeedback(comments, 'author'), true);
125
+ });
126
+
127
+ test('returns true for CHANGES_REQUESTED reviews', async () => {
128
+ const { hasNonBotFeedback } = await import('../../service/utils.js');
129
+
130
+ const comments = [
131
+ { user: { login: 'reviewer', type: 'User' }, state: 'CHANGES_REQUESTED', body: 'Please fix this' },
132
+ ];
133
+
134
+ assert.strictEqual(hasNonBotFeedback(comments, 'author'), true);
135
+ });
136
+ });
137
+
138
+ describe('isApprovalOnly', () => {
139
+ test('returns true for APPROVED state with no body', async () => {
140
+ const { isApprovalOnly } = await import('../../service/utils.js');
141
+
142
+ assert.strictEqual(isApprovalOnly({ state: 'APPROVED' }), true);
143
+ assert.strictEqual(isApprovalOnly({ state: 'APPROVED', body: '' }), true);
144
+ assert.strictEqual(isApprovalOnly({ state: 'APPROVED', body: null }), true);
145
+ });
146
+
147
+ test('returns false for APPROVED with substantive body', async () => {
148
+ const { isApprovalOnly } = await import('../../service/utils.js');
149
+
150
+ // If someone approves but leaves feedback, we should still consider it feedback
151
+ assert.strictEqual(isApprovalOnly({ state: 'APPROVED', body: 'LGTM but consider renaming this function' }), false);
152
+ });
153
+
154
+ test('returns false for CHANGES_REQUESTED', async () => {
155
+ const { isApprovalOnly } = await import('../../service/utils.js');
156
+
157
+ assert.strictEqual(isApprovalOnly({ state: 'CHANGES_REQUESTED', body: 'Please fix this' }), false);
158
+ assert.strictEqual(isApprovalOnly({ state: 'CHANGES_REQUESTED' }), false);
159
+ });
160
+
161
+ test('returns false for COMMENTED state', async () => {
162
+ const { isApprovalOnly } = await import('../../service/utils.js');
163
+
164
+ assert.strictEqual(isApprovalOnly({ state: 'COMMENTED', body: 'This looks good' }), false);
165
+ });
166
+
167
+ test('returns false for regular comments without state', async () => {
168
+ const { isApprovalOnly } = await import('../../service/utils.js');
169
+
170
+ // Issue comments don't have state field
171
+ assert.strictEqual(isApprovalOnly({ body: 'Please address this' }), false);
172
+ assert.strictEqual(isApprovalOnly({}), false);
173
+ });
103
174
  });
104
175
 
105
176
  describe('getNestedValue', () => {