@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.
- package/index.ts +33 -29
- 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
|
|
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
|
-
|
|
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
|
-
|
|
96
|
-
|
|
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 "
|
|
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:
|
|
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:
|
|
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
|
|
228
|
-
|
|
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/${
|
|
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 =
|
|
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