gitnexus 1.6.4-rc.98 → 1.6.4
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/_shared/index.d.ts +8 -0
- package/dist/_shared/index.d.ts.map +1 -1
- package/dist/_shared/index.js +9 -0
- package/dist/_shared/index.js.map +1 -1
- package/dist/_shared/integrations/circuit-breaker.d.ts +183 -0
- package/dist/_shared/integrations/circuit-breaker.d.ts.map +1 -0
- package/dist/_shared/integrations/circuit-breaker.js +236 -0
- package/dist/_shared/integrations/circuit-breaker.js.map +1 -0
- package/dist/_shared/integrations/resilient-fetch.d.ts +99 -0
- package/dist/_shared/integrations/resilient-fetch.d.ts.map +1 -0
- package/dist/_shared/integrations/resilient-fetch.js +204 -0
- package/dist/_shared/integrations/resilient-fetch.js.map +1 -0
- package/dist/_shared/integrations/retry.d.ts +60 -0
- package/dist/_shared/integrations/retry.d.ts.map +1 -0
- package/dist/_shared/integrations/retry.js +67 -0
- package/dist/_shared/integrations/retry.js.map +1 -0
- package/dist/_shared/integrations/understand-quickly.d.ts +79 -0
- package/dist/_shared/integrations/understand-quickly.d.ts.map +1 -0
- package/dist/_shared/integrations/understand-quickly.js +139 -0
- package/dist/_shared/integrations/understand-quickly.js.map +1 -0
- package/dist/_shared/test-helpers.d.ts +13 -0
- package/dist/_shared/test-helpers.d.ts.map +1 -0
- package/dist/_shared/test-helpers.js +13 -0
- package/dist/_shared/test-helpers.js.map +1 -0
- package/dist/cli/index.js +9 -0
- package/dist/cli/publish.d.ts +29 -0
- package/dist/cli/publish.js +174 -0
- package/dist/config/ignore-service.js +2 -0
- package/dist/core/embeddings/hf-env.d.ts +9 -37
- package/dist/core/embeddings/hf-env.js +65 -83
- package/dist/core/embeddings/http-client.d.ts +6 -0
- package/dist/core/embeddings/http-client.js +22 -18
- package/dist/core/group/bridge-db.js +7 -6
- package/dist/core/group/config-parser.js +19 -1
- package/dist/core/group/extractors/include-extractor.d.ts +39 -0
- package/dist/core/group/extractors/include-extractor.js +565 -0
- package/dist/core/group/extractors/manifest-extractor.js +8 -0
- package/dist/core/group/extractors/thrift-extractor.js +8 -0
- package/dist/core/group/matching.js +2 -0
- package/dist/core/group/storage.js +8 -1
- package/dist/core/group/sync.js +33 -0
- package/dist/core/group/types.d.ts +2 -1
- package/dist/core/ingestion/call-processor.js +6 -4
- package/dist/core/ingestion/cpp-ue-preprocessor.d.ts +12 -0
- package/dist/core/ingestion/cpp-ue-preprocessor.js +260 -0
- package/dist/core/ingestion/field-extractors/configs/csharp.js +8 -3
- package/dist/core/ingestion/heritage-processor.js +9 -4
- package/dist/core/ingestion/import-processor.js +3 -2
- package/dist/core/ingestion/language-provider.d.ts +32 -0
- package/dist/core/ingestion/languages/c-cpp.js +2 -0
- package/dist/core/ingestion/languages/csharp/captures.js +93 -0
- package/dist/core/ingestion/languages/csharp/query.js +6 -0
- package/dist/core/ingestion/parsing-processor.js +4 -0
- package/dist/core/ingestion/workers/parse-worker.js +4 -0
- package/dist/core/lbug/csv-generator.js +5 -1
- package/dist/core/lbug/lbug-adapter.js +15 -0
- package/dist/core/lbug/schema.d.ts +1 -1
- package/dist/core/lbug/schema.js +12 -1
- package/dist/core/wiki/llm-client.d.ts +16 -0
- package/dist/core/wiki/llm-client.js +101 -63
- package/dist/mcp/local/local-backend.js +73 -7
- package/dist/server/api.js +7 -2
- package/package.json +2 -2
- package/vendor/node_modules/node-addon-api/node_addon_api.target.mk +16 -14
- package/vendor/node_modules/node-addon-api/node_addon_api_except.target.mk +16 -14
- package/vendor/node_modules/node-addon-api/node_addon_api_except_all.target.mk +16 -14
- package/vendor/node_modules/node-addon-api/node_addon_api_maybe.target.mk +16 -14
- package/web/assets/{agent-Dl2d-mDi.js → agent-BpHQleHE.js} +17 -17
- package/web/assets/architecture-YZFGNWBL-S5CXDPWN-BUzkWgDl.js +1 -0
- package/web/assets/{architectureDiagram-EMZXCZ2Q-uiiIna83.js → architectureDiagram-EMZXCZ2Q-CXbzok1G.js} +3 -3
- package/web/assets/{blockDiagram-IGV67L2C-uBKdDzXA.js → blockDiagram-IGV67L2C-DA1QJ4ZB.js} +4 -4
- package/web/assets/{c4Diagram-DFAF54RM-DTxaX43T.js → c4Diagram-DFAF54RM-DeJV1rCn.js} +2 -2
- package/web/assets/{chunk-3GS5O3IE-BxKKu7d7.js → chunk-3GS5O3IE-B8hpnia8.js} +2 -2
- package/web/assets/{chunk-3YCYZ6SJ-BJZnGFjg.js → chunk-3YCYZ6SJ-B-BVu21C.js} +1 -1
- package/web/assets/{chunk-6NTNNK5N-CIL_eO2d.js → chunk-6NTNNK5N-DW-jYSDr.js} +1 -1
- package/web/assets/{chunk-A34GCYZU-Dv4Vkem9.js → chunk-A34GCYZU-BbZl-IKy.js} +1 -1
- package/web/assets/{chunk-DJ7UZH7F-Cskl-nb3.js → chunk-DJ7UZH7F-B5tMfF87.js} +1 -1
- package/web/assets/{chunk-DKKBVRCY-D3S2BD56.js → chunk-DKKBVRCY-Djm-6K_e.js} +2 -2
- package/web/assets/{chunk-DU5LTGQ6-B8Zh51LG.js → chunk-DU5LTGQ6-QPoynYcp.js} +1 -1
- package/web/assets/{chunk-FXACKDTF-Cb4xHVUz.js → chunk-FXACKDTF-jieSmbe2.js} +3 -3
- package/web/assets/chunk-H3VCZNTA-8EMHZ3SV.js +1 -0
- package/web/assets/chunk-HN6EAY2L-CgE_agsD.js +1 -0
- package/web/assets/{chunk-O5ABG6QK-V3sk7Bps.js → chunk-O5ABG6QK-DEZV1O19.js} +1 -1
- package/web/assets/{chunk-PK6DOVAG-FvG7aKfZ.js → chunk-PK6DOVAG-BXv0JnsJ.js} +3 -3
- package/web/assets/{chunk-RNJOYNJ4-BPANsy8V.js → chunk-RNJOYNJ4-BiBkF9dN.js} +1 -1
- package/web/assets/chunk-RWUO3TPN-DTWzAhQ1.js +1 -0
- package/web/assets/chunk-TBF5ZNIQ-BSl6s9lT.js +1 -0
- package/web/assets/{chunk-TYMNRAUI-BnMe6HfP.js → chunk-TYMNRAUI-D1NDLIMG.js} +1 -1
- package/web/assets/{chunk-W7ZLLLMY-BqZFKM5j.js → chunk-W7ZLLLMY-Bu-cdLzs.js} +1 -1
- package/web/assets/{chunk-WSB5WSVC-EKS7-Nnc.js → chunk-WSB5WSVC-Dor71SOz.js} +1 -1
- package/web/assets/{chunk-XGPFEOL4-BBab-enK.js → chunk-XGPFEOL4-CUonLRle.js} +1 -1
- package/web/assets/classDiagram-PPOCWD7C-Cdd6UdVv.js +1 -0
- package/web/assets/classDiagram-v2-23LJLIIU-DNhxY2Ib.js +1 -0
- package/web/assets/{cose-bilkent-PNC4W37J-DjEQkTZ4.js → cose-bilkent-PNC4W37J-DdZq7Qz_.js} +1 -1
- package/web/assets/dagre-E77IOHMT-Cfj_6e17.js +4 -0
- package/web/assets/{diagram-H7BISOXX-CMuISoJL.js → diagram-H7BISOXX-BNvpsrBv.js} +2 -2
- package/web/assets/{diagram-JC5VWROH-DHq_eoeM.js → diagram-JC5VWROH-Cn0rtDbJ.js} +1 -1
- package/web/assets/{diagram-LXUTUG65-C0NczIgW.js → diagram-LXUTUG65-CHfoVKNm.js} +2 -2
- package/web/assets/{diagram-WEHSV5V5-WsBnpSuc.js → diagram-WEHSV5V5-BLxHksnu.js} +1 -1
- package/web/assets/{erDiagram-GCSMX5X6-DxHFHFR6.js → erDiagram-GCSMX5X6-DfdN6iqF.js} +2 -2
- package/web/assets/flowDiagram-OTCZ4VVT-aAZw4c_M.js +162 -0
- package/web/assets/ganttDiagram-MUNLMDZQ-BEfC_7Or.js +292 -0
- package/web/assets/gitGraph-7Q5UKJZL-54BCDZD5-B6ZeRLP0.js +1 -0
- package/web/assets/gitGraphDiagram-3HKGZ4G3-CG0kC8Q5.js +106 -0
- package/web/assets/{index-DDcYO1IJ.js → index-3NUCUW2s.js} +119 -119
- package/web/assets/info-OMHHGYJF-BF2H5H6G-BIOsZy0W.js +1 -0
- package/web/assets/infoDiagram-MN7RKWGX-DUoMH-pq.js +2 -0
- package/web/assets/{ishikawaDiagram-YMYX4NHK-C-JI2Ih7.js → ishikawaDiagram-YMYX4NHK-BF8AtDaj.js} +2 -2
- package/web/assets/{journeyDiagram-SO5T7YLQ-IPX4h3iD.js → journeyDiagram-SO5T7YLQ-BW8oKa3N.js} +3 -3
- package/web/assets/{kanban-definition-LJHFXRCJ-B23LtshQ.js → kanban-definition-LJHFXRCJ-DzbOsR-2.js} +6 -6
- package/web/assets/{mindmap-definition-2EUWGEK5-DJKUsrCo.js → mindmap-definition-2EUWGEK5-BIXGQALq.js} +25 -25
- package/web/assets/packet-4T2RLAQJ-EV4IVRXR-Bs7ysNMU.js +1 -0
- package/web/assets/pie-ZZUOXDRM-N23DN5KN-Csi8nnbH.js +1 -0
- package/web/assets/{pieDiagram-3IATQBI2-Bj26KTKk.js → pieDiagram-3IATQBI2-QY-u7P6a.js} +3 -3
- package/web/assets/{quadrantDiagram-E256RVCF-VAk5oIRr.js → quadrantDiagram-E256RVCF-Di4aNiU8.js} +3 -3
- package/web/assets/radar-PYXPWWZC-P6TP7ZYP-DJlyXh2-.js +1 -0
- package/web/assets/{requirementDiagram-M5DCFWZL-CmlTJ_vQ.js → requirementDiagram-M5DCFWZL-CzUUrhQD.js} +4 -4
- package/web/assets/{sankeyDiagram-L3NBLAOT-C-f_DdcX.js → sankeyDiagram-L3NBLAOT-D2fbMiIU.js} +3 -3
- package/web/assets/{sequenceDiagram-ZOUHS735-DmtNf1NF.js → sequenceDiagram-ZOUHS735-BNdkqguh.js} +6 -6
- package/web/assets/stateDiagram-MLPALWAM-51_Q2w2L.js +1 -0
- package/web/assets/stateDiagram-v2-B5LQ5ZB2-8nHs2aNE.js +1 -0
- package/web/assets/{timeline-definition-5SPVSISX-BkpqZxt2.js → timeline-definition-5SPVSISX-DkMiGj4Z.js} +9 -9
- package/web/assets/treeView-SZITEDCU-5DXDK3XO-Cy6P0_CZ.js +1 -0
- package/web/assets/treemap-W4RFUUIX-WYLRDWKO-C_pp1R7A.js +1 -0
- package/web/assets/{vennDiagram-IE5QUKF5-iAJ08JBe.js → vennDiagram-IE5QUKF5-BCxMmf9q.js} +3 -3
- package/web/assets/wardley-RL74JXVD-BCRCBASE-CMStJhpn.js +1 -0
- package/web/assets/{wardleyDiagram-XU3VSMPF-BDiKTkL4.js → wardleyDiagram-XU3VSMPF-ijlLF77s.js} +4 -4
- package/web/assets/{xychartDiagram-ZHJ5623Y-CknyZyQK.js → xychartDiagram-ZHJ5623Y-DZ7fP1_3.js} +3 -3
- package/web/index.html +1 -1
- package/web/assets/architecture-YZFGNWBL-S5CXDPWN-BYXnTu07.js +0 -1
- package/web/assets/chunk-H3VCZNTA-BOGhqvxW.js +0 -1
- package/web/assets/chunk-HN6EAY2L-CitKFy1e.js +0 -1
- package/web/assets/chunk-RWUO3TPN-BHusg2jI.js +0 -1
- package/web/assets/chunk-TBF5ZNIQ-CXamrpH3.js +0 -1
- package/web/assets/classDiagram-PPOCWD7C-B-LkKU0P.js +0 -1
- package/web/assets/classDiagram-v2-23LJLIIU-_NGI4VEh.js +0 -1
- package/web/assets/dagre-E77IOHMT-D6Xacg1x.js +0 -4
- package/web/assets/flowDiagram-OTCZ4VVT-_G1VxyQS.js +0 -162
- package/web/assets/ganttDiagram-MUNLMDZQ-BIHObj0t.js +0 -292
- package/web/assets/gitGraph-7Q5UKJZL-54BCDZD5-BXJXsixL.js +0 -1
- package/web/assets/gitGraphDiagram-3HKGZ4G3-COZUkHTD.js +0 -106
- package/web/assets/info-OMHHGYJF-BF2H5H6G-C820LK0J.js +0 -1
- package/web/assets/infoDiagram-MN7RKWGX-BeOjfPc_.js +0 -2
- package/web/assets/packet-4T2RLAQJ-EV4IVRXR-BvWtM5jq.js +0 -1
- package/web/assets/pie-ZZUOXDRM-N23DN5KN-BnDgj0TQ.js +0 -1
- package/web/assets/radar-PYXPWWZC-P6TP7ZYP-2IuDlZ6r.js +0 -1
- package/web/assets/stateDiagram-MLPALWAM-Lq7UVrkj.js +0 -1
- package/web/assets/stateDiagram-v2-B5LQ5ZB2-DCiXBPeS.js +0 -1
- package/web/assets/treeView-SZITEDCU-5DXDK3XO-Ipq1EIaF.js +0 -1
- package/web/assets/treemap-W4RFUUIX-WYLRDWKO-DESFxovu.js +0 -1
- package/web/assets/wardley-RL74JXVD-BCRCBASE-BVkhckBY.js +0 -1
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { ContractExtractor, CypherExecutor } from '../contract-extractor.js';
|
|
2
|
+
import type { ExtractedContract, RepoHandle } from '../types.js';
|
|
3
|
+
export declare class IncludeExtractor implements ContractExtractor {
|
|
4
|
+
type: "include";
|
|
5
|
+
/**
|
|
6
|
+
* Always returns `true`. NOT called by `sync.ts`, which gates extraction via
|
|
7
|
+
* `config.detect.includes` instead (see `sync.ts:174`). Kept solely to satisfy
|
|
8
|
+
* the `ContractExtractor` interface so the type stays uniform across extractors.
|
|
9
|
+
*/
|
|
10
|
+
canExtract(_repo: RepoHandle): Promise<boolean>;
|
|
11
|
+
extract(dbExecutor: CypherExecutor | null, repoPath: string, _repo: RepoHandle): Promise<ExtractedContract[]>;
|
|
12
|
+
/**
|
|
13
|
+
* Discover repo-relative file paths using exactly the same rules the
|
|
14
|
+
* ingestion pipeline uses (`walkRepositoryPaths` in
|
|
15
|
+
* `gitnexus/src/core/ingestion/filesystem-walker.ts`):
|
|
16
|
+
* - `createIgnoreFilter` honors `.gitignore`, `.gitnexusignore`, the
|
|
17
|
+
* hardcoded ignore list, and `.gitnexusignore` last-match-wins
|
|
18
|
+
* negation.
|
|
19
|
+
* - `getMaxFileSizeBytes()` drops files larger than the cap so we
|
|
20
|
+
* never emit `File:<rel>` UIDs for files ingestion would skip.
|
|
21
|
+
*
|
|
22
|
+
* Uses sequential stat — there is no `READ_CONCURRENCY` batching here
|
|
23
|
+
* because group sync runs at startup-time, not the ingestion hot path,
|
|
24
|
+
* and parallelism gains are not worth the import-graph weight.
|
|
25
|
+
*
|
|
26
|
+
* MAINTENANCE: if `walkRepositoryPaths` changes its glob options, ignore
|
|
27
|
+
* filter shape, or size-cap logic, mirror those changes here. The two
|
|
28
|
+
* implementations exist because the consumers need different return
|
|
29
|
+
* shapes (string[] vs ScannedFile[]) and different concurrency, but
|
|
30
|
+
* they MUST agree on which files are reachable — that is what makes
|
|
31
|
+
* `File:<rel>` UIDs in cross-links correspond to graph File nodes.
|
|
32
|
+
*/
|
|
33
|
+
private discoverIndexableFiles;
|
|
34
|
+
private extractProviders;
|
|
35
|
+
private extractProvidersGraph;
|
|
36
|
+
private extractProvidersFallback;
|
|
37
|
+
private extractConsumers;
|
|
38
|
+
private dedupe;
|
|
39
|
+
}
|
|
@@ -0,0 +1,565 @@
|
|
|
1
|
+
import * as path from 'node:path';
|
|
2
|
+
import * as fs from 'node:fs/promises';
|
|
3
|
+
import { glob } from 'glob';
|
|
4
|
+
import Parser from 'tree-sitter';
|
|
5
|
+
import C from 'tree-sitter-c';
|
|
6
|
+
import Cpp from 'tree-sitter-cpp';
|
|
7
|
+
import { readSafe } from './fs-utils.js';
|
|
8
|
+
import { buildSuffixIndex } from '../../ingestion/import-resolvers/utils.js';
|
|
9
|
+
import { createIgnoreFilter } from '../../../config/ignore-service.js';
|
|
10
|
+
import { getMaxFileSizeBytes } from '../../ingestion/utils/max-file-size.js';
|
|
11
|
+
import { logger } from '../../logger.js';
|
|
12
|
+
/**
|
|
13
|
+
* Cross-repo C/C++ `#include` dependency extractor.
|
|
14
|
+
*
|
|
15
|
+
* **Provider side:** registers every `.h/.hpp/.hxx/.hh` file in the repo
|
|
16
|
+
* as a provider contract with `include::<relative-path>`.
|
|
17
|
+
*
|
|
18
|
+
* **Consumer side:** parses all C/C++ source/header files for `#include "…"`
|
|
19
|
+
* directives, attempts suffix-based resolution against the repo's own file
|
|
20
|
+
* list (reusing the same algorithm as the single-repo ingestion pipeline),
|
|
21
|
+
* and emits unresolved include paths as consumer contracts.
|
|
22
|
+
*
|
|
23
|
+
* Matching: a consumer's `include::map/base/dice_map_view.h` in repo A
|
|
24
|
+
* matches a provider's `include::map/base/dice_map_view.h` in repo B via
|
|
25
|
+
* exact contract-id equality in `runExactMatch`.
|
|
26
|
+
*/
|
|
27
|
+
// ---------- constants ----------
|
|
28
|
+
const HEADER_EXTENSIONS = new Set(['.h', '.hpp', '.hxx', '.hh']);
|
|
29
|
+
// Source = headers (provider-eligible) ∪ implementation files (.c/.cpp/.cc/.cxx).
|
|
30
|
+
// Spread keeps the subset relationship explicit so a future contributor adding
|
|
31
|
+
// a new header extension to HEADER_EXTENSIONS does not have to remember to
|
|
32
|
+
// also add it here.
|
|
33
|
+
const SOURCE_EXTENSIONS = new Set([...HEADER_EXTENSIONS, '.c', '.cpp', '.cc', '.cxx']);
|
|
34
|
+
const INCLUDE_QUERY_SRC = '(preproc_include path: (_) @import.source) @import';
|
|
35
|
+
/**
|
|
36
|
+
* Well-known C/C++ standard library headers that can appear in `#include "…"`
|
|
37
|
+
* form (some projects use quotes for system headers).
|
|
38
|
+
*/
|
|
39
|
+
const SYSTEM_HEADERS = new Set([
|
|
40
|
+
// C standard
|
|
41
|
+
'assert.h',
|
|
42
|
+
'complex.h',
|
|
43
|
+
'ctype.h',
|
|
44
|
+
'errno.h',
|
|
45
|
+
'fenv.h',
|
|
46
|
+
'float.h',
|
|
47
|
+
'inttypes.h',
|
|
48
|
+
'iso646.h',
|
|
49
|
+
'limits.h',
|
|
50
|
+
'locale.h',
|
|
51
|
+
'math.h',
|
|
52
|
+
'setjmp.h',
|
|
53
|
+
'signal.h',
|
|
54
|
+
'stdalign.h',
|
|
55
|
+
'stdarg.h',
|
|
56
|
+
'stdatomic.h',
|
|
57
|
+
'stdbool.h',
|
|
58
|
+
'stddef.h',
|
|
59
|
+
'stdint.h',
|
|
60
|
+
'stdio.h',
|
|
61
|
+
'stdlib.h',
|
|
62
|
+
'stdnoreturn.h',
|
|
63
|
+
'string.h',
|
|
64
|
+
'tgmath.h',
|
|
65
|
+
'threads.h',
|
|
66
|
+
'time.h',
|
|
67
|
+
'uchar.h',
|
|
68
|
+
'wchar.h',
|
|
69
|
+
'wctype.h',
|
|
70
|
+
// C++ standard (extensionless)
|
|
71
|
+
'algorithm',
|
|
72
|
+
'any',
|
|
73
|
+
'array',
|
|
74
|
+
'atomic',
|
|
75
|
+
'barrier',
|
|
76
|
+
'bit',
|
|
77
|
+
'bitset',
|
|
78
|
+
'cassert',
|
|
79
|
+
'cctype',
|
|
80
|
+
'cerrno',
|
|
81
|
+
'cfenv',
|
|
82
|
+
'cfloat',
|
|
83
|
+
'charconv',
|
|
84
|
+
'chrono',
|
|
85
|
+
'cinttypes',
|
|
86
|
+
'climits',
|
|
87
|
+
'clocale',
|
|
88
|
+
'cmath',
|
|
89
|
+
'codecvt',
|
|
90
|
+
'compare',
|
|
91
|
+
'complex',
|
|
92
|
+
'concepts',
|
|
93
|
+
'condition_variable',
|
|
94
|
+
'coroutine',
|
|
95
|
+
'csetjmp',
|
|
96
|
+
'csignal',
|
|
97
|
+
'cstdarg',
|
|
98
|
+
'cstddef',
|
|
99
|
+
'cstdint',
|
|
100
|
+
'cstdio',
|
|
101
|
+
'cstdlib',
|
|
102
|
+
'cstring',
|
|
103
|
+
'ctime',
|
|
104
|
+
'cuchar',
|
|
105
|
+
'cwchar',
|
|
106
|
+
'cwctype',
|
|
107
|
+
'deque',
|
|
108
|
+
'exception',
|
|
109
|
+
'execution',
|
|
110
|
+
'expected',
|
|
111
|
+
'filesystem',
|
|
112
|
+
'format',
|
|
113
|
+
'forward_list',
|
|
114
|
+
'fstream',
|
|
115
|
+
'functional',
|
|
116
|
+
'future',
|
|
117
|
+
'generator',
|
|
118
|
+
'initializer_list',
|
|
119
|
+
'iomanip',
|
|
120
|
+
'ios',
|
|
121
|
+
'iosfwd',
|
|
122
|
+
'iostream',
|
|
123
|
+
'istream',
|
|
124
|
+
'iterator',
|
|
125
|
+
'latch',
|
|
126
|
+
'limits',
|
|
127
|
+
'list',
|
|
128
|
+
'locale',
|
|
129
|
+
'map',
|
|
130
|
+
'mdspan',
|
|
131
|
+
'memory',
|
|
132
|
+
'memory_resource',
|
|
133
|
+
'mutex',
|
|
134
|
+
'new',
|
|
135
|
+
'numbers',
|
|
136
|
+
'numeric',
|
|
137
|
+
'optional',
|
|
138
|
+
'ostream',
|
|
139
|
+
'print',
|
|
140
|
+
'queue',
|
|
141
|
+
'random',
|
|
142
|
+
'ranges',
|
|
143
|
+
'ratio',
|
|
144
|
+
'regex',
|
|
145
|
+
'scoped_allocator',
|
|
146
|
+
'semaphore',
|
|
147
|
+
'set',
|
|
148
|
+
'shared_mutex',
|
|
149
|
+
'source_location',
|
|
150
|
+
'span',
|
|
151
|
+
'spanstream',
|
|
152
|
+
'sstream',
|
|
153
|
+
'stack',
|
|
154
|
+
'stacktrace',
|
|
155
|
+
'stdexcept',
|
|
156
|
+
'stdfloat',
|
|
157
|
+
'stop_token',
|
|
158
|
+
'streambuf',
|
|
159
|
+
'string',
|
|
160
|
+
'string_view',
|
|
161
|
+
'strstream',
|
|
162
|
+
'syncstream',
|
|
163
|
+
'system_error',
|
|
164
|
+
'thread',
|
|
165
|
+
'tuple',
|
|
166
|
+
'type_traits',
|
|
167
|
+
'typeindex',
|
|
168
|
+
'typeinfo',
|
|
169
|
+
'unordered_map',
|
|
170
|
+
'unordered_set',
|
|
171
|
+
'utility',
|
|
172
|
+
'valarray',
|
|
173
|
+
'variant',
|
|
174
|
+
'vector',
|
|
175
|
+
'version',
|
|
176
|
+
]);
|
|
177
|
+
/** Path prefixes that indicate system/kernel headers. */
|
|
178
|
+
const SYSTEM_PATH_PREFIXES = [
|
|
179
|
+
'sys/',
|
|
180
|
+
'net/',
|
|
181
|
+
'netinet/',
|
|
182
|
+
'arpa/',
|
|
183
|
+
'linux/',
|
|
184
|
+
'asm/',
|
|
185
|
+
'bits/',
|
|
186
|
+
'gnu/',
|
|
187
|
+
'mach/',
|
|
188
|
+
'machine/',
|
|
189
|
+
'xlocale/',
|
|
190
|
+
];
|
|
191
|
+
/** Regex fallback for files that exceed tree-sitter's 32 KB parse limit. */
|
|
192
|
+
const INCLUDE_REGEX = /^[ \t]*#\s*include\s*"([^"]+)"/gm;
|
|
193
|
+
// ---------- helpers ----------
|
|
194
|
+
/**
|
|
195
|
+
* Normalize an include path to a canonical lowercase forward-slash form.
|
|
196
|
+
*
|
|
197
|
+
* IMPORTANT — case-folding caveat (PR #1156 review finding #3):
|
|
198
|
+
* Header paths are lowercased so consumer `#include "Foo/Bar.h"` and
|
|
199
|
+
* provider file `Foo/Bar.h` normalize to the same contract-id. This is
|
|
200
|
+
* the right trade-off on case-insensitive filesystems (macOS, Windows)
|
|
201
|
+
* but on case-sensitive Linux filesystems two distinct headers `Foo.h`
|
|
202
|
+
* and `foo.h` in the same repo will collide onto the same provider
|
|
203
|
+
* contract-id; only one survives `dedupe()`. The gain (reliable
|
|
204
|
+
* cross-platform matching) outweighs the cost (extremely rare header
|
|
205
|
+
* casing collisions inside a single repo).
|
|
206
|
+
*/
|
|
207
|
+
function normalizeIncludePath(raw) {
|
|
208
|
+
return raw.replace(/\\/g, '/').replace(/^\.\//, '').replace(/\/+/g, '/').toLowerCase();
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Strip C/C++ block comments from a source blob. Used only by the
|
|
212
|
+
* regex-fallback path to avoid emitting consumer contracts for
|
|
213
|
+
* commented-out #include directives. Line comments (`// …`) cannot hide
|
|
214
|
+
* #include directives because the regex anchors on start-of-line.
|
|
215
|
+
* See PR #1156 review finding #5.
|
|
216
|
+
*/
|
|
217
|
+
function stripBlockComments(src) {
|
|
218
|
+
return src.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
219
|
+
}
|
|
220
|
+
function isAngleBracketInclude(rawNodeText) {
|
|
221
|
+
const trimmed = rawNodeText.trim();
|
|
222
|
+
return trimmed.startsWith('<') && trimmed.endsWith('>');
|
|
223
|
+
}
|
|
224
|
+
function isSystemHeader(cleanedPath) {
|
|
225
|
+
// Check well-known standard headers
|
|
226
|
+
if (SYSTEM_HEADERS.has(cleanedPath))
|
|
227
|
+
return true;
|
|
228
|
+
// Check system path prefixes
|
|
229
|
+
const lower = cleanedPath.toLowerCase();
|
|
230
|
+
return SYSTEM_PATH_PREFIXES.some((prefix) => lower.startsWith(prefix));
|
|
231
|
+
}
|
|
232
|
+
function isHeaderFile(filePath) {
|
|
233
|
+
return HEADER_EXTENSIONS.has(path.extname(filePath).toLowerCase());
|
|
234
|
+
}
|
|
235
|
+
function getLanguageForFile(filePath) {
|
|
236
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
237
|
+
switch (ext) {
|
|
238
|
+
case '.c':
|
|
239
|
+
case '.h':
|
|
240
|
+
return C;
|
|
241
|
+
case '.cpp':
|
|
242
|
+
case '.cc':
|
|
243
|
+
case '.cxx':
|
|
244
|
+
case '.hpp':
|
|
245
|
+
case '.hxx':
|
|
246
|
+
case '.hh':
|
|
247
|
+
return Cpp;
|
|
248
|
+
default:
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Check whether an include path resolves to a file inside the local repo.
|
|
254
|
+
*
|
|
255
|
+
* Uses *exact full-path* matching on the suffix index — we never accept a
|
|
256
|
+
* truncated suffix match. For `#include "foo/bar.h"` this checks:
|
|
257
|
+
* (a) a file whose path ends with the full `foo/bar.h`
|
|
258
|
+
* (b) if the include omitted the extension, a file whose path ends with
|
|
259
|
+
* the include + one of the C/C++ header extensions
|
|
260
|
+
*
|
|
261
|
+
* Returns `true` when a local file matches — caller should suppress the
|
|
262
|
+
* cross-repo consumer contract.
|
|
263
|
+
*
|
|
264
|
+
* See PR #1156 review finding #4 (suffixResolve ambiguity).
|
|
265
|
+
*/
|
|
266
|
+
function isLocalInclude(cleaned, suffixIndex) {
|
|
267
|
+
const candidates = [cleaned];
|
|
268
|
+
if (!/\.[a-zA-Z0-9]+$/.test(cleaned)) {
|
|
269
|
+
for (const ext of ['.h', '.hpp', '.hxx', '.hh'])
|
|
270
|
+
candidates.push(cleaned + ext);
|
|
271
|
+
}
|
|
272
|
+
for (const c of candidates) {
|
|
273
|
+
if (suffixIndex.get(c) || suffixIndex.getInsensitive(c))
|
|
274
|
+
return true;
|
|
275
|
+
}
|
|
276
|
+
return false;
|
|
277
|
+
}
|
|
278
|
+
// ---------- main class ----------
|
|
279
|
+
export class IncludeExtractor {
|
|
280
|
+
type = 'include';
|
|
281
|
+
/**
|
|
282
|
+
* Always returns `true`. NOT called by `sync.ts`, which gates extraction via
|
|
283
|
+
* `config.detect.includes` instead (see `sync.ts:174`). Kept solely to satisfy
|
|
284
|
+
* the `ContractExtractor` interface so the type stays uniform across extractors.
|
|
285
|
+
*/
|
|
286
|
+
async canExtract(_repo) {
|
|
287
|
+
return true;
|
|
288
|
+
}
|
|
289
|
+
async extract(dbExecutor, repoPath, _repo) {
|
|
290
|
+
// 1. Build the local file list using the same discovery as ingestion
|
|
291
|
+
// (createIgnoreFilter + getMaxFileSizeBytes). This guarantees the
|
|
292
|
+
// universe of provider/consumer paths matches the universe of File
|
|
293
|
+
// nodes in the LadybugDB graph — so no cross-link points at a UID
|
|
294
|
+
// that group impact cannot fan out to.
|
|
295
|
+
// (PR #1156 Codex follow-up: discovery aligned with ingestion.)
|
|
296
|
+
const allFiles = await this.discoverIndexableFiles(repoPath);
|
|
297
|
+
const normalizedFiles = allFiles.map((f) => f.replace(/\\/g, '/'));
|
|
298
|
+
const suffixIndex = buildSuffixIndex(normalizedFiles, allFiles);
|
|
299
|
+
// 2. Provider: register all header files
|
|
300
|
+
const providers = await this.extractProviders(dbExecutor, repoPath, allFiles);
|
|
301
|
+
// 3. Consumer: filter the shared discovery list for source extensions
|
|
302
|
+
// and parse #include directives in those files.
|
|
303
|
+
const sourceFiles = allFiles.filter((f) => SOURCE_EXTENSIONS.has(path.extname(f).toLowerCase()));
|
|
304
|
+
const consumers = await this.extractConsumers(repoPath, sourceFiles, suffixIndex);
|
|
305
|
+
return this.dedupe([...providers, ...consumers]);
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Discover repo-relative file paths using exactly the same rules the
|
|
309
|
+
* ingestion pipeline uses (`walkRepositoryPaths` in
|
|
310
|
+
* `gitnexus/src/core/ingestion/filesystem-walker.ts`):
|
|
311
|
+
* - `createIgnoreFilter` honors `.gitignore`, `.gitnexusignore`, the
|
|
312
|
+
* hardcoded ignore list, and `.gitnexusignore` last-match-wins
|
|
313
|
+
* negation.
|
|
314
|
+
* - `getMaxFileSizeBytes()` drops files larger than the cap so we
|
|
315
|
+
* never emit `File:<rel>` UIDs for files ingestion would skip.
|
|
316
|
+
*
|
|
317
|
+
* Uses sequential stat — there is no `READ_CONCURRENCY` batching here
|
|
318
|
+
* because group sync runs at startup-time, not the ingestion hot path,
|
|
319
|
+
* and parallelism gains are not worth the import-graph weight.
|
|
320
|
+
*
|
|
321
|
+
* MAINTENANCE: if `walkRepositoryPaths` changes its glob options, ignore
|
|
322
|
+
* filter shape, or size-cap logic, mirror those changes here. The two
|
|
323
|
+
* implementations exist because the consumers need different return
|
|
324
|
+
* shapes (string[] vs ScannedFile[]) and different concurrency, but
|
|
325
|
+
* they MUST agree on which files are reachable — that is what makes
|
|
326
|
+
* `File:<rel>` UIDs in cross-links correspond to graph File nodes.
|
|
327
|
+
*/
|
|
328
|
+
async discoverIndexableFiles(repoPath) {
|
|
329
|
+
const ignoreFilter = await createIgnoreFilter(repoPath);
|
|
330
|
+
const maxFileSizeBytes = getMaxFileSizeBytes();
|
|
331
|
+
const candidates = await glob('**/*', {
|
|
332
|
+
cwd: repoPath,
|
|
333
|
+
nodir: true,
|
|
334
|
+
dot: false,
|
|
335
|
+
ignore: ignoreFilter,
|
|
336
|
+
});
|
|
337
|
+
const survivors = [];
|
|
338
|
+
for (const rel of candidates) {
|
|
339
|
+
try {
|
|
340
|
+
const stat = await fs.stat(path.join(repoPath, rel));
|
|
341
|
+
if (stat.size > maxFileSizeBytes)
|
|
342
|
+
continue;
|
|
343
|
+
survivors.push(rel);
|
|
344
|
+
}
|
|
345
|
+
catch (err) {
|
|
346
|
+
// ENOENT is the documented benign race (glob enumerated a file
|
|
347
|
+
// that was deleted before we stat'd it — same race
|
|
348
|
+
// walkRepositoryPaths absorbs via Promise.allSettled). Anything
|
|
349
|
+
// else (EACCES, EMFILE, EIO) deserves a warning so an operator
|
|
350
|
+
// can spot a permission/resource problem instead of silently
|
|
351
|
+
// shipping fewer contracts than expected.
|
|
352
|
+
const code = err?.code;
|
|
353
|
+
if (code !== 'ENOENT') {
|
|
354
|
+
logger.warn({ err: err.message, file: rel, repoPath }, '⚠️ IncludeExtractor: stat failed during discovery; skipping file');
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
return survivors;
|
|
359
|
+
}
|
|
360
|
+
// ---------- provider extraction ----------
|
|
361
|
+
async extractProviders(dbExecutor, repoPath, allFiles) {
|
|
362
|
+
// Strategy A: graph-assisted
|
|
363
|
+
if (dbExecutor) {
|
|
364
|
+
const graphProviders = await this.extractProvidersGraph(dbExecutor, repoPath);
|
|
365
|
+
if (graphProviders.length > 0)
|
|
366
|
+
return graphProviders;
|
|
367
|
+
}
|
|
368
|
+
// Strategy B: filesystem fallback
|
|
369
|
+
return this.extractProvidersFallback(repoPath, allFiles);
|
|
370
|
+
}
|
|
371
|
+
async extractProvidersGraph(db, repoPath) {
|
|
372
|
+
try {
|
|
373
|
+
const rows = await db(`MATCH (f:File)
|
|
374
|
+
WHERE f.filePath =~ '.*\\\\.(h|hpp|hxx|hh)$'
|
|
375
|
+
RETURN f.filePath AS filePath, f.id AS fileId`);
|
|
376
|
+
// gitnexus analyze stores absolute paths in the File.filePath column.
|
|
377
|
+
// Provider contract IDs MUST be repo-relative — otherwise the consumer
|
|
378
|
+
// emits `include::map/base/view.h` and the provider emits
|
|
379
|
+
// `include::/abs/path/to/repo/map/base/view.h`, which never match
|
|
380
|
+
// through runExactMatch and the cross-link silently disappears.
|
|
381
|
+
// (PR #1156 follow-up review: graph provider absolute-path bug.)
|
|
382
|
+
const normalizedRepoPath = path.resolve(repoPath);
|
|
383
|
+
const out = [];
|
|
384
|
+
for (const r of rows) {
|
|
385
|
+
if (typeof r.filePath !== 'string' || !r.filePath)
|
|
386
|
+
continue;
|
|
387
|
+
const absolute = r.filePath;
|
|
388
|
+
const rel = path.relative(normalizedRepoPath, absolute);
|
|
389
|
+
// Skip rows that resolve outside the repo (e.g., system headers
|
|
390
|
+
// somehow indexed, or stale absolute paths from a different machine).
|
|
391
|
+
// path.relative returns a `..`-prefixed path or an absolute path
|
|
392
|
+
// when the target is outside the base — both are wrong for our IDs.
|
|
393
|
+
if (!rel || rel.startsWith('..') || path.isAbsolute(rel))
|
|
394
|
+
continue;
|
|
395
|
+
const normalizedRel = rel.replace(/\\/g, '/');
|
|
396
|
+
out.push({
|
|
397
|
+
contractId: `include::${normalizeIncludePath(normalizedRel)}`,
|
|
398
|
+
type: 'include',
|
|
399
|
+
role: 'provider',
|
|
400
|
+
symbolUid: String(r.fileId ?? ''),
|
|
401
|
+
symbolRef: { filePath: normalizedRel, name: path.basename(normalizedRel) },
|
|
402
|
+
symbolName: path.basename(normalizedRel),
|
|
403
|
+
confidence: 1.0,
|
|
404
|
+
meta: { source: 'graph' },
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
return out;
|
|
408
|
+
}
|
|
409
|
+
catch {
|
|
410
|
+
return [];
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
extractProvidersFallback(_repoPath, allFiles) {
|
|
414
|
+
return allFiles
|
|
415
|
+
.filter((f) => isHeaderFile(f))
|
|
416
|
+
.map((f) => {
|
|
417
|
+
const filePath = f.replace(/\\/g, '/');
|
|
418
|
+
return {
|
|
419
|
+
contractId: `include::${normalizeIncludePath(filePath)}`,
|
|
420
|
+
type: 'include',
|
|
421
|
+
role: 'provider',
|
|
422
|
+
symbolUid: `File:${filePath}`,
|
|
423
|
+
symbolRef: { filePath, name: path.basename(filePath) },
|
|
424
|
+
symbolName: path.basename(filePath),
|
|
425
|
+
confidence: 0.95,
|
|
426
|
+
meta: { source: 'filesystem' },
|
|
427
|
+
};
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
// ---------- consumer extraction ----------
|
|
431
|
+
async extractConsumers(repoPath, sourceFiles, suffixIndex) {
|
|
432
|
+
const parser = new Parser();
|
|
433
|
+
const out = [];
|
|
434
|
+
// Compile the include query once per grammar to avoid re-compilation per file
|
|
435
|
+
const queryCache = new Map();
|
|
436
|
+
for (const rel of sourceFiles) {
|
|
437
|
+
const lang = getLanguageForFile(rel);
|
|
438
|
+
if (!lang)
|
|
439
|
+
continue;
|
|
440
|
+
const content = readSafe(repoPath, rel);
|
|
441
|
+
if (!content)
|
|
442
|
+
continue;
|
|
443
|
+
let query = queryCache.get(lang);
|
|
444
|
+
if (!query) {
|
|
445
|
+
try {
|
|
446
|
+
query = new Parser.Query(lang, INCLUDE_QUERY_SRC);
|
|
447
|
+
queryCache.set(lang, query);
|
|
448
|
+
}
|
|
449
|
+
catch {
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
// Collect raw include paths: tree-sitter first, regex fallback for large files.
|
|
454
|
+
// `extractionSource` is stamped on each emitted consumer contract so
|
|
455
|
+
// regex-fallback contracts stay auditable post-hoc (PR #1156 review finding #6).
|
|
456
|
+
let rawIncludes;
|
|
457
|
+
let extractionSource;
|
|
458
|
+
try {
|
|
459
|
+
parser.setLanguage(lang);
|
|
460
|
+
const tree = parser.parse(content);
|
|
461
|
+
let matches;
|
|
462
|
+
try {
|
|
463
|
+
matches = query.matches(tree.rootNode);
|
|
464
|
+
}
|
|
465
|
+
catch {
|
|
466
|
+
matches = [];
|
|
467
|
+
}
|
|
468
|
+
rawIncludes = [];
|
|
469
|
+
extractionSource = 'tree_sitter';
|
|
470
|
+
for (const match of matches) {
|
|
471
|
+
const sourceNode = match.captures.find((c) => c.name === 'import.source');
|
|
472
|
+
if (!sourceNode)
|
|
473
|
+
continue;
|
|
474
|
+
const rawText = sourceNode.node.text;
|
|
475
|
+
if (isAngleBracketInclude(rawText))
|
|
476
|
+
continue;
|
|
477
|
+
const cleaned = rawText.replace(/['"<>]/g, '');
|
|
478
|
+
if (cleaned && cleaned.length <= 2048)
|
|
479
|
+
rawIncludes.push(cleaned);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
catch {
|
|
483
|
+
// tree-sitter failed (e.g. file > 32 KB) — fall back to regex.
|
|
484
|
+
// Strip block comments first so we don't emit a consumer contract
|
|
485
|
+
// for a commented-out #include (PR #1156 review finding #5).
|
|
486
|
+
rawIncludes = [];
|
|
487
|
+
extractionSource = 'regex_fallback';
|
|
488
|
+
const scanTarget = stripBlockComments(content);
|
|
489
|
+
INCLUDE_REGEX.lastIndex = 0;
|
|
490
|
+
let m;
|
|
491
|
+
while ((m = INCLUDE_REGEX.exec(scanTarget)) !== null) {
|
|
492
|
+
if (m[1] && m[1].length <= 2048)
|
|
493
|
+
rawIncludes.push(m[1]);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
for (const cleaned of rawIncludes) {
|
|
497
|
+
// Filter: skip known system headers and system path prefixes
|
|
498
|
+
if (isSystemHeader(cleaned))
|
|
499
|
+
continue;
|
|
500
|
+
// Skip relative-up includes: `#include "../include/foo.h"` is
|
|
501
|
+
// almost always an intra-repo reference. The suffix index is built
|
|
502
|
+
// from repo-relative paths, so isLocalInclude can never match
|
|
503
|
+
// `../foo.h`, and emitting it as a consumer contract just pollutes
|
|
504
|
+
// the registry with an entry no provider can ever satisfy.
|
|
505
|
+
// (PR #1156 follow-up review: `../` relative includes produce
|
|
506
|
+
// spurious consumer contracts.)
|
|
507
|
+
if (cleaned.startsWith('../') || cleaned.startsWith('..\\'))
|
|
508
|
+
continue;
|
|
509
|
+
// Skip macro-style includes: `#include PLATFORM_HEADER` parses as an
|
|
510
|
+
// identifier under tree-sitter's `(_) @import.source` wildcard. The
|
|
511
|
+
// identifier text passes the strip/clean step unchanged, so without
|
|
512
|
+
// this guard we would emit `include::platform_header` as a consumer
|
|
513
|
+
// contract — and no provider in any repo will ever expose a contract
|
|
514
|
+
// for a macro identifier (no file is named `PLATFORM_HEADER`). The
|
|
515
|
+
// contract would sit permanently orphaned in the registry. Real
|
|
516
|
+
// header references always contain a path separator (`/`, `\`) or an
|
|
517
|
+
// extension dot (`foo.h`), so an absent both is a reliable signal we
|
|
518
|
+
// are looking at a macro identifier. (PR #1156 follow-up review:
|
|
519
|
+
// macro includes emit orphaned consumer contracts.)
|
|
520
|
+
if (!/[./\\]/.test(cleaned))
|
|
521
|
+
continue;
|
|
522
|
+
// Local resolution (PR #1156 review finding #4): only accept an
|
|
523
|
+
// exact-suffix match on the *full* include path. The generic
|
|
524
|
+
// suffixResolve() iterates all truncated suffixes, which would
|
|
525
|
+
// silently suppress a cross-repo `#include "map/base/view.h"`
|
|
526
|
+
// when the local repo has any `internal/view.h` — a realistic
|
|
527
|
+
// false-negative in large C++ codebases. Here we only resolve
|
|
528
|
+
// locally if a file path ends with the complete include string
|
|
529
|
+
// (optionally re-appending one of the C/C++ header extensions
|
|
530
|
+
// when the include already omits it).
|
|
531
|
+
if (isLocalInclude(cleaned, suffixIndex))
|
|
532
|
+
continue;
|
|
533
|
+
// Unresolved: emit as consumer contract
|
|
534
|
+
const normalizedRel = rel.replace(/\\/g, '/');
|
|
535
|
+
out.push({
|
|
536
|
+
contractId: `include::${normalizeIncludePath(cleaned)}`,
|
|
537
|
+
type: 'include',
|
|
538
|
+
role: 'consumer',
|
|
539
|
+
symbolUid: `File:${normalizedRel}`,
|
|
540
|
+
symbolRef: { filePath: normalizedRel, name: cleaned },
|
|
541
|
+
symbolName: cleaned,
|
|
542
|
+
confidence: 0.85,
|
|
543
|
+
meta: {
|
|
544
|
+
source: extractionSource,
|
|
545
|
+
includePath: cleaned,
|
|
546
|
+
},
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
return out;
|
|
551
|
+
}
|
|
552
|
+
// ---------- deduplication ----------
|
|
553
|
+
dedupe(items) {
|
|
554
|
+
const seen = new Set();
|
|
555
|
+
const out = [];
|
|
556
|
+
for (const c of items) {
|
|
557
|
+
const k = `${c.contractId}|${c.role}|${c.symbolRef.filePath}`;
|
|
558
|
+
if (seen.has(k))
|
|
559
|
+
continue;
|
|
560
|
+
seen.add(k);
|
|
561
|
+
out.push(c);
|
|
562
|
+
}
|
|
563
|
+
return out;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
@@ -221,6 +221,12 @@ export class ManifestExtractor {
|
|
|
221
221
|
rows = await executor(`MATCH (n:Package|Module) WHERE n.name = $contract
|
|
222
222
|
RETURN n.id AS uid, n.name AS name, n.filePath AS filePath
|
|
223
223
|
ORDER BY n.filePath ASC
|
|
224
|
+
LIMIT 1`, { contract: link.contract });
|
|
225
|
+
}
|
|
226
|
+
else if (link.type === 'include') {
|
|
227
|
+
rows = await executor(`MATCH (f:File) WHERE f.filePath = $contract
|
|
228
|
+
RETURN f.id AS uid, f.name AS name, f.filePath AS filePath
|
|
229
|
+
ORDER BY f.filePath ASC
|
|
224
230
|
LIMIT 1`, { contract: link.contract });
|
|
225
231
|
}
|
|
226
232
|
else if (link.type === 'custom') {
|
|
@@ -303,6 +309,8 @@ export class ManifestExtractor {
|
|
|
303
309
|
return `lib::${contract}`;
|
|
304
310
|
case 'custom':
|
|
305
311
|
return `custom::${contract}`;
|
|
312
|
+
case 'include':
|
|
313
|
+
return `include::${contract}`;
|
|
306
314
|
default: {
|
|
307
315
|
const _exhaustive = type;
|
|
308
316
|
throw new Error(`Unhandled ContractType: ${String(_exhaustive)}`);
|
|
@@ -155,6 +155,10 @@ export async function buildThriftContext(repoPath) {
|
|
|
155
155
|
cwd: repoPath,
|
|
156
156
|
absolute: false,
|
|
157
157
|
nodir: true,
|
|
158
|
+
// TODO(#1156-followup): replace this hand-rolled list with createIgnoreFilter
|
|
159
|
+
// (the canonical ingestion ignore filter, like include-extractor.ts now uses).
|
|
160
|
+
// New entries to DEFAULT_IGNORE_LIST in src/config/ignore-service.ts (e.g.
|
|
161
|
+
// third_party, 3rdparty added in commit a9936a9b) silently do not apply here.
|
|
158
162
|
ignore: ['**/node_modules/**', '**/.git/**', '**/vendor/**', '**/dist/**', '**/build/**'],
|
|
159
163
|
});
|
|
160
164
|
const namespacesByThrift = new Map();
|
|
@@ -207,6 +211,10 @@ export class ThriftExtractor {
|
|
|
207
211
|
cwd: repoPath,
|
|
208
212
|
absolute: false,
|
|
209
213
|
nodir: true,
|
|
214
|
+
// TODO(#1156-followup): replace this hand-rolled list with createIgnoreFilter
|
|
215
|
+
// (the canonical ingestion ignore filter, like include-extractor.ts now uses).
|
|
216
|
+
// New entries to DEFAULT_IGNORE_LIST in src/config/ignore-service.ts (e.g.
|
|
217
|
+
// third_party, 3rdparty added in commit a9936a9b) silently do not apply here.
|
|
210
218
|
ignore: ['**/node_modules/**', '**/.git/**', '**/vendor/**', '**/dist/**', '**/build/**'],
|
|
211
219
|
});
|
|
212
220
|
const parser = new Parser();
|
|
@@ -93,6 +93,8 @@ export function normalizeContractId(id) {
|
|
|
93
93
|
return `topic::${rest.trim().toLowerCase()}`;
|
|
94
94
|
case 'lib':
|
|
95
95
|
return `lib::${rest.toLowerCase()}`;
|
|
96
|
+
case 'include':
|
|
97
|
+
return `include::${rest.replace(/\\/g, '/').replace(/^\.\//, '').replace(/\/+/g, '/').toLowerCase()}`;
|
|
96
98
|
default:
|
|
97
99
|
return id;
|
|
98
100
|
}
|
|
@@ -3,6 +3,7 @@ import * as fsp from 'node:fs/promises';
|
|
|
3
3
|
import * as path from 'node:path';
|
|
4
4
|
import * as os from 'node:os';
|
|
5
5
|
import { randomBytes } from 'node:crypto';
|
|
6
|
+
import { retryRename } from './bridge-db.js';
|
|
6
7
|
/**
|
|
7
8
|
* Build an unpredictable suffix for atomic-write tmp files. Replaces the
|
|
8
9
|
* previous `Date.now()` pattern which CodeQL flagged as
|
|
@@ -45,7 +46,13 @@ export async function writeContractRegistry(groupDir, registry) {
|
|
|
45
46
|
finally {
|
|
46
47
|
await handle.close();
|
|
47
48
|
}
|
|
48
|
-
|
|
49
|
+
// retryRename absorbs the documented Windows EPERM/EBUSY/EACCES race that
|
|
50
|
+
// fires when AV scanners or another concurrent rename briefly hold the
|
|
51
|
+
// destination handle between rename calls. Same helper bridge-db.ts uses
|
|
52
|
+
// (lines 304, 583, 587, 595, 605, 677) for the bridge.lbug atomic swap —
|
|
53
|
+
// single source of truth for the Windows-rename pattern across the group
|
|
54
|
+
// package.
|
|
55
|
+
await retryRename(tmpPath, targetPath);
|
|
49
56
|
}
|
|
50
57
|
export async function readContractRegistry(groupDir) {
|
|
51
58
|
const filePath = path.join(groupDir, CONTRACTS_FILE);
|