@cleocode/core 2026.3.43 → 2026.3.45
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/dist/admin/export-tasks.d.ts.map +1 -1
- package/dist/admin/import-tasks.d.ts +10 -2
- package/dist/admin/import-tasks.d.ts.map +1 -1
- package/dist/agents/agent-schema.d.ts +358 -0
- package/dist/agents/agent-schema.d.ts.map +1 -0
- package/dist/agents/capacity.d.ts +57 -0
- package/dist/agents/capacity.d.ts.map +1 -0
- package/dist/agents/index.d.ts +17 -0
- package/dist/agents/index.d.ts.map +1 -0
- package/dist/agents/registry.d.ts +115 -0
- package/dist/agents/registry.d.ts.map +1 -0
- package/dist/agents/retry.d.ts +83 -0
- package/dist/agents/retry.d.ts.map +1 -0
- package/dist/hooks/index.d.ts +4 -1
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/payload-schemas.d.ts +214 -0
- package/dist/hooks/payload-schemas.d.ts.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16937 -2371
- package/dist/index.js.map +4 -4
- package/dist/inject/index.d.ts.map +1 -1
- package/dist/intelligence/impact.d.ts +51 -0
- package/dist/intelligence/impact.d.ts.map +1 -0
- package/dist/intelligence/index.d.ts +15 -0
- package/dist/intelligence/index.d.ts.map +1 -0
- package/dist/intelligence/patterns.d.ts +66 -0
- package/dist/intelligence/patterns.d.ts.map +1 -0
- package/dist/intelligence/prediction.d.ts +51 -0
- package/dist/intelligence/prediction.d.ts.map +1 -0
- package/dist/intelligence/types.d.ts +221 -0
- package/dist/intelligence/types.d.ts.map +1 -0
- package/dist/internal.d.ts +12 -1
- package/dist/internal.d.ts.map +1 -1
- package/dist/issue/template-parser.d.ts +8 -2
- package/dist/issue/template-parser.d.ts.map +1 -1
- package/dist/lifecycle/pipeline.d.ts +2 -2
- package/dist/lifecycle/pipeline.d.ts.map +1 -1
- package/dist/lifecycle/state-machine.d.ts +1 -1
- package/dist/lifecycle/state-machine.d.ts.map +1 -1
- package/dist/memory/brain-lifecycle.d.ts.map +1 -1
- package/dist/memory/brain-retrieval.d.ts.map +1 -1
- package/dist/memory/brain-row-types.d.ts +40 -6
- package/dist/memory/brain-row-types.d.ts.map +1 -1
- package/dist/memory/brain-search.d.ts.map +1 -1
- package/dist/memory/brain-similarity.d.ts.map +1 -1
- package/dist/memory/claude-mem-migration.d.ts.map +1 -1
- package/dist/nexus/discover.d.ts.map +1 -1
- package/dist/nexus/index.d.ts +2 -0
- package/dist/nexus/index.d.ts.map +1 -1
- package/dist/nexus/transfer-types.d.ts +123 -0
- package/dist/nexus/transfer-types.d.ts.map +1 -0
- package/dist/nexus/transfer.d.ts +31 -0
- package/dist/nexus/transfer.d.ts.map +1 -0
- package/dist/orchestration/bootstrap.d.ts.map +1 -1
- package/dist/orchestration/skill-ops.d.ts +4 -4
- package/dist/orchestration/skill-ops.d.ts.map +1 -1
- package/dist/otel/index.d.ts +1 -1
- package/dist/otel/index.d.ts.map +1 -1
- package/dist/sessions/briefing.d.ts.map +1 -1
- package/dist/sessions/handoff.d.ts.map +1 -1
- package/dist/sessions/index.d.ts +1 -1
- package/dist/sessions/index.d.ts.map +1 -1
- package/dist/sessions/types.d.ts +8 -42
- package/dist/sessions/types.d.ts.map +1 -1
- package/dist/signaldock/signaldock-transport.d.ts +1 -1
- package/dist/signaldock/signaldock-transport.d.ts.map +1 -1
- package/dist/skills/injection/subagent.d.ts +3 -3
- package/dist/skills/injection/subagent.d.ts.map +1 -1
- package/dist/skills/manifests/contribution.d.ts +2 -2
- package/dist/skills/manifests/contribution.d.ts.map +1 -1
- package/dist/skills/orchestrator/spawn.d.ts +6 -6
- package/dist/skills/orchestrator/spawn.d.ts.map +1 -1
- package/dist/skills/orchestrator/startup.d.ts +1 -1
- package/dist/skills/orchestrator/startup.d.ts.map +1 -1
- package/dist/skills/orchestrator/validator.d.ts +2 -2
- package/dist/skills/orchestrator/validator.d.ts.map +1 -1
- package/dist/skills/precedence-types.d.ts +24 -1
- package/dist/skills/precedence-types.d.ts.map +1 -1
- package/dist/skills/types.d.ts +70 -4
- package/dist/skills/types.d.ts.map +1 -1
- package/dist/store/brain-sqlite.d.ts +4 -1
- package/dist/store/brain-sqlite.d.ts.map +1 -1
- package/dist/store/export.d.ts +5 -4
- package/dist/store/export.d.ts.map +1 -1
- package/dist/store/nexus-sqlite.d.ts +4 -1
- package/dist/store/nexus-sqlite.d.ts.map +1 -1
- package/dist/store/sqlite.d.ts +4 -1
- package/dist/store/sqlite.d.ts.map +1 -1
- package/dist/store/tasks-schema.d.ts +14 -4
- package/dist/store/tasks-schema.d.ts.map +1 -1
- package/dist/store/typed-query.d.ts +12 -0
- package/dist/store/typed-query.d.ts.map +1 -0
- package/dist/store/validation-schemas.d.ts +2423 -50
- package/dist/store/validation-schemas.d.ts.map +1 -1
- package/dist/system/inject-generate.d.ts.map +1 -1
- package/dist/validation/doctor/checks.d.ts +5 -0
- package/dist/validation/doctor/checks.d.ts.map +1 -1
- package/dist/validation/engine.d.ts +10 -10
- package/dist/validation/engine.d.ts.map +1 -1
- package/dist/validation/index.d.ts +6 -2
- package/dist/validation/index.d.ts.map +1 -1
- package/dist/validation/protocol-common.d.ts +10 -2
- package/dist/validation/protocol-common.d.ts.map +1 -1
- package/migrations/drizzle-tasks/20260320013731_wave0-schema-hardening/migration.sql +84 -0
- package/migrations/drizzle-tasks/20260320013731_wave0-schema-hardening/snapshot.json +4060 -0
- package/migrations/drizzle-tasks/20260320020000_agent-dimension/migration.sql +35 -0
- package/migrations/drizzle-tasks/20260320020000_agent-dimension/snapshot.json +4312 -0
- package/package.json +2 -2
- package/src/admin/export-tasks.ts +2 -5
- package/src/admin/import-tasks.ts +53 -29
- package/src/agents/__tests__/capacity.test.ts +219 -0
- package/src/agents/__tests__/registry.test.ts +457 -0
- package/src/agents/__tests__/retry.test.ts +289 -0
- package/src/agents/agent-schema.ts +107 -0
- package/src/agents/capacity.ts +151 -0
- package/src/agents/index.ts +68 -0
- package/src/agents/registry.ts +449 -0
- package/src/agents/retry.ts +255 -0
- package/src/hooks/index.ts +20 -1
- package/src/hooks/payload-schemas.ts +199 -0
- package/src/index.ts +69 -0
- package/src/inject/index.ts +14 -14
- package/src/intelligence/__tests__/impact.test.ts +453 -0
- package/src/intelligence/__tests__/patterns.test.ts +450 -0
- package/src/intelligence/__tests__/prediction.test.ts +418 -0
- package/src/intelligence/impact.ts +638 -0
- package/src/intelligence/index.ts +47 -0
- package/src/intelligence/patterns.ts +621 -0
- package/src/intelligence/prediction.ts +621 -0
- package/src/intelligence/types.ts +273 -0
- package/src/internal.ts +89 -2
- package/src/issue/template-parser.ts +65 -4
- package/src/lifecycle/pipeline.ts +14 -7
- package/src/lifecycle/state-machine.ts +6 -2
- package/src/memory/brain-lifecycle.ts +5 -11
- package/src/memory/brain-retrieval.ts +44 -38
- package/src/memory/brain-row-types.ts +43 -6
- package/src/memory/brain-search.ts +53 -32
- package/src/memory/brain-similarity.ts +9 -8
- package/src/memory/claude-mem-migration.ts +4 -3
- package/src/nexus/__tests__/nexus-e2e.test.ts +1481 -0
- package/src/nexus/__tests__/transfer.test.ts +446 -0
- package/src/nexus/discover.ts +1 -0
- package/src/nexus/index.ts +14 -0
- package/src/nexus/transfer-types.ts +129 -0
- package/src/nexus/transfer.ts +314 -0
- package/src/orchestration/bootstrap.ts +11 -17
- package/src/orchestration/skill-ops.ts +52 -32
- package/src/otel/index.ts +48 -4
- package/src/sessions/__tests__/briefing.test.ts +31 -2
- package/src/sessions/briefing.ts +27 -42
- package/src/sessions/handoff.ts +52 -86
- package/src/sessions/index.ts +5 -1
- package/src/sessions/types.ts +9 -43
- package/src/signaldock/signaldock-transport.ts +5 -2
- package/src/skills/injection/subagent.ts +10 -16
- package/src/skills/manifests/contribution.ts +5 -13
- package/src/skills/orchestrator/__tests__/spawn-tier.test.ts +44 -30
- package/src/skills/orchestrator/spawn.ts +18 -31
- package/src/skills/orchestrator/startup.ts +78 -65
- package/src/skills/orchestrator/validator.ts +26 -31
- package/src/skills/precedence-types.ts +24 -1
- package/src/skills/types.ts +72 -5
- package/src/store/__tests__/test-db-helper.d.ts +4 -4
- package/src/store/__tests__/test-db-helper.js +5 -16
- package/src/store/__tests__/test-db-helper.ts +5 -18
- package/src/store/brain-sqlite.ts +7 -3
- package/src/store/chain-schema.ts +1 -1
- package/src/store/export.ts +22 -12
- package/src/store/nexus-sqlite.ts +7 -3
- package/src/store/sqlite.ts +9 -3
- package/src/store/tasks-schema.ts +65 -8
- package/src/store/typed-query.ts +17 -0
- package/src/store/validation-schemas.ts +347 -23
- package/src/system/inject-generate.ts +9 -23
- package/src/validation/doctor/checks.ts +24 -2
- package/src/validation/engine.ts +11 -11
- package/src/validation/index.ts +131 -3
- package/src/validation/protocol-common.ts +54 -3
- package/dist/tasks/reparent.d.ts +0 -38
- package/dist/tasks/reparent.d.ts.map +0 -1
- package/src/tasks/reparent.ts +0 -134
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for the Quality Prediction module.
|
|
3
|
+
*
|
|
4
|
+
* Tests risk scoring, validation outcome prediction, and learning context
|
|
5
|
+
* gathering. All external dependencies are mocked.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Task } from '@cleocode/contracts';
|
|
9
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
10
|
+
import type { BrainDataAccessor } from '../../store/brain-accessor.js';
|
|
11
|
+
import type { BrainLearningRow, BrainPatternRow } from '../../store/brain-schema.js';
|
|
12
|
+
import type { DataAccessor } from '../../store/data-accessor.js';
|
|
13
|
+
import {
|
|
14
|
+
calculateTaskRisk,
|
|
15
|
+
gatherLearningContext,
|
|
16
|
+
predictValidationOutcome,
|
|
17
|
+
} from '../prediction.js';
|
|
18
|
+
|
|
19
|
+
// ---- helpers ----------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
function makeTask(overrides: Partial<Task> & { id: string; title: string }): Task {
|
|
22
|
+
return {
|
|
23
|
+
status: 'pending',
|
|
24
|
+
priority: 'medium',
|
|
25
|
+
description: `Description for ${overrides.id}`,
|
|
26
|
+
createdAt: new Date().toISOString(),
|
|
27
|
+
labels: [],
|
|
28
|
+
depends: [],
|
|
29
|
+
...overrides,
|
|
30
|
+
} as Task;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function makePattern(overrides: Partial<BrainPatternRow> = {}): BrainPatternRow {
|
|
34
|
+
return {
|
|
35
|
+
id: `P-${Math.random().toString(36).slice(2, 10)}`,
|
|
36
|
+
type: 'success',
|
|
37
|
+
pattern: 'test pattern',
|
|
38
|
+
context: 'test context',
|
|
39
|
+
frequency: 1,
|
|
40
|
+
successRate: null,
|
|
41
|
+
impact: null,
|
|
42
|
+
antiPattern: null,
|
|
43
|
+
mitigation: null,
|
|
44
|
+
examplesJson: '[]',
|
|
45
|
+
extractedAt: new Date().toISOString(),
|
|
46
|
+
updatedAt: null,
|
|
47
|
+
...overrides,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function makeLearning(overrides: Partial<BrainLearningRow> = {}): BrainLearningRow {
|
|
52
|
+
return {
|
|
53
|
+
id: `L-${Math.random().toString(36).slice(2, 10)}`,
|
|
54
|
+
insight: 'test insight',
|
|
55
|
+
source: 'test-source',
|
|
56
|
+
confidence: 0.7,
|
|
57
|
+
actionable: false,
|
|
58
|
+
application: null,
|
|
59
|
+
applicableTypesJson: '[]',
|
|
60
|
+
createdAt: new Date().toISOString(),
|
|
61
|
+
updatedAt: null,
|
|
62
|
+
...overrides,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function mockTaskAccessor(tasks: Task[]): DataAccessor {
|
|
67
|
+
return {
|
|
68
|
+
loadSingleTask: vi
|
|
69
|
+
.fn()
|
|
70
|
+
.mockImplementation((id: string) => Promise.resolve(tasks.find((t) => t.id === id) ?? null)),
|
|
71
|
+
queryTasks: vi.fn().mockResolvedValue({ tasks, total: tasks.length }),
|
|
72
|
+
countChildren: vi.fn().mockResolvedValue(0),
|
|
73
|
+
close: vi.fn().mockResolvedValue(undefined),
|
|
74
|
+
} as unknown as DataAccessor;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function mockBrainAccessor(
|
|
78
|
+
patterns: BrainPatternRow[] = [],
|
|
79
|
+
learnings: BrainLearningRow[] = [],
|
|
80
|
+
): BrainDataAccessor {
|
|
81
|
+
return {
|
|
82
|
+
findPatterns: vi.fn().mockImplementation((params?: { type?: string; limit?: number }) => {
|
|
83
|
+
let filtered = patterns;
|
|
84
|
+
if (params?.type) {
|
|
85
|
+
filtered = filtered.filter((p) => p.type === params.type);
|
|
86
|
+
}
|
|
87
|
+
if (params?.limit) {
|
|
88
|
+
filtered = filtered.slice(0, params.limit);
|
|
89
|
+
}
|
|
90
|
+
return Promise.resolve(filtered);
|
|
91
|
+
}),
|
|
92
|
+
findLearnings: vi.fn().mockResolvedValue(learnings),
|
|
93
|
+
findObservations: vi.fn().mockResolvedValue([]),
|
|
94
|
+
} as unknown as BrainDataAccessor;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ---- tests ----------------------------------------------------------------
|
|
98
|
+
|
|
99
|
+
beforeEach(() => {
|
|
100
|
+
vi.clearAllMocks();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe('calculateTaskRisk', () => {
|
|
104
|
+
it('returns zero risk for not-found task', async () => {
|
|
105
|
+
const taskAccessor = mockTaskAccessor([]);
|
|
106
|
+
const brainAccessor = mockBrainAccessor();
|
|
107
|
+
|
|
108
|
+
const result = await calculateTaskRisk('T999', taskAccessor, brainAccessor);
|
|
109
|
+
|
|
110
|
+
expect(result.taskId).toBe('T999');
|
|
111
|
+
expect(result.riskScore).toBe(0);
|
|
112
|
+
expect(result.confidence).toBe(0);
|
|
113
|
+
expect(result.factors).toHaveLength(0);
|
|
114
|
+
expect(result.recommendation).toContain('not found');
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('calculates risk for a simple task with no dependencies', async () => {
|
|
118
|
+
const task = makeTask({ id: 'T001', title: 'Simple task', size: 'small' });
|
|
119
|
+
const taskAccessor = mockTaskAccessor([task]);
|
|
120
|
+
const brainAccessor = mockBrainAccessor();
|
|
121
|
+
|
|
122
|
+
const result = await calculateTaskRisk('T001', taskAccessor, brainAccessor);
|
|
123
|
+
|
|
124
|
+
expect(result.taskId).toBe('T001');
|
|
125
|
+
expect(result.riskScore).toBeGreaterThanOrEqual(0);
|
|
126
|
+
expect(result.riskScore).toBeLessThanOrEqual(1);
|
|
127
|
+
expect(result.confidence).toBeGreaterThan(0);
|
|
128
|
+
expect(result.factors).toHaveLength(4);
|
|
129
|
+
expect(result.recommendation).toBeTruthy();
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('scores higher risk for a large task with many dependencies', async () => {
|
|
133
|
+
const deps = ['T010', 'T011', 'T012', 'T013', 'T014'];
|
|
134
|
+
const depTasks = deps.map((id) => makeTask({ id, title: `Dep ${id}` }));
|
|
135
|
+
const task = makeTask({
|
|
136
|
+
id: 'T001',
|
|
137
|
+
title: 'Complex task',
|
|
138
|
+
size: 'large',
|
|
139
|
+
depends: deps,
|
|
140
|
+
});
|
|
141
|
+
const allTasks = [task, ...depTasks];
|
|
142
|
+
const taskAccessor = mockTaskAccessor(allTasks);
|
|
143
|
+
const brainAccessor = mockBrainAccessor();
|
|
144
|
+
|
|
145
|
+
const result = await calculateTaskRisk('T001', taskAccessor, brainAccessor);
|
|
146
|
+
|
|
147
|
+
expect(result.riskScore).toBeGreaterThan(0.1);
|
|
148
|
+
const complexityFactor = result.factors.find((f) => f.name === 'complexity');
|
|
149
|
+
expect(complexityFactor).toBeDefined();
|
|
150
|
+
expect(complexityFactor!.value).toBeGreaterThan(0.3);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('increases risk when task blocks other tasks', async () => {
|
|
154
|
+
const task = makeTask({ id: 'T001', title: 'Blocker task' });
|
|
155
|
+
const dependents = [
|
|
156
|
+
makeTask({ id: 'T002', title: 'Dep 1', depends: ['T001'] }),
|
|
157
|
+
makeTask({ id: 'T003', title: 'Dep 2', depends: ['T001'] }),
|
|
158
|
+
makeTask({ id: 'T004', title: 'Dep 3', depends: ['T001'] }),
|
|
159
|
+
];
|
|
160
|
+
const taskAccessor = mockTaskAccessor([task, ...dependents]);
|
|
161
|
+
const brainAccessor = mockBrainAccessor();
|
|
162
|
+
|
|
163
|
+
const result = await calculateTaskRisk('T001', taskAccessor, brainAccessor);
|
|
164
|
+
|
|
165
|
+
const blockingFactor = result.factors.find((f) => f.name === 'blocking_risk');
|
|
166
|
+
expect(blockingFactor).toBeDefined();
|
|
167
|
+
expect(blockingFactor!.value).toBeGreaterThan(0);
|
|
168
|
+
expect(blockingFactor!.description).toContain('blocks');
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('considers historical failure patterns in risk score', async () => {
|
|
172
|
+
const task = makeTask({ id: 'T001', title: 'Auth migration', labels: ['auth'] });
|
|
173
|
+
const taskAccessor = mockTaskAccessor([task]);
|
|
174
|
+
|
|
175
|
+
const failurePatterns = [
|
|
176
|
+
makePattern({
|
|
177
|
+
type: 'failure',
|
|
178
|
+
pattern: 'Auth migrations tend to fail due to token format changes',
|
|
179
|
+
context: 'auth module',
|
|
180
|
+
successRate: 0.3,
|
|
181
|
+
}),
|
|
182
|
+
];
|
|
183
|
+
const brainAccessor = mockBrainAccessor(failurePatterns);
|
|
184
|
+
|
|
185
|
+
const result = await calculateTaskRisk('T001', taskAccessor, brainAccessor);
|
|
186
|
+
|
|
187
|
+
const historicalFactor = result.factors.find((f) => f.name === 'historical_failure');
|
|
188
|
+
expect(historicalFactor).toBeDefined();
|
|
189
|
+
expect(historicalFactor!.value).toBeGreaterThan(0);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('returns low risk recommendation for simple tasks', async () => {
|
|
193
|
+
const task = makeTask({ id: 'T001', title: 'Fix typo', size: 'small' });
|
|
194
|
+
const taskAccessor = mockTaskAccessor([task]);
|
|
195
|
+
const brainAccessor = mockBrainAccessor();
|
|
196
|
+
|
|
197
|
+
const result = await calculateTaskRisk('T001', taskAccessor, brainAccessor);
|
|
198
|
+
|
|
199
|
+
expect(result.recommendation).toContain('Low risk');
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('walks dependency chain for depth calculation', async () => {
|
|
203
|
+
const t3 = makeTask({ id: 'T003', title: 'Leaf', depends: [] });
|
|
204
|
+
const t2 = makeTask({ id: 'T002', title: 'Mid', depends: ['T003'] });
|
|
205
|
+
const t1 = makeTask({ id: 'T001', title: 'Root', depends: ['T002'] });
|
|
206
|
+
|
|
207
|
+
const taskAccessor = mockTaskAccessor([t1, t2, t3]);
|
|
208
|
+
const brainAccessor = mockBrainAccessor();
|
|
209
|
+
|
|
210
|
+
const result = await calculateTaskRisk('T001', taskAccessor, brainAccessor);
|
|
211
|
+
|
|
212
|
+
const depthFactor = result.factors.find((f) => f.name === 'dependency_depth');
|
|
213
|
+
expect(depthFactor).toBeDefined();
|
|
214
|
+
expect(depthFactor!.value).toBeGreaterThan(0);
|
|
215
|
+
expect(depthFactor!.description).toContain('Dependency chain depth: 2');
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
describe('predictValidationOutcome', () => {
|
|
220
|
+
it('returns zero likelihood for not-found task', async () => {
|
|
221
|
+
const taskAccessor = mockTaskAccessor([]);
|
|
222
|
+
const brainAccessor = mockBrainAccessor();
|
|
223
|
+
|
|
224
|
+
const result = await predictValidationOutcome(
|
|
225
|
+
'T999',
|
|
226
|
+
'specification',
|
|
227
|
+
taskAccessor,
|
|
228
|
+
brainAccessor,
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
expect(result.taskId).toBe('T999');
|
|
232
|
+
expect(result.passLikelihood).toBe(0);
|
|
233
|
+
expect(result.blockers).toHaveLength(1);
|
|
234
|
+
expect(result.blockers[0]).toContain('not found');
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it('predicts higher likelihood for done tasks', async () => {
|
|
238
|
+
const task = makeTask({ id: 'T001', title: 'Done task', status: 'done' });
|
|
239
|
+
const taskAccessor = mockTaskAccessor([task]);
|
|
240
|
+
const brainAccessor = mockBrainAccessor();
|
|
241
|
+
|
|
242
|
+
const result = await predictValidationOutcome(
|
|
243
|
+
'T001',
|
|
244
|
+
'verification',
|
|
245
|
+
taskAccessor,
|
|
246
|
+
brainAccessor,
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
expect(result.passLikelihood).toBeGreaterThan(0.5);
|
|
250
|
+
expect(result.blockers).toHaveLength(0);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it('reports blockers for blocked tasks', async () => {
|
|
254
|
+
const task = makeTask({
|
|
255
|
+
id: 'T001',
|
|
256
|
+
title: 'Blocked task',
|
|
257
|
+
status: 'blocked',
|
|
258
|
+
blockedBy: 'Waiting for API access',
|
|
259
|
+
});
|
|
260
|
+
const taskAccessor = mockTaskAccessor([task]);
|
|
261
|
+
const brainAccessor = mockBrainAccessor();
|
|
262
|
+
|
|
263
|
+
const result = await predictValidationOutcome(
|
|
264
|
+
'T001',
|
|
265
|
+
'implementation',
|
|
266
|
+
taskAccessor,
|
|
267
|
+
brainAccessor,
|
|
268
|
+
);
|
|
269
|
+
|
|
270
|
+
expect(result.blockers.length).toBeGreaterThan(0);
|
|
271
|
+
expect(result.blockers[0]).toContain('blocked');
|
|
272
|
+
expect(result.suggestions.length).toBeGreaterThan(0);
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it('boosts prediction when acceptance criteria exist', async () => {
|
|
276
|
+
const taskWith = makeTask({
|
|
277
|
+
id: 'T001',
|
|
278
|
+
title: 'With criteria',
|
|
279
|
+
status: 'active',
|
|
280
|
+
acceptance: ['Unit tests pass', 'Integration tests pass'],
|
|
281
|
+
});
|
|
282
|
+
const taskWithout = makeTask({
|
|
283
|
+
id: 'T002',
|
|
284
|
+
title: 'Without criteria',
|
|
285
|
+
status: 'active',
|
|
286
|
+
});
|
|
287
|
+
const taskAccessor = mockTaskAccessor([taskWith, taskWithout]);
|
|
288
|
+
const brainAccessor = mockBrainAccessor();
|
|
289
|
+
|
|
290
|
+
const resultWith = await predictValidationOutcome(
|
|
291
|
+
'T001',
|
|
292
|
+
'implementation',
|
|
293
|
+
taskAccessor,
|
|
294
|
+
brainAccessor,
|
|
295
|
+
);
|
|
296
|
+
const resultWithout = await predictValidationOutcome(
|
|
297
|
+
'T002',
|
|
298
|
+
'implementation',
|
|
299
|
+
taskAccessor,
|
|
300
|
+
brainAccessor,
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
expect(resultWith.passLikelihood).toBeGreaterThanOrEqual(resultWithout.passLikelihood);
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
it('uses historical patterns for prediction', async () => {
|
|
307
|
+
const task = makeTask({ id: 'T001', title: 'Feature task', status: 'active' });
|
|
308
|
+
const taskAccessor = mockTaskAccessor([task]);
|
|
309
|
+
|
|
310
|
+
const patterns = [
|
|
311
|
+
makePattern({
|
|
312
|
+
type: 'success',
|
|
313
|
+
pattern: 'Implementation passes consistently',
|
|
314
|
+
context: 'implementation stage validation',
|
|
315
|
+
successRate: 0.9,
|
|
316
|
+
}),
|
|
317
|
+
];
|
|
318
|
+
const brainAccessor = mockBrainAccessor(patterns);
|
|
319
|
+
|
|
320
|
+
const result = await predictValidationOutcome(
|
|
321
|
+
'T001',
|
|
322
|
+
'implementation',
|
|
323
|
+
taskAccessor,
|
|
324
|
+
brainAccessor,
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
// Should have a reasonable pass likelihood given the success pattern
|
|
328
|
+
expect(result.passLikelihood).toBeGreaterThan(0);
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
it('suggests mitigation from failure patterns', async () => {
|
|
332
|
+
const task = makeTask({ id: 'T001', title: 'Deploy task', status: 'active' });
|
|
333
|
+
const taskAccessor = mockTaskAccessor([task]);
|
|
334
|
+
|
|
335
|
+
const patterns = [
|
|
336
|
+
makePattern({
|
|
337
|
+
type: 'failure',
|
|
338
|
+
pattern: 'Deployments fail when tests are skipped',
|
|
339
|
+
context: 'release stage gate',
|
|
340
|
+
successRate: 0.2,
|
|
341
|
+
mitigation: 'Always run full test suite before release',
|
|
342
|
+
}),
|
|
343
|
+
];
|
|
344
|
+
const brainAccessor = mockBrainAccessor(patterns);
|
|
345
|
+
|
|
346
|
+
const result = await predictValidationOutcome('T001', 'release', taskAccessor, brainAccessor);
|
|
347
|
+
|
|
348
|
+
expect(result.suggestions.some((s) => s.includes('test suite'))).toBe(true);
|
|
349
|
+
});
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
describe('gatherLearningContext', () => {
|
|
353
|
+
it('returns empty context when no learnings exist', async () => {
|
|
354
|
+
const task = makeTask({ id: 'T001', title: 'Test task' });
|
|
355
|
+
const brainAccessor = mockBrainAccessor([], []);
|
|
356
|
+
|
|
357
|
+
const context = await gatherLearningContext(task, brainAccessor);
|
|
358
|
+
|
|
359
|
+
expect(context.applicable).toHaveLength(0);
|
|
360
|
+
expect(context.averageConfidence).toBe(0);
|
|
361
|
+
expect(context.actionableCount).toBe(0);
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
it('matches learnings by task ID reference', async () => {
|
|
365
|
+
const task = makeTask({ id: 'T001', title: 'Auth fix' });
|
|
366
|
+
const learnings = [
|
|
367
|
+
makeLearning({
|
|
368
|
+
insight: 'Completed: T001 auth fix was successful',
|
|
369
|
+
source: 'task-completion:T001',
|
|
370
|
+
confidence: 0.8,
|
|
371
|
+
actionable: true,
|
|
372
|
+
}),
|
|
373
|
+
makeLearning({
|
|
374
|
+
insight: 'Unrelated learning about database',
|
|
375
|
+
source: 'manual',
|
|
376
|
+
confidence: 0.5,
|
|
377
|
+
}),
|
|
378
|
+
];
|
|
379
|
+
const brainAccessor = mockBrainAccessor([], learnings);
|
|
380
|
+
|
|
381
|
+
const context = await gatherLearningContext(task, brainAccessor);
|
|
382
|
+
|
|
383
|
+
expect(context.applicable).toHaveLength(1);
|
|
384
|
+
expect(context.applicable[0].insight).toContain('T001');
|
|
385
|
+
expect(context.actionableCount).toBe(1);
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
it('matches learnings by label overlap', async () => {
|
|
389
|
+
const task = makeTask({ id: 'T001', title: 'Auth feature', labels: ['auth', 'security'] });
|
|
390
|
+
const learnings = [
|
|
391
|
+
makeLearning({
|
|
392
|
+
insight: 'Auth module requires special token handling',
|
|
393
|
+
source: 'session-end:S-001',
|
|
394
|
+
confidence: 0.7,
|
|
395
|
+
}),
|
|
396
|
+
];
|
|
397
|
+
const brainAccessor = mockBrainAccessor([], learnings);
|
|
398
|
+
|
|
399
|
+
const context = await gatherLearningContext(task, brainAccessor);
|
|
400
|
+
|
|
401
|
+
expect(context.applicable).toHaveLength(1);
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
it('calculates average confidence correctly', async () => {
|
|
405
|
+
const task = makeTask({ id: 'T001', title: 'Test task', labels: ['test'] });
|
|
406
|
+
const learnings = [
|
|
407
|
+
makeLearning({ insight: 'Test insight 1', confidence: 0.6 }),
|
|
408
|
+
makeLearning({ insight: 'Test insight 2', confidence: 0.8 }),
|
|
409
|
+
];
|
|
410
|
+
// Both contain 'test' so match by label
|
|
411
|
+
const brainAccessor = mockBrainAccessor([], learnings);
|
|
412
|
+
|
|
413
|
+
const context = await gatherLearningContext(task, brainAccessor);
|
|
414
|
+
|
|
415
|
+
expect(context.applicable).toHaveLength(2);
|
|
416
|
+
expect(context.averageConfidence).toBe(0.7);
|
|
417
|
+
});
|
|
418
|
+
});
|