automata-cli 0.1.0-feature-030-do-work.194 → 0.1.0-feature-030-do-work.198

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.
@@ -0,0 +1,478 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ DEFAULT_CHECK_ISSUE_PROMPT,
4
+ DEFAULT_DO_WORK,
5
+ DEFAULT_DO_WORK_ISSUE_DISCUSS_PROMPT,
6
+ DEFAULT_DO_WORK_PR_WORK_PROMPT,
7
+ DEFAULT_FIX_COMMENTS_PROMPT,
8
+ DEFAULT_SONAR_PROMPT,
9
+ readConfig,
10
+ readRawConfig,
11
+ writeConfig
12
+ } from "./chunk-UA32XYYL.js";
13
+
14
+ // src/config/ConfigWizard.tsx
15
+ import { useState } from "react";
16
+ import { Box, Text, useInput, useApp } from "ink";
17
+ import { writeFileSync, mkdirSync } from "fs";
18
+ import { join } from "path";
19
+ import { jsx, jsxs } from "react/jsx-runtime";
20
+ function writePromptFile(filename, content) {
21
+ const dir = join(process.cwd(), ".automata");
22
+ mkdirSync(dir, { recursive: true });
23
+ writeFileSync(join(dir, filename), content, "utf8");
24
+ }
25
+ var REMOTE_OPTIONS = [
26
+ { label: "GitHub", value: "gh" },
27
+ { label: "Azure DevOps", value: "azdo" }
28
+ ];
29
+ var TECHNIQUE_OPTIONS = [
30
+ { label: "By Label", value: "label" },
31
+ { label: "By Assignee", value: "assignee" },
32
+ { label: "By Title Contains", value: "title-contains" }
33
+ ];
34
+ var EXECUTOR_OPTIONS = [
35
+ { label: "Claude Code", value: "claude" },
36
+ { label: "Codex", value: "codex" }
37
+ ];
38
+ var MAIN_MENU_OPTIONS = ["Remote / Mode", "Implement-Next", "Prompts", "Issue Watch", "Do Work"];
39
+ var PROMPTS_MENU_OPTIONS = [
40
+ "Sonar",
41
+ "Fix-Comments",
42
+ "Check-Issue",
43
+ "Do Work \u2014 Discuss",
44
+ "Do Work \u2014 PR"
45
+ ];
46
+ function parseAllowedUsers(value) {
47
+ return value.split(",").map((user) => user.trim()).filter((user) => user.length > 0);
48
+ }
49
+ function handleTextEntry(input, key, screen, cancel) {
50
+ if (key.return) {
51
+ screen.onSubmit();
52
+ } else if (key.backspace || key.delete) {
53
+ screen.setValue((value) => value.slice(0, -1));
54
+ } else if (key.escape) {
55
+ screen.onBack();
56
+ } else if (key.ctrl && input === "c") {
57
+ cancel();
58
+ } else if (input && !key.ctrl && !key.meta) {
59
+ screen.setValue((value) => value + input);
60
+ }
61
+ }
62
+ function handleMenu(input, key, menu, cancel) {
63
+ if (key.upArrow) {
64
+ menu.setIndex((i) => i > 0 ? i - 1 : menu.length - 1);
65
+ } else if (key.downArrow) {
66
+ menu.setIndex((i) => i < menu.length - 1 ? i + 1 : 0);
67
+ } else if (key.return) {
68
+ menu.onSelect();
69
+ } else if (key.escape) {
70
+ if (menu.onBack) menu.onBack();
71
+ else cancel();
72
+ } else if (key.ctrl && input === "c") {
73
+ cancel();
74
+ }
75
+ }
76
+ function TextEntryScreen({ title, label, value, hint }) {
77
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginY: 1, children: [
78
+ /* @__PURE__ */ jsx(Text, { bold: true, children: title }),
79
+ /* @__PURE__ */ jsx(Text, { children: " " }),
80
+ /* @__PURE__ */ jsxs(Text, { children: [
81
+ label,
82
+ " ",
83
+ /* @__PURE__ */ jsxs(Text, { color: "cyan", children: [
84
+ value,
85
+ /* @__PURE__ */ jsx(Text, { children: "_" })
86
+ ] })
87
+ ] }),
88
+ /* @__PURE__ */ jsx(Text, { children: " " }),
89
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: hint })
90
+ ] });
91
+ }
92
+ function MenuEntryScreen({ title, options, index, hint }) {
93
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginY: 1, children: [
94
+ /* @__PURE__ */ jsx(Text, { bold: true, children: title }),
95
+ /* @__PURE__ */ jsx(Text, { children: " " }),
96
+ options.map((option, optionIndex) => /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Text, { color: optionIndex === index ? "cyan" : void 0, children: [
97
+ optionIndex === index ? "\u276F " : " ",
98
+ option
99
+ ] }) }, option)),
100
+ /* @__PURE__ */ jsx(Text, { children: " " }),
101
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: hint })
102
+ ] });
103
+ }
104
+ var BACK = "Esc to go back \xB7 Ctrl+C to cancel";
105
+ function savePrompt(filename, content, merge) {
106
+ let value;
107
+ if (content) {
108
+ writePromptFile(filename, content);
109
+ value = filename;
110
+ }
111
+ writeConfig(merge(value, readRawConfig()));
112
+ }
113
+ var PROMPT_SCREEN_BY_OPTION = {
114
+ Sonar: "sonar-prompt",
115
+ "Fix-Comments": "fix-comments-prompt",
116
+ "Check-Issue": "check-issue-prompt",
117
+ "Do Work \u2014 Discuss": "do-work-discuss-prompt",
118
+ "Do Work \u2014 PR": "do-work-pr-prompt"
119
+ };
120
+ function ConfigWizard() {
121
+ const existing = readConfig();
122
+ const rawExisting = readRawConfig();
123
+ const initialRemoteIndex = REMOTE_OPTIONS.findIndex((o) => o.value === existing.remoteType);
124
+ const initialTechIndex = TECHNIQUE_OPTIONS.findIndex((o) => o.value === existing.issueDiscoveryTechnique);
125
+ const [screen, setScreen] = useState("main");
126
+ const [mainMenuIndex, setMainMenuIndex] = useState(0);
127
+ const [selectedRemoteIndex, setSelectedRemoteIndex] = useState(initialRemoteIndex >= 0 ? initialRemoteIndex : 0);
128
+ const [selectedTechIndex, setSelectedTechIndex] = useState(initialTechIndex >= 0 ? initialTechIndex : 0);
129
+ const [discoveryValue, setDiscoveryValue] = useState(existing.issueDiscoveryValue ?? "");
130
+ const [systemPrompt, setSystemPrompt] = useState(existing.claudeSystemPrompt ?? "");
131
+ const [promptsMenuIndex, setPromptsMenuIndex] = useState(0);
132
+ const [sonarPrompt, setSonarPrompt] = useState(existing.prompts?.sonar ?? DEFAULT_SONAR_PROMPT);
133
+ const [fixCommentsPrompt, setFixCommentsPrompt] = useState(
134
+ existing.prompts?.fixComments ?? DEFAULT_FIX_COMMENTS_PROMPT
135
+ );
136
+ const [checkIssuePrompt, setCheckIssuePrompt] = useState(existing.prompts?.checkIssue ?? DEFAULT_CHECK_ISSUE_PROMPT);
137
+ const [allowedUsers, setAllowedUsers] = useState((existing.allowedUsers ?? []).join(", "));
138
+ const [agentUser, setAgentUser] = useState(existing.agentUser ?? "");
139
+ const [doWorkBaseBranch, setDoWorkBaseBranch] = useState(
140
+ existing.doWork?.baseBranch ?? DEFAULT_DO_WORK.baseBranch
141
+ );
142
+ const initialExecutorIndex = EXECUTOR_OPTIONS.findIndex((o) => o.value === existing.doWork?.executor);
143
+ const [doWorkExecutorIndex, setDoWorkExecutorIndex] = useState(Math.max(initialExecutorIndex, 0));
144
+ const [doWorkClaudeModel, setDoWorkClaudeModel] = useState(existing.doWork?.models?.claude ?? "");
145
+ const [doWorkCodexModel, setDoWorkCodexModel] = useState(existing.doWork?.models?.codex ?? "");
146
+ const [doWorkMaxRuns, setDoWorkMaxRuns] = useState(
147
+ String(existing.doWork?.maxRunsPerTick ?? DEFAULT_DO_WORK.maxRunsPerTick)
148
+ );
149
+ const [doWorkDiscussPrompt, setDoWorkDiscussPrompt] = useState(
150
+ existing.doWork?.prompts?.issueDiscuss ?? DEFAULT_DO_WORK_ISSUE_DISCUSS_PROMPT
151
+ );
152
+ const [doWorkPrPrompt, setDoWorkPrPrompt] = useState(
153
+ existing.doWork?.prompts?.prWork ?? DEFAULT_DO_WORK_PR_WORK_PROMPT
154
+ );
155
+ const [pendingRemote, setPendingRemote] = useState(existing.remoteType ?? "gh");
156
+ const [pendingTechnique, setPendingTechnique] = useState(
157
+ existing.issueDiscoveryTechnique ?? "label"
158
+ );
159
+ const { exit } = useApp();
160
+ const textScreens = {
161
+ value: {
162
+ setValue: setDiscoveryValue,
163
+ onSubmit: () => setScreen("system-prompt"),
164
+ onBack: () => setScreen("main")
165
+ },
166
+ "system-prompt": {
167
+ setValue: setSystemPrompt,
168
+ onSubmit: () => {
169
+ let claudeSystemPromptValue;
170
+ if (systemPrompt) {
171
+ writePromptFile("claude-system-prompt.md", systemPrompt);
172
+ claudeSystemPromptValue = "claude-system-prompt.md";
173
+ }
174
+ writeConfig({
175
+ ...rawExisting,
176
+ remoteType: pendingRemote,
177
+ issueDiscoveryTechnique: pendingTechnique,
178
+ issueDiscoveryValue: discoveryValue || void 0,
179
+ claudeSystemPrompt: claudeSystemPromptValue
180
+ });
181
+ exit();
182
+ },
183
+ onBack: () => setScreen("main")
184
+ },
185
+ "sonar-prompt": {
186
+ setValue: setSonarPrompt,
187
+ onSubmit: () => {
188
+ savePrompt("sonar-prompt.md", sonarPrompt, (value, current) => ({
189
+ ...current,
190
+ prompts: { ...current.prompts, sonar: value }
191
+ }));
192
+ setScreen("prompts-menu");
193
+ },
194
+ onBack: () => setScreen("prompts-menu")
195
+ },
196
+ "fix-comments-prompt": {
197
+ setValue: setFixCommentsPrompt,
198
+ onSubmit: () => {
199
+ savePrompt("fix-comments-prompt.md", fixCommentsPrompt, (value, current) => ({
200
+ ...current,
201
+ prompts: { ...current.prompts, fixComments: value }
202
+ }));
203
+ setScreen("prompts-menu");
204
+ },
205
+ onBack: () => setScreen("prompts-menu")
206
+ },
207
+ "check-issue-prompt": {
208
+ setValue: setCheckIssuePrompt,
209
+ onSubmit: () => {
210
+ savePrompt("check-issue-prompt.md", checkIssuePrompt, (value, current) => ({
211
+ ...current,
212
+ prompts: { ...current.prompts, checkIssue: value }
213
+ }));
214
+ setScreen("prompts-menu");
215
+ },
216
+ onBack: () => setScreen("prompts-menu")
217
+ },
218
+ "allowed-users": {
219
+ setValue: setAllowedUsers,
220
+ onSubmit: () => setScreen("agent-user"),
221
+ onBack: () => setScreen("main")
222
+ },
223
+ "agent-user": {
224
+ setValue: setAgentUser,
225
+ onSubmit: () => {
226
+ const parsedUsers = parseAllowedUsers(allowedUsers);
227
+ const current = readRawConfig();
228
+ writeConfig({
229
+ ...current,
230
+ allowedUsers: parsedUsers.length > 0 ? parsedUsers : void 0,
231
+ agentUser: agentUser.trim() || void 0
232
+ });
233
+ exit();
234
+ },
235
+ onBack: () => setScreen("allowed-users")
236
+ },
237
+ "do-work-base-branch": {
238
+ setValue: setDoWorkBaseBranch,
239
+ onSubmit: () => setScreen("do-work-executor"),
240
+ onBack: () => setScreen("main")
241
+ },
242
+ "do-work-claude-model": {
243
+ setValue: setDoWorkClaudeModel,
244
+ onSubmit: () => setScreen("do-work-codex-model"),
245
+ onBack: () => setScreen("do-work-executor")
246
+ },
247
+ "do-work-codex-model": {
248
+ setValue: setDoWorkCodexModel,
249
+ onSubmit: () => setScreen("do-work-max-runs"),
250
+ onBack: () => setScreen("do-work-claude-model")
251
+ },
252
+ "do-work-max-runs": {
253
+ setValue: setDoWorkMaxRuns,
254
+ onSubmit: () => {
255
+ const parsedMaxRuns = Number.parseInt(doWorkMaxRuns, 10);
256
+ const current = readRawConfig();
257
+ writeConfig({
258
+ ...current,
259
+ doWork: {
260
+ ...current.doWork,
261
+ baseBranch: doWorkBaseBranch.trim() || void 0,
262
+ executor: EXECUTOR_OPTIONS[doWorkExecutorIndex].value,
263
+ models: {
264
+ claude: doWorkClaudeModel.trim() || void 0,
265
+ codex: doWorkCodexModel.trim() || void 0
266
+ },
267
+ maxRunsPerTick: Number.isNaN(parsedMaxRuns) || parsedMaxRuns < 0 ? void 0 : parsedMaxRuns
268
+ }
269
+ });
270
+ exit();
271
+ },
272
+ onBack: () => setScreen("do-work-codex-model")
273
+ },
274
+ "do-work-discuss-prompt": {
275
+ setValue: setDoWorkDiscussPrompt,
276
+ onSubmit: () => {
277
+ savePrompt("do-work-issue-discuss.md", doWorkDiscussPrompt, (value, current) => ({
278
+ ...current,
279
+ doWork: { ...current.doWork, prompts: { ...current.doWork?.prompts, issueDiscuss: value } }
280
+ }));
281
+ setScreen("prompts-menu");
282
+ },
283
+ onBack: () => setScreen("prompts-menu")
284
+ },
285
+ "do-work-pr-prompt": {
286
+ setValue: setDoWorkPrPrompt,
287
+ onSubmit: () => {
288
+ savePrompt("do-work-pr-work.md", doWorkPrPrompt, (value, current) => ({
289
+ ...current,
290
+ doWork: { ...current.doWork, prompts: { ...current.doWork?.prompts, prWork: value } }
291
+ }));
292
+ setScreen("prompts-menu");
293
+ },
294
+ onBack: () => setScreen("prompts-menu")
295
+ }
296
+ };
297
+ const menus = {
298
+ main: {
299
+ index: mainMenuIndex,
300
+ length: MAIN_MENU_OPTIONS.length,
301
+ setIndex: setMainMenuIndex,
302
+ onSelect: () => {
303
+ const chosen = MAIN_MENU_OPTIONS[mainMenuIndex];
304
+ if (chosen === "Remote / Mode") setScreen("remote");
305
+ else if (chosen === "Implement-Next") setScreen("technique");
306
+ else if (chosen === "Issue Watch") setScreen("allowed-users");
307
+ else if (chosen === "Do Work") setScreen("do-work-base-branch");
308
+ else setScreen("prompts-menu");
309
+ }
310
+ },
311
+ remote: {
312
+ index: selectedRemoteIndex,
313
+ length: REMOTE_OPTIONS.length,
314
+ setIndex: setSelectedRemoteIndex,
315
+ onSelect: () => {
316
+ setPendingRemote(REMOTE_OPTIONS[selectedRemoteIndex].value);
317
+ setScreen("technique");
318
+ },
319
+ onBack: () => setScreen("main")
320
+ },
321
+ technique: {
322
+ index: selectedTechIndex,
323
+ length: TECHNIQUE_OPTIONS.length,
324
+ setIndex: setSelectedTechIndex,
325
+ onSelect: () => {
326
+ setPendingTechnique(TECHNIQUE_OPTIONS[selectedTechIndex].value);
327
+ setScreen("value");
328
+ },
329
+ onBack: () => setScreen("main")
330
+ },
331
+ "prompts-menu": {
332
+ index: promptsMenuIndex,
333
+ length: PROMPTS_MENU_OPTIONS.length,
334
+ setIndex: setPromptsMenuIndex,
335
+ onSelect: () => setScreen(PROMPT_SCREEN_BY_OPTION[PROMPTS_MENU_OPTIONS[promptsMenuIndex]]),
336
+ onBack: () => setScreen("main")
337
+ },
338
+ "do-work-executor": {
339
+ index: doWorkExecutorIndex,
340
+ length: EXECUTOR_OPTIONS.length,
341
+ setIndex: setDoWorkExecutorIndex,
342
+ onSelect: () => setScreen("do-work-claude-model"),
343
+ onBack: () => setScreen("do-work-base-branch")
344
+ }
345
+ };
346
+ useInput((input, key) => {
347
+ const textScreen = textScreens[screen];
348
+ if (textScreen) {
349
+ handleTextEntry(input, key, textScreen, exit);
350
+ return;
351
+ }
352
+ const menu = menus[screen];
353
+ if (menu) {
354
+ handleMenu(input, key, menu, exit);
355
+ }
356
+ });
357
+ const techLabel = TECHNIQUE_OPTIONS.find((o) => o.value === pendingTechnique)?.label ?? pendingTechnique;
358
+ const textViews = {
359
+ value: {
360
+ title: "Implement-Next \u2014 Issue Discovery Value",
361
+ label: `${techLabel} value:`,
362
+ value: discoveryValue,
363
+ hint: `Type value \xB7 Enter to continue \xB7 ${BACK}`
364
+ },
365
+ "system-prompt": {
366
+ title: "Implement-Next \u2014 Claude System Prompt",
367
+ label: "System prompt (optional):",
368
+ value: systemPrompt,
369
+ hint: `Type prompt \xB7 Enter to save and exit \xB7 ${BACK}`
370
+ },
371
+ "sonar-prompt": {
372
+ title: "Prompts \u2014 Sonar",
373
+ label: "Sonar prompt:",
374
+ value: sonarPrompt,
375
+ hint: `Type prompt \xB7 Enter to save \xB7 ${BACK}`
376
+ },
377
+ "fix-comments-prompt": {
378
+ title: "Prompts \u2014 Fix-Comments",
379
+ label: "Fix-Comments prompt:",
380
+ value: fixCommentsPrompt,
381
+ hint: `Type prompt \xB7 Enter to save \xB7 ${BACK}`
382
+ },
383
+ "check-issue-prompt": {
384
+ title: "Prompts \u2014 Check-Issue",
385
+ label: "Check-Issue prompt:",
386
+ value: checkIssuePrompt,
387
+ hint: `Type prompt \xB7 Enter to save \xB7 ${BACK}`
388
+ },
389
+ "allowed-users": {
390
+ title: "Issue Watch \u2014 Allowed Users",
391
+ label: "Logins allowed to instruct the agent (comma separated):",
392
+ value: allowedUsers,
393
+ hint: `Type logins \xB7 Enter to continue \xB7 ${BACK}`
394
+ },
395
+ "agent-user": {
396
+ title: "Issue Watch \u2014 Agent User",
397
+ label: "Login the agent posts as:",
398
+ value: agentUser,
399
+ hint: `Type login \xB7 Enter to save and exit \xB7 ${BACK}`
400
+ },
401
+ "do-work-base-branch": {
402
+ title: "Do Work \u2014 Base Branch",
403
+ label: "Branch discussion turns return to:",
404
+ value: doWorkBaseBranch,
405
+ hint: `Type branch \xB7 Enter to continue \xB7 ${BACK}`
406
+ },
407
+ "do-work-claude-model": {
408
+ title: "Do Work \u2014 Claude Model",
409
+ label: "Default model when the executor is Claude (blank = the executor's own default):",
410
+ value: doWorkClaudeModel,
411
+ hint: `Type model \xB7 Enter to continue \xB7 ${BACK}`
412
+ },
413
+ "do-work-codex-model": {
414
+ title: "Do Work \u2014 Codex Model",
415
+ label: "Default model when the executor is Codex (blank = the executor's own default):",
416
+ value: doWorkCodexModel,
417
+ hint: `Type model \xB7 Enter to continue \xB7 ${BACK}`
418
+ },
419
+ "do-work-max-runs": {
420
+ title: "Do Work \u2014 Max Runs Per Tick",
421
+ label: "Model runs allowed per tick (0 = unlimited):",
422
+ value: doWorkMaxRuns,
423
+ hint: `Type a number \xB7 Enter to save \xB7 ${BACK}`
424
+ },
425
+ "do-work-discuss-prompt": {
426
+ title: "Prompts \u2014 Do Work \u2014 Discuss",
427
+ label: "Discussion turn instructions:",
428
+ value: doWorkDiscussPrompt,
429
+ hint: `Type prompt \xB7 Enter to save \xB7 ${BACK}`
430
+ },
431
+ "do-work-pr-prompt": {
432
+ title: "Prompts \u2014 Do Work \u2014 PR",
433
+ label: "Pull request turn instructions:",
434
+ value: doWorkPrPrompt,
435
+ hint: `Type prompt \xB7 Enter to save \xB7 ${BACK}`
436
+ }
437
+ };
438
+ const menuViews = {
439
+ main: {
440
+ title: "Configure Automata",
441
+ options: MAIN_MENU_OPTIONS,
442
+ index: mainMenuIndex,
443
+ hint: "\u2191/\u2193 to move \xB7 Enter to select \xB7 Ctrl+C to cancel"
444
+ },
445
+ remote: {
446
+ title: "Remote / Mode",
447
+ options: REMOTE_OPTIONS.map((o) => o.label),
448
+ index: selectedRemoteIndex,
449
+ hint: `\u2191/\u2193 to move \xB7 Enter to confirm \xB7 ${BACK}`
450
+ },
451
+ technique: {
452
+ title: "Implement-Next \u2014 Issue Discovery Technique",
453
+ options: TECHNIQUE_OPTIONS.map((o) => o.label),
454
+ index: selectedTechIndex,
455
+ hint: `\u2191/\u2193 to move \xB7 Enter to confirm \xB7 ${BACK}`
456
+ },
457
+ "prompts-menu": {
458
+ title: "Prompts",
459
+ options: PROMPTS_MENU_OPTIONS,
460
+ index: promptsMenuIndex,
461
+ hint: `\u2191/\u2193 to move \xB7 Enter to edit \xB7 ${BACK}`
462
+ },
463
+ "do-work-executor": {
464
+ title: "Do Work \u2014 Executor",
465
+ options: EXECUTOR_OPTIONS.map((o) => o.label),
466
+ index: doWorkExecutorIndex,
467
+ hint: `\u2191/\u2193 to move \xB7 Enter to continue \xB7 ${BACK}`
468
+ }
469
+ };
470
+ const textView = textViews[screen];
471
+ if (textView) return /* @__PURE__ */ jsx(TextEntryScreen, { ...textView });
472
+ const menuView = menuViews[screen];
473
+ if (menuView) return /* @__PURE__ */ jsx(MenuEntryScreen, { ...menuView });
474
+ return null;
475
+ }
476
+ export {
477
+ ConfigWizard
478
+ };
package/dist/index.js CHANGED
@@ -183,7 +183,7 @@ var configCommand = new Command("config").description("Configure automata settin
183
183
  const [{ render }, React, { ConfigWizard }] = await Promise.all([
184
184
  import("ink"),
185
185
  import("react"),
186
- import("./ConfigWizard-GN5JC4XP.js")
186
+ import("./ConfigWizard-NTK7NLAE.js")
187
187
  ]);
188
188
  const { waitUntilExit } = render(React.createElement(ConfigWizard));
189
189
  await waitUntilExit();
@@ -1483,9 +1483,11 @@ import { existsSync } from "fs";
1483
1483
  import { delimiter, join } from "path";
1484
1484
 
1485
1485
  // src/cli/spawnUtils.ts
1486
+ var SHELL_SAFE = /^[A-Za-z0-9_@%+=:,./-]+$/;
1487
+ var ESCAPED_QUOTE = String.raw`'\''`;
1486
1488
  function shellQuote(arg) {
1487
- if (/^[A-Za-z0-9_@%+=:,./-]+$/.test(arg)) return arg;
1488
- return `'${arg.replaceAll("'", `'\\''`)}'`;
1489
+ if (SHELL_SAFE.test(arg)) return arg;
1490
+ return "'" + arg.replaceAll("'", ESCAPED_QUOTE) + "'";
1489
1491
  }
1490
1492
  function truncate(str, max) {
1491
1493
  return str.length > max ? str.slice(0, max) + "..." : str;
@@ -2353,6 +2355,31 @@ query($owner:String!,$repo:String!,$cursor:String){
2353
2355
  }
2354
2356
  }`.trim();
2355
2357
  var MAX_LINK_MAP_PAGES = 50;
2358
+ function indexPullRequest(map, node) {
2359
+ const ref = {
2360
+ number: node.number,
2361
+ url: node.url,
2362
+ title: node.title,
2363
+ headRefName: node.headRefName,
2364
+ state: "OPEN",
2365
+ isDraft: node.isDraft,
2366
+ updatedAt: node.updatedAt
2367
+ };
2368
+ if (node.closingIssuesReferences.pageInfo?.hasNextPage) {
2369
+ process.stderr.write(
2370
+ `Warning: pull request #${String(node.number)} closes more than 50 issues; some links were not read.
2371
+ `
2372
+ );
2373
+ }
2374
+ for (const issue of node.closingIssuesReferences.nodes) {
2375
+ const existing = map.get(issue.number);
2376
+ if (existing) {
2377
+ existing.push(ref);
2378
+ } else {
2379
+ map.set(issue.number, [ref]);
2380
+ }
2381
+ }
2382
+ }
2356
2383
  function getOpenPrLinkMap() {
2357
2384
  const { owner, repo } = getRepoSlug();
2358
2385
  const map = /* @__PURE__ */ new Map();
@@ -2363,29 +2390,7 @@ function getOpenPrLinkMap() {
2363
2390
  const response = ghJson(args, "query open pull requests");
2364
2391
  const connection = response.data.repository.pullRequests;
2365
2392
  for (const node of connection.nodes) {
2366
- const ref = {
2367
- number: node.number,
2368
- url: node.url,
2369
- title: node.title,
2370
- headRefName: node.headRefName,
2371
- state: "OPEN",
2372
- isDraft: node.isDraft,
2373
- updatedAt: node.updatedAt
2374
- };
2375
- if (node.closingIssuesReferences.pageInfo?.hasNextPage) {
2376
- process.stderr.write(
2377
- `Warning: pull request #${String(node.number)} closes more than 50 issues; some links were not read.
2378
- `
2379
- );
2380
- }
2381
- for (const issue of node.closingIssuesReferences.nodes) {
2382
- const existing = map.get(issue.number);
2383
- if (existing) {
2384
- existing.push(ref);
2385
- } else {
2386
- map.set(issue.number, [ref]);
2387
- }
2388
- }
2393
+ indexPullRequest(map, node);
2389
2394
  }
2390
2395
  if (!connection.pageInfo?.hasNextPage || connection.pageInfo.endCursor === null) {
2391
2396
  return map;
@@ -2979,17 +2984,21 @@ function planRun(item, settings, silent) {
2979
2984
  }
2980
2985
  function describePlannedRun(item, settings, run5) {
2981
2986
  const rule = "\u2500".repeat(72);
2987
+ const branchAction = item.turn === "pr-work" ? " and fast-forward" : " and pull";
2988
+ const assignment = item.needsAssignment ? `would assign to ${settings.participants.agentUser}` : "already assigned";
2989
+ const markerTarget = item.turn === "pr-work" && item.pr ? `pull request #${String(item.pr.number)}` : `issue #${String(item.issue.number)}`;
2990
+ const modelNote = settings.model === void 0 ? " (no model override)" : ` \xB7 model ${settings.model}`;
2982
2991
  const lines = [
2983
2992
  rule,
2984
2993
  `Issue #${String(item.issue.number)} \u2014 ${item.issue.title}`,
2985
2994
  rule,
2986
2995
  ` Turn ${item.turn}`,
2987
2996
  ` Why ${item.reason}`,
2988
- ` Branch ${item.branch} (would check out${item.turn === "pr-work" ? " and fast-forward" : " and pull"})`,
2989
- ` Assign ${item.needsAssignment ? `would assign to ${settings.participants.agentUser}` : "already assigned"}`,
2990
- ` Marker would post on ${item.turn === "pr-work" && item.pr ? `pull request #${String(item.pr.number)}` : `issue #${String(item.issue.number)}`}`,
2991
- ` Executor ${settings.executor}${settings.model === void 0 ? " (no model override)" : ` \xB7 model ${settings.model}`}`,
2992
- ` Permissions bypassed (do-work always runs unattended)`,
2997
+ ` Branch ${item.branch} (would check out${branchAction})`,
2998
+ ` Assign ${assignment}`,
2999
+ ` Marker would post on ${markerTarget}`,
3000
+ ` Executor ${settings.executor}${modelNote}`,
3001
+ " Permissions bypassed (do-work always runs unattended)",
2993
3002
  ` Prompt ${String(run5.prompt.length)} chars \u2014 frame + assembled context`,
2994
3003
  "",
2995
3004
  " Command that would be launched:",
@@ -3238,40 +3247,7 @@ ${describePlan(decisions)}`;
3238
3247
  out(planText);
3239
3248
  }
3240
3249
  if (options.dryRun) {
3241
- const runnableInPlan = settings.maxRuns > 0 ? items.slice(0, settings.maxRuns) : items;
3242
- const planned = runnableInPlan.map((item) => planRun(item, settings, options.silent === true));
3243
- if (options.json) {
3244
- out(
3245
- JSON.stringify(
3246
- {
3247
- dryRun: true,
3248
- plan: decisions.map(toPlanJson),
3249
- runs: planned.map((run5, index) => ({
3250
- issue: runnableInPlan[index].issue.number,
3251
- turn: runnableInPlan[index].turn,
3252
- executor: settings.executor,
3253
- model: settings.model ?? null,
3254
- bin: run5.bin,
3255
- args: run5.args,
3256
- command: run5.command,
3257
- prompt: run5.prompt
3258
- }))
3259
- },
3260
- null,
3261
- 2
3262
- ) + "\n"
3263
- );
3264
- return 0;
3265
- }
3266
- for (const [index, run5] of planned.entries()) {
3267
- out("\n" + describePlannedRun(runnableInPlan[index], settings, run5));
3268
- }
3269
- if (items.length > runnableInPlan.length) {
3270
- out(`
3271
- (${String(items.length - runnableInPlan.length)} further item(s) deferred by the run cap.)
3272
- `);
3273
- }
3274
- out("\nDry run: nothing was assigned, posted, checked out or executed.\n");
3250
+ reportDryRun(items, decisions, settings, options);
3275
3251
  return 0;
3276
3252
  }
3277
3253
  const runnable = settings.maxRuns > 0 ? items.slice(0, settings.maxRuns) : items;
@@ -3301,6 +3277,43 @@ ${describePlan(decisions)}`;
3301
3277
  }
3302
3278
  return exitCode;
3303
3279
  }
3280
+ function reportDryRun(items, decisions, settings, options) {
3281
+ const describable = settings.maxRuns > 0 ? items.slice(0, settings.maxRuns) : items;
3282
+ const planned = describable.map((item) => planRun(item, settings, options.silent === true));
3283
+ if (options.json) {
3284
+ out(
3285
+ JSON.stringify(
3286
+ {
3287
+ dryRun: true,
3288
+ plan: decisions.map(toPlanJson),
3289
+ runs: planned.map((run5, index) => ({
3290
+ issue: describable[index].issue.number,
3291
+ turn: describable[index].turn,
3292
+ executor: settings.executor,
3293
+ model: settings.model ?? null,
3294
+ bin: run5.bin,
3295
+ args: run5.args,
3296
+ command: run5.command,
3297
+ prompt: run5.prompt
3298
+ }))
3299
+ },
3300
+ null,
3301
+ 2
3302
+ ) + "\n"
3303
+ );
3304
+ return;
3305
+ }
3306
+ for (const [index, run5] of planned.entries()) {
3307
+ out("\n" + describePlannedRun(describable[index], settings, run5));
3308
+ }
3309
+ const deferred = items.length - describable.length;
3310
+ if (deferred > 0) {
3311
+ out(`
3312
+ (${String(deferred)} further item(s) deferred by the run cap.)
3313
+ `);
3314
+ }
3315
+ out("\nDry run: nothing was assigned, posted, checked out or executed.\n");
3316
+ }
3304
3317
  function toPlanJson(decision) {
3305
3318
  if (decision.kind === "skip") {
3306
3319
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "automata-cli",
3
- "version": "0.1.0-feature-030-do-work.194",
3
+ "version": "0.1.0-feature-030-do-work.198",
4
4
  "description": "Automata CLI tool",
5
5
  "type": "module",
6
6
  "bin": {