@blogic-cz/agent-tools 0.1.0 → 0.2.1
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 +68 -30
- package/package.json +7 -4
- package/schemas/agent-tools.schema.json +4 -0
- package/src/az-tool/build.ts +5 -0
- package/src/az-tool/errors.ts +12 -0
- package/src/az-tool/index.ts +129 -105
- package/src/az-tool/service.ts +13 -4
- package/src/config/index.ts +7 -1
- package/src/config/loader.ts +8 -0
- package/src/config/types.ts +2 -0
- package/src/credential-guard/index.ts +2 -1
- package/src/db-tool/config-service.ts +2 -2
- package/src/db-tool/errors.ts +15 -0
- package/src/db-tool/index.ts +47 -8
- package/src/db-tool/types.ts +1 -1
- package/src/gh-tool/errors.ts +15 -0
- package/src/gh-tool/index.ts +5 -1
- package/src/gh-tool/issue.ts +1 -1
- package/src/gh-tool/pr/commands.ts +58 -3
- package/src/gh-tool/pr/core.ts +28 -7
- package/src/gh-tool/pr/helpers.ts +1 -1
- package/src/gh-tool/pr/index.ts +2 -0
- package/src/gh-tool/pr/review.ts +10 -6
- package/src/gh-tool/repo.ts +1 -1
- package/src/gh-tool/service.ts +5 -0
- package/src/gh-tool/workflow.ts +5 -1
- package/src/k8s-tool/errors.ts +9 -0
- package/src/k8s-tool/index.ts +318 -66
- package/src/k8s-tool/service.ts +2 -2
- package/src/k8s-tool/types.ts +4 -0
- package/src/logs-tool/errors.ts +12 -0
- package/src/logs-tool/index.ts +73 -11
- package/src/logs-tool/service.ts +4 -4
- package/src/logs-tool/types.ts +4 -1
- package/src/session-tool/config.ts +1 -1
- package/src/session-tool/index.ts +1 -1
- package/src/session-tool/service.ts +16 -3
- package/src/session-tool/types.ts +1 -1
- package/src/shared/bun.ts +1 -1
- package/src/shared/error-renderer.ts +21 -11
- package/src/shared/index.ts +1 -0
- package/src/shared/types.ts +3 -0
package/src/config/loader.ts
CHANGED
|
@@ -65,6 +65,7 @@ const AgentToolsConfigSchema = Schema.Struct({
|
|
|
65
65
|
}),
|
|
66
66
|
),
|
|
67
67
|
credentialGuard: Schema.optionalKey(CredentialGuardConfigSchema),
|
|
68
|
+
defaultEnvironment: Schema.optionalKey(Schema.String),
|
|
68
69
|
});
|
|
69
70
|
|
|
70
71
|
async function findConfigFile(startDirectory: string = process.cwd()): Promise<string | undefined> {
|
|
@@ -72,11 +73,13 @@ async function findConfigFile(startDirectory: string = process.cwd()): Promise<s
|
|
|
72
73
|
|
|
73
74
|
while (true) {
|
|
74
75
|
const json5Path = `${currentDirectory}/agent-tools.json5`;
|
|
76
|
+
// eslint-disable-next-line eslint/no-await-in-loop -- sequential directory walk, each iteration may short-circuit
|
|
75
77
|
if (await Bun.file(json5Path).exists()) {
|
|
76
78
|
return json5Path;
|
|
77
79
|
}
|
|
78
80
|
|
|
79
81
|
const jsonPath = `${currentDirectory}/agent-tools.json`;
|
|
82
|
+
// eslint-disable-next-line eslint/no-await-in-loop -- sequential directory walk, each iteration may short-circuit
|
|
80
83
|
if (await Bun.file(jsonPath).exists()) {
|
|
81
84
|
return jsonPath;
|
|
82
85
|
}
|
|
@@ -106,6 +109,7 @@ export async function loadConfig(): Promise<AgentToolsConfig | undefined> {
|
|
|
106
109
|
`Invalid agent-tools config at ${configPath}: ${
|
|
107
110
|
error instanceof Error ? error.message : String(error)
|
|
108
111
|
}`,
|
|
112
|
+
{ cause: error },
|
|
109
113
|
);
|
|
110
114
|
}
|
|
111
115
|
}
|
|
@@ -168,3 +172,7 @@ export function getToolConfig<T>(
|
|
|
168
172
|
`Multiple ${section} profiles found: [${keys.join(", ")}]. Use --profile <name> to select one.`,
|
|
169
173
|
);
|
|
170
174
|
}
|
|
175
|
+
|
|
176
|
+
export function getDefaultEnvironment(config: AgentToolsConfig | undefined): string | undefined {
|
|
177
|
+
return config?.defaultEnvironment;
|
|
178
|
+
}
|
package/src/config/types.ts
CHANGED
|
@@ -79,4 +79,6 @@ export type AgentToolsConfig = {
|
|
|
79
79
|
};
|
|
80
80
|
/** Global credential guard config (merged with built-in defaults, not per-profile) */
|
|
81
81
|
credentialGuard?: CredentialGuardConfig;
|
|
82
|
+
/** Optional default environment name (local|test|prod) used by tools when no --env flag is provided */
|
|
83
|
+
defaultEnvironment?: string;
|
|
82
84
|
};
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* at infrastructure level (K8s RBAC, file permissions, etc.)
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import type { CliToolOverride, CredentialGuardConfig } from "
|
|
17
|
+
import type { CliToolOverride, CredentialGuardConfig } from "#src/config/types.ts";
|
|
18
18
|
|
|
19
19
|
// ============================================================================
|
|
20
20
|
// TYPES
|
|
@@ -108,6 +108,7 @@ const SECRET_PATTERNS = [
|
|
|
108
108
|
/(?:secret|token|password|passwd|pwd)[" \t:=]+["']?(?!\$\{|process\.env|z\.|generate|create|read|get|fetch|import|export|const|function|return|Schema)[^\s"']{32,}["']?/i,
|
|
109
109
|
},
|
|
110
110
|
{
|
|
111
|
+
// eslint-disable-next-line eslint/no-useless-concat -- intentionally split to avoid credential guard self-detection
|
|
111
112
|
name: "Priv" + "ate Key",
|
|
112
113
|
pattern: new RegExp("-----BEGIN.*PRIVATE KEY-----"),
|
|
113
114
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Effect, Layer, ServiceMap } from "effect";
|
|
2
2
|
|
|
3
|
-
import { ConfigService, getToolConfig } from "
|
|
4
|
-
import type { DatabaseConfig } from "
|
|
3
|
+
import { ConfigService, getToolConfig } from "#src/config";
|
|
4
|
+
import type { DatabaseConfig } from "#src/config";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* DbConfigService wraps the resolved DatabaseConfig for the selected profile.
|
package/src/db-tool/errors.ts
CHANGED
|
@@ -5,6 +5,9 @@ export class DbConnectionError extends Schema.TaggedErrorClass<DbConnectionError
|
|
|
5
5
|
{
|
|
6
6
|
message: Schema.String,
|
|
7
7
|
environment: Schema.String,
|
|
8
|
+
hint: Schema.optionalKey(Schema.String),
|
|
9
|
+
nextCommand: Schema.optionalKey(Schema.String),
|
|
10
|
+
retryable: Schema.optionalKey(Schema.Boolean),
|
|
8
11
|
},
|
|
9
12
|
) {}
|
|
10
13
|
|
|
@@ -12,16 +15,25 @@ export class DbQueryError extends Schema.TaggedErrorClass<DbQueryError>()("DbQue
|
|
|
12
15
|
message: Schema.String,
|
|
13
16
|
sql: Schema.String,
|
|
14
17
|
stderr: Schema.optionalKey(Schema.String),
|
|
18
|
+
hint: Schema.optionalKey(Schema.String),
|
|
19
|
+
nextCommand: Schema.optionalKey(Schema.String),
|
|
20
|
+
retryable: Schema.optionalKey(Schema.Boolean),
|
|
15
21
|
}) {}
|
|
16
22
|
|
|
17
23
|
export class DbTunnelError extends Schema.TaggedErrorClass<DbTunnelError>()("DbTunnelError", {
|
|
18
24
|
message: Schema.String,
|
|
19
25
|
port: Schema.Number,
|
|
26
|
+
hint: Schema.optionalKey(Schema.String),
|
|
27
|
+
nextCommand: Schema.optionalKey(Schema.String),
|
|
28
|
+
retryable: Schema.optionalKey(Schema.Boolean),
|
|
20
29
|
}) {}
|
|
21
30
|
|
|
22
31
|
export class DbParseError extends Schema.TaggedErrorClass<DbParseError>()("DbParseError", {
|
|
23
32
|
message: Schema.String,
|
|
24
33
|
rawOutput: Schema.String,
|
|
34
|
+
hint: Schema.optionalKey(Schema.String),
|
|
35
|
+
nextCommand: Schema.optionalKey(Schema.String),
|
|
36
|
+
retryable: Schema.optionalKey(Schema.Boolean),
|
|
25
37
|
}) {}
|
|
26
38
|
|
|
27
39
|
export class DbMutationBlockedError extends Schema.TaggedErrorClass<DbMutationBlockedError>()(
|
|
@@ -29,6 +41,9 @@ export class DbMutationBlockedError extends Schema.TaggedErrorClass<DbMutationBl
|
|
|
29
41
|
{
|
|
30
42
|
message: Schema.String,
|
|
31
43
|
environment: Schema.String,
|
|
44
|
+
hint: Schema.optionalKey(Schema.String),
|
|
45
|
+
nextCommand: Schema.optionalKey(Schema.String),
|
|
46
|
+
retryable: Schema.optionalKey(Schema.Boolean),
|
|
32
47
|
},
|
|
33
48
|
) {}
|
|
34
49
|
|
package/src/db-tool/index.ts
CHANGED
|
@@ -5,9 +5,10 @@ import { Console, Effect, Layer, Option } from "effect";
|
|
|
5
5
|
|
|
6
6
|
import type { SchemaMode } from "./types";
|
|
7
7
|
|
|
8
|
-
import { formatOption, formatOutput, renderCauseToStderr, VERSION } from "
|
|
9
|
-
import { ConfigServiceLayer } from "
|
|
8
|
+
import { formatOption, formatOutput, renderCauseToStderr, VERSION } from "#src/shared";
|
|
9
|
+
import { ConfigService, ConfigServiceLayer, getDefaultEnvironment } from "#src/config";
|
|
10
10
|
import { makeDbConfigLayer } from "./config-service";
|
|
11
|
+
import { DbConnectionError } from "./errors";
|
|
11
12
|
import { DbService } from "./service";
|
|
12
13
|
|
|
13
14
|
// Extract --profile from argv before @effect/cli parsing
|
|
@@ -15,11 +16,45 @@ import { DbService } from "./service";
|
|
|
15
16
|
const profileIndex = process.argv.indexOf("--profile");
|
|
16
17
|
const profileArg = profileIndex !== -1 ? process.argv[profileIndex + 1] : undefined;
|
|
17
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Resolve environment from explicit --env flag, config defaultEnvironment, or fail with hint.
|
|
21
|
+
*/
|
|
22
|
+
const resolveEnv = (envOption: Option.Option<string>) =>
|
|
23
|
+
Effect.gen(function* () {
|
|
24
|
+
const explicit = Option.getOrUndefined(envOption);
|
|
25
|
+
if (explicit) return explicit;
|
|
26
|
+
|
|
27
|
+
const config = yield* ConfigService;
|
|
28
|
+
const defaultEnv = getDefaultEnvironment(config);
|
|
29
|
+
|
|
30
|
+
if (defaultEnv === "prod") {
|
|
31
|
+
return yield* new DbConnectionError({
|
|
32
|
+
message:
|
|
33
|
+
"Implicit prod access blocked. Config defaultEnvironment is 'prod' but --env was not passed explicitly.",
|
|
34
|
+
environment: "(prod-safety)",
|
|
35
|
+
hint: "Pass --env prod explicitly to confirm production access, or change defaultEnvironment to a non-prod value.",
|
|
36
|
+
nextCommand: 'agent-tools-db sql --env prod --sql "SELECT 1"',
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (defaultEnv) return defaultEnv;
|
|
41
|
+
|
|
42
|
+
return yield* new DbConnectionError({
|
|
43
|
+
message:
|
|
44
|
+
"No environment specified. Use --env <name> or set defaultEnvironment in agent-tools.json5.",
|
|
45
|
+
environment: "(not specified)",
|
|
46
|
+
hint: 'Set defaultEnvironment in agent-tools.json5 (e.g. defaultEnvironment: "local") or pass --env explicitly.',
|
|
47
|
+
nextCommand: 'agent-tools-db sql --env local --sql "SELECT 1"',
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
18
51
|
const sqlCommand = Command.make(
|
|
19
52
|
"sql",
|
|
20
53
|
{
|
|
21
|
-
env: Flag.string("env").pipe(
|
|
22
|
-
Flag.withDescription(
|
|
54
|
+
env: Flag.optional(Flag.string("env")).pipe(
|
|
55
|
+
Flag.withDescription(
|
|
56
|
+
"Target database environment name (e.g. local, test, prod). Falls back to defaultEnvironment in config.",
|
|
57
|
+
),
|
|
23
58
|
),
|
|
24
59
|
sql: Flag.string("sql").pipe(Flag.withDescription("SQL query to execute")),
|
|
25
60
|
format: formatOption,
|
|
@@ -29,8 +64,9 @@ const sqlCommand = Command.make(
|
|
|
29
64
|
},
|
|
30
65
|
({ env, sql, format }) =>
|
|
31
66
|
Effect.gen(function* () {
|
|
67
|
+
const resolvedEnv = yield* resolveEnv(env);
|
|
32
68
|
const db = yield* DbService;
|
|
33
|
-
const result = yield* db.executeQuery(
|
|
69
|
+
const result = yield* db.executeQuery(resolvedEnv, sql);
|
|
34
70
|
yield* Console.log(formatOutput(result, format));
|
|
35
71
|
}),
|
|
36
72
|
).pipe(Command.withDescription("Execute a SQL query"));
|
|
@@ -38,8 +74,10 @@ const sqlCommand = Command.make(
|
|
|
38
74
|
const schemaCommand = Command.make(
|
|
39
75
|
"schema",
|
|
40
76
|
{
|
|
41
|
-
env: Flag.string("env").pipe(
|
|
42
|
-
Flag.withDescription(
|
|
77
|
+
env: Flag.optional(Flag.string("env")).pipe(
|
|
78
|
+
Flag.withDescription(
|
|
79
|
+
"Target database environment name (e.g. local, test, prod). Falls back to defaultEnvironment in config.",
|
|
80
|
+
),
|
|
43
81
|
),
|
|
44
82
|
mode: Flag.choice("mode", ["tables", "columns", "full", "relationships"]).pipe(
|
|
45
83
|
Flag.withDescription(
|
|
@@ -57,9 +95,10 @@ const schemaCommand = Command.make(
|
|
|
57
95
|
},
|
|
58
96
|
({ env, mode, table, format }) =>
|
|
59
97
|
Effect.gen(function* () {
|
|
98
|
+
const resolvedEnv = yield* resolveEnv(env);
|
|
60
99
|
const db = yield* DbService;
|
|
61
100
|
const result = yield* db.executeSchemaQuery(
|
|
62
|
-
|
|
101
|
+
resolvedEnv,
|
|
63
102
|
mode as SchemaMode,
|
|
64
103
|
Option.getOrUndefined(table),
|
|
65
104
|
);
|
package/src/db-tool/types.ts
CHANGED
package/src/gh-tool/errors.ts
CHANGED
|
@@ -7,6 +7,9 @@ export class GitHubCommandError extends Schema.TaggedErrorClass<GitHubCommandErr
|
|
|
7
7
|
command: Schema.String,
|
|
8
8
|
exitCode: Schema.Number,
|
|
9
9
|
stderr: Schema.String,
|
|
10
|
+
hint: Schema.optionalKey(Schema.String),
|
|
11
|
+
nextCommand: Schema.optionalKey(Schema.String),
|
|
12
|
+
retryable: Schema.optionalKey(Schema.Boolean),
|
|
10
13
|
},
|
|
11
14
|
) {}
|
|
12
15
|
|
|
@@ -16,11 +19,17 @@ export class GitHubNotFoundError extends Schema.TaggedErrorClass<GitHubNotFoundE
|
|
|
16
19
|
message: Schema.String,
|
|
17
20
|
identifier: Schema.String,
|
|
18
21
|
resource: Schema.String,
|
|
22
|
+
hint: Schema.optionalKey(Schema.String),
|
|
23
|
+
nextCommand: Schema.optionalKey(Schema.String),
|
|
24
|
+
retryable: Schema.optionalKey(Schema.Boolean),
|
|
19
25
|
},
|
|
20
26
|
) {}
|
|
21
27
|
|
|
22
28
|
export class GitHubAuthError extends Schema.TaggedErrorClass<GitHubAuthError>()("GitHubAuthError", {
|
|
23
29
|
message: Schema.String,
|
|
30
|
+
hint: Schema.optionalKey(Schema.String),
|
|
31
|
+
nextCommand: Schema.optionalKey(Schema.String),
|
|
32
|
+
retryable: Schema.optionalKey(Schema.Boolean),
|
|
24
33
|
}) {}
|
|
25
34
|
|
|
26
35
|
export class GitHubMergeError extends Schema.TaggedErrorClass<GitHubMergeError>()(
|
|
@@ -28,6 +37,9 @@ export class GitHubMergeError extends Schema.TaggedErrorClass<GitHubMergeError>(
|
|
|
28
37
|
{
|
|
29
38
|
message: Schema.String,
|
|
30
39
|
reason: Schema.Literals(["conflicts", "checks_failing", "branch_protected", "unknown"]),
|
|
40
|
+
hint: Schema.optionalKey(Schema.String),
|
|
41
|
+
nextCommand: Schema.optionalKey(Schema.String),
|
|
42
|
+
retryable: Schema.optionalKey(Schema.Boolean),
|
|
31
43
|
},
|
|
32
44
|
) {}
|
|
33
45
|
|
|
@@ -36,6 +48,9 @@ export class GitHubTimeoutError extends Schema.TaggedErrorClass<GitHubTimeoutErr
|
|
|
36
48
|
{
|
|
37
49
|
message: Schema.String,
|
|
38
50
|
timeoutMs: Schema.Number,
|
|
51
|
+
hint: Schema.optionalKey(Schema.String),
|
|
52
|
+
nextCommand: Schema.optionalKey(Schema.String),
|
|
53
|
+
retryable: Schema.optionalKey(Schema.Boolean),
|
|
39
54
|
},
|
|
40
55
|
) {}
|
|
41
56
|
|
package/src/gh-tool/index.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { Command } from "effect/unstable/cli";
|
|
|
3
3
|
import { BunRuntime, BunServices } from "@effect/platform-bun";
|
|
4
4
|
import { Effect, Layer } from "effect";
|
|
5
5
|
|
|
6
|
-
import { renderCauseToStderr, VERSION } from "
|
|
6
|
+
import { renderCauseToStderr, VERSION } from "#src/shared";
|
|
7
7
|
import {
|
|
8
8
|
issueListCommand,
|
|
9
9
|
issueViewCommand,
|
|
@@ -30,6 +30,8 @@ import {
|
|
|
30
30
|
prChecksCommand,
|
|
31
31
|
prChecksFailedCommand,
|
|
32
32
|
prRerunChecksCommand,
|
|
33
|
+
prReplyAndResolveCommand,
|
|
34
|
+
prReviewTriageCommand,
|
|
33
35
|
} from "./pr/index";
|
|
34
36
|
import { repoInfoCommand, repoListCommand, repoSearchCodeCommand } from "./repo";
|
|
35
37
|
import { GitHubService } from "./service";
|
|
@@ -64,6 +66,8 @@ const prCommand = Command.make("pr", {}).pipe(
|
|
|
64
66
|
prChecksCommand,
|
|
65
67
|
prChecksFailedCommand,
|
|
66
68
|
prRerunChecksCommand,
|
|
69
|
+
prReplyAndResolveCommand,
|
|
70
|
+
prReviewTriageCommand,
|
|
67
71
|
]),
|
|
68
72
|
);
|
|
69
73
|
|
package/src/gh-tool/issue.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Command, Flag } from "effect/unstable/cli";
|
|
2
2
|
import { Effect, Option } from "effect";
|
|
3
3
|
|
|
4
|
-
import { formatOption, logFormatted } from "
|
|
4
|
+
import { formatOption, logFormatted } from "#src/shared";
|
|
5
5
|
import { GitHubCommandError } from "./errors";
|
|
6
6
|
import { GitHubService } from "./service";
|
|
7
7
|
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { Command, Flag } from "effect/unstable/cli";
|
|
2
2
|
import { Effect, Option } from "effect";
|
|
3
3
|
|
|
4
|
-
import type { PRStatusResult } from "
|
|
4
|
+
import type { PRStatusResult } from "#src/gh-tool/types";
|
|
5
5
|
|
|
6
|
-
import { formatOption, logFormatted } from "
|
|
6
|
+
import { formatOption, logFormatted } from "#src/shared";
|
|
7
7
|
import {
|
|
8
8
|
CI_CHECK_WATCH_TIMEOUT_MS,
|
|
9
9
|
DEFAULT_DELETE_BRANCH,
|
|
10
10
|
DEFAULT_MERGE_STRATEGY,
|
|
11
11
|
MERGE_STRATEGIES,
|
|
12
|
-
} from "
|
|
12
|
+
} from "#src/gh-tool/config";
|
|
13
13
|
|
|
14
14
|
import {
|
|
15
15
|
createPR,
|
|
@@ -430,3 +430,58 @@ export const prSubmitReviewCommand = Command.make(
|
|
|
430
430
|
"Submit a pending review as COMMENT (auto-detects your pending review if --review-id is omitted)",
|
|
431
431
|
),
|
|
432
432
|
);
|
|
433
|
+
|
|
434
|
+
export const prReviewTriageCommand = Command.make(
|
|
435
|
+
"review-triage",
|
|
436
|
+
{
|
|
437
|
+
format: formatOption,
|
|
438
|
+
pr: Flag.integer("pr").pipe(
|
|
439
|
+
Flag.withDescription("PR number (default: current branch PR)"),
|
|
440
|
+
Flag.optional,
|
|
441
|
+
),
|
|
442
|
+
},
|
|
443
|
+
({ format, pr }) =>
|
|
444
|
+
Effect.gen(function* () {
|
|
445
|
+
const prNumber = Option.getOrNull(pr);
|
|
446
|
+
const [info, threads, summary, checks] = yield* Effect.all([
|
|
447
|
+
viewPR(prNumber),
|
|
448
|
+
fetchThreads(prNumber, true),
|
|
449
|
+
fetchDiscussionSummary(prNumber),
|
|
450
|
+
fetchChecks(prNumber, false, false, 0),
|
|
451
|
+
]);
|
|
452
|
+
yield* logFormatted({ info, unresolvedThreads: threads, summary, checks }, format);
|
|
453
|
+
}),
|
|
454
|
+
).pipe(
|
|
455
|
+
Command.withDescription(
|
|
456
|
+
"Composite: PR info + unresolved threads + discussion summary + checks status in one call",
|
|
457
|
+
),
|
|
458
|
+
);
|
|
459
|
+
|
|
460
|
+
export const prReplyAndResolveCommand = Command.make(
|
|
461
|
+
"reply-and-resolve",
|
|
462
|
+
{
|
|
463
|
+
body: Flag.string("body").pipe(Flag.withDescription("Reply body text")),
|
|
464
|
+
commentId: Flag.integer("comment-id").pipe(
|
|
465
|
+
Flag.withDescription("ID of the comment to reply to"),
|
|
466
|
+
),
|
|
467
|
+
format: formatOption,
|
|
468
|
+
pr: Flag.integer("pr").pipe(
|
|
469
|
+
Flag.withDescription("PR number (default: current branch PR)"),
|
|
470
|
+
Flag.optional,
|
|
471
|
+
),
|
|
472
|
+
threadId: Flag.string("thread-id").pipe(
|
|
473
|
+
Flag.withDescription("GraphQL node ID of the thread to resolve"),
|
|
474
|
+
),
|
|
475
|
+
},
|
|
476
|
+
({ body, commentId, format, pr, threadId }) =>
|
|
477
|
+
Effect.gen(function* () {
|
|
478
|
+
const prNumber = Option.getOrNull(pr);
|
|
479
|
+
const replyResult = yield* replyToComment(prNumber, commentId, body);
|
|
480
|
+
const resolveResult = yield* resolveThread(threadId);
|
|
481
|
+
yield* logFormatted({ reply: replyResult, resolve: resolveResult }, format);
|
|
482
|
+
}),
|
|
483
|
+
).pipe(
|
|
484
|
+
Command.withDescription(
|
|
485
|
+
"Composite: reply to a review comment and resolve its thread in one call",
|
|
486
|
+
),
|
|
487
|
+
);
|
package/src/gh-tool/pr/core.ts
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import { Console, Effect, Option } from "effect";
|
|
2
2
|
|
|
3
|
-
import type {
|
|
3
|
+
import type {
|
|
4
|
+
BranchPRDetail,
|
|
5
|
+
CheckResult,
|
|
6
|
+
MergeResult,
|
|
7
|
+
MergeStrategy,
|
|
8
|
+
PRInfo,
|
|
9
|
+
} from "#src/gh-tool/types";
|
|
4
10
|
|
|
5
|
-
import { GitHubCommandError, GitHubMergeError, GitHubTimeoutError } from "
|
|
6
|
-
import { GitHubService } from "
|
|
11
|
+
import { GitHubCommandError, GitHubMergeError, GitHubTimeoutError } from "#src/gh-tool/errors";
|
|
12
|
+
import { GitHubService } from "#src/gh-tool/service";
|
|
7
13
|
|
|
8
14
|
import type { ButStatusJson, PRViewJsonResult } from "./helpers";
|
|
9
15
|
import { runLocalCommand } from "./helpers";
|
|
@@ -155,7 +161,7 @@ export const detectPRStatus = Effect.fn("pr.detectPRStatus")(function* () {
|
|
|
155
161
|
{ concurrency: "unbounded" },
|
|
156
162
|
);
|
|
157
163
|
|
|
158
|
-
const foundPrs = branchResults.
|
|
164
|
+
const foundPrs = branchResults.flatMap((r) => (r.openPr === null ? [] : [r.openPr]));
|
|
159
165
|
|
|
160
166
|
if (foundPrs.length === 0) {
|
|
161
167
|
const branchDetails: BranchPRDetail[] = branchResults.map((r) => ({
|
|
@@ -172,7 +178,7 @@ export const detectPRStatus = Effect.fn("pr.detectPRStatus")(function* () {
|
|
|
172
178
|
if (foundPrs.length === 1) {
|
|
173
179
|
return {
|
|
174
180
|
mode: "single" as const,
|
|
175
|
-
pr: foundPrs[0]
|
|
181
|
+
pr: foundPrs[0] as PRInfo,
|
|
176
182
|
};
|
|
177
183
|
}
|
|
178
184
|
|
|
@@ -205,7 +211,11 @@ export const createPR = Effect.fn("pr.createPR")(function* (opts: {
|
|
|
205
211
|
"--limit",
|
|
206
212
|
"1",
|
|
207
213
|
])
|
|
208
|
-
.pipe(
|
|
214
|
+
.pipe(
|
|
215
|
+
Effect.map((prs) =>
|
|
216
|
+
prs.length > 0 ? Option.some(prs[0] as PRInfo) : Option.none<PRInfo>(),
|
|
217
|
+
),
|
|
218
|
+
)
|
|
209
219
|
: gh
|
|
210
220
|
.runGhJson<{ number: number; url: string }>(["pr", "view", "--json", "number,url"])
|
|
211
221
|
.pipe(Effect.option);
|
|
@@ -258,7 +268,7 @@ export const createPR = Effect.fn("pr.createPR")(function* (opts: {
|
|
|
258
268
|
"1",
|
|
259
269
|
]);
|
|
260
270
|
if (prs.length > 0) {
|
|
261
|
-
return prs[0]
|
|
271
|
+
return prs[0] as PRInfo;
|
|
262
272
|
}
|
|
263
273
|
|
|
264
274
|
return yield* Effect.fail(
|
|
@@ -324,6 +334,8 @@ export const mergePR = Effect.fn("pr.mergePR")(function* (opts: {
|
|
|
324
334
|
new GitHubMergeError({
|
|
325
335
|
message: `PR #${opts.pr} has merge conflicts`,
|
|
326
336
|
reason: "conflicts",
|
|
337
|
+
hint: "Resolve merge conflicts locally, push the fix, then retry the merge.",
|
|
338
|
+
nextCommand: `gh pr diff ${opts.pr}`,
|
|
327
339
|
}),
|
|
328
340
|
);
|
|
329
341
|
}
|
|
@@ -333,6 +345,9 @@ export const mergePR = Effect.fn("pr.mergePR")(function* (opts: {
|
|
|
333
345
|
new GitHubMergeError({
|
|
334
346
|
message: `PR #${opts.pr} has failing required checks`,
|
|
335
347
|
reason: "checks_failing",
|
|
348
|
+
hint: "Wait for CI checks to pass or investigate failures before merging.",
|
|
349
|
+
nextCommand: `agent-tools-gh pr checks --pr ${opts.pr}`,
|
|
350
|
+
retryable: true,
|
|
336
351
|
}),
|
|
337
352
|
);
|
|
338
353
|
}
|
|
@@ -342,6 +357,7 @@ export const mergePR = Effect.fn("pr.mergePR")(function* (opts: {
|
|
|
342
357
|
new GitHubMergeError({
|
|
343
358
|
message: `PR #${opts.pr} targets a protected branch`,
|
|
344
359
|
reason: "branch_protected",
|
|
360
|
+
hint: "This branch has protection rules. Ensure required reviews and checks are satisfied, or ask a repo admin.",
|
|
345
361
|
}),
|
|
346
362
|
);
|
|
347
363
|
}
|
|
@@ -350,6 +366,8 @@ export const mergePR = Effect.fn("pr.mergePR")(function* (opts: {
|
|
|
350
366
|
new GitHubMergeError({
|
|
351
367
|
message: `Failed to merge PR #${opts.pr}: ${error.stderr}`,
|
|
352
368
|
reason: "unknown",
|
|
369
|
+
hint: "Check the PR state and branch protections. The PR may already be merged or closed.",
|
|
370
|
+
nextCommand: `agent-tools-gh pr view --pr ${opts.pr}`,
|
|
353
371
|
}),
|
|
354
372
|
);
|
|
355
373
|
}),
|
|
@@ -426,6 +444,9 @@ export const fetchChecks = Effect.fn("pr.fetchChecks")(function* (
|
|
|
426
444
|
new GitHubTimeoutError({
|
|
427
445
|
message: `CI check monitoring timed out after ${timeoutSeconds}s`,
|
|
428
446
|
timeoutMs,
|
|
447
|
+
hint: "CI checks are still running. Retry with a longer --timeout or check status manually.",
|
|
448
|
+
nextCommand: `agent-tools-gh pr checks${pr !== null ? ` --pr ${pr}` : ""}`,
|
|
449
|
+
retryable: true,
|
|
429
450
|
}),
|
|
430
451
|
),
|
|
431
452
|
}),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
|
|
2
2
|
import { Effect, Stream } from "effect";
|
|
3
3
|
|
|
4
|
-
import { GitHubCommandError } from "
|
|
4
|
+
import { GitHubCommandError } from "#src/gh-tool/errors";
|
|
5
5
|
|
|
6
6
|
export type LocalCommandResult = {
|
|
7
7
|
stdout: string;
|
package/src/gh-tool/pr/index.ts
CHANGED
|
@@ -11,9 +11,11 @@ export {
|
|
|
11
11
|
prMergeCommand,
|
|
12
12
|
prReplyCommand,
|
|
13
13
|
prRerunChecksCommand,
|
|
14
|
+
prReplyAndResolveCommand,
|
|
14
15
|
prResolveCommand,
|
|
15
16
|
prStatusCommand,
|
|
16
17
|
prSubmitReviewCommand,
|
|
17
18
|
prThreadsCommand,
|
|
19
|
+
prReviewTriageCommand,
|
|
18
20
|
prViewCommand,
|
|
19
21
|
} from "./commands";
|
package/src/gh-tool/pr/review.ts
CHANGED
|
@@ -7,10 +7,10 @@ import type {
|
|
|
7
7
|
IsoTimestamp,
|
|
8
8
|
ReviewComment,
|
|
9
9
|
ReviewThread,
|
|
10
|
-
} from "
|
|
10
|
+
} from "#src/gh-tool/types";
|
|
11
11
|
|
|
12
|
-
import { GitHubCommandError } from "
|
|
13
|
-
import { GitHubService } from "
|
|
12
|
+
import { GitHubCommandError } from "#src/gh-tool/errors";
|
|
13
|
+
import { GitHubService } from "#src/gh-tool/service";
|
|
14
14
|
|
|
15
15
|
import { viewPR } from "./core";
|
|
16
16
|
|
|
@@ -190,9 +190,12 @@ export const fetchThreads = Effect.fn("pr.fetchThreads")(function* (
|
|
|
190
190
|
const threads = response.repository.pullRequest.reviewThreads.nodes;
|
|
191
191
|
|
|
192
192
|
const mapped: ReviewThread[] = threads
|
|
193
|
-
.filter((node) => node.comments.nodes.length > 0)
|
|
194
193
|
.map((node) => {
|
|
195
|
-
const comment = node.comments.nodes[0]
|
|
194
|
+
const comment = node.comments.nodes[0];
|
|
195
|
+
if (!comment) {
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
|
|
196
199
|
return {
|
|
197
200
|
threadId: node.id,
|
|
198
201
|
commentId: comment.databaseId,
|
|
@@ -201,7 +204,8 @@ export const fetchThreads = Effect.fn("pr.fetchThreads")(function* (
|
|
|
201
204
|
body: comment.body,
|
|
202
205
|
isResolved: node.isResolved,
|
|
203
206
|
};
|
|
204
|
-
})
|
|
207
|
+
})
|
|
208
|
+
.filter((thread): thread is ReviewThread => thread !== null);
|
|
205
209
|
|
|
206
210
|
return unresolvedOnly ? mapped.filter((t) => !t.isResolved) : mapped;
|
|
207
211
|
});
|
package/src/gh-tool/repo.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Command, Flag } from "effect/unstable/cli";
|
|
2
2
|
import { Effect, Option } from "effect";
|
|
3
3
|
|
|
4
|
-
import { formatOption, logFormatted } from "
|
|
4
|
+
import { formatOption, logFormatted } from "#src/shared";
|
|
5
5
|
import { GitHubCommandError } from "./errors";
|
|
6
6
|
import { GitHubService } from "./service";
|
|
7
7
|
|
package/src/gh-tool/service.ts
CHANGED
|
@@ -66,6 +66,8 @@ export class GitHubService extends ServiceMap.Service<
|
|
|
66
66
|
command: `gh ${args.join(" ")}`,
|
|
67
67
|
exitCode: -1,
|
|
68
68
|
stderr: `Command execution failed: ${String(platformError)}`,
|
|
69
|
+
hint: "Ensure the 'gh' CLI is installed and available on PATH.",
|
|
70
|
+
nextCommand: "gh --version",
|
|
69
71
|
}),
|
|
70
72
|
),
|
|
71
73
|
);
|
|
@@ -80,6 +82,8 @@ export class GitHubService extends ServiceMap.Service<
|
|
|
80
82
|
) {
|
|
81
83
|
return yield* new GitHubAuthError({
|
|
82
84
|
message: "GitHub CLI not authenticated. Run 'gh auth login'.",
|
|
85
|
+
hint: "Authenticate with GitHub CLI or set GITHUB_TOKEN environment variable.",
|
|
86
|
+
nextCommand: "gh auth login",
|
|
83
87
|
});
|
|
84
88
|
}
|
|
85
89
|
|
|
@@ -91,6 +95,7 @@ export class GitHubService extends ServiceMap.Service<
|
|
|
91
95
|
message: result.stderr,
|
|
92
96
|
resource: "unknown",
|
|
93
97
|
identifier: "unknown",
|
|
98
|
+
hint: "Verify the resource exists and you have access. Check repository owner/name spelling.",
|
|
94
99
|
});
|
|
95
100
|
}
|
|
96
101
|
|
package/src/gh-tool/workflow.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Command, Flag } from "effect/unstable/cli";
|
|
2
2
|
import { Console, Effect, Option } from "effect";
|
|
3
3
|
|
|
4
|
-
import { formatOption, logFormatted } from "
|
|
4
|
+
import { formatOption, logFormatted } from "#src/shared";
|
|
5
5
|
import { GitHubCommandError, GitHubNotFoundError } from "./errors";
|
|
6
6
|
import { GitHubService } from "./service";
|
|
7
7
|
|
|
@@ -287,6 +287,8 @@ const resolveJobId = Effect.fn("workflow.resolveJobId")(function* (runId: number
|
|
|
287
287
|
command: "workflow job-logs",
|
|
288
288
|
exitCode: 1,
|
|
289
289
|
stderr: "",
|
|
290
|
+
hint: `Multiple jobs match "${jobName}". Use the exact job name from the list above.`,
|
|
291
|
+
nextCommand: `agent-tools-gh workflow jobs --run ${runId}`,
|
|
290
292
|
});
|
|
291
293
|
}
|
|
292
294
|
|
|
@@ -294,6 +296,8 @@ const resolveJobId = Effect.fn("workflow.resolveJobId")(function* (runId: number
|
|
|
294
296
|
message: `Job "${jobName}" not found in run ${runId}. Available jobs: ${jobs.map((j) => j.name).join(", ")}`,
|
|
295
297
|
identifier: jobName,
|
|
296
298
|
resource: "job",
|
|
299
|
+
hint: "Use one of the available job names listed above. Run the jobs command to see all jobs.",
|
|
300
|
+
nextCommand: `agent-tools-gh workflow jobs --run ${runId}`,
|
|
297
301
|
});
|
|
298
302
|
});
|
|
299
303
|
|
package/src/k8s-tool/errors.ts
CHANGED
|
@@ -3,6 +3,9 @@ import { Schema } from "effect";
|
|
|
3
3
|
export class K8sContextError extends Schema.TaggedErrorClass<K8sContextError>()("K8sContextError", {
|
|
4
4
|
message: Schema.String,
|
|
5
5
|
clusterId: Schema.String,
|
|
6
|
+
hint: Schema.optionalKey(Schema.String),
|
|
7
|
+
nextCommand: Schema.optionalKey(Schema.String),
|
|
8
|
+
retryable: Schema.optionalKey(Schema.Boolean),
|
|
6
9
|
}) {}
|
|
7
10
|
|
|
8
11
|
export class K8sCommandError extends Schema.TaggedErrorClass<K8sCommandError>()("K8sCommandError", {
|
|
@@ -10,12 +13,18 @@ export class K8sCommandError extends Schema.TaggedErrorClass<K8sCommandError>()(
|
|
|
10
13
|
command: Schema.String,
|
|
11
14
|
exitCode: Schema.optionalKey(Schema.Number),
|
|
12
15
|
stderr: Schema.optionalKey(Schema.String),
|
|
16
|
+
hint: Schema.optionalKey(Schema.String),
|
|
17
|
+
nextCommand: Schema.optionalKey(Schema.String),
|
|
18
|
+
retryable: Schema.optionalKey(Schema.Boolean),
|
|
13
19
|
}) {}
|
|
14
20
|
|
|
15
21
|
export class K8sTimeoutError extends Schema.TaggedErrorClass<K8sTimeoutError>()("K8sTimeoutError", {
|
|
16
22
|
message: Schema.String,
|
|
17
23
|
command: Schema.String,
|
|
18
24
|
timeoutMs: Schema.Number,
|
|
25
|
+
hint: Schema.optionalKey(Schema.String),
|
|
26
|
+
nextCommand: Schema.optionalKey(Schema.String),
|
|
27
|
+
retryable: Schema.optionalKey(Schema.Boolean),
|
|
19
28
|
}) {}
|
|
20
29
|
|
|
21
30
|
export type K8sError = K8sContextError | K8sCommandError | K8sTimeoutError;
|