@blogic-cz/agent-tools 1.14.4 → 1.14.6
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/README.md +12 -11
- package/dist/api-tool/main.d.ts +6 -1
- package/dist/api-tool/main.d.ts.map +1 -1
- package/dist/audit-tool/main.d.ts +6 -1
- package/dist/audit-tool/main.d.ts.map +1 -1
- package/dist/az-tool/main.d.ts +6 -1
- package/dist/az-tool/main.d.ts.map +1 -1
- package/dist/azdo-tool/main.d.ts +6 -1
- package/dist/azdo-tool/main.d.ts.map +1 -1
- package/dist/credential-guard/issue-227.test.d.ts +2 -0
- package/dist/credential-guard/issue-227.test.d.ts.map +1 -0
- package/dist/credential-guard/policy.d.ts.map +1 -1
- package/dist/credential-guard/registry.d.ts.map +1 -1
- package/dist/db-tool/main.d.ts +8 -1
- package/dist/db-tool/main.d.ts.map +1 -1
- package/dist/db-tool/service.d.ts +1 -0
- package/dist/db-tool/service.d.ts.map +1 -1
- package/dist/gh-tool/main.d.ts +4 -1
- package/dist/gh-tool/main.d.ts.map +1 -1
- package/dist/gh-tool/pr/commands.d.ts +13 -0
- package/dist/gh-tool/pr/commands.d.ts.map +1 -1
- package/dist/gh-tool/pr/review.d.ts +1 -1
- package/dist/gh-tool/pr/review.d.ts.map +1 -1
- package/dist/gh-tool/read/store.d.ts +4 -1
- package/dist/gh-tool/read/store.d.ts.map +1 -1
- package/dist/k8s-tool/main.d.ts +8 -1
- package/dist/k8s-tool/main.d.ts.map +1 -1
- package/dist/logs-tool/main.d.ts +4 -0
- package/dist/logs-tool/main.d.ts.map +1 -1
- package/dist/observability-tool/main.d.ts +6 -1
- package/dist/observability-tool/main.d.ts.map +1 -1
- package/dist/session-tool/main.d.ts +4 -0
- package/dist/session-tool/main.d.ts.map +1 -1
- package/dist/shared/secret-env.d.ts +9 -0
- package/dist/shared/secret-env.d.ts.map +1 -1
- package/package.json +10 -11
- package/src/api-tool/index.ts +1 -1
- package/src/api-tool/main.ts +19 -6
- package/src/audit-tool/index.ts +1 -1
- package/src/audit-tool/main.ts +13 -4
- package/src/az-tool/index.ts +1 -1
- package/src/az-tool/main.ts +10 -4
- package/src/azdo-tool/index.ts +1 -1
- package/src/azdo-tool/main.ts +13 -4
- package/src/credential-guard/issue-227.test.ts +69 -0
- package/src/credential-guard/policy.ts +23 -20
- package/src/credential-guard/registry.ts +99 -11
- package/src/db-tool/index.ts +1 -1
- package/src/db-tool/main.ts +10 -4
- package/src/db-tool/service.ts +6 -1
- package/src/gh-tool/index.ts +1 -1
- package/src/gh-tool/main.ts +5 -4
- package/src/gh-tool/pr/commands.ts +85 -10
- package/src/gh-tool/pr/review.ts +2 -1
- package/src/gh-tool/read/store.ts +8 -2
- package/src/k8s-tool/index.ts +1 -1
- package/src/k8s-tool/main.ts +10 -4
- package/src/logs-tool/index.ts +1 -1
- package/src/logs-tool/main.ts +13 -4
- package/src/observability-tool/index.ts +1 -1
- package/src/observability-tool/main.ts +10 -2
- package/src/session-tool/index.ts +1 -1
- package/src/session-tool/main.ts +10 -13
- package/src/shared/secret-env.ts +108 -88
package/src/azdo-tool/index.ts
CHANGED
package/src/azdo-tool/main.ts
CHANGED
|
@@ -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
|
-
|
|
223
|
-
|
|
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));
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { expect, it } from "vitest";
|
|
2
|
+
import { createCredentialGuard } from "./index";
|
|
3
|
+
import { parseBash } from "./bash";
|
|
4
|
+
import { judge } from "./policy";
|
|
5
|
+
|
|
6
|
+
const bash = (command: string) => judge(parseBash(command), { cwd: "/repo", home: "/users/test" });
|
|
7
|
+
|
|
8
|
+
it.each([
|
|
9
|
+
`sed -i '' 's/"x": "\\^1\\.14\\.[0-9]*"/"x": "^1.14.4"/' package.json`,
|
|
10
|
+
"sed -i '' 's/a*/b/' package.json",
|
|
11
|
+
"sed -i 's/[0-9]/x/' notes.txt",
|
|
12
|
+
"sed -n '/[0-9]*/p' notes.txt",
|
|
13
|
+
])("keeps sed scripts out of write targets: %s", (command) => {
|
|
14
|
+
expect(bash(command)).toEqual({ kind: "pass" });
|
|
15
|
+
expect(() =>
|
|
16
|
+
createCredentialGuard().handleToolExecuteBefore({ tool: "Bash" }, { args: { command } }),
|
|
17
|
+
).not.toThrow();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it.each([
|
|
21
|
+
"cd ~/.claude && printf x > comm?nds/x.md",
|
|
22
|
+
"cd ~/.claude && printf x | tee comm?nds/x.md",
|
|
23
|
+
"cd ~/.claude && printf x > sk?lls/x/SKILL.md",
|
|
24
|
+
"cd ~/.claude && cp /tmp/x sk?lls/foo/SKILL.md",
|
|
25
|
+
"cd ~/.claude && cp /tmp/x hoo?s/x.sh",
|
|
26
|
+
"cd ~/.claude && tee ag?nts/x.md < /tmp/x",
|
|
27
|
+
"cd ~/.claude && touch comm?nds/x.md",
|
|
28
|
+
"cd ~/.claude && truncate -s 0 comm?nds/x.md",
|
|
29
|
+
"cd ~/.claude && cat /tmp/x >> comm*/x.md",
|
|
30
|
+
"cd ~/.agents && printf x > sk?lls/x/SKILL.md",
|
|
31
|
+
"cd ~/.agents && cp /tmp/x sk*/x/SKILL.md",
|
|
32
|
+
"cd /repo/.claude && printf x > comm*/x.md",
|
|
33
|
+
"cd ~/.claude/ && printf x > ./comm?nds/x.md",
|
|
34
|
+
"cd ~/.claude/commands && printf x > ../sk?lls/x.md",
|
|
35
|
+
"cd .claude && cp f c*/x.md",
|
|
36
|
+
"cd .claude && echo x > c*/x.md",
|
|
37
|
+
"cd /repo/.claude && cp f ./c*/x.md",
|
|
38
|
+
"cd /repo/.claude && cp f s*/x/SKILL.md",
|
|
39
|
+
"cd /repo/.agents && cp f s*/x/SKILL.md",
|
|
40
|
+
"sed -i 's/x/y/' AGENT?.md",
|
|
41
|
+
"sed -i 's/x/y/' AGENT[]S].md",
|
|
42
|
+
"sed -i 's/x/y/' AGENT[S].md",
|
|
43
|
+
"sed -i 's/x/y/' .cl?ude/agents/task.md",
|
|
44
|
+
"sed -i 's/x/y/' .claude/a*/task.md",
|
|
45
|
+
"sed -i 's/x/y/' .claude//a*/task.md",
|
|
46
|
+
"sed -i 's/x/y/' .claude/./a*/task.md",
|
|
47
|
+
"sed -i 's/x/y/' .claude/notes/../a*/task.md",
|
|
48
|
+
"sed -i 's/x/y/' .claude/c*/task.md",
|
|
49
|
+
"sed -i 's/x/y/' .cod?x/prompts/task.md",
|
|
50
|
+
"sed -i 's/x/y/' .agents/s*/task.md",
|
|
51
|
+
"tar -vW create -f AGENTS.md src",
|
|
52
|
+
"tar -vW append -f AGENTS.md src",
|
|
53
|
+
"tar -kW create -f AGENTS.md src",
|
|
54
|
+
"tar -vWappend -f AGENTS.md src",
|
|
55
|
+
"tar -A -f AGENTS.md x.tar",
|
|
56
|
+
"tar --catenate -f AGENTS.md x.tar",
|
|
57
|
+
"tar --delete -f AGENTS.md member",
|
|
58
|
+
])("refuses protected write targets: %s", (command) => {
|
|
59
|
+
expect(bash(command)).toMatchObject({
|
|
60
|
+
findings: expect.arrayContaining([expect.objectContaining({ rule: "agent-config-write" })]),
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it.each(["tar --delete -f /tmp/x.tar member", "tar -xf x.tar AGENTS.md"])(
|
|
65
|
+
"keeps documented archive limits: %s",
|
|
66
|
+
(command) => {
|
|
67
|
+
expect(bash(command)).toEqual({ kind: "pass" });
|
|
68
|
+
},
|
|
69
|
+
);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { formatLiteralPrintf } from "./literal-output";
|
|
2
|
+
import { posix } from "node:path";
|
|
2
3
|
import { splitXargsInput } from "./xargs";
|
|
3
4
|
import { parseBash } from "./bash";
|
|
4
5
|
import { parsePowerShell } from "./powershell";
|
|
@@ -98,12 +99,12 @@ function mentions(value: Value): readonly string[] {
|
|
|
98
99
|
}
|
|
99
100
|
function agentConfigGlob(value: Value): boolean {
|
|
100
101
|
return mentions(value).some((text) =>
|
|
101
|
-
|
|
102
|
-
.replace(/\\/g, "/")
|
|
102
|
+
posix
|
|
103
|
+
.normalize(text.replace(/\\/g, "/"))
|
|
103
104
|
.split("/")
|
|
104
|
-
.some((part) => {
|
|
105
|
+
.some((part, index, parts) => {
|
|
105
106
|
if (!/[*?[]/.test(part)) return false;
|
|
106
|
-
if (part.replace(/\[[A-Za-z0-9._]\]/g, "").includes("[")) return true;
|
|
107
|
+
if (part.replace(/\[(?:[A-Za-z0-9._]|0-9)\]/g, "").includes("[")) return true;
|
|
107
108
|
const pattern = new RegExp(
|
|
108
109
|
`^${part
|
|
109
110
|
.replace(/[.+^${}()|\\]/g, "\\$&")
|
|
@@ -112,22 +113,24 @@ function agentConfigGlob(value: Value): boolean {
|
|
|
112
113
|
.replace(/\?/g, ".")}$`,
|
|
113
114
|
"i",
|
|
114
115
|
);
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
"
|
|
128
|
-
"
|
|
129
|
-
|
|
130
|
-
|
|
116
|
+
const names = [".claude", ".codex", ".agents"];
|
|
117
|
+
if (index === parts.length - 1 || (index === parts.length - 2 && parts.at(-1) === ""))
|
|
118
|
+
names.push(
|
|
119
|
+
"AGENTS.md",
|
|
120
|
+
"CLAUDE.md",
|
|
121
|
+
".mcp.json",
|
|
122
|
+
".claude.json",
|
|
123
|
+
"config.toml",
|
|
124
|
+
"settings.json",
|
|
125
|
+
"settings.local.json",
|
|
126
|
+
);
|
|
127
|
+
if (
|
|
128
|
+
/^\.claude$/i.test(parts[index - 1] ?? "") ||
|
|
129
|
+
(!posix.isAbsolute(text) && (index === 0 || parts[index - 1] === ".."))
|
|
130
|
+
)
|
|
131
|
+
names.push("commands", "agents", "skills", "hooks");
|
|
132
|
+
if (/^\.agents$/i.test(parts[index - 1] ?? "")) names.push("skills");
|
|
133
|
+
return names.some((name) => pattern.test(name));
|
|
131
134
|
}),
|
|
132
135
|
);
|
|
133
136
|
}
|
|
@@ -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: {
|
|
@@ -3119,6 +3191,7 @@ export const EXECUTABLES: readonly ExecutableEntry[] = [
|
|
|
3119
3191
|
"-A",
|
|
3120
3192
|
"--catenate",
|
|
3121
3193
|
"--concatenate",
|
|
3194
|
+
"--delete",
|
|
3122
3195
|
"-t",
|
|
3123
3196
|
"--list",
|
|
3124
3197
|
"-v",
|
|
@@ -4205,21 +4278,18 @@ function classifyInvocation(
|
|
|
4205
4278
|
entry.name === "tar" &&
|
|
4206
4279
|
argv.slice(1).some((word, index) => {
|
|
4207
4280
|
if (word === undefined) return false;
|
|
4208
|
-
const longOption =
|
|
4209
|
-
word
|
|
4210
|
-
|
|
4211
|
-
: word.startsWith("-W")
|
|
4212
|
-
? word.slice(2).split("=", 1)[0]
|
|
4213
|
-
: undefined;
|
|
4281
|
+
const longOption = /^-[^-]*W/.test(word)
|
|
4282
|
+
? (word.slice(word.indexOf("W") + 1) || argv[index + 2])?.split("=", 1)[0]
|
|
4283
|
+
: undefined;
|
|
4214
4284
|
if (
|
|
4215
4285
|
longOption &&
|
|
4216
|
-
["create", "append", "update", "catenate", "concatenate"].some((option) =>
|
|
4286
|
+
["create", "append", "update", "catenate", "concatenate", "delete"].some((option) =>
|
|
4217
4287
|
option.startsWith(longOption),
|
|
4218
4288
|
)
|
|
4219
4289
|
)
|
|
4220
4290
|
return true;
|
|
4221
4291
|
if (word.startsWith("--"))
|
|
4222
|
-
return ["--create", "--append", "--update", "--catenate", "--concatenate"].some(
|
|
4292
|
+
return ["--create", "--append", "--update", "--catenate", "--concatenate", "--delete"].some(
|
|
4223
4293
|
(option) => word.length > 2 && option.startsWith(word.split("=")[0] ?? word),
|
|
4224
4294
|
);
|
|
4225
4295
|
const cluster =
|
|
@@ -4233,7 +4303,11 @@ function classifyInvocation(
|
|
|
4233
4303
|
}
|
|
4234
4304
|
const a = argv[position];
|
|
4235
4305
|
if (a === undefined) {
|
|
4236
|
-
if (
|
|
4306
|
+
if (
|
|
4307
|
+
["env", "forward", "timeout", "xargs"].includes(entry.semantics ?? "") ||
|
|
4308
|
+
(entry.name === "hc" &&
|
|
4309
|
+
(mode === entry.subcommands?.run || mode === entry.subcommands?.exec))
|
|
4310
|
+
)
|
|
4237
4311
|
issues.push({
|
|
4238
4312
|
kind: "missing-knowledge",
|
|
4239
4313
|
rule: "opaque-executable",
|
|
@@ -4459,8 +4533,8 @@ function classifyInvocation(
|
|
|
4459
4533
|
(attached ?? argv[position + 1]) === undefined
|
|
4460
4534
|
)
|
|
4461
4535
|
issues.push(unknown("Bun option operand is not literal"));
|
|
4462
|
-
if (entry.name
|
|
4463
|
-
if (cwdOperands.length) issues.push(unknown(
|
|
4536
|
+
if (["bun", "hc"].includes(entry.name) && optionName === "--cwd") {
|
|
4537
|
+
if (cwdOperands.length) issues.push(unknown(`repeated ${entry.name} working directory`));
|
|
4464
4538
|
cwdOperands.push({
|
|
4465
4539
|
index: attached === undefined ? position + 1 : position,
|
|
4466
4540
|
...(attached !== undefined ? { literal: attached } : {}),
|
|
@@ -4583,6 +4657,18 @@ function classifyInvocation(
|
|
|
4583
4657
|
position++;
|
|
4584
4658
|
continue;
|
|
4585
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
|
+
}
|
|
4586
4672
|
if (entry.name === "git" && mode === entry.subcommands?.bisect?.subcommands?.run) {
|
|
4587
4673
|
child(position);
|
|
4588
4674
|
break;
|
|
@@ -4671,6 +4757,8 @@ function classifyInvocation(
|
|
|
4671
4757
|
if (effect && effect !== "none") issues.push(effect);
|
|
4672
4758
|
if (!positional.length && !children.length && !codeSeen && entry.bare) issues.push(entry.bare);
|
|
4673
4759
|
roles.push(...optionRoles);
|
|
4760
|
+
if (entry.name === "hc" && mode === entry.subcommands?.send)
|
|
4761
|
+
issues.push(...unknownOperandIssues(positional.slice(1).map((index) => argv[index])));
|
|
4674
4762
|
const operands =
|
|
4675
4763
|
(filterOption && ["sed", "awk"].includes(entry.semantics ?? "")) ||
|
|
4676
4764
|
(filterOption &&
|
package/src/db-tool/index.ts
CHANGED
package/src/db-tool/main.ts
CHANGED
|
@@ -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
|
-
|
|
265
|
-
|
|
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));
|
package/src/db-tool/service.ts
CHANGED
|
@@ -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)
|
package/src/gh-tool/index.ts
CHANGED
package/src/gh-tool/main.ts
CHANGED
|
@@ -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
|
-
|
|
311
|
-
|
|
312
|
-
|
|
310
|
+
export const runMain = () =>
|
|
311
|
+
BunRuntime.runMain(program, {
|
|
312
|
+
disableErrorReporting: true,
|
|
313
|
+
});
|
|
@@ -405,16 +405,19 @@ export const fetchReviewTriage = Effect.fn("pr.fetchReviewTriage")(function* (
|
|
|
405
405
|
const { info, value } = yield* withStableHead(
|
|
406
406
|
prNumber,
|
|
407
407
|
(number, headSha) =>
|
|
408
|
-
Effect.
|
|
409
|
-
fetchThreads(number, false, false, headSha)
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
408
|
+
Effect.gen(function* () {
|
|
409
|
+
const allThreads = yield* fetchThreads(number, false, false, headSha);
|
|
410
|
+
const [summary, checkReport, reviews, inlineComments] = yield* Effect.all([
|
|
411
|
+
fetchDiscussionSummary(number, allThreads),
|
|
412
|
+
fetchCheckReport(number),
|
|
413
|
+
fetchReviews(number, null, null, null, headSha),
|
|
414
|
+
fetchComments(number, null, headSha),
|
|
415
|
+
]);
|
|
416
|
+
return { allThreads, summary, checkReport, reviews, inlineComments };
|
|
417
|
+
}),
|
|
415
418
|
{ operation: "review triage", command: "gh-tool pr review-triage" },
|
|
416
419
|
);
|
|
417
|
-
const
|
|
420
|
+
const { allThreads, summary, checkReport, reviews, inlineComments } = value;
|
|
418
421
|
const checks = checkReport.checks ?? [];
|
|
419
422
|
const unresolvedThreads = allThreads.filter((thread) => !thread.isResolved);
|
|
420
423
|
const visibleOpenThreads = allThreads.filter((thread) => thread.isVisibleOpen);
|
|
@@ -483,6 +486,60 @@ export const fetchReviewTriage = Effect.fn("pr.fetchReviewTriage")(function* (
|
|
|
483
486
|
};
|
|
484
487
|
});
|
|
485
488
|
|
|
489
|
+
type CompactReviewTriage = {
|
|
490
|
+
readonly pr: number;
|
|
491
|
+
readonly classification: ReviewTriageClassification;
|
|
492
|
+
readonly visibleOpenReviewThreadsCount: number;
|
|
493
|
+
readonly unrepliedReviewThreadsCount: number;
|
|
494
|
+
readonly unresolvedReviewThreadsCount: number;
|
|
495
|
+
readonly visibleOpenThreadIds: readonly string[];
|
|
496
|
+
};
|
|
497
|
+
|
|
498
|
+
const compactReviewTriage = (
|
|
499
|
+
pr: number,
|
|
500
|
+
result: {
|
|
501
|
+
readonly classification: ReviewTriageClassification;
|
|
502
|
+
readonly summary: ReviewTriageSummary;
|
|
503
|
+
readonly visibleOpenThreads: ReadonlyArray<{ readonly threadId: string }>;
|
|
504
|
+
},
|
|
505
|
+
): CompactReviewTriage => ({
|
|
506
|
+
pr,
|
|
507
|
+
classification: result.classification,
|
|
508
|
+
visibleOpenReviewThreadsCount: result.summary.visibleOpenReviewThreadsCount,
|
|
509
|
+
unrepliedReviewThreadsCount: result.summary.unrepliedReviewThreadsCount,
|
|
510
|
+
unresolvedReviewThreadsCount: result.summary.unresolvedReviewThreadsCount,
|
|
511
|
+
visibleOpenThreadIds: result.visibleOpenThreads.map((thread) => thread.threadId),
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
export const fetchReviewTriageBatch = Effect.fn("pr.fetchReviewTriageBatch")(function* (
|
|
515
|
+
prs: readonly number[],
|
|
516
|
+
) {
|
|
517
|
+
return yield* Effect.forEach(
|
|
518
|
+
prs,
|
|
519
|
+
(pr) =>
|
|
520
|
+
fetchReviewTriage(pr).pipe(
|
|
521
|
+
Effect.map((result) => compactReviewTriage(pr, result)),
|
|
522
|
+
Effect.catch((error) =>
|
|
523
|
+
Effect.succeed({
|
|
524
|
+
pr,
|
|
525
|
+
error: error instanceof Error ? error.message : String(error),
|
|
526
|
+
}),
|
|
527
|
+
),
|
|
528
|
+
),
|
|
529
|
+
{ concurrency: 5 },
|
|
530
|
+
);
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
const reviewTriageBatchError = (
|
|
534
|
+
errors: readonly { readonly pr: number; readonly error: string }[],
|
|
535
|
+
) =>
|
|
536
|
+
new GitHubCommandError({
|
|
537
|
+
message: `Failed to fetch review triage for PRs: ${errors.map(({ pr, error }) => `#${pr}: ${error}`).join("; ")}`,
|
|
538
|
+
command: "gh-tool pr review-triage --prs",
|
|
539
|
+
exitCode: 1,
|
|
540
|
+
stderr: "",
|
|
541
|
+
});
|
|
542
|
+
|
|
486
543
|
export const prViewCommand = Command.make(
|
|
487
544
|
"view",
|
|
488
545
|
{
|
|
@@ -1715,12 +1772,30 @@ export const prReviewTriageCommand = Command.make(
|
|
|
1715
1772
|
Flag.withDescription("PR number (default: current branch PR)"),
|
|
1716
1773
|
Flag.optional,
|
|
1717
1774
|
),
|
|
1775
|
+
prs: Flag.String("prs").pipe(
|
|
1776
|
+
Flag.withDescription(
|
|
1777
|
+
"Comma-separated PR numbers for a compact batch summary (overrides --pr)",
|
|
1778
|
+
),
|
|
1779
|
+
Flag.optional,
|
|
1780
|
+
),
|
|
1718
1781
|
repo: repoOption,
|
|
1719
1782
|
},
|
|
1720
|
-
({ format, omit, pr, repo }) =>
|
|
1783
|
+
({ format, omit, pr, prs, repo }) =>
|
|
1721
1784
|
withRepo(
|
|
1722
1785
|
repo,
|
|
1723
1786
|
Effect.gen(function* () {
|
|
1787
|
+
const batch = Option.getOrNull(prs);
|
|
1788
|
+
if (batch !== null) {
|
|
1789
|
+
const numbers = yield* parsePrNumbers(batch);
|
|
1790
|
+
const results = yield* fetchReviewTriageBatch(numbers);
|
|
1791
|
+
yield* logFormatted({ count: results.length, prs: results }, format);
|
|
1792
|
+
const errors = results.filter(
|
|
1793
|
+
(result): result is Extract<(typeof results)[number], { readonly error: string }> =>
|
|
1794
|
+
"error" in result,
|
|
1795
|
+
);
|
|
1796
|
+
if (errors.length > 0) return yield* reviewTriageBatchError(errors);
|
|
1797
|
+
return;
|
|
1798
|
+
}
|
|
1724
1799
|
const prNumber = Option.getOrNull(pr);
|
|
1725
1800
|
const result = yield* fetchReviewTriage(prNumber);
|
|
1726
1801
|
yield* logFormatted(
|
|
@@ -1731,7 +1806,7 @@ export const prReviewTriageCommand = Command.make(
|
|
|
1731
1806
|
),
|
|
1732
1807
|
).pipe(
|
|
1733
1808
|
Command.withDescription(
|
|
1734
|
-
"Composite: PR info + merge-readiness verdict + unresolved
|
|
1809
|
+
"Composite: PR info + merge-readiness verdict + unresolved and visible-open threads + discussion summary + checks status; use --prs for a compact batch summary",
|
|
1735
1810
|
),
|
|
1736
1811
|
);
|
|
1737
1812
|
|
package/src/gh-tool/pr/review.ts
CHANGED
|
@@ -430,11 +430,12 @@ export const postIssueComment = Effect.fn("pr.postIssueComment")(function* (
|
|
|
430
430
|
|
|
431
431
|
export const fetchDiscussionSummary = Effect.fn("pr.fetchDiscussionSummary")(function* (
|
|
432
432
|
pr: number | null,
|
|
433
|
+
collectedThreads?: readonly ReviewThread[],
|
|
433
434
|
) {
|
|
434
435
|
const [issueComments, reviewComments, threads] = yield* Effect.all([
|
|
435
436
|
fetchIssueComments(pr, null, null, null),
|
|
436
437
|
fetchComments(pr, null),
|
|
437
|
-
fetchThreads(pr, false),
|
|
438
|
+
collectedThreads === undefined ? fetchThreads(pr, false) : Effect.succeed(collectedThreads),
|
|
438
439
|
]);
|
|
439
440
|
|
|
440
441
|
const latestIssueComment =
|
|
@@ -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(
|
|
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
|
-
|
|
235
|
+
this.contentionTimeoutMs,
|
|
230
236
|
);
|
|
231
237
|
try {
|
|
232
238
|
return await body();
|
package/src/k8s-tool/index.ts
CHANGED
package/src/k8s-tool/main.ts
CHANGED
|
@@ -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
|
-
|
|
490
|
-
|
|
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));
|
package/src/logs-tool/index.ts
CHANGED