@swarmvaultai/engine 0.1.27 → 0.1.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/dist/chunk-EXD4RWT3.js +1131 -0
- package/dist/chunk-IHMJCCXR.js +1146 -0
- package/dist/index.d.ts +14 -6
- package/dist/index.js +1096 -162
- package/dist/registry-5SYH3Y3U.js +12 -0
- package/dist/registry-KLO5YIHP.js +12 -0
- package/dist/viewer/assets/index-Csm8eB3P.js +331 -0
- package/dist/viewer/assets/index-DUJ6MWHL.css +1 -0
- package/dist/viewer/index.html +2 -2
- package/dist/viewer/lib.js +178 -0
- package/package.json +12 -9
- package/LICENSE +0 -21
- package/dist/viewer/assets/index-C7PCTMog.js +0 -330
- package/dist/viewer/assets/index-DiMCbjBi.css +0 -1
package/dist/index.js
CHANGED
|
@@ -21,19 +21,35 @@ import {
|
|
|
21
21
|
uniqueBy,
|
|
22
22
|
writeFileIfChanged,
|
|
23
23
|
writeJsonFile
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-IHMJCCXR.js";
|
|
25
25
|
|
|
26
26
|
// src/agents.ts
|
|
27
|
+
import crypto from "crypto";
|
|
27
28
|
import fs from "fs/promises";
|
|
28
29
|
import path from "path";
|
|
30
|
+
import YAML from "yaml";
|
|
29
31
|
var managedStart = "<!-- swarmvault:managed:start -->";
|
|
30
32
|
var managedEnd = "<!-- swarmvault:managed:end -->";
|
|
31
33
|
var legacyManagedStart = "<!-- vault:managed:start -->";
|
|
32
34
|
var legacyManagedEnd = "<!-- vault:managed:end -->";
|
|
35
|
+
var claudeSearchMatcher = "Glob|Grep";
|
|
36
|
+
var claudeSessionMatchers = ["startup", "resume", "clear", "compact"];
|
|
37
|
+
var geminiSessionMatcher = "startup";
|
|
38
|
+
var geminiSearchMatcher = "glob|grep|search|find";
|
|
39
|
+
var copilotHookVersion = 1;
|
|
40
|
+
var agentFileKinds = {
|
|
41
|
+
agents: "AGENTS.md",
|
|
42
|
+
claude: "CLAUDE.md",
|
|
43
|
+
gemini: "GEMINI.md",
|
|
44
|
+
cursor: ".cursor/rules/swarmvault.mdc",
|
|
45
|
+
aider: "CONVENTIONS.md",
|
|
46
|
+
copilot: ".github/copilot-instructions.md"
|
|
47
|
+
};
|
|
33
48
|
function buildManagedBlock(target) {
|
|
34
|
-
const
|
|
49
|
+
const heading = target === "aider" ? "# SwarmVault Conventions" : target === "copilot" ? "# SwarmVault Repository Instructions" : "# SwarmVault Rules";
|
|
50
|
+
return [
|
|
35
51
|
managedStart,
|
|
36
|
-
|
|
52
|
+
heading,
|
|
37
53
|
"",
|
|
38
54
|
"- Read `swarmvault.schema.md` before compile or query style work. It is the canonical schema path.",
|
|
39
55
|
"- Treat `raw/` as immutable source input.",
|
|
@@ -46,55 +62,77 @@ function buildManagedBlock(target) {
|
|
|
46
62
|
managedEnd,
|
|
47
63
|
""
|
|
48
64
|
].join("\n");
|
|
49
|
-
if (target === "cursor") {
|
|
50
|
-
return body;
|
|
51
|
-
}
|
|
52
|
-
return body;
|
|
53
65
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
async function installClaudeHook(rootDir) {
|
|
57
|
-
const settingsPath = path.join(rootDir, ".claude", "settings.json");
|
|
58
|
-
await ensureDir(path.dirname(settingsPath));
|
|
59
|
-
let settings = {};
|
|
60
|
-
if (await fileExists(settingsPath)) {
|
|
61
|
-
try {
|
|
62
|
-
settings = JSON.parse(await fs.readFile(settingsPath, "utf8"));
|
|
63
|
-
} catch {
|
|
64
|
-
settings = {};
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
const hooks = settings.hooks ?? {};
|
|
68
|
-
const preToolUse = hooks.PreToolUse ?? [];
|
|
69
|
-
const exists = preToolUse.some((entry) => entry.matcher === claudeHookMatcher && JSON.stringify(entry).includes("swarmvault:"));
|
|
70
|
-
if (!exists) {
|
|
71
|
-
preToolUse.push({
|
|
72
|
-
matcher: claudeHookMatcher,
|
|
73
|
-
hooks: [{ type: "command", command: claudeHookCommand }]
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
settings.hooks = { ...hooks, PreToolUse: preToolUse };
|
|
77
|
-
await fs.writeFile(settingsPath, `${JSON.stringify(settings, null, 2)}
|
|
78
|
-
`, "utf8");
|
|
79
|
-
return settingsPath;
|
|
66
|
+
function supportsAgentHook(agent) {
|
|
67
|
+
return agent === "claude" || agent === "opencode" || agent === "gemini" || agent === "copilot";
|
|
80
68
|
}
|
|
81
|
-
function
|
|
69
|
+
function primaryTargetPathForAgent(rootDir, agent) {
|
|
82
70
|
switch (agent) {
|
|
83
71
|
case "codex":
|
|
84
72
|
case "goose":
|
|
85
73
|
case "pi":
|
|
86
74
|
case "opencode":
|
|
87
|
-
return path.join(rootDir,
|
|
75
|
+
return path.join(rootDir, agentFileKinds.agents);
|
|
88
76
|
case "claude":
|
|
89
|
-
return path.join(rootDir,
|
|
77
|
+
return path.join(rootDir, agentFileKinds.claude);
|
|
90
78
|
case "gemini":
|
|
91
|
-
return path.join(rootDir,
|
|
79
|
+
return path.join(rootDir, agentFileKinds.gemini);
|
|
92
80
|
case "cursor":
|
|
93
|
-
return path.join(rootDir,
|
|
81
|
+
return path.join(rootDir, agentFileKinds.cursor);
|
|
82
|
+
case "aider":
|
|
83
|
+
return path.join(rootDir, agentFileKinds.aider);
|
|
84
|
+
case "copilot":
|
|
85
|
+
return path.join(rootDir, agentFileKinds.copilot);
|
|
94
86
|
default:
|
|
95
87
|
throw new Error(`Unsupported agent ${String(agent)}`);
|
|
96
88
|
}
|
|
97
89
|
}
|
|
90
|
+
function hookScriptPathForAgent(rootDir, agent) {
|
|
91
|
+
switch (agent) {
|
|
92
|
+
case "claude":
|
|
93
|
+
return path.join(rootDir, ".claude", "hooks", "swarmvault-graph-first.js");
|
|
94
|
+
case "opencode":
|
|
95
|
+
return path.join(rootDir, ".opencode", "plugins", "swarmvault-graph-first.js");
|
|
96
|
+
case "gemini":
|
|
97
|
+
return path.join(rootDir, ".gemini", "hooks", "swarmvault-graph-first.js");
|
|
98
|
+
case "copilot":
|
|
99
|
+
return path.join(rootDir, ".github", "hooks", "swarmvault-graph-first.js");
|
|
100
|
+
default:
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function hookConfigPathForAgent(rootDir, agent) {
|
|
105
|
+
switch (agent) {
|
|
106
|
+
case "claude":
|
|
107
|
+
return path.join(rootDir, ".claude", "settings.json");
|
|
108
|
+
case "gemini":
|
|
109
|
+
return path.join(rootDir, ".gemini", "settings.json");
|
|
110
|
+
case "copilot":
|
|
111
|
+
return path.join(rootDir, ".github", "hooks", "swarmvault-graph-first.json");
|
|
112
|
+
default:
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function targetsForAgent(rootDir, agent, options = {}) {
|
|
117
|
+
const targets = [primaryTargetPathForAgent(rootDir, agent)];
|
|
118
|
+
if (agent === "copilot") {
|
|
119
|
+
targets.push(path.join(rootDir, agentFileKinds.agents));
|
|
120
|
+
}
|
|
121
|
+
if (agent === "aider") {
|
|
122
|
+
targets.push(path.join(rootDir, ".aider.conf.yml"));
|
|
123
|
+
}
|
|
124
|
+
if (options.hook && supportsAgentHook(agent)) {
|
|
125
|
+
const configPath = hookConfigPathForAgent(rootDir, agent);
|
|
126
|
+
const scriptPath = hookScriptPathForAgent(rootDir, agent);
|
|
127
|
+
if (configPath) {
|
|
128
|
+
targets.push(configPath);
|
|
129
|
+
}
|
|
130
|
+
if (scriptPath) {
|
|
131
|
+
targets.push(scriptPath);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return [...new Set(targets)];
|
|
135
|
+
}
|
|
98
136
|
async function upsertManagedBlock(filePath, block) {
|
|
99
137
|
const existing = await fileExists(filePath) ? await fs.readFile(filePath, "utf8") : "";
|
|
100
138
|
if (!existing) {
|
|
@@ -115,51 +153,593 @@ async function upsertManagedBlock(filePath, block) {
|
|
|
115
153
|
${block}
|
|
116
154
|
`, "utf8");
|
|
117
155
|
}
|
|
156
|
+
async function writeOwnedFile(filePath, content, executable = false) {
|
|
157
|
+
await ensureDir(path.dirname(filePath));
|
|
158
|
+
await fs.writeFile(filePath, content, {
|
|
159
|
+
encoding: "utf8",
|
|
160
|
+
mode: executable ? 493 : 420
|
|
161
|
+
});
|
|
162
|
+
if (executable) {
|
|
163
|
+
await fs.chmod(filePath, 493);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
async function readJsonWithWarnings(filePath, fallback, label) {
|
|
167
|
+
if (!await fileExists(filePath)) {
|
|
168
|
+
return { data: fallback, warnings: [] };
|
|
169
|
+
}
|
|
170
|
+
try {
|
|
171
|
+
const parsed = JSON.parse(await fs.readFile(filePath, "utf8"));
|
|
172
|
+
return { data: parsed, warnings: [] };
|
|
173
|
+
} catch {
|
|
174
|
+
return {
|
|
175
|
+
data: fallback,
|
|
176
|
+
warnings: [`Could not parse ${label}. Left the existing file unchanged.`]
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
async function installClaudeHook(rootDir) {
|
|
181
|
+
const settingsPath = path.join(rootDir, ".claude", "settings.json");
|
|
182
|
+
const scriptPath = path.join(rootDir, ".claude", "hooks", "swarmvault-graph-first.js");
|
|
183
|
+
await writeOwnedFile(scriptPath, buildClaudeHookScript(), true);
|
|
184
|
+
await ensureDir(path.dirname(settingsPath));
|
|
185
|
+
const { data: settings, warnings } = await readJsonWithWarnings(settingsPath, {}, ".claude/settings.json");
|
|
186
|
+
if (warnings.length > 0 && await fileExists(settingsPath)) {
|
|
187
|
+
return { path: settingsPath, warnings };
|
|
188
|
+
}
|
|
189
|
+
const hooks = settings.hooks ?? {};
|
|
190
|
+
const sessionStart = hooks.SessionStart ?? [];
|
|
191
|
+
const preToolUse = hooks.PreToolUse ?? [];
|
|
192
|
+
const sessionCommand = 'node "$CLAUDE_PROJECT_DIR/.claude/hooks/swarmvault-graph-first.js" session-start';
|
|
193
|
+
const preToolUseCommand = 'node "$CLAUDE_PROJECT_DIR/.claude/hooks/swarmvault-graph-first.js" pre-tool-use';
|
|
194
|
+
for (const matcher of claudeSessionMatchers) {
|
|
195
|
+
if (!sessionStart.some((entry) => entry.matcher === matcher && JSON.stringify(entry).includes("swarmvault-graph-first.js"))) {
|
|
196
|
+
sessionStart.push({
|
|
197
|
+
matcher,
|
|
198
|
+
hooks: [{ type: "command", command: sessionCommand }]
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (!preToolUse.some((entry) => entry.matcher === claudeSearchMatcher && JSON.stringify(entry).includes("swarmvault-graph-first.js"))) {
|
|
203
|
+
preToolUse.push({
|
|
204
|
+
matcher: claudeSearchMatcher,
|
|
205
|
+
hooks: [{ type: "command", command: preToolUseCommand }]
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
settings.hooks = { ...hooks, SessionStart: sessionStart, PreToolUse: preToolUse };
|
|
209
|
+
await fs.writeFile(settingsPath, `${JSON.stringify(settings, null, 2)}
|
|
210
|
+
`, "utf8");
|
|
211
|
+
return { path: settingsPath, warnings: [] };
|
|
212
|
+
}
|
|
213
|
+
function buildClaudeHookScript() {
|
|
214
|
+
return `#!/usr/bin/env node
|
|
215
|
+
import crypto from "node:crypto";
|
|
216
|
+
import fs from "node:fs/promises";
|
|
217
|
+
import os from "node:os";
|
|
218
|
+
import path from "node:path";
|
|
219
|
+
|
|
220
|
+
${markerStateSnippet("claude").trim()}
|
|
221
|
+
|
|
222
|
+
async function readInput() {
|
|
223
|
+
let body = "";
|
|
224
|
+
for await (const chunk of process.stdin) {
|
|
225
|
+
body += chunk;
|
|
226
|
+
}
|
|
227
|
+
if (!body.trim()) {
|
|
228
|
+
return {};
|
|
229
|
+
}
|
|
230
|
+
try {
|
|
231
|
+
return JSON.parse(body);
|
|
232
|
+
} catch {
|
|
233
|
+
return {};
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function emit(value) {
|
|
238
|
+
process.stdout.write(\`\${JSON.stringify(value)}\\n\`);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const mode = process.argv[2] ?? "";
|
|
242
|
+
const input = await readInput();
|
|
243
|
+
const cwd = resolveInputCwd(input);
|
|
244
|
+
const reportNote = "SwarmVault graph report exists at wiki/graph/report.md. Read it before broad grep/glob searching.";
|
|
245
|
+
|
|
246
|
+
if (!(await hasReport(cwd))) {
|
|
247
|
+
emit({});
|
|
248
|
+
process.exit(0);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (mode === "session-start") {
|
|
252
|
+
await resetSession(cwd);
|
|
253
|
+
emit({
|
|
254
|
+
hookSpecificOutput: {
|
|
255
|
+
hookEventName: "SessionStart",
|
|
256
|
+
additionalContext: reportNote
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
process.exit(0);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const toolName = resolveToolName(input);
|
|
263
|
+
if (collectCandidatePaths(input).some((value) => isReportPath(value, cwd))) {
|
|
264
|
+
await markReportRead(cwd);
|
|
265
|
+
emit({});
|
|
266
|
+
process.exit(0);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (isBroadSearchTool(toolName) && !(await hasSeenReport(cwd))) {
|
|
270
|
+
emit({
|
|
271
|
+
hookSpecificOutput: {
|
|
272
|
+
hookEventName: "PreToolUse",
|
|
273
|
+
additionalContext: reportNote
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
process.exit(0);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
emit({});
|
|
280
|
+
`;
|
|
281
|
+
}
|
|
282
|
+
function markerStateSnippet(agentKey) {
|
|
283
|
+
return `
|
|
284
|
+
function markerState(cwd) {
|
|
285
|
+
const hash = crypto.createHash("sha256").update(cwd).digest("hex");
|
|
286
|
+
const dir = path.join(os.tmpdir(), "swarmvault-agent-hooks", "${agentKey}", hash);
|
|
287
|
+
return {
|
|
288
|
+
dir,
|
|
289
|
+
markerPath: path.join(dir, "report-read")
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function isReportPath(value, cwd) {
|
|
294
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
295
|
+
return false;
|
|
296
|
+
}
|
|
297
|
+
const reportSuffix = path.join("wiki", "graph", "report.md");
|
|
298
|
+
const normalized = value.replaceAll("\\\\", "/");
|
|
299
|
+
const reportNormalized = reportSuffix.replaceAll("\\\\", "/");
|
|
300
|
+
if (normalized.endsWith(reportNormalized)) {
|
|
301
|
+
return true;
|
|
302
|
+
}
|
|
303
|
+
return path.resolve(cwd, value) === path.resolve(cwd, reportSuffix);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function collectCandidatePaths(node, acc = []) {
|
|
307
|
+
if (typeof node === "string") {
|
|
308
|
+
acc.push(node);
|
|
309
|
+
return acc;
|
|
310
|
+
}
|
|
311
|
+
if (!node || typeof node !== "object") {
|
|
312
|
+
return acc;
|
|
313
|
+
}
|
|
314
|
+
if (Array.isArray(node)) {
|
|
315
|
+
for (const item of node) {
|
|
316
|
+
collectCandidatePaths(item, acc);
|
|
317
|
+
}
|
|
318
|
+
return acc;
|
|
319
|
+
}
|
|
320
|
+
for (const [key, value] of Object.entries(node)) {
|
|
321
|
+
if (["path", "filePath", "file_path", "paths", "target", "targets"].includes(key)) {
|
|
322
|
+
collectCandidatePaths(value, acc);
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
collectCandidatePaths(value, acc);
|
|
326
|
+
}
|
|
327
|
+
return acc;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function resolveInputCwd(input) {
|
|
331
|
+
return path.resolve(
|
|
332
|
+
input?.cwd ??
|
|
333
|
+
input?.directory ??
|
|
334
|
+
input?.workspace?.cwd ??
|
|
335
|
+
input?.toolInput?.cwd ??
|
|
336
|
+
process.cwd()
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function resolveToolName(input) {
|
|
341
|
+
return String(input?.toolName ?? input?.tool_name ?? input?.tool?.name ?? input?.name ?? "");
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
async function hasReport(cwd) {
|
|
345
|
+
try {
|
|
346
|
+
await fs.access(path.join(cwd, "wiki", "graph", "report.md"));
|
|
347
|
+
return true;
|
|
348
|
+
} catch {
|
|
349
|
+
return false;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
async function markReportRead(cwd) {
|
|
354
|
+
const state = markerState(cwd);
|
|
355
|
+
await fs.mkdir(state.dir, { recursive: true });
|
|
356
|
+
await fs.writeFile(state.markerPath, "seen\\n", "utf8");
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
async function hasSeenReport(cwd) {
|
|
360
|
+
const state = markerState(cwd);
|
|
361
|
+
try {
|
|
362
|
+
await fs.access(state.markerPath);
|
|
363
|
+
return true;
|
|
364
|
+
} catch {
|
|
365
|
+
return false;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
async function resetSession(cwd) {
|
|
370
|
+
const state = markerState(cwd);
|
|
371
|
+
await fs.rm(state.dir, { recursive: true, force: true });
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function isBroadSearchTool(toolName) {
|
|
375
|
+
return /grep|glob|search|find/i.test(toolName);
|
|
376
|
+
}
|
|
377
|
+
`;
|
|
378
|
+
}
|
|
379
|
+
function buildGeminiHookScript() {
|
|
380
|
+
return `#!/usr/bin/env node
|
|
381
|
+
import crypto from "node:crypto";
|
|
382
|
+
import fs from "node:fs/promises";
|
|
383
|
+
import os from "node:os";
|
|
384
|
+
import path from "node:path";
|
|
385
|
+
|
|
386
|
+
${markerStateSnippet("gemini").trim()}
|
|
387
|
+
|
|
388
|
+
async function readInput() {
|
|
389
|
+
let body = "";
|
|
390
|
+
for await (const chunk of process.stdin) {
|
|
391
|
+
body += chunk;
|
|
392
|
+
}
|
|
393
|
+
if (!body.trim()) {
|
|
394
|
+
return {};
|
|
395
|
+
}
|
|
396
|
+
try {
|
|
397
|
+
return JSON.parse(body);
|
|
398
|
+
} catch {
|
|
399
|
+
return {};
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function emit(value) {
|
|
404
|
+
process.stdout.write(\`\${JSON.stringify(value)}\\n\`);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const mode = process.argv[2] ?? "";
|
|
408
|
+
const input = await readInput();
|
|
409
|
+
const cwd = resolveInputCwd(input);
|
|
410
|
+
const reportNote = "SwarmVault graph report exists at wiki/graph/report.md. Read it before broad grep/glob searching.";
|
|
411
|
+
|
|
412
|
+
if (!(await hasReport(cwd))) {
|
|
413
|
+
emit({});
|
|
414
|
+
process.exit(0);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (mode === "session-start") {
|
|
418
|
+
await resetSession(cwd);
|
|
419
|
+
emit({
|
|
420
|
+
systemMessage: reportNote,
|
|
421
|
+
hookSpecificOutput: {
|
|
422
|
+
hookEventName: "SessionStart",
|
|
423
|
+
additionalContext: "SwarmVault graph report: wiki/graph/report.md"
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
process.exit(0);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
const toolName = resolveToolName(input);
|
|
430
|
+
if (collectCandidatePaths(input).some((value) => isReportPath(value, cwd))) {
|
|
431
|
+
await markReportRead(cwd);
|
|
432
|
+
emit({});
|
|
433
|
+
process.exit(0);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
if (isBroadSearchTool(toolName) && !(await hasSeenReport(cwd))) {
|
|
437
|
+
emit({ systemMessage: reportNote });
|
|
438
|
+
process.exit(0);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
emit({});
|
|
442
|
+
`;
|
|
443
|
+
}
|
|
444
|
+
function buildCopilotHookScript() {
|
|
445
|
+
return `#!/usr/bin/env node
|
|
446
|
+
import crypto from "node:crypto";
|
|
447
|
+
import fs from "node:fs/promises";
|
|
448
|
+
import os from "node:os";
|
|
449
|
+
import path from "node:path";
|
|
450
|
+
|
|
451
|
+
${markerStateSnippet("copilot").trim()}
|
|
452
|
+
|
|
453
|
+
async function readInput() {
|
|
454
|
+
let body = "";
|
|
455
|
+
for await (const chunk of process.stdin) {
|
|
456
|
+
body += chunk;
|
|
457
|
+
}
|
|
458
|
+
if (!body.trim()) {
|
|
459
|
+
return {};
|
|
460
|
+
}
|
|
461
|
+
try {
|
|
462
|
+
return JSON.parse(body);
|
|
463
|
+
} catch {
|
|
464
|
+
return {};
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
function emit(value) {
|
|
469
|
+
if (value !== undefined) {
|
|
470
|
+
process.stdout.write(\`\${JSON.stringify(value)}\\n\`);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
const mode = process.argv[2] ?? "";
|
|
475
|
+
const input = await readInput();
|
|
476
|
+
const cwd = resolveInputCwd(input);
|
|
477
|
+
const reportNote = "SwarmVault graph report exists at wiki/graph/report.md. Read it before broad grep/glob searching.";
|
|
478
|
+
|
|
479
|
+
if (!(await hasReport(cwd))) {
|
|
480
|
+
emit({});
|
|
481
|
+
process.exit(0);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
if (mode === "session-start") {
|
|
485
|
+
await resetSession(cwd);
|
|
486
|
+
emit({});
|
|
487
|
+
process.exit(0);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
const toolName = resolveToolName(input);
|
|
491
|
+
if (collectCandidatePaths(input).some((value) => isReportPath(value, cwd))) {
|
|
492
|
+
await markReportRead(cwd);
|
|
493
|
+
emit({});
|
|
494
|
+
process.exit(0);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
if (isBroadSearchTool(toolName) && !(await hasSeenReport(cwd))) {
|
|
498
|
+
emit({
|
|
499
|
+
permissionDecision: "deny",
|
|
500
|
+
permissionDecisionReason: reportNote
|
|
501
|
+
});
|
|
502
|
+
process.exit(0);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
emit({});
|
|
506
|
+
`;
|
|
507
|
+
}
|
|
508
|
+
function buildOpenCodePlugin() {
|
|
509
|
+
return `import path from "node:path";
|
|
510
|
+
|
|
511
|
+
const reportRelativePath = path.join("wiki", "graph", "report.md");
|
|
512
|
+
|
|
513
|
+
export const name = "swarmvault-graph-first";
|
|
514
|
+
|
|
515
|
+
export default async function swarmvaultGraphFirst({ client }) {
|
|
516
|
+
let reportSeen = false;
|
|
517
|
+
|
|
518
|
+
async function hasReport(cwd) {
|
|
519
|
+
try {
|
|
520
|
+
await Bun.file(path.join(cwd, reportRelativePath)).arrayBuffer();
|
|
521
|
+
return true;
|
|
522
|
+
} catch {
|
|
523
|
+
return false;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
async function note(message) {
|
|
528
|
+
if (client?.app?.log) {
|
|
529
|
+
await client.app.log({
|
|
530
|
+
level: "info",
|
|
531
|
+
message
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
return {
|
|
537
|
+
async "session.created"(input) {
|
|
538
|
+
reportSeen = false;
|
|
539
|
+
const cwd = input?.session?.cwd ?? process.cwd();
|
|
540
|
+
if (await hasReport(cwd)) {
|
|
541
|
+
await note("SwarmVault graph report exists. Read wiki/graph/report.md before broad workspace searching.");
|
|
542
|
+
}
|
|
543
|
+
},
|
|
544
|
+
async "tool.execute.before"(input) {
|
|
545
|
+
const cwd = input?.session?.cwd ?? process.cwd();
|
|
546
|
+
if (!(await hasReport(cwd))) {
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
const argsText = JSON.stringify(input?.args ?? {});
|
|
551
|
+
if (argsText.includes("wiki/graph/report.md")) {
|
|
552
|
+
reportSeen = true;
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
if (!reportSeen && ["glob", "grep"].includes(String(input?.tool ?? ""))) {
|
|
557
|
+
await note("SwarmVault graph report exists. Read wiki/graph/report.md before broad workspace searching.");
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
`;
|
|
563
|
+
}
|
|
564
|
+
async function installGeminiHook(rootDir) {
|
|
565
|
+
const settingsPath = path.join(rootDir, ".gemini", "settings.json");
|
|
566
|
+
const scriptPath = path.join(rootDir, ".gemini", "hooks", "swarmvault-graph-first.js");
|
|
567
|
+
await writeOwnedFile(scriptPath, buildGeminiHookScript(), true);
|
|
568
|
+
const { data: settings, warnings } = await readJsonWithWarnings(settingsPath, {}, ".gemini/settings.json");
|
|
569
|
+
if (warnings.length > 0 && await fileExists(settingsPath)) {
|
|
570
|
+
return { paths: [settingsPath, scriptPath], warnings };
|
|
571
|
+
}
|
|
572
|
+
const hooks = settings.hooks ?? {};
|
|
573
|
+
const sessionStart = hooks.SessionStart ?? [];
|
|
574
|
+
const beforeTool = hooks.BeforeTool ?? [];
|
|
575
|
+
const sessionCommand = "node .gemini/hooks/swarmvault-graph-first.js session-start";
|
|
576
|
+
const beforeToolCommand = "node .gemini/hooks/swarmvault-graph-first.js before-tool";
|
|
577
|
+
if (!sessionStart.some((entry) => entry.matcher === geminiSessionMatcher && JSON.stringify(entry).includes("swarmvault-graph-first.js"))) {
|
|
578
|
+
sessionStart.push({
|
|
579
|
+
matcher: geminiSessionMatcher,
|
|
580
|
+
hooks: [{ name: "swarmvault-graph-first", type: "command", command: sessionCommand }]
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
if (!beforeTool.some((entry) => entry.matcher === geminiSearchMatcher && JSON.stringify(entry).includes("swarmvault-graph-first.js"))) {
|
|
584
|
+
beforeTool.push({
|
|
585
|
+
matcher: geminiSearchMatcher,
|
|
586
|
+
hooks: [{ name: "swarmvault-graph-first", type: "command", command: beforeToolCommand }]
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
settings.hooks = {
|
|
590
|
+
...hooks,
|
|
591
|
+
SessionStart: sessionStart,
|
|
592
|
+
BeforeTool: beforeTool
|
|
593
|
+
};
|
|
594
|
+
await writeOwnedFile(settingsPath, `${JSON.stringify(settings, null, 2)}
|
|
595
|
+
`);
|
|
596
|
+
return { paths: [settingsPath, scriptPath], warnings: [] };
|
|
597
|
+
}
|
|
598
|
+
async function mergeAiderConfig(rootDir) {
|
|
599
|
+
const configPath = path.join(rootDir, ".aider.conf.yml");
|
|
600
|
+
const readTarget = "CONVENTIONS.md";
|
|
601
|
+
if (!await fileExists(configPath)) {
|
|
602
|
+
const document = new YAML.Document();
|
|
603
|
+
document.set("read", [readTarget]);
|
|
604
|
+
await writeOwnedFile(configPath, `${document.toString()}`);
|
|
605
|
+
return { path: configPath, warnings: [] };
|
|
606
|
+
}
|
|
607
|
+
try {
|
|
608
|
+
const source = await fs.readFile(configPath, "utf8");
|
|
609
|
+
const document = YAML.parseDocument(source);
|
|
610
|
+
if (document.errors.length > 0) {
|
|
611
|
+
return {
|
|
612
|
+
path: configPath,
|
|
613
|
+
warnings: ["Could not parse .aider.conf.yml. Left the existing file unchanged; add `read: CONVENTIONS.md` manually."]
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
const currentRead = document.get("read", true);
|
|
617
|
+
const values = typeof currentRead === "string" ? [currentRead] : Array.isArray(currentRead) ? currentRead.filter((item) => typeof item === "string") : [];
|
|
618
|
+
if (!values.includes(readTarget)) {
|
|
619
|
+
document.set("read", [...values, readTarget]);
|
|
620
|
+
await writeOwnedFile(configPath, `${document.toString()}`);
|
|
621
|
+
}
|
|
622
|
+
return { path: configPath, warnings: [] };
|
|
623
|
+
} catch {
|
|
624
|
+
return {
|
|
625
|
+
path: configPath,
|
|
626
|
+
warnings: ["Could not parse .aider.conf.yml. Left the existing file unchanged; add `read: CONVENTIONS.md` manually."]
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
async function installCopilotHook(rootDir) {
|
|
631
|
+
const hooksDir = path.join(rootDir, ".github", "hooks");
|
|
632
|
+
const scriptPath = path.join(hooksDir, "swarmvault-graph-first.js");
|
|
633
|
+
const configPath = path.join(hooksDir, "swarmvault-graph-first.json");
|
|
634
|
+
await writeOwnedFile(scriptPath, buildCopilotHookScript(), true);
|
|
635
|
+
const config = {
|
|
636
|
+
version: copilotHookVersion,
|
|
637
|
+
hooks: {
|
|
638
|
+
sessionStart: [
|
|
639
|
+
{
|
|
640
|
+
type: "command",
|
|
641
|
+
bash: "node .github/hooks/swarmvault-graph-first.js session-start",
|
|
642
|
+
powershell: "node .github/hooks/swarmvault-graph-first.js session-start",
|
|
643
|
+
cwd: ".",
|
|
644
|
+
timeoutSec: 10
|
|
645
|
+
}
|
|
646
|
+
],
|
|
647
|
+
preToolUse: [
|
|
648
|
+
{
|
|
649
|
+
matcher: "glob|grep",
|
|
650
|
+
type: "command",
|
|
651
|
+
bash: "node .github/hooks/swarmvault-graph-first.js pre-tool-use",
|
|
652
|
+
powershell: "node .github/hooks/swarmvault-graph-first.js pre-tool-use",
|
|
653
|
+
cwd: ".",
|
|
654
|
+
timeoutSec: 10
|
|
655
|
+
}
|
|
656
|
+
]
|
|
657
|
+
}
|
|
658
|
+
};
|
|
659
|
+
await writeOwnedFile(configPath, `${JSON.stringify(config, null, 2)}
|
|
660
|
+
`);
|
|
661
|
+
return { paths: [configPath, scriptPath], warnings: [] };
|
|
662
|
+
}
|
|
663
|
+
async function installOpenCodeHook(rootDir) {
|
|
664
|
+
const pluginPath = path.join(rootDir, ".opencode", "plugins", "swarmvault-graph-first.js");
|
|
665
|
+
await writeOwnedFile(pluginPath, buildOpenCodePlugin());
|
|
666
|
+
return { paths: [pluginPath], warnings: [] };
|
|
667
|
+
}
|
|
668
|
+
function stableKeyForAgent(rootDir, agent) {
|
|
669
|
+
if (agent === "codex" || agent === "goose" || agent === "pi") {
|
|
670
|
+
return `shared:${path.join(rootDir, agentFileKinds.agents)}`;
|
|
671
|
+
}
|
|
672
|
+
return `${agent}:${crypto.createHash("sha1").update(targetsForAgent(rootDir, agent, { hook: supportsAgentHook(agent) }).join("\n")).digest("hex")}`;
|
|
673
|
+
}
|
|
118
674
|
async function installAgent(rootDir, agent, options = {}) {
|
|
119
675
|
await initWorkspace(rootDir);
|
|
120
|
-
const target =
|
|
676
|
+
const target = primaryTargetPathForAgent(rootDir, agent);
|
|
677
|
+
const warnings = [];
|
|
121
678
|
switch (agent) {
|
|
122
679
|
case "codex":
|
|
123
680
|
case "goose":
|
|
124
681
|
case "pi":
|
|
125
682
|
case "opencode":
|
|
126
|
-
await upsertManagedBlock(
|
|
127
|
-
|
|
128
|
-
case "claude":
|
|
683
|
+
await upsertManagedBlock(path.join(rootDir, agentFileKinds.agents), buildManagedBlock("agents"));
|
|
684
|
+
break;
|
|
685
|
+
case "claude":
|
|
129
686
|
await upsertManagedBlock(target, buildManagedBlock("claude"));
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
}
|
|
133
|
-
return target;
|
|
134
|
-
}
|
|
135
|
-
case "gemini": {
|
|
687
|
+
break;
|
|
688
|
+
case "gemini":
|
|
136
689
|
await upsertManagedBlock(target, buildManagedBlock("gemini"));
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
690
|
+
break;
|
|
691
|
+
case "cursor":
|
|
692
|
+
await writeOwnedFile(target, `${buildManagedBlock("cursor")}
|
|
693
|
+
`);
|
|
694
|
+
break;
|
|
695
|
+
case "aider":
|
|
696
|
+
await upsertManagedBlock(target, buildManagedBlock("aider"));
|
|
697
|
+
break;
|
|
698
|
+
case "copilot":
|
|
699
|
+
await upsertManagedBlock(path.join(rootDir, agentFileKinds.agents), buildManagedBlock("agents"));
|
|
700
|
+
await upsertManagedBlock(target, buildManagedBlock("copilot"));
|
|
701
|
+
break;
|
|
146
702
|
default:
|
|
147
703
|
throw new Error(`Unsupported agent ${String(agent)}`);
|
|
148
704
|
}
|
|
705
|
+
if (agent === "aider") {
|
|
706
|
+
const aiderResult = await mergeAiderConfig(rootDir);
|
|
707
|
+
warnings.push(...aiderResult.warnings);
|
|
708
|
+
}
|
|
709
|
+
if (options.hook && supportsAgentHook(agent)) {
|
|
710
|
+
if (agent === "claude") {
|
|
711
|
+
const result = await installClaudeHook(rootDir);
|
|
712
|
+
warnings.push(...result.warnings);
|
|
713
|
+
}
|
|
714
|
+
if (agent === "opencode") {
|
|
715
|
+
const result = await installOpenCodeHook(rootDir);
|
|
716
|
+
warnings.push(...result.warnings);
|
|
717
|
+
}
|
|
718
|
+
if (agent === "gemini") {
|
|
719
|
+
const result = await installGeminiHook(rootDir);
|
|
720
|
+
warnings.push(...result.warnings);
|
|
721
|
+
}
|
|
722
|
+
if (agent === "copilot") {
|
|
723
|
+
const result = await installCopilotHook(rootDir);
|
|
724
|
+
warnings.push(...result.warnings);
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
const targets = targetsForAgent(rootDir, agent, options);
|
|
728
|
+
return warnings.length > 0 ? { agent, target, targets, warnings } : { agent, target, targets };
|
|
149
729
|
}
|
|
150
730
|
async function installConfiguredAgents(rootDir) {
|
|
151
731
|
const { config } = await initWorkspace(rootDir);
|
|
152
|
-
const
|
|
732
|
+
const dedupedAgents = /* @__PURE__ */ new Map();
|
|
153
733
|
for (const agent of config.agents) {
|
|
154
|
-
const
|
|
155
|
-
if (!
|
|
156
|
-
|
|
734
|
+
const key = stableKeyForAgent(rootDir, agent);
|
|
735
|
+
if (!dedupedAgents.has(key)) {
|
|
736
|
+
dedupedAgents.set(key, agent);
|
|
157
737
|
}
|
|
158
738
|
}
|
|
159
739
|
return Promise.all(
|
|
160
|
-
[...
|
|
740
|
+
[...dedupedAgents.values()].map(
|
|
161
741
|
(agent) => installAgent(rootDir, agent, {
|
|
162
|
-
|
|
742
|
+
hook: supportsAgentHook(agent)
|
|
163
743
|
})
|
|
164
744
|
)
|
|
165
745
|
);
|
|
@@ -179,8 +759,38 @@ var NODE_COLORS = {
|
|
|
179
759
|
function xmlEscape(value) {
|
|
180
760
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
181
761
|
}
|
|
182
|
-
function
|
|
183
|
-
|
|
762
|
+
function cypherStringLiteral(value) {
|
|
763
|
+
let escaped = "";
|
|
764
|
+
for (const char of value) {
|
|
765
|
+
switch (char) {
|
|
766
|
+
case "\\":
|
|
767
|
+
escaped += "\\\\";
|
|
768
|
+
break;
|
|
769
|
+
case "'":
|
|
770
|
+
escaped += "\\'";
|
|
771
|
+
break;
|
|
772
|
+
case "\n":
|
|
773
|
+
escaped += "\\n";
|
|
774
|
+
break;
|
|
775
|
+
case "\r":
|
|
776
|
+
escaped += "\\r";
|
|
777
|
+
break;
|
|
778
|
+
case " ":
|
|
779
|
+
escaped += "\\t";
|
|
780
|
+
break;
|
|
781
|
+
case "\b":
|
|
782
|
+
escaped += "\\b";
|
|
783
|
+
break;
|
|
784
|
+
case "\f":
|
|
785
|
+
escaped += "\\f";
|
|
786
|
+
break;
|
|
787
|
+
default: {
|
|
788
|
+
const code = char.codePointAt(0) ?? 0;
|
|
789
|
+
escaped += code < 32 || code === 8232 || code === 8233 ? `\\u${code.toString(16).padStart(4, "0")}` : char;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
return `'${escaped}'`;
|
|
184
794
|
}
|
|
185
795
|
function relationType(relation) {
|
|
186
796
|
const normalized = relation.toUpperCase().replace(/[^A-Z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
@@ -456,33 +1066,33 @@ function renderCypher(graph) {
|
|
|
456
1066
|
for (const node of [...graph.nodes].sort((left, right) => left.id.localeCompare(right.id))) {
|
|
457
1067
|
const page = node.pageId ? pageById2.get(node.pageId) : void 0;
|
|
458
1068
|
const props = [
|
|
459
|
-
`id:
|
|
460
|
-
`label:
|
|
461
|
-
`type:
|
|
462
|
-
`sourceIds:
|
|
463
|
-
`projectIds:
|
|
464
|
-
node.pageId ? `pageId:
|
|
465
|
-
page?.path ? `pagePath:
|
|
466
|
-
node.language ? `language:
|
|
467
|
-
node.symbolKind ? `symbolKind:
|
|
468
|
-
node.communityId ? `communityId:
|
|
1069
|
+
`id: ${cypherStringLiteral(node.id)}`,
|
|
1070
|
+
`label: ${cypherStringLiteral(node.label)}`,
|
|
1071
|
+
`type: ${cypherStringLiteral(node.type)}`,
|
|
1072
|
+
`sourceIds: ${cypherStringLiteral(JSON.stringify(node.sourceIds))}`,
|
|
1073
|
+
`projectIds: ${cypherStringLiteral(JSON.stringify(node.projectIds))}`,
|
|
1074
|
+
node.pageId ? `pageId: ${cypherStringLiteral(node.pageId)}` : "",
|
|
1075
|
+
page?.path ? `pagePath: ${cypherStringLiteral(page.path)}` : "",
|
|
1076
|
+
node.language ? `language: ${cypherStringLiteral(node.language)}` : "",
|
|
1077
|
+
node.symbolKind ? `symbolKind: ${cypherStringLiteral(node.symbolKind)}` : "",
|
|
1078
|
+
node.communityId ? `communityId: ${cypherStringLiteral(node.communityId)}` : "",
|
|
469
1079
|
node.degree !== void 0 ? `degree: ${node.degree}` : "",
|
|
470
1080
|
node.bridgeScore !== void 0 ? `bridgeScore: ${node.bridgeScore}` : "",
|
|
471
1081
|
node.isGodNode !== void 0 ? `isGodNode: ${node.isGodNode}` : ""
|
|
472
1082
|
].filter(Boolean).join(", ");
|
|
473
|
-
lines.push(`MERGE (n:SwarmNode {id:
|
|
1083
|
+
lines.push(`MERGE (n:SwarmNode {id: ${cypherStringLiteral(node.id)}}) SET n += { ${props} };`);
|
|
474
1084
|
}
|
|
475
1085
|
lines.push("");
|
|
476
1086
|
for (const hyperedge of [...graph.hyperedges ?? []].sort((left, right) => left.id.localeCompare(right.id))) {
|
|
477
1087
|
const hyperedgeNodeId = exportHyperedgeNodeId(hyperedge);
|
|
478
1088
|
lines.push(
|
|
479
|
-
`MERGE (h:SwarmNode {id:
|
|
1089
|
+
`MERGE (h:SwarmNode {id: ${cypherStringLiteral(hyperedgeNodeId)}}) SET h += { id: ${cypherStringLiteral(hyperedgeNodeId)}, label: ${cypherStringLiteral(
|
|
480
1090
|
hyperedge.label
|
|
481
|
-
)}
|
|
1091
|
+
)}, type: ${cypherStringLiteral("hyperedge")}, relation: ${cypherStringLiteral(hyperedge.relation)}, evidenceClass: ${cypherStringLiteral(
|
|
482
1092
|
hyperedge.evidenceClass
|
|
483
|
-
)}
|
|
1093
|
+
)}, confidence: ${hyperedge.confidence}, sourcePageIds: ${cypherStringLiteral(JSON.stringify(hyperedge.sourcePageIds))}, why: ${cypherStringLiteral(
|
|
484
1094
|
hyperedge.why
|
|
485
|
-
)}
|
|
1095
|
+
)} };`
|
|
486
1096
|
);
|
|
487
1097
|
}
|
|
488
1098
|
if ((graph.hyperedges ?? []).length) {
|
|
@@ -492,22 +1102,22 @@ function renderCypher(graph) {
|
|
|
492
1102
|
const hyperedgeNodeId = exportHyperedgeNodeId(hyperedge);
|
|
493
1103
|
for (const nodeId of hyperedge.nodeIds) {
|
|
494
1104
|
lines.push(
|
|
495
|
-
`MATCH (h:SwarmNode {id:
|
|
496
|
-
`MERGE (h)-[r:GROUP_MEMBER {id:
|
|
497
|
-
`SET r += { relation:
|
|
1105
|
+
`MATCH (h:SwarmNode {id: ${cypherStringLiteral(hyperedgeNodeId)}}), (n:SwarmNode {id: ${cypherStringLiteral(nodeId)}})`,
|
|
1106
|
+
`MERGE (h)-[r:GROUP_MEMBER {id: ${cypherStringLiteral(`member:${hyperedge.id}:${nodeId}`)}}]->(n)`,
|
|
1107
|
+
`SET r += { relation: ${cypherStringLiteral("group_member")}, status: ${cypherStringLiteral("inferred")}, evidenceClass: ${cypherStringLiteral(
|
|
498
1108
|
hyperedge.evidenceClass
|
|
499
|
-
)}
|
|
1109
|
+
)}, confidence: ${hyperedge.confidence}, provenance: ${cypherStringLiteral(JSON.stringify(hyperedge.sourcePageIds))} };`
|
|
500
1110
|
);
|
|
501
1111
|
}
|
|
502
1112
|
}
|
|
503
1113
|
lines.push("");
|
|
504
1114
|
for (const edge of [...graph.edges].sort((left, right) => left.id.localeCompare(right.id))) {
|
|
505
1115
|
lines.push(
|
|
506
|
-
`MATCH (a:SwarmNode {id:
|
|
507
|
-
`MERGE (a)-[r:${relationType(edge.relation)} {id:
|
|
508
|
-
`SET r += { relation:
|
|
1116
|
+
`MATCH (a:SwarmNode {id: ${cypherStringLiteral(edge.source)}}), (b:SwarmNode {id: ${cypherStringLiteral(edge.target)}})`,
|
|
1117
|
+
`MERGE (a)-[r:${relationType(edge.relation)} {id: ${cypherStringLiteral(edge.id)}}]->(b)`,
|
|
1118
|
+
`SET r += { relation: ${cypherStringLiteral(edge.relation)}, status: ${cypherStringLiteral(edge.status)}, evidenceClass: ${cypherStringLiteral(
|
|
509
1119
|
edge.evidenceClass
|
|
510
|
-
)}
|
|
1120
|
+
)}, confidence: ${edge.confidence}, provenance: ${cypherStringLiteral(JSON.stringify(edge.provenance))}${edge.similarityReasons?.length ? `, similarityReasons: ${cypherStringLiteral(JSON.stringify(edge.similarityReasons))}` : ""} };`
|
|
511
1121
|
);
|
|
512
1122
|
}
|
|
513
1123
|
lines.push("");
|
|
@@ -536,6 +1146,7 @@ async function exportGraphFormat(rootDir, format, outputPath) {
|
|
|
536
1146
|
// src/hooks.ts
|
|
537
1147
|
import fs3 from "fs/promises";
|
|
538
1148
|
import path3 from "path";
|
|
1149
|
+
import process2 from "process";
|
|
539
1150
|
var hookStart = "# >>> swarmvault hook >>>";
|
|
540
1151
|
var hookEnd = "# <<< swarmvault hook <<<";
|
|
541
1152
|
async function findNearestGitRoot(startPath) {
|
|
@@ -562,12 +1173,22 @@ async function findNearestGitRoot(startPath) {
|
|
|
562
1173
|
function shellQuote(value) {
|
|
563
1174
|
return `'${value.replace(/'/g, `'"'"'`)}'`;
|
|
564
1175
|
}
|
|
1176
|
+
function resolveSwarmvaultExecutableCandidate() {
|
|
1177
|
+
const argvPath = process2.argv[1];
|
|
1178
|
+
if (typeof argvPath === "string" && argvPath.trim() && (argvPath.includes(`${path3.sep}@swarmvaultai${path3.sep}cli${path3.sep}`) || argvPath.includes(`${path3.sep}packages${path3.sep}cli${path3.sep}`))) {
|
|
1179
|
+
return path3.resolve(argvPath);
|
|
1180
|
+
}
|
|
1181
|
+
return "swarmvault";
|
|
1182
|
+
}
|
|
565
1183
|
function managedHookBlock(vaultRoot) {
|
|
1184
|
+
const resolvedExecutable = resolveSwarmvaultExecutableCandidate();
|
|
566
1185
|
return [
|
|
567
1186
|
hookStart,
|
|
568
1187
|
`cd ${shellQuote(vaultRoot)} || exit 0`,
|
|
569
|
-
|
|
570
|
-
"
|
|
1188
|
+
`swarmvault_bin=${shellQuote(resolvedExecutable)}`,
|
|
1189
|
+
'[ ! -x "$swarmvault_bin" ] && swarmvault_bin=$(command -v swarmvault 2>/dev/null || true)',
|
|
1190
|
+
'if [ -n "$swarmvault_bin" ] && [ -x "$swarmvault_bin" ]; then',
|
|
1191
|
+
` "$swarmvault_bin" watch --repo --once >/dev/null 2>&1 || printf '[swarmvault hook] refresh failed\\n' >&2`,
|
|
571
1192
|
"fi",
|
|
572
1193
|
hookEnd,
|
|
573
1194
|
""
|
|
@@ -667,7 +1288,7 @@ import { pathToFileURL } from "url";
|
|
|
667
1288
|
import { Readability } from "@mozilla/readability";
|
|
668
1289
|
import matter3 from "gray-matter";
|
|
669
1290
|
import ignore from "ignore";
|
|
670
|
-
import { JSDOM } from "jsdom";
|
|
1291
|
+
import { JSDOM as JSDOM2 } from "jsdom";
|
|
671
1292
|
import mime from "mime-types";
|
|
672
1293
|
import TurndownService from "turndown";
|
|
673
1294
|
|
|
@@ -792,6 +1413,55 @@ function collectCallNamesFromText(text, availableNames, selfName) {
|
|
|
792
1413
|
}
|
|
793
1414
|
return uniqueBy(names, (name) => name);
|
|
794
1415
|
}
|
|
1416
|
+
function goReceiverBinding(node) {
|
|
1417
|
+
if (!node) {
|
|
1418
|
+
return {};
|
|
1419
|
+
}
|
|
1420
|
+
const names = uniqueBy(
|
|
1421
|
+
node.descendantsOfType(["identifier", "type_identifier"]).filter((item) => item !== null).map((item) => normalizeSymbolReference(item.text)).filter(Boolean),
|
|
1422
|
+
(item) => item
|
|
1423
|
+
);
|
|
1424
|
+
if (names.length === 0) {
|
|
1425
|
+
return {};
|
|
1426
|
+
}
|
|
1427
|
+
if (names.length === 1) {
|
|
1428
|
+
return { typeName: names[0] };
|
|
1429
|
+
}
|
|
1430
|
+
return {
|
|
1431
|
+
variableName: names[0],
|
|
1432
|
+
typeName: names.at(-1)
|
|
1433
|
+
};
|
|
1434
|
+
}
|
|
1435
|
+
function goCalledSymbolName(node, receiver) {
|
|
1436
|
+
if (!node) {
|
|
1437
|
+
return void 0;
|
|
1438
|
+
}
|
|
1439
|
+
if (node.type === "selector_expression") {
|
|
1440
|
+
const targetName = normalizeSymbolReference(
|
|
1441
|
+
extractIdentifier(node.childForFieldName("operand") ?? node.childForFieldName("object") ?? node.namedChildren.at(0) ?? null) ?? ""
|
|
1442
|
+
);
|
|
1443
|
+
const fieldName = normalizeSymbolReference(
|
|
1444
|
+
extractIdentifier(node.childForFieldName("field") ?? findNamedChild(node, "field_identifier") ?? node.namedChildren.at(-1) ?? null) ?? ""
|
|
1445
|
+
);
|
|
1446
|
+
if (!fieldName) {
|
|
1447
|
+
return void 0;
|
|
1448
|
+
}
|
|
1449
|
+
if (receiver.variableName && receiver.typeName && targetName === receiver.variableName) {
|
|
1450
|
+
return `${receiver.typeName}.${fieldName}`;
|
|
1451
|
+
}
|
|
1452
|
+
return fieldName;
|
|
1453
|
+
}
|
|
1454
|
+
return normalizeSymbolReference(extractIdentifier(node) ?? "");
|
|
1455
|
+
}
|
|
1456
|
+
function goCallNamesFromBody(bodyNode, receiver) {
|
|
1457
|
+
if (!bodyNode) {
|
|
1458
|
+
return [];
|
|
1459
|
+
}
|
|
1460
|
+
return uniqueBy(
|
|
1461
|
+
bodyNode.descendantsOfType("call_expression").filter((item) => item !== null).map((callNode) => goCalledSymbolName(callNode.childForFieldName("function") ?? callNode.namedChildren.at(0) ?? null, receiver)).filter((name) => Boolean(name)),
|
|
1462
|
+
(name) => name
|
|
1463
|
+
);
|
|
1464
|
+
}
|
|
795
1465
|
function finalizeCodeAnalysis(manifest, language, imports, draftSymbols, exportLabels, diagnostics, metadata) {
|
|
796
1466
|
const topLevelNames = new Set(draftSymbols.map((symbol) => symbol.name));
|
|
797
1467
|
for (const symbol of draftSymbols) {
|
|
@@ -1278,7 +1948,9 @@ function goCodeAnalysis(manifest, rootNode, diagnostics) {
|
|
|
1278
1948
|
if (!name) {
|
|
1279
1949
|
continue;
|
|
1280
1950
|
}
|
|
1281
|
-
const
|
|
1951
|
+
const receiver = child.type === "method_declaration" ? goReceiverBinding(child.childForFieldName("receiver")) : {};
|
|
1952
|
+
const receiverType = receiver.typeName ?? "";
|
|
1953
|
+
const bodyNode = child.childForFieldName("body");
|
|
1282
1954
|
const symbolName = receiverType ? `${receiverType}.${name}` : name;
|
|
1283
1955
|
const exported = exportedByCapitalization(name);
|
|
1284
1956
|
draftSymbols.push({
|
|
@@ -1286,10 +1958,10 @@ function goCodeAnalysis(manifest, rootNode, diagnostics) {
|
|
|
1286
1958
|
kind: "function",
|
|
1287
1959
|
signature: singleLineSignature(child.text),
|
|
1288
1960
|
exported,
|
|
1289
|
-
callNames:
|
|
1961
|
+
callNames: goCallNamesFromBody(bodyNode, receiver),
|
|
1290
1962
|
extendsNames: [],
|
|
1291
1963
|
implementsNames: [],
|
|
1292
|
-
bodyText: nodeText(
|
|
1964
|
+
bodyText: nodeText(bodyNode)
|
|
1293
1965
|
});
|
|
1294
1966
|
if (exported) {
|
|
1295
1967
|
exportLabels.push(symbolName);
|
|
@@ -2819,6 +3491,8 @@ async function analyzeCodeSource(manifest, extractedText, schemaHash) {
|
|
|
2819
3491
|
import fs6 from "fs/promises";
|
|
2820
3492
|
import os from "os";
|
|
2821
3493
|
import path6 from "path";
|
|
3494
|
+
import { strFromU8, unzipSync } from "fflate";
|
|
3495
|
+
import { JSDOM } from "jsdom";
|
|
2822
3496
|
import { z } from "zod";
|
|
2823
3497
|
var imageVisionExtractionSchema = z.object({
|
|
2824
3498
|
title: z.string().min(1).nullable().optional(),
|
|
@@ -2995,6 +3669,49 @@ function normalizePdfMetadata(raw) {
|
|
|
2995
3669
|
}
|
|
2996
3670
|
return Object.keys(metadata).length ? metadata : void 0;
|
|
2997
3671
|
}
|
|
3672
|
+
function normalizeDocumentText(raw) {
|
|
3673
|
+
return raw.replace(/\r\n/g, "\n").split(/\n{2,}/).map((section) => normalizeWhitespace(section)).filter(Boolean).join("\n\n").trim();
|
|
3674
|
+
}
|
|
3675
|
+
function parseDocxCoreMetadata(bytes) {
|
|
3676
|
+
try {
|
|
3677
|
+
const archive = unzipSync(new Uint8Array(bytes));
|
|
3678
|
+
const coreXml = archive["docProps/core.xml"];
|
|
3679
|
+
if (!coreXml) {
|
|
3680
|
+
return void 0;
|
|
3681
|
+
}
|
|
3682
|
+
const dom = new JSDOM(strFromU8(coreXml), { contentType: "text/xml" });
|
|
3683
|
+
const document = dom.window.document;
|
|
3684
|
+
const valuesByLocalName = /* @__PURE__ */ new Map();
|
|
3685
|
+
for (const node of Array.from(document.getElementsByTagName("*"))) {
|
|
3686
|
+
const localName = node.localName?.trim().toLowerCase();
|
|
3687
|
+
const text = normalizeWhitespace(node.textContent ?? "");
|
|
3688
|
+
if (!localName || !text || valuesByLocalName.has(localName)) {
|
|
3689
|
+
continue;
|
|
3690
|
+
}
|
|
3691
|
+
valuesByLocalName.set(localName, text);
|
|
3692
|
+
}
|
|
3693
|
+
const metadata = {};
|
|
3694
|
+
const mappings = [
|
|
3695
|
+
["title", "title"],
|
|
3696
|
+
["author", "creator"],
|
|
3697
|
+
["subject", "subject"],
|
|
3698
|
+
["description", "description"],
|
|
3699
|
+
["keywords", "keywords"],
|
|
3700
|
+
["last_modified_by", "lastmodifiedby"],
|
|
3701
|
+
["created", "created"],
|
|
3702
|
+
["modified", "modified"]
|
|
3703
|
+
];
|
|
3704
|
+
for (const [targetKey, sourceKey] of mappings) {
|
|
3705
|
+
const value = valuesByLocalName.get(sourceKey);
|
|
3706
|
+
if (value) {
|
|
3707
|
+
metadata[targetKey] = value;
|
|
3708
|
+
}
|
|
3709
|
+
}
|
|
3710
|
+
return Object.keys(metadata).length ? metadata : void 0;
|
|
3711
|
+
} catch {
|
|
3712
|
+
return void 0;
|
|
3713
|
+
}
|
|
3714
|
+
}
|
|
2998
3715
|
async function extractPdfText(input) {
|
|
2999
3716
|
try {
|
|
3000
3717
|
const pdfjs = await import("pdfjs-dist/legacy/build/pdf.mjs");
|
|
@@ -3042,6 +3759,35 @@ async function extractPdfText(input) {
|
|
|
3042
3759
|
};
|
|
3043
3760
|
}
|
|
3044
3761
|
}
|
|
3762
|
+
async function extractDocxText(input) {
|
|
3763
|
+
try {
|
|
3764
|
+
const mammoth = await import("mammoth");
|
|
3765
|
+
const result = await mammoth.extractRawText({
|
|
3766
|
+
buffer: input.bytes
|
|
3767
|
+
});
|
|
3768
|
+
const extractedText = normalizeDocumentText(result.value);
|
|
3769
|
+
const warnings = result.messages.map((message) => normalizeWhitespace(message.message)).filter(Boolean).map((message) => truncate(message, 240));
|
|
3770
|
+
const artifact = {
|
|
3771
|
+
...extractionMetadata("docx", input.mimeType, "docx_text"),
|
|
3772
|
+
metadata: parseDocxCoreMetadata(input.bytes),
|
|
3773
|
+
warnings: warnings.length ? warnings : void 0
|
|
3774
|
+
};
|
|
3775
|
+
if (!extractedText) {
|
|
3776
|
+
artifact.warnings = [...artifact.warnings ?? [], "DOCX text extraction completed but produced no extractable text."];
|
|
3777
|
+
}
|
|
3778
|
+
return {
|
|
3779
|
+
extractedText: extractedText || void 0,
|
|
3780
|
+
artifact
|
|
3781
|
+
};
|
|
3782
|
+
} catch (error) {
|
|
3783
|
+
return {
|
|
3784
|
+
artifact: {
|
|
3785
|
+
...extractionMetadata("docx", input.mimeType, "docx_text"),
|
|
3786
|
+
warnings: [`DOCX text extraction failed: ${error instanceof Error ? truncate(error.message, 240) : "unknown error"}`]
|
|
3787
|
+
}
|
|
3788
|
+
};
|
|
3789
|
+
}
|
|
3790
|
+
}
|
|
3045
3791
|
|
|
3046
3792
|
// src/logs.ts
|
|
3047
3793
|
import fs7 from "fs/promises";
|
|
@@ -3352,6 +4098,9 @@ function inferKind(mimeType, filePath) {
|
|
|
3352
4098
|
if (mimeType === "application/pdf" || filePath.toLowerCase().endsWith(".pdf")) {
|
|
3353
4099
|
return "pdf";
|
|
3354
4100
|
}
|
|
4101
|
+
if (mimeType === "application/vnd.openxmlformats-officedocument.wordprocessingml.document" || filePath.toLowerCase().endsWith(".docx")) {
|
|
4102
|
+
return "docx";
|
|
4103
|
+
}
|
|
3355
4104
|
if (mimeType.startsWith("image/")) {
|
|
3356
4105
|
return "image";
|
|
3357
4106
|
}
|
|
@@ -3530,7 +4279,7 @@ async function fetchResolvedText(url) {
|
|
|
3530
4279
|
};
|
|
3531
4280
|
}
|
|
3532
4281
|
function domTextFromHtml(html, baseUrl) {
|
|
3533
|
-
const dom = new
|
|
4282
|
+
const dom = new JSDOM2(`<body>${html}</body>`, { url: baseUrl });
|
|
3534
4283
|
return normalizeWhitespace(dom.window.document.body.textContent ?? "");
|
|
3535
4284
|
}
|
|
3536
4285
|
async function captureArxivMarkdown(input, options) {
|
|
@@ -3540,7 +4289,7 @@ async function captureArxivMarkdown(input, options) {
|
|
|
3540
4289
|
}
|
|
3541
4290
|
const normalizedUrl = `https://arxiv.org/abs/${arxivId}`;
|
|
3542
4291
|
const html = await fetchText(normalizedUrl);
|
|
3543
|
-
const dom = new
|
|
4292
|
+
const dom = new JSDOM2(html, { url: normalizedUrl });
|
|
3544
4293
|
const document = dom.window.document;
|
|
3545
4294
|
const metaTitle = document.querySelector('meta[name="citation_title"]')?.getAttribute("content")?.trim();
|
|
3546
4295
|
const headingTitle = document.querySelector("h1.title")?.textContent?.trim();
|
|
@@ -3649,7 +4398,7 @@ async function captureArticleMarkdown(rootDir, input, options, extra = { sourceT
|
|
|
3649
4398
|
if (!resolved.contentType.includes("html")) {
|
|
3650
4399
|
throw new Error(`Unsupported article content type: ${resolved.contentType}`);
|
|
3651
4400
|
}
|
|
3652
|
-
const dom = new
|
|
4401
|
+
const dom = new JSDOM2(resolved.text, { url: resolved.finalUrl });
|
|
3653
4402
|
const document = dom.window.document;
|
|
3654
4403
|
const canonicalHref = document.querySelector('link[rel="canonical"]')?.getAttribute("href")?.trim();
|
|
3655
4404
|
const canonicalUrl = canonicalHref ? normalizeOriginUrl(new URL(canonicalHref, resolved.finalUrl).toString()) : resolved.finalUrl;
|
|
@@ -3768,6 +4517,22 @@ function extractMarkdownReferences(content) {
|
|
|
3768
4517
|
}
|
|
3769
4518
|
return references;
|
|
3770
4519
|
}
|
|
4520
|
+
function extractHtmlLocalReferences(html, baseUrl) {
|
|
4521
|
+
const dom = new JSDOM2(html, { url: baseUrl });
|
|
4522
|
+
const document = dom.window.document;
|
|
4523
|
+
const references = [];
|
|
4524
|
+
for (const image of [...document.querySelectorAll("img[src]")]) {
|
|
4525
|
+
const src = image.getAttribute("src");
|
|
4526
|
+
if (!src) {
|
|
4527
|
+
continue;
|
|
4528
|
+
}
|
|
4529
|
+
const normalized = normalizeLocalReference(src);
|
|
4530
|
+
if (normalized) {
|
|
4531
|
+
references.push(normalized);
|
|
4532
|
+
}
|
|
4533
|
+
}
|
|
4534
|
+
return references;
|
|
4535
|
+
}
|
|
3771
4536
|
function normalizeRemoteReference(value, baseUrl) {
|
|
3772
4537
|
const trimmed = value.trim().replace(/^<|>$/g, "");
|
|
3773
4538
|
const [withoutTitle] = trimmed.split(/\s+(?=(?:[^"]*"[^"]*")*[^"]*$)/, 1);
|
|
@@ -3803,7 +4568,7 @@ function extractMarkdownImageReferences(content, baseUrl) {
|
|
|
3803
4568
|
return references;
|
|
3804
4569
|
}
|
|
3805
4570
|
async function convertHtmlToMarkdown(html, url) {
|
|
3806
|
-
const dom = new
|
|
4571
|
+
const dom = new JSDOM2(html, { url });
|
|
3807
4572
|
const article = new Readability(dom.window.document).parse();
|
|
3808
4573
|
const turndown = new TurndownService({ headingStyle: "atx", codeBlockStyle: "fenced" });
|
|
3809
4574
|
const body = article?.content ?? dom.window.document.body.innerHTML;
|
|
@@ -4006,7 +4771,7 @@ async function collectRemoteImageAttachments(assetUrls, options) {
|
|
|
4006
4771
|
return { attachments, skippedCount };
|
|
4007
4772
|
}
|
|
4008
4773
|
function extractHtmlImageReferences(html, baseUrl) {
|
|
4009
|
-
const dom = new
|
|
4774
|
+
const dom = new JSDOM2(html, { url: baseUrl });
|
|
4010
4775
|
const document = dom.window.document;
|
|
4011
4776
|
const references = [];
|
|
4012
4777
|
for (const image of [...document.querySelectorAll("img[src]")]) {
|
|
@@ -4022,7 +4787,7 @@ function extractHtmlImageReferences(html, baseUrl) {
|
|
|
4022
4787
|
return references;
|
|
4023
4788
|
}
|
|
4024
4789
|
function rewriteHtmlImageReferences(html, baseUrl, replacements) {
|
|
4025
|
-
const dom = new
|
|
4790
|
+
const dom = new JSDOM2(html, { url: baseUrl });
|
|
4026
4791
|
const document = dom.window.document;
|
|
4027
4792
|
for (const image of [...document.querySelectorAll("img[src]")]) {
|
|
4028
4793
|
const src = image.getAttribute("src");
|
|
@@ -4037,6 +4802,22 @@ function rewriteHtmlImageReferences(html, baseUrl, replacements) {
|
|
|
4037
4802
|
}
|
|
4038
4803
|
return dom.serialize();
|
|
4039
4804
|
}
|
|
4805
|
+
function rewriteHtmlLocalReferences(html, baseUrl, replacements) {
|
|
4806
|
+
const dom = new JSDOM2(html, { url: baseUrl });
|
|
4807
|
+
const document = dom.window.document;
|
|
4808
|
+
for (const image of [...document.querySelectorAll("img[src]")]) {
|
|
4809
|
+
const src = image.getAttribute("src");
|
|
4810
|
+
if (!src) {
|
|
4811
|
+
continue;
|
|
4812
|
+
}
|
|
4813
|
+
const normalized = normalizeLocalReference(src);
|
|
4814
|
+
const replacement = normalized ? replacements.get(normalized) : void 0;
|
|
4815
|
+
if (replacement) {
|
|
4816
|
+
image.setAttribute("src", replacement);
|
|
4817
|
+
}
|
|
4818
|
+
}
|
|
4819
|
+
return dom.serialize();
|
|
4820
|
+
}
|
|
4040
4821
|
function rewriteMarkdownImageReferences(content, baseUrl, replacements) {
|
|
4041
4822
|
return content.replace(/(!\[[^\]]*]\()([^)]+)(\))/g, (fullMatch, prefix, target, suffix) => {
|
|
4042
4823
|
const normalized = normalizeRemoteReference(target, baseUrl);
|
|
@@ -4177,7 +4958,7 @@ function preparedMatchesManifest(manifest, prepared, contentHash) {
|
|
|
4177
4958
|
return manifest.contentHash === contentHash && manifest.extractionHash === (prepared.extractionHash ?? buildExtractionHash(prepared.extractedText, prepared.extractionArtifact)) && manifest.title === prepared.title && manifest.sourceKind === prepared.sourceKind && manifest.sourceType === prepared.sourceType && manifest.sourceClass === prepared.sourceClass && manifest.language === prepared.language && manifest.mimeType === prepared.mimeType && manifest.repoRelativePath === prepared.repoRelativePath;
|
|
4178
4959
|
}
|
|
4179
4960
|
function shouldDeferWatchSemanticRefresh(sourceKind) {
|
|
4180
|
-
return sourceKind === "markdown" || sourceKind === "text" || sourceKind === "html" || sourceKind === "pdf" || sourceKind === "image";
|
|
4961
|
+
return sourceKind === "markdown" || sourceKind === "text" || sourceKind === "html" || sourceKind === "pdf" || sourceKind === "docx" || sourceKind === "image";
|
|
4181
4962
|
}
|
|
4182
4963
|
function pendingSemanticRefreshId(changeType, repoRoot, relativePath) {
|
|
4183
4964
|
return `pending:${changeType}:${sha256(`${toPosix(repoRoot)}:${relativePath}`).slice(0, 12)}`;
|
|
@@ -4441,6 +5222,12 @@ async function prepareFileInput(rootDir, absoluteInput, repoRoot, sourceClass) {
|
|
|
4441
5222
|
const extracted = await extractPdfText({ mimeType, bytes: payloadBytes });
|
|
4442
5223
|
extractedText = extracted.extractedText;
|
|
4443
5224
|
extractionArtifact = extracted.artifact;
|
|
5225
|
+
} else if (sourceKind === "docx") {
|
|
5226
|
+
title = path10.basename(absoluteInput, path10.extname(absoluteInput));
|
|
5227
|
+
const extracted = await extractDocxText({ mimeType, bytes: payloadBytes });
|
|
5228
|
+
title = extracted.artifact.metadata?.title?.trim() || title;
|
|
5229
|
+
extractedText = extracted.extractedText;
|
|
5230
|
+
extractionArtifact = extracted.artifact;
|
|
4444
5231
|
} else if (sourceKind === "image") {
|
|
4445
5232
|
title = path10.basename(absoluteInput, path10.extname(absoluteInput));
|
|
4446
5233
|
const extracted = await extractImageWithVision(rootDir, {
|
|
@@ -4558,6 +5345,11 @@ async function prepareUrlInput(rootDir, input, options) {
|
|
|
4558
5345
|
const extracted = await extractPdfText({ mimeType, bytes: payloadBytes });
|
|
4559
5346
|
extractedText = extracted.extractedText;
|
|
4560
5347
|
extractionArtifact = extracted.artifact;
|
|
5348
|
+
} else if (sourceKind === "docx") {
|
|
5349
|
+
const extracted = await extractDocxText({ mimeType, bytes: payloadBytes });
|
|
5350
|
+
title = extracted.artifact.metadata?.title?.trim() || title;
|
|
5351
|
+
extractedText = extracted.extractedText;
|
|
5352
|
+
extractionArtifact = extracted.artifact;
|
|
4561
5353
|
} else if (sourceKind === "image") {
|
|
4562
5354
|
const extracted = await extractImageWithVision(rootDir, {
|
|
4563
5355
|
title,
|
|
@@ -4591,11 +5383,11 @@ async function collectInboxAttachmentRefs(inputDir, files) {
|
|
|
4591
5383
|
for (const absolutePath of files) {
|
|
4592
5384
|
const mimeType = guessMimeType(absolutePath);
|
|
4593
5385
|
const sourceKind = inferKind(mimeType, absolutePath);
|
|
4594
|
-
if (sourceKind !== "markdown") {
|
|
5386
|
+
if (sourceKind !== "markdown" && sourceKind !== "html") {
|
|
4595
5387
|
continue;
|
|
4596
5388
|
}
|
|
4597
5389
|
const content = await fs9.readFile(absolutePath, "utf8");
|
|
4598
|
-
const refs = extractMarkdownReferences(content);
|
|
5390
|
+
const refs = sourceKind === "html" ? extractHtmlLocalReferences(content, pathToFileURL(absolutePath).toString()) : extractMarkdownReferences(content);
|
|
4599
5391
|
if (!refs.length) {
|
|
4600
5392
|
continue;
|
|
4601
5393
|
}
|
|
@@ -4673,8 +5465,50 @@ async function prepareInboxMarkdownInput(absolutePath, attachmentRefs) {
|
|
|
4673
5465
|
contentHash
|
|
4674
5466
|
};
|
|
4675
5467
|
}
|
|
5468
|
+
async function prepareInboxHtmlInput(absolutePath, attachmentRefs) {
|
|
5469
|
+
const originalBytes = await fs9.readFile(absolutePath);
|
|
5470
|
+
const originalHtml = originalBytes.toString("utf8");
|
|
5471
|
+
const initialConversion = await convertHtmlToMarkdown(originalHtml, pathToFileURL(absolutePath).toString());
|
|
5472
|
+
const attachments = [];
|
|
5473
|
+
for (const attachmentRef of attachmentRefs) {
|
|
5474
|
+
const bytes = await fs9.readFile(attachmentRef.absolutePath);
|
|
5475
|
+
attachments.push({
|
|
5476
|
+
relativePath: sanitizeAssetRelativePath(attachmentRef.relativeRef),
|
|
5477
|
+
mimeType: guessMimeType(attachmentRef.absolutePath),
|
|
5478
|
+
originalPath: toPosix(attachmentRef.absolutePath),
|
|
5479
|
+
bytes
|
|
5480
|
+
});
|
|
5481
|
+
}
|
|
5482
|
+
const contentHash = buildCompositeHash(originalBytes, attachments);
|
|
5483
|
+
const fallbackTitle = path10.basename(absolutePath, path10.extname(absolutePath));
|
|
5484
|
+
const title = initialConversion.title || fallbackTitle;
|
|
5485
|
+
const sourceId = `${slugify(title)}-${contentHash.slice(0, 8)}`;
|
|
5486
|
+
const replacements = new Map(
|
|
5487
|
+
attachmentRefs.map((attachmentRef) => [
|
|
5488
|
+
attachmentRef.relativeRef.replace(/\\/g, "/"),
|
|
5489
|
+
`../assets/${sourceId}/${sanitizeAssetRelativePath(attachmentRef.relativeRef)}`
|
|
5490
|
+
])
|
|
5491
|
+
);
|
|
5492
|
+
const rewrittenHtml = rewriteHtmlLocalReferences(originalHtml, pathToFileURL(absolutePath).toString(), replacements);
|
|
5493
|
+
const converted = rewrittenHtml === originalHtml ? initialConversion : await convertHtmlToMarkdown(rewrittenHtml, pathToFileURL(absolutePath).toString());
|
|
5494
|
+
const extractionArtifact = createHtmlReadabilityExtractionArtifact("html", "text/html");
|
|
5495
|
+
return {
|
|
5496
|
+
title: converted.title || title,
|
|
5497
|
+
originType: "file",
|
|
5498
|
+
sourceKind: "html",
|
|
5499
|
+
originalPath: toPosix(absolutePath),
|
|
5500
|
+
mimeType: "text/html",
|
|
5501
|
+
storedExtension: path10.extname(absolutePath) || ".html",
|
|
5502
|
+
payloadBytes: Buffer.from(rewrittenHtml, "utf8"),
|
|
5503
|
+
extractedText: converted.markdown,
|
|
5504
|
+
extractionArtifact,
|
|
5505
|
+
extractionHash: buildExtractionHash(converted.markdown, extractionArtifact),
|
|
5506
|
+
attachments,
|
|
5507
|
+
contentHash
|
|
5508
|
+
};
|
|
5509
|
+
}
|
|
4676
5510
|
function isSupportedInboxKind(sourceKind) {
|
|
4677
|
-
return ["markdown", "text", "html", "pdf", "image"].includes(sourceKind);
|
|
5511
|
+
return ["markdown", "text", "html", "pdf", "docx", "image"].includes(sourceKind);
|
|
4678
5512
|
}
|
|
4679
5513
|
async function ingestInput(rootDir, input, options) {
|
|
4680
5514
|
const { paths } = await initWorkspace(rootDir);
|
|
@@ -4834,7 +5668,7 @@ async function importInbox(rootDir, inputDir) {
|
|
|
4834
5668
|
skipped.push({ path: toPosix(path10.relative(rootDir, absolutePath)), reason: `unsupported_kind:${sourceKind}` });
|
|
4835
5669
|
continue;
|
|
4836
5670
|
}
|
|
4837
|
-
const prepared = sourceKind === "markdown" && refsBySource.has(absolutePath) ? await prepareInboxMarkdownInput(absolutePath, refsBySource.get(absolutePath) ?? []) : await prepareFileInput(rootDir, absolutePath);
|
|
5671
|
+
const prepared = sourceKind === "markdown" && refsBySource.has(absolutePath) ? await prepareInboxMarkdownInput(absolutePath, refsBySource.get(absolutePath) ?? []) : sourceKind === "html" && refsBySource.has(absolutePath) ? await prepareInboxHtmlInput(absolutePath, refsBySource.get(absolutePath) ?? []) : await prepareFileInput(rootDir, absolutePath);
|
|
4838
5672
|
const result = await persistPreparedInput(rootDir, prepared, paths);
|
|
4839
5673
|
if (!result.isNew) {
|
|
4840
5674
|
skipped.push({ path: toPosix(path10.relative(rootDir, absolutePath)), reason: "duplicate_content" });
|
|
@@ -6108,8 +6942,14 @@ async function loadPageContents(rootDir, graph) {
|
|
|
6108
6942
|
await Promise.all(
|
|
6109
6943
|
graph.pages.map(async (page) => {
|
|
6110
6944
|
const absolutePath = path16.join(paths.wikiDir, page.path);
|
|
6111
|
-
const content = await fs12.readFile(absolutePath, "utf8").catch(() =>
|
|
6112
|
-
|
|
6945
|
+
const content = await fs12.readFile(absolutePath, "utf8").catch(() => {
|
|
6946
|
+
process.stderr.write(`[swarmvault] Warning: could not read page ${page.path} for embedding
|
|
6947
|
+
`);
|
|
6948
|
+
return "";
|
|
6949
|
+
});
|
|
6950
|
+
if (content) {
|
|
6951
|
+
contents.set(page.id, content);
|
|
6952
|
+
}
|
|
6113
6953
|
})
|
|
6114
6954
|
);
|
|
6115
6955
|
return contents;
|
|
@@ -9207,7 +10047,7 @@ async function resolveImageGenerationProvider(rootDir) {
|
|
|
9207
10047
|
if (!providerConfig) {
|
|
9208
10048
|
throw new Error(`No provider configured with id "${preferredProviderId}" for task "imageProvider".`);
|
|
9209
10049
|
}
|
|
9210
|
-
const { createProvider: createProvider2 } = await import("./registry-
|
|
10050
|
+
const { createProvider: createProvider2 } = await import("./registry-5SYH3Y3U.js");
|
|
9211
10051
|
return createProvider2(preferredProviderId, providerConfig, rootDir);
|
|
9212
10052
|
}
|
|
9213
10053
|
async function generateOutputArtifacts(rootDir, input) {
|
|
@@ -9768,8 +10608,64 @@ function deriveGraphMetrics(nodes, edges) {
|
|
|
9768
10608
|
function resetGraphNodeMetrics(nodes) {
|
|
9769
10609
|
return nodes.map(({ communityId: _communityId, degree: _degree, bridgeScore: _bridgeScore, isGodNode: _isGodNode, ...node }) => node);
|
|
9770
10610
|
}
|
|
10611
|
+
function manifestRepoPath(manifest) {
|
|
10612
|
+
return toPosix(manifest.repoRelativePath ?? path20.basename(manifest.originalPath ?? manifest.storedPath));
|
|
10613
|
+
}
|
|
10614
|
+
function goPackageScopeKey(manifest, analysis) {
|
|
10615
|
+
if (analysis.code?.language !== "go") {
|
|
10616
|
+
return null;
|
|
10617
|
+
}
|
|
10618
|
+
const packageName = analysis.code.namespace?.trim();
|
|
10619
|
+
if (!packageName) {
|
|
10620
|
+
return null;
|
|
10621
|
+
}
|
|
10622
|
+
return `${packageName}:${path20.posix.dirname(manifestRepoPath(manifest))}`;
|
|
10623
|
+
}
|
|
10624
|
+
function buildGoPackageSymbolLookups(analyses, manifestsById) {
|
|
10625
|
+
const lookups = /* @__PURE__ */ new Map();
|
|
10626
|
+
for (const analysis of analyses) {
|
|
10627
|
+
if (analysis.code?.language !== "go") {
|
|
10628
|
+
continue;
|
|
10629
|
+
}
|
|
10630
|
+
const manifest = manifestsById.get(analysis.sourceId);
|
|
10631
|
+
if (!manifest) {
|
|
10632
|
+
continue;
|
|
10633
|
+
}
|
|
10634
|
+
const scopeKey = goPackageScopeKey(manifest, analysis);
|
|
10635
|
+
if (!scopeKey) {
|
|
10636
|
+
continue;
|
|
10637
|
+
}
|
|
10638
|
+
const current = lookups.get(scopeKey) ?? {
|
|
10639
|
+
byName: /* @__PURE__ */ new Map(),
|
|
10640
|
+
methodIdsByShortName: /* @__PURE__ */ new Map()
|
|
10641
|
+
};
|
|
10642
|
+
for (const symbol of analysis.code.symbols) {
|
|
10643
|
+
current.byName.set(symbol.name, symbol.id);
|
|
10644
|
+
const separator = symbol.name.lastIndexOf(".");
|
|
10645
|
+
if (separator > 0) {
|
|
10646
|
+
const shortName = symbol.name.slice(separator + 1);
|
|
10647
|
+
const matches = current.methodIdsByShortName.get(shortName) ?? /* @__PURE__ */ new Set();
|
|
10648
|
+
matches.add(symbol.id);
|
|
10649
|
+
current.methodIdsByShortName.set(shortName, matches);
|
|
10650
|
+
}
|
|
10651
|
+
}
|
|
10652
|
+
lookups.set(scopeKey, current);
|
|
10653
|
+
}
|
|
10654
|
+
return new Map(
|
|
10655
|
+
[...lookups.entries()].map(([scopeKey, value]) => [
|
|
10656
|
+
scopeKey,
|
|
10657
|
+
{
|
|
10658
|
+
byName: value.byName,
|
|
10659
|
+
uniqueMethodIdsByShortName: new Map(
|
|
10660
|
+
[...value.methodIdsByShortName.entries()].filter(([, ids]) => ids.size === 1).map(([shortName, ids]) => [shortName, [...ids][0]])
|
|
10661
|
+
)
|
|
10662
|
+
}
|
|
10663
|
+
])
|
|
10664
|
+
);
|
|
10665
|
+
}
|
|
9771
10666
|
function buildGraph(manifests, analyses, pages, sourceProjects, _codeIndex) {
|
|
9772
10667
|
const manifestsById = new Map(manifests.map((manifest) => [manifest.sourceId, manifest]));
|
|
10668
|
+
const goPackageSymbolLookups = buildGoPackageSymbolLookups(analyses, manifestsById);
|
|
9773
10669
|
const sourceNodes = manifests.map((manifest) => ({
|
|
9774
10670
|
id: `source:${manifest.sourceId}`,
|
|
9775
10671
|
type: "source",
|
|
@@ -9916,6 +10812,10 @@ function buildGraph(manifests, analyses, pages, sourceProjects, _codeIndex) {
|
|
|
9916
10812
|
}
|
|
9917
10813
|
}
|
|
9918
10814
|
const symbolIdsByName = new Map(analysis.code.symbols.map((symbol) => [symbol.name, symbol.id]));
|
|
10815
|
+
const goPackageLookup = analysis.code.language === "go" ? goPackageSymbolLookups.get(goPackageScopeKey(manifest, analysis) ?? "") : void 0;
|
|
10816
|
+
const localSymbolIdsByName = goPackageLookup?.byName ?? symbolIdsByName;
|
|
10817
|
+
const localGoMethodIdsByShortName = goPackageLookup?.uniqueMethodIdsByShortName ?? /* @__PURE__ */ new Map();
|
|
10818
|
+
const resolveLocalSymbolId = (targetName) => localSymbolIdsByName.get(targetName) ?? (analysis.code?.language === "go" ? localGoMethodIdsByShortName.get(targetName) : void 0);
|
|
9919
10819
|
for (const rationale of analysis.rationales) {
|
|
9920
10820
|
const targetSymbolId = rationale.symbolName ? symbolIdsByName.get(rationale.symbolName) : void 0;
|
|
9921
10821
|
const targetId = targetSymbolId ?? moduleId;
|
|
@@ -9968,9 +10868,31 @@ function buildGraph(manifests, analyses, pages, sourceProjects, _codeIndex) {
|
|
|
9968
10868
|
}
|
|
9969
10869
|
}
|
|
9970
10870
|
}
|
|
10871
|
+
if (analysis.code.language === "go") {
|
|
10872
|
+
for (const symbol of analysis.code.symbols) {
|
|
10873
|
+
const separator = symbol.name.lastIndexOf(".");
|
|
10874
|
+
if (separator <= 0) {
|
|
10875
|
+
continue;
|
|
10876
|
+
}
|
|
10877
|
+
const receiverTypeId = localSymbolIdsByName.get(symbol.name.slice(0, separator));
|
|
10878
|
+
if (!receiverTypeId || receiverTypeId === symbol.id) {
|
|
10879
|
+
continue;
|
|
10880
|
+
}
|
|
10881
|
+
pushEdge({
|
|
10882
|
+
id: `${receiverTypeId}->${symbol.id}:defines:receiver`,
|
|
10883
|
+
source: receiverTypeId,
|
|
10884
|
+
target: symbol.id,
|
|
10885
|
+
relation: "defines",
|
|
10886
|
+
status: "extracted",
|
|
10887
|
+
evidenceClass: "extracted",
|
|
10888
|
+
confidence: 1,
|
|
10889
|
+
provenance: [analysis.sourceId]
|
|
10890
|
+
});
|
|
10891
|
+
}
|
|
10892
|
+
}
|
|
9971
10893
|
for (const symbol of analysis.code.symbols) {
|
|
9972
10894
|
for (const targetName of symbol.calls) {
|
|
9973
|
-
const targetId =
|
|
10895
|
+
const targetId = resolveLocalSymbolId(targetName);
|
|
9974
10896
|
if (!targetId || targetId === symbol.id) {
|
|
9975
10897
|
continue;
|
|
9976
10898
|
}
|
|
@@ -9986,7 +10908,7 @@ function buildGraph(manifests, analyses, pages, sourceProjects, _codeIndex) {
|
|
|
9986
10908
|
});
|
|
9987
10909
|
}
|
|
9988
10910
|
for (const targetName of symbol.extends) {
|
|
9989
|
-
const targetId =
|
|
10911
|
+
const targetId = resolveLocalSymbolId(targetName) ?? importedSymbolIdsByName.get(targetName);
|
|
9990
10912
|
if (!targetId) {
|
|
9991
10913
|
continue;
|
|
9992
10914
|
}
|
|
@@ -10002,7 +10924,7 @@ function buildGraph(manifests, analyses, pages, sourceProjects, _codeIndex) {
|
|
|
10002
10924
|
});
|
|
10003
10925
|
}
|
|
10004
10926
|
for (const targetName of symbol.implements) {
|
|
10005
|
-
const targetId =
|
|
10927
|
+
const targetId = resolveLocalSymbolId(targetName) ?? importedSymbolIdsByName.get(targetName);
|
|
10006
10928
|
if (!targetId) {
|
|
10007
10929
|
continue;
|
|
10008
10930
|
}
|
|
@@ -12482,7 +13404,7 @@ async function bootstrapDemo(rootDir, input) {
|
|
|
12482
13404
|
}
|
|
12483
13405
|
|
|
12484
13406
|
// src/mcp.ts
|
|
12485
|
-
var SERVER_VERSION = "0.1.
|
|
13407
|
+
var SERVER_VERSION = "0.1.29";
|
|
12486
13408
|
async function createMcpServer(rootDir) {
|
|
12487
13409
|
const server = new McpServer({
|
|
12488
13410
|
name: "swarmvault",
|
|
@@ -13140,7 +14062,7 @@ import mime2 from "mime-types";
|
|
|
13140
14062
|
|
|
13141
14063
|
// src/watch.ts
|
|
13142
14064
|
import path23 from "path";
|
|
13143
|
-
import
|
|
14065
|
+
import process3 from "process";
|
|
13144
14066
|
import chokidar from "chokidar";
|
|
13145
14067
|
var MAX_BACKOFF_MS = 3e4;
|
|
13146
14068
|
var BACKOFF_THRESHOLD = 3;
|
|
@@ -13405,7 +14327,7 @@ async function watchVault(rootDir, options = {}) {
|
|
|
13405
14327
|
consecutiveFailures++;
|
|
13406
14328
|
pending = true;
|
|
13407
14329
|
if (consecutiveFailures >= CRITICAL_THRESHOLD) {
|
|
13408
|
-
|
|
14330
|
+
process3.stderr.write(
|
|
13409
14331
|
`[swarmvault watch] ${consecutiveFailures} consecutive failures. Check vault state. Continuing at max backoff.
|
|
13410
14332
|
`
|
|
13411
14333
|
);
|
|
@@ -13416,51 +14338,33 @@ async function watchVault(rootDir, options = {}) {
|
|
|
13416
14338
|
}
|
|
13417
14339
|
} finally {
|
|
13418
14340
|
const finishedAt = /* @__PURE__ */ new Date();
|
|
13419
|
-
|
|
13420
|
-
|
|
13421
|
-
|
|
13422
|
-
|
|
13423
|
-
|
|
13424
|
-
|
|
13425
|
-
|
|
13426
|
-
|
|
13427
|
-
|
|
13428
|
-
|
|
13429
|
-
|
|
13430
|
-
|
|
13431
|
-
|
|
13432
|
-
|
|
13433
|
-
|
|
13434
|
-
|
|
13435
|
-
|
|
13436
|
-
|
|
13437
|
-
|
|
13438
|
-
|
|
13439
|
-
|
|
13440
|
-
|
|
13441
|
-
|
|
13442
|
-
|
|
13443
|
-
|
|
13444
|
-
|
|
13445
|
-
|
|
13446
|
-
importedCount: importedCount + repoImportedCount + repoUpdatedCount,
|
|
13447
|
-
scannedCount: scannedCount + repoScannedCount,
|
|
13448
|
-
attachmentCount,
|
|
13449
|
-
changedPages,
|
|
13450
|
-
repoImportedCount,
|
|
13451
|
-
repoUpdatedCount,
|
|
13452
|
-
repoRemovedCount,
|
|
13453
|
-
repoScannedCount,
|
|
13454
|
-
pendingSemanticRefreshCount,
|
|
13455
|
-
pendingSemanticRefreshPaths,
|
|
13456
|
-
lintFindingCount,
|
|
13457
|
-
success,
|
|
13458
|
-
error
|
|
13459
|
-
});
|
|
13460
|
-
await writeWatchStatusArtifact(rootDir, {
|
|
13461
|
-
generatedAt: finishedAt.toISOString(),
|
|
13462
|
-
watchedRepoRoots,
|
|
13463
|
-
lastRun: {
|
|
14341
|
+
try {
|
|
14342
|
+
await recordSession(rootDir, {
|
|
14343
|
+
operation: "watch",
|
|
14344
|
+
title: `Watch cycle for ${paths.inboxDir}${options.repo ? " and tracked repos" : ""}`,
|
|
14345
|
+
startedAt: startedAt.toISOString(),
|
|
14346
|
+
finishedAt: finishedAt.toISOString(),
|
|
14347
|
+
success,
|
|
14348
|
+
error,
|
|
14349
|
+
changedPages,
|
|
14350
|
+
lintFindingCount,
|
|
14351
|
+
lines: [
|
|
14352
|
+
`reasons=${runReasons.join(",") || "none"}`,
|
|
14353
|
+
`imported=${importedCount}`,
|
|
14354
|
+
`scanned=${scannedCount}`,
|
|
14355
|
+
`attachments=${attachmentCount}`,
|
|
14356
|
+
`repo_scanned=${repoScannedCount}`,
|
|
14357
|
+
`repo_imported=${repoImportedCount}`,
|
|
14358
|
+
`repo_updated=${repoUpdatedCount}`,
|
|
14359
|
+
`repo_removed=${repoRemovedCount}`,
|
|
14360
|
+
`lint=${lintFindingCount ?? 0}`
|
|
14361
|
+
]
|
|
14362
|
+
});
|
|
14363
|
+
} catch {
|
|
14364
|
+
process3.stderr.write("[swarmvault watch] Failed to record session log.\n");
|
|
14365
|
+
}
|
|
14366
|
+
try {
|
|
14367
|
+
await appendWatchRun(rootDir, {
|
|
13464
14368
|
startedAt: startedAt.toISOString(),
|
|
13465
14369
|
finishedAt: finishedAt.toISOString(),
|
|
13466
14370
|
durationMs: finishedAt.getTime() - startedAt.getTime(),
|
|
@@ -13479,9 +14383,39 @@ async function watchVault(rootDir, options = {}) {
|
|
|
13479
14383
|
lintFindingCount,
|
|
13480
14384
|
success,
|
|
13481
14385
|
error
|
|
13482
|
-
}
|
|
13483
|
-
|
|
13484
|
-
|
|
14386
|
+
});
|
|
14387
|
+
} catch {
|
|
14388
|
+
process3.stderr.write("[swarmvault watch] Failed to append watch run.\n");
|
|
14389
|
+
}
|
|
14390
|
+
try {
|
|
14391
|
+
await writeWatchStatusArtifact(rootDir, {
|
|
14392
|
+
generatedAt: finishedAt.toISOString(),
|
|
14393
|
+
watchedRepoRoots,
|
|
14394
|
+
lastRun: {
|
|
14395
|
+
startedAt: startedAt.toISOString(),
|
|
14396
|
+
finishedAt: finishedAt.toISOString(),
|
|
14397
|
+
durationMs: finishedAt.getTime() - startedAt.getTime(),
|
|
14398
|
+
inputDir: paths.inboxDir,
|
|
14399
|
+
reasons: runReasons,
|
|
14400
|
+
importedCount: importedCount + repoImportedCount + repoUpdatedCount,
|
|
14401
|
+
scannedCount: scannedCount + repoScannedCount,
|
|
14402
|
+
attachmentCount,
|
|
14403
|
+
changedPages,
|
|
14404
|
+
repoImportedCount,
|
|
14405
|
+
repoUpdatedCount,
|
|
14406
|
+
repoRemovedCount,
|
|
14407
|
+
repoScannedCount,
|
|
14408
|
+
pendingSemanticRefreshCount,
|
|
14409
|
+
pendingSemanticRefreshPaths,
|
|
14410
|
+
lintFindingCount,
|
|
14411
|
+
success,
|
|
14412
|
+
error
|
|
14413
|
+
},
|
|
14414
|
+
pendingSemanticRefresh: await readPendingSemanticRefresh(rootDir)
|
|
14415
|
+
});
|
|
14416
|
+
} catch {
|
|
14417
|
+
process3.stderr.write("[swarmvault watch] Failed to write watch status artifact.\n");
|
|
14418
|
+
}
|
|
13485
14419
|
running = false;
|
|
13486
14420
|
if (pending && !closed) {
|
|
13487
14421
|
schedule("queued");
|