@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.
Files changed (248) hide show
  1. package/dist/adapters/types.d.ts +2 -0
  2. package/dist/adapters/types.d.ts.map +1 -1
  3. package/dist/brain/brain.d.ts +5 -1
  4. package/dist/brain/brain.d.ts.map +1 -1
  5. package/dist/brain/brain.js +97 -10
  6. package/dist/brain/brain.js.map +1 -1
  7. package/dist/brain/intelligence.d.ts.map +1 -1
  8. package/dist/brain/intelligence.js +4 -0
  9. package/dist/brain/intelligence.js.map +1 -1
  10. package/dist/brain/types.d.ts +1 -1
  11. package/dist/brain/types.d.ts.map +1 -1
  12. package/dist/dream/cron-manager.d.ts +10 -0
  13. package/dist/dream/cron-manager.d.ts.map +1 -0
  14. package/dist/dream/cron-manager.js +122 -0
  15. package/dist/dream/cron-manager.js.map +1 -0
  16. package/dist/dream/dream-engine.d.ts +34 -0
  17. package/dist/dream/dream-engine.d.ts.map +1 -0
  18. package/dist/dream/dream-engine.js +88 -0
  19. package/dist/dream/dream-engine.js.map +1 -0
  20. package/dist/dream/dream-ops.d.ts +8 -0
  21. package/dist/dream/dream-ops.d.ts.map +1 -0
  22. package/dist/dream/dream-ops.js +49 -0
  23. package/dist/dream/dream-ops.js.map +1 -0
  24. package/dist/dream/index.d.ts +7 -0
  25. package/dist/dream/index.d.ts.map +1 -0
  26. package/dist/dream/index.js +5 -0
  27. package/dist/dream/index.js.map +1 -0
  28. package/dist/dream/schema.d.ts +3 -0
  29. package/dist/dream/schema.d.ts.map +1 -0
  30. package/dist/dream/schema.js +16 -0
  31. package/dist/dream/schema.js.map +1 -0
  32. package/dist/embeddings/index.d.ts +5 -0
  33. package/dist/embeddings/index.d.ts.map +1 -0
  34. package/dist/embeddings/index.js +3 -0
  35. package/dist/embeddings/index.js.map +1 -0
  36. package/dist/embeddings/openai-provider.d.ts +31 -0
  37. package/dist/embeddings/openai-provider.d.ts.map +1 -0
  38. package/dist/embeddings/openai-provider.js +120 -0
  39. package/dist/embeddings/openai-provider.js.map +1 -0
  40. package/dist/embeddings/pipeline.d.ts +36 -0
  41. package/dist/embeddings/pipeline.d.ts.map +1 -0
  42. package/dist/embeddings/pipeline.js +78 -0
  43. package/dist/embeddings/pipeline.js.map +1 -0
  44. package/dist/embeddings/types.d.ts +62 -0
  45. package/dist/embeddings/types.d.ts.map +1 -0
  46. package/dist/embeddings/types.js +3 -0
  47. package/dist/embeddings/types.js.map +1 -0
  48. package/dist/engine/bin/soleri-engine.js +4 -1
  49. package/dist/engine/bin/soleri-engine.js.map +1 -1
  50. package/dist/engine/module-manifest.d.ts.map +1 -1
  51. package/dist/engine/module-manifest.js +20 -0
  52. package/dist/engine/module-manifest.js.map +1 -1
  53. package/dist/engine/register-engine.d.ts.map +1 -1
  54. package/dist/engine/register-engine.js +12 -0
  55. package/dist/engine/register-engine.js.map +1 -1
  56. package/dist/flows/chain-types.d.ts +8 -8
  57. package/dist/flows/dispatch-registry.d.ts +15 -1
  58. package/dist/flows/dispatch-registry.d.ts.map +1 -1
  59. package/dist/flows/dispatch-registry.js +28 -1
  60. package/dist/flows/dispatch-registry.js.map +1 -1
  61. package/dist/flows/executor.d.ts +20 -2
  62. package/dist/flows/executor.d.ts.map +1 -1
  63. package/dist/flows/executor.js +79 -1
  64. package/dist/flows/executor.js.map +1 -1
  65. package/dist/flows/index.d.ts +2 -1
  66. package/dist/flows/index.d.ts.map +1 -1
  67. package/dist/flows/index.js.map +1 -1
  68. package/dist/flows/types.d.ts +43 -21
  69. package/dist/flows/types.d.ts.map +1 -1
  70. package/dist/index.d.ts +5 -0
  71. package/dist/index.d.ts.map +1 -1
  72. package/dist/index.js +3 -0
  73. package/dist/index.js.map +1 -1
  74. package/dist/planning/plan-lifecycle.d.ts.map +1 -1
  75. package/dist/planning/plan-lifecycle.js +4 -2
  76. package/dist/planning/plan-lifecycle.js.map +1 -1
  77. package/dist/planning/planner-types.d.ts +1 -1
  78. package/dist/planning/planner-types.d.ts.map +1 -1
  79. package/dist/plugins/types.d.ts +31 -31
  80. package/dist/runtime/admin-ops.d.ts.map +1 -1
  81. package/dist/runtime/admin-ops.js +15 -0
  82. package/dist/runtime/admin-ops.js.map +1 -1
  83. package/dist/runtime/admin-setup-ops.js +2 -2
  84. package/dist/runtime/admin-setup-ops.js.map +1 -1
  85. package/dist/runtime/embedding-ops.d.ts +12 -0
  86. package/dist/runtime/embedding-ops.d.ts.map +1 -0
  87. package/dist/runtime/embedding-ops.js +96 -0
  88. package/dist/runtime/embedding-ops.js.map +1 -0
  89. package/dist/runtime/facades/embedding-facade.d.ts +7 -0
  90. package/dist/runtime/facades/embedding-facade.d.ts.map +1 -0
  91. package/dist/runtime/facades/embedding-facade.js +8 -0
  92. package/dist/runtime/facades/embedding-facade.js.map +1 -0
  93. package/dist/runtime/facades/index.d.ts.map +1 -1
  94. package/dist/runtime/facades/index.js +12 -0
  95. package/dist/runtime/facades/index.js.map +1 -1
  96. package/dist/runtime/facades/orchestrate-facade.d.ts.map +1 -1
  97. package/dist/runtime/facades/orchestrate-facade.js +120 -0
  98. package/dist/runtime/facades/orchestrate-facade.js.map +1 -1
  99. package/dist/runtime/feature-flags.d.ts.map +1 -1
  100. package/dist/runtime/feature-flags.js +4 -0
  101. package/dist/runtime/feature-flags.js.map +1 -1
  102. package/dist/runtime/orchestrate-ops.d.ts.map +1 -1
  103. package/dist/runtime/orchestrate-ops.js +146 -12
  104. package/dist/runtime/orchestrate-ops.js.map +1 -1
  105. package/dist/runtime/planning-extra-ops.d.ts.map +1 -1
  106. package/dist/runtime/planning-extra-ops.js +51 -0
  107. package/dist/runtime/planning-extra-ops.js.map +1 -1
  108. package/dist/runtime/preflight.d.ts +32 -0
  109. package/dist/runtime/preflight.d.ts.map +1 -0
  110. package/dist/runtime/preflight.js +29 -0
  111. package/dist/runtime/preflight.js.map +1 -0
  112. package/dist/runtime/quality-signals.d.ts +6 -1
  113. package/dist/runtime/quality-signals.d.ts.map +1 -1
  114. package/dist/runtime/quality-signals.js +41 -5
  115. package/dist/runtime/quality-signals.js.map +1 -1
  116. package/dist/runtime/runtime.d.ts.map +1 -1
  117. package/dist/runtime/runtime.js +33 -2
  118. package/dist/runtime/runtime.js.map +1 -1
  119. package/dist/runtime/types.d.ts +27 -0
  120. package/dist/runtime/types.d.ts.map +1 -1
  121. package/dist/skills/step-tracker.d.ts +39 -0
  122. package/dist/skills/step-tracker.d.ts.map +1 -0
  123. package/dist/skills/step-tracker.js +105 -0
  124. package/dist/skills/step-tracker.js.map +1 -0
  125. package/dist/skills/sync-skills.d.ts +3 -2
  126. package/dist/skills/sync-skills.d.ts.map +1 -1
  127. package/dist/skills/sync-skills.js +42 -8
  128. package/dist/skills/sync-skills.js.map +1 -1
  129. package/dist/subagent/dispatcher.d.ts +4 -3
  130. package/dist/subagent/dispatcher.d.ts.map +1 -1
  131. package/dist/subagent/dispatcher.js +57 -35
  132. package/dist/subagent/dispatcher.js.map +1 -1
  133. package/dist/subagent/index.d.ts +1 -0
  134. package/dist/subagent/index.d.ts.map +1 -1
  135. package/dist/subagent/index.js.map +1 -1
  136. package/dist/subagent/orphan-reaper.d.ts +51 -4
  137. package/dist/subagent/orphan-reaper.d.ts.map +1 -1
  138. package/dist/subagent/orphan-reaper.js +103 -3
  139. package/dist/subagent/orphan-reaper.js.map +1 -1
  140. package/dist/subagent/types.d.ts +7 -0
  141. package/dist/subagent/types.d.ts.map +1 -1
  142. package/dist/subagent/workspace-resolver.d.ts +2 -0
  143. package/dist/subagent/workspace-resolver.d.ts.map +1 -1
  144. package/dist/subagent/workspace-resolver.js +3 -1
  145. package/dist/subagent/workspace-resolver.js.map +1 -1
  146. package/dist/vault/vault-entries.d.ts +18 -0
  147. package/dist/vault/vault-entries.d.ts.map +1 -1
  148. package/dist/vault/vault-entries.js +73 -0
  149. package/dist/vault/vault-entries.js.map +1 -1
  150. package/dist/vault/vault-manager.d.ts.map +1 -1
  151. package/dist/vault/vault-manager.js +1 -0
  152. package/dist/vault/vault-manager.js.map +1 -1
  153. package/dist/vault/vault-schema.d.ts.map +1 -1
  154. package/dist/vault/vault-schema.js +14 -0
  155. package/dist/vault/vault-schema.js.map +1 -1
  156. package/dist/vault/vault.d.ts +1 -0
  157. package/dist/vault/vault.d.ts.map +1 -1
  158. package/dist/vault/vault.js.map +1 -1
  159. package/package.json +3 -5
  160. package/src/__tests__/cron-manager.test.ts +132 -0
  161. package/src/__tests__/deviation-detection.test.ts +234 -0
  162. package/src/__tests__/embeddings.test.ts +536 -0
  163. package/src/__tests__/preflight.test.ts +97 -0
  164. package/src/__tests__/step-persistence.test.ts +324 -0
  165. package/src/__tests__/step-tracker.test.ts +260 -0
  166. package/src/__tests__/subagent/dispatcher.test.ts +122 -4
  167. package/src/__tests__/subagent/orphan-reaper.test.ts +148 -12
  168. package/src/__tests__/subagent/process-lifecycle.test.ts +422 -0
  169. package/src/__tests__/subagent/workspace-resolver.test.ts +6 -1
  170. package/src/adapters/types.ts +2 -0
  171. package/src/brain/brain.ts +117 -9
  172. package/src/brain/intelligence.ts +4 -0
  173. package/src/brain/types.ts +6 -1
  174. package/src/dream/cron-manager.ts +137 -0
  175. package/src/dream/dream-engine.ts +119 -0
  176. package/src/dream/dream-ops.ts +56 -0
  177. package/src/dream/dream.test.ts +182 -0
  178. package/src/dream/index.ts +6 -0
  179. package/src/dream/schema.ts +17 -0
  180. package/src/embeddings/openai-provider.ts +158 -0
  181. package/src/embeddings/pipeline.ts +126 -0
  182. package/src/embeddings/types.ts +67 -0
  183. package/src/engine/bin/soleri-engine.ts +4 -1
  184. package/src/engine/module-manifest.test.ts +4 -4
  185. package/src/engine/module-manifest.ts +20 -0
  186. package/src/engine/register-engine.ts +12 -0
  187. package/src/flows/dispatch-registry.ts +44 -1
  188. package/src/flows/executor.ts +93 -2
  189. package/src/flows/index.ts +2 -0
  190. package/src/flows/types.ts +39 -1
  191. package/src/index.ts +11 -0
  192. package/src/planning/goal-ancestry.test.ts +3 -5
  193. package/src/planning/plan-lifecycle.ts +5 -2
  194. package/src/planning/planner-types.ts +1 -1
  195. package/src/planning/planner.test.ts +73 -3
  196. package/src/runtime/admin-ops.test.ts +2 -2
  197. package/src/runtime/admin-ops.ts +17 -0
  198. package/src/runtime/admin-setup-ops.ts +2 -2
  199. package/src/runtime/embedding-ops.ts +116 -0
  200. package/src/runtime/facades/admin-facade.test.ts +31 -0
  201. package/src/runtime/facades/embedding-facade.ts +11 -0
  202. package/src/runtime/facades/index.ts +12 -0
  203. package/src/runtime/facades/orchestrate-facade.test.ts +16 -0
  204. package/src/runtime/facades/orchestrate-facade.ts +146 -0
  205. package/src/runtime/feature-flags.ts +4 -0
  206. package/src/runtime/orchestrate-ops.test.ts +182 -2
  207. package/src/runtime/orchestrate-ops.ts +170 -13
  208. package/src/runtime/planning-extra-ops.ts +77 -0
  209. package/src/runtime/preflight.ts +53 -0
  210. package/src/runtime/quality-signals.test.ts +182 -8
  211. package/src/runtime/quality-signals.ts +44 -5
  212. package/src/runtime/runtime.ts +41 -2
  213. package/src/runtime/types.ts +20 -0
  214. package/src/skills/__tests__/sync-skills.test.ts +132 -0
  215. package/src/skills/step-tracker.ts +162 -0
  216. package/src/skills/sync-skills.ts +54 -9
  217. package/src/subagent/dispatcher.ts +62 -39
  218. package/src/subagent/index.ts +1 -0
  219. package/src/subagent/orphan-reaper.test.ts +135 -0
  220. package/src/subagent/orphan-reaper.ts +130 -7
  221. package/src/subagent/types.ts +10 -0
  222. package/src/subagent/workspace-resolver.ts +3 -1
  223. package/src/vault/vault-entries.ts +112 -0
  224. package/src/vault/vault-manager.ts +1 -0
  225. package/src/vault/vault-scaling.test.ts +3 -2
  226. package/src/vault/vault-schema.ts +15 -0
  227. package/src/vault/vault.ts +1 -0
  228. package/vitest.config.ts +2 -1
  229. package/dist/brain/strength-scorer.d.ts +0 -31
  230. package/dist/brain/strength-scorer.d.ts.map +0 -1
  231. package/dist/brain/strength-scorer.js +0 -264
  232. package/dist/brain/strength-scorer.js.map +0 -1
  233. package/dist/engine/index.d.ts +0 -21
  234. package/dist/engine/index.d.ts.map +0 -1
  235. package/dist/engine/index.js +0 -18
  236. package/dist/engine/index.js.map +0 -1
  237. package/dist/hooks/index.d.ts +0 -2
  238. package/dist/hooks/index.d.ts.map +0 -1
  239. package/dist/hooks/index.js +0 -2
  240. package/dist/hooks/index.js.map +0 -1
  241. package/dist/persona/index.d.ts +0 -5
  242. package/dist/persona/index.d.ts.map +0 -1
  243. package/dist/persona/index.js +0 -4
  244. package/dist/persona/index.js.map +0 -1
  245. package/dist/vault/vault-interfaces.d.ts +0 -153
  246. package/dist/vault/vault-interfaces.d.ts.map +0 -1
  247. package/dist/vault/vault-interfaces.js +0 -2
  248. 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 { analyzeQualitySignals, captureQualitySignals } from './quality-signals.js';
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('does not flag task with fixIterations === 2 (at threshold, not above)', () => {
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
- 'quality-signal:rework:Fix login',
203
- 't1',
204
- 'dismissed',
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
- 'quality-signal:clean:Add feature',
230
- 't2',
231
- 'accepted',
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 more than this many fix iterations are flagged as anti-patterns. */
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 > 2 → anti-pattern (rework)
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 > REWORK_THRESHOLD) {
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(`quality-signal:rework:${ap.taskTitle}`, ap.taskId, 'dismissed');
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(`quality-signal:clean:${ct.taskTitle}`, ct.taskId, 'accepted');
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
+ }
@@ -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
- const brain = new Brain(vault, vaultManager);
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: new FeatureFlags(getAgentFlagsPath(agentId)),
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(),
@@ -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
+ });