speckit-assistant 0.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/.next/BUILD_ID +1 -0
- package/.next/app-build-manifest.json +71 -0
- package/.next/app-path-routes-manifest.json +10 -0
- package/.next/build-manifest.json +33 -0
- package/.next/cache/.previewinfo +1 -0
- package/.next/cache/.rscinfo +1 -0
- package/.next/cache/webpack/client-production/0.pack +0 -0
- package/.next/cache/webpack/client-production/1.pack +0 -0
- package/.next/cache/webpack/client-production/10.pack +0 -0
- package/.next/cache/webpack/client-production/11.pack +0 -0
- package/.next/cache/webpack/client-production/12.pack +0 -0
- package/.next/cache/webpack/client-production/13.pack +0 -0
- package/.next/cache/webpack/client-production/14.pack +0 -0
- package/.next/cache/webpack/client-production/15.pack +0 -0
- package/.next/cache/webpack/client-production/16.pack +0 -0
- package/.next/cache/webpack/client-production/17.pack +0 -0
- package/.next/cache/webpack/client-production/18.pack +0 -0
- package/.next/cache/webpack/client-production/19.pack +0 -0
- package/.next/cache/webpack/client-production/2.pack +0 -0
- package/.next/cache/webpack/client-production/20.pack +0 -0
- package/.next/cache/webpack/client-production/21.pack +0 -0
- package/.next/cache/webpack/client-production/22.pack +0 -0
- package/.next/cache/webpack/client-production/3.pack +0 -0
- package/.next/cache/webpack/client-production/4.pack +0 -0
- package/.next/cache/webpack/client-production/5.pack +0 -0
- package/.next/cache/webpack/client-production/6.pack +0 -0
- package/.next/cache/webpack/client-production/7.pack +0 -0
- package/.next/cache/webpack/client-production/8.pack +0 -0
- package/.next/cache/webpack/client-production/9.pack +0 -0
- package/.next/cache/webpack/client-production/index.pack +0 -0
- package/.next/cache/webpack/client-production/index.pack.old +0 -0
- package/.next/cache/webpack/edge-server-production/0.pack +0 -0
- package/.next/cache/webpack/edge-server-production/index.pack +0 -0
- package/.next/cache/webpack/server-production/0.pack +0 -0
- package/.next/cache/webpack/server-production/1.pack +0 -0
- package/.next/cache/webpack/server-production/10.pack +0 -0
- package/.next/cache/webpack/server-production/11.pack +0 -0
- package/.next/cache/webpack/server-production/12.pack +0 -0
- package/.next/cache/webpack/server-production/13.pack +0 -0
- package/.next/cache/webpack/server-production/14.pack +0 -0
- package/.next/cache/webpack/server-production/15.pack +0 -0
- package/.next/cache/webpack/server-production/16.pack +0 -0
- package/.next/cache/webpack/server-production/17.pack +0 -0
- package/.next/cache/webpack/server-production/18.pack +0 -0
- package/.next/cache/webpack/server-production/19.pack +0 -0
- package/.next/cache/webpack/server-production/2.pack +0 -0
- package/.next/cache/webpack/server-production/20.pack +0 -0
- package/.next/cache/webpack/server-production/3.pack +0 -0
- package/.next/cache/webpack/server-production/4.pack +0 -0
- package/.next/cache/webpack/server-production/5.pack +0 -0
- package/.next/cache/webpack/server-production/6.pack +0 -0
- package/.next/cache/webpack/server-production/7.pack +0 -0
- package/.next/cache/webpack/server-production/8.pack +0 -0
- package/.next/cache/webpack/server-production/9.pack +0 -0
- package/.next/cache/webpack/server-production/index.pack +0 -0
- package/.next/cache/webpack/server-production/index.pack.old +0 -0
- package/.next/diagnostics/build-diagnostics.json +6 -0
- package/.next/diagnostics/framework.json +1 -0
- package/.next/export-marker.json +6 -0
- package/.next/images-manifest.json +58 -0
- package/.next/next-minimal-server.js.nft.json +1 -0
- package/.next/next-server.js.nft.json +1 -0
- package/.next/package.json +1 -0
- package/.next/prerender-manifest.json +61 -0
- package/.next/react-loadable-manifest.json +1 -0
- package/.next/required-server-files.json +320 -0
- package/.next/routes-manifest.json +53 -0
- package/.next/server/app/_not-found/page.js +2 -0
- package/.next/server/app/_not-found/page.js.nft.json +1 -0
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -0
- package/.next/server/app/_not-found.html +1 -0
- package/.next/server/app/_not-found.meta +8 -0
- package/.next/server/app/_not-found.rsc +15 -0
- package/.next/server/app/api/feature/route.js +1 -0
- package/.next/server/app/api/feature/route.js.nft.json +1 -0
- package/.next/server/app/api/feature/route_client-reference-manifest.js +1 -0
- package/.next/server/app/api/file/route.js +1 -0
- package/.next/server/app/api/file/route.js.nft.json +1 -0
- package/.next/server/app/api/file/route_client-reference-manifest.js +1 -0
- package/.next/server/app/api/phase/route.js +4 -0
- package/.next/server/app/api/phase/route.js.nft.json +1 -0
- package/.next/server/app/api/phase/route_client-reference-manifest.js +1 -0
- package/.next/server/app/api/state/route.js +1 -0
- package/.next/server/app/api/state/route.js.nft.json +1 -0
- package/.next/server/app/api/state/route_client-reference-manifest.js +1 -0
- package/.next/server/app/api/state/watch/route.js +4 -0
- package/.next/server/app/api/state/watch/route.js.nft.json +1 -0
- package/.next/server/app/api/state/watch/route_client-reference-manifest.js +1 -0
- package/.next/server/app/api/task/route.js +1 -0
- package/.next/server/app/api/task/route.js.nft.json +1 -0
- package/.next/server/app/api/task/route_client-reference-manifest.js +1 -0
- package/.next/server/app/index.html +1 -0
- package/.next/server/app/index.meta +7 -0
- package/.next/server/app/index.rsc +20 -0
- package/.next/server/app/page.js +14 -0
- package/.next/server/app/page.js.nft.json +1 -0
- package/.next/server/app/page_client-reference-manifest.js +1 -0
- package/.next/server/app-paths-manifest.json +10 -0
- package/.next/server/chunks/355.js +22 -0
- package/.next/server/chunks/6.js +9 -0
- package/.next/server/chunks/607.js +6 -0
- package/.next/server/chunks/643.js +1 -0
- package/.next/server/chunks/897.js +9 -0
- package/.next/server/functions-config-manifest.json +4 -0
- package/.next/server/interception-route-rewrite-manifest.js +1 -0
- package/.next/server/middleware-build-manifest.js +1 -0
- package/.next/server/middleware-manifest.json +6 -0
- package/.next/server/middleware-react-loadable-manifest.js +1 -0
- package/.next/server/next-font-manifest.js +1 -0
- package/.next/server/next-font-manifest.json +1 -0
- package/.next/server/pages/404.html +1 -0
- package/.next/server/pages/500.html +1 -0
- package/.next/server/pages/_app.js +1 -0
- package/.next/server/pages/_app.js.nft.json +1 -0
- package/.next/server/pages/_document.js +1 -0
- package/.next/server/pages/_document.js.nft.json +1 -0
- package/.next/server/pages/_error.js +19 -0
- package/.next/server/pages/_error.js.nft.json +1 -0
- package/.next/server/pages-manifest.json +6 -0
- package/.next/server/server-reference-manifest.js +1 -0
- package/.next/server/server-reference-manifest.json +1 -0
- package/.next/server/webpack-runtime.js +1 -0
- package/.next/static/chunks/590-a6568595ecd2a994.js +1 -0
- package/.next/static/chunks/8381d2c4-9707dccab70b742b.js +1 -0
- package/.next/static/chunks/920-f9bfc1b0d0402c3e.js +1 -0
- package/.next/static/chunks/acfafb44-8249079a6627ac92.js +1 -0
- package/.next/static/chunks/app/_not-found/page-be798b363e27e8c5.js +1 -0
- package/.next/static/chunks/app/api/feature/route-bb3c1a82e892ab58.js +1 -0
- package/.next/static/chunks/app/api/file/route-bb3c1a82e892ab58.js +1 -0
- package/.next/static/chunks/app/api/phase/route-bb3c1a82e892ab58.js +1 -0
- package/.next/static/chunks/app/api/state/route-bb3c1a82e892ab58.js +1 -0
- package/.next/static/chunks/app/api/state/watch/route-bb3c1a82e892ab58.js +1 -0
- package/.next/static/chunks/app/api/task/route-bb3c1a82e892ab58.js +1 -0
- package/.next/static/chunks/app/layout-3226c76a5f7f74bc.js +1 -0
- package/.next/static/chunks/app/page-8a5248f7704cde29.js +1 -0
- package/.next/static/chunks/framework-20dd4f6054cc5446.js +1 -0
- package/.next/static/chunks/main-91029f76ac1b7110.js +1 -0
- package/.next/static/chunks/main-app-b9ad56d6b1dfa941.js +1 -0
- package/.next/static/chunks/pages/_app-aa33dc41c3472021.js +1 -0
- package/.next/static/chunks/pages/_error-78b0b3b591df0e73.js +1 -0
- package/.next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
- package/.next/static/chunks/webpack-c460f8e58a9e9eff.js +1 -0
- package/.next/static/css/008a05b0ad6b854a.css +31 -0
- package/.next/static/css/6fd2e11db3a59771.css +3 -0
- package/.next/static/fWuQ3yoHovA7Cre6u89W7/_buildManifest.js +1 -0
- package/.next/static/fWuQ3yoHovA7Cre6u89W7/_ssgManifest.js +1 -0
- package/.next/trace +3 -0
- package/.next/types/app/api/feature/route.ts +347 -0
- package/.next/types/app/api/file/route.ts +347 -0
- package/.next/types/app/api/phase/route.ts +347 -0
- package/.next/types/app/api/state/route.ts +347 -0
- package/.next/types/app/api/state/watch/route.ts +347 -0
- package/.next/types/app/api/task/route.ts +347 -0
- package/.next/types/app/layout.ts +84 -0
- package/.next/types/app/page.ts +84 -0
- package/.next/types/cache-life.d.ts +141 -0
- package/.next/types/package.json +1 -0
- package/.next/types/routes.d.ts +78 -0
- package/.next/types/validator.ts +124 -0
- package/LICENSE +21 -0
- package/README.md +284 -0
- package/bin/adapters/di.js +9 -0
- package/bin/adapters/primary/api/utils.js +57 -0
- package/bin/adapters/secondary/agent/ProcessAgentRunner.js +161 -0
- package/bin/adapters/secondary/fs/FSWorkspaceRepository.js +283 -0
- package/bin/app/api/feature/route.js +35 -0
- package/bin/app/api/file/route.js +36 -0
- package/bin/app/api/phase/route.js +55 -0
- package/bin/app/api/state/route.js +28 -0
- package/bin/app/api/state/watch/route.js +92 -0
- package/bin/app/api/task/route.js +20 -0
- package/bin/bin/cli.js +317 -0
- package/bin/cli.js +85 -0
- package/bin/domain/models/types.js +2 -0
- package/bin/domain/ports/in/WorkflowUseCases.js +2 -0
- package/bin/domain/ports/out/AgentRunnerPort.js +2 -0
- package/bin/domain/ports/out/WorkspaceRepositoryPort.js +2 -0
- package/bin/domain/services/WorkflowService.js +174 -0
- package/next.config.ts +13 -0
- package/package.json +53 -0
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.WorkflowService = void 0;
|
|
4
|
+
const FEATURE_PHASES = [
|
|
5
|
+
'specification',
|
|
6
|
+
'clarification',
|
|
7
|
+
'planning',
|
|
8
|
+
'checklist',
|
|
9
|
+
'analyze',
|
|
10
|
+
'tasks',
|
|
11
|
+
'taskstoissues',
|
|
12
|
+
'implementation'
|
|
13
|
+
];
|
|
14
|
+
class WorkflowService {
|
|
15
|
+
workspaceRepo;
|
|
16
|
+
agentRunner;
|
|
17
|
+
constructor(workspaceRepo, agentRunner) {
|
|
18
|
+
this.workspaceRepo = workspaceRepo;
|
|
19
|
+
this.agentRunner = agentRunner;
|
|
20
|
+
}
|
|
21
|
+
async getWorkflowState(workspacePath) {
|
|
22
|
+
const state = await this.workspaceRepo.getWorkflowState(workspacePath);
|
|
23
|
+
return state;
|
|
24
|
+
}
|
|
25
|
+
async createFeature(workspacePath, name) {
|
|
26
|
+
const state = await this.workspaceRepo.createFeature(workspacePath, name);
|
|
27
|
+
state.activeFeatureName = name;
|
|
28
|
+
await this.workspaceRepo.saveWorkflowState(workspacePath, state);
|
|
29
|
+
return state;
|
|
30
|
+
}
|
|
31
|
+
async deleteFeature(workspacePath, name) {
|
|
32
|
+
const state = await this.workspaceRepo.deleteFeature(workspacePath, name);
|
|
33
|
+
return state;
|
|
34
|
+
}
|
|
35
|
+
async setActiveFeature(workspacePath, name) {
|
|
36
|
+
const state = await this.workspaceRepo.getWorkflowState(workspacePath);
|
|
37
|
+
if (state.features.some(f => f.name === name)) {
|
|
38
|
+
state.activeFeatureName = name;
|
|
39
|
+
await this.workspaceRepo.saveWorkflowState(workspacePath, state);
|
|
40
|
+
}
|
|
41
|
+
return state;
|
|
42
|
+
}
|
|
43
|
+
async runPhase(workspacePath, phase, featureName, agentConfig, userPrompt, onData) {
|
|
44
|
+
const state = await this.workspaceRepo.getWorkflowState(workspacePath);
|
|
45
|
+
const targetFeature = featureName || state.activeFeatureName;
|
|
46
|
+
// Set status to running
|
|
47
|
+
this.setPhaseStatus(state, phase, targetFeature, 'running');
|
|
48
|
+
await this.workspaceRepo.saveWorkflowState(workspacePath, state);
|
|
49
|
+
try {
|
|
50
|
+
const exitCode = await this.agentRunner.runPhase(workspacePath, phase, targetFeature, agentConfig, userPrompt, onData);
|
|
51
|
+
// Re-read state in case files changed on disk during run
|
|
52
|
+
const freshState = await this.workspaceRepo.getWorkflowState(workspacePath);
|
|
53
|
+
if (exitCode === 0) {
|
|
54
|
+
this.setPhaseStatus(freshState, phase, targetFeature, 'awaiting_review');
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
this.setPhaseStatus(freshState, phase, targetFeature, 'idle');
|
|
58
|
+
}
|
|
59
|
+
// Check if tasks are completed to auto-complete implementation
|
|
60
|
+
if (phase === 'implementation') {
|
|
61
|
+
await this.checkImplementationAutoReview(workspacePath, freshState, targetFeature);
|
|
62
|
+
}
|
|
63
|
+
await this.workspaceRepo.saveWorkflowState(workspacePath, freshState);
|
|
64
|
+
return freshState;
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
const freshState = await this.workspaceRepo.getWorkflowState(workspacePath);
|
|
68
|
+
this.setPhaseStatus(freshState, phase, targetFeature, 'idle');
|
|
69
|
+
await this.workspaceRepo.saveWorkflowState(workspacePath, freshState);
|
|
70
|
+
throw err;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
async approvePhase(workspacePath, phase, featureName) {
|
|
74
|
+
const state = await this.workspaceRepo.getWorkflowState(workspacePath);
|
|
75
|
+
const targetFeature = featureName || state.activeFeatureName;
|
|
76
|
+
const ps = this.findPhase(state, phase, targetFeature);
|
|
77
|
+
if (ps) {
|
|
78
|
+
ps.status = 'approved';
|
|
79
|
+
ps.stale = false;
|
|
80
|
+
}
|
|
81
|
+
await this.workspaceRepo.saveWorkflowState(workspacePath, state);
|
|
82
|
+
return state;
|
|
83
|
+
}
|
|
84
|
+
async discardPhase(workspacePath, phase, featureName) {
|
|
85
|
+
const state = await this.workspaceRepo.getWorkflowState(workspacePath);
|
|
86
|
+
const targetFeature = featureName || state.activeFeatureName;
|
|
87
|
+
const ps = this.findPhase(state, phase, targetFeature);
|
|
88
|
+
if (ps) {
|
|
89
|
+
ps.status = 'idle';
|
|
90
|
+
ps.content = null;
|
|
91
|
+
ps.stale = false;
|
|
92
|
+
}
|
|
93
|
+
this.markDownstreamStale(state, phase, targetFeature);
|
|
94
|
+
await this.workspaceRepo.saveWorkflowState(workspacePath, state);
|
|
95
|
+
return state;
|
|
96
|
+
}
|
|
97
|
+
async toggleTask(workspacePath, featureName, lineIndex, checked) {
|
|
98
|
+
const freshState = await this.workspaceRepo.toggleTask(workspacePath, featureName, lineIndex, checked);
|
|
99
|
+
await this.checkImplementationAutoReview(workspacePath, freshState, featureName);
|
|
100
|
+
await this.workspaceRepo.saveWorkflowState(workspacePath, freshState);
|
|
101
|
+
return freshState;
|
|
102
|
+
}
|
|
103
|
+
async readFile(workspacePath, filePath) {
|
|
104
|
+
return this.workspaceRepo.readFile(workspacePath, filePath);
|
|
105
|
+
}
|
|
106
|
+
async writeFile(workspacePath, filePath, content) {
|
|
107
|
+
await this.workspaceRepo.writeFile(workspacePath, filePath, content);
|
|
108
|
+
// Determine which phase/feature this file belongs to and update status to awaiting_review if not approved
|
|
109
|
+
const state = await this.workspaceRepo.getWorkflowState(workspacePath);
|
|
110
|
+
await this.workspaceRepo.saveWorkflowState(workspacePath, state);
|
|
111
|
+
return state;
|
|
112
|
+
}
|
|
113
|
+
// Helper methods
|
|
114
|
+
findPhase(state, phase, featureName) {
|
|
115
|
+
if (phase === 'constitution') {
|
|
116
|
+
return state.constitutionPhase;
|
|
117
|
+
}
|
|
118
|
+
const feature = state.features.find(f => f.name === featureName);
|
|
119
|
+
if (!feature)
|
|
120
|
+
return null;
|
|
121
|
+
return feature.phases.find(p => p.phase === phase) || null;
|
|
122
|
+
}
|
|
123
|
+
setPhaseStatus(state, phase, featureName, status) {
|
|
124
|
+
const ps = this.findPhase(state, phase, featureName);
|
|
125
|
+
if (ps) {
|
|
126
|
+
ps.status = status;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
markDownstreamStale(state, discarded, featureName) {
|
|
130
|
+
if (discarded === 'constitution') {
|
|
131
|
+
for (const feature of state.features) {
|
|
132
|
+
for (const p of feature.phases) {
|
|
133
|
+
if (p.status === 'approved')
|
|
134
|
+
p.stale = true;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
const feature = state.features.find(f => f.name === featureName);
|
|
140
|
+
if (!feature)
|
|
141
|
+
return;
|
|
142
|
+
const idx = FEATURE_PHASES.indexOf(discarded);
|
|
143
|
+
if (idx === -1)
|
|
144
|
+
return;
|
|
145
|
+
for (let i = idx + 1; i < FEATURE_PHASES.length; i++) {
|
|
146
|
+
const later = feature.phases.find(p => p.phase === FEATURE_PHASES[i]);
|
|
147
|
+
if (later && later.status === 'approved') {
|
|
148
|
+
later.stale = true;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
async checkImplementationAutoReview(workspacePath, state, featureName) {
|
|
153
|
+
const feature = state.features.find(f => f.name === featureName);
|
|
154
|
+
if (!feature)
|
|
155
|
+
return;
|
|
156
|
+
const tasksPhase = feature.phases.find(p => p.phase === 'tasks');
|
|
157
|
+
const implPhase = feature.phases.find(p => p.phase === 'implementation');
|
|
158
|
+
if (!tasksPhase?.filePath || !implPhase)
|
|
159
|
+
return;
|
|
160
|
+
try {
|
|
161
|
+
const content = await this.workspaceRepo.readFile(workspacePath, tasksPhase.filePath);
|
|
162
|
+
const checkboxes = [...content.matchAll(/^\s*(?:[-*]|\d+\.)\s+\[( |x|X)\]/gm)];
|
|
163
|
+
if (checkboxes.length > 0 && checkboxes.every(c => c[1].toLowerCase() === 'x')) {
|
|
164
|
+
if (implPhase.status === 'running' || implPhase.status === 'idle') {
|
|
165
|
+
implPhase.status = 'awaiting_review';
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
// ignore
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
exports.WorkflowService = WorkflowService;
|
package/next.config.ts
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "speckit-assistant",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Visual orchestrator for Spec-Driven Development (SDD) — Next.js, ReactFlow and Hexagonal Architecture",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"speckit-assistant": "./bin/bin/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"dev": "next dev",
|
|
11
|
+
"build": "next build && tsc -p tsconfig.bin.json",
|
|
12
|
+
"start": "next start",
|
|
13
|
+
"test": "vitest run",
|
|
14
|
+
"prepublishOnly": "pnpm run build"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"bin",
|
|
18
|
+
".next",
|
|
19
|
+
"next.config.ts"
|
|
20
|
+
],
|
|
21
|
+
"keywords": [
|
|
22
|
+
"sdd",
|
|
23
|
+
"speckit",
|
|
24
|
+
"nextjs",
|
|
25
|
+
"reactflow",
|
|
26
|
+
"hexagonal-architecture"
|
|
27
|
+
],
|
|
28
|
+
"author": "",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@xyflow/react": "^12.3.5",
|
|
32
|
+
"chokidar": "^5.0.0",
|
|
33
|
+
"class-variance-authority": "^0.7.1",
|
|
34
|
+
"clsx": "^2.1.1",
|
|
35
|
+
"lucide-react": "^0.468.0",
|
|
36
|
+
"next": "^15.1.0",
|
|
37
|
+
"open": "^10.1.0",
|
|
38
|
+
"react": "^19.0.0",
|
|
39
|
+
"react-dom": "^19.0.0",
|
|
40
|
+
"tailwind-merge": "^2.5.5"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/node": "^20.17.9",
|
|
44
|
+
"@types/react": "^19.0.1",
|
|
45
|
+
"@types/react-dom": "^19.0.2",
|
|
46
|
+
"@vitest/coverage-v8": "^2.1.9",
|
|
47
|
+
"autoprefixer": "^10.4.20",
|
|
48
|
+
"postcss": "^8.4.49",
|
|
49
|
+
"tailwindcss": "^3.4.16",
|
|
50
|
+
"typescript": "^5.7.2",
|
|
51
|
+
"vitest": "^2.1.8"
|
|
52
|
+
}
|
|
53
|
+
}
|