@soleri/core 9.10.0 → 9.12.1
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/adapters/types.d.ts +2 -0
- package/dist/adapters/types.d.ts.map +1 -1
- package/dist/brain/brain.d.ts +5 -1
- package/dist/brain/brain.d.ts.map +1 -1
- package/dist/brain/brain.js +97 -10
- package/dist/brain/brain.js.map +1 -1
- package/dist/brain/intelligence.d.ts.map +1 -1
- package/dist/brain/intelligence.js +4 -0
- package/dist/brain/intelligence.js.map +1 -1
- package/dist/brain/types.d.ts +1 -1
- package/dist/brain/types.d.ts.map +1 -1
- package/dist/dream/cron-manager.d.ts +10 -0
- package/dist/dream/cron-manager.d.ts.map +1 -0
- package/dist/dream/cron-manager.js +122 -0
- package/dist/dream/cron-manager.js.map +1 -0
- package/dist/dream/dream-engine.d.ts +34 -0
- package/dist/dream/dream-engine.d.ts.map +1 -0
- package/dist/dream/dream-engine.js +88 -0
- package/dist/dream/dream-engine.js.map +1 -0
- package/dist/dream/dream-ops.d.ts +8 -0
- package/dist/dream/dream-ops.d.ts.map +1 -0
- package/dist/dream/dream-ops.js +49 -0
- package/dist/dream/dream-ops.js.map +1 -0
- package/dist/dream/index.d.ts +7 -0
- package/dist/dream/index.d.ts.map +1 -0
- package/dist/dream/index.js +5 -0
- package/dist/dream/index.js.map +1 -0
- package/dist/dream/schema.d.ts +3 -0
- package/dist/dream/schema.d.ts.map +1 -0
- package/dist/dream/schema.js +16 -0
- package/dist/dream/schema.js.map +1 -0
- package/dist/embeddings/index.d.ts +5 -0
- package/dist/embeddings/index.d.ts.map +1 -0
- package/dist/embeddings/index.js +3 -0
- package/dist/embeddings/index.js.map +1 -0
- package/dist/embeddings/openai-provider.d.ts +31 -0
- package/dist/embeddings/openai-provider.d.ts.map +1 -0
- package/dist/embeddings/openai-provider.js +120 -0
- package/dist/embeddings/openai-provider.js.map +1 -0
- package/dist/embeddings/pipeline.d.ts +36 -0
- package/dist/embeddings/pipeline.d.ts.map +1 -0
- package/dist/embeddings/pipeline.js +78 -0
- package/dist/embeddings/pipeline.js.map +1 -0
- package/dist/embeddings/types.d.ts +62 -0
- package/dist/embeddings/types.d.ts.map +1 -0
- package/dist/embeddings/types.js +3 -0
- package/dist/embeddings/types.js.map +1 -0
- package/dist/engine/bin/soleri-engine.js +4 -1
- package/dist/engine/bin/soleri-engine.js.map +1 -1
- package/dist/engine/module-manifest.d.ts.map +1 -1
- package/dist/engine/module-manifest.js +20 -0
- package/dist/engine/module-manifest.js.map +1 -1
- package/dist/engine/register-engine.d.ts.map +1 -1
- package/dist/engine/register-engine.js +12 -0
- package/dist/engine/register-engine.js.map +1 -1
- package/dist/flows/chain-types.d.ts +8 -8
- package/dist/flows/dispatch-registry.d.ts +15 -1
- package/dist/flows/dispatch-registry.d.ts.map +1 -1
- package/dist/flows/dispatch-registry.js +28 -1
- package/dist/flows/dispatch-registry.js.map +1 -1
- package/dist/flows/executor.d.ts +20 -2
- package/dist/flows/executor.d.ts.map +1 -1
- package/dist/flows/executor.js +79 -1
- package/dist/flows/executor.js.map +1 -1
- package/dist/flows/index.d.ts +2 -1
- package/dist/flows/index.d.ts.map +1 -1
- package/dist/flows/index.js.map +1 -1
- package/dist/flows/types.d.ts +43 -21
- package/dist/flows/types.d.ts.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/planning/plan-lifecycle.d.ts.map +1 -1
- package/dist/planning/plan-lifecycle.js +4 -2
- package/dist/planning/plan-lifecycle.js.map +1 -1
- package/dist/planning/planner-types.d.ts +1 -1
- package/dist/planning/planner-types.d.ts.map +1 -1
- package/dist/plugins/types.d.ts +31 -31
- package/dist/runtime/admin-ops.d.ts.map +1 -1
- package/dist/runtime/admin-ops.js +15 -0
- package/dist/runtime/admin-ops.js.map +1 -1
- package/dist/runtime/admin-setup-ops.js +2 -2
- package/dist/runtime/admin-setup-ops.js.map +1 -1
- package/dist/runtime/embedding-ops.d.ts +12 -0
- package/dist/runtime/embedding-ops.d.ts.map +1 -0
- package/dist/runtime/embedding-ops.js +96 -0
- package/dist/runtime/embedding-ops.js.map +1 -0
- package/dist/runtime/facades/embedding-facade.d.ts +7 -0
- package/dist/runtime/facades/embedding-facade.d.ts.map +1 -0
- package/dist/runtime/facades/embedding-facade.js +8 -0
- package/dist/runtime/facades/embedding-facade.js.map +1 -0
- package/dist/runtime/facades/index.d.ts.map +1 -1
- package/dist/runtime/facades/index.js +12 -0
- package/dist/runtime/facades/index.js.map +1 -1
- package/dist/runtime/facades/orchestrate-facade.d.ts.map +1 -1
- package/dist/runtime/facades/orchestrate-facade.js +120 -0
- package/dist/runtime/facades/orchestrate-facade.js.map +1 -1
- package/dist/runtime/feature-flags.d.ts.map +1 -1
- package/dist/runtime/feature-flags.js +4 -0
- package/dist/runtime/feature-flags.js.map +1 -1
- package/dist/runtime/orchestrate-ops.d.ts.map +1 -1
- package/dist/runtime/orchestrate-ops.js +146 -12
- package/dist/runtime/orchestrate-ops.js.map +1 -1
- package/dist/runtime/planning-extra-ops.d.ts.map +1 -1
- package/dist/runtime/planning-extra-ops.js +51 -0
- package/dist/runtime/planning-extra-ops.js.map +1 -1
- package/dist/runtime/preflight.d.ts +32 -0
- package/dist/runtime/preflight.d.ts.map +1 -0
- package/dist/runtime/preflight.js +29 -0
- package/dist/runtime/preflight.js.map +1 -0
- package/dist/runtime/quality-signals.d.ts +6 -1
- package/dist/runtime/quality-signals.d.ts.map +1 -1
- package/dist/runtime/quality-signals.js +41 -5
- package/dist/runtime/quality-signals.js.map +1 -1
- package/dist/runtime/runtime.d.ts.map +1 -1
- package/dist/runtime/runtime.js +33 -2
- package/dist/runtime/runtime.js.map +1 -1
- package/dist/runtime/types.d.ts +27 -0
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/skills/step-tracker.d.ts +39 -0
- package/dist/skills/step-tracker.d.ts.map +1 -0
- package/dist/skills/step-tracker.js +105 -0
- package/dist/skills/step-tracker.js.map +1 -0
- package/dist/skills/sync-skills.d.ts +3 -2
- package/dist/skills/sync-skills.d.ts.map +1 -1
- package/dist/skills/sync-skills.js +42 -8
- package/dist/skills/sync-skills.js.map +1 -1
- package/dist/subagent/dispatcher.d.ts +4 -3
- package/dist/subagent/dispatcher.d.ts.map +1 -1
- package/dist/subagent/dispatcher.js +57 -35
- package/dist/subagent/dispatcher.js.map +1 -1
- package/dist/subagent/index.d.ts +1 -0
- package/dist/subagent/index.d.ts.map +1 -1
- package/dist/subagent/index.js.map +1 -1
- package/dist/subagent/orphan-reaper.d.ts +51 -4
- package/dist/subagent/orphan-reaper.d.ts.map +1 -1
- package/dist/subagent/orphan-reaper.js +103 -3
- package/dist/subagent/orphan-reaper.js.map +1 -1
- package/dist/subagent/types.d.ts +7 -0
- package/dist/subagent/types.d.ts.map +1 -1
- package/dist/subagent/workspace-resolver.d.ts +2 -0
- package/dist/subagent/workspace-resolver.d.ts.map +1 -1
- package/dist/subagent/workspace-resolver.js +3 -1
- package/dist/subagent/workspace-resolver.js.map +1 -1
- package/dist/vault/vault-entries.d.ts +18 -0
- package/dist/vault/vault-entries.d.ts.map +1 -1
- package/dist/vault/vault-entries.js +73 -0
- package/dist/vault/vault-entries.js.map +1 -1
- package/dist/vault/vault-manager.d.ts.map +1 -1
- package/dist/vault/vault-manager.js +1 -0
- package/dist/vault/vault-manager.js.map +1 -1
- package/dist/vault/vault-schema.d.ts.map +1 -1
- package/dist/vault/vault-schema.js +14 -0
- package/dist/vault/vault-schema.js.map +1 -1
- package/dist/vault/vault.d.ts +1 -0
- package/dist/vault/vault.d.ts.map +1 -1
- package/dist/vault/vault.js.map +1 -1
- package/package.json +3 -5
- package/src/__tests__/cron-manager.test.ts +132 -0
- package/src/__tests__/deviation-detection.test.ts +234 -0
- package/src/__tests__/embeddings.test.ts +536 -0
- package/src/__tests__/preflight.test.ts +97 -0
- package/src/__tests__/step-persistence.test.ts +324 -0
- package/src/__tests__/step-tracker.test.ts +260 -0
- package/src/__tests__/subagent/dispatcher.test.ts +122 -4
- package/src/__tests__/subagent/orphan-reaper.test.ts +148 -12
- package/src/__tests__/subagent/process-lifecycle.test.ts +422 -0
- package/src/__tests__/subagent/workspace-resolver.test.ts +6 -1
- package/src/adapters/types.ts +2 -0
- package/src/brain/brain.ts +117 -9
- package/src/brain/intelligence.ts +4 -0
- package/src/brain/types.ts +6 -1
- package/src/dream/cron-manager.ts +137 -0
- package/src/dream/dream-engine.ts +119 -0
- package/src/dream/dream-ops.ts +56 -0
- package/src/dream/dream.test.ts +182 -0
- package/src/dream/index.ts +6 -0
- package/src/dream/schema.ts +17 -0
- package/src/embeddings/openai-provider.ts +158 -0
- package/src/embeddings/pipeline.ts +126 -0
- package/src/embeddings/types.ts +67 -0
- package/src/engine/bin/soleri-engine.ts +4 -1
- package/src/engine/module-manifest.test.ts +4 -4
- package/src/engine/module-manifest.ts +20 -0
- package/src/engine/register-engine.ts +12 -0
- package/src/flows/dispatch-registry.ts +44 -1
- package/src/flows/executor.ts +93 -2
- package/src/flows/index.ts +2 -0
- package/src/flows/types.ts +39 -1
- package/src/index.ts +11 -0
- package/src/planning/goal-ancestry.test.ts +3 -5
- package/src/planning/plan-lifecycle.ts +5 -2
- package/src/planning/planner-types.ts +1 -1
- package/src/planning/planner.test.ts +73 -3
- package/src/runtime/admin-ops.test.ts +2 -2
- package/src/runtime/admin-ops.ts +17 -0
- package/src/runtime/admin-setup-ops.ts +2 -2
- package/src/runtime/embedding-ops.ts +116 -0
- package/src/runtime/facades/admin-facade.test.ts +31 -0
- package/src/runtime/facades/embedding-facade.ts +11 -0
- package/src/runtime/facades/index.ts +12 -0
- package/src/runtime/facades/orchestrate-facade.test.ts +16 -0
- package/src/runtime/facades/orchestrate-facade.ts +146 -0
- package/src/runtime/feature-flags.ts +4 -0
- package/src/runtime/orchestrate-ops.test.ts +182 -2
- package/src/runtime/orchestrate-ops.ts +170 -13
- package/src/runtime/planning-extra-ops.ts +77 -0
- package/src/runtime/preflight.ts +53 -0
- package/src/runtime/quality-signals.test.ts +182 -8
- package/src/runtime/quality-signals.ts +44 -5
- package/src/runtime/runtime.ts +41 -2
- package/src/runtime/types.ts +20 -0
- package/src/skills/__tests__/sync-skills.test.ts +132 -0
- package/src/skills/step-tracker.ts +162 -0
- package/src/skills/sync-skills.ts +54 -9
- package/src/subagent/dispatcher.ts +62 -39
- package/src/subagent/index.ts +1 -0
- package/src/subagent/orphan-reaper.test.ts +135 -0
- package/src/subagent/orphan-reaper.ts +130 -7
- package/src/subagent/types.ts +10 -0
- package/src/subagent/workspace-resolver.ts +3 -1
- package/src/vault/vault-entries.ts +112 -0
- package/src/vault/vault-manager.ts +1 -0
- package/src/vault/vault-scaling.test.ts +3 -2
- package/src/vault/vault-schema.ts +15 -0
- package/src/vault/vault.ts +1 -0
- package/vitest.config.ts +2 -1
- package/dist/brain/strength-scorer.d.ts +0 -31
- package/dist/brain/strength-scorer.d.ts.map +0 -1
- package/dist/brain/strength-scorer.js +0 -264
- package/dist/brain/strength-scorer.js.map +0 -1
- package/dist/engine/index.d.ts +0 -21
- package/dist/engine/index.d.ts.map +0 -1
- package/dist/engine/index.js +0 -18
- package/dist/engine/index.js.map +0 -1
- package/dist/hooks/index.d.ts +0 -2
- package/dist/hooks/index.d.ts.map +0 -1
- package/dist/hooks/index.js +0 -2
- package/dist/hooks/index.js.map +0 -1
- package/dist/persona/index.d.ts +0 -5
- package/dist/persona/index.d.ts.map +0 -1
- package/dist/persona/index.js +0 -4
- package/dist/persona/index.js.map +0 -1
- package/dist/vault/vault-interfaces.d.ts +0 -153
- package/dist/vault/vault-interfaces.d.ts.map +0 -1
- package/dist/vault/vault-interfaces.js +0 -2
- package/dist/vault/vault-interfaces.js.map +0 -1
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
analyzeQualitySignals,
|
|
4
|
+
captureQualitySignals,
|
|
5
|
+
buildFixTrailSummary,
|
|
6
|
+
} from './quality-signals.js';
|
|
3
7
|
import type { EvidenceReport } from '../planning/evidence-collector.js';
|
|
4
8
|
|
|
5
9
|
// ---------------------------------------------------------------------------
|
|
@@ -104,7 +108,7 @@ describe('analyzeQualitySignals', () => {
|
|
|
104
108
|
expect(result.antiPatterns).toHaveLength(0);
|
|
105
109
|
});
|
|
106
110
|
|
|
107
|
-
it('
|
|
111
|
+
it('flags task with fixIterations === 2 (at threshold)', () => {
|
|
108
112
|
const report = makeReport({
|
|
109
113
|
taskEvidence: [
|
|
110
114
|
{
|
|
@@ -120,7 +124,28 @@ describe('analyzeQualitySignals', () => {
|
|
|
120
124
|
|
|
121
125
|
const result = analyzeQualitySignals(report);
|
|
122
126
|
|
|
127
|
+
expect(result.antiPatterns).toHaveLength(1);
|
|
128
|
+
expect(result.antiPatterns[0].fixIterations).toBe(2);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('does not flag task with fixIterations === 1 (below threshold)', () => {
|
|
132
|
+
const report = makeReport({
|
|
133
|
+
taskEvidence: [
|
|
134
|
+
{
|
|
135
|
+
taskId: 't4b',
|
|
136
|
+
taskTitle: 'Single retry task',
|
|
137
|
+
plannedStatus: 'completed',
|
|
138
|
+
matchedFiles: [],
|
|
139
|
+
verdict: 'DONE',
|
|
140
|
+
fixIterations: 1,
|
|
141
|
+
},
|
|
142
|
+
],
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
const result = analyzeQualitySignals(report);
|
|
146
|
+
|
|
123
147
|
expect(result.antiPatterns).toHaveLength(0);
|
|
148
|
+
expect(result.cleanTasks).toHaveLength(0);
|
|
124
149
|
});
|
|
125
150
|
|
|
126
151
|
it('detects scope creep from unplanned changes', () => {
|
|
@@ -199,9 +224,14 @@ describe('captureQualitySignals', () => {
|
|
|
199
224
|
expect(entry.tags).toContain('auto-captured');
|
|
200
225
|
|
|
201
226
|
expect(brain.recordFeedback).toHaveBeenCalledWith(
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
227
|
+
expect.objectContaining({
|
|
228
|
+
query: 'Fix login',
|
|
229
|
+
entryId: 'plan-1',
|
|
230
|
+
action: 'dismissed',
|
|
231
|
+
confidence: 0.7,
|
|
232
|
+
source: 'evidence-quality',
|
|
233
|
+
reason: 'Task needed 3 fix iterations — high rework',
|
|
234
|
+
}),
|
|
205
235
|
);
|
|
206
236
|
|
|
207
237
|
expect(result.captured).toBe(1);
|
|
@@ -226,9 +256,14 @@ describe('captureQualitySignals', () => {
|
|
|
226
256
|
const result = captureQualitySignals(analysis, vault, brain, 'plan-1');
|
|
227
257
|
|
|
228
258
|
expect(brain.recordFeedback).toHaveBeenCalledWith(
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
259
|
+
expect.objectContaining({
|
|
260
|
+
query: 'Add feature',
|
|
261
|
+
entryId: 'plan-1',
|
|
262
|
+
action: 'accepted',
|
|
263
|
+
confidence: 0.9,
|
|
264
|
+
source: 'evidence-quality',
|
|
265
|
+
reason: 'Clean first-try completion — no rework needed',
|
|
266
|
+
}),
|
|
232
267
|
);
|
|
233
268
|
expect(result.feedback).toBe(1);
|
|
234
269
|
expect(result.captured).toBe(0);
|
|
@@ -281,6 +316,83 @@ describe('captureQualitySignals', () => {
|
|
|
281
316
|
expect(entry.severity).toBe('critical');
|
|
282
317
|
});
|
|
283
318
|
|
|
319
|
+
it('records positive feedback with evidence-quality source for clean first-try tasks', () => {
|
|
320
|
+
const analysis = {
|
|
321
|
+
antiPatterns: [],
|
|
322
|
+
cleanTasks: [
|
|
323
|
+
{
|
|
324
|
+
taskId: 'clean-1',
|
|
325
|
+
taskTitle: 'Smooth task',
|
|
326
|
+
kind: 'clean' as const,
|
|
327
|
+
fixIterations: 0,
|
|
328
|
+
verdict: 'DONE',
|
|
329
|
+
},
|
|
330
|
+
],
|
|
331
|
+
scopeCreep: [],
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
captureQualitySignals(analysis, vault, brain, 'plan-99');
|
|
335
|
+
|
|
336
|
+
expect(brain.recordFeedback).toHaveBeenCalledWith(
|
|
337
|
+
expect.objectContaining({
|
|
338
|
+
action: 'accepted',
|
|
339
|
+
confidence: 0.9,
|
|
340
|
+
source: 'evidence-quality',
|
|
341
|
+
entryId: 'plan-99',
|
|
342
|
+
}),
|
|
343
|
+
);
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
it('records negative feedback with evidence-quality source for high-rework tasks', () => {
|
|
347
|
+
const analysis = {
|
|
348
|
+
antiPatterns: [
|
|
349
|
+
{
|
|
350
|
+
taskId: 'rework-1',
|
|
351
|
+
taskTitle: 'Painful task',
|
|
352
|
+
kind: 'anti-pattern' as const,
|
|
353
|
+
fixIterations: 3,
|
|
354
|
+
verdict: 'DONE',
|
|
355
|
+
},
|
|
356
|
+
],
|
|
357
|
+
cleanTasks: [],
|
|
358
|
+
scopeCreep: [],
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
captureQualitySignals(analysis, vault, brain, 'plan-99');
|
|
362
|
+
|
|
363
|
+
expect(brain.recordFeedback).toHaveBeenCalledWith(
|
|
364
|
+
expect.objectContaining({
|
|
365
|
+
action: 'dismissed',
|
|
366
|
+
confidence: 0.7,
|
|
367
|
+
source: 'evidence-quality',
|
|
368
|
+
reason: 'Task needed 3 fix iterations — high rework',
|
|
369
|
+
context: JSON.stringify({ taskId: 'rework-1', reworkCount: 3, verdict: 'DONE' }),
|
|
370
|
+
}),
|
|
371
|
+
);
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
it('does not record evidence-quality feedback for tasks with 1 fix iteration', () => {
|
|
375
|
+
// 1 fix iteration = neither clean (fixIterations !== 0) nor anti-pattern (< 2)
|
|
376
|
+
const analysis = analyzeQualitySignals(
|
|
377
|
+
makeReport({
|
|
378
|
+
taskEvidence: [
|
|
379
|
+
{
|
|
380
|
+
taskId: 't-mid',
|
|
381
|
+
taskTitle: 'Single retry',
|
|
382
|
+
plannedStatus: 'completed',
|
|
383
|
+
matchedFiles: [],
|
|
384
|
+
verdict: 'DONE',
|
|
385
|
+
fixIterations: 1,
|
|
386
|
+
},
|
|
387
|
+
],
|
|
388
|
+
}),
|
|
389
|
+
);
|
|
390
|
+
|
|
391
|
+
captureQualitySignals(analysis, vault, brain, 'plan-99');
|
|
392
|
+
|
|
393
|
+
expect(brain.recordFeedback).not.toHaveBeenCalled();
|
|
394
|
+
});
|
|
395
|
+
|
|
284
396
|
it('handles mixed signals correctly', () => {
|
|
285
397
|
const analysis = {
|
|
286
398
|
antiPatterns: [
|
|
@@ -310,3 +422,65 @@ describe('captureQualitySignals', () => {
|
|
|
310
422
|
expect(result.feedback).toBe(2); // 1 dismissed + 1 accepted
|
|
311
423
|
});
|
|
312
424
|
});
|
|
425
|
+
|
|
426
|
+
// ---------------------------------------------------------------------------
|
|
427
|
+
// buildFixTrailSummary
|
|
428
|
+
// ---------------------------------------------------------------------------
|
|
429
|
+
|
|
430
|
+
describe('buildFixTrailSummary', () => {
|
|
431
|
+
it('returns summary string for tasks with rework iterations', () => {
|
|
432
|
+
const report = makeReport({
|
|
433
|
+
taskEvidence: [
|
|
434
|
+
{
|
|
435
|
+
taskId: 'a',
|
|
436
|
+
taskTitle: 'Task A',
|
|
437
|
+
plannedStatus: 'completed',
|
|
438
|
+
matchedFiles: [],
|
|
439
|
+
verdict: 'DONE',
|
|
440
|
+
fixIterations: 2,
|
|
441
|
+
},
|
|
442
|
+
{
|
|
443
|
+
taskId: 'b',
|
|
444
|
+
taskTitle: 'Task B',
|
|
445
|
+
plannedStatus: 'completed',
|
|
446
|
+
matchedFiles: [],
|
|
447
|
+
verdict: 'DONE',
|
|
448
|
+
fixIterations: 0,
|
|
449
|
+
},
|
|
450
|
+
{
|
|
451
|
+
taskId: 'c',
|
|
452
|
+
taskTitle: 'Task C',
|
|
453
|
+
plannedStatus: 'completed',
|
|
454
|
+
matchedFiles: [],
|
|
455
|
+
verdict: 'DONE',
|
|
456
|
+
fixIterations: 3,
|
|
457
|
+
},
|
|
458
|
+
],
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
const summary = buildFixTrailSummary(report);
|
|
462
|
+
expect(summary).toBe('Task A: 2 fix iterations; Task C: 3 fix iterations');
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
it('returns undefined when no tasks have rework', () => {
|
|
466
|
+
const report = makeReport({
|
|
467
|
+
taskEvidence: [
|
|
468
|
+
{
|
|
469
|
+
taskId: 'a',
|
|
470
|
+
taskTitle: 'Clean',
|
|
471
|
+
plannedStatus: 'completed',
|
|
472
|
+
matchedFiles: [],
|
|
473
|
+
verdict: 'DONE',
|
|
474
|
+
fixIterations: 0,
|
|
475
|
+
},
|
|
476
|
+
],
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
expect(buildFixTrailSummary(report)).toBeUndefined();
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
it('returns undefined for empty task evidence', () => {
|
|
483
|
+
const report = makeReport({ taskEvidence: [] });
|
|
484
|
+
expect(buildFixTrailSummary(report)).toBeUndefined();
|
|
485
|
+
});
|
|
486
|
+
});
|
|
@@ -34,8 +34,12 @@ export interface QualityAnalysis {
|
|
|
34
34
|
// Thresholds
|
|
35
35
|
// ---------------------------------------------------------------------------
|
|
36
36
|
|
|
37
|
-
/** Tasks with
|
|
37
|
+
/** Tasks with this many or more fix iterations are flagged as anti-patterns. */
|
|
38
38
|
const REWORK_THRESHOLD = 2;
|
|
39
|
+
/** Brain feedback confidence for clean first-try tasks. */
|
|
40
|
+
const CLEAN_TASK_CONFIDENCE = 0.9;
|
|
41
|
+
/** Brain feedback confidence for high-rework anti-pattern tasks. */
|
|
42
|
+
const REWORK_TASK_CONFIDENCE = 0.7;
|
|
39
43
|
|
|
40
44
|
// ---------------------------------------------------------------------------
|
|
41
45
|
// Analysis
|
|
@@ -44,7 +48,7 @@ const REWORK_THRESHOLD = 2;
|
|
|
44
48
|
/**
|
|
45
49
|
* Analyze an evidence report for quality signals.
|
|
46
50
|
*
|
|
47
|
-
* - fixIterations
|
|
51
|
+
* - fixIterations >= 2 → anti-pattern (rework)
|
|
48
52
|
* - fixIterations === 0 + verdict DONE → clean (first-pass success)
|
|
49
53
|
* - unplannedChanges → scope-creep signals
|
|
50
54
|
*/
|
|
@@ -59,7 +63,7 @@ export function analyzeQualitySignals(
|
|
|
59
63
|
for (const te of report.taskEvidence) {
|
|
60
64
|
const iterations = te.fixIterations ?? 0;
|
|
61
65
|
|
|
62
|
-
if (iterations
|
|
66
|
+
if (iterations >= REWORK_THRESHOLD) {
|
|
63
67
|
antiPatterns.push({
|
|
64
68
|
taskId: te.taskId,
|
|
65
69
|
taskTitle: te.taskTitle,
|
|
@@ -148,7 +152,19 @@ export function captureQualitySignals(
|
|
|
148
152
|
// Record negative brain feedback for rework tasks
|
|
149
153
|
for (const ap of analysis.antiPatterns) {
|
|
150
154
|
try {
|
|
151
|
-
brain.recordFeedback(
|
|
155
|
+
brain.recordFeedback({
|
|
156
|
+
query: ap.taskTitle,
|
|
157
|
+
entryId: planId,
|
|
158
|
+
action: 'dismissed',
|
|
159
|
+
confidence: REWORK_TASK_CONFIDENCE,
|
|
160
|
+
source: 'evidence-quality',
|
|
161
|
+
reason: `Task needed ${ap.fixIterations} fix iterations — high rework`,
|
|
162
|
+
context: JSON.stringify({
|
|
163
|
+
taskId: ap.taskId,
|
|
164
|
+
reworkCount: ap.fixIterations,
|
|
165
|
+
verdict: ap.verdict,
|
|
166
|
+
}),
|
|
167
|
+
});
|
|
152
168
|
feedback++;
|
|
153
169
|
} catch {
|
|
154
170
|
// Best-effort
|
|
@@ -158,7 +174,14 @@ export function captureQualitySignals(
|
|
|
158
174
|
// Record positive brain feedback for clean tasks
|
|
159
175
|
for (const ct of analysis.cleanTasks) {
|
|
160
176
|
try {
|
|
161
|
-
brain.recordFeedback(
|
|
177
|
+
brain.recordFeedback({
|
|
178
|
+
query: ct.taskTitle,
|
|
179
|
+
entryId: planId,
|
|
180
|
+
action: 'accepted',
|
|
181
|
+
confidence: CLEAN_TASK_CONFIDENCE,
|
|
182
|
+
source: 'evidence-quality',
|
|
183
|
+
reason: 'Clean first-try completion — no rework needed',
|
|
184
|
+
});
|
|
162
185
|
feedback++;
|
|
163
186
|
} catch {
|
|
164
187
|
// Best-effort
|
|
@@ -167,3 +190,19 @@ export function captureQualitySignals(
|
|
|
167
190
|
|
|
168
191
|
return { captured, skipped, feedback };
|
|
169
192
|
}
|
|
193
|
+
|
|
194
|
+
// ---------------------------------------------------------------------------
|
|
195
|
+
// Fix-trail summary for knowledge extraction
|
|
196
|
+
// ---------------------------------------------------------------------------
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Build a human-readable fix-trail summary from an evidence report.
|
|
200
|
+
* Returns `undefined` when no tasks had rework iterations.
|
|
201
|
+
*/
|
|
202
|
+
export function buildFixTrailSummary(report: EvidenceReport): string | undefined {
|
|
203
|
+
const entries = report.taskEvidence
|
|
204
|
+
.filter((te) => (te.fixIterations ?? 0) > 0)
|
|
205
|
+
.map((te) => `${te.taskTitle}: ${te.fixIterations} fix iterations`);
|
|
206
|
+
|
|
207
|
+
return entries.length > 0 ? entries.join('; ') : undefined;
|
|
208
|
+
}
|
package/src/runtime/runtime.ts
CHANGED
|
@@ -56,6 +56,9 @@ import { PipelineRunner } from '../queue/pipeline-runner.js';
|
|
|
56
56
|
import { evaluateQuality } from '../curator/quality-gate.js';
|
|
57
57
|
import { classifyEntry } from '../curator/classifier.js';
|
|
58
58
|
import type { AgentRuntimeConfig, AgentRuntime } from './types.js';
|
|
59
|
+
import type { EmbeddingProvider } from '../embeddings/types.js';
|
|
60
|
+
import { OpenAIEmbeddingProvider } from '../embeddings/openai-provider.js';
|
|
61
|
+
import { EmbeddingPipeline } from '../embeddings/pipeline.js';
|
|
59
62
|
import { loadPersona } from '../persona/loader.js';
|
|
60
63
|
import { generatePersonaInstructions } from '../persona/prompt-generator.js';
|
|
61
64
|
import { OperatorProfileStore } from '../operator/operator-profile.js';
|
|
@@ -104,12 +107,46 @@ export function createAgentRuntime(config: AgentRuntimeConfig): AgentRuntime {
|
|
|
104
107
|
}
|
|
105
108
|
}
|
|
106
109
|
|
|
110
|
+
// Feature Flags — file-based + env var + runtime toggles (created early so other modules can check)
|
|
111
|
+
const flags = new FeatureFlags(getAgentFlagsPath(agentId));
|
|
112
|
+
|
|
107
113
|
// Planner — multi-step task tracking
|
|
108
114
|
const planner = new Planner(plansPath);
|
|
109
115
|
|
|
116
|
+
// ─── Embedding Provider (optional) ────────────────────────────────
|
|
117
|
+
// Only initialized when both config.embedding is present AND the
|
|
118
|
+
// 'embedding-enabled' feature flag is on. Brain continues without
|
|
119
|
+
// embeddings when either condition is unmet (vector weight stays 0).
|
|
120
|
+
let embeddingProvider: EmbeddingProvider | undefined;
|
|
121
|
+
let embeddingPipeline: EmbeddingPipeline | undefined;
|
|
122
|
+
|
|
123
|
+
if (config.embedding && flags.isEnabled('embedding-enabled')) {
|
|
124
|
+
try {
|
|
125
|
+
const embeddingConfig = config.embedding;
|
|
126
|
+
if (embeddingConfig.provider === 'openai') {
|
|
127
|
+
const openaiPool = new KeyPool(loadKeyPoolConfig(agentId).openai);
|
|
128
|
+
embeddingProvider = new OpenAIEmbeddingProvider(embeddingConfig, openaiPool);
|
|
129
|
+
}
|
|
130
|
+
// Future providers (ollama, etc.) would be added here
|
|
131
|
+
|
|
132
|
+
if (embeddingProvider) {
|
|
133
|
+
embeddingPipeline = new EmbeddingPipeline(embeddingProvider, vault.getProvider());
|
|
134
|
+
logger.info(
|
|
135
|
+
`[Embedding] Initialized: ${embeddingProvider.providerName}/${embeddingProvider.model} (${embeddingProvider.dimensions}d)`,
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
} catch (err) {
|
|
139
|
+
logger.warn(
|
|
140
|
+
`[Embedding] Failed to initialize: ${err instanceof Error ? err.message : String(err)}`,
|
|
141
|
+
);
|
|
142
|
+
// Graceful degradation — continue without embeddings
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
110
146
|
// Brain — intelligence layer (TF-IDF scoring, auto-tagging, dedup)
|
|
111
147
|
// Pass vaultManager so intelligentSearch queries all connected sources (not just agent tier)
|
|
112
|
-
|
|
148
|
+
// Pass embeddingProvider for hybrid FTS5+vector search when available
|
|
149
|
+
const brain = new Brain(vault, vaultManager, embeddingProvider);
|
|
113
150
|
|
|
114
151
|
// Brain Intelligence — pattern strengths, session knowledge, intelligence pipeline
|
|
115
152
|
const brainIntelligence = new BrainIntelligence(vault, brain);
|
|
@@ -366,7 +403,7 @@ export function createAgentRuntime(config: AgentRuntimeConfig): AgentRuntime {
|
|
|
366
403
|
intakePipeline,
|
|
367
404
|
textIngester,
|
|
368
405
|
authPolicy: { mode: 'permissive', callerLevel: 'admin' },
|
|
369
|
-
flags
|
|
406
|
+
flags,
|
|
370
407
|
health,
|
|
371
408
|
playbookExecutor,
|
|
372
409
|
pluginRegistry,
|
|
@@ -392,6 +429,8 @@ export function createAgentRuntime(config: AgentRuntimeConfig): AgentRuntime {
|
|
|
392
429
|
const p = loadPersona(agentId, config.persona ?? undefined);
|
|
393
430
|
return generatePersonaInstructions(p);
|
|
394
431
|
})(),
|
|
432
|
+
embeddingProvider,
|
|
433
|
+
embeddingPipeline,
|
|
395
434
|
adapterRegistry,
|
|
396
435
|
subagentDispatcher,
|
|
397
436
|
contextHealth: new ContextHealthMonitor(),
|
package/src/runtime/types.ts
CHANGED
|
@@ -39,6 +39,20 @@ import type { ContextHealthMonitor } from './context-health.js';
|
|
|
39
39
|
import type { ShutdownRegistry } from './shutdown-registry.js';
|
|
40
40
|
import type { RuntimeAdapterRegistry } from '../adapters/registry.js';
|
|
41
41
|
import type { SubagentDispatcher } from '../subagent/dispatcher.js';
|
|
42
|
+
import type { EmbeddingConfig, EmbeddingProvider } from '../embeddings/types.js';
|
|
43
|
+
import type { EmbeddingPipeline } from '../embeddings/pipeline.js';
|
|
44
|
+
|
|
45
|
+
/** Pre-flight manifest returned by session_start for agent self-awareness. */
|
|
46
|
+
export interface PreflightManifest {
|
|
47
|
+
tools: Array<{ facade: string; op: string; description: string }>;
|
|
48
|
+
skills: string[];
|
|
49
|
+
activePlans: Array<{ planId: string; title: string; status: string }>;
|
|
50
|
+
vaultSummary: {
|
|
51
|
+
entryCount: number;
|
|
52
|
+
connected: boolean;
|
|
53
|
+
domains: string[];
|
|
54
|
+
};
|
|
55
|
+
}
|
|
42
56
|
|
|
43
57
|
/**
|
|
44
58
|
* Configuration for creating an agent runtime.
|
|
@@ -63,6 +77,8 @@ export interface AgentRuntimeConfig {
|
|
|
63
77
|
agentDir?: string;
|
|
64
78
|
/** Persona configuration from agent.yaml. If omitted, Italian Craftsperson default is used. */
|
|
65
79
|
persona?: Partial<import('../persona/types.js').PersonaConfig>;
|
|
80
|
+
/** Embedding provider configuration. If omitted, embeddings are disabled. */
|
|
81
|
+
embedding?: EmbeddingConfig;
|
|
66
82
|
}
|
|
67
83
|
|
|
68
84
|
/**
|
|
@@ -135,6 +151,10 @@ export interface AgentRuntime {
|
|
|
135
151
|
adapterRegistry: RuntimeAdapterRegistry;
|
|
136
152
|
/** Subagent dispatcher — spawn and manage child agent processes. */
|
|
137
153
|
subagentDispatcher: SubagentDispatcher;
|
|
154
|
+
/** Embedding provider — generates dense vectors for hybrid search (optional). */
|
|
155
|
+
embeddingProvider?: EmbeddingProvider;
|
|
156
|
+
/** Embedding pipeline — batch and incremental embedding of vault entries (optional). */
|
|
157
|
+
embeddingPipeline?: EmbeddingPipeline;
|
|
138
158
|
/** Context health monitor — tracks tool call volume and context window fill. */
|
|
139
159
|
contextHealth: ContextHealthMonitor;
|
|
140
160
|
/** Shutdown registry — centralized cleanup for timers, watchers, child processes. */
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
+
import { mkdirSync, writeFileSync, rmSync, existsSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
|
|
6
|
+
// =============================================================================
|
|
7
|
+
// HELPERS
|
|
8
|
+
// =============================================================================
|
|
9
|
+
|
|
10
|
+
let sourceDir: string;
|
|
11
|
+
let fakeHome: string;
|
|
12
|
+
|
|
13
|
+
function setup(): void {
|
|
14
|
+
const base = join(tmpdir(), `soleri-sync-test-${Date.now()}`);
|
|
15
|
+
mkdirSync(base, { recursive: true });
|
|
16
|
+
|
|
17
|
+
sourceDir = join(base, 'source-skills');
|
|
18
|
+
mkdirSync(sourceDir, { recursive: true });
|
|
19
|
+
|
|
20
|
+
fakeHome = join(base, 'fake-home');
|
|
21
|
+
mkdirSync(join(fakeHome, '.claude', 'skills'), { recursive: true });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function teardown(): void {
|
|
25
|
+
if (fakeHome) {
|
|
26
|
+
const base = join(fakeHome, '..');
|
|
27
|
+
rmSync(base, { recursive: true, force: true });
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Create a source skill directory with a minimal SKILL.md */
|
|
32
|
+
function createSourceSkill(name: string, content?: string): string {
|
|
33
|
+
const dir = join(sourceDir, name);
|
|
34
|
+
mkdirSync(dir, { recursive: true });
|
|
35
|
+
writeFileSync(
|
|
36
|
+
join(dir, 'SKILL.md'),
|
|
37
|
+
content ?? `---\nname: ${name}\n---\n\n# ${name}\n\nA test skill.\n`,
|
|
38
|
+
);
|
|
39
|
+
return dir;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Create a directory in the fake ~/.claude/skills/ target */
|
|
43
|
+
function createTargetSkillDir(name: string): string {
|
|
44
|
+
const dir = join(fakeHome, '.claude', 'skills', name);
|
|
45
|
+
mkdirSync(dir, { recursive: true });
|
|
46
|
+
writeFileSync(join(dir, 'SKILL.md'), `---\nname: ${name}\n---\n\nStale skill.\n`);
|
|
47
|
+
return dir;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function targetSkillsDir(): string {
|
|
51
|
+
return join(fakeHome, '.claude', 'skills');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function targetDirExists(name: string): boolean {
|
|
55
|
+
return existsSync(join(targetSkillsDir(), name));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// =============================================================================
|
|
59
|
+
// TESTS
|
|
60
|
+
// =============================================================================
|
|
61
|
+
|
|
62
|
+
describe('syncSkillsToClaudeCode — orphan cleanup', () => {
|
|
63
|
+
beforeEach(() => {
|
|
64
|
+
setup();
|
|
65
|
+
// Mock homedir() so syncSkillsToClaudeCode writes to our temp directory
|
|
66
|
+
vi.mock('node:os', async (importOriginal) => {
|
|
67
|
+
const original = await importOriginal<typeof import('node:os')>();
|
|
68
|
+
return {
|
|
69
|
+
...original,
|
|
70
|
+
homedir: () => fakeHome,
|
|
71
|
+
};
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
afterEach(() => {
|
|
76
|
+
vi.restoreAllMocks();
|
|
77
|
+
teardown();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('removes orphan directories that match the agent prefix', async () => {
|
|
81
|
+
// Source has "my-skill", target has stale "test-agent-old-skill"
|
|
82
|
+
createSourceSkill('my-skill');
|
|
83
|
+
createTargetSkillDir('test-agent-old-skill');
|
|
84
|
+
|
|
85
|
+
const { syncSkillsToClaudeCode } = await import('../sync-skills.js');
|
|
86
|
+
const result = syncSkillsToClaudeCode([sourceDir], 'Test Agent');
|
|
87
|
+
|
|
88
|
+
// The orphan should be reported as removed
|
|
89
|
+
expect(result.removed).toContain('test-agent-old-skill');
|
|
90
|
+
// The orphan directory should be gone
|
|
91
|
+
expect(targetDirExists('test-agent-old-skill')).toBe(false);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('does NOT remove directories that do not match the agent prefix', async () => {
|
|
95
|
+
createSourceSkill('my-skill');
|
|
96
|
+
// "other-agent-skill" does NOT start with "test-agent-"
|
|
97
|
+
createTargetSkillDir('other-agent-skill');
|
|
98
|
+
|
|
99
|
+
const { syncSkillsToClaudeCode } = await import('../sync-skills.js');
|
|
100
|
+
const result = syncSkillsToClaudeCode([sourceDir], 'Test Agent');
|
|
101
|
+
|
|
102
|
+
// Should still exist — not our prefix
|
|
103
|
+
expect(targetDirExists('other-agent-skill')).toBe(true);
|
|
104
|
+
expect(result.removed).not.toContain('other-agent-skill');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('does NOT remove a skill directory that was just synced', async () => {
|
|
108
|
+
createSourceSkill('active-skill');
|
|
109
|
+
// This directory matches the prefix AND is a current skill
|
|
110
|
+
createTargetSkillDir('test-agent-active-skill');
|
|
111
|
+
|
|
112
|
+
const { syncSkillsToClaudeCode } = await import('../sync-skills.js');
|
|
113
|
+
const result = syncSkillsToClaudeCode([sourceDir], 'Test Agent');
|
|
114
|
+
|
|
115
|
+
// "active-skill" should be synced (installed/updated/skipped), not removed
|
|
116
|
+
const synced = [...result.installed, ...result.updated, ...result.skipped];
|
|
117
|
+
expect(synced).toContain('active-skill');
|
|
118
|
+
expect(result.removed).not.toContain('test-agent-active-skill');
|
|
119
|
+
expect(targetDirExists('test-agent-active-skill')).toBe(true);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('returns an empty removed array when there are no orphans', async () => {
|
|
123
|
+
createSourceSkill('only-skill');
|
|
124
|
+
|
|
125
|
+
const { syncSkillsToClaudeCode } = await import('../sync-skills.js');
|
|
126
|
+
const result = syncSkillsToClaudeCode([sourceDir], 'Test Agent');
|
|
127
|
+
|
|
128
|
+
expect(result.removed).toBeDefined();
|
|
129
|
+
expect(Array.isArray(result.removed)).toBe(true);
|
|
130
|
+
expect(result.removed).toHaveLength(0);
|
|
131
|
+
});
|
|
132
|
+
});
|