@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,92 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* P10-TEST-02: State Machine Tests — unit tests for smart default state detector.
|
|
3
|
-
*/
|
|
4
|
-
import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
5
|
-
import { tmpdir } from "node:os";
|
|
6
|
-
import { join } from "node:path";
|
|
7
|
-
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
8
|
-
import { detectState } from "../state-detector.js";
|
|
9
|
-
let tempDir;
|
|
10
|
-
let unerrDir;
|
|
11
|
-
beforeEach(() => {
|
|
12
|
-
tempDir = join(tmpdir(), `unerr-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
13
|
-
unerrDir = join(tempDir, ".unerr");
|
|
14
|
-
mkdirSync(join(unerrDir, "state"), { recursive: true });
|
|
15
|
-
});
|
|
16
|
-
afterEach(() => {
|
|
17
|
-
try {
|
|
18
|
-
rmSync(tempDir, { recursive: true, force: true });
|
|
19
|
-
}
|
|
20
|
-
catch {
|
|
21
|
-
/* ignore */
|
|
22
|
-
}
|
|
23
|
-
});
|
|
24
|
-
function makeConfig(repoId = "repo-123") {
|
|
25
|
-
writeFileSync(join(unerrDir, "config.json"), JSON.stringify({ repoId }), "utf-8");
|
|
26
|
-
}
|
|
27
|
-
function makePid(pid) {
|
|
28
|
-
mkdirSync(join(unerrDir, "state"), { recursive: true });
|
|
29
|
-
writeFileSync(join(unerrDir, "state", "proxy.pid"), String(pid), "utf-8");
|
|
30
|
-
}
|
|
31
|
-
function baseDeps(overrides) {
|
|
32
|
-
return {
|
|
33
|
-
cwd: tempDir,
|
|
34
|
-
isGitRepo: () => true,
|
|
35
|
-
...overrides,
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
describe("detectState", () => {
|
|
39
|
-
it("returns not_git_repo when not inside a git repo", async () => {
|
|
40
|
-
const result = await detectState(baseDeps({ isGitRepo: () => false }));
|
|
41
|
-
expect(result.state).toBe("not_git_repo");
|
|
42
|
-
});
|
|
43
|
-
it("returns needs_setup when no .unerr/config.json", async () => {
|
|
44
|
-
const result = await detectState(baseDeps());
|
|
45
|
-
expect(result.state).toBe("needs_setup");
|
|
46
|
-
});
|
|
47
|
-
it("returns needs_setup when config.json is invalid JSON", async () => {
|
|
48
|
-
writeFileSync(join(unerrDir, "config.json"), "not json", "utf-8");
|
|
49
|
-
const result = await detectState(baseDeps());
|
|
50
|
-
expect(result.state).toBe("needs_setup");
|
|
51
|
-
});
|
|
52
|
-
it("returns already_running when PID is alive", async () => {
|
|
53
|
-
makeConfig();
|
|
54
|
-
makePid(process.pid);
|
|
55
|
-
const result = await detectState(baseDeps());
|
|
56
|
-
expect(result.state).toBe("already_running");
|
|
57
|
-
expect(result.pid).toBe(process.pid);
|
|
58
|
-
expect(result.repoId).toBe("repo-123");
|
|
59
|
-
});
|
|
60
|
-
it("returns stale_pid when PID is dead and cleans up", async () => {
|
|
61
|
-
makeConfig();
|
|
62
|
-
const deadPid = 9999999;
|
|
63
|
-
makePid(deadPid);
|
|
64
|
-
const result = await detectState(baseDeps());
|
|
65
|
-
expect(result.state).toBe("stale_pid");
|
|
66
|
-
expect(result.pid).toBe(deadPid);
|
|
67
|
-
expect(existsSync(join(unerrDir, "state", "proxy.pid"))).toBe(false);
|
|
68
|
-
});
|
|
69
|
-
it("returns needs_pull when no snapshot exists", async () => {
|
|
70
|
-
makeConfig();
|
|
71
|
-
const result = await detectState(baseDeps());
|
|
72
|
-
expect(["needs_pull", "stale_graph", "ready"]).toContain(result.state);
|
|
73
|
-
expect(result.repoId).toBe("repo-123");
|
|
74
|
-
});
|
|
75
|
-
it("returns ready when everything is configured", async () => {
|
|
76
|
-
makeConfig();
|
|
77
|
-
const result = await detectState(baseDeps());
|
|
78
|
-
expect(result.repoId).toBe("repo-123");
|
|
79
|
-
expect(["needs_pull", "stale_graph", "ready"]).toContain(result.state);
|
|
80
|
-
});
|
|
81
|
-
it("preserves repoId from config", async () => {
|
|
82
|
-
makeConfig("my-special-repo");
|
|
83
|
-
const result = await detectState(baseDeps());
|
|
84
|
-
expect(result.repoId).toBe("my-special-repo");
|
|
85
|
-
});
|
|
86
|
-
it("state transitions follow priority order", async () => {
|
|
87
|
-
const result = await detectState(baseDeps({
|
|
88
|
-
isGitRepo: () => false,
|
|
89
|
-
}));
|
|
90
|
-
expect(result.state).toBe("not_git_repo");
|
|
91
|
-
});
|
|
92
|
-
});
|
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for StatusDashboard Ink component (Task 1.5).
|
|
3
|
-
*
|
|
4
|
-
* Tests validate:
|
|
5
|
-
* - Repo name, branch, commits ahead/behind display
|
|
6
|
-
* - Proxy state (running/stopped) with color
|
|
7
|
-
* - Graph stats (entity count, edge count, age)
|
|
8
|
-
* - Drift summary
|
|
9
|
-
* - Health grade with score
|
|
10
|
-
* - Live session stats with local rate bar
|
|
11
|
-
* - Latency display with budget warning
|
|
12
|
-
* - Deep link display
|
|
13
|
-
*/
|
|
14
|
-
import { render } from "ink-testing-library";
|
|
15
|
-
import React from "react";
|
|
16
|
-
import { describe, expect, it } from "vitest";
|
|
17
|
-
import { StatusDashboard, } from "../components/StatusDashboard.js";
|
|
18
|
-
import { ThemeProvider } from "../components/Theme.js";
|
|
19
|
-
function renderStatus(data) {
|
|
20
|
-
return render(React.createElement(ThemeProvider, null, React.createElement(StatusDashboard, { data })));
|
|
21
|
-
}
|
|
22
|
-
const baseData = {
|
|
23
|
-
repoName: "acme/monorepo",
|
|
24
|
-
repoId: "repo_abc123",
|
|
25
|
-
branch: "main",
|
|
26
|
-
branchDetail: "main (3 ahead, 1 behind)",
|
|
27
|
-
proxyStatus: "Running (PID 12345)",
|
|
28
|
-
proxyRunning: true,
|
|
29
|
-
graphInfo: "2,341 entities, 1,892 edges (pulled 2h ago)",
|
|
30
|
-
};
|
|
31
|
-
describe("StatusDashboard", () => {
|
|
32
|
-
it("renders section header", () => {
|
|
33
|
-
const { lastFrame } = renderStatus(baseData);
|
|
34
|
-
expect(lastFrame()).toContain("unerr status");
|
|
35
|
-
});
|
|
36
|
-
it("renders repo name and ID", () => {
|
|
37
|
-
const { lastFrame } = renderStatus(baseData);
|
|
38
|
-
const frame = lastFrame() ?? "";
|
|
39
|
-
expect(frame).toContain("acme/monorepo");
|
|
40
|
-
expect(frame).toContain("repo_abc123");
|
|
41
|
-
});
|
|
42
|
-
it("renders branch with detail", () => {
|
|
43
|
-
const { lastFrame } = renderStatus(baseData);
|
|
44
|
-
expect(lastFrame()).toContain("main (3 ahead, 1 behind)");
|
|
45
|
-
});
|
|
46
|
-
it("renders proxy running status", () => {
|
|
47
|
-
const { lastFrame } = renderStatus(baseData);
|
|
48
|
-
expect(lastFrame()).toContain("Running (PID 12345)");
|
|
49
|
-
});
|
|
50
|
-
it("renders proxy not running", () => {
|
|
51
|
-
const { lastFrame } = renderStatus({
|
|
52
|
-
...baseData,
|
|
53
|
-
proxyStatus: "Not running",
|
|
54
|
-
proxyRunning: false,
|
|
55
|
-
});
|
|
56
|
-
expect(lastFrame()).toContain("Not running");
|
|
57
|
-
});
|
|
58
|
-
it("renders graph info", () => {
|
|
59
|
-
const { lastFrame } = renderStatus(baseData);
|
|
60
|
-
expect(lastFrame()).toContain("2,341 entities");
|
|
61
|
-
expect(lastFrame()).toContain("1,892 edges");
|
|
62
|
-
});
|
|
63
|
-
it("renders health grade", () => {
|
|
64
|
-
const { lastFrame } = renderStatus({
|
|
65
|
-
...baseData,
|
|
66
|
-
healthGrade: "B+",
|
|
67
|
-
healthScore: 78,
|
|
68
|
-
});
|
|
69
|
-
const frame = lastFrame() ?? "";
|
|
70
|
-
expect(frame).toContain("B+");
|
|
71
|
-
expect(frame).toContain("78/100");
|
|
72
|
-
});
|
|
73
|
-
it("renders drift summary", () => {
|
|
74
|
-
const { lastFrame } = renderStatus({
|
|
75
|
-
...baseData,
|
|
76
|
-
drift: { modified: 5, added: 2, deleted: 1 },
|
|
77
|
-
});
|
|
78
|
-
const frame = lastFrame() ?? "";
|
|
79
|
-
expect(frame).toContain("5 modified");
|
|
80
|
-
expect(frame).toContain("2 added");
|
|
81
|
-
expect(frame).toContain("1 deleted");
|
|
82
|
-
});
|
|
83
|
-
it("renders live session tool calls", () => {
|
|
84
|
-
const { lastFrame } = renderStatus({
|
|
85
|
-
...baseData,
|
|
86
|
-
liveToolCalls: { local: 80 },
|
|
87
|
-
});
|
|
88
|
-
const frame = lastFrame() ?? "";
|
|
89
|
-
expect(frame).toContain("80 calls");
|
|
90
|
-
expect(frame).toContain("all local");
|
|
91
|
-
expect(frame).toContain("Local rate");
|
|
92
|
-
});
|
|
93
|
-
it("renders latency stats", () => {
|
|
94
|
-
const { lastFrame } = renderStatus({
|
|
95
|
-
...baseData,
|
|
96
|
-
latency: {
|
|
97
|
-
localP50: 0.8,
|
|
98
|
-
localP99: 3.2,
|
|
99
|
-
},
|
|
100
|
-
});
|
|
101
|
-
const frame = lastFrame() ?? "";
|
|
102
|
-
expect(frame).toContain("0.8ms");
|
|
103
|
-
});
|
|
104
|
-
it("shows budget warning when local p99 exceeds 5ms", () => {
|
|
105
|
-
const { lastFrame } = renderStatus({
|
|
106
|
-
...baseData,
|
|
107
|
-
latency: {
|
|
108
|
-
localP50: 2.0,
|
|
109
|
-
localP99: 7.5,
|
|
110
|
-
localBudgetExceeded: true,
|
|
111
|
-
},
|
|
112
|
-
});
|
|
113
|
-
expect(lastFrame()).toContain("⚠ >5ms");
|
|
114
|
-
});
|
|
115
|
-
it("renders deep link", () => {
|
|
116
|
-
const { lastFrame } = renderStatus({
|
|
117
|
-
...baseData,
|
|
118
|
-
deepLink: "https://app.unerr.dev/r/repo_abc123?view=drift",
|
|
119
|
-
});
|
|
120
|
-
expect(lastFrame()).toContain("https://app.unerr.dev/r/repo_abc123");
|
|
121
|
-
});
|
|
122
|
-
it("renders indexing status", () => {
|
|
123
|
-
const { lastFrame } = renderStatus({
|
|
124
|
-
...baseData,
|
|
125
|
-
indexingStatus: "Ready",
|
|
126
|
-
});
|
|
127
|
-
expect(lastFrame()).toContain("Ready");
|
|
128
|
-
});
|
|
129
|
-
it("renders minimal data gracefully", () => {
|
|
130
|
-
const { lastFrame } = renderStatus({
|
|
131
|
-
repoName: "(no repo)",
|
|
132
|
-
branch: "unknown",
|
|
133
|
-
proxyStatus: "Not running",
|
|
134
|
-
proxyRunning: false,
|
|
135
|
-
graphInfo: "No local graph",
|
|
136
|
-
});
|
|
137
|
-
const frame = lastFrame() ?? "";
|
|
138
|
-
expect(frame).toContain("(no repo)");
|
|
139
|
-
expect(frame).toContain("No local graph");
|
|
140
|
-
expect(frame).not.toContain("undefined");
|
|
141
|
-
});
|
|
142
|
-
});
|
|
@@ -1,292 +0,0 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, it } from "vitest";
|
|
2
|
-
import { initFactsSchema } from "../intelligence/facts-schema.js";
|
|
3
|
-
import { TemporalFactStore, } from "../intelligence/temporal-facts.js";
|
|
4
|
-
async function createTestDb() {
|
|
5
|
-
const cozoModule = await import("cozo-node");
|
|
6
|
-
const CozoDbConstructor = cozoModule.default
|
|
7
|
-
? cozoModule.default.CozoDb
|
|
8
|
-
: cozoModule.CozoDb;
|
|
9
|
-
return new CozoDbConstructor("mem", "");
|
|
10
|
-
}
|
|
11
|
-
describe("TemporalFactStore", () => {
|
|
12
|
-
let db;
|
|
13
|
-
let store;
|
|
14
|
-
beforeEach(async () => {
|
|
15
|
-
db = await createTestDb();
|
|
16
|
-
await initFactsSchema(db);
|
|
17
|
-
store = TemporalFactStore.fromDb(db);
|
|
18
|
-
});
|
|
19
|
-
describe("createFact", () => {
|
|
20
|
-
it("creates a new fact and returns fact_id", async () => {
|
|
21
|
-
const input = {
|
|
22
|
-
fact_type: "semantic",
|
|
23
|
-
scope: "project",
|
|
24
|
-
subject: "auth-module",
|
|
25
|
-
content: "The auth module uses JWT tokens for session management",
|
|
26
|
-
source: "agent_explicit",
|
|
27
|
-
};
|
|
28
|
-
const factId = await store.createFact(input);
|
|
29
|
-
expect(factId).toBeDefined();
|
|
30
|
-
expect(factId.length).toBe(36);
|
|
31
|
-
});
|
|
32
|
-
it("deduplicates on (fact_type, scope, subject) match", async () => {
|
|
33
|
-
const input = {
|
|
34
|
-
fact_type: "semantic",
|
|
35
|
-
scope: "project",
|
|
36
|
-
subject: "database",
|
|
37
|
-
content: "We use PostgreSQL",
|
|
38
|
-
source: "agent_explicit",
|
|
39
|
-
};
|
|
40
|
-
const id1 = await store.createFact(input);
|
|
41
|
-
const id2 = await store.createFact({
|
|
42
|
-
...input,
|
|
43
|
-
content: "We use PostgreSQL for all data",
|
|
44
|
-
});
|
|
45
|
-
expect(id2).toBe(id1);
|
|
46
|
-
});
|
|
47
|
-
it("truncates content at 280 characters", async () => {
|
|
48
|
-
const longContent = "x".repeat(500);
|
|
49
|
-
const factId = await store.createFact({
|
|
50
|
-
fact_type: "procedural",
|
|
51
|
-
scope: "project",
|
|
52
|
-
subject: "testing",
|
|
53
|
-
content: longContent,
|
|
54
|
-
source: "agent_explicit",
|
|
55
|
-
});
|
|
56
|
-
expect(factId).toBeDefined();
|
|
57
|
-
});
|
|
58
|
-
});
|
|
59
|
-
describe("convention fact type", () => {
|
|
60
|
-
it("creates and recalls a convention fact with slow decay", async () => {
|
|
61
|
-
const factId = await store.createFact({
|
|
62
|
-
fact_type: "convention",
|
|
63
|
-
scope: "project",
|
|
64
|
-
subject: "api-standards",
|
|
65
|
-
content: "All API handlers must return structured JSON responses",
|
|
66
|
-
source: "agent_explicit",
|
|
67
|
-
});
|
|
68
|
-
expect(factId).toBeDefined();
|
|
69
|
-
const facts = await store.recallByScope("project");
|
|
70
|
-
const convention = facts.find((f) => f.fact_type === "convention");
|
|
71
|
-
expect(convention).toBeDefined();
|
|
72
|
-
expect(convention.content).toBe("All API handlers must return structured JSON responses");
|
|
73
|
-
expect(convention.effective_confidence).toBeGreaterThan(0.9);
|
|
74
|
-
});
|
|
75
|
-
it("includes convention in getFactHealth by_type", async () => {
|
|
76
|
-
await store.createFact({
|
|
77
|
-
fact_type: "convention",
|
|
78
|
-
scope: "project",
|
|
79
|
-
subject: "naming",
|
|
80
|
-
content: "Use camelCase for all function names",
|
|
81
|
-
source: "agent_explicit",
|
|
82
|
-
});
|
|
83
|
-
const health = await store.getFactHealth();
|
|
84
|
-
expect(health.by_type.convention).toBe(1);
|
|
85
|
-
});
|
|
86
|
-
});
|
|
87
|
-
describe("recallByScope", () => {
|
|
88
|
-
it("recalls facts matching scope with decay applied", async () => {
|
|
89
|
-
await store.createFact({
|
|
90
|
-
fact_type: "semantic",
|
|
91
|
-
scope: "src/proxy/proxy.ts",
|
|
92
|
-
subject: "proxy",
|
|
93
|
-
content: "The proxy module owns all MCP communication",
|
|
94
|
-
source: "agent_explicit",
|
|
95
|
-
});
|
|
96
|
-
const facts = await store.recallByScope("src/proxy/proxy.ts");
|
|
97
|
-
expect(facts.length).toBe(1);
|
|
98
|
-
expect(facts[0].content).toBe("The proxy module owns all MCP communication");
|
|
99
|
-
expect(facts[0].effective_confidence).toBeGreaterThan(0);
|
|
100
|
-
});
|
|
101
|
-
it("returns empty array for unknown scope", async () => {
|
|
102
|
-
const facts = await store.recallByScope("nonexistent/file.ts");
|
|
103
|
-
expect(facts).toEqual([]);
|
|
104
|
-
});
|
|
105
|
-
});
|
|
106
|
-
describe("recallBySubject", () => {
|
|
107
|
-
it("recalls facts matching subject", async () => {
|
|
108
|
-
await store.createFact({
|
|
109
|
-
fact_type: "semantic",
|
|
110
|
-
scope: "project",
|
|
111
|
-
subject: "src/auth.ts::login",
|
|
112
|
-
content: "Login function requires rate limiting",
|
|
113
|
-
source: "agent_explicit",
|
|
114
|
-
});
|
|
115
|
-
const facts = await store.recallBySubject("src/auth.ts::login");
|
|
116
|
-
expect(facts.length).toBe(1);
|
|
117
|
-
expect(facts[0].subject).toBe("src/auth.ts::login");
|
|
118
|
-
});
|
|
119
|
-
});
|
|
120
|
-
describe("recallNegative", () => {
|
|
121
|
-
it("recalls all negative facts regardless of scope", async () => {
|
|
122
|
-
await store.createFact({
|
|
123
|
-
fact_type: "negative",
|
|
124
|
-
scope: "project",
|
|
125
|
-
subject: "testing",
|
|
126
|
-
content: "Don't mock CozoDB in integration tests",
|
|
127
|
-
source: "negative_knowledge",
|
|
128
|
-
});
|
|
129
|
-
await store.createFact({
|
|
130
|
-
fact_type: "semantic",
|
|
131
|
-
scope: "project",
|
|
132
|
-
subject: "architecture",
|
|
133
|
-
content: "Semantic fact that should not appear",
|
|
134
|
-
source: "agent_explicit",
|
|
135
|
-
});
|
|
136
|
-
const facts = await store.recallNegative();
|
|
137
|
-
expect(facts.length).toBe(1);
|
|
138
|
-
expect(facts[0].fact_type).toBe("negative");
|
|
139
|
-
});
|
|
140
|
-
});
|
|
141
|
-
describe("reinforceFact", () => {
|
|
142
|
-
it("increases reinforcement_count", async () => {
|
|
143
|
-
const factId = await store.createFact({
|
|
144
|
-
fact_type: "semantic",
|
|
145
|
-
scope: "src/index.ts",
|
|
146
|
-
subject: "arch",
|
|
147
|
-
content: "Microservices architecture",
|
|
148
|
-
source: "agent_explicit",
|
|
149
|
-
});
|
|
150
|
-
await store.reinforceFact(factId, {
|
|
151
|
-
session_id: "sess-1",
|
|
152
|
-
action: "reinforced",
|
|
153
|
-
timestamp: Date.now(),
|
|
154
|
-
});
|
|
155
|
-
const facts = await store.recallByScope("src/index.ts", 0);
|
|
156
|
-
const fact = facts.find((f) => f.fact_id === factId);
|
|
157
|
-
expect(fact).toBeDefined();
|
|
158
|
-
expect(fact.reinforcement_count).toBe(2);
|
|
159
|
-
});
|
|
160
|
-
it("increases effective confidence via evidence factor", async () => {
|
|
161
|
-
const factId = await store.createFact({
|
|
162
|
-
fact_type: "semantic",
|
|
163
|
-
scope: "src/util.ts",
|
|
164
|
-
subject: "utils",
|
|
165
|
-
content: "Utility functions are pure",
|
|
166
|
-
source: "convention_detector",
|
|
167
|
-
});
|
|
168
|
-
const before = await store.recallByScope("src/util.ts", 0);
|
|
169
|
-
const confBefore = before[0].effective_confidence;
|
|
170
|
-
await store.reinforceFact(factId, {
|
|
171
|
-
session_id: "s2",
|
|
172
|
-
action: "reinforced",
|
|
173
|
-
timestamp: Date.now(),
|
|
174
|
-
});
|
|
175
|
-
await store.reinforceFact(factId, {
|
|
176
|
-
session_id: "s3",
|
|
177
|
-
action: "reinforced",
|
|
178
|
-
timestamp: Date.now(),
|
|
179
|
-
});
|
|
180
|
-
const after = await store.recallByScope("src/util.ts", 0);
|
|
181
|
-
const confAfter = after[0].effective_confidence;
|
|
182
|
-
expect(confAfter).toBeGreaterThan(confBefore);
|
|
183
|
-
});
|
|
184
|
-
});
|
|
185
|
-
describe("contradictFact", () => {
|
|
186
|
-
it("halves base_confidence", async () => {
|
|
187
|
-
const factId = await store.createFact({
|
|
188
|
-
fact_type: "semantic",
|
|
189
|
-
scope: "src/db.ts",
|
|
190
|
-
subject: "db",
|
|
191
|
-
content: "We use MySQL",
|
|
192
|
-
source: "agent_explicit",
|
|
193
|
-
base_confidence: 0.9,
|
|
194
|
-
});
|
|
195
|
-
await store.contradictFact(factId, "Actually using PostgreSQL");
|
|
196
|
-
const facts = await store.recallByScope("src/db.ts", 0);
|
|
197
|
-
const fact = facts.find((f) => f.fact_id === factId);
|
|
198
|
-
expect(fact).toBeDefined();
|
|
199
|
-
expect(fact.base_confidence).toBeCloseTo(0.45, 1);
|
|
200
|
-
});
|
|
201
|
-
});
|
|
202
|
-
describe("getFactHealth", () => {
|
|
203
|
-
it("returns health summary with correct counts", async () => {
|
|
204
|
-
await store.createFact({
|
|
205
|
-
fact_type: "semantic",
|
|
206
|
-
scope: "project",
|
|
207
|
-
subject: "a",
|
|
208
|
-
content: "Fact A",
|
|
209
|
-
source: "agent_explicit",
|
|
210
|
-
});
|
|
211
|
-
await store.createFact({
|
|
212
|
-
fact_type: "negative",
|
|
213
|
-
scope: "project",
|
|
214
|
-
subject: "b",
|
|
215
|
-
content: "Fact B",
|
|
216
|
-
source: "negative_knowledge",
|
|
217
|
-
});
|
|
218
|
-
const health = await store.getFactHealth();
|
|
219
|
-
expect(health.total).toBe(2);
|
|
220
|
-
expect(health.by_type.semantic).toBe(1);
|
|
221
|
-
expect(health.by_type.negative).toBe(1);
|
|
222
|
-
expect(health.avg_confidence).toBeGreaterThan(0);
|
|
223
|
-
});
|
|
224
|
-
});
|
|
225
|
-
describe("pruneDecayed", () => {
|
|
226
|
-
it("removes facts below prune threshold", async () => {
|
|
227
|
-
const factId = await store.createFact({
|
|
228
|
-
fact_type: "semantic",
|
|
229
|
-
scope: "src/old.ts",
|
|
230
|
-
subject: "prune-target",
|
|
231
|
-
content: "Will be contradicted into oblivion",
|
|
232
|
-
source: "agent_explicit",
|
|
233
|
-
base_confidence: 0.1,
|
|
234
|
-
});
|
|
235
|
-
// Contradict multiple times to drive confidence below threshold
|
|
236
|
-
await store.contradictFact(factId, "wrong");
|
|
237
|
-
await store.contradictFact(factId, "still wrong");
|
|
238
|
-
await store.contradictFact(factId, "really wrong");
|
|
239
|
-
// 0.1 * 0.5^3 = 0.0125, evidence_factor = 1/3 = 0.33
|
|
240
|
-
// effective = 0.0125 * 1.0 * 0.33 = 0.004 — well below 0.05
|
|
241
|
-
const pruned = await store.pruneDecayed(0.05);
|
|
242
|
-
expect(pruned).toBe(1);
|
|
243
|
-
});
|
|
244
|
-
});
|
|
245
|
-
describe("recallForFile", () => {
|
|
246
|
-
it("combines scope + subject + negative facts", async () => {
|
|
247
|
-
await store.createFact({
|
|
248
|
-
fact_type: "semantic",
|
|
249
|
-
scope: "src/auth.ts",
|
|
250
|
-
subject: "auth",
|
|
251
|
-
content: "Auth uses bcrypt for password hashing",
|
|
252
|
-
source: "agent_explicit",
|
|
253
|
-
});
|
|
254
|
-
const negId = await store.createFact({
|
|
255
|
-
fact_type: "negative",
|
|
256
|
-
scope: "project",
|
|
257
|
-
subject: "security",
|
|
258
|
-
content: "Never store passwords in plain text",
|
|
259
|
-
source: "negative_knowledge",
|
|
260
|
-
base_confidence: 0.95,
|
|
261
|
-
});
|
|
262
|
-
// Reinforce to push evidence_factor above threshold boundary
|
|
263
|
-
await store.reinforceFact(negId, {
|
|
264
|
-
session_id: "s2",
|
|
265
|
-
action: "reinforced",
|
|
266
|
-
timestamp: Date.now(),
|
|
267
|
-
});
|
|
268
|
-
await store.reinforceFact(negId, {
|
|
269
|
-
session_id: "s3",
|
|
270
|
-
action: "reinforced",
|
|
271
|
-
timestamp: Date.now(),
|
|
272
|
-
});
|
|
273
|
-
await store.createFact({
|
|
274
|
-
fact_type: "procedural",
|
|
275
|
-
scope: "src/unrelated.ts",
|
|
276
|
-
subject: "unrelated",
|
|
277
|
-
content: "This should not appear",
|
|
278
|
-
source: "session_analysis",
|
|
279
|
-
});
|
|
280
|
-
const facts = await store.recallForFile("src/auth.ts", ["auth"]);
|
|
281
|
-
expect(facts.length).toBe(2);
|
|
282
|
-
const types = facts.map((f) => f.fact_type);
|
|
283
|
-
expect(types).toContain("semantic");
|
|
284
|
-
expect(types).toContain("negative");
|
|
285
|
-
});
|
|
286
|
-
});
|
|
287
|
-
describe("recordInteraction", () => {
|
|
288
|
-
it("records entity interaction without error", async () => {
|
|
289
|
-
await expect(store.recordInteraction("src/auth.ts::login", "session-abc", "read", "get_function", "success")).resolves.not.toThrow();
|
|
290
|
-
});
|
|
291
|
-
});
|
|
292
|
-
});
|
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
import { Hono } from "hono";
|
|
2
|
-
import { describe, expect, it, vi } from "vitest";
|
|
3
|
-
import { createTemporalRoutes, } from "../server/routes/temporal.js";
|
|
4
|
-
function makeFact(overrides = {}) {
|
|
5
|
-
return {
|
|
6
|
-
fact_id: "fact-001",
|
|
7
|
-
fact_type: "semantic",
|
|
8
|
-
scope: "project",
|
|
9
|
-
subject: "architecture",
|
|
10
|
-
content: "We use microservices",
|
|
11
|
-
base_confidence: 0.9,
|
|
12
|
-
effective_confidence: 0.85,
|
|
13
|
-
reinforcement_count: 3,
|
|
14
|
-
created_at: Date.now() - 86400000,
|
|
15
|
-
last_reinforced_at: Date.now() - 3600000,
|
|
16
|
-
last_contradicted_at: 0,
|
|
17
|
-
source: "agent_explicit",
|
|
18
|
-
...overrides,
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
function makeSession(overrides = {}) {
|
|
22
|
-
return {
|
|
23
|
-
session_id: "sess-test",
|
|
24
|
-
written_at: new Date().toISOString(),
|
|
25
|
-
started_at: new Date(Date.now() - 3600000).toISOString(),
|
|
26
|
-
ended_at: new Date().toISOString(),
|
|
27
|
-
duration_ms: 3600000,
|
|
28
|
-
tool_calls: 15,
|
|
29
|
-
chains: 4,
|
|
30
|
-
files_modified: ["src/a.ts"],
|
|
31
|
-
entities_touched: ["src/a.ts::fn"],
|
|
32
|
-
tools_used: { get_function: 10 },
|
|
33
|
-
feature_areas: ["src"],
|
|
34
|
-
facts_recorded: 1,
|
|
35
|
-
facts_surfaced: ["f1"],
|
|
36
|
-
revert_count: 0,
|
|
37
|
-
rot_score: 0.1,
|
|
38
|
-
token_estimate: 8000,
|
|
39
|
-
branch: "main",
|
|
40
|
-
...overrides,
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
function createTestDeps(overrides = {}) {
|
|
44
|
-
return {
|
|
45
|
-
factStore: {
|
|
46
|
-
recallByScope: vi.fn().mockResolvedValue([makeFact()]),
|
|
47
|
-
getFactHealth: vi.fn().mockResolvedValue({
|
|
48
|
-
total: 5,
|
|
49
|
-
active: 4,
|
|
50
|
-
decayed: 1,
|
|
51
|
-
by_type: { semantic: 2, procedural: 1, negative: 1, episodic: 1 },
|
|
52
|
-
avg_confidence: 0.72,
|
|
53
|
-
}),
|
|
54
|
-
reinforceFact: vi.fn().mockResolvedValue(undefined),
|
|
55
|
-
contradictFact: vi.fn().mockResolvedValue(undefined),
|
|
56
|
-
},
|
|
57
|
-
loadRecentSessions: vi.fn().mockReturnValue([makeSession()]),
|
|
58
|
-
emitEvent: vi.fn(),
|
|
59
|
-
...overrides,
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
describe("temporal routes", () => {
|
|
63
|
-
describe("GET /api/facts", () => {
|
|
64
|
-
it("returns facts with default filters", async () => {
|
|
65
|
-
const deps = createTestDeps();
|
|
66
|
-
const app = new Hono();
|
|
67
|
-
app.route("", createTemporalRoutes(deps));
|
|
68
|
-
const res = await app.request("/api/facts");
|
|
69
|
-
expect(res.status).toBe(200);
|
|
70
|
-
const body = await res.json();
|
|
71
|
-
expect(body.facts.length).toBe(1);
|
|
72
|
-
expect(body.facts[0].fact_id).toBe("fact-001");
|
|
73
|
-
expect(body.total).toBe(1);
|
|
74
|
-
});
|
|
75
|
-
it("returns empty when factStore is null", async () => {
|
|
76
|
-
const deps = createTestDeps({ factStore: null });
|
|
77
|
-
const app = new Hono();
|
|
78
|
-
app.route("", createTemporalRoutes(deps));
|
|
79
|
-
const res = await app.request("/api/facts");
|
|
80
|
-
expect(res.status).toBe(200);
|
|
81
|
-
const body = await res.json();
|
|
82
|
-
expect(body.facts).toEqual([]);
|
|
83
|
-
});
|
|
84
|
-
});
|
|
85
|
-
describe("GET /api/facts/health", () => {
|
|
86
|
-
it("returns health summary", async () => {
|
|
87
|
-
const deps = createTestDeps();
|
|
88
|
-
const app = new Hono();
|
|
89
|
-
app.route("", createTemporalRoutes(deps));
|
|
90
|
-
const res = await app.request("/api/facts/health");
|
|
91
|
-
expect(res.status).toBe(200);
|
|
92
|
-
const body = await res.json();
|
|
93
|
-
expect(body.total).toBe(5);
|
|
94
|
-
expect(body.active).toBe(4);
|
|
95
|
-
expect(body.avg_confidence).toBe(0.72);
|
|
96
|
-
});
|
|
97
|
-
});
|
|
98
|
-
describe("GET /api/sessions", () => {
|
|
99
|
-
it("returns recent sessions", async () => {
|
|
100
|
-
const deps = createTestDeps();
|
|
101
|
-
const app = new Hono();
|
|
102
|
-
app.route("", createTemporalRoutes(deps));
|
|
103
|
-
const res = await app.request("/api/sessions");
|
|
104
|
-
expect(res.status).toBe(200);
|
|
105
|
-
const body = await res.json();
|
|
106
|
-
expect(body.sessions.length).toBe(1);
|
|
107
|
-
expect(body.sessions[0].session_id).toBe("sess-test");
|
|
108
|
-
});
|
|
109
|
-
});
|
|
110
|
-
describe("POST /api/facts/:id/reinforce", () => {
|
|
111
|
-
it("reinforces a fact and emits event", async () => {
|
|
112
|
-
const deps = createTestDeps();
|
|
113
|
-
const app = new Hono();
|
|
114
|
-
app.route("", createTemporalRoutes(deps));
|
|
115
|
-
const res = await app.request("/api/facts/fact-001/reinforce", {
|
|
116
|
-
method: "POST",
|
|
117
|
-
});
|
|
118
|
-
expect(res.status).toBe(200);
|
|
119
|
-
const body = await res.json();
|
|
120
|
-
expect(body.success).toBe(true);
|
|
121
|
-
expect(body.action).toBe("reinforced");
|
|
122
|
-
expect(deps.factStore.reinforceFact).toHaveBeenCalledWith("fact-001", expect.objectContaining({ action: "reinforced" }));
|
|
123
|
-
expect(deps.emitEvent).toHaveBeenCalledWith("fact:reinforced", expect.objectContaining({ fact_id: "fact-001" }));
|
|
124
|
-
});
|
|
125
|
-
});
|
|
126
|
-
describe("DELETE /api/facts/:id", () => {
|
|
127
|
-
it("dismisses a fact and emits event", async () => {
|
|
128
|
-
const deps = createTestDeps();
|
|
129
|
-
const app = new Hono();
|
|
130
|
-
app.route("", createTemporalRoutes(deps));
|
|
131
|
-
const res = await app.request("/api/facts/fact-001", {
|
|
132
|
-
method: "DELETE",
|
|
133
|
-
});
|
|
134
|
-
expect(res.status).toBe(200);
|
|
135
|
-
const body = await res.json();
|
|
136
|
-
expect(body.success).toBe(true);
|
|
137
|
-
expect(body.action).toBe("dismissed");
|
|
138
|
-
expect(deps.factStore.contradictFact).toHaveBeenCalledWith("fact-001", "Manually dismissed from dashboard");
|
|
139
|
-
expect(deps.emitEvent).toHaveBeenCalledWith("fact:expired", expect.objectContaining({ fact_id: "fact-001" }));
|
|
140
|
-
});
|
|
141
|
-
});
|
|
142
|
-
});
|