@blogic-cz/agent-tools 0.14.36 → 0.14.38

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.14.36",
3
+ "version": "0.14.38",
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",
@@ -95,6 +95,29 @@
95
95
  "repo": {
96
96
  "type": "string",
97
97
  "description": "Repository name."
98
+ },
99
+ "prTitle": {
100
+ "$ref": "#/definitions/GitHubPrTitlePolicy"
101
+ }
102
+ }
103
+ },
104
+ "GitHubPrTitlePolicy": {
105
+ "description": "Optional pull request title validation policy for this repository profile.",
106
+ "type": "object",
107
+ "additionalProperties": false,
108
+ "required": ["pattern", "expected"],
109
+ "properties": {
110
+ "pattern": {
111
+ "type": "string",
112
+ "description": "JavaScript regular expression source that valid PR titles must match."
113
+ },
114
+ "expected": {
115
+ "type": "string",
116
+ "description": "Human-readable required title format shown when validation fails."
117
+ },
118
+ "example": {
119
+ "type": "string",
120
+ "description": "Example valid title shown when validation fails."
98
121
  }
99
122
  }
100
123
  },
@@ -11,6 +11,7 @@ export type {
11
11
  AuditConfig,
12
12
  CliToolOverride,
13
13
  CredentialGuardConfig,
14
+ GitHubPrTitlePolicy,
14
15
  GitHubRepoConfig,
15
16
  } from "./types";
16
17
 
@@ -141,6 +141,13 @@ const AuditConfigSchema = Schema.Struct({
141
141
  const GitHubRepoConfigSchema = Schema.Struct({
142
142
  owner: Schema.String,
143
143
  repo: Schema.String,
144
+ prTitle: Schema.optionalKey(
145
+ Schema.Struct({
146
+ pattern: Schema.String,
147
+ expected: Schema.String,
148
+ example: Schema.optionalKey(Schema.String),
149
+ }),
150
+ ),
144
151
  });
145
152
 
146
153
  const KNOWN_TOP_LEVEL_KEYS = new Set([
@@ -154,9 +154,16 @@ export type AuditConfig = {
154
154
  };
155
155
 
156
156
  /** Single GitHub repository configuration */
157
+ export type GitHubPrTitlePolicy = {
158
+ pattern: string;
159
+ expected: string;
160
+ example?: string;
161
+ };
162
+
157
163
  export type GitHubRepoConfig = {
158
164
  owner: string;
159
165
  repo: string;
166
+ prTitle?: GitHubPrTitlePolicy;
160
167
  };
161
168
 
162
169
  /**
@@ -21,6 +21,45 @@ import { runLocalCommand } from "./helpers";
21
21
  const CHECK_JSON_FIELDS = "name,state,bucket,link";
22
22
  const GITHUB_ACTIONS_RUN_ID_RE = /github\.com\/[^/]+\/[^/]+\/actions\/runs\/(\d+)/;
23
23
 
24
+ const validatePRTitle = Effect.fn("pr.validatePRTitle")(function* (title: string) {
25
+ const gh = yield* GitHubService;
26
+ const repoConfig = yield* gh.getRepoConfig();
27
+ const policy = repoConfig?.prTitle;
28
+
29
+ if (!policy) {
30
+ return;
31
+ }
32
+
33
+ const pattern = yield* Effect.try({
34
+ try: () => new RegExp(policy.pattern),
35
+ catch: (error) =>
36
+ new GitHubCommandError({
37
+ command: "pr title validation",
38
+ exitCode: 1,
39
+ stderr: `Invalid PR title policy regex: ${error instanceof Error ? error.message : String(error)}`,
40
+ message: "Invalid PR title policy regex",
41
+ }),
42
+ });
43
+
44
+ if (pattern.test(title)) {
45
+ return;
46
+ }
47
+
48
+ const lines = [
49
+ "PR title does not match the required format.",
50
+ `Got: ${title}`,
51
+ `Expected: ${policy.expected}`,
52
+ ...(policy.example ? [`Example: ${policy.example}`] : []),
53
+ ];
54
+
55
+ return yield* new GitHubCommandError({
56
+ command: "pr title validation",
57
+ exitCode: 1,
58
+ stderr: lines.join("\n"),
59
+ message: lines[0] ?? "PR title does not match the required format.",
60
+ });
61
+ });
62
+
24
63
  type WorkflowRunJobsForRerun = {
25
64
  databaseId: number;
26
65
  jobs: Array<{
@@ -246,7 +285,7 @@ export const viewPR = Effect.fn("pr.viewPR")(function* (prNumber: number | null)
246
285
  }
247
286
  args.push(
248
287
  "--json",
249
- "number,url,title,headRefName,baseRefName,state,isDraft,mergeable,body,reviewDecision,reviewRequests",
288
+ "number,url,title,headRefName,baseRefName,state,isDraft,mergeable,body,author,reviewDecision,reviewRequests",
250
289
  );
251
290
 
252
291
  const info = yield* gh.runGhJson<PRViewInfo>(args);
@@ -422,6 +461,7 @@ export const createPR = Effect.fn("pr.createPR")(function* (opts: {
422
461
  head: string | null;
423
462
  }) {
424
463
  const gh = yield* GitHubService;
464
+ yield* validatePRTitle(opts.title);
425
465
 
426
466
  // When --head is provided (e.g. GitButler workspace), use `gh pr list --head`
427
467
  // to find existing PR since `gh pr view` relies on the current git branch.
@@ -719,6 +759,10 @@ export const editPR = Effect.fn("pr.editPR")(function* (opts: {
719
759
  }
720
760
 
721
761
  const gh = yield* GitHubService;
762
+ if (opts.title !== null) {
763
+ yield* validatePRTitle(opts.title);
764
+ }
765
+
722
766
  const repo = yield* gh.getRepoInfo();
723
767
 
724
768
  const editArgs = [
@@ -1,11 +1,12 @@
1
1
  import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
2
2
  import { Context, Effect, Layer, Stream } from "effect";
3
3
 
4
+ import type { GitHubRepoConfig } from "#config";
4
5
  import type { RepoInfo } from "./types";
5
6
 
6
7
  import { GH_BINARY } from "./config";
7
8
  import { GitHubAuthError, GitHubCommandError, GitHubNotFoundError } from "./errors";
8
- import { ConfigService, resolveGitHubRepoTarget } from "#config";
9
+ import { ConfigService, getGitHubConfig, resolveGitHubRepoTarget } from "#config";
9
10
 
10
11
  type GhResult = {
11
12
  stdout: string;
@@ -24,6 +25,7 @@ export class GitHubService extends Context.Service<
24
25
  query: string,
25
26
  variables: Record<string, string | number | null>,
26
27
  ) => Effect.Effect<unknown, GhError>;
28
+ readonly getRepoConfig: () => Effect.Effect<GitHubRepoConfig | undefined, never>;
27
29
  readonly getRepoInfo: () => Effect.Effect<RepoInfo, GhError>;
28
30
  readonly withRepoTarget: <A, E, R>(
29
31
  target: string | null,
@@ -76,6 +78,22 @@ export class GitHubService extends Context.Service<
76
78
  return yield* effect.pipe(Effect.provideService(RepoTarget, resolved));
77
79
  });
78
80
 
81
+ const getRepoConfig = Effect.fn("GitHubService.getRepoConfig")(function* () {
82
+ const ghRepo = yield* RepoTarget;
83
+ const repos = config?.github;
84
+
85
+ if (repos && ghRepo) {
86
+ const repoConfig = Object.values(repos).find(
87
+ (repo) => `${repo.owner}/${repo.repo}` === ghRepo,
88
+ );
89
+ if (repoConfig) {
90
+ return repoConfig;
91
+ }
92
+ }
93
+
94
+ return getGitHubConfig(config);
95
+ });
96
+
79
97
  const executeGh = (args: string[]) =>
80
98
  Effect.scoped(
81
99
  Effect.gen(function* () {
@@ -242,7 +260,7 @@ export class GitHubService extends Context.Service<
242
260
  return repoInfo;
243
261
  });
244
262
 
245
- return { runGh, runGhJson, runGraphQL, getRepoInfo, withRepoTarget };
263
+ return { runGh, runGhJson, runGraphQL, getRepoConfig, getRepoInfo, withRepoTarget };
246
264
  }),
247
265
  ),
248
266
  );
@@ -19,6 +19,7 @@ export type ReviewRequest =
19
19
 
20
20
  export type PRViewInfo = PRInfo & {
21
21
  body: string;
22
+ author: { login: string; is_bot: boolean };
22
23
  reviewDecision: "APPROVED" | "CHANGES_REQUESTED" | "REVIEW_REQUIRED" | "";
23
24
  reviewRequests: ReviewRequest[];
24
25
  };