1- import { describe , it , expect , beforeEach , afterEach } from 'bun:test' ;
2- import { mkdtempSync , rmSync , writeFileSync , readFileSync , existsSync , mkdirSync } from 'fs' ;
1+ import { describe , it , expect , beforeEach , afterEach } from 'vitest' ;
2+ import {
3+ mkdtempSync ,
4+ rmSync ,
5+ writeFileSync ,
6+ readFileSync ,
7+ existsSync ,
8+ mkdirSync ,
9+ } from 'fs' ;
310import { join } from 'path' ;
411import { tmpdir , homedir } from 'os' ;
5- import { DaemonDesirePathService , type DesirePathConfig } from '../desire-path-service.js' ;
12+ import {
13+ DaemonDesirePathService ,
14+ type DesirePathConfig ,
15+ } from '../desire-path-service.js' ;
616
717// Override SM_DIR for tests by using the service's logAction method
818// which writes to ~/.stackmemory/desire-paths/ — we test the public API
@@ -35,7 +45,9 @@ describe('DaemonDesirePathService', () => {
3545 describe ( 'parseHookEvent' , ( ) => {
3646 it ( 'sanitizes file paths into glob patterns' , ( ) => {
3747 const entry = DaemonDesirePathService . parseHookEvent (
38- 'Read' , '/src/runtime/agent-runner.js' , 'sess-1'
48+ 'Read' ,
49+ '/src/runtime/agent-runner.js' ,
50+ 'sess-1'
3951 ) ;
4052 expect ( entry . tool ) . toBe ( 'Read' ) ;
4153 expect ( entry . target ) . toBe ( '/src/runtime/*.js' ) ;
@@ -44,20 +56,30 @@ describe('DaemonDesirePathService', () => {
4456
4557 it ( 'sanitizes bash commands to command + first arg' , ( ) => {
4658 const entry = DaemonDesirePathService . parseHookEvent (
47- 'Bash' , 'npx jest src/runtime --no-coverage' , 'sess-1'
59+ 'Bash' ,
60+ 'npx jest src/runtime --no-coverage' ,
61+ 'sess-1'
4862 ) ;
4963 expect ( entry . tool ) . toBe ( 'Bash' ) ;
5064 expect ( entry . target ) . toBe ( 'npx jest' ) ;
5165 } ) ;
5266
5367 it ( 'handles empty args' , ( ) => {
54- const entry = DaemonDesirePathService . parseHookEvent ( 'Grep' , '' , 'sess-1' ) ;
68+ const entry = DaemonDesirePathService . parseHookEvent (
69+ 'Grep' ,
70+ '' ,
71+ 'sess-1'
72+ ) ;
5573 expect ( entry . target ) . toBe ( '*' ) ;
5674 } ) ;
5775
5876 it ( 'truncates long args' , ( ) => {
5977 const longArg = 'a' . repeat ( 100 ) ;
60- const entry = DaemonDesirePathService . parseHookEvent ( 'Glob' , longArg , 'sess-1' ) ;
78+ const entry = DaemonDesirePathService . parseHookEvent (
79+ 'Glob' ,
80+ longArg ,
81+ 'sess-1'
82+ ) ;
6183 expect ( entry . target . length ) . toBeLessThanOrEqual ( 50 ) ;
6284 } ) ;
6385 } ) ;
@@ -73,26 +95,74 @@ describe('DaemonDesirePathService', () => {
7395
7496 // Session 1: Read → Edit → Bash
7597 const actions = [
76- { ts : '2026-05-09T10:00:00Z' , sid : 'sess-1' , tool : 'Read' , target : 'src/runtime/*.js' } ,
77- { ts : '2026-05-09T10:00:01Z' , sid : 'sess-1' , tool : 'Edit' , target : 'src/runtime/*.js' } ,
78- { ts : '2026-05-09T10:00:02Z' , sid : 'sess-1' , tool : 'Bash' , target : 'npx jest' } ,
98+ {
99+ ts : '2026-05-09T10:00:00Z' ,
100+ sid : 'sess-1' ,
101+ tool : 'Read' ,
102+ target : 'src/runtime/*.js' ,
103+ } ,
104+ {
105+ ts : '2026-05-09T10:00:01Z' ,
106+ sid : 'sess-1' ,
107+ tool : 'Edit' ,
108+ target : 'src/runtime/*.js' ,
109+ } ,
110+ {
111+ ts : '2026-05-09T10:00:02Z' ,
112+ sid : 'sess-1' ,
113+ tool : 'Bash' ,
114+ target : 'npx jest' ,
115+ } ,
79116 // Session 2: same pattern
80- { ts : '2026-05-09T11:00:00Z' , sid : 'sess-2' , tool : 'Read' , target : 'src/runtime/*.js' } ,
81- { ts : '2026-05-09T11:00:01Z' , sid : 'sess-2' , tool : 'Edit' , target : 'src/runtime/*.js' } ,
82- { ts : '2026-05-09T11:00:02Z' , sid : 'sess-2' , tool : 'Bash' , target : 'npx jest' } ,
117+ {
118+ ts : '2026-05-09T11:00:00Z' ,
119+ sid : 'sess-2' ,
120+ tool : 'Read' ,
121+ target : 'src/runtime/*.js' ,
122+ } ,
123+ {
124+ ts : '2026-05-09T11:00:01Z' ,
125+ sid : 'sess-2' ,
126+ tool : 'Edit' ,
127+ target : 'src/runtime/*.js' ,
128+ } ,
129+ {
130+ ts : '2026-05-09T11:00:02Z' ,
131+ sid : 'sess-2' ,
132+ tool : 'Bash' ,
133+ target : 'npx jest' ,
134+ } ,
83135 // Session 3: same pattern again
84- { ts : '2026-05-09T12:00:00Z' , sid : 'sess-3' , tool : 'Read' , target : 'src/runtime/*.js' } ,
85- { ts : '2026-05-09T12:00:01Z' , sid : 'sess-3' , tool : 'Edit' , target : 'src/runtime/*.js' } ,
86- { ts : '2026-05-09T12:00:02Z' , sid : 'sess-3' , tool : 'Bash' , target : 'npx jest' } ,
136+ {
137+ ts : '2026-05-09T12:00:00Z' ,
138+ sid : 'sess-3' ,
139+ tool : 'Read' ,
140+ target : 'src/runtime/*.js' ,
141+ } ,
142+ {
143+ ts : '2026-05-09T12:00:01Z' ,
144+ sid : 'sess-3' ,
145+ tool : 'Edit' ,
146+ target : 'src/runtime/*.js' ,
147+ } ,
148+ {
149+ ts : '2026-05-09T12:00:02Z' ,
150+ sid : 'sess-3' ,
151+ tool : 'Bash' ,
152+ target : 'npx jest' ,
153+ } ,
87154 ] ;
88155
89- writeFileSync ( streamFile , actions . map ( a => JSON . stringify ( a ) ) . join ( '\n' ) + '\n' ) ;
156+ writeFileSync (
157+ streamFile ,
158+ actions . map ( ( a ) => JSON . stringify ( a ) ) . join ( '\n' ) + '\n'
159+ ) ;
90160
91161 const patterns = service . detectPatterns ( ) ;
92162
93163 expect ( patterns . length ) . toBeGreaterThan ( 0 ) ;
94164 // Should find the Read→Edit→Bash sequence
95- const fullPattern = patterns . find ( p => p . sequence . length === 3 ) ;
165+ const fullPattern = patterns . find ( ( p ) => p . sequence . length === 3 ) ;
96166 expect ( fullPattern ) . toBeDefined ( ) ;
97167 expect ( fullPattern ! . frequency ) . toBeGreaterThanOrEqual ( 3 ) ;
98168 expect ( fullPattern ! . sessions ) . toBeGreaterThanOrEqual ( 2 ) ;
@@ -113,16 +183,22 @@ describe('DaemonDesirePathService', () => {
113183 it ( 'generates skill markdown from patterns' , ( ) => {
114184 const service = new DaemonDesirePathService ( config , onLog ) ;
115185
116- const patterns = [ {
117- id : 'test-1' ,
118- sequence : [ 'Read:src/runtime/*.js' , 'Edit:src/runtime/*.js' , 'Bash:npx jest' ] ,
119- frequency : 5 ,
120- sessions : 3 ,
121- avg_steps : 3 ,
122- first_seen : '2026-05-09T10:00:00Z' ,
123- last_seen : '2026-05-09T12:00:00Z' ,
124- score : 15 ,
125- } ] ;
186+ const patterns = [
187+ {
188+ id : 'test-1' ,
189+ sequence : [
190+ 'Read:src/runtime/*.js' ,
191+ 'Edit:src/runtime/*.js' ,
192+ 'Bash:npx jest' ,
193+ ] ,
194+ frequency : 5 ,
195+ sessions : 3 ,
196+ avg_steps : 3 ,
197+ first_seen : '2026-05-09T10:00:00Z' ,
198+ last_seen : '2026-05-09T12:00:00Z' ,
199+ score : 15 ,
200+ } ,
201+ ] ;
126202
127203 const suggestions = service . generateSuggestions ( patterns ) ;
128204
@@ -133,8 +209,15 @@ describe('DaemonDesirePathService', () => {
133209 expect ( suggestions [ 0 ] . pattern_id ) . toBe ( 'test-1' ) ;
134210
135211 // Check suggestion file was written
136- const suggestionsDir = join ( homedir ( ) , '.stackmemory' , 'desire-paths' , 'suggestions' ) ;
137- const files = require ( 'fs' ) . readdirSync ( suggestionsDir ) . filter ( ( f : string ) => f . endsWith ( '.skill.md' ) ) ;
212+ const suggestionsDir = join (
213+ homedir ( ) ,
214+ '.stackmemory' ,
215+ 'desire-paths' ,
216+ 'suggestions'
217+ ) ;
218+ const files = require ( 'fs' )
219+ . readdirSync ( suggestionsDir )
220+ . filter ( ( f : string ) => f . endsWith ( '.skill.md' ) ) ;
138221 expect ( files . length ) . toBeGreaterThan ( 0 ) ;
139222
140223 // Read and verify markdown structure
@@ -169,7 +252,7 @@ describe('DaemonDesirePathService', () => {
169252 const disabledConfig = { ...config , enabled : false } ;
170253 const service = new DaemonDesirePathService ( disabledConfig , onLog ) ;
171254 service . start ( ) ;
172- expect ( logs . some ( l => l . msg . includes ( 'disabled' ) ) ) . toBe ( true ) ;
255+ expect ( logs . some ( ( l ) => l . msg . includes ( 'disabled' ) ) ) . toBe ( true ) ;
173256 } ) ;
174257 } ) ;
175258
0 commit comments