voratiq 0.1.0-beta.6 → 0.1.0-beta.8

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 (54) hide show
  1. package/README.md +27 -2
  2. package/dist/agents/runtime/auth.d.ts +5 -1
  3. package/dist/agents/runtime/auth.js +31 -7
  4. package/dist/agents/runtime/failures.js +78 -4
  5. package/dist/agents/runtime/launcher.js +1 -2
  6. package/dist/agents/runtime/watchdog.js +23 -2
  7. package/dist/auth/providers/codex.js +0 -1
  8. package/dist/bin.js +2 -0
  9. package/dist/cli/auto.d.ts +23 -0
  10. package/dist/cli/auto.js +225 -0
  11. package/dist/cli/output.d.ts +14 -0
  12. package/dist/cli/output.js +150 -2
  13. package/dist/cli/review.d.ts +3 -0
  14. package/dist/cli/review.js +3 -2
  15. package/dist/cli/run.d.ts +9 -0
  16. package/dist/cli/run.js +8 -3
  17. package/dist/cli/spec.d.ts +4 -0
  18. package/dist/cli/spec.js +7 -4
  19. package/dist/commands/apply/command.js +1 -0
  20. package/dist/commands/run/agents/lifecycle.js +23 -8
  21. package/dist/commands/run/agents/preparation.js +2 -2
  22. package/dist/commands/run/errors.d.ts +9 -0
  23. package/dist/commands/run/errors.js +43 -0
  24. package/dist/commands/run/prompt.d.ts +5 -0
  25. package/dist/commands/run/{prompts.js → prompt.js} +1 -1
  26. package/dist/commands/run/validation.js +14 -5
  27. package/dist/commands/spec/command.js +7 -3
  28. package/dist/commands/spec/preview.js +7 -1
  29. package/dist/commands/spec/prompt.d.ts +1 -1
  30. package/dist/commands/spec/prompt.js +1 -1
  31. package/dist/configs/agents/defaults.js +1 -1
  32. package/dist/configs/agents/loader.d.ts +11 -1
  33. package/dist/configs/agents/loader.js +67 -0
  34. package/dist/configs/environment/detect.js +9 -4
  35. package/dist/render/transcripts/auto.d.ts +24 -0
  36. package/dist/render/transcripts/auto.js +17 -0
  37. package/dist/render/transcripts/init.js +16 -14
  38. package/dist/render/transcripts/prune.js +34 -14
  39. package/dist/render/transcripts/review.d.ts +1 -0
  40. package/dist/render/transcripts/review.js +6 -6
  41. package/dist/render/transcripts/run.d.ts +5 -1
  42. package/dist/render/transcripts/run.js +24 -7
  43. package/dist/render/transcripts/spec.d.ts +3 -1
  44. package/dist/render/transcripts/spec.js +7 -4
  45. package/dist/render/utils/transcript.d.ts +7 -1
  46. package/dist/render/utils/transcript.js +12 -2
  47. package/dist/runs/records/persistence.d.ts +6 -0
  48. package/dist/runs/records/persistence.js +51 -2
  49. package/dist/sessions/persistence.d.ts +8 -0
  50. package/dist/sessions/persistence.js +33 -5
  51. package/dist/utils/output.d.ts +5 -1
  52. package/dist/utils/output.js +4 -2
  53. package/package.json +3 -3
  54. package/dist/commands/run/prompts.d.ts +0 -5
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Voratiq
2
2
 
3
- Run agents in parallel, compare results, and apply the best solution.
3
+ Run coding agents against each other. Merge the winner.
4
4
 
5
5
  ![`voratiq run --spec .voratiq/specs/apply-commit-flag.md`](https://raw.githubusercontent.com/voratiq/voratiq/main/assets/run-demo.png)
6
6
 
@@ -9,7 +9,7 @@ Run agents in parallel, compare results, and apply the best solution.
9
9
  Voratiq is in public beta. Install via npm:
10
10
 
11
11
  ```bash
12
- npm install -g voratiq@beta
12
+ npm install -g voratiq
13
13
  ```
14
14
 
15
15
  ### Requirements
@@ -55,6 +55,31 @@ For a full walkthrough, see the [CLI tutorial](https://github.com/voratiq/vorati
55
55
 
56
56
  See the [docs](https://github.com/voratiq/voratiq/blob/main/docs/index.md) for core concepts, CLI reference, and configuration guides.
57
57
 
58
+ ## How It Works
59
+
60
+ Voratiq takes your task spec, fans it out to multiple [sandboxed](https://github.com/voratiq/voratiq/blob/main/docs/configs/sandbox.md) [agents](https://github.com/voratiq/voratiq/blob/main/docs/configs/agents.md), and runs automatic [evals](https://github.com/voratiq/voratiq/blob/main/docs/configs/evals.md) (tests, lint, custom checks) on each result. You pick the best solution and merge it into your codebase.
61
+
62
+ ```mermaid
63
+ flowchart TD
64
+ Spec["Same spec goes to all agents"]
65
+
66
+ subgraph Run[" "]
67
+ direction LR
68
+ A["Agent 1"]
69
+ B["..."]
70
+ C["Agent N"]
71
+ end
72
+
73
+ Evals["Evals run automatically"]
74
+ Pick["User picks the best implementation"]
75
+ Apply["Winning diff merged to codebase"]
76
+
77
+ Spec --> A & B & C
78
+ A & B & C --> Evals
79
+ Evals --> Pick
80
+ Pick --> Apply
81
+ ```
82
+
58
83
  ## License
59
84
 
60
85
  Voratiq is available under the [MIT License](https://github.com/voratiq/voratiq/blob/main/LICENSE).
@@ -17,6 +17,10 @@ export interface StageAuthResult {
17
17
  env: Record<string, string>;
18
18
  context: StagedAuthContext;
19
19
  }
20
- export declare function verifyAgentProviders(agents: readonly AgentDefinition[]): Promise<void>;
20
+ export interface AgentProviderPreflightIssue {
21
+ readonly agentId: string;
22
+ readonly message: string;
23
+ }
24
+ export declare function verifyAgentProviders(agents: readonly Pick<AgentDefinition, "id" | "provider">[]): Promise<readonly AgentProviderPreflightIssue[]>;
21
25
  export declare function stageAgentAuth(options: StageAuthOptions): Promise<StageAuthResult>;
22
26
  export declare function teardownAuthContext(context: StagedAuthContext | undefined): Promise<void>;
@@ -3,29 +3,40 @@ import { resolveAuthProvider } from "../../auth/providers/index.js";
3
3
  import { buildAuthRuntimeContext } from "../../auth/runtime.js";
4
4
  import { toErrorMessage } from "../../utils/errors.js";
5
5
  import { isFileSystemError } from "../../utils/fs.js";
6
- import { AuthProviderStageError, AuthProviderVerificationError, MissingAgentProviderError, UnknownAuthProviderError, } from "./errors.js";
6
+ import { AuthProviderStageError, MissingAgentProviderError, UnknownAuthProviderError, } from "./errors.js";
7
7
  import { getRunCommand } from "./launcher.js";
8
8
  import { checkPlatformSupport } from "./sandbox.js";
9
9
  export async function verifyAgentProviders(agents) {
10
10
  if (agents.length === 0) {
11
- return;
11
+ return [];
12
12
  }
13
13
  // Ensure platform and runtime dependencies are present.
14
14
  checkPlatformSupport();
15
15
  await getRunCommand();
16
16
  const runtime = buildAuthRuntimeContext();
17
+ const issues = [];
17
18
  for (const agent of agents) {
18
- const provider = resolveAgentProvider(agent);
19
- try {
20
- await provider.verify({
19
+ const providerId = agent.provider?.trim();
20
+ if (!providerId) {
21
+ issues.push({ agentId: agent.id, message: "missing provider" });
22
+ continue;
23
+ }
24
+ const provider = resolveAuthProvider(providerId);
25
+ if (!provider) {
26
+ issues.push({
21
27
  agentId: agent.id,
22
- runtime,
28
+ message: `unknown auth provider "${providerId}"`,
23
29
  });
30
+ continue;
31
+ }
32
+ try {
33
+ await provider.verify({ agentId: agent.id, runtime });
24
34
  }
25
35
  catch (error) {
26
- throw new AuthProviderVerificationError(extractAuthProviderMessage(error));
36
+ pushIssueLines(issues, agent.id, extractAuthProviderMessage(error));
27
37
  }
28
38
  }
39
+ return issues;
29
40
  }
30
41
  export async function stageAgentAuth(options) {
31
42
  const { agent, agentRoot, runId, root } = options;
@@ -105,3 +116,16 @@ function extractAuthProviderMessage(error) {
105
116
  }
106
117
  return toErrorMessage(error);
107
118
  }
119
+ function pushIssueLines(issues, agentId, message) {
120
+ const lines = message
121
+ .split(/\r?\n/u)
122
+ .map((line) => line.trim())
123
+ .filter((line) => line.length > 0);
124
+ if (lines.length === 0) {
125
+ issues.push({ agentId, message: "unknown error" });
126
+ return;
127
+ }
128
+ for (const line of lines) {
129
+ issues.push({ agentId, message: line });
130
+ }
131
+ }
@@ -1,20 +1,94 @@
1
1
  import { readFile } from "node:fs/promises";
2
2
  import { CLAUDE_OAUTH_RELOGIN_HINT, CLAUDE_PROVIDER_ID, } from "../../auth/providers/claude/constants.js";
3
+ const GEMINI_PROVIDER_ID = "gemini";
4
+ const CODEX_PROVIDER_ID = "codex";
3
5
  const CLAUDE_FAILURE_PATTERNS = [
4
6
  /Please run \/login/i,
5
7
  /OAuth token has expired/i,
6
8
  ];
9
+ const JSON_MESSAGE_PATTERN = /"message"\s*:\s*"((?:\\.|[^"\\])*)"/;
7
10
  export async function detectAgentProcessFailureDetail(input) {
8
- if (input.provider !== CLAUDE_PROVIDER_ID) {
11
+ if (input.provider !== CLAUDE_PROVIDER_ID &&
12
+ input.provider !== GEMINI_PROVIDER_ID &&
13
+ input.provider !== CODEX_PROVIDER_ID) {
9
14
  return undefined;
10
15
  }
11
16
  const combinedLogs = await readCombinedLogs(input.stdoutPath, input.stderrPath);
12
- if (combinedLogs &&
13
- CLAUDE_FAILURE_PATTERNS.some((pattern) => pattern.test(combinedLogs))) {
14
- return CLAUDE_OAUTH_RELOGIN_HINT;
17
+ if (!combinedLogs) {
18
+ return undefined;
19
+ }
20
+ if (input.provider === CLAUDE_PROVIDER_ID) {
21
+ if (CLAUDE_FAILURE_PATTERNS.some((pattern) => pattern.test(combinedLogs))) {
22
+ return CLAUDE_OAUTH_RELOGIN_HINT;
23
+ }
24
+ return undefined;
25
+ }
26
+ if (input.provider === GEMINI_PROVIDER_ID) {
27
+ return (extractFirstJsonMessage(combinedLogs) ??
28
+ extractGeminiFallbackLine(combinedLogs));
29
+ }
30
+ if (input.provider === CODEX_PROVIDER_ID) {
31
+ return (extractFirstJsonMessage(combinedLogs) ??
32
+ findFirstMatchingLine(combinedLogs, [
33
+ /invalid_request_error/,
34
+ /unsupported_value/,
35
+ /thread .* panicked/i,
36
+ ]));
37
+ }
38
+ return undefined;
39
+ }
40
+ function extractFirstJsonMessage(text) {
41
+ const match = JSON_MESSAGE_PATTERN.exec(text);
42
+ if (!match) {
43
+ return undefined;
44
+ }
45
+ const raw = match[1]?.trim();
46
+ if (!raw) {
47
+ return undefined;
48
+ }
49
+ try {
50
+ const parsed = JSON.parse(`"${raw}"`);
51
+ return isMeaningfulMessage(parsed) ? parsed : undefined;
52
+ }
53
+ catch {
54
+ return isMeaningfulMessage(raw) ? raw : undefined;
55
+ }
56
+ }
57
+ function isMeaningfulMessage(message) {
58
+ const normalized = message.trim();
59
+ if (!normalized) {
60
+ return false;
61
+ }
62
+ if (normalized === "[object Object]") {
63
+ return false;
64
+ }
65
+ return true;
66
+ }
67
+ function findFirstMatchingLine(text, matchers) {
68
+ const lines = text.split(/\r?\n/);
69
+ for (const line of lines) {
70
+ const trimmed = line.trim();
71
+ if (!trimmed)
72
+ continue;
73
+ for (const matcher of matchers) {
74
+ const match = matcher.exec(trimmed);
75
+ if (match) {
76
+ return trimmed.slice(match.index).trim();
77
+ }
78
+ }
15
79
  }
16
80
  return undefined;
17
81
  }
82
+ function extractGeminiFallbackLine(text) {
83
+ return findFirstMatchingLine(text, [
84
+ /TerminalQuotaError:/,
85
+ /PERMISSION_DENIED/,
86
+ /RESOURCE_EXHAUSTED/,
87
+ /No capacity available/i,
88
+ /You have exhausted your capacity/i,
89
+ /exhausted your capacity/i,
90
+ ]);
91
+ }
18
92
  async function readCombinedLogs(stdoutPath, stderrPath) {
19
93
  const [stdout, stderr] = await Promise.all([
20
94
  safeRead(stdoutPath),
@@ -7,7 +7,6 @@ import { spawnStreamingProcess } from "../../utils/process.js";
7
7
  import { AgentRuntimeProcessError } from "./errors.js";
8
8
  import { generateSandboxSettings, resolveSrtBinary, writeSandboxSettings, } from "./sandbox.js";
9
9
  import { createWatchdog, WATCHDOG_DEFAULTS, } from "./watchdog.js";
10
- const DEFAULT_SRT_ARGUMENTS = ["--debug"];
11
10
  const SRT_BINARY_ENV = "VORATIQ_SRT_BINARY";
12
11
  let cachedSrtBinaryPath;
13
12
  export async function configureSandboxSettings(input) {
@@ -32,7 +31,6 @@ export async function getRunCommand() {
32
31
  function getRunArgs(options) {
33
32
  const { settingsArg, configArg, shimEntryPath } = options;
34
33
  return [
35
- ...DEFAULT_SRT_ARGUMENTS,
36
34
  "--settings",
37
35
  settingsArg,
38
36
  "--",
@@ -84,6 +82,7 @@ export async function runAgentProcess(options) {
84
82
  command,
85
83
  args,
86
84
  cwd: agentRoot,
85
+ env: { SRT_DEBUG: process.env.SRT_DEBUG ?? "1" },
87
86
  stdout: { writable: stdoutStream },
88
87
  stderr: { writable: stderrStream },
89
88
  detached: true,
@@ -8,8 +8,29 @@ export const WATCHDOG_DEFAULTS = {
8
8
  hardAbortMs: 10 * 1000,
9
9
  };
10
10
  export const FATAL_PATTERNS = new Map([
11
- ["gemini", [/You have exhausted your capacity on this model\./i]],
12
- ["codex", [/Connection failed: error sending request for url\./i]],
11
+ [
12
+ "gemini",
13
+ [
14
+ /PERMISSION_DENIED/i,
15
+ /RESOURCE_EXHAUSTED/i,
16
+ /MODEL_CAPACITY_EXHAUSTED/i,
17
+ /No capacity available for model/i,
18
+ ],
19
+ ],
20
+ [
21
+ "codex",
22
+ [/invalid_request_error/i, /unsupported_value/i, /thread .* panicked/i],
23
+ ],
24
+ [
25
+ "claude",
26
+ [
27
+ /OAuth token revoked/i,
28
+ /OAuth token has expired/i,
29
+ /Please run \/login/i,
30
+ /invalid.*api.*key/i,
31
+ /insufficient_quota/i,
32
+ ],
33
+ ],
13
34
  ]);
14
35
  export function createWatchdog(child, stderrStream, options) {
15
36
  const { silenceTimeoutMs, wallClockCapMs, killGraceMs, hardAbortMs } = WATCHDOG_DEFAULTS;
@@ -51,7 +51,6 @@ export const codexAuthProvider = {
51
51
  registerSandboxSecrets(sandboxPaths.home, secretHandles);
52
52
  const envResult = composeSandboxEnvResult(sandboxPaths.home, {
53
53
  CODEX_HOME: sandboxPaths.codex,
54
- RUST_BACKTRACE: "1",
55
54
  });
56
55
  return envResult;
57
56
  },
package/dist/bin.js CHANGED
@@ -5,6 +5,7 @@ import process from "node:process";
5
5
  import { fileURLToPath } from "node:url";
6
6
  import { Command, CommanderError } from "commander";
7
7
  import { createApplyCommand } from "./cli/apply.js";
8
+ import { createAutoCommand } from "./cli/auto.js";
8
9
  import { commanderAlreadyRendered } from "./cli/commander-utils.js";
9
10
  import { CliError, toCliError } from "./cli/errors.js";
10
11
  import { createInitCommand } from "./cli/init.js";
@@ -104,6 +105,7 @@ export async function runCli(argv = process.argv) {
104
105
  program.addCommand(createSpecCommand());
105
106
  program.addCommand(createRunCommand());
106
107
  program.addCommand(createReviewCommand());
108
+ program.addCommand(createAutoCommand());
107
109
  program.addCommand(createApplyCommand());
108
110
  program.addCommand(createPruneCommand());
109
111
  if (argv.length <= 2) {
@@ -0,0 +1,23 @@
1
+ import { Command } from "commander";
2
+ export interface AutoCommandOptions {
3
+ description?: string;
4
+ specPath?: string;
5
+ specAgent?: string;
6
+ title?: string;
7
+ output?: string;
8
+ reviewerAgent: string;
9
+ maxParallel?: number;
10
+ branch?: boolean;
11
+ }
12
+ export interface AutoCommandResult {
13
+ exitCode: number;
14
+ specOutputPath?: string;
15
+ runId?: string;
16
+ reviewOutputPath?: string;
17
+ }
18
+ interface AutoRuntimeOptions {
19
+ now?: () => number;
20
+ }
21
+ export declare function runAutoCommand(options: AutoCommandOptions, runtime?: AutoRuntimeOptions): Promise<AutoCommandResult>;
22
+ export declare function createAutoCommand(): Command;
23
+ export {};
@@ -0,0 +1,225 @@
1
+ import { Command, Option } from "commander";
2
+ import { renderAutoSummaryTranscript } from "../render/transcripts/auto.js";
3
+ import { renderCliError } from "../render/utils/errors.js";
4
+ import { parsePositiveInteger } from "../utils/validators.js";
5
+ import { toCliError } from "./errors.js";
6
+ import { beginChainedCommandOutput, writeCommandOutput } from "./output.js";
7
+ import { runReviewCommand } from "./review.js";
8
+ import { runRunCommand } from "./run.js";
9
+ import { runSpecCommand } from "./spec.js";
10
+ function parseMaxParallelOption(value) {
11
+ return parsePositiveInteger(value, "Expected positive integer after --max-parallel", "--max-parallel must be greater than 0");
12
+ }
13
+ export async function runAutoCommand(options, runtime = {}) {
14
+ const now = runtime.now ?? Date.now.bind(Date);
15
+ const overallStart = now();
16
+ const chainedOutput = beginChainedCommandOutput();
17
+ try {
18
+ let exitCode = 0;
19
+ let specStartedAt;
20
+ let specStatus = "skipped";
21
+ let specOutputPath;
22
+ let specDetail;
23
+ let runStartedAt;
24
+ let runStatus = "skipped";
25
+ let runId;
26
+ let runDetail;
27
+ let runRecordStatus;
28
+ let runCreatedAt;
29
+ let runSpecPath;
30
+ let runBaseRevisionSha;
31
+ let reviewStartedAt;
32
+ let reviewStatus = "skipped";
33
+ let reviewOutputPath;
34
+ let reviewDetail;
35
+ if (options.description) {
36
+ specStartedAt = now();
37
+ try {
38
+ if (!options.specAgent) {
39
+ throw new Error("Expected --spec-agent when --description is provided.");
40
+ }
41
+ const specResult = await runSpecCommand({
42
+ description: options.description,
43
+ agent: options.specAgent,
44
+ title: options.title,
45
+ output: options.output,
46
+ yes: true,
47
+ suppressHint: true,
48
+ });
49
+ specStatus = "succeeded";
50
+ specOutputPath = specResult.outputPath;
51
+ writeCommandOutput({ body: specResult.body });
52
+ }
53
+ catch (error) {
54
+ specStatus = "failed";
55
+ specDetail = toCliError(error).headline;
56
+ exitCode = 1;
57
+ writeCommandOutput({ body: renderCliError(toCliError(error)) });
58
+ }
59
+ }
60
+ else if (options.specPath) {
61
+ specStatus = "skipped";
62
+ specOutputPath = options.specPath;
63
+ }
64
+ else {
65
+ specStatus = "failed";
66
+ specDetail = "Either --description or --spec must be provided.";
67
+ exitCode = 1;
68
+ writeCommandOutput({
69
+ body: renderCliError(toCliError(new Error(specDetail))),
70
+ });
71
+ }
72
+ if (exitCode === 0 && specOutputPath) {
73
+ runStartedAt = now();
74
+ try {
75
+ // For non-TTY, suppress run renderer blank lines and let the chained
76
+ // output system handle spacing. For TTY, let the run renderer handle
77
+ // its own spacing since cursor control requires precise line counts.
78
+ const suppressBlankLines = !process.stdout.isTTY;
79
+ const runResult = await runRunCommand({
80
+ specPath: specOutputPath,
81
+ maxParallel: options.maxParallel,
82
+ branch: options.branch,
83
+ suppressHint: true,
84
+ suppressLeadingBlankLine: suppressBlankLines,
85
+ suppressTrailingBlankLine: suppressBlankLines,
86
+ stdout: chainedOutput.stdout,
87
+ stderr: chainedOutput.stderr,
88
+ });
89
+ runStatus = "succeeded";
90
+ runId = runResult.report.runId;
91
+ runRecordStatus = runResult.report.status;
92
+ runCreatedAt = runResult.report.createdAt;
93
+ runSpecPath = runResult.report.spec?.path;
94
+ runBaseRevisionSha = runResult.report.baseRevisionSha;
95
+ if (runResult.exitCode === 1) {
96
+ exitCode = 1;
97
+ }
98
+ writeCommandOutput({ body: runResult.body });
99
+ }
100
+ catch (error) {
101
+ runStatus = "failed";
102
+ runDetail = toCliError(error).headline;
103
+ exitCode = 1;
104
+ writeCommandOutput({ body: renderCliError(toCliError(error)) });
105
+ }
106
+ }
107
+ if (exitCode === 0 || runId) {
108
+ if (!runId) {
109
+ reviewStatus = "skipped";
110
+ }
111
+ else {
112
+ reviewStartedAt = now();
113
+ try {
114
+ const reviewResult = await runReviewCommand({
115
+ runId,
116
+ agentId: options.reviewerAgent,
117
+ suppressHint: true,
118
+ });
119
+ reviewStatus = "succeeded";
120
+ reviewOutputPath = reviewResult.outputPath;
121
+ writeCommandOutput({
122
+ body: reviewResult.body,
123
+ stderr: reviewResult.stderr,
124
+ });
125
+ }
126
+ catch (error) {
127
+ reviewStatus = "failed";
128
+ reviewDetail = toCliError(error).headline;
129
+ exitCode = 1;
130
+ writeCommandOutput({ body: renderCliError(toCliError(error)) });
131
+ }
132
+ }
133
+ }
134
+ const overallDurationMs = now() - overallStart;
135
+ const specDurationMs = specStartedAt !== undefined ? now() - specStartedAt : undefined;
136
+ const runDurationMs = runStartedAt !== undefined ? now() - runStartedAt : undefined;
137
+ const reviewDurationMs = reviewStartedAt !== undefined ? now() - reviewStartedAt : undefined;
138
+ const summaryBody = renderAutoSummaryTranscript({
139
+ totalDurationMs: overallDurationMs,
140
+ spec: {
141
+ status: specStatus,
142
+ ...(typeof specDurationMs === "number"
143
+ ? { durationMs: specDurationMs }
144
+ : {}),
145
+ outputPath: specOutputPath,
146
+ ...(specDetail ? { detail: specDetail } : {}),
147
+ },
148
+ run: {
149
+ status: runStatus,
150
+ ...(typeof runDurationMs === "number"
151
+ ? { durationMs: runDurationMs }
152
+ : {}),
153
+ ...(runId ? { runId } : {}),
154
+ ...(runRecordStatus ? { runStatus: runRecordStatus } : {}),
155
+ ...(runCreatedAt ? { createdAt: runCreatedAt } : {}),
156
+ ...(runSpecPath ? { specPath: runSpecPath } : {}),
157
+ ...(runBaseRevisionSha ? { baseRevisionSha: runBaseRevisionSha } : {}),
158
+ ...(runDetail ? { detail: runDetail } : {}),
159
+ },
160
+ review: {
161
+ status: reviewStatus,
162
+ ...(typeof reviewDurationMs === "number"
163
+ ? { durationMs: reviewDurationMs }
164
+ : {}),
165
+ ...(reviewOutputPath ? { outputPath: reviewOutputPath } : {}),
166
+ ...(reviewDetail ? { detail: reviewDetail } : {}),
167
+ },
168
+ });
169
+ writeCommandOutput({
170
+ body: summaryBody,
171
+ exitCode,
172
+ });
173
+ return {
174
+ exitCode,
175
+ specOutputPath,
176
+ runId,
177
+ reviewOutputPath,
178
+ };
179
+ }
180
+ finally {
181
+ chainedOutput.end();
182
+ }
183
+ }
184
+ export function createAutoCommand() {
185
+ return new Command("auto")
186
+ .description("Generate a spec, run agents, and review results in one command")
187
+ .addOption(new Option("--description <text>", "Generate a spec from a description, then run and review").conflicts("spec"))
188
+ .addOption(new Option("--spec <path>", "Use an existing spec path, then run and review").conflicts("description"))
189
+ .requiredOption("--review-agent <agent-id>", "Reviewer agent identifier")
190
+ .option("--spec-agent <agent-id>", "Spec generator agent identifier")
191
+ .option("--title <text>", "Optional spec title (description mode only)")
192
+ .option("--output <path>", "Optional spec output path within .voratiq/specs/ (description mode only)")
193
+ .option("--max-parallel <count>", "Maximum number of agents to run concurrently", parseMaxParallelOption)
194
+ .option("--branch", "Checkout or create a branch named after the spec file")
195
+ .allowExcessArguments(false)
196
+ .action(async (options, command) => {
197
+ const hasDescription = typeof options.description === "string" &&
198
+ options.description.length > 0;
199
+ const hasSpec = typeof options.spec === "string" && options.spec.length > 0;
200
+ if (!hasDescription && !hasSpec) {
201
+ command.error("error: either --description <text> or --spec <path> must be provided", {
202
+ exitCode: 1,
203
+ });
204
+ }
205
+ if (hasDescription &&
206
+ (!options.specAgent || options.specAgent.length === 0)) {
207
+ command.error("error: --spec-agent <agent-id> is required with --description", {
208
+ exitCode: 1,
209
+ });
210
+ }
211
+ if (hasSpec && (options.specAgent || options.title || options.output)) {
212
+ command.error("error: --spec-agent/--title/--output are only valid with --description", { exitCode: 1 });
213
+ }
214
+ await runAutoCommand({
215
+ description: options.description,
216
+ specPath: options.spec,
217
+ specAgent: options.specAgent,
218
+ title: options.title,
219
+ output: options.output,
220
+ reviewerAgent: options.reviewAgent,
221
+ maxParallel: options.maxParallel,
222
+ branch: options.branch,
223
+ });
224
+ });
225
+ }
@@ -1,4 +1,8 @@
1
+ import { type FormatCliOutputOptions } from "../utils/output.js";
1
2
  export declare function writeCommandPreface(preface: string): void;
3
+ type CliWriter = Pick<NodeJS.WriteStream, "write"> & {
4
+ isTTY?: boolean;
5
+ };
2
6
  export type AlertSeverity = "info" | "warn" | "error";
3
7
  export interface Alert {
4
8
  readonly severity: AlertSeverity;
@@ -9,6 +13,16 @@ export interface CommandOutputPayload {
9
13
  readonly alerts?: readonly Alert[];
10
14
  readonly stderr?: string | readonly string[];
11
15
  readonly exitCode?: number;
16
+ readonly formatBody?: FormatCliOutputOptions;
17
+ readonly leadingNewline?: boolean;
12
18
  }
19
+ export type CommandOutputWriter = (payload: CommandOutputPayload) => void;
20
+ export interface ChainedCommandOutput {
21
+ stdout: CliWriter;
22
+ stderr: CliWriter;
23
+ end(): void;
24
+ }
25
+ export declare function beginChainedCommandOutput(): ChainedCommandOutput;
13
26
  export declare function writeCommandOutput(payload: CommandOutputPayload): void;
14
27
  export declare function renderCommandOutput(payload: CommandOutputPayload): void;
28
+ export {};