@vitest-agent/sdk 2.5.1 → 3.1.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/index.d.ts +608 -14
- package/index.js +12 -8
- package/layers/DataReaderLive.js +90 -0
- package/layers/DataStoreLive.js +48 -2
- package/migrations/0002_test_artifacts.js +30 -0
- package/migrations/index.js +26 -0
- package/package.json +7 -6
- package/schemas/CoverageTargets.js +9 -4
- package/schemas/Options.js +20 -4
- package/schemas/RunEvent.js +8 -2
- package/schemas/RunReportFile.js +51 -0
- package/schemas/TestArtifacts.js +74 -0
- package/schemas/Thresholds.js +23 -3
- package/schemas/run-report-file-1.0.0.json +963 -0
- package/services/DataStore.js +8 -1
- package/testing/layers.js +2 -2
- package/testing.d.ts +165 -2
- package/testing.js +5 -4
- package/utils/ensure-migrated.js +2 -2
- package/utils/test-location.js +11 -1
- package/utils/validate-coverage-targets-shape.js +12 -4
package/index.js
CHANGED
|
@@ -25,7 +25,7 @@ import { HistoryTracker, historyKey } from "./services/HistoryTracker.js";
|
|
|
25
25
|
import { DataReaderLive } from "./layers/DataReaderLive.js";
|
|
26
26
|
import { ActorType, AgentId, ChatId, ConversationId, HostKind, ProjectKey, TddTaskId } from "./schemas/Identity.js";
|
|
27
27
|
import { Agent, IdempotencyHit } from "./schemas/Agent.js";
|
|
28
|
-
import { DataStore } from "./services/DataStore.js";
|
|
28
|
+
import { DataStore, INLINE_ATTACHMENT_BODY_CAP_BYTES } from "./services/DataStore.js";
|
|
29
29
|
import { coerceErrorField, coerceErrorText } from "./utils/coerce-error-text.js";
|
|
30
30
|
import { DataStoreLive } from "./layers/DataStoreLive.js";
|
|
31
31
|
import { DetailResolver } from "./services/DetailResolver.js";
|
|
@@ -50,7 +50,7 @@ import { PathResolutionLive } from "./layers/PathResolutionLive.js";
|
|
|
50
50
|
import { PerClientSessionMapReader, PerClientSessionMapWriter } from "./services/PerClientSessionMap.js";
|
|
51
51
|
import { PerClientSessionMapReaderLive, PerClientSessionMapWriterLive } from "./layers/PerClientSessionMapLive.js";
|
|
52
52
|
import { ProjectDiscovery } from "./services/ProjectDiscovery.js";
|
|
53
|
-
import { NON_DISCOVERABLE_DIRS, SRC_DIR, TEST_DIR, TEST_FILE_GLOB_SUFFIX, TEST_HELPER_DIRS, classifyTestPath, findOwningWorkspace, isTestFileName } from "./utils/test-location.js";
|
|
53
|
+
import { BATS_FILE_GLOB_SUFFIX, NON_DISCOVERABLE_DIRS, SRC_DIR, TEST_DIR, TEST_FILE_GLOB_SUFFIX, TEST_HELPER_DIRS, classifyTestPath, findOwningWorkspace, isTestFileName } from "./utils/test-location.js";
|
|
54
54
|
import { ProjectDiscoveryLive } from "./layers/ProjectDiscoveryLive.js";
|
|
55
55
|
import { ProjectDiscoveryTest } from "./layers/ProjectDiscoveryTest.js";
|
|
56
56
|
import { canonicalizeGitUrl, gitUrlToProjectKey } from "./utils/canonicalize-git-url.js";
|
|
@@ -63,11 +63,13 @@ import { RunContextLive, RunContextTest } from "./layers/RunContextLive.js";
|
|
|
63
63
|
import { formatTriageEffect } from "./lib/format-triage.js";
|
|
64
64
|
import { formatWrapupEffect } from "./lib/format-wrapup.js";
|
|
65
65
|
import migration from "./migrations/0001_initial.js";
|
|
66
|
-
import migration$1 from "./migrations/
|
|
67
|
-
import
|
|
66
|
+
import migration$1 from "./migrations/0002_test_artifacts.js";
|
|
67
|
+
import { PROJECT_MIGRATIONS } from "./migrations/index.js";
|
|
68
|
+
import migration$2 from "./migrations/registry_0001_initial.js";
|
|
69
|
+
import migration$3 from "./migrations/session_map_0001_initial.js";
|
|
68
70
|
import { AgentConsoleMode, CiConsoleMode, ConsoleMode, ConsoleOutputMode, DetailLevel, Environment, Executor, HumanConsoleMode, OutputFormat, PackageManager, ReportError, TestClassification, TestRunReason, TestState } from "./schemas/Common.js";
|
|
69
71
|
import { ConsoleLeakFile, ConsoleLeaks, ConsoleLeaksFromFailingTests } from "./schemas/ConsoleLeaks.js";
|
|
70
|
-
import { MetricThresholds, PatternThresholds, ResolvedThresholds } from "./schemas/Thresholds.js";
|
|
72
|
+
import { MetricThresholds, PatternMetricThresholds, PatternThresholds, PerFileThresholds, ResolvedThresholds } from "./schemas/Thresholds.js";
|
|
71
73
|
import { CoverageReport, CoverageTotals, FileCoverageReport } from "./schemas/Coverage.js";
|
|
72
74
|
import { AgentReport, ModuleReport, ReportSummary, TagCountEntry, TestReport } from "./schemas/AgentReport.js";
|
|
73
75
|
import { CoverageBaselines } from "./schemas/Baselines.js";
|
|
@@ -77,9 +79,11 @@ import { CoverageLevel, resolveCoverageInput, validateCoverageConfig } from "./s
|
|
|
77
79
|
import { CoverageTargets, CoverageTargetsMetrics } from "./schemas/CoverageTargets.js";
|
|
78
80
|
import { HistoryRecord, TestHistory, TestRun } from "./schemas/History.js";
|
|
79
81
|
import { Transport } from "./schemas/Transport.js";
|
|
80
|
-
import { AgentPluginOptions, AgentReporterOptions, ConsoleOutputs } from "./schemas/Options.js";
|
|
82
|
+
import { AgentPluginOptions, AgentReporterOptions, ConsoleOutputs, ReportOption, ReportSettings } from "./schemas/Options.js";
|
|
83
|
+
import { TestAnnotation, TestArtifact, TestArtifactLocation, TestAttachment } from "./schemas/TestArtifacts.js";
|
|
81
84
|
import { ActionSeverity, ConsoleLogLevel, CoverageGap, CoverageMetric, HookStatus, HookType, RunEvent } from "./schemas/RunEvent.js";
|
|
82
85
|
import { CoverageRenderState, FailureRecord, ModuleRecord, RenderState, RenderTotals, SuggestedActionRecord, TestRecord, initialRenderState } from "./schemas/RenderState.js";
|
|
86
|
+
import { RUN_REPORT_FILE_SCHEMA_URL, RunReportFile } from "./schemas/RunReportFile.js";
|
|
83
87
|
import { BehaviorDetail, BehaviorRow, BehaviorStatus, GoalDetail, GoalRow, GoalStatus } from "./schemas/Tdd.js";
|
|
84
88
|
import { TrendEntry, TrendRecord } from "./schemas/Trends.js";
|
|
85
89
|
import { FileEditPayload } from "./schemas/turns/FileEditPayload.js";
|
|
@@ -119,7 +123,7 @@ import { requiredArtifactForTransition, transitionEnforcesBehaviorMatch, validat
|
|
|
119
123
|
* substituted yet. Exported for version introspection by downstream tooling.
|
|
120
124
|
* @public
|
|
121
125
|
*/
|
|
122
|
-
const CURRENT_SDK_VERSION = "
|
|
126
|
+
const CURRENT_SDK_VERSION = "3.1.0";
|
|
123
127
|
|
|
124
128
|
//#endregion
|
|
125
|
-
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, ConsoleLeaksFromFailingTests, 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, NON_DISCOVERABLE_DIRS, 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, SRC_DIR, SessionCompleteEvent, SessionNotFoundError, SessionOutcome, SidecarTimeoutError, SilentFormatter, SuggestedActionRecord, TEST_DIR, TEST_FILE_GLOB_SUFFIX, TEST_HELPER_DIRS, 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, classifyTestPath, coerceErrorField, coerceErrorText, collectConsoleLeakEntries, compressLines, computeFailureSignature, computeTrend, countSuiteFailures, deriveIdempotencyKey, detectNonDefaultDiscoverStrategy, detectPackageManager, detectVitestScripts, determineTier, ensureMigrated, extractSqlReason, findFunctionBoundary, findOwningWorkspace, formatConsoleMarkdown, formatFatalError, formatGfm, formatScopedCoverageNote, formatTriageEffect, formatWrapupEffect, getRecentDirection, getRunCommand, getWorstMetric, gitUrlToProjectKey, hashTargets, historyKey, initialRenderState, isTestFileName, isTimeoutError, isVitestInvocation, migration as migration0001, normalizeAssertionShape, normalizeWorkspaceKey, osc8, probeHostMetadataFromEnv, migration$
|
|
129
|
+
export { ActionSeverity, ActorType, Agent, AgentConsoleMode, AgentContext, AgentId, AgentNotFoundError, AgentPluginOptions, AgentReport, AgentReporterOptions, BATS_FILE_GLOB_SUFFIX, BehaviorAbandonedEvent, BehaviorAddedEvent, BehaviorCompletedEvent, BehaviorDetail, BehaviorNotFoundError, BehaviorRow, BehaviorScopedEventTypes, BehaviorStartedEvent, BehaviorStatus, BehaviorsReadyEvent, BlockedEvent, CURRENT_SDK_VERSION, CacheManifest, CacheManifestEntry, ChannelEvent, ChatId, CiConsoleMode, ConfigLive, ConsoleLeakFile, ConsoleLeaks, ConsoleLeaksFromFailingTests, 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, INLINE_ATTACHMENT_BODY_CAP_BYTES, IdempotencyHit, IllegalStatusTransitionError, JsonFormatter, LoggerLive, MarkdownFormatter, MetricThresholds, ModuleRecord, ModuleReport, NON_DISCOVERABLE_DIRS, NotePayload, OutputFormat, OutputPipelineLive, OutputRenderer, OutputRendererLive, PROJECT_MIGRATIONS, PackageManager, PathResolutionError, PathResolutionLive, PatternMetricThresholds, PatternThresholds, PerClientSessionMapReader, PerClientSessionMapReaderLive, PerClientSessionMapWriter, PerClientSessionMapWriterLive, PerFileThresholds, PhaseTransitionEvent, ProbeMissError, ProjectDiscovery, ProjectDiscoveryLive, ProjectDiscoveryTest, ProjectIdentity, ProjectIdentityLive, ProjectIdentityNotResolvableError, ProjectIdentityTest, ProjectKey, RUN_REPORT_FILE_SCHEMA_URL, RegistrationConflictError, RenderState, RenderTotals, ReportError, ReportOption, ReportSettings, ReportSummary, ResolvedThresholds, RunContext, RunContext$, RunContextLive, RunContextService, RunContextTest, RunEvent, RunReportFile, SRC_DIR, SessionCompleteEvent, SessionNotFoundError, SessionOutcome, SidecarTimeoutError, SilentFormatter, SuggestedActionRecord, TEST_DIR, TEST_FILE_GLOB_SUFFIX, TEST_HELPER_DIRS, TagCountEntry, TddTaskAlreadyEndedError, TddTaskId, TddTaskNotFoundError, TerminalFormatter, TestAnnotation, TestArtifact, TestArtifactLocation, TestAttachment, 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, classifyTestPath, coerceErrorField, coerceErrorText, collectConsoleLeakEntries, compressLines, computeFailureSignature, computeTrend, countSuiteFailures, deriveIdempotencyKey, detectNonDefaultDiscoverStrategy, detectPackageManager, detectVitestScripts, determineTier, ensureMigrated, extractSqlReason, findFunctionBoundary, findOwningWorkspace, formatConsoleMarkdown, formatFatalError, formatGfm, formatScopedCoverageNote, formatTriageEffect, formatWrapupEffect, getRecentDirection, getRunCommand, getWorstMetric, gitUrlToProjectKey, hashTargets, historyKey, initialRenderState, isTestFileName, isTimeoutError, isVitestInvocation, migration as migration0001, migration$1 as migration0002, normalizeAssertionShape, normalizeWorkspaceKey, osc8, probeHostMetadataFromEnv, migration$2 as registryMigration0001, relativePath, requiredArtifactForTransition, resolveCoverageInput, resolveDataPath, resolveLogFile, resolveLogLevel, resolveProjectIdentityFromCandidates, resolveProjectKeyFromCwd, resolveWorkspaceKey, rewriteBashCommand, safeFilename, migration$3 as sessionMapMigration0001, stripAnsi, transitionEnforcesBehaviorMatch, validateCoverageConfig, validateCoverageTargetsShape, validatePhaseTransition };
|
package/layers/DataReaderLive.js
CHANGED
|
@@ -602,6 +602,94 @@ const DataReaderLive = Layer.effect(DataReader, Effect.gen(function* () {
|
|
|
602
602
|
table: "test_errors",
|
|
603
603
|
reason: extractSqlReason(e)
|
|
604
604
|
})));
|
|
605
|
+
const latestRunId = (project) => Effect.gen(function* () {
|
|
606
|
+
const runs = yield* sql`SELECT id FROM test_runs WHERE project = ${project} ORDER BY timestamp DESC LIMIT 1`;
|
|
607
|
+
return runs.length === 0 ? null : runs[0].id;
|
|
608
|
+
});
|
|
609
|
+
const attachmentsFor = (column, ownerId) => Effect.gen(function* () {
|
|
610
|
+
return (column === "annotation_id" ? yield* sql`SELECT content_type, path, body, body_encoding, byte_size FROM attachments WHERE annotation_id = ${ownerId} ORDER BY id` : yield* sql`SELECT content_type, path, body, body_encoding, byte_size FROM attachments WHERE artifact_id = ${ownerId} ORDER BY id`).map((r) => ({
|
|
611
|
+
...r.content_type !== null && { contentType: r.content_type },
|
|
612
|
+
...r.path !== null && { path: r.path },
|
|
613
|
+
...r.body !== null && { body: r.body },
|
|
614
|
+
...r.body_encoding !== null && { bodyEncoding: r.body_encoding },
|
|
615
|
+
byteSize: r.byte_size
|
|
616
|
+
}));
|
|
617
|
+
});
|
|
618
|
+
const getAnnotationsForTest = (project, fullName, options) => Effect.gen(function* () {
|
|
619
|
+
yield* Effect.logDebug("getAnnotationsForTest").pipe(Effect.annotateLogs({
|
|
620
|
+
project,
|
|
621
|
+
fullName
|
|
622
|
+
}));
|
|
623
|
+
const runId = yield* latestRunId(project);
|
|
624
|
+
if (runId === null) return [];
|
|
625
|
+
const modulePath = options?.modulePath ?? null;
|
|
626
|
+
const rows = yield* sql`SELECT ta.id, ta.type, ta.message, ta.location_line, ta.location_column, f.path AS location_path
|
|
627
|
+
FROM test_annotations ta
|
|
628
|
+
JOIN test_cases tc ON tc.id = ta.test_case_id
|
|
629
|
+
JOIN test_modules tm ON tm.id = tc.module_id
|
|
630
|
+
LEFT JOIN files f ON f.id = ta.location_file_id
|
|
631
|
+
WHERE tm.run_id = ${runId} AND tc.full_name = ${fullName}
|
|
632
|
+
AND (${modulePath} IS NULL OR tm.relative_module_id = ${modulePath})
|
|
633
|
+
ORDER BY ta.id`;
|
|
634
|
+
const out = [];
|
|
635
|
+
for (const r of rows) {
|
|
636
|
+
const attachments = yield* attachmentsFor("annotation_id", r.id);
|
|
637
|
+
out.push({
|
|
638
|
+
id: r.id,
|
|
639
|
+
type: r.type,
|
|
640
|
+
message: r.message,
|
|
641
|
+
...r.location_path !== null && r.location_line !== null && r.location_column !== null && { location: {
|
|
642
|
+
file: r.location_path,
|
|
643
|
+
line: r.location_line,
|
|
644
|
+
column: r.location_column
|
|
645
|
+
} },
|
|
646
|
+
attachments
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
return out;
|
|
650
|
+
}).pipe(Effect.annotateLogs("service", "DataReader"), Effect.mapError((e) => new DataStoreError({
|
|
651
|
+
operation: "read",
|
|
652
|
+
table: "test_annotations",
|
|
653
|
+
reason: extractSqlReason(e)
|
|
654
|
+
})));
|
|
655
|
+
const getArtifactsForTest = (project, fullName, options) => Effect.gen(function* () {
|
|
656
|
+
yield* Effect.logDebug("getArtifactsForTest").pipe(Effect.annotateLogs({
|
|
657
|
+
project,
|
|
658
|
+
fullName
|
|
659
|
+
}));
|
|
660
|
+
const runId = yield* latestRunId(project);
|
|
661
|
+
if (runId === null) return [];
|
|
662
|
+
const modulePath = options?.modulePath ?? null;
|
|
663
|
+
const rows = yield* sql`SELECT ta.id, ta.type, ta.message, ta.data, ta.location_line, ta.location_column, f.path AS location_path
|
|
664
|
+
FROM test_artifacts ta
|
|
665
|
+
JOIN test_cases tc ON tc.id = ta.test_case_id
|
|
666
|
+
JOIN test_modules tm ON tm.id = tc.module_id
|
|
667
|
+
LEFT JOIN files f ON f.id = ta.location_file_id
|
|
668
|
+
WHERE tm.run_id = ${runId} AND tc.full_name = ${fullName}
|
|
669
|
+
AND (${modulePath} IS NULL OR tm.relative_module_id = ${modulePath})
|
|
670
|
+
ORDER BY ta.id`;
|
|
671
|
+
const out = [];
|
|
672
|
+
for (const r of rows) {
|
|
673
|
+
const attachments = yield* attachmentsFor("artifact_id", r.id);
|
|
674
|
+
out.push({
|
|
675
|
+
id: r.id,
|
|
676
|
+
type: r.type,
|
|
677
|
+
message: r.message,
|
|
678
|
+
data: r.data,
|
|
679
|
+
...r.location_path !== null && r.location_line !== null && r.location_column !== null && { location: {
|
|
680
|
+
file: r.location_path,
|
|
681
|
+
line: r.location_line,
|
|
682
|
+
column: r.location_column
|
|
683
|
+
} },
|
|
684
|
+
attachments
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
return out;
|
|
688
|
+
}).pipe(Effect.annotateLogs("service", "DataReader"), Effect.mapError((e) => new DataStoreError({
|
|
689
|
+
operation: "read",
|
|
690
|
+
table: "test_artifacts",
|
|
691
|
+
reason: extractSqlReason(e)
|
|
692
|
+
})));
|
|
605
693
|
const getNotes = (scope, project, testFullName) => Effect.gen(function* () {
|
|
606
694
|
yield* Effect.logDebug("getNotes").pipe(Effect.annotateLogs({
|
|
607
695
|
scope: scope ?? null,
|
|
@@ -1759,6 +1847,8 @@ const DataReaderLive = Layer.effect(DataReader, Effect.gen(function* () {
|
|
|
1759
1847
|
getCoverage,
|
|
1760
1848
|
getTestsForFile,
|
|
1761
1849
|
getErrors,
|
|
1850
|
+
getAnnotationsForTest,
|
|
1851
|
+
getArtifactsForTest,
|
|
1762
1852
|
getNotes,
|
|
1763
1853
|
getNoteById,
|
|
1764
1854
|
searchNotes,
|
package/layers/DataStoreLive.js
CHANGED
|
@@ -2,10 +2,11 @@ import { AgentNotFoundError, RegistrationConflictError } from "../errors/AgentEr
|
|
|
2
2
|
import { DataStoreError, extractSqlReason } from "../errors/DataStoreError.js";
|
|
3
3
|
import { BehaviorNotFoundError, GoalNotFoundError, IllegalStatusTransitionError, TddTaskAlreadyEndedError, TddTaskNotFoundError } from "../errors/TddErrors.js";
|
|
4
4
|
import { Agent, IdempotencyHit } from "../schemas/Agent.js";
|
|
5
|
-
import { DataStore } from "../services/DataStore.js";
|
|
5
|
+
import { DataStore, INLINE_ATTACHMENT_BODY_CAP_BYTES } from "../services/DataStore.js";
|
|
6
6
|
import { coerceErrorText } from "../utils/coerce-error-text.js";
|
|
7
7
|
import { Effect, Layer, Option } from "effect";
|
|
8
8
|
import { SqlClient } from "effect/unstable/sql/SqlClient";
|
|
9
|
+
import { Buffer } from "node:buffer";
|
|
9
10
|
import { randomUUID } from "node:crypto";
|
|
10
11
|
|
|
11
12
|
//#region src/layers/DataStoreLive.ts
|
|
@@ -163,6 +164,49 @@ const DataStoreLive = Layer.effect(DataStore, Effect.gen(function* () {
|
|
|
163
164
|
table: "test_errors",
|
|
164
165
|
reason: extractSqlReason(e)
|
|
165
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
|
+
})));
|
|
166
210
|
const writeCoverage = (runId, coverage) => Effect.gen(function* () {
|
|
167
211
|
yield* Effect.logDebug("writeCoverage").pipe(Effect.annotateLogs({
|
|
168
212
|
runId,
|
|
@@ -183,7 +227,7 @@ const DataStoreLive = Layer.effect(DataStore, Effect.gen(function* () {
|
|
|
183
227
|
modulePath,
|
|
184
228
|
runId
|
|
185
229
|
}));
|
|
186
|
-
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})`;
|
|
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`;
|
|
187
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}`;
|
|
188
232
|
}).pipe(Effect.annotateLogs("service", "DataStore"), Effect.mapError((e) => new DataStoreError({
|
|
189
233
|
operation: "write",
|
|
@@ -1193,6 +1237,8 @@ const DataStoreLive = Layer.effect(DataStore, Effect.gen(function* () {
|
|
|
1193
1237
|
writeSuites,
|
|
1194
1238
|
writeTestCases,
|
|
1195
1239
|
writeErrors,
|
|
1240
|
+
writeAnnotations,
|
|
1241
|
+
writeArtifacts,
|
|
1196
1242
|
writeCoverage,
|
|
1197
1243
|
writeHistory,
|
|
1198
1244
|
writeBaselines,
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { SqlClient } from "effect/unstable/sql/SqlClient";
|
|
3
|
+
|
|
4
|
+
//#region src/migrations/0002_test_artifacts.ts
|
|
5
|
+
/** @internal */
|
|
6
|
+
const migration = Effect.gen(function* () {
|
|
7
|
+
const sql = yield* SqlClient;
|
|
8
|
+
yield* sql`DROP INDEX IF EXISTS idx_test_annotations_case`;
|
|
9
|
+
yield* sql`DROP INDEX IF EXISTS idx_test_annotations_type`;
|
|
10
|
+
yield* sql`DROP TABLE IF EXISTS test_annotations`;
|
|
11
|
+
yield* sql`
|
|
12
|
+
CREATE TABLE test_annotations (
|
|
13
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
14
|
+
test_case_id INTEGER NOT NULL REFERENCES test_cases(id) ON DELETE CASCADE,
|
|
15
|
+
type TEXT NOT NULL,
|
|
16
|
+
message TEXT NOT NULL,
|
|
17
|
+
location_file_id INTEGER REFERENCES files(id),
|
|
18
|
+
location_line INTEGER,
|
|
19
|
+
location_column INTEGER
|
|
20
|
+
)
|
|
21
|
+
`;
|
|
22
|
+
yield* sql`CREATE INDEX idx_test_annotations_case ON test_annotations(test_case_id)`;
|
|
23
|
+
yield* sql`CREATE INDEX idx_test_annotations_type ON test_annotations(type)`;
|
|
24
|
+
yield* sql`ALTER TABLE test_artifacts ADD COLUMN data TEXT`;
|
|
25
|
+
yield* sql`ALTER TABLE attachments ADD COLUMN byte_size INTEGER`;
|
|
26
|
+
yield* sql`ALTER TABLE attachments ADD COLUMN body_encoding TEXT`;
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
//#endregion
|
|
30
|
+
export { migration as default };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import migration from "./0001_initial.js";
|
|
2
|
+
import migration$1 from "./0002_test_artifacts.js";
|
|
3
|
+
|
|
4
|
+
//#region src/migrations/index.ts
|
|
5
|
+
/**
|
|
6
|
+
* Every migration that must run against a per-project `data.db`, keyed by
|
|
7
|
+
* migration id in application order.
|
|
8
|
+
*
|
|
9
|
+
* This is the single source of truth for the project database's migration
|
|
10
|
+
* set: `ensureMigrated`, the sdk testing layer, `ReporterLive`, `McpLive`,
|
|
11
|
+
* `CliLive` and `SidecarLive` all pass it straight to
|
|
12
|
+
* `SqliteMigrator.fromRecord`, so adding a migration file and registering it
|
|
13
|
+
* here is enough to reach every process that opens a project database.
|
|
14
|
+
*
|
|
15
|
+
* The session-map and discovery-registry databases are separate schemas with
|
|
16
|
+
* their own migrations and are deliberately not listed here.
|
|
17
|
+
*
|
|
18
|
+
* @public
|
|
19
|
+
*/
|
|
20
|
+
const PROJECT_MIGRATIONS = {
|
|
21
|
+
"0001_initial": migration,
|
|
22
|
+
"0002_test_artifacts": migration$1
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
//#endregion
|
|
26
|
+
export { PROJECT_MIGRATIONS };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vitest-agent/sdk",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.1.0",
|
|
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": [
|
|
@@ -42,18 +42,19 @@
|
|
|
42
42
|
"import": "./testing.js",
|
|
43
43
|
"default": "./testing.js"
|
|
44
44
|
},
|
|
45
|
+
"./schemas/*.json": "./schemas/*.json",
|
|
45
46
|
"./package.json": "./package.json"
|
|
46
47
|
},
|
|
47
48
|
"dependencies": {
|
|
48
49
|
"@effect/platform-node": "4.0.0-rc.112",
|
|
49
50
|
"@effect/sql-sqlite-node": "4.0.0-rc.112",
|
|
50
|
-
"@effected/config-file": "^0.
|
|
51
|
+
"@effected/config-file": "^0.7.0",
|
|
51
52
|
"@effected/jsonc": "^0.9.0",
|
|
52
53
|
"@effected/toml": "^0.6.0",
|
|
53
|
-
"@effected/walker": "^0.
|
|
54
|
-
"@effected/workspaces": "^0.20.
|
|
55
|
-
"@effected/xdg": "^0.4.
|
|
56
|
-
"@effected/yaml": "^0.
|
|
54
|
+
"@effected/walker": "^0.7.0",
|
|
55
|
+
"@effected/workspaces": "^0.20.1",
|
|
56
|
+
"@effected/xdg": "^0.4.1",
|
|
57
|
+
"@effected/yaml": "^0.14.0",
|
|
57
58
|
"acorn": "^8.18.0",
|
|
58
59
|
"acorn-typescript": "^1.4.13",
|
|
59
60
|
"ajv": "^8.20.0",
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { PerFileThresholds } from "./Thresholds.js";
|
|
1
2
|
import { Schema } from "effect";
|
|
2
3
|
|
|
3
4
|
//#region src/schemas/CoverageTargets.ts
|
|
@@ -8,7 +9,8 @@ import { Schema } from "effect";
|
|
|
8
9
|
const PositivePercent = Schema.Number.check(Schema.isGreaterThan(0));
|
|
9
10
|
/**
|
|
10
11
|
* Per-metric leaf shape for coverageTargets entries. Allows optional
|
|
11
|
-
* numeric targets per metric
|
|
12
|
+
* numeric targets per metric, the `100: true` shortcut, and — mirroring
|
|
13
|
+
* Vitest 5 — a per-glob `perFile` setting.
|
|
12
14
|
* @public
|
|
13
15
|
*/
|
|
14
16
|
const CoverageTargetsMetrics = Schema.Struct({
|
|
@@ -16,7 +18,8 @@ const CoverageTargetsMetrics = Schema.Struct({
|
|
|
16
18
|
functions: Schema.optional(PositivePercent),
|
|
17
19
|
branches: Schema.optional(PositivePercent),
|
|
18
20
|
statements: Schema.optional(PositivePercent),
|
|
19
|
-
100: Schema.optional(Schema.Literal(true))
|
|
21
|
+
100: Schema.optional(Schema.Literal(true)),
|
|
22
|
+
perFile: Schema.optional(PerFileThresholds)
|
|
20
23
|
}).annotate({ identifier: "CoverageTargetsMetrics" });
|
|
21
24
|
/**
|
|
22
25
|
* Typed schema for the coverageTargets option.
|
|
@@ -24,8 +27,10 @@ const CoverageTargetsMetrics = Schema.Struct({
|
|
|
24
27
|
* Mirrors Vitest's coverage.thresholds shape — per-metric positive numbers,
|
|
25
28
|
* the `100: true` shortcut (only valid at key `"100"`), and glob-pattern
|
|
26
29
|
* entries with metric objects. Positive numbers only; negatives and zeros
|
|
27
|
-
* are rejected. `perFile` is not allowed here —
|
|
28
|
-
* `coverage.thresholds.perFile`.
|
|
30
|
+
* are rejected. A top-level `perFile` is not allowed here — set it on
|
|
31
|
+
* `coverage.thresholds.perFile`. A `perFile` inside a glob-pattern entry
|
|
32
|
+
* IS allowed, mirroring Vitest 5, where a glob entry no longer inherits
|
|
33
|
+
* the top-level setting.
|
|
29
34
|
*
|
|
30
35
|
* A decode-time refinement rejects `true` at any key other than `"100"`
|
|
31
36
|
* so `{ statements: true }` fails parse rather than silently flowing
|
package/schemas/Options.js
CHANGED
|
@@ -25,18 +25,34 @@ const ConsoleOutputs = Schema.Struct({
|
|
|
25
25
|
ci: Schema.optional(CiConsoleMode)
|
|
26
26
|
}).annotate({ identifier: "ConsoleOutputs" });
|
|
27
27
|
/**
|
|
28
|
+
* Report-file settings. An object overrides the scope directory name
|
|
29
|
+
* used under `.vitest/`; the default scope is `vitest-agent`.
|
|
30
|
+
*
|
|
31
|
+
* @public
|
|
32
|
+
*/
|
|
33
|
+
const ReportSettings = Schema.Struct({ scope: Schema.optional(Schema.String.check(Schema.makeFilter((scope) => scope.includes("/") || scope.includes("\\") || scope === ".." || scope === "." ? `report.scope ${scope} must be a single directory name — it is resolved directly under .vitest/ and cannot nest or escape.` : void 0))) }).annotate({ identifier: "ReportSettings" });
|
|
34
|
+
/**
|
|
35
|
+
* Report-file settings. `false` disables `.vitest/<scope>/` writes
|
|
36
|
+
* entirely; an object overrides the scope directory name.
|
|
37
|
+
*
|
|
38
|
+
* Default: on for the `agent` and `ci` executors, off for `human`.
|
|
39
|
+
* @public
|
|
40
|
+
*/
|
|
41
|
+
const ReportOption = Schema.Union([Schema.Literal(false), ReportSettings]).annotate({ identifier: "ReportOption" });
|
|
42
|
+
/**
|
|
28
43
|
* The 2.0 user-facing options for `AgentPlugin`.
|
|
29
44
|
*
|
|
30
|
-
*
|
|
45
|
+
* Six fields total. Two are function-typed (`reporter`, `onRunEvent`)
|
|
31
46
|
* and live on the `AgentPluginConstructorOptions` companion interface in
|
|
32
47
|
* `@vitest-agent/plugin` — Effect Schema cannot encode functions cleanly,
|
|
33
|
-
* so the schema-decodable struct carries the
|
|
48
|
+
* so the schema-decodable struct carries the four data-shaped fields.
|
|
34
49
|
* @public
|
|
35
50
|
*/
|
|
36
51
|
const AgentPluginOptions = Schema.Struct({
|
|
37
52
|
console: Schema.optional(ConsoleOutputs),
|
|
38
53
|
coverageTargets: Schema.optional(CoverageTargets),
|
|
39
|
-
transport: Schema.optional(Transport)
|
|
54
|
+
transport: Schema.optional(Transport),
|
|
55
|
+
report: Schema.optional(ReportOption)
|
|
40
56
|
}).annotate({ identifier: "AgentPluginOptions" });
|
|
41
57
|
/**
|
|
42
58
|
* Reporter-implementation options.
|
|
@@ -54,4 +70,4 @@ const AgentReporterOptions = Schema.Struct({
|
|
|
54
70
|
projectFilter: Schema.optional(Schema.String) }).annotate({ identifier: "AgentReporterOptions" });
|
|
55
71
|
|
|
56
72
|
//#endregion
|
|
57
|
-
export { AgentPluginOptions, AgentReporterOptions, ConsoleOutputs };
|
|
73
|
+
export { AgentPluginOptions, AgentReporterOptions, ConsoleOutputs, ReportOption, ReportSettings };
|
package/schemas/RunEvent.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ReportError, TestClassification, TestState } from "./Common.js";
|
|
2
2
|
import { MetricThresholds } from "./Thresholds.js";
|
|
3
3
|
import { CoverageTotals, FileCoverageReport } from "./Coverage.js";
|
|
4
|
+
import { TestArtifactLocation, TestAttachment } from "./TestArtifacts.js";
|
|
4
5
|
import { Schema } from "effect";
|
|
5
6
|
|
|
6
7
|
//#region src/schemas/RunEvent.ts
|
|
@@ -145,13 +146,18 @@ const RunEvent = Schema.Union([
|
|
|
145
146
|
modulePath: Schema.String,
|
|
146
147
|
testName: Schema.String,
|
|
147
148
|
suitePath: Schema.Array(Schema.String),
|
|
148
|
-
annotation: Schema.String
|
|
149
|
+
annotation: Schema.String,
|
|
150
|
+
annotationType: Schema.String,
|
|
151
|
+
location: Schema.optional(TestArtifactLocation),
|
|
152
|
+
attachments: Schema.Array(TestAttachment)
|
|
149
153
|
}),
|
|
150
154
|
Schema.TaggedStruct("TestArtifactRecorded", {
|
|
151
155
|
modulePath: Schema.String,
|
|
152
156
|
testName: Schema.String,
|
|
153
157
|
suitePath: Schema.Array(Schema.String),
|
|
154
|
-
artifact: Schema.String
|
|
158
|
+
artifact: Schema.String,
|
|
159
|
+
location: Schema.optional(TestArtifactLocation),
|
|
160
|
+
attachments: Schema.Array(TestAttachment)
|
|
155
161
|
}),
|
|
156
162
|
Schema.TaggedStruct("WatcherReady", {}),
|
|
157
163
|
Schema.TaggedStruct("WatcherRerun", {
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { AgentReport } from "./AgentReport.js";
|
|
2
|
+
import { Schema } from "effect";
|
|
3
|
+
|
|
4
|
+
//#region src/schemas/RunReportFile.ts
|
|
5
|
+
/**
|
|
6
|
+
* The `.vitest/vitest-agent/run.json` file contract.
|
|
7
|
+
*
|
|
8
|
+
* This is a versioned public contract, not the raw `AgentReport`
|
|
9
|
+
* encoding — a Claude Code hook or a CI step reads this file, so the
|
|
10
|
+
* envelope carries its own `schemaVersion`. `reports` holds one
|
|
11
|
+
* `AgentReport` per Vitest project in the run.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Canonical `$schema` URL for `RunReportFile` version 1.
|
|
15
|
+
*
|
|
16
|
+
* Written as the first key of `run.json` so an editor or a JSON Schema
|
|
17
|
+
* validator can resolve the contract without knowing about vitest-agent.
|
|
18
|
+
*
|
|
19
|
+
* @public
|
|
20
|
+
*/
|
|
21
|
+
const RUN_REPORT_FILE_SCHEMA_URL = "https://vitest-agent.dev/schemas/run-report-file-1.0.0.json";
|
|
22
|
+
/**
|
|
23
|
+
* ISO-8601 instant, the shape `Date.prototype.toISOString` produces.
|
|
24
|
+
* `Date.parse` alone is too permissive — it accepts `"Sep 7 2026"` and
|
|
25
|
+
* other locale spellings a JSON Schema validator would reject — so the
|
|
26
|
+
* pattern is checked first and the parse only rules out impossible
|
|
27
|
+
* component values such as month 13.
|
|
28
|
+
*/
|
|
29
|
+
const ISO_8601_INSTANT = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/;
|
|
30
|
+
const ISO_8601_MESSAGE = "generatedAt must be an ISO-8601 instant, e.g. 2026-09-07T23:45:57.308Z.";
|
|
31
|
+
const IsoInstant = Schema.String.check(Schema.isPattern(ISO_8601_INSTANT, {
|
|
32
|
+
description: "An ISO-8601 instant, the shape Date.prototype.toISOString produces.",
|
|
33
|
+
message: ISO_8601_MESSAGE
|
|
34
|
+
}), Schema.makeFilter((value) => Number.isNaN(Date.parse(value)) ? ISO_8601_MESSAGE : void 0)).annotate({ identifier: "IsoInstant" });
|
|
35
|
+
/**
|
|
36
|
+
* Envelope for the `run.json` report file.
|
|
37
|
+
*
|
|
38
|
+
* @public
|
|
39
|
+
*/
|
|
40
|
+
const RunReportFile = Schema.Struct({
|
|
41
|
+
$schema: Schema.optionalKey(Schema.String),
|
|
42
|
+
schemaVersion: Schema.Literal(1),
|
|
43
|
+
generatedAt: IsoInstant,
|
|
44
|
+
reports: Schema.Array(AgentReport)
|
|
45
|
+
}).annotate({
|
|
46
|
+
identifier: "RunReportFile",
|
|
47
|
+
description: "The vitest-agent run report file (.vitest/vitest-agent/run.json): one AgentReport per Vitest project from the most recent run. Read reports[].summary for pass/fail counts and reports[].failed for the failing modules and their tests. schemaVersion is the envelope contract version; generatedAt is when the run finished."
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
//#endregion
|
|
51
|
+
export { RUN_REPORT_FILE_SCHEMA_URL, RunReportFile };
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
|
|
3
|
+
//#region src/schemas/TestArtifacts.ts
|
|
4
|
+
/**
|
|
5
|
+
* Schemas for Vitest 5 test annotations and test artifacts.
|
|
6
|
+
*
|
|
7
|
+
* Vocabulary (spec §4.1): a *test annotation* is a `context.annotate`
|
|
8
|
+
* note; a *test artifact* is a `recordArtifact` payload. Neither is a
|
|
9
|
+
* *TDD artifact* (`tdd_artifacts`), which is red/green evidence.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Source position a test annotation or test artifact points at.
|
|
13
|
+
* @public
|
|
14
|
+
*/
|
|
15
|
+
const TestArtifactLocation = Schema.Struct({
|
|
16
|
+
file: Schema.String,
|
|
17
|
+
line: Schema.Number,
|
|
18
|
+
column: Schema.Number
|
|
19
|
+
}).annotate({ identifier: "TestArtifactLocation" });
|
|
20
|
+
/**
|
|
21
|
+
* One attachment descriptor.
|
|
22
|
+
*
|
|
23
|
+
* `path` is the location Vitest already rewrote the attachment to
|
|
24
|
+
* (usually under `.vitest/attachments/`) or an external `http(s)` URL —
|
|
25
|
+
* vitest-agent never copies the file itself. `byteSize` is always
|
|
26
|
+
* recorded so a dangling path stays describable. Vitest treats a string
|
|
27
|
+
* `body` as base64 unless `bodyEncoding` is `"utf-8"`.
|
|
28
|
+
*
|
|
29
|
+
* `body` and `bodyEncoding` are populated only on the persistence path,
|
|
30
|
+
* for inline attachments under the 64 KiB cap. Attachments carried by
|
|
31
|
+
* `TestAnnotated` and `TestArtifactRecorded` run events are descriptors
|
|
32
|
+
* only — never assume an event attachment has a body; read the stored
|
|
33
|
+
* row when the bytes are needed.
|
|
34
|
+
* @public
|
|
35
|
+
*/
|
|
36
|
+
const TestAttachment = Schema.Struct({
|
|
37
|
+
contentType: Schema.optional(Schema.String),
|
|
38
|
+
path: Schema.optional(Schema.String),
|
|
39
|
+
body: Schema.optional(Schema.String),
|
|
40
|
+
bodyEncoding: Schema.optional(Schema.Literals(["base64", "utf-8"])),
|
|
41
|
+
byteSize: Schema.Number
|
|
42
|
+
}).annotate({ identifier: "TestAttachment" });
|
|
43
|
+
/**
|
|
44
|
+
* A test annotation as recorded by `context.annotate`.
|
|
45
|
+
*
|
|
46
|
+
* `type` is an arbitrary string — Vitest applies no enum
|
|
47
|
+
* (`.repos/vitest/packages/vitest/src/runtime/runner/types.ts`).
|
|
48
|
+
* @public
|
|
49
|
+
*/
|
|
50
|
+
const TestAnnotation = Schema.Struct({
|
|
51
|
+
type: Schema.String,
|
|
52
|
+
message: Schema.String,
|
|
53
|
+
location: Schema.optional(TestArtifactLocation),
|
|
54
|
+
attachments: Schema.Array(TestAttachment)
|
|
55
|
+
}).annotate({ identifier: "TestAnnotation" });
|
|
56
|
+
/**
|
|
57
|
+
* A test artifact as recorded by `recordArtifact`.
|
|
58
|
+
*
|
|
59
|
+
* `data` is the JSON encoding of the artifact's custom fields, minus
|
|
60
|
+
* `attachments` and `location` (both modelled separately). Artifacts
|
|
61
|
+
* whose `type` starts with `internal:` are Vitest's own and are not
|
|
62
|
+
* persisted.
|
|
63
|
+
* @public
|
|
64
|
+
*/
|
|
65
|
+
const TestArtifact = Schema.Struct({
|
|
66
|
+
type: Schema.String,
|
|
67
|
+
message: Schema.optional(Schema.String),
|
|
68
|
+
data: Schema.optional(Schema.String),
|
|
69
|
+
location: Schema.optional(TestArtifactLocation),
|
|
70
|
+
attachments: Schema.Array(TestAttachment)
|
|
71
|
+
}).annotate({ identifier: "TestArtifact" });
|
|
72
|
+
|
|
73
|
+
//#endregion
|
|
74
|
+
export { TestAnnotation, TestArtifact, TestArtifactLocation, TestAttachment };
|
package/schemas/Thresholds.js
CHANGED
|
@@ -12,19 +12,39 @@ const MetricThresholds = Schema.Struct({
|
|
|
12
12
|
statements: Schema.optional(Schema.Number)
|
|
13
13
|
}).annotate({ identifier: "MetricThresholds" });
|
|
14
14
|
/**
|
|
15
|
+
* A `perFile` setting. Vitest 5 allows either a plain boolean or a
|
|
16
|
+
* per-metric object that overrides the metric numbers used for the
|
|
17
|
+
* per-file check.
|
|
18
|
+
*
|
|
19
|
+
* An object constrains exactly the metrics it names, so `{}` enforces
|
|
20
|
+
* nothing at all. Write `true` to check every metric.
|
|
21
|
+
* @public
|
|
22
|
+
*/
|
|
23
|
+
const PerFileThresholds = Schema.Union([Schema.Boolean, MetricThresholds]).annotate({ identifier: "PerFileThresholds" });
|
|
24
|
+
/**
|
|
25
|
+
* The metric values carried by a glob-pattern entry. Under Vitest 5 a
|
|
26
|
+
* glob entry no longer inherits the top-level `perFile`, so it carries
|
|
27
|
+
* its own optional setting.
|
|
28
|
+
* @public
|
|
29
|
+
*/
|
|
30
|
+
const PatternMetricThresholds = Schema.Struct({
|
|
31
|
+
...MetricThresholds.fields,
|
|
32
|
+
perFile: Schema.optionalKey(PerFileThresholds)
|
|
33
|
+
}).annotate({ identifier: "PatternMetricThresholds" });
|
|
34
|
+
/**
|
|
15
35
|
* A glob pattern paired with its metric thresholds.
|
|
16
36
|
* @public
|
|
17
37
|
*/
|
|
18
|
-
const PatternThresholds = Schema.Tuple([Schema.String,
|
|
38
|
+
const PatternThresholds = Schema.Tuple([Schema.String, PatternMetricThresholds]).annotate({ identifier: "PatternThresholds" });
|
|
19
39
|
/**
|
|
20
40
|
* Fully resolved thresholds ready for evaluation.
|
|
21
41
|
* @public
|
|
22
42
|
*/
|
|
23
43
|
const ResolvedThresholds = Schema.Struct({
|
|
24
44
|
global: MetricThresholds,
|
|
25
|
-
perFile:
|
|
45
|
+
perFile: PerFileThresholds.pipe(Schema.withDecodingDefaultKey(Effect.succeed(false))),
|
|
26
46
|
patterns: Schema.Array(PatternThresholds).pipe(Schema.withDecodingDefaultKey(Effect.succeed([])))
|
|
27
47
|
}).annotate({ identifier: "ResolvedThresholds" });
|
|
28
48
|
|
|
29
49
|
//#endregion
|
|
30
|
-
export { MetricThresholds, PatternThresholds, ResolvedThresholds };
|
|
50
|
+
export { MetricThresholds, PatternMetricThresholds, PatternThresholds, PerFileThresholds, ResolvedThresholds };
|