sverklo 0.7.0 → 0.7.2

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.
Files changed (33) hide show
  1. package/dist/src/indexer/embedding-providers.js +21 -1
  2. package/dist/src/indexer/embedding-providers.js.map +1 -1
  3. package/dist/src/indexer/watcher.js +1 -0
  4. package/dist/src/indexer/watcher.js.map +1 -1
  5. package/dist/src/memory/import.js +9 -2
  6. package/dist/src/memory/import.js.map +1 -1
  7. package/dist/src/search/cluster.test.d.ts +1 -0
  8. package/dist/src/search/cluster.test.js +168 -0
  9. package/dist/src/search/cluster.test.js.map +1 -0
  10. package/dist/src/server/http-server.js +10 -3
  11. package/dist/src/server/http-server.js.map +1 -1
  12. package/dist/src/server/mcp-server.js +23 -7
  13. package/dist/src/server/mcp-server.js.map +1 -1
  14. package/dist/src/server/tools/ast-grep.js +18 -13
  15. package/dist/src/server/tools/ast-grep.js.map +1 -1
  16. package/dist/src/server/tools/diff-heuristics.js +10 -3
  17. package/dist/src/server/tools/diff-heuristics.js.map +1 -1
  18. package/dist/src/server/tools/diff-search.js +13 -2
  19. package/dist/src/server/tools/diff-search.js.map +1 -1
  20. package/dist/src/server/tools/review-diff.js +23 -4
  21. package/dist/src/server/tools/review-diff.js.map +1 -1
  22. package/dist/src/server/tools/test-map.js +13 -2
  23. package/dist/src/server/tools/test-map.js.map +1 -1
  24. package/dist/src/utils/budget.test.d.ts +1 -0
  25. package/dist/src/utils/budget.test.js +75 -0
  26. package/dist/src/utils/budget.test.js.map +1 -0
  27. package/dist/src/utils/config-file.test.d.ts +1 -0
  28. package/dist/src/utils/config-file.test.js +130 -0
  29. package/dist/src/utils/config-file.test.js.map +1 -0
  30. package/dist/src/utils/git-validation.d.ts +26 -0
  31. package/dist/src/utils/git-validation.js +39 -0
  32. package/dist/src/utils/git-validation.js.map +1 -0
  33. package/package.json +1 -1
@@ -0,0 +1,130 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
+ import { mkdtempSync, writeFileSync, rmSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { loadSverkloConfig, getWeight } from "./config-file.js";
6
+ describe("loadSverkloConfig", () => {
7
+ let tmpRoot;
8
+ beforeEach(() => {
9
+ tmpRoot = mkdtempSync(join(tmpdir(), "sverklo-config-test-"));
10
+ });
11
+ afterEach(() => {
12
+ rmSync(tmpRoot, { recursive: true, force: true });
13
+ });
14
+ it("returns null when no config file exists", () => {
15
+ expect(loadSverkloConfig(tmpRoot)).toBeNull();
16
+ });
17
+ it("loads a valid .sverklo.yaml file", () => {
18
+ writeFileSync(join(tmpRoot, ".sverklo.yaml"), `weights:\n - glob: "src/**"\n weight: 2.0\nignore:\n - node_modules\n`, "utf-8");
19
+ const config = loadSverkloConfig(tmpRoot);
20
+ expect(config).not.toBeNull();
21
+ expect(config.weights).toHaveLength(1);
22
+ expect(config.weights[0].glob).toBe("src/**");
23
+ expect(config.weights[0].weight).toBe(2.0);
24
+ expect(config.ignore).toEqual(["node_modules"]);
25
+ });
26
+ it("loads .sverklo.yml as fallback", () => {
27
+ writeFileSync(join(tmpRoot, ".sverklo.yml"), `weights:\n - glob: "lib/**"\n weight: 1.5\n`, "utf-8");
28
+ const config = loadSverkloConfig(tmpRoot);
29
+ expect(config).not.toBeNull();
30
+ expect(config.weights[0].glob).toBe("lib/**");
31
+ });
32
+ it("prefers .sverklo.yaml over .sverklo.yml when both exist", () => {
33
+ writeFileSync(join(tmpRoot, ".sverklo.yaml"), `weights:\n - glob: "from-yaml"\n weight: 1.0\n`, "utf-8");
34
+ writeFileSync(join(tmpRoot, ".sverklo.yml"), `weights:\n - glob: "from-yml"\n weight: 1.0\n`, "utf-8");
35
+ const config = loadSverkloConfig(tmpRoot);
36
+ expect(config.weights[0].glob).toBe("from-yaml");
37
+ });
38
+ it("returns null for empty YAML", () => {
39
+ writeFileSync(join(tmpRoot, ".sverklo.yaml"), "", "utf-8");
40
+ expect(loadSverkloConfig(tmpRoot)).toBeNull();
41
+ });
42
+ it("returns null for non-object YAML (scalar)", () => {
43
+ writeFileSync(join(tmpRoot, ".sverklo.yaml"), "just a string", "utf-8");
44
+ expect(loadSverkloConfig(tmpRoot)).toBeNull();
45
+ });
46
+ it("returns null for invalid YAML syntax", () => {
47
+ writeFileSync(join(tmpRoot, ".sverklo.yaml"), "weights:\n - glob: [unterminated", "utf-8");
48
+ expect(loadSverkloConfig(tmpRoot)).toBeNull();
49
+ });
50
+ it("clamps weights above 10.0 to 10.0", () => {
51
+ writeFileSync(join(tmpRoot, ".sverklo.yaml"), `weights:\n - glob: "**"\n weight: 99.0\n`, "utf-8");
52
+ const config = loadSverkloConfig(tmpRoot);
53
+ expect(config.weights[0].weight).toBe(10.0);
54
+ });
55
+ it("clamps negative weights to 0.0", () => {
56
+ writeFileSync(join(tmpRoot, ".sverklo.yaml"), `weights:\n - glob: "**"\n weight: -5.0\n`, "utf-8");
57
+ const config = loadSverkloConfig(tmpRoot);
58
+ expect(config.weights[0].weight).toBe(0.0);
59
+ });
60
+ it("filters out weight entries with non-finite values (Infinity, NaN)", () => {
61
+ writeFileSync(join(tmpRoot, ".sverklo.yaml"), `weights:\n - glob: "a"\n weight: .inf\n - glob: "b"\n weight: 3.0\n`, "utf-8");
62
+ const config = loadSverkloConfig(tmpRoot);
63
+ expect(config.weights).toHaveLength(1);
64
+ expect(config.weights[0].glob).toBe("b");
65
+ });
66
+ it("filters out malformed weight entries (missing glob or weight)", () => {
67
+ writeFileSync(join(tmpRoot, ".sverklo.yaml"), `weights:\n - glob: "ok"\n weight: 1.0\n - weight: 2.0\n - glob: "missing-weight"\n`, "utf-8");
68
+ const config = loadSverkloConfig(tmpRoot);
69
+ expect(config.weights).toHaveLength(1);
70
+ expect(config.weights[0].glob).toBe("ok");
71
+ });
72
+ it("discards ignore when it is not an array", () => {
73
+ writeFileSync(join(tmpRoot, ".sverklo.yaml"), `ignore: "not-an-array"\n`, "utf-8");
74
+ const config = loadSverkloConfig(tmpRoot);
75
+ expect(config.ignore).toBeUndefined();
76
+ });
77
+ it("filters non-string entries from ignore array", () => {
78
+ writeFileSync(join(tmpRoot, ".sverklo.yaml"), `ignore:\n - node_modules\n - 42\n - dist\n`, "utf-8");
79
+ const config = loadSverkloConfig(tmpRoot);
80
+ expect(config.ignore).toEqual(["node_modules", "dist"]);
81
+ });
82
+ it("loads search config section", () => {
83
+ writeFileSync(join(tmpRoot, ".sverklo.yaml"), `search:\n defaultTokenBudget: 8000\n maxResults: 20\n budgets:\n overview: 4000\n`, "utf-8");
84
+ const config = loadSverkloConfig(tmpRoot);
85
+ expect(config.search.defaultTokenBudget).toBe(8000);
86
+ expect(config.search.maxResults).toBe(20);
87
+ expect(config.search.budgets.overview).toBe(4000);
88
+ });
89
+ it("loads embeddings config section", () => {
90
+ writeFileSync(join(tmpRoot, ".sverklo.yaml"), `embeddings:\n provider: ollama\n ollama:\n baseUrl: http://localhost:11434\n model: nomic-embed-text\n`, "utf-8");
91
+ const config = loadSverkloConfig(tmpRoot);
92
+ expect(config.embeddings.provider).toBe("ollama");
93
+ expect(config.embeddings.ollama.baseUrl).toBe("http://localhost:11434");
94
+ });
95
+ });
96
+ describe("getWeight", () => {
97
+ it("returns 1.0 when config is null", () => {
98
+ expect(getWeight(null, "src/foo.ts")).toBe(1.0);
99
+ });
100
+ it("returns 1.0 when config has no weights", () => {
101
+ expect(getWeight({}, "src/foo.ts")).toBe(1.0);
102
+ });
103
+ it("returns 1.0 when no glob matches", () => {
104
+ const config = { weights: [{ glob: "lib/**", weight: 3.0 }] };
105
+ expect(getWeight(config, "src/foo.ts")).toBe(1.0);
106
+ });
107
+ it("returns the matching weight for a glob", () => {
108
+ const config = { weights: [{ glob: "src/**", weight: 2.5 }] };
109
+ expect(getWeight(config, "src/foo.ts")).toBe(2.5);
110
+ });
111
+ it("last matching glob wins", () => {
112
+ const config = {
113
+ weights: [
114
+ { glob: "src/**", weight: 2.0 },
115
+ { glob: "src/core/**", weight: 5.0 },
116
+ ],
117
+ };
118
+ expect(getWeight(config, "src/core/index.ts")).toBe(5.0);
119
+ });
120
+ it("returns first match weight when later globs do not match", () => {
121
+ const config = {
122
+ weights: [
123
+ { glob: "src/**", weight: 2.0 },
124
+ { glob: "lib/**", weight: 5.0 },
125
+ ],
126
+ };
127
+ expect(getWeight(config, "src/foo.ts")).toBe(2.0);
128
+ });
129
+ });
130
+ //# sourceMappingURL=config-file.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-file.test.js","sourceRoot":"","sources":["../../../src/utils/config-file.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Input validation helpers for git parameters that originate from
3
+ * user-controlled MCP tool arguments. Every git ref, file path, or
4
+ * numeric parameter that flows into a child process MUST be validated
5
+ * here before use.
6
+ *
7
+ * Defence rationale: MCP tool arguments are attacker-controlled strings.
8
+ * Interpolating them into shell commands via execSync creates command
9
+ * injection (CWE-78). Even with spawnSync (no shell), validating early
10
+ * provides defence-in-depth and produces better error messages.
11
+ */
12
+ /**
13
+ * Validate that a string looks like a safe git refspec.
14
+ * Allows: branch names, tags, SHAs, ranges (A..B, A...B), HEAD~N,
15
+ * HEAD^N, and typical ref syntax characters.
16
+ *
17
+ * Rejects: spaces, semicolons, backticks, pipes, dollar signs,
18
+ * parentheses, and other shell metacharacters.
19
+ */
20
+ export declare function validateGitRef(ref: string): boolean;
21
+ /**
22
+ * Validate and parse a positive integer limit parameter.
23
+ * Returns the parsed integer or the provided default if the input
24
+ * is not a valid positive integer.
25
+ */
26
+ export declare function validatePositiveInt(value: unknown, defaultValue: number): number;
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Input validation helpers for git parameters that originate from
3
+ * user-controlled MCP tool arguments. Every git ref, file path, or
4
+ * numeric parameter that flows into a child process MUST be validated
5
+ * here before use.
6
+ *
7
+ * Defence rationale: MCP tool arguments are attacker-controlled strings.
8
+ * Interpolating them into shell commands via execSync creates command
9
+ * injection (CWE-78). Even with spawnSync (no shell), validating early
10
+ * provides defence-in-depth and produces better error messages.
11
+ */
12
+ /**
13
+ * Validate that a string looks like a safe git refspec.
14
+ * Allows: branch names, tags, SHAs, ranges (A..B, A...B), HEAD~N,
15
+ * HEAD^N, and typical ref syntax characters.
16
+ *
17
+ * Rejects: spaces, semicolons, backticks, pipes, dollar signs,
18
+ * parentheses, and other shell metacharacters.
19
+ */
20
+ export function validateGitRef(ref) {
21
+ return /^[a-zA-Z0-9_.\/@{}\-~^:]+(\.\.[a-zA-Z0-9_.\/@{}\-~^:]+)?$/.test(ref);
22
+ }
23
+ /**
24
+ * Validate and parse a positive integer limit parameter.
25
+ * Returns the parsed integer or the provided default if the input
26
+ * is not a valid positive integer.
27
+ */
28
+ export function validatePositiveInt(value, defaultValue) {
29
+ if (typeof value === "number" && Number.isInteger(value) && value > 0) {
30
+ return value;
31
+ }
32
+ if (typeof value === "string") {
33
+ const parsed = parseInt(value, 10);
34
+ if (Number.isInteger(parsed) && parsed > 0)
35
+ return parsed;
36
+ }
37
+ return defaultValue;
38
+ }
39
+ //# sourceMappingURL=git-validation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-validation.js","sourceRoot":"","sources":["../../../src/utils/git-validation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAAC,GAAW;IACxC,OAAO,2DAA2D,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC/E,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,KAAc,EAAE,YAAoB;IACtE,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;QACtE,OAAO,KAAK,CAAC;IACf,CAAC;IACD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACnC,IAAI,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,MAAM,GAAG,CAAC;YAAE,OAAO,MAAM,CAAC;IAC5D,CAAC;IACD,OAAO,YAAY,CAAC;AACtB,CAAC"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sverklo",
3
3
  "mcpName": "io.github.sverklo/sverklo",
4
- "version": "0.7.0",
4
+ "version": "0.7.2",
5
5
  "description": "Sverklo — local-first code intelligence MCP server. Diff-aware MR review, risk scoring, hybrid semantic search, PageRank ranking, persistent memory. Zero config.",
6
6
  "type": "module",
7
7
  "bin": {