@soleri/core 9.2.0 → 9.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/data/flows/build.flow.yaml +8 -9
- package/data/flows/deliver.flow.yaml +9 -10
- package/data/flows/design.flow.yaml +3 -4
- package/data/flows/enhance.flow.yaml +5 -6
- package/data/flows/explore.flow.yaml +3 -4
- package/data/flows/fix.flow.yaml +5 -6
- package/data/flows/plan.flow.yaml +4 -5
- package/data/flows/review.flow.yaml +3 -4
- package/dist/curator/curator.d.ts.map +1 -1
- package/dist/curator/curator.js +98 -22
- package/dist/curator/curator.js.map +1 -1
- package/dist/engine/bin/soleri-engine.js.map +1 -1
- package/dist/engine/module-manifest.d.ts +2 -0
- package/dist/engine/module-manifest.d.ts.map +1 -1
- package/dist/engine/module-manifest.js +136 -1
- package/dist/engine/module-manifest.js.map +1 -1
- package/dist/engine/register-engine.d.ts.map +1 -1
- package/dist/engine/register-engine.js +25 -1
- package/dist/engine/register-engine.js.map +1 -1
- package/dist/flows/gate-evaluator.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/operator/operator-profile.d.ts.map +1 -1
- package/dist/operator/operator-profile.js +11 -5
- package/dist/operator/operator-profile.js.map +1 -1
- package/dist/operator/operator-signals.d.ts.map +1 -1
- package/dist/operator/operator-signals.js.map +1 -1
- package/dist/planning/evidence-collector.js.map +1 -1
- package/dist/planning/gap-passes.d.ts.map +1 -1
- package/dist/planning/gap-passes.js +23 -6
- package/dist/planning/gap-passes.js.map +1 -1
- package/dist/planning/gap-patterns.d.ts.map +1 -1
- package/dist/planning/gap-patterns.js +57 -11
- package/dist/planning/gap-patterns.js.map +1 -1
- package/dist/planning/github-projection.d.ts.map +1 -1
- package/dist/planning/github-projection.js +39 -20
- package/dist/planning/github-projection.js.map +1 -1
- package/dist/planning/impact-analyzer.d.ts.map +1 -1
- package/dist/planning/impact-analyzer.js +20 -18
- package/dist/planning/impact-analyzer.js.map +1 -1
- package/dist/planning/plan-lifecycle.d.ts.map +1 -1
- package/dist/planning/plan-lifecycle.js +22 -9
- package/dist/planning/plan-lifecycle.js.map +1 -1
- package/dist/planning/planner.d.ts.map +1 -1
- package/dist/planning/planner.js +60 -17
- package/dist/planning/planner.js.map +1 -1
- package/dist/planning/rationalization-detector.d.ts.map +1 -1
- package/dist/planning/rationalization-detector.js.map +1 -1
- package/dist/planning/reconciliation-engine.d.ts.map +1 -1
- package/dist/planning/reconciliation-engine.js.map +1 -1
- package/dist/planning/task-complexity-assessor.d.ts +42 -0
- package/dist/planning/task-complexity-assessor.d.ts.map +1 -0
- package/dist/planning/task-complexity-assessor.js +132 -0
- package/dist/planning/task-complexity-assessor.js.map +1 -0
- package/dist/planning/task-verifier.d.ts.map +1 -1
- package/dist/planning/task-verifier.js +14 -6
- package/dist/planning/task-verifier.js.map +1 -1
- package/dist/runtime/admin-ops.d.ts.map +1 -1
- package/dist/runtime/admin-ops.js +18 -0
- package/dist/runtime/admin-ops.js.map +1 -1
- package/dist/runtime/admin-setup-ops.d.ts.map +1 -1
- package/dist/runtime/admin-setup-ops.js +2 -1
- package/dist/runtime/admin-setup-ops.js.map +1 -1
- package/dist/runtime/branching-ops.d.ts +12 -0
- package/dist/runtime/branching-ops.d.ts.map +1 -0
- package/dist/runtime/branching-ops.js +100 -0
- package/dist/runtime/branching-ops.js.map +1 -0
- package/dist/runtime/context-health.d.ts.map +1 -1
- package/dist/runtime/context-health.js.map +1 -1
- package/dist/runtime/facades/branching-facade.d.ts +7 -0
- package/dist/runtime/facades/branching-facade.d.ts.map +1 -0
- package/dist/runtime/facades/branching-facade.js +8 -0
- package/dist/runtime/facades/branching-facade.js.map +1 -0
- package/dist/runtime/facades/chat-service-ops.d.ts.map +1 -1
- package/dist/runtime/facades/chat-service-ops.js +3 -1
- package/dist/runtime/facades/chat-service-ops.js.map +1 -1
- package/dist/runtime/facades/chat-transport-ops.d.ts.map +1 -1
- package/dist/runtime/facades/chat-transport-ops.js.map +1 -1
- package/dist/runtime/facades/index.d.ts.map +1 -1
- package/dist/runtime/facades/index.js +42 -0
- package/dist/runtime/facades/index.js.map +1 -1
- package/dist/runtime/facades/intake-facade.d.ts +9 -0
- package/dist/runtime/facades/intake-facade.d.ts.map +1 -0
- package/dist/runtime/facades/intake-facade.js +11 -0
- package/dist/runtime/facades/intake-facade.js.map +1 -0
- package/dist/runtime/facades/links-facade.d.ts +9 -0
- package/dist/runtime/facades/links-facade.d.ts.map +1 -0
- package/dist/runtime/facades/links-facade.js +10 -0
- package/dist/runtime/facades/links-facade.js.map +1 -0
- package/dist/runtime/facades/operator-facade.d.ts.map +1 -1
- package/dist/runtime/facades/operator-facade.js.map +1 -1
- package/dist/runtime/facades/plan-facade.d.ts.map +1 -1
- package/dist/runtime/facades/plan-facade.js +4 -1
- package/dist/runtime/facades/plan-facade.js.map +1 -1
- package/dist/runtime/facades/tier-facade.d.ts +7 -0
- package/dist/runtime/facades/tier-facade.d.ts.map +1 -0
- package/dist/runtime/facades/tier-facade.js +8 -0
- package/dist/runtime/facades/tier-facade.js.map +1 -0
- package/dist/runtime/facades/vault-facade.d.ts +9 -1
- package/dist/runtime/facades/vault-facade.d.ts.map +1 -1
- package/dist/runtime/facades/vault-facade.js +44 -187
- package/dist/runtime/facades/vault-facade.js.map +1 -1
- package/dist/runtime/github-integration.d.ts.map +1 -1
- package/dist/runtime/github-integration.js +11 -4
- package/dist/runtime/github-integration.js.map +1 -1
- package/dist/runtime/orchestrate-ops.d.ts.map +1 -1
- package/dist/runtime/orchestrate-ops.js +75 -42
- package/dist/runtime/orchestrate-ops.js.map +1 -1
- package/dist/runtime/planning-extra-ops.d.ts.map +1 -1
- package/dist/runtime/planning-extra-ops.js.map +1 -1
- package/dist/runtime/runtime.d.ts.map +1 -1
- package/dist/runtime/runtime.js +3 -1
- package/dist/runtime/runtime.js.map +1 -1
- package/dist/runtime/session-briefing.d.ts.map +1 -1
- package/dist/runtime/session-briefing.js +5 -1
- package/dist/runtime/session-briefing.js.map +1 -1
- package/dist/runtime/tier-ops.d.ts +13 -0
- package/dist/runtime/tier-ops.d.ts.map +1 -0
- package/dist/runtime/tier-ops.js +110 -0
- package/dist/runtime/tier-ops.js.map +1 -0
- package/dist/skills/sync-skills.d.ts.map +1 -1
- package/dist/skills/sync-skills.js +1 -1
- package/dist/skills/sync-skills.js.map +1 -1
- package/dist/vault/linking.d.ts.map +1 -1
- package/dist/vault/linking.js +41 -5
- package/dist/vault/linking.js.map +1 -1
- package/dist/vault/vault-entries.d.ts.map +1 -1
- package/dist/vault/vault-entries.js +68 -26
- package/dist/vault/vault-entries.js.map +1 -1
- package/dist/vault/vault-maintenance.d.ts.map +1 -1
- package/dist/vault/vault-maintenance.js +6 -2
- package/dist/vault/vault-maintenance.js.map +1 -1
- package/dist/vault/vault-markdown-sync.d.ts.map +1 -1
- package/dist/vault/vault-markdown-sync.js.map +1 -1
- package/dist/vault/vault-memories.d.ts.map +1 -1
- package/dist/vault/vault-memories.js +3 -1
- package/dist/vault/vault-memories.js.map +1 -1
- package/dist/vault/vault-schema.js +36 -10
- package/dist/vault/vault-schema.js.map +1 -1
- package/dist/vault/vault.d.ts.map +1 -1
- package/dist/vault/vault.js +5 -1
- package/dist/vault/vault.js.map +1 -1
- package/package.json +7 -7
- package/src/agency/agency-manager.test.ts +60 -40
- package/src/agency/default-rules.test.ts +17 -9
- package/src/capabilities/registry.test.ts +2 -12
- package/src/chat/agent-loop.test.ts +33 -43
- package/src/chat/mcp-bridge.test.ts +7 -2
- package/src/claudemd/inject.test.ts +2 -12
- package/src/context/context-engine.test.ts +96 -51
- package/src/control/intent-router.test.ts +3 -3
- package/src/curator/classifier.test.ts +14 -8
- package/src/curator/contradiction-detector.test.ts +30 -5
- package/src/curator/curator.ts +278 -56
- package/src/curator/duplicate-detector.test.ts +77 -15
- package/src/curator/quality-gate.test.ts +71 -31
- package/src/curator/tag-manager.test.ts +12 -4
- package/src/domain-packs/knowledge-installer.test.ts +2 -10
- package/src/domain-packs/token-resolver.test.ts +1 -3
- package/src/domain-packs/types.test.ts +16 -2
- package/src/enforcement/registry.test.ts +2 -8
- package/src/engine/bin/soleri-engine.ts +3 -1
- package/src/engine/module-manifest.test.ts +48 -4
- package/src/engine/module-manifest.ts +138 -1
- package/src/engine/register-engine.test.ts +6 -1
- package/src/engine/register-engine.ts +26 -3
- package/src/errors/classify.test.ts +6 -2
- package/src/errors/retry.test.ts +1 -4
- package/src/facades/facade-factory.test.ts +110 -64
- package/src/flows/epilogue.test.ts +16 -10
- package/src/flows/gate-evaluator.test.ts +12 -6
- package/src/flows/gate-evaluator.ts +1 -3
- package/src/governance/governance.test.ts +137 -21
- package/src/health/health-registry.test.ts +8 -1
- package/src/index.ts +8 -0
- package/src/intake/content-classifier.test.ts +121 -51
- package/src/intake/dedup-gate.test.ts +38 -22
- package/src/intake/intake-pipeline.test.ts +5 -3
- package/src/intake/text-ingester.test.ts +26 -20
- package/src/llm/key-pool.test.ts +1 -3
- package/src/llm/llm-client.test.ts +1 -4
- package/src/llm/oauth-discovery.test.ts +16 -16
- package/src/llm/utils.test.ts +62 -18
- package/src/logging/logger.test.ts +4 -1
- package/src/loop/loop-manager.test.ts +2 -6
- package/src/migrations/migration-runner.edge-cases.test.ts +2 -7
- package/src/operator/operator-profile-extended.test.ts +15 -5
- package/src/operator/operator-profile.test.ts +26 -8
- package/src/operator/operator-profile.ts +38 -22
- package/src/operator/operator-signals-extended.test.ts +35 -23
- package/src/operator/operator-signals.test.ts +6 -10
- package/src/operator/operator-signals.ts +2 -1
- package/src/operator/prompts/hook-precompact-operator-dispatch.md +10 -6
- package/src/operator/prompts/subagent-soft-signal-extractor.md +5 -0
- package/src/operator/prompts/subagent-synthesis-cognition.md +19 -10
- package/src/operator/prompts/subagent-synthesis-communication.md +13 -7
- package/src/operator/prompts/subagent-synthesis-technical.md +19 -9
- package/src/operator/prompts/subagent-synthesis-trust.md +27 -21
- package/src/persona/defaults.test.ts +1 -5
- package/src/planning/evidence-collector.test.ts +147 -38
- package/src/planning/evidence-collector.ts +1 -4
- package/src/planning/gap-analysis-alternatives.test.ts +41 -11
- package/src/planning/gap-passes.test.ts +215 -33
- package/src/planning/gap-passes.ts +115 -46
- package/src/planning/gap-patterns.test.ts +87 -13
- package/src/planning/gap-patterns.ts +114 -31
- package/src/planning/github-projection.test.ts +6 -1
- package/src/planning/github-projection.ts +41 -20
- package/src/planning/impact-analyzer.test.ts +10 -23
- package/src/planning/impact-analyzer.ts +33 -46
- package/src/planning/plan-lifecycle.test.ts +103 -36
- package/src/planning/plan-lifecycle.ts +49 -18
- package/src/planning/planner.test.ts +12 -2
- package/src/planning/planner.ts +198 -58
- package/src/planning/rationalization-detector.test.ts +5 -20
- package/src/planning/rationalization-detector.ts +14 -16
- package/src/planning/reconciliation-engine.test.ts +20 -3
- package/src/planning/reconciliation-engine.ts +1 -2
- package/src/planning/task-complexity-assessor.test.ts +298 -0
- package/src/planning/task-complexity-assessor.ts +183 -0
- package/src/planning/task-verifier.test.ts +59 -27
- package/src/planning/task-verifier.ts +15 -9
- package/src/playbooks/playbook-executor.test.ts +1 -3
- package/src/plugins/plugin-loader.test.ts +19 -14
- package/src/plugins/plugin-registry.test.ts +45 -33
- package/src/project/project-registry.test.ts +23 -12
- package/src/prompts/template-manager.test.ts +4 -1
- package/src/queue/job-queue.test.ts +10 -14
- package/src/runtime/admin-extra-ops.test.ts +5 -19
- package/src/runtime/admin-ops.test.ts +22 -1
- package/src/runtime/admin-ops.ts +19 -0
- package/src/runtime/admin-setup-ops.test.ts +3 -4
- package/src/runtime/admin-setup-ops.ts +9 -2
- package/src/runtime/archive-ops.test.ts +4 -1
- package/src/runtime/branching-ops.test.ts +144 -0
- package/src/runtime/branching-ops.ts +107 -0
- package/src/runtime/capture-ops.test.ts +7 -21
- package/src/runtime/chain-ops.test.ts +16 -6
- package/src/runtime/claude-md-helpers.test.ts +1 -3
- package/src/runtime/context-health.test.ts +1 -3
- package/src/runtime/context-health.ts +1 -3
- package/src/runtime/curator-extra-ops.test.ts +3 -1
- package/src/runtime/domain-ops.test.ts +46 -36
- package/src/runtime/facades/admin-facade.test.ts +1 -4
- package/src/runtime/facades/archive-facade.test.ts +21 -7
- package/src/runtime/facades/brain-facade.test.ts +176 -72
- package/src/runtime/facades/branching-facade.test.ts +43 -0
- package/src/runtime/facades/branching-facade.ts +11 -0
- package/src/runtime/facades/chat-facade.test.ts +81 -28
- package/src/runtime/facades/chat-service-ops.test.ts +178 -73
- package/src/runtime/facades/chat-service-ops.ts +3 -1
- package/src/runtime/facades/chat-session-ops.test.ts +25 -10
- package/src/runtime/facades/chat-transport-ops.test.ts +101 -34
- package/src/runtime/facades/chat-transport-ops.ts +0 -1
- package/src/runtime/facades/context-facade.test.ts +19 -4
- package/src/runtime/facades/control-facade.test.ts +3 -3
- package/src/runtime/facades/index.ts +42 -0
- package/src/runtime/facades/intake-facade.test.ts +215 -0
- package/src/runtime/facades/intake-facade.ts +14 -0
- package/src/runtime/facades/links-facade.test.ts +203 -0
- package/src/runtime/facades/links-facade.ts +13 -0
- package/src/runtime/facades/loop-facade.test.ts +22 -5
- package/src/runtime/facades/memory-facade.test.ts +19 -5
- package/src/runtime/facades/operator-facade.test.ts +17 -4
- package/src/runtime/facades/operator-facade.ts +11 -3
- package/src/runtime/facades/orchestrate-facade.test.ts +7 -1
- package/src/runtime/facades/plan-facade.test.ts +29 -12
- package/src/runtime/facades/plan-facade.ts +7 -2
- package/src/runtime/facades/tier-facade.test.ts +47 -0
- package/src/runtime/facades/tier-facade.ts +11 -0
- package/src/runtime/facades/vault-facade.test.ts +174 -242
- package/src/runtime/facades/vault-facade.ts +55 -199
- package/src/runtime/github-integration.ts +11 -8
- package/src/runtime/grading-ops.test.ts +39 -8
- package/src/runtime/intake-ops.test.ts +69 -16
- package/src/runtime/loop-ops.test.ts +16 -6
- package/src/runtime/memory-cross-project-ops.test.ts +25 -14
- package/src/runtime/orchestrate-ops.test.ts +204 -0
- package/src/runtime/orchestrate-ops.ts +103 -65
- package/src/runtime/pack-ops.test.ts +23 -6
- package/src/runtime/planning-extra-ops.test.ts +17 -7
- package/src/runtime/planning-extra-ops.ts +3 -1
- package/src/runtime/playbook-ops.test.ts +26 -3
- package/src/runtime/plugin-ops.test.ts +83 -25
- package/src/runtime/project-ops.test.ts +26 -6
- package/src/runtime/runtime.ts +3 -1
- package/src/runtime/session-briefing.test.ts +183 -54
- package/src/runtime/session-briefing.ts +8 -2
- package/src/runtime/sync-ops.test.ts +3 -12
- package/src/runtime/telemetry-ops.test.ts +31 -6
- package/src/runtime/tier-ops.test.ts +159 -0
- package/src/runtime/tier-ops.ts +119 -0
- package/src/runtime/vault-extra-ops.test.ts +32 -8
- package/src/runtime/vault-sharing-ops.test.ts +1 -4
- package/src/skills/sync-skills.ts +2 -12
- package/src/transport/ws-server.test.ts +7 -4
- package/src/vault/__tests__/vault-characterization.test.ts +492 -81
- package/src/vault/linking.test.ts +50 -17
- package/src/vault/linking.ts +48 -7
- package/src/vault/obsidian-sync.test.ts +6 -3
- package/src/vault/scope-detector.test.ts +1 -3
- package/src/vault/vault-branching.test.ts +9 -7
- package/src/vault/vault-entries.ts +209 -65
- package/src/vault/vault-maintenance.ts +7 -12
- package/src/vault/vault-manager.test.ts +10 -10
- package/src/vault/vault-markdown-sync.ts +4 -1
- package/src/vault/vault-memories.ts +7 -7
- package/src/vault/vault-scaling.test.ts +5 -5
- package/src/vault/vault-schema.ts +72 -15
- package/src/vault/vault.ts +55 -9
- package/src/brain/strength-scorer.ts +0 -404
- package/src/engine/index.ts +0 -21
- package/src/persona/index.ts +0 -9
- package/src/vault/vault-interfaces.ts +0 -56
package/src/llm/utils.test.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
CircuitBreaker,
|
|
4
|
+
CircuitOpenError,
|
|
5
|
+
computeDelay,
|
|
6
|
+
retry,
|
|
7
|
+
parseRateLimitHeaders,
|
|
8
|
+
} from './utils.js';
|
|
3
9
|
import { LLMError } from './types.js';
|
|
4
10
|
|
|
5
11
|
// ─── CircuitOpenError ───────────────────────────────────────────────
|
|
@@ -46,7 +52,11 @@ describe('CircuitBreaker', () => {
|
|
|
46
52
|
|
|
47
53
|
it('should rethrow errors from the wrapped function', async () => {
|
|
48
54
|
const cb = new CircuitBreaker({ name: 'test' });
|
|
49
|
-
await expect(
|
|
55
|
+
await expect(
|
|
56
|
+
cb.call(async () => {
|
|
57
|
+
throw new Error('boom');
|
|
58
|
+
}),
|
|
59
|
+
).rejects.toThrow('boom');
|
|
50
60
|
});
|
|
51
61
|
|
|
52
62
|
it('should not count non-retryable errors as failures', async () => {
|
|
@@ -54,7 +64,11 @@ describe('CircuitBreaker', () => {
|
|
|
54
64
|
const nonRetryable = new Error('not retryable');
|
|
55
65
|
|
|
56
66
|
for (let i = 0; i < 5; i++) {
|
|
57
|
-
await expect(
|
|
67
|
+
await expect(
|
|
68
|
+
cb.call(async () => {
|
|
69
|
+
throw nonRetryable;
|
|
70
|
+
}),
|
|
71
|
+
).rejects.toThrow();
|
|
58
72
|
}
|
|
59
73
|
expect(cb.getState().state).toBe('closed');
|
|
60
74
|
});
|
|
@@ -63,8 +77,16 @@ describe('CircuitBreaker', () => {
|
|
|
63
77
|
const cb = new CircuitBreaker({ name: 'test', failureThreshold: 2 });
|
|
64
78
|
const retryableErr = new LLMError('rate limited', { retryable: true });
|
|
65
79
|
|
|
66
|
-
await expect(
|
|
67
|
-
|
|
80
|
+
await expect(
|
|
81
|
+
cb.call(async () => {
|
|
82
|
+
throw retryableErr;
|
|
83
|
+
}),
|
|
84
|
+
).rejects.toThrow();
|
|
85
|
+
await expect(
|
|
86
|
+
cb.call(async () => {
|
|
87
|
+
throw retryableErr;
|
|
88
|
+
}),
|
|
89
|
+
).rejects.toThrow();
|
|
68
90
|
expect(cb.isOpen()).toBe(true);
|
|
69
91
|
});
|
|
70
92
|
|
|
@@ -72,7 +94,11 @@ describe('CircuitBreaker', () => {
|
|
|
72
94
|
const cb = new CircuitBreaker({ name: 'test', failureThreshold: 1, resetTimeoutMs: 999_999 });
|
|
73
95
|
const retryableErr = new LLMError('fail', { retryable: true });
|
|
74
96
|
|
|
75
|
-
await expect(
|
|
97
|
+
await expect(
|
|
98
|
+
cb.call(async () => {
|
|
99
|
+
throw retryableErr;
|
|
100
|
+
}),
|
|
101
|
+
).rejects.toThrow();
|
|
76
102
|
await expect(cb.call(async () => 'ok')).rejects.toThrow(CircuitOpenError);
|
|
77
103
|
});
|
|
78
104
|
|
|
@@ -80,7 +106,11 @@ describe('CircuitBreaker', () => {
|
|
|
80
106
|
const cb = new CircuitBreaker({ name: 'test', failureThreshold: 1, resetTimeoutMs: 10 });
|
|
81
107
|
const retryableErr = new LLMError('fail', { retryable: true });
|
|
82
108
|
|
|
83
|
-
await expect(
|
|
109
|
+
await expect(
|
|
110
|
+
cb.call(async () => {
|
|
111
|
+
throw retryableErr;
|
|
112
|
+
}),
|
|
113
|
+
).rejects.toThrow();
|
|
84
114
|
expect(cb.isOpen()).toBe(true);
|
|
85
115
|
|
|
86
116
|
// Wait for reset timeout
|
|
@@ -93,7 +123,11 @@ describe('CircuitBreaker', () => {
|
|
|
93
123
|
const cb = new CircuitBreaker({ name: 'test', failureThreshold: 1, resetTimeoutMs: 10 });
|
|
94
124
|
const retryableErr = new LLMError('fail', { retryable: true });
|
|
95
125
|
|
|
96
|
-
await expect(
|
|
126
|
+
await expect(
|
|
127
|
+
cb.call(async () => {
|
|
128
|
+
throw retryableErr;
|
|
129
|
+
}),
|
|
130
|
+
).rejects.toThrow();
|
|
97
131
|
await new Promise((r) => setTimeout(r, 20));
|
|
98
132
|
|
|
99
133
|
const result = await cb.call(async () => 'recovered');
|
|
@@ -105,10 +139,18 @@ describe('CircuitBreaker', () => {
|
|
|
105
139
|
const cb = new CircuitBreaker({ name: 'test', failureThreshold: 1, resetTimeoutMs: 10 });
|
|
106
140
|
const retryableErr = new LLMError('fail', { retryable: true });
|
|
107
141
|
|
|
108
|
-
await expect(
|
|
142
|
+
await expect(
|
|
143
|
+
cb.call(async () => {
|
|
144
|
+
throw retryableErr;
|
|
145
|
+
}),
|
|
146
|
+
).rejects.toThrow();
|
|
109
147
|
await new Promise((r) => setTimeout(r, 20));
|
|
110
148
|
|
|
111
|
-
await expect(
|
|
149
|
+
await expect(
|
|
150
|
+
cb.call(async () => {
|
|
151
|
+
throw retryableErr;
|
|
152
|
+
}),
|
|
153
|
+
).rejects.toThrow();
|
|
112
154
|
expect(cb.getState().state).toBe('open');
|
|
113
155
|
});
|
|
114
156
|
|
|
@@ -116,7 +158,11 @@ describe('CircuitBreaker', () => {
|
|
|
116
158
|
const cb = new CircuitBreaker({ name: 'test', failureThreshold: 1, resetTimeoutMs: 999_999 });
|
|
117
159
|
const retryableErr = new LLMError('fail', { retryable: true });
|
|
118
160
|
|
|
119
|
-
await expect(
|
|
161
|
+
await expect(
|
|
162
|
+
cb.call(async () => {
|
|
163
|
+
throw retryableErr;
|
|
164
|
+
}),
|
|
165
|
+
).rejects.toThrow();
|
|
120
166
|
expect(cb.isOpen()).toBe(true);
|
|
121
167
|
|
|
122
168
|
cb.reset();
|
|
@@ -204,7 +250,9 @@ describe('retry', () => {
|
|
|
204
250
|
|
|
205
251
|
it('should throw if maxAttempts < 1', async () => {
|
|
206
252
|
vi.useRealTimers();
|
|
207
|
-
await expect(retry(async () => 'ok', { maxAttempts: 0 })).rejects.toThrow(
|
|
253
|
+
await expect(retry(async () => 'ok', { maxAttempts: 0 })).rejects.toThrow(
|
|
254
|
+
'maxAttempts must be >= 1',
|
|
255
|
+
);
|
|
208
256
|
});
|
|
209
257
|
|
|
210
258
|
it('should throw non-retryable errors immediately', async () => {
|
|
@@ -227,9 +275,7 @@ describe('retry', () => {
|
|
|
227
275
|
it('should succeed on retry after initial failures', async () => {
|
|
228
276
|
vi.useRealTimers();
|
|
229
277
|
const retryableErr = new LLMError('transient', { retryable: true });
|
|
230
|
-
const fn = vi.fn()
|
|
231
|
-
.mockRejectedValueOnce(retryableErr)
|
|
232
|
-
.mockResolvedValueOnce('recovered');
|
|
278
|
+
const fn = vi.fn().mockRejectedValueOnce(retryableErr).mockResolvedValueOnce('recovered');
|
|
233
279
|
|
|
234
280
|
const result = await retry(fn, { maxAttempts: 3, baseDelayMs: 1, maxDelayMs: 1, jitter: 0 });
|
|
235
281
|
expect(result).toBe('recovered');
|
|
@@ -240,9 +286,7 @@ describe('retry', () => {
|
|
|
240
286
|
vi.useRealTimers();
|
|
241
287
|
const retryableErr = new LLMError('transient', { retryable: true });
|
|
242
288
|
const onRetry = vi.fn();
|
|
243
|
-
const fn = vi.fn()
|
|
244
|
-
.mockRejectedValueOnce(retryableErr)
|
|
245
|
-
.mockResolvedValueOnce('ok');
|
|
289
|
+
const fn = vi.fn().mockRejectedValueOnce(retryableErr).mockResolvedValueOnce('ok');
|
|
246
290
|
|
|
247
291
|
await retry(fn, { maxAttempts: 3, baseDelayMs: 1, maxDelayMs: 1, jitter: 0, onRetry });
|
|
248
292
|
expect(onRetry).toHaveBeenCalledTimes(1);
|
|
@@ -103,7 +103,10 @@ describe('Logger file logging', () => {
|
|
|
103
103
|
beforeEach(() => {
|
|
104
104
|
vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
105
105
|
vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
106
|
-
tempDir = join(
|
|
106
|
+
tempDir = join(
|
|
107
|
+
tmpdir(),
|
|
108
|
+
`logger-colocated-${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
|
109
|
+
);
|
|
107
110
|
mkdirSync(tempDir, { recursive: true });
|
|
108
111
|
});
|
|
109
112
|
|
|
@@ -34,9 +34,7 @@ describe('extractPromise', () => {
|
|
|
34
34
|
});
|
|
35
35
|
|
|
36
36
|
it('handles multiline content inside tags', () => {
|
|
37
|
-
expect(extractPromise('<promise>\nSALVADOR_VALIDATED\n</promise>')).toBe(
|
|
38
|
-
'SALVADOR_VALIDATED',
|
|
39
|
-
);
|
|
37
|
+
expect(extractPromise('<promise>\nSALVADOR_VALIDATED\n</promise>')).toBe('SALVADOR_VALIDATED');
|
|
40
38
|
});
|
|
41
39
|
|
|
42
40
|
it('extracts only the first match', () => {
|
|
@@ -349,9 +347,7 @@ describe('LoopManager', () => {
|
|
|
349
347
|
|
|
350
348
|
it('detects completion promise and ends loop', () => {
|
|
351
349
|
mgr.startLoop(makeConfig({ completionPromise: 'SALVADOR_VALIDATED' }));
|
|
352
|
-
const result = mgr.iterateWithGate(
|
|
353
|
-
'Done! <promise>SALVADOR_VALIDATED</promise>',
|
|
354
|
-
);
|
|
350
|
+
const result = mgr.iterateWithGate('Done! <promise>SALVADOR_VALIDATED</promise>');
|
|
355
351
|
|
|
356
352
|
expect(result.decision).toBe('allow');
|
|
357
353
|
expect(result.outcome).toBe('completed');
|
|
@@ -8,9 +8,7 @@
|
|
|
8
8
|
import { describe, test, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
9
9
|
import Database from 'better-sqlite3';
|
|
10
10
|
import { MigrationRunner } from './migration-runner.js';
|
|
11
|
-
import {
|
|
12
|
-
createMigration,
|
|
13
|
-
} from './migration-runner.test-helpers.js';
|
|
11
|
+
import { createMigration } from './migration-runner.test-helpers.js';
|
|
14
12
|
|
|
15
13
|
describe('MigrationRunner — edge cases', () => {
|
|
16
14
|
let db: Database.Database;
|
|
@@ -302,10 +300,7 @@ describe('MigrationRunner — edge cases', () => {
|
|
|
302
300
|
test('getPending consistent across runners sharing same db', () => {
|
|
303
301
|
// Arrange
|
|
304
302
|
const runner2 = new MigrationRunner(db);
|
|
305
|
-
const all = [
|
|
306
|
-
createMigration({ version: '1.0.0' }),
|
|
307
|
-
createMigration({ version: '2.0.0' }),
|
|
308
|
-
];
|
|
303
|
+
const all = [createMigration({ version: '1.0.0' }), createMigration({ version: '2.0.0' })];
|
|
309
304
|
runner.run([all[0]]);
|
|
310
305
|
|
|
311
306
|
// Act
|
|
@@ -61,7 +61,9 @@ describe('OperatorProfileStore (extended)', () => {
|
|
|
61
61
|
|
|
62
62
|
describe('parallel section updates', () => {
|
|
63
63
|
it('two concurrent updateSection for different sections both succeed', () => {
|
|
64
|
-
store.accumulateSignals([
|
|
64
|
+
store.accumulateSignals([
|
|
65
|
+
makeSignal(SignalType.CommandStyle, { style: 'terse', snippet: 'x' }),
|
|
66
|
+
]);
|
|
65
67
|
|
|
66
68
|
const commData: CommunicationSection = {
|
|
67
69
|
style: 'formal',
|
|
@@ -88,7 +90,9 @@ describe('OperatorProfileStore (extended)', () => {
|
|
|
88
90
|
});
|
|
89
91
|
|
|
90
92
|
it('updating one section does not overwrite another', () => {
|
|
91
|
-
store.accumulateSignals([
|
|
93
|
+
store.accumulateSignals([
|
|
94
|
+
makeSignal(SignalType.CommandStyle, { style: 'terse', snippet: 'x' }),
|
|
95
|
+
]);
|
|
92
96
|
|
|
93
97
|
const identity: IdentitySection = {
|
|
94
98
|
background: 'Senior engineer',
|
|
@@ -116,7 +120,9 @@ describe('OperatorProfileStore (extended)', () => {
|
|
|
116
120
|
|
|
117
121
|
describe('profile snapshot', () => {
|
|
118
122
|
it('creates history entry with correct version increment', () => {
|
|
119
|
-
store.accumulateSignals([
|
|
123
|
+
store.accumulateSignals([
|
|
124
|
+
makeSignal(SignalType.CommandStyle, { style: 'terse', snippet: 'x' }),
|
|
125
|
+
]);
|
|
120
126
|
const profileBefore = store.getProfile();
|
|
121
127
|
expect(profileBefore!.version).toBe(0);
|
|
122
128
|
|
|
@@ -197,7 +203,9 @@ describe('OperatorProfileStore (extended)', () => {
|
|
|
197
203
|
});
|
|
198
204
|
|
|
199
205
|
it('lastSynthesis updates after snapshot', () => {
|
|
200
|
-
store.accumulateSignals([
|
|
206
|
+
store.accumulateSignals([
|
|
207
|
+
makeSignal(SignalType.CommandStyle, { style: 'terse', snippet: 'x' }),
|
|
208
|
+
]);
|
|
201
209
|
store.snapshot('synthesis');
|
|
202
210
|
const stats = store.signalStats();
|
|
203
211
|
expect(stats.lastSynthesis).not.toBeNull();
|
|
@@ -273,7 +281,9 @@ describe('OperatorProfileStore (extended)', () => {
|
|
|
273
281
|
|
|
274
282
|
describe('delete profile archives to history', () => {
|
|
275
283
|
it('archives profile snapshot before deletion', () => {
|
|
276
|
-
store.accumulateSignals([
|
|
284
|
+
store.accumulateSignals([
|
|
285
|
+
makeSignal(SignalType.CommandStyle, { style: 'terse', snippet: 'x' }),
|
|
286
|
+
]);
|
|
277
287
|
const identity: IdentitySection = {
|
|
278
288
|
background: 'Architect',
|
|
279
289
|
role: 'Principal',
|
|
@@ -2,7 +2,11 @@ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
|
2
2
|
import { Vault } from '../vault/vault.js';
|
|
3
3
|
import { OperatorProfileStore } from './operator-profile.js';
|
|
4
4
|
import { SignalType } from './operator-types.js';
|
|
5
|
-
import type {
|
|
5
|
+
import type {
|
|
6
|
+
OperatorSignal,
|
|
7
|
+
CommunicationSection,
|
|
8
|
+
TechnicalContextSection,
|
|
9
|
+
} from './operator-types.js';
|
|
6
10
|
|
|
7
11
|
// ─── Helpers ──────────────────────────────────────────────────────────
|
|
8
12
|
|
|
@@ -83,7 +87,9 @@ describe('OperatorProfileStore', () => {
|
|
|
83
87
|
});
|
|
84
88
|
|
|
85
89
|
it('auto-creates profile when accumulating signals', () => {
|
|
86
|
-
store.accumulateSignals([
|
|
90
|
+
store.accumulateSignals([
|
|
91
|
+
makeSignal(SignalType.WorkRhythm, { pattern: 'burst', durationMinutes: 30, taskCount: 5 }),
|
|
92
|
+
]);
|
|
87
93
|
const profile = store.getProfile();
|
|
88
94
|
expect(profile).not.toBeNull();
|
|
89
95
|
expect(profile!.sessionCount).toBe(0);
|
|
@@ -127,7 +133,9 @@ describe('OperatorProfileStore', () => {
|
|
|
127
133
|
|
|
128
134
|
it('parallel updateSection on different sections both succeed', () => {
|
|
129
135
|
// Ensure profile exists
|
|
130
|
-
store.accumulateSignals([
|
|
136
|
+
store.accumulateSignals([
|
|
137
|
+
makeSignal(SignalType.CommandStyle, { style: 'terse', snippet: 'x' }),
|
|
138
|
+
]);
|
|
131
139
|
|
|
132
140
|
const commData: CommunicationSection = {
|
|
133
141
|
style: 'concise',
|
|
@@ -157,7 +165,9 @@ describe('OperatorProfileStore', () => {
|
|
|
157
165
|
// ─── correctSection ─────────────────────────────────────────────
|
|
158
166
|
|
|
159
167
|
it('correctSection records history with correction trigger', () => {
|
|
160
|
-
store.accumulateSignals([
|
|
168
|
+
store.accumulateSignals([
|
|
169
|
+
makeSignal(SignalType.CommandStyle, { style: 'terse', snippet: 'x' }),
|
|
170
|
+
]);
|
|
161
171
|
const profileBefore = store.getProfile();
|
|
162
172
|
expect(profileBefore).not.toBeNull();
|
|
163
173
|
|
|
@@ -183,7 +193,9 @@ describe('OperatorProfileStore', () => {
|
|
|
183
193
|
// ─── snapshot ───────────────────────────────────────────────────
|
|
184
194
|
|
|
185
195
|
it('creates history row with full profile JSON', () => {
|
|
186
|
-
store.accumulateSignals([
|
|
196
|
+
store.accumulateSignals([
|
|
197
|
+
makeSignal(SignalType.CommandStyle, { style: 'terse', snippet: 'x' }),
|
|
198
|
+
]);
|
|
187
199
|
const profile = store.getProfile();
|
|
188
200
|
expect(profile).not.toBeNull();
|
|
189
201
|
|
|
@@ -205,7 +217,9 @@ describe('OperatorProfileStore', () => {
|
|
|
205
217
|
});
|
|
206
218
|
|
|
207
219
|
it('increments synthesis_version on snapshot', () => {
|
|
208
|
-
store.accumulateSignals([
|
|
220
|
+
store.accumulateSignals([
|
|
221
|
+
makeSignal(SignalType.CommandStyle, { style: 'terse', snippet: 'x' }),
|
|
222
|
+
]);
|
|
209
223
|
|
|
210
224
|
store.snapshot('synthesis');
|
|
211
225
|
store.snapshot('synthesis');
|
|
@@ -219,7 +233,9 @@ describe('OperatorProfileStore', () => {
|
|
|
219
233
|
// ─── deleteProfile ──────────────────────────────────────────────
|
|
220
234
|
|
|
221
235
|
it('archives to history before deletion', () => {
|
|
222
|
-
store.accumulateSignals([
|
|
236
|
+
store.accumulateSignals([
|
|
237
|
+
makeSignal(SignalType.CommandStyle, { style: 'terse', snippet: 'x' }),
|
|
238
|
+
]);
|
|
223
239
|
const profile = store.getProfile();
|
|
224
240
|
expect(profile).not.toBeNull();
|
|
225
241
|
const profileId = profile!.id;
|
|
@@ -287,7 +303,9 @@ describe('OperatorProfileStore', () => {
|
|
|
287
303
|
});
|
|
288
304
|
|
|
289
305
|
it('returns section data after update', () => {
|
|
290
|
-
store.accumulateSignals([
|
|
306
|
+
store.accumulateSignals([
|
|
307
|
+
makeSignal(SignalType.CommandStyle, { style: 'terse', snippet: 'x' }),
|
|
308
|
+
]);
|
|
291
309
|
const commData: CommunicationSection = {
|
|
292
310
|
style: 'detailed',
|
|
293
311
|
signalWords: ['please'],
|
|
@@ -165,11 +165,7 @@ export class OperatorProfileStore {
|
|
|
165
165
|
return JSON.parse((row[col] as string) || '{}') as ProfileSection;
|
|
166
166
|
}
|
|
167
167
|
|
|
168
|
-
updateSection(
|
|
169
|
-
section: ProfileSectionKey,
|
|
170
|
-
data: ProfileSection,
|
|
171
|
-
profileId?: string,
|
|
172
|
-
): boolean {
|
|
168
|
+
updateSection(section: ProfileSectionKey, data: ProfileSection, profileId?: string): boolean {
|
|
173
169
|
const id = profileId ?? this.ensureProfile();
|
|
174
170
|
const col = SECTION_COLUMNS[section];
|
|
175
171
|
const result = this.provider.run(
|
|
@@ -179,11 +175,7 @@ export class OperatorProfileStore {
|
|
|
179
175
|
return result.changes > 0;
|
|
180
176
|
}
|
|
181
177
|
|
|
182
|
-
correctSection(
|
|
183
|
-
section: ProfileSectionKey,
|
|
184
|
-
data: ProfileSection,
|
|
185
|
-
profileId?: string,
|
|
186
|
-
): boolean {
|
|
178
|
+
correctSection(section: ProfileSectionKey, data: ProfileSection, profileId?: string): boolean {
|
|
187
179
|
const id = profileId ?? this.ensureProfile();
|
|
188
180
|
this.snapshot('correction', id);
|
|
189
181
|
return this.updateSection(section, data, id);
|
|
@@ -210,7 +202,14 @@ export class OperatorProfileStore {
|
|
|
210
202
|
this.provider.run(
|
|
211
203
|
`INSERT INTO operator_signals (profile_id, signal_type, signal_data, source, confidence, created_at)
|
|
212
204
|
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
213
|
-
[
|
|
205
|
+
[
|
|
206
|
+
id,
|
|
207
|
+
signal.signalType,
|
|
208
|
+
JSON.stringify(signal.data),
|
|
209
|
+
signal.source ?? null,
|
|
210
|
+
signal.confidence,
|
|
211
|
+
signal.timestamp,
|
|
212
|
+
],
|
|
214
213
|
);
|
|
215
214
|
inserted++;
|
|
216
215
|
}
|
|
@@ -225,7 +224,15 @@ export class OperatorProfileStore {
|
|
|
225
224
|
listSignals(
|
|
226
225
|
filter: { types?: string[]; processed?: boolean; limit?: number } = {},
|
|
227
226
|
profileId?: string,
|
|
228
|
-
): Array<{
|
|
227
|
+
): Array<{
|
|
228
|
+
id: number;
|
|
229
|
+
signalType: string;
|
|
230
|
+
signalData: unknown;
|
|
231
|
+
source: string | null;
|
|
232
|
+
confidence: number;
|
|
233
|
+
processed: boolean;
|
|
234
|
+
createdAt: string;
|
|
235
|
+
}> {
|
|
229
236
|
const id = profileId ?? this.getDefaultProfileId();
|
|
230
237
|
if (!id) return [];
|
|
231
238
|
const conditions = ['profile_id = ?'];
|
|
@@ -296,7 +303,11 @@ export class OperatorProfileStore {
|
|
|
296
303
|
};
|
|
297
304
|
}
|
|
298
305
|
const stats = this.signalStats(id);
|
|
299
|
-
const profile = this.provider.get<{
|
|
306
|
+
const profile = this.provider.get<{
|
|
307
|
+
session_count: number;
|
|
308
|
+
last_synthesis: string | null;
|
|
309
|
+
synthesis_version: number;
|
|
310
|
+
}>(
|
|
300
311
|
'SELECT session_count, last_synthesis, synthesis_version FROM operator_profiles WHERE id = ?',
|
|
301
312
|
[id],
|
|
302
313
|
);
|
|
@@ -311,14 +322,14 @@ export class OperatorProfileStore {
|
|
|
311
322
|
}
|
|
312
323
|
|
|
313
324
|
const pending = stats.totalUnprocessed;
|
|
314
|
-
const sessionsSinceSynthesis =
|
|
315
|
-
? profile.session_count
|
|
316
|
-
: profile.session_count; // tracked by incrementing session_count
|
|
325
|
+
const sessionsSinceSynthesis =
|
|
326
|
+
profile.synthesis_version === 0 ? profile.session_count : profile.session_count; // tracked by incrementing session_count
|
|
317
327
|
|
|
318
328
|
const signalThresholdMet = pending >= SYNTHESIS_SIGNAL_THRESHOLD;
|
|
319
|
-
const sessionThresholdMet =
|
|
320
|
-
|
|
321
|
-
|
|
329
|
+
const sessionThresholdMet =
|
|
330
|
+
sessionsSinceSynthesis >= SYNTHESIS_SESSION_THRESHOLD && profile.synthesis_version > 0
|
|
331
|
+
? false // sessions threshold only meaningful after first synthesis
|
|
332
|
+
: sessionsSinceSynthesis >= SYNTHESIS_SESSION_THRESHOLD;
|
|
322
333
|
|
|
323
334
|
const due = signalThresholdMet || sessionThresholdMet;
|
|
324
335
|
|
|
@@ -332,8 +343,12 @@ export class OperatorProfileStore {
|
|
|
332
343
|
}
|
|
333
344
|
|
|
334
345
|
const reasons: string[] = [];
|
|
335
|
-
if (signalThresholdMet)
|
|
336
|
-
|
|
346
|
+
if (signalThresholdMet)
|
|
347
|
+
reasons.push(`${pending} unprocessed signals (threshold: ${SYNTHESIS_SIGNAL_THRESHOLD})`);
|
|
348
|
+
if (sessionThresholdMet)
|
|
349
|
+
reasons.push(
|
|
350
|
+
`${sessionsSinceSynthesis} sessions since last synthesis (threshold: ${SYNTHESIS_SESSION_THRESHOLD})`,
|
|
351
|
+
);
|
|
337
352
|
if (!due) reasons.push('Below all thresholds');
|
|
338
353
|
|
|
339
354
|
return {
|
|
@@ -385,7 +400,8 @@ export class OperatorProfileStore {
|
|
|
385
400
|
`INSERT INTO operator_profiles (id, agent_id, identity, cognition, communication, working_rules, trust_model, taste_profile, growth_edges, technical_context)
|
|
386
401
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
387
402
|
[
|
|
388
|
-
id,
|
|
403
|
+
id,
|
|
404
|
+
agentId,
|
|
389
405
|
JSON.stringify(profile.identity),
|
|
390
406
|
JSON.stringify(profile.cognition),
|
|
391
407
|
JSON.stringify(profile.communication),
|
|
@@ -106,12 +106,14 @@ describe('operator-signals (extended)', () => {
|
|
|
106
106
|
|
|
107
107
|
describe('edge cases', () => {
|
|
108
108
|
it('empty session data — null fields produce minimal signals', () => {
|
|
109
|
-
const signals = extractFromSession(
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
109
|
+
const signals = extractFromSession(
|
|
110
|
+
makeSession({
|
|
111
|
+
intent: null as unknown as string,
|
|
112
|
+
toolsUsed: null as unknown as string[],
|
|
113
|
+
filesModified: null as unknown as string[],
|
|
114
|
+
decisions: null as unknown as string[],
|
|
115
|
+
}),
|
|
116
|
+
);
|
|
115
117
|
// Should still get work_rhythm and session_depth
|
|
116
118
|
expect(signals.some((s) => s.signalType === 'work_rhythm')).toBe(true);
|
|
117
119
|
expect(signals.some((s) => s.signalType === 'session_depth')).toBe(true);
|
|
@@ -139,10 +141,12 @@ describe('operator-signals (extended)', () => {
|
|
|
139
141
|
});
|
|
140
142
|
|
|
141
143
|
it('empty filesModified and decisions produces shallow depth', () => {
|
|
142
|
-
const signals = extractFromSession(
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
144
|
+
const signals = extractFromSession(
|
|
145
|
+
makeSession({
|
|
146
|
+
filesModified: [],
|
|
147
|
+
decisions: [],
|
|
148
|
+
}),
|
|
149
|
+
);
|
|
146
150
|
const sd = signals.find((s) => s.signalType === 'session_depth');
|
|
147
151
|
expect(sd).toBeDefined();
|
|
148
152
|
expect((sd!.data as { depth: string }).depth).toBe('shallow');
|
|
@@ -211,7 +215,9 @@ describe('operator-signals (extended)', () => {
|
|
|
211
215
|
});
|
|
212
216
|
|
|
213
217
|
it('returns empty array for completely unknown type', () => {
|
|
214
|
-
expect(extractFromRadar(makeRadarCandidate({ signalType: 'alien_signal' as never }))).toEqual(
|
|
218
|
+
expect(extractFromRadar(makeRadarCandidate({ signalType: 'alien_signal' as never }))).toEqual(
|
|
219
|
+
[],
|
|
220
|
+
);
|
|
215
221
|
});
|
|
216
222
|
});
|
|
217
223
|
|
|
@@ -219,26 +225,32 @@ describe('operator-signals (extended)', () => {
|
|
|
219
225
|
|
|
220
226
|
describe('extractFromRadar frustration level mapping', () => {
|
|
221
227
|
it('high confidence (>=0.7) → high frustration', () => {
|
|
222
|
-
const signals = extractFromRadar(
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
228
|
+
const signals = extractFromRadar(
|
|
229
|
+
makeRadarCandidate({
|
|
230
|
+
signalType: 'repeated_question',
|
|
231
|
+
confidence: 0.75,
|
|
232
|
+
}),
|
|
233
|
+
);
|
|
226
234
|
expect((signals[0].data as { level: string }).level).toBe('high');
|
|
227
235
|
});
|
|
228
236
|
|
|
229
237
|
it('medium confidence (0.5-0.7) → moderate frustration', () => {
|
|
230
|
-
const signals = extractFromRadar(
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
238
|
+
const signals = extractFromRadar(
|
|
239
|
+
makeRadarCandidate({
|
|
240
|
+
signalType: 'repeated_question',
|
|
241
|
+
confidence: 0.55,
|
|
242
|
+
}),
|
|
243
|
+
);
|
|
234
244
|
expect((signals[0].data as { level: string }).level).toBe('moderate');
|
|
235
245
|
});
|
|
236
246
|
|
|
237
247
|
it('low confidence (<0.5) → mild frustration', () => {
|
|
238
|
-
const signals = extractFromRadar(
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
248
|
+
const signals = extractFromRadar(
|
|
249
|
+
makeRadarCandidate({
|
|
250
|
+
signalType: 'repeated_question',
|
|
251
|
+
confidence: 0.3,
|
|
252
|
+
}),
|
|
253
|
+
);
|
|
242
254
|
expect((signals[0].data as { level: string }).level).toBe('mild');
|
|
243
255
|
});
|
|
244
256
|
});
|
|
@@ -123,18 +123,14 @@ describe('operator-signals', () => {
|
|
|
123
123
|
});
|
|
124
124
|
|
|
125
125
|
it('returns shallow depth for minimal session', () => {
|
|
126
|
-
const signals = extractFromSession(
|
|
127
|
-
makeSession({ filesModified: [], decisions: [] }),
|
|
128
|
-
);
|
|
126
|
+
const signals = extractFromSession(makeSession({ filesModified: [], decisions: [] }));
|
|
129
127
|
const sd = signals.find((s) => s.signalType === 'session_depth');
|
|
130
128
|
expect(sd).toBeDefined();
|
|
131
129
|
expect((sd!.data as { depth: string }).depth).toBe('shallow');
|
|
132
130
|
});
|
|
133
131
|
|
|
134
132
|
it('handles null toolsUsed gracefully — returns signals for other fields', () => {
|
|
135
|
-
const signals = extractFromSession(
|
|
136
|
-
makeSession({ toolsUsed: null as unknown as string[] }),
|
|
137
|
-
);
|
|
133
|
+
const signals = extractFromSession(makeSession({ toolsUsed: null as unknown as string[] }));
|
|
138
134
|
// Should still have command_style, work_rhythm, session_depth
|
|
139
135
|
expect(signals.some((s) => s.signalType === 'command_style')).toBe(true);
|
|
140
136
|
expect(signals.some((s) => s.signalType === 'work_rhythm')).toBe(true);
|
|
@@ -144,9 +140,7 @@ describe('operator-signals', () => {
|
|
|
144
140
|
});
|
|
145
141
|
|
|
146
142
|
it('handles null intent gracefully — skips command_style', () => {
|
|
147
|
-
const signals = extractFromSession(
|
|
148
|
-
makeSession({ intent: null as unknown as string }),
|
|
149
|
-
);
|
|
143
|
+
const signals = extractFromSession(makeSession({ intent: null as unknown as string }));
|
|
150
144
|
expect(signals.some((s) => s.signalType === 'command_style')).toBe(false);
|
|
151
145
|
expect(signals.some((s) => s.signalType === 'work_rhythm')).toBe(true);
|
|
152
146
|
});
|
|
@@ -208,7 +202,9 @@ describe('operator-signals', () => {
|
|
|
208
202
|
|
|
209
203
|
describe('extractFromBrainStrengths', () => {
|
|
210
204
|
it('creates domain_expertise for strengths with score > 0.6', () => {
|
|
211
|
-
const strengths = [
|
|
205
|
+
const strengths = [
|
|
206
|
+
makeStrength({ pattern: 'typescript-strict', domain: 'typescript', strength: 0.8 }),
|
|
207
|
+
];
|
|
212
208
|
const signals = extractFromBrainStrengths(strengths);
|
|
213
209
|
expect(signals.length).toBe(1);
|
|
214
210
|
expect(signals[0].signalType).toBe('domain_expertise');
|
|
@@ -221,7 +221,8 @@ export function extractFromRadar(candidate: RadarCandidate): OperatorSignal[] {
|
|
|
221
221
|
signalType: SignalType.Frustration,
|
|
222
222
|
source: 'learning_radar',
|
|
223
223
|
data: {
|
|
224
|
-
level:
|
|
224
|
+
level:
|
|
225
|
+
candidate.confidence >= 0.7 ? 'high' : candidate.confidence >= 0.5 ? 'moderate' : 'mild',
|
|
225
226
|
trigger: candidate.sourceQuery ?? candidate.title,
|
|
226
227
|
context: candidate.context ?? candidate.description,
|
|
227
228
|
},
|