gitnexus 1.6.10-rc.2 → 1.6.10-rc.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +56 -12
- package/dist/cli/analyze.js +166 -5
- package/dist/cli/clean.js +17 -3
- package/dist/cli/cli-message.d.ts +1 -1
- package/dist/cli/doctor.d.ts +18 -0
- package/dist/cli/doctor.js +76 -3
- package/dist/cli/embeddings.d.ts +12 -0
- package/dist/cli/embeddings.js +49 -0
- package/dist/cli/help-i18n.js +4 -0
- package/dist/cli/i18n/en.d.ts +10 -5
- package/dist/cli/i18n/en.js +10 -5
- package/dist/cli/i18n/resources.d.ts +15 -5
- package/dist/cli/i18n/zh-CN.d.ts +5 -0
- package/dist/cli/i18n/zh-CN.js +10 -5
- package/dist/cli/index.js +12 -1
- package/dist/core/augmentation/engine.js +4 -3
- package/dist/core/embeddings/embedder.js +16 -2
- package/dist/core/embeddings/embedding-pipeline.d.ts +20 -0
- package/dist/core/embeddings/embedding-pipeline.js +10 -2
- package/dist/core/embeddings/http-client.d.ts +28 -1
- package/dist/core/embeddings/http-client.js +107 -15
- package/dist/core/embeddings/node-module-compat.d.ts +23 -0
- package/dist/core/embeddings/node-module-compat.js +23 -0
- package/dist/core/embeddings/onnxruntime-common-resolver.js +6 -3
- package/dist/core/embeddings/onnxruntime-node-resolver.js +31 -5
- package/dist/core/embeddings/runtime-install.d.ts +119 -0
- package/dist/core/embeddings/runtime-install.js +372 -0
- package/dist/core/embeddings/runtime-support.d.ts +48 -17
- package/dist/core/embeddings/runtime-support.js +96 -0
- package/dist/core/group/extractors/http-patterns/python.js +246 -108
- package/dist/core/group/extractors/manifest-extractor.js +4 -0
- package/dist/core/incremental/escalation-gate.d.ts +38 -0
- package/dist/core/incremental/escalation-gate.js +48 -0
- package/dist/core/incremental/shadow-candidates.d.ts +3 -3
- package/dist/core/incremental/shadow-candidates.js +3 -3
- package/dist/core/incremental/subgraph-extract.d.ts +2 -2
- package/dist/core/incremental/subgraph-extract.js +2 -2
- package/dist/core/ingestion/cobol/jcl-processor.js +9 -8
- package/dist/core/ingestion/cobol-processor.js +27 -26
- package/dist/core/ingestion/community-processor.d.ts +51 -1
- package/dist/core/ingestion/community-processor.js +316 -63
- package/dist/core/ingestion/emit-references.js +3 -2
- package/dist/core/ingestion/languages/cpp/captures.js +157 -74
- package/dist/core/ingestion/markdown-processor.js +3 -2
- package/dist/core/ingestion/parsing-processor.d.ts +3 -1
- package/dist/core/ingestion/parsing-processor.js +4 -0
- package/dist/core/ingestion/pipeline-phases/parse-impl.js +42 -0
- package/dist/core/ingestion/route-extractors/constant-resolver.d.ts +83 -0
- package/dist/core/ingestion/route-extractors/constant-resolver.js +131 -0
- package/dist/core/ingestion/route-extractors/python-const-resolver.d.ts +75 -0
- package/dist/core/ingestion/route-extractors/python-const-resolver.js +299 -0
- package/dist/core/ingestion/tree-sitter-queries.d.ts +1 -1
- package/dist/core/ingestion/tree-sitter-queries.js +12 -1
- package/dist/core/ingestion/utils/line-base.d.ts +20 -0
- package/dist/core/ingestion/utils/line-base.js +20 -0
- package/dist/core/ingestion/utils/symbol-labels.d.ts +21 -0
- package/dist/core/ingestion/utils/symbol-labels.js +45 -0
- package/dist/core/ingestion/workers/parse-worker.d.ts +34 -0
- package/dist/core/ingestion/workers/parse-worker.js +40 -5
- package/dist/core/ingestion/workers/result-merge.js +4 -0
- package/dist/core/ingestion/workers/worker-pool.d.ts +14 -0
- package/dist/core/ingestion/workers/worker-pool.js +85 -5
- package/dist/core/lbug/csv-generator.js +8 -2
- package/dist/core/lbug/cypher-escape.d.ts +20 -0
- package/dist/core/lbug/cypher-escape.js +20 -0
- package/dist/core/lbug/extension-load-error.d.ts +67 -0
- package/dist/core/lbug/extension-load-error.js +320 -0
- package/dist/core/lbug/extension-loader.d.ts +17 -2
- package/dist/core/lbug/extension-loader.js +38 -13
- package/dist/core/lbug/lbug-adapter.d.ts +147 -1
- package/dist/core/lbug/lbug-adapter.js +390 -70
- package/dist/core/lbug/lbug-config.d.ts +28 -0
- package/dist/core/lbug/lbug-config.js +114 -4
- package/dist/core/lbug/native-check.d.ts +22 -0
- package/dist/core/lbug/native-check.js +66 -0
- package/dist/core/lbug/pool-adapter.js +10 -1
- package/dist/core/lbug/sidecar-recovery.d.ts +172 -0
- package/dist/core/lbug/sidecar-recovery.js +418 -16
- package/dist/core/platform/capabilities.js +27 -1
- package/dist/core/run-analyze.js +470 -108
- package/dist/core/search/fts-indexes.d.ts +7 -0
- package/dist/core/search/fts-indexes.js +37 -0
- package/dist/core/tree-sitter/safe-parse.d.ts +8 -3
- package/dist/core/tree-sitter/safe-parse.js +21 -7
- package/dist/core/wiki/graph-queries.js +6 -5
- package/dist/mcp/core/embedder.js +16 -2
- package/dist/mcp/local/line-display.d.ts +22 -0
- package/dist/mcp/local/line-display.js +3 -0
- package/dist/mcp/local/local-backend.d.ts +14 -0
- package/dist/mcp/local/local-backend.js +63 -19
- package/dist/mcp/local/pdg-impact.d.ts +5 -3
- package/dist/mcp/local/pdg-impact.js +5 -2
- package/dist/mcp/resources.js +1 -0
- package/dist/mcp/tools.js +1 -1
- package/dist/server/analyze-launch.d.ts +6 -0
- package/dist/server/analyze-launch.js +82 -6
- package/dist/server/api.d.ts +30 -0
- package/dist/server/api.js +63 -21
- package/dist/storage/parse-cache.js +1 -1
- package/dist/storage/repo-manager.d.ts +65 -1
- package/dist/storage/repo-manager.js +18 -1
- package/hooks/antigravity/gitnexus-antigravity-hook.cjs +50 -3
- package/hooks/claude/gitnexus-hook.cjs +59 -9
- package/package.json +5 -3
- package/scripts/cross-platform-tests.ts +43 -0
- package/scripts/ensure-fts.ts +32 -0
- package/scripts/install-duckdb-extension.mjs +85 -24
- package/scripts/run-cross-platform.ts +46 -6
- package/scripts/shard-arg.ts +30 -0
- package/web/assets/ProcessFlowModal-Dna7GkwO.js +22 -0
- package/web/assets/{agent-Dli_x7bU.js → agent-DNYDy-KA.js} +49 -49
- package/web/assets/architecture-7EHR7CIX-6QZW5X65-BVWFvYz2.js +1 -0
- package/web/assets/architectureDiagram-UL44E2DR-BU5ZRITg.js +36 -0
- package/web/assets/{blockDiagram-7IZFK4PR-BkWhpzWB.js → blockDiagram-7IZFK4PR-Dut1BpTV.js} +2 -2
- package/web/assets/{c4Diagram-Y2BXMSZH-DtBvpzWD.js → c4Diagram-Y2BXMSZH-DOXVs7-C.js} +2 -2
- package/web/assets/chunk-2T2R6R2M-pkw2oXHH.js +4 -0
- package/web/assets/chunk-2UTLFMKG-CXlhF3Pt.js +1 -0
- package/web/assets/chunk-3SSMPTDK-rPsRM1x0.js +321 -0
- package/web/assets/chunk-4R4BOZG6-Be_F0JoX.js +159 -0
- package/web/assets/{chunk-6764PJDD-DpIS7hxD.js → chunk-6764PJDD-C7xxk063.js} +1 -1
- package/web/assets/{chunk-67TQ5CYL-4sNPJU1q.js → chunk-67TQ5CYL-zxhDWgyF.js} +3 -3
- package/web/assets/chunk-7FYTHRHK-CsmYIswB.js +32 -0
- package/web/assets/chunk-7J6CGLKN-AmkN8jJN.js +10 -0
- package/web/assets/chunk-7W6UQGC5-pyKGssV3.js +1 -0
- package/web/assets/chunk-C62D2QBJ-3gdL8EKc.js +1 -0
- package/web/assets/chunk-CEXFNPSA-CjwtMDkc.js +1 -0
- package/web/assets/{chunk-INKRHTLW-Nu4ri9P-.js → chunk-INKRHTLW-D9BM9HWZ.js} +1 -1
- package/web/assets/chunk-J5EP6P6S-L19oz55R.js +1 -0
- package/web/assets/{chunk-KGFNY3KK-JGMJvKCK.js → chunk-KGFNY3KK-tD2F6wxK.js} +1 -1
- package/web/assets/chunk-KGYTTC2M-zm6n242I.js +1 -0
- package/web/assets/{chunk-KRXBNO2N-BW5o0KUN.js → chunk-KRXBNO2N-C-UNuKKf.js} +1 -1
- package/web/assets/{chunk-LCXTWHL2-CfLLCywY.js → chunk-LCXTWHL2-Ba9JK-6F.js} +2 -2
- package/web/assets/{chunk-LII3EMHJ-D-jm-dLa.js → chunk-LII3EMHJ-EGx-eBIu.js} +1 -1
- package/web/assets/chunk-LRIF4GLE-C3_PqUBq.js +1 -0
- package/web/assets/{chunk-QA3QBVWF-BgRW3SXd.js → chunk-QA3QBVWF-D4cSJBf6.js} +1 -1
- package/web/assets/chunk-RERM46MO-B5dAdWpP.js +1 -0
- package/web/assets/{chunk-RG4AUYOV-CqWzWsV3.js → chunk-RG4AUYOV-OqBBMwuQ.js} +4 -4
- package/web/assets/chunk-RKZBBQEN-MyIQbGgD.js +1 -0
- package/web/assets/chunk-RLI5ZMPA-CkXZ0y0U.js +1 -0
- package/web/assets/{chunk-T2UQINTJ-DSBqkzH7.js → chunk-T2UQINTJ-BJusEiGe.js} +1 -1
- package/web/assets/chunk-UP6H54XL-BG-n7qDp.js +1 -0
- package/web/assets/chunk-UXSXWOXI-CW1_r5cS.js +1 -0
- package/web/assets/{chunk-UY5QBCOK-DyF0vyGd.js → chunk-UY5QBCOK-BCpUlARQ.js} +1 -1
- package/web/assets/chunk-VU6ZFW4Y-BeXVk45_.js +2 -0
- package/web/assets/{chunk-W44A43WB-JqMLIpR0.js → chunk-W44A43WB-BN8EQc_u.js} +1 -1
- package/web/assets/{chunk-ZXARS5L4-CxN8oiwI.js → chunk-ZXARS5L4-BI_qR2Nm.js} +1 -1
- package/web/assets/classDiagram-KGZ6W3CR-D9zSIg0x.js +1 -0
- package/web/assets/classDiagram-v2-72OJOZXJ-D9zSIg0x.js +1 -0
- package/web/assets/cose-bilkent-UX7MHV2Q-BmnZ7Mvj.js +1 -0
- package/web/assets/dagre-ND4H6XIP-CpcV5kbl.js +4 -0
- package/web/assets/diagram-3NCE3AQN-6SMxBRpj.js +43 -0
- package/web/assets/diagram-GF46GFSD-BZEdNcRZ.js +24 -0
- package/web/assets/{diagram-HNR7UZ2L-DFCfd5LI.js → diagram-HNR7UZ2L-DxKW2a2Y.js} +2 -2
- package/web/assets/diagram-QXG6HAR7-HfTHcXX_.js +24 -0
- package/web/assets/diagram-WEQXMOUZ-CyijUS8r.js +10 -0
- package/web/assets/{erDiagram-L5TCEMPS-8X1y6R4Y.js → erDiagram-L5TCEMPS-D0fvpWEX.js} +3 -3
- package/web/assets/eventmodeling-FCH6USID-MREXMVOE-CgFBM7e-.js +1 -0
- package/web/assets/flowDiagram-H6V6AXG4-Bvr9bbhk.js +162 -0
- package/web/assets/{ganttDiagram-JCBTUEKG-D3iL2Aet.js → ganttDiagram-JCBTUEKG-TzODrLzv.js} +2 -2
- package/web/assets/gitGraph-WXDBUCRP-R675I2BI-DCJv7SII.js +1 -0
- package/web/assets/gitGraphDiagram-S2ZK5IYY-ONQSwJIY.js +106 -0
- package/web/assets/index-B4eB4dNZ.js +575 -0
- package/web/assets/index-CX_fADmQ.css +2 -0
- package/web/assets/info-J43DQDTF-KCYPFFUO-BfNy-9Nd.js +1 -0
- package/web/assets/infoDiagram-3YFTVSEB-DIPVUIQ6.js +2 -0
- package/web/assets/{ishikawaDiagram-BNXS4ZKH-DfYpxnE5.js → ishikawaDiagram-BNXS4ZKH-BLjivjXL.js} +3 -3
- package/web/assets/{journeyDiagram-M6C3CM3L-CvArszLo.js → journeyDiagram-M6C3CM3L-Cetgs3NH.js} +3 -3
- package/web/assets/{kanban-definition-75IXJCU3-CpfVLLf9.js → kanban-definition-75IXJCU3-DMlCaIpf.js} +5 -5
- package/web/assets/{katex-K3KEBU37-CTc5BslQ.js → katex-K3KEBU37-qRQBy-EZ.js} +2 -2
- package/web/assets/{mindmap-definition-2TDM6QVE-DypvbOMQ.js → mindmap-definition-2TDM6QVE-B4GbXQBc.js} +3 -3
- package/web/assets/packet-YPE3B663-LP52Z2RK-D4qkeG9k.js +1 -0
- package/web/assets/pie-LRSECV5Y-TCRJHUBD-B_yMKyQa.js +1 -0
- package/web/assets/{pieDiagram-CU6KROY3-s27EFWgP.js → pieDiagram-CU6KROY3-D3YC_xyg.js} +2 -2
- package/web/assets/{quadrantDiagram-VICAPDV7-yXSZ2lLz.js → quadrantDiagram-VICAPDV7-B1TQnlm0.js} +2 -2
- package/web/assets/radar-GUYGQ44K-RDLRG3WG-DeD6yN_0.js +1 -0
- package/web/assets/{requirementDiagram-JXO7QTGE-CWhqInuf.js → requirementDiagram-JXO7QTGE-f8wczLQf.js} +3 -3
- package/web/assets/rolldown-runtime-QTnfLwEv.js +1 -0
- package/web/assets/{sankeyDiagram-URQDO5SZ-1qEjlFIB.js → sankeyDiagram-URQDO5SZ-BTxzG5j0.js} +2 -2
- package/web/assets/{sequenceDiagram-VS2MUI6T-RyTlZ5cR.js → sequenceDiagram-VS2MUI6T-DuamM-IR.js} +5 -5
- package/web/assets/src-BNCXICdA.js +1 -0
- package/web/assets/stateDiagram-7D4R322I-Ch2pEUcw.js +1 -0
- package/web/assets/stateDiagram-v2-36443NZ5-7ANuoZLL.js +1 -0
- package/web/assets/{timeline-definition-O6YCAMPW-DrrqLYAf.js → timeline-definition-O6YCAMPW-07BR8R8G.js} +3 -3
- package/web/assets/treeView-BLDUP644-QA4HXRO3-CDuSYnd4.js +1 -0
- package/web/assets/treemap-LRROVOQU-LLAWBHMP-imazucyJ.js +1 -0
- package/web/assets/{vennDiagram-MWXL3ELB-B0RkWAim.js → vennDiagram-MWXL3ELB-BQX2ossk.js} +4 -4
- package/web/assets/wardley-L42UT6IY-5TKZOOLJ-DqrT9tB8.js +1 -0
- package/web/assets/{wardleyDiagram-CUQ6CDDI-boUxiQ5F.js → wardleyDiagram-CUQ6CDDI-CGIJBBiK.js} +3 -3
- package/web/assets/{xychartDiagram-N2JHSOCM-BOLm0eNm.js → xychartDiagram-N2JHSOCM-B3HNMuWp.js} +4 -4
- package/web/index.html +17 -10
- package/web/assets/architecture-7EHR7CIX-6QZW5X65-DxJw65fT.js +0 -1
- package/web/assets/architectureDiagram-UL44E2DR-dEkqUZN9.js +0 -36
- package/web/assets/chunk-3SSMPTDK-B4_etUhr.js +0 -321
- package/web/assets/chunk-7W6UQGC5-KyEG0HQg.js +0 -1
- package/web/assets/chunk-KGYTTC2M-CWC_c3H9.js +0 -161
- package/web/assets/classDiagram-KGZ6W3CR-CMne4tG9.js +0 -1
- package/web/assets/classDiagram-v2-72OJOZXJ-CMne4tG9.js +0 -1
- package/web/assets/cose-bilkent-UX7MHV2Q-D71wNYRJ.js +0 -1
- package/web/assets/dagre-ND4H6XIP-Co5rRx9X.js +0 -4
- package/web/assets/diagram-3NCE3AQN-DBp4O00j.js +0 -43
- package/web/assets/diagram-GF46GFSD-6N_8yS0i.js +0 -24
- package/web/assets/diagram-QXG6HAR7-Di093rFg.js +0 -24
- package/web/assets/diagram-WEQXMOUZ-DZWi4SUN.js +0 -10
- package/web/assets/eventmodeling-FCH6USID-MREXMVOE-BQm9QzEa.js +0 -1
- package/web/assets/flowDiagram-H6V6AXG4-CMpY8Ufn.js +0 -162
- package/web/assets/gitGraph-WXDBUCRP-R675I2BI-Cr_Bm2Nb.js +0 -1
- package/web/assets/gitGraphDiagram-S2ZK5IYY-CPIDgxGm.js +0 -106
- package/web/assets/index-KctnI3Vq.js +0 -635
- package/web/assets/index-_lgn7hs5.css +0 -2
- package/web/assets/info-J43DQDTF-KCYPFFUO-Cs4p2oyk.js +0 -1
- package/web/assets/infoDiagram-3YFTVSEB-BaLMZkUg.js +0 -2
- package/web/assets/packet-YPE3B663-LP52Z2RK-CKCrztD2.js +0 -1
- package/web/assets/pie-LRSECV5Y-TCRJHUBD-BB7pHqoD.js +0 -1
- package/web/assets/radar-GUYGQ44K-RDLRG3WG-DhoTOcuK.js +0 -1
- package/web/assets/stateDiagram-7D4R322I-DTjVfX4A.js +0 -1
- package/web/assets/stateDiagram-v2-36443NZ5-xY9_k9_Q.js +0 -1
- package/web/assets/treeView-BLDUP644-QA4HXRO3-ODehyGKL.js +0 -1
- package/web/assets/treemap-LRROVOQU-LLAWBHMP-BfpgC-7o.js +0 -1
- package/web/assets/wardley-L42UT6IY-5TKZOOLJ-CNeL6VHE.js +0 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from 'fs/promises';
|
|
2
2
|
import path from 'path';
|
|
3
|
+
import { HANDLE_RELEASE_PROBE_ATTEMPTS, HANDLE_RELEASE_PROBE_DELAY_MS, sleep, } from './lbug-config.js';
|
|
3
4
|
export const TINY_ORPHAN_WAL_BYTES = 4 * 1024;
|
|
4
5
|
/**
|
|
5
6
|
* Counter-based warn anti-spam (PR #1747 review, Finding 6).
|
|
@@ -87,16 +88,47 @@ const warnOnce = (logger, key, message) => {
|
|
|
87
88
|
logger.warn(`${message} (${ordinal(next)} occurrence of this condition)`);
|
|
88
89
|
};
|
|
89
90
|
// LADYBUGDB-CONTRACT: matches @ladybugdb/core ^0.18.0 native error text.
|
|
90
|
-
// When bumping LadybugDB, re-validate this
|
|
91
|
+
// When bumping LadybugDB, re-validate this against the new error format
|
|
91
92
|
// — `git grep "LADYBUGDB-CONTRACT"` enumerates every version-coupled spot.
|
|
92
|
-
//
|
|
93
|
-
//
|
|
94
|
-
//
|
|
95
|
-
//
|
|
96
|
-
//
|
|
93
|
+
//
|
|
94
|
+
// Two native formats reach here for a genuinely-missing shadow sidecar:
|
|
95
|
+
// POSIX: `Cannot open file <path>.shadow: No such file or directory`
|
|
96
|
+
// Windows: `Cannot open file. path: <path>.shadow - Error 2: <system text>`
|
|
97
|
+
// Windows OS text is localized on non-English installs (issue #2382 was filed
|
|
98
|
+
// from a non-English Windows), so we key on the locale-invariant Win32 code
|
|
99
|
+
// (2 = ERROR_FILE_NOT_FOUND), NOT the English phrase. The code is matched only
|
|
100
|
+
// in the reason AFTER the LAST `.shadow` token (the real failing sidecar; the
|
|
101
|
+
// reason text never contains `.shadow`), so a repo *path* containing e.g.
|
|
102
|
+
// `\error 2\` — even under a `.shadow`-suffixed parent directory — cannot trip
|
|
103
|
+
// it. Deliberate exclusions:
|
|
104
|
+
// - `Error 3` (ERROR_PATH_NOT_FOUND): the #1811 non-ASCII path-garble
|
|
105
|
+
// artifact (see lbug-config.ts) where the shadow is PRESENT on disk;
|
|
106
|
+
// treating it as missing would quarantine a live WAL — data loss.
|
|
107
|
+
// - `Error 5` / `Error 32` / POSIX `Permission denied`: present-but-locked;
|
|
108
|
+
// handled as permission/lock classes, must not quarantine.
|
|
109
|
+
// The quarantine path adds a present-shadow disk check as a belt (see
|
|
110
|
+
// refuseLargeWalQuarantine in lbug-adapter.ts).
|
|
111
|
+
//
|
|
112
|
+
// The Windows branch is derived from the issue #2382 reported string, not a
|
|
113
|
+
// self-produced live crash; unit/consumer tests inject that same string, so
|
|
114
|
+
// GREEN TESTS DO NOT PROVE the byte-exact 0.18.0 Windows format — confirm
|
|
115
|
+
// against a real Windows run before closing #2382.
|
|
97
116
|
export const isMissingShadowSidecarError = (err) => {
|
|
98
117
|
const msg = err instanceof Error ? err.message : String(err);
|
|
99
|
-
|
|
118
|
+
if (!/cannot open file/i.test(msg))
|
|
119
|
+
return false;
|
|
120
|
+
// Anchor on the LAST `.shadow`, not the first: LadybugDB names the failing
|
|
121
|
+
// sidecar as the final `.shadow` token and its reason text (POSIX
|
|
122
|
+
// `: No such file or directory` / Windows ` - Error N: ...`) never contains
|
|
123
|
+
// `.shadow`. Slicing from the last match isolates the true reason, so an
|
|
124
|
+
// earlier `.shadow`-suffixed path segment (e.g. a `branch=subdir` directory
|
|
125
|
+
// like `snap.shadow\`) can't shift the anchor and let a path-embedded
|
|
126
|
+
// `error 2` be read as the Win32 code (issue #2382 review, Finding A).
|
|
127
|
+
const lastShadow = [...msg.matchAll(/\.shadow\b/gi)].at(-1);
|
|
128
|
+
if (lastShadow?.index === undefined)
|
|
129
|
+
return false;
|
|
130
|
+
const reason = msg.slice(lastShadow.index);
|
|
131
|
+
return /no such file or directory/i.test(reason) || /\berror\s+2\b/i.test(reason);
|
|
100
132
|
};
|
|
101
133
|
// LADYBUGDB-CONTRACT: matches @ladybugdb/core ^0.18.0 native error text.
|
|
102
134
|
// When bumping LadybugDB, re-validate this regex against the new error format
|
|
@@ -114,11 +146,44 @@ export const shadowSidecarRecoveryMessage = (dbPath, err) => {
|
|
|
114
146
|
'Rebuild the index with `gitnexus analyze --force <repo-path> --index-only` and restart `gitnexus serve`.' +
|
|
115
147
|
`\n Original error: ${msg.slice(0, 200)}`);
|
|
116
148
|
};
|
|
149
|
+
/**
|
|
150
|
+
* Actionable message for the case where LadybugDB reports a "missing shadow"
|
|
151
|
+
* but `inspectLbugSidecars` finds the `.shadow` PRESENT on disk — the open
|
|
152
|
+
* failed on path reachability or a lock, not a genuinely-missing sidecar (issue
|
|
153
|
+
* #2382 review, S2). Unlike `shadowSidecarRecoveryMessage` it does NOT tell the
|
|
154
|
+
* operator to rebuild the index (the remedy is fixing the lock/path). Keeps the
|
|
155
|
+
* `Original error:` tail so downstream `isMissingShadowSidecarError` recognition
|
|
156
|
+
* still matches the wrapped error.
|
|
157
|
+
*/
|
|
158
|
+
export const presentShadowUnreachableMessage = (dbPath, err) => {
|
|
159
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
160
|
+
return (`LadybugDB checkpoint sidecar is present but unreachable for ${dbPath}. ` +
|
|
161
|
+
'The .shadow file is on disk, so the open likely failed on path reachability or a file lock ' +
|
|
162
|
+
'(antivirus, another process holding a handle, or a non-ASCII path) rather than a missing sidecar. ' +
|
|
163
|
+
'Check filesystem access and locks; only run `gitnexus analyze --force <repo-path> --index-only` ' +
|
|
164
|
+
'if the index is genuinely broken.' +
|
|
165
|
+
`\n Original error: ${msg.slice(0, 200)}`);
|
|
166
|
+
};
|
|
117
167
|
const PERMISSION_RENAME_CODES = new Set(['EACCES', 'EPERM', 'EBUSY']);
|
|
118
168
|
export const isPermissionRenameError = (err) => {
|
|
119
169
|
const code = err?.code;
|
|
120
170
|
return typeof code === 'string' && PERMISSION_RENAME_CODES.has(code);
|
|
121
171
|
};
|
|
172
|
+
/**
|
|
173
|
+
* Canonical remediation guidance for the LadybugDB file-lock class
|
|
174
|
+
* (EBUSY/EPERM/EACCES — an MCP/serve process holding the index, or an
|
|
175
|
+
* antivirus scan). One exported producer (this shipping review, FIX 7):
|
|
176
|
+
* the dirty-recovery park warning below, `LbugWipeError`'s message builder
|
|
177
|
+
* (lbug-adapter.ts) and {@link renameFailureMessage} previously carried
|
|
178
|
+
* three divergent hand-written copies of the same advice.
|
|
179
|
+
*
|
|
180
|
+
* `rerun` names the command to retry once the lock clears — the analyze
|
|
181
|
+
* wipe/park surfaces re-run the analyze; the read-path quarantine surface
|
|
182
|
+
* re-runs whatever command failed. No trailing period: callers own the
|
|
183
|
+
* sentence end.
|
|
184
|
+
*/
|
|
185
|
+
export const lbugLockRemediation = (rerun = 're-run `gitnexus analyze`') => 'stop any GitNexus MCP or serve process using this repository, add an antivirus ' +
|
|
186
|
+
`exclusion for the GitNexus storage directory, then ${rerun}`;
|
|
122
187
|
/**
|
|
123
188
|
* Classify a failure surfaced by quarantine rename into an actionable user-facing
|
|
124
189
|
* message.
|
|
@@ -140,10 +205,10 @@ export const renameFailureMessage = (dbPath, err) => {
|
|
|
140
205
|
const msg = err instanceof Error ? err.message : String(err);
|
|
141
206
|
return (`GitNexus could not move the LadybugDB WAL sidecar at ${dbPath}.wal because of a ` +
|
|
142
207
|
`filesystem permission or file-lock error (${code}). ` +
|
|
143
|
-
'
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
'
|
|
208
|
+
'The index does not need to be rebuilt — ' +
|
|
209
|
+
// Shared remediation copy (FIX 7); this surface serves read paths too,
|
|
210
|
+
// so the re-run target is the failing command, not the analyze.
|
|
211
|
+
`${lbugLockRemediation('re-run the failing command once the lock or permission is resolved')}.` +
|
|
147
212
|
`\n Original error: ${msg.slice(0, 200)}`);
|
|
148
213
|
}
|
|
149
214
|
return shadowSidecarRecoveryMessage(dbPath, err);
|
|
@@ -165,6 +230,41 @@ export async function inspectLbugSidecars(dbPath) {
|
|
|
165
230
|
}
|
|
166
231
|
return { kind: 'clean', dbPath };
|
|
167
232
|
}
|
|
233
|
+
/**
|
|
234
|
+
* Reject the WAL-quarantine path when discarding the WAL would be unsafe or
|
|
235
|
+
* wrong. Shared by every reactive missing-shadow recovery consumer — serve (via
|
|
236
|
+
* lbug-adapter's `refuseLargeWalQuarantine`) and the MCP/wiki/augmentation pool
|
|
237
|
+
* (via pool-adapter's `tryQuarantineForMissingShadow`) — so the quarantine
|
|
238
|
+
* safety policy has a single source of truth (issue #2382 review, Finding B).
|
|
239
|
+
*
|
|
240
|
+
* 1. `wal-with-shadow` — the `.shadow` sidecar is PRESENT on disk. A
|
|
241
|
+
* "missing shadow" error alongside a present shadow means the open failed
|
|
242
|
+
* on path reachability or a lock (the #1811 non-ASCII path-garble on
|
|
243
|
+
* Windows), not a genuinely-missing shadow; quarantining would move a live
|
|
244
|
+
* WAL sitting next to its shadow — data loss.
|
|
245
|
+
* 2. `orphan-wal` — the orphan WAL is too large to safely discard
|
|
246
|
+
* (>TINY_ORPHAN_WAL_BYTES); preserve the uncheckpointed pages for explicit
|
|
247
|
+
* operator recovery.
|
|
248
|
+
*
|
|
249
|
+
* Throws `shadowSidecarRecoveryMessage` in either case. Returns silently only
|
|
250
|
+
* when the shadow is absent AND the WAL is absent or tiny — the states where
|
|
251
|
+
* the existing recovery path is safe to proceed. `mode` is a label used only in
|
|
252
|
+
* the warning text (e.g. 'read-only', 'writable', 'pool read-only recovery').
|
|
253
|
+
*/
|
|
254
|
+
export const guardWalQuarantine = async (dbPath, mode, triggeringErr, logger) => {
|
|
255
|
+
const state = await inspectLbugSidecars(dbPath);
|
|
256
|
+
if (state.kind === 'wal-with-shadow') {
|
|
257
|
+
warnOnce(logger, `${dbPath}:present-shadow-refuse:${mode}`, `GitNexus: refusing to quarantine WAL at ${dbPath}.wal during ${mode} recovery — ` +
|
|
258
|
+
'the .shadow sidecar is present on disk, so the open likely failed on path reachability or a lock ' +
|
|
259
|
+
'rather than a missing shadow. Run `gitnexus analyze --force <repo-path> --index-only` if the index is genuinely broken.');
|
|
260
|
+
throw new Error(presentShadowUnreachableMessage(dbPath, triggeringErr));
|
|
261
|
+
}
|
|
262
|
+
if (state.kind === 'orphan-wal') {
|
|
263
|
+
warnOnce(logger, `${dbPath}:large-wal-refuse:${mode}`, `GitNexus: refusing to quarantine large WAL (${state.walBytes} bytes) at ${dbPath}.wal during ${mode} recovery; ` +
|
|
264
|
+
'manual recovery required — run `gitnexus analyze --force <repo-path> --index-only`.');
|
|
265
|
+
throw new Error(shadowSidecarRecoveryMessage(dbPath, triggeringErr));
|
|
266
|
+
}
|
|
267
|
+
};
|
|
168
268
|
export async function quarantineWalForMissingShadow(dbPath, options) {
|
|
169
269
|
const walPath = `${dbPath}.wal`;
|
|
170
270
|
const quarantinePath = `${walPath}.missing-shadow.${Date.now()}-${Math.random()
|
|
@@ -254,6 +354,227 @@ export async function finalizeLbugSidecarsAfterClose(dbPath, options) {
|
|
|
254
354
|
'keeping it for recovery. If this repeats, run `gitnexus analyze --force --index-only` or the sidecar repair command.');
|
|
255
355
|
}
|
|
256
356
|
}
|
|
357
|
+
/**
|
|
358
|
+
* Corrected parking-failure warning (tri-review 4669518496 P2-3). The old
|
|
359
|
+
* text promised "the rebuild will wipe it in place instead" — false: the
|
|
360
|
+
* recovery run's pre-wipe DB open would replay the poisoned WAL and die
|
|
361
|
+
* before any wipe could happen. Mirrors {@link renameFailureMessage}'s
|
|
362
|
+
* EBUSY/EPERM framing via the shared {@link lbugLockRemediation} copy
|
|
363
|
+
* (FIX 7): the problem is environmental (file lock, AV), not data
|
|
364
|
+
* integrity — fix the lock and re-run.
|
|
365
|
+
*/
|
|
366
|
+
const sidecarParkRefusedWarning = (from, err) => `Warning: could not park or remove ${path.basename(from)} before the recovery rebuild ` +
|
|
367
|
+
`(${err instanceof Error ? err.message : String(err)}). Another process likely holds an ` +
|
|
368
|
+
`open handle on it — ${lbugLockRemediation()}.`;
|
|
369
|
+
/**
|
|
370
|
+
* The sidecar family parked by {@link quarantineSidecarsForDirtyRecovery}
|
|
371
|
+
* and enumerated by {@link listParkedDirtyRecoverySidecars} — one shared
|
|
372
|
+
* roster so the park and clean surfaces cannot drift apart (tri-review
|
|
373
|
+
* 4669518496 P2-7).
|
|
374
|
+
*/
|
|
375
|
+
const DIRTY_RECOVERY_SIDECAR_SUFFIXES = ['.wal', '.shadow'];
|
|
376
|
+
/**
|
|
377
|
+
* Every fixed name the dirty-recovery park can leave beside `dbPath`: the
|
|
378
|
+
* two `.dirty-recovery` destinations PLUS their `.next` probe residues
|
|
379
|
+
* (this shipping review, FIX 5 — the residue used to be invisible to every
|
|
380
|
+
* cleanup surface while the docs said "remove manually"). Single roster
|
|
381
|
+
* authority for {@link listParkedDirtyRecoverySidecars}.
|
|
382
|
+
*/
|
|
383
|
+
const dirtyRecoveryParkedNames = (dbPath) => DIRTY_RECOVERY_SIDECAR_SUFFIXES.flatMap((suffix) => [
|
|
384
|
+
`${dbPath}${suffix}.dirty-recovery`,
|
|
385
|
+
`${dbPath}${suffix}.dirty-recovery.next`,
|
|
386
|
+
]);
|
|
387
|
+
/**
|
|
388
|
+
* Move the WAL/shadow sidecars aside before a dirty-flag recovery rebuild
|
|
389
|
+
* (#2409 defect 2).
|
|
390
|
+
*
|
|
391
|
+
* When `incrementalInProgress` forces a full rebuild, the previous run
|
|
392
|
+
* died mid-writeback — its WAL can be poisoned in a way that natively
|
|
393
|
+
* kills the process on replay. The recovery run used to open the DB
|
|
394
|
+
* BEFORE the rebuild wipe (the embedding-cache preservation open), replay
|
|
395
|
+
* the poisoned WAL, and die on the spot — so recovery never happened and
|
|
396
|
+
* only a manual rename-aside of the index dir escaped the loop. The
|
|
397
|
+
* rebuild discards every pending WAL byte anyway (the DB files are wiped),
|
|
398
|
+
* so parking the sidecars first costs nothing and makes every subsequent
|
|
399
|
+
* open replay-free.
|
|
400
|
+
*
|
|
401
|
+
* Renamed when possible, so the bytes stay available for post-mortem
|
|
402
|
+
* debugging — and, like {@link quarantineWalForMissingShadow}'s quarantine
|
|
403
|
+
* files, the parked copies are surfaced and removable by
|
|
404
|
+
* `gitnexus clean --lbug-sidecars` (tri-review 4669518496 P2-7; before
|
|
405
|
+
* that, this comment claimed a "same philosophy" parity while the
|
|
406
|
+
* dirty-recovery files were invisible to every cleanup surface). Real
|
|
407
|
+
* lifecycle: the destinations are FIXED names — no timestamp, see
|
|
408
|
+
* {@link listParkedDirtyRecoverySidecars} — so each new crash overwrites
|
|
409
|
+
* the previous parked copy, capping accumulation at one file per sidecar;
|
|
410
|
+
* remove them via `clean --lbug-sidecars` or manually once their
|
|
411
|
+
* post-mortem value has passed.
|
|
412
|
+
*
|
|
413
|
+
* Escalation ladder per suffix (this shipping review, FIX 1 — replacing
|
|
414
|
+
* the drop-shape design, whose park had ZERO retry while the wipe path
|
|
415
|
+
* retried the very same lock class):
|
|
416
|
+
*
|
|
417
|
+
* 1. `rename(from, to)` retried over the shared handle-release budget
|
|
418
|
+
* (HANDLE_RELEASE_PROBE_ATTEMPTS × linear HANDLE_RELEASE_PROBE_DELAY_MS,
|
|
419
|
+
* lbug-config.ts) — a transient AV/handle-lag EBUSY must not cost the
|
|
420
|
+
* run anything.
|
|
421
|
+
* 2. Structural confirm probe: a bare "does `to` exist?" check cannot
|
|
422
|
+
* discriminate a Windows rename-onto-existing collision from a locked
|
|
423
|
+
* source that happens to have a leftover parked copy. Renaming the
|
|
424
|
+
* source to the collision-free `${to}.next` can — success proves the
|
|
425
|
+
* failure was the collision, so the stale copy is replaced (newest
|
|
426
|
+
* forensics win). The crash window between the `rm(to)` and the final
|
|
427
|
+
* promote rename strands the bytes at `.next` — acceptable: `.next`
|
|
428
|
+
* residues are enumerated by the dirty-recovery lister and removed by
|
|
429
|
+
* `clean --lbug-sidecars` (FIX 5). Never pre-delete the previous
|
|
430
|
+
* crash's parked copy on the bet that a rename will then succeed
|
|
431
|
+
* (tri-review 4669518496 P2-3: the old rm-first shape destroyed the
|
|
432
|
+
* prior forensics exactly when the source was locked and nothing
|
|
433
|
+
* replaced them).
|
|
434
|
+
* 3. rm-fallback: the source itself is locked for RENAME, but Windows
|
|
435
|
+
* lets some holders' files be unlink-marked — retry
|
|
436
|
+
* `rm(from, {force:true})` over the same budget and require the file
|
|
437
|
+
* verifiably GONE. Success eliminates the replay risk at the cost of
|
|
438
|
+
* the post-mortem forensics (logged exactly so).
|
|
439
|
+
* 4. Report in `failed` with the corrected lock guidance — the caller
|
|
440
|
+
* must abort (run-analyze throws a LbugWipeError in seconds instead
|
|
441
|
+
* of running the whole pipeline and dying at the wipe on the same
|
|
442
|
+
* handle).
|
|
443
|
+
*
|
|
444
|
+
* Per-suffix isolation: a `.wal` failure never skips the `.shadow`
|
|
445
|
+
* attempt.
|
|
446
|
+
*
|
|
447
|
+
* INVARIANT: after this function returns, either no original sidecar
|
|
448
|
+
* remains adjacent to the DB — every entry is in `moved` or `removed`, so
|
|
449
|
+
* every subsequent open this run performs is replay-free — or the entry is
|
|
450
|
+
* in `failed` and the caller MUST abort before any DB open.
|
|
451
|
+
*
|
|
452
|
+
* @returns `moved` — destination paths now holding the parked bytes;
|
|
453
|
+
* `removed` — source sidecars whose bytes are GONE (forensics lost, replay
|
|
454
|
+
* risk eliminated); `failed` — source sidecars still in place: a
|
|
455
|
+
* possibly-poisoned sidecar sits next to the DB and any pre-wipe open
|
|
456
|
+
* would replay it and die (there is no "wipe it in place" fallback).
|
|
457
|
+
*/
|
|
458
|
+
export async function quarantineSidecarsForDirtyRecovery(dbPath, log) {
|
|
459
|
+
const moved = [];
|
|
460
|
+
const removed = [];
|
|
461
|
+
const failed = [];
|
|
462
|
+
for (const suffix of DIRTY_RECOVERY_SIDECAR_SUFFIXES) {
|
|
463
|
+
const from = `${dbPath}${suffix}`;
|
|
464
|
+
const to = `${from}.dirty-recovery`;
|
|
465
|
+
try {
|
|
466
|
+
if (!(await statIfExists(from)))
|
|
467
|
+
continue;
|
|
468
|
+
}
|
|
469
|
+
catch (err) {
|
|
470
|
+
// Non-ENOENT stat failure (EPERM/EBUSY class — statIfExists swallows
|
|
471
|
+
// ENOENT itself): assume the sidecar exists and is unreachable; the
|
|
472
|
+
// caller must fail safe.
|
|
473
|
+
failed.push(from);
|
|
474
|
+
log(sidecarParkRefusedWarning(from, err));
|
|
475
|
+
continue;
|
|
476
|
+
}
|
|
477
|
+
// 1. Rename, retried over the shared handle-release budget for the
|
|
478
|
+
// transient lock class only (EACCES/EPERM/EBUSY — an AV scan or
|
|
479
|
+
// handle-release lag clears within it; a structural failure like
|
|
480
|
+
// EEXIST goes straight to the confirm probe).
|
|
481
|
+
let outcome = 'rename-failed';
|
|
482
|
+
let renameErr;
|
|
483
|
+
for (let attempt = 1; attempt <= HANDLE_RELEASE_PROBE_ATTEMPTS; attempt++) {
|
|
484
|
+
try {
|
|
485
|
+
await fs.rename(from, to);
|
|
486
|
+
outcome = 'moved';
|
|
487
|
+
break;
|
|
488
|
+
}
|
|
489
|
+
catch (err) {
|
|
490
|
+
if (missing(err)) {
|
|
491
|
+
outcome = 'raced'; // source raced away between stat and rename
|
|
492
|
+
break;
|
|
493
|
+
}
|
|
494
|
+
renameErr = err;
|
|
495
|
+
if (!isPermissionRenameError(err) || attempt === HANDLE_RELEASE_PROBE_ATTEMPTS)
|
|
496
|
+
break;
|
|
497
|
+
await sleep(HANDLE_RELEASE_PROBE_DELAY_MS * attempt);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
if (outcome === 'moved') {
|
|
501
|
+
moved.push(to);
|
|
502
|
+
continue;
|
|
503
|
+
}
|
|
504
|
+
if (outcome === 'raced')
|
|
505
|
+
continue;
|
|
506
|
+
// 2. Structural confirm probe (see TSDoc step 2).
|
|
507
|
+
const probe = `${to}.next`;
|
|
508
|
+
let probeLanded = false;
|
|
509
|
+
try {
|
|
510
|
+
await fs.rename(from, probe);
|
|
511
|
+
probeLanded = true;
|
|
512
|
+
}
|
|
513
|
+
catch (probeErr) {
|
|
514
|
+
if (missing(probeErr))
|
|
515
|
+
continue; // source raced away mid-probe
|
|
516
|
+
// Source locked for rename — fall through to the rm-fallback below.
|
|
517
|
+
}
|
|
518
|
+
if (probeLanded) {
|
|
519
|
+
try {
|
|
520
|
+
// True collision — replace the stale parked copy: newest forensics win.
|
|
521
|
+
await fs.rm(to, { force: true });
|
|
522
|
+
await fs.rename(probe, to);
|
|
523
|
+
moved.push(to);
|
|
524
|
+
}
|
|
525
|
+
catch {
|
|
526
|
+
// Double failure: the stale copy is itself locked/undeletable. The
|
|
527
|
+
// interrupted run's sidecar is already out of the replay path at the
|
|
528
|
+
// probe name, so the recovery open stays safe — keep both files.
|
|
529
|
+
moved.push(probe);
|
|
530
|
+
log(`Warning: parked ${path.basename(from)} as ${path.basename(probe)} — the stale ` +
|
|
531
|
+
`${path.basename(to)} from an earlier crash is locked and could not be replaced.`);
|
|
532
|
+
}
|
|
533
|
+
continue;
|
|
534
|
+
}
|
|
535
|
+
// 3. rm-fallback, retried over the same budget. `force: true` swallows
|
|
536
|
+
// ENOENT, so a resolving rm proves nothing on Windows (delete-pending
|
|
537
|
+
// keeps the name visible) — only a verifiably-absent file counts.
|
|
538
|
+
let removedOk = false;
|
|
539
|
+
for (let attempt = 1; attempt <= HANDLE_RELEASE_PROBE_ATTEMPTS; attempt++) {
|
|
540
|
+
try {
|
|
541
|
+
await fs.rm(from, { force: true });
|
|
542
|
+
}
|
|
543
|
+
catch {
|
|
544
|
+
/* verified below — the absence probe is authoritative */
|
|
545
|
+
}
|
|
546
|
+
let stillPresent = true;
|
|
547
|
+
try {
|
|
548
|
+
stillPresent = (await statIfExists(from)) !== null;
|
|
549
|
+
}
|
|
550
|
+
catch {
|
|
551
|
+
// Non-ENOENT stat failure: not verifiably gone — keep retrying.
|
|
552
|
+
}
|
|
553
|
+
if (!stillPresent) {
|
|
554
|
+
removedOk = true;
|
|
555
|
+
break;
|
|
556
|
+
}
|
|
557
|
+
if (attempt < HANDLE_RELEASE_PROBE_ATTEMPTS) {
|
|
558
|
+
await sleep(HANDLE_RELEASE_PROBE_DELAY_MS * attempt);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
if (removedOk) {
|
|
562
|
+
removed.push(from);
|
|
563
|
+
log(`Removed ${path.basename(from)} from the interrupted run — it could not be parked ` +
|
|
564
|
+
'aside (rename locked), so its bytes were deleted instead: post-mortem forensics ' +
|
|
565
|
+
'are lost, but the replay risk is eliminated and recovery can proceed.');
|
|
566
|
+
continue;
|
|
567
|
+
}
|
|
568
|
+
// 4. Everything failed — the poisoned bytes still sit next to the DB.
|
|
569
|
+
failed.push(from);
|
|
570
|
+
log(sidecarParkRefusedWarning(from, renameErr));
|
|
571
|
+
}
|
|
572
|
+
if (moved.length > 0) {
|
|
573
|
+
log(`Parked ${moved.map((p) => path.basename(p)).join(', ')} from the interrupted run ` +
|
|
574
|
+
'so the recovery rebuild opens without replaying it.');
|
|
575
|
+
}
|
|
576
|
+
return { moved, removed, failed };
|
|
577
|
+
}
|
|
257
578
|
export async function listQuarantinedMissingShadowWals(dbPath) {
|
|
258
579
|
const dir = path.dirname(dbPath);
|
|
259
580
|
const base = path.basename(dbPath);
|
|
@@ -271,14 +592,95 @@ export async function listQuarantinedMissingShadowWals(dbPath) {
|
|
|
271
592
|
.map((entry) => path.join(dir, entry))
|
|
272
593
|
.sort();
|
|
273
594
|
}
|
|
274
|
-
|
|
275
|
-
|
|
595
|
+
/**
|
|
596
|
+
* Shared unlink walker for the parked-sidecar cleaners (this shipping
|
|
597
|
+
* review, FIX 5). Per-file error policy: ENOENT is skipped silently (a
|
|
598
|
+
* list→delete race means the file is already gone — the desired state);
|
|
599
|
+
* EBUSY/EPERM/anything else lands in `failed` and the walk CONTINUES —
|
|
600
|
+
* the old per-family cleaners threw on the first locked file, crashing
|
|
601
|
+
* the whole clean mid-command after a partial deletion.
|
|
602
|
+
*/
|
|
603
|
+
const unlinkParkedFiles = async (files) => {
|
|
276
604
|
const deleted = [];
|
|
605
|
+
const failed = [];
|
|
277
606
|
for (const file of files) {
|
|
278
|
-
|
|
279
|
-
|
|
607
|
+
try {
|
|
608
|
+
await fs.unlink(file);
|
|
609
|
+
deleted.push(file);
|
|
610
|
+
}
|
|
611
|
+
catch (err) {
|
|
612
|
+
if (missing(err))
|
|
613
|
+
continue;
|
|
614
|
+
failed.push(file);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
return { deleted, failed };
|
|
618
|
+
};
|
|
619
|
+
/**
|
|
620
|
+
* Delete the missing-shadow WAL quarantines for `dbPath` and return the
|
|
621
|
+
* deleted paths. Locked files are skipped, not thrown (FIX 5) — user-facing
|
|
622
|
+
* surfaces should call {@link cleanParkedLbugSidecars}, which also REPORTS
|
|
623
|
+
* the skipped files.
|
|
624
|
+
*/
|
|
625
|
+
export async function cleanQuarantinedMissingShadowWals(dbPath) {
|
|
626
|
+
return (await unlinkParkedFiles(await listQuarantinedMissingShadowWals(dbPath))).deleted;
|
|
627
|
+
}
|
|
628
|
+
/**
|
|
629
|
+
* List the `.dirty-recovery` sidecars parked beside `dbPath` by
|
|
630
|
+
* {@link quarantineSidecarsForDirtyRecovery}, so `gitnexus clean
|
|
631
|
+
* --lbug-sidecars` can surface them next to the missing-shadow quarantines
|
|
632
|
+
* (tri-review 4669518496 P2-7 — they were previously invisible to every
|
|
633
|
+
* cleanup surface). Only fixed names can exist (see
|
|
634
|
+
* {@link dirtyRecoveryParkedNames}: `<dbPath>.wal.dirty-recovery`,
|
|
635
|
+
* `<dbPath>.shadow.dirty-recovery`, and their `.next` probe residues from a
|
|
636
|
+
* double park failure — enumerated since FIX 5 of this shipping review; the
|
|
637
|
+
* docs used to say "remove manually" while no surface even listed them), so
|
|
638
|
+
* this stats them directly instead of prefix-scanning the directory the way
|
|
639
|
+
* the timestamped missing-shadow lister must.
|
|
640
|
+
*
|
|
641
|
+
* Returns existing parked files as sorted absolute paths. Branch-scoped
|
|
642
|
+
* index slots (`branches/<slug>/`) are outside `clean.ts`'s flat-path
|
|
643
|
+
* resolution — the same documented limitation as the missing-shadow pair.
|
|
644
|
+
*/
|
|
645
|
+
export async function listParkedDirtyRecoverySidecars(dbPath) {
|
|
646
|
+
const present = [];
|
|
647
|
+
for (const parked of dirtyRecoveryParkedNames(dbPath)) {
|
|
648
|
+
if (await statIfExists(parked))
|
|
649
|
+
present.push(parked);
|
|
280
650
|
}
|
|
281
|
-
return
|
|
651
|
+
return present.sort();
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* Delete the `.dirty-recovery` parked sidecars for `dbPath` and return the
|
|
655
|
+
* deleted paths. Sibling of {@link cleanQuarantinedMissingShadowWals}; same
|
|
656
|
+
* skip-not-throw policy (FIX 5) — user-facing surfaces should call
|
|
657
|
+
* {@link cleanParkedLbugSidecars}, which also reports locked files.
|
|
658
|
+
*/
|
|
659
|
+
export async function cleanParkedDirtyRecoverySidecars(dbPath) {
|
|
660
|
+
return (await unlinkParkedFiles(await listParkedDirtyRecoverySidecars(dbPath))).deleted;
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* Aggregate roster of every parked/quarantined sidecar family beside
|
|
664
|
+
* `dbPath` (this shipping review, FIX 5): the timestamped missing-shadow
|
|
665
|
+
* WAL quarantines plus the fixed-name dirty-recovery parks (`.next`
|
|
666
|
+
* residues included). Single roster authority for `clean --lbug-sidecars`
|
|
667
|
+
* — the command previously concatenated the families inline in two places,
|
|
668
|
+
* which is how the `.next` residue stayed invisible.
|
|
669
|
+
*/
|
|
670
|
+
export async function listParkedLbugSidecars(dbPath) {
|
|
671
|
+
return [
|
|
672
|
+
...(await listQuarantinedMissingShadowWals(dbPath)),
|
|
673
|
+
...(await listParkedDirtyRecoverySidecars(dbPath)),
|
|
674
|
+
];
|
|
675
|
+
}
|
|
676
|
+
/**
|
|
677
|
+
* Delete every file {@link listParkedLbugSidecars} enumerates. Per-file
|
|
678
|
+
* error policy via {@link unlinkParkedFiles}: ENOENT skipped silently,
|
|
679
|
+
* locked files collected into `failed` while the rest are still deleted —
|
|
680
|
+
* a locked parked file must not crash the whole clean mid-command.
|
|
681
|
+
*/
|
|
682
|
+
export async function cleanParkedLbugSidecars(dbPath) {
|
|
683
|
+
return unlinkParkedFiles(await listParkedLbugSidecars(dbPath));
|
|
282
684
|
}
|
|
283
685
|
export const _resetSidecarRecoveryWarningsForTest = () => {
|
|
284
686
|
warnedKeyCounts.clear();
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
1
2
|
import os from 'os';
|
|
3
|
+
import path from 'path';
|
|
2
4
|
import { createRequire } from 'module';
|
|
3
5
|
const require = createRequire(import.meta.url);
|
|
4
6
|
const packageVersion = (name) => {
|
|
@@ -6,7 +8,31 @@ const packageVersion = (name) => {
|
|
|
6
8
|
return require(`${name}/package.json`).version;
|
|
7
9
|
}
|
|
8
10
|
catch {
|
|
9
|
-
|
|
11
|
+
// Packages whose `exports` map omits ./package.json (e.g. @ladybugdb/core)
|
|
12
|
+
// reject the direct require with ERR_PACKAGE_PATH_NOT_EXPORTED, which made
|
|
13
|
+
// doctor print "LadybugDB: unknown" on every platform (#2374). Resolve the
|
|
14
|
+
// entry point instead and walk up to the package's own package.json.
|
|
15
|
+
try {
|
|
16
|
+
let dir = path.dirname(require.resolve(name));
|
|
17
|
+
// Entry points sit at the package root or a shallow dist/ dir; a few
|
|
18
|
+
// hops always reach the package's own package.json.
|
|
19
|
+
for (let hops = 0; hops < 5; hops++) {
|
|
20
|
+
const candidate = path.join(dir, 'package.json');
|
|
21
|
+
if (fs.existsSync(candidate)) {
|
|
22
|
+
const pkg = JSON.parse(fs.readFileSync(candidate, 'utf8'));
|
|
23
|
+
if (pkg.name === name)
|
|
24
|
+
return pkg.version;
|
|
25
|
+
}
|
|
26
|
+
const parent = path.dirname(dir);
|
|
27
|
+
if (parent === dir)
|
|
28
|
+
break;
|
|
29
|
+
dir = parent;
|
|
30
|
+
}
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
10
36
|
}
|
|
11
37
|
};
|
|
12
38
|
const gitnexusVersion = () => {
|