issue-flow 0.5.2 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/dist/{analyze-QVQ4CTCU.js → analyze-VVG7UABS.js} +15 -5
  2. package/dist/analyze-VVG7UABS.js.map +1 -0
  3. package/dist/{chunk-3ULIAC2J.js → chunk-2CSHLHYP.js} +2 -2
  4. package/dist/{chunk-PA3GI7LR.js → chunk-4HT4EVYV.js} +78 -32
  5. package/dist/chunk-4HT4EVYV.js.map +1 -0
  6. package/dist/{chunk-DLWRUTRL.js → chunk-7W5VGYXW.js} +11 -11
  7. package/dist/{chunk-JMSRJ43G.js → chunk-EHX4J5G5.js} +15 -8
  8. package/dist/chunk-EHX4J5G5.js.map +1 -0
  9. package/dist/{chunk-KMX3DUJN.js → chunk-FPGHTTVV.js} +15 -8
  10. package/dist/chunk-FPGHTTVV.js.map +1 -0
  11. package/dist/{chunk-OEJSMZMO.js → chunk-JTSAL3TI.js} +12 -5
  12. package/dist/chunk-JTSAL3TI.js.map +1 -0
  13. package/dist/{chunk-LJI45O5B.js → chunk-KWGRBNUG.js} +22 -8
  14. package/dist/chunk-KWGRBNUG.js.map +1 -0
  15. package/dist/chunk-SVOWWZQJ.js +982 -0
  16. package/dist/chunk-SVOWWZQJ.js.map +1 -0
  17. package/dist/{chunk-O5XI2CFF.js → chunk-WZDCTAWZ.js} +55 -20
  18. package/dist/chunk-WZDCTAWZ.js.map +1 -0
  19. package/dist/{chunk-VM7C7L3A.js → chunk-YM3G5BLR.js} +21 -9
  20. package/dist/chunk-YM3G5BLR.js.map +1 -0
  21. package/dist/cli.js +130 -29
  22. package/dist/cli.js.map +1 -1
  23. package/dist/config-B564P45C.js +29 -0
  24. package/dist/execute-PNOFNLZG.js +12 -0
  25. package/dist/generate-IN62ARBS.js +143 -0
  26. package/dist/generate-IN62ARBS.js.map +1 -0
  27. package/dist/init-5242XSYC.js +9 -0
  28. package/dist/plan-6KLONWPR.js +14 -0
  29. package/dist/pr-JLJLMFN3.js +14 -0
  30. package/dist/prd-TVJDYZXH.js +14 -0
  31. package/dist/review-FRV4ZMS2.js +12 -0
  32. package/dist/review-FRV4ZMS2.js.map +1 -0
  33. package/dist/{run-3GJC54DB.js → run-GPBICAD6.js} +45 -26
  34. package/dist/run-GPBICAD6.js.map +1 -0
  35. package/package.json +2 -1
  36. package/prompts/analyze.md +18 -5
  37. package/prompts/generate.md +27 -6
  38. package/prompts/plan.md +19 -2
  39. package/prompts/pr.md +23 -6
  40. package/prompts/prd.md +18 -5
  41. package/prompts/review.md +18 -6
  42. package/dist/analyze-QVQ4CTCU.js.map +0 -1
  43. package/dist/chunk-JMSRJ43G.js.map +0 -1
  44. package/dist/chunk-KLBAHTXX.js +0 -283
  45. package/dist/chunk-KLBAHTXX.js.map +0 -1
  46. package/dist/chunk-KMX3DUJN.js.map +0 -1
  47. package/dist/chunk-LJI45O5B.js.map +0 -1
  48. package/dist/chunk-O5XI2CFF.js.map +0 -1
  49. package/dist/chunk-OEJSMZMO.js.map +0 -1
  50. package/dist/chunk-PA3GI7LR.js.map +0 -1
  51. package/dist/chunk-VM7C7L3A.js.map +0 -1
  52. package/dist/execute-TEJ26L4O.js +0 -12
  53. package/dist/generate-KBQ4KW4T.js +0 -48
  54. package/dist/generate-KBQ4KW4T.js.map +0 -1
  55. package/dist/init-4VMPBRMA.js +0 -9
  56. package/dist/plan-PWYZP5AN.js +0 -13
  57. package/dist/pr-CJQWZUDR.js +0 -13
  58. package/dist/prd-4KLQY7BC.js +0 -13
  59. package/dist/review-YFCWBBKY.js +0 -11
  60. package/dist/run-3GJC54DB.js.map +0 -1
  61. /package/dist/{chunk-3ULIAC2J.js.map → chunk-2CSHLHYP.js.map} +0 -0
  62. /package/dist/{chunk-DLWRUTRL.js.map → chunk-7W5VGYXW.js.map} +0 -0
  63. /package/dist/{execute-TEJ26L4O.js.map → config-B564P45C.js.map} +0 -0
  64. /package/dist/{init-4VMPBRMA.js.map → execute-PNOFNLZG.js.map} +0 -0
  65. /package/dist/{plan-PWYZP5AN.js.map → init-5242XSYC.js.map} +0 -0
  66. /package/dist/{pr-CJQWZUDR.js.map → plan-6KLONWPR.js.map} +0 -0
  67. /package/dist/{prd-4KLQY7BC.js.map → pr-JLJLMFN3.js.map} +0 -0
  68. /package/dist/{review-YFCWBBKY.js.map → prd-TVJDYZXH.js.map} +0 -0
@@ -1,11 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ issuePlaceholders,
4
+ resolveCommandIssue,
3
5
  runHeadless
4
- } from "./chunk-KLBAHTXX.js";
6
+ } from "./chunk-SVOWWZQJ.js";
5
7
  import {
6
8
  applyPlaceholders,
7
9
  loadPrompt
8
10
  } from "./chunk-SLHZPB3C.js";
11
+ import "./chunk-4HT4EVYV.js";
9
12
  import {
10
13
  getGlobalTimeout,
11
14
  loadTaskPlan,
@@ -13,20 +16,27 @@ import {
13
16
  printInfo,
14
17
  printSuccess,
15
18
  saveTaskPlan
16
- } from "./chunk-O5XI2CFF.js";
19
+ } from "./chunk-WZDCTAWZ.js";
17
20
 
18
21
  // src/commands/analyze.ts
19
22
  import { mkdir, readFile, writeFile } from "fs/promises";
20
23
  import { join } from "path";
21
- async function runAnalyze(issue) {
24
+ async function runAnalyze(issue, resolvedIssue) {
22
25
  const issueNumber = issue.replace(/^#/, "");
23
26
  const issueDir = join("issues", issueNumber);
24
27
  const analysisPath = join(issueDir, "analysis.md");
28
+ const resolution = await resolveCommandIssue(issueNumber, resolvedIssue);
29
+ if (!resolution.ok) {
30
+ return resolution.code;
31
+ }
25
32
  await mkdir(issueDir, { recursive: true });
26
33
  const template = await loadPrompt("analyze");
27
34
  const prompt = applyPlaceholders(template, {
28
35
  __ISSUE_NUMBER__: issueNumber,
29
- __ANALYSIS_PATH__: analysisPath
36
+ __ANALYSIS_PATH__: analysisPath,
37
+ // Last: the Issue content is substituted in but never scanned again, so a
38
+ // body that happens to contain a placeholder is left untouched.
39
+ ...issuePlaceholders(resolution.resolved)
30
40
  });
31
41
  const result = await runHeadless({
32
42
  prompt,
@@ -63,4 +73,4 @@ async function runAnalyze(issue) {
63
73
  export {
64
74
  runAnalyze
65
75
  };
66
- //# sourceMappingURL=analyze-QVQ4CTCU.js.map
76
+ //# sourceMappingURL=analyze-VVG7UABS.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/analyze.ts"],"sourcesContent":["import { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { runHeadless } from '../core/headless.js';\nimport { applyPlaceholders, loadPrompt } from '../core/prompt-resolver.js';\nimport { loadTaskPlan, saveTaskPlan } from '../core/state-manager.js';\nimport { getGlobalTimeout } from '../core/verbose.js';\nimport { issuePlaceholders, resolveCommandIssue } from '../issues/context.js';\nimport type { ResolvedIssue } from '../issues/types.js';\nimport { printError, printInfo, printSuccess } from '../ui/logger.js';\n\nexport async function runAnalyze(issue: string, resolvedIssue?: ResolvedIssue): Promise<number> {\n const issueNumber = issue.replace(/^#/, '');\n const issueDir = join('issues', issueNumber);\n const analysisPath = join(issueDir, 'analysis.md');\n\n const resolution = await resolveCommandIssue(issueNumber, resolvedIssue);\n if (!resolution.ok) {\n return resolution.code;\n }\n\n await mkdir(issueDir, { recursive: true });\n\n const template = await loadPrompt('analyze');\n const prompt = applyPlaceholders(template, {\n __ISSUE_NUMBER__: issueNumber,\n __ANALYSIS_PATH__: analysisPath,\n // Last: the Issue content is substituted in but never scanned again, so a\n // body that happens to contain a placeholder is left untouched.\n ...issuePlaceholders(resolution.resolved),\n });\n\n const result = await runHeadless({\n prompt,\n maxTurns: 30,\n timeout: getGlobalTimeout() ?? 300_000,\n outputFormat: 'text',\n allowedTools: ['Bash', 'Read', 'Glob', 'Grep', 'Write'],\n statusMessage: `Analyzing issue #${issueNumber}...`,\n });\n\n if (!result.success) {\n printError(`Analysis failed: ${result.error}`);\n return 1;\n }\n\n // Verify the file was created\n try {\n const content = await readFile(analysisPath, 'utf-8');\n if (content.length < 10) {\n printError('Analysis file was created but appears empty');\n return 1;\n }\n } catch {\n // File wasn't created by headless — save the result as analysis\n printInfo('Headless did not create analysis file; saving output directly');\n await writeFile(analysisPath, result.result, 'utf-8');\n }\n\n // Update pipeline state\n const tasksPath = join(issueDir, 'tasks.json');\n try {\n const plan = await loadTaskPlan(tasksPath);\n plan.pipeline.analyzeCompleted = true;\n await saveTaskPlan(tasksPath, plan);\n } catch {\n // tasks.json may not exist yet — that's OK for standalone analyze\n }\n\n printSuccess(`Analysis saved to ${analysisPath}`);\n return 0;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,OAAO,UAAU,iBAAiB;AAC3C,SAAS,YAAY;AASrB,eAAsB,WAAW,OAAe,eAAgD;AAC9F,QAAM,cAAc,MAAM,QAAQ,MAAM,EAAE;AAC1C,QAAM,WAAW,KAAK,UAAU,WAAW;AAC3C,QAAM,eAAe,KAAK,UAAU,aAAa;AAEjD,QAAM,aAAa,MAAM,oBAAoB,aAAa,aAAa;AACvE,MAAI,CAAC,WAAW,IAAI;AAClB,WAAO,WAAW;AAAA,EACpB;AAEA,QAAM,MAAM,UAAU,EAAE,WAAW,KAAK,CAAC;AAEzC,QAAM,WAAW,MAAM,WAAW,SAAS;AAC3C,QAAM,SAAS,kBAAkB,UAAU;AAAA,IACzC,kBAAkB;AAAA,IAClB,mBAAmB;AAAA;AAAA;AAAA,IAGnB,GAAG,kBAAkB,WAAW,QAAQ;AAAA,EAC1C,CAAC;AAED,QAAM,SAAS,MAAM,YAAY;AAAA,IAC/B;AAAA,IACA,UAAU;AAAA,IACV,SAAS,iBAAiB,KAAK;AAAA,IAC/B,cAAc;AAAA,IACd,cAAc,CAAC,QAAQ,QAAQ,QAAQ,QAAQ,OAAO;AAAA,IACtD,eAAe,oBAAoB,WAAW;AAAA,EAChD,CAAC;AAED,MAAI,CAAC,OAAO,SAAS;AACnB,eAAW,oBAAoB,OAAO,KAAK,EAAE;AAC7C,WAAO;AAAA,EACT;AAGA,MAAI;AACF,UAAM,UAAU,MAAM,SAAS,cAAc,OAAO;AACpD,QAAI,QAAQ,SAAS,IAAI;AACvB,iBAAW,6CAA6C;AACxD,aAAO;AAAA,IACT;AAAA,EACF,QAAQ;AAEN,cAAU,+DAA+D;AACzE,UAAM,UAAU,cAAc,OAAO,QAAQ,OAAO;AAAA,EACtD;AAGA,QAAM,YAAY,KAAK,UAAU,YAAY;AAC7C,MAAI;AACF,UAAM,OAAO,MAAM,aAAa,SAAS;AACzC,SAAK,SAAS,mBAAmB;AACjC,UAAM,aAAa,WAAW,IAAI;AAAA,EACpC,QAAQ;AAAA,EAER;AAEA,eAAa,qBAAqB,YAAY,EAAE;AAChD,SAAO;AACT;","names":[]}
@@ -7,7 +7,7 @@ import {
7
7
  getSessionPublisher,
8
8
  isoNow,
9
9
  printRetry
10
- } from "./chunk-O5XI2CFF.js";
10
+ } from "./chunk-WZDCTAWZ.js";
11
11
 
12
12
  // src/core/phase-runner.ts
13
13
  import { readFile } from "fs/promises";
@@ -55,4 +55,4 @@ export {
55
55
  runPhaseWithRetry,
56
56
  readFileWithGrace
57
57
  };
58
- //# sourceMappingURL=chunk-3ULIAC2J.js.map
58
+ //# sourceMappingURL=chunk-2CSHLHYP.js.map
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ issuesConfigSchema,
3
4
  printWarning,
4
5
  webConfigSchema
5
- } from "./chunk-O5XI2CFF.js";
6
+ } from "./chunk-WZDCTAWZ.js";
6
7
 
7
8
  // src/config.ts
8
9
  import { readFile } from "fs/promises";
@@ -126,7 +127,37 @@ async function validateDependencies() {
126
127
  }
127
128
  return errors;
128
129
  }
129
- var WEB_CONFIG_FILENAME = ".issue-flow.json";
130
+ var PROJECT_CONFIG_FILENAME = ".issue-flow.json";
131
+ var WEB_CONFIG_FILENAME = PROJECT_CONFIG_FILENAME;
132
+ async function readProjectConfigFile(projectRoot, warn) {
133
+ let root = projectRoot;
134
+ if (root === void 0) {
135
+ try {
136
+ root = await getProjectRoot();
137
+ } catch {
138
+ return null;
139
+ }
140
+ }
141
+ const filePath = join(root, PROJECT_CONFIG_FILENAME);
142
+ let raw;
143
+ try {
144
+ raw = await readFile(filePath, "utf-8");
145
+ } catch {
146
+ return null;
147
+ }
148
+ let parsed;
149
+ try {
150
+ parsed = JSON.parse(raw);
151
+ } catch {
152
+ warn(`Ignoring ${PROJECT_CONFIG_FILENAME}: invalid JSON.`);
153
+ return null;
154
+ }
155
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
156
+ warn(`Ignoring ${PROJECT_CONFIG_FILENAME}: expected a JSON object.`);
157
+ return null;
158
+ }
159
+ return parsed;
160
+ }
130
161
  var webCliOverrides = {};
131
162
  function setWebCliOverrides(overrides) {
132
163
  webCliOverrides = overrides;
@@ -170,40 +201,15 @@ function readWebConfigEnv(env, warn) {
170
201
  return layer;
171
202
  }
172
203
  async function readWebConfigFile(projectRoot, warn) {
173
- let root = projectRoot;
174
- if (root === void 0) {
175
- try {
176
- root = await getProjectRoot();
177
- } catch {
178
- return {};
179
- }
180
- }
181
- const filePath = join(root, WEB_CONFIG_FILENAME);
182
- let raw;
183
- try {
184
- raw = await readFile(filePath, "utf-8");
185
- } catch {
186
- return {};
187
- }
188
- let parsed;
189
- try {
190
- parsed = JSON.parse(raw);
191
- } catch {
192
- warn(`Ignoring ${WEB_CONFIG_FILENAME}: invalid JSON.`);
193
- return {};
194
- }
195
- if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
196
- warn(`Ignoring ${WEB_CONFIG_FILENAME}: expected a JSON object.`);
197
- return {};
198
- }
199
- const web = parsed.web;
204
+ const file = await readProjectConfigFile(projectRoot, warn);
205
+ const web = file?.web;
200
206
  if (web === void 0) {
201
207
  return {};
202
208
  }
203
209
  const result = webConfigSchema.partial().safeParse(web);
204
210
  if (!result.success) {
205
211
  warn(
206
- `Ignoring "web" key of ${WEB_CONFIG_FILENAME}: ${result.error.issues[0]?.message ?? "invalid value"}.`
212
+ `Ignoring "web" key of ${PROJECT_CONFIG_FILENAME}: ${result.error.issues[0]?.message ?? "invalid value"}.`
207
213
  );
208
214
  return {};
209
215
  }
@@ -225,16 +231,56 @@ async function loadWebConfig(options = {}) {
225
231
  );
226
232
  return webConfigSchema.parse({});
227
233
  }
234
+ var issuesCliOverrides = {};
235
+ function setIssuesCliOverrides(overrides) {
236
+ issuesCliOverrides = overrides;
237
+ }
238
+ async function readIssuesConfigFile(projectRoot, warn) {
239
+ const file = await readProjectConfigFile(projectRoot, warn);
240
+ const issues = file?.issues;
241
+ if (issues === void 0) {
242
+ return {};
243
+ }
244
+ const result = issuesConfigSchema.partial().safeParse(issues);
245
+ if (!result.success) {
246
+ warn(
247
+ `Ignoring "issues" key of ${PROJECT_CONFIG_FILENAME}: ${result.error.issues[0]?.message ?? "invalid value"}.`
248
+ );
249
+ return {};
250
+ }
251
+ return result.data;
252
+ }
253
+ async function loadIssuesConfig(options = {}) {
254
+ const warn = options.warn ?? printWarning;
255
+ const cli = options.cli ?? issuesCliOverrides;
256
+ const fileLayer = await readIssuesConfigFile(options.projectRoot, warn);
257
+ const merged = { ...fileLayer, ...cli };
258
+ const result = issuesConfigSchema.safeParse(merged);
259
+ if (result.success) {
260
+ return result.data;
261
+ }
262
+ warn(
263
+ `Invalid Issue provider configuration (${result.error.issues[0]?.message ?? "invalid value"}); using defaults.`
264
+ );
265
+ return issuesConfigSchema.parse({});
266
+ }
228
267
 
229
268
  export {
230
269
  run,
270
+ getProjectRoot,
231
271
  getCurrentBranch,
232
272
  getBaseBranch,
233
273
  getCommitsSince,
274
+ DEFAULTS,
234
275
  createConfig,
235
276
  resolvePaths,
277
+ getInstallHint,
236
278
  validateDependencies,
279
+ PROJECT_CONFIG_FILENAME,
280
+ WEB_CONFIG_FILENAME,
237
281
  setWebCliOverrides,
238
- loadWebConfig
282
+ loadWebConfig,
283
+ setIssuesCliOverrides,
284
+ loadIssuesConfig
239
285
  };
240
- //# sourceMappingURL=chunk-PA3GI7LR.js.map
286
+ //# sourceMappingURL=chunk-4HT4EVYV.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/config.ts","../src/utils/shell.ts","../src/utils/git.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\nimport { platform } from 'node:os';\nimport { join } from 'node:path';\nimport type { IssuesConfig } from './issues/types.js';\nimport { issuesConfigSchema, type WebConfig, webConfigSchema } from './schemas.js';\nimport type { EngineConfig, ResolvedPaths } from './types.js';\nimport { printWarning } from './ui/logger.js';\nimport { getProjectRoot } from './utils/git.js';\nimport { run } from './utils/shell.js';\n\n/**\n * Default configuration values — matching the Bash script exactly.\n */\nexport const DEFAULTS = {\n retryLimit: 10,\n retryForever: false,\n backoffBaseSeconds: 30,\n backoffMaxSeconds: 900,\n} as const;\n\n/**\n * Create a EngineConfig with defaults merged with provided options.\n */\nexport function createConfig(options: Partial<EngineConfig>): EngineConfig {\n return {\n issueNumber: options.issueNumber,\n maxIterations: options.maxIterations,\n retryLimit: options.retryLimit ?? DEFAULTS.retryLimit,\n retryForever: options.retryForever ?? DEFAULTS.retryForever,\n backoffBaseSeconds: options.backoffBaseSeconds ?? DEFAULTS.backoffBaseSeconds,\n backoffMaxSeconds: options.backoffMaxSeconds ?? DEFAULTS.backoffMaxSeconds,\n };\n}\n\n/**\n * Resolve file paths based on issue number and project root.\n *\n * With --issue N:\n * prdFile = {projectRoot}/issues/{N}/tasks.json\n * progressFile = {projectRoot}/issues/{N}/progress.txt\n *\n * Standalone:\n * prdFile = {projectRoot}/prd.json\n * progressFile = {projectRoot}/progress.txt\n */\nexport async function resolvePaths(\n config: EngineConfig,\n scriptDir?: string,\n): Promise<ResolvedPaths> {\n const projectRoot = await getProjectRoot();\n\n if (config.issueNumber) {\n const issueDir = join(projectRoot, 'issues', config.issueNumber);\n return {\n prdFile: join(issueDir, 'tasks.json'),\n progressFile: join(issueDir, 'progress.txt'),\n archiveDir: join(issueDir, 'archive'),\n lastBranchFile: join(issueDir, '.last-branch'),\n projectRoot,\n };\n }\n\n // Standalone mode — use scriptDir if available, otherwise projectRoot\n const base = scriptDir ?? projectRoot;\n return {\n prdFile: join(base, 'prd.json'),\n progressFile: join(base, 'progress.txt'),\n archiveDir: join(base, 'archive'),\n lastBranchFile: join(base, '.last-branch'),\n projectRoot,\n };\n}\n\n/**\n * Return a platform-appropriate install hint for a given package.\n */\nexport function getInstallHint(pkg: string): string {\n const os = platform();\n\n if (os === 'darwin') {\n return `brew install ${pkg}`;\n }\n if (os === 'linux') {\n return `apt install ${pkg} (or your distro's package manager)`;\n }\n if (os === 'win32') {\n return `winget install ${pkg} (or choco install ${pkg})`;\n }\n\n return `install ${pkg} using your system package manager`;\n}\n\n/**\n * Validate that required external dependencies are available.\n * Returns an array of error messages (empty if all deps are found).\n */\nexport async function validateDependencies(): Promise<string[]> {\n const errors: string[] = [];\n\n // Check git\n const gitResult = await run('git', ['--version']);\n if (gitResult.exitCode !== 0) {\n errors.push(` - git (install with: ${getInstallHint('git')})`);\n }\n\n // Check claude\n const claudeResult = await run('claude', ['--version']);\n if (claudeResult.exitCode !== 0) {\n errors.push(' - claude (install with: npm install -g @anthropic-ai/claude-code)');\n }\n\n // Note: jq is NOT required — the TypeScript CLI handles JSON natively\n\n return errors;\n}\n\n// ── Per-project configuration file ──────────────────────────────────────────\n\n/** Optional per-project configuration file, read from the project root. */\nexport const PROJECT_CONFIG_FILENAME = '.issue-flow.json';\n\n/** Historical alias kept for the web monitoring call sites. */\nexport const WEB_CONFIG_FILENAME = PROJECT_CONFIG_FILENAME;\n\n/**\n * Read and parse .issue-flow.json from the project root.\n *\n * Never throws: an absent file, unreadable path, invalid JSON or a non-object\n * root all degrade to `null` (with a warning for the malformed cases), so every\n * consumer falls back to its own defaults.\n */\nasync function readProjectConfigFile(\n projectRoot: string | undefined,\n warn: (message: string) => void,\n): Promise<Record<string, unknown> | null> {\n let root = projectRoot;\n if (root === undefined) {\n try {\n root = await getProjectRoot();\n } catch {\n return null;\n }\n }\n\n const filePath = join(root, PROJECT_CONFIG_FILENAME);\n let raw: string;\n try {\n raw = await readFile(filePath, 'utf-8');\n } catch {\n // The file is entirely optional — absence is the common case.\n return null;\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n warn(`Ignoring ${PROJECT_CONFIG_FILENAME}: invalid JSON.`);\n return null;\n }\n\n if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {\n warn(`Ignoring ${PROJECT_CONFIG_FILENAME}: expected a JSON object.`);\n return null;\n }\n\n return parsed as Record<string, unknown>;\n}\n\n// ── Web monitoring configuration ────────────────────────────────────────────\n\n/**\n * CLI overrides captured by the preAction hook in cli.ts. Highest-precedence\n * source consumed by loadWebConfig().\n */\nlet webCliOverrides: Partial<WebConfig> = {};\n\nexport function setWebCliOverrides(overrides: Partial<WebConfig>): void {\n webCliOverrides = overrides;\n}\n\nexport interface LoadWebConfigOptions {\n /** CLI flag overrides. Defaults to the values set via setWebCliOverrides(). */\n cli?: Partial<WebConfig>;\n /** Environment source. Defaults to process.env. */\n env?: NodeJS.ProcessEnv;\n /** Directory containing .issue-flow.json. Defaults to the git project root. */\n projectRoot?: string;\n /** Warning sink. Defaults to printWarning. */\n warn?: (message: string) => void;\n}\n\nconst FALSY_ENV_VALUES = new Set(['', '0', 'false', 'no', 'off']);\n\nfunction parseBooleanEnv(value: string): boolean {\n return !FALSY_ENV_VALUES.has(value.trim().toLowerCase());\n}\n\nfunction readNumberEnv(\n env: NodeJS.ProcessEnv,\n name: string,\n warn: (message: string) => void,\n): number | undefined {\n const raw = env[name];\n if (raw === undefined) {\n return undefined;\n }\n const parsed = Number(raw);\n if (!Number.isFinite(parsed)) {\n warn(`Ignoring ${name}=\"${raw}\": not a number.`);\n return undefined;\n }\n return parsed;\n}\n\nfunction readWebConfigEnv(\n env: NodeJS.ProcessEnv,\n warn: (message: string) => void,\n): Partial<WebConfig> {\n const layer: Partial<WebConfig> = {};\n if (env.ISSUE_FLOW_WEB !== undefined) {\n layer.enabled = parseBooleanEnv(env.ISSUE_FLOW_WEB);\n }\n const port = readNumberEnv(env, 'ISSUE_FLOW_WEB_PORT', warn);\n if (port !== undefined) {\n layer.port = port;\n }\n if (env.ISSUE_FLOW_WEB_HOST !== undefined) {\n layer.host = env.ISSUE_FLOW_WEB_HOST;\n }\n const refresh = readNumberEnv(env, 'ISSUE_FLOW_WEB_REFRESH', warn);\n if (refresh !== undefined) {\n layer.refreshSeconds = refresh;\n }\n const logLimit = readNumberEnv(env, 'ISSUE_FLOW_WEB_LOG_LIMIT', warn);\n if (logLimit !== undefined) {\n layer.logLimit = logLimit;\n }\n return layer;\n}\n\nasync function readWebConfigFile(\n projectRoot: string | undefined,\n warn: (message: string) => void,\n): Promise<Partial<WebConfig>> {\n const file = await readProjectConfigFile(projectRoot, warn);\n const web = file?.web;\n if (web === undefined) {\n return {};\n }\n\n const result = webConfigSchema.partial().safeParse(web);\n if (!result.success) {\n warn(\n `Ignoring \"web\" key of ${PROJECT_CONFIG_FILENAME}: ${result.error.issues[0]?.message ?? 'invalid value'}.`,\n );\n return {};\n }\n return result.data;\n}\n\n/**\n * Resolve the web monitoring configuration with the documented precedence:\n * CLI flag > environment variable > .issue-flow.json > defaults.\n *\n * Never throws: missing or invalid sources degrade to the defaults with a\n * warning.\n */\nexport async function loadWebConfig(options: LoadWebConfigOptions = {}): Promise<WebConfig> {\n const warn = options.warn ?? printWarning;\n const cli = options.cli ?? webCliOverrides;\n const env = options.env ?? process.env;\n\n const fileLayer = await readWebConfigFile(options.projectRoot, warn);\n const envLayer = readWebConfigEnv(env, warn);\n const merged = { ...fileLayer, ...envLayer, ...cli };\n\n const result = webConfigSchema.safeParse(merged);\n if (result.success) {\n return result.data;\n }\n warn(\n `Invalid web monitoring configuration (${result.error.issues[0]?.message ?? 'invalid value'}); using defaults.`,\n );\n return webConfigSchema.parse({});\n}\n\n// ── Issue provider configuration ────────────────────────────────────────────\n\n/**\n * CLI overrides captured by the preAction hook in cli.ts (--local, --github,\n * --prefer-local, --prefer-github, --ask). Highest-precedence source consumed\n * by loadIssuesConfig().\n */\nlet issuesCliOverrides: Partial<IssuesConfig> = {};\n\nexport function setIssuesCliOverrides(overrides: Partial<IssuesConfig>): void {\n issuesCliOverrides = overrides;\n}\n\nexport interface LoadIssuesConfigOptions {\n /** CLI flag overrides. Defaults to the values set via setIssuesCliOverrides(). */\n cli?: Partial<IssuesConfig>;\n /** Directory containing .issue-flow.json. Defaults to the git project root. */\n projectRoot?: string;\n /** Warning sink. Defaults to printWarning. */\n warn?: (message: string) => void;\n}\n\nasync function readIssuesConfigFile(\n projectRoot: string | undefined,\n warn: (message: string) => void,\n): Promise<Partial<IssuesConfig>> {\n const file = await readProjectConfigFile(projectRoot, warn);\n const issues = file?.issues;\n if (issues === undefined) {\n return {};\n }\n\n const result = issuesConfigSchema.partial().safeParse(issues);\n if (!result.success) {\n warn(\n `Ignoring \"issues\" key of ${PROJECT_CONFIG_FILENAME}: ${result.error.issues[0]?.message ?? 'invalid value'}.`,\n );\n return {};\n }\n return result.data;\n}\n\n/**\n * Resolve the Issue provider configuration with the documented precedence:\n * CLI flag > .issue-flow.json > defaults.\n *\n * Never throws: missing or invalid sources degrade to the defaults with a\n * warning, and the defaults reproduce the GitHub-only behaviour.\n */\nexport async function loadIssuesConfig(\n options: LoadIssuesConfigOptions = {},\n): Promise<IssuesConfig> {\n const warn = options.warn ?? printWarning;\n const cli = options.cli ?? issuesCliOverrides;\n\n const fileLayer = await readIssuesConfigFile(options.projectRoot, warn);\n const merged = { ...fileLayer, ...cli };\n\n const result = issuesConfigSchema.safeParse(merged);\n if (result.success) {\n return result.data;\n }\n warn(\n `Invalid Issue provider configuration (${result.error.issues[0]?.message ?? 'invalid value'}); using defaults.`,\n );\n return issuesConfigSchema.parse({});\n}\n","import { type Options as ExecaOptions, execa } from 'execa';\n\nexport interface ExecResult {\n stdout: string;\n stderr: string;\n exitCode: number;\n}\n\n/**\n * Execute a command with arguments and capture its output.\n * Uses execa which calls execFile internally (no shell injection risk).\n * Does not throw on non-zero exit codes.\n */\nexport async function run(\n command: string,\n args: string[] = [],\n options?: ExecaOptions,\n): Promise<ExecResult> {\n const result = await execa(command, args, {\n reject: false,\n ...options,\n });\n\n return {\n stdout: result.stdout?.toString() ?? '',\n stderr: result.stderr?.toString() ?? '',\n exitCode: result.exitCode ?? 1,\n };\n}\n","import { run } from './shell.js';\n\n/**\n * Get the root directory of the current git repository.\n * Throws if not inside a git repository.\n */\nexport async function getProjectRoot(): Promise<string> {\n const result = await run('git', ['rev-parse', '--show-toplevel']);\n\n if (result.exitCode !== 0) {\n throw new Error(\n 'Not inside a git repository. Please run issue-flow from within a git project.',\n );\n }\n\n return result.stdout.trim();\n}\n\n/**\n * Get the current git branch name.\n * Returns an empty string if in detached HEAD state.\n */\nexport async function getCurrentBranch(): Promise<string> {\n const result = await run('git', ['branch', '--show-current']);\n\n if (result.exitCode !== 0) {\n throw new Error(\n 'Failed to detect git branch. Ensure git is installed and you are inside a repository.',\n );\n }\n\n return result.stdout.trim();\n}\n\nexport interface CommitInfo {\n hash: string;\n subject: string;\n}\n\n/**\n * Get the base branch of the repository: the remote HEAD (origin/HEAD) when\n * available, otherwise the first of main/master that exists locally.\n * Never throws; defaults to 'main'.\n */\nexport async function getBaseBranch(): Promise<string> {\n const remoteHead = await run('git', ['symbolic-ref', '--short', 'refs/remotes/origin/HEAD']);\n if (remoteHead.exitCode === 0) {\n const name = remoteHead.stdout.trim().replace(/^origin\\//, '');\n if (name) return name;\n }\n\n for (const candidate of ['main', 'master']) {\n const check = await run('git', ['rev-parse', '--verify', '--quiet', `refs/heads/${candidate}`]);\n if (check.exitCode === 0) return candidate;\n }\n\n return 'main';\n}\n\n/**\n * List commits on HEAD that are not on the given base branch (base..HEAD),\n * most recent first. Never throws; returns [] when the range cannot be\n * resolved (e.g. unknown base branch or shallow clone).\n */\nexport async function getCommitsSince(base: string): Promise<CommitInfo[]> {\n const result = await run('git', ['log', '--pretty=format:%h%x09%s', `${base}..HEAD`]);\n if (result.exitCode !== 0) return [];\n\n return result.stdout\n .split('\\n')\n .filter((line) => line.trim() !== '')\n .map((line) => {\n const tab = line.indexOf('\\t');\n return tab === -1\n ? { hash: line.trim(), subject: '' }\n : { hash: line.slice(0, tab), subject: line.slice(tab + 1) };\n });\n}\n"],"mappings":"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","names":[]}
@@ -1,13 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import {
3
- createConfig,
4
- getBaseBranch,
5
- getCommitsSince,
6
- getCurrentBranch,
7
- resolvePaths,
8
- run,
9
- validateDependencies
10
- } from "./chunk-PA3GI7LR.js";
11
2
  import {
12
3
  isTransientFailure,
13
4
  retryDelaySeconds,
@@ -17,6 +8,15 @@ import {
17
8
  applyPlaceholders,
18
9
  loadPrompt
19
10
  } from "./chunk-SLHZPB3C.js";
11
+ import {
12
+ createConfig,
13
+ getBaseBranch,
14
+ getCommitsSince,
15
+ getCurrentBranch,
16
+ resolvePaths,
17
+ run,
18
+ validateDependencies
19
+ } from "./chunk-4HT4EVYV.js";
20
20
  import {
21
21
  NullPublisher,
22
22
  allStoriesPass,
@@ -43,7 +43,7 @@ import {
43
43
  trimErrorMessage,
44
44
  useColor,
45
45
  useUnicode
46
- } from "./chunk-O5XI2CFF.js";
46
+ } from "./chunk-WZDCTAWZ.js";
47
47
 
48
48
  // src/core/engine.ts
49
49
  import { existsSync } from "fs";
@@ -565,4 +565,4 @@ export {
565
565
  publishGitState,
566
566
  runExecute
567
567
  };
568
- //# sourceMappingURL=chunk-DLWRUTRL.js.map
568
+ //# sourceMappingURL=chunk-7W5VGYXW.js.map
@@ -2,13 +2,15 @@
2
2
  import {
3
3
  readFileWithGrace,
4
4
  runPhaseWithRetry
5
- } from "./chunk-3ULIAC2J.js";
5
+ } from "./chunk-2CSHLHYP.js";
6
+ import {
7
+ issuePlaceholders,
8
+ resolveCommandIssue,
9
+ runHeadless
10
+ } from "./chunk-SVOWWZQJ.js";
6
11
  import {
7
12
  isTransientFailure
8
13
  } from "./chunk-HDV3YDTG.js";
9
- import {
10
- runHeadless
11
- } from "./chunk-KLBAHTXX.js";
12
14
  import {
13
15
  applyPlaceholders,
14
16
  loadPrompt
@@ -19,20 +21,25 @@ import {
19
21
  printError,
20
22
  printSuccess,
21
23
  saveTaskPlan
22
- } from "./chunk-O5XI2CFF.js";
24
+ } from "./chunk-WZDCTAWZ.js";
23
25
 
24
26
  // src/commands/prd.ts
25
27
  import { mkdir } from "fs/promises";
26
28
  import { join } from "path";
27
- async function runPrd(issue) {
29
+ async function runPrd(issue, resolvedIssue) {
28
30
  const issueNumber = issue.replace(/^#/, "");
29
31
  const issueDir = join("issues", issueNumber);
30
32
  const prdPath = join(issueDir, "prd.md");
33
+ const resolution = await resolveCommandIssue(issueNumber, resolvedIssue);
34
+ if (!resolution.ok) {
35
+ return resolution.code;
36
+ }
31
37
  await mkdir(issueDir, { recursive: true });
32
38
  const template = await loadPrompt("prd");
33
39
  const prompt = applyPlaceholders(template, {
34
40
  __ISSUE_NUMBER__: issueNumber,
35
- __PRD_PATH__: prdPath
41
+ __PRD_PATH__: prdPath,
42
+ ...issuePlaceholders(resolution.resolved)
36
43
  });
37
44
  const outcome = await runPhaseWithRetry({
38
45
  phase: "prd",
@@ -81,4 +88,4 @@ async function runPrd(issue) {
81
88
  export {
82
89
  runPrd
83
90
  };
84
- //# sourceMappingURL=chunk-JMSRJ43G.js.map
91
+ //# sourceMappingURL=chunk-EHX4J5G5.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/prd.ts"],"sourcesContent":["import { mkdir } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { runHeadless } from '../core/headless.js';\nimport { readFileWithGrace, runPhaseWithRetry } from '../core/phase-runner.js';\nimport { applyPlaceholders, loadPrompt } from '../core/prompt-resolver.js';\nimport { loadTaskPlan, saveTaskPlan } from '../core/state-manager.js';\nimport { getGlobalTimeout } from '../core/verbose.js';\nimport { issuePlaceholders, resolveCommandIssue } from '../issues/context.js';\nimport type { ResolvedIssue } from '../issues/types.js';\nimport { printError, printSuccess } from '../ui/logger.js';\nimport { isTransientFailure } from '../utils/retry.js';\n\nexport async function runPrd(issue: string, resolvedIssue?: ResolvedIssue): Promise<number> {\n const issueNumber = issue.replace(/^#/, '');\n const issueDir = join('issues', issueNumber);\n const prdPath = join(issueDir, 'prd.md');\n\n const resolution = await resolveCommandIssue(issueNumber, resolvedIssue);\n if (!resolution.ok) {\n return resolution.code;\n }\n\n await mkdir(issueDir, { recursive: true });\n\n const template = await loadPrompt('prd');\n const prompt = applyPlaceholders(template, {\n __ISSUE_NUMBER__: issueNumber,\n __PRD_PATH__: prdPath,\n ...issuePlaceholders(resolution.resolved),\n });\n\n const outcome = await runPhaseWithRetry({\n phase: 'prd',\n attempt: async () => {\n const result = await runHeadless({\n prompt,\n maxTurns: 25,\n timeout: getGlobalTimeout() ?? 300_000,\n outputFormat: 'text',\n allowedTools: ['Bash', 'Read', 'Glob', 'Grep', 'Write'],\n statusMessage: `Generating PRD for issue #${issueNumber}...`,\n });\n\n if (!result.success) {\n return {\n ok: false,\n transient: isTransientFailure(1, result.error ?? ''),\n error: `PRD generation failed: ${result.error}`,\n };\n }\n\n // Verify the file was created. A brief grace period absorbs a lag\n // between the Write tool completing and this process seeing it; if it\n // still isn't there, treat it as recoverable and let the phase retry.\n try {\n const content = await readFileWithGrace(prdPath);\n if (content.length < 10) {\n return { ok: false, transient: true, error: 'PRD file was created but appears empty' };\n }\n } catch {\n return { ok: false, transient: true, error: `PRD file was not created at ${prdPath}` };\n }\n\n return { ok: true };\n },\n });\n\n if (!outcome.ok) {\n printError(outcome.error ?? `PRD generation failed for issue #${issueNumber}`);\n return 1;\n }\n\n // Update pipeline state\n const tasksPath = join(issueDir, 'tasks.json');\n try {\n const plan = await loadTaskPlan(tasksPath);\n plan.pipeline.prdCompleted = true;\n await saveTaskPlan(tasksPath, plan);\n } catch {\n // tasks.json may not exist yet\n }\n\n printSuccess(`PRD saved to ${prdPath}`);\n return 0;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,aAAa;AACtB,SAAS,YAAY;AAWrB,eAAsB,OAAO,OAAe,eAAgD;AAC1F,QAAM,cAAc,MAAM,QAAQ,MAAM,EAAE;AAC1C,QAAM,WAAW,KAAK,UAAU,WAAW;AAC3C,QAAM,UAAU,KAAK,UAAU,QAAQ;AAEvC,QAAM,aAAa,MAAM,oBAAoB,aAAa,aAAa;AACvE,MAAI,CAAC,WAAW,IAAI;AAClB,WAAO,WAAW;AAAA,EACpB;AAEA,QAAM,MAAM,UAAU,EAAE,WAAW,KAAK,CAAC;AAEzC,QAAM,WAAW,MAAM,WAAW,KAAK;AACvC,QAAM,SAAS,kBAAkB,UAAU;AAAA,IACzC,kBAAkB;AAAA,IAClB,cAAc;AAAA,IACd,GAAG,kBAAkB,WAAW,QAAQ;AAAA,EAC1C,CAAC;AAED,QAAM,UAAU,MAAM,kBAAkB;AAAA,IACtC,OAAO;AAAA,IACP,SAAS,YAAY;AACnB,YAAM,SAAS,MAAM,YAAY;AAAA,QAC/B;AAAA,QACA,UAAU;AAAA,QACV,SAAS,iBAAiB,KAAK;AAAA,QAC/B,cAAc;AAAA,QACd,cAAc,CAAC,QAAQ,QAAQ,QAAQ,QAAQ,OAAO;AAAA,QACtD,eAAe,6BAA6B,WAAW;AAAA,MACzD,CAAC;AAED,UAAI,CAAC,OAAO,SAAS;AACnB,eAAO;AAAA,UACL,IAAI;AAAA,UACJ,WAAW,mBAAmB,GAAG,OAAO,SAAS,EAAE;AAAA,UACnD,OAAO,0BAA0B,OAAO,KAAK;AAAA,QAC/C;AAAA,MACF;AAKA,UAAI;AACF,cAAM,UAAU,MAAM,kBAAkB,OAAO;AAC/C,YAAI,QAAQ,SAAS,IAAI;AACvB,iBAAO,EAAE,IAAI,OAAO,WAAW,MAAM,OAAO,yCAAyC;AAAA,QACvF;AAAA,MACF,QAAQ;AACN,eAAO,EAAE,IAAI,OAAO,WAAW,MAAM,OAAO,+BAA+B,OAAO,GAAG;AAAA,MACvF;AAEA,aAAO,EAAE,IAAI,KAAK;AAAA,IACpB;AAAA,EACF,CAAC;AAED,MAAI,CAAC,QAAQ,IAAI;AACf,eAAW,QAAQ,SAAS,oCAAoC,WAAW,EAAE;AAC7E,WAAO;AAAA,EACT;AAGA,QAAM,YAAY,KAAK,UAAU,YAAY;AAC7C,MAAI;AACF,UAAM,OAAO,MAAM,aAAa,SAAS;AACzC,SAAK,SAAS,eAAe;AAC7B,UAAM,aAAa,WAAW,IAAI;AAAA,EACpC,QAAQ;AAAA,EAER;AAEA,eAAa,gBAAgB,OAAO,EAAE;AACtC,SAAO;AACT;","names":[]}
@@ -2,13 +2,15 @@
2
2
  import {
3
3
  readFileWithGrace,
4
4
  runPhaseWithRetry
5
- } from "./chunk-3ULIAC2J.js";
5
+ } from "./chunk-2CSHLHYP.js";
6
+ import {
7
+ issuePlaceholders,
8
+ resolveCommandIssue,
9
+ runHeadless
10
+ } from "./chunk-SVOWWZQJ.js";
6
11
  import {
7
12
  isTransientFailure
8
13
  } from "./chunk-HDV3YDTG.js";
9
- import {
10
- runHeadless
11
- } from "./chunk-KLBAHTXX.js";
12
14
  import {
13
15
  applyPlaceholders,
14
16
  loadPrompt
@@ -20,14 +22,18 @@ import {
20
22
  printSuccess,
21
23
  saveTaskPlan,
22
24
  taskPlanSchema
23
- } from "./chunk-O5XI2CFF.js";
25
+ } from "./chunk-WZDCTAWZ.js";
24
26
 
25
27
  // src/commands/plan.ts
26
28
  import { mkdir, readFile } from "fs/promises";
27
29
  import { join } from "path";
28
- async function runPlan(issue) {
30
+ async function runPlan(issue, resolvedIssue) {
29
31
  const issueNumber = issue.replace(/^#/, "");
30
32
  const issueDir = join("issues", issueNumber);
33
+ const resolution = await resolveCommandIssue(issueNumber, resolvedIssue);
34
+ if (!resolution.ok) {
35
+ return resolution.code;
36
+ }
31
37
  const prdPath = join(issueDir, "prd.md");
32
38
  let prdContent;
33
39
  try {
@@ -41,7 +47,8 @@ async function runPlan(issue) {
41
47
  const prompt = applyPlaceholders(template, {
42
48
  __ISSUE_NUMBER__: issueNumber,
43
49
  __PRD_CONTENT__: prdContent,
44
- __TASKS_PATH__: tasksPath
50
+ __TASKS_PATH__: tasksPath,
51
+ ...issuePlaceholders(resolution.resolved)
45
52
  });
46
53
  await mkdir(issueDir, { recursive: true });
47
54
  const outcome = await runPhaseWithRetry({
@@ -101,4 +108,4 @@ ${issues}`
101
108
  export {
102
109
  runPlan
103
110
  };
104
- //# sourceMappingURL=chunk-KMX3DUJN.js.map
111
+ //# sourceMappingURL=chunk-FPGHTTVV.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/plan.ts"],"sourcesContent":["import { mkdir, readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { runHeadless } from '../core/headless.js';\nimport { readFileWithGrace, runPhaseWithRetry } from '../core/phase-runner.js';\nimport { applyPlaceholders, loadPrompt } from '../core/prompt-resolver.js';\nimport { loadTaskPlan, saveTaskPlan } from '../core/state-manager.js';\nimport { getGlobalTimeout } from '../core/verbose.js';\nimport { issuePlaceholders, resolveCommandIssue } from '../issues/context.js';\nimport type { ResolvedIssue } from '../issues/types.js';\nimport { taskPlanSchema } from '../schemas.js';\nimport { printError, printSuccess } from '../ui/logger.js';\nimport { isTransientFailure } from '../utils/retry.js';\n\nexport async function runPlan(issue: string, resolvedIssue?: ResolvedIssue): Promise<number> {\n const issueNumber = issue.replace(/^#/, '');\n const issueDir = join('issues', issueNumber);\n\n const resolution = await resolveCommandIssue(issueNumber, resolvedIssue);\n if (!resolution.ok) {\n return resolution.code;\n }\n\n // Read the PRD\n const prdPath = join(issueDir, 'prd.md');\n let prdContent: string;\n try {\n prdContent = await readFile(prdPath, 'utf-8');\n } catch {\n printError(`PRD not found at ${prdPath}. Run 'issue-flow prd ${issueNumber}' first.`);\n return 1;\n }\n\n const tasksPath = join(issueDir, 'tasks.json');\n\n const template = await loadPrompt('plan');\n const prompt = applyPlaceholders(template, {\n __ISSUE_NUMBER__: issueNumber,\n __PRD_CONTENT__: prdContent,\n __TASKS_PATH__: tasksPath,\n ...issuePlaceholders(resolution.resolved),\n });\n\n await mkdir(issueDir, { recursive: true });\n\n const outcome = await runPhaseWithRetry({\n phase: 'plan',\n attempt: async () => {\n const result = await runHeadless({\n prompt,\n maxTurns: 25,\n timeout: getGlobalTimeout() ?? 300_000,\n outputFormat: 'text',\n allowedTools: ['Bash', 'Read', 'Glob', 'Grep', 'Write'],\n statusMessage: `Converting PRD to task plan for issue #${issueNumber}...`,\n });\n\n if (!result.success) {\n return {\n ok: false,\n transient: isTransientFailure(1, result.error ?? ''),\n error: `Task plan generation failed: ${result.error}`,\n };\n }\n\n // Verify the file was created, tolerating a brief FS-visibility lag.\n let rawContent: string;\n try {\n rawContent = await readFileWithGrace(tasksPath);\n } catch {\n return { ok: false, transient: true, error: `tasks.json was not created at ${tasksPath}` };\n }\n\n // Validate JSON structure — a content defect, not a timing issue, but\n // still worth a bounded retry since it's a fresh Claude invocation.\n let parsed: unknown;\n try {\n parsed = JSON.parse(rawContent);\n } catch {\n return { ok: false, transient: true, error: 'tasks.json contains invalid JSON' };\n }\n\n // Validate with zod schema\n const validation = taskPlanSchema.safeParse(parsed);\n if (!validation.success) {\n const issues = validation.error.issues\n .map((i) => ` - ${i.path.join('.')}: ${i.message}`)\n .join('\\n');\n return {\n ok: false,\n transient: true,\n error: `tasks.json does not match expected schema:\\n${issues}`,\n };\n }\n\n return { ok: true };\n },\n });\n\n if (!outcome.ok) {\n printError(outcome.error ?? `Task plan generation failed for issue #${issueNumber}`);\n return 1;\n }\n\n // Ensure pipeline state reflects completion of this phase\n const plan = await loadTaskPlan(tasksPath);\n plan.pipeline.jsonCompleted = true;\n await saveTaskPlan(tasksPath, plan);\n\n printSuccess(`Task plan saved to ${tasksPath} (${plan.userStories.length} stories)`);\n return 0;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,OAAO,gBAAgB;AAChC,SAAS,YAAY;AAYrB,eAAsB,QAAQ,OAAe,eAAgD;AAC3F,QAAM,cAAc,MAAM,QAAQ,MAAM,EAAE;AAC1C,QAAM,WAAW,KAAK,UAAU,WAAW;AAE3C,QAAM,aAAa,MAAM,oBAAoB,aAAa,aAAa;AACvE,MAAI,CAAC,WAAW,IAAI;AAClB,WAAO,WAAW;AAAA,EACpB;AAGA,QAAM,UAAU,KAAK,UAAU,QAAQ;AACvC,MAAI;AACJ,MAAI;AACF,iBAAa,MAAM,SAAS,SAAS,OAAO;AAAA,EAC9C,QAAQ;AACN,eAAW,oBAAoB,OAAO,yBAAyB,WAAW,UAAU;AACpF,WAAO;AAAA,EACT;AAEA,QAAM,YAAY,KAAK,UAAU,YAAY;AAE7C,QAAM,WAAW,MAAM,WAAW,MAAM;AACxC,QAAM,SAAS,kBAAkB,UAAU;AAAA,IACzC,kBAAkB;AAAA,IAClB,iBAAiB;AAAA,IACjB,gBAAgB;AAAA,IAChB,GAAG,kBAAkB,WAAW,QAAQ;AAAA,EAC1C,CAAC;AAED,QAAM,MAAM,UAAU,EAAE,WAAW,KAAK,CAAC;AAEzC,QAAM,UAAU,MAAM,kBAAkB;AAAA,IACtC,OAAO;AAAA,IACP,SAAS,YAAY;AACnB,YAAM,SAAS,MAAM,YAAY;AAAA,QAC/B;AAAA,QACA,UAAU;AAAA,QACV,SAAS,iBAAiB,KAAK;AAAA,QAC/B,cAAc;AAAA,QACd,cAAc,CAAC,QAAQ,QAAQ,QAAQ,QAAQ,OAAO;AAAA,QACtD,eAAe,0CAA0C,WAAW;AAAA,MACtE,CAAC;AAED,UAAI,CAAC,OAAO,SAAS;AACnB,eAAO;AAAA,UACL,IAAI;AAAA,UACJ,WAAW,mBAAmB,GAAG,OAAO,SAAS,EAAE;AAAA,UACnD,OAAO,gCAAgC,OAAO,KAAK;AAAA,QACrD;AAAA,MACF;AAGA,UAAI;AACJ,UAAI;AACF,qBAAa,MAAM,kBAAkB,SAAS;AAAA,MAChD,QAAQ;AACN,eAAO,EAAE,IAAI,OAAO,WAAW,MAAM,OAAO,iCAAiC,SAAS,GAAG;AAAA,MAC3F;AAIA,UAAI;AACJ,UAAI;AACF,iBAAS,KAAK,MAAM,UAAU;AAAA,MAChC,QAAQ;AACN,eAAO,EAAE,IAAI,OAAO,WAAW,MAAM,OAAO,mCAAmC;AAAA,MACjF;AAGA,YAAM,aAAa,eAAe,UAAU,MAAM;AAClD,UAAI,CAAC,WAAW,SAAS;AACvB,cAAM,SAAS,WAAW,MAAM,OAC7B,IAAI,CAAC,MAAM,OAAO,EAAE,KAAK,KAAK,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,EAClD,KAAK,IAAI;AACZ,eAAO;AAAA,UACL,IAAI;AAAA,UACJ,WAAW;AAAA,UACX,OAAO;AAAA,EAA+C,MAAM;AAAA,QAC9D;AAAA,MACF;AAEA,aAAO,EAAE,IAAI,KAAK;AAAA,IACpB;AAAA,EACF,CAAC;AAED,MAAI,CAAC,QAAQ,IAAI;AACf,eAAW,QAAQ,SAAS,0CAA0C,WAAW,EAAE;AACnF,WAAO;AAAA,EACT;AAGA,QAAM,OAAO,MAAM,aAAa,SAAS;AACzC,OAAK,SAAS,gBAAgB;AAC9B,QAAM,aAAa,WAAW,IAAI;AAElC,eAAa,sBAAsB,SAAS,KAAK,KAAK,YAAY,MAAM,WAAW;AACnF,SAAO;AACT;","names":[]}
@@ -1,7 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ issuePlaceholders,
4
+ resolveCommandIssue,
3
5
  runHeadless
4
- } from "./chunk-KLBAHTXX.js";
6
+ } from "./chunk-SVOWWZQJ.js";
5
7
  import {
6
8
  applyPlaceholders,
7
9
  loadPrompt
@@ -12,7 +14,7 @@ import {
12
14
  printError,
13
15
  printSuccess,
14
16
  saveTaskPlan
15
- } from "./chunk-O5XI2CFF.js";
17
+ } from "./chunk-WZDCTAWZ.js";
16
18
 
17
19
  // src/commands/review.ts
18
20
  import { join } from "path";
@@ -38,14 +40,19 @@ function parseReviewResult(output) {
38
40
  findings: findingsMatch?.[1]?.trim() ?? "Unknown findings"
39
41
  };
40
42
  }
41
- async function runReview(issue) {
43
+ async function runReview(issue, resolvedIssue) {
42
44
  const issueNumber = issue.replace(/^#/, "");
43
45
  const issueDir = join("issues", issueNumber);
44
46
  const tasksPath = join(issueDir, "tasks.json");
47
+ const resolution = await resolveCommandIssue(issueNumber, resolvedIssue);
48
+ if (!resolution.ok) {
49
+ return resolution.code;
50
+ }
45
51
  const template = await loadPrompt("review");
46
52
  const prompt = applyPlaceholders(template, {
47
53
  __ISSUE_NUMBER__: issueNumber,
48
- __TASKS_PATH__: tasksPath
54
+ __TASKS_PATH__: tasksPath,
55
+ ...issuePlaceholders(resolution.resolved)
49
56
  });
50
57
  const result = await runHeadless({
51
58
  prompt,
@@ -80,4 +87,4 @@ async function runReview(issue) {
80
87
  export {
81
88
  runReview
82
89
  };
83
- //# sourceMappingURL=chunk-OEJSMZMO.js.map
90
+ //# sourceMappingURL=chunk-JTSAL3TI.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/review.ts"],"sourcesContent":["import { join } from 'node:path';\nimport { runHeadless } from '../core/headless.js';\nimport { applyPlaceholders, loadPrompt } from '../core/prompt-resolver.js';\nimport { loadTaskPlan, saveTaskPlan } from '../core/state-manager.js';\nimport { getGlobalTimeout } from '../core/verbose.js';\nimport { issuePlaceholders, resolveCommandIssue } from '../issues/context.js';\nimport type { ResolvedIssue } from '../issues/types.js';\nimport { printError, printSuccess } from '../ui/logger.js';\n\nexport interface ReviewResult {\n status: 'PASS' | 'FAIL';\n findings?: string;\n}\n\n/**\n * Parse the <review-result> block from headless output.\n */\nfunction parseReviewResult(output: string): ReviewResult {\n const match = output.match(/<review-result>([\\s\\S]*?)<\\/review-result>/);\n if (!match) {\n // Try to detect PASS/FAIL from the raw output\n if (/STATUS:\\s*PASS/i.test(output)) {\n return { status: 'PASS' };\n }\n if (/STATUS:\\s*FAIL/i.test(output)) {\n const findingsMatch = output.match(/FINDINGS:([\\s\\S]*?)(?:$|<\\/)/);\n return { status: 'FAIL', findings: findingsMatch?.[1]?.trim() ?? 'Unknown findings' };\n }\n // Default to PASS if no explicit status found and no errors\n return { status: 'PASS' };\n }\n\n const block = match[1];\n if (/STATUS:\\s*PASS/i.test(block)) {\n return { status: 'PASS' };\n }\n\n const findingsMatch = block.match(/FINDINGS:([\\s\\S]*)/);\n return {\n status: 'FAIL',\n findings: findingsMatch?.[1]?.trim() ?? 'Unknown findings',\n };\n}\n\nexport async function runReview(issue: string, resolvedIssue?: ResolvedIssue): Promise<number> {\n const issueNumber = issue.replace(/^#/, '');\n const issueDir = join('issues', issueNumber);\n const tasksPath = join(issueDir, 'tasks.json');\n\n const resolution = await resolveCommandIssue(issueNumber, resolvedIssue);\n if (!resolution.ok) {\n return resolution.code;\n }\n\n const template = await loadPrompt('review');\n const prompt = applyPlaceholders(template, {\n __ISSUE_NUMBER__: issueNumber,\n __TASKS_PATH__: tasksPath,\n ...issuePlaceholders(resolution.resolved),\n });\n\n const result = await runHeadless({\n prompt,\n maxTurns: 25,\n timeout: getGlobalTimeout() ?? 300_000,\n outputFormat: 'text',\n allowedTools: ['Bash', 'Read', 'Glob', 'Grep'],\n statusMessage: `Reviewing issue #${issueNumber} resolution...`,\n });\n\n if (!result.success) {\n printError(`Review failed: ${result.error}`);\n return 1;\n }\n\n const review = parseReviewResult(result.result);\n\n if (review.status === 'PASS') {\n // Update pipeline state\n try {\n const plan = await loadTaskPlan(tasksPath);\n plan.pipeline.reviewCompleted = true;\n await saveTaskPlan(tasksPath, plan);\n } catch {\n // tasks.json may not exist\n }\n\n printSuccess('Review PASSED — implementation meets acceptance criteria');\n return 0;\n }\n\n printError('Review FAILED — issues found:');\n if (review.findings) {\n console.log(review.findings);\n }\n return 1;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAAA,SAAS,YAAY;AAiBrB,SAAS,kBAAkB,QAA8B;AACvD,QAAM,QAAQ,OAAO,MAAM,4CAA4C;AACvE,MAAI,CAAC,OAAO;AAEV,QAAI,kBAAkB,KAAK,MAAM,GAAG;AAClC,aAAO,EAAE,QAAQ,OAAO;AAAA,IAC1B;AACA,QAAI,kBAAkB,KAAK,MAAM,GAAG;AAClC,YAAMA,iBAAgB,OAAO,MAAM,8BAA8B;AACjE,aAAO,EAAE,QAAQ,QAAQ,UAAUA,iBAAgB,CAAC,GAAG,KAAK,KAAK,mBAAmB;AAAA,IACtF;AAEA,WAAO,EAAE,QAAQ,OAAO;AAAA,EAC1B;AAEA,QAAM,QAAQ,MAAM,CAAC;AACrB,MAAI,kBAAkB,KAAK,KAAK,GAAG;AACjC,WAAO,EAAE,QAAQ,OAAO;AAAA,EAC1B;AAEA,QAAM,gBAAgB,MAAM,MAAM,oBAAoB;AACtD,SAAO;AAAA,IACL,QAAQ;AAAA,IACR,UAAU,gBAAgB,CAAC,GAAG,KAAK,KAAK;AAAA,EAC1C;AACF;AAEA,eAAsB,UAAU,OAAe,eAAgD;AAC7F,QAAM,cAAc,MAAM,QAAQ,MAAM,EAAE;AAC1C,QAAM,WAAW,KAAK,UAAU,WAAW;AAC3C,QAAM,YAAY,KAAK,UAAU,YAAY;AAE7C,QAAM,aAAa,MAAM,oBAAoB,aAAa,aAAa;AACvE,MAAI,CAAC,WAAW,IAAI;AAClB,WAAO,WAAW;AAAA,EACpB;AAEA,QAAM,WAAW,MAAM,WAAW,QAAQ;AAC1C,QAAM,SAAS,kBAAkB,UAAU;AAAA,IACzC,kBAAkB;AAAA,IAClB,gBAAgB;AAAA,IAChB,GAAG,kBAAkB,WAAW,QAAQ;AAAA,EAC1C,CAAC;AAED,QAAM,SAAS,MAAM,YAAY;AAAA,IAC/B;AAAA,IACA,UAAU;AAAA,IACV,SAAS,iBAAiB,KAAK;AAAA,IAC/B,cAAc;AAAA,IACd,cAAc,CAAC,QAAQ,QAAQ,QAAQ,MAAM;AAAA,IAC7C,eAAe,oBAAoB,WAAW;AAAA,EAChD,CAAC;AAED,MAAI,CAAC,OAAO,SAAS;AACnB,eAAW,kBAAkB,OAAO,KAAK,EAAE;AAC3C,WAAO;AAAA,EACT;AAEA,QAAM,SAAS,kBAAkB,OAAO,MAAM;AAE9C,MAAI,OAAO,WAAW,QAAQ;AAE5B,QAAI;AACF,YAAM,OAAO,MAAM,aAAa,SAAS;AACzC,WAAK,SAAS,kBAAkB;AAChC,YAAM,aAAa,WAAW,IAAI;AAAA,IACpC,QAAQ;AAAA,IAER;AAEA,iBAAa,+DAA0D;AACvE,WAAO;AAAA,EACT;AAEA,aAAW,oCAA+B;AAC1C,MAAI,OAAO,UAAU;AACnB,YAAQ,IAAI,OAAO,QAAQ;AAAA,EAC7B;AACA,SAAO;AACT;","names":["findingsMatch"]}
@@ -1,13 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  runPhaseWithRetry
4
- } from "./chunk-3ULIAC2J.js";
4
+ } from "./chunk-2CSHLHYP.js";
5
+ import {
6
+ issuePlaceholders,
7
+ resolveCommandIssue,
8
+ runHeadless
9
+ } from "./chunk-SVOWWZQJ.js";
5
10
  import {
6
11
  isTransientFailure
7
12
  } from "./chunk-HDV3YDTG.js";
8
- import {
9
- runHeadless
10
- } from "./chunk-KLBAHTXX.js";
11
13
  import {
12
14
  applyPlaceholders,
13
15
  loadPrompt
@@ -18,7 +20,7 @@ import {
18
20
  printError,
19
21
  printSuccess,
20
22
  saveTaskPlan
21
- } from "./chunk-O5XI2CFF.js";
23
+ } from "./chunk-WZDCTAWZ.js";
22
24
 
23
25
  // src/commands/pr.ts
24
26
  import { join } from "path";
@@ -27,10 +29,20 @@ function parsePrUrl(output) {
27
29
  const match = output.match(/(https:\/\/github\.com\/[^\s]+\/pull\/\d+)/);
28
30
  return match?.[1] ?? null;
29
31
  }
30
- async function runPr(issue) {
32
+ function issueClosesLine(issue, fallbackId) {
33
+ if (issue.remoteRef === null) {
34
+ return "";
35
+ }
36
+ return `Closes #${issue.number ?? fallbackId}`;
37
+ }
38
+ async function runPr(issue, resolvedIssue) {
31
39
  const issueNumber = issue.replace(/^#/, "");
32
40
  const issueDir = join("issues", issueNumber);
33
41
  const tasksPath = join(issueDir, "tasks.json");
42
+ const resolution = await resolveCommandIssue(issueNumber, resolvedIssue);
43
+ if (!resolution.ok) {
44
+ return resolution.code;
45
+ }
34
46
  let branchName;
35
47
  try {
36
48
  const proc = await execa("git", ["branch", "--show-current"], { reject: false });
@@ -47,7 +59,9 @@ async function runPr(issue) {
47
59
  const prompt = applyPlaceholders(template, {
48
60
  __ISSUE_NUMBER__: issueNumber,
49
61
  __BRANCH_NAME__: branchName,
50
- __TASKS_PATH__: tasksPath
62
+ __TASKS_PATH__: tasksPath,
63
+ __ISSUE_CLOSES__: issueClosesLine(resolution.resolved.issue, issueNumber),
64
+ ...issuePlaceholders(resolution.resolved)
51
65
  });
52
66
  let headlessOutput = "";
53
67
  const outcome = await runPhaseWithRetry({
@@ -94,4 +108,4 @@ async function runPr(issue) {
94
108
  export {
95
109
  runPr
96
110
  };
97
- //# sourceMappingURL=chunk-LJI45O5B.js.map
111
+ //# sourceMappingURL=chunk-KWGRBNUG.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/pr.ts"],"sourcesContent":["import { join } from 'node:path';\nimport { execa } from 'execa';\nimport { runHeadless } from '../core/headless.js';\nimport { runPhaseWithRetry } from '../core/phase-runner.js';\nimport { applyPlaceholders, loadPrompt } from '../core/prompt-resolver.js';\nimport { loadTaskPlan, saveTaskPlan } from '../core/state-manager.js';\nimport { getGlobalTimeout } from '../core/verbose.js';\nimport { issuePlaceholders, resolveCommandIssue } from '../issues/context.js';\nimport type { Issue, ResolvedIssue } from '../issues/types.js';\nimport { printError, printSuccess } from '../ui/logger.js';\nimport { isTransientFailure } from '../utils/retry.js';\n\n/**\n * Extract a PR URL from headless output.\n */\nfunction parsePrUrl(output: string): string | null {\n const match = output.match(/(https:\\/\\/github\\.com\\/[^\\s]+\\/pull\\/\\d+)/);\n return match?.[1] ?? null;\n}\n\n/**\n * The `Closes #N` line for the PR body, empty when the Issue has no remote\n * counterpart: GitHub only understands the reference for Issues it hosts, and\n * an invented `#N` would silently point at an unrelated Issue.\n */\nfunction issueClosesLine(issue: Issue, fallbackId: string): string {\n if (issue.remoteRef === null) {\n return '';\n }\n return `Closes #${issue.number ?? fallbackId}`;\n}\n\nexport async function runPr(issue: string, resolvedIssue?: ResolvedIssue): Promise<number> {\n const issueNumber = issue.replace(/^#/, '');\n const issueDir = join('issues', issueNumber);\n const tasksPath = join(issueDir, 'tasks.json');\n\n const resolution = await resolveCommandIssue(issueNumber, resolvedIssue);\n if (!resolution.ok) {\n return resolution.code;\n }\n\n // Get current branch\n let branchName: string;\n try {\n const proc = await execa('git', ['branch', '--show-current'], { reject: false });\n branchName = proc.stdout?.toString().trim() ?? '';\n if (!branchName) {\n printError('Could not determine current branch');\n return 1;\n }\n } catch {\n printError('Failed to get current branch');\n return 1;\n }\n\n const template = await loadPrompt('pr');\n const prompt = applyPlaceholders(template, {\n __ISSUE_NUMBER__: issueNumber,\n __BRANCH_NAME__: branchName,\n __TASKS_PATH__: tasksPath,\n __ISSUE_CLOSES__: issueClosesLine(resolution.resolved.issue, issueNumber),\n ...issuePlaceholders(resolution.resolved),\n });\n\n let headlessOutput = '';\n\n const outcome = await runPhaseWithRetry({\n phase: 'pr',\n attempt: async () => {\n const result = await runHeadless({\n prompt,\n maxTurns: 15,\n timeout: getGlobalTimeout() ?? 300_000,\n outputFormat: 'text',\n allowedTools: ['Bash', 'Read', 'Glob', 'Grep'],\n statusMessage: `Creating PR for issue #${issueNumber}...`,\n });\n\n if (!result.success) {\n return {\n ok: false,\n transient: isTransientFailure(1, result.error ?? ''),\n error: `PR creation failed: ${result.error}`,\n };\n }\n\n headlessOutput = result.result;\n return { ok: true };\n },\n });\n\n if (!outcome.ok) {\n printError(outcome.error ?? `PR creation failed for issue #${issueNumber}`);\n return 1;\n }\n\n const prUrl = parsePrUrl(headlessOutput);\n\n // Update pipeline state\n try {\n const plan = await loadTaskPlan(tasksPath);\n plan.pipeline.prCreated = true;\n await saveTaskPlan(tasksPath, plan);\n } catch {\n // tasks.json may not exist\n }\n\n if (prUrl) {\n printSuccess(`PR created: ${prUrl}`);\n } else {\n printSuccess('PR creation completed');\n }\n return 0;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,YAAY;AACrB,SAAS,aAAa;AActB,SAAS,WAAW,QAA+B;AACjD,QAAM,QAAQ,OAAO,MAAM,4CAA4C;AACvE,SAAO,QAAQ,CAAC,KAAK;AACvB;AAOA,SAAS,gBAAgB,OAAc,YAA4B;AACjE,MAAI,MAAM,cAAc,MAAM;AAC5B,WAAO;AAAA,EACT;AACA,SAAO,WAAW,MAAM,UAAU,UAAU;AAC9C;AAEA,eAAsB,MAAM,OAAe,eAAgD;AACzF,QAAM,cAAc,MAAM,QAAQ,MAAM,EAAE;AAC1C,QAAM,WAAW,KAAK,UAAU,WAAW;AAC3C,QAAM,YAAY,KAAK,UAAU,YAAY;AAE7C,QAAM,aAAa,MAAM,oBAAoB,aAAa,aAAa;AACvE,MAAI,CAAC,WAAW,IAAI;AAClB,WAAO,WAAW;AAAA,EACpB;AAGA,MAAI;AACJ,MAAI;AACF,UAAM,OAAO,MAAM,MAAM,OAAO,CAAC,UAAU,gBAAgB,GAAG,EAAE,QAAQ,MAAM,CAAC;AAC/E,iBAAa,KAAK,QAAQ,SAAS,EAAE,KAAK,KAAK;AAC/C,QAAI,CAAC,YAAY;AACf,iBAAW,oCAAoC;AAC/C,aAAO;AAAA,IACT;AAAA,EACF,QAAQ;AACN,eAAW,8BAA8B;AACzC,WAAO;AAAA,EACT;AAEA,QAAM,WAAW,MAAM,WAAW,IAAI;AACtC,QAAM,SAAS,kBAAkB,UAAU;AAAA,IACzC,kBAAkB;AAAA,IAClB,iBAAiB;AAAA,IACjB,gBAAgB;AAAA,IAChB,kBAAkB,gBAAgB,WAAW,SAAS,OAAO,WAAW;AAAA,IACxE,GAAG,kBAAkB,WAAW,QAAQ;AAAA,EAC1C,CAAC;AAED,MAAI,iBAAiB;AAErB,QAAM,UAAU,MAAM,kBAAkB;AAAA,IACtC,OAAO;AAAA,IACP,SAAS,YAAY;AACnB,YAAM,SAAS,MAAM,YAAY;AAAA,QAC/B;AAAA,QACA,UAAU;AAAA,QACV,SAAS,iBAAiB,KAAK;AAAA,QAC/B,cAAc;AAAA,QACd,cAAc,CAAC,QAAQ,QAAQ,QAAQ,MAAM;AAAA,QAC7C,eAAe,0BAA0B,WAAW;AAAA,MACtD,CAAC;AAED,UAAI,CAAC,OAAO,SAAS;AACnB,eAAO;AAAA,UACL,IAAI;AAAA,UACJ,WAAW,mBAAmB,GAAG,OAAO,SAAS,EAAE;AAAA,UACnD,OAAO,uBAAuB,OAAO,KAAK;AAAA,QAC5C;AAAA,MACF;AAEA,uBAAiB,OAAO;AACxB,aAAO,EAAE,IAAI,KAAK;AAAA,IACpB;AAAA,EACF,CAAC;AAED,MAAI,CAAC,QAAQ,IAAI;AACf,eAAW,QAAQ,SAAS,iCAAiC,WAAW,EAAE;AAC1E,WAAO;AAAA,EACT;AAEA,QAAM,QAAQ,WAAW,cAAc;AAGvC,MAAI;AACF,UAAM,OAAO,MAAM,aAAa,SAAS;AACzC,SAAK,SAAS,YAAY;AAC1B,UAAM,aAAa,WAAW,IAAI;AAAA,EACpC,QAAQ;AAAA,EAER;AAEA,MAAI,OAAO;AACT,iBAAa,eAAe,KAAK,EAAE;AAAA,EACrC,OAAO;AACL,iBAAa,uBAAuB;AAAA,EACtC;AACA,SAAO;AACT;","names":[]}