@generacy-ai/workflow-engine 0.5.0 → 0.7.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.
@@ -0,0 +1,22 @@
1
+ import type { CiRun, CiVerdict } from '../../../types/github.js';
2
+ /**
3
+ * Aggregate a set of CI runs for a single head SHA into a three-state verdict.
4
+ *
5
+ * Pure, total, and never throws. Zero I/O.
6
+ *
7
+ * Precedence (per contracts/ci-verdict.md), after dropping the ignore-set
8
+ * (`skipped`, `neutral`):
9
+ * 1. any failing terminal conclusion → `not-passed`
10
+ * 2. any in-progress run (`status !== 'completed'` or `conclusion === null`) → `pending`
11
+ * 3. any `success` → `green`
12
+ * 4. otherwise → `pending`
13
+ *
14
+ * Unknown terminal conclusions are treated conservatively: they are not
15
+ * `success`, so they never contribute to a `green` verdict, and they fall
16
+ * through to `pending` (rule 4) unless a concrete success exists.
17
+ *
18
+ * Invariants: empty input and all-ignored input both yield `pending` — never
19
+ * `green` (SC-001). A `green` verdict requires at least one concrete `success`.
20
+ */
21
+ export declare function aggregateCiVerdict(runs: CiRun[]): CiVerdict;
22
+ //# sourceMappingURL=ci-verdict.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ci-verdict.d.ts","sourceRoot":"","sources":["../../../../src/actions/github/client/ci-verdict.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAqBjE;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,SAAS,CAgC3D"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Conclusions that are ignored entirely when computing merge readiness.
3
+ * A skipped or neutral run is NOT a pass — it means CI never ran for that job
4
+ * (skipped≠passed, #1133 / SC-001).
5
+ */
6
+ const IGNORED_CONCLUSIONS = new Set(['skipped', 'neutral']);
7
+ /**
8
+ * Terminal conclusions that block merge readiness.
9
+ */
10
+ const FAILING_CONCLUSIONS = new Set([
11
+ 'failure',
12
+ 'cancelled',
13
+ 'timed_out',
14
+ 'action_required',
15
+ 'startup_failure', // #1157 FR-006
16
+ 'stale', // #1157 FR-006
17
+ ]);
18
+ /**
19
+ * Aggregate a set of CI runs for a single head SHA into a three-state verdict.
20
+ *
21
+ * Pure, total, and never throws. Zero I/O.
22
+ *
23
+ * Precedence (per contracts/ci-verdict.md), after dropping the ignore-set
24
+ * (`skipped`, `neutral`):
25
+ * 1. any failing terminal conclusion → `not-passed`
26
+ * 2. any in-progress run (`status !== 'completed'` or `conclusion === null`) → `pending`
27
+ * 3. any `success` → `green`
28
+ * 4. otherwise → `pending`
29
+ *
30
+ * Unknown terminal conclusions are treated conservatively: they are not
31
+ * `success`, so they never contribute to a `green` verdict, and they fall
32
+ * through to `pending` (rule 4) unless a concrete success exists.
33
+ *
34
+ * Invariants: empty input and all-ignored input both yield `pending` — never
35
+ * `green` (SC-001). A `green` verdict requires at least one concrete `success`.
36
+ */
37
+ export function aggregateCiVerdict(runs) {
38
+ const relevant = runs.filter((run) => run.conclusion === null || !IGNORED_CONCLUSIONS.has(run.conclusion));
39
+ if (relevant.length === 0) {
40
+ return 'pending';
41
+ }
42
+ // Rule 1: any failing terminal conclusion blocks readiness.
43
+ if (relevant.some((run) => run.conclusion !== null && FAILING_CONCLUSIONS.has(run.conclusion))) {
44
+ return 'not-passed';
45
+ }
46
+ // Rule 2: any in-progress run keeps the verdict pending.
47
+ if (relevant.some((run) => run.status !== 'completed' || run.conclusion === null)) {
48
+ return 'pending';
49
+ }
50
+ // Rule 3: at least one concrete success and nothing failing/in-progress.
51
+ if (relevant.some((run) => run.conclusion === 'success')) {
52
+ return 'green';
53
+ }
54
+ // Rule 4: only unknown terminal conclusions remain — conservatively pending.
55
+ return 'pending';
56
+ }
57
+ //# sourceMappingURL=ci-verdict.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ci-verdict.js","sourceRoot":"","sources":["../../../../src/actions/github/client/ci-verdict.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,mBAAmB,GAAG,IAAI,GAAG,CAAS,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC;AAEpE;;GAEG;AACH,MAAM,mBAAmB,GAAG,IAAI,GAAG,CAAS;IAC1C,SAAS;IACT,WAAW;IACX,WAAW;IACX,iBAAiB;IACjB,iBAAiB,EAAE,eAAe;IAClC,OAAO,EAAE,eAAe;CACzB,CAAC,CAAC;AAEH;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAa;IAC9C,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAC1B,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,UAAU,KAAK,IAAI,IAAI,CAAC,mBAAmB,CAAC,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,CAC7E,CAAC;IAEF,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,4DAA4D;IAC5D,IACE,QAAQ,CAAC,IAAI,CACX,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,UAAU,KAAK,IAAI,IAAI,mBAAmB,CAAC,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,CAC5E,EACD,CAAC;QACD,OAAO,YAAY,CAAC;IACtB,CAAC;IAED,yDAAyD;IACzD,IACE,QAAQ,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,KAAK,WAAW,IAAI,GAAG,CAAC,UAAU,KAAK,IAAI,CAAC,EAC7E,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,yEAAyE;IACzE,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,UAAU,KAAK,SAAS,CAAC,EAAE,CAAC;QACzD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,6EAA6E;IAC7E,OAAO,SAAS,CAAC;AACnB,CAAC"}
@@ -3,7 +3,7 @@
3
3
  * Uses the GitHub CLI for all GitHub API operations and git for local operations.
4
4
  */
5
5
  import type { GitHubClient, IssueUpdate, PRCreate, PRUpdate, MergeResult, CommitResult, PushResult, GitStatus, LabelDefinition } from './interface.js';
6
- import type { Issue, PullRequest, Comment, Label, RepoInfo, Review, ReviewThread } from '../../../types/github.js';
6
+ import type { Issue, PullRequest, Comment, Label, RepoInfo, Review, ReviewThread, CreateReviewInput, PullRequestFile, CiRun } from '../../../types/github.js';
7
7
  /**
8
8
  * Thrown by `executeGh()` when the gh CLI's stderr signals HTTP 401 or 403.
9
9
  * Callers (label/PR-feedback monitors) catch this to drive auth-health state.
@@ -59,6 +59,9 @@ export declare class GhCliGitHubClient implements GitHubClient {
59
59
  getPRComments(owner: string, repo: string, number: number): Promise<Comment[]>;
60
60
  getPRReviewThreads(owner: string, repo: string, number: number): Promise<ReviewThread[]>;
61
61
  listReviews(owner: string, repo: string, prNumber: number): Promise<Review[]>;
62
+ createReview(owner: string, repo: string, prNumber: number, input: CreateReviewInput): Promise<Review>;
63
+ convertPullRequestToDraft(owner: string, repo: string, prNumber: number): Promise<void>;
64
+ listPullRequestFiles(owner: string, repo: string, prNumber: number): Promise<PullRequestFile[]>;
62
65
  replyToPRComment(owner: string, repo: string, number: number, commentId: number, body: string): Promise<Comment>;
63
66
  resolveReviewThread(threadId: string): Promise<void>;
64
67
  listOpenPullRequests(owner: string, repo: string): Promise<PullRequest[]>;
@@ -79,7 +82,7 @@ export declare class GhCliGitHubClient implements GitHubClient {
79
82
  checkout(branch: string): Promise<void>;
80
83
  stageFiles(files: string[]): Promise<void>;
81
84
  stageAll(): Promise<void>;
82
- commit(message: string): Promise<CommitResult>;
85
+ commit(message: string, pathspec?: string[]): Promise<CommitResult>;
83
86
  push(remote?: string, branch?: string, setUpstream?: boolean): Promise<PushResult>;
84
87
  fetch(remote?: string, prune?: boolean): Promise<void>;
85
88
  merge(branch: string, noCommit?: boolean): Promise<MergeResult>;
@@ -89,6 +92,7 @@ export declare class GhCliGitHubClient implements GitHubClient {
89
92
  success: boolean;
90
93
  conflicts: boolean;
91
94
  }>;
95
+ discardWorkingTreeChanges(excludePaths?: string[]): Promise<void>;
92
96
  getConflictedFiles(): Promise<string[]>;
93
97
  getDefaultBranch(): Promise<string>;
94
98
  getCommitsBetween(base: string, head: string): Promise<{
@@ -96,6 +100,9 @@ export declare class GhCliGitHubClient implements GitHubClient {
96
100
  message: string;
97
101
  }[]>;
98
102
  getFilesChangedBetween(base: string, head: string): Promise<string[]>;
103
+ getCurrentCommitSha(): Promise<string>;
104
+ getFilesChangedByOwnCommits(startRef: string): Promise<string[]>;
105
+ commitExistsInCheckout(sha: string): Promise<boolean>;
99
106
  /**
100
107
  * Fetch the head commit SHA for a branch/ref (#892).
101
108
  * Uses `gh api repos/{o}/{r}/commits/{ref} --jq .sha` and validates that the
@@ -103,6 +110,37 @@ export declare class GhCliGitHubClient implements GitHubClient {
103
110
  * 401 → `GhAuthError` via `executeGh` (existing #762 path).
104
111
  */
105
112
  getRefHeadSha(owner: string, repo: string, ref: string): Promise<string>;
113
+ /**
114
+ * Read CI runs for a commit SHA for merge-readiness aggregation (#1133).
115
+ *
116
+ * Primary: `gh api repos/{o}/{r}/commits/{sha}/check-runs` → source
117
+ * `check-runs`. On non-zero exit — including the `GhAuthError` (HTTP 401/403)
118
+ * that `executeGh` raises, which is the observed symptom of a token lacking
119
+ * `checks:read` (FR-002) — fall back to
120
+ * `gh api repos/{o}/{r}/actions/runs?branch={branch}` filtered client-side to
121
+ * the head SHA → source `actions-runs`.
122
+ *
123
+ * Both paths are paginated (`--paginate` + `per_page=100`): the check-runs
124
+ * endpoint caps at 30 results per page by default (same trap #1043 fixed for
125
+ * `listBranches`). Without pagination a head SHA with >30 checks would expose
126
+ * only page 1 to `aggregateCiVerdict`, so a failing or still-pending run past
127
+ * page 1 would be invisible and could yield a false `green` — the exact
128
+ * skipped≠passed safety hole this feature closes.
129
+ *
130
+ * Both paths normalize to `CiRun[]` consumable by `aggregateCiVerdict`
131
+ * unchanged (SC-004). Empty result → `{ runs: [], source }`. Non-zero exit on
132
+ * BOTH paths → throw with stderr (mirrors `getRefHeadSha`).
133
+ */
134
+ getCiRunsForSha(owner: string, repo: string, headSha: string, branch: string): Promise<{
135
+ runs: CiRun[];
136
+ source: 'check-runs' | 'actions-runs';
137
+ }>;
138
+ /**
139
+ * Parse newline-delimited `{status, conclusion}` JSON objects (jq stream
140
+ * output) into `CiRun[]`. Blank lines and unparseable lines are skipped.
141
+ * Unknown `conclusion` values are passed through as-is.
142
+ */
143
+ private parseCiRunLines;
106
144
  /**
107
145
  * List file names touched by a PR (`gh pr diff --name-only`, #892).
108
146
  * Non-zero exit → throw with stderr for visibility.
@@ -1 +1 @@
1
- {"version":3,"file":"gh-cli.d.ts","sourceRoot":"","sources":["../../../../src/actions/github/client/gh-cli.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"gh-cli.d.ts","sourceRoot":"","sources":["../../../../src/actions/github/client/gh-cli.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,8 @@
1
1
  import { executeCommand, parseJSONSafe } from '../../cli-utils.js';
2
+ import { writeFile, unlink } from 'node:fs/promises';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { randomUUID } from 'node:crypto';
2
6
  /**
3
7
  * Thrown by `executeGh()` when the gh CLI's stderr signals HTTP 401 or 403.
4
8
  * Callers (label/PR-feedback monitors) catch this to drive auth-health state.
@@ -573,6 +577,141 @@ export class GhCliGitHubClient {
573
577
  }
574
578
  return reviews;
575
579
  }
580
+ async createReview(owner, repo, prNumber, input) {
581
+ // The reviews endpoint accepts a nested `comments[]` array of objects,
582
+ // which `-f field=value` cannot express. The shared `executeCommand`
583
+ // wrapper ignores stdin (both the launcher and direct-spawn paths use
584
+ // `stdio: ['ignore', ...]`), so `--input -` is unavailable here. Write the
585
+ // JSON body to a temp file and pass `--input <file>` — same wire result.
586
+ const body = {
587
+ event: input.event,
588
+ body: input.body,
589
+ comments: (input.comments ?? []).map(c => ({
590
+ path: c.path,
591
+ line: c.line,
592
+ side: c.side ?? 'RIGHT',
593
+ body: c.body,
594
+ })),
595
+ };
596
+ const inputPath = join(tmpdir(), `generacy-review-${randomUUID()}.json`);
597
+ await writeFile(inputPath, JSON.stringify(body), 'utf8');
598
+ let result;
599
+ try {
600
+ result = await this.executeGh([
601
+ 'api',
602
+ '--method', 'POST',
603
+ `/repos/${owner}/${repo}/pulls/${prNumber}/reviews`,
604
+ '--input', inputPath,
605
+ ]);
606
+ }
607
+ finally {
608
+ await unlink(inputPath).catch(() => {
609
+ /* best-effort cleanup */
610
+ });
611
+ }
612
+ if (result.exitCode !== 0) {
613
+ throw new Error(`Failed to create review on PR #${prNumber}: ${result.stderr}`);
614
+ }
615
+ const data = parseJSONSafe(result.stdout);
616
+ if (!data) {
617
+ throw new Error(`Failed to parse created review response on PR #${prNumber}`);
618
+ }
619
+ const review = {
620
+ id: data.id,
621
+ user: { login: data.user?.login ?? '' },
622
+ body: data.body ?? '',
623
+ state: data.state,
624
+ submittedAt: data.submitted_at,
625
+ };
626
+ if (typeof data.author_association === 'string' && data.author_association.length > 0) {
627
+ review.authorAssociation = data.author_association;
628
+ }
629
+ return review;
630
+ }
631
+ async convertPullRequestToDraft(owner, repo, prNumber) {
632
+ // Step 1 — resolve the PR node id + current draft state. If it is already
633
+ // a draft, the conversion is a no-op (idempotent short-circuit).
634
+ const query = 'query($owner: String!, $repo: String!, $n: Int!) { repository(owner: $owner, name: $repo) { pullRequest(number: $n) { id isDraft } } }';
635
+ const queryResult = await this.executeGh([
636
+ 'api', 'graphql',
637
+ '-f', `query=${query}`,
638
+ '-F', `owner=${owner}`,
639
+ '-F', `repo=${repo}`,
640
+ '-F', `n=${prNumber}`,
641
+ ]);
642
+ if (queryResult.exitCode !== 0) {
643
+ throw new Error(`convertPullRequestToDraft failed to resolve PR #${prNumber}: ${queryResult.stderr}`);
644
+ }
645
+ const queryParsed = parseJSONSafe(queryResult.stdout);
646
+ if (queryParsed?.errors && queryParsed.errors.length > 0) {
647
+ const messages = queryParsed.errors.map(e => e.message ?? 'unknown').join('; ');
648
+ throw new Error(`convertPullRequestToDraft returned GraphQL errors resolving PR #${prNumber}: ${messages}`);
649
+ }
650
+ const pr = queryParsed?.data?.repository?.pullRequest;
651
+ if (!pr?.id) {
652
+ throw new Error(`convertPullRequestToDraft could not resolve node id for PR #${prNumber}`);
653
+ }
654
+ if (pr.isDraft === true)
655
+ return;
656
+ // Step 2 — run the mutation, mirroring `resolveReviewThread`'s retry/auth
657
+ // handling: 3× backoff, rethrow GhAuthError, terminal on GraphQL errors[].
658
+ const mutation = 'mutation($id: ID!) { convertPullRequestToDraft(input: { pullRequestId: $id }) { pullRequest { id isDraft } } }';
659
+ const backoffs = [1000, 2000, 4000];
660
+ let lastError = null;
661
+ for (let attempt = 0; attempt < 3; attempt++) {
662
+ try {
663
+ const result = await this.executeGh([
664
+ 'api', 'graphql',
665
+ '-f', `query=${mutation}`,
666
+ '-F', `id=${pr.id}`,
667
+ ]);
668
+ if (result.exitCode !== 0) {
669
+ lastError = new Error(`convertPullRequestToDraft mutation failed for PR #${prNumber}: ${result.stderr}`);
670
+ }
671
+ else {
672
+ const parsed = parseJSONSafe(result.stdout);
673
+ if (parsed?.errors && parsed.errors.length > 0) {
674
+ const messages = parsed.errors.map(e => e.message ?? 'unknown').join('; ');
675
+ throw new Error(`convertPullRequestToDraft returned GraphQL errors for PR #${prNumber}: ${messages}`);
676
+ }
677
+ return;
678
+ }
679
+ }
680
+ catch (err) {
681
+ if (err instanceof GhAuthError)
682
+ throw err;
683
+ if (err instanceof Error &&
684
+ err.message.startsWith('convertPullRequestToDraft returned GraphQL errors')) {
685
+ throw err;
686
+ }
687
+ lastError = err instanceof Error ? err : new Error(String(err));
688
+ }
689
+ const backoff = backoffs[attempt];
690
+ if (backoff !== undefined) {
691
+ await new Promise(resolve => setTimeout(resolve, backoff));
692
+ }
693
+ }
694
+ throw lastError ?? new Error(`convertPullRequestToDraft failed for PR #${prNumber}`);
695
+ }
696
+ async listPullRequestFiles(owner, repo, prNumber) {
697
+ const result = await this.executeGh([
698
+ 'api',
699
+ `/repos/${owner}/${repo}/pulls/${prNumber}/files?per_page=100`,
700
+ '--paginate',
701
+ ]);
702
+ if (result.exitCode !== 0) {
703
+ throw new Error(`Failed to list files for PR #${prNumber}: ${result.stderr}`);
704
+ }
705
+ const data = parseJSONSafe(result.stdout);
706
+ if (!data)
707
+ return [];
708
+ return data.map(f => {
709
+ const file = { filename: f.filename, status: f.status };
710
+ if (typeof f.patch === 'string')
711
+ file.patch = f.patch;
712
+ return file;
713
+ });
714
+ }
576
715
  async replyToPRComment(owner, repo, number, commentId, body) {
577
716
  const result = await this.executeGh([
578
717
  'api',
@@ -879,7 +1018,12 @@ export class GhCliGitHubClient {
879
1018
  async getStatus() {
880
1019
  const branchResult = await executeCommand('git', ['branch', '--show-current'], { cwd: this.workdir });
881
1020
  const branch = branchResult.stdout.trim();
882
- const statusResult = await executeCommand('git', ['status', '--porcelain'], { cwd: this.workdir });
1021
+ // `--untracked-files=all`: report every untracked file individually. The
1022
+ // default collapses a wholly-untracked directory into one `?? dir/` entry,
1023
+ // which hides the files inside from path-based consumers (e.g. the
1024
+ // orchestrator's engine-sidecar staging filter, #1162 — a bare `.generacy/`
1025
+ // entry would be staged wholesale, committing every sidecar).
1026
+ const statusResult = await executeCommand('git', ['status', '--porcelain', '--untracked-files=all'], { cwd: this.workdir });
883
1027
  const lines = statusResult.stdout.split('\n').filter(l => l);
884
1028
  const staged = [];
885
1029
  const unstaged = [];
@@ -975,8 +1119,15 @@ export class GhCliGitHubClient {
975
1119
  throw new Error(`Failed to stage all: ${result.stderr}`);
976
1120
  }
977
1121
  }
978
- async commit(message) {
979
- const result = await executeCommand('git', ['commit', '-m', message], { cwd: this.workdir });
1122
+ async commit(message, pathspec) {
1123
+ // With an explicit pathspec, `git commit -m <msg> -- <paths>` records only
1124
+ // those paths and disregards anything else staged in the index (#1162) —
1125
+ // the caller must have already staged untracked members of `pathspec`.
1126
+ // Without it, the whole index is committed (legacy behavior).
1127
+ const args = pathspec && pathspec.length > 0
1128
+ ? ['commit', '-m', message, '--', ...pathspec]
1129
+ : ['commit', '-m', message];
1130
+ const result = await executeCommand('git', args, { cwd: this.workdir });
980
1131
  if (result.exitCode !== 0) {
981
1132
  throw new Error(`Failed to commit: ${result.stderr}`);
982
1133
  }
@@ -1089,6 +1240,23 @@ export class GhCliGitHubClient {
1089
1240
  }
1090
1241
  return { success: true, conflicts: false };
1091
1242
  }
1243
+ async discardWorkingTreeChanges(excludePaths = []) {
1244
+ // Revert tracked modifications (staged + unstaged) to HEAD.
1245
+ const reset = await executeCommand('git', ['reset', '--hard', 'HEAD'], { cwd: this.workdir });
1246
+ if (reset.exitCode !== 0) {
1247
+ throw new Error(`Failed to reset working tree: ${reset.stderr}`);
1248
+ }
1249
+ // Remove untracked files/directories left behind by the abandoned work.
1250
+ // `-e <pattern>` keeps caller-owned untracked state (e.g. `.generacy/`).
1251
+ const cleanArgs = ['clean', '-fd'];
1252
+ for (const pattern of excludePaths) {
1253
+ cleanArgs.push('-e', pattern);
1254
+ }
1255
+ const clean = await executeCommand('git', cleanArgs, { cwd: this.workdir });
1256
+ if (clean.exitCode !== 0) {
1257
+ throw new Error(`Failed to clean working tree: ${clean.stderr}`);
1258
+ }
1259
+ }
1092
1260
  async getConflictedFiles() {
1093
1261
  const result = await executeCommand('git', ['diff', '--name-only', '--diff-filter=U'], { cwd: this.workdir });
1094
1262
  return result.stdout.split('\n').filter(f => f);
@@ -1132,6 +1300,44 @@ export class GhCliGitHubClient {
1132
1300
  }
1133
1301
  return result.stdout.split('\n').filter(Boolean);
1134
1302
  }
1303
+ async getCurrentCommitSha() {
1304
+ const result = await executeCommand('git', [
1305
+ 'rev-parse', 'HEAD',
1306
+ ], { cwd: this.workdir });
1307
+ if (result.exitCode !== 0) {
1308
+ throw new Error(`git rev-parse HEAD failed (exit ${result.exitCode}): ${result.stderr.trim()}`);
1309
+ }
1310
+ return result.stdout.trim();
1311
+ }
1312
+ async getFilesChangedByOwnCommits(startRef) {
1313
+ const result = await executeCommand('git', [
1314
+ 'log', '--first-parent', '--no-merges', '--name-only', '--pretty=format:',
1315
+ `${startRef}..HEAD`,
1316
+ ], { cwd: this.workdir });
1317
+ if (result.exitCode !== 0) {
1318
+ throw new Error(`git log --first-parent --no-merges --name-only ${startRef}..HEAD failed ` +
1319
+ `(exit ${result.exitCode}): ${result.stderr.trim()}`);
1320
+ }
1321
+ const seen = new Set();
1322
+ for (const line of result.stdout.split('\n')) {
1323
+ const path = line.trim();
1324
+ if (path) {
1325
+ seen.add(path);
1326
+ }
1327
+ }
1328
+ return [...seen];
1329
+ }
1330
+ async commitExistsInCheckout(sha) {
1331
+ const result = await executeCommand('git', [
1332
+ 'rev-parse', '--verify', '--quiet', `${sha}^{commit}`,
1333
+ ], { cwd: this.workdir });
1334
+ if (result.exitCode === 0)
1335
+ return true;
1336
+ if (result.exitCode === 1)
1337
+ return false; // commit-missing (FR-003, Q4=B)
1338
+ throw new Error(`git rev-parse --verify --quiet ${sha}^{commit} failed ` +
1339
+ `(exit ${result.exitCode}): ${result.stderr.trim()}`);
1340
+ }
1135
1341
  // ==========================================================================
1136
1342
  // Alias Methods (convenience wrappers)
1137
1343
  // ==========================================================================
@@ -1156,6 +1362,109 @@ export class GhCliGitHubClient {
1156
1362
  }
1157
1363
  return sha;
1158
1364
  }
1365
+ /**
1366
+ * Read CI runs for a commit SHA for merge-readiness aggregation (#1133).
1367
+ *
1368
+ * Primary: `gh api repos/{o}/{r}/commits/{sha}/check-runs` → source
1369
+ * `check-runs`. On non-zero exit — including the `GhAuthError` (HTTP 401/403)
1370
+ * that `executeGh` raises, which is the observed symptom of a token lacking
1371
+ * `checks:read` (FR-002) — fall back to
1372
+ * `gh api repos/{o}/{r}/actions/runs?branch={branch}` filtered client-side to
1373
+ * the head SHA → source `actions-runs`.
1374
+ *
1375
+ * Both paths are paginated (`--paginate` + `per_page=100`): the check-runs
1376
+ * endpoint caps at 30 results per page by default (same trap #1043 fixed for
1377
+ * `listBranches`). Without pagination a head SHA with >30 checks would expose
1378
+ * only page 1 to `aggregateCiVerdict`, so a failing or still-pending run past
1379
+ * page 1 would be invisible and could yield a false `green` — the exact
1380
+ * skipped≠passed safety hole this feature closes.
1381
+ *
1382
+ * Both paths normalize to `CiRun[]` consumable by `aggregateCiVerdict`
1383
+ * unchanged (SC-004). Empty result → `{ runs: [], source }`. Non-zero exit on
1384
+ * BOTH paths → throw with stderr (mirrors `getRefHeadSha`).
1385
+ */
1386
+ async getCiRunsForSha(owner, repo, headSha, branch) {
1387
+ let primaryError = '';
1388
+ try {
1389
+ const primary = await this.executeGh([
1390
+ 'api',
1391
+ '--paginate',
1392
+ `repos/${owner}/${repo}/commits/${headSha}/check-runs?per_page=100`,
1393
+ '--jq', '.check_runs[] | {status, conclusion}',
1394
+ ]);
1395
+ if (primary.exitCode === 0) {
1396
+ return {
1397
+ runs: this.parseCiRunLines(primary.stdout),
1398
+ source: 'check-runs',
1399
+ };
1400
+ }
1401
+ primaryError = `exit ${primary.exitCode}: ${primary.stderr.trim()}`;
1402
+ }
1403
+ catch (err) {
1404
+ // executeGh throws GhAuthError on 401/403 — the checks:read-missing
1405
+ // symptom. Treat it as a fallback trigger, not a fatal readout error.
1406
+ primaryError = err instanceof Error ? err.message : String(err);
1407
+ }
1408
+ // #1157 FR-007: this fallback only enumerates GitHub-Actions `workflow_runs`
1409
+ // for the branch (filtered client-side to the head SHA). It is BLIND to
1410
+ // third-party required checks (external status contexts), so a `green`
1411
+ // aggregated from these runs may be a false green. The primary path is used
1412
+ // only when `check-runs` failed — the canonical symptom of a token lacking
1413
+ // `checks:read`. To close the false-green hole, `evaluateCiReadiness`
1414
+ // (packages/orchestrator/src/worker/ci-merge-readiness.ts) fails-closed:
1415
+ // when `source === 'actions-runs'` a would-be `green` is downgraded to
1416
+ // `not-passed`. Operator note: a `checks:read`-lacking cluster fails closed
1417
+ // (CI merge readiness never reports green via this fallback); granting the
1418
+ // token `checks:read` restores full third-party-check visibility via the
1419
+ // primary `check-runs` path.
1420
+ const fallback = await this.executeGh([
1421
+ 'api',
1422
+ '--paginate',
1423
+ `repos/${owner}/${repo}/actions/runs?branch=${branch}&per_page=100`,
1424
+ '--jq', '.workflow_runs[] | {head_sha, status, conclusion}',
1425
+ ]);
1426
+ if (fallback.exitCode !== 0) {
1427
+ throw new Error(`getCiRunsForSha ${owner}/${repo}@${headSha} failed on both paths ` +
1428
+ `(check-runs ${primaryError}; ` +
1429
+ `actions-runs exit ${fallback.exitCode}: ${fallback.stderr.trim()})`);
1430
+ }
1431
+ // The actions/runs jq keeps head_sha, so filter to the target SHA.
1432
+ const filtered = [];
1433
+ for (const line of fallback.stdout.split('\n')) {
1434
+ const trimmed = line.trim();
1435
+ if (!trimmed)
1436
+ continue;
1437
+ const parsed = parseJSONSafe(trimmed);
1438
+ if (!parsed || parsed.head_sha !== headSha)
1439
+ continue;
1440
+ filtered.push({
1441
+ status: typeof parsed.status === 'string' ? parsed.status : '',
1442
+ conclusion: (parsed.conclusion ?? null),
1443
+ });
1444
+ }
1445
+ return { runs: filtered, source: 'actions-runs' };
1446
+ }
1447
+ /**
1448
+ * Parse newline-delimited `{status, conclusion}` JSON objects (jq stream
1449
+ * output) into `CiRun[]`. Blank lines and unparseable lines are skipped.
1450
+ * Unknown `conclusion` values are passed through as-is.
1451
+ */
1452
+ parseCiRunLines(stdout) {
1453
+ const runs = [];
1454
+ for (const line of stdout.split('\n')) {
1455
+ const trimmed = line.trim();
1456
+ if (!trimmed)
1457
+ continue;
1458
+ const parsed = parseJSONSafe(trimmed);
1459
+ if (!parsed)
1460
+ continue;
1461
+ runs.push({
1462
+ status: typeof parsed.status === 'string' ? parsed.status : '',
1463
+ conclusion: (parsed.conclusion ?? null),
1464
+ });
1465
+ }
1466
+ return runs;
1467
+ }
1159
1468
  /**
1160
1469
  * List file names touched by a PR (`gh pr diff --name-only`, #892).
1161
1470
  * Non-zero exit → throw with stderr for visibility.