automata-cli 0.2.0-develop.7 → 0.2.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 (3) hide show
  1. package/README.md +57 -9
  2. package/dist/index.js +953 -23
  3. package/package.json +3 -2
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # automata-cli
2
2
 
3
- A command-line interface tool.
3
+ A command-line interface tool for automating Git and project workflows.
4
4
 
5
5
  ## Installation
6
6
 
@@ -8,32 +8,80 @@ A command-line interface tool.
8
8
  npm install -g automata-cli
9
9
  ```
10
10
 
11
- ## Usage
11
+ ## Quick start
12
12
 
13
13
  ```bash
14
14
  automata --help
15
15
  ```
16
16
 
17
- ## Commands
17
+ ---
18
18
 
19
- ### `automata config`
19
+ ## `automata config`
20
20
 
21
- Launch the interactive configuration wizard. Use arrow keys to select the remote environment type and press Enter to save.
21
+ Launch the interactive configuration wizard to select the remote environment type.
22
22
 
23
23
  ```bash
24
24
  automata config
25
25
  ```
26
26
 
27
+ Configuration is saved to `.automata/config.json` in the current directory.
28
+
27
29
  ### `automata config set type <value>`
28
30
 
29
- Set a configuration value non-interactively (useful in scripts or CI).
31
+ Set the remote type non-interactively (useful in scripts or CI).
30
32
 
31
33
  ```bash
32
- automata config set type gh # GitHub
34
+ automata config set type gh # GitHub (requires gh CLI)
33
35
  automata config set type azdo # Azure DevOps
34
36
  ```
35
37
 
36
- Configuration is saved to `.automata/config.json` in the current directory.
38
+ ---
39
+
40
+ ## `automata git`
41
+
42
+ Git workflow helpers. Most commands require the [`gh` CLI](https://cli.github.com/) installed and authenticated. `publish-release` only requires `git`.
43
+
44
+ ### `automata git get-pr-info`
45
+
46
+ Show the pull request for the current branch, including CI check results.
47
+
48
+ ```bash
49
+ automata git get-pr-info # human-readable output
50
+ automata git get-pr-info --json # full PR object as JSON
51
+ ```
52
+
53
+ ### `automata git get-pr-comments`
54
+
55
+ List open (unresolved) review thread comments on the current branch's PR.
56
+
57
+ ```bash
58
+ automata git get-pr-comments # human-readable output
59
+ automata git get-pr-comments --json # JSON array output
60
+ ```
61
+
62
+ ### `automata git finish-feature`
63
+
64
+ Clean up a merged feature branch: checkout `develop`, pull latest, delete the local branch.
65
+
66
+ ```bash
67
+ automata git finish-feature
68
+ ```
69
+
70
+ Checks that the PR exists and is merged, the working tree is clean, and the remote tracking branch is gone before making any changes.
71
+
72
+ ### `automata git publish-release`
73
+
74
+ Execute the full GitFlow release sequence and push to `origin`. Only requires `git`.
75
+
76
+ ```bash
77
+ automata git publish-release # auto-detect version from master tag
78
+ automata git publish-release 2.0.0 # explicit version
79
+ automata git publish-release --dry-run # preview without executing
80
+ ```
81
+
82
+ When no version is given, the latest semver tag on `master` is detected and the minor segment is incremented (e.g. `1.2.0 → 1.3.0`).
83
+
84
+ ---
37
85
 
38
86
  ## Development
39
87
 
@@ -53,7 +101,7 @@ npm install
53
101
  ### Scripts
54
102
 
55
103
  | Command | Description |
56
- | --- | --- |
104
+ |---|---|
57
105
  | `npm run build` | Build the CLI with tsup |
58
106
  | `npm test` | Build and run tests with vitest |
59
107
  | `npm run lint` | Lint source files with ESLint |
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { Command as Command2 } from "commander";
4
+ import { Command as Command4 } from "commander";
5
5
 
6
6
  // src/version.ts
7
7
  import { readFileSync } from "fs";
@@ -44,43 +44,151 @@ function writeConfig(config) {
44
44
 
45
45
  // src/config/ConfigWizard.tsx
46
46
  import { jsx, jsxs } from "react/jsx-runtime";
47
- var OPTIONS = [
47
+ var REMOTE_OPTIONS = [
48
48
  { label: "GitHub", value: "gh" },
49
49
  { label: "Azure DevOps", value: "azdo" }
50
50
  ];
51
+ var TECHNIQUE_OPTIONS = [
52
+ { label: "By Label", value: "label" },
53
+ { label: "By Assignee", value: "assignee" },
54
+ { label: "By Title Contains", value: "title-contains" }
55
+ ];
51
56
  function ConfigWizard() {
52
57
  const existing = readConfig();
53
- const initialIndex = OPTIONS.findIndex((o) => o.value === existing.remoteType);
54
- const [selectedIndex, setSelectedIndex] = useState(initialIndex >= 0 ? initialIndex : 0);
58
+ const initialRemoteIndex = REMOTE_OPTIONS.findIndex((o) => o.value === existing.remoteType);
59
+ const initialTechIndex = TECHNIQUE_OPTIONS.findIndex((o) => o.value === existing.issueDiscoveryTechnique);
60
+ const [screen, setScreen] = useState("remote");
61
+ const [selectedRemoteIndex, setSelectedRemoteIndex] = useState(initialRemoteIndex >= 0 ? initialRemoteIndex : 0);
62
+ const [selectedTechIndex, setSelectedTechIndex] = useState(initialTechIndex >= 0 ? initialTechIndex : 0);
63
+ const [discoveryValue, setDiscoveryValue] = useState(existing.issueDiscoveryValue ?? "");
64
+ const [systemPrompt, setSystemPrompt] = useState(existing.claudeSystemPrompt ?? "");
65
+ const [pendingRemote, setPendingRemote] = useState(existing.remoteType ?? "gh");
66
+ const [pendingTechnique, setPendingTechnique] = useState(
67
+ existing.issueDiscoveryTechnique ?? "label"
68
+ );
55
69
  const { exit } = useApp();
56
70
  useInput((input, key) => {
57
- if (key.upArrow) {
58
- setSelectedIndex((i) => i > 0 ? i - 1 : OPTIONS.length - 1);
59
- } else if (key.downArrow) {
60
- setSelectedIndex((i) => i < OPTIONS.length - 1 ? i + 1 : 0);
61
- } else if (key.return) {
62
- const chosen = OPTIONS[selectedIndex];
63
- writeConfig({ ...existing, remoteType: chosen.value });
64
- exit();
65
- } else if (key.escape || key.ctrl && input === "c") {
66
- exit();
71
+ if (screen === "remote") {
72
+ if (key.upArrow) {
73
+ setSelectedRemoteIndex((i) => i > 0 ? i - 1 : REMOTE_OPTIONS.length - 1);
74
+ } else if (key.downArrow) {
75
+ setSelectedRemoteIndex((i) => i < REMOTE_OPTIONS.length - 1 ? i + 1 : 0);
76
+ } else if (key.return) {
77
+ const chosen = REMOTE_OPTIONS[selectedRemoteIndex];
78
+ setPendingRemote(chosen.value);
79
+ if (chosen.value === "gh") {
80
+ setScreen("technique");
81
+ } else {
82
+ writeConfig({ ...existing, remoteType: chosen.value });
83
+ exit();
84
+ }
85
+ } else if (key.escape || key.ctrl && input === "c") {
86
+ exit();
87
+ }
88
+ } else if (screen === "technique") {
89
+ if (key.upArrow) {
90
+ setSelectedTechIndex((i) => i > 0 ? i - 1 : TECHNIQUE_OPTIONS.length - 1);
91
+ } else if (key.downArrow) {
92
+ setSelectedTechIndex((i) => i < TECHNIQUE_OPTIONS.length - 1 ? i + 1 : 0);
93
+ } else if (key.return) {
94
+ const chosen = TECHNIQUE_OPTIONS[selectedTechIndex];
95
+ setPendingTechnique(chosen.value);
96
+ setScreen("value");
97
+ } else if (key.escape || key.ctrl && input === "c") {
98
+ exit();
99
+ }
100
+ } else if (screen === "value") {
101
+ if (key.return) {
102
+ setScreen("system-prompt");
103
+ } else if (key.backspace || key.delete) {
104
+ setDiscoveryValue((v) => v.slice(0, -1));
105
+ } else if (key.escape || key.ctrl && input === "c") {
106
+ exit();
107
+ } else if (input && !key.ctrl && !key.meta) {
108
+ setDiscoveryValue((v) => v + input);
109
+ }
110
+ } else if (screen === "system-prompt") {
111
+ if (key.return) {
112
+ writeConfig({
113
+ ...existing,
114
+ remoteType: pendingRemote,
115
+ issueDiscoveryTechnique: pendingTechnique,
116
+ issueDiscoveryValue: discoveryValue || void 0,
117
+ claudeSystemPrompt: systemPrompt || void 0
118
+ });
119
+ exit();
120
+ } else if (key.backspace || key.delete) {
121
+ setSystemPrompt((v) => v.slice(0, -1));
122
+ } else if (key.escape || key.ctrl && input === "c") {
123
+ exit();
124
+ } else if (input && !key.ctrl && !key.meta) {
125
+ setSystemPrompt((v) => v + input);
126
+ }
67
127
  }
68
128
  });
129
+ if (screen === "remote") {
130
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginY: 1, children: [
131
+ /* @__PURE__ */ jsx(Text, { bold: true, children: "Configure Automata" }),
132
+ /* @__PURE__ */ jsx(Text, { children: " " }),
133
+ /* @__PURE__ */ jsx(Text, { children: "Remote environment type:" }),
134
+ REMOTE_OPTIONS.map((option, index) => /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Text, { color: index === selectedRemoteIndex ? "cyan" : void 0, children: [
135
+ index === selectedRemoteIndex ? "\u276F " : " ",
136
+ option.label
137
+ ] }) }, option.value)),
138
+ /* @__PURE__ */ jsx(Text, { children: " " }),
139
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191/\u2193 to move \xB7 Enter to confirm \xB7 Ctrl+C to cancel" })
140
+ ] });
141
+ }
142
+ if (screen === "technique") {
143
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginY: 1, children: [
144
+ /* @__PURE__ */ jsx(Text, { bold: true, children: "Configure Issue Discovery Technique" }),
145
+ /* @__PURE__ */ jsx(Text, { children: " " }),
146
+ /* @__PURE__ */ jsx(Text, { children: "How to find the next issue to work on:" }),
147
+ TECHNIQUE_OPTIONS.map((option, index) => /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Text, { color: index === selectedTechIndex ? "cyan" : void 0, children: [
148
+ index === selectedTechIndex ? "\u276F " : " ",
149
+ option.label
150
+ ] }) }, option.value)),
151
+ /* @__PURE__ */ jsx(Text, { children: " " }),
152
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191/\u2193 to move \xB7 Enter to confirm \xB7 Ctrl+C to cancel" })
153
+ ] });
154
+ }
155
+ if (screen === "value") {
156
+ const techLabel = TECHNIQUE_OPTIONS.find((t) => t.value === pendingTechnique)?.label ?? pendingTechnique;
157
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginY: 1, children: [
158
+ /* @__PURE__ */ jsx(Text, { bold: true, children: "Configure Issue Discovery Value" }),
159
+ /* @__PURE__ */ jsx(Text, { children: " " }),
160
+ /* @__PURE__ */ jsxs(Text, { children: [
161
+ techLabel,
162
+ " value:",
163
+ " ",
164
+ /* @__PURE__ */ jsxs(Text, { color: "cyan", children: [
165
+ discoveryValue,
166
+ /* @__PURE__ */ jsx(Text, { children: "_" })
167
+ ] })
168
+ ] }),
169
+ /* @__PURE__ */ jsx(Text, { children: " " }),
170
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Type value \xB7 Enter to confirm \xB7 Ctrl+C to cancel" })
171
+ ] });
172
+ }
69
173
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginY: 1, children: [
70
- /* @__PURE__ */ jsx(Text, { bold: true, children: "Configure Automata" }),
174
+ /* @__PURE__ */ jsx(Text, { bold: true, children: "Configure Claude System Prompt" }),
71
175
  /* @__PURE__ */ jsx(Text, { children: " " }),
72
- /* @__PURE__ */ jsx(Text, { children: "Remote environment type:" }),
73
- OPTIONS.map((option, index) => /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Text, { color: index === selectedIndex ? "cyan" : void 0, children: [
74
- index === selectedIndex ? "\u276F " : " ",
75
- option.label
76
- ] }) }, option.value)),
176
+ /* @__PURE__ */ jsxs(Text, { children: [
177
+ "System prompt (optional):",
178
+ " ",
179
+ /* @__PURE__ */ jsxs(Text, { color: "cyan", children: [
180
+ systemPrompt,
181
+ /* @__PURE__ */ jsx(Text, { children: "_" })
182
+ ] })
183
+ ] }),
77
184
  /* @__PURE__ */ jsx(Text, { children: " " }),
78
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191/\u2193 to move \xB7 Enter to confirm \xB7 Ctrl+C to cancel" })
185
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Type prompt \xB7 Enter to save and exit \xB7 Ctrl+C to cancel" })
79
186
  ] });
80
187
  }
81
188
 
82
189
  // src/commands/config.ts
83
190
  var VALID_TYPES = ["gh", "azdo"];
191
+ var VALID_TECHNIQUES = ["label", "assignee", "title-contains"];
84
192
  var configSetType = new Command("type").description("Set the remote environment type").argument("<value>", "Remote type: gh (GitHub) or azdo (Azure DevOps)").action((value) => {
85
193
  if (!VALID_TYPES.includes(value)) {
86
194
  process.stderr.write(`Error: invalid type "${value}". Must be one of: ${VALID_TYPES.join(", ")}
@@ -92,16 +200,838 @@ var configSetType = new Command("type").description("Set the remote environment
92
200
  process.stdout.write(`Remote type set to: ${value}
93
201
  `);
94
202
  });
95
- var configSet = new Command("set").description("Set a configuration value").addCommand(configSetType);
203
+ var configSetIssueDiscoveryTechnique = new Command("issue-discovery-technique").description("Set the issue discovery technique (GitHub mode only)").argument("<value>", `Technique: ${VALID_TECHNIQUES.join(", ")}`).action((value) => {
204
+ if (!VALID_TECHNIQUES.includes(value)) {
205
+ process.stderr.write(`Error: invalid technique "${value}". Must be one of: ${VALID_TECHNIQUES.join(", ")}
206
+ `);
207
+ process.exit(1);
208
+ }
209
+ const current = readConfig();
210
+ writeConfig({ ...current, issueDiscoveryTechnique: value });
211
+ process.stdout.write(`Issue discovery technique set to: ${value}
212
+ `);
213
+ });
214
+ var configSetIssueDiscoveryValue = new Command("issue-discovery-value").description("Set the value for the issue discovery technique (label name, username, or search string)").argument("<value>", "The filter value").action((value) => {
215
+ const current = readConfig();
216
+ writeConfig({ ...current, issueDiscoveryValue: value });
217
+ process.stdout.write(`Issue discovery value set to: ${value}
218
+ `);
219
+ });
220
+ var configSetClaudeSystemPrompt = new Command("claude-system-prompt").description("Set the system prompt used when invoking Claude Code").argument("<value>", "System prompt text").action((value) => {
221
+ const current = readConfig();
222
+ writeConfig({ ...current, claudeSystemPrompt: value });
223
+ process.stdout.write(`Claude system prompt set.
224
+ `);
225
+ });
226
+ var configSet = new Command("set").description("Set a configuration value").addCommand(configSetType).addCommand(configSetIssueDiscoveryTechnique).addCommand(configSetIssueDiscoveryValue).addCommand(configSetClaudeSystemPrompt);
96
227
  var configCommand = new Command("config").description("Configure automata settings").addCommand(configSet).action(async () => {
97
228
  const { waitUntilExit } = render(React2.createElement(ConfigWizard));
98
229
  await waitUntilExit();
99
230
  });
100
231
 
232
+ // src/commands/git.ts
233
+ import { Command as Command2 } from "commander";
234
+
235
+ // src/git/gitService.ts
236
+ import { spawnSync as spawnSync2 } from "child_process";
237
+
238
+ // src/config/azdoService.ts
239
+ import { spawnSync } from "child_process";
240
+ function run(cmd, args) {
241
+ const result = spawnSync(cmd, args, { encoding: "utf8" });
242
+ if (result.error) {
243
+ const err = result.error;
244
+ if (err.code === "ENOENT") {
245
+ throw new Error("`azdo` CLI is not installed or not on PATH.");
246
+ }
247
+ throw new Error(err.message);
248
+ }
249
+ return {
250
+ stdout: result.stdout ?? "",
251
+ stderr: result.stderr ?? "",
252
+ status: result.status ?? 1
253
+ };
254
+ }
255
+ function mapStatus(azdoStatus) {
256
+ switch (azdoStatus) {
257
+ case "active":
258
+ return "OPEN";
259
+ case "completed":
260
+ return "MERGED";
261
+ case "abandoned":
262
+ return "CLOSED";
263
+ default:
264
+ return azdoStatus.toUpperCase();
265
+ }
266
+ }
267
+ function getPrInfo() {
268
+ const { stdout, stderr, status } = run("azdo", ["pr", "status", "--json"]);
269
+ if (status !== 0) {
270
+ throw new Error(stderr.trim() || "Failed to query Azure DevOps PR status. Is `azdo` installed and authenticated?");
271
+ }
272
+ const parsed = JSON.parse(stdout);
273
+ if (parsed.pullRequests.length === 0) {
274
+ return null;
275
+ }
276
+ const pr = parsed.pullRequests[0];
277
+ return {
278
+ number: pr.id,
279
+ title: pr.title,
280
+ state: mapStatus(pr.status),
281
+ url: pr.url,
282
+ checks: []
283
+ };
284
+ }
285
+
286
+ // src/git/gitService.ts
287
+ function run2(cmd, args) {
288
+ const result = spawnSync2(cmd, args, { encoding: "utf8" });
289
+ return {
290
+ stdout: result.stdout ?? "",
291
+ stderr: result.stderr ?? "",
292
+ status: result.status ?? 1
293
+ };
294
+ }
295
+ function getCurrentBranch() {
296
+ const { stdout, status } = run2("git", ["rev-parse", "--abbrev-ref", "HEAD"]);
297
+ if (status !== 0) {
298
+ throw new Error("Failed to determine current branch. Are you inside a git repository?");
299
+ }
300
+ return stdout.trim();
301
+ }
302
+ function parseOwnerRepo() {
303
+ const { stdout, status } = run2("git", ["remote", "get-url", "origin"]);
304
+ if (status !== 0) return null;
305
+ const url = stdout.trim();
306
+ const https = url.match(/github\.com\/([^/]+\/[^/]+?)(?:\.git)?$/);
307
+ if (https) return https[1];
308
+ const ssh = url.match(/github\.com:([^/]+\/[^/]+?)(?:\.git)?$/);
309
+ if (ssh) return ssh[1];
310
+ return null;
311
+ }
312
+ function extractLastMarkdownUrl(markdown) {
313
+ const matches = [...markdown.matchAll(/\]\((https?:\/\/[^)]+)\)/g)];
314
+ return matches.length > 0 ? matches[matches.length - 1][1] ?? null : null;
315
+ }
316
+ function fetchCheckRunOutputs(ownerRepo, sha) {
317
+ const { stdout, status } = run2("gh", [
318
+ "api",
319
+ `repos/${ownerRepo}/commits/${sha}/check-runs`,
320
+ "--jq",
321
+ ".check_runs[] | {name, html_url, details_url, output}"
322
+ ]);
323
+ const map = /* @__PURE__ */ new Map();
324
+ if (status !== 0) return map;
325
+ for (const line of stdout.trim().split("\n")) {
326
+ if (!line) continue;
327
+ try {
328
+ const item = JSON.parse(line);
329
+ const title = item.output?.title ?? "";
330
+ const summaryUrl = item.output?.summary ? extractLastMarkdownUrl(item.output.summary) : null;
331
+ const detailsUrl = summaryUrl ?? (item.details_url !== item.html_url ? item.details_url : "") ?? item.html_url;
332
+ map.set(item.name, { title, detailsUrl });
333
+ } catch {
334
+ }
335
+ }
336
+ return map;
337
+ }
338
+ function getPrInfoGh(branch) {
339
+ const { stdout, stderr, status } = run2("gh", [
340
+ "pr",
341
+ "view",
342
+ branch,
343
+ "--json",
344
+ "number,title,state,url,headRefOid,statusCheckRollup"
345
+ ]);
346
+ if (status !== 0) {
347
+ if (stderr.includes("no pull requests found") || stderr.includes("Could not resolve")) {
348
+ return null;
349
+ }
350
+ throw new Error(stderr.trim() || "Failed to query GitHub. Is `gh` installed and authenticated?");
351
+ }
352
+ const raw = JSON.parse(stdout);
353
+ const failedChecks = (raw.statusCheckRollup ?? []).filter(
354
+ (c) => c.conclusion !== null && ["FAILURE", "TIMED_OUT", "ACTION_REQUIRED", "CANCELLED"].includes(c.conclusion)
355
+ );
356
+ const ownerRepo = failedChecks.length > 0 ? parseOwnerRepo() : null;
357
+ const checkOutputs = ownerRepo ? fetchCheckRunOutputs(ownerRepo, raw.headRefOid) : /* @__PURE__ */ new Map();
358
+ const checks = (raw.statusCheckRollup ?? []).map((c) => {
359
+ const enriched = checkOutputs.get(c.name);
360
+ return {
361
+ name: c.name,
362
+ status: c.status,
363
+ conclusion: c.conclusion,
364
+ description: enriched?.title || c.description || "",
365
+ detailsUrl: enriched?.detailsUrl || c.detailsUrl || ""
366
+ };
367
+ });
368
+ return { number: raw.number, title: raw.title, state: raw.state, url: raw.url, checks };
369
+ }
370
+ function getPrInfo2(branch) {
371
+ const config = readConfig();
372
+ if (config.remoteType === "azdo") {
373
+ return getPrInfo();
374
+ }
375
+ return getPrInfoGh(branch);
376
+ }
377
+ function isUpstreamGone(branch) {
378
+ const { status } = run2("git", ["ls-remote", "--exit-code", "--heads", "origin", branch]);
379
+ return status !== 0;
380
+ }
381
+ function hasUncommittedChanges() {
382
+ const { stdout } = run2("git", ["status", "--porcelain"]);
383
+ return stdout.trim().length > 0;
384
+ }
385
+ function checkoutAndPull(targetBranch) {
386
+ const checkout = run2("git", ["checkout", targetBranch]);
387
+ if (checkout.status !== 0) {
388
+ throw new Error(`Failed to checkout ${targetBranch}: ${checkout.stderr.trim()}`);
389
+ }
390
+ const pull = run2("git", ["pull"]);
391
+ if (pull.status !== 0) {
392
+ throw new Error(`Failed to pull ${targetBranch}: ${pull.stderr.trim()}`);
393
+ }
394
+ }
395
+ function fetchPrune() {
396
+ const result = run2("git", ["fetch", "--prune"]);
397
+ if (result.status !== 0) {
398
+ throw new Error(`Failed to fetch --prune: ${result.stderr.trim()}`);
399
+ }
400
+ }
401
+ function deleteLocalBranch(branch) {
402
+ const result = run2("git", ["branch", "-D", branch]);
403
+ if (result.status !== 0) {
404
+ throw new Error(`Failed to delete branch ${branch}: ${result.stderr.trim()}`);
405
+ }
406
+ }
407
+ var REVIEW_THREADS_QUERY = `
408
+ query($owner:String!,$repo:String!,$prNumber:Int!){
409
+ repository(owner:$owner,name:$repo){
410
+ pullRequest(number:$prNumber){
411
+ reviewThreads(first:100){
412
+ nodes{
413
+ isResolved
414
+ isOutdated
415
+ comments(first:1){
416
+ nodes{ author{login} body path line createdAt }
417
+ }
418
+ }
419
+ }
420
+ }
421
+ }
422
+ }`.trim();
423
+ function getPrCommentsGh(branch) {
424
+ const prView = run2("gh", ["pr", "view", branch, "--json", "number"]);
425
+ if (prView.status !== 0) {
426
+ if (prView.stderr.includes("no pull requests found") || prView.stderr.includes("Could not resolve")) {
427
+ return null;
428
+ }
429
+ throw new Error(prView.stderr.trim() || "Failed to query GitHub. Is `gh` installed and authenticated?");
430
+ }
431
+ const { number: prNumber } = JSON.parse(prView.stdout);
432
+ const ownerRepo = parseOwnerRepo();
433
+ if (!ownerRepo) {
434
+ throw new Error("Could not determine GitHub owner/repo from git remote. Is 'origin' set to a GitHub URL?");
435
+ }
436
+ const slashIdx = ownerRepo.indexOf("/");
437
+ const owner = ownerRepo.slice(0, slashIdx);
438
+ const repo = ownerRepo.slice(slashIdx + 1);
439
+ const gql = run2("gh", [
440
+ "api",
441
+ "graphql",
442
+ "-f",
443
+ `query=${REVIEW_THREADS_QUERY}`,
444
+ "-f",
445
+ `owner=${owner}`,
446
+ "-f",
447
+ `repo=${repo}`,
448
+ "-F",
449
+ `prNumber=${String(prNumber)}`
450
+ ]);
451
+ if (gql.status !== 0) {
452
+ throw new Error(gql.stderr.trim() || "Failed to query GitHub GraphQL API.");
453
+ }
454
+ const response = JSON.parse(gql.stdout);
455
+ const threads = response.data.repository.pullRequest.reviewThreads.nodes;
456
+ return threads.filter((t) => !t.isResolved && t.comments.nodes.length > 0).map((t) => {
457
+ const c = t.comments.nodes[0];
458
+ return {
459
+ author: c.author.login,
460
+ body: c.body,
461
+ path: c.path,
462
+ line: c.line ?? null,
463
+ createdAt: c.createdAt
464
+ };
465
+ });
466
+ }
467
+ function getPrComments(branch) {
468
+ const config = readConfig();
469
+ if (config.remoteType === "azdo") {
470
+ return "unsupported";
471
+ }
472
+ return getPrCommentsGh(branch);
473
+ }
474
+ var SEMVER_RE = /^v?(\d+)\.(\d+)\.(\d+)$/;
475
+ function getLatestTagOnMaster() {
476
+ const { stdout, status } = run2("git", [
477
+ "describe",
478
+ "--tags",
479
+ "--abbrev=0",
480
+ "--match",
481
+ "[0-9]*.[0-9]*.[0-9]*",
482
+ "--match",
483
+ "v[0-9]*.[0-9]*.[0-9]*",
484
+ "master"
485
+ ]);
486
+ if (status !== 0) return null;
487
+ const tag = stdout.trim();
488
+ const m = SEMVER_RE.exec(tag);
489
+ if (!m) return null;
490
+ return `${m[1]}.${m[2]}.${m[3]}`;
491
+ }
492
+ function bumpMinorVersion(version2) {
493
+ const m = SEMVER_RE.exec(version2);
494
+ if (!m) throw new Error(`Invalid semver: ${version2}`);
495
+ return `${m[1]}.${String(Number(m[2]) + 1)}.0`;
496
+ }
497
+ function tagExists(version2) {
498
+ const { stdout, status, stderr } = run2("git", ["tag", "-l", version2]);
499
+ if (status !== 0) {
500
+ throw new Error(`Command failed: git tag -l ${version2}
501
+ ${stderr.trim()}`);
502
+ }
503
+ return stdout.trim().length > 0;
504
+ }
505
+ function publishRelease(version2, dryRun) {
506
+ const releaseBranch = `release/${version2}`;
507
+ const steps = [
508
+ { args: ["checkout", "-b", releaseBranch], desc: `git checkout -b ${releaseBranch}` },
509
+ { args: ["checkout", "master"], desc: `git checkout master` },
510
+ { args: ["merge", "--no-ff", releaseBranch], desc: `git merge --no-ff ${releaseBranch}` },
511
+ { args: ["tag", version2], desc: `git tag ${version2}` },
512
+ { args: ["checkout", "develop"], desc: `git checkout develop` },
513
+ { args: ["merge", "--no-ff", releaseBranch], desc: `git merge --no-ff ${releaseBranch}` },
514
+ { args: ["branch", "-d", releaseBranch], desc: `git branch -d ${releaseBranch}` },
515
+ { args: ["push", "origin", "develop", "master", version2], desc: `git push origin develop master ${version2}` }
516
+ ];
517
+ for (const step of steps) {
518
+ if (dryRun) {
519
+ process.stdout.write(`[dry-run] ${step.desc}
520
+ `);
521
+ continue;
522
+ }
523
+ const { status, stderr } = run2("git", step.args);
524
+ if (status !== 0) {
525
+ throw new Error(`Command failed: ${step.desc}
526
+ ${stderr.trim()}`);
527
+ }
528
+ }
529
+ }
530
+
531
+ // src/commands/git.ts
532
+ var FAIL_CONCLUSIONS = /* @__PURE__ */ new Set(["FAILURE", "TIMED_OUT", "ACTION_REQUIRED", "CANCELLED"]);
533
+ var SKIP_CONCLUSIONS = /* @__PURE__ */ new Set(["SKIPPED", "NEUTRAL"]);
534
+ function checkSymbol(check) {
535
+ if (check.status !== "COMPLETED") return "\u25CF";
536
+ if (check.conclusion === "SUCCESS") return "\u2713";
537
+ if (check.conclusion !== null && SKIP_CONCLUSIONS.has(check.conclusion)) return "\u25CB";
538
+ if (check.conclusion !== null && FAIL_CONCLUSIONS.has(check.conclusion)) return "\u2717";
539
+ return "\u25CF";
540
+ }
541
+ function formatCheckSummary(checks) {
542
+ const running = checks.some((c) => c.status !== "COMPLETED");
543
+ const failed = checks.filter((c) => c.conclusion !== null && FAIL_CONCLUSIONS.has(c.conclusion));
544
+ const errors = failed.length === 0 ? "none" : failed.map((c) => `${c.name}: ${c.description.trim() || c.detailsUrl || "no details available"}`).join("; ");
545
+ return `Checks Running: ${String(running)}
546
+ Check Errors: ${errors}
547
+ `;
548
+ }
549
+ function formatChecks(checks) {
550
+ if (checks.length === 0) return "Checks: none\n";
551
+ const lines = ["Checks:"];
552
+ for (const check of checks) {
553
+ const sym = checkSymbol(check);
554
+ const pending = check.status !== "COMPLETED" ? " (pending)" : "";
555
+ lines.push(` ${sym} ${check.name}${pending}`);
556
+ if (check.conclusion !== null && FAIL_CONCLUSIONS.has(check.conclusion)) {
557
+ const desc = check.description.trim();
558
+ const url = check.detailsUrl.trim();
559
+ if (desc) lines.push(` Details: ${desc}`);
560
+ if (url) lines.push(` URL: ${url}`);
561
+ if (!desc && !url) lines.push(` Details: (no details available)`);
562
+ }
563
+ }
564
+ return lines.join("\n") + "\n";
565
+ }
566
+ function sleep(ms) {
567
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
568
+ }
569
+ function formatFailedChecks(failed) {
570
+ const lines = [];
571
+ for (const check of failed) {
572
+ lines.push(` \u2717 ${check.name}`);
573
+ const desc = check.description.trim();
574
+ const url = check.detailsUrl.trim();
575
+ if (desc) lines.push(` Details: ${desc}`);
576
+ if (url) lines.push(` URL: ${url}`);
577
+ if (!desc && !url) lines.push(` Details: (no details available)`);
578
+ }
579
+ return lines.join("\n") + "\n";
580
+ }
581
+ var POLL_INTERVAL_MS = 1e4;
582
+ var getPrInfoCmd = new Command2("get-pr-info").description("Show pull request info for the current branch").option("--json", "Output as JSON").option("--wait-finish-checks", "Poll until all checks complete, then report pass/fail (exit 1 on failure)").addHelpText(
583
+ "after",
584
+ `
585
+ Check status symbols:
586
+ \u2713 Passed (conclusion: SUCCESS)
587
+ \u2717 Failed (conclusion: FAILURE / TIMED_OUT / ACTION_REQUIRED / CANCELLED)
588
+ \u25CF Pending (status: QUEUED or IN_PROGRESS)
589
+ \u25CB Skipped (conclusion: SKIPPED or NEUTRAL)
590
+
591
+ Failure details are printed beneath each \u2717 check.
592
+ See docs/git.md for full output reference.`
593
+ ).action(async (options) => {
594
+ let branch;
595
+ try {
596
+ branch = getCurrentBranch();
597
+ } catch (err) {
598
+ process.stderr.write(`Error: ${err.message}
599
+ `);
600
+ process.exit(1);
601
+ }
602
+ if (options.waitFinishChecks) {
603
+ let pr2;
604
+ while (true) {
605
+ try {
606
+ pr2 = getPrInfo2(branch);
607
+ } catch (err) {
608
+ process.stderr.write(`Error: ${err.message}
609
+ `);
610
+ process.exit(1);
611
+ }
612
+ if (pr2 === null) {
613
+ process.stderr.write(`Error: No pull request found for branch: ${branch}
614
+ `);
615
+ process.exit(1);
616
+ }
617
+ const running = pr2.checks.filter((c) => c.status !== "COMPLETED");
618
+ if (running.length === 0) break;
619
+ process.stdout.write(`Waiting for ${running.length} check(s) to complete...
620
+ `);
621
+ await sleep(POLL_INTERVAL_MS);
622
+ }
623
+ const failed = pr2.checks.filter((c) => c.conclusion !== null && FAIL_CONCLUSIONS.has(c.conclusion));
624
+ if (failed.length === 0) {
625
+ if (options.json) {
626
+ process.stdout.write(JSON.stringify({ result: "passed" }, null, 2) + "\n");
627
+ } else {
628
+ process.stdout.write(`All checks passed. \u2713
629
+ `);
630
+ }
631
+ process.exit(0);
632
+ } else {
633
+ if (options.json) {
634
+ process.stdout.write(JSON.stringify({ result: "failed", failed }, null, 2) + "\n");
635
+ } else {
636
+ process.stdout.write(`${failed.length} check(s) failed:
637
+ `);
638
+ process.stdout.write(formatFailedChecks(failed));
639
+ }
640
+ process.exit(1);
641
+ }
642
+ return;
643
+ }
644
+ let pr;
645
+ try {
646
+ pr = getPrInfo2(branch);
647
+ } catch (err) {
648
+ process.stderr.write(`Error: ${err.message}
649
+ `);
650
+ process.exit(1);
651
+ }
652
+ if (pr === null) {
653
+ process.stdout.write(`No pull request found for branch: ${branch}
654
+ `);
655
+ process.exit(0);
656
+ }
657
+ if (options.json) {
658
+ process.stdout.write(JSON.stringify(pr, null, 2) + "\n");
659
+ } else {
660
+ process.stdout.write(`PR: #${pr.number}
661
+ Title: ${pr.title}
662
+ State: ${pr.state}
663
+ URL: ${pr.url}
664
+ `);
665
+ process.stdout.write(formatCheckSummary(pr.checks));
666
+ process.stdout.write(formatChecks(pr.checks));
667
+ }
668
+ });
669
+ var ANSI_ESCAPE_RE = new RegExp("\x1B(?:[@-Z\\\\-_]|\\[[0-?]*[ -/]*[@-~])", "g");
670
+ var CONTROL_CHARS_RE = new RegExp("[\0-\b\v\f-\x7F]", "g");
671
+ function sanitizeText(text) {
672
+ return text.replace(ANSI_ESCAPE_RE, "").replace(CONTROL_CHARS_RE, "");
673
+ }
674
+ var getPrCommentsCmd = new Command2("get-pr-comments").description("List open (unresolved) review comments on the pull request for the current branch (GitHub only)").option("--json", "Output as JSON array").addHelpText(
675
+ "after",
676
+ `
677
+ Only GitHub (remoteType: gh) is supported. Azure DevOps is not supported.
678
+ See docs/azdo-gap.md for details.`
679
+ ).action((options) => {
680
+ let branch;
681
+ try {
682
+ branch = getCurrentBranch();
683
+ } catch (err) {
684
+ process.stderr.write(`Error: ${err.message}
685
+ `);
686
+ process.exit(1);
687
+ }
688
+ let comments;
689
+ try {
690
+ comments = getPrComments(branch);
691
+ } catch (err) {
692
+ process.stderr.write(`Error: ${err.message}
693
+ `);
694
+ process.exit(1);
695
+ }
696
+ if (comments === "unsupported") {
697
+ process.stderr.write(
698
+ `Error: get-pr-comments is not supported for Azure DevOps. See docs/azdo-gap.md for details.
699
+ `
700
+ );
701
+ process.exit(1);
702
+ }
703
+ if (comments === null) {
704
+ process.stderr.write(`Error: No pull request found for branch: ${branch}
705
+ `);
706
+ process.exit(1);
707
+ }
708
+ if (options.json) {
709
+ process.stdout.write(JSON.stringify(comments, null, 2) + "\n");
710
+ return;
711
+ }
712
+ if (comments.length === 0) {
713
+ process.stdout.write(`No open comments.
714
+ `);
715
+ return;
716
+ }
717
+ const lines = [];
718
+ for (const c of comments) {
719
+ const loc = c.line !== null ? `${c.path}:${String(c.line)}` : `${c.path}:(file)`;
720
+ const safeBody = sanitizeText(c.body);
721
+ const safeAuthor = sanitizeText(c.author);
722
+ lines.push(`[${safeAuthor}] on ${loc}
723
+ ${safeBody}`);
724
+ }
725
+ process.stdout.write(lines.join("\n\n") + "\n");
726
+ });
727
+ var finishFeatureCmd = new Command2("finish-feature").description("Clean up a merged feature branch: checkout develop, pull, and delete local branch").action(() => {
728
+ let branch;
729
+ try {
730
+ branch = getCurrentBranch();
731
+ } catch (err) {
732
+ process.stderr.write(`Error: ${err.message}
733
+ `);
734
+ process.exit(1);
735
+ }
736
+ if (branch === "develop") {
737
+ process.stderr.write("Error: finish-feature cannot be run from the develop branch.\n");
738
+ process.exit(1);
739
+ }
740
+ if (hasUncommittedChanges()) {
741
+ process.stderr.write(
742
+ "Error: You have uncommitted changes. Commit or stash them before running finish-feature.\n"
743
+ );
744
+ process.exit(1);
745
+ }
746
+ let pr;
747
+ try {
748
+ pr = getPrInfo2(branch);
749
+ } catch (err) {
750
+ process.stderr.write(`Error: ${err.message}
751
+ `);
752
+ process.exit(1);
753
+ }
754
+ if (pr === null) {
755
+ process.stderr.write(`Error: No pull request found for branch: ${branch}
756
+ `);
757
+ process.exit(1);
758
+ }
759
+ if (pr.state === "OPEN") {
760
+ process.stderr.write(`Error: Pull request #${pr.number} is still open. Merge it before finishing the feature.
761
+ `);
762
+ process.exit(1);
763
+ }
764
+ if (pr.state === "CLOSED") {
765
+ process.stderr.write(
766
+ `Error: Pull request #${pr.number} was closed without merging. finish-feature only proceeds for merged PRs.
767
+ `
768
+ );
769
+ process.exit(1);
770
+ }
771
+ if (!isUpstreamGone(branch)) {
772
+ process.stderr.write(
773
+ `Error: Remote tracking branch 'origin/${branch}' still exists. Push or delete it remotely before finishing.
774
+ `
775
+ );
776
+ process.exit(1);
777
+ }
778
+ try {
779
+ process.stdout.write(`Fetching and pruning remote refs...
780
+ `);
781
+ fetchPrune();
782
+ process.stdout.write(`Checking out develop and pulling latest...
783
+ `);
784
+ checkoutAndPull("develop");
785
+ process.stdout.write(`Deleting local branch: ${branch}
786
+ `);
787
+ deleteLocalBranch(branch);
788
+ process.stdout.write(`Done. Branch '${branch}' has been removed.
789
+ `);
790
+ } catch (err) {
791
+ process.stderr.write(`Error: ${err.message}
792
+ `);
793
+ process.exit(1);
794
+ }
795
+ });
796
+ var SEMVER_ARG_RE = /^\d+\.\d+\.\d+$/;
797
+ var publishReleaseCmd = new Command2("publish-release").description("Execute the full GitFlow release sequence and push to origin").argument("[version]", "Release version in X.Y.Z format (auto-detected from master tag if omitted)").option("--dry-run", "Print git commands without executing them").addHelpText(
798
+ "after",
799
+ `
800
+ Release sequence:
801
+ 1. git checkout -b release/<version>
802
+ 2. git checkout master && git merge --no-ff release/<version>
803
+ 3. git tag <version>
804
+ 4. git checkout develop && git merge --no-ff release/<version>
805
+ 5. git branch -d release/<version>
806
+ 6. git push origin develop master <version>
807
+
808
+ When [version] is omitted the latest semver tag on master is detected and the
809
+ minor segment is incremented (e.g. 1.2.0 \u2192 1.3.0).`
810
+ ).action((version2, options) => {
811
+ const dryRun = options.dryRun ?? false;
812
+ let branch;
813
+ try {
814
+ branch = getCurrentBranch();
815
+ } catch (err) {
816
+ process.stderr.write(`Error: ${err.message}
817
+ `);
818
+ process.exit(1);
819
+ }
820
+ if (branch !== "develop") {
821
+ process.stderr.write(
822
+ `Error: publish-release must be run from the 'develop' branch (currently on '${branch}').
823
+ `
824
+ );
825
+ process.exit(1);
826
+ }
827
+ if (hasUncommittedChanges()) {
828
+ process.stderr.write("Error: You have uncommitted changes. Commit or stash them before publishing a release.\n");
829
+ process.exit(1);
830
+ }
831
+ let resolvedVersion;
832
+ if (version2 !== void 0) {
833
+ if (!SEMVER_ARG_RE.test(version2)) {
834
+ process.stderr.write(`Error: Version '${version2}' is not valid semver. Use X.Y.Z format (e.g. 1.2.0).
835
+ `);
836
+ process.exit(1);
837
+ }
838
+ resolvedVersion = version2;
839
+ } else {
840
+ const latest = getLatestTagOnMaster();
841
+ if (latest === null) {
842
+ process.stderr.write(
843
+ "Error: No semver tag found on master. Pass a version explicitly: automata git publish-release <X.Y.Z>\n"
844
+ );
845
+ process.exit(1);
846
+ }
847
+ resolvedVersion = bumpMinorVersion(latest);
848
+ process.stdout.write(`Auto-detected version: ${latest} \u2192 ${resolvedVersion}
849
+ `);
850
+ }
851
+ if (tagExists(resolvedVersion)) {
852
+ process.stderr.write(`Error: Tag '${resolvedVersion}' already exists.
853
+ `);
854
+ process.exit(1);
855
+ }
856
+ if (dryRun) {
857
+ process.stdout.write(`Dry-run: release ${resolvedVersion}
858
+ `);
859
+ } else {
860
+ process.stdout.write(`Publishing release ${resolvedVersion}...
861
+ `);
862
+ }
863
+ try {
864
+ publishRelease(resolvedVersion, dryRun);
865
+ } catch (err) {
866
+ process.stderr.write(`Error: ${err.message}
867
+ `);
868
+ process.exit(1);
869
+ }
870
+ if (!dryRun) {
871
+ process.stdout.write(`Release ${resolvedVersion} published successfully.
872
+ `);
873
+ }
874
+ });
875
+ var gitCommand = new Command2("git").description("Git workflow commands (some require gh CLI)").addCommand(getPrInfoCmd).addCommand(getPrCommentsCmd).addCommand(finishFeatureCmd).addCommand(publishReleaseCmd);
876
+
877
+ // src/commands/getReady.ts
878
+ import { Command as Command3 } from "commander";
879
+ import { spawnSync as spawnSync4 } from "child_process";
880
+ import { existsSync } from "fs";
881
+ import { delimiter, join as join2 } from "path";
882
+
883
+ // src/config/githubService.ts
884
+ import { spawnSync as spawnSync3 } from "child_process";
885
+ function run3(cmd, args) {
886
+ const result = spawnSync3(cmd, args, { encoding: "utf8" });
887
+ if (result.error) {
888
+ const err = result.error;
889
+ if (err.code === "ENOENT") {
890
+ throw new Error("`gh` CLI is not installed or not on PATH.");
891
+ }
892
+ throw new Error(err.message);
893
+ }
894
+ return {
895
+ stdout: result.stdout ?? "",
896
+ stderr: result.stderr ?? "",
897
+ status: result.status ?? 1
898
+ };
899
+ }
900
+ function listIssues(technique, value) {
901
+ const baseArgs = [
902
+ "issue",
903
+ "list",
904
+ "--state",
905
+ "open",
906
+ "--sort",
907
+ "created",
908
+ "--order",
909
+ "asc",
910
+ "--limit",
911
+ "1",
912
+ "--json",
913
+ "number,title,body,url"
914
+ ];
915
+ let filterArgs;
916
+ switch (technique) {
917
+ case "label":
918
+ filterArgs = ["--label", value];
919
+ break;
920
+ case "assignee":
921
+ filterArgs = ["--assignee", value];
922
+ break;
923
+ case "title-contains":
924
+ filterArgs = ["--search", `${value} in:title`];
925
+ break;
926
+ }
927
+ const { stdout, stderr, status } = run3("gh", [...baseArgs, ...filterArgs]);
928
+ if (status !== 0) {
929
+ throw new Error(stderr.trim() || "Failed to query GitHub issues. Is `gh` installed and authenticated?");
930
+ }
931
+ const issues = JSON.parse(stdout);
932
+ if (issues.length === 0) {
933
+ return null;
934
+ }
935
+ return issues[0];
936
+ }
937
+ function postComment(issueNumber, body) {
938
+ const { stderr, status } = run3("gh", ["issue", "comment", String(issueNumber), "--body", body]);
939
+ if (status !== 0) {
940
+ throw new Error(stderr.trim() || `Failed to post comment on issue #${issueNumber}.`);
941
+ }
942
+ }
943
+
944
+ // src/commands/getReady.ts
945
+ function resolveCommand(name) {
946
+ const pathDirs = (process.env["PATH"] ?? "").split(delimiter);
947
+ for (const dir of pathDirs) {
948
+ const candidate = join2(dir, name);
949
+ if (existsSync(candidate)) return candidate;
950
+ }
951
+ return name;
952
+ }
953
+ function invokeClaudeCode(issue, systemPrompt) {
954
+ const prompt = systemPrompt ? `${systemPrompt}
955
+
956
+ ${issue.body}` : issue.body;
957
+ const claudeBin = resolveCommand("claude");
958
+ const result = spawnSync4(claudeBin, ["-p", prompt], { encoding: "utf8", stdio: "inherit" });
959
+ if (result.error) {
960
+ const err = result.error;
961
+ if (err.code === "ENOENT") {
962
+ process.stderr.write("Error: `claude` CLI is not installed or not on PATH.\n");
963
+ process.exit(1);
964
+ }
965
+ process.stderr.write(`Error: ${err.message}
966
+ `);
967
+ process.exit(1);
968
+ }
969
+ if (result.status !== 0) {
970
+ process.stderr.write(`Error: Claude Code exited with code ${result.status ?? "unknown"}.
971
+ `);
972
+ process.exit(result.status ?? 1);
973
+ }
974
+ }
975
+ var getReadyCommand = new Command3("get-ready").description("Find the next open GitHub issue matching the configured filter, claim it, and invoke Claude Code").option("--json", "Output issue details as JSON").option("--no-claude", "Skip Claude Code invocation after claiming the issue").action((options) => {
976
+ const config = readConfig();
977
+ if (config.remoteType !== "gh") {
978
+ process.stderr.write(
979
+ "Error: get-ready is not supported in Azure DevOps mode. Work item discovery is not available in azdo-cli. See docs/azdo-gap.md for details.\n"
980
+ );
981
+ process.exit(1);
982
+ }
983
+ if (!config.issueDiscoveryTechnique) {
984
+ process.stderr.write(
985
+ "Error: No issue discovery technique configured. Run `automata config` to set one.\n"
986
+ );
987
+ process.exit(1);
988
+ }
989
+ if (!config.issueDiscoveryValue) {
990
+ process.stderr.write(
991
+ "Error: No issue discovery value configured. Run `automata config` to set one.\n"
992
+ );
993
+ process.exit(1);
994
+ }
995
+ let issue;
996
+ try {
997
+ issue = listIssues(config.issueDiscoveryTechnique, config.issueDiscoveryValue);
998
+ } catch (err) {
999
+ process.stderr.write(`Error: ${err.message}
1000
+ `);
1001
+ process.exit(1);
1002
+ }
1003
+ if (issue === null) {
1004
+ process.stdout.write("No issues found matching the configured filter.\n");
1005
+ process.exit(0);
1006
+ }
1007
+ if (options.json) {
1008
+ process.stdout.write(JSON.stringify({ number: issue.number, title: issue.title, body: issue.body, url: issue.url }, null, 2) + "\n");
1009
+ } else {
1010
+ process.stdout.write(`Issue: #${issue.number}
1011
+ Title: ${issue.title}
1012
+ URL: ${issue.url}
1013
+
1014
+ ${issue.body}
1015
+ `);
1016
+ }
1017
+ try {
1018
+ postComment(issue.number, "working");
1019
+ } catch (err) {
1020
+ process.stderr.write(`Error: ${err.message}
1021
+ `);
1022
+ process.exit(1);
1023
+ }
1024
+ if (options.claude !== false) {
1025
+ invokeClaudeCode(issue, config.claudeSystemPrompt);
1026
+ }
1027
+ });
1028
+
101
1029
  // src/index.ts
102
- var program = new Command2();
1030
+ var program = new Command4();
103
1031
  program.name("automata").description("Automata CLI tool").version(version, "-v, --version");
104
1032
  program.addCommand(configCommand);
1033
+ program.addCommand(gitCommand);
1034
+ program.addCommand(getReadyCommand);
105
1035
  program.showHelpAfterError();
106
1036
  program.parse();
107
1037
  if (process.argv.length <= 2) {
package/package.json CHANGED
@@ -1,13 +1,14 @@
1
1
  {
2
2
  "name": "automata-cli",
3
- "version": "0.2.0-develop.7",
3
+ "version": "0.2.1",
4
4
  "description": "Automata CLI tool",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "automata": "./dist/index.js"
8
8
  },
9
9
  "files": [
10
- "dist"
10
+ "dist",
11
+ "README.md"
11
12
  ],
12
13
  "scripts": {
13
14
  "build": "tsup",