@soleri/core 2.1.0 → 2.5.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/brain/brain.d.ts +10 -1
- package/dist/brain/brain.d.ts.map +1 -1
- package/dist/brain/brain.js +116 -13
- package/dist/brain/brain.js.map +1 -1
- package/dist/brain/intelligence.d.ts +36 -1
- package/dist/brain/intelligence.d.ts.map +1 -1
- package/dist/brain/intelligence.js +119 -14
- package/dist/brain/intelligence.js.map +1 -1
- package/dist/brain/types.d.ts +34 -2
- package/dist/brain/types.d.ts.map +1 -1
- package/dist/cognee/client.d.ts +3 -0
- package/dist/cognee/client.d.ts.map +1 -1
- package/dist/cognee/client.js +17 -0
- package/dist/cognee/client.js.map +1 -1
- package/dist/cognee/sync-manager.d.ts +94 -0
- package/dist/cognee/sync-manager.d.ts.map +1 -0
- package/dist/cognee/sync-manager.js +293 -0
- package/dist/cognee/sync-manager.js.map +1 -0
- package/dist/control/identity-manager.d.ts +22 -0
- package/dist/control/identity-manager.d.ts.map +1 -0
- package/dist/control/identity-manager.js +233 -0
- package/dist/control/identity-manager.js.map +1 -0
- package/dist/control/intent-router.d.ts +32 -0
- package/dist/control/intent-router.d.ts.map +1 -0
- package/dist/control/intent-router.js +242 -0
- package/dist/control/intent-router.js.map +1 -0
- package/dist/control/types.d.ts +68 -0
- package/dist/control/types.d.ts.map +1 -0
- package/dist/control/types.js +9 -0
- package/dist/control/types.js.map +1 -0
- package/dist/curator/curator.d.ts +37 -1
- package/dist/curator/curator.d.ts.map +1 -1
- package/dist/curator/curator.js +199 -1
- package/dist/curator/curator.js.map +1 -1
- package/dist/errors/classify.d.ts +13 -0
- package/dist/errors/classify.d.ts.map +1 -0
- package/dist/errors/classify.js +97 -0
- package/dist/errors/classify.js.map +1 -0
- package/dist/errors/index.d.ts +6 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +4 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/errors/retry.d.ts +40 -0
- package/dist/errors/retry.d.ts.map +1 -0
- package/dist/errors/retry.js +97 -0
- package/dist/errors/retry.js.map +1 -0
- package/dist/errors/types.d.ts +48 -0
- package/dist/errors/types.d.ts.map +1 -0
- package/dist/errors/types.js +59 -0
- package/dist/errors/types.js.map +1 -0
- package/dist/facades/types.d.ts +1 -1
- package/dist/governance/governance.d.ts +42 -0
- package/dist/governance/governance.d.ts.map +1 -0
- package/dist/governance/governance.js +488 -0
- package/dist/governance/governance.js.map +1 -0
- package/dist/governance/index.d.ts +3 -0
- package/dist/governance/index.d.ts.map +1 -0
- package/dist/governance/index.js +2 -0
- package/dist/governance/index.js.map +1 -0
- package/dist/governance/types.d.ts +102 -0
- package/dist/governance/types.d.ts.map +1 -0
- package/dist/governance/types.js +3 -0
- package/dist/governance/types.js.map +1 -0
- package/dist/index.d.ts +52 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +47 -1
- package/dist/index.js.map +1 -1
- package/dist/intake/content-classifier.d.ts +14 -0
- package/dist/intake/content-classifier.d.ts.map +1 -0
- package/dist/intake/content-classifier.js +125 -0
- package/dist/intake/content-classifier.js.map +1 -0
- package/dist/intake/dedup-gate.d.ts +17 -0
- package/dist/intake/dedup-gate.d.ts.map +1 -0
- package/dist/intake/dedup-gate.js +66 -0
- package/dist/intake/dedup-gate.js.map +1 -0
- package/dist/intake/intake-pipeline.d.ts +63 -0
- package/dist/intake/intake-pipeline.d.ts.map +1 -0
- package/dist/intake/intake-pipeline.js +373 -0
- package/dist/intake/intake-pipeline.js.map +1 -0
- package/dist/intake/types.d.ts +65 -0
- package/dist/intake/types.d.ts.map +1 -0
- package/dist/intake/types.js +3 -0
- package/dist/intake/types.js.map +1 -0
- package/dist/intelligence/loader.js +1 -1
- package/dist/intelligence/loader.js.map +1 -1
- package/dist/intelligence/types.d.ts +3 -1
- package/dist/intelligence/types.d.ts.map +1 -1
- package/dist/logging/logger.d.ts +37 -0
- package/dist/logging/logger.d.ts.map +1 -0
- package/dist/logging/logger.js +145 -0
- package/dist/logging/logger.js.map +1 -0
- package/dist/logging/types.d.ts +19 -0
- package/dist/logging/types.d.ts.map +1 -0
- package/dist/logging/types.js +2 -0
- package/dist/logging/types.js.map +1 -0
- package/dist/loop/loop-manager.d.ts +100 -0
- package/dist/loop/loop-manager.d.ts.map +1 -0
- package/dist/loop/loop-manager.js +379 -0
- package/dist/loop/loop-manager.js.map +1 -0
- package/dist/loop/types.d.ts +103 -0
- package/dist/loop/types.d.ts.map +1 -0
- package/dist/loop/types.js +11 -0
- package/dist/loop/types.js.map +1 -0
- package/dist/persistence/index.d.ts +3 -0
- package/dist/persistence/index.d.ts.map +1 -0
- package/dist/persistence/index.js +2 -0
- package/dist/persistence/index.js.map +1 -0
- package/dist/persistence/sqlite-provider.d.ts +25 -0
- package/dist/persistence/sqlite-provider.d.ts.map +1 -0
- package/dist/persistence/sqlite-provider.js +59 -0
- package/dist/persistence/sqlite-provider.js.map +1 -0
- package/dist/persistence/types.d.ts +36 -0
- package/dist/persistence/types.d.ts.map +1 -0
- package/dist/persistence/types.js +8 -0
- package/dist/persistence/types.js.map +1 -0
- package/dist/planning/gap-analysis.d.ts +72 -0
- package/dist/planning/gap-analysis.d.ts.map +1 -0
- package/dist/planning/gap-analysis.js +442 -0
- package/dist/planning/gap-analysis.js.map +1 -0
- package/dist/planning/gap-types.d.ts +29 -0
- package/dist/planning/gap-types.d.ts.map +1 -0
- package/dist/planning/gap-types.js +28 -0
- package/dist/planning/gap-types.js.map +1 -0
- package/dist/planning/planner.d.ts +421 -4
- package/dist/planning/planner.d.ts.map +1 -1
- package/dist/planning/planner.js +949 -21
- package/dist/planning/planner.js.map +1 -1
- package/dist/playbooks/generic/brainstorming.d.ts +9 -0
- package/dist/playbooks/generic/brainstorming.d.ts.map +1 -0
- package/dist/playbooks/generic/brainstorming.js +105 -0
- package/dist/playbooks/generic/brainstorming.js.map +1 -0
- package/dist/playbooks/generic/code-review.d.ts +11 -0
- package/dist/playbooks/generic/code-review.d.ts.map +1 -0
- package/dist/playbooks/generic/code-review.js +176 -0
- package/dist/playbooks/generic/code-review.js.map +1 -0
- package/dist/playbooks/generic/subagent-execution.d.ts +9 -0
- package/dist/playbooks/generic/subagent-execution.d.ts.map +1 -0
- package/dist/playbooks/generic/subagent-execution.js +68 -0
- package/dist/playbooks/generic/subagent-execution.js.map +1 -0
- package/dist/playbooks/generic/systematic-debugging.d.ts +9 -0
- package/dist/playbooks/generic/systematic-debugging.d.ts.map +1 -0
- package/dist/playbooks/generic/systematic-debugging.js +87 -0
- package/dist/playbooks/generic/systematic-debugging.js.map +1 -0
- package/dist/playbooks/generic/tdd.d.ts +9 -0
- package/dist/playbooks/generic/tdd.d.ts.map +1 -0
- package/dist/playbooks/generic/tdd.js +70 -0
- package/dist/playbooks/generic/tdd.js.map +1 -0
- package/dist/playbooks/generic/verification.d.ts +9 -0
- package/dist/playbooks/generic/verification.d.ts.map +1 -0
- package/dist/playbooks/generic/verification.js +74 -0
- package/dist/playbooks/generic/verification.js.map +1 -0
- package/dist/playbooks/index.d.ts +4 -0
- package/dist/playbooks/index.d.ts.map +1 -0
- package/dist/playbooks/index.js +5 -0
- package/dist/playbooks/index.js.map +1 -0
- package/dist/playbooks/playbook-registry.d.ts +42 -0
- package/dist/playbooks/playbook-registry.d.ts.map +1 -0
- package/dist/playbooks/playbook-registry.js +227 -0
- package/dist/playbooks/playbook-registry.js.map +1 -0
- package/dist/playbooks/playbook-seeder.d.ts +47 -0
- package/dist/playbooks/playbook-seeder.d.ts.map +1 -0
- package/dist/playbooks/playbook-seeder.js +104 -0
- package/dist/playbooks/playbook-seeder.js.map +1 -0
- package/dist/playbooks/playbook-types.d.ts +132 -0
- package/dist/playbooks/playbook-types.d.ts.map +1 -0
- package/dist/playbooks/playbook-types.js +12 -0
- package/dist/playbooks/playbook-types.js.map +1 -0
- package/dist/project/project-registry.d.ts +79 -0
- package/dist/project/project-registry.d.ts.map +1 -0
- package/dist/project/project-registry.js +274 -0
- package/dist/project/project-registry.js.map +1 -0
- package/dist/project/types.d.ts +28 -0
- package/dist/project/types.d.ts.map +1 -0
- package/dist/project/types.js +5 -0
- package/dist/project/types.js.map +1 -0
- package/dist/prompts/index.d.ts +4 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +3 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/prompts/parser.d.ts +17 -0
- package/dist/prompts/parser.d.ts.map +1 -0
- package/dist/prompts/parser.js +47 -0
- package/dist/prompts/parser.js.map +1 -0
- package/dist/prompts/template-manager.d.ts +25 -0
- package/dist/prompts/template-manager.d.ts.map +1 -0
- package/dist/prompts/template-manager.js +71 -0
- package/dist/prompts/template-manager.js.map +1 -0
- package/dist/prompts/types.d.ts +26 -0
- package/dist/prompts/types.d.ts.map +1 -0
- package/dist/prompts/types.js +5 -0
- package/dist/prompts/types.js.map +1 -0
- package/dist/runtime/admin-extra-ops.d.ts +15 -0
- package/dist/runtime/admin-extra-ops.d.ts.map +1 -0
- package/dist/runtime/admin-extra-ops.js +595 -0
- package/dist/runtime/admin-extra-ops.js.map +1 -0
- package/dist/runtime/admin-ops.d.ts +15 -0
- package/dist/runtime/admin-ops.d.ts.map +1 -0
- package/dist/runtime/admin-ops.js +329 -0
- package/dist/runtime/admin-ops.js.map +1 -0
- package/dist/runtime/capture-ops.d.ts +15 -0
- package/dist/runtime/capture-ops.d.ts.map +1 -0
- package/dist/runtime/capture-ops.js +363 -0
- package/dist/runtime/capture-ops.js.map +1 -0
- package/dist/runtime/cognee-sync-ops.d.ts +12 -0
- package/dist/runtime/cognee-sync-ops.d.ts.map +1 -0
- package/dist/runtime/cognee-sync-ops.js +55 -0
- package/dist/runtime/cognee-sync-ops.js.map +1 -0
- package/dist/runtime/core-ops.d.ts +9 -3
- package/dist/runtime/core-ops.d.ts.map +1 -1
- package/dist/runtime/core-ops.js +693 -10
- package/dist/runtime/core-ops.js.map +1 -1
- package/dist/runtime/curator-extra-ops.d.ts +9 -0
- package/dist/runtime/curator-extra-ops.d.ts.map +1 -0
- package/dist/runtime/curator-extra-ops.js +71 -0
- package/dist/runtime/curator-extra-ops.js.map +1 -0
- package/dist/runtime/domain-ops.d.ts.map +1 -1
- package/dist/runtime/domain-ops.js +61 -15
- package/dist/runtime/domain-ops.js.map +1 -1
- package/dist/runtime/grading-ops.d.ts +14 -0
- package/dist/runtime/grading-ops.d.ts.map +1 -0
- package/dist/runtime/grading-ops.js +105 -0
- package/dist/runtime/grading-ops.js.map +1 -0
- package/dist/runtime/intake-ops.d.ts +14 -0
- package/dist/runtime/intake-ops.d.ts.map +1 -0
- package/dist/runtime/intake-ops.js +110 -0
- package/dist/runtime/intake-ops.js.map +1 -0
- package/dist/runtime/loop-ops.d.ts +14 -0
- package/dist/runtime/loop-ops.d.ts.map +1 -0
- package/dist/runtime/loop-ops.js +251 -0
- package/dist/runtime/loop-ops.js.map +1 -0
- package/dist/runtime/memory-cross-project-ops.d.ts +12 -0
- package/dist/runtime/memory-cross-project-ops.d.ts.map +1 -0
- package/dist/runtime/memory-cross-project-ops.js +165 -0
- package/dist/runtime/memory-cross-project-ops.js.map +1 -0
- package/dist/runtime/memory-extra-ops.d.ts +13 -0
- package/dist/runtime/memory-extra-ops.d.ts.map +1 -0
- package/dist/runtime/memory-extra-ops.js +173 -0
- package/dist/runtime/memory-extra-ops.js.map +1 -0
- package/dist/runtime/orchestrate-ops.d.ts +17 -0
- package/dist/runtime/orchestrate-ops.d.ts.map +1 -0
- package/dist/runtime/orchestrate-ops.js +246 -0
- package/dist/runtime/orchestrate-ops.js.map +1 -0
- package/dist/runtime/planning-extra-ops.d.ts +25 -0
- package/dist/runtime/planning-extra-ops.d.ts.map +1 -0
- package/dist/runtime/planning-extra-ops.js +663 -0
- package/dist/runtime/planning-extra-ops.js.map +1 -0
- package/dist/runtime/playbook-ops.d.ts +14 -0
- package/dist/runtime/playbook-ops.d.ts.map +1 -0
- package/dist/runtime/playbook-ops.js +141 -0
- package/dist/runtime/playbook-ops.js.map +1 -0
- package/dist/runtime/project-ops.d.ts +15 -0
- package/dist/runtime/project-ops.d.ts.map +1 -0
- package/dist/runtime/project-ops.js +186 -0
- package/dist/runtime/project-ops.js.map +1 -0
- package/dist/runtime/runtime.d.ts.map +1 -1
- package/dist/runtime/runtime.js +65 -3
- package/dist/runtime/runtime.js.map +1 -1
- package/dist/runtime/types.d.ts +29 -0
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/runtime/vault-extra-ops.d.ts +10 -0
- package/dist/runtime/vault-extra-ops.d.ts.map +1 -0
- package/dist/runtime/vault-extra-ops.js +536 -0
- package/dist/runtime/vault-extra-ops.js.map +1 -0
- package/dist/telemetry/telemetry.d.ts +48 -0
- package/dist/telemetry/telemetry.d.ts.map +1 -0
- package/dist/telemetry/telemetry.js +87 -0
- package/dist/telemetry/telemetry.js.map +1 -0
- package/dist/vault/playbook.d.ts +34 -0
- package/dist/vault/playbook.d.ts.map +1 -0
- package/dist/vault/playbook.js +60 -0
- package/dist/vault/playbook.js.map +1 -0
- package/dist/vault/vault.d.ts +97 -4
- package/dist/vault/vault.d.ts.map +1 -1
- package/dist/vault/vault.js +424 -65
- package/dist/vault/vault.js.map +1 -1
- package/package.json +7 -3
- package/src/__tests__/admin-extra-ops.test.ts +467 -0
- package/src/__tests__/admin-ops.test.ts +271 -0
- package/src/__tests__/brain-intelligence.test.ts +205 -0
- package/src/__tests__/brain.test.ts +134 -3
- package/src/__tests__/capture-ops.test.ts +509 -0
- package/src/__tests__/cognee-integration.test.ts +80 -0
- package/src/__tests__/cognee-sync-manager.test.ts +103 -0
- package/src/__tests__/core-ops.test.ts +292 -2
- package/src/__tests__/curator-extra-ops.test.ts +381 -0
- package/src/__tests__/domain-ops.test.ts +66 -0
- package/src/__tests__/errors.test.ts +388 -0
- package/src/__tests__/governance.test.ts +522 -0
- package/src/__tests__/grading-ops.test.ts +361 -0
- package/src/__tests__/identity-manager.test.ts +243 -0
- package/src/__tests__/intake-pipeline.test.ts +162 -0
- package/src/__tests__/intent-router.test.ts +222 -0
- package/src/__tests__/logger.test.ts +200 -0
- package/src/__tests__/loop-ops.test.ts +469 -0
- package/src/__tests__/memory-cross-project-ops.test.ts +248 -0
- package/src/__tests__/memory-extra-ops.test.ts +352 -0
- package/src/__tests__/orchestrate-ops.test.ts +289 -0
- package/src/__tests__/persistence.test.ts +225 -0
- package/src/__tests__/planner.test.ts +416 -7
- package/src/__tests__/planning-extra-ops.test.ts +706 -0
- package/src/__tests__/playbook-registry.test.ts +326 -0
- package/src/__tests__/playbook-seeder.test.ts +163 -0
- package/src/__tests__/playbook.test.ts +389 -0
- package/src/__tests__/project-ops.test.ts +381 -0
- package/src/__tests__/template-manager.test.ts +222 -0
- package/src/__tests__/vault-extra-ops.test.ts +482 -0
- package/src/brain/brain.ts +185 -16
- package/src/brain/intelligence.ts +179 -10
- package/src/brain/types.ts +40 -2
- package/src/cognee/client.ts +18 -0
- package/src/cognee/sync-manager.ts +389 -0
- package/src/control/identity-manager.ts +354 -0
- package/src/control/intent-router.ts +326 -0
- package/src/control/types.ts +102 -0
- package/src/curator/curator.ts +295 -1
- package/src/errors/classify.ts +102 -0
- package/src/errors/index.ts +5 -0
- package/src/errors/retry.ts +132 -0
- package/src/errors/types.ts +81 -0
- package/src/governance/governance.ts +698 -0
- package/src/governance/index.ts +18 -0
- package/src/governance/types.ts +111 -0
- package/src/index.ts +213 -2
- package/src/intake/content-classifier.ts +146 -0
- package/src/intake/dedup-gate.ts +92 -0
- package/src/intake/intake-pipeline.ts +503 -0
- package/src/intake/types.ts +69 -0
- package/src/intelligence/loader.ts +1 -1
- package/src/intelligence/types.ts +3 -1
- package/src/logging/logger.ts +154 -0
- package/src/logging/types.ts +21 -0
- package/src/loop/loop-manager.ts +448 -0
- package/src/loop/types.ts +115 -0
- package/src/persistence/index.ts +7 -0
- package/src/persistence/sqlite-provider.ts +62 -0
- package/src/persistence/types.ts +44 -0
- package/src/planning/gap-analysis.ts +775 -0
- package/src/planning/gap-types.ts +61 -0
- package/src/planning/planner.ts +1273 -24
- package/src/playbooks/generic/brainstorming.ts +110 -0
- package/src/playbooks/generic/code-review.ts +181 -0
- package/src/playbooks/generic/subagent-execution.ts +74 -0
- package/src/playbooks/generic/systematic-debugging.ts +92 -0
- package/src/playbooks/generic/tdd.ts +75 -0
- package/src/playbooks/generic/verification.ts +79 -0
- package/src/playbooks/index.ts +27 -0
- package/src/playbooks/playbook-registry.ts +284 -0
- package/src/playbooks/playbook-seeder.ts +119 -0
- package/src/playbooks/playbook-types.ts +162 -0
- package/src/project/project-registry.ts +370 -0
- package/src/project/types.ts +31 -0
- package/src/prompts/index.ts +3 -0
- package/src/prompts/parser.ts +59 -0
- package/src/prompts/template-manager.ts +77 -0
- package/src/prompts/types.ts +28 -0
- package/src/runtime/admin-extra-ops.ts +652 -0
- package/src/runtime/admin-ops.ts +340 -0
- package/src/runtime/capture-ops.ts +404 -0
- package/src/runtime/cognee-sync-ops.ts +63 -0
- package/src/runtime/core-ops.ts +787 -9
- package/src/runtime/curator-extra-ops.ts +85 -0
- package/src/runtime/domain-ops.ts +67 -15
- package/src/runtime/grading-ops.ts +130 -0
- package/src/runtime/intake-ops.ts +126 -0
- package/src/runtime/loop-ops.ts +277 -0
- package/src/runtime/memory-cross-project-ops.ts +191 -0
- package/src/runtime/memory-extra-ops.ts +186 -0
- package/src/runtime/orchestrate-ops.ts +278 -0
- package/src/runtime/planning-extra-ops.ts +718 -0
- package/src/runtime/playbook-ops.ts +169 -0
- package/src/runtime/project-ops.ts +202 -0
- package/src/runtime/runtime.ts +77 -3
- package/src/runtime/types.ts +29 -0
- package/src/runtime/vault-extra-ops.ts +606 -0
- package/src/telemetry/telemetry.ts +118 -0
- package/src/vault/playbook.ts +87 -0
- package/src/vault/vault.ts +575 -98
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types for Identity Management and Intent Routing.
|
|
3
|
+
*
|
|
4
|
+
* Two separate concerns:
|
|
5
|
+
* - Identity: agent persona CRUD with versioning and guidelines
|
|
6
|
+
* - Intent Routing: keyword-based intent classification and operational modes
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// ─── Identity Types ──────────────────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
export type GuidelineCategory = 'behavior' | 'preference' | 'restriction' | 'style';
|
|
12
|
+
|
|
13
|
+
export interface Guideline {
|
|
14
|
+
id: string;
|
|
15
|
+
category: GuidelineCategory;
|
|
16
|
+
text: string;
|
|
17
|
+
priority: number;
|
|
18
|
+
createdAt: string;
|
|
19
|
+
updatedAt: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface AgentIdentity {
|
|
23
|
+
agentId: string;
|
|
24
|
+
name: string;
|
|
25
|
+
role: string;
|
|
26
|
+
description: string;
|
|
27
|
+
personality: string[];
|
|
28
|
+
guidelines: Guideline[];
|
|
29
|
+
version: number;
|
|
30
|
+
updatedAt: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface IdentityVersion {
|
|
34
|
+
version: number;
|
|
35
|
+
snapshot: string;
|
|
36
|
+
changedBy: string;
|
|
37
|
+
changeReason: string;
|
|
38
|
+
createdAt: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface IdentityUpdateInput {
|
|
42
|
+
name?: string;
|
|
43
|
+
role?: string;
|
|
44
|
+
description?: string;
|
|
45
|
+
personality?: string[];
|
|
46
|
+
changedBy?: string;
|
|
47
|
+
changeReason?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface GuidelineInput {
|
|
51
|
+
category: GuidelineCategory;
|
|
52
|
+
text: string;
|
|
53
|
+
priority?: number;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ─── Intent Routing Types ────────────────────────────────────────────
|
|
57
|
+
|
|
58
|
+
export type IntentType =
|
|
59
|
+
| 'build'
|
|
60
|
+
| 'fix'
|
|
61
|
+
| 'validate'
|
|
62
|
+
| 'design'
|
|
63
|
+
| 'improve'
|
|
64
|
+
| 'deliver'
|
|
65
|
+
| 'explore'
|
|
66
|
+
| 'plan'
|
|
67
|
+
| 'review'
|
|
68
|
+
| 'general';
|
|
69
|
+
|
|
70
|
+
export type OperationalMode =
|
|
71
|
+
| 'BUILD-MODE'
|
|
72
|
+
| 'FIX-MODE'
|
|
73
|
+
| 'VALIDATE-MODE'
|
|
74
|
+
| 'DESIGN-MODE'
|
|
75
|
+
| 'IMPROVE-MODE'
|
|
76
|
+
| 'DELIVER-MODE'
|
|
77
|
+
| 'EXPLORE-MODE'
|
|
78
|
+
| 'PLAN-MODE'
|
|
79
|
+
| 'REVIEW-MODE'
|
|
80
|
+
| 'GENERAL-MODE';
|
|
81
|
+
|
|
82
|
+
export interface IntentClassification {
|
|
83
|
+
intent: IntentType;
|
|
84
|
+
mode: OperationalMode;
|
|
85
|
+
confidence: number;
|
|
86
|
+
method: 'keyword';
|
|
87
|
+
matchedKeywords: string[];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface ModeConfig {
|
|
91
|
+
mode: OperationalMode;
|
|
92
|
+
intent: IntentType;
|
|
93
|
+
description: string;
|
|
94
|
+
behaviorRules: string[];
|
|
95
|
+
keywords: string[];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface MorphResult {
|
|
99
|
+
previousMode: OperationalMode;
|
|
100
|
+
currentMode: OperationalMode;
|
|
101
|
+
behaviorRules: string[];
|
|
102
|
+
}
|
package/src/curator/curator.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Vault } from '../vault/vault.js';
|
|
2
2
|
import type { IntelligenceEntry } from '../intelligence/types.js';
|
|
3
|
+
import type { CogneeClient } from '../cognee/client.js';
|
|
3
4
|
import {
|
|
4
5
|
tokenize,
|
|
5
6
|
calculateTfIdf,
|
|
@@ -49,9 +50,11 @@ const DEFAULT_TAG_ALIASES: Array<[string, string]> = [
|
|
|
49
50
|
|
|
50
51
|
export class Curator {
|
|
51
52
|
private vault: Vault;
|
|
53
|
+
private cognee: CogneeClient | undefined;
|
|
52
54
|
|
|
53
|
-
constructor(vault: Vault) {
|
|
55
|
+
constructor(vault: Vault, cognee?: CogneeClient) {
|
|
54
56
|
this.vault = vault;
|
|
57
|
+
this.cognee = cognee;
|
|
55
58
|
this.initializeTables();
|
|
56
59
|
this.seedDefaultAliases();
|
|
57
60
|
}
|
|
@@ -93,6 +96,15 @@ export class Curator {
|
|
|
93
96
|
created_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
94
97
|
);
|
|
95
98
|
|
|
99
|
+
CREATE TABLE IF NOT EXISTS curator_entry_history (
|
|
100
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
101
|
+
entry_id TEXT NOT NULL,
|
|
102
|
+
snapshot TEXT NOT NULL,
|
|
103
|
+
changed_by TEXT DEFAULT 'system',
|
|
104
|
+
change_reason TEXT,
|
|
105
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
106
|
+
);
|
|
107
|
+
|
|
96
108
|
CREATE TABLE IF NOT EXISTS curator_contradictions (
|
|
97
109
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
98
110
|
pattern_id TEXT NOT NULL,
|
|
@@ -369,6 +381,85 @@ export class Curator {
|
|
|
369
381
|
return row ? this.rowToContradiction(row) : null;
|
|
370
382
|
}
|
|
371
383
|
|
|
384
|
+
async detectContradictionsHybrid(threshold?: number): Promise<{
|
|
385
|
+
contradictions: Contradiction[];
|
|
386
|
+
cogneeAvailable: boolean;
|
|
387
|
+
method: 'hybrid' | 'tfidf-only';
|
|
388
|
+
}> {
|
|
389
|
+
const effectiveThreshold = threshold ?? DEFAULT_CONTRADICTION_THRESHOLD;
|
|
390
|
+
const entries = this.vault.list({ limit: 100000 });
|
|
391
|
+
const antipatterns = entries.filter((e) => e.type === 'anti-pattern');
|
|
392
|
+
const patterns = entries.filter((e) => e.type === 'pattern');
|
|
393
|
+
|
|
394
|
+
if (antipatterns.length === 0 || patterns.length === 0) {
|
|
395
|
+
return { contradictions: [], cogneeAvailable: false, method: 'tfidf-only' };
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const vocabulary = this.buildVocabulary(entries);
|
|
399
|
+
const db = this.vault.getDb();
|
|
400
|
+
const detected: Contradiction[] = [];
|
|
401
|
+
|
|
402
|
+
const insertStmt = db.prepare(
|
|
403
|
+
`INSERT OR IGNORE INTO curator_contradictions (pattern_id, antipattern_id, similarity)
|
|
404
|
+
VALUES (?, ?, ?)`,
|
|
405
|
+
);
|
|
406
|
+
|
|
407
|
+
const cogneeAvailable = this.cognee?.isAvailable ?? false;
|
|
408
|
+
|
|
409
|
+
for (const ap of antipatterns) {
|
|
410
|
+
let candidates: IntelligenceEntry[];
|
|
411
|
+
try {
|
|
412
|
+
const searchResults = this.vault.search(ap.title, { type: 'pattern', limit: 20 });
|
|
413
|
+
candidates = searchResults.length > 0 ? searchResults.map((r) => r.entry) : patterns;
|
|
414
|
+
} catch {
|
|
415
|
+
candidates = patterns;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const apText = [ap.title, ap.description, ap.context ?? ''].join(' ');
|
|
419
|
+
const apVec = calculateTfIdf(tokenize(apText), vocabulary);
|
|
420
|
+
|
|
421
|
+
for (const pattern of candidates) {
|
|
422
|
+
const pText = [pattern.title, pattern.description, pattern.context ?? ''].join(' ');
|
|
423
|
+
const pVec = calculateTfIdf(tokenize(pText), vocabulary);
|
|
424
|
+
const tfidfScore = cosineSimilarity(apVec, pVec);
|
|
425
|
+
|
|
426
|
+
let finalScore = tfidfScore;
|
|
427
|
+
if (cogneeAvailable && this.cognee) {
|
|
428
|
+
try {
|
|
429
|
+
const cogneeResults = await this.cognee.search(`${ap.title} ${pattern.title}`, {
|
|
430
|
+
limit: 5,
|
|
431
|
+
});
|
|
432
|
+
const cogneeScore =
|
|
433
|
+
cogneeResults.length > 0
|
|
434
|
+
? cogneeResults.reduce((sum, r) => sum + r.score, 0) / cogneeResults.length
|
|
435
|
+
: 0;
|
|
436
|
+
finalScore = 0.6 * tfidfScore + 0.4 * cogneeScore;
|
|
437
|
+
} catch {
|
|
438
|
+
finalScore = tfidfScore;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if (finalScore >= effectiveThreshold) {
|
|
443
|
+
const result = insertStmt.run(pattern.id, ap.id, finalScore);
|
|
444
|
+
if (result.changes > 0) {
|
|
445
|
+
const row = db
|
|
446
|
+
.prepare(
|
|
447
|
+
'SELECT * FROM curator_contradictions WHERE pattern_id = ? AND antipattern_id = ?',
|
|
448
|
+
)
|
|
449
|
+
.get(pattern.id, ap.id) as Record<string, unknown>;
|
|
450
|
+
detected.push(this.rowToContradiction(row));
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
return {
|
|
457
|
+
contradictions: detected,
|
|
458
|
+
cogneeAvailable,
|
|
459
|
+
method: cogneeAvailable ? 'hybrid' : 'tfidf-only',
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
|
|
372
463
|
// ─── Grooming ───────────────────────────────────────────────────
|
|
373
464
|
|
|
374
465
|
groomEntry(entryId: string): GroomResult | null {
|
|
@@ -638,6 +729,209 @@ export class Curator {
|
|
|
638
729
|
};
|
|
639
730
|
}
|
|
640
731
|
|
|
732
|
+
// ─── Entry History (Version Snapshots) ─────────────────────────
|
|
733
|
+
|
|
734
|
+
recordSnapshot(
|
|
735
|
+
entryId: string,
|
|
736
|
+
changedBy?: string,
|
|
737
|
+
changeReason?: string,
|
|
738
|
+
): { recorded: boolean; historyId: number } {
|
|
739
|
+
const entry = this.vault.get(entryId);
|
|
740
|
+
if (!entry) return { recorded: false, historyId: -1 };
|
|
741
|
+
|
|
742
|
+
const db = this.vault.getDb();
|
|
743
|
+
const result = db
|
|
744
|
+
.prepare(
|
|
745
|
+
'INSERT INTO curator_entry_history (entry_id, snapshot, changed_by, change_reason, created_at) VALUES (?, ?, ?, ?, unixepoch())',
|
|
746
|
+
)
|
|
747
|
+
.run(entryId, JSON.stringify(entry), changedBy ?? 'system', changeReason ?? null);
|
|
748
|
+
|
|
749
|
+
return { recorded: true, historyId: Number(result.lastInsertRowid) };
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
getVersionHistory(entryId: string): Array<{
|
|
753
|
+
historyId: number;
|
|
754
|
+
entryId: string;
|
|
755
|
+
snapshot: IntelligenceEntry;
|
|
756
|
+
changedBy: string;
|
|
757
|
+
changeReason: string | null;
|
|
758
|
+
createdAt: number;
|
|
759
|
+
}> {
|
|
760
|
+
const db = this.vault.getDb();
|
|
761
|
+
const rows = db
|
|
762
|
+
.prepare(
|
|
763
|
+
'SELECT * FROM curator_entry_history WHERE entry_id = ? ORDER BY created_at ASC, id ASC',
|
|
764
|
+
)
|
|
765
|
+
.all(entryId) as Array<Record<string, unknown>>;
|
|
766
|
+
|
|
767
|
+
return rows.map((row) => ({
|
|
768
|
+
historyId: row.id as number,
|
|
769
|
+
entryId: row.entry_id as string,
|
|
770
|
+
snapshot: JSON.parse(row.snapshot as string) as IntelligenceEntry,
|
|
771
|
+
changedBy: row.changed_by as string,
|
|
772
|
+
changeReason: (row.change_reason as string) ?? null,
|
|
773
|
+
createdAt: row.created_at as number,
|
|
774
|
+
}));
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
// ─── Queue Stats ──────────────────────────────────────────────
|
|
778
|
+
|
|
779
|
+
getQueueStats(): {
|
|
780
|
+
totalEntries: number;
|
|
781
|
+
groomedEntries: number;
|
|
782
|
+
ungroomedEntries: number;
|
|
783
|
+
staleEntries: number;
|
|
784
|
+
freshEntries: number;
|
|
785
|
+
avgDaysSinceGroom: number;
|
|
786
|
+
} {
|
|
787
|
+
const db = this.vault.getDb();
|
|
788
|
+
const totalEntries = (
|
|
789
|
+
db.prepare('SELECT COUNT(*) as count FROM entries').get() as { count: number }
|
|
790
|
+
).count;
|
|
791
|
+
|
|
792
|
+
const groomedEntries = (
|
|
793
|
+
db
|
|
794
|
+
.prepare(
|
|
795
|
+
'SELECT COUNT(*) as count FROM curator_entry_state WHERE last_groomed_at IS NOT NULL',
|
|
796
|
+
)
|
|
797
|
+
.get() as { count: number }
|
|
798
|
+
).count;
|
|
799
|
+
|
|
800
|
+
const ungroomedEntries = totalEntries - groomedEntries;
|
|
801
|
+
|
|
802
|
+
const now = Math.floor(Date.now() / 1000);
|
|
803
|
+
const staleThreshold = now - 30 * 86400;
|
|
804
|
+
const freshThreshold = now - 7 * 86400;
|
|
805
|
+
|
|
806
|
+
const staleEntries = (
|
|
807
|
+
db
|
|
808
|
+
.prepare(
|
|
809
|
+
'SELECT COUNT(*) as count FROM curator_entry_state WHERE last_groomed_at IS NOT NULL AND last_groomed_at < ?',
|
|
810
|
+
)
|
|
811
|
+
.get(staleThreshold) as { count: number }
|
|
812
|
+
).count;
|
|
813
|
+
|
|
814
|
+
const freshEntries = (
|
|
815
|
+
db
|
|
816
|
+
.prepare(
|
|
817
|
+
'SELECT COUNT(*) as count FROM curator_entry_state WHERE last_groomed_at IS NOT NULL AND last_groomed_at >= ?',
|
|
818
|
+
)
|
|
819
|
+
.get(freshThreshold) as { count: number }
|
|
820
|
+
).count;
|
|
821
|
+
|
|
822
|
+
let avgDaysSinceGroom = 0;
|
|
823
|
+
if (groomedEntries > 0) {
|
|
824
|
+
const sumRow = db
|
|
825
|
+
.prepare(
|
|
826
|
+
'SELECT SUM(? - last_groomed_at) as total FROM curator_entry_state WHERE last_groomed_at IS NOT NULL',
|
|
827
|
+
)
|
|
828
|
+
.get(now) as { total: number | null };
|
|
829
|
+
const totalSeconds = sumRow.total ?? 0;
|
|
830
|
+
avgDaysSinceGroom = Math.round((totalSeconds / groomedEntries / 86400) * 100) / 100;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
return {
|
|
834
|
+
totalEntries,
|
|
835
|
+
groomedEntries,
|
|
836
|
+
ungroomedEntries,
|
|
837
|
+
staleEntries,
|
|
838
|
+
freshEntries,
|
|
839
|
+
avgDaysSinceGroom,
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
// ─── Metadata Enrichment ──────────────────────────────────────
|
|
844
|
+
|
|
845
|
+
enrichMetadata(entryId: string): {
|
|
846
|
+
enriched: boolean;
|
|
847
|
+
changes: Array<{ field: string; before: string; after: string }>;
|
|
848
|
+
} {
|
|
849
|
+
const entry = this.vault.get(entryId);
|
|
850
|
+
if (!entry) return { enriched: false, changes: [] };
|
|
851
|
+
|
|
852
|
+
const changes: Array<{ field: string; before: string; after: string }> = [];
|
|
853
|
+
const updates: Partial<
|
|
854
|
+
Pick<IntelligenceEntry, 'title' | 'description' | 'tags' | 'severity' | 'type'>
|
|
855
|
+
> = {};
|
|
856
|
+
|
|
857
|
+
// Auto-capitalize title
|
|
858
|
+
if (entry.title.length > 0 && entry.title[0] !== entry.title[0].toUpperCase()) {
|
|
859
|
+
const capitalized = entry.title[0].toUpperCase() + entry.title.slice(1);
|
|
860
|
+
changes.push({ field: 'title', before: entry.title, after: capitalized });
|
|
861
|
+
updates.title = capitalized;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
// Normalize tags: lowercase, trim, dedup
|
|
865
|
+
const normalizedTags = [...new Set(entry.tags.map((t) => t.toLowerCase().trim()))];
|
|
866
|
+
const tagsChanged =
|
|
867
|
+
normalizedTags.length !== entry.tags.length ||
|
|
868
|
+
normalizedTags.some((t, i) => t !== entry.tags[i]);
|
|
869
|
+
if (tagsChanged) {
|
|
870
|
+
changes.push({
|
|
871
|
+
field: 'tags',
|
|
872
|
+
before: JSON.stringify(entry.tags),
|
|
873
|
+
after: JSON.stringify(normalizedTags),
|
|
874
|
+
});
|
|
875
|
+
updates.tags = normalizedTags;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
// Infer severity from keywords if currently 'suggestion'
|
|
879
|
+
if (entry.severity === 'suggestion') {
|
|
880
|
+
const text = (entry.title + ' ' + entry.description).toLowerCase();
|
|
881
|
+
const criticalKeywords = ['never', 'must not', 'critical', 'security', 'vulnerability'];
|
|
882
|
+
const warningKeywords = ['avoid', 'should not', 'deprecated', 'careful', 'warning'];
|
|
883
|
+
if (criticalKeywords.some((k) => text.includes(k))) {
|
|
884
|
+
changes.push({ field: 'severity', before: entry.severity, after: 'critical' });
|
|
885
|
+
updates.severity = 'critical';
|
|
886
|
+
} else if (warningKeywords.some((k) => text.includes(k))) {
|
|
887
|
+
changes.push({ field: 'severity', before: entry.severity, after: 'warning' });
|
|
888
|
+
updates.severity = 'warning';
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
// Infer type from title patterns
|
|
893
|
+
if (entry.type === 'pattern') {
|
|
894
|
+
const titleLower = entry.title.toLowerCase();
|
|
895
|
+
if (
|
|
896
|
+
titleLower.startsWith('avoid') ||
|
|
897
|
+
titleLower.startsWith('never') ||
|
|
898
|
+
titleLower.startsWith("don't") ||
|
|
899
|
+
titleLower.startsWith('do not')
|
|
900
|
+
) {
|
|
901
|
+
changes.push({ field: 'type', before: entry.type, after: 'anti-pattern' });
|
|
902
|
+
updates.type = 'anti-pattern';
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
// Trim whitespace from description
|
|
907
|
+
const trimmed = entry.description.trim();
|
|
908
|
+
if (trimmed !== entry.description) {
|
|
909
|
+
changes.push({ field: 'description', before: entry.description, after: trimmed });
|
|
910
|
+
updates.description = trimmed;
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
if (changes.length === 0) {
|
|
914
|
+
return { enriched: false, changes: [] };
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
// Apply updates
|
|
918
|
+
this.vault.update(entryId, updates);
|
|
919
|
+
|
|
920
|
+
// Record snapshot
|
|
921
|
+
this.recordSnapshot(entryId, 'curator', 'Metadata enrichment');
|
|
922
|
+
|
|
923
|
+
// Log change
|
|
924
|
+
this.logChange(
|
|
925
|
+
'enrich_metadata',
|
|
926
|
+
entryId,
|
|
927
|
+
JSON.stringify(changes.map((c) => c.field)),
|
|
928
|
+
JSON.stringify(changes.map((c) => c.after)),
|
|
929
|
+
'Rule-based metadata enrichment',
|
|
930
|
+
);
|
|
931
|
+
|
|
932
|
+
return { enriched: true, changes };
|
|
933
|
+
}
|
|
934
|
+
|
|
641
935
|
// ─── Private Helpers ────────────────────────────────────────────
|
|
642
936
|
|
|
643
937
|
private buildVocabulary(entries: IntelligenceEntry[]): Map<string, number> {
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error classifier — converts any thrown value into a typed SoleriError.
|
|
3
|
+
*
|
|
4
|
+
* Maps HTTP status codes, network error codes, and message patterns
|
|
5
|
+
* to the appropriate SoleriErrorCode.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { SoleriError, SoleriErrorCode } from './types.js';
|
|
9
|
+
|
|
10
|
+
interface ErrorLike {
|
|
11
|
+
message?: string;
|
|
12
|
+
status?: number;
|
|
13
|
+
statusCode?: number;
|
|
14
|
+
code?: string;
|
|
15
|
+
cause?: unknown;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function toErrorLike(error: unknown): ErrorLike {
|
|
19
|
+
if (error instanceof SoleriError) return error;
|
|
20
|
+
if (error instanceof Error) return error as unknown as ErrorLike;
|
|
21
|
+
if (typeof error === 'object' && error !== null) return error as ErrorLike;
|
|
22
|
+
return { message: String(error) };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function getHttpStatus(e: ErrorLike): number | undefined {
|
|
26
|
+
return e.status ?? e.statusCode;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function classifyByHttpStatus(status: number): SoleriErrorCode | undefined {
|
|
30
|
+
if (status === 401 || status === 403) return SoleriErrorCode.AUTH;
|
|
31
|
+
if (status === 404) return SoleriErrorCode.RESOURCE_NOT_FOUND;
|
|
32
|
+
if (status === 408) return SoleriErrorCode.TIMEOUT;
|
|
33
|
+
if (status === 429) return SoleriErrorCode.RATE_LIMIT;
|
|
34
|
+
if (status === 422) return SoleriErrorCode.VALIDATION;
|
|
35
|
+
if (status >= 500 && status < 600) return SoleriErrorCode.INTERNAL;
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const NETWORK_CODES = new Set(['ECONNREFUSED', 'ENOTFOUND', 'ECONNRESET', 'EPIPE', 'EHOSTUNREACH']);
|
|
40
|
+
const TIMEOUT_CODES = new Set(['ETIMEDOUT', 'ESOCKETTIMEDOUT', 'UND_ERR_CONNECT_TIMEOUT']);
|
|
41
|
+
|
|
42
|
+
function classifyByErrorCode(code: string | undefined): SoleriErrorCode | undefined {
|
|
43
|
+
if (!code) return undefined;
|
|
44
|
+
if (NETWORK_CODES.has(code)) return SoleriErrorCode.NETWORK;
|
|
45
|
+
if (TIMEOUT_CODES.has(code)) return SoleriErrorCode.TIMEOUT;
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const MESSAGE_PATTERNS: Array<[RegExp, SoleriErrorCode]> = [
|
|
50
|
+
[/overloaded|capacity|model.*busy/i, SoleriErrorCode.LLM_OVERLOAD],
|
|
51
|
+
[/timeout|timed?\s*out/i, SoleriErrorCode.TIMEOUT],
|
|
52
|
+
[/vault|database|sqlite/i, SoleriErrorCode.VAULT_UNREACHABLE],
|
|
53
|
+
[/invalid|validation|schema/i, SoleriErrorCode.VALIDATION],
|
|
54
|
+
[/config(uration)?|missing.*key|env/i, SoleriErrorCode.CONFIG_ERROR],
|
|
55
|
+
[/auth(entication|orization)?|forbidden|denied|unauthorized/i, SoleriErrorCode.AUTH],
|
|
56
|
+
[/not\s*found|404|no\s+such/i, SoleriErrorCode.RESOURCE_NOT_FOUND],
|
|
57
|
+
[/rate\s*limit|too\s+many\s+requests|throttl/i, SoleriErrorCode.RATE_LIMIT],
|
|
58
|
+
[/network|connect|socket|dns/i, SoleriErrorCode.NETWORK],
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
function classifyByMessage(message: string | undefined): SoleriErrorCode | undefined {
|
|
62
|
+
if (!message) return undefined;
|
|
63
|
+
for (const [pattern, code] of MESSAGE_PATTERNS) {
|
|
64
|
+
if (pattern.test(message)) return code;
|
|
65
|
+
}
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Classify any thrown value into a SoleriError.
|
|
71
|
+
* If the value is already a SoleriError, returns it as-is.
|
|
72
|
+
*/
|
|
73
|
+
export function classifyError(error: unknown): SoleriError {
|
|
74
|
+
if (error instanceof SoleriError) return error;
|
|
75
|
+
|
|
76
|
+
const e = toErrorLike(error);
|
|
77
|
+
const originalError = error instanceof Error ? error : undefined;
|
|
78
|
+
const message = e.message ?? 'Unknown error';
|
|
79
|
+
|
|
80
|
+
// 1. HTTP status code
|
|
81
|
+
const httpStatus = getHttpStatus(e);
|
|
82
|
+
if (httpStatus !== undefined) {
|
|
83
|
+
const code = classifyByHttpStatus(httpStatus);
|
|
84
|
+
if (code)
|
|
85
|
+
return new SoleriError(message, code, { cause: originalError, context: { httpStatus } });
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// 2. Node.js error code
|
|
89
|
+
const errCode = classifyByErrorCode(e.code);
|
|
90
|
+
if (errCode)
|
|
91
|
+
return new SoleriError(message, errCode, {
|
|
92
|
+
cause: originalError,
|
|
93
|
+
context: { errorCode: e.code },
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// 3. Message pattern matching
|
|
97
|
+
const msgCode = classifyByMessage(message);
|
|
98
|
+
if (msgCode) return new SoleriError(message, msgCode, { cause: originalError });
|
|
99
|
+
|
|
100
|
+
// 4. Default: permanent INTERNAL
|
|
101
|
+
return new SoleriError(message, SoleriErrorCode.INTERNAL, { cause: originalError });
|
|
102
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { SoleriErrorCode, SoleriError, ok, err, isOk, isErr } from './types.js';
|
|
2
|
+
export type { ErrorClassification, Result, SoleriErrorOptions } from './types.js';
|
|
3
|
+
export { classifyError } from './classify.js';
|
|
4
|
+
export { retryWithPreset, shouldRetry, getRetryDelay, RETRY_PRESETS } from './retry.js';
|
|
5
|
+
export type { RetryPreset, RetryConfig, RetryOptions } from './retry.js';
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retry presets with exponential backoff + jitter.
|
|
3
|
+
*
|
|
4
|
+
* Three presets map to different execution contexts:
|
|
5
|
+
* - fast: quick API calls, 3 attempts, short waits
|
|
6
|
+
* - normal: standard operations, 10 attempts, moderate waits
|
|
7
|
+
* - patient: batch/pipeline work, 25 attempts, long waits
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { classifyError } from './classify.js';
|
|
11
|
+
import { SoleriError, type Result, ok, err } from './types.js';
|
|
12
|
+
|
|
13
|
+
// ─── Types ─────────────────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
export type RetryPreset = 'fast' | 'normal' | 'patient';
|
|
16
|
+
|
|
17
|
+
export interface RetryConfig {
|
|
18
|
+
initialIntervalMs: number;
|
|
19
|
+
maxIntervalMs: number;
|
|
20
|
+
maxAttempts: number;
|
|
21
|
+
backoffMultiplier: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface RetryOptions {
|
|
25
|
+
onRetry?: (error: SoleriError, attempt: number, delayMs: number) => void;
|
|
26
|
+
signal?: AbortSignal;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ─── Presets ───────────────────────────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
export const RETRY_PRESETS: Record<RetryPreset, RetryConfig> = {
|
|
32
|
+
fast: { initialIntervalMs: 1_000, maxIntervalMs: 10_000, maxAttempts: 3, backoffMultiplier: 2 },
|
|
33
|
+
normal: {
|
|
34
|
+
initialIntervalMs: 10_000,
|
|
35
|
+
maxIntervalMs: 120_000,
|
|
36
|
+
maxAttempts: 10,
|
|
37
|
+
backoffMultiplier: 2,
|
|
38
|
+
},
|
|
39
|
+
patient: {
|
|
40
|
+
initialIntervalMs: 60_000,
|
|
41
|
+
maxIntervalMs: 900_000,
|
|
42
|
+
maxAttempts: 25,
|
|
43
|
+
backoffMultiplier: 1.5,
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// ─── Helpers ───────────────────────────────────────────────────────────
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Check if a classified error should be retried at the given attempt.
|
|
51
|
+
*/
|
|
52
|
+
export function shouldRetry(error: SoleriError, attempt: number, preset: RetryPreset): boolean {
|
|
53
|
+
if (!error.retryable) return false;
|
|
54
|
+
return attempt < RETRY_PRESETS[preset].maxAttempts;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Calculate retry delay with exponential backoff + jitter.
|
|
59
|
+
* Jitter adds ±25% to prevent thundering herd.
|
|
60
|
+
*/
|
|
61
|
+
export function getRetryDelay(attempt: number, preset: RetryPreset): number {
|
|
62
|
+
const config = RETRY_PRESETS[preset];
|
|
63
|
+
const base = config.initialIntervalMs * Math.pow(config.backoffMultiplier, attempt);
|
|
64
|
+
const capped = Math.min(base, config.maxIntervalMs);
|
|
65
|
+
// ±25% jitter
|
|
66
|
+
const jitter = capped * 0.25 * (Math.random() * 2 - 1);
|
|
67
|
+
return Math.max(0, Math.round(capped + jitter));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ─── Retry Loop ────────────────────────────────────────────────────────
|
|
71
|
+
|
|
72
|
+
function sleep(ms: number, signal?: AbortSignal): Promise<void> {
|
|
73
|
+
return new Promise((resolve, reject) => {
|
|
74
|
+
if (signal?.aborted) {
|
|
75
|
+
reject(signal.reason ?? new Error('Aborted'));
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const timer = setTimeout(resolve, ms);
|
|
79
|
+
signal?.addEventListener(
|
|
80
|
+
'abort',
|
|
81
|
+
() => {
|
|
82
|
+
clearTimeout(timer);
|
|
83
|
+
reject(signal.reason ?? new Error('Aborted'));
|
|
84
|
+
},
|
|
85
|
+
{ once: true },
|
|
86
|
+
);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Retry an async operation with a named preset.
|
|
92
|
+
*
|
|
93
|
+
* - On success: returns ok(result)
|
|
94
|
+
* - On permanent/fixable error: returns err() immediately
|
|
95
|
+
* - On retryable error: retries up to maxAttempts with backoff
|
|
96
|
+
* - On exhaustion: returns err() with last error
|
|
97
|
+
*/
|
|
98
|
+
export async function retryWithPreset<T>(
|
|
99
|
+
fn: () => Promise<T>,
|
|
100
|
+
preset: RetryPreset,
|
|
101
|
+
options?: RetryOptions,
|
|
102
|
+
): Promise<Result<T>> {
|
|
103
|
+
let lastError: SoleriError | undefined;
|
|
104
|
+
|
|
105
|
+
for (let attempt = 0; attempt < RETRY_PRESETS[preset].maxAttempts; attempt++) {
|
|
106
|
+
try {
|
|
107
|
+
const value = await fn();
|
|
108
|
+
return ok(value);
|
|
109
|
+
} catch (thrown: unknown) {
|
|
110
|
+
lastError = classifyError(thrown);
|
|
111
|
+
|
|
112
|
+
if (!shouldRetry(lastError, attempt + 1, preset)) {
|
|
113
|
+
return err(lastError);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const delay = getRetryDelay(attempt, preset);
|
|
117
|
+
options?.onRetry?.(lastError, attempt + 1, delay);
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
await sleep(delay, options?.signal);
|
|
121
|
+
} catch {
|
|
122
|
+
// Aborted during sleep
|
|
123
|
+
return err(lastError);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return err(lastError ?? new SoleriError('Max retries exceeded', SoleriErrorCode.INTERNAL));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Re-import for the err fallback
|
|
132
|
+
import { SoleriErrorCode } from './types.js';
|