@rachel_rotenberg/ai-contribution-tracker 1.0.19 → 1.0.21
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/cli.js +34 -31
- package/index.ts +2 -1
- package/package.json +1 -1
package/cli.js
CHANGED
|
@@ -15,11 +15,12 @@
|
|
|
15
15
|
const fs = require("fs");
|
|
16
16
|
const path = require("path");
|
|
17
17
|
const os = require("os");
|
|
18
|
-
const { execSync } = require("child_process");
|
|
18
|
+
const { execSync, execFileSync } = require("child_process");
|
|
19
19
|
|
|
20
20
|
// ─── Constants ──────────────────────────────────────────────
|
|
21
21
|
const PLUGIN_NAME = "@rachel_rotenberg/ai-contribution-tracker";
|
|
22
|
-
const
|
|
22
|
+
const HOOK_BEGIN = "# BEGIN ai-contribution-tracker-cli";
|
|
23
|
+
const HOOK_END = "# END ai-contribution-tracker-cli";
|
|
23
24
|
|
|
24
25
|
// ─── Logging helpers ────────────────────────────────────────
|
|
25
26
|
function ok(msg) { console.log(` \u2713 ${msg}`); }
|
|
@@ -29,20 +30,22 @@ function fail(msg) { console.error(` \u2717 ${msg}`); }
|
|
|
29
30
|
|
|
30
31
|
// ─── Git hook body (shell script, runs on all platforms via Git Bash) ───
|
|
31
32
|
const HOOK_BODY = [
|
|
32
|
-
"",
|
|
33
|
-
"# AI Contribution Tracker \u2014 reads AI_IMPACT_PENDING flag",
|
|
33
|
+
"# BEGIN ai-contribution-tracker-cli",
|
|
34
34
|
'IMPACT_FLAG=$(git rev-parse --git-path AI_IMPACT_PENDING)',
|
|
35
35
|
'STATE_FILE=$(git rev-parse --git-path ai-tracker-state.json)',
|
|
36
36
|
'if [ -f "$IMPACT_FLAG" ]; then',
|
|
37
37
|
' MARKER=$(cat "$IMPACT_FLAG")',
|
|
38
38
|
' if [ -z "$MARKER" ]; then MARKER="Impacted by AI"; fi',
|
|
39
|
-
'
|
|
39
|
+
' FIRST_LINE=$(head -n 1 "$IMPACT_FLAG")',
|
|
40
|
+
' if ! grep -qF "$FIRST_LINE" "$1"; then',
|
|
41
|
+
' if [ -s "$1" ] && [ "$(tail -c 1 "$1" | wc -l)" -eq 0 ]; then printf "\\n" >> "$1"; fi',
|
|
40
42
|
' echo "" >> "$1"',
|
|
41
43
|
' echo "$MARKER" >> "$1"',
|
|
42
44
|
' fi',
|
|
43
45
|
' rm "$IMPACT_FLAG"',
|
|
44
46
|
'fi',
|
|
45
47
|
'if [ -f "$STATE_FILE" ]; then rm "$STATE_FILE"; fi',
|
|
48
|
+
"# END ai-contribution-tracker-cli",
|
|
46
49
|
].join("\n");
|
|
47
50
|
|
|
48
51
|
// ─── Git hook installation ──────────────────────────────────
|
|
@@ -53,7 +56,7 @@ function appendOrCreateHook(hooksDir) {
|
|
|
53
56
|
|
|
54
57
|
if (fs.existsSync(hookPath)) {
|
|
55
58
|
const existing = fs.readFileSync(hookPath, "utf8");
|
|
56
|
-
if (existing.includes(
|
|
59
|
+
if (existing.includes(HOOK_BEGIN)) {
|
|
57
60
|
skip(`commit-msg hook already has AI tracker snippet: ${hookPath}`);
|
|
58
61
|
return;
|
|
59
62
|
}
|
|
@@ -95,7 +98,7 @@ function installGitHook() {
|
|
|
95
98
|
|
|
96
99
|
try {
|
|
97
100
|
const gitPath = hooksDir.replace(/\\/g, "/");
|
|
98
|
-
|
|
101
|
+
execFileSync("git", ["config", "--global", "core.hooksPath", gitPath], {
|
|
99
102
|
encoding: "utf8",
|
|
100
103
|
stdio: ["pipe", "pipe", "pipe"],
|
|
101
104
|
});
|
|
@@ -133,7 +136,7 @@ function addPluginToConfig(configDir) {
|
|
|
133
136
|
|
|
134
137
|
// Check if already registered (plain string or [name, options] tuple)
|
|
135
138
|
const alreadyRegistered = plugins.some(
|
|
136
|
-
(p) => (typeof p === "string" ? p : p[0]) === PLUGIN_NAME
|
|
139
|
+
(p) => p && (typeof p === "string" ? p : Array.isArray(p) ? p[0] : null) === PLUGIN_NAME
|
|
137
140
|
);
|
|
138
141
|
|
|
139
142
|
if (alreadyRegistered) {
|
|
@@ -172,7 +175,7 @@ function showStatus() {
|
|
|
172
175
|
|
|
173
176
|
if (hooksPath) {
|
|
174
177
|
const hookFile = path.join(hooksPath, "commit-msg");
|
|
175
|
-
if (fs.existsSync(hookFile) && fs.readFileSync(hookFile, "utf8").includes(
|
|
178
|
+
if (fs.existsSync(hookFile) && fs.readFileSync(hookFile, "utf8").includes(HOOK_BEGIN)) {
|
|
176
179
|
ok(`Git hook installed: ${hookFile}`);
|
|
177
180
|
} else {
|
|
178
181
|
warn(`core.hooksPath set to ${hooksPath} but no AI tracker snippet found`);
|
|
@@ -188,7 +191,7 @@ function showStatus() {
|
|
|
188
191
|
const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
189
192
|
const plugins = Array.isArray(config.plugin) ? config.plugin : [];
|
|
190
193
|
const found = plugins.some(
|
|
191
|
-
(p) => (typeof p === "string" ? p : p[0]) === PLUGIN_NAME
|
|
194
|
+
(p) => p && (typeof p === "string" ? p : Array.isArray(p) ? p[0] : null) === PLUGIN_NAME
|
|
192
195
|
);
|
|
193
196
|
if (found) {
|
|
194
197
|
ok(`OpenCode plugin registered: ${configPath}`);
|
|
@@ -228,8 +231,8 @@ function removeGitHook() {
|
|
|
228
231
|
}
|
|
229
232
|
|
|
230
233
|
const content = fs.readFileSync(hookFile, "utf8");
|
|
231
|
-
if (!content.includes(
|
|
232
|
-
skip("commit-msg hook does not contain AI tracker snippet");
|
|
234
|
+
if (!content.includes(HOOK_BEGIN)) {
|
|
235
|
+
skip("commit-msg hook does not contain AI tracker CLI snippet");
|
|
233
236
|
return;
|
|
234
237
|
}
|
|
235
238
|
|
|
@@ -237,30 +240,29 @@ function removeGitHook() {
|
|
|
237
240
|
const filtered = [];
|
|
238
241
|
let skipping = false;
|
|
239
242
|
|
|
240
|
-
for (
|
|
241
|
-
if (
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
}
|
|
245
|
-
if (skipping) {
|
|
246
|
-
if (lines[i].includes('rm "$STATE_FILE"')) {
|
|
247
|
-
// consume the closing `fi` on the next non-empty line, then stop skipping
|
|
248
|
-
for (let j = i + 1; j < lines.length; j++) {
|
|
249
|
-
if (lines[j].trim() === "fi") { i = j; break; }
|
|
250
|
-
if (lines[j].trim() !== "") { i = j - 1; break; }
|
|
251
|
-
}
|
|
252
|
-
skipping = false;
|
|
253
|
-
continue;
|
|
254
|
-
}
|
|
255
|
-
continue;
|
|
256
|
-
}
|
|
257
|
-
filtered.push(lines[i]);
|
|
243
|
+
for (const line of lines) {
|
|
244
|
+
if (line.includes(HOOK_BEGIN)) { skipping = true; continue; }
|
|
245
|
+
if (line.includes(HOOK_END)) { skipping = false; continue; }
|
|
246
|
+
if (!skipping) filtered.push(line);
|
|
258
247
|
}
|
|
259
248
|
|
|
260
249
|
const remaining = filtered.join("\n").trim();
|
|
261
250
|
if (remaining === "#!/bin/sh" || remaining === "") {
|
|
262
251
|
fs.unlinkSync(hookFile);
|
|
263
252
|
ok(`Removed commit-msg hook: ${hookFile}`);
|
|
253
|
+
|
|
254
|
+
const ourDefaultDir = path.join(os.homedir(), ".config", "ai-contribution-tracker", "git-hooks");
|
|
255
|
+
const normalizedHooksPath = hooksPath.replace(/\\/g, "/");
|
|
256
|
+
const normalizedOurDir = ourDefaultDir.replace(/\\/g, "/");
|
|
257
|
+
if (normalizedHooksPath === normalizedOurDir) {
|
|
258
|
+
try {
|
|
259
|
+
execFileSync("git", ["config", "--global", "--unset", "core.hooksPath"], {
|
|
260
|
+
encoding: "utf8",
|
|
261
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
262
|
+
});
|
|
263
|
+
ok("Unset git global core.hooksPath");
|
|
264
|
+
} catch { }
|
|
265
|
+
}
|
|
264
266
|
} else {
|
|
265
267
|
fs.writeFileSync(hookFile, remaining + "\n");
|
|
266
268
|
ok(`Removed AI tracker snippet from: ${hookFile}`);
|
|
@@ -285,7 +287,7 @@ function removeOpenCodePlugin() {
|
|
|
285
287
|
|
|
286
288
|
const before = config.plugin.length;
|
|
287
289
|
config.plugin = config.plugin.filter(
|
|
288
|
-
(p) => (typeof p === "string" ? p : p[0]) !== PLUGIN_NAME
|
|
290
|
+
(p) => !p || (typeof p === "string" ? p : Array.isArray(p) ? p[0] : null) !== PLUGIN_NAME
|
|
289
291
|
);
|
|
290
292
|
|
|
291
293
|
if (config.plugin.length === before) {
|
|
@@ -358,3 +360,4 @@ switch (command) {
|
|
|
358
360
|
printUsage();
|
|
359
361
|
process.exit(command ? 1 : 0);
|
|
360
362
|
}
|
|
363
|
+
|
package/index.ts
CHANGED
|
@@ -278,7 +278,7 @@ const AIContributionTracker: Plugin = async ({ directory, worktree }) => {
|
|
|
278
278
|
if (sess.isSubagent) return;
|
|
279
279
|
|
|
280
280
|
// Resolve gitDir from file path — this is the ONLY place we resolve
|
|
281
|
-
if (!sess.gitDir) {
|
|
281
|
+
if (!sess.gitDir || !fs.existsSync(sess.gitDir)) {
|
|
282
282
|
const args = input.args as Record<string, unknown> ?? {};
|
|
283
283
|
const fp = typeof args.filePath === "string" ? args.filePath : typeof args.path === "string" ? args.path : undefined;
|
|
284
284
|
if (fp) {
|
|
@@ -306,3 +306,4 @@ const AIContributionTracker: Plugin = async ({ directory, worktree }) => {
|
|
|
306
306
|
};
|
|
307
307
|
export default AIContributionTracker;
|
|
308
308
|
// Named exports omitted — OpenCode calls all exported functions as plugins
|
|
309
|
+
|
package/package.json
CHANGED