@rachel_rotenberg/ai-contribution-tracker 1.0.16 → 1.0.18

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.
Files changed (2) hide show
  1. package/index.ts +33 -29
  2. package/package.json +1 -1
package/index.ts CHANGED
@@ -60,7 +60,14 @@ function loadState(g: string): TrackerState {
60
60
  }
61
61
  function saveState(g: string, s: TrackerState) { s.lastUpdated = new Date().toISOString(); fs.writeFileSync(getStatePath(g), JSON.stringify(s, null, 2)); }
62
62
  function formatK(n: number) { return n >= 1000 ? `${Math.round(n / 1000)}k` : String(n); }
63
- function buildMarkerParts(s: TrackerState): string[] {
63
+ function buildCoAuthoredBy(s: TrackerState): string {
64
+ const models = s.models.join(", ");
65
+ const coAuthorName = models || s.promptCount > 0
66
+ ? `OpenCode (${[models, s.promptCount > 0 ? `${s.promptCount}p` : ""].filter(Boolean).join(", ")})`
67
+ : "OpenCode";
68
+ return `Co-authored-by: ${coAuthorName} <noreply@opencode.dev>`;
69
+ }
70
+ function formatMarker(s: TrackerState): string {
64
71
  const p: string[] = [];
65
72
  const ma = [...new Set(s.mainAgentTypes)]; if (ma.length) p.push(`Agent mode: ${ma.join(", ")}`);
66
73
  if (s.models.length) p.push(`Model: ${s.models.join(", ")}`);
@@ -74,26 +81,25 @@ function buildMarkerParts(s: TrackerState): string[] {
74
81
  if (t.reasoningTokens > 0) r += ` +${formatK(t.reasoningTokens)} reasoning`;
75
82
  return r;
76
83
  }).join(" | ")}`); }
77
- return p;
78
- }
79
- function buildCoAuthoredBy(s: TrackerState): string {
80
- const models = s.models.join(", ");
81
- const coAuthorName = models || s.promptCount > 0
82
- ? `OpenCode (${[models, s.promptCount > 0 ? `${s.promptCount}p` : ""].filter(Boolean).join(", ")})`
83
- : "OpenCode";
84
- return `Co-authored-by: ${coAuthorName} <noreply@opencode.dev>`;
85
- }
86
- function formatMarker(s: TrackerState, prefix?: string): string {
87
- const parts = buildMarkerParts(s);
88
- if (prefix) parts.unshift(prefix);
89
- const marker = parts.length ? `Impacted by AI (${parts.join(" | ")})` : "Impacted by AI";
84
+ const marker = p.length ? `Impacted by AI (${p.join(" | ")})` : "Impacted by AI";
90
85
  return `${marker}\n${buildCoAuthoredBy(s)}`;
91
86
  }
92
87
  function writeFlag(g: string, s: TrackerState) {
93
88
  if (s.promptCount === 0 && s.mainAgentTypes.length === 0 && s.subagentTypes.length === 0 && s.subagentCount === 0 && Object.keys(s.tokensByModel).length === 0) return;
94
- const fp = getFlagPath(g);
95
- const isInline = fs.existsSync(fp) && fs.readFileSync(fp, "utf8").includes("Inline");
96
- fs.writeFileSync(fp, formatMarker(s, isInline ? "Inline" : undefined));
89
+ const fp = getFlagPath(g), marker = formatMarker(s);
90
+ if (fs.existsSync(fp)) {
91
+ const ex = fs.readFileSync(fp, "utf8").trim();
92
+ if (ex.includes("Inline")) {
93
+ // Merge: preserve Inline marker, add agent data
94
+ const inner = marker.split('\n')[0].match(/\((.+)\)$/)?.[1];
95
+ const coAuthorLine = marker.split('\n').slice(1).join('\n');
96
+ const merged = inner ? `Impacted by AI (Inline + ${inner})` : "Impacted by AI (Inline)";
97
+ fs.writeFileSync(fp, coAuthorLine ? `${merged}\n${coAuthorLine}` : merged);
98
+ return;
99
+ }
100
+ // Always overwrite with latest state — state only grows, never shrinks
101
+ }
102
+ fs.writeFileSync(fp, marker);
97
103
  }
98
104
 
99
105
  // ─── Plugin ─────────────────────────────────────────────────
@@ -109,7 +115,7 @@ function appendOrCreateHook(hooksDir: string) {
109
115
  'if [ -f "$IMPACT_FLAG" ]; then',
110
116
  ' MARKER=$(cat "$IMPACT_FLAG")',
111
117
  ' if [ -z "$MARKER" ]; then MARKER="Impacted by AI"; fi',
112
- ' if ! grep -qF "Impacted by AI" "$1"; then',
118
+ ' if ! grep -qF "$MARKER" "$1"; then',
113
119
  ' echo "" >> "$1"',
114
120
  ' echo "$MARKER" >> "$1"',
115
121
  ' fi',
@@ -155,15 +161,11 @@ function extractSessionId(event: any): string | null {
155
161
  return i.sessionID || i.session_id || i.id || null;
156
162
  }
157
163
 
158
- function sanitizeAgentName(name: string): string {
159
- return name.trim();
160
- }
161
-
162
164
  function getOrCreateSession(sid: string, agent?: string): SessionState {
163
165
  let sess = sessions.get(sid);
164
166
  if (sess) return sess;
165
167
  // gitDir starts null — resolved ONLY from file paths in tool.execute.after
166
- sess = { gitDir: null, isSubagent: false, agentName: sanitizeAgentName(agent ?? "session"), pendingPrompts: 0, pendingModels: [], lastTokens: new Map() };
168
+ sess = { gitDir: null, isSubagent: false, agentName: agent ?? "session", pendingPrompts: 0, pendingModels: [], lastTokens: new Map() };
167
169
  sessions.set(sid, sess);
168
170
  return sess;
169
171
  }
@@ -199,7 +201,7 @@ const AIContributionTracker: Plugin = async ({ directory, worktree }) => {
199
201
  sessions.set(sid, {
200
202
  gitDir: null, // NEVER resolve from cwd — wait for file paths
201
203
  isSubagent: Boolean(info?.parentID),
202
- agentName: sanitizeAgentName(info?.agent ?? "session"),
204
+ agentName: info?.agent ?? "session",
203
205
  pendingPrompts: 0, pendingModels: [],
204
206
  lastTokens: new Map(),
205
207
  });
@@ -224,8 +226,11 @@ const AIContributionTracker: Plugin = async ({ directory, worktree }) => {
224
226
  if (info.role !== "assistant" || !info.finish || !info.tokens) return;
225
227
  const modelId = info.modelID ?? "unknown";
226
228
  const msgId = info.id || info.messageID;
227
- const cur: TokenTotals = { inputTokens: Number(info.tokens.input ?? 0), outputTokens: Number(info.tokens.output ?? 0),
228
- cachedTokens: Number(info.tokens.cache?.read ?? 0), reasoningTokens: Number(info.tokens.reasoning ?? 0) };
229
+ const rawInput = Number(info.tokens.input ?? 0);
230
+ const cacheRead = Number(info.tokens.cache?.read ?? 0);
231
+ const totalInput = Number(info.tokens.total ?? 0) || (rawInput + cacheRead);
232
+ const cur: TokenTotals = { inputTokens: totalInput, outputTokens: Number(info.tokens.output ?? 0),
233
+ cachedTokens: cacheRead, reasoningTokens: Number(info.tokens.reasoning ?? 0) };
229
234
  const prev = msgId ? sess.lastTokens.get(msgId) : undefined;
230
235
  // Clamp deltas to >= 0 (snapshots can decrease; we never subtract)
231
236
  const delta: TokenTotals = {
@@ -253,7 +258,7 @@ const AIContributionTracker: Plugin = async ({ directory, worktree }) => {
253
258
  if (sess.gitDir) {
254
259
  // gitDir known — write directly
255
260
  const state = loadState(sess.gitDir);
256
- const src = `opencode/${sanitizeAgentName(input.agent ?? "session")}`;
261
+ const src = `opencode/${input.agent ?? "session"}`;
257
262
  if (!state.mainAgentTypes.includes(src)) state.mainAgentTypes.push(src);
258
263
  state.promptCount += 1;
259
264
  if (input.model?.modelID && !state.models.includes(input.model.modelID)) state.models.push(input.model.modelID);
@@ -262,7 +267,7 @@ const AIContributionTracker: Plugin = async ({ directory, worktree }) => {
262
267
  } else {
263
268
  // gitDir NOT known yet — buffer in memory
264
269
  sess.pendingPrompts += 1;
265
- if (input.agent) sess.agentName = sanitizeAgentName(input.agent);
270
+ if (input.agent) sess.agentName = input.agent;
266
271
  if (input.model?.modelID && !sess.pendingModels.includes(input.model.modelID)) sess.pendingModels.push(input.model.modelID);
267
272
  }
268
273
  } catch { /* never crash OpenCode */ }
@@ -293,7 +298,6 @@ const AIContributionTracker: Plugin = async ({ directory, worktree }) => {
293
298
  state.subagentCount += 1;
294
299
  if (!state.subagentTypes.includes(agentType)) state.subagentTypes.push(agentType);
295
300
  saveState(sess.gitDir, state);
296
- writeFlag(sess.gitDir, state);
297
301
  writeFlag(sess.gitDir, state); // keep flag current for commit-before-idle
298
302
  }
299
303
  } catch { /* never crash OpenCode */ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rachel_rotenberg/ai-contribution-tracker",
3
- "version": "1.0.16",
3
+ "version": "1.0.18",
4
4
  "description": "OpenCode plugin \u2014 tracks AI coding sessions and tags git commits with Impacted by AI markers",
5
5
  "main": "index.ts",
6
6
  "types": "index.ts",