issue-flow 0.4.3 → 0.5.1

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 (65) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +38 -1
  3. package/dist/{chunk-MEB3B2FI.js → analyze-IJ2SYXVM.js} +7 -10
  4. package/dist/{chunk-MEB3B2FI.js.map → analyze-IJ2SYXVM.js.map} +1 -1
  5. package/dist/{chunk-4OQ2ARLP.js → chunk-2XWXVWVD.js} +10 -2
  6. package/dist/chunk-2XWXVWVD.js.map +1 -0
  7. package/dist/{chunk-MWUIIUAS.js → chunk-AY7UZAGX.js} +8 -10
  8. package/dist/{chunk-MWUIIUAS.js.map → chunk-AY7UZAGX.js.map} +1 -1
  9. package/dist/chunk-FNLXZXWP.js +846 -0
  10. package/dist/chunk-FNLXZXWP.js.map +1 -0
  11. package/dist/{chunk-HYP64MZI.js → chunk-GYR4YHEB.js} +110 -116
  12. package/dist/chunk-GYR4YHEB.js.map +1 -0
  13. package/dist/{chunk-CYWHYPTU.js → chunk-MKQIP7YW.js} +7 -9
  14. package/dist/{chunk-CYWHYPTU.js.map → chunk-MKQIP7YW.js.map} +1 -1
  15. package/dist/{chunk-CSSXTPWE.js → chunk-Q2TQCJDN.js} +2 -2
  16. package/dist/{chunk-2JZCWJEQ.js → chunk-SI4ALORB.js} +7 -21
  17. package/dist/chunk-SI4ALORB.js.map +1 -0
  18. package/dist/{chunk-OZVHOVDT.js → chunk-SLHZPB3C.js} +15 -8
  19. package/dist/chunk-SLHZPB3C.js.map +1 -0
  20. package/dist/chunk-TYOFZ6DD.js +240 -0
  21. package/dist/chunk-TYOFZ6DD.js.map +1 -0
  22. package/dist/{chunk-4PZXIOXD.js → chunk-XJJP4TSK.js} +7 -9
  23. package/dist/{chunk-4PZXIOXD.js.map → chunk-XJJP4TSK.js.map} +1 -1
  24. package/dist/cli.js +56 -19
  25. package/dist/cli.js.map +1 -1
  26. package/dist/execute-DALVYZIH.js +11 -0
  27. package/dist/{generate-HP4CPMJG.js → generate-5OWWNRIB.js} +4 -4
  28. package/dist/init-6AWQI3IE.js +9 -0
  29. package/dist/plan-RQXK2OCP.js +11 -0
  30. package/dist/pr-UU4H7ZZR.js +11 -0
  31. package/dist/prd-WB3QH6QF.js +11 -0
  32. package/dist/review-2WHYJG7M.js +11 -0
  33. package/dist/run-UCSNYSQO.js +756 -0
  34. package/dist/run-UCSNYSQO.js.map +1 -0
  35. package/package.json +13 -3
  36. package/prompts/prd.md +5 -4
  37. package/web/public/app.css +481 -0
  38. package/web/public/app.js +522 -0
  39. package/web/public/index.html +113 -0
  40. package/dist/analyze-3SN7ZAZU.js +0 -12
  41. package/dist/chunk-2JZCWJEQ.js.map +0 -1
  42. package/dist/chunk-4OQ2ARLP.js.map +0 -1
  43. package/dist/chunk-64FU2L2T.js +0 -180
  44. package/dist/chunk-64FU2L2T.js.map +0 -1
  45. package/dist/chunk-HYP64MZI.js.map +0 -1
  46. package/dist/chunk-OZVHOVDT.js.map +0 -1
  47. package/dist/chunk-YAFHVFV5.js +0 -199
  48. package/dist/chunk-YAFHVFV5.js.map +0 -1
  49. package/dist/execute-GIA4IKS5.js +0 -11
  50. package/dist/init-SXFGOCML.js +0 -9
  51. package/dist/plan-HWZRG5GP.js +0 -12
  52. package/dist/pr-TTOFYUCV.js +0 -12
  53. package/dist/prd-ET2QN7GB.js +0 -12
  54. package/dist/review-3QDZCOMT.js +0 -12
  55. package/dist/review-3QDZCOMT.js.map +0 -1
  56. package/dist/run-PKHPULYL.js +0 -451
  57. package/dist/run-PKHPULYL.js.map +0 -1
  58. /package/dist/{chunk-CSSXTPWE.js.map → chunk-Q2TQCJDN.js.map} +0 -0
  59. /package/dist/{analyze-3SN7ZAZU.js.map → execute-DALVYZIH.js.map} +0 -0
  60. /package/dist/{generate-HP4CPMJG.js.map → generate-5OWWNRIB.js.map} +0 -0
  61. /package/dist/{execute-GIA4IKS5.js.map → init-6AWQI3IE.js.map} +0 -0
  62. /package/dist/{init-SXFGOCML.js.map → plan-RQXK2OCP.js.map} +0 -0
  63. /package/dist/{plan-HWZRG5GP.js.map → pr-UU4H7ZZR.js.map} +0 -0
  64. /package/dist/{pr-TTOFYUCV.js.map → prd-WB3QH6QF.js.map} +0 -0
  65. /package/dist/{prd-ET2QN7GB.js.map → review-2WHYJG7M.js.map} +0 -0
@@ -1,20 +1,18 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  runHeadless
4
- } from "./chunk-4OQ2ARLP.js";
5
- import {
6
- loadTaskPlan,
7
- saveTaskPlan
8
- } from "./chunk-64FU2L2T.js";
4
+ } from "./chunk-2XWXVWVD.js";
9
5
  import {
10
6
  applyPlaceholders,
11
7
  loadPrompt
12
- } from "./chunk-OZVHOVDT.js";
8
+ } from "./chunk-SLHZPB3C.js";
13
9
  import {
14
10
  getGlobalTimeout,
11
+ loadTaskPlan,
15
12
  printError,
16
- printSuccess
17
- } from "./chunk-YAFHVFV5.js";
13
+ printSuccess,
14
+ saveTaskPlan
15
+ } from "./chunk-FNLXZXWP.js";
18
16
 
19
17
  // src/commands/prd.ts
20
18
  import { mkdir, readFile } from "fs/promises";
@@ -24,21 +22,9 @@ async function runPrd(issue) {
24
22
  const issueDir = join("issues", issueNumber);
25
23
  const prdPath = join(issueDir, "prd.md");
26
24
  await mkdir(issueDir, { recursive: true });
27
- let analysisContext = "";
28
- const analysisPath = join(issueDir, "analysis.md");
29
- try {
30
- const content = await readFile(analysisPath, "utf-8");
31
- analysisContext = `
32
-
33
- Here is the existing analysis for this issue:
34
-
35
- ${content}`;
36
- } catch {
37
- }
38
25
  const template = await loadPrompt("prd");
39
26
  const prompt = applyPlaceholders(template, {
40
27
  __ISSUE_NUMBER__: issueNumber,
41
- __ANALYSIS_CONTEXT__: analysisContext,
42
28
  __PRD_PATH__: prdPath
43
29
  });
44
30
  const result = await runHeadless({
@@ -77,4 +63,4 @@ ${content}`;
77
63
  export {
78
64
  runPrd
79
65
  };
80
- //# sourceMappingURL=chunk-2JZCWJEQ.js.map
66
+ //# sourceMappingURL=chunk-SI4ALORB.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/prd.ts"],"sourcesContent":["import { mkdir, readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { runHeadless } from '../core/headless.js';\nimport { applyPlaceholders, loadPrompt } from '../core/prompt-resolver.js';\nimport { loadTaskPlan, saveTaskPlan } from '../core/state-manager.js';\nimport { getGlobalTimeout } from '../core/verbose.js';\nimport { printError, printSuccess } from '../ui/logger.js';\n\nexport async function runPrd(issue: string): Promise<number> {\n const issueNumber = issue.replace(/^#/, '');\n const issueDir = join('issues', issueNumber);\n const prdPath = join(issueDir, 'prd.md');\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 });\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', 'Write'],\n statusMessage: `Generating PRD for issue #${issueNumber}...`,\n });\n\n if (!result.success) {\n printError(`PRD generation failed: ${result.error}`);\n return 1;\n }\n\n // Verify the file was created\n try {\n const content = await readFile(prdPath, 'utf-8');\n if (content.length < 10) {\n printError('PRD file was created but appears empty');\n return 1;\n }\n } catch {\n printError(`PRD file was not created at ${prdPath}`);\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,OAAO,gBAAgB;AAChC,SAAS,YAAY;AAOrB,eAAsB,OAAO,OAAgC;AAC3D,QAAM,cAAc,MAAM,QAAQ,MAAM,EAAE;AAC1C,QAAM,WAAW,KAAK,UAAU,WAAW;AAC3C,QAAM,UAAU,KAAK,UAAU,QAAQ;AAEvC,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,EAChB,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,6BAA6B,WAAW;AAAA,EACzD,CAAC;AAED,MAAI,CAAC,OAAO,SAAS;AACnB,eAAW,0BAA0B,OAAO,KAAK,EAAE;AACnD,WAAO;AAAA,EACT;AAGA,MAAI;AACF,UAAM,UAAU,MAAM,SAAS,SAAS,OAAO;AAC/C,QAAI,QAAQ,SAAS,IAAI;AACvB,iBAAW,wCAAwC;AACnD,aAAO;AAAA,IACT;AAAA,EACF,QAAQ;AACN,eAAW,+BAA+B,OAAO,EAAE;AACnD,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":[]}
@@ -5,19 +5,25 @@ import { existsSync } from "fs";
5
5
  import { readFile } from "fs/promises";
6
6
  import { dirname, join } from "path";
7
7
  import { fileURLToPath } from "url";
8
- function getPromptsDir() {
9
- const currentFile = fileURLToPath(import.meta.url);
10
- let dir = dirname(currentFile);
8
+ function resolvePackageDir(name, startDir) {
9
+ let dir = startDir ?? dirname(fileURLToPath(import.meta.url));
11
10
  for (let i = 0; i < 5; i++) {
12
- const candidate = join(dir, "prompts");
11
+ const candidate = join(dir, name);
13
12
  if (existsSync(candidate)) {
14
13
  return candidate;
15
14
  }
16
15
  dir = dirname(dir);
17
16
  }
18
- throw new Error(
19
- "Could not locate the prompts/ directory. Ensure the package is installed correctly."
20
- );
17
+ return null;
18
+ }
19
+ function getPromptsDir() {
20
+ const dir = resolvePackageDir("prompts");
21
+ if (dir === null) {
22
+ throw new Error(
23
+ "Could not locate the prompts/ directory. Ensure the package is installed correctly."
24
+ );
25
+ }
26
+ return dir;
21
27
  }
22
28
  async function loadPrompt(name) {
23
29
  const promptsDir = getPromptsDir();
@@ -37,7 +43,8 @@ function applyPlaceholders(template, vars) {
37
43
  }
38
44
 
39
45
  export {
46
+ resolvePackageDir,
40
47
  loadPrompt,
41
48
  applyPlaceholders
42
49
  };
43
- //# sourceMappingURL=chunk-OZVHOVDT.js.map
50
+ //# sourceMappingURL=chunk-SLHZPB3C.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/prompt-resolver.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { readFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\n/**\n * Resolve the absolute path to a directory shipped at the package root\n * (e.g. 'prompts', 'web/public'). Works from both source (src/core/) and\n * compiled (dist/) locations by walking up the directory tree until the\n * named folder is found. Returns null when it cannot be located.\n *\n * `startDir` is injectable for tests; it defaults to this module's directory.\n */\nexport function resolvePackageDir(name: string, startDir?: string): string | null {\n let dir = startDir ?? dirname(fileURLToPath(import.meta.url));\n\n for (let i = 0; i < 5; i++) {\n const candidate = join(dir, name);\n if (existsSync(candidate)) {\n return candidate;\n }\n dir = dirname(dir);\n }\n\n return null;\n}\n\nfunction getPromptsDir(): string {\n const dir = resolvePackageDir('prompts');\n if (dir === null) {\n throw new Error(\n 'Could not locate the prompts/ directory. Ensure the package is installed correctly.',\n );\n }\n return dir;\n}\n\n/**\n * Load a prompt template by name from the package's prompts/ directory.\n *\n * @param name - Prompt name without extension (e.g., 'execute', 'analyze')\n * @returns The raw template content with placeholders intact\n */\nexport async function loadPrompt(name: string): Promise<string> {\n const promptsDir = getPromptsDir();\n const filePath = join(promptsDir, `${name}.md`);\n\n try {\n return await readFile(filePath, 'utf-8');\n } catch {\n throw new Error(`Prompt template not found: ${filePath}`);\n }\n}\n\n/**\n * Replace placeholders in a prompt template with actual values.\n *\n * Placeholders use the format __KEY__ (e.g., __ISSUE_NUMBER__, __PRD_FILE__).\n */\nexport function applyPlaceholders(template: string, vars: Record<string, string>): string {\n let result = template;\n for (const [key, value] of Object.entries(vars)) {\n result = result.replaceAll(key, value);\n }\n return result;\n}\n"],"mappings":";;;AAAA,SAAS,kBAAkB;AAC3B,SAAS,gBAAgB;AACzB,SAAS,SAAS,YAAY;AAC9B,SAAS,qBAAqB;AAUvB,SAAS,kBAAkB,MAAc,UAAkC;AAChF,MAAI,MAAM,YAAY,QAAQ,cAAc,YAAY,GAAG,CAAC;AAE5D,WAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,UAAM,YAAY,KAAK,KAAK,IAAI;AAChC,QAAI,WAAW,SAAS,GAAG;AACzB,aAAO;AAAA,IACT;AACA,UAAM,QAAQ,GAAG;AAAA,EACnB;AAEA,SAAO;AACT;AAEA,SAAS,gBAAwB;AAC/B,QAAM,MAAM,kBAAkB,SAAS;AACvC,MAAI,QAAQ,MAAM;AAChB,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAQA,eAAsB,WAAW,MAA+B;AAC9D,QAAM,aAAa,cAAc;AACjC,QAAM,WAAW,KAAK,YAAY,GAAG,IAAI,KAAK;AAE9C,MAAI;AACF,WAAO,MAAM,SAAS,UAAU,OAAO;AAAA,EACzC,QAAQ;AACN,UAAM,IAAI,MAAM,8BAA8B,QAAQ,EAAE;AAAA,EAC1D;AACF;AAOO,SAAS,kBAAkB,UAAkB,MAAsC;AACxF,MAAI,SAAS;AACb,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,IAAI,GAAG;AAC/C,aAAS,OAAO,WAAW,KAAK,KAAK;AAAA,EACvC;AACA,SAAO;AACT;","names":[]}
@@ -0,0 +1,240 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ printWarning,
4
+ webConfigSchema
5
+ } from "./chunk-FNLXZXWP.js";
6
+
7
+ // src/config.ts
8
+ import { readFile } from "fs/promises";
9
+ import { platform } from "os";
10
+ import { join } from "path";
11
+
12
+ // src/utils/shell.ts
13
+ import { execa } from "execa";
14
+ async function run(command, args = [], options) {
15
+ const result = await execa(command, args, {
16
+ reject: false,
17
+ ...options
18
+ });
19
+ return {
20
+ stdout: result.stdout?.toString() ?? "",
21
+ stderr: result.stderr?.toString() ?? "",
22
+ exitCode: result.exitCode ?? 1
23
+ };
24
+ }
25
+
26
+ // src/utils/git.ts
27
+ async function getProjectRoot() {
28
+ const result = await run("git", ["rev-parse", "--show-toplevel"]);
29
+ if (result.exitCode !== 0) {
30
+ throw new Error(
31
+ "Not inside a git repository. Please run issue-flow from within a git project."
32
+ );
33
+ }
34
+ return result.stdout.trim();
35
+ }
36
+ async function getCurrentBranch() {
37
+ const result = await run("git", ["branch", "--show-current"]);
38
+ if (result.exitCode !== 0) {
39
+ throw new Error(
40
+ "Failed to detect git branch. Ensure git is installed and you are inside a repository."
41
+ );
42
+ }
43
+ return result.stdout.trim();
44
+ }
45
+ async function getBaseBranch() {
46
+ const remoteHead = await run("git", ["symbolic-ref", "--short", "refs/remotes/origin/HEAD"]);
47
+ if (remoteHead.exitCode === 0) {
48
+ const name = remoteHead.stdout.trim().replace(/^origin\//, "");
49
+ if (name) return name;
50
+ }
51
+ for (const candidate of ["main", "master"]) {
52
+ const check = await run("git", ["rev-parse", "--verify", "--quiet", `refs/heads/${candidate}`]);
53
+ if (check.exitCode === 0) return candidate;
54
+ }
55
+ return "main";
56
+ }
57
+ async function getCommitsSince(base) {
58
+ const result = await run("git", ["log", "--pretty=format:%h%x09%s", `${base}..HEAD`]);
59
+ if (result.exitCode !== 0) return [];
60
+ return result.stdout.split("\n").filter((line) => line.trim() !== "").map((line) => {
61
+ const tab = line.indexOf(" ");
62
+ return tab === -1 ? { hash: line.trim(), subject: "" } : { hash: line.slice(0, tab), subject: line.slice(tab + 1) };
63
+ });
64
+ }
65
+
66
+ // src/config.ts
67
+ var DEFAULTS = {
68
+ retryLimit: 10,
69
+ retryForever: false,
70
+ backoffBaseSeconds: 30,
71
+ backoffMaxSeconds: 900
72
+ };
73
+ function createConfig(options) {
74
+ return {
75
+ issueNumber: options.issueNumber,
76
+ maxIterations: options.maxIterations,
77
+ retryLimit: options.retryLimit ?? DEFAULTS.retryLimit,
78
+ retryForever: options.retryForever ?? DEFAULTS.retryForever,
79
+ backoffBaseSeconds: options.backoffBaseSeconds ?? DEFAULTS.backoffBaseSeconds,
80
+ backoffMaxSeconds: options.backoffMaxSeconds ?? DEFAULTS.backoffMaxSeconds
81
+ };
82
+ }
83
+ async function resolvePaths(config, scriptDir) {
84
+ const projectRoot = await getProjectRoot();
85
+ if (config.issueNumber) {
86
+ const issueDir = join(projectRoot, "issues", config.issueNumber);
87
+ return {
88
+ prdFile: join(issueDir, "tasks.json"),
89
+ progressFile: join(issueDir, "progress.txt"),
90
+ archiveDir: join(issueDir, "archive"),
91
+ lastBranchFile: join(issueDir, ".last-branch"),
92
+ projectRoot
93
+ };
94
+ }
95
+ const base = scriptDir ?? projectRoot;
96
+ return {
97
+ prdFile: join(base, "prd.json"),
98
+ progressFile: join(base, "progress.txt"),
99
+ archiveDir: join(base, "archive"),
100
+ lastBranchFile: join(base, ".last-branch"),
101
+ projectRoot
102
+ };
103
+ }
104
+ function getInstallHint(pkg) {
105
+ const os = platform();
106
+ if (os === "darwin") {
107
+ return `brew install ${pkg}`;
108
+ }
109
+ if (os === "linux") {
110
+ return `apt install ${pkg} (or your distro's package manager)`;
111
+ }
112
+ if (os === "win32") {
113
+ return `winget install ${pkg} (or choco install ${pkg})`;
114
+ }
115
+ return `install ${pkg} using your system package manager`;
116
+ }
117
+ async function validateDependencies() {
118
+ const errors = [];
119
+ const gitResult = await run("git", ["--version"]);
120
+ if (gitResult.exitCode !== 0) {
121
+ errors.push(` - git (install with: ${getInstallHint("git")})`);
122
+ }
123
+ const claudeResult = await run("claude", ["--version"]);
124
+ if (claudeResult.exitCode !== 0) {
125
+ errors.push(" - claude (install with: npm install -g @anthropic-ai/claude-code)");
126
+ }
127
+ return errors;
128
+ }
129
+ var WEB_CONFIG_FILENAME = ".issue-flow.json";
130
+ var webCliOverrides = {};
131
+ function setWebCliOverrides(overrides) {
132
+ webCliOverrides = overrides;
133
+ }
134
+ var FALSY_ENV_VALUES = /* @__PURE__ */ new Set(["", "0", "false", "no", "off"]);
135
+ function parseBooleanEnv(value) {
136
+ return !FALSY_ENV_VALUES.has(value.trim().toLowerCase());
137
+ }
138
+ function readNumberEnv(env, name, warn) {
139
+ const raw = env[name];
140
+ if (raw === void 0) {
141
+ return void 0;
142
+ }
143
+ const parsed = Number(raw);
144
+ if (!Number.isFinite(parsed)) {
145
+ warn(`Ignoring ${name}="${raw}": not a number.`);
146
+ return void 0;
147
+ }
148
+ return parsed;
149
+ }
150
+ function readWebConfigEnv(env, warn) {
151
+ const layer = {};
152
+ if (env.ISSUE_FLOW_WEB !== void 0) {
153
+ layer.enabled = parseBooleanEnv(env.ISSUE_FLOW_WEB);
154
+ }
155
+ const port = readNumberEnv(env, "ISSUE_FLOW_WEB_PORT", warn);
156
+ if (port !== void 0) {
157
+ layer.port = port;
158
+ }
159
+ if (env.ISSUE_FLOW_WEB_HOST !== void 0) {
160
+ layer.host = env.ISSUE_FLOW_WEB_HOST;
161
+ }
162
+ const refresh = readNumberEnv(env, "ISSUE_FLOW_WEB_REFRESH", warn);
163
+ if (refresh !== void 0) {
164
+ layer.refreshSeconds = refresh;
165
+ }
166
+ const logLimit = readNumberEnv(env, "ISSUE_FLOW_WEB_LOG_LIMIT", warn);
167
+ if (logLimit !== void 0) {
168
+ layer.logLimit = logLimit;
169
+ }
170
+ return layer;
171
+ }
172
+ 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;
200
+ if (web === void 0) {
201
+ return {};
202
+ }
203
+ const result = webConfigSchema.partial().safeParse(web);
204
+ if (!result.success) {
205
+ warn(
206
+ `Ignoring "web" key of ${WEB_CONFIG_FILENAME}: ${result.error.issues[0]?.message ?? "invalid value"}.`
207
+ );
208
+ return {};
209
+ }
210
+ return result.data;
211
+ }
212
+ async function loadWebConfig(options = {}) {
213
+ const warn = options.warn ?? printWarning;
214
+ const cli = options.cli ?? webCliOverrides;
215
+ const env = options.env ?? process.env;
216
+ const fileLayer = await readWebConfigFile(options.projectRoot, warn);
217
+ const envLayer = readWebConfigEnv(env, warn);
218
+ const merged = { ...fileLayer, ...envLayer, ...cli };
219
+ const result = webConfigSchema.safeParse(merged);
220
+ if (result.success) {
221
+ return result.data;
222
+ }
223
+ warn(
224
+ `Invalid web monitoring configuration (${result.error.issues[0]?.message ?? "invalid value"}); using defaults.`
225
+ );
226
+ return webConfigSchema.parse({});
227
+ }
228
+
229
+ export {
230
+ run,
231
+ getCurrentBranch,
232
+ getBaseBranch,
233
+ getCommitsSince,
234
+ createConfig,
235
+ resolvePaths,
236
+ validateDependencies,
237
+ setWebCliOverrides,
238
+ loadWebConfig
239
+ };
240
+ //# sourceMappingURL=chunk-TYOFZ6DD.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 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// ── Web monitoring configuration ────────────────────────────────────────────\n\n/** Optional per-project configuration file, read from the project root. */\nexport const WEB_CONFIG_FILENAME = '.issue-flow.json';\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 let root = projectRoot;\n if (root === undefined) {\n try {\n root = await getProjectRoot();\n } catch {\n return {};\n }\n }\n\n const filePath = join(root, WEB_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 {};\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n warn(`Ignoring ${WEB_CONFIG_FILENAME}: invalid JSON.`);\n return {};\n }\n\n if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {\n warn(`Ignoring ${WEB_CONFIG_FILENAME}: expected a JSON object.`);\n return {};\n }\n\n const web = (parsed as Record<string, unknown>).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 ${WEB_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","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,20 +1,18 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  runHeadless
4
- } from "./chunk-4OQ2ARLP.js";
5
- import {
6
- loadTaskPlan,
7
- saveTaskPlan
8
- } from "./chunk-64FU2L2T.js";
4
+ } from "./chunk-2XWXVWVD.js";
9
5
  import {
10
6
  applyPlaceholders,
11
7
  loadPrompt
12
- } from "./chunk-OZVHOVDT.js";
8
+ } from "./chunk-SLHZPB3C.js";
13
9
  import {
14
10
  getGlobalTimeout,
11
+ loadTaskPlan,
15
12
  printError,
16
- printSuccess
17
- } from "./chunk-YAFHVFV5.js";
13
+ printSuccess,
14
+ saveTaskPlan
15
+ } from "./chunk-FNLXZXWP.js";
18
16
 
19
17
  // src/commands/review.ts
20
18
  import { join } from "path";
@@ -82,4 +80,4 @@ async function runReview(issue) {
82
80
  export {
83
81
  runReview
84
82
  };
85
- //# sourceMappingURL=chunk-4PZXIOXD.js.map
83
+ //# sourceMappingURL=chunk-XJJP4TSK.js.map
@@ -1 +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 { 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): Promise<number> {\n const issueNumber = issue.replace(/^#/, '');\n const issueDir = join('issues', issueNumber);\n const tasksPath = join(issueDir, 'tasks.json');\n\n const template = await loadPrompt('review');\n const prompt = applyPlaceholders(template, {\n __ISSUE_NUMBER__: issueNumber,\n __TASKS_PATH__: tasksPath,\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;AAerB,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,OAAgC;AAC9D,QAAM,cAAc,MAAM,QAAQ,MAAM,EAAE;AAC1C,QAAM,WAAW,KAAK,UAAU,WAAW;AAC3C,QAAM,YAAY,KAAK,UAAU,YAAY;AAE7C,QAAM,WAAW,MAAM,WAAW,QAAQ;AAC1C,QAAM,SAAS,kBAAkB,UAAU;AAAA,IACzC,kBAAkB;AAAA,IAClB,gBAAgB;AAAA,EAClB,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
+ {"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 { 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): Promise<number> {\n const issueNumber = issue.replace(/^#/, '');\n const issueDir = join('issues', issueNumber);\n const tasksPath = join(issueDir, 'tasks.json');\n\n const template = await loadPrompt('review');\n const prompt = applyPlaceholders(template, {\n __ISSUE_NUMBER__: issueNumber,\n __TASKS_PATH__: tasksPath,\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;AAerB,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,OAAgC;AAC9D,QAAM,cAAc,MAAM,QAAQ,MAAM,EAAE;AAC1C,QAAM,WAAW,KAAK,UAAU,WAAW;AAC3C,QAAM,YAAY,KAAK,UAAU,YAAY;AAE7C,QAAM,WAAW,MAAM,WAAW,QAAQ;AAC1C,QAAM,SAAS,kBAAkB,UAAU;AAAA,IACzC,kBAAkB;AAAA,IAClB,gBAAgB;AAAA,EAClB,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"]}
package/dist/cli.js CHANGED
@@ -1,9 +1,12 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ setWebCliOverrides
4
+ } from "./chunk-TYOFZ6DD.js";
2
5
  import {
3
6
  printError,
4
7
  setGlobalTimeout,
5
8
  setVerbose
6
- } from "./chunk-YAFHVFV5.js";
9
+ } from "./chunk-FNLXZXWP.js";
7
10
 
8
11
  // src/cli.ts
9
12
  import { createRequire } from "module";
@@ -24,6 +27,31 @@ function withGlobalOptions(cmd) {
24
27
  parseInteger
25
28
  );
26
29
  }
30
+ function withWebOptions(cmd) {
31
+ return cmd.option("--web", "Enable the web monitoring server").option("--serve", "Alias for --web").option("--port <n>", "Web server port (default: 3737)", parseInteger).option("--host <h>", "Web server host (default: 127.0.0.1)").option("--refresh <s>", "Suggested UI polling interval in seconds", parseInteger).option("--web-log-limit <n>", "Max log entries kept in the snapshot", parseInteger).option("--web-no-logs", "Exclude logs from the published snapshot");
32
+ }
33
+ function resolveWebOverrides(opts) {
34
+ const overrides = {};
35
+ if (opts.web === true || opts.serve === true) {
36
+ overrides.enabled = true;
37
+ }
38
+ if (opts.port !== void 0) {
39
+ overrides.port = opts.port;
40
+ }
41
+ if (opts.host !== void 0) {
42
+ overrides.host = opts.host;
43
+ }
44
+ if (opts.refresh !== void 0) {
45
+ overrides.refreshSeconds = opts.refresh;
46
+ }
47
+ if (opts.webLogLimit !== void 0) {
48
+ overrides.logLimit = opts.webLogLimit;
49
+ }
50
+ if (opts.webNoLogs === true) {
51
+ overrides.includeLogs = false;
52
+ }
53
+ return overrides;
54
+ }
27
55
  var program = new Command();
28
56
  program.name("issue-flow").description(
29
57
  "Unified CLI for orchestrating the full issue-flow pipeline via Claude Code Headless."
@@ -36,59 +64,68 @@ program.hook("preAction", (_thisCommand, actionCommand) => {
36
64
  if (opts.timeout !== void 0) {
37
65
  setGlobalTimeout(opts.timeout * 1e3);
38
66
  }
67
+ setWebCliOverrides(resolveWebOverrides(opts));
39
68
  });
40
69
  withGlobalOptions(
41
70
  program.command("init").description("Verify that all prerequisites (claude, gh, git) are met")
42
71
  ).action(async () => {
43
- const { runInit } = await import("./init-SXFGOCML.js");
72
+ const { runInit } = await import("./init-6AWQI3IE.js");
44
73
  const code = await runInit();
45
74
  process.exit(code);
46
75
  });
47
76
  withGlobalOptions(
48
77
  program.command("generate").description("Create a GitHub issue via Claude Code Headless").requiredOption("--prompt <text>", "Issue description text")
49
78
  ).action(async (options) => {
50
- const { runGenerate } = await import("./generate-HP4CPMJG.js");
79
+ const { runGenerate } = await import("./generate-5OWWNRIB.js");
51
80
  const code = await runGenerate(options.prompt);
52
81
  process.exit(code);
53
82
  });
54
- withGlobalOptions(
55
- program.command("run").description("Execute the full pipeline: analyze \u2192 prd \u2192 plan \u2192 execute \u2192 review \u2192 pr").argument("<issue>", "Issue number").option("--mode <mode>", "Execution mode: auto | manual", "auto").option("--from <phase>", "Resume from a specific phase")
83
+ withWebOptions(
84
+ withGlobalOptions(
85
+ program.command("run").description("Execute the full pipeline: prd \u2192 plan \u2192 execute \u2192 review \u2192 pr").argument("<issue>", "Issue number").option("--mode <mode>", "Execution mode: auto | manual", "auto").option("--from <phase>", "Resume from a specific phase").option("--no-branch", "Run pipeline on current branch without creating a new branch or PR")
86
+ )
56
87
  ).action(async (issue, options) => {
57
- const { runPipeline } = await import("./run-PKHPULYL.js");
58
- const code = await runPipeline(issue, options.mode, options.from);
88
+ const { runPipeline } = await import("./run-UCSNYSQO.js");
89
+ const code = await runPipeline(issue, options.mode, options.from, options.noBranch);
59
90
  process.exit(code);
60
91
  });
61
92
  withGlobalOptions(
62
93
  program.command("analyze").description("Analyze a GitHub issue via Claude Code Headless").argument("<issue>", "Issue number")
63
94
  ).action(async (issue) => {
64
- const { runAnalyze } = await import("./analyze-3SN7ZAZU.js");
95
+ const { runAnalyze } = await import("./analyze-IJ2SYXVM.js");
65
96
  const code = await runAnalyze(issue);
66
97
  process.exit(code);
67
98
  });
68
99
  withGlobalOptions(
69
100
  program.command("prd").description("Generate a PRD from an analyzed issue via Claude Code Headless").argument("<issue>", "Issue number")
70
101
  ).action(async (issue) => {
71
- const { runPrd } = await import("./prd-ET2QN7GB.js");
102
+ const { runPrd } = await import("./prd-WB3QH6QF.js");
72
103
  const code = await runPrd(issue);
73
104
  process.exit(code);
74
105
  });
75
106
  withGlobalOptions(
76
107
  program.command("plan").description("Convert a PRD to a tasks.json task plan via Claude Code Headless").argument("<issue>", "Issue number")
77
108
  ).action(async (issue) => {
78
- const { runPlan } = await import("./plan-HWZRG5GP.js");
109
+ const { runPlan } = await import("./plan-RQXK2OCP.js");
79
110
  const code = await runPlan(issue);
80
111
  process.exit(code);
81
112
  });
82
- withGlobalOptions(
83
- program.command("execute").description("Run the iterative story execution loop (issue-flow engine)").option("--issue <number>", "Issue number \u2014 reads artifacts from issues/N/").option("--max-iterations <number>", "Stop after N iterations", parseInteger).option(
84
- "--retry-limit <number>",
85
- "Retry transient Claude failures up to N consecutive times",
86
- parseInteger
87
- ).option("--retry-forever", "Retry transient Claude failures indefinitely").argument("[max-iterations]", "Backward-compatible alias for --max-iterations N", parseInteger)
113
+ withWebOptions(
114
+ withGlobalOptions(
115
+ program.command("execute").description("Run the iterative story execution loop (issue-flow engine)").option("--issue <number>", "Issue number \u2014 reads artifacts from issues/N/").option("--max-iterations <number>", "Stop after N iterations", parseInteger).option(
116
+ "--retry-limit <number>",
117
+ "Retry transient Claude failures up to N consecutive times",
118
+ parseInteger
119
+ ).option("--retry-forever", "Retry transient Claude failures indefinitely").argument(
120
+ "[max-iterations]",
121
+ "Backward-compatible alias for --max-iterations N",
122
+ parseInteger
123
+ )
124
+ )
88
125
  ).action(
89
126
  async (positionalMaxIter, options) => {
90
127
  try {
91
- const { runExecute } = await import("./execute-GIA4IKS5.js");
128
+ const { runExecute } = await import("./execute-DALVYZIH.js");
92
129
  const code = await runExecute(positionalMaxIter, options);
93
130
  process.exit(code);
94
131
  } catch (error) {
@@ -100,14 +137,14 @@ withGlobalOptions(
100
137
  withGlobalOptions(
101
138
  program.command("review").description("Validate an issue resolution via Claude Code Headless").argument("<issue>", "Issue number")
102
139
  ).action(async (issue) => {
103
- const { runReview } = await import("./review-3QDZCOMT.js");
140
+ const { runReview } = await import("./review-2WHYJG7M.js");
104
141
  const code = await runReview(issue);
105
142
  process.exit(code);
106
143
  });
107
144
  withGlobalOptions(
108
145
  program.command("pr").description("Create a pull request via Claude Code Headless").argument("<issue>", "Issue number")
109
146
  ).action(async (issue) => {
110
- const { runPr } = await import("./pr-TTOFYUCV.js");
147
+ const { runPr } = await import("./pr-UU4H7ZZR.js");
111
148
  const code = await runPr(issue);
112
149
  process.exit(code);
113
150
  });