@blogic-cz/agent-tools 1.7.4 → 1.8.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 (128) hide show
  1. package/README.md +2 -1
  2. package/dist/db-tool/service.d.ts.map +1 -1
  3. package/dist/gh-tool/branch.d.ts +2 -3
  4. package/dist/gh-tool/branch.d.ts.map +1 -1
  5. package/dist/gh-tool/environment.d.ts +1 -1
  6. package/dist/gh-tool/environment.d.ts.map +1 -1
  7. package/dist/gh-tool/gist.d.ts +8 -28
  8. package/dist/gh-tool/gist.d.ts.map +1 -1
  9. package/dist/gh-tool/issue/commands.d.ts +9 -9
  10. package/dist/gh-tool/issue/commands.d.ts.map +1 -1
  11. package/dist/gh-tool/issue/core.d.ts +103 -9
  12. package/dist/gh-tool/issue/core.d.ts.map +1 -1
  13. package/dist/gh-tool/issue/triage.d.ts +6 -6
  14. package/dist/gh-tool/issue/triage.d.ts.map +1 -1
  15. package/dist/gh-tool/pr/commands.d.ts +53 -46
  16. package/dist/gh-tool/pr/commands.d.ts.map +1 -1
  17. package/dist/gh-tool/pr/core.d.ts +179 -74
  18. package/dist/gh-tool/pr/core.d.ts.map +1 -1
  19. package/dist/gh-tool/pr/review.d.ts +16 -16
  20. package/dist/gh-tool/pr/review.d.ts.map +1 -1
  21. package/dist/gh-tool/pr/snapshot.d.ts +9 -0
  22. package/dist/gh-tool/pr/snapshot.d.ts.map +1 -0
  23. package/dist/gh-tool/pr/stack-read.d.ts +1 -6
  24. package/dist/gh-tool/pr/stack-read.d.ts.map +1 -1
  25. package/dist/gh-tool/pr/stack.d.ts +3 -3
  26. package/dist/gh-tool/pr/stack.d.ts.map +1 -1
  27. package/dist/gh-tool/{api.d.ts → read/api.d.ts} +9 -3
  28. package/dist/gh-tool/read/api.d.ts.map +1 -0
  29. package/dist/gh-tool/read/cache.d.ts +44 -0
  30. package/dist/gh-tool/read/cache.d.ts.map +1 -0
  31. package/dist/gh-tool/read/catalog.d.ts +170 -0
  32. package/dist/gh-tool/read/catalog.d.ts.map +1 -0
  33. package/dist/gh-tool/read/classes.d.ts +94 -0
  34. package/dist/gh-tool/read/classes.d.ts.map +1 -0
  35. package/dist/gh-tool/read/commands.d.ts +3 -0
  36. package/dist/gh-tool/read/commands.d.ts.map +1 -0
  37. package/dist/gh-tool/read/gated.d.ts +14 -0
  38. package/dist/gh-tool/read/gated.d.ts.map +1 -0
  39. package/dist/gh-tool/read/index.d.ts +5 -0
  40. package/dist/gh-tool/read/index.d.ts.map +1 -0
  41. package/dist/gh-tool/read/issues.d.ts +68 -0
  42. package/dist/gh-tool/read/issues.d.ts.map +1 -0
  43. package/dist/gh-tool/read/keys.d.ts +61 -0
  44. package/dist/gh-tool/read/keys.d.ts.map +1 -0
  45. package/dist/gh-tool/read/porcelain.d.ts +68 -0
  46. package/dist/gh-tool/read/porcelain.d.ts.map +1 -0
  47. package/dist/gh-tool/read/pulls.d.ts +137 -0
  48. package/dist/gh-tool/read/pulls.d.ts.map +1 -0
  49. package/dist/gh-tool/read/reads.d.ts +209 -0
  50. package/dist/gh-tool/read/reads.d.ts.map +1 -0
  51. package/dist/gh-tool/read/rest.d.ts +49 -0
  52. package/dist/gh-tool/read/rest.d.ts.map +1 -0
  53. package/dist/gh-tool/read/review-guards.d.ts +20 -0
  54. package/dist/gh-tool/read/review-guards.d.ts.map +1 -0
  55. package/dist/gh-tool/read/review-model.d.ts +190 -0
  56. package/dist/gh-tool/read/review-model.d.ts.map +1 -0
  57. package/dist/gh-tool/read/reviews.d.ts +9 -0
  58. package/dist/gh-tool/read/reviews.d.ts.map +1 -0
  59. package/dist/gh-tool/read/stacks.d.ts +7 -0
  60. package/dist/gh-tool/read/stacks.d.ts.map +1 -0
  61. package/dist/gh-tool/read/store.d.ts +70 -0
  62. package/dist/gh-tool/read/store.d.ts.map +1 -0
  63. package/dist/gh-tool/{service.d.ts → read/transport.d.ts} +5 -3
  64. package/dist/gh-tool/read/transport.d.ts.map +1 -0
  65. package/dist/gh-tool/read/waits.d.ts +22 -0
  66. package/dist/gh-tool/read/waits.d.ts.map +1 -0
  67. package/dist/gh-tool/read/writes.d.ts +16 -0
  68. package/dist/gh-tool/read/writes.d.ts.map +1 -0
  69. package/dist/gh-tool/release.d.ts +10 -26
  70. package/dist/gh-tool/release.d.ts.map +1 -1
  71. package/dist/gh-tool/repo.d.ts +4 -4
  72. package/dist/gh-tool/repo.d.ts.map +1 -1
  73. package/dist/gh-tool/types.d.ts +1 -1
  74. package/dist/gh-tool/types.d.ts.map +1 -1
  75. package/dist/gh-tool/workflow.d.ts +43 -21
  76. package/dist/gh-tool/workflow.d.ts.map +1 -1
  77. package/dist/observability-tool/shared.d.ts.map +1 -1
  78. package/dist/shared/content-security.d.ts +2 -0
  79. package/dist/shared/content-security.d.ts.map +1 -1
  80. package/package.json +6 -6
  81. package/src/api-tool/proxy-entry.ts +3 -1
  82. package/src/db-tool/service.ts +4 -1
  83. package/src/gh-tool/branch.ts +5 -8
  84. package/src/gh-tool/environment.ts +2 -1
  85. package/src/gh-tool/gist.ts +8 -35
  86. package/src/gh-tool/issue/commands.ts +1 -1
  87. package/src/gh-tool/issue/core.ts +55 -122
  88. package/src/gh-tool/issue/triage.ts +4 -26
  89. package/src/gh-tool/main.ts +5 -2
  90. package/src/gh-tool/pr/commands.ts +19 -23
  91. package/src/gh-tool/pr/core.ts +542 -582
  92. package/src/gh-tool/pr/review.ts +106 -454
  93. package/src/gh-tool/pr/snapshot.ts +23 -0
  94. package/src/gh-tool/pr/stack-read.ts +1 -73
  95. package/src/gh-tool/pr/stack.ts +37 -36
  96. package/src/gh-tool/{api.ts → read/api.ts} +40 -7
  97. package/src/gh-tool/read/cache.ts +192 -0
  98. package/src/gh-tool/read/catalog.ts +310 -0
  99. package/src/gh-tool/read/classes.ts +30 -0
  100. package/src/gh-tool/read/commands.ts +25 -0
  101. package/src/gh-tool/read/gated.ts +56 -0
  102. package/src/gh-tool/read/index.ts +4 -0
  103. package/src/gh-tool/read/issues.ts +107 -0
  104. package/src/gh-tool/read/keys.ts +102 -0
  105. package/src/gh-tool/read/porcelain.ts +58 -0
  106. package/src/gh-tool/read/pulls.ts +266 -0
  107. package/src/gh-tool/read/reads.ts +306 -0
  108. package/src/gh-tool/read/rest.ts +111 -0
  109. package/src/gh-tool/read/review-guards.ts +61 -0
  110. package/src/gh-tool/read/review-model.ts +286 -0
  111. package/src/gh-tool/read/reviews.ts +114 -0
  112. package/src/gh-tool/read/stacks.ts +73 -0
  113. package/src/gh-tool/read/store.ts +504 -0
  114. package/src/gh-tool/{service.ts → read/transport.ts} +26 -6
  115. package/src/gh-tool/read/waits.ts +52 -0
  116. package/src/gh-tool/read/writes.ts +37 -0
  117. package/src/gh-tool/release.ts +15 -168
  118. package/src/gh-tool/repo.ts +5 -78
  119. package/src/gh-tool/types.ts +1 -1
  120. package/src/gh-tool/workflow.ts +123 -214
  121. package/src/observability-tool/shared.ts +2 -1
  122. package/src/shared/content-security.ts +10 -1
  123. package/src/shared/credential-patterns.ts +1 -1
  124. package/dist/gh-tool/api.d.ts.map +0 -1
  125. package/dist/gh-tool/pr/check-status-cache.d.ts +0 -6
  126. package/dist/gh-tool/pr/check-status-cache.d.ts.map +0 -1
  127. package/dist/gh-tool/service.d.ts.map +0 -1
  128. package/src/gh-tool/pr/check-status-cache.ts +0 -142
@@ -1,50 +1,20 @@
1
1
  import { Command, Flag, Param } from "effect/cli";
2
- import { Duration, Effect, Option } from "effect";
2
+ import { Effect, Option } from "effect";
3
3
 
4
4
  import { formatOption, logFormatted, logText } from "#shared";
5
5
  import { CI_CHECK_WATCH_TIMEOUT_MS } from "#gh/config";
6
6
  import { GitHubCommandError, GitHubNotFoundError } from "./errors";
7
- import { GitHubService } from "./service";
7
+ import { GitHubService } from "./read/transport";
8
+ import { reads, resolveRepo, waits, writes } from "./read";
9
+ import { rawJobLog } from "./read/reads";
10
+ import type { JobRef } from "./read/reads";
11
+ import { repoSlug } from "./read/keys";
8
12
  import type { CheckRunAnnotation, JobAnnotations, LogDiagnosis } from "./types";
9
13
 
10
14
  // ---------------------------------------------------------------------------
11
15
  // Types
12
16
  // ---------------------------------------------------------------------------
13
17
 
14
- type WorkflowRun = {
15
- databaseId: number;
16
- displayTitle: string;
17
- status: string;
18
- conclusion: string | null;
19
- headBranch: string;
20
- createdAt: string;
21
- event: string;
22
- url: string;
23
- workflowName: string;
24
- };
25
-
26
- type WorkflowJob = {
27
- databaseId: number;
28
- name: string;
29
- status: string;
30
- conclusion: string | null;
31
- startedAt: string;
32
- completedAt: string | null;
33
- url: string;
34
- steps: Array<{
35
- name: string;
36
- status: string;
37
- conclusion: string | null;
38
- number: number;
39
- startedAt: string | null;
40
- completedAt: string | null;
41
- }>;
42
- };
43
-
44
- type WorkflowRunDetail = WorkflowRun & {
45
- jobs: WorkflowJob[];
46
- };
47
-
48
18
  export type LogEntry = {
49
19
  step: string;
50
20
  message: string;
@@ -62,6 +32,7 @@ const resolveRepoArg = Effect.fn("workflow.resolveRepoArg")(function* (
62
32
  if (target === null) {
63
33
  return null;
64
34
  }
35
+ if (/^[^/\s]+\/[^/\s]+$/.test(target)) return target;
65
36
 
66
37
  const gh = yield* GitHubService;
67
38
  const info = yield* gh.withRepoTarget(target, gh.getRepoInfo());
@@ -78,66 +49,26 @@ const listRuns = Effect.fn("workflow.listRuns")(function* (opts: {
78
49
  limit: number;
79
50
  repo: string | null;
80
51
  }) {
81
- const gh = yield* GitHubService;
82
-
83
- const args = [
84
- "run",
85
- "list",
86
- "--json",
87
- "databaseId,displayTitle,status,conclusion,headBranch,createdAt,event,url,workflowName",
88
- "--limit",
89
- String(opts.limit),
90
- ];
91
-
92
- if (opts.repo !== null) {
93
- args.push("--repo", opts.repo);
94
- }
95
-
96
- if (opts.workflow !== null) {
97
- args.push("--workflow", opts.workflow);
98
- }
99
-
100
- if (opts.branch !== null) {
101
- args.push("--branch", opts.branch);
102
- }
103
-
104
- if (opts.status !== null) {
105
- args.push("--status", opts.status);
106
- }
107
-
108
- return yield* gh.runGhJson<WorkflowRun[]>(args);
52
+ const repo = yield* resolveRepo(opts.repo);
53
+ const runs = yield* reads.runList(repo, {
54
+ workflow: opts.workflow,
55
+ branch: opts.branch,
56
+ status: opts.status,
57
+ event: null,
58
+ limit: opts.limit,
59
+ });
60
+ return runs.map(({ headSha: _headSha, ...run }) => run);
109
61
  });
110
62
 
111
63
  const viewRun = Effect.fn("workflow.viewRun")(function* (runId: number, repo: string | null) {
112
- const gh = yield* GitHubService;
113
-
114
- const args = [
115
- "run",
116
- "view",
117
- String(runId),
118
- "--json",
119
- "databaseId,displayTitle,status,conclusion,headBranch,createdAt,event,url,workflowName,jobs",
120
- ];
121
- if (repo !== null) {
122
- args.push("--repo", repo);
123
- }
124
-
125
- return yield* gh.runGhJson<WorkflowRunDetail>(args);
64
+ const target = yield* resolveRepo(repo);
65
+ const detail = yield* reads.runView(target, runId);
66
+ const { attempt: _attempt, headSha: _headSha, ...snapshot } = detail;
67
+ return snapshot;
126
68
  });
127
69
 
128
70
  const listJobs = Effect.fn("workflow.listJobs")(function* (runId: number, repo: string | null) {
129
- const gh = yield* GitHubService;
130
-
131
- const args = ["run", "view", String(runId), "--json", "jobs"];
132
- if (repo !== null) {
133
- args.push("--repo", repo);
134
- }
135
-
136
- const run = yield* gh.runGhJson<{
137
- jobs: WorkflowJob[];
138
- }>(args);
139
-
140
- return run.jobs;
71
+ return yield* reads.runJobs(yield* resolveRepo(repo), runId);
141
72
  });
142
73
 
143
74
  const fetchLogs = Effect.fn("workflow.fetchLogs")(function* (
@@ -146,36 +77,19 @@ const fetchLogs = Effect.fn("workflow.fetchLogs")(function* (
146
77
  jobId: number | null = null,
147
78
  repo: string | null = null,
148
79
  ) {
149
- const gh = yield* GitHubService;
150
- const args = ["run", "view", String(runId)];
151
-
152
- if (repo !== null) {
153
- args.push("--repo", repo);
154
- }
155
-
156
- if (jobId !== null) {
157
- args.push(failedOnly ? "--log-failed" : "--log", "--job", String(jobId));
158
- } else if (failedOnly) {
159
- args.push("--log-failed");
160
- } else {
161
- args.push("--log");
162
- }
163
-
164
- const result = yield* gh.runGh(args);
80
+ const log = yield* reads.runLog(yield* resolveRepo(repo), runId, failedOnly, jobId);
165
81
  return {
166
82
  runId,
167
83
  failedOnly,
168
- log: result.stdout,
84
+ log,
169
85
  };
170
86
  });
171
87
 
172
- const rerunWorkflow = Effect.fn("workflow.rerunWorkflow")(function* (
88
+ export const rerunWorkflow = Effect.fn("workflow.rerunWorkflow")(function* (
173
89
  runId: number,
174
90
  failedOnly: boolean,
175
91
  repo: string | null,
176
92
  ) {
177
- const gh = yield* GitHubService;
178
-
179
93
  const args = ["run", "rerun", String(runId)];
180
94
  if (failedOnly) {
181
95
  args.push("--failed");
@@ -184,7 +98,8 @@ const rerunWorkflow = Effect.fn("workflow.rerunWorkflow")(function* (
184
98
  args.push("--repo", repo);
185
99
  }
186
100
 
187
- yield* gh.runGh(args);
101
+ const target = yield* resolveRepo(repo);
102
+ yield* writes.run(args, [`repo:${repoSlug(target)}`]);
188
103
 
189
104
  return {
190
105
  rerun: true as const,
@@ -196,15 +111,17 @@ const rerunWorkflow = Effect.fn("workflow.rerunWorkflow")(function* (
196
111
  };
197
112
  });
198
113
 
199
- const cancelRun = Effect.fn("workflow.cancelRun")(function* (runId: number, repo: string | null) {
200
- const gh = yield* GitHubService;
201
-
114
+ export const cancelRun = Effect.fn("workflow.cancelRun")(function* (
115
+ runId: number,
116
+ repo: string | null,
117
+ ) {
202
118
  const args = ["run", "cancel", String(runId)];
203
119
  if (repo !== null) {
204
120
  args.push("--repo", repo);
205
121
  }
206
122
 
207
- yield* gh.runGh(args);
123
+ const target = yield* resolveRepo(repo);
124
+ yield* writes.run(args, [`repo:${repoSlug(target)}`]);
208
125
 
209
126
  return {
210
127
  cancelled: true as const,
@@ -219,7 +136,6 @@ export const dispatchWorkflow = Effect.fn("workflow.dispatchWorkflow")(function*
219
136
  fields: ReadonlyArray<string>;
220
137
  repo: string | null;
221
138
  }) {
222
- const gh = yield* GitHubService;
223
139
  const args = ["workflow", "run", opts.workflow, "--ref", opts.ref];
224
140
 
225
141
  if (opts.repo !== null) {
@@ -230,7 +146,8 @@ export const dispatchWorkflow = Effect.fn("workflow.dispatchWorkflow")(function*
230
146
  args.push("-f", field);
231
147
  }
232
148
 
233
- yield* gh.runGh(args);
149
+ const target = yield* resolveRepo(opts.repo);
150
+ yield* writes.run(args, [`repo:${repoSlug(target)}`]);
234
151
 
235
152
  return {
236
153
  dispatched: true as const,
@@ -249,36 +166,38 @@ export type DispatchedRun = {
249
166
  url: string;
250
167
  };
251
168
 
252
- const DISPATCH_DISCOVERY_ATTEMPTS = 8;
253
- const DISPATCH_DISCOVERY_INTERVAL_MS = 2000;
169
+ const DISPATCH_DISCOVERY_BUDGET_MS = 16_000;
170
+ const DISPATCH_DISCOVERY_INTERVAL_MS = 5000;
254
171
 
255
172
  export const listDispatchedRuns = Effect.fn("workflow.listDispatchedRuns")(function* (opts: {
256
173
  workflow: string;
257
174
  ref: string;
258
175
  repo: string | null;
259
176
  }) {
260
- const gh = yield* GitHubService;
261
- const args = [
262
- "run",
263
- "list",
264
- "--json",
265
- "databaseId,headSha,status,conclusion,url",
266
- "--limit",
267
- "20",
268
- "--workflow",
269
- opts.workflow,
270
- "--branch",
271
- opts.ref,
272
- "--event",
273
- "workflow_dispatch",
274
- ];
275
-
276
- if (opts.repo !== null) {
277
- args.push("--repo", opts.repo);
278
- }
279
-
280
- return yield* gh
281
- .runGhJson<DispatchedRun[]>(args)
177
+ const repo = yield* resolveRepo(opts.repo);
178
+ return yield* reads
179
+ .runList(
180
+ repo,
181
+ {
182
+ workflow: opts.workflow,
183
+ branch: opts.ref,
184
+ status: null,
185
+ event: "workflow_dispatch",
186
+ limit: 20,
187
+ },
188
+ "origin",
189
+ )
190
+ .pipe(
191
+ Effect.map((runs) =>
192
+ runs.map((run) => ({
193
+ conclusion: run.conclusion,
194
+ databaseId: run.databaseId,
195
+ headSha: run.headSha,
196
+ status: run.status,
197
+ url: run.url,
198
+ })),
199
+ ),
200
+ )
282
201
  .pipe(Effect.catchTag("GitHubCommandError", () => Effect.succeed<DispatchedRun[]>([])));
283
202
  });
284
203
 
@@ -291,13 +210,13 @@ export const discoverDispatchedRun = Effect.fn("workflow.discoverDispatchedRun")
291
210
  repo: string | null;
292
211
  knownRunIds: ReadonlySet<number>;
293
212
  }) {
294
- for (let attempt = 0; attempt < DISPATCH_DISCOVERY_ATTEMPTS; attempt += 1) {
295
- const runs = yield* listDispatchedRuns(opts);
296
- const created = runs.find((run) => !opts.knownRunIds.has(run.databaseId));
297
- if (created !== undefined) return created;
298
- yield* Effect.sleep(Duration.millis(DISPATCH_DISCOVERY_INTERVAL_MS));
299
- }
300
- return null;
213
+ const outcome = yield* waits.until(
214
+ listDispatchedRuns(opts),
215
+ (runs) => runs.some((run) => !opts.knownRunIds.has(run.databaseId)),
216
+ { intervalMs: DISPATCH_DISCOVERY_INTERVAL_MS, timeoutMs: DISPATCH_DISCOVERY_BUDGET_MS },
217
+ );
218
+ const runs = outcome.kind === "done" ? outcome.value : outcome.last;
219
+ return runs.find((run) => !opts.knownRunIds.has(run.databaseId)) ?? null;
301
220
  });
302
221
 
303
222
  // `gh run watch` has no native timeout (observed hanging 36 min). Block for the caller's --timeout,
@@ -338,37 +257,29 @@ const watchRun = Effect.fn("workflow.watchRun")(function* (
338
257
  timeoutSeconds: number,
339
258
  frames: boolean,
340
259
  ) {
341
- const gh = yield* GitHubService;
342
-
343
- const watchArgs = ["run", "watch", String(runId), "--exit-status"];
344
- if (repo !== null) {
345
- watchArgs.push("--repo", repo);
346
- }
347
-
348
- const result = yield* gh.runGh(watchArgs).pipe(
349
- Effect.catchTag("GitHubCommandError", (error) => {
350
- // exit-status returns non-zero if run failed, but we still want the output
351
- if (error.exitCode > 0 && error.stderr === "") {
352
- return Effect.succeed({
353
- stdout: "",
354
- stderr: "",
355
- exitCode: error.exitCode,
356
- });
357
- }
358
- return Effect.fail(error);
359
- }),
360
- Effect.timeoutOrElse({
361
- duration: timeoutSeconds * 1000,
362
- orElse: () => Effect.succeed(null),
363
- }),
260
+ const target = yield* resolveRepo(repo);
261
+ const progress: string[] = [];
262
+ let previous = "";
263
+ const outcome = yield* waits.until(
264
+ reads.runView(target, runId),
265
+ (run) => run.status === "completed",
266
+ {
267
+ intervalMs: 5000,
268
+ timeoutMs: timeoutSeconds * 1000,
269
+ onValue: (run) =>
270
+ Effect.sync(() => {
271
+ const frame = `${run.status} ${run.conclusion ?? ""}\n${run.jobs.map((job) => `${job.name}: ${job.status} ${job.conclusion ?? ""}`).join("\n")}`;
272
+ if (frames && frame !== previous) progress.push(frame);
273
+ previous = frame;
274
+ }),
275
+ },
364
276
  );
365
-
366
- const finalState = yield* viewRun(runId, repo);
277
+ const finalState = outcome.kind === "done" ? outcome.value : outcome.last;
367
278
 
368
279
  return buildWatchResult(
369
280
  runId,
370
281
  finalState,
371
- result === null ? null : result.stdout,
282
+ outcome.kind === "timedOut" ? null : progress.join("\n"),
372
283
  frames,
373
284
  timeoutSeconds,
374
285
  );
@@ -405,18 +316,14 @@ const fetchAnnotations = Effect.fn("workflow.fetchAnnotations")(function* (opts:
405
316
 
406
317
  let targetJobs = jobs;
407
318
  if (opts.job !== null) {
408
- const jobId = yield* resolveJobId(opts.runId, opts.job, opts.repo);
409
- targetJobs = jobs.filter((j) => j.databaseId === jobId);
319
+ const selected = yield* resolveJob(opts.runId, opts.job, opts.repo);
320
+ targetJobs = jobs.filter((j) => j.databaseId === selected.databaseId);
410
321
  }
411
322
 
412
323
  const results: JobAnnotations[] = [];
413
324
  for (const job of targetJobs) {
414
- const annotations = yield* gh
415
- .runGhJson<CheckRunAnnotation[]>([
416
- "api",
417
- `repos/${owner}/${repoName}/check-runs/${job.databaseId}/annotations`,
418
- "--paginate",
419
- ])
325
+ const annotations = yield* reads
326
+ .annotations({ owner, name: repoName }, { id: job.databaseId, status: job.status })
420
327
  .pipe(
421
328
  Effect.catchTag("GitHubCommandError", () => Effect.succeed([] as CheckRunAnnotation[])),
422
329
  );
@@ -425,7 +332,7 @@ const fetchAnnotations = Effect.fn("workflow.fetchAnnotations")(function* (opts:
425
332
  results.push({
426
333
  jobId: job.databaseId,
427
334
  jobName: job.name,
428
- annotations,
335
+ annotations: [...annotations],
429
336
  });
430
337
  }
431
338
  }
@@ -457,8 +364,8 @@ export function cleanLogLine(line: string): string {
457
364
  type StepTiming = {
458
365
  name: string;
459
366
  conclusion: string | null;
460
- startedAt: string | null;
461
- completedAt: string | null;
367
+ startedAt?: string | null;
368
+ completedAt?: string | null;
462
369
  };
463
370
 
464
371
  type StepWindow = { name: string; failed: boolean; start: number; end: number };
@@ -466,18 +373,7 @@ type StepWindow = { name: string; failed: boolean; start: number; end: number };
466
373
  const LINE_SECOND_RE = /^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})/;
467
374
 
468
375
  // Older gh versions reject --allow-escape-sequences; they print escapes without it.
469
- export const readJobLogApi = Effect.fn("workflow.readJobLogApi")(function* (path: string) {
470
- const gh = yield* GitHubService;
471
- return yield* gh
472
- .runGh(["api", path, "--allow-escape-sequences"])
473
- .pipe(
474
- Effect.catchTag("GitHubCommandError", (error) =>
475
- `${error.stderr}${error.message}`.includes("unknown flag: --allow-escape-sequences")
476
- ? gh.runGh(["api", path])
477
- : Effect.fail(error),
478
- ),
479
- );
480
- });
376
+ export const readJobLogApi = rawJobLog;
481
377
 
482
378
  // Step times have one-second precision and several steps can share a second, so a
483
379
  // boundary line goes to the failed step: a few extra lines beat a lost final error.
@@ -621,7 +517,7 @@ export const diagnoseLogEntries = (entries: readonly LogEntry[]): LogDiagnosis =
621
517
  };
622
518
  };
623
519
 
624
- const resolveJobId = Effect.fn("workflow.resolveJobId")(function* (
520
+ const resolveJob = Effect.fn("workflow.resolveJob")(function* (
625
521
  runId: number,
626
522
  jobName: string,
627
523
  repo: string | null,
@@ -630,13 +526,13 @@ const resolveJobId = Effect.fn("workflow.resolveJobId")(function* (
630
526
 
631
527
  // Exact match first
632
528
  const exact = jobs.find((j) => j.name === jobName);
633
- if (exact) return exact.databaseId;
529
+ if (exact) return exact;
634
530
 
635
531
  // Case-insensitive partial match
636
532
  const lower = jobName.toLowerCase();
637
533
  const partial = jobs.filter((j) => j.name.toLowerCase().includes(lower));
638
534
 
639
- if (partial.length === 1) return partial[0].databaseId;
535
+ if (partial.length === 1) return partial[0];
640
536
 
641
537
  if (partial.length > 1) {
642
538
  return yield* new GitHubCommandError({
@@ -658,17 +554,18 @@ const resolveJobId = Effect.fn("workflow.resolveJobId")(function* (
658
554
  });
659
555
  });
660
556
 
661
- export const fetchJobLogs = Effect.fn("workflow.fetchJobLogs")(function* (opts: {
662
- runId: number;
663
- job: string;
664
- jobId?: number | null;
665
- failedStepNames?: readonly string[] | null;
666
- steps?: ReadonlyArray<StepTiming>;
667
- failedStepsOnly: boolean;
668
- diagnose?: boolean;
669
- format: string;
670
- repo: string | null;
671
- }) {
557
+ export const fetchJobLogs = Effect.fn("workflow.fetchJobLogs")(function* (
558
+ opts: {
559
+ runId: number;
560
+ job: string;
561
+ failedStepNames?: readonly string[] | null;
562
+ steps?: ReadonlyArray<StepTiming>;
563
+ failedStepsOnly: boolean;
564
+ diagnose?: boolean;
565
+ format: string;
566
+ repo: string | null;
567
+ } & ({ jobRef: JobRef; jobId?: never } | { jobId?: number | null; jobRef?: never }),
568
+ ) {
672
569
  const gh = yield* GitHubService;
673
570
 
674
571
  let owner: string;
@@ -691,11 +588,23 @@ export const fetchJobLogs = Effect.fn("workflow.fetchJobLogs")(function* (opts:
691
588
  repoName = info.name;
692
589
  }
693
590
 
694
- const jobId = opts.jobId ?? (yield* resolveJobId(opts.runId, opts.job, opts.repo));
591
+ const job =
592
+ opts.jobRef === undefined && (opts.jobId === null || opts.jobId === undefined)
593
+ ? yield* resolveJob(opts.runId, opts.job, opts.repo)
594
+ : null;
595
+ const jobId = opts.jobRef?.id ?? opts.jobId ?? job?.databaseId;
596
+ if (jobId === undefined)
597
+ return yield* new GitHubCommandError({
598
+ message: "Job identity unavailable",
599
+ command: "workflow job-logs",
600
+ exitCode: 1,
601
+ stderr: "",
602
+ });
695
603
 
696
604
  // `gh run view --log` can omit lines, so the job log endpoint is the primary source.
697
- const apiLogs = readJobLogApi(`repos/${owner}/${repoName}/actions/jobs/${jobId}/logs`).pipe(
698
- Effect.map((r) => r.stdout),
605
+ const apiLogs = reads.jobLog(
606
+ { owner, name: repoName },
607
+ opts.jobRef ?? { id: jobId, status: job?.status ?? "unknown" },
699
608
  );
700
609
  const cliLogs = fetchLogs(opts.runId, opts.failedStepsOnly, jobId, opts.repo).pipe(
701
610
  Effect.map((r) => r.log),
@@ -5,7 +5,7 @@ import { ConfigService, getToolConfig } from "#config";
5
5
  import type { ObservabilityConfig } from "#config";
6
6
 
7
7
  import { ObservabilityToolError } from "./errors";
8
- import { unsafeOutboundTextReason } from "#shared/content-security";
8
+ import { registerResolvedSecret, unsafeOutboundTextReason } from "#shared/content-security";
9
9
  import type {
10
10
  DsQueryOpts,
11
11
  DsQueryResponse,
@@ -58,6 +58,7 @@ function resolveToken(tokenEnvVar?: string): string | undefined {
58
58
  throw new Error(`${tokenEnvVar} environment variable is not set`);
59
59
  }
60
60
 
61
+ registerResolvedSecret(token);
61
62
  return token;
62
63
  }
63
64
 
@@ -8,11 +8,20 @@ const isSensitiveEnvironmentName = (name: string) =>
8
8
  name !== "CODEX_SESSION_ID" &&
9
9
  SENSITIVE_ENV_NAME.test(name);
10
10
 
11
+ const MIN_SECRET_LENGTH = 8;
12
+ const resolvedSecrets = new Set<string>();
13
+
14
+ /** Redact a credential a tool resolved outside a sensitively named environment variable. */
15
+ export const registerResolvedSecret = (value: string) => {
16
+ if (value.length >= MIN_SECRET_LENGTH) resolvedSecrets.add(value);
17
+ };
18
+
11
19
  const sensitiveEnvironmentValues = (environment: Record<string, string | undefined>) =>
12
20
  Object.entries(environment)
13
21
  .flatMap(([name, value]) =>
14
- isSensitiveEnvironmentName(name) && value && value.length >= 8 ? [value] : [],
22
+ isSensitiveEnvironmentName(name) && value && value.length >= MIN_SECRET_LENGTH ? [value] : [],
15
23
  )
24
+ .concat([...resolvedSecrets])
16
25
  .toSorted((left, right) => right.length - left.length);
17
26
 
18
27
  export function unsafeOutboundTextReason(
@@ -1,7 +1,7 @@
1
1
  /** Patterns used by the credential guard and shared output redaction. */
2
2
  export const SECRET_PATTERNS = [
3
3
  { name: "AWS Access Key", pattern: /(?:AKIA|ABIA|ACCA|ASIA)[A-Z0-9]{16}/ },
4
- { name: "GitHub Token", pattern: /gh[ps]_[A-Za-z0-9]{36}/ },
4
+ { name: "GitHub Token", pattern: /gh[opsur]_[A-Za-z0-9]{36}/ },
5
5
  { name: "GitHub PAT", pattern: /github_pat_[A-Za-z0-9]{22}_[A-Za-z0-9]{59}/ },
6
6
  { name: "OpenAI Key", pattern: /sk-[A-Za-z0-9]{48}/ },
7
7
  {
@@ -1 +0,0 @@
1
- {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../src/gh-tool/api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,MAAM,EAAE,MAAM,QAAQ,CAAC;AAIvC,OAAO,EAAE,eAAe,EAA2C,MAAM,UAAU,CAAC;AACpF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAW/C,MAAM,MAAM,iBAAiB,CAAC,CAAC,IAAI;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,CAAC,CAAC;CACT,CAAC;AAYF,eAAO,MAAM,kBAAkB,qDA6B7B,CAAC;AAEH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,CAAC;IACrD,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC7B,CAAC;AAuHF,eAAO,MAAM,SAAS,GAAI,CAAC,QACnB,gBAAgB,KACrB,MAAM,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC,CAAC,EAAE,cAAc,CAyCjD,CAAC"}
@@ -1,6 +0,0 @@
1
- import { Context } from "effect";
2
- import type { CheckResult } from "#gh/types";
3
- export declare const CheckStatusCacheDirectory: Context.Reference<string | undefined>;
4
- export declare const readCheckStatusCache: (identity: string, repo: string, pr: number, headSha: string, now: number, watchPending?: boolean, directory?: string) => Promise<CheckResult[] | null>;
5
- export declare const writeCheckStatusCache: (identity: string, repo: string, pr: number, headSha: string, checks: CheckResult[], fetchedAt: number, directory?: string) => Promise<void>;
6
- //# sourceMappingURL=check-status-cache.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"check-status-cache.d.ts","sourceRoot":"","sources":["../../../src/gh-tool/pr/check-status-cache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAMjC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAM7C,eAAO,MAAM,yBAAyB,uCAGrC,CAAC;AAqBF,eAAO,MAAM,oBAAoB,aACrB,MAAM,QACV,MAAM,MACR,MAAM,WACD,MAAM,OACV,MAAM,iDAGV,OAAO,CAAC,WAAW,EAAE,GAAG,IAAI,CA+C9B,CAAC;AAEF,eAAO,MAAM,qBAAqB,aACtB,MAAM,QACV,MAAM,MACR,MAAM,WACD,MAAM,UACP,WAAW,EAAE,aACV,MAAM,yBAEhB,OAAO,CAAC,IAAI,CAwCd,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/gh-tool/service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AACnE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAU,MAAM,QAAQ,CAAC;AAExD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAChD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAIxC,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAGpF,OAAO,KAAK,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,OAAO,CAAC;AACjE,OAAO,EAAE,aAAa,EAA4C,MAAM,SAAS,CAAC;AA4DlF,MAAM,MAAM,QAAQ,GAAG;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,OAAO,GAAG,kBAAkB,GAAG,eAAe,GAAG,mBAAmB,CAAC;;oBAK7D,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC;wBAChD,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,OAAO,CAAC;yBAC/C,CACnB,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC,KAC9C,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC;yBACf,CAAC,CAAC,EACrB,IAAI,EAAE,gBAAgB,KACnB,MAAM,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC;4BACzB,MAAM,MAAM,CAAC,MAAM,CAAC,gBAAgB,GAAG,SAAS,EAAE,KAAK,CAAC;0BAC1D,MAAM,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC;6BACnC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAC/B,MAAM,EAAE,MAAM,GAAG,IAAI,EACrB,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,KAC3B,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,GAAG,kBAAkB,EAAE,CAAC,CAAC;;AAjBpD,qBAAa,aAAc,SAAQ,kBAmBF;IAC/B,MAAM,CAAC,QAAQ,CAAC,KAAK,6FAuTnB;CACH"}