@vitest-agent/sdk 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DataReader-2kJ9I9Hh.d.ts +1809 -0
- package/LICENSE +21 -0
- package/README.md +52 -0
- package/dispatch.d.ts +48 -0
- package/dispatch.js +5 -0
- package/errors/AgentErrors.js +65 -0
- package/errors/DataStoreError.js +44 -0
- package/errors/DiscoveryError.js +21 -0
- package/errors/PathResolutionError.js +27 -0
- package/errors/ProjectIdentityError.js +29 -0
- package/errors/RunContextError.js +35 -0
- package/errors/TddErrors.js +66 -0
- package/exit-code-for-tag.js +18 -0
- package/formatters/ci-annotations.js +61 -0
- package/formatters/gfm.js +20 -0
- package/formatters/json.js +18 -0
- package/formatters/markdown.js +39 -0
- package/formatters/silent.js +9 -0
- package/formatters/terminal.js +56 -0
- package/index.d.ts +3334 -0
- package/index.js +120 -0
- package/internal-inject-env.js +42 -0
- package/layers/ConfigLive.js +47 -0
- package/layers/DataReaderLive.js +1666 -0
- package/layers/DataStoreLive.js +1185 -0
- package/layers/DetailResolverLive.js +9 -0
- package/layers/DiscoveryRegistryLive.js +70 -0
- package/layers/EnvironmentDetectorLive.js +21 -0
- package/layers/EnvironmentDetectorTest.js +13 -0
- package/layers/ExecutorResolverLive.js +9 -0
- package/layers/FormatSelectorLive.js +20 -0
- package/layers/HistoryTrackerLive.js +47 -0
- package/layers/HistoryTrackerTest.js +25 -0
- package/layers/LoggerLive.js +64 -0
- package/layers/OutputPipelineLive.js +13 -0
- package/layers/OutputRendererLive.js +28 -0
- package/layers/PathResolutionLive.js +36 -0
- package/layers/PerClientSessionMapLive.js +145 -0
- package/layers/ProjectDiscoveryLive.js +70 -0
- package/layers/ProjectDiscoveryTest.js +12 -0
- package/layers/ProjectIdentityLive.js +92 -0
- package/layers/RunContextLive.js +81 -0
- package/lib/format-triage.js +103 -0
- package/lib/format-wrapup.js +67 -0
- package/migrations/0001_initial.js +849 -0
- package/migrations/registry_0001_initial.js +39 -0
- package/migrations/session_map_0001_initial.js +61 -0
- package/package.json +64 -0
- package/schemas/Agent.js +42 -0
- package/schemas/AgentReport.js +71 -0
- package/schemas/Baselines.js +16 -0
- package/schemas/CacheManifest.js +27 -0
- package/schemas/ChannelEvent.js +138 -0
- package/schemas/Common.js +105 -0
- package/schemas/Config.js +28 -0
- package/schemas/Coverage.js +51 -0
- package/schemas/CoverageLevel.js +117 -0
- package/schemas/CoverageTargets.js +39 -0
- package/schemas/History.js +31 -0
- package/schemas/Identity.js +64 -0
- package/schemas/Options.js +57 -0
- package/schemas/RenderState.js +161 -0
- package/schemas/RunEvent.js +163 -0
- package/schemas/Tdd.js +44 -0
- package/schemas/Thresholds.js +30 -0
- package/schemas/Transport.js +16 -0
- package/schemas/Trends.js +23 -0
- package/schemas/turns/FileEditPayload.js +15 -0
- package/schemas/turns/HookFirePayload.js +13 -0
- package/schemas/turns/HypothesisPayload.js +13 -0
- package/schemas/turns/NotePayload.js +13 -0
- package/schemas/turns/ToolCallPayload.js +13 -0
- package/schemas/turns/ToolResultPayload.js +15 -0
- package/schemas/turns/UserPromptPayload.js +12 -0
- package/schemas/turns/index.js +15 -0
- package/services/Config.js +15 -0
- package/services/DataReader.js +8 -0
- package/services/DataStore.js +8 -0
- package/services/DetailResolver.js +8 -0
- package/services/DiscoveryRegistry.js +8 -0
- package/services/EnvironmentDetector.js +8 -0
- package/services/ExecutorResolver.js +8 -0
- package/services/FormatSelector.js +8 -0
- package/services/HistoryTracker.js +8 -0
- package/services/OutputRenderer.js +8 -0
- package/services/PerClientSessionMap.js +22 -0
- package/services/ProjectDiscovery.js +8 -0
- package/services/ProjectIdentity.js +63 -0
- package/services/RunContext.js +53 -0
- package/services/idempotency.js +57 -0
- package/sidecar-dispatch.js +84 -0
- package/sql/assemblers.js +59 -0
- package/testing/layers.js +21 -0
- package/testing.d.ts +25 -0
- package/testing.js +243 -0
- package/tsdoc-metadata.json +11 -0
- package/utils/ansi.js +49 -0
- package/utils/build-report.js +130 -0
- package/utils/canonicalize-git-url.js +66 -0
- package/utils/classify-test.js +22 -0
- package/utils/compress-lines.js +45 -0
- package/utils/compute-trend.js +88 -0
- package/utils/detect-pm.js +58 -0
- package/utils/detect-timeout.js +17 -0
- package/utils/ensure-migrated.js +52 -0
- package/utils/failure-signature.js +33 -0
- package/utils/format-console.js +219 -0
- package/utils/format-fatal-error.js +35 -0
- package/utils/format-gfm.js +161 -0
- package/utils/format-terminal.js +480 -0
- package/utils/function-boundary.js +75 -0
- package/utils/hyperlink.js +11 -0
- package/utils/match-vitest-command.js +88 -0
- package/utils/normalize-workspace-key.js +34 -0
- package/utils/probe-host-metadata.js +99 -0
- package/utils/resolve-data-path.js +66 -0
- package/utils/resolve-project-key-from-cwd.js +52 -0
- package/utils/resolve-workspace-key.js +33 -0
- package/utils/safe-filename.js +34 -0
- package/utils/validate-coverage-targets-shape.js +84 -0
- package/utils/validate-phase-transition.js +113 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 C. Spencer Beggs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# @vitest-agent/sdk
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@vitest-agent/sdk)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://www.typescriptlang.org/)
|
|
6
|
+
|
|
7
|
+
> **Part of the [vitest-agent](https://vitest-agent.dev) ecosystem.** Most users want **[@vitest-agent/plugin](https://www.npmjs.com/package/@vitest-agent/plugin)**, which pulls this package in automatically. Install `@vitest-agent/sdk` directly only if you build tooling on the shared schemas or data layer.
|
|
8
|
+
|
|
9
|
+
The no-internal-deps base for the vitest-agent ecosystem. Carries the Effect Schemas, SQLite migrations and data layer, services and live layers, formatters, XDG path resolution, the public reporter and dispatcher contract types, and testing utilities.
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- **Effect Schemas** — all domain types (`RunEvent`, `RenderState`, `CoverageTargets`, `TurnPayload`, identity types) defined with Effect Schema; runtime validation and TypeScript types from one source
|
|
14
|
+
- **SQLite data layer** — `DataStore` and `DataReader` Effect services with live and `:memory:` test layers; `ensureMigrated` for safe multi-project setups
|
|
15
|
+
- **XDG path resolution** — deterministic `dbPath` derivation from workspace identity with a five-source fallback chain
|
|
16
|
+
- **Reporter and dispatcher contracts** — `VitestAgentReporterFactory`, `ReporterKit`, `ResolvedReporterConfig`, `DispatchInputs` and the types consumed by every other package
|
|
17
|
+
- **Sidecar dispatch core** — `dispatch`, `injectEnv` and `exitCodeForTag` on the `@vitest-agent/sdk/dispatch` sub-path for a minimal SEA bundle
|
|
18
|
+
- **Test utilities** — `makeTestLayer`, `DataStoreTestLayer` and five preset factory functions on the `@vitest-agent/sdk/testing` sub-path
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install @vitest-agent/sdk
|
|
24
|
+
# or
|
|
25
|
+
pnpm add @vitest-agent/sdk
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Quick start
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import { Effect } from "effect";
|
|
32
|
+
import { DataReader } from "@vitest-agent/sdk";
|
|
33
|
+
import { singlePassingRun } from "@vitest-agent/sdk/testing";
|
|
34
|
+
|
|
35
|
+
const layer = singlePassingRun(":memory:");
|
|
36
|
+
|
|
37
|
+
await Effect.runPromise(
|
|
38
|
+
Effect.provide(
|
|
39
|
+
Effect.flatMap(DataReader, (r) => r.getLatestRun("default", null)),
|
|
40
|
+
layer,
|
|
41
|
+
),
|
|
42
|
+
);
|
|
43
|
+
// returns the seeded run row
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Documentation
|
|
47
|
+
|
|
48
|
+
Package reference at [vitest-agent.dev/sdk](https://vitest-agent.dev/sdk).
|
|
49
|
+
|
|
50
|
+
## License
|
|
51
|
+
|
|
52
|
+
[MIT](LICENSE)
|
package/dispatch.d.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
//#region src/exit-code-for-tag.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Map a tagged-error `_tag` to the agent-agnostic sidecar exit-code
|
|
4
|
+
* taxonomy. Unknown tags collapse to `5` (other unexpected defect).
|
|
5
|
+
* @public
|
|
6
|
+
*/
|
|
7
|
+
declare const exitCodeForTag: (tag: string) => number;
|
|
8
|
+
//#endregion
|
|
9
|
+
//#region src/internal-inject-env.d.ts
|
|
10
|
+
/** Input to {@link injectEnv}. @public */
|
|
11
|
+
interface InjectEnvInput {
|
|
12
|
+
readonly command: string;
|
|
13
|
+
readonly cwd: string;
|
|
14
|
+
readonly env: Record<string, string | undefined>;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Compute the (possibly rewritten) Bash command. Returns the original
|
|
18
|
+
* unchanged when:
|
|
19
|
+
*
|
|
20
|
+
* - The command does not match any Vitest pattern
|
|
21
|
+
* - `VITEST_AGENT_CONVERSATION_ID` or `VITEST_AGENT_AGENT_ID` is
|
|
22
|
+
* missing from env (no agent context to attribute to)
|
|
23
|
+
*
|
|
24
|
+
* Always synchronous — the package.json read is the only I/O and is
|
|
25
|
+
* fast enough not to need Effect wrapping.
|
|
26
|
+
* @public
|
|
27
|
+
*/
|
|
28
|
+
declare const injectEnv: (input: InjectEnvInput) => string;
|
|
29
|
+
//#endregion
|
|
30
|
+
//#region src/sidecar-dispatch.d.ts
|
|
31
|
+
/** Result of a {@link dispatch} call: captured stdout/stderr + exit code.
|
|
32
|
+
* @public
|
|
33
|
+
*/
|
|
34
|
+
interface DispatchResult {
|
|
35
|
+
readonly stdout: string;
|
|
36
|
+
readonly stderr: string;
|
|
37
|
+
readonly code: number;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Dispatch one argv invocation. `argv` is the post-`node post-bin`
|
|
41
|
+
* slice — i.e. `process.argv.slice(2)`. Never throws: every failure is
|
|
42
|
+
* folded into the returned {@link DispatchResult}.
|
|
43
|
+
* @public
|
|
44
|
+
*/
|
|
45
|
+
declare const dispatch: (argv: readonly string[]) => Promise<DispatchResult>;
|
|
46
|
+
//#endregion
|
|
47
|
+
export { type DispatchResult, type InjectEnvInput, dispatch, exitCodeForTag, injectEnv };
|
|
48
|
+
//# sourceMappingURL=dispatch.d.ts.map
|
package/dispatch.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Data } from "effect";
|
|
2
|
+
|
|
3
|
+
//#region src/errors/AgentErrors.ts
|
|
4
|
+
/**
|
|
5
|
+
* `agent_id` does not exist for the given chat. Surface to the
|
|
6
|
+
* caller; the typical resolution is to call `register_agent` for
|
|
7
|
+
* this agent first.
|
|
8
|
+
* @public
|
|
9
|
+
*/
|
|
10
|
+
var AgentNotFoundError = class extends Data.TaggedError("AgentNotFoundError") {};
|
|
11
|
+
/**
|
|
12
|
+
* Session row for the given `chat_id` does not exist. The host
|
|
13
|
+
* probably hasn't fired the SessionStart equivalent for this chat yet.
|
|
14
|
+
* @public
|
|
15
|
+
*/
|
|
16
|
+
var SessionNotFoundError = class extends Data.TaggedError("SessionNotFoundError") {};
|
|
17
|
+
/**
|
|
18
|
+
* `conversation_id` does not exist. Used by rollup queries that
|
|
19
|
+
* require a conversation row.
|
|
20
|
+
* @public
|
|
21
|
+
*/
|
|
22
|
+
var ConversationNotFoundError = class extends Data.TaggedError("ConversationNotFoundError") {};
|
|
23
|
+
/**
|
|
24
|
+
* True conflict — e.g., the `parentAgentId` supplied to
|
|
25
|
+
* `register_agent` refers to a different session than the one the
|
|
26
|
+
* caller named. Distinct from `IdempotencyHit`, which is a success.
|
|
27
|
+
* @public
|
|
28
|
+
*/
|
|
29
|
+
var RegistrationConflictError = class extends Data.TaggedError("RegistrationConflictError") {
|
|
30
|
+
constructor(args) {
|
|
31
|
+
super(args);
|
|
32
|
+
Object.defineProperty(this, "message", {
|
|
33
|
+
value: `[register_agent] ${args.reason}`,
|
|
34
|
+
enumerable: true,
|
|
35
|
+
writable: false,
|
|
36
|
+
configurable: true
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* `host_metadata` JSON column failed to parse on read. Usually mapped
|
|
42
|
+
* to `DataStoreError` at the row-read boundary; surfaces alone in
|
|
43
|
+
* paths that explicitly carry through `Schema.parseJson` errors.
|
|
44
|
+
* @public
|
|
45
|
+
*/
|
|
46
|
+
var HostMetadataParseError = class extends Data.TaggedError("HostMetadataParseError") {};
|
|
47
|
+
/**
|
|
48
|
+
* Sidecar exceeded its timeout. Surfaces from the sidecar CLI's
|
|
49
|
+
* `Effect.timeout`; the top-level wrapper maps it to exit code 2.
|
|
50
|
+
* @public
|
|
51
|
+
*/
|
|
52
|
+
var SidecarTimeoutError = class extends Data.TaggedError("SidecarTimeoutError") {
|
|
53
|
+
constructor(args) {
|
|
54
|
+
super(args);
|
|
55
|
+
Object.defineProperty(this, "message", {
|
|
56
|
+
value: `[sidecar ${args.operation}] timed out after ${args.timeoutMs}ms`,
|
|
57
|
+
enumerable: true,
|
|
58
|
+
writable: false,
|
|
59
|
+
configurable: true
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
//#endregion
|
|
65
|
+
export { AgentNotFoundError, ConversationNotFoundError, HostMetadataParseError, RegistrationConflictError, SessionNotFoundError, SidecarTimeoutError };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Data } from "effect";
|
|
2
|
+
|
|
3
|
+
//#region src/errors/DataStoreError.ts
|
|
4
|
+
/**
|
|
5
|
+
* Error raised when a SQLite operation fails at read, write, or migration time.
|
|
6
|
+
* @public
|
|
7
|
+
*/
|
|
8
|
+
var DataStoreError = class extends Data.TaggedError("DataStoreError") {
|
|
9
|
+
constructor(args) {
|
|
10
|
+
super(args);
|
|
11
|
+
Object.defineProperty(this, "message", {
|
|
12
|
+
value: `[${args.operation} ${args.table}] ${args.reason}`,
|
|
13
|
+
enumerable: true,
|
|
14
|
+
writable: false,
|
|
15
|
+
configurable: true
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Extract a human-readable reason string from an Effect SqlError or unknown error.
|
|
21
|
+
*
|
|
22
|
+
* SqlError wraps the underlying better-sqlite3 error in `cause`. The actual SQLite
|
|
23
|
+
* message (e.g. "SQLITE_BUSY: database is locked", "UNIQUE constraint failed: ...")
|
|
24
|
+
* lives on `cause.message`, while the SqlError's own `message` is generic
|
|
25
|
+
* ("Failed to execute statement"). Prefer the cause's message when available.
|
|
26
|
+
* @public
|
|
27
|
+
*/
|
|
28
|
+
const extractSqlReason = (e) => {
|
|
29
|
+
const err = e;
|
|
30
|
+
if (err && typeof err === "object") {
|
|
31
|
+
if (err.cause) {
|
|
32
|
+
if (typeof err.cause === "string") return err.cause;
|
|
33
|
+
if (typeof err.cause === "object" && typeof err.cause.message === "string") return err.cause.message;
|
|
34
|
+
}
|
|
35
|
+
if (typeof err.message === "string" && err.message.length > 0) return err.message;
|
|
36
|
+
try {
|
|
37
|
+
return JSON.stringify(e);
|
|
38
|
+
} catch {}
|
|
39
|
+
}
|
|
40
|
+
return String(e);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
//#endregion
|
|
44
|
+
export { DataStoreError, extractSqlReason };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Data } from "effect";
|
|
2
|
+
|
|
3
|
+
//#region src/errors/DiscoveryError.ts
|
|
4
|
+
/**
|
|
5
|
+
* Error raised when project discovery (glob, file read, or stat) fails.
|
|
6
|
+
* @public
|
|
7
|
+
*/
|
|
8
|
+
var DiscoveryError = class extends Data.TaggedError("DiscoveryError") {
|
|
9
|
+
constructor(args) {
|
|
10
|
+
super(args);
|
|
11
|
+
Object.defineProperty(this, "message", {
|
|
12
|
+
value: `[${args.operation} ${args.path}] ${args.reason}`,
|
|
13
|
+
enumerable: true,
|
|
14
|
+
writable: false,
|
|
15
|
+
configurable: true
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
//#endregion
|
|
21
|
+
export { DiscoveryError };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Data } from "effect";
|
|
2
|
+
|
|
3
|
+
//#region src/errors/PathResolutionError.ts
|
|
4
|
+
/**
|
|
5
|
+
* Error raised when the data directory path cannot be resolved.
|
|
6
|
+
*
|
|
7
|
+
* The most common case is missing workspace identity: no `projectKey`
|
|
8
|
+
* override in `vitest-agent.config.toml` and no `name` field in
|
|
9
|
+
* the root workspace's `package.json`. The path is a function of the
|
|
10
|
+
* workspace's identity, so without one we have no canonical location to
|
|
11
|
+
* write to.
|
|
12
|
+
* @public
|
|
13
|
+
*/
|
|
14
|
+
var PathResolutionError = class extends Data.TaggedError("PathResolutionError") {
|
|
15
|
+
constructor(args) {
|
|
16
|
+
super(args);
|
|
17
|
+
Object.defineProperty(this, "message", {
|
|
18
|
+
value: args.reason,
|
|
19
|
+
enumerable: true,
|
|
20
|
+
writable: false,
|
|
21
|
+
configurable: true
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
//#endregion
|
|
27
|
+
export { PathResolutionError };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Data } from "effect";
|
|
2
|
+
|
|
3
|
+
//#region src/errors/ProjectIdentityError.ts
|
|
4
|
+
/**
|
|
5
|
+
* Raised at plugin / sidecar / MCP boot when the project's identity
|
|
6
|
+
* cannot be resolved through the documented fallback chain. The error
|
|
7
|
+
* carries the full list of attempted sources so the user knows which
|
|
8
|
+
* lever to pull (set `projectId` explicitly, configure the TOML file,
|
|
9
|
+
* add a git remote, or set `name` in `package.json`).
|
|
10
|
+
*
|
|
11
|
+
* Replaces the legacy `WorkspaceRootNotFoundError` for callers that
|
|
12
|
+
* went through the new resolver.
|
|
13
|
+
* @public
|
|
14
|
+
*/
|
|
15
|
+
var ProjectIdentityNotResolvableError = class extends Data.TaggedError("ProjectIdentityNotResolvableError") {
|
|
16
|
+
constructor(args) {
|
|
17
|
+
super(args);
|
|
18
|
+
const summary = args.tried.length === 0 ? "no sources attempted" : args.tried.map((c) => `${c.source}: ${c.reason}`).join("; ");
|
|
19
|
+
Object.defineProperty(this, "message", {
|
|
20
|
+
value: `Project identity could not be resolved (${summary}). Set projectId explicitly in vitest.config.ts, projectKey in vitest-agent.config.toml, configure a git remote, or add "name" to package.json.`,
|
|
21
|
+
enumerable: true,
|
|
22
|
+
writable: false,
|
|
23
|
+
configurable: true
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
//#endregion
|
|
29
|
+
export { ProjectIdentityNotResolvableError };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Data } from "effect";
|
|
2
|
+
|
|
3
|
+
//#region src/errors/RunContextError.ts
|
|
4
|
+
/**
|
|
5
|
+
* Surfaces unexpected git-command failures from `RunContext`. Expected
|
|
6
|
+
* non-zero exits (e.g., "not a git repository") are absorbed via
|
|
7
|
+
* `Effect.option` and surface as `null` columns — they are not errors.
|
|
8
|
+
* This error is reserved for "the executable is missing" /
|
|
9
|
+
* "permission denied" / other system-level failures the caller cannot
|
|
10
|
+
* meaningfully recover from.
|
|
11
|
+
* @public
|
|
12
|
+
*/
|
|
13
|
+
var GitCommandError = class extends Data.TaggedError("GitCommandError") {
|
|
14
|
+
constructor(args) {
|
|
15
|
+
super(args);
|
|
16
|
+
Object.defineProperty(this, "message", {
|
|
17
|
+
value: `[git ${args.command}] ${args.reason}`,
|
|
18
|
+
enumerable: true,
|
|
19
|
+
writable: false,
|
|
20
|
+
configurable: true
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Internal probe miss — consumed by `Effect.firstSuccessOf` /
|
|
26
|
+
* `Effect.orElse` inside the host-metadata probe chain. Never surfaces
|
|
27
|
+
* to the caller of `RunContext.captureRunContext`; the public API
|
|
28
|
+
* always returns a complete `RunContext` (with NULLs for missed
|
|
29
|
+
* probes).
|
|
30
|
+
* @internal
|
|
31
|
+
*/
|
|
32
|
+
var ProbeMissError = class extends Data.TaggedError("ProbeMissError") {};
|
|
33
|
+
|
|
34
|
+
//#endregion
|
|
35
|
+
export { GitCommandError, ProbeMissError };
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { Data } from "effect";
|
|
2
|
+
|
|
3
|
+
//#region src/errors/TddErrors.ts
|
|
4
|
+
/** @public */
|
|
5
|
+
var GoalNotFoundError = class extends Data.TaggedError("GoalNotFoundError") {
|
|
6
|
+
constructor(args) {
|
|
7
|
+
super(args);
|
|
8
|
+
Object.defineProperty(this, "message", {
|
|
9
|
+
value: `[goal not_found id=${args.id}] ${args.reason}`,
|
|
10
|
+
enumerable: true,
|
|
11
|
+
writable: false,
|
|
12
|
+
configurable: true
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
/** @public */
|
|
17
|
+
var BehaviorNotFoundError = class extends Data.TaggedError("BehaviorNotFoundError") {
|
|
18
|
+
constructor(args) {
|
|
19
|
+
super(args);
|
|
20
|
+
Object.defineProperty(this, "message", {
|
|
21
|
+
value: `[behavior not_found id=${args.id}] ${args.reason}`,
|
|
22
|
+
enumerable: true,
|
|
23
|
+
writable: false,
|
|
24
|
+
configurable: true
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
/** @public */
|
|
29
|
+
var TddTaskNotFoundError = class extends Data.TaggedError("TddTaskNotFoundError") {
|
|
30
|
+
constructor(args) {
|
|
31
|
+
super(args);
|
|
32
|
+
Object.defineProperty(this, "message", {
|
|
33
|
+
value: `[tdd_task not_found id=${args.id}] ${args.reason}`,
|
|
34
|
+
enumerable: true,
|
|
35
|
+
writable: false,
|
|
36
|
+
configurable: true
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
/** @public */
|
|
41
|
+
var TddTaskAlreadyEndedError = class extends Data.TaggedError("TddTaskAlreadyEndedError") {
|
|
42
|
+
constructor(args) {
|
|
43
|
+
super(args);
|
|
44
|
+
Object.defineProperty(this, "message", {
|
|
45
|
+
value: `[tdd_task ended id=${args.id}] outcome=${args.outcome} endedAt=${args.endedAt}`,
|
|
46
|
+
enumerable: true,
|
|
47
|
+
writable: false,
|
|
48
|
+
configurable: true
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
/** @public */
|
|
53
|
+
var IllegalStatusTransitionError = class extends Data.TaggedError("IllegalStatusTransitionError") {
|
|
54
|
+
constructor(args) {
|
|
55
|
+
super(args);
|
|
56
|
+
Object.defineProperty(this, "message", {
|
|
57
|
+
value: `[${args.entity} illegal_transition id=${args.id}] ${args.from} → ${args.to}: ${args.reason}`,
|
|
58
|
+
enumerable: true,
|
|
59
|
+
writable: false,
|
|
60
|
+
configurable: true
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
//#endregion
|
|
66
|
+
export { BehaviorNotFoundError, GoalNotFoundError, IllegalStatusTransitionError, TddTaskAlreadyEndedError, TddTaskNotFoundError };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
//#region src/exit-code-for-tag.ts
|
|
2
|
+
/**
|
|
3
|
+
* Map a tagged-error `_tag` to the agent-agnostic sidecar exit-code
|
|
4
|
+
* taxonomy. Unknown tags collapse to `5` (other unexpected defect).
|
|
5
|
+
* @public
|
|
6
|
+
*/
|
|
7
|
+
const exitCodeForTag = (tag) => {
|
|
8
|
+
switch (tag) {
|
|
9
|
+
case "RegistrationConflictError": return 1;
|
|
10
|
+
case "SidecarTimeoutError": return 2;
|
|
11
|
+
case "DataStoreError": return 3;
|
|
12
|
+
case "ProjectIdentityNotResolvableError": return 4;
|
|
13
|
+
default: return 5;
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
//#endregion
|
|
18
|
+
export { exitCodeForTag };
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
//#region src/formatters/ci-annotations.ts
|
|
2
|
+
const escapeData = (s) => s.replace(/%/g, "%25").replace(/\n/g, "%0A").replace(/\r/g, "%0D");
|
|
3
|
+
const escapeProperty = (s) => s.replace(/%/g, "%25").replace(/\n/g, "%0A").replace(/\r/g, "%0D").replace(/:/g, "%3A").replace(/,/g, "%2C");
|
|
4
|
+
const STACK_FILE_LINE = /\(([^():\s]+):(\d+):\d+\)/;
|
|
5
|
+
function extractFileAndLine(error, fallbackFile) {
|
|
6
|
+
if (error.stack !== void 0) {
|
|
7
|
+
const m = STACK_FILE_LINE.exec(error.stack);
|
|
8
|
+
if (m) return {
|
|
9
|
+
file: m[1],
|
|
10
|
+
line: Number.parseInt(m[2], 10)
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
return {
|
|
14
|
+
file: fallbackFile,
|
|
15
|
+
line: 1
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function annotateFailure(report) {
|
|
19
|
+
const lines = [];
|
|
20
|
+
for (const mod of report.failed) for (const test of mod.tests) {
|
|
21
|
+
if (test.state !== "failed") continue;
|
|
22
|
+
const errors = test.errors ?? [];
|
|
23
|
+
if (errors.length === 0) {
|
|
24
|
+
lines.push(`::error file=${escapeProperty(mod.file)},line=1,title=${escapeProperty(test.fullName)}::${escapeData(`${test.fullName} failed`)}`);
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
for (const err of errors) {
|
|
28
|
+
const { file, line } = extractFileAndLine(err, mod.file);
|
|
29
|
+
lines.push(`::error file=${escapeProperty(file)},line=${line},title=${escapeProperty(test.fullName)}::${escapeData(err.message)}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
for (const err of report.unhandledErrors) lines.push(`::error title=Unhandled%20error::${escapeData(err.message)}`);
|
|
33
|
+
return lines;
|
|
34
|
+
}
|
|
35
|
+
/** @public */
|
|
36
|
+
const ciAnnotationsFormatter = {
|
|
37
|
+
format: "ci-annotations",
|
|
38
|
+
render: (reports, _context) => {
|
|
39
|
+
const allLines = [];
|
|
40
|
+
let totalPassed = 0;
|
|
41
|
+
let totalFailed = 0;
|
|
42
|
+
let totalSkipped = 0;
|
|
43
|
+
let totalDuration = 0;
|
|
44
|
+
for (const r of reports) {
|
|
45
|
+
allLines.push(...annotateFailure(r));
|
|
46
|
+
totalPassed += r.summary.passed;
|
|
47
|
+
totalFailed += r.summary.failed;
|
|
48
|
+
totalSkipped += r.summary.skipped;
|
|
49
|
+
totalDuration += r.summary.duration;
|
|
50
|
+
}
|
|
51
|
+
if (allLines.length === 0) allLines.push(`::notice::${escapeData(`Vitest: ${totalPassed} passed, ${totalSkipped} skipped (${totalDuration}ms)`)}`);
|
|
52
|
+
return [{
|
|
53
|
+
target: "stdout",
|
|
54
|
+
content: `${allLines.join("\n")}\n`,
|
|
55
|
+
contentType: "text/plain"
|
|
56
|
+
}];
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
//#endregion
|
|
61
|
+
export { ciAnnotationsFormatter };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { formatGfm } from "../utils/format-gfm.js";
|
|
2
|
+
|
|
3
|
+
//#region src/formatters/gfm.ts
|
|
4
|
+
/** @public */
|
|
5
|
+
const GfmFormatter = {
|
|
6
|
+
format: "gfm",
|
|
7
|
+
render: (reports, _context) => {
|
|
8
|
+
const outputs = [];
|
|
9
|
+
const gfm = formatGfm([...reports]);
|
|
10
|
+
if (gfm) outputs.push({
|
|
11
|
+
target: "github-summary",
|
|
12
|
+
content: gfm,
|
|
13
|
+
contentType: "text/markdown"
|
|
14
|
+
});
|
|
15
|
+
return outputs;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
//#endregion
|
|
20
|
+
export { GfmFormatter };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
//#region src/formatters/json.ts
|
|
2
|
+
/** @public */
|
|
3
|
+
const JsonFormatter = {
|
|
4
|
+
format: "json",
|
|
5
|
+
render: (reports, _context) => {
|
|
6
|
+
const outputs = [];
|
|
7
|
+
const json = JSON.stringify(reports, null, 2);
|
|
8
|
+
outputs.push({
|
|
9
|
+
target: "stdout",
|
|
10
|
+
content: json,
|
|
11
|
+
contentType: "application/json"
|
|
12
|
+
});
|
|
13
|
+
return outputs;
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
//#endregion
|
|
18
|
+
export { JsonFormatter };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { formatConsoleMarkdown } from "../utils/format-console.js";
|
|
2
|
+
|
|
3
|
+
//#region src/formatters/markdown.ts
|
|
4
|
+
/**
|
|
5
|
+
* Markdown formatter for surfaces that *render* markdown.
|
|
6
|
+
*
|
|
7
|
+
* Stdout output goes through {@link TerminalFormatter} (plain text +
|
|
8
|
+
* ANSI), not this formatter — markdown syntax (e.g. heading prefixes,
|
|
9
|
+
* backticks, list dashes) is visual noise in a terminal that doesn't
|
|
10
|
+
* render it. This formatter remains for callers that explicitly request
|
|
11
|
+
* `format: "markdown"`, e.g. when piping the rendered output into a
|
|
12
|
+
* Markdown-aware sink.
|
|
13
|
+
* @public
|
|
14
|
+
*/
|
|
15
|
+
const MarkdownFormatter = {
|
|
16
|
+
format: "markdown",
|
|
17
|
+
render: (reports, context) => {
|
|
18
|
+
const outputs = [];
|
|
19
|
+
for (const report of reports) {
|
|
20
|
+
const md = formatConsoleMarkdown(report, {
|
|
21
|
+
consoleOutput: context.detail === "minimal" ? "failures" : "full",
|
|
22
|
+
coverageConsoleLimit: context.coverageConsoleLimit,
|
|
23
|
+
noColor: context.noColor,
|
|
24
|
+
...context.trendSummary !== void 0 ? { trendSummary: context.trendSummary } : {},
|
|
25
|
+
...context.runCommand !== void 0 ? { runCommand: context.runCommand } : {},
|
|
26
|
+
...context.mcp !== void 0 ? { mcp: context.mcp } : {}
|
|
27
|
+
});
|
|
28
|
+
if (md) outputs.push({
|
|
29
|
+
target: "stdout",
|
|
30
|
+
content: md,
|
|
31
|
+
contentType: "text/markdown"
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
return outputs;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
//#endregion
|
|
39
|
+
export { MarkdownFormatter };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { formatTerminal } from "../utils/format-terminal.js";
|
|
2
|
+
import { osc8 } from "../utils/hyperlink.js";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
|
|
5
|
+
//#region src/formatters/terminal.ts
|
|
6
|
+
/**
|
|
7
|
+
* Wrap test-file paths in failing-test rows with `file://` OSC-8
|
|
8
|
+
* hyperlinks. Only fires when the target is stdout and noColor is
|
|
9
|
+
* unset; MCP responses never reach this formatter so they cannot
|
|
10
|
+
* accidentally pick up escape sequences.
|
|
11
|
+
*
|
|
12
|
+
* Failing-test rows are emitted by `renderFailedTest` in
|
|
13
|
+
* `format-terminal.ts` as
|
|
14
|
+
*
|
|
15
|
+
* ` ` + ANSI(red, ✗) + ` ` + path + ` > ` + fullName
|
|
16
|
+
*
|
|
17
|
+
* The ✗ is wrapped in ANSI color escapes whenever color is on (which
|
|
18
|
+
* is always when this function runs — `wrapHyperlinks` is gated on
|
|
19
|
+
* `!noColor`). The optional `(?:\x1b\[\d+m)?` groups around `✗` make
|
|
20
|
+
* the pattern tolerant of those escapes; without them the regex
|
|
21
|
+
* silently never matched and the hyperlink feature was a no-op.
|
|
22
|
+
*
|
|
23
|
+
* The captured path is project-relative (the failing-test row goes
|
|
24
|
+
* through `relativePath()` in `format-terminal.ts`). RFC 8089
|
|
25
|
+
* requires an absolute filesystem path inside a `file://` URL —
|
|
26
|
+
* iTerm2 / WezTerm / Kitty / VSCode all silently fail to open
|
|
27
|
+
* relative targets. Resolve the captured value back to absolute
|
|
28
|
+
* against the cwd before handing it to `osc8`. The display label
|
|
29
|
+
* stays relative so the rendered output is unchanged.
|
|
30
|
+
* @public
|
|
31
|
+
*/
|
|
32
|
+
const FAILED_TEST_ROW = /^( {4}(?:\x1b\[\d+m)?✗(?:\x1b\[\d+m)? )([^ ]+)( > )/gm;
|
|
33
|
+
const wrapHyperlinks = (text, ctx) => text.replace(FAILED_TEST_ROW, (_match, prefix, captured, suffix) => {
|
|
34
|
+
return `${prefix}${osc8(`file://${path.resolve(process.cwd(), captured)}`, captured, { enabled: !ctx.noColor })}${suffix}`;
|
|
35
|
+
});
|
|
36
|
+
/** @public */
|
|
37
|
+
const TerminalFormatter = {
|
|
38
|
+
format: "terminal",
|
|
39
|
+
render: (reports, context) => {
|
|
40
|
+
const text = formatTerminal(reports, {
|
|
41
|
+
noColor: context.noColor,
|
|
42
|
+
coverageConsoleLimit: context.coverageConsoleLimit,
|
|
43
|
+
...context.trendSummary !== void 0 ? { trendSummary: context.trendSummary } : {},
|
|
44
|
+
...context.mcp !== void 0 ? { mcp: context.mcp } : {}
|
|
45
|
+
});
|
|
46
|
+
if (text === "") return [];
|
|
47
|
+
return [{
|
|
48
|
+
target: "stdout",
|
|
49
|
+
content: context.noColor ? text : wrapHyperlinks(text, context),
|
|
50
|
+
contentType: "text/plain"
|
|
51
|
+
}];
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
//#endregion
|
|
56
|
+
export { TerminalFormatter };
|