gitnexus 1.6.13-rc.29 → 1.6.13-rc.30
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/cli/clean.js +98 -107
- package/dist/cli/doctor.d.ts +1 -1
- package/dist/cli/doctor.js +7 -9
- package/dist/cli/i18n/en.d.ts +1 -0
- package/dist/cli/i18n/en.js +1 -0
- package/dist/cli/i18n/resources.d.ts +2 -0
- package/dist/cli/i18n/zh-CN.d.ts +1 -0
- package/dist/cli/i18n/zh-CN.js +1 -0
- package/dist/core/group/service.js +13 -2
- package/dist/mcp/local/local-backend.js +5 -30
- package/dist/mcp/local/query-process-attaches.d.ts +49 -0
- package/dist/mcp/local/query-process-attaches.js +52 -0
- package/dist/mcp/tools.js +2 -2
- package/dist/server/analyze-job.d.ts +35 -1
- package/dist/server/analyze-job.js +139 -18
- package/dist/server/analyze-launch.js +90 -17
- package/dist/server/api.d.ts +33 -1
- package/dist/server/api.js +225 -46
- package/dist/server/ops-snapshot.d.ts +99 -0
- package/dist/server/ops-snapshot.js +240 -0
- package/dist/server/public-repo-id.d.ts +1 -0
- package/dist/server/public-repo-id.js +21 -0
- package/dist/server/repo-projection.d.ts +1 -0
- package/dist/server/repo-projection.js +3 -0
- package/dist/server/sse-progress.d.ts +5 -3
- package/dist/server/sse-progress.js +18 -8
- package/dist/storage/git.js +1 -3
- package/dist/storage/map-pool.d.ts +2 -0
- package/dist/storage/map-pool.js +18 -0
- package/dist/storage/repo-manager.d.ts +11 -0
- package/dist/storage/repo-manager.js +12 -18
- package/dist/storage/stale-branch-slots.d.ts +1 -1
- package/dist/storage/stale-branch-slots.js +232 -105
- package/package.json +1 -1
- package/web/assets/{abnfDiagram-PAR5IXJI-8u563MEU.js → abnfDiagram-PAR5IXJI-4yVR-hMp.js} +1 -1
- package/web/assets/{agent-DyMwsoMH.js → agent-3KMucoRj.js} +6 -4
- package/web/assets/{architectureDiagram-2PNWDENW-BjWbN2PO.js → architectureDiagram-2PNWDENW-5WR38bGK.js} +1 -1
- package/web/assets/{chunk-32MCUW2P-DEek-0Ht.js → chunk-32MCUW2P-9JwhsLAQ.js} +1 -1
- package/web/assets/{chunk-CLS4B6BI-D8hARACd.js → chunk-CLS4B6BI-cE_tHXKy.js} +1 -1
- package/web/assets/{chunk-NF366YI6-B-9n0uvs.js → chunk-NF366YI6-DgPaL5Pp.js} +1 -1
- package/web/assets/{chunk-SLDQ463B-BskBwVBC.js → chunk-SLDQ463B-Da9sF2jK.js} +1 -1
- package/web/assets/{classDiagram-FGAMPOII-DaSs7-5O.js → classDiagram-FGAMPOII-D5xpy-7A.js} +1 -1
- package/web/assets/{classDiagram-v2-AI5RJEK4-DaSs7-5O.js → classDiagram-v2-AI5RJEK4-D5xpy-7A.js} +1 -1
- package/web/assets/{cynefinDiagram-2OOWUY4M-DjbooVji.js → cynefinDiagram-2OOWUY4M-DABJiD-b.js} +1 -1
- package/web/assets/{dagre-MPVFI544-B-wqpxUN.js → dagre-MPVFI544-pYMKtWlh.js} +1 -1
- package/web/assets/{diagram-AE4NWXFW-Bkmf_O6L.js → diagram-AE4NWXFW-CZ04hcNr.js} +1 -1
- package/web/assets/{diagram-BUJID5UG-CbUC2Fie.js → diagram-BUJID5UG-D0nf-uew.js} +1 -1
- package/web/assets/{diagram-C44CZ5NI-dwPTAnfj.js → diagram-C44CZ5NI-ypaw31pk.js} +1 -1
- package/web/assets/{diagram-EYOLEXXS-OD8zf2mA.js → diagram-EYOLEXXS-BxDZOcdK.js} +1 -1
- package/web/assets/{ebnfDiagram-FTPZ3PV6-u4D1E694.js → ebnfDiagram-FTPZ3PV6-Oe7rPBmK.js} +1 -1
- package/web/assets/{erDiagram-H3ERR26V-BBtjOXBa.js → erDiagram-H3ERR26V-DSuDKH7X.js} +1 -1
- package/web/assets/{flowDiagram-YHGXBVSY-C7uNeQLW.js → flowDiagram-YHGXBVSY-DQisgp3A.js} +1 -1
- package/web/assets/index-6K-W6Rj-.css +2 -0
- package/web/assets/{index-BvcE-KTJ.js → index-fyU3YunG.js} +69 -68
- package/web/assets/{infoDiagram-LV547AFH-BHH9OuWU.js → infoDiagram-LV547AFH-Ce8Ylfhp.js} +1 -1
- package/web/assets/{ishikawaDiagram-PUJQGNDS-uat_n043.js → ishikawaDiagram-PUJQGNDS-Cf1XsKfx.js} +1 -1
- package/web/assets/{kanban-definition-JSQERAZB-Cc-n7-iv.js → kanban-definition-JSQERAZB-D1ygctat.js} +1 -1
- package/web/assets/{mindmap-definition-VBJCLQLM-CcnYi775.js → mindmap-definition-VBJCLQLM-EB7Llk3q.js} +1 -1
- package/web/assets/{node.browser-Dx1ppNym.js → node.browser-BISeS8dh.js} +1 -1
- package/web/assets/{pegDiagram-7WILOQTJ-CjsU2E8I.js → pegDiagram-7WILOQTJ-CyGZpmyT.js} +1 -1
- package/web/assets/{pieDiagram-EFA7CBSB-DFarVbT5.js → pieDiagram-EFA7CBSB-fl4j0AVe.js} +1 -1
- package/web/assets/{railroadDiagram-5NIQVNLQ-BOc4TKmb.js → railroadDiagram-5NIQVNLQ-Bztg21cQ.js} +1 -1
- package/web/assets/{requirementDiagram-SXREGOKZ-IYeU8wzI.js → requirementDiagram-SXREGOKZ-Dt_6vjm_.js} +1 -1
- package/web/assets/{sequenceDiagram-52ZFIFDL-Bvb237FL.js → sequenceDiagram-52ZFIFDL-Dl4-Ga10.js} +1 -1
- package/web/assets/{stateDiagram-3QAKPIVI-Bj5eSjtk.js → stateDiagram-3QAKPIVI-BJGGrzEz.js} +1 -1
- package/web/assets/{stateDiagram-v2-HN2P4RDZ-CgNVPo1K.js → stateDiagram-v2-HN2P4RDZ-BDlFhXzb.js} +1 -1
- package/web/assets/{swimlanes-QLVP42QD-BBXlMTuI.js → swimlanes-QLVP42QD-CCly8LhV.js} +1 -1
- package/web/assets/{swimlanesDiagram-JKPDBFCN-g8DoalWG.js → swimlanesDiagram-JKPDBFCN-bJms4nVX.js} +1 -1
- package/web/assets/{timeline-definition-GS45XAXW-Dkl4ie3U.js → timeline-definition-GS45XAXW-BsUXbFPa.js} +1 -1
- package/web/assets/{vennDiagram-QIQU2ZRJ-8swT0k92.js → vennDiagram-QIQU2ZRJ-C4UUmZOE.js} +1 -1
- package/web/assets/{wardleyDiagram-U6YNM66N-CtH6gYM2.js → wardleyDiagram-U6YNM66N-CUCmW8Uk.js} +1 -1
- package/web/assets/{xychartDiagram-XJAZEDNM-BkWLFLb6.js → xychartDiagram-XJAZEDNM-BB5mKEY8.js} +1 -1
- package/web/index.html +2 -2
- package/web/assets/index-NLQtvayY.css +0 -2
package/dist/cli/clean.js
CHANGED
|
@@ -14,136 +14,127 @@ import { listLocalHeads } from '../storage/git.js';
|
|
|
14
14
|
import { isContainedBranchDir, isDeleteCandidate, listStaleBranchSlots, removeBranchSlot, staleListingBlock, } from '../storage/stale-branch-slots.js';
|
|
15
15
|
import { cleanParkedLbugSidecars, inspectLbugSidecars, listParkedLbugSidecars, } from '../core/lbug/sidecar-recovery.js';
|
|
16
16
|
import { t } from './i18n/index.js';
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const entries = await listRegisteredRepos();
|
|
28
|
-
const entry = findRegistryEntryByRepoPath(entries, repo.repoPath);
|
|
29
|
-
let storagePath;
|
|
30
|
-
try {
|
|
31
|
-
storagePath = await requireDeletableStoragePath({
|
|
17
|
+
const resolveOwnedCwdStorage = async (refusePrefix) => {
|
|
18
|
+
const repo = await findRepo(process.cwd());
|
|
19
|
+
if (!repo) {
|
|
20
|
+
console.log(t('clean.notFoundHere'));
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
const [entries, storagePath] = await Promise.all([
|
|
25
|
+
listRegisteredRepos(),
|
|
26
|
+
requireDeletableStoragePath({
|
|
32
27
|
path: repo.repoPath,
|
|
33
28
|
storagePath: repo.storagePath,
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
throw err;
|
|
42
|
-
}
|
|
43
|
-
const slots = await listStaleBranchSlots({
|
|
44
|
-
repoPath: repo.repoPath,
|
|
29
|
+
}),
|
|
30
|
+
]);
|
|
31
|
+
return {
|
|
32
|
+
repo,
|
|
33
|
+
entry: findRegistryEntryByRepoPath(entries, repo.repoPath),
|
|
45
34
|
storagePath,
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
for (const slot of slots.filter((row) => row.reason === 'heads-unavailable')) {
|
|
53
|
-
console.log(` - ${formatStaleSlotLine(slot)}`);
|
|
54
|
-
}
|
|
55
|
-
return;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
if (err instanceof StorageDeletionError) {
|
|
39
|
+
logger.error(`${refusePrefix}${err.message}`);
|
|
40
|
+
return null;
|
|
56
41
|
}
|
|
57
|
-
|
|
58
|
-
|
|
42
|
+
throw err;
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
const printStaleSlotLines = (message, slots) => {
|
|
46
|
+
console.log(message);
|
|
47
|
+
for (const slot of slots) {
|
|
48
|
+
console.log(` - ${formatStaleSlotLine(slot)}`);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
const cleanStaleBranchSlots = async (force) => {
|
|
52
|
+
const owned = await resolveOwnedCwdStorage('Refusing to clean leftover branch indexes: ');
|
|
53
|
+
if (!owned)
|
|
54
|
+
return;
|
|
55
|
+
const { repo, entry, storagePath } = owned;
|
|
56
|
+
const slots = await listStaleBranchSlots({
|
|
57
|
+
repoPath: repo.repoPath,
|
|
58
|
+
storagePath,
|
|
59
|
+
branches: entry?.branches,
|
|
60
|
+
includeSize: !force,
|
|
61
|
+
});
|
|
62
|
+
const listingBlock = staleListingBlock(slots);
|
|
63
|
+
if (listingBlock === 'heads-unavailable') {
|
|
64
|
+
printStaleSlotLines(t('clean.stale.headsUnavailable'), slots.filter((row) => row.reason === 'heads-unavailable'));
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (listingBlock === 'listing-failed') {
|
|
68
|
+
console.log(t('clean.stale.listingFailed'));
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const candidates = slots.filter(isDeleteCandidate);
|
|
72
|
+
const probeFailed = slots.filter((slot) => slot.reason === 'probe-failed');
|
|
73
|
+
if (candidates.length === 0) {
|
|
74
|
+
if (probeFailed.length > 0) {
|
|
75
|
+
printStaleSlotLines(t('clean.stale.probeFailed'), probeFailed);
|
|
59
76
|
return;
|
|
60
77
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
};
|
|
69
|
-
if (candidates.length === 0) {
|
|
70
|
-
if (probeFailed.length > 0) {
|
|
71
|
-
printProbeFailed();
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
console.log(t('clean.stale.none'));
|
|
75
|
-
return;
|
|
78
|
+
console.log(t('clean.stale.none'));
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (!force) {
|
|
82
|
+
printStaleSlotLines(t('clean.stale.preview', { count: candidates.length }), candidates);
|
|
83
|
+
if (probeFailed.length > 0) {
|
|
84
|
+
printStaleSlotLines(t('clean.stale.probeFailed'), probeFailed);
|
|
76
85
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
console.log(`\n${t('common.runForceConfirm')}`);
|
|
86
|
+
console.log(`\n${t('common.runForceConfirm')}`);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
let deletedAny = false;
|
|
90
|
+
for (const slot of candidates) {
|
|
91
|
+
const heads = listLocalHeads(repo.repoPath);
|
|
92
|
+
if (heads === null) {
|
|
93
|
+
console.log(deletedAny ? t('clean.stale.remainingSkipped') : t('clean.stale.headsUnavailable'));
|
|
86
94
|
return;
|
|
87
95
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
console.log(t('clean.stale.headsUnavailable'));
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
if (heads.includes(slot.branch)) {
|
|
95
|
-
console.log(t('clean.stale.skippedLive', { branch: slot.branch }));
|
|
96
|
-
continue;
|
|
97
|
-
}
|
|
98
|
-
const result = await removeBranchSlot({
|
|
99
|
-
repoPath: repo.repoPath,
|
|
100
|
-
storagePath,
|
|
101
|
-
branch: slot.branch,
|
|
102
|
-
dir: slot.dir,
|
|
103
|
-
});
|
|
104
|
-
if (!result.ok) {
|
|
105
|
-
console.log(t('clean.stale.failed', { branch: slot.branch }));
|
|
106
|
-
logger.error({ err: result.error }, 'Failed to delete leftover branch index:');
|
|
107
|
-
continue;
|
|
108
|
-
}
|
|
109
|
-
console.log(t('clean.stale.deleted', { branch: slot.branch }));
|
|
96
|
+
if (heads.includes(slot.branch)) {
|
|
97
|
+
console.log(t('clean.stale.skippedLive', { branch: slot.branch }));
|
|
98
|
+
continue;
|
|
110
99
|
}
|
|
111
|
-
|
|
112
|
-
|
|
100
|
+
const result = await removeBranchSlot({
|
|
101
|
+
repoPath: repo.repoPath,
|
|
102
|
+
storagePath,
|
|
103
|
+
branch: slot.branch,
|
|
104
|
+
dir: slot.dir,
|
|
105
|
+
});
|
|
106
|
+
if (!result.ok) {
|
|
107
|
+
console.log(t('clean.stale.failed', { branch: slot.branch }));
|
|
108
|
+
logger.error({ err: result.error }, 'Failed to delete leftover branch index:');
|
|
109
|
+
continue;
|
|
113
110
|
}
|
|
111
|
+
deletedAny = true;
|
|
112
|
+
console.log(t('clean.stale.deleted', { branch: slot.branch }));
|
|
113
|
+
}
|
|
114
|
+
if (probeFailed.length > 0) {
|
|
115
|
+
printStaleSlotLines(t('clean.stale.probeFailed'), probeFailed);
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
export const cleanCommand = async (options) => {
|
|
119
|
+
// --stale: reclaim leftover per-branch slots whose recorded branch is not
|
|
120
|
+
// a live local head (#3331). Exclusive arm before --branch.
|
|
121
|
+
if (options?.stale) {
|
|
122
|
+
await cleanStaleBranchSlots(options.force === true);
|
|
114
123
|
return;
|
|
115
124
|
}
|
|
116
125
|
// --branch <name>: remove a single non-primary branch's index (#2106 R7).
|
|
117
126
|
// Resolve against the RECORDED branches[] summary (never by slugging the
|
|
118
127
|
// user's raw input, which can disagree with the index-time-sanitized label).
|
|
119
128
|
if (options?.branch) {
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
if (!repo) {
|
|
123
|
-
console.log(t('clean.notFoundHere'));
|
|
129
|
+
const owned = await resolveOwnedCwdStorage('Refusing to clean branch index: ');
|
|
130
|
+
if (!owned)
|
|
124
131
|
return;
|
|
125
|
-
}
|
|
126
|
-
const entries = await listRegisteredRepos();
|
|
127
|
-
const entry = findRegistryEntryByRepoPath(entries, repo.repoPath);
|
|
132
|
+
const { repo, entry, storagePath } = owned;
|
|
128
133
|
const summary = entry?.branches?.find((b) => b.branch === options.branch);
|
|
129
134
|
if (!summary) {
|
|
130
135
|
console.log(t('clean.branchNotIndexed', { branch: options.branch }));
|
|
131
136
|
return;
|
|
132
137
|
}
|
|
133
|
-
let storagePath;
|
|
134
|
-
try {
|
|
135
|
-
storagePath = await requireDeletableStoragePath({
|
|
136
|
-
path: repo.repoPath,
|
|
137
|
-
storagePath: repo.storagePath,
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
catch (err) {
|
|
141
|
-
if (err instanceof StorageDeletionError) {
|
|
142
|
-
logger.error(`Refusing to clean branch index: ${err.message}`);
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
throw err;
|
|
146
|
-
}
|
|
147
138
|
const { lbugPath } = getStoragePaths(repo.repoPath, summary.branch, storagePath);
|
|
148
139
|
const branchDir = path.dirname(lbugPath);
|
|
149
140
|
if (!isContainedBranchDir(storagePath, branchDir)) {
|
package/dist/cli/doctor.d.ts
CHANGED
|
@@ -66,7 +66,7 @@ export declare function nativeStatusLine(check: NativeCheckResult): string;
|
|
|
66
66
|
* When heads cannot be listed, do not title rows as orphaned or name
|
|
67
67
|
* `clean --stale` (#3337): that command refuses to delete in the same state.
|
|
68
68
|
*/
|
|
69
|
-
export declare function
|
|
69
|
+
export declare function leftoverBranchSlotDoctorLines(slots: StaleBranchSlot[]): string[];
|
|
70
70
|
export declare function cachedUpdateDoctorLine(options: {
|
|
71
71
|
installedVersion: string;
|
|
72
72
|
eligible: boolean;
|
package/dist/cli/doctor.js
CHANGED
|
@@ -153,7 +153,7 @@ export function nativeStatusLine(check) {
|
|
|
153
153
|
* When heads cannot be listed, do not title rows as orphaned or name
|
|
154
154
|
* `clean --stale` (#3337): that command refuses to delete in the same state.
|
|
155
155
|
*/
|
|
156
|
-
export function
|
|
156
|
+
export function leftoverBranchSlotDoctorLines(slots) {
|
|
157
157
|
if (slots.length === 0)
|
|
158
158
|
return [];
|
|
159
159
|
const listingBlock = staleListingBlock(slots);
|
|
@@ -315,24 +315,22 @@ export const doctorCommand = async () => {
|
|
|
315
315
|
console.log(` ${padDisplayEnd('', 12)}${cudaRedirect.detail}`);
|
|
316
316
|
}
|
|
317
317
|
}
|
|
318
|
-
//
|
|
319
|
-
|
|
320
|
-
// for recorded branch slugs; do not report leftovers for every registered
|
|
321
|
-
// repo, and never delete.
|
|
322
|
-
const [cwdRepo, entries] = await Promise.all([findRepo(process.cwd()), listRegisteredRepos()]);
|
|
318
|
+
// Cwd leftover-slot report only; never delete.
|
|
319
|
+
const cwdRepo = await findRepo(process.cwd());
|
|
323
320
|
if (!cwdRepo)
|
|
324
321
|
return;
|
|
322
|
+
const entries = await listRegisteredRepos();
|
|
325
323
|
const entry = findRegistryEntryByRepoPath(entries, cwdRepo.repoPath);
|
|
326
324
|
const slots = await listStaleBranchSlots({
|
|
327
325
|
repoPath: cwdRepo.repoPath,
|
|
328
326
|
storagePath: cwdRepo.storagePath,
|
|
329
327
|
branches: entry?.branches,
|
|
330
328
|
});
|
|
331
|
-
const
|
|
332
|
-
if (
|
|
329
|
+
const leftoverLines = leftoverBranchSlotDoctorLines(slots);
|
|
330
|
+
if (leftoverLines.length === 0)
|
|
333
331
|
return;
|
|
334
332
|
console.log('');
|
|
335
|
-
for (const line of
|
|
333
|
+
for (const line of leftoverLines) {
|
|
336
334
|
console.log(line);
|
|
337
335
|
}
|
|
338
336
|
};
|
package/dist/cli/i18n/en.d.ts
CHANGED
|
@@ -59,6 +59,7 @@ export declare const en: {
|
|
|
59
59
|
readonly 'clean.stale.item': '{{branch}} {{reason}} {{path}} {{size}}';
|
|
60
60
|
readonly 'clean.stale.registryOnlyPath': '(registry only)';
|
|
61
61
|
readonly 'clean.stale.headsUnavailable': 'Could not list local heads; leftover branch indexes were not deleted. Re-run `gitnexus clean --stale` when git is available.';
|
|
62
|
+
readonly 'clean.stale.remainingSkipped': 'Could not list local heads; remaining leftover branch indexes were skipped. Re-run `gitnexus clean --stale` when git is available.';
|
|
62
63
|
readonly 'clean.stale.listingFailed': 'Could not read leftover branch index directories; leftover indexes were not deleted. Check permissions on the branches/ directory and re-run `gitnexus clean --stale`.';
|
|
63
64
|
readonly 'clean.stale.probeFailed': 'Could not inspect leftover branch index path(s); those slots were not deleted.';
|
|
64
65
|
readonly 'clean.stale.deleted': 'Deleted leftover branch index: {{branch}}';
|
package/dist/cli/i18n/en.js
CHANGED
|
@@ -59,6 +59,7 @@ export const en = {
|
|
|
59
59
|
'clean.stale.item': '{{branch}} {{reason}} {{path}} {{size}}',
|
|
60
60
|
'clean.stale.registryOnlyPath': '(registry only)',
|
|
61
61
|
'clean.stale.headsUnavailable': 'Could not list local heads; leftover branch indexes were not deleted. Re-run `gitnexus clean --stale` when git is available.',
|
|
62
|
+
'clean.stale.remainingSkipped': 'Could not list local heads; remaining leftover branch indexes were skipped. Re-run `gitnexus clean --stale` when git is available.',
|
|
62
63
|
'clean.stale.listingFailed': 'Could not read leftover branch index directories; leftover indexes were not deleted. Check permissions on the branches/ directory and re-run `gitnexus clean --stale`.',
|
|
63
64
|
'clean.stale.probeFailed': 'Could not inspect leftover branch index path(s); those slots were not deleted.',
|
|
64
65
|
'clean.stale.deleted': 'Deleted leftover branch index: {{branch}}',
|
|
@@ -60,6 +60,7 @@ export declare const cliResources: {
|
|
|
60
60
|
readonly 'clean.stale.item': '{{branch}} {{reason}} {{path}} {{size}}';
|
|
61
61
|
readonly 'clean.stale.registryOnlyPath': '(registry only)';
|
|
62
62
|
readonly 'clean.stale.headsUnavailable': 'Could not list local heads; leftover branch indexes were not deleted. Re-run `gitnexus clean --stale` when git is available.';
|
|
63
|
+
readonly 'clean.stale.remainingSkipped': 'Could not list local heads; remaining leftover branch indexes were skipped. Re-run `gitnexus clean --stale` when git is available.';
|
|
63
64
|
readonly 'clean.stale.listingFailed': 'Could not read leftover branch index directories; leftover indexes were not deleted. Check permissions on the branches/ directory and re-run `gitnexus clean --stale`.';
|
|
64
65
|
readonly 'clean.stale.probeFailed': 'Could not inspect leftover branch index path(s); those slots were not deleted.';
|
|
65
66
|
readonly 'clean.stale.deleted': 'Deleted leftover branch index: {{branch}}';
|
|
@@ -366,6 +367,7 @@ export declare const cliResources: {
|
|
|
366
367
|
'clean.stale.item': string;
|
|
367
368
|
'clean.stale.registryOnlyPath': string;
|
|
368
369
|
'clean.stale.headsUnavailable': string;
|
|
370
|
+
'clean.stale.remainingSkipped': string;
|
|
369
371
|
'clean.stale.listingFailed': string;
|
|
370
372
|
'clean.stale.probeFailed': string;
|
|
371
373
|
'clean.stale.deleted': string;
|
package/dist/cli/i18n/zh-CN.d.ts
CHANGED
|
@@ -59,6 +59,7 @@ export declare const zhCN: {
|
|
|
59
59
|
'clean.stale.item': string;
|
|
60
60
|
'clean.stale.registryOnlyPath': string;
|
|
61
61
|
'clean.stale.headsUnavailable': string;
|
|
62
|
+
'clean.stale.remainingSkipped': string;
|
|
62
63
|
'clean.stale.listingFailed': string;
|
|
63
64
|
'clean.stale.probeFailed': string;
|
|
64
65
|
'clean.stale.deleted': string;
|
package/dist/cli/i18n/zh-CN.js
CHANGED
|
@@ -59,6 +59,7 @@ export const zhCN = {
|
|
|
59
59
|
'clean.stale.item': '{{branch}} {{reason}} {{path}} {{size}}',
|
|
60
60
|
'clean.stale.registryOnlyPath': '(仅注册表)',
|
|
61
61
|
'clean.stale.headsUnavailable': '无法列出本地分支,因此未删除残留分支索引。请在 git 可用后重新运行 `gitnexus clean --stale`。',
|
|
62
|
+
'clean.stale.remainingSkipped': '无法列出本地分支,因此已跳过其余残留分支索引。请在 git 可用后重新运行 `gitnexus clean --stale`。',
|
|
62
63
|
'clean.stale.listingFailed': '无法读取残留分支索引目录,因此未删除残留索引。请检查 branches/ 目录权限后重新运行 `gitnexus clean --stale`。',
|
|
63
64
|
'clean.stale.probeFailed': '无法检查残留分支索引路径,因此未删除这些槽位。',
|
|
64
65
|
'clean.stale.deleted': '已删除残留分支索引:{{branch}}',
|
|
@@ -41,8 +41,19 @@ function isStoredContract(raw) {
|
|
|
41
41
|
}
|
|
42
42
|
function filterQueryByServicePrefix(queryResult, servicePrefix) {
|
|
43
43
|
const symbols = (queryResult.process_symbols || []).filter((s) => fileMatchesServicePrefix(typeof s.filePath === 'string' ? s.filePath : undefined, servicePrefix));
|
|
44
|
-
const
|
|
45
|
-
const
|
|
44
|
+
const countByProcess = new Map();
|
|
45
|
+
for (const symbol of symbols) {
|
|
46
|
+
const pid = String(symbol.process_id ?? '');
|
|
47
|
+
if (!pid)
|
|
48
|
+
continue;
|
|
49
|
+
countByProcess.set(pid, (countByProcess.get(pid) ?? 0) + 1);
|
|
50
|
+
}
|
|
51
|
+
const processes = (queryResult.processes || []).filter((p) => countByProcess.has(String(p.id)));
|
|
52
|
+
for (const process of processes) {
|
|
53
|
+
const id = String(process.id ?? '');
|
|
54
|
+
if (id)
|
|
55
|
+
process.symbol_count = countByProcess.get(id) ?? 0;
|
|
56
|
+
}
|
|
46
57
|
return { processes, process_symbols: symbols };
|
|
47
58
|
}
|
|
48
59
|
function isCrossLink(raw) {
|
|
@@ -15,6 +15,7 @@ import { querySpringAopMetadata } from './aop-metadata.js';
|
|
|
15
15
|
import { queryConvexDispatchMetadata } from './convex-metadata.js';
|
|
16
16
|
import { isValidQueryParams } from '../../core/lbug/query-params.js';
|
|
17
17
|
import { toDisplayLine } from './line-display.js';
|
|
18
|
+
import { shapeQueryProcessAttaches } from './query-process-attaches.js';
|
|
18
19
|
import { LBUG_ID_PROBE_BATCH_SIZE, LBUG_QUERY_BATCH_SIZE } from '../../core/lbug/query-batch.js';
|
|
19
20
|
import { chunk, mapConcurrent } from '../../lib/utils.js';
|
|
20
21
|
import { pathSuffixOf } from './path-predicate.js';
|
|
@@ -2750,35 +2751,9 @@ export class LocalBackend {
|
|
|
2750
2751
|
}
|
|
2751
2752
|
// Step 4: Build response
|
|
2752
2753
|
timer.start('formatting');
|
|
2753
|
-
const processes = rankedProcesses
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
priority: Math.round(p.priority * 1000) / 1000,
|
|
2757
|
-
symbol_count: p.symbols.length,
|
|
2758
|
-
process_type: p.processType,
|
|
2759
|
-
step_count: p.stepCount,
|
|
2760
|
-
...(p.routes && p.routes.length > 0
|
|
2761
|
-
? {
|
|
2762
|
-
route: p.routes[0].url,
|
|
2763
|
-
method: p.routes[0].method || undefined,
|
|
2764
|
-
routes: p.routes,
|
|
2765
|
-
}
|
|
2766
|
-
: {}),
|
|
2767
|
-
...(chainByProcessId.has(p.id) ? { chain: chainByProcessId.get(p.id) } : {}),
|
|
2768
|
-
}));
|
|
2769
|
-
const processSymbols = rankedProcesses.flatMap((p) => p.symbols.slice(0, maxSymbolsPerProcess).map((s) => ({
|
|
2770
|
-
...s,
|
|
2771
|
-
// mark the entry-point symbol so an agent reading the
|
|
2772
|
-
// process can tell procedure vs. workflow vs. helper at a glance.
|
|
2773
|
-
...(p.entryPointId && s.id === p.entryPointId ? { is_entry_point: true } : {}),
|
|
2774
|
-
})));
|
|
2775
|
-
// Deduplicate process_symbols by id
|
|
2776
|
-
const seen = new Set();
|
|
2777
|
-
const dedupedSymbols = processSymbols.filter((s) => {
|
|
2778
|
-
if (seen.has(s.id))
|
|
2779
|
-
return false;
|
|
2780
|
-
seen.add(s.id);
|
|
2781
|
-
return true;
|
|
2754
|
+
const { processes, process_symbols } = shapeQueryProcessAttaches(rankedProcesses, {
|
|
2755
|
+
maxSymbolsPerProcess,
|
|
2756
|
+
chainByProcessId,
|
|
2782
2757
|
});
|
|
2783
2758
|
timer.stop(); // formatting
|
|
2784
2759
|
// End-to-end wall time — deliberately a separate mark so callers can
|
|
@@ -2941,7 +2916,7 @@ export class LocalBackend {
|
|
|
2941
2916
|
const ftsPartial = ftsUsed && !!ftsQueryErrors;
|
|
2942
2917
|
return {
|
|
2943
2918
|
processes,
|
|
2944
|
-
process_symbols
|
|
2919
|
+
process_symbols,
|
|
2945
2920
|
definitions: definitions.slice(0, 20), // cap standalone definitions
|
|
2946
2921
|
timing,
|
|
2947
2922
|
...(contentAvailability ? { contentAvailability } : {}),
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shape ranked query processes into the MCP `process_symbols` attach list
|
|
3
|
+
* and honest per-process `symbol_count` (#3351).
|
|
4
|
+
*
|
|
5
|
+
* Aggregation already pushes a hub onto every owning process. This helper
|
|
6
|
+
* is the last shaping step: slice each process to `max_symbols`, keep one
|
|
7
|
+
* row per `(id, process_id)`, then set `symbol_count` from those rows.
|
|
8
|
+
*/
|
|
9
|
+
export type QueryProcessAttach = {
|
|
10
|
+
id: string;
|
|
11
|
+
process_id: string;
|
|
12
|
+
is_entry_point?: boolean;
|
|
13
|
+
} & Record<string, unknown>;
|
|
14
|
+
export type RankedQueryProcess<S extends QueryProcessAttach = QueryProcessAttach> = {
|
|
15
|
+
id: string;
|
|
16
|
+
label?: string;
|
|
17
|
+
heuristicLabel?: string;
|
|
18
|
+
processType?: string;
|
|
19
|
+
stepCount?: number;
|
|
20
|
+
entryPointId?: string;
|
|
21
|
+
priority: number;
|
|
22
|
+
symbols: S[];
|
|
23
|
+
routes?: Array<{
|
|
24
|
+
url: string;
|
|
25
|
+
method?: string;
|
|
26
|
+
}>;
|
|
27
|
+
};
|
|
28
|
+
export type QueryProcessCard = {
|
|
29
|
+
id: string;
|
|
30
|
+
summary: string;
|
|
31
|
+
priority: number;
|
|
32
|
+
symbol_count: number;
|
|
33
|
+
process_type?: string;
|
|
34
|
+
step_count?: number;
|
|
35
|
+
route?: string;
|
|
36
|
+
method?: string;
|
|
37
|
+
routes?: Array<{
|
|
38
|
+
url: string;
|
|
39
|
+
method?: string;
|
|
40
|
+
}>;
|
|
41
|
+
chain?: unknown;
|
|
42
|
+
};
|
|
43
|
+
export declare function shapeQueryProcessAttaches<S extends QueryProcessAttach>(rankedProcesses: Array<RankedQueryProcess<S>>, options: {
|
|
44
|
+
maxSymbolsPerProcess: number;
|
|
45
|
+
chainByProcessId?: ReadonlyMap<string, unknown>;
|
|
46
|
+
}): {
|
|
47
|
+
processes: QueryProcessCard[];
|
|
48
|
+
process_symbols: S[];
|
|
49
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shape ranked query processes into the MCP `process_symbols` attach list
|
|
3
|
+
* and honest per-process `symbol_count` (#3351).
|
|
4
|
+
*
|
|
5
|
+
* Aggregation already pushes a hub onto every owning process. This helper
|
|
6
|
+
* is the last shaping step: slice each process to `max_symbols`, keep one
|
|
7
|
+
* row per `(id, process_id)`, then set `symbol_count` from those rows.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Pair key for attach rows. Both `id` and `process_id` routinely contain
|
|
11
|
+
* colons (`func:validate`, `proc:login-flow`), so a `:` join would collide.
|
|
12
|
+
*/
|
|
13
|
+
function attachPairKey(id, processId) {
|
|
14
|
+
return `${id}\0${processId}`;
|
|
15
|
+
}
|
|
16
|
+
export function shapeQueryProcessAttaches(rankedProcesses, options) {
|
|
17
|
+
const { maxSymbolsPerProcess, chainByProcessId } = options;
|
|
18
|
+
const seen = new Set();
|
|
19
|
+
const process_symbols = [];
|
|
20
|
+
const countByProcess = new Map();
|
|
21
|
+
for (const p of rankedProcesses) {
|
|
22
|
+
for (const s of p.symbols.slice(0, maxSymbolsPerProcess)) {
|
|
23
|
+
const key = attachPairKey(s.id, s.process_id);
|
|
24
|
+
if (seen.has(key))
|
|
25
|
+
continue;
|
|
26
|
+
seen.add(key);
|
|
27
|
+
const row = p.entryPointId && s.id === p.entryPointId ? { ...s, is_entry_point: true } : s;
|
|
28
|
+
process_symbols.push(row);
|
|
29
|
+
countByProcess.set(s.process_id, (countByProcess.get(s.process_id) ?? 0) + 1);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
const processes = rankedProcesses.map((p) => {
|
|
33
|
+
const card = {
|
|
34
|
+
id: p.id,
|
|
35
|
+
summary: p.heuristicLabel || p.label || '',
|
|
36
|
+
priority: Math.round(p.priority * 1000) / 1000,
|
|
37
|
+
symbol_count: countByProcess.get(p.id) ?? 0,
|
|
38
|
+
process_type: p.processType,
|
|
39
|
+
step_count: p.stepCount,
|
|
40
|
+
};
|
|
41
|
+
if (p.routes && p.routes.length > 0) {
|
|
42
|
+
card.route = p.routes[0].url;
|
|
43
|
+
card.method = p.routes[0].method || undefined;
|
|
44
|
+
card.routes = p.routes;
|
|
45
|
+
}
|
|
46
|
+
const chain = chainByProcessId?.get(p.id);
|
|
47
|
+
if (chain !== undefined)
|
|
48
|
+
card.chain = chain;
|
|
49
|
+
return card;
|
|
50
|
+
});
|
|
51
|
+
return { processes, process_symbols };
|
|
52
|
+
}
|
package/dist/mcp/tools.js
CHANGED
|
@@ -100,14 +100,14 @@ specify the "repo" parameter explicitly.`,
|
|
|
100
100
|
{
|
|
101
101
|
name: 'query',
|
|
102
102
|
description: `Query the code knowledge graph for execution flows related to a concept.
|
|
103
|
-
Returns processes
|
|
103
|
+
Returns ranked processes plus a flat process_symbols list. Join processes[].id to process_symbols[].process_id.
|
|
104
104
|
|
|
105
105
|
WHEN TO USE: Understanding how code works together. Use this when you need execution flows and relationships, not just file matches. Complements grep/IDE search.
|
|
106
106
|
AFTER THIS: Use context() on a specific symbol for 360-degree view (callers, callees, categorized refs).
|
|
107
107
|
|
|
108
108
|
Returns results grouped by process (execution flow):
|
|
109
109
|
- processes: ranked execution flows with relevance priority. When a process has an HTTP endpoint, each item includes route and method string aliases plus routes: [{ url, method? }] (same shape as context). When chain_depth > 0, each item also includes chain — layered upstream callers + downstream callees from the process entry symbol (same BFS as context({chain_depth})).
|
|
110
|
-
- process_symbols: search-hit symbols in those flows with file locations and module (functional area). When the process entry is among those hits, it is marked is_entry_point: true.
|
|
110
|
+
- process_symbols: search-hit symbols in those flows with file locations and module (functional area). On the single-repo envelope { processes, process_symbols, definitions }: One row per (id, process_id) — the same symbol id may appear under more than one process. Join a process to its rows by process_id; symbol_count is the number of those rows. When the process entry is among those hits, it is marked is_entry_point: true. A repo of "@<group>" returns { group, query, results, per_repo } and does not include process_symbols. results[].symbol_count is the member's post-slice attach count; when service is set, it counts only attaches under that prefix. To get process_symbols for one member, query again with repo "@<group>/<memberPath>" (member path from group.yaml, or results[]._repo).
|
|
111
111
|
- definitions: standalone types/interfaces not in any process. Keyword hits on Route URLs (route_fts) are bridged to their handler via HANDLES_ROUTE (handlerSymbolId, routes) when the edge exists; use route_map({route}) for the full HTTP surface.
|
|
112
112
|
|
|
113
113
|
Hybrid ranking: BM25 keyword + semantic vector search, ranked by Reciprocal Rank Fusion.
|
|
@@ -74,6 +74,9 @@ export declare class JobManager {
|
|
|
74
74
|
private children;
|
|
75
75
|
private abortControllers;
|
|
76
76
|
private timeouts;
|
|
77
|
+
private cancelGraceTimers;
|
|
78
|
+
/** Cancel reason to apply when a still-running worker exits. */
|
|
79
|
+
private pendingCancelReasons;
|
|
77
80
|
private emitter;
|
|
78
81
|
private cleanupTimer;
|
|
79
82
|
constructor();
|
|
@@ -99,13 +102,44 @@ export declare class JobManager {
|
|
|
99
102
|
updateJob(id: string, update: Partial<Pick<AnalyzeJob, 'status' | 'progress' | 'error' | 'partial' | 'repoPath' | 'repoName' | 'completedAt'>>): void;
|
|
100
103
|
/** Register a child process for a job — enables cancellation and timeout. */
|
|
101
104
|
registerChild(jobId: string, child: ChildProcess): void;
|
|
105
|
+
/** True while cancel was requested and the worker has not exited yet. */
|
|
106
|
+
hasPendingCancel(jobId: string): boolean;
|
|
107
|
+
/**
|
|
108
|
+
* Drop a registered child without waiting for `exit`. Spawn failures emit
|
|
109
|
+
* `error` and never `exit`, which would otherwise leave `isSlotOccupied`
|
|
110
|
+
* true forever after the job is already failed.
|
|
111
|
+
*/
|
|
112
|
+
releaseChild(jobId: string): void;
|
|
113
|
+
/**
|
|
114
|
+
* Apply a stored cancel reason if one is pending. Returns true when a reason
|
|
115
|
+
* was consumed. Used by the worker-exit handler and by analyze-launch when
|
|
116
|
+
* the child reports a generic cancel IPC — that message must not overwrite
|
|
117
|
+
* the caller's reason (timeout vs user cancel).
|
|
118
|
+
*/
|
|
119
|
+
applyPendingCancel(jobId: string): boolean;
|
|
102
120
|
/** Register cancellable in-process work for a job. */
|
|
103
121
|
registerAbortController(jobId: string, controller: AbortController): void;
|
|
104
|
-
/**
|
|
122
|
+
/**
|
|
123
|
+
* Cancel a running job.
|
|
124
|
+
*
|
|
125
|
+
* The worker is asked to stop over IPC first (`{ type: 'cancel' }`), which
|
|
126
|
+
* lets it reach a JS-visible safe point and checkpoint before exiting —
|
|
127
|
+
* the same cross-platform control path `core/auto-sync` uses. A signal is
|
|
128
|
+
* sent only as a bounded fallback: on Windows `child.kill('SIGTERM')` is a
|
|
129
|
+
* forceful termination (Node ignores the signal name there), so leading
|
|
130
|
+
* with it could kill the worker mid LadybugDB write.
|
|
131
|
+
*/
|
|
105
132
|
cancelJob(jobId: string, reason?: string): boolean;
|
|
106
133
|
/** Subscribe to progress events for a job. Returns unsubscribe function. */
|
|
107
134
|
onProgress(jobId: string, listener: (progress: AnalyzeJobProgress) => void): () => void;
|
|
135
|
+
/**
|
|
136
|
+
* Ask a worker to shut down: IPC cancel now, signal after a grace period if
|
|
137
|
+
* it has not exited on its own. The grace timer is cleared by the child's
|
|
138
|
+
* `exit` handler registered in `registerChild`.
|
|
139
|
+
*/
|
|
140
|
+
private requestChildShutdown;
|
|
108
141
|
dispose(): void;
|
|
109
142
|
private isTerminal;
|
|
143
|
+
private isSlotOccupied;
|
|
110
144
|
private cleanup;
|
|
111
145
|
}
|