principles-disciple 1.34.2 → 1.35.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/.dependency-cruiser.json +19 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +6 -3
- package/src/config/defaults/runtime.ts +100 -24
- package/src/core/event-log.ts +87 -20
- package/src/core/nocturnal-candidate-scoring.ts +6 -6
- package/src/core/nocturnal-trinity-types.ts +94 -0
- package/src/core/nocturnal-trinity.ts +35 -99
- package/src/core/session-tracker.ts +7 -6
- package/src/core/system-logger.ts +104 -12
- package/src/core/workspace-dir-service.ts +40 -6
- package/src/core/workspace-dir-validation.ts +5 -37
- package/src/hooks/trajectory-collector.ts +7 -7
- package/src/index.ts +8 -68
- package/src/service/central-sync-service.ts +3 -8
- package/src/service/correction-observer-workflow-manager.ts +2 -2
- package/src/service/evolution-worker.ts +14 -28
- package/src/service/nocturnal-service.ts +62 -43
- package/src/service/subagent-workflow/deep-reflect-workflow-manager.ts +4 -4
- package/src/service/subagent-workflow/empathy-observer-workflow-manager.ts +4 -4
- package/src/service/subagent-workflow/nocturnal-workflow-manager.ts +2 -2
- package/src/service/subagent-workflow/types.ts +69 -3
- package/src/utils/shadow-fingerprint.ts +42 -0
- package/src/utils/workspace-resolver.ts +54 -0
- package/tests/core/workspace-dir-validation.test.ts +1 -1
- package/tests/integration/tool-hooks-workspace-dir.e2e.test.ts +3 -3
- package/vitest.config.ts +53 -6
|
@@ -13,11 +13,11 @@ import type { RuntimeDirectDriver } from './runtime-direct-driver.js';
|
|
|
13
13
|
import { isSubagentRuntimeAvailable } from '../../utils/subagent-probe.js';
|
|
14
14
|
import { buildCritiquePromptV2 } from '../../tools/critique-prompt.js';
|
|
15
15
|
import { WorkflowManagerBase } from './workflow-manager-base.js';
|
|
16
|
+
import { DEEP_REFLECT_TTL_MS } from '../../config/defaults/runtime.js';
|
|
16
17
|
|
|
17
18
|
const WORKFLOW_SESSION_PREFIX = 'agent:main:subagent:workflow-';
|
|
18
19
|
|
|
19
20
|
const DEFAULT_TIMEOUT_MS = 60_000; // Deep-reflect needs more time than empathy
|
|
20
|
-
const DEFAULT_TTL_MS = 10 * 60 * 1000;
|
|
21
21
|
|
|
22
22
|
export interface DeepReflectWorkflowOptions {
|
|
23
23
|
workspaceDir: string;
|
|
@@ -37,7 +37,7 @@ export class DeepReflectWorkflowManager extends WorkflowManagerBase {
|
|
|
37
37
|
workflowType: 'deep-reflect',
|
|
38
38
|
sessionPrefix: WORKFLOW_SESSION_PREFIX,
|
|
39
39
|
defaultTimeoutMs: DEFAULT_TIMEOUT_MS,
|
|
40
|
-
defaultTtlMs:
|
|
40
|
+
defaultTtlMs: DEEP_REFLECT_TTL_MS,
|
|
41
41
|
});
|
|
42
42
|
}
|
|
43
43
|
|
|
@@ -70,7 +70,7 @@ export class DeepReflectWorkflowManager extends WorkflowManagerBase {
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
|
|
73
|
-
|
|
73
|
+
|
|
74
74
|
protected override generateWorkflowId(): string {
|
|
75
75
|
return `wf_dr_${Date.now()}_${Math.random().toString(36).substring(2, 10)}`;
|
|
76
76
|
}
|
|
@@ -98,7 +98,7 @@ export const deepReflectWorkflowSpec: SubagentWorkflowSpec<DeepReflectResult> =
|
|
|
98
98
|
workflowType: 'deep-reflect',
|
|
99
99
|
transport: 'runtime_direct',
|
|
100
100
|
timeoutMs: 60_000,
|
|
101
|
-
ttlMs:
|
|
101
|
+
ttlMs: DEEP_REFLECT_TTL_MS,
|
|
102
102
|
shouldDeleteSessionAfterFinalize: true,
|
|
103
103
|
|
|
104
104
|
buildPrompt(taskInput: unknown, ctx: DeepReflectBuildPromptContext): string {
|
|
@@ -14,11 +14,11 @@ import { trackFriction } from '../../core/session-tracker.js';
|
|
|
14
14
|
import { isSubagentRuntimeAvailable } from '../../utils/subagent-probe.js';
|
|
15
15
|
import { WorkflowManagerBase } from './workflow-manager-base.js';
|
|
16
16
|
import { normalizeSeverity } from '../../core/empathy-types.js';
|
|
17
|
+
import { WORKFLOW_TTL_MS } from '../../config/defaults/runtime.js';
|
|
17
18
|
|
|
18
19
|
const WORKFLOW_SESSION_PREFIX = 'agent:main:subagent:workflow-';
|
|
19
20
|
|
|
20
21
|
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
21
|
-
const DEFAULT_TTL_MS = 5 * 60 * 1000;
|
|
22
22
|
|
|
23
23
|
export interface EmpathyObserverWorkflowOptions {
|
|
24
24
|
workspaceDir: string;
|
|
@@ -38,7 +38,7 @@ export class EmpathyObserverWorkflowManager extends WorkflowManagerBase {
|
|
|
38
38
|
workflowType: 'empathy-observer',
|
|
39
39
|
sessionPrefix: WORKFLOW_SESSION_PREFIX,
|
|
40
40
|
defaultTimeoutMs: DEFAULT_TIMEOUT_MS,
|
|
41
|
-
defaultTtlMs:
|
|
41
|
+
defaultTtlMs: WORKFLOW_TTL_MS,
|
|
42
42
|
});
|
|
43
43
|
}
|
|
44
44
|
|
|
@@ -71,7 +71,7 @@ export class EmpathyObserverWorkflowManager extends WorkflowManagerBase {
|
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
|
|
74
|
-
|
|
74
|
+
|
|
75
75
|
protected override createWorkflowMetadata<TResult>(
|
|
76
76
|
spec: SubagentWorkflowSpec<TResult>,
|
|
77
77
|
options: {
|
|
@@ -105,7 +105,7 @@ export class EmpathyObserverWorkflowManager extends WorkflowManagerBase {
|
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
|
|
108
|
-
|
|
108
|
+
|
|
109
109
|
protected override generateWorkflowId(): string {
|
|
110
110
|
return `wf_${Date.now()}_${Math.random().toString(36).substring(2, 10)}`;
|
|
111
111
|
}
|
|
@@ -36,7 +36,7 @@ import {
|
|
|
36
36
|
} from '../nocturnal-service.js';
|
|
37
37
|
import { type TrinityStageFailure, type TrinityResult } from '../../core/nocturnal-trinity.js';
|
|
38
38
|
import type { TrinityRuntimeAdapter } from '../../core/nocturnal-trinity.js';
|
|
39
|
-
import type { RecentPainContext } from '
|
|
39
|
+
import type { RecentPainContext } from './types.js';
|
|
40
40
|
import * as fs from 'fs';
|
|
41
41
|
import * as path from 'path';
|
|
42
42
|
import { validateNocturnalSnapshotIngress } from '../../core/nocturnal-snapshot-contract.js';
|
|
@@ -394,7 +394,7 @@ export class NocturnalWorkflowManager implements WorkflowManager {
|
|
|
394
394
|
}
|
|
395
395
|
|
|
396
396
|
|
|
397
|
-
|
|
397
|
+
|
|
398
398
|
async notifyLifecycleEvent(
|
|
399
399
|
|
|
400
400
|
_workflowId: string,
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
import type {
|
|
13
13
|
NocturnalArtifact,
|
|
14
|
+
ArbiterResult,
|
|
14
15
|
} from '../../core/nocturnal-arbiter.js';
|
|
15
16
|
import type {
|
|
16
17
|
BoundedAction,
|
|
@@ -18,12 +19,16 @@ import type {
|
|
|
18
19
|
import type {
|
|
19
20
|
NocturnalSessionSnapshot,
|
|
20
21
|
} from '../../core/nocturnal-trajectory-extractor.js';
|
|
21
|
-
import type {
|
|
22
|
-
NocturnalRunDiagnostics,
|
|
23
|
-
} from '../nocturnal-service.js';
|
|
24
22
|
import type {
|
|
25
23
|
TrinityResult,
|
|
26
24
|
} from '../../core/nocturnal-trinity.js';
|
|
25
|
+
import type {
|
|
26
|
+
IdleCheckResult,
|
|
27
|
+
PreflightCheckResult,
|
|
28
|
+
} from '../nocturnal-runtime.js';
|
|
29
|
+
import type {
|
|
30
|
+
NocturnalSelectionResult,
|
|
31
|
+
} from '../nocturnal-target-selector.js';
|
|
27
32
|
|
|
28
33
|
// ── Workflow Transport ────────────────────────────────────────────────────────
|
|
29
34
|
|
|
@@ -366,6 +371,27 @@ export type { PluginLogger } from '../../openclaw-sdk.js';
|
|
|
366
371
|
|
|
367
372
|
// ── Nocturnal Workflow Types ───────────────────────────────────────────────────
|
|
368
373
|
|
|
374
|
+
/**
|
|
375
|
+
* Recent pain context for sleep_reflection tasks.
|
|
376
|
+
* Used by target selector for ranking bias and context enrichment.
|
|
377
|
+
* Originally from evolution-worker.ts, moved here to break circular dependency.
|
|
378
|
+
*/
|
|
379
|
+
export interface RecentPainContext {
|
|
380
|
+
/** Most recent unresolved pain event */
|
|
381
|
+
mostRecent: {
|
|
382
|
+
score: number;
|
|
383
|
+
source: string;
|
|
384
|
+
reason: string;
|
|
385
|
+
timestamp: string;
|
|
386
|
+
/** Session ID where the pain occurred */
|
|
387
|
+
sessionId: string;
|
|
388
|
+
} | null;
|
|
389
|
+
/** Count of pain events in the recent window (for signal strength) */
|
|
390
|
+
recentPainCount: number;
|
|
391
|
+
/** Highest pain score in the recent window */
|
|
392
|
+
recentMaxPainScore: number;
|
|
393
|
+
}
|
|
394
|
+
|
|
369
395
|
/**
|
|
370
396
|
* Nocturnal workflow result type.
|
|
371
397
|
* Mirrors NocturnalRunResult from nocturnal-service.ts (per D-02).
|
|
@@ -381,3 +407,43 @@ export type NocturnalResult = {
|
|
|
381
407
|
diagnostics: NocturnalRunDiagnostics;
|
|
382
408
|
trinityTelemetry?: TrinityResult['telemetry'];
|
|
383
409
|
};
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Diagnostics from each pipeline stage.
|
|
413
|
+
* Duplicated from nocturnal-service.ts to break circular dependency.
|
|
414
|
+
*/
|
|
415
|
+
export interface NocturnalRunDiagnostics {
|
|
416
|
+
/** Pre-flight check result */
|
|
417
|
+
preflight: PreflightCheckResult | null;
|
|
418
|
+
/** Selection result */
|
|
419
|
+
selection: NocturnalSelectionResult | null;
|
|
420
|
+
/** Idle check result */
|
|
421
|
+
idle: IdleCheckResult | null;
|
|
422
|
+
/** Whether Trinity chain was attempted */
|
|
423
|
+
trinityAttempted: boolean;
|
|
424
|
+
/** Trinity result (if trinityAttempted === true) */
|
|
425
|
+
trinityResult: TrinityResult | null;
|
|
426
|
+
/** Which chain mode was used */
|
|
427
|
+
chainModeUsed: 'trinity' | 'single-reflector' | null;
|
|
428
|
+
/** Arbiter validation result */
|
|
429
|
+
arbiterResult: ArbiterResult | null;
|
|
430
|
+
/** Executability validation result (if arbiter passed) */
|
|
431
|
+
executabilityResult: { executable: boolean; failures: string[] } | null;
|
|
432
|
+
/** Whether artifact was persisted */
|
|
433
|
+
persisted: boolean;
|
|
434
|
+
/** Persistence path (if persisted) */
|
|
435
|
+
persistedPath?: string;
|
|
436
|
+
/** Code-candidate sidecar diagnostics */
|
|
437
|
+
artificer: NocturnalArtificerDiagnostics;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
export interface NocturnalArtificerDiagnostics {
|
|
441
|
+
status: 'skipped' | 'validation_failed' | 'persisted_candidate';
|
|
442
|
+
reason?:
|
|
443
|
+
| 'behavioral_artifact_unavailable'
|
|
444
|
+
| 'no_deterministic_rule'
|
|
445
|
+
| 'persistence_failed'
|
|
446
|
+
| 'cancelled';
|
|
447
|
+
validationErrors?: string[];
|
|
448
|
+
persistedPath?: string;
|
|
449
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shadow Observation Fingerprint Utilities
|
|
3
|
+
*
|
|
4
|
+
* Computes fingerprints for shadow task routing and tracking.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as crypto from 'crypto';
|
|
8
|
+
import type { PluginHookSubagentSpawningEvent } from '../openclaw-sdk.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* PD local worker profiles that are managed by the shadow routing policy.
|
|
12
|
+
*/
|
|
13
|
+
const _pdLocalProfiles = new Set(['local-reader', 'local-editor']);
|
|
14
|
+
export const PD_LOCAL_PROFILES: ReadonlySet<string> = _pdLocalProfiles;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Check if a profile is a local PD profile.
|
|
18
|
+
*/
|
|
19
|
+
export function isLocalProfile(profile: string): boolean {
|
|
20
|
+
return _pdLocalProfiles.has(profile);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const RUNTIME_SHADOW_FINGERPRINT_HEX_LENGTH = 16;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Compute a fingerprint for runtime shadow task tracking.
|
|
27
|
+
* Used to correlate shadow routing decisions with subagent lifecycle events.
|
|
28
|
+
*/
|
|
29
|
+
export function computeRuntimeShadowTaskFingerprint(
|
|
30
|
+
event: PluginHookSubagentSpawningEvent,
|
|
31
|
+
): string {
|
|
32
|
+
const payload = {
|
|
33
|
+
childSessionKey: event.childSessionKey,
|
|
34
|
+
agentId: event.agentId,
|
|
35
|
+
label: event.label ?? '',
|
|
36
|
+
mode: event.mode,
|
|
37
|
+
threadRequested: event.threadRequested,
|
|
38
|
+
requesterChannel: event.requester?.channel ?? '',
|
|
39
|
+
requesterThreadId: event.requester?.threadId ?? '',
|
|
40
|
+
};
|
|
41
|
+
return crypto.createHash('sha256').update(JSON.stringify(payload)).digest('hex').slice(0, RUNTIME_SHADOW_FINGERPRINT_HEX_LENGTH);
|
|
42
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace Directory Resolution Utilities
|
|
3
|
+
*
|
|
4
|
+
* Shared helpers for resolving workspace directories across commands and hooks.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { OpenClawPluginApi } from '../openclaw-sdk.js';
|
|
8
|
+
import { validateWorkspaceDir, type WorkspaceResolutionContext } from '../core/workspace-dir-validation.js';
|
|
9
|
+
import { resolveWorkspaceDir } from '../core/workspace-dir-service.js';
|
|
10
|
+
import { resolveWorkspaceDirFromApi } from '../core/path-resolver.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Resolve workspace directory for command execution.
|
|
14
|
+
*
|
|
15
|
+
* Chain: ctx.workspaceDir → resolveWorkspaceDirFromApi (official OpenClaw API + env vars)
|
|
16
|
+
*
|
|
17
|
+
* CRITICAL: Throws if workspaceDir cannot be resolved. Silent failures are dangerous
|
|
18
|
+
* because commands might operate on the wrong directory.
|
|
19
|
+
*/
|
|
20
|
+
export function resolveCommandWorkspaceDir(
|
|
21
|
+
api: OpenClawPluginApi,
|
|
22
|
+
ctx: { workspaceDir?: string },
|
|
23
|
+
): string {
|
|
24
|
+
// 1. Direct from command context (most reliable — set by OpenClaw for current session)
|
|
25
|
+
if (ctx.workspaceDir) {
|
|
26
|
+
const issue = validateWorkspaceDir(ctx.workspaceDir);
|
|
27
|
+
if (!issue) return ctx.workspaceDir;
|
|
28
|
+
api.logger.error(`[PD:Command] ctx.workspaceDir="${ctx.workspaceDir}" is invalid: ${issue}`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// 2. Official OpenClaw API → env vars → config file
|
|
32
|
+
const resolved = resolveWorkspaceDirFromApi(api);
|
|
33
|
+
if (resolved) return resolved;
|
|
34
|
+
|
|
35
|
+
// CRITICAL FAILURE: Cannot determine workspace directory
|
|
36
|
+
const errorMsg = `[PD:Command] CRITICAL: Cannot resolve workspace directory. ` +
|
|
37
|
+
`ctx.workspaceDir="${ctx.workspaceDir}" is invalid, and all fallbacks failed. ` +
|
|
38
|
+
`Commands will NOT execute to prevent data corruption.`;
|
|
39
|
+
api.logger.error(errorMsg);
|
|
40
|
+
|
|
41
|
+
throw new Error(errorMsg);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Resolve workspace directory for tool hook execution (safe version).
|
|
46
|
+
* Returns undefined instead of throwing if resolution fails.
|
|
47
|
+
*/
|
|
48
|
+
export function resolveToolHookWorkspaceDirSafe(
|
|
49
|
+
ctx: WorkspaceResolutionContext,
|
|
50
|
+
api: OpenClawPluginApi,
|
|
51
|
+
source: string,
|
|
52
|
+
): string | undefined {
|
|
53
|
+
return resolveWorkspaceDir(api, ctx, { source });
|
|
54
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
2
|
import * as os from 'os';
|
|
3
|
-
import { validateWorkspaceDir, resolveValidWorkspaceDir, logWorkspaceDirHealth } from '../../src/core/workspace-dir-
|
|
3
|
+
import { validateWorkspaceDir, resolveValidWorkspaceDir, logWorkspaceDirHealth } from '../../src/core/workspace-dir-service.js';
|
|
4
4
|
|
|
5
5
|
const homeDir = os.homedir();
|
|
6
6
|
|
|
@@ -65,7 +65,7 @@ describe('E2E: Tool Hooks workspaceDir Resolution', () => {
|
|
|
65
65
|
|
|
66
66
|
describe('Scenario 2: ctx.workspaceDir is undefined (current OpenClaw behavior)', () => {
|
|
67
67
|
it('should fallback to agentId resolution', async () => {
|
|
68
|
-
const { resolveValidWorkspaceDir } = await import('../../src/core/workspace-dir-
|
|
68
|
+
const { resolveValidWorkspaceDir } = await import('../../src/core/workspace-dir-service.js');
|
|
69
69
|
|
|
70
70
|
const mockApi = createMockApi(testWorkspaceDir);
|
|
71
71
|
const ctx = {
|
|
@@ -80,7 +80,7 @@ describe('E2E: Tool Hooks workspaceDir Resolution', () => {
|
|
|
80
80
|
});
|
|
81
81
|
|
|
82
82
|
it('should refuse to guess a workspace when agentId is also undefined', async () => {
|
|
83
|
-
const { resolveValidWorkspaceDir } = await import('../../src/core/workspace-dir-
|
|
83
|
+
const { resolveValidWorkspaceDir } = await import('../../src/core/workspace-dir-service.js');
|
|
84
84
|
|
|
85
85
|
const mockApi = createMockApi(testWorkspaceDir);
|
|
86
86
|
const ctx = {
|
|
@@ -131,7 +131,7 @@ describe('E2E: Tool Hooks workspaceDir Resolution', () => {
|
|
|
131
131
|
|
|
132
132
|
describe('Scenario 4: Invalid workspace candidates are rejected', () => {
|
|
133
133
|
it('should return undefined when all workspace resolution candidates are invalid', async () => {
|
|
134
|
-
const { resolveValidWorkspaceDir } = await import('../../src/core/workspace-dir-
|
|
134
|
+
const { resolveValidWorkspaceDir } = await import('../../src/core/workspace-dir-service.js');
|
|
135
135
|
|
|
136
136
|
const mockApi = createMockApi(os.homedir());
|
|
137
137
|
mockApi.runtime.agent.resolveAgentWorkspaceDir.mockReturnValue(os.homedir());
|
package/vitest.config.ts
CHANGED
|
@@ -1,12 +1,41 @@
|
|
|
1
1
|
import { defineConfig } from 'vitest/config';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Vitest configuration with test layering
|
|
5
|
+
*
|
|
6
|
+
* LAYERS:
|
|
7
|
+
* - unit: Mock-based tests, no real DB (fast, parallel)
|
|
8
|
+
* - integration: Tests using real SQLite DB (requires threads pool)
|
|
9
|
+
*
|
|
10
|
+
* USAGE:
|
|
11
|
+
* - npm test → run all tests
|
|
12
|
+
* - npm run test:unit → run unit tests only (fast)
|
|
13
|
+
* - npm run test:integration → run integration tests only
|
|
14
|
+
*
|
|
15
|
+
* WHY threads pool?
|
|
16
|
+
* better-sqlite3 native handles don't clean up properly in fork subprocesses,
|
|
17
|
+
* causing teardown hangs. Threads pool handles this correctly.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
// Integration tests: use real SQLite database
|
|
21
|
+
const integrationTests = [
|
|
22
|
+
'tests/core/control-ui-db.test.ts',
|
|
23
|
+
'tests/core/evolution-logger.test.ts',
|
|
24
|
+
'tests/core/nocturnal-e2e.test.ts',
|
|
25
|
+
'tests/core/nocturnal-trajectory-extractor.test.ts',
|
|
26
|
+
'tests/core/replay-engine.test.ts',
|
|
27
|
+
'tests/core/trajectory.test.ts',
|
|
28
|
+
'tests/integration/**/*.test.ts',
|
|
29
|
+
'tests/integration/**/*.test.tsx',
|
|
30
|
+
'tests/service/nocturnal-service-code-candidate.test.ts',
|
|
31
|
+
'tests/service/nocturnal-target-selector.test.ts',
|
|
32
|
+
];
|
|
33
|
+
|
|
3
34
|
export default defineConfig({
|
|
4
35
|
test: {
|
|
5
36
|
environment: 'node',
|
|
6
37
|
include: ['tests/**/*.test.ts', 'tests/**/*.test.tsx'],
|
|
7
|
-
|
|
8
|
-
pool: 'forks',
|
|
9
|
-
// 确保测试完成后进程能正常退出
|
|
38
|
+
pool: 'threads',
|
|
10
39
|
teardownTimeout: 15000,
|
|
11
40
|
coverage: {
|
|
12
41
|
provider: 'v8',
|
|
@@ -18,6 +47,24 @@ export default defineConfig({
|
|
|
18
47
|
branches: 60,
|
|
19
48
|
statements: 70,
|
|
20
49
|
},
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
|
|
50
|
+
},
|
|
51
|
+
// Workspace projects for layered testing
|
|
52
|
+
projects: [
|
|
53
|
+
{
|
|
54
|
+
test: {
|
|
55
|
+
name: 'unit',
|
|
56
|
+
include: ['tests/**/*.test.ts', 'tests/**/*.test.tsx'],
|
|
57
|
+
exclude: integrationTests,
|
|
58
|
+
pool: 'threads',
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
test: {
|
|
63
|
+
name: 'integration',
|
|
64
|
+
include: integrationTests,
|
|
65
|
+
pool: 'threads',
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
},
|
|
70
|
+
});
|