issue-flow 0.3.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 (51) hide show
  1. package/README.md +226 -0
  2. package/dist/analyze-YYQQP64A.js +12 -0
  3. package/dist/analyze-YYQQP64A.js.map +1 -0
  4. package/dist/chunk-64FU2L2T.js +180 -0
  5. package/dist/chunk-64FU2L2T.js.map +1 -0
  6. package/dist/chunk-7APADNBV.js +68 -0
  7. package/dist/chunk-7APADNBV.js.map +1 -0
  8. package/dist/chunk-7FRXZ2XA.js +78 -0
  9. package/dist/chunk-7FRXZ2XA.js.map +1 -0
  10. package/dist/chunk-D4WBCVB4.js +85 -0
  11. package/dist/chunk-D4WBCVB4.js.map +1 -0
  12. package/dist/chunk-G36DIQ4J.js +80 -0
  13. package/dist/chunk-G36DIQ4J.js.map +1 -0
  14. package/dist/chunk-JEUHCE3V.js +123 -0
  15. package/dist/chunk-JEUHCE3V.js.map +1 -0
  16. package/dist/chunk-L7RIGFP6.js +92 -0
  17. package/dist/chunk-L7RIGFP6.js.map +1 -0
  18. package/dist/chunk-OZVHOVDT.js +43 -0
  19. package/dist/chunk-OZVHOVDT.js.map +1 -0
  20. package/dist/chunk-PINC2LST.js +82 -0
  21. package/dist/chunk-PINC2LST.js.map +1 -0
  22. package/dist/chunk-V356G3JS.js +611 -0
  23. package/dist/chunk-V356G3JS.js.map +1 -0
  24. package/dist/chunk-ZOX7M2C2.js +85 -0
  25. package/dist/chunk-ZOX7M2C2.js.map +1 -0
  26. package/dist/cli.js +76 -0
  27. package/dist/cli.js.map +1 -0
  28. package/dist/execute-UF3IY2MG.js +11 -0
  29. package/dist/execute-UF3IY2MG.js.map +1 -0
  30. package/dist/generate-WMYJEPI4.js +48 -0
  31. package/dist/generate-WMYJEPI4.js.map +1 -0
  32. package/dist/init-QES2HLP5.js +9 -0
  33. package/dist/init-QES2HLP5.js.map +1 -0
  34. package/dist/plan-BKSUDN34.js +12 -0
  35. package/dist/plan-BKSUDN34.js.map +1 -0
  36. package/dist/pr-E2ASXBWE.js +12 -0
  37. package/dist/pr-E2ASXBWE.js.map +1 -0
  38. package/dist/prd-WYW35SGL.js +12 -0
  39. package/dist/prd-WYW35SGL.js.map +1 -0
  40. package/dist/review-YI2WPNC4.js +12 -0
  41. package/dist/review-YI2WPNC4.js.map +1 -0
  42. package/dist/run-J62FIB5O.js +288 -0
  43. package/dist/run-J62FIB5O.js.map +1 -0
  44. package/package.json +58 -0
  45. package/prompts/analyze.md +28 -0
  46. package/prompts/execute.md +124 -0
  47. package/prompts/generate.md +19 -0
  48. package/prompts/plan.md +49 -0
  49. package/prompts/pr.md +19 -0
  50. package/prompts/prd.md +30 -0
  51. package/prompts/review.md +27 -0
package/README.md ADDED
@@ -0,0 +1,226 @@
1
+ # issue-flow
2
+
3
+ > This is the npm package README. For full project documentation, see the [root README](../../README.md).
4
+
5
+ Unified CLI that orchestrates the full issue-to-PR pipeline via [Claude Code](https://docs.anthropic.com/en/docs/claude-code) Headless mode. Analyzes issues, generates PRDs, creates task plans, implements code iteratively, reviews results, and opens pull requests -- all programmatically, without interactive sessions.
6
+
7
+ Built on the [Ralph pattern](https://ghuntley.com/ralph/) for autonomous AI agent loops.
8
+
9
+ ## Pipeline Flow
10
+
11
+ ```mermaid
12
+ flowchart LR
13
+ subgraph "issue-flow run 42"
14
+ A["init"] --> B["analyze"]
15
+ B --> C["prd"]
16
+ C --> D["plan"]
17
+ D --> E["execute"]
18
+ E --> F["review"]
19
+ F --> G{PASS?}
20
+ G -- Yes --> H["pr"]
21
+ G -- No --> I{"Retries\n< max?"}
22
+ I -- Yes --> E
23
+ I -- No --> J["Stop"]
24
+ end
25
+ ```
26
+
27
+ ## Requirements
28
+
29
+ - **Node.js** >= 18.0.0
30
+ - **git** installed and available in PATH
31
+ - **Claude Code** (`npm install -g @anthropic-ai/claude-code`)
32
+ - **GitHub CLI** (`gh`) authenticated (`gh auth login`)
33
+
34
+ Run `npx issue-flow init` to verify all prerequisites.
35
+
36
+ ## Installation
37
+
38
+ ```bash
39
+ # Run directly via npx (no install needed)
40
+ npx issue-flow run 42
41
+
42
+ # Or install globally
43
+ npm install -g issue-flow
44
+ issue-flow run 42
45
+ ```
46
+
47
+ ## Commands
48
+
49
+ ### `run` -- Full pipeline (end-to-end)
50
+
51
+ ```bash
52
+ # Run the complete pipeline for an issue
53
+ npx issue-flow run 42
54
+
55
+ # Resume from a specific phase
56
+ npx issue-flow run 42 --from execute
57
+
58
+ # Manual mode (artifacts only, no execution)
59
+ npx issue-flow run 42 --mode manual
60
+ ```
61
+
62
+ Executes all phases in order: **init** → **analyze** → **prd** → **plan** → **execute** → **review** → **pr**. Automatically resumes from the last incomplete phase if pipeline state exists. On review failure, runs correction cycles (re-execute + re-review) up to `maxCorrectionCycles`.
63
+
64
+ ### `init` -- Check prerequisites
65
+
66
+ ```bash
67
+ npx issue-flow init
68
+ ```
69
+
70
+ Verifies that `claude`, `gh` (authenticated), and `git` (inside a repo) are available. Reports pass/fail for each with install hints.
71
+
72
+ ### `analyze` -- Analyze an issue
73
+
74
+ ```bash
75
+ npx issue-flow analyze 42
76
+ ```
77
+
78
+ Invokes Claude headlessly to fetch issue data, analyze the codebase, and produce a structured analysis saved to `issues/42/analysis.md`.
79
+
80
+ ### `prd` -- Generate a PRD
81
+
82
+ ```bash
83
+ npx issue-flow prd 42
84
+ ```
85
+
86
+ Generates a Product Requirements Document from the issue analysis. Reads `issues/N/analysis.md` as context if available. Saves to `issues/42/prd.md`.
87
+
88
+ ### `plan` -- Convert PRD to task plan
89
+
90
+ ```bash
91
+ npx issue-flow plan 42
92
+ ```
93
+
94
+ Converts the PRD into a structured `issues/42/tasks.json` with ordered user stories, acceptance criteria, and pipeline state. Validates the output with zod schemas.
95
+
96
+ ### `execute` -- Run the story execution loop
97
+
98
+ ```bash
99
+ npx issue-flow execute --issue 42
100
+ npx issue-flow execute --issue 42 --max-iterations 15
101
+ npx issue-flow execute --issue 42 --retry-forever
102
+ ```
103
+
104
+ Runs the iterative agent loop. Each iteration is a fresh Claude instance that picks the next pending story, implements it, runs quality checks, and commits.
105
+
106
+ | Flag | Description |
107
+ |------|-------------|
108
+ | `--issue N` | Issue number -- reads artifacts from `issues/N/` |
109
+ | `--max-iterations N` | Stop after N iterations (default: unlimited) |
110
+ | `--retry-limit N` | Retry transient Claude failures up to N consecutive times (default: 10) |
111
+ | `--retry-forever` | Retry transient Claude failures indefinitely |
112
+
113
+ ### `review` -- Validate the implementation
114
+
115
+ ```bash
116
+ npx issue-flow review 42
117
+ ```
118
+
119
+ Invokes Claude headlessly to verify acceptance criteria, run tests, and check for regressions. Outputs `PASS` or `FAIL` with findings.
120
+
121
+ ### `pr` -- Create a pull request
122
+
123
+ ```bash
124
+ npx issue-flow pr 42
125
+ ```
126
+
127
+ Creates a well-structured PR referencing the issue, with summary and test plan.
128
+
129
+ ### `generate` -- Create a new issue
130
+
131
+ ```bash
132
+ npx issue-flow generate --prompt "Add dark mode support to the settings page"
133
+ ```
134
+
135
+ Analyzes the project and creates a detailed GitHub issue via Claude headless.
136
+
137
+ ## Pipeline State
138
+
139
+ Each issue's state is tracked in `issues/N/tasks.json`:
140
+
141
+ ```
142
+ issues/42/
143
+ analysis.md # Issue analysis
144
+ prd.md # Product requirements
145
+ tasks.json # Task plan with pipeline state and user stories
146
+ progress.txt # Execution log
147
+ ```
148
+
149
+ The `pipeline` field tracks which phases have completed, enabling resume from any point:
150
+
151
+ ```json
152
+ {
153
+ "pipeline": {
154
+ "analyzeCompleted": true,
155
+ "prdCompleted": true,
156
+ "jsonCompleted": true,
157
+ "executionCompleted": false,
158
+ "reviewCompleted": false,
159
+ "prCreated": false
160
+ }
161
+ }
162
+ ```
163
+
164
+ ## Architecture
165
+
166
+ ```
167
+ src/
168
+ cli.ts # Entry point, subcommand registration (commander)
169
+ config.ts # Configuration resolution and defaults
170
+ types.ts # Shared TypeScript interfaces
171
+ schemas.ts # Zod validation schemas
172
+ commands/
173
+ init.ts # Prerequisite verification
174
+ generate.ts # Headless issue creation
175
+ run.ts # Full pipeline orchestrator
176
+ analyze.ts # Headless issue analysis
177
+ prd.ts # Headless PRD generation
178
+ plan.ts # Headless PRD-to-JSON conversion
179
+ execute.ts # Iterative story execution (engine wrapper)
180
+ review.ts # Headless implementation review
181
+ pr.ts # Headless PR creation
182
+ core/
183
+ engine.ts # Main agent loop
184
+ executor.ts # Claude CLI invocation via execa
185
+ headless.ts # Typed wrapper for claude -p invocations
186
+ pipeline.ts # Pipeline state machine
187
+ state-manager.ts # Typed CRUD for tasks.json
188
+ prompt-resolver.ts # Prompt resolution and templating
189
+ ui/
190
+ logger.ts # Colored logging utilities
191
+ progress.ts # Progress bar and iteration headers
192
+ summary.ts # Box drawing and summary display
193
+ utils/
194
+ shell.ts # Shell command execution
195
+ git.ts # Git operations
196
+ retry.ts # Transient failure detection and backoff
197
+ ```
198
+
199
+ ## Development
200
+
201
+ ```bash
202
+ # Install dependencies
203
+ npm install
204
+
205
+ # Build
206
+ npm run build
207
+
208
+ # Type check
209
+ npm run typecheck
210
+
211
+ # Run tests
212
+ npm test
213
+
214
+ # Watch mode
215
+ npm run dev
216
+ ```
217
+
218
+ For the full development setup, local testing, and NPM publishing guide, see [CONTRIBUTING.md](CONTRIBUTING.md).
219
+
220
+ ## Credits
221
+
222
+ Based on [Geoffrey Huntley's Ralph pattern](https://ghuntley.com/ralph/) and the [snarktank/ralph](https://github.com/snarktank/ralph) repository.
223
+
224
+ ## See Also
225
+
226
+ - [Skills & Sub-Agent Architecture](../../docs/skills-and-agents.md) -- Using Issue Flow interactively via Claude Code skills and the `resolve-issue` sub-agent.
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ runAnalyze
4
+ } from "./chunk-7APADNBV.js";
5
+ import "./chunk-PINC2LST.js";
6
+ import "./chunk-64FU2L2T.js";
7
+ import "./chunk-OZVHOVDT.js";
8
+ import "./chunk-L7RIGFP6.js";
9
+ export {
10
+ runAnalyze
11
+ };
12
+ //# sourceMappingURL=analyze-YYQQP64A.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,180 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/schemas.ts
4
+ import { z } from "zod";
5
+ var userStorySchema = z.object({
6
+ id: z.string(),
7
+ title: z.string(),
8
+ description: z.string(),
9
+ acceptanceCriteria: z.array(z.string()),
10
+ priority: z.number().int().positive(),
11
+ passes: z.boolean(),
12
+ notes: z.string()
13
+ });
14
+ var pipelineStateSchema = z.object({
15
+ analyzeCompleted: z.boolean(),
16
+ prdCompleted: z.boolean(),
17
+ jsonCompleted: z.boolean(),
18
+ executionCompleted: z.boolean(),
19
+ reviewCompleted: z.boolean(),
20
+ prCreated: z.boolean()
21
+ });
22
+ var lastErrorSchema = z.object({
23
+ category: z.string(),
24
+ message: z.string(),
25
+ at: z.string()
26
+ });
27
+ var taskPlanSchema = z.object({
28
+ project: z.string(),
29
+ issueNumber: z.number().int().positive(),
30
+ issueUrl: z.string(),
31
+ branchName: z.string(),
32
+ description: z.string(),
33
+ issueStatus: z.enum(["pending", "in_progress", "completed"]),
34
+ completedAt: z.string().nullable(),
35
+ lastAttemptAt: z.string().nullable(),
36
+ lastError: lastErrorSchema.nullable(),
37
+ correctionCycle: z.number().int().min(0),
38
+ maxCorrectionCycles: z.number().int().min(0),
39
+ pipeline: pipelineStateSchema,
40
+ userStories: z.array(userStorySchema)
41
+ });
42
+ var headlessResultSchema = z.object({
43
+ success: z.boolean(),
44
+ result: z.string(),
45
+ cost: z.object({
46
+ inputTokens: z.number().optional(),
47
+ outputTokens: z.number().optional()
48
+ }).nullable(),
49
+ error: z.string().nullable()
50
+ });
51
+
52
+ // src/core/state-manager.ts
53
+ import { copyFile, mkdtemp, readFile, rename, unlink, writeFile } from "fs/promises";
54
+ import { tmpdir } from "os";
55
+ import { join } from "path";
56
+ import { ZodError } from "zod";
57
+ function isoNow() {
58
+ return (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
59
+ }
60
+ async function loadTaskPlan(path) {
61
+ const content = await readFile(path, "utf-8");
62
+ const raw = JSON.parse(content);
63
+ try {
64
+ return taskPlanSchema.parse(raw);
65
+ } catch (err) {
66
+ if (err instanceof ZodError) {
67
+ const issues = err.issues.map((i) => ` - ${i.path.join(".")}: ${i.message}`).join("\n");
68
+ throw new Error(`Invalid tasks.json at ${path}:
69
+ ${issues}`);
70
+ }
71
+ throw err;
72
+ }
73
+ }
74
+ async function saveTaskPlan(path, plan) {
75
+ const tmpDir = await mkdtemp(join(tmpdir(), "issue-flow-task-plan-"));
76
+ const tmpFile = join(tmpDir, "tasks.json");
77
+ await writeFile(tmpFile, `${JSON.stringify(plan, null, 2)}
78
+ `, "utf-8");
79
+ try {
80
+ await rename(tmpFile, path);
81
+ } catch (err) {
82
+ if (err.code === "EXDEV") {
83
+ await copyFile(tmpFile, path);
84
+ await unlink(tmpFile);
85
+ } else {
86
+ throw err;
87
+ }
88
+ }
89
+ }
90
+ function initializeState(plan) {
91
+ const defaultPipeline = {
92
+ analyzeCompleted: false,
93
+ prdCompleted: false,
94
+ jsonCompleted: false,
95
+ executionCompleted: false,
96
+ reviewCompleted: false,
97
+ prCreated: false
98
+ };
99
+ return {
100
+ ...plan,
101
+ issueStatus: plan.issueStatus ?? "pending",
102
+ completedAt: plan.completedAt ?? null,
103
+ lastAttemptAt: plan.lastAttemptAt ?? null,
104
+ lastError: plan.lastError ?? null,
105
+ correctionCycle: plan.correctionCycle ?? 0,
106
+ maxCorrectionCycles: plan.maxCorrectionCycles ?? 3,
107
+ pipeline: plan.pipeline ? { ...defaultPipeline, ...plan.pipeline } : defaultPipeline
108
+ };
109
+ }
110
+ function allStoriesPass(plan) {
111
+ if (plan.userStories.length === 0) {
112
+ return false;
113
+ }
114
+ return plan.userStories.every((story) => story.passes === true);
115
+ }
116
+ function markIssueInProgress(plan, timestamp) {
117
+ const ts = timestamp ?? isoNow();
118
+ return {
119
+ ...plan,
120
+ issueStatus: "in_progress",
121
+ completedAt: null,
122
+ lastAttemptAt: ts
123
+ };
124
+ }
125
+ function markIssueCompleted(plan) {
126
+ const ts = isoNow();
127
+ return {
128
+ ...plan,
129
+ issueStatus: "completed",
130
+ completedAt: ts,
131
+ lastAttemptAt: ts,
132
+ lastError: null,
133
+ pipeline: plan.pipeline ? { ...plan.pipeline, executionCompleted: true } : plan.pipeline
134
+ };
135
+ }
136
+ function setLastError(plan, category, message) {
137
+ const ts = isoNow();
138
+ const error = {
139
+ category,
140
+ message,
141
+ at: ts
142
+ };
143
+ return {
144
+ ...plan,
145
+ lastAttemptAt: ts,
146
+ lastError: error
147
+ };
148
+ }
149
+ function clearLastError(plan, attemptStartedAt) {
150
+ const ts = isoNow();
151
+ if (plan.lastError && plan.lastError.at > attemptStartedAt) {
152
+ return {
153
+ ...plan,
154
+ lastAttemptAt: ts
155
+ };
156
+ }
157
+ return {
158
+ ...plan,
159
+ lastAttemptAt: ts,
160
+ lastError: null
161
+ };
162
+ }
163
+ function trimErrorMessage(message) {
164
+ return message.split("\n").filter((line) => line.trim().length > 0).slice(0, 8).join("\n");
165
+ }
166
+
167
+ export {
168
+ taskPlanSchema,
169
+ isoNow,
170
+ loadTaskPlan,
171
+ saveTaskPlan,
172
+ initializeState,
173
+ allStoriesPass,
174
+ markIssueInProgress,
175
+ markIssueCompleted,
176
+ setLastError,
177
+ clearLastError,
178
+ trimErrorMessage
179
+ };
180
+ //# sourceMappingURL=chunk-64FU2L2T.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/schemas.ts","../src/core/state-manager.ts"],"sourcesContent":["import { z } from 'zod';\n\n/**\n * Zod schemas for validating tasks.json structure and headless invocation outputs.\n */\n\nexport const userStorySchema = z.object({\n id: z.string(),\n title: z.string(),\n description: z.string(),\n acceptanceCriteria: z.array(z.string()),\n priority: z.number().int().positive(),\n passes: z.boolean(),\n notes: z.string(),\n});\n\nexport const pipelineStateSchema = z.object({\n analyzeCompleted: z.boolean(),\n prdCompleted: z.boolean(),\n jsonCompleted: z.boolean(),\n executionCompleted: z.boolean(),\n reviewCompleted: z.boolean(),\n prCreated: z.boolean(),\n});\n\nconst lastErrorSchema = z.object({\n category: z.string(),\n message: z.string(),\n at: z.string(),\n});\n\nexport const taskPlanSchema = z.object({\n project: z.string(),\n issueNumber: z.number().int().positive(),\n issueUrl: z.string(),\n branchName: z.string(),\n description: z.string(),\n issueStatus: z.enum(['pending', 'in_progress', 'completed']),\n completedAt: z.string().nullable(),\n lastAttemptAt: z.string().nullable(),\n lastError: lastErrorSchema.nullable(),\n correctionCycle: z.number().int().min(0),\n maxCorrectionCycles: z.number().int().min(0),\n pipeline: pipelineStateSchema,\n userStories: z.array(userStorySchema),\n});\n\nexport const headlessResultSchema = z.object({\n success: z.boolean(),\n result: z.string(),\n cost: z\n .object({\n inputTokens: z.number().optional(),\n outputTokens: z.number().optional(),\n })\n .nullable(),\n error: z.string().nullable(),\n});\n\nexport type ValidatedTaskPlan = z.infer<typeof taskPlanSchema>;\nexport type ValidatedHeadlessResult = z.infer<typeof headlessResultSchema>;\n","import { copyFile, mkdtemp, readFile, rename, unlink, writeFile } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport { ZodError } from 'zod';\nimport { taskPlanSchema } from '../schemas.js';\nimport type { LastError, PipelineState, TaskPlan } from '../types.js';\n\n/**\n * Get the current ISO timestamp.\n */\nexport function isoNow(): string {\n return new Date().toISOString().replace(/\\.\\d{3}Z$/, 'Z');\n}\n\n/**\n * Load and parse a tasks.json file.\n * Validates that it has the expected structure.\n */\nexport async function loadTaskPlan(path: string): Promise<TaskPlan> {\n const content = await readFile(path, 'utf-8');\n const raw = JSON.parse(content);\n\n try {\n return taskPlanSchema.parse(raw) as TaskPlan;\n } catch (err) {\n if (err instanceof ZodError) {\n const issues = err.issues.map((i) => ` - ${i.path.join('.')}: ${i.message}`).join('\\n');\n throw new Error(`Invalid tasks.json at ${path}:\\n${issues}`);\n }\n throw err;\n }\n}\n\n/**\n * Save a TaskPlan to disk atomically (write-to-temp + rename).\n * This prevents corruption if the process is interrupted during write.\n */\nexport async function saveTaskPlan(path: string, plan: TaskPlan): Promise<void> {\n const tmpDir = await mkdtemp(join(tmpdir(), 'issue-flow-task-plan-'));\n const tmpFile = join(tmpDir, 'tasks.json');\n\n await writeFile(tmpFile, `${JSON.stringify(plan, null, 2)}\\n`, 'utf-8');\n\n try {\n await rename(tmpFile, path);\n } catch (err: unknown) {\n // EXDEV: rename fails across different devices/drives (common on Windows)\n if ((err as NodeJS.ErrnoException).code === 'EXDEV') {\n await copyFile(tmpFile, path);\n await unlink(tmpFile);\n } else {\n throw err;\n }\n }\n}\n\n/**\n * Initialize default state fields on a TaskPlan.\n * Fills in missing fields with defaults, matching the Bash script behavior.\n */\nexport function initializeState(plan: TaskPlan): TaskPlan {\n const defaultPipeline: PipelineState = {\n analyzeCompleted: false,\n prdCompleted: false,\n jsonCompleted: false,\n executionCompleted: false,\n reviewCompleted: false,\n prCreated: false,\n };\n\n return {\n ...plan,\n issueStatus: plan.issueStatus ?? 'pending',\n completedAt: plan.completedAt ?? null,\n lastAttemptAt: plan.lastAttemptAt ?? null,\n lastError: plan.lastError ?? null,\n correctionCycle: plan.correctionCycle ?? 0,\n maxCorrectionCycles: plan.maxCorrectionCycles ?? 3,\n pipeline: plan.pipeline ? { ...defaultPipeline, ...plan.pipeline } : defaultPipeline,\n };\n}\n\n/**\n * Check if all user stories have passes=true.\n * Returns false if there are no stories.\n */\nexport function allStoriesPass(plan: TaskPlan): boolean {\n if (plan.userStories.length === 0) {\n return false;\n }\n\n return plan.userStories.every((story) => story.passes === true);\n}\n\n/**\n * Mark a specific story as passing.\n */\nexport function markStoryPassing(plan: TaskPlan, storyId: string): TaskPlan {\n return {\n ...plan,\n userStories: plan.userStories.map((story) =>\n story.id === storyId ? { ...story, passes: true } : story,\n ),\n };\n}\n\n/**\n * Mark the issue as in_progress.\n */\nexport function markIssueInProgress(plan: TaskPlan, timestamp?: string): TaskPlan {\n const ts = timestamp ?? isoNow();\n return {\n ...plan,\n issueStatus: 'in_progress',\n completedAt: null,\n lastAttemptAt: ts,\n };\n}\n\n/**\n * Mark the issue as completed.\n */\nexport function markIssueCompleted(plan: TaskPlan): TaskPlan {\n const ts = isoNow();\n return {\n ...plan,\n issueStatus: 'completed',\n completedAt: ts,\n lastAttemptAt: ts,\n lastError: null,\n pipeline: plan.pipeline ? { ...plan.pipeline, executionCompleted: true } : plan.pipeline,\n };\n}\n\n/**\n * Set the lastError field on the task plan.\n */\nexport function setLastError(plan: TaskPlan, category: string, message: string): TaskPlan {\n const ts = isoNow();\n const error: LastError = {\n category,\n message,\n at: ts,\n };\n\n return {\n ...plan,\n lastAttemptAt: ts,\n lastError: error,\n };\n}\n\n/**\n * Clear the lastError field, but only if it was set before the attempt started.\n * This prevents clearing errors that were set during the current attempt.\n */\nexport function clearLastError(plan: TaskPlan, attemptStartedAt: string): TaskPlan {\n const ts = isoNow();\n\n // If the error was set after the attempt started, keep it\n if (plan.lastError && plan.lastError.at > attemptStartedAt) {\n return {\n ...plan,\n lastAttemptAt: ts,\n };\n }\n\n return {\n ...plan,\n lastAttemptAt: ts,\n lastError: null,\n };\n}\n\n/**\n * Trim an error message to at most 8 non-empty lines.\n */\nexport function trimErrorMessage(message: string): string {\n return message\n .split('\\n')\n .filter((line) => line.trim().length > 0)\n .slice(0, 8)\n .join('\\n');\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ runHeadless
4
+ } from "./chunk-PINC2LST.js";
5
+ import {
6
+ loadTaskPlan,
7
+ saveTaskPlan
8
+ } from "./chunk-64FU2L2T.js";
9
+ import {
10
+ applyPlaceholders,
11
+ loadPrompt
12
+ } from "./chunk-OZVHOVDT.js";
13
+ import {
14
+ printError,
15
+ printInfo,
16
+ printSuccess
17
+ } from "./chunk-L7RIGFP6.js";
18
+
19
+ // src/commands/analyze.ts
20
+ import { mkdir, readFile, writeFile } from "fs/promises";
21
+ import { join } from "path";
22
+ async function runAnalyze(issue) {
23
+ const issueNumber = issue.replace(/^#/, "");
24
+ const issueDir = join("issues", issueNumber);
25
+ const analysisPath = join(issueDir, "analysis.md");
26
+ printInfo(`Analyzing issue #${issueNumber}...`);
27
+ await mkdir(issueDir, { recursive: true });
28
+ const template = await loadPrompt("analyze");
29
+ const prompt = applyPlaceholders(template, {
30
+ __ISSUE_NUMBER__: issueNumber,
31
+ __ANALYSIS_PATH__: analysisPath
32
+ });
33
+ const result = await runHeadless({
34
+ prompt,
35
+ maxTurns: 15,
36
+ timeout: 12e4,
37
+ outputFormat: "text",
38
+ allowedTools: ["Bash", "Read", "Glob", "Grep", "Write"]
39
+ });
40
+ if (!result.success) {
41
+ printError(`Analysis failed: ${result.error}`);
42
+ return 1;
43
+ }
44
+ try {
45
+ const content = await readFile(analysisPath, "utf-8");
46
+ if (content.length < 10) {
47
+ printError("Analysis file was created but appears empty");
48
+ return 1;
49
+ }
50
+ } catch {
51
+ printInfo("Headless did not create analysis file; saving output directly");
52
+ await writeFile(analysisPath, result.result, "utf-8");
53
+ }
54
+ const tasksPath = join(issueDir, "tasks.json");
55
+ try {
56
+ const plan = await loadTaskPlan(tasksPath);
57
+ plan.pipeline.analyzeCompleted = true;
58
+ await saveTaskPlan(tasksPath, plan);
59
+ } catch {
60
+ }
61
+ printSuccess(`Analysis saved to ${analysisPath}`);
62
+ return 0;
63
+ }
64
+
65
+ export {
66
+ runAnalyze
67
+ };
68
+ //# sourceMappingURL=chunk-7APADNBV.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 { printError, printInfo, printSuccess } from '../ui/logger.js';\n\nexport async function runAnalyze(issue: string): Promise<number> {\n const issueNumber = issue.replace(/^#/, '');\n const issueDir = join('issues', issueNumber);\n const analysisPath = join(issueDir, 'analysis.md');\n\n printInfo(`Analyzing issue #${issueNumber}...`);\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 });\n\n const result = await runHeadless({\n prompt,\n maxTurns: 15,\n timeout: 120_000,\n outputFormat: 'text',\n allowedTools: ['Bash', 'Read', 'Glob', 'Grep', 'Write'],\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;AAMrB,eAAsB,WAAW,OAAgC;AAC/D,QAAM,cAAc,MAAM,QAAQ,MAAM,EAAE;AAC1C,QAAM,WAAW,KAAK,UAAU,WAAW;AAC3C,QAAM,eAAe,KAAK,UAAU,aAAa;AAEjD,YAAU,oBAAoB,WAAW,KAAK;AAE9C,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,EACrB,CAAC;AAED,QAAM,SAAS,MAAM,YAAY;AAAA,IAC/B;AAAA,IACA,UAAU;AAAA,IACV,SAAS;AAAA,IACT,cAAc;AAAA,IACd,cAAc,CAAC,QAAQ,QAAQ,QAAQ,QAAQ,OAAO;AAAA,EACxD,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":[]}
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ runHeadless
4
+ } from "./chunk-PINC2LST.js";
5
+ import {
6
+ loadTaskPlan,
7
+ saveTaskPlan
8
+ } from "./chunk-64FU2L2T.js";
9
+ import {
10
+ applyPlaceholders,
11
+ loadPrompt
12
+ } from "./chunk-OZVHOVDT.js";
13
+ import {
14
+ printError,
15
+ printInfo,
16
+ printSuccess
17
+ } from "./chunk-L7RIGFP6.js";
18
+
19
+ // src/commands/pr.ts
20
+ import { join } from "path";
21
+ import { execa } from "execa";
22
+ function parsePrUrl(output) {
23
+ const match = output.match(/(https:\/\/github\.com\/[^\s]+\/pull\/\d+)/);
24
+ return match?.[1] ?? null;
25
+ }
26
+ async function runPr(issue) {
27
+ const issueNumber = issue.replace(/^#/, "");
28
+ const issueDir = join("issues", issueNumber);
29
+ const tasksPath = join(issueDir, "tasks.json");
30
+ printInfo(`Creating PR for issue #${issueNumber}...`);
31
+ let branchName;
32
+ try {
33
+ const proc = await execa("git", ["branch", "--show-current"], { reject: false });
34
+ branchName = proc.stdout?.toString().trim() ?? "";
35
+ if (!branchName) {
36
+ printError("Could not determine current branch");
37
+ return 1;
38
+ }
39
+ } catch {
40
+ printError("Failed to get current branch");
41
+ return 1;
42
+ }
43
+ const template = await loadPrompt("pr");
44
+ const prompt = applyPlaceholders(template, {
45
+ __ISSUE_NUMBER__: issueNumber,
46
+ __BRANCH_NAME__: branchName,
47
+ __TASKS_PATH__: tasksPath
48
+ });
49
+ const result = await runHeadless({
50
+ prompt,
51
+ maxTurns: 15,
52
+ timeout: 12e4,
53
+ outputFormat: "text",
54
+ allowedTools: ["Bash", "Read", "Glob", "Grep"]
55
+ });
56
+ if (!result.success) {
57
+ printError(`PR creation failed: ${result.error}`);
58
+ return 1;
59
+ }
60
+ const prUrl = parsePrUrl(result.result);
61
+ try {
62
+ const plan = await loadTaskPlan(tasksPath);
63
+ plan.pipeline.prCreated = true;
64
+ await saveTaskPlan(tasksPath, plan);
65
+ } catch {
66
+ }
67
+ if (prUrl) {
68
+ printSuccess(`PR created: ${prUrl}`);
69
+ } else {
70
+ printSuccess("PR creation completed");
71
+ }
72
+ return 0;
73
+ }
74
+
75
+ export {
76
+ runPr
77
+ };
78
+ //# sourceMappingURL=chunk-7FRXZ2XA.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 { applyPlaceholders, loadPrompt } from '../core/prompt-resolver.js';\nimport { loadTaskPlan, saveTaskPlan } from '../core/state-manager.js';\nimport { printError, printInfo, printSuccess } from '../ui/logger.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\nexport async function runPr(issue: string): Promise<number> {\n const issueNumber = issue.replace(/^#/, '');\n const issueDir = join('issues', issueNumber);\n const tasksPath = join(issueDir, 'tasks.json');\n\n printInfo(`Creating PR for issue #${issueNumber}...`);\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 });\n\n const result = await runHeadless({\n prompt,\n maxTurns: 15,\n timeout: 120_000,\n outputFormat: 'text',\n allowedTools: ['Bash', 'Read', 'Glob', 'Grep'],\n });\n\n if (!result.success) {\n printError(`PR creation failed: ${result.error}`);\n return 1;\n }\n\n const prUrl = parsePrUrl(result.result);\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;AAStB,SAAS,WAAW,QAA+B;AACjD,QAAM,QAAQ,OAAO,MAAM,4CAA4C;AACvE,SAAO,QAAQ,CAAC,KAAK;AACvB;AAEA,eAAsB,MAAM,OAAgC;AAC1D,QAAM,cAAc,MAAM,QAAQ,MAAM,EAAE;AAC1C,QAAM,WAAW,KAAK,UAAU,WAAW;AAC3C,QAAM,YAAY,KAAK,UAAU,YAAY;AAE7C,YAAU,0BAA0B,WAAW,KAAK;AAGpD,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,EAClB,CAAC;AAED,QAAM,SAAS,MAAM,YAAY;AAAA,IAC/B;AAAA,IACA,UAAU;AAAA,IACV,SAAS;AAAA,IACT,cAAc;AAAA,IACd,cAAc,CAAC,QAAQ,QAAQ,QAAQ,MAAM;AAAA,EAC/C,CAAC;AAED,MAAI,CAAC,OAAO,SAAS;AACnB,eAAW,uBAAuB,OAAO,KAAK,EAAE;AAChD,WAAO;AAAA,EACT;AAEA,QAAM,QAAQ,WAAW,OAAO,MAAM;AAGtC,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":[]}