@swarmvaultai/engine 0.1.21 → 0.1.22
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 +2 -1
- package/dist/index.d.ts +35 -1
- package/dist/index.js +842 -475
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -593,8 +593,8 @@ async function uninstallGitHooks(rootDir) {
|
|
|
593
593
|
}
|
|
594
594
|
|
|
595
595
|
// src/ingest.ts
|
|
596
|
-
import
|
|
597
|
-
import
|
|
596
|
+
import fs9 from "fs/promises";
|
|
597
|
+
import path9 from "path";
|
|
598
598
|
import { Readability } from "@mozilla/readability";
|
|
599
599
|
import ignore from "ignore";
|
|
600
600
|
import { JSDOM } from "jsdom";
|
|
@@ -2728,9 +2728,10 @@ async function analyzeCodeSource(manifest, extractedText, schemaHash) {
|
|
|
2728
2728
|
const language = manifest.language ?? inferCodeLanguage(manifest.originalPath ?? manifest.storedPath, manifest.mimeType) ?? "typescript";
|
|
2729
2729
|
const { code, rationales } = language === "javascript" || language === "jsx" || language === "typescript" || language === "tsx" ? analyzeTypeScriptLikeCode(manifest, extractedText) : await analyzeTreeSitterCode(manifest, extractedText, language);
|
|
2730
2730
|
return {
|
|
2731
|
-
analysisVersion:
|
|
2731
|
+
analysisVersion: 5,
|
|
2732
2732
|
sourceId: manifest.sourceId,
|
|
2733
2733
|
sourceHash: manifest.contentHash,
|
|
2734
|
+
extractionHash: manifest.extractionHash,
|
|
2734
2735
|
schemaHash,
|
|
2735
2736
|
title: manifest.title,
|
|
2736
2737
|
summary: summarizeModule(manifest, code),
|
|
@@ -2744,19 +2745,247 @@ async function analyzeCodeSource(manifest, extractedText, schemaHash) {
|
|
|
2744
2745
|
};
|
|
2745
2746
|
}
|
|
2746
2747
|
|
|
2747
|
-
// src/
|
|
2748
|
+
// src/extraction.ts
|
|
2748
2749
|
import fs6 from "fs/promises";
|
|
2750
|
+
import os from "os";
|
|
2749
2751
|
import path6 from "path";
|
|
2752
|
+
import { z } from "zod";
|
|
2753
|
+
var imageVisionExtractionSchema = z.object({
|
|
2754
|
+
title: z.string().min(1).nullable().optional(),
|
|
2755
|
+
summary: z.string().min(1),
|
|
2756
|
+
text: z.string().default(""),
|
|
2757
|
+
concepts: z.array(
|
|
2758
|
+
z.object({
|
|
2759
|
+
name: z.string().min(1),
|
|
2760
|
+
description: z.string().default("")
|
|
2761
|
+
})
|
|
2762
|
+
).max(12).default([]),
|
|
2763
|
+
entities: z.array(
|
|
2764
|
+
z.object({
|
|
2765
|
+
name: z.string().min(1),
|
|
2766
|
+
description: z.string().default("")
|
|
2767
|
+
})
|
|
2768
|
+
).max(12).default([]),
|
|
2769
|
+
claims: z.array(
|
|
2770
|
+
z.object({
|
|
2771
|
+
text: z.string().min(1),
|
|
2772
|
+
confidence: z.number().min(0).max(1).default(0.65),
|
|
2773
|
+
polarity: z.enum(["positive", "negative", "neutral"]).default("neutral")
|
|
2774
|
+
})
|
|
2775
|
+
).max(8).default([]),
|
|
2776
|
+
questions: z.array(z.string().min(1)).max(6).default([])
|
|
2777
|
+
});
|
|
2778
|
+
function extractionMetadata(sourceKind, mimeType, extractor) {
|
|
2779
|
+
return {
|
|
2780
|
+
extractor,
|
|
2781
|
+
sourceKind,
|
|
2782
|
+
mimeType,
|
|
2783
|
+
producedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2784
|
+
};
|
|
2785
|
+
}
|
|
2786
|
+
function buildExtractionHash(extractedText, artifact) {
|
|
2787
|
+
if (!extractedText && !artifact) {
|
|
2788
|
+
return void 0;
|
|
2789
|
+
}
|
|
2790
|
+
const normalizedArtifact = artifact ? {
|
|
2791
|
+
...artifact,
|
|
2792
|
+
producedAt: void 0
|
|
2793
|
+
} : null;
|
|
2794
|
+
return sha256(
|
|
2795
|
+
JSON.stringify({
|
|
2796
|
+
extractedText: extractedText ?? null,
|
|
2797
|
+
artifact: normalizedArtifact
|
|
2798
|
+
})
|
|
2799
|
+
);
|
|
2800
|
+
}
|
|
2801
|
+
function createPlainTextExtractionArtifact(sourceKind, mimeType) {
|
|
2802
|
+
return extractionMetadata(sourceKind, mimeType, "plain_text");
|
|
2803
|
+
}
|
|
2804
|
+
function createHtmlReadabilityExtractionArtifact(sourceKind, mimeType) {
|
|
2805
|
+
return extractionMetadata(sourceKind, mimeType, "html_readability");
|
|
2806
|
+
}
|
|
2807
|
+
function normalizeVisionMarkdown(payload) {
|
|
2808
|
+
const sections = [];
|
|
2809
|
+
if (payload.summary.trim()) {
|
|
2810
|
+
sections.push(payload.summary.trim());
|
|
2811
|
+
}
|
|
2812
|
+
if (payload.text.trim()) {
|
|
2813
|
+
sections.push(payload.text.trim());
|
|
2814
|
+
}
|
|
2815
|
+
if (payload.claims.length) {
|
|
2816
|
+
sections.push(payload.claims.map((claim) => `- ${claim.text}`).join("\n"));
|
|
2817
|
+
}
|
|
2818
|
+
return sections.join("\n\n").trim();
|
|
2819
|
+
}
|
|
2820
|
+
async function materializeAttachmentPath(input) {
|
|
2821
|
+
if (input.filePath) {
|
|
2822
|
+
return {
|
|
2823
|
+
filePath: input.filePath,
|
|
2824
|
+
cleanup: async () => {
|
|
2825
|
+
}
|
|
2826
|
+
};
|
|
2827
|
+
}
|
|
2828
|
+
if (!input.bytes) {
|
|
2829
|
+
throw new Error("Image extraction requires a file path or bytes.");
|
|
2830
|
+
}
|
|
2831
|
+
const tempDir = await fs6.mkdtemp(path6.join(os.tmpdir(), "swarmvault-image-extract-"));
|
|
2832
|
+
const extension = input.mimeType.split("/")[1]?.split("+")[0] ?? "bin";
|
|
2833
|
+
const tempPath = path6.join(tempDir, `source.${extension}`);
|
|
2834
|
+
await fs6.writeFile(tempPath, input.bytes);
|
|
2835
|
+
return {
|
|
2836
|
+
filePath: tempPath,
|
|
2837
|
+
cleanup: async () => {
|
|
2838
|
+
await fs6.rm(tempDir, { recursive: true, force: true });
|
|
2839
|
+
}
|
|
2840
|
+
};
|
|
2841
|
+
}
|
|
2842
|
+
async function extractImageWithVision(rootDir, input) {
|
|
2843
|
+
let provider;
|
|
2844
|
+
try {
|
|
2845
|
+
provider = await getProviderForTask(rootDir, "visionProvider");
|
|
2846
|
+
} catch (error) {
|
|
2847
|
+
return {
|
|
2848
|
+
artifact: {
|
|
2849
|
+
...extractionMetadata("image", input.mimeType, "image_vision"),
|
|
2850
|
+
warnings: [`Vision extraction unavailable: ${error instanceof Error ? error.message : "provider not configured"}`]
|
|
2851
|
+
}
|
|
2852
|
+
};
|
|
2853
|
+
}
|
|
2854
|
+
if (provider.type === "heuristic" || !provider.capabilities.has("vision") || !provider.capabilities.has("structured")) {
|
|
2855
|
+
return {
|
|
2856
|
+
artifact: {
|
|
2857
|
+
...extractionMetadata("image", input.mimeType, "image_vision"),
|
|
2858
|
+
warnings: [`Vision extraction unavailable for provider ${provider.id}. Configure a structured multimodal provider.`]
|
|
2859
|
+
}
|
|
2860
|
+
};
|
|
2861
|
+
}
|
|
2862
|
+
const attachment = await materializeAttachmentPath(input);
|
|
2863
|
+
try {
|
|
2864
|
+
const parsed = await provider.generateStructured(
|
|
2865
|
+
{
|
|
2866
|
+
system: [
|
|
2867
|
+
"You extract grounded notes from a single image for a local-first knowledge vault.",
|
|
2868
|
+
"Only describe content that is actually visible.",
|
|
2869
|
+
"If the image contains text, transcribe it accurately.",
|
|
2870
|
+
"If the image is a diagram or screenshot, summarize the key visible relationships and labels without speculation."
|
|
2871
|
+
].join("\n"),
|
|
2872
|
+
prompt: [
|
|
2873
|
+
`Source title: ${input.title}`,
|
|
2874
|
+
"Return structured extraction for this image.",
|
|
2875
|
+
"Include a concise summary, OCR-style text, grounded concepts/entities, visible claims, and follow-up questions."
|
|
2876
|
+
].join("\n"),
|
|
2877
|
+
attachments: [{ mimeType: input.mimeType, filePath: attachment.filePath }]
|
|
2878
|
+
},
|
|
2879
|
+
imageVisionExtractionSchema
|
|
2880
|
+
);
|
|
2881
|
+
const artifact = {
|
|
2882
|
+
...extractionMetadata("image", input.mimeType, "image_vision"),
|
|
2883
|
+
providerId: provider.id,
|
|
2884
|
+
providerModel: provider.model,
|
|
2885
|
+
vision: {
|
|
2886
|
+
title: parsed.title ?? void 0,
|
|
2887
|
+
summary: parsed.summary,
|
|
2888
|
+
text: parsed.text,
|
|
2889
|
+
concepts: parsed.concepts,
|
|
2890
|
+
entities: parsed.entities,
|
|
2891
|
+
claims: parsed.claims,
|
|
2892
|
+
questions: parsed.questions
|
|
2893
|
+
}
|
|
2894
|
+
};
|
|
2895
|
+
return {
|
|
2896
|
+
title: parsed.title ?? void 0,
|
|
2897
|
+
extractedText: normalizeVisionMarkdown(parsed),
|
|
2898
|
+
artifact
|
|
2899
|
+
};
|
|
2900
|
+
} catch (error) {
|
|
2901
|
+
return {
|
|
2902
|
+
artifact: {
|
|
2903
|
+
...extractionMetadata("image", input.mimeType, "image_vision"),
|
|
2904
|
+
providerId: provider.id,
|
|
2905
|
+
providerModel: provider.model,
|
|
2906
|
+
warnings: [`Vision extraction failed: ${error instanceof Error ? truncate(error.message, 240) : "unknown error"}`]
|
|
2907
|
+
}
|
|
2908
|
+
};
|
|
2909
|
+
} finally {
|
|
2910
|
+
await attachment.cleanup();
|
|
2911
|
+
}
|
|
2912
|
+
}
|
|
2913
|
+
function normalizePdfMetadata(raw) {
|
|
2914
|
+
if (!raw || typeof raw !== "object") {
|
|
2915
|
+
return void 0;
|
|
2916
|
+
}
|
|
2917
|
+
const metadata = {};
|
|
2918
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
2919
|
+
if (typeof value === "string") {
|
|
2920
|
+
const cleaned = normalizeWhitespace(value);
|
|
2921
|
+
if (cleaned) {
|
|
2922
|
+
metadata[key] = cleaned;
|
|
2923
|
+
}
|
|
2924
|
+
}
|
|
2925
|
+
}
|
|
2926
|
+
return Object.keys(metadata).length ? metadata : void 0;
|
|
2927
|
+
}
|
|
2928
|
+
async function extractPdfText(input) {
|
|
2929
|
+
try {
|
|
2930
|
+
const pdfjs = await import("pdfjs-dist/legacy/build/pdf.mjs");
|
|
2931
|
+
const task = pdfjs.getDocument({
|
|
2932
|
+
data: new Uint8Array(input.bytes),
|
|
2933
|
+
useWorkerFetch: false,
|
|
2934
|
+
isEvalSupported: false,
|
|
2935
|
+
disableFontFace: true,
|
|
2936
|
+
verbosity: 0
|
|
2937
|
+
});
|
|
2938
|
+
const document = await task.promise;
|
|
2939
|
+
const pageTexts = [];
|
|
2940
|
+
for (let pageNumber = 1; pageNumber <= document.numPages; pageNumber += 1) {
|
|
2941
|
+
const page = await document.getPage(pageNumber);
|
|
2942
|
+
const textContent = await page.getTextContent();
|
|
2943
|
+
const pageText = normalizeWhitespace(
|
|
2944
|
+
textContent.items.map((item) => typeof item === "object" && item && "str" in item && typeof item.str === "string" ? item.str : "").join(" ")
|
|
2945
|
+
);
|
|
2946
|
+
if (pageText) {
|
|
2947
|
+
pageTexts.push(pageText);
|
|
2948
|
+
}
|
|
2949
|
+
page.cleanup();
|
|
2950
|
+
}
|
|
2951
|
+
const metadataResult = await document.getMetadata().catch(() => null);
|
|
2952
|
+
await task.destroy();
|
|
2953
|
+
const extractedText = pageTexts.join("\n\n").trim();
|
|
2954
|
+
const artifact = {
|
|
2955
|
+
...extractionMetadata("pdf", input.mimeType, "pdf_text"),
|
|
2956
|
+
pageCount: document.numPages,
|
|
2957
|
+
metadata: normalizePdfMetadata(metadataResult?.info)
|
|
2958
|
+
};
|
|
2959
|
+
if (!extractedText) {
|
|
2960
|
+
artifact.warnings = ["PDF text extraction completed but produced no extractable text."];
|
|
2961
|
+
}
|
|
2962
|
+
return {
|
|
2963
|
+
extractedText: extractedText || void 0,
|
|
2964
|
+
artifact
|
|
2965
|
+
};
|
|
2966
|
+
} catch (error) {
|
|
2967
|
+
return {
|
|
2968
|
+
artifact: {
|
|
2969
|
+
...extractionMetadata("pdf", input.mimeType, "pdf_text"),
|
|
2970
|
+
warnings: [`PDF text extraction failed: ${error instanceof Error ? truncate(error.message, 240) : "unknown error"}`]
|
|
2971
|
+
}
|
|
2972
|
+
};
|
|
2973
|
+
}
|
|
2974
|
+
}
|
|
2975
|
+
|
|
2976
|
+
// src/logs.ts
|
|
2977
|
+
import fs7 from "fs/promises";
|
|
2978
|
+
import path7 from "path";
|
|
2750
2979
|
import matter from "gray-matter";
|
|
2751
2980
|
async function resolveUniqueSessionPath(rootDir, operation, title, startedAt) {
|
|
2752
2981
|
const { paths } = await initWorkspace(rootDir);
|
|
2753
2982
|
await ensureDir(paths.sessionsDir);
|
|
2754
2983
|
const timestamp = startedAt.replace(/[:.]/g, "-");
|
|
2755
2984
|
const baseName = `${timestamp}-${operation}-${slugify(title)}`;
|
|
2756
|
-
let candidate =
|
|
2985
|
+
let candidate = path7.join(paths.sessionsDir, `${baseName}.md`);
|
|
2757
2986
|
let counter = 2;
|
|
2758
2987
|
while (await fileExists(candidate)) {
|
|
2759
|
-
candidate =
|
|
2988
|
+
candidate = path7.join(paths.sessionsDir, `${baseName}-${counter}.md`);
|
|
2760
2989
|
counter++;
|
|
2761
2990
|
}
|
|
2762
2991
|
return candidate;
|
|
@@ -2764,11 +2993,11 @@ async function resolveUniqueSessionPath(rootDir, operation, title, startedAt) {
|
|
|
2764
2993
|
async function appendLogEntry(rootDir, action, title, lines = []) {
|
|
2765
2994
|
const { paths } = await initWorkspace(rootDir);
|
|
2766
2995
|
await ensureDir(paths.wikiDir);
|
|
2767
|
-
const logPath =
|
|
2996
|
+
const logPath = path7.join(paths.wikiDir, "log.md");
|
|
2768
2997
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace("T", " ");
|
|
2769
2998
|
const entry = [`## [${timestamp}] ${action} | ${title}`, ...lines.map((line) => `- ${line}`), ""].join("\n");
|
|
2770
|
-
const existing = await fileExists(logPath) ? await
|
|
2771
|
-
await
|
|
2999
|
+
const existing = await fileExists(logPath) ? await fs7.readFile(logPath, "utf8") : "# Log\n\n";
|
|
3000
|
+
await fs7.writeFile(logPath, `${existing}${entry}
|
|
2772
3001
|
`, "utf8");
|
|
2773
3002
|
}
|
|
2774
3003
|
async function recordSession(rootDir, input) {
|
|
@@ -2778,8 +3007,8 @@ async function recordSession(rootDir, input) {
|
|
|
2778
3007
|
const finishedAtIso = new Date(input.finishedAt ?? input.startedAt).toISOString();
|
|
2779
3008
|
const durationMs = Math.max(0, new Date(finishedAtIso).getTime() - new Date(startedAtIso).getTime());
|
|
2780
3009
|
const sessionPath = await resolveUniqueSessionPath(rootDir, input.operation, input.title, startedAtIso);
|
|
2781
|
-
const sessionId =
|
|
2782
|
-
const relativeSessionPath =
|
|
3010
|
+
const sessionId = path7.basename(sessionPath, ".md");
|
|
3011
|
+
const relativeSessionPath = path7.relative(rootDir, sessionPath).split(path7.sep).join(path7.posix.sep);
|
|
2783
3012
|
const frontmatter = Object.fromEntries(
|
|
2784
3013
|
Object.entries({
|
|
2785
3014
|
session_id: sessionId,
|
|
@@ -2827,7 +3056,7 @@ async function recordSession(rootDir, input) {
|
|
|
2827
3056
|
frontmatter
|
|
2828
3057
|
);
|
|
2829
3058
|
await writeFileIfChanged(sessionPath, content);
|
|
2830
|
-
const logPath =
|
|
3059
|
+
const logPath = path7.join(paths.wikiDir, "log.md");
|
|
2831
3060
|
const timestamp = startedAtIso.slice(0, 19).replace("T", " ");
|
|
2832
3061
|
const entry = [
|
|
2833
3062
|
`## [${timestamp}] ${input.operation} | ${input.title}`,
|
|
@@ -2835,8 +3064,8 @@ async function recordSession(rootDir, input) {
|
|
|
2835
3064
|
...(input.lines ?? []).map((line) => `- ${line}`),
|
|
2836
3065
|
""
|
|
2837
3066
|
].join("\n");
|
|
2838
|
-
const existing = await fileExists(logPath) ? await
|
|
2839
|
-
await
|
|
3067
|
+
const existing = await fileExists(logPath) ? await fs7.readFile(logPath, "utf8") : "# Log\n\n";
|
|
3068
|
+
await fs7.writeFile(logPath, `${existing}${entry}
|
|
2840
3069
|
`, "utf8");
|
|
2841
3070
|
return { sessionPath, sessionId };
|
|
2842
3071
|
}
|
|
@@ -2846,8 +3075,8 @@ async function appendWatchRun(rootDir, run) {
|
|
|
2846
3075
|
}
|
|
2847
3076
|
|
|
2848
3077
|
// src/watch-state.ts
|
|
2849
|
-
import
|
|
2850
|
-
import
|
|
3078
|
+
import fs8 from "fs/promises";
|
|
3079
|
+
import path8 from "path";
|
|
2851
3080
|
import matter2 from "gray-matter";
|
|
2852
3081
|
function pendingEntryKey(entry) {
|
|
2853
3082
|
return entry.path;
|
|
@@ -2861,7 +3090,7 @@ function normalizeRelativePath(rootDir, filePath) {
|
|
|
2861
3090
|
if (!filePath) {
|
|
2862
3091
|
return void 0;
|
|
2863
3092
|
}
|
|
2864
|
-
return toPosix(
|
|
3093
|
+
return toPosix(path8.relative(rootDir, path8.resolve(filePath)));
|
|
2865
3094
|
}
|
|
2866
3095
|
async function readPendingSemanticRefresh(rootDir) {
|
|
2867
3096
|
const { paths } = await initWorkspace(rootDir);
|
|
@@ -2955,11 +3184,11 @@ async function markPagesStaleForSources(rootDir, sourceIds) {
|
|
|
2955
3184
|
if (page.freshness !== "stale" || !page.sourceIds.some((sourceId) => affectedSourceIds.has(sourceId))) {
|
|
2956
3185
|
continue;
|
|
2957
3186
|
}
|
|
2958
|
-
const absolutePath =
|
|
3187
|
+
const absolutePath = path8.join(paths.wikiDir, page.path);
|
|
2959
3188
|
if (!await fileExists(absolutePath)) {
|
|
2960
3189
|
continue;
|
|
2961
3190
|
}
|
|
2962
|
-
const raw = await
|
|
3191
|
+
const raw = await fs8.readFile(absolutePath, "utf8");
|
|
2963
3192
|
const parsed = matter2(raw);
|
|
2964
3193
|
if (parsed.data.freshness === "stale") {
|
|
2965
3194
|
continue;
|
|
@@ -3007,7 +3236,7 @@ function normalizeIngestOptions(options) {
|
|
|
3007
3236
|
return {
|
|
3008
3237
|
includeAssets: options?.includeAssets ?? true,
|
|
3009
3238
|
maxAssetSize: Math.max(0, Math.floor(options?.maxAssetSize ?? DEFAULT_MAX_ASSET_SIZE)),
|
|
3010
|
-
repoRoot: options?.repoRoot ?
|
|
3239
|
+
repoRoot: options?.repoRoot ? path9.resolve(options.repoRoot) : void 0,
|
|
3011
3240
|
include: (options?.include ?? []).map((pattern) => pattern.trim()).filter(Boolean),
|
|
3012
3241
|
exclude: (options?.exclude ?? []).map((pattern) => pattern.trim()).filter(Boolean),
|
|
3013
3242
|
maxFiles: Math.max(1, Math.floor(options?.maxFiles ?? DEFAULT_MAX_DIRECTORY_FILES)),
|
|
@@ -3016,27 +3245,27 @@ function normalizeIngestOptions(options) {
|
|
|
3016
3245
|
}
|
|
3017
3246
|
function matchesAnyGlob(relativePath, patterns) {
|
|
3018
3247
|
return patterns.some(
|
|
3019
|
-
(pattern) =>
|
|
3248
|
+
(pattern) => path9.matchesGlob(relativePath, pattern) || path9.matchesGlob(path9.posix.basename(relativePath), pattern)
|
|
3020
3249
|
);
|
|
3021
3250
|
}
|
|
3022
3251
|
function supportedDirectoryKind(sourceKind) {
|
|
3023
3252
|
return sourceKind !== "binary";
|
|
3024
3253
|
}
|
|
3025
3254
|
async function findNearestGitRoot2(startPath) {
|
|
3026
|
-
let current =
|
|
3255
|
+
let current = path9.resolve(startPath);
|
|
3027
3256
|
try {
|
|
3028
|
-
const stat = await
|
|
3257
|
+
const stat = await fs9.stat(current);
|
|
3029
3258
|
if (!stat.isDirectory()) {
|
|
3030
|
-
current =
|
|
3259
|
+
current = path9.dirname(current);
|
|
3031
3260
|
}
|
|
3032
3261
|
} catch {
|
|
3033
|
-
current =
|
|
3262
|
+
current = path9.dirname(current);
|
|
3034
3263
|
}
|
|
3035
3264
|
while (true) {
|
|
3036
|
-
if (await fileExists(
|
|
3265
|
+
if (await fileExists(path9.join(current, ".git"))) {
|
|
3037
3266
|
return current;
|
|
3038
3267
|
}
|
|
3039
|
-
const parent =
|
|
3268
|
+
const parent = path9.dirname(current);
|
|
3040
3269
|
if (parent === current) {
|
|
3041
3270
|
return null;
|
|
3042
3271
|
}
|
|
@@ -3044,26 +3273,26 @@ async function findNearestGitRoot2(startPath) {
|
|
|
3044
3273
|
}
|
|
3045
3274
|
}
|
|
3046
3275
|
function withinRoot(rootPath, targetPath) {
|
|
3047
|
-
const relative =
|
|
3048
|
-
return relative === "" || !relative.startsWith("..") && !
|
|
3276
|
+
const relative = path9.relative(rootPath, targetPath);
|
|
3277
|
+
return relative === "" || !relative.startsWith("..") && !path9.isAbsolute(relative);
|
|
3049
3278
|
}
|
|
3050
3279
|
function repoRootFromManifest(manifest) {
|
|
3051
3280
|
if (manifest.originType !== "file" || !manifest.originalPath || !manifest.repoRelativePath) {
|
|
3052
3281
|
return null;
|
|
3053
3282
|
}
|
|
3054
|
-
const repoDir =
|
|
3055
|
-
const fileDir =
|
|
3283
|
+
const repoDir = path9.posix.dirname(manifest.repoRelativePath);
|
|
3284
|
+
const fileDir = path9.dirname(path9.resolve(manifest.originalPath));
|
|
3056
3285
|
if (repoDir === "." || !repoDir) {
|
|
3057
3286
|
return fileDir;
|
|
3058
3287
|
}
|
|
3059
3288
|
const segments = repoDir.split("/").filter(Boolean);
|
|
3060
|
-
return
|
|
3289
|
+
return path9.resolve(fileDir, ...segments.map(() => ".."));
|
|
3061
3290
|
}
|
|
3062
3291
|
function repoRelativePathFor(absolutePath, repoRoot) {
|
|
3063
3292
|
if (!repoRoot || !withinRoot(repoRoot, absolutePath)) {
|
|
3064
3293
|
return void 0;
|
|
3065
3294
|
}
|
|
3066
|
-
const relative = toPosix(
|
|
3295
|
+
const relative = toPosix(path9.relative(repoRoot, absolutePath));
|
|
3067
3296
|
return relative && !relative.startsWith("..") ? relative : void 0;
|
|
3068
3297
|
}
|
|
3069
3298
|
function normalizeOriginUrl(input) {
|
|
@@ -3231,7 +3460,7 @@ function buildCompositeHash(payloadBytes, attachments = []) {
|
|
|
3231
3460
|
return sha256(`${sha256(payloadBytes)}|${attachmentSignature}`);
|
|
3232
3461
|
}
|
|
3233
3462
|
function sanitizeAssetRelativePath(value) {
|
|
3234
|
-
const normalized =
|
|
3463
|
+
const normalized = path9.posix.normalize(value.replace(/\\/g, "/"));
|
|
3235
3464
|
const segments = normalized.split("/").filter(Boolean).map((segment) => {
|
|
3236
3465
|
if (segment === ".") {
|
|
3237
3466
|
return "";
|
|
@@ -3251,7 +3480,7 @@ function normalizeLocalReference(value) {
|
|
|
3251
3480
|
return null;
|
|
3252
3481
|
}
|
|
3253
3482
|
const lowered = candidate.toLowerCase();
|
|
3254
|
-
if (lowered.startsWith("http://") || lowered.startsWith("https://") || lowered.startsWith("data:") || lowered.startsWith("mailto:") || lowered.startsWith("#") ||
|
|
3483
|
+
if (lowered.startsWith("http://") || lowered.startsWith("https://") || lowered.startsWith("data:") || lowered.startsWith("mailto:") || lowered.startsWith("#") || path9.isAbsolute(candidate)) {
|
|
3255
3484
|
return null;
|
|
3256
3485
|
}
|
|
3257
3486
|
return candidate.replace(/\\/g, "/");
|
|
@@ -3313,12 +3542,12 @@ async function convertHtmlToMarkdown(html, url) {
|
|
|
3313
3542
|
};
|
|
3314
3543
|
}
|
|
3315
3544
|
async function readManifestByHash(manifestsDir, contentHash) {
|
|
3316
|
-
const entries = await
|
|
3545
|
+
const entries = await fs9.readdir(manifestsDir, { withFileTypes: true }).catch(() => []);
|
|
3317
3546
|
for (const entry of entries) {
|
|
3318
3547
|
if (!entry.isFile() || !entry.name.endsWith(".json")) {
|
|
3319
3548
|
continue;
|
|
3320
3549
|
}
|
|
3321
|
-
const manifest = await readJsonFile(
|
|
3550
|
+
const manifest = await readJsonFile(path9.join(manifestsDir, entry.name));
|
|
3322
3551
|
if (manifest?.contentHash === contentHash) {
|
|
3323
3552
|
return manifest;
|
|
3324
3553
|
}
|
|
@@ -3326,12 +3555,12 @@ async function readManifestByHash(manifestsDir, contentHash) {
|
|
|
3326
3555
|
return null;
|
|
3327
3556
|
}
|
|
3328
3557
|
async function readManifestByOrigin(manifestsDir, prepared) {
|
|
3329
|
-
const entries = await
|
|
3558
|
+
const entries = await fs9.readdir(manifestsDir, { withFileTypes: true }).catch(() => []);
|
|
3330
3559
|
for (const entry of entries) {
|
|
3331
3560
|
if (!entry.isFile() || !entry.name.endsWith(".json")) {
|
|
3332
3561
|
continue;
|
|
3333
3562
|
}
|
|
3334
|
-
const manifest = await readJsonFile(
|
|
3563
|
+
const manifest = await readJsonFile(path9.join(manifestsDir, entry.name));
|
|
3335
3564
|
if (manifest && manifestMatchesOrigin(manifest, prepared)) {
|
|
3336
3565
|
return manifest;
|
|
3337
3566
|
}
|
|
@@ -3342,12 +3571,12 @@ async function loadGitignoreMatcher(repoRoot, enabled) {
|
|
|
3342
3571
|
if (!enabled) {
|
|
3343
3572
|
return null;
|
|
3344
3573
|
}
|
|
3345
|
-
const gitignorePath =
|
|
3574
|
+
const gitignorePath = path9.join(repoRoot, ".gitignore");
|
|
3346
3575
|
if (!await fileExists(gitignorePath)) {
|
|
3347
3576
|
return null;
|
|
3348
3577
|
}
|
|
3349
3578
|
const matcher = ignore();
|
|
3350
|
-
matcher.add(await
|
|
3579
|
+
matcher.add(await fs9.readFile(gitignorePath, "utf8"));
|
|
3351
3580
|
return matcher;
|
|
3352
3581
|
}
|
|
3353
3582
|
function builtInIgnoreReason(relativePath) {
|
|
@@ -3368,23 +3597,23 @@ async function collectDirectoryFiles(rootDir, inputDir, repoRoot, options) {
|
|
|
3368
3597
|
if (!currentDir) {
|
|
3369
3598
|
continue;
|
|
3370
3599
|
}
|
|
3371
|
-
const entries = await
|
|
3600
|
+
const entries = await fs9.readdir(currentDir, { withFileTypes: true });
|
|
3372
3601
|
entries.sort((left, right) => left.name.localeCompare(right.name));
|
|
3373
3602
|
for (const entry of entries) {
|
|
3374
|
-
const absolutePath =
|
|
3375
|
-
const relativeToRepo = repoRelativePathFor(absolutePath, repoRoot) ?? toPosix(
|
|
3603
|
+
const absolutePath = path9.join(currentDir, entry.name);
|
|
3604
|
+
const relativeToRepo = repoRelativePathFor(absolutePath, repoRoot) ?? toPosix(path9.relative(inputDir, absolutePath));
|
|
3376
3605
|
const relativePath = relativeToRepo || entry.name;
|
|
3377
3606
|
const builtInReason = builtInIgnoreReason(relativePath);
|
|
3378
3607
|
if (builtInReason) {
|
|
3379
|
-
skipped.push({ path: toPosix(
|
|
3608
|
+
skipped.push({ path: toPosix(path9.relative(rootDir, absolutePath)), reason: builtInReason });
|
|
3380
3609
|
continue;
|
|
3381
3610
|
}
|
|
3382
3611
|
if (matcher?.ignores(relativePath)) {
|
|
3383
|
-
skipped.push({ path: toPosix(
|
|
3612
|
+
skipped.push({ path: toPosix(path9.relative(rootDir, absolutePath)), reason: "gitignore" });
|
|
3384
3613
|
continue;
|
|
3385
3614
|
}
|
|
3386
3615
|
if (matchesAnyGlob(relativePath, options.exclude)) {
|
|
3387
|
-
skipped.push({ path: toPosix(
|
|
3616
|
+
skipped.push({ path: toPosix(path9.relative(rootDir, absolutePath)), reason: "exclude_glob" });
|
|
3388
3617
|
continue;
|
|
3389
3618
|
}
|
|
3390
3619
|
if (entry.isDirectory()) {
|
|
@@ -3392,21 +3621,21 @@ async function collectDirectoryFiles(rootDir, inputDir, repoRoot, options) {
|
|
|
3392
3621
|
continue;
|
|
3393
3622
|
}
|
|
3394
3623
|
if (!entry.isFile()) {
|
|
3395
|
-
skipped.push({ path: toPosix(
|
|
3624
|
+
skipped.push({ path: toPosix(path9.relative(rootDir, absolutePath)), reason: "unsupported_entry" });
|
|
3396
3625
|
continue;
|
|
3397
3626
|
}
|
|
3398
3627
|
if (options.include.length > 0 && !matchesAnyGlob(relativePath, options.include)) {
|
|
3399
|
-
skipped.push({ path: toPosix(
|
|
3628
|
+
skipped.push({ path: toPosix(path9.relative(rootDir, absolutePath)), reason: "include_glob" });
|
|
3400
3629
|
continue;
|
|
3401
3630
|
}
|
|
3402
3631
|
const mimeType = guessMimeType(absolutePath);
|
|
3403
3632
|
const sourceKind = inferKind(mimeType, absolutePath);
|
|
3404
3633
|
if (!supportedDirectoryKind(sourceKind)) {
|
|
3405
|
-
skipped.push({ path: toPosix(
|
|
3634
|
+
skipped.push({ path: toPosix(path9.relative(rootDir, absolutePath)), reason: `unsupported_kind:${sourceKind}` });
|
|
3406
3635
|
continue;
|
|
3407
3636
|
}
|
|
3408
3637
|
if (files.length >= options.maxFiles) {
|
|
3409
|
-
skipped.push({ path: toPosix(
|
|
3638
|
+
skipped.push({ path: toPosix(path9.relative(rootDir, absolutePath)), reason: "max_files" });
|
|
3410
3639
|
continue;
|
|
3411
3640
|
}
|
|
3412
3641
|
files.push(absolutePath);
|
|
@@ -3428,12 +3657,12 @@ function resolveUrlMimeType(input, response) {
|
|
|
3428
3657
|
function buildRemoteAssetRelativePath(assetUrl, mimeType) {
|
|
3429
3658
|
const url = new URL(assetUrl);
|
|
3430
3659
|
const normalized = sanitizeAssetRelativePath(`${url.hostname}${url.pathname || "/asset"}`);
|
|
3431
|
-
const extension =
|
|
3432
|
-
const directory =
|
|
3433
|
-
const basename = extension ?
|
|
3660
|
+
const extension = path9.posix.extname(normalized);
|
|
3661
|
+
const directory = path9.posix.dirname(normalized);
|
|
3662
|
+
const basename = extension ? path9.posix.basename(normalized, extension) : path9.posix.basename(normalized);
|
|
3434
3663
|
const resolvedExtension = extension || `.${mime.extension(mimeType) || "bin"}`;
|
|
3435
3664
|
const hashedName = `${basename || "asset"}-${sha256(assetUrl).slice(0, 8)}${resolvedExtension}`;
|
|
3436
|
-
return directory === "." ? hashedName :
|
|
3665
|
+
return directory === "." ? hashedName : path9.posix.join(directory, hashedName);
|
|
3437
3666
|
}
|
|
3438
3667
|
async function readResponseBytesWithinLimit(response, maxBytes) {
|
|
3439
3668
|
const contentLength = Number.parseInt(response.headers.get("content-length") ?? "", 10);
|
|
@@ -3557,9 +3786,10 @@ async function persistPreparedInput(rootDir, prepared, paths) {
|
|
|
3557
3786
|
await ensureDir(paths.extractsDir);
|
|
3558
3787
|
const attachments = prepared.attachments ?? [];
|
|
3559
3788
|
const contentHash = prepared.contentHash ?? buildCompositeHash(prepared.payloadBytes, attachments);
|
|
3789
|
+
const extractionHash = prepared.extractionHash ?? buildExtractionHash(prepared.extractedText, prepared.extractionArtifact);
|
|
3560
3790
|
const existingByOrigin = await readManifestByOrigin(paths.manifestsDir, prepared);
|
|
3561
3791
|
const existingByHash = existingByOrigin ? null : await readManifestByHash(paths.manifestsDir, contentHash);
|
|
3562
|
-
if (existingByOrigin && existingByOrigin.contentHash === contentHash && existingByOrigin.title === prepared.title && existingByOrigin.sourceKind === prepared.sourceKind && existingByOrigin.language === prepared.language && existingByOrigin.mimeType === prepared.mimeType && existingByOrigin.repoRelativePath === prepared.repoRelativePath) {
|
|
3792
|
+
if (existingByOrigin && existingByOrigin.contentHash === contentHash && existingByOrigin.extractionHash === extractionHash && existingByOrigin.title === prepared.title && existingByOrigin.sourceKind === prepared.sourceKind && existingByOrigin.language === prepared.language && existingByOrigin.mimeType === prepared.mimeType && existingByOrigin.repoRelativePath === prepared.repoRelativePath) {
|
|
3563
3793
|
return { manifest: existingByOrigin, isNew: false, wasUpdated: false };
|
|
3564
3794
|
}
|
|
3565
3795
|
if (existingByHash) {
|
|
@@ -3568,27 +3798,34 @@ async function persistPreparedInput(rootDir, prepared, paths) {
|
|
|
3568
3798
|
const previous = existingByOrigin ?? void 0;
|
|
3569
3799
|
const sourceId = previous?.sourceId ?? `${slugify(prepared.title)}-${contentHash.slice(0, 8)}`;
|
|
3570
3800
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3571
|
-
const storedPath =
|
|
3572
|
-
const extractedTextPath = prepared.extractedText ?
|
|
3573
|
-
const
|
|
3801
|
+
const storedPath = path9.join(paths.rawSourcesDir, `${sourceId}${prepared.storedExtension}`);
|
|
3802
|
+
const extractedTextPath = prepared.extractedText ? path9.join(paths.extractsDir, `${sourceId}.md`) : void 0;
|
|
3803
|
+
const extractedMetadataPath = prepared.extractionArtifact ? path9.join(paths.extractsDir, `${sourceId}.json`) : void 0;
|
|
3804
|
+
const attachmentsDir = path9.join(paths.rawAssetsDir, sourceId);
|
|
3574
3805
|
if (previous?.storedPath) {
|
|
3575
|
-
await
|
|
3806
|
+
await fs9.rm(path9.resolve(rootDir, previous.storedPath), { force: true });
|
|
3576
3807
|
}
|
|
3577
3808
|
if (previous?.extractedTextPath) {
|
|
3578
|
-
await
|
|
3809
|
+
await fs9.rm(path9.resolve(rootDir, previous.extractedTextPath), { force: true });
|
|
3810
|
+
}
|
|
3811
|
+
if (previous?.extractedMetadataPath) {
|
|
3812
|
+
await fs9.rm(path9.resolve(rootDir, previous.extractedMetadataPath), { force: true });
|
|
3579
3813
|
}
|
|
3580
|
-
await
|
|
3581
|
-
await
|
|
3814
|
+
await fs9.rm(attachmentsDir, { recursive: true, force: true });
|
|
3815
|
+
await fs9.writeFile(storedPath, prepared.payloadBytes);
|
|
3582
3816
|
if (prepared.extractedText && extractedTextPath) {
|
|
3583
|
-
await
|
|
3817
|
+
await fs9.writeFile(extractedTextPath, prepared.extractedText, "utf8");
|
|
3818
|
+
}
|
|
3819
|
+
if (prepared.extractionArtifact && extractedMetadataPath) {
|
|
3820
|
+
await writeJsonFile(extractedMetadataPath, prepared.extractionArtifact);
|
|
3584
3821
|
}
|
|
3585
3822
|
const manifestAttachments = [];
|
|
3586
3823
|
for (const attachment of attachments) {
|
|
3587
|
-
const absoluteAttachmentPath =
|
|
3588
|
-
await ensureDir(
|
|
3589
|
-
await
|
|
3824
|
+
const absoluteAttachmentPath = path9.join(attachmentsDir, attachment.relativePath);
|
|
3825
|
+
await ensureDir(path9.dirname(absoluteAttachmentPath));
|
|
3826
|
+
await fs9.writeFile(absoluteAttachmentPath, attachment.bytes);
|
|
3590
3827
|
manifestAttachments.push({
|
|
3591
|
-
path: toPosix(
|
|
3828
|
+
path: toPosix(path9.relative(rootDir, absoluteAttachmentPath)),
|
|
3592
3829
|
mimeType: attachment.mimeType,
|
|
3593
3830
|
originalPath: attachment.originalPath
|
|
3594
3831
|
});
|
|
@@ -3602,15 +3839,17 @@ async function persistPreparedInput(rootDir, prepared, paths) {
|
|
|
3602
3839
|
originalPath: prepared.originalPath,
|
|
3603
3840
|
repoRelativePath: prepared.repoRelativePath,
|
|
3604
3841
|
url: prepared.url,
|
|
3605
|
-
storedPath: toPosix(
|
|
3606
|
-
extractedTextPath: extractedTextPath ? toPosix(
|
|
3842
|
+
storedPath: toPosix(path9.relative(rootDir, storedPath)),
|
|
3843
|
+
extractedTextPath: extractedTextPath ? toPosix(path9.relative(rootDir, extractedTextPath)) : void 0,
|
|
3844
|
+
extractedMetadataPath: extractedMetadataPath ? toPosix(path9.relative(rootDir, extractedMetadataPath)) : void 0,
|
|
3845
|
+
extractionHash,
|
|
3607
3846
|
mimeType: prepared.mimeType,
|
|
3608
3847
|
contentHash,
|
|
3609
3848
|
createdAt: previous?.createdAt ?? now,
|
|
3610
3849
|
updatedAt: now,
|
|
3611
3850
|
attachments: manifestAttachments.length ? manifestAttachments : void 0
|
|
3612
3851
|
};
|
|
3613
|
-
await writeJsonFile(
|
|
3852
|
+
await writeJsonFile(path9.join(paths.manifestsDir, `${sourceId}.json`), manifest);
|
|
3614
3853
|
await appendLogEntry(rootDir, "ingest", prepared.title, [
|
|
3615
3854
|
`source_id=${sourceId}`,
|
|
3616
3855
|
`kind=${prepared.sourceKind}`,
|
|
@@ -3628,13 +3867,16 @@ async function persistPreparedInput(rootDir, prepared, paths) {
|
|
|
3628
3867
|
return { manifest, isNew: !previous, wasUpdated: Boolean(previous) };
|
|
3629
3868
|
}
|
|
3630
3869
|
async function removeManifestArtifacts(rootDir, manifest, paths) {
|
|
3631
|
-
await
|
|
3632
|
-
await
|
|
3870
|
+
await fs9.rm(path9.join(paths.manifestsDir, `${manifest.sourceId}.json`), { force: true });
|
|
3871
|
+
await fs9.rm(path9.resolve(rootDir, manifest.storedPath), { force: true });
|
|
3633
3872
|
if (manifest.extractedTextPath) {
|
|
3634
|
-
await
|
|
3873
|
+
await fs9.rm(path9.resolve(rootDir, manifest.extractedTextPath), { force: true });
|
|
3635
3874
|
}
|
|
3636
|
-
|
|
3637
|
-
|
|
3875
|
+
if (manifest.extractedMetadataPath) {
|
|
3876
|
+
await fs9.rm(path9.resolve(rootDir, manifest.extractedMetadataPath), { force: true });
|
|
3877
|
+
}
|
|
3878
|
+
await fs9.rm(path9.join(paths.rawAssetsDir, manifest.sourceId), { recursive: true, force: true });
|
|
3879
|
+
await fs9.rm(path9.join(paths.analysesDir, `${manifest.sourceId}.json`), { force: true });
|
|
3638
3880
|
}
|
|
3639
3881
|
function repoSyncWorkspaceIgnorePaths(rootDir, paths, repoRoot) {
|
|
3640
3882
|
const candidates = [
|
|
@@ -3643,14 +3885,14 @@ function repoSyncWorkspaceIgnorePaths(rootDir, paths, repoRoot) {
|
|
|
3643
3885
|
paths.stateDir,
|
|
3644
3886
|
paths.agentDir,
|
|
3645
3887
|
paths.inboxDir,
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3888
|
+
path9.join(rootDir, ".claude"),
|
|
3889
|
+
path9.join(rootDir, ".cursor"),
|
|
3890
|
+
path9.join(rootDir, ".obsidian")
|
|
3649
3891
|
];
|
|
3650
|
-
return candidates.map((candidate) =>
|
|
3892
|
+
return candidates.map((candidate) => path9.resolve(candidate)).filter((candidate, index, items) => items.indexOf(candidate) === index).filter((candidate) => withinRoot(repoRoot, candidate));
|
|
3651
3893
|
}
|
|
3652
3894
|
function preparedMatchesManifest(manifest, prepared, contentHash) {
|
|
3653
|
-
return manifest.contentHash === contentHash && manifest.title === prepared.title && manifest.sourceKind === prepared.sourceKind && manifest.language === prepared.language && manifest.mimeType === prepared.mimeType && manifest.repoRelativePath === prepared.repoRelativePath;
|
|
3895
|
+
return manifest.contentHash === contentHash && manifest.extractionHash === (prepared.extractionHash ?? buildExtractionHash(prepared.extractedText, prepared.extractionArtifact)) && manifest.title === prepared.title && manifest.sourceKind === prepared.sourceKind && manifest.language === prepared.language && manifest.mimeType === prepared.mimeType && manifest.repoRelativePath === prepared.repoRelativePath;
|
|
3654
3896
|
}
|
|
3655
3897
|
function shouldDeferWatchSemanticRefresh(sourceKind) {
|
|
3656
3898
|
return sourceKind === "markdown" || sourceKind === "text" || sourceKind === "html" || sourceKind === "pdf" || sourceKind === "image";
|
|
@@ -3669,16 +3911,16 @@ async function syncTrackedRepos(rootDir, options, repoRoots) {
|
|
|
3669
3911
|
const normalizedOptions = normalizeIngestOptions(options);
|
|
3670
3912
|
const manifests = await listManifests(rootDir);
|
|
3671
3913
|
const trackedRoots = (repoRoots && repoRoots.length > 0 ? repoRoots : await listTrackedRepoRoots(rootDir)).map(
|
|
3672
|
-
(item) =>
|
|
3914
|
+
(item) => path9.resolve(item)
|
|
3673
3915
|
);
|
|
3674
3916
|
const uniqueRoots = [...new Set(trackedRoots)].sort((left, right) => left.localeCompare(right));
|
|
3675
3917
|
const manifestsByRepoRoot = /* @__PURE__ */ new Map();
|
|
3676
3918
|
for (const manifest of manifests) {
|
|
3677
3919
|
const repoRoot = repoRootFromManifest(manifest);
|
|
3678
|
-
if (!repoRoot || !uniqueRoots.includes(
|
|
3920
|
+
if (!repoRoot || !uniqueRoots.includes(path9.resolve(repoRoot))) {
|
|
3679
3921
|
continue;
|
|
3680
3922
|
}
|
|
3681
|
-
const key =
|
|
3923
|
+
const key = path9.resolve(repoRoot);
|
|
3682
3924
|
const bucket = manifestsByRepoRoot.get(key) ?? [];
|
|
3683
3925
|
bucket.push(manifest);
|
|
3684
3926
|
manifestsByRepoRoot.set(key, bucket);
|
|
@@ -3703,12 +3945,12 @@ async function syncTrackedRepos(rootDir, options, repoRoots) {
|
|
|
3703
3945
|
skipped.push(
|
|
3704
3946
|
...collected.skipped,
|
|
3705
3947
|
...collected.files.filter((absolutePath) => ignoreRoots.some((ignoreRoot) => withinRoot(ignoreRoot, absolutePath))).map((absolutePath) => ({
|
|
3706
|
-
path: toPosix(
|
|
3948
|
+
path: toPosix(path9.relative(rootDir, absolutePath)),
|
|
3707
3949
|
reason: "workspace_generated"
|
|
3708
3950
|
}))
|
|
3709
3951
|
);
|
|
3710
3952
|
scannedCount += files.length;
|
|
3711
|
-
const currentPaths = new Set(files.map((absolutePath) =>
|
|
3953
|
+
const currentPaths = new Set(files.map((absolutePath) => path9.resolve(absolutePath)));
|
|
3712
3954
|
for (const absolutePath of files) {
|
|
3713
3955
|
const prepared = await prepareFileInput(rootDir, absolutePath, repoRoot);
|
|
3714
3956
|
const result = await persistPreparedInput(rootDir, prepared, paths);
|
|
@@ -3719,7 +3961,7 @@ async function syncTrackedRepos(rootDir, options, repoRoots) {
|
|
|
3719
3961
|
}
|
|
3720
3962
|
}
|
|
3721
3963
|
for (const manifest of repoManifests) {
|
|
3722
|
-
const originalPath = manifest.originalPath ?
|
|
3964
|
+
const originalPath = manifest.originalPath ? path9.resolve(manifest.originalPath) : null;
|
|
3723
3965
|
if (originalPath && !currentPaths.has(originalPath)) {
|
|
3724
3966
|
await removeManifestArtifacts(rootDir, manifest, paths);
|
|
3725
3967
|
removed.push(manifest);
|
|
@@ -3727,7 +3969,7 @@ async function syncTrackedRepos(rootDir, options, repoRoots) {
|
|
|
3727
3969
|
}
|
|
3728
3970
|
}
|
|
3729
3971
|
if (uniqueRoots.length > 0) {
|
|
3730
|
-
await appendLogEntry(rootDir, "sync_repo", uniqueRoots.map((repoRoot) => toPosix(
|
|
3972
|
+
await appendLogEntry(rootDir, "sync_repo", uniqueRoots.map((repoRoot) => toPosix(path9.relative(rootDir, repoRoot)) || ".").join(","), [
|
|
3731
3973
|
`repo_roots=${uniqueRoots.length}`,
|
|
3732
3974
|
`scanned=${scannedCount}`,
|
|
3733
3975
|
`imported=${imported.length}`,
|
|
@@ -3750,16 +3992,16 @@ async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
|
|
|
3750
3992
|
const normalizedOptions = normalizeIngestOptions(options);
|
|
3751
3993
|
const manifests = await listManifests(rootDir);
|
|
3752
3994
|
const trackedRoots = (repoRoots && repoRoots.length > 0 ? repoRoots : await listTrackedRepoRoots(rootDir)).map(
|
|
3753
|
-
(item) =>
|
|
3995
|
+
(item) => path9.resolve(item)
|
|
3754
3996
|
);
|
|
3755
3997
|
const uniqueRoots = [...new Set(trackedRoots)].sort((left, right) => left.localeCompare(right));
|
|
3756
3998
|
const manifestsByRepoRoot = /* @__PURE__ */ new Map();
|
|
3757
3999
|
for (const manifest of manifests) {
|
|
3758
4000
|
const repoRoot = repoRootFromManifest(manifest);
|
|
3759
|
-
if (!repoRoot || !uniqueRoots.includes(
|
|
4001
|
+
if (!repoRoot || !uniqueRoots.includes(path9.resolve(repoRoot))) {
|
|
3760
4002
|
continue;
|
|
3761
4003
|
}
|
|
3762
|
-
const key =
|
|
4004
|
+
const key = path9.resolve(repoRoot);
|
|
3763
4005
|
const bucket = manifestsByRepoRoot.get(key) ?? [];
|
|
3764
4006
|
bucket.push(manifest);
|
|
3765
4007
|
manifestsByRepoRoot.set(key, bucket);
|
|
@@ -3774,7 +4016,7 @@ async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
|
|
|
3774
4016
|
for (const repoRoot of uniqueRoots) {
|
|
3775
4017
|
const repoManifests = manifestsByRepoRoot.get(repoRoot) ?? [];
|
|
3776
4018
|
const manifestsByOriginalPath = new Map(
|
|
3777
|
-
repoManifests.filter((manifest) => manifest.originalPath).map((manifest) => [
|
|
4019
|
+
repoManifests.filter((manifest) => manifest.originalPath).map((manifest) => [path9.resolve(manifest.originalPath), manifest])
|
|
3778
4020
|
);
|
|
3779
4021
|
if (!await fileExists(repoRoot)) {
|
|
3780
4022
|
for (const manifest of repoManifests) {
|
|
@@ -3782,7 +4024,7 @@ async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
|
|
|
3782
4024
|
pendingSemanticRefresh.push({
|
|
3783
4025
|
id: pendingSemanticRefreshId("removed", repoRoot, manifest.repoRelativePath ?? manifest.storedPath),
|
|
3784
4026
|
repoRoot,
|
|
3785
|
-
path: toPosix(
|
|
4027
|
+
path: toPosix(path9.relative(rootDir, manifest.originalPath ?? manifest.storedPath)),
|
|
3786
4028
|
changeType: "removed",
|
|
3787
4029
|
detectedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3788
4030
|
sourceId: manifest.sourceId,
|
|
@@ -3802,16 +4044,16 @@ async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
|
|
|
3802
4044
|
skipped.push(
|
|
3803
4045
|
...collected.skipped,
|
|
3804
4046
|
...collected.files.filter((absolutePath) => ignoreRoots.some((ignoreRoot) => withinRoot(ignoreRoot, absolutePath))).map((absolutePath) => ({
|
|
3805
|
-
path: toPosix(
|
|
4047
|
+
path: toPosix(path9.relative(rootDir, absolutePath)),
|
|
3806
4048
|
reason: "workspace_generated"
|
|
3807
4049
|
}))
|
|
3808
4050
|
);
|
|
3809
4051
|
scannedCount += files.length;
|
|
3810
|
-
const currentPaths = new Set(files.map((absolutePath) =>
|
|
4052
|
+
const currentPaths = new Set(files.map((absolutePath) => path9.resolve(absolutePath)));
|
|
3811
4053
|
for (const absolutePath of files) {
|
|
3812
4054
|
const prepared = await prepareFileInput(rootDir, absolutePath, repoRoot);
|
|
3813
4055
|
if (shouldDeferWatchSemanticRefresh(prepared.sourceKind)) {
|
|
3814
|
-
const existing = manifestsByOriginalPath.get(
|
|
4056
|
+
const existing = manifestsByOriginalPath.get(path9.resolve(absolutePath));
|
|
3815
4057
|
const contentHash = buildCompositeHash(prepared.payloadBytes, prepared.attachments);
|
|
3816
4058
|
const changed = !existing || !preparedMatchesManifest(existing, prepared, contentHash);
|
|
3817
4059
|
if (changed) {
|
|
@@ -3819,10 +4061,10 @@ async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
|
|
|
3819
4061
|
id: pendingSemanticRefreshId(
|
|
3820
4062
|
existing ? "modified" : "added",
|
|
3821
4063
|
repoRoot,
|
|
3822
|
-
prepared.repoRelativePath ?? toPosix(
|
|
4064
|
+
prepared.repoRelativePath ?? toPosix(path9.relative(repoRoot, absolutePath))
|
|
3823
4065
|
),
|
|
3824
4066
|
repoRoot,
|
|
3825
|
-
path: toPosix(
|
|
4067
|
+
path: toPosix(path9.relative(rootDir, absolutePath)),
|
|
3826
4068
|
changeType: existing ? "modified" : "added",
|
|
3827
4069
|
detectedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3828
4070
|
sourceId: existing?.sourceId,
|
|
@@ -3842,13 +4084,13 @@ async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
|
|
|
3842
4084
|
}
|
|
3843
4085
|
}
|
|
3844
4086
|
for (const manifest of repoManifests) {
|
|
3845
|
-
const originalPath = manifest.originalPath ?
|
|
4087
|
+
const originalPath = manifest.originalPath ? path9.resolve(manifest.originalPath) : null;
|
|
3846
4088
|
if (originalPath && !currentPaths.has(originalPath)) {
|
|
3847
4089
|
if (shouldDeferWatchSemanticRefresh(manifest.sourceKind)) {
|
|
3848
4090
|
pendingSemanticRefresh.push({
|
|
3849
|
-
id: pendingSemanticRefreshId("removed", repoRoot, manifest.repoRelativePath ?? toPosix(
|
|
4091
|
+
id: pendingSemanticRefreshId("removed", repoRoot, manifest.repoRelativePath ?? toPosix(path9.relative(repoRoot, originalPath))),
|
|
3850
4092
|
repoRoot,
|
|
3851
|
-
path: toPosix(
|
|
4093
|
+
path: toPosix(path9.relative(rootDir, originalPath)),
|
|
3852
4094
|
changeType: "removed",
|
|
3853
4095
|
detectedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3854
4096
|
sourceId: manifest.sourceId,
|
|
@@ -3866,7 +4108,7 @@ async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
|
|
|
3866
4108
|
await appendLogEntry(
|
|
3867
4109
|
rootDir,
|
|
3868
4110
|
"sync_repo_watch",
|
|
3869
|
-
uniqueRoots.map((repoRoot) => toPosix(
|
|
4111
|
+
uniqueRoots.map((repoRoot) => toPosix(path9.relative(rootDir, repoRoot)) || ".").join(","),
|
|
3870
4112
|
[
|
|
3871
4113
|
`repo_roots=${uniqueRoots.length}`,
|
|
3872
4114
|
`scanned=${scannedCount}`,
|
|
@@ -3891,19 +4133,36 @@ async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
|
|
|
3891
4133
|
staleSourceIds: [...staleSourceIds]
|
|
3892
4134
|
};
|
|
3893
4135
|
}
|
|
3894
|
-
async function prepareFileInput(
|
|
3895
|
-
const payloadBytes = await
|
|
4136
|
+
async function prepareFileInput(rootDir, absoluteInput, repoRoot) {
|
|
4137
|
+
const payloadBytes = await fs9.readFile(absoluteInput);
|
|
3896
4138
|
const mimeType = guessMimeType(absoluteInput);
|
|
3897
4139
|
const sourceKind = inferKind(mimeType, absoluteInput);
|
|
3898
4140
|
const language = inferCodeLanguage(absoluteInput, mimeType);
|
|
3899
|
-
const storedExtension =
|
|
4141
|
+
const storedExtension = path9.extname(absoluteInput) || `.${mime.extension(mimeType) || "bin"}`;
|
|
3900
4142
|
let title;
|
|
3901
4143
|
let extractedText;
|
|
4144
|
+
let extractionArtifact;
|
|
3902
4145
|
if (sourceKind === "markdown" || sourceKind === "text" || sourceKind === "code") {
|
|
3903
4146
|
extractedText = payloadBytes.toString("utf8");
|
|
3904
|
-
title = titleFromText(
|
|
4147
|
+
title = titleFromText(path9.basename(absoluteInput, path9.extname(absoluteInput)), extractedText);
|
|
4148
|
+
extractionArtifact = createPlainTextExtractionArtifact(sourceKind, mimeType);
|
|
4149
|
+
} else if (sourceKind === "pdf") {
|
|
4150
|
+
title = path9.basename(absoluteInput, path9.extname(absoluteInput));
|
|
4151
|
+
const extracted = await extractPdfText({ mimeType, bytes: payloadBytes });
|
|
4152
|
+
extractedText = extracted.extractedText;
|
|
4153
|
+
extractionArtifact = extracted.artifact;
|
|
4154
|
+
} else if (sourceKind === "image") {
|
|
4155
|
+
title = path9.basename(absoluteInput, path9.extname(absoluteInput));
|
|
4156
|
+
const extracted = await extractImageWithVision(rootDir, {
|
|
4157
|
+
title,
|
|
4158
|
+
mimeType,
|
|
4159
|
+
filePath: absoluteInput
|
|
4160
|
+
});
|
|
4161
|
+
title = extracted.title?.trim() || title;
|
|
4162
|
+
extractedText = extracted.extractedText;
|
|
4163
|
+
extractionArtifact = extracted.artifact;
|
|
3905
4164
|
} else {
|
|
3906
|
-
title =
|
|
4165
|
+
title = path9.basename(absoluteInput, path9.extname(absoluteInput));
|
|
3907
4166
|
}
|
|
3908
4167
|
return {
|
|
3909
4168
|
title,
|
|
@@ -3915,10 +4174,12 @@ async function prepareFileInput(_rootDir, absoluteInput, repoRoot) {
|
|
|
3915
4174
|
mimeType,
|
|
3916
4175
|
storedExtension,
|
|
3917
4176
|
payloadBytes,
|
|
3918
|
-
extractedText
|
|
4177
|
+
extractedText,
|
|
4178
|
+
extractionArtifact,
|
|
4179
|
+
extractionHash: buildExtractionHash(extractedText, extractionArtifact)
|
|
3919
4180
|
};
|
|
3920
4181
|
}
|
|
3921
|
-
async function prepareUrlInput(input, options) {
|
|
4182
|
+
async function prepareUrlInput(rootDir, input, options) {
|
|
3922
4183
|
const response = await fetch(input);
|
|
3923
4184
|
if (!response.ok) {
|
|
3924
4185
|
throw new Error(`Failed to fetch ${input}: ${response.status} ${response.statusText}`);
|
|
@@ -3932,6 +4193,7 @@ async function prepareUrlInput(input, options) {
|
|
|
3932
4193
|
let storedExtension = ".bin";
|
|
3933
4194
|
let title = inputUrl.hostname + inputUrl.pathname;
|
|
3934
4195
|
let extractedText;
|
|
4196
|
+
let extractionArtifact;
|
|
3935
4197
|
let attachments;
|
|
3936
4198
|
let contentHash;
|
|
3937
4199
|
const logDetails = [];
|
|
@@ -3962,6 +4224,7 @@ async function prepareUrlInput(input, options) {
|
|
|
3962
4224
|
}
|
|
3963
4225
|
const converted = localizedHtml === html && !attachments?.length ? initialConversion : await convertHtmlToMarkdown(localizedHtml, input);
|
|
3964
4226
|
extractedText = converted.markdown;
|
|
4227
|
+
extractionArtifact = createHtmlReadabilityExtractionArtifact("markdown", "text/markdown");
|
|
3965
4228
|
if (localAssetReplacements?.size) {
|
|
3966
4229
|
const absoluteLocalAssetReplacements = new Map(
|
|
3967
4230
|
[...localAssetReplacements.values()].map((replacement) => [new URL(replacement, input).toString(), replacement])
|
|
@@ -3973,11 +4236,12 @@ async function prepareUrlInput(input, options) {
|
|
|
3973
4236
|
sourceKind = "markdown";
|
|
3974
4237
|
storedExtension = ".md";
|
|
3975
4238
|
} else {
|
|
3976
|
-
const extension =
|
|
4239
|
+
const extension = path9.extname(inputUrl.pathname);
|
|
3977
4240
|
storedExtension = extension || `.${mime.extension(mimeType) || "bin"}`;
|
|
3978
4241
|
if (sourceKind === "markdown" || sourceKind === "text" || sourceKind === "code") {
|
|
3979
4242
|
extractedText = payloadBytes.toString("utf8");
|
|
3980
4243
|
title = titleFromText(title || inputUrl.hostname, extractedText);
|
|
4244
|
+
extractionArtifact = createPlainTextExtractionArtifact(sourceKind, mimeType);
|
|
3981
4245
|
if (sourceKind === "markdown" && options.includeAssets) {
|
|
3982
4246
|
const { attachments: remoteAttachments, skippedCount } = await collectRemoteImageAttachments(
|
|
3983
4247
|
extractMarkdownImageReferences(extractedText, input),
|
|
@@ -3998,6 +4262,19 @@ async function prepareUrlInput(input, options) {
|
|
|
3998
4262
|
logDetails.push(`remote_asset_skips=${skippedCount}`);
|
|
3999
4263
|
}
|
|
4000
4264
|
}
|
|
4265
|
+
} else if (sourceKind === "pdf") {
|
|
4266
|
+
const extracted = await extractPdfText({ mimeType, bytes: payloadBytes });
|
|
4267
|
+
extractedText = extracted.extractedText;
|
|
4268
|
+
extractionArtifact = extracted.artifact;
|
|
4269
|
+
} else if (sourceKind === "image") {
|
|
4270
|
+
const extracted = await extractImageWithVision(rootDir, {
|
|
4271
|
+
title,
|
|
4272
|
+
mimeType,
|
|
4273
|
+
bytes: payloadBytes
|
|
4274
|
+
});
|
|
4275
|
+
title = extracted.title?.trim() || title;
|
|
4276
|
+
extractedText = extracted.extractedText;
|
|
4277
|
+
extractionArtifact = extracted.artifact;
|
|
4001
4278
|
}
|
|
4002
4279
|
}
|
|
4003
4280
|
return {
|
|
@@ -4010,6 +4287,8 @@ async function prepareUrlInput(input, options) {
|
|
|
4010
4287
|
storedExtension,
|
|
4011
4288
|
payloadBytes,
|
|
4012
4289
|
extractedText,
|
|
4290
|
+
extractionArtifact,
|
|
4291
|
+
extractionHash: buildExtractionHash(extractedText, extractionArtifact),
|
|
4013
4292
|
attachments,
|
|
4014
4293
|
contentHash,
|
|
4015
4294
|
logDetails
|
|
@@ -4023,14 +4302,14 @@ async function collectInboxAttachmentRefs(inputDir, files) {
|
|
|
4023
4302
|
if (sourceKind !== "markdown") {
|
|
4024
4303
|
continue;
|
|
4025
4304
|
}
|
|
4026
|
-
const content = await
|
|
4305
|
+
const content = await fs9.readFile(absolutePath, "utf8");
|
|
4027
4306
|
const refs = extractMarkdownReferences(content);
|
|
4028
4307
|
if (!refs.length) {
|
|
4029
4308
|
continue;
|
|
4030
4309
|
}
|
|
4031
4310
|
const sourceRefs = [];
|
|
4032
4311
|
for (const ref of refs) {
|
|
4033
|
-
const resolved =
|
|
4312
|
+
const resolved = path9.resolve(path9.dirname(absolutePath), ref);
|
|
4034
4313
|
if (!resolved.startsWith(inputDir) || !await fileExists(resolved)) {
|
|
4035
4314
|
continue;
|
|
4036
4315
|
}
|
|
@@ -4064,12 +4343,12 @@ function rewriteMarkdownReferences(content, replacements) {
|
|
|
4064
4343
|
});
|
|
4065
4344
|
}
|
|
4066
4345
|
async function prepareInboxMarkdownInput(absolutePath, attachmentRefs) {
|
|
4067
|
-
const originalBytes = await
|
|
4346
|
+
const originalBytes = await fs9.readFile(absolutePath);
|
|
4068
4347
|
const originalText = originalBytes.toString("utf8");
|
|
4069
|
-
const title = titleFromText(
|
|
4348
|
+
const title = titleFromText(path9.basename(absolutePath, path9.extname(absolutePath)), originalText);
|
|
4070
4349
|
const attachments = [];
|
|
4071
4350
|
for (const attachmentRef of attachmentRefs) {
|
|
4072
|
-
const bytes = await
|
|
4351
|
+
const bytes = await fs9.readFile(attachmentRef.absolutePath);
|
|
4073
4352
|
attachments.push({
|
|
4074
4353
|
relativePath: sanitizeAssetRelativePath(attachmentRef.relativeRef),
|
|
4075
4354
|
mimeType: guessMimeType(attachmentRef.absolutePath),
|
|
@@ -4086,15 +4365,18 @@ async function prepareInboxMarkdownInput(absolutePath, attachmentRefs) {
|
|
|
4086
4365
|
])
|
|
4087
4366
|
);
|
|
4088
4367
|
const rewrittenText = rewriteMarkdownReferences(originalText, replacements);
|
|
4368
|
+
const extractionArtifact = createPlainTextExtractionArtifact("markdown", "text/markdown");
|
|
4089
4369
|
return {
|
|
4090
4370
|
title,
|
|
4091
4371
|
originType: "file",
|
|
4092
4372
|
sourceKind: "markdown",
|
|
4093
4373
|
originalPath: toPosix(absolutePath),
|
|
4094
4374
|
mimeType: "text/markdown",
|
|
4095
|
-
storedExtension:
|
|
4375
|
+
storedExtension: path9.extname(absolutePath) || ".md",
|
|
4096
4376
|
payloadBytes: Buffer.from(rewrittenText, "utf8"),
|
|
4097
4377
|
extractedText: rewrittenText,
|
|
4378
|
+
extractionArtifact,
|
|
4379
|
+
extractionHash: buildExtractionHash(rewrittenText, extractionArtifact),
|
|
4098
4380
|
attachments,
|
|
4099
4381
|
contentHash
|
|
4100
4382
|
};
|
|
@@ -4105,9 +4387,9 @@ function isSupportedInboxKind(sourceKind) {
|
|
|
4105
4387
|
async function ingestInput(rootDir, input, options) {
|
|
4106
4388
|
const { paths } = await initWorkspace(rootDir);
|
|
4107
4389
|
const normalizedOptions = normalizeIngestOptions(options);
|
|
4108
|
-
const absoluteInput =
|
|
4109
|
-
const repoRoot = isHttpUrl(input) || normalizedOptions.repoRoot ? normalizedOptions.repoRoot : await findNearestGitRoot2(absoluteInput).then((value) => value ??
|
|
4110
|
-
const prepared = isHttpUrl(input) ? await prepareUrlInput(input, normalizedOptions) : await prepareFileInput(rootDir, absoluteInput, repoRoot);
|
|
4390
|
+
const absoluteInput = path9.resolve(rootDir, input);
|
|
4391
|
+
const repoRoot = isHttpUrl(input) || normalizedOptions.repoRoot ? normalizedOptions.repoRoot : await findNearestGitRoot2(absoluteInput).then((value) => value ?? path9.dirname(absoluteInput));
|
|
4392
|
+
const prepared = isHttpUrl(input) ? await prepareUrlInput(rootDir, input, normalizedOptions) : await prepareFileInput(rootDir, absoluteInput, repoRoot);
|
|
4111
4393
|
const result = await persistPreparedInput(rootDir, prepared, paths);
|
|
4112
4394
|
return result.manifest;
|
|
4113
4395
|
}
|
|
@@ -4167,7 +4449,7 @@ async function addInput(rootDir, input, options = {}) {
|
|
|
4167
4449
|
async function ingestDirectory(rootDir, inputDir, options) {
|
|
4168
4450
|
const { paths } = await initWorkspace(rootDir);
|
|
4169
4451
|
const normalizedOptions = normalizeIngestOptions(options);
|
|
4170
|
-
const absoluteInputDir =
|
|
4452
|
+
const absoluteInputDir = path9.resolve(rootDir, inputDir);
|
|
4171
4453
|
const repoRoot = normalizedOptions.repoRoot ?? await findNearestGitRoot2(absoluteInputDir) ?? absoluteInputDir;
|
|
4172
4454
|
if (!await fileExists(absoluteInputDir)) {
|
|
4173
4455
|
throw new Error(`Directory not found: ${absoluteInputDir}`);
|
|
@@ -4183,11 +4465,11 @@ async function ingestDirectory(rootDir, inputDir, options) {
|
|
|
4183
4465
|
} else if (result.wasUpdated) {
|
|
4184
4466
|
updated.push(result.manifest);
|
|
4185
4467
|
} else {
|
|
4186
|
-
skipped.push({ path: toPosix(
|
|
4468
|
+
skipped.push({ path: toPosix(path9.relative(rootDir, absolutePath)), reason: "duplicate_content" });
|
|
4187
4469
|
}
|
|
4188
4470
|
}
|
|
4189
|
-
await appendLogEntry(rootDir, "ingest_directory", toPosix(
|
|
4190
|
-
`repo_root=${toPosix(
|
|
4471
|
+
await appendLogEntry(rootDir, "ingest_directory", toPosix(path9.relative(rootDir, absoluteInputDir)) || ".", [
|
|
4472
|
+
`repo_root=${toPosix(path9.relative(rootDir, repoRoot)) || "."}`,
|
|
4191
4473
|
`scanned=${files.length}`,
|
|
4192
4474
|
`imported=${imported.length}`,
|
|
4193
4475
|
`updated=${updated.length}`,
|
|
@@ -4204,7 +4486,7 @@ async function ingestDirectory(rootDir, inputDir, options) {
|
|
|
4204
4486
|
}
|
|
4205
4487
|
async function importInbox(rootDir, inputDir) {
|
|
4206
4488
|
const { paths } = await initWorkspace(rootDir);
|
|
4207
|
-
const effectiveInputDir =
|
|
4489
|
+
const effectiveInputDir = path9.resolve(rootDir, inputDir ?? paths.inboxDir);
|
|
4208
4490
|
if (!await fileExists(effectiveInputDir)) {
|
|
4209
4491
|
throw new Error(`Inbox directory not found: ${effectiveInputDir}`);
|
|
4210
4492
|
}
|
|
@@ -4215,31 +4497,31 @@ async function importInbox(rootDir, inputDir) {
|
|
|
4215
4497
|
const skipped = [];
|
|
4216
4498
|
let attachmentCount = 0;
|
|
4217
4499
|
for (const absolutePath of files) {
|
|
4218
|
-
const basename =
|
|
4500
|
+
const basename = path9.basename(absolutePath);
|
|
4219
4501
|
if (basename.startsWith(".")) {
|
|
4220
|
-
skipped.push({ path: toPosix(
|
|
4502
|
+
skipped.push({ path: toPosix(path9.relative(rootDir, absolutePath)), reason: "hidden_file" });
|
|
4221
4503
|
continue;
|
|
4222
4504
|
}
|
|
4223
4505
|
if (claimedAttachments.has(absolutePath)) {
|
|
4224
|
-
skipped.push({ path: toPosix(
|
|
4506
|
+
skipped.push({ path: toPosix(path9.relative(rootDir, absolutePath)), reason: "referenced_attachment" });
|
|
4225
4507
|
continue;
|
|
4226
4508
|
}
|
|
4227
4509
|
const mimeType = guessMimeType(absolutePath);
|
|
4228
4510
|
const sourceKind = inferKind(mimeType, absolutePath);
|
|
4229
4511
|
if (!isSupportedInboxKind(sourceKind)) {
|
|
4230
|
-
skipped.push({ path: toPosix(
|
|
4512
|
+
skipped.push({ path: toPosix(path9.relative(rootDir, absolutePath)), reason: `unsupported_kind:${sourceKind}` });
|
|
4231
4513
|
continue;
|
|
4232
4514
|
}
|
|
4233
4515
|
const prepared = sourceKind === "markdown" && refsBySource.has(absolutePath) ? await prepareInboxMarkdownInput(absolutePath, refsBySource.get(absolutePath) ?? []) : await prepareFileInput(rootDir, absolutePath);
|
|
4234
4516
|
const result = await persistPreparedInput(rootDir, prepared, paths);
|
|
4235
4517
|
if (!result.isNew) {
|
|
4236
|
-
skipped.push({ path: toPosix(
|
|
4518
|
+
skipped.push({ path: toPosix(path9.relative(rootDir, absolutePath)), reason: "duplicate_content" });
|
|
4237
4519
|
continue;
|
|
4238
4520
|
}
|
|
4239
4521
|
attachmentCount += result.manifest.attachments?.length ?? 0;
|
|
4240
4522
|
imported.push(result.manifest);
|
|
4241
4523
|
}
|
|
4242
|
-
await appendLogEntry(rootDir, "inbox_import", toPosix(
|
|
4524
|
+
await appendLogEntry(rootDir, "inbox_import", toPosix(path9.relative(rootDir, effectiveInputDir)) || ".", [
|
|
4243
4525
|
`scanned=${files.length}`,
|
|
4244
4526
|
`imported=${imported.length}`,
|
|
4245
4527
|
`attachments=${attachmentCount}`,
|
|
@@ -4258,9 +4540,9 @@ async function listManifests(rootDir) {
|
|
|
4258
4540
|
if (!await fileExists(paths.manifestsDir)) {
|
|
4259
4541
|
return [];
|
|
4260
4542
|
}
|
|
4261
|
-
const entries = await
|
|
4543
|
+
const entries = await fs9.readdir(paths.manifestsDir);
|
|
4262
4544
|
const manifests = await Promise.all(
|
|
4263
|
-
entries.filter((entry) => entry.endsWith(".json")).map((entry) => readJsonFile(
|
|
4545
|
+
entries.filter((entry) => entry.endsWith(".json")).map((entry) => readJsonFile(path9.join(paths.manifestsDir, entry)))
|
|
4264
4546
|
);
|
|
4265
4547
|
return manifests.filter((manifest) => Boolean(manifest));
|
|
4266
4548
|
}
|
|
@@ -4268,28 +4550,38 @@ async function readExtractedText(rootDir, manifest) {
|
|
|
4268
4550
|
if (!manifest.extractedTextPath) {
|
|
4269
4551
|
return void 0;
|
|
4270
4552
|
}
|
|
4271
|
-
const absolutePath =
|
|
4553
|
+
const absolutePath = path9.resolve(rootDir, manifest.extractedTextPath);
|
|
4554
|
+
if (!await fileExists(absolutePath)) {
|
|
4555
|
+
return void 0;
|
|
4556
|
+
}
|
|
4557
|
+
return fs9.readFile(absolutePath, "utf8");
|
|
4558
|
+
}
|
|
4559
|
+
async function readExtractionArtifact(rootDir, manifest) {
|
|
4560
|
+
if (!manifest.extractedMetadataPath) {
|
|
4561
|
+
return void 0;
|
|
4562
|
+
}
|
|
4563
|
+
const absolutePath = path9.resolve(rootDir, manifest.extractedMetadataPath);
|
|
4272
4564
|
if (!await fileExists(absolutePath)) {
|
|
4273
4565
|
return void 0;
|
|
4274
4566
|
}
|
|
4275
|
-
return
|
|
4567
|
+
return await readJsonFile(absolutePath) ?? void 0;
|
|
4276
4568
|
}
|
|
4277
4569
|
|
|
4278
4570
|
// src/mcp.ts
|
|
4279
|
-
import
|
|
4280
|
-
import
|
|
4571
|
+
import fs16 from "fs/promises";
|
|
4572
|
+
import path19 from "path";
|
|
4281
4573
|
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4282
4574
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4283
|
-
import { z as
|
|
4575
|
+
import { z as z8 } from "zod";
|
|
4284
4576
|
|
|
4285
4577
|
// src/schema.ts
|
|
4286
|
-
import
|
|
4287
|
-
import
|
|
4578
|
+
import fs10 from "fs/promises";
|
|
4579
|
+
import path10 from "path";
|
|
4288
4580
|
function normalizeSchemaContent(content) {
|
|
4289
4581
|
return content.trim() ? content.trim() : defaultVaultSchema().trim();
|
|
4290
4582
|
}
|
|
4291
4583
|
async function readSchemaFile(schemaPath, fallback = defaultVaultSchema()) {
|
|
4292
|
-
const content = await fileExists(schemaPath) ? await
|
|
4584
|
+
const content = await fileExists(schemaPath) ? await fs10.readFile(schemaPath, "utf8") : fallback;
|
|
4293
4585
|
const normalized = normalizeSchemaContent(content);
|
|
4294
4586
|
return {
|
|
4295
4587
|
path: schemaPath,
|
|
@@ -4298,7 +4590,7 @@ async function readSchemaFile(schemaPath, fallback = defaultVaultSchema()) {
|
|
|
4298
4590
|
};
|
|
4299
4591
|
}
|
|
4300
4592
|
function resolveProjectSchemaPath(rootDir, schemaPath) {
|
|
4301
|
-
return
|
|
4593
|
+
return path10.resolve(rootDir, schemaPath);
|
|
4302
4594
|
}
|
|
4303
4595
|
function composeVaultSchema(root, projectSchemas = []) {
|
|
4304
4596
|
if (!projectSchemas.length) {
|
|
@@ -4314,7 +4606,7 @@ function composeVaultSchema(root, projectSchemas = []) {
|
|
|
4314
4606
|
(schema) => [
|
|
4315
4607
|
`## Project Schema`,
|
|
4316
4608
|
"",
|
|
4317
|
-
`Path: ${toPosix(
|
|
4609
|
+
`Path: ${toPosix(path10.relative(path10.dirname(root.path), schema.path) || schema.path)}`,
|
|
4318
4610
|
"",
|
|
4319
4611
|
schema.content
|
|
4320
4612
|
].join("\n")
|
|
@@ -4390,30 +4682,30 @@ function buildSchemaPrompt(schema, instruction) {
|
|
|
4390
4682
|
}
|
|
4391
4683
|
|
|
4392
4684
|
// src/vault.ts
|
|
4393
|
-
import
|
|
4394
|
-
import
|
|
4685
|
+
import fs15 from "fs/promises";
|
|
4686
|
+
import path18 from "path";
|
|
4395
4687
|
import matter8 from "gray-matter";
|
|
4396
|
-
import { z as
|
|
4688
|
+
import { z as z7 } from "zod";
|
|
4397
4689
|
|
|
4398
4690
|
// src/analysis.ts
|
|
4399
|
-
import
|
|
4400
|
-
import { z } from "zod";
|
|
4401
|
-
var ANALYSIS_FORMAT_VERSION =
|
|
4402
|
-
var sourceAnalysisSchema =
|
|
4403
|
-
title:
|
|
4404
|
-
summary:
|
|
4405
|
-
concepts:
|
|
4406
|
-
entities:
|
|
4407
|
-
claims:
|
|
4408
|
-
|
|
4409
|
-
text:
|
|
4410
|
-
confidence:
|
|
4411
|
-
status:
|
|
4412
|
-
polarity:
|
|
4413
|
-
citation:
|
|
4691
|
+
import path11 from "path";
|
|
4692
|
+
import { z as z2 } from "zod";
|
|
4693
|
+
var ANALYSIS_FORMAT_VERSION = 5;
|
|
4694
|
+
var sourceAnalysisSchema = z2.object({
|
|
4695
|
+
title: z2.string().min(1),
|
|
4696
|
+
summary: z2.string().min(1),
|
|
4697
|
+
concepts: z2.array(z2.object({ name: z2.string().min(1), description: z2.string().default("") })).max(12).default([]),
|
|
4698
|
+
entities: z2.array(z2.object({ name: z2.string().min(1), description: z2.string().default("") })).max(12).default([]),
|
|
4699
|
+
claims: z2.array(
|
|
4700
|
+
z2.object({
|
|
4701
|
+
text: z2.string().min(1),
|
|
4702
|
+
confidence: z2.number().min(0).max(1).default(0.6),
|
|
4703
|
+
status: z2.enum(["extracted", "inferred", "conflicted", "stale"]).default("extracted"),
|
|
4704
|
+
polarity: z2.enum(["positive", "negative", "neutral"]).default("neutral"),
|
|
4705
|
+
citation: z2.string().min(1)
|
|
4414
4706
|
})
|
|
4415
4707
|
).max(8).default([]),
|
|
4416
|
-
questions:
|
|
4708
|
+
questions: z2.array(z2.string()).max(6).default([])
|
|
4417
4709
|
});
|
|
4418
4710
|
var STOPWORDS = /* @__PURE__ */ new Set([
|
|
4419
4711
|
"about",
|
|
@@ -4502,6 +4794,7 @@ function heuristicAnalysis(manifest, text, schemaHash) {
|
|
|
4502
4794
|
analysisVersion: ANALYSIS_FORMAT_VERSION,
|
|
4503
4795
|
sourceId: manifest.sourceId,
|
|
4504
4796
|
sourceHash: manifest.contentHash,
|
|
4797
|
+
extractionHash: manifest.extractionHash,
|
|
4505
4798
|
schemaHash,
|
|
4506
4799
|
title: deriveTitle(manifest, text),
|
|
4507
4800
|
summary: firstSentences(normalized, 3) || truncate(normalized, 280) || `Imported ${manifest.sourceKind} source.`,
|
|
@@ -4548,6 +4841,7 @@ ${truncate(text, 18e3)}`
|
|
|
4548
4841
|
analysisVersion: ANALYSIS_FORMAT_VERSION,
|
|
4549
4842
|
sourceId: manifest.sourceId,
|
|
4550
4843
|
sourceHash: manifest.contentHash,
|
|
4844
|
+
extractionHash: manifest.extractionHash,
|
|
4551
4845
|
schemaHash: schema.hash,
|
|
4552
4846
|
title: parsed.title,
|
|
4553
4847
|
summary: parsed.summary,
|
|
@@ -4574,24 +4868,97 @@ ${truncate(text, 18e3)}`
|
|
|
4574
4868
|
producedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4575
4869
|
};
|
|
4576
4870
|
}
|
|
4871
|
+
function analysisFromVisionExtraction(manifest, extraction, schemaHash) {
|
|
4872
|
+
if (!extraction.vision) {
|
|
4873
|
+
return null;
|
|
4874
|
+
}
|
|
4875
|
+
return {
|
|
4876
|
+
analysisVersion: ANALYSIS_FORMAT_VERSION,
|
|
4877
|
+
sourceId: manifest.sourceId,
|
|
4878
|
+
sourceHash: manifest.contentHash,
|
|
4879
|
+
extractionHash: manifest.extractionHash,
|
|
4880
|
+
schemaHash,
|
|
4881
|
+
title: extraction.vision.title?.trim() || manifest.title,
|
|
4882
|
+
summary: extraction.vision.summary,
|
|
4883
|
+
concepts: extraction.vision.concepts.map((term) => ({
|
|
4884
|
+
id: `concept:${slugify(term.name)}`,
|
|
4885
|
+
name: term.name,
|
|
4886
|
+
description: term.description
|
|
4887
|
+
})),
|
|
4888
|
+
entities: extraction.vision.entities.map((term) => ({
|
|
4889
|
+
id: `entity:${slugify(term.name)}`,
|
|
4890
|
+
name: term.name,
|
|
4891
|
+
description: term.description
|
|
4892
|
+
})),
|
|
4893
|
+
claims: extraction.vision.claims.map((claim, index) => ({
|
|
4894
|
+
id: `claim:${manifest.sourceId}:${index + 1}`,
|
|
4895
|
+
text: claim.text,
|
|
4896
|
+
confidence: claim.confidence,
|
|
4897
|
+
status: "extracted",
|
|
4898
|
+
polarity: claim.polarity,
|
|
4899
|
+
citation: manifest.sourceId
|
|
4900
|
+
})),
|
|
4901
|
+
questions: extraction.vision.questions,
|
|
4902
|
+
rationales: [],
|
|
4903
|
+
producedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4904
|
+
};
|
|
4905
|
+
}
|
|
4906
|
+
function extractionWarningSummary(manifest, extraction) {
|
|
4907
|
+
const warning = extraction?.warnings?.find(Boolean);
|
|
4908
|
+
if (warning) {
|
|
4909
|
+
return `Imported ${manifest.sourceKind} source. ${warning}`;
|
|
4910
|
+
}
|
|
4911
|
+
return `Imported ${manifest.sourceKind} source. Text extraction is not yet available for this source.`;
|
|
4912
|
+
}
|
|
4577
4913
|
async function analyzeSource(manifest, extractedText, provider, paths, schema) {
|
|
4578
|
-
const cachePath =
|
|
4914
|
+
const cachePath = path11.join(paths.analysesDir, `${manifest.sourceId}.json`);
|
|
4579
4915
|
const cached = await readJsonFile(cachePath);
|
|
4580
|
-
if (cached && cached.analysisVersion === ANALYSIS_FORMAT_VERSION && cached.sourceHash === manifest.contentHash && cached.schemaHash === schema.hash) {
|
|
4916
|
+
if (cached && cached.analysisVersion === ANALYSIS_FORMAT_VERSION && cached.sourceHash === manifest.contentHash && cached.extractionHash === manifest.extractionHash && cached.schemaHash === schema.hash) {
|
|
4581
4917
|
return cached;
|
|
4582
4918
|
}
|
|
4919
|
+
const extraction = await readExtractionArtifact(paths.rootDir, manifest);
|
|
4583
4920
|
const content = normalizeWhitespace(extractedText ?? "");
|
|
4584
4921
|
let analysis;
|
|
4585
4922
|
if (manifest.sourceKind === "code" && content) {
|
|
4586
4923
|
analysis = await analyzeCodeSource(manifest, extractedText ?? "", schema.hash);
|
|
4924
|
+
} else if (manifest.sourceKind === "image") {
|
|
4925
|
+
const visionAnalysis = extraction ? analysisFromVisionExtraction(manifest, extraction, schema.hash) : null;
|
|
4926
|
+
if (visionAnalysis) {
|
|
4927
|
+
analysis = visionAnalysis;
|
|
4928
|
+
} else if (!content) {
|
|
4929
|
+
analysis = {
|
|
4930
|
+
analysisVersion: ANALYSIS_FORMAT_VERSION,
|
|
4931
|
+
sourceId: manifest.sourceId,
|
|
4932
|
+
sourceHash: manifest.contentHash,
|
|
4933
|
+
extractionHash: manifest.extractionHash,
|
|
4934
|
+
schemaHash: schema.hash,
|
|
4935
|
+
title: manifest.title,
|
|
4936
|
+
summary: extractionWarningSummary(manifest, extraction),
|
|
4937
|
+
concepts: [],
|
|
4938
|
+
entities: [],
|
|
4939
|
+
claims: [],
|
|
4940
|
+
questions: [],
|
|
4941
|
+
rationales: [],
|
|
4942
|
+
producedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4943
|
+
};
|
|
4944
|
+
} else if (provider.type === "heuristic") {
|
|
4945
|
+
analysis = heuristicAnalysis(manifest, content, schema.hash);
|
|
4946
|
+
} else {
|
|
4947
|
+
try {
|
|
4948
|
+
analysis = await providerAnalysis(manifest, content, provider, schema);
|
|
4949
|
+
} catch {
|
|
4950
|
+
analysis = heuristicAnalysis(manifest, content, schema.hash);
|
|
4951
|
+
}
|
|
4952
|
+
}
|
|
4587
4953
|
} else if (!content) {
|
|
4588
4954
|
analysis = {
|
|
4589
4955
|
analysisVersion: ANALYSIS_FORMAT_VERSION,
|
|
4590
4956
|
sourceId: manifest.sourceId,
|
|
4591
4957
|
sourceHash: manifest.contentHash,
|
|
4958
|
+
extractionHash: manifest.extractionHash,
|
|
4592
4959
|
schemaHash: schema.hash,
|
|
4593
4960
|
title: manifest.title,
|
|
4594
|
-
summary:
|
|
4961
|
+
summary: extractionWarningSummary(manifest, extraction),
|
|
4595
4962
|
concepts: [],
|
|
4596
4963
|
entities: [],
|
|
4597
4964
|
claims: [],
|
|
@@ -4714,10 +5081,10 @@ function conflictConfidence(claimA, claimB) {
|
|
|
4714
5081
|
}
|
|
4715
5082
|
|
|
4716
5083
|
// src/deep-lint.ts
|
|
4717
|
-
import
|
|
4718
|
-
import
|
|
5084
|
+
import fs11 from "fs/promises";
|
|
5085
|
+
import path14 from "path";
|
|
4719
5086
|
import matter3 from "gray-matter";
|
|
4720
|
-
import { z as
|
|
5087
|
+
import { z as z5 } from "zod";
|
|
4721
5088
|
|
|
4722
5089
|
// src/findings.ts
|
|
4723
5090
|
function normalizeFindingSeverity(value) {
|
|
@@ -4736,25 +5103,25 @@ function normalizeFindingSeverity(value) {
|
|
|
4736
5103
|
|
|
4737
5104
|
// src/orchestration.ts
|
|
4738
5105
|
import { spawn } from "child_process";
|
|
4739
|
-
import
|
|
4740
|
-
import { z as
|
|
4741
|
-
var orchestrationRoleResultSchema =
|
|
4742
|
-
summary:
|
|
4743
|
-
findings:
|
|
4744
|
-
|
|
4745
|
-
severity:
|
|
4746
|
-
message:
|
|
4747
|
-
relatedPageIds:
|
|
4748
|
-
relatedSourceIds:
|
|
4749
|
-
suggestedQuery:
|
|
5106
|
+
import path12 from "path";
|
|
5107
|
+
import { z as z3 } from "zod";
|
|
5108
|
+
var orchestrationRoleResultSchema = z3.object({
|
|
5109
|
+
summary: z3.string().optional(),
|
|
5110
|
+
findings: z3.array(
|
|
5111
|
+
z3.object({
|
|
5112
|
+
severity: z3.string().optional().default("info"),
|
|
5113
|
+
message: z3.string().min(1),
|
|
5114
|
+
relatedPageIds: z3.array(z3.string()).optional(),
|
|
5115
|
+
relatedSourceIds: z3.array(z3.string()).optional(),
|
|
5116
|
+
suggestedQuery: z3.string().optional()
|
|
4750
5117
|
})
|
|
4751
5118
|
).default([]),
|
|
4752
|
-
questions:
|
|
4753
|
-
proposals:
|
|
4754
|
-
|
|
4755
|
-
path:
|
|
4756
|
-
content:
|
|
4757
|
-
reason:
|
|
5119
|
+
questions: z3.array(z3.string().min(1)).default([]),
|
|
5120
|
+
proposals: z3.array(
|
|
5121
|
+
z3.object({
|
|
5122
|
+
path: z3.string().min(1),
|
|
5123
|
+
content: z3.string().min(1),
|
|
5124
|
+
reason: z3.string().min(1)
|
|
4758
5125
|
})
|
|
4759
5126
|
).default([])
|
|
4760
5127
|
});
|
|
@@ -4829,7 +5196,7 @@ async function runProviderRole(rootDir, role, roleConfig, input) {
|
|
|
4829
5196
|
}
|
|
4830
5197
|
async function runCommandRole(rootDir, role, executor, input) {
|
|
4831
5198
|
const [command, ...args] = executor.command;
|
|
4832
|
-
const cwd = executor.cwd ?
|
|
5199
|
+
const cwd = executor.cwd ? path12.resolve(rootDir, executor.cwd) : rootDir;
|
|
4833
5200
|
const child = spawn(command, args, {
|
|
4834
5201
|
cwd,
|
|
4835
5202
|
env: {
|
|
@@ -4923,9 +5290,9 @@ function summarizeRoleQuestions(results) {
|
|
|
4923
5290
|
}
|
|
4924
5291
|
|
|
4925
5292
|
// src/web-search/registry.ts
|
|
4926
|
-
import
|
|
5293
|
+
import path13 from "path";
|
|
4927
5294
|
import { pathToFileURL } from "url";
|
|
4928
|
-
import { z as
|
|
5295
|
+
import { z as z4 } from "zod";
|
|
4929
5296
|
|
|
4930
5297
|
// src/web-search/http-json.ts
|
|
4931
5298
|
function deepGet(value, pathValue) {
|
|
@@ -5007,10 +5374,10 @@ var HttpJsonWebSearchAdapter = class {
|
|
|
5007
5374
|
};
|
|
5008
5375
|
|
|
5009
5376
|
// src/web-search/registry.ts
|
|
5010
|
-
var customWebSearchModuleSchema =
|
|
5011
|
-
createAdapter:
|
|
5012
|
-
input: [
|
|
5013
|
-
output:
|
|
5377
|
+
var customWebSearchModuleSchema = z4.object({
|
|
5378
|
+
createAdapter: z4.function({
|
|
5379
|
+
input: [z4.string(), z4.custom(), z4.string()],
|
|
5380
|
+
output: z4.promise(z4.custom())
|
|
5014
5381
|
})
|
|
5015
5382
|
});
|
|
5016
5383
|
async function createWebSearchAdapter(id, config, rootDir) {
|
|
@@ -5021,7 +5388,7 @@ async function createWebSearchAdapter(id, config, rootDir) {
|
|
|
5021
5388
|
if (!config.module) {
|
|
5022
5389
|
throw new Error(`Web search provider ${id} is type "custom" but no module path was configured.`);
|
|
5023
5390
|
}
|
|
5024
|
-
const resolvedModule =
|
|
5391
|
+
const resolvedModule = path13.isAbsolute(config.module) ? config.module : path13.resolve(rootDir, config.module);
|
|
5025
5392
|
const loaded = await import(pathToFileURL(resolvedModule).href);
|
|
5026
5393
|
const parsed = customWebSearchModuleSchema.parse(loaded);
|
|
5027
5394
|
return parsed.createAdapter(id, config, rootDir);
|
|
@@ -5045,15 +5412,15 @@ async function getWebSearchAdapterForTask(rootDir, task) {
|
|
|
5045
5412
|
}
|
|
5046
5413
|
|
|
5047
5414
|
// src/deep-lint.ts
|
|
5048
|
-
var deepLintResponseSchema =
|
|
5049
|
-
findings:
|
|
5050
|
-
|
|
5051
|
-
severity:
|
|
5052
|
-
code:
|
|
5053
|
-
message:
|
|
5054
|
-
relatedSourceIds:
|
|
5055
|
-
relatedPageIds:
|
|
5056
|
-
suggestedQuery:
|
|
5415
|
+
var deepLintResponseSchema = z5.object({
|
|
5416
|
+
findings: z5.array(
|
|
5417
|
+
z5.object({
|
|
5418
|
+
severity: z5.string().optional().default("info"),
|
|
5419
|
+
code: z5.enum(["coverage_gap", "contradiction_candidate", "missing_citation", "candidate_page", "follow_up_question"]),
|
|
5420
|
+
message: z5.string().min(1),
|
|
5421
|
+
relatedSourceIds: z5.array(z5.string()).default([]),
|
|
5422
|
+
relatedPageIds: z5.array(z5.string()).default([]),
|
|
5423
|
+
suggestedQuery: z5.string().optional()
|
|
5057
5424
|
})
|
|
5058
5425
|
).max(20)
|
|
5059
5426
|
});
|
|
@@ -5081,8 +5448,8 @@ async function loadContextPages(rootDir, graph) {
|
|
|
5081
5448
|
);
|
|
5082
5449
|
return Promise.all(
|
|
5083
5450
|
contextPages.slice(0, 18).map(async (page) => {
|
|
5084
|
-
const absolutePath =
|
|
5085
|
-
const raw = await
|
|
5451
|
+
const absolutePath = path14.join(paths.wikiDir, page.path);
|
|
5452
|
+
const raw = await fs11.readFile(absolutePath, "utf8").catch(() => "");
|
|
5086
5453
|
const parsed = matter3(raw);
|
|
5087
5454
|
return {
|
|
5088
5455
|
id: page.id,
|
|
@@ -5130,7 +5497,7 @@ function heuristicDeepFindings(contextPages, structuralFindings, graph) {
|
|
|
5130
5497
|
code: "missing_citation",
|
|
5131
5498
|
message: finding.message,
|
|
5132
5499
|
pagePath: finding.pagePath,
|
|
5133
|
-
suggestedQuery: finding.pagePath ? `Which sources support the claims in ${
|
|
5500
|
+
suggestedQuery: finding.pagePath ? `Which sources support the claims in ${path14.basename(finding.pagePath, ".md")}?` : void 0
|
|
5134
5501
|
});
|
|
5135
5502
|
}
|
|
5136
5503
|
for (const page of contextPages.filter((item) => item.kind === "source").slice(0, 3)) {
|
|
@@ -6653,49 +7020,49 @@ function buildExploreHubPage(input) {
|
|
|
6653
7020
|
}
|
|
6654
7021
|
|
|
6655
7022
|
// src/output-artifacts.ts
|
|
6656
|
-
import { z as
|
|
7023
|
+
import { z as z6 } from "zod";
|
|
6657
7024
|
function escapeXml(value) {
|
|
6658
7025
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
6659
7026
|
}
|
|
6660
7027
|
function clampNumber(value, min, max) {
|
|
6661
7028
|
return Math.min(max, Math.max(min, value));
|
|
6662
7029
|
}
|
|
6663
|
-
var chartSpecSchema =
|
|
6664
|
-
kind:
|
|
6665
|
-
title:
|
|
6666
|
-
subtitle:
|
|
6667
|
-
xLabel:
|
|
6668
|
-
yLabel:
|
|
6669
|
-
seriesLabel:
|
|
6670
|
-
data:
|
|
6671
|
-
|
|
6672
|
-
label:
|
|
6673
|
-
value:
|
|
7030
|
+
var chartSpecSchema = z6.object({
|
|
7031
|
+
kind: z6.enum(["bar", "line"]).default("bar"),
|
|
7032
|
+
title: z6.string().min(1),
|
|
7033
|
+
subtitle: z6.string().optional(),
|
|
7034
|
+
xLabel: z6.string().optional(),
|
|
7035
|
+
yLabel: z6.string().optional(),
|
|
7036
|
+
seriesLabel: z6.string().optional(),
|
|
7037
|
+
data: z6.array(
|
|
7038
|
+
z6.object({
|
|
7039
|
+
label: z6.string().min(1),
|
|
7040
|
+
value: z6.number().finite()
|
|
6674
7041
|
})
|
|
6675
7042
|
).min(2).max(12),
|
|
6676
|
-
notes:
|
|
7043
|
+
notes: z6.array(z6.string().min(1)).max(5).optional()
|
|
6677
7044
|
});
|
|
6678
|
-
var sceneSpecSchema =
|
|
6679
|
-
title:
|
|
6680
|
-
alt:
|
|
6681
|
-
background:
|
|
6682
|
-
width:
|
|
6683
|
-
height:
|
|
6684
|
-
elements:
|
|
6685
|
-
|
|
6686
|
-
kind:
|
|
6687
|
-
shape:
|
|
6688
|
-
x:
|
|
6689
|
-
y:
|
|
6690
|
-
width:
|
|
6691
|
-
height:
|
|
6692
|
-
radius:
|
|
6693
|
-
text:
|
|
6694
|
-
fontSize:
|
|
6695
|
-
fill:
|
|
6696
|
-
stroke:
|
|
6697
|
-
strokeWidth:
|
|
6698
|
-
opacity:
|
|
7045
|
+
var sceneSpecSchema = z6.object({
|
|
7046
|
+
title: z6.string().min(1),
|
|
7047
|
+
alt: z6.string().min(1),
|
|
7048
|
+
background: z6.string().optional(),
|
|
7049
|
+
width: z6.number().int().positive().max(2400).optional(),
|
|
7050
|
+
height: z6.number().int().positive().max(2400).optional(),
|
|
7051
|
+
elements: z6.array(
|
|
7052
|
+
z6.object({
|
|
7053
|
+
kind: z6.enum(["shape", "label"]),
|
|
7054
|
+
shape: z6.enum(["rect", "circle", "line"]).optional(),
|
|
7055
|
+
x: z6.number().finite(),
|
|
7056
|
+
y: z6.number().finite(),
|
|
7057
|
+
width: z6.number().finite().optional(),
|
|
7058
|
+
height: z6.number().finite().optional(),
|
|
7059
|
+
radius: z6.number().finite().optional(),
|
|
7060
|
+
text: z6.string().optional(),
|
|
7061
|
+
fontSize: z6.number().finite().optional(),
|
|
7062
|
+
fill: z6.string().optional(),
|
|
7063
|
+
stroke: z6.string().optional(),
|
|
7064
|
+
strokeWidth: z6.number().finite().optional(),
|
|
7065
|
+
opacity: z6.number().finite().optional()
|
|
6699
7066
|
})
|
|
6700
7067
|
).min(1).max(32)
|
|
6701
7068
|
});
|
|
@@ -6847,13 +7214,13 @@ function buildOutputAssetManifest(input) {
|
|
|
6847
7214
|
}
|
|
6848
7215
|
|
|
6849
7216
|
// src/outputs.ts
|
|
6850
|
-
import
|
|
6851
|
-
import
|
|
7217
|
+
import fs13 from "fs/promises";
|
|
7218
|
+
import path16 from "path";
|
|
6852
7219
|
import matter6 from "gray-matter";
|
|
6853
7220
|
|
|
6854
7221
|
// src/pages.ts
|
|
6855
|
-
import
|
|
6856
|
-
import
|
|
7222
|
+
import fs12 from "fs/promises";
|
|
7223
|
+
import path15 from "path";
|
|
6857
7224
|
import matter5 from "gray-matter";
|
|
6858
7225
|
function normalizeStringArray(value) {
|
|
6859
7226
|
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
@@ -6925,7 +7292,7 @@ async function loadExistingManagedPageState(absolutePath, defaults = {}) {
|
|
|
6925
7292
|
updatedAt: updatedFallback
|
|
6926
7293
|
};
|
|
6927
7294
|
}
|
|
6928
|
-
const content = await
|
|
7295
|
+
const content = await fs12.readFile(absolutePath, "utf8");
|
|
6929
7296
|
const parsed = matter5(content);
|
|
6930
7297
|
return {
|
|
6931
7298
|
status: normalizePageStatus(parsed.data.status, defaults.status ?? "active"),
|
|
@@ -6964,7 +7331,7 @@ function parseStoredPage(relativePath, content, defaults = {}) {
|
|
|
6964
7331
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
6965
7332
|
const fallbackCreatedAt = defaults.createdAt ?? now;
|
|
6966
7333
|
const fallbackUpdatedAt = defaults.updatedAt ?? fallbackCreatedAt;
|
|
6967
|
-
const title = typeof parsed.data.title === "string" ? parsed.data.title :
|
|
7334
|
+
const title = typeof parsed.data.title === "string" ? parsed.data.title : path15.basename(relativePath, ".md");
|
|
6968
7335
|
const kind = inferPageKind(relativePath, parsed.data.kind);
|
|
6969
7336
|
const sourceIds = normalizeStringArray(parsed.data.source_ids);
|
|
6970
7337
|
const projectIds = normalizeProjectIds(parsed.data.project_ids);
|
|
@@ -7003,18 +7370,18 @@ function parseStoredPage(relativePath, content, defaults = {}) {
|
|
|
7003
7370
|
};
|
|
7004
7371
|
}
|
|
7005
7372
|
async function loadInsightPages(wikiDir) {
|
|
7006
|
-
const insightsDir =
|
|
7373
|
+
const insightsDir = path15.join(wikiDir, "insights");
|
|
7007
7374
|
if (!await fileExists(insightsDir)) {
|
|
7008
7375
|
return [];
|
|
7009
7376
|
}
|
|
7010
|
-
const files = (await listFilesRecursive(insightsDir)).filter((filePath) => filePath.endsWith(".md")).filter((filePath) =>
|
|
7377
|
+
const files = (await listFilesRecursive(insightsDir)).filter((filePath) => filePath.endsWith(".md")).filter((filePath) => path15.basename(filePath) !== "index.md").sort((left, right) => left.localeCompare(right));
|
|
7011
7378
|
const insights = [];
|
|
7012
7379
|
for (const absolutePath of files) {
|
|
7013
|
-
const relativePath = toPosix(
|
|
7014
|
-
const content = await
|
|
7380
|
+
const relativePath = toPosix(path15.relative(wikiDir, absolutePath));
|
|
7381
|
+
const content = await fs12.readFile(absolutePath, "utf8");
|
|
7015
7382
|
const parsed = matter5(content);
|
|
7016
|
-
const stats = await
|
|
7017
|
-
const title = typeof parsed.data.title === "string" ? parsed.data.title :
|
|
7383
|
+
const stats = await fs12.stat(absolutePath);
|
|
7384
|
+
const title = typeof parsed.data.title === "string" ? parsed.data.title : path15.basename(absolutePath, ".md");
|
|
7018
7385
|
const sourceIds = normalizeStringArray(parsed.data.source_ids);
|
|
7019
7386
|
const projectIds = normalizeProjectIds(parsed.data.project_ids);
|
|
7020
7387
|
const nodeIds = normalizeStringArray(parsed.data.node_ids);
|
|
@@ -7076,27 +7443,27 @@ function relatedOutputsForPage(targetPage, outputPages) {
|
|
|
7076
7443
|
return outputPages.map((page) => ({ page, rank: relationRank(page, targetPage) })).filter((item) => item.rank > 0).sort((left, right) => right.rank - left.rank || left.page.title.localeCompare(right.page.title)).map((item) => item.page);
|
|
7077
7444
|
}
|
|
7078
7445
|
async function resolveUniqueOutputSlug(wikiDir, baseSlug) {
|
|
7079
|
-
const outputsDir =
|
|
7446
|
+
const outputsDir = path16.join(wikiDir, "outputs");
|
|
7080
7447
|
const root = baseSlug || "output";
|
|
7081
7448
|
let candidate = root;
|
|
7082
7449
|
let counter = 2;
|
|
7083
|
-
while (await fileExists(
|
|
7450
|
+
while (await fileExists(path16.join(outputsDir, `${candidate}.md`))) {
|
|
7084
7451
|
candidate = `${root}-${counter}`;
|
|
7085
7452
|
counter++;
|
|
7086
7453
|
}
|
|
7087
7454
|
return candidate;
|
|
7088
7455
|
}
|
|
7089
7456
|
async function loadSavedOutputPages(wikiDir) {
|
|
7090
|
-
const outputsDir =
|
|
7091
|
-
const entries = await
|
|
7457
|
+
const outputsDir = path16.join(wikiDir, "outputs");
|
|
7458
|
+
const entries = await fs13.readdir(outputsDir, { withFileTypes: true }).catch(() => []);
|
|
7092
7459
|
const outputs = [];
|
|
7093
7460
|
for (const entry of entries) {
|
|
7094
7461
|
if (!entry.isFile() || !entry.name.endsWith(".md") || entry.name === "index.md") {
|
|
7095
7462
|
continue;
|
|
7096
7463
|
}
|
|
7097
|
-
const relativePath =
|
|
7098
|
-
const absolutePath =
|
|
7099
|
-
const content = await
|
|
7464
|
+
const relativePath = path16.posix.join("outputs", entry.name);
|
|
7465
|
+
const absolutePath = path16.join(outputsDir, entry.name);
|
|
7466
|
+
const content = await fs13.readFile(absolutePath, "utf8");
|
|
7100
7467
|
const parsed = matter6(content);
|
|
7101
7468
|
const slug = entry.name.replace(/\.md$/, "");
|
|
7102
7469
|
const title = typeof parsed.data.title === "string" ? parsed.data.title : slug;
|
|
@@ -7109,7 +7476,7 @@ async function loadSavedOutputPages(wikiDir) {
|
|
|
7109
7476
|
const relatedSourceIds = normalizeStringArray(parsed.data.related_source_ids);
|
|
7110
7477
|
const backlinks = normalizeStringArray(parsed.data.backlinks);
|
|
7111
7478
|
const compiledFrom = normalizeStringArray(parsed.data.compiled_from);
|
|
7112
|
-
const stats = await
|
|
7479
|
+
const stats = await fs13.stat(absolutePath);
|
|
7113
7480
|
const createdAt = typeof parsed.data.created_at === "string" ? parsed.data.created_at : stats.birthtimeMs > 0 ? stats.birthtime.toISOString() : stats.mtime.toISOString();
|
|
7114
7481
|
const updatedAt = typeof parsed.data.updated_at === "string" ? parsed.data.updated_at : stats.mtime.toISOString();
|
|
7115
7482
|
outputs.push({
|
|
@@ -7147,8 +7514,8 @@ async function loadSavedOutputPages(wikiDir) {
|
|
|
7147
7514
|
}
|
|
7148
7515
|
|
|
7149
7516
|
// src/search.ts
|
|
7150
|
-
import
|
|
7151
|
-
import
|
|
7517
|
+
import fs14 from "fs/promises";
|
|
7518
|
+
import path17 from "path";
|
|
7152
7519
|
import matter7 from "gray-matter";
|
|
7153
7520
|
function getDatabaseSync() {
|
|
7154
7521
|
const builtin = process.getBuiltinModule?.("node:sqlite");
|
|
@@ -7168,7 +7535,7 @@ function normalizeStatus(value) {
|
|
|
7168
7535
|
return value === "draft" || value === "candidate" || value === "active" || value === "archived" ? value : void 0;
|
|
7169
7536
|
}
|
|
7170
7537
|
async function rebuildSearchIndex(dbPath, pages, wikiDir) {
|
|
7171
|
-
await ensureDir(
|
|
7538
|
+
await ensureDir(path17.dirname(dbPath));
|
|
7172
7539
|
const DatabaseSync = getDatabaseSync();
|
|
7173
7540
|
const db = new DatabaseSync(dbPath);
|
|
7174
7541
|
db.exec("PRAGMA journal_mode = WAL;");
|
|
@@ -7198,8 +7565,8 @@ async function rebuildSearchIndex(dbPath, pages, wikiDir) {
|
|
|
7198
7565
|
"INSERT INTO pages (id, path, title, body, kind, status, project_ids, project_key) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
|
|
7199
7566
|
);
|
|
7200
7567
|
for (const page of pages) {
|
|
7201
|
-
const absolutePath =
|
|
7202
|
-
const content = await
|
|
7568
|
+
const absolutePath = path17.join(wikiDir, page.path);
|
|
7569
|
+
const content = await fs14.readFile(absolutePath, "utf8");
|
|
7203
7570
|
const parsed = matter7(content);
|
|
7204
7571
|
insertPage.run(
|
|
7205
7572
|
page.id,
|
|
@@ -7302,7 +7669,7 @@ function outputFormatInstruction(format) {
|
|
|
7302
7669
|
}
|
|
7303
7670
|
}
|
|
7304
7671
|
function outputAssetPath(slug, fileName) {
|
|
7305
|
-
return toPosix(
|
|
7672
|
+
return toPosix(path18.join("outputs", "assets", slug, fileName));
|
|
7306
7673
|
}
|
|
7307
7674
|
function outputAssetId(slug, role) {
|
|
7308
7675
|
return `output:${slug}:asset:${role}`;
|
|
@@ -7640,7 +8007,7 @@ async function generateOutputArtifacts(rootDir, input) {
|
|
|
7640
8007
|
};
|
|
7641
8008
|
}
|
|
7642
8009
|
function normalizeProjectRoot(root) {
|
|
7643
|
-
const normalized = toPosix(
|
|
8010
|
+
const normalized = toPosix(path18.posix.normalize(root.replace(/\\/g, "/"))).replace(/^\.\/+/, "").replace(/\/+$/, "");
|
|
7644
8011
|
return normalized;
|
|
7645
8012
|
}
|
|
7646
8013
|
function projectEntries(config) {
|
|
@@ -7666,10 +8033,10 @@ function manifestPathForProject(rootDir, manifest) {
|
|
|
7666
8033
|
if (!rawPath) {
|
|
7667
8034
|
return toPosix(manifest.storedPath);
|
|
7668
8035
|
}
|
|
7669
|
-
if (!
|
|
8036
|
+
if (!path18.isAbsolute(rawPath)) {
|
|
7670
8037
|
return normalizeProjectRoot(rawPath);
|
|
7671
8038
|
}
|
|
7672
|
-
const relative = toPosix(
|
|
8039
|
+
const relative = toPosix(path18.relative(rootDir, rawPath));
|
|
7673
8040
|
return relative.startsWith("..") ? toPosix(rawPath) : normalizeProjectRoot(relative);
|
|
7674
8041
|
}
|
|
7675
8042
|
function prefixMatches(value, prefix) {
|
|
@@ -7843,7 +8210,7 @@ function pageHashes(pages) {
|
|
|
7843
8210
|
return Object.fromEntries(pages.map((page) => [page.page.id, page.contentHash]));
|
|
7844
8211
|
}
|
|
7845
8212
|
async function buildManagedGraphPage(absolutePath, defaults, build) {
|
|
7846
|
-
const existingContent = await fileExists(absolutePath) ? await
|
|
8213
|
+
const existingContent = await fileExists(absolutePath) ? await fs15.readFile(absolutePath, "utf8") : null;
|
|
7847
8214
|
let existing = await loadExistingManagedPageState(absolutePath, {
|
|
7848
8215
|
status: defaults.status ?? "active",
|
|
7849
8216
|
managedBy: defaults.managedBy
|
|
@@ -7881,7 +8248,7 @@ async function buildManagedGraphPage(absolutePath, defaults, build) {
|
|
|
7881
8248
|
return built;
|
|
7882
8249
|
}
|
|
7883
8250
|
async function buildManagedContent(absolutePath, defaults, build) {
|
|
7884
|
-
const existingContent = await fileExists(absolutePath) ? await
|
|
8251
|
+
const existingContent = await fileExists(absolutePath) ? await fs15.readFile(absolutePath, "utf8") : null;
|
|
7885
8252
|
let existing = await loadExistingManagedPageState(absolutePath, {
|
|
7886
8253
|
status: defaults.status ?? "active",
|
|
7887
8254
|
managedBy: defaults.managedBy
|
|
@@ -8325,7 +8692,7 @@ async function buildGraphOrientationPages(graph, paths, schemaHash) {
|
|
|
8325
8692
|
const benchmark = await readJsonFile(paths.benchmarkPath);
|
|
8326
8693
|
const communityRecords = [];
|
|
8327
8694
|
for (const community of graph.communities ?? []) {
|
|
8328
|
-
const absolutePath =
|
|
8695
|
+
const absolutePath = path18.join(paths.wikiDir, "graph", "communities", `${community.id.replace(/^community:/, "")}.md`);
|
|
8329
8696
|
communityRecords.push(
|
|
8330
8697
|
await buildManagedGraphPage(
|
|
8331
8698
|
absolutePath,
|
|
@@ -8345,7 +8712,7 @@ async function buildGraphOrientationPages(graph, paths, schemaHash) {
|
|
|
8345
8712
|
)
|
|
8346
8713
|
);
|
|
8347
8714
|
}
|
|
8348
|
-
const reportAbsolutePath =
|
|
8715
|
+
const reportAbsolutePath = path18.join(paths.wikiDir, "graph", "report.md");
|
|
8349
8716
|
const reportRecord = await buildManagedGraphPage(
|
|
8350
8717
|
reportAbsolutePath,
|
|
8351
8718
|
{
|
|
@@ -8364,7 +8731,7 @@ async function buildGraphOrientationPages(graph, paths, schemaHash) {
|
|
|
8364
8731
|
return [reportRecord, ...communityRecords];
|
|
8365
8732
|
}
|
|
8366
8733
|
async function writePage(wikiDir, relativePath, content, changedPages) {
|
|
8367
|
-
const absolutePath =
|
|
8734
|
+
const absolutePath = path18.resolve(wikiDir, relativePath);
|
|
8368
8735
|
const changed = await writeFileIfChanged(absolutePath, content);
|
|
8369
8736
|
if (changed) {
|
|
8370
8737
|
changedPages.push(relativePath);
|
|
@@ -8426,29 +8793,29 @@ async function requiredCompileArtifactsExist(paths) {
|
|
|
8426
8793
|
paths.graphPath,
|
|
8427
8794
|
paths.codeIndexPath,
|
|
8428
8795
|
paths.searchDbPath,
|
|
8429
|
-
|
|
8430
|
-
|
|
8431
|
-
|
|
8432
|
-
|
|
8433
|
-
|
|
8434
|
-
|
|
8435
|
-
|
|
8436
|
-
|
|
8796
|
+
path18.join(paths.wikiDir, "index.md"),
|
|
8797
|
+
path18.join(paths.wikiDir, "sources", "index.md"),
|
|
8798
|
+
path18.join(paths.wikiDir, "code", "index.md"),
|
|
8799
|
+
path18.join(paths.wikiDir, "concepts", "index.md"),
|
|
8800
|
+
path18.join(paths.wikiDir, "entities", "index.md"),
|
|
8801
|
+
path18.join(paths.wikiDir, "outputs", "index.md"),
|
|
8802
|
+
path18.join(paths.wikiDir, "projects", "index.md"),
|
|
8803
|
+
path18.join(paths.wikiDir, "candidates", "index.md")
|
|
8437
8804
|
];
|
|
8438
8805
|
const checks = await Promise.all(requiredPaths.map((filePath) => fileExists(filePath)));
|
|
8439
8806
|
return checks.every(Boolean);
|
|
8440
8807
|
}
|
|
8441
8808
|
async function loadAvailableCachedAnalyses(paths, manifests) {
|
|
8442
8809
|
const analyses = await Promise.all(
|
|
8443
|
-
manifests.map(async (manifest) => readJsonFile(
|
|
8810
|
+
manifests.map(async (manifest) => readJsonFile(path18.join(paths.analysesDir, `${manifest.sourceId}.json`)))
|
|
8444
8811
|
);
|
|
8445
8812
|
return analyses.filter((analysis) => Boolean(analysis));
|
|
8446
8813
|
}
|
|
8447
8814
|
function approvalManifestPath(paths, approvalId) {
|
|
8448
|
-
return
|
|
8815
|
+
return path18.join(paths.approvalsDir, approvalId, "manifest.json");
|
|
8449
8816
|
}
|
|
8450
8817
|
function approvalGraphPath(paths, approvalId) {
|
|
8451
|
-
return
|
|
8818
|
+
return path18.join(paths.approvalsDir, approvalId, "state", "graph.json");
|
|
8452
8819
|
}
|
|
8453
8820
|
async function readApprovalManifest(paths, approvalId) {
|
|
8454
8821
|
const manifest = await readJsonFile(approvalManifestPath(paths, approvalId));
|
|
@@ -8458,7 +8825,7 @@ async function readApprovalManifest(paths, approvalId) {
|
|
|
8458
8825
|
return manifest;
|
|
8459
8826
|
}
|
|
8460
8827
|
async function writeApprovalManifest(paths, manifest) {
|
|
8461
|
-
await
|
|
8828
|
+
await fs15.writeFile(approvalManifestPath(paths, manifest.approvalId), `${JSON.stringify(manifest, null, 2)}
|
|
8462
8829
|
`, "utf8");
|
|
8463
8830
|
}
|
|
8464
8831
|
async function buildApprovalEntries(paths, changedFiles, deletedPaths, previousGraph, graph) {
|
|
@@ -8473,7 +8840,7 @@ async function buildApprovalEntries(paths, changedFiles, deletedPaths, previousG
|
|
|
8473
8840
|
continue;
|
|
8474
8841
|
}
|
|
8475
8842
|
const previousPage = previousPagesById.get(nextPage.id);
|
|
8476
|
-
const currentExists = await fileExists(
|
|
8843
|
+
const currentExists = await fileExists(path18.join(paths.wikiDir, file.relativePath));
|
|
8477
8844
|
if (previousPage && previousPage.path !== nextPage.path) {
|
|
8478
8845
|
entries.push({
|
|
8479
8846
|
pageId: nextPage.id,
|
|
@@ -8506,7 +8873,7 @@ async function buildApprovalEntries(paths, changedFiles, deletedPaths, previousG
|
|
|
8506
8873
|
const previousPage = previousPagesByPath.get(deletedPath);
|
|
8507
8874
|
entries.push({
|
|
8508
8875
|
pageId: previousPage?.id ?? `page:${slugify(deletedPath)}`,
|
|
8509
|
-
title: previousPage?.title ??
|
|
8876
|
+
title: previousPage?.title ?? path18.basename(deletedPath, ".md"),
|
|
8510
8877
|
kind: previousPage?.kind ?? "index",
|
|
8511
8878
|
changeType: "delete",
|
|
8512
8879
|
status: "pending",
|
|
@@ -8518,16 +8885,16 @@ async function buildApprovalEntries(paths, changedFiles, deletedPaths, previousG
|
|
|
8518
8885
|
}
|
|
8519
8886
|
async function stageApprovalBundle(paths, changedFiles, deletedPaths, previousGraph, graph) {
|
|
8520
8887
|
const approvalId = `compile-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
|
|
8521
|
-
const approvalDir =
|
|
8888
|
+
const approvalDir = path18.join(paths.approvalsDir, approvalId);
|
|
8522
8889
|
await ensureDir(approvalDir);
|
|
8523
|
-
await ensureDir(
|
|
8524
|
-
await ensureDir(
|
|
8890
|
+
await ensureDir(path18.join(approvalDir, "wiki"));
|
|
8891
|
+
await ensureDir(path18.join(approvalDir, "state"));
|
|
8525
8892
|
for (const file of changedFiles) {
|
|
8526
|
-
const targetPath =
|
|
8527
|
-
await ensureDir(
|
|
8528
|
-
await
|
|
8893
|
+
const targetPath = path18.join(approvalDir, "wiki", file.relativePath);
|
|
8894
|
+
await ensureDir(path18.dirname(targetPath));
|
|
8895
|
+
await fs15.writeFile(targetPath, file.content, "utf8");
|
|
8529
8896
|
}
|
|
8530
|
-
await
|
|
8897
|
+
await fs15.writeFile(path18.join(approvalDir, "state", "graph.json"), JSON.stringify(graph, null, 2), "utf8");
|
|
8531
8898
|
await writeApprovalManifest(paths, {
|
|
8532
8899
|
approvalId,
|
|
8533
8900
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -8587,7 +8954,7 @@ async function syncVaultArtifacts(rootDir, input) {
|
|
|
8587
8954
|
confidence: 1
|
|
8588
8955
|
});
|
|
8589
8956
|
const sourceRecord = await buildManagedGraphPage(
|
|
8590
|
-
|
|
8957
|
+
path18.join(paths.wikiDir, preview.path),
|
|
8591
8958
|
{
|
|
8592
8959
|
managedBy: "system",
|
|
8593
8960
|
confidence: 1,
|
|
@@ -8632,7 +8999,7 @@ async function syncVaultArtifacts(rootDir, input) {
|
|
|
8632
8999
|
);
|
|
8633
9000
|
records.push(
|
|
8634
9001
|
await buildManagedGraphPage(
|
|
8635
|
-
|
|
9002
|
+
path18.join(paths.wikiDir, modulePreview.path),
|
|
8636
9003
|
{
|
|
8637
9004
|
managedBy: "system",
|
|
8638
9005
|
confidence: 1,
|
|
@@ -8665,8 +9032,8 @@ async function syncVaultArtifacts(rootDir, input) {
|
|
|
8665
9032
|
const promoted = previousEntry?.status === "active" || promoteCandidates && shouldPromoteCandidate(previousEntry, sourceIds);
|
|
8666
9033
|
const relativePath = promoted ? activeAggregatePath(itemKind, slug) : candidatePagePathFor(itemKind, slug);
|
|
8667
9034
|
const fallbackPaths = [
|
|
8668
|
-
|
|
8669
|
-
|
|
9035
|
+
path18.join(paths.wikiDir, activeAggregatePath(itemKind, slug)),
|
|
9036
|
+
path18.join(paths.wikiDir, candidatePagePathFor(itemKind, slug))
|
|
8670
9037
|
];
|
|
8671
9038
|
const confidence = nodeConfidence(aggregate.sourceAnalyses.length);
|
|
8672
9039
|
const preview = emptyGraphPage({
|
|
@@ -8683,7 +9050,7 @@ async function syncVaultArtifacts(rootDir, input) {
|
|
|
8683
9050
|
status: promoted ? "active" : "candidate"
|
|
8684
9051
|
});
|
|
8685
9052
|
const pageRecord = await buildManagedGraphPage(
|
|
8686
|
-
|
|
9053
|
+
path18.join(paths.wikiDir, relativePath),
|
|
8687
9054
|
{
|
|
8688
9055
|
status: promoted ? "active" : "candidate",
|
|
8689
9056
|
managedBy: "system",
|
|
@@ -8764,7 +9131,7 @@ async function syncVaultArtifacts(rootDir, input) {
|
|
|
8764
9131
|
confidence: 1
|
|
8765
9132
|
}),
|
|
8766
9133
|
content: await buildManagedContent(
|
|
8767
|
-
|
|
9134
|
+
path18.join(paths.wikiDir, "projects", "index.md"),
|
|
8768
9135
|
{
|
|
8769
9136
|
managedBy: "system",
|
|
8770
9137
|
compiledFrom: indexCompiledFrom(projectIndexRefs)
|
|
@@ -8788,7 +9155,7 @@ async function syncVaultArtifacts(rootDir, input) {
|
|
|
8788
9155
|
records.push({
|
|
8789
9156
|
page: projectIndexRef,
|
|
8790
9157
|
content: await buildManagedContent(
|
|
8791
|
-
|
|
9158
|
+
path18.join(paths.wikiDir, projectIndexRef.path),
|
|
8792
9159
|
{
|
|
8793
9160
|
managedBy: "system",
|
|
8794
9161
|
compiledFrom: indexCompiledFrom(Object.values(sections).flat())
|
|
@@ -8816,7 +9183,7 @@ async function syncVaultArtifacts(rootDir, input) {
|
|
|
8816
9183
|
confidence: 1
|
|
8817
9184
|
}),
|
|
8818
9185
|
content: await buildManagedContent(
|
|
8819
|
-
|
|
9186
|
+
path18.join(paths.wikiDir, "index.md"),
|
|
8820
9187
|
{
|
|
8821
9188
|
managedBy: "system",
|
|
8822
9189
|
compiledFrom: indexCompiledFrom(allPages)
|
|
@@ -8847,7 +9214,7 @@ async function syncVaultArtifacts(rootDir, input) {
|
|
|
8847
9214
|
confidence: 1
|
|
8848
9215
|
}),
|
|
8849
9216
|
content: await buildManagedContent(
|
|
8850
|
-
|
|
9217
|
+
path18.join(paths.wikiDir, relativePath),
|
|
8851
9218
|
{
|
|
8852
9219
|
managedBy: "system",
|
|
8853
9220
|
compiledFrom: indexCompiledFrom(pages)
|
|
@@ -8858,12 +9225,12 @@ async function syncVaultArtifacts(rootDir, input) {
|
|
|
8858
9225
|
}
|
|
8859
9226
|
const nextPagePaths = new Set(records.map((record) => record.page.path));
|
|
8860
9227
|
const obsoleteGraphPaths = (previousGraph?.pages ?? []).filter((page) => page.kind !== "output" && page.kind !== "insight").map((page) => page.path).filter((relativePath) => !nextPagePaths.has(relativePath));
|
|
8861
|
-
const existingProjectIndexPaths = (await listFilesRecursive(paths.projectsDir)).filter((absolutePath) => absolutePath.endsWith(".md")).map((absolutePath) => toPosix(
|
|
9228
|
+
const existingProjectIndexPaths = (await listFilesRecursive(paths.projectsDir)).filter((absolutePath) => absolutePath.endsWith(".md")).map((absolutePath) => toPosix(path18.relative(paths.wikiDir, absolutePath))).filter((relativePath) => !nextPagePaths.has(relativePath));
|
|
8862
9229
|
const obsoletePaths = uniqueStrings2([...obsoleteGraphPaths, ...existingProjectIndexPaths]);
|
|
8863
9230
|
const changedFiles = [];
|
|
8864
9231
|
for (const record of records) {
|
|
8865
|
-
const absolutePath =
|
|
8866
|
-
const current = await fileExists(absolutePath) ? await
|
|
9232
|
+
const absolutePath = path18.join(paths.wikiDir, record.page.path);
|
|
9233
|
+
const current = await fileExists(absolutePath) ? await fs15.readFile(absolutePath, "utf8") : null;
|
|
8867
9234
|
if (current !== record.content) {
|
|
8868
9235
|
changedPages.push(record.page.path);
|
|
8869
9236
|
changedFiles.push({ relativePath: record.page.path, content: record.content });
|
|
@@ -8888,7 +9255,7 @@ async function syncVaultArtifacts(rootDir, input) {
|
|
|
8888
9255
|
await writePage(paths.wikiDir, record.page.path, record.content, writeChanges);
|
|
8889
9256
|
}
|
|
8890
9257
|
for (const relativePath of obsoletePaths) {
|
|
8891
|
-
await
|
|
9258
|
+
await fs15.rm(path18.join(paths.wikiDir, relativePath), { force: true });
|
|
8892
9259
|
}
|
|
8893
9260
|
await writeJsonFile(paths.graphPath, graph);
|
|
8894
9261
|
await writeJsonFile(paths.codeIndexPath, input.codeIndex);
|
|
@@ -8959,17 +9326,17 @@ async function refreshIndexesAndSearch(rootDir, pages) {
|
|
|
8959
9326
|
})
|
|
8960
9327
|
);
|
|
8961
9328
|
await Promise.all([
|
|
8962
|
-
ensureDir(
|
|
8963
|
-
ensureDir(
|
|
8964
|
-
ensureDir(
|
|
8965
|
-
ensureDir(
|
|
8966
|
-
ensureDir(
|
|
8967
|
-
ensureDir(
|
|
8968
|
-
ensureDir(
|
|
8969
|
-
ensureDir(
|
|
8970
|
-
ensureDir(
|
|
9329
|
+
ensureDir(path18.join(paths.wikiDir, "sources")),
|
|
9330
|
+
ensureDir(path18.join(paths.wikiDir, "code")),
|
|
9331
|
+
ensureDir(path18.join(paths.wikiDir, "concepts")),
|
|
9332
|
+
ensureDir(path18.join(paths.wikiDir, "entities")),
|
|
9333
|
+
ensureDir(path18.join(paths.wikiDir, "outputs")),
|
|
9334
|
+
ensureDir(path18.join(paths.wikiDir, "graph")),
|
|
9335
|
+
ensureDir(path18.join(paths.wikiDir, "graph", "communities")),
|
|
9336
|
+
ensureDir(path18.join(paths.wikiDir, "projects")),
|
|
9337
|
+
ensureDir(path18.join(paths.wikiDir, "candidates"))
|
|
8971
9338
|
]);
|
|
8972
|
-
const projectsIndexPath =
|
|
9339
|
+
const projectsIndexPath = path18.join(paths.wikiDir, "projects", "index.md");
|
|
8973
9340
|
await writeFileIfChanged(
|
|
8974
9341
|
projectsIndexPath,
|
|
8975
9342
|
await buildManagedContent(
|
|
@@ -8990,7 +9357,7 @@ async function refreshIndexesAndSearch(rootDir, pages) {
|
|
|
8990
9357
|
outputs: pages.filter((page) => page.kind === "output" && page.projectIds.includes(project.id)),
|
|
8991
9358
|
candidates: pages.filter((page) => page.status === "candidate" && page.projectIds.includes(project.id))
|
|
8992
9359
|
};
|
|
8993
|
-
const absolutePath =
|
|
9360
|
+
const absolutePath = path18.join(paths.wikiDir, "projects", project.id, "index.md");
|
|
8994
9361
|
await writeFileIfChanged(
|
|
8995
9362
|
absolutePath,
|
|
8996
9363
|
await buildManagedContent(
|
|
@@ -9008,7 +9375,7 @@ async function refreshIndexesAndSearch(rootDir, pages) {
|
|
|
9008
9375
|
)
|
|
9009
9376
|
);
|
|
9010
9377
|
}
|
|
9011
|
-
const rootIndexPath =
|
|
9378
|
+
const rootIndexPath = path18.join(paths.wikiDir, "index.md");
|
|
9012
9379
|
await writeFileIfChanged(
|
|
9013
9380
|
rootIndexPath,
|
|
9014
9381
|
await buildManagedContent(
|
|
@@ -9029,7 +9396,7 @@ async function refreshIndexesAndSearch(rootDir, pages) {
|
|
|
9029
9396
|
["candidates/index.md", "candidates", pagesWithGraph.filter((page) => page.status === "candidate")],
|
|
9030
9397
|
["graph/index.md", "graph", pagesWithGraph.filter((page) => page.kind === "graph_report" || page.kind === "community_summary")]
|
|
9031
9398
|
]) {
|
|
9032
|
-
const absolutePath =
|
|
9399
|
+
const absolutePath = path18.join(paths.wikiDir, relativePath);
|
|
9033
9400
|
await writeFileIfChanged(
|
|
9034
9401
|
absolutePath,
|
|
9035
9402
|
await buildManagedContent(
|
|
@@ -9043,20 +9410,20 @@ async function refreshIndexesAndSearch(rootDir, pages) {
|
|
|
9043
9410
|
);
|
|
9044
9411
|
}
|
|
9045
9412
|
for (const record of graphOrientationRecords) {
|
|
9046
|
-
await writeFileIfChanged(
|
|
9413
|
+
await writeFileIfChanged(path18.join(paths.wikiDir, record.page.path), record.content);
|
|
9047
9414
|
}
|
|
9048
|
-
const existingProjectIndexPaths = (await listFilesRecursive(paths.projectsDir)).filter((absolutePath) => absolutePath.endsWith(".md")).map((absolutePath) => toPosix(
|
|
9415
|
+
const existingProjectIndexPaths = (await listFilesRecursive(paths.projectsDir)).filter((absolutePath) => absolutePath.endsWith(".md")).map((absolutePath) => toPosix(path18.relative(paths.wikiDir, absolutePath)));
|
|
9049
9416
|
const allowedProjectIndexPaths = /* @__PURE__ */ new Set([
|
|
9050
9417
|
"projects/index.md",
|
|
9051
9418
|
...configuredProjects.map((project) => `projects/${project.id}/index.md`)
|
|
9052
9419
|
]);
|
|
9053
9420
|
await Promise.all(
|
|
9054
|
-
existingProjectIndexPaths.filter((relativePath) => !allowedProjectIndexPaths.has(relativePath)).map((relativePath) =>
|
|
9421
|
+
existingProjectIndexPaths.filter((relativePath) => !allowedProjectIndexPaths.has(relativePath)).map((relativePath) => fs15.rm(path18.join(paths.wikiDir, relativePath), { force: true }))
|
|
9055
9422
|
);
|
|
9056
|
-
const existingGraphPages = (await listFilesRecursive(
|
|
9423
|
+
const existingGraphPages = (await listFilesRecursive(path18.join(paths.wikiDir, "graph").replace(/\/$/, "")).catch(() => [])).filter((absolutePath) => absolutePath.endsWith(".md")).map((absolutePath) => toPosix(path18.relative(paths.wikiDir, absolutePath)));
|
|
9057
9424
|
const allowedGraphPages = /* @__PURE__ */ new Set(["graph/index.md", ...graphOrientationRecords.map((record) => record.page.path)]);
|
|
9058
9425
|
await Promise.all(
|
|
9059
|
-
existingGraphPages.filter((relativePath) => !allowedGraphPages.has(relativePath)).map((relativePath) =>
|
|
9426
|
+
existingGraphPages.filter((relativePath) => !allowedGraphPages.has(relativePath)).map((relativePath) => fs15.rm(path18.join(paths.wikiDir, relativePath), { force: true }))
|
|
9060
9427
|
);
|
|
9061
9428
|
await rebuildSearchIndex(paths.searchDbPath, pagesWithGraph, paths.wikiDir);
|
|
9062
9429
|
}
|
|
@@ -9076,7 +9443,7 @@ async function prepareOutputPageSave(rootDir, input) {
|
|
|
9076
9443
|
confidence: 0.74
|
|
9077
9444
|
}
|
|
9078
9445
|
});
|
|
9079
|
-
const absolutePath =
|
|
9446
|
+
const absolutePath = path18.join(paths.wikiDir, output.page.path);
|
|
9080
9447
|
return {
|
|
9081
9448
|
page: output.page,
|
|
9082
9449
|
savedPath: absolutePath,
|
|
@@ -9088,15 +9455,15 @@ async function prepareOutputPageSave(rootDir, input) {
|
|
|
9088
9455
|
async function persistOutputPage(rootDir, input) {
|
|
9089
9456
|
const { paths } = await loadVaultConfig(rootDir);
|
|
9090
9457
|
const prepared = await prepareOutputPageSave(rootDir, input);
|
|
9091
|
-
await ensureDir(
|
|
9092
|
-
await
|
|
9458
|
+
await ensureDir(path18.dirname(prepared.savedPath));
|
|
9459
|
+
await fs15.writeFile(prepared.savedPath, prepared.content, "utf8");
|
|
9093
9460
|
for (const assetFile of prepared.assetFiles) {
|
|
9094
|
-
const assetPath =
|
|
9095
|
-
await ensureDir(
|
|
9461
|
+
const assetPath = path18.join(paths.wikiDir, assetFile.relativePath);
|
|
9462
|
+
await ensureDir(path18.dirname(assetPath));
|
|
9096
9463
|
if (typeof assetFile.content === "string") {
|
|
9097
|
-
await
|
|
9464
|
+
await fs15.writeFile(assetPath, assetFile.content, assetFile.encoding ?? "utf8");
|
|
9098
9465
|
} else {
|
|
9099
|
-
await
|
|
9466
|
+
await fs15.writeFile(assetPath, assetFile.content);
|
|
9100
9467
|
}
|
|
9101
9468
|
}
|
|
9102
9469
|
return { page: prepared.page, savedPath: prepared.savedPath, outputAssets: prepared.outputAssets };
|
|
@@ -9117,7 +9484,7 @@ async function prepareExploreHubSave(rootDir, input) {
|
|
|
9117
9484
|
confidence: 0.76
|
|
9118
9485
|
}
|
|
9119
9486
|
});
|
|
9120
|
-
const absolutePath =
|
|
9487
|
+
const absolutePath = path18.join(paths.wikiDir, hub.page.path);
|
|
9121
9488
|
return {
|
|
9122
9489
|
page: hub.page,
|
|
9123
9490
|
savedPath: absolutePath,
|
|
@@ -9129,15 +9496,15 @@ async function prepareExploreHubSave(rootDir, input) {
|
|
|
9129
9496
|
async function persistExploreHub(rootDir, input) {
|
|
9130
9497
|
const { paths } = await loadVaultConfig(rootDir);
|
|
9131
9498
|
const prepared = await prepareExploreHubSave(rootDir, input);
|
|
9132
|
-
await ensureDir(
|
|
9133
|
-
await
|
|
9499
|
+
await ensureDir(path18.dirname(prepared.savedPath));
|
|
9500
|
+
await fs15.writeFile(prepared.savedPath, prepared.content, "utf8");
|
|
9134
9501
|
for (const assetFile of prepared.assetFiles) {
|
|
9135
|
-
const assetPath =
|
|
9136
|
-
await ensureDir(
|
|
9502
|
+
const assetPath = path18.join(paths.wikiDir, assetFile.relativePath);
|
|
9503
|
+
await ensureDir(path18.dirname(assetPath));
|
|
9137
9504
|
if (typeof assetFile.content === "string") {
|
|
9138
|
-
await
|
|
9505
|
+
await fs15.writeFile(assetPath, assetFile.content, assetFile.encoding ?? "utf8");
|
|
9139
9506
|
} else {
|
|
9140
|
-
await
|
|
9507
|
+
await fs15.writeFile(assetPath, assetFile.content);
|
|
9141
9508
|
}
|
|
9142
9509
|
}
|
|
9143
9510
|
return { page: prepared.page, savedPath: prepared.savedPath, outputAssets: prepared.outputAssets };
|
|
@@ -9154,17 +9521,17 @@ async function stageOutputApprovalBundle(rootDir, stagedPages) {
|
|
|
9154
9521
|
}))
|
|
9155
9522
|
]);
|
|
9156
9523
|
const approvalId = `schedule-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
|
|
9157
|
-
const approvalDir =
|
|
9524
|
+
const approvalDir = path18.join(paths.approvalsDir, approvalId);
|
|
9158
9525
|
await ensureDir(approvalDir);
|
|
9159
|
-
await ensureDir(
|
|
9160
|
-
await ensureDir(
|
|
9526
|
+
await ensureDir(path18.join(approvalDir, "wiki"));
|
|
9527
|
+
await ensureDir(path18.join(approvalDir, "state"));
|
|
9161
9528
|
for (const file of changedFiles) {
|
|
9162
|
-
const targetPath =
|
|
9163
|
-
await ensureDir(
|
|
9529
|
+
const targetPath = path18.join(approvalDir, "wiki", file.relativePath);
|
|
9530
|
+
await ensureDir(path18.dirname(targetPath));
|
|
9164
9531
|
if ("binary" in file && file.binary) {
|
|
9165
|
-
await
|
|
9532
|
+
await fs15.writeFile(targetPath, Buffer.from(file.content, "base64"));
|
|
9166
9533
|
} else {
|
|
9167
|
-
await
|
|
9534
|
+
await fs15.writeFile(targetPath, file.content, "utf8");
|
|
9168
9535
|
}
|
|
9169
9536
|
}
|
|
9170
9537
|
const nextPages = sortGraphPages([
|
|
@@ -9178,7 +9545,7 @@ async function stageOutputApprovalBundle(rootDir, stagedPages) {
|
|
|
9178
9545
|
sources: previousGraph?.sources ?? [],
|
|
9179
9546
|
pages: nextPages
|
|
9180
9547
|
};
|
|
9181
|
-
await
|
|
9548
|
+
await fs15.writeFile(path18.join(approvalDir, "state", "graph.json"), JSON.stringify(graph, null, 2), "utf8");
|
|
9182
9549
|
await writeApprovalManifest(paths, {
|
|
9183
9550
|
approvalId,
|
|
9184
9551
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -9207,9 +9574,9 @@ async function executeQuery(rootDir, question, format) {
|
|
|
9207
9574
|
const searchResults = searchPages(paths.searchDbPath, question, 5);
|
|
9208
9575
|
const excerpts = await Promise.all(
|
|
9209
9576
|
searchResults.map(async (result) => {
|
|
9210
|
-
const absolutePath =
|
|
9577
|
+
const absolutePath = path18.join(paths.wikiDir, result.path);
|
|
9211
9578
|
try {
|
|
9212
|
-
const content = await
|
|
9579
|
+
const content = await fs15.readFile(absolutePath, "utf8");
|
|
9213
9580
|
const parsed = matter8(content);
|
|
9214
9581
|
return `# ${result.title}
|
|
9215
9582
|
${truncate(normalizeWhitespace(parsed.content), 1200)}`;
|
|
@@ -9307,8 +9674,8 @@ async function generateFollowUpQuestions(rootDir, question, answer) {
|
|
|
9307
9674
|
Current answer:
|
|
9308
9675
|
${answer}`
|
|
9309
9676
|
},
|
|
9310
|
-
|
|
9311
|
-
questions:
|
|
9677
|
+
z7.object({
|
|
9678
|
+
questions: z7.array(z7.string().min(1)).max(5)
|
|
9312
9679
|
})
|
|
9313
9680
|
);
|
|
9314
9681
|
return uniqueBy(response.questions, (item) => item).filter((item) => item !== question);
|
|
@@ -9391,7 +9758,7 @@ function sortGraphPages(pages) {
|
|
|
9391
9758
|
async function listApprovals(rootDir) {
|
|
9392
9759
|
const { paths } = await loadVaultConfig(rootDir);
|
|
9393
9760
|
const manifests = await Promise.all(
|
|
9394
|
-
(await
|
|
9761
|
+
(await fs15.readdir(paths.approvalsDir, { withFileTypes: true }).catch(() => [])).filter((entry) => entry.isDirectory()).map(async (entry) => {
|
|
9395
9762
|
try {
|
|
9396
9763
|
return await readApprovalManifest(paths, entry.name);
|
|
9397
9764
|
} catch {
|
|
@@ -9407,8 +9774,8 @@ async function readApproval(rootDir, approvalId) {
|
|
|
9407
9774
|
const details = await Promise.all(
|
|
9408
9775
|
manifest.entries.map(async (entry) => {
|
|
9409
9776
|
const currentPath = entry.previousPath ?? entry.nextPath;
|
|
9410
|
-
const currentContent = currentPath ? await
|
|
9411
|
-
const stagedContent = entry.nextPath ? await
|
|
9777
|
+
const currentContent = currentPath ? await fs15.readFile(path18.join(paths.wikiDir, currentPath), "utf8").catch(() => void 0) : void 0;
|
|
9778
|
+
const stagedContent = entry.nextPath ? await fs15.readFile(path18.join(paths.approvalsDir, approvalId, "wiki", entry.nextPath), "utf8").catch(() => void 0) : void 0;
|
|
9412
9779
|
return {
|
|
9413
9780
|
...entry,
|
|
9414
9781
|
currentContent,
|
|
@@ -9436,26 +9803,26 @@ async function acceptApproval(rootDir, approvalId, targets = []) {
|
|
|
9436
9803
|
if (!entry.nextPath) {
|
|
9437
9804
|
throw new Error(`Approval entry ${entry.pageId} is missing a staged path.`);
|
|
9438
9805
|
}
|
|
9439
|
-
const stagedAbsolutePath =
|
|
9440
|
-
const stagedContent = await
|
|
9441
|
-
const targetAbsolutePath =
|
|
9442
|
-
await ensureDir(
|
|
9443
|
-
await
|
|
9806
|
+
const stagedAbsolutePath = path18.join(paths.approvalsDir, approvalId, "wiki", entry.nextPath);
|
|
9807
|
+
const stagedContent = await fs15.readFile(stagedAbsolutePath, "utf8");
|
|
9808
|
+
const targetAbsolutePath = path18.join(paths.wikiDir, entry.nextPath);
|
|
9809
|
+
await ensureDir(path18.dirname(targetAbsolutePath));
|
|
9810
|
+
await fs15.writeFile(targetAbsolutePath, stagedContent, "utf8");
|
|
9444
9811
|
if (entry.changeType === "promote" && entry.previousPath) {
|
|
9445
|
-
await
|
|
9812
|
+
await fs15.rm(path18.join(paths.wikiDir, entry.previousPath), { force: true });
|
|
9446
9813
|
}
|
|
9447
9814
|
const nextPage = bundleGraph?.pages.find((page) => page.id === entry.pageId && page.path === entry.nextPath) ?? parseStoredPage(entry.nextPath, stagedContent);
|
|
9448
9815
|
if (nextPage.kind === "output" && nextPage.outputAssets?.length) {
|
|
9449
|
-
const outputAssetDir =
|
|
9450
|
-
await
|
|
9816
|
+
const outputAssetDir = path18.join(paths.wikiDir, "outputs", "assets", path18.basename(nextPage.path, ".md"));
|
|
9817
|
+
await fs15.rm(outputAssetDir, { recursive: true, force: true });
|
|
9451
9818
|
for (const asset of nextPage.outputAssets) {
|
|
9452
|
-
const stagedAssetPath =
|
|
9819
|
+
const stagedAssetPath = path18.join(paths.approvalsDir, approvalId, "wiki", asset.path);
|
|
9453
9820
|
if (!await fileExists(stagedAssetPath)) {
|
|
9454
9821
|
continue;
|
|
9455
9822
|
}
|
|
9456
|
-
const targetAssetPath =
|
|
9457
|
-
await ensureDir(
|
|
9458
|
-
await
|
|
9823
|
+
const targetAssetPath = path18.join(paths.wikiDir, asset.path);
|
|
9824
|
+
await ensureDir(path18.dirname(targetAssetPath));
|
|
9825
|
+
await fs15.copyFile(stagedAssetPath, targetAssetPath);
|
|
9459
9826
|
}
|
|
9460
9827
|
}
|
|
9461
9828
|
nextPages = nextPages.filter(
|
|
@@ -9466,10 +9833,10 @@ async function acceptApproval(rootDir, approvalId, targets = []) {
|
|
|
9466
9833
|
} else {
|
|
9467
9834
|
const deletedPage = nextPages.find((page) => page.id === entry.pageId || page.path === entry.previousPath) ?? bundleGraph?.pages.find((page) => page.id === entry.pageId || page.path === entry.previousPath) ?? null;
|
|
9468
9835
|
if (entry.previousPath) {
|
|
9469
|
-
await
|
|
9836
|
+
await fs15.rm(path18.join(paths.wikiDir, entry.previousPath), { force: true });
|
|
9470
9837
|
}
|
|
9471
9838
|
if (deletedPage?.kind === "output") {
|
|
9472
|
-
await
|
|
9839
|
+
await fs15.rm(path18.join(paths.wikiDir, "outputs", "assets", path18.basename(deletedPage.path, ".md")), {
|
|
9473
9840
|
recursive: true,
|
|
9474
9841
|
force: true
|
|
9475
9842
|
});
|
|
@@ -9559,7 +9926,7 @@ async function promoteCandidate(rootDir, target) {
|
|
|
9559
9926
|
const { paths } = await loadVaultConfig(rootDir);
|
|
9560
9927
|
const graph = await readJsonFile(paths.graphPath);
|
|
9561
9928
|
const candidate = resolveCandidateTarget(graph?.pages ?? [], target);
|
|
9562
|
-
const raw = await
|
|
9929
|
+
const raw = await fs15.readFile(path18.join(paths.wikiDir, candidate.path), "utf8");
|
|
9563
9930
|
const parsed = matter8(raw);
|
|
9564
9931
|
const nextUpdatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
9565
9932
|
const nextContent = matter8.stringify(parsed.content, {
|
|
@@ -9571,10 +9938,10 @@ async function promoteCandidate(rootDir, target) {
|
|
|
9571
9938
|
)
|
|
9572
9939
|
});
|
|
9573
9940
|
const nextPath = candidateActivePath(candidate);
|
|
9574
|
-
const nextAbsolutePath =
|
|
9575
|
-
await ensureDir(
|
|
9576
|
-
await
|
|
9577
|
-
await
|
|
9941
|
+
const nextAbsolutePath = path18.join(paths.wikiDir, nextPath);
|
|
9942
|
+
await ensureDir(path18.dirname(nextAbsolutePath));
|
|
9943
|
+
await fs15.writeFile(nextAbsolutePath, nextContent, "utf8");
|
|
9944
|
+
await fs15.rm(path18.join(paths.wikiDir, candidate.path), { force: true });
|
|
9578
9945
|
const nextPage = parseStoredPage(nextPath, nextContent, { createdAt: candidate.createdAt, updatedAt: nextUpdatedAt });
|
|
9579
9946
|
const nextPages = sortGraphPages(
|
|
9580
9947
|
(graph?.pages ?? []).filter((page) => page.id !== candidate.id && page.path !== candidate.path).concat(nextPage)
|
|
@@ -9618,7 +9985,7 @@ async function archiveCandidate(rootDir, target) {
|
|
|
9618
9985
|
const { paths } = await loadVaultConfig(rootDir);
|
|
9619
9986
|
const graph = await readJsonFile(paths.graphPath);
|
|
9620
9987
|
const candidate = resolveCandidateTarget(graph?.pages ?? [], target);
|
|
9621
|
-
await
|
|
9988
|
+
await fs15.rm(path18.join(paths.wikiDir, candidate.path), { force: true });
|
|
9622
9989
|
const nextPages = sortGraphPages((graph?.pages ?? []).filter((page) => page.id !== candidate.id && page.path !== candidate.path));
|
|
9623
9990
|
const nextGraph = {
|
|
9624
9991
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -9656,18 +10023,18 @@ async function archiveCandidate(rootDir, target) {
|
|
|
9656
10023
|
}
|
|
9657
10024
|
async function ensureObsidianWorkspace(rootDir) {
|
|
9658
10025
|
const { config } = await loadVaultConfig(rootDir);
|
|
9659
|
-
const obsidianDir =
|
|
10026
|
+
const obsidianDir = path18.join(rootDir, ".obsidian");
|
|
9660
10027
|
const projectIds = projectEntries(config).map((project) => project.id);
|
|
9661
10028
|
await ensureDir(obsidianDir);
|
|
9662
10029
|
await Promise.all([
|
|
9663
|
-
writeJsonFile(
|
|
10030
|
+
writeJsonFile(path18.join(obsidianDir, "app.json"), {
|
|
9664
10031
|
alwaysUpdateLinks: true,
|
|
9665
10032
|
newFileLocation: "folder",
|
|
9666
10033
|
newFileFolderPath: "wiki/insights",
|
|
9667
10034
|
useMarkdownLinks: false,
|
|
9668
10035
|
attachmentFolderPath: "raw/assets"
|
|
9669
10036
|
}),
|
|
9670
|
-
writeJsonFile(
|
|
10037
|
+
writeJsonFile(path18.join(obsidianDir, "core-plugins.json"), [
|
|
9671
10038
|
"file-explorer",
|
|
9672
10039
|
"global-search",
|
|
9673
10040
|
"switcher",
|
|
@@ -9677,7 +10044,7 @@ async function ensureObsidianWorkspace(rootDir) {
|
|
|
9677
10044
|
"tag-pane",
|
|
9678
10045
|
"page-preview"
|
|
9679
10046
|
]),
|
|
9680
|
-
writeJsonFile(
|
|
10047
|
+
writeJsonFile(path18.join(obsidianDir, "graph.json"), {
|
|
9681
10048
|
"collapse-filter": false,
|
|
9682
10049
|
search: "",
|
|
9683
10050
|
showTags: true,
|
|
@@ -9689,7 +10056,7 @@ async function ensureObsidianWorkspace(rootDir) {
|
|
|
9689
10056
|
})),
|
|
9690
10057
|
localJumps: false
|
|
9691
10058
|
}),
|
|
9692
|
-
writeJsonFile(
|
|
10059
|
+
writeJsonFile(path18.join(obsidianDir, "workspace.json"), {
|
|
9693
10060
|
active: "root",
|
|
9694
10061
|
lastOpenFiles: ["wiki/index.md", "wiki/projects/index.md", "wiki/candidates/index.md", "wiki/insights/index.md"],
|
|
9695
10062
|
left: {
|
|
@@ -9704,7 +10071,7 @@ async function ensureObsidianWorkspace(rootDir) {
|
|
|
9704
10071
|
async function initVault(rootDir, options = {}) {
|
|
9705
10072
|
const { paths } = await initWorkspace(rootDir);
|
|
9706
10073
|
await installConfiguredAgents(rootDir);
|
|
9707
|
-
const insightsIndexPath =
|
|
10074
|
+
const insightsIndexPath = path18.join(paths.wikiDir, "insights", "index.md");
|
|
9708
10075
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
9709
10076
|
await writeFileIfChanged(
|
|
9710
10077
|
insightsIndexPath,
|
|
@@ -9740,7 +10107,7 @@ async function initVault(rootDir, options = {}) {
|
|
|
9740
10107
|
)
|
|
9741
10108
|
);
|
|
9742
10109
|
await writeFileIfChanged(
|
|
9743
|
-
|
|
10110
|
+
path18.join(paths.wikiDir, "projects", "index.md"),
|
|
9744
10111
|
matter8.stringify(["# Projects", "", "- Run `swarmvault compile` to build project rollups.", ""].join("\n"), {
|
|
9745
10112
|
page_id: "projects:index",
|
|
9746
10113
|
kind: "index",
|
|
@@ -9762,7 +10129,7 @@ async function initVault(rootDir, options = {}) {
|
|
|
9762
10129
|
})
|
|
9763
10130
|
);
|
|
9764
10131
|
await writeFileIfChanged(
|
|
9765
|
-
|
|
10132
|
+
path18.join(paths.wikiDir, "candidates", "index.md"),
|
|
9766
10133
|
matter8.stringify(["# Candidates", "", "- Run `swarmvault compile` to stage candidate pages.", ""].join("\n"), {
|
|
9767
10134
|
page_id: "candidates:index",
|
|
9768
10135
|
kind: "index",
|
|
@@ -9879,7 +10246,7 @@ async function compileVault(rootDir, options = {}) {
|
|
|
9879
10246
|
),
|
|
9880
10247
|
Promise.all(
|
|
9881
10248
|
clean.map(async (manifest) => {
|
|
9882
|
-
const cached = await readJsonFile(
|
|
10249
|
+
const cached = await readJsonFile(path18.join(paths.analysesDir, `${manifest.sourceId}.json`));
|
|
9883
10250
|
if (cached) {
|
|
9884
10251
|
return cached;
|
|
9885
10252
|
}
|
|
@@ -9903,22 +10270,22 @@ async function compileVault(rootDir, options = {}) {
|
|
|
9903
10270
|
}
|
|
9904
10271
|
const enriched = enrichResolvedCodeImports(manifest, analysis, codeIndex);
|
|
9905
10272
|
if (analysisSignature(enriched) !== analysisSignature(analysis)) {
|
|
9906
|
-
await writeJsonFile(
|
|
10273
|
+
await writeJsonFile(path18.join(paths.analysesDir, `${analysis.sourceId}.json`), enriched);
|
|
9907
10274
|
}
|
|
9908
10275
|
return enriched;
|
|
9909
10276
|
})
|
|
9910
10277
|
);
|
|
9911
10278
|
await Promise.all([
|
|
9912
|
-
ensureDir(
|
|
9913
|
-
ensureDir(
|
|
9914
|
-
ensureDir(
|
|
9915
|
-
ensureDir(
|
|
9916
|
-
ensureDir(
|
|
9917
|
-
ensureDir(
|
|
9918
|
-
ensureDir(
|
|
9919
|
-
ensureDir(
|
|
9920
|
-
ensureDir(
|
|
9921
|
-
ensureDir(
|
|
10279
|
+
ensureDir(path18.join(paths.wikiDir, "sources")),
|
|
10280
|
+
ensureDir(path18.join(paths.wikiDir, "code")),
|
|
10281
|
+
ensureDir(path18.join(paths.wikiDir, "concepts")),
|
|
10282
|
+
ensureDir(path18.join(paths.wikiDir, "entities")),
|
|
10283
|
+
ensureDir(path18.join(paths.wikiDir, "outputs")),
|
|
10284
|
+
ensureDir(path18.join(paths.wikiDir, "projects")),
|
|
10285
|
+
ensureDir(path18.join(paths.wikiDir, "insights")),
|
|
10286
|
+
ensureDir(path18.join(paths.wikiDir, "candidates")),
|
|
10287
|
+
ensureDir(path18.join(paths.wikiDir, "candidates", "concepts")),
|
|
10288
|
+
ensureDir(path18.join(paths.wikiDir, "candidates", "entities"))
|
|
9922
10289
|
]);
|
|
9923
10290
|
const sync = await syncVaultArtifacts(rootDir, {
|
|
9924
10291
|
schemas,
|
|
@@ -10060,7 +10427,7 @@ async function queryVault(rootDir, options) {
|
|
|
10060
10427
|
assetFiles: staged.assetFiles
|
|
10061
10428
|
}
|
|
10062
10429
|
]);
|
|
10063
|
-
stagedPath =
|
|
10430
|
+
stagedPath = path18.join(approval.approvalDir, "wiki", staged.page.path);
|
|
10064
10431
|
savedPageId = staged.page.id;
|
|
10065
10432
|
approvalId = approval.approvalId;
|
|
10066
10433
|
approvalDir = approval.approvalDir;
|
|
@@ -10316,9 +10683,9 @@ ${orchestrationNotes.join("\n")}
|
|
|
10316
10683
|
approvalId = approval.approvalId;
|
|
10317
10684
|
approvalDir = approval.approvalDir;
|
|
10318
10685
|
stepResults.forEach((result, index) => {
|
|
10319
|
-
result.stagedPath =
|
|
10686
|
+
result.stagedPath = path18.join(approval.approvalDir, "wiki", stagedStepPages[index]?.page.path ?? "");
|
|
10320
10687
|
});
|
|
10321
|
-
stagedHubPath =
|
|
10688
|
+
stagedHubPath = path18.join(approval.approvalDir, "wiki", hubPage.path);
|
|
10322
10689
|
} else {
|
|
10323
10690
|
await refreshVaultAfterOutputSave(rootDir);
|
|
10324
10691
|
}
|
|
@@ -10398,11 +10765,11 @@ async function benchmarkVault(rootDir, options = {}) {
|
|
|
10398
10765
|
}
|
|
10399
10766
|
}
|
|
10400
10767
|
for (const page of graph.pages) {
|
|
10401
|
-
const absolutePath =
|
|
10768
|
+
const absolutePath = path18.join(paths.wikiDir, page.path);
|
|
10402
10769
|
if (!await fileExists(absolutePath)) {
|
|
10403
10770
|
continue;
|
|
10404
10771
|
}
|
|
10405
|
-
const parsed = matter8(await
|
|
10772
|
+
const parsed = matter8(await fs15.readFile(absolutePath, "utf8"));
|
|
10406
10773
|
pageContentsById.set(page.id, parsed.content);
|
|
10407
10774
|
}
|
|
10408
10775
|
const questions = (options.questions ?? []).map((question) => normalizeWhitespace(question)).filter(Boolean);
|
|
@@ -10448,15 +10815,15 @@ async function listPages(rootDir) {
|
|
|
10448
10815
|
}
|
|
10449
10816
|
async function readPage(rootDir, relativePath) {
|
|
10450
10817
|
const { paths } = await loadVaultConfig(rootDir);
|
|
10451
|
-
const absolutePath =
|
|
10818
|
+
const absolutePath = path18.resolve(paths.wikiDir, relativePath);
|
|
10452
10819
|
if (!absolutePath.startsWith(paths.wikiDir) || !await fileExists(absolutePath)) {
|
|
10453
10820
|
return null;
|
|
10454
10821
|
}
|
|
10455
|
-
const raw = await
|
|
10822
|
+
const raw = await fs15.readFile(absolutePath, "utf8");
|
|
10456
10823
|
const parsed = matter8(raw);
|
|
10457
10824
|
return {
|
|
10458
10825
|
path: relativePath,
|
|
10459
|
-
title: typeof parsed.data.title === "string" ? parsed.data.title :
|
|
10826
|
+
title: typeof parsed.data.title === "string" ? parsed.data.title : path18.basename(relativePath, path18.extname(relativePath)),
|
|
10460
10827
|
frontmatter: parsed.data,
|
|
10461
10828
|
content: parsed.content
|
|
10462
10829
|
};
|
|
@@ -10492,7 +10859,7 @@ function structuralLintFindings(_rootDir, paths, graph, schemas, manifests, sour
|
|
|
10492
10859
|
severity: "warning",
|
|
10493
10860
|
code: "stale_page",
|
|
10494
10861
|
message: `Page ${page.title} is stale because the vault schema changed.`,
|
|
10495
|
-
pagePath:
|
|
10862
|
+
pagePath: path18.join(paths.wikiDir, page.path),
|
|
10496
10863
|
relatedPageIds: [page.id]
|
|
10497
10864
|
});
|
|
10498
10865
|
}
|
|
@@ -10503,7 +10870,7 @@ function structuralLintFindings(_rootDir, paths, graph, schemas, manifests, sour
|
|
|
10503
10870
|
severity: "warning",
|
|
10504
10871
|
code: "stale_page",
|
|
10505
10872
|
message: `Page ${page.title} is stale because source ${sourceId} changed.`,
|
|
10506
|
-
pagePath:
|
|
10873
|
+
pagePath: path18.join(paths.wikiDir, page.path),
|
|
10507
10874
|
relatedSourceIds: [sourceId],
|
|
10508
10875
|
relatedPageIds: [page.id]
|
|
10509
10876
|
});
|
|
@@ -10514,13 +10881,13 @@ function structuralLintFindings(_rootDir, paths, graph, schemas, manifests, sour
|
|
|
10514
10881
|
severity: "info",
|
|
10515
10882
|
code: "orphan_page",
|
|
10516
10883
|
message: `Page ${page.title} has no backlinks.`,
|
|
10517
|
-
pagePath:
|
|
10884
|
+
pagePath: path18.join(paths.wikiDir, page.path),
|
|
10518
10885
|
relatedPageIds: [page.id]
|
|
10519
10886
|
});
|
|
10520
10887
|
}
|
|
10521
|
-
const absolutePath =
|
|
10888
|
+
const absolutePath = path18.join(paths.wikiDir, page.path);
|
|
10522
10889
|
if (await fileExists(absolutePath)) {
|
|
10523
|
-
const content = await
|
|
10890
|
+
const content = await fs15.readFile(absolutePath, "utf8");
|
|
10524
10891
|
if (content.includes("## Claims")) {
|
|
10525
10892
|
const uncited = content.split("\n").filter((line) => line.startsWith("- ") && !line.includes("[source:"));
|
|
10526
10893
|
if (uncited.length) {
|
|
@@ -10600,7 +10967,7 @@ async function bootstrapDemo(rootDir, input) {
|
|
|
10600
10967
|
}
|
|
10601
10968
|
|
|
10602
10969
|
// src/mcp.ts
|
|
10603
|
-
var SERVER_VERSION = "0.1.
|
|
10970
|
+
var SERVER_VERSION = "0.1.22";
|
|
10604
10971
|
async function createMcpServer(rootDir) {
|
|
10605
10972
|
const server = new McpServer({
|
|
10606
10973
|
name: "swarmvault",
|
|
@@ -10622,8 +10989,8 @@ async function createMcpServer(rootDir) {
|
|
|
10622
10989
|
{
|
|
10623
10990
|
description: "Search compiled wiki pages using the local full-text index.",
|
|
10624
10991
|
inputSchema: {
|
|
10625
|
-
query:
|
|
10626
|
-
limit:
|
|
10992
|
+
query: z8.string().min(1).describe("Search query"),
|
|
10993
|
+
limit: z8.number().int().min(1).max(25).optional().describe("Maximum number of results")
|
|
10627
10994
|
}
|
|
10628
10995
|
},
|
|
10629
10996
|
async ({ query, limit }) => {
|
|
@@ -10636,7 +11003,7 @@ async function createMcpServer(rootDir) {
|
|
|
10636
11003
|
{
|
|
10637
11004
|
description: "Read a generated wiki page by its path relative to wiki/.",
|
|
10638
11005
|
inputSchema: {
|
|
10639
|
-
path:
|
|
11006
|
+
path: z8.string().min(1).describe("Path relative to wiki/, for example sources/example.md")
|
|
10640
11007
|
}
|
|
10641
11008
|
},
|
|
10642
11009
|
async ({ path: relativePath }) => {
|
|
@@ -10652,7 +11019,7 @@ async function createMcpServer(rootDir) {
|
|
|
10652
11019
|
{
|
|
10653
11020
|
description: "List source manifests in the current workspace.",
|
|
10654
11021
|
inputSchema: {
|
|
10655
|
-
limit:
|
|
11022
|
+
limit: z8.number().int().min(1).max(100).optional().describe("Maximum number of manifests to return")
|
|
10656
11023
|
}
|
|
10657
11024
|
},
|
|
10658
11025
|
async ({ limit }) => {
|
|
@@ -10665,9 +11032,9 @@ async function createMcpServer(rootDir) {
|
|
|
10665
11032
|
{
|
|
10666
11033
|
description: "Traverse the local graph from search seeds without calling a model provider.",
|
|
10667
11034
|
inputSchema: {
|
|
10668
|
-
question:
|
|
10669
|
-
traversal:
|
|
10670
|
-
budget:
|
|
11035
|
+
question: z8.string().min(1).describe("Question or graph search seed"),
|
|
11036
|
+
traversal: z8.enum(["bfs", "dfs"]).optional().describe("Traversal strategy"),
|
|
11037
|
+
budget: z8.number().int().min(3).max(50).optional().describe("Maximum nodes to summarize")
|
|
10671
11038
|
}
|
|
10672
11039
|
},
|
|
10673
11040
|
async ({ question, traversal, budget }) => {
|
|
@@ -10683,7 +11050,7 @@ async function createMcpServer(rootDir) {
|
|
|
10683
11050
|
{
|
|
10684
11051
|
description: "Explain a graph node, its page, community, and neighbors.",
|
|
10685
11052
|
inputSchema: {
|
|
10686
|
-
target:
|
|
11053
|
+
target: z8.string().min(1).describe("Node or page label/id")
|
|
10687
11054
|
}
|
|
10688
11055
|
},
|
|
10689
11056
|
async ({ target }) => {
|
|
@@ -10695,7 +11062,7 @@ async function createMcpServer(rootDir) {
|
|
|
10695
11062
|
{
|
|
10696
11063
|
description: "Return the neighbors of a graph node or page target.",
|
|
10697
11064
|
inputSchema: {
|
|
10698
|
-
target:
|
|
11065
|
+
target: z8.string().min(1).describe("Node or page label/id")
|
|
10699
11066
|
}
|
|
10700
11067
|
},
|
|
10701
11068
|
async ({ target }) => {
|
|
@@ -10708,8 +11075,8 @@ async function createMcpServer(rootDir) {
|
|
|
10708
11075
|
{
|
|
10709
11076
|
description: "Find the shortest graph path between two targets.",
|
|
10710
11077
|
inputSchema: {
|
|
10711
|
-
from:
|
|
10712
|
-
to:
|
|
11078
|
+
from: z8.string().min(1).describe("Start node/page label or id"),
|
|
11079
|
+
to: z8.string().min(1).describe("End node/page label or id")
|
|
10713
11080
|
}
|
|
10714
11081
|
},
|
|
10715
11082
|
async ({ from, to }) => {
|
|
@@ -10721,7 +11088,7 @@ async function createMcpServer(rootDir) {
|
|
|
10721
11088
|
{
|
|
10722
11089
|
description: "List the highest-connectivity graph nodes.",
|
|
10723
11090
|
inputSchema: {
|
|
10724
|
-
limit:
|
|
11091
|
+
limit: z8.number().int().min(1).max(25).optional().describe("Maximum nodes to return")
|
|
10725
11092
|
}
|
|
10726
11093
|
},
|
|
10727
11094
|
async ({ limit }) => {
|
|
@@ -10733,9 +11100,9 @@ async function createMcpServer(rootDir) {
|
|
|
10733
11100
|
{
|
|
10734
11101
|
description: "Ask a question against the compiled vault and optionally save the answer.",
|
|
10735
11102
|
inputSchema: {
|
|
10736
|
-
question:
|
|
10737
|
-
save:
|
|
10738
|
-
format:
|
|
11103
|
+
question: z8.string().min(1).describe("Question to ask the vault"),
|
|
11104
|
+
save: z8.boolean().optional().describe("Persist the answer to wiki/outputs"),
|
|
11105
|
+
format: z8.enum(["markdown", "report", "slides", "chart", "image"]).optional().describe("Output format")
|
|
10739
11106
|
}
|
|
10740
11107
|
},
|
|
10741
11108
|
async ({ question, save, format }) => {
|
|
@@ -10752,7 +11119,7 @@ async function createMcpServer(rootDir) {
|
|
|
10752
11119
|
{
|
|
10753
11120
|
description: "Ingest a local file path or URL into the SwarmVault workspace.",
|
|
10754
11121
|
inputSchema: {
|
|
10755
|
-
input:
|
|
11122
|
+
input: z8.string().min(1).describe("Local path or URL to ingest")
|
|
10756
11123
|
}
|
|
10757
11124
|
},
|
|
10758
11125
|
async ({ input }) => {
|
|
@@ -10765,7 +11132,7 @@ async function createMcpServer(rootDir) {
|
|
|
10765
11132
|
{
|
|
10766
11133
|
description: "Compile source manifests into wiki pages, graph data, and search index.",
|
|
10767
11134
|
inputSchema: {
|
|
10768
|
-
approve:
|
|
11135
|
+
approve: z8.boolean().optional().describe("Stage a review bundle without applying active page changes")
|
|
10769
11136
|
}
|
|
10770
11137
|
},
|
|
10771
11138
|
async ({ approve }) => {
|
|
@@ -10849,7 +11216,7 @@ async function createMcpServer(rootDir) {
|
|
|
10849
11216
|
},
|
|
10850
11217
|
async () => {
|
|
10851
11218
|
const { paths } = await loadVaultConfig(rootDir);
|
|
10852
|
-
const files = (await listFilesRecursive(paths.sessionsDir)).filter((filePath) => filePath.endsWith(".md")).map((filePath) => toPosix(
|
|
11219
|
+
const files = (await listFilesRecursive(paths.sessionsDir)).filter((filePath) => filePath.endsWith(".md")).map((filePath) => toPosix(path19.relative(paths.sessionsDir, filePath))).sort();
|
|
10853
11220
|
return asTextResource("swarmvault://sessions", JSON.stringify(files, null, 2));
|
|
10854
11221
|
}
|
|
10855
11222
|
);
|
|
@@ -10882,8 +11249,8 @@ async function createMcpServer(rootDir) {
|
|
|
10882
11249
|
return asTextResource(`swarmvault://pages/${encodedPath}`, `Page not found: ${relativePath}`);
|
|
10883
11250
|
}
|
|
10884
11251
|
const { paths } = await loadVaultConfig(rootDir);
|
|
10885
|
-
const absolutePath =
|
|
10886
|
-
return asTextResource(`swarmvault://pages/${encodedPath}`, await
|
|
11252
|
+
const absolutePath = path19.resolve(paths.wikiDir, relativePath);
|
|
11253
|
+
return asTextResource(`swarmvault://pages/${encodedPath}`, await fs16.readFile(absolutePath, "utf8"));
|
|
10887
11254
|
}
|
|
10888
11255
|
);
|
|
10889
11256
|
server.registerResource(
|
|
@@ -10891,11 +11258,11 @@ async function createMcpServer(rootDir) {
|
|
|
10891
11258
|
new ResourceTemplate("swarmvault://sessions/{path}", {
|
|
10892
11259
|
list: async () => {
|
|
10893
11260
|
const { paths } = await loadVaultConfig(rootDir);
|
|
10894
|
-
const files = (await listFilesRecursive(paths.sessionsDir)).filter((filePath) => filePath.endsWith(".md")).map((filePath) => toPosix(
|
|
11261
|
+
const files = (await listFilesRecursive(paths.sessionsDir)).filter((filePath) => filePath.endsWith(".md")).map((filePath) => toPosix(path19.relative(paths.sessionsDir, filePath))).sort();
|
|
10895
11262
|
return {
|
|
10896
11263
|
resources: files.map((relativePath) => ({
|
|
10897
11264
|
uri: `swarmvault://sessions/${encodeURIComponent(relativePath)}`,
|
|
10898
|
-
name:
|
|
11265
|
+
name: path19.basename(relativePath, ".md"),
|
|
10899
11266
|
title: relativePath,
|
|
10900
11267
|
description: "SwarmVault session artifact",
|
|
10901
11268
|
mimeType: "text/markdown"
|
|
@@ -10912,11 +11279,11 @@ async function createMcpServer(rootDir) {
|
|
|
10912
11279
|
const { paths } = await loadVaultConfig(rootDir);
|
|
10913
11280
|
const encodedPath = typeof variables.path === "string" ? variables.path : "";
|
|
10914
11281
|
const relativePath = decodeURIComponent(encodedPath);
|
|
10915
|
-
const absolutePath =
|
|
11282
|
+
const absolutePath = path19.resolve(paths.sessionsDir, relativePath);
|
|
10916
11283
|
if (!absolutePath.startsWith(paths.sessionsDir) || !await fileExists(absolutePath)) {
|
|
10917
11284
|
return asTextResource(`swarmvault://sessions/${encodedPath}`, `Session not found: ${relativePath}`);
|
|
10918
11285
|
}
|
|
10919
|
-
return asTextResource(`swarmvault://sessions/${encodedPath}`, await
|
|
11286
|
+
return asTextResource(`swarmvault://sessions/${encodedPath}`, await fs16.readFile(absolutePath, "utf8"));
|
|
10920
11287
|
}
|
|
10921
11288
|
);
|
|
10922
11289
|
return server;
|
|
@@ -10964,13 +11331,13 @@ function asTextResource(uri, text) {
|
|
|
10964
11331
|
}
|
|
10965
11332
|
|
|
10966
11333
|
// src/schedule.ts
|
|
10967
|
-
import
|
|
10968
|
-
import
|
|
11334
|
+
import fs17 from "fs/promises";
|
|
11335
|
+
import path20 from "path";
|
|
10969
11336
|
function scheduleStatePath(schedulesDir, jobId) {
|
|
10970
|
-
return
|
|
11337
|
+
return path20.join(schedulesDir, `${encodeURIComponent(jobId)}.json`);
|
|
10971
11338
|
}
|
|
10972
11339
|
function scheduleLockPath(schedulesDir, jobId) {
|
|
10973
|
-
return
|
|
11340
|
+
return path20.join(schedulesDir, `${encodeURIComponent(jobId)}.lock`);
|
|
10974
11341
|
}
|
|
10975
11342
|
function parseEveryDuration(value) {
|
|
10976
11343
|
const match = value.trim().match(/^(\d+)(m|h|d)$/i);
|
|
@@ -11073,13 +11440,13 @@ async function acquireJobLease(rootDir, jobId) {
|
|
|
11073
11440
|
const { paths } = await loadVaultConfig(rootDir);
|
|
11074
11441
|
const leasePath = scheduleLockPath(paths.schedulesDir, jobId);
|
|
11075
11442
|
await ensureDir(paths.schedulesDir);
|
|
11076
|
-
const handle = await
|
|
11443
|
+
const handle = await fs17.open(leasePath, "wx");
|
|
11077
11444
|
await handle.writeFile(`${process.pid}
|
|
11078
11445
|
${(/* @__PURE__ */ new Date()).toISOString()}
|
|
11079
11446
|
`);
|
|
11080
11447
|
await handle.close();
|
|
11081
11448
|
return async () => {
|
|
11082
|
-
await
|
|
11449
|
+
await fs17.rm(leasePath, { force: true });
|
|
11083
11450
|
};
|
|
11084
11451
|
}
|
|
11085
11452
|
async function listSchedules(rootDir) {
|
|
@@ -11227,15 +11594,15 @@ async function serveSchedules(rootDir, pollMs = 3e4) {
|
|
|
11227
11594
|
|
|
11228
11595
|
// src/viewer.ts
|
|
11229
11596
|
import { execFile } from "child_process";
|
|
11230
|
-
import
|
|
11597
|
+
import fs18 from "fs/promises";
|
|
11231
11598
|
import http from "http";
|
|
11232
|
-
import
|
|
11599
|
+
import path22 from "path";
|
|
11233
11600
|
import { promisify } from "util";
|
|
11234
11601
|
import matter9 from "gray-matter";
|
|
11235
11602
|
import mime2 from "mime-types";
|
|
11236
11603
|
|
|
11237
11604
|
// src/watch.ts
|
|
11238
|
-
import
|
|
11605
|
+
import path21 from "path";
|
|
11239
11606
|
import process2 from "process";
|
|
11240
11607
|
import chokidar from "chokidar";
|
|
11241
11608
|
var MAX_BACKOFF_MS = 3e4;
|
|
@@ -11243,15 +11610,15 @@ var BACKOFF_THRESHOLD = 3;
|
|
|
11243
11610
|
var CRITICAL_THRESHOLD = 10;
|
|
11244
11611
|
var REPO_WATCH_IGNORES = /* @__PURE__ */ new Set([".git", "node_modules", "dist", "build", ".next", "coverage", ".venv", "vendor", "target"]);
|
|
11245
11612
|
function withinRoot2(rootPath, targetPath) {
|
|
11246
|
-
const relative =
|
|
11247
|
-
return relative === "" || !relative.startsWith("..") && !
|
|
11613
|
+
const relative = path21.relative(rootPath, targetPath);
|
|
11614
|
+
return relative === "" || !relative.startsWith("..") && !path21.isAbsolute(relative);
|
|
11248
11615
|
}
|
|
11249
11616
|
function hasIgnoredRepoSegment(baseDir, targetPath) {
|
|
11250
|
-
const relativePath =
|
|
11617
|
+
const relativePath = path21.relative(baseDir, targetPath);
|
|
11251
11618
|
if (!relativePath || relativePath.startsWith("..")) {
|
|
11252
11619
|
return false;
|
|
11253
11620
|
}
|
|
11254
|
-
return relativePath.split(
|
|
11621
|
+
return relativePath.split(path21.sep).some((segment) => REPO_WATCH_IGNORES.has(segment));
|
|
11255
11622
|
}
|
|
11256
11623
|
function workspaceIgnoreRoots(rootDir, paths) {
|
|
11257
11624
|
return [
|
|
@@ -11260,16 +11627,16 @@ function workspaceIgnoreRoots(rootDir, paths) {
|
|
|
11260
11627
|
paths.stateDir,
|
|
11261
11628
|
paths.agentDir,
|
|
11262
11629
|
paths.inboxDir,
|
|
11263
|
-
|
|
11264
|
-
|
|
11265
|
-
|
|
11266
|
-
].map((candidate) =>
|
|
11630
|
+
path21.join(rootDir, ".claude"),
|
|
11631
|
+
path21.join(rootDir, ".cursor"),
|
|
11632
|
+
path21.join(rootDir, ".obsidian")
|
|
11633
|
+
].map((candidate) => path21.resolve(candidate));
|
|
11267
11634
|
}
|
|
11268
11635
|
async function resolveWatchTargets(rootDir, paths, options) {
|
|
11269
|
-
const targets = /* @__PURE__ */ new Set([
|
|
11636
|
+
const targets = /* @__PURE__ */ new Set([path21.resolve(paths.inboxDir)]);
|
|
11270
11637
|
if (options.repo) {
|
|
11271
11638
|
for (const repoRoot of await listTrackedRepoRoots(rootDir)) {
|
|
11272
|
-
targets.add(
|
|
11639
|
+
targets.add(path21.resolve(repoRoot));
|
|
11273
11640
|
}
|
|
11274
11641
|
}
|
|
11275
11642
|
return [...targets].sort((left, right) => left.localeCompare(right));
|
|
@@ -11399,7 +11766,7 @@ async function watchVault(rootDir, options = {}) {
|
|
|
11399
11766
|
const { paths } = await initWorkspace(rootDir);
|
|
11400
11767
|
const baseDebounceMs = options.debounceMs ?? 900;
|
|
11401
11768
|
const ignoredRoots = workspaceIgnoreRoots(rootDir, paths);
|
|
11402
|
-
const inboxWatchRoot =
|
|
11769
|
+
const inboxWatchRoot = path21.resolve(paths.inboxDir);
|
|
11403
11770
|
let watchTargets = await resolveWatchTargets(rootDir, paths, options);
|
|
11404
11771
|
let timer;
|
|
11405
11772
|
let running = false;
|
|
@@ -11413,7 +11780,7 @@ async function watchVault(rootDir, options = {}) {
|
|
|
11413
11780
|
usePolling: true,
|
|
11414
11781
|
interval: 100,
|
|
11415
11782
|
ignored: (targetPath) => {
|
|
11416
|
-
const absolutePath =
|
|
11783
|
+
const absolutePath = path21.resolve(targetPath);
|
|
11417
11784
|
const primaryTarget = watchTargets.filter((watchTarget) => withinRoot2(watchTarget, absolutePath)).sort((left, right) => right.length - left.length)[0] ?? null;
|
|
11418
11785
|
if (!primaryTarget) {
|
|
11419
11786
|
return false;
|
|
@@ -11585,8 +11952,8 @@ async function watchVault(rootDir, options = {}) {
|
|
|
11585
11952
|
}
|
|
11586
11953
|
};
|
|
11587
11954
|
const reasonForPath = (targetPath) => {
|
|
11588
|
-
const baseDir = watchTargets.filter((watchTarget) => withinRoot2(watchTarget,
|
|
11589
|
-
return
|
|
11955
|
+
const baseDir = watchTargets.filter((watchTarget) => withinRoot2(watchTarget, path21.resolve(targetPath))).sort((left, right) => right.length - left.length)[0] ?? paths.inboxDir;
|
|
11956
|
+
return path21.relative(baseDir, targetPath) || ".";
|
|
11590
11957
|
};
|
|
11591
11958
|
watcher.on("add", (filePath) => schedule(`add:${reasonForPath(filePath)}`)).on("change", (filePath) => schedule(`change:${reasonForPath(filePath)}`)).on("unlink", (filePath) => schedule(`unlink:${reasonForPath(filePath)}`)).on("addDir", (dirPath) => schedule(`addDir:${reasonForPath(dirPath)}`)).on("unlinkDir", (dirPath) => schedule(`unlinkDir:${reasonForPath(dirPath)}`)).on("error", (caught) => schedule(`error:${caught instanceof Error ? caught.message : String(caught)}`));
|
|
11592
11959
|
await new Promise((resolve, reject) => {
|
|
@@ -11627,15 +11994,15 @@ async function getWatchStatus(rootDir) {
|
|
|
11627
11994
|
var execFileAsync = promisify(execFile);
|
|
11628
11995
|
async function readViewerPage(rootDir, relativePath) {
|
|
11629
11996
|
const { paths } = await loadVaultConfig(rootDir);
|
|
11630
|
-
const absolutePath =
|
|
11997
|
+
const absolutePath = path22.resolve(paths.wikiDir, relativePath);
|
|
11631
11998
|
if (!absolutePath.startsWith(paths.wikiDir) || !await fileExists(absolutePath)) {
|
|
11632
11999
|
return null;
|
|
11633
12000
|
}
|
|
11634
|
-
const raw = await
|
|
12001
|
+
const raw = await fs18.readFile(absolutePath, "utf8");
|
|
11635
12002
|
const parsed = matter9(raw);
|
|
11636
12003
|
return {
|
|
11637
12004
|
path: relativePath,
|
|
11638
|
-
title: typeof parsed.data.title === "string" ? parsed.data.title :
|
|
12005
|
+
title: typeof parsed.data.title === "string" ? parsed.data.title : path22.basename(relativePath, path22.extname(relativePath)),
|
|
11639
12006
|
frontmatter: parsed.data,
|
|
11640
12007
|
content: parsed.content,
|
|
11641
12008
|
assets: normalizeOutputAssets(parsed.data.output_assets)
|
|
@@ -11643,12 +12010,12 @@ async function readViewerPage(rootDir, relativePath) {
|
|
|
11643
12010
|
}
|
|
11644
12011
|
async function readViewerAsset(rootDir, relativePath) {
|
|
11645
12012
|
const { paths } = await loadVaultConfig(rootDir);
|
|
11646
|
-
const absolutePath =
|
|
12013
|
+
const absolutePath = path22.resolve(paths.wikiDir, relativePath);
|
|
11647
12014
|
if (!absolutePath.startsWith(paths.wikiDir) || !await fileExists(absolutePath)) {
|
|
11648
12015
|
return null;
|
|
11649
12016
|
}
|
|
11650
12017
|
return {
|
|
11651
|
-
buffer: await
|
|
12018
|
+
buffer: await fs18.readFile(absolutePath),
|
|
11652
12019
|
mimeType: mime2.lookup(absolutePath) || "application/octet-stream"
|
|
11653
12020
|
};
|
|
11654
12021
|
}
|
|
@@ -11671,12 +12038,12 @@ async function readJsonBody(request) {
|
|
|
11671
12038
|
return JSON.parse(raw);
|
|
11672
12039
|
}
|
|
11673
12040
|
async function ensureViewerDist(viewerDistDir) {
|
|
11674
|
-
const indexPath =
|
|
12041
|
+
const indexPath = path22.join(viewerDistDir, "index.html");
|
|
11675
12042
|
if (await fileExists(indexPath)) {
|
|
11676
12043
|
return;
|
|
11677
12044
|
}
|
|
11678
|
-
const viewerProjectDir =
|
|
11679
|
-
if (await fileExists(
|
|
12045
|
+
const viewerProjectDir = path22.dirname(viewerDistDir);
|
|
12046
|
+
if (await fileExists(path22.join(viewerProjectDir, "package.json"))) {
|
|
11680
12047
|
await execFileAsync("pnpm", ["build"], { cwd: viewerProjectDir });
|
|
11681
12048
|
}
|
|
11682
12049
|
}
|
|
@@ -11693,7 +12060,7 @@ async function startGraphServer(rootDir, port) {
|
|
|
11693
12060
|
return;
|
|
11694
12061
|
}
|
|
11695
12062
|
response.writeHead(200, { "content-type": "application/json" });
|
|
11696
|
-
response.end(await
|
|
12063
|
+
response.end(await fs18.readFile(paths.graphPath, "utf8"));
|
|
11697
12064
|
return;
|
|
11698
12065
|
}
|
|
11699
12066
|
if (url.pathname === "/api/graph/query") {
|
|
@@ -11825,8 +12192,8 @@ async function startGraphServer(rootDir, port) {
|
|
|
11825
12192
|
return;
|
|
11826
12193
|
}
|
|
11827
12194
|
const relativePath = url.pathname === "/" ? "index.html" : url.pathname.slice(1);
|
|
11828
|
-
const target =
|
|
11829
|
-
const fallback =
|
|
12195
|
+
const target = path22.join(paths.viewerDistDir, relativePath);
|
|
12196
|
+
const fallback = path22.join(paths.viewerDistDir, "index.html");
|
|
11830
12197
|
const filePath = await fileExists(target) ? target : fallback;
|
|
11831
12198
|
if (!await fileExists(filePath)) {
|
|
11832
12199
|
response.writeHead(503, { "content-type": "text/plain" });
|
|
@@ -11834,7 +12201,7 @@ async function startGraphServer(rootDir, port) {
|
|
|
11834
12201
|
return;
|
|
11835
12202
|
}
|
|
11836
12203
|
response.writeHead(200, { "content-type": mime2.lookup(filePath) || "text/plain" });
|
|
11837
|
-
response.end(await
|
|
12204
|
+
response.end(await fs18.readFile(filePath));
|
|
11838
12205
|
});
|
|
11839
12206
|
await new Promise((resolve) => {
|
|
11840
12207
|
server.listen(effectivePort, resolve);
|
|
@@ -11861,7 +12228,7 @@ async function exportGraphHtml(rootDir, outputPath) {
|
|
|
11861
12228
|
throw new Error("Graph artifact not found. Run `swarmvault compile` first.");
|
|
11862
12229
|
}
|
|
11863
12230
|
await ensureViewerDist(paths.viewerDistDir);
|
|
11864
|
-
const indexPath =
|
|
12231
|
+
const indexPath = path22.join(paths.viewerDistDir, "index.html");
|
|
11865
12232
|
if (!await fileExists(indexPath)) {
|
|
11866
12233
|
throw new Error("Viewer build not found. Run `pnpm build` first.");
|
|
11867
12234
|
}
|
|
@@ -11885,16 +12252,16 @@ async function exportGraphHtml(rootDir, outputPath) {
|
|
|
11885
12252
|
} : null;
|
|
11886
12253
|
})
|
|
11887
12254
|
);
|
|
11888
|
-
const rawHtml = await
|
|
12255
|
+
const rawHtml = await fs18.readFile(indexPath, "utf8");
|
|
11889
12256
|
const scriptMatch = rawHtml.match(/<script type="module" crossorigin src="([^"]+)"><\/script>/);
|
|
11890
12257
|
const styleMatch = rawHtml.match(/<link rel="stylesheet" crossorigin href="([^"]+)">/);
|
|
11891
|
-
const scriptPath = scriptMatch?.[1] ?
|
|
11892
|
-
const stylePath = styleMatch?.[1] ?
|
|
12258
|
+
const scriptPath = scriptMatch?.[1] ? path22.join(paths.viewerDistDir, scriptMatch[1].replace(/^\//, "")) : null;
|
|
12259
|
+
const stylePath = styleMatch?.[1] ? path22.join(paths.viewerDistDir, styleMatch[1].replace(/^\//, "")) : null;
|
|
11893
12260
|
if (!scriptPath || !await fileExists(scriptPath)) {
|
|
11894
12261
|
throw new Error("Viewer script bundle not found. Run `pnpm build` first.");
|
|
11895
12262
|
}
|
|
11896
|
-
const script = await
|
|
11897
|
-
const style = stylePath && await fileExists(stylePath) ? await
|
|
12263
|
+
const script = await fs18.readFile(scriptPath, "utf8");
|
|
12264
|
+
const style = stylePath && await fileExists(stylePath) ? await fs18.readFile(stylePath, "utf8") : "";
|
|
11898
12265
|
const embeddedData = JSON.stringify({ graph, pages: pages.filter(Boolean) }, null, 2).replace(/</g, "\\u003c");
|
|
11899
12266
|
const html = [
|
|
11900
12267
|
"<!doctype html>",
|
|
@@ -11913,9 +12280,9 @@ async function exportGraphHtml(rootDir, outputPath) {
|
|
|
11913
12280
|
"</html>",
|
|
11914
12281
|
""
|
|
11915
12282
|
].filter(Boolean).join("\n");
|
|
11916
|
-
await
|
|
11917
|
-
await
|
|
11918
|
-
return
|
|
12283
|
+
await fs18.mkdir(path22.dirname(outputPath), { recursive: true });
|
|
12284
|
+
await fs18.writeFile(outputPath, html, "utf8");
|
|
12285
|
+
return path22.resolve(outputPath);
|
|
11919
12286
|
}
|
|
11920
12287
|
export {
|
|
11921
12288
|
acceptApproval,
|