@vitest-agent/sdk 1.2.0 → 1.3.1
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/index.d.ts +32 -7
- package/index.js +3 -3
- package/layers/DataReaderLive.js +44 -23
- package/layers/DataStoreLive.js +13 -8
- package/layers/HistoryTrackerLive.js +7 -5
- package/migrations/0001_initial.js +3 -2
- package/package.json +4 -4
- package/schemas/History.js +1 -0
- package/services/HistoryTracker.js +13 -1
- package/services/idempotency.js +2 -1
- package/sql/assemblers.js +15 -5
- package/testing.d.ts +4 -1
- package/testing.js +5 -5
package/index.d.ts
CHANGED
|
@@ -1932,6 +1932,7 @@ type TestRun = typeof TestRun.Type;
|
|
|
1932
1932
|
* @public
|
|
1933
1933
|
*/
|
|
1934
1934
|
declare const TestHistory: Schema.Struct<{
|
|
1935
|
+
modulePath: typeof Schema.String;
|
|
1935
1936
|
fullName: typeof Schema.String;
|
|
1936
1937
|
runs: Schema.Array$<Schema.Struct<{
|
|
1937
1938
|
timestamp: typeof Schema.String;
|
|
@@ -1948,6 +1949,7 @@ declare const HistoryRecord: Schema.Struct<{
|
|
|
1948
1949
|
project: typeof Schema.String;
|
|
1949
1950
|
updatedAt: typeof Schema.String;
|
|
1950
1951
|
tests: Schema.Array$<Schema.Struct<{
|
|
1952
|
+
modulePath: typeof Schema.String;
|
|
1951
1953
|
fullName: typeof Schema.String;
|
|
1952
1954
|
runs: Schema.Array$<Schema.Struct<{
|
|
1953
1955
|
timestamp: typeof Schema.String;
|
|
@@ -2596,7 +2598,7 @@ declare const DataStore_base: Context.TagClass<DataStore, "vitest-agent/DataStor
|
|
|
2596
2598
|
readonly writeTestCases: (moduleId: number, tests: ReadonlyArray<TestCaseInput>) => Effect.Effect<ReadonlyArray<number>, DataStoreError>;
|
|
2597
2599
|
readonly writeErrors: (runId: number, errors: ReadonlyArray<TestErrorInput>) => Effect.Effect<void, DataStoreError>;
|
|
2598
2600
|
readonly writeCoverage: (runId: number, coverage: ReadonlyArray<FileCoverageInput>) => Effect.Effect<void, DataStoreError>;
|
|
2599
|
-
readonly writeHistory: (project: string, fullName: string, runId: number, timestamp: string, state: string, duration: number | null, flaky: boolean, retryCount: number, errorMessage: string | null) => Effect.Effect<void, DataStoreError>;
|
|
2601
|
+
readonly writeHistory: (project: string, fullName: string, modulePath: string, runId: number, timestamp: string, state: string, duration: number | null, flaky: boolean, retryCount: number, errorMessage: string | null) => Effect.Effect<void, DataStoreError>;
|
|
2600
2602
|
readonly writeBaselines: (baselines: CoverageBaselines) => Effect.Effect<void, DataStoreError>;
|
|
2601
2603
|
readonly writeTrends: (project: string, runId: number, entry: TrendEntry) => Effect.Effect<void, DataStoreError>;
|
|
2602
2604
|
readonly writeSourceMap: (sourceFilePath: string, testModuleId: number, mappingType: string) => Effect.Effect<void, DataStoreError>;
|
|
@@ -2682,6 +2684,7 @@ interface ProjectRunSummary {
|
|
|
2682
2684
|
/** @public */
|
|
2683
2685
|
interface FlakyTest {
|
|
2684
2686
|
readonly fullName: string;
|
|
2687
|
+
readonly modulePath: string;
|
|
2685
2688
|
readonly project: string;
|
|
2686
2689
|
readonly passCount: number;
|
|
2687
2690
|
readonly failCount: number;
|
|
@@ -2691,6 +2694,7 @@ interface FlakyTest {
|
|
|
2691
2694
|
/** @public */
|
|
2692
2695
|
interface PersistentFailure {
|
|
2693
2696
|
readonly fullName: string;
|
|
2697
|
+
readonly modulePath: string;
|
|
2694
2698
|
readonly project: string;
|
|
2695
2699
|
readonly consecutiveFailures: number;
|
|
2696
2700
|
readonly firstFailedAt: string;
|
|
@@ -3208,9 +3212,22 @@ declare const FormatSelectorLive: Layer.Layer<FormatSelector, never, never>;
|
|
|
3208
3212
|
* @public
|
|
3209
3213
|
*/
|
|
3210
3214
|
interface TestOutcome {
|
|
3215
|
+
readonly modulePath: string;
|
|
3211
3216
|
readonly fullName: string;
|
|
3212
3217
|
readonly state: "passed" | "failed";
|
|
3213
3218
|
}
|
|
3219
|
+
/**
|
|
3220
|
+
* Builds the composite (modulePath, fullName) key used to key the internal
|
|
3221
|
+
* testMap and the returned classifications Map, so identically-named tests
|
|
3222
|
+
* in different files classify independently instead of colliding.
|
|
3223
|
+
*
|
|
3224
|
+
* Uses `JSON.stringify` for an injective encoding: a plain delimiter such as
|
|
3225
|
+
* a space is not collision-proof (e.g. `("a b", "c")` and `("a", "b c")` would
|
|
3226
|
+
* both yield `"a b c"`), which would reintroduce the very collision class this
|
|
3227
|
+
* key exists to prevent.
|
|
3228
|
+
* @public
|
|
3229
|
+
*/
|
|
3230
|
+
declare const historyKey: (modulePath: string, fullName: string) => string;
|
|
3214
3231
|
declare const HistoryTracker_base: Context.TagClass<HistoryTracker, "vitest-agent/HistoryTracker", {
|
|
3215
3232
|
readonly classify: (project: string, testOutcomes: ReadonlyArray<TestOutcome>, timestamp: string) => Effect.Effect<{
|
|
3216
3233
|
history: HistoryRecord;
|
|
@@ -4325,12 +4342,15 @@ interface ManifestRow {
|
|
|
4325
4342
|
* @public
|
|
4326
4343
|
*/
|
|
4327
4344
|
interface HistoryRow {
|
|
4345
|
+
module_path: string;
|
|
4328
4346
|
full_name: string;
|
|
4329
4347
|
timestamp: string;
|
|
4330
4348
|
state: string;
|
|
4331
4349
|
}
|
|
4332
4350
|
/**
|
|
4333
|
-
* A `Record<
|
|
4351
|
+
* A `Record<compositeKey, { modulePath, fullName, runs: TestRun[] }>` keyed by
|
|
4352
|
+
* the injective `historyKey(modulePath, fullName)` string, so identically-named
|
|
4353
|
+
* tests in different files are tracked as distinct entries.
|
|
4334
4354
|
*
|
|
4335
4355
|
* This is the internal representation returned by `assembleHistoryRecord`.
|
|
4336
4356
|
* Callers that need the Effect Schema `HistoryRecord` type should adapt this
|
|
@@ -4338,7 +4358,9 @@ interface HistoryRow {
|
|
|
4338
4358
|
* @public
|
|
4339
4359
|
*/
|
|
4340
4360
|
interface AssembledHistoryRecord {
|
|
4341
|
-
[
|
|
4361
|
+
[compositeKey: string]: {
|
|
4362
|
+
modulePath: string;
|
|
4363
|
+
fullName: string;
|
|
4342
4364
|
runs: TestRun[];
|
|
4343
4365
|
};
|
|
4344
4366
|
}
|
|
@@ -4351,10 +4373,13 @@ interface AssembledHistoryRecord {
|
|
|
4351
4373
|
*/
|
|
4352
4374
|
declare function assembleManifest(rows: ManifestRow[], dbPath: string): CacheManifest;
|
|
4353
4375
|
/**
|
|
4354
|
-
* Groups test_history rows by
|
|
4376
|
+
* Groups test_history rows by the composite (module_path, full_name) key
|
|
4377
|
+
* into a keyed record.
|
|
4355
4378
|
*
|
|
4356
|
-
* Returns `Record<
|
|
4357
|
-
* accumulated test runs for that test, in the order
|
|
4379
|
+
* Returns `Record<compositeKey, { modulePath, fullName, runs: TestRun[] }>`
|
|
4380
|
+
* where `runs` are the accumulated test runs for that test, in the order
|
|
4381
|
+
* they appear in `rows`. Two rows sharing `full_name` but differing
|
|
4382
|
+
* `module_path` are tracked as distinct entries rather than merged.
|
|
4358
4383
|
*
|
|
4359
4384
|
* Only rows with state `"passed"` or `"failed"` are included in runs
|
|
4360
4385
|
* (matching the `TestRun` schema constraint).
|
|
@@ -5240,5 +5265,5 @@ declare function validateCoverageTargetsShape(input: unknown): CoverageTargetsSh
|
|
|
5240
5265
|
*/
|
|
5241
5266
|
declare const CURRENT_SDK_VERSION: string;
|
|
5242
5267
|
//#endregion
|
|
5243
|
-
export { AcceptanceMetrics, ActionSeverity, ActorType, Agent, AgentConsoleMode, AgentContext, AgentId, AgentNotFoundError, AgentPluginOptions, AgentReport, AgentReporterOptions, AgentTreeNode, AnsiOptions, ArtifactKind, AssembledHistoryRecord, AssociateRunSessionInput, BehaviorAbandonedEvent, BehaviorAddedEvent, BehaviorCompletedEvent, BehaviorDetail, BehaviorNotFoundError, BehaviorRow, BehaviorScopedEventType, BehaviorScopedEventTypes, BehaviorStartedEvent, BehaviorStatus, BehaviorsReadyEvent, BlockedEvent, CURRENT_SDK_VERSION, CacheManifest, CacheManifestEntry, CandidateContext, CellOptions, ChangeKind, ChannelEvent, ChatId, CiConsoleMode, CitedArtifact, CitedArtifactRow, CommitChangesEntry, ConfigLive, ConsoleFormatOptions, ConsoleLeakEntry, ConsoleLeakFile, ConsoleLeakTask, ConsoleLeaks, ConsoleLogLevel, ConsoleMode, ConsoleOutputMode, ConsoleOutputs, ConversationId, ConversationNotFoundError, CoverageBaselines, FileCoverageReport as CoverageFile, FileCoverageReport, CoverageGap, CoverageInput, CoverageLevel, CoverageLevelName, CoverageMetric, CoverageRenderState, CoverageReport, CoverageTargets, CoverageTargetsMetrics, CoverageTargetsShapeError, CoverageTargetsShapeInfo, CoverageTargetsShapeResult, CoverageTargetsShapeWarning, CoverageTotals, CreateBehaviorInput, CreateGoalInput, CurrentTddPhase, DATABASE_FILENAME, DataReader, DataReaderLive, DataStore, DataStoreError, DataStoreLive, DenialReason, DetailLevel, DetailResolver, DetailResolverLive, DiscoveryError, DiscoveryRegistry, DiscoveryRegistryLive, DispatchInputs, EndTddTaskInput, Environment, EnvironmentDetector, EnvironmentDetectorLive, EnvironmentDetectorTest, Executor, ExecutorResolver, ExecutorResolverLive, FailureRecord, FailureSignatureDetail, FailureSignatureInput, FailureSignatureWriteInput, FileCoverageInput, FileEditPayload, FileSystemAdapter, FlakyTest, FormatSelector, FormatSelectorLive, type FormatTriageOptions, type FormatWrapupOptions, Formatter, FormatterContext, FunctionBoundary, GfmFormatter, GitCommandError, GoalAbandonedEvent, GoalAddedEvent, GoalCompletedEvent, GoalDetail, GoalNotFoundError, GoalRow, GoalStartedEvent, GoalStatus, GoalsReadyEvent, HistoryRecord, HistoryRow, HistoryTracker, HistoryTrackerLive, HistoryTrackerTest, HistoryTrackerTestState, HookFirePayload, HookStatus, HookType, HostKind, HostMetadataParseError, HostMetadataResult, HumanConsoleMode, HypothesisDetail, HypothesisInput, HypothesisPayload, IDEMPOTENCY_ROOT_SENTINEL, IdempotencyHit, IdempotencyInput, IdempotentResponseInput, IllegalStatusTransitionEntity, IllegalStatusTransitionError, JsonFormatter, KnownProject, KnownProjectInput, LoggerLive, ManifestRow, MapSessionInput, MapSessionOutput, MarkdownFormatter, MetricThresholds, ModuleInput, ModuleListEntry, ModuleRecord, ModuleReport, NoteInput, NotePayload, NoteRow, Osc8Options, OutputFormat, OutputPipelineLive, OutputRenderer, OutputRendererLive, PackageManager, PathResolutionError, PathResolutionLive, PatternThresholds, PerClientSessionMapReader, PerClientSessionMapReaderLive, PerClientSessionMapWriter, PerClientSessionMapWriterLive, PersistentFailure, Phase, PhaseTransitionContext, PhaseTransitionEvent, PhaseTransitionResult, ProbeMissError, ProjectDiscovery, ProjectDiscoveryLive, ProjectDiscoveryTest, ProjectIdentity, ProjectIdentityCandidate, ProjectIdentityCandidates, ProjectIdentityLive, ProjectIdentityNotResolvableError, ProjectIdentitySource, ProjectIdentityTest, ProjectKey, ProjectRunSummary, ProjectSummary, RegisterAgentInput, RegisterAgentResult, RegistrationConflictError, Remediation, RenderState, RenderTotals, RenderedOutput, ReportError, ReportSummary, ReporterKit, ReporterRenderInput, ResolveDataPathOptions, ResolveProjectIdentityOptions, ResolvedIdentity, ResolvedReporterConfig, ResolvedThresholds, RunChangedFile, RunContext, RunContext$, RunContextLive, RunContextService, RunContextTest, RunEvent, RunEventByTag, RunHealth, RunInvocationMethod, RunOutcome, RunShape, SessionCompleteEvent, SessionContextRow, SessionDetail, SessionInput, SessionNotFoundError, SessionOutcome, SettingsInput, SettingsListEntry, SettingsRow, SidecarTimeoutError, SilentFormatter, StackFrameInput, SuggestedActionRecord, SuiteInput, SuiteListEntry, TagCountEntry, TagInventoryRow, TddArtifactDetail, TddArtifactRow, TddPhaseDetail, TddTaskAlreadyEndedError, TddTaskDetail, TddTaskEndOutcome, TddTaskId, TddTaskInput, TddTaskNotFoundError, TddTaskSummary, TerminalFormatter, TestCaseInput, TestClassification, TestError, TestErrorInput, TestFileEntry, TestHistory, TestListEntry, TestOutcome, TestRecord, TestReport, TestRun, TestRunInput, TestRunReason, TestState, ToolCallPayload, ToolResultPayload, Transport, TrendEntry, TrendRecord, TrendSummary, TurnInput, TurnPayload, TurnSearchOptions, TurnSummary, UpdateBehaviorInput, UpdateGoalInput, UserPromptPayload, ValidateHypothesisInput, VitestAgentConfig, VitestAgentConfigFile, VitestAgentConfigFileService, VitestAgentReporter, VitestAgentReporterFactory, VitestModuleDiagnostic, VitestModuleError, VitestParsedStack, VitestTestCase, VitestTestDiagnostic, VitestTestError, VitestTestModule, VitestTestResult, VitestTestSuite, type WrapupKind, WriteCommitInput, WriteRunChangedFilesInput, WriteTddArtifactInput, WriteTddPhaseInput, WriteTddPhaseOutput, _resetMigrationCacheForTesting, ansi, assembleHistoryRecord, assembleManifest, buildAgentReport, buildConsoleLeaks, buildEnvPrefix, canonicalizeGitUrl, ciAnnotationsFormatter, classifyTest, collectConsoleLeakEntries, compressLines, computeFailureSignature, computeTrend, deriveIdempotencyKey, detectPackageManager, detectVitestScripts, determineTier, ensureMigrated, extractSqlReason, findFunctionBoundary, formatConsoleMarkdown, formatFatalError, formatGfm, formatTriageEffect, formatWrapupEffect, getRecentDirection, getRunCommand, getWorstMetric, gitUrlToProjectKey, hashTargets, initialRenderState, isTimeoutError, isVitestInvocation, migration as migration0001, normalizeAssertionShape, normalizeWorkspaceKey, osc8, probeHostMetadataFromEnv, migration$1 as registryMigration0001, relativePath, requiredArtifactForTransition, resolveCoverageInput, resolveDataPath, resolveLogFile, resolveLogLevel, resolveProjectIdentityFromCandidates, resolveProjectKeyFromCwd, resolveWorkspaceKey, rewriteBashCommand, safeFilename, migration$2 as sessionMapMigration0001, stripAnsi, transitionEnforcesBehaviorMatch, validateCoverageConfig, validateCoverageTargetsShape, validatePhaseTransition };
|
|
5268
|
+
export { AcceptanceMetrics, ActionSeverity, ActorType, Agent, AgentConsoleMode, AgentContext, AgentId, AgentNotFoundError, AgentPluginOptions, AgentReport, AgentReporterOptions, AgentTreeNode, AnsiOptions, ArtifactKind, AssembledHistoryRecord, AssociateRunSessionInput, BehaviorAbandonedEvent, BehaviorAddedEvent, BehaviorCompletedEvent, BehaviorDetail, BehaviorNotFoundError, BehaviorRow, BehaviorScopedEventType, BehaviorScopedEventTypes, BehaviorStartedEvent, BehaviorStatus, BehaviorsReadyEvent, BlockedEvent, CURRENT_SDK_VERSION, CacheManifest, CacheManifestEntry, CandidateContext, CellOptions, ChangeKind, ChannelEvent, ChatId, CiConsoleMode, CitedArtifact, CitedArtifactRow, CommitChangesEntry, ConfigLive, ConsoleFormatOptions, ConsoleLeakEntry, ConsoleLeakFile, ConsoleLeakTask, ConsoleLeaks, ConsoleLogLevel, ConsoleMode, ConsoleOutputMode, ConsoleOutputs, ConversationId, ConversationNotFoundError, CoverageBaselines, FileCoverageReport as CoverageFile, FileCoverageReport, CoverageGap, CoverageInput, CoverageLevel, CoverageLevelName, CoverageMetric, CoverageRenderState, CoverageReport, CoverageTargets, CoverageTargetsMetrics, CoverageTargetsShapeError, CoverageTargetsShapeInfo, CoverageTargetsShapeResult, CoverageTargetsShapeWarning, CoverageTotals, CreateBehaviorInput, CreateGoalInput, CurrentTddPhase, DATABASE_FILENAME, DataReader, DataReaderLive, DataStore, DataStoreError, DataStoreLive, DenialReason, DetailLevel, DetailResolver, DetailResolverLive, DiscoveryError, DiscoveryRegistry, DiscoveryRegistryLive, DispatchInputs, EndTddTaskInput, Environment, EnvironmentDetector, EnvironmentDetectorLive, EnvironmentDetectorTest, Executor, ExecutorResolver, ExecutorResolverLive, FailureRecord, FailureSignatureDetail, FailureSignatureInput, FailureSignatureWriteInput, FileCoverageInput, FileEditPayload, FileSystemAdapter, FlakyTest, FormatSelector, FormatSelectorLive, type FormatTriageOptions, type FormatWrapupOptions, Formatter, FormatterContext, FunctionBoundary, GfmFormatter, GitCommandError, GoalAbandonedEvent, GoalAddedEvent, GoalCompletedEvent, GoalDetail, GoalNotFoundError, GoalRow, GoalStartedEvent, GoalStatus, GoalsReadyEvent, HistoryRecord, HistoryRow, HistoryTracker, HistoryTrackerLive, HistoryTrackerTest, HistoryTrackerTestState, HookFirePayload, HookStatus, HookType, HostKind, HostMetadataParseError, HostMetadataResult, HumanConsoleMode, HypothesisDetail, HypothesisInput, HypothesisPayload, IDEMPOTENCY_ROOT_SENTINEL, IdempotencyHit, IdempotencyInput, IdempotentResponseInput, IllegalStatusTransitionEntity, IllegalStatusTransitionError, JsonFormatter, KnownProject, KnownProjectInput, LoggerLive, ManifestRow, MapSessionInput, MapSessionOutput, MarkdownFormatter, MetricThresholds, ModuleInput, ModuleListEntry, ModuleRecord, ModuleReport, NoteInput, NotePayload, NoteRow, Osc8Options, OutputFormat, OutputPipelineLive, OutputRenderer, OutputRendererLive, PackageManager, PathResolutionError, PathResolutionLive, PatternThresholds, PerClientSessionMapReader, PerClientSessionMapReaderLive, PerClientSessionMapWriter, PerClientSessionMapWriterLive, PersistentFailure, Phase, PhaseTransitionContext, PhaseTransitionEvent, PhaseTransitionResult, ProbeMissError, ProjectDiscovery, ProjectDiscoveryLive, ProjectDiscoveryTest, ProjectIdentity, ProjectIdentityCandidate, ProjectIdentityCandidates, ProjectIdentityLive, ProjectIdentityNotResolvableError, ProjectIdentitySource, ProjectIdentityTest, ProjectKey, ProjectRunSummary, ProjectSummary, RegisterAgentInput, RegisterAgentResult, RegistrationConflictError, Remediation, RenderState, RenderTotals, RenderedOutput, ReportError, ReportSummary, ReporterKit, ReporterRenderInput, ResolveDataPathOptions, ResolveProjectIdentityOptions, ResolvedIdentity, ResolvedReporterConfig, ResolvedThresholds, RunChangedFile, RunContext, RunContext$, RunContextLive, RunContextService, RunContextTest, RunEvent, RunEventByTag, RunHealth, RunInvocationMethod, RunOutcome, RunShape, SessionCompleteEvent, SessionContextRow, SessionDetail, SessionInput, SessionNotFoundError, SessionOutcome, SettingsInput, SettingsListEntry, SettingsRow, SidecarTimeoutError, SilentFormatter, StackFrameInput, SuggestedActionRecord, SuiteInput, SuiteListEntry, TagCountEntry, TagInventoryRow, TddArtifactDetail, TddArtifactRow, TddPhaseDetail, TddTaskAlreadyEndedError, TddTaskDetail, TddTaskEndOutcome, TddTaskId, TddTaskInput, TddTaskNotFoundError, TddTaskSummary, TerminalFormatter, TestCaseInput, TestClassification, TestError, TestErrorInput, TestFileEntry, TestHistory, TestListEntry, TestOutcome, TestRecord, TestReport, TestRun, TestRunInput, TestRunReason, TestState, ToolCallPayload, ToolResultPayload, Transport, TrendEntry, TrendRecord, TrendSummary, TurnInput, TurnPayload, TurnSearchOptions, TurnSummary, UpdateBehaviorInput, UpdateGoalInput, UserPromptPayload, ValidateHypothesisInput, VitestAgentConfig, VitestAgentConfigFile, VitestAgentConfigFileService, VitestAgentReporter, VitestAgentReporterFactory, VitestModuleDiagnostic, VitestModuleError, VitestParsedStack, VitestTestCase, VitestTestDiagnostic, VitestTestError, VitestTestModule, VitestTestResult, VitestTestSuite, type WrapupKind, WriteCommitInput, WriteRunChangedFilesInput, WriteTddArtifactInput, WriteTddPhaseInput, WriteTddPhaseOutput, _resetMigrationCacheForTesting, ansi, assembleHistoryRecord, assembleManifest, buildAgentReport, buildConsoleLeaks, buildEnvPrefix, canonicalizeGitUrl, ciAnnotationsFormatter, classifyTest, collectConsoleLeakEntries, compressLines, computeFailureSignature, computeTrend, deriveIdempotencyKey, detectPackageManager, detectVitestScripts, determineTier, ensureMigrated, extractSqlReason, findFunctionBoundary, formatConsoleMarkdown, formatFatalError, formatGfm, formatTriageEffect, formatWrapupEffect, getRecentDirection, getRunCommand, getWorstMetric, gitUrlToProjectKey, hashTargets, historyKey, initialRenderState, isTimeoutError, isVitestInvocation, migration as migration0001, normalizeAssertionShape, normalizeWorkspaceKey, osc8, probeHostMetadataFromEnv, migration$1 as registryMigration0001, relativePath, requiredArtifactForTransition, resolveCoverageInput, resolveDataPath, resolveLogFile, resolveLogLevel, resolveProjectIdentityFromCandidates, resolveProjectKeyFromCwd, resolveWorkspaceKey, rewriteBashCommand, safeFilename, migration$2 as sessionMapMigration0001, stripAnsi, transitionEnforcesBehaviorMatch, validateCoverageConfig, validateCoverageTargetsShape, validatePhaseTransition };
|
|
5244
5269
|
//# sourceMappingURL=index.d.ts.map
|
package/index.js
CHANGED
|
@@ -20,6 +20,7 @@ import { VitestAgentConfig } from "./schemas/Config.js";
|
|
|
20
20
|
import { VitestAgentConfigFile } from "./services/Config.js";
|
|
21
21
|
import { ConfigLive } from "./layers/ConfigLive.js";
|
|
22
22
|
import { DataReader } from "./services/DataReader.js";
|
|
23
|
+
import { HistoryTracker, historyKey } from "./services/HistoryTracker.js";
|
|
23
24
|
import { DataReaderLive } from "./layers/DataReaderLive.js";
|
|
24
25
|
import { ActorType, AgentId, ChatId, ConversationId, HostKind, ProjectKey, TddTaskId } from "./schemas/Identity.js";
|
|
25
26
|
import { Agent, IdempotencyHit } from "./schemas/Agent.js";
|
|
@@ -36,7 +37,6 @@ import { ExecutorResolver } from "./services/ExecutorResolver.js";
|
|
|
36
37
|
import { ExecutorResolverLive } from "./layers/ExecutorResolverLive.js";
|
|
37
38
|
import { FormatSelector } from "./services/FormatSelector.js";
|
|
38
39
|
import { FormatSelectorLive } from "./layers/FormatSelectorLive.js";
|
|
39
|
-
import { HistoryTracker } from "./services/HistoryTracker.js";
|
|
40
40
|
import { classifyTest } from "./utils/classify-test.js";
|
|
41
41
|
import { HistoryTrackerLive } from "./layers/HistoryTrackerLive.js";
|
|
42
42
|
import { HistoryTrackerTest } from "./layers/HistoryTrackerTest.js";
|
|
@@ -115,7 +115,7 @@ import { requiredArtifactForTransition, transitionEnforcesBehaviorMatch, validat
|
|
|
115
115
|
* substituted yet. Exported for version introspection by downstream tooling.
|
|
116
116
|
* @public
|
|
117
117
|
*/
|
|
118
|
-
const CURRENT_SDK_VERSION = "1.
|
|
118
|
+
const CURRENT_SDK_VERSION = "1.3.1";
|
|
119
119
|
|
|
120
120
|
//#endregion
|
|
121
|
-
export { ActionSeverity, ActorType, Agent, AgentConsoleMode, AgentContext, AgentId, AgentNotFoundError, AgentPluginOptions, AgentReport, AgentReporterOptions, BehaviorAbandonedEvent, BehaviorAddedEvent, BehaviorCompletedEvent, BehaviorDetail, BehaviorNotFoundError, BehaviorRow, BehaviorScopedEventTypes, BehaviorStartedEvent, BehaviorStatus, BehaviorsReadyEvent, BlockedEvent, CURRENT_SDK_VERSION, CacheManifest, CacheManifestEntry, ChannelEvent, ChatId, CiConsoleMode, ConfigLive, ConsoleLeakFile, ConsoleLeaks, ConsoleLogLevel, ConsoleMode, ConsoleOutputMode, ConsoleOutputs, ConversationId, ConversationNotFoundError, CoverageBaselines, FileCoverageReport as CoverageFile, CoverageGap, CoverageLevel, CoverageMetric, CoverageRenderState, CoverageReport, CoverageTargets, CoverageTargetsMetrics, CoverageTotals, DATABASE_FILENAME, DataReader, DataReaderLive, DataStore, DataStoreError, DataStoreLive, DetailLevel, DetailResolver, DetailResolverLive, DiscoveryError, DiscoveryRegistry, DiscoveryRegistryLive, Environment, EnvironmentDetector, EnvironmentDetectorLive, EnvironmentDetectorTest, Executor, ExecutorResolver, ExecutorResolverLive, FailureRecord, FileCoverageReport, FileEditPayload, FormatSelector, FormatSelectorLive, GfmFormatter, GitCommandError, GoalAbandonedEvent, GoalAddedEvent, GoalCompletedEvent, GoalDetail, GoalNotFoundError, GoalRow, GoalStartedEvent, GoalStatus, GoalsReadyEvent, HistoryRecord, HistoryTracker, HistoryTrackerLive, HistoryTrackerTest, HookFirePayload, HookStatus, HookType, HostKind, HostMetadataParseError, HumanConsoleMode, HypothesisPayload, IDEMPOTENCY_ROOT_SENTINEL, IdempotencyHit, IllegalStatusTransitionError, JsonFormatter, LoggerLive, MarkdownFormatter, MetricThresholds, ModuleRecord, ModuleReport, NotePayload, OutputFormat, OutputPipelineLive, OutputRenderer, OutputRendererLive, PackageManager, PathResolutionError, PathResolutionLive, PatternThresholds, PerClientSessionMapReader, PerClientSessionMapReaderLive, PerClientSessionMapWriter, PerClientSessionMapWriterLive, PhaseTransitionEvent, ProbeMissError, ProjectDiscovery, ProjectDiscoveryLive, ProjectDiscoveryTest, ProjectIdentity, ProjectIdentityLive, ProjectIdentityNotResolvableError, ProjectIdentityTest, ProjectKey, RegistrationConflictError, RenderState, RenderTotals, ReportError, ReportSummary, ResolvedThresholds, RunContext, RunContext$, RunContextLive, RunContextService, RunContextTest, RunEvent, SessionCompleteEvent, SessionNotFoundError, SessionOutcome, SidecarTimeoutError, SilentFormatter, SuggestedActionRecord, TagCountEntry, TddTaskAlreadyEndedError, TddTaskId, TddTaskNotFoundError, TerminalFormatter, TestClassification, TestHistory, TestRecord, TestReport, TestRun, TestRunReason, TestState, ToolCallPayload, ToolResultPayload, Transport, TrendEntry, TrendRecord, TurnPayload, UserPromptPayload, VitestAgentConfig, VitestAgentConfigFile, _resetMigrationCacheForTesting, ansi, assembleHistoryRecord, assembleManifest, buildAgentReport, buildConsoleLeaks, buildEnvPrefix, canonicalizeGitUrl, ciAnnotationsFormatter, classifyTest, collectConsoleLeakEntries, compressLines, computeFailureSignature, computeTrend, deriveIdempotencyKey, detectPackageManager, detectVitestScripts, determineTier, ensureMigrated, extractSqlReason, findFunctionBoundary, formatConsoleMarkdown, formatFatalError, formatGfm, formatTriageEffect, formatWrapupEffect, getRecentDirection, getRunCommand, getWorstMetric, gitUrlToProjectKey, hashTargets, initialRenderState, isTimeoutError, isVitestInvocation, migration as migration0001, normalizeAssertionShape, normalizeWorkspaceKey, osc8, probeHostMetadataFromEnv, migration$1 as registryMigration0001, relativePath, requiredArtifactForTransition, resolveCoverageInput, resolveDataPath, resolveLogFile, resolveLogLevel, resolveProjectIdentityFromCandidates, resolveProjectKeyFromCwd, resolveWorkspaceKey, rewriteBashCommand, safeFilename, migration$2 as sessionMapMigration0001, stripAnsi, transitionEnforcesBehaviorMatch, validateCoverageConfig, validateCoverageTargetsShape, validatePhaseTransition };
|
|
121
|
+
export { ActionSeverity, ActorType, Agent, AgentConsoleMode, AgentContext, AgentId, AgentNotFoundError, AgentPluginOptions, AgentReport, AgentReporterOptions, BehaviorAbandonedEvent, BehaviorAddedEvent, BehaviorCompletedEvent, BehaviorDetail, BehaviorNotFoundError, BehaviorRow, BehaviorScopedEventTypes, BehaviorStartedEvent, BehaviorStatus, BehaviorsReadyEvent, BlockedEvent, CURRENT_SDK_VERSION, CacheManifest, CacheManifestEntry, ChannelEvent, ChatId, CiConsoleMode, ConfigLive, ConsoleLeakFile, ConsoleLeaks, ConsoleLogLevel, ConsoleMode, ConsoleOutputMode, ConsoleOutputs, ConversationId, ConversationNotFoundError, CoverageBaselines, FileCoverageReport as CoverageFile, CoverageGap, CoverageLevel, CoverageMetric, CoverageRenderState, CoverageReport, CoverageTargets, CoverageTargetsMetrics, CoverageTotals, DATABASE_FILENAME, DataReader, DataReaderLive, DataStore, DataStoreError, DataStoreLive, DetailLevel, DetailResolver, DetailResolverLive, DiscoveryError, DiscoveryRegistry, DiscoveryRegistryLive, Environment, EnvironmentDetector, EnvironmentDetectorLive, EnvironmentDetectorTest, Executor, ExecutorResolver, ExecutorResolverLive, FailureRecord, FileCoverageReport, FileEditPayload, FormatSelector, FormatSelectorLive, GfmFormatter, GitCommandError, GoalAbandonedEvent, GoalAddedEvent, GoalCompletedEvent, GoalDetail, GoalNotFoundError, GoalRow, GoalStartedEvent, GoalStatus, GoalsReadyEvent, HistoryRecord, HistoryTracker, HistoryTrackerLive, HistoryTrackerTest, HookFirePayload, HookStatus, HookType, HostKind, HostMetadataParseError, HumanConsoleMode, HypothesisPayload, IDEMPOTENCY_ROOT_SENTINEL, IdempotencyHit, IllegalStatusTransitionError, JsonFormatter, LoggerLive, MarkdownFormatter, MetricThresholds, ModuleRecord, ModuleReport, NotePayload, OutputFormat, OutputPipelineLive, OutputRenderer, OutputRendererLive, PackageManager, PathResolutionError, PathResolutionLive, PatternThresholds, PerClientSessionMapReader, PerClientSessionMapReaderLive, PerClientSessionMapWriter, PerClientSessionMapWriterLive, PhaseTransitionEvent, ProbeMissError, ProjectDiscovery, ProjectDiscoveryLive, ProjectDiscoveryTest, ProjectIdentity, ProjectIdentityLive, ProjectIdentityNotResolvableError, ProjectIdentityTest, ProjectKey, RegistrationConflictError, RenderState, RenderTotals, ReportError, ReportSummary, ResolvedThresholds, RunContext, RunContext$, RunContextLive, RunContextService, RunContextTest, RunEvent, SessionCompleteEvent, SessionNotFoundError, SessionOutcome, SidecarTimeoutError, SilentFormatter, SuggestedActionRecord, TagCountEntry, TddTaskAlreadyEndedError, TddTaskId, TddTaskNotFoundError, TerminalFormatter, TestClassification, TestHistory, TestRecord, TestReport, TestRun, TestRunReason, TestState, ToolCallPayload, ToolResultPayload, Transport, TrendEntry, TrendRecord, TurnPayload, UserPromptPayload, VitestAgentConfig, VitestAgentConfigFile, _resetMigrationCacheForTesting, ansi, assembleHistoryRecord, assembleManifest, buildAgentReport, buildConsoleLeaks, buildEnvPrefix, canonicalizeGitUrl, ciAnnotationsFormatter, classifyTest, collectConsoleLeakEntries, compressLines, computeFailureSignature, computeTrend, deriveIdempotencyKey, detectPackageManager, detectVitestScripts, determineTier, ensureMigrated, extractSqlReason, findFunctionBoundary, formatConsoleMarkdown, formatFatalError, formatGfm, formatTriageEffect, formatWrapupEffect, getRecentDirection, getRunCommand, getWorstMetric, gitUrlToProjectKey, hashTargets, historyKey, initialRenderState, isTimeoutError, isVitestInvocation, migration as migration0001, normalizeAssertionShape, normalizeWorkspaceKey, osc8, probeHostMetadataFromEnv, migration$1 as registryMigration0001, relativePath, requiredArtifactForTransition, resolveCoverageInput, resolveDataPath, resolveLogFile, resolveLogLevel, resolveProjectIdentityFromCandidates, resolveProjectKeyFromCwd, resolveWorkspaceKey, rewriteBashCommand, safeFilename, migration$2 as sessionMapMigration0001, stripAnsi, transitionEnforcesBehaviorMatch, validateCoverageConfig, validateCoverageTargetsShape, validatePhaseTransition };
|
package/layers/DataReaderLive.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { DataStoreError, extractSqlReason } from "../errors/DataStoreError.js";
|
|
2
2
|
import { DataReader } from "../services/DataReader.js";
|
|
3
|
+
import { historyKey } from "../services/HistoryTracker.js";
|
|
3
4
|
import { Effect, Layer, Option } from "effect";
|
|
4
5
|
import { SqlClient } from "@effect/sql/SqlClient";
|
|
5
6
|
|
|
@@ -195,24 +196,30 @@ const DataReaderLive = Layer.effect(DataReader, Effect.gen(function* () {
|
|
|
195
196
|
})));
|
|
196
197
|
const getHistory = (project) => Effect.gen(function* () {
|
|
197
198
|
yield* Effect.logDebug("getHistory").pipe(Effect.annotateLogs({ project }));
|
|
198
|
-
const rows = yield* sql`SELECT full_name, timestamp, state
|
|
199
|
+
const rows = yield* sql`SELECT module_path, full_name, timestamp, state
|
|
199
200
|
FROM test_history
|
|
200
201
|
WHERE project = ${project}
|
|
201
|
-
ORDER BY full_name, timestamp DESC`;
|
|
202
|
+
ORDER BY module_path, full_name, timestamp DESC`;
|
|
202
203
|
const testsMap = /* @__PURE__ */ new Map();
|
|
203
204
|
for (const row of rows) {
|
|
204
205
|
if (row.state !== "passed" && row.state !== "failed") continue;
|
|
205
|
-
const
|
|
206
|
-
|
|
206
|
+
const key = historyKey(row.module_path, row.full_name);
|
|
207
|
+
const existing = testsMap.get(key);
|
|
208
|
+
if (existing) existing.runs.push({
|
|
207
209
|
timestamp: row.timestamp,
|
|
208
210
|
state: row.state
|
|
209
211
|
});
|
|
210
|
-
else testsMap.set(
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
212
|
+
else testsMap.set(key, {
|
|
213
|
+
modulePath: row.module_path,
|
|
214
|
+
fullName: row.full_name,
|
|
215
|
+
runs: [{
|
|
216
|
+
timestamp: row.timestamp,
|
|
217
|
+
state: row.state
|
|
218
|
+
}]
|
|
219
|
+
});
|
|
214
220
|
}
|
|
215
|
-
const tests = Array.from(testsMap.
|
|
221
|
+
const tests = Array.from(testsMap.values()).map(({ modulePath, fullName, runs }) => ({
|
|
222
|
+
modulePath,
|
|
216
223
|
fullName,
|
|
217
224
|
runs
|
|
218
225
|
}));
|
|
@@ -307,27 +314,31 @@ const DataReaderLive = Layer.effect(DataReader, Effect.gen(function* () {
|
|
|
307
314
|
yield* Effect.logDebug("getFlaky").pipe(Effect.annotateLogs({ project }));
|
|
308
315
|
return (yield* sql`SELECT
|
|
309
316
|
th1.full_name,
|
|
317
|
+
th1.module_path,
|
|
310
318
|
th1.project,
|
|
311
319
|
SUM(CASE WHEN th1.state = 'passed' THEN 1 ELSE 0 END) as pass_count,
|
|
312
320
|
SUM(CASE WHEN th1.state = 'failed' THEN 1 ELSE 0 END) as fail_count,
|
|
313
321
|
(SELECT state FROM test_history th2
|
|
314
322
|
WHERE th2.full_name = th1.full_name
|
|
323
|
+
AND th2.module_path = th1.module_path
|
|
315
324
|
AND th2.project = th1.project
|
|
316
325
|
ORDER BY timestamp DESC LIMIT 1) as last_state,
|
|
317
326
|
MAX(th1.timestamp) as last_timestamp
|
|
318
327
|
FROM test_history th1
|
|
319
328
|
WHERE th1.project = ${project}
|
|
320
|
-
GROUP BY th1.full_name, th1.project
|
|
329
|
+
GROUP BY th1.full_name, th1.module_path, th1.project
|
|
321
330
|
-- Flaky means oscillation, not a clean recovery. A test with both
|
|
322
331
|
-- passes and fails is only flaky when at least one failure occurs at
|
|
323
332
|
-- or after the earliest pass (a fail-after-pass regression). A
|
|
324
333
|
-- monotonic red->green cycle — every failure precedes every pass — is
|
|
325
334
|
-- a recovery, not flakiness, so it is excluded here. ISO-8601
|
|
326
|
-
-- timestamps compare lexicographically.
|
|
335
|
+
-- timestamps compare lexicographically. Grouping keys on module_path
|
|
336
|
+
-- so same-named tests in different files stay distinct series.
|
|
327
337
|
HAVING pass_count > 0 AND fail_count > 0
|
|
328
338
|
AND MAX(CASE WHEN th1.state = 'failed' THEN th1.timestamp END)
|
|
329
339
|
>= MIN(CASE WHEN th1.state = 'passed' THEN th1.timestamp END)`).map((r) => ({
|
|
330
340
|
fullName: r.full_name,
|
|
341
|
+
modulePath: r.module_path,
|
|
331
342
|
project: r.project,
|
|
332
343
|
passCount: r.pass_count,
|
|
333
344
|
failCount: r.fail_count,
|
|
@@ -342,36 +353,38 @@ const DataReaderLive = Layer.effect(DataReader, Effect.gen(function* () {
|
|
|
342
353
|
const getPersistentFailures = (project) => Effect.gen(function* () {
|
|
343
354
|
yield* Effect.logDebug("getPersistentFailures").pipe(Effect.annotateLogs({ project }));
|
|
344
355
|
return (yield* sql`WITH ranked AS (
|
|
345
|
-
SELECT full_name, project, state, timestamp, error_message,
|
|
356
|
+
SELECT full_name, module_path, project, state, timestamp, error_message,
|
|
346
357
|
ROW_NUMBER() OVER (
|
|
347
|
-
PARTITION BY project, full_name
|
|
358
|
+
PARTITION BY project, module_path, full_name
|
|
348
359
|
ORDER BY timestamp DESC
|
|
349
360
|
) as rn
|
|
350
361
|
FROM test_history
|
|
351
362
|
WHERE project = ${project}
|
|
352
363
|
),
|
|
353
364
|
streak AS (
|
|
354
|
-
SELECT full_name, project, state, timestamp, error_message, rn,
|
|
365
|
+
SELECT full_name, module_path, project, state, timestamp, error_message, rn,
|
|
355
366
|
MIN(CASE WHEN state != 'failed' THEN rn END) OVER (
|
|
356
|
-
PARTITION BY project, full_name
|
|
367
|
+
PARTITION BY project, module_path, full_name
|
|
357
368
|
) as first_pass_rn
|
|
358
369
|
FROM ranked
|
|
359
370
|
)
|
|
360
371
|
SELECT
|
|
361
|
-
full_name, project,
|
|
372
|
+
full_name, module_path, project,
|
|
362
373
|
COUNT(*) as consecutive_failures,
|
|
363
374
|
MIN(timestamp) as first_failed_at,
|
|
364
375
|
MAX(timestamp) as last_failed_at,
|
|
365
376
|
(SELECT error_message FROM streak s2
|
|
366
377
|
WHERE s2.full_name = streak.full_name
|
|
378
|
+
AND s2.module_path = streak.module_path
|
|
367
379
|
AND s2.project = streak.project
|
|
368
380
|
AND s2.rn = 1) as last_error_message
|
|
369
381
|
FROM streak
|
|
370
382
|
WHERE state = 'failed'
|
|
371
383
|
AND (first_pass_rn IS NULL OR rn < first_pass_rn)
|
|
372
|
-
GROUP BY full_name, project
|
|
384
|
+
GROUP BY full_name, module_path, project
|
|
373
385
|
HAVING consecutive_failures >= 2`).map((row) => ({
|
|
374
386
|
fullName: row.full_name,
|
|
387
|
+
modulePath: row.module_path,
|
|
375
388
|
project: row.project,
|
|
376
389
|
consecutiveFailures: row.consecutive_failures,
|
|
377
390
|
firstFailedAt: row.first_failed_at,
|
|
@@ -409,9 +422,10 @@ const DataReaderLive = Layer.effect(DataReader, Effect.gen(function* () {
|
|
|
409
422
|
WHERE project = ${project}
|
|
410
423
|
ORDER BY timestamp DESC LIMIT 1`;
|
|
411
424
|
if (runs.length === 0) return Option.none();
|
|
425
|
+
const runId = runs[0].id;
|
|
412
426
|
const fileCoverageRows = yield* sql`SELECT f.path as file_path, fc.statements, fc.branches, fc.functions, fc.lines, fc.uncovered_lines
|
|
413
427
|
FROM file_coverage fc JOIN files f ON f.id = fc.file_id
|
|
414
|
-
WHERE fc.run_id = ${
|
|
428
|
+
WHERE fc.run_id = ${runId}`;
|
|
415
429
|
const trendRows = yield* sql`SELECT statements, branches, functions, lines
|
|
416
430
|
FROM coverage_trends
|
|
417
431
|
WHERE project = ${project}
|
|
@@ -674,11 +688,12 @@ const DataReaderLive = Layer.effect(DataReader, Effect.gen(function* () {
|
|
|
674
688
|
WHERE project = ${project}
|
|
675
689
|
ORDER BY timestamp DESC LIMIT 1`;
|
|
676
690
|
if (runs.length === 0) return Option.none();
|
|
691
|
+
const runId = runs[0].id;
|
|
677
692
|
const rows = yield* sql`SELECT tc.id, tc.full_name, tc.state, tc.duration, f.path as relative_module_id, tc.classification
|
|
678
693
|
FROM test_cases tc
|
|
679
694
|
JOIN test_modules tm ON tm.id = tc.module_id
|
|
680
695
|
JOIN files f ON f.id = tm.file_id
|
|
681
|
-
WHERE tm.run_id = ${
|
|
696
|
+
WHERE tm.run_id = ${runId} AND tc.full_name = ${fullName}
|
|
682
697
|
LIMIT 1`;
|
|
683
698
|
if (rows.length === 0) return Option.none();
|
|
684
699
|
const r = rows[0];
|
|
@@ -744,12 +759,13 @@ const DataReaderLive = Layer.effect(DataReader, Effect.gen(function* () {
|
|
|
744
759
|
WHERE project = ${project}
|
|
745
760
|
ORDER BY timestamp DESC LIMIT 1`;
|
|
746
761
|
if (runs.length === 0) return [];
|
|
762
|
+
const runId = runs[0].id;
|
|
747
763
|
return (yield* sql`SELECT tm.id, f.path as file_path, tm.state,
|
|
748
764
|
(SELECT COUNT(*) FROM test_cases tc WHERE tc.module_id = tm.id) as test_count,
|
|
749
765
|
tm.duration
|
|
750
766
|
FROM test_modules tm
|
|
751
767
|
JOIN files f ON f.id = tm.file_id
|
|
752
|
-
WHERE tm.run_id = ${
|
|
768
|
+
WHERE tm.run_id = ${runId}
|
|
753
769
|
ORDER BY f.path`).map((r) => ({
|
|
754
770
|
id: r.id,
|
|
755
771
|
file: r.file_path,
|
|
@@ -847,7 +863,8 @@ const DataReaderLive = Layer.effect(DataReader, Effect.gen(function* () {
|
|
|
847
863
|
})));
|
|
848
864
|
const findSessionsByChatPrefix = (prefix) => Effect.gen(function* () {
|
|
849
865
|
yield* Effect.logDebug("findSessionsByChatPrefix").pipe(Effect.annotateLogs({ prefix }));
|
|
850
|
-
|
|
866
|
+
const pattern = `${prefix}%`;
|
|
867
|
+
return (yield* sql`SELECT id, chat_id, project, cwd, agent_kind, agent_type, parent_session_id, triage_was_non_empty, started_at, ended_at, end_reason FROM sessions WHERE chat_id LIKE ${pattern} ESCAPE '\\' ORDER BY started_at DESC`).map(sessionRowToDetail);
|
|
851
868
|
}).pipe(Effect.annotateLogs("service", "DataReader"), Effect.mapError((e) => new DataStoreError({
|
|
852
869
|
operation: "read",
|
|
853
870
|
table: "sessions",
|
|
@@ -866,7 +883,10 @@ const DataReaderLive = Layer.effect(DataReader, Effect.gen(function* () {
|
|
|
866
883
|
const searchTurns = (options) => Effect.gen(function* () {
|
|
867
884
|
yield* Effect.logDebug("searchTurns").pipe(Effect.annotateLogs({ ...options }));
|
|
868
885
|
const limit = options.limit ?? 100;
|
|
869
|
-
|
|
886
|
+
const sessionFilter = options.sessionId !== void 0 ? sql` AND session_id = ${options.sessionId}` : sql``;
|
|
887
|
+
const typeFilter = options.type !== void 0 ? sql` AND type = ${options.type}` : sql``;
|
|
888
|
+
const sinceFilter = options.since !== void 0 ? sql` AND occurred_at >= ${options.since}` : sql``;
|
|
889
|
+
return (yield* sql`SELECT id, session_id, turn_no, type, payload, occurred_at FROM turns WHERE 1=1${sessionFilter}${typeFilter}${sinceFilter} ORDER BY occurred_at DESC, turn_no DESC LIMIT ${limit}`).map((r) => ({
|
|
870
890
|
id: r.id,
|
|
871
891
|
sessionId: r.session_id,
|
|
872
892
|
turnNo: r.turn_no,
|
|
@@ -1551,6 +1571,7 @@ const DataReaderLive = Layer.effect(DataReader, Effect.gen(function* () {
|
|
|
1551
1571
|
WHERE project = ${options.project}
|
|
1552
1572
|
ORDER BY timestamp DESC LIMIT 1`;
|
|
1553
1573
|
if (runs.length === 0) return [];
|
|
1574
|
+
const runId = runs[0].id;
|
|
1554
1575
|
return (yield* sql`
|
|
1555
1576
|
SELECT tc.id, tc.full_name, tc.state, tc.duration, f.path AS file_path, tc.classification
|
|
1556
1577
|
FROM test_cases tc
|
|
@@ -1558,7 +1579,7 @@ const DataReaderLive = Layer.effect(DataReader, Effect.gen(function* () {
|
|
|
1558
1579
|
JOIN files f ON f.id = tm.file_id
|
|
1559
1580
|
JOIN test_case_tags tct ON tct.test_case_id = tc.id
|
|
1560
1581
|
JOIN tags t ON t.id = tct.tag_id
|
|
1561
|
-
WHERE tm.run_id = ${
|
|
1582
|
+
WHERE tm.run_id = ${runId} AND t.name = ${tag}
|
|
1562
1583
|
ORDER BY tc.full_name
|
|
1563
1584
|
`).map(mapTestListRow);
|
|
1564
1585
|
}
|
package/layers/DataStoreLive.js
CHANGED
|
@@ -174,13 +174,14 @@ const DataStoreLive = Layer.effect(DataStore, Effect.gen(function* () {
|
|
|
174
174
|
table: "file_coverage",
|
|
175
175
|
reason: extractSqlReason(e)
|
|
176
176
|
})));
|
|
177
|
-
const writeHistory = (project, fullName, runId, timestamp, state, duration, flaky, retryCount, errorMessage) => Effect.gen(function* () {
|
|
177
|
+
const writeHistory = (project, fullName, modulePath, runId, timestamp, state, duration, flaky, retryCount, errorMessage) => Effect.gen(function* () {
|
|
178
178
|
yield* Effect.logDebug("writeHistory").pipe(Effect.annotateLogs({
|
|
179
179
|
project,
|
|
180
|
+
modulePath,
|
|
180
181
|
runId
|
|
181
182
|
}));
|
|
182
|
-
yield* sql`INSERT INTO test_history (run_id, project, full_name, timestamp, state, duration, flaky, retry_count, error_message) VALUES (${runId}, ${project}, ${fullName}, ${timestamp}, ${state}, ${duration}, ${flaky ? 1 : 0}, ${retryCount}, ${errorMessage})`;
|
|
183
|
-
yield* sql`DELETE FROM test_history WHERE id NOT IN (SELECT id FROM test_history WHERE project = ${project} AND full_name = ${fullName} ORDER BY timestamp DESC LIMIT 10) AND project = ${project} AND full_name = ${fullName}`;
|
|
183
|
+
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})`;
|
|
184
|
+
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}`;
|
|
184
185
|
}).pipe(Effect.annotateLogs("service", "DataStore"), Effect.mapError((e) => new DataStoreError({
|
|
185
186
|
operation: "write",
|
|
186
187
|
table: "test_history",
|
|
@@ -226,7 +227,8 @@ const DataStoreLive = Layer.effect(DataStore, Effect.gen(function* () {
|
|
|
226
227
|
testModuleId,
|
|
227
228
|
mappingType
|
|
228
229
|
}));
|
|
229
|
-
|
|
230
|
+
const sourceFileId = yield* ensureFile(sourceFilePath);
|
|
231
|
+
yield* sql`INSERT OR IGNORE INTO source_test_map (source_file_id, test_module_id, mapping_type) VALUES (${sourceFileId}, ${testModuleId}, ${mappingType})`;
|
|
230
232
|
}).pipe(Effect.annotateLogs("service", "DataStore"), Effect.mapError((e) => new DataStoreError({
|
|
231
233
|
operation: "write",
|
|
232
234
|
table: "source_test_map",
|
|
@@ -342,11 +344,12 @@ const DataStoreLive = Layer.effect(DataStore, Effect.gen(function* () {
|
|
|
342
344
|
if (input.type === "file_edit") {
|
|
343
345
|
const filePath = payload.file_path;
|
|
344
346
|
yield* sql`INSERT OR IGNORE INTO files (path) VALUES (${filePath})`;
|
|
347
|
+
const fileId = (yield* sql`SELECT id FROM files WHERE path = ${filePath}`)[0].id;
|
|
345
348
|
yield* sql`
|
|
346
349
|
INSERT INTO file_edits (turn_id, file_id, edit_kind, lines_added, lines_removed, diff)
|
|
347
350
|
VALUES (
|
|
348
351
|
${turnId},
|
|
349
|
-
${
|
|
352
|
+
${fileId},
|
|
350
353
|
${payload.edit_kind},
|
|
351
354
|
${payload.lines_added ?? null},
|
|
352
355
|
${payload.lines_removed ?? null},
|
|
@@ -355,11 +358,12 @@ const DataStoreLive = Layer.effect(DataStore, Effect.gen(function* () {
|
|
|
355
358
|
`;
|
|
356
359
|
} else {
|
|
357
360
|
const rawToolName = payload.tool_name;
|
|
361
|
+
const toolName = typeof rawToolName === "string" && rawToolName.startsWith("mcp__") ? rawToolName.split("__").at(-1) ?? rawToolName : rawToolName;
|
|
358
362
|
yield* sql`
|
|
359
363
|
INSERT INTO tool_invocations (turn_id, tool_name, params_hash, result_summary, duration_ms, success)
|
|
360
364
|
VALUES (
|
|
361
365
|
${turnId},
|
|
362
|
-
${
|
|
366
|
+
${toolName},
|
|
363
367
|
${null},
|
|
364
368
|
${payload.result_summary ?? null},
|
|
365
369
|
${payload.duration_ms ?? null},
|
|
@@ -529,7 +533,7 @@ const DataStoreLive = Layer.effect(DataStore, Effect.gen(function* () {
|
|
|
529
533
|
goal: input.goal
|
|
530
534
|
}));
|
|
531
535
|
yield* ensureTddTaskOpen(input.tddTaskId);
|
|
532
|
-
|
|
536
|
+
const rows = yield* sql`
|
|
533
537
|
INSERT INTO tdd_session_goals (session_id, ordinal, goal)
|
|
534
538
|
SELECT ${input.tddTaskId},
|
|
535
539
|
COALESCE(MAX(ordinal), -1) + 1,
|
|
@@ -541,7 +545,8 @@ const DataStoreLive = Layer.effect(DataStore, Effect.gen(function* () {
|
|
|
541
545
|
operation: "write",
|
|
542
546
|
table: "tdd_session_goals",
|
|
543
547
|
reason: extractSqlReason(e)
|
|
544
|
-
})))
|
|
548
|
+
})));
|
|
549
|
+
return goalRowFromDb(rows[0]);
|
|
545
550
|
}).pipe(Effect.annotateLogs("service", "DataStore"));
|
|
546
551
|
const getGoal = (id) => Effect.gen(function* () {
|
|
547
552
|
const rows = yield* sql`
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DataReader } from "../services/DataReader.js";
|
|
2
|
-
import { HistoryTracker } from "../services/HistoryTracker.js";
|
|
2
|
+
import { HistoryTracker, historyKey } from "../services/HistoryTracker.js";
|
|
3
3
|
import { classifyTest } from "../utils/classify-test.js";
|
|
4
4
|
import { Effect, Layer } from "effect";
|
|
5
5
|
|
|
@@ -11,26 +11,28 @@ const HistoryTrackerLive = Layer.effect(HistoryTracker, Effect.gen(function* ()
|
|
|
11
11
|
return { classify: (project, testOutcomes, timestamp) => Effect.gen(function* () {
|
|
12
12
|
const existing = yield* reader.getHistory(project);
|
|
13
13
|
const testMap = /* @__PURE__ */ new Map();
|
|
14
|
-
for (const entry of existing.tests) testMap.set(entry.fullName, {
|
|
14
|
+
for (const entry of existing.tests) testMap.set(historyKey(entry.modulePath, entry.fullName), {
|
|
15
15
|
...entry,
|
|
16
16
|
runs: [...entry.runs]
|
|
17
17
|
});
|
|
18
18
|
const classifications = /* @__PURE__ */ new Map();
|
|
19
19
|
for (const outcome of testOutcomes) {
|
|
20
|
-
|
|
20
|
+
const key = historyKey(outcome.modulePath, outcome.fullName);
|
|
21
|
+
let entry = testMap.get(key);
|
|
21
22
|
if (!entry) {
|
|
22
23
|
entry = {
|
|
24
|
+
modulePath: outcome.modulePath,
|
|
23
25
|
fullName: outcome.fullName,
|
|
24
26
|
runs: []
|
|
25
27
|
};
|
|
26
|
-
testMap.set(
|
|
28
|
+
testMap.set(key, entry);
|
|
27
29
|
}
|
|
28
30
|
const priorRuns = entry.runs;
|
|
29
31
|
entry.runs = [{
|
|
30
32
|
timestamp,
|
|
31
33
|
state: outcome.state
|
|
32
34
|
}, ...priorRuns].slice(0, WINDOW_SIZE);
|
|
33
|
-
classifications.set(
|
|
35
|
+
classifications.set(key, classifyTest(outcome.state, priorRuns));
|
|
34
36
|
}
|
|
35
37
|
return {
|
|
36
38
|
history: {
|
|
@@ -352,6 +352,7 @@ const migration = Effect.gen(function* () {
|
|
|
352
352
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
353
353
|
run_id INTEGER NOT NULL REFERENCES test_runs(id) ON DELETE CASCADE,
|
|
354
354
|
project TEXT NOT NULL,
|
|
355
|
+
module_path TEXT NOT NULL,
|
|
355
356
|
full_name TEXT NOT NULL,
|
|
356
357
|
timestamp TEXT NOT NULL,
|
|
357
358
|
state TEXT NOT NULL CHECK (state IN ('passed', 'failed', 'skipped', 'pending')),
|
|
@@ -359,10 +360,10 @@ const migration = Effect.gen(function* () {
|
|
|
359
360
|
flaky INTEGER CHECK (flaky IN (0, 1)),
|
|
360
361
|
retry_count INTEGER DEFAULT 0,
|
|
361
362
|
error_message TEXT,
|
|
362
|
-
UNIQUE(project, full_name, timestamp)
|
|
363
|
+
UNIQUE(project, module_path, full_name, timestamp)
|
|
363
364
|
)
|
|
364
365
|
`;
|
|
365
|
-
yield* sql`CREATE INDEX idx_test_history_lookup ON test_history(project, full_name)`;
|
|
366
|
+
yield* sql`CREATE INDEX idx_test_history_lookup ON test_history(project, module_path, full_name)`;
|
|
366
367
|
yield* sql`CREATE INDEX idx_test_history_full_name ON test_history(full_name, timestamp)`;
|
|
367
368
|
yield* sql`CREATE INDEX idx_test_history_run ON test_history(run_id)`;
|
|
368
369
|
yield* sql`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vitest-agent/sdk",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Shared Effect Schema, SQLite migrations, data layer, services, formatters, and utilities for vitest-agent packages.",
|
|
6
6
|
"keywords": [
|
|
@@ -52,11 +52,11 @@
|
|
|
52
52
|
"acorn": "^8.17.0",
|
|
53
53
|
"acorn-typescript": "^1.4.13",
|
|
54
54
|
"ajv": "^8.20.0",
|
|
55
|
-
"config-file-effect": "^0.
|
|
55
|
+
"config-file-effect": "^0.3.0",
|
|
56
56
|
"effect": "^3.21.4",
|
|
57
57
|
"std-env": "^4.1.0",
|
|
58
|
-
"workspaces-effect": "^
|
|
59
|
-
"xdg-effect": "^2.0
|
|
58
|
+
"workspaces-effect": "^2.0.2",
|
|
59
|
+
"xdg-effect": "^2.1.0"
|
|
60
60
|
},
|
|
61
61
|
"engines": {
|
|
62
62
|
"node": ">=24.11.0"
|
package/schemas/History.js
CHANGED
|
@@ -1,8 +1,20 @@
|
|
|
1
1
|
import { Context } from "effect";
|
|
2
2
|
|
|
3
3
|
//#region src/services/HistoryTracker.ts
|
|
4
|
+
/**
|
|
5
|
+
* Builds the composite (modulePath, fullName) key used to key the internal
|
|
6
|
+
* testMap and the returned classifications Map, so identically-named tests
|
|
7
|
+
* in different files classify independently instead of colliding.
|
|
8
|
+
*
|
|
9
|
+
* Uses `JSON.stringify` for an injective encoding: a plain delimiter such as
|
|
10
|
+
* a space is not collision-proof (e.g. `("a b", "c")` and `("a", "b c")` would
|
|
11
|
+
* both yield `"a b c"`), which would reintroduce the very collision class this
|
|
12
|
+
* key exists to prevent.
|
|
13
|
+
* @public
|
|
14
|
+
*/
|
|
15
|
+
const historyKey = (modulePath, fullName) => JSON.stringify([modulePath, fullName]);
|
|
4
16
|
/** @public */
|
|
5
17
|
var HistoryTracker = class extends Context.Tag("vitest-agent/HistoryTracker")() {};
|
|
6
18
|
|
|
7
19
|
//#endregion
|
|
8
|
-
export { HistoryTracker };
|
|
20
|
+
export { HistoryTracker, historyKey };
|
package/services/idempotency.js
CHANGED
|
@@ -25,7 +25,8 @@ const IDEMPOTENCY_ROOT_SENTINEL = "__ROOT__";
|
|
|
25
25
|
const deriveIdempotencyKey = (input) => {
|
|
26
26
|
const parent = input.parentAgentId ?? "__ROOT__";
|
|
27
27
|
const message = `${input.agentType}|${parent}|${input.clientNonce}`;
|
|
28
|
-
|
|
28
|
+
const digest = createHash("sha256").update(message).digest();
|
|
29
|
+
return base32Encode(digest).slice(0, 26);
|
|
29
30
|
};
|
|
30
31
|
const BASE32_ALPHABET = "abcdefghijklmnopqrstuvwxyz234567";
|
|
31
32
|
/**
|
package/sql/assemblers.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { historyKey } from "../services/HistoryTracker.js";
|
|
2
|
+
|
|
1
3
|
//#region src/sql/assemblers.ts
|
|
2
4
|
/**
|
|
3
5
|
* Builds a `CacheManifest` from an array of test_runs aggregation rows.
|
|
@@ -32,10 +34,13 @@ function assembleManifest(rows, dbPath) {
|
|
|
32
34
|
};
|
|
33
35
|
}
|
|
34
36
|
/**
|
|
35
|
-
* Groups test_history rows by
|
|
37
|
+
* Groups test_history rows by the composite (module_path, full_name) key
|
|
38
|
+
* into a keyed record.
|
|
36
39
|
*
|
|
37
|
-
* Returns `Record<
|
|
38
|
-
* accumulated test runs for that test, in the order
|
|
40
|
+
* Returns `Record<compositeKey, { modulePath, fullName, runs: TestRun[] }>`
|
|
41
|
+
* where `runs` are the accumulated test runs for that test, in the order
|
|
42
|
+
* they appear in `rows`. Two rows sharing `full_name` but differing
|
|
43
|
+
* `module_path` are tracked as distinct entries rather than merged.
|
|
39
44
|
*
|
|
40
45
|
* Only rows with state `"passed"` or `"failed"` are included in runs
|
|
41
46
|
* (matching the `TestRun` schema constraint).
|
|
@@ -46,8 +51,13 @@ function assembleHistoryRecord(rows) {
|
|
|
46
51
|
for (const row of rows) {
|
|
47
52
|
const state = row.state === "passed" || row.state === "failed" ? row.state : null;
|
|
48
53
|
if (state === null) continue;
|
|
49
|
-
|
|
50
|
-
record[
|
|
54
|
+
const key = historyKey(row.module_path, row.full_name);
|
|
55
|
+
if (!record[key]) record[key] = {
|
|
56
|
+
modulePath: row.module_path,
|
|
57
|
+
fullName: row.full_name,
|
|
58
|
+
runs: []
|
|
59
|
+
};
|
|
60
|
+
record[key].runs.push({
|
|
51
61
|
timestamp: row.timestamp,
|
|
52
62
|
state
|
|
53
63
|
});
|
package/testing.d.ts
CHANGED
|
@@ -742,7 +742,7 @@ declare const DataStore_base: Context.TagClass<DataStore, "vitest-agent/DataStor
|
|
|
742
742
|
readonly writeTestCases: (moduleId: number, tests: ReadonlyArray<TestCaseInput>) => Effect.Effect<ReadonlyArray<number>, DataStoreError>;
|
|
743
743
|
readonly writeErrors: (runId: number, errors: ReadonlyArray<TestErrorInput>) => Effect.Effect<void, DataStoreError>;
|
|
744
744
|
readonly writeCoverage: (runId: number, coverage: ReadonlyArray<FileCoverageInput>) => Effect.Effect<void, DataStoreError>;
|
|
745
|
-
readonly writeHistory: (project: string, fullName: string, runId: number, timestamp: string, state: string, duration: number | null, flaky: boolean, retryCount: number, errorMessage: string | null) => Effect.Effect<void, DataStoreError>;
|
|
745
|
+
readonly writeHistory: (project: string, fullName: string, modulePath: string, runId: number, timestamp: string, state: string, duration: number | null, flaky: boolean, retryCount: number, errorMessage: string | null) => Effect.Effect<void, DataStoreError>;
|
|
746
746
|
readonly writeBaselines: (baselines: CoverageBaselines) => Effect.Effect<void, DataStoreError>;
|
|
747
747
|
readonly writeTrends: (project: string, runId: number, entry: TrendEntry) => Effect.Effect<void, DataStoreError>;
|
|
748
748
|
readonly writeSourceMap: (sourceFilePath: string, testModuleId: number, mappingType: string) => Effect.Effect<void, DataStoreError>;
|
|
@@ -1102,6 +1102,7 @@ declare const HistoryRecord: Schema.Struct<{
|
|
|
1102
1102
|
project: typeof Schema.String;
|
|
1103
1103
|
updatedAt: typeof Schema.String;
|
|
1104
1104
|
tests: Schema.Array$<Schema.Struct<{
|
|
1105
|
+
modulePath: typeof Schema.String;
|
|
1105
1106
|
fullName: typeof Schema.String;
|
|
1106
1107
|
runs: Schema.Array$<Schema.Struct<{
|
|
1107
1108
|
timestamp: typeof Schema.String;
|
|
@@ -1126,6 +1127,7 @@ interface ProjectRunSummary {
|
|
|
1126
1127
|
/** @public */
|
|
1127
1128
|
interface FlakyTest {
|
|
1128
1129
|
readonly fullName: string;
|
|
1130
|
+
readonly modulePath: string;
|
|
1129
1131
|
readonly project: string;
|
|
1130
1132
|
readonly passCount: number;
|
|
1131
1133
|
readonly failCount: number;
|
|
@@ -1135,6 +1137,7 @@ interface FlakyTest {
|
|
|
1135
1137
|
/** @public */
|
|
1136
1138
|
interface PersistentFailure {
|
|
1137
1139
|
readonly fullName: string;
|
|
1140
|
+
readonly modulePath: string;
|
|
1138
1141
|
readonly project: string;
|
|
1139
1142
|
readonly consecutiveFailures: number;
|
|
1140
1143
|
readonly firstFailedAt: string;
|
package/testing.js
CHANGED
|
@@ -176,9 +176,9 @@ function flaky(filename) {
|
|
|
176
176
|
state: "passed",
|
|
177
177
|
duration: 300
|
|
178
178
|
}]);
|
|
179
|
-
yield* store.writeHistory("default", "async > resolves within timeout", runId1, "2025-12-31T23:00:00.000Z", "passed", 500, false, 0, null);
|
|
180
|
-
yield* store.writeHistory("default", "async > resolves within timeout", runId1, "2026-01-01T00:00:00.000Z", "failed", 500, false, 0, null);
|
|
181
|
-
yield* store.writeHistory("default", "async > rejects on error", runId1, "2026-01-01T00:00:00.000Z", "passed", 300, false, 0, null);
|
|
179
|
+
yield* store.writeHistory("default", "async > resolves within timeout", "src/async.test.ts", runId1, "2025-12-31T23:00:00.000Z", "passed", 500, false, 0, null);
|
|
180
|
+
yield* store.writeHistory("default", "async > resolves within timeout", "src/async.test.ts", runId1, "2026-01-01T00:00:00.000Z", "failed", 500, false, 0, null);
|
|
181
|
+
yield* store.writeHistory("default", "async > rejects on error", "src/async.test.ts", runId1, "2026-01-01T00:00:00.000Z", "passed", 300, false, 0, null);
|
|
182
182
|
const runId2 = yield* store.writeRun({
|
|
183
183
|
invocationId: "inv-preset-flaky-2",
|
|
184
184
|
project: "default",
|
|
@@ -211,8 +211,8 @@ function flaky(filename) {
|
|
|
211
211
|
state: "passed",
|
|
212
212
|
duration: 300
|
|
213
213
|
}]);
|
|
214
|
-
yield* store.writeHistory("default", "async > resolves within timeout", runId2, "2026-01-01T01:00:00.000Z", "passed", 400, false, 0, null);
|
|
215
|
-
yield* store.writeHistory("default", "async > rejects on error", runId2, "2026-01-01T01:00:00.000Z", "passed", 300, false, 0, null);
|
|
214
|
+
yield* store.writeHistory("default", "async > resolves within timeout", "src/async.test.ts", runId2, "2026-01-01T01:00:00.000Z", "passed", 400, false, 0, null);
|
|
215
|
+
yield* store.writeHistory("default", "async > rejects on error", "src/async.test.ts", runId2, "2026-01-01T01:00:00.000Z", "passed", 300, false, 0, null);
|
|
216
216
|
});
|
|
217
217
|
return Layer.effectDiscard(seed).pipe(Layer.provideMerge(base));
|
|
218
218
|
}
|