@magnusekdahl/parallix 1.2.0 → 1.3.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/index.js +190 -185
- package/lib/agents/agents.js +737 -838
- package/lib/agents/agents.ts +930 -0
- package/lib/agents/claude-telemetry.js +149 -154
- package/lib/agents/claude-telemetry.ts +233 -0
- package/lib/agents/claude.js +121 -131
- package/lib/agents/claude.ts +156 -0
- package/lib/agents/codex-telemetry.js +176 -164
- package/lib/agents/codex-telemetry.ts +205 -0
- package/lib/agents/codex.js +168 -201
- package/lib/agents/codex.ts +236 -0
- package/lib/agents/limit-hit.js +216 -226
- package/lib/agents/limit-hit.ts +262 -0
- package/lib/agents/mistral-telemetry.js +7 -10
- package/lib/agents/mistral-telemetry.ts +44 -0
- package/lib/agents/mistral.js +40 -58
- package/lib/agents/mistral.ts +84 -0
- package/lib/agents/opencode-export.js +126 -127
- package/lib/agents/opencode-export.ts +160 -0
- package/lib/agents/opencode-telemetry.js +282 -261
- package/lib/agents/opencode-telemetry.ts +350 -0
- package/lib/agents/opencode.js +263 -305
- package/lib/agents/opencode.ts +370 -0
- package/lib/agents/stage-telemetry.js +23 -24
- package/lib/agents/stage-telemetry.ts +47 -0
- package/lib/commands/active.js +502 -579
- package/lib/commands/active.ts +665 -0
- package/lib/commands/checkpoint.js +98 -74
- package/lib/commands/checkpoint.ts +79 -0
- package/lib/commands/config.js +61 -31
- package/lib/commands/config.ts +47 -0
- package/lib/commands/coverage-gate.js +271 -246
- package/lib/commands/coverage-gate.ts +362 -0
- package/lib/commands/diff.js +140 -119
- package/lib/commands/diff.ts +125 -0
- package/lib/commands/draft.js +770 -923
- package/lib/commands/draft.ts +1021 -0
- package/lib/commands/handoff.js +588 -608
- package/lib/commands/handoff.ts +694 -0
- package/lib/commands/integrate.js +1332 -1463
- package/lib/commands/integrate.ts +1688 -0
- package/lib/commands/mission-start.js +279 -237
- package/lib/commands/mission-start.ts +263 -0
- package/lib/commands/rebase.js +606 -582
- package/lib/commands/rebase.ts +635 -0
- package/lib/commands/repair-handoff.js +181 -179
- package/lib/commands/repair-handoff.ts +228 -0
- package/lib/commands/resolve-conflict.js +121 -102
- package/lib/commands/resolve-conflict.ts +112 -0
- package/lib/commands/review.js +3 -4
- package/lib/commands/review.ts +14 -0
- package/lib/commands/setup-review.js +4 -5
- package/lib/commands/setup-review.ts +14 -0
- package/lib/commands/setup.js +3 -3
- package/lib/commands/setup.ts +2 -0
- package/lib/commands/stats-backfill.js +394 -359
- package/lib/commands/stats-backfill.ts +418 -0
- package/lib/commands/stats.js +1424 -1665
- package/lib/commands/stats.ts +2186 -0
- package/lib/commands/status.js +219 -200
- package/lib/commands/status.ts +219 -0
- package/lib/commands/verify.js +3 -1
- package/lib/commands/verify.ts +2 -0
- package/lib/core/fmt.js +140 -211
- package/lib/core/fmt.ts +189 -0
- package/lib/core/git.js +121 -102
- package/lib/core/git.ts +146 -0
- package/lib/core/gitignore.js +59 -106
- package/lib/core/gitignore.ts +103 -0
- package/lib/core/mission-utils.js +833 -875
- package/lib/core/mission-utils.ts +998 -0
- package/lib/core/nels.js +112 -127
- package/lib/core/nels.ts +189 -0
- package/lib/core/persistent-data-migration.js +247 -205
- package/lib/core/persistent-data-migration.ts +255 -0
- package/lib/core/product-config.js +400 -476
- package/lib/core/product-config.ts +518 -0
- package/lib/core/runtime-matrix.js +41 -87
- package/lib/core/runtime-matrix.ts +84 -0
- package/lib/core/spawn-tee.js +152 -192
- package/lib/core/spawn-tee.ts +202 -0
- package/lib/core/state-map.js +92 -92
- package/lib/core/state-map.ts +115 -0
- package/lib/core/storage.js +101 -129
- package/lib/core/storage.ts +182 -0
- package/lib/core/subagent-limit.js +15 -16
- package/lib/core/subagent-limit.ts +25 -0
- package/lib/core/verification.js +148 -138
- package/lib/core/verification.ts +200 -0
- package/lib/index.js +142 -59
- package/lib/index.ts +134 -0
- package/lib/review/rebase.js +142 -145
- package/lib/review/rebase.ts +188 -0
- package/lib/review/review-adapter.js +99 -87
- package/lib/review/review-adapter.ts +198 -0
- package/lib/review/review-artifacts.js +486 -629
- package/lib/review/review-artifacts.ts +622 -0
- package/lib/review/review-commands.js +1220 -1373
- package/lib/review/review-commands.ts +1579 -0
- package/lib/review/review-events.js +544 -886
- package/lib/review/review-events.ts +954 -0
- package/lib/review/review-loop.js +945 -1020
- package/lib/review/review-loop.ts +1174 -0
- package/lib/review/review-polling.js +86 -115
- package/lib/review/review-polling.ts +194 -0
- package/lib/review/review-prompts.js +154 -189
- package/lib/review/review-prompts.ts +182 -0
- package/lib/review/review-state.js +246 -225
- package/lib/review/review-state.ts +353 -0
- package/lib/review/review.js +63 -84
- package/lib/review/review.ts +138 -0
- package/lib/tools/backlog.js +694 -680
- package/lib/tools/backlog.ts +835 -0
- package/lib/tools/forgejo.js +1270 -1405
- package/lib/tools/forgejo.ts +1793 -0
- package/lib/tools/gatekeeper.js +106 -96
- package/lib/tools/gatekeeper.ts +124 -0
- package/lib/tools/redgreen.js +200 -181
- package/lib/tools/redgreen.ts +221 -0
- package/lib/tools/sessions.js +58 -52
- package/lib/tools/sessions.ts +81 -0
- package/lib/tools/setup-review.js +902 -975
- package/lib/tools/setup-review.ts +1152 -0
- package/package.json +7 -5
- package/prompts/draft.md +1 -0
- package/prompts/portfolio.md +1 -1
- package/px.js +226 -210
package/lib/core/fmt.ts
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared formatting and palette layer for workflow-owned output.
|
|
3
|
+
* Uses Node.js built-in util.styleText for color rendering (ADR 0042).
|
|
4
|
+
* No external dependencies.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { styleText } from 'node:util';
|
|
8
|
+
|
|
9
|
+
const colors = {
|
|
10
|
+
reset: 'reset',
|
|
11
|
+
bold: 'bold',
|
|
12
|
+
red: 'red',
|
|
13
|
+
green: 'green',
|
|
14
|
+
yellow: 'yellow',
|
|
15
|
+
blue: 'blue',
|
|
16
|
+
magenta: 'magenta',
|
|
17
|
+
cyan: 'cyan',
|
|
18
|
+
white: 'white',
|
|
19
|
+
dim: 'gray',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const statusMap: Record<string, string> = {
|
|
23
|
+
PASS: 'green',
|
|
24
|
+
FAIL: 'red',
|
|
25
|
+
WARN: 'yellow',
|
|
26
|
+
INFO: 'cyan',
|
|
27
|
+
DEBUG: 'gray',
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const agentMap: Record<string, string> = {
|
|
31
|
+
codex: 'magenta',
|
|
32
|
+
claude: 'blue',
|
|
33
|
+
gemini: 'cyan',
|
|
34
|
+
custom: 'yellow',
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export function colorize(format: string, text: string): string {
|
|
38
|
+
return styleText(format as import('node:util').InspectColor, String(text ?? ''));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function stripAnsi(text: string): string {
|
|
42
|
+
return String(text ?? '').replace(/\x1B\[[0-9;]*m/g, '');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function visibleWidth(text: string): number {
|
|
46
|
+
return stripAnsi(text).length;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function padVisibleEnd(text: string, width: number): string {
|
|
50
|
+
const stringValue = String(text ?? '');
|
|
51
|
+
const padLength = Math.max(0, width - visibleWidth(stringValue));
|
|
52
|
+
return stringValue + ' '.repeat(padLength);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function status(type: string, text: string): string {
|
|
56
|
+
const format = statusMap[type];
|
|
57
|
+
if (!format) {return `[${type}] ${text}`;}
|
|
58
|
+
return `${colorize(format, `[${type}]`)} ${text}`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function agent(family: string, text: string = family): string {
|
|
62
|
+
let label = text;
|
|
63
|
+
if (family === 'custom' && text !== 'custom') {
|
|
64
|
+
label = 'custom (opencode)';
|
|
65
|
+
}
|
|
66
|
+
const format = agentMap[family];
|
|
67
|
+
if (!format) {return label;}
|
|
68
|
+
return colorize(format, label);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function bold(text: string): string {
|
|
72
|
+
return colorize('bold', text);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function dim(text: string): string {
|
|
76
|
+
return colorize('gray', text);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function kv(key: string, value: string, width: number = 30): string {
|
|
80
|
+
return `${bold(key.padEnd(width))} ${value}`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function table(rows: string[][], { indent = 2, colPadding = 2 }: { indent?: number; colPadding?: number } = {}): string {
|
|
84
|
+
if (rows.length === 0) {return '';}
|
|
85
|
+
const colWidths = rows[0].map((_, i) => Math.max(...rows.map(row => visibleWidth(row[i] ?? ''))));
|
|
86
|
+
const padding = ' '.repeat(indent);
|
|
87
|
+
return rows.map(row => padding + row.map((col, i) => padVisibleEnd(col ?? '', colWidths[i] + colPadding)).join('')).join('\n');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function list(items: string[], { bullet = '-', indent = 2 }: { bullet?: string; indent?: number } = {}): string {
|
|
91
|
+
const padding = ' '.repeat(indent);
|
|
92
|
+
return items.map(item => `${padding}${bullet} ${item}`).join('\n');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function path(text: string): string { return colorize('blue', text); }
|
|
96
|
+
export function slug(text: string): string { return colorize('cyan', bold(text)); }
|
|
97
|
+
export function branch(text: string): string { return colorize('magenta', text); }
|
|
98
|
+
export function sha(text: string): string { return colorize('yellow', text); }
|
|
99
|
+
export function command(text: string): string { return colorize('green', text); }
|
|
100
|
+
|
|
101
|
+
type LogFn = (...args: unknown[]) => void;
|
|
102
|
+
type LogFunc = (text: string) => string | null;
|
|
103
|
+
|
|
104
|
+
type Logger = {
|
|
105
|
+
log: LogFn;
|
|
106
|
+
error: LogFn;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
type LoggerInput = { log?: LogFn; error?: LogFn } | LogFn;
|
|
110
|
+
|
|
111
|
+
let currentLogger: Logger = {
|
|
112
|
+
log: (...args: unknown[]) => console.log(...args),
|
|
113
|
+
error: (...args: unknown[]) => console.error(...args),
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export const log: {
|
|
117
|
+
info: LogFunc;
|
|
118
|
+
pass: LogFunc;
|
|
119
|
+
fail: LogFunc;
|
|
120
|
+
warn: LogFunc;
|
|
121
|
+
error: LogFunc;
|
|
122
|
+
debug: LogFunc;
|
|
123
|
+
plain: LogFunc;
|
|
124
|
+
plainError: LogFunc;
|
|
125
|
+
} = {
|
|
126
|
+
info: (text: string): string => {
|
|
127
|
+
return text.toString().split('\n').map(line => {
|
|
128
|
+
const s = status('INFO', line);
|
|
129
|
+
currentLogger.log(s);
|
|
130
|
+
return s;
|
|
131
|
+
}).join('\n');
|
|
132
|
+
},
|
|
133
|
+
pass: (text: string): string => {
|
|
134
|
+
return text.toString().split('\n').map(line => {
|
|
135
|
+
const s = status('PASS', line);
|
|
136
|
+
currentLogger.log(s);
|
|
137
|
+
return s;
|
|
138
|
+
}).join('\n');
|
|
139
|
+
},
|
|
140
|
+
fail: (text: string): string => {
|
|
141
|
+
return text.toString().split('\n').map(line => {
|
|
142
|
+
const s = status('FAIL', line);
|
|
143
|
+
currentLogger.error(s);
|
|
144
|
+
return s;
|
|
145
|
+
}).join('\n');
|
|
146
|
+
},
|
|
147
|
+
warn: (text: string): string => {
|
|
148
|
+
return text.toString().split('\n').map(line => {
|
|
149
|
+
const s = status('WARN', line);
|
|
150
|
+
currentLogger.log(s);
|
|
151
|
+
return s;
|
|
152
|
+
}).join('\n');
|
|
153
|
+
},
|
|
154
|
+
error: (text: string): string => {
|
|
155
|
+
return text.toString().split('\n').map(line => {
|
|
156
|
+
const s = status('FAIL', line);
|
|
157
|
+
currentLogger.error(s);
|
|
158
|
+
return s;
|
|
159
|
+
}).join('\n');
|
|
160
|
+
},
|
|
161
|
+
debug: (text: string): string | null => {
|
|
162
|
+
if (!process.env.DEBUG) {return null;}
|
|
163
|
+
return text.toString().split('\n').map(line => {
|
|
164
|
+
const s = status('DEBUG', line);
|
|
165
|
+
currentLogger.log(s);
|
|
166
|
+
return s;
|
|
167
|
+
}).join('\n');
|
|
168
|
+
},
|
|
169
|
+
plain: (text: string): string => {
|
|
170
|
+
text.toString().split('\n').forEach(line => currentLogger.log(line));
|
|
171
|
+
return text;
|
|
172
|
+
},
|
|
173
|
+
plainError: (text: string): string => {
|
|
174
|
+
text.toString().split('\n').forEach(line => currentLogger.error(line));
|
|
175
|
+
return text;
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
export function setLogger(newLogger: LoggerInput): Logger {
|
|
180
|
+
const old = currentLogger;
|
|
181
|
+
const logger = newLogger as { log?: LogFn; error?: LogFn };
|
|
182
|
+
currentLogger = {
|
|
183
|
+
log: typeof logger.log === 'function' ? logger.log : (() => {}),
|
|
184
|
+
error: typeof logger.error === 'function' ? logger.error : (typeof logger.log === 'function' ? logger.log : (() => {})),
|
|
185
|
+
};
|
|
186
|
+
return old;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export { colors };
|
package/lib/core/git.js
CHANGED
|
@@ -1,121 +1,140 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.git = git;
|
|
37
|
+
exports.run = run;
|
|
38
|
+
exports.getCurrentBranch = getCurrentBranch;
|
|
39
|
+
exports.getWorktreeStatus = getWorktreeStatus;
|
|
40
|
+
exports.isDirty = isDirty;
|
|
41
|
+
exports.getUncommittedCount = getUncommittedCount;
|
|
42
|
+
exports.parseUnmergedFiles = parseUnmergedFiles;
|
|
43
|
+
exports.detectRebaseState = detectRebaseState;
|
|
44
|
+
exports.getLastCommit = getLastCommit;
|
|
45
|
+
exports.getLastThreeCommits = getLastThreeCommits;
|
|
46
|
+
const node_child_process_1 = require("node:child_process");
|
|
47
|
+
const fsMod = __importStar(require("node:fs"));
|
|
48
|
+
const pathMod = __importStar(require("node:path"));
|
|
4
49
|
function git(args, options = {}) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
50
|
+
const spawnOptions = {
|
|
51
|
+
encoding: 'utf8',
|
|
52
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
53
|
+
...options
|
|
54
|
+
};
|
|
55
|
+
const result = (0, node_child_process_1.spawnSync)('git', args, spawnOptions);
|
|
56
|
+
if (result.error && result.status === null) {
|
|
57
|
+
throw result.error;
|
|
58
|
+
}
|
|
59
|
+
return { status: result.status, signal: result.signal, stdout: String(result.stdout ?? ''), stderr: String(result.stderr ?? ''), error: result.error };
|
|
15
60
|
}
|
|
16
|
-
|
|
17
|
-
/** @param {string} command @param {string[]} args @param {object} options */
|
|
18
61
|
function run(command, args, options = {}) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
62
|
+
const spawnOptions = {
|
|
63
|
+
encoding: 'utf8',
|
|
64
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
65
|
+
...options
|
|
66
|
+
};
|
|
67
|
+
const result = (0, node_child_process_1.spawnSync)(command, args, spawnOptions);
|
|
68
|
+
if (result.error && result.status === null) {
|
|
69
|
+
throw result.error;
|
|
70
|
+
}
|
|
71
|
+
return { status: result.status, signal: result.signal, stdout: String(result.stdout ?? ''), stderr: String(result.stderr ?? ''), error: result.error };
|
|
29
72
|
}
|
|
30
|
-
|
|
31
73
|
function getCurrentBranch(cwd = process.cwd()) {
|
|
32
|
-
|
|
33
|
-
|
|
74
|
+
const result = git(['-C', cwd, 'branch', '--show-current']);
|
|
75
|
+
return result.stdout.trim();
|
|
34
76
|
}
|
|
35
|
-
|
|
36
77
|
function getWorktreeStatus(cwd = process.cwd()) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
78
|
+
const result = git(['-C', cwd, 'status', '--porcelain']);
|
|
79
|
+
return result.stdout
|
|
80
|
+
.split('\n')
|
|
81
|
+
.map(line => line.trimEnd())
|
|
82
|
+
.filter(Boolean);
|
|
42
83
|
}
|
|
43
|
-
|
|
44
84
|
function isDirty(cwd = process.cwd()) {
|
|
45
|
-
|
|
46
|
-
|
|
85
|
+
const result = git(['-C', cwd, 'status', '--porcelain']);
|
|
86
|
+
return result.stdout.trim().length > 0;
|
|
47
87
|
}
|
|
48
|
-
|
|
49
88
|
function getUncommittedCount(cwd = process.cwd()) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
89
|
+
const result = git(['-C', cwd, 'status', '--porcelain']);
|
|
90
|
+
if (!result.stdout.trim()) {
|
|
91
|
+
return 0;
|
|
92
|
+
}
|
|
93
|
+
return result.stdout.trim().split('\n').length;
|
|
53
94
|
}
|
|
54
|
-
|
|
55
95
|
function parseUnmergedFiles(output = '') {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
.filter(Boolean)
|
|
63
|
-
));
|
|
96
|
+
return Array.from(new Set(output
|
|
97
|
+
.split('\n')
|
|
98
|
+
.map(line => line.trim())
|
|
99
|
+
.filter(Boolean)
|
|
100
|
+
.map(line => line.split('\t')[1])
|
|
101
|
+
.filter(Boolean)));
|
|
64
102
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
rebaseHead,
|
|
94
|
-
detached,
|
|
95
|
-
unmergedFiles,
|
|
96
|
-
rebaseDir
|
|
97
|
-
};
|
|
103
|
+
function detectRebaseState(cwd = process.cwd(), options = {}) {
|
|
104
|
+
const { gitRunner = git, fsModule = fsMod, pathModule = pathMod } = options;
|
|
105
|
+
const gitDirResult = gitRunner(['-C', cwd, 'rev-parse', '--git-dir']);
|
|
106
|
+
const resolvedGitDir = gitDirResult.status === 0
|
|
107
|
+
? gitDirResult.stdout.trim()
|
|
108
|
+
: '.git';
|
|
109
|
+
const gitDir = pathModule.isAbsolute(resolvedGitDir)
|
|
110
|
+
? resolvedGitDir
|
|
111
|
+
: pathModule.join(cwd, resolvedGitDir);
|
|
112
|
+
const rebaseMergeDir = pathModule.join(gitDir, 'rebase-merge');
|
|
113
|
+
const rebaseApplyDir = pathModule.join(gitDir, 'rebase-apply');
|
|
114
|
+
const rebaseDir = fsModule.existsSync(rebaseMergeDir)
|
|
115
|
+
? rebaseMergeDir
|
|
116
|
+
: (fsModule.existsSync(rebaseApplyDir) ? rebaseApplyDir : null);
|
|
117
|
+
const headResult = gitRunner(['-C', cwd, 'symbolic-ref', '--quiet', '--short', 'HEAD']);
|
|
118
|
+
const detached = headResult.status !== 0;
|
|
119
|
+
const showCurrentResult = gitRunner(['-C', cwd, 'rebase', '--show-current']);
|
|
120
|
+
const rebaseHead = showCurrentResult.status === 0 ? showCurrentResult.stdout.trim() : '';
|
|
121
|
+
const unmergedResult = gitRunner(['-C', cwd, 'ls-files', '-u']);
|
|
122
|
+
const unmergedFiles = unmergedResult.status === 0 ? parseUnmergedFiles(unmergedResult.stdout) : [];
|
|
123
|
+
const inProgress = Boolean(rebaseDir || rebaseHead || unmergedFiles.length > 0);
|
|
124
|
+
return {
|
|
125
|
+
inProgress,
|
|
126
|
+
rebaseHead,
|
|
127
|
+
detached,
|
|
128
|
+
unmergedFiles,
|
|
129
|
+
rebaseDir
|
|
130
|
+
};
|
|
98
131
|
}
|
|
99
|
-
|
|
100
132
|
function getLastCommit() {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
133
|
+
const result = git(['log', '-1', '--format=%H|%ad|%s']);
|
|
134
|
+
const [sha, date, subject] = result.stdout.trim().split('|');
|
|
135
|
+
return { sha, date, subject };
|
|
104
136
|
}
|
|
105
|
-
|
|
106
137
|
function getLastThreeCommits() {
|
|
107
|
-
|
|
108
|
-
|
|
138
|
+
const result = git(['log', '-3', '--format=%s']);
|
|
139
|
+
return result.stdout.trim().split('\n');
|
|
109
140
|
}
|
|
110
|
-
|
|
111
|
-
module.exports = {
|
|
112
|
-
git,
|
|
113
|
-
run,
|
|
114
|
-
getCurrentBranch,
|
|
115
|
-
getWorktreeStatus,
|
|
116
|
-
isDirty,
|
|
117
|
-
getUncommittedCount,
|
|
118
|
-
detectRebaseState,
|
|
119
|
-
getLastCommit,
|
|
120
|
-
getLastThreeCommits
|
|
121
|
-
};
|
package/lib/core/git.ts
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import childProcess, { spawnSync } from 'node:child_process';
|
|
2
|
+
import type { SpawnSyncOptions } from 'node:child_process';
|
|
3
|
+
import * as fsMod from 'node:fs';
|
|
4
|
+
import * as pathMod from 'node:path';
|
|
5
|
+
|
|
6
|
+
interface GitOptions {
|
|
7
|
+
encoding?: BufferEncoding;
|
|
8
|
+
stdio?: import('node:child_process').StdioOptions;
|
|
9
|
+
cwd?: string;
|
|
10
|
+
env?: NodeJS.ProcessEnv;
|
|
11
|
+
maxBuffer?: number;
|
|
12
|
+
[key: string]: unknown;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface GitResult {
|
|
16
|
+
status: number | null;
|
|
17
|
+
signal: string | null;
|
|
18
|
+
stdout: string;
|
|
19
|
+
stderr: string;
|
|
20
|
+
error?: Error | null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function git(args: string[], options: GitOptions = {}): GitResult {
|
|
24
|
+
const spawnOptions: SpawnSyncOptions = {
|
|
25
|
+
encoding: 'utf8',
|
|
26
|
+
stdio: ['ignore', 'pipe', 'pipe'] as const,
|
|
27
|
+
...options
|
|
28
|
+
};
|
|
29
|
+
const result = spawnSync('git', args, spawnOptions);
|
|
30
|
+
if (result.error && result.status === null) {
|
|
31
|
+
throw result.error;
|
|
32
|
+
}
|
|
33
|
+
return { status: result.status, signal: result.signal, stdout: String(result.stdout ?? ''), stderr: String(result.stderr ?? ''), error: result.error };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function run(command: string, args: string[], options: GitOptions = {}): GitResult {
|
|
37
|
+
const spawnOptions: SpawnSyncOptions = {
|
|
38
|
+
encoding: 'utf8',
|
|
39
|
+
stdio: ['ignore', 'pipe', 'pipe'] as const,
|
|
40
|
+
...options
|
|
41
|
+
};
|
|
42
|
+
const result = spawnSync(command, args, spawnOptions);
|
|
43
|
+
if (result.error && result.status === null) {
|
|
44
|
+
throw result.error;
|
|
45
|
+
}
|
|
46
|
+
return { status: result.status, signal: result.signal, stdout: String(result.stdout ?? ''), stderr: String(result.stderr ?? ''), error: result.error };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function getCurrentBranch(cwd: string = process.cwd()): string {
|
|
50
|
+
const result = git(['-C', cwd, 'branch', '--show-current']);
|
|
51
|
+
return result.stdout.trim();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function getWorktreeStatus(cwd: string = process.cwd()): string[] {
|
|
55
|
+
const result = git(['-C', cwd, 'status', '--porcelain']);
|
|
56
|
+
return result.stdout
|
|
57
|
+
.split('\n')
|
|
58
|
+
.map(line => line.trimEnd())
|
|
59
|
+
.filter(Boolean);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function isDirty(cwd: string = process.cwd()): boolean {
|
|
63
|
+
const result = git(['-C', cwd, 'status', '--porcelain']);
|
|
64
|
+
return result.stdout.trim().length > 0;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function getUncommittedCount(cwd: string = process.cwd()): number {
|
|
68
|
+
const result = git(['-C', cwd, 'status', '--porcelain']);
|
|
69
|
+
if (!result.stdout.trim()) {return 0;}
|
|
70
|
+
return result.stdout.trim().split('\n').length;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function parseUnmergedFiles(output: string = ''): string[] {
|
|
74
|
+
return Array.from(new Set(
|
|
75
|
+
output
|
|
76
|
+
.split('\n')
|
|
77
|
+
.map(line => line.trim())
|
|
78
|
+
.filter(Boolean)
|
|
79
|
+
.map(line => line.split('\t')[1])
|
|
80
|
+
.filter(Boolean)
|
|
81
|
+
));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
interface RebaseStateResult {
|
|
85
|
+
inProgress: boolean;
|
|
86
|
+
rebaseHead: string;
|
|
87
|
+
detached: boolean;
|
|
88
|
+
unmergedFiles: string[];
|
|
89
|
+
rebaseDir: string | null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
interface GitRunner {
|
|
93
|
+
(args: string[]): GitResult;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
interface DetectRebaseOptions {
|
|
97
|
+
gitRunner?: GitRunner;
|
|
98
|
+
fsModule?: typeof fsMod;
|
|
99
|
+
pathModule?: typeof pathMod;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function detectRebaseState(cwd: string = process.cwd(), options: DetectRebaseOptions = {}): RebaseStateResult {
|
|
103
|
+
const { gitRunner = git, fsModule = fsMod, pathModule = pathMod } = options;
|
|
104
|
+
const gitDirResult = gitRunner(['-C', cwd, 'rev-parse', '--git-dir']);
|
|
105
|
+
const resolvedGitDir = gitDirResult.status === 0
|
|
106
|
+
? gitDirResult.stdout.trim()
|
|
107
|
+
: '.git';
|
|
108
|
+
const gitDir = pathModule.isAbsolute(resolvedGitDir)
|
|
109
|
+
? resolvedGitDir
|
|
110
|
+
: pathModule.join(cwd, resolvedGitDir);
|
|
111
|
+
const rebaseMergeDir = pathModule.join(gitDir, 'rebase-merge');
|
|
112
|
+
const rebaseApplyDir = pathModule.join(gitDir, 'rebase-apply');
|
|
113
|
+
const rebaseDir = fsModule.existsSync(rebaseMergeDir)
|
|
114
|
+
? rebaseMergeDir
|
|
115
|
+
: (fsModule.existsSync(rebaseApplyDir) ? rebaseApplyDir : null);
|
|
116
|
+
|
|
117
|
+
const headResult = gitRunner(['-C', cwd, 'symbolic-ref', '--quiet', '--short', 'HEAD']);
|
|
118
|
+
const detached = headResult.status !== 0;
|
|
119
|
+
|
|
120
|
+
const showCurrentResult = gitRunner(['-C', cwd, 'rebase', '--show-current']);
|
|
121
|
+
const rebaseHead = showCurrentResult.status === 0 ? showCurrentResult.stdout.trim() : '';
|
|
122
|
+
|
|
123
|
+
const unmergedResult = gitRunner(['-C', cwd, 'ls-files', '-u']);
|
|
124
|
+
const unmergedFiles = unmergedResult.status === 0 ? parseUnmergedFiles(unmergedResult.stdout) : [];
|
|
125
|
+
|
|
126
|
+
const inProgress = Boolean(rebaseDir || rebaseHead || unmergedFiles.length > 0);
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
inProgress,
|
|
130
|
+
rebaseHead,
|
|
131
|
+
detached,
|
|
132
|
+
unmergedFiles,
|
|
133
|
+
rebaseDir
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function getLastCommit(): { sha: string; date: string; subject: string } {
|
|
138
|
+
const result = git(['log', '-1', '--format=%H|%ad|%s']);
|
|
139
|
+
const [sha, date, subject] = result.stdout.trim().split('|');
|
|
140
|
+
return { sha, date, subject };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function getLastThreeCommits(): string[] {
|
|
144
|
+
const result = git(['log', '-3', '--format=%s']);
|
|
145
|
+
return result.stdout.trim().split('\n');
|
|
146
|
+
}
|