@generacy-ai/workflow-engine 0.6.0 → 0.8.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, IssueRefState, 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.
@@ -45,6 +45,7 @@ export declare class GhCliGitHubClient implements GitHubClient {
45
45
  private executeGh;
46
46
  getRepoInfo(): Promise<RepoInfo>;
47
47
  getIssue(owner: string, repo: string, number: number): Promise<Issue>;
48
+ getIssueRefState(owner: string, repo: string, number: number): Promise<IssueRefState>;
48
49
  getIssueLabels(owner: string, repo: string, number: number): Promise<string[]>;
49
50
  listIssuesWithLabel(owner: string, repo: string, label: string): Promise<Issue[]>;
50
51
  updateIssue(owner: string, repo: string, number: number, data: IssueUpdate): Promise<void>;
@@ -59,6 +60,9 @@ export declare class GhCliGitHubClient implements GitHubClient {
59
60
  getPRComments(owner: string, repo: string, number: number): Promise<Comment[]>;
60
61
  getPRReviewThreads(owner: string, repo: string, number: number): Promise<ReviewThread[]>;
61
62
  listReviews(owner: string, repo: string, prNumber: number): Promise<Review[]>;
63
+ createReview(owner: string, repo: string, prNumber: number, input: CreateReviewInput): Promise<Review>;
64
+ convertPullRequestToDraft(owner: string, repo: string, prNumber: number): Promise<void>;
65
+ listPullRequestFiles(owner: string, repo: string, prNumber: number): Promise<PullRequestFile[]>;
62
66
  replyToPRComment(owner: string, repo: string, number: number, commentId: number, body: string): Promise<Comment>;
63
67
  resolveReviewThread(threadId: string): Promise<void>;
64
68
  listOpenPullRequests(owner: string, repo: string): Promise<PullRequest[]>;
@@ -79,7 +83,7 @@ export declare class GhCliGitHubClient implements GitHubClient {
79
83
  checkout(branch: string): Promise<void>;
80
84
  stageFiles(files: string[]): Promise<void>;
81
85
  stageAll(): Promise<void>;
82
- commit(message: string): Promise<CommitResult>;
86
+ commit(message: string, pathspec?: string[]): Promise<CommitResult>;
83
87
  push(remote?: string, branch?: string, setUpstream?: boolean): Promise<PushResult>;
84
88
  fetch(remote?: string, prune?: boolean): Promise<void>;
85
89
  merge(branch: string, noCommit?: boolean): Promise<MergeResult>;
@@ -89,6 +93,8 @@ export declare class GhCliGitHubClient implements GitHubClient {
89
93
  success: boolean;
90
94
  conflicts: boolean;
91
95
  }>;
96
+ discardWorkingTreeChanges(excludePaths?: string[]): Promise<void>;
97
+ revertPaths(paths: string[]): Promise<void>;
92
98
  getConflictedFiles(): Promise<string[]>;
93
99
  getDefaultBranch(): Promise<string>;
94
100
  getCommitsBetween(base: string, head: string): Promise<{
@@ -106,6 +112,37 @@ export declare class GhCliGitHubClient implements GitHubClient {
106
112
  * 401 → `GhAuthError` via `executeGh` (existing #762 path).
107
113
  */
108
114
  getRefHeadSha(owner: string, repo: string, ref: string): Promise<string>;
115
+ /**
116
+ * Read CI runs for a commit SHA for merge-readiness aggregation (#1133).
117
+ *
118
+ * Primary: `gh api repos/{o}/{r}/commits/{sha}/check-runs` → source
119
+ * `check-runs`. On non-zero exit — including the `GhAuthError` (HTTP 401/403)
120
+ * that `executeGh` raises, which is the observed symptom of a token lacking
121
+ * `checks:read` (FR-002) — fall back to
122
+ * `gh api repos/{o}/{r}/actions/runs?branch={branch}` filtered client-side to
123
+ * the head SHA → source `actions-runs`.
124
+ *
125
+ * Both paths are paginated (`--paginate` + `per_page=100`): the check-runs
126
+ * endpoint caps at 30 results per page by default (same trap #1043 fixed for
127
+ * `listBranches`). Without pagination a head SHA with >30 checks would expose
128
+ * only page 1 to `aggregateCiVerdict`, so a failing or still-pending run past
129
+ * page 1 would be invisible and could yield a false `green` — the exact
130
+ * skipped≠passed safety hole this feature closes.
131
+ *
132
+ * Both paths normalize to `CiRun[]` consumable by `aggregateCiVerdict`
133
+ * unchanged (SC-004). Empty result → `{ runs: [], source }`. Non-zero exit on
134
+ * BOTH paths → throw with stderr (mirrors `getRefHeadSha`).
135
+ */
136
+ getCiRunsForSha(owner: string, repo: string, headSha: string, branch: string): Promise<{
137
+ runs: CiRun[];
138
+ source: 'check-runs' | 'actions-runs';
139
+ }>;
140
+ /**
141
+ * Parse newline-delimited `{status, conclusion}` JSON objects (jq stream
142
+ * output) into `CiRun[]`. Blank lines and unparseable lines are skipped.
143
+ * Unknown `conclusion` values are passed through as-is.
144
+ */
145
+ private parseCiRunLines;
109
146
  /**
110
147
  * List file names touched by a PR (`gh pr diff --name-only`, #892).
111
148
  * 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":"AAAA;;;GAGG;AACH,OAAO,KAAK,EACV,YAAY,EACZ,WAAW,EACX,QAAQ,EACR,QAAQ,EACR,WAAW,EACX,YAAY,EACZ,UAAU,EACV,SAAS,EACT,eAAe,EAChB,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EACV,KAAK,EACL,WAAW,EACX,OAAO,EACP,KAAK,EACL,QAAQ,EAER,MAAM,EAEN,YAAY,EACb,MAAM,0BAA0B,CAAC;AAGlC;;;;GAIG;AACH,qBAAa,WAAY,SAAQ,KAAK;aAElB,UAAU,EAAE,GAAG,GAAG,GAAG;aACrB,MAAM,EAAE,MAAM;gBADd,UAAU,EAAE,GAAG,GAAG,GAAG,EACrB,MAAM,EAAE,MAAM,EAC9B,OAAO,CAAC,EAAE,MAAM;CAKnB;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAIpE;AAED;;GAEG;AACH,qBAAa,iBAAkB,YAAW,YAAY;IACpD,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,aAAa,CAAC,CAAoC;gBAGxD,OAAO,CAAC,EAAE,MAAM,EAChB,aAAa,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAMnD;;;;;;;;;;;;;OAaG;YACW,eAAe;YAMf,SAAS;IAgBjB,WAAW,IAAI,OAAO,CAAC,QAAQ,CAAC;IA+BhC,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;IAqCrE,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAoB9E,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;IAwCjF,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IA4B1F,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAsB5F,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;IAmCjF,8BAA8B,CAClC,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,OAAO,EAAE,CAAC;IAwEf,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAkB1F,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,WAAW,CAAC;IAwDpF,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAoCjF,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IA2B7F,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAWvE,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;IAiC9E,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IA2GxF,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IA2D7E,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IA0BhH,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAgDpD,oBAAoB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAuCzE,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAuBrF,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAWzF,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IAoCzF,uBAAuB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IAqEjG,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAcvF,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAiB1F,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;IAqB5D,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;IA4CvG,SAAS,IAAI,OAAO,CAAC,SAAS,CAAC;IA4D/B,gBAAgB,IAAI,OAAO,CAAC,MAAM,CAAC;IAQnC,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,UAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;IAU9D,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAY9D,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAOvC,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAS1C,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAOzB,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAoB9C,IAAI,CAAC,MAAM,SAAW,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,WAAW,UAAQ,GAAG,OAAO,CAAC,UAAU,CAAC;IAoBlF,KAAK,CAAC,MAAM,SAAW,EAAE,KAAK,UAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAYrD,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,UAAQ,GAAG,OAAO,CAAC,WAAW,CAAC;IAuD7D,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAO3B,KAAK,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAWzC,QAAQ,IAAI,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,SAAS,EAAE,OAAO,CAAA;KAAE,CAAC;IAa7D,kBAAkB,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAKvC,gBAAgB,IAAI,OAAO,CAAC,MAAM,CAAC;IAgBnC,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAmB1F,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAcrE,mBAAmB,IAAI,OAAO,CAAC,MAAM,CAAC;IActC,2BAA2B,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAuBhE,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAiB3D;;;;;OAKG;IACG,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAsB9E;;;OAGG;IACG,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAgBnE,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAyB5D,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,WAAW,CAAC;IAI3E,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAIpF,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IAIxF,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;IAIzD,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAgB1G,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;CAe5H"}
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, rm } 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.
@@ -123,6 +127,40 @@ export class GhCliGitHubClient {
123
127
  updated_at: data['updatedAt'],
124
128
  };
125
129
  }
130
+ async getIssueRefState(owner, repo, number) {
131
+ const result = await this.executeGh([
132
+ 'api', `repos/${owner}/${repo}/issues/${number}`,
133
+ '--jq', '{state, state_reason, pull_request: .pull_request != null}',
134
+ ]);
135
+ if (result.exitCode !== 0) {
136
+ throw new Error(`Failed to get issue ref state for ${owner}/${repo}#${number}: ${result.stderr}`);
137
+ }
138
+ const data = parseJSONSafe(result.stdout);
139
+ if (!data) {
140
+ throw new Error(`Failed to parse issue ref state for ${owner}/${repo}#${number}`);
141
+ }
142
+ const state = data['state']?.toLowerCase();
143
+ const stateReason = data['state_reason'] ?? null;
144
+ const isPullRequest = data['pull_request'] === true;
145
+ let merged = null;
146
+ if (isPullRequest) {
147
+ const prResult = await this.executeGh([
148
+ 'api', `repos/${owner}/${repo}/pulls/${number}`,
149
+ '--jq', '.merged',
150
+ ]);
151
+ if (prResult.exitCode !== 0) {
152
+ throw new Error(`Failed to get PR merged state for ${owner}/${repo}#${number}: ${prResult.stderr}`);
153
+ }
154
+ const prData = parseJSONSafe(prResult.stdout);
155
+ merged = prData === true || (typeof prData === 'string' && prData.trim() === 'true');
156
+ }
157
+ return {
158
+ state,
159
+ stateReason: stateReason,
160
+ isPullRequest,
161
+ merged,
162
+ };
163
+ }
126
164
  async getIssueLabels(owner, repo, number) {
127
165
  const result = await this.executeGh([
128
166
  'issue', 'view', String(number),
@@ -573,6 +611,141 @@ export class GhCliGitHubClient {
573
611
  }
574
612
  return reviews;
575
613
  }
614
+ async createReview(owner, repo, prNumber, input) {
615
+ // The reviews endpoint accepts a nested `comments[]` array of objects,
616
+ // which `-f field=value` cannot express. The shared `executeCommand`
617
+ // wrapper ignores stdin (both the launcher and direct-spawn paths use
618
+ // `stdio: ['ignore', ...]`), so `--input -` is unavailable here. Write the
619
+ // JSON body to a temp file and pass `--input <file>` — same wire result.
620
+ const body = {
621
+ event: input.event,
622
+ body: input.body,
623
+ comments: (input.comments ?? []).map(c => ({
624
+ path: c.path,
625
+ line: c.line,
626
+ side: c.side ?? 'RIGHT',
627
+ body: c.body,
628
+ })),
629
+ };
630
+ const inputPath = join(tmpdir(), `generacy-review-${randomUUID()}.json`);
631
+ await writeFile(inputPath, JSON.stringify(body), 'utf8');
632
+ let result;
633
+ try {
634
+ result = await this.executeGh([
635
+ 'api',
636
+ '--method', 'POST',
637
+ `/repos/${owner}/${repo}/pulls/${prNumber}/reviews`,
638
+ '--input', inputPath,
639
+ ]);
640
+ }
641
+ finally {
642
+ await unlink(inputPath).catch(() => {
643
+ /* best-effort cleanup */
644
+ });
645
+ }
646
+ if (result.exitCode !== 0) {
647
+ throw new Error(`Failed to create review on PR #${prNumber}: ${result.stderr}`);
648
+ }
649
+ const data = parseJSONSafe(result.stdout);
650
+ if (!data) {
651
+ throw new Error(`Failed to parse created review response on PR #${prNumber}`);
652
+ }
653
+ const review = {
654
+ id: data.id,
655
+ user: { login: data.user?.login ?? '' },
656
+ body: data.body ?? '',
657
+ state: data.state,
658
+ submittedAt: data.submitted_at,
659
+ };
660
+ if (typeof data.author_association === 'string' && data.author_association.length > 0) {
661
+ review.authorAssociation = data.author_association;
662
+ }
663
+ return review;
664
+ }
665
+ async convertPullRequestToDraft(owner, repo, prNumber) {
666
+ // Step 1 — resolve the PR node id + current draft state. If it is already
667
+ // a draft, the conversion is a no-op (idempotent short-circuit).
668
+ const query = 'query($owner: String!, $repo: String!, $n: Int!) { repository(owner: $owner, name: $repo) { pullRequest(number: $n) { id isDraft } } }';
669
+ const queryResult = await this.executeGh([
670
+ 'api', 'graphql',
671
+ '-f', `query=${query}`,
672
+ '-F', `owner=${owner}`,
673
+ '-F', `repo=${repo}`,
674
+ '-F', `n=${prNumber}`,
675
+ ]);
676
+ if (queryResult.exitCode !== 0) {
677
+ throw new Error(`convertPullRequestToDraft failed to resolve PR #${prNumber}: ${queryResult.stderr}`);
678
+ }
679
+ const queryParsed = parseJSONSafe(queryResult.stdout);
680
+ if (queryParsed?.errors && queryParsed.errors.length > 0) {
681
+ const messages = queryParsed.errors.map(e => e.message ?? 'unknown').join('; ');
682
+ throw new Error(`convertPullRequestToDraft returned GraphQL errors resolving PR #${prNumber}: ${messages}`);
683
+ }
684
+ const pr = queryParsed?.data?.repository?.pullRequest;
685
+ if (!pr?.id) {
686
+ throw new Error(`convertPullRequestToDraft could not resolve node id for PR #${prNumber}`);
687
+ }
688
+ if (pr.isDraft === true)
689
+ return;
690
+ // Step 2 — run the mutation, mirroring `resolveReviewThread`'s retry/auth
691
+ // handling: 3× backoff, rethrow GhAuthError, terminal on GraphQL errors[].
692
+ const mutation = 'mutation($id: ID!) { convertPullRequestToDraft(input: { pullRequestId: $id }) { pullRequest { id isDraft } } }';
693
+ const backoffs = [1000, 2000, 4000];
694
+ let lastError = null;
695
+ for (let attempt = 0; attempt < 3; attempt++) {
696
+ try {
697
+ const result = await this.executeGh([
698
+ 'api', 'graphql',
699
+ '-f', `query=${mutation}`,
700
+ '-F', `id=${pr.id}`,
701
+ ]);
702
+ if (result.exitCode !== 0) {
703
+ lastError = new Error(`convertPullRequestToDraft mutation failed for PR #${prNumber}: ${result.stderr}`);
704
+ }
705
+ else {
706
+ const parsed = parseJSONSafe(result.stdout);
707
+ if (parsed?.errors && parsed.errors.length > 0) {
708
+ const messages = parsed.errors.map(e => e.message ?? 'unknown').join('; ');
709
+ throw new Error(`convertPullRequestToDraft returned GraphQL errors for PR #${prNumber}: ${messages}`);
710
+ }
711
+ return;
712
+ }
713
+ }
714
+ catch (err) {
715
+ if (err instanceof GhAuthError)
716
+ throw err;
717
+ if (err instanceof Error &&
718
+ err.message.startsWith('convertPullRequestToDraft returned GraphQL errors')) {
719
+ throw err;
720
+ }
721
+ lastError = err instanceof Error ? err : new Error(String(err));
722
+ }
723
+ const backoff = backoffs[attempt];
724
+ if (backoff !== undefined) {
725
+ await new Promise(resolve => setTimeout(resolve, backoff));
726
+ }
727
+ }
728
+ throw lastError ?? new Error(`convertPullRequestToDraft failed for PR #${prNumber}`);
729
+ }
730
+ async listPullRequestFiles(owner, repo, prNumber) {
731
+ const result = await this.executeGh([
732
+ 'api',
733
+ `/repos/${owner}/${repo}/pulls/${prNumber}/files?per_page=100`,
734
+ '--paginate',
735
+ ]);
736
+ if (result.exitCode !== 0) {
737
+ throw new Error(`Failed to list files for PR #${prNumber}: ${result.stderr}`);
738
+ }
739
+ const data = parseJSONSafe(result.stdout);
740
+ if (!data)
741
+ return [];
742
+ return data.map(f => {
743
+ const file = { filename: f.filename, status: f.status };
744
+ if (typeof f.patch === 'string')
745
+ file.patch = f.patch;
746
+ return file;
747
+ });
748
+ }
576
749
  async replyToPRComment(owner, repo, number, commentId, body) {
577
750
  const result = await this.executeGh([
578
751
  'api',
@@ -879,7 +1052,12 @@ export class GhCliGitHubClient {
879
1052
  async getStatus() {
880
1053
  const branchResult = await executeCommand('git', ['branch', '--show-current'], { cwd: this.workdir });
881
1054
  const branch = branchResult.stdout.trim();
882
- const statusResult = await executeCommand('git', ['status', '--porcelain'], { cwd: this.workdir });
1055
+ // `--untracked-files=all`: report every untracked file individually. The
1056
+ // default collapses a wholly-untracked directory into one `?? dir/` entry,
1057
+ // which hides the files inside from path-based consumers (e.g. the
1058
+ // orchestrator's engine-sidecar staging filter, #1162 — a bare `.generacy/`
1059
+ // entry would be staged wholesale, committing every sidecar).
1060
+ const statusResult = await executeCommand('git', ['status', '--porcelain', '--untracked-files=all'], { cwd: this.workdir });
883
1061
  const lines = statusResult.stdout.split('\n').filter(l => l);
884
1062
  const staged = [];
885
1063
  const unstaged = [];
@@ -975,8 +1153,15 @@ export class GhCliGitHubClient {
975
1153
  throw new Error(`Failed to stage all: ${result.stderr}`);
976
1154
  }
977
1155
  }
978
- async commit(message) {
979
- const result = await executeCommand('git', ['commit', '-m', message], { cwd: this.workdir });
1156
+ async commit(message, pathspec) {
1157
+ // With an explicit pathspec, `git commit -m <msg> -- <paths>` records only
1158
+ // those paths and disregards anything else staged in the index (#1162) —
1159
+ // the caller must have already staged untracked members of `pathspec`.
1160
+ // Without it, the whole index is committed (legacy behavior).
1161
+ const args = pathspec && pathspec.length > 0
1162
+ ? ['commit', '-m', message, '--', ...pathspec]
1163
+ : ['commit', '-m', message];
1164
+ const result = await executeCommand('git', args, { cwd: this.workdir });
980
1165
  if (result.exitCode !== 0) {
981
1166
  throw new Error(`Failed to commit: ${result.stderr}`);
982
1167
  }
@@ -1089,6 +1274,57 @@ export class GhCliGitHubClient {
1089
1274
  }
1090
1275
  return { success: true, conflicts: false };
1091
1276
  }
1277
+ async discardWorkingTreeChanges(excludePaths = []) {
1278
+ // Revert tracked modifications (staged + unstaged) to HEAD.
1279
+ const reset = await executeCommand('git', ['reset', '--hard', 'HEAD'], { cwd: this.workdir });
1280
+ if (reset.exitCode !== 0) {
1281
+ throw new Error(`Failed to reset working tree: ${reset.stderr}`);
1282
+ }
1283
+ // Remove untracked files/directories left behind by the abandoned work.
1284
+ // `-e <pattern>` keeps caller-owned untracked state (e.g. `.generacy/`).
1285
+ const cleanArgs = ['clean', '-fd'];
1286
+ for (const pattern of excludePaths) {
1287
+ cleanArgs.push('-e', pattern);
1288
+ }
1289
+ const clean = await executeCommand('git', cleanArgs, { cwd: this.workdir });
1290
+ if (clean.exitCode !== 0) {
1291
+ throw new Error(`Failed to clean working tree: ${clean.stderr}`);
1292
+ }
1293
+ }
1294
+ async revertPaths(paths) {
1295
+ if (paths.length === 0)
1296
+ return;
1297
+ // Unstage first so a staged-*new* path (agent ran `git add` on a file absent
1298
+ // from HEAD) becomes plain untracked, and the index matches HEAD for these
1299
+ // paths — after which `git ls-files` cleanly partitions tracked vs untracked.
1300
+ const reset = await executeCommand('git', ['reset', '-q', 'HEAD', '--', ...paths], {
1301
+ cwd: this.workdir,
1302
+ });
1303
+ if (reset.exitCode !== 0) {
1304
+ throw new Error(`Failed to unstage paths for revert: ${reset.stderr}`);
1305
+ }
1306
+ // Listed ⇒ tracked in HEAD; unlisted ⇒ untracked. A bare
1307
+ // `git checkout -- <untracked>` errors with "pathspec did not match", so the
1308
+ // two sets must be handled separately.
1309
+ const ls = await executeCommand('git', ['ls-files', '--', ...paths], { cwd: this.workdir });
1310
+ if (ls.exitCode !== 0) {
1311
+ throw new Error(`Failed to list tracked paths for revert: ${ls.stderr}`);
1312
+ }
1313
+ const tracked = new Set(ls.stdout.split('\n').filter((f) => f));
1314
+ if (tracked.size > 0) {
1315
+ const checkout = await executeCommand('git', ['checkout', '--', ...tracked], {
1316
+ cwd: this.workdir,
1317
+ });
1318
+ if (checkout.exitCode !== 0) {
1319
+ throw new Error(`Failed to restore tracked paths: ${checkout.stderr}`);
1320
+ }
1321
+ }
1322
+ for (const path of paths) {
1323
+ if (tracked.has(path))
1324
+ continue;
1325
+ await rm(join(this.workdir, path), { force: true });
1326
+ }
1327
+ }
1092
1328
  async getConflictedFiles() {
1093
1329
  const result = await executeCommand('git', ['diff', '--name-only', '--diff-filter=U'], { cwd: this.workdir });
1094
1330
  return result.stdout.split('\n').filter(f => f);
@@ -1194,6 +1430,109 @@ export class GhCliGitHubClient {
1194
1430
  }
1195
1431
  return sha;
1196
1432
  }
1433
+ /**
1434
+ * Read CI runs for a commit SHA for merge-readiness aggregation (#1133).
1435
+ *
1436
+ * Primary: `gh api repos/{o}/{r}/commits/{sha}/check-runs` → source
1437
+ * `check-runs`. On non-zero exit — including the `GhAuthError` (HTTP 401/403)
1438
+ * that `executeGh` raises, which is the observed symptom of a token lacking
1439
+ * `checks:read` (FR-002) — fall back to
1440
+ * `gh api repos/{o}/{r}/actions/runs?branch={branch}` filtered client-side to
1441
+ * the head SHA → source `actions-runs`.
1442
+ *
1443
+ * Both paths are paginated (`--paginate` + `per_page=100`): the check-runs
1444
+ * endpoint caps at 30 results per page by default (same trap #1043 fixed for
1445
+ * `listBranches`). Without pagination a head SHA with >30 checks would expose
1446
+ * only page 1 to `aggregateCiVerdict`, so a failing or still-pending run past
1447
+ * page 1 would be invisible and could yield a false `green` — the exact
1448
+ * skipped≠passed safety hole this feature closes.
1449
+ *
1450
+ * Both paths normalize to `CiRun[]` consumable by `aggregateCiVerdict`
1451
+ * unchanged (SC-004). Empty result → `{ runs: [], source }`. Non-zero exit on
1452
+ * BOTH paths → throw with stderr (mirrors `getRefHeadSha`).
1453
+ */
1454
+ async getCiRunsForSha(owner, repo, headSha, branch) {
1455
+ let primaryError = '';
1456
+ try {
1457
+ const primary = await this.executeGh([
1458
+ 'api',
1459
+ '--paginate',
1460
+ `repos/${owner}/${repo}/commits/${headSha}/check-runs?per_page=100`,
1461
+ '--jq', '.check_runs[] | {status, conclusion}',
1462
+ ]);
1463
+ if (primary.exitCode === 0) {
1464
+ return {
1465
+ runs: this.parseCiRunLines(primary.stdout),
1466
+ source: 'check-runs',
1467
+ };
1468
+ }
1469
+ primaryError = `exit ${primary.exitCode}: ${primary.stderr.trim()}`;
1470
+ }
1471
+ catch (err) {
1472
+ // executeGh throws GhAuthError on 401/403 — the checks:read-missing
1473
+ // symptom. Treat it as a fallback trigger, not a fatal readout error.
1474
+ primaryError = err instanceof Error ? err.message : String(err);
1475
+ }
1476
+ // #1157 FR-007: this fallback only enumerates GitHub-Actions `workflow_runs`
1477
+ // for the branch (filtered client-side to the head SHA). It is BLIND to
1478
+ // third-party required checks (external status contexts), so a `green`
1479
+ // aggregated from these runs may be a false green. The primary path is used
1480
+ // only when `check-runs` failed — the canonical symptom of a token lacking
1481
+ // `checks:read`. To close the false-green hole, `evaluateCiReadiness`
1482
+ // (packages/orchestrator/src/worker/ci-merge-readiness.ts) fails-closed:
1483
+ // when `source === 'actions-runs'` a would-be `green` is downgraded to
1484
+ // `not-passed`. Operator note: a `checks:read`-lacking cluster fails closed
1485
+ // (CI merge readiness never reports green via this fallback); granting the
1486
+ // token `checks:read` restores full third-party-check visibility via the
1487
+ // primary `check-runs` path.
1488
+ const fallback = await this.executeGh([
1489
+ 'api',
1490
+ '--paginate',
1491
+ `repos/${owner}/${repo}/actions/runs?branch=${branch}&per_page=100`,
1492
+ '--jq', '.workflow_runs[] | {head_sha, status, conclusion}',
1493
+ ]);
1494
+ if (fallback.exitCode !== 0) {
1495
+ throw new Error(`getCiRunsForSha ${owner}/${repo}@${headSha} failed on both paths ` +
1496
+ `(check-runs ${primaryError}; ` +
1497
+ `actions-runs exit ${fallback.exitCode}: ${fallback.stderr.trim()})`);
1498
+ }
1499
+ // The actions/runs jq keeps head_sha, so filter to the target SHA.
1500
+ const filtered = [];
1501
+ for (const line of fallback.stdout.split('\n')) {
1502
+ const trimmed = line.trim();
1503
+ if (!trimmed)
1504
+ continue;
1505
+ const parsed = parseJSONSafe(trimmed);
1506
+ if (!parsed || parsed.head_sha !== headSha)
1507
+ continue;
1508
+ filtered.push({
1509
+ status: typeof parsed.status === 'string' ? parsed.status : '',
1510
+ conclusion: (parsed.conclusion ?? null),
1511
+ });
1512
+ }
1513
+ return { runs: filtered, source: 'actions-runs' };
1514
+ }
1515
+ /**
1516
+ * Parse newline-delimited `{status, conclusion}` JSON objects (jq stream
1517
+ * output) into `CiRun[]`. Blank lines and unparseable lines are skipped.
1518
+ * Unknown `conclusion` values are passed through as-is.
1519
+ */
1520
+ parseCiRunLines(stdout) {
1521
+ const runs = [];
1522
+ for (const line of stdout.split('\n')) {
1523
+ const trimmed = line.trim();
1524
+ if (!trimmed)
1525
+ continue;
1526
+ const parsed = parseJSONSafe(trimmed);
1527
+ if (!parsed)
1528
+ continue;
1529
+ runs.push({
1530
+ status: typeof parsed.status === 'string' ? parsed.status : '',
1531
+ conclusion: (parsed.conclusion ?? null),
1532
+ });
1533
+ }
1534
+ return runs;
1535
+ }
1197
1536
  /**
1198
1537
  * List file names touched by a PR (`gh pr diff --name-only`, #892).
1199
1538
  * Non-zero exit → throw with stderr for visibility.