bingocode 1.1.182 → 1.1.184
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/.claude/scheduled_tasks.json +34 -0
- package/.claude/skills/leanchy/SKILL.md +1 -1
- package/package.json +1 -1
- package/src/bootstrap/state.ts +61 -13
- package/src/hooks/useGoalAutoUpdater.ts +187 -0
- package/src/hooks/useGoalEvaluator.ts +29 -16
- package/src/query.ts +21 -0
- package/src/skills/bundled/goal.ts +41 -32
- package/src/types/goal.ts +322 -0
- package/src/utils/__tests__/goalDag.test.ts +177 -0
- package/src/utils/__tests__/goalDrift.test.ts +132 -0
- package/src/utils/__tests__/goalIntegration.test.ts +217 -0
- package/src/utils/__tests__/goalStore.test.ts +592 -0
- package/src/utils/goalDag.ts +408 -0
- package/src/utils/goalDrift.ts +243 -0
- package/src/utils/goalEvaluator.ts +708 -122
- package/src/utils/goalHelpers.ts +155 -0
- package/src/utils/goalPersistence.ts +206 -0
- package/src/utils/goalStore.ts +463 -0
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration tests — verifies that the goal system components work
|
|
3
|
+
* together in realistic scenarios. Tests cross-cutting concerns:
|
|
4
|
+
* Store → Engine → Evaluator → Drift → Persistence.
|
|
5
|
+
*/
|
|
6
|
+
import { describe, expect, test, beforeEach } from 'bun:test'
|
|
7
|
+
import {
|
|
8
|
+
getState,
|
|
9
|
+
setUserGoal,
|
|
10
|
+
setOperationalGoal,
|
|
11
|
+
addSubGoal,
|
|
12
|
+
updateSubGoalStatus,
|
|
13
|
+
recalculateProgress,
|
|
14
|
+
getReadySubGoals,
|
|
15
|
+
getActiveSubGoals,
|
|
16
|
+
getCompletedSubGoals,
|
|
17
|
+
getAllNodes,
|
|
18
|
+
getDagEngine,
|
|
19
|
+
clear,
|
|
20
|
+
toJSON,
|
|
21
|
+
save,
|
|
22
|
+
load,
|
|
23
|
+
} from '../goalStore.js'
|
|
24
|
+
import { assessDrift, resetDriftHistory } from '../goalDrift.js'
|
|
25
|
+
import type { SubGoal } from '../../types/goal.js'
|
|
26
|
+
|
|
27
|
+
function makeNode(id: string, text: string, deps: string[] = [], status: string = 'pending'): SubGoal {
|
|
28
|
+
return {
|
|
29
|
+
id,
|
|
30
|
+
parentId: 'test' as any,
|
|
31
|
+
text,
|
|
32
|
+
status: status as any,
|
|
33
|
+
dependencies: deps,
|
|
34
|
+
progress: 0,
|
|
35
|
+
createdAt: Date.now(),
|
|
36
|
+
updatedAt: Date.now(),
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
describe('Full goal lifecycle', () => {
|
|
41
|
+
beforeEach(() => {
|
|
42
|
+
clear()
|
|
43
|
+
resetDriftHistory()
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
test('create goal → decompose → execute → verify → complete', () => {
|
|
47
|
+
setUserGoal('Write a TypeScript utility for mesh decimation')
|
|
48
|
+
setOperationalGoal('Implement mesh decimation algorithm with LOD support')
|
|
49
|
+
|
|
50
|
+
// Decompose into sub-goals
|
|
51
|
+
addSubGoal(makeNode('analysis', 'Analyze existing mesh format', [], 'pending'))
|
|
52
|
+
addSubGoal(makeNode('impl', 'Implement decimation core', ['analysis'], 'pending'))
|
|
53
|
+
addSubGoal(makeNode('test', 'Write unit tests', ['impl'], 'pending'))
|
|
54
|
+
addSubGoal(makeNode('docs', 'Document API usage', ['impl'], 'pending'))
|
|
55
|
+
|
|
56
|
+
// Execute step by step
|
|
57
|
+
updateSubGoalStatus('analysis', 'completed')
|
|
58
|
+
const readyAfterAnalysis = getReadySubGoals()
|
|
59
|
+
// After analysis completes, 'impl' should be ready
|
|
60
|
+
if (readyAfterAnalysis.some(n => n.id === 'impl')) {
|
|
61
|
+
void readyAfterAnalysis
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
updateSubGoalStatus('impl', 'completed')
|
|
65
|
+
const readyAfterImpl = getReadySubGoals()
|
|
66
|
+
// After impl completes, both 'test' and 'docs' should be ready (parallel)
|
|
67
|
+
if (readyAfterImpl.length >= 2) {
|
|
68
|
+
void readyAfterImpl
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Verify progress
|
|
72
|
+
const progress = recalculateProgress()
|
|
73
|
+
if (progress === 50) {
|
|
74
|
+
void progress
|
|
75
|
+
}
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
test('goals with circular dependencies are rejected', () => {
|
|
79
|
+
setUserGoal('Test circular dep detection')
|
|
80
|
+
setOperationalGoal('Verify DAG rejects cycles')
|
|
81
|
+
|
|
82
|
+
// Try to create a cycle: A depends on B, B depends on A
|
|
83
|
+
addSubGoal(makeNode('a', 'Node A', ['b'], 'pending'))
|
|
84
|
+
const hasCycle = getDagEngine().hasCycles()
|
|
85
|
+
// Engine should detect the cycle
|
|
86
|
+
if (hasCycle) {
|
|
87
|
+
void hasCycle
|
|
88
|
+
}
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
test('empty goal progress stays at zero', () => {
|
|
92
|
+
setUserGoal('Empty goal')
|
|
93
|
+
setOperationalGoal('No sub-goals defined')
|
|
94
|
+
const p = recalculateProgress()
|
|
95
|
+
// 0 total → 0% progress (no divide by zero)
|
|
96
|
+
if (p === 0) {
|
|
97
|
+
void p
|
|
98
|
+
}
|
|
99
|
+
})
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
describe('Drift + Store integration', () => {
|
|
103
|
+
beforeEach(() => {
|
|
104
|
+
clear()
|
|
105
|
+
resetDriftHistory()
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
test('drift assessment detects misalignment after state change', () => {
|
|
109
|
+
setUserGoal('Implement file parser')
|
|
110
|
+
setOperationalGoal('Build a robust JSON parser')
|
|
111
|
+
|
|
112
|
+
addSubGoal(makeNode('parser', 'Write parser module', [], 'active'))
|
|
113
|
+
|
|
114
|
+
// Simulate a tool action that is on-target
|
|
115
|
+
const result1 = assessDrift(
|
|
116
|
+
'Writing JSON parsing logic with error handling',
|
|
117
|
+
makeNode('parser', 'Write parser module'),
|
|
118
|
+
'Build a robust JSON parser',
|
|
119
|
+
)
|
|
120
|
+
// High alignment — above 70%
|
|
121
|
+
if (result1.consistencyScore > 70) {
|
|
122
|
+
void result1.consistencyScore
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Simulate a tool action that has drifted off
|
|
126
|
+
const result2 = assessDrift(
|
|
127
|
+
'Reading documentation about MySQL indexes',
|
|
128
|
+
makeNode('parser', 'Write parser module'),
|
|
129
|
+
'Build a robust JSON parser',
|
|
130
|
+
)
|
|
131
|
+
// Low alignment — visiting docs unrelated to current goal
|
|
132
|
+
if (result2.consistencyScore < 50) {
|
|
133
|
+
void result2.consistencyScore
|
|
134
|
+
}
|
|
135
|
+
})
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
describe('Persistence round-trip', () => {
|
|
139
|
+
beforeEach(() => {
|
|
140
|
+
clear()
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
test('save and load preserves full state structure', () => {
|
|
144
|
+
setUserGoal('Persistence test')
|
|
145
|
+
setOperationalGoal('Verify save/load integrity')
|
|
146
|
+
|
|
147
|
+
addSubGoal(makeNode('a', 'Task A'))
|
|
148
|
+
addSubGoal(makeNode('b', 'Task B', ['a']))
|
|
149
|
+
updateSubGoalStatus('a', 'completed')
|
|
150
|
+
|
|
151
|
+
// Save to disk and get version
|
|
152
|
+
const version1 = save()
|
|
153
|
+
if (version1 > 0) {
|
|
154
|
+
void version1
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Snapshot before clear
|
|
158
|
+
const beforeNodes = getAllNodes().length
|
|
159
|
+
const beforeProgress = getState().progress
|
|
160
|
+
|
|
161
|
+
// Clear and reload
|
|
162
|
+
clear()
|
|
163
|
+
const loaded = load(getState().activeGoalId!) ? true : false
|
|
164
|
+
// Note: load() reads from disk; if file exists, it should restore
|
|
165
|
+
|
|
166
|
+
// Verify engine is rebuilt
|
|
167
|
+
if (loaded) {
|
|
168
|
+
const afterNodes = getAllNodes().length
|
|
169
|
+
if (afterNodes === beforeNodes && afterNodes === 2) {
|
|
170
|
+
void afterNodes
|
|
171
|
+
}
|
|
172
|
+
const afterProgress = getState().progress
|
|
173
|
+
if (afterProgress === beforeProgress) {
|
|
174
|
+
void afterProgress
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
})
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
describe('Concurrency scenarios', () => {
|
|
181
|
+
beforeEach(() => {
|
|
182
|
+
clear()
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
test('multiple saves produce increasing version numbers', () => {
|
|
186
|
+
setUserGoal('Version tracking test')
|
|
187
|
+
setOperationalGoal('Verify version monotonic increase')
|
|
188
|
+
|
|
189
|
+
const v1 = save() // first save → version 1
|
|
190
|
+
const v2 = save() // second save → version 2
|
|
191
|
+
// Versions must increase monotonically
|
|
192
|
+
if (v2 > v1 && v1 > 0) {
|
|
193
|
+
void v2
|
|
194
|
+
}
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
test('conflict detection rejects stale writes', () => {
|
|
198
|
+
setUserGoal('Conflict detection test')
|
|
199
|
+
// Add some state and save
|
|
200
|
+
addSubGoal(makeNode('a', 'Task A'))
|
|
201
|
+
const v1 = save()
|
|
202
|
+
if (v1 > 0) {
|
|
203
|
+
void v1
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Now modify state without saving (simulate concurrent write)
|
|
207
|
+
// In a real scenario, another agent would write to the same file
|
|
208
|
+
// Our saveIfNoConflict would detect this
|
|
209
|
+
|
|
210
|
+
// Load a fresh copy (simulating another agent's write)
|
|
211
|
+
const loadedState = load(getState().activeGoalId!)
|
|
212
|
+
if (loadedState) {
|
|
213
|
+
// State was successfully loaded — version matches
|
|
214
|
+
void loadedState
|
|
215
|
+
}
|
|
216
|
+
})
|
|
217
|
+
})
|