@unerr-ai/unerr 0.1.6 → 0.1.7
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/README.md +70 -194
- package/dist/cli.js +39149 -36991
- package/package.json +9 -2
- package/dist/__tests__/architecture-guard.test.js +0 -122
- package/dist/__tests__/arg-validator.test.js +0 -205
- package/dist/__tests__/ast-extractor.test.js +0 -203
- package/dist/__tests__/auto-bootstrap.test.js +0 -280
- package/dist/__tests__/background-indexer.test.js +0 -228
- package/dist/__tests__/blast-radius-engine.test.js +0 -200
- package/dist/__tests__/bridge-isolation.test.js +0 -37
- package/dist/__tests__/budget-enforcer.test.js +0 -53
- package/dist/__tests__/cfg-test-detection-perf.test.js +0 -82
- package/dist/__tests__/change-narrative.test.js +0 -190
- package/dist/__tests__/check-commit.test.js +0 -258
- package/dist/__tests__/checksum.test.js +0 -34
- package/dist/__tests__/commit-watcher.test.js +0 -154
- package/dist/__tests__/community-detection.test.js +0 -179
- package/dist/__tests__/community-tools.test.js +0 -299
- package/dist/__tests__/components.test.js +0 -449
- package/dist/__tests__/compression-log.test.js +0 -174
- package/dist/__tests__/compression-quality-monitor.test.js +0 -40
- package/dist/__tests__/config-healer.test.js +0 -165
- package/dist/__tests__/context-ledger.test.js +0 -58
- package/dist/__tests__/convention-detector.test.js +0 -99
- package/dist/__tests__/convention-learner.test.js +0 -86
- package/dist/__tests__/correction-detector.test.js +0 -330
- package/dist/__tests__/daemon-autostart-install.test.js +0 -283
- package/dist/__tests__/daemon-bridge.test.js +0 -222
- package/dist/__tests__/daemon-dashboard.test.js +0 -202
- package/dist/__tests__/daemon-registry.test.js +0 -240
- package/dist/__tests__/daemon-supervisor.test.js +0 -318
- package/dist/__tests__/daemon-version-check.test.js +0 -275
- package/dist/__tests__/decision-point-detector.test.js +0 -98
- package/dist/__tests__/deep-link.test.js +0 -143
- package/dist/__tests__/disallowed-tools.test.js +0 -115
- package/dist/__tests__/drift-tracker.test.js +0 -582
- package/dist/__tests__/durability-scorer.test.js +0 -152
- package/dist/__tests__/efficiency-tracker.test.js +0 -65
- package/dist/__tests__/enrich.test.js +0 -144
- package/dist/__tests__/entity-rewind.test.js +0 -248
- package/dist/__tests__/ephemeral.test.js +0 -111
- package/dist/__tests__/exploration-cost.test.js +0 -93
- package/dist/__tests__/fact-generator.test.js +0 -197
- package/dist/__tests__/file-l0-graph.test.js +0 -244
- package/dist/__tests__/file-logger.test.js +0 -82
- package/dist/__tests__/file-outline.test.js +0 -141
- package/dist/__tests__/file-read-protocol.test.js +0 -188
- package/dist/__tests__/format-encoder.test.js +0 -233
- package/dist/__tests__/git-attribution.test.js +0 -259
- package/dist/__tests__/graph-temporal-joiner.test.js +0 -219
- package/dist/__tests__/health-grade-enhanced.test.js +0 -138
- package/dist/__tests__/health-map-data.test.js +0 -173
- package/dist/__tests__/helpers/mcp-harness.js +0 -45
- package/dist/__tests__/helpers/mcp-harness.test.js +0 -68
- package/dist/__tests__/hook-dedup.test.js +0 -112
- package/dist/__tests__/hook-runner.test.js +0 -253
- package/dist/__tests__/indexer-cfg.test.js +0 -185
- package/dist/__tests__/indexer-cross-file.test.js +0 -172
- package/dist/__tests__/indexer-extraction.test.js +0 -245
- package/dist/__tests__/indexer-incremental.test.js +0 -232
- package/dist/__tests__/indexer-language-expansion.test.js +0 -165
- package/dist/__tests__/init-push.test.js +0 -131
- package/dist/__tests__/instruction-writer.test.js +0 -179
- package/dist/__tests__/intelligence-integration.test.js +0 -217
- package/dist/__tests__/intent-correlator.test.js +0 -175
- package/dist/__tests__/intent-detector.test.js +0 -235
- package/dist/__tests__/intent-encoder.test.js +0 -167
- package/dist/__tests__/java-build-tool-detection.test.js +0 -174
- package/dist/__tests__/layer3-sprint-q.test.js +0 -160
- package/dist/__tests__/layer3-sprint-r.test.js +0 -91
- package/dist/__tests__/layer3-sprint-s.test.js +0 -183
- package/dist/__tests__/layer3-sprint-t.test.js +0 -201
- package/dist/__tests__/layer3-sprint-u.test.js +0 -174
- package/dist/__tests__/layer4-sprint-ba2.test.js +0 -354
- package/dist/__tests__/layer4-sprint-ba4.test.js +0 -84
- package/dist/__tests__/layer4-sprint-vs.test.js +0 -105
- package/dist/__tests__/ledger-chains.test.js +0 -162
- package/dist/__tests__/lifecycle-machine.test.js +0 -226
- package/dist/__tests__/local-chat-provider.test.js +0 -170
- package/dist/__tests__/local-convention-detector.test.js +0 -308
- package/dist/__tests__/local-embeddings.test.js +0 -422
- package/dist/__tests__/local-graph.test.js +0 -540
- package/dist/__tests__/local-indexer.test.js +0 -228
- package/dist/__tests__/local-intelligence-l3.test.js +0 -332
- package/dist/__tests__/local-llm.test.js +0 -253
- package/dist/__tests__/local-mode-offline.test.js +0 -187
- package/dist/__tests__/local-mode-stats.test.js +0 -273
- package/dist/__tests__/local-mode-tui.test.js +0 -343
- package/dist/__tests__/local-parse.test.js +0 -199
- package/dist/__tests__/log-tailer.test.js +0 -208
- package/dist/__tests__/loop-breaker.test.js +0 -276
- package/dist/__tests__/loop-miner.test.js +0 -226
- package/dist/__tests__/mcp-config.test.js +0 -126
- package/dist/__tests__/mcp-content-json.test.js +0 -10
- package/dist/__tests__/mcp-envelope.test.js +0 -124
- package/dist/__tests__/metrics-store.test.js +0 -223
- package/dist/__tests__/native-watcher.test.js +0 -191
- package/dist/__tests__/navigation-hooks-agent-aware.test.js +0 -145
- package/dist/__tests__/negative-knowledge.test.js +0 -116
- package/dist/__tests__/network-boundary.test.js +0 -190
- package/dist/__tests__/network-firewall.test.js +0 -112
- package/dist/__tests__/nudge-invariants.test.js +0 -160
- package/dist/__tests__/nudge-v2.test.js +0 -225
- package/dist/__tests__/offline-rewind.test.js +0 -251
- package/dist/__tests__/open-threads.test.js +0 -89
- package/dist/__tests__/output-compressor.test.js +0 -93
- package/dist/__tests__/pending-violations.test.js +0 -112
- package/dist/__tests__/persistence-effectiveness.test.js +0 -143
- package/dist/__tests__/provider-factory.test.js +0 -42
- package/dist/__tests__/providers.test.js +0 -24
- package/dist/__tests__/proxy.test.js +0 -314
- package/dist/__tests__/query-router.test.js +0 -1018
- package/dist/__tests__/reasoning-quality-route.test.js +0 -138
- package/dist/__tests__/redactor.test.js +0 -120
- package/dist/__tests__/resource-monitor.test.js +0 -57
- package/dist/__tests__/response-envelope.test.js +0 -100
- package/dist/__tests__/risk-classifier.test.js +0 -101
- package/dist/__tests__/risk-signal-scope.test.js +0 -75
- package/dist/__tests__/rule-evaluator.test.js +0 -280
- package/dist/__tests__/scip-decoder.test.js +0 -49
- package/dist/__tests__/scip-downloader.test.js +0 -201
- package/dist/__tests__/scip-merger.test.js +0 -103
- package/dist/__tests__/search-index.test.js +0 -422
- package/dist/__tests__/semantic-enrichment.test.js +0 -360
- package/dist/__tests__/session-brief-builder.test.js +0 -187
- package/dist/__tests__/session-context.test.js +0 -221
- package/dist/__tests__/session-continuity.test.js +0 -144
- package/dist/__tests__/session-dedup.test.js +0 -74
- package/dist/__tests__/session-event-wiring.test.js +0 -206
- package/dist/__tests__/session-events.test.js +0 -149
- package/dist/__tests__/session-legend.test.js +0 -20
- package/dist/__tests__/session-persistence.test.js +0 -131
- package/dist/__tests__/session-resume-block.test.js +0 -107
- package/dist/__tests__/session-resume.test.js +0 -97
- package/dist/__tests__/session-summary-writer.test.js +0 -134
- package/dist/__tests__/shadow-ledger.test.js +0 -203
- package/dist/__tests__/shell-classifier.test.js +0 -151
- package/dist/__tests__/shell-compression-floor.test.js +0 -189
- package/dist/__tests__/shell-compression-v2.test.js +0 -339
- package/dist/__tests__/shell-compressor.test.js +0 -35
- package/dist/__tests__/shell-hooks.test.js +0 -128
- package/dist/__tests__/shell-strategies.test.js +0 -644
- package/dist/__tests__/shell-tee.test.js +0 -133
- package/dist/__tests__/signal-dedup.test.js +0 -158
- package/dist/__tests__/signal-reinforcer.test.js +0 -77
- package/dist/__tests__/signal-scorer.test.js +0 -251
- package/dist/__tests__/signal-show-store.test.js +0 -108
- package/dist/__tests__/smart-truncate.test.js +0 -215
- package/dist/__tests__/snapshot-v2.test.js +0 -113
- package/dist/__tests__/sprint-l1-local-mode.test.js +0 -130
- package/dist/__tests__/sprint-l10-boot.test.js +0 -220
- package/dist/__tests__/sprint-l9-offline-commands.test.js +0 -189
- package/dist/__tests__/sprint-q-persistent-context.test.js +0 -198
- package/dist/__tests__/sprint-s1-wiring.test.js +0 -215
- package/dist/__tests__/sprint-s2-wiring.test.js +0 -256
- package/dist/__tests__/sprint-s3-wiring.test.js +0 -195
- package/dist/__tests__/sprint-s4-wiring.test.js +0 -213
- package/dist/__tests__/sprint-s6-hooks.test.js +0 -222
- package/dist/__tests__/sprint-s7-persistent.test.js +0 -263
- package/dist/__tests__/sprint-s8-value.test.js +0 -167
- package/dist/__tests__/sprint-s9-behavioral.test.js +0 -179
- package/dist/__tests__/sprint3-intelligence.test.js +0 -297
- package/dist/__tests__/sprint5-mcp-server.test.js +0 -136
- package/dist/__tests__/startup-display.test.js +0 -302
- package/dist/__tests__/startup-log-file.test.js +0 -97
- package/dist/__tests__/stash-manager.test.js +0 -229
- package/dist/__tests__/state-detector.test.js +0 -92
- package/dist/__tests__/status-dashboard.test.js +0 -142
- package/dist/__tests__/temporal-facts.test.js +0 -292
- package/dist/__tests__/temporal-routes.test.js +0 -142
- package/dist/__tests__/test-detector.test.js +0 -174
- package/dist/__tests__/theme.test.js +0 -72
- package/dist/__tests__/timeline-agents.test.js +0 -122
- package/dist/__tests__/timeline-bootstrap.test.js +0 -176
- package/dist/__tests__/timeline-filters.test.js +0 -193
- package/dist/__tests__/timeline-markers.test.js +0 -151
- package/dist/__tests__/timeline-routes.test.js +0 -156
- package/dist/__tests__/timeline-store.test.js +0 -171
- package/dist/__tests__/token-counter.test.js +0 -86
- package/dist/__tests__/token-estimator.test.js +0 -96
- package/dist/__tests__/token-flow-api.test.js +0 -239
- package/dist/__tests__/token-flow-instrumentation.test.js +0 -437
- package/dist/__tests__/token-flow-persistence.test.js +0 -356
- package/dist/__tests__/token-flow-routes.test.js +0 -199
- package/dist/__tests__/token-flow.test.js +0 -695
- package/dist/__tests__/tool-clusters.test.js +0 -177
- package/dist/__tests__/transport-mux.test.js +0 -283
- package/dist/__tests__/turn-segmenter.test.js +0 -166
- package/dist/__tests__/uninstall.test.js +0 -141
- package/dist/__tests__/warm-start-policy.test.js +0 -271
- package/dist/__tests__/wire-cap-nudge.test.js +0 -77
- package/dist/__tests__/worker-pool.test.js +0 -101
- package/dist/ui/assets/index-7gl3mIuY.css +0 -1
- package/dist/ui/assets/index-CX4FCWGT.js +0 -10
- package/dist/ui/assets/rolldown-runtime-S-ySWqyJ.js +0 -1
- package/dist/ui/assets/vis-network-NIJHUFI3.js +0 -908
- package/dist/ui/fonts/jetbrains-mono-latin-400-normal.woff +0 -0
- package/dist/ui/icon-wordmark.png +0 -0
- package/dist/ui/icon-wordmark.svg +0 -30
- package/dist/ui/icon.png +0 -0
- package/dist/ui/icon.svg +0 -25
- package/dist/ui/index.html +0 -15
- package/dist/ui/prototype-sandbox/index.html +0 -257
- package/dist/ui/screenshots/activity.png +0 -0
- package/dist/ui/screenshots/code-base-intelligence.png +0 -0
- package/dist/ui/screenshots/dashboard.png +0 -0
- package/dist/ui/screenshots/project-memory.png +0 -0
- package/dist/ui/screenshots/reasoning-quality.png +0 -0
- package/dist/ui/screenshots/reasoning-session.png +0 -0
- package/dist/ui/screenshots/token-session.png +0 -0
- package/dist/ui/screenshots/token-trace-main.png +0 -0
- package/dist/ui/screenshots/token-turn.png +0 -0
- package/dist/ui/unerr-wordmark.png +0 -0
- package/dist/ui/unerr-wordmark.svg +0 -9
- package/dist/ui/unerr.png +0 -0
- package/dist/ui/unerr.svg +0 -25
- package/dist/ui/web-app-manifest-192x192.png +0 -0
- package/dist/ui/web-app-manifest-512x512.png +0 -0
|
@@ -1,263 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Sprint S7: Wire Persistent Context (Layer 2) — Integration Tests.
|
|
3
|
-
*
|
|
4
|
-
* Verifies:
|
|
5
|
-
* - S7.1: Auto-snapshot triggers fire on test_pass and pre_critical_change
|
|
6
|
-
* - S7.2+S7.3: Session resume generates structured greeting from ledger
|
|
7
|
-
* - S7.4: Resume context injected as _context.session_resume on first response
|
|
8
|
-
* - S7.5: Durability scorer wired into entity responses (< 0.5 warning)
|
|
9
|
-
* - S7.6: Negative knowledge anti-patterns injected before agent touches code
|
|
10
|
-
* - S7.7: Timeline fork created on rewind
|
|
11
|
-
* - S7.8: Durability data feeds into session health monitor
|
|
12
|
-
*/
|
|
13
|
-
import { describe, expect, it } from "vitest";
|
|
14
|
-
import { createDurabilityScorer } from "../intelligence/durability-scorer.js";
|
|
15
|
-
import { detectInstableEntities } from "../intelligence/negative-knowledge.js";
|
|
16
|
-
import { orderContextFields, } from "../intelligence/query-router.js";
|
|
17
|
-
import { generateSessionResume } from "../proxy/session-resume.js";
|
|
18
|
-
import { shouldAutoSnapshot } from "../tracking/auto-snapshot-triggers.js";
|
|
19
|
-
import { createTimelineFork } from "../tracking/timeline-fork.js";
|
|
20
|
-
function makeLedgerEntry(overrides) {
|
|
21
|
-
return {
|
|
22
|
-
id: overrides.id ?? `e${Math.random().toString(36).slice(2, 10)}`,
|
|
23
|
-
ts: overrides.ts ?? new Date().toISOString(),
|
|
24
|
-
tool: overrides.tool ?? "get_function",
|
|
25
|
-
args_summary: overrides.args_summary ?? {},
|
|
26
|
-
result_summary: overrides.result_summary ?? { found: true },
|
|
27
|
-
branch: overrides.branch ?? "main",
|
|
28
|
-
session_id: overrides.session_id ?? "sess-1",
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
describe("Sprint S7: Wire Persistent Context", () => {
|
|
32
|
-
describe("S7.1: Auto-snapshot triggers in proxy lifecycle", () => {
|
|
33
|
-
it("triggers on test_pass result", () => {
|
|
34
|
-
const result = shouldAutoSnapshot("run_command", { command: "npm test" }, { exitCode: 0 });
|
|
35
|
-
expect(result).toBeTruthy();
|
|
36
|
-
expect(result.type).toBe("test_pass");
|
|
37
|
-
});
|
|
38
|
-
it("triggers on high fan_in threshold (pre-critical change)", () => {
|
|
39
|
-
const result = shouldAutoSnapshot("sync_local_diff", { file: "src/core.ts", entity_key: "src/core.ts::processPayment" }, { fan_in: 50 }, 50);
|
|
40
|
-
expect(result).toBeTruthy();
|
|
41
|
-
expect(result.type).toBe("pre_critical_change");
|
|
42
|
-
});
|
|
43
|
-
it("does not trigger on normal tool call", () => {
|
|
44
|
-
const result = shouldAutoSnapshot("search_code", { query: "hello" }, { count: 5 });
|
|
45
|
-
expect(result).toBeFalsy();
|
|
46
|
-
});
|
|
47
|
-
});
|
|
48
|
-
describe("S7.2+S7.3: Session resume from ledger", () => {
|
|
49
|
-
it("generates resume context from ledger entries", () => {
|
|
50
|
-
const entries = [
|
|
51
|
-
makeLedgerEntry({
|
|
52
|
-
tool: "get_function",
|
|
53
|
-
args_summary: { key: "src/auth.ts::login" },
|
|
54
|
-
ts: "2026-05-01T10:00:00Z",
|
|
55
|
-
}),
|
|
56
|
-
makeLedgerEntry({
|
|
57
|
-
tool: "sync_local_diff",
|
|
58
|
-
args_summary: { files: ["src/auth.ts"] },
|
|
59
|
-
ts: "2026-05-01T10:01:00Z",
|
|
60
|
-
}),
|
|
61
|
-
makeLedgerEntry({
|
|
62
|
-
tool: "get_callers",
|
|
63
|
-
args_summary: { key: "src/auth.ts::login" },
|
|
64
|
-
ts: "2026-05-01T10:02:00Z",
|
|
65
|
-
}),
|
|
66
|
-
makeLedgerEntry({
|
|
67
|
-
tool: "sync_local_diff",
|
|
68
|
-
args_summary: { files: ["src/db.ts"] },
|
|
69
|
-
ts: "2026-05-01T10:03:00Z",
|
|
70
|
-
}),
|
|
71
|
-
];
|
|
72
|
-
const resume = generateSessionResume(entries);
|
|
73
|
-
expect(resume).not.toBeNull();
|
|
74
|
-
expect(resume.summary).toBeDefined();
|
|
75
|
-
expect(resume.filesModified.length).toBeGreaterThan(0);
|
|
76
|
-
});
|
|
77
|
-
it("returns null for empty entries", () => {
|
|
78
|
-
const resume = generateSessionResume([]);
|
|
79
|
-
expect(resume).toBeNull();
|
|
80
|
-
});
|
|
81
|
-
});
|
|
82
|
-
describe("S7.4: Context ordering includes session_resume", () => {
|
|
83
|
-
it("orders session_resume between corrections and reminder", () => {
|
|
84
|
-
const ctx = {
|
|
85
|
-
session_resume: {
|
|
86
|
-
summary: "Last session modified auth.ts",
|
|
87
|
-
filesModified: ["src/auth.ts"],
|
|
88
|
-
incompleteEntities: ["login"],
|
|
89
|
-
},
|
|
90
|
-
blast_radius: "14 callers",
|
|
91
|
-
conventions: ["naming: camelCase"],
|
|
92
|
-
};
|
|
93
|
-
const ordered = orderContextFields(ctx);
|
|
94
|
-
const keys = Object.keys(ordered);
|
|
95
|
-
expect(keys.indexOf("blast_radius")).toBeLessThan(keys.indexOf("session_resume"));
|
|
96
|
-
expect(keys.indexOf("session_resume")).toBeLessThan(keys.indexOf("conventions"));
|
|
97
|
-
});
|
|
98
|
-
});
|
|
99
|
-
describe("S7.5: Durability scorer wired into entity responses", () => {
|
|
100
|
-
it("computes durability scores from ledger entries", () => {
|
|
101
|
-
const baseTime = Date.now();
|
|
102
|
-
// Fragile: modified 4 times rapidly, stable: modified once early
|
|
103
|
-
const entries = [
|
|
104
|
-
makeLedgerEntry({
|
|
105
|
-
tool: "sync_local_diff",
|
|
106
|
-
args_summary: { files: ["src/stable.ts"] },
|
|
107
|
-
ts: new Date(baseTime).toISOString(),
|
|
108
|
-
}),
|
|
109
|
-
makeLedgerEntry({
|
|
110
|
-
tool: "sync_local_diff",
|
|
111
|
-
args_summary: { files: ["src/fragile.ts"] },
|
|
112
|
-
ts: new Date(baseTime + 10000).toISOString(),
|
|
113
|
-
}),
|
|
114
|
-
makeLedgerEntry({
|
|
115
|
-
tool: "sync_local_diff",
|
|
116
|
-
args_summary: { files: ["src/fragile.ts"] },
|
|
117
|
-
ts: new Date(baseTime + 20000).toISOString(),
|
|
118
|
-
}),
|
|
119
|
-
makeLedgerEntry({
|
|
120
|
-
tool: "sync_local_diff",
|
|
121
|
-
args_summary: { files: ["src/fragile.ts"] },
|
|
122
|
-
ts: new Date(baseTime + 30000).toISOString(),
|
|
123
|
-
}),
|
|
124
|
-
makeLedgerEntry({
|
|
125
|
-
tool: "sync_local_diff",
|
|
126
|
-
args_summary: { files: ["src/fragile.ts"] },
|
|
127
|
-
ts: new Date(baseTime + 600000).toISOString(),
|
|
128
|
-
}),
|
|
129
|
-
];
|
|
130
|
-
const scorer = createDurabilityScorer();
|
|
131
|
-
scorer.computeScores(entries);
|
|
132
|
-
const fragile = scorer.getScore("src/fragile.ts");
|
|
133
|
-
expect(fragile).not.toBeNull();
|
|
134
|
-
expect(fragile.modificationCount).toBe(4);
|
|
135
|
-
const stable = scorer.getScore("src/stable.ts");
|
|
136
|
-
expect(stable).not.toBeNull();
|
|
137
|
-
expect(stable.modificationCount).toBe(1);
|
|
138
|
-
// Fragile should have lower or equal durability (more churn = less stable)
|
|
139
|
-
expect(fragile.score).toBeLessThanOrEqual(stable.score);
|
|
140
|
-
});
|
|
141
|
-
it("getTopUnstable returns most fragile entities", () => {
|
|
142
|
-
const baseTime = Date.now();
|
|
143
|
-
const entries = [
|
|
144
|
-
makeLedgerEntry({
|
|
145
|
-
tool: "sync_local_diff",
|
|
146
|
-
args_summary: { files: ["src/a.ts"] },
|
|
147
|
-
ts: new Date(baseTime).toISOString(),
|
|
148
|
-
}),
|
|
149
|
-
makeLedgerEntry({
|
|
150
|
-
tool: "sync_local_diff",
|
|
151
|
-
args_summary: { files: ["src/a.ts"] },
|
|
152
|
-
ts: new Date(baseTime + 1000).toISOString(),
|
|
153
|
-
}),
|
|
154
|
-
makeLedgerEntry({
|
|
155
|
-
tool: "sync_local_diff",
|
|
156
|
-
args_summary: { files: ["src/a.ts"] },
|
|
157
|
-
ts: new Date(baseTime + 2000).toISOString(),
|
|
158
|
-
}),
|
|
159
|
-
makeLedgerEntry({
|
|
160
|
-
tool: "sync_local_diff",
|
|
161
|
-
args_summary: { files: ["src/b.ts"] },
|
|
162
|
-
ts: new Date(baseTime).toISOString(),
|
|
163
|
-
}),
|
|
164
|
-
];
|
|
165
|
-
const scorer = createDurabilityScorer();
|
|
166
|
-
scorer.computeScores(entries);
|
|
167
|
-
const unstable = scorer.getTopUnstable(2);
|
|
168
|
-
expect(unstable.length).toBeGreaterThan(0);
|
|
169
|
-
expect(unstable[0].entityKey).toBe("src/a.ts");
|
|
170
|
-
});
|
|
171
|
-
});
|
|
172
|
-
describe("S7.6: Negative knowledge anti-pattern injection", () => {
|
|
173
|
-
it("detects instable entities from rapid modifications", () => {
|
|
174
|
-
const baseTime = Date.now();
|
|
175
|
-
const entries = [
|
|
176
|
-
makeLedgerEntry({
|
|
177
|
-
tool: "sync_local_diff",
|
|
178
|
-
args_summary: { files: ["src/flaky.ts"] },
|
|
179
|
-
ts: new Date(baseTime).toISOString(),
|
|
180
|
-
}),
|
|
181
|
-
makeLedgerEntry({
|
|
182
|
-
tool: "sync_local_diff",
|
|
183
|
-
args_summary: { files: ["src/flaky.ts"] },
|
|
184
|
-
ts: new Date(baseTime + 30000).toISOString(),
|
|
185
|
-
}),
|
|
186
|
-
makeLedgerEntry({
|
|
187
|
-
tool: "sync_local_diff",
|
|
188
|
-
args_summary: { files: ["src/flaky.ts"] },
|
|
189
|
-
ts: new Date(baseTime + 60000).toISOString(),
|
|
190
|
-
}),
|
|
191
|
-
makeLedgerEntry({
|
|
192
|
-
tool: "sync_local_diff",
|
|
193
|
-
args_summary: { files: ["src/flaky.ts"] },
|
|
194
|
-
ts: new Date(baseTime + 90000).toISOString(),
|
|
195
|
-
}),
|
|
196
|
-
makeLedgerEntry({
|
|
197
|
-
tool: "sync_local_diff",
|
|
198
|
-
args_summary: { files: ["src/flaky.ts"] },
|
|
199
|
-
ts: new Date(baseTime + 120000).toISOString(),
|
|
200
|
-
}),
|
|
201
|
-
];
|
|
202
|
-
const corrections = detectInstableEntities(entries, 10 * 60 * 1000);
|
|
203
|
-
expect(corrections.length).toBeGreaterThan(0);
|
|
204
|
-
expect(corrections[0].entityKey).toBe("src/flaky.ts");
|
|
205
|
-
expect(corrections[0].pattern).toBeDefined();
|
|
206
|
-
expect(corrections[0].reason).toBeDefined();
|
|
207
|
-
});
|
|
208
|
-
it("does not flag entities modified only once", () => {
|
|
209
|
-
const entries = [
|
|
210
|
-
makeLedgerEntry({
|
|
211
|
-
tool: "sync_local_diff",
|
|
212
|
-
args_summary: { files: ["src/once.ts"] },
|
|
213
|
-
ts: new Date().toISOString(),
|
|
214
|
-
}),
|
|
215
|
-
];
|
|
216
|
-
const corrections = detectInstableEntities(entries);
|
|
217
|
-
const forOnce = corrections.filter((c) => c.entityKey === "src/once.ts");
|
|
218
|
-
expect(forOnce.length).toBe(0);
|
|
219
|
-
});
|
|
220
|
-
});
|
|
221
|
-
describe("S7.7: Timeline fork on rewind", () => {
|
|
222
|
-
it("creates timeline fork with abandoned entities", () => {
|
|
223
|
-
const fork = createTimelineFork("snap-123", ["processPayment", "validateOrder"], ["fix the payment flow"], "Approach failed — infinite loop");
|
|
224
|
-
expect(fork.forkPoint).toBe("snap-123");
|
|
225
|
-
expect(fork.abandonedBranch.entityChanges).toContain("processPayment");
|
|
226
|
-
expect(fork.abandonedBranch.entityChanges).toContain("validateOrder");
|
|
227
|
-
expect(fork.abandonedBranch.promptsTried).toContain("fix the payment flow");
|
|
228
|
-
expect(fork.abandonedBranch.failureReason).toBe("Approach failed — infinite loop");
|
|
229
|
-
expect(fork.newBranch.timelineId).toBeGreaterThan(fork.abandonedBranch.timelineId);
|
|
230
|
-
});
|
|
231
|
-
it("deduplicates abandoned entities", () => {
|
|
232
|
-
const fork = createTimelineFork("snap-456", ["entity1", "entity1", "entity2", "entity2"], []);
|
|
233
|
-
expect(fork.abandonedBranch.entityChanges).toEqual([
|
|
234
|
-
"entity1",
|
|
235
|
-
"entity2",
|
|
236
|
-
]);
|
|
237
|
-
});
|
|
238
|
-
});
|
|
239
|
-
describe("S7.8: Durability feeds into session health monitor", () => {
|
|
240
|
-
it("context ordering places durability_warning with high priority", () => {
|
|
241
|
-
const ctx = {
|
|
242
|
-
durability_warning: "FRAGILE: entity has durability 0.3",
|
|
243
|
-
conventions: ["naming: camelCase"],
|
|
244
|
-
blast_radius: "14 callers",
|
|
245
|
-
};
|
|
246
|
-
const ordered = orderContextFields(ctx);
|
|
247
|
-
const keys = Object.keys(ordered);
|
|
248
|
-
// durability_warning should come after blast_radius but before conventions
|
|
249
|
-
expect(keys.indexOf("blast_radius")).toBeLessThan(keys.indexOf("durability_warning"));
|
|
250
|
-
expect(keys.indexOf("durability_warning")).toBeLessThan(keys.indexOf("conventions"));
|
|
251
|
-
});
|
|
252
|
-
it("anti_patterns ordered with high priority", () => {
|
|
253
|
-
const ctx = {
|
|
254
|
-
anti_patterns: ["ANTI-PATTERN: repeated modification without test"],
|
|
255
|
-
conventions: ["naming: camelCase"],
|
|
256
|
-
blast_radius: "14 callers",
|
|
257
|
-
};
|
|
258
|
-
const ordered = orderContextFields(ctx);
|
|
259
|
-
const keys = Object.keys(ordered);
|
|
260
|
-
expect(keys.indexOf("anti_patterns")).toBeLessThan(keys.indexOf("conventions"));
|
|
261
|
-
});
|
|
262
|
-
});
|
|
263
|
-
});
|
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Sprint S8: Value Surfacing — Tests
|
|
3
|
-
*
|
|
4
|
-
* S8.1: Guard fires once when threshold crossed
|
|
5
|
-
* S8.2: Per-response _meta has optimization + powered_by
|
|
6
|
-
* S8.3: Scorecard formats correctly
|
|
7
|
-
* S8.4: Weekly accumulator persists and resets
|
|
8
|
-
* S8.7: Counterfactual explanation format
|
|
9
|
-
*/
|
|
10
|
-
import { describe, expect, it } from "vitest";
|
|
11
|
-
import { assembleValueMeta, createValueGuard, formatCounterfactual, formatScorecard, } from "../config/value-surfacing.js";
|
|
12
|
-
import { formatStatsReport, loadStats, } from "../tracking/weekly-accumulator.js";
|
|
13
|
-
// ── S8.1: Value Guard ──────────────────────────────────────────────
|
|
14
|
-
describe("S8.1: Value Guard", () => {
|
|
15
|
-
it("does not fire below threshold", () => {
|
|
16
|
-
const guard = createValueGuard(1.0);
|
|
17
|
-
expect(guard.check(0.49)).toBeNull();
|
|
18
|
-
expect(guard.hasFired()).toBe(false);
|
|
19
|
-
});
|
|
20
|
-
it("fires once when threshold crossed", () => {
|
|
21
|
-
const guard = createValueGuard(0.5);
|
|
22
|
-
const msg = guard.check(0.75);
|
|
23
|
-
expect(msg).toContain("$0.75");
|
|
24
|
-
expect(guard.hasFired()).toBe(true);
|
|
25
|
-
});
|
|
26
|
-
it("does not fire a second time", () => {
|
|
27
|
-
const guard = createValueGuard(0.5);
|
|
28
|
-
guard.check(0.6); // fires
|
|
29
|
-
expect(guard.check(1.0)).toBeNull(); // already fired
|
|
30
|
-
});
|
|
31
|
-
it("resets correctly", () => {
|
|
32
|
-
const guard = createValueGuard(0.5);
|
|
33
|
-
guard.check(0.6);
|
|
34
|
-
guard.reset();
|
|
35
|
-
expect(guard.hasFired()).toBe(false);
|
|
36
|
-
const msg = guard.check(0.8);
|
|
37
|
-
expect(msg).toContain("$0.80");
|
|
38
|
-
});
|
|
39
|
-
});
|
|
40
|
-
// ── S8.2: Per-response _meta ───────────────────────────────────────
|
|
41
|
-
describe("S8.2: Value Meta Assembly", () => {
|
|
42
|
-
it("assembles basic meta fields", () => {
|
|
43
|
-
const meta = assembleValueMeta(1500, 0.009);
|
|
44
|
-
expect(meta.tokens_saved).toBe(1500);
|
|
45
|
-
expect(meta.dollar_savings).toBe(0.009);
|
|
46
|
-
expect(meta.powered_by).toContain("unerr");
|
|
47
|
-
expect(meta.optimization).toBeUndefined();
|
|
48
|
-
});
|
|
49
|
-
it("includes optimization description when provided", () => {
|
|
50
|
-
const meta = assembleValueMeta(3000, 0.018, "blast_radius served 7 files");
|
|
51
|
-
expect(meta.optimization).toBe("blast_radius served 7 files");
|
|
52
|
-
});
|
|
53
|
-
it("rounds dollar savings to 6 decimal places", () => {
|
|
54
|
-
const meta = assembleValueMeta(100, 0.0001234567);
|
|
55
|
-
expect(meta.dollar_savings).toBe(0.000123);
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
|
-
// ── S8.3: Session Scorecard ────────────────────────────────────────
|
|
59
|
-
describe("S8.3: Session Scorecard", () => {
|
|
60
|
-
it("formats basic scorecard", () => {
|
|
61
|
-
const input = {
|
|
62
|
-
toolCalls: 47,
|
|
63
|
-
tokensSaved: 45200,
|
|
64
|
-
dollarsSaved: 0.27,
|
|
65
|
-
efficiency: 73,
|
|
66
|
-
durationMs: 12 * 60_000,
|
|
67
|
-
blastRadiusComputed: 4,
|
|
68
|
-
conventionsInjected: 3,
|
|
69
|
-
outputsCompressed: 12,
|
|
70
|
-
correctionsApplied: 2,
|
|
71
|
-
wrongApproachesPrevented: 0,
|
|
72
|
-
};
|
|
73
|
-
const sc = formatScorecard(input);
|
|
74
|
-
expect(sc.toolCalls).toBe(47);
|
|
75
|
-
expect(sc.tokensSaved).toBe("45.2K");
|
|
76
|
-
expect(sc.dollarsSaved).toBe("$0.27");
|
|
77
|
-
expect(sc.efficiency).toBe("73%");
|
|
78
|
-
expect(sc.duration).toBe("12 min");
|
|
79
|
-
expect(sc.intelligenceApplied).toContain("4 blast radius computations");
|
|
80
|
-
expect(sc.intelligenceApplied).toContain("3 convention injections");
|
|
81
|
-
expect(sc.intelligenceApplied).toContain("12 outputs compressed");
|
|
82
|
-
expect(sc.intelligenceApplied).toContain("2 corrections applied");
|
|
83
|
-
});
|
|
84
|
-
it("handles zero duration", () => {
|
|
85
|
-
const input = {
|
|
86
|
-
toolCalls: 3,
|
|
87
|
-
tokensSaved: 500,
|
|
88
|
-
dollarsSaved: 0.003,
|
|
89
|
-
efficiency: 50,
|
|
90
|
-
durationMs: 20_000,
|
|
91
|
-
blastRadiusComputed: 0,
|
|
92
|
-
conventionsInjected: 0,
|
|
93
|
-
outputsCompressed: 0,
|
|
94
|
-
correctionsApplied: 0,
|
|
95
|
-
wrongApproachesPrevented: 0,
|
|
96
|
-
};
|
|
97
|
-
const sc = formatScorecard(input);
|
|
98
|
-
expect(sc.duration).toBe("<1 min");
|
|
99
|
-
expect(sc.intelligenceApplied).toHaveLength(0);
|
|
100
|
-
});
|
|
101
|
-
it("formats million tokens", () => {
|
|
102
|
-
const input = {
|
|
103
|
-
toolCalls: 200,
|
|
104
|
-
tokensSaved: 1_500_000,
|
|
105
|
-
dollarsSaved: 9.0,
|
|
106
|
-
efficiency: 85,
|
|
107
|
-
durationMs: 45 * 60_000,
|
|
108
|
-
blastRadiusComputed: 10,
|
|
109
|
-
conventionsInjected: 5,
|
|
110
|
-
outputsCompressed: 30,
|
|
111
|
-
correctionsApplied: 4,
|
|
112
|
-
wrongApproachesPrevented: 2,
|
|
113
|
-
};
|
|
114
|
-
const sc = formatScorecard(input);
|
|
115
|
-
expect(sc.tokensSaved).toBe("1.5M");
|
|
116
|
-
expect(sc.intelligenceApplied).toContain("2 wrong approaches prevented");
|
|
117
|
-
});
|
|
118
|
-
});
|
|
119
|
-
// ── S8.4: Weekly Accumulator ───────────────────────────────────────
|
|
120
|
-
describe("S8.4: Weekly Accumulator", () => {
|
|
121
|
-
it("loadStats returns valid empty structure", () => {
|
|
122
|
-
const stats = loadStats();
|
|
123
|
-
expect(stats.version).toBe(1);
|
|
124
|
-
expect(stats.weekly.sessions).toBeGreaterThanOrEqual(0);
|
|
125
|
-
expect(stats.allTime.totalSessions).toBeGreaterThanOrEqual(0);
|
|
126
|
-
expect(stats.lastUpdated).toBeDefined();
|
|
127
|
-
});
|
|
128
|
-
it("formatStatsReport produces readable output", () => {
|
|
129
|
-
const stats = loadStats();
|
|
130
|
-
// Seed some data for formatting
|
|
131
|
-
stats.weekly.sessions = 5;
|
|
132
|
-
stats.weekly.tokensSaved = 280_000;
|
|
133
|
-
stats.weekly.dollarsSaved = 1.68;
|
|
134
|
-
stats.weekly.avgEfficiency = 73;
|
|
135
|
-
stats.weekly.violationsCaught = 12;
|
|
136
|
-
stats.allTime.totalSessions = 20;
|
|
137
|
-
stats.allTime.totalTokensSaved = 1_200_000;
|
|
138
|
-
stats.allTime.totalDollarsSaved = 7.2;
|
|
139
|
-
stats.allTime.totalViolationsCaught = 45;
|
|
140
|
-
const report = formatStatsReport(stats);
|
|
141
|
-
expect(report).toContain("This week:");
|
|
142
|
-
expect(report).toContain("All time:");
|
|
143
|
-
expect(report).toContain("$1.68");
|
|
144
|
-
expect(report).toContain("280.0K");
|
|
145
|
-
expect(report).toContain("73%");
|
|
146
|
-
expect(report).toContain("1.2M");
|
|
147
|
-
});
|
|
148
|
-
});
|
|
149
|
-
// ── S8.7: Counterfactual Explanation ───────────────────────────────
|
|
150
|
-
describe("S8.7: Counterfactual Explanation", () => {
|
|
151
|
-
it("formats basic counterfactual", () => {
|
|
152
|
-
const result = formatCounterfactual(100_000, 35_000);
|
|
153
|
-
expect(result).toContain("Without unerr: ~100.0K tokens");
|
|
154
|
-
expect(result).toContain("with unerr: 35.0K tokens");
|
|
155
|
-
expect(result).toContain("65% reduction");
|
|
156
|
-
});
|
|
157
|
-
it("handles zero tokens without", () => {
|
|
158
|
-
const result = formatCounterfactual(0, 0);
|
|
159
|
-
expect(result).toContain("0% reduction");
|
|
160
|
-
});
|
|
161
|
-
it("formats millions", () => {
|
|
162
|
-
const result = formatCounterfactual(2_000_000, 600_000);
|
|
163
|
-
expect(result).toContain("2.0M");
|
|
164
|
-
expect(result).toContain("600.0K");
|
|
165
|
-
expect(result).toContain("70% reduction");
|
|
166
|
-
});
|
|
167
|
-
});
|
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Sprint S9: Day 1 Behavioral Hooks — Tests
|
|
3
|
-
*
|
|
4
|
-
* S9.1: Circuit breaker fires after 4 retries on same entity
|
|
5
|
-
* S9.2: Health-triggered circuit break (health < 0.3)
|
|
6
|
-
* S9.3: Caught event counter increments
|
|
7
|
-
* S9.4: Halt message format
|
|
8
|
-
* S9.5: Convention violations wire into caught counter
|
|
9
|
-
* S9.6: Caught counter feeds value counter (every 3rd event)
|
|
10
|
-
*/
|
|
11
|
-
import { beforeEach, describe, expect, it } from "vitest";
|
|
12
|
-
import { SessionContext } from "../intelligence/session-context.js";
|
|
13
|
-
import { createSessionEvents, totalCaughtEvents, } from "../proxy/session-stats.js";
|
|
14
|
-
import { LedgerCircuitBreaker, formatHaltMessage, } from "../tracking/circuit-breaker.js";
|
|
15
|
-
// ── S9.1: Circuit Breaker fires after 4 retries ───────────────────
|
|
16
|
-
describe("S9.1: Circuit Breaker — entity retry detection", () => {
|
|
17
|
-
let breaker;
|
|
18
|
-
beforeEach(() => {
|
|
19
|
-
breaker = new LedgerCircuitBreaker();
|
|
20
|
-
});
|
|
21
|
-
it("does not fire with fewer than 4 violation attempts", () => {
|
|
22
|
-
breaker.recordAttempt("src/core.ts::processPayment", true);
|
|
23
|
-
breaker.recordAttempt("src/core.ts::processPayment", true);
|
|
24
|
-
breaker.recordAttempt("src/core.ts::processPayment", true);
|
|
25
|
-
const result = breaker.check(["src/core.ts::processPayment"]);
|
|
26
|
-
expect(result).toBeNull();
|
|
27
|
-
});
|
|
28
|
-
it("fires after 4 consecutive violation attempts", () => {
|
|
29
|
-
const entity = "src/core.ts::processPayment";
|
|
30
|
-
breaker.recordAttempt(entity, true);
|
|
31
|
-
breaker.recordAttempt(entity, true);
|
|
32
|
-
breaker.recordAttempt(entity, true);
|
|
33
|
-
breaker.recordAttempt(entity, true);
|
|
34
|
-
const result = breaker.check([entity]);
|
|
35
|
-
expect(result).not.toBeNull();
|
|
36
|
-
expect(result?.triggered).toBe(true);
|
|
37
|
-
expect(result?.entity).toBe(entity);
|
|
38
|
-
expect(result?.attempts).toBe(4);
|
|
39
|
-
});
|
|
40
|
-
it("does not fire if attempts have no violations", () => {
|
|
41
|
-
const entity = "src/utils.ts::formatDate";
|
|
42
|
-
breaker.recordAttempt(entity, false);
|
|
43
|
-
breaker.recordAttempt(entity, false);
|
|
44
|
-
breaker.recordAttempt(entity, false);
|
|
45
|
-
breaker.recordAttempt(entity, false);
|
|
46
|
-
const result = breaker.check([entity]);
|
|
47
|
-
expect(result).toBeNull();
|
|
48
|
-
});
|
|
49
|
-
it("does not fire if violations are interspersed with successes", () => {
|
|
50
|
-
const entity = "src/api.ts::handleRequest";
|
|
51
|
-
breaker.recordAttempt(entity, true);
|
|
52
|
-
breaker.recordAttempt(entity, true);
|
|
53
|
-
breaker.recordAttempt(entity, false); // success breaks the streak
|
|
54
|
-
breaker.recordAttempt(entity, true);
|
|
55
|
-
breaker.recordAttempt(entity, true);
|
|
56
|
-
const result = breaker.check([entity]);
|
|
57
|
-
expect(result).toBeNull();
|
|
58
|
-
});
|
|
59
|
-
it("remains halted on subsequent checks", () => {
|
|
60
|
-
const entity = "src/core.ts::processPayment";
|
|
61
|
-
for (let i = 0; i < 4; i++)
|
|
62
|
-
breaker.recordAttempt(entity, true);
|
|
63
|
-
breaker.check([entity]); // trips it
|
|
64
|
-
// Subsequent check should still show halted
|
|
65
|
-
const result = breaker.check([entity]);
|
|
66
|
-
expect(result).not.toBeNull();
|
|
67
|
-
expect(result?.triggered).toBe(true);
|
|
68
|
-
});
|
|
69
|
-
it("resets correctly for a specific entity", () => {
|
|
70
|
-
const entity = "src/core.ts::processPayment";
|
|
71
|
-
for (let i = 0; i < 4; i++)
|
|
72
|
-
breaker.recordAttempt(entity, true);
|
|
73
|
-
breaker.check([entity]);
|
|
74
|
-
breaker.reset(entity);
|
|
75
|
-
expect(breaker.isHalted(entity)).toBe(false);
|
|
76
|
-
const result = breaker.check([entity]);
|
|
77
|
-
expect(result).toBeNull();
|
|
78
|
-
});
|
|
79
|
-
it("tracks multiple entities independently", () => {
|
|
80
|
-
const entity1 = "src/a.ts::foo";
|
|
81
|
-
const entity2 = "src/b.ts::bar";
|
|
82
|
-
for (let i = 0; i < 4; i++)
|
|
83
|
-
breaker.recordAttempt(entity1, true);
|
|
84
|
-
breaker.recordAttempt(entity2, true);
|
|
85
|
-
breaker.recordAttempt(entity2, true);
|
|
86
|
-
const result1 = breaker.check([entity1]);
|
|
87
|
-
const result2 = breaker.check([entity2]);
|
|
88
|
-
expect(result1).not.toBeNull();
|
|
89
|
-
expect(result2).toBeNull();
|
|
90
|
-
});
|
|
91
|
-
});
|
|
92
|
-
// ── S9.3: Caught Event Counter ─────────────────────────────────────
|
|
93
|
-
describe("S9.3: Caught event counter", () => {
|
|
94
|
-
it("increments convention violations", () => {
|
|
95
|
-
const events = createSessionEvents();
|
|
96
|
-
events.conventionViolationsCaught += 3;
|
|
97
|
-
expect(totalCaughtEvents(events)).toBe(3);
|
|
98
|
-
});
|
|
99
|
-
it("increments chokepoint warnings", () => {
|
|
100
|
-
const events = createSessionEvents();
|
|
101
|
-
events.chokepointWarningsIssued += 2;
|
|
102
|
-
expect(totalCaughtEvents(events)).toBe(2);
|
|
103
|
-
});
|
|
104
|
-
it("increments circular deps", () => {
|
|
105
|
-
const events = createSessionEvents();
|
|
106
|
-
events.circularDepsDetected += 1;
|
|
107
|
-
expect(totalCaughtEvents(events)).toBe(1);
|
|
108
|
-
});
|
|
109
|
-
it("sums all caught categories", () => {
|
|
110
|
-
const events = createSessionEvents();
|
|
111
|
-
events.conventionViolationsCaught = 2;
|
|
112
|
-
events.chokepointWarningsIssued = 3;
|
|
113
|
-
events.circularDepsDetected = 1;
|
|
114
|
-
events.signaturePreservations = 1;
|
|
115
|
-
events.deadCodeReferences = 2;
|
|
116
|
-
expect(totalCaughtEvents(events)).toBe(9);
|
|
117
|
-
});
|
|
118
|
-
});
|
|
119
|
-
// ── S9.4: Halt Message Formatter ───────────────────────────────────
|
|
120
|
-
describe("S9.4: Halt message formatter", () => {
|
|
121
|
-
it("includes entity name", () => {
|
|
122
|
-
const msg = formatHaltMessage("src/core.ts::processPayment", 4);
|
|
123
|
-
expect(msg).toContain("src/core.ts::processPayment");
|
|
124
|
-
});
|
|
125
|
-
it("includes attempt count", () => {
|
|
126
|
-
const msg = formatHaltMessage("src/api.ts::handleAuth", 6);
|
|
127
|
-
expect(msg).toContain("6 times");
|
|
128
|
-
});
|
|
129
|
-
it("starts with Stop", () => {
|
|
130
|
-
const msg = formatHaltMessage("entity", 4);
|
|
131
|
-
expect(msg).toMatch(/^Stop\./);
|
|
132
|
-
});
|
|
133
|
-
it("suggests different approach", () => {
|
|
134
|
-
const msg = formatHaltMessage("entity", 5);
|
|
135
|
-
expect(msg).toContain("different approach");
|
|
136
|
-
});
|
|
137
|
-
});
|
|
138
|
-
// ── S9.6: Value Counter integration ────────────────────────────────
|
|
139
|
-
describe("S9.6: Value counter fires on caught events", () => {
|
|
140
|
-
it("fires at every 3rd caught event after 10 tool calls", () => {
|
|
141
|
-
const ctx = new SessionContext();
|
|
142
|
-
const events = createSessionEvents();
|
|
143
|
-
// Record 11 tool calls (value counter only fires after 10)
|
|
144
|
-
for (let i = 0; i < 11; i++)
|
|
145
|
-
ctx.recordToolCall();
|
|
146
|
-
// 1st and 2nd caught event — no fire
|
|
147
|
-
events.conventionViolationsCaught = 1;
|
|
148
|
-
expect(ctx.getValueCounter(events)).toBeUndefined();
|
|
149
|
-
events.conventionViolationsCaught = 2;
|
|
150
|
-
expect(ctx.getValueCounter(events)).toBeUndefined();
|
|
151
|
-
// 3rd caught event — fires
|
|
152
|
-
events.conventionViolationsCaught = 3;
|
|
153
|
-
const msg = ctx.getValueCounter(events);
|
|
154
|
-
expect(msg).toContain("3 issues");
|
|
155
|
-
});
|
|
156
|
-
it("does not fire before 10 tool calls", () => {
|
|
157
|
-
const ctx = new SessionContext();
|
|
158
|
-
const events = createSessionEvents();
|
|
159
|
-
for (let i = 0; i < 5; i++)
|
|
160
|
-
ctx.recordToolCall();
|
|
161
|
-
events.conventionViolationsCaught = 3;
|
|
162
|
-
expect(ctx.getValueCounter(events)).toBeUndefined();
|
|
163
|
-
});
|
|
164
|
-
it("fires again at 6th caught event", () => {
|
|
165
|
-
const ctx = new SessionContext();
|
|
166
|
-
const events = createSessionEvents();
|
|
167
|
-
for (let i = 0; i < 11; i++)
|
|
168
|
-
ctx.recordToolCall();
|
|
169
|
-
events.conventionViolationsCaught = 3;
|
|
170
|
-
ctx.getValueCounter(events); // fires at 3
|
|
171
|
-
events.conventionViolationsCaught = 4;
|
|
172
|
-
expect(ctx.getValueCounter(events)).toBeUndefined();
|
|
173
|
-
events.conventionViolationsCaught = 5;
|
|
174
|
-
expect(ctx.getValueCounter(events)).toBeUndefined();
|
|
175
|
-
events.conventionViolationsCaught = 6;
|
|
176
|
-
const msg = ctx.getValueCounter(events);
|
|
177
|
-
expect(msg).toContain("6 issues");
|
|
178
|
-
});
|
|
179
|
-
});
|