@soleri/core 9.4.0 → 9.6.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/adapters/claude-code-adapter.d.ts +27 -0
- package/dist/adapters/claude-code-adapter.d.ts.map +1 -0
- package/dist/adapters/claude-code-adapter.js +111 -0
- package/dist/adapters/claude-code-adapter.js.map +1 -0
- package/dist/adapters/index.d.ts +9 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +10 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/registry.d.ts +21 -0
- package/dist/adapters/registry.d.ts.map +1 -0
- package/dist/adapters/registry.js +44 -0
- package/dist/adapters/registry.js.map +1 -0
- package/dist/adapters/types.d.ts +93 -0
- package/dist/adapters/types.d.ts.map +1 -0
- package/dist/adapters/types.js +10 -0
- package/dist/adapters/types.js.map +1 -0
- package/dist/brain/brain.d.ts +12 -1
- package/dist/brain/brain.d.ts.map +1 -1
- package/dist/brain/brain.js +106 -44
- package/dist/brain/brain.js.map +1 -1
- package/dist/brain/intelligence.d.ts.map +1 -1
- package/dist/brain/intelligence.js +36 -30
- package/dist/brain/intelligence.js.map +1 -1
- package/dist/chat/agent-loop.js +1 -1
- package/dist/chat/agent-loop.js.map +1 -1
- package/dist/chat/notifications.d.ts.map +1 -1
- package/dist/chat/notifications.js +4 -0
- package/dist/chat/notifications.js.map +1 -1
- package/dist/control/intent-router.d.ts +1 -0
- package/dist/control/intent-router.d.ts.map +1 -1
- package/dist/control/intent-router.js +11 -5
- package/dist/control/intent-router.js.map +1 -1
- package/dist/curator/curator.d.ts +4 -0
- package/dist/curator/curator.d.ts.map +1 -1
- package/dist/curator/curator.js +141 -27
- package/dist/curator/curator.js.map +1 -1
- package/dist/hooks/candidate-scorer.d.ts +28 -0
- package/dist/hooks/candidate-scorer.d.ts.map +1 -0
- package/dist/hooks/candidate-scorer.js +20 -0
- package/dist/hooks/candidate-scorer.js.map +1 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +2 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/index.d.ts +14 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -1
- package/dist/index.js.map +1 -1
- package/dist/llm/llm-client.d.ts.map +1 -1
- package/dist/llm/llm-client.js +1 -0
- package/dist/llm/llm-client.js.map +1 -1
- package/dist/packs/index.d.ts +3 -2
- package/dist/packs/index.d.ts.map +1 -1
- package/dist/packs/index.js +3 -2
- package/dist/packs/index.js.map +1 -1
- package/dist/packs/lockfile.d.ts +23 -1
- package/dist/packs/lockfile.d.ts.map +1 -1
- package/dist/packs/lockfile.js +50 -4
- package/dist/packs/lockfile.js.map +1 -1
- package/dist/packs/pack-installer.d.ts +10 -0
- package/dist/packs/pack-installer.d.ts.map +1 -1
- package/dist/packs/pack-installer.js +69 -2
- package/dist/packs/pack-installer.js.map +1 -1
- package/dist/packs/pack-lifecycle.d.ts +50 -0
- package/dist/packs/pack-lifecycle.d.ts.map +1 -0
- package/dist/packs/pack-lifecycle.js +91 -0
- package/dist/packs/pack-lifecycle.js.map +1 -0
- package/dist/packs/types.d.ts +64 -44
- package/dist/packs/types.d.ts.map +1 -1
- package/dist/packs/types.js +9 -0
- package/dist/packs/types.js.map +1 -1
- package/dist/persistence/sqlite-provider.d.ts +5 -1
- package/dist/persistence/sqlite-provider.d.ts.map +1 -1
- package/dist/persistence/sqlite-provider.js +22 -2
- package/dist/persistence/sqlite-provider.js.map +1 -1
- package/dist/planning/github-projection.d.ts +8 -8
- package/dist/planning/github-projection.d.ts.map +1 -1
- package/dist/planning/github-projection.js +42 -42
- package/dist/planning/github-projection.js.map +1 -1
- package/dist/planning/plan-lifecycle.d.ts.map +1 -1
- package/dist/planning/plan-lifecycle.js +6 -1
- package/dist/planning/plan-lifecycle.js.map +1 -1
- package/dist/plugins/types.d.ts +21 -21
- package/dist/queue/pipeline-runner.d.ts.map +1 -1
- package/dist/queue/pipeline-runner.js +4 -0
- package/dist/queue/pipeline-runner.js.map +1 -1
- package/dist/runtime/curator-extra-ops.d.ts.map +1 -1
- package/dist/runtime/curator-extra-ops.js +9 -1
- package/dist/runtime/curator-extra-ops.js.map +1 -1
- package/dist/runtime/facades/memory-facade.d.ts.map +1 -1
- package/dist/runtime/facades/memory-facade.js +169 -0
- package/dist/runtime/facades/memory-facade.js.map +1 -1
- package/dist/runtime/orchestrate-ops.d.ts.map +1 -1
- package/dist/runtime/orchestrate-ops.js +133 -4
- package/dist/runtime/orchestrate-ops.js.map +1 -1
- package/dist/runtime/runtime.d.ts.map +1 -1
- package/dist/runtime/runtime.js +128 -90
- package/dist/runtime/runtime.js.map +1 -1
- package/dist/runtime/session-briefing.d.ts.map +1 -1
- package/dist/runtime/session-briefing.js +44 -11
- package/dist/runtime/session-briefing.js.map +1 -1
- package/dist/runtime/shutdown-registry.d.ts +36 -0
- package/dist/runtime/shutdown-registry.d.ts.map +1 -0
- package/dist/runtime/shutdown-registry.js +74 -0
- package/dist/runtime/shutdown-registry.js.map +1 -0
- package/dist/runtime/types.d.ts +10 -1
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/subagent/concurrency-manager.d.ts +29 -0
- package/dist/subagent/concurrency-manager.d.ts.map +1 -0
- package/dist/subagent/concurrency-manager.js +73 -0
- package/dist/subagent/concurrency-manager.js.map +1 -0
- package/dist/subagent/dispatcher.d.ts +41 -0
- package/dist/subagent/dispatcher.d.ts.map +1 -0
- package/dist/subagent/dispatcher.js +259 -0
- package/dist/subagent/dispatcher.js.map +1 -0
- package/dist/subagent/index.d.ts +14 -0
- package/dist/subagent/index.d.ts.map +1 -0
- package/dist/subagent/index.js +15 -0
- package/dist/subagent/index.js.map +1 -0
- package/dist/subagent/orphan-reaper.d.ts +37 -0
- package/dist/subagent/orphan-reaper.d.ts.map +1 -0
- package/dist/subagent/orphan-reaper.js +71 -0
- package/dist/subagent/orphan-reaper.js.map +1 -0
- package/dist/subagent/result-aggregator.d.ts +7 -0
- package/dist/subagent/result-aggregator.d.ts.map +1 -0
- package/dist/subagent/result-aggregator.js +57 -0
- package/dist/subagent/result-aggregator.js.map +1 -0
- package/dist/subagent/task-checkout.d.ts +36 -0
- package/dist/subagent/task-checkout.d.ts.map +1 -0
- package/dist/subagent/task-checkout.js +52 -0
- package/dist/subagent/task-checkout.js.map +1 -0
- package/dist/subagent/types.d.ts +114 -0
- package/dist/subagent/types.d.ts.map +1 -0
- package/dist/subagent/types.js +9 -0
- package/dist/subagent/types.js.map +1 -0
- package/dist/subagent/workspace-resolver.d.ts +35 -0
- package/dist/subagent/workspace-resolver.d.ts.map +1 -0
- package/dist/subagent/workspace-resolver.js +99 -0
- package/dist/subagent/workspace-resolver.js.map +1 -0
- package/dist/transport/http-server.d.ts.map +1 -1
- package/dist/transport/http-server.js +49 -3
- package/dist/transport/http-server.js.map +1 -1
- package/dist/transport/ws-server.d.ts.map +1 -1
- package/dist/transport/ws-server.js +7 -0
- package/dist/transport/ws-server.js.map +1 -1
- package/dist/vault/linking.d.ts +3 -4
- package/dist/vault/linking.d.ts.map +1 -1
- package/dist/vault/linking.js +79 -32
- package/dist/vault/linking.js.map +1 -1
- package/dist/vault/vault-maintenance.d.ts.map +1 -1
- package/dist/vault/vault-maintenance.js +7 -14
- package/dist/vault/vault-maintenance.js.map +1 -1
- package/dist/vault/vault-memories.d.ts.map +1 -1
- package/dist/vault/vault-memories.js +19 -9
- package/dist/vault/vault-memories.js.map +1 -1
- package/dist/vault/vault-schema.d.ts +1 -0
- package/dist/vault/vault-schema.d.ts.map +1 -1
- package/dist/vault/vault-schema.js +20 -0
- package/dist/vault/vault-schema.js.map +1 -1
- package/dist/vault/vault.d.ts.map +1 -1
- package/dist/vault/vault.js +7 -3
- package/dist/vault/vault.js.map +1 -1
- package/package.json +8 -2
- package/src/__tests__/adapters/claude-code-adapter.test.ts +167 -0
- package/src/__tests__/adapters/registry.test.ts +100 -0
- package/src/__tests__/packs/pack-lifecycle.test.ts +379 -0
- package/src/__tests__/subagent/concurrency-manager.test.ts +132 -0
- package/src/__tests__/subagent/dispatcher.test.ts +195 -0
- package/src/__tests__/subagent/orphan-reaper.test.ts +141 -0
- package/src/__tests__/subagent/result-aggregator.test.ts +141 -0
- package/src/__tests__/subagent/task-checkout.test.ts +86 -0
- package/src/__tests__/subagent/workspace-resolver.test.ts +138 -0
- package/src/adapters/claude-code-adapter.ts +163 -0
- package/src/adapters/index.ts +22 -0
- package/src/adapters/registry.ts +53 -0
- package/src/adapters/types.ts +114 -0
- package/src/brain/brain.ts +120 -46
- package/src/brain/intelligence.ts +42 -34
- package/src/chat/agent-loop.ts +1 -1
- package/src/chat/notifications.ts +4 -0
- package/src/control/intent-router.ts +10 -8
- package/src/curator/curator.ts +146 -29
- package/src/hooks/candidate-scorer.test.ts +76 -0
- package/src/hooks/candidate-scorer.ts +39 -0
- package/src/index.ts +40 -1
- package/src/llm/llm-client.ts +1 -0
- package/src/packs/index.ts +5 -1
- package/src/packs/lockfile.ts +70 -5
- package/src/packs/pack-installer.ts +78 -2
- package/src/packs/pack-lifecycle.ts +115 -0
- package/src/packs/pack-lockfile.test.ts +1 -1
- package/src/packs/pack-system.test.ts +1 -1
- package/src/packs/types.ts +40 -2
- package/src/persistence/sqlite-provider.ts +27 -2
- package/src/planning/github-projection.ts +48 -44
- package/src/planning/plan-lifecycle.ts +14 -1
- package/src/queue/pipeline-runner.ts +4 -0
- package/src/runtime/admin-setup-ops.test.ts +9 -4
- package/src/runtime/curator-extra-ops.test.ts +7 -0
- package/src/runtime/curator-extra-ops.ts +10 -1
- package/src/runtime/facades/curator-facade.test.ts +7 -0
- package/src/runtime/facades/memory-facade.ts +187 -0
- package/src/runtime/orchestrate-ops.ts +156 -4
- package/src/runtime/runtime.test.ts +50 -2
- package/src/runtime/runtime.ts +132 -89
- package/src/runtime/session-briefing.test.ts +94 -2
- package/src/runtime/session-briefing.ts +48 -12
- package/src/runtime/shutdown-registry.test.ts +151 -0
- package/src/runtime/shutdown-registry.ts +85 -0
- package/src/runtime/types.ts +10 -1
- package/src/subagent/concurrency-manager.ts +89 -0
- package/src/subagent/dispatcher.ts +326 -0
- package/src/subagent/index.ts +28 -0
- package/src/subagent/orphan-reaper.ts +82 -0
- package/src/subagent/result-aggregator.ts +66 -0
- package/src/subagent/task-checkout.ts +60 -0
- package/src/subagent/types.ts +138 -0
- package/src/subagent/workspace-resolver.ts +117 -0
- package/src/transport/http-server.ts +50 -3
- package/src/transport/ws-server.ts +8 -0
- package/src/vault/linking.test.ts +12 -0
- package/src/vault/linking.ts +90 -44
- package/src/vault/vault-maintenance.ts +11 -18
- package/src/vault/vault-memories.ts +21 -13
- package/src/vault/vault-scaling.test.ts +3 -2
- package/src/vault/vault-schema.ts +21 -0
- package/src/vault/vault.ts +8 -3
- package/vitest.config.ts +2 -0
package/src/curator/curator.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type {
|
|
|
5
5
|
CuratorStatus,
|
|
6
6
|
TagNormalizationResult,
|
|
7
7
|
CanonicalTag,
|
|
8
|
+
DuplicateCandidate,
|
|
8
9
|
DuplicateDetectionResult,
|
|
9
10
|
Contradiction,
|
|
10
11
|
ContradictionStatus,
|
|
@@ -17,14 +18,17 @@ import type {
|
|
|
17
18
|
} from './types.js';
|
|
18
19
|
|
|
19
20
|
import {
|
|
20
|
-
detectDuplicates as detectDuplicatesPure,
|
|
21
21
|
DEFAULT_DUPLICATE_THRESHOLD,
|
|
22
|
+
MERGE_SUGGESTION_THRESHOLD,
|
|
23
|
+
buildVocabulary,
|
|
24
|
+
entryToText,
|
|
22
25
|
} from './duplicate-detector.js';
|
|
23
26
|
import {
|
|
24
27
|
findContradictions,
|
|
25
28
|
DEFAULT_CONTRADICTION_THRESHOLD,
|
|
26
29
|
type ContradictionCandidate,
|
|
27
30
|
} from './contradiction-detector.js';
|
|
31
|
+
import { tokenize, calculateTfIdf, cosineSimilarity } from '../text/similarity.js';
|
|
28
32
|
import {
|
|
29
33
|
normalizeTag as normalizeTagPure,
|
|
30
34
|
normalizeAndDedup,
|
|
@@ -40,6 +44,7 @@ import { enrichEntryMetadata } from './metadata-enricher.js';
|
|
|
40
44
|
// ─── Constants ──────────────────────────────────────────────────────
|
|
41
45
|
|
|
42
46
|
const DEFAULT_STALE_DAYS = 90;
|
|
47
|
+
const DEFAULT_BATCH_SIZE = 100;
|
|
43
48
|
|
|
44
49
|
// ─── Curator Class ──────────────────────────────────────────────────
|
|
45
50
|
|
|
@@ -151,19 +156,101 @@ export class Curator {
|
|
|
151
156
|
// ─── Duplicates (delegates to duplicate-detector) ─────────────
|
|
152
157
|
|
|
153
158
|
detectDuplicates(entryId?: string, threshold?: number): DuplicateDetectionResult[] {
|
|
154
|
-
const
|
|
155
|
-
// Filter out dismissed pairs
|
|
159
|
+
const effectiveThreshold = threshold ?? DEFAULT_DUPLICATE_THRESHOLD;
|
|
156
160
|
const dismissed = this.getDismissedPairs();
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
161
|
+
|
|
162
|
+
// --- Phase 1: Content-hash exact duplicates (O(n) via GROUP BY) ---
|
|
163
|
+
const exactDupes = this.provider.all<{ content_hash: string; ids: string }>(
|
|
164
|
+
`SELECT content_hash, GROUP_CONCAT(id) as ids FROM entries
|
|
165
|
+
WHERE content_hash IS NOT NULL
|
|
166
|
+
GROUP BY content_hash HAVING COUNT(*) > 1`,
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
const results: DuplicateDetectionResult[] = [];
|
|
170
|
+
const seenPairs = new Set<string>();
|
|
171
|
+
|
|
172
|
+
for (const { ids } of exactDupes) {
|
|
173
|
+
const idList = ids.split(',');
|
|
174
|
+
if (entryId && !idList.includes(entryId)) continue;
|
|
175
|
+
const targets = entryId ? [entryId] : idList;
|
|
176
|
+
for (const targetId of targets) {
|
|
177
|
+
const matches: DuplicateCandidate[] = [];
|
|
178
|
+
for (const otherId of idList) {
|
|
179
|
+
if (otherId === targetId) continue;
|
|
180
|
+
const pairKey = [targetId, otherId].sort().join('::');
|
|
181
|
+
if (dismissed.has(pairKey) || seenPairs.has(pairKey)) continue;
|
|
182
|
+
seenPairs.add(pairKey);
|
|
183
|
+
const other = this.vault.get(otherId);
|
|
184
|
+
if (!other) continue;
|
|
185
|
+
matches.push({
|
|
186
|
+
entryId: otherId,
|
|
187
|
+
title: other.title,
|
|
188
|
+
similarity: 1.0,
|
|
189
|
+
suggestMerge: true,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
if (matches.length > 0) {
|
|
193
|
+
results.push({ entryId: targetId, matches, scannedCount: idList.length - 1 });
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// --- Phase 2: FTS5 fuzzy candidate matching ---
|
|
199
|
+
// For each entry, use FTS5 MATCH to find top candidates, then TF-IDF cosine similarity.
|
|
200
|
+
// Complexity is O(n * k) where k = FTS5 candidate limit (10), not O(n^2).
|
|
201
|
+
const exactDupeEntryIds = new Set(results.map((r) => r.entryId));
|
|
202
|
+
const targetEntries = entryId
|
|
203
|
+
? ([this.vault.get(entryId)].filter(Boolean) as IntelligenceEntry[])
|
|
204
|
+
: this.listBatched();
|
|
205
|
+
|
|
206
|
+
// Build vocabulary from all entries (batched)
|
|
207
|
+
const allEntries = entryId ? this.listBatched() : targetEntries;
|
|
208
|
+
const vocabulary = buildVocabulary(allEntries);
|
|
209
|
+
|
|
210
|
+
for (const entry of targetEntries) {
|
|
211
|
+
// Skip entries already fully handled by exact-hash matches
|
|
212
|
+
if (exactDupeEntryIds.has(entry.id)) continue;
|
|
213
|
+
|
|
214
|
+
// FTS5 candidate retrieval: find top-10 similar entries
|
|
215
|
+
let candidates: IntelligenceEntry[];
|
|
216
|
+
try {
|
|
217
|
+
candidates = this.vault
|
|
218
|
+
.search(entry.title, { domain: entry.domain, limit: 10 })
|
|
219
|
+
.map((r) => r.entry)
|
|
220
|
+
.filter((c) => c.id !== entry.id);
|
|
221
|
+
} catch {
|
|
222
|
+
candidates = [];
|
|
223
|
+
}
|
|
224
|
+
if (candidates.length === 0) continue;
|
|
225
|
+
|
|
226
|
+
const entryVec = calculateTfIdf(tokenize(entryToText(entry)), vocabulary);
|
|
227
|
+
const matches: DuplicateCandidate[] = [];
|
|
228
|
+
|
|
229
|
+
for (const candidate of candidates) {
|
|
230
|
+
// Skip cross-domain pairs
|
|
231
|
+
if (entry.domain !== candidate.domain) continue;
|
|
232
|
+
const pairKey = [entry.id, candidate.id].sort().join('::');
|
|
233
|
+
if (dismissed.has(pairKey)) continue;
|
|
234
|
+
|
|
235
|
+
const candidateVec = calculateTfIdf(tokenize(entryToText(candidate)), vocabulary);
|
|
236
|
+
const similarity = cosineSimilarity(entryVec, candidateVec);
|
|
237
|
+
if (similarity >= effectiveThreshold) {
|
|
238
|
+
matches.push({
|
|
239
|
+
entryId: candidate.id,
|
|
240
|
+
title: candidate.title,
|
|
241
|
+
similarity,
|
|
242
|
+
suggestMerge: similarity >= MERGE_SUGGESTION_THRESHOLD,
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (matches.length > 0) {
|
|
248
|
+
matches.sort((a, b) => b.similarity - a.similarity);
|
|
249
|
+
results.push({ entryId: entry.id, matches, scannedCount: candidates.length });
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return results;
|
|
167
254
|
}
|
|
168
255
|
|
|
169
256
|
dismissDuplicate(entryIdA: string, entryIdB: string, reason?: string): { dismissed: boolean } {
|
|
@@ -187,9 +274,11 @@ export class Curator {
|
|
|
187
274
|
detectContradictions(threshold?: number): Contradiction[] {
|
|
188
275
|
const searchFn = (title: string) =>
|
|
189
276
|
this.vault.search(title, { type: 'pattern', limit: 20 }).map((r) => r.entry);
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
);
|
|
277
|
+
// Load only anti-patterns and patterns (bounded by type), not the entire vault
|
|
278
|
+
const antipatterns = this.vault.list({ type: 'anti-pattern', limit: 10000 });
|
|
279
|
+
const patterns = this.vault.list({ type: 'pattern', limit: 10000 });
|
|
280
|
+
const entries = [...antipatterns, ...patterns];
|
|
281
|
+
return this.persistContradictions(findContradictions(entries, threshold, searchFn));
|
|
193
282
|
}
|
|
194
283
|
|
|
195
284
|
getContradictions(status?: ContradictionStatus): Contradiction[] {
|
|
@@ -218,10 +307,12 @@ export class Curator {
|
|
|
218
307
|
): Promise<{ contradictions: Contradiction[]; method: 'tfidf-only' }> {
|
|
219
308
|
const searchFn = (title: string) =>
|
|
220
309
|
this.vault.search(title, { type: 'pattern', limit: 20 }).map((r) => r.entry);
|
|
310
|
+
// Load only anti-patterns and patterns (bounded by type), not the entire vault
|
|
311
|
+
const antipatterns = this.vault.list({ type: 'anti-pattern', limit: 10000 });
|
|
312
|
+
const patterns = this.vault.list({ type: 'pattern', limit: 10000 });
|
|
313
|
+
const entries = [...antipatterns, ...patterns];
|
|
221
314
|
return {
|
|
222
|
-
contradictions: this.persistContradictions(
|
|
223
|
-
findContradictions(this.vault.list({ limit: 100000 }), threshold, searchFn),
|
|
224
|
-
),
|
|
315
|
+
contradictions: this.persistContradictions(findContradictions(entries, threshold, searchFn)),
|
|
225
316
|
method: 'tfidf-only',
|
|
226
317
|
};
|
|
227
318
|
}
|
|
@@ -249,19 +340,28 @@ export class Curator {
|
|
|
249
340
|
|
|
250
341
|
groomAll(): GroomAllResult {
|
|
251
342
|
const start = Date.now();
|
|
252
|
-
const entries = this.vault.list({ limit: 100000 });
|
|
253
343
|
let tagsNormalized = 0,
|
|
254
|
-
staleCount = 0
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
344
|
+
staleCount = 0,
|
|
345
|
+
totalEntries = 0;
|
|
346
|
+
// Batch pagination — process entries in batches instead of loading all at once
|
|
347
|
+
let offset = 0;
|
|
348
|
+
while (true) {
|
|
349
|
+
const batch = this.vault.list({ limit: DEFAULT_BATCH_SIZE, offset });
|
|
350
|
+
if (batch.length === 0) break;
|
|
351
|
+
totalEntries += batch.length;
|
|
352
|
+
for (const entry of batch) {
|
|
353
|
+
const result = this.groomEntry(entry.id);
|
|
354
|
+
if (result) {
|
|
355
|
+
tagsNormalized += result.tagsNormalized.filter((t) => t.wasAliased).length;
|
|
356
|
+
if (result.stale) staleCount++;
|
|
357
|
+
}
|
|
260
358
|
}
|
|
359
|
+
if (batch.length < DEFAULT_BATCH_SIZE) break;
|
|
360
|
+
offset += DEFAULT_BATCH_SIZE;
|
|
261
361
|
}
|
|
262
362
|
return {
|
|
263
|
-
totalEntries
|
|
264
|
-
groomedCount:
|
|
363
|
+
totalEntries,
|
|
364
|
+
groomedCount: totalEntries,
|
|
265
365
|
tagsNormalized,
|
|
266
366
|
staleCount,
|
|
267
367
|
durationMs: Date.now() - start,
|
|
@@ -343,7 +443,8 @@ export class Curator {
|
|
|
343
443
|
// ─── Health Audit (delegates to health-audit) ─────────────────
|
|
344
444
|
|
|
345
445
|
healthAudit(): HealthAuditResult {
|
|
346
|
-
|
|
446
|
+
// Load entries in batches instead of all at once
|
|
447
|
+
const entries = this.listBatched();
|
|
347
448
|
const dataProvider: HealthDataProvider = {
|
|
348
449
|
getStaleCount: (threshold) =>
|
|
349
450
|
(
|
|
@@ -480,6 +581,22 @@ export class Curator {
|
|
|
480
581
|
|
|
481
582
|
// ─── Private Helpers ──────────────────────────────────────────
|
|
482
583
|
|
|
584
|
+
/**
|
|
585
|
+
* Load all vault entries using batched pagination instead of a single 100k query.
|
|
586
|
+
*/
|
|
587
|
+
private listBatched(batchSize: number = DEFAULT_BATCH_SIZE): IntelligenceEntry[] {
|
|
588
|
+
const all: IntelligenceEntry[] = [];
|
|
589
|
+
let offset = 0;
|
|
590
|
+
while (true) {
|
|
591
|
+
const batch = this.vault.list({ limit: batchSize, offset });
|
|
592
|
+
if (batch.length === 0) break;
|
|
593
|
+
all.push(...batch);
|
|
594
|
+
if (batch.length < batchSize) break;
|
|
595
|
+
offset += batchSize;
|
|
596
|
+
}
|
|
597
|
+
return all;
|
|
598
|
+
}
|
|
599
|
+
|
|
483
600
|
private persistContradictions(candidates: ContradictionCandidate[]): Contradiction[] {
|
|
484
601
|
const detected: Contradiction[] = [];
|
|
485
602
|
for (const c of candidates) {
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { scoreCandidateForConversion } from './candidate-scorer.js';
|
|
3
|
+
import type { CandidateDimensions } from './candidate-scorer.js';
|
|
4
|
+
|
|
5
|
+
describe('scoreCandidateForConversion', () => {
|
|
6
|
+
it('should identify strong candidate (4/4 HIGH)', () => {
|
|
7
|
+
const dimensions: CandidateDimensions = {
|
|
8
|
+
frequency: 'HIGH',
|
|
9
|
+
eventCorrelation: 'HIGH',
|
|
10
|
+
determinism: 'HIGH',
|
|
11
|
+
autonomy: 'HIGH',
|
|
12
|
+
};
|
|
13
|
+
const result = scoreCandidateForConversion(dimensions);
|
|
14
|
+
expect(result.highCount).toBe(4);
|
|
15
|
+
expect(result.candidate).toBe(true);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should identify borderline candidate (3/4 HIGH)', () => {
|
|
19
|
+
const dimensions: CandidateDimensions = {
|
|
20
|
+
frequency: 'HIGH',
|
|
21
|
+
eventCorrelation: 'HIGH',
|
|
22
|
+
determinism: 'HIGH',
|
|
23
|
+
autonomy: 'LOW',
|
|
24
|
+
};
|
|
25
|
+
const result = scoreCandidateForConversion(dimensions);
|
|
26
|
+
expect(result.highCount).toBe(3);
|
|
27
|
+
expect(result.candidate).toBe(true);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should reject non-candidate (2/4 HIGH)', () => {
|
|
31
|
+
const dimensions: CandidateDimensions = {
|
|
32
|
+
frequency: 'HIGH',
|
|
33
|
+
eventCorrelation: 'LOW',
|
|
34
|
+
determinism: 'HIGH',
|
|
35
|
+
autonomy: 'LOW',
|
|
36
|
+
};
|
|
37
|
+
const result = scoreCandidateForConversion(dimensions);
|
|
38
|
+
expect(result.highCount).toBe(2);
|
|
39
|
+
expect(result.candidate).toBe(false);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should reject weak candidate (1/4 HIGH)', () => {
|
|
43
|
+
const dimensions: CandidateDimensions = {
|
|
44
|
+
frequency: 'LOW',
|
|
45
|
+
eventCorrelation: 'LOW',
|
|
46
|
+
determinism: 'LOW',
|
|
47
|
+
autonomy: 'HIGH',
|
|
48
|
+
};
|
|
49
|
+
const result = scoreCandidateForConversion(dimensions);
|
|
50
|
+
expect(result.highCount).toBe(1);
|
|
51
|
+
expect(result.candidate).toBe(false);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should reject zero score (0/4 HIGH)', () => {
|
|
55
|
+
const dimensions: CandidateDimensions = {
|
|
56
|
+
frequency: 'LOW',
|
|
57
|
+
eventCorrelation: 'LOW',
|
|
58
|
+
determinism: 'LOW',
|
|
59
|
+
autonomy: 'LOW',
|
|
60
|
+
};
|
|
61
|
+
const result = scoreCandidateForConversion(dimensions);
|
|
62
|
+
expect(result.highCount).toBe(0);
|
|
63
|
+
expect(result.candidate).toBe(false);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should preserve dimension values in result', () => {
|
|
67
|
+
const dimensions: CandidateDimensions = {
|
|
68
|
+
frequency: 'HIGH',
|
|
69
|
+
eventCorrelation: 'LOW',
|
|
70
|
+
determinism: 'HIGH',
|
|
71
|
+
autonomy: 'HIGH',
|
|
72
|
+
};
|
|
73
|
+
const result = scoreCandidateForConversion(dimensions);
|
|
74
|
+
expect(result.dimensions).toEqual(dimensions);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Candidate scoring rubric for skill-to-hook conversion.
|
|
3
|
+
* 4-dimension scoring: frequency, eventCorrelation, determinism, autonomy.
|
|
4
|
+
* Threshold: >= 3 HIGH dimensions = candidate for conversion.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export type DimensionLevel = 'HIGH' | 'LOW';
|
|
8
|
+
|
|
9
|
+
export interface CandidateDimensions {
|
|
10
|
+
/** 3+ manual calls per session for same event type → HIGH */
|
|
11
|
+
frequency: DimensionLevel;
|
|
12
|
+
/** Skill consistently triggers on a recognizable hook event → HIGH */
|
|
13
|
+
eventCorrelation: DimensionLevel;
|
|
14
|
+
/** Skill produces consistent, non-exploratory guidance → HIGH */
|
|
15
|
+
determinism: DimensionLevel;
|
|
16
|
+
/** Skill requires no interactive user decisions mid-execution → HIGH */
|
|
17
|
+
autonomy: DimensionLevel;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface CandidateScore {
|
|
21
|
+
dimensions: CandidateDimensions;
|
|
22
|
+
highCount: number;
|
|
23
|
+
candidate: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Score a skill for hook conversion candidacy.
|
|
28
|
+
* @param dimensions - The 4 scored dimensions (each HIGH or LOW)
|
|
29
|
+
* @returns Score with candidate boolean (true if >= 3 HIGH)
|
|
30
|
+
*/
|
|
31
|
+
export function scoreCandidateForConversion(dimensions: CandidateDimensions): CandidateScore {
|
|
32
|
+
const values = Object.values(dimensions);
|
|
33
|
+
const highCount = values.filter((v) => v === 'HIGH').length;
|
|
34
|
+
return {
|
|
35
|
+
dimensions,
|
|
36
|
+
highCount,
|
|
37
|
+
candidate: highCount >= 3,
|
|
38
|
+
};
|
|
39
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,3 +1,34 @@
|
|
|
1
|
+
// ─── Adapters ────────────────────────────────────────────────────────
|
|
2
|
+
export { RuntimeAdapterRegistry } from './adapters/registry.js';
|
|
3
|
+
export { ClaudeCodeRuntimeAdapter } from './adapters/claude-code-adapter.js';
|
|
4
|
+
export type {
|
|
5
|
+
RuntimeAdapter,
|
|
6
|
+
AdapterExecutionContext,
|
|
7
|
+
AdapterExecutionResult,
|
|
8
|
+
AdapterTokenUsage,
|
|
9
|
+
AdapterSessionState,
|
|
10
|
+
AdapterSessionCodec,
|
|
11
|
+
AdapterEnvironmentTestResult,
|
|
12
|
+
} from './adapters/types.js';
|
|
13
|
+
|
|
14
|
+
// ─── Subagent ────────────────────────────────────────────────────────
|
|
15
|
+
export { SubagentDispatcher } from './subagent/dispatcher.js';
|
|
16
|
+
export { TaskCheckout } from './subagent/task-checkout.js';
|
|
17
|
+
export { WorkspaceResolver } from './subagent/workspace-resolver.js';
|
|
18
|
+
export { ConcurrencyManager } from './subagent/concurrency-manager.js';
|
|
19
|
+
export { OrphanReaper } from './subagent/orphan-reaper.js';
|
|
20
|
+
export { aggregate as aggregateResults } from './subagent/result-aggregator.js';
|
|
21
|
+
export type {
|
|
22
|
+
SubagentTask,
|
|
23
|
+
SubagentStatus,
|
|
24
|
+
SubagentResult,
|
|
25
|
+
DispatchOptions,
|
|
26
|
+
AggregatedResult,
|
|
27
|
+
ClaimInfo,
|
|
28
|
+
WorktreeInfo,
|
|
29
|
+
TrackedProcess,
|
|
30
|
+
} from './subagent/types.js';
|
|
31
|
+
|
|
1
32
|
// ─── Paths ──────────────────────────────────────────────────────────
|
|
2
33
|
export {
|
|
3
34
|
SOLERI_HOME,
|
|
@@ -551,6 +582,8 @@ export { createSemanticFacades } from './runtime/facades/index.js';
|
|
|
551
582
|
export { createDomainFacade, createDomainFacades } from './runtime/domain-ops.js';
|
|
552
583
|
export { FeatureFlags } from './runtime/feature-flags.js';
|
|
553
584
|
export type { FlagDefinition } from './runtime/feature-flags.js';
|
|
585
|
+
export { ShutdownRegistry } from './runtime/shutdown-registry.js';
|
|
586
|
+
export type { ShutdownCallback } from './runtime/shutdown-registry.js';
|
|
554
587
|
export type { AgentRuntimeConfig, AgentRuntime } from './runtime/types.js';
|
|
555
588
|
export {
|
|
556
589
|
deprecationWarning,
|
|
@@ -615,7 +648,13 @@ export { computeContentHash } from './vault/content-hash.js';
|
|
|
615
648
|
export type { HashableEntry } from './vault/content-hash.js';
|
|
616
649
|
|
|
617
650
|
// ─── Knowledge Packs ────────────────────────────────────────────────────
|
|
618
|
-
export {
|
|
651
|
+
export {
|
|
652
|
+
PackInstaller,
|
|
653
|
+
PackLifecycleManager,
|
|
654
|
+
packManifestSchema,
|
|
655
|
+
VALID_TRANSITIONS,
|
|
656
|
+
} from './packs/index.js';
|
|
657
|
+
export type { PackState, PackTransition } from './packs/index.js';
|
|
619
658
|
export { PackLockfile, inferPackType } from './packs/index.js';
|
|
620
659
|
export { resolvePack, checkNpmVersion, checkVersionCompat } from './packs/index.js';
|
|
621
660
|
export type {
|
package/src/llm/llm-client.ts
CHANGED
package/src/packs/index.ts
CHANGED
|
@@ -5,17 +5,21 @@
|
|
|
5
5
|
export {
|
|
6
6
|
packManifestSchema,
|
|
7
7
|
PACK_TIERS,
|
|
8
|
+
VALID_TRANSITIONS,
|
|
8
9
|
type PackManifest,
|
|
9
10
|
type PackTier as ManifestPackTier,
|
|
10
11
|
type PackStatus,
|
|
12
|
+
type PackState,
|
|
13
|
+
type PackTransition,
|
|
11
14
|
type InstalledPack,
|
|
12
15
|
type InstallResult,
|
|
13
16
|
type ValidateResult,
|
|
14
17
|
} from './types.js';
|
|
15
18
|
|
|
16
19
|
export { PackInstaller } from './pack-installer.js';
|
|
20
|
+
export { PackLifecycleManager } from './pack-lifecycle.js';
|
|
17
21
|
|
|
18
|
-
export { PackLockfile, inferPackType } from './lockfile.js';
|
|
22
|
+
export { PackLockfile, inferPackType, LOCKFILE_VERSION } from './lockfile.js';
|
|
19
23
|
export type { LockEntry, PackType, PackSource, PackTier, LockfileData } from './lockfile.js';
|
|
20
24
|
|
|
21
25
|
export { resolvePack, checkNpmVersion, checkVersionCompat } from './resolver.js';
|
package/src/packs/lockfile.ts
CHANGED
|
@@ -41,15 +41,25 @@ export interface LockEntry {
|
|
|
41
41
|
soleriRange?: string;
|
|
42
42
|
/** Pack tier: default (ships with engine), community (free), premium (unlocked today) */
|
|
43
43
|
tier?: PackTier;
|
|
44
|
+
/** Current pack lifecycle state */
|
|
45
|
+
state?: string;
|
|
46
|
+
/** Most recent lifecycle transition */
|
|
47
|
+
lastTransition?: { from: string; to: string; timestamp: string; reason?: string };
|
|
48
|
+
/** ISO timestamp when the pack was disabled */
|
|
49
|
+
disabledAt?: string;
|
|
50
|
+
/** Error details if the pack is in an error state */
|
|
51
|
+
errorMessage?: string;
|
|
44
52
|
}
|
|
45
53
|
|
|
46
54
|
export type PackType = 'hooks' | 'skills' | 'knowledge' | 'domain' | 'bundle';
|
|
47
55
|
export type PackSource = 'built-in' | 'local' | 'npm';
|
|
48
56
|
export type PackTier = 'default' | 'community' | 'premium';
|
|
49
57
|
|
|
58
|
+
export const LOCKFILE_VERSION = 2;
|
|
59
|
+
|
|
50
60
|
export interface LockfileData {
|
|
51
61
|
/** Lockfile format version */
|
|
52
|
-
version:
|
|
62
|
+
version: number;
|
|
53
63
|
/** Map of packId → lock entry */
|
|
54
64
|
packs: Record<string, LockEntry>;
|
|
55
65
|
}
|
|
@@ -141,18 +151,73 @@ export class PackLockfile {
|
|
|
141
151
|
return 'sha256-' + createHash('sha256').update(content).digest('hex');
|
|
142
152
|
}
|
|
143
153
|
|
|
154
|
+
/**
|
|
155
|
+
* Update lifecycle fields for a pack entry and save.
|
|
156
|
+
*/
|
|
157
|
+
updateLifecycle(
|
|
158
|
+
packId: string,
|
|
159
|
+
state: string,
|
|
160
|
+
transition?: { from: string; to: string; reason?: string },
|
|
161
|
+
): void {
|
|
162
|
+
const entry = this.data.packs[packId];
|
|
163
|
+
if (!entry) return;
|
|
164
|
+
|
|
165
|
+
entry.state = state;
|
|
166
|
+
|
|
167
|
+
if (transition) {
|
|
168
|
+
entry.lastTransition = {
|
|
169
|
+
from: transition.from,
|
|
170
|
+
to: transition.to,
|
|
171
|
+
timestamp: new Date().toISOString(),
|
|
172
|
+
reason: transition.reason,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (state === 'disabled') {
|
|
177
|
+
entry.disabledAt = new Date().toISOString();
|
|
178
|
+
} else {
|
|
179
|
+
delete entry.disabledAt;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (state === 'error') {
|
|
183
|
+
// errorMessage is set externally if needed; keep existing value
|
|
184
|
+
} else {
|
|
185
|
+
delete entry.errorMessage;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
this.dirty = true;
|
|
189
|
+
this.save();
|
|
190
|
+
}
|
|
191
|
+
|
|
144
192
|
private load(): LockfileData {
|
|
145
193
|
if (!existsSync(this.filePath)) {
|
|
146
|
-
return { version:
|
|
194
|
+
return { version: LOCKFILE_VERSION, packs: {} };
|
|
147
195
|
}
|
|
148
196
|
try {
|
|
149
197
|
const raw = JSON.parse(readFileSync(this.filePath, 'utf-8'));
|
|
150
|
-
if (
|
|
198
|
+
if (typeof raw.packs !== 'object') {
|
|
199
|
+
return { version: LOCKFILE_VERSION, packs: {} };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Migrate v1 → v2: add lifecycle fields
|
|
203
|
+
if (raw.version === 1) {
|
|
204
|
+
for (const entry of Object.values(raw.packs) as LockEntry[]) {
|
|
205
|
+
if (!entry.state) {
|
|
206
|
+
entry.state = 'ready';
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
raw.version = LOCKFILE_VERSION;
|
|
210
|
+
// Mark dirty so the migrated data gets persisted on next save
|
|
211
|
+
this.dirty = true;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (raw.version === LOCKFILE_VERSION) {
|
|
151
215
|
return raw as LockfileData;
|
|
152
216
|
}
|
|
153
|
-
|
|
217
|
+
|
|
218
|
+
return { version: LOCKFILE_VERSION, packs: {} };
|
|
154
219
|
} catch {
|
|
155
|
-
return { version:
|
|
220
|
+
return { version: LOCKFILE_VERSION, packs: {} };
|
|
156
221
|
}
|
|
157
222
|
}
|
|
158
223
|
}
|