@bendyline/gezel-service 0.1.0 → 1.0.1
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 +13 -5
- package/dist/bin/gezeld.js +56269 -50686
- 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.26224.md +82 -0
- package/dist/handboek-content/whats-new/whats-new-index.md +20 -0
- package/dist/handboek.d.ts +53 -26
- package/dist/handboek.js +109 -27
- package/dist/index-store/static-index-worker.js +518 -98
- package/dist/index.d.ts +377 -22
- package/dist/index.js +56204 -50670
- package/dist/ui/assets/{TerminalCodeEditor-CB-AXtKI.js → TerminalCodeEditor-BpkIEKrW.js} +2 -2
- package/dist/ui/assets/{_.contribution-C6KlZ1yR.js → _.contribution-DESswd3T.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-DazoHQwn.js} +1 -1
- package/dist/ui/assets/{csharp.contribution-jBA6TWhG.js → csharp.contribution-B64MdTmq.js} +1 -1
- package/dist/ui/assets/{css.contribution-DQSNU6vC.js → css.contribution-u617lXYH.js} +1 -1
- package/dist/ui/assets/{cssMode-68UP_lrS.js → cssMode-Cf_QqRjJ.js} +1 -1
- package/dist/ui/assets/{csv-Ctux8fO7.js → csv-RAgECoUg.js} +1 -1
- package/dist/ui/assets/dist-DB6xxrlD.js +2 -0
- package/dist/ui/assets/dist-DucbO86I.js +222 -0
- package/dist/ui/assets/{doc-BdMAKGDm.js → doc-B7NXSLxH.js} +33 -23
- package/dist/ui/assets/{dockerfile.contribution-CSpSPKgK.js → dockerfile.contribution-P8T1FxHz.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-BqqmPpYF.js} +1 -1
- package/dist/ui/assets/{handlebars-Dq5dJ5WJ.js → handlebars-B9qcPqjD.js} +1 -1
- package/dist/ui/assets/handlebars.contribution-vZslMuRE.js +2 -0
- package/dist/ui/assets/{html-fQ6X2xaa.js → html-Cgejr9YM.js} +24 -24
- package/dist/ui/assets/{html-Bgyk9kiZ.js → html-Fp2jEZ7j.js} +1 -1
- package/dist/ui/assets/html.contribution-B1GzaEV-.js +2 -0
- package/dist/ui/assets/{htmlMode-D5UKInqK.js → htmlMode-B5cPhGim.js} +1 -1
- package/dist/ui/assets/{index-CPXp6BEZ.js → index-CQSh1NUQ.js} +193 -205
- package/dist/ui/assets/index-Ch0qGtVJ.css +1 -0
- package/dist/ui/assets/infer-v-bCXeT9.js +2 -0
- package/dist/ui/assets/{ini.contribution-BiJ4T5K-.js → ini.contribution-XkZYQphY.js} +1 -1
- package/dist/ui/assets/{java.contribution-B8o1lBqP.js → java.contribution-CUQitE0a.js} +1 -1
- package/dist/ui/assets/{javascript-COWkS1tr.js → javascript-CcBUpTjJ.js} +1 -1
- package/dist/ui/assets/javascript.contribution-D6DhUpPP.js +2 -0
- package/dist/ui/assets/{jsonMode-CWWZvfzr.js → jsonMode-D9SbmDW6.js} +1 -1
- package/dist/ui/assets/{kotlin.contribution-CxSOPQ6P.js → kotlin.contribution-B9AYd3Yp.js} +1 -1
- package/dist/ui/assets/layouts-2A7QNF6Y-D94iKLLQ.js +1 -0
- package/dist/ui/assets/{less.contribution-Cthyflev.js → less.contribution-DtKQK3HF.js} +1 -1
- package/dist/ui/assets/{lua.contribution-894DRgPR.js → lua.contribution-DxFFDcBW.js} +1 -1
- package/dist/ui/assets/{markdown.contribution-d_TBTsNA.js → markdown.contribution-B7aCxle-.js} +1 -1
- package/dist/ui/assets/{monaco-PebRhLpo.js → monaco-BBFILBn6.js} +3 -3
- package/dist/ui/assets/{monaco-base-4S0M5BiY.js → monaco-base-CvsKN-Z-.js} +1 -1
- package/dist/ui/assets/{monaco-setup-7hp0Z9jn.js → monaco-setup-BeHgXlpG.js} +1 -1
- package/dist/ui/assets/{monaco.contribution-CKDvyGVE.js → monaco.contribution-B-c9k_-7.js} +2 -2
- package/dist/ui/assets/{monaco.contribution-BL_zgiu2.js → monaco.contribution-BBrbIBfd.js} +2 -2
- package/dist/ui/assets/{monaco.contribution-D61txvUB.js → monaco.contribution-Baa3Cv83.js} +2 -2
- package/dist/ui/assets/{monaco.contribution-DQjPk7Rx.js → monaco.contribution-DRZnYNPl.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-vC08ovbS.js} +1 -1
- package/dist/ui/assets/pptx-CsdVR0xn.js +6 -0
- package/dist/ui/assets/{python-CHygH1eM.js → python-CevkXHVb.js} +1 -1
- package/dist/ui/assets/python.contribution-C6TUOYNZ.js +2 -0
- package/dist/ui/assets/{ruby.contribution-BQdPKFM2.js → ruby.contribution-BMZvE4z1.js} +1 -1
- package/dist/ui/assets/{rust.contribution-D7wKqShc.js → rust.contribution-DOWKjtrn.js} +1 -1
- package/dist/ui/assets/{scss.contribution-kSTLMoty.js → scss.contribution-BXLJWEv0.js} +1 -1
- package/dist/ui/assets/{shell.contribution-BQnT3BBJ.js → shell.contribution-gbqcscnj.js} +1 -1
- package/dist/ui/assets/{sql.contribution-C9z55nWb.js → sql.contribution-DO39C_-u.js} +1 -1
- package/dist/ui/assets/{swift.contribution-BZoW5ZZj.js → swift.contribution-D4Vjb2VA.js} +1 -1
- package/dist/ui/assets/{terminal-monaco-setup-DbSXji0d.js → terminal-monaco-setup-Bb4lnoOq.js} +1 -1
- package/dist/ui/assets/{tsMode-jPpKNC6E.js → tsMode-BAHQHAnm.js} +1 -1
- package/dist/ui/assets/{typescript-Djn_8BHF.js → typescript-15FhCrR2.js} +1 -1
- package/dist/ui/assets/typescript.contribution-D30L44Mw.js +2 -0
- package/dist/ui/assets/xlsx-CdLmEC42.js +4 -0
- package/dist/ui/assets/{xml-DmE4Aqa-.js → xml-BA1mrkDo.js} +1 -1
- package/dist/ui/assets/xml.contribution-CN9NFcUA.js +2 -0
- package/dist/ui/assets/{yaml-BjmK3C-c.js → yaml-CIqCv-ro.js} +1 -1
- package/dist/ui/assets/yaml.contribution-BsLB8vrs.js +2 -0
- package/dist/ui/index.html +15 -15
- package/package.json +16 -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,11 +1008,11 @@ 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
|
-
import {
|
|
1015
|
+
import { windowsHeadlessSpawnOptions } from "@bendyline/gezel/native";
|
|
919
1016
|
|
|
920
1017
|
// src/sandbox/macos.ts
|
|
921
1018
|
function runUnderMacSandbox(command, args, ctx, opts = {}) {
|
|
@@ -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
|
}
|
|
@@ -1064,11 +1161,9 @@ async function readNodeHelp(nodeBin) {
|
|
|
1064
1161
|
const child = spawn2(nodeBin, ["--help"], {
|
|
1065
1162
|
stdio: ["ignore", "pipe", "pipe"],
|
|
1066
1163
|
env: sandboxEnv(process.env),
|
|
1067
|
-
//
|
|
1068
|
-
//
|
|
1069
|
-
|
|
1070
|
-
// (CREATE_NO_WINDOW) does not do that; it still allocates.
|
|
1071
|
-
...windowsDetachedSpawnOptions()
|
|
1164
|
+
// The capability probe is owned and awaited. Hide its Windows console
|
|
1165
|
+
// rather than giving the short-lived process a detached one.
|
|
1166
|
+
...windowsHeadlessSpawnOptions()
|
|
1072
1167
|
});
|
|
1073
1168
|
let stdout = "";
|
|
1074
1169
|
let stderr = "";
|
|
@@ -1090,16 +1185,11 @@ async function runSandboxChild(command, args, opts) {
|
|
|
1090
1185
|
const child = spawn2(command, args, {
|
|
1091
1186
|
cwd: opts.cwd,
|
|
1092
1187
|
stdio: opts.rpcChannel ? ["pipe", "pipe", "pipe", "pipe"] : ["pipe", "pipe", "pipe"],
|
|
1093
|
-
//
|
|
1094
|
-
//
|
|
1095
|
-
//
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
// console-subsystem child from asking the Session 0 service for a
|
|
1099
|
-
// console it cannot allocate — `windowsHide` (CREATE_NO_WINDOW) does
|
|
1100
|
-
// not, because it still allocates one. `taskkill /T` is unaffected:
|
|
1101
|
-
// detaching changes console and process group, not the recorded parent.
|
|
1102
|
-
detached: true,
|
|
1188
|
+
// A separate process group is useful on POSIX for group termination.
|
|
1189
|
+
// Windows tree cleanup follows parent PIDs instead, so detaching there
|
|
1190
|
+
// only creates a new console window; keep it owned and hidden.
|
|
1191
|
+
detached: process.platform !== "win32",
|
|
1192
|
+
...windowsHeadlessSpawnOptions(),
|
|
1103
1193
|
// Allowlist-scrub the env — inheriting everything leaked tokens
|
|
1104
1194
|
// (GEZEL_TOKEN, OPENAI_API_KEY, GITHUB_PERSONAL_ACCESS_TOKEN,
|
|
1105
1195
|
// etc.) into the sandboxed script, letting it `fetch` them
|
|
@@ -1210,7 +1300,7 @@ async function expandPermissionPaths(paths) {
|
|
|
1210
1300
|
const expanded = [];
|
|
1211
1301
|
const seen = /* @__PURE__ */ new Set();
|
|
1212
1302
|
for (const p of paths) {
|
|
1213
|
-
for (const candidate of [p, await
|
|
1303
|
+
for (const candidate of [p, await realpath3(p).catch(() => p)]) {
|
|
1214
1304
|
if (seen.has(candidate)) continue;
|
|
1215
1305
|
seen.add(candidate);
|
|
1216
1306
|
expanded.push(candidate);
|
|
@@ -1448,7 +1538,7 @@ syncBuiltinESMExports();
|
|
|
1448
1538
|
|
|
1449
1539
|
// src/utils/service-worker-entry.ts
|
|
1450
1540
|
import { existsSync } from "fs";
|
|
1451
|
-
import { dirname as
|
|
1541
|
+
import { dirname as dirname3, join as join4, resolve } from "path";
|
|
1452
1542
|
import { fileURLToPath } from "url";
|
|
1453
1543
|
var SERVICE_WORKER_ENTRIES = {
|
|
1454
1544
|
embeddings: { built: "memory/embed-worker.js" },
|
|
@@ -1461,10 +1551,10 @@ var SERVICE_WORKER_ENTRIES = {
|
|
|
1461
1551
|
};
|
|
1462
1552
|
var MAX_ANCESTOR_DEPTH = 8;
|
|
1463
1553
|
function findServiceBundleResource(moduleUrl, relativePaths) {
|
|
1464
|
-
let dir =
|
|
1554
|
+
let dir = dirname3(fileURLToPath(moduleUrl));
|
|
1465
1555
|
const visited = /* @__PURE__ */ new Set();
|
|
1466
1556
|
for (let depth = 0; depth < MAX_ANCESTOR_DEPTH; depth++) {
|
|
1467
|
-
for (const root of [dir,
|
|
1557
|
+
for (const root of [dir, join4(dir, "dist")]) {
|
|
1468
1558
|
for (const relativePath of relativePaths) {
|
|
1469
1559
|
const candidate = resolve(root, relativePath);
|
|
1470
1560
|
if (visited.has(candidate)) continue;
|
|
@@ -1472,7 +1562,7 @@ function findServiceBundleResource(moduleUrl, relativePaths) {
|
|
|
1472
1562
|
if (existsSync(candidate)) return candidate;
|
|
1473
1563
|
}
|
|
1474
1564
|
}
|
|
1475
|
-
const parent =
|
|
1565
|
+
const parent = dirname3(dir);
|
|
1476
1566
|
if (parent === dir) break;
|
|
1477
1567
|
dir = parent;
|
|
1478
1568
|
}
|
|
@@ -1494,8 +1584,8 @@ function findRepoRoot(start) {
|
|
|
1494
1584
|
let dir = start;
|
|
1495
1585
|
let highest = null;
|
|
1496
1586
|
for (; ; ) {
|
|
1497
|
-
if (existsSync2(
|
|
1498
|
-
const parent =
|
|
1587
|
+
if (existsSync2(join5(dir, "node_modules"))) highest = dir;
|
|
1588
|
+
const parent = dirname4(dir);
|
|
1499
1589
|
if (parent === dir) break;
|
|
1500
1590
|
dir = parent;
|
|
1501
1591
|
}
|
|
@@ -1505,12 +1595,12 @@ var readRootsCache;
|
|
|
1505
1595
|
function sandboxReadRoots() {
|
|
1506
1596
|
if (readRootsCache !== void 0) return readRootsCache;
|
|
1507
1597
|
const roots = /* @__PURE__ */ new Set();
|
|
1508
|
-
const repoRoot = findRepoRoot(
|
|
1598
|
+
const repoRoot = findRepoRoot(dirname4(fileURLToPath2(import.meta.url)));
|
|
1509
1599
|
if (repoRoot) roots.add(repoRoot);
|
|
1510
1600
|
try {
|
|
1511
1601
|
const require2 = createRequire(import.meta.url);
|
|
1512
1602
|
const resolved = require2.resolve("@bendyline/squisq-formats");
|
|
1513
|
-
const linkedRoot = findRepoRoot(
|
|
1603
|
+
const linkedRoot = findRepoRoot(dirname4(resolved));
|
|
1514
1604
|
if (linkedRoot) roots.add(linkedRoot);
|
|
1515
1605
|
} catch {
|
|
1516
1606
|
readRootsCache = null;
|
|
@@ -1557,10 +1647,10 @@ async function convertInSandbox(absPath, ext) {
|
|
|
1557
1647
|
log.warn("sandbox unavailable (worker or node_modules root unresolved); skipping conversion");
|
|
1558
1648
|
return { markdown: null };
|
|
1559
1649
|
}
|
|
1560
|
-
const scratch = await
|
|
1650
|
+
const scratch = await realpath4(await mkdtemp2(join5(tmpdir2(), "gezel-convert-")));
|
|
1561
1651
|
try {
|
|
1562
1652
|
const inputName = `input.${ext}`;
|
|
1563
|
-
await writeFile3(
|
|
1653
|
+
await writeFile3(join5(scratch, inputName), bytes);
|
|
1564
1654
|
const res = await runInSandbox({
|
|
1565
1655
|
entry: worker.path,
|
|
1566
1656
|
cwd: scratch,
|
|
@@ -1585,7 +1675,7 @@ async function convertInSandbox(absPath, ext) {
|
|
|
1585
1675
|
);
|
|
1586
1676
|
return { markdown: null };
|
|
1587
1677
|
}
|
|
1588
|
-
const md = await readFile(
|
|
1678
|
+
const md = await readFile(join5(scratch, "output.md"), "utf8").catch(() => null);
|
|
1589
1679
|
return { markdown: md };
|
|
1590
1680
|
} finally {
|
|
1591
1681
|
await rm3(scratch, { recursive: true, force: true });
|
|
@@ -1603,16 +1693,19 @@ async function convertDocToMarkdown(absPath) {
|
|
|
1603
1693
|
if (!isConvertibleDoc(ext)) return { markdown: null };
|
|
1604
1694
|
return convertInSandbox(absPath, ext);
|
|
1605
1695
|
}
|
|
1606
|
-
function
|
|
1607
|
-
const base =
|
|
1608
|
-
const nameNoExt = base.replace(/\.[^.]+$/, "");
|
|
1609
|
-
const parent =
|
|
1610
|
-
const
|
|
1611
|
-
const
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1696
|
+
function shadowDocFilesPaths(artifactsDir, relPath) {
|
|
1697
|
+
const base = basename3(relPath);
|
|
1698
|
+
const nameNoExt = base.replace(/\.[^.]+$/, "") || base;
|
|
1699
|
+
const parent = dirname5(relPath);
|
|
1700
|
+
const relMd = join6(parent === "." ? "" : parent, `${base}_files`, `${nameNoExt}.md`);
|
|
1701
|
+
const shadowRoot = join6(artifactsDir, PROJECT_SHADOW_DIR_NAME);
|
|
1702
|
+
const mdPath = safeJoin(shadowRoot, relMd);
|
|
1703
|
+
if (!mdPath) return null;
|
|
1704
|
+
return {
|
|
1705
|
+
dir: dirname5(mdPath),
|
|
1706
|
+
mdPath,
|
|
1707
|
+
mdRel: relative2(artifactsDir, mdPath).replaceAll("\\", "/")
|
|
1708
|
+
};
|
|
1616
1709
|
}
|
|
1617
1710
|
async function writeConvertedMarkdownAt(paths, markdown) {
|
|
1618
1711
|
await mkdir(paths.dir, { recursive: true });
|
|
@@ -1637,6 +1730,15 @@ async function ensureConvertedMarkdownSidecar(sourcePath, paths) {
|
|
|
1637
1730
|
}
|
|
1638
1731
|
return { ...converted, paths };
|
|
1639
1732
|
}
|
|
1733
|
+
async function ensureShadowDocSidecar(sourceAbs, artifactsDir, relPath) {
|
|
1734
|
+
const paths = shadowDocFilesPaths(artifactsDir, relPath);
|
|
1735
|
+
if (!paths) return null;
|
|
1736
|
+
const shadowRoot = join6(artifactsDir, PROJECT_SHADOW_DIR_NAME);
|
|
1737
|
+
await mkdir(shadowRoot, { recursive: true }).catch(() => {
|
|
1738
|
+
});
|
|
1739
|
+
if (!await realpathContained(shadowRoot, paths.mdPath)) return null;
|
|
1740
|
+
return ensureConvertedMarkdownSidecar(sourceAbs, paths);
|
|
1741
|
+
}
|
|
1640
1742
|
function chunkMarkdown(md) {
|
|
1641
1743
|
const lines = md.split(/\r?\n/);
|
|
1642
1744
|
const headIdx = [];
|
|
@@ -1684,8 +1786,8 @@ function parseFrontmatter(content) {
|
|
|
1684
1786
|
|
|
1685
1787
|
// src/index-store/gitignore.ts
|
|
1686
1788
|
import { mkdir as mkdir2, writeFile as writeFile4 } from "fs/promises";
|
|
1687
|
-
import { join as
|
|
1688
|
-
import {
|
|
1789
|
+
import { join as join7 } from "path";
|
|
1790
|
+
import { projectLocalIndexDir } from "@bendyline/gezel/paths";
|
|
1689
1791
|
|
|
1690
1792
|
// src/index-store/hash.ts
|
|
1691
1793
|
import { createHash } from "crypto";
|
|
@@ -1994,7 +2096,7 @@ import { createLogger as createLogger3 } from "@bendyline/gezel";
|
|
|
1994
2096
|
|
|
1995
2097
|
// src/transformers-cache.ts
|
|
1996
2098
|
import { mkdir as mkdir3 } from "fs/promises";
|
|
1997
|
-
import { join as
|
|
2099
|
+
import { join as join8 } from "path";
|
|
1998
2100
|
import { createLogger as createLogger2 } from "@bendyline/gezel";
|
|
1999
2101
|
var log2 = createLogger2("service");
|
|
2000
2102
|
|
|
@@ -2103,6 +2205,10 @@ CREATE TABLE IF NOT EXISTS summaries (
|
|
|
2103
2205
|
summary_md TEXT,
|
|
2104
2206
|
tags TEXT,
|
|
2105
2207
|
model TEXT,
|
|
2208
|
+
provider TEXT,
|
|
2209
|
+
gezel_id TEXT,
|
|
2210
|
+
gezel_name TEXT,
|
|
2211
|
+
app_version TEXT,
|
|
2106
2212
|
created_at TEXT
|
|
2107
2213
|
);
|
|
2108
2214
|
|
|
@@ -2219,6 +2325,10 @@ CREATE TABLE IF NOT EXISTS symbol_summaries (
|
|
|
2219
2325
|
symbol_name TEXT NOT NULL,
|
|
2220
2326
|
summary TEXT NOT NULL,
|
|
2221
2327
|
model TEXT,
|
|
2328
|
+
provider TEXT,
|
|
2329
|
+
gezel_id TEXT,
|
|
2330
|
+
gezel_name TEXT,
|
|
2331
|
+
app_version TEXT,
|
|
2222
2332
|
created_at TEXT,
|
|
2223
2333
|
PRIMARY KEY (collection_id, file_path, file_hash, symbol_name)
|
|
2224
2334
|
);
|
|
@@ -2235,6 +2345,10 @@ CREATE TABLE IF NOT EXISTS area_summaries (
|
|
|
2235
2345
|
input_hash TEXT NOT NULL,
|
|
2236
2346
|
summary_md TEXT NOT NULL,
|
|
2237
2347
|
model TEXT,
|
|
2348
|
+
provider TEXT,
|
|
2349
|
+
gezel_id TEXT,
|
|
2350
|
+
gezel_name TEXT,
|
|
2351
|
+
app_version TEXT,
|
|
2238
2352
|
created_at TEXT,
|
|
2239
2353
|
PRIMARY KEY (collection_id, area_path)
|
|
2240
2354
|
);
|
|
@@ -2258,12 +2372,30 @@ CREATE TABLE IF NOT EXISTS file_reviews (
|
|
|
2258
2372
|
health INTEGER,
|
|
2259
2373
|
health_reason TEXT,
|
|
2260
2374
|
model TEXT,
|
|
2375
|
+
provider TEXT,
|
|
2376
|
+
gezel_id TEXT,
|
|
2377
|
+
gezel_name TEXT,
|
|
2378
|
+
app_version TEXT,
|
|
2261
2379
|
reviewed_at TEXT,
|
|
2262
2380
|
attempt_rubric_hash TEXT,
|
|
2263
2381
|
attempts INTEGER DEFAULT 0,
|
|
2264
2382
|
last_attempt_at TEXT
|
|
2265
2383
|
);
|
|
2266
2384
|
|
|
2385
|
+
-- AI-shadow gate (images described by the vision model, audio transcribed by
|
|
2386
|
+
-- STT into artifacts/shadow markdown). Same content-addressed discipline as
|
|
2387
|
+
-- enrichments: 'ok' rows survive renames/reverts; failures retry up to a cap
|
|
2388
|
+
-- per hash. The sidecar file is the artifact; this row is only the work gate.
|
|
2389
|
+
CREATE TABLE IF NOT EXISTS shadow_state (
|
|
2390
|
+
content_hash TEXT PRIMARY KEY,
|
|
2391
|
+
collection_id TEXT,
|
|
2392
|
+
file_path TEXT,
|
|
2393
|
+
state TEXT,
|
|
2394
|
+
attempts INTEGER DEFAULT 0,
|
|
2395
|
+
model TEXT,
|
|
2396
|
+
updated_at TEXT
|
|
2397
|
+
);
|
|
2398
|
+
|
|
2267
2399
|
-- Mirror of the append-only history JSONL (the audit log). kind/project/gezel/at
|
|
2268
2400
|
-- are real columns so HistoryFilter fields stay SQL-filterable; only the q text
|
|
2269
2401
|
-- goes through fts_history. INSERT OR IGNORE on the event id makes JSONL
|
|
@@ -2292,7 +2424,7 @@ CREATE VIRTUAL TABLE IF NOT EXISTS fts_metadata
|
|
|
2292
2424
|
CREATE VIRTUAL TABLE IF NOT EXISTS fts_history
|
|
2293
2425
|
USING fts5(body, event_id UNINDEXED, collection_id UNINDEXED);
|
|
2294
2426
|
`;
|
|
2295
|
-
var SCHEMA_VERSION =
|
|
2427
|
+
var SCHEMA_VERSION = 10;
|
|
2296
2428
|
function addColumnIfMissing(db, table, column, decl) {
|
|
2297
2429
|
try {
|
|
2298
2430
|
const cols = db.prepare(`PRAGMA table_info(${table})`).all();
|
|
@@ -2316,6 +2448,12 @@ function applySchema(db) {
|
|
|
2316
2448
|
WHERE fingerprint IS NULL OR fingerprint = ''`);
|
|
2317
2449
|
} catch {
|
|
2318
2450
|
}
|
|
2451
|
+
for (const table of ["summaries", "symbol_summaries", "area_summaries", "file_reviews"]) {
|
|
2452
|
+
addColumnIfMissing(db, table, "provider", "TEXT");
|
|
2453
|
+
addColumnIfMissing(db, table, "gezel_id", "TEXT");
|
|
2454
|
+
addColumnIfMissing(db, table, "gezel_name", "TEXT");
|
|
2455
|
+
addColumnIfMissing(db, table, "app_version", "TEXT");
|
|
2456
|
+
}
|
|
2319
2457
|
let fts = true;
|
|
2320
2458
|
try {
|
|
2321
2459
|
db.exec(FTS_DDL);
|
|
@@ -2343,7 +2481,7 @@ function applySchema(db) {
|
|
|
2343
2481
|
// src/index-store/sqlite-driver.ts
|
|
2344
2482
|
import { mkdir as mkdir4, rename as rename2 } from "fs/promises";
|
|
2345
2483
|
import { createRequire as createRequire2 } from "module";
|
|
2346
|
-
import { dirname as
|
|
2484
|
+
import { dirname as dirname6 } from "path";
|
|
2347
2485
|
import { createLogger as createLogger4 } from "@bendyline/gezel";
|
|
2348
2486
|
var nodeRequire = createRequire2(import.meta.url);
|
|
2349
2487
|
var log4 = createLogger4("sqlite");
|
|
@@ -2366,7 +2504,7 @@ async function openIndexDatabase(dbPath) {
|
|
|
2366
2504
|
}
|
|
2367
2505
|
if (dbPath !== ":memory:") {
|
|
2368
2506
|
try {
|
|
2369
|
-
await mkdir4(
|
|
2507
|
+
await mkdir4(dirname6(dbPath), { recursive: true });
|
|
2370
2508
|
} catch (error) {
|
|
2371
2509
|
if (isUnavailableIndexError(error)) return null;
|
|
2372
2510
|
throw error;
|
|
@@ -2375,6 +2513,7 @@ async function openIndexDatabase(dbPath) {
|
|
|
2375
2513
|
let db;
|
|
2376
2514
|
try {
|
|
2377
2515
|
db = new DatabaseSync(dbPath, { allowExtension: true });
|
|
2516
|
+
setBusyTimeout(db);
|
|
2378
2517
|
assertReadableHeader(db);
|
|
2379
2518
|
} catch (error) {
|
|
2380
2519
|
if (dbPath === ":memory:" || !isCorruptDatabaseError(error)) {
|
|
@@ -2391,6 +2530,7 @@ async function openIndexDatabase(dbPath) {
|
|
|
2391
2530
|
await quarantineCorruptDatabase(dbPath);
|
|
2392
2531
|
log4.warn(`[sqlite] quarantined corrupt rebuildable index ${dbPath}`);
|
|
2393
2532
|
db = new DatabaseSync(dbPath, { allowExtension: true });
|
|
2533
|
+
setBusyTimeout(db);
|
|
2394
2534
|
}
|
|
2395
2535
|
if (!db) throw new Error("failed to open sqlite index");
|
|
2396
2536
|
let vecAvailable = false;
|
|
@@ -2439,6 +2579,12 @@ async function openIndexDatabase(dbPath) {
|
|
|
2439
2579
|
function assertReadableHeader(db) {
|
|
2440
2580
|
db.prepare("PRAGMA schema_version").get();
|
|
2441
2581
|
}
|
|
2582
|
+
function setBusyTimeout(db) {
|
|
2583
|
+
try {
|
|
2584
|
+
db.exec("PRAGMA busy_timeout=5000;");
|
|
2585
|
+
} catch {
|
|
2586
|
+
}
|
|
2587
|
+
}
|
|
2442
2588
|
function isCorruptDatabaseError(error) {
|
|
2443
2589
|
const message = error instanceof Error ? error.message : String(error);
|
|
2444
2590
|
return /SQLITE_CORRUPT|SQLITE_NOTADB|database disk image is malformed|file is not a database/i.test(
|
|
@@ -2447,9 +2593,7 @@ function isCorruptDatabaseError(error) {
|
|
|
2447
2593
|
}
|
|
2448
2594
|
function isUnavailableIndexError(error) {
|
|
2449
2595
|
const message = error instanceof Error ? error.message : String(error);
|
|
2450
|
-
return /SQLITE_READONLY|SQLITE_CANTOPEN|
|
|
2451
|
-
message
|
|
2452
|
-
);
|
|
2596
|
+
return /SQLITE_READONLY|SQLITE_CANTOPEN|EACCES|EPERM|read-only/i.test(message);
|
|
2453
2597
|
}
|
|
2454
2598
|
async function quarantineCorruptDatabase(dbPath) {
|
|
2455
2599
|
const suffix = `.corrupt-${Date.now()}`;
|
|
@@ -2728,7 +2872,9 @@ var IndexStore = class _IndexStore {
|
|
|
2728
2872
|
`SELECT f.* FROM files f
|
|
2729
2873
|
LEFT JOIN enrichments e ON e.content_hash = f.hash
|
|
2730
2874
|
WHERE f.collection_id = ? AND f.trivial = 0 AND f.hash IS NOT NULL
|
|
2731
|
-
AND f.modality IN ('code','text','doc')
|
|
2875
|
+
AND (f.modality IN ('code','text','doc')
|
|
2876
|
+
OR EXISTS (SELECT 1 FROM shadow_state s
|
|
2877
|
+
WHERE s.content_hash = f.hash AND s.state = 'ok'))
|
|
2732
2878
|
AND (e.content_hash IS NULL
|
|
2733
2879
|
OR (e.embedded_at IS NULL AND COALESCE(e.attempts, 0) < ${MAX_ENRICH_ATTEMPTS}))
|
|
2734
2880
|
ORDER BY f.path LIMIT ?`
|
|
@@ -2742,12 +2888,60 @@ var IndexStore = class _IndexStore {
|
|
|
2742
2888
|
`SELECT COUNT(*) AS n FROM files f
|
|
2743
2889
|
LEFT JOIN enrichments e ON e.content_hash = f.hash
|
|
2744
2890
|
WHERE f.collection_id = ? AND f.trivial = 0 AND f.hash IS NOT NULL
|
|
2745
|
-
AND f.modality IN ('code','text','doc')
|
|
2891
|
+
AND (f.modality IN ('code','text','doc')
|
|
2892
|
+
OR EXISTS (SELECT 1 FROM shadow_state s
|
|
2893
|
+
WHERE s.content_hash = f.hash AND s.state = 'ok'))
|
|
2746
2894
|
AND (e.content_hash IS NULL
|
|
2747
2895
|
OR (e.embedded_at IS NULL AND COALESCE(e.attempts, 0) < ${MAX_ENRICH_ATTEMPTS}))`
|
|
2748
2896
|
).get(this.collectionId);
|
|
2749
2897
|
return row?.n ?? 0;
|
|
2750
2898
|
}
|
|
2899
|
+
/**
|
|
2900
|
+
* Image/audio files whose AI shadow (description / transcript) is missing
|
|
2901
|
+
* or stale for the current content hash. Same capped-retry discipline as
|
|
2902
|
+
* {@link filesNeedingEnrichment}; an 'ok' row is the terminal success.
|
|
2903
|
+
*/
|
|
2904
|
+
filesNeedingAiShadow(limit = 5, maxAttempts = MAX_ENRICH_ATTEMPTS) {
|
|
2905
|
+
return this.db.prepare(
|
|
2906
|
+
`SELECT f.* FROM files f
|
|
2907
|
+
LEFT JOIN shadow_state s ON s.content_hash = f.hash
|
|
2908
|
+
WHERE f.collection_id = ? AND f.trivial = 0 AND f.hash IS NOT NULL
|
|
2909
|
+
AND f.modality IN ('image','audio')
|
|
2910
|
+
AND (s.content_hash IS NULL
|
|
2911
|
+
OR (s.state <> 'ok' AND COALESCE(s.attempts, 0) < ?))
|
|
2912
|
+
ORDER BY f.path LIMIT ?`
|
|
2913
|
+
).all(this.collectionId, maxAttempts, limit).map(rowToFile);
|
|
2914
|
+
}
|
|
2915
|
+
markAiShadowOk(contentHash, filePath, model) {
|
|
2916
|
+
this.db.prepare(
|
|
2917
|
+
`INSERT OR REPLACE INTO shadow_state (content_hash, collection_id, file_path, state, attempts, model, updated_at)
|
|
2918
|
+
VALUES (?, ?, ?, 'ok', 0, ?, ?)`
|
|
2919
|
+
).run(contentHash, this.collectionId, filePath, model ?? null, nowIso());
|
|
2920
|
+
}
|
|
2921
|
+
markAiShadowAttempt(contentHash, filePath) {
|
|
2922
|
+
this.db.prepare(
|
|
2923
|
+
`INSERT INTO shadow_state (content_hash, collection_id, file_path, state, attempts, updated_at)
|
|
2924
|
+
VALUES (?, ?, ?, 'failed', 1, ?)
|
|
2925
|
+
ON CONFLICT(content_hash) DO UPDATE SET
|
|
2926
|
+
state='failed', attempts=COALESCE(shadow_state.attempts, 0) + 1,
|
|
2927
|
+
updated_at=excluded.updated_at`
|
|
2928
|
+
).run(contentHash, this.collectionId, filePath, nowIso());
|
|
2929
|
+
return Number(
|
|
2930
|
+
this.db.prepare("SELECT attempts FROM shadow_state WHERE content_hash = ?").get(contentHash)?.attempts ?? 1
|
|
2931
|
+
);
|
|
2932
|
+
}
|
|
2933
|
+
/**
|
|
2934
|
+
* Terminally skip a media hash the AI-shadow tier can never process — a
|
|
2935
|
+
* format with no raster decoder in the vision stack (SVG is vector, ICO is
|
|
2936
|
+
* multi-frame). Jumps straight to the attempt cap under a distinct state so
|
|
2937
|
+
* the row reads as "wrong tool", not "engine flaked three times".
|
|
2938
|
+
*/
|
|
2939
|
+
markAiShadowUnsupported(contentHash, filePath) {
|
|
2940
|
+
this.db.prepare(
|
|
2941
|
+
`INSERT OR REPLACE INTO shadow_state (content_hash, collection_id, file_path, state, attempts, updated_at)
|
|
2942
|
+
VALUES (?, ?, ?, 'unsupported', ${MAX_ENRICH_ATTEMPTS}, ?)`
|
|
2943
|
+
).run(contentHash, this.collectionId, filePath, nowIso());
|
|
2944
|
+
}
|
|
2751
2945
|
/**
|
|
2752
2946
|
* Enrichment coverage counts. `summarized` counts real summaries (join on
|
|
2753
2947
|
* the summaries table), deliberately NOT the enrichments gate — the gate also
|
|
@@ -2766,15 +2960,39 @@ var IndexStore = class _IndexStore {
|
|
|
2766
2960
|
`SELECT COUNT(*) AS n FROM files f JOIN enrichments e ON e.content_hash = f.hash
|
|
2767
2961
|
WHERE e.embedded_at IS NOT NULL AND ${ELIGIBLE}`
|
|
2768
2962
|
);
|
|
2963
|
+
const skipped = count(
|
|
2964
|
+
`SELECT COUNT(*) AS n FROM files f JOIN enrichments e ON e.content_hash = f.hash
|
|
2965
|
+
WHERE e.embedded_at IS NULL AND COALESCE(e.attempts, 0) >= ${MAX_ENRICH_ATTEMPTS}
|
|
2966
|
+
AND ${ELIGIBLE}`
|
|
2967
|
+
);
|
|
2769
2968
|
const embedModel = this.getMeta("embed_model");
|
|
2770
2969
|
return {
|
|
2771
2970
|
eligible,
|
|
2772
2971
|
summarized,
|
|
2773
2972
|
embedded,
|
|
2774
2973
|
pending: this.countNeedingEnrichment(),
|
|
2974
|
+
skipped,
|
|
2975
|
+
shadowsPending: this.countNeedingAiShadow(),
|
|
2775
2976
|
...embedModel ? { embedModel } : {}
|
|
2776
2977
|
};
|
|
2777
2978
|
}
|
|
2979
|
+
/**
|
|
2980
|
+
* COUNT twin of `filesNeedingAiShadow` — media files still awaiting an AI
|
|
2981
|
+
* description, attempt-capped ones excluded. The drive works this tier
|
|
2982
|
+
* before summaries, so status must surface it or a fresh scan looks stuck.
|
|
2983
|
+
*/
|
|
2984
|
+
countNeedingAiShadow(maxAttempts = MAX_ENRICH_ATTEMPTS) {
|
|
2985
|
+
return Number(
|
|
2986
|
+
this.db.prepare(
|
|
2987
|
+
`SELECT COUNT(*) AS n FROM files f
|
|
2988
|
+
LEFT JOIN shadow_state s ON s.content_hash = f.hash
|
|
2989
|
+
WHERE f.collection_id = ? AND f.trivial = 0 AND f.hash IS NOT NULL
|
|
2990
|
+
AND f.modality IN ('image','audio')
|
|
2991
|
+
AND (s.content_hash IS NULL
|
|
2992
|
+
OR (s.state <> 'ok' AND COALESCE(s.attempts, 0) < ?))`
|
|
2993
|
+
).get(this.collectionId, maxAttempts)?.n ?? 0
|
|
2994
|
+
);
|
|
2995
|
+
}
|
|
2778
2996
|
/** Mark a content hash as enriched (vectors built) so it isn't reprocessed. */
|
|
2779
2997
|
markEnriched(contentHash) {
|
|
2780
2998
|
this.db.prepare("INSERT OR REPLACE INTO enrichments (content_hash, embedded_at) VALUES (?, ?)").run(contentHash, nowIso());
|
|
@@ -2790,6 +3008,23 @@ var IndexStore = class _IndexStore {
|
|
|
2790
3008
|
`INSERT INTO enrichments (content_hash, embedded_at, attempts) VALUES (?, NULL, 1)
|
|
2791
3009
|
ON CONFLICT(content_hash) DO UPDATE SET attempts = COALESCE(enrichments.attempts, 0) + 1`
|
|
2792
3010
|
).run(contentHash);
|
|
3011
|
+
return Number(
|
|
3012
|
+
this.db.prepare("SELECT attempts FROM enrichments WHERE content_hash = ?").get(contentHash)?.attempts ?? 1
|
|
3013
|
+
);
|
|
3014
|
+
}
|
|
3015
|
+
/**
|
|
3016
|
+
* Jump a hash straight to the attempt cap — for deterministic failures
|
|
3017
|
+
* (a provider policy-blocking the file's content) where every retry would
|
|
3018
|
+
* fail identically. The file drops off the work list and counts as
|
|
3019
|
+
* `skipped` in {@link enrichmentCounts} until its content changes.
|
|
3020
|
+
*/
|
|
3021
|
+
markEnrichSkipped(contentHash) {
|
|
3022
|
+
this.db.prepare(
|
|
3023
|
+
`INSERT INTO enrichments (content_hash, embedded_at, attempts)
|
|
3024
|
+
VALUES (?, NULL, ${MAX_ENRICH_ATTEMPTS})
|
|
3025
|
+
ON CONFLICT(content_hash) DO UPDATE
|
|
3026
|
+
SET attempts = MAX(COALESCE(enrichments.attempts, 0), ${MAX_ENRICH_ATTEMPTS})`
|
|
3027
|
+
).run(contentHash);
|
|
2793
3028
|
}
|
|
2794
3029
|
/**
|
|
2795
3030
|
* Keyword search over enrichment summaries. fts_summaries rows are
|
|
@@ -2942,11 +3177,15 @@ var IndexStore = class _IndexStore {
|
|
|
2942
3177
|
upsertSummary(rec) {
|
|
2943
3178
|
this.db.transaction(() => {
|
|
2944
3179
|
this.db.prepare(
|
|
2945
|
-
`INSERT INTO summaries (content_hash, collection_id, file_path, summary_md, tags, model,
|
|
2946
|
-
|
|
3180
|
+
`INSERT INTO summaries (content_hash, collection_id, file_path, summary_md, tags, model,
|
|
3181
|
+
provider, gezel_id, gezel_name, app_version, created_at)
|
|
3182
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
2947
3183
|
ON CONFLICT(content_hash) DO UPDATE SET
|
|
2948
3184
|
file_path=excluded.file_path, summary_md=excluded.summary_md,
|
|
2949
|
-
tags=excluded.tags, model=excluded.model,
|
|
3185
|
+
tags=excluded.tags, model=excluded.model,
|
|
3186
|
+
provider=excluded.provider, gezel_id=excluded.gezel_id,
|
|
3187
|
+
gezel_name=excluded.gezel_name, app_version=excluded.app_version,
|
|
3188
|
+
created_at=excluded.created_at`
|
|
2950
3189
|
).run(
|
|
2951
3190
|
rec.contentHash,
|
|
2952
3191
|
this.collectionId,
|
|
@@ -2954,6 +3193,10 @@ var IndexStore = class _IndexStore {
|
|
|
2954
3193
|
rec.summaryMd,
|
|
2955
3194
|
rec.tags ?? null,
|
|
2956
3195
|
rec.model,
|
|
3196
|
+
rec.provenance?.provider ?? null,
|
|
3197
|
+
rec.provenance?.gezelId ?? null,
|
|
3198
|
+
rec.provenance?.gezelName ?? null,
|
|
3199
|
+
rec.provenance?.appVersion ?? null,
|
|
2957
3200
|
nowIso()
|
|
2958
3201
|
);
|
|
2959
3202
|
if (this.caps.fts) {
|
|
@@ -2982,16 +3225,29 @@ var IndexStore = class _IndexStore {
|
|
|
2982
3225
|
* for any other (stale) hash of the same file. Keyed by symbol name only —
|
|
2983
3226
|
* overloads/same-name symbols share a line (accepted v1 simplification).
|
|
2984
3227
|
*/
|
|
2985
|
-
putSymbolSummaries(filePath, fileHash, entries, model) {
|
|
3228
|
+
putSymbolSummaries(filePath, fileHash, entries, model, provenance) {
|
|
2986
3229
|
this.db.transaction(() => {
|
|
2987
3230
|
this.db.prepare("DELETE FROM symbol_summaries WHERE collection_id = ? AND file_path = ?").run(this.collectionId, filePath);
|
|
2988
3231
|
const ins = this.db.prepare(
|
|
2989
|
-
`INSERT OR REPLACE INTO symbol_summaries (collection_id, file_path, file_hash, symbol_name, summary, model,
|
|
2990
|
-
|
|
3232
|
+
`INSERT OR REPLACE INTO symbol_summaries (collection_id, file_path, file_hash, symbol_name, summary, model,
|
|
3233
|
+
provider, gezel_id, gezel_name, app_version, created_at)
|
|
3234
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
2991
3235
|
);
|
|
2992
3236
|
const at = nowIso();
|
|
2993
3237
|
for (const e of entries) {
|
|
2994
|
-
ins.run(
|
|
3238
|
+
ins.run(
|
|
3239
|
+
this.collectionId,
|
|
3240
|
+
filePath,
|
|
3241
|
+
fileHash,
|
|
3242
|
+
e.name,
|
|
3243
|
+
e.summary,
|
|
3244
|
+
model ?? null,
|
|
3245
|
+
provenance?.provider ?? null,
|
|
3246
|
+
provenance?.gezelId ?? null,
|
|
3247
|
+
provenance?.gezelName ?? null,
|
|
3248
|
+
provenance?.appVersion ?? null,
|
|
3249
|
+
at
|
|
3250
|
+
);
|
|
2995
3251
|
}
|
|
2996
3252
|
});
|
|
2997
3253
|
}
|
|
@@ -3009,14 +3265,17 @@ var IndexStore = class _IndexStore {
|
|
|
3009
3265
|
upsertFileReview(rec) {
|
|
3010
3266
|
this.db.prepare(
|
|
3011
3267
|
`INSERT INTO file_reviews (content_hash, collection_id, file_path, rubric_hash, notes_md,
|
|
3012
|
-
issues, health, health_reason, model,
|
|
3268
|
+
issues, health, health_reason, model,
|
|
3269
|
+
provider, gezel_id, gezel_name, app_version, reviewed_at,
|
|
3013
3270
|
attempt_rubric_hash, attempts, last_attempt_at)
|
|
3014
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, 0, NULL)
|
|
3271
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, 0, NULL)
|
|
3015
3272
|
ON CONFLICT(content_hash) DO UPDATE SET
|
|
3016
3273
|
collection_id=excluded.collection_id, file_path=excluded.file_path,
|
|
3017
3274
|
rubric_hash=excluded.rubric_hash, notes_md=excluded.notes_md,
|
|
3018
3275
|
issues=excluded.issues, health=excluded.health,
|
|
3019
3276
|
health_reason=excluded.health_reason, model=excluded.model,
|
|
3277
|
+
provider=excluded.provider, gezel_id=excluded.gezel_id,
|
|
3278
|
+
gezel_name=excluded.gezel_name, app_version=excluded.app_version,
|
|
3020
3279
|
reviewed_at=excluded.reviewed_at,
|
|
3021
3280
|
attempt_rubric_hash=NULL, attempts=0, last_attempt_at=NULL`
|
|
3022
3281
|
).run(
|
|
@@ -3029,9 +3288,32 @@ var IndexStore = class _IndexStore {
|
|
|
3029
3288
|
rec.health,
|
|
3030
3289
|
rec.healthReason,
|
|
3031
3290
|
rec.model,
|
|
3291
|
+
rec.provenance?.provider ?? null,
|
|
3292
|
+
rec.provenance?.gezelId ?? null,
|
|
3293
|
+
rec.provenance?.gezelName ?? null,
|
|
3294
|
+
rec.provenance?.appVersion ?? null,
|
|
3032
3295
|
nowIso()
|
|
3033
3296
|
);
|
|
3034
3297
|
}
|
|
3298
|
+
/**
|
|
3299
|
+
* Terminal skip for content that CANNOT be reviewed under its current hash
|
|
3300
|
+
* — a doc whose conversion was blocked/failed, an empty file, a source
|
|
3301
|
+
* deleted between scan and review. Burns the whole attempt budget in one
|
|
3302
|
+
* write (success fields untouched) so `filesNeedingReview` stops selecting
|
|
3303
|
+
* the file; without this, one blocked doc permanently steals a batch slot
|
|
3304
|
+
* from its kind on every sweep. A content change (new hash) or a rubric
|
|
3305
|
+
* edit re-admits it exactly like any capped-out attempt.
|
|
3306
|
+
*/
|
|
3307
|
+
markReviewUnavailable(contentHash, filePath, rubricHash, maxAttempts) {
|
|
3308
|
+
this.db.prepare(
|
|
3309
|
+
`INSERT INTO file_reviews (content_hash, collection_id, file_path, attempt_rubric_hash, attempts, last_attempt_at)
|
|
3310
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
3311
|
+
ON CONFLICT(content_hash) DO UPDATE SET
|
|
3312
|
+
attempts = excluded.attempts,
|
|
3313
|
+
attempt_rubric_hash = excluded.attempt_rubric_hash,
|
|
3314
|
+
last_attempt_at = excluded.last_attempt_at`
|
|
3315
|
+
).run(contentHash, this.collectionId, filePath, rubricHash, maxAttempts, nowIso());
|
|
3316
|
+
}
|
|
3035
3317
|
/**
|
|
3036
3318
|
* Record a non-empty-but-unparseable review reply WITHOUT touching any
|
|
3037
3319
|
* success field — a stale-rubric review keeps serving until a new one
|
|
@@ -3052,7 +3334,8 @@ var IndexStore = class _IndexStore {
|
|
|
3052
3334
|
/** The successful review for a content hash (attempt-only rows excluded). */
|
|
3053
3335
|
getFileReview(contentHash) {
|
|
3054
3336
|
const row = this.db.prepare(
|
|
3055
|
-
`SELECT notes_md, issues, health, health_reason, rubric_hash, model,
|
|
3337
|
+
`SELECT notes_md, issues, health, health_reason, rubric_hash, model,
|
|
3338
|
+
provider, gezel_id, gezel_name, app_version, reviewed_at
|
|
3056
3339
|
FROM file_reviews
|
|
3057
3340
|
WHERE content_hash = ? AND notes_md IS NOT NULL AND health IS NOT NULL`
|
|
3058
3341
|
).get(contentHash);
|
|
@@ -3064,9 +3347,31 @@ var IndexStore = class _IndexStore {
|
|
|
3064
3347
|
healthReason: row.health_reason ?? "",
|
|
3065
3348
|
rubricHash: row.rubric_hash ?? "",
|
|
3066
3349
|
model: row.model,
|
|
3350
|
+
provider: row.provider,
|
|
3351
|
+
gezelId: row.gezel_id,
|
|
3352
|
+
gezelName: row.gezel_name,
|
|
3353
|
+
appVersion: row.app_version,
|
|
3067
3354
|
reviewedAt: row.reviewed_at
|
|
3068
3355
|
};
|
|
3069
3356
|
}
|
|
3357
|
+
/**
|
|
3358
|
+
* Current file-hash review observations, including clean reviews. The
|
|
3359
|
+
* durable Boekwachter registry consumes these so a later file edit cannot
|
|
3360
|
+
* erase identity/lifecycle merely by invalidating `file_reviews`.
|
|
3361
|
+
*/
|
|
3362
|
+
currentFileReviewIssues() {
|
|
3363
|
+
return this.db.prepare(
|
|
3364
|
+
`SELECT f.path, f.hash AS content_hash, r.issues
|
|
3365
|
+
FROM files f
|
|
3366
|
+
JOIN file_reviews r ON r.content_hash = f.hash
|
|
3367
|
+
WHERE f.collection_id = ? AND f.hash IS NOT NULL AND r.notes_md IS NOT NULL
|
|
3368
|
+
ORDER BY f.path`
|
|
3369
|
+
).all(this.collectionId).map((row) => ({
|
|
3370
|
+
path: row.path,
|
|
3371
|
+
contentHash: row.content_hash,
|
|
3372
|
+
issues: parseIssuesJson(row.issues)
|
|
3373
|
+
}));
|
|
3374
|
+
}
|
|
3070
3375
|
/**
|
|
3071
3376
|
* Files of one kind whose current hash needs a review under the given
|
|
3072
3377
|
* rubric: no row yet, or covered by a DIFFERENT rubric and still holding
|
|
@@ -3236,6 +3541,17 @@ var IndexStore = class _IndexStore {
|
|
|
3236
3541
|
return out;
|
|
3237
3542
|
};
|
|
3238
3543
|
const bySeverity = tally(`COALESCE(json_extract(j.value, '$.severity'), 'info')`);
|
|
3544
|
+
const reviewers = this.db.prepare(
|
|
3545
|
+
`SELECT r.model, r.provider, r.gezel_name, COUNT(DISTINCT r.content_hash) AS files
|
|
3546
|
+
${FROM}
|
|
3547
|
+
GROUP BY r.model, r.provider, r.gezel_name
|
|
3548
|
+
ORDER BY files DESC LIMIT 5`
|
|
3549
|
+
).all(...params).map((r) => ({
|
|
3550
|
+
model: r.model,
|
|
3551
|
+
provider: r.provider,
|
|
3552
|
+
gezelName: r.gezel_name,
|
|
3553
|
+
files: Number(r.files)
|
|
3554
|
+
}));
|
|
3239
3555
|
return {
|
|
3240
3556
|
issues: rows.filter((r) => r.message).map((r) => ({
|
|
3241
3557
|
path: r.path,
|
|
@@ -3248,18 +3564,34 @@ var IndexStore = class _IndexStore {
|
|
|
3248
3564
|
total: Object.values(bySeverity).reduce((a, b) => a + b, 0),
|
|
3249
3565
|
bySeverity,
|
|
3250
3566
|
byCategory: tally(`COALESCE(json_extract(j.value, '$.category'), 'general')`)
|
|
3251
|
-
}
|
|
3567
|
+
},
|
|
3568
|
+
reviewers
|
|
3252
3569
|
};
|
|
3253
3570
|
}
|
|
3254
3571
|
// ── area summaries (deep pass) ─────────────────────────────────────────
|
|
3255
3572
|
upsertAreaSummary(rec) {
|
|
3256
3573
|
this.db.prepare(
|
|
3257
|
-
`INSERT INTO area_summaries (collection_id, area_path, input_hash, summary_md, model,
|
|
3258
|
-
|
|
3574
|
+
`INSERT INTO area_summaries (collection_id, area_path, input_hash, summary_md, model,
|
|
3575
|
+
provider, gezel_id, gezel_name, app_version, created_at)
|
|
3576
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
3259
3577
|
ON CONFLICT(collection_id, area_path) DO UPDATE SET
|
|
3260
3578
|
input_hash=excluded.input_hash, summary_md=excluded.summary_md,
|
|
3261
|
-
model=excluded.model,
|
|
3262
|
-
|
|
3579
|
+
model=excluded.model,
|
|
3580
|
+
provider=excluded.provider, gezel_id=excluded.gezel_id,
|
|
3581
|
+
gezel_name=excluded.gezel_name, app_version=excluded.app_version,
|
|
3582
|
+
created_at=excluded.created_at`
|
|
3583
|
+
).run(
|
|
3584
|
+
this.collectionId,
|
|
3585
|
+
rec.areaPath,
|
|
3586
|
+
rec.inputHash,
|
|
3587
|
+
rec.summaryMd,
|
|
3588
|
+
rec.model,
|
|
3589
|
+
rec.provenance?.provider ?? null,
|
|
3590
|
+
rec.provenance?.gezelId ?? null,
|
|
3591
|
+
rec.provenance?.gezelName ?? null,
|
|
3592
|
+
rec.provenance?.appVersion ?? null,
|
|
3593
|
+
nowIso()
|
|
3594
|
+
);
|
|
3263
3595
|
}
|
|
3264
3596
|
getAreaSummary(areaPath) {
|
|
3265
3597
|
const row = this.db.prepare(
|
|
@@ -4025,7 +4357,7 @@ function parseBindings(raw) {
|
|
|
4025
4357
|
|
|
4026
4358
|
// src/index-store/symbols.ts
|
|
4027
4359
|
import { createRequire as createRequire3 } from "module";
|
|
4028
|
-
import { dirname as
|
|
4360
|
+
import { dirname as dirname7, join as join9 } from "path";
|
|
4029
4361
|
var nodeRequire2 = createRequire3(import.meta.url);
|
|
4030
4362
|
var SIG_CAP = 160;
|
|
4031
4363
|
var GRAMMAR_FILE = {
|
|
@@ -4098,8 +4430,8 @@ async function ensureInit() {
|
|
|
4098
4430
|
initPromise = (async () => {
|
|
4099
4431
|
try {
|
|
4100
4432
|
const mod = nodeRequire2("web-tree-sitter");
|
|
4101
|
-
const wtsDir =
|
|
4102
|
-
await mod.Parser.init({ locateFile: (f) =>
|
|
4433
|
+
const wtsDir = dirname7(nodeRequire2.resolve("web-tree-sitter"));
|
|
4434
|
+
await mod.Parser.init({ locateFile: (f) => join9(wtsDir, f) });
|
|
4103
4435
|
ParserCtor = mod.Parser;
|
|
4104
4436
|
LanguageMod = mod.Language;
|
|
4105
4437
|
return true;
|
|
@@ -4110,7 +4442,7 @@ async function ensureInit() {
|
|
|
4110
4442
|
return initPromise;
|
|
4111
4443
|
}
|
|
4112
4444
|
function grammarDir() {
|
|
4113
|
-
return
|
|
4445
|
+
return join9(dirname7(nodeRequire2.resolve("tree-sitter-wasms/package.json")), "out");
|
|
4114
4446
|
}
|
|
4115
4447
|
async function loadLanguage(langId) {
|
|
4116
4448
|
const file = GRAMMAR_FILE[langId];
|
|
@@ -4118,7 +4450,7 @@ async function loadLanguage(langId) {
|
|
|
4118
4450
|
if (langCache.has(langId)) return langCache.get(langId) ?? null;
|
|
4119
4451
|
let lang = null;
|
|
4120
4452
|
try {
|
|
4121
|
-
lang = await LanguageMod.load(
|
|
4453
|
+
lang = await LanguageMod.load(join9(grammarDir(), file));
|
|
4122
4454
|
} catch {
|
|
4123
4455
|
lang = null;
|
|
4124
4456
|
}
|
|
@@ -4387,7 +4719,7 @@ function firstLine(text) {
|
|
|
4387
4719
|
|
|
4388
4720
|
// src/index-store/content-indexer.ts
|
|
4389
4721
|
var MAX_FILES = 5e4;
|
|
4390
|
-
var EXTRACTOR_VERSION =
|
|
4722
|
+
var EXTRACTOR_VERSION = 3;
|
|
4391
4723
|
var INDEXED_PNG_TEXT_KEYS = ["parameters", "prompt", "Description", "Comment", "Title"];
|
|
4392
4724
|
var MAX_INDEXED_PNG_TEXT = 2e3;
|
|
4393
4725
|
function embeddablePngText(text) {
|
|
@@ -4402,7 +4734,7 @@ function embeddablePngText(text) {
|
|
|
4402
4734
|
}
|
|
4403
4735
|
return parts.length > 0 ? ` ${parts.join(" ")}` : "";
|
|
4404
4736
|
}
|
|
4405
|
-
async function indexWorkspaceContent(store, workspaceDir) {
|
|
4737
|
+
async function indexWorkspaceContent(store, workspaceDir, artifactsDir) {
|
|
4406
4738
|
const stats = {
|
|
4407
4739
|
scanned: 0,
|
|
4408
4740
|
changed: 0,
|
|
@@ -4490,6 +4822,39 @@ async function indexWorkspaceContent(store, workspaceDir) {
|
|
|
4490
4822
|
]);
|
|
4491
4823
|
continue;
|
|
4492
4824
|
}
|
|
4825
|
+
if (cls.modality === "audio") {
|
|
4826
|
+
let bytes;
|
|
4827
|
+
try {
|
|
4828
|
+
bytes = await readFile3(file.abs);
|
|
4829
|
+
} catch {
|
|
4830
|
+
stats.skipped++;
|
|
4831
|
+
continue;
|
|
4832
|
+
}
|
|
4833
|
+
const hash2 = sha256(bytes);
|
|
4834
|
+
const record = {
|
|
4835
|
+
path: file.path,
|
|
4836
|
+
hash: hash2,
|
|
4837
|
+
size: file.size,
|
|
4838
|
+
mtimeMs: file.mtimeMs,
|
|
4839
|
+
lang: cls.lang,
|
|
4840
|
+
kind: cls.kind,
|
|
4841
|
+
modality: cls.modality,
|
|
4842
|
+
trivial: cls.trivial,
|
|
4843
|
+
indexedAt,
|
|
4844
|
+
loc: null
|
|
4845
|
+
};
|
|
4846
|
+
if (existing && existing.hash === hash2) {
|
|
4847
|
+
store.upsertFile(record);
|
|
4848
|
+
continue;
|
|
4849
|
+
}
|
|
4850
|
+
stats.changed++;
|
|
4851
|
+
store.upsertFile(record);
|
|
4852
|
+
const nameWords = file.path.replace(/\.[^.]+$/, "").split(/[^a-zA-Z0-9]+/).filter(Boolean).join(" ");
|
|
4853
|
+
store.putChunks(file.path, hash2, [
|
|
4854
|
+
{ kind: "audio", lineStart: 1, lineEnd: 1, text: `${nameWords} audio recording` }
|
|
4855
|
+
]);
|
|
4856
|
+
continue;
|
|
4857
|
+
}
|
|
4493
4858
|
if (cls.modality === "doc") {
|
|
4494
4859
|
let bytes;
|
|
4495
4860
|
try {
|
|
@@ -4511,21 +4876,32 @@ async function indexWorkspaceContent(store, workspaceDir) {
|
|
|
4511
4876
|
indexedAt,
|
|
4512
4877
|
loc: null
|
|
4513
4878
|
};
|
|
4879
|
+
const convertible = isConvertibleDoc(extname3(file.path));
|
|
4514
4880
|
if (existing && existing.hash === hash2) {
|
|
4515
4881
|
store.upsertFile(record);
|
|
4882
|
+
if (!convertible) continue;
|
|
4883
|
+
const paths = shadowDocFilesPaths(artifactsDir, file.path);
|
|
4884
|
+
const sidecarPresent = paths ? await stat3(paths.mdPath).then(
|
|
4885
|
+
(s) => s.isFile(),
|
|
4886
|
+
() => false
|
|
4887
|
+
) : true;
|
|
4888
|
+
if (sidecarPresent) continue;
|
|
4889
|
+
const state = store.getMetadata(file.path)["doc:convert"];
|
|
4890
|
+
if (state && state !== "ok") continue;
|
|
4891
|
+
const conv = await ensureShadowDocSidecar(file.abs, artifactsDir, file.path);
|
|
4892
|
+
store.setMetadata(file.path, [{ key: "doc:convert", value: convState(conv) }]);
|
|
4893
|
+
if (conv?.markdown != null) stats.docsConverted++;
|
|
4516
4894
|
continue;
|
|
4517
4895
|
}
|
|
4518
4896
|
stats.changed++;
|
|
4519
4897
|
store.upsertFile(record);
|
|
4520
|
-
if (
|
|
4521
|
-
const conv = await
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
);
|
|
4525
|
-
if (conv.markdown != null) {
|
|
4898
|
+
if (convertible) {
|
|
4899
|
+
const conv = await ensureShadowDocSidecar(file.abs, artifactsDir, file.path);
|
|
4900
|
+
store.setMetadata(file.path, [{ key: "doc:convert", value: convState(conv) }]);
|
|
4901
|
+
if (conv?.markdown != null) {
|
|
4526
4902
|
store.putChunks(file.path, hash2, chunkMarkdown(conv.markdown));
|
|
4527
4903
|
stats.docsConverted++;
|
|
4528
|
-
} else if (conv
|
|
4904
|
+
} else if (conv?.blocked) {
|
|
4529
4905
|
store.putChunks(file.path, hash2, [
|
|
4530
4906
|
{
|
|
4531
4907
|
kind: "doc",
|
|
@@ -4604,11 +4980,51 @@ async function indexWorkspaceContent(store, workspaceDir) {
|
|
|
4604
4980
|
if (!seen.has(f.path)) {
|
|
4605
4981
|
store.deleteFile(f.path);
|
|
4606
4982
|
stats.removed++;
|
|
4983
|
+
if (isConvertibleDoc(extname3(f.path))) {
|
|
4984
|
+
const paths = shadowDocFilesPaths(artifactsDir, f.path);
|
|
4985
|
+
if (paths) await rm4(paths.dir, { recursive: true, force: true }).catch(() => {
|
|
4986
|
+
});
|
|
4987
|
+
}
|
|
4607
4988
|
}
|
|
4608
4989
|
}
|
|
4990
|
+
await sweepShadowOrphans(artifactsDir, seen);
|
|
4609
4991
|
if (forceCode) store.setMeta("extractor_version", String(EXTRACTOR_VERSION));
|
|
4610
4992
|
return stats;
|
|
4611
4993
|
}
|
|
4994
|
+
var SHADOW_SWEEP_MAX_DIRS = 5e4;
|
|
4995
|
+
async function sweepShadowOrphans(artifactsDir, seen) {
|
|
4996
|
+
const shadowRoot = join10(artifactsDir, PROJECT_SHADOW_DIR_NAME2);
|
|
4997
|
+
const queue = [""];
|
|
4998
|
+
let visited = 0;
|
|
4999
|
+
while (queue.length > 0) {
|
|
5000
|
+
const rel = queue.shift();
|
|
5001
|
+
let entries;
|
|
5002
|
+
try {
|
|
5003
|
+
entries = await readdir2(join10(shadowRoot, rel), { withFileTypes: true });
|
|
5004
|
+
} catch {
|
|
5005
|
+
continue;
|
|
5006
|
+
}
|
|
5007
|
+
for (const e of entries) {
|
|
5008
|
+
if (!e.isDirectory()) continue;
|
|
5009
|
+
if (++visited > SHADOW_SWEEP_MAX_DIRS) return;
|
|
5010
|
+
const childRel = rel ? `${rel}/${e.name}` : e.name;
|
|
5011
|
+
if (e.name.endsWith("_files")) {
|
|
5012
|
+
const sourceRel = childRel.slice(0, -"_files".length);
|
|
5013
|
+
if (!seen.has(sourceRel)) {
|
|
5014
|
+
await rm4(join10(shadowRoot, childRel), { recursive: true, force: true }).catch(() => {
|
|
5015
|
+
});
|
|
5016
|
+
}
|
|
5017
|
+
} else {
|
|
5018
|
+
queue.push(childRel);
|
|
5019
|
+
}
|
|
5020
|
+
}
|
|
5021
|
+
}
|
|
5022
|
+
}
|
|
5023
|
+
function convState(conv) {
|
|
5024
|
+
if (conv?.markdown != null) return "ok";
|
|
5025
|
+
if (conv?.blocked) return "blocked";
|
|
5026
|
+
return "failed";
|
|
5027
|
+
}
|
|
4612
5028
|
|
|
4613
5029
|
// src/index-store/static-index-worker.ts
|
|
4614
5030
|
if (!parentPort) throw new Error("static-index-worker must be run as a worker thread");
|
|
@@ -4624,7 +5040,11 @@ port.on("message", (request) => {
|
|
|
4624
5040
|
});
|
|
4625
5041
|
if (!index) throw new Error(`content index unavailable at ${request.dbPath}`);
|
|
4626
5042
|
try {
|
|
4627
|
-
const stats = await indexWorkspaceContent(
|
|
5043
|
+
const stats = await indexWorkspaceContent(
|
|
5044
|
+
index,
|
|
5045
|
+
request.workspaceDir,
|
|
5046
|
+
request.artifactsDir
|
|
5047
|
+
);
|
|
4628
5048
|
port.postMessage({ id: request.id, stats });
|
|
4629
5049
|
} finally {
|
|
4630
5050
|
index.close();
|