@unerr-ai/unerr 0.1.5 → 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 +39151 -36992
- 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-BsMTQdhX.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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unerr-ai/unerr",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "Local-first code intelligence CLI for unerr",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"format": "biome format --write src/",
|
|
25
25
|
"typecheck": "tsc --noEmit",
|
|
26
26
|
"test:local-mode": "vitest run src/__tests__/local-mode-offline.test.ts src/__tests__/network-boundary.test.ts",
|
|
27
|
+
"check:tool-budget": "tsx scripts/check-tool-budget.ts",
|
|
27
28
|
"postinstall": "node scripts/postinstall.mjs || true"
|
|
28
29
|
},
|
|
29
30
|
"dependencies": {
|
|
@@ -46,6 +47,7 @@
|
|
|
46
47
|
"commander": "^12.1.0",
|
|
47
48
|
"consola": "^3.4.2",
|
|
48
49
|
"cozo-node": "^0.7.6",
|
|
50
|
+
"gpt-tokenizer": "^3.4.0",
|
|
49
51
|
"graphology": "^0.25.4",
|
|
50
52
|
"graphology-communities-louvain": "^2.0.1",
|
|
51
53
|
"graphology-layout-forceatlas2": "^0.10.1",
|
|
@@ -94,7 +96,12 @@
|
|
|
94
96
|
"node": ">=20.9.0"
|
|
95
97
|
},
|
|
96
98
|
"files": [
|
|
97
|
-
"dist",
|
|
99
|
+
"dist/**/*.js",
|
|
100
|
+
"dist/**/*.d.ts",
|
|
101
|
+
"dist/**/*.json",
|
|
102
|
+
"dist/**/*.wasm",
|
|
103
|
+
"!dist/__tests__/**",
|
|
104
|
+
"!dist/ui/**",
|
|
98
105
|
"scripts/postinstall.mjs"
|
|
99
106
|
],
|
|
100
107
|
"license": "Elastic-2.0",
|
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* BA-3.4: Architecture Boundary Guard tests.
|
|
3
|
-
*
|
|
4
|
-
* Verifies:
|
|
5
|
-
* - Cross-community imports blocked
|
|
6
|
-
* - Type imports allowed (configurable)
|
|
7
|
-
* - Override comment works
|
|
8
|
-
* - Auto-bridge after 3+ overrides
|
|
9
|
-
* - Non-edit tools ignored
|
|
10
|
-
* - Non-relative imports ignored
|
|
11
|
-
*/
|
|
12
|
-
import { describe, expect, it } from "vitest";
|
|
13
|
-
import { ArchitectureBoundaryGuard } from "../behaviors/architecture-guard.js";
|
|
14
|
-
function makeCtx(overrides = {}) {
|
|
15
|
-
return {
|
|
16
|
-
toolName: "edit_file",
|
|
17
|
-
args: {},
|
|
18
|
-
sessionId: "test-session",
|
|
19
|
-
...overrides,
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
describe("Architecture Boundary Guard (BA-3.1)", () => {
|
|
23
|
-
describe("Behavior Identity", () => {
|
|
24
|
-
it("has correct id, hooks, and default level", () => {
|
|
25
|
-
const guard = new ArchitectureBoundaryGuard();
|
|
26
|
-
expect(guard.id).toBe("architecture_boundary");
|
|
27
|
-
expect(guard.hooks).toContain("pre_tool_use");
|
|
28
|
-
expect(guard.defaultLevel).toBe("enforcement");
|
|
29
|
-
});
|
|
30
|
-
});
|
|
31
|
-
describe("Import Parsing", () => {
|
|
32
|
-
it("returns null for non-edit tools", async () => {
|
|
33
|
-
const guard = new ArchitectureBoundaryGuard();
|
|
34
|
-
const ctx = makeCtx({ toolName: "get_entity" });
|
|
35
|
-
expect(await guard.onPreToolUse(ctx)).toBeNull();
|
|
36
|
-
});
|
|
37
|
-
it("returns null for non-code files", async () => {
|
|
38
|
-
const guard = new ArchitectureBoundaryGuard();
|
|
39
|
-
const ctx = makeCtx({
|
|
40
|
-
args: {
|
|
41
|
-
path: "README.md",
|
|
42
|
-
content: "import { foo } from './bar'",
|
|
43
|
-
},
|
|
44
|
-
});
|
|
45
|
-
expect(await guard.onPreToolUse(ctx)).toBeNull();
|
|
46
|
-
});
|
|
47
|
-
it("returns null when no graph attached", async () => {
|
|
48
|
-
const guard = new ArchitectureBoundaryGuard();
|
|
49
|
-
const ctx = makeCtx({
|
|
50
|
-
filePath: "src/proxy/handler.ts",
|
|
51
|
-
args: {
|
|
52
|
-
path: "src/proxy/handler.ts",
|
|
53
|
-
content: `import { ShadowLedger } from '../../tracking/shadow-ledger'`,
|
|
54
|
-
},
|
|
55
|
-
});
|
|
56
|
-
expect(await guard.onPreToolUse(ctx)).toBeNull();
|
|
57
|
-
});
|
|
58
|
-
it("ignores non-relative imports (npm packages)", async () => {
|
|
59
|
-
const guard = new ArchitectureBoundaryGuard();
|
|
60
|
-
const ctx = makeCtx({
|
|
61
|
-
filePath: "src/proxy/handler.ts",
|
|
62
|
-
args: {
|
|
63
|
-
path: "src/proxy/handler.ts",
|
|
64
|
-
content: `import { Server } from "@modelcontextprotocol/sdk/server/index.js"`,
|
|
65
|
-
},
|
|
66
|
-
});
|
|
67
|
-
expect(await guard.onPreToolUse(ctx)).toBeNull();
|
|
68
|
-
});
|
|
69
|
-
});
|
|
70
|
-
describe("Type Import Allowance", () => {
|
|
71
|
-
it("allows type imports by default", () => {
|
|
72
|
-
const guard = new ArchitectureBoundaryGuard();
|
|
73
|
-
expect(guard.getSessionStats().typeImportsAllowed).toBe(0);
|
|
74
|
-
});
|
|
75
|
-
it("can disable type import allowance via config", () => {
|
|
76
|
-
const guard = new ArchitectureBoundaryGuard({ allowTypeImports: false });
|
|
77
|
-
expect(guard.enabled).toBe(true);
|
|
78
|
-
});
|
|
79
|
-
});
|
|
80
|
-
describe("Override Comment", () => {
|
|
81
|
-
it("records overrides from @unerr-allow comments", () => {
|
|
82
|
-
const guard = new ArchitectureBoundaryGuard();
|
|
83
|
-
const stats = guard.getSessionStats();
|
|
84
|
-
expect(stats.overridesRecorded).toBe(0);
|
|
85
|
-
});
|
|
86
|
-
});
|
|
87
|
-
describe("Auto-Bridge", () => {
|
|
88
|
-
it("starts with no auto-bridges", () => {
|
|
89
|
-
const guard = new ArchitectureBoundaryGuard();
|
|
90
|
-
expect(guard.getSessionStats().autoBridges).toBe(0);
|
|
91
|
-
});
|
|
92
|
-
it("bridge threshold is 3 by default", () => {
|
|
93
|
-
const guard = new ArchitectureBoundaryGuard();
|
|
94
|
-
expect(guard.getBridges().size).toBe(0);
|
|
95
|
-
});
|
|
96
|
-
it("accepts custom bridge threshold", () => {
|
|
97
|
-
const guard = new ArchitectureBoundaryGuard({ bridgeThreshold: 5 });
|
|
98
|
-
expect(guard.enabled).toBe(true);
|
|
99
|
-
});
|
|
100
|
-
});
|
|
101
|
-
describe("Session Stats", () => {
|
|
102
|
-
it("starts with clean stats", () => {
|
|
103
|
-
const guard = new ArchitectureBoundaryGuard();
|
|
104
|
-
const stats = guard.getSessionStats();
|
|
105
|
-
expect(stats.violationsBlocked).toBe(0);
|
|
106
|
-
expect(stats.typeImportsAllowed).toBe(0);
|
|
107
|
-
expect(stats.overridesRecorded).toBe(0);
|
|
108
|
-
expect(stats.autoBridges).toBe(0);
|
|
109
|
-
});
|
|
110
|
-
});
|
|
111
|
-
describe("Learning Loop", () => {
|
|
112
|
-
it("tracks feedback correctly", () => {
|
|
113
|
-
const guard = new ArchitectureBoundaryGuard();
|
|
114
|
-
guard.recordFeedback("accepted");
|
|
115
|
-
guard.recordFeedback("overridden");
|
|
116
|
-
const stats = guard.getLearningStats();
|
|
117
|
-
expect(stats.accepted).toBe(1);
|
|
118
|
-
expect(stats.overridden).toBe(1);
|
|
119
|
-
expect(stats.confidence).toBeCloseTo(0.5, 2);
|
|
120
|
-
});
|
|
121
|
-
});
|
|
122
|
-
});
|
|
@@ -1,205 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Boundary validation tests — guard against the silent-failure regression
|
|
3
|
-
* where missing required params or aliased param names reached handlers,
|
|
4
|
-
* ran queries with undefined filters, and returned empty results agents
|
|
5
|
-
* mistook for "graph has no data".
|
|
6
|
-
*/
|
|
7
|
-
import { describe, expect, it } from "vitest";
|
|
8
|
-
import { aliasAndValidate, normalizeArgAliases, validateRequiredArgs, } from "../proxy/arg-validator.js";
|
|
9
|
-
const GET_REFERENCES_DEF = {
|
|
10
|
-
name: "get_references",
|
|
11
|
-
inputSchema: {
|
|
12
|
-
type: "object",
|
|
13
|
-
properties: {
|
|
14
|
-
key: { type: "string", description: "Entity key" },
|
|
15
|
-
entity_name: { type: "string", description: "Alias for key" },
|
|
16
|
-
direction: { type: "string" },
|
|
17
|
-
limit: { type: "number" },
|
|
18
|
-
},
|
|
19
|
-
required: ["key"],
|
|
20
|
-
},
|
|
21
|
-
};
|
|
22
|
-
const RECALL_FACTS_DEF = {
|
|
23
|
-
name: "recall_facts",
|
|
24
|
-
inputSchema: {
|
|
25
|
-
type: "object",
|
|
26
|
-
properties: {
|
|
27
|
-
scope: { type: "string", description: "Scope filter" },
|
|
28
|
-
fact_type: { type: "string" },
|
|
29
|
-
},
|
|
30
|
-
required: ["scope"],
|
|
31
|
-
},
|
|
32
|
-
};
|
|
33
|
-
const SEARCH_CODE_DEF = {
|
|
34
|
-
name: "search_code",
|
|
35
|
-
inputSchema: {
|
|
36
|
-
type: "object",
|
|
37
|
-
properties: {
|
|
38
|
-
query: { type: "string", description: "Search query" },
|
|
39
|
-
limit: { type: "number" },
|
|
40
|
-
},
|
|
41
|
-
required: ["query"],
|
|
42
|
-
},
|
|
43
|
-
};
|
|
44
|
-
const GET_PROJECT_STATS_DEF = {
|
|
45
|
-
name: "get_project_stats",
|
|
46
|
-
inputSchema: {
|
|
47
|
-
type: "object",
|
|
48
|
-
properties: {},
|
|
49
|
-
},
|
|
50
|
-
};
|
|
51
|
-
const GET_IMPORTS_DEF = {
|
|
52
|
-
name: "get_imports",
|
|
53
|
-
inputSchema: {
|
|
54
|
-
type: "object",
|
|
55
|
-
properties: {
|
|
56
|
-
file_path: { type: "string", description: "File path" },
|
|
57
|
-
},
|
|
58
|
-
required: ["file_path"],
|
|
59
|
-
},
|
|
60
|
-
};
|
|
61
|
-
describe("normalizeArgAliases — entity_name / entity → key", () => {
|
|
62
|
-
it("rewrites entity_name to key when key is missing", () => {
|
|
63
|
-
const args = {
|
|
64
|
-
entity_name: "compressShellOutput",
|
|
65
|
-
};
|
|
66
|
-
normalizeArgAliases(GET_REFERENCES_DEF, args);
|
|
67
|
-
expect(args.key).toBe("compressShellOutput");
|
|
68
|
-
});
|
|
69
|
-
it("rewrites bare `entity` alias to key", () => {
|
|
70
|
-
const args = { entity: "QueryRouter" };
|
|
71
|
-
normalizeArgAliases(GET_REFERENCES_DEF, args);
|
|
72
|
-
expect(args.key).toBe("QueryRouter");
|
|
73
|
-
});
|
|
74
|
-
it("does NOT overwrite key when already present", () => {
|
|
75
|
-
const args = {
|
|
76
|
-
key: "canonical",
|
|
77
|
-
entity_name: "alias-loser",
|
|
78
|
-
};
|
|
79
|
-
normalizeArgAliases(GET_REFERENCES_DEF, args);
|
|
80
|
-
expect(args.key).toBe("canonical");
|
|
81
|
-
});
|
|
82
|
-
it("does NOT add key when tool schema doesn't declare key", () => {
|
|
83
|
-
const args = { entity_name: "foo" };
|
|
84
|
-
normalizeArgAliases(SEARCH_CODE_DEF, args);
|
|
85
|
-
expect(args.key).toBeUndefined();
|
|
86
|
-
});
|
|
87
|
-
it("treats empty-string key as missing for alias purposes", () => {
|
|
88
|
-
const args = {
|
|
89
|
-
key: "",
|
|
90
|
-
entity_name: "real-name",
|
|
91
|
-
};
|
|
92
|
-
normalizeArgAliases(GET_REFERENCES_DEF, args);
|
|
93
|
-
expect(args.key).toBe("real-name");
|
|
94
|
-
});
|
|
95
|
-
it("treats whitespace-only alias as absent (does NOT set key)", () => {
|
|
96
|
-
const args = { entity_name: " " };
|
|
97
|
-
normalizeArgAliases(GET_REFERENCES_DEF, args);
|
|
98
|
-
expect(args.key).toBeUndefined();
|
|
99
|
-
});
|
|
100
|
-
});
|
|
101
|
-
describe("normalizeArgAliases — file / path → file_path", () => {
|
|
102
|
-
it("rewrites `file` alias to file_path", () => {
|
|
103
|
-
const args = { file: "src/foo.ts" };
|
|
104
|
-
normalizeArgAliases(GET_IMPORTS_DEF, args);
|
|
105
|
-
expect(args.file_path).toBe("src/foo.ts");
|
|
106
|
-
});
|
|
107
|
-
it("rewrites `path` alias to file_path", () => {
|
|
108
|
-
const args = { path: "src/bar.ts" };
|
|
109
|
-
normalizeArgAliases(GET_IMPORTS_DEF, args);
|
|
110
|
-
expect(args.file_path).toBe("src/bar.ts");
|
|
111
|
-
});
|
|
112
|
-
});
|
|
113
|
-
describe("validateRequiredArgs — missing fields fail loudly", () => {
|
|
114
|
-
it("returns failure when required key is missing", () => {
|
|
115
|
-
const failure = validateRequiredArgs(GET_REFERENCES_DEF, {
|
|
116
|
-
direction: "callers",
|
|
117
|
-
});
|
|
118
|
-
expect(failure).not.toBeNull();
|
|
119
|
-
expect(failure?.error).toContain("get_references");
|
|
120
|
-
expect(failure?.error).toContain("key");
|
|
121
|
-
expect(failure?.required).toEqual(["key"]);
|
|
122
|
-
expect(failure?.details).toContain("Entity key");
|
|
123
|
-
});
|
|
124
|
-
it("returns failure when required scope is missing (recall_facts)", () => {
|
|
125
|
-
const failure = validateRequiredArgs(RECALL_FACTS_DEF, { limit: 5 });
|
|
126
|
-
expect(failure).not.toBeNull();
|
|
127
|
-
expect(failure?.error).toContain("scope");
|
|
128
|
-
expect(failure?.required).toEqual(["scope"]);
|
|
129
|
-
});
|
|
130
|
-
it("returns failure when required field is empty string", () => {
|
|
131
|
-
const failure = validateRequiredArgs(RECALL_FACTS_DEF, { scope: "" });
|
|
132
|
-
expect(failure).not.toBeNull();
|
|
133
|
-
expect(failure?.required).toEqual(["scope"]);
|
|
134
|
-
});
|
|
135
|
-
it("returns failure when required field is whitespace only", () => {
|
|
136
|
-
const failure = validateRequiredArgs(RECALL_FACTS_DEF, { scope: " " });
|
|
137
|
-
expect(failure).not.toBeNull();
|
|
138
|
-
expect(failure?.required).toEqual(["scope"]);
|
|
139
|
-
});
|
|
140
|
-
it("returns failure when required field is null", () => {
|
|
141
|
-
const failure = validateRequiredArgs(SEARCH_CODE_DEF, { query: null });
|
|
142
|
-
expect(failure).not.toBeNull();
|
|
143
|
-
});
|
|
144
|
-
it("returns null (success) when all required fields present", () => {
|
|
145
|
-
const failure = validateRequiredArgs(GET_REFERENCES_DEF, {
|
|
146
|
-
key: "compressShellOutput",
|
|
147
|
-
});
|
|
148
|
-
expect(failure).toBeNull();
|
|
149
|
-
});
|
|
150
|
-
it("returns null for tools with no required fields", () => {
|
|
151
|
-
const failure = validateRequiredArgs(GET_PROJECT_STATS_DEF, {});
|
|
152
|
-
expect(failure).toBeNull();
|
|
153
|
-
});
|
|
154
|
-
it("lists multiple missing fields when several are absent", () => {
|
|
155
|
-
const def = {
|
|
156
|
-
name: "test_tool",
|
|
157
|
-
inputSchema: {
|
|
158
|
-
type: "object",
|
|
159
|
-
properties: {
|
|
160
|
-
a: { type: "string", description: "first" },
|
|
161
|
-
b: { type: "string", description: "second" },
|
|
162
|
-
c: { type: "string", description: "third" },
|
|
163
|
-
},
|
|
164
|
-
required: ["a", "b", "c"],
|
|
165
|
-
},
|
|
166
|
-
};
|
|
167
|
-
const failure = validateRequiredArgs(def, { a: "x" });
|
|
168
|
-
expect(failure?.required).toEqual(["b", "c"]);
|
|
169
|
-
});
|
|
170
|
-
});
|
|
171
|
-
describe("aliasAndValidate — composed boundary check", () => {
|
|
172
|
-
it("passes when entity_name supplies the canonical key", () => {
|
|
173
|
-
const args = {
|
|
174
|
-
entity_name: "compressShellOutput",
|
|
175
|
-
};
|
|
176
|
-
const failure = aliasAndValidate(GET_REFERENCES_DEF, args);
|
|
177
|
-
expect(failure).toBeNull();
|
|
178
|
-
expect(args.key).toBe("compressShellOutput");
|
|
179
|
-
});
|
|
180
|
-
it("fails when neither key nor any alias is set", () => {
|
|
181
|
-
const args = { direction: "callers" };
|
|
182
|
-
const failure = aliasAndValidate(GET_REFERENCES_DEF, args);
|
|
183
|
-
expect(failure).not.toBeNull();
|
|
184
|
-
expect(failure?.required).toEqual(["key"]);
|
|
185
|
-
});
|
|
186
|
-
it("reproduces the recall_facts({}) silent-failure case as a hard error", () => {
|
|
187
|
-
const args = {};
|
|
188
|
-
const failure = aliasAndValidate(RECALL_FACTS_DEF, args);
|
|
189
|
-
expect(failure).not.toBeNull();
|
|
190
|
-
expect(failure?.error).toMatch(/recall_facts.*scope/);
|
|
191
|
-
expect(failure?.details).toContain("Scope filter");
|
|
192
|
-
});
|
|
193
|
-
it("reproduces the get_references({entity_name:...}) silent-failure case as a pass", () => {
|
|
194
|
-
// Before the fix: entity_name was ignored, args.key was undefined,
|
|
195
|
-
// resolveKeyArg returned undefined, getCallersOf(undefined) returned
|
|
196
|
-
// []. After the fix: alias is rewritten, validation passes.
|
|
197
|
-
const args = {
|
|
198
|
-
entity_name: "compressShellOutput",
|
|
199
|
-
direction: "callers",
|
|
200
|
-
};
|
|
201
|
-
const failure = aliasAndValidate(GET_REFERENCES_DEF, args);
|
|
202
|
-
expect(failure).toBeNull();
|
|
203
|
-
expect(args.key).toBe("compressShellOutput");
|
|
204
|
-
});
|
|
205
|
-
});
|
|
@@ -1,203 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* P10-TEST-03: AST extractor tests — entity extraction from source files.
|
|
3
|
-
*/
|
|
4
|
-
import { describe, expect, it } from "vitest";
|
|
5
|
-
import { detectLanguage, entityKey, extractEntities, } from "../intelligence/ast-extractor.js";
|
|
6
|
-
// ── Language Detection ──────────────────────────────────────────
|
|
7
|
-
describe("detectLanguage", () => {
|
|
8
|
-
it("detects TypeScript files", () => {
|
|
9
|
-
expect(detectLanguage("src/index.ts")).toBe("typescript");
|
|
10
|
-
expect(detectLanguage("components/Button.tsx")).toBe("typescript");
|
|
11
|
-
});
|
|
12
|
-
it("detects JavaScript files", () => {
|
|
13
|
-
expect(detectLanguage("index.js")).toBe("javascript");
|
|
14
|
-
expect(detectLanguage("config.mjs")).toBe("javascript");
|
|
15
|
-
expect(detectLanguage("server.cjs")).toBe("javascript");
|
|
16
|
-
});
|
|
17
|
-
it("detects Python files", () => {
|
|
18
|
-
expect(detectLanguage("main.py")).toBe("python");
|
|
19
|
-
});
|
|
20
|
-
it("detects Go files", () => {
|
|
21
|
-
expect(detectLanguage("main.go")).toBe("go");
|
|
22
|
-
});
|
|
23
|
-
it("detects Java files", () => {
|
|
24
|
-
expect(detectLanguage("App.java")).toBe("java");
|
|
25
|
-
});
|
|
26
|
-
it("detects Rust files", () => {
|
|
27
|
-
expect(detectLanguage("main.rs")).toBe("rust");
|
|
28
|
-
});
|
|
29
|
-
it("detects C/C++ files", () => {
|
|
30
|
-
expect(detectLanguage("main.c")).toBe("c");
|
|
31
|
-
expect(detectLanguage("main.h")).toBe("c");
|
|
32
|
-
expect(detectLanguage("main.cpp")).toBe("cpp");
|
|
33
|
-
expect(detectLanguage("main.hpp")).toBe("cpp");
|
|
34
|
-
});
|
|
35
|
-
it("returns null for unsupported languages", () => {
|
|
36
|
-
expect(detectLanguage("data.json")).toBeNull();
|
|
37
|
-
expect(detectLanguage("style.css")).toBeNull();
|
|
38
|
-
expect(detectLanguage("readme.md")).toBeNull();
|
|
39
|
-
expect(detectLanguage("Makefile")).toBeNull();
|
|
40
|
-
});
|
|
41
|
-
});
|
|
42
|
-
// ── TypeScript/JavaScript Extraction ────────────────────────────
|
|
43
|
-
describe("extractEntities — TypeScript", () => {
|
|
44
|
-
it("extracts exported functions", () => {
|
|
45
|
-
const code = `export function processPayment(amount: number): boolean {
|
|
46
|
-
if (amount <= 0) return false
|
|
47
|
-
return true
|
|
48
|
-
}`;
|
|
49
|
-
const entities = extractEntities(code, "src/billing.ts");
|
|
50
|
-
expect(entities).toHaveLength(1);
|
|
51
|
-
expect(entities[0]?.name).toBe("processPayment");
|
|
52
|
-
expect(entities[0]?.kind).toBe("function");
|
|
53
|
-
expect(entities[0]?.signature).toBe("(amount: number)");
|
|
54
|
-
expect(entities[0]?.line_start).toBe(1);
|
|
55
|
-
expect(entities[0]?.line_end).toBe(4);
|
|
56
|
-
});
|
|
57
|
-
it("extracts async functions", () => {
|
|
58
|
-
const code = `export async function fetchData(url: string) {
|
|
59
|
-
return await fetch(url)
|
|
60
|
-
}`;
|
|
61
|
-
const entities = extractEntities(code, "src/api.ts");
|
|
62
|
-
expect(entities).toHaveLength(1);
|
|
63
|
-
expect(entities[0]?.name).toBe("fetchData");
|
|
64
|
-
expect(entities[0]?.kind).toBe("function");
|
|
65
|
-
});
|
|
66
|
-
it("extracts classes", () => {
|
|
67
|
-
const code = `export class UserService {
|
|
68
|
-
private db: Database
|
|
69
|
-
|
|
70
|
-
getUser(id: string) {
|
|
71
|
-
return this.db.find(id)
|
|
72
|
-
}
|
|
73
|
-
}`;
|
|
74
|
-
const entities = extractEntities(code, "src/user.ts");
|
|
75
|
-
expect(entities.length).toBeGreaterThanOrEqual(1);
|
|
76
|
-
const classEntity = entities.find((e) => e.kind === "class");
|
|
77
|
-
expect(classEntity).toBeDefined();
|
|
78
|
-
expect(classEntity?.name).toBe("UserService");
|
|
79
|
-
});
|
|
80
|
-
it("extracts interfaces", () => {
|
|
81
|
-
const code = `export interface ApiResponse {
|
|
82
|
-
data: unknown
|
|
83
|
-
error?: string
|
|
84
|
-
}`;
|
|
85
|
-
const entities = extractEntities(code, "src/types.ts");
|
|
86
|
-
expect(entities).toHaveLength(1);
|
|
87
|
-
expect(entities[0]?.name).toBe("ApiResponse");
|
|
88
|
-
expect(entities[0]?.kind).toBe("interface");
|
|
89
|
-
});
|
|
90
|
-
it("extracts arrow functions assigned to const", () => {
|
|
91
|
-
const code = `export const validate = (input: string) => {
|
|
92
|
-
return input.length > 0
|
|
93
|
-
}`;
|
|
94
|
-
const entities = extractEntities(code, "src/validate.ts");
|
|
95
|
-
expect(entities).toHaveLength(1);
|
|
96
|
-
expect(entities[0]?.name).toBe("validate");
|
|
97
|
-
expect(entities[0]?.kind).toBe("function");
|
|
98
|
-
});
|
|
99
|
-
it("generates content hash for each entity", () => {
|
|
100
|
-
const code = `function hello() {
|
|
101
|
-
console.log("hello")
|
|
102
|
-
}`;
|
|
103
|
-
const entities = extractEntities(code, "src/greet.ts");
|
|
104
|
-
expect(entities).toHaveLength(1);
|
|
105
|
-
expect(entities[0]?.content_hash).toBeDefined();
|
|
106
|
-
expect(entities[0]?.content_hash.length).toBe(16);
|
|
107
|
-
});
|
|
108
|
-
it("different content produces different hashes", () => {
|
|
109
|
-
const code1 = `function hello() {
|
|
110
|
-
console.log("hello")
|
|
111
|
-
}`;
|
|
112
|
-
const code2 = `function hello() {
|
|
113
|
-
console.log("goodbye")
|
|
114
|
-
}`;
|
|
115
|
-
const e1 = extractEntities(code1, "a.ts");
|
|
116
|
-
const e2 = extractEntities(code2, "a.ts");
|
|
117
|
-
expect(e1[0]?.content_hash).not.toBe(e2[0]?.content_hash);
|
|
118
|
-
});
|
|
119
|
-
});
|
|
120
|
-
// ── Python Extraction ───────────────────────────────────────────
|
|
121
|
-
describe("extractEntities — Python", () => {
|
|
122
|
-
it("extracts functions and classes", () => {
|
|
123
|
-
const code = `def process(data):
|
|
124
|
-
return data.strip()
|
|
125
|
-
|
|
126
|
-
class DataProcessor:
|
|
127
|
-
def run(self):
|
|
128
|
-
pass
|
|
129
|
-
`;
|
|
130
|
-
const entities = extractEntities(code, "main.py");
|
|
131
|
-
expect(entities.length).toBeGreaterThanOrEqual(2);
|
|
132
|
-
const fn = entities.find((e) => e.name === "process");
|
|
133
|
-
expect(fn).toBeDefined();
|
|
134
|
-
expect(fn?.kind).toBe("function");
|
|
135
|
-
const cls = entities.find((e) => e.name === "DataProcessor");
|
|
136
|
-
expect(cls).toBeDefined();
|
|
137
|
-
expect(cls?.kind).toBe("class");
|
|
138
|
-
});
|
|
139
|
-
});
|
|
140
|
-
// ── Go Extraction ───────────────────────────────────────────────
|
|
141
|
-
describe("extractEntities — Go", () => {
|
|
142
|
-
it("extracts functions and methods", () => {
|
|
143
|
-
const code = `func main() {
|
|
144
|
-
fmt.Println("hello")
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
func (s *Server) Start(port int) error {
|
|
148
|
-
return s.listen(port)
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
type Server struct {
|
|
152
|
-
port int
|
|
153
|
-
}
|
|
154
|
-
`;
|
|
155
|
-
const entities = extractEntities(code, "main.go");
|
|
156
|
-
const fn = entities.find((e) => e.name === "main");
|
|
157
|
-
expect(fn).toBeDefined();
|
|
158
|
-
expect(fn?.kind).toBe("function");
|
|
159
|
-
const method = entities.find((e) => e.name === "Start");
|
|
160
|
-
expect(method).toBeDefined();
|
|
161
|
-
expect(method?.kind).toBe("method");
|
|
162
|
-
const cls = entities.find((e) => e.name === "Server");
|
|
163
|
-
expect(cls).toBeDefined();
|
|
164
|
-
expect(cls?.kind).toBe("class");
|
|
165
|
-
});
|
|
166
|
-
});
|
|
167
|
-
// ── Unsupported Languages ───────────────────────────────────────
|
|
168
|
-
describe("extractEntities — unsupported", () => {
|
|
169
|
-
it("returns empty array for JSON files", () => {
|
|
170
|
-
expect(extractEntities('{"key": "value"}', "config.json")).toEqual([]);
|
|
171
|
-
});
|
|
172
|
-
it("returns empty array for CSS files", () => {
|
|
173
|
-
expect(extractEntities(".class { color: red; }", "style.css")).toEqual([]);
|
|
174
|
-
});
|
|
175
|
-
it("never throws for any input", () => {
|
|
176
|
-
expect(() => extractEntities("", "test.ts")).not.toThrow();
|
|
177
|
-
expect(() => extractEntities("random garbage !@#$%", "test.py")).not.toThrow();
|
|
178
|
-
expect(() => extractEntities("{{{}", "test.go")).not.toThrow();
|
|
179
|
-
});
|
|
180
|
-
});
|
|
181
|
-
// ── Entity Key Hashing ──────────────────────────────────────────
|
|
182
|
-
describe("entityKey", () => {
|
|
183
|
-
it("produces 16-char hex string", () => {
|
|
184
|
-
const key = entityKey("repo1", "src/index.ts", "function", "main");
|
|
185
|
-
expect(key).toHaveLength(16);
|
|
186
|
-
expect(/^[a-f0-9]+$/.test(key)).toBe(true);
|
|
187
|
-
});
|
|
188
|
-
it("is deterministic", () => {
|
|
189
|
-
const a = entityKey("repo1", "src/index.ts", "function", "main");
|
|
190
|
-
const b = entityKey("repo1", "src/index.ts", "function", "main");
|
|
191
|
-
expect(a).toBe(b);
|
|
192
|
-
});
|
|
193
|
-
it("different inputs produce different keys", () => {
|
|
194
|
-
const a = entityKey("repo1", "src/a.ts", "function", "foo");
|
|
195
|
-
const b = entityKey("repo1", "src/b.ts", "function", "foo");
|
|
196
|
-
expect(a).not.toBe(b);
|
|
197
|
-
});
|
|
198
|
-
it("includes signature in hash when provided", () => {
|
|
199
|
-
const a = entityKey("repo1", "src/a.ts", "function", "foo", "(x: number)");
|
|
200
|
-
const b = entityKey("repo1", "src/a.ts", "function", "foo", "(x: string)");
|
|
201
|
-
expect(a).not.toBe(b);
|
|
202
|
-
});
|
|
203
|
-
});
|