@soleri/core 9.7.2 → 9.8.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/dist/enforcement/adapters/index.d.ts +15 -0
- package/dist/enforcement/adapters/index.d.ts.map +1 -1
- package/dist/enforcement/adapters/index.js +38 -0
- package/dist/enforcement/adapters/index.js.map +1 -1
- package/dist/enforcement/adapters/opencode.d.ts +21 -0
- package/dist/enforcement/adapters/opencode.d.ts.map +1 -0
- package/dist/enforcement/adapters/opencode.js +115 -0
- package/dist/enforcement/adapters/opencode.js.map +1 -0
- package/dist/planning/evidence-collector.d.ts +2 -0
- package/dist/planning/evidence-collector.d.ts.map +1 -1
- package/dist/planning/evidence-collector.js +7 -2
- package/dist/planning/evidence-collector.js.map +1 -1
- package/dist/planning/plan-lifecycle.d.ts.map +1 -1
- package/dist/planning/plan-lifecycle.js +5 -0
- package/dist/planning/plan-lifecycle.js.map +1 -1
- package/dist/planning/planner-types.d.ts +2 -0
- package/dist/planning/planner-types.d.ts.map +1 -1
- package/dist/runtime/orchestrate-ops.d.ts.map +1 -1
- package/dist/runtime/orchestrate-ops.js +65 -1
- package/dist/runtime/orchestrate-ops.js.map +1 -1
- package/dist/runtime/quality-signals.d.ts +42 -0
- package/dist/runtime/quality-signals.d.ts.map +1 -0
- package/dist/runtime/quality-signals.js +124 -0
- package/dist/runtime/quality-signals.js.map +1 -0
- package/dist/skills/trust-classifier.js +1 -1
- package/dist/skills/trust-classifier.js.map +1 -1
- package/package.json +1 -1
- package/src/enforcement/adapters/index.ts +45 -0
- package/src/enforcement/adapters/opencode.test.ts +404 -0
- package/src/enforcement/adapters/opencode.ts +153 -0
- package/src/planning/evidence-collector.test.ts +95 -0
- package/src/planning/evidence-collector.ts +11 -0
- package/src/planning/plan-lifecycle.test.ts +49 -0
- package/src/planning/plan-lifecycle.ts +5 -0
- package/src/planning/planner-types.ts +2 -0
- package/src/runtime/orchestrate-ops.test.ts +78 -1
- package/src/runtime/orchestrate-ops.ts +91 -1
- package/src/runtime/orchestrate-status-readiness.test.ts +162 -0
- package/src/runtime/quality-signals.test.ts +312 -0
- package/src/runtime/quality-signals.ts +169 -0
- package/src/skills/trust-classifier.ts +1 -1
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { analyzeQualitySignals, captureQualitySignals } from './quality-signals.js';
|
|
3
|
+
import type { EvidenceReport } from '../planning/evidence-collector.js';
|
|
4
|
+
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// Helpers
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
function makeReport(overrides: Partial<EvidenceReport> = {}): EvidenceReport {
|
|
10
|
+
return {
|
|
11
|
+
planId: 'plan-test',
|
|
12
|
+
planObjective: 'Test plan',
|
|
13
|
+
accuracy: 80,
|
|
14
|
+
evidenceSources: ['git'],
|
|
15
|
+
taskEvidence: [],
|
|
16
|
+
unplannedChanges: [],
|
|
17
|
+
missingWork: [],
|
|
18
|
+
verificationGaps: [],
|
|
19
|
+
summary: '',
|
|
20
|
+
...overrides,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function makeVault() {
|
|
25
|
+
return {
|
|
26
|
+
search: vi.fn().mockReturnValue([]),
|
|
27
|
+
add: vi.fn(),
|
|
28
|
+
} as unknown as ReturnType<
|
|
29
|
+
(typeof import('../vault/vault.js'))['Vault']['prototype']['search']
|
|
30
|
+
> & { add: ReturnType<typeof vi.fn>; search: ReturnType<typeof vi.fn> };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function makeBrain() {
|
|
34
|
+
return {
|
|
35
|
+
recordFeedback: vi.fn(),
|
|
36
|
+
} as unknown as { recordFeedback: ReturnType<typeof vi.fn> };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
// analyzeQualitySignals
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
describe('analyzeQualitySignals', () => {
|
|
44
|
+
it('detects anti-pattern when fixIterations > 2', () => {
|
|
45
|
+
const report = makeReport({
|
|
46
|
+
taskEvidence: [
|
|
47
|
+
{
|
|
48
|
+
taskId: 't1',
|
|
49
|
+
taskTitle: 'Fix login bug',
|
|
50
|
+
plannedStatus: 'completed',
|
|
51
|
+
matchedFiles: [],
|
|
52
|
+
verdict: 'DONE',
|
|
53
|
+
fixIterations: 3,
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const result = analyzeQualitySignals(report);
|
|
59
|
+
|
|
60
|
+
expect(result.antiPatterns).toHaveLength(1);
|
|
61
|
+
expect(result.antiPatterns[0].taskId).toBe('t1');
|
|
62
|
+
expect(result.antiPatterns[0].kind).toBe('anti-pattern');
|
|
63
|
+
expect(result.antiPatterns[0].fixIterations).toBe(3);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('detects clean task when fixIterations === 0 and verdict DONE', () => {
|
|
67
|
+
const report = makeReport({
|
|
68
|
+
taskEvidence: [
|
|
69
|
+
{
|
|
70
|
+
taskId: 't2',
|
|
71
|
+
taskTitle: 'Add feature',
|
|
72
|
+
plannedStatus: 'completed',
|
|
73
|
+
matchedFiles: [],
|
|
74
|
+
verdict: 'DONE',
|
|
75
|
+
fixIterations: 0,
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const result = analyzeQualitySignals(report);
|
|
81
|
+
|
|
82
|
+
expect(result.cleanTasks).toHaveLength(1);
|
|
83
|
+
expect(result.cleanTasks[0].taskId).toBe('t2');
|
|
84
|
+
expect(result.cleanTasks[0].kind).toBe('clean');
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('does not flag task with fixIterations === 0 but verdict PARTIAL', () => {
|
|
88
|
+
const report = makeReport({
|
|
89
|
+
taskEvidence: [
|
|
90
|
+
{
|
|
91
|
+
taskId: 't3',
|
|
92
|
+
taskTitle: 'Partial work',
|
|
93
|
+
plannedStatus: 'in_progress',
|
|
94
|
+
matchedFiles: [],
|
|
95
|
+
verdict: 'PARTIAL',
|
|
96
|
+
fixIterations: 0,
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const result = analyzeQualitySignals(report);
|
|
102
|
+
|
|
103
|
+
expect(result.cleanTasks).toHaveLength(0);
|
|
104
|
+
expect(result.antiPatterns).toHaveLength(0);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('does not flag task with fixIterations === 2 (at threshold, not above)', () => {
|
|
108
|
+
const report = makeReport({
|
|
109
|
+
taskEvidence: [
|
|
110
|
+
{
|
|
111
|
+
taskId: 't4',
|
|
112
|
+
taskTitle: 'Borderline task',
|
|
113
|
+
plannedStatus: 'completed',
|
|
114
|
+
matchedFiles: [],
|
|
115
|
+
verdict: 'DONE',
|
|
116
|
+
fixIterations: 2,
|
|
117
|
+
},
|
|
118
|
+
],
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
const result = analyzeQualitySignals(report);
|
|
122
|
+
|
|
123
|
+
expect(result.antiPatterns).toHaveLength(0);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('detects scope creep from unplanned changes', () => {
|
|
127
|
+
const report = makeReport({
|
|
128
|
+
unplannedChanges: [
|
|
129
|
+
{
|
|
130
|
+
file: { path: 'src/extra.ts', status: 'added' },
|
|
131
|
+
possibleReason: 'unplanned scope',
|
|
132
|
+
},
|
|
133
|
+
],
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
const result = analyzeQualitySignals(report);
|
|
137
|
+
|
|
138
|
+
expect(result.scopeCreep).toHaveLength(1);
|
|
139
|
+
expect(result.scopeCreep[0].kind).toBe('scope-creep');
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('handles undefined fixIterations as 0', () => {
|
|
143
|
+
const report = makeReport({
|
|
144
|
+
taskEvidence: [
|
|
145
|
+
{
|
|
146
|
+
taskId: 't5',
|
|
147
|
+
taskTitle: 'No iterations field',
|
|
148
|
+
plannedStatus: 'completed',
|
|
149
|
+
matchedFiles: [],
|
|
150
|
+
verdict: 'DONE',
|
|
151
|
+
// fixIterations omitted
|
|
152
|
+
},
|
|
153
|
+
],
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
const result = analyzeQualitySignals(report);
|
|
157
|
+
|
|
158
|
+
expect(result.cleanTasks).toHaveLength(1);
|
|
159
|
+
expect(result.antiPatterns).toHaveLength(0);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// ---------------------------------------------------------------------------
|
|
164
|
+
// captureQualitySignals
|
|
165
|
+
// ---------------------------------------------------------------------------
|
|
166
|
+
|
|
167
|
+
describe('captureQualitySignals', () => {
|
|
168
|
+
let vault: ReturnType<typeof makeVault>;
|
|
169
|
+
let brain: ReturnType<typeof makeBrain>;
|
|
170
|
+
|
|
171
|
+
beforeEach(() => {
|
|
172
|
+
vault = makeVault();
|
|
173
|
+
brain = makeBrain();
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('captures anti-pattern to vault and records negative brain feedback', () => {
|
|
177
|
+
const analysis = {
|
|
178
|
+
antiPatterns: [
|
|
179
|
+
{
|
|
180
|
+
taskId: 't1',
|
|
181
|
+
taskTitle: 'Fix login',
|
|
182
|
+
kind: 'anti-pattern' as const,
|
|
183
|
+
fixIterations: 3,
|
|
184
|
+
verdict: 'DONE',
|
|
185
|
+
},
|
|
186
|
+
],
|
|
187
|
+
cleanTasks: [],
|
|
188
|
+
scopeCreep: [],
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const result = captureQualitySignals(analysis, vault, brain, 'plan-1');
|
|
192
|
+
|
|
193
|
+
expect(vault.add).toHaveBeenCalledTimes(1);
|
|
194
|
+
const entry = vault.add.mock.calls[0][0];
|
|
195
|
+
expect(entry.type).toBe('anti-pattern');
|
|
196
|
+
expect(entry.severity).toBe('warning');
|
|
197
|
+
expect(entry.tags).toContain('rework');
|
|
198
|
+
expect(entry.tags).toContain('fix-trail');
|
|
199
|
+
expect(entry.tags).toContain('auto-captured');
|
|
200
|
+
|
|
201
|
+
expect(brain.recordFeedback).toHaveBeenCalledWith(
|
|
202
|
+
'quality-signal:rework:Fix login',
|
|
203
|
+
't1',
|
|
204
|
+
'dismissed',
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
expect(result.captured).toBe(1);
|
|
208
|
+
expect(result.feedback).toBe(1);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it('records positive brain feedback for clean tasks', () => {
|
|
212
|
+
const analysis = {
|
|
213
|
+
antiPatterns: [],
|
|
214
|
+
cleanTasks: [
|
|
215
|
+
{
|
|
216
|
+
taskId: 't2',
|
|
217
|
+
taskTitle: 'Add feature',
|
|
218
|
+
kind: 'clean' as const,
|
|
219
|
+
fixIterations: 0,
|
|
220
|
+
verdict: 'DONE',
|
|
221
|
+
},
|
|
222
|
+
],
|
|
223
|
+
scopeCreep: [],
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
const result = captureQualitySignals(analysis, vault, brain, 'plan-1');
|
|
227
|
+
|
|
228
|
+
expect(brain.recordFeedback).toHaveBeenCalledWith(
|
|
229
|
+
'quality-signal:clean:Add feature',
|
|
230
|
+
't2',
|
|
231
|
+
'accepted',
|
|
232
|
+
);
|
|
233
|
+
expect(result.feedback).toBe(1);
|
|
234
|
+
expect(result.captured).toBe(0);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it('skips duplicate anti-patterns when vault search returns high-score match', () => {
|
|
238
|
+
vault.search.mockReturnValue([{ entry: { id: 'existing' }, score: 0.8 }]);
|
|
239
|
+
|
|
240
|
+
const analysis = {
|
|
241
|
+
antiPatterns: [
|
|
242
|
+
{
|
|
243
|
+
taskId: 't1',
|
|
244
|
+
taskTitle: 'Fix login',
|
|
245
|
+
kind: 'anti-pattern' as const,
|
|
246
|
+
fixIterations: 3,
|
|
247
|
+
verdict: 'DONE',
|
|
248
|
+
},
|
|
249
|
+
],
|
|
250
|
+
cleanTasks: [],
|
|
251
|
+
scopeCreep: [],
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
const result = captureQualitySignals(analysis, vault, brain, 'plan-1');
|
|
255
|
+
|
|
256
|
+
expect(vault.add).not.toHaveBeenCalled();
|
|
257
|
+
expect(result.skipped).toBe(1);
|
|
258
|
+
expect(result.captured).toBe(0);
|
|
259
|
+
// Brain feedback still recorded even for deduplicated captures
|
|
260
|
+
expect(brain.recordFeedback).toHaveBeenCalledTimes(1);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it('assigns critical severity for fixIterations > 4', () => {
|
|
264
|
+
const analysis = {
|
|
265
|
+
antiPatterns: [
|
|
266
|
+
{
|
|
267
|
+
taskId: 't1',
|
|
268
|
+
taskTitle: 'Hard bug',
|
|
269
|
+
kind: 'anti-pattern' as const,
|
|
270
|
+
fixIterations: 5,
|
|
271
|
+
verdict: 'DONE',
|
|
272
|
+
},
|
|
273
|
+
],
|
|
274
|
+
cleanTasks: [],
|
|
275
|
+
scopeCreep: [],
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
captureQualitySignals(analysis, vault, brain, 'plan-1');
|
|
279
|
+
|
|
280
|
+
const entry = vault.add.mock.calls[0][0];
|
|
281
|
+
expect(entry.severity).toBe('critical');
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it('handles mixed signals correctly', () => {
|
|
285
|
+
const analysis = {
|
|
286
|
+
antiPatterns: [
|
|
287
|
+
{
|
|
288
|
+
taskId: 't1',
|
|
289
|
+
taskTitle: 'Rework task',
|
|
290
|
+
kind: 'anti-pattern' as const,
|
|
291
|
+
fixIterations: 4,
|
|
292
|
+
verdict: 'DONE',
|
|
293
|
+
},
|
|
294
|
+
],
|
|
295
|
+
cleanTasks: [
|
|
296
|
+
{
|
|
297
|
+
taskId: 't2',
|
|
298
|
+
taskTitle: 'Clean task',
|
|
299
|
+
kind: 'clean' as const,
|
|
300
|
+
fixIterations: 0,
|
|
301
|
+
verdict: 'DONE',
|
|
302
|
+
},
|
|
303
|
+
],
|
|
304
|
+
scopeCreep: [],
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
const result = captureQualitySignals(analysis, vault, brain, 'plan-1');
|
|
308
|
+
|
|
309
|
+
expect(result.captured).toBe(1);
|
|
310
|
+
expect(result.feedback).toBe(2); // 1 dismissed + 1 accepted
|
|
311
|
+
});
|
|
312
|
+
});
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quality Signals — analyze evidence reports for rework patterns and clean execution.
|
|
3
|
+
*
|
|
4
|
+
* Extracts anti-patterns (high rework), clean tasks (first-pass success),
|
|
5
|
+
* and scope creep signals from evidence reports. Captures findings to vault
|
|
6
|
+
* and feeds brain feedback. Best-effort — never throws.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { EvidenceReport } from '../planning/evidence-collector.js';
|
|
10
|
+
import type { Plan } from '../planning/planner.js';
|
|
11
|
+
import type { Vault } from '../vault/vault.js';
|
|
12
|
+
import type { Brain } from '../brain/brain.js';
|
|
13
|
+
import type { IntelligenceEntry } from '../intelligence/types.js';
|
|
14
|
+
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// Types
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
export interface QualitySignal {
|
|
20
|
+
taskId: string;
|
|
21
|
+
taskTitle: string;
|
|
22
|
+
kind: 'anti-pattern' | 'clean' | 'scope-creep';
|
|
23
|
+
fixIterations: number;
|
|
24
|
+
verdict: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface QualityAnalysis {
|
|
28
|
+
antiPatterns: QualitySignal[];
|
|
29
|
+
cleanTasks: QualitySignal[];
|
|
30
|
+
scopeCreep: QualitySignal[];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// Thresholds
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
/** Tasks with more than this many fix iterations are flagged as anti-patterns. */
|
|
38
|
+
const REWORK_THRESHOLD = 2;
|
|
39
|
+
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
// Analysis
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Analyze an evidence report for quality signals.
|
|
46
|
+
*
|
|
47
|
+
* - fixIterations > 2 → anti-pattern (rework)
|
|
48
|
+
* - fixIterations === 0 + verdict DONE → clean (first-pass success)
|
|
49
|
+
* - unplannedChanges → scope-creep signals
|
|
50
|
+
*/
|
|
51
|
+
export function analyzeQualitySignals(
|
|
52
|
+
report: EvidenceReport,
|
|
53
|
+
_plan?: Plan | null,
|
|
54
|
+
): QualityAnalysis {
|
|
55
|
+
const antiPatterns: QualitySignal[] = [];
|
|
56
|
+
const cleanTasks: QualitySignal[] = [];
|
|
57
|
+
const scopeCreep: QualitySignal[] = [];
|
|
58
|
+
|
|
59
|
+
for (const te of report.taskEvidence) {
|
|
60
|
+
const iterations = te.fixIterations ?? 0;
|
|
61
|
+
|
|
62
|
+
if (iterations > REWORK_THRESHOLD) {
|
|
63
|
+
antiPatterns.push({
|
|
64
|
+
taskId: te.taskId,
|
|
65
|
+
taskTitle: te.taskTitle,
|
|
66
|
+
kind: 'anti-pattern',
|
|
67
|
+
fixIterations: iterations,
|
|
68
|
+
verdict: te.verdict,
|
|
69
|
+
});
|
|
70
|
+
} else if (iterations === 0 && te.verdict === 'DONE') {
|
|
71
|
+
cleanTasks.push({
|
|
72
|
+
taskId: te.taskId,
|
|
73
|
+
taskTitle: te.taskTitle,
|
|
74
|
+
kind: 'clean',
|
|
75
|
+
fixIterations: 0,
|
|
76
|
+
verdict: te.verdict,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Unplanned changes signal scope creep
|
|
82
|
+
for (const uc of report.unplannedChanges) {
|
|
83
|
+
scopeCreep.push({
|
|
84
|
+
taskId: 'unplanned',
|
|
85
|
+
taskTitle: uc.file.path,
|
|
86
|
+
kind: 'scope-creep',
|
|
87
|
+
fixIterations: 0,
|
|
88
|
+
verdict: uc.possibleReason,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return { antiPatterns, cleanTasks, scopeCreep };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ---------------------------------------------------------------------------
|
|
96
|
+
// Capture to vault + brain
|
|
97
|
+
// ---------------------------------------------------------------------------
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Persist quality signals to vault (anti-patterns) and brain (feedback).
|
|
101
|
+
* Deduplicates anti-patterns via vault search before adding.
|
|
102
|
+
* Best-effort — swallows all errors.
|
|
103
|
+
*/
|
|
104
|
+
export function captureQualitySignals(
|
|
105
|
+
analysis: QualityAnalysis,
|
|
106
|
+
vault: Vault,
|
|
107
|
+
brain: Brain,
|
|
108
|
+
planId: string,
|
|
109
|
+
): { captured: number; skipped: number; feedback: number } {
|
|
110
|
+
let captured = 0;
|
|
111
|
+
let skipped = 0;
|
|
112
|
+
let feedback = 0;
|
|
113
|
+
|
|
114
|
+
// Capture anti-patterns to vault (dedup first)
|
|
115
|
+
for (const ap of analysis.antiPatterns) {
|
|
116
|
+
try {
|
|
117
|
+
const query = `rework fix-trail ${ap.taskTitle}`;
|
|
118
|
+
const existing = vault.search(query, { type: 'anti-pattern', limit: 3 });
|
|
119
|
+
const isDuplicate = existing.some((r) => r.score > 0.7);
|
|
120
|
+
|
|
121
|
+
if (isDuplicate) {
|
|
122
|
+
skipped++;
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const severity = ap.fixIterations > 4 ? 'critical' : 'warning';
|
|
127
|
+
const entry: IntelligenceEntry = {
|
|
128
|
+
id: `qs-ap-${planId}-${ap.taskId}-${Date.now()}`,
|
|
129
|
+
type: 'anti-pattern',
|
|
130
|
+
domain: 'engineering',
|
|
131
|
+
title: `Rework detected: ${ap.taskTitle}`,
|
|
132
|
+
severity,
|
|
133
|
+
description:
|
|
134
|
+
`Task "${ap.taskTitle}" required ${ap.fixIterations} fix iterations ` +
|
|
135
|
+
`(threshold: ${REWORK_THRESHOLD}). Investigate root cause — ` +
|
|
136
|
+
`unclear requirements, missing tests, or incomplete understanding.`,
|
|
137
|
+
tags: ['rework', 'fix-trail', 'auto-captured'],
|
|
138
|
+
origin: 'agent',
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
vault.add(entry);
|
|
142
|
+
captured++;
|
|
143
|
+
} catch {
|
|
144
|
+
// Best-effort — skip this signal
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Record negative brain feedback for rework tasks
|
|
149
|
+
for (const ap of analysis.antiPatterns) {
|
|
150
|
+
try {
|
|
151
|
+
brain.recordFeedback(`quality-signal:rework:${ap.taskTitle}`, ap.taskId, 'dismissed');
|
|
152
|
+
feedback++;
|
|
153
|
+
} catch {
|
|
154
|
+
// Best-effort
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Record positive brain feedback for clean tasks
|
|
159
|
+
for (const ct of analysis.cleanTasks) {
|
|
160
|
+
try {
|
|
161
|
+
brain.recordFeedback(`quality-signal:clean:${ct.taskTitle}`, ct.taskId, 'accepted');
|
|
162
|
+
feedback++;
|
|
163
|
+
} catch {
|
|
164
|
+
// Best-effort
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return { captured, skipped, feedback };
|
|
169
|
+
}
|
|
@@ -92,7 +92,7 @@ function walkDir(rootDir: string, currentDir: string, inventory: SkillInventoryI
|
|
|
92
92
|
|
|
93
93
|
if (!stat.isFile()) continue;
|
|
94
94
|
|
|
95
|
-
const relPath = relative(rootDir, fullPath);
|
|
95
|
+
const relPath = relative(rootDir, fullPath).replaceAll('\\', '/');
|
|
96
96
|
const ext = extname(name).toLowerCase();
|
|
97
97
|
const kind = classifyFile(name, ext);
|
|
98
98
|
|