@jonit-dev/night-watch-cli 1.8.12-beta.10 → 1.8.12-beta.11

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.
@@ -120,6 +120,73 @@ get_review_score() {
120
120
  echo "${score}"
121
121
  }
122
122
 
123
+ # Count pending review feedback that should send the PR back through reviewer
124
+ # instead of allowing the merger to land it. We combine GitHub's review decision
125
+ # with unresolved review threads because some providers leave actionable feedback
126
+ # as thread comments without lowering the score.
127
+ get_pending_review_feedback_count() {
128
+ local pr_number="${1}"
129
+ local review_decision=""
130
+ local change_request_count="0"
131
+ local unresolved_thread_count="0"
132
+ local repo=""
133
+ local owner=""
134
+ local name=""
135
+
136
+ review_decision=$(gh pr view "${pr_number}" --json reviewDecision --jq '.reviewDecision // ""' 2>/dev/null || echo "")
137
+
138
+ change_request_count=$(gh api "repos/{owner}/{repo}/pulls/${pr_number}/reviews" \
139
+ --jq '[.[] | select(.state == "CHANGES_REQUESTED")] | length' 2>/dev/null || echo "0")
140
+ if ! [[ "${change_request_count}" =~ ^[0-9]+$ ]]; then
141
+ change_request_count="0"
142
+ fi
143
+
144
+ repo=$(gh repo view --json nameWithOwner --jq '.nameWithOwner' 2>/dev/null || echo "")
145
+ if [[ "${repo}" == */* ]]; then
146
+ owner="${repo%%/*}"
147
+ name="${repo#*/}"
148
+ unresolved_thread_count=$(gh api graphql \
149
+ -F owner="${owner}" \
150
+ -F name="${name}" \
151
+ -F number="${pr_number}" \
152
+ -f query='
153
+ query($owner: String!, $name: String!, $number: Int!) {
154
+ repository(owner: $owner, name: $name) {
155
+ pullRequest(number: $number) {
156
+ reviewThreads(first: 100) {
157
+ nodes {
158
+ isResolved
159
+ }
160
+ }
161
+ }
162
+ }
163
+ }
164
+ ' \
165
+ --jq '[.data.repository.pullRequest.reviewThreads.nodes[]? | select(.isResolved == false)] | length' \
166
+ 2>/dev/null || echo "0")
167
+ if ! [[ "${unresolved_thread_count}" =~ ^[0-9]+$ ]]; then
168
+ unresolved_thread_count="0"
169
+ fi
170
+ fi
171
+
172
+ if [ "${unresolved_thread_count}" -gt 0 ]; then
173
+ echo "${unresolved_thread_count}"
174
+ return 0
175
+ fi
176
+
177
+ if [ "${change_request_count}" -gt 0 ]; then
178
+ echo "${change_request_count}"
179
+ return 0
180
+ fi
181
+
182
+ if [ "${review_decision}" = "CHANGES_REQUESTED" ]; then
183
+ echo "1"
184
+ return 0
185
+ fi
186
+
187
+ echo "0"
188
+ }
189
+
123
190
  # Get the current head OID for a PR.
124
191
  get_pr_head_oid() {
125
192
  local pr_number="${1}"
@@ -335,11 +402,17 @@ while IFS= read -r pr_json; do
335
402
  if [ "${MIN_REVIEW_SCORE}" -gt "0" ]; then
336
403
  score=$(get_review_score "${pr_number}")
337
404
  if [ "${score}" -lt "0" ] || [ "${score}" -lt "${MIN_REVIEW_SCORE}" ]; then
338
- log "INFO: PR #${pr_number} (${pr_branch}): Review score ${score} < ${MIN_REVIEW_SCORE} (or no score found), skipping"
405
+ log "INFO: PR #${pr_number} (${pr_branch}): Review score ${score} < ${MIN_REVIEW_SCORE} (or no score found), reviewer job required before merge"
339
406
  continue
340
407
  fi
341
408
  fi
342
409
 
410
+ pending_review_feedback_count=$(get_pending_review_feedback_count "${pr_number}")
411
+ if [ "${pending_review_feedback_count}" -gt "0" ]; then
412
+ log "INFO: PR #${pr_number} (${pr_branch}): ${pending_review_feedback_count} pending review feedback item(s), reviewer job required before merge"
413
+ continue
414
+ fi
415
+
343
416
  log "INFO: PR #${pr_number} (${pr_branch}): Eligible for merge"
344
417
 
345
418
  # Rebase before merge if configured
@@ -132,6 +132,72 @@ get_pr_comments() {
132
132
  } | awk '!seen[$0]++'
133
133
  }
134
134
 
135
+ get_pr_pending_review_feedback_count() {
136
+ local pr_number="${1:?PR number required}"
137
+ local review_decision=""
138
+ local change_request_count="0"
139
+ local unresolved_thread_count="0"
140
+ local repo="${REPO:-}"
141
+ local owner=""
142
+ local name=""
143
+
144
+ review_decision=$(gh pr view "${pr_number}" --json reviewDecision --jq '.reviewDecision // ""' 2>/dev/null || echo "")
145
+
146
+ change_request_count=$(gh api "repos/{owner}/{repo}/pulls/${pr_number}/reviews" \
147
+ --jq '[.[] | select(.state == "CHANGES_REQUESTED")] | length' 2>/dev/null || echo "0")
148
+ if ! [[ "${change_request_count}" =~ ^[0-9]+$ ]]; then
149
+ change_request_count="0"
150
+ fi
151
+
152
+ if [ -z "${repo}" ]; then
153
+ repo=$(gh repo view --json nameWithOwner --jq '.nameWithOwner' 2>/dev/null || echo "")
154
+ fi
155
+
156
+ if [[ "${repo}" == */* ]]; then
157
+ owner="${repo%%/*}"
158
+ name="${repo#*/}"
159
+ unresolved_thread_count=$(gh api graphql \
160
+ -F owner="${owner}" \
161
+ -F name="${name}" \
162
+ -F number="${pr_number}" \
163
+ -f query='
164
+ query($owner: String!, $name: String!, $number: Int!) {
165
+ repository(owner: $owner, name: $name) {
166
+ pullRequest(number: $number) {
167
+ reviewThreads(first: 100) {
168
+ nodes {
169
+ isResolved
170
+ }
171
+ }
172
+ }
173
+ }
174
+ }
175
+ ' \
176
+ --jq '[.data.repository.pullRequest.reviewThreads.nodes[]? | select(.isResolved == false)] | length' \
177
+ 2>/dev/null || echo "0")
178
+ if ! [[ "${unresolved_thread_count}" =~ ^[0-9]+$ ]]; then
179
+ unresolved_thread_count="0"
180
+ fi
181
+ fi
182
+
183
+ if [ "${unresolved_thread_count}" -gt 0 ]; then
184
+ echo "${unresolved_thread_count}"
185
+ return 0
186
+ fi
187
+
188
+ if [ "${change_request_count}" -gt 0 ]; then
189
+ echo "${change_request_count}"
190
+ return 0
191
+ fi
192
+
193
+ if [ "${review_decision}" = "CHANGES_REQUESTED" ]; then
194
+ echo "1"
195
+ return 0
196
+ fi
197
+
198
+ echo "0"
199
+ }
200
+
135
201
  get_pr_head_ref_oid() {
136
202
  local pr_number="${1:?PR number required}"
137
203
  gh pr view "${pr_number}" --json headRefOid --jq '.headRefOid' 2>/dev/null || echo ""
@@ -760,6 +826,18 @@ while IFS=$'\t' read -r pr_number pr_branch pr_labels; do
760
826
  continue
761
827
  fi
762
828
 
829
+ PENDING_REVIEW_FEEDBACK_COUNT=$(get_pr_pending_review_feedback_count "${pr_number}")
830
+ if [ "${PENDING_REVIEW_FEEDBACK_COUNT}" -gt 0 ]; then
831
+ if [ "${local_ready_for_review_label_present}" -eq 1 ]; then
832
+ log "INFO: PR #${pr_number} (${pr_branch}) has pending review feedback; removing stale ${READY_FOR_REVIEW_LABEL} label"
833
+ clear_ready_for_human_review_label "${pr_number}"
834
+ fi
835
+ log "INFO: PR #${pr_number} (${pr_branch}) has ${PENDING_REVIEW_FEEDBACK_COUNT} pending review feedback item(s)"
836
+ NEEDS_WORK=1
837
+ PRS_NEEDING_WORK="${PRS_NEEDING_WORK} #${pr_number}"
838
+ continue
839
+ fi
840
+
763
841
  if has_ready_for_human_review_marker "${all_comments}" "${current_head_sha}"; then
764
842
  SKIPPED_ALREADY_REVIEWED_CURRENT_HEAD=1
765
843
  log "INFO: PR #${pr_number} (${pr_branch}) is already marked ready for human review at head ${current_head_sha:0:12}; skipping repeat automated review"
@@ -1077,6 +1155,7 @@ if [ -n "${TARGET_PR}" ]; then
1077
1155
  TARGET_MERGE_STATE=$(gh pr view "${TARGET_PR}" --json mergeStateStatus --jq '.mergeStateStatus' 2>/dev/null || echo "UNKNOWN")
1078
1156
  TARGET_FAILED_CHECKS=$(get_pr_failed_ci_summary "${TARGET_PR}")
1079
1157
  TARGET_SCORE=$(get_pr_score "${TARGET_PR}")
1158
+ TARGET_PENDING_REVIEW_FEEDBACK=$(get_pr_pending_review_feedback_count "${TARGET_PR}")
1080
1159
 
1081
1160
  TARGET_SCOPE_PROMPT+=$'\n## Preflight Data (from CLI)\n- mergeStateStatus: '"${TARGET_MERGE_STATE}"$'\n'
1082
1161
  if [ -n "${TARGET_FAILED_CHECKS}" ]; then
@@ -1084,6 +1163,7 @@ if [ -n "${TARGET_PR}" ]; then
1084
1163
  else
1085
1164
  TARGET_SCOPE_PROMPT+=$'- failing checks: none detected\n'
1086
1165
  fi
1166
+ TARGET_SCOPE_PROMPT+=$'- pending review feedback: '"${TARGET_PENDING_REVIEW_FEEDBACK}"$' item(s)\n'
1087
1167
  if [ -n "${TARGET_SCORE}" ]; then
1088
1168
  TARGET_SCOPE_PROMPT+=$'- latest review score: '"${TARGET_SCORE}"$'/100\n'
1089
1169
  TARGET_SCOPE_PROMPT+=$'- action: fix\n'
@@ -1,7 +1,7 @@
1
1
  You are the Night Watch PR Reviewer agent. Your job is to check open PRs for three things:
2
2
 
3
3
  1. Merge conflicts -- rebase onto the base branch and resolve them.
4
- 2. Review comments with a score below 80 -- address the feedback.
4
+ 2. Review comments with a score below 80 or unresolved review feedback -- address the feedback.
5
5
  3. Failed CI jobs -- diagnose and fix the failures.
6
6
 
7
7
  ## Context
@@ -10,7 +10,7 @@ The repo can have multiple PR checks/workflows (project CI plus Night Watch auto
10
10
  Common examples include `typecheck`, `lint`, `test`, `build`, `verify`, `executor`, `qa`, and `audit`.
11
11
  Treat `gh pr checks <number> --json name,state,conclusion` as the source of truth for which checks failed.
12
12
 
13
- A PR needs attention if **any** of the following: merge conflicts present, review score below 80, or any CI job failed.
13
+ A PR needs attention if **any** of the following: merge conflicts present, no review score yet, review score below 80, unresolved review feedback, or any CI job failed.
14
14
 
15
15
  ## PRD Context
16
16
 
@@ -21,7 +21,7 @@ If current PR code or review feedback conflicts with the PRD context, call out t
21
21
  ## Important: Early Exit
22
22
 
23
23
  - If there are **no open PRs** on `night-watch/` or `feat/` branches, **stop immediately** and report "No PRs to review."
24
- - If all open PRs have **no merge conflicts**, **passing CI**, and **review score >= 80**, **stop immediately** and report "All PRs are in good shape."
24
+ - If all open PRs have **no merge conflicts**, **passing CI**, **review score >= 80**, and **no unresolved review feedback**, **stop immediately** and report "All PRs are in good shape."
25
25
  - If a PR has no review score yet, it needs a first review — do NOT skip it.
26
26
  - Do **NOT** loop or retry. Process each PR **once** per run. After processing all PRs, stop.
27
27
  - Do **NOT** re-check PRs after pushing fixes -- the CI will re-run automatically on the next push.
@@ -90,8 +90,8 @@ Parse the review score from the comment body. Look for patterns like:
90
90
  Extract the numeric score. If multiple comments have scores, use the **most recent** one.
91
91
 
92
92
  3. **Determine if PR needs work**:
93
- - If no merge conflicts **AND** score >= 80 **AND** all CI checks pass --> skip this PR.
94
- - If merge conflicts present **OR** score < 80 **OR** any CI check failed --> fix the issues.
93
+ - If no merge conflicts **AND** score >= 80 **AND** no unresolved review feedback **AND** all CI checks pass --> skip this PR.
94
+ - If merge conflicts present **OR** score < 80 **OR** unresolved review feedback exists **OR** any CI check failed --> fix the issues.
95
95
 
96
96
  4. **Fix the PR**:
97
97
 
@@ -122,8 +122,9 @@ Parse the review score from the comment body. Look for patterns like:
122
122
  - Push the clean branch: `git push --force-with-lease origin <branch-name>`
123
123
  - **Do NOT leave any conflict markers (`<<<<<<<`, `=======`, `>>>>>>>`) in any file.**
124
124
 
125
- c. **Address review feedback** (if score < 80):
125
+ c. **Address review feedback** (if score < 80 or pending review feedback exists):
126
126
  - Read the review comments carefully. Extract areas for improvement, bugs found, issues found, and specific file/line suggestions.
127
+ - If `pending review feedback` is greater than 0 in the cron-provided preflight data, inspect review conversations with `gh pr view <number> --comments` and the GitHub PR review UI/API as needed.
127
128
  - For each review suggestion:
128
129
  - If you agree, implement the change.
129
130
  - If you do not agree, do not implement it blindly. Capture a short technical reason and include that reason in the PR comment.
@@ -1,7 +1,7 @@
1
1
  You are the Night Watch PR Reviewer agent. Your job is to check open PRs for three things:
2
2
 
3
3
  1. Merge conflicts -- rebase onto the base branch and resolve them.
4
- 2. Review comments with a score below 80 -- address the feedback.
4
+ 2. Review comments with a score below 80 or unresolved review feedback -- address the feedback.
5
5
  3. Failed CI jobs -- diagnose and fix the failures.
6
6
 
7
7
  ## Context
@@ -10,7 +10,7 @@ The repo can have multiple PR checks/workflows (project CI plus Night Watch auto
10
10
  Common examples include `typecheck`, `lint`, `test`, `build`, `verify`, `executor`, `qa`, and `audit`.
11
11
  Treat `gh pr checks <number> --json name,state,conclusion` as the source of truth for which checks failed.
12
12
 
13
- A PR needs attention if **any** of the following: merge conflicts present, review score below 80, or any CI job failed.
13
+ A PR needs attention if **any** of the following: merge conflicts present, no review score yet, review score below 80, unresolved review feedback, or any CI job failed.
14
14
 
15
15
  ## PRD Context
16
16
 
@@ -21,7 +21,7 @@ If current PR code or review feedback conflicts with the PRD context, call out t
21
21
  ## Important: Early Exit
22
22
 
23
23
  - If there are **no open PRs** on `night-watch/` or `feat/` branches, **stop immediately** and report "No PRs to review."
24
- - If all open PRs have **no merge conflicts**, **passing CI**, and **review score >= 80**, **stop immediately** and report "All PRs are in good shape."
24
+ - If all open PRs have **no merge conflicts**, **passing CI**, **review score >= 80**, and **no unresolved review feedback**, **stop immediately** and report "All PRs are in good shape."
25
25
  - If a PR has no review score yet, it needs a first review — do NOT skip it.
26
26
  - Do **NOT** loop or retry. Process each PR **once** per run. After processing all PRs, stop.
27
27
  - Do **NOT** re-check PRs after pushing fixes -- the CI will re-run automatically on the next push.
@@ -90,8 +90,8 @@ Parse the review score from the comment body. Look for patterns like:
90
90
  Extract the numeric score. If multiple comments have scores, use the **most recent** one.
91
91
 
92
92
  3. **Determine if PR needs work**:
93
- - If no merge conflicts **AND** score >= 80 **AND** all CI checks pass --> skip this PR.
94
- - If merge conflicts present **OR** score < 80 **OR** any CI check failed --> fix the issues.
93
+ - If no merge conflicts **AND** score >= 80 **AND** no unresolved review feedback **AND** all CI checks pass --> skip this PR.
94
+ - If merge conflicts present **OR** score < 80 **OR** unresolved review feedback exists **OR** any CI check failed --> fix the issues.
95
95
 
96
96
  4. **Fix the PR**:
97
97
 
@@ -122,8 +122,9 @@ Parse the review score from the comment body. Look for patterns like:
122
122
  - Push the clean branch: `git push --force-with-lease origin <branch-name>`
123
123
  - **Do NOT leave any conflict markers (`<<<<<<<`, `=======`, `>>>>>>>`) in any file.**
124
124
 
125
- c. **Address review feedback** (if score < 80):
125
+ c. **Address review feedback** (if score < 80 or pending review feedback exists):
126
126
  - Read the review comments carefully. Extract areas for improvement, bugs found, issues found, and specific file/line suggestions.
127
+ - If `pending review feedback` is greater than 0 in the cron-provided preflight data, inspect review conversations with `gh pr view <number> --comments` and the GitHub PR review UI/API as needed.
127
128
  - For each review suggestion:
128
129
  - If you agree, implement the change.
129
130
  - If you do not agree, do not implement it blindly. Capture a short technical reason and include that reason in the PR comment.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jonit-dev/night-watch-cli",
3
- "version": "1.8.12-beta.10",
3
+ "version": "1.8.12-beta.11",
4
4
  "description": "AI agent that implements your specs, opens PRs, and reviews code overnight. Queue GitHub issues or PRDs, wake up to pull requests.",
5
5
  "type": "module",
6
6
  "bin": {