@blogic-cz/agent-tools 1.7.5 → 1.9.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 (142) hide show
  1. package/README.md +2 -1
  2. package/dist/config/loader.d.ts.map +1 -1
  3. package/dist/config/types.d.ts +1 -0
  4. package/dist/config/types.d.ts.map +1 -1
  5. package/dist/credential-guard/index.d.ts.map +1 -1
  6. package/dist/db-tool/service.d.ts.map +1 -1
  7. package/dist/gh-tool/branch.d.ts +2 -3
  8. package/dist/gh-tool/branch.d.ts.map +1 -1
  9. package/dist/gh-tool/environment.d.ts +1 -1
  10. package/dist/gh-tool/environment.d.ts.map +1 -1
  11. package/dist/gh-tool/gist.d.ts +8 -28
  12. package/dist/gh-tool/gist.d.ts.map +1 -1
  13. package/dist/gh-tool/issue/commands.d.ts +11 -9
  14. package/dist/gh-tool/issue/commands.d.ts.map +1 -1
  15. package/dist/gh-tool/issue/core.d.ts +116 -9
  16. package/dist/gh-tool/issue/core.d.ts.map +1 -1
  17. package/dist/gh-tool/issue/triage.d.ts +6 -6
  18. package/dist/gh-tool/issue/triage.d.ts.map +1 -1
  19. package/dist/gh-tool/label.d.ts +20 -0
  20. package/dist/gh-tool/label.d.ts.map +1 -0
  21. package/dist/gh-tool/pr/commands.d.ts +53 -46
  22. package/dist/gh-tool/pr/commands.d.ts.map +1 -1
  23. package/dist/gh-tool/pr/core.d.ts +156 -86
  24. package/dist/gh-tool/pr/core.d.ts.map +1 -1
  25. package/dist/gh-tool/pr/review.d.ts +16 -16
  26. package/dist/gh-tool/pr/review.d.ts.map +1 -1
  27. package/dist/gh-tool/pr/snapshot.d.ts +9 -0
  28. package/dist/gh-tool/pr/snapshot.d.ts.map +1 -0
  29. package/dist/gh-tool/pr/stack-read.d.ts +1 -6
  30. package/dist/gh-tool/pr/stack-read.d.ts.map +1 -1
  31. package/dist/gh-tool/pr/stack.d.ts +3 -3
  32. package/dist/gh-tool/pr/stack.d.ts.map +1 -1
  33. package/dist/gh-tool/{api.d.ts → read/api.d.ts} +9 -3
  34. package/dist/gh-tool/read/api.d.ts.map +1 -0
  35. package/dist/gh-tool/read/cache.d.ts +44 -0
  36. package/dist/gh-tool/read/cache.d.ts.map +1 -0
  37. package/dist/gh-tool/read/catalog.d.ts +170 -0
  38. package/dist/gh-tool/read/catalog.d.ts.map +1 -0
  39. package/dist/gh-tool/read/classes.d.ts +98 -0
  40. package/dist/gh-tool/read/classes.d.ts.map +1 -0
  41. package/dist/gh-tool/read/commands.d.ts +3 -0
  42. package/dist/gh-tool/read/commands.d.ts.map +1 -0
  43. package/dist/gh-tool/read/commits.d.ts +10 -0
  44. package/dist/gh-tool/read/commits.d.ts.map +1 -0
  45. package/dist/gh-tool/read/gated.d.ts +14 -0
  46. package/dist/gh-tool/read/gated.d.ts.map +1 -0
  47. package/dist/gh-tool/read/index.d.ts +5 -0
  48. package/dist/gh-tool/read/index.d.ts.map +1 -0
  49. package/dist/gh-tool/read/issues.d.ts +80 -0
  50. package/dist/gh-tool/read/issues.d.ts.map +1 -0
  51. package/dist/gh-tool/read/keys.d.ts +64 -0
  52. package/dist/gh-tool/read/keys.d.ts.map +1 -0
  53. package/dist/gh-tool/read/porcelain.d.ts +68 -0
  54. package/dist/gh-tool/read/porcelain.d.ts.map +1 -0
  55. package/dist/gh-tool/read/pulls.d.ts +137 -0
  56. package/dist/gh-tool/read/pulls.d.ts.map +1 -0
  57. package/dist/gh-tool/read/reads.d.ts +220 -0
  58. package/dist/gh-tool/read/reads.d.ts.map +1 -0
  59. package/dist/gh-tool/read/rest.d.ts +49 -0
  60. package/dist/gh-tool/read/rest.d.ts.map +1 -0
  61. package/dist/gh-tool/read/review-guards.d.ts +20 -0
  62. package/dist/gh-tool/read/review-guards.d.ts.map +1 -0
  63. package/dist/gh-tool/read/review-model.d.ts +190 -0
  64. package/dist/gh-tool/read/review-model.d.ts.map +1 -0
  65. package/dist/gh-tool/read/reviews.d.ts +9 -0
  66. package/dist/gh-tool/read/reviews.d.ts.map +1 -0
  67. package/dist/gh-tool/read/stacks.d.ts +7 -0
  68. package/dist/gh-tool/read/stacks.d.ts.map +1 -0
  69. package/dist/gh-tool/read/store.d.ts +70 -0
  70. package/dist/gh-tool/read/store.d.ts.map +1 -0
  71. package/dist/gh-tool/{service.d.ts → read/transport.d.ts} +5 -3
  72. package/dist/gh-tool/read/transport.d.ts.map +1 -0
  73. package/dist/gh-tool/read/waits.d.ts +22 -0
  74. package/dist/gh-tool/read/waits.d.ts.map +1 -0
  75. package/dist/gh-tool/read/writes.d.ts +16 -0
  76. package/dist/gh-tool/read/writes.d.ts.map +1 -0
  77. package/dist/gh-tool/release.d.ts +10 -26
  78. package/dist/gh-tool/release.d.ts.map +1 -1
  79. package/dist/gh-tool/repo.d.ts +9 -4
  80. package/dist/gh-tool/repo.d.ts.map +1 -1
  81. package/dist/gh-tool/types.d.ts +1 -1
  82. package/dist/gh-tool/types.d.ts.map +1 -1
  83. package/dist/gh-tool/workflow.d.ts +43 -21
  84. package/dist/gh-tool/workflow.d.ts.map +1 -1
  85. package/dist/observability-tool/shared.d.ts.map +1 -1
  86. package/dist/shared/content-security.d.ts +2 -0
  87. package/dist/shared/content-security.d.ts.map +1 -1
  88. package/package.json +7 -6
  89. package/schemas/agent-tools.schema.json +5 -0
  90. package/src/api-tool/proxy-entry.ts +3 -1
  91. package/src/config/loader.ts +1 -0
  92. package/src/config/types.ts +1 -0
  93. package/src/credential-guard/index.ts +169 -1
  94. package/src/db-tool/service.ts +4 -1
  95. package/src/gh-tool/branch.ts +5 -8
  96. package/src/gh-tool/environment.ts +2 -1
  97. package/src/gh-tool/gist.ts +8 -35
  98. package/src/gh-tool/issue/commands.ts +12 -2
  99. package/src/gh-tool/issue/core.ts +58 -122
  100. package/src/gh-tool/issue/triage.ts +4 -26
  101. package/src/gh-tool/label.ts +57 -0
  102. package/src/gh-tool/main.ts +24 -4
  103. package/src/gh-tool/pr/commands.ts +17 -15
  104. package/src/gh-tool/pr/core.ts +486 -582
  105. package/src/gh-tool/pr/review.ts +106 -454
  106. package/src/gh-tool/pr/snapshot.ts +23 -0
  107. package/src/gh-tool/pr/stack-read.ts +1 -73
  108. package/src/gh-tool/pr/stack.ts +37 -36
  109. package/src/gh-tool/{api.ts → read/api.ts} +40 -7
  110. package/src/gh-tool/read/cache.ts +192 -0
  111. package/src/gh-tool/read/catalog.ts +310 -0
  112. package/src/gh-tool/read/classes.ts +31 -0
  113. package/src/gh-tool/read/commands.ts +25 -0
  114. package/src/gh-tool/read/commits.ts +28 -0
  115. package/src/gh-tool/read/gated.ts +56 -0
  116. package/src/gh-tool/read/index.ts +4 -0
  117. package/src/gh-tool/read/issues.ts +124 -0
  118. package/src/gh-tool/read/keys.ts +104 -0
  119. package/src/gh-tool/read/porcelain.ts +58 -0
  120. package/src/gh-tool/read/pulls.ts +266 -0
  121. package/src/gh-tool/read/reads.ts +306 -0
  122. package/src/gh-tool/read/rest.ts +111 -0
  123. package/src/gh-tool/read/review-guards.ts +61 -0
  124. package/src/gh-tool/read/review-model.ts +286 -0
  125. package/src/gh-tool/read/reviews.ts +114 -0
  126. package/src/gh-tool/read/stacks.ts +73 -0
  127. package/src/gh-tool/read/store.ts +504 -0
  128. package/src/gh-tool/{service.ts → read/transport.ts} +26 -6
  129. package/src/gh-tool/read/waits.ts +52 -0
  130. package/src/gh-tool/read/writes.ts +37 -0
  131. package/src/gh-tool/release.ts +15 -168
  132. package/src/gh-tool/repo.ts +26 -78
  133. package/src/gh-tool/types.ts +1 -1
  134. package/src/gh-tool/workflow.ts +123 -214
  135. package/src/observability-tool/shared.ts +2 -1
  136. package/src/shared/content-security.ts +10 -1
  137. package/src/shared/credential-patterns.ts +1 -1
  138. package/dist/gh-tool/api.d.ts.map +0 -1
  139. package/dist/gh-tool/pr/check-status-cache.d.ts +0 -6
  140. package/dist/gh-tool/pr/check-status-cache.d.ts.map +0 -1
  141. package/dist/gh-tool/service.d.ts.map +0 -1
  142. package/src/gh-tool/pr/check-status-cache.ts +0 -142
@@ -3,7 +3,11 @@ import { Effect } from "effect";
3
3
  import type { GitHubIssueCommentUrl, IssueComment, IssueCommentId, IsoTimestamp } from "#gh/types";
4
4
 
5
5
  import { GitHubCommandError } from "#gh/errors";
6
- import { GitHubService } from "#gh/service";
6
+ import { GitHubService } from "#gh/read/transport";
7
+ import { reads, resolveRepo } from "#gh/read/reads";
8
+ import { writes } from "#gh/read/writes";
9
+ import { project } from "#gh/read/porcelain";
10
+ import { repoSlug } from "#gh/read/keys";
7
11
 
8
12
  export type IssueInfo = {
9
13
  number: number;
@@ -34,52 +38,6 @@ export type RawIssueComment = {
34
38
  html_url: string;
35
39
  };
36
40
 
37
- const REST_PAGE_SIZE = 100;
38
-
39
- const parseJson = <T>(
40
- stdout: string,
41
- command: string,
42
- parseFailurePrefix: string,
43
- ): Effect.Effect<T, GitHubCommandError> =>
44
- Effect.try({
45
- try: () => JSON.parse(stdout) as T,
46
- catch: (error) =>
47
- new GitHubCommandError({
48
- command,
49
- exitCode: 0,
50
- stderr: `${parseFailurePrefix}: ${error instanceof Error ? error.message : String(error)}`,
51
- message: `${parseFailurePrefix}: ${error instanceof Error ? error.message : String(error)}`,
52
- }),
53
- });
54
-
55
- const fetchAllRestPages = Effect.fn("issue.fetchAllRestPages")(function* <T>(
56
- endpoint: string,
57
- command: string,
58
- parseFailurePrefix: string,
59
- ) {
60
- const service = yield* GitHubService;
61
-
62
- const results: T[] = [];
63
- let page = 1;
64
-
65
- while (true) {
66
- const separator = endpoint.includes("?") ? "&" : "?";
67
- const result = yield* service.runGh([
68
- "api",
69
- `${endpoint}${separator}per_page=${REST_PAGE_SIZE}&page=${page}`,
70
- ]);
71
-
72
- const rawPage = yield* parseJson<T[]>(result.stdout, command, parseFailurePrefix);
73
- results.push(...rawPage);
74
-
75
- if (rawPage.length < REST_PAGE_SIZE) {
76
- return results;
77
- }
78
-
79
- page += 1;
80
- }
81
- });
82
-
83
41
  const mapRawIssueComment = (comment: RawIssueComment): IssueComment => ({
84
42
  id: comment.id as IssueCommentId,
85
43
  commitSha: null,
@@ -94,36 +52,25 @@ export const listIssues = Effect.fn("issue.listIssues")(function* (opts: {
94
52
  state: string;
95
53
  labels: string | null;
96
54
  limit: number;
55
+ includeBody?: boolean;
56
+ fresh?: boolean;
97
57
  }) {
98
- const gh = yield* GitHubService;
99
-
100
- const args = [
101
- "issue",
102
- "list",
103
- "--state",
104
- opts.state,
105
- "--limit",
106
- String(opts.limit),
107
- "--json",
108
- "number,title,state,url,labels,createdAt",
109
- ];
110
-
111
- if (opts.labels !== null) {
112
- args.push("--label", opts.labels);
113
- }
114
-
115
- return yield* gh.runGhJson<IssueListItem[]>(args);
58
+ const { fresh, ...filter } = opts;
59
+ return yield* reads.issueList(yield* resolveRepo(null), filter, fresh ? "origin" : "cached");
116
60
  });
117
61
 
118
62
  export const viewIssue = Effect.fn("issue.viewIssue")(function* (issueNumber: number) {
119
- const gh = yield* GitHubService;
120
-
121
- return yield* gh.runGhJson<IssueInfo>([
122
- "issue",
123
- "view",
124
- String(issueNumber),
125
- "--json",
126
- "number,title,state,url,labels,assignees,author,createdAt,closedAt",
63
+ const value = yield* reads.issueView(yield* resolveRepo(null), issueNumber);
64
+ return project(value, [
65
+ "number",
66
+ "title",
67
+ "state",
68
+ "url",
69
+ "labels",
70
+ "assignees",
71
+ "author",
72
+ "createdAt",
73
+ "closedAt",
127
74
  ]);
128
75
  });
129
76
 
@@ -133,14 +80,7 @@ export const fetchIssueComments = Effect.fn("issue.fetchIssueComments")(function
133
80
  author: string | null,
134
81
  bodyContains: string | null,
135
82
  ) {
136
- const gh = yield* GitHubService;
137
- const repoInfo = yield* gh.getRepoInfo();
138
-
139
- const raw = yield* fetchAllRestPages<RawIssueComment>(
140
- `repos/${repoInfo.owner}/${repoInfo.name}/issues/${issueNumber}/comments`,
141
- "gh-tool issue comments",
142
- "Failed to parse response",
143
- );
83
+ const raw = yield* reads.issueComments(yield* resolveRepo(null), issueNumber);
144
84
 
145
85
  let comments = raw.map(mapRawIssueComment);
146
86
 
@@ -167,46 +107,40 @@ export const closeIssue = Effect.fn("issue.closeIssue")(function* (opts: {
167
107
  comment: string | null;
168
108
  reason: string;
169
109
  }) {
170
- const gh = yield* GitHubService;
171
-
172
110
  const args = ["issue", "close", String(opts.issue), "--reason", opts.reason];
173
111
 
174
112
  if (opts.comment !== null) {
175
113
  args.push("--comment", opts.comment);
176
114
  }
177
115
 
178
- yield* gh.runGh(args);
179
-
180
- return yield* gh.runGhJson<IssueInfo>([
181
- "issue",
182
- "view",
183
- String(opts.issue),
184
- "--json",
185
- "number,title,state,url,labels,assignees,author,createdAt,closedAt",
116
+ const repo = yield* resolveRepo(null);
117
+ yield* writes.run(args, [
118
+ `issue:${repoSlug(repo)}#${opts.issue}`,
119
+ `issues:${repoSlug(repo)}`,
120
+ `issue-comments:${repoSlug(repo)}#${opts.issue}`,
186
121
  ]);
122
+
123
+ return yield* viewIssue(opts.issue);
187
124
  });
188
125
 
189
126
  export const reopenIssue = Effect.fn("issue.reopenIssue")(function* (opts: {
190
127
  issue: number;
191
128
  comment: string | null;
192
129
  }) {
193
- const gh = yield* GitHubService;
194
-
195
130
  const args = ["issue", "reopen", String(opts.issue)];
196
131
 
197
132
  if (opts.comment !== null) {
198
133
  args.push("--comment", opts.comment);
199
134
  }
200
135
 
201
- yield* gh.runGh(args);
202
-
203
- return yield* gh.runGhJson<IssueInfo>([
204
- "issue",
205
- "view",
206
- String(opts.issue),
207
- "--json",
208
- "number,title,state,url,labels,assignees,author,createdAt,closedAt",
136
+ const repo = yield* resolveRepo(null);
137
+ yield* writes.run(args, [
138
+ `issue:${repoSlug(repo)}#${opts.issue}`,
139
+ `issues:${repoSlug(repo)}`,
140
+ `issue-comments:${repoSlug(repo)}#${opts.issue}`,
209
141
  ]);
142
+
143
+ return yield* viewIssue(opts.issue);
210
144
  });
211
145
 
212
146
  export const commentOnIssue = Effect.fn("issue.commentOnIssue")(function* (opts: {
@@ -227,14 +161,20 @@ export const commentOnIssue = Effect.fn("issue.commentOnIssue")(function* (opts:
227
161
  );
228
162
  }
229
163
 
230
- const result = yield* gh.runGh([
231
- "api",
232
- "-X",
233
- "POST",
234
- `repos/${repoInfo.owner}/${repoInfo.name}/issues/${opts.issue}/comments`,
235
- "-f",
236
- `body=${opts.body}`,
237
- ]);
164
+ const result = yield* writes.run(
165
+ [
166
+ "api",
167
+ "-X",
168
+ "POST",
169
+ `repos/${repoInfo.owner}/${repoInfo.name}/issues/${opts.issue}/comments`,
170
+ "-f",
171
+ `body=${opts.body}`,
172
+ ],
173
+ [
174
+ `issue-comments:${repoInfo.owner.toLowerCase()}/${repoInfo.name.toLowerCase()}#${opts.issue}`,
175
+ `issue:${repoInfo.owner.toLowerCase()}/${repoInfo.name.toLowerCase()}#${opts.issue}`,
176
+ ],
177
+ );
238
178
 
239
179
  const rawComment = yield* Effect.try({
240
180
  try: () => JSON.parse(result.stdout) as RawIssueComment,
@@ -262,8 +202,6 @@ export const createIssue = Effect.fn("issue.createIssue")(function* (opts: {
262
202
  assignee: string | null;
263
203
  labels: string | null;
264
204
  }) {
265
- const gh = yield* GitHubService;
266
-
267
205
  const args = ["issue", "create", "--title", opts.title, "--body", opts.body];
268
206
 
269
207
  if (opts.assignee !== null) {
@@ -273,7 +211,8 @@ export const createIssue = Effect.fn("issue.createIssue")(function* (opts: {
273
211
  args.push("--label", opts.labels);
274
212
  }
275
213
 
276
- const result = yield* gh.runGh(args);
214
+ const repo = yield* resolveRepo(null);
215
+ const result = yield* writes.run(args, [`repo:${repoSlug(repo)}`]);
277
216
 
278
217
  const urlMatch = result.stdout.match(/\/issues\/(\d+)/);
279
218
  if (!urlMatch?.[1]) {
@@ -299,8 +238,6 @@ export const editIssue = Effect.fn("issue.editIssue")(function* (opts: {
299
238
  addAssignee: string | null;
300
239
  removeAssignee: string | null;
301
240
  }) {
302
- const gh = yield* GitHubService;
303
-
304
241
  const args = ["issue", "edit", String(opts.issue)];
305
242
 
306
243
  if (opts.title !== null) {
@@ -322,13 +259,12 @@ export const editIssue = Effect.fn("issue.editIssue")(function* (opts: {
322
259
  args.push("--remove-assignee", opts.removeAssignee);
323
260
  }
324
261
 
325
- yield* gh.runGh(args);
326
-
327
- return yield* gh.runGhJson<IssueInfo>([
328
- "issue",
329
- "view",
330
- String(opts.issue),
331
- "--json",
332
- "number,title,state,url,labels,assignees,author,createdAt,closedAt",
262
+ const repo = yield* resolveRepo(null);
263
+ yield* writes.run(args, [
264
+ `issue:${repoSlug(repo)}#${opts.issue}`,
265
+ `issues:${repoSlug(repo)}`,
266
+ `issue-comments:${repoSlug(repo)}#${opts.issue}`,
333
267
  ]);
268
+
269
+ return yield* viewIssue(opts.issue);
334
270
  });
@@ -4,10 +4,11 @@ import { Effect, Option, Schema } from "effect";
4
4
  import type { CheckResult, IssueComment, PRViewInfo, ReviewThread } from "#gh/types";
5
5
 
6
6
  import { formatOption, logFormatted } from "#shared";
7
- import { GitHubService } from "#gh/service";
7
+ import { GitHubService } from "#gh/read/transport";
8
8
  import { fetchReviewTriage } from "#gh/pr/commands";
9
9
 
10
10
  import { fetchIssueComments } from "./core";
11
+ import { reads, resolveRepo } from "#gh/read/reads";
11
12
 
12
13
  const repoOption = Flag.String("repo").pipe(
13
14
  Flag.withDescription("Target repository profile name or owner/name"),
@@ -23,19 +24,6 @@ const withRepo = <A, E, R>(repo: Option.Option<string>, effect: Effect.Effect<A,
23
24
  const TriageVerbosity = Schema.Literals(["compact", "full"]);
24
25
  type TriageVerbosity = typeof TriageVerbosity.Type;
25
26
 
26
- type RawTriageIssue = {
27
- number: number;
28
- title: string;
29
- state: string;
30
- url: string;
31
- labels: Array<{ name: string }>;
32
- assignees: Array<{ login: string }>;
33
- author: { login: string };
34
- body: string;
35
- createdAt: string;
36
- closedAt: string | null;
37
- };
38
-
39
27
  type TriageIssue = {
40
28
  number: number;
41
29
  title: string;
@@ -114,19 +102,9 @@ export const fetchIssueTriage = Effect.fn("issue.fetchIssueTriage")(function* (o
114
102
  issue: number;
115
103
  verbosity: TriageVerbosity;
116
104
  }) {
117
- const gh = yield* GitHubService;
118
-
105
+ const repo = yield* resolveRepo(null);
119
106
  const [rawIssue, comments] = yield* Effect.all(
120
- [
121
- gh.runGhJson<RawTriageIssue>([
122
- "issue",
123
- "view",
124
- String(opts.issue),
125
- "--json",
126
- "number,title,state,url,labels,assignees,author,body,createdAt,closedAt",
127
- ]),
128
- fetchIssueComments(opts.issue, null, null, null),
129
- ],
107
+ [reads.issueView(repo, opts.issue), fetchIssueComments(opts.issue, null, null, null)],
130
108
  { concurrency: "unbounded" },
131
109
  );
132
110
 
@@ -0,0 +1,57 @@
1
+ import { Command, Flag } from "effect/cli";
2
+ import { Effect, Option } from "effect";
3
+
4
+ import { formatOption, logFormatted } from "#shared";
5
+ import { GitHubService } from "#gh/read/transport";
6
+ import { resolveRepo } from "#gh/read/reads";
7
+ import { repoSlug } from "#gh/read/keys";
8
+ import { writes } from "#gh/read/writes";
9
+
10
+ export const createLabel = Effect.fn("label.createLabel")(function* (opts: {
11
+ name: string;
12
+ color: string;
13
+ description: string;
14
+ force: boolean;
15
+ }) {
16
+ const repo = yield* resolveRepo(null);
17
+ const args = [
18
+ "label",
19
+ "create",
20
+ opts.name,
21
+ "--color",
22
+ opts.color,
23
+ "--description",
24
+ opts.description,
25
+ ];
26
+ if (opts.force) args.push("--force");
27
+ yield* writes.run(args, [`repo:${repoSlug(repo)}`]);
28
+ return { name: opts.name };
29
+ });
30
+
31
+ export const labelCreateCommand = Command.make(
32
+ "create",
33
+ {
34
+ name: Flag.String("name").pipe(Flag.withDescription("Label name")),
35
+ color: Flag.String("color").pipe(Flag.withDescription("Six-character hexadecimal color")),
36
+ description: Flag.String("description").pipe(Flag.withDefault("")),
37
+ force: Flag.Boolean("force").pipe(
38
+ Flag.withDescription("Update the label if it already exists"),
39
+ Flag.withDefault(false),
40
+ ),
41
+ repo: Flag.String("repo").pipe(
42
+ Flag.withDescription("Target repository profile name or owner/name"),
43
+ Flag.optional,
44
+ ),
45
+ format: formatOption,
46
+ },
47
+ ({ format, repo, ...opts }) =>
48
+ Effect.gen(function* () {
49
+ const gh = yield* GitHubService;
50
+ yield* gh.withRepoTarget(
51
+ Option.getOrNull(repo),
52
+ Effect.gen(function* () {
53
+ yield* logFormatted(yield* createLabel(opts), format);
54
+ }),
55
+ );
56
+ }),
57
+ ).pipe(Command.withDescription("Create a repository label"));
@@ -71,8 +71,16 @@ import {
71
71
  releaseStatusCommand,
72
72
  releaseViewCommand,
73
73
  } from "./release";
74
- import { repoInfoCommand, repoListCommand, repoSearchCodeCommand } from "./repo";
75
- import { GitHubService } from "./service";
74
+ import {
75
+ repoCommitAuthorCommand,
76
+ repoInfoCommand,
77
+ repoListCommand,
78
+ repoSearchCodeCommand,
79
+ } from "./repo";
80
+ import { labelCreateCommand } from "./label";
81
+ import { GitHubService } from "./read/transport";
82
+ import { withReadCache } from "./read";
83
+ import { cacheCommand } from "./read/commands";
76
84
  import {
77
85
  workflowAnnotationsCommand,
78
86
  workflowCancelCommand,
@@ -151,7 +159,17 @@ const branchCommand = Command.make("branch", {}).pipe(
151
159
 
152
160
  const repoCommand = Command.make("repo", {}).pipe(
153
161
  Command.withDescription("Repository operations"),
154
- Command.withSubcommands([repoInfoCommand, repoListCommand, repoSearchCodeCommand]),
162
+ Command.withSubcommands([
163
+ repoInfoCommand,
164
+ repoListCommand,
165
+ repoSearchCodeCommand,
166
+ repoCommitAuthorCommand,
167
+ ]),
168
+ );
169
+
170
+ const labelCommand = Command.make("label", {}).pipe(
171
+ Command.withDescription("Repository label operations"),
172
+ Command.withSubcommands([labelCreateCommand]),
155
173
  );
156
174
 
157
175
  const workflowCommand = Command.make("workflow", {}).pipe(
@@ -235,11 +253,13 @@ WORKFLOW FOR AI AGENTS:
235
253
  prCommand,
236
254
  issueCommand,
237
255
  repoCommand,
256
+ labelCommand,
238
257
  branchCommand,
239
258
  workflowCommand,
240
259
  releaseCommand,
241
260
  gistCommand,
242
261
  commandsCommand,
262
+ cacheCommand,
243
263
  ]),
244
264
  );
245
265
 
@@ -254,7 +274,7 @@ const MainLayer = GitHubService.layer.pipe(
254
274
  Layer.provideMerge(ConfigServiceLayer),
255
275
  );
256
276
 
257
- const program = withRedactedOutput(withAudit("gh", cli)).pipe(
277
+ const program = withReadCache(withRedactedOutput(withAudit("gh", cli))).pipe(
258
278
  Effect.provide(MainLayer),
259
279
  Effect.tapCause(renderCauseToStderr),
260
280
  );
@@ -1,10 +1,12 @@
1
+ import { reads, writes } from "#gh/read/index";
2
+ import { repoSlug } from "#gh/read/keys";
1
3
  import { Command, Flag, Param } from "effect/cli";
2
4
  import { Console, Effect, Option } from "effect";
3
5
 
4
6
  import type { CheckResult, PRStatusResult } from "#gh/types";
5
7
 
6
8
  import { formatOption, logFormatted, logText } from "#shared";
7
- import { GitHubService } from "#gh/service";
9
+ import { GitHubService } from "#gh/read/transport";
8
10
  import { GitHubCommandError } from "#gh/errors";
9
11
 
10
12
  const emptyBatchError = (batch: string) =>
@@ -152,20 +154,20 @@ export const requestReview = (pr: number, reviewers: string) =>
152
154
  const repo = yield* gh.getRepoInfo();
153
155
  // ponytail: not atomic — a concurrent request between GET and POST misreports newlyRequested.
154
156
  const pendingBefore = new Set(
155
- (yield* confirmedReviewers(
156
- yield* gh.runGhJson<RequestedReviewersResponse>([
157
- "api",
158
- `repos/${repo.owner}/${repo.name}/pulls/${pr}`,
159
- ]),
160
- )).map((login) => login.toLowerCase()),
157
+ (yield* confirmedReviewers(yield* reads.reviewGuards.requestedReviewers(repo, pr))).map(
158
+ (login) => login.toLowerCase(),
159
+ ),
160
+ );
161
+ const response = yield* writes.json<RequestedReviewersResponse>(
162
+ [
163
+ "api",
164
+ "--method",
165
+ "POST",
166
+ `repos/${repo.owner}/${repo.name}/pulls/${pr}/requested_reviewers`,
167
+ ...submittedReviewers.flatMap((reviewer) => ["-f", `reviewers[]=${reviewer}`]),
168
+ ],
169
+ [`pr:${repoSlug(repo)}#${pr}`, `reviews:${repoSlug(repo)}#${pr}`, `pulls:${repoSlug(repo)}`],
161
170
  );
162
- const response = yield* gh.runGhJson<RequestedReviewersResponse>([
163
- "api",
164
- "--method",
165
- "POST",
166
- `repos/${repo.owner}/${repo.name}/pulls/${pr}/requested_reviewers`,
167
- ...submittedReviewers.flatMap((reviewer) => ["-f", `reviewers[]=${reviewer}`]),
168
- ]);
169
171
  return {
170
172
  pr,
171
173
  submittedReviewers,
@@ -239,7 +241,7 @@ const withStableHead = Effect.fn("pr.withStableHead")(function* <A, E, R>(
239
241
  initial,
240
242
  (info) => collect(info.number, info.headSha),
241
243
  (info) =>
242
- fetchPRView(info.number).pipe(
244
+ fetchPRView(info.number, "origin").pipe(
243
245
  Effect.map((refreshed) => ({ ...refreshed, baseSha: initial.baseSha })),
244
246
  ),
245
247
  (before, after) => after.headSha === before.headSha,