@blogic-cz/agent-tools 1.14.5 → 1.15.0

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.
Files changed (62) hide show
  1. package/README.md +3 -11
  2. package/dist/api-tool/main.d.ts +6 -1
  3. package/dist/api-tool/main.d.ts.map +1 -1
  4. package/dist/audit-tool/main.d.ts +6 -1
  5. package/dist/audit-tool/main.d.ts.map +1 -1
  6. package/dist/az-tool/main.d.ts +6 -1
  7. package/dist/az-tool/main.d.ts.map +1 -1
  8. package/dist/azdo-tool/main.d.ts +6 -1
  9. package/dist/azdo-tool/main.d.ts.map +1 -1
  10. package/dist/credential-guard/registry.d.ts.map +1 -1
  11. package/dist/db-tool/main.d.ts +8 -1
  12. package/dist/db-tool/main.d.ts.map +1 -1
  13. package/dist/db-tool/service.d.ts +1 -0
  14. package/dist/db-tool/service.d.ts.map +1 -1
  15. package/dist/gh-tool/main.d.ts +4 -1
  16. package/dist/gh-tool/main.d.ts.map +1 -1
  17. package/dist/gh-tool/pr/commands.d.ts +1 -0
  18. package/dist/gh-tool/pr/commands.d.ts.map +1 -1
  19. package/dist/gh-tool/read/pulls.d.ts +44 -0
  20. package/dist/gh-tool/read/pulls.d.ts.map +1 -1
  21. package/dist/gh-tool/read/reads.d.ts +22 -0
  22. package/dist/gh-tool/read/reads.d.ts.map +1 -1
  23. package/dist/gh-tool/read/store.d.ts +4 -1
  24. package/dist/gh-tool/read/store.d.ts.map +1 -1
  25. package/dist/k8s-tool/main.d.ts +8 -1
  26. package/dist/k8s-tool/main.d.ts.map +1 -1
  27. package/dist/logs-tool/main.d.ts +4 -0
  28. package/dist/logs-tool/main.d.ts.map +1 -1
  29. package/dist/observability-tool/main.d.ts +6 -1
  30. package/dist/observability-tool/main.d.ts.map +1 -1
  31. package/dist/session-tool/main.d.ts +4 -0
  32. package/dist/session-tool/main.d.ts.map +1 -1
  33. package/dist/shared/secret-env.d.ts +9 -0
  34. package/dist/shared/secret-env.d.ts.map +1 -1
  35. package/package.json +2 -2
  36. package/src/api-tool/index.ts +1 -1
  37. package/src/api-tool/main.ts +19 -6
  38. package/src/audit-tool/index.ts +1 -1
  39. package/src/audit-tool/main.ts +13 -4
  40. package/src/az-tool/index.ts +1 -1
  41. package/src/az-tool/main.ts +10 -4
  42. package/src/azdo-tool/index.ts +1 -1
  43. package/src/azdo-tool/main.ts +13 -4
  44. package/src/credential-guard/registry.ts +93 -3
  45. package/src/db-tool/index.ts +1 -1
  46. package/src/db-tool/main.ts +10 -4
  47. package/src/db-tool/service.ts +6 -1
  48. package/src/gh-tool/index.ts +1 -1
  49. package/src/gh-tool/main.ts +5 -4
  50. package/src/gh-tool/pr/commands.ts +35 -2
  51. package/src/gh-tool/read/pulls.ts +178 -0
  52. package/src/gh-tool/read/reads.ts +10 -1
  53. package/src/gh-tool/read/store.ts +8 -2
  54. package/src/k8s-tool/index.ts +1 -1
  55. package/src/k8s-tool/main.ts +10 -4
  56. package/src/logs-tool/index.ts +1 -1
  57. package/src/logs-tool/main.ts +13 -4
  58. package/src/observability-tool/index.ts +1 -1
  59. package/src/observability-tool/main.ts +10 -2
  60. package/src/session-tool/index.ts +1 -1
  61. package/src/session-tool/main.ts +10 -13
  62. package/src/shared/secret-env.ts +108 -88
@@ -184,7 +184,7 @@ EXAMPLES:
184
184
 
185
185
  const commandsCommand = makeSchemaCommand(() => mainCommand);
186
186
 
187
- const mainCommand = Command.make("azdo-tool", {}).pipe(
187
+ export const mainCommand = Command.make("azdo-tool", {}).pipe(
188
188
  Command.withDescription(
189
189
  `Azure DevOps Tool for Coding Agents (READ-ONLY)
190
190
 
@@ -219,6 +219,15 @@ const program = withRedactedOutput(withAudit("azdo", cli)).pipe(
219
219
  Effect.tapCause(renderCauseToStderr),
220
220
  );
221
221
 
222
- BunRuntime.runMain(program, {
223
- disableErrorReporting: true,
224
- });
222
+ export const runMain = () =>
223
+ BunRuntime.runMain(program, {
224
+ disableErrorReporting: true,
225
+ });
226
+
227
+ export const runProgram = (args: readonly string[]) =>
228
+ withRedactedOutput(
229
+ withAudit(
230
+ "azdo",
231
+ Command.runWith(mainCommand, { version: VERSION, renderErrors: false })(args),
232
+ ),
233
+ ).pipe(Effect.provide(MainLayer), Effect.tapCause(renderCauseToStderr));
@@ -1164,6 +1164,78 @@ export const EXECUTABLES: readonly ExecutableEntry[] = [
1164
1164
  operands: [path],
1165
1165
  startup: ["JAVA_TOOL_OPTIONS", "JDK_JAVA_OPTIONS", "CLASSPATH", "_JAVA_OPTIONS", "JAVA_HOME"],
1166
1166
  },
1167
+ {
1168
+ name: "hc",
1169
+ rejectsAbbreviations: true,
1170
+ options: {
1171
+ ...flags("--json", "--help", "-h", "--version", "-V", "--skill", "--licenses"),
1172
+ },
1173
+ operands: [data],
1174
+ subcommands: {
1175
+ ...Object.fromEntries(
1176
+ [
1177
+ "get",
1178
+ "keys",
1179
+ "interrupt",
1180
+ "attach",
1181
+ "kill",
1182
+ "rm",
1183
+ "label",
1184
+ "keep",
1185
+ "ls",
1186
+ "read",
1187
+ "wait",
1188
+ "templates",
1189
+ "events",
1190
+ "schema",
1191
+ "connect",
1192
+ "server",
1193
+ ].map((name) => [
1194
+ name,
1195
+ {
1196
+ operands: [data],
1197
+ options: {
1198
+ ...flags("--all", "--off", "--wait", "--force"),
1199
+ "--lines": arg(data),
1200
+ "--output": arg(data),
1201
+ "--children": arg(data),
1202
+ "--timeout": arg(data),
1203
+ },
1204
+ },
1205
+ ]),
1206
+ ),
1207
+ send: {
1208
+ operands: [data, path],
1209
+ options: {
1210
+ ...flags("--force", "--json", "--wait"),
1211
+ "--if": arg(data),
1212
+ "--timeout": arg(data),
1213
+ },
1214
+ },
1215
+ run: {
1216
+ operands: [data],
1217
+ options: {
1218
+ ...flags("--bg", "--outlive-parent", "--no-parent", "--no-notify", "--rm", "--keep"),
1219
+ "--name": arg(data),
1220
+ "--cwd": arg(path),
1221
+ "--on": arg(data),
1222
+ "--label": arg(data),
1223
+ "--parent": arg(data),
1224
+ "--timeout": arg(data),
1225
+ },
1226
+ },
1227
+ exec: {
1228
+ operands: [data],
1229
+ options: {
1230
+ ...flags("-i", "--stdin"),
1231
+ "--cwd": arg(path),
1232
+ "--on": arg(data),
1233
+ "--env": { roles: [data], issue: unknown("hc environment override") },
1234
+ "--timeout": arg(data),
1235
+ },
1236
+ },
1237
+ },
1238
+ },
1167
1239
  {
1168
1240
  name: "herdr",
1169
1241
  options: {
@@ -4231,7 +4303,11 @@ function classifyInvocation(
4231
4303
  }
4232
4304
  const a = argv[position];
4233
4305
  if (a === undefined) {
4234
- if (["env", "forward", "timeout", "xargs"].includes(entry.semantics ?? ""))
4306
+ if (
4307
+ ["env", "forward", "timeout", "xargs"].includes(entry.semantics ?? "") ||
4308
+ (entry.name === "hc" &&
4309
+ (mode === entry.subcommands?.run || mode === entry.subcommands?.exec))
4310
+ )
4235
4311
  issues.push({
4236
4312
  kind: "missing-knowledge",
4237
4313
  rule: "opaque-executable",
@@ -4457,8 +4533,8 @@ function classifyInvocation(
4457
4533
  (attached ?? argv[position + 1]) === undefined
4458
4534
  )
4459
4535
  issues.push(unknown("Bun option operand is not literal"));
4460
- if (entry.name === "bun" && optionName === "--cwd") {
4461
- if (cwdOperands.length) issues.push(unknown("repeated Bun working directory"));
4536
+ if (["bun", "hc"].includes(entry.name) && optionName === "--cwd") {
4537
+ if (cwdOperands.length) issues.push(unknown(`repeated ${entry.name} working directory`));
4462
4538
  cwdOperands.push({
4463
4539
  index: attached === undefined ? position + 1 : position,
4464
4540
  ...(attached !== undefined ? { literal: attached } : {}),
@@ -4581,6 +4657,18 @@ function classifyInvocation(
4581
4657
  position++;
4582
4658
  continue;
4583
4659
  }
4660
+ if (
4661
+ entry.name === "hc" &&
4662
+ (mode === entry.subcommands?.run || mode === entry.subcommands?.exec)
4663
+ ) {
4664
+ if (mode === entry.subcommands?.run && position === argv.length - 1) {
4665
+ roles.push({ index: position, role: code("bash") });
4666
+ roles.push({ index: position, role: path });
4667
+ issues.push(...unknownOperandIssues([a]));
4668
+ codeSeen = true;
4669
+ } else child(position);
4670
+ break;
4671
+ }
4584
4672
  if (entry.name === "git" && mode === entry.subcommands?.bisect?.subcommands?.run) {
4585
4673
  child(position);
4586
4674
  break;
@@ -4669,6 +4757,8 @@ function classifyInvocation(
4669
4757
  if (effect && effect !== "none") issues.push(effect);
4670
4758
  if (!positional.length && !children.length && !codeSeen && entry.bare) issues.push(entry.bare);
4671
4759
  roles.push(...optionRoles);
4760
+ if (entry.name === "hc" && mode === entry.subcommands?.send)
4761
+ issues.push(...unknownOperandIssues(positional.slice(1).map((index) => argv[index])));
4672
4762
  const operands =
4673
4763
  (filterOption && ["sed", "awk"].includes(entry.semantics ?? "")) ||
4674
4764
  (filterOption &&
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env bun
2
2
  import { checkEffectVersion } from "#shared/effect-version";
3
3
 
4
- if (await checkEffectVersion()) await import("./main");
4
+ if (await checkEffectVersion()) (await import("./main")).runMain();
5
5
  else process.exitCode = 1;
@@ -225,7 +225,7 @@ const grantsCommand = makeDiagnosticCommand(
225
225
 
226
226
  const commandsCommand = makeSchemaCommand(() => mainCommand);
227
227
 
228
- const mainCommand = Command.make("db-tool", {}).pipe(
228
+ export const mainCommand = Command.make("db-tool", {}).pipe(
229
229
  Command.withDescription("Database Query Tool for Coding Agents"),
230
230
  Command.withSubcommands([
231
231
  sqlCommand,
@@ -261,6 +261,12 @@ const program = withRedactedOutput(withAudit("db", cli)).pipe(
261
261
  Effect.tapCause(renderCauseToStderr),
262
262
  );
263
263
 
264
- BunRuntime.runMain(program, {
265
- disableErrorReporting: true,
266
- });
264
+ export const runMain = () =>
265
+ BunRuntime.runMain(program, {
266
+ disableErrorReporting: true,
267
+ });
268
+
269
+ export const runProgram = (args: readonly string[]) =>
270
+ withRedactedOutput(
271
+ withAudit("db", Command.runWith(mainCommand, { version: VERSION, renderErrors: false })(args)),
272
+ ).pipe(Effect.provide(MainLayer), Effect.tapCause(renderCauseToStderr));
@@ -133,6 +133,10 @@ export function isFullyReadOnly(
133
133
  // Re-exported from the shared probe so existing `#db/service` consumers keep working.
134
134
  export { buildApiProbeArgs } from "#shared/k8s-probe";
135
135
 
136
+ export const DbGuardianInProcess = Context.Reference<boolean>("@agent-tools/DbGuardianInProcess", {
137
+ defaultValue: () => false,
138
+ });
139
+
136
140
  export class DbService extends Context.Service<
137
141
  DbService,
138
142
  {
@@ -156,6 +160,7 @@ export class DbService extends Context.Service<
156
160
  const sqlClient = yield* DbSqlClient;
157
161
  const secretEnvResolver = yield* SecretEnvResolver;
158
162
  const agentToolsConfig = yield* ConfigService;
163
+ const runGuardianInProcess = yield* DbGuardianInProcess;
159
164
  const dbConfig = yield* DbConfigService;
160
165
 
161
166
  if (!dbConfig) {
@@ -364,7 +369,7 @@ export class DbService extends Context.Service<
364
369
  prerequisiteConfig,
365
370
  (command, _label) => executeShellCommand(command),
366
371
  effect,
367
- { tryWithoutPrerequisites: true },
372
+ { tryWithoutPrerequisites: true, runGuardianInProcess },
368
373
  ).pipe(
369
374
  Effect.mapError((error) =>
370
375
  isPrerequisiteRunError(error)
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env bun
2
2
  import { checkEffectVersion } from "#shared/effect-version";
3
3
 
4
- if (await checkEffectVersion()) await import("./main");
4
+ if (await checkEffectVersion()) (await import("./main")).runMain();
5
5
  else process.exitCode = 1;
@@ -222,7 +222,7 @@ const gistCommand = Command.make("gist", {}).pipe(
222
222
 
223
223
  const commandsCommand = makeSchemaCommand(() => mainCommand);
224
224
 
225
- const mainCommand = Command.make("gh-tool", {}).pipe(
225
+ export const mainCommand = Command.make("gh-tool", {}).pipe(
226
226
  Command.withDescription(
227
227
  `GitHub CLI Tool for Coding Agents
228
228
 
@@ -307,6 +307,7 @@ const observed = Effect.gen(function* () {
307
307
  });
308
308
  const program = observed.pipe(Effect.provide(MainLayer), Effect.tapCause(renderCauseToStderr));
309
309
 
310
- BunRuntime.runMain(program, {
311
- disableErrorReporting: true,
312
- });
310
+ export const runMain = () =>
311
+ BunRuntime.runMain(program, {
312
+ disableErrorReporting: true,
313
+ });
@@ -1,4 +1,4 @@
1
- import { reads, writes } from "#gh/read/index";
1
+ import { reads, resolveRepo, writes } from "#gh/read/index";
2
2
  import { repoSlug } from "#gh/read/keys";
3
3
  import { Command, Flag, Param } from "effect/cli";
4
4
  import { Console, Effect, Option } from "effect";
@@ -511,6 +511,28 @@ const compactReviewTriage = (
511
511
  visibleOpenThreadIds: result.visibleOpenThreads.map((thread) => thread.threadId),
512
512
  });
513
513
 
514
+ const fetchReviewTriageSummary = Effect.fn("pr.fetchReviewTriageSummary")(function* (pr: number) {
515
+ const repo = yield* resolveRepo(null);
516
+ return yield* reads
517
+ .prReviewTriageSummary(repo, pr)
518
+ .pipe(Effect.map((result) => ({ pr, ...result })));
519
+ });
520
+
521
+ const fetchReviewTriageSummaryBatch = Effect.fn("pr.fetchReviewTriageSummaryBatch")(function* (
522
+ prs: readonly number[],
523
+ ) {
524
+ return yield* Effect.forEach(
525
+ prs,
526
+ (pr) =>
527
+ fetchReviewTriageSummary(pr).pipe(
528
+ Effect.catch((error) =>
529
+ Effect.succeed({ pr, error: error instanceof Error ? error.message : String(error) }),
530
+ ),
531
+ ),
532
+ { concurrency: 5 },
533
+ );
534
+ });
535
+
514
536
  export const fetchReviewTriageBatch = Effect.fn("pr.fetchReviewTriageBatch")(function* (
515
537
  prs: readonly number[],
516
538
  ) {
@@ -1835,12 +1857,23 @@ export const prReviewTriageBatchCommand = Command.make(
1835
1857
  format: formatOption,
1836
1858
  prs: Flag.String("prs").pipe(Flag.withDescription("Comma-separated PR numbers")),
1837
1859
  repo: repoOption,
1860
+ summary: Flag.Boolean("summary").pipe(
1861
+ Flag.withDescription(
1862
+ "Fetch only PR state, review thread counts, and check names and buckets",
1863
+ ),
1864
+ Flag.withDefault(false),
1865
+ ),
1838
1866
  },
1839
- ({ format, prs, repo }) =>
1867
+ ({ format, prs, repo, summary }) =>
1840
1868
  withRepo(
1841
1869
  repo,
1842
1870
  Effect.gen(function* () {
1843
1871
  const numbers = yield* parsePrNumbers(prs);
1872
+ if (summary) {
1873
+ const results = yield* fetchReviewTriageSummaryBatch(numbers);
1874
+ yield* logFormatted(results, format);
1875
+ return;
1876
+ }
1844
1877
  const results = yield* Effect.all(
1845
1878
  numbers.map((prNumber) => fetchReviewTriage(prNumber)),
1846
1879
  { concurrency: "unbounded" },
@@ -278,6 +278,184 @@ export const prChecks = Effect.fn("reads.prChecks")(function* (
278
278
  });
279
279
  });
280
280
 
281
+ const PR_REVIEW_TRIAGE_SUMMARY_QUERY = `
282
+ query($owner: String!, $name: String!, $pr: Int!, $after: String) {
283
+ repository(owner: $owner, name: $name) {
284
+ pullRequest(number: $pr) {
285
+ number
286
+ title
287
+ state
288
+ isDraft
289
+ baseRefName
290
+ reviewDecision
291
+ mergeable
292
+ reviewRequests(first: 100) {
293
+ nodes {
294
+ requestedReviewer {
295
+ ... on User { login }
296
+ ... on Team { slug }
297
+ ... on Bot { login }
298
+ ... on EnterpriseTeam { name }
299
+ ... on Mannequin { login }
300
+ }
301
+ }
302
+ }
303
+ reviewThreads(first: 100, after: $after) {
304
+ nodes {
305
+ isResolved
306
+ comments(first: 2) {
307
+ nodes { replyTo { id } }
308
+ }
309
+ }
310
+ pageInfo { hasNextPage endCursor }
311
+ }
312
+ }
313
+ }
314
+ }
315
+ `;
316
+
317
+ const PR_REVIEW_THREAD_COUNTS_QUERY = `
318
+ query($owner: String!, $name: String!, $pr: Int!, $after: String) {
319
+ repository(owner: $owner, name: $name) {
320
+ pullRequest(number: $pr) {
321
+ reviewThreads(first: 100, after: $after) {
322
+ nodes {
323
+ isResolved
324
+ comments(first: 2) {
325
+ nodes { replyTo { id } }
326
+ }
327
+ }
328
+ pageInfo { hasNextPage endCursor }
329
+ }
330
+ }
331
+ }
332
+ }
333
+ `;
334
+
335
+ type PrReviewTriageSummaryPage = {
336
+ readonly repository: {
337
+ readonly pullRequest: {
338
+ readonly number: number;
339
+ readonly title: string;
340
+ readonly state: string;
341
+ readonly isDraft: boolean;
342
+ readonly baseRefName: string;
343
+ readonly reviewDecision: string | null;
344
+ readonly mergeable: "MERGEABLE" | "CONFLICTING" | "UNKNOWN";
345
+ readonly reviewRequests: {
346
+ readonly nodes: ReadonlyArray<{
347
+ readonly requestedReviewer: {
348
+ readonly login?: string;
349
+ readonly slug?: string;
350
+ readonly name?: string;
351
+ } | null;
352
+ }>;
353
+ };
354
+ readonly reviewThreads: {
355
+ readonly nodes: ReadonlyArray<{
356
+ readonly isResolved: boolean;
357
+ readonly comments: {
358
+ readonly nodes: ReadonlyArray<{ readonly replyTo: { readonly id: string } | null }>;
359
+ };
360
+ }>;
361
+ readonly pageInfo: { readonly hasNextPage: boolean; readonly endCursor: string | null };
362
+ };
363
+ };
364
+ };
365
+ };
366
+
367
+ export type PrReviewTriageSummary = {
368
+ readonly info: {
369
+ readonly number: number;
370
+ readonly title: string;
371
+ readonly state: string;
372
+ readonly isDraft: boolean;
373
+ readonly baseRefName: string;
374
+ readonly reviewDecision: string;
375
+ readonly mergeable: "MERGEABLE" | "CONFLICTING" | "UNKNOWN";
376
+ readonly reviewRequests: ReadonlyArray<{ readonly login: string }>;
377
+ };
378
+ readonly summary: {
379
+ readonly unresolvedReviewThreadsCount: number;
380
+ readonly unrepliedReviewThreadsCount: number;
381
+ };
382
+ readonly checks: ReadonlyArray<{ readonly name: string; readonly bucket: string }>;
383
+ };
384
+
385
+ export const prReviewTriageSummary = Effect.fn("reads.prReviewTriageSummary")(function* (
386
+ repo: RepoRef,
387
+ pr: number,
388
+ ) {
389
+ const gh = yield* GitHubService;
390
+ const variables = { owner: repo.owner, name: repo.name, pr, after: null as string | null };
391
+ const first = (yield* gh.runGraphQL(
392
+ PR_REVIEW_TRIAGE_SUMMARY_QUERY,
393
+ variables,
394
+ )) as PrReviewTriageSummaryPage;
395
+ const firstPage = first.repository.pullRequest;
396
+ let unresolvedReviewThreadsCount = 0;
397
+ let unrepliedReviewThreadsCount = 0;
398
+ const countThreads = (
399
+ threads: PrReviewTriageSummaryPage["repository"]["pullRequest"]["reviewThreads"]["nodes"],
400
+ ) => {
401
+ for (const thread of threads) {
402
+ if (!thread.isResolved) unresolvedReviewThreadsCount++;
403
+ if (!thread.comments.nodes.some((comment) => comment.replyTo !== null))
404
+ unrepliedReviewThreadsCount++;
405
+ }
406
+ };
407
+ countThreads(firstPage.reviewThreads.nodes);
408
+
409
+ let pageInfo = firstPage.reviewThreads.pageInfo;
410
+ while (pageInfo.hasNextPage && pageInfo.endCursor !== null) {
411
+ const response = (yield* gh.runGraphQL(PR_REVIEW_THREAD_COUNTS_QUERY, {
412
+ ...variables,
413
+ after: pageInfo.endCursor,
414
+ })) as PrReviewTriageSummaryPage;
415
+ const pullRequest = response.repository.pullRequest;
416
+ countThreads(pullRequest.reviewThreads.nodes);
417
+ pageInfo = pullRequest.reviewThreads.pageInfo;
418
+ }
419
+
420
+ const checks = yield* gh
421
+ .runGhJson<ReadonlyArray<{ readonly name: string; readonly bucket: string }>>([
422
+ "pr",
423
+ "checks",
424
+ String(pr),
425
+ "--repo",
426
+ repoSlug(repo),
427
+ "--json",
428
+ "name,bucket",
429
+ ])
430
+ .pipe(
431
+ Effect.catchTag("GitHubCommandError", (error) =>
432
+ /no checks reported/i.test(`${error.message}\n${error.stderr}`)
433
+ ? Effect.succeed([])
434
+ : Effect.fail(error),
435
+ ),
436
+ );
437
+ const reviewRequests = firstPage.reviewRequests.nodes.flatMap(({ requestedReviewer }) => {
438
+ if (requestedReviewer === null) return [];
439
+ const login = requestedReviewer.login ?? requestedReviewer.slug ?? requestedReviewer.name;
440
+ return login === undefined ? [] : [{ login }];
441
+ });
442
+
443
+ return {
444
+ info: {
445
+ number: firstPage.number,
446
+ title: firstPage.title,
447
+ state: firstPage.state,
448
+ isDraft: firstPage.isDraft,
449
+ baseRefName: firstPage.baseRefName,
450
+ reviewDecision: firstPage.reviewDecision ?? "",
451
+ mergeable: firstPage.mergeable,
452
+ reviewRequests,
453
+ },
454
+ summary: { unresolvedReviewThreadsCount, unrepliedReviewThreadsCount },
455
+ checks,
456
+ };
457
+ });
458
+
281
459
  const movedHead = (pr: number) =>
282
460
  new GitHubCommandError({
283
461
  command: `gh-tool pr checks --pr ${pr}`,
@@ -6,7 +6,15 @@ import { cachedRead, ReadCache } from "./cache";
6
6
  import { OPS } from "./classes";
7
7
  import { repoSlug } from "./keys";
8
8
  import { issueView, issueList, issueComments } from "./issues";
9
- import { prView, prList, prChecks, prHead, prBody, checkSignal } from "./pulls";
9
+ import {
10
+ prView,
11
+ prList,
12
+ prChecks,
13
+ prHead,
14
+ prBody,
15
+ checkSignal,
16
+ prReviewTriageSummary,
17
+ } from "./pulls";
10
18
  import { prComments, prReviews, reviewThreads, lastHumanReviewer } from "./reviews";
11
19
  import { reviewGuards } from "./review-guards";
12
20
  import type { RepoRef, RunFilter } from "./keys";
@@ -375,6 +383,7 @@ export const reads = {
375
383
  prView,
376
384
  prList,
377
385
  prChecks,
386
+ prReviewTriageSummary,
378
387
  prHead,
379
388
  prBody,
380
389
  checkSignal,
@@ -87,11 +87,13 @@ export class FileStore {
87
87
  private constructor(
88
88
  readonly directory: string,
89
89
  readonly identity: string,
90
+ private readonly contentionTimeoutMs: number,
90
91
  ) {}
91
92
 
92
93
  static async open(
93
94
  identity: string,
94
95
  directory = process.env["GH_TOOL_CACHE_DIR"] ?? defaultCacheDirectory(),
96
+ options: { contentionTimeoutMs?: number } = {},
95
97
  ): Promise<FileStore> {
96
98
  const uid = process.getuid?.();
97
99
  if (uid === undefined) throw new Error("Cache ownership cannot be verified");
@@ -116,7 +118,11 @@ export class FileStore {
116
118
  await checkDirectory(path);
117
119
  }),
118
120
  );
119
- const store = new FileStore(join(directory, "gh-read-v1"), identity);
121
+ const store = new FileStore(
122
+ join(directory, "gh-read-v1"),
123
+ identity,
124
+ options.contentionTimeoutMs ?? 2000,
125
+ );
120
126
  await mkdir(store.ghCacheDirectory, { recursive: true, mode: 0o700 });
121
127
  await checkDirectory(store.ghCacheDirectory);
122
128
  await store.readClock();
@@ -226,7 +232,7 @@ export class FileStore {
226
232
  }
227
233
  },
228
234
  25,
229
- 2000,
235
+ this.contentionTimeoutMs,
230
236
  );
231
237
  try {
232
238
  return await body();
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env bun
2
2
  import { checkEffectVersion } from "#shared/effect-version";
3
3
 
4
- if (await checkEffectVersion()) await import("./main");
4
+ if (await checkEffectVersion()) (await import("./main")).runMain();
5
5
  else process.exitCode = 1;
@@ -457,7 +457,7 @@ const topCommand = Command.make(
457
457
 
458
458
  const commandsCommand = makeSchemaCommand(() => mainCommand);
459
459
 
460
- const mainCommand = Command.make("k8s-tool", {}).pipe(
460
+ export const mainCommand = Command.make("k8s-tool", {}).pipe(
461
461
  Command.withDescription("Kubernetes CLI Tool for Coding Agents"),
462
462
  Command.withSubcommands([
463
463
  kubectlCommand,
@@ -486,6 +486,12 @@ const program = withRedactedOutput(withAudit("k8s", cli)).pipe(
486
486
  Effect.tapCause(renderCauseToStderr),
487
487
  );
488
488
 
489
- BunRuntime.runMain(program, {
490
- disableErrorReporting: true,
491
- });
489
+ export const runMain = () =>
490
+ BunRuntime.runMain(program, {
491
+ disableErrorReporting: true,
492
+ });
493
+
494
+ export const runProgram = (args: readonly string[]) =>
495
+ withRedactedOutput(
496
+ withAudit("k8s", Command.runWith(mainCommand, { version: VERSION, renderErrors: false })(args)),
497
+ ).pipe(Effect.provide(MainLayer), Effect.tapCause(renderCauseToStderr));
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env bun
2
2
  import { checkEffectVersion } from "#shared/effect-version";
3
3
 
4
- if (await checkEffectVersion()) await import("./main");
4
+ if (await checkEffectVersion()) (await import("./main")).runMain();
5
5
  else process.exitCode = 1;
@@ -221,7 +221,7 @@ const readCommand = Command.make(
221
221
 
222
222
  const commandsCommand = makeSchemaCommand(() => mainCommand);
223
223
 
224
- const mainCommand = Command.make("logs-tool", {}).pipe(
224
+ export const mainCommand = Command.make("logs-tool", {}).pipe(
225
225
  Command.withDescription("Application Logs Tool for Coding Agents"),
226
226
  Command.withSubcommands([listCommand, readCommand, commandsCommand]),
227
227
  );
@@ -249,6 +249,15 @@ const program = withRedactedOutput(withAudit("logs", cli)).pipe(
249
249
  Effect.tapCause(renderCauseToStderr),
250
250
  );
251
251
 
252
- BunRuntime.runMain(program, {
253
- disableErrorReporting: true,
254
- });
252
+ export const runMain = () =>
253
+ BunRuntime.runMain(program, {
254
+ disableErrorReporting: true,
255
+ });
256
+
257
+ export const runProgram = (args: readonly string[]) =>
258
+ withRedactedOutput(
259
+ withAudit(
260
+ "logs",
261
+ Command.runWith(mainCommand, { version: VERSION, renderErrors: false })(args),
262
+ ),
263
+ ).pipe(Effect.provide(MainLayer), Effect.tapCause(renderCauseToStderr));
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env bun
2
2
  import { checkEffectVersion } from "#shared/effect-version";
3
3
 
4
- if (await checkEffectVersion()) await import("./main");
4
+ if (await checkEffectVersion()) (await import("./main")).runMain();
5
5
  else process.exitCode = 1;
@@ -16,7 +16,7 @@ import { traceCommand } from "./trace";
16
16
 
17
17
  const commandsCommand = makeSchemaCommand(() => mainCommand);
18
18
 
19
- const mainCommand = Command.make("observability-tool", {}).pipe(
19
+ export const mainCommand = Command.make("observability-tool", {}).pipe(
20
20
  Command.withDescription(
21
21
  "LGTM observability queries — Tempo traces, Loki logs, Prometheus metrics",
22
22
  ),
@@ -37,4 +37,12 @@ const program = withRedactedOutput(withAudit("observability", cli)).pipe(
37
37
  Effect.tapCause(renderCauseToStderr),
38
38
  );
39
39
 
40
- BunRuntime.runMain(program, { disableErrorReporting: true });
40
+ export const runMain = () => BunRuntime.runMain(program, { disableErrorReporting: true });
41
+
42
+ export const runProgram = (args: readonly string[]) =>
43
+ withRedactedOutput(
44
+ withAudit(
45
+ "observability",
46
+ Command.runWith(mainCommand, { version: VERSION, renderErrors: false })(args),
47
+ ),
48
+ ).pipe(Effect.provide(MainLayer), Effect.tapCause(renderCauseToStderr));
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env bun
2
2
  import { checkEffectVersion } from "#shared/effect-version";
3
3
 
4
- if (await checkEffectVersion()) await import("./main");
4
+ if (await checkEffectVersion()) (await import("./main")).runMain();
5
5
  else process.exitCode = 1;