superskill 0.2.0
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/CHANGELOG.md +42 -0
- package/LICENSE +21 -0
- package/README.md +235 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +1158 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/brainstorm.d.ts +9 -0
- package/dist/commands/brainstorm.js +37 -0
- package/dist/commands/brainstorm.js.map +1 -0
- package/dist/commands/brainstorm.test.d.ts +1 -0
- package/dist/commands/brainstorm.test.js +110 -0
- package/dist/commands/brainstorm.test.js.map +1 -0
- package/dist/commands/context.d.ts +18 -0
- package/dist/commands/context.js +88 -0
- package/dist/commands/context.js.map +1 -0
- package/dist/commands/context.test.d.ts +1 -0
- package/dist/commands/context.test.js +230 -0
- package/dist/commands/context.test.js.map +1 -0
- package/dist/commands/decide.d.ts +12 -0
- package/dist/commands/decide.js +40 -0
- package/dist/commands/decide.js.map +1 -0
- package/dist/commands/decide.test.d.ts +1 -0
- package/dist/commands/decide.test.js +109 -0
- package/dist/commands/decide.test.js.map +1 -0
- package/dist/commands/graph.d.ts +15 -0
- package/dist/commands/graph.js +89 -0
- package/dist/commands/graph.js.map +1 -0
- package/dist/commands/graph.test.d.ts +1 -0
- package/dist/commands/graph.test.js +215 -0
- package/dist/commands/graph.test.js.map +1 -0
- package/dist/commands/init.d.ts +11 -0
- package/dist/commands/init.js +257 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/learn.d.ts +25 -0
- package/dist/commands/learn.js +92 -0
- package/dist/commands/learn.js.map +1 -0
- package/dist/commands/learn.test.d.ts +1 -0
- package/dist/commands/learn.test.js +241 -0
- package/dist/commands/learn.test.js.map +1 -0
- package/dist/commands/prune.d.ts +58 -0
- package/dist/commands/prune.js +246 -0
- package/dist/commands/prune.js.map +1 -0
- package/dist/commands/read.d.ts +8 -0
- package/dist/commands/read.js +7 -0
- package/dist/commands/read.js.map +1 -0
- package/dist/commands/read.test.d.ts +1 -0
- package/dist/commands/read.test.js +48 -0
- package/dist/commands/read.test.js.map +1 -0
- package/dist/commands/resume.d.ts +20 -0
- package/dist/commands/resume.js +141 -0
- package/dist/commands/resume.js.map +1 -0
- package/dist/commands/resume.test.d.ts +1 -0
- package/dist/commands/resume.test.js +243 -0
- package/dist/commands/resume.test.js.map +1 -0
- package/dist/commands/search.d.ts +8 -0
- package/dist/commands/search.js +25 -0
- package/dist/commands/search.js.map +1 -0
- package/dist/commands/search.test.d.ts +1 -0
- package/dist/commands/search.test.js +61 -0
- package/dist/commands/search.test.js.map +1 -0
- package/dist/commands/session.d.ts +22 -0
- package/dist/commands/session.js +79 -0
- package/dist/commands/session.js.map +1 -0
- package/dist/commands/session.test.d.ts +1 -0
- package/dist/commands/session.test.js +231 -0
- package/dist/commands/session.test.js.map +1 -0
- package/dist/commands/skill/catalog.d.ts +39 -0
- package/dist/commands/skill/catalog.js +260 -0
- package/dist/commands/skill/catalog.js.map +1 -0
- package/dist/commands/skill/index.d.ts +55 -0
- package/dist/commands/skill/index.js +82 -0
- package/dist/commands/skill/index.js.map +1 -0
- package/dist/commands/skill/index.test.d.ts +1 -0
- package/dist/commands/skill/index.test.js +137 -0
- package/dist/commands/skill/index.test.js.map +1 -0
- package/dist/commands/skill/install.d.ts +14 -0
- package/dist/commands/skill/install.js +108 -0
- package/dist/commands/skill/install.js.map +1 -0
- package/dist/commands/skill/install.test.d.ts +1 -0
- package/dist/commands/skill/install.test.js +225 -0
- package/dist/commands/skill/install.test.js.map +1 -0
- package/dist/commands/skill/list.d.ts +6 -0
- package/dist/commands/skill/list.js +5 -0
- package/dist/commands/skill/list.js.map +1 -0
- package/dist/commands/skill/list.test.d.ts +1 -0
- package/dist/commands/skill/list.test.js +101 -0
- package/dist/commands/skill/list.test.js.map +1 -0
- package/dist/commands/skill/marketplace.d.ts +121 -0
- package/dist/commands/skill/marketplace.js +548 -0
- package/dist/commands/skill/marketplace.js.map +1 -0
- package/dist/commands/skill/schema.d.ts +27 -0
- package/dist/commands/skill/schema.js +55 -0
- package/dist/commands/skill/schema.js.map +1 -0
- package/dist/commands/skill/schema.test.d.ts +1 -0
- package/dist/commands/skill/schema.test.js +142 -0
- package/dist/commands/skill/schema.test.js.map +1 -0
- package/dist/commands/skill/validate.d.ts +10 -0
- package/dist/commands/skill/validate.js +40 -0
- package/dist/commands/skill/validate.js.map +1 -0
- package/dist/commands/skill/validate.test.d.ts +1 -0
- package/dist/commands/skill/validate.test.js +171 -0
- package/dist/commands/skill/validate.test.js.map +1 -0
- package/dist/commands/task.d.ts +34 -0
- package/dist/commands/task.js +160 -0
- package/dist/commands/task.js.map +1 -0
- package/dist/commands/task.test.d.ts +1 -0
- package/dist/commands/task.test.js +395 -0
- package/dist/commands/task.test.js.map +1 -0
- package/dist/commands/todo.d.ts +15 -0
- package/dist/commands/todo.js +99 -0
- package/dist/commands/todo.js.map +1 -0
- package/dist/commands/todo.test.d.ts +1 -0
- package/dist/commands/todo.test.js +324 -0
- package/dist/commands/todo.test.js.map +1 -0
- package/dist/commands/write.d.ts +12 -0
- package/dist/commands/write.js +40 -0
- package/dist/commands/write.js.map +1 -0
- package/dist/commands/write.test.d.ts +1 -0
- package/dist/commands/write.test.js +79 -0
- package/dist/commands/write.test.js.map +1 -0
- package/dist/config.d.ts +15 -0
- package/dist/config.js +49 -0
- package/dist/config.js.map +1 -0
- package/dist/config.test.d.ts +1 -0
- package/dist/config.test.js +147 -0
- package/dist/config.test.js.map +1 -0
- package/dist/core/executor.d.ts +11 -0
- package/dist/core/executor.js +42 -0
- package/dist/core/executor.js.map +1 -0
- package/dist/core/executor.test.d.ts +1 -0
- package/dist/core/executor.test.js +206 -0
- package/dist/core/executor.test.js.map +1 -0
- package/dist/core/middleware/error-handler.d.ts +2 -0
- package/dist/core/middleware/error-handler.js +29 -0
- package/dist/core/middleware/error-handler.js.map +1 -0
- package/dist/core/middleware/index.d.ts +2 -0
- package/dist/core/middleware/index.js +3 -0
- package/dist/core/middleware/index.js.map +1 -0
- package/dist/core/middleware/logging.d.ts +2 -0
- package/dist/core/middleware/logging.js +18 -0
- package/dist/core/middleware/logging.js.map +1 -0
- package/dist/core/registry.d.ts +12 -0
- package/dist/core/registry.js +552 -0
- package/dist/core/registry.js.map +1 -0
- package/dist/core/registry.test.d.ts +1 -0
- package/dist/core/registry.test.js +213 -0
- package/dist/core/registry.test.js.map +1 -0
- package/dist/core/types.d.ts +38 -0
- package/dist/core/types.js +2 -0
- package/dist/core/types.js.map +1 -0
- package/dist/integration/coordination.test.d.ts +1 -0
- package/dist/integration/coordination.test.js +286 -0
- package/dist/integration/coordination.test.js.map +1 -0
- package/dist/integration/project-artifacts.test.d.ts +1 -0
- package/dist/integration/project-artifacts.test.js +275 -0
- package/dist/integration/project-artifacts.test.js.map +1 -0
- package/dist/integration/registry-graph.test.d.ts +1 -0
- package/dist/integration/registry-graph.test.js +157 -0
- package/dist/integration/registry-graph.test.js.map +1 -0
- package/dist/integration/vault-lifecycle.test.d.ts +1 -0
- package/dist/integration/vault-lifecycle.test.js +183 -0
- package/dist/integration/vault-lifecycle.test.js.map +1 -0
- package/dist/lib/auto-number.d.ts +10 -0
- package/dist/lib/auto-number.js +33 -0
- package/dist/lib/auto-number.js.map +1 -0
- package/dist/lib/auto-number.test.d.ts +1 -0
- package/dist/lib/auto-number.test.js +88 -0
- package/dist/lib/auto-number.test.js.map +1 -0
- package/dist/lib/auto-profile.d.ts +11 -0
- package/dist/lib/auto-profile.js +123 -0
- package/dist/lib/auto-profile.js.map +1 -0
- package/dist/lib/auto-profile.test.d.ts +1 -0
- package/dist/lib/auto-profile.test.js +227 -0
- package/dist/lib/auto-profile.test.js.map +1 -0
- package/dist/lib/escape-regex.d.ts +4 -0
- package/dist/lib/escape-regex.js +8 -0
- package/dist/lib/escape-regex.js.map +1 -0
- package/dist/lib/escape-regex.test.d.ts +1 -0
- package/dist/lib/escape-regex.test.js +27 -0
- package/dist/lib/escape-regex.test.js.map +1 -0
- package/dist/lib/frontmatter.d.ts +34 -0
- package/dist/lib/frontmatter.js +75 -0
- package/dist/lib/frontmatter.js.map +1 -0
- package/dist/lib/frontmatter.test.d.ts +1 -0
- package/dist/lib/frontmatter.test.js +192 -0
- package/dist/lib/frontmatter.test.js.map +1 -0
- package/dist/lib/project-detector.d.ts +12 -0
- package/dist/lib/project-detector.js +124 -0
- package/dist/lib/project-detector.js.map +1 -0
- package/dist/lib/project-detector.test.d.ts +1 -0
- package/dist/lib/project-detector.test.js +117 -0
- package/dist/lib/project-detector.test.js.map +1 -0
- package/dist/lib/safe-external-path.d.ts +10 -0
- package/dist/lib/safe-external-path.js +47 -0
- package/dist/lib/safe-external-path.js.map +1 -0
- package/dist/lib/safe-external-path.test.d.ts +1 -0
- package/dist/lib/safe-external-path.test.js +99 -0
- package/dist/lib/safe-external-path.test.js.map +1 -0
- package/dist/lib/search-engine.d.ts +19 -0
- package/dist/lib/search-engine.js +164 -0
- package/dist/lib/search-engine.js.map +1 -0
- package/dist/lib/search-engine.test.d.ts +1 -0
- package/dist/lib/search-engine.test.js +120 -0
- package/dist/lib/search-engine.test.js.map +1 -0
- package/dist/lib/session-registry.d.ts +59 -0
- package/dist/lib/session-registry.js +231 -0
- package/dist/lib/session-registry.js.map +1 -0
- package/dist/lib/session-registry.test.d.ts +1 -0
- package/dist/lib/session-registry.test.js +199 -0
- package/dist/lib/session-registry.test.js.map +1 -0
- package/dist/lib/skill-registry.d.ts +13 -0
- package/dist/lib/skill-registry.js +77 -0
- package/dist/lib/skill-registry.js.map +1 -0
- package/dist/lib/stack-detector.d.ts +7 -0
- package/dist/lib/stack-detector.js +184 -0
- package/dist/lib/stack-detector.js.map +1 -0
- package/dist/lib/stack-detector.test.d.ts +1 -0
- package/dist/lib/stack-detector.test.js +110 -0
- package/dist/lib/stack-detector.test.js.map +1 -0
- package/dist/lib/token-estimator.d.ts +19 -0
- package/dist/lib/token-estimator.js +59 -0
- package/dist/lib/token-estimator.js.map +1 -0
- package/dist/lib/token-estimator.test.d.ts +1 -0
- package/dist/lib/token-estimator.test.js +65 -0
- package/dist/lib/token-estimator.test.js.map +1 -0
- package/dist/lib/tool-detector.d.ts +8 -0
- package/dist/lib/tool-detector.js +76 -0
- package/dist/lib/tool-detector.js.map +1 -0
- package/dist/lib/tool-detector.test.d.ts +1 -0
- package/dist/lib/tool-detector.test.js +170 -0
- package/dist/lib/tool-detector.test.js.map +1 -0
- package/dist/lib/vault-fs.d.ts +39 -0
- package/dist/lib/vault-fs.js +184 -0
- package/dist/lib/vault-fs.js.map +1 -0
- package/dist/lib/vault-fs.test.d.ts +1 -0
- package/dist/lib/vault-fs.test.js +210 -0
- package/dist/lib/vault-fs.test.js.map +1 -0
- package/dist/mcp-server.d.ts +2 -0
- package/dist/mcp-server.js +387 -0
- package/dist/mcp-server.js.map +1 -0
- package/dist/setup/clients.d.ts +2 -0
- package/dist/setup/clients.js +109 -0
- package/dist/setup/clients.js.map +1 -0
- package/dist/setup/configure.d.ts +6 -0
- package/dist/setup/configure.js +126 -0
- package/dist/setup/configure.js.map +1 -0
- package/dist/setup/configure.test.d.ts +1 -0
- package/dist/setup/configure.test.js +112 -0
- package/dist/setup/configure.test.js.map +1 -0
- package/dist/setup/detect.d.ts +3 -0
- package/dist/setup/detect.js +24 -0
- package/dist/setup/detect.js.map +1 -0
- package/dist/setup/detect.test.d.ts +1 -0
- package/dist/setup/detect.test.js +44 -0
- package/dist/setup/detect.test.js.map +1 -0
- package/dist/setup/index.d.ts +2 -0
- package/dist/setup/index.js +147 -0
- package/dist/setup/index.js.map +1 -0
- package/dist/setup/instructions.d.ts +4 -0
- package/dist/setup/instructions.js +62 -0
- package/dist/setup/instructions.js.map +1 -0
- package/dist/setup/instructions.test.d.ts +1 -0
- package/dist/setup/instructions.test.js +84 -0
- package/dist/setup/instructions.test.js.map +1 -0
- package/dist/setup/json-config.d.ts +10 -0
- package/dist/setup/json-config.js +43 -0
- package/dist/setup/json-config.js.map +1 -0
- package/dist/setup/json-config.test.d.ts +1 -0
- package/dist/setup/json-config.test.js +92 -0
- package/dist/setup/json-config.test.js.map +1 -0
- package/dist/setup/postinstall.d.ts +2 -0
- package/dist/setup/postinstall.js +22 -0
- package/dist/setup/postinstall.js.map +1 -0
- package/dist/setup/preuninstall.d.ts +2 -0
- package/dist/setup/preuninstall.js +12 -0
- package/dist/setup/preuninstall.js.map +1 -0
- package/dist/setup/setup-integration.test.d.ts +1 -0
- package/dist/setup/setup-integration.test.js +72 -0
- package/dist/setup/setup-integration.test.js.map +1 -0
- package/dist/setup/teardown.d.ts +5 -0
- package/dist/setup/teardown.js +94 -0
- package/dist/setup/teardown.js.map +1 -0
- package/dist/setup/teardown.test.d.ts +1 -0
- package/dist/setup/teardown.test.js +105 -0
- package/dist/setup/teardown.test.js.map +1 -0
- package/dist/setup/toml-config.d.ts +5 -0
- package/dist/setup/toml-config.js +26 -0
- package/dist/setup/toml-config.js.map +1 -0
- package/dist/setup/toml-config.test.d.ts +1 -0
- package/dist/setup/toml-config.test.js +49 -0
- package/dist/setup/toml-config.test.js.map +1 -0
- package/dist/setup/types.d.ts +52 -0
- package/dist/setup/types.js +23 -0
- package/dist/setup/types.js.map +1 -0
- package/dist/test-helpers.d.ts +20 -0
- package/dist/test-helpers.js +47 -0
- package/dist/test-helpers.js.map +1 -0
- package/dist/test-helpers.test.d.ts +1 -0
- package/dist/test-helpers.test.js +90 -0
- package/dist/test-helpers.test.js.map +1 -0
- package/package.json +74 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later OR Commercial
|
|
2
|
+
import { readFile } from "fs/promises";
|
|
3
|
+
import { resolve } from "path";
|
|
4
|
+
import { execFile } from "child_process";
|
|
5
|
+
import { promisify } from "util";
|
|
6
|
+
const execFileAsync = promisify(execFile);
|
|
7
|
+
const CACHE_TTL_MS = 60_000; // 1 minute
|
|
8
|
+
let cache = null;
|
|
9
|
+
export function invalidateProjectMapCache() {
|
|
10
|
+
cache = null;
|
|
11
|
+
}
|
|
12
|
+
function setCache(entry) {
|
|
13
|
+
cache = entry;
|
|
14
|
+
}
|
|
15
|
+
function getCache(vaultPath) {
|
|
16
|
+
const now = Date.now();
|
|
17
|
+
if (cache && cache.vaultPath === vaultPath && now - cache.time < CACHE_TTL_MS) {
|
|
18
|
+
return cache.map;
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Load the project map from the vault. Cache is keyed by vaultPath.
|
|
24
|
+
*/
|
|
25
|
+
async function loadProjectMap(vaultPath) {
|
|
26
|
+
const cached = getCache(vaultPath);
|
|
27
|
+
if (cached)
|
|
28
|
+
return cached;
|
|
29
|
+
const mapPath = resolve(vaultPath, "project-map.json");
|
|
30
|
+
try {
|
|
31
|
+
const raw = await readFile(mapPath, "utf-8");
|
|
32
|
+
const parsed = JSON.parse(raw);
|
|
33
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
34
|
+
return {};
|
|
35
|
+
}
|
|
36
|
+
const map = {};
|
|
37
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
38
|
+
if (typeof value === "string") {
|
|
39
|
+
map[key] = value;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
setCache({ map, time: Date.now(), vaultPath });
|
|
43
|
+
return map;
|
|
44
|
+
}
|
|
45
|
+
catch (e) {
|
|
46
|
+
if (e?.code === "EACCES") {
|
|
47
|
+
console.error(`Warning: permission denied reading ${mapPath}`);
|
|
48
|
+
}
|
|
49
|
+
else if (e?.code !== "ENOENT" && !(e instanceof SyntaxError)) {
|
|
50
|
+
throw e;
|
|
51
|
+
}
|
|
52
|
+
return {};
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Strip worktree suffixes from a path.
|
|
57
|
+
*/
|
|
58
|
+
function stripWorktreeSuffix(cwd) {
|
|
59
|
+
// Pattern: /path/to/project/.worktrees/feat-name
|
|
60
|
+
const worktreeMatch = cwd.match(/^(.+?)\/\.worktrees\/[^/]+/);
|
|
61
|
+
if (worktreeMatch)
|
|
62
|
+
return worktreeMatch[1];
|
|
63
|
+
// Pattern: /path/to/project/.claude/worktrees/feat/name
|
|
64
|
+
const claudeWorktreeMatch = cwd.match(/^(.+?)\/\.claude\/worktrees\/.+/);
|
|
65
|
+
if (claudeWorktreeMatch)
|
|
66
|
+
return claudeWorktreeMatch[1];
|
|
67
|
+
// Pattern: /path/to/project/.claude-worktrees/name
|
|
68
|
+
const oldWorktreeMatch = cwd.match(/^(.+?)\/\.claude-worktrees\/[^/]+/);
|
|
69
|
+
if (oldWorktreeMatch)
|
|
70
|
+
return oldWorktreeMatch[1];
|
|
71
|
+
return cwd;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Try to detect git root of current directory (async, non-blocking).
|
|
75
|
+
*/
|
|
76
|
+
async function getGitRoot(cwd) {
|
|
77
|
+
try {
|
|
78
|
+
const { stdout } = await execFileAsync("git", ["rev-parse", "--show-toplevel"], {
|
|
79
|
+
cwd,
|
|
80
|
+
timeout: 3000,
|
|
81
|
+
});
|
|
82
|
+
return stdout.trim();
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Detect the project slug from a working directory.
|
|
90
|
+
*
|
|
91
|
+
* Strategy:
|
|
92
|
+
* 1. Strip worktree suffixes from cwd
|
|
93
|
+
* 2. Try exact match in project-map.json
|
|
94
|
+
* 3. Try matching cwd as a subdirectory of a mapped path
|
|
95
|
+
* 4. Try git root as a mapped path
|
|
96
|
+
* 5. Return null if no match
|
|
97
|
+
*/
|
|
98
|
+
export async function detectProject(cwd, vaultPath) {
|
|
99
|
+
const map = await loadProjectMap(vaultPath);
|
|
100
|
+
const cleanCwd = stripWorktreeSuffix(cwd);
|
|
101
|
+
// Exact match
|
|
102
|
+
if (map[cleanCwd])
|
|
103
|
+
return map[cleanCwd];
|
|
104
|
+
// Subdirectory match
|
|
105
|
+
for (const [path, slug] of Object.entries(map)) {
|
|
106
|
+
if (cleanCwd.startsWith(path + "/") || cleanCwd === path) {
|
|
107
|
+
return slug;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// Git root match
|
|
111
|
+
const gitRoot = await getGitRoot(cwd);
|
|
112
|
+
if (gitRoot) {
|
|
113
|
+
const cleanGitRoot = stripWorktreeSuffix(gitRoot);
|
|
114
|
+
if (map[cleanGitRoot])
|
|
115
|
+
return map[cleanGitRoot];
|
|
116
|
+
for (const [path, slug] of Object.entries(map)) {
|
|
117
|
+
if (cleanGitRoot.startsWith(path + "/") || cleanGitRoot === path) {
|
|
118
|
+
return slug;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=project-detector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project-detector.js","sourceRoot":"","sources":["../../src/lib/project-detector.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,117 @@
|
|
|
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 { detectProject, invalidateProjectMapCache } from "./project-detector.js";
|
|
6
|
+
describe("project-detector", () => {
|
|
7
|
+
let vaultRoot;
|
|
8
|
+
let originalCwd;
|
|
9
|
+
beforeEach(async () => {
|
|
10
|
+
vaultRoot = join(tmpdir(), `project-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
11
|
+
await mkdir(vaultRoot, { recursive: true });
|
|
12
|
+
originalCwd = process.cwd();
|
|
13
|
+
invalidateProjectMapCache();
|
|
14
|
+
});
|
|
15
|
+
afterEach(async () => {
|
|
16
|
+
process.chdir(originalCwd);
|
|
17
|
+
await rm(vaultRoot, { recursive: true, force: true });
|
|
18
|
+
invalidateProjectMapCache();
|
|
19
|
+
});
|
|
20
|
+
describe("detectProject", () => {
|
|
21
|
+
it("returns null when no project-map.json", async () => {
|
|
22
|
+
const result = await detectProject("/nonexistent/path", vaultRoot);
|
|
23
|
+
expect(result).toBeNull();
|
|
24
|
+
});
|
|
25
|
+
it("detects project from exact cwd match in project-map.json", async () => {
|
|
26
|
+
const projectMap = { "/Users/test/myproject": "myproject" };
|
|
27
|
+
await writeFile(join(vaultRoot, "project-map.json"), JSON.stringify(projectMap));
|
|
28
|
+
invalidateProjectMapCache();
|
|
29
|
+
const result = await detectProject("/Users/test/myproject", vaultRoot);
|
|
30
|
+
expect(result).toBe("myproject");
|
|
31
|
+
});
|
|
32
|
+
it("detects project from subdirectory of mapped path", async () => {
|
|
33
|
+
const projectMap = { "/Users/test/myproject": "myproject" };
|
|
34
|
+
await writeFile(join(vaultRoot, "project-map.json"), JSON.stringify(projectMap));
|
|
35
|
+
invalidateProjectMapCache();
|
|
36
|
+
const result = await detectProject("/Users/test/myproject/src/components", vaultRoot);
|
|
37
|
+
expect(result).toBe("myproject");
|
|
38
|
+
});
|
|
39
|
+
it("returns null for unmapped path", async () => {
|
|
40
|
+
const projectMap = { "/Users/test/other": "other" };
|
|
41
|
+
await writeFile(join(vaultRoot, "project-map.json"), JSON.stringify(projectMap));
|
|
42
|
+
invalidateProjectMapCache();
|
|
43
|
+
const result = await detectProject("/Users/test/unmapped", vaultRoot);
|
|
44
|
+
expect(result).toBeNull();
|
|
45
|
+
});
|
|
46
|
+
it("handles invalid JSON in project-map.json", async () => {
|
|
47
|
+
await writeFile(join(vaultRoot, "project-map.json"), "not valid json {{{");
|
|
48
|
+
invalidateProjectMapCache();
|
|
49
|
+
const result = await detectProject("/any/path", vaultRoot);
|
|
50
|
+
expect(result).toBeNull();
|
|
51
|
+
});
|
|
52
|
+
it("handles non-object project-map.json", async () => {
|
|
53
|
+
await writeFile(join(vaultRoot, "project-map.json"), JSON.stringify(["array", "not", "object"]));
|
|
54
|
+
invalidateProjectMapCache();
|
|
55
|
+
const result = await detectProject("/any/path", vaultRoot);
|
|
56
|
+
expect(result).toBeNull();
|
|
57
|
+
});
|
|
58
|
+
it("strips .worktrees suffix from cwd", async () => {
|
|
59
|
+
const projectMap = { "/Users/test/myproject": "myproject" };
|
|
60
|
+
await writeFile(join(vaultRoot, "project-map.json"), JSON.stringify(projectMap));
|
|
61
|
+
invalidateProjectMapCache();
|
|
62
|
+
const result = await detectProject("/Users/test/myproject/.worktrees/feat-x", vaultRoot);
|
|
63
|
+
expect(result).toBe("myproject");
|
|
64
|
+
});
|
|
65
|
+
it("strips .claude/worktrees suffix from cwd", async () => {
|
|
66
|
+
const projectMap = { "/Users/test/myproject": "myproject" };
|
|
67
|
+
await writeFile(join(vaultRoot, "project-map.json"), JSON.stringify(projectMap));
|
|
68
|
+
invalidateProjectMapCache();
|
|
69
|
+
const result = await detectProject("/Users/test/myproject/.claude/worktrees/feat/name", vaultRoot);
|
|
70
|
+
expect(result).toBe("myproject");
|
|
71
|
+
});
|
|
72
|
+
it("strips .claude-worktrees suffix from cwd", async () => {
|
|
73
|
+
const projectMap = { "/Users/test/myproject": "myproject" };
|
|
74
|
+
await writeFile(join(vaultRoot, "project-map.json"), JSON.stringify(projectMap));
|
|
75
|
+
invalidateProjectMapCache();
|
|
76
|
+
const result = await detectProject("/Users/test/myproject/.claude-worktrees/feat-name", vaultRoot);
|
|
77
|
+
expect(result).toBe("myproject");
|
|
78
|
+
});
|
|
79
|
+
it("ignores non-string values in project-map.json", async () => {
|
|
80
|
+
const projectMap = {
|
|
81
|
+
"/Users/test/good": "good",
|
|
82
|
+
"/Users/test/bad": 123,
|
|
83
|
+
"/Users/test/also-bad": null
|
|
84
|
+
};
|
|
85
|
+
await writeFile(join(vaultRoot, "project-map.json"), JSON.stringify(projectMap));
|
|
86
|
+
invalidateProjectMapCache();
|
|
87
|
+
const goodResult = await detectProject("/Users/test/good", vaultRoot);
|
|
88
|
+
expect(goodResult).toBe("good");
|
|
89
|
+
const badResult = await detectProject("/Users/test/bad", vaultRoot);
|
|
90
|
+
expect(badResult).toBeNull();
|
|
91
|
+
});
|
|
92
|
+
it("caches project-map.json", async () => {
|
|
93
|
+
const projectMap = { "/Users/test/cached": "cached" };
|
|
94
|
+
await writeFile(join(vaultRoot, "project-map.json"), JSON.stringify(projectMap));
|
|
95
|
+
invalidateProjectMapCache();
|
|
96
|
+
await detectProject("/Users/test/cached", vaultRoot);
|
|
97
|
+
await writeFile(join(vaultRoot, "project-map.json"), JSON.stringify({ "/Users/test/other": "other" }));
|
|
98
|
+
const result = await detectProject("/Users/test/cached", vaultRoot);
|
|
99
|
+
expect(result).toBe("cached");
|
|
100
|
+
});
|
|
101
|
+
it("cache invalidates for different vault path", async () => {
|
|
102
|
+
const projectMap1 = { "/Users/test/path1": "project1" };
|
|
103
|
+
await writeFile(join(vaultRoot, "project-map.json"), JSON.stringify(projectMap1));
|
|
104
|
+
invalidateProjectMapCache();
|
|
105
|
+
const vaultRoot2 = join(tmpdir(), `project-test-2-${Date.now()}`);
|
|
106
|
+
await mkdir(vaultRoot2, { recursive: true });
|
|
107
|
+
const projectMap2 = { "/Users/test/path1": "project2" };
|
|
108
|
+
await writeFile(join(vaultRoot2, "project-map.json"), JSON.stringify(projectMap2));
|
|
109
|
+
const result1 = await detectProject("/Users/test/path1", vaultRoot);
|
|
110
|
+
const result2 = await detectProject("/Users/test/path1", vaultRoot2);
|
|
111
|
+
expect(result1).toBe("project1");
|
|
112
|
+
expect(result2).toBe("project2");
|
|
113
|
+
await rm(vaultRoot2, { recursive: true, force: true });
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
//# sourceMappingURL=project-detector.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project-detector.test.js","sourceRoot":"","sources":["../../src/lib/project-detector.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validate that a path is safe to read from outside the vault.
|
|
3
|
+
* Used only by vault_init to scan git repos.
|
|
4
|
+
*
|
|
5
|
+
* Enforces:
|
|
6
|
+
* - Must be under $HOME
|
|
7
|
+
* - Must not contain .. or symlinks escaping home
|
|
8
|
+
* - Must be a git repository
|
|
9
|
+
*/
|
|
10
|
+
export declare function safeExternalPath(rawPath: string): Promise<string>;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later OR Commercial
|
|
2
|
+
import { homedir } from "os";
|
|
3
|
+
import { resolve } from "path";
|
|
4
|
+
import { realpath } from "fs/promises";
|
|
5
|
+
import { execFile } from "child_process";
|
|
6
|
+
import { promisify } from "util";
|
|
7
|
+
const execFileAsync = promisify(execFile);
|
|
8
|
+
/**
|
|
9
|
+
* Validate that a path is safe to read from outside the vault.
|
|
10
|
+
* Used only by vault_init to scan git repos.
|
|
11
|
+
*
|
|
12
|
+
* Enforces:
|
|
13
|
+
* - Must be under $HOME
|
|
14
|
+
* - Must not contain .. or symlinks escaping home
|
|
15
|
+
* - Must be a git repository
|
|
16
|
+
*/
|
|
17
|
+
export async function safeExternalPath(rawPath) {
|
|
18
|
+
const home = homedir();
|
|
19
|
+
const resolved = resolve(rawPath.startsWith("~") ? rawPath.replace("~", home) : rawPath);
|
|
20
|
+
// Must be under home directory (after resolution, this catches traversal attacks)
|
|
21
|
+
if (resolved !== home && !resolved.startsWith(home + "/")) {
|
|
22
|
+
throw new Error(`Path must be under home directory. Got: ${resolved}`);
|
|
23
|
+
}
|
|
24
|
+
// Verify real path doesn't escape home (symlink check)
|
|
25
|
+
try {
|
|
26
|
+
const real = await realpath(resolved);
|
|
27
|
+
if (real !== home && !real.startsWith(home + "/")) {
|
|
28
|
+
throw new Error(`Symlink escapes home directory: ${rawPath}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
catch (e) {
|
|
32
|
+
if (e.code === "ENOENT")
|
|
33
|
+
throw new Error(`Path does not exist: ${rawPath}`);
|
|
34
|
+
if (e.message?.includes("Symlink"))
|
|
35
|
+
throw e;
|
|
36
|
+
throw e;
|
|
37
|
+
}
|
|
38
|
+
// Must be a git repo
|
|
39
|
+
try {
|
|
40
|
+
await execFileAsync("git", ["rev-parse", "--git-dir"], { cwd: resolved, timeout: 3000 });
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
throw new Error(`Not a git repository: ${rawPath}`);
|
|
44
|
+
}
|
|
45
|
+
return resolved;
|
|
46
|
+
}
|
|
47
|
+
//# 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,2DAA2D;AAC3D,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,164 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later OR Commercial
|
|
2
|
+
import { execFile } from "child_process";
|
|
3
|
+
import { promisify } from "util";
|
|
4
|
+
import { resolve, relative } from "path";
|
|
5
|
+
import { readFile, stat } from "fs/promises";
|
|
6
|
+
import { parseFrontmatter } from "./frontmatter.js";
|
|
7
|
+
import { escapeRegex } from "./escape-regex.js";
|
|
8
|
+
const execFileAsync = promisify(execFile);
|
|
9
|
+
/**
|
|
10
|
+
* Validate that a search path stays within the vault root.
|
|
11
|
+
* Throws if the path escapes the vault boundary.
|
|
12
|
+
*/
|
|
13
|
+
function validateSearchPath(vaultPath, searchPath) {
|
|
14
|
+
const resolved = resolve(vaultPath, searchPath);
|
|
15
|
+
const rel = relative(vaultPath, resolved);
|
|
16
|
+
if (rel.startsWith("..") || rel.startsWith("/")) {
|
|
17
|
+
throw new Error(`Search path escapes vault: ${searchPath}`);
|
|
18
|
+
}
|
|
19
|
+
return resolved;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Convert an absolute path from rg output to a vault-relative path.
|
|
23
|
+
* Uses path.relative for correctness instead of string replacement.
|
|
24
|
+
*/
|
|
25
|
+
function toRelativePath(vaultPath, absPath) {
|
|
26
|
+
return relative(vaultPath, absPath);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Full-text search using ripgrep.
|
|
30
|
+
*/
|
|
31
|
+
export async function searchText(vaultPath, query, options = {}) {
|
|
32
|
+
const { pathFilter, limit = 10 } = options;
|
|
33
|
+
const args = [
|
|
34
|
+
"--json",
|
|
35
|
+
"--max-count", "1", // one match per file
|
|
36
|
+
"--type", "md",
|
|
37
|
+
"--ignore-case",
|
|
38
|
+
"--fixed-strings",
|
|
39
|
+
query,
|
|
40
|
+
];
|
|
41
|
+
// Validate pathFilter stays within vault
|
|
42
|
+
const searchPath = pathFilter
|
|
43
|
+
? validateSearchPath(vaultPath, pathFilter)
|
|
44
|
+
: vaultPath;
|
|
45
|
+
try {
|
|
46
|
+
const { stdout } = await execFileAsync("rg", [...args, searchPath], {
|
|
47
|
+
timeout: 10_000,
|
|
48
|
+
maxBuffer: 1024 * 1024,
|
|
49
|
+
});
|
|
50
|
+
const results = [];
|
|
51
|
+
for (const line of stdout.split("\n").filter(Boolean)) {
|
|
52
|
+
try {
|
|
53
|
+
const parsed = JSON.parse(line);
|
|
54
|
+
if (parsed.type === "match") {
|
|
55
|
+
const absPath = parsed.data.path.text;
|
|
56
|
+
const relPath = toRelativePath(vaultPath, absPath);
|
|
57
|
+
const snippet = parsed.data.lines.text.trim().slice(0, 200);
|
|
58
|
+
const lineNum = parsed.data.line_number;
|
|
59
|
+
// Skip hidden dirs and paths escaping vault
|
|
60
|
+
if (relPath.startsWith(".") || relPath.startsWith(".."))
|
|
61
|
+
continue;
|
|
62
|
+
results.push({ path: relPath, snippet, line: lineNum });
|
|
63
|
+
if (results.length >= limit)
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch (e) {
|
|
68
|
+
if (e.code !== "ENOENT") {
|
|
69
|
+
console.error("[search] Skipping malformed rg line:", e.message);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return results;
|
|
74
|
+
}
|
|
75
|
+
catch (e) {
|
|
76
|
+
if (e.code === "ENOENT" && e.message.includes("rg")) {
|
|
77
|
+
console.error("[search] ripgrep (rg) not found. Install it or search will be unavailable.");
|
|
78
|
+
throw new Error("ripgrep (rg) is required for text search. Install it: https://github.com/BurntSushi/ripgrep");
|
|
79
|
+
}
|
|
80
|
+
if (e.code === 1 || e.status === 1)
|
|
81
|
+
return [];
|
|
82
|
+
throw e;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Structured search by frontmatter properties.
|
|
87
|
+
* Scans files and filters by frontmatter fields.
|
|
88
|
+
*/
|
|
89
|
+
export async function searchStructured(vaultPath, filters, options = {}) {
|
|
90
|
+
const { limit = 10 } = options;
|
|
91
|
+
const results = [];
|
|
92
|
+
// Build grep patterns to narrow candidates (search for both key and value)
|
|
93
|
+
// SECURITY: escape both key and value to prevent regex injection
|
|
94
|
+
// Note: We search separately because YAML arrays span multiple lines
|
|
95
|
+
const firstKey = Object.keys(filters)[0];
|
|
96
|
+
const firstValue = filters[firstKey];
|
|
97
|
+
const keyPattern = escapeRegex(firstKey) + ":";
|
|
98
|
+
const valuePattern = escapeRegex(firstValue);
|
|
99
|
+
let candidates;
|
|
100
|
+
try {
|
|
101
|
+
const { stdout } = await execFileAsync("rg", [
|
|
102
|
+
"--files-with-matches",
|
|
103
|
+
"--type", "md",
|
|
104
|
+
"--regexp", keyPattern,
|
|
105
|
+
"--regexp", valuePattern,
|
|
106
|
+
vaultPath,
|
|
107
|
+
], { timeout: 10_000, maxBuffer: 1024 * 1024 });
|
|
108
|
+
candidates = stdout.trim().split("\n").filter(Boolean);
|
|
109
|
+
}
|
|
110
|
+
catch (e) {
|
|
111
|
+
if (e.code === "ENOENT" && e.message.includes("rg")) {
|
|
112
|
+
throw new Error("ripgrep (rg) is required for structured search. Install it: https://github.com/BurntSushi/ripgrep");
|
|
113
|
+
}
|
|
114
|
+
if (e.code === 1 || e.status === 1)
|
|
115
|
+
return [];
|
|
116
|
+
throw e;
|
|
117
|
+
}
|
|
118
|
+
const MAX_FILE_SIZE = 100_000; // 100 KB — skip large files in structured search
|
|
119
|
+
for (const absPath of candidates) {
|
|
120
|
+
if (results.length >= limit)
|
|
121
|
+
break;
|
|
122
|
+
const relPath = toRelativePath(vaultPath, absPath);
|
|
123
|
+
if (relPath.startsWith(".") || relPath.startsWith(".."))
|
|
124
|
+
continue;
|
|
125
|
+
try {
|
|
126
|
+
const fileStat = await stat(absPath);
|
|
127
|
+
if (fileStat.size > MAX_FILE_SIZE)
|
|
128
|
+
continue;
|
|
129
|
+
const content = await readFile(absPath, "utf-8");
|
|
130
|
+
const { data } = parseFrontmatter(content);
|
|
131
|
+
// Check all filters match
|
|
132
|
+
let match = true;
|
|
133
|
+
for (const [key, value] of Object.entries(filters)) {
|
|
134
|
+
const fieldValue = data[key];
|
|
135
|
+
if (Array.isArray(fieldValue)) {
|
|
136
|
+
if (!fieldValue.includes(value)) {
|
|
137
|
+
match = false;
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
else if (String(fieldValue) !== value) {
|
|
142
|
+
match = false;
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (match) {
|
|
147
|
+
const lines = content.split("\n");
|
|
148
|
+
const firstContentLine = lines.find((l) => l.startsWith("# ") || (l.trim() && !l.startsWith("---") && !l.match(/^\w+:\s/)));
|
|
149
|
+
results.push({
|
|
150
|
+
path: relPath,
|
|
151
|
+
snippet: firstContentLine?.trim().slice(0, 200) ?? "",
|
|
152
|
+
line: 1,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
catch (e) {
|
|
157
|
+
if (e.code !== "ENOENT") {
|
|
158
|
+
console.error(`[search] Skipping unreadable file ${absPath}:`, e.message);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return results;
|
|
163
|
+
}
|
|
164
|
+
//# 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 {};
|