gitnexus 1.6.11-rc.2 → 1.6.11-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 +43 -1
- package/dist/_shared/impact-risk.d.ts +37 -0
- package/dist/_shared/impact-risk.d.ts.map +1 -0
- package/dist/_shared/impact-risk.js +92 -0
- package/dist/_shared/impact-risk.js.map +1 -0
- package/dist/_shared/index.d.ts +2 -0
- package/dist/_shared/index.d.ts.map +1 -1
- package/dist/_shared/index.js +2 -0
- package/dist/_shared/index.js.map +1 -1
- package/dist/cli/ai-context.js +4 -4
- package/dist/cli/analyze-config.d.ts +2 -0
- package/dist/cli/analyze-config.js +16 -0
- package/dist/cli/analyze-options.d.ts +4 -0
- package/dist/cli/analyze.d.ts +17 -0
- package/dist/cli/analyze.js +43 -7
- package/dist/cli/eval-server.js +22 -5
- package/dist/cli/group.js +7 -1
- package/dist/cli/help-i18n.js +2 -0
- package/dist/cli/i18n/en.d.ts +12 -2
- package/dist/cli/i18n/en.js +12 -2
- package/dist/cli/i18n/resources.d.ts +22 -2
- package/dist/cli/i18n/zh-CN.d.ts +10 -0
- package/dist/cli/i18n/zh-CN.js +12 -2
- package/dist/cli/index.js +11 -4
- package/dist/cli/status.js +91 -6
- package/dist/cli/watch-queue.d.ts +41 -0
- package/dist/cli/watch-queue.js +184 -0
- package/dist/cli/watch.d.ts +20 -0
- package/dist/cli/watch.js +372 -0
- package/dist/cli/wiki.js +15 -2
- package/dist/config/ignore-service.d.ts +11 -0
- package/dist/config/ignore-service.js +41 -4
- package/dist/config/repo-control-file.d.ts +3 -0
- package/dist/config/repo-control-file.js +115 -0
- package/dist/core/group/config-parser.js +20 -2
- package/dist/core/group/cross-impact.d.ts +2 -1
- package/dist/core/group/cross-impact.js +33 -2
- package/dist/core/group/extractors/fs-utils.d.ts +2 -0
- package/dist/core/group/extractors/fs-utils.js +86 -0
- package/dist/core/group/extractors/graphql-extractor.d.ts +17 -0
- package/dist/core/group/extractors/graphql-extractor.js +652 -0
- package/dist/core/group/extractors/java-workspace-extractor.js +244 -31
- package/dist/core/group/extractors/manifest-extractor.d.ts +1 -1
- package/dist/core/group/extractors/manifest-extractor.js +1 -1
- package/dist/core/group/matching.js +8 -1
- package/dist/core/group/service.js +5 -1
- package/dist/core/group/storage.js +1 -0
- package/dist/core/group/sync.d.ts +3 -1
- package/dist/core/group/sync.js +43 -5
- package/dist/core/group/types.d.ts +19 -5
- package/dist/core/incremental/derived-writeback.d.ts +36 -0
- package/dist/core/incremental/derived-writeback.js +68 -0
- package/dist/core/incremental/subgraph-extract.d.ts +6 -4
- package/dist/core/incremental/subgraph-extract.js +7 -5
- package/dist/core/index-content-drift.d.ts +54 -0
- package/dist/core/index-content-drift.js +127 -0
- package/dist/core/ingestion/filesystem-walker.d.ts +15 -5
- package/dist/core/ingestion/filesystem-walker.js +20 -3
- package/dist/core/ingestion/frameworks/spring/dynamic-lookups.d.ts +22 -0
- package/dist/core/ingestion/frameworks/spring/dynamic-lookups.js +124 -0
- package/dist/core/ingestion/language-provider.d.ts +43 -0
- package/dist/core/ingestion/languages/csharp/razor-view-components.d.ts +62 -0
- package/dist/core/ingestion/languages/csharp/razor-view-components.js +954 -0
- package/dist/core/ingestion/languages/csharp/resolution-config.d.ts +3 -0
- package/dist/core/ingestion/languages/csharp/resolution-config.js +6 -1
- package/dist/core/ingestion/languages/csharp/scope-resolver.js +8 -0
- package/dist/core/ingestion/languages/java/capture-side-channel.d.ts +4 -0
- package/dist/core/ingestion/languages/java/capture-side-channel.js +16 -0
- package/dist/core/ingestion/languages/java/captures.js +14 -1
- package/dist/core/ingestion/languages/java/lombok-synthesizer.d.ts +42 -0
- package/dist/core/ingestion/languages/java/lombok-synthesizer.js +439 -0
- package/dist/core/ingestion/languages/java/scope-resolver.js +2 -0
- package/dist/core/ingestion/languages/java/spring-dynamic-lookup.d.ts +8 -0
- package/dist/core/ingestion/languages/java/spring-dynamic-lookup.js +62 -0
- package/dist/core/ingestion/languages/java.js +2 -0
- package/dist/core/ingestion/languages/jvm/accessor-synthesis.d.ts +99 -0
- package/dist/core/ingestion/languages/jvm/accessor-synthesis.js +173 -0
- package/dist/core/ingestion/languages/jvm/beanspec.d.ts +17 -0
- package/dist/core/ingestion/languages/jvm/beanspec.js +42 -0
- package/dist/core/ingestion/languages/kotlin/capture-side-channel.d.ts +5 -0
- package/dist/core/ingestion/languages/kotlin/capture-side-channel.js +16 -0
- package/dist/core/ingestion/languages/kotlin/captures.js +14 -1
- package/dist/core/ingestion/languages/kotlin/lombok-synthesizer.d.ts +26 -0
- package/dist/core/ingestion/languages/kotlin/lombok-synthesizer.js +417 -0
- package/dist/core/ingestion/languages/kotlin/scope-resolver.js +2 -0
- package/dist/core/ingestion/languages/kotlin/spring-dynamic-lookup.d.ts +8 -0
- package/dist/core/ingestion/languages/kotlin/spring-dynamic-lookup.js +77 -0
- package/dist/core/ingestion/languages/kotlin.js +2 -0
- package/dist/core/ingestion/pipeline-phases/di.js +47 -14
- package/dist/core/ingestion/pipeline-phases/parse-impl.d.ts +3 -1
- package/dist/core/ingestion/pipeline-phases/parse-impl.js +57 -86
- package/dist/core/ingestion/pipeline-phases/parse.d.ts +2 -0
- package/dist/core/ingestion/pipeline-phases/runner.d.ts +4 -1
- package/dist/core/ingestion/pipeline-phases/runner.js +34 -14
- package/dist/core/ingestion/pipeline-phases/scan.js +25 -13
- package/dist/core/ingestion/pipeline.d.ts +6 -0
- package/dist/core/ingestion/pipeline.js +44 -12
- package/dist/core/ingestion/scope-extractor.js +1 -0
- package/dist/core/ingestion/utils/symbol-labels.d.ts +2 -2
- package/dist/core/ingestion/utils/symbol-labels.js +2 -2
- package/dist/core/ingestion/workers/parse-worker.js +28 -2
- package/dist/core/lbug/lbug-adapter.d.ts +59 -0
- package/dist/core/lbug/lbug-adapter.js +154 -1
- package/dist/core/run-analyze.d.ts +17 -0
- package/dist/core/run-analyze.js +231 -23
- package/dist/core/search/fts-indexes.d.ts +19 -1
- package/dist/core/search/fts-indexes.js +28 -1
- package/dist/core/wiki/generator.js +8 -0
- package/dist/core/wiki/grok-client.d.ts +21 -0
- package/dist/core/wiki/grok-client.js +287 -0
- package/dist/core/wiki/llm-client.d.ts +1 -1
- package/dist/core/wiki/llm-client.js +5 -2
- package/dist/core/wiki/local-cli-client.d.ts +11 -0
- package/dist/core/wiki/local-cli-client.js +22 -9
- package/dist/mcp/local/local-backend.d.ts +24 -7
- package/dist/mcp/local/local-backend.js +190 -81
- package/dist/mcp/local/pdg-impact.d.ts +8 -4
- package/dist/mcp/local/pdg-impact.js +7 -2
- package/dist/mcp/repository-policy.d.ts +5 -1
- package/dist/mcp/repository-policy.js +48 -4
- package/dist/mcp/resources.js +2 -1
- package/dist/mcp/server.js +6 -5
- package/dist/mcp/tools.js +31 -19
- package/dist/server/api.js +23 -64
- package/dist/server/grep-params.d.ts +18 -0
- package/dist/server/grep-params.js +83 -0
- package/dist/server/grep-scan.d.ts +23 -0
- package/dist/server/grep-scan.js +107 -0
- package/dist/server/grep-worker.d.ts +1 -0
- package/dist/server/grep-worker.js +12 -0
- package/dist/server/mcp-http.d.ts +8 -0
- package/dist/server/mcp-http.js +16 -1
- package/dist/storage/file-hash.d.ts +5 -0
- package/dist/storage/file-hash.js +16 -6
- package/dist/storage/fs-atomic.d.ts +24 -0
- package/dist/storage/fs-atomic.js +86 -2
- package/dist/storage/git.d.ts +19 -8
- package/dist/storage/git.js +83 -24
- package/dist/storage/gitnexus-managed-paths.d.ts +36 -0
- package/dist/storage/gitnexus-managed-paths.js +46 -0
- package/dist/storage/parse-cache.d.ts +22 -4
- package/dist/storage/parse-cache.js +106 -29
- package/dist/storage/parsedfile-store.d.ts +34 -60
- package/dist/storage/parsedfile-store.js +177 -171
- package/dist/storage/repo-manager.d.ts +11 -1
- package/dist/storage/repo-manager.js +23 -2
- package/dist/storage/repo-meta.d.ts +12 -0
- package/dist/storage/v8-sidecar.d.ts +48 -0
- package/dist/storage/v8-sidecar.js +347 -0
- package/dist/types/pipeline.d.ts +14 -0
- package/package.json +4 -1
- package/scripts/cross-platform-shard.ts +4 -2
- package/scripts/cross-platform-tests.ts +7 -1
- package/skills/gitnexus-cli.md +11 -3
- package/skills/gitnexus-impact-analysis.md +9 -0
- package/web/assets/{agent-Dr4l5EOp.js → agent-CFqT4hjR.js} +108 -104
- package/web/assets/{index-2zdvEdzg.js → index-BMIniRtX.js} +3 -3
- package/web/index.html +1 -1
|
@@ -1,7 +1,20 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { XMLParser } from 'fast-xml-parser';
|
|
3
4
|
import { shouldIgnorePath, loadIgnoreRules } from '../../../config/ignore-service.js';
|
|
4
5
|
import { logger } from '../../logger.js';
|
|
6
|
+
// POMs are static metadata. Parse hierarchy with a real XML parser, but do not
|
|
7
|
+
// invoke Maven or resolve the effective model. Properties, profiles, and remote
|
|
8
|
+
// parent resolution remain outside this extractor's deterministic boundary.
|
|
9
|
+
const pomParser = new XMLParser({
|
|
10
|
+
ignoreAttributes: true,
|
|
11
|
+
removeNSPrefix: true,
|
|
12
|
+
trimValues: true,
|
|
13
|
+
parseTagValue: false,
|
|
14
|
+
processEntities: false,
|
|
15
|
+
ignoreDeclaration: true,
|
|
16
|
+
ignorePiTags: true,
|
|
17
|
+
});
|
|
5
18
|
async function parseJavaManifest(repoPath) {
|
|
6
19
|
const pomPath = path.join(repoPath, 'pom.xml');
|
|
7
20
|
try {
|
|
@@ -9,13 +22,14 @@ async function parseJavaManifest(repoPath) {
|
|
|
9
22
|
return parsePom(content);
|
|
10
23
|
}
|
|
11
24
|
catch {
|
|
12
|
-
// fall through to Gradle
|
|
25
|
+
// Missing pom.xml — fall through to Gradle.
|
|
13
26
|
}
|
|
27
|
+
const gradleSidecars = await readGradleSidecars(repoPath);
|
|
14
28
|
for (const name of ['build.gradle.kts', 'build.gradle']) {
|
|
15
29
|
const gradlePath = path.join(repoPath, name);
|
|
16
30
|
try {
|
|
17
31
|
const content = await fs.readFile(gradlePath, 'utf-8');
|
|
18
|
-
return parseGradle(content, repoPath);
|
|
32
|
+
return parseGradle(content, repoPath, gradleSidecars);
|
|
19
33
|
}
|
|
20
34
|
catch {
|
|
21
35
|
continue;
|
|
@@ -23,45 +37,244 @@ async function parseJavaManifest(repoPath) {
|
|
|
23
37
|
}
|
|
24
38
|
return null;
|
|
25
39
|
}
|
|
40
|
+
async function readIfPresent(filePath) {
|
|
41
|
+
try {
|
|
42
|
+
return await fs.readFile(filePath, 'utf-8');
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
async function readGradleSidecars(repoPath) {
|
|
49
|
+
const [properties, settingsKts, settingsGroovy, catalog] = await Promise.all([
|
|
50
|
+
readIfPresent(path.join(repoPath, 'gradle.properties')),
|
|
51
|
+
readIfPresent(path.join(repoPath, 'settings.gradle.kts')),
|
|
52
|
+
readIfPresent(path.join(repoPath, 'settings.gradle')),
|
|
53
|
+
readIfPresent(path.join(repoPath, 'gradle', 'libs.versions.toml')),
|
|
54
|
+
]);
|
|
55
|
+
const sidecars = {
|
|
56
|
+
catalogLibraries: new Map(),
|
|
57
|
+
catalogBundles: new Map(),
|
|
58
|
+
};
|
|
59
|
+
const groupMatch = properties?.match(/(?:^|\n)\s*group\s*=\s*([^\s#]+)/);
|
|
60
|
+
if (groupMatch)
|
|
61
|
+
sidecars.propertiesGroup = groupMatch[1];
|
|
62
|
+
const settings = settingsKts ?? settingsGroovy;
|
|
63
|
+
const nameMatch = settings?.match(/rootProject\.name\s*=\s*['"]([^'"]+)['"]/);
|
|
64
|
+
if (nameMatch)
|
|
65
|
+
sidecars.rootProjectName = nameMatch[1];
|
|
66
|
+
if (catalog) {
|
|
67
|
+
const parsed = parseGradleVersionCatalog(catalog);
|
|
68
|
+
sidecars.catalogLibraries = parsed.libraries;
|
|
69
|
+
sidecars.catalogBundles = parsed.bundles;
|
|
70
|
+
}
|
|
71
|
+
return sidecars;
|
|
72
|
+
}
|
|
73
|
+
function catalogAccessors(alias) {
|
|
74
|
+
const dotted = alias.replace(/[-_]/g, '.');
|
|
75
|
+
const camel = alias.replace(/[-_]+([A-Za-z0-9])/g, (_, char) => char.toUpperCase());
|
|
76
|
+
return [...new Set([alias, dotted, camel])];
|
|
77
|
+
}
|
|
78
|
+
function projectAccessorToArtifactId(accessor) {
|
|
79
|
+
const last = accessor.split('.').pop();
|
|
80
|
+
return last.replace(/[A-Z]/g, (char) => `-${char.toLowerCase()}`).replace(/^-/, '');
|
|
81
|
+
}
|
|
82
|
+
function moduleToGa(module) {
|
|
83
|
+
const parts = module.split(':');
|
|
84
|
+
return parts.length >= 2 ? `${parts[0]}:${parts[1]}` : undefined;
|
|
85
|
+
}
|
|
86
|
+
function parseInlineTomlTable(rhs) {
|
|
87
|
+
const fields = {};
|
|
88
|
+
for (const match of rhs.matchAll(/([A-Za-z0-9_-]+)\s*=\s*['"]([^'"]+)['"]/g)) {
|
|
89
|
+
fields[match[1]] = match[2];
|
|
90
|
+
}
|
|
91
|
+
return fields;
|
|
92
|
+
}
|
|
93
|
+
/** Default Gradle catalog (`gradle/libs.versions.toml`) — aliases only, no version resolution. */
|
|
94
|
+
function parseGradleVersionCatalog(toml) {
|
|
95
|
+
const libraries = new Map();
|
|
96
|
+
const bundles = new Map();
|
|
97
|
+
let section = 'other';
|
|
98
|
+
const addLibrary = (alias, ga) => {
|
|
99
|
+
for (const accessor of catalogAccessors(alias))
|
|
100
|
+
libraries.set(accessor, ga);
|
|
101
|
+
};
|
|
102
|
+
for (const raw of toml.split(/\r?\n/)) {
|
|
103
|
+
const line = raw.replace(/#.*$/, '').trim();
|
|
104
|
+
if (!line)
|
|
105
|
+
continue;
|
|
106
|
+
const header = line.match(/^\[([^\]]+)\]$/);
|
|
107
|
+
if (header) {
|
|
108
|
+
const name = header[1];
|
|
109
|
+
section =
|
|
110
|
+
name === 'libraries' || name.endsWith('.libraries')
|
|
111
|
+
? 'libraries'
|
|
112
|
+
: name === 'bundles' || name.endsWith('.bundles')
|
|
113
|
+
? 'bundles'
|
|
114
|
+
: 'other';
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (section === 'libraries') {
|
|
118
|
+
const dottedModule = line.match(/^([A-Za-z0-9._-]+)\.module\s*=\s*['"]([^'"]+)['"]$/);
|
|
119
|
+
if (dottedModule) {
|
|
120
|
+
const ga = moduleToGa(dottedModule[2]);
|
|
121
|
+
if (ga)
|
|
122
|
+
addLibrary(dottedModule[1], ga);
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
const assignment = line.match(/^([A-Za-z0-9._-]+)\s*=\s*(.+)$/);
|
|
126
|
+
if (!assignment)
|
|
127
|
+
continue;
|
|
128
|
+
const alias = assignment[1];
|
|
129
|
+
const rhs = assignment[2].trim();
|
|
130
|
+
const quoted = rhs.match(/^['"]([^'"]+)['"]$/);
|
|
131
|
+
if (quoted) {
|
|
132
|
+
const ga = moduleToGa(quoted[1]);
|
|
133
|
+
if (ga)
|
|
134
|
+
addLibrary(alias, ga);
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const table = parseInlineTomlTable(rhs);
|
|
138
|
+
const ga = table.module
|
|
139
|
+
? moduleToGa(table.module)
|
|
140
|
+
: table.group && table.name
|
|
141
|
+
? `${table.group}:${table.name}`
|
|
142
|
+
: undefined;
|
|
143
|
+
if (ga)
|
|
144
|
+
addLibrary(alias, ga);
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
if (section === 'bundles') {
|
|
148
|
+
const assignment = line.match(/^([A-Za-z0-9._-]+)\s*=\s*\[([^\]]*)\]$/);
|
|
149
|
+
if (!assignment)
|
|
150
|
+
continue;
|
|
151
|
+
const members = [...assignment[2].matchAll(/['"]([^'"]+)['"]/g)].map((match) => match[1]);
|
|
152
|
+
for (const accessor of catalogAccessors(assignment[1]))
|
|
153
|
+
bundles.set(accessor, members);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return { libraries, bundles };
|
|
157
|
+
}
|
|
158
|
+
const GRADLE_GROUP_PATTERNS = [
|
|
159
|
+
/(?:^|[\n{;])\s*(?:rootProject\.)?group\s*=\s*['"]([^'"]+)['"]/,
|
|
160
|
+
/(?:^|[\n{;])\s*group\s+['"]([^'"]+)['"]/,
|
|
161
|
+
];
|
|
162
|
+
const GRADLE_COORD_CONFIGS = 'implementation|api|compileOnly|runtimeOnly|testImplementation|testApi|testCompileOnly|compile|kapt|ksp|commonMainImplementation|commonMainApi';
|
|
163
|
+
const CATALOG_ALIAS = '([A-Za-z0-9_]+(?:\\.[A-Za-z0-9_]+)*)(?:\\.get\\(\\)|\\.asProvider\\(\\))?';
|
|
164
|
+
function gradleDepRe(suffix) {
|
|
165
|
+
return new RegExp(`(?:${GRADLE_COORD_CONFIGS})\\s*${suffix}`, 'g');
|
|
166
|
+
}
|
|
167
|
+
function parseGradleGroup(content) {
|
|
168
|
+
for (const pattern of GRADLE_GROUP_PATTERNS) {
|
|
169
|
+
const match = content.match(pattern);
|
|
170
|
+
if (match?.[1])
|
|
171
|
+
return match[1];
|
|
172
|
+
}
|
|
173
|
+
return undefined;
|
|
174
|
+
}
|
|
175
|
+
function asXmlNode(value) {
|
|
176
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
177
|
+
? value
|
|
178
|
+
: undefined;
|
|
179
|
+
}
|
|
180
|
+
function xmlText(value) {
|
|
181
|
+
if (typeof value === 'string' || typeof value === 'number') {
|
|
182
|
+
const text = String(value).trim();
|
|
183
|
+
return text || undefined;
|
|
184
|
+
}
|
|
185
|
+
const nested = asXmlNode(value)?.['#text'];
|
|
186
|
+
if (nested === undefined)
|
|
187
|
+
return undefined;
|
|
188
|
+
return xmlText(nested);
|
|
189
|
+
}
|
|
190
|
+
function xmlChildText(node, name) {
|
|
191
|
+
return node ? xmlText(node[name]) : undefined;
|
|
192
|
+
}
|
|
193
|
+
function asList(value) {
|
|
194
|
+
if (value === undefined || value === null)
|
|
195
|
+
return [];
|
|
196
|
+
return Array.isArray(value) ? value : [value];
|
|
197
|
+
}
|
|
198
|
+
/** Direct project dependencies only — not BOM, profiles, or plugin classpath. */
|
|
199
|
+
function collectProjectDependencies(project, deps) {
|
|
200
|
+
const dependencies = asXmlNode(project.dependencies);
|
|
201
|
+
if (!dependencies)
|
|
202
|
+
return;
|
|
203
|
+
for (const dep of asList(dependencies.dependency)) {
|
|
204
|
+
const depNode = asXmlNode(dep);
|
|
205
|
+
const groupId = xmlChildText(depNode, 'groupId');
|
|
206
|
+
const artifactId = xmlChildText(depNode, 'artifactId');
|
|
207
|
+
if (groupId && artifactId)
|
|
208
|
+
deps.push(`${groupId}:${artifactId}`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
26
211
|
function parsePom(content) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
212
|
+
let parsed;
|
|
213
|
+
try {
|
|
214
|
+
// parseSourceSafe guards tree-sitter's Windows SIGSEGV by switching to a
|
|
215
|
+
// chunked input callback above 16 KB; XMLParser only accepts XML text, so
|
|
216
|
+
// routing POMs through it silently yields an empty document.
|
|
217
|
+
// eslint-disable-next-line gitnexus/require-safe-parse
|
|
218
|
+
parsed = pomParser.parse(content);
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
30
221
|
return null;
|
|
31
|
-
const groupId = projectGroupMatch[1].trim();
|
|
32
|
-
const artifactId = projectArtifactMatch[1].trim();
|
|
33
|
-
const deps = [];
|
|
34
|
-
const depBlocks = content.matchAll(/<dependency>\s*([\s\S]*?)<\/dependency>/g);
|
|
35
|
-
for (const block of depBlocks) {
|
|
36
|
-
const gMatch = block[1].match(/<groupId>([^<]+)<\/groupId>/);
|
|
37
|
-
const aMatch = block[1].match(/<artifactId>([^<]+)<\/artifactId>/);
|
|
38
|
-
if (gMatch && aMatch) {
|
|
39
|
-
deps.push(`${gMatch[1].trim()}:${aMatch[1].trim()}`);
|
|
40
|
-
}
|
|
41
222
|
}
|
|
223
|
+
const project = asXmlNode(asXmlNode(parsed)?.project);
|
|
224
|
+
if (!project)
|
|
225
|
+
return null;
|
|
226
|
+
// Maven inherits groupId from <parent>, but artifactId is always the
|
|
227
|
+
// project's own direct child and must never fall back to parent.artifactId.
|
|
228
|
+
const groupId = xmlChildText(project, 'groupId') ?? xmlChildText(asXmlNode(project.parent), 'groupId');
|
|
229
|
+
const artifactId = xmlChildText(project, 'artifactId');
|
|
230
|
+
if (!groupId || !artifactId)
|
|
231
|
+
return null;
|
|
232
|
+
const deps = [];
|
|
233
|
+
collectProjectDependencies(project, deps);
|
|
42
234
|
return { groupId, artifactId, deps: [...new Set(deps)] };
|
|
43
235
|
}
|
|
44
|
-
function parseGradle(content, repoPath) {
|
|
45
|
-
|
|
46
|
-
const
|
|
47
|
-
const groupId = groupMatch ? groupMatch[1] : '';
|
|
236
|
+
function parseGradle(content, repoPath, sidecars = { catalogLibraries: new Map(), catalogBundles: new Map() }) {
|
|
237
|
+
// Static text + default catalog file. Do not execute Gradle.
|
|
238
|
+
const groupId = parseGradleGroup(content) ?? sidecars.propertiesGroup ?? '';
|
|
48
239
|
if (!groupId)
|
|
49
240
|
return null;
|
|
50
|
-
const artifactId =
|
|
241
|
+
const artifactId = sidecars.rootProjectName ?? path.basename(repoPath);
|
|
242
|
+
const { catalogLibraries, catalogBundles } = sidecars;
|
|
51
243
|
const deps = [];
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
244
|
+
const pushCatalogAlias = (alias) => {
|
|
245
|
+
const ga = catalogLibraries.get(alias);
|
|
246
|
+
if (ga)
|
|
247
|
+
deps.push(ga);
|
|
248
|
+
};
|
|
249
|
+
const namedPattern = gradleDepRe(`(?:\\(\\s*)?(?:group\\s*=\\s*['"](?<group1>[^'"]+)['"]\\s*,\\s*name\\s*=\\s*['"](?<name1>[^'"]+)['"]|name\\s*=\\s*['"](?<name2>[^'"]+)['"]\\s*,\\s*group\\s*=\\s*['"](?<group2>[^'"]+)['"]|group:\\s*['"](?<group3>[^'"]+)['"]\\s*,\\s*name:\\s*['"](?<name3>[^'"]+)['"]|name:\\s*['"](?<name4>[^'"]+)['"]\\s*,\\s*group:\\s*['"](?<group4>[^'"]+)['"])`);
|
|
250
|
+
for (const match of content.matchAll(namedPattern)) {
|
|
251
|
+
const group = match.groups?.group1 ?? match.groups?.group2 ?? match.groups?.group3 ?? match.groups?.group4;
|
|
252
|
+
const name = match.groups?.name1 ?? match.groups?.name2 ?? match.groups?.name3 ?? match.groups?.name4;
|
|
253
|
+
if (group && name)
|
|
254
|
+
deps.push(`${group}:${name}`);
|
|
255
|
+
}
|
|
256
|
+
for (const match of content.matchAll(gradleDepRe(`(?:\\(\\s*)?libs(?:\\.libraries)?\\.(?!bundles\\.|plugins\\.)${CATALOG_ALIAS}`))) {
|
|
257
|
+
pushCatalogAlias(match[1]);
|
|
258
|
+
}
|
|
259
|
+
for (const match of content.matchAll(gradleDepRe(`(?:\\(\\s*)?libs\\.bundles\\.${CATALOG_ALIAS}`))) {
|
|
260
|
+
for (const member of catalogBundles.get(match[1]) ?? []) {
|
|
261
|
+
for (const accessor of catalogAccessors(member))
|
|
262
|
+
pushCatalogAlias(accessor);
|
|
58
263
|
}
|
|
59
264
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
265
|
+
for (const match of content.matchAll(gradleDepRe(`\\(\\s*projects\\.([A-Za-z][A-Za-z0-9.]*)`))) {
|
|
266
|
+
deps.push(`${groupId}:${projectAccessorToArtifactId(match[1])}`);
|
|
267
|
+
}
|
|
268
|
+
for (const match of content.matchAll(gradleDepRe(`(?:\\(\\s*['"]([^'"]+)['"]\\s*\\)|['"]([^'"]+)['"])`))) {
|
|
269
|
+
const coord = match[1] ?? match[2];
|
|
270
|
+
if (!coord)
|
|
271
|
+
continue;
|
|
272
|
+
const parts = coord.split(':');
|
|
273
|
+
if (parts.length >= 2)
|
|
274
|
+
deps.push(`${parts[0]}:${parts[1]}`);
|
|
275
|
+
}
|
|
276
|
+
for (const match of content.matchAll(gradleDepRe(`(?:\\(\\s*)?project\\s*\\(\\s*['"]([^'"]+)['"]\\s*\\)`))) {
|
|
277
|
+
deps.push(`${groupId}:${match[1].replace(/^:/, '')}`);
|
|
65
278
|
}
|
|
66
279
|
return { groupId, artifactId, deps: [...new Set(deps)] };
|
|
67
280
|
}
|
|
@@ -47,7 +47,7 @@ export declare class ManifestExtractor {
|
|
|
47
47
|
* equality matching without requiring wildcard logic downstream.
|
|
48
48
|
*
|
|
49
49
|
* NOTE on exhaustiveness: the switch covers every current
|
|
50
|
-
*
|
|
50
|
+
* manifest-declared contract type and falls through to a `never` assertion so
|
|
51
51
|
* TypeScript fails the build if a new variant is added without a
|
|
52
52
|
* corresponding case.
|
|
53
53
|
*/
|
|
@@ -311,7 +311,7 @@ export class ManifestExtractor {
|
|
|
311
311
|
* equality matching without requiring wildcard logic downstream.
|
|
312
312
|
*
|
|
313
313
|
* NOTE on exhaustiveness: the switch covers every current
|
|
314
|
-
*
|
|
314
|
+
* manifest-declared contract type and falls through to a `never` assertion so
|
|
315
315
|
* TypeScript fails the build if a new variant is added without a
|
|
316
316
|
* corresponding case.
|
|
317
317
|
*/
|
|
@@ -21,7 +21,14 @@ function buildNoisyContractFilter(matchingConfig) {
|
|
|
21
21
|
? new Set(matchingConfig.exclude_links_paths.map((p) => p.replace(/\/+$/, '')))
|
|
22
22
|
: new Set();
|
|
23
23
|
const excludeParamOnly = matchingConfig?.exclude_links_param_only_paths === true;
|
|
24
|
-
return function
|
|
24
|
+
return function isNoisyContract(contractId) {
|
|
25
|
+
if (contractId.startsWith('graphql::')) {
|
|
26
|
+
const parts = contractId.split('::');
|
|
27
|
+
if (parts.length < 3)
|
|
28
|
+
return false;
|
|
29
|
+
const field = parts.slice(2).join('::');
|
|
30
|
+
return excludePaths.has(field) || excludePaths.has(`/${field}`);
|
|
31
|
+
}
|
|
25
32
|
if (!contractId.startsWith('http::'))
|
|
26
33
|
return false;
|
|
27
34
|
const parts = contractId.split('::');
|
|
@@ -301,8 +301,9 @@ export class GroupService {
|
|
|
301
301
|
// group tools never need it — so deferring it here keeps that closure off
|
|
302
302
|
// MCP server startup entirely and off every non-sync group call. The CLI
|
|
303
303
|
// already does exactly this at `cli/group.ts`'s sync command.
|
|
304
|
-
const { syncGroup } = await import('./sync.js');
|
|
304
|
+
const { syncGroup, formatGroupSyncAmbiguousError } = await import('./sync.js');
|
|
305
305
|
const { GroupSyncLockError } = await import('./group-lock.js');
|
|
306
|
+
const { RegistryAmbiguousTargetError } = await import('../../storage/repo-manager.js');
|
|
306
307
|
let result;
|
|
307
308
|
try {
|
|
308
309
|
result = await syncGroup(config, {
|
|
@@ -315,6 +316,9 @@ export class GroupService {
|
|
|
315
316
|
});
|
|
316
317
|
}
|
|
317
318
|
catch (err) {
|
|
319
|
+
if (err instanceof RegistryAmbiguousTargetError) {
|
|
320
|
+
return { error: formatGroupSyncAmbiguousError(err) };
|
|
321
|
+
}
|
|
318
322
|
// Fails closed (R9): this sync could not be protected against a concurrent
|
|
319
323
|
// one, so it did not run and wrote nothing. Return it through the same
|
|
320
324
|
// error channel a missing group uses — NEVER as a success payload of zeroes,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type RegistryEntry } from '../../storage/repo-manager.js';
|
|
1
|
+
import { RegistryAmbiguousTargetError, type RegistryEntry } from '../../storage/repo-manager.js';
|
|
2
2
|
import type { GroupConfig, RepoHandle, RepoSnapshot, StoredContract, CrossLink, GroupManifestLink, MatchType } from './types.js';
|
|
3
3
|
export interface SyncOptions {
|
|
4
4
|
extractorOverride?: ((repo: RepoHandle) => Promise<StoredContract[]>) | (() => Promise<StoredContract[]>);
|
|
@@ -64,6 +64,8 @@ export interface SyncResult {
|
|
|
64
64
|
registryOutcome: RegistryWriteOutcome;
|
|
65
65
|
}
|
|
66
66
|
export declare function stableRepoPoolId(entry: RegistryEntry, allEntries: RegistryEntry[]): string;
|
|
67
|
+
/** Operator copy for group sync — unique `--name`, not a path in yaml. */
|
|
68
|
+
export declare function formatGroupSyncAmbiguousError(err: RegistryAmbiguousTargetError): string;
|
|
67
69
|
/** A batch of manifest links whose referenced in-group repos fit one resident window. */
|
|
68
70
|
export interface ManifestWindow {
|
|
69
71
|
links: GroupManifestLink[];
|
package/dist/core/group/sync.js
CHANGED
|
@@ -2,8 +2,9 @@ import fs from 'node:fs/promises';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { Buffer } from 'node:buffer';
|
|
4
4
|
import { initLbug, executeParameterized, pinRepo, getMaxResidentRepos, } from '../lbug/pool-adapter.js';
|
|
5
|
-
import { readRegistry, readRegistryStrict, } from '../../storage/repo-manager.js';
|
|
5
|
+
import { findRegistryEntryByName, canonicalizePath, registryPathEquals, readRegistry, readRegistryStrict, RegistryAmbiguousTargetError, } from '../../storage/repo-manager.js';
|
|
6
6
|
import { HttpRouteExtractor } from './extractors/http-route-extractor.js';
|
|
7
|
+
import { GraphqlExtractor } from './extractors/graphql-extractor.js';
|
|
7
8
|
import { GrpcExtractor } from './extractors/grpc-extractor.js';
|
|
8
9
|
import { ThriftExtractor } from './extractors/thrift-extractor.js';
|
|
9
10
|
import { TopicExtractor } from './extractors/topic-extractor.js';
|
|
@@ -27,9 +28,16 @@ export function stableRepoPoolId(entry, allEntries) {
|
|
|
27
28
|
}
|
|
28
29
|
return base;
|
|
29
30
|
}
|
|
31
|
+
/** Operator copy for group sync — unique `--name`, not a path in yaml. */
|
|
32
|
+
export function formatGroupSyncAmbiguousError(err) {
|
|
33
|
+
const listing = err.matches.map((m) => ` - ${m.path}`).join('\n');
|
|
34
|
+
return (`Multiple registered repos are named "${err.target}":\n${listing}\n` +
|
|
35
|
+
`Give each clone a unique registry name with \`gitnexus analyze --name\`, then re-sync. ` +
|
|
36
|
+
`Do not put a filesystem path in group.yaml.`);
|
|
37
|
+
}
|
|
30
38
|
function defaultResolveHandle(allEntries) {
|
|
31
39
|
return async (registryName, groupPath) => {
|
|
32
|
-
const e = allEntries
|
|
40
|
+
const e = findRegistryEntryByName(allEntries, registryName);
|
|
33
41
|
if (!e)
|
|
34
42
|
return null;
|
|
35
43
|
const poolId = stableRepoPoolId(e, allEntries);
|
|
@@ -135,6 +143,9 @@ export async function syncGroup(config, opts) {
|
|
|
135
143
|
// windowed manifest resolution below (re-init + lease per window). Keyed by
|
|
136
144
|
// group path because manifest links reference repos by group path.
|
|
137
145
|
const repoHandles = new Map();
|
|
146
|
+
// Keep resolved disk paths even when extraction fails and removes the
|
|
147
|
+
// corresponding handle; workspace discovery does not need a readable index.
|
|
148
|
+
const resolvedRepoPaths = new Map();
|
|
138
149
|
// Every eviction lease this sync holds. Window loops release their own leases
|
|
139
150
|
// (bounding residency); this set is the defensive outer-finally sweep —
|
|
140
151
|
// release disposers are idempotent, so double-release is a safe no-op.
|
|
@@ -152,7 +163,13 @@ export async function syncGroup(config, opts) {
|
|
|
152
163
|
registryEntries = await readRegistryStrict();
|
|
153
164
|
const entries = registryEntries;
|
|
154
165
|
const resolve = opts?.resolveRepoHandle ?? defaultResolveHandle(entries);
|
|
166
|
+
if (!opts?.resolveRepoHandle) {
|
|
167
|
+
for (const regName of Object.values(config.repos)) {
|
|
168
|
+
findRegistryEntryByName(entries, regName);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
155
171
|
const httpEx = new HttpRouteExtractor();
|
|
172
|
+
const graphqlEx = new GraphqlExtractor();
|
|
156
173
|
const grpcEx = new GrpcExtractor();
|
|
157
174
|
const thriftEx = new ThriftExtractor();
|
|
158
175
|
const topicEx = new TopicExtractor();
|
|
@@ -163,6 +180,7 @@ export async function syncGroup(config, opts) {
|
|
|
163
180
|
missingRepos.push(groupPath);
|
|
164
181
|
continue;
|
|
165
182
|
}
|
|
183
|
+
resolvedRepoPaths.set(groupPath, handle.repoPath);
|
|
166
184
|
const poolId = handle.id;
|
|
167
185
|
const lbugPath = path.join(handle.storagePath, 'lbug');
|
|
168
186
|
// Staged per repo, not appended straight to `autoContracts`. Extractors
|
|
@@ -181,7 +199,7 @@ export async function syncGroup(config, opts) {
|
|
|
181
199
|
// resolution no longer reuses these executors — it re-inits + leases
|
|
182
200
|
// each repo per window (see windowed resolution below, issue #2189).
|
|
183
201
|
// Record the pool identity so windowed resolution can re-init.
|
|
184
|
-
repoHandles.set(groupPath, { poolId, lbugPath });
|
|
202
|
+
repoHandles.set(groupPath, { poolId, lbugPath, repoPath: handle.repoPath });
|
|
185
203
|
const executor = (query, params) => executeParameterized(poolId, query, params ?? {});
|
|
186
204
|
const boundaries = await detectServiceBoundaries(handle.repoPath);
|
|
187
205
|
if (config.detect.http) {
|
|
@@ -194,6 +212,16 @@ export async function syncGroup(config, opts) {
|
|
|
194
212
|
});
|
|
195
213
|
}
|
|
196
214
|
}
|
|
215
|
+
if (config.detect.graphql === true) {
|
|
216
|
+
const extracted = await graphqlEx.extract(executor, handle.repoPath, handle);
|
|
217
|
+
for (const c of extracted) {
|
|
218
|
+
repoContracts.push({
|
|
219
|
+
...c,
|
|
220
|
+
repo: groupPath,
|
|
221
|
+
service: assignService(c.symbolRef.filePath, boundaries),
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
197
225
|
if (config.detect.grpc) {
|
|
198
226
|
const extracted = await grpcEx.extract(executor, handle.repoPath, handle);
|
|
199
227
|
for (const c of extracted) {
|
|
@@ -244,7 +272,8 @@ export async function syncGroup(config, opts) {
|
|
|
244
272
|
};
|
|
245
273
|
}
|
|
246
274
|
catch {
|
|
247
|
-
const
|
|
275
|
+
const resolvedHandlePath = canonicalizePath(handle.repoPath);
|
|
276
|
+
const e = entries.find((en) => registryPathEquals(canonicalizePath(en.path), resolvedHandlePath));
|
|
248
277
|
repoSnapshots[groupPath] = {
|
|
249
278
|
indexedAt: e?.indexedAt || '',
|
|
250
279
|
lastCommit: e?.lastCommit || '',
|
|
@@ -267,6 +296,8 @@ export async function syncGroup(config, opts) {
|
|
|
267
296
|
autoContracts.push(contract);
|
|
268
297
|
}
|
|
269
298
|
catch (err) {
|
|
299
|
+
if (err instanceof RegistryAmbiguousTargetError)
|
|
300
|
+
throw err;
|
|
270
301
|
// This spans initLbug plus all contract extraction for the repo. The
|
|
271
302
|
// error used to be discarded entirely, so the only trace of (say) a
|
|
272
303
|
// storage-version mismatch was an empty contracts.json and a later
|
|
@@ -297,7 +328,14 @@ export async function syncGroup(config, opts) {
|
|
|
297
328
|
if (!registryEntries)
|
|
298
329
|
registryEntries = await readRegistry();
|
|
299
330
|
for (const [groupPath, regName] of Object.entries(config.repos)) {
|
|
300
|
-
const
|
|
331
|
+
const resolvedPath = resolvedRepoPaths.get(groupPath);
|
|
332
|
+
if (resolvedPath) {
|
|
333
|
+
repoPaths.set(groupPath, resolvedPath);
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
if (opts?.resolveRepoHandle)
|
|
337
|
+
continue;
|
|
338
|
+
const e = findRegistryEntryByName(registryEntries, regName);
|
|
301
339
|
if (e)
|
|
302
340
|
repoPaths.set(groupPath, e.path);
|
|
303
341
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
import type { ImpactRisk, ImpactRiskResult } from '../../_shared/index.js';
|
|
2
|
+
export type ContractType = 'http' | 'graphql' | 'grpc' | 'thrift' | 'topic' | 'lib' | 'custom' | 'include';
|
|
3
|
+
export type ManifestContractType = Exclude<ContractType, 'graphql'>;
|
|
2
4
|
export type MatchType = 'exact' | 'manifest' | 'wildcard';
|
|
3
5
|
export type ContractRole = 'provider' | 'consumer';
|
|
4
6
|
export interface GroupConfig {
|
|
@@ -14,12 +16,13 @@ export interface GroupConfig {
|
|
|
14
16
|
export interface GroupManifestLink {
|
|
15
17
|
from: string;
|
|
16
18
|
to: string;
|
|
17
|
-
type:
|
|
19
|
+
type: ManifestContractType;
|
|
18
20
|
contract: string;
|
|
19
21
|
role: ContractRole;
|
|
20
22
|
}
|
|
21
23
|
export interface DetectConfig {
|
|
22
24
|
http: boolean;
|
|
25
|
+
graphql?: boolean;
|
|
23
26
|
grpc: boolean;
|
|
24
27
|
thrift: boolean;
|
|
25
28
|
topics: boolean;
|
|
@@ -28,11 +31,12 @@ export interface DetectConfig {
|
|
|
28
31
|
}
|
|
29
32
|
export interface MatchingConfig {
|
|
30
33
|
/**
|
|
31
|
-
* HTTP paths to exclude from cross-link matching. Contracts at these paths
|
|
34
|
+
* HTTP paths or GraphQL root fields to exclude from cross-link matching. Contracts at these paths
|
|
32
35
|
* are still extracted and visible in the registry, but they don't produce
|
|
33
36
|
* cross-repo links. Useful for health-check endpoints (`/ping`, `/health`)
|
|
34
37
|
* that every service exposes and would otherwise create N×M false links.
|
|
35
|
-
* Trailing slashes are normalized before comparison.
|
|
38
|
+
* Trailing slashes are normalized before comparison. GraphQL fields may be
|
|
39
|
+
* written as `health` or `/health`.
|
|
36
40
|
* @default []
|
|
37
41
|
*/
|
|
38
42
|
exclude_links_paths?: string[];
|
|
@@ -163,7 +167,17 @@ export interface GroupImpactResult {
|
|
|
163
167
|
modules_affected: number;
|
|
164
168
|
cross_repo_hits: number;
|
|
165
169
|
};
|
|
166
|
-
risk:
|
|
170
|
+
risk: ImpactRisk;
|
|
171
|
+
/**
|
|
172
|
+
* Two-axis (direct + total) risk from the local leg, then `mergeRisk` with
|
|
173
|
+
* crossings — compare File vs symbol here, not via top-level `risk`.
|
|
174
|
+
*/
|
|
175
|
+
riskSharedAxes?: ImpactRisk;
|
|
176
|
+
/**
|
|
177
|
+
* Local-leg scale metadata (File / skipped enrichment). Crossings do not
|
|
178
|
+
* invent process/module membership for File nodes.
|
|
179
|
+
*/
|
|
180
|
+
riskScale?: ImpactRiskResult['riskScale'];
|
|
167
181
|
/**
|
|
168
182
|
* `'lower-bound'` when the fan-out was cut short, so `risk` is a FLOOR, not a
|
|
169
183
|
* verdict. Same vocabulary as single-repo `impact`'s `epistemic` field.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { KnowledgeGraph } from '../graph/types.js';
|
|
2
|
+
import type { FileHashDiff } from '../../storage/file-hash.js';
|
|
3
|
+
/** The FTS-backed members of `tables`. */
|
|
4
|
+
export declare const ftsTablesAmong: (tables: Iterable<string>) => Set<string>;
|
|
5
|
+
/**
|
|
6
|
+
* Whether a surgical incremental write may reuse the persisted derived layer.
|
|
7
|
+
*
|
|
8
|
+
* Deletions disqualify it: the persisted Community/Process rows and their
|
|
9
|
+
* MEMBER_OF / STEP_IN_PROCESS edges can reference nodes that no longer exist
|
|
10
|
+
* after this run, and nothing short of re-deriving can tell which.
|
|
11
|
+
*
|
|
12
|
+
* Added or content-changed files also disqualify it: they can introduce,
|
|
13
|
+
* rename, or retarget symbols and CALLS edges that Leiden and flow extraction
|
|
14
|
+
* consume. File-deletion-only was too weak a proof that the derived graph is
|
|
15
|
+
* still valid.
|
|
16
|
+
*/
|
|
17
|
+
export declare const shouldPreservePersistedDerivedGraph: (diff: Pick<FileHashDiff, "deleted" | "added" | "changed">) => boolean;
|
|
18
|
+
/**
|
|
19
|
+
* FTS-backed node tables that the fresh graph will WRITE rows into for
|
|
20
|
+
* `fileSet` — the inserting half of the DML.
|
|
21
|
+
*
|
|
22
|
+
* Callers must union this with a DB probe for the deleting half
|
|
23
|
+
* (`nodeTablesWithRowsForFiles`): a table whose last row in these files was
|
|
24
|
+
* just removed by the edit has nothing here, but still holds a stale row that
|
|
25
|
+
* the writeback must delete, and deleting it means taking its index down too.
|
|
26
|
+
*/
|
|
27
|
+
export declare const incrementalFtsTablesFromGraph: (graph: KnowledgeGraph, fileSet: ReadonlySet<string>) => Set<string>;
|
|
28
|
+
/**
|
|
29
|
+
* The node tables an incremental DETACH DELETE should target, given the FTS
|
|
30
|
+
* tables this run is rebuilding.
|
|
31
|
+
*
|
|
32
|
+
* Every non-FTS table (Folder, CodeElement, …) deletes as before. An FTS-backed
|
|
33
|
+
* table only deletes when its index is being rebuilt anyway, because deleting
|
|
34
|
+
* from it otherwise would mean DML against a live FTS index (#2589).
|
|
35
|
+
*/
|
|
36
|
+
export declare const nodeTablesForIncrementalDelete: (allNodeTables: readonly string[], rebuildingFtsTables: ReadonlySet<string>) => string[];
|