x7-code-line 0.3.0 → 0.4.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/README.md +412 -398
- package/bin/x7-code-line.js +8 -8
- package/hooks/git/commit-msg +4 -4
- package/hooks/git/pre-commit +4 -4
- package/package.json +30 -30
- package/src/agent-hooks.js +59 -28
- package/src/cache.js +128 -69
- package/src/cli.js +153 -108
- package/src/diff-lines.js +115 -115
- package/src/git-trailer.js +46 -46
- package/src/index.js +7 -7
- package/src/install.js +268 -267
- package/src/postinstall.js +13 -13
- package/templates/.codex/hooks.json +30 -14
- package/templates/.cursor/hooks.json +17 -14
package/src/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
3
|
const fs = require("node:fs");
|
|
4
4
|
const path = require("node:path");
|
|
5
5
|
const { handlePromptSubmit, handleStop } = require("./agent-hooks");
|
|
@@ -10,139 +10,184 @@ const {
|
|
|
10
10
|
NORMAL_DIFF_CACHE_FILE,
|
|
11
11
|
PENDING_COMMIT_FILE,
|
|
12
12
|
clearProjectIds,
|
|
13
|
+
findInstalledBase,
|
|
13
14
|
getCacheDir,
|
|
14
15
|
getProjectIds,
|
|
16
|
+
hasInstallFile,
|
|
17
|
+
readBasePointer,
|
|
15
18
|
readLineIdCache
|
|
16
19
|
} = require("./cache");
|
|
17
20
|
const { getGitDiffLineIds } = require("./diff-lines");
|
|
18
|
-
|
|
19
|
-
async function runCli(argv) {
|
|
20
|
-
const [command, ...rest] = argv;
|
|
21
|
-
|
|
22
|
-
switch (command) {
|
|
21
|
+
|
|
22
|
+
async function runCli(argv) {
|
|
23
|
+
const [command, ...rest] = argv;
|
|
24
|
+
|
|
25
|
+
switch (command) {
|
|
23
26
|
case "install":
|
|
24
|
-
return
|
|
27
|
+
return runInstall(rest);
|
|
25
28
|
case "addDir":
|
|
26
|
-
return
|
|
29
|
+
return runAddDir(rest);
|
|
27
30
|
case "prompt-submit":
|
|
28
31
|
case "cursor-before-submit-prompt":
|
|
29
32
|
case "codex-user-prompt-submit":
|
|
30
|
-
return handlePromptSubmit();
|
|
33
|
+
return handlePromptSubmit(requireBaseDir());
|
|
31
34
|
case "stop":
|
|
32
35
|
case "cursor-stop":
|
|
33
36
|
case "codex-stop":
|
|
34
|
-
return handleStop();
|
|
35
|
-
case "git-pre-commit":
|
|
36
|
-
return runGitPreCommit();
|
|
37
|
-
case "git-commit-msg":
|
|
38
|
-
return runGitCommitMsg(rest);
|
|
39
|
-
case "-h":
|
|
40
|
-
case "--help":
|
|
41
|
-
case undefined:
|
|
42
|
-
return printHelp();
|
|
43
|
-
default:
|
|
44
|
-
throw new Error(`Unknown command: ${command}`);
|
|
45
|
-
}
|
|
37
|
+
return handleStop(requireBaseDir());
|
|
38
|
+
case "git-pre-commit":
|
|
39
|
+
return runGitPreCommit();
|
|
40
|
+
case "git-commit-msg":
|
|
41
|
+
return runGitCommitMsg(rest);
|
|
42
|
+
case "-h":
|
|
43
|
+
case "--help":
|
|
44
|
+
case undefined:
|
|
45
|
+
return printHelp();
|
|
46
|
+
default:
|
|
47
|
+
throw new Error(`Unknown command: ${command}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function parseInstallOptions(args) {
|
|
52
|
+
const options = {
|
|
53
|
+
targetDirs: [],
|
|
54
|
+
configPath: undefined,
|
|
55
|
+
relativePaths: false
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
59
|
+
const arg = args[index];
|
|
60
|
+
if (arg === "--config" || arg === "-c") {
|
|
61
|
+
options.configPath = requireValue(args, (index += 1), arg);
|
|
62
|
+
} else if (arg === "--relative") {
|
|
63
|
+
options.relativePaths = true;
|
|
64
|
+
} else if (arg === "--dir" || arg === "-d") {
|
|
65
|
+
options.targetDirs.push(requireValue(args, (index += 1), arg));
|
|
66
|
+
} else if (arg === "--dirs") {
|
|
67
|
+
options.targetDirs.push(...requireValue(args, (index += 1), arg).split(","));
|
|
68
|
+
} else {
|
|
69
|
+
options.targetDirs.push(arg);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return options;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function requireValue(args, index, flag) {
|
|
77
|
+
const value = args[index];
|
|
78
|
+
if (!value || value.startsWith("-")) {
|
|
79
|
+
throw new Error(`${flag} requires a value`);
|
|
80
|
+
}
|
|
81
|
+
return value;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function runGitPreCommit() {
|
|
85
|
+
const baseDir = requireBaseDir();
|
|
86
|
+
const projectDir = process.cwd();
|
|
87
|
+
const aiCache = readLineIdCache(baseDir, AI_DIFF_CACHE_FILE);
|
|
88
|
+
const normalCache = readLineIdCache(baseDir, NORMAL_DIFF_CACHE_FILE);
|
|
89
|
+
const aiIds = getProjectIds(aiCache, projectDir);
|
|
90
|
+
const stagedIds = getGitDiffLineIds(projectDir, { staged: true });
|
|
91
|
+
const aiLineCount = stagedIds.filter((id) => aiIds.has(id)).length;
|
|
92
|
+
const pendingPath = path.join(getCacheDir(baseDir), PENDING_COMMIT_FILE);
|
|
93
|
+
fs.writeFileSync(
|
|
94
|
+
pendingPath,
|
|
95
|
+
`${JSON.stringify({ projects: { [path.resolve(projectDir)]: { aiLineCount } } }, null, 2)}\n`
|
|
96
|
+
);
|
|
97
|
+
clearProjectIds(aiCache, projectDir);
|
|
98
|
+
clearProjectIds(normalCache, projectDir);
|
|
99
|
+
writeCacheFile(baseDir, AI_DIFF_CACHE_FILE, aiCache);
|
|
100
|
+
writeCacheFile(baseDir, NORMAL_DIFF_CACHE_FILE, normalCache);
|
|
101
|
+
|
|
102
|
+
if (process.env.X7_CODE_LINE_DEBUG === "1") {
|
|
103
|
+
console.error(`[x7-code-line] git pre-commit ai lines: ${aiLineCount}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function runGitCommitMsg(args) {
|
|
108
|
+
const commitMsgPath = args[0];
|
|
109
|
+
if (!commitMsgPath) {
|
|
110
|
+
throw new Error("git-commit-msg requires a commit message file path");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
appendCommitTrailer(path.resolve(commitMsgPath), readPendingAiLineCount(requireBaseDir(), process.cwd()));
|
|
46
114
|
}
|
|
47
115
|
|
|
48
|
-
function
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
for (let index = 0; index < args.length; index += 1) {
|
|
56
|
-
const arg = args[index];
|
|
57
|
-
if (arg === "--config" || arg === "-c") {
|
|
58
|
-
options.configPath = requireValue(args, (index += 1), arg);
|
|
59
|
-
} else if (arg === "--relative") {
|
|
60
|
-
options.relativePaths = true;
|
|
61
|
-
} else if (arg === "--dir" || arg === "-d") {
|
|
62
|
-
options.targetDirs.push(requireValue(args, (index += 1), arg));
|
|
63
|
-
} else if (arg === "--dirs") {
|
|
64
|
-
options.targetDirs.push(...requireValue(args, (index += 1), arg).split(","));
|
|
65
|
-
} else {
|
|
66
|
-
options.targetDirs.push(arg);
|
|
67
|
-
}
|
|
116
|
+
function requireBaseDir() {
|
|
117
|
+
const baseDir = resolveBaseDir();
|
|
118
|
+
if (!baseDir) {
|
|
119
|
+
throw new Error(
|
|
120
|
+
"Unable to locate x7-code-line install base. Run the command from the install base, or set X7_CODE_LINE_BASE."
|
|
121
|
+
);
|
|
68
122
|
}
|
|
69
|
-
|
|
70
|
-
return options;
|
|
123
|
+
return baseDir;
|
|
71
124
|
}
|
|
72
125
|
|
|
73
|
-
function
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
throw new Error(`${flag} requires a value`);
|
|
126
|
+
function resolveBaseDir() {
|
|
127
|
+
if (process.env.X7_CODE_LINE_BASE && hasInstallFile(process.env.X7_CODE_LINE_BASE)) {
|
|
128
|
+
return path.resolve(process.env.X7_CODE_LINE_BASE);
|
|
77
129
|
}
|
|
78
|
-
return value;
|
|
79
|
-
}
|
|
80
130
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const aiCache = readLineIdCache(baseDir, AI_DIFF_CACHE_FILE);
|
|
85
|
-
const normalCache = readLineIdCache(baseDir, NORMAL_DIFF_CACHE_FILE);
|
|
86
|
-
const aiIds = getProjectIds(aiCache, projectDir);
|
|
87
|
-
const stagedIds = getGitDiffLineIds(projectDir, { staged: true });
|
|
88
|
-
const aiLineCount = stagedIds.filter((id) => aiIds.has(id)).length;
|
|
89
|
-
const pendingPath = path.join(getCacheDir(baseDir), PENDING_COMMIT_FILE);
|
|
90
|
-
fs.writeFileSync(
|
|
91
|
-
pendingPath,
|
|
92
|
-
`${JSON.stringify({ projects: { [path.resolve(projectDir)]: { aiLineCount } } }, null, 2)}\n`
|
|
93
|
-
);
|
|
94
|
-
clearProjectIds(aiCache, projectDir);
|
|
95
|
-
clearProjectIds(normalCache, projectDir);
|
|
96
|
-
writeCacheFile(baseDir, AI_DIFF_CACHE_FILE, aiCache);
|
|
97
|
-
writeCacheFile(baseDir, NORMAL_DIFF_CACHE_FILE, normalCache);
|
|
98
|
-
|
|
99
|
-
if (process.env.X7_CODE_LINE_DEBUG === "1") {
|
|
100
|
-
console.error(`[x7-code-line] git pre-commit ai lines: ${aiLineCount}`);
|
|
131
|
+
const discoveredFromCwd = findInstalledBase(process.cwd());
|
|
132
|
+
if (discoveredFromCwd) {
|
|
133
|
+
return discoveredFromCwd;
|
|
101
134
|
}
|
|
102
|
-
}
|
|
103
135
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
136
|
+
if (process.env.INIT_CWD) {
|
|
137
|
+
const discoveredFromInit = findInstalledBase(process.env.INIT_CWD);
|
|
138
|
+
if (discoveredFromInit) {
|
|
139
|
+
return discoveredFromInit;
|
|
140
|
+
}
|
|
108
141
|
}
|
|
109
142
|
|
|
110
|
-
|
|
143
|
+
return readBasePointer();
|
|
111
144
|
}
|
|
112
145
|
|
|
113
|
-
function
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
function readPendingAiLineCount(baseDir, projectDir) {
|
|
118
|
-
const pendingPath = path.join(getCacheDir(baseDir), PENDING_COMMIT_FILE);
|
|
119
|
-
try {
|
|
120
|
-
const pending = JSON.parse(fs.readFileSync(pendingPath, "utf8"));
|
|
121
|
-
const project = pending.projects && pending.projects[path.resolve(projectDir)];
|
|
122
|
-
return Number.isInteger(project && project.aiLineCount) ? project.aiLineCount : 0;
|
|
123
|
-
} catch {
|
|
124
|
-
return 0;
|
|
146
|
+
function runInstall(args) {
|
|
147
|
+
const installed = install(parseInstallOptions(args));
|
|
148
|
+
for (const targetDir of installed) {
|
|
149
|
+
console.log(`[x7-code-line] ${targetDir} configured successfully.`);
|
|
125
150
|
}
|
|
151
|
+
return installed;
|
|
126
152
|
}
|
|
127
153
|
|
|
128
|
-
function
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const text = [
|
|
135
|
-
"Usage:",
|
|
136
|
-
" x7-code-line install --dir <absolute-path> [--dir <absolute-path> ...] [--config path] [--relative]",
|
|
137
|
-
" x7-code-line addDir --dir <absolute-path> [--dir <absolute-path> ...] [--config path] [--relative]",
|
|
138
|
-
" x7-code-line prompt-submit",
|
|
139
|
-
" x7-code-line stop",
|
|
140
|
-
" x7-code-line git-pre-commit",
|
|
141
|
-
" x7-code-line git-commit-msg <commit-msg-file>"
|
|
142
|
-
].join("\n");
|
|
143
|
-
fs.writeSync(process.stdout.fd, `${text}\n`);
|
|
154
|
+
function runAddDir(args) {
|
|
155
|
+
const added = addDirs(parseInstallOptions(args));
|
|
156
|
+
for (const targetDir of added) {
|
|
157
|
+
console.log(`[x7-code-line] ${targetDir} configured successfully.`);
|
|
158
|
+
}
|
|
159
|
+
return added;
|
|
144
160
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
161
|
+
|
|
162
|
+
function readPendingAiLineCount(baseDir, projectDir) {
|
|
163
|
+
const pendingPath = path.join(getCacheDir(baseDir), PENDING_COMMIT_FILE);
|
|
164
|
+
try {
|
|
165
|
+
const pending = JSON.parse(fs.readFileSync(pendingPath, "utf8"));
|
|
166
|
+
const project = pending.projects && pending.projects[path.resolve(projectDir)];
|
|
167
|
+
return Number.isInteger(project && project.aiLineCount) ? project.aiLineCount : 0;
|
|
168
|
+
} catch {
|
|
169
|
+
return 0;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function writeCacheFile(baseDir, fileName, cache) {
|
|
174
|
+
const cacheDir = getCacheDir(baseDir);
|
|
175
|
+
fs.writeFileSync(path.join(cacheDir, fileName), `${JSON.stringify(cache, null, 2)}\n`);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function printHelp() {
|
|
179
|
+
const text = [
|
|
180
|
+
"Usage:",
|
|
181
|
+
" x7-code-line install --dir <absolute-path> [--dir <absolute-path> ...] [--config path] [--relative]",
|
|
182
|
+
" x7-code-line addDir --dir <absolute-path> [--dir <absolute-path> ...] [--config path] [--relative]",
|
|
183
|
+
" x7-code-line prompt-submit",
|
|
184
|
+
" x7-code-line stop",
|
|
185
|
+
" x7-code-line git-pre-commit",
|
|
186
|
+
" x7-code-line git-commit-msg <commit-msg-file>"
|
|
187
|
+
].join("\n");
|
|
188
|
+
fs.writeSync(process.stdout.fd, `${text}\n`);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
module.exports = {
|
|
192
|
+
runCli
|
|
193
|
+
};
|
package/src/diff-lines.js
CHANGED
|
@@ -1,115 +1,115 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
const crypto = require("node:crypto");
|
|
4
|
-
const fs = require("node:fs");
|
|
5
|
-
const path = require("node:path");
|
|
6
|
-
const { execFileSync } = require("node:child_process");
|
|
7
|
-
|
|
8
|
-
function getGitDiffLineIds(projectDir, options = {}) {
|
|
9
|
-
const args = ["diff", "--no-ext-diff", "--unified=0", "--no-color"];
|
|
10
|
-
if (options.staged) {
|
|
11
|
-
args.splice(1, 0, "--cached");
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const diff = execFileSync("git", args, {
|
|
15
|
-
cwd: projectDir,
|
|
16
|
-
encoding: "utf8",
|
|
17
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
return parseDiffLineIds(projectDir, diff, options);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function parseDiffLineIds(projectDir, diff, options = {}) {
|
|
24
|
-
const ids = [];
|
|
25
|
-
let currentFile = "";
|
|
26
|
-
let oldLine = 0;
|
|
27
|
-
let newLine = 0;
|
|
28
|
-
const fileMtimes = new Map();
|
|
29
|
-
|
|
30
|
-
for (const line of diff.split(/\r?\n/u)) {
|
|
31
|
-
if (line.startsWith("+++ b/")) {
|
|
32
|
-
currentFile = line.slice("+++ b/".length);
|
|
33
|
-
continue;
|
|
34
|
-
}
|
|
35
|
-
if (line.startsWith("+++ ")) {
|
|
36
|
-
currentFile = line.slice("+++ ".length);
|
|
37
|
-
continue;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const hunk = /^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/u.exec(line);
|
|
41
|
-
if (hunk) {
|
|
42
|
-
oldLine = Number(hunk[1]);
|
|
43
|
-
newLine = Number(hunk[2]);
|
|
44
|
-
continue;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
if (!currentFile || line.startsWith("diff --git") || line.startsWith("index ")) {
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (line.startsWith("+") && !line.startsWith("+++")) {
|
|
52
|
-
ids.push(
|
|
53
|
-
makeLineId(projectDir, currentFile, "add", newLine, line.slice(1), getFileModifiedTime(projectDir, currentFile, fileMtimes))
|
|
54
|
-
);
|
|
55
|
-
newLine += 1;
|
|
56
|
-
} else if (line.startsWith("-") && !line.startsWith("---")) {
|
|
57
|
-
ids.push(
|
|
58
|
-
makeLineId(
|
|
59
|
-
projectDir,
|
|
60
|
-
currentFile,
|
|
61
|
-
"delete",
|
|
62
|
-
oldLine,
|
|
63
|
-
line.slice(1),
|
|
64
|
-
getFileModifiedTime(projectDir, currentFile, fileMtimes)
|
|
65
|
-
)
|
|
66
|
-
);
|
|
67
|
-
oldLine += 1;
|
|
68
|
-
} else if (line.startsWith(" ")) {
|
|
69
|
-
oldLine += 1;
|
|
70
|
-
newLine += 1;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
return ids;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function makeLineId(projectDir, filePath, changeType, lineNumber, content, modifiedTimeMs = 0) {
|
|
78
|
-
return crypto
|
|
79
|
-
.createHash("sha256")
|
|
80
|
-
.update(
|
|
81
|
-
JSON.stringify({
|
|
82
|
-
project: path.resolve(projectDir),
|
|
83
|
-
file: filePath,
|
|
84
|
-
changeType,
|
|
85
|
-
lineNumber,
|
|
86
|
-
content,
|
|
87
|
-
modifiedTimeMs
|
|
88
|
-
})
|
|
89
|
-
)
|
|
90
|
-
.digest("hex");
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function getFileModifiedTime(projectDir, filePath, fileMtimes) {
|
|
94
|
-
const key = `${path.resolve(projectDir)}\0${filePath}`;
|
|
95
|
-
if (fileMtimes.has(key)) {
|
|
96
|
-
return fileMtimes.get(key);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const absolutePath = path.resolve(projectDir, filePath);
|
|
100
|
-
let modifiedTimeMs = 0;
|
|
101
|
-
try {
|
|
102
|
-
modifiedTimeMs = fs.statSync(absolutePath).mtimeMs;
|
|
103
|
-
} catch {
|
|
104
|
-
modifiedTimeMs = 0;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
fileMtimes.set(key, modifiedTimeMs);
|
|
108
|
-
return modifiedTimeMs;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
module.exports = {
|
|
112
|
-
getGitDiffLineIds,
|
|
113
|
-
makeLineId,
|
|
114
|
-
parseDiffLineIds
|
|
115
|
-
};
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const crypto = require("node:crypto");
|
|
4
|
+
const fs = require("node:fs");
|
|
5
|
+
const path = require("node:path");
|
|
6
|
+
const { execFileSync } = require("node:child_process");
|
|
7
|
+
|
|
8
|
+
function getGitDiffLineIds(projectDir, options = {}) {
|
|
9
|
+
const args = ["diff", "--no-ext-diff", "--unified=0", "--no-color"];
|
|
10
|
+
if (options.staged) {
|
|
11
|
+
args.splice(1, 0, "--cached");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const diff = execFileSync("git", args, {
|
|
15
|
+
cwd: projectDir,
|
|
16
|
+
encoding: "utf8",
|
|
17
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
return parseDiffLineIds(projectDir, diff, options);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function parseDiffLineIds(projectDir, diff, options = {}) {
|
|
24
|
+
const ids = [];
|
|
25
|
+
let currentFile = "";
|
|
26
|
+
let oldLine = 0;
|
|
27
|
+
let newLine = 0;
|
|
28
|
+
const fileMtimes = new Map();
|
|
29
|
+
|
|
30
|
+
for (const line of diff.split(/\r?\n/u)) {
|
|
31
|
+
if (line.startsWith("+++ b/")) {
|
|
32
|
+
currentFile = line.slice("+++ b/".length);
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (line.startsWith("+++ ")) {
|
|
36
|
+
currentFile = line.slice("+++ ".length);
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const hunk = /^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/u.exec(line);
|
|
41
|
+
if (hunk) {
|
|
42
|
+
oldLine = Number(hunk[1]);
|
|
43
|
+
newLine = Number(hunk[2]);
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (!currentFile || line.startsWith("diff --git") || line.startsWith("index ")) {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (line.startsWith("+") && !line.startsWith("+++")) {
|
|
52
|
+
ids.push(
|
|
53
|
+
makeLineId(projectDir, currentFile, "add", newLine, line.slice(1), getFileModifiedTime(projectDir, currentFile, fileMtimes))
|
|
54
|
+
);
|
|
55
|
+
newLine += 1;
|
|
56
|
+
} else if (line.startsWith("-") && !line.startsWith("---")) {
|
|
57
|
+
ids.push(
|
|
58
|
+
makeLineId(
|
|
59
|
+
projectDir,
|
|
60
|
+
currentFile,
|
|
61
|
+
"delete",
|
|
62
|
+
oldLine,
|
|
63
|
+
line.slice(1),
|
|
64
|
+
getFileModifiedTime(projectDir, currentFile, fileMtimes)
|
|
65
|
+
)
|
|
66
|
+
);
|
|
67
|
+
oldLine += 1;
|
|
68
|
+
} else if (line.startsWith(" ")) {
|
|
69
|
+
oldLine += 1;
|
|
70
|
+
newLine += 1;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return ids;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function makeLineId(projectDir, filePath, changeType, lineNumber, content, modifiedTimeMs = 0) {
|
|
78
|
+
return crypto
|
|
79
|
+
.createHash("sha256")
|
|
80
|
+
.update(
|
|
81
|
+
JSON.stringify({
|
|
82
|
+
project: path.resolve(projectDir),
|
|
83
|
+
file: filePath,
|
|
84
|
+
changeType,
|
|
85
|
+
lineNumber,
|
|
86
|
+
content,
|
|
87
|
+
modifiedTimeMs
|
|
88
|
+
})
|
|
89
|
+
)
|
|
90
|
+
.digest("hex");
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function getFileModifiedTime(projectDir, filePath, fileMtimes) {
|
|
94
|
+
const key = `${path.resolve(projectDir)}\0${filePath}`;
|
|
95
|
+
if (fileMtimes.has(key)) {
|
|
96
|
+
return fileMtimes.get(key);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const absolutePath = path.resolve(projectDir, filePath);
|
|
100
|
+
let modifiedTimeMs = 0;
|
|
101
|
+
try {
|
|
102
|
+
modifiedTimeMs = fs.statSync(absolutePath).mtimeMs;
|
|
103
|
+
} catch {
|
|
104
|
+
modifiedTimeMs = 0;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
fileMtimes.set(key, modifiedTimeMs);
|
|
108
|
+
return modifiedTimeMs;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
module.exports = {
|
|
112
|
+
getGitDiffLineIds,
|
|
113
|
+
makeLineId,
|
|
114
|
+
parseDiffLineIds
|
|
115
|
+
};
|
package/src/git-trailer.js
CHANGED
|
@@ -1,46 +1,46 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
const fs = require("node:fs");
|
|
4
|
-
const os = require("node:os");
|
|
5
|
-
const { execFileSync } = require("node:child_process");
|
|
6
|
-
|
|
7
|
-
const TRAILER_TOKEN = "x7-ai-lines";
|
|
8
|
-
|
|
9
|
-
function appendCommitTrailer(commitMsgPath, aiLineCount = 0) {
|
|
10
|
-
const original = fs.readFileSync(commitMsgPath, "utf8");
|
|
11
|
-
const withTrailer = addTrailerWithGit(original, aiLineCount) || addTrailer(original, aiLineCount);
|
|
12
|
-
fs.writeFileSync(commitMsgPath, withTrailer);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function addTrailerWithGit(message, aiLineCount) {
|
|
16
|
-
try {
|
|
17
|
-
return execFileSync(
|
|
18
|
-
"git",
|
|
19
|
-
["interpret-trailers", "--if-exists=replace", `--trailer=${TRAILER_TOKEN}=${aiLineCount}`],
|
|
20
|
-
{
|
|
21
|
-
input: message,
|
|
22
|
-
encoding: "utf8",
|
|
23
|
-
stdio: ["pipe", "pipe", "ignore"]
|
|
24
|
-
}
|
|
25
|
-
);
|
|
26
|
-
} catch {
|
|
27
|
-
return undefined;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function addTrailer(message, aiLineCount) {
|
|
32
|
-
const normalized = message.replace(/\s+$/u, "");
|
|
33
|
-
const trailerPattern = new RegExp(`^${TRAILER_TOKEN}:`, "imu");
|
|
34
|
-
const withoutExisting = normalized
|
|
35
|
-
.split(/\r?\n/u)
|
|
36
|
-
.filter((line) => !trailerPattern.test(line))
|
|
37
|
-
.join(os.EOL)
|
|
38
|
-
.replace(/\s+$/u, "");
|
|
39
|
-
|
|
40
|
-
return `${withoutExisting}${os.EOL}${os.EOL}${TRAILER_TOKEN}: ${aiLineCount}${os.EOL}`;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
module.exports = {
|
|
44
|
-
TRAILER_TOKEN,
|
|
45
|
-
appendCommitTrailer
|
|
46
|
-
};
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("node:fs");
|
|
4
|
+
const os = require("node:os");
|
|
5
|
+
const { execFileSync } = require("node:child_process");
|
|
6
|
+
|
|
7
|
+
const TRAILER_TOKEN = "x7-ai-lines";
|
|
8
|
+
|
|
9
|
+
function appendCommitTrailer(commitMsgPath, aiLineCount = 0) {
|
|
10
|
+
const original = fs.readFileSync(commitMsgPath, "utf8");
|
|
11
|
+
const withTrailer = addTrailerWithGit(original, aiLineCount) || addTrailer(original, aiLineCount);
|
|
12
|
+
fs.writeFileSync(commitMsgPath, withTrailer);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function addTrailerWithGit(message, aiLineCount) {
|
|
16
|
+
try {
|
|
17
|
+
return execFileSync(
|
|
18
|
+
"git",
|
|
19
|
+
["interpret-trailers", "--if-exists=replace", `--trailer=${TRAILER_TOKEN}=${aiLineCount}`],
|
|
20
|
+
{
|
|
21
|
+
input: message,
|
|
22
|
+
encoding: "utf8",
|
|
23
|
+
stdio: ["pipe", "pipe", "ignore"]
|
|
24
|
+
}
|
|
25
|
+
);
|
|
26
|
+
} catch {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function addTrailer(message, aiLineCount) {
|
|
32
|
+
const normalized = message.replace(/\s+$/u, "");
|
|
33
|
+
const trailerPattern = new RegExp(`^${TRAILER_TOKEN}:`, "imu");
|
|
34
|
+
const withoutExisting = normalized
|
|
35
|
+
.split(/\r?\n/u)
|
|
36
|
+
.filter((line) => !trailerPattern.test(line))
|
|
37
|
+
.join(os.EOL)
|
|
38
|
+
.replace(/\s+$/u, "");
|
|
39
|
+
|
|
40
|
+
return `${withoutExisting}${os.EOL}${os.EOL}${TRAILER_TOKEN}: ${aiLineCount}${os.EOL}`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
module.exports = {
|
|
44
|
+
TRAILER_TOKEN,
|
|
45
|
+
appendCommitTrailer
|
|
46
|
+
};
|
package/src/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
const { install } = require("./install");
|
|
4
|
-
|
|
5
|
-
module.exports = {
|
|
6
|
-
install
|
|
7
|
-
};
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { install } = require("./install");
|
|
4
|
+
|
|
5
|
+
module.exports = {
|
|
6
|
+
install
|
|
7
|
+
};
|