@rachel_rotenberg/ai-contribution-tracker 1.0.20 → 1.0.22
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 +27 -30
- package/index.ts +1 -0
- package/package.json +1 -1
package/cli.js
CHANGED
|
@@ -19,7 +19,8 @@ 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,14 +56,22 @@ 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
|
}
|
|
60
63
|
fs.appendFileSync(hookPath, "\n" + HOOK_BODY + "\n");
|
|
61
64
|
ok(`Appended AI tracker snippet to existing hook: ${hookPath}`);
|
|
62
65
|
} else {
|
|
63
|
-
const
|
|
66
|
+
const delegation = [
|
|
67
|
+
"#!/bin/sh",
|
|
68
|
+
'LOCAL_HOOK="$(git rev-parse --git-dir)/hooks/commit-msg"',
|
|
69
|
+
'if [ -f "$LOCAL_HOOK" ] && [ -x "$LOCAL_HOOK" ]; then',
|
|
70
|
+
' "$LOCAL_HOOK" "$@" || exit $?',
|
|
71
|
+
'fi',
|
|
72
|
+
"",
|
|
73
|
+
].join("\n");
|
|
74
|
+
const content = (delegation + HOOK_BODY + "\n").replace(/\r\n/g, "\n");
|
|
64
75
|
fs.writeFileSync(hookPath, content);
|
|
65
76
|
ok(`Created commit-msg hook: ${hookPath}`);
|
|
66
77
|
}
|
|
@@ -133,7 +144,7 @@ function addPluginToConfig(configDir) {
|
|
|
133
144
|
|
|
134
145
|
// Check if already registered (plain string or [name, options] tuple)
|
|
135
146
|
const alreadyRegistered = plugins.some(
|
|
136
|
-
(p) => (typeof p === "string" ? p : p[0]) === PLUGIN_NAME
|
|
147
|
+
(p) => p && (typeof p === "string" ? p : Array.isArray(p) ? p[0] : null) === PLUGIN_NAME
|
|
137
148
|
);
|
|
138
149
|
|
|
139
150
|
if (alreadyRegistered) {
|
|
@@ -172,7 +183,7 @@ function showStatus() {
|
|
|
172
183
|
|
|
173
184
|
if (hooksPath) {
|
|
174
185
|
const hookFile = path.join(hooksPath, "commit-msg");
|
|
175
|
-
if (fs.existsSync(hookFile) && fs.readFileSync(hookFile, "utf8").includes(
|
|
186
|
+
if (fs.existsSync(hookFile) && fs.readFileSync(hookFile, "utf8").includes(HOOK_BEGIN)) {
|
|
176
187
|
ok(`Git hook installed: ${hookFile}`);
|
|
177
188
|
} else {
|
|
178
189
|
warn(`core.hooksPath set to ${hooksPath} but no AI tracker snippet found`);
|
|
@@ -188,7 +199,7 @@ function showStatus() {
|
|
|
188
199
|
const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
189
200
|
const plugins = Array.isArray(config.plugin) ? config.plugin : [];
|
|
190
201
|
const found = plugins.some(
|
|
191
|
-
(p) => (typeof p === "string" ? p : p[0]) === PLUGIN_NAME
|
|
202
|
+
(p) => p && (typeof p === "string" ? p : Array.isArray(p) ? p[0] : null) === PLUGIN_NAME
|
|
192
203
|
);
|
|
193
204
|
if (found) {
|
|
194
205
|
ok(`OpenCode plugin registered: ${configPath}`);
|
|
@@ -228,8 +239,8 @@ function removeGitHook() {
|
|
|
228
239
|
}
|
|
229
240
|
|
|
230
241
|
const content = fs.readFileSync(hookFile, "utf8");
|
|
231
|
-
if (!content.includes(
|
|
232
|
-
skip("commit-msg hook does not contain AI tracker snippet");
|
|
242
|
+
if (!content.includes(HOOK_BEGIN)) {
|
|
243
|
+
skip("commit-msg hook does not contain AI tracker CLI snippet");
|
|
233
244
|
return;
|
|
234
245
|
}
|
|
235
246
|
|
|
@@ -237,24 +248,10 @@ function removeGitHook() {
|
|
|
237
248
|
const filtered = [];
|
|
238
249
|
let skipping = false;
|
|
239
250
|
|
|
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]);
|
|
251
|
+
for (const line of lines) {
|
|
252
|
+
if (line.includes(HOOK_BEGIN)) { skipping = true; continue; }
|
|
253
|
+
if (line.includes(HOOK_END)) { skipping = false; continue; }
|
|
254
|
+
if (!skipping) filtered.push(line);
|
|
258
255
|
}
|
|
259
256
|
|
|
260
257
|
const remaining = filtered.join("\n").trim();
|
|
@@ -298,7 +295,7 @@ function removeOpenCodePlugin() {
|
|
|
298
295
|
|
|
299
296
|
const before = config.plugin.length;
|
|
300
297
|
config.plugin = config.plugin.filter(
|
|
301
|
-
(p) => (typeof p === "string" ? p : p[0]) !== PLUGIN_NAME
|
|
298
|
+
(p) => !p || (typeof p === "string" ? p : Array.isArray(p) ? p[0] : null) !== PLUGIN_NAME
|
|
302
299
|
);
|
|
303
300
|
|
|
304
301
|
if (config.plugin.length === before) {
|
package/index.ts
CHANGED
package/package.json
CHANGED