@gopherine/obsidian-mcp 0.1.1

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 (223) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/LICENSE +661 -0
  3. package/README.md +235 -0
  4. package/dist/cli.d.ts +2 -0
  5. package/dist/cli.js +805 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/commands/brainstorm.d.ts +9 -0
  8. package/dist/commands/brainstorm.js +37 -0
  9. package/dist/commands/brainstorm.js.map +1 -0
  10. package/dist/commands/brainstorm.test.d.ts +1 -0
  11. package/dist/commands/brainstorm.test.js +110 -0
  12. package/dist/commands/brainstorm.test.js.map +1 -0
  13. package/dist/commands/context.d.ts +18 -0
  14. package/dist/commands/context.js +88 -0
  15. package/dist/commands/context.js.map +1 -0
  16. package/dist/commands/context.test.d.ts +1 -0
  17. package/dist/commands/context.test.js +230 -0
  18. package/dist/commands/context.test.js.map +1 -0
  19. package/dist/commands/decide.d.ts +12 -0
  20. package/dist/commands/decide.js +40 -0
  21. package/dist/commands/decide.js.map +1 -0
  22. package/dist/commands/decide.test.d.ts +1 -0
  23. package/dist/commands/decide.test.js +109 -0
  24. package/dist/commands/decide.test.js.map +1 -0
  25. package/dist/commands/graph.d.ts +15 -0
  26. package/dist/commands/graph.js +89 -0
  27. package/dist/commands/graph.js.map +1 -0
  28. package/dist/commands/graph.test.d.ts +1 -0
  29. package/dist/commands/graph.test.js +215 -0
  30. package/dist/commands/graph.test.js.map +1 -0
  31. package/dist/commands/init.d.ts +11 -0
  32. package/dist/commands/init.js +256 -0
  33. package/dist/commands/init.js.map +1 -0
  34. package/dist/commands/learn.d.ts +25 -0
  35. package/dist/commands/learn.js +92 -0
  36. package/dist/commands/learn.js.map +1 -0
  37. package/dist/commands/learn.test.d.ts +1 -0
  38. package/dist/commands/learn.test.js +241 -0
  39. package/dist/commands/learn.test.js.map +1 -0
  40. package/dist/commands/prune.d.ts +58 -0
  41. package/dist/commands/prune.js +246 -0
  42. package/dist/commands/prune.js.map +1 -0
  43. package/dist/commands/read.d.ts +8 -0
  44. package/dist/commands/read.js +7 -0
  45. package/dist/commands/read.js.map +1 -0
  46. package/dist/commands/read.test.d.ts +1 -0
  47. package/dist/commands/read.test.js +48 -0
  48. package/dist/commands/read.test.js.map +1 -0
  49. package/dist/commands/resume.d.ts +20 -0
  50. package/dist/commands/resume.js +138 -0
  51. package/dist/commands/resume.js.map +1 -0
  52. package/dist/commands/resume.test.d.ts +1 -0
  53. package/dist/commands/resume.test.js +243 -0
  54. package/dist/commands/resume.test.js.map +1 -0
  55. package/dist/commands/search.d.ts +8 -0
  56. package/dist/commands/search.js +25 -0
  57. package/dist/commands/search.js.map +1 -0
  58. package/dist/commands/search.test.d.ts +1 -0
  59. package/dist/commands/search.test.js +61 -0
  60. package/dist/commands/search.test.js.map +1 -0
  61. package/dist/commands/session.d.ts +22 -0
  62. package/dist/commands/session.js +79 -0
  63. package/dist/commands/session.js.map +1 -0
  64. package/dist/commands/session.test.d.ts +1 -0
  65. package/dist/commands/session.test.js +185 -0
  66. package/dist/commands/session.test.js.map +1 -0
  67. package/dist/commands/skill/index.d.ts +30 -0
  68. package/dist/commands/skill/index.js +61 -0
  69. package/dist/commands/skill/index.js.map +1 -0
  70. package/dist/commands/skill/index.test.d.ts +1 -0
  71. package/dist/commands/skill/index.test.js +137 -0
  72. package/dist/commands/skill/index.test.js.map +1 -0
  73. package/dist/commands/skill/install.d.ts +14 -0
  74. package/dist/commands/skill/install.js +107 -0
  75. package/dist/commands/skill/install.js.map +1 -0
  76. package/dist/commands/skill/install.test.d.ts +1 -0
  77. package/dist/commands/skill/install.test.js +225 -0
  78. package/dist/commands/skill/install.test.js.map +1 -0
  79. package/dist/commands/skill/list.d.ts +6 -0
  80. package/dist/commands/skill/list.js +5 -0
  81. package/dist/commands/skill/list.js.map +1 -0
  82. package/dist/commands/skill/list.test.d.ts +1 -0
  83. package/dist/commands/skill/list.test.js +101 -0
  84. package/dist/commands/skill/list.test.js.map +1 -0
  85. package/dist/commands/skill/schema.d.ts +27 -0
  86. package/dist/commands/skill/schema.js +55 -0
  87. package/dist/commands/skill/schema.js.map +1 -0
  88. package/dist/commands/skill/schema.test.d.ts +1 -0
  89. package/dist/commands/skill/schema.test.js +142 -0
  90. package/dist/commands/skill/schema.test.js.map +1 -0
  91. package/dist/commands/skill/validate.d.ts +10 -0
  92. package/dist/commands/skill/validate.js +39 -0
  93. package/dist/commands/skill/validate.js.map +1 -0
  94. package/dist/commands/skill/validate.test.d.ts +1 -0
  95. package/dist/commands/skill/validate.test.js +171 -0
  96. package/dist/commands/skill/validate.test.js.map +1 -0
  97. package/dist/commands/task.d.ts +34 -0
  98. package/dist/commands/task.js +160 -0
  99. package/dist/commands/task.js.map +1 -0
  100. package/dist/commands/task.test.d.ts +1 -0
  101. package/dist/commands/task.test.js +395 -0
  102. package/dist/commands/task.test.js.map +1 -0
  103. package/dist/commands/todo.d.ts +15 -0
  104. package/dist/commands/todo.js +99 -0
  105. package/dist/commands/todo.js.map +1 -0
  106. package/dist/commands/todo.test.d.ts +1 -0
  107. package/dist/commands/todo.test.js +324 -0
  108. package/dist/commands/todo.test.js.map +1 -0
  109. package/dist/commands/write.d.ts +12 -0
  110. package/dist/commands/write.js +40 -0
  111. package/dist/commands/write.js.map +1 -0
  112. package/dist/commands/write.test.d.ts +1 -0
  113. package/dist/commands/write.test.js +79 -0
  114. package/dist/commands/write.test.js.map +1 -0
  115. package/dist/config.d.ts +15 -0
  116. package/dist/config.js +48 -0
  117. package/dist/config.js.map +1 -0
  118. package/dist/config.test.d.ts +1 -0
  119. package/dist/config.test.js +147 -0
  120. package/dist/config.test.js.map +1 -0
  121. package/dist/core/executor.d.ts +11 -0
  122. package/dist/core/executor.js +42 -0
  123. package/dist/core/executor.js.map +1 -0
  124. package/dist/core/executor.test.d.ts +1 -0
  125. package/dist/core/executor.test.js +206 -0
  126. package/dist/core/executor.test.js.map +1 -0
  127. package/dist/core/middleware/error-handler.d.ts +2 -0
  128. package/dist/core/middleware/error-handler.js +29 -0
  129. package/dist/core/middleware/error-handler.js.map +1 -0
  130. package/dist/core/middleware/index.d.ts +2 -0
  131. package/dist/core/middleware/index.js +3 -0
  132. package/dist/core/middleware/index.js.map +1 -0
  133. package/dist/core/middleware/logging.d.ts +2 -0
  134. package/dist/core/middleware/logging.js +18 -0
  135. package/dist/core/middleware/logging.js.map +1 -0
  136. package/dist/core/registry.d.ts +12 -0
  137. package/dist/core/registry.js +407 -0
  138. package/dist/core/registry.js.map +1 -0
  139. package/dist/core/registry.test.d.ts +1 -0
  140. package/dist/core/registry.test.js +162 -0
  141. package/dist/core/registry.test.js.map +1 -0
  142. package/dist/core/types.d.ts +38 -0
  143. package/dist/core/types.js +2 -0
  144. package/dist/core/types.js.map +1 -0
  145. package/dist/integration/coordination.test.d.ts +1 -0
  146. package/dist/integration/coordination.test.js +286 -0
  147. package/dist/integration/coordination.test.js.map +1 -0
  148. package/dist/integration/project-artifacts.test.d.ts +1 -0
  149. package/dist/integration/project-artifacts.test.js +275 -0
  150. package/dist/integration/project-artifacts.test.js.map +1 -0
  151. package/dist/integration/registry-graph.test.d.ts +1 -0
  152. package/dist/integration/registry-graph.test.js +157 -0
  153. package/dist/integration/registry-graph.test.js.map +1 -0
  154. package/dist/integration/vault-lifecycle.test.d.ts +1 -0
  155. package/dist/integration/vault-lifecycle.test.js +183 -0
  156. package/dist/integration/vault-lifecycle.test.js.map +1 -0
  157. package/dist/lib/auto-number.d.ts +10 -0
  158. package/dist/lib/auto-number.js +33 -0
  159. package/dist/lib/auto-number.js.map +1 -0
  160. package/dist/lib/auto-number.test.d.ts +1 -0
  161. package/dist/lib/auto-number.test.js +88 -0
  162. package/dist/lib/auto-number.test.js.map +1 -0
  163. package/dist/lib/escape-regex.d.ts +4 -0
  164. package/dist/lib/escape-regex.js +7 -0
  165. package/dist/lib/escape-regex.js.map +1 -0
  166. package/dist/lib/escape-regex.test.d.ts +1 -0
  167. package/dist/lib/escape-regex.test.js +27 -0
  168. package/dist/lib/escape-regex.test.js.map +1 -0
  169. package/dist/lib/frontmatter.d.ts +34 -0
  170. package/dist/lib/frontmatter.js +74 -0
  171. package/dist/lib/frontmatter.js.map +1 -0
  172. package/dist/lib/frontmatter.test.d.ts +1 -0
  173. package/dist/lib/frontmatter.test.js +192 -0
  174. package/dist/lib/frontmatter.test.js.map +1 -0
  175. package/dist/lib/project-detector.d.ts +12 -0
  176. package/dist/lib/project-detector.js +123 -0
  177. package/dist/lib/project-detector.js.map +1 -0
  178. package/dist/lib/project-detector.test.d.ts +1 -0
  179. package/dist/lib/project-detector.test.js +117 -0
  180. package/dist/lib/project-detector.test.js.map +1 -0
  181. package/dist/lib/safe-external-path.d.ts +10 -0
  182. package/dist/lib/safe-external-path.js +46 -0
  183. package/dist/lib/safe-external-path.js.map +1 -0
  184. package/dist/lib/safe-external-path.test.d.ts +1 -0
  185. package/dist/lib/safe-external-path.test.js +99 -0
  186. package/dist/lib/safe-external-path.test.js.map +1 -0
  187. package/dist/lib/search-engine.d.ts +19 -0
  188. package/dist/lib/search-engine.js +157 -0
  189. package/dist/lib/search-engine.js.map +1 -0
  190. package/dist/lib/search-engine.test.d.ts +1 -0
  191. package/dist/lib/search-engine.test.js +120 -0
  192. package/dist/lib/search-engine.test.js.map +1 -0
  193. package/dist/lib/session-registry.d.ts +59 -0
  194. package/dist/lib/session-registry.js +230 -0
  195. package/dist/lib/session-registry.js.map +1 -0
  196. package/dist/lib/session-registry.test.d.ts +1 -0
  197. package/dist/lib/session-registry.test.js +199 -0
  198. package/dist/lib/session-registry.test.js.map +1 -0
  199. package/dist/lib/skill-registry.d.ts +13 -0
  200. package/dist/lib/skill-registry.js +76 -0
  201. package/dist/lib/skill-registry.js.map +1 -0
  202. package/dist/lib/token-estimator.d.ts +19 -0
  203. package/dist/lib/token-estimator.js +58 -0
  204. package/dist/lib/token-estimator.js.map +1 -0
  205. package/dist/lib/token-estimator.test.d.ts +1 -0
  206. package/dist/lib/token-estimator.test.js +65 -0
  207. package/dist/lib/token-estimator.test.js.map +1 -0
  208. package/dist/lib/vault-fs.d.ts +39 -0
  209. package/dist/lib/vault-fs.js +183 -0
  210. package/dist/lib/vault-fs.js.map +1 -0
  211. package/dist/lib/vault-fs.test.d.ts +1 -0
  212. package/dist/lib/vault-fs.test.js +210 -0
  213. package/dist/lib/vault-fs.test.js.map +1 -0
  214. package/dist/mcp-server.d.ts +2 -0
  215. package/dist/mcp-server.js +385 -0
  216. package/dist/mcp-server.js.map +1 -0
  217. package/dist/test-helpers.d.ts +20 -0
  218. package/dist/test-helpers.js +46 -0
  219. package/dist/test-helpers.js.map +1 -0
  220. package/dist/test-helpers.test.d.ts +1 -0
  221. package/dist/test-helpers.test.js +90 -0
  222. package/dist/test-helpers.test.js.map +1 -0
  223. package/package.json +72 -0
@@ -0,0 +1,46 @@
1
+ import { homedir } from "os";
2
+ import { resolve } from "path";
3
+ import { realpath } from "fs/promises";
4
+ import { execFile } from "child_process";
5
+ import { promisify } from "util";
6
+ const execFileAsync = promisify(execFile);
7
+ /**
8
+ * Validate that a path is safe to read from outside the vault.
9
+ * Used only by vault_init to scan git repos.
10
+ *
11
+ * Enforces:
12
+ * - Must be under $HOME
13
+ * - Must not contain .. or symlinks escaping home
14
+ * - Must be a git repository
15
+ */
16
+ export async function safeExternalPath(rawPath) {
17
+ const home = homedir();
18
+ const resolved = resolve(rawPath.startsWith("~") ? rawPath.replace("~", home) : rawPath);
19
+ // Must be under home directory (after resolution, this catches traversal attacks)
20
+ if (resolved !== home && !resolved.startsWith(home + "/")) {
21
+ throw new Error(`Path must be under home directory. Got: ${resolved}`);
22
+ }
23
+ // Verify real path doesn't escape home (symlink check)
24
+ try {
25
+ const real = await realpath(resolved);
26
+ if (real !== home && !real.startsWith(home + "/")) {
27
+ throw new Error(`Symlink escapes home directory: ${rawPath}`);
28
+ }
29
+ }
30
+ catch (e) {
31
+ if (e.code === "ENOENT")
32
+ throw new Error(`Path does not exist: ${rawPath}`);
33
+ if (e.message?.includes("Symlink"))
34
+ throw e;
35
+ throw e;
36
+ }
37
+ // Must be a git repo
38
+ try {
39
+ await execFileAsync("git", ["rev-parse", "--git-dir"], { cwd: resolved, timeout: 3000 });
40
+ }
41
+ catch {
42
+ throw new Error(`Not a git repository: ${rawPath}`);
43
+ }
44
+ return resolved;
45
+ }
46
+ //# sourceMappingURL=safe-external-path.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"safe-external-path.js","sourceRoot":"","sources":["../../src/lib/safe-external-path.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAC7B,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAC/B,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC;AAEjC,MAAM,aAAa,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;AAE1C;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,OAAe;IACpD,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;IACvB,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;IAEzF,kFAAkF;IAClF,IAAI,QAAQ,KAAK,IAAI,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,GAAG,GAAG,CAAC,EAAE,CAAC;QAC1D,MAAM,IAAI,KAAK,CAAC,2CAA2C,QAAQ,EAAE,CAAC,CAAC;IACzE,CAAC;IAED,uDAAuD;IACvD,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,QAAQ,CAAC,CAAC;QACtC,IAAI,IAAI,KAAK,IAAI,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,GAAG,GAAG,CAAC,EAAE,CAAC;YAClD,MAAM,IAAI,KAAK,CAAC,mCAAmC,OAAO,EAAE,CAAC,CAAC;QAChE,CAAC;IACH,CAAC;IAAC,OAAO,CAAM,EAAE,CAAC;QAChB,IAAI,CAAC,CAAC,IAAI,KAAK,QAAQ;YAAE,MAAM,IAAI,KAAK,CAAC,wBAAwB,OAAO,EAAE,CAAC,CAAC;QAC5E,IAAI,CAAC,CAAC,OAAO,EAAE,QAAQ,CAAC,SAAS,CAAC;YAAE,MAAM,CAAC,CAAC;QAC5C,MAAM,CAAC,CAAC;IACV,CAAC;IAED,qBAAqB;IACrB,IAAI,CAAC;QACH,MAAM,aAAa,CAAC,KAAK,EAAE,CAAC,WAAW,EAAE,WAAW,CAAC,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3F,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,yBAAyB,OAAO,EAAE,CAAC,CAAC;IACtD,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,99 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
+ import { mkdir, rm, symlink } from "fs/promises";
3
+ import { tmpdir, homedir } from "os";
4
+ import { join } from "path";
5
+ import { safeExternalPath } from "./safe-external-path.js";
6
+ import { execFile } from "child_process";
7
+ import { promisify } from "util";
8
+ const execFileAsync = promisify(execFile);
9
+ describe("safeExternalPath", () => {
10
+ let testDir;
11
+ const home = homedir();
12
+ beforeEach(async () => {
13
+ testDir = join(tmpdir(), `safe-path-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
14
+ await mkdir(testDir, { recursive: true });
15
+ await execFileAsync("git", ["init"], { cwd: testDir });
16
+ });
17
+ afterEach(async () => {
18
+ await rm(testDir, { recursive: true, force: true });
19
+ });
20
+ describe("valid paths", () => {
21
+ it("accepts path under home directory", async () => {
22
+ const homeTestDir = join(home, `.safe-path-test-${Date.now()}`);
23
+ await mkdir(homeTestDir, { recursive: true });
24
+ await execFileAsync("git", ["init"], { cwd: homeTestDir });
25
+ const result = await safeExternalPath(homeTestDir);
26
+ expect(result).toBe(homeTestDir);
27
+ await rm(homeTestDir, { recursive: true, force: true });
28
+ });
29
+ it("accepts path with ~ prefix", async () => {
30
+ const homeTestDir = join(home, `.safe-path-test-tilde-${Date.now()}`);
31
+ await mkdir(homeTestDir, { recursive: true });
32
+ await execFileAsync("git", ["init"], { cwd: homeTestDir });
33
+ const result = await safeExternalPath(homeTestDir.replace(home, "~"));
34
+ expect(result).toBe(homeTestDir);
35
+ await rm(homeTestDir, { recursive: true, force: true });
36
+ });
37
+ it("resolves relative path segments", async () => {
38
+ const homeTestDir = join(home, `.safe-path-test-rel-${Date.now()}`);
39
+ await mkdir(homeTestDir, { recursive: true });
40
+ await execFileAsync("git", ["init"], { cwd: homeTestDir });
41
+ const result = await safeExternalPath(`${homeTestDir}/./subdir/..`);
42
+ expect(result).toBe(homeTestDir);
43
+ await rm(homeTestDir, { recursive: true, force: true });
44
+ });
45
+ });
46
+ describe("traversal attack prevention", () => {
47
+ it("rejects path with .. traversal", async () => {
48
+ await expect(safeExternalPath("/Users/../../../etc/passwd")).rejects.toThrow("Path must be under home directory");
49
+ });
50
+ });
51
+ describe("symlink escape prevention", () => {
52
+ it("rejects symlink escaping home directory", async () => {
53
+ const outsideDir = join(tmpdir(), `outside-safe-${Date.now()}`);
54
+ await mkdir(outsideDir, { recursive: true });
55
+ const linkPath = join(testDir, "escape-link");
56
+ try {
57
+ await symlink(outsideDir, linkPath);
58
+ await expect(safeExternalPath(linkPath)).rejects.toThrow();
59
+ }
60
+ finally {
61
+ await rm(outsideDir, { recursive: true, force: true });
62
+ }
63
+ });
64
+ it("accepts symlink within home directory", async () => {
65
+ const homeTestDir = join(home, `.safe-path-test-link-${Date.now()}`);
66
+ await mkdir(homeTestDir, { recursive: true });
67
+ await execFileAsync("git", ["init"], { cwd: homeTestDir });
68
+ const linkPath = join(home, `.safe-path-link-${Date.now()}`);
69
+ await symlink(homeTestDir, linkPath);
70
+ const result = await safeExternalPath(linkPath);
71
+ expect(result).toBeDefined();
72
+ await rm(linkPath, { force: true });
73
+ await rm(homeTestDir, { recursive: true, force: true });
74
+ });
75
+ });
76
+ describe("non-home directory rejection", () => {
77
+ it("rejects path outside home directory", async () => {
78
+ await expect(safeExternalPath("/etc/passwd")).rejects.toThrow("Path must be under home directory");
79
+ });
80
+ it("rejects /tmp directly (outside home)", async () => {
81
+ await expect(safeExternalPath("/tmp")).rejects.toThrow("Path must be under home directory");
82
+ });
83
+ });
84
+ describe("git repository check", () => {
85
+ it("rejects non-git directory", async () => {
86
+ const homeNonGit = join(home, `.safe-path-non-git-${Date.now()}`);
87
+ await mkdir(homeNonGit, { recursive: true });
88
+ await expect(safeExternalPath(homeNonGit)).rejects.toThrow("Not a git repository");
89
+ await rm(homeNonGit, { recursive: true, force: true });
90
+ });
91
+ });
92
+ describe("nonexistent path handling", () => {
93
+ it("rejects nonexistent path", async () => {
94
+ const nonexistent = join(home, `.nonexistent-path-${Date.now()}`);
95
+ await expect(safeExternalPath(nonexistent)).rejects.toThrow("Path does not exist");
96
+ });
97
+ });
98
+ });
99
+ //# sourceMappingURL=safe-external-path.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"safe-external-path.test.js","sourceRoot":"","sources":["../../src/lib/safe-external-path.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ export interface SearchResult {
2
+ path: string;
3
+ snippet: string;
4
+ line: number;
5
+ }
6
+ /**
7
+ * Full-text search using ripgrep.
8
+ */
9
+ export declare function searchText(vaultPath: string, query: string, options?: {
10
+ pathFilter?: string;
11
+ limit?: number;
12
+ }): Promise<SearchResult[]>;
13
+ /**
14
+ * Structured search by frontmatter properties.
15
+ * Scans files and filters by frontmatter fields.
16
+ */
17
+ export declare function searchStructured(vaultPath: string, filters: Record<string, string>, options?: {
18
+ limit?: number;
19
+ }): Promise<SearchResult[]>;
@@ -0,0 +1,157 @@
1
+ import { execFile } from "child_process";
2
+ import { promisify } from "util";
3
+ import { resolve, relative } from "path";
4
+ import { readFile, stat } from "fs/promises";
5
+ import { parseFrontmatter } from "./frontmatter.js";
6
+ import { escapeRegex } from "./escape-regex.js";
7
+ const execFileAsync = promisify(execFile);
8
+ /**
9
+ * Validate that a search path stays within the vault root.
10
+ * Throws if the path escapes the vault boundary.
11
+ */
12
+ function validateSearchPath(vaultPath, searchPath) {
13
+ const resolved = resolve(vaultPath, searchPath);
14
+ const rel = relative(vaultPath, resolved);
15
+ if (rel.startsWith("..") || rel.startsWith("/")) {
16
+ throw new Error(`Search path escapes vault: ${searchPath}`);
17
+ }
18
+ return resolved;
19
+ }
20
+ /**
21
+ * Convert an absolute path from rg output to a vault-relative path.
22
+ * Uses path.relative for correctness instead of string replacement.
23
+ */
24
+ function toRelativePath(vaultPath, absPath) {
25
+ return relative(vaultPath, absPath);
26
+ }
27
+ /**
28
+ * Full-text search using ripgrep.
29
+ */
30
+ export async function searchText(vaultPath, query, options = {}) {
31
+ const { pathFilter, limit = 10 } = options;
32
+ const args = [
33
+ "--json",
34
+ "--max-count", "1", // one match per file
35
+ "--type", "md",
36
+ "--ignore-case",
37
+ "--fixed-strings",
38
+ query,
39
+ ];
40
+ // Validate pathFilter stays within vault
41
+ const searchPath = pathFilter
42
+ ? validateSearchPath(vaultPath, pathFilter)
43
+ : vaultPath;
44
+ try {
45
+ const { stdout } = await execFileAsync("rg", [...args, searchPath], {
46
+ timeout: 10_000,
47
+ maxBuffer: 1024 * 1024,
48
+ });
49
+ const results = [];
50
+ for (const line of stdout.split("\n").filter(Boolean)) {
51
+ try {
52
+ const parsed = JSON.parse(line);
53
+ if (parsed.type === "match") {
54
+ const absPath = parsed.data.path.text;
55
+ const relPath = toRelativePath(vaultPath, absPath);
56
+ const snippet = parsed.data.lines.text.trim().slice(0, 200);
57
+ const lineNum = parsed.data.line_number;
58
+ // Skip hidden dirs and paths escaping vault
59
+ if (relPath.startsWith(".") || relPath.startsWith(".."))
60
+ continue;
61
+ results.push({ path: relPath, snippet, line: lineNum });
62
+ if (results.length >= limit)
63
+ break;
64
+ }
65
+ }
66
+ catch (e) {
67
+ if (e.code !== "ENOENT") {
68
+ console.error("[search] Skipping malformed rg line:", e.message);
69
+ }
70
+ }
71
+ }
72
+ return results;
73
+ }
74
+ catch (e) {
75
+ // rg exits with code 1 when no matches found
76
+ if (e.code === 1 || e.status === 1)
77
+ return [];
78
+ throw e;
79
+ }
80
+ }
81
+ /**
82
+ * Structured search by frontmatter properties.
83
+ * Scans files and filters by frontmatter fields.
84
+ */
85
+ export async function searchStructured(vaultPath, filters, options = {}) {
86
+ const { limit = 10 } = options;
87
+ const results = [];
88
+ // Build grep patterns to narrow candidates (search for both key and value)
89
+ // SECURITY: escape both key and value to prevent regex injection
90
+ // Note: We search separately because YAML arrays span multiple lines
91
+ const firstKey = Object.keys(filters)[0];
92
+ const firstValue = filters[firstKey];
93
+ const keyPattern = escapeRegex(firstKey) + ":";
94
+ const valuePattern = escapeRegex(firstValue);
95
+ let candidates;
96
+ try {
97
+ const { stdout } = await execFileAsync("rg", [
98
+ "--files-with-matches",
99
+ "--type", "md",
100
+ "--regexp", keyPattern,
101
+ "--regexp", valuePattern,
102
+ vaultPath,
103
+ ], { timeout: 10_000, maxBuffer: 1024 * 1024 });
104
+ candidates = stdout.trim().split("\n").filter(Boolean);
105
+ }
106
+ catch (e) {
107
+ if (e.code === 1 || e.status === 1)
108
+ return [];
109
+ throw e;
110
+ }
111
+ const MAX_FILE_SIZE = 100_000; // 100 KB — skip large files in structured search
112
+ for (const absPath of candidates) {
113
+ if (results.length >= limit)
114
+ break;
115
+ const relPath = toRelativePath(vaultPath, absPath);
116
+ if (relPath.startsWith(".") || relPath.startsWith(".."))
117
+ continue;
118
+ try {
119
+ const fileStat = await stat(absPath);
120
+ if (fileStat.size > MAX_FILE_SIZE)
121
+ continue;
122
+ const content = await readFile(absPath, "utf-8");
123
+ const { data } = parseFrontmatter(content);
124
+ // Check all filters match
125
+ let match = true;
126
+ for (const [key, value] of Object.entries(filters)) {
127
+ const fieldValue = data[key];
128
+ if (Array.isArray(fieldValue)) {
129
+ if (!fieldValue.includes(value)) {
130
+ match = false;
131
+ break;
132
+ }
133
+ }
134
+ else if (String(fieldValue) !== value) {
135
+ match = false;
136
+ break;
137
+ }
138
+ }
139
+ if (match) {
140
+ const lines = content.split("\n");
141
+ const firstContentLine = lines.find((l) => l.startsWith("# ") || (l.trim() && !l.startsWith("---") && !l.match(/^\w+:\s/)));
142
+ results.push({
143
+ path: relPath,
144
+ snippet: firstContentLine?.trim().slice(0, 200) ?? "",
145
+ line: 1,
146
+ });
147
+ }
148
+ }
149
+ catch (e) {
150
+ if (e.code !== "ENOENT") {
151
+ console.error(`[search] Skipping unreadable file ${absPath}:`, e.message);
152
+ }
153
+ }
154
+ }
155
+ return results;
156
+ }
157
+ //# sourceMappingURL=search-engine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search-engine.js","sourceRoot":"","sources":["../../src/lib/search-engine.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,120 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
+ import { mkdir, writeFile, rm } from "fs/promises";
3
+ import { tmpdir } from "os";
4
+ import { join } from "path";
5
+ import { searchText, searchStructured } from "./search-engine.js";
6
+ describe("search-engine", () => {
7
+ let vaultRoot;
8
+ beforeEach(async () => {
9
+ vaultRoot = join(tmpdir(), `search-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
10
+ await mkdir(vaultRoot, { recursive: true });
11
+ });
12
+ afterEach(async () => {
13
+ await rm(vaultRoot, { recursive: true, force: true });
14
+ });
15
+ describe("searchText", () => {
16
+ beforeEach(async () => {
17
+ await writeFile(join(vaultRoot, "doc1.md"), "# Hello World\n\nThis is a test document.");
18
+ await writeFile(join(vaultRoot, "doc2.md"), "# Another Doc\n\nNo matching content here.");
19
+ await mkdir(join(vaultRoot, "subdir"), { recursive: true });
20
+ await writeFile(join(vaultRoot, "subdir/nested.md"), "# Nested Hello\n\nHello from nested path.");
21
+ await mkdir(join(vaultRoot, ".obsidian"), { recursive: true });
22
+ await writeFile(join(vaultRoot, ".obsidian/config"), "hello in hidden");
23
+ });
24
+ it("finds matching documents", async () => {
25
+ const results = await searchText(vaultRoot, "Hello");
26
+ expect(results.length).toBeGreaterThan(0);
27
+ expect(results.some(r => r.path === "doc1.md")).toBe(true);
28
+ });
29
+ it("is case insensitive", async () => {
30
+ const results = await searchText(vaultRoot, "HELLO");
31
+ expect(results.length).toBeGreaterThan(0);
32
+ });
33
+ it("respects limit parameter", async () => {
34
+ const results = await searchText(vaultRoot, "Hello", { limit: 1 });
35
+ expect(results.length).toBeLessThanOrEqual(1);
36
+ });
37
+ it("filters by path", async () => {
38
+ const results = await searchText(vaultRoot, "Hello", { pathFilter: "subdir" });
39
+ expect(results.every(r => r.path.startsWith("subdir"))).toBe(true);
40
+ });
41
+ it("excludes hidden directories", async () => {
42
+ const results = await searchText(vaultRoot, "hello");
43
+ expect(results.every(r => !r.path.startsWith(".obsidian"))).toBe(true);
44
+ });
45
+ it("returns empty array for no matches", async () => {
46
+ const results = await searchText(vaultRoot, "zzzznomatchxxxx");
47
+ expect(results).toEqual([]);
48
+ });
49
+ it("rejects traversal attack in pathFilter", async () => {
50
+ await expect(searchText(vaultRoot, "test", { pathFilter: "../outside" })).rejects.toThrow();
51
+ });
52
+ it("rejects absolute path in pathFilter", async () => {
53
+ await expect(searchText(vaultRoot, "test", { pathFilter: "/etc/passwd" })).rejects.toThrow();
54
+ });
55
+ it("returns path, snippet, and line number", async () => {
56
+ const results = await searchText(vaultRoot, "World");
57
+ expect(results.length).toBeGreaterThan(0);
58
+ expect(results[0].path).toBeDefined();
59
+ expect(results[0].snippet).toBeDefined();
60
+ expect(results[0].line).toBeGreaterThan(0);
61
+ });
62
+ it("handles special regex characters safely", async () => {
63
+ await writeFile(join(vaultRoot, "special.md"), "Content with $pecial ch@rs");
64
+ const results = await searchText(vaultRoot, "$pecial");
65
+ expect(results.length).toBeGreaterThan(0);
66
+ });
67
+ });
68
+ describe("searchStructured", () => {
69
+ beforeEach(async () => {
70
+ await writeFile(join(vaultRoot, "task1.md"), "---\ntype: task\nstatus: in-progress\nproject: test-project\ntags:\n - urgent\n - backend\n---\n\n# Task 1\n\nTask one content.");
71
+ await writeFile(join(vaultRoot, "task2.md"), "---\ntype: task\nstatus: done\nproject: other-project\ntags:\n - frontend\n---\n\n# Task 2\n\nTask two content.");
72
+ await writeFile(join(vaultRoot, "note.md"), "---\ntype: index\n---\n\n# Note\n\nJust a note.");
73
+ });
74
+ it("filters by single frontmatter field", async () => {
75
+ const results = await searchStructured(vaultRoot, { type: "task" });
76
+ expect(results.length).toBe(2);
77
+ });
78
+ it("filters by multiple fields", async () => {
79
+ const results = await searchStructured(vaultRoot, { type: "task", status: "done" });
80
+ expect(results.length).toBe(1);
81
+ expect(results[0].path).toBe("task2.md");
82
+ });
83
+ it("filters by array field value", async () => {
84
+ const results = await searchStructured(vaultRoot, { tags: "urgent" });
85
+ expect(results.length).toBe(1);
86
+ expect(results[0].path).toBe("task1.md");
87
+ });
88
+ it("respects limit parameter", async () => {
89
+ const results = await searchStructured(vaultRoot, { type: "task" }, { limit: 1 });
90
+ expect(results.length).toBeLessThanOrEqual(1);
91
+ });
92
+ it("returns empty array for no matches", async () => {
93
+ const results = await searchStructured(vaultRoot, { type: "nonexistent" });
94
+ expect(results).toEqual([]);
95
+ });
96
+ it("excludes hidden directories", async () => {
97
+ await mkdir(join(vaultRoot, ".hidden"), { recursive: true });
98
+ await writeFile(join(vaultRoot, ".hidden/secret.md"), "---\ntype: secret\n---\n\n# Secret");
99
+ const results = await searchStructured(vaultRoot, { type: "secret" });
100
+ expect(results.every(r => !r.path.startsWith("."))).toBe(true);
101
+ });
102
+ it("returns path and snippet", async () => {
103
+ const results = await searchStructured(vaultRoot, { type: "task" });
104
+ expect(results.length).toBeGreaterThan(0);
105
+ expect(results[0].path).toBeDefined();
106
+ expect(results[0].snippet).toBeDefined();
107
+ });
108
+ it("handles files without frontmatter", async () => {
109
+ await writeFile(join(vaultRoot, "plain.md"), "Just plain text");
110
+ const results = await searchStructured(vaultRoot, { type: "task" });
111
+ expect(results.every(r => r.path !== "plain.md")).toBe(true);
112
+ });
113
+ it("handles malformed YAML gracefully", async () => {
114
+ await writeFile(join(vaultRoot, "bad.md"), "---\ninvalid: yaml: content\n---\n\n# Bad");
115
+ const results = await searchStructured(vaultRoot, { type: "task" });
116
+ expect(results.every(r => r.path !== "bad.md")).toBe(true);
117
+ });
118
+ });
119
+ });
120
+ //# sourceMappingURL=search-engine.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search-engine.test.js","sourceRoot":"","sources":["../../src/lib/search-engine.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,59 @@
1
+ export interface Session {
2
+ id: string;
3
+ tool: string;
4
+ project: string | null;
5
+ task_summary: string | null;
6
+ files_touched: string[];
7
+ started_at: string;
8
+ last_heartbeat: string;
9
+ completed_at: string | null;
10
+ status: "active" | "stale" | "completed";
11
+ }
12
+ export interface SessionRegistry {
13
+ sessions: Session[];
14
+ }
15
+ interface Conflict {
16
+ session_id: string;
17
+ tool: string;
18
+ overlapping_files: string[];
19
+ task_summary: string | null;
20
+ }
21
+ /**
22
+ * Manages the session registry for multi-agent coordination.
23
+ * Uses PID lockfiles for safe concurrent access.
24
+ */
25
+ export declare class SessionRegistryManager {
26
+ private readonly ttlHours;
27
+ private readonly registryPath;
28
+ private readonly locksDir;
29
+ constructor(vaultPath: string, ttlHours: number);
30
+ /**
31
+ * Register a new session. Returns session ID and any conflicts.
32
+ */
33
+ register(tool: string, project: string | null, taskSummary?: string | null, filesTouched?: string[]): Promise<{
34
+ session_id: string;
35
+ conflicts: Conflict[];
36
+ }>;
37
+ /**
38
+ * Update heartbeat for a session. Returns false if session not found.
39
+ */
40
+ heartbeat(sessionId: string): Promise<boolean>;
41
+ /**
42
+ * Mark a session as completed.
43
+ */
44
+ complete(sessionId: string, summary?: string): Promise<void>;
45
+ /**
46
+ * List active sessions. Persists stale status changes.
47
+ */
48
+ listActive(): Promise<Session[]>;
49
+ private cleanOldCompleted;
50
+ private cleanStale;
51
+ private readRegistry;
52
+ private writeRegistry;
53
+ /**
54
+ * PID-based lockfile for safe concurrent writes.
55
+ * Times out after 2 seconds with retry.
56
+ */
57
+ private withLock;
58
+ }
59
+ export {};