issue-flow 0.12.0 → 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-OEET7KGE.js → analyze-WS47UYGN.js} +28 -16
- package/dist/analyze-WS47UYGN.js.map +1 -0
- package/dist/{apply-ZIA3C52S.js → apply-OS62UWRN.js} +15 -7
- package/dist/{apply-ZIA3C52S.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-MJULIKQU.js → chunk-664Y3NDX.js} +23 -47
- 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-56KS3TFQ.js → chunk-7KD66LIM.js} +115 -160
- 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-NS6R37II.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-M5UCXTIT.js → chunk-HJGGNLWI.js} +22 -18
- 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-FJP3UQGD.js → chunk-IHIEXUTC.js} +39 -19
- package/dist/chunk-IHIEXUTC.js.map +1 -0
- package/dist/{chunk-Y5ZDJ4PJ.js → chunk-IV5SS4JN.js} +18 -10
- package/dist/chunk-IV5SS4JN.js.map +1 -0
- package/dist/{chunk-BYZDPR7N.js → chunk-IYV6KNVF.js} +146 -13
- package/dist/chunk-IYV6KNVF.js.map +1 -0
- package/dist/{chunk-TSOLWK7Y.js → chunk-J62QSVP6.js} +8 -8
- 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-3QHK4YQA.js → chunk-LZ6GFQAG.js} +334 -117
- package/dist/chunk-LZ6GFQAG.js.map +1 -0
- package/dist/{chunk-LAFNLLQP.js → chunk-MIVHK5YI.js} +5 -5
- package/dist/chunk-NEZZ6KDR.js +1217 -0
- package/dist/chunk-NEZZ6KDR.js.map +1 -0
- package/dist/{chunk-NTMMF5UD.js → chunk-OPNGPUKJ.js} +436 -254
- 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-FADTCQGM.js → chunk-PIVGJM6K.js} +94 -32
- package/dist/chunk-PIVGJM6K.js.map +1 -0
- package/dist/{chunk-73ATPIYX.js → chunk-PV4OX5TC.js} +36 -71
- 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-XQUSIEKA.js → chunk-QXDNBYQF.js} +52 -14
- 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-QCPGHBSZ.js → chunk-TM67EHSV.js} +63 -21
- 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-A4SLHOW7.js → chunk-UJ7XWK7J.js} +24 -18
- package/dist/chunk-UJ7XWK7J.js.map +1 -0
- package/dist/{chunk-EZATO3DW.js → chunk-UOCPC2Y2.js} +594 -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 +264 -44
- package/dist/cli.js.map +1 -1
- package/dist/{config-PRJGWW7J.js → config-MEM2IWWN.js} +28 -5
- 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-EGB4KV2B.js → generate-MVQI7A45.js} +34 -20
- package/dist/{generate-EGB4KV2B.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-BYT626KQ.js → operations-LU6JA3BX.js} +24 -15
- package/dist/{operations-BYT626KQ.js.map → operations-LU6JA3BX.js.map} +1 -1
- package/dist/permissions-7NMDFU27.js +18 -0
- package/dist/plan-LEJJF3DE.js +31 -0
- package/dist/{policy-6S4GFC76.js → policy-JSYEK5B2.js} +17 -6
- 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/prd-5O6GYCC6.js +30 -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-PPCNQVVV.js → resume-QMEPMSZW.js} +51 -34
- package/dist/{resume-PPCNQVVV.js.map → resume-QMEPMSZW.js.map} +1 -1
- 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-2EDVDBAR.js → state-manager-X3PT3FGT.js} +4 -2
- 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-EF5WIIGG.js → web-XVHLX62B.js} +41 -30
- 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 +2 -2
- package/web/AGENTS.md +3 -3
- package/web/public/app.css +38 -0
- package/web/public/app.js +121 -11
- package/web/public/index.html +24 -0
- package/dist/analyze-OEET7KGE.js.map +0 -1
- package/dist/chunk-3QHK4YQA.js.map +0 -1
- package/dist/chunk-42PJFMAR.js +0 -605
- package/dist/chunk-42PJFMAR.js.map +0 -1
- package/dist/chunk-4JSOYE4G.js +0 -368
- package/dist/chunk-4JSOYE4G.js.map +0 -1
- package/dist/chunk-56KS3TFQ.js.map +0 -1
- package/dist/chunk-73ATPIYX.js.map +0 -1
- package/dist/chunk-A4SLHOW7.js.map +0 -1
- package/dist/chunk-BAUPQXPE.js +0 -173
- package/dist/chunk-BAUPQXPE.js.map +0 -1
- package/dist/chunk-BYZDPR7N.js.map +0 -1
- package/dist/chunk-CD5ULRVC.js +0 -615
- package/dist/chunk-CD5ULRVC.js.map +0 -1
- package/dist/chunk-DIDUVL6O.js +0 -1365
- package/dist/chunk-DIDUVL6O.js.map +0 -1
- package/dist/chunk-EZATO3DW.js.map +0 -1
- package/dist/chunk-FADTCQGM.js.map +0 -1
- package/dist/chunk-FJP3UQGD.js.map +0 -1
- package/dist/chunk-LWRSMTTZ.js +0 -18
- package/dist/chunk-LWRSMTTZ.js.map +0 -1
- package/dist/chunk-M5UCXTIT.js.map +0 -1
- package/dist/chunk-M66SJHQQ.js +0 -206
- package/dist/chunk-M66SJHQQ.js.map +0 -1
- package/dist/chunk-MJULIKQU.js.map +0 -1
- package/dist/chunk-NS6R37II.js.map +0 -1
- package/dist/chunk-NTMMF5UD.js.map +0 -1
- package/dist/chunk-QCPGHBSZ.js.map +0 -1
- package/dist/chunk-XQUSIEKA.js.map +0 -1
- package/dist/chunk-Y5ZDJ4PJ.js.map +0 -1
- package/dist/execute-XBNVL753.js +0 -18
- package/dist/init-AJHCXJBP.js +0 -10
- package/dist/plan-QOZ3RIO6.js +0 -21
- package/dist/policy-6S4GFC76.js.map +0 -1
- package/dist/pr-EBBHD2ZX.js +0 -26
- package/dist/pr-review-VVMW5PO4.js +0 -20
- package/dist/prd-JQSZF7PR.js +0 -21
- package/dist/review-3F6MARWH.js +0 -19
- package/dist/run-RXE5GDQO.js +0 -36
- package/dist/web-EF5WIIGG.js.map +0 -1
- /package/dist/{chunk-TSOLWK7Y.js.map → chunk-J62QSVP6.js.map} +0 -0
- /package/dist/{chunk-LAFNLLQP.js.map → chunk-MIVHK5YI.js.map} +0 -0
- /package/dist/{config-PRJGWW7J.js.map → config-MEM2IWWN.js.map} +0 -0
- /package/dist/{execute-XBNVL753.js.map → contract-JH2WBIO6.js.map} +0 -0
- /package/dist/{init-AJHCXJBP.js.map → execute-NITIHHCK.js.map} +0 -0
- /package/dist/{plan-QOZ3RIO6.js.map → git-YRVRUN3C.js.map} +0 -0
- /package/dist/{pr-EBBHD2ZX.js.map → init-OKWEC3EV.js.map} +0 -0
- /package/dist/{pr-review-VVMW5PO4.js.map → permissions-7NMDFU27.js.map} +0 -0
- /package/dist/{prd-JQSZF7PR.js.map → plan-LEJJF3DE.js.map} +0 -0
- /package/dist/{review-3F6MARWH.js.map → pr-HPVUZ7S3.js.map} +0 -0
- /package/dist/{run-RXE5GDQO.js.map → pr-review-R2QZBT3F.js.map} +0 -0
- /package/dist/{state-manager-2EDVDBAR.js.map → prd-5O6GYCC6.js.map} +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/config.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\nimport { platform } from 'node:os';\nimport { join } from 'node:path';\nimport type { IssuesConfig } from './issues/types.js';\nimport { RETRY_CONFIG_KEYS, type RetryPolicyOverride } from './resilience/policy.js';\nimport {\n issuesConfigSchema,\n type PolicyConfig,\n type PolicyConfigInput,\n type PrReviewConfig,\n policyConfigInputSchema,\n policyConfigSchema,\n prReviewConfigSchema,\n type WebConfig,\n webConfigSchema,\n} from './schemas.js';\nimport { getGlobalRoot } from './storage/paths.js';\nimport { resolveIssuePaths } from './storage/resolve.js';\nimport {\n type GlobalConfig,\n globalConfigSchema,\n type ResilienceConfig,\n type ResilienceRetryConfig,\n resilienceConfigSchema,\n} from './storage/schemas.js';\nimport type { EngineConfig, ResolvedPaths } from './types.js';\nimport { printWarning } from './ui/logger.js';\nimport { getProjectRoot } from './utils/git.js';\nimport { run } from './utils/shell.js';\n\n/**\n * Default configuration values — matching the Bash script exactly.\n */\nexport const DEFAULTS = {\n retryLimit: 10,\n retryForever: false,\n backoffBaseSeconds: 30,\n backoffMaxSeconds: 900,\n} as const;\n\n/**\n * Create a EngineConfig with defaults merged with provided options.\n */\nexport function createConfig(options: Partial<EngineConfig>): EngineConfig {\n return {\n issueNumber: options.issueNumber,\n maxIterations: options.maxIterations,\n retryLimit: options.retryLimit ?? DEFAULTS.retryLimit,\n retryForever: options.retryForever ?? DEFAULTS.retryForever,\n backoffBaseSeconds: options.backoffBaseSeconds ?? DEFAULTS.backoffBaseSeconds,\n backoffMaxSeconds: options.backoffMaxSeconds ?? DEFAULTS.backoffMaxSeconds,\n // Left absent (rather than defaulted to an empty string) so the execute\n // prompt keeps its historical commit format unless a queue asks otherwise.\n ...(options.commitScope === undefined ? {} : { commitScope: options.commitScope }),\n };\n}\n\n/**\n * Resolve file paths based on issue number and project root.\n *\n * With --issue N, every artifact comes from the global storage layer via\n * `resolveIssuePaths()`, which also migrates the legacy `<projectRoot>/issues/`\n * tree on first read:\n * prdFile = ~/.issue-flow/projects/{id}/issues/{N}/tasks.json\n * progressFile = ~/.issue-flow/projects/{id}/issues/{N}/progress.txt\n *\n * Standalone:\n * prdFile = {projectRoot}/prd.json\n * progressFile = {projectRoot}/progress.txt\n *\n * Beware of the asymmetric mapping in the issue branch: `ResolvedPaths.prdFile`\n * is the engine's *task plan*, so it maps to `IssuePaths.tasksFile`\n * (`tasks.json`) and **not** to `IssuePaths.prdFile` (`prd.md`, the human-facing\n * document produced by the `prd` phase). The name predates the split and is kept\n * because standalone mode really does read a `prd.json`.\n *\n * `projectRoot` stays on the result either way: `core/engine.ts` uses it as the\n * cwd of its git operations, which the global storage does not replace.\n */\nexport async function resolvePaths(\n config: EngineConfig,\n scriptDir?: string,\n): Promise<ResolvedPaths> {\n const projectRoot = await getProjectRoot();\n\n if (config.issueNumber) {\n // projectRoot is forwarded so the resolver does not shell out to\n // `git rev-parse --show-toplevel` a second time for the answer we just got.\n const issuePaths = await resolveIssuePaths(config.issueNumber, { projectRoot });\n return {\n prdFile: issuePaths.tasksFile,\n progressFile: issuePaths.progressFile,\n archiveDir: issuePaths.archiveDir,\n lastBranchFile: issuePaths.lastBranchFile,\n projectRoot,\n };\n }\n\n // Standalone mode — use scriptDir if available, otherwise projectRoot\n const base = scriptDir ?? projectRoot;\n return {\n prdFile: join(base, 'prd.json'),\n progressFile: join(base, 'progress.txt'),\n archiveDir: join(base, 'archive'),\n lastBranchFile: join(base, '.last-branch'),\n projectRoot,\n };\n}\n\n/**\n * Return a platform-appropriate install hint for a given package.\n */\nexport function getInstallHint(pkg: string): string {\n const os = platform();\n\n if (os === 'darwin') {\n return `brew install ${pkg}`;\n }\n if (os === 'linux') {\n return `apt install ${pkg} (or your distro's package manager)`;\n }\n if (os === 'win32') {\n return `winget install ${pkg} (or choco install ${pkg})`;\n }\n\n return `install ${pkg} using your system package manager`;\n}\n\n/**\n * Validate that required external dependencies are available.\n * Returns an array of error messages (empty if all deps are found).\n */\nexport async function validateDependencies(): Promise<string[]> {\n const errors: string[] = [];\n\n // Check git\n const gitResult = await run('git', ['--version']);\n if (gitResult.exitCode !== 0) {\n errors.push(` - git (install with: ${getInstallHint('git')})`);\n }\n\n // Check claude\n const claudeResult = await run('claude', ['--version']);\n if (claudeResult.exitCode !== 0) {\n errors.push(' - claude (install with: npm install -g @anthropic-ai/claude-code)');\n }\n\n // Note: jq is NOT required — the TypeScript CLI handles JSON natively\n\n return errors;\n}\n\n// ── Per-project configuration file ──────────────────────────────────────────\n\n/** Optional per-project configuration file, read from the project root. */\nexport const PROJECT_CONFIG_FILENAME = '.issue-flow.json';\n\n/** Historical alias kept for the web monitoring call sites. */\nexport const WEB_CONFIG_FILENAME = PROJECT_CONFIG_FILENAME;\n\n/**\n * Read and parse .issue-flow.json from the project root.\n *\n * Never throws: an absent file, unreadable path, invalid JSON or a non-object\n * root all degrade to `null` (with a warning for the malformed cases), so every\n * consumer falls back to its own defaults.\n */\nasync function readProjectConfigFile(\n projectRoot: string | undefined,\n warn: (message: string) => void,\n): Promise<Record<string, unknown> | null> {\n let root = projectRoot;\n if (root === undefined) {\n try {\n root = await getProjectRoot();\n } catch {\n return null;\n }\n }\n\n const filePath = join(root, PROJECT_CONFIG_FILENAME);\n let raw: string;\n try {\n raw = await readFile(filePath, 'utf-8');\n } catch {\n // The file is entirely optional — absence is the common case.\n return null;\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n warn(`Ignoring ${PROJECT_CONFIG_FILENAME}: invalid JSON.`);\n return null;\n }\n\n if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {\n warn(`Ignoring ${PROJECT_CONFIG_FILENAME}: expected a JSON object.`);\n return null;\n }\n\n return parsed as Record<string, unknown>;\n}\n\n// ── Global configuration file ───────────────────────────────────────────────\n\n/** Machine-wide configuration file, read from the global storage root. */\nexport const GLOBAL_CONFIG_FILENAME = 'config.json';\n\nexport interface LoadGlobalConfigOptions {\n /** Environment source, forwarded to getGlobalRoot(). Defaults to process.env. */\n env?: NodeJS.ProcessEnv;\n /** Directory holding config.json. Defaults to getGlobalRoot(). */\n globalRoot?: string;\n /** Warning sink. Defaults to printWarning. */\n warn?: (message: string) => void;\n}\n\n/**\n * Read and parse `~/.issue-flow/config.json`, the preferences a user sets once\n * for every project.\n *\n * Never throws, exactly like readProjectConfigFile(): an absent file, an\n * unreadable path, invalid JSON, a non-object root or an invalid key all\n * degrade to \"no global preference\" so the caller falls back to the layers\n * below. Absence is silent — it is the common case; every other failure warns,\n * because a preference the user did write is being dropped.\n *\n * Validation happens key by key on purpose: a typo in `retry` must not cost the\n * user their `web` settings. Unknown keys are dropped without a warning, which\n * is what keeps a file written by a newer release readable here.\n */\nexport async function loadGlobalConfig(\n options: LoadGlobalConfigOptions = {},\n): Promise<GlobalConfig> {\n const warn = options.warn ?? printWarning;\n\n let root: string;\n try {\n root = options.globalRoot ?? getGlobalRoot({ env: options.env ?? process.env });\n } catch (err: unknown) {\n warn(`Ignoring ${GLOBAL_CONFIG_FILENAME}: ${(err as Error).message}`);\n return {};\n }\n\n const filePath = join(root, GLOBAL_CONFIG_FILENAME);\n let raw: string;\n try {\n raw = await readFile(filePath, 'utf-8');\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException).code !== 'ENOENT') {\n warn(`Ignoring ${GLOBAL_CONFIG_FILENAME}: ${(err as Error).message}`);\n }\n return {};\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n warn(`Ignoring ${GLOBAL_CONFIG_FILENAME}: invalid JSON.`);\n return {};\n }\n\n if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {\n warn(`Ignoring ${GLOBAL_CONFIG_FILENAME}: expected a JSON object.`);\n return {};\n }\n\n const config: GlobalConfig = {};\n for (const [key, value] of Object.entries(parsed)) {\n const result = globalConfigSchema.safeParse({ [key]: value });\n if (!result.success) {\n warn(\n `Ignoring \"${key}\" key of ${GLOBAL_CONFIG_FILENAME}: ${result.error.issues[0]?.message ?? 'invalid value'}.`,\n );\n continue;\n }\n // An unknown key parses successfully into an empty object and disappears\n // here, which is the retro-compatible behaviour we want.\n Object.assign(config, result.data);\n }\n\n return config;\n}\n\n/**\n * The configuration sources of the engine, ordered from the lowest to the\n * highest precedence:\n *\n * defaults < discovered < config.json (global) < .issue-flow.json (project)\n * < env < CLI\n */\nexport interface ConfigLayers<T extends object> {\n /** Hard-coded fallbacks, e.g. DEFAULTS or the values baked into a schema. */\n defaults?: Partial<T>;\n /**\n * Values read from the consumer repository itself — the policies discovered\n * by `src/policy/`. They sit just above the defaults: a repository's own\n * convention beats a fallback Issue Flow invented, and loses to anything the\n * user explicitly configured.\n */\n discovered?: Partial<T>;\n /** ~/.issue-flow/config.json, via loadGlobalConfig(). */\n global?: Partial<T>;\n /** The matching key of .issue-flow.json in the project root. */\n project?: Partial<T>;\n /** ISSUE_FLOW_* environment variables. */\n env?: Partial<T>;\n /** CLI flags. */\n cli?: Partial<T>;\n}\n\n/**\n * Merge configuration layers following the documented precedence.\n *\n * Pure and shallow: a layer only participates with the keys it actually\n * carries, so an absent key never erases the layer below it. `undefined` counts\n * as absent — that is what lets a layer be built by assigning only the values\n * that were really provided.\n *\n * Because the merge is shallow, nested objects (`web`, `retry`) are replaced\n * whole rather than merged field by field; callers that need per-field\n * precedence inside a nested key must flatten it into its own merge.\n *\n * Caveat for the `project` layer: it must be the *raw* set of keys the user\n * wrote, not the output of a schema that materializes defaults. In zod 4 a\n * `.default()` survives `.partial()`, so parsing the project file with\n * `webConfigSchema.partial()` yields every default and would make the project\n * layer swallow the global one.\n */\nexport function mergeConfigLayers<T extends object>(layers: ConfigLayers<T>): Partial<T> {\n const merged: Record<string, unknown> = {};\n\n for (const layer of [\n layers.defaults,\n layers.discovered,\n layers.global,\n layers.project,\n layers.env,\n layers.cli,\n ]) {\n for (const [key, value] of Object.entries(layer ?? {})) {\n if (value !== undefined) {\n merged[key] = value;\n }\n }\n }\n\n return merged as Partial<T>;\n}\n\n// ── Web monitoring configuration ────────────────────────────────────────────\n\n/**\n * CLI overrides captured by the preAction hook in cli.ts. Highest-precedence\n * source consumed by loadWebConfig().\n */\nlet webCliOverrides: Partial<WebConfig> = {};\n\nexport function setWebCliOverrides(overrides: Partial<WebConfig>): void {\n webCliOverrides = overrides;\n}\n\nexport interface LoadWebConfigOptions {\n /** CLI flag overrides. Defaults to the values set via setWebCliOverrides(). */\n cli?: Partial<WebConfig>;\n /** Environment source. Defaults to process.env. */\n env?: NodeJS.ProcessEnv;\n /** Directory containing .issue-flow.json. Defaults to the git project root. */\n projectRoot?: string;\n /** Warning sink. Defaults to printWarning. */\n warn?: (message: string) => void;\n}\n\nconst FALSY_ENV_VALUES = new Set(['', '0', 'false', 'no', 'off']);\n\nfunction parseBooleanEnv(value: string): boolean {\n return !FALSY_ENV_VALUES.has(value.trim().toLowerCase());\n}\n\nfunction readNumberEnv(\n env: NodeJS.ProcessEnv,\n name: string,\n warn: (message: string) => void,\n): number | undefined {\n const raw = env[name];\n if (raw === undefined) {\n return undefined;\n }\n const parsed = Number(raw);\n if (!Number.isFinite(parsed)) {\n warn(`Ignoring ${name}=\"${raw}\": not a number.`);\n return undefined;\n }\n return parsed;\n}\n\nfunction readWebConfigEnv(\n env: NodeJS.ProcessEnv,\n warn: (message: string) => void,\n): Partial<WebConfig> {\n const layer: Partial<WebConfig> = {};\n if (env.ISSUE_FLOW_WEB !== undefined) {\n layer.enabled = parseBooleanEnv(env.ISSUE_FLOW_WEB);\n }\n const port = readNumberEnv(env, 'ISSUE_FLOW_WEB_PORT', warn);\n if (port !== undefined) {\n layer.port = port;\n }\n if (env.ISSUE_FLOW_WEB_HOST !== undefined) {\n layer.host = env.ISSUE_FLOW_WEB_HOST;\n }\n const refresh = readNumberEnv(env, 'ISSUE_FLOW_WEB_REFRESH', warn);\n if (refresh !== undefined) {\n layer.refreshSeconds = refresh;\n }\n const logLimit = readNumberEnv(env, 'ISSUE_FLOW_WEB_LOG_LIMIT', warn);\n if (logLimit !== undefined) {\n layer.logLimit = logLimit;\n }\n return layer;\n}\n\nasync function readWebConfigFile(\n projectRoot: string | undefined,\n warn: (message: string) => void,\n): Promise<Partial<WebConfig>> {\n const file = await readProjectConfigFile(projectRoot, warn);\n const web = file?.web;\n if (web === undefined) {\n return {};\n }\n\n const result = webConfigSchema.partial().safeParse(web);\n if (!result.success) {\n warn(\n `Ignoring \"web\" key of ${PROJECT_CONFIG_FILENAME}: ${result.error.issues[0]?.message ?? 'invalid value'}.`,\n );\n return {};\n }\n return result.data;\n}\n\n/**\n * Resolve the web monitoring configuration with the documented precedence:\n * CLI flag > environment variable > .issue-flow.json > defaults.\n *\n * Never throws: missing or invalid sources degrade to the defaults with a\n * warning.\n */\nexport async function loadWebConfig(options: LoadWebConfigOptions = {}): Promise<WebConfig> {\n const warn = options.warn ?? printWarning;\n const cli = options.cli ?? webCliOverrides;\n const env = options.env ?? process.env;\n\n const fileLayer = await readWebConfigFile(options.projectRoot, warn);\n const envLayer = readWebConfigEnv(env, warn);\n const merged = { ...fileLayer, ...envLayer, ...cli };\n\n const result = webConfigSchema.safeParse(merged);\n if (result.success) {\n return result.data;\n }\n warn(\n `Invalid web monitoring configuration (${result.error.issues[0]?.message ?? 'invalid value'}); using defaults.`,\n );\n return webConfigSchema.parse({});\n}\n\n// ── Issue provider configuration ────────────────────────────────────────────\n\n/**\n * CLI overrides captured by the preAction hook in cli.ts (--local, --github,\n * --prefer-local, --prefer-github, --ask). Highest-precedence source consumed\n * by loadIssuesConfig().\n */\nlet issuesCliOverrides: Partial<IssuesConfig> = {};\n\nexport function setIssuesCliOverrides(overrides: Partial<IssuesConfig>): void {\n issuesCliOverrides = overrides;\n}\n\nexport interface LoadIssuesConfigOptions {\n /** CLI flag overrides. Defaults to the values set via setIssuesCliOverrides(). */\n cli?: Partial<IssuesConfig>;\n /** Directory containing .issue-flow.json. Defaults to the git project root. */\n projectRoot?: string;\n /** Warning sink. Defaults to printWarning. */\n warn?: (message: string) => void;\n}\n\nasync function readIssuesConfigFile(\n projectRoot: string | undefined,\n warn: (message: string) => void,\n): Promise<Partial<IssuesConfig>> {\n const file = await readProjectConfigFile(projectRoot, warn);\n const issues = file?.issues;\n if (issues === undefined) {\n return {};\n }\n\n const result = issuesConfigSchema.partial().safeParse(issues);\n if (!result.success) {\n warn(\n `Ignoring \"issues\" key of ${PROJECT_CONFIG_FILENAME}: ${result.error.issues[0]?.message ?? 'invalid value'}.`,\n );\n return {};\n }\n return result.data;\n}\n\n/**\n * Resolve the Issue provider configuration with the documented precedence:\n * CLI flag > .issue-flow.json > defaults.\n *\n * Never throws: missing or invalid sources degrade to the defaults with a\n * warning, and the defaults reproduce the GitHub-only behaviour.\n */\nexport async function loadIssuesConfig(\n options: LoadIssuesConfigOptions = {},\n): Promise<IssuesConfig> {\n const warn = options.warn ?? printWarning;\n const cli = options.cli ?? issuesCliOverrides;\n\n const fileLayer = await readIssuesConfigFile(options.projectRoot, warn);\n const merged = { ...fileLayer, ...cli };\n\n const result = issuesConfigSchema.safeParse(merged);\n if (result.success) {\n return result.data;\n }\n warn(\n `Invalid Issue provider configuration (${result.error.issues[0]?.message ?? 'invalid value'}); using defaults.`,\n );\n return issuesConfigSchema.parse({});\n}\n\n// ── PR review configuration ─────────────────────────────────────────────────\n\nexport interface LoadPrReviewConfigOptions {\n /** Highest-precedence layer, for a future `--publisher` flag. */\n cli?: Partial<PrReviewConfig>;\n /** Environment source. Defaults to process.env. */\n env?: NodeJS.ProcessEnv;\n /** Directory containing .issue-flow.json. Defaults to the git project root. */\n projectRoot?: string;\n /** Warning sink. Defaults to printWarning. */\n warn?: (message: string) => void;\n}\n\nfunction readPrReviewConfigEnv(env: NodeJS.ProcessEnv): Partial<PrReviewConfig> {\n const publisher = env.ISSUE_FLOW_PR_REVIEW_PUBLISHER;\n // An unknown value is not dropped here: it goes through the schema below, so\n // the user gets the same warning a bad config file would produce.\n return publisher === undefined ? {} : { publisher: publisher as PrReviewConfig['publisher'] };\n}\n\nasync function readPrReviewConfigFile(\n projectRoot: string | undefined,\n warn: (message: string) => void,\n): Promise<Partial<PrReviewConfig>> {\n const file = await readProjectConfigFile(projectRoot, warn);\n const prReview = file?.prReview;\n if (prReview === undefined) {\n return {};\n }\n\n const result = prReviewConfigSchema.partial().safeParse(prReview);\n if (!result.success) {\n warn(\n `Ignoring \"prReview\" key of ${PROJECT_CONFIG_FILENAME}: ${result.error.issues[0]?.message ?? 'invalid value'}.`,\n );\n return {};\n }\n return result.data;\n}\n\n/**\n * Resolve the `pr-review` configuration with the documented precedence:\n * CLI flag > environment variable > .issue-flow.json > defaults.\n *\n * Never throws: an absent, malformed or unknown value degrades to the default\n * publisher with a warning, so a typo in .issue-flow.json costs a warning\n * rather than the review.\n */\nexport async function loadPrReviewConfig(\n options: LoadPrReviewConfigOptions = {},\n): Promise<PrReviewConfig> {\n const warn = options.warn ?? printWarning;\n const env = options.env ?? process.env;\n\n const fileLayer = await readPrReviewConfigFile(options.projectRoot, warn);\n const envLayer = readPrReviewConfigEnv(env);\n const merged = { ...fileLayer, ...envLayer, ...(options.cli ?? {}) };\n\n const result = prReviewConfigSchema.safeParse(merged);\n if (result.success) {\n return result.data;\n }\n warn(\n `Invalid PR review configuration (${result.error.issues[0]?.message ?? 'invalid value'}); using defaults.`,\n );\n return prReviewConfigSchema.parse({});\n}\n\n// ── Repository policy configuration ─────────────────────────────────────────\n\n/**\n * CLI overrides for the `policy` key, captured by the preAction hook in cli.ts.\n * Highest-precedence source consumed by loadPolicyConfig().\n */\nlet policyCliOverrides: PolicyConfigInput = {};\n\nexport function setPolicyCliOverrides(overrides: PolicyConfigInput): void {\n policyCliOverrides = overrides;\n}\n\nexport interface LoadPolicyConfigOptions {\n /** CLI flag overrides. Defaults to the values set via setPolicyCliOverrides(). */\n cli?: PolicyConfigInput;\n /** Environment source. Defaults to process.env. */\n env?: NodeJS.ProcessEnv;\n /** Directory containing .issue-flow.json. Defaults to the git project root. */\n projectRoot?: string;\n /** Warning sink. Defaults to printWarning. */\n warn?: (message: string) => void;\n}\n\n/**\n * Drop the keys a layer left as `null` or `undefined`.\n *\n * `mergeConfigLayers` only treats `undefined` as \"absent\", and the input schema\n * accepts `null` as the natural way to write \"I do not declare this\" — without\n * this, a `\"baseBranch\": null` would win over the branch discovery found.\n */\nfunction dropNullish<T extends object>(layer: T | undefined): Partial<T> {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(layer ?? {})) {\n if (value !== undefined && value !== null) {\n result[key] = value;\n }\n }\n return result as Partial<T>;\n}\n\nfunction readPolicyConfigEnv(\n env: NodeJS.ProcessEnv,\n warn: (message: string) => void,\n): PolicyConfigInput {\n const layer: PolicyConfigInput = {};\n\n if (env.ISSUE_FLOW_POLICY !== undefined) {\n layer.enabled = parseBooleanEnv(env.ISSUE_FLOW_POLICY);\n }\n const budget = readNumberEnv(env, 'ISSUE_FLOW_POLICY_CONTEXT_BUDGET', warn);\n if (budget !== undefined) {\n layer.contextBudget = budget;\n }\n\n const issues: NonNullable<PolicyConfigInput['issues']> = {};\n if (env.ISSUE_FLOW_POLICY_ISSUE_TITLE_CONVENTION !== undefined) {\n issues.titleConvention = env.ISSUE_FLOW_POLICY_ISSUE_TITLE_CONVENTION;\n }\n if (Object.keys(issues).length > 0) layer.issues = issues;\n\n const pullRequests: NonNullable<PolicyConfigInput['pullRequests']> = {};\n if (env.ISSUE_FLOW_POLICY_BASE_BRANCH !== undefined) {\n pullRequests.baseBranch = env.ISSUE_FLOW_POLICY_BASE_BRANCH;\n }\n if (env.ISSUE_FLOW_POLICY_PR_TITLE_CONVENTION !== undefined) {\n pullRequests.titleConvention = env.ISSUE_FLOW_POLICY_PR_TITLE_CONVENTION;\n }\n if (Object.keys(pullRequests).length > 0) layer.pullRequests = pullRequests;\n\n const git: NonNullable<PolicyConfigInput['git']> = {};\n if (env.ISSUE_FLOW_POLICY_BRANCH_CONVENTION !== undefined) {\n git.branchConvention = env.ISSUE_FLOW_POLICY_BRANCH_CONVENTION;\n }\n if (env.ISSUE_FLOW_POLICY_COMMIT_CONVENTION !== undefined) {\n git.commitConvention = env.ISSUE_FLOW_POLICY_COMMIT_CONVENTION;\n }\n if (Object.keys(git).length > 0) layer.git = git;\n\n // An empty string is how a shell spells \"unset it again\"; it reaches the\n // schema below and is reported like any other invalid value would be.\n const invalid = Object.entries(layer).find(\n ([, value]) => typeof value === 'object' && value !== null && Object.values(value).includes(''),\n );\n if (invalid !== undefined) {\n warn(`Ignoring empty ISSUE_FLOW_POLICY_* value in \"${invalid[0]}\".`);\n }\n\n return layer;\n}\n\nasync function readPolicyConfigFile(\n projectRoot: string | undefined,\n warn: (message: string) => void,\n): Promise<PolicyConfigInput> {\n const file = await readProjectConfigFile(projectRoot, warn);\n const policy = file?.policy;\n if (policy === undefined) {\n return {};\n }\n\n const result = policyConfigInputSchema.safeParse(policy);\n if (!result.success) {\n warn(\n `Ignoring \"policy\" key of ${PROJECT_CONFIG_FILENAME}: ${result.error.issues[0]?.message ?? 'invalid value'}.`,\n );\n return {};\n }\n return result.data;\n}\n\n/**\n * Resolve the repository policy configuration with the documented precedence:\n * CLI flag > ISSUE_FLOW_POLICY_* > .issue-flow.json > defaults.\n *\n * This is only the *explicit* half of the ladder. What the repository declares\n * about itself is discovered by `src/policy/` and merged one rung below, so\n * this function deliberately does not materialize the declarations as `null`:\n * an unset `baseBranch` must stay absent, or it would overrule the base branch\n * discovery actually found.\n *\n * Never throws: an absent, malformed or invalid source degrades to the\n * defaults — discovery on, nothing declared — with a warning.\n */\nexport async function loadPolicyConfig(\n options: LoadPolicyConfigOptions = {},\n): Promise<PolicyConfig> {\n const warn = options.warn ?? printWarning;\n const cli = options.cli ?? policyCliOverrides;\n const env = options.env ?? process.env;\n\n const fileLayer = await readPolicyConfigFile(options.projectRoot, warn);\n const envLayer = readPolicyConfigEnv(env, warn);\n\n const merged = {\n ...mergeConfigLayers<{ enabled: boolean; contextBudget: number }>({\n project: dropNullish({ enabled: fileLayer.enabled, contextBudget: fileLayer.contextBudget }),\n env: dropNullish({ enabled: envLayer.enabled, contextBudget: envLayer.contextBudget }),\n cli: dropNullish({ enabled: cli.enabled, contextBudget: cli.contextBudget }),\n }),\n discovery: mergeConfigLayers({\n project: dropNullish(fileLayer.discovery),\n env: dropNullish(envLayer.discovery),\n cli: dropNullish(cli.discovery),\n }),\n issues: mergeConfigLayers({\n project: dropNullish(fileLayer.issues),\n env: dropNullish(envLayer.issues),\n cli: dropNullish(cli.issues),\n }),\n pullRequests: mergeConfigLayers({\n project: dropNullish(fileLayer.pullRequests),\n env: dropNullish(envLayer.pullRequests),\n cli: dropNullish(cli.pullRequests),\n }),\n git: mergeConfigLayers({\n project: dropNullish(fileLayer.git),\n env: dropNullish(envLayer.git),\n cli: dropNullish(cli.git),\n }),\n };\n\n const result = policyConfigSchema.safeParse(merged);\n if (result.success) {\n return result.data;\n }\n warn(\n `Invalid repository policy configuration (${result.error.issues[0]?.message ?? 'invalid value'}); using defaults.`,\n );\n return policyConfigSchema.parse({});\n}\n\n// ── Resilience configuration ────────────────────────────────────────────────\n\n/**\n * CLI overrides for the `resilience` key, captured by the preAction hook in\n * cli.ts. Highest-precedence source consumed by loadResilienceConfig().\n */\nlet resilienceCliOverrides: ResilienceConfig = {};\n\nexport function setResilienceCliOverrides(overrides: ResilienceConfig): void {\n resilienceCliOverrides = overrides;\n}\n\nexport interface LoadResilienceConfigOptions {\n /** CLI flag overrides. Defaults to the values set via setResilienceCliOverrides(). */\n cli?: ResilienceConfig;\n /** Environment source. Defaults to process.env. */\n env?: NodeJS.ProcessEnv;\n /** Pre-read `config.json` layer, so a caller holding it avoids a second read. */\n global?: ResilienceConfig;\n /** Directory holding config.json. Defaults to getGlobalRoot(). */\n globalRoot?: string;\n /** Directory containing .issue-flow.json. Defaults to the git project root. */\n projectRoot?: string;\n /** Warning sink. Defaults to printWarning. */\n warn?: (message: string) => void;\n}\n\n/**\n * Merge one nested section of the key across the four layers, in the ladder's\n * order, and report **absence as absence**: a section nobody configured comes\n * back `undefined` rather than `{}`, which is what lets an unconfigured\n * `resilience` resolve to an empty object instead of a skeleton of empty ones.\n */\nfunction mergeResilienceSection<T extends object>(\n layers: readonly (Partial<T> | undefined)[],\n): Partial<T> | undefined {\n const merged = mergeConfigLayers<T>({\n global: layers[0],\n project: layers[1],\n env: layers[2],\n cli: layers[3],\n });\n return Object.keys(merged).length > 0 ? merged : undefined;\n}\n\n/**\n * `resilience.retry`, merged per `FailureKind` **and** per field inside each\n * kind: a project file that only raises `network.maxDelayMs` must not erase a\n * `network.retryForever` set machine-wide, nor the other kinds next to it.\n *\n * This is the one place the merge goes deeper than `mergeConfigLayers()` does\n * on its own — the documented shallowness stops at the first nested object, and\n * `retry` is two levels deep by construction.\n */\nfunction mergeResilienceRetry(\n layers: readonly (ResilienceRetryConfig | undefined)[],\n): ResilienceRetryConfig | undefined {\n const merged: ResilienceRetryConfig = {};\n for (const key of RETRY_CONFIG_KEYS) {\n const entry = mergeResilienceSection<RetryPolicyOverride>(layers.map((layer) => layer?.[key]));\n if (entry !== undefined) {\n merged[key] = entry;\n }\n }\n return Object.keys(merged).length > 0 ? merged : undefined;\n}\n\n/** Validate one layer, degrading a malformed one to \"nothing configured\". */\nfunction parseResilienceLayer(\n value: unknown,\n origin: string,\n warn: (message: string) => void,\n): ResilienceConfig {\n const result = resilienceConfigSchema.safeParse(value);\n if (!result.success) {\n warn(\n `Ignoring \"resilience\" key of ${origin}: ${result.error.issues[0]?.message ?? 'invalid value'}.`,\n );\n return {};\n }\n return result.data;\n}\n\nasync function readResilienceConfigFile(\n projectRoot: string | undefined,\n warn: (message: string) => void,\n): Promise<ResilienceConfig> {\n const file = await readProjectConfigFile(projectRoot, warn);\n const resilience = file?.resilience;\n if (resilience === undefined) {\n return {};\n }\n return parseResilienceLayer(resilience, PROJECT_CONFIG_FILENAME, warn);\n}\n\n/**\n * The `ISSUE_FLOW_RESILIENCE_*` layer.\n *\n * Only the scalar knobs get a variable of their own; the per-kind `retry` table\n * is too shaped for a shell variable, so it travels whole as JSON in\n * `ISSUE_FLOW_RESILIENCE_RETRY`. Everything collected here still goes through\n * the schema afterwards, so a typo warns exactly like a bad config file does.\n */\nfunction readResilienceConfigEnv(\n env: NodeJS.ProcessEnv,\n warn: (message: string) => void,\n): ResilienceConfig {\n const layer: Record<string, unknown> = {};\n\n if (env.ISSUE_FLOW_RESILIENCE_PROFILE !== undefined) {\n layer.profile = env.ISSUE_FLOW_RESILIENCE_PROFILE;\n }\n if (env.ISSUE_FLOW_RESILIENCE_FAILOVER_ON_AUTH !== undefined) {\n layer.failoverOnAuth = parseBooleanEnv(env.ISSUE_FLOW_RESILIENCE_FAILOVER_ON_AUTH);\n }\n\n const retry = env.ISSUE_FLOW_RESILIENCE_RETRY;\n if (retry !== undefined) {\n try {\n layer.retry = JSON.parse(retry);\n } catch {\n warn('Ignoring ISSUE_FLOW_RESILIENCE_RETRY: invalid JSON.');\n }\n }\n\n const providers: Record<string, unknown> = {};\n if (env.ISSUE_FLOW_RESILIENCE_FAILOVER !== undefined) {\n providers.failover = parseBooleanEnv(env.ISSUE_FLOW_RESILIENCE_FAILOVER);\n }\n if (env.ISSUE_FLOW_RESILIENCE_PROVIDER_CHAIN !== undefined) {\n providers.chain = env.ISSUE_FLOW_RESILIENCE_PROVIDER_CHAIN.split(',')\n .map((name) => name.trim())\n .filter((name) => name !== '');\n }\n const cooldown = readNumberEnv(env, 'ISSUE_FLOW_RESILIENCE_PROVIDER_COOLDOWN_MS', warn);\n if (cooldown !== undefined) {\n providers.cooldownMs = cooldown;\n }\n if (Object.keys(providers).length > 0) layer.providers = providers;\n\n const queue: Record<string, unknown> = {};\n if (env.ISSUE_FLOW_RESILIENCE_ON_ISSUE_FAILURE !== undefined) {\n queue.onIssueFailure = env.ISSUE_FLOW_RESILIENCE_ON_ISSUE_FAILURE;\n }\n const maxIssueAttempts = readNumberEnv(env, 'ISSUE_FLOW_RESILIENCE_MAX_ISSUE_ATTEMPTS', warn);\n if (maxIssueAttempts !== undefined) {\n queue.maxIssueAttempts = maxIssueAttempts;\n }\n if (Object.keys(queue).length > 0) layer.queue = queue;\n\n const inactivity = readNumberEnv(env, 'ISSUE_FLOW_RESILIENCE_INACTIVITY_TIMEOUT_MS', warn);\n if (inactivity !== undefined) {\n layer.watchdog = { inactivityTimeoutMs: inactivity };\n }\n\n const journal: Record<string, unknown> = {};\n if (env.ISSUE_FLOW_RESILIENCE_JOURNAL !== undefined) {\n journal.enabled = parseBooleanEnv(env.ISSUE_FLOW_RESILIENCE_JOURNAL);\n }\n const journalBytes = readNumberEnv(env, 'ISSUE_FLOW_RESILIENCE_JOURNAL_MAX_BYTES', warn);\n if (journalBytes !== undefined) {\n journal.maxFileBytes = journalBytes;\n }\n if (Object.keys(journal).length > 0) layer.journal = journal;\n\n if (env.ISSUE_FLOW_RESILIENCE_AUTO_DECOMPOSE !== undefined) {\n layer.decompose = { auto: parseBooleanEnv(env.ISSUE_FLOW_RESILIENCE_AUTO_DECOMPOSE) };\n }\n\n if (Object.keys(layer).length === 0) {\n return {};\n }\n return parseResilienceLayer(layer, 'the ISSUE_FLOW_RESILIENCE_* environment', warn);\n}\n\n/**\n * Resolve the `resilience` key with the documented precedence:\n * CLI flag > `ISSUE_FLOW_RESILIENCE_*` > `.issue-flow.json` > `config.json` >\n * defaults.\n *\n * **An unconfigured project resolves to `{}`** — not to a skeleton of empty\n * sections, and never to a materialized default. That is the whole contract of\n * this loader: `resolvePolicy(kind, {})` is the base table of the PRD, so\n * \"nothing configured\" and \"the behaviour of every release before this one\" are\n * the same object. The defaults of each sub-key belong to the layer that\n * consumes it (`resilience/policy.ts` for `retry`, and one later story each for\n * `providers`, `queue`, `watchdog`, `journal` and `decompose`), never here.\n *\n * Never throws: an absent, malformed or invalid source degrades to \"nothing\n * configured\" with a warning, key by key.\n */\nexport async function loadResilienceConfig(\n options: LoadResilienceConfigOptions = {},\n): Promise<ResilienceConfig> {\n const warn = options.warn ?? printWarning;\n const env = options.env ?? process.env;\n const cli = options.cli ?? resilienceCliOverrides;\n\n const globalLayer =\n options.global ??\n (\n await loadGlobalConfig({\n env,\n ...(options.globalRoot === undefined ? {} : { globalRoot: options.globalRoot }),\n warn,\n })\n ).resilience ??\n {};\n const projectLayer = await readResilienceConfigFile(options.projectRoot, warn);\n const envLayer = readResilienceConfigEnv(env, warn);\n\n const layers = [globalLayer, projectLayer, envLayer, cli] as const;\n\n const scalars = mergeConfigLayers<Pick<ResilienceConfig, 'profile' | 'failoverOnAuth'>>({\n global: { profile: globalLayer.profile, failoverOnAuth: globalLayer.failoverOnAuth },\n project: { profile: projectLayer.profile, failoverOnAuth: projectLayer.failoverOnAuth },\n env: { profile: envLayer.profile, failoverOnAuth: envLayer.failoverOnAuth },\n cli: { profile: cli.profile, failoverOnAuth: cli.failoverOnAuth },\n });\n\n const retry = mergeResilienceRetry(layers.map((layer) => layer.retry));\n const providers = mergeResilienceSection(layers.map((layer) => layer.providers));\n const queue = mergeResilienceSection(layers.map((layer) => layer.queue));\n const watchdog = mergeResilienceSection(layers.map((layer) => layer.watchdog));\n const journal = mergeResilienceSection(layers.map((layer) => layer.journal));\n const decompose = mergeResilienceSection(layers.map((layer) => layer.decompose));\n\n return {\n ...scalars,\n ...(retry === undefined ? {} : { retry }),\n ...(providers === undefined ? {} : { providers }),\n ...(queue === undefined ? {} : { queue }),\n ...(watchdog === undefined ? {} : { watchdog }),\n ...(journal === undefined ? {} : { journal }),\n ...(decompose === undefined ? {} : { decompose }),\n };\n}\n\n/**\n * The `resilience` key in force for this process.\n *\n * Read **synchronously**, by call sites that cannot afford four disk reads per\n * `gh` invocation and must not perform I/O of their own — a provider that\n * shelled out to `git` to find its retry budget would be doing exactly the\n * thing this key exists to make survivable.\n *\n * It starts empty on purpose: `{}` is the base table of `resolvePolicy()`, so\n * a process that never installs anything behaves exactly as it did before the\n * key existed. `initResilienceConfig()` is what fills it in, once, from the\n * command that owns the run.\n */\nlet activeResilienceConfig: ResilienceConfig = {};\n\n/** The key in force. `{}` until a command installs one. */\nexport function getActiveResilienceConfig(): ResilienceConfig {\n return activeResilienceConfig;\n}\n\n/** Install a key directly. For tests, and for a caller that already loaded it. */\nexport function setActiveResilienceConfig(config: ResilienceConfig): void {\n activeResilienceConfig = config;\n}\n\n/**\n * Load the key off the ladder and install it. Never throws: a failed load\n * leaves the base table in place, which is the documented default.\n */\nexport async function initResilienceConfig(\n options: LoadResilienceConfigOptions = {},\n): Promise<ResilienceConfig> {\n try {\n activeResilienceConfig = await loadResilienceConfig(options);\n } catch {\n activeResilienceConfig = {};\n }\n return activeResilienceConfig;\n}\n"],"mappings":"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","names":[]}
|
package/dist/chunk-4JSOYE4G.js
DELETED
|
@@ -1,368 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
readClaudeStream
|
|
4
|
-
} from "./chunk-MJULIKQU.js";
|
|
5
|
-
import {
|
|
6
|
-
createWatchdog,
|
|
7
|
-
describeStall,
|
|
8
|
-
registerChild
|
|
9
|
-
} from "./chunk-M66SJHQQ.js";
|
|
10
|
-
import {
|
|
11
|
-
ElapsedTimer,
|
|
12
|
-
createSpinner,
|
|
13
|
-
formatDuration,
|
|
14
|
-
getIcons,
|
|
15
|
-
getInactivityTimeout,
|
|
16
|
-
getOutputCallback,
|
|
17
|
-
getSessionPublisher,
|
|
18
|
-
isVerbose,
|
|
19
|
-
useColor
|
|
20
|
-
} from "./chunk-BYZDPR7N.js";
|
|
21
|
-
import {
|
|
22
|
-
isoNow,
|
|
23
|
-
parseUsage
|
|
24
|
-
} from "./chunk-CD5ULRVC.js";
|
|
25
|
-
|
|
26
|
-
// src/core/headless.ts
|
|
27
|
-
import { createInterface } from "readline";
|
|
28
|
-
import chalk from "chalk";
|
|
29
|
-
import { execa } from "execa";
|
|
30
|
-
function inactivityOptions() {
|
|
31
|
-
const configured = getInactivityTimeout();
|
|
32
|
-
return configured === void 0 ? {} : { inactivityTimeoutMs: configured };
|
|
33
|
-
}
|
|
34
|
-
var DEFAULT_HEADLESS_TIMEOUT_MS = 9e5;
|
|
35
|
-
var TIMEOUT_ATTRIBUTION_RATIO = 0.9;
|
|
36
|
-
function reachedTimeout(timeoutMs, elapsedMs) {
|
|
37
|
-
return timeoutMs > 0 && elapsedMs >= timeoutMs * TIMEOUT_ATTRIBUTION_RATIO;
|
|
38
|
-
}
|
|
39
|
-
function wasTimedOut(proc, timeoutMs, elapsedMs) {
|
|
40
|
-
if (proc.timedOut === true) return true;
|
|
41
|
-
if (!reachedTimeout(timeoutMs, elapsedMs)) return false;
|
|
42
|
-
return proc.signal === "SIGTERM" || proc.signal === "SIGKILL" || proc.exitCode === 143 || proc.exitCode === 137;
|
|
43
|
-
}
|
|
44
|
-
function describeFailure(proc, diagnostics, timeoutMs, elapsedMs) {
|
|
45
|
-
if (wasTimedOut(proc, timeoutMs, elapsedMs)) {
|
|
46
|
-
return `Headless invocation timed out after ${formatDuration(Math.round(timeoutMs / 1e3))}. Raise the limit with --timeout <seconds> (0 = no limit).`;
|
|
47
|
-
}
|
|
48
|
-
return diagnostics.trim() || `claude exited with code ${proc.exitCode ?? "unknown"}`;
|
|
49
|
-
}
|
|
50
|
-
function pushRepeatedFlag(args, flag, values) {
|
|
51
|
-
if (!values || values.length === 0) return;
|
|
52
|
-
for (const value of values) {
|
|
53
|
-
args.push(flag, value);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
function getToolContext(name, input) {
|
|
57
|
-
switch (name) {
|
|
58
|
-
case "Read":
|
|
59
|
-
return shortPath(input.file_path);
|
|
60
|
-
case "Write":
|
|
61
|
-
case "Edit":
|
|
62
|
-
return shortPath(input.file_path);
|
|
63
|
-
case "Glob":
|
|
64
|
-
return input.pattern ?? "";
|
|
65
|
-
case "Grep":
|
|
66
|
-
return input.pattern ?? "";
|
|
67
|
-
case "Bash": {
|
|
68
|
-
const cmd = input.command ?? "";
|
|
69
|
-
return cmd.length > 60 ? `${cmd.substring(0, 57)}...` : cmd;
|
|
70
|
-
}
|
|
71
|
-
default:
|
|
72
|
-
return "";
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
function shortPath(filePath) {
|
|
76
|
-
if (!filePath) return "";
|
|
77
|
-
const cwd = process.cwd();
|
|
78
|
-
if (filePath.startsWith(cwd)) {
|
|
79
|
-
return filePath.substring(cwd.length + 1);
|
|
80
|
-
}
|
|
81
|
-
const parts = filePath.split("/");
|
|
82
|
-
if (parts.length > 2) {
|
|
83
|
-
return `.../${parts.slice(-2).join("/")}`;
|
|
84
|
-
}
|
|
85
|
-
return filePath;
|
|
86
|
-
}
|
|
87
|
-
function printStreamEvent(line, state, onOutput) {
|
|
88
|
-
let event;
|
|
89
|
-
try {
|
|
90
|
-
event = JSON.parse(line);
|
|
91
|
-
} catch {
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
const icons = getIcons();
|
|
95
|
-
const colored = useColor();
|
|
96
|
-
const emit = onOutput ?? ((msg) => process.stderr.write(`${msg}
|
|
97
|
-
`));
|
|
98
|
-
if (event.type === "assistant" && event.message?.content) {
|
|
99
|
-
state.turnCount++;
|
|
100
|
-
for (const block of event.message.content) {
|
|
101
|
-
if (block.type === "text" && block.text) {
|
|
102
|
-
const lines = block.text.split("\n");
|
|
103
|
-
for (const textLine of lines) {
|
|
104
|
-
if (!textLine.trim()) continue;
|
|
105
|
-
const prefix = colored ? chalk.dim(` ${icons.connector} `) : ` ${icons.connector} `;
|
|
106
|
-
const text = colored ? chalk.dim(textLine) : textLine;
|
|
107
|
-
emit(`${prefix}${text}`);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
if (block.type === "tool_use" && block.name) {
|
|
111
|
-
const context = block.input ? getToolContext(block.name, block.input) : "";
|
|
112
|
-
getSessionPublisher().publish({
|
|
113
|
-
type: "activity",
|
|
114
|
-
at: isoNow(),
|
|
115
|
-
tool: block.name,
|
|
116
|
-
detail: context || void 0
|
|
117
|
-
});
|
|
118
|
-
const toolName = block.name.padEnd(12);
|
|
119
|
-
const prefix = colored ? chalk.dim(` ${icons.connector} `) : ` ${icons.connector} `;
|
|
120
|
-
const toolIcon = colored ? chalk.cyan(icons.tool) : icons.tool;
|
|
121
|
-
const toolLabel = colored ? chalk.cyan(toolName) : toolName;
|
|
122
|
-
const contextText = context ? colored ? chalk.dim(context) : context : "";
|
|
123
|
-
emit(`${prefix}${toolIcon} ${toolLabel} ${contextText}`);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
async function runHeadlessVerbose(options) {
|
|
129
|
-
const { prompt, maxTurns, timeout, allowedTools, addDirs, statusMessage, onOutput } = options;
|
|
130
|
-
const icons = getIcons();
|
|
131
|
-
const colored = useColor();
|
|
132
|
-
const emit = onOutput ?? ((msg) => process.stderr.write(`${msg}
|
|
133
|
-
`));
|
|
134
|
-
if (statusMessage) {
|
|
135
|
-
const msg = colored ? chalk.blue(`${icons.start} ${statusMessage}`) : `${icons.start} ${statusMessage}`;
|
|
136
|
-
emit(msg);
|
|
137
|
-
}
|
|
138
|
-
const connectorLine = colored ? chalk.dim(` ${icons.connector}`) : ` ${icons.connector}`;
|
|
139
|
-
emit(connectorLine);
|
|
140
|
-
const startTime = Date.now();
|
|
141
|
-
const args = [
|
|
142
|
-
"-p",
|
|
143
|
-
prompt,
|
|
144
|
-
"--output-format",
|
|
145
|
-
"stream-json",
|
|
146
|
-
"--verbose",
|
|
147
|
-
"--max-turns",
|
|
148
|
-
String(maxTurns)
|
|
149
|
-
];
|
|
150
|
-
pushRepeatedFlag(args, "--allowedTools", allowedTools);
|
|
151
|
-
pushRepeatedFlag(args, "--add-dir", addDirs);
|
|
152
|
-
const subprocess = execa("claude", args, {
|
|
153
|
-
stdin: "ignore",
|
|
154
|
-
reject: false,
|
|
155
|
-
timeout,
|
|
156
|
-
stripFinalNewline: false
|
|
157
|
-
});
|
|
158
|
-
const unregisterChild = registerChild({
|
|
159
|
-
kill: (signal) => subprocess.kill(signal),
|
|
160
|
-
done: subprocess.then(
|
|
161
|
-
() => void 0,
|
|
162
|
-
() => void 0
|
|
163
|
-
)
|
|
164
|
-
});
|
|
165
|
-
let resultText = "";
|
|
166
|
-
let isError = false;
|
|
167
|
-
let costData = null;
|
|
168
|
-
const state = { turnCount: 0 };
|
|
169
|
-
if (subprocess.stdout) {
|
|
170
|
-
const rl = createInterface({ input: subprocess.stdout });
|
|
171
|
-
for await (const line of rl) {
|
|
172
|
-
printStreamEvent(line, state, onOutput);
|
|
173
|
-
try {
|
|
174
|
-
const event = JSON.parse(line);
|
|
175
|
-
if (event.type === "result") {
|
|
176
|
-
resultText = event.result ?? "";
|
|
177
|
-
isError = event.is_error === true;
|
|
178
|
-
costData = parseUsage(event) ?? costData;
|
|
179
|
-
}
|
|
180
|
-
} catch {
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
const proc = await subprocess;
|
|
185
|
-
unregisterChild();
|
|
186
|
-
const elapsedSec = Math.floor((Date.now() - startTime) / 1e3);
|
|
187
|
-
const durationStr = formatDuration(elapsedSec);
|
|
188
|
-
const doneLine = colored ? chalk.dim(` ${icons.connector} ${chalk.italic(`Done in ${durationStr}`)}`) : ` ${icons.connector} Done in ${durationStr}`;
|
|
189
|
-
emit(doneLine);
|
|
190
|
-
emit(connectorLine);
|
|
191
|
-
if (proc.exitCode !== 0 && !resultText) {
|
|
192
|
-
return {
|
|
193
|
-
success: false,
|
|
194
|
-
result: "",
|
|
195
|
-
cost: null,
|
|
196
|
-
error: describeFailure(proc, proc.stderr?.toString() ?? "", timeout, Date.now() - startTime)
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
return {
|
|
200
|
-
success: !isError,
|
|
201
|
-
result: resultText,
|
|
202
|
-
cost: costData,
|
|
203
|
-
error: isError ? resultText : null
|
|
204
|
-
};
|
|
205
|
-
}
|
|
206
|
-
async function runHeadless(options) {
|
|
207
|
-
const {
|
|
208
|
-
prompt,
|
|
209
|
-
maxTurns = 10,
|
|
210
|
-
timeout = DEFAULT_HEADLESS_TIMEOUT_MS,
|
|
211
|
-
outputFormat = "json",
|
|
212
|
-
allowedTools,
|
|
213
|
-
addDirs,
|
|
214
|
-
statusMessage,
|
|
215
|
-
onOutput
|
|
216
|
-
} = options;
|
|
217
|
-
if (isVerbose()) {
|
|
218
|
-
const effectiveOnOutput = onOutput ?? getOutputCallback();
|
|
219
|
-
return runHeadlessVerbose({
|
|
220
|
-
prompt,
|
|
221
|
-
maxTurns,
|
|
222
|
-
timeout,
|
|
223
|
-
allowedTools,
|
|
224
|
-
addDirs,
|
|
225
|
-
statusMessage,
|
|
226
|
-
onOutput: effectiveOnOutput
|
|
227
|
-
});
|
|
228
|
-
}
|
|
229
|
-
const startTime = Date.now();
|
|
230
|
-
const spinner = statusMessage ? createSpinner(statusMessage).start() : null;
|
|
231
|
-
let timer = null;
|
|
232
|
-
if (spinner) {
|
|
233
|
-
timer = new ElapsedTimer((elapsed) => {
|
|
234
|
-
spinner.suffixText = useColor() ? chalk.dim(`(${elapsed})`) : `(${elapsed})`;
|
|
235
|
-
}).start();
|
|
236
|
-
}
|
|
237
|
-
const args = [
|
|
238
|
-
"-p",
|
|
239
|
-
prompt,
|
|
240
|
-
"--output-format",
|
|
241
|
-
"stream-json",
|
|
242
|
-
"--verbose",
|
|
243
|
-
"--max-turns",
|
|
244
|
-
String(maxTurns)
|
|
245
|
-
];
|
|
246
|
-
pushRepeatedFlag(args, "--allowedTools", allowedTools);
|
|
247
|
-
pushRepeatedFlag(args, "--add-dir", addDirs);
|
|
248
|
-
const subprocess = execa("claude", args, {
|
|
249
|
-
stdin: "ignore",
|
|
250
|
-
reject: false,
|
|
251
|
-
timeout,
|
|
252
|
-
stripFinalNewline: false
|
|
253
|
-
});
|
|
254
|
-
const unregisterChild = registerChild({
|
|
255
|
-
kill: (signal) => subprocess.kill(signal),
|
|
256
|
-
done: subprocess.then(
|
|
257
|
-
() => void 0,
|
|
258
|
-
() => void 0
|
|
259
|
-
)
|
|
260
|
-
});
|
|
261
|
-
const watchdog = createWatchdog({
|
|
262
|
-
...inactivityOptions(),
|
|
263
|
-
child: {
|
|
264
|
-
kill: (signal) => subprocess.kill(signal),
|
|
265
|
-
done: subprocess.then(
|
|
266
|
-
() => void 0,
|
|
267
|
-
() => void 0
|
|
268
|
-
)
|
|
269
|
-
}
|
|
270
|
-
});
|
|
271
|
-
try {
|
|
272
|
-
const streamed = subprocess.stdout ? await readClaudeStream(subprocess.stdout, { onLine: () => watchdog.beat() }) : { result: "", isError: false, usage: null, events: 0, raw: "" };
|
|
273
|
-
const proc = await subprocess;
|
|
274
|
-
watchdog.stop();
|
|
275
|
-
unregisterChild();
|
|
276
|
-
if (watchdog.stalled) {
|
|
277
|
-
const elapsed2 = timer?.stop() ?? 0;
|
|
278
|
-
const dur2 = useColor() ? chalk.dim(` (${formatDuration(elapsed2)})`) : ` (${formatDuration(elapsed2)})`;
|
|
279
|
-
spinner?.fail(`${statusMessage}${dur2}`);
|
|
280
|
-
return {
|
|
281
|
-
success: false,
|
|
282
|
-
result: "",
|
|
283
|
-
cost: null,
|
|
284
|
-
error: describeStall(watchdog.silentMs)
|
|
285
|
-
};
|
|
286
|
-
}
|
|
287
|
-
const stdout = streamed.raw === "" ? proc.stdout?.toString() ?? "" : streamed.raw;
|
|
288
|
-
const stderr = proc.stderr?.toString() ?? "";
|
|
289
|
-
if (proc.exitCode !== 0) {
|
|
290
|
-
const elapsed2 = timer?.stop() ?? 0;
|
|
291
|
-
const dur2 = useColor() ? chalk.dim(` (${formatDuration(elapsed2)})`) : ` (${formatDuration(elapsed2)})`;
|
|
292
|
-
spinner?.fail(`${statusMessage}${dur2}`);
|
|
293
|
-
return {
|
|
294
|
-
success: false,
|
|
295
|
-
result: "",
|
|
296
|
-
cost: null,
|
|
297
|
-
error: describeFailure(proc, stderr || stdout, timeout, Date.now() - startTime)
|
|
298
|
-
};
|
|
299
|
-
}
|
|
300
|
-
const elapsed = timer?.stop() ?? 0;
|
|
301
|
-
const dur = useColor() ? chalk.dim(` (${formatDuration(elapsed)})`) : ` (${formatDuration(elapsed)})`;
|
|
302
|
-
spinner?.succeed(`${statusMessage}${dur}`);
|
|
303
|
-
if (outputFormat === "json") {
|
|
304
|
-
if (streamed.result !== "") {
|
|
305
|
-
return {
|
|
306
|
-
success: !streamed.isError,
|
|
307
|
-
result: streamed.result,
|
|
308
|
-
cost: streamed.usage,
|
|
309
|
-
error: streamed.isError ? streamed.result : null
|
|
310
|
-
};
|
|
311
|
-
}
|
|
312
|
-
try {
|
|
313
|
-
const parsed = JSON.parse(stdout);
|
|
314
|
-
return {
|
|
315
|
-
success: true,
|
|
316
|
-
result: parsed.result ?? stdout,
|
|
317
|
-
cost: parseUsage(parsed),
|
|
318
|
-
error: null
|
|
319
|
-
};
|
|
320
|
-
} catch {
|
|
321
|
-
return {
|
|
322
|
-
success: true,
|
|
323
|
-
result: stdout,
|
|
324
|
-
cost: null,
|
|
325
|
-
error: null
|
|
326
|
-
};
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
return {
|
|
330
|
-
success: true,
|
|
331
|
-
result: stdout,
|
|
332
|
-
cost: null,
|
|
333
|
-
error: null
|
|
334
|
-
};
|
|
335
|
-
} catch (err) {
|
|
336
|
-
unregisterChild();
|
|
337
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
338
|
-
const catchElapsed = timer?.stop() ?? 0;
|
|
339
|
-
const catchDur = useColor() ? chalk.dim(` (${formatDuration(catchElapsed)})`) : ` (${formatDuration(catchElapsed)})`;
|
|
340
|
-
spinner?.fail(`${statusMessage}${catchDur}`);
|
|
341
|
-
if (message.includes("timed out") || message.includes("ETIMEDOUT")) {
|
|
342
|
-
const elapsedMs = Date.now() - startTime;
|
|
343
|
-
return {
|
|
344
|
-
success: false,
|
|
345
|
-
result: "",
|
|
346
|
-
cost: null,
|
|
347
|
-
error: describeFailure(
|
|
348
|
-
{ timedOut: reachedTimeout(timeout, elapsedMs) },
|
|
349
|
-
message,
|
|
350
|
-
timeout,
|
|
351
|
-
elapsedMs
|
|
352
|
-
)
|
|
353
|
-
};
|
|
354
|
-
}
|
|
355
|
-
return {
|
|
356
|
-
success: false,
|
|
357
|
-
result: "",
|
|
358
|
-
cost: null,
|
|
359
|
-
error: message
|
|
360
|
-
};
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
export {
|
|
365
|
-
DEFAULT_HEADLESS_TIMEOUT_MS,
|
|
366
|
-
runHeadless
|
|
367
|
-
};
|
|
368
|
-
//# sourceMappingURL=chunk-4JSOYE4G.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/headless.ts"],"sourcesContent":["import { createInterface } from 'node:readline';\nimport chalk from 'chalk';\nimport { execa } from 'execa';\nimport { createSpinner, ElapsedTimer, formatDuration, getIcons, useColor } from '../ui/logger.js';\nimport { type ClaudeUsage, parseUsage } from './metrics.js';\nimport { getSessionPublisher } from './session-publisher.js';\nimport { registerChild } from './shutdown.js';\nimport { isoNow } from './state-manager.js';\nimport { readClaudeStream } from './stream.js';\nimport { getInactivityTimeout, getOutputCallback, isVerbose } from './verbose.js';\nimport { createWatchdog, describeStall } from './watchdog.js';\n\n/** The watchdog budget of one invocation, from the process-wide setting. */\nfunction inactivityOptions(): { inactivityTimeoutMs?: number } {\n const configured = getInactivityTimeout();\n return configured === undefined ? {} : { inactivityTimeoutMs: configured };\n}\n\nexport interface HeadlessOptions {\n prompt: string;\n maxTurns?: number;\n timeout?: number;\n outputFormat?: 'json' | 'text' | 'stream-json';\n allowedTools?: string[];\n /**\n * Extra directories the headless session may read from and write to, passed\n * through as `--add-dir`. Required whenever a prompt points at a path outside\n * the working tree (e.g. the global issue storage under `~/.issue-flow`),\n * since `claude -p` otherwise refuses the access.\n */\n addDirs?: string[];\n /** Status message displayed as spinner (non-verbose) or header (verbose). */\n statusMessage?: string;\n /** Optional callback for routing verbose output (e.g., through listr2 task.output). When provided, verbose stream events are sent here instead of directly to stderr. */\n onOutput?: (line: string) => void;\n}\n\n/**\n * Token/cost metrics of a headless invocation.\n *\n * Alias of {@link ClaudeUsage} — the name is kept for the existing call sites,\n * but the shape (and the parsing behind it) now lives in core/metrics.ts, so\n * cache tokens and USD cost come along for free.\n */\nexport type HeadlessCost = ClaudeUsage;\n\nexport interface HeadlessResult {\n success: boolean;\n result: string;\n cost: HeadlessCost | null;\n error: string | null;\n}\n\n/**\n * Default wall-clock limit for a single headless invocation.\n *\n * Every phase that invokes `claude` once (analyze, prd, plan, review, pr,\n * pr-review, generate) shares it, so the limit is raised in one place. It is\n * deliberately generous: the phases read the repository before writing their\n * artifact, and on a large issue that alone outlives a few minutes. The execute\n * loop is the exception and runs with no limit at all (`core/executor.ts`),\n * because its iteration budget is what bounds it.\n */\nexport const DEFAULT_HEADLESS_TIMEOUT_MS = 900_000;\n\n/**\n * The subset of an execa result this module inspects. Declared structurally so\n * both the awaited `execa()` result and the awaited subprocess of the verbose\n * path fit it without a cast to `any`.\n */\ninterface FinishedProcess {\n exitCode?: number | undefined;\n signal?: string | undefined;\n timedOut?: boolean | undefined;\n}\n\n/**\n * How much of the limit an invocation has to have burned through before an\n * unlabelled kill is attributed to the timeout rather than to something\n * external. A limit of 0 means no limit, and nothing can be attributed to it.\n */\nconst TIMEOUT_ATTRIBUTION_RATIO = 0.9;\n\nfunction reachedTimeout(timeoutMs: number, elapsedMs: number): boolean {\n return timeoutMs > 0 && elapsedMs >= timeoutMs * TIMEOUT_ATTRIBUTION_RATIO;\n}\n\n/**\n * Whether a finished invocation was killed by the `timeout` option.\n *\n * `reject: false` means execa never throws on a timeout — it resolves with a\n * result carrying `timedOut: true`, which is why the `catch` block below never\n * sees one. That flag is the authoritative signal; the rest is a fallback for\n * the case execa cannot label, a CLI that installs its own SIGTERM handler and\n * exits by itself. `claude` does exactly that and leaves 143 (128 + SIGTERM),\n * with no signal for execa to report. The elapsed-time guard keeps that\n * fallback from mislabelling an unrelated external kill as a timeout.\n */\nfunction wasTimedOut(proc: FinishedProcess, timeoutMs: number, elapsedMs: number): boolean {\n if (proc.timedOut === true) return true;\n if (!reachedTimeout(timeoutMs, elapsedMs)) return false;\n return (\n proc.signal === 'SIGTERM' ||\n proc.signal === 'SIGKILL' ||\n proc.exitCode === 143 ||\n proc.exitCode === 137\n );\n}\n\n/**\n * The error text of a failed invocation.\n *\n * A timeout gets a message of its own, and it has to keep saying \"timed out\":\n * `resilience/errors.ts` falls back to matching that text when no structured\n * evidence survived, and it is what earns the phase its retries in\n * `core/phase-runner.ts`. Reporting a timeout as a bare `claude exited with\n * code 143` — which is what the CLI leaves behind when it handles the SIGTERM\n * itself — both hid the cause and cost the phase every retry it had.\n */\nfunction describeFailure(\n proc: FinishedProcess,\n diagnostics: string,\n timeoutMs: number,\n elapsedMs: number,\n): string {\n if (wasTimedOut(proc, timeoutMs, elapsedMs)) {\n return `Headless invocation timed out after ${formatDuration(Math.round(timeoutMs / 1000))}. Raise the limit with --timeout <seconds> (0 = no limit).`;\n }\n return diagnostics.trim() || `claude exited with code ${proc.exitCode ?? 'unknown'}`;\n}\n\n/* ── argument helpers ───────────────────────────────────────────────────── */\n\n/**\n * Append one `<flag> <value>` pair per value. An empty or absent list leaves\n * `args` untouched, so callers that pass nothing keep the exact same argv.\n */\nfunction pushRepeatedFlag(args: string[], flag: string, values: string[] | undefined): void {\n if (!values || values.length === 0) return;\n for (const value of values) {\n args.push(flag, value);\n }\n}\n\n/* ── verbose stream formatting ──────────────────────────────────────────── */\n\n/**\n * Extract a short context string from a tool_use input object.\n */\nfunction getToolContext(name: string, input: Record<string, unknown>): string {\n switch (name) {\n case 'Read':\n return shortPath(input.file_path as string);\n case 'Write':\n case 'Edit':\n return shortPath(input.file_path as string);\n case 'Glob':\n return (input.pattern as string) ?? '';\n case 'Grep':\n return (input.pattern as string) ?? '';\n case 'Bash': {\n const cmd = (input.command as string) ?? '';\n return cmd.length > 60 ? `${cmd.substring(0, 57)}...` : cmd;\n }\n default:\n return '';\n }\n}\n\n/**\n * Shorten a file path to be relative-friendly.\n */\nfunction shortPath(filePath: string | undefined): string {\n if (!filePath) return '';\n const cwd = process.cwd();\n if (filePath.startsWith(cwd)) {\n return filePath.substring(cwd.length + 1);\n }\n // Show last 2 segments for absolute paths\n const parts = filePath.split('/');\n if (parts.length > 2) {\n return `.../${parts.slice(-2).join('/')}`;\n }\n return filePath;\n}\n\n/**\n * Print a formatted stream event line to stderr.\n */\nfunction printStreamEvent(\n line: string,\n state: { turnCount: number },\n onOutput?: (line: string) => void,\n): void {\n let event: {\n type?: string;\n subtype?: string;\n message?: {\n content?: { type: string; text?: string; name?: string; input?: Record<string, unknown> }[];\n };\n };\n\n try {\n event = JSON.parse(line);\n } catch {\n return;\n }\n\n const icons = getIcons();\n const colored = useColor();\n const emit = onOutput ?? ((msg: string) => process.stderr.write(`${msg}\\n`));\n\n if (event.type === 'assistant' && event.message?.content) {\n state.turnCount++;\n\n for (const block of event.message.content) {\n if (block.type === 'text' && block.text) {\n // Wrap text with connector prefix\n const lines = block.text.split('\\n');\n for (const textLine of lines) {\n if (!textLine.trim()) continue;\n const prefix = colored ? chalk.dim(` ${icons.connector} `) : ` ${icons.connector} `;\n const text = colored ? chalk.dim(textLine) : textLine;\n emit(`${prefix}${text}`);\n }\n }\n\n if (block.type === 'tool_use' && block.name) {\n const context = block.input ? getToolContext(block.name, block.input) : '';\n getSessionPublisher().publish({\n type: 'activity',\n at: isoNow(),\n tool: block.name,\n detail: context || undefined,\n });\n const toolName = block.name.padEnd(12);\n\n const prefix = colored ? chalk.dim(` ${icons.connector} `) : ` ${icons.connector} `;\n const toolIcon = colored ? chalk.cyan(icons.tool) : icons.tool;\n const toolLabel = colored ? chalk.cyan(toolName) : toolName;\n const contextText = context ? (colored ? chalk.dim(context) : context) : '';\n\n emit(`${prefix}${toolIcon} ${toolLabel} ${contextText}`);\n }\n }\n }\n}\n\n/* ── verbose execution ──────────────────────────────────────────────────── */\n\n/**\n * Run headless in verbose mode using stream-json to display real-time progress.\n */\nasync function runHeadlessVerbose(options: {\n prompt: string;\n maxTurns: number;\n timeout: number;\n allowedTools?: string[];\n addDirs?: string[];\n statusMessage?: string;\n onOutput?: (line: string) => void;\n}): Promise<HeadlessResult> {\n const { prompt, maxTurns, timeout, allowedTools, addDirs, statusMessage, onOutput } = options;\n const icons = getIcons();\n const colored = useColor();\n const emit = onOutput ?? ((msg: string) => process.stderr.write(`${msg}\\n`));\n\n // Print header\n if (statusMessage) {\n const msg = colored\n ? chalk.blue(`${icons.start} ${statusMessage}`)\n : `${icons.start} ${statusMessage}`;\n emit(msg);\n }\n\n // Print opening connector\n const connectorLine = colored ? chalk.dim(` ${icons.connector}`) : ` ${icons.connector}`;\n emit(connectorLine);\n\n const startTime = Date.now();\n\n const args: string[] = [\n '-p',\n prompt,\n '--output-format',\n 'stream-json',\n '--verbose',\n '--max-turns',\n String(maxTurns),\n ];\n\n pushRepeatedFlag(args, '--allowedTools', allowedTools);\n pushRepeatedFlag(args, '--add-dir', addDirs);\n\n const subprocess = execa('claude', args, {\n stdin: 'ignore',\n reject: false,\n timeout,\n stripFinalNewline: false,\n });\n // A `Ctrl+C` must reach the agent, not just this process: without this the\n // child is orphaned and keeps writing to the repository after the pipeline\n // that started it is gone.\n const unregisterChild = registerChild({\n kill: (signal) => subprocess.kill(signal),\n done: subprocess.then(\n () => undefined,\n () => undefined,\n ),\n });\n\n let resultText = '';\n let isError = false;\n let costData: HeadlessCost | null = null;\n const state = { turnCount: 0 };\n\n if (subprocess.stdout) {\n const rl = createInterface({ input: subprocess.stdout });\n for await (const line of rl) {\n printStreamEvent(line, state, onOutput);\n\n try {\n const event = JSON.parse(line);\n if (event.type === 'result') {\n resultText = event.result ?? '';\n isError = event.is_error === true;\n // Keep the previous metrics when this event carries none, so a\n // malformed trailing result never erases what was already captured.\n costData = parseUsage(event) ?? costData;\n }\n } catch {\n // ignore malformed lines\n }\n }\n }\n\n // Wait for the process to finish\n const proc = await subprocess;\n unregisterChild();\n\n // Close connector with elapsed time\n const elapsedSec = Math.floor((Date.now() - startTime) / 1000);\n const durationStr = formatDuration(elapsedSec);\n const doneLine = colored\n ? chalk.dim(` ${icons.connector} ${chalk.italic(`Done in ${durationStr}`)}`)\n : ` ${icons.connector} Done in ${durationStr}`;\n emit(doneLine);\n emit(connectorLine);\n\n if (proc.exitCode !== 0 && !resultText) {\n return {\n success: false,\n result: '',\n cost: null,\n error: describeFailure(proc, proc.stderr?.toString() ?? '', timeout, Date.now() - startTime),\n };\n }\n\n return {\n success: !isError,\n result: resultText,\n cost: costData,\n error: isError ? resultText : null,\n };\n}\n\n/* ── standard execution ─────────────────────────────────────────────────── */\n\n/**\n * Invoke Claude Code in headless mode via `claude -p`.\n *\n * Each invocation is an isolated session — no context is shared between calls.\n * Output is parsed as JSON when outputFormat is 'json' (default).\n *\n * When verbose mode is active, uses stream-json to display real-time progress.\n * Otherwise, shows a spinner while waiting.\n */\nexport async function runHeadless(options: HeadlessOptions): Promise<HeadlessResult> {\n const {\n prompt,\n maxTurns = 10,\n timeout = DEFAULT_HEADLESS_TIMEOUT_MS,\n outputFormat = 'json',\n allowedTools,\n addDirs,\n statusMessage,\n onOutput,\n } = options;\n\n if (isVerbose()) {\n // Use explicit onOutput, fall back to global output callback, or default to stderr\n const effectiveOnOutput = onOutput ?? getOutputCallback();\n return runHeadlessVerbose({\n prompt,\n maxTurns,\n timeout,\n allowedTools,\n addDirs,\n statusMessage,\n onOutput: effectiveOnOutput,\n });\n }\n\n // Non-verbose: use spinner with elapsed timer\n const startTime = Date.now();\n const spinner = statusMessage ? createSpinner(statusMessage).start() : null;\n\n let timer: ElapsedTimer | null = null;\n if (spinner) {\n timer = new ElapsedTimer((elapsed) => {\n spinner.suffixText = useColor() ? chalk.dim(`(${elapsed})`) : `(${elapsed})`;\n }).start();\n }\n\n // The stream is always what is asked for, whatever the caller requested as\n // `outputFormat`: a single envelope arrives in one write at the very end, so\n // an invocation that hangs is indistinguishable from one that is thinking.\n // The rendering is what differs — here it feeds a spinner and a watchdog\n // heartbeat instead of being printed line by line.\n const args: string[] = [\n '-p',\n prompt,\n '--output-format',\n 'stream-json',\n '--verbose',\n '--max-turns',\n String(maxTurns),\n ];\n\n pushRepeatedFlag(args, '--allowedTools', allowedTools);\n pushRepeatedFlag(args, '--add-dir', addDirs);\n\n const subprocess = execa('claude', args, {\n stdin: 'ignore',\n reject: false,\n timeout,\n stripFinalNewline: false,\n });\n const unregisterChild = registerChild({\n kill: (signal) => subprocess.kill(signal),\n done: subprocess.then(\n () => undefined,\n () => undefined,\n ),\n });\n const watchdog = createWatchdog({\n ...inactivityOptions(),\n child: {\n kill: (signal) => subprocess.kill(signal),\n done: subprocess.then(\n () => undefined,\n () => undefined,\n ),\n },\n });\n\n try {\n const streamed = subprocess.stdout\n ? await readClaudeStream(subprocess.stdout, { onLine: () => watchdog.beat() })\n : { result: '', isError: false, usage: null, events: 0, raw: '' };\n\n const proc = await subprocess;\n watchdog.stop();\n unregisterChild();\n\n if (watchdog.stalled) {\n const elapsed = timer?.stop() ?? 0;\n const dur = useColor()\n ? chalk.dim(` (${formatDuration(elapsed)})`)\n : ` (${formatDuration(elapsed)})`;\n spinner?.fail(`${statusMessage}${dur}`);\n return {\n success: false,\n result: '',\n cost: null,\n error: describeStall(watchdog.silentMs),\n };\n }\n\n // The stream is consumed by the time the process finishes, so what it\n // printed lives here rather than in `proc.stdout`.\n const stdout = streamed.raw === '' ? (proc.stdout?.toString() ?? '') : streamed.raw;\n const stderr = proc.stderr?.toString() ?? '';\n\n if (proc.exitCode !== 0) {\n const elapsed = timer?.stop() ?? 0;\n const dur = useColor()\n ? chalk.dim(` (${formatDuration(elapsed)})`)\n : ` (${formatDuration(elapsed)})`;\n spinner?.fail(`${statusMessage}${dur}`);\n return {\n success: false,\n result: '',\n cost: null,\n error: describeFailure(proc, stderr || stdout, timeout, Date.now() - startTime),\n };\n }\n\n const elapsed = timer?.stop() ?? 0;\n const dur = useColor()\n ? chalk.dim(` (${formatDuration(elapsed)})`)\n : ` (${formatDuration(elapsed)})`;\n spinner?.succeed(`${statusMessage}${dur}`);\n\n if (outputFormat === 'json') {\n if (streamed.result !== '') {\n return {\n success: !streamed.isError,\n result: streamed.result,\n cost: streamed.usage,\n error: streamed.isError ? streamed.result : null,\n };\n }\n try {\n const parsed = JSON.parse(stdout);\n return {\n success: true,\n result: parsed.result ?? stdout,\n cost: parseUsage(parsed),\n error: null,\n };\n } catch {\n return {\n success: true,\n result: stdout,\n cost: null,\n error: null,\n };\n }\n }\n\n return {\n success: true,\n result: stdout,\n cost: null,\n error: null,\n };\n } catch (err) {\n unregisterChild();\n const message = err instanceof Error ? err.message : String(err);\n const catchElapsed = timer?.stop() ?? 0;\n const catchDur = useColor()\n ? chalk.dim(` (${formatDuration(catchElapsed)})`)\n : ` (${formatDuration(catchElapsed)})`;\n spinner?.fail(`${statusMessage}${catchDur}`);\n\n if (message.includes('timed out') || message.includes('ETIMEDOUT')) {\n // There is no finished process to inspect here, so the clock is the only\n // evidence. Claim our own limit only when the invocation actually ran up\n // against it: an ETIMEDOUT raised minutes earlier — or with no limit set\n // at all — keeps its own message, which is the honest diagnosis and\n // already carries the words isTransientFailure() matches on.\n const elapsedMs = Date.now() - startTime;\n return {\n success: false,\n result: '',\n cost: null,\n error: describeFailure(\n { timedOut: reachedTimeout(timeout, elapsedMs) },\n message,\n timeout,\n elapsedMs,\n ),\n };\n }\n\n return {\n success: false,\n result: '',\n cost: null,\n error: message,\n };\n }\n}\n"],"mappings":"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","names":["elapsed","dur"]}
|