issue-flow 0.11.1 → 0.13.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.
- package/README.md +79 -274
- package/dist/agent-BXRIEKVM.js +238 -0
- package/dist/agent-BXRIEKVM.js.map +1 -0
- package/dist/{analyze-XV3K25K3.js → analyze-WS47UYGN.js} +32 -15
- package/dist/analyze-WS47UYGN.js.map +1 -0
- package/dist/{apply-WUJHDD7A.js → apply-OS62UWRN.js} +15 -5
- package/dist/{apply-WUJHDD7A.js.map → apply-OS62UWRN.js.map} +1 -1
- package/dist/bench-SA3BOME5.js +906 -0
- package/dist/bench-SA3BOME5.js.map +1 -0
- package/dist/chunk-37N2RJW2.js +120 -0
- package/dist/chunk-37N2RJW2.js.map +1 -0
- package/dist/chunk-3XLCNVMD.js +180 -0
- package/dist/chunk-3XLCNVMD.js.map +1 -0
- package/dist/chunk-5TB6KLC2.js +72 -0
- package/dist/chunk-5TB6KLC2.js.map +1 -0
- package/dist/{chunk-OBLVLIUS.js → chunk-664Y3NDX.js} +25 -13
- package/dist/chunk-664Y3NDX.js.map +1 -0
- package/dist/chunk-6FDFRNXZ.js +357 -0
- package/dist/chunk-6FDFRNXZ.js.map +1 -0
- package/dist/chunk-7KD66LIM.js +658 -0
- package/dist/chunk-7KD66LIM.js.map +1 -0
- package/dist/chunk-7Z4Y3VKM.js +26 -0
- package/dist/chunk-7Z4Y3VKM.js.map +1 -0
- package/dist/chunk-ARFVHC2F.js +1111 -0
- package/dist/chunk-ARFVHC2F.js.map +1 -0
- package/dist/{chunk-RKTEV6EG.js → chunk-BHJCGWQX.js} +141 -12
- package/dist/chunk-BHJCGWQX.js.map +1 -0
- package/dist/chunk-BQDQTIM7.js +74 -0
- package/dist/chunk-BQDQTIM7.js.map +1 -0
- package/dist/chunk-CNBYCMSU.js +2709 -0
- package/dist/chunk-CNBYCMSU.js.map +1 -0
- package/dist/chunk-CRGIQFUQ.js +83 -0
- package/dist/chunk-CRGIQFUQ.js.map +1 -0
- package/dist/chunk-DIG5XKQQ.js +298 -0
- package/dist/chunk-DIG5XKQQ.js.map +1 -0
- package/dist/chunk-FZDR7CGH.js +71 -0
- package/dist/chunk-FZDR7CGH.js.map +1 -0
- package/dist/chunk-HEF2RMKN.js +413 -0
- package/dist/chunk-HEF2RMKN.js.map +1 -0
- package/dist/{chunk-IVHZEYAX.js → chunk-HJGGNLWI.js} +25 -19
- package/dist/chunk-HJGGNLWI.js.map +1 -0
- package/dist/chunk-HQAHNIAR.js +283 -0
- package/dist/chunk-HQAHNIAR.js.map +1 -0
- package/dist/{chunk-GANSDJV7.js → chunk-IHIEXUTC.js} +46 -24
- package/dist/chunk-IHIEXUTC.js.map +1 -0
- package/dist/chunk-IV5SS4JN.js +84 -0
- package/dist/chunk-IV5SS4JN.js.map +1 -0
- package/dist/{chunk-HC2MT7BX.js → chunk-IYV6KNVF.js} +158 -592
- package/dist/chunk-IYV6KNVF.js.map +1 -0
- package/dist/{chunk-LVKQ2AKT.js → chunk-J62QSVP6.js} +10 -8
- package/dist/chunk-J62QSVP6.js.map +1 -0
- package/dist/chunk-KMMASOA6.js +134 -0
- package/dist/chunk-KMMASOA6.js.map +1 -0
- package/dist/chunk-KQHB7ZT3.js +559 -0
- package/dist/chunk-KQHB7ZT3.js.map +1 -0
- package/dist/chunk-L6HSKTQE.js +77 -0
- package/dist/chunk-L6HSKTQE.js.map +1 -0
- package/dist/{chunk-6N7LZZ5U.js → chunk-LZ6GFQAG.js} +474 -160
- package/dist/chunk-LZ6GFQAG.js.map +1 -0
- package/dist/{chunk-HQYXVIH6.js → chunk-MIVHK5YI.js} +5 -5
- package/dist/chunk-NEZZ6KDR.js +1217 -0
- package/dist/chunk-NEZZ6KDR.js.map +1 -0
- package/dist/{run-DBF2VWUN.js → chunk-OPNGPUKJ.js} +747 -594
- package/dist/chunk-OPNGPUKJ.js.map +1 -0
- package/dist/chunk-P3LRUNC6.js +16 -0
- package/dist/chunk-P3LRUNC6.js.map +1 -0
- package/dist/{chunk-BXCPXRBR.js → chunk-PIVGJM6K.js} +129 -32
- package/dist/chunk-PIVGJM6K.js.map +1 -0
- package/dist/{chunk-AAY6KM7N.js → chunk-PV4OX5TC.js} +167 -196
- package/dist/chunk-PV4OX5TC.js.map +1 -0
- package/dist/chunk-QCXPNSEL.js +141 -0
- package/dist/chunk-QCXPNSEL.js.map +1 -0
- package/dist/{chunk-34WNTK3K.js → chunk-QXDNBYQF.js} +53 -13
- package/dist/chunk-QXDNBYQF.js.map +1 -0
- package/dist/chunk-SNTTOQAD.js +30 -0
- package/dist/chunk-SNTTOQAD.js.map +1 -0
- package/dist/{chunk-EY6WMHV2.js → chunk-TM67EHSV.js} +67 -23
- package/dist/chunk-TM67EHSV.js.map +1 -0
- package/dist/chunk-TVN2JVOF.js +77 -0
- package/dist/chunk-TVN2JVOF.js.map +1 -0
- package/dist/chunk-TX5WIVYP.js +129 -0
- package/dist/chunk-TX5WIVYP.js.map +1 -0
- package/dist/{chunk-KQ4JQN7T.js → chunk-UJ7XWK7J.js} +83 -21
- package/dist/chunk-UJ7XWK7J.js.map +1 -0
- package/dist/{chunk-47VIN33V.js → chunk-UOCPC2Y2.js} +596 -17
- package/dist/chunk-UOCPC2Y2.js.map +1 -0
- package/dist/chunk-XEYH6ZZI.js +289 -0
- package/dist/chunk-XEYH6ZZI.js.map +1 -0
- package/dist/cli.js +390 -40
- package/dist/cli.js.map +1 -1
- package/dist/config-MEM2IWWN.js +76 -0
- package/dist/contract-JH2WBIO6.js +8 -0
- package/dist/conventions-WV4NDXGH.js +143 -0
- package/dist/conventions-WV4NDXGH.js.map +1 -0
- package/dist/execute-NITIHHCK.js +31 -0
- package/dist/{generate-TBVTLMOP.js → generate-MVQI7A45.js} +39 -19
- package/dist/{generate-TBVTLMOP.js.map → generate-MVQI7A45.js.map} +1 -1
- package/dist/git-YRVRUN3C.js +31 -0
- package/dist/init-OKWEC3EV.js +21 -0
- package/dist/operations-LU6JA3BX.js +374 -0
- package/dist/operations-LU6JA3BX.js.map +1 -0
- package/dist/permissions-7NMDFU27.js +18 -0
- package/dist/plan-LEJJF3DE.js +31 -0
- package/dist/{policy-XQW5NUIE.js → policy-JSYEK5B2.js} +17 -4
- package/dist/policy-JSYEK5B2.js.map +1 -0
- package/dist/pr-HPVUZ7S3.js +36 -0
- package/dist/pr-review-R2QZBT3F.js +29 -0
- package/dist/pr-review-R2QZBT3F.js.map +1 -0
- package/dist/prd-5O6GYCC6.js +30 -0
- package/dist/prd-5O6GYCC6.js.map +1 -0
- package/dist/ps-TNE44R5V.js +90 -0
- package/dist/ps-TNE44R5V.js.map +1 -0
- package/dist/recorder-XGF55WXI.js +31 -0
- package/dist/recorder-XGF55WXI.js.map +1 -0
- package/dist/registry-LGPE53QD.js +15 -0
- package/dist/registry-LGPE53QD.js.map +1 -0
- package/dist/resume-QMEPMSZW.js +238 -0
- package/dist/resume-QMEPMSZW.js.map +1 -0
- package/dist/review-YKWD6SNN.js +32 -0
- package/dist/review-YKWD6SNN.js.map +1 -0
- package/dist/routing-2ZUMZPJB.js +91 -0
- package/dist/routing-2ZUMZPJB.js.map +1 -0
- package/dist/run-YU4TR62R.js +50 -0
- package/dist/run-YU4TR62R.js.map +1 -0
- package/dist/state-manager-X3PT3FGT.js +36 -0
- package/dist/state-manager-X3PT3FGT.js.map +1 -0
- package/dist/usage-VCFLDA5Y.js +174 -0
- package/dist/usage-VCFLDA5Y.js.map +1 -0
- package/dist/{web-ANQQTTRD.js → web-XVHLX62B.js} +43 -27
- package/dist/web-XVHLX62B.js.map +1 -0
- package/package.json +6 -2
- package/prompts/execute.md +7 -8
- package/prompts/plan.md +4 -5
- package/prompts/pr.md +13 -6
- package/web/AGENTS.md +3 -3
- package/web/public/app.css +38 -0
- package/web/public/app.js +132 -10
- package/web/public/index.html +24 -0
- package/dist/analyze-XV3K25K3.js.map +0 -1
- package/dist/chunk-34WNTK3K.js.map +0 -1
- package/dist/chunk-3BOHTH4V.js +0 -58
- package/dist/chunk-3BOHTH4V.js.map +0 -1
- package/dist/chunk-47VIN33V.js.map +0 -1
- package/dist/chunk-6N7LZZ5U.js.map +0 -1
- package/dist/chunk-AAY6KM7N.js.map +0 -1
- package/dist/chunk-BXCPXRBR.js.map +0 -1
- package/dist/chunk-EWDJENFW.js +0 -303
- package/dist/chunk-EWDJENFW.js.map +0 -1
- package/dist/chunk-EY6WMHV2.js.map +0 -1
- package/dist/chunk-GANSDJV7.js.map +0 -1
- package/dist/chunk-HC2MT7BX.js.map +0 -1
- package/dist/chunk-HDV3YDTG.js +0 -52
- package/dist/chunk-HDV3YDTG.js.map +0 -1
- package/dist/chunk-IVHZEYAX.js.map +0 -1
- package/dist/chunk-KQ4JQN7T.js.map +0 -1
- package/dist/chunk-LVKQ2AKT.js.map +0 -1
- package/dist/chunk-OBLVLIUS.js.map +0 -1
- package/dist/chunk-RKTEV6EG.js.map +0 -1
- package/dist/chunk-VQ5VN7DF.js +0 -1018
- package/dist/chunk-VQ5VN7DF.js.map +0 -1
- package/dist/config-4THHOXNN.js +0 -41
- package/dist/execute-5OEKLM5X.js +0 -15
- package/dist/init-PRMOTQ7N.js +0 -9
- package/dist/plan-Y2BYZD24.js +0 -17
- package/dist/policy-XQW5NUIE.js.map +0 -1
- package/dist/pr-H6GLOSXF.js +0 -21
- package/dist/pr-review-CK5FR6WX.js +0 -17
- package/dist/prd-4PWLCGH5.js +0 -17
- package/dist/review-QGVAHEZF.js +0 -15
- package/dist/run-DBF2VWUN.js.map +0 -1
- package/dist/web-ANQQTTRD.js.map +0 -1
- /package/dist/{chunk-HQYXVIH6.js.map → chunk-MIVHK5YI.js.map} +0 -0
- /package/dist/{config-4THHOXNN.js.map → config-MEM2IWWN.js.map} +0 -0
- /package/dist/{execute-5OEKLM5X.js.map → contract-JH2WBIO6.js.map} +0 -0
- /package/dist/{init-PRMOTQ7N.js.map → execute-NITIHHCK.js.map} +0 -0
- /package/dist/{plan-Y2BYZD24.js.map → git-YRVRUN3C.js.map} +0 -0
- /package/dist/{pr-H6GLOSXF.js.map → init-OKWEC3EV.js.map} +0 -0
- /package/dist/{pr-review-CK5FR6WX.js.map → permissions-7NMDFU27.js.map} +0 -0
- /package/dist/{prd-4PWLCGH5.js.map → plan-LEJJF3DE.js.map} +0 -0
- /package/dist/{review-QGVAHEZF.js.map → pr-HPVUZ7S3.js.map} +0 -0
|
@@ -0,0 +1,559 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
classify,
|
|
4
|
+
requiresHumanAction
|
|
5
|
+
} from "./chunk-XEYH6ZZI.js";
|
|
6
|
+
|
|
7
|
+
// src/utils/shell.ts
|
|
8
|
+
import { basename } from "path";
|
|
9
|
+
import { execa } from "execa";
|
|
10
|
+
|
|
11
|
+
// src/resilience/policy.ts
|
|
12
|
+
var CONFIG_KEYS = {
|
|
13
|
+
network: "network",
|
|
14
|
+
timeout: "timeout",
|
|
15
|
+
stalled: "stalled",
|
|
16
|
+
rate_limit: "rateLimit",
|
|
17
|
+
provider_down: "providerDown",
|
|
18
|
+
provider_crash: "providerCrash",
|
|
19
|
+
authentication: "authentication",
|
|
20
|
+
configuration: "configuration",
|
|
21
|
+
repository_state: "repositoryState",
|
|
22
|
+
task_execution: "taskExecution",
|
|
23
|
+
internal: "internal",
|
|
24
|
+
unknown: "unknown"
|
|
25
|
+
};
|
|
26
|
+
function retryConfigKey(kind) {
|
|
27
|
+
return CONFIG_KEYS[kind];
|
|
28
|
+
}
|
|
29
|
+
var RETRY_CONFIG_KEYS = Object.values(CONFIG_KEYS);
|
|
30
|
+
var SECOND = 1e3;
|
|
31
|
+
function policy(overrides) {
|
|
32
|
+
return {
|
|
33
|
+
maxAttempts: 0,
|
|
34
|
+
initialDelayMs: 0,
|
|
35
|
+
maxDelayMs: 0,
|
|
36
|
+
backoffFactor: 2,
|
|
37
|
+
jitter: "full",
|
|
38
|
+
retryForever: false,
|
|
39
|
+
failover: "never",
|
|
40
|
+
failoverAfterAttempts: 2,
|
|
41
|
+
onExhausted: "fail",
|
|
42
|
+
...overrides
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
var BASE_POLICIES = {
|
|
46
|
+
network: policy({ maxAttempts: 8, initialDelayMs: 2 * SECOND, maxDelayMs: 120 * SECOND }),
|
|
47
|
+
timeout: policy({
|
|
48
|
+
maxAttempts: 2,
|
|
49
|
+
initialDelayMs: 30 * SECOND,
|
|
50
|
+
maxDelayMs: 120 * SECOND,
|
|
51
|
+
failover: "after_attempts"
|
|
52
|
+
}),
|
|
53
|
+
stalled: policy({
|
|
54
|
+
maxAttempts: 2,
|
|
55
|
+
initialDelayMs: 15 * SECOND,
|
|
56
|
+
maxDelayMs: 15 * SECOND,
|
|
57
|
+
failover: "after_attempts"
|
|
58
|
+
}),
|
|
59
|
+
rate_limit: policy({
|
|
60
|
+
maxAttempts: 6,
|
|
61
|
+
initialDelayMs: 60 * SECOND,
|
|
62
|
+
maxDelayMs: 900 * SECOND,
|
|
63
|
+
failover: "after_attempts"
|
|
64
|
+
}),
|
|
65
|
+
provider_down: policy({
|
|
66
|
+
maxAttempts: 4,
|
|
67
|
+
initialDelayMs: 10 * SECOND,
|
|
68
|
+
maxDelayMs: 300 * SECOND,
|
|
69
|
+
failover: "after_attempts"
|
|
70
|
+
}),
|
|
71
|
+
provider_crash: policy({
|
|
72
|
+
maxAttempts: 3,
|
|
73
|
+
initialDelayMs: 5 * SECOND,
|
|
74
|
+
maxDelayMs: 60 * SECOND,
|
|
75
|
+
failover: "after_attempts"
|
|
76
|
+
}),
|
|
77
|
+
authentication: policy({ maxAttempts: 0, onExhausted: "block" }),
|
|
78
|
+
configuration: policy({ maxAttempts: 0 }),
|
|
79
|
+
repository_state: policy({ maxAttempts: 0, onExhausted: "block" }),
|
|
80
|
+
task_execution: policy({ maxAttempts: 0 }),
|
|
81
|
+
internal: policy({ maxAttempts: 2, initialDelayMs: 5 * SECOND, maxDelayMs: 5 * SECOND }),
|
|
82
|
+
unknown: policy({ maxAttempts: 2, initialDelayMs: 5 * SECOND, maxDelayMs: 5 * SECOND })
|
|
83
|
+
};
|
|
84
|
+
var CONTINUOUS_OVERRIDES = {
|
|
85
|
+
network: { retryForever: true },
|
|
86
|
+
timeout: { maxAttempts: 3 },
|
|
87
|
+
stalled: { maxAttempts: 3 },
|
|
88
|
+
rate_limit: { retryForever: true },
|
|
89
|
+
provider_down: { retryForever: true, failover: "after_attempts" },
|
|
90
|
+
provider_crash: { maxAttempts: 5 }
|
|
91
|
+
};
|
|
92
|
+
function resolvePolicy(kind, config = {}) {
|
|
93
|
+
const base = BASE_POLICIES[kind];
|
|
94
|
+
const profile = config.profile === "continuous" ? CONTINUOUS_OVERRIDES[kind] : void 0;
|
|
95
|
+
const user = config.retry?.[retryConfigKey(kind)];
|
|
96
|
+
const resolved = { ...base, ...profile, ...user };
|
|
97
|
+
if (!requiresHumanAction(kind)) return sanitize(resolved);
|
|
98
|
+
return sanitize({
|
|
99
|
+
...resolved,
|
|
100
|
+
maxAttempts: 0,
|
|
101
|
+
retryForever: false,
|
|
102
|
+
failover: kind === "authentication" && config.failoverOnAuth === true ? resolved.failover : "never"
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
function sanitize(value) {
|
|
106
|
+
return {
|
|
107
|
+
...value,
|
|
108
|
+
maxAttempts: Math.max(0, Math.trunc(value.maxAttempts)),
|
|
109
|
+
initialDelayMs: Math.max(0, value.initialDelayMs),
|
|
110
|
+
maxDelayMs: Math.max(0, value.maxDelayMs),
|
|
111
|
+
backoffFactor: value.backoffFactor >= 1 ? value.backoffFactor : 1,
|
|
112
|
+
failoverAfterAttempts: Math.max(1, Math.trunc(value.failoverAfterAttempts))
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function shouldRetry(policy2, attemptsMade) {
|
|
116
|
+
if (policy2.maxAttempts <= 0) return false;
|
|
117
|
+
if (policy2.retryForever) return true;
|
|
118
|
+
return attemptsMade < policy2.maxAttempts;
|
|
119
|
+
}
|
|
120
|
+
function shouldFailover(policy2, attemptsMade) {
|
|
121
|
+
if (policy2.failover === "never") return false;
|
|
122
|
+
if (policy2.failover === "immediate") return true;
|
|
123
|
+
return attemptsMade >= policy2.failoverAfterAttempts;
|
|
124
|
+
}
|
|
125
|
+
function computeDelayMs(policy2, attemptsMade, options = {}) {
|
|
126
|
+
if (options.retryAfterMs !== void 0 && options.retryAfterMs >= 0) {
|
|
127
|
+
return Math.round(options.retryAfterMs);
|
|
128
|
+
}
|
|
129
|
+
const exponent = Math.max(0, attemptsMade - 1);
|
|
130
|
+
const growth = policy2.initialDelayMs * policy2.backoffFactor ** exponent;
|
|
131
|
+
const ceiling = Math.min(policy2.maxDelayMs, growth);
|
|
132
|
+
if (ceiling <= 0) return 0;
|
|
133
|
+
if (policy2.jitter === "none") return Math.round(ceiling);
|
|
134
|
+
const random = options.random ?? Math.random;
|
|
135
|
+
return Math.round(random() * ceiling);
|
|
136
|
+
}
|
|
137
|
+
function abortableDelay(ms, options = {}) {
|
|
138
|
+
const { signal } = options;
|
|
139
|
+
if (signal?.aborted === true) return Promise.resolve(false);
|
|
140
|
+
if (ms <= 0) return Promise.resolve(true);
|
|
141
|
+
return new Promise((resolve) => {
|
|
142
|
+
const onAbort = () => {
|
|
143
|
+
clearTimeout(timer);
|
|
144
|
+
resolve(false);
|
|
145
|
+
};
|
|
146
|
+
const timer = setTimeout(() => {
|
|
147
|
+
signal?.removeEventListener("abort", onAbort);
|
|
148
|
+
resolve(true);
|
|
149
|
+
}, ms);
|
|
150
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// src/resilience/retry.ts
|
|
155
|
+
async function withRetry(fn, options) {
|
|
156
|
+
const { policy: policy2, evaluate, signal, onAttempt, random } = options;
|
|
157
|
+
const delay = options.delay ?? abortableDelay;
|
|
158
|
+
const policyFor = typeof policy2 === "function" ? policy2 : () => policy2;
|
|
159
|
+
for (let attempt = 1; ; attempt++) {
|
|
160
|
+
const value = await fn(attempt);
|
|
161
|
+
const failure = evaluate(value, attempt);
|
|
162
|
+
if (failure === null) {
|
|
163
|
+
await onAttempt?.({ attempt, value, failure, willRetry: false, delayMs: 0 });
|
|
164
|
+
return { value, attempts: attempt, failure: null, exhausted: false, aborted: false };
|
|
165
|
+
}
|
|
166
|
+
const active = policyFor(failure);
|
|
167
|
+
const eligible = failure.retryable && !requiresHumanAction(failure.kind);
|
|
168
|
+
const willRetry = eligible && shouldRetry(active, attempt);
|
|
169
|
+
const delayMs = willRetry ? computeDelayMs(active, attempt, { random, retryAfterMs: failure.retryAfterMs }) : 0;
|
|
170
|
+
await onAttempt?.({ attempt, value, failure, willRetry, delayMs });
|
|
171
|
+
if (!willRetry) {
|
|
172
|
+
return { value, attempts: attempt, failure, exhausted: eligible, aborted: false };
|
|
173
|
+
}
|
|
174
|
+
if (!await delay(delayMs, { signal })) {
|
|
175
|
+
return { value, attempts: attempt, failure, exhausted: false, aborted: true };
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
function fixedBackoffPolicy(maxAttempts, baseSeconds, maxSeconds, overrides = {}) {
|
|
180
|
+
return {
|
|
181
|
+
maxAttempts,
|
|
182
|
+
initialDelayMs: baseSeconds * 1e3,
|
|
183
|
+
maxDelayMs: maxSeconds * 1e3,
|
|
184
|
+
backoffFactor: 2,
|
|
185
|
+
jitter: "none",
|
|
186
|
+
retryForever: false,
|
|
187
|
+
failover: "never",
|
|
188
|
+
failoverAfterAttempts: 2,
|
|
189
|
+
onExhausted: "fail",
|
|
190
|
+
...overrides
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// src/utils/shell.ts
|
|
195
|
+
var SOURCE_BY_COMMAND = {
|
|
196
|
+
git: "git",
|
|
197
|
+
gh: "github"
|
|
198
|
+
};
|
|
199
|
+
function inferSource(command) {
|
|
200
|
+
return SOURCE_BY_COMMAND[basename(command)] ?? "internal";
|
|
201
|
+
}
|
|
202
|
+
var GIT_GLOBAL_OPTIONS_WITH_VALUE = /* @__PURE__ */ new Set([
|
|
203
|
+
"-C",
|
|
204
|
+
"-c",
|
|
205
|
+
"--git-dir",
|
|
206
|
+
"--work-tree",
|
|
207
|
+
"--namespace",
|
|
208
|
+
"--exec-path",
|
|
209
|
+
"--super-prefix"
|
|
210
|
+
]);
|
|
211
|
+
var DESTRUCTIVE_GIT_COMMANDS = {
|
|
212
|
+
rebase: true,
|
|
213
|
+
"cherry-pick": true,
|
|
214
|
+
revert: true,
|
|
215
|
+
merge: true,
|
|
216
|
+
am: true,
|
|
217
|
+
restore: true,
|
|
218
|
+
"filter-branch": true,
|
|
219
|
+
gc: true,
|
|
220
|
+
prune: true,
|
|
221
|
+
"update-ref": true,
|
|
222
|
+
push: ["-f", "--force", "--force-with-lease", "--force-if-includes", "--delete", "--mirror"],
|
|
223
|
+
reset: ["--hard", "--merge", "--keep"],
|
|
224
|
+
clean: ["-f", "--force"],
|
|
225
|
+
checkout: ["-f", "--force", "-B"],
|
|
226
|
+
switch: ["-f", "--force", "--discard-changes"],
|
|
227
|
+
branch: ["-d", "-D", "--delete", "-M"],
|
|
228
|
+
tag: ["-d", "--delete", "-f", "--force"],
|
|
229
|
+
stash: ["drop", "clear", "pop"],
|
|
230
|
+
worktree: ["remove", "prune"],
|
|
231
|
+
submodule: ["deinit"]
|
|
232
|
+
};
|
|
233
|
+
function gitSubcommand(args) {
|
|
234
|
+
for (let index = 0; index < args.length; index++) {
|
|
235
|
+
const arg = args[index];
|
|
236
|
+
if (arg === void 0) continue;
|
|
237
|
+
if (GIT_GLOBAL_OPTIONS_WITH_VALUE.has(arg)) {
|
|
238
|
+
index++;
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
if (arg.startsWith("-")) continue;
|
|
242
|
+
return { name: arg, rest: args.slice(index + 1) };
|
|
243
|
+
}
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
function hasToken(rest, token) {
|
|
247
|
+
if (token.startsWith("--")) {
|
|
248
|
+
return rest.some((arg) => arg === token || arg.startsWith(`${token}=`));
|
|
249
|
+
}
|
|
250
|
+
if (token.startsWith("-")) {
|
|
251
|
+
const letter = token.slice(1);
|
|
252
|
+
return rest.some((arg) => /^-[A-Za-z]+$/.test(arg) && arg.slice(1).includes(letter));
|
|
253
|
+
}
|
|
254
|
+
return rest.includes(token);
|
|
255
|
+
}
|
|
256
|
+
function isRetryableInvocation(command, args) {
|
|
257
|
+
if (basename(command) !== "git") return true;
|
|
258
|
+
const subcommand = gitSubcommand(args);
|
|
259
|
+
if (subcommand === null) return true;
|
|
260
|
+
const rule = DESTRUCTIVE_GIT_COMMANDS[subcommand.name];
|
|
261
|
+
if (rule === void 0) return true;
|
|
262
|
+
if (rule === true) return false;
|
|
263
|
+
return !rule.some((token) => hasToken(subcommand.rest, token));
|
|
264
|
+
}
|
|
265
|
+
function asString(value) {
|
|
266
|
+
return typeof value === "string" && value !== "" ? value : void 0;
|
|
267
|
+
}
|
|
268
|
+
function toFailureSignal(raw, result, source) {
|
|
269
|
+
const fields = raw ?? {};
|
|
270
|
+
const errno = asString(fields.code);
|
|
271
|
+
const signal = asString(fields.signal);
|
|
272
|
+
const stderr = result.stderr.trim() !== "" ? result.stderr : asString(fields.originalMessage) ?? asString(fields.shortMessage) ?? "";
|
|
273
|
+
return {
|
|
274
|
+
source,
|
|
275
|
+
exitCode: result.exitCode,
|
|
276
|
+
stdout: result.stdout,
|
|
277
|
+
stderr,
|
|
278
|
+
...errno === void 0 ? {} : { errno },
|
|
279
|
+
...signal === void 0 ? {} : { signal },
|
|
280
|
+
...fields.timedOut === true ? { timedOut: true } : {}
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
async function execOnce(command, args, options) {
|
|
284
|
+
const raw = await execa(command, args, {
|
|
285
|
+
reject: false,
|
|
286
|
+
...options
|
|
287
|
+
});
|
|
288
|
+
return {
|
|
289
|
+
raw,
|
|
290
|
+
result: {
|
|
291
|
+
stdout: raw.stdout?.toString() ?? "",
|
|
292
|
+
stderr: raw.stderr?.toString() ?? "",
|
|
293
|
+
exitCode: raw.exitCode ?? 1
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
async function run(command, args = [], options) {
|
|
298
|
+
const { retry, source, retrySignal, onRetryAttempt, ...execaOptions } = options ?? {};
|
|
299
|
+
if (retry === void 0 || !isRetryableInvocation(command, args)) {
|
|
300
|
+
return (await execOnce(command, args, execaOptions)).result;
|
|
301
|
+
}
|
|
302
|
+
const failureSource = source ?? inferSource(command);
|
|
303
|
+
const outcome = await withRetry(
|
|
304
|
+
async () => {
|
|
305
|
+
const { result, raw } = await execOnce(command, args, execaOptions);
|
|
306
|
+
const failure = result.exitCode === 0 ? null : classify(toFailureSignal(raw, result, failureSource));
|
|
307
|
+
return { result, failure };
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
policy: retry,
|
|
311
|
+
evaluate: (attempt) => attempt.failure,
|
|
312
|
+
...retrySignal === void 0 ? {} : { signal: retrySignal },
|
|
313
|
+
...onRetryAttempt === void 0 ? {} : {
|
|
314
|
+
onAttempt: (info) => onRetryAttempt({
|
|
315
|
+
attempt: info.attempt,
|
|
316
|
+
result: info.value.result,
|
|
317
|
+
failure: info.value.failure,
|
|
318
|
+
willRetry: info.willRetry,
|
|
319
|
+
delayMs: info.delayMs
|
|
320
|
+
})
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
);
|
|
324
|
+
return {
|
|
325
|
+
...outcome.value.result,
|
|
326
|
+
attempts: outcome.attempts,
|
|
327
|
+
...outcome.failure === null ? {} : { failure: outcome.failure }
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// src/utils/git.ts
|
|
332
|
+
async function getProjectRoot() {
|
|
333
|
+
const result = await run("git", ["rev-parse", "--show-toplevel"]);
|
|
334
|
+
if (result.exitCode !== 0) {
|
|
335
|
+
throw new Error(
|
|
336
|
+
"Not inside a git repository. Please run issue-flow from within a git project."
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
return result.stdout.trim();
|
|
340
|
+
}
|
|
341
|
+
async function getCurrentBranch() {
|
|
342
|
+
const result = await run("git", ["branch", "--show-current"]);
|
|
343
|
+
if (result.exitCode !== 0) {
|
|
344
|
+
throw new Error(
|
|
345
|
+
"Failed to detect git branch. Ensure git is installed and you are inside a repository."
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
return result.stdout.trim();
|
|
349
|
+
}
|
|
350
|
+
async function getBaseBranch() {
|
|
351
|
+
const remoteHead = await run("git", ["symbolic-ref", "--short", "refs/remotes/origin/HEAD"]);
|
|
352
|
+
if (remoteHead.exitCode === 0) {
|
|
353
|
+
const name = remoteHead.stdout.trim().replace(/^origin\//, "");
|
|
354
|
+
if (name) return name;
|
|
355
|
+
}
|
|
356
|
+
for (const candidate of ["main", "master"]) {
|
|
357
|
+
const check = await run("git", ["rev-parse", "--verify", "--quiet", `refs/heads/${candidate}`]);
|
|
358
|
+
if (check.exitCode === 0) return candidate;
|
|
359
|
+
}
|
|
360
|
+
return "main";
|
|
361
|
+
}
|
|
362
|
+
async function getRemoteUrl(cwd) {
|
|
363
|
+
let result;
|
|
364
|
+
try {
|
|
365
|
+
result = await run("git", ["remote", "get-url", "origin"], { cwd });
|
|
366
|
+
} catch {
|
|
367
|
+
return null;
|
|
368
|
+
}
|
|
369
|
+
if (result.exitCode !== 0) return null;
|
|
370
|
+
const url = result.stdout.trim();
|
|
371
|
+
return url === "" ? null : url;
|
|
372
|
+
}
|
|
373
|
+
async function getHeadCommit(cwd) {
|
|
374
|
+
let result;
|
|
375
|
+
try {
|
|
376
|
+
result = await run("git", ["rev-parse", "--short", "HEAD"], { cwd });
|
|
377
|
+
} catch {
|
|
378
|
+
return null;
|
|
379
|
+
}
|
|
380
|
+
if (result.exitCode !== 0) return null;
|
|
381
|
+
const hash = result.stdout.trim();
|
|
382
|
+
return hash === "" ? null : hash;
|
|
383
|
+
}
|
|
384
|
+
function normalizeRemoteUrl(url) {
|
|
385
|
+
if (typeof url !== "string") return null;
|
|
386
|
+
const trimmed = url.trim();
|
|
387
|
+
if (trimmed === "") return null;
|
|
388
|
+
let authority;
|
|
389
|
+
let path;
|
|
390
|
+
const scheme = /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.exec(trimmed);
|
|
391
|
+
if (scheme) {
|
|
392
|
+
const rest = trimmed.slice(scheme[0].length);
|
|
393
|
+
const slash = rest.indexOf("/");
|
|
394
|
+
if (slash === -1) return null;
|
|
395
|
+
authority = rest.slice(0, slash);
|
|
396
|
+
path = rest.slice(slash + 1);
|
|
397
|
+
} else {
|
|
398
|
+
const colon = trimmed.indexOf(":");
|
|
399
|
+
if (colon === -1) return null;
|
|
400
|
+
authority = trimmed.slice(0, colon);
|
|
401
|
+
path = trimmed.slice(colon + 1);
|
|
402
|
+
}
|
|
403
|
+
const at = authority.lastIndexOf("@");
|
|
404
|
+
if (at !== -1) authority = authority.slice(at + 1);
|
|
405
|
+
const host = authority.replace(/:\d+$/, "").toLowerCase();
|
|
406
|
+
if (host === "") return null;
|
|
407
|
+
const normalizedPath = path.replace(/[?#].*$/, "").replace(/\/+/g, "/").replace(/^\/+|\/+$/g, "").replace(/\.git$/i, "").replace(/\/+$/, "").toLowerCase();
|
|
408
|
+
if (normalizedPath === "") return null;
|
|
409
|
+
return `${host}/${normalizedPath}`;
|
|
410
|
+
}
|
|
411
|
+
function stripRemoteUrlCredentials(url) {
|
|
412
|
+
if (typeof url !== "string") return null;
|
|
413
|
+
const trimmed = url.trim();
|
|
414
|
+
if (trimmed === "") return null;
|
|
415
|
+
const httpScheme = /^https?:\/\//i.exec(trimmed);
|
|
416
|
+
if (!httpScheme) return trimmed;
|
|
417
|
+
const rest = trimmed.slice(httpScheme[0].length);
|
|
418
|
+
const slash = rest.indexOf("/");
|
|
419
|
+
const authority = slash === -1 ? rest : rest.slice(0, slash);
|
|
420
|
+
const remainder = slash === -1 ? "" : rest.slice(slash);
|
|
421
|
+
const at = authority.lastIndexOf("@");
|
|
422
|
+
if (at === -1) return trimmed;
|
|
423
|
+
return `${httpScheme[0]}${authority.slice(at + 1)}${remainder}`;
|
|
424
|
+
}
|
|
425
|
+
async function getCommitsSince(base) {
|
|
426
|
+
const result = await run("git", ["log", "--pretty=format:%h%x09%s", `${base}..HEAD`]);
|
|
427
|
+
if (result.exitCode !== 0) return [];
|
|
428
|
+
return result.stdout.split("\n").filter((line) => line.trim() !== "").map((line) => {
|
|
429
|
+
const tab = line.indexOf(" ");
|
|
430
|
+
return tab === -1 ? { hash: line.trim(), subject: "" } : { hash: line.slice(0, tab), subject: line.slice(tab + 1) };
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
async function refExists(ref, cwd) {
|
|
434
|
+
const result = await run("git", ["rev-parse", "--verify", "--quiet", ref], {
|
|
435
|
+
...cwd === void 0 ? {} : { cwd }
|
|
436
|
+
});
|
|
437
|
+
return result.exitCode === 0;
|
|
438
|
+
}
|
|
439
|
+
var SEQUENCER_REFS = [
|
|
440
|
+
{
|
|
441
|
+
ref: "REBASE_HEAD",
|
|
442
|
+
kind: "rebase_in_progress",
|
|
443
|
+
name: "a rebase",
|
|
444
|
+
suggestion: "git rebase --continue (or git rebase --abort)"
|
|
445
|
+
},
|
|
446
|
+
{
|
|
447
|
+
ref: "MERGE_HEAD",
|
|
448
|
+
kind: "merge_in_progress",
|
|
449
|
+
name: "a merge",
|
|
450
|
+
suggestion: "git merge --continue (or git merge --abort)"
|
|
451
|
+
},
|
|
452
|
+
{
|
|
453
|
+
ref: "CHERRY_PICK_HEAD",
|
|
454
|
+
kind: "cherry_pick_in_progress",
|
|
455
|
+
name: "a cherry-pick",
|
|
456
|
+
suggestion: "git cherry-pick --continue (or git cherry-pick --abort)"
|
|
457
|
+
},
|
|
458
|
+
{
|
|
459
|
+
ref: "REVERT_HEAD",
|
|
460
|
+
kind: "revert_in_progress",
|
|
461
|
+
name: "a revert",
|
|
462
|
+
suggestion: "git revert --continue (or git revert --abort)"
|
|
463
|
+
}
|
|
464
|
+
];
|
|
465
|
+
async function preflightRepository(options = {}) {
|
|
466
|
+
const { cwd, expectedBranch, intent = "new-phase" } = options;
|
|
467
|
+
const at = cwd === void 0 ? {} : { cwd };
|
|
468
|
+
const blocks = [];
|
|
469
|
+
for (const sequencer of SEQUENCER_REFS) {
|
|
470
|
+
if (await refExists(sequencer.ref, cwd)) {
|
|
471
|
+
blocks.push({
|
|
472
|
+
kind: sequencer.kind,
|
|
473
|
+
message: `The repository is in the middle of ${sequencer.name} (${sequencer.ref} is present).`,
|
|
474
|
+
suggestion: sequencer.suggestion
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
const unmerged = await run("git", ["diff", "--name-only", "--diff-filter=U"], at);
|
|
479
|
+
const conflicted = unmerged.stdout.split("\n").map((line) => line.trim()).filter((line) => line !== "");
|
|
480
|
+
if (conflicted.length > 0) {
|
|
481
|
+
blocks.push({
|
|
482
|
+
kind: "unmerged_paths",
|
|
483
|
+
message: `${conflicted.length} file(s) still have unresolved conflicts: ${conflicted.slice(0, 5).join(", ")}${conflicted.length > 5 ? ", \u2026" : ""}.`,
|
|
484
|
+
suggestion: "Resolve them, then git add the files"
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
const symbolic = await run("git", ["symbolic-ref", "-q", "HEAD"], at);
|
|
488
|
+
const detached = symbolic.exitCode !== 0;
|
|
489
|
+
const branch = detached ? null : symbolic.stdout.trim().replace(/^refs\/heads\//, "");
|
|
490
|
+
if (detached) {
|
|
491
|
+
blocks.push({
|
|
492
|
+
kind: "detached_head",
|
|
493
|
+
message: "HEAD is detached, so a commit would belong to no branch.",
|
|
494
|
+
suggestion: `git switch ${expectedBranch ?? "<branch>"}`
|
|
495
|
+
});
|
|
496
|
+
} else if (expectedBranch !== void 0 && expectedBranch !== null && expectedBranch !== "" && branch !== expectedBranch) {
|
|
497
|
+
blocks.push({
|
|
498
|
+
kind: "branch_mismatch",
|
|
499
|
+
message: `The plan works on '${expectedBranch}' but the repository is on '${branch}'.`,
|
|
500
|
+
suggestion: `git switch ${expectedBranch}`
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
const status = await run("git", ["status", "--porcelain"], at);
|
|
504
|
+
const dirty = status.stdout.trim() !== "";
|
|
505
|
+
if (dirty && intent !== "resume-same-phase") {
|
|
506
|
+
blocks.push({
|
|
507
|
+
kind: "dirty_tree",
|
|
508
|
+
message: "The working tree has uncommitted changes that do not belong to this phase.",
|
|
509
|
+
suggestion: "Commit or stash them yourself, then run again"
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
return { ok: blocks.length === 0, blocks, branch, dirty };
|
|
513
|
+
}
|
|
514
|
+
function describePreflight(result) {
|
|
515
|
+
return result.blocks.map((block) => `${block.message} Suggested: ${block.suggestion}`);
|
|
516
|
+
}
|
|
517
|
+
var STORY_ID_IN_SUBJECT = /\bUS-\d+\b/g;
|
|
518
|
+
async function committedStoryIds(baseBranch, cwd) {
|
|
519
|
+
const at = cwd === void 0 ? {} : { cwd };
|
|
520
|
+
const range = baseBranch === "" ? "HEAD" : `${baseBranch}..HEAD`;
|
|
521
|
+
const result = await run("git", ["log", range, "--format=%s"], at);
|
|
522
|
+
if (result.exitCode !== 0) return /* @__PURE__ */ new Set();
|
|
523
|
+
const ids = /* @__PURE__ */ new Set();
|
|
524
|
+
for (const subject of result.stdout.split("\n")) {
|
|
525
|
+
for (const match of subject.matchAll(STORY_ID_IN_SUBJECT)) {
|
|
526
|
+
ids.add(match[0]);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
return ids;
|
|
530
|
+
}
|
|
531
|
+
async function isWorkingTreeClean(cwd) {
|
|
532
|
+
const at = cwd === void 0 ? {} : { cwd };
|
|
533
|
+
const result = await run("git", ["status", "--porcelain"], at);
|
|
534
|
+
if (result.exitCode !== 0) return false;
|
|
535
|
+
return result.stdout.trim() === "";
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
export {
|
|
539
|
+
RETRY_CONFIG_KEYS,
|
|
540
|
+
resolvePolicy,
|
|
541
|
+
shouldFailover,
|
|
542
|
+
abortableDelay,
|
|
543
|
+
withRetry,
|
|
544
|
+
fixedBackoffPolicy,
|
|
545
|
+
run,
|
|
546
|
+
getProjectRoot,
|
|
547
|
+
getCurrentBranch,
|
|
548
|
+
getBaseBranch,
|
|
549
|
+
getRemoteUrl,
|
|
550
|
+
getHeadCommit,
|
|
551
|
+
normalizeRemoteUrl,
|
|
552
|
+
stripRemoteUrlCredentials,
|
|
553
|
+
getCommitsSince,
|
|
554
|
+
preflightRepository,
|
|
555
|
+
describePreflight,
|
|
556
|
+
committedStoryIds,
|
|
557
|
+
isWorkingTreeClean
|
|
558
|
+
};
|
|
559
|
+
//# sourceMappingURL=chunk-KQHB7ZT3.js.map
|