@blogic-cz/agent-tools 0.15.11 → 0.15.13
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 +1 -1
- package/package.json +1 -1
- package/src/db-tool/psql.ts +27 -0
- package/src/db-tool/service.ts +4 -1
- package/src/gh-tool/pr/commands.ts +11 -0
- package/src/gh-tool/pr/review.ts +5 -51
- package/src/k8s-tool/service.ts +2 -2
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
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
// Homebrew keeps libpq keg-only, so psql is routinely installed yet absent from PATH.
|
|
5
|
+
const KEG_ONLY_PSQL_DIRECTORIES = ["/opt/homebrew/opt/libpq/bin", "/usr/local/opt/libpq/bin"];
|
|
6
|
+
|
|
7
|
+
export const PSQL_MISSING_HINT =
|
|
8
|
+
"psql was not found on PATH. On macOS install it with `brew install libpq`; libpq is keg-only, so also add /opt/homebrew/opt/libpq/bin (Apple silicon) or /usr/local/opt/libpq/bin (Intel) to PATH.";
|
|
9
|
+
|
|
10
|
+
export const resolvePsqlSearchPath = (
|
|
11
|
+
pathEnv: string | undefined,
|
|
12
|
+
fileExists: (candidate: string) => boolean = existsSync,
|
|
13
|
+
): string | undefined => {
|
|
14
|
+
const directories = (pathEnv ?? "").split(":").filter((directory) => directory.length > 0);
|
|
15
|
+
if (directories.some((directory) => fileExists(join(directory, "psql")))) {
|
|
16
|
+
return pathEnv;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const kegOnly = KEG_ONLY_PSQL_DIRECTORIES.find((directory) =>
|
|
20
|
+
fileExists(join(directory, "psql")),
|
|
21
|
+
);
|
|
22
|
+
if (kegOnly === undefined) {
|
|
23
|
+
return pathEnv;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return directories.length > 0 ? [...directories, kegOnly].join(":") : kegOnly;
|
|
27
|
+
};
|
package/src/db-tool/service.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { resolveEnvironmentScopedPrerequisites } from "#shared/prerequisites/con
|
|
|
10
10
|
import { runWithProfilePrerequisites } from "#shared/prerequisites/runtime";
|
|
11
11
|
import { buildApiProbeArgs } from "#shared/k8s-probe";
|
|
12
12
|
import { DbConfigService, TUNNEL_CHECK_INTERVAL_MS } from "./config-service";
|
|
13
|
+
import { PSQL_MISSING_HINT, resolvePsqlSearchPath } from "./psql";
|
|
13
14
|
import {
|
|
14
15
|
DbConnectionError,
|
|
15
16
|
DbMutationBlockedError,
|
|
@@ -235,7 +236,8 @@ export class DbService extends Context.Service<
|
|
|
235
236
|
new DbQueryError({
|
|
236
237
|
message: `Command execution failed: ${String(platformError)}`,
|
|
237
238
|
sql: "shell command",
|
|
238
|
-
stderr:
|
|
239
|
+
stderr: String(platformError),
|
|
240
|
+
...(String(platformError).includes("psql") ? { hint: PSQL_MISSING_HINT } : {}),
|
|
239
241
|
}),
|
|
240
242
|
),
|
|
241
243
|
);
|
|
@@ -402,6 +404,7 @@ export class DbService extends Context.Service<
|
|
|
402
404
|
stderr: "pipe",
|
|
403
405
|
env: {
|
|
404
406
|
...process.env,
|
|
407
|
+
PATH: resolvePsqlSearchPath(process.env.PATH),
|
|
405
408
|
...(password ? { PGPASSWORD: password } : {}),
|
|
406
409
|
...(isFullyReadOnly(config)
|
|
407
410
|
? { PGOPTIONS: "-c default_transaction_read_only=on" }
|
|
@@ -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
|
});
|
package/src/gh-tool/pr/review.ts
CHANGED
|
@@ -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
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
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) => {
|
package/src/k8s-tool/service.ts
CHANGED
|
@@ -141,7 +141,7 @@ export class K8sService extends Context.Service<
|
|
|
141
141
|
message: `Command execution failed: ${String(platformError)}`,
|
|
142
142
|
command: commandStr,
|
|
143
143
|
exitCode: -1,
|
|
144
|
-
stderr:
|
|
144
|
+
stderr: String(platformError),
|
|
145
145
|
}),
|
|
146
146
|
),
|
|
147
147
|
);
|
|
@@ -320,7 +320,7 @@ export class K8sService extends Context.Service<
|
|
|
320
320
|
message: `Command execution failed: ${String(platformError)}`,
|
|
321
321
|
command: fullCommand,
|
|
322
322
|
exitCode: -1,
|
|
323
|
-
stderr:
|
|
323
|
+
stderr: String(platformError),
|
|
324
324
|
}),
|
|
325
325
|
),
|
|
326
326
|
);
|