@rachel_rotenberg/ai-contribution-tracker 1.0.15 → 1.0.17
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 +26 -30
- package/package.json +1 -1
package/index.ts
CHANGED
|
@@ -60,7 +60,7 @@ 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 formatMarker(s: TrackerState): string {
|
|
64
64
|
const p: string[] = [];
|
|
65
65
|
const ma = [...new Set(s.mainAgentTypes)]; if (ma.length) p.push(`Agent mode: ${ma.join(", ")}`);
|
|
66
66
|
if (s.models.length) p.push(`Model: ${s.models.join(", ")}`);
|
|
@@ -74,26 +74,24 @@ function buildMarkerParts(s: TrackerState): string[] {
|
|
|
74
74
|
if (t.reasoningTokens > 0) r += ` +${formatK(t.reasoningTokens)} reasoning`;
|
|
75
75
|
return r;
|
|
76
76
|
}).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";
|
|
90
|
-
return `${marker}\n${buildCoAuthoredBy(s)}`;
|
|
77
|
+
return p.length ? `Impacted by AI (${p.join(" | ")})` : "Impacted by AI";
|
|
91
78
|
}
|
|
92
79
|
function writeFlag(g: string, s: TrackerState) {
|
|
93
80
|
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
|
-
|
|
81
|
+
const fp = getFlagPath(g), marker = formatMarker(s);
|
|
82
|
+
if (fs.existsSync(fp)) {
|
|
83
|
+
const ex = fs.readFileSync(fp, "utf8").trim();
|
|
84
|
+
if (ex.includes("Inline")) {
|
|
85
|
+
// Merge: preserve Inline marker, add agent data
|
|
86
|
+
const inner = marker.split('\n')[0].match(/\((.+)\)$/)?.[1];
|
|
87
|
+
const coAuthorLine = marker.split('\n').slice(1).join('\n');
|
|
88
|
+
const merged = inner ? `Impacted by AI (Inline + ${inner})` : "Impacted by AI (Inline)";
|
|
89
|
+
fs.writeFileSync(fp, coAuthorLine ? `${merged}\n${coAuthorLine}` : merged);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
// Always overwrite with latest state — state only grows, never shrinks
|
|
93
|
+
}
|
|
94
|
+
fs.writeFileSync(fp, marker);
|
|
97
95
|
}
|
|
98
96
|
|
|
99
97
|
// ─── Plugin ─────────────────────────────────────────────────
|
|
@@ -109,7 +107,7 @@ function appendOrCreateHook(hooksDir: string) {
|
|
|
109
107
|
'if [ -f "$IMPACT_FLAG" ]; then',
|
|
110
108
|
' MARKER=$(cat "$IMPACT_FLAG")',
|
|
111
109
|
' if [ -z "$MARKER" ]; then MARKER="Impacted by AI"; fi',
|
|
112
|
-
' if ! grep -qF "
|
|
110
|
+
' if ! grep -qF "$MARKER" "$1"; then',
|
|
113
111
|
' echo "" >> "$1"',
|
|
114
112
|
' echo "$MARKER" >> "$1"',
|
|
115
113
|
' fi',
|
|
@@ -155,15 +153,11 @@ function extractSessionId(event: any): string | null {
|
|
|
155
153
|
return i.sessionID || i.session_id || i.id || null;
|
|
156
154
|
}
|
|
157
155
|
|
|
158
|
-
function sanitizeAgentName(name: string): string {
|
|
159
|
-
return name.trim();
|
|
160
|
-
}
|
|
161
|
-
|
|
162
156
|
function getOrCreateSession(sid: string, agent?: string): SessionState {
|
|
163
157
|
let sess = sessions.get(sid);
|
|
164
158
|
if (sess) return sess;
|
|
165
159
|
// gitDir starts null — resolved ONLY from file paths in tool.execute.after
|
|
166
|
-
sess = { gitDir: null, isSubagent: false, agentName:
|
|
160
|
+
sess = { gitDir: null, isSubagent: false, agentName: agent ?? "session", pendingPrompts: 0, pendingModels: [], lastTokens: new Map() };
|
|
167
161
|
sessions.set(sid, sess);
|
|
168
162
|
return sess;
|
|
169
163
|
}
|
|
@@ -199,7 +193,7 @@ const AIContributionTracker: Plugin = async ({ directory, worktree }) => {
|
|
|
199
193
|
sessions.set(sid, {
|
|
200
194
|
gitDir: null, // NEVER resolve from cwd — wait for file paths
|
|
201
195
|
isSubagent: Boolean(info?.parentID),
|
|
202
|
-
agentName:
|
|
196
|
+
agentName: info?.agent ?? "session",
|
|
203
197
|
pendingPrompts: 0, pendingModels: [],
|
|
204
198
|
lastTokens: new Map(),
|
|
205
199
|
});
|
|
@@ -224,8 +218,11 @@ const AIContributionTracker: Plugin = async ({ directory, worktree }) => {
|
|
|
224
218
|
if (info.role !== "assistant" || !info.finish || !info.tokens) return;
|
|
225
219
|
const modelId = info.modelID ?? "unknown";
|
|
226
220
|
const msgId = info.id || info.messageID;
|
|
227
|
-
const
|
|
228
|
-
|
|
221
|
+
const rawInput = Number(info.tokens.input ?? 0);
|
|
222
|
+
const cacheRead = Number(info.tokens.cache?.read ?? 0);
|
|
223
|
+
const totalInput = Number(info.tokens.total ?? 0) || (rawInput + cacheRead);
|
|
224
|
+
const cur: TokenTotals = { inputTokens: totalInput, outputTokens: Number(info.tokens.output ?? 0),
|
|
225
|
+
cachedTokens: cacheRead, reasoningTokens: Number(info.tokens.reasoning ?? 0) };
|
|
229
226
|
const prev = msgId ? sess.lastTokens.get(msgId) : undefined;
|
|
230
227
|
// Clamp deltas to >= 0 (snapshots can decrease; we never subtract)
|
|
231
228
|
const delta: TokenTotals = {
|
|
@@ -253,7 +250,7 @@ const AIContributionTracker: Plugin = async ({ directory, worktree }) => {
|
|
|
253
250
|
if (sess.gitDir) {
|
|
254
251
|
// gitDir known — write directly
|
|
255
252
|
const state = loadState(sess.gitDir);
|
|
256
|
-
const src = `opencode/${
|
|
253
|
+
const src = `opencode/${input.agent ?? "session"}`;
|
|
257
254
|
if (!state.mainAgentTypes.includes(src)) state.mainAgentTypes.push(src);
|
|
258
255
|
state.promptCount += 1;
|
|
259
256
|
if (input.model?.modelID && !state.models.includes(input.model.modelID)) state.models.push(input.model.modelID);
|
|
@@ -262,7 +259,7 @@ const AIContributionTracker: Plugin = async ({ directory, worktree }) => {
|
|
|
262
259
|
} else {
|
|
263
260
|
// gitDir NOT known yet — buffer in memory
|
|
264
261
|
sess.pendingPrompts += 1;
|
|
265
|
-
if (input.agent) sess.agentName =
|
|
262
|
+
if (input.agent) sess.agentName = input.agent;
|
|
266
263
|
if (input.model?.modelID && !sess.pendingModels.includes(input.model.modelID)) sess.pendingModels.push(input.model.modelID);
|
|
267
264
|
}
|
|
268
265
|
} catch { /* never crash OpenCode */ }
|
|
@@ -293,7 +290,6 @@ const AIContributionTracker: Plugin = async ({ directory, worktree }) => {
|
|
|
293
290
|
state.subagentCount += 1;
|
|
294
291
|
if (!state.subagentTypes.includes(agentType)) state.subagentTypes.push(agentType);
|
|
295
292
|
saveState(sess.gitDir, state);
|
|
296
|
-
writeFlag(sess.gitDir, state);
|
|
297
293
|
writeFlag(sess.gitDir, state); // keep flag current for commit-before-idle
|
|
298
294
|
}
|
|
299
295
|
} catch { /* never crash OpenCode */ }
|
package/package.json
CHANGED