@blogic-cz/agent-tools 0.8.16 → 0.8.18

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blogic-cz/agent-tools",
3
- "version": "0.8.16",
3
+ "version": "0.8.18",
4
4
  "description": "CLI tools for AI coding agent workflows — GitHub, database, Kubernetes, Azure DevOps, logs, sessions, and audit",
5
5
  "keywords": [
6
6
  "agent",
@@ -116,22 +116,26 @@
116
116
  "lint": "oxlint -c ./.oxlintrc.json --deny-warnings",
117
117
  "lint:fix": "oxlint -c ./.oxlintrc.json --fix",
118
118
  "session-tool": "bun src/session-tool/index.ts",
119
+ "update:packages": "bun update -i -r",
119
120
  "update:skills": "bun run .agents/skills/update-packages/references/skills-update-local.ts",
120
121
  "test": "vitest run"
121
122
  },
122
123
  "dependencies": {
123
- "@effect/platform-bun": "4.0.0-beta.25",
124
+ "@effect/platform-bun": "4.0.0-beta.48",
124
125
  "@toon-format/toon": "2.1.0",
125
- "effect": "4.0.0-beta.25"
126
+ "effect": "4.0.0-beta.48"
126
127
  },
127
128
  "devDependencies": {
128
- "@effect/language-service": "0.77.0",
129
- "@effect/vitest": "4.0.0-beta.25",
130
- "@types/bun": "1.3.9",
131
- "oxfmt": "0.35.0",
132
- "oxlint": "1.50.0",
133
- "typescript": "5.9.3",
134
- "vitest": "^4.0.18"
129
+ "@effect/language-service": "0.85.1",
130
+ "@effect/vitest": "4.0.0-beta.48",
131
+ "@types/bun": "1.3.12",
132
+ "oxfmt": "0.44.0",
133
+ "oxlint": "1.59.0",
134
+ "typescript": "6.0.2",
135
+ "vitest": "^4.1.4"
136
+ },
137
+ "overrides": {
138
+ "@effect/platform-node-shared": "4.0.0-beta.48"
135
139
  },
136
140
  "engines": {
137
141
  "bun": ">=1.0.0"
@@ -1,5 +1,5 @@
1
1
  import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
2
- import { Effect, Layer, ServiceMap, Stream, Option } from "effect";
2
+ import { Context, Effect, Layer, Option, Stream } from "effect";
3
3
 
4
4
  import type { InvokeParams } from "./types";
5
5
  import type { AzureConfig } from "#config/types";
@@ -10,7 +10,7 @@ import { isCommandAllowed, isInvokeAllowed } from "./security";
10
10
  import { transformCmdOutput } from "./transformers";
11
11
  import { ConfigService, getToolConfig } from "#config";
12
12
 
13
- export class AzService extends ServiceMap.Service<
13
+ export class AzService extends Context.Service<
14
14
  AzService,
15
15
  {
16
16
  readonly runCommand: (
@@ -1,6 +1,6 @@
1
1
  import { dirname } from "node:path";
2
2
 
3
- import { Data, Effect, Layer, Schema, ServiceMap } from "effect";
3
+ import { Context, Data, Effect, Layer, Schema } from "effect";
4
4
 
5
5
  import type { AgentToolsConfig, GitHubRepoConfig } from "./types";
6
6
 
@@ -159,10 +159,9 @@ export async function loadConfig(): Promise<AgentToolsConfig | undefined> {
159
159
  return decodeConfig(parsed, configPath);
160
160
  }
161
161
 
162
- export class ConfigService extends ServiceMap.Service<
163
- ConfigService,
164
- AgentToolsConfig | undefined
165
- >()("@agent-tools/ConfigService") {}
162
+ export class ConfigService extends Context.Service<ConfigService, AgentToolsConfig | undefined>()(
163
+ "@agent-tools/ConfigService",
164
+ ) {}
166
165
 
167
166
  export class ConfigLoadError extends Data.TaggedError("ConfigLoadError")<{
168
167
  readonly cause: unknown;
@@ -1,4 +1,4 @@
1
- import { Effect, Layer, ServiceMap } from "effect";
1
+ import { Context, Effect, Layer } from "effect";
2
2
 
3
3
  import { ConfigService, getToolConfig } from "#config";
4
4
  import type { DatabaseConfig } from "#config";
@@ -12,10 +12,9 @@ import type { DatabaseConfig } from "#config";
12
12
  * const dbConfig = yield* DbConfigService;
13
13
  * if (!dbConfig) { // no config }
14
14
  */
15
- export class DbConfigService extends ServiceMap.Service<
16
- DbConfigService,
17
- DatabaseConfig | undefined
18
- >()("@agent-tools/DbConfigService") {}
15
+ export class DbConfigService extends Context.Service<DbConfigService, DatabaseConfig | undefined>()(
16
+ "@agent-tools/DbConfigService",
17
+ ) {}
19
18
 
20
19
  /**
21
20
  * Creates a DbConfigService layer that resolves the database config
@@ -1,5 +1,5 @@
1
1
  import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
2
- import { Clock, Duration, Effect, Layer, Ref, ServiceMap, Stream } from "effect";
2
+ import { Clock, Context, Duration, Effect, Layer, Ref, Stream } from "effect";
3
3
 
4
4
  import type { DbConfig, QueryResult, SchemaMode } from "./types";
5
5
 
@@ -33,7 +33,7 @@ export function resolveDbAccessMode(
33
33
  };
34
34
  }
35
35
 
36
- export class DbService extends ServiceMap.Service<
36
+ export class DbService extends Context.Service<
37
37
  DbService,
38
38
  {
39
39
  readonly executeQuery: (env: string, sql: string) => Effect.Effect<QueryResult, DbError>;
@@ -1,4 +1,4 @@
1
- import { Console, Effect, Option } from "effect";
1
+ import { Console, Effect, Option, Result } from "effect";
2
2
 
3
3
  import type {
4
4
  BranchPRDetail,
@@ -20,6 +20,16 @@ import { runLocalCommand } from "./helpers";
20
20
  const CHECK_JSON_FIELDS = "name,state,bucket,link";
21
21
  const GITHUB_ACTIONS_RUN_ID_RE = /github\.com\/[^/]+\/[^/]+\/actions\/runs\/(\d+)/;
22
22
 
23
+ type WorkflowRunJobsForRerun = {
24
+ databaseId: number;
25
+ jobs: Array<{
26
+ databaseId: number;
27
+ name: string;
28
+ status: string;
29
+ conclusion: string | null;
30
+ }>;
31
+ };
32
+
23
33
  const buildChecksCommand = (pr: number | null, includeWatch: boolean): string =>
24
34
  `bun agent-tools-gh pr checks${pr !== null ? ` --pr ${pr}` : ""}${includeWatch ? " --watch" : ""}`;
25
35
 
@@ -36,6 +46,47 @@ const extractRunIdFromCheckLink = (link: string): number | null => {
36
46
  return Number.isFinite(runId) ? runId : null;
37
47
  };
38
48
 
49
+ const isFailedWorkflowJob = (job: { status: string; conclusion: string | null }) =>
50
+ job.conclusion === "failure" || job.status === "failure";
51
+
52
+ const getCheckJobNameCandidates = (checkName: string): string[] => {
53
+ const exact = checkName.trim();
54
+ const suffixParts = exact.split("/").map((part) => part.trim());
55
+ let suffix: string | undefined;
56
+ for (let index = suffixParts.length - 1; index >= 0; index -= 1) {
57
+ const part = suffixParts[index];
58
+ if (part !== undefined && part.length > 0) {
59
+ suffix = part;
60
+ break;
61
+ }
62
+ }
63
+
64
+ return [...new Set([exact, suffix].filter((value): value is string => value !== undefined))];
65
+ };
66
+
67
+ const resolveJobIdsForFailedChecks = (
68
+ checks: CheckResult[],
69
+ jobs: WorkflowRunJobsForRerun["jobs"],
70
+ ): number[] | null => {
71
+ const failedJobs = jobs.filter(isFailedWorkflowJob);
72
+ const jobIds = new Set<number>();
73
+
74
+ for (const check of checks) {
75
+ const candidates = getCheckJobNameCandidates(check.name);
76
+ const matches = failedJobs.filter((job) =>
77
+ candidates.some((candidate) => job.name.toLowerCase() === candidate.toLowerCase()),
78
+ );
79
+
80
+ if (matches.length !== 1) {
81
+ return null;
82
+ }
83
+
84
+ jobIds.add(matches[0].databaseId);
85
+ }
86
+
87
+ return [...jobIds];
88
+ };
89
+
39
90
  const fetchWorkflowRunFailureContext = Effect.fn("pr.fetchWorkflowRunFailureContext")(function* (
40
91
  runId: number,
41
92
  ) {
@@ -610,7 +661,7 @@ export const fetchChecks = Effect.fn("pr.fetchChecks")(function* (
610
661
  yield* gh.runGh(watchArgs).pipe(
611
662
  Effect.timeoutOrElse({
612
663
  duration: timeoutMs,
613
- onTimeout: () =>
664
+ orElse: () =>
614
665
  Effect.fail(
615
666
  new GitHubTimeoutError({
616
667
  message: `CI check monitoring timed out after ${timeoutSeconds}s`,
@@ -652,13 +703,18 @@ export const fetchChecksForCommand = Effect.fn("pr.fetchChecksForCommand")(funct
652
703
  }
653
704
 
654
705
  const watchedChecks = yield* fetchChecks(pr, true, failFast, timeoutSeconds).pipe(
655
- Effect.catchTag("GitHubCommandError", (error) =>
656
- Effect.succeed({ _tag: "command_error" as const, error }),
657
- ),
706
+ Effect.result,
707
+ Effect.flatMap((result) => {
708
+ if (Result.isFailure(result) && result.failure._tag !== "GitHubCommandError") {
709
+ return Effect.fail(result.failure);
710
+ }
711
+
712
+ return Effect.succeed(result);
713
+ }),
658
714
  );
659
715
 
660
- if (Array.isArray(watchedChecks)) {
661
- return watchedChecks;
716
+ if (Result.isSuccess(watchedChecks)) {
717
+ return watchedChecks.success;
662
718
  }
663
719
 
664
720
  const finalChecks = yield* fetchCheckResults(pr);
@@ -666,7 +722,7 @@ export const fetchChecksForCommand = Effect.fn("pr.fetchChecksForCommand")(funct
666
722
  return yield* buildFailedChecksReport(pr, finalChecks);
667
723
  }
668
724
 
669
- return yield* Effect.fail(watchedChecks.error);
725
+ return yield* Effect.fail(watchedChecks.failure);
670
726
  });
671
727
 
672
728
  export const rerunChecks = Effect.fn("pr.rerunChecks")(function* (
@@ -675,21 +731,20 @@ export const rerunChecks = Effect.fn("pr.rerunChecks")(function* (
675
731
  ) {
676
732
  const gh = yield* GitHubService;
677
733
 
678
- const checks = yield* gh.runGhJson<
679
- Array<{
680
- name: string;
681
- link: string;
682
- bucket: string;
683
- state: string;
684
- }>
685
- >(["pr", "checks", ...(pr !== null ? [String(pr)] : []), "--json", "name,link,bucket,state"]);
734
+ const checks = yield* fetchCheckResults(pr);
735
+
736
+ const targetChecks = failedOnly ? checks.filter((check) => check.bucket === "fail") : checks;
686
737
 
687
738
  // Extract unique GitHub Actions run IDs from links
688
739
  const runIds = new Set<string>();
689
- for (const check of failedOnly ? checks.filter((c) => c.bucket === "fail") : checks) {
690
- const match = check.link.match(/github\.com\/[^/]+\/[^/]+\/actions\/runs\/(\d+)/);
740
+ const checksByRun = new Map<string, CheckResult[]>();
741
+ for (const check of targetChecks) {
742
+ const match = check.link.match(GITHUB_ACTIONS_RUN_ID_RE);
691
743
  if (match?.[1]) {
692
744
  runIds.add(match[1]);
745
+ const existing = checksByRun.get(match[1]) ?? [];
746
+ existing.push(check);
747
+ checksByRun.set(match[1], existing);
693
748
  }
694
749
  }
695
750
 
@@ -707,11 +762,40 @@ export const rerunChecks = Effect.fn("pr.rerunChecks")(function* (
707
762
  success: boolean;
708
763
  }> = [];
709
764
  for (const runId of runIds) {
710
- const rerunArgs = failedOnly ? ["run", "rerun", runId, "--failed"] : ["run", "rerun", runId];
711
- const success = yield* gh.runGh(rerunArgs).pipe(
712
- Effect.map(() => true),
713
- Effect.catch(() => Effect.succeed(false)),
714
- );
765
+ const success = yield* Effect.gen(function* () {
766
+ if (!failedOnly) {
767
+ return yield* gh.runGh(["run", "rerun", runId]).pipe(
768
+ Effect.map(() => true),
769
+ Effect.catch(() => Effect.succeed(false)),
770
+ );
771
+ }
772
+
773
+ const checksForRun = checksByRun.get(runId) ?? [];
774
+ const run = yield* gh
775
+ .runGhJson<WorkflowRunJobsForRerun>(["run", "view", runId, "--json", "databaseId,jobs"])
776
+ .pipe(Effect.catchTag("GitHubCommandError", () => Effect.succeed(null)));
777
+
778
+ const jobIds = run === null ? null : resolveJobIdsForFailedChecks(checksForRun, run.jobs);
779
+ if (jobIds === null || jobIds.length === 0) {
780
+ return yield* gh.runGh(["run", "rerun", runId, "--failed"]).pipe(
781
+ Effect.map(() => true),
782
+ Effect.catch(() => Effect.succeed(false)),
783
+ );
784
+ }
785
+
786
+ const rerunResults = yield* Effect.forEach(
787
+ jobIds,
788
+ (jobId) =>
789
+ gh.runGh(["run", "rerun", "--job", String(jobId)]).pipe(
790
+ Effect.map(() => true),
791
+ Effect.catch(() => Effect.succeed(false)),
792
+ ),
793
+ { concurrency: 1 },
794
+ );
795
+
796
+ return rerunResults.every(Boolean);
797
+ });
798
+
715
799
  results.push({ runId, success });
716
800
  }
717
801
 
@@ -1,5 +1,5 @@
1
1
  import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
2
- import { Effect, Layer, ServiceMap, Stream } from "effect";
2
+ import { Context, Effect, Layer, Stream } from "effect";
3
3
 
4
4
  import type { RepoInfo } from "./types";
5
5
 
@@ -15,7 +15,7 @@ type GhResult = {
15
15
 
16
16
  type GhError = GitHubCommandError | GitHubAuthError | GitHubNotFoundError;
17
17
 
18
- export class GitHubService extends ServiceMap.Service<
18
+ export class GitHubService extends Context.Service<
19
19
  GitHubService,
20
20
  {
21
21
  readonly runGh: (args: string[]) => Effect.Effect<GhResult, GhError>;
@@ -1,5 +1,5 @@
1
1
  import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
2
- import { Effect, Layer, Option, Ref, ServiceMap, Stream } from "effect";
2
+ import { Context, Effect, Layer, Option, Ref, Stream } from "effect";
3
3
 
4
4
  import type { CommandResult, Environment } from "./types";
5
5
 
@@ -13,7 +13,7 @@ import { ConfigService, getToolConfig } from "#config";
13
13
  import type { K8sConfig } from "#config";
14
14
  import { isKubectlCommandAllowed } from "./security";
15
15
 
16
- export class K8sService extends ServiceMap.Service<
16
+ export class K8sService extends Context.Service<
17
17
  K8sService,
18
18
  {
19
19
  readonly runCommand: (
@@ -1,5 +1,5 @@
1
1
  import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
2
- import { Effect, Layer, Result, ServiceMap, Stream } from "effect";
2
+ import { Context, Effect, Layer, Result, Stream } from "effect";
3
3
 
4
4
  import type { Environment, LogFile, ReadOptions } from "./types";
5
5
 
@@ -47,7 +47,7 @@ export const sanitizeShellArg = (input: string): string => `'${input.replace(/'/
47
47
 
48
48
  const readCommandOutput = (output: unknown): string => (typeof output === "string" ? output : "");
49
49
 
50
- export class LogsService extends ServiceMap.Service<
50
+ export class LogsService extends Context.Service<
51
51
  LogsService,
52
52
  {
53
53
  readonly listLogs: (env: Environment, profile?: string) => Effect.Effect<LogFile[], LogsError>;
@@ -1,4 +1,4 @@
1
- import { Effect, Layer, ServiceMap } from "effect";
1
+ import { Context, Effect, Layer } from "effect";
2
2
  import { existsSync } from "node:fs";
3
3
  import { homedir } from "node:os";
4
4
  import { join } from "node:path";
@@ -38,7 +38,7 @@ export const resolveSessionsPath = Effect.gen(function* () {
38
38
  /**
39
39
  * Context tag for resolved paths (cached during effect execution).
40
40
  */
41
- export class ResolvedPaths extends ServiceMap.Service<
41
+ export class ResolvedPaths extends Context.Service<
42
42
  ResolvedPaths,
43
43
  {
44
44
  readonly messagesPath: string;
@@ -1,4 +1,4 @@
1
- import { Effect, Layer, ServiceMap } from "effect";
1
+ import { Context, Effect, Layer } from "effect";
2
2
  import { readdir } from "node:fs/promises";
3
3
 
4
4
  import type { MessageSummary, SessionInfo } from "./types";
@@ -142,7 +142,7 @@ const readJsonFilesFlat = (dir: string): Effect.Effect<FileEntry[], SessionError
142
142
  }),
143
143
  });
144
144
 
145
- export class SessionService extends ServiceMap.Service<
145
+ export class SessionService extends Context.Service<
146
146
  SessionService,
147
147
  {
148
148
  readonly getSessionsForProject: (
@@ -3,7 +3,7 @@ import { mkdirSync } from "node:fs";
3
3
  import { homedir } from "node:os";
4
4
  import { basename, dirname, join } from "node:path";
5
5
 
6
- import { Cause, Effect, Layer, ServiceMap } from "effect";
6
+ import { Cause, Context, Effect, Layer } from "effect";
7
7
 
8
8
  import { loadConfig } from "#config";
9
9
 
@@ -65,7 +65,7 @@ type TableInfoRow = {
65
65
  name: string;
66
66
  };
67
67
 
68
- export class AuditService extends ServiceMap.Service<AuditService, AuditServiceShape>()(
68
+ export class AuditService extends Context.Service<AuditService, AuditServiceShape>()(
69
69
  "@agent-tools/AuditService",
70
70
  ) {}
71
71