@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,98 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Decision Point Detector tests — tool→decision level mapping, pre-edit detection.
|
|
3
|
-
*/
|
|
4
|
-
import { describe, expect, it } from "vitest";
|
|
5
|
-
import { DecisionPointDetector, getDecisionPointDetector, } from "../intelligence/decision-point-detector.js";
|
|
6
|
-
import { SessionContext } from "../intelligence/session-context.js";
|
|
7
|
-
describe("DecisionPointDetector", () => {
|
|
8
|
-
const detector = new DecisionPointDetector();
|
|
9
|
-
function makeSession(historyEntities = []) {
|
|
10
|
-
const ctx = new SessionContext();
|
|
11
|
-
for (const key of historyEntities) {
|
|
12
|
-
ctx.recordEntityHistory(key, 3, "normal");
|
|
13
|
-
}
|
|
14
|
-
return ctx;
|
|
15
|
-
}
|
|
16
|
-
describe("exploration tools → low", () => {
|
|
17
|
-
const explorationTools = [
|
|
18
|
-
"file_outline",
|
|
19
|
-
"get_project_stats",
|
|
20
|
-
"get_conventions",
|
|
21
|
-
"get_critical_nodes",
|
|
22
|
-
"get_cross_boundary_links",
|
|
23
|
-
];
|
|
24
|
-
for (const tool of explorationTools) {
|
|
25
|
-
it(`${tool} → low`, () => {
|
|
26
|
-
const level = detector.detect(tool, {}, makeSession());
|
|
27
|
-
expect(level).toBe("low");
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
|
-
});
|
|
31
|
-
describe("understanding tools → medium", () => {
|
|
32
|
-
const understandingTools = [
|
|
33
|
-
"get_references",
|
|
34
|
-
"get_imports",
|
|
35
|
-
"get_callers",
|
|
36
|
-
"get_callees",
|
|
37
|
-
"search_code",
|
|
38
|
-
"file_connections",
|
|
39
|
-
"get_test_coverage",
|
|
40
|
-
];
|
|
41
|
-
for (const tool of understandingTools) {
|
|
42
|
-
it(`${tool} → medium`, () => {
|
|
43
|
-
const level = detector.detect(tool, {}, makeSession());
|
|
44
|
-
expect(level).toBe("medium");
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
describe("pre-edit tools", () => {
|
|
49
|
-
it("file_read without history → medium", () => {
|
|
50
|
-
const level = detector.detect("file_read", { key: "fn1" }, makeSession());
|
|
51
|
-
expect(level).toBe("medium");
|
|
52
|
-
});
|
|
53
|
-
it("file_read with prior entity history → high", () => {
|
|
54
|
-
const level = detector.detect("file_read", { key: "fn1" }, makeSession(["fn1"]));
|
|
55
|
-
expect(level).toBe("high");
|
|
56
|
-
});
|
|
57
|
-
it("get_entity with prior history → high", () => {
|
|
58
|
-
const level = detector.detect("get_entity", { key: "myClass" }, makeSession(["myClass"]));
|
|
59
|
-
expect(level).toBe("high");
|
|
60
|
-
});
|
|
61
|
-
it("get_function with prior history → high", () => {
|
|
62
|
-
const level = detector.detect("get_function", { key: "doStuff" }, makeSession(["doStuff"]));
|
|
63
|
-
expect(level).toBe("high");
|
|
64
|
-
});
|
|
65
|
-
it("file_read with purpose=edit → high (even without history)", () => {
|
|
66
|
-
const level = detector.detect("file_read", { key: "fn1", purpose: "edit" }, makeSession());
|
|
67
|
-
expect(level).toBe("high");
|
|
68
|
-
});
|
|
69
|
-
it("get_file without history → medium", () => {
|
|
70
|
-
const level = detector.detect("get_file", { name: "src/index.ts" }, makeSession());
|
|
71
|
-
expect(level).toBe("medium");
|
|
72
|
-
});
|
|
73
|
-
});
|
|
74
|
-
describe("unknown tools → medium", () => {
|
|
75
|
-
it("unknown tool defaults to medium", () => {
|
|
76
|
-
const level = detector.detect("some_new_tool", {}, makeSession());
|
|
77
|
-
expect(level).toBe("medium");
|
|
78
|
-
});
|
|
79
|
-
});
|
|
80
|
-
describe("getMaxSignals", () => {
|
|
81
|
-
it("high → 5", () => {
|
|
82
|
-
expect(detector.getMaxSignals("high")).toBe(5);
|
|
83
|
-
});
|
|
84
|
-
it("medium → 3", () => {
|
|
85
|
-
expect(detector.getMaxSignals("medium")).toBe(3);
|
|
86
|
-
});
|
|
87
|
-
it("low → 2", () => {
|
|
88
|
-
expect(detector.getMaxSignals("low")).toBe(2);
|
|
89
|
-
});
|
|
90
|
-
});
|
|
91
|
-
describe("singleton", () => {
|
|
92
|
-
it("returns same instance", () => {
|
|
93
|
-
const a = getDecisionPointDetector();
|
|
94
|
-
const b = getDecisionPointDetector();
|
|
95
|
-
expect(a).toBe(b);
|
|
96
|
-
});
|
|
97
|
-
});
|
|
98
|
-
});
|
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for deep-link.ts (Task 1.6) — URL generation for CLI → web dashboard bridge.
|
|
3
|
-
*
|
|
4
|
-
* Tests validate:
|
|
5
|
-
* - buildDeepLink(): correct URL structure with repo path and query params
|
|
6
|
-
* - utm_source: always present (default "cli", overridable)
|
|
7
|
-
* - View targeting: health, drift, timeline, graph views
|
|
8
|
-
* - Entity/intent list serialization (comma-joined)
|
|
9
|
-
* - Fallback: returns generic landing when repoId is missing
|
|
10
|
-
*/
|
|
11
|
-
import { describe, expect, it } from "vitest";
|
|
12
|
-
import { buildDeepLink } from "../utils/deep-link.js";
|
|
13
|
-
describe("Deep-Link URL Generation (1.6)", () => {
|
|
14
|
-
// ── Basic URL structure ────────────────────────────────────────
|
|
15
|
-
describe("basic URL structure", () => {
|
|
16
|
-
it("generates URL with repo path and default utm_source", () => {
|
|
17
|
-
const url = buildDeepLink("repo_abc123");
|
|
18
|
-
expect(url).toBe("https://app.unerr.dev/r/repo_abc123?utm_source=cli");
|
|
19
|
-
});
|
|
20
|
-
it("includes view parameter when specified", () => {
|
|
21
|
-
const url = buildDeepLink("repo_abc123", { view: "health" });
|
|
22
|
-
const parsed = new URL(url);
|
|
23
|
-
expect(parsed.pathname).toBe("/r/repo_abc123");
|
|
24
|
-
expect(parsed.searchParams.get("view")).toBe("health");
|
|
25
|
-
expect(parsed.searchParams.get("utm_source")).toBe("cli");
|
|
26
|
-
});
|
|
27
|
-
it("includes branch parameter", () => {
|
|
28
|
-
const url = buildDeepLink("repo_abc123", { branch: "feature/auth" });
|
|
29
|
-
const parsed = new URL(url);
|
|
30
|
-
expect(parsed.searchParams.get("branch")).toBe("feature/auth");
|
|
31
|
-
});
|
|
32
|
-
it("serializes entities as comma-joined list", () => {
|
|
33
|
-
const url = buildDeepLink("repo_abc123", {
|
|
34
|
-
entities: ["fn_pay", "cls_user", "fn_validate"],
|
|
35
|
-
});
|
|
36
|
-
const parsed = new URL(url);
|
|
37
|
-
expect(parsed.searchParams.get("entities")).toBe("fn_pay,cls_user,fn_validate");
|
|
38
|
-
});
|
|
39
|
-
it("serializes intents as comma-joined list", () => {
|
|
40
|
-
const url = buildDeepLink("repo_abc123", {
|
|
41
|
-
intents: ["intent_001", "intent_002"],
|
|
42
|
-
});
|
|
43
|
-
const parsed = new URL(url);
|
|
44
|
-
expect(parsed.searchParams.get("intents")).toBe("intent_001,intent_002");
|
|
45
|
-
});
|
|
46
|
-
});
|
|
47
|
-
// ── utm_source tracking ────────────────────────────────────────
|
|
48
|
-
describe("utm_source tracking", () => {
|
|
49
|
-
it("defaults to 'cli' when no utm_source provided", () => {
|
|
50
|
-
const url = buildDeepLink("repo_abc123");
|
|
51
|
-
const parsed = new URL(url);
|
|
52
|
-
expect(parsed.searchParams.get("utm_source")).toBe("cli");
|
|
53
|
-
});
|
|
54
|
-
it("uses custom utm_source for startup context", () => {
|
|
55
|
-
const url = buildDeepLink("repo_abc123", {
|
|
56
|
-
utm_source: "cli_startup",
|
|
57
|
-
});
|
|
58
|
-
const parsed = new URL(url);
|
|
59
|
-
expect(parsed.searchParams.get("utm_source")).toBe("cli_startup");
|
|
60
|
-
});
|
|
61
|
-
it("uses custom utm_source for first boot health shock", () => {
|
|
62
|
-
const url = buildDeepLink("repo_abc123", {
|
|
63
|
-
view: "health",
|
|
64
|
-
utm_source: "cli_first_boot",
|
|
65
|
-
});
|
|
66
|
-
const parsed = new URL(url);
|
|
67
|
-
expect(parsed.searchParams.get("utm_source")).toBe("cli_first_boot");
|
|
68
|
-
});
|
|
69
|
-
it("uses custom utm_source for session summary", () => {
|
|
70
|
-
const url = buildDeepLink("repo_abc123", {
|
|
71
|
-
view: "timeline",
|
|
72
|
-
intents: ["i1"],
|
|
73
|
-
utm_source: "cli_session",
|
|
74
|
-
});
|
|
75
|
-
const parsed = new URL(url);
|
|
76
|
-
expect(parsed.searchParams.get("utm_source")).toBe("cli_session");
|
|
77
|
-
});
|
|
78
|
-
});
|
|
79
|
-
// ── View types ─────────────────────────────────────────────────
|
|
80
|
-
describe("view types", () => {
|
|
81
|
-
for (const view of ["health", "drift", "timeline", "graph"]) {
|
|
82
|
-
it(`generates correct URL for '${view}' view`, () => {
|
|
83
|
-
const url = buildDeepLink("repo_123", { view });
|
|
84
|
-
const parsed = new URL(url);
|
|
85
|
-
expect(parsed.searchParams.get("view")).toBe(view);
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
// ── Combined parameters ────────────────────────────────────────
|
|
90
|
-
describe("combined parameters", () => {
|
|
91
|
-
it("generates URL with all parameters", () => {
|
|
92
|
-
const url = buildDeepLink("repo_abc123", {
|
|
93
|
-
view: "drift",
|
|
94
|
-
branch: "main",
|
|
95
|
-
entities: ["fn_a", "fn_b"],
|
|
96
|
-
intents: ["intent_1"],
|
|
97
|
-
utm_source: "cli_status",
|
|
98
|
-
});
|
|
99
|
-
const parsed = new URL(url);
|
|
100
|
-
expect(parsed.pathname).toBe("/r/repo_abc123");
|
|
101
|
-
expect(parsed.searchParams.get("view")).toBe("drift");
|
|
102
|
-
expect(parsed.searchParams.get("branch")).toBe("main");
|
|
103
|
-
expect(parsed.searchParams.get("entities")).toBe("fn_a,fn_b");
|
|
104
|
-
expect(parsed.searchParams.get("intents")).toBe("intent_1");
|
|
105
|
-
expect(parsed.searchParams.get("utm_source")).toBe("cli_status");
|
|
106
|
-
});
|
|
107
|
-
it("omits empty optional parameters", () => {
|
|
108
|
-
const url = buildDeepLink("repo_abc123", { utm_source: "cli" });
|
|
109
|
-
const parsed = new URL(url);
|
|
110
|
-
expect(parsed.searchParams.has("view")).toBe(false);
|
|
111
|
-
expect(parsed.searchParams.has("branch")).toBe(false);
|
|
112
|
-
expect(parsed.searchParams.has("entities")).toBe(false);
|
|
113
|
-
expect(parsed.searchParams.has("intents")).toBe(false);
|
|
114
|
-
});
|
|
115
|
-
it("omits entities when array is empty", () => {
|
|
116
|
-
const url = buildDeepLink("repo_abc123", { entities: [] });
|
|
117
|
-
const parsed = new URL(url);
|
|
118
|
-
expect(parsed.searchParams.has("entities")).toBe(false);
|
|
119
|
-
});
|
|
120
|
-
it("omits intents when array is empty", () => {
|
|
121
|
-
const url = buildDeepLink("repo_abc123", { intents: [] });
|
|
122
|
-
const parsed = new URL(url);
|
|
123
|
-
expect(parsed.searchParams.has("intents")).toBe(false);
|
|
124
|
-
});
|
|
125
|
-
});
|
|
126
|
-
// ── Fallback behavior ──────────────────────────────────────────
|
|
127
|
-
describe("fallback when repoId unavailable", () => {
|
|
128
|
-
it("returns generic landing URL when repoId is undefined", () => {
|
|
129
|
-
const url = buildDeepLink(undefined);
|
|
130
|
-
expect(url).toBe("https://app.unerr.dev?utm_source=cli");
|
|
131
|
-
});
|
|
132
|
-
it("returns generic landing URL when repoId is empty string", () => {
|
|
133
|
-
const url = buildDeepLink("");
|
|
134
|
-
expect(url).toBe("https://app.unerr.dev?utm_source=cli");
|
|
135
|
-
});
|
|
136
|
-
it("preserves utm_source on fallback URL", () => {
|
|
137
|
-
const url = buildDeepLink(undefined, {
|
|
138
|
-
utm_source: "cli_startup",
|
|
139
|
-
});
|
|
140
|
-
expect(url).toBe("https://app.unerr.dev?utm_source=cli_startup");
|
|
141
|
-
});
|
|
142
|
-
});
|
|
143
|
-
});
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* S8: --disallowedTools integration tests.
|
|
3
|
-
*
|
|
4
|
-
* Tests: add/remove permissions.deny entries in .claude/settings.json.
|
|
5
|
-
*/
|
|
6
|
-
import { mkdirSync, readFileSync, rmSync, writeFileSync, } from "node:fs";
|
|
7
|
-
import { tmpdir } from "node:os";
|
|
8
|
-
import { join } from "node:path";
|
|
9
|
-
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
10
|
-
import { addDisallowedTools, removeDisallowedTools, } from "../config/claude-settings-hooks.js";
|
|
11
|
-
let testDir;
|
|
12
|
-
beforeEach(() => {
|
|
13
|
-
testDir = join(tmpdir(), `unerr-disallow-test-${Date.now()}`);
|
|
14
|
-
mkdirSync(testDir, { recursive: true });
|
|
15
|
-
});
|
|
16
|
-
afterEach(() => {
|
|
17
|
-
try {
|
|
18
|
-
rmSync(testDir, { recursive: true, force: true });
|
|
19
|
-
}
|
|
20
|
-
catch {
|
|
21
|
-
// Cleanup best-effort
|
|
22
|
-
}
|
|
23
|
-
});
|
|
24
|
-
// DISALLOWED_TOOLS was reduced from ["Read", "Grep", "Glob"] to ["Grep", "Glob"].
|
|
25
|
-
// Read is required by the native Edit workflow (Edit refuses to run if Read
|
|
26
|
-
// wasn't called first), so denying it breaks editing across all agents.
|
|
27
|
-
// `addDisallowedTools` also migrates existing settings: if "Read" was previously
|
|
28
|
-
// denied, it gets removed. See src/config/claude-settings-hooks.ts:200-242.
|
|
29
|
-
describe("addDisallowedTools", () => {
|
|
30
|
-
it("creates settings with deny entries when no settings exist", () => {
|
|
31
|
-
const result = addDisallowedTools(testDir);
|
|
32
|
-
expect(result.added).toBe(2); // Grep + Glob
|
|
33
|
-
const settings = JSON.parse(readFileSync(join(testDir, ".claude", "settings.json"), "utf-8"));
|
|
34
|
-
expect(settings.permissions.deny).not.toContain("Read"); // intentionally excluded
|
|
35
|
-
expect(settings.permissions.deny).toContain("Grep");
|
|
36
|
-
expect(settings.permissions.deny).toContain("Glob");
|
|
37
|
-
});
|
|
38
|
-
it("adds deny entries to existing settings without overwriting", () => {
|
|
39
|
-
const dir = join(testDir, ".claude");
|
|
40
|
-
mkdirSync(dir, { recursive: true });
|
|
41
|
-
writeFileSync(join(dir, "settings.json"), JSON.stringify({ someExisting: true }, null, 2));
|
|
42
|
-
const result = addDisallowedTools(testDir);
|
|
43
|
-
expect(result.added).toBe(2);
|
|
44
|
-
const settings = JSON.parse(readFileSync(join(dir, "settings.json"), "utf-8"));
|
|
45
|
-
expect(settings.someExisting).toBe(true);
|
|
46
|
-
expect(settings.permissions.deny).toContain("Grep");
|
|
47
|
-
expect(settings.permissions.deny).toContain("Glob");
|
|
48
|
-
});
|
|
49
|
-
it("preserves existing non-managed deny entries", () => {
|
|
50
|
-
const dir = join(testDir, ".claude");
|
|
51
|
-
mkdirSync(dir, { recursive: true });
|
|
52
|
-
writeFileSync(join(dir, "settings.json"), JSON.stringify({ permissions: { deny: ["SomeOtherTool"] } }, null, 2));
|
|
53
|
-
addDisallowedTools(testDir);
|
|
54
|
-
const settings = JSON.parse(readFileSync(join(dir, "settings.json"), "utf-8"));
|
|
55
|
-
expect(settings.permissions.deny).toContain("SomeOtherTool");
|
|
56
|
-
expect(settings.permissions.deny).toContain("Grep");
|
|
57
|
-
expect(settings.permissions.deny).toContain("Glob");
|
|
58
|
-
expect(settings.permissions.deny).toHaveLength(3); // 1 existing + 2 new
|
|
59
|
-
});
|
|
60
|
-
it("is idempotent — skips already-denied tools", () => {
|
|
61
|
-
addDisallowedTools(testDir);
|
|
62
|
-
const result = addDisallowedTools(testDir);
|
|
63
|
-
expect(result.added).toBe(0);
|
|
64
|
-
const settings = JSON.parse(readFileSync(join(testDir, ".claude", "settings.json"), "utf-8"));
|
|
65
|
-
// No duplicates — only Grep + Glob
|
|
66
|
-
expect(settings.permissions.deny).toHaveLength(2);
|
|
67
|
-
});
|
|
68
|
-
});
|
|
69
|
-
describe("removeDisallowedTools", () => {
|
|
70
|
-
it("removes deny entries", () => {
|
|
71
|
-
addDisallowedTools(testDir);
|
|
72
|
-
const removed = removeDisallowedTools(testDir);
|
|
73
|
-
expect(removed).toBe(true);
|
|
74
|
-
const settings = JSON.parse(readFileSync(join(testDir, ".claude", "settings.json"), "utf-8"));
|
|
75
|
-
// permissions.deny cleaned up entirely
|
|
76
|
-
expect(settings.permissions).toBeUndefined();
|
|
77
|
-
});
|
|
78
|
-
it("preserves non-unerr deny entries", () => {
|
|
79
|
-
const dir = join(testDir, ".claude");
|
|
80
|
-
mkdirSync(dir, { recursive: true });
|
|
81
|
-
writeFileSync(join(dir, "settings.json"), JSON.stringify({ permissions: { deny: ["Read", "Grep", "Glob", "SomeOtherTool"] } }, null, 2));
|
|
82
|
-
removeDisallowedTools(testDir);
|
|
83
|
-
const settings = JSON.parse(readFileSync(join(dir, "settings.json"), "utf-8"));
|
|
84
|
-
// Read is no longer unerr-managed, so it survives a `remove`. Migration
|
|
85
|
-
// away from Read happens only on `add`.
|
|
86
|
-
expect(settings.permissions.deny).toEqual(["Read", "SomeOtherTool"]);
|
|
87
|
-
});
|
|
88
|
-
it("returns false when no settings file exists", () => {
|
|
89
|
-
expect(removeDisallowedTools(testDir)).toBe(false);
|
|
90
|
-
});
|
|
91
|
-
it("returns false when no deny entries exist", () => {
|
|
92
|
-
const dir = join(testDir, ".claude");
|
|
93
|
-
mkdirSync(dir, { recursive: true });
|
|
94
|
-
writeFileSync(join(dir, "settings.json"), JSON.stringify({}, null, 2));
|
|
95
|
-
expect(removeDisallowedTools(testDir)).toBe(false);
|
|
96
|
-
});
|
|
97
|
-
it("returns false when deny list has no unerr entries", () => {
|
|
98
|
-
const dir = join(testDir, ".claude");
|
|
99
|
-
mkdirSync(dir, { recursive: true });
|
|
100
|
-
writeFileSync(join(dir, "settings.json"), JSON.stringify({ permissions: { deny: ["SomeOtherTool"] } }, null, 2));
|
|
101
|
-
expect(removeDisallowedTools(testDir)).toBe(false);
|
|
102
|
-
});
|
|
103
|
-
});
|
|
104
|
-
describe("add + remove roundtrip", () => {
|
|
105
|
-
it("leaves settings clean after roundtrip", () => {
|
|
106
|
-
const dir = join(testDir, ".claude");
|
|
107
|
-
mkdirSync(dir, { recursive: true });
|
|
108
|
-
writeFileSync(join(dir, "settings.json"), JSON.stringify({ hooks: { PreToolUse: [] } }, null, 2));
|
|
109
|
-
addDisallowedTools(testDir);
|
|
110
|
-
removeDisallowedTools(testDir);
|
|
111
|
-
const settings = JSON.parse(readFileSync(join(dir, "settings.json"), "utf-8"));
|
|
112
|
-
expect(settings.hooks).toBeDefined(); // preserved
|
|
113
|
-
expect(settings.permissions).toBeUndefined(); // cleaned up
|
|
114
|
-
});
|
|
115
|
-
});
|