gitnexus 1.6.8-rc.2 → 1.6.8-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 +20 -0
- package/dist/_shared/scope-resolution/parsed-file.d.ts +21 -0
- package/dist/_shared/scope-resolution/parsed-file.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/symbol-definition.d.ts +4 -0
- package/dist/_shared/scope-resolution/symbol-definition.d.ts.map +1 -1
- package/dist/cli/ai-context.js +1 -0
- package/dist/cli/analyze-config.js +40 -0
- package/dist/cli/analyze.d.ts +22 -0
- package/dist/cli/analyze.js +36 -6
- package/dist/cli/clean.d.ts +1 -0
- package/dist/cli/clean.js +43 -1
- package/dist/cli/eval-server.js +43 -0
- package/dist/cli/help-i18n.js +11 -0
- package/dist/cli/i18n/en.d.ts +13 -0
- package/dist/cli/i18n/en.js +13 -0
- package/dist/cli/i18n/resources.d.ts +26 -0
- package/dist/cli/i18n/zh-CN.d.ts +13 -0
- package/dist/cli/i18n/zh-CN.js +13 -0
- package/dist/cli/index.js +19 -0
- package/dist/cli/list.js +12 -0
- package/dist/cli/optional-grammars.d.ts +6 -8
- package/dist/cli/optional-grammars.js +8 -11
- package/dist/cli/skill-gen.js +1 -0
- package/dist/cli/status.js +26 -5
- package/dist/cli/tool.d.ts +12 -1
- package/dist/cli/tool.js +41 -1
- package/dist/core/embeddings/embedder.js +4 -0
- package/dist/core/embeddings/embedding-pipeline.js +27 -16
- package/dist/core/embeddings/onnxruntime-common-resolver.d.ts +6 -0
- package/dist/core/embeddings/onnxruntime-common-resolver.js +130 -0
- package/dist/core/graph/import-cycles.d.ts +10 -0
- package/dist/core/graph/import-cycles.js +103 -0
- package/dist/core/group/extractors/grpc-patterns/proto.js +10 -6
- package/dist/core/group/extractors/http-patterns/java.js +3 -50
- package/dist/core/group/extractors/http-patterns/kotlin.js +7 -7
- package/dist/core/group/extractors/include-extractor.js +7 -7
- package/dist/core/ingestion/cfg/cfg-builder.d.ts +64 -0
- package/dist/core/ingestion/cfg/cfg-builder.js +129 -0
- package/dist/core/ingestion/cfg/collect.d.ts +30 -0
- package/dist/core/ingestion/cfg/collect.js +34 -0
- package/dist/core/ingestion/cfg/control-flow-context.d.ts +97 -0
- package/dist/core/ingestion/cfg/control-flow-context.js +113 -0
- package/dist/core/ingestion/cfg/emit.d.ts +144 -0
- package/dist/core/ingestion/cfg/emit.js +315 -0
- package/dist/core/ingestion/cfg/reaching-defs.d.ts +90 -0
- package/dist/core/ingestion/cfg/reaching-defs.js +364 -0
- package/dist/core/ingestion/cfg/traversal-result.d.ts +20 -0
- package/dist/core/ingestion/cfg/traversal-result.js +2 -0
- package/dist/core/ingestion/cfg/types.d.ts +231 -0
- package/dist/core/ingestion/cfg/types.js +13 -0
- package/dist/core/ingestion/cfg/visitors/typescript-harvest.d.ts +144 -0
- package/dist/core/ingestion/cfg/visitors/typescript-harvest.js +1032 -0
- package/dist/core/ingestion/cfg/visitors/typescript.d.ts +66 -0
- package/dist/core/ingestion/cfg/visitors/typescript.js +584 -0
- package/dist/core/ingestion/language-provider.d.ts +36 -2
- package/dist/core/ingestion/languages/c-cpp.js +11 -4
- package/dist/core/ingestion/languages/cpp/arity-metadata.js +6 -2
- package/dist/core/ingestion/languages/cpp/captures.js +24 -1
- package/dist/core/ingestion/languages/cpp/query.js +23 -0
- package/dist/core/ingestion/languages/java.js +3 -0
- package/dist/core/ingestion/languages/kotlin/query.js +3 -2
- package/dist/core/ingestion/languages/kotlin.js +5 -1
- package/dist/core/ingestion/languages/typescript.js +5 -0
- package/dist/core/ingestion/method-extractors/configs/c-cpp.js +6 -13
- package/dist/core/ingestion/method-extractors/generic.js +1 -0
- package/dist/core/ingestion/method-types.d.ts +2 -0
- package/dist/core/ingestion/model/symbol-table.d.ts +1 -0
- package/dist/core/ingestion/model/symbol-table.js +1 -0
- package/dist/core/ingestion/parsing-processor.js +22 -0
- package/dist/core/ingestion/pipeline-phases/parse-impl.js +18 -1
- package/dist/core/ingestion/pipeline-phases/routes.js +64 -14
- package/dist/core/ingestion/pipeline.d.ts +57 -0
- package/dist/core/ingestion/route-extractors/spring-shared.d.ts +50 -0
- package/dist/core/ingestion/route-extractors/spring-shared.js +80 -0
- package/dist/core/ingestion/route-extractors/spring.d.ts +35 -0
- package/dist/core/ingestion/route-extractors/spring.js +136 -0
- package/dist/core/ingestion/scope-extractor.js +3 -0
- package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +12 -0
- package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +145 -24
- package/dist/core/ingestion/scope-resolution/pipeline/phase.js +6 -0
- package/dist/core/ingestion/scope-resolution/pipeline/reconcile-ownership.js +43 -3
- package/dist/core/ingestion/scope-resolution/pipeline/run.d.ts +21 -0
- package/dist/core/ingestion/scope-resolution/pipeline/run.js +207 -0
- package/dist/core/ingestion/scope-resolution/resolution-outcome.d.ts +1 -1
- package/dist/core/ingestion/taint/emit.d.ts +124 -0
- package/dist/core/ingestion/taint/emit.js +204 -0
- package/dist/core/ingestion/taint/match.d.ts +153 -0
- package/dist/core/ingestion/taint/match.js +278 -0
- package/dist/core/ingestion/taint/path-codec.d.ts +134 -0
- package/dist/core/ingestion/taint/path-codec.js +190 -0
- package/dist/core/ingestion/taint/propagate.d.ts +216 -0
- package/dist/core/ingestion/taint/propagate.js +664 -0
- package/dist/core/ingestion/taint/site-safety.d.ts +29 -0
- package/dist/core/ingestion/taint/site-safety.js +98 -0
- package/dist/core/ingestion/taint/source-sink-config.d.ts +94 -23
- package/dist/core/ingestion/taint/source-sink-config.js +11 -11
- package/dist/core/ingestion/taint/source-sink-registry.d.ts +6 -4
- package/dist/core/ingestion/taint/source-sink-registry.js +6 -4
- package/dist/core/ingestion/taint/typescript-model.d.ts +38 -0
- package/dist/core/ingestion/taint/typescript-model.js +102 -0
- package/dist/core/ingestion/utils/method-props.js +1 -0
- package/dist/core/ingestion/workers/clone-safety.d.ts +109 -0
- package/dist/core/ingestion/workers/clone-safety.js +465 -0
- package/dist/core/ingestion/workers/parse-worker.d.ts +11 -0
- package/dist/core/ingestion/workers/parse-worker.js +70 -53
- package/dist/core/ingestion/workers/post-result.d.ts +22 -0
- package/dist/core/ingestion/workers/post-result.js +87 -0
- package/dist/core/ingestion/workers/result-merge.d.ts +20 -0
- package/dist/core/ingestion/workers/result-merge.js +43 -0
- package/dist/core/ingestion/workers/worker-pool.d.ts +15 -0
- package/dist/core/ingestion/workers/worker-pool.js +33 -16
- package/dist/core/lbug/lbug-adapter.d.ts +19 -0
- package/dist/core/lbug/lbug-adapter.js +56 -1
- package/dist/core/run-analyze.d.ts +87 -0
- package/dist/core/run-analyze.js +280 -25
- package/dist/core/tree-sitter/parser-loader.js +5 -4
- package/dist/core/tree-sitter/vendored-grammars.d.ts +39 -0
- package/dist/core/tree-sitter/vendored-grammars.js +57 -0
- package/dist/mcp/core/embedder.js +4 -0
- package/dist/mcp/local/local-backend.d.ts +86 -2
- package/dist/mcp/local/local-backend.js +657 -27
- package/dist/mcp/resources.js +1 -0
- package/dist/mcp/tools.d.ts +9 -0
- package/dist/mcp/tools.js +109 -0
- package/dist/server/analyze-launch.d.ts +29 -0
- package/dist/server/analyze-launch.js +137 -0
- package/dist/server/analyze-upload.d.ts +33 -0
- package/dist/server/analyze-upload.js +123 -0
- package/dist/server/analyze-worker-ipc.d.ts +58 -0
- package/dist/server/analyze-worker-ipc.js +16 -0
- package/dist/server/analyze-worker.d.ts +17 -1
- package/dist/server/analyze-worker.js +7 -1
- package/dist/server/api.js +46 -140
- package/dist/server/git-clone.d.ts +1 -0
- package/dist/server/git-clone.js +1 -1
- package/dist/server/middleware.d.ts +11 -0
- package/dist/server/middleware.js +27 -0
- package/dist/server/upload-ingest.d.ts +56 -0
- package/dist/server/upload-ingest.js +276 -0
- package/dist/server/upload-paths.d.ts +31 -0
- package/dist/server/upload-paths.js +51 -0
- package/dist/server/upload-sweep.d.ts +20 -0
- package/dist/server/upload-sweep.js +57 -0
- package/dist/storage/branch-index.d.ts +52 -0
- package/dist/storage/branch-index.js +65 -0
- package/dist/storage/git.d.ts +11 -0
- package/dist/storage/git.js +28 -0
- package/dist/storage/parse-cache.d.ts +22 -1
- package/dist/storage/parse-cache.js +32 -10
- package/dist/storage/repo-manager.d.ts +122 -10
- package/dist/storage/repo-manager.js +162 -21
- package/hooks/antigravity/gitnexus-antigravity-hook.cjs +42 -8
- package/hooks/claude/gitnexus-hook.cjs +36 -8
- package/hooks/claude/hook-db-lock-probe.cjs +123 -2
- package/package.json +4 -2
- package/scripts/assert-publish-grammar-coverage.cjs +31 -0
- package/scripts/build-tree-sitter-grammars.cjs +16 -10
- package/skills/gitnexus-guide.md +11 -0
- package/vendor/tree-sitter-c/package.json +1 -1
- package/vendor/tree-sitter-dart/package.json +1 -1
- package/vendor/tree-sitter-kotlin/package.json +1 -1
- package/vendor/tree-sitter-proto/package.json +1 -1
- package/vendor/tree-sitter-swift/package.json +1 -1
- package/web/assets/{agent-CKPMqImC.js → agent-ay4LD70X.js} +1 -1
- package/web/assets/{architectureDiagram-UL44E2DR-D1EXI0zA.js → architectureDiagram-UL44E2DR-Dc-viYhd.js} +1 -1
- package/web/assets/{chunk-LCXTWHL2-Dfmux4m1.js → chunk-LCXTWHL2-4rpojOyj.js} +1 -1
- package/web/assets/{chunk-RG4AUYOV-CAkzcoRj.js → chunk-RG4AUYOV-BnOy944n.js} +1 -1
- package/web/assets/{classDiagram-KGZ6W3CR-Bv93af_b.js → classDiagram-KGZ6W3CR-Bsgpy98Q.js} +1 -1
- package/web/assets/{classDiagram-v2-72OJOZXJ-CLfEqHUa.js → classDiagram-v2-72OJOZXJ-D5atDGjc.js} +1 -1
- package/web/assets/{diagram-3NCE3AQN-BqAtKUpW.js → diagram-3NCE3AQN-Cz1OEMVi.js} +1 -1
- package/web/assets/{diagram-GF46GFSD-CaBG6n6o.js → diagram-GF46GFSD-CWYwzfP8.js} +1 -1
- package/web/assets/{diagram-QXG6HAR7-CZ-O3rcV.js → diagram-QXG6HAR7-DtBObr8L.js} +1 -1
- package/web/assets/{diagram-WEQXMOUZ-P4lSL4GH.js → diagram-WEQXMOUZ-BUV44Ov_.js} +1 -1
- package/web/assets/{erDiagram-L5TCEMPS-QN2eEP1e.js → erDiagram-L5TCEMPS-Ds9s-sRF.js} +1 -1
- package/web/assets/{flowDiagram-H6V6AXG4-CXbXImlN.js → flowDiagram-H6V6AXG4-CupStHQb.js} +1 -1
- package/web/assets/index-COMMmbxW.css +2 -0
- package/web/assets/{index-CG6q8eTs.js → index-Do8AE5yF.js} +85 -85
- package/web/assets/{infoDiagram-3YFTVSEB-DmLICZx1.js → infoDiagram-3YFTVSEB-DPTXSbgu.js} +1 -1
- package/web/assets/{ishikawaDiagram-BNXS4ZKH-6LKOvBfp.js → ishikawaDiagram-BNXS4ZKH-CeK-Qhet.js} +1 -1
- package/web/assets/{kanban-definition-75IXJCU3-DgDi9oJT.js → kanban-definition-75IXJCU3-D4tx6QQ-.js} +1 -1
- package/web/assets/{mindmap-definition-2TDM6QVE-CwR5sBB-.js → mindmap-definition-2TDM6QVE-Bcto_kGa.js} +1 -1
- package/web/assets/{pieDiagram-CU6KROY3-By8g6f6B.js → pieDiagram-CU6KROY3-DPIeBTG6.js} +1 -1
- package/web/assets/{requirementDiagram-JXO7QTGE-7oDcJ1_J.js → requirementDiagram-JXO7QTGE-DDGXjVKO.js} +1 -1
- package/web/assets/{sequenceDiagram-VS2MUI6T-FySeKCUy.js → sequenceDiagram-VS2MUI6T-BzDqpcVW.js} +1 -1
- package/web/assets/{stateDiagram-7D4R322I-CB2nABwH.js → stateDiagram-7D4R322I-Bx__als3.js} +1 -1
- package/web/assets/{stateDiagram-v2-36443NZ5-COBGd2RL.js → stateDiagram-v2-36443NZ5-QqOh2yO-.js} +1 -1
- package/web/assets/{timeline-definition-O6YCAMPW-Ds2CnVZK.js → timeline-definition-O6YCAMPW-C1eogTOG.js} +1 -1
- package/web/assets/{vennDiagram-MWXL3ELB-DUIEwXWp.js → vennDiagram-MWXL3ELB-D20F4rSW.js} +1 -1
- package/web/assets/{wardleyDiagram-CUQ6CDDI-DEiFPQih.js → wardleyDiagram-CUQ6CDDI-BeBHg7ST.js} +1 -1
- package/web/assets/{xychartDiagram-N2JHSOCM-BUbayhST.js → xychartDiagram-N2JHSOCM-CdOWeoNI.js} +1 -1
- package/web/index.html +2 -2
- package/scripts/materialize-vendor-grammars.cjs +0 -97
- package/web/assets/index-BKWA-m7o.css +0 -2
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Upload working-directory paths.
|
|
3
|
+
*
|
|
4
|
+
* Browser folder uploads are written into ~/.gitnexus/uploads/{name}/ — a
|
|
5
|
+
* sibling of the clone root (git-clone.ts CLONE_ROOT) — so an uploaded repo
|
|
6
|
+
* persists and behaves like a cloned one (the graph UI's /api/file reads its
|
|
7
|
+
* files after analysis, and DELETE /api/repo removes it). Staging happens in
|
|
8
|
+
* an mkdtemp dir *under* UPLOAD_ROOT so the promote rename stays on one
|
|
9
|
+
* filesystem and remains atomic (a rename from os.tmpdir() could trip EXDEV —
|
|
10
|
+
* the exact Docker case this feature targets; see bridge-db.ts for the same
|
|
11
|
+
* anchored-staging pattern).
|
|
12
|
+
*/
|
|
13
|
+
import path from 'path';
|
|
14
|
+
import os from 'os';
|
|
15
|
+
import { sanitizeRepoName } from '../storage/git.js';
|
|
16
|
+
import { REPO_NAME_PATTERN } from './git-clone.js';
|
|
17
|
+
/** Root directory for all uploaded repositories. Targets must resolve inside this. */
|
|
18
|
+
export const UPLOAD_ROOT = path.resolve(path.join(os.homedir(), '.gitnexus', 'uploads'));
|
|
19
|
+
/** Prefix for per-upload staging directories created under UPLOAD_ROOT. */
|
|
20
|
+
export const STAGING_PREFIX = '.staging-';
|
|
21
|
+
/**
|
|
22
|
+
* Get the upload target directory for a repo name.
|
|
23
|
+
*
|
|
24
|
+
* Re-validates at the boundary (callers may derive the name from an untrusted
|
|
25
|
+
* manifest). Rejects `.`, `..`, the `'unknown'` sentinel that sanitizeRepoName
|
|
26
|
+
* emits for un-nameable inputs, names beginning with `.` (which would collide
|
|
27
|
+
* with the `.staging-` prefix), and anything outside the safe charset.
|
|
28
|
+
*/
|
|
29
|
+
export function getUploadDir(repoName) {
|
|
30
|
+
if (!repoName ||
|
|
31
|
+
repoName === '.' ||
|
|
32
|
+
repoName === '..' ||
|
|
33
|
+
repoName === 'unknown' ||
|
|
34
|
+
repoName.startsWith('.') ||
|
|
35
|
+
!REPO_NAME_PATTERN.test(repoName)) {
|
|
36
|
+
throw new Error('Invalid repository name');
|
|
37
|
+
}
|
|
38
|
+
return path.join(UPLOAD_ROOT, repoName);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Derive a filesystem-safe upload directory name from the manifest's
|
|
42
|
+
* top-level folder. Returns null when the name is un-nameable (so the caller
|
|
43
|
+
* rejects with 400 rather than colliding everyone on `UPLOAD_ROOT/unknown`).
|
|
44
|
+
*/
|
|
45
|
+
export function deriveUploadName(topLevelName) {
|
|
46
|
+
const safe = sanitizeRepoName(topLevelName);
|
|
47
|
+
if (safe === 'unknown' || safe === '.' || safe === '..' || safe.startsWith('.')) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
return safe;
|
|
51
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Backstop cleanup for abandoned upload staging directories.
|
|
3
|
+
*
|
|
4
|
+
* A crashed/killed process can leave a `.staging-*` directory under
|
|
5
|
+
* UPLOAD_ROOT (the normal path removes it on success/failure/abort). This
|
|
6
|
+
* sweep, run once at server startup, removes staging dirs older than a
|
|
7
|
+
* threshold. Promoted upload dirs are persistent registered repos (like
|
|
8
|
+
* clones) and are NOT touched here — they are removed via DELETE /api/repo.
|
|
9
|
+
*/
|
|
10
|
+
export interface SweepOptions {
|
|
11
|
+
/** Remove staging dirs older than this (default 6h). */
|
|
12
|
+
maxAgeMs?: number;
|
|
13
|
+
/** Override the root to sweep (defaults to UPLOAD_ROOT; for tests). */
|
|
14
|
+
root?: string;
|
|
15
|
+
/** Clock injection for tests. */
|
|
16
|
+
now?: number;
|
|
17
|
+
}
|
|
18
|
+
export declare function sweepStaleUploads(opts?: SweepOptions): Promise<{
|
|
19
|
+
removed: string[];
|
|
20
|
+
}>;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Backstop cleanup for abandoned upload staging directories.
|
|
3
|
+
*
|
|
4
|
+
* A crashed/killed process can leave a `.staging-*` directory under
|
|
5
|
+
* UPLOAD_ROOT (the normal path removes it on success/failure/abort). This
|
|
6
|
+
* sweep, run once at server startup, removes staging dirs older than a
|
|
7
|
+
* threshold. Promoted upload dirs are persistent registered repos (like
|
|
8
|
+
* clones) and are NOT touched here — they are removed via DELETE /api/repo.
|
|
9
|
+
*/
|
|
10
|
+
import path from 'path';
|
|
11
|
+
import fsp from 'fs/promises';
|
|
12
|
+
import { UPLOAD_ROOT, STAGING_PREFIX } from './upload-paths.js';
|
|
13
|
+
export async function sweepStaleUploads(opts = {}) {
|
|
14
|
+
const maxAgeMs = opts.maxAgeMs ?? 6 * 60 * 60 * 1000;
|
|
15
|
+
const root = opts.root ?? UPLOAD_ROOT;
|
|
16
|
+
const now = opts.now ?? Date.now();
|
|
17
|
+
const removed = [];
|
|
18
|
+
let entries;
|
|
19
|
+
try {
|
|
20
|
+
entries = await fsp.readdir(root, { withFileTypes: true });
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return { removed }; // root does not exist yet — nothing to sweep
|
|
24
|
+
}
|
|
25
|
+
for (const entry of entries) {
|
|
26
|
+
if (!entry.isDirectory())
|
|
27
|
+
continue;
|
|
28
|
+
const full = path.join(root, entry.name);
|
|
29
|
+
try {
|
|
30
|
+
const st = await fsp.stat(full);
|
|
31
|
+
if (now - st.mtimeMs <= maxAgeMs)
|
|
32
|
+
continue; // recent — keep
|
|
33
|
+
if (entry.name.startsWith(STAGING_PREFIX)) {
|
|
34
|
+
// Transient staging dir orphaned by a crash — always removable.
|
|
35
|
+
await fsp.rm(full, { recursive: true, force: true }).catch(() => { });
|
|
36
|
+
removed.push(full);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
// Promoted upload dir. A successfully-analyzed (registered) repo always
|
|
40
|
+
// has a `.gitnexus` index inside it; a stale promoted dir WITHOUT one is
|
|
41
|
+
// an orphan from an analysis that failed before registering — remove it.
|
|
42
|
+
const hasIndex = await fsp
|
|
43
|
+
.access(path.join(full, '.gitnexus'))
|
|
44
|
+
.then(() => true)
|
|
45
|
+
.catch(() => false);
|
|
46
|
+
if (!hasIndex) {
|
|
47
|
+
await fsp.rm(full, { recursive: true, force: true }).catch(() => { });
|
|
48
|
+
removed.push(full);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
/* stat race — skip */
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return { removed };
|
|
57
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Branch-index primitives (#2106).
|
|
3
|
+
*
|
|
4
|
+
* Extracted from `repo-manager.ts` to keep the multi-branch slug/placement
|
|
5
|
+
* logic in one focused module. `getStoragePaths`, `loadMeta`, and the registry
|
|
6
|
+
* I/O stay in `repo-manager.ts`; this module imports the two it needs at
|
|
7
|
+
* call-time only (no module-load cross-calls), so the repo-manager ⇄
|
|
8
|
+
* branch-index import cycle is ESM-safe. `repo-manager.ts` re-exports these so
|
|
9
|
+
* existing import sites keep working unchanged.
|
|
10
|
+
*/
|
|
11
|
+
import { type RepoMeta } from './repo-manager.js';
|
|
12
|
+
/**
|
|
13
|
+
* Per-branch index summary nested under a registry entry (#2106). Records
|
|
14
|
+
* non-primary branches indexed for the same repo path so `list`, `status`, and
|
|
15
|
+
* `list_repos` can surface them without a second registry entry.
|
|
16
|
+
*/
|
|
17
|
+
export interface BranchSummary {
|
|
18
|
+
/** Git branch name this sub-index represents. */
|
|
19
|
+
branch: string;
|
|
20
|
+
indexedAt: string;
|
|
21
|
+
lastCommit: string;
|
|
22
|
+
stats?: RepoMeta['stats'];
|
|
23
|
+
}
|
|
24
|
+
/** Branch-index sub-directory name, relative to the flat `.gitnexus` storage. */
|
|
25
|
+
export declare const BRANCHES_DIR = "branches";
|
|
26
|
+
/**
|
|
27
|
+
* Filesystem-safe slug for a git branch ref (#2106).
|
|
28
|
+
*
|
|
29
|
+
* `sanitizeRepoName` alone is lossy — it maps `/`→`_`, so `feature/x` and
|
|
30
|
+
* `feature_x` would collide into the same directory. We append a short sha256
|
|
31
|
+
* of the RAW ref (mirroring `assignRepoId`'s digest fallback) so two distinct
|
|
32
|
+
* refs can never share a branch directory, while keeping the human prefix
|
|
33
|
+
* readable.
|
|
34
|
+
*/
|
|
35
|
+
export declare const branchSlug: (rawRef: string) => string;
|
|
36
|
+
/**
|
|
37
|
+
* Decide where a freshly-analyzed branch's index lives: the flat (primary) slot
|
|
38
|
+
* or a per-branch sub-directory (#2106 KTD2).
|
|
39
|
+
*
|
|
40
|
+
* Returns `{}` for the flat/primary placement (byte-identical layout) or
|
|
41
|
+
* `{ branch }` for a `branches/<slug>/` sub-directory. The flat slot is owned by
|
|
42
|
+
* the FIRST branch indexed, recorded as `branch` in the flat `meta.json`; a
|
|
43
|
+
* different checked-out branch then auto-routes to its own sub-directory so it
|
|
44
|
+
* never overwrites the primary index.
|
|
45
|
+
*
|
|
46
|
+
* `label` is the resolved index-branch (explicit `--branch`, else the
|
|
47
|
+
* checked-out branch, else `null`). A `null` label — detached HEAD, non-git
|
|
48
|
+
* folder, or CI checkout — always maps to the flat slot.
|
|
49
|
+
*/
|
|
50
|
+
export declare const resolveBranchPlacement: (repoPath: string, label: string | null) => Promise<{
|
|
51
|
+
branch?: string;
|
|
52
|
+
}>;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Branch-index primitives (#2106).
|
|
3
|
+
*
|
|
4
|
+
* Extracted from `repo-manager.ts` to keep the multi-branch slug/placement
|
|
5
|
+
* logic in one focused module. `getStoragePaths`, `loadMeta`, and the registry
|
|
6
|
+
* I/O stay in `repo-manager.ts`; this module imports the two it needs at
|
|
7
|
+
* call-time only (no module-load cross-calls), so the repo-manager ⇄
|
|
8
|
+
* branch-index import cycle is ESM-safe. `repo-manager.ts` re-exports these so
|
|
9
|
+
* existing import sites keep working unchanged.
|
|
10
|
+
*/
|
|
11
|
+
import { createHash } from 'crypto';
|
|
12
|
+
import { sanitizeRepoName } from './git.js';
|
|
13
|
+
import { getStoragePaths, loadMeta } from './repo-manager.js';
|
|
14
|
+
/** Branch-index sub-directory name, relative to the flat `.gitnexus` storage. */
|
|
15
|
+
export const BRANCHES_DIR = 'branches';
|
|
16
|
+
/**
|
|
17
|
+
* Filesystem-safe slug for a git branch ref (#2106).
|
|
18
|
+
*
|
|
19
|
+
* `sanitizeRepoName` alone is lossy — it maps `/`→`_`, so `feature/x` and
|
|
20
|
+
* `feature_x` would collide into the same directory. We append a short sha256
|
|
21
|
+
* of the RAW ref (mirroring `assignRepoId`'s digest fallback) so two distinct
|
|
22
|
+
* refs can never share a branch directory, while keeping the human prefix
|
|
23
|
+
* readable.
|
|
24
|
+
*/
|
|
25
|
+
export const branchSlug = (rawRef) => {
|
|
26
|
+
const safe = sanitizeRepoName(rawRef);
|
|
27
|
+
const hash = createHash('sha256').update(rawRef).digest('hex').slice(0, 8);
|
|
28
|
+
return `${safe}-${hash}`;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Decide where a freshly-analyzed branch's index lives: the flat (primary) slot
|
|
32
|
+
* or a per-branch sub-directory (#2106 KTD2).
|
|
33
|
+
*
|
|
34
|
+
* Returns `{}` for the flat/primary placement (byte-identical layout) or
|
|
35
|
+
* `{ branch }` for a `branches/<slug>/` sub-directory. The flat slot is owned by
|
|
36
|
+
* the FIRST branch indexed, recorded as `branch` in the flat `meta.json`; a
|
|
37
|
+
* different checked-out branch then auto-routes to its own sub-directory so it
|
|
38
|
+
* never overwrites the primary index.
|
|
39
|
+
*
|
|
40
|
+
* `label` is the resolved index-branch (explicit `--branch`, else the
|
|
41
|
+
* checked-out branch, else `null`). A `null` label — detached HEAD, non-git
|
|
42
|
+
* folder, or CI checkout — always maps to the flat slot.
|
|
43
|
+
*/
|
|
44
|
+
export const resolveBranchPlacement = async (repoPath, label) => {
|
|
45
|
+
// Detached HEAD / non-git / no label → flat (CI-safe, byte-identical).
|
|
46
|
+
if (!label)
|
|
47
|
+
return {};
|
|
48
|
+
const { storagePath } = getStoragePaths(repoPath);
|
|
49
|
+
const flatMeta = await loadMeta(storagePath);
|
|
50
|
+
// The flat slot's owner is authoritative ONLY when it is a non-empty string.
|
|
51
|
+
// A corrupt/hand-edited meta (empty string, or a non-string value that slips
|
|
52
|
+
// past JSON typing) must not be trusted to route the real primary into a
|
|
53
|
+
// sub-directory (#2106 review R5).
|
|
54
|
+
const owner = flatMeta && typeof flatMeta.branch === 'string' && flatMeta.branch.length > 0
|
|
55
|
+
? flatMeta.branch
|
|
56
|
+
: undefined;
|
|
57
|
+
// Fresh repo (no flat index) or legacy/unstamped flat index (no recorded
|
|
58
|
+
// owner): the current label claims/adopts the flat slot. The legacy case
|
|
59
|
+
// preserves today's overwrite-in-place behavior until the slot is stamped.
|
|
60
|
+
if (!owner)
|
|
61
|
+
return {};
|
|
62
|
+
// Flat slot is owned. Same branch → flat; otherwise this branch gets its own
|
|
63
|
+
// sub-directory.
|
|
64
|
+
return owner === label ? {} : { branch: label };
|
|
65
|
+
};
|
package/dist/storage/git.d.ts
CHANGED
|
@@ -118,6 +118,17 @@ export declare const getRemoteOriginUrl: (repoPath: string) => string | null;
|
|
|
118
118
|
* git is unavailable, so callers can fall back to a configured/default branch.
|
|
119
119
|
*/
|
|
120
120
|
export declare const getDefaultBranch: (repoPath: string) => string | null;
|
|
121
|
+
/**
|
|
122
|
+
* Name of the currently checked-out branch, or `null` when HEAD is detached
|
|
123
|
+
* (CI checkouts, `git checkout <sha>`), the directory is not a git worktree, or
|
|
124
|
+
* git is unavailable.
|
|
125
|
+
*
|
|
126
|
+
* `git rev-parse --abbrev-ref HEAD` prints the literal `HEAD` for a detached
|
|
127
|
+
* checkout. We map that (and empty output) to `null` so callers fall back to the
|
|
128
|
+
* flat/default index rather than ever creating a branch literally named
|
|
129
|
+
* "HEAD" (#2106).
|
|
130
|
+
*/
|
|
131
|
+
export declare const getCurrentBranch: (repoPath: string) => string | null;
|
|
121
132
|
/**
|
|
122
133
|
* Sanitize a repository name to prevent argument injection and ensure
|
|
123
134
|
* cross-platform filesystem compatibility.
|
package/dist/storage/git.js
CHANGED
|
@@ -295,6 +295,34 @@ export const getDefaultBranch = (repoPath) => {
|
|
|
295
295
|
return null;
|
|
296
296
|
}
|
|
297
297
|
};
|
|
298
|
+
/**
|
|
299
|
+
* Name of the currently checked-out branch, or `null` when HEAD is detached
|
|
300
|
+
* (CI checkouts, `git checkout <sha>`), the directory is not a git worktree, or
|
|
301
|
+
* git is unavailable.
|
|
302
|
+
*
|
|
303
|
+
* `git rev-parse --abbrev-ref HEAD` prints the literal `HEAD` for a detached
|
|
304
|
+
* checkout. We map that (and empty output) to `null` so callers fall back to the
|
|
305
|
+
* flat/default index rather than ever creating a branch literally named
|
|
306
|
+
* "HEAD" (#2106).
|
|
307
|
+
*/
|
|
308
|
+
export const getCurrentBranch = (repoPath) => {
|
|
309
|
+
try {
|
|
310
|
+
const branch = execSync('git rev-parse --abbrev-ref HEAD', {
|
|
311
|
+
cwd: repoPath,
|
|
312
|
+
// Suppress stderr -- see getCurrentCommit comment and #1172.
|
|
313
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
314
|
+
windowsHide: true,
|
|
315
|
+
})
|
|
316
|
+
.toString()
|
|
317
|
+
.trim();
|
|
318
|
+
if (!branch || branch === 'HEAD')
|
|
319
|
+
return null;
|
|
320
|
+
return branch;
|
|
321
|
+
}
|
|
322
|
+
catch {
|
|
323
|
+
return null;
|
|
324
|
+
}
|
|
325
|
+
};
|
|
298
326
|
/**
|
|
299
327
|
* Sanitize a repository name to prevent argument injection and ensure
|
|
300
328
|
* cross-platform filesystem compatibility.
|
|
@@ -53,10 +53,31 @@ export declare const fileContentHash: (content: Buffer | string) => string;
|
|
|
53
53
|
* in the chunk. We sort by filePath before hashing so chunks composed of
|
|
54
54
|
* the same files in different order produce the same key.
|
|
55
55
|
*/
|
|
56
|
+
/** PDG/CFG cache namespace (#2081 M1) — every input that changes the
|
|
57
|
+
* WORKER-EMITTED `cfgSideChannel` must be folded into the chunk key, and
|
|
58
|
+
* ONLY those. The classification test for a future option: does the worker
|
|
59
|
+
* see it (workerData) and does it change the bytes the worker writes to the
|
|
60
|
+
* shard? `pdgMaxEdgesPerFunction` famously fails that test — it is applied
|
|
61
|
+
* at EMIT time on the main thread (scope-resolution run.ts), the worker
|
|
62
|
+
* never receives it, and the cached output is byte-identical across cap
|
|
63
|
+
* values; folding it in (as a prior review round did) only forced a
|
|
64
|
+
* spurious full re-parse on every cap change (#2099 F3). Options that
|
|
65
|
+
* change the PERSISTED GRAPH but not the shard belong in the RepoMeta pdg
|
|
66
|
+
* stamp (incremental-eligibility), not here. */
|
|
67
|
+
export interface PdgCacheKey {
|
|
68
|
+
readonly pdg?: boolean;
|
|
69
|
+
/** Per-function source-line cap (changes WHICH functions get a CFG —
|
|
70
|
+
* applied in the worker, so it shapes the cached shard). Callers must
|
|
71
|
+
* pass the RESOLVED value (the production call site in parse-impl.ts
|
|
72
|
+
* applies the worker's default before folding) so an explicit-default
|
|
73
|
+
* run shares the default run's keys — this function folds whatever it
|
|
74
|
+
* is given verbatim. */
|
|
75
|
+
readonly maxFunctionLines?: number;
|
|
76
|
+
}
|
|
56
77
|
export declare const computeChunkHash: (entries: Array<{
|
|
57
78
|
filePath: string;
|
|
58
79
|
contentHash: string;
|
|
59
|
-
}
|
|
80
|
+
}>, pdg?: boolean | PdgCacheKey) => string;
|
|
60
81
|
export declare const mapReplacer: (_key: string, value: unknown) => unknown;
|
|
61
82
|
export declare const mapReviver: (_key: string, value: unknown) => unknown;
|
|
62
83
|
/**
|
|
@@ -52,7 +52,7 @@ import { fileURLToPath } from 'url';
|
|
|
52
52
|
// the main thread (the #1983 OOM). Because the two stores share this version,
|
|
53
53
|
// any future change to the `ParsedFile` serialization shape MUST bump
|
|
54
54
|
// SCHEMA_BUMP so both invalidate in lockstep.
|
|
55
|
-
const SCHEMA_BUMP =
|
|
55
|
+
const SCHEMA_BUMP = 6; // #2082 M2: cfgSideChannel gained bindings + per-block statement facts
|
|
56
56
|
const GITNEXUS_PKG_VERSION = (() => {
|
|
57
57
|
try {
|
|
58
58
|
// package.json sits at gitnexus/package.json — two levels up from
|
|
@@ -92,17 +92,36 @@ const sha256Hex = (input) => createHash('sha256')
|
|
|
92
92
|
.digest('hex');
|
|
93
93
|
/** Stable hash of a single file's contents — used by callers to compose a chunk hash. */
|
|
94
94
|
export const fileContentHash = (content) => sha256Hex(content);
|
|
95
|
-
|
|
96
|
-
* Compute the canonical cache key for a chunk's contents.
|
|
97
|
-
*
|
|
98
|
-
* `entries` is the list of (filePath, file content hash) for every file
|
|
99
|
-
* in the chunk. We sort by filePath before hashing so chunks composed of
|
|
100
|
-
* the same files in different order produce the same key.
|
|
101
|
-
*/
|
|
102
|
-
export const computeChunkHash = (entries) => {
|
|
95
|
+
export const computeChunkHash = (entries, pdg = false) => {
|
|
103
96
|
const sorted = [...entries].sort((a, b) => (a.filePath < b.filePath ? -1 : 1));
|
|
104
97
|
const joined = sorted.map((e) => `${e.filePath}:${e.contentHash}`).join('\n');
|
|
105
|
-
|
|
98
|
+
const opts = typeof pdg === 'boolean' ? { pdg } : pdg;
|
|
99
|
+
// pdg-off path keeps its pre-#2081 chunk-KEY format verbatim. Note this does
|
|
100
|
+
// NOT mean caches survive the M1 upgrade: SCHEMA_BUMP 4→5 changed
|
|
101
|
+
// PARSE_CACHE_VERSION, and both loadParseCache (below) and the durable
|
|
102
|
+
// parsedfile-store index hard-invalidate on it — every user pays one full
|
|
103
|
+
// cold re-parse on upgrade regardless of --pdg. Keeping the key format
|
|
104
|
+
// stable only means no SECOND invalidation class is introduced here.
|
|
105
|
+
if (!opts.pdg)
|
|
106
|
+
return sha256Hex(joined);
|
|
107
|
+
// Fold the worker-visible --pdg configuration into the key: the boolean
|
|
108
|
+
// plus `maxFunctionLines` (decides which functions get a CFG at all, in the
|
|
109
|
+
// worker). Without it a warm chunk built under one cap is served to a run
|
|
110
|
+
// with a different cap → a stale/under-built CFG: the #2038-class
|
|
111
|
+
// option-blind-key trap. `def` marks an unset (default) value so two
|
|
112
|
+
// default-cap runs share a key. The emit-time edge cap is deliberately
|
|
113
|
+
// absent — see the PdgCacheKey doc comment.
|
|
114
|
+
//
|
|
115
|
+
// NAMESPACE VERSION (`pdg:2`): bumped when the worker-emitted
|
|
116
|
+
// `cfgSideChannel` SHAPE changes for pdg-mode runs only — pdg:1→2 in #2083
|
|
117
|
+
// M3 U1 (TsHarvester emits taint `sites` on StatementFacts). Invalidates
|
|
118
|
+
// pdg-mode chunks and their durable parsedfile-cache entries; flag-off
|
|
119
|
+
// chunk keys never reach this line and stay byte-identical, so non-pdg
|
|
120
|
+
// users pay nothing. Deliberately NOT a SCHEMA_BUMP — that gates the whole
|
|
121
|
+
// cache version and would force a full cold re-parse on EVERY user (the M1
|
|
122
|
+
// bump comment above records that cost).
|
|
123
|
+
const ns = `pdg:2;maxFn=${opts.maxFunctionLines ?? 'def'}`;
|
|
124
|
+
return sha256Hex(`${ns}\n${joined}`);
|
|
106
125
|
};
|
|
107
126
|
/**
|
|
108
127
|
* JSON replacer that round-trips Map/Set instances through plain JSON.
|
|
@@ -149,6 +168,9 @@ export const slimParseWorkerResultsForCache = (chunkResults) => {
|
|
|
149
168
|
assignments: [],
|
|
150
169
|
constructorBindings: [],
|
|
151
170
|
parsedFiles: [],
|
|
171
|
+
// #2112: a clone-safety skip list is per-run telemetry, not graph data —
|
|
172
|
+
// replay ignores it. Drop it so it doesn't bloat the cached shard.
|
|
173
|
+
skippedPaths: [],
|
|
152
174
|
});
|
|
153
175
|
}
|
|
154
176
|
return slim;
|
|
@@ -5,6 +5,9 @@
|
|
|
5
5
|
* Also maintains a global registry at ~/.gitnexus/registry.json
|
|
6
6
|
* so the MCP server can discover indexed repos from any cwd.
|
|
7
7
|
*/
|
|
8
|
+
import { branchSlug, resolveBranchPlacement, type BranchSummary } from './branch-index.js';
|
|
9
|
+
export { branchSlug, resolveBranchPlacement };
|
|
10
|
+
export type { BranchSummary };
|
|
8
11
|
/**
|
|
9
12
|
* Normalise a repo path for registry comparison across platforms
|
|
10
13
|
* (#664 review feedback from @evander-wang).
|
|
@@ -71,18 +74,85 @@ export interface RepoMeta {
|
|
|
71
74
|
*/
|
|
72
75
|
fileHashes?: Record<string, string>;
|
|
73
76
|
/**
|
|
74
|
-
* Crash-recovery dirty flag
|
|
75
|
-
* destructive DB mutation
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
77
|
+
* Crash-recovery dirty flag — a generic marker written to meta.json
|
|
78
|
+
* BEFORE any destructive DB mutation by BOTH writeback branches
|
|
79
|
+
* (incremental since its introduction; full rebuilds over an existing
|
|
80
|
+
* meta since #2099 F1); cleared on success by overwriting meta.json.
|
|
81
|
+
* If a run crashes between, the next run sees the flag and forces a
|
|
82
|
+
* full rebuild — the cheapest path back to a known-good index.
|
|
79
83
|
*/
|
|
80
84
|
incrementalInProgress?: {
|
|
81
|
-
/** When the
|
|
85
|
+
/** When the run started (epoch ms). */
|
|
82
86
|
startedAt: number;
|
|
83
|
-
/** Number of files in the writable set, for diagnostic logs.
|
|
87
|
+
/** Number of files in the writable set, for diagnostic logs.
|
|
88
|
+
* `0` on the full-rebuild path (no incremental write set exists). */
|
|
84
89
|
toWriteCount: number;
|
|
85
90
|
};
|
|
91
|
+
/**
|
|
92
|
+
* Name of the git branch this index represents (#2106). Absent for the
|
|
93
|
+
* default/legacy single-branch case so the flat `meta.json` stays
|
|
94
|
+
* byte-identical to pre-multi-branch output. When present in the FLAT
|
|
95
|
+
* `meta.json`, it records which branch "owns" the flat slot (the first
|
|
96
|
+
* branch indexed); per-branch indexes under `branches/<slug>/` always carry
|
|
97
|
+
* their own `branch`.
|
|
98
|
+
*/
|
|
99
|
+
branch?: string;
|
|
100
|
+
/**
|
|
101
|
+
* The parse-cache chunk keys this branch's index needs (#2106 R6). The
|
|
102
|
+
* parse-cache and durable parsedfile store live ONCE at the repo root and are
|
|
103
|
+
* shared across branches; recording each branch's live chunk keys lets the
|
|
104
|
+
* prune step union them so re-analyzing one branch doesn't evict another
|
|
105
|
+
* branch's still-live shards. Additive/optional; absent in legacy metas.
|
|
106
|
+
*/
|
|
107
|
+
cacheKeys?: string[];
|
|
108
|
+
/**
|
|
109
|
+
* The effective `--pdg` configuration this index's DB rows were built
|
|
110
|
+
* under (#2099 F1). Presence ≡ the BasicBlock/CFG layer exists in the DB;
|
|
111
|
+
* ABSENT ≡ pdg-off — which covers every legacy meta, since `--pdg`
|
|
112
|
+
* shipped opt-in. Caps are recorded RESOLVED (defaults applied) so an
|
|
113
|
+
* explicit-default run compares equal to a default run. run-analyze
|
|
114
|
+
* compares this against the requested options and forces a full
|
|
115
|
+
* writeback on any mismatch — the incremental path only persists
|
|
116
|
+
* changed-file nodes and would otherwise silently drop (or strand) the
|
|
117
|
+
* CFG layer on a mode flip. Additive/optional, no
|
|
118
|
+
* INCREMENTAL_SCHEMA_VERSION bump (a bump would force a one-time full
|
|
119
|
+
* rebuild for every user). NOTE the removal mechanism is load-bearing:
|
|
120
|
+
* the end-of-run meta is a fresh object literal, NOT a spread of the
|
|
121
|
+
* prior meta, so omitting this field on a pdg-off run is what clears
|
|
122
|
+
* the stamp after an on→off flip.
|
|
123
|
+
*/
|
|
124
|
+
pdg?: {
|
|
125
|
+
/** Worker-side per-function source-line cap, resolved (0 = unlimited). */
|
|
126
|
+
maxFunctionLines: number;
|
|
127
|
+
/** Emit-side per-function CFG edge cap, resolved (0 = unlimited). */
|
|
128
|
+
maxEdgesPerFunction: number;
|
|
129
|
+
/**
|
|
130
|
+
* Emit-side per-function REACHING_DEF edge cap, resolved (0 = unlimited;
|
|
131
|
+
* #2082 M2). ABSENT on an M1-era stamp — which is exactly what makes
|
|
132
|
+
* `pdgModeMismatch` trip on the first M2 run over an M1 index and force
|
|
133
|
+
* the full writeback that populates REACHING_DEF rows. Optional in the
|
|
134
|
+
* type for that reason; resolved (always present) on every M2+ write.
|
|
135
|
+
*/
|
|
136
|
+
maxReachingDefEdgesPerFunction?: number;
|
|
137
|
+
/**
|
|
138
|
+
* Per-function taint findings cap, resolved (0 = unlimited; #2083 M3).
|
|
139
|
+
* ABSENT on an M1/M2-era stamp — like `maxReachingDefEdgesPerFunction`,
|
|
140
|
+
* that absence is what trips `pdgModeMismatch` on the first M3 run and
|
|
141
|
+
* forces the full writeback that populates TAINTED/SANITIZES rows.
|
|
142
|
+
*/
|
|
143
|
+
maxTaintFindingsPerFunction?: number;
|
|
144
|
+
/** Per-finding taint hop cap, resolved (0 = unlimited; #2083 M3 KTD6 —
|
|
145
|
+
* bounds the persisted hop-encoded `reason`). Optional for the same
|
|
146
|
+
* M2-era-stamp upgrade reason as the findings cap. */
|
|
147
|
+
maxTaintHops?: number;
|
|
148
|
+
/**
|
|
149
|
+
* Digest of the built-in taint model the persisted findings were
|
|
150
|
+
* produced under (#2083 M3 KTD7/R7). Any model-content change ships a
|
|
151
|
+
* new digest → mismatch → full writeback repopulates taint edges
|
|
152
|
+
* without `--force`. Optional: absent on pre-M3 stamps.
|
|
153
|
+
*/
|
|
154
|
+
taintModelVersion?: string;
|
|
155
|
+
};
|
|
86
156
|
}
|
|
87
157
|
/**
|
|
88
158
|
* Bumped whenever incremental-indexing invariants change incompatibly.
|
|
@@ -107,15 +177,33 @@ export interface RegistryEntry {
|
|
|
107
177
|
/** See {@link RepoMeta.remoteUrl}. Mirrored from meta at register time. */
|
|
108
178
|
remoteUrl?: string;
|
|
109
179
|
stats?: RepoMeta['stats'];
|
|
180
|
+
/**
|
|
181
|
+
* Branch name owning the flat/primary index (#2106). Mirrors the flat
|
|
182
|
+
* `meta.branch`. Absent for legacy single-branch entries and non-git repos —
|
|
183
|
+
* additive and backward compatible.
|
|
184
|
+
*/
|
|
185
|
+
branch?: string;
|
|
186
|
+
/**
|
|
187
|
+
* Non-primary branch indexes for this same path (#2106). Absent when only the
|
|
188
|
+
* primary branch is indexed, preserving the one-entry-per-path model and the
|
|
189
|
+
* legacy registry shape.
|
|
190
|
+
*/
|
|
191
|
+
branches?: BranchSummary[];
|
|
110
192
|
}
|
|
111
193
|
/**
|
|
112
194
|
* Get the .gitnexus storage path for a repository
|
|
113
195
|
*/
|
|
114
196
|
export declare const getStoragePath: (repoPath: string) => string;
|
|
115
197
|
/**
|
|
116
|
-
* Get paths to key storage files
|
|
198
|
+
* Get paths to key storage files.
|
|
199
|
+
*
|
|
200
|
+
* `storagePath` is ALWAYS the flat `<repo>/.gitnexus` — content-addressed
|
|
201
|
+
* caches (`parse-cache/`, `parsedfile-store/`) live there and are shared
|
|
202
|
+
* across branches (#2106 KTD7). When `branch` is provided, only `lbugPath` and
|
|
203
|
+
* `metaPath` are scoped under `branches/<slug>/`; the flat call (no `branch`)
|
|
204
|
+
* returns byte-identical paths to the pre-multi-branch behavior.
|
|
117
205
|
*/
|
|
118
|
-
export declare const getStoragePaths: (repoPath: string) => {
|
|
206
|
+
export declare const getStoragePaths: (repoPath: string, branch?: string) => {
|
|
119
207
|
storagePath: string;
|
|
120
208
|
lbugPath: string;
|
|
121
209
|
metaPath: string;
|
|
@@ -213,6 +301,14 @@ export interface RegisterRepoOptions {
|
|
|
213
301
|
* re-run the full pipeline.
|
|
214
302
|
*/
|
|
215
303
|
allowDuplicateName?: boolean;
|
|
304
|
+
/**
|
|
305
|
+
* Non-primary branch this run indexed (#2106). When set, the branch's
|
|
306
|
+
* summary is upserted into the entry's `branches[]` and the primary
|
|
307
|
+
* top-level fields are left untouched. When `undefined`, this is a
|
|
308
|
+
* primary/flat run that refreshes the top-level fields (and preserves any
|
|
309
|
+
* existing branch summaries).
|
|
310
|
+
*/
|
|
311
|
+
branch?: string;
|
|
216
312
|
}
|
|
217
313
|
/**
|
|
218
314
|
* Thrown by {@link registerRepo} when a requested name is already in
|
|
@@ -262,6 +358,15 @@ export declare const registerRepo: (repoPath: string, meta: RepoMeta, opts?: Reg
|
|
|
262
358
|
* Called after `gitnexus clean`.
|
|
263
359
|
*/
|
|
264
360
|
export declare const unregisterRepo: (repoPath: string) => Promise<void>;
|
|
361
|
+
/**
|
|
362
|
+
* Remove a single non-primary branch's summary from a repo's registry entry
|
|
363
|
+
* (#2106 R7). Called by `gitnexus clean --branch`. Returns `true` when a
|
|
364
|
+
* matching `branches[]` summary was found and removed; `false` otherwise (so
|
|
365
|
+
* the CLI can report "no such indexed branch" without crashing). The top-level
|
|
366
|
+
* primary entry is left intact; an empty `branches[]` is dropped to keep the
|
|
367
|
+
* registry shape legacy-clean.
|
|
368
|
+
*/
|
|
369
|
+
export declare const removeBranchIndex: (repoPath: string, branch: string) => Promise<boolean>;
|
|
265
370
|
/**
|
|
266
371
|
* Thrown by {@link resolveRegistryEntry} when no registered repo matches
|
|
267
372
|
* the caller's target string (by alias, basename, remote-inferred name,
|
|
@@ -404,7 +509,14 @@ export declare const assertSafeStoragePath: (entry: RegistryEntry) => void;
|
|
|
404
509
|
export declare const resolveRegistryEntry: (entries: RegistryEntry[], target: string) => RegistryEntry;
|
|
405
510
|
/**
|
|
406
511
|
* List all registered repos from the global registry.
|
|
407
|
-
*
|
|
512
|
+
*
|
|
513
|
+
* With `validate: true`, prunes only entries whose index is *provably* gone
|
|
514
|
+
* (fs.access on .gitnexus/meta.json fails with ENOENT or ENOTDIR) and persists
|
|
515
|
+
* the result. Entries that are merely "not provably absent" — any other
|
|
516
|
+
* fs.access failure (EIO/EAGAIN/EBUSY/EACCES, etc.) — are KEPT, so a transient
|
|
517
|
+
* I/O storm cannot wipe the registry. A kept entry is therefore "not confirmed
|
|
518
|
+
* present," not "confirmed present"; downstream DB opens are independently and
|
|
519
|
+
* lazily guarded.
|
|
408
520
|
*/
|
|
409
521
|
export declare const listRegisteredRepos: (opts?: {
|
|
410
522
|
validate?: boolean;
|