chief-clancy 0.5.6 → 0.5.8
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 +1 -1
- package/dist/bundle/clancy-once.js +72 -66
- package/dist/schemas/bitbucket-pr.d.ts +15 -0
- package/dist/schemas/bitbucket-pr.d.ts.map +1 -1
- package/dist/schemas/bitbucket-pr.js +6 -0
- package/dist/schemas/bitbucket-pr.js.map +1 -1
- package/dist/schemas/github.d.ts +12 -0
- package/dist/schemas/github.d.ts.map +1 -1
- package/dist/schemas/github.js +2 -0
- package/dist/schemas/github.js.map +1 -1
- package/dist/schemas/gitlab-mr.d.ts +11 -0
- package/dist/schemas/gitlab-mr.d.ts.map +1 -1
- package/dist/schemas/gitlab-mr.js +2 -0
- package/dist/schemas/gitlab-mr.js.map +1 -1
- package/dist/scripts/board/github/github.d.ts +1 -1
- package/dist/scripts/board/github/github.d.ts.map +1 -1
- package/dist/scripts/board/github/github.js +2 -2
- package/dist/scripts/board/github/github.js.map +1 -1
- package/dist/scripts/once/board-ops.d.ts +11 -0
- package/dist/scripts/once/board-ops.d.ts.map +1 -0
- package/dist/scripts/once/board-ops.js +81 -0
- package/dist/scripts/once/board-ops.js.map +1 -0
- package/dist/scripts/once/deliver.d.ts +13 -0
- package/dist/scripts/once/deliver.d.ts.map +1 -0
- package/dist/scripts/once/deliver.js +134 -0
- package/dist/scripts/once/deliver.js.map +1 -0
- package/dist/scripts/once/fetch-ticket.d.ts +4 -0
- package/dist/scripts/once/fetch-ticket.d.ts.map +1 -0
- package/dist/scripts/once/fetch-ticket.js +59 -0
- package/dist/scripts/once/fetch-ticket.js.map +1 -0
- package/dist/scripts/once/git-token.d.ts +13 -0
- package/dist/scripts/once/git-token.d.ts.map +1 -0
- package/dist/scripts/once/git-token.js +30 -0
- package/dist/scripts/once/git-token.js.map +1 -0
- package/dist/scripts/once/once.d.ts.map +1 -1
- package/dist/scripts/once/once.js +23 -421
- package/dist/scripts/once/once.js.map +1 -1
- package/dist/scripts/once/pr-creation.d.ts +11 -0
- package/dist/scripts/once/pr-creation.d.ts.map +1 -0
- package/dist/scripts/once/pr-creation.js +50 -0
- package/dist/scripts/once/pr-creation.js.map +1 -0
- package/dist/scripts/once/rework.d.ts +32 -0
- package/dist/scripts/once/rework.d.ts.map +1 -0
- package/dist/scripts/once/rework.js +208 -0
- package/dist/scripts/once/rework.js.map +1 -0
- package/dist/scripts/once/types.d.ts +13 -0
- package/dist/scripts/once/types.d.ts.map +1 -0
- package/dist/scripts/once/types.js +2 -0
- package/dist/scripts/once/types.js.map +1 -0
- package/dist/scripts/shared/git-ops/git-ops.d.ts +11 -0
- package/dist/scripts/shared/git-ops/git-ops.d.ts.map +1 -1
- package/dist/scripts/shared/git-ops/git-ops.js +26 -0
- package/dist/scripts/shared/git-ops/git-ops.js.map +1 -1
- package/dist/scripts/shared/preflight/preflight.d.ts.map +1 -1
- package/dist/scripts/shared/preflight/preflight.js +15 -3
- package/dist/scripts/shared/preflight/preflight.js.map +1 -1
- package/dist/scripts/shared/progress/progress.d.ts +2 -1
- package/dist/scripts/shared/progress/progress.d.ts.map +1 -1
- package/dist/scripts/shared/progress/progress.js +29 -13
- package/dist/scripts/shared/progress/progress.js.map +1 -1
- package/dist/scripts/shared/pull-request/bitbucket/bitbucket.d.ts +12 -0
- package/dist/scripts/shared/pull-request/bitbucket/bitbucket.d.ts.map +1 -1
- package/dist/scripts/shared/pull-request/bitbucket/bitbucket.js +51 -1
- package/dist/scripts/shared/pull-request/bitbucket/bitbucket.js.map +1 -1
- package/dist/scripts/shared/pull-request/github/github.d.ts +26 -2
- package/dist/scripts/shared/pull-request/github/github.d.ts.map +1 -1
- package/dist/scripts/shared/pull-request/github/github.js +102 -9
- package/dist/scripts/shared/pull-request/github/github.js.map +1 -1
- package/dist/scripts/shared/pull-request/gitlab/gitlab.d.ts +30 -2
- package/dist/scripts/shared/pull-request/gitlab/gitlab.d.ts.map +1 -1
- package/dist/scripts/shared/pull-request/gitlab/gitlab.js +70 -4
- package/dist/scripts/shared/pull-request/gitlab/gitlab.js.map +1 -1
- package/dist/scripts/shared/pull-request/pr-body/pr-body.d.ts.map +1 -1
- package/dist/scripts/shared/pull-request/pr-body/pr-body.js +6 -1
- package/dist/scripts/shared/pull-request/pr-body/pr-body.js.map +1 -1
- package/dist/types/remote.d.ts +2 -0
- package/dist/types/remote.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { fetchIssue as fetchGitHubIssue, resolveUsername, } from '../../scripts/board/github/github.js';
|
|
2
|
+
import { buildAuthHeader, fetchTicket as fetchJiraTicket, } from '../../scripts/board/jira/jira.js';
|
|
3
|
+
import { fetchIssue as fetchLinearIssue } from '../../scripts/board/linear/linear.js';
|
|
4
|
+
// ─── Board-specific fetch ────────────────────────────────────────────────────
|
|
5
|
+
export async function fetchTicket(config) {
|
|
6
|
+
switch (config.provider) {
|
|
7
|
+
case 'jira': {
|
|
8
|
+
const { env } = config;
|
|
9
|
+
const auth = buildAuthHeader(env.JIRA_USER, env.JIRA_API_TOKEN);
|
|
10
|
+
const ticket = await fetchJiraTicket(env.JIRA_BASE_URL, auth, env.JIRA_PROJECT_KEY, env.CLANCY_JQL_STATUS ?? 'To Do', env.CLANCY_JQL_SPRINT, env.CLANCY_LABEL);
|
|
11
|
+
if (!ticket)
|
|
12
|
+
return undefined;
|
|
13
|
+
const blockerStr = ticket.blockers.length
|
|
14
|
+
? `Blocked by: ${ticket.blockers.join(', ')}`
|
|
15
|
+
: 'None';
|
|
16
|
+
return {
|
|
17
|
+
key: ticket.key,
|
|
18
|
+
title: ticket.title,
|
|
19
|
+
description: ticket.description,
|
|
20
|
+
parentInfo: ticket.epicKey ?? 'none',
|
|
21
|
+
blockers: blockerStr,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
case 'github': {
|
|
25
|
+
const { env } = config;
|
|
26
|
+
const username = await resolveUsername(env.GITHUB_TOKEN);
|
|
27
|
+
const ticket = await fetchGitHubIssue(env.GITHUB_TOKEN, env.GITHUB_REPO, env.CLANCY_LABEL, username);
|
|
28
|
+
if (!ticket)
|
|
29
|
+
return undefined;
|
|
30
|
+
return {
|
|
31
|
+
key: ticket.key,
|
|
32
|
+
title: ticket.title,
|
|
33
|
+
description: ticket.description,
|
|
34
|
+
parentInfo: ticket.milestone ?? 'none',
|
|
35
|
+
blockers: 'None',
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
case 'linear': {
|
|
39
|
+
const { env } = config;
|
|
40
|
+
const ticket = await fetchLinearIssue({
|
|
41
|
+
LINEAR_API_KEY: env.LINEAR_API_KEY,
|
|
42
|
+
LINEAR_TEAM_ID: env.LINEAR_TEAM_ID,
|
|
43
|
+
CLANCY_LABEL: env.CLANCY_LABEL,
|
|
44
|
+
});
|
|
45
|
+
if (!ticket)
|
|
46
|
+
return undefined;
|
|
47
|
+
return {
|
|
48
|
+
key: ticket.key,
|
|
49
|
+
title: ticket.title,
|
|
50
|
+
description: ticket.description,
|
|
51
|
+
parentInfo: ticket.parentIdentifier ?? 'none',
|
|
52
|
+
blockers: 'None',
|
|
53
|
+
linearIssueId: ticket.issueId,
|
|
54
|
+
issueId: ticket.issueId,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=fetch-ticket.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetch-ticket.js","sourceRoot":"","sources":["../../../src/scripts/once/fetch-ticket.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,IAAI,gBAAgB,EAC9B,eAAe,GAChB,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EACL,eAAe,EACf,WAAW,IAAI,eAAe,GAC/B,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,UAAU,IAAI,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AAKlF,gFAAgF;AAEhF,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,MAAmB;IAEnB,QAAQ,MAAM,CAAC,QAAQ,EAAE,CAAC;QACxB,KAAK,MAAM,CAAC,CAAC,CAAC;YACZ,MAAM,EAAE,GAAG,EAAE,GAAG,MAAM,CAAC;YACvB,MAAM,IAAI,GAAG,eAAe,CAAC,GAAG,CAAC,SAAS,EAAE,GAAG,CAAC,cAAc,CAAC,CAAC;YAChE,MAAM,MAAM,GAAG,MAAM,eAAe,CAClC,GAAG,CAAC,aAAa,EACjB,IAAI,EACJ,GAAG,CAAC,gBAAgB,EACpB,GAAG,CAAC,iBAAiB,IAAI,OAAO,EAChC,GAAG,CAAC,iBAAiB,EACrB,GAAG,CAAC,YAAY,CACjB,CAAC;YAEF,IAAI,CAAC,MAAM;gBAAE,OAAO,SAAS,CAAC;YAE9B,MAAM,UAAU,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM;gBACvC,CAAC,CAAC,eAAe,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;gBAC7C,CAAC,CAAC,MAAM,CAAC;YAEX,OAAO;gBACL,GAAG,EAAE,MAAM,CAAC,GAAG;gBACf,KAAK,EAAE,MAAM,CAAC,KAAK;gBACnB,WAAW,EAAE,MAAM,CAAC,WAAW;gBAC/B,UAAU,EAAE,MAAM,CAAC,OAAO,IAAI,MAAM;gBACpC,QAAQ,EAAE,UAAU;aACrB,CAAC;QACJ,CAAC;QAED,KAAK,QAAQ,CAAC,CAAC,CAAC;YACd,MAAM,EAAE,GAAG,EAAE,GAAG,MAAM,CAAC;YACvB,MAAM,QAAQ,GAAG,MAAM,eAAe,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;YACzD,MAAM,MAAM,GAAG,MAAM,gBAAgB,CACnC,GAAG,CAAC,YAAY,EAChB,GAAG,CAAC,WAAW,EACf,GAAG,CAAC,YAAY,EAChB,QAAQ,CACT,CAAC;YAEF,IAAI,CAAC,MAAM;gBAAE,OAAO,SAAS,CAAC;YAE9B,OAAO;gBACL,GAAG,EAAE,MAAM,CAAC,GAAG;gBACf,KAAK,EAAE,MAAM,CAAC,KAAK;gBACnB,WAAW,EAAE,MAAM,CAAC,WAAW;gBAC/B,UAAU,EAAE,MAAM,CAAC,SAAS,IAAI,MAAM;gBACtC,QAAQ,EAAE,MAAM;aACjB,CAAC;QACJ,CAAC;QAED,KAAK,QAAQ,CAAC,CAAC,CAAC;YACd,MAAM,EAAE,GAAG,EAAE,GAAG,MAAM,CAAC;YACvB,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC;gBACpC,cAAc,EAAE,GAAG,CAAC,cAAc;gBAClC,cAAc,EAAE,GAAG,CAAC,cAAc;gBAClC,YAAY,EAAE,GAAG,CAAC,YAAY;aAC/B,CAAC,CAAC;YAEH,IAAI,CAAC,MAAM;gBAAE,OAAO,SAAS,CAAC;YAE9B,OAAO;gBACL,GAAG,EAAE,MAAM,CAAC,GAAG;gBACf,KAAK,EAAE,MAAM,CAAC,KAAK;gBACnB,WAAW,EAAE,MAAM,CAAC,WAAW;gBAC/B,UAAU,EAAE,MAAM,CAAC,gBAAgB,IAAI,MAAM;gBAC7C,QAAQ,EAAE,MAAM;gBAChB,aAAa,EAAE,MAAM,CAAC,OAAO;gBAC7B,OAAO,EAAE,MAAM,CAAC,OAAO;aACxB,CAAC;QACJ,CAAC;IACH,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { BoardConfig } from '../../scripts/shared/env-schema/env-schema.js';
|
|
2
|
+
import type { RemoteInfo } from '../../types/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* Resolve a git host token from the board config's env.
|
|
5
|
+
*
|
|
6
|
+
* For GitHub boards, `GITHUB_TOKEN` is always present.
|
|
7
|
+
* For Jira/Linear boards, check the shared optional vars.
|
|
8
|
+
*/
|
|
9
|
+
export declare function resolveGitToken(config: BoardConfig, remote: RemoteInfo): {
|
|
10
|
+
token: string;
|
|
11
|
+
username?: string;
|
|
12
|
+
} | undefined;
|
|
13
|
+
//# sourceMappingURL=git-token.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git-token.d.ts","sourceRoot":"","sources":["../../../src/scripts/once/git-token.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2CAA2C,CAAC;AAC7E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAInD;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,WAAW,EACnB,MAAM,EAAE,UAAU,GACjB;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAmBlD"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { sharedEnv } from './board-ops.js';
|
|
2
|
+
/**
|
|
3
|
+
* Resolve a git host token from the board config's env.
|
|
4
|
+
*
|
|
5
|
+
* For GitHub boards, `GITHUB_TOKEN` is always present.
|
|
6
|
+
* For Jira/Linear boards, check the shared optional vars.
|
|
7
|
+
*/
|
|
8
|
+
export function resolveGitToken(config, remote) {
|
|
9
|
+
const env = sharedEnv(config);
|
|
10
|
+
switch (remote.host) {
|
|
11
|
+
case 'github':
|
|
12
|
+
if (env.GITHUB_TOKEN)
|
|
13
|
+
return { token: env.GITHUB_TOKEN };
|
|
14
|
+
break;
|
|
15
|
+
case 'gitlab':
|
|
16
|
+
if (env.GITLAB_TOKEN)
|
|
17
|
+
return { token: env.GITLAB_TOKEN };
|
|
18
|
+
break;
|
|
19
|
+
case 'bitbucket':
|
|
20
|
+
if (env.BITBUCKET_USER && env.BITBUCKET_TOKEN)
|
|
21
|
+
return { token: env.BITBUCKET_TOKEN, username: env.BITBUCKET_USER };
|
|
22
|
+
break;
|
|
23
|
+
case 'bitbucket-server':
|
|
24
|
+
if (env.BITBUCKET_TOKEN)
|
|
25
|
+
return { token: env.BITBUCKET_TOKEN };
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=git-token.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git-token.js","sourceRoot":"","sources":["../../../src/scripts/once/git-token.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAE3C;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAC7B,MAAmB,EACnB,MAAkB;IAElB,MAAM,GAAG,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;IAE9B,QAAQ,MAAM,CAAC,IAAI,EAAE,CAAC;QACpB,KAAK,QAAQ;YACX,IAAI,GAAG,CAAC,YAAY;gBAAE,OAAO,EAAE,KAAK,EAAE,GAAG,CAAC,YAAY,EAAE,CAAC;YACzD,MAAM;QACR,KAAK,QAAQ;YACX,IAAI,GAAG,CAAC,YAAY;gBAAE,OAAO,EAAE,KAAK,EAAE,GAAG,CAAC,YAAY,EAAE,CAAC;YACzD,MAAM;QACR,KAAK,WAAW;YACd,IAAI,GAAG,CAAC,cAAc,IAAI,GAAG,CAAC,eAAe;gBAC3C,OAAO,EAAE,KAAK,EAAE,GAAG,CAAC,eAAe,EAAE,QAAQ,EAAE,GAAG,CAAC,cAAc,EAAE,CAAC;YACtE,MAAM;QACR,KAAK,kBAAkB;YACrB,IAAI,GAAG,CAAC,eAAe;gBAAE,OAAO,EAAE,KAAK,EAAE,GAAG,CAAC,eAAe,EAAE,CAAC;YAC/D,MAAM;IACV,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"once.d.ts","sourceRoot":"","sources":["../../../src/scripts/once/once.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"once.d.ts","sourceRoot":"","sources":["../../../src/scripts/once/once.ts"],"names":[],"mappings":"AAqDA;;;;;;;;;GASG;AACH,wBAAsB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAsUvD"}
|
|
@@ -10,431 +10,21 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { resolve } from 'node:path';
|
|
12
12
|
import { fileURLToPath } from 'node:url';
|
|
13
|
-
import { closeIssue, fetchIssue as fetchGitHubIssue, isValidRepo, pingGitHub, resolveUsername, } from '../../scripts/board/github/github.js';
|
|
14
|
-
import { buildAuthHeader, fetchTicket as fetchJiraTicket, isSafeJqlValue, pingJira, transitionIssue as transitionJiraIssue, } from '../../scripts/board/jira/jira.js';
|
|
15
|
-
import { fetchIssue as fetchLinearIssue, isValidTeamId, pingLinear, transitionIssue as transitionLinearIssue, } from '../../scripts/board/linear/linear.js';
|
|
16
13
|
import { computeTargetBranch, computeTicketBranch, } from '../../scripts/shared/branch/branch.js';
|
|
17
14
|
import { invokeClaudeSession } from '../../scripts/shared/claude-cli/claude-cli.js';
|
|
18
15
|
import { detectBoard } from '../../scripts/shared/env-schema/env-schema.js';
|
|
19
16
|
import { checkFeasibility } from '../../scripts/shared/feasibility/feasibility.js';
|
|
20
17
|
import { formatDuration } from '../../scripts/shared/format/format.js';
|
|
21
|
-
import { checkout, currentBranch,
|
|
18
|
+
import { checkout, currentBranch, diffAgainstBranch, ensureBranch, fetchRemoteBranch, } from '../../scripts/shared/git-ops/git-ops.js';
|
|
22
19
|
import { sendNotification } from '../../scripts/shared/notify/notify.js';
|
|
23
20
|
import { runPreflight } from '../../scripts/shared/preflight/preflight.js';
|
|
24
|
-
import { appendProgress, countReworkCycles,
|
|
21
|
+
import { appendProgress, countReworkCycles, } from '../../scripts/shared/progress/progress.js';
|
|
25
22
|
import { buildPrompt, buildReworkPrompt, } from '../../scripts/shared/prompt/prompt.js';
|
|
26
|
-
import { checkPrReviewState as checkBitbucketPrReviewState, checkServerPrReviewState as checkBitbucketServerPrReviewState, createPullRequest as createBitbucketPr, createServerPullRequest as createBitbucketServerPr, fetchPrReviewComments as fetchBitbucketPrReviewComments, fetchServerPrReviewComments as fetchBitbucketServerPrReviewComments, } from '../../scripts/shared/pull-request/bitbucket/bitbucket.js';
|
|
27
|
-
import { checkPrReviewState as checkGitHubPrReviewState, createPullRequest as createGitHubPr, fetchPrReviewComments as fetchGitHubPrReviewComments, } from '../../scripts/shared/pull-request/github/github.js';
|
|
28
|
-
import { checkMrReviewState as checkGitLabMrReviewState, createMergeRequest as createGitLabMr, fetchMrReviewComments as fetchGitLabMrReviewComments, } from '../../scripts/shared/pull-request/gitlab/gitlab.js';
|
|
29
|
-
import { buildPrBody } from '../../scripts/shared/pull-request/pr-body/pr-body.js';
|
|
30
|
-
import { buildApiBaseUrl, detectRemote, } from '../../scripts/shared/remote/remote.js';
|
|
31
23
|
import { bold, dim, green, red, yellow } from '../../utils/ansi/ansi.js';
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
-
// ─── Board-specific fetch ────────────────────────────────────────────────────
|
|
38
|
-
async function fetchTicket(config) {
|
|
39
|
-
switch (config.provider) {
|
|
40
|
-
case 'jira': {
|
|
41
|
-
const { env } = config;
|
|
42
|
-
const auth = buildAuthHeader(env.JIRA_USER, env.JIRA_API_TOKEN);
|
|
43
|
-
const ticket = await fetchJiraTicket(env.JIRA_BASE_URL, auth, env.JIRA_PROJECT_KEY, env.CLANCY_JQL_STATUS ?? 'To Do', env.CLANCY_JQL_SPRINT, env.CLANCY_LABEL);
|
|
44
|
-
if (!ticket)
|
|
45
|
-
return undefined;
|
|
46
|
-
const blockerStr = ticket.blockers.length
|
|
47
|
-
? `Blocked by: ${ticket.blockers.join(', ')}`
|
|
48
|
-
: 'None';
|
|
49
|
-
return {
|
|
50
|
-
key: ticket.key,
|
|
51
|
-
title: ticket.title,
|
|
52
|
-
description: ticket.description,
|
|
53
|
-
parentInfo: ticket.epicKey ?? 'none',
|
|
54
|
-
blockers: blockerStr,
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
case 'github': {
|
|
58
|
-
const { env } = config;
|
|
59
|
-
const username = await resolveUsername(env.GITHUB_TOKEN);
|
|
60
|
-
const ticket = await fetchGitHubIssue(env.GITHUB_TOKEN, env.GITHUB_REPO, env.CLANCY_LABEL, username);
|
|
61
|
-
if (!ticket)
|
|
62
|
-
return undefined;
|
|
63
|
-
return {
|
|
64
|
-
key: ticket.key,
|
|
65
|
-
title: ticket.title,
|
|
66
|
-
description: ticket.description,
|
|
67
|
-
parentInfo: ticket.milestone ?? 'none',
|
|
68
|
-
blockers: 'None',
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
case 'linear': {
|
|
72
|
-
const { env } = config;
|
|
73
|
-
const ticket = await fetchLinearIssue({
|
|
74
|
-
LINEAR_API_KEY: env.LINEAR_API_KEY,
|
|
75
|
-
LINEAR_TEAM_ID: env.LINEAR_TEAM_ID,
|
|
76
|
-
CLANCY_LABEL: env.CLANCY_LABEL,
|
|
77
|
-
});
|
|
78
|
-
if (!ticket)
|
|
79
|
-
return undefined;
|
|
80
|
-
return {
|
|
81
|
-
key: ticket.key,
|
|
82
|
-
title: ticket.title,
|
|
83
|
-
description: ticket.description,
|
|
84
|
-
parentInfo: ticket.parentIdentifier ?? 'none',
|
|
85
|
-
blockers: 'None',
|
|
86
|
-
linearIssueId: ticket.issueId,
|
|
87
|
-
issueId: ticket.issueId,
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
// ─── PR-based rework detection ────────────────────────────────────────────────
|
|
93
|
-
/**
|
|
94
|
-
* Check open PRs for review feedback requesting changes.
|
|
95
|
-
*
|
|
96
|
-
* Scans progress.txt for tickets with status PR_CREATED, then checks
|
|
97
|
-
* the corresponding PR's review state on the detected remote platform.
|
|
98
|
-
* If a reviewer has requested changes, returns the ticket and feedback.
|
|
99
|
-
*
|
|
100
|
-
* This is best-effort — errors are swallowed so the orchestrator
|
|
101
|
-
* can fall through to fresh ticket fetch.
|
|
102
|
-
*/
|
|
103
|
-
async function fetchReworkFromPrReview(config) {
|
|
104
|
-
const prCreated = findEntriesWithStatus(process.cwd(), 'PR_CREATED');
|
|
105
|
-
const reworked = findEntriesWithStatus(process.cwd(), 'REWORK');
|
|
106
|
-
const candidates = [...prCreated, ...reworked];
|
|
107
|
-
if (candidates.length === 0)
|
|
108
|
-
return undefined;
|
|
109
|
-
const platformOverride = sharedEnv(config).CLANCY_GIT_PLATFORM;
|
|
110
|
-
const remote = detectRemote(platformOverride);
|
|
111
|
-
if (remote.host === 'none' ||
|
|
112
|
-
remote.host === 'unknown' ||
|
|
113
|
-
remote.host === 'azure') {
|
|
114
|
-
return undefined;
|
|
115
|
-
}
|
|
116
|
-
const creds = resolveGitToken(config, remote);
|
|
117
|
-
if (!creds)
|
|
118
|
-
return undefined;
|
|
119
|
-
const apiBase = buildApiBaseUrl(remote, sharedEnv(config).CLANCY_GIT_API_URL);
|
|
120
|
-
if (!apiBase)
|
|
121
|
-
return undefined;
|
|
122
|
-
// Limit to first 5 candidates to avoid rate limits
|
|
123
|
-
const toCheck = candidates.slice(0, 5);
|
|
124
|
-
for (const entry of toCheck) {
|
|
125
|
-
const branch = computeTicketBranch(config.provider, entry.key);
|
|
126
|
-
// Convert progress timestamp (YYYY-MM-DD HH:MM) to ISO 8601 for API filtering.
|
|
127
|
-
// Only comments created AFTER this timestamp should trigger rework,
|
|
128
|
-
// preventing stale inline comments from causing infinite rework loops.
|
|
129
|
-
// Parse local timestamp (YYYY-MM-DD HH:MM) and convert to ISO 8601.
|
|
130
|
-
// Falls back to undefined if timestamp is invalid (skips filtering).
|
|
131
|
-
let since;
|
|
132
|
-
if (entry.timestamp) {
|
|
133
|
-
const date = new Date(entry.timestamp.replace(' ', 'T'));
|
|
134
|
-
since = Number.isNaN(date.getTime()) ? undefined : date.toISOString();
|
|
135
|
-
}
|
|
136
|
-
let reviewState;
|
|
137
|
-
switch (remote.host) {
|
|
138
|
-
case 'github':
|
|
139
|
-
reviewState = await checkGitHubPrReviewState(creds.token, `${remote.owner}/${remote.repo}`, branch, remote.owner, apiBase, since);
|
|
140
|
-
break;
|
|
141
|
-
case 'gitlab':
|
|
142
|
-
reviewState = await checkGitLabMrReviewState(creds.token, apiBase, remote.projectPath, branch, since);
|
|
143
|
-
break;
|
|
144
|
-
case 'bitbucket':
|
|
145
|
-
reviewState = await checkBitbucketPrReviewState(creds.username, creds.token, remote.workspace, remote.repoSlug, branch, since);
|
|
146
|
-
break;
|
|
147
|
-
case 'bitbucket-server':
|
|
148
|
-
reviewState = await checkBitbucketServerPrReviewState(creds.token, apiBase, remote.projectKey, remote.repoSlug, branch, since);
|
|
149
|
-
break;
|
|
150
|
-
}
|
|
151
|
-
if (reviewState?.changesRequested) {
|
|
152
|
-
// Fetch review comments for the PR
|
|
153
|
-
let feedback = [];
|
|
154
|
-
switch (remote.host) {
|
|
155
|
-
case 'github':
|
|
156
|
-
feedback = await fetchGitHubPrReviewComments(creds.token, `${remote.owner}/${remote.repo}`, reviewState.prNumber, apiBase, since);
|
|
157
|
-
break;
|
|
158
|
-
case 'gitlab':
|
|
159
|
-
feedback = await fetchGitLabMrReviewComments(creds.token, apiBase, remote.projectPath, reviewState.prNumber, since);
|
|
160
|
-
break;
|
|
161
|
-
case 'bitbucket':
|
|
162
|
-
feedback = await fetchBitbucketPrReviewComments(creds.username, creds.token, remote.workspace, remote.repoSlug, reviewState.prNumber, since);
|
|
163
|
-
break;
|
|
164
|
-
case 'bitbucket-server':
|
|
165
|
-
feedback = await fetchBitbucketServerPrReviewComments(creds.token, apiBase, remote.projectKey, remote.repoSlug, reviewState.prNumber, since);
|
|
166
|
-
break;
|
|
167
|
-
}
|
|
168
|
-
const ticket = {
|
|
169
|
-
key: entry.key,
|
|
170
|
-
title: entry.summary,
|
|
171
|
-
description: '',
|
|
172
|
-
parentInfo: 'none',
|
|
173
|
-
blockers: 'None',
|
|
174
|
-
};
|
|
175
|
-
return { ticket, feedback, prNumber: reviewState.prNumber };
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
return undefined;
|
|
179
|
-
}
|
|
180
|
-
// ─── Board-specific ping ─────────────────────────────────────────────────────
|
|
181
|
-
async function pingBoard(config) {
|
|
182
|
-
switch (config.provider) {
|
|
183
|
-
case 'jira': {
|
|
184
|
-
const { env } = config;
|
|
185
|
-
const auth = buildAuthHeader(env.JIRA_USER, env.JIRA_API_TOKEN);
|
|
186
|
-
return pingJira(env.JIRA_BASE_URL, env.JIRA_PROJECT_KEY, auth);
|
|
187
|
-
}
|
|
188
|
-
case 'github':
|
|
189
|
-
return pingGitHub(config.env.GITHUB_TOKEN, config.env.GITHUB_REPO);
|
|
190
|
-
case 'linear':
|
|
191
|
-
return pingLinear(config.env.LINEAR_API_KEY);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
// ─── Board-specific validation ───────────────────────────────────────────────
|
|
195
|
-
function validateInputs(config) {
|
|
196
|
-
switch (config.provider) {
|
|
197
|
-
case 'jira': {
|
|
198
|
-
const { env } = config;
|
|
199
|
-
if (!isSafeJqlValue(env.JIRA_PROJECT_KEY)) {
|
|
200
|
-
return '✗ JIRA_PROJECT_KEY contains invalid characters';
|
|
201
|
-
}
|
|
202
|
-
if (env.CLANCY_LABEL && !isSafeJqlValue(env.CLANCY_LABEL)) {
|
|
203
|
-
return '✗ CLANCY_LABEL contains invalid characters';
|
|
204
|
-
}
|
|
205
|
-
if (env.CLANCY_JQL_STATUS && !isSafeJqlValue(env.CLANCY_JQL_STATUS)) {
|
|
206
|
-
return '✗ CLANCY_JQL_STATUS contains invalid characters';
|
|
207
|
-
}
|
|
208
|
-
return undefined;
|
|
209
|
-
}
|
|
210
|
-
case 'github': {
|
|
211
|
-
if (!isValidRepo(config.env.GITHUB_REPO)) {
|
|
212
|
-
return '✗ GITHUB_REPO format is invalid — expected owner/repo';
|
|
213
|
-
}
|
|
214
|
-
return undefined;
|
|
215
|
-
}
|
|
216
|
-
case 'linear': {
|
|
217
|
-
if (!isValidTeamId(config.env.LINEAR_TEAM_ID)) {
|
|
218
|
-
return '✗ LINEAR_TEAM_ID contains invalid characters';
|
|
219
|
-
}
|
|
220
|
-
return undefined;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
// ─── Board-specific transitions ──────────────────────────────────────────────
|
|
225
|
-
async function transitionToStatus(config, ticket, statusName) {
|
|
226
|
-
switch (config.provider) {
|
|
227
|
-
case 'jira': {
|
|
228
|
-
const { env } = config;
|
|
229
|
-
const auth = buildAuthHeader(env.JIRA_USER, env.JIRA_API_TOKEN);
|
|
230
|
-
const issueKey = ticket.key;
|
|
231
|
-
const ok = await transitionJiraIssue(env.JIRA_BASE_URL, auth, issueKey, statusName);
|
|
232
|
-
if (ok)
|
|
233
|
-
console.log(` → Transitioned to ${statusName}`);
|
|
234
|
-
break;
|
|
235
|
-
}
|
|
236
|
-
case 'github': {
|
|
237
|
-
// GitHub Issues only has open/closed — status transitions not applicable
|
|
238
|
-
// closeIssue is called separately after merge
|
|
239
|
-
break;
|
|
240
|
-
}
|
|
241
|
-
case 'linear': {
|
|
242
|
-
const { env } = config;
|
|
243
|
-
if (!ticket.linearIssueId)
|
|
244
|
-
break;
|
|
245
|
-
const ok = await transitionLinearIssue(env.LINEAR_API_KEY, env.LINEAR_TEAM_ID, ticket.linearIssueId, statusName);
|
|
246
|
-
if (ok)
|
|
247
|
-
console.log(` → Transitioned to ${statusName}`);
|
|
248
|
-
break;
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
// ─── PR/MR creation ──────────────────────────────────────────────────────────
|
|
253
|
-
/**
|
|
254
|
-
* Resolve a git host token from the board config's env.
|
|
255
|
-
*
|
|
256
|
-
* For GitHub boards, `GITHUB_TOKEN` is always present.
|
|
257
|
-
* For Jira/Linear boards, check the shared optional vars.
|
|
258
|
-
*/
|
|
259
|
-
function resolveGitToken(config, remote) {
|
|
260
|
-
const env = sharedEnv(config);
|
|
261
|
-
switch (remote.host) {
|
|
262
|
-
case 'github':
|
|
263
|
-
if (env.GITHUB_TOKEN)
|
|
264
|
-
return { token: env.GITHUB_TOKEN };
|
|
265
|
-
break;
|
|
266
|
-
case 'gitlab':
|
|
267
|
-
if (env.GITLAB_TOKEN)
|
|
268
|
-
return { token: env.GITLAB_TOKEN };
|
|
269
|
-
break;
|
|
270
|
-
case 'bitbucket':
|
|
271
|
-
if (env.BITBUCKET_USER && env.BITBUCKET_TOKEN)
|
|
272
|
-
return { token: env.BITBUCKET_TOKEN, username: env.BITBUCKET_USER };
|
|
273
|
-
break;
|
|
274
|
-
case 'bitbucket-server':
|
|
275
|
-
if (env.BITBUCKET_TOKEN)
|
|
276
|
-
return { token: env.BITBUCKET_TOKEN };
|
|
277
|
-
break;
|
|
278
|
-
}
|
|
279
|
-
return undefined;
|
|
280
|
-
}
|
|
281
|
-
/**
|
|
282
|
-
* Attempt to create a PR/MR on the detected remote platform.
|
|
283
|
-
*/
|
|
284
|
-
async function attemptPrCreation(config, remote, ticketBranch, targetBranch, title, body) {
|
|
285
|
-
const creds = resolveGitToken(config, remote);
|
|
286
|
-
if (!creds)
|
|
287
|
-
return undefined;
|
|
288
|
-
const apiBase = buildApiBaseUrl(remote, sharedEnv(config).CLANCY_GIT_API_URL);
|
|
289
|
-
if (!apiBase)
|
|
290
|
-
return undefined;
|
|
291
|
-
switch (remote.host) {
|
|
292
|
-
case 'github':
|
|
293
|
-
return createGitHubPr(creds.token, `${remote.owner}/${remote.repo}`, ticketBranch, targetBranch, title, body, apiBase);
|
|
294
|
-
case 'gitlab':
|
|
295
|
-
return createGitLabMr(creds.token, apiBase, remote.projectPath, ticketBranch, targetBranch, title, body);
|
|
296
|
-
case 'bitbucket':
|
|
297
|
-
return createBitbucketPr(creds.username, creds.token, remote.workspace, remote.repoSlug, ticketBranch, targetBranch, title, body);
|
|
298
|
-
case 'bitbucket-server':
|
|
299
|
-
return createBitbucketServerPr(creds.token, apiBase, remote.projectKey, remote.repoSlug, ticketBranch, targetBranch, title, body);
|
|
300
|
-
default:
|
|
301
|
-
return undefined;
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
/**
|
|
305
|
-
* Build a manual PR/MR URL for the user to click.
|
|
306
|
-
*/
|
|
307
|
-
function buildManualPrUrl(remote, ticketBranch, targetBranch) {
|
|
308
|
-
const encodedTicket = encodeURIComponent(ticketBranch);
|
|
309
|
-
const encodedTarget = encodeURIComponent(targetBranch);
|
|
310
|
-
if (remote.host === 'github') {
|
|
311
|
-
return `https://${remote.hostname}/${remote.owner}/${remote.repo}/compare/${encodedTarget}...${encodedTicket}`;
|
|
312
|
-
}
|
|
313
|
-
if (remote.host === 'gitlab') {
|
|
314
|
-
return `https://${remote.hostname}/${remote.projectPath}/-/merge_requests/new?merge_request[source_branch]=${encodedTicket}&merge_request[target_branch]=${encodedTarget}`;
|
|
315
|
-
}
|
|
316
|
-
if (remote.host === 'bitbucket') {
|
|
317
|
-
return `https://${remote.hostname}/${remote.workspace}/${remote.repoSlug}/pull-requests/new?source=${encodedTicket}&dest=${encodedTarget}`;
|
|
318
|
-
}
|
|
319
|
-
return undefined;
|
|
320
|
-
}
|
|
321
|
-
// ─── Delivery paths ──────────────────────────────────────────────────────────
|
|
322
|
-
/**
|
|
323
|
-
* Epic/parent flow: squash merge locally, delete branch, transition to Done.
|
|
324
|
-
*/
|
|
325
|
-
async function deliverViaEpicMerge(config, ticket, ticketBranch, targetBranch) {
|
|
326
|
-
checkout(targetBranch);
|
|
327
|
-
const commitMsg = `feat(${ticket.key}): ${ticket.title}`;
|
|
328
|
-
const hadChanges = squashMerge(ticketBranch, commitMsg);
|
|
329
|
-
if (!hadChanges) {
|
|
330
|
-
console.log(yellow('⚠ No changes staged after squash merge. Claude may not have committed any work.'));
|
|
331
|
-
}
|
|
332
|
-
deleteBranch(ticketBranch);
|
|
333
|
-
// Transition to Done / close issue (best-effort)
|
|
334
|
-
const statusDone = config.env.CLANCY_STATUS_DONE;
|
|
335
|
-
if (config.provider === 'github') {
|
|
336
|
-
const issueNumber = parseInt(ticket.key.replace('#', ''), 10);
|
|
337
|
-
if (Number.isNaN(issueNumber)) {
|
|
338
|
-
console.log(`⚠ Could not parse issue number from ${ticket.key}. Close it manually on GitHub.`);
|
|
339
|
-
}
|
|
340
|
-
else {
|
|
341
|
-
const closed = await closeIssue(config.env.GITHUB_TOKEN, config.env.GITHUB_REPO, issueNumber);
|
|
342
|
-
if (!closed) {
|
|
343
|
-
console.log(`⚠ Could not close issue ${ticket.key}. Close it manually on GitHub.`);
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
else if (statusDone) {
|
|
348
|
-
await transitionToStatus(config, ticket, statusDone);
|
|
349
|
-
}
|
|
350
|
-
appendProgress(process.cwd(), ticket.key, ticket.title, 'DONE');
|
|
351
|
-
}
|
|
352
|
-
/**
|
|
353
|
-
* PR flow: push branch to remote, create PR/MR, transition to In Review.
|
|
354
|
-
*
|
|
355
|
-
* @returns `false` if the push failed (caller should handle early return).
|
|
356
|
-
*/
|
|
357
|
-
async function deliverViaPullRequest(config, ticket, ticketBranch, targetBranch, startTime) {
|
|
358
|
-
const pushed = pushBranch(ticketBranch);
|
|
359
|
-
if (!pushed) {
|
|
360
|
-
console.log(yellow(`⚠ Could not push ${ticketBranch} to origin.`));
|
|
361
|
-
console.log(dim(' The branch is still available locally. Push manually:'));
|
|
362
|
-
console.log(dim(` git push -u origin ${ticketBranch}`));
|
|
363
|
-
appendProgress(process.cwd(), ticket.key, ticket.title, 'PUSH_FAILED');
|
|
364
|
-
checkout(targetBranch);
|
|
365
|
-
const elapsed = formatDuration(Date.now() - startTime);
|
|
366
|
-
console.log('');
|
|
367
|
-
console.log(yellow(`⚠ ${ticket.key} implemented but push failed`) +
|
|
368
|
-
dim(` (${elapsed})`));
|
|
369
|
-
return false;
|
|
370
|
-
}
|
|
371
|
-
console.log(green(` ✓ Pushed ${ticketBranch}`));
|
|
372
|
-
// Attempt PR/MR creation
|
|
373
|
-
const platformOverride = sharedEnv(config).CLANCY_GIT_PLATFORM;
|
|
374
|
-
const remote = detectRemote(platformOverride);
|
|
375
|
-
const prTitle = `feat(${ticket.key}): ${ticket.title}`;
|
|
376
|
-
const prBody = buildPrBody(config, {
|
|
377
|
-
key: ticket.key,
|
|
378
|
-
title: ticket.title,
|
|
379
|
-
description: ticket.description,
|
|
380
|
-
provider: config.provider,
|
|
381
|
-
});
|
|
382
|
-
if (remote.host !== 'none' &&
|
|
383
|
-
remote.host !== 'unknown' &&
|
|
384
|
-
remote.host !== 'azure') {
|
|
385
|
-
const pr = await attemptPrCreation(config, remote, ticketBranch, targetBranch, prTitle, prBody);
|
|
386
|
-
if (pr?.ok) {
|
|
387
|
-
console.log(green(` ✓ PR created: ${pr.url}`));
|
|
388
|
-
appendProgress(process.cwd(), ticket.key, ticket.title, 'PR_CREATED');
|
|
389
|
-
}
|
|
390
|
-
else if (pr && !pr.ok && pr.alreadyExists) {
|
|
391
|
-
console.log(yellow(` ⚠ A PR/MR already exists for ${ticketBranch}. Branch pushed.`));
|
|
392
|
-
appendProgress(process.cwd(), ticket.key, ticket.title, 'PUSHED');
|
|
393
|
-
}
|
|
394
|
-
else if (pr && !pr.ok) {
|
|
395
|
-
console.log(yellow(` ⚠ PR/MR creation failed: ${pr.error}`));
|
|
396
|
-
const manualUrl = buildManualPrUrl(remote, ticketBranch, targetBranch);
|
|
397
|
-
if (manualUrl) {
|
|
398
|
-
console.log(dim(` Create one manually: ${manualUrl}`));
|
|
399
|
-
}
|
|
400
|
-
else {
|
|
401
|
-
console.log(dim(' Branch pushed — create a PR/MR manually.'));
|
|
402
|
-
}
|
|
403
|
-
appendProgress(process.cwd(), ticket.key, ticket.title, 'PUSHED');
|
|
404
|
-
}
|
|
405
|
-
else {
|
|
406
|
-
// No token available for this platform
|
|
407
|
-
const manualUrl = buildManualPrUrl(remote, ticketBranch, targetBranch);
|
|
408
|
-
if (manualUrl) {
|
|
409
|
-
console.log(dim(` Create a PR: ${manualUrl}`));
|
|
410
|
-
}
|
|
411
|
-
else {
|
|
412
|
-
console.log(dim(' Branch pushed to remote. Create a PR/MR manually.'));
|
|
413
|
-
}
|
|
414
|
-
appendProgress(process.cwd(), ticket.key, ticket.title, 'PUSHED');
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
else if (remote.host === 'none') {
|
|
418
|
-
console.log(yellow(`⚠ No git remote configured. Branch available locally: ${ticketBranch}`));
|
|
419
|
-
appendProgress(process.cwd(), ticket.key, ticket.title, 'LOCAL');
|
|
420
|
-
}
|
|
421
|
-
else {
|
|
422
|
-
// Unknown or Azure remote — just note the push
|
|
423
|
-
console.log(dim(' Branch pushed to remote. Create a PR/MR manually.'));
|
|
424
|
-
appendProgress(process.cwd(), ticket.key, ticket.title, 'PUSHED');
|
|
425
|
-
}
|
|
426
|
-
// Transition to In Review (not Done — PR hasn't been merged yet)
|
|
427
|
-
// For GitHub Issues: do NOT close — PR body has "Closes #N" for auto-close on merge
|
|
428
|
-
if (config.provider !== 'github') {
|
|
429
|
-
const statusReview = config.env.CLANCY_STATUS_REVIEW ?? config.env.CLANCY_STATUS_DONE;
|
|
430
|
-
if (statusReview) {
|
|
431
|
-
await transitionToStatus(config, ticket, statusReview);
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
// Switch back to target branch
|
|
435
|
-
checkout(targetBranch);
|
|
436
|
-
return true;
|
|
437
|
-
}
|
|
24
|
+
import { pingBoard, sharedEnv, transitionToStatus, validateInputs, } from './board-ops.js';
|
|
25
|
+
import { deliverViaEpicMerge, deliverViaPullRequest } from './deliver.js';
|
|
26
|
+
import { fetchTicket } from './fetch-ticket.js';
|
|
27
|
+
import { fetchReworkFromPrReview, postReworkActions } from './rework.js';
|
|
438
28
|
// ─── Main orchestrator ───────────────────────────────────────────────────────
|
|
439
29
|
/**
|
|
440
30
|
* Run the once orchestrator — full ticket lifecycle.
|
|
@@ -489,6 +79,9 @@ export async function run(argv) {
|
|
|
489
79
|
// 5. Check rework — PR-based detection, then fresh ticket
|
|
490
80
|
let isRework = false;
|
|
491
81
|
let prFeedback;
|
|
82
|
+
let reworkPrNumber;
|
|
83
|
+
let reworkDiscussionIds;
|
|
84
|
+
let reworkReviewers;
|
|
492
85
|
let ticket;
|
|
493
86
|
// PR-based rework (automatic, no config needed)
|
|
494
87
|
try {
|
|
@@ -497,6 +90,9 @@ export async function run(argv) {
|
|
|
497
90
|
isRework = true;
|
|
498
91
|
ticket = prRework.ticket;
|
|
499
92
|
prFeedback = prRework.feedback;
|
|
93
|
+
reworkPrNumber = prRework.prNumber;
|
|
94
|
+
reworkDiscussionIds = prRework.discussionIds;
|
|
95
|
+
reworkReviewers = prRework.reviewers;
|
|
500
96
|
console.log(yellow(` ↻ PR rework: [${ticket.key}] ${ticket.title}`));
|
|
501
97
|
}
|
|
502
98
|
}
|
|
@@ -609,7 +205,7 @@ export async function run(argv) {
|
|
|
609
205
|
description: ticket.description,
|
|
610
206
|
provider: config.provider,
|
|
611
207
|
feedbackComments: prFeedback ?? [],
|
|
612
|
-
previousContext:
|
|
208
|
+
previousContext: diffAgainstBranch(targetBranch),
|
|
613
209
|
});
|
|
614
210
|
}
|
|
615
211
|
else {
|
|
@@ -631,11 +227,17 @@ export async function run(argv) {
|
|
|
631
227
|
const hasParent = ticket.parentInfo !== 'none';
|
|
632
228
|
if (isRework) {
|
|
633
229
|
// PR-flow rework: push to existing branch, PR updates automatically
|
|
634
|
-
const delivered = await deliverViaPullRequest(config, ticket, ticketBranch, targetBranch, startTime);
|
|
635
|
-
if (!delivered)
|
|
230
|
+
const delivered = await deliverViaPullRequest(config, ticket, ticketBranch, targetBranch, startTime, true);
|
|
231
|
+
if (!delivered) {
|
|
232
|
+
appendProgress(process.cwd(), ticket.key, ticket.title, 'PUSH_FAILED');
|
|
636
233
|
return;
|
|
637
|
-
|
|
638
|
-
|
|
234
|
+
}
|
|
235
|
+
// Log single REWORK entry (skipLog prevents deliverViaPullRequest from double-logging)
|
|
236
|
+
appendProgress(process.cwd(), ticket.key, ticket.title, 'REWORK', reworkPrNumber);
|
|
237
|
+
// Post-rework actions (all best-effort)
|
|
238
|
+
if (reworkPrNumber != null) {
|
|
239
|
+
await postReworkActions(config, reworkPrNumber, prFeedback ?? [], reworkDiscussionIds, reworkReviewers);
|
|
240
|
+
}
|
|
639
241
|
}
|
|
640
242
|
else if (hasParent) {
|
|
641
243
|
await deliverViaEpicMerge(config, ticket, ticketBranch, targetBranch);
|