issue-flow 0.9.0 → 0.10.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.
Files changed (68) hide show
  1. package/dist/{analyze-VSWAEVN3.js → analyze-UZYJAWAP.js} +6 -6
  2. package/dist/{chunk-66ZGYXTH.js → chunk-2LNQL4XH.js} +7 -7
  3. package/dist/{chunk-X7XBF5TS.js → chunk-2Z7OEZZT.js} +6 -6
  4. package/dist/{chunk-VNKSMWDN.js → chunk-652DJBCN.js} +3 -3
  5. package/dist/{chunk-DP43WJY4.js → chunk-67ZO5ROQ.js} +5 -5
  6. package/dist/{chunk-TLSIK656.js → chunk-EWA3OGCM.js} +6 -6
  7. package/dist/{chunk-M72ZEPWD.js → chunk-EZMOEBLU.js} +2 -2
  8. package/dist/{chunk-JGKPSWOA.js → chunk-MXTGCD5C.js} +2 -2
  9. package/dist/{chunk-DTNM3LTO.js → chunk-PPLIXZI5.js} +2 -2
  10. package/dist/{chunk-KXAG24DD.js → chunk-S67Q4I2O.js} +3 -3
  11. package/dist/{chunk-TJMZWRS6.js → chunk-SP7DD4ID.js} +7 -7
  12. package/dist/{chunk-WHEU55ZP.js → chunk-UH4KAJBJ.js} +7 -7
  13. package/dist/{chunk-NB6WKTBL.js → chunk-UUY4PCWW.js} +2 -2
  14. package/dist/{chunk-57MH6RYJ.js → chunk-V5OHFSW4.js} +229 -181
  15. package/dist/chunk-V5OHFSW4.js.map +1 -0
  16. package/dist/{chunk-M37YZYDO.js → chunk-VOKNTBOO.js} +3 -3
  17. package/dist/{chunk-MTJQOOMG.js → chunk-VYFKOUUR.js} +119 -4
  18. package/dist/chunk-VYFKOUUR.js.map +1 -0
  19. package/dist/cli.js +22 -15
  20. package/dist/cli.js.map +1 -1
  21. package/dist/{config-J5DW6HSY.js → config-YCF3HIKR.js} +7 -3
  22. package/dist/execute-QYXA225W.js +14 -0
  23. package/dist/{generate-ERDPOTKX.js → generate-TW6QKSW5.js} +6 -6
  24. package/dist/init-G57UXKDG.js +9 -0
  25. package/dist/plan-YJLGBYBQ.js +16 -0
  26. package/dist/policy-NP4ZAVF6.js +1013 -0
  27. package/dist/policy-NP4ZAVF6.js.map +1 -0
  28. package/dist/pr-V5H65MRW.js +20 -0
  29. package/dist/pr-review-RCMXKXVP.js +16 -0
  30. package/dist/prd-3XR5KYJS.js +16 -0
  31. package/dist/review-ZZG3CSUO.js +14 -0
  32. package/dist/{run-BRBFHVUO.js → run-DIBC3YKX.js} +16 -16
  33. package/dist/{web-WQISLO66.js → web-O2FVOYZW.js} +4 -4
  34. package/package.json +1 -1
  35. package/dist/chunk-57MH6RYJ.js.map +0 -1
  36. package/dist/chunk-MTJQOOMG.js.map +0 -1
  37. package/dist/execute-Z2DKGVLR.js +0 -14
  38. package/dist/init-UY6ZCIFT.js +0 -9
  39. package/dist/plan-REMAMCX4.js +0 -16
  40. package/dist/pr-KERP52TI.js +0 -20
  41. package/dist/pr-review-IU4N3TEK.js +0 -16
  42. package/dist/prd-SHVQW6RM.js +0 -16
  43. package/dist/review-L5F77QAC.js +0 -14
  44. /package/dist/{analyze-VSWAEVN3.js.map → analyze-UZYJAWAP.js.map} +0 -0
  45. /package/dist/{chunk-66ZGYXTH.js.map → chunk-2LNQL4XH.js.map} +0 -0
  46. /package/dist/{chunk-X7XBF5TS.js.map → chunk-2Z7OEZZT.js.map} +0 -0
  47. /package/dist/{chunk-VNKSMWDN.js.map → chunk-652DJBCN.js.map} +0 -0
  48. /package/dist/{chunk-DP43WJY4.js.map → chunk-67ZO5ROQ.js.map} +0 -0
  49. /package/dist/{chunk-TLSIK656.js.map → chunk-EWA3OGCM.js.map} +0 -0
  50. /package/dist/{chunk-M72ZEPWD.js.map → chunk-EZMOEBLU.js.map} +0 -0
  51. /package/dist/{chunk-JGKPSWOA.js.map → chunk-MXTGCD5C.js.map} +0 -0
  52. /package/dist/{chunk-DTNM3LTO.js.map → chunk-PPLIXZI5.js.map} +0 -0
  53. /package/dist/{chunk-KXAG24DD.js.map → chunk-S67Q4I2O.js.map} +0 -0
  54. /package/dist/{chunk-TJMZWRS6.js.map → chunk-SP7DD4ID.js.map} +0 -0
  55. /package/dist/{chunk-WHEU55ZP.js.map → chunk-UH4KAJBJ.js.map} +0 -0
  56. /package/dist/{chunk-NB6WKTBL.js.map → chunk-UUY4PCWW.js.map} +0 -0
  57. /package/dist/{chunk-M37YZYDO.js.map → chunk-VOKNTBOO.js.map} +0 -0
  58. /package/dist/{config-J5DW6HSY.js.map → config-YCF3HIKR.js.map} +0 -0
  59. /package/dist/{execute-Z2DKGVLR.js.map → execute-QYXA225W.js.map} +0 -0
  60. /package/dist/{generate-ERDPOTKX.js.map → generate-TW6QKSW5.js.map} +0 -0
  61. /package/dist/{init-UY6ZCIFT.js.map → init-G57UXKDG.js.map} +0 -0
  62. /package/dist/{plan-REMAMCX4.js.map → plan-YJLGBYBQ.js.map} +0 -0
  63. /package/dist/{pr-KERP52TI.js.map → pr-V5H65MRW.js.map} +0 -0
  64. /package/dist/{pr-review-IU4N3TEK.js.map → pr-review-RCMXKXVP.js.map} +0 -0
  65. /package/dist/{prd-SHVQW6RM.js.map → prd-3XR5KYJS.js.map} +0 -0
  66. /package/dist/{review-L5F77QAC.js.map → review-ZZG3CSUO.js.map} +0 -0
  67. /package/dist/{run-BRBFHVUO.js.map → run-DIBC3YKX.js.map} +0 -0
  68. /package/dist/{web-WQISLO66.js.map → web-O2FVOYZW.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/config.ts","../src/storage/paths.ts","../src/utils/shell.ts","../src/utils/git.ts","../src/storage/resolve.ts","../src/storage/compat.ts","../src/storage/schemas.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 {\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 { type GlobalConfig, globalConfigSchema } 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\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 }>({\n project: fileLayer.enabled === undefined ? {} : { enabled: fileLayer.enabled },\n env: envLayer.enabled === undefined ? {} : { enabled: envLayer.enabled },\n cli: cli.enabled === undefined ? {} : { enabled: cli.enabled },\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","import { createHash } from 'node:crypto';\nimport { homedir } from 'node:os';\nimport { basename, join, resolve } from 'node:path';\nimport { getRemoteUrl, normalizeRemoteUrl } from '../utils/git.js';\n\n/** Directory created under the user's home when no override is provided. */\nexport const GLOBAL_DIR_NAME = '.issue-flow';\n\n/** Environment variable that relocates the whole global storage tree. */\nexport const GLOBAL_ROOT_ENV = 'ISSUE_FLOW_HOME';\n\n/** Directory under the global root holding one folder per project. */\nexport const PROJECTS_DIR_NAME = 'projects';\n\n/** Directory under a project holding one folder per issue. */\nexport const ISSUES_DIR_NAME = 'issues';\n\n/** Directory under a project holding one folder per multi-issue execution queue. */\nexport const QUEUES_DIR_NAME = 'queues';\n\nexport interface GetGlobalRootOptions {\n /** Environment source. Defaults to process.env. */\n env?: NodeJS.ProcessEnv;\n}\n\n/**\n * Resolve the root directory of the global storage tree (`~/.issue-flow`).\n *\n * Every path under the global storage MUST be derived from this function: it is\n * the single seam where `ISSUE_FLOW_HOME` takes effect, which is what lets\n * tests, CI and sandboxes point the whole tree at a temporary directory instead\n * of touching the real `$HOME`. A call site that joins `homedir()` by hand\n * silently opts out of that isolation.\n *\n * Pure and synchronous: it never creates directories nor touches the\n * filesystem — callers decide when (and whether) the directory should exist.\n */\nexport function getGlobalRoot(options: GetGlobalRootOptions = {}): string {\n const env = options.env ?? process.env;\n\n const override = env[GLOBAL_ROOT_ENV]?.trim();\n if (override) {\n // A relative override is resolved against the CWD so that every consumer\n // gets an absolute path, exactly as in the homedir() branch below.\n return resolve(override);\n }\n\n // The only failure mode: an environment where the home directory cannot be\n // determined (some containers and CI images). The override is the escape\n // hatch, so the error names it.\n let home: string;\n try {\n home = homedir();\n } catch {\n home = '';\n }\n if (home.trim() === '') {\n throw new Error(\n `Unable to resolve the home directory for the global storage. Set ${GLOBAL_ROOT_ENV} to an explicit path.`,\n );\n }\n\n return join(home, GLOBAL_DIR_NAME);\n}\n\n/** Maximum length of the human-readable half of a project id. */\nconst SLUG_MAX_LENGTH = 32;\n\n/** Number of hex characters kept from the sha256 of the seed. */\nconst HASH_LENGTH = 12;\n\n/** Used when the repository name has no character that survives sanitization. */\nconst FALLBACK_SLUG = 'project';\n\n/**\n * Reduce an arbitrary repository name to a path-safe slug: lowercase, only\n * `[a-z0-9-]`, runs of separators collapsed, truncated to {@link SLUG_MAX_LENGTH}.\n */\nfunction slugify(name: string): string {\n const slug = name\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .slice(0, SLUG_MAX_LENGTH)\n .replace(/-+$/g, '');\n\n return slug === '' ? FALLBACK_SLUG : slug;\n}\n\n/**\n * Derive a stable identifier for a project, in the form `<slug>-<hash12>`.\n *\n * The seed is canonical rather than local, so the same repository yields the\n * same id on any machine: `remote:<normalizedRemote>` whenever an `origin`\n * remote exists, falling back to `path:<absoluteProjectRoot>` when it does not.\n * That is what makes moving or renaming the local folder harmless for a cloned\n * repository — the history stays attached to the remote identity.\n *\n * The `remote:` / `path:` prefix is part of the hashed seed, so a project\n * identified by path can never collide with one identified by remote even if\n * the two strings were to coincide. The slug is only cosmetic (it makes the\n * directory recognizable); the hash carries the identity.\n *\n * Known limitation of the path fallback: for a repository with no `origin`\n * remote, the absolute path *is* the identity. Moving or renaming that folder\n * produces a different id, and the previous history is left behind under the\n * old one. Configuring a remote before adopting the global storage avoids it.\n */\nexport async function getProjectId(projectRoot: string): Promise<string> {\n const remote = normalizeRemoteUrl(await getRemoteUrl(projectRoot));\n return projectIdFromRemote(remote, projectRoot);\n}\n\n/**\n * Pure half of {@link getProjectId}: derive the id once the normalized remote\n * (or `null`) is already known.\n *\n * Exported so a caller that already resolved the remote for another reason\n * (e.g. {@link resolveStorageMode} in `compat.ts`, which also persists it into\n * `metadata.json`) can compute the id without shelling out to git a second\n * time.\n */\nexport function projectIdFromRemote(remote: string | null, projectRoot: string): string {\n let seed: string;\n let name: string;\n if (remote) {\n // `host/org/repo` -> the repository name is the last path segment.\n seed = `remote:${remote}`;\n name = remote.split('/').pop() ?? '';\n } else {\n const absolute = resolve(projectRoot);\n seed = `path:${absolute}`;\n name = basename(absolute);\n }\n\n const hash = createHash('sha256').update(seed).digest('hex').slice(0, HASH_LENGTH);\n\n return `${slugify(name)}-${hash}`;\n}\n\n/**\n * Absolute directory of a project inside the global storage tree.\n *\n * `projectId` is expected to come from {@link getProjectId}, which already\n * guarantees a single safe path segment.\n */\nexport function getProjectDir(projectId: string, options: GetGlobalRootOptions = {}): string {\n return join(getGlobalRoot(options), PROJECTS_DIR_NAME, projectId);\n}\n\n/**\n * Every artifact of one issue, resolved from a single place.\n *\n * Note that `prdFile` is the requirements document (`prd.md`) and `tasksFile`\n * is the task plan (`tasks.json`) — unlike `ResolvedPaths.prdFile` in\n * `types.ts`, which historically points at `tasks.json`.\n */\nexport interface IssuePaths {\n issueDir: string;\n issueFile: string;\n metadataFile: string;\n prdFile: string;\n tasksFile: string;\n progressFile: string;\n analysisFile: string;\n sessionFile: string;\n lastBranchFile: string;\n archiveDir: string;\n prReviewDir: string;\n}\n\n/**\n * Artifacts of one multi-issue execution queue.\n *\n * A queue coordinates several issues that share a branch; each issue keeps its\n * own directory under {@link ISSUES_DIR_NAME} exactly as before, and only the\n * coordination state (order, per-issue status, shared branch, consolidated Pull\n * Request) lives here. That is what keeps a single-issue run byte-identical: it\n * never creates a queue at all.\n */\nexport interface QueuePaths {\n queueDir: string;\n planFile: string;\n}\n\n/**\n * Resolve the paths of one execution queue under the global storage.\n *\n * The queue id is the identifier of the primary issue — the first one the user\n * asked for — so re-running the same command finds the same queue and resumes\n * it instead of starting a parallel one.\n *\n * Pure and synchronous like every other helper here: nothing is created.\n */\nexport function getQueuePaths(\n projectId: string,\n queueId: string | number,\n options: GetGlobalRootOptions = {},\n): QueuePaths {\n const queueDir = join(\n getProjectDir(projectId, options),\n QUEUES_DIR_NAME,\n normalizeIssueNumber(queueId),\n );\n\n return { queueDir, planFile: join(queueDir, 'execution-plan.json') };\n}\n\n/**\n * Issue identifiers become path segments, so anything that could escape the\n * issues directory is rejected before it reaches `join` — the same discipline\n * as `normalizeId()` in `issues/providers/local.ts`.\n *\n * Non-numeric identifiers (`auth-refactor`, `pr-184`) are first-class here:\n * the local provider already supports them, so the global storage must too.\n */\nfunction normalizeIssueNumber(issueNumber: string | number): string {\n const normalized = String(issueNumber).trim().replace(/^#/, '');\n\n if (normalized.length === 0) {\n throw new Error('Issue identifier cannot be empty');\n }\n if (/[/\\\\]/.test(normalized) || normalized === '.' || normalized === '..') {\n throw new Error(`Invalid issue identifier: '${issueNumber}'`);\n }\n\n return normalized;\n}\n\n/**\n * Resolve every path of a single issue under the global storage.\n *\n * This is the only place allowed to know the layout of an issue directory:\n * call sites ask for the artifact they need instead of joining names by hand,\n * so renaming or adding an artifact stays a one-file change.\n *\n * Pure and synchronous: nothing here creates directories or touches the\n * filesystem.\n */\nexport function getIssuePaths(\n projectId: string,\n issueNumber: string | number,\n options: GetGlobalRootOptions = {},\n): IssuePaths {\n const issueDir = join(\n getProjectDir(projectId, options),\n ISSUES_DIR_NAME,\n normalizeIssueNumber(issueNumber),\n );\n\n return {\n issueDir,\n issueFile: join(issueDir, 'issue.md'),\n metadataFile: join(issueDir, 'metadata.json'),\n prdFile: join(issueDir, 'prd.md'),\n tasksFile: join(issueDir, 'tasks.json'),\n progressFile: join(issueDir, 'progress.txt'),\n analysisFile: join(issueDir, 'analysis.md'),\n sessionFile: join(issueDir, 'session.json'),\n lastBranchFile: join(issueDir, '.last-branch'),\n archiveDir: join(issueDir, 'archive'),\n prReviewDir: join(issueDir, 'pr-review'),\n };\n}\n","import { type Options as ExecaOptions, execa } from 'execa';\n\nexport interface ExecResult {\n stdout: string;\n stderr: string;\n exitCode: number;\n}\n\n/**\n * Execute a command with arguments and capture its output.\n * Uses execa which calls execFile internally (no shell injection risk).\n * Does not throw on non-zero exit codes.\n */\nexport async function run(\n command: string,\n args: string[] = [],\n options?: ExecaOptions,\n): Promise<ExecResult> {\n const result = await execa(command, args, {\n reject: false,\n ...options,\n });\n\n return {\n stdout: result.stdout?.toString() ?? '',\n stderr: result.stderr?.toString() ?? '',\n exitCode: result.exitCode ?? 1,\n };\n}\n","import { type ExecResult, run } from './shell.js';\n\n/**\n * Get the root directory of the current git repository.\n * Throws if not inside a git repository.\n */\nexport async function getProjectRoot(): Promise<string> {\n const result = await run('git', ['rev-parse', '--show-toplevel']);\n\n if (result.exitCode !== 0) {\n throw new Error(\n 'Not inside a git repository. Please run issue-flow from within a git project.',\n );\n }\n\n return result.stdout.trim();\n}\n\n/**\n * Get the current git branch name.\n * Returns an empty string if in detached HEAD state.\n */\nexport async function getCurrentBranch(): Promise<string> {\n const result = await run('git', ['branch', '--show-current']);\n\n if (result.exitCode !== 0) {\n throw new Error(\n 'Failed to detect git branch. Ensure git is installed and you are inside a repository.',\n );\n }\n\n return result.stdout.trim();\n}\n\nexport interface CommitInfo {\n hash: string;\n subject: string;\n}\n\n/**\n * Get the base branch of the repository: the remote HEAD (origin/HEAD) when\n * available, otherwise the first of main/master that exists locally.\n * Never throws; defaults to 'main'.\n */\nexport async function getBaseBranch(): Promise<string> {\n const remoteHead = await run('git', ['symbolic-ref', '--short', 'refs/remotes/origin/HEAD']);\n if (remoteHead.exitCode === 0) {\n const name = remoteHead.stdout.trim().replace(/^origin\\//, '');\n if (name) return name;\n }\n\n for (const candidate of ['main', 'master']) {\n const check = await run('git', ['rev-parse', '--verify', '--quiet', `refs/heads/${candidate}`]);\n if (check.exitCode === 0) return candidate;\n }\n\n return 'main';\n}\n\n/**\n * Get the URL of the `origin` remote.\n * Never throws: a missing remote, a non-zero exit code, an empty stdout or a\n * git binary that cannot even be spawned all resolve to `null` — the same\n * discipline as {@link getBaseBranch}, so callers can treat \"no remote\" as an\n * ordinary state instead of an error path.\n *\n * `cwd` selects which repository is queried; omitting it falls back to\n * `process.cwd()`, which is almost never what a caller resolving a specific\n * `projectRoot` wants — pass it explicitly whenever one is available.\n */\nexport async function getRemoteUrl(cwd?: string): Promise<string | null> {\n let result: ExecResult;\n try {\n result = await run('git', ['remote', 'get-url', 'origin'], { cwd });\n } catch {\n return null;\n }\n\n if (result.exitCode !== 0) return null;\n\n const url = result.stdout.trim();\n return url === '' ? null : url;\n}\n\n/**\n * Get the abbreviated hash of the current HEAD commit.\n * Never throws, same discipline as {@link getRemoteUrl}: a repository with no\n * commits yet, a non-zero exit code, an empty stdout or a git binary that\n * cannot be spawned all resolve to `null`, so callers can treat \"no HEAD\" as\n * an ordinary state instead of an error path.\n *\n * `cwd` selects which repository is queried; omitting it falls back to\n * `process.cwd()`.\n */\nexport async function getHeadCommit(cwd?: string): Promise<string | null> {\n let result: ExecResult;\n try {\n result = await run('git', ['rev-parse', '--short', 'HEAD'], { cwd });\n } catch {\n return null;\n }\n\n if (result.exitCode !== 0) return null;\n\n const hash = result.stdout.trim();\n return hash === '' ? null : hash;\n}\n\n/**\n * Reduce a git remote URL to a canonical `host/path` identity, so that every\n * way of addressing the same repository collapses to the same string:\n *\n * ```\n * https://github.com/org/repo.git -> github.com/org/repo\n * git@github.com:org/repo.git -> github.com/org/repo\n * ssh://git@github.com:22/org/repo.git -> github.com/org/repo\n * https://user:token@github.com/org/repo -> github.com/org/repo\n * https://github.com/Org/Repo/ -> github.com/org/repo\n * ```\n *\n * Pure function: it never shells out, so it is testable without a git\n * repository. Returns `null` when no host and path can be extracted.\n *\n * Known limitation: both host and path are lowercased. Hostnames are\n * case-insensitive by definition, but repository paths are case-sensitive on\n * some self-hosted servers — so `host/org/Repo` and `host/org/repo` normalize\n * to the same identity even if that server considers them distinct. This is a\n * deliberate trade: matching the same repository across clones (where users\n * routinely type the wrong case) matters more than telling apart two repos\n * whose paths differ only by case.\n */\nexport function normalizeRemoteUrl(url: string | null | undefined): string | null {\n if (typeof url !== 'string') return null;\n\n const trimmed = url.trim();\n if (trimmed === '') return null;\n\n let authority: string;\n let path: string;\n\n const scheme = /^[a-zA-Z][a-zA-Z0-9+.-]*:\\/\\//.exec(trimmed);\n if (scheme) {\n const rest = trimmed.slice(scheme[0].length);\n const slash = rest.indexOf('/');\n if (slash === -1) return null;\n authority = rest.slice(0, slash);\n path = rest.slice(slash + 1);\n } else {\n // scp-like syntax: [user@]host:path — everything after the first colon is\n // the path, which is how git itself reads it (no port is possible here).\n const colon = trimmed.indexOf(':');\n if (colon === -1) return null;\n authority = trimmed.slice(0, colon);\n path = trimmed.slice(colon + 1);\n }\n\n // Drop embedded credentials (user, user:token) and the ssh user.\n const at = authority.lastIndexOf('@');\n if (at !== -1) authority = authority.slice(at + 1);\n\n const host = authority.replace(/:\\d+$/, '').toLowerCase();\n if (host === '') return null;\n\n const normalizedPath = path\n .replace(/[?#].*$/, '')\n .replace(/\\/+/g, '/')\n .replace(/^\\/+|\\/+$/g, '')\n .replace(/\\.git$/i, '')\n .replace(/\\/+$/, '')\n .toLowerCase();\n if (normalizedPath === '') return null;\n\n return `${host}/${normalizedPath}`;\n}\n\n/**\n * Remove Basic-Auth-style credentials (`user:token@`, `user@`) embedded in an\n * `http(s)` remote URL, without touching anything else about it — case, port,\n * the `.git` suffix, query string and trailing slashes are all preserved.\n *\n * Deliberately scoped to `http`/`https`: that is the only place a git remote\n * can carry a real secret (a PAT is routinely embedded as\n * `https://x-access-token:TOKEN@host/...` by CI and automation), and the URL\n * stays a valid, clonable remote once the userinfo is gone. SSH has no\n * equivalent syntax — both `ssh://user@host/path` and the scp-like shorthand\n * `user@host:path` require that user segment to connect at all (it is almost\n * always the fixed, non-secret `git` service account), so this function\n * leaves any other scheme, the scp-like form and local paths untouched.\n *\n * Unlike {@link normalizeRemoteUrl}, which collapses a URL to a lossy\n * `host/path` identity, this keeps the URL \"as configured\" — it exists for\n * surfaces (like the web monitor's `repository.remoteUrl`) that want to show\n * the real remote, minus whatever secret an HTTPS token commonly embeds in\n * it.\n *\n * Pure function, never throws. Returns `null` for a nullish or empty input.\n */\nexport function stripRemoteUrlCredentials(url: string | null | undefined): string | null {\n if (typeof url !== 'string') return null;\n\n const trimmed = url.trim();\n if (trimmed === '') return null;\n\n const httpScheme = /^https?:\\/\\//i.exec(trimmed);\n if (!httpScheme) return trimmed;\n\n const rest = trimmed.slice(httpScheme[0].length);\n const slash = rest.indexOf('/');\n const authority = slash === -1 ? rest : rest.slice(0, slash);\n const remainder = slash === -1 ? '' : rest.slice(slash);\n\n const at = authority.lastIndexOf('@');\n if (at === -1) return trimmed;\n return `${httpScheme[0]}${authority.slice(at + 1)}${remainder}`;\n}\n\n/**\n * List commits on HEAD that are not on the given base branch (base..HEAD),\n * most recent first. Never throws; returns [] when the range cannot be\n * resolved (e.g. unknown base branch or shallow clone).\n */\nexport async function getCommitsSince(base: string): Promise<CommitInfo[]> {\n const result = await run('git', ['log', '--pretty=format:%h%x09%s', `${base}..HEAD`]);\n if (result.exitCode !== 0) return [];\n\n return result.stdout\n .split('\\n')\n .filter((line) => line.trim() !== '')\n .map((line) => {\n const tab = line.indexOf('\\t');\n return tab === -1\n ? { hash: line.trim(), subject: '' }\n : { hash: line.slice(0, tab), subject: line.slice(tab + 1) };\n });\n}\n","import { stat } from 'node:fs/promises';\nimport { basename, join, resolve } from 'node:path';\nimport { printInfo } from '../ui/logger.js';\nimport { getProjectRoot } from '../utils/git.js';\nimport { type MigrationResult, migrateLegacyStorage, resolveStorageMode } from './compat.js';\nimport {\n type GetGlobalRootOptions,\n getGlobalRoot,\n getIssuePaths,\n getProjectDir,\n getQueuePaths,\n ISSUES_DIR_NAME,\n type IssuePaths,\n type QueuePaths,\n} from './paths.js';\n\n/**\n * The single entry point every pipeline command uses to learn where an issue's\n * artifacts live.\n *\n * `paths.ts` and `compat.ts` stay pure and explicit — they take a `projectId` or\n * a `projectRoot` and never decide anything on their own. This module is the\n * opposite end: it knows the current repository, resolves the storage mode,\n * triggers the legacy migration when one is due, and caches all of that for the\n * lifetime of the process, so a call site is reduced to one line.\n *\n * It deliberately does **not** create the issue directory. Callers that write\n * keep doing their own `mkdir(..., { recursive: true })`, which preserves the\n * rule in `storage/CLAUDE.md` that path resolution never touches the\n * filesystem's shape.\n */\n\nexport interface ResolveIssuePathsOptions extends GetGlobalRootOptions {\n /**\n * Repository root. Defaults to {@link getProjectRoot}, i.e. the git toplevel\n * of the current working directory — which is what makes the result identical\n * from the repo root and from any subdirectory.\n */\n projectRoot?: string;\n}\n\n/** Everything the process needs to know about a project, resolved once. */\ninterface ProjectResolution {\n projectId: string;\n legacyDir: string;\n}\n\n/**\n * Cached project resolutions, keyed by `<globalRoot>::<projectRoot>`.\n *\n * `projectRoot` is the identity of the project, but the global root takes part\n * in the key as well: `ISSUE_FLOW_HOME` decides *where* the resolution lands,\n * so two different roots must never share an entry (that would silently hand a\n * test the previous test's tree).\n *\n * The cached value is the in-flight promise rather than its result, so two\n * commands resolving concurrently share a single git call instead of racing\n * into two migrations.\n */\nconst projectCache = new Map<string, Promise<ProjectResolution>>();\n\n/**\n * Issues whose global directory has already been checked in this process, so\n * the per-issue fallback below stats each issue at most once.\n */\nconst checkedIssues = new Set<string>();\n\n/**\n * Drop every cached resolution.\n *\n * Exported for tests: the cache is keyed by project and global root, so a suite\n * that moves `ISSUE_FLOW_HOME` between cases must reset it in `beforeEach`.\n */\nexport function resetStorageResolutionCache(): void {\n projectCache.clear();\n checkedIssues.clear();\n}\n\nasync function directoryExists(path: string): Promise<boolean> {\n try {\n return (await stat(path)).isDirectory();\n } catch {\n return false;\n }\n}\n\n/**\n * Tell the user that their artifacts moved, and that nothing was taken away.\n *\n * A migration that copied nothing stays silent: `migrateLegacyStorage` is\n * called speculatively (once per project, then once per issue seen for the\n * first time), and every one of those runs after the first copies zero files.\n * Printing on those would turn a one-time notice into noise on every command.\n *\n * The \"one notice per process\" guarantee falls out of the US-001 cache: the\n * project-level migration runs once, and the per-issue fallback only reaches a\n * second migration when an issue really is still legacy-only — in which case a\n * notice is the correct output, not a duplicate.\n */\nfunction announceMigration(result: MigrationResult): void {\n if (result.copied.length === 0) return;\n\n const count = result.copied.length;\n const target = join(result.globalDir, ISSUES_DIR_NAME);\n\n printInfo(\n `Migrated ${count} file${count === 1 ? '' : 's'} from ${result.legacyDir} to ${target}`,\n );\n printInfo(\n `The legacy directory was not modified or removed — ${result.legacyDir} is kept as-is, read-only, for compatibility.`,\n );\n}\n\n/**\n * Resolve the project id, migrating the legacy tree when it is the only thing\n * that exists yet.\n *\n * A migration failure is not swallowed: `migrateLegacyStorage` already names the\n * issue and file that failed and states that the legacy directory was left\n * untouched, which is exactly what the user needs to fix it and retry.\n */\nasync function resolveProject(\n projectRoot: string,\n options: GetGlobalRootOptions,\n): Promise<ProjectResolution> {\n // resolveStorageMode returns the project id *and* the remote in a single git\n // call — calling getProjectId() afterwards would shell out to\n // `git remote get-url origin` a second time for the same answer.\n const status = await resolveStorageMode(projectRoot, options);\n\n if (status.mode === 'needs-migration') {\n announceMigration(await migrateLegacyStorage(projectRoot, options));\n }\n\n return {\n projectId: status.projectId,\n legacyDir: status.legacyDir,\n };\n}\n\n/** Cached half of {@link resolveIssuePaths}: one git call per project, per process. */\nfunction getProjectResolution(\n projectRoot: string,\n options: GetGlobalRootOptions,\n): Promise<ProjectResolution> {\n const key = `${getGlobalRoot(options)}::${projectRoot}`;\n\n const cached = projectCache.get(key);\n if (cached) return cached;\n\n // A rejected resolution is evicted so the next command retries instead of\n // inheriting a permanently failed promise.\n const pending = resolveProject(projectRoot, options).catch((err) => {\n projectCache.delete(key);\n throw err;\n });\n\n projectCache.set(key, pending);\n return pending;\n}\n\n/** Project-level directories of the current repository inside the global tree. */\nexport interface ProjectStoragePaths {\n /** Deterministic id derived from the repository's remote (or its path). */\n projectId: string;\n /** `<globalRoot>/projects/<projectId>`. */\n projectDir: string;\n /** `<projectDir>/issues` — the parent of every issue directory. */\n issuesDir: string;\n}\n\n/**\n * Resolve the project-level directories, without naming an issue.\n *\n * The issue-agnostic half of {@link resolveIssuePaths}, for the two questions\n * that are about the project rather than about one issue: \"can I write here?\"\n * (`LocalFileIssueProvider.isAvailable`) and \"which identifiers are taken?\"\n * (`highestLocalNumber`, which walks `issuesDir`). It shares the same cache, so\n * asking here and then resolving an issue still costs a single git call.\n *\n * Like the rest of this module it creates nothing — a caller that needs the\n * directory to exist does its own `mkdir(..., { recursive: true })`.\n */\nexport async function resolveProjectPaths(\n options: ResolveIssuePathsOptions = {},\n): Promise<ProjectStoragePaths> {\n const { projectRoot: projectRootOption, ...rootOptions } = options;\n const projectRoot = resolve(projectRootOption ?? (await getProjectRoot()));\n\n const project = await getProjectResolution(projectRoot, rootOptions);\n const projectDir = getProjectDir(project.projectId, rootOptions);\n\n return {\n projectId: project.projectId,\n projectDir,\n issuesDir: join(projectDir, ISSUES_DIR_NAME),\n };\n}\n\n/**\n * Resolve the paths of a multi-issue execution queue in the current project.\n *\n * Shares the project cache with {@link resolveIssuePaths}, so a run that\n * resolves both still costs a single git call. There is no legacy tree for\n * queues — the concept did not exist before — so no migration is attempted.\n */\nexport async function resolveQueuePaths(\n queueId: string | number,\n options: ResolveIssuePathsOptions = {},\n): Promise<QueuePaths> {\n const { projectRoot: projectRootOption, ...rootOptions } = options;\n const projectRoot = resolve(projectRootOption ?? (await getProjectRoot()));\n\n const project = await getProjectResolution(projectRoot, rootOptions);\n return getQueuePaths(project.projectId, queueId, rootOptions);\n}\n\n/**\n * Resolve every path of `issueNumber` under the global storage, migrating the\n * legacy `<projectRoot>/issues/` tree first when needed.\n *\n * The migration is triggered in two situations:\n *\n * 1. project level — the global project directory does not exist at all and the\n * legacy one does (`mode === 'needs-migration'`);\n * 2. issue level — the project directory exists (so the mode is already\n * `global`) but *this* issue is only present in the legacy tree. That\n * happens whenever a project was migrated before an older issue was ever\n * read, or when a collaborator on a previous version added an issue to the\n * legacy directory afterwards. Re-running the migration is cheap and safe:\n * it skips every destination that already exists.\n */\nexport async function resolveIssuePaths(\n issueNumber: string | number,\n options: ResolveIssuePathsOptions = {},\n): Promise<IssuePaths> {\n const { projectRoot: projectRootOption, ...rootOptions } = options;\n const projectRoot = resolve(projectRootOption ?? (await getProjectRoot()));\n\n const project = await getProjectResolution(projectRoot, rootOptions);\n const paths = getIssuePaths(project.projectId, issueNumber, rootOptions);\n\n if (!checkedIssues.has(paths.issueDir)) {\n // `getIssuePaths` already validated and normalized the identifier, so the\n // last segment of issueDir is the safe form to look for under the legacy\n // tree.\n const legacyIssueDir = join(project.legacyDir, basename(paths.issueDir));\n\n if (!(await directoryExists(paths.issueDir)) && (await directoryExists(legacyIssueDir))) {\n announceMigration(await migrateLegacyStorage(projectRoot, rootOptions));\n }\n\n // Marked only once the check completed: a failed migration must be retried\n // by the next command, not remembered as done.\n checkedIssues.add(paths.issueDir);\n }\n\n return paths;\n}\n","import type { Dirent } from 'node:fs';\nimport { copyFile, mkdir, readdir, readFile, stat } from 'node:fs/promises';\nimport { join, resolve, sep } from 'node:path';\nimport { isoNow } from '../core/state-manager.js';\nimport { writeFileAtomic } from '../utils/fs.js';\nimport { getRemoteUrl, normalizeRemoteUrl } from '../utils/git.js';\nimport {\n type GetGlobalRootOptions,\n getProjectDir,\n ISSUES_DIR_NAME,\n projectIdFromRemote,\n} from './paths.js';\nimport {\n type ProjectMetadata,\n projectMetadataSchema,\n STORAGE_SCHEMA_VERSION,\n type UserStoryNumberingDecision,\n} from './schemas.js';\n\n/**\n * Coexistence between the legacy per-project layout (`<projectRoot>/issues/`)\n * and the global storage tree (`~/.issue-flow/projects/<project-id>/`).\n *\n * The single rule of this module: **the legacy directory is read-only.** Nothing\n * here removes or rewrites a byte under `<projectRoot>/issues/` — not even\n * behind an opt-in flag — so adopting the global storage can never cost a user\n * their history. Migration is a copy, and a copy that refuses to overwrite.\n */\n\n/** Directory holding the legacy per-project artifacts. */\nexport const LEGACY_ISSUES_DIR_NAME = 'issues';\n\n/** File holding the per-project metadata inside the global storage. */\nexport const METADATA_FILENAME = 'metadata.json';\n\n/**\n * - `global`: the global directory is the source of truth (it already exists,\n * or neither layout does and a fresh project starts there).\n * - `needs-migration`: only the legacy directory exists, so its content has not\n * been copied over yet.\n */\nexport type StorageMode = 'global' | 'needs-migration';\n\nexport interface StorageStatus {\n mode: StorageMode;\n /** Deterministic id, derived from {@link projectIdFromRemote}. */\n projectId: string;\n /** `<globalRoot>/projects/<projectId>`. */\n globalDir: string;\n /** `<projectRoot>/issues`. */\n legacyDir: string;\n /** Whether `globalDir` currently exists on disk. */\n globalExists: boolean;\n /** Whether `legacyDir` currently exists on disk. */\n legacyExists: boolean;\n /**\n * Normalized `origin` remote of `projectRoot`, or `null` without one.\n * Resolved once here so `migrateLegacyStorage` can reuse it instead of\n * shelling out to git a second time for `metadata.json`.\n */\n remoteUrl: string | null;\n}\n\nexport interface MigrationResult {\n projectId: string;\n globalDir: string;\n legacyDir: string;\n /** Mode observed *before* the migration ran. */\n previousMode: StorageMode;\n /** Paths copied, relative to `legacyDir`. */\n copied: string[];\n /** Paths left alone because the destination already had a file. */\n skipped: string[];\n /** Absolute path of the metadata file written by this run. */\n metadataFile: string;\n /** Metadata as persisted. */\n metadata: ProjectMetadata;\n}\n\nexport interface MigrateLegacyStorageOptions extends GetGlobalRootOptions {\n /** Timestamp source, injectable so tests can assert createdAt/updatedAt. */\n now?: () => string;\n}\n\nasync function directoryExists(path: string): Promise<boolean> {\n try {\n return (await stat(path)).isDirectory();\n } catch {\n return false;\n }\n}\n\nasync function pathExists(path: string): Promise<boolean> {\n try {\n await stat(path);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Describe where a project's artifacts live, without touching anything.\n *\n * Read-only by construction: it stats the two candidate directories and reports\n * what it found. It never creates a directory, so a caller can ask the question\n * (a status command, a warning banner) without committing the user to the new\n * layout.\n *\n * An existing global directory always wins: once artifacts are there, that is\n * the source of truth, and a legacy directory left behind is simply preserved\n * rather than re-read or re-migrated.\n */\nexport async function resolveStorageMode(\n projectRoot: string,\n options: GetGlobalRootOptions = {},\n): Promise<StorageStatus> {\n const remoteUrl = normalizeRemoteUrl(await getRemoteUrl(projectRoot));\n const projectId = projectIdFromRemote(remoteUrl, projectRoot);\n const globalDir = getProjectDir(projectId, options);\n const legacyDir = join(resolve(projectRoot), LEGACY_ISSUES_DIR_NAME);\n\n const [globalExists, legacyExists] = await Promise.all([\n directoryExists(globalDir),\n directoryExists(legacyDir),\n ]);\n\n return {\n // Only one state calls for work: legacy content that has nowhere to be read\n // from yet. Everything else — including \"neither exists\" — is already the\n // global layout.\n mode: legacyExists && !globalExists ? 'needs-migration' : 'global',\n projectId,\n globalDir,\n legacyDir,\n globalExists,\n legacyExists,\n remoteUrl,\n };\n}\n\n/**\n * Wrap an IO failure with the issue and file it happened on, so a partial\n * migration is diagnosable instead of a bare `EACCES`.\n */\nfunction migrationFailure(issue: string, relativePath: string, err: unknown): Error {\n const reason = err instanceof Error ? err.message : String(err);\n\n return new Error(\n `Failed to migrate '${relativePath}' (issue '${issue}'): ${reason}. ` +\n 'The legacy directory was left untouched — fix the cause and run the migration again.',\n );\n}\n\n/** First path segment of a relative path — the issue a file belongs to. */\nfunction issueOf(relativePath: string): string {\n return relativePath.split(sep)[0] || '.';\n}\n\n/**\n * Copy one directory level, recursing into subdirectories.\n *\n * Symlinks and other non-regular entries are skipped on purpose: following them\n * could copy content from outside the legacy directory into the global tree.\n */\nasync function copyTree(\n sourceRoot: string,\n targetRoot: string,\n relativeDir: string,\n result: { copied: string[]; skipped: string[] },\n): Promise<void> {\n const source = join(sourceRoot, relativeDir);\n const target = join(targetRoot, relativeDir);\n\n let entries: Dirent[];\n try {\n entries = await readdir(source, { withFileTypes: true });\n } catch (err) {\n throw migrationFailure(issueOf(relativeDir), relativeDir || '.', err);\n }\n\n try {\n // recursive: true also makes re-running a no-op on an existing directory.\n await mkdir(target, { recursive: true });\n } catch (err) {\n throw migrationFailure(issueOf(relativeDir), relativeDir || '.', err);\n }\n\n for (const entry of entries) {\n const relativePath = join(relativeDir, entry.name);\n\n if (entry.isDirectory()) {\n await copyTree(sourceRoot, targetRoot, relativePath, result);\n continue;\n }\n if (!entry.isFile()) {\n continue;\n }\n\n const to = join(targetRoot, relativePath);\n try {\n // Idempotency and safety in one check: a destination that already exists\n // is never overwritten, so a second run copies nothing and a hand-edited\n // artifact in the global tree survives.\n if (await pathExists(to)) {\n result.skipped.push(relativePath);\n continue;\n }\n await copyFile(join(sourceRoot, relativePath), to);\n result.copied.push(relativePath);\n } catch (err) {\n throw migrationFailure(issueOf(relativePath), relativePath, err);\n }\n }\n}\n\n/**\n * Read `metadata.json`, tolerating every failure.\n *\n * A missing, corrupt or schema-invalid file must not block a migration: the\n * caller simply writes a fresh one. The only thing worth recovering is\n * `createdAt`/`lastAttemptAt` from a valid previous file.\n */\nasync function readExistingMetadata(file: string): Promise<ProjectMetadata | null> {\n let raw: string;\n try {\n raw = await readFile(file, 'utf-8');\n } catch {\n return null;\n }\n\n try {\n const parsed = projectMetadataSchema.safeParse(JSON.parse(raw));\n return parsed.success ? parsed.data : null;\n } catch {\n return null;\n }\n}\n\n/**\n * Copy `<projectRoot>/issues/` into the project's global directory and stamp\n * its `metadata.json`.\n *\n * Non-destructive and idempotent, by design rather than by convention:\n *\n * - the source is only ever read — there is no removal option in this release;\n * - a destination file that already exists is skipped, never overwritten, so a\n * second run reports everything as skipped and changes nothing but\n * `updatedAt`;\n * - a failure mid-way leaves the legacy directory intact and names the issue\n * and file that failed, so the run can be repeated after the fix and will\n * resume by skipping what already made it across.\n *\n * Called on a project with no legacy directory it is still useful: it creates\n * the project directory and its metadata.\n */\nexport async function migrateLegacyStorage(\n projectRoot: string,\n options: MigrateLegacyStorageOptions = {},\n): Promise<MigrationResult> {\n const status = await resolveStorageMode(projectRoot, { env: options.env });\n const result = { copied: [] as string[], skipped: [] as string[] };\n\n try {\n await mkdir(status.globalDir, { recursive: true });\n } catch (err) {\n throw migrationFailure('.', status.globalDir, err);\n }\n\n if (status.legacyExists) {\n await copyTree(status.legacyDir, join(status.globalDir, ISSUES_DIR_NAME), '', result);\n }\n\n const now = (options.now ?? isoNow)();\n const metadataFile = join(status.globalDir, METADATA_FILENAME);\n const existing = await readExistingMetadata(metadataFile);\n\n const metadata: ProjectMetadata = {\n schemaVersion: STORAGE_SCHEMA_VERSION,\n projectId: status.projectId,\n root: resolve(projectRoot),\n remoteUrl: status.remoteUrl,\n // The project was first seen whenever it was first written, not now.\n createdAt: existing?.createdAt ?? now,\n updatedAt: now,\n lastAttemptAt: existing?.lastAttemptAt ?? null,\n };\n\n await writeFileAtomic(metadataFile, `${JSON.stringify(metadata, null, 2)}\\n`);\n\n return {\n projectId: status.projectId,\n globalDir: status.globalDir,\n legacyDir: status.legacyDir,\n previousMode: status.mode,\n ...result,\n metadataFile,\n metadata,\n };\n}\n\nexport interface RecordUserStoryNumberingOptions extends GetGlobalRootOptions {\n /** Timestamp source, injectable so tests can assert `updatedAt`. */\n now?: () => string;\n}\n\n/**\n * Persist a `plan` run's User Story numbering decision into the project's\n * `metadata.json`, for audit (issue #36) — this is a read-modify-write on the\n * same file {@link migrateLegacyStorage} stamps, never a second file.\n *\n * The decision itself is never read back to resolve a *later* one: the next\n * `plan` run always re-scans `tasks.json` from scratch (see\n * `storage/user-story-numbering.ts`). Losing this field only costs an audit\n * trail entry, never correctness.\n *\n * A missing `metadata.json` (a project whose first command ever is a numbered\n * `plan`, with no legacy directory to trigger a migration) is created here\n * from scratch, exactly like {@link migrateLegacyStorage} would.\n */\nexport async function recordUserStoryNumbering(\n projectRoot: string,\n decision: UserStoryNumberingDecision,\n options: RecordUserStoryNumberingOptions = {},\n): Promise<void> {\n const status = await resolveStorageMode(projectRoot, { env: options.env });\n const metadataFile = join(status.globalDir, METADATA_FILENAME);\n const existing = await readExistingMetadata(metadataFile);\n const now = (options.now ?? isoNow)();\n\n const metadata: ProjectMetadata = {\n schemaVersion: STORAGE_SCHEMA_VERSION,\n projectId: status.projectId,\n root: resolve(projectRoot),\n remoteUrl: status.remoteUrl,\n createdAt: existing?.createdAt ?? now,\n updatedAt: now,\n lastAttemptAt: existing?.lastAttemptAt ?? null,\n userStoryNumbering: decision,\n };\n\n await mkdir(status.globalDir, { recursive: true });\n await writeFileAtomic(metadataFile, `${JSON.stringify(metadata, null, 2)}\\n`);\n}\n","import { z } from 'zod';\nimport type { ExecutionPlan } from '../execution/types.js';\nimport { pullRequestRefSchema, webConfigSchema } from '../schemas.js';\n\n/**\n * Zod schemas for the files written under the global storage tree\n * (`~/.issue-flow`): the per-project `metadata.json` and the global\n * `config.json`.\n *\n * They live here rather than in `src/schemas.ts` so that file stays focused on\n * the pipeline domain (task plans, Issue metadata, session snapshots) and the\n * storage layer keeps owning its own formats.\n */\n\n/** Version stamped on files written by this release of the storage layer. */\nexport const STORAGE_SCHEMA_VERSION = 1;\n\n/**\n * Where a `plan` run's `US-NNN` numbering came from (issue #36):\n *\n * - `history`: recovered from the highest number already used anywhere in the\n * project's `tasks.json` files, automatically or via `--continue`.\n * - `start-us`: forced by the user via `--start-us <n>`, ignoring history.\n * - `none`: no history found (the project's first `plan` run) — starts at\n * `US-001`.\n */\nexport const userStoryNumberingSourceSchema = z.enum(['history', 'start-us', 'none']);\n\n/**\n * A single numbering decision, persisted for audit under\n * `metadata.json`'s `userStoryNumbering` (see below) — never used to resolve\n * the *next* decision, which always re-scans `tasks.json` from scratch.\n */\nexport const userStoryNumberingDecisionSchema = z.object({\n /** The `US-NNN` number the `plan` prompt was told to continue from. */\n nextNumber: z.number().int().positive(),\n source: userStoryNumberingSourceSchema,\n /** Issue the decision was made for. */\n issueNumber: z.string().min(1),\n decidedAt: z.string().min(1),\n /** Human-readable origin, e.g. the previous story id and issue it came from. */\n detail: z.string().optional(),\n});\n\n/**\n * `~/.issue-flow/projects/<project-id>/metadata.json`.\n *\n * Deliberately **not** `.strict()`: a newer release may add fields (dashboard\n * history, counters) and an older one must still be able to read the file\n * instead of rejecting it. Unknown keys are dropped on parse, never fatal.\n *\n * `root` is the last known local checkout — informative only. Identity lives in\n * `projectId` (see `getProjectId()` in `paths.ts`), so moving the folder of a\n * project that has a remote updates `root` without changing the id.\n */\nexport const projectMetadataSchema = z.object({\n schemaVersion: z.number().int().positive(),\n projectId: z.string().min(1),\n root: z.string().min(1),\n /** Normalized remote (`host/org/repo`), or null when the project has none. */\n remoteUrl: z.string().nullable(),\n createdAt: z.string().min(1),\n updatedAt: z.string().min(1),\n /** Null until the project is used by a pipeline run. */\n lastAttemptAt: z.string().nullable(),\n /**\n * Most recent User Story numbering decision made by `plan` (issue #36).\n * Absent on a project whose `plan` never ran through the numbering\n * resolver, or on a `metadata.json` written before the feature existed.\n */\n userStoryNumbering: userStoryNumberingDecisionSchema.optional(),\n});\n\n/**\n * Global counterpart of the `web` key of `.issue-flow.json`, restricted to the\n * settings that make sense machine-wide: `enabled` and `includeLogs` stay a\n * per-project decision.\n *\n * The validation of each field is reused from `webConfigSchema` through\n * `.unwrap()`, which strips the `.default()` and keeps the constraints. Picking\n * and calling `.partial()` would not be enough: in zod 4 an optional field\n * still materializes its default when the key is absent\n * (`z.object({ port: z.number().default(3737) }).partial().parse({})` yields\n * `{ port: 3737 }`), and a materialized default in this layer would override\n * the project layer during the merge.\n */\nconst webShape = webConfigSchema.shape;\nexport const globalWebConfigSchema = z\n .object({\n port: webShape.port.unwrap(),\n host: webShape.host.unwrap(),\n refreshSeconds: webShape.refreshSeconds.unwrap(),\n logLimit: webShape.logLimit.unwrap(),\n })\n .partial();\n\n/** Global retry preferences, mirroring `DEFAULTS` in `config.ts`. */\nexport const globalRetryConfigSchema = z\n .object({\n retryLimit: z.number().int().nonnegative(),\n retryForever: z.boolean(),\n backoffBaseSeconds: z.number().nonnegative(),\n backoffMaxSeconds: z.number().nonnegative(),\n })\n .partial();\n\n/** Global commit preferences. Consumed by later issues, not by this release. */\nexport const globalCommitConfigSchema = z\n .object({\n signoff: z.boolean(),\n conventional: z.boolean(),\n })\n .partial();\n\n/**\n * `~/.issue-flow/config.json`.\n *\n * **Every key is optional and no key carries a `.default()`.** This is a design\n * constraint, not an omission: the global file is an intermediate precedence\n * layer (CLI > env > `.issue-flow.json` > `config.json` > defaults), so a\n * default materialized here would be indistinguishable from a value the user\n * actually wrote and would silently override the project layer during the\n * merge. Same reason `readWebConfigFile()` parses with\n * `webConfigSchema.partial()`. Defaults belong to the final layer only.\n */\nexport const globalConfigSchema = z\n .object({\n schemaVersion: z.number().int().positive(),\n /** Override for the directory holding `projects/`. */\n storageDir: z.string().min(1),\n web: globalWebConfigSchema,\n retry: globalRetryConfigSchema,\n commit: globalCommitConfigSchema,\n })\n .partial();\n\n/**\n * `~/.issue-flow/web.lock`.\n *\n * Marks the single web monitoring server active on this machine: `pid` and\n * `port` let a new invocation tell a live instance from a stale one before\n * attempting to bind its own (see `web/lock.ts`). Not `.strict()` for the same\n * reason as the schemas above — a newer release may add fields.\n */\nexport const webLockSchema = z.object({\n pid: z.number().int().positive(),\n port: z.number().int().positive(),\n host: z.string().min(1),\n startedAt: z.string().min(1),\n});\n\n/**\n * `~/.issue-flow/projects/<project-id>/queues/<queue-id>/execution-plan.json`.\n *\n * The coordination state of a multi-issue run: which Issues, in which order, on\n * which shared branch, and how far the queue got. Each Issue keeps its own\n * `tasks.json` — nothing of the task plan is duplicated here.\n *\n * Written only when a queue really has more than one Issue: a single-issue run\n * creates no queue directory at all, which is what keeps the storage layout of\n * every existing user unchanged.\n *\n * Not `.strict()`, like every other file schema here, and `satisfies` keeps it\n * in lockstep with the `ExecutionPlan` interface in `src/execution/types.ts`.\n */\nexport const executionPlanIssueSchema = z.object({\n id: z.string().min(1),\n number: z.number().int().positive().nullable(),\n title: z.string(),\n url: z.string().nullable(),\n source: z.string().min(1),\n position: z.number().int().positive(),\n status: z.enum(['pending', 'in_progress', 'completed', 'failed']),\n origin: z.enum(['requested', 'discovered']),\n dependsOn: z.array(z.string()).default([]),\n parent: z.string().nullable().default(null),\n priority: z.enum(['high', 'medium', 'low']).nullable().default(null),\n heuristic: z.boolean().default(false),\n failedPhase: z.string().nullable().default(null),\n lastError: z\n .object({ category: z.string(), message: z.string(), at: z.string() })\n .nullable()\n .default(null),\n startedAt: z.string().nullable().default(null),\n completedAt: z.string().nullable().default(null),\n});\n\nexport const executionPlanSchema = z.object({\n schemaVersion: z.literal(1),\n id: z.string().min(1),\n project: z.string().min(1),\n requested: z.array(z.string().min(1)),\n branchName: z.string().nullable().default(null),\n noBranch: z.boolean().default(false),\n prReview: z.boolean().default(false),\n status: z.enum(['pending', 'in_progress', 'completed', 'failed']),\n createdAt: z.string().min(1),\n updatedAt: z.string().min(1),\n truncated: z.boolean().default(false),\n issues: z.array(executionPlanIssueSchema),\n excluded: z\n .array(\n z.object({\n id: z.string().min(1),\n number: z.number().int().positive().nullable(),\n title: z.string(),\n url: z.string().nullable(),\n reason: z.string(),\n }),\n )\n .default([]),\n pullRequest: pullRequestRefSchema.optional(),\n}) satisfies z.ZodType<ExecutionPlan>;\n\nexport type UserStoryNumberingSource = z.infer<typeof userStoryNumberingSourceSchema>;\nexport type UserStoryNumberingDecision = z.infer<typeof userStoryNumberingDecisionSchema>;\nexport type ProjectMetadata = z.infer<typeof projectMetadataSchema>;\nexport type ValidatedExecutionPlan = z.infer<typeof executionPlanSchema>;\nexport type GlobalWebConfig = z.infer<typeof globalWebConfigSchema>;\nexport type GlobalRetryConfig = z.infer<typeof globalRetryConfigSchema>;\nexport type GlobalCommitConfig = z.infer<typeof globalCommitConfigSchema>;\nexport type GlobalConfig = z.infer<typeof globalConfigSchema>;\nexport type WebLock = z.infer<typeof webLockSchema>;\n"],"mappings":"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","names":["readFile","join","stat","basename","join","resolve","join","resolve","join","resolve","directoryExists","stat","join","resolve","basename","join","readFile"]}
package/dist/cli.js CHANGED
@@ -2,12 +2,12 @@
2
2
  import {
3
3
  setIssuesCliOverrides,
4
4
  setWebCliOverrides
5
- } from "./chunk-MTJQOOMG.js";
5
+ } from "./chunk-VYFKOUUR.js";
6
6
  import {
7
7
  printError,
8
8
  setGlobalTimeout,
9
9
  setVerbose
10
- } from "./chunk-57MH6RYJ.js";
10
+ } from "./chunk-V5OHFSW4.js";
11
11
 
12
12
  // src/cli.ts
13
13
  import { createRequire } from "module";
@@ -194,8 +194,8 @@ withIssueOptions(
194
194
  program.command("init").description("Verify that all prerequisites (claude, gh, git) are met")
195
195
  )
196
196
  ).action(async () => {
197
- const { loadIssuesConfig } = await import("./config-J5DW6HSY.js");
198
- const { runInit } = await import("./init-UY6ZCIFT.js");
197
+ const { loadIssuesConfig } = await import("./config-YCF3HIKR.js");
198
+ const { runInit } = await import("./init-G57UXKDG.js");
199
199
  const { preferredProvider } = await loadIssuesConfig();
200
200
  const code = await runInit(preferredProvider);
201
201
  process.exit(code);
@@ -213,7 +213,7 @@ withGlobalOptions(
213
213
  }
214
214
  throw error;
215
215
  }
216
- const { runGenerate } = await import("./generate-ERDPOTKX.js");
216
+ const { runGenerate } = await import("./generate-TW6QKSW5.js");
217
217
  const code = await runGenerate(options.prompt, target);
218
218
  process.exit(code);
219
219
  });
@@ -246,7 +246,7 @@ withUserStoryNumberingOptions(
246
246
  }
247
247
  throw error;
248
248
  }
249
- const { runPipeline } = await import("./run-BRBFHVUO.js");
249
+ const { runPipeline } = await import("./run-DIBC3YKX.js");
250
250
  const code = await runPipeline(
251
251
  issues,
252
252
  options.mode,
@@ -268,7 +268,7 @@ withIssueOptions(
268
268
  program.command("analyze").description("Analyze an issue via Claude Code Headless").argument("<issue>", "Issue number")
269
269
  )
270
270
  ).action(async (issue) => {
271
- const { runAnalyze } = await import("./analyze-VSWAEVN3.js");
271
+ const { runAnalyze } = await import("./analyze-UZYJAWAP.js");
272
272
  const code = await runAnalyze(issue);
273
273
  process.exit(code);
274
274
  });
@@ -277,7 +277,7 @@ withIssueOptions(
277
277
  program.command("prd").description("Generate a PRD from an analyzed issue via Claude Code Headless").argument("<issue>", "Issue number")
278
278
  )
279
279
  ).action(async (issue) => {
280
- const { runPrd } = await import("./prd-SHVQW6RM.js");
280
+ const { runPrd } = await import("./prd-3XR5KYJS.js");
281
281
  const code = await runPrd(issue);
282
282
  process.exit(code);
283
283
  });
@@ -298,7 +298,7 @@ withUserStoryNumberingOptions(
298
298
  }
299
299
  throw error;
300
300
  }
301
- const { runPlan } = await import("./plan-REMAMCX4.js");
301
+ const { runPlan } = await import("./plan-YJLGBYBQ.js");
302
302
  const code = await runPlan(issue, void 0, numbering);
303
303
  process.exit(code);
304
304
  });
@@ -317,7 +317,7 @@ withWebOptions(
317
317
  ).action(
318
318
  async (positionalMaxIter, options) => {
319
319
  try {
320
- const { runExecute } = await import("./execute-Z2DKGVLR.js");
320
+ const { runExecute } = await import("./execute-QYXA225W.js");
321
321
  const code = await runExecute(positionalMaxIter, options);
322
322
  process.exit(code);
323
323
  } catch (error) {
@@ -331,7 +331,7 @@ withIssueOptions(
331
331
  program.command("review").description("Validate an issue resolution via Claude Code Headless").argument("<issue>", "Issue number")
332
332
  )
333
333
  ).action(async (issue) => {
334
- const { runReview } = await import("./review-L5F77QAC.js");
334
+ const { runReview } = await import("./review-ZZG3CSUO.js");
335
335
  const code = await runReview(issue);
336
336
  process.exit(code);
337
337
  });
@@ -340,7 +340,7 @@ withIssueOptions(
340
340
  program.command("pr").description("Create a pull request via Claude Code Headless").argument("<issue>", "Issue number")
341
341
  )
342
342
  ).action(async (issue) => {
343
- const { runPr } = await import("./pr-KERP52TI.js");
343
+ const { runPr } = await import("./pr-V5H65MRW.js");
344
344
  const code = await runPr(issue);
345
345
  process.exit(code);
346
346
  });
@@ -348,17 +348,24 @@ var webCommand = program.command("web").description("Manage the web monitoring s
348
348
  webCommand.command("serve").description(
349
349
  "Run the web monitor server in the foreground (internal \u2014 spawned detached by --web)"
350
350
  ).option("--port <n>", "Web server port (default: 3737)", parseInteger).option("--host <h>", "Web server host (default: 0.0.0.0)").option("--refresh <s>", "Suggested UI polling interval in seconds", parseInteger).action(async (options) => {
351
- const { runWebServe } = await import("./web-WQISLO66.js");
351
+ const { runWebServe } = await import("./web-O2FVOYZW.js");
352
352
  const code = await runWebServe(options);
353
353
  if (code !== 0) {
354
354
  process.exit(code);
355
355
  }
356
356
  });
357
357
  webCommand.command("stop").description("Stop the running web monitor server").action(async () => {
358
- const { runWebStop } = await import("./web-WQISLO66.js");
358
+ const { runWebStop } = await import("./web-O2FVOYZW.js");
359
359
  const code = await runWebStop();
360
360
  process.exit(code);
361
361
  });
362
+ withGlobalOptions(
363
+ program.command("policy").description("Inspect the policies discovered in this repository and their provenance").option("--scope <dir>", "Resolve the policy for a subdirectory (monorepo)").option("--json", "Emit the resolved policy as JSON")
364
+ ).action(async (options) => {
365
+ const { runPolicy } = await import("./policy-NP4ZAVF6.js");
366
+ const code = await runPolicy(options);
367
+ process.exit(code);
368
+ });
362
369
  withGlobalOptions(
363
370
  program.command("pr-review").description("Review a Pull Request as a whole via Claude Code Headless").argument("[pr]", "Pull Request number (discovered from the session when omitted)").option("--issue <n>", "Issue the Pull Request belongs to").option("--round <n>", "Rewrite a specific review round instead of appending a new one").option("--yes", "Skip the confirmation of the discovered Pull Request").option(
364
371
  "--fail-on <level>",
@@ -366,7 +373,7 @@ withGlobalOptions(
366
373
  )
367
374
  ).action(
368
375
  async (pr, options) => {
369
- const { runPrReview } = await import("./pr-review-IU4N3TEK.js");
376
+ const { runPrReview } = await import("./pr-review-RCMXKXVP.js");
370
377
  const code = await runPrReview(pr, options);
371
378
  process.exit(code);
372
379
  }
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cli.ts","../src/cli-options.ts","../src/issues/cli-flags.ts"],"sourcesContent":["import { createRequire } from 'node:module';\nimport { Command, InvalidArgumentError } from 'commander';\nimport {\n CliFlagError,\n resolveQueueScopeFlags,\n resolveRunPhaseFlags,\n resolveUserStoryNumberingFlags,\n} from './cli-options.js';\nimport { setIssuesCliOverrides, setWebCliOverrides } from './config.js';\nimport { setGlobalTimeout, setVerbose } from './core/verbose.js';\nimport {\n IssueFlagError,\n resolveGenerateTarget,\n resolveIssuesOverrides,\n} from './issues/cli-flags.js';\nimport type { IssueGenerateTarget } from './issues/types.js';\nimport type { WebConfig } from './schemas.js';\nimport { printError } from './ui/logger.js';\n\nconst require = createRequire(import.meta.url);\nconst { version } = require('../package.json') as { version: string };\n\n/**\n * Parse a numeric string, throwing InvalidArgumentError if not a valid number.\n */\nfunction parseInteger(value: string): number {\n const parsed = parseInt(value, 10);\n if (Number.isNaN(parsed) || parsed < 0) {\n throw new InvalidArgumentError('Must be a non-negative integer.');\n }\n return parsed;\n}\n\n/**\n * Parse `--start-us <n>`: a User Story number is 1-based, so 0 is rejected\n * along with everything `parseInteger` already rejects.\n */\nfunction parseStartUs(value: string): number {\n const parsed = parseInteger(value);\n if (parsed < 1) {\n throw new InvalidArgumentError('Must be a positive integer (US-001 is the first story).');\n }\n return parsed;\n}\n\n/**\n * Add the User Story numbering override options to a subcommand that\n * triggers `plan` (`run` and `plan` itself) — see issue #36.\n */\nfunction withUserStoryNumberingOptions(cmd: Command): Command {\n return cmd\n .option('--continue', 'Continue User Story numbering from the last used in this project')\n .option(\n '--start-us <n>',\n 'Force User Story numbering to start at a specific number, ignoring history',\n parseStartUs,\n );\n}\n\n/**\n * Add shared options (--verbose) to a subcommand.\n */\nfunction withGlobalOptions(cmd: Command): Command {\n return cmd\n .option('-v, --verbose', 'Show Claude progress output in real time')\n .option(\n '-t, --timeout <seconds>',\n 'Override headless timeout in seconds (0 = no limit)',\n parseInteger,\n );\n}\n\n/**\n * Add web monitoring options to a subcommand (run and execute only).\n * The values are resolved into CLI overrides by the preAction hook below;\n * loadWebConfig() applies the flag > env > file > defaults precedence.\n */\nfunction withWebOptions(cmd: Command): Command {\n return cmd\n .option('--web', 'Enable the web monitoring server')\n .option('--serve', 'Alias for --web')\n .option('--port <n>', 'Web server port (default: 3737)', parseInteger)\n .option('--host <h>', 'Web server host (default: 0.0.0.0)')\n .option('--refresh <s>', 'Suggested UI polling interval in seconds', parseInteger)\n .option('--web-log-limit <n>', 'Max log entries kept in the snapshot', parseInteger)\n .option('--web-no-logs', 'Exclude logs from the published snapshot');\n}\n\n/**\n * Add the Issue provider options to a subcommand.\n *\n * Declared once here (like withWebOptions) so no command repeats the flag\n * list; the preAction hook below turns them into config overrides, and\n * loadIssuesConfig() applies the flag > .issue-flow.json > defaults precedence.\n */\nfunction withIssueOptions(cmd: Command): Command {\n return cmd\n .option('--local', 'Prefer the local file Issue provider')\n .option('--github', 'Prefer the GitHub Issue provider')\n .option('--prefer-local', 'On divergence, use the local version without asking')\n .option('--prefer-github', 'On divergence, use the GitHub version without asking')\n .option('--ask', 'On divergence, ask which version to use (interactive only)');\n}\n\n/**\n * Extract the web-related CLI flags from a command's options, keeping only\n * the ones the user actually set.\n */\nfunction resolveWebOverrides(opts: Record<string, unknown>): Partial<WebConfig> {\n const overrides: Partial<WebConfig> = {};\n if (opts.web === true || opts.serve === true) {\n overrides.enabled = true;\n }\n if (opts.port !== undefined) {\n overrides.port = opts.port as number;\n }\n if (opts.host !== undefined) {\n overrides.host = opts.host as string;\n }\n if (opts.refresh !== undefined) {\n overrides.refreshSeconds = opts.refresh as number;\n }\n if (opts.webLogLimit !== undefined) {\n overrides.logLimit = opts.webLogLimit as number;\n }\n if (opts.webNoLogs === true) {\n overrides.includeLogs = false;\n }\n return overrides;\n}\n\nconst program = new Command();\n\nprogram\n .name('issue-flow')\n .description(\n 'Unified CLI for orchestrating the full issue-flow pipeline via Claude Code Headless.',\n )\n .version(version);\n\nprogram.hook('preAction', (_thisCommand, actionCommand) => {\n const opts = actionCommand.opts();\n if (opts.verbose) {\n setVerbose(true);\n }\n if (opts.timeout !== undefined) {\n setGlobalTimeout(opts.timeout * 1000);\n }\n setWebCliOverrides(resolveWebOverrides(opts));\n try {\n setIssuesCliOverrides(resolveIssuesOverrides(opts));\n } catch (error) {\n if (error instanceof IssueFlagError) {\n printError(error.message);\n process.exit(1);\n }\n throw error;\n }\n});\n\n// ── init ────────────────────────────────────────────────────────────────────\nwithIssueOptions(\n withGlobalOptions(\n program.command('init').description('Verify that all prerequisites (claude, gh, git) are met'),\n ),\n).action(async () => {\n const { loadIssuesConfig } = await import('./config.js');\n const { runInit } = await import('./commands/init.js');\n const { preferredProvider } = await loadIssuesConfig();\n const code = await runInit(preferredProvider);\n process.exit(code);\n});\n\n// ── generate ────────────────────────────────────────────────────────────────\nwithGlobalOptions(\n program\n .command('generate')\n .description('Draft an issue via Claude Code Headless and create it')\n .requiredOption('--prompt <text>', 'Issue description text')\n .option('--github', 'Create the issue on GitHub')\n .option('--local', 'Create the issue under issues/<n>/ only')\n .option('--both', 'Create the issue on GitHub and mirror it locally'),\n).action(async (options: { prompt: string; github?: boolean; local?: boolean; both?: boolean }) => {\n let target: IssueGenerateTarget | undefined;\n try {\n target = resolveGenerateTarget(options);\n } catch (error) {\n if (error instanceof IssueFlagError) {\n printError(error.message);\n process.exit(1);\n }\n throw error;\n }\n\n const { runGenerate } = await import('./commands/generate.js');\n const code = await runGenerate(options.prompt, target);\n process.exit(code);\n});\n\n// ── run ─────────────────────────────────────────────────────────────────────\nwithUserStoryNumberingOptions(\n withWebOptions(\n withIssueOptions(\n withGlobalOptions(\n program\n .command('run')\n .description(\n 'Execute the full pipeline: prd → plan → execute → review → pr (→ pr-review, optional)',\n )\n .argument('<issues...>', 'Issue number(s): 42, \"42,43\" or 42 43')\n .option('--mode <mode>', 'Execution mode: auto | manual', 'auto')\n .option('--from <phase>', 'Resume from a specific phase')\n .option(\n '--no-branch',\n 'Run pipeline on current branch without creating a new branch or PR',\n )\n .option('--pr-review', 'Review the created Pull Request after the pr phase')\n .option('-y, --yes', 'Run the whole discovered hierarchy without confirmation')\n .option('--only', 'Run just the issues informed, without their hierarchy'),\n ),\n ),\n ),\n).action(\n async (\n issues: string[],\n options: {\n mode: string;\n from?: string;\n branch?: boolean;\n prReview?: boolean;\n yes?: boolean;\n only?: boolean;\n continue?: boolean;\n startUs?: number;\n },\n ) => {\n let phases: ReturnType<typeof resolveRunPhaseFlags>;\n let scope: ReturnType<typeof resolveQueueScopeFlags>;\n let numbering: ReturnType<typeof resolveUserStoryNumberingFlags>;\n try {\n phases = resolveRunPhaseFlags(options);\n scope = resolveQueueScopeFlags(options);\n numbering = resolveUserStoryNumberingFlags(options);\n } catch (error) {\n if (error instanceof CliFlagError) {\n printError(error.message);\n process.exit(1);\n }\n throw error;\n }\n\n const { runPipeline } = await import('./commands/run.js');\n const code = await runPipeline(\n issues,\n options.mode,\n options.from,\n phases.noBranch,\n phases.prReview,\n {\n yes: scope.yes,\n only: scope.only,\n continueNumbering: numbering.continueFlag,\n startUs: numbering.startUs,\n },\n );\n process.exit(code);\n },\n);\n\n// ── analyze ─────────────────────────────────────────────────────────────────\nwithIssueOptions(\n withGlobalOptions(\n program\n .command('analyze')\n .description('Analyze an issue via Claude Code Headless')\n .argument('<issue>', 'Issue number'),\n ),\n).action(async (issue: string) => {\n const { runAnalyze } = await import('./commands/analyze.js');\n const code = await runAnalyze(issue);\n process.exit(code);\n});\n\n// ── prd ─────────────────────────────────────────────────────────────────────\nwithIssueOptions(\n withGlobalOptions(\n program\n .command('prd')\n .description('Generate a PRD from an analyzed issue via Claude Code Headless')\n .argument('<issue>', 'Issue number'),\n ),\n).action(async (issue: string) => {\n const { runPrd } = await import('./commands/prd.js');\n const code = await runPrd(issue);\n process.exit(code);\n});\n\n// ── plan ────────────────────────────────────────────────────────────────────\nwithUserStoryNumberingOptions(\n withIssueOptions(\n withGlobalOptions(\n program\n .command('plan')\n .description('Convert a PRD to a tasks.json task plan via Claude Code Headless')\n .argument('<issue>', 'Issue number'),\n ),\n ),\n).action(async (issue: string, options: { continue?: boolean; startUs?: number }) => {\n let numbering: ReturnType<typeof resolveUserStoryNumberingFlags>;\n try {\n numbering = resolveUserStoryNumberingFlags(options);\n } catch (error) {\n if (error instanceof CliFlagError) {\n printError(error.message);\n process.exit(1);\n }\n throw error;\n }\n\n const { runPlan } = await import('./commands/plan.js');\n const code = await runPlan(issue, undefined, numbering);\n process.exit(code);\n});\n\n// ── execute ─────────────────────────────────────────────────────────────────\nwithWebOptions(\n withGlobalOptions(\n program\n .command('execute')\n .description('Run the iterative story execution loop (issue-flow engine)')\n .option('--issue <number>', 'Issue number — reads artifacts from issues/N/')\n .option('--max-iterations <number>', 'Stop after N iterations', parseInteger)\n .option(\n '--retry-limit <number>',\n 'Retry transient Claude failures up to N consecutive times',\n parseInteger,\n )\n .option('--retry-forever', 'Retry transient Claude failures indefinitely')\n .argument(\n '[max-iterations]',\n 'Backward-compatible alias for --max-iterations N',\n parseInteger,\n ),\n ),\n).action(\n async (\n positionalMaxIter: number | undefined,\n options: {\n issue?: string;\n maxIterations?: number;\n retryLimit?: number;\n retryForever?: boolean;\n },\n ) => {\n try {\n const { runExecute } = await import('./commands/execute.js');\n const code = await runExecute(positionalMaxIter, options);\n process.exit(code);\n } catch (error) {\n printError(error instanceof Error ? error.message : String(error));\n process.exit(1);\n }\n },\n);\n\n// ── review ──────────────────────────────────────────────────────────────────\nwithIssueOptions(\n withGlobalOptions(\n program\n .command('review')\n .description('Validate an issue resolution via Claude Code Headless')\n .argument('<issue>', 'Issue number'),\n ),\n).action(async (issue: string) => {\n const { runReview } = await import('./commands/review.js');\n const code = await runReview(issue);\n process.exit(code);\n});\n\n// ── pr ──────────────────────────────────────────────────────────────────────\nwithIssueOptions(\n withGlobalOptions(\n program\n .command('pr')\n .description('Create a pull request via Claude Code Headless')\n .argument('<issue>', 'Issue number'),\n ),\n).action(async (issue: string) => {\n const { runPr } = await import('./commands/pr.js');\n const code = await runPr(issue);\n process.exit(code);\n});\n\n// ── web ─────────────────────────────────────────────────────────────────────\nconst webCommand = program.command('web').description('Manage the web monitoring server');\n\nwebCommand\n .command('serve')\n .description(\n 'Run the web monitor server in the foreground (internal — spawned detached by --web)',\n )\n .option('--port <n>', 'Web server port (default: 3737)', parseInteger)\n .option('--host <h>', 'Web server host (default: 0.0.0.0)')\n .option('--refresh <s>', 'Suggested UI polling interval in seconds', parseInteger)\n .action(async (options: { port?: number; host?: string; refresh?: number }) => {\n const { runWebServe } = await import('./commands/web.js');\n const code = await runWebServe(options);\n // On success this process stays alive for as long as the server is bound\n // (server.ts binds it with unref: false) — only a failure exits here.\n if (code !== 0) {\n process.exit(code);\n }\n });\n\nwebCommand\n .command('stop')\n .description('Stop the running web monitor server')\n .action(async () => {\n const { runWebStop } = await import('./commands/web.js');\n const code = await runWebStop();\n process.exit(code);\n });\n\n// ── pr-review ───────────────────────────────────────────────────────────────\nwithGlobalOptions(\n program\n .command('pr-review')\n .description('Review a Pull Request as a whole via Claude Code Headless')\n .argument('[pr]', 'Pull Request number (discovered from the session when omitted)')\n .option('--issue <n>', 'Issue the Pull Request belongs to')\n .option('--round <n>', 'Rewrite a specific review round instead of appending a new one')\n .option('--yes', 'Skip the confirmation of the discovered Pull Request')\n .option(\n '--fail-on <level>',\n 'Verdict that fails the command: request-changes | suggestions | none',\n ),\n).action(\n async (\n pr: string | undefined,\n options: { issue?: string; round?: string; yes?: boolean; failOn?: string },\n ) => {\n const { runPrReview } = await import('./commands/pr-review.js');\n const code = await runPrReview(pr, options);\n process.exit(code);\n },\n);\n\nprogram.parse();\n","/**\n * Pure mappings from parsed CLI options to command arguments.\n *\n * They live outside cli.ts because that module runs `program.parse()` on\n * import, so anything defined there is untestable.\n */\n\n/** Shape commander produces for a negated boolean option (`--no-branch`). */\nexport interface BranchFlagOptions {\n branch?: boolean;\n}\n\n/**\n * Translate commander's `--no-branch` into the tri-state `runPipeline` expects.\n *\n * Commander stores a negated option under its positive name: `--no-branch`\n * yields `{ branch: false }` and never a `noBranch` key. Reading `noBranch`\n * silently dropped the flag, so the pipeline always created a branch and opened\n * a PR.\n *\n * `undefined` (flag absent) is preserved on purpose: `runPipeline` uses it to\n * tell \"the user said nothing, keep the mode persisted in tasks.json\" apart\n * from an explicit choice.\n */\nexport function resolveNoBranch(options: BranchFlagOptions): boolean | undefined {\n return options.branch === false ? true : undefined;\n}\n\n/** Invalid flag combination on a command, reported as a CLI error. */\nexport class CliFlagError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'CliFlagError';\n }\n}\n\n/** Options `run` accepts that select which phases the pipeline executes. */\nexport interface RunPhaseFlagOptions extends BranchFlagOptions {\n prReview?: boolean;\n}\n\n/** Phase selection `runPipeline` expects, both entries tri-state. */\nexport interface RunPhaseFlags {\n noBranch: boolean | undefined;\n prReview: boolean | undefined;\n}\n\n/** Scope flags of `run`, deciding what a discovered hierarchy does. */\nexport interface QueueScopeFlagOptions {\n yes?: boolean;\n only?: boolean;\n}\n\n/** Resolved scope answer; both absent means \"ask, when the terminal allows it\". */\nexport interface QueueScopeFlags {\n yes: boolean | undefined;\n only: boolean | undefined;\n}\n\n/**\n * Resolve `--yes` / `--only`, rejecting the combination that has no meaning.\n *\n * They answer the same question in opposite ways — run everything that was\n * discovered, or run only what was asked for — so passing both is a mistake\n * worth naming rather than silently resolving by precedence.\n *\n * @throws CliFlagError when both are present.\n */\nexport function resolveQueueScopeFlags(options: QueueScopeFlagOptions): QueueScopeFlags {\n if (options.yes === true && options.only === true) {\n throw new CliFlagError(\n '--yes cannot be combined with --only: --yes runs the whole discovered hierarchy, ' +\n '--only runs just the issues you informed.',\n );\n }\n\n return {\n yes: options.yes === true ? true : undefined,\n only: options.only === true ? true : undefined,\n };\n}\n\n/**\n * Resolve the phase-selection flags of `run`, rejecting the one combination\n * that cannot work.\n *\n * `--no-branch` skips the `pr` phase, so there is no Pull Request for\n * `--pr-review` to review. Failing here — before any phase runs — beats\n * discovering it at the end of a full pipeline.\n *\n * Both values stay `undefined` when the user passed nothing: `runPipeline`\n * uses that to tell \"no opinion, keep what tasks.json persisted\" apart from an\n * explicit choice.\n *\n * @throws CliFlagError when `--pr-review` is combined with `--no-branch`.\n */\nexport function resolveRunPhaseFlags(options: RunPhaseFlagOptions): RunPhaseFlags {\n const noBranch = resolveNoBranch(options);\n const prReview = options.prReview === true ? true : undefined;\n\n if (prReview === true && noBranch === true) {\n throw new CliFlagError(\n '--pr-review cannot be combined with --no-branch: --no-branch skips the pr phase, ' +\n 'so there is no Pull Request to review.',\n );\n }\n\n return { noBranch, prReview };\n}\n\n/** Options `run` and `plan` accept to override User Story numbering (issue #36). */\nexport interface UserStoryNumberingFlagOptions {\n continue?: boolean;\n startUs?: number;\n}\n\n/** Resolved User Story numbering override, ready for `determineUserStoryNumbering()`. */\nexport interface UserStoryNumberingFlags {\n continueFlag: boolean;\n startUs: number | undefined;\n}\n\n/**\n * Resolve the `--continue` / `--start-us <n>` override of the User Story\n * numbering cascade (issue #36), rejecting the one combination that cannot\n * work: the two disagree about whether history should be consulted at all.\n *\n * @throws CliFlagError when both flags are passed together.\n */\nexport function resolveUserStoryNumberingFlags(\n options: UserStoryNumberingFlagOptions,\n): UserStoryNumberingFlags {\n const continueFlag = options.continue === true;\n const startUs = options.startUs;\n\n if (continueFlag && startUs !== undefined) {\n throw new CliFlagError(\n '--continue and --start-us cannot be combined: choose either the last known numbering ' +\n '(--continue) or an explicit override (--start-us).',\n );\n }\n\n return { continueFlag, startUs };\n}\n","import type { IssueGenerateTarget, IssuesConfig } from './types.js';\n\n/**\n * Mapping from the Issue provider CLI flags to configuration overrides.\n *\n * Kept out of cli.ts (which runs `program.parse()` on import) so the\n * precedence rules stay unit-testable. Only the keys the user actually passed\n * are returned: a key present with `undefined` would override .issue-flow.json\n * when the layers are merged.\n */\n\nexport interface IssueCliFlags {\n local?: boolean;\n github?: boolean;\n preferLocal?: boolean;\n preferGithub?: boolean;\n ask?: boolean;\n}\n\n/** Invalid flag combination, reported to the user as a CLI error. */\nexport class IssueFlagError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'IssueFlagError';\n }\n}\n\n/**\n * Translate the parsed options of a command into `IssuesConfig` overrides.\n *\n * @throws IssueFlagError when mutually exclusive flags are combined.\n */\nexport function resolveIssuesOverrides(opts: IssueCliFlags): Partial<IssuesConfig> {\n const overrides: Partial<IssuesConfig> = {};\n\n if (opts.local === true && opts.github === true) {\n throw new IssueFlagError('--local and --github are mutually exclusive; pass only one.');\n }\n if (opts.local === true) {\n overrides.preferredProvider = 'local';\n }\n if (opts.github === true) {\n overrides.preferredProvider = 'github';\n }\n\n const conflictFlags = [\n opts.preferLocal === true ? '--prefer-local' : null,\n opts.preferGithub === true ? '--prefer-github' : null,\n opts.ask === true ? '--ask' : null,\n ].filter((flag): flag is string => flag !== null);\n\n if (conflictFlags.length > 1) {\n throw new IssueFlagError(\n `${conflictFlags.join(', ')} are mutually exclusive; pass only one conflict policy.`,\n );\n }\n if (opts.preferLocal === true) {\n overrides.conflictPolicy = 'prefer-local';\n }\n if (opts.preferGithub === true) {\n overrides.conflictPolicy = 'prefer-github';\n }\n if (opts.ask === true) {\n overrides.conflictPolicy = 'ask';\n }\n\n return overrides;\n}\n\n/** Destination flags accepted by `generate`. */\nexport interface GenerateTargetFlags {\n github?: boolean;\n local?: boolean;\n both?: boolean;\n}\n\n/**\n * Destination the generated Issue is created in, or `undefined` when the user\n * passed no flag (the caller then falls back to `defaultGenerateTarget`).\n *\n * @throws IssueFlagError when more than one destination is requested.\n */\nexport function resolveGenerateTarget(opts: GenerateTargetFlags): IssueGenerateTarget | undefined {\n const selected: IssueGenerateTarget[] = [];\n if (opts.github === true) selected.push('github');\n if (opts.local === true) selected.push('local');\n if (opts.both === true) selected.push('both');\n\n if (selected.length > 1) {\n throw new IssueFlagError(\n `${selected.map((target) => `--${target}`).join(', ')} are mutually exclusive; ` +\n 'pass only one destination.',\n );\n }\n\n return selected[0];\n}\n"],"mappings":"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","names":["require"]}
1
+ {"version":3,"sources":["../src/cli.ts","../src/cli-options.ts","../src/issues/cli-flags.ts"],"sourcesContent":["import { createRequire } from 'node:module';\nimport { Command, InvalidArgumentError } from 'commander';\nimport {\n CliFlagError,\n resolveQueueScopeFlags,\n resolveRunPhaseFlags,\n resolveUserStoryNumberingFlags,\n} from './cli-options.js';\nimport { setIssuesCliOverrides, setWebCliOverrides } from './config.js';\nimport { setGlobalTimeout, setVerbose } from './core/verbose.js';\nimport {\n IssueFlagError,\n resolveGenerateTarget,\n resolveIssuesOverrides,\n} from './issues/cli-flags.js';\nimport type { IssueGenerateTarget } from './issues/types.js';\nimport type { WebConfig } from './schemas.js';\nimport { printError } from './ui/logger.js';\n\nconst require = createRequire(import.meta.url);\nconst { version } = require('../package.json') as { version: string };\n\n/**\n * Parse a numeric string, throwing InvalidArgumentError if not a valid number.\n */\nfunction parseInteger(value: string): number {\n const parsed = parseInt(value, 10);\n if (Number.isNaN(parsed) || parsed < 0) {\n throw new InvalidArgumentError('Must be a non-negative integer.');\n }\n return parsed;\n}\n\n/**\n * Parse `--start-us <n>`: a User Story number is 1-based, so 0 is rejected\n * along with everything `parseInteger` already rejects.\n */\nfunction parseStartUs(value: string): number {\n const parsed = parseInteger(value);\n if (parsed < 1) {\n throw new InvalidArgumentError('Must be a positive integer (US-001 is the first story).');\n }\n return parsed;\n}\n\n/**\n * Add the User Story numbering override options to a subcommand that\n * triggers `plan` (`run` and `plan` itself) — see issue #36.\n */\nfunction withUserStoryNumberingOptions(cmd: Command): Command {\n return cmd\n .option('--continue', 'Continue User Story numbering from the last used in this project')\n .option(\n '--start-us <n>',\n 'Force User Story numbering to start at a specific number, ignoring history',\n parseStartUs,\n );\n}\n\n/**\n * Add shared options (--verbose) to a subcommand.\n */\nfunction withGlobalOptions(cmd: Command): Command {\n return cmd\n .option('-v, --verbose', 'Show Claude progress output in real time')\n .option(\n '-t, --timeout <seconds>',\n 'Override headless timeout in seconds (0 = no limit)',\n parseInteger,\n );\n}\n\n/**\n * Add web monitoring options to a subcommand (run and execute only).\n * The values are resolved into CLI overrides by the preAction hook below;\n * loadWebConfig() applies the flag > env > file > defaults precedence.\n */\nfunction withWebOptions(cmd: Command): Command {\n return cmd\n .option('--web', 'Enable the web monitoring server')\n .option('--serve', 'Alias for --web')\n .option('--port <n>', 'Web server port (default: 3737)', parseInteger)\n .option('--host <h>', 'Web server host (default: 0.0.0.0)')\n .option('--refresh <s>', 'Suggested UI polling interval in seconds', parseInteger)\n .option('--web-log-limit <n>', 'Max log entries kept in the snapshot', parseInteger)\n .option('--web-no-logs', 'Exclude logs from the published snapshot');\n}\n\n/**\n * Add the Issue provider options to a subcommand.\n *\n * Declared once here (like withWebOptions) so no command repeats the flag\n * list; the preAction hook below turns them into config overrides, and\n * loadIssuesConfig() applies the flag > .issue-flow.json > defaults precedence.\n */\nfunction withIssueOptions(cmd: Command): Command {\n return cmd\n .option('--local', 'Prefer the local file Issue provider')\n .option('--github', 'Prefer the GitHub Issue provider')\n .option('--prefer-local', 'On divergence, use the local version without asking')\n .option('--prefer-github', 'On divergence, use the GitHub version without asking')\n .option('--ask', 'On divergence, ask which version to use (interactive only)');\n}\n\n/**\n * Extract the web-related CLI flags from a command's options, keeping only\n * the ones the user actually set.\n */\nfunction resolveWebOverrides(opts: Record<string, unknown>): Partial<WebConfig> {\n const overrides: Partial<WebConfig> = {};\n if (opts.web === true || opts.serve === true) {\n overrides.enabled = true;\n }\n if (opts.port !== undefined) {\n overrides.port = opts.port as number;\n }\n if (opts.host !== undefined) {\n overrides.host = opts.host as string;\n }\n if (opts.refresh !== undefined) {\n overrides.refreshSeconds = opts.refresh as number;\n }\n if (opts.webLogLimit !== undefined) {\n overrides.logLimit = opts.webLogLimit as number;\n }\n if (opts.webNoLogs === true) {\n overrides.includeLogs = false;\n }\n return overrides;\n}\n\nconst program = new Command();\n\nprogram\n .name('issue-flow')\n .description(\n 'Unified CLI for orchestrating the full issue-flow pipeline via Claude Code Headless.',\n )\n .version(version);\n\nprogram.hook('preAction', (_thisCommand, actionCommand) => {\n const opts = actionCommand.opts();\n if (opts.verbose) {\n setVerbose(true);\n }\n if (opts.timeout !== undefined) {\n setGlobalTimeout(opts.timeout * 1000);\n }\n setWebCliOverrides(resolveWebOverrides(opts));\n try {\n setIssuesCliOverrides(resolveIssuesOverrides(opts));\n } catch (error) {\n if (error instanceof IssueFlagError) {\n printError(error.message);\n process.exit(1);\n }\n throw error;\n }\n});\n\n// ── init ────────────────────────────────────────────────────────────────────\nwithIssueOptions(\n withGlobalOptions(\n program.command('init').description('Verify that all prerequisites (claude, gh, git) are met'),\n ),\n).action(async () => {\n const { loadIssuesConfig } = await import('./config.js');\n const { runInit } = await import('./commands/init.js');\n const { preferredProvider } = await loadIssuesConfig();\n const code = await runInit(preferredProvider);\n process.exit(code);\n});\n\n// ── generate ────────────────────────────────────────────────────────────────\nwithGlobalOptions(\n program\n .command('generate')\n .description('Draft an issue via Claude Code Headless and create it')\n .requiredOption('--prompt <text>', 'Issue description text')\n .option('--github', 'Create the issue on GitHub')\n .option('--local', 'Create the issue under issues/<n>/ only')\n .option('--both', 'Create the issue on GitHub and mirror it locally'),\n).action(async (options: { prompt: string; github?: boolean; local?: boolean; both?: boolean }) => {\n let target: IssueGenerateTarget | undefined;\n try {\n target = resolveGenerateTarget(options);\n } catch (error) {\n if (error instanceof IssueFlagError) {\n printError(error.message);\n process.exit(1);\n }\n throw error;\n }\n\n const { runGenerate } = await import('./commands/generate.js');\n const code = await runGenerate(options.prompt, target);\n process.exit(code);\n});\n\n// ── run ─────────────────────────────────────────────────────────────────────\nwithUserStoryNumberingOptions(\n withWebOptions(\n withIssueOptions(\n withGlobalOptions(\n program\n .command('run')\n .description(\n 'Execute the full pipeline: prd → plan → execute → review → pr (→ pr-review, optional)',\n )\n .argument('<issues...>', 'Issue number(s): 42, \"42,43\" or 42 43')\n .option('--mode <mode>', 'Execution mode: auto | manual', 'auto')\n .option('--from <phase>', 'Resume from a specific phase')\n .option(\n '--no-branch',\n 'Run pipeline on current branch without creating a new branch or PR',\n )\n .option('--pr-review', 'Review the created Pull Request after the pr phase')\n .option('-y, --yes', 'Run the whole discovered hierarchy without confirmation')\n .option('--only', 'Run just the issues informed, without their hierarchy'),\n ),\n ),\n ),\n).action(\n async (\n issues: string[],\n options: {\n mode: string;\n from?: string;\n branch?: boolean;\n prReview?: boolean;\n yes?: boolean;\n only?: boolean;\n continue?: boolean;\n startUs?: number;\n },\n ) => {\n let phases: ReturnType<typeof resolveRunPhaseFlags>;\n let scope: ReturnType<typeof resolveQueueScopeFlags>;\n let numbering: ReturnType<typeof resolveUserStoryNumberingFlags>;\n try {\n phases = resolveRunPhaseFlags(options);\n scope = resolveQueueScopeFlags(options);\n numbering = resolveUserStoryNumberingFlags(options);\n } catch (error) {\n if (error instanceof CliFlagError) {\n printError(error.message);\n process.exit(1);\n }\n throw error;\n }\n\n const { runPipeline } = await import('./commands/run.js');\n const code = await runPipeline(\n issues,\n options.mode,\n options.from,\n phases.noBranch,\n phases.prReview,\n {\n yes: scope.yes,\n only: scope.only,\n continueNumbering: numbering.continueFlag,\n startUs: numbering.startUs,\n },\n );\n process.exit(code);\n },\n);\n\n// ── analyze ─────────────────────────────────────────────────────────────────\nwithIssueOptions(\n withGlobalOptions(\n program\n .command('analyze')\n .description('Analyze an issue via Claude Code Headless')\n .argument('<issue>', 'Issue number'),\n ),\n).action(async (issue: string) => {\n const { runAnalyze } = await import('./commands/analyze.js');\n const code = await runAnalyze(issue);\n process.exit(code);\n});\n\n// ── prd ─────────────────────────────────────────────────────────────────────\nwithIssueOptions(\n withGlobalOptions(\n program\n .command('prd')\n .description('Generate a PRD from an analyzed issue via Claude Code Headless')\n .argument('<issue>', 'Issue number'),\n ),\n).action(async (issue: string) => {\n const { runPrd } = await import('./commands/prd.js');\n const code = await runPrd(issue);\n process.exit(code);\n});\n\n// ── plan ────────────────────────────────────────────────────────────────────\nwithUserStoryNumberingOptions(\n withIssueOptions(\n withGlobalOptions(\n program\n .command('plan')\n .description('Convert a PRD to a tasks.json task plan via Claude Code Headless')\n .argument('<issue>', 'Issue number'),\n ),\n ),\n).action(async (issue: string, options: { continue?: boolean; startUs?: number }) => {\n let numbering: ReturnType<typeof resolveUserStoryNumberingFlags>;\n try {\n numbering = resolveUserStoryNumberingFlags(options);\n } catch (error) {\n if (error instanceof CliFlagError) {\n printError(error.message);\n process.exit(1);\n }\n throw error;\n }\n\n const { runPlan } = await import('./commands/plan.js');\n const code = await runPlan(issue, undefined, numbering);\n process.exit(code);\n});\n\n// ── execute ─────────────────────────────────────────────────────────────────\nwithWebOptions(\n withGlobalOptions(\n program\n .command('execute')\n .description('Run the iterative story execution loop (issue-flow engine)')\n .option('--issue <number>', 'Issue number — reads artifacts from issues/N/')\n .option('--max-iterations <number>', 'Stop after N iterations', parseInteger)\n .option(\n '--retry-limit <number>',\n 'Retry transient Claude failures up to N consecutive times',\n parseInteger,\n )\n .option('--retry-forever', 'Retry transient Claude failures indefinitely')\n .argument(\n '[max-iterations]',\n 'Backward-compatible alias for --max-iterations N',\n parseInteger,\n ),\n ),\n).action(\n async (\n positionalMaxIter: number | undefined,\n options: {\n issue?: string;\n maxIterations?: number;\n retryLimit?: number;\n retryForever?: boolean;\n },\n ) => {\n try {\n const { runExecute } = await import('./commands/execute.js');\n const code = await runExecute(positionalMaxIter, options);\n process.exit(code);\n } catch (error) {\n printError(error instanceof Error ? error.message : String(error));\n process.exit(1);\n }\n },\n);\n\n// ── review ──────────────────────────────────────────────────────────────────\nwithIssueOptions(\n withGlobalOptions(\n program\n .command('review')\n .description('Validate an issue resolution via Claude Code Headless')\n .argument('<issue>', 'Issue number'),\n ),\n).action(async (issue: string) => {\n const { runReview } = await import('./commands/review.js');\n const code = await runReview(issue);\n process.exit(code);\n});\n\n// ── pr ──────────────────────────────────────────────────────────────────────\nwithIssueOptions(\n withGlobalOptions(\n program\n .command('pr')\n .description('Create a pull request via Claude Code Headless')\n .argument('<issue>', 'Issue number'),\n ),\n).action(async (issue: string) => {\n const { runPr } = await import('./commands/pr.js');\n const code = await runPr(issue);\n process.exit(code);\n});\n\n// ── web ─────────────────────────────────────────────────────────────────────\nconst webCommand = program.command('web').description('Manage the web monitoring server');\n\nwebCommand\n .command('serve')\n .description(\n 'Run the web monitor server in the foreground (internal — spawned detached by --web)',\n )\n .option('--port <n>', 'Web server port (default: 3737)', parseInteger)\n .option('--host <h>', 'Web server host (default: 0.0.0.0)')\n .option('--refresh <s>', 'Suggested UI polling interval in seconds', parseInteger)\n .action(async (options: { port?: number; host?: string; refresh?: number }) => {\n const { runWebServe } = await import('./commands/web.js');\n const code = await runWebServe(options);\n // On success this process stays alive for as long as the server is bound\n // (server.ts binds it with unref: false) — only a failure exits here.\n if (code !== 0) {\n process.exit(code);\n }\n });\n\nwebCommand\n .command('stop')\n .description('Stop the running web monitor server')\n .action(async () => {\n const { runWebStop } = await import('./commands/web.js');\n const code = await runWebStop();\n process.exit(code);\n });\n\n// ── policy ──────────────────────────────────────────────────────────────\nwithGlobalOptions(\n program\n .command('policy')\n .description('Inspect the policies discovered in this repository and their provenance')\n .option('--scope <dir>', 'Resolve the policy for a subdirectory (monorepo)')\n .option('--json', 'Emit the resolved policy as JSON'),\n).action(async (options: { scope?: string; json?: boolean }) => {\n const { runPolicy } = await import('./commands/policy.js');\n const code = await runPolicy(options);\n process.exit(code);\n});\n\n// ── pr-review ───────────────────────────────────────────────────────────────\nwithGlobalOptions(\n program\n .command('pr-review')\n .description('Review a Pull Request as a whole via Claude Code Headless')\n .argument('[pr]', 'Pull Request number (discovered from the session when omitted)')\n .option('--issue <n>', 'Issue the Pull Request belongs to')\n .option('--round <n>', 'Rewrite a specific review round instead of appending a new one')\n .option('--yes', 'Skip the confirmation of the discovered Pull Request')\n .option(\n '--fail-on <level>',\n 'Verdict that fails the command: request-changes | suggestions | none',\n ),\n).action(\n async (\n pr: string | undefined,\n options: { issue?: string; round?: string; yes?: boolean; failOn?: string },\n ) => {\n const { runPrReview } = await import('./commands/pr-review.js');\n const code = await runPrReview(pr, options);\n process.exit(code);\n },\n);\n\nprogram.parse();\n","/**\n * Pure mappings from parsed CLI options to command arguments.\n *\n * They live outside cli.ts because that module runs `program.parse()` on\n * import, so anything defined there is untestable.\n */\n\n/** Shape commander produces for a negated boolean option (`--no-branch`). */\nexport interface BranchFlagOptions {\n branch?: boolean;\n}\n\n/**\n * Translate commander's `--no-branch` into the tri-state `runPipeline` expects.\n *\n * Commander stores a negated option under its positive name: `--no-branch`\n * yields `{ branch: false }` and never a `noBranch` key. Reading `noBranch`\n * silently dropped the flag, so the pipeline always created a branch and opened\n * a PR.\n *\n * `undefined` (flag absent) is preserved on purpose: `runPipeline` uses it to\n * tell \"the user said nothing, keep the mode persisted in tasks.json\" apart\n * from an explicit choice.\n */\nexport function resolveNoBranch(options: BranchFlagOptions): boolean | undefined {\n return options.branch === false ? true : undefined;\n}\n\n/** Invalid flag combination on a command, reported as a CLI error. */\nexport class CliFlagError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'CliFlagError';\n }\n}\n\n/** Options `run` accepts that select which phases the pipeline executes. */\nexport interface RunPhaseFlagOptions extends BranchFlagOptions {\n prReview?: boolean;\n}\n\n/** Phase selection `runPipeline` expects, both entries tri-state. */\nexport interface RunPhaseFlags {\n noBranch: boolean | undefined;\n prReview: boolean | undefined;\n}\n\n/** Scope flags of `run`, deciding what a discovered hierarchy does. */\nexport interface QueueScopeFlagOptions {\n yes?: boolean;\n only?: boolean;\n}\n\n/** Resolved scope answer; both absent means \"ask, when the terminal allows it\". */\nexport interface QueueScopeFlags {\n yes: boolean | undefined;\n only: boolean | undefined;\n}\n\n/**\n * Resolve `--yes` / `--only`, rejecting the combination that has no meaning.\n *\n * They answer the same question in opposite ways — run everything that was\n * discovered, or run only what was asked for — so passing both is a mistake\n * worth naming rather than silently resolving by precedence.\n *\n * @throws CliFlagError when both are present.\n */\nexport function resolveQueueScopeFlags(options: QueueScopeFlagOptions): QueueScopeFlags {\n if (options.yes === true && options.only === true) {\n throw new CliFlagError(\n '--yes cannot be combined with --only: --yes runs the whole discovered hierarchy, ' +\n '--only runs just the issues you informed.',\n );\n }\n\n return {\n yes: options.yes === true ? true : undefined,\n only: options.only === true ? true : undefined,\n };\n}\n\n/**\n * Resolve the phase-selection flags of `run`, rejecting the one combination\n * that cannot work.\n *\n * `--no-branch` skips the `pr` phase, so there is no Pull Request for\n * `--pr-review` to review. Failing here — before any phase runs — beats\n * discovering it at the end of a full pipeline.\n *\n * Both values stay `undefined` when the user passed nothing: `runPipeline`\n * uses that to tell \"no opinion, keep what tasks.json persisted\" apart from an\n * explicit choice.\n *\n * @throws CliFlagError when `--pr-review` is combined with `--no-branch`.\n */\nexport function resolveRunPhaseFlags(options: RunPhaseFlagOptions): RunPhaseFlags {\n const noBranch = resolveNoBranch(options);\n const prReview = options.prReview === true ? true : undefined;\n\n if (prReview === true && noBranch === true) {\n throw new CliFlagError(\n '--pr-review cannot be combined with --no-branch: --no-branch skips the pr phase, ' +\n 'so there is no Pull Request to review.',\n );\n }\n\n return { noBranch, prReview };\n}\n\n/** Options `run` and `plan` accept to override User Story numbering (issue #36). */\nexport interface UserStoryNumberingFlagOptions {\n continue?: boolean;\n startUs?: number;\n}\n\n/** Resolved User Story numbering override, ready for `determineUserStoryNumbering()`. */\nexport interface UserStoryNumberingFlags {\n continueFlag: boolean;\n startUs: number | undefined;\n}\n\n/**\n * Resolve the `--continue` / `--start-us <n>` override of the User Story\n * numbering cascade (issue #36), rejecting the one combination that cannot\n * work: the two disagree about whether history should be consulted at all.\n *\n * @throws CliFlagError when both flags are passed together.\n */\nexport function resolveUserStoryNumberingFlags(\n options: UserStoryNumberingFlagOptions,\n): UserStoryNumberingFlags {\n const continueFlag = options.continue === true;\n const startUs = options.startUs;\n\n if (continueFlag && startUs !== undefined) {\n throw new CliFlagError(\n '--continue and --start-us cannot be combined: choose either the last known numbering ' +\n '(--continue) or an explicit override (--start-us).',\n );\n }\n\n return { continueFlag, startUs };\n}\n","import type { IssueGenerateTarget, IssuesConfig } from './types.js';\n\n/**\n * Mapping from the Issue provider CLI flags to configuration overrides.\n *\n * Kept out of cli.ts (which runs `program.parse()` on import) so the\n * precedence rules stay unit-testable. Only the keys the user actually passed\n * are returned: a key present with `undefined` would override .issue-flow.json\n * when the layers are merged.\n */\n\nexport interface IssueCliFlags {\n local?: boolean;\n github?: boolean;\n preferLocal?: boolean;\n preferGithub?: boolean;\n ask?: boolean;\n}\n\n/** Invalid flag combination, reported to the user as a CLI error. */\nexport class IssueFlagError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'IssueFlagError';\n }\n}\n\n/**\n * Translate the parsed options of a command into `IssuesConfig` overrides.\n *\n * @throws IssueFlagError when mutually exclusive flags are combined.\n */\nexport function resolveIssuesOverrides(opts: IssueCliFlags): Partial<IssuesConfig> {\n const overrides: Partial<IssuesConfig> = {};\n\n if (opts.local === true && opts.github === true) {\n throw new IssueFlagError('--local and --github are mutually exclusive; pass only one.');\n }\n if (opts.local === true) {\n overrides.preferredProvider = 'local';\n }\n if (opts.github === true) {\n overrides.preferredProvider = 'github';\n }\n\n const conflictFlags = [\n opts.preferLocal === true ? '--prefer-local' : null,\n opts.preferGithub === true ? '--prefer-github' : null,\n opts.ask === true ? '--ask' : null,\n ].filter((flag): flag is string => flag !== null);\n\n if (conflictFlags.length > 1) {\n throw new IssueFlagError(\n `${conflictFlags.join(', ')} are mutually exclusive; pass only one conflict policy.`,\n );\n }\n if (opts.preferLocal === true) {\n overrides.conflictPolicy = 'prefer-local';\n }\n if (opts.preferGithub === true) {\n overrides.conflictPolicy = 'prefer-github';\n }\n if (opts.ask === true) {\n overrides.conflictPolicy = 'ask';\n }\n\n return overrides;\n}\n\n/** Destination flags accepted by `generate`. */\nexport interface GenerateTargetFlags {\n github?: boolean;\n local?: boolean;\n both?: boolean;\n}\n\n/**\n * Destination the generated Issue is created in, or `undefined` when the user\n * passed no flag (the caller then falls back to `defaultGenerateTarget`).\n *\n * @throws IssueFlagError when more than one destination is requested.\n */\nexport function resolveGenerateTarget(opts: GenerateTargetFlags): IssueGenerateTarget | undefined {\n const selected: IssueGenerateTarget[] = [];\n if (opts.github === true) selected.push('github');\n if (opts.local === true) selected.push('local');\n if (opts.both === true) selected.push('both');\n\n if (selected.length > 1) {\n throw new IssueFlagError(\n `${selected.map((target) => `--${target}`).join(', ')} are mutually exclusive; ` +\n 'pass only one destination.',\n );\n }\n\n return selected[0];\n}\n"],"mappings":"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","names":["require"]}
@@ -8,15 +8,17 @@ import {
8
8
  getInstallHint,
9
9
  loadGlobalConfig,
10
10
  loadIssuesConfig,
11
+ loadPolicyConfig,
11
12
  loadPrReviewConfig,
12
13
  loadWebConfig,
13
14
  mergeConfigLayers,
14
15
  resolvePaths,
15
16
  setIssuesCliOverrides,
17
+ setPolicyCliOverrides,
16
18
  setWebCliOverrides,
17
19
  validateDependencies
18
- } from "./chunk-MTJQOOMG.js";
19
- import "./chunk-57MH6RYJ.js";
20
+ } from "./chunk-VYFKOUUR.js";
21
+ import "./chunk-V5OHFSW4.js";
20
22
  export {
21
23
  DEFAULTS,
22
24
  GLOBAL_CONFIG_FILENAME,
@@ -26,12 +28,14 @@ export {
26
28
  getInstallHint,
27
29
  loadGlobalConfig,
28
30
  loadIssuesConfig,
31
+ loadPolicyConfig,
29
32
  loadPrReviewConfig,
30
33
  loadWebConfig,
31
34
  mergeConfigLayers,
32
35
  resolvePaths,
33
36
  setIssuesCliOverrides,
37
+ setPolicyCliOverrides,
34
38
  setWebCliOverrides,
35
39
  validateDependencies
36
40
  };
37
- //# sourceMappingURL=config-J5DW6HSY.js.map
41
+ //# sourceMappingURL=config-YCF3HIKR.js.map
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ runExecute
4
+ } from "./chunk-67ZO5ROQ.js";
5
+ import "./chunk-HDV3YDTG.js";
6
+ import "./chunk-VOKNTBOO.js";
7
+ import "./chunk-PPLIXZI5.js";
8
+ import "./chunk-SLHZPB3C.js";
9
+ import "./chunk-VYFKOUUR.js";
10
+ import "./chunk-V5OHFSW4.js";
11
+ export {
12
+ runExecute
13
+ };
14
+ //# sourceMappingURL=execute-QYXA225W.js.map