@bendyline/gezel-service 0.1.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/NOTICE.md +1 -1
- package/dist/bin/gezeld.js +51635 -50059
- package/dist/handboek-content/README.md +24 -2
- package/dist/handboek-content/conceptual/connected-apps.md +2 -0
- package/dist/handboek-content/technical/building-connected-apps-with-gezel-app-sdk.md +144 -0
- package/dist/handboek-content/technical/cli-reference.md +11 -0
- package/dist/handboek-content/technical/how-we-test-models.md +57 -9
- package/dist/handboek-content/technical/model-scorecard.md +27 -9
- package/dist/handboek-content/technical/npm-packages.md +67 -0
- package/dist/handboek-content/technical/security-model.md +18 -1
- package/dist/handboek-content/technical/where-files-live.md +3 -0
- package/dist/handboek-content/technical/writing-scripts-with-gezel-sdk.md +106 -0
- package/dist/handboek-content/whats-new/1.26223.md +82 -0
- package/dist/handboek-content/whats-new/whats-new-index.md +28 -0
- package/dist/handboek.d.ts +53 -26
- package/dist/handboek.js +103 -26
- package/dist/index-store/static-index-worker.js +476 -82
- package/dist/index.d.ts +206 -11
- package/dist/index.js +51615 -50051
- package/dist/ui/assets/{TerminalCodeEditor-CB-AXtKI.js → TerminalCodeEditor-DQICIlaI.js} +2 -2
- package/dist/ui/assets/{_.contribution-C6KlZ1yR.js → _.contribution-BXBGjh98.js} +1 -1
- package/dist/ui/assets/{chunk-6N2J7C2B-DIF7DJtk.js → chunk-6N2J7C2B-Du7clJkV.js} +1 -1
- package/dist/ui/assets/chunk-ILCJ3WFD-fHFoZ9wm.js +4 -0
- package/dist/ui/assets/{chunk-S5PCVMKU-CmFF6ff9.js → chunk-S5PCVMKU-DopU3Q9j.js} +2 -2
- package/dist/ui/assets/chunk-USU6HTKB-B-ElCUQS.js +1 -0
- package/dist/ui/assets/{chunk-WATAOG4Y-perfmDXF.js → chunk-WATAOG4Y-BnCgSncI.js} +1 -1
- package/dist/ui/assets/{cpp.contribution-BBBWN3Dm.js → cpp.contribution-UC-hXlnG.js} +1 -1
- package/dist/ui/assets/{csharp.contribution-jBA6TWhG.js → csharp.contribution-VSLdDbxa.js} +1 -1
- package/dist/ui/assets/{css.contribution-DQSNU6vC.js → css.contribution-DC9uQgZx.js} +1 -1
- package/dist/ui/assets/{cssMode-68UP_lrS.js → cssMode-DZsXawIN.js} +1 -1
- package/dist/ui/assets/{csv-Ctux8fO7.js → csv-RAgECoUg.js} +1 -1
- package/dist/ui/assets/dist-D6I_6t8i.js +195 -0
- package/dist/ui/assets/dist-a57vyaRu.js +2 -0
- package/dist/ui/assets/{doc-BdMAKGDm.js → doc-B7NXSLxH.js} +33 -23
- package/dist/ui/assets/{dockerfile.contribution-CSpSPKgK.js → dockerfile.contribution-PUE2MRJF.js} +1 -1
- package/dist/ui/assets/docx-IZY6hX8A.js +2 -0
- package/dist/ui/assets/{epub-oLed1tAU.js → epub-00L2M424.js} +1 -1
- package/dist/ui/assets/extract-UTAOG7LV-BZ_L7lLL.js +1 -0
- package/dist/ui/assets/{go.contribution-BjibqVfF.js → go.contribution-CsrBAk1W.js} +1 -1
- package/dist/ui/assets/{handlebars-Dq5dJ5WJ.js → handlebars-Bqyi6xyi.js} +1 -1
- package/dist/ui/assets/handlebars.contribution-C6VLDI51.js +2 -0
- package/dist/ui/assets/{html-fQ6X2xaa.js → html-Cgejr9YM.js} +24 -24
- package/dist/ui/assets/{html-Bgyk9kiZ.js → html-NLb64cMt.js} +1 -1
- package/dist/ui/assets/html.contribution-BP2a6izy.js +2 -0
- package/dist/ui/assets/{htmlMode-D5UKInqK.js → htmlMode-Utzysyes.js} +1 -1
- package/dist/ui/assets/{index-CPXp6BEZ.js → index-B_NFDPxp.js} +197 -195
- package/dist/ui/assets/index-CgP6VwNS.css +1 -0
- package/dist/ui/assets/infer-v-bCXeT9.js +2 -0
- package/dist/ui/assets/{ini.contribution-BiJ4T5K-.js → ini.contribution-CfehzoVj.js} +1 -1
- package/dist/ui/assets/{java.contribution-B8o1lBqP.js → java.contribution-CXk8bwAp.js} +1 -1
- package/dist/ui/assets/{javascript-COWkS1tr.js → javascript-BcbxETop.js} +1 -1
- package/dist/ui/assets/javascript.contribution-BRDuHf7J.js +2 -0
- package/dist/ui/assets/{jsonMode-CWWZvfzr.js → jsonMode-BORbF5qz.js} +1 -1
- package/dist/ui/assets/{kotlin.contribution-CxSOPQ6P.js → kotlin.contribution-CbKeqa_3.js} +1 -1
- package/dist/ui/assets/layouts-2A7QNF6Y-D94iKLLQ.js +1 -0
- package/dist/ui/assets/{less.contribution-Cthyflev.js → less.contribution-BgNGBZAo.js} +1 -1
- package/dist/ui/assets/{lua.contribution-894DRgPR.js → lua.contribution-CtZNsqOj.js} +1 -1
- package/dist/ui/assets/{markdown.contribution-d_TBTsNA.js → markdown.contribution-CNyxmHy1.js} +1 -1
- package/dist/ui/assets/{monaco-PebRhLpo.js → monaco-Ryh-SF_b.js} +3 -3
- package/dist/ui/assets/{monaco-base-4S0M5BiY.js → monaco-base-C-4W0je-.js} +1 -1
- package/dist/ui/assets/{monaco-setup-7hp0Z9jn.js → monaco-setup-BW0DQ8MV.js} +1 -1
- package/dist/ui/assets/{monaco.contribution-DQjPk7Rx.js → monaco.contribution-279M9-Uu.js} +2 -2
- package/dist/ui/assets/{monaco.contribution-CKDvyGVE.js → monaco.contribution-Bu1oyef5.js} +2 -2
- package/dist/ui/assets/{monaco.contribution-BL_zgiu2.js → monaco.contribution-C57BAnnI.js} +2 -2
- package/dist/ui/assets/{monaco.contribution-D61txvUB.js → monaco.contribution-o3cuP4Wp.js} +2 -2
- package/dist/ui/assets/{pdf-CwJ6ZJmB.js → pdf-66KslaXf.js} +1 -1
- package/dist/ui/assets/{php.contribution-B8kn8QoR.js → php.contribution-BulxzIpe.js} +1 -1
- package/dist/ui/assets/pptx-CsdVR0xn.js +6 -0
- package/dist/ui/assets/{python-CHygH1eM.js → python-D4zGC71G.js} +1 -1
- package/dist/ui/assets/python.contribution-CDI1t73R.js +2 -0
- package/dist/ui/assets/{ruby.contribution-BQdPKFM2.js → ruby.contribution-DVOdhLt8.js} +1 -1
- package/dist/ui/assets/{rust.contribution-D7wKqShc.js → rust.contribution-DLR9kg2m.js} +1 -1
- package/dist/ui/assets/{scss.contribution-kSTLMoty.js → scss.contribution-CYIIxBxH.js} +1 -1
- package/dist/ui/assets/{shell.contribution-BQnT3BBJ.js → shell.contribution-D0KjHQ5E.js} +1 -1
- package/dist/ui/assets/{sql.contribution-C9z55nWb.js → sql.contribution-CKhLelqr.js} +1 -1
- package/dist/ui/assets/{swift.contribution-BZoW5ZZj.js → swift.contribution-DiAAMfJp.js} +1 -1
- package/dist/ui/assets/{terminal-monaco-setup-DbSXji0d.js → terminal-monaco-setup-Dhxpm7Z8.js} +1 -1
- package/dist/ui/assets/{tsMode-jPpKNC6E.js → tsMode-DFEqg3P9.js} +1 -1
- package/dist/ui/assets/{typescript-Djn_8BHF.js → typescript-X_x8S3Dp.js} +1 -1
- package/dist/ui/assets/typescript.contribution-jI3BakLa.js +2 -0
- package/dist/ui/assets/xlsx-CdLmEC42.js +4 -0
- package/dist/ui/assets/{xml-DmE4Aqa-.js → xml-ANmRi8ZZ.js} +1 -1
- package/dist/ui/assets/xml.contribution-D5t83zAi.js +2 -0
- package/dist/ui/assets/{yaml-BjmK3C-c.js → yaml-R4WpJved.js} +1 -1
- package/dist/ui/assets/yaml.contribution-DV8dAjKa.js +2 -0
- package/dist/ui/index.html +15 -15
- package/package.json +13 -15
- package/dist/postinstall.d.ts +0 -2
- package/dist/postinstall.js +0 -40
- package/dist/ui/assets/chunk-ILCJ3WFD-uwKSpQd8.js +0 -4
- package/dist/ui/assets/chunk-USU6HTKB-hbaA8KyS.js +0 -1
- package/dist/ui/assets/dist-i9jV8CX8.js +0 -2
- package/dist/ui/assets/dist-jh33rACm.js +0 -195
- package/dist/ui/assets/docx-CNW-xqHT.js +0 -2
- package/dist/ui/assets/extract-UTAOG7LV-WxmEnrrc.js +0 -1
- package/dist/ui/assets/handlebars.contribution-DDSQBNPu.js +0 -2
- package/dist/ui/assets/html.contribution-DrClfj2L.js +0 -2
- package/dist/ui/assets/index-Caz-FRQy.css +0 -1
- package/dist/ui/assets/infer-Cc7ks6NX.js +0 -2
- package/dist/ui/assets/javascript.contribution-DMEzq0y4.js +0 -2
- package/dist/ui/assets/layouts-2A7QNF6Y-DR7-KboS.js +0 -1
- package/dist/ui/assets/pptx-Bmj2v9CT.js +0 -6
- package/dist/ui/assets/python.contribution-CmwCxp-v.js +0 -2
- package/dist/ui/assets/typescript.contribution-D4JmB9tz.js +0 -2
- package/dist/ui/assets/xlsx-BCnO44v8.js +0 -4
- package/dist/ui/assets/xml.contribution-BepqhMeK.js +0 -2
- package/dist/ui/assets/yaml.contribution-BwT8qEVt.js +0 -2
- package/postinstall.mjs +0 -8
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
import { parentPort } from "worker_threads";
|
|
5
5
|
|
|
6
6
|
// src/index-store/content-indexer.ts
|
|
7
|
-
import { readFile as readFile3 } from "fs/promises";
|
|
8
|
-
import { extname as extname3 } from "path";
|
|
7
|
+
import { readFile as readFile3, readdir as readdir2, rm as rm4, stat as stat3 } from "fs/promises";
|
|
8
|
+
import { extname as extname3, join as join10 } from "path";
|
|
9
9
|
import { nowIso as nowIso2 } from "@bendyline/gezel";
|
|
10
|
-
import { projectLocalIndexDbFile } from "@bendyline/gezel/paths";
|
|
10
|
+
import { PROJECT_SHADOW_DIR_NAME as PROJECT_SHADOW_DIR_NAME2, projectLocalIndexDbFile } from "@bendyline/gezel/paths";
|
|
11
11
|
|
|
12
12
|
// src/security/code-patterns.ts
|
|
13
13
|
var EVIDENCE_CAP = 160;
|
|
@@ -560,7 +560,17 @@ var CODE_LANGS = {
|
|
|
560
560
|
bash: "bash",
|
|
561
561
|
zsh: "bash",
|
|
562
562
|
lua: "lua",
|
|
563
|
-
sql: "sql"
|
|
563
|
+
sql: "sql",
|
|
564
|
+
// Web-source formats. Without these a project's index.html fell through to
|
|
565
|
+
// kind 'other' — no review rubric, no security scan, excluded from every
|
|
566
|
+
// coverage denominator — while the status popover read "complete". Symbol
|
|
567
|
+
// extraction still gates on GRAMMAR_FILE separately, so a lang here without
|
|
568
|
+
// a wired grammar degrades to zero symbols, not an error.
|
|
569
|
+
html: "html",
|
|
570
|
+
htm: "html",
|
|
571
|
+
css: "css",
|
|
572
|
+
vue: "vue",
|
|
573
|
+
svelte: "svelte"
|
|
564
574
|
};
|
|
565
575
|
var MARKDOWN_EXTS = /* @__PURE__ */ new Set(["md", "mdx", "markdown"]);
|
|
566
576
|
var DOC_EXTS = /* @__PURE__ */ new Set(["docx", "pptx", "xlsx", "pdf", "odt", "odp", "ods", "rtf"]);
|
|
@@ -612,14 +622,11 @@ var BINARY_EXTS = /* @__PURE__ */ new Set([
|
|
|
612
622
|
"avi",
|
|
613
623
|
"mkv",
|
|
614
624
|
"webm",
|
|
615
|
-
"mp3",
|
|
616
|
-
"wav",
|
|
617
|
-
"flac",
|
|
618
|
-
"ogg",
|
|
619
625
|
"db",
|
|
620
626
|
"sqlite",
|
|
621
627
|
"lock"
|
|
622
628
|
]);
|
|
629
|
+
var AUDIO_EXTS = /* @__PURE__ */ new Set(["mp3", "wav", "flac", "ogg", "oga", "m4a", "aac", "opus"]);
|
|
623
630
|
var MAX_INDEXABLE_BYTES = 512 * 1024;
|
|
624
631
|
var DENSE_BLOB_MIN_BYTES = 100 * 1024;
|
|
625
632
|
var DENSE_BLOB_BYTES_PER_LINE = 1e3;
|
|
@@ -645,6 +652,7 @@ function classifyFile(path, size) {
|
|
|
645
652
|
}
|
|
646
653
|
if (DOC_EXTS.has(ext)) return { lang: null, kind: "doc", modality: "doc", trivial: false };
|
|
647
654
|
if (IMAGE_EXTS.has(ext)) return { lang: null, kind: "image", modality: "image", trivial: false };
|
|
655
|
+
if (AUDIO_EXTS.has(ext)) return { lang: null, kind: "audio", modality: "audio", trivial: false };
|
|
648
656
|
if (CONFIG_EXTS.has(ext)) {
|
|
649
657
|
return { lang: ext, kind: "config", modality: "text", trivial: size > MAX_INDEXABLE_BYTES };
|
|
650
658
|
}
|
|
@@ -663,8 +671,8 @@ function triv(kind) {
|
|
|
663
671
|
|
|
664
672
|
// src/index-store/docs.ts
|
|
665
673
|
import { mkdir, readFile as readFile2, stat as stat2 } from "fs/promises";
|
|
666
|
-
import { basename as
|
|
667
|
-
import {
|
|
674
|
+
import { basename as basename3, dirname as dirname5, extname as extname2, join as join6, relative as relative2 } from "path";
|
|
675
|
+
import { PROJECT_SHADOW_DIR_NAME } from "@bendyline/gezel/paths";
|
|
668
676
|
|
|
669
677
|
// src/fs/atomic.ts
|
|
670
678
|
import { randomUUID } from "crypto";
|
|
@@ -737,12 +745,101 @@ async function renameWithWindowsRetry(from, to) {
|
|
|
737
745
|
}
|
|
738
746
|
}
|
|
739
747
|
|
|
748
|
+
// src/fs/safe-paths.ts
|
|
749
|
+
import { realpath as realpath2 } from "fs/promises";
|
|
750
|
+
import { basename as basename2, dirname as dirname2, isAbsolute, join as join2, normalize, sep } from "path";
|
|
751
|
+
function pathEq(a, b) {
|
|
752
|
+
if (process.platform === "win32" || process.platform === "darwin") {
|
|
753
|
+
return a.toLowerCase() === b.toLowerCase();
|
|
754
|
+
}
|
|
755
|
+
return a === b;
|
|
756
|
+
}
|
|
757
|
+
function pathStartsWithDir(candidate, base) {
|
|
758
|
+
if (process.platform === "win32" || process.platform === "darwin") {
|
|
759
|
+
return candidate.toLowerCase().startsWith(base.toLowerCase() + sep.toLowerCase());
|
|
760
|
+
}
|
|
761
|
+
return candidate.startsWith(base + sep);
|
|
762
|
+
}
|
|
763
|
+
function safeJoin(base, relPath) {
|
|
764
|
+
if (typeof relPath !== "string") return null;
|
|
765
|
+
if (hasUnsafePortableSegment(relPath)) return null;
|
|
766
|
+
if (isAbsolute(relPath)) return null;
|
|
767
|
+
const normalizedBase = normalize(base);
|
|
768
|
+
const candidate = normalize(join2(normalizedBase, relPath));
|
|
769
|
+
if (pathEq(candidate, normalizedBase)) return candidate;
|
|
770
|
+
if (pathStartsWithDir(candidate, normalizedBase)) return candidate;
|
|
771
|
+
return null;
|
|
772
|
+
}
|
|
773
|
+
async function realpathContained(base, fullPath) {
|
|
774
|
+
const realBase = await realpathSafe(base);
|
|
775
|
+
if (!realBase) return false;
|
|
776
|
+
const realTarget = await realpathNearest(fullPath);
|
|
777
|
+
if (!realTarget) return false;
|
|
778
|
+
if (pathEq(realTarget, realBase)) return true;
|
|
779
|
+
return pathStartsWithDir(realTarget, realBase);
|
|
780
|
+
}
|
|
781
|
+
async function realpathSafe(p) {
|
|
782
|
+
try {
|
|
783
|
+
return normalize(await realpath2(p));
|
|
784
|
+
} catch {
|
|
785
|
+
return null;
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
async function realpathNearest(p) {
|
|
789
|
+
const direct = await realpathSafe(p);
|
|
790
|
+
if (direct) return direct;
|
|
791
|
+
const parent = dirname2(p);
|
|
792
|
+
if (parent === p) return null;
|
|
793
|
+
const realParent = await realpathNearest(parent);
|
|
794
|
+
if (!realParent) return null;
|
|
795
|
+
return normalize(join2(realParent, basename2(p)));
|
|
796
|
+
}
|
|
797
|
+
var RESERVED_WINDOWS_BASENAMES = /* @__PURE__ */ new Set([
|
|
798
|
+
"CON",
|
|
799
|
+
"PRN",
|
|
800
|
+
"AUX",
|
|
801
|
+
"NUL",
|
|
802
|
+
"COM1",
|
|
803
|
+
"COM2",
|
|
804
|
+
"COM3",
|
|
805
|
+
"COM4",
|
|
806
|
+
"COM5",
|
|
807
|
+
"COM6",
|
|
808
|
+
"COM7",
|
|
809
|
+
"COM8",
|
|
810
|
+
"COM9",
|
|
811
|
+
"LPT1",
|
|
812
|
+
"LPT2",
|
|
813
|
+
"LPT3",
|
|
814
|
+
"LPT4",
|
|
815
|
+
"LPT5",
|
|
816
|
+
"LPT6",
|
|
817
|
+
"LPT7",
|
|
818
|
+
"LPT8",
|
|
819
|
+
"LPT9"
|
|
820
|
+
]);
|
|
821
|
+
function isReservedWindowsName(name) {
|
|
822
|
+
if (!name) return false;
|
|
823
|
+
const normalized = name.replace(/[ .]+$/g, "");
|
|
824
|
+
const base = (normalized.split(".")[0] ?? "").replace(/[ ]+$/g, "");
|
|
825
|
+
return RESERVED_WINDOWS_BASENAMES.has(base.toUpperCase());
|
|
826
|
+
}
|
|
827
|
+
function hasUnsafePortableSegment(relPath) {
|
|
828
|
+
if (relPath.includes("\0") || relPath.startsWith("\\\\")) return true;
|
|
829
|
+
const segments = relPath.split(/[\\/]+/);
|
|
830
|
+
return segments.some((segment) => {
|
|
831
|
+
if (!segment || segment === "." || segment === "..") return false;
|
|
832
|
+
if (segment.includes(":") || /[ .]$/.test(segment)) return true;
|
|
833
|
+
return isReservedWindowsName(segment);
|
|
834
|
+
});
|
|
835
|
+
}
|
|
836
|
+
|
|
740
837
|
// src/index-store/sandbox-convert.ts
|
|
741
838
|
import { existsSync as existsSync2 } from "fs";
|
|
742
|
-
import { mkdtemp as mkdtemp2, readFile, realpath as
|
|
839
|
+
import { mkdtemp as mkdtemp2, readFile, realpath as realpath4, rm as rm3, stat, writeFile as writeFile3 } from "fs/promises";
|
|
743
840
|
import { createRequire } from "module";
|
|
744
841
|
import { tmpdir as tmpdir2 } from "os";
|
|
745
|
-
import { dirname as
|
|
842
|
+
import { dirname as dirname4, join as join5 } from "path";
|
|
746
843
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
747
844
|
import { createLogger } from "@bendyline/gezel";
|
|
748
845
|
|
|
@@ -911,9 +1008,9 @@ function sniffContainer(bytes, ext) {
|
|
|
911
1008
|
|
|
912
1009
|
// src/sandbox/runner.ts
|
|
913
1010
|
import { spawn as spawn2 } from "child_process";
|
|
914
|
-
import { mkdtemp, realpath as
|
|
1011
|
+
import { mkdtemp, realpath as realpath3, rm as rm2, writeFile as writeFile2 } from "fs/promises";
|
|
915
1012
|
import { tmpdir } from "os";
|
|
916
|
-
import { isAbsolute, join as
|
|
1013
|
+
import { isAbsolute as isAbsolute2, join as join3 } from "path";
|
|
917
1014
|
import { pathToFileURL } from "url";
|
|
918
1015
|
import { windowsDetachedSpawnOptions } from "@bendyline/gezel/native";
|
|
919
1016
|
|
|
@@ -966,11 +1063,11 @@ async function runInSandbox(opts) {
|
|
|
966
1063
|
for (const line of splitLines(result.stderr)) opts.onStderr?.(line);
|
|
967
1064
|
return result;
|
|
968
1065
|
}
|
|
969
|
-
const workdir = await
|
|
1066
|
+
const workdir = await realpath3(await mkdtemp(join3(tmpdir(), "gezel-task-")));
|
|
970
1067
|
try {
|
|
971
|
-
const cwd = await
|
|
972
|
-
const entryCandidate =
|
|
973
|
-
const entry = await
|
|
1068
|
+
const cwd = await realpath3(opts.cwd).catch(() => opts.cwd);
|
|
1069
|
+
const entryCandidate = isAbsolute2(opts.entry) ? opts.entry : join3(cwd, opts.entry);
|
|
1070
|
+
const entry = await realpath3(entryCandidate).catch(() => opts.entry);
|
|
974
1071
|
const readPaths = await expandPermissionPaths([
|
|
975
1072
|
opts.cwd,
|
|
976
1073
|
workdir,
|
|
@@ -979,7 +1076,7 @@ async function runInSandbox(opts) {
|
|
|
979
1076
|
const writePaths = await expandPermissionPaths([opts.cwd, workdir]);
|
|
980
1077
|
const netBlockArgs = [];
|
|
981
1078
|
if (opts.denyNet) {
|
|
982
|
-
const preloadPath =
|
|
1079
|
+
const preloadPath = join3(workdir, "__net_block.mjs");
|
|
983
1080
|
await writeFile2(preloadPath, NET_BLOCK_PRELOAD, "utf8");
|
|
984
1081
|
netBlockArgs.push(`--import=${pathToFileURL(preloadPath).href}`);
|
|
985
1082
|
}
|
|
@@ -1210,7 +1307,7 @@ async function expandPermissionPaths(paths) {
|
|
|
1210
1307
|
const expanded = [];
|
|
1211
1308
|
const seen = /* @__PURE__ */ new Set();
|
|
1212
1309
|
for (const p of paths) {
|
|
1213
|
-
for (const candidate of [p, await
|
|
1310
|
+
for (const candidate of [p, await realpath3(p).catch(() => p)]) {
|
|
1214
1311
|
if (seen.has(candidate)) continue;
|
|
1215
1312
|
seen.add(candidate);
|
|
1216
1313
|
expanded.push(candidate);
|
|
@@ -1448,7 +1545,7 @@ syncBuiltinESMExports();
|
|
|
1448
1545
|
|
|
1449
1546
|
// src/utils/service-worker-entry.ts
|
|
1450
1547
|
import { existsSync } from "fs";
|
|
1451
|
-
import { dirname as
|
|
1548
|
+
import { dirname as dirname3, join as join4, resolve } from "path";
|
|
1452
1549
|
import { fileURLToPath } from "url";
|
|
1453
1550
|
var SERVICE_WORKER_ENTRIES = {
|
|
1454
1551
|
embeddings: { built: "memory/embed-worker.js" },
|
|
@@ -1461,10 +1558,10 @@ var SERVICE_WORKER_ENTRIES = {
|
|
|
1461
1558
|
};
|
|
1462
1559
|
var MAX_ANCESTOR_DEPTH = 8;
|
|
1463
1560
|
function findServiceBundleResource(moduleUrl, relativePaths) {
|
|
1464
|
-
let dir =
|
|
1561
|
+
let dir = dirname3(fileURLToPath(moduleUrl));
|
|
1465
1562
|
const visited = /* @__PURE__ */ new Set();
|
|
1466
1563
|
for (let depth = 0; depth < MAX_ANCESTOR_DEPTH; depth++) {
|
|
1467
|
-
for (const root of [dir,
|
|
1564
|
+
for (const root of [dir, join4(dir, "dist")]) {
|
|
1468
1565
|
for (const relativePath of relativePaths) {
|
|
1469
1566
|
const candidate = resolve(root, relativePath);
|
|
1470
1567
|
if (visited.has(candidate)) continue;
|
|
@@ -1472,7 +1569,7 @@ function findServiceBundleResource(moduleUrl, relativePaths) {
|
|
|
1472
1569
|
if (existsSync(candidate)) return candidate;
|
|
1473
1570
|
}
|
|
1474
1571
|
}
|
|
1475
|
-
const parent =
|
|
1572
|
+
const parent = dirname3(dir);
|
|
1476
1573
|
if (parent === dir) break;
|
|
1477
1574
|
dir = parent;
|
|
1478
1575
|
}
|
|
@@ -1494,8 +1591,8 @@ function findRepoRoot(start) {
|
|
|
1494
1591
|
let dir = start;
|
|
1495
1592
|
let highest = null;
|
|
1496
1593
|
for (; ; ) {
|
|
1497
|
-
if (existsSync2(
|
|
1498
|
-
const parent =
|
|
1594
|
+
if (existsSync2(join5(dir, "node_modules"))) highest = dir;
|
|
1595
|
+
const parent = dirname4(dir);
|
|
1499
1596
|
if (parent === dir) break;
|
|
1500
1597
|
dir = parent;
|
|
1501
1598
|
}
|
|
@@ -1505,12 +1602,12 @@ var readRootsCache;
|
|
|
1505
1602
|
function sandboxReadRoots() {
|
|
1506
1603
|
if (readRootsCache !== void 0) return readRootsCache;
|
|
1507
1604
|
const roots = /* @__PURE__ */ new Set();
|
|
1508
|
-
const repoRoot = findRepoRoot(
|
|
1605
|
+
const repoRoot = findRepoRoot(dirname4(fileURLToPath2(import.meta.url)));
|
|
1509
1606
|
if (repoRoot) roots.add(repoRoot);
|
|
1510
1607
|
try {
|
|
1511
1608
|
const require2 = createRequire(import.meta.url);
|
|
1512
1609
|
const resolved = require2.resolve("@bendyline/squisq-formats");
|
|
1513
|
-
const linkedRoot = findRepoRoot(
|
|
1610
|
+
const linkedRoot = findRepoRoot(dirname4(resolved));
|
|
1514
1611
|
if (linkedRoot) roots.add(linkedRoot);
|
|
1515
1612
|
} catch {
|
|
1516
1613
|
readRootsCache = null;
|
|
@@ -1557,10 +1654,10 @@ async function convertInSandbox(absPath, ext) {
|
|
|
1557
1654
|
log.warn("sandbox unavailable (worker or node_modules root unresolved); skipping conversion");
|
|
1558
1655
|
return { markdown: null };
|
|
1559
1656
|
}
|
|
1560
|
-
const scratch = await
|
|
1657
|
+
const scratch = await realpath4(await mkdtemp2(join5(tmpdir2(), "gezel-convert-")));
|
|
1561
1658
|
try {
|
|
1562
1659
|
const inputName = `input.${ext}`;
|
|
1563
|
-
await writeFile3(
|
|
1660
|
+
await writeFile3(join5(scratch, inputName), bytes);
|
|
1564
1661
|
const res = await runInSandbox({
|
|
1565
1662
|
entry: worker.path,
|
|
1566
1663
|
cwd: scratch,
|
|
@@ -1585,7 +1682,7 @@ async function convertInSandbox(absPath, ext) {
|
|
|
1585
1682
|
);
|
|
1586
1683
|
return { markdown: null };
|
|
1587
1684
|
}
|
|
1588
|
-
const md = await readFile(
|
|
1685
|
+
const md = await readFile(join5(scratch, "output.md"), "utf8").catch(() => null);
|
|
1589
1686
|
return { markdown: md };
|
|
1590
1687
|
} finally {
|
|
1591
1688
|
await rm3(scratch, { recursive: true, force: true });
|
|
@@ -1603,16 +1700,19 @@ async function convertDocToMarkdown(absPath) {
|
|
|
1603
1700
|
if (!isConvertibleDoc(ext)) return { markdown: null };
|
|
1604
1701
|
return convertInSandbox(absPath, ext);
|
|
1605
1702
|
}
|
|
1606
|
-
function
|
|
1607
|
-
const base =
|
|
1608
|
-
const nameNoExt = base.replace(/\.[^.]+$/, "");
|
|
1609
|
-
const parent =
|
|
1610
|
-
const
|
|
1611
|
-
const
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1703
|
+
function shadowDocFilesPaths(artifactsDir, relPath) {
|
|
1704
|
+
const base = basename3(relPath);
|
|
1705
|
+
const nameNoExt = base.replace(/\.[^.]+$/, "") || base;
|
|
1706
|
+
const parent = dirname5(relPath);
|
|
1707
|
+
const relMd = join6(parent === "." ? "" : parent, `${base}_files`, `${nameNoExt}.md`);
|
|
1708
|
+
const shadowRoot = join6(artifactsDir, PROJECT_SHADOW_DIR_NAME);
|
|
1709
|
+
const mdPath = safeJoin(shadowRoot, relMd);
|
|
1710
|
+
if (!mdPath) return null;
|
|
1711
|
+
return {
|
|
1712
|
+
dir: dirname5(mdPath),
|
|
1713
|
+
mdPath,
|
|
1714
|
+
mdRel: relative2(artifactsDir, mdPath).replaceAll("\\", "/")
|
|
1715
|
+
};
|
|
1616
1716
|
}
|
|
1617
1717
|
async function writeConvertedMarkdownAt(paths, markdown) {
|
|
1618
1718
|
await mkdir(paths.dir, { recursive: true });
|
|
@@ -1637,6 +1737,15 @@ async function ensureConvertedMarkdownSidecar(sourcePath, paths) {
|
|
|
1637
1737
|
}
|
|
1638
1738
|
return { ...converted, paths };
|
|
1639
1739
|
}
|
|
1740
|
+
async function ensureShadowDocSidecar(sourceAbs, artifactsDir, relPath) {
|
|
1741
|
+
const paths = shadowDocFilesPaths(artifactsDir, relPath);
|
|
1742
|
+
if (!paths) return null;
|
|
1743
|
+
const shadowRoot = join6(artifactsDir, PROJECT_SHADOW_DIR_NAME);
|
|
1744
|
+
await mkdir(shadowRoot, { recursive: true }).catch(() => {
|
|
1745
|
+
});
|
|
1746
|
+
if (!await realpathContained(shadowRoot, paths.mdPath)) return null;
|
|
1747
|
+
return ensureConvertedMarkdownSidecar(sourceAbs, paths);
|
|
1748
|
+
}
|
|
1640
1749
|
function chunkMarkdown(md) {
|
|
1641
1750
|
const lines = md.split(/\r?\n/);
|
|
1642
1751
|
const headIdx = [];
|
|
@@ -1684,8 +1793,8 @@ function parseFrontmatter(content) {
|
|
|
1684
1793
|
|
|
1685
1794
|
// src/index-store/gitignore.ts
|
|
1686
1795
|
import { mkdir as mkdir2, writeFile as writeFile4 } from "fs/promises";
|
|
1687
|
-
import { join as
|
|
1688
|
-
import {
|
|
1796
|
+
import { join as join7 } from "path";
|
|
1797
|
+
import { projectLocalIndexDir } from "@bendyline/gezel/paths";
|
|
1689
1798
|
|
|
1690
1799
|
// src/index-store/hash.ts
|
|
1691
1800
|
import { createHash } from "crypto";
|
|
@@ -1994,7 +2103,7 @@ import { createLogger as createLogger3 } from "@bendyline/gezel";
|
|
|
1994
2103
|
|
|
1995
2104
|
// src/transformers-cache.ts
|
|
1996
2105
|
import { mkdir as mkdir3 } from "fs/promises";
|
|
1997
|
-
import { join as
|
|
2106
|
+
import { join as join8 } from "path";
|
|
1998
2107
|
import { createLogger as createLogger2 } from "@bendyline/gezel";
|
|
1999
2108
|
var log2 = createLogger2("service");
|
|
2000
2109
|
|
|
@@ -2103,6 +2212,10 @@ CREATE TABLE IF NOT EXISTS summaries (
|
|
|
2103
2212
|
summary_md TEXT,
|
|
2104
2213
|
tags TEXT,
|
|
2105
2214
|
model TEXT,
|
|
2215
|
+
provider TEXT,
|
|
2216
|
+
gezel_id TEXT,
|
|
2217
|
+
gezel_name TEXT,
|
|
2218
|
+
app_version TEXT,
|
|
2106
2219
|
created_at TEXT
|
|
2107
2220
|
);
|
|
2108
2221
|
|
|
@@ -2219,6 +2332,10 @@ CREATE TABLE IF NOT EXISTS symbol_summaries (
|
|
|
2219
2332
|
symbol_name TEXT NOT NULL,
|
|
2220
2333
|
summary TEXT NOT NULL,
|
|
2221
2334
|
model TEXT,
|
|
2335
|
+
provider TEXT,
|
|
2336
|
+
gezel_id TEXT,
|
|
2337
|
+
gezel_name TEXT,
|
|
2338
|
+
app_version TEXT,
|
|
2222
2339
|
created_at TEXT,
|
|
2223
2340
|
PRIMARY KEY (collection_id, file_path, file_hash, symbol_name)
|
|
2224
2341
|
);
|
|
@@ -2235,6 +2352,10 @@ CREATE TABLE IF NOT EXISTS area_summaries (
|
|
|
2235
2352
|
input_hash TEXT NOT NULL,
|
|
2236
2353
|
summary_md TEXT NOT NULL,
|
|
2237
2354
|
model TEXT,
|
|
2355
|
+
provider TEXT,
|
|
2356
|
+
gezel_id TEXT,
|
|
2357
|
+
gezel_name TEXT,
|
|
2358
|
+
app_version TEXT,
|
|
2238
2359
|
created_at TEXT,
|
|
2239
2360
|
PRIMARY KEY (collection_id, area_path)
|
|
2240
2361
|
);
|
|
@@ -2258,12 +2379,30 @@ CREATE TABLE IF NOT EXISTS file_reviews (
|
|
|
2258
2379
|
health INTEGER,
|
|
2259
2380
|
health_reason TEXT,
|
|
2260
2381
|
model TEXT,
|
|
2382
|
+
provider TEXT,
|
|
2383
|
+
gezel_id TEXT,
|
|
2384
|
+
gezel_name TEXT,
|
|
2385
|
+
app_version TEXT,
|
|
2261
2386
|
reviewed_at TEXT,
|
|
2262
2387
|
attempt_rubric_hash TEXT,
|
|
2263
2388
|
attempts INTEGER DEFAULT 0,
|
|
2264
2389
|
last_attempt_at TEXT
|
|
2265
2390
|
);
|
|
2266
2391
|
|
|
2392
|
+
-- AI-shadow gate (images described by the vision model, audio transcribed by
|
|
2393
|
+
-- STT into artifacts/shadow markdown). Same content-addressed discipline as
|
|
2394
|
+
-- enrichments: 'ok' rows survive renames/reverts; failures retry up to a cap
|
|
2395
|
+
-- per hash. The sidecar file is the artifact; this row is only the work gate.
|
|
2396
|
+
CREATE TABLE IF NOT EXISTS shadow_state (
|
|
2397
|
+
content_hash TEXT PRIMARY KEY,
|
|
2398
|
+
collection_id TEXT,
|
|
2399
|
+
file_path TEXT,
|
|
2400
|
+
state TEXT,
|
|
2401
|
+
attempts INTEGER DEFAULT 0,
|
|
2402
|
+
model TEXT,
|
|
2403
|
+
updated_at TEXT
|
|
2404
|
+
);
|
|
2405
|
+
|
|
2267
2406
|
-- Mirror of the append-only history JSONL (the audit log). kind/project/gezel/at
|
|
2268
2407
|
-- are real columns so HistoryFilter fields stay SQL-filterable; only the q text
|
|
2269
2408
|
-- goes through fts_history. INSERT OR IGNORE on the event id makes JSONL
|
|
@@ -2292,7 +2431,7 @@ CREATE VIRTUAL TABLE IF NOT EXISTS fts_metadata
|
|
|
2292
2431
|
CREATE VIRTUAL TABLE IF NOT EXISTS fts_history
|
|
2293
2432
|
USING fts5(body, event_id UNINDEXED, collection_id UNINDEXED);
|
|
2294
2433
|
`;
|
|
2295
|
-
var SCHEMA_VERSION =
|
|
2434
|
+
var SCHEMA_VERSION = 10;
|
|
2296
2435
|
function addColumnIfMissing(db, table, column, decl) {
|
|
2297
2436
|
try {
|
|
2298
2437
|
const cols = db.prepare(`PRAGMA table_info(${table})`).all();
|
|
@@ -2316,6 +2455,12 @@ function applySchema(db) {
|
|
|
2316
2455
|
WHERE fingerprint IS NULL OR fingerprint = ''`);
|
|
2317
2456
|
} catch {
|
|
2318
2457
|
}
|
|
2458
|
+
for (const table of ["summaries", "symbol_summaries", "area_summaries", "file_reviews"]) {
|
|
2459
|
+
addColumnIfMissing(db, table, "provider", "TEXT");
|
|
2460
|
+
addColumnIfMissing(db, table, "gezel_id", "TEXT");
|
|
2461
|
+
addColumnIfMissing(db, table, "gezel_name", "TEXT");
|
|
2462
|
+
addColumnIfMissing(db, table, "app_version", "TEXT");
|
|
2463
|
+
}
|
|
2319
2464
|
let fts = true;
|
|
2320
2465
|
try {
|
|
2321
2466
|
db.exec(FTS_DDL);
|
|
@@ -2343,7 +2488,7 @@ function applySchema(db) {
|
|
|
2343
2488
|
// src/index-store/sqlite-driver.ts
|
|
2344
2489
|
import { mkdir as mkdir4, rename as rename2 } from "fs/promises";
|
|
2345
2490
|
import { createRequire as createRequire2 } from "module";
|
|
2346
|
-
import { dirname as
|
|
2491
|
+
import { dirname as dirname6 } from "path";
|
|
2347
2492
|
import { createLogger as createLogger4 } from "@bendyline/gezel";
|
|
2348
2493
|
var nodeRequire = createRequire2(import.meta.url);
|
|
2349
2494
|
var log4 = createLogger4("sqlite");
|
|
@@ -2366,7 +2511,7 @@ async function openIndexDatabase(dbPath) {
|
|
|
2366
2511
|
}
|
|
2367
2512
|
if (dbPath !== ":memory:") {
|
|
2368
2513
|
try {
|
|
2369
|
-
await mkdir4(
|
|
2514
|
+
await mkdir4(dirname6(dbPath), { recursive: true });
|
|
2370
2515
|
} catch (error) {
|
|
2371
2516
|
if (isUnavailableIndexError(error)) return null;
|
|
2372
2517
|
throw error;
|
|
@@ -2375,6 +2520,7 @@ async function openIndexDatabase(dbPath) {
|
|
|
2375
2520
|
let db;
|
|
2376
2521
|
try {
|
|
2377
2522
|
db = new DatabaseSync(dbPath, { allowExtension: true });
|
|
2523
|
+
setBusyTimeout(db);
|
|
2378
2524
|
assertReadableHeader(db);
|
|
2379
2525
|
} catch (error) {
|
|
2380
2526
|
if (dbPath === ":memory:" || !isCorruptDatabaseError(error)) {
|
|
@@ -2391,6 +2537,7 @@ async function openIndexDatabase(dbPath) {
|
|
|
2391
2537
|
await quarantineCorruptDatabase(dbPath);
|
|
2392
2538
|
log4.warn(`[sqlite] quarantined corrupt rebuildable index ${dbPath}`);
|
|
2393
2539
|
db = new DatabaseSync(dbPath, { allowExtension: true });
|
|
2540
|
+
setBusyTimeout(db);
|
|
2394
2541
|
}
|
|
2395
2542
|
if (!db) throw new Error("failed to open sqlite index");
|
|
2396
2543
|
let vecAvailable = false;
|
|
@@ -2439,6 +2586,12 @@ async function openIndexDatabase(dbPath) {
|
|
|
2439
2586
|
function assertReadableHeader(db) {
|
|
2440
2587
|
db.prepare("PRAGMA schema_version").get();
|
|
2441
2588
|
}
|
|
2589
|
+
function setBusyTimeout(db) {
|
|
2590
|
+
try {
|
|
2591
|
+
db.exec("PRAGMA busy_timeout=5000;");
|
|
2592
|
+
} catch {
|
|
2593
|
+
}
|
|
2594
|
+
}
|
|
2442
2595
|
function isCorruptDatabaseError(error) {
|
|
2443
2596
|
const message = error instanceof Error ? error.message : String(error);
|
|
2444
2597
|
return /SQLITE_CORRUPT|SQLITE_NOTADB|database disk image is malformed|file is not a database/i.test(
|
|
@@ -2447,9 +2600,7 @@ function isCorruptDatabaseError(error) {
|
|
|
2447
2600
|
}
|
|
2448
2601
|
function isUnavailableIndexError(error) {
|
|
2449
2602
|
const message = error instanceof Error ? error.message : String(error);
|
|
2450
|
-
return /SQLITE_READONLY|SQLITE_CANTOPEN|
|
|
2451
|
-
message
|
|
2452
|
-
);
|
|
2603
|
+
return /SQLITE_READONLY|SQLITE_CANTOPEN|EACCES|EPERM|read-only/i.test(message);
|
|
2453
2604
|
}
|
|
2454
2605
|
async function quarantineCorruptDatabase(dbPath) {
|
|
2455
2606
|
const suffix = `.corrupt-${Date.now()}`;
|
|
@@ -2728,7 +2879,9 @@ var IndexStore = class _IndexStore {
|
|
|
2728
2879
|
`SELECT f.* FROM files f
|
|
2729
2880
|
LEFT JOIN enrichments e ON e.content_hash = f.hash
|
|
2730
2881
|
WHERE f.collection_id = ? AND f.trivial = 0 AND f.hash IS NOT NULL
|
|
2731
|
-
AND f.modality IN ('code','text','doc')
|
|
2882
|
+
AND (f.modality IN ('code','text','doc')
|
|
2883
|
+
OR EXISTS (SELECT 1 FROM shadow_state s
|
|
2884
|
+
WHERE s.content_hash = f.hash AND s.state = 'ok'))
|
|
2732
2885
|
AND (e.content_hash IS NULL
|
|
2733
2886
|
OR (e.embedded_at IS NULL AND COALESCE(e.attempts, 0) < ${MAX_ENRICH_ATTEMPTS}))
|
|
2734
2887
|
ORDER BY f.path LIMIT ?`
|
|
@@ -2742,12 +2895,45 @@ var IndexStore = class _IndexStore {
|
|
|
2742
2895
|
`SELECT COUNT(*) AS n FROM files f
|
|
2743
2896
|
LEFT JOIN enrichments e ON e.content_hash = f.hash
|
|
2744
2897
|
WHERE f.collection_id = ? AND f.trivial = 0 AND f.hash IS NOT NULL
|
|
2745
|
-
AND f.modality IN ('code','text','doc')
|
|
2898
|
+
AND (f.modality IN ('code','text','doc')
|
|
2899
|
+
OR EXISTS (SELECT 1 FROM shadow_state s
|
|
2900
|
+
WHERE s.content_hash = f.hash AND s.state = 'ok'))
|
|
2746
2901
|
AND (e.content_hash IS NULL
|
|
2747
2902
|
OR (e.embedded_at IS NULL AND COALESCE(e.attempts, 0) < ${MAX_ENRICH_ATTEMPTS}))`
|
|
2748
2903
|
).get(this.collectionId);
|
|
2749
2904
|
return row?.n ?? 0;
|
|
2750
2905
|
}
|
|
2906
|
+
/**
|
|
2907
|
+
* Image/audio files whose AI shadow (description / transcript) is missing
|
|
2908
|
+
* or stale for the current content hash. Same capped-retry discipline as
|
|
2909
|
+
* {@link filesNeedingEnrichment}; an 'ok' row is the terminal success.
|
|
2910
|
+
*/
|
|
2911
|
+
filesNeedingAiShadow(limit = 5, maxAttempts = MAX_ENRICH_ATTEMPTS) {
|
|
2912
|
+
return this.db.prepare(
|
|
2913
|
+
`SELECT f.* FROM files f
|
|
2914
|
+
LEFT JOIN shadow_state s ON s.content_hash = f.hash
|
|
2915
|
+
WHERE f.collection_id = ? AND f.trivial = 0 AND f.hash IS NOT NULL
|
|
2916
|
+
AND f.modality IN ('image','audio')
|
|
2917
|
+
AND (s.content_hash IS NULL
|
|
2918
|
+
OR (s.state <> 'ok' AND COALESCE(s.attempts, 0) < ?))
|
|
2919
|
+
ORDER BY f.path LIMIT ?`
|
|
2920
|
+
).all(this.collectionId, maxAttempts, limit).map(rowToFile);
|
|
2921
|
+
}
|
|
2922
|
+
markAiShadowOk(contentHash, filePath, model) {
|
|
2923
|
+
this.db.prepare(
|
|
2924
|
+
`INSERT OR REPLACE INTO shadow_state (content_hash, collection_id, file_path, state, attempts, model, updated_at)
|
|
2925
|
+
VALUES (?, ?, ?, 'ok', 0, ?, ?)`
|
|
2926
|
+
).run(contentHash, this.collectionId, filePath, model ?? null, nowIso());
|
|
2927
|
+
}
|
|
2928
|
+
markAiShadowAttempt(contentHash, filePath) {
|
|
2929
|
+
this.db.prepare(
|
|
2930
|
+
`INSERT INTO shadow_state (content_hash, collection_id, file_path, state, attempts, updated_at)
|
|
2931
|
+
VALUES (?, ?, ?, 'failed', 1, ?)
|
|
2932
|
+
ON CONFLICT(content_hash) DO UPDATE SET
|
|
2933
|
+
state='failed', attempts=COALESCE(shadow_state.attempts, 0) + 1,
|
|
2934
|
+
updated_at=excluded.updated_at`
|
|
2935
|
+
).run(contentHash, this.collectionId, filePath, nowIso());
|
|
2936
|
+
}
|
|
2751
2937
|
/**
|
|
2752
2938
|
* Enrichment coverage counts. `summarized` counts real summaries (join on
|
|
2753
2939
|
* the summaries table), deliberately NOT the enrichments gate — the gate also
|
|
@@ -2766,15 +2952,39 @@ var IndexStore = class _IndexStore {
|
|
|
2766
2952
|
`SELECT COUNT(*) AS n FROM files f JOIN enrichments e ON e.content_hash = f.hash
|
|
2767
2953
|
WHERE e.embedded_at IS NOT NULL AND ${ELIGIBLE}`
|
|
2768
2954
|
);
|
|
2955
|
+
const skipped = count(
|
|
2956
|
+
`SELECT COUNT(*) AS n FROM files f JOIN enrichments e ON e.content_hash = f.hash
|
|
2957
|
+
WHERE e.embedded_at IS NULL AND COALESCE(e.attempts, 0) >= ${MAX_ENRICH_ATTEMPTS}
|
|
2958
|
+
AND ${ELIGIBLE}`
|
|
2959
|
+
);
|
|
2769
2960
|
const embedModel = this.getMeta("embed_model");
|
|
2770
2961
|
return {
|
|
2771
2962
|
eligible,
|
|
2772
2963
|
summarized,
|
|
2773
2964
|
embedded,
|
|
2774
2965
|
pending: this.countNeedingEnrichment(),
|
|
2966
|
+
skipped,
|
|
2967
|
+
shadowsPending: this.countNeedingAiShadow(),
|
|
2775
2968
|
...embedModel ? { embedModel } : {}
|
|
2776
2969
|
};
|
|
2777
2970
|
}
|
|
2971
|
+
/**
|
|
2972
|
+
* COUNT twin of `filesNeedingAiShadow` — media files still awaiting an AI
|
|
2973
|
+
* description, attempt-capped ones excluded. The drive works this tier
|
|
2974
|
+
* before summaries, so status must surface it or a fresh scan looks stuck.
|
|
2975
|
+
*/
|
|
2976
|
+
countNeedingAiShadow(maxAttempts = MAX_ENRICH_ATTEMPTS) {
|
|
2977
|
+
return Number(
|
|
2978
|
+
this.db.prepare(
|
|
2979
|
+
`SELECT COUNT(*) AS n FROM files f
|
|
2980
|
+
LEFT JOIN shadow_state s ON s.content_hash = f.hash
|
|
2981
|
+
WHERE f.collection_id = ? AND f.trivial = 0 AND f.hash IS NOT NULL
|
|
2982
|
+
AND f.modality IN ('image','audio')
|
|
2983
|
+
AND (s.content_hash IS NULL
|
|
2984
|
+
OR (s.state <> 'ok' AND COALESCE(s.attempts, 0) < ?))`
|
|
2985
|
+
).get(this.collectionId, maxAttempts)?.n ?? 0
|
|
2986
|
+
);
|
|
2987
|
+
}
|
|
2778
2988
|
/** Mark a content hash as enriched (vectors built) so it isn't reprocessed. */
|
|
2779
2989
|
markEnriched(contentHash) {
|
|
2780
2990
|
this.db.prepare("INSERT OR REPLACE INTO enrichments (content_hash, embedded_at) VALUES (?, ?)").run(contentHash, nowIso());
|
|
@@ -2790,6 +3000,23 @@ var IndexStore = class _IndexStore {
|
|
|
2790
3000
|
`INSERT INTO enrichments (content_hash, embedded_at, attempts) VALUES (?, NULL, 1)
|
|
2791
3001
|
ON CONFLICT(content_hash) DO UPDATE SET attempts = COALESCE(enrichments.attempts, 0) + 1`
|
|
2792
3002
|
).run(contentHash);
|
|
3003
|
+
return Number(
|
|
3004
|
+
this.db.prepare("SELECT attempts FROM enrichments WHERE content_hash = ?").get(contentHash)?.attempts ?? 1
|
|
3005
|
+
);
|
|
3006
|
+
}
|
|
3007
|
+
/**
|
|
3008
|
+
* Jump a hash straight to the attempt cap — for deterministic failures
|
|
3009
|
+
* (a provider policy-blocking the file's content) where every retry would
|
|
3010
|
+
* fail identically. The file drops off the work list and counts as
|
|
3011
|
+
* `skipped` in {@link enrichmentCounts} until its content changes.
|
|
3012
|
+
*/
|
|
3013
|
+
markEnrichSkipped(contentHash) {
|
|
3014
|
+
this.db.prepare(
|
|
3015
|
+
`INSERT INTO enrichments (content_hash, embedded_at, attempts)
|
|
3016
|
+
VALUES (?, NULL, ${MAX_ENRICH_ATTEMPTS})
|
|
3017
|
+
ON CONFLICT(content_hash) DO UPDATE
|
|
3018
|
+
SET attempts = MAX(COALESCE(enrichments.attempts, 0), ${MAX_ENRICH_ATTEMPTS})`
|
|
3019
|
+
).run(contentHash);
|
|
2793
3020
|
}
|
|
2794
3021
|
/**
|
|
2795
3022
|
* Keyword search over enrichment summaries. fts_summaries rows are
|
|
@@ -2942,11 +3169,15 @@ var IndexStore = class _IndexStore {
|
|
|
2942
3169
|
upsertSummary(rec) {
|
|
2943
3170
|
this.db.transaction(() => {
|
|
2944
3171
|
this.db.prepare(
|
|
2945
|
-
`INSERT INTO summaries (content_hash, collection_id, file_path, summary_md, tags, model,
|
|
2946
|
-
|
|
3172
|
+
`INSERT INTO summaries (content_hash, collection_id, file_path, summary_md, tags, model,
|
|
3173
|
+
provider, gezel_id, gezel_name, app_version, created_at)
|
|
3174
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
2947
3175
|
ON CONFLICT(content_hash) DO UPDATE SET
|
|
2948
3176
|
file_path=excluded.file_path, summary_md=excluded.summary_md,
|
|
2949
|
-
tags=excluded.tags, model=excluded.model,
|
|
3177
|
+
tags=excluded.tags, model=excluded.model,
|
|
3178
|
+
provider=excluded.provider, gezel_id=excluded.gezel_id,
|
|
3179
|
+
gezel_name=excluded.gezel_name, app_version=excluded.app_version,
|
|
3180
|
+
created_at=excluded.created_at`
|
|
2950
3181
|
).run(
|
|
2951
3182
|
rec.contentHash,
|
|
2952
3183
|
this.collectionId,
|
|
@@ -2954,6 +3185,10 @@ var IndexStore = class _IndexStore {
|
|
|
2954
3185
|
rec.summaryMd,
|
|
2955
3186
|
rec.tags ?? null,
|
|
2956
3187
|
rec.model,
|
|
3188
|
+
rec.provenance?.provider ?? null,
|
|
3189
|
+
rec.provenance?.gezelId ?? null,
|
|
3190
|
+
rec.provenance?.gezelName ?? null,
|
|
3191
|
+
rec.provenance?.appVersion ?? null,
|
|
2957
3192
|
nowIso()
|
|
2958
3193
|
);
|
|
2959
3194
|
if (this.caps.fts) {
|
|
@@ -2982,16 +3217,29 @@ var IndexStore = class _IndexStore {
|
|
|
2982
3217
|
* for any other (stale) hash of the same file. Keyed by symbol name only —
|
|
2983
3218
|
* overloads/same-name symbols share a line (accepted v1 simplification).
|
|
2984
3219
|
*/
|
|
2985
|
-
putSymbolSummaries(filePath, fileHash, entries, model) {
|
|
3220
|
+
putSymbolSummaries(filePath, fileHash, entries, model, provenance) {
|
|
2986
3221
|
this.db.transaction(() => {
|
|
2987
3222
|
this.db.prepare("DELETE FROM symbol_summaries WHERE collection_id = ? AND file_path = ?").run(this.collectionId, filePath);
|
|
2988
3223
|
const ins = this.db.prepare(
|
|
2989
|
-
`INSERT OR REPLACE INTO symbol_summaries (collection_id, file_path, file_hash, symbol_name, summary, model,
|
|
2990
|
-
|
|
3224
|
+
`INSERT OR REPLACE INTO symbol_summaries (collection_id, file_path, file_hash, symbol_name, summary, model,
|
|
3225
|
+
provider, gezel_id, gezel_name, app_version, created_at)
|
|
3226
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
2991
3227
|
);
|
|
2992
3228
|
const at = nowIso();
|
|
2993
3229
|
for (const e of entries) {
|
|
2994
|
-
ins.run(
|
|
3230
|
+
ins.run(
|
|
3231
|
+
this.collectionId,
|
|
3232
|
+
filePath,
|
|
3233
|
+
fileHash,
|
|
3234
|
+
e.name,
|
|
3235
|
+
e.summary,
|
|
3236
|
+
model ?? null,
|
|
3237
|
+
provenance?.provider ?? null,
|
|
3238
|
+
provenance?.gezelId ?? null,
|
|
3239
|
+
provenance?.gezelName ?? null,
|
|
3240
|
+
provenance?.appVersion ?? null,
|
|
3241
|
+
at
|
|
3242
|
+
);
|
|
2995
3243
|
}
|
|
2996
3244
|
});
|
|
2997
3245
|
}
|
|
@@ -3009,14 +3257,17 @@ var IndexStore = class _IndexStore {
|
|
|
3009
3257
|
upsertFileReview(rec) {
|
|
3010
3258
|
this.db.prepare(
|
|
3011
3259
|
`INSERT INTO file_reviews (content_hash, collection_id, file_path, rubric_hash, notes_md,
|
|
3012
|
-
issues, health, health_reason, model,
|
|
3260
|
+
issues, health, health_reason, model,
|
|
3261
|
+
provider, gezel_id, gezel_name, app_version, reviewed_at,
|
|
3013
3262
|
attempt_rubric_hash, attempts, last_attempt_at)
|
|
3014
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, 0, NULL)
|
|
3263
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, 0, NULL)
|
|
3015
3264
|
ON CONFLICT(content_hash) DO UPDATE SET
|
|
3016
3265
|
collection_id=excluded.collection_id, file_path=excluded.file_path,
|
|
3017
3266
|
rubric_hash=excluded.rubric_hash, notes_md=excluded.notes_md,
|
|
3018
3267
|
issues=excluded.issues, health=excluded.health,
|
|
3019
3268
|
health_reason=excluded.health_reason, model=excluded.model,
|
|
3269
|
+
provider=excluded.provider, gezel_id=excluded.gezel_id,
|
|
3270
|
+
gezel_name=excluded.gezel_name, app_version=excluded.app_version,
|
|
3020
3271
|
reviewed_at=excluded.reviewed_at,
|
|
3021
3272
|
attempt_rubric_hash=NULL, attempts=0, last_attempt_at=NULL`
|
|
3022
3273
|
).run(
|
|
@@ -3029,9 +3280,32 @@ var IndexStore = class _IndexStore {
|
|
|
3029
3280
|
rec.health,
|
|
3030
3281
|
rec.healthReason,
|
|
3031
3282
|
rec.model,
|
|
3283
|
+
rec.provenance?.provider ?? null,
|
|
3284
|
+
rec.provenance?.gezelId ?? null,
|
|
3285
|
+
rec.provenance?.gezelName ?? null,
|
|
3286
|
+
rec.provenance?.appVersion ?? null,
|
|
3032
3287
|
nowIso()
|
|
3033
3288
|
);
|
|
3034
3289
|
}
|
|
3290
|
+
/**
|
|
3291
|
+
* Terminal skip for content that CANNOT be reviewed under its current hash
|
|
3292
|
+
* — a doc whose conversion was blocked/failed, an empty file, a source
|
|
3293
|
+
* deleted between scan and review. Burns the whole attempt budget in one
|
|
3294
|
+
* write (success fields untouched) so `filesNeedingReview` stops selecting
|
|
3295
|
+
* the file; without this, one blocked doc permanently steals a batch slot
|
|
3296
|
+
* from its kind on every sweep. A content change (new hash) or a rubric
|
|
3297
|
+
* edit re-admits it exactly like any capped-out attempt.
|
|
3298
|
+
*/
|
|
3299
|
+
markReviewUnavailable(contentHash, filePath, rubricHash, maxAttempts) {
|
|
3300
|
+
this.db.prepare(
|
|
3301
|
+
`INSERT INTO file_reviews (content_hash, collection_id, file_path, attempt_rubric_hash, attempts, last_attempt_at)
|
|
3302
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
3303
|
+
ON CONFLICT(content_hash) DO UPDATE SET
|
|
3304
|
+
attempts = excluded.attempts,
|
|
3305
|
+
attempt_rubric_hash = excluded.attempt_rubric_hash,
|
|
3306
|
+
last_attempt_at = excluded.last_attempt_at`
|
|
3307
|
+
).run(contentHash, this.collectionId, filePath, rubricHash, maxAttempts, nowIso());
|
|
3308
|
+
}
|
|
3035
3309
|
/**
|
|
3036
3310
|
* Record a non-empty-but-unparseable review reply WITHOUT touching any
|
|
3037
3311
|
* success field — a stale-rubric review keeps serving until a new one
|
|
@@ -3052,7 +3326,8 @@ var IndexStore = class _IndexStore {
|
|
|
3052
3326
|
/** The successful review for a content hash (attempt-only rows excluded). */
|
|
3053
3327
|
getFileReview(contentHash) {
|
|
3054
3328
|
const row = this.db.prepare(
|
|
3055
|
-
`SELECT notes_md, issues, health, health_reason, rubric_hash, model,
|
|
3329
|
+
`SELECT notes_md, issues, health, health_reason, rubric_hash, model,
|
|
3330
|
+
provider, gezel_id, gezel_name, app_version, reviewed_at
|
|
3056
3331
|
FROM file_reviews
|
|
3057
3332
|
WHERE content_hash = ? AND notes_md IS NOT NULL AND health IS NOT NULL`
|
|
3058
3333
|
).get(contentHash);
|
|
@@ -3064,6 +3339,10 @@ var IndexStore = class _IndexStore {
|
|
|
3064
3339
|
healthReason: row.health_reason ?? "",
|
|
3065
3340
|
rubricHash: row.rubric_hash ?? "",
|
|
3066
3341
|
model: row.model,
|
|
3342
|
+
provider: row.provider,
|
|
3343
|
+
gezelId: row.gezel_id,
|
|
3344
|
+
gezelName: row.gezel_name,
|
|
3345
|
+
appVersion: row.app_version,
|
|
3067
3346
|
reviewedAt: row.reviewed_at
|
|
3068
3347
|
};
|
|
3069
3348
|
}
|
|
@@ -3236,6 +3515,17 @@ var IndexStore = class _IndexStore {
|
|
|
3236
3515
|
return out;
|
|
3237
3516
|
};
|
|
3238
3517
|
const bySeverity = tally(`COALESCE(json_extract(j.value, '$.severity'), 'info')`);
|
|
3518
|
+
const reviewers = this.db.prepare(
|
|
3519
|
+
`SELECT r.model, r.provider, r.gezel_name, COUNT(DISTINCT r.content_hash) AS files
|
|
3520
|
+
${FROM}
|
|
3521
|
+
GROUP BY r.model, r.provider, r.gezel_name
|
|
3522
|
+
ORDER BY files DESC LIMIT 5`
|
|
3523
|
+
).all(...params).map((r) => ({
|
|
3524
|
+
model: r.model,
|
|
3525
|
+
provider: r.provider,
|
|
3526
|
+
gezelName: r.gezel_name,
|
|
3527
|
+
files: Number(r.files)
|
|
3528
|
+
}));
|
|
3239
3529
|
return {
|
|
3240
3530
|
issues: rows.filter((r) => r.message).map((r) => ({
|
|
3241
3531
|
path: r.path,
|
|
@@ -3248,18 +3538,34 @@ var IndexStore = class _IndexStore {
|
|
|
3248
3538
|
total: Object.values(bySeverity).reduce((a, b) => a + b, 0),
|
|
3249
3539
|
bySeverity,
|
|
3250
3540
|
byCategory: tally(`COALESCE(json_extract(j.value, '$.category'), 'general')`)
|
|
3251
|
-
}
|
|
3541
|
+
},
|
|
3542
|
+
reviewers
|
|
3252
3543
|
};
|
|
3253
3544
|
}
|
|
3254
3545
|
// ── area summaries (deep pass) ─────────────────────────────────────────
|
|
3255
3546
|
upsertAreaSummary(rec) {
|
|
3256
3547
|
this.db.prepare(
|
|
3257
|
-
`INSERT INTO area_summaries (collection_id, area_path, input_hash, summary_md, model,
|
|
3258
|
-
|
|
3548
|
+
`INSERT INTO area_summaries (collection_id, area_path, input_hash, summary_md, model,
|
|
3549
|
+
provider, gezel_id, gezel_name, app_version, created_at)
|
|
3550
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
3259
3551
|
ON CONFLICT(collection_id, area_path) DO UPDATE SET
|
|
3260
3552
|
input_hash=excluded.input_hash, summary_md=excluded.summary_md,
|
|
3261
|
-
model=excluded.model,
|
|
3262
|
-
|
|
3553
|
+
model=excluded.model,
|
|
3554
|
+
provider=excluded.provider, gezel_id=excluded.gezel_id,
|
|
3555
|
+
gezel_name=excluded.gezel_name, app_version=excluded.app_version,
|
|
3556
|
+
created_at=excluded.created_at`
|
|
3557
|
+
).run(
|
|
3558
|
+
this.collectionId,
|
|
3559
|
+
rec.areaPath,
|
|
3560
|
+
rec.inputHash,
|
|
3561
|
+
rec.summaryMd,
|
|
3562
|
+
rec.model,
|
|
3563
|
+
rec.provenance?.provider ?? null,
|
|
3564
|
+
rec.provenance?.gezelId ?? null,
|
|
3565
|
+
rec.provenance?.gezelName ?? null,
|
|
3566
|
+
rec.provenance?.appVersion ?? null,
|
|
3567
|
+
nowIso()
|
|
3568
|
+
);
|
|
3263
3569
|
}
|
|
3264
3570
|
getAreaSummary(areaPath) {
|
|
3265
3571
|
const row = this.db.prepare(
|
|
@@ -4025,7 +4331,7 @@ function parseBindings(raw) {
|
|
|
4025
4331
|
|
|
4026
4332
|
// src/index-store/symbols.ts
|
|
4027
4333
|
import { createRequire as createRequire3 } from "module";
|
|
4028
|
-
import { dirname as
|
|
4334
|
+
import { dirname as dirname7, join as join9 } from "path";
|
|
4029
4335
|
var nodeRequire2 = createRequire3(import.meta.url);
|
|
4030
4336
|
var SIG_CAP = 160;
|
|
4031
4337
|
var GRAMMAR_FILE = {
|
|
@@ -4098,8 +4404,8 @@ async function ensureInit() {
|
|
|
4098
4404
|
initPromise = (async () => {
|
|
4099
4405
|
try {
|
|
4100
4406
|
const mod = nodeRequire2("web-tree-sitter");
|
|
4101
|
-
const wtsDir =
|
|
4102
|
-
await mod.Parser.init({ locateFile: (f) =>
|
|
4407
|
+
const wtsDir = dirname7(nodeRequire2.resolve("web-tree-sitter"));
|
|
4408
|
+
await mod.Parser.init({ locateFile: (f) => join9(wtsDir, f) });
|
|
4103
4409
|
ParserCtor = mod.Parser;
|
|
4104
4410
|
LanguageMod = mod.Language;
|
|
4105
4411
|
return true;
|
|
@@ -4110,7 +4416,7 @@ async function ensureInit() {
|
|
|
4110
4416
|
return initPromise;
|
|
4111
4417
|
}
|
|
4112
4418
|
function grammarDir() {
|
|
4113
|
-
return
|
|
4419
|
+
return join9(dirname7(nodeRequire2.resolve("tree-sitter-wasms/package.json")), "out");
|
|
4114
4420
|
}
|
|
4115
4421
|
async function loadLanguage(langId) {
|
|
4116
4422
|
const file = GRAMMAR_FILE[langId];
|
|
@@ -4118,7 +4424,7 @@ async function loadLanguage(langId) {
|
|
|
4118
4424
|
if (langCache.has(langId)) return langCache.get(langId) ?? null;
|
|
4119
4425
|
let lang = null;
|
|
4120
4426
|
try {
|
|
4121
|
-
lang = await LanguageMod.load(
|
|
4427
|
+
lang = await LanguageMod.load(join9(grammarDir(), file));
|
|
4122
4428
|
} catch {
|
|
4123
4429
|
lang = null;
|
|
4124
4430
|
}
|
|
@@ -4387,7 +4693,7 @@ function firstLine(text) {
|
|
|
4387
4693
|
|
|
4388
4694
|
// src/index-store/content-indexer.ts
|
|
4389
4695
|
var MAX_FILES = 5e4;
|
|
4390
|
-
var EXTRACTOR_VERSION =
|
|
4696
|
+
var EXTRACTOR_VERSION = 3;
|
|
4391
4697
|
var INDEXED_PNG_TEXT_KEYS = ["parameters", "prompt", "Description", "Comment", "Title"];
|
|
4392
4698
|
var MAX_INDEXED_PNG_TEXT = 2e3;
|
|
4393
4699
|
function embeddablePngText(text) {
|
|
@@ -4402,7 +4708,7 @@ function embeddablePngText(text) {
|
|
|
4402
4708
|
}
|
|
4403
4709
|
return parts.length > 0 ? ` ${parts.join(" ")}` : "";
|
|
4404
4710
|
}
|
|
4405
|
-
async function indexWorkspaceContent(store, workspaceDir) {
|
|
4711
|
+
async function indexWorkspaceContent(store, workspaceDir, artifactsDir) {
|
|
4406
4712
|
const stats = {
|
|
4407
4713
|
scanned: 0,
|
|
4408
4714
|
changed: 0,
|
|
@@ -4490,6 +4796,39 @@ async function indexWorkspaceContent(store, workspaceDir) {
|
|
|
4490
4796
|
]);
|
|
4491
4797
|
continue;
|
|
4492
4798
|
}
|
|
4799
|
+
if (cls.modality === "audio") {
|
|
4800
|
+
let bytes;
|
|
4801
|
+
try {
|
|
4802
|
+
bytes = await readFile3(file.abs);
|
|
4803
|
+
} catch {
|
|
4804
|
+
stats.skipped++;
|
|
4805
|
+
continue;
|
|
4806
|
+
}
|
|
4807
|
+
const hash2 = sha256(bytes);
|
|
4808
|
+
const record = {
|
|
4809
|
+
path: file.path,
|
|
4810
|
+
hash: hash2,
|
|
4811
|
+
size: file.size,
|
|
4812
|
+
mtimeMs: file.mtimeMs,
|
|
4813
|
+
lang: cls.lang,
|
|
4814
|
+
kind: cls.kind,
|
|
4815
|
+
modality: cls.modality,
|
|
4816
|
+
trivial: cls.trivial,
|
|
4817
|
+
indexedAt,
|
|
4818
|
+
loc: null
|
|
4819
|
+
};
|
|
4820
|
+
if (existing && existing.hash === hash2) {
|
|
4821
|
+
store.upsertFile(record);
|
|
4822
|
+
continue;
|
|
4823
|
+
}
|
|
4824
|
+
stats.changed++;
|
|
4825
|
+
store.upsertFile(record);
|
|
4826
|
+
const nameWords = file.path.replace(/\.[^.]+$/, "").split(/[^a-zA-Z0-9]+/).filter(Boolean).join(" ");
|
|
4827
|
+
store.putChunks(file.path, hash2, [
|
|
4828
|
+
{ kind: "audio", lineStart: 1, lineEnd: 1, text: `${nameWords} audio recording` }
|
|
4829
|
+
]);
|
|
4830
|
+
continue;
|
|
4831
|
+
}
|
|
4493
4832
|
if (cls.modality === "doc") {
|
|
4494
4833
|
let bytes;
|
|
4495
4834
|
try {
|
|
@@ -4511,21 +4850,32 @@ async function indexWorkspaceContent(store, workspaceDir) {
|
|
|
4511
4850
|
indexedAt,
|
|
4512
4851
|
loc: null
|
|
4513
4852
|
};
|
|
4853
|
+
const convertible = isConvertibleDoc(extname3(file.path));
|
|
4514
4854
|
if (existing && existing.hash === hash2) {
|
|
4515
4855
|
store.upsertFile(record);
|
|
4856
|
+
if (!convertible) continue;
|
|
4857
|
+
const paths = shadowDocFilesPaths(artifactsDir, file.path);
|
|
4858
|
+
const sidecarPresent = paths ? await stat3(paths.mdPath).then(
|
|
4859
|
+
(s) => s.isFile(),
|
|
4860
|
+
() => false
|
|
4861
|
+
) : true;
|
|
4862
|
+
if (sidecarPresent) continue;
|
|
4863
|
+
const state = store.getMetadata(file.path)["doc:convert"];
|
|
4864
|
+
if (state && state !== "ok") continue;
|
|
4865
|
+
const conv = await ensureShadowDocSidecar(file.abs, artifactsDir, file.path);
|
|
4866
|
+
store.setMetadata(file.path, [{ key: "doc:convert", value: convState(conv) }]);
|
|
4867
|
+
if (conv?.markdown != null) stats.docsConverted++;
|
|
4516
4868
|
continue;
|
|
4517
4869
|
}
|
|
4518
4870
|
stats.changed++;
|
|
4519
4871
|
store.upsertFile(record);
|
|
4520
|
-
if (
|
|
4521
|
-
const conv = await
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
);
|
|
4525
|
-
if (conv.markdown != null) {
|
|
4872
|
+
if (convertible) {
|
|
4873
|
+
const conv = await ensureShadowDocSidecar(file.abs, artifactsDir, file.path);
|
|
4874
|
+
store.setMetadata(file.path, [{ key: "doc:convert", value: convState(conv) }]);
|
|
4875
|
+
if (conv?.markdown != null) {
|
|
4526
4876
|
store.putChunks(file.path, hash2, chunkMarkdown(conv.markdown));
|
|
4527
4877
|
stats.docsConverted++;
|
|
4528
|
-
} else if (conv
|
|
4878
|
+
} else if (conv?.blocked) {
|
|
4529
4879
|
store.putChunks(file.path, hash2, [
|
|
4530
4880
|
{
|
|
4531
4881
|
kind: "doc",
|
|
@@ -4604,11 +4954,51 @@ async function indexWorkspaceContent(store, workspaceDir) {
|
|
|
4604
4954
|
if (!seen.has(f.path)) {
|
|
4605
4955
|
store.deleteFile(f.path);
|
|
4606
4956
|
stats.removed++;
|
|
4957
|
+
if (isConvertibleDoc(extname3(f.path))) {
|
|
4958
|
+
const paths = shadowDocFilesPaths(artifactsDir, f.path);
|
|
4959
|
+
if (paths) await rm4(paths.dir, { recursive: true, force: true }).catch(() => {
|
|
4960
|
+
});
|
|
4961
|
+
}
|
|
4607
4962
|
}
|
|
4608
4963
|
}
|
|
4964
|
+
await sweepShadowOrphans(artifactsDir, seen);
|
|
4609
4965
|
if (forceCode) store.setMeta("extractor_version", String(EXTRACTOR_VERSION));
|
|
4610
4966
|
return stats;
|
|
4611
4967
|
}
|
|
4968
|
+
var SHADOW_SWEEP_MAX_DIRS = 5e4;
|
|
4969
|
+
async function sweepShadowOrphans(artifactsDir, seen) {
|
|
4970
|
+
const shadowRoot = join10(artifactsDir, PROJECT_SHADOW_DIR_NAME2);
|
|
4971
|
+
const queue = [""];
|
|
4972
|
+
let visited = 0;
|
|
4973
|
+
while (queue.length > 0) {
|
|
4974
|
+
const rel = queue.shift();
|
|
4975
|
+
let entries;
|
|
4976
|
+
try {
|
|
4977
|
+
entries = await readdir2(join10(shadowRoot, rel), { withFileTypes: true });
|
|
4978
|
+
} catch {
|
|
4979
|
+
continue;
|
|
4980
|
+
}
|
|
4981
|
+
for (const e of entries) {
|
|
4982
|
+
if (!e.isDirectory()) continue;
|
|
4983
|
+
if (++visited > SHADOW_SWEEP_MAX_DIRS) return;
|
|
4984
|
+
const childRel = rel ? `${rel}/${e.name}` : e.name;
|
|
4985
|
+
if (e.name.endsWith("_files")) {
|
|
4986
|
+
const sourceRel = childRel.slice(0, -"_files".length);
|
|
4987
|
+
if (!seen.has(sourceRel)) {
|
|
4988
|
+
await rm4(join10(shadowRoot, childRel), { recursive: true, force: true }).catch(() => {
|
|
4989
|
+
});
|
|
4990
|
+
}
|
|
4991
|
+
} else {
|
|
4992
|
+
queue.push(childRel);
|
|
4993
|
+
}
|
|
4994
|
+
}
|
|
4995
|
+
}
|
|
4996
|
+
}
|
|
4997
|
+
function convState(conv) {
|
|
4998
|
+
if (conv?.markdown != null) return "ok";
|
|
4999
|
+
if (conv?.blocked) return "blocked";
|
|
5000
|
+
return "failed";
|
|
5001
|
+
}
|
|
4612
5002
|
|
|
4613
5003
|
// src/index-store/static-index-worker.ts
|
|
4614
5004
|
if (!parentPort) throw new Error("static-index-worker must be run as a worker thread");
|
|
@@ -4624,7 +5014,11 @@ port.on("message", (request) => {
|
|
|
4624
5014
|
});
|
|
4625
5015
|
if (!index) throw new Error(`content index unavailable at ${request.dbPath}`);
|
|
4626
5016
|
try {
|
|
4627
|
-
const stats = await indexWorkspaceContent(
|
|
5017
|
+
const stats = await indexWorkspaceContent(
|
|
5018
|
+
index,
|
|
5019
|
+
request.workspaceDir,
|
|
5020
|
+
request.artifactsDir
|
|
5021
|
+
);
|
|
4628
5022
|
port.postMessage({ id: request.id, stats });
|
|
4629
5023
|
} finally {
|
|
4630
5024
|
index.close();
|