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.
- package/README.md +226 -0
- package/dist/analyze-YYQQP64A.js +12 -0
- package/dist/analyze-YYQQP64A.js.map +1 -0
- package/dist/chunk-64FU2L2T.js +180 -0
- package/dist/chunk-64FU2L2T.js.map +1 -0
- package/dist/chunk-7APADNBV.js +68 -0
- package/dist/chunk-7APADNBV.js.map +1 -0
- package/dist/chunk-7FRXZ2XA.js +78 -0
- package/dist/chunk-7FRXZ2XA.js.map +1 -0
- package/dist/chunk-D4WBCVB4.js +85 -0
- package/dist/chunk-D4WBCVB4.js.map +1 -0
- package/dist/chunk-G36DIQ4J.js +80 -0
- package/dist/chunk-G36DIQ4J.js.map +1 -0
- package/dist/chunk-JEUHCE3V.js +123 -0
- package/dist/chunk-JEUHCE3V.js.map +1 -0
- package/dist/chunk-L7RIGFP6.js +92 -0
- package/dist/chunk-L7RIGFP6.js.map +1 -0
- package/dist/chunk-OZVHOVDT.js +43 -0
- package/dist/chunk-OZVHOVDT.js.map +1 -0
- package/dist/chunk-PINC2LST.js +82 -0
- package/dist/chunk-PINC2LST.js.map +1 -0
- package/dist/chunk-V356G3JS.js +611 -0
- package/dist/chunk-V356G3JS.js.map +1 -0
- package/dist/chunk-ZOX7M2C2.js +85 -0
- package/dist/chunk-ZOX7M2C2.js.map +1 -0
- package/dist/cli.js +76 -0
- package/dist/cli.js.map +1 -0
- package/dist/execute-UF3IY2MG.js +11 -0
- package/dist/execute-UF3IY2MG.js.map +1 -0
- package/dist/generate-WMYJEPI4.js +48 -0
- package/dist/generate-WMYJEPI4.js.map +1 -0
- package/dist/init-QES2HLP5.js +9 -0
- package/dist/init-QES2HLP5.js.map +1 -0
- package/dist/plan-BKSUDN34.js +12 -0
- package/dist/plan-BKSUDN34.js.map +1 -0
- package/dist/pr-E2ASXBWE.js +12 -0
- package/dist/pr-E2ASXBWE.js.map +1 -0
- package/dist/prd-WYW35SGL.js +12 -0
- package/dist/prd-WYW35SGL.js.map +1 -0
- package/dist/review-YI2WPNC4.js +12 -0
- package/dist/review-YI2WPNC4.js.map +1 -0
- package/dist/run-J62FIB5O.js +288 -0
- package/dist/run-J62FIB5O.js.map +1 -0
- package/package.json +58 -0
- package/prompts/analyze.md +28 -0
- package/prompts/execute.md +124 -0
- package/prompts/generate.md +19 -0
- package/prompts/plan.md +49 -0
- package/prompts/pr.md +19 -0
- package/prompts/prd.md +30 -0
- 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":[]}
|