grepmax 0.7.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.

Potentially problematic release.


This version of grepmax might be problematic. Click here for more details.

Files changed (70) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +33 -0
  3. package/README.md +375 -0
  4. package/dist/commands/claude-code.js +60 -0
  5. package/dist/commands/codex.js +98 -0
  6. package/dist/commands/doctor.js +92 -0
  7. package/dist/commands/droid.js +189 -0
  8. package/dist/commands/index.js +125 -0
  9. package/dist/commands/list.js +120 -0
  10. package/dist/commands/mcp.js +572 -0
  11. package/dist/commands/opencode.js +199 -0
  12. package/dist/commands/search.js +539 -0
  13. package/dist/commands/serve.js +512 -0
  14. package/dist/commands/setup.js +162 -0
  15. package/dist/commands/skeleton.js +288 -0
  16. package/dist/commands/symbols.js +129 -0
  17. package/dist/commands/trace.js +50 -0
  18. package/dist/commands/verify.js +174 -0
  19. package/dist/config.js +120 -0
  20. package/dist/eval.js +618 -0
  21. package/dist/index.js +82 -0
  22. package/dist/lib/core/languages.js +237 -0
  23. package/dist/lib/graph/graph-builder.js +105 -0
  24. package/dist/lib/index/chunker.js +663 -0
  25. package/dist/lib/index/grammar-loader.js +110 -0
  26. package/dist/lib/index/ignore-patterns.js +63 -0
  27. package/dist/lib/index/index-config.js +86 -0
  28. package/dist/lib/index/sync-helpers.js +97 -0
  29. package/dist/lib/index/syncer.js +396 -0
  30. package/dist/lib/index/walker.js +164 -0
  31. package/dist/lib/index/watcher.js +245 -0
  32. package/dist/lib/output/formatter.js +161 -0
  33. package/dist/lib/output/json-formatter.js +6 -0
  34. package/dist/lib/search/intent.js +23 -0
  35. package/dist/lib/search/searcher.js +475 -0
  36. package/dist/lib/setup/model-loader.js +107 -0
  37. package/dist/lib/setup/setup-helpers.js +106 -0
  38. package/dist/lib/skeleton/body-fields.js +175 -0
  39. package/dist/lib/skeleton/index.js +24 -0
  40. package/dist/lib/skeleton/retriever.js +36 -0
  41. package/dist/lib/skeleton/skeletonizer.js +483 -0
  42. package/dist/lib/skeleton/summary-formatter.js +90 -0
  43. package/dist/lib/store/meta-cache.js +143 -0
  44. package/dist/lib/store/types.js +2 -0
  45. package/dist/lib/store/vector-db.js +340 -0
  46. package/dist/lib/utils/cleanup.js +33 -0
  47. package/dist/lib/utils/exit.js +38 -0
  48. package/dist/lib/utils/file-utils.js +131 -0
  49. package/dist/lib/utils/filter-builder.js +17 -0
  50. package/dist/lib/utils/formatter.js +230 -0
  51. package/dist/lib/utils/git.js +83 -0
  52. package/dist/lib/utils/lock.js +157 -0
  53. package/dist/lib/utils/project-root.js +107 -0
  54. package/dist/lib/utils/server-registry.js +97 -0
  55. package/dist/lib/workers/colbert-math.js +107 -0
  56. package/dist/lib/workers/colbert-tokenizer.js +113 -0
  57. package/dist/lib/workers/download-worker.js +169 -0
  58. package/dist/lib/workers/embeddings/colbert.js +213 -0
  59. package/dist/lib/workers/embeddings/granite.js +180 -0
  60. package/dist/lib/workers/embeddings/mlx-client.js +144 -0
  61. package/dist/lib/workers/orchestrator.js +350 -0
  62. package/dist/lib/workers/pool.js +373 -0
  63. package/dist/lib/workers/process-child.js +92 -0
  64. package/dist/lib/workers/worker.js +31 -0
  65. package/package.json +80 -0
  66. package/plugins/osgrep/.claude-plugin/plugin.json +20 -0
  67. package/plugins/osgrep/hooks/start.js +92 -0
  68. package/plugins/osgrep/hooks/stop.js +3 -0
  69. package/plugins/osgrep/hooks.json +26 -0
  70. package/plugins/osgrep/skills/osgrep/SKILL.md +82 -0
@@ -0,0 +1,131 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
36
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
37
+ return new (P || (P = Promise))(function (resolve, reject) {
38
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
39
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
40
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
41
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
42
+ });
43
+ };
44
+ Object.defineProperty(exports, "__esModule", { value: true });
45
+ exports.computeBufferHash = computeBufferHash;
46
+ exports.hasNullByte = hasNullByte;
47
+ exports.readFileSnapshot = readFileSnapshot;
48
+ exports.isIndexableFile = isIndexableFile;
49
+ exports.isIndexablePath = isIndexablePath;
50
+ exports.formatDenseSnippet = formatDenseSnippet;
51
+ exports.isDevelopment = isDevelopment;
52
+ const node_crypto_1 = require("node:crypto");
53
+ const fs = __importStar(require("node:fs"));
54
+ const path = __importStar(require("node:path"));
55
+ const node_path_1 = require("node:path");
56
+ const config_1 = require("../../config");
57
+ function computeBufferHash(buffer) {
58
+ return (0, node_crypto_1.createHash)("sha256").update(buffer).digest("hex");
59
+ }
60
+ function hasNullByte(buffer, sampleLength = 1024) {
61
+ const length = Math.min(buffer.length, sampleLength);
62
+ for (let i = 0; i < length; i++) {
63
+ if (buffer[i] === 0)
64
+ return true;
65
+ }
66
+ return false;
67
+ }
68
+ function readFileSnapshot(filePath) {
69
+ return __awaiter(this, void 0, void 0, function* () {
70
+ const handle = yield fs.promises.open(filePath, "r");
71
+ try {
72
+ const before = yield handle.stat();
73
+ if (before.size > config_1.MAX_FILE_SIZE_BYTES) {
74
+ throw new Error("File exceeds maximum allowed size");
75
+ }
76
+ const size = before.size;
77
+ const buffer = size > 0 ? Buffer.allocUnsafe(size) : Buffer.alloc(0);
78
+ if (size > 0) {
79
+ yield handle.read(buffer, 0, size, 0);
80
+ }
81
+ const after = yield handle.stat();
82
+ if (before.mtimeMs !== after.mtimeMs || before.size !== after.size) {
83
+ throw new Error("File changed during read");
84
+ }
85
+ return { buffer, mtimeMs: after.mtimeMs, size: after.size };
86
+ }
87
+ finally {
88
+ yield handle.close();
89
+ }
90
+ });
91
+ }
92
+ // Check if a file should be indexed (extension and size).
93
+ function isIndexableFile(filePath, size) {
94
+ const ext = (0, node_path_1.extname)(filePath).toLowerCase();
95
+ const basename = path.basename(filePath).toLowerCase();
96
+ if (!config_1.INDEXABLE_EXTENSIONS.has(ext) && !config_1.INDEXABLE_EXTENSIONS.has(basename)) {
97
+ return false;
98
+ }
99
+ const withinSize = (s) => s > 0 && s <= config_1.MAX_FILE_SIZE_BYTES;
100
+ if (typeof size === "number") {
101
+ return withinSize(size);
102
+ }
103
+ try {
104
+ const stats = fs.statSync(filePath);
105
+ return withinSize(stats.size);
106
+ }
107
+ catch (_a) {
108
+ return false;
109
+ }
110
+ }
111
+ function isIndexablePath(filePath) {
112
+ return isIndexableFile(filePath);
113
+ }
114
+ function formatDenseSnippet(text, maxLength = 1500) {
115
+ const clean = text !== null && text !== void 0 ? text : "";
116
+ if (clean.length <= maxLength)
117
+ return clean;
118
+ return `${clean.slice(0, maxLength)}...`;
119
+ }
120
+ function isDevelopment() {
121
+ // Return false when running from within node_modules
122
+ if (__dirname.includes("node_modules")) {
123
+ return false;
124
+ }
125
+ // Return true only when NODE_ENV is explicitly "development"
126
+ if (process.env.NODE_ENV === "development") {
127
+ return true;
128
+ }
129
+ // Otherwise return false (production/other environments)
130
+ return false;
131
+ }
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.escapeSqlString = escapeSqlString;
4
+ exports.normalizePath = normalizePath;
5
+ function escapeSqlString(str) {
6
+ // LanceDB (via DataFusion) treats backslashes literally in standard strings.
7
+ // We only need to escape single quotes by doubling them.
8
+ return str.replace(/'/g, "''");
9
+ }
10
+ /**
11
+ * Normalizes a path to use forward slashes, ensuring consistency across platforms.
12
+ * @param p The path to normalize
13
+ * @returns The normalized path with forward slashes
14
+ */
15
+ function normalizePath(p) {
16
+ return p.replace(/\\/g, "/");
17
+ }
@@ -0,0 +1,230 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.formatTextResults = formatTextResults;
37
+ const path = __importStar(require("node:path"));
38
+ const cli_highlight_1 = require("cli-highlight");
39
+ const style = {
40
+ bold: (s) => `\x1b[1m${s}\x1b[22m`,
41
+ dim: (s) => `\x1b[2m${s}\x1b[22m`,
42
+ green: (s) => `\x1b[32m${s}\x1b[39m`,
43
+ blue: (s) => `\x1b[34m${s}\x1b[39m`,
44
+ };
45
+ const languages_1 = require("../core/languages");
46
+ function detectLanguage(filePath) {
47
+ const ext = path.extname(filePath);
48
+ const lang = (0, languages_1.getLanguageByExtension)(ext);
49
+ return (lang === null || lang === void 0 ? void 0 : lang.id) || "plaintext";
50
+ }
51
+ function cleanSnippetLines(snippet) {
52
+ const NOISE_PREFIXES = [
53
+ "File:",
54
+ "Top comments:",
55
+ "Preamble:",
56
+ "(anchor)",
57
+ "Imports:",
58
+ "Exports:",
59
+ "---",
60
+ ];
61
+ return snippet
62
+ .split("\n")
63
+ .map((line) => {
64
+ let next = line.trimEnd();
65
+ // Strip inline metadata that sometimes gets glued onto code lines
66
+ const fileIdx = next.indexOf("File:");
67
+ if (fileIdx !== -1) {
68
+ next = next.slice(0, fileIdx).trimEnd();
69
+ }
70
+ return next;
71
+ })
72
+ .filter((line) => {
73
+ const trimmed = line.trim();
74
+ if (!trimmed)
75
+ return false;
76
+ return !NOISE_PREFIXES.some((p) => trimmed.startsWith(p));
77
+ })
78
+ .map((line) => {
79
+ const limit = 140;
80
+ if (line.length <= limit)
81
+ return line;
82
+ return `${line.slice(0, limit)}${style.dim(" ...")}`;
83
+ });
84
+ }
85
+ /**
86
+ * Formats search results for display (agent plain mode or human pretty mode).
87
+ * Supports compact (paths only), score display, and content truncation options.
88
+ */
89
+ function formatTextResults(results, query, root, options) {
90
+ var _a, _b, _c;
91
+ if (results.length === 0)
92
+ return `osgrep: No results found for "${query}".`;
93
+ // --- MODE: COMPACT (File paths only) ---
94
+ if (options.compact) {
95
+ const uniquePaths = Array.from(new Set(results.map((r) => r.path))).sort();
96
+ return uniquePaths.map((p) => path.relative(root, p)).join("\n");
97
+ }
98
+ const fileGroups = new Map();
99
+ results.forEach((r) => {
100
+ const existing = fileGroups.get(r.path) || [];
101
+ existing.push(r);
102
+ fileGroups.set(r.path, existing);
103
+ });
104
+ const fileCount = fileGroups.size;
105
+ // --- MODE A: AGENT (Hyper-Dense) ---
106
+ // Goal: Max information density per token.
107
+ // --- MODE A: AGENT (Hyper-Dense) ---
108
+ // Goal: Max information density per token.
109
+ if (options.isPlain) {
110
+ let output = "";
111
+ // Keep snippets compact but present so agents still see the code.
112
+ const maxLines = 20;
113
+ results.forEach((item) => {
114
+ const relPath = path.relative(root, item.path);
115
+ const line = Math.max(1, item.start_line + 1);
116
+ // 1. Semantic Tags (The Agent's Guide)
117
+ const tags = [];
118
+ const type = item.chunk_type || "";
119
+ if (type.match(/function|class|method/))
120
+ tags.push("Definition");
121
+ const isTestPath = /(^|\/)(__tests__|tests?|specs?)(\/|$)/i.test(relPath) ||
122
+ /\.(test|spec)\.[cm]?[jt]sx?$/i.test(relPath);
123
+ if (isTestPath)
124
+ tags.push("Test");
125
+ const tagStr = tags.length > 0 ? ` [${tags.join(",")}]` : "";
126
+ const lines = cleanSnippetLines(item.content);
127
+ const truncated = !options.content && lines.length > maxLines
128
+ ? [
129
+ ...lines.slice(0, maxLines),
130
+ `... (+${lines.length - maxLines} more lines)`,
131
+ ]
132
+ : lines;
133
+ const scoreStr = options.showScores
134
+ ? ` ${style.dim(`(score: ${item.score.toFixed(3)})`)}`
135
+ : "";
136
+ output += `${relPath}:${line}${scoreStr}${tagStr}\n`;
137
+ truncated.forEach((ln) => {
138
+ output += ` ${ln}\n`;
139
+ });
140
+ output += "\n";
141
+ });
142
+ output += `osgrep results (${results.length} matches across ${fileCount} files)`;
143
+ return output.trim();
144
+ }
145
+ // --- MODE B: HUMAN (Pretty) ---
146
+ // First pass: merge chunks and count actual displayed results
147
+ const mergedGroups = [];
148
+ for (const [filePath, chunks] of fileGroups) {
149
+ // 1. Sort by score descending (best matches first)
150
+ chunks.sort((a, b) => b.score - a.score);
151
+ // 2. Apply per-file limit
152
+ const limit = (_a = options.perFile) !== null && _a !== void 0 ? _a : 1000;
153
+ const limitedChunks = chunks.slice(0, limit);
154
+ // 3. Re-sort by line number for display
155
+ limitedChunks.sort((a, b) => a.start_line - b.start_line);
156
+ // Smart Stitching Logic
157
+ const merged = [];
158
+ if (limitedChunks.length > 0) {
159
+ let current = limitedChunks[0];
160
+ for (let i = 1; i < limitedChunks.length; i++) {
161
+ const next = limitedChunks[i];
162
+ if (next.start_line <= current.end_line + 10) {
163
+ current.content += `\n // ...\n${next.content}`;
164
+ current.end_line = next.end_line;
165
+ if ((_b = next.chunk_type) === null || _b === void 0 ? void 0 : _b.match(/function|class/))
166
+ current.chunk_type = next.chunk_type;
167
+ }
168
+ else {
169
+ merged.push(current);
170
+ current = Object.assign({}, next);
171
+ }
172
+ }
173
+ merged.push(current);
174
+ }
175
+ mergedGroups.push({ filePath, merged });
176
+ }
177
+ const displayedCount = mergedGroups.reduce((sum, g) => sum + g.merged.length, 0);
178
+ let output = `\n${style.bold(`osgrep results (query: "${query}", ${displayedCount} matches across ${fileCount} files)`)}\n`;
179
+ let rank = 1;
180
+ for (const { filePath, merged } of mergedGroups) {
181
+ const relPath = path.relative(root, filePath);
182
+ for (const item of merged) {
183
+ const tags = [];
184
+ if ((_c = item.chunk_type) === null || _c === void 0 ? void 0 : _c.match(/function|class/))
185
+ tags.push("Definition");
186
+ const tagStr = tags.length > 0 ? ` ${style.blue(`[${tags.join(", ")}]`)}` : "";
187
+ const line = Math.max(1, item.start_line + 1);
188
+ const snippet = item.content
189
+ .trim()
190
+ .replace(/^File:.*\n/, "")
191
+ .replace(/^Function:.*\n/, "")
192
+ .trim();
193
+ const lines = cleanSnippetLines(snippet);
194
+ const maxLines = 12;
195
+ const truncated = !options.content && lines.length > maxLines
196
+ ? [
197
+ ...lines.slice(0, maxLines),
198
+ style.dim(`... (+${lines.length - maxLines} more lines)`),
199
+ ]
200
+ : lines;
201
+ // Apply syntax highlighting for humans
202
+ let rendered = truncated.join("\n");
203
+ try {
204
+ const lang = detectLanguage(filePath);
205
+ rendered = (0, cli_highlight_1.highlight)(rendered, {
206
+ language: lang,
207
+ ignoreIllegals: true,
208
+ });
209
+ }
210
+ catch (_d) {
211
+ // fall back to non-highlighted text
212
+ }
213
+ const scoreStr = options.showScores
214
+ ? ` ${style.dim(`(score: ${item.score.toFixed(3)})`)}`
215
+ : "";
216
+ output += `${rank}) 📂 ${style.green(relPath)}${style.dim(`:${line}`)}${tagStr}${scoreStr}\n`;
217
+ const numbered = rendered.split("\n").map((ln, idx) => {
218
+ const num = style.dim(`${line + idx}`.padStart(4));
219
+ return `${num} │ ${ln}`;
220
+ });
221
+ numbered.forEach((ln) => {
222
+ output += `${ln}\n`;
223
+ });
224
+ output += "\n";
225
+ rank++;
226
+ }
227
+ }
228
+ output += style.dim(`${displayedCount} matches across ${fileCount} files`);
229
+ return output.trimEnd();
230
+ }
@@ -0,0 +1,83 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.isWorktree = isWorktree;
37
+ exports.getGitCommonDir = getGitCommonDir;
38
+ exports.getMainRepoRoot = getMainRepoRoot;
39
+ const fs = __importStar(require("node:fs"));
40
+ const path = __importStar(require("node:path"));
41
+ function isWorktree(dir) {
42
+ const gitPath = path.join(dir, ".git");
43
+ try {
44
+ const stats = fs.statSync(gitPath);
45
+ return stats.isFile();
46
+ }
47
+ catch (_a) {
48
+ return false;
49
+ }
50
+ }
51
+ function getGitCommonDir(worktreeRoot) {
52
+ const gitPath = path.join(worktreeRoot, ".git");
53
+ try {
54
+ const content = fs.readFileSync(gitPath, "utf-8").trim();
55
+ if (!content.startsWith("gitdir: "))
56
+ return null;
57
+ const gitDir = content.slice(8).trim();
58
+ const absGitDir = path.resolve(worktreeRoot, gitDir);
59
+ const commonDirFile = path.join(absGitDir, "commondir");
60
+ if (fs.existsSync(commonDirFile)) {
61
+ const commonPath = fs.readFileSync(commonDirFile, "utf-8").trim();
62
+ return path.resolve(absGitDir, commonPath);
63
+ }
64
+ // Fallback: assume standard structure
65
+ return path.resolve(absGitDir, "../../");
66
+ }
67
+ catch (_a) {
68
+ return null;
69
+ }
70
+ }
71
+ /**
72
+ * Resolves the main repository root from a worktree root.
73
+ */
74
+ function getMainRepoRoot(worktreeRoot) {
75
+ if (!isWorktree(worktreeRoot))
76
+ return null;
77
+ const commonDir = getGitCommonDir(worktreeRoot);
78
+ if (!commonDir)
79
+ return null;
80
+ // The common dir is usually .git inside the main repo root.
81
+ // So the main repo root is the parent of commonDir.
82
+ return path.dirname(commonDir);
83
+ }
@@ -0,0 +1,157 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
36
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
37
+ return new (P || (P = Promise))(function (resolve, reject) {
38
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
39
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
40
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
41
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
42
+ });
43
+ };
44
+ Object.defineProperty(exports, "__esModule", { value: true });
45
+ exports.acquireWriterLock = acquireWriterLock;
46
+ exports.acquireWriterLockWithRetry = acquireWriterLockWithRetry;
47
+ exports.isLocked = isLocked;
48
+ const fs = __importStar(require("node:fs"));
49
+ const path = __importStar(require("node:path"));
50
+ function parseLock(lockPath) {
51
+ try {
52
+ const contents = fs.readFileSync(lockPath, "utf-8").trim();
53
+ const [pidLine, ts] = contents.split("\n");
54
+ const pid = Number.parseInt(pidLine !== null && pidLine !== void 0 ? pidLine : "", 10);
55
+ return { pid: Number.isFinite(pid) ? pid : null, startedAt: ts };
56
+ }
57
+ catch (_a) {
58
+ return { pid: null };
59
+ }
60
+ }
61
+ function isProcessAlive(pid) {
62
+ if (!pid || pid <= 0)
63
+ return false;
64
+ try {
65
+ process.kill(pid, 0);
66
+ return true;
67
+ }
68
+ catch (err) {
69
+ const code = err === null || err === void 0 ? void 0 : err.code;
70
+ // ESRCH: no such process. EPERM means the process exists but we lack permission.
71
+ if (code === "ESRCH")
72
+ return false;
73
+ return true;
74
+ }
75
+ }
76
+ function removeLock(lockPath) {
77
+ return __awaiter(this, void 0, void 0, function* () {
78
+ try {
79
+ yield fs.promises.unlink(lockPath);
80
+ }
81
+ catch (err) {
82
+ if ((err === null || err === void 0 ? void 0 : err.code) !== "ENOENT") {
83
+ throw err;
84
+ }
85
+ }
86
+ });
87
+ }
88
+ function acquireWriterLock(lockDir) {
89
+ return __awaiter(this, void 0, void 0, function* () {
90
+ const lockPath = path.join(lockDir, "LOCK");
91
+ const writeLock = () => __awaiter(this, void 0, void 0, function* () {
92
+ yield fs.promises.writeFile(lockPath, `${process.pid}\n${new Date().toISOString()}`, { flag: "wx" });
93
+ });
94
+ try {
95
+ yield writeLock();
96
+ }
97
+ catch (err) {
98
+ const code = err === null || err === void 0 ? void 0 : err.code;
99
+ if (code !== "EEXIST")
100
+ throw err;
101
+ const { pid, startedAt } = parseLock(lockPath);
102
+ const alive = isProcessAlive(pid);
103
+ if (!alive) {
104
+ yield removeLock(lockPath);
105
+ yield writeLock();
106
+ }
107
+ else {
108
+ const holderDesc = pid
109
+ ? `${pid}${startedAt ? ` @ ${startedAt}` : ""}`
110
+ : "unknown";
111
+ throw new Error(`.osgrep lock already held (${holderDesc}). Another indexing process is running or the lock must be cleared.`);
112
+ }
113
+ }
114
+ return {
115
+ release: () => __awaiter(this, void 0, void 0, function* () {
116
+ try {
117
+ yield removeLock(lockPath);
118
+ }
119
+ catch (err) {
120
+ console.warn("[lock] Failed to remove lock:", err);
121
+ }
122
+ }),
123
+ };
124
+ });
125
+ }
126
+ function acquireWriterLockWithRetry(lockDir, options) {
127
+ return __awaiter(this, void 0, void 0, function* () {
128
+ var _a, _b;
129
+ const maxRetries = (_a = options === null || options === void 0 ? void 0 : options.maxRetries) !== null && _a !== void 0 ? _a : 5;
130
+ const retryDelayMs = (_b = options === null || options === void 0 ? void 0 : options.retryDelayMs) !== null && _b !== void 0 ? _b : 1000;
131
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
132
+ try {
133
+ return yield acquireWriterLock(lockDir);
134
+ }
135
+ catch (err) {
136
+ const isLockHeld = err instanceof Error && err.message.includes("lock already held");
137
+ if (!isLockHeld || attempt === maxRetries - 1) {
138
+ throw err;
139
+ }
140
+ yield new Promise((resolve) => setTimeout(resolve, retryDelayMs * (attempt + 1)));
141
+ }
142
+ }
143
+ throw new Error("Failed to acquire lock after retries");
144
+ });
145
+ }
146
+ function isLocked(lockDir) {
147
+ try {
148
+ const lockPath = path.join(lockDir, "LOCK");
149
+ if (!fs.existsSync(lockPath))
150
+ return false;
151
+ const { pid } = parseLock(lockPath);
152
+ return isProcessAlive(pid);
153
+ }
154
+ catch (_a) {
155
+ return false;
156
+ }
157
+ }