@blogic-cz/agent-tools 0.14.49 → 0.14.51
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 +1 -1
- package/src/db-tool/index.ts +84 -1
- package/src/gh-tool/index.ts +2 -0
- package/src/gh-tool/pr/commands.ts +45 -8
- package/src/gh-tool/pr/index.ts +1 -0
- package/src/gh-tool/pr/review.ts +18 -0
- package/src/gh-tool/workflow.ts +42 -15
package/package.json
CHANGED
package/src/db-tool/index.ts
CHANGED
|
@@ -144,11 +144,94 @@ const envsCommand = Command.make(
|
|
|
144
144
|
Command.withDescription("List configured database environments and the default (no network)"),
|
|
145
145
|
);
|
|
146
146
|
|
|
147
|
+
const ACTIVITY_SQL = `SELECT pid, usename AS "user", state,
|
|
148
|
+
date_trunc('second', now() - xact_start) AS xact_age,
|
|
149
|
+
date_trunc('second', now() - query_start) AS query_age,
|
|
150
|
+
wait_event_type, wait_event, left(query, 200) AS query
|
|
151
|
+
FROM pg_stat_activity
|
|
152
|
+
WHERE pid <> pg_backend_pid() AND state IS NOT NULL
|
|
153
|
+
ORDER BY xact_start ASC NULLS LAST`;
|
|
154
|
+
|
|
155
|
+
const LOCKS_SQL = `SELECT blocking.pid AS blocking_pid, blocking.usename AS blocking_user,
|
|
156
|
+
left(blocking.query, 150) AS blocking_query,
|
|
157
|
+
blocked.pid AS blocked_pid, blocked.usename AS blocked_user,
|
|
158
|
+
left(blocked.query, 150) AS blocked_query
|
|
159
|
+
FROM pg_stat_activity blocked
|
|
160
|
+
JOIN pg_stat_activity blocking ON blocking.pid = ANY(pg_blocking_pids(blocked.pid))
|
|
161
|
+
ORDER BY blocking.pid, blocked.pid`;
|
|
162
|
+
|
|
163
|
+
const GRANTS_SQL = `SELECT grantee, table_schema AS schema, 'table' AS kind, table_name AS object,
|
|
164
|
+
string_agg(privilege_type, ', ' ORDER BY privilege_type) AS privileges
|
|
165
|
+
FROM information_schema.role_table_grants
|
|
166
|
+
WHERE table_schema NOT IN ('pg_catalog', 'information_schema')
|
|
167
|
+
GROUP BY grantee, table_schema, table_name
|
|
168
|
+
UNION ALL
|
|
169
|
+
SELECT grantee, object_schema AS schema, 'sequence' AS kind, object_name AS object,
|
|
170
|
+
string_agg(privilege_type, ', ' ORDER BY privilege_type) AS privileges
|
|
171
|
+
FROM information_schema.role_usage_grants
|
|
172
|
+
WHERE object_type = 'SEQUENCE' AND object_schema NOT IN ('pg_catalog', 'information_schema')
|
|
173
|
+
GROUP BY grantee, object_schema, object_name
|
|
174
|
+
ORDER BY grantee, schema, kind, object`;
|
|
175
|
+
|
|
176
|
+
const makeDiagnosticCommand = (name: string, description: string, sql: string) =>
|
|
177
|
+
Command.make(
|
|
178
|
+
name,
|
|
179
|
+
{
|
|
180
|
+
env: Flag.optional(Flag.string("env")).pipe(
|
|
181
|
+
Flag.withDescription(
|
|
182
|
+
"Target database environment name (e.g. local, test, prod). Falls back to defaultEnvironment in config.",
|
|
183
|
+
),
|
|
184
|
+
),
|
|
185
|
+
limit: Flag.optional(Flag.integer("limit")).pipe(
|
|
186
|
+
Flag.withDescription("Max rows to return (default 50). Use 0 for no cap."),
|
|
187
|
+
),
|
|
188
|
+
format: formatOption,
|
|
189
|
+
profile: Flag.optional(Flag.string("profile")).pipe(
|
|
190
|
+
Flag.withDescription(
|
|
191
|
+
"Database profile name from agent-tools.json5 (if multiple configured)",
|
|
192
|
+
),
|
|
193
|
+
),
|
|
194
|
+
},
|
|
195
|
+
({ env, limit, format }) =>
|
|
196
|
+
Effect.gen(function* () {
|
|
197
|
+
const resolvedEnv = yield* resolveEnv(env);
|
|
198
|
+
const db = yield* DbService;
|
|
199
|
+
const result = yield* db.executeQuery(resolvedEnv, sql, Option.getOrUndefined(limit));
|
|
200
|
+
yield* Console.log(formatOutput(result, format));
|
|
201
|
+
}),
|
|
202
|
+
).pipe(Command.withDescription(description));
|
|
203
|
+
|
|
204
|
+
const activityCommand = makeDiagnosticCommand(
|
|
205
|
+
"activity",
|
|
206
|
+
"Show live sessions from pg_stat_activity (state, transaction/query age, wait event), longest-running first",
|
|
207
|
+
ACTIVITY_SQL,
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
const locksCommand = makeDiagnosticCommand(
|
|
211
|
+
"locks",
|
|
212
|
+
"Show blocking chains (blocker -> blocked) from pg_blocking_pids + pg_stat_activity",
|
|
213
|
+
LOCKS_SQL,
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
const grantsCommand = makeDiagnosticCommand(
|
|
217
|
+
"grants",
|
|
218
|
+
"Show per-role table/sequence privileges by schema (excludes system schemas)",
|
|
219
|
+
GRANTS_SQL,
|
|
220
|
+
);
|
|
221
|
+
|
|
147
222
|
const commandsCommand = makeSchemaCommand(() => mainCommand);
|
|
148
223
|
|
|
149
224
|
const mainCommand = Command.make("db-tool", {}).pipe(
|
|
150
225
|
Command.withDescription("Database Query Tool for Coding Agents"),
|
|
151
|
-
Command.withSubcommands([
|
|
226
|
+
Command.withSubcommands([
|
|
227
|
+
sqlCommand,
|
|
228
|
+
schemaCommand,
|
|
229
|
+
activityCommand,
|
|
230
|
+
locksCommand,
|
|
231
|
+
grantsCommand,
|
|
232
|
+
envsCommand,
|
|
233
|
+
commandsCommand,
|
|
234
|
+
]),
|
|
152
235
|
);
|
|
153
236
|
|
|
154
237
|
const cli = Command.run(mainCommand, {
|
package/src/gh-tool/index.ts
CHANGED
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
prIssueCommentsLatestCommand,
|
|
32
32
|
prCommentCommand,
|
|
33
33
|
prDiscussionSummaryCommand,
|
|
34
|
+
prFeedbackCommand,
|
|
34
35
|
prReplyCommand,
|
|
35
36
|
prResolveCommand,
|
|
36
37
|
prReviewsCommand,
|
|
@@ -81,6 +82,7 @@ const prCommand = Command.make("pr", {}).pipe(
|
|
|
81
82
|
prThreadsCommand,
|
|
82
83
|
prCommentsCommand,
|
|
83
84
|
prReviewsCommand,
|
|
85
|
+
prFeedbackCommand,
|
|
84
86
|
prIssueCommentsCommand,
|
|
85
87
|
prIssueCommentsLatestCommand,
|
|
86
88
|
prCommentCommand,
|
|
@@ -44,6 +44,7 @@ import {
|
|
|
44
44
|
import {
|
|
45
45
|
fetchComments,
|
|
46
46
|
fetchDiscussionSummary,
|
|
47
|
+
fetchFeedback,
|
|
47
48
|
fetchIssueComments,
|
|
48
49
|
fetchLatestIssueComment,
|
|
49
50
|
fetchReviews,
|
|
@@ -102,13 +103,16 @@ export const parsePrNumbers = (input: string): readonly number[] =>
|
|
|
102
103
|
export const fetchReviewTriage = Effect.fn("pr.fetchReviewTriage")(function* (
|
|
103
104
|
prNumber: number | null,
|
|
104
105
|
) {
|
|
105
|
-
const [info, unresolvedThreads, visibleOpenThreads, summary, checks] = yield* Effect.all(
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
106
|
+
const [info, unresolvedThreads, visibleOpenThreads, summary, checks, reviews] = yield* Effect.all(
|
|
107
|
+
[
|
|
108
|
+
viewPR(prNumber),
|
|
109
|
+
fetchThreads(prNumber, true),
|
|
110
|
+
fetchThreads(prNumber, false, true),
|
|
111
|
+
fetchDiscussionSummary(prNumber),
|
|
112
|
+
fetchChecks(prNumber, false, false, 0),
|
|
113
|
+
fetchReviews(prNumber, null, null, null),
|
|
114
|
+
],
|
|
115
|
+
);
|
|
112
116
|
const classification = classifyReviewTriage(summary, checks);
|
|
113
117
|
|
|
114
118
|
// Single merge-readiness verdict so agents stop re-stitching mergeable + checks + threads +
|
|
@@ -128,7 +132,16 @@ export const fetchReviewTriage = Effect.fn("pr.fetchReviewTriage")(function* (
|
|
|
128
132
|
blocking,
|
|
129
133
|
};
|
|
130
134
|
|
|
131
|
-
return {
|
|
135
|
+
return {
|
|
136
|
+
ready,
|
|
137
|
+
classification,
|
|
138
|
+
info,
|
|
139
|
+
unresolvedThreads,
|
|
140
|
+
visibleOpenThreads,
|
|
141
|
+
summary,
|
|
142
|
+
checks,
|
|
143
|
+
reviews,
|
|
144
|
+
};
|
|
132
145
|
});
|
|
133
146
|
|
|
134
147
|
export const prViewCommand = Command.make(
|
|
@@ -650,6 +663,30 @@ export const prReviewsCommand = Command.make(
|
|
|
650
663
|
),
|
|
651
664
|
);
|
|
652
665
|
|
|
666
|
+
export const prFeedbackCommand = Command.make(
|
|
667
|
+
"feedback",
|
|
668
|
+
{
|
|
669
|
+
format: formatOption,
|
|
670
|
+
pr: Flag.integer("pr").pipe(
|
|
671
|
+
Flag.withDescription("PR number (default: current branch PR)"),
|
|
672
|
+
Flag.optional,
|
|
673
|
+
),
|
|
674
|
+
repo: repoOption,
|
|
675
|
+
},
|
|
676
|
+
({ format, pr, repo }) =>
|
|
677
|
+
withRepo(
|
|
678
|
+
repo,
|
|
679
|
+
Effect.gen(function* () {
|
|
680
|
+
const feedback = yield* fetchFeedback(Option.getOrNull(pr));
|
|
681
|
+
yield* logFormatted(feedback, format);
|
|
682
|
+
}),
|
|
683
|
+
),
|
|
684
|
+
).pipe(
|
|
685
|
+
Command.withDescription(
|
|
686
|
+
"Full review-response inventory in one call: submitted reviews + threads (with state) + inline comments + issue comments",
|
|
687
|
+
),
|
|
688
|
+
);
|
|
689
|
+
|
|
653
690
|
export const prIssueCommentsCommand = Command.make(
|
|
654
691
|
"issue-comments",
|
|
655
692
|
{
|
package/src/gh-tool/pr/index.ts
CHANGED
package/src/gh-tool/pr/review.ts
CHANGED
|
@@ -464,6 +464,24 @@ export const fetchReviews = Effect.fn("pr.fetchReviews")(function* (
|
|
|
464
464
|
return reviews;
|
|
465
465
|
});
|
|
466
466
|
|
|
467
|
+
/**
|
|
468
|
+
* Full review-response inventory for a PR in one call: submitted review summaries,
|
|
469
|
+
* all review threads with resolution state, inline review comments, and issue
|
|
470
|
+
* (discussion) comments. Collapses the four separate fetches agents otherwise stitch.
|
|
471
|
+
*/
|
|
472
|
+
export const fetchFeedback = Effect.fn("pr.fetchFeedback")(function* (pr: number | null) {
|
|
473
|
+
const resolvedPr = pr ?? (yield* viewPR(null)).number;
|
|
474
|
+
|
|
475
|
+
const [reviews, threads, inlineComments, issueComments] = yield* Effect.all([
|
|
476
|
+
fetchReviews(resolvedPr, null, null, null),
|
|
477
|
+
fetchThreads(resolvedPr, false),
|
|
478
|
+
fetchComments(resolvedPr, null),
|
|
479
|
+
fetchIssueComments(resolvedPr, null, null, null),
|
|
480
|
+
]);
|
|
481
|
+
|
|
482
|
+
return { reviews, threads, inlineComments, issueComments };
|
|
483
|
+
});
|
|
484
|
+
|
|
467
485
|
export const fetchLatestIssueComment = Effect.fn("pr.fetchLatestIssueComment")(function* (
|
|
468
486
|
pr: number | null,
|
|
469
487
|
author: string | null,
|
package/src/gh-tool/workflow.ts
CHANGED
|
@@ -245,10 +245,39 @@ export const dispatchWorkflow = Effect.fn("workflow.dispatchWorkflow")(function*
|
|
|
245
245
|
// then fall back to a one-shot snapshot so a timeout never returns nothing.
|
|
246
246
|
const DEFAULT_WATCH_RUN_TIMEOUT_SECONDS = CI_CHECK_WATCH_TIMEOUT_MS / 1000;
|
|
247
247
|
|
|
248
|
+
export const buildWatchResult = (
|
|
249
|
+
runId: number,
|
|
250
|
+
finalState: {
|
|
251
|
+
status: string;
|
|
252
|
+
conclusion: string | null;
|
|
253
|
+
jobs: ReadonlyArray<{ name: string; status: string; conclusion: string | null }>;
|
|
254
|
+
},
|
|
255
|
+
watchStdout: string | null,
|
|
256
|
+
frames: boolean,
|
|
257
|
+
timeoutSeconds: number,
|
|
258
|
+
) => ({
|
|
259
|
+
runId,
|
|
260
|
+
status: finalState.status,
|
|
261
|
+
conclusion: finalState.conclusion,
|
|
262
|
+
jobs: finalState.jobs.map((job) => ({
|
|
263
|
+
name: job.name,
|
|
264
|
+
status: job.status,
|
|
265
|
+
conclusion: job.conclusion,
|
|
266
|
+
})),
|
|
267
|
+
...(watchStdout === null
|
|
268
|
+
? {
|
|
269
|
+
watchOutput: `(watch timed out after ${timeoutSeconds}s; status taken from snapshot — re-run to keep watching)`,
|
|
270
|
+
}
|
|
271
|
+
: frames
|
|
272
|
+
? { watchOutput: watchStdout }
|
|
273
|
+
: {}),
|
|
274
|
+
});
|
|
275
|
+
|
|
248
276
|
const watchRun = Effect.fn("workflow.watchRun")(function* (
|
|
249
277
|
runId: number,
|
|
250
278
|
repo: string | null,
|
|
251
279
|
timeoutSeconds: number,
|
|
280
|
+
frames: boolean,
|
|
252
281
|
) {
|
|
253
282
|
const gh = yield* GitHubService;
|
|
254
283
|
|
|
@@ -277,20 +306,13 @@ const watchRun = Effect.fn("workflow.watchRun")(function* (
|
|
|
277
306
|
|
|
278
307
|
const finalState = yield* viewRun(runId, repo);
|
|
279
308
|
|
|
280
|
-
return
|
|
309
|
+
return buildWatchResult(
|
|
281
310
|
runId,
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
conclusion: job.conclusion,
|
|
288
|
-
})),
|
|
289
|
-
watchOutput:
|
|
290
|
-
result === null
|
|
291
|
-
? `(watch timed out after ${timeoutSeconds}s; status taken from snapshot — re-run to keep watching)`
|
|
292
|
-
: result.stdout,
|
|
293
|
-
};
|
|
311
|
+
finalState,
|
|
312
|
+
result === null ? null : result.stdout,
|
|
313
|
+
frames,
|
|
314
|
+
timeoutSeconds,
|
|
315
|
+
);
|
|
294
316
|
});
|
|
295
317
|
|
|
296
318
|
const fetchAnnotations = Effect.fn("workflow.fetchAnnotations")(function* (opts: {
|
|
@@ -712,6 +734,11 @@ export const workflowWatchCommand = Command.make(
|
|
|
712
734
|
"watch",
|
|
713
735
|
{
|
|
714
736
|
format: formatOption,
|
|
737
|
+
frames: Flag.boolean("frames").pipe(
|
|
738
|
+
Flag.withDescription(
|
|
739
|
+
"Include the raw watch progress frames (large); omitted by default — final status/conclusion/jobs are always returned",
|
|
740
|
+
),
|
|
741
|
+
),
|
|
715
742
|
repo: repoOption,
|
|
716
743
|
run: Flag.integer("run").pipe(Flag.withDescription("Workflow run ID to watch")),
|
|
717
744
|
timeout: Flag.integer("timeout").pipe(
|
|
@@ -725,10 +752,10 @@ export const workflowWatchCommand = Command.make(
|
|
|
725
752
|
),
|
|
726
753
|
),
|
|
727
754
|
},
|
|
728
|
-
({ format, repo, run, timeout }) =>
|
|
755
|
+
({ format, frames, repo, run, timeout }) =>
|
|
729
756
|
Effect.gen(function* () {
|
|
730
757
|
const resolvedRepo = yield* resolveRepoArg(repo);
|
|
731
|
-
const result = yield* watchRun(run, resolvedRepo, timeout);
|
|
758
|
+
const result = yield* watchRun(run, resolvedRepo, timeout, frames);
|
|
732
759
|
yield* logFormatted(result, format);
|
|
733
760
|
}),
|
|
734
761
|
).pipe(Command.withDescription("Watch a workflow run until it completes, then show final status"));
|