voratiq 0.1.0-beta.1 → 0.1.0-beta.2

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.
@@ -2,4 +2,7 @@ import { HintedError } from "../utils/errors.js";
2
2
  export declare class CliError extends HintedError {
3
3
  constructor(headline: string, detailLines?: readonly string[], hintLines?: readonly string[]);
4
4
  }
5
+ export declare class NonInteractiveShellError extends CliError {
6
+ constructor();
7
+ }
5
8
  export declare function toCliError(error: unknown): CliError;
@@ -5,6 +5,12 @@ export class CliError extends HintedError {
5
5
  this.name = "CliError";
6
6
  }
7
7
  }
8
+ export class NonInteractiveShellError extends CliError {
9
+ constructor() {
10
+ super("Non-interactive shell detected; re-run with --yes to accept defaults.");
11
+ this.name = "NonInteractiveShellError";
12
+ }
13
+ }
8
14
  export function toCliError(error) {
9
15
  if (error instanceof CliError) {
10
16
  return error;
package/dist/cli/init.js CHANGED
@@ -3,6 +3,7 @@ import { executeInitCommand } from "../commands/init/command.js";
3
3
  import { resolveCliContext } from "../preflight/index.js";
4
4
  import { renderInitTranscript } from "../render/transcripts/init.js";
5
5
  import { createConfirmationWorkflow } from "./confirmation.js";
6
+ import { NonInteractiveShellError } from "./errors.js";
6
7
  import { writeCommandOutput } from "./output.js";
7
8
  export async function runInitCommand(options = {}) {
8
9
  const { root } = await resolveCliContext({ requireWorkspace: false });
@@ -10,7 +11,7 @@ export async function runInitCommand(options = {}) {
10
11
  const confirmation = createConfirmationWorkflow({
11
12
  assumeYes,
12
13
  onUnavailable: () => {
13
- throw new Error("Non-interactive shell detected; re-run with --yes to accept defaults.");
14
+ throw new NonInteractiveShellError();
14
15
  },
15
16
  });
16
17
  try {
package/dist/cli/prune.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import { Command } from "commander";
2
2
  import { executePruneCommand } from "../commands/prune/command.js";
3
- import { InteractiveConfirmationRequiredError } from "../commands/prune/errors.js";
4
3
  import { resolveCliContext } from "../preflight/index.js";
5
4
  import { renderPruneTranscript } from "../render/transcripts/prune.js";
6
5
  import { createConfirmationWorkflow } from "./confirmation.js";
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;
@@ -13,7 +13,7 @@ export async function runPruneCommand(options) {
13
13
  const confirmation = createConfirmationWorkflow({
14
14
  assumeYes,
15
15
  onUnavailable: () => {
16
- throw new InteractiveConfirmationRequiredError();
16
+ throw new NonInteractiveShellError();
17
17
  },
18
18
  });
19
19
  try {
@@ -16,7 +16,7 @@ export class ApplyRunDeletedError extends ApplyError {
16
16
  export class ApplyRunMetadataCorruptedError extends ApplyError {
17
17
  constructor(detail) {
18
18
  super("Run history is corrupted; cannot apply.", [detail], [
19
- "Inspect `.voratiq/runs/index.json` and the affected run directory under `.voratiq/runs/<id>` or regenerate the run with `voratiq run`.",
19
+ "Inspect `.voratiq/runs/index.json` and the affected run directory under `.voratiq/runs/sessions/<id>` or regenerate the run with `voratiq run`.",
20
20
  ]);
21
21
  this.name = "ApplyRunMetadataCorruptedError";
22
22
  }
@@ -1,4 +1,5 @@
1
1
  import * as fs from "node:fs/promises";
2
+ import { NonInteractiveShellError } from "../../cli/errors.js";
2
3
  import { RunRecordNotFoundError, RunRecordParseError, } from "../../records/errors.js";
3
4
  import { rewriteRunRecord, RUN_RECORD_FILENAME, } from "../../records/persistence.js";
4
5
  import { buildPruneConfirmationPreface } from "../../render/transcripts/prune.js";
@@ -7,14 +8,14 @@ import { pathExists } from "../../utils/fs.js";
7
8
  import { getGitStderr, runGitCommand } from "../../utils/git.js";
8
9
  import { normalizePathForDisplay, resolveDisplayPath, resolvePath, } from "../../utils/path.js";
9
10
  import { deriveAgentBranches, removeRunDirectory, removeWorkspaceEntry, } from "../../workspace/prune.js";
10
- import { buildAgentArtifactPaths, formatWorkspacePath, getAgentDirectoryPath, getAgentEvalsDirectoryPath, getAgentWorkspaceDirectoryPath, resolveWorkspacePath, VORATIQ_RUNS_DIR, } from "../../workspace/structure.js";
11
+ import { buildAgentArtifactPaths, getAgentDirectoryPath, getAgentEvalsDirectoryPath, getAgentWorkspaceDirectoryPath, getRunDirectoryPath, resolveWorkspacePath, VORATIQ_RUNS_SESSIONS_DIR, } from "../../workspace/structure.js";
11
12
  import { fetchRunSafely } from "../fetch.js";
12
- import { InteractiveConfirmationRequiredError, PruneBranchDeletionError, PruneRunDeletedError, RunMetadataMissingError, } from "./errors.js";
13
+ import { PruneBranchDeletionError, PruneRunDeletedError, RunMetadataMissingError, } from "./errors.js";
13
14
  export async function executePruneCommand(input) {
14
15
  const { root, runsFilePath, runId, confirm, clock, purge: purgeInput, } = input;
15
16
  const purge = purgeInput ?? false;
16
17
  if (!confirm) {
17
- throw new InteractiveConfirmationRequiredError();
18
+ throw new NonInteractiveShellError();
18
19
  }
19
20
  const runRecord = await fetchRunSafely({
20
21
  root,
@@ -22,7 +23,7 @@ export async function executePruneCommand(input) {
22
23
  runId,
23
24
  onDeleted: (record) => new PruneRunDeletedError(record.runId),
24
25
  });
25
- const runPathDisplay = formatWorkspacePath(VORATIQ_RUNS_DIR, runRecord.runId);
26
+ const runPathDisplay = getRunDirectoryPath(runRecord.runId);
26
27
  const branches = deriveAgentBranches(runRecord);
27
28
  const workspaceTargets = buildWorkspaceTargets({
28
29
  root,
@@ -263,7 +264,7 @@ async function pruneArtifacts(options) {
263
264
  }
264
265
  async function purgeRunDirectoryExceptRecord(options) {
265
266
  const { root, runRecord } = options;
266
- const runDir = resolveWorkspacePath(root, VORATIQ_RUNS_DIR, runRecord.runId);
267
+ const runDir = resolveWorkspacePath(root, VORATIQ_RUNS_SESSIONS_DIR, runRecord.runId);
267
268
  if (!(await pathExists(runDir))) {
268
269
  return;
269
270
  }
@@ -6,9 +6,6 @@ export declare class RunMetadataMissingError extends PruneError {
6
6
  readonly runId: string;
7
7
  constructor(runId: string);
8
8
  }
9
- export declare class InteractiveConfirmationRequiredError extends PruneError {
10
- constructor();
11
- }
12
9
  export declare class PruneBranchDeletionError extends PruneError {
13
10
  readonly branch: string;
14
11
  readonly detail: string;
@@ -8,17 +8,11 @@ export class PruneError extends CliError {
8
8
  export class RunMetadataMissingError extends PruneError {
9
9
  runId;
10
10
  constructor(runId) {
11
- super(`Run metadata for ${runId} is missing from .voratiq/runs/${runId}/record.json; prune cannot proceed.`);
11
+ super(`Run metadata for ${runId} is missing from .voratiq/runs/sessions/${runId}/record.json; prune cannot proceed.`);
12
12
  this.runId = runId;
13
13
  this.name = "RunMetadataMissingError";
14
14
  }
15
15
  }
16
- export class InteractiveConfirmationRequiredError extends PruneError {
17
- constructor() {
18
- super("Interactive confirmation required; re-run with --yes or from a tty.");
19
- this.name = "InteractiveConfirmationRequiredError";
20
- }
21
- }
22
16
  export class PruneBranchDeletionError extends PruneError {
23
17
  branch;
24
18
  detail;
@@ -6,4 +6,5 @@ export interface AgentDefault {
6
6
  }
7
7
  export declare function sanitizeAgentIdFromModel(model: string): string;
8
8
  export declare const DEFAULT_AGENT_DEFAULTS: readonly AgentDefault[];
9
+ export declare function getDefaultAgentIdByProvider(provider: string): string | undefined;
9
10
  export declare function getAgentDefault(provider: string): AgentDefault | undefined;
@@ -24,7 +24,7 @@ export const DEFAULT_AGENT_DEFAULTS = [
24
24
  },
25
25
  {
26
26
  provider: "codex",
27
- model: "gpt-5.1-codex-max",
27
+ model: "gpt-5.2-codex",
28
28
  argv: [
29
29
  "exec",
30
30
  "--model",
@@ -42,6 +42,10 @@ export const DEFAULT_AGENT_DEFAULTS = [
42
42
  },
43
43
  ];
44
44
  const AGENT_DEFAULTS_BY_PROVIDER = new Map(DEFAULT_AGENT_DEFAULTS.map((agentDefault) => [agentDefault.provider, agentDefault]));
45
+ export function getDefaultAgentIdByProvider(provider) {
46
+ const entry = AGENT_DEFAULTS_BY_PROVIDER.get(provider);
47
+ return entry ? sanitizeAgentIdFromModel(entry.model) : undefined;
48
+ }
45
49
  export function getAgentDefault(provider) {
46
50
  const agentDefault = AGENT_DEFAULTS_BY_PROVIDER.get(provider);
47
51
  return agentDefault ? cloneAgentDefault(agentDefault) : undefined;
@@ -18,6 +18,15 @@ export declare class DuplicateAgentIdError extends AgentsConfigError {
18
18
  readonly displayPath: string;
19
19
  constructor(agentId: string, displayPath: string);
20
20
  }
21
+ export declare class AgentNotFoundError extends AgentsConfigError {
22
+ readonly agentId: string;
23
+ readonly enabledAgentIds: readonly string[];
24
+ constructor(agentId: string, enabledAgentIds: readonly string[]);
25
+ }
26
+ export declare class AgentDisabledError extends AgentsConfigError {
27
+ readonly agentId: string;
28
+ constructor(agentId: string);
29
+ }
21
30
  export declare class ModelPlaceholderMissingError extends AgentsError {
22
31
  readonly agentId: string;
23
32
  readonly placeholder: string;
@@ -36,6 +36,27 @@ export class DuplicateAgentIdError extends AgentsConfigError {
36
36
  this.name = "DuplicateAgentIdError";
37
37
  }
38
38
  }
39
+ export class AgentNotFoundError extends AgentsConfigError {
40
+ agentId;
41
+ enabledAgentIds;
42
+ constructor(agentId, enabledAgentIds) {
43
+ const enabledList = enabledAgentIds.length > 0
44
+ ? enabledAgentIds.slice().sort().join(", ")
45
+ : "none";
46
+ super(`${DEFAULT_ERROR_CONTEXT}: Agent "${agentId}" is not defined in agents.yaml. Enabled agents: ${enabledList}.`);
47
+ this.agentId = agentId;
48
+ this.enabledAgentIds = enabledAgentIds;
49
+ this.name = "AgentNotFoundError";
50
+ }
51
+ }
52
+ export class AgentDisabledError extends AgentsConfigError {
53
+ agentId;
54
+ constructor(agentId) {
55
+ super(`${DEFAULT_ERROR_CONTEXT}: Agent "${agentId}" is disabled in agents.yaml.`);
56
+ this.agentId = agentId;
57
+ this.name = "AgentDisabledError";
58
+ }
59
+ }
39
60
  export class ModelPlaceholderMissingError extends AgentsError {
40
61
  agentId;
41
62
  placeholder;
@@ -1,4 +1,4 @@
1
- import { type AgentCatalog, type AgentsConfig } from "./types.js";
1
+ import { type AgentCatalog, type AgentDefinition, type AgentsConfig } from "./types.js";
2
2
  export declare function readAgentsConfig(content: string): AgentsConfig;
3
3
  export interface LoadAgentCatalogOptions {
4
4
  root?: string;
@@ -6,3 +6,4 @@ export interface LoadAgentCatalogOptions {
6
6
  readFile?: (path: string) => string;
7
7
  }
8
8
  export declare function loadAgentCatalog(options?: LoadAgentCatalogOptions): AgentCatalog;
9
+ export declare function loadAgentById(id: string, options?: LoadAgentCatalogOptions): AgentDefinition;
@@ -6,7 +6,7 @@ import { resolveWorkspacePath } from "../../workspace/structure.js";
6
6
  import { createConfigLoader } from "../shared/loader-factory.js";
7
7
  import { formatYamlErrorMessage } from "../shared/yaml-error-formatter.js";
8
8
  import { getAgentDefault, MODEL_PLACEHOLDER, } from "./defaults.js";
9
- import { AgentBinaryAccessError, AgentBinaryMissingError, AgentsYamlParseError, DEFAULT_ERROR_CONTEXT, DuplicateAgentIdError, MissingAgentsConfigError, ModelPlaceholderMissingError, UnknownAgentProviderTemplateError, } from "./errors.js";
9
+ import { AgentBinaryAccessError, AgentBinaryMissingError, AgentDisabledError, AgentNotFoundError, AgentsYamlParseError, DEFAULT_ERROR_CONTEXT, DuplicateAgentIdError, MissingAgentsConfigError, ModelPlaceholderMissingError, UnknownAgentProviderTemplateError, } from "./errors.js";
10
10
  import { agentsConfigSchema, } from "./types.js";
11
11
  const AGENTS_CONFIG_FILENAME = "agents.yaml";
12
12
  export function readAgentsConfig(content) {
@@ -21,7 +21,7 @@ function formatAgentsYamlError(detail) {
21
21
  });
22
22
  return new AgentsYamlParseError(message);
23
23
  }
24
- const loadAgentCatalogInternal = createConfigLoader({
24
+ const loadAgentsConfigInternal = createConfigLoader({
25
25
  resolveFilePath: (root, options) => options.filePath ?? resolveWorkspacePath(root, AGENTS_CONFIG_FILENAME),
26
26
  selectReadFile: (options) => options.readFile,
27
27
  handleMissing: ({ filePath }) => {
@@ -38,13 +38,30 @@ const loadAgentCatalogInternal = createConfigLoader({
38
38
  }
39
39
  seenAgentIds.add(entry.id);
40
40
  }
41
- const catalog = enabledAgents.map((entry) => buildAgentDefinition(entry));
42
- validateAgentBinaries(catalog);
43
- return catalog;
41
+ return { config, displayPath, enabledAgents };
44
42
  },
45
43
  });
44
+ function loadAgentsConfig(options = {}) {
45
+ return loadAgentsConfigInternal(options);
46
+ }
46
47
  export function loadAgentCatalog(options = {}) {
47
- return loadAgentCatalogInternal(options);
48
+ const { enabledAgents } = loadAgentsConfig(options);
49
+ const catalog = enabledAgents.map((entry) => buildAgentDefinition(entry));
50
+ validateAgentBinaries(catalog);
51
+ return catalog;
52
+ }
53
+ export function loadAgentById(id, options = {}) {
54
+ const { config, enabledAgents } = loadAgentsConfig(options);
55
+ const entry = config.agents.find((agent) => agent.id === id);
56
+ if (!entry) {
57
+ throw new AgentNotFoundError(id, enabledAgents.map((agent) => agent.id));
58
+ }
59
+ if (entry.enabled === false) {
60
+ throw new AgentDisabledError(entry.id);
61
+ }
62
+ const definition = buildAgentDefinition(entry);
63
+ assertAgentBinary(definition);
64
+ return definition;
48
65
  }
49
66
  function validateAgentBinaries(agents) {
50
67
  for (const agent of agents) {
@@ -10,7 +10,7 @@ export const DEFAULT_SANDBOX_PROVIDERS = [
10
10
  {
11
11
  id: "codex",
12
12
  network: {
13
- allowedDomains: ["api.openai.com", "chatgpt.com"],
13
+ allowedDomains: ["api.openai.com", "chatgpt.com", "auth.openai.com"],
14
14
  deniedDomains: [],
15
15
  allowLocalBinding: false,
16
16
  },
@@ -54,7 +54,7 @@ type ReadRunRecordsFn = (options: ReadRunRecordsOptions) => Promise<RunRecord[]>
54
54
  export type RunIndexEntry = Pick<RunRecord, "runId" | "createdAt" | "status">;
55
55
  export interface RunIndexPayload {
56
56
  version: number;
57
- runs: RunIndexEntry[];
57
+ sessions: RunIndexEntry[];
58
58
  }
59
59
  export declare const RUN_RECORD_FILENAME = "record.json";
60
60
  export type RunRecordBufferSnapshotEntry = {
@@ -8,7 +8,7 @@ import { RunOptionValidationError, RunRecordMutationError, RunRecordNotFoundErro
8
8
  import { acquireHistoryLock } from "./history-lock.js";
9
9
  import { runRecordSchema } from "./types.js";
10
10
  export { HISTORY_LOCK_STALE_GRACE_MS } from "./history-lock.js";
11
- const RUN_INDEX_VERSION = 1;
11
+ const RUN_INDEX_VERSION = 2;
12
12
  export const RUN_RECORD_FILENAME = "record.json";
13
13
  const HISTORY_LOCK_FILENAME = "history.lock";
14
14
  const BUFFER_FLUSH_DELAY_MS = 250;
@@ -18,7 +18,7 @@ const readRunRecordsInternal = async (options) => {
18
18
  if (limit !== undefined && (!Number.isInteger(limit) || limit <= 0)) {
19
19
  throw new RunOptionValidationError("limit", "must be a positive integer");
20
20
  }
21
- const runsDir = getRunsDirectory(runsFilePath);
21
+ const sessionsDir = getSessionsDirectory(runsFilePath);
22
22
  let index;
23
23
  try {
24
24
  index = await readRunIndex(runsFilePath);
@@ -31,12 +31,12 @@ const readRunRecordsInternal = async (options) => {
31
31
  throw error;
32
32
  }
33
33
  const matches = [];
34
- for (let i = index.runs.length - 1; i >= 0; i -= 1) {
35
- const entry = index.runs[i];
34
+ for (let i = index.sessions.length - 1; i >= 0; i -= 1) {
35
+ const entry = index.sessions[i];
36
36
  if (!entry) {
37
37
  continue;
38
38
  }
39
- const recordPath = join(runsDir, entry.runId, RUN_RECORD_FILENAME);
39
+ const recordPath = join(sessionsDir, entry.runId, RUN_RECORD_FILENAME);
40
40
  try {
41
41
  const record = await readRunRecordFromDisk(recordPath);
42
42
  if (predicate && !predicate(record)) {
@@ -86,11 +86,12 @@ export async function readRunRecords(options) {
86
86
  }
87
87
  export async function appendRunRecord(options) {
88
88
  const { root, runsFilePath, record } = options;
89
- const runsDir = getRunsDirectory(runsFilePath);
90
- const recordDir = join(runsDir, record.runId);
89
+ const runsRoot = getRunsDirectory(runsFilePath);
90
+ const sessionsDir = getSessionsDirectory(runsFilePath);
91
+ const recordDir = join(sessionsDir, record.runId);
91
92
  const recordPath = join(recordDir, RUN_RECORD_FILENAME);
92
93
  const displayPath = relativeToRoot(root, recordPath);
93
- const lockPath = join(runsDir, HISTORY_LOCK_FILENAME);
94
+ const lockPath = join(runsRoot, HISTORY_LOCK_FILENAME);
94
95
  await mkdir(recordDir, { recursive: true });
95
96
  const releaseLock = await acquireHistoryLock(lockPath);
96
97
  try {
@@ -108,7 +109,7 @@ export async function appendRunRecord(options) {
108
109
  runId: record.runId,
109
110
  recordPath,
110
111
  lockPath,
111
- runsDir,
112
+ runsDir: runsRoot,
112
113
  runsFilePath,
113
114
  root,
114
115
  record,
@@ -131,15 +132,16 @@ export async function appendRunRecord(options) {
131
132
  }
132
133
  export async function rewriteRunRecord(options) {
133
134
  const { root, runsFilePath, runId, mutate } = options;
134
- const runsDir = getRunsDirectory(runsFilePath);
135
- const recordPath = join(runsDir, runId, RUN_RECORD_FILENAME);
136
- const lockPath = join(runsDir, HISTORY_LOCK_FILENAME);
135
+ const runsRoot = getRunsDirectory(runsFilePath);
136
+ const sessionsDir = getSessionsDirectory(runsFilePath);
137
+ const recordPath = join(sessionsDir, runId, RUN_RECORD_FILENAME);
138
+ const lockPath = join(runsRoot, HISTORY_LOCK_FILENAME);
137
139
  const entry = await getOrLoadBufferEntry({
138
140
  key: recordPath,
139
141
  runId,
140
142
  recordPath,
141
143
  lockPath,
142
- runsDir,
144
+ runsDir: runsRoot,
143
145
  runsFilePath,
144
146
  root,
145
147
  });
@@ -161,8 +163,8 @@ export async function rewriteRunRecord(options) {
161
163
  }
162
164
  export async function getRunRecordSnapshot(options) {
163
165
  const { runsFilePath, runId } = options;
164
- const runsDir = getRunsDirectory(runsFilePath);
165
- const recordPath = join(runsDir, runId, RUN_RECORD_FILENAME);
166
+ const sessionsDir = getSessionsDirectory(runsFilePath);
167
+ const recordPath = join(sessionsDir, runId, RUN_RECORD_FILENAME);
166
168
  const buffered = runRecordBuffers.get(recordPath);
167
169
  if (buffered) {
168
170
  return structuredClone(buffered.record);
@@ -189,7 +191,7 @@ export async function flushAllRunRecordBuffers() {
189
191
  }
190
192
  export async function disposeRunRecordBuffer(options) {
191
193
  const { runsFilePath, runId } = options;
192
- const recordPath = join(getRunsDirectory(runsFilePath), runId, RUN_RECORD_FILENAME);
194
+ const recordPath = join(getSessionsDirectory(runsFilePath), runId, RUN_RECORD_FILENAME);
193
195
  const entry = runRecordBuffers.get(recordPath);
194
196
  if (!entry) {
195
197
  return;
@@ -198,7 +200,7 @@ export async function disposeRunRecordBuffer(options) {
198
200
  }
199
201
  export async function flushRunRecordBuffer(options) {
200
202
  const { runsFilePath, runId } = options;
201
- const recordPath = join(getRunsDirectory(runsFilePath), runId, RUN_RECORD_FILENAME);
203
+ const recordPath = join(getSessionsDirectory(runsFilePath), runId, RUN_RECORD_FILENAME);
202
204
  const entry = runRecordBuffers.get(recordPath);
203
205
  if (!entry) {
204
206
  return;
@@ -390,22 +392,30 @@ async function atomicWriteJson(path, payload) {
390
392
  function getRunsDirectory(runsFilePath) {
391
393
  return dirname(runsFilePath);
392
394
  }
395
+ function getSessionsDirectory(runsFilePath) {
396
+ return join(getRunsDirectory(runsFilePath), "sessions");
397
+ }
393
398
  async function readRunIndex(path) {
394
399
  try {
395
400
  const raw = await readFile(path, "utf8");
396
401
  const trimmed = raw.trim();
397
402
  if (!trimmed) {
398
- return { version: RUN_INDEX_VERSION, runs: [] };
403
+ return { version: RUN_INDEX_VERSION, sessions: [] };
399
404
  }
400
405
  const parsed = JSON.parse(trimmed);
401
- if (!Array.isArray(parsed.runs)) {
402
- return { version: RUN_INDEX_VERSION, runs: [] };
403
- }
404
- return parsed;
406
+ const sessions = Array.isArray(parsed.sessions)
407
+ ? parsed.sessions
408
+ : Array.isArray(parsed.runs)
409
+ ? parsed.runs
410
+ : [];
411
+ return {
412
+ version: parsed.version ?? RUN_INDEX_VERSION,
413
+ sessions,
414
+ };
405
415
  }
406
416
  catch (error) {
407
417
  if (isFileSystemError(error) && error.code === "ENOENT") {
408
- return { version: RUN_INDEX_VERSION, runs: [] };
418
+ return { version: RUN_INDEX_VERSION, sessions: [] };
409
419
  }
410
420
  if (error instanceof SyntaxError) {
411
421
  throw new RunRecordParseError(path, error.message);
@@ -415,15 +425,15 @@ async function readRunIndex(path) {
415
425
  }
416
426
  async function upsertRunIndexEntry(indexPath, entry) {
417
427
  const payload = await readRunIndex(indexPath);
418
- const existingIndex = payload.runs.findIndex((run) => run.runId === entry.runId);
428
+ const existingIndex = payload.sessions.findIndex((run) => run.runId === entry.runId);
419
429
  if (existingIndex >= 0) {
420
- payload.runs[existingIndex] = {
421
- ...payload.runs[existingIndex],
430
+ payload.sessions[existingIndex] = {
431
+ ...payload.sessions[existingIndex],
422
432
  ...entry,
423
433
  };
424
434
  }
425
435
  else {
426
- payload.runs.push(entry);
436
+ payload.sessions.push(entry);
427
437
  }
428
438
  payload.version = RUN_INDEX_VERSION;
429
439
  await atomicWriteJson(indexPath, payload);
@@ -28,6 +28,7 @@ export interface GitCommitOptions {
28
28
  message: string;
29
29
  authorName?: string;
30
30
  authorEmail?: string;
31
+ bypassHooks?: boolean;
31
32
  }
32
33
  export declare function gitCommitAll(options: GitCommitOptions): Promise<void>;
33
34
  export interface GitDiffStatOptions {
package/dist/utils/git.js CHANGED
@@ -79,15 +79,25 @@ export async function gitHasStagedChanges(cwd) {
79
79
  return output.length > 0;
80
80
  }
81
81
  export async function gitCommitAll(options) {
82
- const { cwd, message, authorName = GIT_AUTHOR_NAME, authorEmail = GIT_AUTHOR_EMAIL, } = options;
83
- await runGitCommand(["commit", "-m", message], {
82
+ const { cwd, message, authorName = GIT_AUTHOR_NAME, authorEmail = GIT_AUTHOR_EMAIL, bypassHooks = false, } = options;
83
+ const args = ["commit", "-m", message];
84
+ if (bypassHooks) {
85
+ args.push("--no-verify");
86
+ }
87
+ const env = {
88
+ GIT_AUTHOR_NAME: authorName,
89
+ GIT_AUTHOR_EMAIL: authorEmail,
90
+ GIT_COMMITTER_NAME: authorName,
91
+ GIT_COMMITTER_EMAIL: authorEmail,
92
+ };
93
+ if (bypassHooks) {
94
+ env.HUSKY = "0";
95
+ env.HUSKY_SKIP_HOOKS = "1";
96
+ env.LEFTHOOK = "0";
97
+ }
98
+ await runGitCommand(args, {
84
99
  cwd,
85
- env: {
86
- GIT_AUTHOR_NAME: authorName,
87
- GIT_AUTHOR_EMAIL: authorEmail,
88
- GIT_COMMITTER_NAME: authorName,
89
- GIT_COMMITTER_EMAIL: authorEmail,
90
- },
100
+ env,
91
101
  });
92
102
  }
93
103
  export async function gitDiffShortStat(options) {
@@ -89,6 +89,7 @@ export async function collectAgentArtifacts(options) {
89
89
  message: summary,
90
90
  authorName: persona.authorName,
91
91
  authorEmail: persona.authorEmail,
92
+ bypassHooks: true,
92
93
  }));
93
94
  commitSha = await runGitStep("Git rev-parse failed", async () => runGitCommand(["rev-parse", "HEAD"], { cwd: workspacePath }));
94
95
  const diffContent = await runGitStep("Git diff failed", async () => gitDiff({
@@ -114,11 +114,13 @@ export async function scaffoldAgentWorkspace(paths) {
114
114
  filesToInitialize.add(absolutePath);
115
115
  }
116
116
  }
117
+ for (const filePath of filesToInitialize) {
118
+ directoriesToEnsure.add(dirname(filePath));
119
+ }
117
120
  for (const dirPath of directoriesToEnsure) {
118
121
  await mkdir(dirPath, { recursive: true });
119
122
  }
120
123
  for (const filePath of filesToInitialize) {
121
- await mkdir(dirname(filePath), { recursive: true });
122
124
  await writeFile(filePath, "", { encoding: "utf8" });
123
125
  }
124
126
  }
@@ -2,7 +2,7 @@ import { mkdir, writeFile } from "node:fs/promises";
2
2
  import { ensureDirectoryExists, ensureFileExists, isDirectory, pathExists, } from "../utils/fs.js";
3
3
  import { relativeToRoot } from "../utils/path.js";
4
4
  import { WorkspaceMissingEntryError, WorkspaceNotInitializedError, } from "./errors.js";
5
- import { resolveWorkspacePath, VORATIQ_AGENTS_FILE, VORATIQ_ENVIRONMENT_FILE, VORATIQ_EVALS_FILE, VORATIQ_RUNS_DIR, VORATIQ_RUNS_FILE, VORATIQ_SANDBOX_FILE, } from "./structure.js";
5
+ import { resolveWorkspacePath, VORATIQ_AGENTS_FILE, VORATIQ_ENVIRONMENT_FILE, VORATIQ_EVALS_FILE, VORATIQ_RUNS_DIR, VORATIQ_RUNS_FILE, VORATIQ_RUNS_SESSIONS_DIR, VORATIQ_SANDBOX_FILE, } from "./structure.js";
6
6
  import { buildDefaultAgentsTemplate, buildDefaultEnvironmentTemplate, buildDefaultEvalsTemplate, buildDefaultSandboxTemplate, } from "./templates.js";
7
7
  export async function createWorkspace(root) {
8
8
  const createdDirectories = [];
@@ -17,9 +17,14 @@ export async function createWorkspace(root) {
17
17
  await mkdir(runsDir, { recursive: true });
18
18
  createdDirectories.push(relativeToRoot(root, runsDir));
19
19
  }
20
+ const sessionsDir = resolveWorkspacePath(root, VORATIQ_RUNS_SESSIONS_DIR);
21
+ if (!(await pathExists(sessionsDir))) {
22
+ await mkdir(sessionsDir, { recursive: true });
23
+ createdDirectories.push(relativeToRoot(root, sessionsDir));
24
+ }
20
25
  const runsIndexPath = resolveWorkspacePath(root, VORATIQ_RUNS_FILE);
21
26
  if (!(await pathExists(runsIndexPath))) {
22
- const initialIndex = JSON.stringify({ version: 1, runs: [] }, null, 2);
27
+ const initialIndex = JSON.stringify({ version: 2, sessions: [] }, null, 2);
23
28
  await writeFile(runsIndexPath, `${initialIndex}\n`, { encoding: "utf8" });
24
29
  createdFiles.push(relativeToRoot(root, runsIndexPath));
25
30
  }
@@ -56,6 +61,7 @@ export async function createWorkspace(root) {
56
61
  export async function validateWorkspace(root) {
57
62
  const workspaceDir = resolveWorkspacePath(root);
58
63
  const runsDir = resolveWorkspacePath(root, VORATIQ_RUNS_DIR);
64
+ const sessionsDir = resolveWorkspacePath(root, VORATIQ_RUNS_SESSIONS_DIR);
59
65
  const runsIndexPath = resolveWorkspacePath(root, VORATIQ_RUNS_FILE);
60
66
  const agentsConfigPath = resolveWorkspacePath(root, VORATIQ_AGENTS_FILE);
61
67
  const evalsConfigPath = resolveWorkspacePath(root, VORATIQ_EVALS_FILE);
@@ -65,6 +71,7 @@ export async function validateWorkspace(root) {
65
71
  const missingEntries = [
66
72
  `${relativeToRoot(root, workspaceDir)}/`,
67
73
  `${relativeToRoot(root, runsDir)}/`,
74
+ `${relativeToRoot(root, sessionsDir)}/`,
68
75
  relativeToRoot(root, runsIndexPath),
69
76
  relativeToRoot(root, agentsConfigPath),
70
77
  relativeToRoot(root, environmentConfigPath),
@@ -73,6 +80,7 @@ export async function validateWorkspace(root) {
73
80
  throw new WorkspaceNotInitializedError(missingEntries);
74
81
  }
75
82
  await ensureDirectoryExists(runsDir, () => new WorkspaceMissingEntryError(relativeToRoot(root, runsDir)));
83
+ await ensureDirectoryExists(sessionsDir, () => new WorkspaceMissingEntryError(relativeToRoot(root, sessionsDir)));
76
84
  await ensureFileExists(runsIndexPath, () => new WorkspaceMissingEntryError(relativeToRoot(root, runsIndexPath)));
77
85
  await ensureFileExists(agentsConfigPath, () => new WorkspaceMissingEntryError(relativeToRoot(root, agentsConfigPath)));
78
86
  await ensureFileExists(evalsConfigPath, () => new WorkspaceMissingEntryError(relativeToRoot(root, evalsConfigPath)));
@@ -3,6 +3,7 @@ import type { ChatArtifactFormat } from "./chat/types.js";
3
3
  export declare const VORATIQ_DIR = ".voratiq";
4
4
  export declare const VORATIQ_RUNS_DIR = "runs";
5
5
  export declare const VORATIQ_RUNS_FILE = "runs/index.json";
6
+ export declare const VORATIQ_RUNS_SESSIONS_DIR = "runs/sessions";
6
7
  export declare const VORATIQ_AGENTS_FILE = "agents.yaml";
7
8
  export declare const VORATIQ_EVALS_FILE = "evals.yaml";
8
9
  export declare const VORATIQ_ENVIRONMENT_FILE = "environment.yaml";
@@ -4,6 +4,7 @@ import { assertRepoRelativePath, resolvePathWithinRoot, } from "../utils/path.js
4
4
  export const VORATIQ_DIR = ".voratiq";
5
5
  export const VORATIQ_RUNS_DIR = "runs";
6
6
  export const VORATIQ_RUNS_FILE = "runs/index.json";
7
+ export const VORATIQ_RUNS_SESSIONS_DIR = "runs/sessions";
7
8
  export const VORATIQ_AGENTS_FILE = "agents.yaml";
8
9
  export const VORATIQ_EVALS_FILE = "evals.yaml";
9
10
  export const VORATIQ_ENVIRONMENT_FILE = "environment.yaml";
@@ -28,7 +29,7 @@ export function resolveWorkspacePath(root, ...segments) {
28
29
  export function formatWorkspacePath(...segments) {
29
30
  return [VORATIQ_DIR, ...segments].join("/");
30
31
  }
31
- const RUNS_SEGMENT = VORATIQ_RUNS_DIR;
32
+ const RUNS_SESSION_SEGMENTS = [VORATIQ_RUNS_DIR, "sessions"];
32
33
  function assertPathSegment(label, value) {
33
34
  if (typeof value !== "string") {
34
35
  throw new Error(`${label} must be a string`);
@@ -43,7 +44,7 @@ function assertPathSegment(label, value) {
43
44
  }
44
45
  function formatRunScopedPath(runId, ...segments) {
45
46
  const safeRunId = assertPathSegment("runId", runId);
46
- const scoped = formatWorkspacePath(RUNS_SEGMENT, safeRunId, ...segments);
47
+ const scoped = formatWorkspacePath(...RUNS_SESSION_SEGMENTS, safeRunId, ...segments);
47
48
  return assertRepoRelativePath(scoped);
48
49
  }
49
50
  function formatAgentScopedPath(runId, agentId, ...segments) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "voratiq",
3
- "version": "0.1.0-beta.1",
3
+ "version": "0.1.0-beta.2",
4
4
  "description": "Run multiple AI coding agents in parallel, compare their results, and apply the best solution.",
5
5
  "keywords": [
6
6
  "ai",