@blogic-cz/agent-tools 0.15.11 → 0.15.12

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
@@ -263,7 +263,7 @@ Pod `exec` is limited to direct `redis-cli PING/INFO` and `ls` diagnostics. Gene
263
263
 
264
264
  ### gh-tool machine contracts
265
265
 
266
- `pr view` adds `headSha` and `baseSha`; failed-check evidence adds the same SHA pair. Review summaries, inline comments, and threads add `commitSha` plus `feedbackOrigin`: `current_head` only for an exact `commitSha === headSha`, `pre_existing` for a different known SHA (not an obsolescence verdict), and `unknown` when either SHA is absent. Issue comments always use `commitSha: null` and `feedbackOrigin: unknown`. `review-triage` preserves existing fields and adds `inlineComments` plus per-kind `feedbackOriginCounts`; batch triage returns the same object per PR. `pr request-review --reviewers alice,bob` emits sorted `submittedReviewers` (normalized input) and `requestedReviewers` (GitHub-confirmed result).
266
+ `pr view` adds `headSha` and `baseSha`; failed-check evidence adds the same SHA pair. Review summaries, inline comments, and threads add `commitSha` plus `feedbackOrigin`: `current_head` only for an exact `commitSha === headSha`, `pre_existing` for a different known SHA (not an obsolescence verdict), and `unknown` when either SHA is absent. Issue comments always use `commitSha: null` and `feedbackOrigin: unknown`. `review-triage` preserves existing fields and adds `inlineComments` plus per-kind `feedbackOriginCounts`; batch triage returns the same object per PR. `pr request-review --reviewers alice,bob` emits sorted `submittedReviewers` (normalized input), `newlyRequested` and `alreadyPending` (the submitted logins split by whether a pending request already existed, so a fresh re-request is distinguishable from a no-op), and `requestedReviewers` (GitHub-confirmed result). `pr last-human-reviewer` derives `currentRequestedReviewers` from live `reviewRequests` only; timeline events are not replayed because GitHub clears a pending request on review submit without emitting `ReviewRequestRemovedEvent`.
267
267
 
268
268
  `pr watch --prs 12,34 --until terminal --format jsonl` accepts at most 50 unique, digits-only PR numbers and emits only JSONL state transitions. Identity uses `repo/pr/headSha/runId/attempt/jobId`; `runId`, `attempt`, and `jobId` are omitted from an event rather than emitted as `null`, and `checkId` is no longer emitted. State/bucket revisions emit even when identity stays stable; `supersedes` appears only when identity changes. Open PRs with no checks become terminal only after three stable empty snapshots, allowing bounded GitHub eventual consistency, and carry `checksObserved: false` — a terminal snapshot with no observed check is never green evidence. `pr checks`, batch checks, triage, and batch triage keep stderr silent with `--format json`; JSONL watch is also informationally silent. Failures still return structured nonzero errors on stderr.
269
269
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blogic-cz/agent-tools",
3
- "version": "0.15.11",
3
+ "version": "0.15.12",
4
4
  "description": "CLI tools for AI coding agent workflows — GitHub, database, Kubernetes, Azure DevOps, logs, sessions, and audit",
5
5
  "keywords": [
6
6
  "agent",
@@ -141,6 +141,15 @@ export const requestReview = (pr: number, reviewers: string) =>
141
141
  const submittedReviewers = yield* parseReviewers(reviewers);
142
142
  const gh = yield* GitHubService;
143
143
  const repo = yield* gh.getRepoInfo();
144
+ // ponytail: not atomic — a concurrent request between GET and POST misreports newlyRequested.
145
+ const pendingBefore = new Set(
146
+ (yield* confirmedReviewers(
147
+ yield* gh.runGhJson<RequestedReviewersResponse>([
148
+ "api",
149
+ `repos/${repo.owner}/${repo.name}/pulls/${pr}`,
150
+ ]),
151
+ )).map((login) => login.toLowerCase()),
152
+ );
144
153
  const response = yield* gh.runGhJson<RequestedReviewersResponse>([
145
154
  "api",
146
155
  "--method",
@@ -151,6 +160,8 @@ export const requestReview = (pr: number, reviewers: string) =>
151
160
  return {
152
161
  pr,
153
162
  submittedReviewers,
163
+ newlyRequested: submittedReviewers.filter((reviewer) => !pendingBefore.has(reviewer)),
164
+ alreadyPending: submittedReviewers.filter((reviewer) => pendingBefore.has(reviewer)),
154
165
  requestedReviewers: yield* confirmedReviewers(response),
155
166
  };
156
167
  });
@@ -126,27 +126,6 @@ const LAST_HUMAN_REVIEWER_QUERY = `
126
126
  submittedAt
127
127
  }
128
128
  }
129
- timelineItems(first: 250, itemTypes: [REVIEW_REQUESTED_EVENT, REVIEW_REQUEST_REMOVED_EVENT]) {
130
- nodes {
131
- __typename
132
- ... on ReviewRequestedEvent {
133
- requestedReviewer {
134
- __typename
135
- ... on User { login }
136
- ... on Team { name }
137
- ... on Bot { login }
138
- }
139
- }
140
- ... on ReviewRequestRemovedEvent {
141
- requestedReviewer {
142
- __typename
143
- ... on User { login }
144
- ... on Team { name }
145
- ... on Bot { login }
146
- }
147
- }
148
- }
149
- }
150
129
  }
151
130
  }
152
131
  }
@@ -237,12 +216,6 @@ type LastHumanReviewerQueryResult = {
237
216
  submittedAt: string | null;
238
217
  }>;
239
218
  };
240
- timelineItems: {
241
- nodes: Array<{
242
- __typename: string;
243
- requestedReviewer: RequestedReviewerNode;
244
- }>;
245
- };
246
219
  };
247
220
  };
248
221
  };
@@ -1066,30 +1039,11 @@ export const fetchLastHumanReviewer = Effect.fn("pr.fetchLastHumanReviewer")(fun
1066
1039
 
1067
1040
  const prNode = response.repository.pullRequest;
1068
1041
 
1069
- const requestedFromTimeline: string[] = [];
1070
- for (const item of prNode.timelineItems.nodes) {
1071
- const login = requestedReviewerLogin(item.requestedReviewer);
1072
- if (login === null) {
1073
- continue;
1074
- }
1075
- if (item.__typename === "ReviewRequestedEvent") {
1076
- if (!requestedFromTimeline.includes(login)) {
1077
- requestedFromTimeline.push(login);
1078
- }
1079
- } else {
1080
- const index = requestedFromTimeline.indexOf(login);
1081
- if (index !== -1) {
1082
- requestedFromTimeline.splice(index, 1);
1083
- }
1084
- }
1085
- }
1086
-
1087
- const currentRequestedReviewers =
1088
- requestedFromTimeline.length > 0
1089
- ? requestedFromTimeline
1090
- : prNode.reviewRequests.nodes
1091
- .map((node) => requestedReviewerLogin(node.requestedReviewer))
1092
- .filter((login): login is string => login !== null);
1042
+ // GitHub clears a pending request on review submit without a ReviewRequestRemovedEvent, so
1043
+ // only reviewRequests is authoritative; timeline replay would report stale reviewers forever.
1044
+ const currentRequestedReviewers = prNode.reviewRequests.nodes
1045
+ .map((node) => requestedReviewerLogin(node.requestedReviewer))
1046
+ .filter((login): login is string => login !== null);
1093
1047
 
1094
1048
  const latestHumanReview = prNode.reviews.nodes.reduce<{ login: string; at: string } | null>(
1095
1049
  (latest, review) => {