voratiq 0.1.0-beta.5 → 0.1.0-beta.7

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 (42) hide show
  1. package/README.md +11 -6
  2. package/dist/cli/apply.d.ts +1 -0
  3. package/dist/cli/apply.js +4 -1
  4. package/dist/cli/prune.d.ts +4 -3
  5. package/dist/cli/prune.js +29 -5
  6. package/dist/cli/run.d.ts +10 -0
  7. package/dist/cli/run.js +28 -3
  8. package/dist/commands/apply/command.d.ts +1 -0
  9. package/dist/commands/apply/command.js +72 -3
  10. package/dist/commands/apply/errors.d.ts +16 -0
  11. package/dist/commands/apply/errors.js +36 -0
  12. package/dist/commands/apply/types.d.ts +1 -0
  13. package/dist/commands/prune/command.d.ts +2 -1
  14. package/dist/commands/prune/command.js +58 -2
  15. package/dist/commands/prune/types.d.ts +21 -0
  16. package/dist/commands/run/agents/run-context.js +3 -20
  17. package/dist/commands/run/command.js +1 -1
  18. package/dist/commands/run/lifecycle.d.ts +2 -2
  19. package/dist/commands/run/lifecycle.js +2 -2
  20. package/dist/commands/spec/command.js +5 -1
  21. package/dist/commands/spec/preview.js +7 -1
  22. package/dist/configs/agents/defaults.js +1 -1
  23. package/dist/configs/environment/detect.js +9 -4
  24. package/dist/preflight/branch.d.ts +9 -0
  25. package/dist/preflight/branch.js +48 -0
  26. package/dist/preflight/errors.d.ts +3 -0
  27. package/dist/preflight/errors.js +7 -0
  28. package/dist/render/transcripts/apply.js +8 -2
  29. package/dist/render/transcripts/init.js +16 -14
  30. package/dist/render/transcripts/prune.d.ts +7 -1
  31. package/dist/render/transcripts/prune.js +58 -11
  32. package/dist/render/utils/transcript.d.ts +7 -1
  33. package/dist/render/utils/transcript.js +12 -2
  34. package/dist/reviews/records/types.d.ts +3 -7
  35. package/dist/reviews/records/types.js +2 -5
  36. package/dist/runs/records/types.d.ts +5 -14
  37. package/dist/runs/records/types.js +4 -20
  38. package/dist/specs/records/types.d.ts +3 -11
  39. package/dist/specs/records/types.js +2 -14
  40. package/dist/status/index.d.ts +58 -7
  41. package/dist/status/index.js +81 -4
  42. package/package.json +2 -2
package/README.md CHANGED
@@ -1,15 +1,15 @@
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
- ![`voratiq run --spec specs/p1/agent-workspace-guardrails.md`](https://raw.githubusercontent.com/voratiq/voratiq/main/assets/run-demo.png)
5
+ ![`voratiq run --spec .voratiq/specs/apply-commit-flag.md`](https://raw.githubusercontent.com/voratiq/voratiq/main/assets/run-demo.png)
6
6
 
7
7
  ## Installation
8
8
 
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
@@ -18,7 +18,7 @@ Core:
18
18
 
19
19
  - Node 20+
20
20
  - git
21
- - 1+ AI coding agent (Claude [(>=2.0.55)](https://github.com/anthropics/claude-code?tab=readme-ov-file#get-started), Codex [(>=0.66.0)](https://github.com/openai/codex?tab=readme-ov-file#quickstart), or Gemini [(>=0.19.4)](https://github.com/google-gemini/gemini-cli?tab=readme-ov-file#quick-install))
21
+ - 1+ AI coding agent (Claude [>=2.0.55](https://github.com/anthropics/claude-code?tab=readme-ov-file#get-started), Codex [>=0.66.0](https://github.com/openai/codex?tab=readme-ov-file#quickstart), or Gemini [>=0.19.4](https://github.com/google-gemini/gemini-cli?tab=readme-ov-file#quick-install))
22
22
 
23
23
  Platform-specific:
24
24
 
@@ -36,7 +36,10 @@ Note: Windows is not currently supported.
36
36
  voratiq init
37
37
 
38
38
  # Generate a spec
39
- voratiq spec --description "add dark mode toggle with localStorage persistence" --agent <agent-id> --yes
39
+ voratiq spec \
40
+ --description "add dark mode toggle with localStorage persistence" \
41
+ --agent <agent-id> \
42
+ --yes
40
43
 
41
44
  # Run agents in parallel
42
45
  voratiq run --spec .voratiq/specs/add-dark-mode-toggle.md
@@ -48,7 +51,9 @@ voratiq review --run <run-id> --agent <agent-id>
48
51
  voratiq apply --run <run-id> --agent <agent-id>
49
52
  ```
50
53
 
51
- See the [docs](https://github.com/voratiq/voratiq/blob/main/docs/index.md) for core concepts, CLI reference, and guides on configuring agents, evals, runtime environments, and sandbox restrictions.
54
+ For a full walkthrough, see the [CLI tutorial](https://github.com/voratiq/voratiq/blob/main/docs/tutorial.md).
55
+
56
+ See the [docs](https://github.com/voratiq/voratiq/blob/main/docs/index.md) for core concepts, CLI reference, and configuration guides.
52
57
 
53
58
  ## License
54
59
 
@@ -4,6 +4,7 @@ export interface ApplyCommandOptions {
4
4
  runId: string;
5
5
  agentId: string;
6
6
  ignoreBaseMismatch?: boolean;
7
+ commit?: boolean;
7
8
  }
8
9
  export interface ApplyCommandResult {
9
10
  result: ApplyResult;
package/dist/cli/apply.js CHANGED
@@ -4,7 +4,7 @@ import { ensureCleanWorkingTree, resolveCliContext, } from "../preflight/index.j
4
4
  import { renderApplyTranscript } from "../render/transcripts/apply.js";
5
5
  import { writeCommandOutput } from "./output.js";
6
6
  export async function runApplyCommand(options) {
7
- const { runId, agentId, ignoreBaseMismatch = false } = options;
7
+ const { runId, agentId, ignoreBaseMismatch = false, commit = false, } = options;
8
8
  const { root, workspacePaths } = await resolveCliContext();
9
9
  await ensureCleanWorkingTree(root);
10
10
  const result = await executeApplyCommand({
@@ -13,6 +13,7 @@ export async function runApplyCommand(options) {
13
13
  runId,
14
14
  agentId,
15
15
  ignoreBaseMismatch,
16
+ commit,
16
17
  });
17
18
  const body = renderApplyTranscript(result);
18
19
  return { result, body };
@@ -23,12 +24,14 @@ export function createApplyCommand() {
23
24
  .requiredOption("--run <run-id>", "Identifier of the recorded run")
24
25
  .requiredOption("--agent <agent-id>", "Agent id to apply from the run")
25
26
  .option("--ignore-base-mismatch", "Apply even if the current HEAD differs from the recorded base", () => true)
27
+ .option("--commit", "Commit the applied diff immediately using the agent summary", () => true)
26
28
  .allowExcessArguments(false)
27
29
  .action(async (options) => {
28
30
  const result = await runApplyCommand({
29
31
  runId: options.run,
30
32
  agentId: options.agent,
31
33
  ignoreBaseMismatch: options.ignoreBaseMismatch ?? false,
34
+ commit: options.commit ?? false,
32
35
  });
33
36
  writeCommandOutput({
34
37
  body: result.body,
@@ -1,12 +1,13 @@
1
1
  import { Command } from "commander";
2
- import type { PruneResult } from "../commands/prune/types.js";
2
+ import type { PruneAllResult, PruneResult } from "../commands/prune/types.js";
3
3
  export interface PruneCommandOptions {
4
- runId: string;
4
+ runId?: string;
5
+ all?: boolean;
5
6
  purge?: boolean;
6
7
  yes?: boolean;
7
8
  }
8
9
  export interface PruneCommandResult {
9
- result: PruneResult;
10
+ result: PruneResult | PruneAllResult;
10
11
  body: string;
11
12
  exitCode?: number;
12
13
  }
package/dist/cli/prune.js CHANGED
@@ -1,12 +1,13 @@
1
- import { Command } from "commander";
2
- import { executePruneCommand } from "../commands/prune/command.js";
1
+ import { Command, Option } from "commander";
2
+ import { executePruneAllCommand, executePruneCommand, } from "../commands/prune/command.js";
3
3
  import { resolveCliContext } from "../preflight/index.js";
4
- import { renderPruneTranscript } from "../render/transcripts/prune.js";
4
+ import { renderPruneAllTranscript, renderPruneTranscript, } from "../render/transcripts/prune.js";
5
5
  import { createConfirmationWorkflow } from "./confirmation.js";
6
6
  import { NonInteractiveShellError } from "./errors.js";
7
7
  import { writeCommandOutput } from "./output.js";
8
8
  export async function runPruneCommand(options) {
9
9
  const { runId } = options;
10
+ const all = Boolean(options.all);
10
11
  const purge = Boolean(options.purge);
11
12
  const assumeYes = Boolean(options.yes);
12
13
  const { root, workspacePaths } = await resolveCliContext();
@@ -17,6 +18,20 @@ export async function runPruneCommand(options) {
17
18
  },
18
19
  });
19
20
  try {
21
+ if (all) {
22
+ const result = await executePruneAllCommand({
23
+ root,
24
+ runsDir: workspacePaths.runsDir,
25
+ runsFilePath: workspacePaths.runsFile,
26
+ confirm: confirmation.confirm,
27
+ purge,
28
+ });
29
+ const body = renderPruneAllTranscript(result);
30
+ return { result, body };
31
+ }
32
+ if (!runId) {
33
+ throw new Error("Expected --run <run-id> when --all is not set.");
34
+ }
20
35
  const result = await executePruneCommand({
21
36
  root,
22
37
  runsDir: workspacePaths.runsDir,
@@ -35,14 +50,23 @@ export async function runPruneCommand(options) {
35
50
  export function createPruneCommand() {
36
51
  return new Command("prune")
37
52
  .description("Remove artifacts for a recorded run")
38
- .requiredOption("--run <run-id>", "Identifier of the run to delete")
53
+ .addOption(new Option("--run <run-id>", "Identifier of the run to delete").conflicts("all"))
54
+ .addOption(new Option("--all", "Prune all non-pruned runs").conflicts("run"))
39
55
  .option("--purge", "Delete all associated configs and artifacts")
40
56
  .option("-y, --yes", "Assume yes for all prompts")
41
57
  .addHelpText("after", "\nThis command removes agent workspaces, deletes agent branches, and marks runs as pruned. \nPass --purge to also delete all associated configs and artifacts.")
42
58
  .allowExcessArguments(false)
43
- .action(async (options) => {
59
+ .action(async (options, command) => {
60
+ const hasRun = typeof options.run === "string" && options.run.length > 0;
61
+ const wantsAll = Boolean(options.all);
62
+ if (!hasRun && !wantsAll) {
63
+ command.error("error: either --run <run-id> or --all must be provided", {
64
+ exitCode: 1,
65
+ });
66
+ }
44
67
  const result = await runPruneCommand({
45
68
  runId: options.run,
69
+ all: wantsAll,
46
70
  purge: Boolean(options.purge),
47
71
  yes: Boolean(options.yes),
48
72
  });
package/dist/cli/run.d.ts CHANGED
@@ -3,6 +3,7 @@ import type { RunReport } from "../runs/records/types.js";
3
3
  export interface RunCommandOptions {
4
4
  specPath: string;
5
5
  maxParallel?: number;
6
+ branch?: boolean;
6
7
  }
7
8
  export interface RunCommandResult {
8
9
  report: RunReport;
@@ -10,4 +11,13 @@ export interface RunCommandResult {
10
11
  exitCode?: number;
11
12
  }
12
13
  export declare function runRunCommand(options: RunCommandOptions): Promise<RunCommandResult>;
14
+ /**
15
+ * Derives a branch name from a spec file path by extracting the basename without extension.
16
+ *
17
+ * Examples:
18
+ * - `specs/separate-eval-outcomes.md` → `separate-eval-outcomes`
19
+ * - `specs/foo/bar.md` → `bar`
20
+ * - `my-feature.md` → `my-feature`
21
+ */
22
+ export declare function deriveBranchNameFromSpecPath(specPath: string): string;
13
23
  export declare function createRunCommand(): Command;
package/dist/cli/run.js CHANGED
@@ -1,17 +1,23 @@
1
+ import { basename } from "node:path";
1
2
  import { Command } from "commander";
2
3
  import { checkPlatformSupport } from "../agents/runtime/sandbox.js";
3
4
  import { executeRunCommand } from "../commands/run/command.js";
5
+ import { checkoutOrCreateBranch } from "../preflight/branch.js";
4
6
  import { ensureCleanWorkingTree, ensureSandboxDependencies, ensureSpecPath, resolveCliContext, } from "../preflight/index.js";
5
7
  import { createRunRenderer } from "../render/transcripts/run.js";
6
8
  import { parsePositiveInteger } from "../utils/validators.js";
7
9
  import { writeCommandOutput } from "./output.js";
8
10
  export async function runRunCommand(options) {
9
- const { specPath, maxParallel } = options;
11
+ const { specPath, maxParallel, branch } = options;
10
12
  const { root, workspacePaths } = await resolveCliContext();
11
13
  checkPlatformSupport();
12
14
  ensureSandboxDependencies();
13
15
  await ensureCleanWorkingTree(root);
14
16
  const { absolutePath, displayPath } = await ensureSpecPath(specPath, root);
17
+ if (branch) {
18
+ const branchName = deriveBranchNameFromSpecPath(displayPath);
19
+ await checkoutOrCreateBranch(root, branchName);
20
+ }
15
21
  const renderer = createRunRenderer();
16
22
  const report = await executeRunCommand({
17
23
  root,
@@ -23,11 +29,28 @@ export async function runRunCommand(options) {
23
29
  });
24
30
  const body = renderer.complete(report);
25
31
  // Unlike other commands, `run` signals a degraded outcome via exit code 1
26
- // when any agent or eval fails. All other CLI commands either throw on error
32
+ // when any agent fails. Eval failures are quality signals displayed in the output
33
+ // but do not affect exit code. All other CLI commands either throw on error
27
34
  // or return a clean success with exit code 0, so keep this deviation explicit.
28
- const exitCode = report.hadAgentFailure || report.hadEvalFailure ? 1 : undefined;
35
+ const exitCode = report.hadAgentFailure ? 1 : undefined;
29
36
  return { report, body, exitCode };
30
37
  }
38
+ /**
39
+ * Derives a branch name from a spec file path by extracting the basename without extension.
40
+ *
41
+ * Examples:
42
+ * - `specs/separate-eval-outcomes.md` → `separate-eval-outcomes`
43
+ * - `specs/foo/bar.md` → `bar`
44
+ * - `my-feature.md` → `my-feature`
45
+ */
46
+ export function deriveBranchNameFromSpecPath(specPath) {
47
+ const base = basename(specPath);
48
+ const lastDotIndex = base.lastIndexOf(".");
49
+ if (lastDotIndex <= 0) {
50
+ return base;
51
+ }
52
+ return base.slice(0, lastDotIndex);
53
+ }
31
54
  function parseMaxParallelOption(value) {
32
55
  return parsePositiveInteger(value, "Expected positive integer after --max-parallel", "--max-parallel must be greater than 0");
33
56
  }
@@ -36,11 +59,13 @@ export function createRunCommand() {
36
59
  .description("Execute configured agents against a spec")
37
60
  .requiredOption("--spec <path>", "Path to the specification to execute")
38
61
  .option("--max-parallel <count>", "Maximum number of agents to run concurrently", parseMaxParallelOption)
62
+ .option("--branch", "Checkout or create a branch named after the spec file")
39
63
  .allowExcessArguments(false)
40
64
  .action(async (options) => {
41
65
  const runOptions = {
42
66
  specPath: options.spec,
43
67
  maxParallel: options.maxParallel,
68
+ branch: options.branch,
44
69
  };
45
70
  const result = await runRunCommand(runOptions);
46
71
  writeCommandOutput({
@@ -5,5 +5,6 @@ export interface ApplyCommandInput {
5
5
  runId: string;
6
6
  agentId: string;
7
7
  ignoreBaseMismatch: boolean;
8
+ commit?: boolean;
8
9
  }
9
10
  export declare function executeApplyCommand(input: ApplyCommandInput): Promise<ApplyResult>;
@@ -1,3 +1,4 @@
1
+ import { readFile } from "node:fs/promises";
1
2
  import { buildRunRecordEnhanced } from "../../runs/records/enhanced.js";
2
3
  import { rewriteRunRecord } from "../../runs/records/persistence.js";
3
4
  import { toErrorMessage } from "../../utils/errors.js";
@@ -5,9 +6,9 @@ import { ensureFileExists } from "../../utils/fs.js";
5
6
  import { getGitStderr, getHeadRevision, runGitCommand, } from "../../utils/git.js";
6
7
  import { resolveDisplayPath } from "../../utils/path.js";
7
8
  import { fetchRunSafely } from "../fetch.js";
8
- import { ApplyAgentDiffMissingOnDiskError, ApplyAgentDiffNotRecordedError, ApplyAgentNotFoundError, ApplyBaseMismatchError, ApplyPatchApplicationError, ApplyRunDeletedError, } from "./errors.js";
9
+ import { ApplyAgentDiffMissingOnDiskError, ApplyAgentDiffNotRecordedError, ApplyAgentNotFoundError, ApplyAgentSummaryEmptyError, ApplyAgentSummaryMissingOnDiskError, ApplyAgentSummaryNotRecordedError, ApplyBaseMismatchError, ApplyGitCommitError, ApplyPatchApplicationError, ApplyRunDeletedError, } from "./errors.js";
9
10
  export async function executeApplyCommand(input) {
10
- const { root, runsFilePath, runId, agentId, ignoreBaseMismatch } = input;
11
+ const { root, runsFilePath, runId, agentId, ignoreBaseMismatch, commit = false, } = input;
11
12
  const runRecord = await fetchRunSafely({
12
13
  root,
13
14
  runsFilePath,
@@ -30,6 +31,8 @@ export async function executeApplyCommand(input) {
30
31
  }
31
32
  const diffAbsolutePath = resolveDisplayPath(root, diffDisplayPath) ?? diffDisplayPath;
32
33
  await ensureFileExists(diffAbsolutePath, () => new ApplyAgentDiffMissingOnDiskError(diffDisplayPath));
34
+ const summaryRecorded = agentRecord.artifacts?.summaryCaptured ?? false;
35
+ const summaryDisplayPath = enhancedAgent.assets.summaryPath;
33
36
  const headRevision = await getHeadRevision(root);
34
37
  const baseRevisionSha = runRecord.baseRevisionSha;
35
38
  const baseMismatch = headRevision !== baseRevisionSha;
@@ -63,6 +66,30 @@ export async function executeApplyCommand(input) {
63
66
  }
64
67
  throw error;
65
68
  }
69
+ let appliedCommitSha;
70
+ if (commit) {
71
+ try {
72
+ appliedCommitSha = await commitAppliedDiff({
73
+ root,
74
+ runId,
75
+ agentId,
76
+ summaryRecorded,
77
+ summaryDisplayPath,
78
+ });
79
+ }
80
+ catch (error) {
81
+ await recordApplyStatus({
82
+ root,
83
+ runsFilePath,
84
+ runId,
85
+ agentId,
86
+ ignoredBaseMismatch,
87
+ status: "failed",
88
+ detail: extractCommitFailureDetail(error),
89
+ });
90
+ throw error;
91
+ }
92
+ }
66
93
  await recordApplyStatus({
67
94
  root,
68
95
  runsFilePath,
@@ -70,6 +97,7 @@ export async function executeApplyCommand(input) {
70
97
  agentId,
71
98
  ignoredBaseMismatch,
72
99
  status: "succeeded",
100
+ appliedCommitSha,
73
101
  });
74
102
  return {
75
103
  runId: runRecord.runId,
@@ -81,6 +109,7 @@ export async function executeApplyCommand(input) {
81
109
  agent: agentRecord,
82
110
  diffPath: diffDisplayPath,
83
111
  ignoredBaseMismatch,
112
+ ...(appliedCommitSha ? { appliedCommitSha } : {}),
84
113
  };
85
114
  }
86
115
  async function applyPatch(options) {
@@ -96,7 +125,7 @@ async function applyPatch(options) {
96
125
  }
97
126
  }
98
127
  async function recordApplyStatus(options) {
99
- const { root, runsFilePath, runId, agentId, status, ignoredBaseMismatch, detail, } = options;
128
+ const { root, runsFilePath, runId, agentId, status, ignoredBaseMismatch, appliedCommitSha, detail, } = options;
100
129
  const appliedAt = new Date().toISOString();
101
130
  const normalizedDetail = typeof detail === "string" && detail.length > 0
102
131
  ? truncateDetail(detail)
@@ -115,6 +144,9 @@ async function recordApplyStatus(options) {
115
144
  if (normalizedDetail !== undefined) {
116
145
  applyStatus.detail = normalizedDetail;
117
146
  }
147
+ if (typeof appliedCommitSha === "string" && appliedCommitSha.length > 0) {
148
+ applyStatus.appliedCommitSha = appliedCommitSha;
149
+ }
118
150
  return {
119
151
  ...record,
120
152
  applyStatus,
@@ -129,7 +161,44 @@ function extractApplyFailureDetail(error) {
129
161
  }
130
162
  return error.message;
131
163
  }
164
+ function extractCommitFailureDetail(error) {
165
+ const stderr = getGitStderr(error);
166
+ if (stderr && stderr.trim().length > 0) {
167
+ const [firstLine] = stderr.split(/\r?\n/);
168
+ if (firstLine && firstLine.trim().length > 0) {
169
+ return firstLine.trim();
170
+ }
171
+ return stderr.trim();
172
+ }
173
+ return toErrorMessage(error);
174
+ }
132
175
  function truncateDetail(detail) {
133
176
  const trimmed = detail.trim();
134
177
  return trimmed.length > 256 ? trimmed.slice(0, 256) : trimmed;
135
178
  }
179
+ async function commitAppliedDiff(options) {
180
+ const { root, runId, agentId, summaryRecorded, summaryDisplayPath } = options;
181
+ if (!summaryRecorded || !summaryDisplayPath) {
182
+ throw new ApplyAgentSummaryNotRecordedError(runId, agentId);
183
+ }
184
+ const summaryAbsolutePath = resolveDisplayPath(root, summaryDisplayPath) ?? summaryDisplayPath;
185
+ await ensureFileExists(summaryAbsolutePath, () => new ApplyAgentSummaryMissingOnDiskError(summaryDisplayPath));
186
+ const rawSummary = await readFile(summaryAbsolutePath, "utf8");
187
+ const commitSubject = normalizeCommitSubject(rawSummary);
188
+ if (!commitSubject) {
189
+ throw new ApplyAgentSummaryEmptyError(summaryDisplayPath);
190
+ }
191
+ try {
192
+ await runGitCommand(["add", "-A"], { cwd: root });
193
+ await runGitCommand(["commit", "-m", commitSubject], { cwd: root });
194
+ return await runGitCommand(["rev-parse", "HEAD"], { cwd: root });
195
+ }
196
+ catch (error) {
197
+ const detail = getGitStderr(error) ?? toErrorMessage(error);
198
+ throw new ApplyGitCommitError(detail);
199
+ }
200
+ }
201
+ function normalizeCommitSubject(summary) {
202
+ const normalized = summary.trim().replace(/\s+/gu, " ");
203
+ return normalized.length > 0 ? normalized : "";
204
+ }
@@ -23,6 +23,22 @@ export declare class ApplyAgentDiffMissingOnDiskError extends ApplyError {
23
23
  readonly diffPath: string;
24
24
  constructor(diffPath: string);
25
25
  }
26
+ export declare class ApplyAgentSummaryNotRecordedError extends ApplyError {
27
+ readonly runId: string;
28
+ readonly agentId: string;
29
+ constructor(runId: string, agentId: string);
30
+ }
31
+ export declare class ApplyAgentSummaryMissingOnDiskError extends ApplyError {
32
+ readonly summaryPath: string;
33
+ constructor(summaryPath: string);
34
+ }
35
+ export declare class ApplyAgentSummaryEmptyError extends ApplyError {
36
+ readonly summaryPath: string;
37
+ constructor(summaryPath: string);
38
+ }
39
+ export declare class ApplyGitCommitError extends ApplyError {
40
+ constructor(detail: string);
41
+ }
26
42
  export interface ApplyBaseMismatchOptions {
27
43
  baseRevisionSha: string;
28
44
  headRevision: string;
@@ -53,6 +53,42 @@ export class ApplyAgentDiffMissingOnDiskError extends ApplyError {
53
53
  this.name = "ApplyAgentDiffMissingOnDiskError";
54
54
  }
55
55
  }
56
+ export class ApplyAgentSummaryNotRecordedError extends ApplyError {
57
+ runId;
58
+ agentId;
59
+ constructor(runId, agentId) {
60
+ super(`Agent ${agentId} did not record a summary for run ${runId}.`, ["A summary artifact is required for `voratiq apply --commit`."], [
61
+ "Re-run the spec to regenerate artifacts or apply without `--commit` and commit manually.",
62
+ ]);
63
+ this.runId = runId;
64
+ this.agentId = agentId;
65
+ this.name = "ApplyAgentSummaryNotRecordedError";
66
+ }
67
+ }
68
+ export class ApplyAgentSummaryMissingOnDiskError extends ApplyError {
69
+ summaryPath;
70
+ constructor(summaryPath) {
71
+ super("Recorded summary is missing from disk.", [`Expected summary at ${summaryPath} but it was not found.`], ["Ensure the run directory still exists or re-run the agents."]);
72
+ this.summaryPath = summaryPath;
73
+ this.name = "ApplyAgentSummaryMissingOnDiskError";
74
+ }
75
+ }
76
+ export class ApplyAgentSummaryEmptyError extends ApplyError {
77
+ summaryPath;
78
+ constructor(summaryPath) {
79
+ super("Recorded summary is empty.", [`Expected summary at ${summaryPath} to contain a commit subject.`], [
80
+ "Re-run the spec to regenerate artifacts or apply without `--commit` and commit manually.",
81
+ ]);
82
+ this.summaryPath = summaryPath;
83
+ this.name = "ApplyAgentSummaryEmptyError";
84
+ }
85
+ }
86
+ export class ApplyGitCommitError extends ApplyError {
87
+ constructor(detail) {
88
+ super("Failed to create git commit.", [detail], ["The diff remains applied; resolve the issue and commit manually."]);
89
+ this.name = "ApplyGitCommitError";
90
+ }
91
+ }
56
92
  export class ApplyBaseMismatchError extends ApplyError {
57
93
  constructor(options) {
58
94
  const { baseRevisionSha, headRevision } = options;
@@ -10,4 +10,5 @@ export interface ApplyResult {
10
10
  agent: AgentInvocationRecord;
11
11
  diffPath: string;
12
12
  ignoredBaseMismatch: boolean;
13
+ appliedCommitSha?: string;
13
14
  }
@@ -1,2 +1,3 @@
1
- import type { PruneCommandInput, PruneResult } from "./types.js";
1
+ import type { PruneAllCommandInput, PruneAllResult, PruneCommandInput, PruneResult } from "./types.js";
2
2
  export declare function executePruneCommand(input: PruneCommandInput): Promise<PruneResult>;
3
+ export declare function executePruneAllCommand(input: PruneAllCommandInput): Promise<PruneAllResult>;
@@ -1,8 +1,8 @@
1
1
  import * as fs from "node:fs/promises";
2
2
  import { NonInteractiveShellError } from "../../cli/errors.js";
3
- import { buildPruneConfirmationPreface } from "../../render/transcripts/prune.js";
3
+ import { buildPruneAllConfirmationPreface, buildPruneConfirmationPreface, } from "../../render/transcripts/prune.js";
4
4
  import { RunRecordNotFoundError, RunRecordParseError, } from "../../runs/records/errors.js";
5
- import { rewriteRunRecord, RUN_RECORD_FILENAME, } from "../../runs/records/persistence.js";
5
+ import { fetchRunsSafely, rewriteRunRecord, RUN_RECORD_FILENAME, } from "../../runs/records/persistence.js";
6
6
  import { toErrorMessage } from "../../utils/errors.js";
7
7
  import { pathExists } from "../../utils/fs.js";
8
8
  import { getGitStderr, runGitCommand } from "../../utils/git.js";
@@ -103,6 +103,52 @@ export async function executePruneCommand(input) {
103
103
  branches: branchSummary,
104
104
  };
105
105
  }
106
+ export async function executePruneAllCommand(input) {
107
+ const { root, runsFilePath, confirm, clock, purge: purgeInput } = input;
108
+ const purge = purgeInput ?? false;
109
+ if (!confirm) {
110
+ throw new NonInteractiveShellError();
111
+ }
112
+ if (!(await pathExists(runsFilePath))) {
113
+ return { status: "noop", runIds: [] };
114
+ }
115
+ const { records, warnings } = await fetchRunsSafely({
116
+ root,
117
+ runsFilePath,
118
+ });
119
+ const firstWarning = warnings[0];
120
+ if (firstWarning) {
121
+ if (firstWarning.kind === "parse-error") {
122
+ throw new RunRecordParseError(firstWarning.displayPath, firstWarning.details);
123
+ }
124
+ throw new RunMetadataMissingError(firstWarning.runId);
125
+ }
126
+ if (records.length === 0) {
127
+ return { status: "noop", runIds: [] };
128
+ }
129
+ const eligibleRuns = sortRunRecordsByCreatedAt(records);
130
+ const runIds = eligibleRuns.map((record) => record.runId);
131
+ const confirmationAccepted = await confirm({
132
+ message: "Proceed?",
133
+ defaultValue: false,
134
+ prefaceLines: buildPruneAllConfirmationPreface({
135
+ records: eligibleRuns,
136
+ }),
137
+ });
138
+ if (!confirmationAccepted) {
139
+ return { status: "aborted", runIds };
140
+ }
141
+ for (const runId of runIds) {
142
+ await executePruneCommand({
143
+ ...input,
144
+ runId,
145
+ confirm: () => Promise.resolve(true),
146
+ purge,
147
+ clock,
148
+ });
149
+ }
150
+ return { status: "pruned", runIds };
151
+ }
106
152
  function buildAgentDirectoryDisplayPaths(options) {
107
153
  const { runRecord } = options;
108
154
  const unique = new Set();
@@ -311,6 +357,16 @@ async function branchExists(root, branch) {
311
357
  });
312
358
  return output.length > 0;
313
359
  }
360
+ function sortRunRecordsByCreatedAt(records) {
361
+ return Array.from(records).sort((a, b) => {
362
+ const aTimestamp = Date.parse(a.createdAt);
363
+ const bTimestamp = Date.parse(b.createdAt);
364
+ if (Number.isNaN(aTimestamp) || Number.isNaN(bTimestamp)) {
365
+ return a.createdAt.localeCompare(b.createdAt);
366
+ }
367
+ return aTimestamp - bTimestamp;
368
+ });
369
+ }
314
370
  async function rewriteHistory(options) {
315
371
  const { root, runsFilePath, runId, deletedAt } = options;
316
372
  try {
@@ -9,6 +9,14 @@ export interface PruneCommandInput {
9
9
  purge?: boolean;
10
10
  clock?: () => Date;
11
11
  }
12
+ export interface PruneAllCommandInput {
13
+ root: string;
14
+ runsDir: string;
15
+ runsFilePath: string;
16
+ confirm: PruneConfirmationHandler;
17
+ purge?: boolean;
18
+ clock?: () => Date;
19
+ }
12
20
  export interface PruneBranchSummary {
13
21
  deleted: string[];
14
22
  skipped: string[];
@@ -40,3 +48,16 @@ export interface PruneAbortedResult {
40
48
  runPath: string;
41
49
  }
42
50
  export type PruneResult = PruneSuccessResult | PruneAbortedResult;
51
+ export interface PruneAllSuccessResult {
52
+ status: "pruned";
53
+ runIds: string[];
54
+ }
55
+ export interface PruneAllNoopResult {
56
+ status: "noop";
57
+ runIds: string[];
58
+ }
59
+ export interface PruneAllAbortedResult {
60
+ status: "aborted";
61
+ runIds: string[];
62
+ }
63
+ export type PruneAllResult = PruneAllSuccessResult | PruneAllNoopResult | PruneAllAbortedResult;
@@ -83,26 +83,9 @@ export class AgentRunContext {
83
83
  const evaluation = bySlug.get(fallback.slug);
84
84
  return evaluation ?? fallback;
85
85
  });
86
- if (this.status === "failed") {
87
- return;
88
- }
89
- const hasErrored = this.evalResults.some((evaluation) => {
90
- return evaluation.status === "errored";
91
- });
92
- const hasFailed = this.evalResults.some((evaluation) => evaluation.status === "failed");
93
- if (hasErrored) {
94
- this.status = "errored";
95
- if (!this.errorMessage) {
96
- const erroredEval = this.evalResults.find((evaluation) => evaluation.status === "errored" && evaluation.error);
97
- if (erroredEval?.error) {
98
- this.errorMessage = erroredEval.error;
99
- }
100
- }
101
- return;
102
- }
103
- if (hasFailed) {
104
- this.status = "failed";
105
- }
86
+ // Evals are quality signals, not execution outcomes.
87
+ // Agent status depends only on execution results (process exit code, etc.),
88
+ // not on eval failures. Eval results are tracked and exposed separately.
106
89
  }
107
90
  markChatArtifact(format) {
108
91
  this.artifactState.chatCaptured = true;
@@ -90,7 +90,7 @@ export async function executeRunCommand(input) {
90
90
  mutators,
91
91
  });
92
92
  agentRecords = executionResult.agentRecords;
93
- const derivedRunStatus = executionResult.hadAgentFailure || executionResult.hadEvalFailure
93
+ const derivedRunStatus = executionResult.hadAgentFailure
94
94
  ? "failed"
95
95
  : executionResult.agentReports.some((report) => report.status === "errored")
96
96
  ? "errored"
@@ -1,4 +1,5 @@
1
1
  import type { RunStatus } from "../../status/index.js";
2
+ import { TERMINABLE_RUN_STATUSES } from "../../status/index.js";
2
3
  export declare const RUN_ABORT_WARNING = "Run aborted before agent completed.";
3
4
  interface ActiveRunContext {
4
5
  root: string;
@@ -11,9 +12,8 @@ interface ActiveRunAgentContext {
11
12
  providerId?: string;
12
13
  agentRoot: string;
13
14
  }
14
- declare const TERMINABLE_STATUSES: readonly ["failed", "aborted"];
15
15
  export declare function registerActiveRun(context: ActiveRunContext): void;
16
16
  export declare function clearActiveRun(runId: string): void;
17
17
  export declare function getActiveTerminationStatus(runId: string): RunStatus | undefined;
18
- export declare function terminateActiveRun(status: Extract<RunStatus, (typeof TERMINABLE_STATUSES)[number]>): Promise<void>;
18
+ export declare function terminateActiveRun(status: Extract<RunStatus, (typeof TERMINABLE_RUN_STATUSES)[number]>): Promise<void>;
19
19
  export {};