@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blogic-cz/agent-tools",
3
- "version": "0.14.49",
3
+ "version": "0.14.51",
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",
@@ -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([sqlCommand, schemaCommand, envsCommand, commandsCommand]),
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, {
@@ -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
- viewPR(prNumber),
107
- fetchThreads(prNumber, true),
108
- fetchThreads(prNumber, false, true),
109
- fetchDiscussionSummary(prNumber),
110
- fetchChecks(prNumber, false, false, 0),
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 { ready, classification, info, unresolvedThreads, visibleOpenThreads, summary, checks };
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
  {
@@ -7,6 +7,7 @@ export {
7
7
  prCreateCommand,
8
8
  prDiscussionSummaryCommand,
9
9
  prEditCommand,
10
+ prFeedbackCommand,
10
11
  prIssueCommentsCommand,
11
12
  prIssueCommentsLatestCommand,
12
13
  prListCommand,
@@ -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,
@@ -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
- status: finalState.status,
283
- conclusion: finalState.conclusion,
284
- jobs: finalState.jobs.map((job) => ({
285
- name: job.name,
286
- status: job.status,
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"));