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/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 install(parseInstallOptions(rest));
27
+ return runInstall(rest);
25
28
  case "addDir":
26
- return addDirs(parseInstallOptions(rest));
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 parseInstallOptions(args) {
49
- const options = {
50
- targetDirs: [],
51
- configPath: undefined,
52
- relativePaths: false
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 requireValue(args, index, flag) {
74
- const value = args[index];
75
- if (!value || value.startsWith("-")) {
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
- async function runGitPreCommit() {
82
- const baseDir = getBaseDir();
83
- const projectDir = process.cwd();
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
- async function runGitCommitMsg(args) {
105
- const commitMsgPath = args[0];
106
- if (!commitMsgPath) {
107
- throw new Error("git-commit-msg requires a commit message file path");
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
- appendCommitTrailer(path.resolve(commitMsgPath), readPendingAiLineCount(getBaseDir(), process.cwd()));
143
+ return readBasePointer();
111
144
  }
112
145
 
113
- function getBaseDir() {
114
- return process.env.X7_CODE_LINE_BASE || process.env.INIT_CWD || process.cwd();
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 writeCacheFile(baseDir, fileName, cache) {
129
- const cacheDir = getCacheDir(baseDir);
130
- fs.writeFileSync(path.join(cacheDir, fileName), `${JSON.stringify(cache, null, 2)}\n`);
131
- }
132
-
133
- function printHelp() {
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
- module.exports = {
147
- runCli
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
+ };
@@ -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
+ };