bestviber 0.0.0-stage → 0.2.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.
@@ -0,0 +1,174 @@
1
+ import { describe, expect, test } from 'vite-plus/test'
2
+ import { EMPTY_STATE, decide, projectName } from './tracker.mjs'
3
+
4
+ const at = (hm) => new Date(`2026-11-02T${hm}:00Z`)
5
+ const iso = (hm) => at(hm).toISOString()
6
+ const P = 'amar/bestviber'
7
+ const mine = (overrides = {}) => ({
8
+ id: 's1',
9
+ status: 'active',
10
+ project: P,
11
+ ...overrides,
12
+ })
13
+ const tracking = (lastHm, overrides = {}) => ({
14
+ sessionId: 's1',
15
+ project: P,
16
+ lastActivity: iso(lastHm),
17
+ ...overrides,
18
+ })
19
+
20
+ describe('automatic tracking', () => {
21
+ test('the first prompt starts a session for the project', () => {
22
+ const r = decide({
23
+ state: EMPTY_STATE,
24
+ open: null,
25
+ event: 'prompt',
26
+ now: at('09:00'),
27
+ project: P,
28
+ })
29
+ expect(r.calls).toEqual([
30
+ { name: 'start_focus_session', input: { project: P } },
31
+ ])
32
+ expect(r.state.lastActivity).toBe(iso('09:00'))
33
+ })
34
+
35
+ test('a quick next prompt changes nothing', () => {
36
+ const r = decide({
37
+ state: tracking('09:00'),
38
+ open: mine(),
39
+ event: 'prompt',
40
+ now: at('09:01'),
41
+ project: P,
42
+ })
43
+ expect(r.calls).toEqual([])
44
+ expect(r.state.sessionId).toBe('s1')
45
+ })
46
+
47
+ test('quiet between 2 and 10 minutes becomes a pause in the same session', () => {
48
+ const r = decide({
49
+ state: tracking('09:00'),
50
+ open: mine(),
51
+ event: 'prompt',
52
+ now: at('09:06'),
53
+ project: P,
54
+ })
55
+ expect(r.calls).toEqual([
56
+ { name: 'pause_focus_session', input: { at: iso('09:00') } },
57
+ { name: 'resume_focus_session', input: {} },
58
+ ])
59
+ expect(r.state.sessionId).toBe('s1')
60
+ })
61
+
62
+ test('quiet of 10 minutes or more ends the session when the quiet began', () => {
63
+ const r = decide({
64
+ state: tracking('09:00'),
65
+ open: mine(),
66
+ event: 'prompt',
67
+ now: at('09:30'),
68
+ project: P,
69
+ })
70
+ expect(r.calls).toEqual([
71
+ { name: 'finish_focus_session', input: { at: iso('09:00') } },
72
+ { name: 'start_focus_session', input: { project: P } },
73
+ ])
74
+ expect(r.state.sessionId).toBeNull()
75
+ })
76
+
77
+ test('switching repositories closes the old project and starts the new one', () => {
78
+ const r = decide({
79
+ state: tracking('09:00'),
80
+ open: mine(),
81
+ event: 'prompt',
82
+ now: at('09:01'),
83
+ project: 'amar/website',
84
+ })
85
+ expect(r.calls).toEqual([
86
+ { name: 'finish_focus_session', input: { at: iso('09:01') } },
87
+ { name: 'start_focus_session', input: { project: 'amar/website' } },
88
+ ])
89
+ })
90
+
91
+ test('a finished reply is activity, with no API call', () => {
92
+ const r = decide({
93
+ state: tracking('09:00'),
94
+ open: mine(),
95
+ event: 'reply',
96
+ now: at('09:04'),
97
+ project: P,
98
+ })
99
+ expect(r.calls).toEqual([])
100
+ expect(r.state.lastActivity).toBe(iso('09:04'))
101
+ })
102
+
103
+ test('Claude Code exiting finishes the session, at the last activity if it was quiet', () => {
104
+ expect(
105
+ decide({
106
+ state: tracking('09:00'),
107
+ open: mine(),
108
+ event: 'end',
109
+ now: at('09:20'),
110
+ project: P,
111
+ }).calls,
112
+ ).toEqual([{ name: 'finish_focus_session', input: { at: iso('09:00') } }])
113
+ expect(
114
+ decide({
115
+ state: tracking('09:00'),
116
+ open: mine(),
117
+ event: 'end',
118
+ now: at('09:01'),
119
+ project: P,
120
+ }).calls,
121
+ ).toEqual([{ name: 'finish_focus_session', input: { at: iso('09:01') } }])
122
+ })
123
+
124
+ test('a session someone started by hand is left alone', () => {
125
+ const r = decide({
126
+ state: EMPTY_STATE,
127
+ open: { id: 'manual', status: 'active', project: null },
128
+ event: 'prompt',
129
+ now: at('09:00'),
130
+ project: P,
131
+ })
132
+ expect(r.calls).toEqual([])
133
+ expect(r.state.sessionId).toBeNull()
134
+ })
135
+
136
+ test('if our session was closed elsewhere, the next prompt starts a new one', () => {
137
+ const r = decide({
138
+ state: tracking('09:00'),
139
+ open: null,
140
+ event: 'prompt',
141
+ now: at('09:01'),
142
+ project: P,
143
+ })
144
+ expect(r.calls).toEqual([
145
+ { name: 'start_focus_session', input: { project: P } },
146
+ ])
147
+ })
148
+ })
149
+
150
+ describe('project names', () => {
151
+ test.each([
152
+ ['git@github.com:amaralazizy/bestviber.git', 'amaralazizy/bestviber'],
153
+ ['https://github.com/amaralazizy/bestviber.git', 'amaralazizy/bestviber'],
154
+ ['https://gitlab.com/team/app', 'team/app'],
155
+ ['ssh://git@host:2222/team/app.git', 'team/app'],
156
+ ])('%s', (remote, expected) => {
157
+ expect(projectName({ remote, toplevel: '/x/y', cwd: '/x/y/z' })).toBe(
158
+ expected,
159
+ )
160
+ })
161
+
162
+ test('falls back to the repository folder, then the working folder', () => {
163
+ expect(
164
+ projectName({
165
+ remote: null,
166
+ toplevel: '/home/a/notes',
167
+ cwd: '/home/a/notes/src',
168
+ }),
169
+ ).toBe('notes')
170
+ expect(
171
+ projectName({ remote: null, toplevel: null, cwd: '/home/a/scratch/' }),
172
+ ).toBe('scratch')
173
+ })
174
+ })