spidersan 0.4.5 → 0.5.0
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/CHANGELOG.md +59 -0
- package/README.md +58 -5
- package/dist/bin/spidersan.d.ts.map +1 -1
- package/dist/bin/spidersan.js +12 -1
- package/dist/bin/spidersan.js.map +1 -1
- package/dist/commands/abandon.d.ts.map +1 -1
- package/dist/commands/abandon.js +4 -2
- package/dist/commands/abandon.js.map +1 -1
- package/dist/commands/auto.js +6 -6
- package/dist/commands/auto.js.map +1 -1
- package/dist/commands/cleanup.d.ts.map +1 -1
- package/dist/commands/cleanup.js +3 -8
- package/dist/commands/cleanup.js.map +1 -1
- package/dist/commands/config.js +1 -1
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/conflicts.d.ts.map +1 -1
- package/dist/commands/conflicts.js +200 -15
- package/dist/commands/conflicts.js.map +1 -1
- package/dist/commands/cross-conflicts.d.ts +17 -0
- package/dist/commands/cross-conflicts.d.ts.map +1 -0
- package/dist/commands/cross-conflicts.js +305 -0
- package/dist/commands/cross-conflicts.js.map +1 -0
- package/dist/commands/daily.d.ts +7 -0
- package/dist/commands/daily.d.ts.map +1 -0
- package/dist/commands/daily.js +171 -0
- package/dist/commands/daily.js.map +1 -0
- package/dist/commands/dashboard.d.ts +22 -0
- package/dist/commands/dashboard.d.ts.map +1 -0
- package/dist/commands/dashboard.js +401 -0
- package/dist/commands/dashboard.js.map +1 -0
- package/dist/commands/depends.js +2 -2
- package/dist/commands/depends.js.map +1 -1
- package/dist/commands/doctor.d.ts +22 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +215 -14
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/github-sync.d.ts +9 -0
- package/dist/commands/github-sync.d.ts.map +1 -0
- package/dist/commands/github-sync.js +180 -0
- package/dist/commands/github-sync.js.map +1 -0
- package/dist/commands/index.d.ts +10 -0
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +16 -0
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/log.d.ts +3 -4
- package/dist/commands/log.d.ts.map +1 -1
- package/dist/commands/log.js +102 -87
- package/dist/commands/log.js.map +1 -1
- package/dist/commands/merge-order.js +8 -1
- package/dist/commands/merge-order.js.map +1 -1
- package/dist/commands/merged.d.ts.map +1 -1
- package/dist/commands/merged.js +4 -2
- package/dist/commands/merged.js.map +1 -1
- package/dist/commands/pulse.d.ts +6 -2
- package/dist/commands/pulse.d.ts.map +1 -1
- package/dist/commands/pulse.js +152 -202
- package/dist/commands/pulse.js.map +1 -1
- package/dist/commands/queen.d.ts +25 -0
- package/dist/commands/queen.d.ts.map +1 -0
- package/dist/commands/queen.js +309 -0
- package/dist/commands/queen.js.map +1 -0
- package/dist/commands/ready-check.d.ts.map +1 -1
- package/dist/commands/ready-check.js +42 -27
- package/dist/commands/ready-check.js.map +1 -1
- package/dist/commands/rebase-helper.d.ts.map +1 -1
- package/dist/commands/rebase-helper.js +13 -5
- package/dist/commands/rebase-helper.js.map +1 -1
- package/dist/commands/register.d.ts +1 -0
- package/dist/commands/register.d.ts.map +1 -1
- package/dist/commands/register.js +46 -19
- package/dist/commands/register.js.map +1 -1
- package/dist/commands/registry-sync.d.ts +9 -0
- package/dist/commands/registry-sync.d.ts.map +1 -0
- package/dist/commands/registry-sync.js +163 -0
- package/dist/commands/registry-sync.js.map +1 -0
- package/dist/commands/rescue.d.ts.map +1 -1
- package/dist/commands/rescue.js +42 -6
- package/dist/commands/rescue.js.map +1 -1
- package/dist/commands/stale.d.ts +16 -0
- package/dist/commands/stale.d.ts.map +1 -1
- package/dist/commands/stale.js +55 -21
- package/dist/commands/stale.js.map +1 -1
- package/dist/commands/sync-advisor.d.ts +14 -0
- package/dist/commands/sync-advisor.d.ts.map +1 -0
- package/dist/commands/sync-advisor.js +110 -0
- package/dist/commands/sync-advisor.js.map +1 -0
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +16 -5
- package/dist/commands/sync.js.map +1 -1
- package/dist/commands/torrent.d.ts.map +1 -1
- package/dist/commands/torrent.js +16 -14
- package/dist/commands/torrent.js.map +1 -1
- package/dist/commands/watch.d.ts.map +1 -1
- package/dist/commands/watch.js +197 -6
- package/dist/commands/watch.js.map +1 -1
- package/dist/lib/activity.d.ts +53 -0
- package/dist/lib/activity.d.ts.map +1 -0
- package/dist/lib/activity.js +267 -0
- package/dist/lib/activity.js.map +1 -0
- package/dist/lib/agent-errors.d.ts.map +1 -1
- package/dist/lib/agent-errors.js +3 -1
- package/dist/lib/agent-errors.js.map +1 -1
- package/dist/lib/ast.d.ts.map +1 -1
- package/dist/lib/ast.js +66 -19
- package/dist/lib/ast.js.map +1 -1
- package/dist/lib/colony-subscriber.d.ts +53 -0
- package/dist/lib/colony-subscriber.d.ts.map +1 -0
- package/dist/lib/colony-subscriber.js +184 -0
- package/dist/lib/colony-subscriber.js.map +1 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +5 -4
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/crypto.d.ts.map +1 -1
- package/dist/lib/crypto.js +7 -3
- package/dist/lib/crypto.js.map +1 -1
- package/dist/lib/daily-bridge.d.ts +41 -0
- package/dist/lib/daily-bridge.d.ts.map +1 -0
- package/dist/lib/daily-bridge.js +185 -0
- package/dist/lib/daily-bridge.js.map +1 -0
- package/dist/lib/github.d.ts +83 -0
- package/dist/lib/github.d.ts.map +1 -0
- package/dist/lib/github.js +206 -0
- package/dist/lib/github.js.map +1 -0
- package/dist/lib/repo-scanner.d.ts +36 -0
- package/dist/lib/repo-scanner.d.ts.map +1 -0
- package/dist/lib/repo-scanner.js +159 -0
- package/dist/lib/repo-scanner.js.map +1 -0
- package/dist/lib/salvage-analyzer.d.ts +29 -0
- package/dist/lib/salvage-analyzer.d.ts.map +1 -0
- package/dist/lib/salvage-analyzer.js +122 -0
- package/dist/lib/salvage-analyzer.js.map +1 -0
- package/dist/lib/security.d.ts +5 -0
- package/dist/lib/security.d.ts.map +1 -1
- package/dist/lib/security.js +10 -0
- package/dist/lib/security.js.map +1 -1
- package/dist/lib/symbol-extractor.d.ts +41 -0
- package/dist/lib/symbol-extractor.d.ts.map +1 -0
- package/dist/lib/symbol-extractor.js +78 -0
- package/dist/lib/symbol-extractor.js.map +1 -0
- package/dist/storage/adapter.d.ts +1 -1
- package/dist/storage/adapter.d.ts.map +1 -1
- package/dist/storage/git-messages.d.ts.map +1 -1
- package/dist/storage/git-messages.js +2 -0
- package/dist/storage/git-messages.js.map +1 -1
- package/dist/storage/local.d.ts +1 -1
- package/dist/storage/local.d.ts.map +1 -1
- package/dist/storage/local.js +7 -5
- package/dist/storage/local.js.map +1 -1
- package/dist/storage/mycmail-adapter.d.ts.map +1 -1
- package/dist/storage/mycmail-adapter.js +11 -6
- package/dist/storage/mycmail-adapter.js.map +1 -1
- package/dist/storage/supabase.d.ts +21 -1
- package/dist/storage/supabase.d.ts.map +1 -1
- package/dist/storage/supabase.js +171 -6
- package/dist/storage/supabase.js.map +1 -1
- package/dist/tui/dashboard.d.ts.map +1 -1
- package/dist/tui/dashboard.js +15 -6
- package/dist/tui/dashboard.js.map +1 -1
- package/dist/types/cloud.d.ts +101 -0
- package/dist/types/cloud.d.ts.map +1 -0
- package/dist/types/cloud.js +13 -0
- package/dist/types/cloud.js.map +1 -0
- package/package.json +6 -5
- package/dist/commands/sync-all.d.ts +0 -9
- package/dist/commands/sync-all.d.ts.map +0 -1
- package/dist/commands/sync-all.js +0 -180
- package/dist/commands/sync-all.js.map +0 -1
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub API client wrapper using `gh` CLI
|
|
3
|
+
*
|
|
4
|
+
* Provides access to branch, PR, and CI status from GitHub
|
|
5
|
+
* without requiring Octokit or managing authentication.
|
|
6
|
+
*/
|
|
7
|
+
export interface GitHubRepoConfig {
|
|
8
|
+
owner: string;
|
|
9
|
+
repo: string;
|
|
10
|
+
}
|
|
11
|
+
export interface GitHubBranchInfo {
|
|
12
|
+
name: string;
|
|
13
|
+
sha: string;
|
|
14
|
+
protected: boolean;
|
|
15
|
+
commit: {
|
|
16
|
+
sha: string;
|
|
17
|
+
date: string;
|
|
18
|
+
author: string;
|
|
19
|
+
message: string;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export interface GitHubPRInfo {
|
|
23
|
+
number: number;
|
|
24
|
+
title: string;
|
|
25
|
+
state: string;
|
|
26
|
+
headBranch: string;
|
|
27
|
+
draft: boolean;
|
|
28
|
+
createdAt: string;
|
|
29
|
+
updatedAt: string;
|
|
30
|
+
}
|
|
31
|
+
export interface GitHubCIStatus {
|
|
32
|
+
status: string;
|
|
33
|
+
conclusion: string | null;
|
|
34
|
+
url: string;
|
|
35
|
+
runId: string;
|
|
36
|
+
createdAt: string;
|
|
37
|
+
}
|
|
38
|
+
export interface GitHubCommitInfo {
|
|
39
|
+
sha: string;
|
|
40
|
+
date: string;
|
|
41
|
+
author: string;
|
|
42
|
+
message: string;
|
|
43
|
+
}
|
|
44
|
+
export interface AheadBehind {
|
|
45
|
+
ahead: number;
|
|
46
|
+
behind: number;
|
|
47
|
+
}
|
|
48
|
+
export interface GitHubPRDetails {
|
|
49
|
+
number: number;
|
|
50
|
+
title: string;
|
|
51
|
+
headBranch: string;
|
|
52
|
+
files: string[];
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Check if GitHub CLI (gh) is available and authenticated.
|
|
56
|
+
*/
|
|
57
|
+
export declare function isGhAvailable(): boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Fetch all branches from a GitHub repository.
|
|
60
|
+
*/
|
|
61
|
+
export declare function listBranches(config: GitHubRepoConfig): Promise<GitHubBranchInfo[]>;
|
|
62
|
+
/**
|
|
63
|
+
* Fetch commit details for a specific branch.
|
|
64
|
+
*/
|
|
65
|
+
export declare function getCommitInfo(config: GitHubRepoConfig, branch: string): Promise<GitHubCommitInfo | null>;
|
|
66
|
+
/**
|
|
67
|
+
* Fetch all pull requests for a repository.
|
|
68
|
+
*/
|
|
69
|
+
export declare function listPullRequests(config: GitHubRepoConfig): Promise<GitHubPRInfo[]>;
|
|
70
|
+
/**
|
|
71
|
+
* Get CI status for a branch (from latest workflow run).
|
|
72
|
+
*/
|
|
73
|
+
export declare function getCIStatus(config: GitHubRepoConfig, branch: string): Promise<GitHubCIStatus | null>;
|
|
74
|
+
/**
|
|
75
|
+
* Compare a branch to main (or specified base) and get ahead/behind counts.
|
|
76
|
+
*/
|
|
77
|
+
export declare function getAheadBehind(config: GitHubRepoConfig, branch: string, baseBranch?: string): Promise<AheadBehind>;
|
|
78
|
+
/**
|
|
79
|
+
* Fetch details for a specific pull request: head branch name and list of changed files.
|
|
80
|
+
* Requires `gh` CLI to be authenticated and run from inside the repository directory.
|
|
81
|
+
*/
|
|
82
|
+
export declare function getPRDetails(prNumber: number): Promise<GitHubPRDetails>;
|
|
83
|
+
//# sourceMappingURL=github.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github.d.ts","sourceRoot":"","sources":["../../src/lib/github.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,MAAM,WAAW,gBAAgB;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,OAAO,CAAC;IACnB,MAAM,EAAE;QACJ,GAAG,EAAE,MAAM,CAAC;QACZ,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,EAAE,MAAM,CAAC;KACnB,CAAC;CACL;AAED,MAAM,WAAW,YAAY;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,OAAO,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,cAAc;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,gBAAgB;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,OAAO,CAOvC;AAED;;GAEG;AACH,wBAAsB,YAAY,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,CA8BxF;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,MAAM,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAgB9G;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CA0BxF;AAED;;GAEG;AACH,wBAAsB,WAAW,CAAC,MAAM,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CA0B1G;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,MAAM,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,SAAS,GAAG,OAAO,CAAC,WAAW,CAAC,CAiBxH;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAuC7E"}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub API client wrapper using `gh` CLI
|
|
3
|
+
*
|
|
4
|
+
* Provides access to branch, PR, and CI status from GitHub
|
|
5
|
+
* without requiring Octokit or managing authentication.
|
|
6
|
+
*/
|
|
7
|
+
import { execFileSync } from 'child_process';
|
|
8
|
+
/**
|
|
9
|
+
* Check if GitHub CLI (gh) is available and authenticated.
|
|
10
|
+
*/
|
|
11
|
+
export function isGhAvailable() {
|
|
12
|
+
try {
|
|
13
|
+
execFileSync('gh', ['auth', 'status'], { encoding: 'utf-8', stdio: 'pipe' });
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Fetch all branches from a GitHub repository.
|
|
22
|
+
*/
|
|
23
|
+
export async function listBranches(config) {
|
|
24
|
+
try {
|
|
25
|
+
const output = execFileSync('gh', [
|
|
26
|
+
'api',
|
|
27
|
+
`repos/${config.owner}/${config.repo}/branches`,
|
|
28
|
+
'--paginate',
|
|
29
|
+
'--jq',
|
|
30
|
+
'.[] | {name: .name, sha: .commit.sha, protected: .protected}',
|
|
31
|
+
], { encoding: 'utf-8' });
|
|
32
|
+
const branches = [];
|
|
33
|
+
const lines = output.trim().split('\n').filter(l => l.length > 0);
|
|
34
|
+
for (const line of lines) {
|
|
35
|
+
try {
|
|
36
|
+
const data = JSON.parse(line);
|
|
37
|
+
branches.push({
|
|
38
|
+
name: data.name,
|
|
39
|
+
sha: data.sha,
|
|
40
|
+
protected: data.protected,
|
|
41
|
+
commit: { sha: data.sha, date: '', author: '', message: '' },
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
// Skip invalid JSON lines
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return branches;
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
52
|
+
throw new Error(`Failed to list branches: ${msg}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Fetch commit details for a specific branch.
|
|
57
|
+
*/
|
|
58
|
+
export async function getCommitInfo(config, branch) {
|
|
59
|
+
try {
|
|
60
|
+
const output = execFileSync('gh', [
|
|
61
|
+
'api',
|
|
62
|
+
`repos/${config.owner}/${config.repo}/commits/${branch}`,
|
|
63
|
+
'--jq',
|
|
64
|
+
'{sha: .sha, date: .commit.author.date, author: .commit.author.name, message: .commit.message}',
|
|
65
|
+
], { encoding: 'utf-8' });
|
|
66
|
+
try {
|
|
67
|
+
return JSON.parse(output);
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Fetch all pull requests for a repository.
|
|
79
|
+
*/
|
|
80
|
+
export async function listPullRequests(config) {
|
|
81
|
+
try {
|
|
82
|
+
const output = execFileSync('gh', [
|
|
83
|
+
'pr', 'list',
|
|
84
|
+
'--repo', `${config.owner}/${config.repo}`,
|
|
85
|
+
'--state', 'all',
|
|
86
|
+
'--json', 'number,title,state,headRefName,isDraft,createdAt,updatedAt',
|
|
87
|
+
'--limit', '1000',
|
|
88
|
+
], { encoding: 'utf-8' });
|
|
89
|
+
try {
|
|
90
|
+
const data = JSON.parse(output);
|
|
91
|
+
return data.map((pr) => ({
|
|
92
|
+
number: pr.number,
|
|
93
|
+
title: pr.title,
|
|
94
|
+
state: pr.state,
|
|
95
|
+
headBranch: pr.headRefName,
|
|
96
|
+
draft: pr.isDraft,
|
|
97
|
+
createdAt: pr.createdAt,
|
|
98
|
+
updatedAt: pr.updatedAt,
|
|
99
|
+
}));
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return [];
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
return [];
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Get CI status for a branch (from latest workflow run).
|
|
111
|
+
*/
|
|
112
|
+
export async function getCIStatus(config, branch) {
|
|
113
|
+
try {
|
|
114
|
+
const output = execFileSync('gh', [
|
|
115
|
+
'run', 'list',
|
|
116
|
+
'--repo', `${config.owner}/${config.repo}`,
|
|
117
|
+
'--branch', branch,
|
|
118
|
+
'--limit', '1',
|
|
119
|
+
'--json', 'status,conclusion,url,databaseId,createdAt',
|
|
120
|
+
], { encoding: 'utf-8' });
|
|
121
|
+
try {
|
|
122
|
+
const data = JSON.parse(output);
|
|
123
|
+
if (!Array.isArray(data) || data.length === 0)
|
|
124
|
+
return null;
|
|
125
|
+
const run = data[0];
|
|
126
|
+
return {
|
|
127
|
+
status: run.status,
|
|
128
|
+
conclusion: run.conclusion,
|
|
129
|
+
url: run.url,
|
|
130
|
+
runId: String(run.databaseId),
|
|
131
|
+
createdAt: run.createdAt,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Compare a branch to main (or specified base) and get ahead/behind counts.
|
|
144
|
+
*/
|
|
145
|
+
export async function getAheadBehind(config, branch, baseBranch = 'main') {
|
|
146
|
+
try {
|
|
147
|
+
const output = execFileSync('gh', [
|
|
148
|
+
'api',
|
|
149
|
+
`repos/${config.owner}/${config.repo}/compare/${baseBranch}...${branch}`,
|
|
150
|
+
'--jq',
|
|
151
|
+
'{ahead: .ahead_by, behind: .behind_by}',
|
|
152
|
+
], { encoding: 'utf-8' });
|
|
153
|
+
try {
|
|
154
|
+
const data = JSON.parse(output);
|
|
155
|
+
return { ahead: data.ahead || 0, behind: data.behind || 0 };
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
return { ahead: 0, behind: 0 };
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
return { ahead: 0, behind: 0 };
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Fetch details for a specific pull request: head branch name and list of changed files.
|
|
167
|
+
* Requires `gh` CLI to be authenticated and run from inside the repository directory.
|
|
168
|
+
*/
|
|
169
|
+
export async function getPRDetails(prNumber) {
|
|
170
|
+
if (!Number.isInteger(prNumber) || prNumber <= 0) {
|
|
171
|
+
throw new Error(`Invalid PR number: ${prNumber}`);
|
|
172
|
+
}
|
|
173
|
+
// Get PR metadata (head branch, title, number)
|
|
174
|
+
let prOutput;
|
|
175
|
+
try {
|
|
176
|
+
prOutput = execFileSync('gh', [
|
|
177
|
+
'pr', 'view', String(prNumber),
|
|
178
|
+
'--json', 'headRefName,title,number',
|
|
179
|
+
], { encoding: 'utf-8' });
|
|
180
|
+
}
|
|
181
|
+
catch (err) {
|
|
182
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
183
|
+
throw new Error(`Failed to fetch PR #${prNumber} metadata: ${msg}`);
|
|
184
|
+
}
|
|
185
|
+
const prData = JSON.parse(prOutput);
|
|
186
|
+
// Get changed file paths via the diff (--name-only lists one file per line)
|
|
187
|
+
let diffOutput;
|
|
188
|
+
try {
|
|
189
|
+
diffOutput = execFileSync('gh', [
|
|
190
|
+
'pr', 'diff', String(prNumber),
|
|
191
|
+
'--name-only',
|
|
192
|
+
], { encoding: 'utf-8' });
|
|
193
|
+
}
|
|
194
|
+
catch (err) {
|
|
195
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
196
|
+
throw new Error(`Failed to fetch changed files for PR #${prNumber}: ${msg}`);
|
|
197
|
+
}
|
|
198
|
+
const files = diffOutput.trim().split('\n').filter(f => f.length > 0);
|
|
199
|
+
return {
|
|
200
|
+
number: prData.number,
|
|
201
|
+
title: prData.title,
|
|
202
|
+
headBranch: prData.headRefName,
|
|
203
|
+
files,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
//# sourceMappingURL=github.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github.js","sourceRoot":"","sources":["../../src/lib/github.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Multi-repo git state scanner for Smart Sync Advisor (F4)
|
|
3
|
+
*
|
|
4
|
+
* Scans all repos under a base directory and identifies:
|
|
5
|
+
* - Unpushed commits (ahead of remote)
|
|
6
|
+
* - Unpulled commits (behind remote)
|
|
7
|
+
* - Dirty state (uncommitted files)
|
|
8
|
+
* - Diverged branches (ahead AND behind)
|
|
9
|
+
*
|
|
10
|
+
* Uses execFileSync with { cwd: repoPath } — no process.chdir()
|
|
11
|
+
*/
|
|
12
|
+
import type { SyncRecommendation } from '../types/cloud.js';
|
|
13
|
+
export interface RepoState {
|
|
14
|
+
name: string;
|
|
15
|
+
path: string;
|
|
16
|
+
branch: string;
|
|
17
|
+
unpushed: number;
|
|
18
|
+
unpulled: number;
|
|
19
|
+
isDirty: boolean;
|
|
20
|
+
isDiverged: boolean;
|
|
21
|
+
hasUpstream: boolean;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Scan a single repository for git state.
|
|
25
|
+
* Returns null if not a git repo or unreadable.
|
|
26
|
+
*/
|
|
27
|
+
export declare function scanRepo(repoPath: string): RepoState | null;
|
|
28
|
+
/**
|
|
29
|
+
* Scan all repos under a base directory.
|
|
30
|
+
*/
|
|
31
|
+
export declare function scanAllRepos(basePath: string): RepoState[];
|
|
32
|
+
/**
|
|
33
|
+
* Convert RepoState[] to SyncRecommendation[].
|
|
34
|
+
*/
|
|
35
|
+
export declare function generateRecommendations(states: RepoState[]): SyncRecommendation[];
|
|
36
|
+
//# sourceMappingURL=repo-scanner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"repo-scanner.d.ts","sourceRoot":"","sources":["../../src/lib/repo-scanner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAE5D,MAAM,WAAW,SAAS;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,OAAO,CAAC;IACpB,WAAW,EAAE,OAAO,CAAC;CACxB;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CA0D3D;AA0BD;;GAEG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAAE,CAG1D;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,kBAAkB,EAAE,CA0CjF"}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Multi-repo git state scanner for Smart Sync Advisor (F4)
|
|
3
|
+
*
|
|
4
|
+
* Scans all repos under a base directory and identifies:
|
|
5
|
+
* - Unpushed commits (ahead of remote)
|
|
6
|
+
* - Unpulled commits (behind remote)
|
|
7
|
+
* - Dirty state (uncommitted files)
|
|
8
|
+
* - Diverged branches (ahead AND behind)
|
|
9
|
+
*
|
|
10
|
+
* Uses execFileSync with { cwd: repoPath } — no process.chdir()
|
|
11
|
+
*/
|
|
12
|
+
import { execFileSync } from 'child_process';
|
|
13
|
+
import { readdirSync, existsSync } from 'fs';
|
|
14
|
+
import { basename, join } from 'path';
|
|
15
|
+
/**
|
|
16
|
+
* Scan a single repository for git state.
|
|
17
|
+
* Returns null if not a git repo or unreadable.
|
|
18
|
+
*/
|
|
19
|
+
export function scanRepo(repoPath) {
|
|
20
|
+
try {
|
|
21
|
+
if (!existsSync(join(repoPath, '.git')))
|
|
22
|
+
return null;
|
|
23
|
+
let branch = '';
|
|
24
|
+
try {
|
|
25
|
+
branch = execFileSync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], {
|
|
26
|
+
cwd: repoPath, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'],
|
|
27
|
+
}).trim();
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
let hasUpstream = false;
|
|
33
|
+
let unpushed = 0;
|
|
34
|
+
let unpulled = 0;
|
|
35
|
+
try {
|
|
36
|
+
execFileSync('git', ['rev-parse', '--abbrev-ref', '@{u}'], {
|
|
37
|
+
cwd: repoPath, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'],
|
|
38
|
+
});
|
|
39
|
+
hasUpstream = true;
|
|
40
|
+
try {
|
|
41
|
+
unpushed = parseInt(execFileSync('git', ['rev-list', '--count', 'HEAD..@{u}'], {
|
|
42
|
+
cwd: repoPath, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'],
|
|
43
|
+
}).trim(), 10) || 0;
|
|
44
|
+
}
|
|
45
|
+
catch { /* no commits to push */ }
|
|
46
|
+
try {
|
|
47
|
+
unpulled = parseInt(execFileSync('git', ['rev-list', '--count', '@{u}..HEAD'], {
|
|
48
|
+
cwd: repoPath, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'],
|
|
49
|
+
}).trim(), 10) || 0;
|
|
50
|
+
}
|
|
51
|
+
catch { /* no commits to pull */ }
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
hasUpstream = false;
|
|
55
|
+
}
|
|
56
|
+
let isDirty = false;
|
|
57
|
+
try {
|
|
58
|
+
isDirty = execFileSync('git', ['status', '--porcelain'], {
|
|
59
|
+
cwd: repoPath, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'],
|
|
60
|
+
}).trim().length > 0;
|
|
61
|
+
}
|
|
62
|
+
catch { /* error checking status */ }
|
|
63
|
+
return {
|
|
64
|
+
name: basename(repoPath),
|
|
65
|
+
path: repoPath,
|
|
66
|
+
branch,
|
|
67
|
+
unpushed,
|
|
68
|
+
unpulled,
|
|
69
|
+
isDirty,
|
|
70
|
+
isDiverged: unpushed > 0 && unpulled > 0,
|
|
71
|
+
hasUpstream,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function findRepos(basePath, maxDepth = 2, currentDepth = 0) {
|
|
79
|
+
const repos = [];
|
|
80
|
+
if (currentDepth >= maxDepth)
|
|
81
|
+
return repos;
|
|
82
|
+
try {
|
|
83
|
+
const entries = readdirSync(basePath, { withFileTypes: true });
|
|
84
|
+
for (const entry of entries) {
|
|
85
|
+
if (entry.name.startsWith('.') && entry.name !== '.git')
|
|
86
|
+
continue;
|
|
87
|
+
if (['node_modules', 'dist', 'build', '.cache', '.next'].includes(entry.name))
|
|
88
|
+
continue;
|
|
89
|
+
const fullPath = join(basePath, entry.name);
|
|
90
|
+
if (entry.isDirectory()) {
|
|
91
|
+
if (existsSync(join(fullPath, '.git'))) {
|
|
92
|
+
repos.push(fullPath);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
repos.push(...findRepos(fullPath, maxDepth, currentDepth + 1));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
catch { /* permission denied or other errors */ }
|
|
101
|
+
return repos;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Scan all repos under a base directory.
|
|
105
|
+
*/
|
|
106
|
+
export function scanAllRepos(basePath) {
|
|
107
|
+
const repos = findRepos(basePath);
|
|
108
|
+
return repos.map(scanRepo).filter((s) => s !== null);
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Convert RepoState[] to SyncRecommendation[].
|
|
112
|
+
*/
|
|
113
|
+
export function generateRecommendations(states) {
|
|
114
|
+
return states.map((state) => {
|
|
115
|
+
let action = 'up-to-date';
|
|
116
|
+
let priority = 'low';
|
|
117
|
+
let reason = 'Repository is in sync';
|
|
118
|
+
if (!state.hasUpstream) {
|
|
119
|
+
action = 'conflict';
|
|
120
|
+
priority = 'medium';
|
|
121
|
+
reason = 'No upstream tracking branch configured';
|
|
122
|
+
}
|
|
123
|
+
else if (state.isDiverged) {
|
|
124
|
+
action = 'rebase';
|
|
125
|
+
priority = 'high';
|
|
126
|
+
reason = `${state.unpushed} commits ahead, ${state.unpulled} commits behind (diverged)`;
|
|
127
|
+
}
|
|
128
|
+
else if (state.unpushed > 0) {
|
|
129
|
+
action = 'push';
|
|
130
|
+
priority = 'high';
|
|
131
|
+
reason = `${state.unpushed} commit${state.unpushed === 1 ? '' : 's'} ahead of remote`;
|
|
132
|
+
}
|
|
133
|
+
else if (state.unpulled > 0) {
|
|
134
|
+
action = 'pull';
|
|
135
|
+
priority = 'high';
|
|
136
|
+
reason = `${state.unpulled} commit${state.unpulled === 1 ? '' : 's'} behind remote`;
|
|
137
|
+
}
|
|
138
|
+
else if (state.isDirty) {
|
|
139
|
+
action = 'conflict';
|
|
140
|
+
priority = 'medium';
|
|
141
|
+
reason = 'Uncommitted changes detected';
|
|
142
|
+
}
|
|
143
|
+
return {
|
|
144
|
+
repo_name: state.name,
|
|
145
|
+
branch_name: state.branch,
|
|
146
|
+
machine_name: '',
|
|
147
|
+
action,
|
|
148
|
+
reason,
|
|
149
|
+
priority,
|
|
150
|
+
};
|
|
151
|
+
}).sort((a, b) => {
|
|
152
|
+
const pOrder = { high: 0, medium: 1, low: 2 };
|
|
153
|
+
const aOrder = { push: 0, pull: 1, rebase: 2, conflict: 3, 'up-to-date': 4 };
|
|
154
|
+
if (pOrder[a.priority] !== pOrder[b.priority])
|
|
155
|
+
return pOrder[a.priority] - pOrder[b.priority];
|
|
156
|
+
return (aOrder[a.action] ?? 5) - (aOrder[b.action] ?? 5);
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
//# sourceMappingURL=repo-scanner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"repo-scanner.js","sourceRoot":"","sources":["../../src/lib/repo-scanner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* salvage-analyzer.ts — Phase C3 semantic intelligence
|
|
3
|
+
*
|
|
4
|
+
* Diff abandoned branch symbols vs main and report unique symbols
|
|
5
|
+
* not present in main (salvageable code). Wired into `rescue --symbols`.
|
|
6
|
+
*/
|
|
7
|
+
export interface SalvageableSymbol {
|
|
8
|
+
name: string;
|
|
9
|
+
kind: string;
|
|
10
|
+
line: number;
|
|
11
|
+
file: string;
|
|
12
|
+
branch: string;
|
|
13
|
+
}
|
|
14
|
+
export interface SalvageReport {
|
|
15
|
+
branch: string;
|
|
16
|
+
analysedFiles: number;
|
|
17
|
+
salvageable: SalvageableSymbol[];
|
|
18
|
+
alreadyInMain: number;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Analyse an abandoned branch for symbols not present in main.
|
|
22
|
+
* Uses ts-morph (via symbol-extractor) for reliable TypeScript parsing.
|
|
23
|
+
*/
|
|
24
|
+
export declare function analyzeSalvage(branch: string, mainRef?: string): Promise<SalvageReport>;
|
|
25
|
+
/**
|
|
26
|
+
* Format a salvage report for CLI display.
|
|
27
|
+
*/
|
|
28
|
+
export declare function formatSalvageReport(report: SalvageReport): string;
|
|
29
|
+
//# sourceMappingURL=salvage-analyzer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"salvage-analyzer.d.ts","sourceRoot":"","sources":["../../src/lib/salvage-analyzer.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AASH,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,iBAAiB,EAAE,CAAC;IACjC,aAAa,EAAE,MAAM,CAAC;CACvB;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,MAAM,EACd,OAAO,SAAS,GACf,OAAO,CAAC,aAAa,CAAC,CAyFxB;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,CAmBjE"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* salvage-analyzer.ts — Phase C3 semantic intelligence
|
|
3
|
+
*
|
|
4
|
+
* Diff abandoned branch symbols vs main and report unique symbols
|
|
5
|
+
* not present in main (salvageable code). Wired into `rescue --symbols`.
|
|
6
|
+
*/
|
|
7
|
+
import { execFileSync } from 'child_process';
|
|
8
|
+
import { mkdirSync, writeFileSync, rmSync } from 'fs';
|
|
9
|
+
import { join } from 'path';
|
|
10
|
+
import { tmpdir } from 'os';
|
|
11
|
+
import { extractSymbols } from './symbol-extractor.js';
|
|
12
|
+
import { validateBranchName } from './security.js';
|
|
13
|
+
/**
|
|
14
|
+
* Analyse an abandoned branch for symbols not present in main.
|
|
15
|
+
* Uses ts-morph (via symbol-extractor) for reliable TypeScript parsing.
|
|
16
|
+
*/
|
|
17
|
+
export async function analyzeSalvage(branch, mainRef = 'main') {
|
|
18
|
+
// Validate inputs before passing to git to prevent injection
|
|
19
|
+
validateBranchName(branch);
|
|
20
|
+
validateBranchName(mainRef);
|
|
21
|
+
const report = {
|
|
22
|
+
branch,
|
|
23
|
+
analysedFiles: 0,
|
|
24
|
+
salvageable: [],
|
|
25
|
+
alreadyInMain: 0,
|
|
26
|
+
};
|
|
27
|
+
// List TypeScript/JavaScript files in the abandoned branch
|
|
28
|
+
let branchFiles;
|
|
29
|
+
try {
|
|
30
|
+
const raw = execFileSync('git', ['ls-tree', '-r', '--name-only', branch], {
|
|
31
|
+
encoding: 'utf-8',
|
|
32
|
+
});
|
|
33
|
+
branchFiles = raw
|
|
34
|
+
.trim()
|
|
35
|
+
.split('\n')
|
|
36
|
+
.filter(f => /\.(ts|js|tsx|jsx)$/.test(f) && !f.includes('node_modules'));
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return report;
|
|
40
|
+
}
|
|
41
|
+
const tmpDir = join(tmpdir(), `ssan-salvage-${Date.now()}`);
|
|
42
|
+
mkdirSync(tmpDir, { recursive: true });
|
|
43
|
+
try {
|
|
44
|
+
for (const file of branchFiles) {
|
|
45
|
+
// Get file content from the abandoned branch
|
|
46
|
+
let branchContent;
|
|
47
|
+
try {
|
|
48
|
+
branchContent = execFileSync('git', ['show', `${branch}:${file}`], {
|
|
49
|
+
encoding: 'utf-8',
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
// Write to temp file for ts-morph
|
|
56
|
+
const tmpFile = join(tmpDir, file.replace(/\//g, '_'));
|
|
57
|
+
writeFileSync(tmpFile, branchContent, 'utf-8');
|
|
58
|
+
const branchSymbols = extractSymbols(tmpFile);
|
|
59
|
+
report.analysedFiles++;
|
|
60
|
+
// Get main version (may not exist)
|
|
61
|
+
let mainContent = null;
|
|
62
|
+
try {
|
|
63
|
+
mainContent = execFileSync('git', ['show', `${mainRef}:${file}`], {
|
|
64
|
+
encoding: 'utf-8',
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
// File not in main — all its symbols are salvageable
|
|
69
|
+
}
|
|
70
|
+
if (!mainContent) {
|
|
71
|
+
// Entire file is unique to the branch
|
|
72
|
+
for (const sym of branchSymbols.symbols) {
|
|
73
|
+
report.salvageable.push({ ...sym, file, branch });
|
|
74
|
+
}
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
// Compare symbols with main version
|
|
78
|
+
const mainTmpFile = join(tmpDir, `main_${file.replace(/\//g, '_')}`);
|
|
79
|
+
writeFileSync(mainTmpFile, mainContent, 'utf-8');
|
|
80
|
+
const mainSymbols = extractSymbols(mainTmpFile);
|
|
81
|
+
const mainNames = new Set(mainSymbols.symbols.map(s => s.name));
|
|
82
|
+
for (const sym of branchSymbols.symbols) {
|
|
83
|
+
if (!mainNames.has(sym.name)) {
|
|
84
|
+
report.salvageable.push({ ...sym, file, branch });
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
report.alreadyInMain++;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
finally {
|
|
93
|
+
try {
|
|
94
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
// best-effort cleanup
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return report;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Format a salvage report for CLI display.
|
|
104
|
+
*/
|
|
105
|
+
export function formatSalvageReport(report) {
|
|
106
|
+
const lines = [
|
|
107
|
+
`🔍 Salvage analysis: ${report.branch}`,
|
|
108
|
+
` Files analysed: ${report.analysedFiles}`,
|
|
109
|
+
` Symbols already in main: ${report.alreadyInMain}`,
|
|
110
|
+
` Salvageable symbols: ${report.salvageable.length}`,
|
|
111
|
+
];
|
|
112
|
+
if (report.salvageable.length === 0) {
|
|
113
|
+
lines.push('\n ✅ No unique symbols — branch is fully superseded by main.');
|
|
114
|
+
return lines.join('\n');
|
|
115
|
+
}
|
|
116
|
+
lines.push('\n Salvageable symbols (unique to this branch):');
|
|
117
|
+
for (const sym of report.salvageable) {
|
|
118
|
+
lines.push(` • [${sym.kind.padEnd(9)}] ${sym.name} (${sym.file}:${sym.line})`);
|
|
119
|
+
}
|
|
120
|
+
return lines.join('\n');
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=salvage-analyzer.js.map
|