@vitest-agent/sdk 3.1.2 → 4.0.0
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.
- package/README.md +23 -19
- package/dispatch.d.ts +25 -6
- package/formatters/markdown.js +1 -0
- package/formatters/terminal.js +4 -3
- package/index.d.ts +448 -2341
- package/index.js +10 -68
- package/internal-inject-env.js +5 -7
- package/package.json +3 -19
- package/sidecar-dispatch.js +7 -5
- package/utils/format-console.js +9 -10
- package/utils/format-terminal.js +7 -7
- package/utils/posix-path.js +52 -0
- package/utils/test-location.js +11 -5
- package/version.js +17 -0
- package/layers/ConfigLive.js +0 -46
- package/layers/DataReaderLive.js +0 -1923
- package/layers/DataStoreLive.js +0 -1287
- package/layers/DetailResolverLive.js +0 -9
- package/layers/DiscoveryRegistryLive.js +0 -70
- package/layers/EnvironmentDetectorLive.js +0 -21
- package/layers/EnvironmentDetectorTest.js +0 -13
- package/layers/ExecutorResolverLive.js +0 -9
- package/layers/FormatSelectorLive.js +0 -20
- package/layers/HistoryTrackerLive.js +0 -49
- package/layers/HistoryTrackerTest.js +0 -25
- package/layers/LoggerLive.js +0 -64
- package/layers/OutputPipelineLive.js +0 -13
- package/layers/OutputRendererLive.js +0 -28
- package/layers/PathResolutionLive.js +0 -40
- package/layers/PerClientSessionMapLive.js +0 -145
- package/layers/ProjectDiscoveryLive.js +0 -68
- package/layers/ProjectDiscoveryTest.js +0 -12
- package/layers/ProjectIdentityLive.js +0 -96
- package/layers/RunContextLive.js +0 -81
- package/lib/format-triage.js +0 -103
- package/lib/format-wrapup.js +0 -67
- package/migrations/0001_initial.js +0 -824
- package/migrations/0002_test_artifacts.js +0 -30
- package/migrations/index.js +0 -26
- package/migrations/registry_0001_initial.js +0 -39
- package/migrations/session_map_0001_initial.js +0 -38
- package/services/Config.js +0 -15
- package/services/DataReader.js +0 -8
- package/services/DataStore.js +0 -15
- package/services/DetailResolver.js +0 -8
- package/services/DiscoveryRegistry.js +0 -8
- package/services/EnvironmentDetector.js +0 -8
- package/services/ExecutorResolver.js +0 -8
- package/services/FormatSelector.js +0 -8
- package/services/HistoryTracker.js +0 -20
- package/services/OutputRenderer.js +0 -8
- package/services/PerClientSessionMap.js +0 -22
- package/services/ProjectDiscovery.js +0 -8
- package/services/ProjectIdentity.js +0 -63
- package/services/RunContext.js +0 -53
- package/services/idempotency.js +0 -58
- package/sql/assemblers.js +0 -69
- package/testing/layers.js +0 -21
- package/testing.d.ts +0 -1862
- package/testing.js +0 -254
- package/utils/ensure-migrated.js +0 -52
- package/utils/failure-signature.js +0 -34
- package/utils/resolve-data-path.js +0 -65
- package/utils/resolve-project-key-from-cwd.js +0 -53
- package/utils/resolve-workspace-key.js +0 -41
package/layers/DataStoreLive.js
DELETED
|
@@ -1,1287 +0,0 @@
|
|
|
1
|
-
import { AgentNotFoundError, RegistrationConflictError } from "../errors/AgentErrors.js";
|
|
2
|
-
import { DataStoreError, extractSqlReason } from "../errors/DataStoreError.js";
|
|
3
|
-
import { BehaviorNotFoundError, GoalNotFoundError, IllegalStatusTransitionError, TddTaskAlreadyEndedError, TddTaskNotFoundError } from "../errors/TddErrors.js";
|
|
4
|
-
import { Agent, IdempotencyHit } from "../schemas/Agent.js";
|
|
5
|
-
import { DataStore, INLINE_ATTACHMENT_BODY_CAP_BYTES } from "../services/DataStore.js";
|
|
6
|
-
import { coerceErrorText } from "../utils/coerce-error-text.js";
|
|
7
|
-
import { Effect, Layer, Option } from "effect";
|
|
8
|
-
import { SqlClient } from "effect/unstable/sql/SqlClient";
|
|
9
|
-
import { Buffer } from "node:buffer";
|
|
10
|
-
import { randomUUID } from "node:crypto";
|
|
11
|
-
|
|
12
|
-
//#region src/layers/DataStoreLive.ts
|
|
13
|
-
const isLegalLifecycleTransition = (from, to) => {
|
|
14
|
-
if (from === to) return true;
|
|
15
|
-
if (from === "done" || from === "abandoned") return false;
|
|
16
|
-
if (from === "pending") return to === "in_progress" || to === "done" || to === "abandoned";
|
|
17
|
-
if (from === "in_progress") return to === "done" || to === "abandoned";
|
|
18
|
-
return false;
|
|
19
|
-
};
|
|
20
|
-
const goalRowFromDb = (row) => ({
|
|
21
|
-
id: row.id,
|
|
22
|
-
sessionId: row.session_id,
|
|
23
|
-
ordinal: row.ordinal,
|
|
24
|
-
goal: row.goal,
|
|
25
|
-
status: row.status,
|
|
26
|
-
createdAt: row.created_at
|
|
27
|
-
});
|
|
28
|
-
const behaviorRowFromDb = (row) => ({
|
|
29
|
-
id: row.id,
|
|
30
|
-
goalId: row.goal_id,
|
|
31
|
-
ordinal: row.ordinal,
|
|
32
|
-
behavior: row.behavior,
|
|
33
|
-
suggestedTestName: row.suggested_test_name,
|
|
34
|
-
status: row.status,
|
|
35
|
-
createdAt: row.created_at
|
|
36
|
-
});
|
|
37
|
-
const boolToInt = (v) => v === void 0 ? null : v ? 1 : 0;
|
|
38
|
-
/** @public */
|
|
39
|
-
const DataStoreLive = Layer.effect(DataStore, Effect.gen(function* () {
|
|
40
|
-
const sql = yield* SqlClient;
|
|
41
|
-
yield* sql`PRAGMA foreign_keys=ON`.pipe(Effect.catch(() => Effect.void));
|
|
42
|
-
const ensureFile = (filePath) => Effect.gen(function* () {
|
|
43
|
-
yield* Effect.logDebug("ensureFile").pipe(Effect.annotateLogs({ filePath }));
|
|
44
|
-
yield* sql`INSERT OR IGNORE INTO files (path) VALUES (${filePath})`;
|
|
45
|
-
return (yield* sql`SELECT id FROM files WHERE path = ${filePath}`)[0].id;
|
|
46
|
-
}).pipe(Effect.annotateLogs("service", "DataStore"), Effect.mapError((e) => new DataStoreError({
|
|
47
|
-
operation: "write",
|
|
48
|
-
table: "files",
|
|
49
|
-
reason: extractSqlReason(e)
|
|
50
|
-
})));
|
|
51
|
-
const writeSettings = (hash, settings, envVars) => Effect.gen(function* () {
|
|
52
|
-
yield* Effect.logDebug("writeSettings").pipe(Effect.annotateLogs({ hash }));
|
|
53
|
-
yield* sql`INSERT OR IGNORE INTO settings (hash, vitest_version, pool, environment, test_timeout, hook_timeout, slow_test_threshold, max_concurrency, max_workers, isolate, bail, globals, file_parallelism, sequence_seed, coverage_provider) VALUES (${hash}, ${settings.vitestVersion}, ${settings.pool ?? null}, ${settings.environment ?? null}, ${settings.testTimeout ?? null}, ${settings.hookTimeout ?? null}, ${settings.slowTestThreshold ?? null}, ${settings.maxConcurrency ?? null}, ${settings.maxWorkers ?? null}, ${boolToInt(settings.isolate)}, ${settings.bail ?? null}, ${boolToInt(settings.globals)}, ${boolToInt(settings.fileParallelism)}, ${settings.sequenceSeed ?? null}, ${settings.coverageProvider ?? null})`;
|
|
54
|
-
for (const [key, value] of Object.entries(envVars)) yield* sql`INSERT OR IGNORE INTO settings_env_vars (settings_hash, key, value) VALUES (${hash}, ${key}, ${value})`;
|
|
55
|
-
}).pipe(Effect.annotateLogs("service", "DataStore"), Effect.mapError((e) => new DataStoreError({
|
|
56
|
-
operation: "write",
|
|
57
|
-
table: "settings",
|
|
58
|
-
reason: extractSqlReason(e)
|
|
59
|
-
})));
|
|
60
|
-
const writeRun = (input) => Effect.gen(function* () {
|
|
61
|
-
yield* Effect.logDebug("writeRun").pipe(Effect.annotateLogs({
|
|
62
|
-
project: input.project,
|
|
63
|
-
invocationId: input.invocationId
|
|
64
|
-
}));
|
|
65
|
-
const actorType = input.actorType ?? "system";
|
|
66
|
-
const agentId = input.agentId ?? null;
|
|
67
|
-
const conversationId = input.conversationId ?? null;
|
|
68
|
-
const gitDirtyInt = input.gitDirty === void 0 || input.gitDirty === null ? null : input.gitDirty ? 1 : 0;
|
|
69
|
-
const hostMetadataJson = input.hostMetadata == null ? null : JSON.stringify(input.hostMetadata);
|
|
70
|
-
yield* sql`INSERT INTO test_runs (invocation_id, project, settings_hash, timestamp, commit_sha, branch, reason, duration, total, passed, failed, skipped, scoped, snapshot_added, snapshot_matched, snapshot_unmatched, snapshot_updated, snapshot_unchecked, snapshot_total, snapshot_failure, snapshot_did_update, snapshot_files_added, snapshot_files_removed, snapshot_files_unmatched, snapshot_files_updated, actor_type, agent_id, conversation_id, git_branch, git_commit_sha, git_dirty, git_upstream, git_worktree_dir, host_source, host_value, host_metadata) VALUES (${input.invocationId}, ${input.project}, ${input.settingsHash}, ${input.timestamp}, ${input.commitSha ?? null}, ${input.branch ?? null}, ${input.reason}, ${input.duration}, ${input.total}, ${input.passed}, ${input.failed}, ${input.skipped}, ${input.scoped ? 1 : 0}, ${input.snapshotAdded ?? 0}, ${input.snapshotMatched ?? 0}, ${input.snapshotUnmatched ?? 0}, ${input.snapshotUpdated ?? 0}, ${input.snapshotUnchecked ?? 0}, ${input.snapshotTotal ?? 0}, ${boolToInt(input.snapshotFailure) ?? 0}, ${boolToInt(input.snapshotDidUpdate) ?? 0}, ${input.snapshotFilesAdded ?? 0}, ${input.snapshotFilesRemoved ?? 0}, ${input.snapshotFilesUnmatched ?? 0}, ${input.snapshotFilesUpdated ?? 0}, ${actorType}, ${agentId}, ${conversationId}, ${input.gitBranch ?? null}, ${input.gitCommitSha ?? null}, ${gitDirtyInt}, ${input.gitUpstream ?? null}, ${input.gitWorktreeDir ?? null}, ${input.hostSource ?? null}, ${input.hostValue ?? null}, ${hostMetadataJson})`;
|
|
71
|
-
return (yield* sql`SELECT last_insert_rowid() as id`)[0].id;
|
|
72
|
-
}).pipe(Effect.annotateLogs("service", "DataStore"), Effect.mapError((e) => new DataStoreError({
|
|
73
|
-
operation: "write",
|
|
74
|
-
table: "test_runs",
|
|
75
|
-
reason: extractSqlReason(e)
|
|
76
|
-
})));
|
|
77
|
-
const writeModules = (runId, modules) => Effect.gen(function* () {
|
|
78
|
-
yield* Effect.logDebug("writeModules").pipe(Effect.annotateLogs({
|
|
79
|
-
runId,
|
|
80
|
-
count: modules.length
|
|
81
|
-
}));
|
|
82
|
-
const ids = [];
|
|
83
|
-
for (const mod of modules) {
|
|
84
|
-
yield* sql`INSERT INTO test_modules (run_id, file_id, relative_module_id, state, duration, environment_setup_duration, prepare_duration, collect_duration, setup_duration, heap) VALUES (${runId}, ${mod.fileId}, ${mod.relativeModuleId}, ${mod.state}, ${mod.duration ?? null}, ${mod.environmentSetupDuration ?? null}, ${mod.prepareDuration ?? null}, ${mod.collectDuration ?? null}, ${mod.setupDuration ?? null}, ${mod.heap ?? null})`;
|
|
85
|
-
const rows = yield* sql`SELECT last_insert_rowid() as id`;
|
|
86
|
-
ids.push(rows[0].id);
|
|
87
|
-
}
|
|
88
|
-
return ids;
|
|
89
|
-
}).pipe(Effect.annotateLogs("service", "DataStore"), Effect.mapError((e) => new DataStoreError({
|
|
90
|
-
operation: "write",
|
|
91
|
-
table: "test_modules",
|
|
92
|
-
reason: extractSqlReason(e)
|
|
93
|
-
})));
|
|
94
|
-
const writeSuites = (moduleId, suites) => Effect.gen(function* () {
|
|
95
|
-
yield* Effect.logDebug("writeSuites").pipe(Effect.annotateLogs({
|
|
96
|
-
moduleId,
|
|
97
|
-
count: suites.length
|
|
98
|
-
}));
|
|
99
|
-
const ids = [];
|
|
100
|
-
for (const suite of suites) {
|
|
101
|
-
yield* sql`INSERT INTO test_suites (module_id, parent_suite_id, name, full_name, state, mode, concurrent, shuffle, retry, repeats, location_line, location_column) VALUES (${moduleId}, ${suite.parentSuiteId ?? null}, ${suite.name}, ${suite.fullName}, ${suite.state}, ${suite.mode ?? null}, ${boolToInt(suite.concurrent)}, ${boolToInt(suite.shuffle)}, ${suite.retry ?? null}, ${suite.repeats ?? null}, ${suite.locationLine ?? null}, ${suite.locationColumn ?? null})`;
|
|
102
|
-
const rows = yield* sql`SELECT last_insert_rowid() as id`;
|
|
103
|
-
ids.push(rows[0].id);
|
|
104
|
-
}
|
|
105
|
-
return ids;
|
|
106
|
-
}).pipe(Effect.annotateLogs("service", "DataStore"), Effect.mapError((e) => new DataStoreError({
|
|
107
|
-
operation: "write",
|
|
108
|
-
table: "test_suites",
|
|
109
|
-
reason: extractSqlReason(e)
|
|
110
|
-
})));
|
|
111
|
-
const writeTestCases = (moduleId, tests) => Effect.gen(function* () {
|
|
112
|
-
yield* Effect.logDebug("writeTestCases").pipe(Effect.annotateLogs({
|
|
113
|
-
moduleId,
|
|
114
|
-
count: tests.length
|
|
115
|
-
}));
|
|
116
|
-
const ids = [];
|
|
117
|
-
for (const tc of tests) {
|
|
118
|
-
yield* sql`INSERT INTO test_cases (module_id, suite_id, vitest_id, name, full_name, state, classification, duration, start_time, flaky, slow, retry_count, repeat_count, heap, mode, each, fails, concurrent, shuffle, timeout, skip_note, location_line, location_column, created_turn_id) VALUES (${moduleId}, ${tc.suiteId ?? null}, ${tc.vitestId ?? null}, ${tc.name}, ${tc.fullName}, ${tc.state}, ${tc.classification ?? null}, ${tc.duration ?? null}, ${tc.startTime ?? null}, ${boolToInt(tc.flaky)}, ${boolToInt(tc.slow)}, ${tc.retryCount ?? 0}, ${tc.repeatCount ?? 0}, ${tc.heap ?? null}, ${tc.mode ?? null}, ${boolToInt(tc.each)}, ${boolToInt(tc.fails)}, ${boolToInt(tc.concurrent)}, ${boolToInt(tc.shuffle)}, ${tc.timeout ?? null}, ${tc.skipNote ?? null}, ${tc.locationLine ?? null}, ${tc.locationColumn ?? null}, ${tc.createdTurnId ?? null})`;
|
|
119
|
-
const testCaseId = (yield* sql`SELECT last_insert_rowid() as id`)[0].id;
|
|
120
|
-
ids.push(testCaseId);
|
|
121
|
-
if (tc.tags && tc.tags.length > 0) for (const tag of tc.tags) {
|
|
122
|
-
yield* sql`INSERT OR IGNORE INTO tags (name) VALUES (${tag})`;
|
|
123
|
-
const tagRows = yield* sql`SELECT id FROM tags WHERE name = ${tag}`;
|
|
124
|
-
if (tagRows.length > 0) yield* sql`INSERT OR IGNORE INTO test_case_tags (test_case_id, tag_id) VALUES (${testCaseId}, ${tagRows[0].id})`;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
return ids;
|
|
128
|
-
}).pipe(Effect.annotateLogs("service", "DataStore"), Effect.mapError((e) => new DataStoreError({
|
|
129
|
-
operation: "write",
|
|
130
|
-
table: "test_cases",
|
|
131
|
-
reason: extractSqlReason(e)
|
|
132
|
-
})));
|
|
133
|
-
const writeErrors = (runId, errors) => Effect.gen(function* () {
|
|
134
|
-
yield* Effect.logDebug("writeErrors").pipe(Effect.annotateLogs({
|
|
135
|
-
runId,
|
|
136
|
-
count: errors.length
|
|
137
|
-
}));
|
|
138
|
-
for (const err of errors) {
|
|
139
|
-
const message = coerceErrorText(err.message) ?? "<missing message>";
|
|
140
|
-
yield* sql`INSERT INTO test_errors (run_id, test_case_id, test_suite_id, module_id, scope, name, message, diff, actual, expected, stack, cause_error_id, signature_hash, ordinal) VALUES (${runId}, ${err.testCaseId ?? null}, ${err.testSuiteId ?? null}, ${err.moduleId ?? null}, ${err.scope}, ${coerceErrorText(err.name) ?? null}, ${message}, ${coerceErrorText(err.diff) ?? null}, ${coerceErrorText(err.actual) ?? null}, ${coerceErrorText(err.expected) ?? null}, ${coerceErrorText(err.stack) ?? null}, ${err.causeErrorId ?? null}, ${err.signatureHash ?? null}, ${err.ordinal ?? 0})`;
|
|
141
|
-
const stackText = coerceErrorText(err.stack);
|
|
142
|
-
if (err.frames && err.frames.length > 0) {
|
|
143
|
-
const errorId = (yield* sql`SELECT last_insert_rowid() as id`)[0].id;
|
|
144
|
-
for (const frame of err.frames) {
|
|
145
|
-
const fileId = yield* ensureFile(frame.filePath);
|
|
146
|
-
yield* sql`INSERT INTO stack_frames (error_id, ordinal, method, file_id, line, col, source_mapped_line, function_boundary_line) VALUES (${errorId}, ${frame.ordinal}, ${frame.method}, ${fileId}, ${frame.line}, ${frame.col}, ${frame.sourceMappedLine ?? null}, ${frame.functionBoundaryLine ?? null})`;
|
|
147
|
-
}
|
|
148
|
-
} else if (stackText) {
|
|
149
|
-
const errorId = (yield* sql`SELECT last_insert_rowid() as id`)[0].id;
|
|
150
|
-
const frames = [...stackText.matchAll(/at\s+(?:(.+?)\s+)?\(?(.+?):(\d+):(\d+)\)?/g)];
|
|
151
|
-
for (let frameOrdinal = 0; frameOrdinal < frames.length; frameOrdinal++) {
|
|
152
|
-
const m = frames[frameOrdinal];
|
|
153
|
-
const method = m[1] ?? null;
|
|
154
|
-
const filePath = m[2];
|
|
155
|
-
const line = Number.parseInt(m[3], 10);
|
|
156
|
-
const col = Number.parseInt(m[4], 10);
|
|
157
|
-
const fileId = yield* ensureFile(filePath);
|
|
158
|
-
yield* sql`INSERT INTO stack_frames (error_id, ordinal, method, file_id, line, col, source_mapped_line, function_boundary_line) VALUES (${errorId}, ${frameOrdinal}, ${method}, ${fileId}, ${line}, ${col}, NULL, NULL)`;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
}).pipe(Effect.annotateLogs("service", "DataStore"), Effect.mapError((e) => new DataStoreError({
|
|
163
|
-
operation: "write",
|
|
164
|
-
table: "test_errors",
|
|
165
|
-
reason: extractSqlReason(e)
|
|
166
|
-
})));
|
|
167
|
-
const writeAttachments = (owner, attachments) => Effect.gen(function* () {
|
|
168
|
-
for (const att of attachments) {
|
|
169
|
-
const artifactId = "artifactId" in owner ? owner.artifactId : null;
|
|
170
|
-
const annotationId = "annotationId" in owner ? owner.annotationId : null;
|
|
171
|
-
const storedBytes = att.body === void 0 ? 0 : Buffer.byteLength(att.body, "utf8");
|
|
172
|
-
const inline = att.body !== void 0 && Math.max(storedBytes, att.byteSize) <= 65536;
|
|
173
|
-
const body = inline ? att.body : null;
|
|
174
|
-
const bodyEncoding = inline ? att.bodyEncoding ?? null : null;
|
|
175
|
-
yield* sql`INSERT INTO attachments (artifact_id, annotation_id, content_type, path, body, body_encoding, byte_size) VALUES (${artifactId}, ${annotationId}, ${att.contentType ?? null}, ${att.path ?? null}, ${body}, ${bodyEncoding}, ${att.byteSize})`;
|
|
176
|
-
}
|
|
177
|
-
});
|
|
178
|
-
const writeAnnotations = (runId, annotations) => Effect.gen(function* () {
|
|
179
|
-
yield* Effect.logDebug("writeAnnotations").pipe(Effect.annotateLogs({
|
|
180
|
-
runId,
|
|
181
|
-
count: annotations.length
|
|
182
|
-
}));
|
|
183
|
-
for (const anno of annotations) {
|
|
184
|
-
const locationFileId = anno.locationFile !== void 0 ? yield* ensureFile(anno.locationFile) : null;
|
|
185
|
-
yield* sql`INSERT INTO test_annotations (test_case_id, type, message, location_file_id, location_line, location_column) VALUES (${anno.testCaseId}, ${anno.type}, ${anno.message}, ${locationFileId}, ${anno.locationLine ?? null}, ${anno.locationColumn ?? null})`;
|
|
186
|
-
const idRows = yield* sql`SELECT last_insert_rowid() as id`;
|
|
187
|
-
yield* writeAttachments({ annotationId: idRows[0].id }, anno.attachments ?? []);
|
|
188
|
-
}
|
|
189
|
-
}).pipe(Effect.annotateLogs("service", "DataStore"), Effect.mapError((e) => new DataStoreError({
|
|
190
|
-
operation: "write",
|
|
191
|
-
table: "test_annotations",
|
|
192
|
-
reason: extractSqlReason(e)
|
|
193
|
-
})));
|
|
194
|
-
const writeArtifacts = (runId, artifacts) => Effect.gen(function* () {
|
|
195
|
-
yield* Effect.logDebug("writeArtifacts").pipe(Effect.annotateLogs({
|
|
196
|
-
runId,
|
|
197
|
-
count: artifacts.length
|
|
198
|
-
}));
|
|
199
|
-
for (const art of artifacts) {
|
|
200
|
-
const locationFileId = art.locationFile !== void 0 ? yield* ensureFile(art.locationFile) : null;
|
|
201
|
-
yield* sql`INSERT INTO test_artifacts (test_case_id, type, message, data, location_file_id, location_line, location_column) VALUES (${art.testCaseId}, ${art.type}, ${art.message ?? null}, ${art.data ?? null}, ${locationFileId}, ${art.locationLine ?? null}, ${art.locationColumn ?? null})`;
|
|
202
|
-
const idRows = yield* sql`SELECT last_insert_rowid() as id`;
|
|
203
|
-
yield* writeAttachments({ artifactId: idRows[0].id }, art.attachments ?? []);
|
|
204
|
-
}
|
|
205
|
-
}).pipe(Effect.annotateLogs("service", "DataStore"), Effect.mapError((e) => new DataStoreError({
|
|
206
|
-
operation: "write",
|
|
207
|
-
table: "test_artifacts",
|
|
208
|
-
reason: extractSqlReason(e)
|
|
209
|
-
})));
|
|
210
|
-
const writeCoverage = (runId, coverage) => Effect.gen(function* () {
|
|
211
|
-
yield* Effect.logDebug("writeCoverage").pipe(Effect.annotateLogs({
|
|
212
|
-
runId,
|
|
213
|
-
count: coverage.length
|
|
214
|
-
}));
|
|
215
|
-
for (const cov of coverage) {
|
|
216
|
-
const tier = cov.tier ?? "below_threshold";
|
|
217
|
-
yield* sql`INSERT INTO file_coverage (run_id, file_id, statements, branches, functions, lines, uncovered_lines, tier) VALUES (${runId}, ${cov.fileId}, ${cov.statements}, ${cov.branches}, ${cov.functions}, ${cov.lines}, ${cov.uncoveredLines ?? null}, ${tier})`;
|
|
218
|
-
}
|
|
219
|
-
}).pipe(Effect.annotateLogs("service", "DataStore"), Effect.mapError((e) => new DataStoreError({
|
|
220
|
-
operation: "write",
|
|
221
|
-
table: "file_coverage",
|
|
222
|
-
reason: extractSqlReason(e)
|
|
223
|
-
})));
|
|
224
|
-
const writeHistory = (project, fullName, modulePath, runId, timestamp, state, duration, flaky, retryCount, errorMessage) => Effect.gen(function* () {
|
|
225
|
-
yield* Effect.logDebug("writeHistory").pipe(Effect.annotateLogs({
|
|
226
|
-
project,
|
|
227
|
-
modulePath,
|
|
228
|
-
runId
|
|
229
|
-
}));
|
|
230
|
-
yield* sql`INSERT INTO test_history (run_id, project, module_path, full_name, timestamp, state, duration, flaky, retry_count, error_message) VALUES (${runId}, ${project}, ${modulePath}, ${fullName}, ${timestamp}, ${state}, ${duration}, ${flaky ? 1 : 0}, ${retryCount}, ${errorMessage}) ON CONFLICT(project, module_path, full_name, timestamp) DO UPDATE SET run_id = excluded.run_id, state = excluded.state, duration = excluded.duration, flaky = excluded.flaky, retry_count = excluded.retry_count, error_message = excluded.error_message`;
|
|
231
|
-
yield* sql`DELETE FROM test_history WHERE id NOT IN (SELECT id FROM test_history WHERE project = ${project} AND module_path = ${modulePath} AND full_name = ${fullName} ORDER BY timestamp DESC LIMIT 10) AND project = ${project} AND module_path = ${modulePath} AND full_name = ${fullName}`;
|
|
232
|
-
}).pipe(Effect.annotateLogs("service", "DataStore"), Effect.mapError((e) => new DataStoreError({
|
|
233
|
-
operation: "write",
|
|
234
|
-
table: "test_history",
|
|
235
|
-
reason: extractSqlReason(e)
|
|
236
|
-
})));
|
|
237
|
-
const writeBaselines = (baselines) => Effect.gen(function* () {
|
|
238
|
-
yield* Effect.logDebug("writeBaselines").pipe(Effect.annotateLogs({ updatedAt: baselines.updatedAt }));
|
|
239
|
-
const { updatedAt, global: g, patterns = [] } = baselines;
|
|
240
|
-
const metrics = [
|
|
241
|
-
"lines",
|
|
242
|
-
"functions",
|
|
243
|
-
"branches",
|
|
244
|
-
"statements"
|
|
245
|
-
];
|
|
246
|
-
for (const metric of metrics) {
|
|
247
|
-
const value = g[metric];
|
|
248
|
-
if (value !== void 0 && Number.isFinite(value)) yield* sql`INSERT INTO coverage_baselines (project, kind, metric, value, pattern, updated_at) VALUES ('__global__', 'baseline', ${metric}, ${value}, '', ${updatedAt}) ON CONFLICT (project, kind, metric, pattern) DO UPDATE SET value = ${value}, updated_at = ${updatedAt}`;
|
|
249
|
-
}
|
|
250
|
-
for (const [pattern, thresholds] of patterns) for (const metric of metrics) {
|
|
251
|
-
const value = thresholds[metric];
|
|
252
|
-
if (value !== void 0 && Number.isFinite(value)) yield* sql`INSERT INTO coverage_baselines (project, kind, metric, value, pattern, updated_at) VALUES ('__global__', 'baseline', ${metric}, ${value}, ${pattern}, ${updatedAt}) ON CONFLICT (project, kind, metric, pattern) DO UPDATE SET value = ${value}, updated_at = ${updatedAt}`;
|
|
253
|
-
}
|
|
254
|
-
}).pipe(Effect.annotateLogs("service", "DataStore"), Effect.mapError((e) => new DataStoreError({
|
|
255
|
-
operation: "write",
|
|
256
|
-
table: "coverage_baselines",
|
|
257
|
-
reason: extractSqlReason(e)
|
|
258
|
-
})));
|
|
259
|
-
/**
|
|
260
|
-
* Shared upsert for the `coverage_baselines` table's non-baseline
|
|
261
|
-
* `kind` rows (`threshold` / `target`). Distinct from `writeBaselines`
|
|
262
|
-
* so the ratcheted high-water mark never collides with the resolved,
|
|
263
|
-
* enforced Vitest thresholds or the aspirational coverageTargets
|
|
264
|
-
* (issue #237).
|
|
265
|
-
*/
|
|
266
|
-
const writeCoveragePolicy = (kind, policy) => sql.withTransaction(Effect.gen(function* () {
|
|
267
|
-
yield* Effect.logDebug("writeCoveragePolicy").pipe(Effect.annotateLogs({ kind }));
|
|
268
|
-
const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
269
|
-
const { global: g, patterns = [] } = policy;
|
|
270
|
-
const metrics = [
|
|
271
|
-
"lines",
|
|
272
|
-
"functions",
|
|
273
|
-
"branches",
|
|
274
|
-
"statements"
|
|
275
|
-
];
|
|
276
|
-
yield* sql`DELETE FROM coverage_baselines WHERE project = '__global__' AND kind = ${kind}`;
|
|
277
|
-
for (const metric of metrics) {
|
|
278
|
-
const value = g[metric];
|
|
279
|
-
if (value !== void 0 && Number.isFinite(value)) yield* sql`INSERT INTO coverage_baselines (project, kind, metric, value, pattern, updated_at) VALUES ('__global__', ${kind}, ${metric}, ${value}, '', ${updatedAt})`;
|
|
280
|
-
}
|
|
281
|
-
for (const [pattern, thresholds] of patterns) for (const metric of metrics) {
|
|
282
|
-
const value = thresholds[metric];
|
|
283
|
-
if (value !== void 0 && Number.isFinite(value)) yield* sql`INSERT INTO coverage_baselines (project, kind, metric, value, pattern, updated_at) VALUES ('__global__', ${kind}, ${metric}, ${value}, ${pattern}, ${updatedAt})`;
|
|
284
|
-
}
|
|
285
|
-
})).pipe(Effect.annotateLogs("service", "DataStore"), Effect.mapError((e) => new DataStoreError({
|
|
286
|
-
operation: "write",
|
|
287
|
-
table: "coverage_baselines",
|
|
288
|
-
reason: extractSqlReason(e)
|
|
289
|
-
})));
|
|
290
|
-
const writeThresholds = (thresholds) => writeCoveragePolicy("threshold", thresholds);
|
|
291
|
-
const writeTargets = (targets) => writeCoveragePolicy("target", targets);
|
|
292
|
-
const writeTrends = (project, runId, entry) => Effect.gen(function* () {
|
|
293
|
-
yield* Effect.logDebug("writeTrends").pipe(Effect.annotateLogs({
|
|
294
|
-
project,
|
|
295
|
-
runId
|
|
296
|
-
}));
|
|
297
|
-
yield* sql`INSERT OR REPLACE INTO coverage_trends (run_id, project, timestamp, lines, functions, branches, statements, direction, targets_hash) VALUES (${runId}, ${project}, ${entry.timestamp}, ${entry.coverage.lines}, ${entry.coverage.functions}, ${entry.coverage.branches}, ${entry.coverage.statements}, ${entry.direction}, ${entry.targetsHash ?? null})`;
|
|
298
|
-
yield* sql`DELETE FROM coverage_trends WHERE id NOT IN (SELECT id FROM coverage_trends WHERE project = ${project} ORDER BY timestamp DESC LIMIT 50) AND project = ${project}`;
|
|
299
|
-
}).pipe(Effect.annotateLogs("service", "DataStore"), Effect.mapError((e) => new DataStoreError({
|
|
300
|
-
operation: "write",
|
|
301
|
-
table: "coverage_trends",
|
|
302
|
-
reason: extractSqlReason(e)
|
|
303
|
-
})));
|
|
304
|
-
const writeSourceMap = (sourceFilePath, testModuleId, mappingType) => Effect.gen(function* () {
|
|
305
|
-
yield* Effect.logDebug("writeSourceMap").pipe(Effect.annotateLogs({
|
|
306
|
-
sourceFilePath,
|
|
307
|
-
testModuleId,
|
|
308
|
-
mappingType
|
|
309
|
-
}));
|
|
310
|
-
const sourceFileId = yield* ensureFile(sourceFilePath);
|
|
311
|
-
yield* sql`INSERT OR IGNORE INTO source_test_map (source_file_id, test_module_id, mapping_type) VALUES (${sourceFileId}, ${testModuleId}, ${mappingType})`;
|
|
312
|
-
}).pipe(Effect.annotateLogs("service", "DataStore"), Effect.mapError((e) => new DataStoreError({
|
|
313
|
-
operation: "write",
|
|
314
|
-
table: "source_test_map",
|
|
315
|
-
reason: extractSqlReason(e)
|
|
316
|
-
})));
|
|
317
|
-
const writeNote = (note) => Effect.gen(function* () {
|
|
318
|
-
yield* Effect.logDebug("writeNote").pipe(Effect.annotateLogs({
|
|
319
|
-
scope: note.scope,
|
|
320
|
-
project: note.project ?? null
|
|
321
|
-
}));
|
|
322
|
-
yield* sql`INSERT INTO notes (title, content, scope, project, test_full_name, module_path, parent_note_id, created_by, expires_at, pinned) VALUES (${note.title}, ${note.content}, ${note.scope}, ${note.project ?? null}, ${note.testFullName ?? null}, ${note.modulePath ?? null}, ${note.parentNoteId ?? null}, ${note.createdBy ?? null}, ${note.expiresAt ?? null}, ${note.pinned ? 1 : 0})`;
|
|
323
|
-
return (yield* sql`SELECT last_insert_rowid() as id`)[0].id;
|
|
324
|
-
}).pipe(Effect.annotateLogs("service", "DataStore"), Effect.mapError((e) => new DataStoreError({
|
|
325
|
-
operation: "write",
|
|
326
|
-
table: "notes",
|
|
327
|
-
reason: extractSqlReason(e)
|
|
328
|
-
})));
|
|
329
|
-
const updateNote = (id, fields) => Effect.gen(function* () {
|
|
330
|
-
yield* Effect.logDebug("updateNote").pipe(Effect.annotateLogs({ id }));
|
|
331
|
-
const setClauses = [];
|
|
332
|
-
const values = [];
|
|
333
|
-
if (fields.title !== void 0) {
|
|
334
|
-
setClauses.push("title = ?");
|
|
335
|
-
values.push(fields.title);
|
|
336
|
-
}
|
|
337
|
-
if (fields.content !== void 0) {
|
|
338
|
-
setClauses.push("content = ?");
|
|
339
|
-
values.push(fields.content);
|
|
340
|
-
}
|
|
341
|
-
if (fields.scope !== void 0) {
|
|
342
|
-
setClauses.push("scope = ?");
|
|
343
|
-
values.push(fields.scope);
|
|
344
|
-
}
|
|
345
|
-
if (fields.project !== void 0) {
|
|
346
|
-
setClauses.push("project = ?");
|
|
347
|
-
values.push(fields.project);
|
|
348
|
-
}
|
|
349
|
-
if (fields.testFullName !== void 0) {
|
|
350
|
-
setClauses.push("test_full_name = ?");
|
|
351
|
-
values.push(fields.testFullName);
|
|
352
|
-
}
|
|
353
|
-
if (fields.modulePath !== void 0) {
|
|
354
|
-
setClauses.push("module_path = ?");
|
|
355
|
-
values.push(fields.modulePath);
|
|
356
|
-
}
|
|
357
|
-
if (fields.parentNoteId !== void 0) {
|
|
358
|
-
setClauses.push("parent_note_id = ?");
|
|
359
|
-
values.push(fields.parentNoteId);
|
|
360
|
-
}
|
|
361
|
-
if (fields.createdBy !== void 0) {
|
|
362
|
-
setClauses.push("created_by = ?");
|
|
363
|
-
values.push(fields.createdBy);
|
|
364
|
-
}
|
|
365
|
-
if (fields.expiresAt !== void 0) {
|
|
366
|
-
setClauses.push("expires_at = ?");
|
|
367
|
-
values.push(fields.expiresAt);
|
|
368
|
-
}
|
|
369
|
-
if (fields.pinned !== void 0) {
|
|
370
|
-
setClauses.push("pinned = ?");
|
|
371
|
-
values.push(fields.pinned ? 1 : 0);
|
|
372
|
-
}
|
|
373
|
-
if (setClauses.length === 0) return;
|
|
374
|
-
setClauses.push("updated_at = datetime('now')");
|
|
375
|
-
yield* sql.unsafe(`UPDATE notes SET ${setClauses.join(", ")} WHERE id = ?`, [...values, id]);
|
|
376
|
-
}).pipe(Effect.annotateLogs("service", "DataStore"), Effect.mapError((e) => new DataStoreError({
|
|
377
|
-
operation: "write",
|
|
378
|
-
table: "notes",
|
|
379
|
-
reason: extractSqlReason(e)
|
|
380
|
-
})));
|
|
381
|
-
const deleteNote = (id) => Effect.gen(function* () {
|
|
382
|
-
yield* Effect.logDebug("deleteNote").pipe(Effect.annotateLogs({ id }));
|
|
383
|
-
yield* sql`DELETE FROM notes WHERE id = ${id}`;
|
|
384
|
-
}).pipe(Effect.annotateLogs("service", "DataStore"), Effect.mapError((e) => new DataStoreError({
|
|
385
|
-
operation: "write",
|
|
386
|
-
table: "notes",
|
|
387
|
-
reason: extractSqlReason(e)
|
|
388
|
-
})));
|
|
389
|
-
const writeSession = (input) => Effect.gen(function* () {
|
|
390
|
-
yield* Effect.logDebug("writeSession").pipe(Effect.annotateLogs({ chatId: input.chatId }));
|
|
391
|
-
yield* sql`INSERT INTO sessions (chat_id, project, cwd, agent_kind, agent_type, parent_session_id, triage_was_non_empty, started_at, conversation_id) VALUES (${input.chatId}, ${input.project}, ${input.cwd}, ${input.agentKind}, ${input.agentType ?? null}, ${input.parentSessionId ?? null}, ${boolToInt(input.triageWasNonEmpty) ?? 0}, ${input.startedAt}, ${input.conversationId ?? null})`;
|
|
392
|
-
return (yield* sql`SELECT id FROM sessions WHERE chat_id = ${input.chatId}`)[0].id;
|
|
393
|
-
}).pipe(Effect.annotateLogs("service", "DataStore"), Effect.mapError((e) => new DataStoreError({
|
|
394
|
-
operation: "write",
|
|
395
|
-
table: "sessions",
|
|
396
|
-
reason: extractSqlReason(e)
|
|
397
|
-
})));
|
|
398
|
-
const upsertSession = (input) => Effect.gen(function* () {
|
|
399
|
-
yield* Effect.logDebug("upsertSession").pipe(Effect.annotateLogs({ chatId: input.chatId }));
|
|
400
|
-
yield* sql`INSERT INTO sessions (chat_id, project, cwd, agent_kind, agent_type, parent_session_id, triage_was_non_empty, started_at, conversation_id) VALUES (${input.chatId}, ${input.project}, ${input.cwd}, ${input.agentKind}, ${input.agentType ?? null}, ${input.parentSessionId ?? null}, ${boolToInt(input.triageWasNonEmpty) ?? 0}, ${input.startedAt}, ${input.conversationId ?? null}) ON CONFLICT(chat_id) DO NOTHING`;
|
|
401
|
-
return (yield* sql`SELECT id FROM sessions WHERE chat_id = ${input.chatId}`)[0].id;
|
|
402
|
-
}).pipe(Effect.annotateLogs("service", "DataStore"), Effect.mapError((e) => new DataStoreError({
|
|
403
|
-
operation: "write",
|
|
404
|
-
table: "sessions",
|
|
405
|
-
reason: extractSqlReason(e)
|
|
406
|
-
})));
|
|
407
|
-
const setSessionConversationIdIfNull = (input) => Effect.gen(function* () {
|
|
408
|
-
yield* Effect.logDebug("setSessionConversationIdIfNull").pipe(Effect.annotateLogs(input));
|
|
409
|
-
yield* sql`
|
|
410
|
-
UPDATE sessions SET conversation_id = ${input.conversationId}
|
|
411
|
-
WHERE id = ${input.sessionId} AND conversation_id IS NULL
|
|
412
|
-
`;
|
|
413
|
-
}).pipe(Effect.annotateLogs("service", "DataStore"), Effect.mapError((e) => new DataStoreError({
|
|
414
|
-
operation: "write",
|
|
415
|
-
table: "sessions",
|
|
416
|
-
reason: extractSqlReason(e)
|
|
417
|
-
})));
|
|
418
|
-
const writeTurn = (input) => sql.withTransaction(Effect.gen(function* () {
|
|
419
|
-
yield* Effect.logDebug("writeTurn").pipe(Effect.annotateLogs({
|
|
420
|
-
sessionId: input.sessionId,
|
|
421
|
-
type: input.type
|
|
422
|
-
}));
|
|
423
|
-
if (input.turnNo !== void 0) yield* sql`INSERT INTO turns (session_id, turn_no, type, payload, occurred_at) VALUES (${input.sessionId}, ${input.turnNo}, ${input.type}, ${input.payload}, ${input.occurredAt})`;
|
|
424
|
-
else yield* sql`INSERT INTO turns (session_id, turn_no, type, payload, occurred_at) SELECT ${input.sessionId}, COALESCE(MAX(turn_no), 0) + 1, ${input.type}, ${input.payload}, ${input.occurredAt} FROM turns WHERE session_id = ${input.sessionId}`;
|
|
425
|
-
const turnId = (yield* sql`SELECT last_insert_rowid() as id`)[0].id;
|
|
426
|
-
if (input.type === "file_edit" || input.type === "tool_result") {
|
|
427
|
-
const payload = yield* Effect.try({
|
|
428
|
-
try: () => JSON.parse(input.payload),
|
|
429
|
-
catch: (e) => new DataStoreError({
|
|
430
|
-
operation: "write",
|
|
431
|
-
table: input.type === "file_edit" ? "file_edits" : "tool_invocations",
|
|
432
|
-
reason: `invalid turn payload JSON: ${e.message}`
|
|
433
|
-
})
|
|
434
|
-
});
|
|
435
|
-
if (input.type === "file_edit") {
|
|
436
|
-
const filePath = payload.file_path;
|
|
437
|
-
yield* sql`INSERT OR IGNORE INTO files (path) VALUES (${filePath})`;
|
|
438
|
-
const fileId = (yield* sql`SELECT id FROM files WHERE path = ${filePath}`)[0].id;
|
|
439
|
-
yield* sql`
|
|
440
|
-
INSERT INTO file_edits (turn_id, file_id, edit_kind, lines_added, lines_removed, diff)
|
|
441
|
-
VALUES (
|
|
442
|
-
${turnId},
|
|
443
|
-
${fileId},
|
|
444
|
-
${payload.edit_kind},
|
|
445
|
-
${payload.lines_added ?? null},
|
|
446
|
-
${payload.lines_removed ?? null},
|
|
447
|
-
${payload.diff ?? null}
|
|
448
|
-
)
|
|
449
|
-
`;
|
|
450
|
-
} else {
|
|
451
|
-
const rawToolName = payload.tool_name;
|
|
452
|
-
const toolName = typeof rawToolName === "string" && rawToolName.startsWith("mcp__") ? rawToolName.split("__").at(-1) ?? rawToolName : rawToolName;
|
|
453
|
-
yield* sql`
|
|
454
|
-
INSERT INTO tool_invocations (turn_id, tool_name, params_hash, result_summary, duration_ms, success)
|
|
455
|
-
VALUES (
|
|
456
|
-
${turnId},
|
|
457
|
-
${toolName},
|
|
458
|
-
${null},
|
|
459
|
-
${payload.result_summary ?? null},
|
|
460
|
-
${payload.duration_ms ?? null},
|
|
461
|
-
${boolToInt(payload.success) ?? 0}
|
|
462
|
-
)
|
|
463
|
-
`;
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
return turnId;
|
|
467
|
-
})).pipe(Effect.annotateLogs("service", "DataStore"), Effect.mapError((e) => e instanceof DataStoreError ? e : new DataStoreError({
|
|
468
|
-
operation: "write",
|
|
469
|
-
table: "turns",
|
|
470
|
-
reason: extractSqlReason(e)
|
|
471
|
-
})));
|
|
472
|
-
const endSession = (chatId, endedAt, endReason) => Effect.gen(function* () {
|
|
473
|
-
yield* Effect.logDebug("endSession").pipe(Effect.annotateLogs({
|
|
474
|
-
chatId,
|
|
475
|
-
endReason
|
|
476
|
-
}));
|
|
477
|
-
yield* sql`UPDATE sessions SET ended_at = ${endedAt}, end_reason = ${endReason} WHERE chat_id = ${chatId}`;
|
|
478
|
-
if ((yield* sql`SELECT changes() as changes`)[0].changes === 0) return yield* Effect.fail(new DataStoreError({
|
|
479
|
-
operation: "write",
|
|
480
|
-
table: "sessions",
|
|
481
|
-
reason: `unknown chat_id: ${chatId}`
|
|
482
|
-
}));
|
|
483
|
-
}).pipe(Effect.annotateLogs("service", "DataStore"), Effect.mapError((e) => e instanceof DataStoreError ? e : new DataStoreError({
|
|
484
|
-
operation: "write",
|
|
485
|
-
table: "sessions",
|
|
486
|
-
reason: extractSqlReason(e)
|
|
487
|
-
})));
|
|
488
|
-
const writeFailureSignature = (input) => Effect.gen(function* () {
|
|
489
|
-
yield* Effect.logDebug("writeFailureSignature").pipe(Effect.annotateLogs({
|
|
490
|
-
signatureHash: input.signatureHash,
|
|
491
|
-
runId: input.runId
|
|
492
|
-
}));
|
|
493
|
-
yield* sql`INSERT INTO failure_signatures (signature_hash, first_seen_run_id, first_seen_at, last_seen_at, occurrence_count) VALUES (${input.signatureHash}, ${input.runId}, ${input.seenAt}, ${input.seenAt}, 1) ON CONFLICT(signature_hash) DO UPDATE SET occurrence_count = occurrence_count + 1, last_seen_at = excluded.last_seen_at`;
|
|
494
|
-
}).pipe(Effect.annotateLogs("service", "DataStore"), Effect.mapError((e) => new DataStoreError({
|
|
495
|
-
operation: "write",
|
|
496
|
-
table: "failure_signatures",
|
|
497
|
-
reason: extractSqlReason(e)
|
|
498
|
-
})));
|
|
499
|
-
const writeHypothesis = (input) => Effect.gen(function* () {
|
|
500
|
-
yield* Effect.logDebug("writeHypothesis").pipe(Effect.annotateLogs({ sessionId: input.sessionId }));
|
|
501
|
-
yield* sql`INSERT INTO hypotheses (session_id, content, created_turn_id, cited_test_error_id, cited_stack_frame_id) VALUES (${input.sessionId}, ${input.content}, ${input.createdTurnId ?? null}, ${input.citedTestErrorId ?? null}, ${input.citedStackFrameId ?? null})`;
|
|
502
|
-
return (yield* sql`SELECT last_insert_rowid() as id`)[0].id;
|
|
503
|
-
}).pipe(Effect.annotateLogs("service", "DataStore"), Effect.mapError((e) => new DataStoreError({
|
|
504
|
-
operation: "write",
|
|
505
|
-
table: "hypotheses",
|
|
506
|
-
reason: extractSqlReason(e)
|
|
507
|
-
})));
|
|
508
|
-
const validateHypothesis = (input) => Effect.gen(function* () {
|
|
509
|
-
yield* Effect.logDebug("validateHypothesis").pipe(Effect.annotateLogs({
|
|
510
|
-
id: input.id,
|
|
511
|
-
outcome: input.outcome
|
|
512
|
-
}));
|
|
513
|
-
yield* sql`UPDATE hypotheses SET validation_outcome = ${input.outcome}, validated_at = ${input.validatedAt}, validated_turn_id = ${input.validatedTurnId ?? null} WHERE id = ${input.id}`;
|
|
514
|
-
if ((yield* sql`SELECT changes() as changes`)[0].changes === 0) return yield* Effect.fail(new DataStoreError({
|
|
515
|
-
operation: "write",
|
|
516
|
-
table: "hypotheses",
|
|
517
|
-
reason: `unknown hypothesis id: ${input.id}`
|
|
518
|
-
}));
|
|
519
|
-
}).pipe(Effect.annotateLogs("service", "DataStore"), Effect.mapError((e) => e instanceof DataStoreError ? e : new DataStoreError({
|
|
520
|
-
operation: "write",
|
|
521
|
-
table: "hypotheses",
|
|
522
|
-
reason: extractSqlReason(e)
|
|
523
|
-
})));
|
|
524
|
-
const writeTddTask = (input) => sql.withTransaction(Effect.gen(function* () {
|
|
525
|
-
yield* Effect.logDebug("writeTddTask").pipe(Effect.annotateLogs({
|
|
526
|
-
sessionId: input.sessionId,
|
|
527
|
-
goal: input.goal
|
|
528
|
-
}));
|
|
529
|
-
const rows = yield* sql`
|
|
530
|
-
INSERT OR IGNORE INTO tdd_tasks (session_id, goal, started_at, parent_tdd_task_id, run_id)
|
|
531
|
-
VALUES (
|
|
532
|
-
${input.sessionId},
|
|
533
|
-
${input.goal},
|
|
534
|
-
${input.startedAt},
|
|
535
|
-
${input.parentTddTaskId ?? null},
|
|
536
|
-
${input.runId ?? null}
|
|
537
|
-
)
|
|
538
|
-
RETURNING id
|
|
539
|
-
`;
|
|
540
|
-
let tddTaskId;
|
|
541
|
-
let isNewTask;
|
|
542
|
-
if (rows.length > 0) {
|
|
543
|
-
tddTaskId = rows[0].id;
|
|
544
|
-
isNewTask = true;
|
|
545
|
-
} else {
|
|
546
|
-
const existing = yield* sql`
|
|
547
|
-
SELECT id, goal FROM tdd_tasks
|
|
548
|
-
WHERE session_id = ${input.sessionId} AND run_id = ${input.runId}
|
|
549
|
-
LIMIT 1
|
|
550
|
-
`;
|
|
551
|
-
if (existing.length === 0) return yield* Effect.fail(/* @__PURE__ */ new Error("writeTddTask: INSERT was ignored but no existing row found"));
|
|
552
|
-
if (existing[0].goal !== input.goal) return yield* Effect.fail(/* @__PURE__ */ new Error(`writeTddTask: runId conflict — existing task has goal "${existing[0].goal}", caller provided "${input.goal}"`));
|
|
553
|
-
tddTaskId = existing[0].id;
|
|
554
|
-
isNewTask = false;
|
|
555
|
-
}
|
|
556
|
-
if (isNewTask) yield* sql`
|
|
557
|
-
INSERT INTO tdd_phases
|
|
558
|
-
(tdd_task_id, behavior_id, phase, started_at, transition_reason, parent_phase_id)
|
|
559
|
-
VALUES
|
|
560
|
-
(
|
|
561
|
-
${tddTaskId},
|
|
562
|
-
${null},
|
|
563
|
-
${"spike"},
|
|
564
|
-
${input.startedAt},
|
|
565
|
-
${"opened by tdd_task_start"},
|
|
566
|
-
${null}
|
|
567
|
-
)
|
|
568
|
-
`;
|
|
569
|
-
return tddTaskId;
|
|
570
|
-
})).pipe(Effect.annotateLogs("service", "DataStore"), Effect.mapError((e) => new DataStoreError({
|
|
571
|
-
operation: "write",
|
|
572
|
-
table: "tdd_tasks",
|
|
573
|
-
reason: extractSqlReason(e)
|
|
574
|
-
})));
|
|
575
|
-
const endTddTask = (input) => Effect.gen(function* () {
|
|
576
|
-
yield* Effect.logDebug("endTddTask").pipe(Effect.annotateLogs({
|
|
577
|
-
id: input.id,
|
|
578
|
-
outcome: input.outcome
|
|
579
|
-
}));
|
|
580
|
-
yield* sql`
|
|
581
|
-
UPDATE tdd_tasks
|
|
582
|
-
SET ended_at = ${input.endedAt},
|
|
583
|
-
outcome = ${input.outcome},
|
|
584
|
-
summary_note_id = ${input.summaryNoteId ?? null}
|
|
585
|
-
WHERE id = ${input.id}
|
|
586
|
-
`;
|
|
587
|
-
}).pipe(Effect.annotateLogs("service", "DataStore"), Effect.mapError((e) => new DataStoreError({
|
|
588
|
-
operation: "write",
|
|
589
|
-
table: "tdd_tasks",
|
|
590
|
-
reason: extractSqlReason(e)
|
|
591
|
-
})));
|
|
592
|
-
const ensureTddTaskOpen = (tddTaskId) => Effect.gen(function* () {
|
|
593
|
-
const rows = yield* sql`
|
|
594
|
-
SELECT ended_at, outcome FROM tdd_tasks WHERE id = ${tddTaskId}
|
|
595
|
-
`.pipe(Effect.mapError((e) => new DataStoreError({
|
|
596
|
-
operation: "read",
|
|
597
|
-
table: "tdd_tasks",
|
|
598
|
-
reason: extractSqlReason(e)
|
|
599
|
-
})));
|
|
600
|
-
if (rows.length === 0) return yield* Effect.fail(new TddTaskNotFoundError({
|
|
601
|
-
id: tddTaskId,
|
|
602
|
-
reason: "no tdd_tasks row for that id"
|
|
603
|
-
}));
|
|
604
|
-
const row = rows[0];
|
|
605
|
-
if (row.ended_at !== null) return yield* Effect.fail(new TddTaskAlreadyEndedError({
|
|
606
|
-
id: tddTaskId,
|
|
607
|
-
endedAt: row.ended_at,
|
|
608
|
-
outcome: row.outcome ?? "abandoned"
|
|
609
|
-
}));
|
|
610
|
-
});
|
|
611
|
-
const ensureTddTaskExists = (tddTaskId) => Effect.gen(function* () {
|
|
612
|
-
if ((yield* sql`SELECT id FROM tdd_tasks WHERE id = ${tddTaskId}`.pipe(Effect.mapError((e) => new DataStoreError({
|
|
613
|
-
operation: "read",
|
|
614
|
-
table: "tdd_tasks",
|
|
615
|
-
reason: extractSqlReason(e)
|
|
616
|
-
})))).length === 0) return yield* Effect.fail(new TddTaskNotFoundError({
|
|
617
|
-
id: tddTaskId,
|
|
618
|
-
reason: "no tdd_tasks row for that id"
|
|
619
|
-
}));
|
|
620
|
-
});
|
|
621
|
-
const createGoal = (input) => Effect.gen(function* () {
|
|
622
|
-
yield* Effect.logDebug("createGoal").pipe(Effect.annotateLogs({
|
|
623
|
-
tddTaskId: input.tddTaskId,
|
|
624
|
-
goal: input.goal
|
|
625
|
-
}));
|
|
626
|
-
yield* ensureTddTaskOpen(input.tddTaskId);
|
|
627
|
-
const rows = yield* sql`
|
|
628
|
-
INSERT INTO tdd_session_goals (session_id, ordinal, goal)
|
|
629
|
-
SELECT ${input.tddTaskId},
|
|
630
|
-
COALESCE(MAX(ordinal), -1) + 1,
|
|
631
|
-
${input.goal}
|
|
632
|
-
FROM tdd_session_goals
|
|
633
|
-
WHERE session_id = ${input.tddTaskId}
|
|
634
|
-
RETURNING id, session_id, ordinal, goal, status, created_at
|
|
635
|
-
`.pipe(Effect.mapError((e) => new DataStoreError({
|
|
636
|
-
operation: "write",
|
|
637
|
-
table: "tdd_session_goals",
|
|
638
|
-
reason: extractSqlReason(e)
|
|
639
|
-
})));
|
|
640
|
-
return goalRowFromDb(rows[0]);
|
|
641
|
-
}).pipe(Effect.annotateLogs("service", "DataStore"));
|
|
642
|
-
const getGoal = (id) => Effect.gen(function* () {
|
|
643
|
-
const rows = yield* sql`
|
|
644
|
-
SELECT id, session_id, ordinal, goal, status, created_at
|
|
645
|
-
FROM tdd_session_goals
|
|
646
|
-
WHERE id = ${id}
|
|
647
|
-
`;
|
|
648
|
-
return rows.length === 0 ? Option.none() : Option.some(goalRowFromDb(rows[0]));
|
|
649
|
-
}).pipe(Effect.annotateLogs("service", "DataStore"), Effect.mapError((e) => new DataStoreError({
|
|
650
|
-
operation: "read",
|
|
651
|
-
table: "tdd_session_goals",
|
|
652
|
-
reason: extractSqlReason(e)
|
|
653
|
-
})));
|
|
654
|
-
const updateGoal = (input) => Effect.gen(function* () {
|
|
655
|
-
yield* Effect.logDebug("updateGoal").pipe(Effect.annotateLogs({ id: input.id }));
|
|
656
|
-
const existing = yield* sql`
|
|
657
|
-
SELECT id, session_id, ordinal, goal, status, created_at
|
|
658
|
-
FROM tdd_session_goals
|
|
659
|
-
WHERE id = ${input.id}
|
|
660
|
-
`.pipe(Effect.mapError((e) => new DataStoreError({
|
|
661
|
-
operation: "read",
|
|
662
|
-
table: "tdd_session_goals",
|
|
663
|
-
reason: extractSqlReason(e)
|
|
664
|
-
})));
|
|
665
|
-
if (existing.length === 0) return yield* Effect.fail(new GoalNotFoundError({
|
|
666
|
-
id: input.id,
|
|
667
|
-
reason: "no tdd_session_goals row for that id"
|
|
668
|
-
}));
|
|
669
|
-
const current = existing[0];
|
|
670
|
-
yield* ensureTddTaskOpen(current.session_id).pipe(Effect.catchTag("TddTaskNotFoundError", (e) => Effect.fail(new DataStoreError({
|
|
671
|
-
operation: "read",
|
|
672
|
-
table: "tdd_tasks",
|
|
673
|
-
reason: `FK integrity violation: goal ${input.id} references missing tdd_tasks row ${e.id}`
|
|
674
|
-
}))));
|
|
675
|
-
const fromStatus = current.status;
|
|
676
|
-
const toStatus = input.status ?? fromStatus;
|
|
677
|
-
if (input.status !== void 0 && !isLegalLifecycleTransition(fromStatus, input.status)) return yield* Effect.fail(new IllegalStatusTransitionError({
|
|
678
|
-
entity: "goal",
|
|
679
|
-
id: input.id,
|
|
680
|
-
from: fromStatus,
|
|
681
|
-
to: input.status,
|
|
682
|
-
reason: "transition forbidden by goal lifecycle rules"
|
|
683
|
-
}));
|
|
684
|
-
const newGoalText = input.goal ?? current.goal;
|
|
685
|
-
yield* sql`
|
|
686
|
-
UPDATE tdd_session_goals
|
|
687
|
-
SET goal = ${newGoalText},
|
|
688
|
-
status = ${toStatus}
|
|
689
|
-
WHERE id = ${input.id}
|
|
690
|
-
`.pipe(Effect.mapError((e) => new DataStoreError({
|
|
691
|
-
operation: "write",
|
|
692
|
-
table: "tdd_session_goals",
|
|
693
|
-
reason: extractSqlReason(e)
|
|
694
|
-
})));
|
|
695
|
-
return {
|
|
696
|
-
id: current.id,
|
|
697
|
-
sessionId: current.session_id,
|
|
698
|
-
ordinal: current.ordinal,
|
|
699
|
-
goal: newGoalText,
|
|
700
|
-
status: toStatus,
|
|
701
|
-
createdAt: current.created_at
|
|
702
|
-
};
|
|
703
|
-
}).pipe(Effect.annotateLogs("service", "DataStore"));
|
|
704
|
-
const deleteGoal = (id) => Effect.gen(function* () {
|
|
705
|
-
yield* Effect.logDebug("deleteGoal").pipe(Effect.annotateLogs({ id }));
|
|
706
|
-
if ((yield* sql`SELECT id FROM tdd_session_goals WHERE id = ${id}`.pipe(Effect.mapError((e) => new DataStoreError({
|
|
707
|
-
operation: "read",
|
|
708
|
-
table: "tdd_session_goals",
|
|
709
|
-
reason: extractSqlReason(e)
|
|
710
|
-
})))).length === 0) return yield* Effect.fail(new GoalNotFoundError({
|
|
711
|
-
id,
|
|
712
|
-
reason: "no tdd_session_goals row for that id"
|
|
713
|
-
}));
|
|
714
|
-
yield* sql`DELETE FROM tdd_session_goals WHERE id = ${id}`.pipe(Effect.mapError((e) => new DataStoreError({
|
|
715
|
-
operation: "write",
|
|
716
|
-
table: "tdd_session_goals",
|
|
717
|
-
reason: extractSqlReason(e)
|
|
718
|
-
})));
|
|
719
|
-
}).pipe(Effect.annotateLogs("service", "DataStore"));
|
|
720
|
-
const listGoalsByTddTask = (tddTaskId) => Effect.gen(function* () {
|
|
721
|
-
yield* ensureTddTaskExists(tddTaskId);
|
|
722
|
-
return (yield* sql`
|
|
723
|
-
SELECT id, session_id, ordinal, goal, status, created_at
|
|
724
|
-
FROM tdd_session_goals
|
|
725
|
-
WHERE session_id = ${tddTaskId}
|
|
726
|
-
ORDER BY ordinal
|
|
727
|
-
`.pipe(Effect.mapError((e) => new DataStoreError({
|
|
728
|
-
operation: "read",
|
|
729
|
-
table: "tdd_session_goals",
|
|
730
|
-
reason: extractSqlReason(e)
|
|
731
|
-
})))).map(goalRowFromDb);
|
|
732
|
-
}).pipe(Effect.annotateLogs("service", "DataStore"));
|
|
733
|
-
const ensureGoalOpenAndTaskOpen = (goalId) => Effect.gen(function* () {
|
|
734
|
-
const goals = yield* sql`
|
|
735
|
-
SELECT id, session_id, status FROM tdd_session_goals WHERE id = ${goalId}
|
|
736
|
-
`.pipe(Effect.mapError((e) => new DataStoreError({
|
|
737
|
-
operation: "read",
|
|
738
|
-
table: "tdd_session_goals",
|
|
739
|
-
reason: extractSqlReason(e)
|
|
740
|
-
})));
|
|
741
|
-
if (goals.length === 0) return yield* Effect.fail(new GoalNotFoundError({
|
|
742
|
-
id: goalId,
|
|
743
|
-
reason: "no tdd_session_goals row"
|
|
744
|
-
}));
|
|
745
|
-
const goal = goals[0];
|
|
746
|
-
yield* ensureTddTaskOpen(goal.session_id).pipe(Effect.catchTag("TddTaskNotFoundError", (e) => Effect.fail(new DataStoreError({
|
|
747
|
-
operation: "read",
|
|
748
|
-
table: "tdd_tasks",
|
|
749
|
-
reason: `FK integrity violation: goal ${goalId} references missing tdd_tasks row ${e.id}`
|
|
750
|
-
}))));
|
|
751
|
-
if (goal.status === "done" || goal.status === "abandoned") return yield* Effect.fail(new IllegalStatusTransitionError({
|
|
752
|
-
entity: "goal",
|
|
753
|
-
id: goalId,
|
|
754
|
-
from: goal.status,
|
|
755
|
-
to: "in_progress",
|
|
756
|
-
reason: "cannot create a behavior under a closed goal"
|
|
757
|
-
}));
|
|
758
|
-
return {
|
|
759
|
-
goalTddTaskId: goal.session_id,
|
|
760
|
-
goalStatus: goal.status
|
|
761
|
-
};
|
|
762
|
-
});
|
|
763
|
-
const writeBehaviorDependencies = (behaviorId, goalId, depIds) => Effect.gen(function* () {
|
|
764
|
-
const uniqueDepIds = Array.from(new Set(depIds));
|
|
765
|
-
if (uniqueDepIds.length === 0) return;
|
|
766
|
-
const verified = yield* sql`
|
|
767
|
-
SELECT id FROM tdd_session_behaviors
|
|
768
|
-
WHERE goal_id = ${goalId} AND id IN ${sql.in(uniqueDepIds)}
|
|
769
|
-
`.pipe(Effect.mapError((e) => new DataStoreError({
|
|
770
|
-
operation: "read",
|
|
771
|
-
table: "tdd_session_behaviors",
|
|
772
|
-
reason: extractSqlReason(e)
|
|
773
|
-
})));
|
|
774
|
-
const verifiedIds = new Set(verified.map((r) => r.id));
|
|
775
|
-
for (const depId of uniqueDepIds) if (!verifiedIds.has(depId)) return yield* Effect.fail(new BehaviorNotFoundError({
|
|
776
|
-
id: depId,
|
|
777
|
-
reason: `dependency id ${depId} does not belong to goal ${goalId}`
|
|
778
|
-
}));
|
|
779
|
-
for (const depId of uniqueDepIds) yield* sql`
|
|
780
|
-
INSERT INTO tdd_behavior_dependencies (behavior_id, depends_on_id)
|
|
781
|
-
VALUES (${behaviorId}, ${depId})
|
|
782
|
-
`.pipe(Effect.mapError((e) => new DataStoreError({
|
|
783
|
-
operation: "write",
|
|
784
|
-
table: "tdd_behavior_dependencies",
|
|
785
|
-
reason: extractSqlReason(e)
|
|
786
|
-
})));
|
|
787
|
-
});
|
|
788
|
-
const createBehavior = (input) => sql.withTransaction(Effect.gen(function* () {
|
|
789
|
-
yield* Effect.logDebug("createBehavior").pipe(Effect.annotateLogs({
|
|
790
|
-
goalId: input.goalId,
|
|
791
|
-
behavior: input.behavior
|
|
792
|
-
}));
|
|
793
|
-
yield* ensureGoalOpenAndTaskOpen(input.goalId);
|
|
794
|
-
const beh = (yield* sql`
|
|
795
|
-
INSERT INTO tdd_session_behaviors (goal_id, ordinal, behavior, suggested_test_name)
|
|
796
|
-
SELECT ${input.goalId},
|
|
797
|
-
COALESCE(MAX(ordinal), -1) + 1,
|
|
798
|
-
${input.behavior},
|
|
799
|
-
${input.suggestedTestName ?? null}
|
|
800
|
-
FROM tdd_session_behaviors
|
|
801
|
-
WHERE goal_id = ${input.goalId}
|
|
802
|
-
RETURNING id, goal_id, ordinal, behavior, suggested_test_name, status, created_at
|
|
803
|
-
`.pipe(Effect.mapError((e) => new DataStoreError({
|
|
804
|
-
operation: "write",
|
|
805
|
-
table: "tdd_session_behaviors",
|
|
806
|
-
reason: extractSqlReason(e)
|
|
807
|
-
}))))[0];
|
|
808
|
-
if (input.dependsOnBehaviorIds && input.dependsOnBehaviorIds.length > 0) yield* writeBehaviorDependencies(beh.id, input.goalId, input.dependsOnBehaviorIds);
|
|
809
|
-
return behaviorRowFromDb(beh);
|
|
810
|
-
})).pipe(Effect.annotateLogs("service", "DataStore"), Effect.mapError((e) => e instanceof DataStoreError || e instanceof GoalNotFoundError || e instanceof BehaviorNotFoundError || e instanceof TddTaskAlreadyEndedError || e instanceof IllegalStatusTransitionError ? e : new DataStoreError({
|
|
811
|
-
operation: "write",
|
|
812
|
-
table: "tdd_session_behaviors",
|
|
813
|
-
reason: extractSqlReason(e)
|
|
814
|
-
})));
|
|
815
|
-
const getBehavior = (id) => Effect.gen(function* () {
|
|
816
|
-
const rows = yield* sql`
|
|
817
|
-
SELECT id, goal_id, ordinal, behavior, suggested_test_name, status, created_at
|
|
818
|
-
FROM tdd_session_behaviors
|
|
819
|
-
WHERE id = ${id}
|
|
820
|
-
`;
|
|
821
|
-
return rows.length === 0 ? Option.none() : Option.some(behaviorRowFromDb(rows[0]));
|
|
822
|
-
}).pipe(Effect.annotateLogs("service", "DataStore"), Effect.mapError((e) => new DataStoreError({
|
|
823
|
-
operation: "read",
|
|
824
|
-
table: "tdd_session_behaviors",
|
|
825
|
-
reason: extractSqlReason(e)
|
|
826
|
-
})));
|
|
827
|
-
const updateBehavior = (input) => sql.withTransaction(Effect.gen(function* () {
|
|
828
|
-
yield* Effect.logDebug("updateBehavior").pipe(Effect.annotateLogs({ id: input.id }));
|
|
829
|
-
const existing = yield* sql`
|
|
830
|
-
SELECT id, goal_id, ordinal, behavior, suggested_test_name, status, created_at
|
|
831
|
-
FROM tdd_session_behaviors
|
|
832
|
-
WHERE id = ${input.id}
|
|
833
|
-
`.pipe(Effect.mapError((e) => new DataStoreError({
|
|
834
|
-
operation: "read",
|
|
835
|
-
table: "tdd_session_behaviors",
|
|
836
|
-
reason: extractSqlReason(e)
|
|
837
|
-
})));
|
|
838
|
-
if (existing.length === 0) return yield* Effect.fail(new BehaviorNotFoundError({
|
|
839
|
-
id: input.id,
|
|
840
|
-
reason: "no tdd_session_behaviors row for that id"
|
|
841
|
-
}));
|
|
842
|
-
const current = existing[0];
|
|
843
|
-
const goalRows = yield* sql`
|
|
844
|
-
SELECT session_id FROM tdd_session_goals WHERE id = ${current.goal_id}
|
|
845
|
-
`.pipe(Effect.mapError((e) => new DataStoreError({
|
|
846
|
-
operation: "read",
|
|
847
|
-
table: "tdd_session_goals",
|
|
848
|
-
reason: extractSqlReason(e)
|
|
849
|
-
})));
|
|
850
|
-
if (goalRows.length === 0) return yield* Effect.fail(new DataStoreError({
|
|
851
|
-
operation: "read",
|
|
852
|
-
table: "tdd_session_goals",
|
|
853
|
-
reason: `FK integrity violation: behavior ${input.id} references missing tdd_session_goals row ${current.goal_id}`
|
|
854
|
-
}));
|
|
855
|
-
yield* ensureTddTaskOpen(goalRows[0].session_id).pipe(Effect.catchTag("TddTaskNotFoundError", (e) => Effect.fail(new DataStoreError({
|
|
856
|
-
operation: "read",
|
|
857
|
-
table: "tdd_tasks",
|
|
858
|
-
reason: `FK integrity violation: goal ${current.goal_id} references missing tdd_tasks row ${e.id}`
|
|
859
|
-
}))));
|
|
860
|
-
const fromStatus = current.status;
|
|
861
|
-
if (input.status !== void 0 && !isLegalLifecycleTransition(fromStatus, input.status)) return yield* Effect.fail(new IllegalStatusTransitionError({
|
|
862
|
-
entity: "behavior",
|
|
863
|
-
id: input.id,
|
|
864
|
-
from: fromStatus,
|
|
865
|
-
to: input.status,
|
|
866
|
-
reason: "transition forbidden by behavior lifecycle rules"
|
|
867
|
-
}));
|
|
868
|
-
const newBehaviorText = input.behavior ?? current.behavior;
|
|
869
|
-
const newStatus = input.status ?? fromStatus;
|
|
870
|
-
const newSuggested = input.suggestedTestName === void 0 ? current.suggested_test_name : input.suggestedTestName;
|
|
871
|
-
yield* sql`
|
|
872
|
-
UPDATE tdd_session_behaviors
|
|
873
|
-
SET behavior = ${newBehaviorText},
|
|
874
|
-
suggested_test_name = ${newSuggested},
|
|
875
|
-
status = ${newStatus}
|
|
876
|
-
WHERE id = ${input.id}
|
|
877
|
-
`.pipe(Effect.mapError((e) => new DataStoreError({
|
|
878
|
-
operation: "write",
|
|
879
|
-
table: "tdd_session_behaviors",
|
|
880
|
-
reason: extractSqlReason(e)
|
|
881
|
-
})));
|
|
882
|
-
if (input.dependsOnBehaviorIds !== void 0) {
|
|
883
|
-
yield* sql`DELETE FROM tdd_behavior_dependencies WHERE behavior_id = ${input.id}`.pipe(Effect.mapError((e) => new DataStoreError({
|
|
884
|
-
operation: "write",
|
|
885
|
-
table: "tdd_behavior_dependencies",
|
|
886
|
-
reason: extractSqlReason(e)
|
|
887
|
-
})));
|
|
888
|
-
if (input.dependsOnBehaviorIds.length > 0) yield* writeBehaviorDependencies(input.id, current.goal_id, input.dependsOnBehaviorIds);
|
|
889
|
-
}
|
|
890
|
-
return {
|
|
891
|
-
id: current.id,
|
|
892
|
-
goalId: current.goal_id,
|
|
893
|
-
ordinal: current.ordinal,
|
|
894
|
-
behavior: newBehaviorText,
|
|
895
|
-
suggestedTestName: newSuggested,
|
|
896
|
-
status: newStatus,
|
|
897
|
-
createdAt: current.created_at
|
|
898
|
-
};
|
|
899
|
-
})).pipe(Effect.annotateLogs("service", "DataStore"), Effect.mapError((e) => e instanceof DataStoreError || e instanceof BehaviorNotFoundError || e instanceof TddTaskAlreadyEndedError || e instanceof IllegalStatusTransitionError ? e : new DataStoreError({
|
|
900
|
-
operation: "write",
|
|
901
|
-
table: "tdd_session_behaviors",
|
|
902
|
-
reason: extractSqlReason(e)
|
|
903
|
-
})));
|
|
904
|
-
const deleteBehavior = (id) => Effect.gen(function* () {
|
|
905
|
-
yield* Effect.logDebug("deleteBehavior").pipe(Effect.annotateLogs({ id }));
|
|
906
|
-
if ((yield* sql`SELECT id FROM tdd_session_behaviors WHERE id = ${id}`.pipe(Effect.mapError((e) => new DataStoreError({
|
|
907
|
-
operation: "read",
|
|
908
|
-
table: "tdd_session_behaviors",
|
|
909
|
-
reason: extractSqlReason(e)
|
|
910
|
-
})))).length === 0) return yield* Effect.fail(new BehaviorNotFoundError({
|
|
911
|
-
id,
|
|
912
|
-
reason: "no tdd_session_behaviors row for that id"
|
|
913
|
-
}));
|
|
914
|
-
yield* sql`DELETE FROM tdd_session_behaviors WHERE id = ${id}`.pipe(Effect.mapError((e) => new DataStoreError({
|
|
915
|
-
operation: "write",
|
|
916
|
-
table: "tdd_session_behaviors",
|
|
917
|
-
reason: extractSqlReason(e)
|
|
918
|
-
})));
|
|
919
|
-
}).pipe(Effect.annotateLogs("service", "DataStore"));
|
|
920
|
-
const ensureGoalExists = (goalId) => Effect.gen(function* () {
|
|
921
|
-
if ((yield* sql`SELECT id FROM tdd_session_goals WHERE id = ${goalId}`.pipe(Effect.mapError((e) => new DataStoreError({
|
|
922
|
-
operation: "read",
|
|
923
|
-
table: "tdd_session_goals",
|
|
924
|
-
reason: extractSqlReason(e)
|
|
925
|
-
})))).length === 0) return yield* Effect.fail(new GoalNotFoundError({
|
|
926
|
-
id: goalId,
|
|
927
|
-
reason: "no tdd_session_goals row"
|
|
928
|
-
}));
|
|
929
|
-
});
|
|
930
|
-
const listBehaviorsByGoal = (goalId) => Effect.gen(function* () {
|
|
931
|
-
yield* ensureGoalExists(goalId);
|
|
932
|
-
return (yield* sql`
|
|
933
|
-
SELECT id, goal_id, ordinal, behavior, suggested_test_name, status, created_at
|
|
934
|
-
FROM tdd_session_behaviors
|
|
935
|
-
WHERE goal_id = ${goalId}
|
|
936
|
-
ORDER BY ordinal
|
|
937
|
-
`.pipe(Effect.mapError((e) => new DataStoreError({
|
|
938
|
-
operation: "read",
|
|
939
|
-
table: "tdd_session_behaviors",
|
|
940
|
-
reason: extractSqlReason(e)
|
|
941
|
-
})))).map(behaviorRowFromDb);
|
|
942
|
-
}).pipe(Effect.annotateLogs("service", "DataStore"));
|
|
943
|
-
const listBehaviorsByTddTask = (tddTaskId) => Effect.gen(function* () {
|
|
944
|
-
yield* ensureTddTaskExists(tddTaskId);
|
|
945
|
-
return (yield* sql`
|
|
946
|
-
SELECT b.id, b.goal_id, b.ordinal, b.behavior, b.suggested_test_name, b.status, b.created_at
|
|
947
|
-
FROM tdd_session_behaviors b
|
|
948
|
-
JOIN tdd_session_goals g ON g.id = b.goal_id
|
|
949
|
-
WHERE g.session_id = ${tddTaskId}
|
|
950
|
-
ORDER BY g.ordinal, b.ordinal
|
|
951
|
-
`.pipe(Effect.mapError((e) => new DataStoreError({
|
|
952
|
-
operation: "read",
|
|
953
|
-
table: "tdd_session_behaviors",
|
|
954
|
-
reason: extractSqlReason(e)
|
|
955
|
-
})))).map(behaviorRowFromDb);
|
|
956
|
-
}).pipe(Effect.annotateLogs("service", "DataStore"));
|
|
957
|
-
const writeTddArtifact = (input) => Effect.gen(function* () {
|
|
958
|
-
yield* Effect.logDebug("writeTddArtifact").pipe(Effect.annotateLogs({
|
|
959
|
-
phaseId: input.phaseId,
|
|
960
|
-
artifactKind: input.artifactKind
|
|
961
|
-
}));
|
|
962
|
-
const truncatedDiff = input.diffExcerpt !== void 0 && input.diffExcerpt.length > 4096 ? input.diffExcerpt.slice(0, 4096) : input.diffExcerpt ?? null;
|
|
963
|
-
return (yield* sql`
|
|
964
|
-
INSERT INTO tdd_artifacts
|
|
965
|
-
(phase_id, artifact_kind, file_id, test_case_id, test_run_id,
|
|
966
|
-
test_first_failure_run_id, diff_excerpt, recorded_at, suite)
|
|
967
|
-
VALUES
|
|
968
|
-
(
|
|
969
|
-
${input.phaseId},
|
|
970
|
-
${input.artifactKind},
|
|
971
|
-
${input.fileId ?? null},
|
|
972
|
-
${input.testCaseId ?? null},
|
|
973
|
-
${input.testRunId ?? null},
|
|
974
|
-
${input.testFirstFailureRunId ?? null},
|
|
975
|
-
${truncatedDiff},
|
|
976
|
-
${input.recordedAt},
|
|
977
|
-
${input.suite ?? "vitest"}
|
|
978
|
-
)
|
|
979
|
-
RETURNING id
|
|
980
|
-
`)[0].id;
|
|
981
|
-
}).pipe(Effect.annotateLogs("service", "DataStore"), Effect.mapError((e) => new DataStoreError({
|
|
982
|
-
operation: "write",
|
|
983
|
-
table: "tdd_artifacts",
|
|
984
|
-
reason: extractSqlReason(e)
|
|
985
|
-
})));
|
|
986
|
-
const writeTddPhase = (input) => Effect.gen(function* () {
|
|
987
|
-
yield* Effect.logDebug("writeTddPhase").pipe(Effect.annotateLogs({
|
|
988
|
-
tddTaskId: input.tddTaskId,
|
|
989
|
-
phase: input.phase
|
|
990
|
-
}));
|
|
991
|
-
const open = yield* sql`
|
|
992
|
-
SELECT id FROM tdd_phases
|
|
993
|
-
WHERE tdd_task_id = ${input.tddTaskId} AND ended_at IS NULL
|
|
994
|
-
ORDER BY started_at DESC LIMIT 1
|
|
995
|
-
`;
|
|
996
|
-
const previousPhaseId = open.length === 0 ? null : open[0].id;
|
|
997
|
-
if (previousPhaseId !== null) yield* sql`
|
|
998
|
-
UPDATE tdd_phases SET ended_at = ${input.startedAt}
|
|
999
|
-
WHERE id = ${previousPhaseId}
|
|
1000
|
-
`;
|
|
1001
|
-
return {
|
|
1002
|
-
id: (yield* sql`
|
|
1003
|
-
INSERT INTO tdd_phases
|
|
1004
|
-
(tdd_task_id, behavior_id, phase, started_at, transition_reason, parent_phase_id)
|
|
1005
|
-
VALUES
|
|
1006
|
-
(
|
|
1007
|
-
${input.tddTaskId},
|
|
1008
|
-
${input.behaviorId ?? null},
|
|
1009
|
-
${input.phase},
|
|
1010
|
-
${input.startedAt},
|
|
1011
|
-
${input.transitionReason ?? null},
|
|
1012
|
-
${input.parentPhaseId ?? null}
|
|
1013
|
-
)
|
|
1014
|
-
RETURNING id
|
|
1015
|
-
`)[0].id,
|
|
1016
|
-
previousPhaseId
|
|
1017
|
-
};
|
|
1018
|
-
}).pipe(Effect.annotateLogs("service", "DataStore"), Effect.mapError((e) => new DataStoreError({
|
|
1019
|
-
operation: "write",
|
|
1020
|
-
table: "tdd_phases",
|
|
1021
|
-
reason: extractSqlReason(e)
|
|
1022
|
-
})));
|
|
1023
|
-
const writeCommit = (input) => Effect.gen(function* () {
|
|
1024
|
-
yield* Effect.logDebug("writeCommit").pipe(Effect.annotateLogs({ sha: input.sha }));
|
|
1025
|
-
yield* sql`
|
|
1026
|
-
INSERT INTO commits (sha, parent_sha, message, author, committed_at, branch)
|
|
1027
|
-
VALUES (
|
|
1028
|
-
${input.sha},
|
|
1029
|
-
${input.parentSha ?? null},
|
|
1030
|
-
${input.message ?? null},
|
|
1031
|
-
${input.author ?? null},
|
|
1032
|
-
${input.committedAt ?? null},
|
|
1033
|
-
${input.branch ?? null}
|
|
1034
|
-
)
|
|
1035
|
-
ON CONFLICT(sha) DO NOTHING
|
|
1036
|
-
`;
|
|
1037
|
-
}).pipe(Effect.annotateLogs("service", "DataStore"), Effect.mapError((e) => new DataStoreError({
|
|
1038
|
-
operation: "write",
|
|
1039
|
-
table: "commits",
|
|
1040
|
-
reason: extractSqlReason(e)
|
|
1041
|
-
})));
|
|
1042
|
-
const writeRunChangedFiles = (input) => Effect.gen(function* () {
|
|
1043
|
-
yield* Effect.logDebug("writeRunChangedFiles").pipe(Effect.annotateLogs({
|
|
1044
|
-
runId: input.runId,
|
|
1045
|
-
count: input.files.length
|
|
1046
|
-
}));
|
|
1047
|
-
for (const file of input.files) {
|
|
1048
|
-
const fileId = yield* ensureFile(file.filePath);
|
|
1049
|
-
yield* sql`
|
|
1050
|
-
INSERT INTO run_changed_files (run_id, file_id, change_kind, commit_sha)
|
|
1051
|
-
VALUES (${input.runId}, ${fileId}, ${file.changeKind}, ${file.commitSha ?? null})
|
|
1052
|
-
ON CONFLICT(run_id, file_id) DO UPDATE SET
|
|
1053
|
-
change_kind = excluded.change_kind,
|
|
1054
|
-
commit_sha = excluded.commit_sha
|
|
1055
|
-
`;
|
|
1056
|
-
}
|
|
1057
|
-
}).pipe(Effect.annotateLogs("service", "DataStore"), Effect.mapError((e) => new DataStoreError({
|
|
1058
|
-
operation: "write",
|
|
1059
|
-
table: "run_changed_files",
|
|
1060
|
-
reason: extractSqlReason(e)
|
|
1061
|
-
})));
|
|
1062
|
-
const recordIdempotentResponse = (input) => Effect.gen(function* () {
|
|
1063
|
-
yield* Effect.logDebug("recordIdempotentResponse").pipe(Effect.annotateLogs({
|
|
1064
|
-
procedurePath: input.procedurePath,
|
|
1065
|
-
key: input.key
|
|
1066
|
-
}));
|
|
1067
|
-
yield* sql`INSERT INTO mcp_idempotent_responses (procedure_path, key, result_json, created_at) VALUES (${input.procedurePath}, ${input.key}, ${input.resultJson}, ${input.createdAt}) ON CONFLICT(procedure_path, key) DO NOTHING`;
|
|
1068
|
-
}).pipe(Effect.annotateLogs("service", "DataStore"), Effect.mapError((e) => new DataStoreError({
|
|
1069
|
-
operation: "write",
|
|
1070
|
-
table: "mcp_idempotent_responses",
|
|
1071
|
-
reason: extractSqlReason(e)
|
|
1072
|
-
})));
|
|
1073
|
-
const pruneSessions = (keepRecent) => Effect.gen(function* () {
|
|
1074
|
-
yield* Effect.logDebug("pruneSessions").pipe(Effect.annotateLogs({ keepRecent }));
|
|
1075
|
-
const cutoffRows = yield* sql`
|
|
1076
|
-
SELECT started_at FROM sessions ORDER BY started_at DESC LIMIT 1 OFFSET ${keepRecent}
|
|
1077
|
-
`;
|
|
1078
|
-
if (cutoffRows.length === 0) return {
|
|
1079
|
-
affectedSessions: 0,
|
|
1080
|
-
prunedTurns: 0
|
|
1081
|
-
};
|
|
1082
|
-
const cutoff = cutoffRows[0].started_at;
|
|
1083
|
-
const prunedTurns = (yield* sql`
|
|
1084
|
-
SELECT COUNT(*) AS count FROM turns
|
|
1085
|
-
WHERE session_id IN (SELECT id FROM sessions WHERE started_at <= ${cutoff})
|
|
1086
|
-
`)[0]?.count ?? 0;
|
|
1087
|
-
const affectedSessions = (yield* sql`
|
|
1088
|
-
SELECT COUNT(*) AS count FROM sessions WHERE started_at <= ${cutoff}
|
|
1089
|
-
`)[0]?.count ?? 0;
|
|
1090
|
-
yield* sql`
|
|
1091
|
-
DELETE FROM turns WHERE session_id IN (
|
|
1092
|
-
SELECT id FROM sessions WHERE started_at <= ${cutoff}
|
|
1093
|
-
)
|
|
1094
|
-
`;
|
|
1095
|
-
return {
|
|
1096
|
-
affectedSessions,
|
|
1097
|
-
prunedTurns
|
|
1098
|
-
};
|
|
1099
|
-
}).pipe(Effect.annotateLogs("service", "DataStore"), Effect.mapError((e) => new DataStoreError({
|
|
1100
|
-
operation: "write",
|
|
1101
|
-
table: "turns",
|
|
1102
|
-
reason: extractSqlReason(e)
|
|
1103
|
-
})));
|
|
1104
|
-
const associateLatestRunWithSession = (input) => Effect.gen(function* () {
|
|
1105
|
-
yield* Effect.logDebug("associateLatestRunWithSession").pipe(Effect.annotateLogs({
|
|
1106
|
-
chatId: input.chatId,
|
|
1107
|
-
invocationMethod: input.invocationMethod
|
|
1108
|
-
}));
|
|
1109
|
-
yield* sql`
|
|
1110
|
-
INSERT OR IGNORE INTO run_triggers (run_id, trigger, invocation_method, agent_session_id)
|
|
1111
|
-
SELECT r.id, 'agent', ${input.invocationMethod}, s.id
|
|
1112
|
-
FROM (SELECT id FROM test_runs ORDER BY id DESC LIMIT 1) r
|
|
1113
|
-
CROSS JOIN (SELECT id FROM sessions WHERE chat_id = ${input.chatId} LIMIT 1) s
|
|
1114
|
-
`;
|
|
1115
|
-
}).pipe(Effect.annotateLogs("service", "DataStore"), Effect.mapError((e) => new DataStoreError({
|
|
1116
|
-
operation: "write",
|
|
1117
|
-
table: "run_triggers",
|
|
1118
|
-
reason: extractSqlReason(e)
|
|
1119
|
-
})));
|
|
1120
|
-
const backfillTestCaseTurns = (chatId) => Effect.gen(function* () {
|
|
1121
|
-
yield* Effect.logDebug("backfillTestCaseTurns").pipe(Effect.annotateLogs({ chatId }));
|
|
1122
|
-
yield* sql`
|
|
1123
|
-
UPDATE test_cases
|
|
1124
|
-
SET created_turn_id = (
|
|
1125
|
-
SELECT t.id
|
|
1126
|
-
FROM turns t
|
|
1127
|
-
JOIN file_edits fe ON fe.turn_id = t.id
|
|
1128
|
-
JOIN files f_edit ON fe.file_id = f_edit.id
|
|
1129
|
-
JOIN sessions s ON t.session_id = s.id
|
|
1130
|
-
WHERE s.chat_id = ${chatId}
|
|
1131
|
-
AND EXISTS (
|
|
1132
|
-
SELECT 1
|
|
1133
|
-
FROM test_modules tm
|
|
1134
|
-
JOIN files f_mod ON f_mod.id = tm.file_id
|
|
1135
|
-
WHERE tm.id = test_cases.module_id
|
|
1136
|
-
AND (
|
|
1137
|
-
f_edit.path = f_mod.path
|
|
1138
|
-
OR f_edit.path LIKE '%/' || f_mod.path
|
|
1139
|
-
OR f_mod.path LIKE '%/' || f_edit.path
|
|
1140
|
-
)
|
|
1141
|
-
)
|
|
1142
|
-
ORDER BY t.occurred_at DESC
|
|
1143
|
-
LIMIT 1
|
|
1144
|
-
)
|
|
1145
|
-
WHERE test_cases.created_turn_id IS NULL
|
|
1146
|
-
AND test_cases.module_id IN (
|
|
1147
|
-
SELECT id FROM test_modules
|
|
1148
|
-
WHERE run_id = (SELECT id FROM test_runs ORDER BY id DESC LIMIT 1)
|
|
1149
|
-
)
|
|
1150
|
-
`;
|
|
1151
|
-
return (yield* sql`SELECT changes() AS n`)[0]?.n ?? 0;
|
|
1152
|
-
}).pipe(Effect.annotateLogs("service", "DataStore"), Effect.mapError((e) => new DataStoreError({
|
|
1153
|
-
operation: "write",
|
|
1154
|
-
table: "test_cases",
|
|
1155
|
-
reason: extractSqlReason(e)
|
|
1156
|
-
})));
|
|
1157
|
-
const registerAgent = (input) => Effect.gen(function* () {
|
|
1158
|
-
yield* Effect.logDebug("registerAgent").pipe(Effect.annotateLogs({
|
|
1159
|
-
sessionId: input.sessionId,
|
|
1160
|
-
agentType: input.agentType
|
|
1161
|
-
}));
|
|
1162
|
-
const existing = yield* sql`
|
|
1163
|
-
SELECT agent_id FROM agents WHERE session_id = ${input.sessionId} AND idempotency_key = ${input.idempotencyKey}
|
|
1164
|
-
`.pipe(Effect.mapError((e) => new DataStoreError({
|
|
1165
|
-
operation: "read",
|
|
1166
|
-
table: "agents",
|
|
1167
|
-
reason: extractSqlReason(e)
|
|
1168
|
-
})));
|
|
1169
|
-
if (existing.length > 0) return new IdempotencyHit({ existingAgentId: existing[0].agent_id });
|
|
1170
|
-
if (input.parentAgentId !== null) {
|
|
1171
|
-
const parentRows = yield* sql`
|
|
1172
|
-
SELECT session_id FROM agents WHERE agent_id = ${input.parentAgentId}
|
|
1173
|
-
`.pipe(Effect.mapError((e) => new DataStoreError({
|
|
1174
|
-
operation: "read",
|
|
1175
|
-
table: "agents",
|
|
1176
|
-
reason: extractSqlReason(e)
|
|
1177
|
-
})));
|
|
1178
|
-
if (parentRows.length === 0) return yield* Effect.fail(new RegistrationConflictError({ reason: `parent agent ${input.parentAgentId} does not exist` }));
|
|
1179
|
-
const registeringParentSessionId = (yield* sql`
|
|
1180
|
-
SELECT parent_session_id FROM sessions WHERE id = ${input.sessionId} LIMIT 1
|
|
1181
|
-
`.pipe(Effect.mapError((e) => new DataStoreError({
|
|
1182
|
-
operation: "read",
|
|
1183
|
-
table: "sessions",
|
|
1184
|
-
reason: extractSqlReason(e)
|
|
1185
|
-
}))))[0]?.parent_session_id ?? null;
|
|
1186
|
-
const parentSession = parentRows[0].session_id;
|
|
1187
|
-
if (parentSession !== input.sessionId && parentSession !== registeringParentSessionId) return yield* Effect.fail(new RegistrationConflictError({ reason: `parent agent ${input.parentAgentId} belongs to session ${parentSession}, which is neither the registering session ${input.sessionId} nor its parent session ${registeringParentSessionId ?? "(none)"}` }));
|
|
1188
|
-
}
|
|
1189
|
-
const agentId = input.agentId ?? randomUUID();
|
|
1190
|
-
yield* sql`
|
|
1191
|
-
INSERT INTO agents (
|
|
1192
|
-
agent_id, session_id, parent_agent_id, conversation_id, agent_type,
|
|
1193
|
-
started_at, ended_at, start_git_branch, start_git_commit_sha, start_worktree_dir,
|
|
1194
|
-
idempotency_key
|
|
1195
|
-
) VALUES (
|
|
1196
|
-
${agentId}, ${input.sessionId}, ${input.parentAgentId}, ${input.conversationId},
|
|
1197
|
-
${input.agentType}, ${input.startedAt}, ${null}, ${input.startGitBranch ?? null},
|
|
1198
|
-
${input.startGitCommitSha ?? null}, ${input.startWorktreeDir ?? null}, ${input.idempotencyKey}
|
|
1199
|
-
)
|
|
1200
|
-
`.pipe(Effect.mapError((e) => new DataStoreError({
|
|
1201
|
-
operation: "write",
|
|
1202
|
-
table: "agents",
|
|
1203
|
-
reason: extractSqlReason(e)
|
|
1204
|
-
})));
|
|
1205
|
-
return new Agent({
|
|
1206
|
-
agentId,
|
|
1207
|
-
sessionId: input.sessionId,
|
|
1208
|
-
parentAgentId: input.parentAgentId,
|
|
1209
|
-
conversationId: input.conversationId,
|
|
1210
|
-
agentType: input.agentType,
|
|
1211
|
-
startedAt: input.startedAt,
|
|
1212
|
-
endedAt: null,
|
|
1213
|
-
startGitBranch: input.startGitBranch ?? null,
|
|
1214
|
-
startGitCommitSha: input.startGitCommitSha ?? null,
|
|
1215
|
-
startWorktreeDir: input.startWorktreeDir ?? null,
|
|
1216
|
-
idempotencyKey: input.idempotencyKey
|
|
1217
|
-
});
|
|
1218
|
-
}).pipe(Effect.annotateLogs("service", "DataStore"));
|
|
1219
|
-
const endAgent = (agentId, endedAt) => Effect.gen(function* () {
|
|
1220
|
-
yield* Effect.logDebug("endAgent").pipe(Effect.annotateLogs({ agentId }));
|
|
1221
|
-
yield* sql`UPDATE agents SET ended_at = ${endedAt} WHERE agent_id = ${agentId}`.pipe(Effect.mapError((e) => new DataStoreError({
|
|
1222
|
-
operation: "write",
|
|
1223
|
-
table: "agents",
|
|
1224
|
-
reason: extractSqlReason(e)
|
|
1225
|
-
})));
|
|
1226
|
-
if (((yield* sql`SELECT changes() AS n`.pipe(Effect.mapError((e) => new DataStoreError({
|
|
1227
|
-
operation: "read",
|
|
1228
|
-
table: "agents",
|
|
1229
|
-
reason: extractSqlReason(e)
|
|
1230
|
-
}))))[0]?.n ?? 0) === 0) return yield* Effect.fail(new AgentNotFoundError({ agentId }));
|
|
1231
|
-
}).pipe(Effect.annotateLogs("service", "DataStore"));
|
|
1232
|
-
return {
|
|
1233
|
-
ensureFile,
|
|
1234
|
-
writeSettings,
|
|
1235
|
-
writeRun,
|
|
1236
|
-
writeModules,
|
|
1237
|
-
writeSuites,
|
|
1238
|
-
writeTestCases,
|
|
1239
|
-
writeErrors,
|
|
1240
|
-
writeAnnotations,
|
|
1241
|
-
writeArtifacts,
|
|
1242
|
-
writeCoverage,
|
|
1243
|
-
writeHistory,
|
|
1244
|
-
writeBaselines,
|
|
1245
|
-
writeThresholds,
|
|
1246
|
-
writeTargets,
|
|
1247
|
-
writeTrends,
|
|
1248
|
-
writeSourceMap,
|
|
1249
|
-
writeNote,
|
|
1250
|
-
updateNote,
|
|
1251
|
-
deleteNote,
|
|
1252
|
-
writeSession,
|
|
1253
|
-
upsertSession,
|
|
1254
|
-
setSessionConversationIdIfNull,
|
|
1255
|
-
writeTurn,
|
|
1256
|
-
writeFailureSignature,
|
|
1257
|
-
endSession,
|
|
1258
|
-
writeHypothesis,
|
|
1259
|
-
validateHypothesis,
|
|
1260
|
-
writeTddTask,
|
|
1261
|
-
endTddTask,
|
|
1262
|
-
createGoal,
|
|
1263
|
-
getGoal,
|
|
1264
|
-
updateGoal,
|
|
1265
|
-
deleteGoal,
|
|
1266
|
-
listGoalsByTddTask,
|
|
1267
|
-
createBehavior,
|
|
1268
|
-
getBehavior,
|
|
1269
|
-
updateBehavior,
|
|
1270
|
-
deleteBehavior,
|
|
1271
|
-
listBehaviorsByGoal,
|
|
1272
|
-
listBehaviorsByTddTask,
|
|
1273
|
-
writeTddPhase,
|
|
1274
|
-
writeTddArtifact,
|
|
1275
|
-
writeCommit,
|
|
1276
|
-
writeRunChangedFiles,
|
|
1277
|
-
recordIdempotentResponse,
|
|
1278
|
-
pruneSessions,
|
|
1279
|
-
associateLatestRunWithSession,
|
|
1280
|
-
backfillTestCaseTurns,
|
|
1281
|
-
registerAgent,
|
|
1282
|
-
endAgent
|
|
1283
|
-
};
|
|
1284
|
-
}));
|
|
1285
|
-
|
|
1286
|
-
//#endregion
|
|
1287
|
-
export { DataStoreLive };
|