issue-flow 0.4.4 → 0.5.2
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/LICENSE +21 -0
- package/README.md +38 -1
- package/dist/{analyze-PDDIU7HD.js → analyze-QVQ4CTCU.js} +7 -9
- package/dist/{analyze-PDDIU7HD.js.map → analyze-QVQ4CTCU.js.map} +1 -1
- package/dist/chunk-3ULIAC2J.js +58 -0
- package/dist/chunk-3ULIAC2J.js.map +1 -0
- package/dist/{chunk-LTPXKMPE.js → chunk-DLWRUTRL.js} +105 -157
- package/dist/chunk-DLWRUTRL.js.map +1 -0
- package/dist/chunk-HDV3YDTG.js +52 -0
- package/dist/chunk-HDV3YDTG.js.map +1 -0
- package/dist/chunk-JMSRJ43G.js +84 -0
- package/dist/chunk-JMSRJ43G.js.map +1 -0
- package/dist/{chunk-4OQ2ARLP.js → chunk-KLBAHTXX.js} +10 -2
- package/dist/chunk-KLBAHTXX.js.map +1 -0
- package/dist/chunk-KMX3DUJN.js +104 -0
- package/dist/chunk-KMX3DUJN.js.map +1 -0
- package/dist/{chunk-65KDGHLM.js → chunk-LJI45O5B.js} +38 -19
- package/dist/chunk-LJI45O5B.js.map +1 -0
- package/dist/chunk-O5XI2CFF.js +850 -0
- package/dist/chunk-O5XI2CFF.js.map +1 -0
- package/dist/{chunk-HPSU5GGE.js → chunk-OEJSMZMO.js} +7 -9
- package/dist/{chunk-HPSU5GGE.js.map → chunk-OEJSMZMO.js.map} +1 -1
- package/dist/chunk-PA3GI7LR.js +240 -0
- package/dist/chunk-PA3GI7LR.js.map +1 -0
- package/dist/{chunk-OZVHOVDT.js → chunk-SLHZPB3C.js} +15 -8
- package/dist/chunk-SLHZPB3C.js.map +1 -0
- package/dist/{chunk-CSSXTPWE.js → chunk-VM7C7L3A.js} +2 -2
- package/dist/cli.js +55 -18
- package/dist/cli.js.map +1 -1
- package/dist/execute-TEJ26L4O.js +12 -0
- package/dist/{generate-HP4CPMJG.js → generate-KBQ4KW4T.js} +4 -4
- package/dist/init-4VMPBRMA.js +9 -0
- package/dist/plan-PWYZP5AN.js +13 -0
- package/dist/pr-CJQWZUDR.js +13 -0
- package/dist/prd-4KLQY7BC.js +13 -0
- package/dist/review-YFCWBBKY.js +11 -0
- package/dist/{run-A44XQ77Z.js → run-3GJC54DB.js} +292 -26
- package/dist/run-3GJC54DB.js.map +1 -0
- package/package.json +13 -3
- package/web/public/app.css +481 -0
- package/web/public/app.js +522 -0
- package/web/public/index.html +113 -0
- package/dist/chunk-4OQ2ARLP.js.map +0 -1
- package/dist/chunk-65KDGHLM.js.map +0 -1
- package/dist/chunk-LTPXKMPE.js.map +0 -1
- package/dist/chunk-OZVHOVDT.js.map +0 -1
- package/dist/chunk-SKTTLX7R.js +0 -68
- package/dist/chunk-SKTTLX7R.js.map +0 -1
- package/dist/chunk-X5QCISNM.js +0 -180
- package/dist/chunk-X5QCISNM.js.map +0 -1
- package/dist/chunk-YAFHVFV5.js +0 -199
- package/dist/chunk-YAFHVFV5.js.map +0 -1
- package/dist/chunk-ZJB2IMPU.js +0 -85
- package/dist/chunk-ZJB2IMPU.js.map +0 -1
- package/dist/execute-WKYSGIGH.js +0 -11
- package/dist/init-SXFGOCML.js +0 -9
- package/dist/plan-JUOBYTCD.js +0 -12
- package/dist/pr-CYZYJZZV.js +0 -12
- package/dist/prd-PZOY3FCC.js +0 -12
- package/dist/review-MK5FTVUE.js +0 -12
- package/dist/run-A44XQ77Z.js.map +0 -1
- /package/dist/{chunk-CSSXTPWE.js.map → chunk-VM7C7L3A.js.map} +0 -0
- /package/dist/{execute-WKYSGIGH.js.map → execute-TEJ26L4O.js.map} +0 -0
- /package/dist/{generate-HP4CPMJG.js.map → generate-KBQ4KW4T.js.map} +0 -0
- /package/dist/{init-SXFGOCML.js.map → init-4VMPBRMA.js.map} +0 -0
- /package/dist/{plan-JUOBYTCD.js.map → plan-PWYZP5AN.js.map} +0 -0
- /package/dist/{pr-CYZYJZZV.js.map → pr-CJQWZUDR.js.map} +0 -0
- /package/dist/{prd-PZOY3FCC.js.map → prd-4KLQY7BC.js.map} +0 -0
- /package/dist/{review-MK5FTVUE.js.map → review-YFCWBBKY.js.map} +0 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Fábio Assunção
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -82,6 +82,9 @@ npx issue-flow run 42 --from execute
|
|
|
82
82
|
|
|
83
83
|
# Manual mode (artifacts only, no execution)
|
|
84
84
|
npx issue-flow run 42 --mode manual
|
|
85
|
+
|
|
86
|
+
# Watch the run live in the browser (see "Web Monitoring" below)
|
|
87
|
+
npx issue-flow run 42 --web
|
|
85
88
|
```
|
|
86
89
|
|
|
87
90
|
Executes all phases in order: **init** → **analyze** → **prd** → **plan** → **execute** → **review** → **pr**. Automatically resumes from the last incomplete phase if pipeline state exists. On review failure, runs correction cycles (re-execute + re-review) up to `maxCorrectionCycles`.
|
|
@@ -159,6 +162,33 @@ npx issue-flow generate --prompt "Add dark mode support to the settings page"
|
|
|
159
162
|
|
|
160
163
|
Analyzes the project and creates a detailed GitHub issue via Claude headless.
|
|
161
164
|
|
|
165
|
+
## Web Monitoring
|
|
166
|
+
|
|
167
|
+
`run` and `execute` support an optional (off by default) real-time monitoring mode: a local HTTP server (plain `node:http`, zero new dependencies) serves a self-contained, read-only web UI with live progress -- current phase and activity, user stories, commits, pull requests, logs, and time estimates.
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
# Enable with defaults -- binds to 0.0.0.0:3737, reachable from your LAN/VPN
|
|
171
|
+
npx issue-flow run 42 --web
|
|
172
|
+
|
|
173
|
+
# Restrict to this machine only
|
|
174
|
+
npx issue-flow run 42 --web --host 127.0.0.1
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Each setting resolves with the precedence **CLI flag > environment variable > `.issue-flow.json` > default**:
|
|
178
|
+
|
|
179
|
+
| CLI flag | Environment variable | `.issue-flow.json` key | Default |
|
|
180
|
+
|----------|----------------------|------------------------|---------|
|
|
181
|
+
| `--web` / `--serve` | `ISSUE_FLOW_WEB` | `web.enabled` | `false` |
|
|
182
|
+
| `--port <n>` | `ISSUE_FLOW_WEB_PORT` | `web.port` | `3737` |
|
|
183
|
+
| `--host <h>` | `ISSUE_FLOW_WEB_HOST` | `web.host` | `0.0.0.0` |
|
|
184
|
+
| `--refresh <s>` | `ISSUE_FLOW_WEB_REFRESH` | `web.refreshSeconds` | `5` |
|
|
185
|
+
| `--web-log-limit <n>` | `ISSUE_FLOW_WEB_LOG_LIMIT` | `web.logLimit` | `200` |
|
|
186
|
+
| `--web-no-logs` | -- | `web.includeLogs` | logs included |
|
|
187
|
+
|
|
188
|
+
Monitoring never affects the pipeline: with `--web` off the behavior is byte-for-byte identical, a busy port just skips the server with a warning, and killing the server mid-run has no effect on the execution. While enabled, the snapshot served at `/api/status` is also persisted to `issues/N/session.json` -- if your project commits the `issues/` directory, add `issues/*/session.json` to your `.gitignore`.
|
|
189
|
+
|
|
190
|
+
For the full documentation (endpoints, `session.json` format, Tailscale setup), see the [root README](../../README.md#web-monitoring).
|
|
191
|
+
|
|
162
192
|
## Pipeline State
|
|
163
193
|
|
|
164
194
|
Each issue's state is tracked in `issues/N/tasks.json`:
|
|
@@ -211,6 +241,11 @@ src/
|
|
|
211
241
|
pipeline.ts # Pipeline state machine
|
|
212
242
|
state-manager.ts # Typed CRUD for tasks.json
|
|
213
243
|
prompt-resolver.ts # Prompt resolution and templating
|
|
244
|
+
session-state.ts # Session snapshot reducer and publishers
|
|
245
|
+
session-publisher.ts # Global session publisher slot
|
|
246
|
+
session-git.ts # Commit/PR enrichment for the snapshot
|
|
247
|
+
web/
|
|
248
|
+
server.ts # Web monitoring HTTP server (assets in web/public/)
|
|
214
249
|
ui/
|
|
215
250
|
logger.ts # Colored logging utilities
|
|
216
251
|
progress.ts # Progress bar and iteration headers
|
|
@@ -240,7 +275,9 @@ npm test
|
|
|
240
275
|
npm run dev
|
|
241
276
|
```
|
|
242
277
|
|
|
243
|
-
For the full development setup
|
|
278
|
+
For the full development setup and local testing guide, see [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
279
|
+
The official release procedure (changelog, version bump, tag, `npm publish`, GitHub
|
|
280
|
+
Release) is documented in [CONTRIBUTING.md → Release process](CONTRIBUTING.md#release-process).
|
|
244
281
|
|
|
245
282
|
## Credits
|
|
246
283
|
|
|
@@ -1,21 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
runHeadless
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import {
|
|
6
|
-
loadTaskPlan,
|
|
7
|
-
saveTaskPlan
|
|
8
|
-
} from "./chunk-X5QCISNM.js";
|
|
4
|
+
} from "./chunk-KLBAHTXX.js";
|
|
9
5
|
import {
|
|
10
6
|
applyPlaceholders,
|
|
11
7
|
loadPrompt
|
|
12
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-SLHZPB3C.js";
|
|
13
9
|
import {
|
|
14
10
|
getGlobalTimeout,
|
|
11
|
+
loadTaskPlan,
|
|
15
12
|
printError,
|
|
16
13
|
printInfo,
|
|
17
|
-
printSuccess
|
|
18
|
-
|
|
14
|
+
printSuccess,
|
|
15
|
+
saveTaskPlan
|
|
16
|
+
} from "./chunk-O5XI2CFF.js";
|
|
19
17
|
|
|
20
18
|
// src/commands/analyze.ts
|
|
21
19
|
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
@@ -65,4 +63,4 @@ async function runAnalyze(issue) {
|
|
|
65
63
|
export {
|
|
66
64
|
runAnalyze
|
|
67
65
|
};
|
|
68
|
-
//# sourceMappingURL=analyze-
|
|
66
|
+
//# sourceMappingURL=analyze-QVQ4CTCU.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/commands/analyze.ts"],"sourcesContent":["import { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { runHeadless } from '../core/headless.js';\nimport { applyPlaceholders, loadPrompt } from '../core/prompt-resolver.js';\nimport { loadTaskPlan, saveTaskPlan } from '../core/state-manager.js';\nimport { getGlobalTimeout } from '../core/verbose.js';\nimport { printError, printInfo, printSuccess } from '../ui/logger.js';\n\nexport async function runAnalyze(issue: string): Promise<number> {\n const issueNumber = issue.replace(/^#/, '');\n const issueDir = join('issues', issueNumber);\n const analysisPath = join(issueDir, 'analysis.md');\n\n await mkdir(issueDir, { recursive: true });\n\n const template = await loadPrompt('analyze');\n const prompt = applyPlaceholders(template, {\n __ISSUE_NUMBER__: issueNumber,\n __ANALYSIS_PATH__: analysisPath,\n });\n\n const result = await runHeadless({\n prompt,\n maxTurns: 30,\n timeout: getGlobalTimeout() ?? 300_000,\n outputFormat: 'text',\n allowedTools: ['Bash', 'Read', 'Glob', 'Grep', 'Write'],\n statusMessage: `Analyzing issue #${issueNumber}...`,\n });\n\n if (!result.success) {\n printError(`Analysis failed: ${result.error}`);\n return 1;\n }\n\n // Verify the file was created\n try {\n const content = await readFile(analysisPath, 'utf-8');\n if (content.length < 10) {\n printError('Analysis file was created but appears empty');\n return 1;\n }\n } catch {\n // File wasn't created by headless — save the result as analysis\n printInfo('Headless did not create analysis file; saving output directly');\n await writeFile(analysisPath, result.result, 'utf-8');\n }\n\n // Update pipeline state\n const tasksPath = join(issueDir, 'tasks.json');\n try {\n const plan = await loadTaskPlan(tasksPath);\n plan.pipeline.analyzeCompleted = true;\n await saveTaskPlan(tasksPath, plan);\n } catch {\n // tasks.json may not exist yet — that's OK for standalone analyze\n }\n\n printSuccess(`Analysis saved to ${analysisPath}`);\n return 0;\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../src/commands/analyze.ts"],"sourcesContent":["import { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { runHeadless } from '../core/headless.js';\nimport { applyPlaceholders, loadPrompt } from '../core/prompt-resolver.js';\nimport { loadTaskPlan, saveTaskPlan } from '../core/state-manager.js';\nimport { getGlobalTimeout } from '../core/verbose.js';\nimport { printError, printInfo, printSuccess } from '../ui/logger.js';\n\nexport async function runAnalyze(issue: string): Promise<number> {\n const issueNumber = issue.replace(/^#/, '');\n const issueDir = join('issues', issueNumber);\n const analysisPath = join(issueDir, 'analysis.md');\n\n await mkdir(issueDir, { recursive: true });\n\n const template = await loadPrompt('analyze');\n const prompt = applyPlaceholders(template, {\n __ISSUE_NUMBER__: issueNumber,\n __ANALYSIS_PATH__: analysisPath,\n });\n\n const result = await runHeadless({\n prompt,\n maxTurns: 30,\n timeout: getGlobalTimeout() ?? 300_000,\n outputFormat: 'text',\n allowedTools: ['Bash', 'Read', 'Glob', 'Grep', 'Write'],\n statusMessage: `Analyzing issue #${issueNumber}...`,\n });\n\n if (!result.success) {\n printError(`Analysis failed: ${result.error}`);\n return 1;\n }\n\n // Verify the file was created\n try {\n const content = await readFile(analysisPath, 'utf-8');\n if (content.length < 10) {\n printError('Analysis file was created but appears empty');\n return 1;\n }\n } catch {\n // File wasn't created by headless — save the result as analysis\n printInfo('Headless did not create analysis file; saving output directly');\n await writeFile(analysisPath, result.result, 'utf-8');\n }\n\n // Update pipeline state\n const tasksPath = join(issueDir, 'tasks.json');\n try {\n const plan = await loadTaskPlan(tasksPath);\n plan.pipeline.analyzeCompleted = true;\n await saveTaskPlan(tasksPath, plan);\n } catch {\n // tasks.json may not exist yet — that's OK for standalone analyze\n }\n\n printSuccess(`Analysis saved to ${analysisPath}`);\n return 0;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA,SAAS,OAAO,UAAU,iBAAiB;AAC3C,SAAS,YAAY;AAOrB,eAAsB,WAAW,OAAgC;AAC/D,QAAM,cAAc,MAAM,QAAQ,MAAM,EAAE;AAC1C,QAAM,WAAW,KAAK,UAAU,WAAW;AAC3C,QAAM,eAAe,KAAK,UAAU,aAAa;AAEjD,QAAM,MAAM,UAAU,EAAE,WAAW,KAAK,CAAC;AAEzC,QAAM,WAAW,MAAM,WAAW,SAAS;AAC3C,QAAM,SAAS,kBAAkB,UAAU;AAAA,IACzC,kBAAkB;AAAA,IAClB,mBAAmB;AAAA,EACrB,CAAC;AAED,QAAM,SAAS,MAAM,YAAY;AAAA,IAC/B;AAAA,IACA,UAAU;AAAA,IACV,SAAS,iBAAiB,KAAK;AAAA,IAC/B,cAAc;AAAA,IACd,cAAc,CAAC,QAAQ,QAAQ,QAAQ,QAAQ,OAAO;AAAA,IACtD,eAAe,oBAAoB,WAAW;AAAA,EAChD,CAAC;AAED,MAAI,CAAC,OAAO,SAAS;AACnB,eAAW,oBAAoB,OAAO,KAAK,EAAE;AAC7C,WAAO;AAAA,EACT;AAGA,MAAI;AACF,UAAM,UAAU,MAAM,SAAS,cAAc,OAAO;AACpD,QAAI,QAAQ,SAAS,IAAI;AACvB,iBAAW,6CAA6C;AACxD,aAAO;AAAA,IACT;AAAA,EACF,QAAQ;AAEN,cAAU,+DAA+D;AACzE,UAAM,UAAU,cAAc,OAAO,QAAQ,OAAO;AAAA,EACtD;AAGA,QAAM,YAAY,KAAK,UAAU,YAAY;AAC7C,MAAI;AACF,UAAM,OAAO,MAAM,aAAa,SAAS;AACzC,SAAK,SAAS,mBAAmB;AACjC,UAAM,aAAa,WAAW,IAAI;AAAA,EACpC,QAAQ;AAAA,EAER;AAEA,eAAa,qBAAqB,YAAY,EAAE;AAChD,SAAO;AACT;","names":[]}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
retryDelaySeconds,
|
|
4
|
+
sleep
|
|
5
|
+
} from "./chunk-HDV3YDTG.js";
|
|
6
|
+
import {
|
|
7
|
+
getSessionPublisher,
|
|
8
|
+
isoNow,
|
|
9
|
+
printRetry
|
|
10
|
+
} from "./chunk-O5XI2CFF.js";
|
|
11
|
+
|
|
12
|
+
// src/core/phase-runner.ts
|
|
13
|
+
import { readFile } from "fs/promises";
|
|
14
|
+
var DEFAULT_PHASE_RETRY_LIMIT = 3;
|
|
15
|
+
var DEFAULT_BACKOFF_BASE_SECONDS = 15;
|
|
16
|
+
var DEFAULT_BACKOFF_MAX_SECONDS = 120;
|
|
17
|
+
async function runPhaseWithRetry(options) {
|
|
18
|
+
const retryLimit = options.retryLimit ?? DEFAULT_PHASE_RETRY_LIMIT;
|
|
19
|
+
const backoffBaseSeconds = options.backoffBaseSeconds ?? DEFAULT_BACKOFF_BASE_SECONDS;
|
|
20
|
+
const backoffMaxSeconds = options.backoffMaxSeconds ?? DEFAULT_BACKOFF_MAX_SECONDS;
|
|
21
|
+
let lastResult = { ok: false, error: "Phase never attempted" };
|
|
22
|
+
let retryCount = 0;
|
|
23
|
+
for (let attemptNumber = 1; attemptNumber <= retryLimit; attemptNumber++) {
|
|
24
|
+
lastResult = await options.attempt(attemptNumber);
|
|
25
|
+
if (lastResult.ok) return lastResult;
|
|
26
|
+
if (!lastResult.transient || attemptNumber === retryLimit) return lastResult;
|
|
27
|
+
retryCount++;
|
|
28
|
+
const delaySeconds = retryDelaySeconds(retryCount, backoffBaseSeconds, backoffMaxSeconds);
|
|
29
|
+
getSessionPublisher().publish({
|
|
30
|
+
type: "retry",
|
|
31
|
+
at: isoNow(),
|
|
32
|
+
attempt: retryCount,
|
|
33
|
+
delaySeconds,
|
|
34
|
+
reason: lastResult.error
|
|
35
|
+
});
|
|
36
|
+
printRetry(
|
|
37
|
+
`Phase ${options.phase} hit a recoverable failure (attempt ${attemptNumber}/${retryLimit}): ${lastResult.error}. Retrying in ${delaySeconds}s.`
|
|
38
|
+
);
|
|
39
|
+
await sleep(delaySeconds);
|
|
40
|
+
}
|
|
41
|
+
return lastResult;
|
|
42
|
+
}
|
|
43
|
+
async function readFileWithGrace(path, delaysMs = [300, 800]) {
|
|
44
|
+
for (let i = 0; ; i++) {
|
|
45
|
+
try {
|
|
46
|
+
return await readFile(path, "utf-8");
|
|
47
|
+
} catch (err) {
|
|
48
|
+
if (i >= delaysMs.length) throw err;
|
|
49
|
+
await new Promise((resolve) => setTimeout(resolve, delaysMs[i]));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export {
|
|
55
|
+
runPhaseWithRetry,
|
|
56
|
+
readFileWithGrace
|
|
57
|
+
};
|
|
58
|
+
//# sourceMappingURL=chunk-3ULIAC2J.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/phase-runner.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\nimport { printRetry } from '../ui/logger.js';\nimport { retryDelaySeconds, sleep } from '../utils/retry.js';\nimport { getSessionPublisher } from './session-publisher.js';\nimport { isoNow } from './state-manager.js';\n\nexport interface PhaseAttemptResult {\n ok: boolean;\n /** Whether a failed attempt is worth retrying. Ignored when ok is true. */\n transient?: boolean;\n error?: string;\n}\n\nexport interface PhaseRetryOptions {\n phase: string;\n attempt: (attemptNumber: number) => Promise<PhaseAttemptResult>;\n retryLimit?: number;\n backoffBaseSeconds?: number;\n backoffMaxSeconds?: number;\n}\n\nconst DEFAULT_PHASE_RETRY_LIMIT = 3;\nconst DEFAULT_BACKOFF_BASE_SECONDS = 15;\nconst DEFAULT_BACKOFF_MAX_SECONDS = 120;\n\n/**\n * Bounded retry wrapper for single-shot phases (prd, plan, pr) that invoke\n * the Claude CLI once and validate an artifact. Mirrors the transient-retry\n * pattern already used by the execute phase's iteration loop (core/engine.ts:\n * isTransientFailure + retryDelaySeconds), applied here to phases that\n * previously aborted the whole pipeline on the first failure with no chance\n * to recover. Non-transient failures (or exhausting the retry budget) still\n * end the phase — this bounds recovery attempts, it never retries forever.\n */\nexport async function runPhaseWithRetry(options: PhaseRetryOptions): Promise<PhaseAttemptResult> {\n const retryLimit = options.retryLimit ?? DEFAULT_PHASE_RETRY_LIMIT;\n const backoffBaseSeconds = options.backoffBaseSeconds ?? DEFAULT_BACKOFF_BASE_SECONDS;\n const backoffMaxSeconds = options.backoffMaxSeconds ?? DEFAULT_BACKOFF_MAX_SECONDS;\n\n let lastResult: PhaseAttemptResult = { ok: false, error: 'Phase never attempted' };\n let retryCount = 0;\n\n for (let attemptNumber = 1; attemptNumber <= retryLimit; attemptNumber++) {\n lastResult = await options.attempt(attemptNumber);\n if (lastResult.ok) return lastResult;\n if (!lastResult.transient || attemptNumber === retryLimit) return lastResult;\n\n retryCount++;\n const delaySeconds = retryDelaySeconds(retryCount, backoffBaseSeconds, backoffMaxSeconds);\n getSessionPublisher().publish({\n type: 'retry',\n at: isoNow(),\n attempt: retryCount,\n delaySeconds,\n reason: lastResult.error,\n });\n printRetry(\n `Phase ${options.phase} hit a recoverable failure (attempt ${attemptNumber}/${retryLimit}): ${lastResult.error}. Retrying in ${delaySeconds}s.`,\n );\n await sleep(delaySeconds);\n }\n\n return lastResult;\n}\n\n/**\n * Re-read a file a few times with short delays before giving up. Absorbs a\n * brief filesystem-visibility lag between the Claude CLI's Write tool\n * completing and this process observing the result, without spending a full\n * phase retry (and a new Claude invocation) on what may just be a timing gap.\n */\nexport async function readFileWithGrace(\n path: string,\n delaysMs: number[] = [300, 800],\n): Promise<string> {\n for (let i = 0; ; i++) {\n try {\n return await readFile(path, 'utf-8');\n } catch (err) {\n if (i >= delaysMs.length) throw err;\n await new Promise((resolve) => setTimeout(resolve, delaysMs[i]));\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;AAAA,SAAS,gBAAgB;AAqBzB,IAAM,4BAA4B;AAClC,IAAM,+BAA+B;AACrC,IAAM,8BAA8B;AAWpC,eAAsB,kBAAkB,SAAyD;AAC/F,QAAM,aAAa,QAAQ,cAAc;AACzC,QAAM,qBAAqB,QAAQ,sBAAsB;AACzD,QAAM,oBAAoB,QAAQ,qBAAqB;AAEvD,MAAI,aAAiC,EAAE,IAAI,OAAO,OAAO,wBAAwB;AACjF,MAAI,aAAa;AAEjB,WAAS,gBAAgB,GAAG,iBAAiB,YAAY,iBAAiB;AACxE,iBAAa,MAAM,QAAQ,QAAQ,aAAa;AAChD,QAAI,WAAW,GAAI,QAAO;AAC1B,QAAI,CAAC,WAAW,aAAa,kBAAkB,WAAY,QAAO;AAElE;AACA,UAAM,eAAe,kBAAkB,YAAY,oBAAoB,iBAAiB;AACxF,wBAAoB,EAAE,QAAQ;AAAA,MAC5B,MAAM;AAAA,MACN,IAAI,OAAO;AAAA,MACX,SAAS;AAAA,MACT;AAAA,MACA,QAAQ,WAAW;AAAA,IACrB,CAAC;AACD;AAAA,MACE,SAAS,QAAQ,KAAK,uCAAuC,aAAa,IAAI,UAAU,MAAM,WAAW,KAAK,iBAAiB,YAAY;AAAA,IAC7I;AACA,UAAM,MAAM,YAAY;AAAA,EAC1B;AAEA,SAAO;AACT;AAQA,eAAsB,kBACpB,MACA,WAAqB,CAAC,KAAK,GAAG,GACb;AACjB,WAAS,IAAI,KAAK,KAAK;AACrB,QAAI;AACF,aAAO,MAAM,SAAS,MAAM,OAAO;AAAA,IACrC,SAAS,KAAK;AACZ,UAAI,KAAK,SAAS,OAAQ,OAAM;AAChC,YAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,SAAS,CAAC,CAAC,CAAC;AAAA,IACjE;AAAA,EACF;AACF;","names":[]}
|
|
@@ -1,133 +1,54 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
3
|
+
createConfig,
|
|
4
|
+
getBaseBranch,
|
|
5
|
+
getCommitsSince,
|
|
6
|
+
getCurrentBranch,
|
|
7
|
+
resolvePaths,
|
|
8
|
+
run,
|
|
9
|
+
validateDependencies
|
|
10
|
+
} from "./chunk-PA3GI7LR.js";
|
|
11
|
+
import {
|
|
12
|
+
isTransientFailure,
|
|
13
|
+
retryDelaySeconds,
|
|
14
|
+
sleep
|
|
15
|
+
} from "./chunk-HDV3YDTG.js";
|
|
14
16
|
import {
|
|
15
17
|
applyPlaceholders,
|
|
16
18
|
loadPrompt
|
|
17
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-SLHZPB3C.js";
|
|
18
20
|
import {
|
|
21
|
+
NullPublisher,
|
|
22
|
+
allStoriesPass,
|
|
23
|
+
clearLastError,
|
|
19
24
|
formatDuration,
|
|
20
25
|
getIcons,
|
|
21
26
|
getOutputCallback,
|
|
27
|
+
getSessionPublisher,
|
|
22
28
|
getStoryUpdateCallback,
|
|
23
29
|
getTermWidth,
|
|
30
|
+
initializeState,
|
|
24
31
|
isVerbose,
|
|
32
|
+
isoNow,
|
|
33
|
+
loadTaskPlan,
|
|
34
|
+
markIssueCompleted,
|
|
35
|
+
markIssueInProgress,
|
|
25
36
|
printError,
|
|
26
37
|
printInfo,
|
|
27
38
|
printRetry,
|
|
28
39
|
printSuccess,
|
|
29
40
|
printWarning,
|
|
41
|
+
saveTaskPlan,
|
|
42
|
+
setLastError,
|
|
43
|
+
trimErrorMessage,
|
|
30
44
|
useColor,
|
|
31
45
|
useUnicode
|
|
32
|
-
} from "./chunk-
|
|
33
|
-
|
|
34
|
-
// src/config.ts
|
|
35
|
-
import { platform } from "os";
|
|
36
|
-
import { join } from "path";
|
|
37
|
-
|
|
38
|
-
// src/utils/shell.ts
|
|
39
|
-
import { execa } from "execa";
|
|
40
|
-
async function run(command, args = [], options) {
|
|
41
|
-
const result = await execa(command, args, {
|
|
42
|
-
reject: false,
|
|
43
|
-
...options
|
|
44
|
-
});
|
|
45
|
-
return {
|
|
46
|
-
stdout: result.stdout?.toString() ?? "",
|
|
47
|
-
stderr: result.stderr?.toString() ?? "",
|
|
48
|
-
exitCode: result.exitCode ?? 1
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// src/utils/git.ts
|
|
53
|
-
async function getProjectRoot() {
|
|
54
|
-
const result = await run("git", ["rev-parse", "--show-toplevel"]);
|
|
55
|
-
if (result.exitCode !== 0) {
|
|
56
|
-
throw new Error(
|
|
57
|
-
"Not inside a git repository. Please run issue-flow from within a git project."
|
|
58
|
-
);
|
|
59
|
-
}
|
|
60
|
-
return result.stdout.trim();
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// src/config.ts
|
|
64
|
-
var DEFAULTS = {
|
|
65
|
-
retryLimit: 10,
|
|
66
|
-
retryForever: false,
|
|
67
|
-
backoffBaseSeconds: 30,
|
|
68
|
-
backoffMaxSeconds: 900
|
|
69
|
-
};
|
|
70
|
-
function createConfig(options) {
|
|
71
|
-
return {
|
|
72
|
-
issueNumber: options.issueNumber,
|
|
73
|
-
maxIterations: options.maxIterations,
|
|
74
|
-
retryLimit: options.retryLimit ?? DEFAULTS.retryLimit,
|
|
75
|
-
retryForever: options.retryForever ?? DEFAULTS.retryForever,
|
|
76
|
-
backoffBaseSeconds: options.backoffBaseSeconds ?? DEFAULTS.backoffBaseSeconds,
|
|
77
|
-
backoffMaxSeconds: options.backoffMaxSeconds ?? DEFAULTS.backoffMaxSeconds
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
async function resolvePaths(config, scriptDir) {
|
|
81
|
-
const projectRoot = await getProjectRoot();
|
|
82
|
-
if (config.issueNumber) {
|
|
83
|
-
const issueDir = join(projectRoot, "issues", config.issueNumber);
|
|
84
|
-
return {
|
|
85
|
-
prdFile: join(issueDir, "tasks.json"),
|
|
86
|
-
progressFile: join(issueDir, "progress.txt"),
|
|
87
|
-
archiveDir: join(issueDir, "archive"),
|
|
88
|
-
lastBranchFile: join(issueDir, ".last-branch"),
|
|
89
|
-
projectRoot
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
const base = scriptDir ?? projectRoot;
|
|
93
|
-
return {
|
|
94
|
-
prdFile: join(base, "prd.json"),
|
|
95
|
-
progressFile: join(base, "progress.txt"),
|
|
96
|
-
archiveDir: join(base, "archive"),
|
|
97
|
-
lastBranchFile: join(base, ".last-branch"),
|
|
98
|
-
projectRoot
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
function getInstallHint(pkg) {
|
|
102
|
-
const os = platform();
|
|
103
|
-
if (os === "darwin") {
|
|
104
|
-
return `brew install ${pkg}`;
|
|
105
|
-
}
|
|
106
|
-
if (os === "linux") {
|
|
107
|
-
return `apt install ${pkg} (or your distro's package manager)`;
|
|
108
|
-
}
|
|
109
|
-
if (os === "win32") {
|
|
110
|
-
return `winget install ${pkg} (or choco install ${pkg})`;
|
|
111
|
-
}
|
|
112
|
-
return `install ${pkg} using your system package manager`;
|
|
113
|
-
}
|
|
114
|
-
async function validateDependencies() {
|
|
115
|
-
const errors = [];
|
|
116
|
-
const gitResult = await run("git", ["--version"]);
|
|
117
|
-
if (gitResult.exitCode !== 0) {
|
|
118
|
-
errors.push(` - git (install with: ${getInstallHint("git")})`);
|
|
119
|
-
}
|
|
120
|
-
const claudeResult = await run("claude", ["--version"]);
|
|
121
|
-
if (claudeResult.exitCode !== 0) {
|
|
122
|
-
errors.push(" - claude (install with: npm install -g @anthropic-ai/claude-code)");
|
|
123
|
-
}
|
|
124
|
-
return errors;
|
|
125
|
-
}
|
|
46
|
+
} from "./chunk-O5XI2CFF.js";
|
|
126
47
|
|
|
127
48
|
// src/core/engine.ts
|
|
128
49
|
import { existsSync } from "fs";
|
|
129
50
|
import { cp, mkdir, readFile, writeFile } from "fs/promises";
|
|
130
|
-
import { join
|
|
51
|
+
import { join } from "path";
|
|
131
52
|
|
|
132
53
|
// src/ui/progress.ts
|
|
133
54
|
import chalk from "chalk";
|
|
@@ -294,51 +215,10 @@ function printSummaryBox(status, iterations, totalRetries, elapsedSeconds, plan,
|
|
|
294
215
|
printBox(boxLines);
|
|
295
216
|
}
|
|
296
217
|
|
|
297
|
-
// src/utils/retry.ts
|
|
298
|
-
var TRANSIENT_PATTERNS = [
|
|
299
|
-
"timed out",
|
|
300
|
-
"timeout",
|
|
301
|
-
"connection reset",
|
|
302
|
-
"connection refused",
|
|
303
|
-
"connection aborted",
|
|
304
|
-
"network error",
|
|
305
|
-
"network unavailable",
|
|
306
|
-
"temporary failure",
|
|
307
|
-
"temporarily unavailable",
|
|
308
|
-
"service unavailable",
|
|
309
|
-
"overloaded",
|
|
310
|
-
"rate limit",
|
|
311
|
-
"too many requests",
|
|
312
|
-
"bad gateway",
|
|
313
|
-
"gateway timeout",
|
|
314
|
-
"internal server error",
|
|
315
|
-
"http 429",
|
|
316
|
-
"http 500",
|
|
317
|
-
"http 502",
|
|
318
|
-
"http 503",
|
|
319
|
-
"http 504",
|
|
320
|
-
"econnreset",
|
|
321
|
-
"econnrefused",
|
|
322
|
-
"enotfound",
|
|
323
|
-
"etimedout",
|
|
324
|
-
"socket hang up"
|
|
325
|
-
];
|
|
326
|
-
function isTransientFailure(exitCode, output) {
|
|
327
|
-
if (exitCode === 75) {
|
|
328
|
-
return true;
|
|
329
|
-
}
|
|
330
|
-
const lowered = output.toLowerCase();
|
|
331
|
-
return TRANSIENT_PATTERNS.some((pattern) => lowered.includes(pattern));
|
|
332
|
-
}
|
|
333
|
-
function retryDelaySeconds(attempt, baseSeconds = 30, maxSeconds = 900) {
|
|
334
|
-
const delay = baseSeconds * 2 ** (attempt - 1);
|
|
335
|
-
return Math.min(delay, maxSeconds);
|
|
336
|
-
}
|
|
337
|
-
|
|
338
218
|
// src/core/executor.ts
|
|
339
|
-
import { execa
|
|
219
|
+
import { execa } from "execa";
|
|
340
220
|
async function executeClaude(prompt) {
|
|
341
|
-
const result = await
|
|
221
|
+
const result = await execa("claude", ["--dangerously-skip-permissions", "--print"], {
|
|
342
222
|
input: prompt,
|
|
343
223
|
reject: false,
|
|
344
224
|
timeout: 0,
|
|
@@ -362,10 +242,62 @@ ${stderr}` : "");
|
|
|
362
242
|
};
|
|
363
243
|
}
|
|
364
244
|
|
|
365
|
-
// src/core/
|
|
366
|
-
function
|
|
367
|
-
|
|
245
|
+
// src/core/session-git.ts
|
|
246
|
+
async function listPullRequests(branch) {
|
|
247
|
+
if (!branch) return [];
|
|
248
|
+
const result = await run("gh", [
|
|
249
|
+
"pr",
|
|
250
|
+
"list",
|
|
251
|
+
"--head",
|
|
252
|
+
branch,
|
|
253
|
+
"--state",
|
|
254
|
+
"all",
|
|
255
|
+
"--json",
|
|
256
|
+
"number,url,title",
|
|
257
|
+
"--limit",
|
|
258
|
+
"10"
|
|
259
|
+
]);
|
|
260
|
+
if (result.exitCode !== 0) return [];
|
|
261
|
+
try {
|
|
262
|
+
const parsed = JSON.parse(result.stdout || "[]");
|
|
263
|
+
if (!Array.isArray(parsed)) return [];
|
|
264
|
+
return parsed.filter(
|
|
265
|
+
(pr) => typeof pr === "object" && pr !== null && typeof pr.number === "number" && typeof pr.url === "string" && typeof pr.title === "string"
|
|
266
|
+
).map((pr) => ({ number: pr.number, url: pr.url, title: pr.title }));
|
|
267
|
+
} catch {
|
|
268
|
+
return [];
|
|
269
|
+
}
|
|
368
270
|
}
|
|
271
|
+
var defaultSources = {
|
|
272
|
+
currentBranch: getCurrentBranch,
|
|
273
|
+
baseBranch: getBaseBranch,
|
|
274
|
+
commitsSince: getCommitsSince,
|
|
275
|
+
pullRequests: listPullRequests,
|
|
276
|
+
now: isoNow
|
|
277
|
+
};
|
|
278
|
+
async function publishGitState(publisher, sources = {}) {
|
|
279
|
+
if (publisher instanceof NullPublisher) return;
|
|
280
|
+
const src = { ...defaultSources, ...sources };
|
|
281
|
+
try {
|
|
282
|
+
const branch = await src.currentBranch();
|
|
283
|
+
const baseBranch = await src.baseBranch();
|
|
284
|
+
const [commits, pullRequests] = await Promise.all([
|
|
285
|
+
src.commitsSince(baseBranch),
|
|
286
|
+
src.pullRequests(branch)
|
|
287
|
+
]);
|
|
288
|
+
publisher.publish({
|
|
289
|
+
type: "git:update",
|
|
290
|
+
at: src.now(),
|
|
291
|
+
branch,
|
|
292
|
+
baseBranch,
|
|
293
|
+
commits,
|
|
294
|
+
pullRequests
|
|
295
|
+
});
|
|
296
|
+
} catch {
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// src/core/engine.ts
|
|
369
301
|
function emitLog(message) {
|
|
370
302
|
const cb = getOutputCallback();
|
|
371
303
|
if (cb) {
|
|
@@ -400,14 +332,14 @@ async function archiveIfBranchChanged(plan, paths) {
|
|
|
400
332
|
if (currentBranch && lastBranch && currentBranch !== lastBranch) {
|
|
401
333
|
const dateStr = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
402
334
|
const folderName = lastBranch.replace(/^issue\//, "").replace(/[<>:"|?*\\]/g, "_");
|
|
403
|
-
const archiveFolder =
|
|
335
|
+
const archiveFolder = join(archiveDir, `${dateStr}-${folderName}`);
|
|
404
336
|
printInfo(`Archiving previous run: ${lastBranch}`);
|
|
405
337
|
await mkdir(archiveFolder, { recursive: true });
|
|
406
338
|
if (existsSync(prdFile)) {
|
|
407
|
-
await cp(prdFile,
|
|
339
|
+
await cp(prdFile, join(archiveFolder, "tasks.json"));
|
|
408
340
|
}
|
|
409
341
|
if (existsSync(progressFile)) {
|
|
410
|
-
await cp(progressFile,
|
|
342
|
+
await cp(progressFile, join(archiveFolder, "progress.txt"));
|
|
411
343
|
}
|
|
412
344
|
printInfo(` Archived to: ${archiveFolder}`);
|
|
413
345
|
await writeFile(
|
|
@@ -474,6 +406,7 @@ async function runEngine(config, paths) {
|
|
|
474
406
|
break;
|
|
475
407
|
}
|
|
476
408
|
i++;
|
|
409
|
+
getSessionPublisher().publish({ type: "iteration:start", at: isoNow(), iteration: i });
|
|
477
410
|
plan = await loadTaskPlan(paths.prdFile);
|
|
478
411
|
if (isVerbose()) {
|
|
479
412
|
printIterationHeader(i, config.maxIterations, plan.userStories);
|
|
@@ -512,6 +445,13 @@ async function runEngine(config, paths) {
|
|
|
512
445
|
config.backoffBaseSeconds,
|
|
513
446
|
config.backoffMaxSeconds
|
|
514
447
|
);
|
|
448
|
+
getSessionPublisher().publish({
|
|
449
|
+
type: "retry",
|
|
450
|
+
at: isoNow(),
|
|
451
|
+
attempt: retryCount,
|
|
452
|
+
delaySeconds,
|
|
453
|
+
reason: "transient_claude_failure"
|
|
454
|
+
});
|
|
515
455
|
if (isVerbose()) {
|
|
516
456
|
emitLog("");
|
|
517
457
|
}
|
|
@@ -544,6 +484,13 @@ async function runEngine(config, paths) {
|
|
|
544
484
|
if (storyUpdateCb) {
|
|
545
485
|
storyUpdateCb(plan.userStories);
|
|
546
486
|
}
|
|
487
|
+
getSessionPublisher().publish({
|
|
488
|
+
type: "stories:update",
|
|
489
|
+
at: isoNow(),
|
|
490
|
+
stories: plan.userStories
|
|
491
|
+
});
|
|
492
|
+
getSessionPublisher().publish({ type: "iteration:end", at: isoNow(), iteration: i });
|
|
493
|
+
await publishGitState(getSessionPublisher());
|
|
547
494
|
if (result.output.includes("<promise>COMPLETE</promise>")) {
|
|
548
495
|
plan = await loadTaskPlan(paths.prdFile);
|
|
549
496
|
if (allStoriesPass(plan)) {
|
|
@@ -615,6 +562,7 @@ async function runExecute(positionalMaxIter, options) {
|
|
|
615
562
|
}
|
|
616
563
|
|
|
617
564
|
export {
|
|
565
|
+
publishGitState,
|
|
618
566
|
runExecute
|
|
619
567
|
};
|
|
620
|
-
//# sourceMappingURL=chunk-
|
|
568
|
+
//# sourceMappingURL=chunk-DLWRUTRL.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/engine.ts","../src/ui/progress.ts","../src/ui/summary.ts","../src/core/executor.ts","../src/core/session-git.ts","../src/commands/execute.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { cp, mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { EngineConfig, ResolvedPaths, TaskPlan } from '../types.js';\nimport { printError, printInfo, printRetry, printSuccess, printWarning } from '../ui/logger.js';\nimport { printIterationHeader } from '../ui/progress.js';\nimport { printStartupHeader, printSummaryBox } from '../ui/summary.js';\nimport { isTransientFailure, retryDelaySeconds, sleep } from '../utils/retry.js';\nimport { executeClaude } from './executor.js';\nimport { applyPlaceholders, loadPrompt } from './prompt-resolver.js';\nimport { publishGitState } from './session-git.js';\nimport { getSessionPublisher } from './session-publisher.js';\nimport {\n allStoriesPass,\n clearLastError,\n initializeState,\n isoNow,\n loadTaskPlan,\n markIssueCompleted,\n markIssueInProgress,\n saveTaskPlan,\n setLastError,\n trimErrorMessage,\n} from './state-manager.js';\nimport { getOutputCallback, getStoryUpdateCallback, isVerbose } from './verbose.js';\n\n/**\n * Emit a message through the output callback if available, otherwise console.log.\n * Used for bare console.log calls in engine functions so they route through listr2.\n */\nfunction emitLog(message: string): void {\n const cb = getOutputCallback();\n if (cb) {\n // Skip empty lines in listr2 context — they don't render meaningfully\n if (message) {\n cb(message);\n }\n } else {\n console.log(message);\n }\n}\n\n/**\n * Initialize the progress file if it doesn't exist.\n */\nasync function ensureProgressFile(progressFile: string): Promise<void> {\n if (!existsSync(progressFile)) {\n const content = `# Issue Flow Progress Log\\nStarted: ${new Date().toString()}\\n---\\n`;\n await writeFile(progressFile, content, 'utf-8');\n }\n}\n\n/**\n * Archive previous run artifacts if the branch has changed.\n */\nasync function archiveIfBranchChanged(plan: TaskPlan, paths: ResolvedPaths): Promise<void> {\n const { lastBranchFile, archiveDir, prdFile, progressFile } = paths;\n\n if (!existsSync(lastBranchFile)) {\n return;\n }\n\n const currentBranch = plan.branchName ?? '';\n let lastBranch = '';\n\n try {\n lastBranch = (await readFile(lastBranchFile, 'utf-8')).trim();\n } catch {\n return;\n }\n\n if (currentBranch && lastBranch && currentBranch !== lastBranch) {\n const dateStr = new Date().toISOString().split('T')[0];\n const folderName = lastBranch.replace(/^issue\\//, '').replace(/[<>:\"|?*\\\\]/g, '_');\n const archiveFolder = join(archiveDir, `${dateStr}-${folderName}`);\n\n printInfo(`Archiving previous run: ${lastBranch}`);\n await mkdir(archiveFolder, { recursive: true });\n\n if (existsSync(prdFile)) {\n await cp(prdFile, join(archiveFolder, 'tasks.json'));\n }\n if (existsSync(progressFile)) {\n await cp(progressFile, join(archiveFolder, 'progress.txt'));\n }\n\n printInfo(` Archived to: ${archiveFolder}`);\n\n // Reset progress file for new run\n await writeFile(\n progressFile,\n `# Issue Flow Progress Log\\nStarted: ${new Date().toString()}\\n---\\n`,\n 'utf-8',\n );\n }\n}\n\n/**\n * Write the current branch to the last-branch tracking file.\n */\nasync function trackBranch(plan: TaskPlan, lastBranchFile: string): Promise<void> {\n const branch = plan.branchName ?? '';\n if (branch) {\n await writeFile(lastBranchFile, `${branch}\\n`, 'utf-8');\n }\n}\n\n/**\n * Run the issue-flow engine loop.\n *\n * This replicates the full execution flow:\n * 1. Load and initialize task plan state\n * 2. Check for early exit (already complete)\n * 3. Archive previous run if branch changed\n * 4. Resolve prompt\n * 5. Main loop: iterate, execute Claude, handle results\n * 6. Print summary\n */\nexport async function runEngine(config: EngineConfig, paths: ResolvedPaths): Promise<number> {\n // Load task plan\n if (!existsSync(paths.prdFile)) {\n printError(`PRD file not found at ${paths.prdFile}`);\n if (config.issueNumber) {\n emitLog(`Have you run the resolve-issue skill for issue #${config.issueNumber} first?`);\n }\n return 1;\n }\n\n let plan = await loadTaskPlan(paths.prdFile);\n plan = initializeState(plan);\n await saveTaskPlan(paths.prdFile, plan);\n\n // Check if already completed\n if (plan.issueStatus === 'completed' && allStoriesPass(plan)) {\n emitLog(`Issue already marked complete in ${paths.prdFile}`);\n return 0;\n }\n\n // Warn if marked complete but stories still pending\n if (plan.issueStatus === 'completed' && !allStoriesPass(plan)) {\n printWarning(\n 'Issue marked completed but some stories are still pending. Resetting to in_progress.',\n );\n plan = markIssueInProgress(plan);\n plan = setLastError(\n plan,\n 'invalid_completion_state',\n 'tasks.json claimed the issue was completed before every story had passes=true.',\n );\n await saveTaskPlan(paths.prdFile, plan);\n }\n\n // Check if all stories already pass\n if (allStoriesPass(plan)) {\n emitLog('All user stories already pass. Marking issue as completed.');\n plan = markIssueCompleted(plan);\n await saveTaskPlan(paths.prdFile, plan);\n return 0;\n }\n\n // Archive previous run if branch changed\n await archiveIfBranchChanged(plan, paths);\n\n // Track current branch\n await trackBranch(plan, paths.lastBranchFile);\n\n // Initialize progress file\n await ensureProgressFile(paths.progressFile);\n\n // Load prompt template\n const promptTemplate = await loadPrompt('execute');\n\n // Print startup header\n printStartupHeader(config, plan);\n\n const startTime = Date.now();\n let i = 0;\n let retryCount = 0;\n let totalRetryCount = 0;\n\n // Main loop\n while (true) {\n // Check iteration limit\n if (config.maxIterations !== undefined && i >= config.maxIterations) {\n break;\n }\n\n i++;\n getSessionPublisher().publish({ type: 'iteration:start', at: isoNow(), iteration: i });\n\n // Re-read plan to get latest state\n plan = await loadTaskPlan(paths.prdFile);\n\n if (isVerbose()) {\n printIterationHeader(i, config.maxIterations, plan.userStories);\n }\n\n // Apply placeholders to prompt\n const prompt = applyPlaceholders(promptTemplate, {\n __PRD_FILE__: paths.prdFile,\n __PROGRESS_FILE__: paths.progressFile,\n });\n\n const iterationStartedAt = isoNow();\n plan = markIssueInProgress(plan, iterationStartedAt);\n await saveTaskPlan(paths.prdFile, plan);\n\n // Execute Claude\n const result = await executeClaude(prompt);\n\n if (result.exitCode !== 0) {\n const errorMessage = trimErrorMessage(result.output);\n\n if (isTransientFailure(result.exitCode, result.output)) {\n retryCount++;\n totalRetryCount++;\n plan = await loadTaskPlan(paths.prdFile);\n plan = setLastError(plan, 'transient_claude_failure', errorMessage);\n await saveTaskPlan(paths.prdFile, plan);\n\n if (!config.retryForever && retryCount > config.retryLimit) {\n const elapsed = Math.floor((Date.now() - startTime) / 1000);\n plan = await loadTaskPlan(paths.prdFile);\n printSummaryBox(\n 'failed',\n i,\n totalRetryCount,\n elapsed,\n plan,\n `Exceeded retry limit (${config.retryLimit}) on transient errors`,\n );\n return result.exitCode;\n }\n\n const delaySeconds = retryDelaySeconds(\n retryCount,\n config.backoffBaseSeconds,\n config.backoffMaxSeconds,\n );\n getSessionPublisher().publish({\n type: 'retry',\n at: isoNow(),\n attempt: retryCount,\n delaySeconds,\n reason: 'transient_claude_failure',\n });\n\n if (isVerbose()) {\n emitLog('');\n }\n printRetry(\n `Transient Claude failure on iteration ${i} (attempt ${retryCount}). Retrying in ${delaySeconds}s.`,\n );\n\n // Stay within current iteration budget\n i--;\n await sleep(delaySeconds);\n continue;\n }\n\n // Fatal failure\n plan = await loadTaskPlan(paths.prdFile);\n plan = setLastError(plan, 'fatal_claude_failure', errorMessage);\n await saveTaskPlan(paths.prdFile, plan);\n\n const elapsed = Math.floor((Date.now() - startTime) / 1000);\n printSummaryBox(\n 'failed',\n i,\n totalRetryCount,\n elapsed,\n plan,\n `Claude CLI failed with exit code ${result.exitCode}`,\n );\n return result.exitCode;\n }\n\n // Success — reset retry counter\n retryCount = 0;\n plan = await loadTaskPlan(paths.prdFile);\n plan = clearLastError(plan, iterationStartedAt);\n await saveTaskPlan(paths.prdFile, plan);\n\n // Notify story progress listeners (e.g., listr2 subtasks)\n const storyUpdateCb = getStoryUpdateCallback();\n if (storyUpdateCb) {\n storyUpdateCb(plan.userStories);\n }\n getSessionPublisher().publish({\n type: 'stories:update',\n at: isoNow(),\n stories: plan.userStories,\n });\n getSessionPublisher().publish({ type: 'iteration:end', at: isoNow(), iteration: i });\n // Low-frequency commit/PR enrichment: iteration end is one of the only\n // two sanctioned points (the other is phase boundaries in run.ts).\n await publishGitState(getSessionPublisher());\n\n // Check for completion signal\n if (result.output.includes('<promise>COMPLETE</promise>')) {\n plan = await loadTaskPlan(paths.prdFile);\n if (allStoriesPass(plan)) {\n plan = markIssueCompleted(plan);\n await saveTaskPlan(paths.prdFile, plan);\n\n const elapsed = Math.floor((Date.now() - startTime) / 1000);\n printSummaryBox('success', i, totalRetryCount, elapsed, plan);\n return 0;\n }\n\n plan = setLastError(\n plan,\n 'invalid_completion_signal',\n 'Claude returned <promise>COMPLETE</promise> before every story had passes=true.',\n );\n await saveTaskPlan(paths.prdFile, plan);\n\n emitLog('');\n printWarning(\n 'Claude returned a completion signal, but tasks.json still has pending stories. Ignoring completion and continuing.',\n );\n }\n\n if (isVerbose()) {\n printSuccess(`Iteration ${i} complete. Continuing...`);\n }\n await sleep(2);\n }\n\n // Reached max iterations\n const elapsed = Math.floor((Date.now() - startTime) / 1000);\n plan = await loadTaskPlan(paths.prdFile);\n printSummaryBox(\n 'incomplete',\n config.maxIterations ?? i,\n totalRetryCount,\n elapsed,\n plan,\n 'Reached max iterations without completing all tasks.',\n );\n return 1;\n}\n","import chalk from 'chalk';\nimport { getOutputCallback } from '../core/verbose.js';\nimport type { UserStory } from '../types.js';\nimport { getIcons, useColor, useUnicode } from './logger.js';\n\n/**\n * Print a visual progress bar showing passed/total stories.\n */\nexport function printProgressBar(passed: number, total: number): string {\n const barWidth = 20;\n let pct = 0;\n let filled = 0;\n\n if (total > 0) {\n pct = Math.floor((passed * 100) / total);\n filled = Math.floor((passed * barWidth) / total);\n }\n\n const empty = barWidth - filled;\n const fillChar = useUnicode() ? '\\u2588' : '#';\n const emptyChar = useUnicode() ? '\\u2591' : '-';\n\n const bar = fillChar.repeat(filled) + emptyChar.repeat(empty);\n const text = `${passed}/${total} (${pct}%)`;\n\n if (useColor()) {\n return `${chalk.green(bar)} ${text}`;\n }\n return `${bar} ${text}`;\n}\n\n/**\n * Print the iteration header showing story statuses and progress bar.\n *\n * When a global output callback is set (e.g., inside a listr2 task),\n * output is routed through it instead of console.log.\n */\nexport function printIterationHeader(\n iteration: number,\n maxIter: number | undefined,\n stories: UserStory[],\n): void {\n const icons = getIcons();\n const colored = useColor();\n const cb = getOutputCallback();\n\n const iterLabel = maxIter ? `Iteration ${iteration} of ${maxIter}` : `Iteration ${iteration}`;\n\n const total = stories.length;\n const passed = stories.filter((s) => s.passes).length;\n\n const lines: string[] = [];\n\n if (colored) {\n lines.push(chalk.blue(`\\u2501\\u2501\\u2501 ${icons.start} ${iterLabel} \\u2501\\u2501\\u2501`));\n } else {\n lines.push(`--- ${icons.start} ${iterLabel} ---`);\n }\n\n // Display each story status\n let foundFirstPending = false;\n for (const story of stories) {\n let icon: string;\n let colorFn: (s: string) => string;\n\n if (story.passes) {\n icon = icons.success;\n colorFn = colored ? chalk.green : (s: string) => s;\n } else if (!foundFirstPending) {\n // First non-passing story = current in-progress\n icon = icons.pending;\n colorFn = colored ? chalk.yellow : (s: string) => s;\n foundFirstPending = true;\n } else {\n // Not yet reached\n icon = icons.notReached;\n colorFn = colored ? chalk.gray : (s: string) => s;\n }\n\n lines.push(colorFn(` ${icon} ${story.id}: ${story.title}`));\n }\n\n lines.push(` ${printProgressBar(passed, total)}`);\n\n if (cb) {\n // Send as single output to listr2 task context\n cb(lines.join('\\n'));\n } else {\n console.log('');\n for (const line of lines) {\n console.log(line);\n }\n console.log('');\n }\n}\n","import chalk from 'chalk';\nimport type { EngineConfig, TaskPlan } from '../types.js';\nimport { formatDuration, getIcons, getTermWidth, useColor, useUnicode } from './logger.js';\n\n/**\n * Truncate or pad a string to fit within a given width.\n */\nfunction fitLine(text: string, width: number): string {\n if (text.length > width) {\n return text.substring(0, width);\n }\n return text.padEnd(width);\n}\n\n/**\n * Print a box with border characters around content lines.\n * Lines equal to \"---\" render as separator rows.\n */\nexport function printBox(lines: string[]): void {\n const colored = useColor();\n const unicode = useUnicode();\n const termWidth = getTermWidth();\n\n // Find max content width\n let maxContentWidth = 0;\n for (const line of lines) {\n if (line !== '---' && line.length > maxContentWidth) {\n maxContentWidth = line.length;\n }\n }\n\n // Cap to terminal width (border + padding = 4 chars)\n const available = termWidth - 4;\n if (maxContentWidth > available) {\n maxContentWidth = available;\n }\n if (maxContentWidth < 20) {\n maxContentWidth = 20;\n }\n\n // Box drawing characters\n const tl = unicode ? '\\u256D' : '+';\n const tr = unicode ? '\\u256E' : '+';\n const bl = unicode ? '\\u2570' : '+';\n const br = unicode ? '\\u256F' : '+';\n const h = unicode ? '\\u2500' : '-';\n const v = unicode ? '\\u2502' : '|';\n const sepL = unicode ? '\\u251C' : '+';\n const sepR = unicode ? '\\u2524' : '+';\n\n const hrule = h.repeat(maxContentWidth + 2);\n\n const blue = colored ? chalk.blue : (s: string) => s;\n const _reset = (s: string) => s;\n\n // Top border\n console.log(blue(`${tl}${hrule}${tr}`));\n\n // Content lines\n for (const line of lines) {\n if (line === '---') {\n console.log(blue(`${sepL}${hrule}${sepR}`));\n } else {\n const fitted = fitLine(line, maxContentWidth);\n console.log(`${blue(v)} ${fitted} ${blue(v)}`);\n }\n }\n\n // Bottom border\n console.log(blue(`${bl}${hrule}${br}`));\n}\n\n/**\n * Print the startup header box showing engine configuration.\n */\nexport function printStartupHeader(config: EngineConfig, plan: TaskPlan): void {\n const icons = getIcons();\n\n const storiesTotal = plan.userStories.length;\n const storiesPassing = plan.userStories.filter((s) => s.passes).length;\n const branchName = plan.branchName ?? 'N/A';\n\n const issueLabel = config.issueNumber ? `Issue #${config.issueNumber}` : 'Standalone mode';\n\n const maxIterLabel =\n config.maxIterations !== undefined ? String(config.maxIterations) : 'unlimited';\n\n const retryLabel = config.retryForever\n ? 'unlimited retries'\n : `${config.retryLimit} consecutive retries`;\n\n printBox([\n `${icons.start} Issue Flow`,\n '---',\n `Issue: ${issueLabel}`,\n `Branch: ${branchName}`,\n `Stories: ${storiesPassing}/${storiesTotal} passing`,\n `Iterations: ${maxIterLabel}`,\n `Retries: ${retryLabel}`,\n ]);\n}\n\n// Re-export formatDuration from logger to maintain backwards compatibility\nexport { formatDuration } from './logger.js';\n\n/**\n * Print the final summary box.\n */\nexport function printSummaryBox(\n status: 'success' | 'incomplete' | 'failed',\n iterations: number,\n totalRetries: number,\n elapsedSeconds: number,\n plan: TaskPlan,\n extraInfo?: string,\n): void {\n const icons = getIcons();\n\n const storiesTotal = plan.userStories.length;\n const storiesPassing = plan.userStories.filter((s) => s.passes).length;\n const duration = formatDuration(elapsedSeconds);\n\n let statusIcon: string;\n let statusLabel: string;\n\n switch (status) {\n case 'success':\n statusIcon = icons.success;\n statusLabel = 'Completed';\n break;\n case 'incomplete':\n statusIcon = icons.warn;\n statusLabel = 'Incomplete';\n break;\n case 'failed':\n statusIcon = icons.fail;\n statusLabel = 'Failed';\n break;\n }\n\n const boxLines = [\n `${icons.end} Issue Flow Summary`,\n '---',\n `Status: ${statusIcon} ${statusLabel}`,\n `Stories: ${storiesPassing}/${storiesTotal} passing`,\n `Iterations: ${iterations}`,\n `Duration: ${duration}`,\n `Retries: ${totalRetries}`,\n ];\n\n if (extraInfo) {\n boxLines.push('---');\n boxLines.push(extraInfo);\n }\n\n console.log('');\n printBox(boxLines);\n}\n","import { execa } from 'execa';\nimport type { ClaudeResult } from '../types.js';\nimport { getOutputCallback } from './verbose.js';\n\n/**\n * Execute Claude CLI with a prompt piped to stdin.\n *\n * Runs: echo $PROMPT | claude --dangerously-skip-permissions --print\n *\n * Captures combined stdout+stderr output. Does not throw on non-zero exit codes.\n * Output is returned via ClaudeResult and routed through the output callback infrastructure.\n */\nexport async function executeClaude(prompt: string): Promise<ClaudeResult> {\n const result = await execa('claude', ['--dangerously-skip-permissions', '--print'], {\n input: prompt,\n reject: false,\n timeout: 0, // No timeout — let the engine handle iteration limits\n stripFinalNewline: false,\n });\n\n // Combine stdout and stderr to match Bash's 2>&1 behavior\n const stdout = result.stdout?.toString() ?? '';\n const stderr = result.stderr?.toString() ?? '';\n const output = stdout + (stderr ? `\\n${stderr}` : '');\n\n // Forward output through the global callback (listr2 renderer) when active\n const onOutput = getOutputCallback();\n if (onOutput) {\n const trimmed = output.trim();\n if (trimmed) {\n onOutput(trimmed);\n }\n }\n\n return {\n exitCode: result.exitCode ?? 1,\n output,\n };\n}\n","import { getBaseBranch, getCommitsSince, getCurrentBranch } from '../utils/git.js';\nimport { run } from '../utils/shell.js';\nimport {\n NullPublisher,\n type SessionCommit,\n type SessionPublisher,\n type SessionPullRequest,\n} from './session-state.js';\nimport { isoNow } from './state-manager.js';\n\n/**\n * Low-frequency enrichment of the session snapshot with git commits and pull\n * requests. Called only at phase boundaries (run.ts) and at the end of each\n * iteration (engine.ts) — never per HTTP request: the server (US-006) serves\n * the in-memory snapshot as-is.\n *\n * Like every monitoring surface, this must never affect the pipeline: with\n * the NullPublisher installed it returns before spawning any subprocess, and\n * every failure is swallowed.\n */\n\n/** Data sources, injectable for tests. */\nexport interface GitStateSources {\n currentBranch(): Promise<string>;\n baseBranch(): Promise<string>;\n commitsSince(base: string): Promise<SessionCommit[]>;\n pullRequests(branch: string): Promise<SessionPullRequest[]>;\n now(): string;\n}\n\n/**\n * List PRs whose head is the given branch via the GitHub CLI. Never throws;\n * returns [] when gh is unavailable, unauthenticated or returns bad JSON.\n */\nexport async function listPullRequests(branch: string): Promise<SessionPullRequest[]> {\n if (!branch) return [];\n\n const result = await run('gh', [\n 'pr',\n 'list',\n '--head',\n branch,\n '--state',\n 'all',\n '--json',\n 'number,url,title',\n '--limit',\n '10',\n ]);\n if (result.exitCode !== 0) return [];\n\n try {\n const parsed: unknown = JSON.parse(result.stdout || '[]');\n if (!Array.isArray(parsed)) return [];\n return parsed\n .filter(\n (pr): pr is { number: number; url: string; title: string } =>\n typeof pr === 'object' &&\n pr !== null &&\n typeof (pr as { number?: unknown }).number === 'number' &&\n typeof (pr as { url?: unknown }).url === 'string' &&\n typeof (pr as { title?: unknown }).title === 'string',\n )\n .map((pr) => ({ number: pr.number, url: pr.url, title: pr.title }));\n } catch {\n return [];\n }\n}\n\nconst defaultSources: GitStateSources = {\n currentBranch: getCurrentBranch,\n baseBranch: getBaseBranch,\n commitsSince: getCommitsSince,\n pullRequests: listPullRequests,\n now: isoNow,\n};\n\n/**\n * Gather branch, base branch, commits and PRs, then publish a single\n * git:update event. Never throws.\n */\nexport async function publishGitState(\n publisher: SessionPublisher,\n sources: Partial<GitStateSources> = {},\n): Promise<void> {\n if (publisher instanceof NullPublisher) return;\n const src: GitStateSources = { ...defaultSources, ...sources };\n\n try {\n const branch = await src.currentBranch();\n const baseBranch = await src.baseBranch();\n const [commits, pullRequests] = await Promise.all([\n src.commitsSince(baseBranch),\n src.pullRequests(branch),\n ]);\n\n publisher.publish({\n type: 'git:update',\n at: src.now(),\n branch,\n baseBranch,\n commits,\n pullRequests,\n });\n } catch {\n // Monitoring enrichment must never propagate errors to the pipeline.\n }\n}\n","import { createConfig, resolvePaths, validateDependencies } from '../config.js';\nimport { runEngine } from '../core/engine.js';\nimport { allStoriesPass, loadTaskPlan, saveTaskPlan } from '../core/state-manager.js';\nimport { printError } from '../ui/logger.js';\n\nexport interface ExecuteOptions {\n issue?: string;\n maxIterations?: number;\n retryLimit?: number;\n retryForever?: boolean;\n}\n\nexport async function runExecute(\n positionalMaxIter: number | undefined,\n options: ExecuteOptions,\n): Promise<number> {\n const errors = await validateDependencies();\n if (errors.length > 0) {\n printError('The following required tools are not installed:');\n for (const err of errors) {\n console.log(err);\n }\n return 1;\n }\n\n const maxIterations = options.maxIterations ?? positionalMaxIter;\n\n const config = createConfig({\n issueNumber: options.issue,\n maxIterations,\n retryLimit: options.retryLimit,\n retryForever: options.retryForever,\n });\n\n const paths = await resolvePaths(config);\n const exitCode = await runEngine(config, paths);\n\n // Update pipeline state if all stories pass\n if (exitCode === 0 && config.issueNumber) {\n try {\n const plan = await loadTaskPlan(paths.prdFile);\n if (allStoriesPass(plan)) {\n plan.pipeline.executionCompleted = true;\n await saveTaskPlan(paths.prdFile, plan);\n }\n } catch {\n // Non-critical — engine already handled state\n }\n }\n\n return exitCode;\n}\n"],"mappings":"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","names":["chalk","chalk","elapsed"]}
|