issue-flow 0.9.0 → 0.11.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 (100) hide show
  1. package/dist/{analyze-VSWAEVN3.js → analyze-7B5TORNK.js} +13 -8
  2. package/dist/analyze-7B5TORNK.js.map +1 -0
  3. package/dist/apply-WUJHDD7A.js +788 -0
  4. package/dist/apply-WUJHDD7A.js.map +1 -0
  5. package/dist/{chunk-NB6WKTBL.js → chunk-3BOHTH4V.js} +2 -2
  6. package/dist/chunk-3D4A4B46.js +2 -0
  7. package/dist/chunk-47VIN33V.js +983 -0
  8. package/dist/chunk-47VIN33V.js.map +1 -0
  9. package/dist/{chunk-DP43WJY4.js → chunk-6N7LZZ5U.js} +19 -13
  10. package/dist/chunk-6N7LZZ5U.js.map +1 -0
  11. package/dist/{chunk-KXAG24DD.js → chunk-AAY6KM7N.js} +6 -3
  12. package/dist/chunk-AAY6KM7N.js.map +1 -0
  13. package/dist/{chunk-VNKSMWDN.js → chunk-GANSDJV7.js} +4 -4
  14. package/dist/{chunk-57MH6RYJ.js → chunk-HC2MT7BX.js} +249 -181
  15. package/dist/chunk-HC2MT7BX.js.map +1 -0
  16. package/dist/chunk-HQYXVIH6.js +110 -0
  17. package/dist/chunk-HQYXVIH6.js.map +1 -0
  18. package/dist/{chunk-66ZGYXTH.js → chunk-JW7WLSZ6.js} +15 -11
  19. package/dist/chunk-JW7WLSZ6.js.map +1 -0
  20. package/dist/{chunk-M37YZYDO.js → chunk-LVKQ2AKT.js} +3 -3
  21. package/dist/{chunk-TLSIK656.js → chunk-MKD2D7WE.js} +12 -8
  22. package/dist/chunk-MKD2D7WE.js.map +1 -0
  23. package/dist/{chunk-WHEU55ZP.js → chunk-MSPUB635.js} +15 -11
  24. package/dist/chunk-MSPUB635.js.map +1 -0
  25. package/dist/chunk-OBLVLIUS.js +285 -0
  26. package/dist/chunk-OBLVLIUS.js.map +1 -0
  27. package/dist/{chunk-JGKPSWOA.js → chunk-PL6CNMVS.js} +2 -2
  28. package/dist/chunk-RKTEV6EG.js +246 -0
  29. package/dist/chunk-RKTEV6EG.js.map +1 -0
  30. package/dist/{chunk-TJMZWRS6.js → chunk-UBIQXCVE.js} +15 -11
  31. package/dist/chunk-UBIQXCVE.js.map +1 -0
  32. package/dist/{chunk-X7XBF5TS.js → chunk-VCKX262Z.js} +12 -8
  33. package/dist/chunk-VCKX262Z.js.map +1 -0
  34. package/dist/{chunk-MTJQOOMG.js → chunk-VQ5VN7DF.js} +123 -4
  35. package/dist/chunk-VQ5VN7DF.js.map +1 -0
  36. package/dist/cli.js +32 -21
  37. package/dist/cli.js.map +1 -1
  38. package/dist/{config-J5DW6HSY.js → config-4THHOXNN.js} +7 -3
  39. package/dist/execute-5OEKLM5X.js +15 -0
  40. package/dist/{generate-ERDPOTKX.js → generate-6RA2MFJB.js} +99 -12
  41. package/dist/generate-6RA2MFJB.js.map +1 -0
  42. package/dist/init-PRMOTQ7N.js +9 -0
  43. package/dist/plan-KENR6MJ3.js +17 -0
  44. package/dist/policy-XQW5NUIE.js +117 -0
  45. package/dist/policy-XQW5NUIE.js.map +1 -0
  46. package/dist/pr-KDGKG4BP.js +21 -0
  47. package/dist/pr-review-CAMAB7KO.js +17 -0
  48. package/dist/prd-V3MWDIHK.js +17 -0
  49. package/dist/review-7LN47SAN.js +15 -0
  50. package/dist/review-7LN47SAN.js.map +1 -0
  51. package/dist/{run-BRBFHVUO.js → run-H75MD5BQ.js} +26 -25
  52. package/dist/{run-BRBFHVUO.js.map → run-H75MD5BQ.js.map} +1 -1
  53. package/dist/{web-WQISLO66.js → web-ANQQTTRD.js} +5 -5
  54. package/package.json +1 -1
  55. package/prompts/analyze.md +24 -0
  56. package/prompts/execute.md +29 -1
  57. package/prompts/generate.md +61 -7
  58. package/prompts/plan.md +24 -1
  59. package/prompts/pr-review.md +65 -4
  60. package/prompts/pr.md +37 -3
  61. package/prompts/prd.md +17 -0
  62. package/prompts/review.md +40 -0
  63. package/dist/analyze-VSWAEVN3.js.map +0 -1
  64. package/dist/chunk-57MH6RYJ.js.map +0 -1
  65. package/dist/chunk-66ZGYXTH.js.map +0 -1
  66. package/dist/chunk-DP43WJY4.js.map +0 -1
  67. package/dist/chunk-DTNM3LTO.js +0 -101
  68. package/dist/chunk-DTNM3LTO.js.map +0 -1
  69. package/dist/chunk-KXAG24DD.js.map +0 -1
  70. package/dist/chunk-M72ZEPWD.js +0 -135
  71. package/dist/chunk-M72ZEPWD.js.map +0 -1
  72. package/dist/chunk-MTJQOOMG.js.map +0 -1
  73. package/dist/chunk-SLHZPB3C.js +0 -50
  74. package/dist/chunk-SLHZPB3C.js.map +0 -1
  75. package/dist/chunk-TJMZWRS6.js.map +0 -1
  76. package/dist/chunk-TLSIK656.js.map +0 -1
  77. package/dist/chunk-WHEU55ZP.js.map +0 -1
  78. package/dist/chunk-X7XBF5TS.js.map +0 -1
  79. package/dist/execute-Z2DKGVLR.js +0 -14
  80. package/dist/generate-ERDPOTKX.js.map +0 -1
  81. package/dist/init-UY6ZCIFT.js +0 -9
  82. package/dist/plan-REMAMCX4.js +0 -16
  83. package/dist/pr-KERP52TI.js +0 -20
  84. package/dist/pr-review-IU4N3TEK.js +0 -16
  85. package/dist/prd-SHVQW6RM.js +0 -16
  86. package/dist/review-L5F77QAC.js +0 -14
  87. /package/dist/{chunk-NB6WKTBL.js.map → chunk-3BOHTH4V.js.map} +0 -0
  88. /package/dist/{config-J5DW6HSY.js.map → chunk-3D4A4B46.js.map} +0 -0
  89. /package/dist/{chunk-VNKSMWDN.js.map → chunk-GANSDJV7.js.map} +0 -0
  90. /package/dist/{chunk-M37YZYDO.js.map → chunk-LVKQ2AKT.js.map} +0 -0
  91. /package/dist/{chunk-JGKPSWOA.js.map → chunk-PL6CNMVS.js.map} +0 -0
  92. /package/dist/{execute-Z2DKGVLR.js.map → config-4THHOXNN.js.map} +0 -0
  93. /package/dist/{init-UY6ZCIFT.js.map → execute-5OEKLM5X.js.map} +0 -0
  94. /package/dist/{plan-REMAMCX4.js.map → init-PRMOTQ7N.js.map} +0 -0
  95. /package/dist/{pr-KERP52TI.js.map → plan-KENR6MJ3.js.map} +0 -0
  96. /package/dist/{pr-review-IU4N3TEK.js.map → pr-KDGKG4BP.js.map} +0 -0
  97. /package/dist/{prd-SHVQW6RM.js.map → pr-review-CAMAB7KO.js.map} +0 -0
  98. /package/dist/{review-L5F77QAC.js.map → prd-V3MWDIHK.js.map} +0 -0
  99. /package/dist/{web-WQISLO66.js.map → web-ANQQTTRD.js.map} +0 -0
  100. /package/web/{CLAUDE.md → AGENTS.md} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/policy/resolve.ts","../src/policy/discovery.ts","../src/policy/parsers/docs.ts","../src/policy/parsers/labels.ts","../src/policy/parsers/templates.ts","../src/policy/parsers/issue-forms.ts","../src/policy/types.ts"],"sourcesContent":["import { isAbsolute, normalize, sep } from 'node:path';\nimport { loadPolicyConfig, mergeConfigLayers } from '../config.js';\nimport type { PolicyConfig, PolicyConfigInput } from '../schemas.js';\nimport { printWarning } from '../ui/logger.js';\nimport { getProjectRoot } from '../utils/git.js';\nimport {\n discoverBaseBranch,\n discoverCodeowners,\n discoverDocuments,\n discoverGitHubSlug,\n discoverIssueTemplates,\n discoverIssueTypes,\n discoverLabels,\n discoverOrganizationForms,\n discoverOrganizationTemplates,\n discoverPullRequestTemplates,\n} from './discovery.js';\nimport {\n POLICY_SCHEMA_VERSION,\n type PolicyExec,\n type PolicyGit,\n type PolicyIssues,\n type PolicyPullRequests,\n type PolicySource,\n type RepositoryPolicy,\n} from './types.js';\n\n/**\n * Resolution of the repository policy: discovery, precedence, scope and cache.\n */\n\nexport interface LoadRepositoryPolicyOptions {\n /** Repository root. Defaults to the git project root. */\n root?: string;\n /** Subdirectory the policy applies to, for monorepos. Root when omitted. */\n scope?: string | null;\n /** CLI overrides of the `policy` key. */\n cli?: PolicyConfigInput;\n /** Environment source. Defaults to process.env. */\n env?: NodeJS.ProcessEnv;\n /** Warning sink. Defaults to printWarning. */\n warn?: (message: string) => void;\n /** External command seam, for tests. Defaults to `utils/shell.ts`'s `run`. */\n exec?: PolicyExec;\n /** Set to false to bypass the per-process cache. Defaults to true. */\n cache?: boolean;\n}\n\n/**\n * Normalize a scope into a repository-relative POSIX path, or null.\n *\n * An absolute path, a path escaping the root and `.` all mean \"the root\": a\n * scope is a hint about where in a monorepo the work is happening, never a way\n * to read outside the repository.\n */\nexport function normalizeScope(scope: string | null | undefined): string | null {\n if (scope === null || scope === undefined) return null;\n\n const trimmed = scope.trim();\n if (trimmed === '' || trimmed === '.' || isAbsolute(trimmed)) return null;\n\n const normalized = normalize(trimmed)\n .split(sep)\n .join('/')\n .replace(/^\\.\\//, '')\n .replace(/\\/+$/, '');\n if (normalized === '' || normalized === '.' || normalized.startsWith('..')) return null;\n\n return normalized;\n}\n\n/** An enabled policy with nothing in it — also the shape of a disabled one. */\nfunction emptyPolicy(\n root: string,\n scope: string | null,\n enabled: boolean,\n sources: PolicySource[],\n): RepositoryPolicy {\n return {\n schemaVersion: POLICY_SCHEMA_VERSION,\n root,\n scope,\n enabled,\n issues: { templates: [], types: [], labels: [], titleConvention: null },\n pullRequests: { template: null, templates: [], baseBranch: null, titleConvention: null },\n git: { branchConvention: null, commitConvention: null },\n docs: [],\n codeowners: null,\n sources,\n };\n}\n\n/** Provenance entry for a discovery pass the configuration turned off. */\nfunction disabledSource(kind: PolicySource['kind']): PolicySource {\n return {\n kind,\n origin: 'config',\n path: null,\n status: 'disabled',\n detail: 'disabled by configuration',\n };\n}\n\n/** Which `sources` entry a declared configuration key is accounted under. */\nconst DECLARATION_KINDS = {\n issues: 'issue-templates',\n pullRequests: 'pull-request-template',\n git: 'base-branch',\n} as const satisfies Record<string, PolicySource['kind']>;\n\n/**\n * Resolve the policy of `root` for `scope`, uncached.\n *\n * The ladder, exactly as `mergeConfigLayers` applies it:\n *\n * Issue Flow defaults < discovered repository policies\n * < \"policy\" key of .issue-flow.json < ISSUE_FLOW_POLICY_* < CLI flags\n *\n * The three explicit layers are already collapsed by `loadPolicyConfig()`, so\n * what arrives here is \"the defaults\" and \"what the user declared\", with the\n * discovery in between. A declaration therefore corrects a wrong discovery\n * without having to disable it, and `discovery.*: false` turns a whole pass off\n * for the case where the discovery is not merely wrong but unwanted.\n */\nasync function resolvePolicy(\n root: string,\n scope: string | null,\n options: LoadRepositoryPolicyOptions,\n): Promise<RepositoryPolicy> {\n const warn = options.warn ?? printWarning;\n const config: PolicyConfig = await loadPolicyConfig({\n projectRoot: root,\n env: options.env,\n cli: options.cli,\n warn,\n });\n\n if (!config.enabled) {\n // Not a single stat() or round-trip: \"off\" has to be free, otherwise it is\n // not really a way out of a discovery that is misbehaving.\n return emptyPolicy(root, scope, false, [\n { kind: 'docs', origin: 'config', path: null, status: 'disabled', detail: 'policy.enabled' },\n ]);\n }\n\n const toggles = config.discovery;\n\n const [templateDiscovery, prTemplates, documents, codeowners] = await Promise.all([\n toggles.issueTemplates\n ? discoverIssueTemplates(root)\n : Promise.resolve({ templates: [], sources: [disabledSource('issue-templates')] }),\n toggles.pullRequestTemplate\n ? discoverPullRequestTemplates(root)\n : Promise.resolve({ templates: [], sources: [disabledSource('pull-request-template')] }),\n toggles.docs\n ? discoverDocuments(root, scope)\n : Promise.resolve({ documents: [], sources: [disabledSource('docs')] }),\n toggles.codeowners\n ? discoverCodeowners(root)\n : Promise.resolve({ content: null, sources: [disabledSource('codeowners')] }),\n ]);\n\n // The organization defaults are only worth asking for when the local tree has\n // none — that is precisely the case local discovery cannot see.\n const wantsOrgTemplates = toggles.issueTemplates && templateDiscovery.templates.length === 0;\n const [baseBranch, slug] = await Promise.all([\n discoverBaseBranch(root, options.exec),\n toggles.issueTypes || wantsOrgTemplates\n ? discoverGitHubSlug(root, options.exec)\n : Promise.resolve(null),\n ]);\n\n const [labels, issueTypes, orgTemplates, orgForms] = await Promise.all([\n toggles.labels\n ? discoverLabels(root, options.exec)\n : Promise.resolve({ labels: [], sources: [disabledSource('labels')] }),\n toggles.issueTypes\n ? discoverIssueTypes(root, slug?.owner ?? null, options.exec)\n : Promise.resolve({ types: [], sources: [disabledSource('issue-types')] }),\n wantsOrgTemplates\n ? discoverOrganizationTemplates(root, slug, options.exec)\n : Promise.resolve({ templates: [], sources: [] }),\n // Two calls, because GitHub answers the two kinds of organization default\n // through different surfaces: `issueTemplates` covers markdown templates and\n // is blind to Issue Forms, which only exist as files in the org's `.github`\n // repository. A repository whose organization publishes forms would\n // otherwise look like one with no templates at all.\n wantsOrgTemplates\n ? discoverOrganizationForms(root, slug?.owner ?? null, options.exec)\n : Promise.resolve({ templates: [], sources: [] }),\n ]);\n\n const sources: PolicySource[] = [\n ...templateDiscovery.sources,\n ...orgTemplates.sources,\n ...orgForms.sources,\n ...issueTypes.sources,\n ...labels.sources,\n ...prTemplates.sources,\n ...baseBranch.sources,\n ...documents.sources,\n ...codeowners.sources,\n ];\n\n // Only the base branch is inferable today; the conventions have no reliable\n // signal in a repository and are therefore declared, never guessed.\n const discoveredPullRequests: Partial<PolicyPullRequests> = {};\n if (baseBranch.baseBranch !== null) {\n discoveredPullRequests.baseBranch = baseBranch.baseBranch;\n }\n\n const issues = mergeConfigLayers<PolicyIssues>({\n defaults: { titleConvention: null },\n project: config.issues,\n });\n const pullRequests = mergeConfigLayers<PolicyPullRequests>({\n defaults: { baseBranch: null, titleConvention: null },\n discovered: discoveredPullRequests,\n project: config.pullRequests,\n });\n const git = mergeConfigLayers<PolicyGit>({\n defaults: { branchConvention: null, commitConvention: null },\n project: config.git,\n });\n\n for (const group of ['issues', 'pullRequests', 'git'] as const) {\n for (const key of Object.keys(config[group])) {\n sources.push({\n kind: DECLARATION_KINDS[group],\n origin: 'config',\n path: null,\n status: 'found',\n detail: `${group}.${key} declared`,\n });\n }\n }\n\n return {\n schemaVersion: POLICY_SCHEMA_VERSION,\n root,\n scope,\n enabled: true,\n issues: {\n templates: [...templateDiscovery.templates, ...orgTemplates.templates, ...orgForms.templates],\n types: issueTypes.types,\n labels: labels.labels,\n titleConvention: issues.titleConvention ?? null,\n },\n pullRequests: {\n template: prTemplates.templates[0]?.content ?? null,\n templates: prTemplates.templates,\n baseBranch: pullRequests.baseBranch ?? null,\n titleConvention: pullRequests.titleConvention ?? null,\n },\n git: {\n branchConvention: git.branchConvention ?? null,\n commitConvention: git.commitConvention ?? null,\n },\n docs: documents.documents,\n codeowners: codeowners.content,\n sources,\n };\n}\n\n/**\n * Per-process cache, keyed by `(root, scope)`.\n *\n * Discovery walks the filesystem and may talk to `gh`; a pipeline that resolved\n * it once per phase would pay that several times over for an answer that cannot\n * change mid-run. The promise — not its result — is cached, so two concurrent\n * callers share a single discovery instead of racing two.\n */\nconst cache = new Map<string, Promise<RepositoryPolicy>>();\n\n/** Drop every cached resolution. Exported for tests and long-lived processes. */\nexport function resetPolicyCache(): void {\n cache.clear();\n}\n\n/**\n * The repository policy applying to `scope`, resolved once per process.\n *\n * Never throws for a repository that declares nothing: the result is an empty\n * policy, with no warning, and every consumer keeps its own defaults. The only\n * error it can raise is the one `getProjectRoot()` already raises when there is\n * no repository at all — and passing `root` avoids even that.\n */\nexport async function loadRepositoryPolicy(\n options: LoadRepositoryPolicyOptions = {},\n): Promise<RepositoryPolicy> {\n const root = options.root ?? (await getProjectRoot());\n const scope = normalizeScope(options.scope);\n\n if (options.cache === false) {\n return resolvePolicy(root, scope, options);\n }\n\n const key = `${root}\u0000${scope ?? ''}`;\n const cached = cache.get(key);\n if (cached !== undefined) {\n return cached;\n }\n\n const pending = resolvePolicy(root, scope, options);\n cache.set(key, pending);\n try {\n return await pending;\n } catch (err) {\n // A rejected promise must not be served to every later caller.\n cache.delete(key);\n throw err;\n }\n}\n","import { readdir, readFile, stat } from 'node:fs/promises';\nimport { join, relative, sep } from 'node:path';\nimport { run } from '../utils/shell.js';\nimport { classifyDocument, extractReferencedDocuments } from './parsers/docs.js';\nimport { parseIssueTypes, parseLabels } from './parsers/labels.js';\nimport {\n parseIssueTemplateFile,\n parseOrganizationForms,\n parseOrganizationTemplates,\n} from './parsers/templates.js';\nimport {\n type IssueTemplate,\n type LabelDefinition,\n MAX_POLICY_DOCUMENT_BYTES,\n type PolicyDocument,\n type PolicyExec,\n type PolicySource,\n type PullRequestTemplate,\n} from './types.js';\n\n/**\n * Locating the policy sources of a repository on disk and, when `gh` can\n * answer, on GitHub.\n *\n * Every function here is best-effort by construction: an unreadable directory,\n * a missing binary, a network that never answers and a repository that simply\n * declares nothing all produce the same thing — an empty result plus, when the\n * source *could* have answered, a `PolicySource` recording why it did not.\n * Discovery must never be able to break a flow that works offline today.\n */\n\n/**\n * Timeout of every `gh` and `git` invocation. Matches the `init` prerequisite\n * probes; the point is that no discovery call can hang a pipeline.\n */\nexport const DISCOVERY_TIMEOUT_MS = 10_000;\n\n/** The directories GitHub itself looks in, in the order it looks. */\nconst POLICY_DIRECTORIES = ['.github', '', 'docs'] as const;\n\nconst defaultExec: PolicyExec = (command, args, options) =>\n run(command, args, { cwd: options.cwd, timeout: options.timeout });\n\n/** Repository-relative path with POSIX separators, for stable output. */\nexport function toRelativePath(root: string, absolute: string): string {\n return relative(root, absolute).split(sep).join('/');\n}\n\n/** Directory entries, or [] when the directory is absent or unreadable. */\nasync function listDirectory(dir: string): Promise<string[]> {\n try {\n const entries = await readdir(dir, { withFileTypes: true });\n return entries.filter((entry) => !entry.isDirectory()).map((entry) => entry.name);\n } catch {\n return [];\n }\n}\n\nasync function isDirectory(path: string): Promise<boolean> {\n try {\n return (await stat(path)).isDirectory();\n } catch {\n return false;\n }\n}\n\n/**\n * Read a file, capped at {@link MAX_POLICY_DOCUMENT_BYTES}.\n *\n * Returns null when the file is absent or unreadable. The second element says\n * whether the content was truncated, so the caller can record it in `sources`\n * instead of silently handing a half document to an agent.\n */\nasync function readCapped(path: string): Promise<{ content: string; truncated: boolean } | null> {\n let raw: string;\n try {\n raw = await readFile(path, 'utf-8');\n } catch {\n return null;\n }\n\n if (Buffer.byteLength(raw, 'utf-8') <= MAX_POLICY_DOCUMENT_BYTES) {\n return { content: raw, truncated: false };\n }\n const clipped = Buffer.from(raw, 'utf-8')\n .subarray(0, MAX_POLICY_DOCUMENT_BYTES)\n .toString('utf-8');\n return { content: clipped, truncated: true };\n}\n\n/** Find an entry of `dir` whose name matches one of `names`, case-insensitively. */\nasync function findFile(dir: string, names: string[]): Promise<string | null> {\n const entries = await listDirectory(dir);\n const wanted = names.map((name) => name.toLowerCase());\n for (const name of wanted) {\n const hit = entries.find((entry) => entry.toLowerCase() === name);\n if (hit !== undefined) return hit;\n }\n return null;\n}\n\n// ── Issue Templates and Forms ───────────────────────────────────────────────\n\nconst TEMPLATE_EXTENSIONS = ['.yml', '.yaml', '.md', '.markdown'];\n\n/** The chooser configuration is not a template. */\nfunction isTemplateFile(name: string): boolean {\n const lower = name.toLowerCase();\n if (lower === 'config.yml' || lower === 'config.yaml') return false;\n return TEMPLATE_EXTENSIONS.some((extension) => lower.endsWith(extension));\n}\n\nexport interface IssueTemplateDiscovery {\n templates: IssueTemplate[];\n sources: PolicySource[];\n}\n\n/**\n * Issue Templates and Forms of the local tree.\n *\n * Searched in `.github/ISSUE_TEMPLATE/`, the repository root and `docs/`, plus\n * the single-file `ISSUE_TEMPLATE.md` variant of each — the three locations\n * GitHub honours. Files are sorted by name so two runs over the same tree\n * produce the same order.\n */\nexport async function discoverIssueTemplates(root: string): Promise<IssueTemplateDiscovery> {\n const templates: IssueTemplate[] = [];\n const sources: PolicySource[] = [];\n\n for (const base of POLICY_DIRECTORIES) {\n const baseDir = base === '' ? root : join(root, base);\n\n const templateDir = (await findDirectory(baseDir, 'ISSUE_TEMPLATE')) ?? null;\n if (templateDir !== null) {\n const names = (await listDirectory(templateDir)).filter(isTemplateFile).sort();\n for (const name of names) {\n const filePath = join(templateDir, name);\n const file = await readCapped(filePath);\n if (file === null) continue;\n const relPath = toRelativePath(root, filePath);\n templates.push(parseIssueTemplateFile(relPath, file.content));\n sources.push({\n kind: 'issue-templates',\n origin: 'filesystem',\n path: relPath,\n status: 'found',\n detail: file.truncated ? 'content truncated' : null,\n });\n }\n }\n\n const singleFile = await findFile(baseDir, [\n 'ISSUE_TEMPLATE.md',\n 'ISSUE_TEMPLATE.yml',\n 'ISSUE_TEMPLATE.yaml',\n 'ISSUE_TEMPLATE.markdown',\n ]);\n if (singleFile !== null) {\n const filePath = join(baseDir, singleFile);\n const file = await readCapped(filePath);\n if (file !== null) {\n const relPath = toRelativePath(root, filePath);\n templates.push(parseIssueTemplateFile(relPath, file.content));\n sources.push({\n kind: 'issue-templates',\n origin: 'filesystem',\n path: relPath,\n status: 'found',\n detail: file.truncated ? 'content truncated' : null,\n });\n }\n }\n }\n\n return { templates, sources };\n}\n\n/** Case-insensitive lookup of a subdirectory, returning its real path. */\nasync function findDirectory(parent: string, name: string): Promise<string | null> {\n let entries: string[];\n try {\n entries = (await readdir(parent, { withFileTypes: true }))\n .filter((entry) => entry.isDirectory())\n .map((entry) => entry.name);\n } catch {\n return null;\n }\n const hit = entries.find((entry) => entry.toLowerCase() === name.toLowerCase());\n return hit === undefined ? null : join(parent, hit);\n}\n\n// ── Pull Request template ───────────────────────────────────────────────────\n\nconst PR_TEMPLATE_FILES = [\n 'PULL_REQUEST_TEMPLATE.md',\n 'PULL_REQUEST_TEMPLATE.markdown',\n 'PULL_REQUEST_TEMPLATE.txt',\n 'PULL_REQUEST_TEMPLATE',\n];\n\nexport interface PullRequestTemplateDiscovery {\n templates: PullRequestTemplate[];\n sources: PolicySource[];\n}\n\n/**\n * Pull Request templates, in every layout GitHub supports: the single file in\n * `.github/`, the root or `docs/`, and the `PULL_REQUEST_TEMPLATE/` directory\n * that holds several named ones.\n *\n * The first template found is the default one; the rest are kept so a caller\n * can offer the choice the repository set up.\n */\nexport async function discoverPullRequestTemplates(\n root: string,\n): Promise<PullRequestTemplateDiscovery> {\n const templates: PullRequestTemplate[] = [];\n const sources: PolicySource[] = [];\n\n const collect = async (filePath: string): Promise<void> => {\n const file = await readCapped(filePath);\n if (file === null) return;\n const relPath = toRelativePath(root, filePath);\n templates.push({\n path: relPath,\n name: relPath.split('/').pop() ?? relPath,\n content: file.content,\n });\n sources.push({\n kind: 'pull-request-template',\n origin: 'filesystem',\n path: relPath,\n status: 'found',\n detail: file.truncated ? 'content truncated' : null,\n });\n };\n\n for (const base of POLICY_DIRECTORIES) {\n const baseDir = base === '' ? root : join(root, base);\n\n const single = await findFile(baseDir, PR_TEMPLATE_FILES);\n if (single !== null && !(await isDirectory(join(baseDir, single)))) {\n await collect(join(baseDir, single));\n }\n\n const templateDir = await findDirectory(baseDir, 'PULL_REQUEST_TEMPLATE');\n if (templateDir !== null) {\n for (const name of (await listDirectory(templateDir)).sort()) {\n if (!/\\.(md|markdown|txt)$/i.test(name)) continue;\n await collect(join(templateDir, name));\n }\n }\n }\n\n return { templates, sources };\n}\n\n// ── CODEOWNERS ──────────────────────────────────────────────────────────────\n\nexport interface CodeownersDiscovery {\n content: string | null;\n sources: PolicySource[];\n}\n\nexport async function discoverCodeowners(root: string): Promise<CodeownersDiscovery> {\n for (const base of POLICY_DIRECTORIES) {\n const baseDir = base === '' ? root : join(root, base);\n const name = await findFile(baseDir, ['CODEOWNERS']);\n if (name === null) continue;\n\n const file = await readCapped(join(baseDir, name));\n if (file === null) continue;\n\n const relPath = toRelativePath(root, join(baseDir, name));\n return {\n content: file.content,\n sources: [\n {\n kind: 'codeowners',\n origin: 'filesystem',\n path: relPath,\n status: 'found',\n detail: file.truncated ? 'content truncated' : null,\n },\n ],\n };\n }\n\n return { content: null, sources: [] };\n}\n\n// ── Policy documents ────────────────────────────────────────────────────────\n\n/** Root-only documents: GitHub reads these from one place, not per directory. */\nconst ROOT_DOCUMENT_FILES = ['CONTRIBUTING.md', 'CODE_OF_CONDUCT.md'];\n\n/** Per-directory agent instructions, composed from the root down to the scope. */\nconst SCOPED_DOCUMENT_FILES = ['AGENTS.md', 'CLAUDE.md'];\n\nexport interface DocumentDiscovery {\n documents: PolicyDocument[];\n sources: PolicySource[];\n}\n\n/**\n * The scope ladder: `''`, then every ancestor of `scope`, then `scope` itself.\n * Composing in this order is what makes the more specific document win — it is\n * simply the later one.\n */\nexport function scopeLadder(scope: string | null): string[] {\n if (scope === null || scope === '') return [''];\n\n const segments = scope.split('/').filter((segment) => segment !== '');\n const ladder = [''];\n let current = '';\n for (const segment of segments) {\n current = current === '' ? segment : `${current}/${segment}`;\n ladder.push(current);\n }\n return ladder;\n}\n\n/**\n * Agent instructions and governance documents applying to `scope`.\n *\n * `AGENTS.md` (the open standard) and `CLAUDE.md` are read at every level of\n * the ladder, root first, so a monorepo's `apps/api/AGENTS.md` arrives after —\n * and therefore wins over — the one at the root. `CONTRIBUTING.md` and\n * `CODE_OF_CONDUCT.md` are read once, at the root, because that is where\n * GitHub reads them from.\n *\n * Documents linked from an `AGENTS.md` or a `CLAUDE.md` are followed one level;\n * see {@link extractReferencedDocuments} for why that is a link walk rather than\n * a scan of `docs/`.\n */\nexport async function discoverDocuments(\n root: string,\n scope: string | null,\n): Promise<DocumentDiscovery> {\n const documents: PolicyDocument[] = [];\n const sources: PolicySource[] = [];\n const seen = new Set<string>();\n\n const collect = async (\n filePath: string,\n kind: PolicyDocument['kind'],\n docScope: string,\n referencedFrom: string | null,\n ): Promise<PolicyDocument | null> => {\n const relPath = toRelativePath(root, filePath);\n if (seen.has(relPath)) return null;\n\n const file = await readCapped(filePath);\n if (file === null) return null;\n seen.add(relPath);\n\n const document: PolicyDocument = {\n path: relPath,\n kind,\n scope: docScope,\n referencedFrom,\n content: file.content,\n };\n documents.push(document);\n sources.push({\n kind: 'docs',\n origin: 'filesystem',\n path: relPath,\n status: 'found',\n detail: file.truncated ? 'content truncated' : null,\n });\n return document;\n };\n\n for (const base of POLICY_DIRECTORIES) {\n const baseDir = base === '' ? root : join(root, base);\n for (const wanted of ROOT_DOCUMENT_FILES) {\n const name = await findFile(baseDir, [wanted]);\n if (name === null) continue;\n const kind = classifyDocument(name);\n if (kind === null) continue;\n await collect(join(baseDir, name), kind, '', null);\n }\n }\n\n const indexes: PolicyDocument[] = [];\n for (const level of scopeLadder(scope)) {\n const levelDir = level === '' ? root : join(root, level);\n for (const wanted of SCOPED_DOCUMENT_FILES) {\n const name = await findFile(levelDir, [wanted]);\n if (name === null) continue;\n const kind = classifyDocument(name);\n if (kind === null) continue;\n const document = await collect(join(levelDir, name), kind, level, null);\n // Both agent-instruction files are indexes. A `CLAUDE.md` whose entire\n // content is \"Read and follow the instructions in AGENTS.md\" is a real and\n // common shape: stopping at it would report a repository that declares\n // nothing, when in fact it forwards everything.\n if (document !== null && (kind === 'agents' || kind === 'claude')) {\n indexes.push(document);\n }\n }\n }\n\n // One level only: a referenced document that itself links onwards does not\n // drag its own bibliography into the context.\n for (const index of indexes) {\n for (const target of extractReferencedDocuments(index.content, index.path)) {\n await collect(join(root, target), 'referenced', index.scope, index.path);\n }\n }\n\n return { documents, sources };\n}\n\n// ── git ─────────────────────────────────────────────────────────────────────\n\nexport interface BaseBranchDiscovery {\n baseBranch: string | null;\n sources: PolicySource[];\n}\n\n/**\n * The repository's base branch, as git actually knows it.\n *\n * Unlike `utils/git.ts`'s `getBaseBranch()`, this never falls back to `'main'`:\n * a discovery layer that invents a value is indistinguishable from one that\n * found it, and `sources` would be lying. An undetermined base branch is null,\n * and the configuration layer above is free to declare one.\n */\nexport async function discoverBaseBranch(\n root: string,\n exec: PolicyExec = defaultExec,\n): Promise<BaseBranchDiscovery> {\n const options = { cwd: root, timeout: DISCOVERY_TIMEOUT_MS };\n\n try {\n const remoteHead = await exec(\n 'git',\n ['symbolic-ref', '--short', 'refs/remotes/origin/HEAD'],\n options,\n );\n if (remoteHead.exitCode === 0) {\n const name = remoteHead.stdout.trim().replace(/^origin\\//, '');\n if (name !== '') {\n return {\n baseBranch: name,\n sources: [\n {\n kind: 'base-branch',\n origin: 'git',\n path: null,\n status: 'found',\n detail: 'origin/HEAD',\n },\n ],\n };\n }\n }\n\n for (const candidate of ['main', 'master']) {\n const check = await exec(\n 'git',\n ['rev-parse', '--verify', '--quiet', `refs/heads/${candidate}`],\n options,\n );\n if (check.exitCode === 0) {\n return {\n baseBranch: candidate,\n sources: [\n {\n kind: 'base-branch',\n origin: 'git',\n path: null,\n status: 'found',\n detail: `refs/heads/${candidate}`,\n },\n ],\n };\n }\n }\n } catch {\n return {\n baseBranch: null,\n sources: [\n {\n kind: 'base-branch',\n origin: 'git',\n path: null,\n status: 'unavailable',\n detail: 'git could not be executed',\n },\n ],\n };\n }\n\n return { baseBranch: null, sources: [] };\n}\n\n/**\n * `owner` and `repo` of the `origin` remote, with their original case.\n *\n * Deliberately derived from git rather than from `gh repo view`: the identity\n * is already on disk, and spending a network round-trip on it would break the\n * \"at most one `gh` call per kind of data\" budget for nothing.\n */\nexport async function discoverGitHubSlug(\n root: string,\n exec: PolicyExec = defaultExec,\n): Promise<{ owner: string; repo: string } | null> {\n let url = '';\n try {\n const result = await exec('git', ['remote', 'get-url', 'origin'], {\n cwd: root,\n timeout: DISCOVERY_TIMEOUT_MS,\n });\n if (result.exitCode !== 0) return null;\n url = result.stdout.trim();\n } catch {\n return null;\n }\n if (url === '') return null;\n\n // Both the scp-like shorthand and every URL scheme end in `<owner>/<repo>`.\n const match = /[/:]([^/:\\s]+)\\/([^/\\s]+?)(?:\\.git)?\\/?$/.exec(url);\n if (match?.[1] === undefined || match[2] === undefined) return null;\n return { owner: match[1], repo: match[2] };\n}\n\n// ── GitHub, via gh ──────────────────────────────────────────────────────────\n\n/** One `gh` call, JSON-decoded. Returns null for every kind of failure. */\nasync function ghJson(\n root: string,\n args: string[],\n exec: PolicyExec,\n): Promise<{ value: unknown } | { error: string }> {\n let result: { stdout: string; stderr: string; exitCode: number };\n try {\n result = await exec('gh', args, { cwd: root, timeout: DISCOVERY_TIMEOUT_MS });\n } catch (err) {\n return { error: err instanceof Error ? err.message : String(err) };\n }\n if (result.exitCode !== 0) {\n const stderr = result.stderr.trim().split('\\n')[0] ?? '';\n return { error: stderr === '' ? `gh exited with ${result.exitCode}` : stderr };\n }\n try {\n return { value: JSON.parse(result.stdout) as unknown };\n } catch {\n return { error: 'gh returned unparseable JSON' };\n }\n}\n\n/** How many labels a single `gh label list` page returns. */\nconst LABEL_PAGE_SIZE = 200;\n\nexport interface LabelDiscovery {\n labels: LabelDefinition[];\n sources: PolicySource[];\n}\n\n/**\n * The labels that exist in the repository.\n *\n * A machine with no `gh`, no authentication or no network degrades to an empty\n * list and a single `unavailable` source — never to a warning, and never to a\n * failure: reading labels is an enrichment, not a prerequisite.\n */\nexport async function discoverLabels(\n root: string,\n exec: PolicyExec = defaultExec,\n): Promise<LabelDiscovery> {\n const outcome = await ghJson(\n root,\n ['label', 'list', '--json', 'name,description,color', '--limit', String(LABEL_PAGE_SIZE)],\n exec,\n );\n\n if ('error' in outcome) {\n return {\n labels: [],\n sources: [\n { kind: 'labels', origin: 'gh', path: null, status: 'unavailable', detail: outcome.error },\n ],\n };\n }\n\n const labels = parseLabels(outcome.value);\n return {\n labels,\n sources: [\n {\n kind: 'labels',\n origin: 'gh',\n path: null,\n status: 'found',\n detail: `${labels.length} label(s)`,\n },\n ],\n };\n}\n\nexport interface IssueTypeDiscovery {\n types: string[];\n sources: PolicySource[];\n}\n\n/**\n * Issue Types of the organization owning the repository.\n *\n * Requires the owner, which comes from the git remote; a repository with no\n * remote simply has no organization to ask about, which is an ordinary state\n * and not an `unavailable` source.\n */\nexport async function discoverIssueTypes(\n root: string,\n owner: string | null,\n exec: PolicyExec = defaultExec,\n): Promise<IssueTypeDiscovery> {\n if (owner === null || owner === '') {\n return { types: [], sources: [] };\n }\n\n const outcome = await ghJson(root, ['api', `orgs/${owner}/issue-types`], exec);\n if ('error' in outcome) {\n return {\n types: [],\n sources: [\n {\n kind: 'issue-types',\n origin: 'gh',\n path: null,\n // A personal account (404) and a plan without Issue Types (403) are\n // the common answers here, so the detail carries what gh said.\n status: 'unavailable',\n detail: outcome.error,\n },\n ],\n };\n }\n\n const types = parseIssueTypes(outcome.value);\n return {\n types,\n sources: [\n {\n kind: 'issue-types',\n origin: 'gh',\n path: null,\n status: 'found',\n detail: `${types.length} type(s)`,\n },\n ],\n };\n}\n\n/**\n * The `issueTemplates` connection, as one GraphQL document.\n *\n * Owner and repository travel as GraphQL variables rather than being spliced\n * into the query text: they come from a git remote, which is user-controlled\n * input, and a query built by concatenation is a query someone can rewrite.\n */\nconst ORGANIZATION_TEMPLATES_QUERY = `query($owner:String!,$name:String!){\n repository(owner:$owner,name:$name){\n issueTemplates{\n name\n title\n about\n filename\n body\n assignees(first:10){nodes{login}}\n labels(first:20){nodes{name}}\n }\n }\n}`;\n\n/**\n * The organization's `.github` repository, read as a tree.\n *\n * `issueTemplates` above only ever returns **markdown** templates: GitHub does\n * not expose organization-level Issue *Forms* through it, and a repository whose\n * organization publishes `.yml` forms therefore looks like a repository with no\n * templates at all. Reading the tree of the org's `.github` repository is the\n * only way to see them.\n *\n * One round-trip for names *and* contents, which is why it is a tree query\n * rather than the contents endpoint (a directory listing there carries no file\n * bodies, costing one call per template).\n */\nconst ORGANIZATION_FORMS_QUERY = `query($owner:String!){\n repository(owner:$owner,name:\".github\"){\n object(expression:\"HEAD:.github/ISSUE_TEMPLATE\"){\n ... on Tree {\n entries { name type object { ... on Blob { text } } }\n }\n }\n }\n}`;\n\n/**\n * Issue Forms published by the organization's `.github` repository.\n *\n * Answers an empty list for every ordinary case — no organization, no `.github`\n * repository, no `ISSUE_TEMPLATE` directory — so it is only ever an enrichment.\n */\nexport async function discoverOrganizationForms(\n root: string,\n owner: string | null,\n exec: PolicyExec = defaultExec,\n): Promise<IssueTemplateDiscovery> {\n if (owner === null || owner === '') {\n return { templates: [], sources: [] };\n }\n\n const outcome = await ghJson(\n root,\n ['api', 'graphql', '-f', `query=${ORGANIZATION_FORMS_QUERY}`, '-f', `owner=${owner}`],\n exec,\n );\n if ('error' in outcome) {\n return {\n templates: [],\n sources: [\n {\n kind: 'issue-templates',\n origin: 'gh',\n path: null,\n status: 'unavailable',\n detail: outcome.error,\n },\n ],\n };\n }\n\n const templates = parseOrganizationForms(outcome.value);\n if (templates.length === 0) {\n return { templates, sources: [] };\n }\n return {\n templates,\n sources: templates.map((template) => ({\n kind: 'issue-templates' as const,\n origin: 'gh' as const,\n path: null,\n status: 'found' as const,\n detail: `organization form \"${template.path}\"`,\n })),\n };\n}\n\n/**\n * Issue Templates served by GitHub, including the ones the organization's\n * `.github` repository provides.\n *\n * Only consulted when the local tree has none: that is precisely the case local\n * discovery cannot see, and consulting it otherwise would spend a round-trip to\n * re-learn what is already on disk.\n *\n * GraphQL, not REST: the REST API exposes no issue-template endpoint at all,\n * and the connection returns the bodies inline, so the organization defaults\n * cost a single round-trip instead of one listing plus one call per template.\n */\nexport async function discoverOrganizationTemplates(\n root: string,\n slug: { owner: string; repo: string } | null,\n exec: PolicyExec = defaultExec,\n): Promise<IssueTemplateDiscovery> {\n if (slug === null) {\n return { templates: [], sources: [] };\n }\n\n const outcome = await ghJson(\n root,\n [\n 'api',\n 'graphql',\n '-f',\n `query=${ORGANIZATION_TEMPLATES_QUERY}`,\n '-f',\n `owner=${slug.owner}`,\n '-f',\n `name=${slug.repo}`,\n ],\n exec,\n );\n if ('error' in outcome) {\n return {\n templates: [],\n sources: [\n {\n kind: 'issue-templates',\n origin: 'gh',\n path: null,\n status: 'unavailable',\n detail: outcome.error,\n },\n ],\n };\n }\n\n const templates = parseOrganizationTemplates(outcome.value);\n if (templates.length === 0) {\n return { templates, sources: [] };\n }\n return {\n templates,\n sources: templates.map((template) => ({\n kind: 'issue-templates' as const,\n origin: 'gh' as const,\n path: null,\n status: 'found' as const,\n detail: `organization default \"${template.name}\"`,\n })),\n };\n}\n","import { posix } from 'node:path';\nimport type { PolicyDocumentKind } from '../types.js';\n\n/**\n * Classification and link extraction for the prose documents that carry\n * repository policy.\n */\n\n/** Which kind of policy document a file name denotes, or null. */\nexport function classifyDocument(fileName: string): PolicyDocumentKind | null {\n switch (fileName.toLowerCase()) {\n case 'agents.md':\n return 'agents';\n case 'claude.md':\n return 'claude';\n case 'contributing.md':\n return 'contributing';\n case 'code_of_conduct.md':\n case 'code-of-conduct.md':\n return 'code-of-conduct';\n default:\n return null;\n }\n}\n\n/** How many referenced documents a single index may pull in. */\nexport const MAX_REFERENCED_DOCUMENTS = 20;\n\nconst MARKDOWN_LINK = /\\[[^\\]\\n]*\\]\\(([^)\\s]+)(?:\\s+\"[^\"]*\")?\\)/g;\n\n/**\n * Repository-relative paths of the markdown documents an index links to.\n *\n * `AGENTS.md` is an index, not a corpus: following its links one level respects\n * what the repository chose to point at, where scanning `docs/` blindly would\n * pull in changelogs, ADR archives and translated copies — a large context cost\n * for content the repository never nominated as policy.\n *\n * Only in-repository markdown targets survive: an absolute URL, a mailto, an\n * anchor, a path that escapes the root, or a link to a non-markdown asset are\n * all dropped. `fromPath` is the repository-relative path of the linking\n * document, used to resolve relative links.\n */\nexport function extractReferencedDocuments(content: string, fromPath: string): string[] {\n const fromDir = posix.dirname(fromPath.replace(/\\\\/g, '/'));\n const found: string[] = [];\n\n for (const match of content.matchAll(MARKDOWN_LINK)) {\n const raw = match[1];\n if (raw === undefined) continue;\n\n const target = raw.split('#')[0]?.split('?')[0]?.trim() ?? '';\n if (target === '') continue;\n // Absolute URLs, protocol-relative URLs and mail links point outside the\n // repository; a Windows-style drive letter is not a repository path either.\n if (/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(target) || target.startsWith('//')) continue;\n if (!/\\.(md|markdown)$/i.test(target)) continue;\n\n const resolved = posix.normalize(\n target.startsWith('/')\n ? target.slice(1)\n : fromDir === '.'\n ? target\n : posix.join(fromDir, target),\n );\n if (resolved.startsWith('..') || resolved === '' || resolved === '.') continue;\n if (resolved === fromPath || found.includes(resolved)) continue;\n\n found.push(resolved);\n if (found.length >= MAX_REFERENCED_DOCUMENTS) break;\n }\n\n return found;\n}\n","import type { LabelDefinition } from '../types.js';\n\n/**\n * Parse `gh label list --json name,description,color`.\n *\n * These are the labels that *really exist*. The distinction matters: a pipeline\n * that invents a label either fails to apply it or, worse, creates it — and a\n * taxonomy nobody agreed to is harder to undo than a missing label.\n *\n * Never throws: a payload that is not an array of label-shaped objects yields\n * an empty list, indistinguishable to the caller from \"gh answered nothing\".\n */\nexport function parseLabels(payload: unknown): LabelDefinition[] {\n if (!Array.isArray(payload)) return [];\n\n const labels: LabelDefinition[] = [];\n for (const entry of payload) {\n if (entry === null || typeof entry !== 'object') continue;\n const record = entry as Record<string, unknown>;\n const name = typeof record.name === 'string' ? record.name.trim() : '';\n if (name === '') continue;\n\n const description = typeof record.description === 'string' ? record.description.trim() : '';\n const color = typeof record.color === 'string' ? record.color.trim() : '';\n\n labels.push({\n name,\n description: description === '' ? null : description,\n color: color === '' ? null : color,\n });\n }\n return labels;\n}\n\n/**\n * Parse `gh api orgs/{org}/issue-types`.\n *\n * Issue Types are an organization-level taxonomy exposed only on some plans, so\n * an empty result is the normal case rather than a failure.\n */\nexport function parseIssueTypes(payload: unknown): string[] {\n const entries = Array.isArray(payload)\n ? payload\n : Array.isArray((payload as { issue_types?: unknown })?.issue_types)\n ? (payload as { issue_types: unknown[] }).issue_types\n : [];\n\n const types: string[] = [];\n for (const entry of entries) {\n const name =\n typeof entry === 'string'\n ? entry\n : entry !== null && typeof entry === 'object'\n ? (entry as Record<string, unknown>).name\n : undefined;\n if (typeof name !== 'string') continue;\n const trimmed = name.trim();\n if (trimmed !== '' && !types.includes(trimmed)) {\n types.push(trimmed);\n }\n }\n return types;\n}\n","import { basename, extname } from 'node:path';\nimport type { IssueTemplate } from '../types.js';\nimport { emptyTemplateMetadata, extractFrontMatter, parseTemplateMetadata } from './issue-forms.js';\n\n/**\n * Turn a template file into an {@link IssueTemplate}.\n *\n * The format is decided by the extension, not by the content: `.yml`/`.yaml` is\n * an Issue Form whose whole document is metadata, everything else is a legacy\n * markdown template whose metadata — if any — lives in a front matter block.\n */\nexport function parseIssueTemplateFile(relPath: string, content: string): IssueTemplate {\n const extension = extname(relPath).toLowerCase();\n const isForm = extension === '.yml' || extension === '.yaml';\n\n const metadataSource = isForm ? content : extractFrontMatter(content);\n const metadata =\n metadataSource === null ? emptyTemplateMetadata() : parseTemplateMetadata(metadataSource);\n\n return {\n path: relPath,\n format: isForm ? 'form' : 'markdown',\n origin: 'filesystem',\n // A template with no `name:` is still addressable by its file name, which\n // is what GitHub falls back to for legacy markdown templates.\n name: metadata.name ?? basename(relPath, extension),\n about: metadata.about,\n title: metadata.title,\n labels: metadata.labels,\n type: metadata.type,\n assignees: metadata.assignees,\n content,\n };\n}\n\nfunction asString(value: unknown): string | null {\n return typeof value === 'string' && value.trim() !== '' ? value : null;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/**\n * Read a GraphQL connection (`{ nodes: [{ name }] }`) as a list of names, also\n * accepting a plain array of strings so a shape change does not break parsing.\n */\nfunction asNameList(value: unknown, key: 'name' | 'login'): string[] {\n const entries = Array.isArray(value)\n ? value\n : isRecord(value) && Array.isArray(value.nodes)\n ? value.nodes\n : [];\n\n return entries\n .map((entry) =>\n typeof entry === 'string' ? entry : asString(isRecord(entry) ? entry[key] : null),\n )\n .filter((entry): entry is string => entry !== null && entry !== '');\n}\n\n/**\n * Parse the `repository.issueTemplates` connection of the GitHub GraphQL API.\n *\n * This is what makes the organization's `.github` repository visible: a\n * repository with no `.github/ISSUE_TEMPLATE/` of its own still serves the\n * organization defaults on github.com, and no amount of local filesystem\n * discovery can see them. GraphQL is used rather than REST because REST simply\n * has no endpoint for issue templates — and because the connection answers with\n * the bodies included, which keeps the whole discovery to a single round-trip.\n *\n * Never throws: an error payload, a missing repository or anything that is not\n * a list of template-shaped objects yields an empty list, which the caller\n * treats exactly like \"GitHub answered nothing\".\n */\nexport function parseOrganizationTemplates(payload: unknown): IssueTemplate[] {\n const repository = isRecord(payload) && isRecord(payload.data) ? payload.data.repository : null;\n const entries = isRecord(repository) ? repository.issueTemplates : null;\n if (!Array.isArray(entries)) return [];\n\n const templates: IssueTemplate[] = [];\n for (const entry of entries) {\n if (!isRecord(entry)) continue;\n const name = asString(entry.name);\n if (name === null) continue;\n\n templates.push({\n // The filename is the stable identity; the display name is not unique.\n path: asString(entry.filename) ?? name,\n format: 'markdown',\n origin: 'organization',\n name,\n about: asString(entry.about),\n title: asString(entry.title),\n labels: asNameList(entry.labels, 'name'),\n // GraphQL exposes no Issue Type on a template, so it stays unknown here\n // rather than being guessed from the name.\n type: null,\n assignees: asNameList(entry.assignees, 'login'),\n content: asString(entry.body) ?? '',\n });\n }\n return templates;\n}\n\n/**\n * Parse the organization `.github` repository's `ISSUE_TEMPLATE` tree.\n *\n * These are the Issue **Forms** an organization publishes, which the\n * `issueTemplates` connection cannot see. Each entry is a real file, so it is\n * parsed exactly like a local one — the only difference is where it came from,\n * and the caller marks that on `origin`.\n */\nexport function parseOrganizationForms(payload: unknown): IssueTemplate[] {\n const repository = isRecord(payload) && isRecord(payload.data) ? payload.data.repository : null;\n const tree = isRecord(repository) ? repository.object : null;\n const entries = isRecord(tree) && Array.isArray(tree.entries) ? tree.entries : [];\n\n const templates: IssueTemplate[] = [];\n for (const entry of entries) {\n if (!isRecord(entry)) continue;\n const name = asString(entry.name);\n if (name === null || entry.type !== 'blob') continue;\n // The chooser configuration is not a template, same as on the filesystem.\n if (/^config\\.(yml|yaml)$/i.test(name)) continue;\n if (!/\\.(yml|yaml|md|markdown)$/i.test(name)) continue;\n\n const text = isRecord(entry.object) ? asString(entry.object.text) : null;\n if (text === null) continue;\n\n templates.push({ ...parseIssueTemplateFile(name, text), origin: 'organization' });\n }\n\n return templates.sort((a, b) => a.path.localeCompare(b.path));\n}\n","/**\n * A deliberately small YAML reader for Issue Form and Issue Template metadata.\n *\n * Issue Flow does not need a YAML engine: it needs six top-level keys (`name`,\n * `description`/`about`, `title`, `labels`, `type`, `assignees`) out of a file\n * whose remaining structure — the `body:` array of form elements — it has no\n * use for. Pulling a parser dependency in to read those six keys would trade a\n * supply-chain surface for nothing.\n *\n * The reader therefore works on top-level keys only: it locates every line that\n * starts a key at indentation zero, and parses the region of the ones it cares\n * about. Anything nested (a `body:` element, its `attributes:`) is skipped\n * whole, which is why `- type: markdown` inside `body:` can never be mistaken\n * for the top-level `type:` of an Issue Type.\n */\n\n/** The metadata block shared by Issue Forms (`.yml`) and templates (`.md`). */\nexport interface TemplateMetadata {\n name: string | null;\n about: string | null;\n title: string | null;\n labels: string[];\n type: string | null;\n assignees: string[];\n}\n\nexport function emptyTemplateMetadata(): TemplateMetadata {\n return { name: null, about: null, title: null, labels: [], type: null, assignees: [] };\n}\n\nconst TOP_LEVEL_KEY = /^([A-Za-z_][A-Za-z0-9_-]*):(?:\\s+(.*))?$/;\n\ninterface KeyRegion {\n key: string;\n /** Text on the key's own line, after the colon. Empty when the value is a block. */\n inline: string;\n /** Lines below the key, up to the next top-level key. */\n block: string[];\n}\n\n/**\n * Split a YAML document into its top-level key regions.\n *\n * Exported for the templates parser, which reads the same regions out of a\n * markdown front matter block.\n */\nexport function topLevelRegions(source: string): Map<string, KeyRegion> {\n const regions = new Map<string, KeyRegion>();\n const lines = source.split(/\\r?\\n/);\n\n let current: KeyRegion | null = null;\n for (const line of lines) {\n // A line that begins with whitespace, a dash or a comment can never open a\n // top-level key, so it always belongs to the region already open.\n const match = /^[^\\s#-]/.test(line) ? TOP_LEVEL_KEY.exec(line) : null;\n if (match?.[1] !== undefined) {\n current = { key: match[1], inline: (match[2] ?? '').trim(), block: [] };\n // First occurrence wins, matching how a YAML loader would report a\n // duplicate key as a document error rather than silently taking the last.\n if (!regions.has(current.key)) {\n regions.set(current.key, current);\n }\n continue;\n }\n current?.block.push(line);\n }\n\n return regions;\n}\n\n/** Strip a trailing `# comment` that is not inside quotes. */\nfunction stripComment(value: string): string {\n let quote: string | null = null;\n for (let i = 0; i < value.length; i++) {\n const char = value[i];\n if (quote !== null) {\n if (char === quote) quote = null;\n continue;\n }\n if (char === '\"' || char === \"'\") {\n quote = char;\n continue;\n }\n if (char === '#' && (i === 0 || /\\s/.test(value[i - 1] ?? ''))) {\n return value.slice(0, i);\n }\n }\n return value;\n}\n\n/** Unquote a scalar, handling the two YAML quoting styles. */\nfunction unquote(value: string): string {\n const trimmed = value.trim();\n if (trimmed.length >= 2) {\n const first = trimmed[0];\n const last = trimmed[trimmed.length - 1];\n if (first === '\"' && last === '\"') {\n return trimmed.slice(1, -1).replace(/\\\\([\"\\\\])/g, '$1');\n }\n if (first === \"'\" && last === \"'\") {\n return trimmed.slice(1, -1).replace(/''/g, \"'\");\n }\n }\n return trimmed;\n}\n\n/** Split a flow sequence body (`a, \"b, c\", d`) on its top-level commas. */\nfunction splitFlow(inner: string): string[] {\n const parts: string[] = [];\n let buffer = '';\n let quote: string | null = null;\n\n for (const char of inner) {\n if (quote !== null) {\n if (char === quote) quote = null;\n buffer += char;\n continue;\n }\n if (char === '\"' || char === \"'\") {\n quote = char;\n buffer += char;\n continue;\n }\n if (char === ',') {\n parts.push(buffer);\n buffer = '';\n continue;\n }\n buffer += char;\n }\n parts.push(buffer);\n\n return parts.map(unquote).filter((part) => part !== '');\n}\n\n/** Read a region as a scalar, returning null when it holds no usable value. */\nfunction readScalar(region: KeyRegion | undefined): string | null {\n if (region === undefined) return null;\n\n const inline = stripComment(region.inline).trim();\n if (inline !== '' && inline !== '|' && inline !== '>' && inline !== '|-' && inline !== '>-') {\n const value = unquote(inline);\n return value === '' ? null : value;\n }\n\n // Block scalar (`|`, `>`) or a value that simply starts on the next line.\n const collected = region.block\n .filter((line) => line.trim() !== '')\n .map((line) => line.trim())\n .join(inline.startsWith('>') ? ' ' : '\\n')\n .trim();\n return collected === '' ? null : collected;\n}\n\n/** Read a region as a sequence, accepting both the flow and the block style. */\nfunction readSequence(region: KeyRegion | undefined): string[] {\n if (region === undefined) return [];\n\n const inline = stripComment(region.inline).trim();\n if (inline.startsWith('[')) {\n const closing = inline.lastIndexOf(']');\n return splitFlow(inline.slice(1, closing === -1 ? undefined : closing));\n }\n if (inline !== '') {\n // A bare scalar where a list was expected — GitHub accepts `labels: bug`.\n const single = unquote(inline);\n return single === '' ? [] : [single];\n }\n\n const items: string[] = [];\n for (const line of region.block) {\n const trimmed = line.trim();\n if (trimmed === '' || trimmed.startsWith('#')) continue;\n if (!trimmed.startsWith('- ') && trimmed !== '-') break;\n const value = unquote(stripComment(trimmed.slice(1)).trim());\n // `- key: value` is a mapping, not a label — a list of those is not a\n // taxonomy and taking its first line would invent one.\n if (value === '' || /^[A-Za-z_][A-Za-z0-9_-]*:\\s/.test(value)) continue;\n items.push(value);\n }\n return items;\n}\n\n/**\n * Read the metadata of an Issue Form or Issue Template body.\n *\n * Never throws: a file that is not YAML at all yields empty metadata, which is\n * the same thing the caller does with a template it cannot understand.\n */\nexport function parseTemplateMetadata(source: string): TemplateMetadata {\n const regions = topLevelRegions(source);\n\n return {\n name: readScalar(regions.get('name')),\n // Issue Forms spell it `description`, legacy markdown templates `about`.\n about: readScalar(regions.get('about')) ?? readScalar(regions.get('description')),\n title: readScalar(regions.get('title')),\n labels: readSequence(regions.get('labels')),\n type: readScalar(regions.get('type')),\n assignees: readSequence(regions.get('assignees')),\n };\n}\n\n/**\n * Extract the YAML front matter of a markdown template.\n *\n * Returns null when the file does not open with a `---` fence, which is the\n * common case for a plain `.md` template with no metadata at all.\n */\nexport function extractFrontMatter(source: string): string | null {\n const normalized = source.replace(/^\\uFEFF/, '');\n if (!/^---[ \\t]*\\r?\\n/.test(normalized)) return null;\n\n const rest = normalized.slice(normalized.indexOf('\\n') + 1);\n const closing = /^---[ \\t]*$/m.exec(rest);\n if (closing?.index === undefined) return null;\n\n return rest.slice(0, closing.index);\n}\n","/**\n * The repository policy layer: what the *consumer* repository already declares\n * about how issues, Pull Requests, branches and agents are supposed to work.\n *\n * Every field is discovered, never invented. A repository that declares nothing\n * produces an empty policy — which is exactly the behaviour Issue Flow had\n * before this layer existed.\n */\n\n/**\n * Version of the {@link RepositoryPolicy} shape. Bumped whenever a consumer\n * reading `issue-flow policy --json` would have to change to keep working;\n * purely additive fields do not bump it.\n */\nexport const POLICY_SCHEMA_VERSION = 1;\n\n/** What a {@link PolicySource} is accounting for. */\nexport type PolicySourceKind =\n | 'issue-templates'\n | 'issue-types'\n | 'labels'\n | 'pull-request-template'\n | 'base-branch'\n | 'docs'\n | 'codeowners';\n\n/** Where a {@link PolicySource} came from. */\nexport type PolicySourceOrigin = 'filesystem' | 'gh' | 'git' | 'config' | 'env' | 'cli';\n\n/**\n * Outcome of consulting a source.\n *\n * `found` and `disabled` are ordinary states. `unavailable` is the one that\n * matters for debugging: the source *could* have answered but did not — no\n * `gh`, no network, a timeout — and the policy is therefore incomplete rather\n * than empty. Nothing absent is recorded: a repository without a\n * `CONTRIBUTING.md` would otherwise drown `sources` in negative entries.\n */\nexport type PolicySourceStatus = 'found' | 'unavailable' | 'disabled';\n\n/**\n * Provenance of one piece of the resolved policy.\n *\n * Not an accessory: without it there is no way to explain why a decision was\n * taken, nor to debug a wrong discovery.\n */\nexport interface PolicySource {\n kind: PolicySourceKind;\n origin: PolicySourceOrigin;\n /** Repository-relative path (POSIX separators), or null for non-file sources. */\n path: string | null;\n status: PolicySourceStatus;\n /** Human-readable note: the gh command that failed, the reason, a count. */\n detail: string | null;\n}\n\n/** An Issue Template or Issue Form declared by the repository. */\nexport interface IssueTemplate {\n /** Repository-relative path, or the org-level template name when remote. */\n path: string;\n /** `form` for a GitHub Issue Form (`.yml`), `markdown` for a legacy template. */\n format: 'form' | 'markdown';\n /** Where the template was found — the local tree or the organization defaults. */\n origin: 'filesystem' | 'organization';\n name: string | null;\n about: string | null;\n /** Pre-filled title, which is also the closest thing to a title convention. */\n title: string | null;\n labels: string[];\n /** GitHub Issue Type declared by the template (`type:` key), when present. */\n type: string | null;\n assignees: string[];\n /** Raw file content, truncated at {@link MAX_POLICY_DOCUMENT_BYTES}. */\n content: string;\n}\n\n/** A label that really exists in the repository. */\nexport interface LabelDefinition {\n name: string;\n description: string | null;\n color: string | null;\n}\n\n/** A Pull Request template file. */\nexport interface PullRequestTemplate {\n path: string;\n name: string;\n content: string;\n}\n\n/** What a {@link PolicyDocument} is. */\nexport type PolicyDocumentKind =\n | 'agents'\n | 'claude'\n | 'contributing'\n | 'code-of-conduct'\n | 'referenced';\n\n/** A prose document that carries repository policy. */\nexport interface PolicyDocument {\n path: string;\n kind: PolicyDocumentKind;\n /**\n * Directory the document governs, repository-relative; `''` is the root.\n * Documents are ordered from the root down to the requested scope, so the\n * later entry is the more specific one.\n */\n scope: string;\n /** Repository-relative path of the document that linked to this one. */\n referencedFrom: string | null;\n /** File content, truncated at {@link MAX_POLICY_DOCUMENT_BYTES}. */\n content: string;\n}\n\n/** Conventions of the repository around issues. */\nexport interface PolicyIssues {\n templates: IssueTemplate[];\n /** Issue Types of the organization, when the plan exposes them. */\n types: string[];\n /** Labels that really exist in the repository, never a guessed taxonomy. */\n labels: LabelDefinition[];\n titleConvention: string | null;\n}\n\n/** Conventions of the repository around Pull Requests. */\nexport interface PolicyPullRequests {\n /** Content of the default Pull Request template, or null. */\n template: string | null;\n /** Every template found, for the multi-template directory layout. */\n templates: PullRequestTemplate[];\n baseBranch: string | null;\n titleConvention: string | null;\n}\n\n/** Conventions of the repository around git itself. */\nexport interface PolicyGit {\n branchConvention: string | null;\n commitConvention: string | null;\n}\n\n/** The resolved policy of a repository, at a given scope. */\nexport interface RepositoryPolicy {\n schemaVersion: typeof POLICY_SCHEMA_VERSION;\n /** Absolute path of the repository root the policy was resolved against. */\n root: string;\n /** Repository-relative subdirectory the policy was resolved for, or null. */\n scope: string | null;\n /** False when `policy.enabled` is off — every section is then empty. */\n enabled: boolean;\n issues: PolicyIssues;\n pullRequests: PolicyPullRequests;\n git: PolicyGit;\n docs: PolicyDocument[];\n /** Content of the CODEOWNERS file, or null. */\n codeowners: string | null;\n sources: PolicySource[];\n}\n\n/** Result of an external command, mirroring `utils/shell.ts`'s ExecResult. */\nexport interface PolicyExecResult {\n stdout: string;\n stderr: string;\n exitCode: number;\n}\n\n/**\n * Seam for every external command the discovery shells out to (`gh`, `git`).\n * Injectable so the suite can exercise both the answer and the absence without\n * a real binary.\n */\nexport type PolicyExec = (\n command: string,\n args: string[],\n options: { cwd: string; timeout: number },\n) => Promise<PolicyExecResult>;\n\n/**\n * Upper bound on the bytes read from any single policy document or template.\n *\n * Discovery feeds an agent's context window: an unbounded `AGENTS.md` would\n * cost more than it explains. Truncation is recorded in `sources`.\n */\nexport const MAX_POLICY_DOCUMENT_BYTES = 64 * 1024;\n"],"mappings":"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","names":["sep","sep"]}
@@ -1,28 +1,29 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ publishGitState
4
+ } from "./chunk-LVKQ2AKT.js";
2
5
  import {
3
6
  isTransientFailure,
4
7
  retryDelaySeconds,
5
8
  sleep
6
9
  } from "./chunk-HDV3YDTG.js";
7
- import {
8
- publishGitState
9
- } from "./chunk-M37YZYDO.js";
10
10
  import {
11
11
  EXECUTE_PHASE,
12
12
  elapsedSecondsSince,
13
13
  getPhaseUsageTotals,
14
14
  publishIterationMetrics,
15
- publishStoryMetrics
16
- } from "./chunk-DTNM3LTO.js";
15
+ publishStoryMetrics,
16
+ resolvePolicyPlaceholders
17
+ } from "./chunk-OBLVLIUS.js";
17
18
  import {
18
19
  applyPlaceholders,
19
20
  loadPrompt
20
- } from "./chunk-SLHZPB3C.js";
21
+ } from "./chunk-HQYXVIH6.js";
21
22
  import {
22
23
  createConfig,
23
24
  resolvePaths,
24
25
  validateDependencies
25
- } from "./chunk-MTJQOOMG.js";
26
+ } from "./chunk-VQ5VN7DF.js";
26
27
  import {
27
28
  allStoriesPass,
28
29
  applyStoryMetrics,
@@ -55,7 +56,7 @@ import {
55
56
  trimErrorMessage,
56
57
  useColor,
57
58
  useUnicode
58
- } from "./chunk-57MH6RYJ.js";
59
+ } from "./chunk-HC2MT7BX.js";
59
60
 
60
61
  // src/core/engine.ts
61
62
  import { existsSync } from "fs";
@@ -370,10 +371,11 @@ function newlyCompletedStoryIds(before, after) {
370
371
  const wasPassing = new Map(before.map((story) => [story.id, story.passes]));
371
372
  return after.filter((story) => story.passes && wasPassing.get(story.id) === false).map((story) => story.id);
372
373
  }
373
- function commitPlaceholders(scope) {
374
+ function commitPlaceholders(scope, convention) {
374
375
  const suffix = scope === void 0 || scope === "" ? "" : `(${scope})`;
376
+ const declared = convention !== void 0 && convention !== null && convention !== "";
375
377
  return {
376
- __COMMIT_MESSAGE__: `feat${suffix}: [Story ID] - [Story Title]`,
378
+ __COMMIT_MESSAGE__: declared ? `<type>${suffix}: [Story ID] - [Story Title]` : `feat${suffix}: [Story ID] - [Story Title]`,
377
379
  __FIX_COMMIT_MESSAGE__: `fix${suffix}: address review findings`
378
380
  };
379
381
  }
@@ -464,7 +466,8 @@ async function runEngine(config, paths) {
464
466
  await archiveIfBranchChanged(plan, paths);
465
467
  await trackBranch(plan, paths.lastBranchFile);
466
468
  await ensureProgressFile(paths.progressFile);
467
- const promptTemplate = await loadPrompt("execute");
469
+ const promptTemplate = await loadPrompt("execute", { projectRoot: paths.projectRoot });
470
+ const policy = await resolvePolicyPlaceholders({ root: paths.projectRoot });
468
471
  printStartupHeader(config, plan);
469
472
  const startTime = Date.now();
470
473
  let i = 0;
@@ -491,7 +494,10 @@ async function runEngine(config, paths) {
491
494
  const prompt = applyPlaceholders(promptTemplate, {
492
495
  __PRD_FILE__: paths.prdFile,
493
496
  __PROGRESS_FILE__: paths.progressFile,
494
- ...commitPlaceholders(config.commitScope)
497
+ ...policy,
498
+ // The convention comes off the projection above rather than from a second
499
+ // discovery: one resolution per run is the whole point of the cache.
500
+ ...commitPlaceholders(config.commitScope, policy.__COMMIT_CONVENTION__)
495
501
  });
496
502
  const iterationStartedAt = isoNow();
497
503
  plan = markIssueInProgress(plan, iterationStartedAt);
@@ -674,4 +680,4 @@ export {
674
680
  printRunSummary,
675
681
  runExecute
676
682
  };
677
- //# sourceMappingURL=chunk-DP43WJY4.js.map
683
+ //# sourceMappingURL=chunk-6N7LZZ5U.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/engine.ts","../src/ui/progress.ts","../src/ui/summary.ts","../src/core/executor.ts","../src/commands/execute.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { cp, mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { resolvePolicyPlaceholders } from '../policy/placeholders.js';\nimport type { EngineConfig, ResolvedPaths, TaskPlan, UserStory } from '../types.js';\nimport { printError, printInfo, printRetry, printSuccess, printWarning } from '../ui/logger.js';\nimport { printIterationHeader } from '../ui/progress.js';\nimport { printStartupHeader, printSummaryBox } from '../ui/summary.js';\nimport { isTransientFailure, retryDelaySeconds, sleep } from '../utils/retry.js';\nimport { executeClaude } from './executor.js';\nimport { divideUsage } from './metrics.js';\nimport { applyPlaceholders, loadPrompt } from './prompt-resolver.js';\nimport { publishGitState } from './session-git.js';\nimport {\n EXECUTE_PHASE,\n elapsedSecondsSince,\n getPhaseUsageTotals,\n publishIterationMetrics,\n publishStoryMetrics,\n} from './session-metrics.js';\nimport { getSessionPublisher } from './session-publisher.js';\nimport {\n allStoriesPass,\n applyStoryMetrics,\n clearLastError,\n hasPendingCorrection,\n initializeState,\n isoNow,\n loadTaskPlan,\n markIssueCompleted,\n markIssueInProgress,\n saveTaskPlan,\n selectActiveStory,\n setLastError,\n trimErrorMessage,\n} from './state-manager.js';\nimport {\n getOutputCallback,\n getStoryStageCallback,\n getStoryUpdateCallback,\n isVerbose,\n} from './verbose.js';\n\n/**\n * Emit a message through the output callback if available, otherwise console.log.\n * Used for bare console.log calls in engine functions so they route through listr2.\n */\nfunction emitLog(message: string): void {\n const cb = getOutputCallback();\n if (cb) {\n // Skip empty lines in listr2 context — they don't render meaningfully\n if (message) {\n cb(message);\n }\n } else {\n console.log(message);\n }\n}\n\n/**\n * Ids of the stories that flipped from `passes: false` to `passes: true`\n * between the two readings of the plan taken around one execute iteration.\n *\n * Stories already passing before the iteration, and stories that only exist in\n * the newer plan, are never attributed: nothing says this iteration paid for\n * them.\n */\nfunction newlyCompletedStoryIds(before: UserStory[], after: UserStory[]): string[] {\n const wasPassing = new Map(before.map((story) => [story.id, story.passes]));\n return after\n .filter((story) => story.passes && wasPassing.get(story.id) === false)\n .map((story) => story.id);\n}\n\n/**\n * Commit message formats handed to the execute prompt.\n *\n * Without a scope they are byte-for-byte what the prompt has always spelled\n * out, so a single-issue run produces the same history it always did. With one\n * — the only case being several issues sharing a branch — the issue becomes a\n * conventional-commit scope, which is what makes `git log` on the shared branch\n * readable per issue.\n *\n * `convention` is the repository's own commit convention, when it declares one.\n * The hard-coded `feat` is wrong for most stories under Conventional Commits: a\n * bug fix committed as `feat:` corrupts a changelog and a semver bump computed\n * from the history. So a repository that declared a convention gets a `<type>`\n * placeholder and the instruction to choose; one that declared none keeps the\n * exact string it always had.\n */\nexport function commitPlaceholders(\n scope?: string,\n convention?: string | null,\n): Record<string, string> {\n const suffix = scope === undefined || scope === '' ? '' : `(${scope})`;\n const declared = convention !== undefined && convention !== null && convention !== '';\n\n return {\n __COMMIT_MESSAGE__: declared\n ? `<type>${suffix}: [Story ID] - [Story Title]`\n : `feat${suffix}: [Story ID] - [Story Title]`,\n __FIX_COMMIT_MESSAGE__: `fix${suffix}: address review findings`,\n };\n}\n\n/**\n * Initialize the progress file if it doesn't exist.\n */\nasync function ensureProgressFile(progressFile: string): Promise<void> {\n if (!existsSync(progressFile)) {\n const content = `# Issue Flow Progress Log\\nStarted: ${new Date().toString()}\\n---\\n`;\n await writeFile(progressFile, content, 'utf-8');\n }\n}\n\n/**\n * Archive previous run artifacts if the branch has changed.\n */\nasync function archiveIfBranchChanged(plan: TaskPlan, paths: ResolvedPaths): Promise<void> {\n const { lastBranchFile, archiveDir, prdFile, progressFile } = paths;\n\n if (!existsSync(lastBranchFile)) {\n return;\n }\n\n const currentBranch = plan.branchName ?? '';\n let lastBranch = '';\n\n try {\n lastBranch = (await readFile(lastBranchFile, 'utf-8')).trim();\n } catch {\n return;\n }\n\n if (currentBranch && lastBranch && currentBranch !== lastBranch) {\n const dateStr = new Date().toISOString().split('T')[0];\n const folderName = lastBranch.replace(/^issue\\//, '').replace(/[<>:\"|?*\\\\]/g, '_');\n const archiveFolder = join(archiveDir, `${dateStr}-${folderName}`);\n\n printInfo(`Archiving previous run: ${lastBranch}`);\n await mkdir(archiveFolder, { recursive: true });\n\n if (existsSync(prdFile)) {\n await cp(prdFile, join(archiveFolder, 'tasks.json'));\n }\n if (existsSync(progressFile)) {\n await cp(progressFile, join(archiveFolder, 'progress.txt'));\n }\n\n printInfo(` Archived to: ${archiveFolder}`);\n\n // Reset progress file for new run\n await writeFile(\n progressFile,\n `# Issue Flow Progress Log\\nStarted: ${new Date().toString()}\\n---\\n`,\n 'utf-8',\n );\n }\n}\n\n/**\n * Write the current branch to the last-branch tracking file.\n */\nasync function trackBranch(plan: TaskPlan, lastBranchFile: string): Promise<void> {\n const branch = plan.branchName ?? '';\n if (branch) {\n await writeFile(lastBranchFile, `${branch}\\n`, 'utf-8');\n }\n}\n\n/**\n * Run the issue-flow engine loop.\n *\n * This replicates the full execution flow:\n * 1. Load and initialize task plan state\n * 2. Check for early exit (already complete)\n * 3. Archive previous run if branch changed\n * 4. Resolve prompt\n * 5. Main loop: iterate, execute Claude, handle results\n * 6. Print summary\n */\nexport async function runEngine(config: EngineConfig, paths: ResolvedPaths): Promise<number> {\n // Load task plan\n if (!existsSync(paths.prdFile)) {\n printError(`PRD file not found at ${paths.prdFile}`);\n if (config.issueNumber) {\n emitLog(`Have you run the resolve-issue skill for issue #${config.issueNumber} first?`);\n }\n return 1;\n }\n\n let plan = await loadTaskPlan(paths.prdFile);\n plan = initializeState(plan);\n await saveTaskPlan(paths.prdFile, plan);\n\n // Check if already completed. A pending correction (a failed review whose\n // findings haven't been addressed yet) always overrides this, even though\n // every userStories[].passes is already true — otherwise a correction\n // cycle's re-run of execute would exit here without ever looking at what\n // the review found.\n if (plan.issueStatus === 'completed' && allStoriesPass(plan) && !hasPendingCorrection(plan)) {\n emitLog(`Issue already marked complete in ${paths.prdFile}`);\n return 0;\n }\n\n // Warn if marked complete but stories still pending\n if (plan.issueStatus === 'completed' && !allStoriesPass(plan)) {\n printWarning(\n 'Issue marked completed but some stories are still pending. Resetting to in_progress.',\n );\n plan = markIssueInProgress(plan);\n plan = setLastError(\n plan,\n 'invalid_completion_state',\n 'tasks.json claimed the issue was completed before every story had passes=true.',\n );\n await saveTaskPlan(paths.prdFile, plan);\n }\n\n // Check if all stories already pass\n if (allStoriesPass(plan) && !hasPendingCorrection(plan)) {\n emitLog('All user stories already pass. Marking issue as completed.');\n plan = markIssueCompleted(plan);\n await saveTaskPlan(paths.prdFile, plan);\n return 0;\n }\n\n // Archive previous run if branch changed\n await archiveIfBranchChanged(plan, paths);\n\n // Track current branch\n await trackBranch(plan, paths.lastBranchFile);\n\n // Initialize progress file\n await ensureProgressFile(paths.progressFile);\n\n // Load prompt template. The root is handed over rather than rediscovered:\n // it was resolved once by `resolvePaths()`, and a second `git rev-parse` here\n // would be a subprocess per run for an answer already in hand.\n const promptTemplate = await loadPrompt('execute', { projectRoot: paths.projectRoot });\n\n // The repository's own conventions, resolved once for the whole loop: they\n // cannot change mid-run, and every iteration renders the same projection.\n const policy = await resolvePolicyPlaceholders({ root: paths.projectRoot });\n\n // Print startup header\n printStartupHeader(config, plan);\n\n const startTime = Date.now();\n let i = 0;\n let retryCount = 0;\n let totalRetryCount = 0;\n\n // Main loop\n while (true) {\n // Check iteration limit\n if (config.maxIterations !== undefined && i >= config.maxIterations) {\n break;\n }\n\n i++;\n\n // Re-read plan to get latest state\n plan = await loadTaskPlan(paths.prdFile);\n // Baseline for story attribution: whatever was still pending before the\n // agent ran is what this iteration can claim credit for.\n const storiesBeforeIteration = plan.userStories;\n\n // Highest-priority story with passes: false — the same rule\n // prompts/execute.md gives the agent. Computed once, here, and shared by\n // the published event and the verbose terminal header, so every surface\n // agrees on who is active instead of each deriving its own heuristic.\n const activeStoryId = selectActiveStory(plan.userStories)?.id;\n getSessionPublisher().publish({\n type: 'iteration:start',\n at: isoNow(),\n iteration: i,\n storyId: activeStoryId,\n });\n getStoryStageCallback()?.(activeStoryId);\n\n if (isVerbose()) {\n printIterationHeader(i, config.maxIterations, plan.userStories, activeStoryId);\n }\n\n // Apply placeholders to prompt\n const prompt = applyPlaceholders(promptTemplate, {\n __PRD_FILE__: paths.prdFile,\n __PROGRESS_FILE__: paths.progressFile,\n ...policy,\n // The convention comes off the projection above rather than from a second\n // discovery: one resolution per run is the whole point of the cache.\n ...commitPlaceholders(config.commitScope, policy.__COMMIT_CONVENTION__),\n });\n\n const iterationStartedAt = isoNow();\n plan = markIssueInProgress(plan, iterationStartedAt);\n await saveTaskPlan(paths.prdFile, plan);\n\n // Execute Claude\n const iterationStartedAtMs = Date.now();\n const result = await executeClaude(prompt);\n const iterationSeconds = elapsedSecondsSince(iterationStartedAtMs);\n\n if (result.exitCode !== 0) {\n // A failed iteration still burned tokens: report them on the execute\n // phase, with no attribution to any story.\n publishIterationMetrics(i, result.cost, iterationSeconds);\n\n const errorMessage = trimErrorMessage(result.output);\n\n if (isTransientFailure(result.exitCode, result.output)) {\n retryCount++;\n totalRetryCount++;\n plan = await loadTaskPlan(paths.prdFile);\n plan = setLastError(plan, 'transient_claude_failure', errorMessage);\n await saveTaskPlan(paths.prdFile, plan);\n\n if (!config.retryForever && retryCount > config.retryLimit) {\n const elapsed = Math.floor((Date.now() - startTime) / 1000);\n plan = await loadTaskPlan(paths.prdFile);\n printSummaryBox(\n 'failed',\n i,\n totalRetryCount,\n elapsed,\n plan,\n `Exceeded retry limit (${config.retryLimit}) on transient errors`,\n getPhaseUsageTotals(EXECUTE_PHASE),\n );\n return result.exitCode;\n }\n\n const delaySeconds = retryDelaySeconds(\n retryCount,\n config.backoffBaseSeconds,\n config.backoffMaxSeconds,\n );\n getSessionPublisher().publish({\n type: 'retry',\n at: isoNow(),\n attempt: retryCount,\n delaySeconds,\n reason: 'transient_claude_failure',\n });\n\n if (isVerbose()) {\n emitLog('');\n }\n printRetry(\n `Transient Claude failure on iteration ${i} (attempt ${retryCount}). Retrying in ${delaySeconds}s.`,\n );\n\n // Stay within current iteration budget\n i--;\n await sleep(delaySeconds);\n continue;\n }\n\n // Fatal failure\n plan = await loadTaskPlan(paths.prdFile);\n plan = setLastError(plan, 'fatal_claude_failure', errorMessage);\n await saveTaskPlan(paths.prdFile, plan);\n\n const elapsed = Math.floor((Date.now() - startTime) / 1000);\n printSummaryBox(\n 'failed',\n i,\n totalRetryCount,\n elapsed,\n plan,\n `Claude CLI failed with exit code ${result.exitCode}`,\n getPhaseUsageTotals(EXECUTE_PHASE),\n );\n return result.exitCode;\n }\n\n // Success — reset retry counter\n retryCount = 0;\n plan = await loadTaskPlan(paths.prdFile);\n plan = clearLastError(plan, iterationStartedAt);\n await saveTaskPlan(paths.prdFile, plan);\n\n // Notify story progress listeners (e.g., listr2 subtasks)\n const storyUpdateCb = getStoryUpdateCallback();\n if (storyUpdateCb) {\n storyUpdateCb(plan.userStories);\n }\n getSessionPublisher().publish({\n type: 'stories:update',\n at: isoNow(),\n stories: plan.userStories,\n });\n\n // Story attribution — an approximation by construction: the CLI reports a\n // single usage for the whole iteration, so the stories that flipped to\n // passing in it split tokens, cost and duration evenly. With none, nothing\n // is attributed and the whole cost stays on the execute phase. Published\n // after stories:update, which rebuilds the stories array.\n const completedStoryIds = newlyCompletedStoryIds(storiesBeforeIteration, plan.userStories);\n if (completedStoryIds.length > 0) {\n const storyShare = divideUsage(result.cost, completedStoryIds.length);\n const storySeconds = Math.round(iterationSeconds / completedStoryIds.length);\n for (const storyId of completedStoryIds) {\n publishStoryMetrics(storyId, storyShare, storySeconds);\n }\n\n // The same shares also land on tasks.json, so the numbers outlive the\n // session and are readable with web monitoring off. Persisting them is\n // observational: a write failure must never change the iteration's\n // outcome, so the plan in memory only advances when the write succeeded.\n const planWithMetrics = applyStoryMetrics(plan, completedStoryIds, storyShare, storySeconds);\n try {\n await saveTaskPlan(paths.prdFile, planWithMetrics);\n plan = planWithMetrics;\n } catch {\n // Metrics are a nice-to-have; the iteration succeeded regardless.\n }\n }\n\n getSessionPublisher().publish({ type: 'iteration:end', at: isoNow(), iteration: i });\n publishIterationMetrics(i, result.cost, iterationSeconds);\n // Low-frequency commit/PR enrichment: iteration end is one of the only\n // two sanctioned points (the other is phase boundaries in run.ts).\n await publishGitState(getSessionPublisher());\n\n // Check for completion signal\n if (result.output.includes('<promise>COMPLETE</promise>')) {\n plan = await loadTaskPlan(paths.prdFile);\n if (allStoriesPass(plan) && !hasPendingCorrection(plan)) {\n plan = markIssueCompleted(plan);\n await saveTaskPlan(paths.prdFile, plan);\n\n const elapsed = Math.floor((Date.now() - startTime) / 1000);\n printSummaryBox(\n 'success',\n i,\n totalRetryCount,\n elapsed,\n plan,\n undefined,\n getPhaseUsageTotals(EXECUTE_PHASE),\n );\n return 0;\n }\n\n plan = setLastError(\n plan,\n 'invalid_completion_signal',\n hasPendingCorrection(plan)\n ? 'Claude returned <promise>COMPLETE</promise> while lastReviewFindings was still set.'\n : 'Claude returned <promise>COMPLETE</promise> before every story had passes=true.',\n );\n await saveTaskPlan(paths.prdFile, plan);\n\n emitLog('');\n printWarning(\n hasPendingCorrection(plan)\n ? 'Claude returned a completion signal, but lastReviewFindings is still set. Ignoring completion and continuing.'\n : 'Claude returned a completion signal, but tasks.json still has pending stories. Ignoring completion and continuing.',\n );\n }\n\n if (isVerbose()) {\n printSuccess(`Iteration ${i} complete. Continuing...`);\n }\n await sleep(2);\n }\n\n // Reached max iterations\n const elapsed = Math.floor((Date.now() - startTime) / 1000);\n plan = await loadTaskPlan(paths.prdFile);\n printSummaryBox(\n 'incomplete',\n config.maxIterations ?? i,\n totalRetryCount,\n elapsed,\n plan,\n 'Reached max iterations without completing all tasks.',\n getPhaseUsageTotals(EXECUTE_PHASE),\n );\n return 1;\n}\n","import chalk from 'chalk';\nimport { getOutputCallback } from '../core/verbose.js';\nimport type { UserStory } from '../types.js';\nimport { getIcons, useColor, useUnicode } from './logger.js';\n\n/**\n * Print a visual progress bar showing passed/total stories.\n */\nexport function printProgressBar(passed: number, total: number): string {\n const barWidth = 20;\n let pct = 0;\n let filled = 0;\n\n if (total > 0) {\n pct = Math.floor((passed * 100) / total);\n filled = Math.floor((passed * barWidth) / total);\n }\n\n const empty = barWidth - filled;\n const fillChar = useUnicode() ? '\\u2588' : '#';\n const emptyChar = useUnicode() ? '\\u2591' : '-';\n\n const bar = fillChar.repeat(filled) + emptyChar.repeat(empty);\n const text = `${passed}/${total} (${pct}%)`;\n\n if (useColor()) {\n return `${chalk.green(bar)} ${text}`;\n }\n return `${bar} ${text}`;\n}\n\n/**\n * Print the iteration header showing story statuses and progress bar.\n *\n * When a global output callback is set (e.g., inside a listr2 task),\n * output is routed through it instead of console.log.\n *\n * `activeStoryId`, when provided, identifies the story the engine is\n * actually about to work on this iteration (the same id published on\n * `iteration:start` \\u2014 see `core/state-manager.ts`'s `selectActiveStory`),\n * so the header agrees with the session snapshot instead of guessing.\n * Omitted, it falls back to the previous heuristic (\"first non-passing story\n * in array order\"), which existing callers that cannot supply the real id\n * keep relying on.\n */\nexport function printIterationHeader(\n iteration: number,\n maxIter: number | undefined,\n stories: UserStory[],\n activeStoryId?: string,\n): void {\n const icons = getIcons();\n const colored = useColor();\n const cb = getOutputCallback();\n\n const iterLabel = maxIter ? `Iteration ${iteration} of ${maxIter}` : `Iteration ${iteration}`;\n\n const total = stories.length;\n const passed = stories.filter((s) => s.passes).length;\n\n const lines: string[] = [];\n\n if (colored) {\n lines.push(chalk.blue(`\\u2501\\u2501\\u2501 ${icons.start} ${iterLabel} \\u2501\\u2501\\u2501`));\n } else {\n lines.push(`--- ${icons.start} ${iterLabel} ---`);\n }\n\n // Display each story status\n let foundFirstPending = false;\n for (const story of stories) {\n let icon: string;\n let colorFn: (s: string) => string;\n const isActive =\n activeStoryId !== undefined\n ? story.id === activeStoryId\n : !story.passes && !foundFirstPending;\n\n if (story.passes) {\n icon = icons.success;\n colorFn = colored ? chalk.green : (s: string) => s;\n } else if (isActive) {\n icon = icons.pending;\n colorFn = colored ? chalk.yellow : (s: string) => s;\n foundFirstPending = true;\n } else {\n // Not yet reached\n icon = icons.notReached;\n colorFn = colored ? chalk.gray : (s: string) => s;\n }\n\n lines.push(colorFn(` ${icon} ${story.id}: ${story.title}`));\n }\n\n lines.push(` ${printProgressBar(passed, total)}`);\n\n if (cb) {\n // Send as single output to listr2 task context\n cb(lines.join('\\n'));\n } else {\n console.log('');\n for (const line of lines) {\n console.log(line);\n }\n console.log('');\n }\n}\n","import chalk from 'chalk';\nimport { type ClaudeUsage, formatTokens } from '../core/metrics.js';\nimport type { EngineConfig, PrReviewRecommendation, TaskPlan } from '../types.js';\nimport {\n formatDuration,\n getIcons,\n getTermWidth,\n printSuccess,\n printWarning,\n useColor,\n useUnicode,\n} from './logger.js';\n\n/**\n * Truncate or pad a string to fit within a given width.\n */\nfunction fitLine(text: string, width: number): string {\n if (text.length > width) {\n return text.substring(0, width);\n }\n return text.padEnd(width);\n}\n\n/**\n * Print a box with border characters around content lines.\n * Lines equal to \"---\" render as separator rows.\n */\nexport function printBox(lines: string[]): void {\n const colored = useColor();\n const unicode = useUnicode();\n const termWidth = getTermWidth();\n\n // Find max content width\n let maxContentWidth = 0;\n for (const line of lines) {\n if (line !== '---' && line.length > maxContentWidth) {\n maxContentWidth = line.length;\n }\n }\n\n // Cap to terminal width (border + padding = 4 chars)\n const available = termWidth - 4;\n if (maxContentWidth > available) {\n maxContentWidth = available;\n }\n if (maxContentWidth < 20) {\n maxContentWidth = 20;\n }\n\n // Box drawing characters\n const tl = unicode ? '\\u256D' : '+';\n const tr = unicode ? '\\u256E' : '+';\n const bl = unicode ? '\\u2570' : '+';\n const br = unicode ? '\\u256F' : '+';\n const h = unicode ? '\\u2500' : '-';\n const v = unicode ? '\\u2502' : '|';\n const sepL = unicode ? '\\u251C' : '+';\n const sepR = unicode ? '\\u2524' : '+';\n\n const hrule = h.repeat(maxContentWidth + 2);\n\n const blue = colored ? chalk.blue : (s: string) => s;\n const _reset = (s: string) => s;\n\n // Top border\n console.log(blue(`${tl}${hrule}${tr}`));\n\n // Content lines\n for (const line of lines) {\n if (line === '---') {\n console.log(blue(`${sepL}${hrule}${sepR}`));\n } else {\n const fitted = fitLine(line, maxContentWidth);\n console.log(`${blue(v)} ${fitted} ${blue(v)}`);\n }\n }\n\n // Bottom border\n console.log(blue(`${bl}${hrule}${br}`));\n}\n\n/**\n * Print the startup header box showing engine configuration.\n */\nexport function printStartupHeader(config: EngineConfig, plan: TaskPlan): void {\n const icons = getIcons();\n\n const storiesTotal = plan.userStories.length;\n const storiesPassing = plan.userStories.filter((s) => s.passes).length;\n const branchName = plan.branchName ?? 'N/A';\n\n const issueLabel = config.issueNumber ? `Issue #${config.issueNumber}` : 'Standalone mode';\n\n const maxIterLabel =\n config.maxIterations !== undefined ? String(config.maxIterations) : 'unlimited';\n\n const retryLabel = config.retryForever\n ? 'unlimited retries'\n : `${config.retryLimit} consecutive retries`;\n\n printBox([\n `${icons.start} Issue Flow`,\n '---',\n `Issue: ${issueLabel}`,\n `Branch: ${branchName}`,\n `Stories: ${storiesPassing}/${storiesTotal} passing`,\n `Iterations: ${maxIterLabel}`,\n `Retries: ${retryLabel}`,\n ]);\n}\n\n// Re-export formatDuration from logger to maintain backwards compatibility\nexport { formatDuration } from './logger.js';\n\n/**\n * Print the final summary box.\n */\nexport function printSummaryBox(\n status: 'success' | 'incomplete' | 'failed',\n iterations: number,\n totalRetries: number,\n elapsedSeconds: number,\n plan: TaskPlan,\n extraInfo?: string,\n /**\n * Tokens and cost spent by this run, already resolved by the caller (the\n * process-owned counters in `core/session-metrics.ts`, never the session\n * snapshot — that one is empty whenever web monitoring is off). Omitted or\n * empty means \"the CLI reported nothing\", and the line is skipped entirely.\n */\n usage?: ClaudeUsage | null,\n): void {\n const icons = getIcons();\n\n const storiesTotal = plan.userStories.length;\n const storiesPassing = plan.userStories.filter((s) => s.passes).length;\n const duration = formatDuration(elapsedSeconds);\n\n let statusIcon: string;\n let statusLabel: string;\n\n switch (status) {\n case 'success':\n statusIcon = icons.success;\n statusLabel = 'Completed';\n break;\n case 'incomplete':\n statusIcon = icons.warn;\n statusLabel = 'Incomplete';\n break;\n case 'failed':\n statusIcon = icons.fail;\n statusLabel = 'Failed';\n break;\n }\n\n const boxLines = [\n `${icons.end} Issue Flow Summary`,\n '---',\n `Status: ${statusIcon} ${statusLabel}`,\n `Stories: ${storiesPassing}/${storiesTotal} passing`,\n `Iterations: ${iterations}`,\n `Duration: ${duration}`,\n ];\n\n const tokens = formatTokens(usage);\n if (tokens !== '') {\n boxLines.push(`Tokens: ${tokens}`);\n }\n\n boxLines.push(`Retries: ${totalRetries}`);\n\n if (extraInfo) {\n boxLines.push('---');\n boxLines.push(extraInfo);\n }\n\n console.log('');\n printBox(boxLines);\n}\n\n/**\n * What the optional `pr-review` phase produced, as far as the summary cares.\n * `null` in `recommendation` means the verdict could not be recovered from\n * disk — the exit code still told us whether changes were requested.\n */\nexport interface RunSummaryPrReview {\n requestedChanges: boolean;\n recommendation: PrReviewRecommendation | null;\n reportPath: string | null;\n}\n\n/**\n * Everything the final `run` summary prints, already resolved by the caller.\n */\nexport interface RunSummaryInfo {\n issueNumber: string;\n /** 'unknown' when the branch could not be detected. */\n branchName: string;\n /** Pipelines run with --no-branch open no Pull Request. */\n noBranch: boolean;\n storyCount: number;\n elapsedSeconds: number;\n /** 'unknown' when no Pull Request could be resolved. */\n prUrl: string;\n /** Only set when the pr-review phase actually ran. */\n prReview?: RunSummaryPrReview | null;\n /**\n * Tokens and cost of the whole pipeline, from the process-owned counters in\n * `core/session-metrics.ts`. Absent or empty omits the line.\n */\n usage?: ClaudeUsage | null;\n}\n\n/**\n * Detail lines of the final `run` summary (everything below the headline).\n *\n * Kept separate from the printing so the shape is testable without capturing\n * stdout. Without `prReview` the lines are exactly the ones the pipeline has\n * always printed.\n */\nexport function buildRunSummaryLines(info: RunSummaryInfo): string[] {\n const lines = [\n ` Branch: ${info.branchName}${info.noBranch ? ' (current)' : ''}`,\n ` Stories: ${info.storyCount}`,\n ` Duration: ${formatDuration(info.elapsedSeconds)}`,\n ];\n\n // Skipped altogether when the CLI reported nothing — better no line than\n // \"0 in / 0 out\" or \"~$NaN\".\n const tokens = formatTokens(info.usage);\n if (tokens !== '') {\n lines.push(` Tokens: ${tokens}`);\n }\n\n if (!info.noBranch) {\n lines.push(` PR: ${info.prUrl}`);\n }\n\n const review = info.prReview;\n if (review) {\n // The exit code is the source of truth for the verdict, so a report whose\n // recommendation could not be read still reports REQUEST_CHANGES.\n const verdict =\n review.recommendation ?? (review.requestedChanges ? 'REQUEST_CHANGES' : 'unknown');\n lines.push(` Review: ${verdict}`);\n if (review.reportPath !== null) {\n lines.push(` Report: ${review.reportPath}`);\n }\n }\n\n return lines;\n}\n\n/** One issue of a multi-issue queue, as the consolidated summary reports it. */\nexport interface QueueIssueSummary {\n id: string;\n title: string;\n storyCount: number;\n elapsedSeconds: number;\n /** Tokens and cost of this issue alone — never of the whole queue. */\n usage?: ClaudeUsage | null;\n}\n\n/** Everything the consolidated summary of a queue prints. */\nexport interface QueueSummaryInfo {\n /** Identifier of the queue, i.e. of its primary issue. */\n queueId: string;\n /** Branch every issue shared, `null` when the queue ran with --no-branch. */\n branchName: string | null;\n issues: QueueIssueSummary[];\n /** Issues discovered but left out of the run. */\n excluded: { id: string; title: string }[];\n elapsedSeconds: number;\n /** The single consolidated Pull Request, `null` when none was opened. */\n prUrl: string | null;\n /** Tokens and cost of the whole queue. */\n usage?: ClaudeUsage | null;\n /** Verdict of the pr-review phase, when the queue ran it. */\n prReview?: RunSummaryPrReview | null;\n}\n\n/**\n * Detail lines of a queue's final summary.\n *\n * Per-issue lines carry their own duration and cost, which is the visible half\n * of the per-issue metric scoping: what one issue spent is never folded into\n * another's line.\n */\nexport function buildQueueSummaryLines(info: QueueSummaryInfo): string[] {\n const lines: string[] = [];\n\n lines.push(` Branch: ${info.branchName ?? 'current'}`);\n lines.push(` Issues: ${info.issues.length}`);\n\n for (const issue of info.issues) {\n const tokens = formatTokens(issue.usage);\n const title = issue.title === '' ? '' : ` ${issue.title}`;\n lines.push(\n ` #${issue.id}${title} — ${issue.storyCount} stories, ${formatDuration(issue.elapsedSeconds)}` +\n (tokens === '' ? '' : `, ${tokens}`),\n );\n }\n\n lines.push(` Duration: ${formatDuration(info.elapsedSeconds)}`);\n\n const tokens = formatTokens(info.usage);\n if (tokens !== '') {\n lines.push(` Tokens: ${tokens}`);\n }\n if (info.prUrl !== null) {\n lines.push(` PR: ${info.prUrl}`);\n }\n if (info.excluded.length > 0) {\n lines.push(` Skipped: ${info.excluded.map((entry) => `#${entry.id}`).join(', ')}`);\n }\n\n const review = info.prReview;\n if (review) {\n lines.push(\n ` Review: ${review.recommendation ?? (review.requestedChanges ? 'REQUEST_CHANGES' : 'unknown')}`,\n );\n if (review.reportPath !== null) {\n lines.push(` Report: ${review.reportPath}`);\n }\n }\n\n return lines;\n}\n\n/** Print the consolidated summary of a multi-issue queue. */\nexport function printQueueSummary(info: QueueSummaryInfo): void {\n console.log('');\n if (info.prReview?.requestedChanges === true) {\n printWarning(`Queue finished for issue #${info.queueId}, but the PR review requested changes.`);\n } else {\n printSuccess(`Queue complete for issue #${info.queueId} (${info.issues.length} issues)!`);\n }\n for (const line of buildQueueSummaryLines(info)) {\n console.log(line);\n }\n}\n\n/**\n * Print the final summary of a `run` (the flat listing, not the box used by\n * the execute engine).\n */\nexport function printRunSummary(info: RunSummaryInfo): void {\n console.log('');\n if (info.prReview?.requestedChanges) {\n printWarning(\n `Pipeline finished for issue #${info.issueNumber}, but the PR review requested changes.`,\n );\n } else {\n printSuccess(`Pipeline complete for issue #${info.issueNumber}!`);\n }\n for (const line of buildRunSummaryLines(info)) {\n console.log(line);\n }\n}\n","import { execa } from 'execa';\nimport type { ClaudeResult } from '../types.js';\nimport { type ClaudeUsage, parseUsage } from './metrics.js';\nimport { getOutputCallback } from './verbose.js';\n\n/**\n * Execute Claude CLI with a prompt piped to stdin.\n *\n * Runs: echo $PROMPT | claude --dangerously-skip-permissions --print --output-format json\n *\n * The JSON envelope is requested purely for observability: it carries the token\n * and cost metrics of the execute phase. Every flow decision keeps working off\n * the same values as before — `exitCode` and the assistant's text — so the\n * envelope is unwrapped here and never leaks to callers. Whenever stdout is not\n * parseable JSON (or the CLI failed), the behaviour falls back exactly to the\n * previous combined stdout+stderr text with no metrics.\n */\nexport async function executeClaude(prompt: string): Promise<ClaudeResult> {\n const result = await execa(\n 'claude',\n ['--dangerously-skip-permissions', '--print', '--output-format', 'json'],\n {\n input: prompt,\n reject: false,\n timeout: 0, // No timeout — let the engine handle iteration limits\n stripFinalNewline: false,\n },\n );\n\n // Combine stdout and stderr to match Bash's 2>&1 behavior\n const stdout = result.stdout?.toString() ?? '';\n const stderr = result.stderr?.toString() ?? '';\n const exitCode = result.exitCode ?? 1;\n\n let output = stdout + (stderr ? `\\n${stderr}` : '');\n let cost: ClaudeUsage | null = null;\n\n // Only unwrap on success: a failing CLI may print diagnostics on stderr that\n // trimErrorMessage()/isTransientFailure() need to see verbatim.\n if (exitCode === 0) {\n try {\n const parsed: unknown = JSON.parse(stdout);\n const text = (parsed as { result?: unknown } | null)?.result;\n // Non-string `result` would break the callers that treat output as text.\n output = typeof text === 'string' ? text : stdout;\n cost = parseUsage(parsed);\n } catch {\n // Not JSON — keep the raw combined output and report no metrics.\n }\n }\n\n // Forward output through the global callback (listr2 renderer) when active\n const onOutput = getOutputCallback();\n if (onOutput) {\n const trimmed = output.trim();\n if (trimmed) {\n onOutput(trimmed);\n }\n }\n\n return {\n exitCode,\n output,\n cost,\n };\n}\n","import { createConfig, resolvePaths, validateDependencies } from '../config.js';\nimport { runEngine } from '../core/engine.js';\nimport { allStoriesPass, loadTaskPlan, saveTaskPlan } from '../core/state-manager.js';\nimport { printError } from '../ui/logger.js';\n\nexport interface ExecuteOptions {\n issue?: string;\n maxIterations?: number;\n retryLimit?: number;\n retryForever?: boolean;\n /**\n * Conventional-commit scope for the stories of this issue (`issue-71`).\n * Only the multi-issue queue sets it — see `EngineConfig.commitScope`.\n */\n commitScope?: string;\n}\n\nexport async function runExecute(\n positionalMaxIter: number | undefined,\n options: ExecuteOptions,\n): Promise<number> {\n const errors = await validateDependencies();\n if (errors.length > 0) {\n printError('The following required tools are not installed:');\n for (const err of errors) {\n console.log(err);\n }\n return 1;\n }\n\n const maxIterations = options.maxIterations ?? positionalMaxIter;\n\n const config = createConfig({\n issueNumber: options.issue,\n maxIterations,\n retryLimit: options.retryLimit,\n retryForever: options.retryForever,\n commitScope: options.commitScope,\n });\n\n const paths = await resolvePaths(config);\n const exitCode = await runEngine(config, paths);\n\n // Update pipeline state if all stories pass\n if (exitCode === 0 && config.issueNumber) {\n try {\n const plan = await loadTaskPlan(paths.prdFile);\n if (allStoriesPass(plan)) {\n plan.pipeline.executionCompleted = true;\n await saveTaskPlan(paths.prdFile, plan);\n }\n } catch {\n // Non-critical — engine already handled state\n }\n }\n\n return exitCode;\n}\n"],"mappings":"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","names":["chalk","chalk","tokens","elapsed"]}
@@ -5,7 +5,7 @@ import {
5
5
  resolveIssuePaths,
6
6
  resolveProjectPaths,
7
7
  run
8
- } from "./chunk-MTJQOOMG.js";
8
+ } from "./chunk-VQ5VN7DF.js";
9
9
  import {
10
10
  isoNow,
11
11
  issueMetadataSchema,
@@ -13,7 +13,7 @@ import {
13
13
  printInfo,
14
14
  printWarning,
15
15
  writeFileAtomic
16
- } from "./chunk-57MH6RYJ.js";
16
+ } from "./chunk-HC2MT7BX.js";
17
17
 
18
18
  // src/issues/registry.ts
19
19
  var providers = /* @__PURE__ */ new Map();
@@ -284,6 +284,9 @@ var GitHubIssueProvider = class {
284
284
  for (const label of draft.labels) {
285
285
  args.push("--label", label);
286
286
  }
287
+ if (draft.type !== void 0 && draft.type !== "") {
288
+ args.push("--type", draft.type);
289
+ }
287
290
  const result = await run("gh", args);
288
291
  if (result.exitCode !== 0) {
289
292
  const detail = (result.stderr || result.stdout).trim();
@@ -969,4 +972,4 @@ export {
969
972
  issuePlaceholders,
970
973
  resolveCommandIssue
971
974
  };
972
- //# sourceMappingURL=chunk-KXAG24DD.js.map
975
+ //# sourceMappingURL=chunk-AAY6KM7N.js.map