@wasabeef/agentnote 0.1.4 → 0.1.6
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/dist/cli.js +628 -513
- package/package.json +9 -4
package/dist/cli.js
CHANGED
|
@@ -1,82 +1,48 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
// src/commands/
|
|
4
|
-
import {
|
|
5
|
-
import { existsSync as
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
// src/paths.ts
|
|
9
|
-
import { join } from "node:path";
|
|
3
|
+
// src/commands/commit.ts
|
|
4
|
+
import { spawn } from "node:child_process";
|
|
5
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
6
|
+
import { readFile as readFile4 } from "node:fs/promises";
|
|
10
7
|
|
|
11
|
-
// src/
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
var
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
8
|
+
// src/core/constants.ts
|
|
9
|
+
var TRAILER_KEY = "Agentnote-Session";
|
|
10
|
+
var NOTES_REF = "agentnote";
|
|
11
|
+
var NOTES_REF_FULL = `refs/notes/${NOTES_REF}`;
|
|
12
|
+
var NOTES_FETCH_REFSPEC = `+${NOTES_REF_FULL}:${NOTES_REF_FULL}`;
|
|
13
|
+
var AGENTNOTE_DIR = "agentnote";
|
|
14
|
+
var SESSIONS_DIR = "sessions";
|
|
15
|
+
var PROMPTS_FILE = "prompts.jsonl";
|
|
16
|
+
var CHANGES_FILE = "changes.jsonl";
|
|
17
|
+
var EVENTS_FILE = "events.jsonl";
|
|
18
|
+
var TRANSCRIPT_PATH_FILE = "transcript_path";
|
|
19
|
+
var TURN_FILE = "turn";
|
|
20
|
+
var SESSION_FILE = "session";
|
|
21
|
+
var MAX_COMMITS = 500;
|
|
22
|
+
var BAR_WIDTH_COMPACT = 5;
|
|
23
|
+
var BAR_WIDTH_FULL = 20;
|
|
24
|
+
var TRUNCATE_PROMPT = 120;
|
|
25
|
+
var TRUNCATE_RESPONSE_SHOW = 200;
|
|
26
|
+
var TRUNCATE_RESPONSE_PR = 500;
|
|
27
|
+
var TRUNCATE_RESPONSE_CHAT = 800;
|
|
28
|
+
var SCHEMA_VERSION = 1;
|
|
29
|
+
var DEBUG = !!process.env.AGENTNOTE_DEBUG;
|
|
33
30
|
|
|
34
|
-
// src/
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if (!_root) {
|
|
39
|
-
try {
|
|
40
|
-
_root = await repoRoot();
|
|
41
|
-
} catch {
|
|
42
|
-
console.error("error: git repository not found");
|
|
43
|
-
process.exit(1);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
return _root;
|
|
47
|
-
}
|
|
48
|
-
async function gitDir() {
|
|
49
|
-
if (!_gitDir) {
|
|
50
|
-
_gitDir = await git(["rev-parse", "--git-dir"]);
|
|
51
|
-
if (!_gitDir.startsWith("/")) {
|
|
52
|
-
_gitDir = join(await root(), _gitDir);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
return _gitDir;
|
|
56
|
-
}
|
|
57
|
-
async function agentnoteDir() {
|
|
58
|
-
return join(await gitDir(), "agentnote");
|
|
59
|
-
}
|
|
60
|
-
async function sessionFile() {
|
|
61
|
-
return join(await agentnoteDir(), "session");
|
|
62
|
-
}
|
|
31
|
+
// src/core/record.ts
|
|
32
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
33
|
+
import { readdir, readFile as readFile3 } from "node:fs/promises";
|
|
34
|
+
import { join as join2 } from "node:path";
|
|
63
35
|
|
|
64
36
|
// src/agents/claude-code.ts
|
|
65
|
-
import { readFile, writeFile, mkdir } from "node:fs/promises";
|
|
66
37
|
import { existsSync, readdirSync } from "node:fs";
|
|
67
|
-
import {
|
|
38
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
68
39
|
import { homedir } from "node:os";
|
|
40
|
+
import { join } from "node:path";
|
|
69
41
|
var HOOK_COMMAND = "npx --yes @wasabeef/agentnote hook";
|
|
70
42
|
var HOOKS_CONFIG = {
|
|
71
|
-
SessionStart: [
|
|
72
|
-
|
|
73
|
-
],
|
|
74
|
-
Stop: [
|
|
75
|
-
{ hooks: [{ type: "command", command: HOOK_COMMAND, async: true }] }
|
|
76
|
-
],
|
|
77
|
-
UserPromptSubmit: [
|
|
78
|
-
{ hooks: [{ type: "command", command: HOOK_COMMAND, async: true }] }
|
|
79
|
-
],
|
|
43
|
+
SessionStart: [{ hooks: [{ type: "command", command: HOOK_COMMAND, async: true }] }],
|
|
44
|
+
Stop: [{ hooks: [{ type: "command", command: HOOK_COMMAND, async: true }] }],
|
|
45
|
+
UserPromptSubmit: [{ hooks: [{ type: "command", command: HOOK_COMMAND, async: true }] }],
|
|
80
46
|
PreToolUse: [
|
|
81
47
|
{
|
|
82
48
|
matcher: "Bash",
|
|
@@ -95,18 +61,17 @@ function isValidSessionId(id) {
|
|
|
95
61
|
return UUID_PATTERN.test(id);
|
|
96
62
|
}
|
|
97
63
|
function isValidTranscriptPath(p) {
|
|
98
|
-
const claudeBase =
|
|
64
|
+
const claudeBase = join(homedir(), ".claude");
|
|
99
65
|
return p.startsWith(claudeBase);
|
|
100
66
|
}
|
|
101
67
|
function isGitCommit(cmd) {
|
|
102
|
-
|
|
103
|
-
return (trimmed.startsWith("git commit") || trimmed.startsWith("git -c ")) && trimmed.includes("commit") && !trimmed.includes("--amend");
|
|
68
|
+
return cmd.includes("git commit") && !cmd.includes("--amend");
|
|
104
69
|
}
|
|
105
70
|
var claudeCode = {
|
|
106
71
|
name: "claude-code",
|
|
107
72
|
settingsRelPath: ".claude/settings.json",
|
|
108
73
|
async installHooks(repoRoot2) {
|
|
109
|
-
const settingsPath =
|
|
74
|
+
const settingsPath = join(repoRoot2, this.settingsRelPath);
|
|
110
75
|
const { dirname } = await import("node:path");
|
|
111
76
|
await mkdir(dirname(settingsPath), { recursive: true });
|
|
112
77
|
let settings = {};
|
|
@@ -119,15 +84,17 @@ var claudeCode = {
|
|
|
119
84
|
}
|
|
120
85
|
const hooks = settings.hooks ?? {};
|
|
121
86
|
const raw = JSON.stringify(hooks);
|
|
122
|
-
if (raw.includes("@wasabeef/agentnote"))
|
|
87
|
+
if (raw.includes("@wasabeef/agentnote") || raw.includes("agentnote hook") || raw.includes("cli.js hook"))
|
|
88
|
+
return;
|
|
123
89
|
for (const [event, entries] of Object.entries(HOOKS_CONFIG)) {
|
|
124
90
|
hooks[event] = [...hooks[event] ?? [], ...entries];
|
|
125
91
|
}
|
|
126
92
|
settings.hooks = hooks;
|
|
127
|
-
await writeFile(settingsPath, JSON.stringify(settings, null, 2)
|
|
93
|
+
await writeFile(settingsPath, `${JSON.stringify(settings, null, 2)}
|
|
94
|
+
`);
|
|
128
95
|
},
|
|
129
96
|
async removeHooks(repoRoot2) {
|
|
130
|
-
const settingsPath =
|
|
97
|
+
const settingsPath = join(repoRoot2, this.settingsRelPath);
|
|
131
98
|
if (!existsSync(settingsPath)) return;
|
|
132
99
|
try {
|
|
133
100
|
const settings = JSON.parse(await readFile(settingsPath, "utf-8"));
|
|
@@ -135,21 +102,22 @@ var claudeCode = {
|
|
|
135
102
|
for (const [event, entries] of Object.entries(settings.hooks)) {
|
|
136
103
|
settings.hooks[event] = entries.filter((e) => {
|
|
137
104
|
const text = JSON.stringify(e);
|
|
138
|
-
return !text.includes("@wasabeef/agentnote");
|
|
105
|
+
return !text.includes("@wasabeef/agentnote") && !text.includes("agentnote hook") && !text.includes("cli.js hook");
|
|
139
106
|
});
|
|
140
107
|
if (settings.hooks[event].length === 0) delete settings.hooks[event];
|
|
141
108
|
}
|
|
142
109
|
if (Object.keys(settings.hooks).length === 0) delete settings.hooks;
|
|
143
|
-
await writeFile(settingsPath, JSON.stringify(settings, null, 2)
|
|
110
|
+
await writeFile(settingsPath, `${JSON.stringify(settings, null, 2)}
|
|
111
|
+
`);
|
|
144
112
|
} catch {
|
|
145
113
|
}
|
|
146
114
|
},
|
|
147
115
|
async isEnabled(repoRoot2) {
|
|
148
|
-
const settingsPath =
|
|
116
|
+
const settingsPath = join(repoRoot2, this.settingsRelPath);
|
|
149
117
|
if (!existsSync(settingsPath)) return false;
|
|
150
118
|
try {
|
|
151
119
|
const content = await readFile(settingsPath, "utf-8");
|
|
152
|
-
return content.includes("@wasabeef/agentnote");
|
|
120
|
+
return content.includes("@wasabeef/agentnote") || content.includes("agentnote hook") || content.includes("cli.js hook");
|
|
153
121
|
} catch {
|
|
154
122
|
return false;
|
|
155
123
|
}
|
|
@@ -167,7 +135,13 @@ var claudeCode = {
|
|
|
167
135
|
const tp = e.transcript_path && isValidTranscriptPath(e.transcript_path) ? e.transcript_path : void 0;
|
|
168
136
|
switch (e.hook_event_name) {
|
|
169
137
|
case "SessionStart":
|
|
170
|
-
return {
|
|
138
|
+
return {
|
|
139
|
+
kind: "session_start",
|
|
140
|
+
sessionId: sid,
|
|
141
|
+
timestamp: ts,
|
|
142
|
+
model: e.model,
|
|
143
|
+
transcriptPath: tp
|
|
144
|
+
};
|
|
171
145
|
case "Stop":
|
|
172
146
|
return { kind: "stop", sessionId: sid, timestamp: ts, transcriptPath: tp };
|
|
173
147
|
case "UserPromptSubmit":
|
|
@@ -182,7 +156,13 @@ var claudeCode = {
|
|
|
182
156
|
case "PostToolUse": {
|
|
183
157
|
const tool = e.tool_name;
|
|
184
158
|
if ((tool === "Edit" || tool === "Write" || tool === "NotebookEdit") && e.tool_input?.file_path) {
|
|
185
|
-
return {
|
|
159
|
+
return {
|
|
160
|
+
kind: "file_change",
|
|
161
|
+
sessionId: sid,
|
|
162
|
+
timestamp: ts,
|
|
163
|
+
tool,
|
|
164
|
+
file: e.tool_input.file_path
|
|
165
|
+
};
|
|
186
166
|
}
|
|
187
167
|
if (tool === "Bash" && isGitCommit(e.tool_input?.command ?? "")) {
|
|
188
168
|
return { kind: "post_commit", sessionId: sid, timestamp: ts, transcriptPath: tp };
|
|
@@ -195,13 +175,13 @@ var claudeCode = {
|
|
|
195
175
|
},
|
|
196
176
|
findTranscript(sessionId) {
|
|
197
177
|
if (!isValidSessionId(sessionId)) return null;
|
|
198
|
-
const claudeDir =
|
|
178
|
+
const claudeDir = join(homedir(), ".claude", "projects");
|
|
199
179
|
if (!existsSync(claudeDir)) return null;
|
|
200
180
|
try {
|
|
201
181
|
for (const project of readdirSync(claudeDir)) {
|
|
202
|
-
const sessionsDir =
|
|
182
|
+
const sessionsDir = join(claudeDir, project, "sessions");
|
|
203
183
|
if (!existsSync(sessionsDir)) continue;
|
|
204
|
-
const candidate =
|
|
184
|
+
const candidate = join(sessionsDir, `${sessionId}.jsonl`);
|
|
205
185
|
if (existsSync(candidate) && isValidTranscriptPath(candidate)) {
|
|
206
186
|
return candidate;
|
|
207
187
|
}
|
|
@@ -253,141 +233,86 @@ var claudeCode = {
|
|
|
253
233
|
}
|
|
254
234
|
};
|
|
255
235
|
|
|
256
|
-
// src/
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
async function init(args2) {
|
|
278
|
-
const skipHooks = args2.includes("--no-hooks");
|
|
279
|
-
const skipAction = args2.includes("--no-action");
|
|
280
|
-
const skipNotes = args2.includes("--no-notes");
|
|
281
|
-
const hooksOnly = args2.includes("--hooks");
|
|
282
|
-
const actionOnly = args2.includes("--action");
|
|
283
|
-
const repoRoot2 = await root();
|
|
284
|
-
const results = [];
|
|
285
|
-
await mkdir2(await agentnoteDir(), { recursive: true });
|
|
286
|
-
if (!skipHooks && !actionOnly) {
|
|
287
|
-
const adapter = claudeCode;
|
|
288
|
-
if (await adapter.isEnabled(repoRoot2)) {
|
|
289
|
-
results.push(" \xB7 hooks already configured");
|
|
290
|
-
} else {
|
|
291
|
-
await adapter.installHooks(repoRoot2);
|
|
292
|
-
results.push(" \u2713 hooks added to .claude/settings.json");
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
if (!skipAction && !hooksOnly) {
|
|
296
|
-
const workflowDir = join3(repoRoot2, ".github", "workflows");
|
|
297
|
-
const workflowPath = join3(workflowDir, "agentnote.yml");
|
|
298
|
-
if (existsSync2(workflowPath)) {
|
|
299
|
-
results.push(" \xB7 workflow already exists at .github/workflows/agentnote.yml");
|
|
300
|
-
} else {
|
|
301
|
-
await mkdir2(workflowDir, { recursive: true });
|
|
302
|
-
await writeFile2(workflowPath, WORKFLOW_TEMPLATE);
|
|
303
|
-
results.push(" \u2713 workflow created at .github/workflows/agentnote.yml");
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
if (!skipNotes && !hooksOnly && !actionOnly) {
|
|
307
|
-
const { stdout } = await gitSafe([
|
|
308
|
-
"config",
|
|
309
|
-
"--get-all",
|
|
310
|
-
"remote.origin.fetch"
|
|
311
|
-
]);
|
|
312
|
-
if (stdout.includes("refs/notes/agentnote")) {
|
|
313
|
-
results.push(" \xB7 git already configured to fetch notes");
|
|
314
|
-
} else {
|
|
315
|
-
await gitSafe([
|
|
316
|
-
"config",
|
|
317
|
-
"--add",
|
|
318
|
-
"remote.origin.fetch",
|
|
319
|
-
"+refs/notes/agentnote:refs/notes/agentnote"
|
|
320
|
-
]);
|
|
321
|
-
results.push(" \u2713 git configured to auto-fetch notes on pull");
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
console.log("");
|
|
325
|
-
console.log("agentnote init");
|
|
326
|
-
console.log("");
|
|
327
|
-
for (const line of results) {
|
|
328
|
-
console.log(line);
|
|
329
|
-
}
|
|
330
|
-
const toCommit = [];
|
|
331
|
-
if (!skipHooks && !actionOnly) toCommit.push(".claude/settings.json");
|
|
332
|
-
if (!skipAction && !hooksOnly) {
|
|
333
|
-
const workflowPath = join3(repoRoot2, ".github", "workflows", "agentnote.yml");
|
|
334
|
-
if (existsSync2(workflowPath)) toCommit.push(".github/workflows/agentnote.yml");
|
|
335
|
-
}
|
|
336
|
-
if (toCommit.length > 0) {
|
|
337
|
-
console.log("");
|
|
338
|
-
console.log(" Next: commit and push these files");
|
|
339
|
-
console.log(` git add ${toCommit.join(" ")}`);
|
|
340
|
-
console.log(' git commit -m "chore: enable agentnote session tracking"');
|
|
341
|
-
console.log(" git push");
|
|
236
|
+
// src/git.ts
|
|
237
|
+
import { execFile } from "node:child_process";
|
|
238
|
+
import { promisify } from "node:util";
|
|
239
|
+
var execFileAsync = promisify(execFile);
|
|
240
|
+
async function git(args2, options) {
|
|
241
|
+
const { stdout } = await execFileAsync("git", args2, {
|
|
242
|
+
cwd: options?.cwd,
|
|
243
|
+
encoding: "utf-8"
|
|
244
|
+
});
|
|
245
|
+
return stdout.trim();
|
|
246
|
+
}
|
|
247
|
+
async function gitSafe(args2, options) {
|
|
248
|
+
try {
|
|
249
|
+
const stdout = await git(args2, options);
|
|
250
|
+
return { stdout, exitCode: 0 };
|
|
251
|
+
} catch (err) {
|
|
252
|
+
const e = err;
|
|
253
|
+
return {
|
|
254
|
+
stdout: typeof e.stdout === "string" ? e.stdout.trim() : "",
|
|
255
|
+
exitCode: typeof e.code === "number" ? e.code : 1
|
|
256
|
+
};
|
|
342
257
|
}
|
|
343
|
-
|
|
258
|
+
}
|
|
259
|
+
async function repoRoot() {
|
|
260
|
+
return git(["rev-parse", "--show-toplevel"]);
|
|
344
261
|
}
|
|
345
262
|
|
|
346
|
-
// src/
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
263
|
+
// src/core/entry.ts
|
|
264
|
+
function calcAiRatio(commitFiles, aiFiles) {
|
|
265
|
+
if (commitFiles.length === 0) return 0;
|
|
266
|
+
const aiSet = new Set(aiFiles);
|
|
267
|
+
const matched = commitFiles.filter((f) => aiSet.has(f));
|
|
268
|
+
return Math.round(matched.length / commitFiles.length * 100);
|
|
269
|
+
}
|
|
270
|
+
function buildEntry(opts) {
|
|
271
|
+
return {
|
|
272
|
+
v: SCHEMA_VERSION,
|
|
273
|
+
session_id: opts.sessionId,
|
|
274
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
275
|
+
interactions: opts.interactions.map((i) => {
|
|
276
|
+
const base = { prompt: i.prompt, response: i.response };
|
|
277
|
+
if (i.files_touched && i.files_touched.length > 0) {
|
|
278
|
+
base.files_touched = i.files_touched;
|
|
279
|
+
}
|
|
280
|
+
return base;
|
|
281
|
+
}),
|
|
282
|
+
files_in_commit: opts.commitFiles,
|
|
283
|
+
files_by_ai: opts.aiFiles,
|
|
284
|
+
ai_ratio: calcAiRatio(opts.commitFiles, opts.aiFiles)
|
|
285
|
+
};
|
|
286
|
+
}
|
|
351
287
|
|
|
352
288
|
// src/core/jsonl.ts
|
|
353
|
-
import {
|
|
354
|
-
import {
|
|
355
|
-
async function
|
|
356
|
-
if (!
|
|
289
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
290
|
+
import { appendFile, readFile as readFile2 } from "node:fs/promises";
|
|
291
|
+
async function readJsonlEntries(filePath) {
|
|
292
|
+
if (!existsSync2(filePath)) return [];
|
|
357
293
|
const content = await readFile2(filePath, "utf-8");
|
|
358
|
-
const
|
|
359
|
-
const values = [];
|
|
294
|
+
const entries = [];
|
|
360
295
|
for (const line of content.trim().split("\n")) {
|
|
361
296
|
if (!line) continue;
|
|
362
297
|
try {
|
|
363
|
-
|
|
364
|
-
const val = entry[field];
|
|
365
|
-
if (val && !seen.has(val)) {
|
|
366
|
-
seen.add(val);
|
|
367
|
-
values.push(val);
|
|
368
|
-
}
|
|
298
|
+
entries.push(JSON.parse(line));
|
|
369
299
|
} catch {
|
|
370
300
|
}
|
|
371
301
|
}
|
|
372
|
-
return
|
|
302
|
+
return entries;
|
|
373
303
|
}
|
|
374
304
|
async function appendJsonl(filePath, data) {
|
|
375
|
-
await appendFile(filePath, JSON.stringify(data)
|
|
305
|
+
await appendFile(filePath, `${JSON.stringify(data)}
|
|
306
|
+
`);
|
|
376
307
|
}
|
|
377
308
|
|
|
378
309
|
// src/core/storage.ts
|
|
379
|
-
var NOTES_REF = "agentnote";
|
|
380
310
|
async function writeNote(commitSha, data) {
|
|
381
311
|
const body = JSON.stringify(data, null, 2);
|
|
382
312
|
await gitSafe(["notes", `--ref=${NOTES_REF}`, "add", "-f", "-m", body, commitSha]);
|
|
383
313
|
}
|
|
384
314
|
async function readNote(commitSha) {
|
|
385
|
-
const { stdout, exitCode } = await gitSafe([
|
|
386
|
-
"notes",
|
|
387
|
-
`--ref=${NOTES_REF}`,
|
|
388
|
-
"show",
|
|
389
|
-
commitSha
|
|
390
|
-
]);
|
|
315
|
+
const { stdout, exitCode } = await gitSafe(["notes", `--ref=${NOTES_REF}`, "show", commitSha]);
|
|
391
316
|
if (exitCode !== 0 || !stdout.trim()) return null;
|
|
392
317
|
try {
|
|
393
318
|
return JSON.parse(stdout);
|
|
@@ -396,61 +321,144 @@ async function readNote(commitSha) {
|
|
|
396
321
|
}
|
|
397
322
|
}
|
|
398
323
|
|
|
399
|
-
// src/core/
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
324
|
+
// src/core/record.ts
|
|
325
|
+
async function recordCommitEntry(opts) {
|
|
326
|
+
const sessionDir = join2(opts.agentnoteDirPath, "sessions", opts.sessionId);
|
|
327
|
+
const commitSha = await git(["rev-parse", "HEAD"]);
|
|
328
|
+
let commitFiles = [];
|
|
329
|
+
try {
|
|
330
|
+
const raw = await git(["diff-tree", "--no-commit-id", "--name-only", "-r", "HEAD"]);
|
|
331
|
+
commitFiles = raw.split("\n").filter(Boolean);
|
|
332
|
+
} catch {
|
|
333
|
+
}
|
|
334
|
+
const commitFileSet = new Set(commitFiles);
|
|
335
|
+
const changeEntries = await readAllSessionJsonl(sessionDir, CHANGES_FILE);
|
|
336
|
+
const promptEntries = await readAllSessionJsonl(sessionDir, PROMPTS_FILE);
|
|
337
|
+
const hasTurnData = promptEntries.some((e) => typeof e.turn === "number" && e.turn > 0);
|
|
338
|
+
let aiFiles;
|
|
339
|
+
let prompts;
|
|
340
|
+
let relevantPromptEntries;
|
|
341
|
+
if (hasTurnData) {
|
|
342
|
+
aiFiles = [
|
|
343
|
+
...new Set(
|
|
344
|
+
changeEntries.map((e) => e.file).filter((f) => f && commitFileSet.has(f))
|
|
345
|
+
)
|
|
346
|
+
];
|
|
347
|
+
const relevantTurns = /* @__PURE__ */ new Set();
|
|
348
|
+
for (const entry2 of changeEntries) {
|
|
349
|
+
const file = entry2.file;
|
|
350
|
+
if (file && commitFileSet.has(file)) {
|
|
351
|
+
relevantTurns.add(typeof entry2.turn === "number" ? entry2.turn : 0);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
relevantPromptEntries = promptEntries.filter((e) => {
|
|
355
|
+
const turn = typeof e.turn === "number" ? e.turn : 0;
|
|
356
|
+
return relevantTurns.has(turn);
|
|
357
|
+
});
|
|
358
|
+
prompts = relevantPromptEntries.map((e) => e.prompt);
|
|
359
|
+
} else {
|
|
360
|
+
aiFiles = changeEntries.map((e) => e.file).filter(Boolean);
|
|
361
|
+
prompts = promptEntries.map((e) => e.prompt);
|
|
362
|
+
relevantPromptEntries = promptEntries;
|
|
363
|
+
}
|
|
364
|
+
const transcriptPath = opts.transcriptPath ?? await readSavedTranscriptPath(sessionDir);
|
|
365
|
+
let interactions;
|
|
366
|
+
if (transcriptPath && prompts.length > 0) {
|
|
367
|
+
const allInteractions = await claudeCode.extractInteractions(transcriptPath);
|
|
368
|
+
interactions = allInteractions.length > 0 ? allInteractions.slice(-prompts.length) : prompts.map((p) => ({ prompt: p, response: null }));
|
|
369
|
+
} else {
|
|
370
|
+
interactions = prompts.map((p) => ({ prompt: p, response: null }));
|
|
371
|
+
}
|
|
372
|
+
if (hasTurnData) {
|
|
373
|
+
attachFilesTouched(changeEntries, relevantPromptEntries, interactions, commitFileSet);
|
|
374
|
+
}
|
|
375
|
+
const entry = buildEntry({
|
|
376
|
+
sessionId: opts.sessionId,
|
|
377
|
+
interactions,
|
|
378
|
+
commitFiles,
|
|
379
|
+
aiFiles
|
|
380
|
+
});
|
|
381
|
+
await writeNote(commitSha, entry);
|
|
382
|
+
return { promptCount: interactions.length, aiRatio: entry.ai_ratio };
|
|
407
383
|
}
|
|
408
|
-
function
|
|
409
|
-
|
|
410
|
-
|
|
384
|
+
function attachFilesTouched(changeEntries, promptEntries, interactions, commitFileSet) {
|
|
385
|
+
const filesByTurn = /* @__PURE__ */ new Map();
|
|
386
|
+
for (const entry of changeEntries) {
|
|
387
|
+
const turn = typeof entry.turn === "number" ? entry.turn : 0;
|
|
388
|
+
const file = entry.file;
|
|
389
|
+
if (!file || !commitFileSet.has(file)) continue;
|
|
390
|
+
if (!filesByTurn.has(turn)) filesByTurn.set(turn, /* @__PURE__ */ new Set());
|
|
391
|
+
filesByTurn.get(turn)?.add(file);
|
|
392
|
+
}
|
|
393
|
+
for (let i = 0; i < interactions.length; i++) {
|
|
394
|
+
const promptEntry = promptEntries[i];
|
|
395
|
+
if (!promptEntry) continue;
|
|
396
|
+
const turn = typeof promptEntry.turn === "number" ? promptEntry.turn : 0;
|
|
397
|
+
const files = filesByTurn.get(turn);
|
|
398
|
+
if (files && files.size > 0) {
|
|
399
|
+
interactions[i].files_touched = [...files];
|
|
400
|
+
}
|
|
401
|
+
}
|
|
411
402
|
}
|
|
412
|
-
function
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
403
|
+
async function readAllSessionJsonl(sessionDir, baseFile) {
|
|
404
|
+
const stem = baseFile.slice(0, baseFile.lastIndexOf(".jsonl"));
|
|
405
|
+
const files = await readdir(sessionDir).catch(() => []);
|
|
406
|
+
const matching = files.filter((f) => f === baseFile || f.startsWith(`${stem}-`) && f.endsWith(".jsonl")).sort().map((f) => join2(sessionDir, f));
|
|
407
|
+
const all = [];
|
|
408
|
+
for (const file of matching) {
|
|
409
|
+
const entries = await readJsonlEntries(file);
|
|
410
|
+
all.push(...entries);
|
|
411
|
+
}
|
|
412
|
+
return all;
|
|
413
|
+
}
|
|
414
|
+
async function readSavedTranscriptPath(sessionDir) {
|
|
415
|
+
const saved = join2(sessionDir, TRANSCRIPT_PATH_FILE);
|
|
416
|
+
if (!existsSync3(saved)) return null;
|
|
417
|
+
const p = (await readFile3(saved, "utf-8")).trim();
|
|
418
|
+
return p || null;
|
|
425
419
|
}
|
|
426
420
|
|
|
427
|
-
// src/
|
|
428
|
-
import {
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
async function
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
421
|
+
// src/paths.ts
|
|
422
|
+
import { join as join3 } from "node:path";
|
|
423
|
+
var _root = null;
|
|
424
|
+
var _gitDir = null;
|
|
425
|
+
async function root() {
|
|
426
|
+
if (!_root) {
|
|
427
|
+
try {
|
|
428
|
+
_root = await repoRoot();
|
|
429
|
+
} catch {
|
|
430
|
+
console.error("error: git repository not found");
|
|
431
|
+
process.exit(1);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
return _root;
|
|
435
|
+
}
|
|
436
|
+
async function gitDir() {
|
|
437
|
+
if (!_gitDir) {
|
|
438
|
+
_gitDir = await git(["rev-parse", "--git-dir"]);
|
|
439
|
+
if (!_gitDir.startsWith("/")) {
|
|
440
|
+
_gitDir = join3(await root(), _gitDir);
|
|
440
441
|
}
|
|
441
442
|
}
|
|
443
|
+
return _gitDir;
|
|
444
|
+
}
|
|
445
|
+
async function agentnoteDir() {
|
|
446
|
+
return join3(await gitDir(), AGENTNOTE_DIR);
|
|
447
|
+
}
|
|
448
|
+
async function sessionFile() {
|
|
449
|
+
return join3(await agentnoteDir(), SESSION_FILE);
|
|
442
450
|
}
|
|
443
451
|
|
|
444
452
|
// src/commands/commit.ts
|
|
445
453
|
async function commit(args2) {
|
|
446
454
|
const sf = await sessionFile();
|
|
447
455
|
let sessionId = "";
|
|
448
|
-
if (
|
|
449
|
-
sessionId = (await
|
|
456
|
+
if (existsSync4(sf)) {
|
|
457
|
+
sessionId = (await readFile4(sf, "utf-8")).trim();
|
|
450
458
|
}
|
|
451
459
|
const gitArgs = ["commit"];
|
|
452
460
|
if (sessionId) {
|
|
453
|
-
gitArgs.push("--trailer",
|
|
461
|
+
gitArgs.push("--trailer", `${TRAILER_KEY}: ${sessionId}`);
|
|
454
462
|
}
|
|
455
463
|
gitArgs.push(...args2);
|
|
456
464
|
const child = spawn("git", gitArgs, {
|
|
@@ -466,210 +474,44 @@ async function commit(args2) {
|
|
|
466
474
|
if (sessionId) {
|
|
467
475
|
try {
|
|
468
476
|
const agentnoteDirPath = await agentnoteDir();
|
|
469
|
-
const
|
|
470
|
-
|
|
471
|
-
let commitFiles = [];
|
|
472
|
-
try {
|
|
473
|
-
const raw = await git([
|
|
474
|
-
"diff-tree",
|
|
475
|
-
"--no-commit-id",
|
|
476
|
-
"--name-only",
|
|
477
|
-
"-r",
|
|
478
|
-
"HEAD"
|
|
479
|
-
]);
|
|
480
|
-
commitFiles = raw.split("\n").filter(Boolean);
|
|
481
|
-
} catch {
|
|
482
|
-
}
|
|
483
|
-
const aiFiles = await readJsonlField(
|
|
484
|
-
join5(sessionDir, "changes.jsonl"),
|
|
485
|
-
"file"
|
|
486
|
-
);
|
|
487
|
-
const prompts = await readJsonlField(
|
|
488
|
-
join5(sessionDir, "prompts.jsonl"),
|
|
489
|
-
"prompt"
|
|
490
|
-
);
|
|
491
|
-
let interactions;
|
|
492
|
-
const transcriptPathFile = join5(sessionDir, "transcript_path");
|
|
493
|
-
if (existsSync5(transcriptPathFile)) {
|
|
494
|
-
const transcriptPath = (await readFile3(transcriptPathFile, "utf-8")).trim();
|
|
495
|
-
if (transcriptPath) {
|
|
496
|
-
const allInteractions = await claudeCode.extractInteractions(transcriptPath);
|
|
497
|
-
interactions = prompts.length > 0 && allInteractions.length > 0 ? allInteractions.slice(-prompts.length) : prompts.map((p) => ({ prompt: p, response: null }));
|
|
498
|
-
} else {
|
|
499
|
-
interactions = prompts.map((p) => ({ prompt: p, response: null }));
|
|
500
|
-
}
|
|
501
|
-
} else {
|
|
502
|
-
interactions = prompts.map((p) => ({ prompt: p, response: null }));
|
|
503
|
-
}
|
|
504
|
-
const entry = buildEntry({
|
|
505
|
-
sessionId,
|
|
506
|
-
interactions,
|
|
507
|
-
commitFiles,
|
|
508
|
-
aiFiles
|
|
509
|
-
});
|
|
510
|
-
await writeNote(commitSha, entry);
|
|
511
|
-
await rotateLogs(sessionDir, commitSha);
|
|
512
|
-
console.log(
|
|
513
|
-
`agentnote: ${interactions.length} prompts, AI ratio ${entry.ai_ratio}%`
|
|
514
|
-
);
|
|
477
|
+
const result = await recordCommitEntry({ agentnoteDirPath, sessionId });
|
|
478
|
+
console.log(`agentnote: ${result.promptCount} prompts, AI ratio ${result.aiRatio}%`);
|
|
515
479
|
} catch (err) {
|
|
516
480
|
console.error(`agentnote: warning: ${err.message}`);
|
|
517
481
|
}
|
|
518
482
|
}
|
|
519
483
|
}
|
|
520
484
|
|
|
521
|
-
// src/commands/
|
|
522
|
-
import {
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
const commitInfo = await git(["log", "-1", "--format=%h %s", ref]);
|
|
526
|
-
const commitSha = await git(["log", "-1", "--format=%H", ref]);
|
|
527
|
-
const sessionId = (await git([
|
|
528
|
-
"log",
|
|
529
|
-
"-1",
|
|
530
|
-
"--format=%(trailers:key=Agentnote-Session,valueonly)",
|
|
531
|
-
ref
|
|
532
|
-
])).trim();
|
|
533
|
-
console.log(`commit: ${commitInfo}`);
|
|
534
|
-
if (!sessionId) {
|
|
535
|
-
console.log("session: none (no agentnote data)");
|
|
536
|
-
return;
|
|
537
|
-
}
|
|
538
|
-
console.log(`session: ${sessionId}`);
|
|
539
|
-
const raw = await readNote(commitSha);
|
|
540
|
-
const entry = raw;
|
|
541
|
-
if (entry) {
|
|
542
|
-
console.log();
|
|
543
|
-
const ratioBar = renderRatioBar(entry.ai_ratio);
|
|
544
|
-
console.log(`ai: ${entry.ai_ratio}% ${ratioBar}`);
|
|
545
|
-
console.log(
|
|
546
|
-
`files: ${entry.files_in_commit.length} changed, ${entry.files_by_ai.length} by AI`
|
|
547
|
-
);
|
|
548
|
-
if (entry.files_in_commit.length > 0) {
|
|
549
|
-
console.log();
|
|
550
|
-
for (const file of entry.files_in_commit) {
|
|
551
|
-
const isAi = entry.files_by_ai.includes(file);
|
|
552
|
-
const marker = isAi ? " \u{1F916}" : " \u{1F464}";
|
|
553
|
-
console.log(` ${file}${marker}`);
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
const interactions = entry.interactions ?? (entry.prompts ?? []).map((p) => ({
|
|
557
|
-
prompt: p,
|
|
558
|
-
response: null
|
|
559
|
-
}));
|
|
560
|
-
if (interactions.length > 0) {
|
|
561
|
-
console.log();
|
|
562
|
-
console.log(`prompts: ${interactions.length}`);
|
|
563
|
-
for (let i = 0; i < interactions.length; i++) {
|
|
564
|
-
const { prompt, response } = interactions[i];
|
|
565
|
-
console.log();
|
|
566
|
-
console.log(` ${i + 1}. ${truncateLines(prompt, 120)}`);
|
|
567
|
-
if (response) {
|
|
568
|
-
console.log(` \u2192 ${truncateLines(response, 200)}`);
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
} else {
|
|
573
|
-
console.log("entry: no agentnote note found for this commit");
|
|
574
|
-
}
|
|
575
|
-
console.log();
|
|
576
|
-
const adapter = claudeCode;
|
|
577
|
-
const transcriptPath = adapter.findTranscript(sessionId);
|
|
578
|
-
if (transcriptPath) {
|
|
579
|
-
const stats = await stat(transcriptPath);
|
|
580
|
-
const sizeKb = (stats.size / 1024).toFixed(1);
|
|
581
|
-
console.log(`transcript: ${transcriptPath} (${sizeKb} KB)`);
|
|
582
|
-
} else {
|
|
583
|
-
console.log("transcript: not found locally");
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
function renderRatioBar(ratio) {
|
|
587
|
-
const width = 20;
|
|
588
|
-
const filled = Math.round(ratio / 100 * width);
|
|
589
|
-
const empty = width - filled;
|
|
590
|
-
return `[${"\u2588".repeat(filled)}${"\u2591".repeat(empty)}]`;
|
|
591
|
-
}
|
|
592
|
-
function truncateLines(text, maxLen) {
|
|
593
|
-
const firstLine = text.split("\n")[0];
|
|
594
|
-
if (firstLine.length <= maxLen) return firstLine;
|
|
595
|
-
return firstLine.slice(0, maxLen) + "\u2026";
|
|
596
|
-
}
|
|
485
|
+
// src/commands/hook.ts
|
|
486
|
+
import { existsSync as existsSync6 } from "node:fs";
|
|
487
|
+
import { mkdir as mkdir2, readFile as readFile5, realpath, writeFile as writeFile2 } from "node:fs/promises";
|
|
488
|
+
import { isAbsolute, join as join5, relative } from "node:path";
|
|
597
489
|
|
|
598
|
-
// src/
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
for (const line of raw.split("\n")) {
|
|
610
|
-
if (!line.trim()) continue;
|
|
611
|
-
const parts = line.split(" ");
|
|
612
|
-
const fullSha = parts[0];
|
|
613
|
-
const commitPart = parts[1];
|
|
614
|
-
const sid = parts[2]?.trim();
|
|
615
|
-
if (!fullSha || !commitPart) continue;
|
|
616
|
-
if (!sid) {
|
|
617
|
-
console.log(commitPart);
|
|
618
|
-
continue;
|
|
619
|
-
}
|
|
620
|
-
let ratioStr = "";
|
|
621
|
-
let promptCount = "";
|
|
622
|
-
const note = await readNote(fullSha);
|
|
623
|
-
if (note) {
|
|
624
|
-
const entry = note;
|
|
625
|
-
ratioStr = `${entry.ai_ratio}%`;
|
|
626
|
-
promptCount = `${entry.interactions?.length ?? entry.prompts?.length ?? 0}p`;
|
|
627
|
-
}
|
|
628
|
-
if (ratioStr) {
|
|
629
|
-
console.log(
|
|
630
|
-
`${commitPart} [${sid.slice(0, 8)}\u2026 | \u{1F916}${ratioStr} | ${promptCount}]`
|
|
631
|
-
);
|
|
632
|
-
} else {
|
|
633
|
-
console.log(`${commitPart} [${sid.slice(0, 8)}\u2026]`);
|
|
490
|
+
// src/core/rotate.ts
|
|
491
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
492
|
+
import { readdir as readdir2, rename, unlink } from "node:fs/promises";
|
|
493
|
+
import { join as join4 } from "node:path";
|
|
494
|
+
async function rotateLogs(sessionDir, commitSha, fileNames = [PROMPTS_FILE, CHANGES_FILE]) {
|
|
495
|
+
await purgeRotatedArchives(sessionDir, fileNames);
|
|
496
|
+
for (const name of fileNames) {
|
|
497
|
+
const src = join4(sessionDir, name);
|
|
498
|
+
if (existsSync5(src)) {
|
|
499
|
+
const base = name.replace(".jsonl", "");
|
|
500
|
+
await rename(src, join4(sessionDir, `${base}-${commitSha.slice(0, 8)}.jsonl`));
|
|
634
501
|
}
|
|
635
502
|
}
|
|
636
503
|
}
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
const repoRoot2 = await root();
|
|
646
|
-
const adapter = claudeCode;
|
|
647
|
-
const hooksActive = await adapter.isEnabled(repoRoot2);
|
|
648
|
-
if (hooksActive) {
|
|
649
|
-
console.log("hooks: active");
|
|
650
|
-
} else {
|
|
651
|
-
console.log("hooks: not configured (run 'agentnote init')");
|
|
652
|
-
}
|
|
653
|
-
const sessionPath = await sessionFile();
|
|
654
|
-
if (existsSync6(sessionPath)) {
|
|
655
|
-
const sid = (await readFile4(sessionPath, "utf-8")).trim();
|
|
656
|
-
console.log(`session: ${sid.slice(0, 8)}\u2026`);
|
|
657
|
-
} else {
|
|
658
|
-
console.log("session: none");
|
|
659
|
-
}
|
|
660
|
-
const { stdout } = await gitSafe([
|
|
661
|
-
"log",
|
|
662
|
-
"-20",
|
|
663
|
-
"--format=%(trailers:key=Agentnote-Session,valueonly)"
|
|
664
|
-
]);
|
|
665
|
-
const linked = stdout.split("\n").filter((line) => line.trim().length > 0).length;
|
|
666
|
-
console.log(`linked: ${linked}/20 recent commits`);
|
|
504
|
+
async function purgeRotatedArchives(sessionDir, fileNames) {
|
|
505
|
+
const files = await readdir2(sessionDir).catch(() => []);
|
|
506
|
+
for (const name of fileNames) {
|
|
507
|
+
const stem = name.replace(".jsonl", "");
|
|
508
|
+
const rotated = files.filter((f) => f.startsWith(`${stem}-`) && f.endsWith(".jsonl"));
|
|
509
|
+
await Promise.all(rotated.map((f) => unlink(join4(sessionDir, f)).catch(() => {
|
|
510
|
+
})));
|
|
511
|
+
}
|
|
667
512
|
}
|
|
668
513
|
|
|
669
514
|
// src/commands/hook.ts
|
|
670
|
-
import { mkdir as mkdir3, readFile as readFile5, writeFile as writeFile3, realpath } from "node:fs/promises";
|
|
671
|
-
import { existsSync as existsSync7 } from "node:fs";
|
|
672
|
-
import { join as join6, relative, isAbsolute } from "node:path";
|
|
673
515
|
async function readStdin() {
|
|
674
516
|
const chunks = [];
|
|
675
517
|
for await (const chunk of process.stdin) {
|
|
@@ -691,15 +533,15 @@ async function hook() {
|
|
|
691
533
|
const event = adapter.parseEvent(input);
|
|
692
534
|
if (!event) return;
|
|
693
535
|
const agentnoteDirPath = await agentnoteDir();
|
|
694
|
-
const sessionDir =
|
|
695
|
-
await
|
|
536
|
+
const sessionDir = join5(agentnoteDirPath, SESSIONS_DIR, event.sessionId);
|
|
537
|
+
await mkdir2(sessionDir, { recursive: true });
|
|
696
538
|
switch (event.kind) {
|
|
697
539
|
case "session_start": {
|
|
698
|
-
await
|
|
540
|
+
await writeFile2(join5(agentnoteDirPath, SESSION_FILE), event.sessionId);
|
|
699
541
|
if (event.transcriptPath) {
|
|
700
|
-
await
|
|
542
|
+
await writeFile2(join5(sessionDir, TRANSCRIPT_PATH_FILE), event.transcriptPath);
|
|
701
543
|
}
|
|
702
|
-
await appendJsonl(
|
|
544
|
+
await appendJsonl(join5(sessionDir, EVENTS_FILE), {
|
|
703
545
|
event: "session_start",
|
|
704
546
|
session_id: event.sessionId,
|
|
705
547
|
timestamp: event.timestamp,
|
|
@@ -708,11 +550,11 @@ async function hook() {
|
|
|
708
550
|
break;
|
|
709
551
|
}
|
|
710
552
|
case "stop": {
|
|
711
|
-
await
|
|
553
|
+
await writeFile2(join5(agentnoteDirPath, SESSION_FILE), event.sessionId);
|
|
712
554
|
if (event.transcriptPath) {
|
|
713
|
-
await
|
|
555
|
+
await writeFile2(join5(sessionDir, TRANSCRIPT_PATH_FILE), event.transcriptPath);
|
|
714
556
|
}
|
|
715
|
-
await appendJsonl(
|
|
557
|
+
await appendJsonl(join5(sessionDir, EVENTS_FILE), {
|
|
716
558
|
event: "stop",
|
|
717
559
|
session_id: event.sessionId,
|
|
718
560
|
timestamp: event.timestamp
|
|
@@ -720,10 +562,21 @@ async function hook() {
|
|
|
720
562
|
break;
|
|
721
563
|
}
|
|
722
564
|
case "prompt": {
|
|
723
|
-
|
|
565
|
+
const rotateId = Date.now().toString(36);
|
|
566
|
+
await rotateLogs(sessionDir, rotateId);
|
|
567
|
+
const turnPath = join5(sessionDir, TURN_FILE);
|
|
568
|
+
let turn = 0;
|
|
569
|
+
if (existsSync6(turnPath)) {
|
|
570
|
+
const raw2 = (await readFile5(turnPath, "utf-8")).trim();
|
|
571
|
+
turn = Number.parseInt(raw2, 10) || 0;
|
|
572
|
+
}
|
|
573
|
+
turn += 1;
|
|
574
|
+
await writeFile2(turnPath, String(turn));
|
|
575
|
+
await appendJsonl(join5(sessionDir, PROMPTS_FILE), {
|
|
724
576
|
event: "prompt",
|
|
725
577
|
timestamp: event.timestamp,
|
|
726
|
-
prompt: event.prompt
|
|
578
|
+
prompt: event.prompt,
|
|
579
|
+
turn
|
|
727
580
|
});
|
|
728
581
|
break;
|
|
729
582
|
}
|
|
@@ -735,7 +588,7 @@ async function hook() {
|
|
|
735
588
|
const repoRoot2 = await realpath(rawRoot);
|
|
736
589
|
let normalizedFile = filePath;
|
|
737
590
|
if (repoRoot2.startsWith("/private") && !normalizedFile.startsWith("/private")) {
|
|
738
|
-
normalizedFile =
|
|
591
|
+
normalizedFile = `/private${normalizedFile}`;
|
|
739
592
|
} else if (!repoRoot2.startsWith("/private") && normalizedFile.startsWith("/private")) {
|
|
740
593
|
normalizedFile = normalizedFile.replace(/^\/private/, "");
|
|
741
594
|
}
|
|
@@ -743,24 +596,31 @@ async function hook() {
|
|
|
743
596
|
} catch {
|
|
744
597
|
}
|
|
745
598
|
}
|
|
746
|
-
|
|
599
|
+
let turn = 0;
|
|
600
|
+
const turnPath = join5(sessionDir, TURN_FILE);
|
|
601
|
+
if (existsSync6(turnPath)) {
|
|
602
|
+
const raw2 = (await readFile5(turnPath, "utf-8")).trim();
|
|
603
|
+
turn = Number.parseInt(raw2, 10) || 0;
|
|
604
|
+
}
|
|
605
|
+
await appendJsonl(join5(sessionDir, CHANGES_FILE), {
|
|
747
606
|
event: "file_change",
|
|
748
607
|
timestamp: event.timestamp,
|
|
749
608
|
tool: event.tool,
|
|
750
609
|
file: filePath,
|
|
751
|
-
session_id: event.sessionId
|
|
610
|
+
session_id: event.sessionId,
|
|
611
|
+
turn
|
|
752
612
|
});
|
|
753
613
|
break;
|
|
754
614
|
}
|
|
755
615
|
case "pre_commit": {
|
|
756
616
|
const cmd = event.commitCommand ?? "";
|
|
757
|
-
if (!cmd.includes(
|
|
617
|
+
if (!cmd.includes(TRAILER_KEY) && event.sessionId) {
|
|
758
618
|
process.stdout.write(
|
|
759
619
|
JSON.stringify({
|
|
760
620
|
hookSpecificOutput: {
|
|
761
621
|
hookEventName: "PreToolUse",
|
|
762
622
|
updatedInput: {
|
|
763
|
-
command: `${cmd} --trailer '
|
|
623
|
+
command: `${cmd} --trailer '${TRAILER_KEY}: ${event.sessionId}'`
|
|
764
624
|
}
|
|
765
625
|
}
|
|
766
626
|
})
|
|
@@ -770,47 +630,138 @@ async function hook() {
|
|
|
770
630
|
}
|
|
771
631
|
case "post_commit": {
|
|
772
632
|
try {
|
|
773
|
-
await
|
|
633
|
+
await recordCommitEntry({
|
|
634
|
+
agentnoteDirPath,
|
|
635
|
+
sessionId: event.sessionId,
|
|
636
|
+
transcriptPath: event.transcriptPath
|
|
637
|
+
});
|
|
774
638
|
} catch {
|
|
775
639
|
}
|
|
776
640
|
break;
|
|
777
641
|
}
|
|
778
642
|
}
|
|
779
643
|
}
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
644
|
+
|
|
645
|
+
// src/commands/init.ts
|
|
646
|
+
import { existsSync as existsSync7 } from "node:fs";
|
|
647
|
+
import { mkdir as mkdir3, writeFile as writeFile3 } from "node:fs/promises";
|
|
648
|
+
import { join as join6 } from "node:path";
|
|
649
|
+
var WORKFLOW_TEMPLATE = `name: Agent Note
|
|
650
|
+
on:
|
|
651
|
+
pull_request:
|
|
652
|
+
types: [opened, synchronize]
|
|
653
|
+
concurrency:
|
|
654
|
+
group: agentnote-\${{ github.event.pull_request.number }}
|
|
655
|
+
cancel-in-progress: true
|
|
656
|
+
permissions:
|
|
657
|
+
contents: read
|
|
658
|
+
pull-requests: write
|
|
659
|
+
jobs:
|
|
660
|
+
report:
|
|
661
|
+
runs-on: ubuntu-latest
|
|
662
|
+
timeout-minutes: 5
|
|
663
|
+
steps:
|
|
664
|
+
- uses: actions/checkout@v4
|
|
665
|
+
with:
|
|
666
|
+
fetch-depth: 0
|
|
667
|
+
- uses: wasabeef/agentnote@v0
|
|
668
|
+
`;
|
|
669
|
+
async function init(args2) {
|
|
670
|
+
const skipHooks = args2.includes("--no-hooks");
|
|
671
|
+
const skipAction = args2.includes("--no-action");
|
|
672
|
+
const skipNotes = args2.includes("--no-notes");
|
|
673
|
+
const hooksOnly = args2.includes("--hooks");
|
|
674
|
+
const actionOnly = args2.includes("--action");
|
|
675
|
+
const repoRoot2 = await root();
|
|
676
|
+
const results = [];
|
|
677
|
+
await mkdir3(await agentnoteDir(), { recursive: true });
|
|
678
|
+
if (!skipHooks && !actionOnly) {
|
|
679
|
+
const adapter = claudeCode;
|
|
680
|
+
if (await adapter.isEnabled(repoRoot2)) {
|
|
681
|
+
results.push(" \xB7 hooks already configured");
|
|
682
|
+
} else {
|
|
683
|
+
await adapter.installHooks(repoRoot2);
|
|
684
|
+
results.push(" \u2713 hooks added to .claude/settings.json");
|
|
685
|
+
}
|
|
788
686
|
}
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
687
|
+
if (!skipAction && !hooksOnly) {
|
|
688
|
+
const workflowDir = join6(repoRoot2, ".github", "workflows");
|
|
689
|
+
const workflowPath = join6(workflowDir, "agentnote.yml");
|
|
690
|
+
if (existsSync7(workflowPath)) {
|
|
691
|
+
results.push(" \xB7 workflow already exists at .github/workflows/agentnote.yml");
|
|
692
|
+
} else {
|
|
693
|
+
await mkdir3(workflowDir, { recursive: true });
|
|
694
|
+
await writeFile3(workflowPath, WORKFLOW_TEMPLATE);
|
|
695
|
+
results.push(" \u2713 workflow created at .github/workflows/agentnote.yml");
|
|
696
|
+
}
|
|
799
697
|
}
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
698
|
+
if (!skipNotes && !hooksOnly && !actionOnly) {
|
|
699
|
+
const { stdout } = await gitSafe(["config", "--get-all", "remote.origin.fetch"]);
|
|
700
|
+
if (stdout.includes(NOTES_REF_FULL)) {
|
|
701
|
+
results.push(" \xB7 git already configured to fetch notes");
|
|
702
|
+
} else {
|
|
703
|
+
await gitSafe(["config", "--add", "remote.origin.fetch", NOTES_FETCH_REFSPEC]);
|
|
704
|
+
results.push(" \u2713 git configured to auto-fetch notes on pull");
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
console.log("");
|
|
708
|
+
console.log("agentnote init");
|
|
709
|
+
console.log("");
|
|
710
|
+
for (const line of results) {
|
|
711
|
+
console.log(line);
|
|
712
|
+
}
|
|
713
|
+
const toCommit = [];
|
|
714
|
+
if (!skipHooks && !actionOnly) toCommit.push(".claude/settings.json");
|
|
715
|
+
if (!skipAction && !hooksOnly) {
|
|
716
|
+
const workflowPath = join6(repoRoot2, ".github", "workflows", "agentnote.yml");
|
|
717
|
+
if (existsSync7(workflowPath)) toCommit.push(".github/workflows/agentnote.yml");
|
|
718
|
+
}
|
|
719
|
+
if (toCommit.length > 0) {
|
|
720
|
+
console.log("");
|
|
721
|
+
console.log(" Next: commit and push these files");
|
|
722
|
+
console.log(` git add ${toCommit.join(" ")}`);
|
|
723
|
+
console.log(' git commit -m "chore: enable agentnote session tracking"');
|
|
724
|
+
console.log(" git push");
|
|
725
|
+
}
|
|
726
|
+
console.log("");
|
|
808
727
|
}
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
const
|
|
813
|
-
|
|
728
|
+
|
|
729
|
+
// src/commands/log.ts
|
|
730
|
+
async function log(count = 10) {
|
|
731
|
+
const raw = await git([
|
|
732
|
+
"log",
|
|
733
|
+
`-${count}`,
|
|
734
|
+
`--format=%H %h %s %(trailers:key=${TRAILER_KEY},valueonly)`
|
|
735
|
+
]);
|
|
736
|
+
if (!raw) {
|
|
737
|
+
console.log("no commits found");
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
for (const line of raw.split("\n")) {
|
|
741
|
+
if (!line.trim()) continue;
|
|
742
|
+
const parts = line.split(" ");
|
|
743
|
+
const fullSha = parts[0];
|
|
744
|
+
const commitPart = parts[1];
|
|
745
|
+
const sid = parts[2]?.trim();
|
|
746
|
+
if (!fullSha || !commitPart) continue;
|
|
747
|
+
if (!sid) {
|
|
748
|
+
console.log(commitPart);
|
|
749
|
+
continue;
|
|
750
|
+
}
|
|
751
|
+
let ratioStr = "";
|
|
752
|
+
let promptCount = "";
|
|
753
|
+
const note = await readNote(fullSha);
|
|
754
|
+
if (note) {
|
|
755
|
+
const entry = note;
|
|
756
|
+
ratioStr = `${entry.ai_ratio}%`;
|
|
757
|
+
promptCount = `${entry.interactions?.length ?? entry.prompts?.length ?? 0}p`;
|
|
758
|
+
}
|
|
759
|
+
if (ratioStr) {
|
|
760
|
+
console.log(`${commitPart} [${sid.slice(0, 8)}\u2026 | \u{1F916}${ratioStr} | ${promptCount}]`);
|
|
761
|
+
} else {
|
|
762
|
+
console.log(`${commitPart} [${sid.slice(0, 8)}\u2026]`);
|
|
763
|
+
}
|
|
764
|
+
}
|
|
814
765
|
}
|
|
815
766
|
|
|
816
767
|
// src/commands/pr.ts
|
|
@@ -821,12 +772,7 @@ var MARKER_BEGIN = "<!-- agentnote-begin -->";
|
|
|
821
772
|
var MARKER_END = "<!-- agentnote-end -->";
|
|
822
773
|
async function collectReport(base) {
|
|
823
774
|
const head = await git(["rev-parse", "--short", "HEAD"]);
|
|
824
|
-
const raw = await git([
|
|
825
|
-
"log",
|
|
826
|
-
"--reverse",
|
|
827
|
-
"--format=%H %h %s",
|
|
828
|
-
`${base}..HEAD`
|
|
829
|
-
]);
|
|
775
|
+
const raw = await git(["log", "--reverse", "--format=%H %h %s", `${base}..HEAD`]);
|
|
830
776
|
if (!raw.trim()) return null;
|
|
831
777
|
const commits = [];
|
|
832
778
|
for (const line of raw.trim().split("\n")) {
|
|
@@ -916,7 +862,7 @@ function renderMarkdown(report) {
|
|
|
916
862
|
for (const { prompt, response } of c.interactions) {
|
|
917
863
|
lines.push(`> **Prompt:** ${prompt}`);
|
|
918
864
|
if (response) {
|
|
919
|
-
const truncated = response.length >
|
|
865
|
+
const truncated = response.length > TRUNCATE_RESPONSE_PR ? `${response.slice(0, TRUNCATE_RESPONSE_PR)}\u2026` : response;
|
|
920
866
|
lines.push(">");
|
|
921
867
|
lines.push(`> **Response:** ${truncated.split("\n").join("\n> ")}`);
|
|
922
868
|
}
|
|
@@ -952,7 +898,9 @@ function renderChat(report) {
|
|
|
952
898
|
continue;
|
|
953
899
|
}
|
|
954
900
|
lines.push(`<details>`);
|
|
955
|
-
lines.push(
|
|
901
|
+
lines.push(
|
|
902
|
+
`<summary><code>${c.short}</code> ${c.message}${summaryExtra}${summaryFiles}</summary>`
|
|
903
|
+
);
|
|
956
904
|
lines.push("");
|
|
957
905
|
for (const { prompt, response } of c.interactions) {
|
|
958
906
|
lines.push(`> **\u{1F9D1} Prompt**`);
|
|
@@ -961,7 +909,7 @@ function renderChat(report) {
|
|
|
961
909
|
if (response) {
|
|
962
910
|
lines.push(`**\u{1F916} Response**`);
|
|
963
911
|
lines.push("");
|
|
964
|
-
const truncated = response.length >
|
|
912
|
+
const truncated = response.length > TRUNCATE_RESPONSE_CHAT ? `${response.slice(0, TRUNCATE_RESPONSE_CHAT)}\u2026` : response;
|
|
965
913
|
lines.push(truncated);
|
|
966
914
|
lines.push("");
|
|
967
915
|
}
|
|
@@ -1015,27 +963,23 @@ function upsertInDescription(existingBody, section) {
|
|
|
1015
963
|
if (existingBody.includes(MARKER_BEGIN)) {
|
|
1016
964
|
const before = existingBody.slice(0, existingBody.indexOf(MARKER_BEGIN));
|
|
1017
965
|
const after = existingBody.includes(MARKER_END) ? existingBody.slice(existingBody.indexOf(MARKER_END) + MARKER_END.length) : "";
|
|
1018
|
-
return before.trimEnd()
|
|
966
|
+
return `${before.trimEnd()}
|
|
967
|
+
|
|
968
|
+
${marked}${after}`;
|
|
1019
969
|
}
|
|
1020
|
-
return existingBody.trimEnd()
|
|
970
|
+
return `${existingBody.trimEnd()}
|
|
971
|
+
|
|
972
|
+
${marked}`;
|
|
1021
973
|
}
|
|
1022
974
|
async function updatePrDescription(prNumber, section) {
|
|
1023
|
-
const { stdout: bodyJson } = await execFileAsync2(
|
|
1024
|
-
"
|
|
1025
|
-
"view",
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
"body"
|
|
1029
|
-
], { encoding: "utf-8" });
|
|
975
|
+
const { stdout: bodyJson } = await execFileAsync2(
|
|
976
|
+
"gh",
|
|
977
|
+
["pr", "view", prNumber, "--json", "body"],
|
|
978
|
+
{ encoding: "utf-8" }
|
|
979
|
+
);
|
|
1030
980
|
const currentBody = JSON.parse(bodyJson).body ?? "";
|
|
1031
981
|
const newBody = upsertInDescription(currentBody, section);
|
|
1032
|
-
await execFileAsync2("gh", [
|
|
1033
|
-
"pr",
|
|
1034
|
-
"edit",
|
|
1035
|
-
prNumber,
|
|
1036
|
-
"--body",
|
|
1037
|
-
newBody
|
|
1038
|
-
], { encoding: "utf-8" });
|
|
982
|
+
await execFileAsync2("gh", ["pr", "edit", prNumber, "--body", newBody], { encoding: "utf-8" });
|
|
1039
983
|
}
|
|
1040
984
|
async function pr(args2) {
|
|
1041
985
|
const isJson = args2.includes("--json");
|
|
@@ -1048,9 +992,7 @@ async function pr(args2) {
|
|
|
1048
992
|
);
|
|
1049
993
|
const base = positional[0] ?? await detectBaseBranch();
|
|
1050
994
|
if (!base) {
|
|
1051
|
-
console.error(
|
|
1052
|
-
"error: could not detect base branch. pass it as argument: agentnote pr <base>"
|
|
1053
|
-
);
|
|
995
|
+
console.error("error: could not detect base branch. pass it as argument: agentnote pr <base>");
|
|
1054
996
|
process.exit(1);
|
|
1055
997
|
}
|
|
1056
998
|
const report = await collectReport(base);
|
|
@@ -1058,7 +1000,7 @@ async function pr(args2) {
|
|
|
1058
1000
|
if (isJson) {
|
|
1059
1001
|
console.log(JSON.stringify({ error: "no commits found" }));
|
|
1060
1002
|
} else {
|
|
1061
|
-
console.log(
|
|
1003
|
+
console.log(`no commits found between HEAD and ${base}`);
|
|
1062
1004
|
}
|
|
1063
1005
|
return;
|
|
1064
1006
|
}
|
|
@@ -1078,7 +1020,7 @@ async function pr(args2) {
|
|
|
1078
1020
|
}
|
|
1079
1021
|
}
|
|
1080
1022
|
function renderBar(ratio) {
|
|
1081
|
-
const width =
|
|
1023
|
+
const width = BAR_WIDTH_COMPACT;
|
|
1082
1024
|
const filled = Math.round(ratio / 100 * width);
|
|
1083
1025
|
return "\u2588".repeat(filled) + "\u2591".repeat(width - filled);
|
|
1084
1026
|
}
|
|
@@ -1087,20 +1029,190 @@ function basename(path) {
|
|
|
1087
1029
|
}
|
|
1088
1030
|
async function detectBaseBranch() {
|
|
1089
1031
|
for (const name of ["main", "master", "develop"]) {
|
|
1090
|
-
const { exitCode } = await gitSafe([
|
|
1091
|
-
"rev-parse",
|
|
1092
|
-
"--verify",
|
|
1093
|
-
`origin/${name}`
|
|
1094
|
-
]);
|
|
1032
|
+
const { exitCode } = await gitSafe(["rev-parse", "--verify", `origin/${name}`]);
|
|
1095
1033
|
if (exitCode === 0) return `origin/${name}`;
|
|
1096
1034
|
}
|
|
1097
1035
|
return null;
|
|
1098
1036
|
}
|
|
1099
1037
|
|
|
1038
|
+
// src/commands/session.ts
|
|
1039
|
+
async function session(sessionId) {
|
|
1040
|
+
if (!sessionId) {
|
|
1041
|
+
console.error("usage: agentnote session <session-id>");
|
|
1042
|
+
process.exit(1);
|
|
1043
|
+
}
|
|
1044
|
+
const raw = await git([
|
|
1045
|
+
"log",
|
|
1046
|
+
"--all",
|
|
1047
|
+
`--max-count=${MAX_COMMITS}`,
|
|
1048
|
+
`--format=%H %h %s %(trailers:key=${TRAILER_KEY},valueonly)`
|
|
1049
|
+
]);
|
|
1050
|
+
if (!raw) {
|
|
1051
|
+
console.log("no commits found");
|
|
1052
|
+
return;
|
|
1053
|
+
}
|
|
1054
|
+
const matches = [];
|
|
1055
|
+
for (const line of raw.split("\n")) {
|
|
1056
|
+
if (!line.trim()) continue;
|
|
1057
|
+
const parts = line.split(" ");
|
|
1058
|
+
const fullSha = parts[0];
|
|
1059
|
+
const shortInfo = parts[1];
|
|
1060
|
+
const trailer = parts[2]?.trim();
|
|
1061
|
+
if (!fullSha || !shortInfo) continue;
|
|
1062
|
+
if (trailer === sessionId) {
|
|
1063
|
+
const note = await readNote(fullSha);
|
|
1064
|
+
const entry = note;
|
|
1065
|
+
matches.push({ sha: fullSha, shortInfo, entry });
|
|
1066
|
+
continue;
|
|
1067
|
+
}
|
|
1068
|
+
if (!trailer) {
|
|
1069
|
+
const note = await readNote(fullSha);
|
|
1070
|
+
if (note && note.session_id === sessionId) {
|
|
1071
|
+
const entry = note;
|
|
1072
|
+
matches.push({ sha: fullSha, shortInfo, entry });
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
if (matches.length === 0) {
|
|
1077
|
+
console.log(`no commits found for session ${sessionId}`);
|
|
1078
|
+
return;
|
|
1079
|
+
}
|
|
1080
|
+
matches.reverse();
|
|
1081
|
+
console.log(`Session: ${sessionId}`);
|
|
1082
|
+
console.log(`Commits: ${matches.length}`);
|
|
1083
|
+
console.log();
|
|
1084
|
+
let totalPrompts = 0;
|
|
1085
|
+
let totalRatio = 0;
|
|
1086
|
+
let ratioCount = 0;
|
|
1087
|
+
for (const m of matches) {
|
|
1088
|
+
let suffix = "";
|
|
1089
|
+
if (m.entry) {
|
|
1090
|
+
const promptCount = m.entry.interactions?.length ?? m.entry.prompts?.length ?? 0;
|
|
1091
|
+
totalPrompts += promptCount;
|
|
1092
|
+
totalRatio += m.entry.ai_ratio;
|
|
1093
|
+
ratioCount++;
|
|
1094
|
+
suffix = ` [\u{1F916}${m.entry.ai_ratio}% | ${promptCount}p]`;
|
|
1095
|
+
}
|
|
1096
|
+
console.log(`${m.shortInfo}${suffix}`);
|
|
1097
|
+
}
|
|
1098
|
+
console.log();
|
|
1099
|
+
if (ratioCount > 0) {
|
|
1100
|
+
const avgRatio = Math.round(totalRatio / ratioCount);
|
|
1101
|
+
console.log(`Total: ${totalPrompts} prompts, avg AI ratio ${avgRatio}%`);
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
// src/commands/show.ts
|
|
1106
|
+
import { stat } from "node:fs/promises";
|
|
1107
|
+
async function show(commitRef) {
|
|
1108
|
+
const ref = commitRef ?? "HEAD";
|
|
1109
|
+
const commitInfo = await git(["log", "-1", "--format=%h %s", ref]);
|
|
1110
|
+
const commitSha = await git(["log", "-1", "--format=%H", ref]);
|
|
1111
|
+
const sessionId = (await git(["log", "-1", `--format=%(trailers:key=${TRAILER_KEY},valueonly)`, ref])).trim();
|
|
1112
|
+
console.log(`commit: ${commitInfo}`);
|
|
1113
|
+
if (!sessionId) {
|
|
1114
|
+
console.log("session: none (no agentnote data)");
|
|
1115
|
+
return;
|
|
1116
|
+
}
|
|
1117
|
+
console.log(`session: ${sessionId}`);
|
|
1118
|
+
const raw = await readNote(commitSha);
|
|
1119
|
+
const entry = raw;
|
|
1120
|
+
if (entry) {
|
|
1121
|
+
console.log();
|
|
1122
|
+
const ratioBar = renderRatioBar(entry.ai_ratio);
|
|
1123
|
+
console.log(`ai: ${entry.ai_ratio}% ${ratioBar}`);
|
|
1124
|
+
console.log(
|
|
1125
|
+
`files: ${entry.files_in_commit.length} changed, ${entry.files_by_ai.length} by AI`
|
|
1126
|
+
);
|
|
1127
|
+
if (entry.files_in_commit.length > 0) {
|
|
1128
|
+
console.log();
|
|
1129
|
+
for (const file of entry.files_in_commit) {
|
|
1130
|
+
const isAi = entry.files_by_ai.includes(file);
|
|
1131
|
+
const marker = isAi ? " \u{1F916}" : " \u{1F464}";
|
|
1132
|
+
console.log(` ${file}${marker}`);
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
const legacy = entry;
|
|
1136
|
+
const interactions = entry.interactions ?? (legacy.prompts ?? []).map((p) => ({
|
|
1137
|
+
prompt: p,
|
|
1138
|
+
response: null
|
|
1139
|
+
}));
|
|
1140
|
+
if (interactions.length > 0) {
|
|
1141
|
+
console.log();
|
|
1142
|
+
console.log(`prompts: ${interactions.length}`);
|
|
1143
|
+
for (let i = 0; i < interactions.length; i++) {
|
|
1144
|
+
const interaction = interactions[i];
|
|
1145
|
+
console.log();
|
|
1146
|
+
console.log(` ${i + 1}. ${truncateLines(interaction.prompt, TRUNCATE_PROMPT)}`);
|
|
1147
|
+
if (interaction.response) {
|
|
1148
|
+
console.log(` \u2192 ${truncateLines(interaction.response, TRUNCATE_RESPONSE_SHOW)}`);
|
|
1149
|
+
}
|
|
1150
|
+
if (interaction.files_touched && interaction.files_touched.length > 0) {
|
|
1151
|
+
for (const file of interaction.files_touched) {
|
|
1152
|
+
console.log(` \u{1F4C4} ${file}`);
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
} else {
|
|
1158
|
+
console.log("entry: no agentnote note found for this commit");
|
|
1159
|
+
}
|
|
1160
|
+
const adapter = claudeCode;
|
|
1161
|
+
const transcriptPath = adapter.findTranscript(sessionId);
|
|
1162
|
+
if (transcriptPath) {
|
|
1163
|
+
console.log();
|
|
1164
|
+
const stats = await stat(transcriptPath);
|
|
1165
|
+
const sizeKb = (stats.size / 1024).toFixed(1);
|
|
1166
|
+
console.log(`transcript: ${transcriptPath} (${sizeKb} KB)`);
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
function renderRatioBar(ratio) {
|
|
1170
|
+
const width = BAR_WIDTH_FULL;
|
|
1171
|
+
const filled = Math.round(ratio / 100 * width);
|
|
1172
|
+
const empty = width - filled;
|
|
1173
|
+
return `[${"\u2588".repeat(filled)}${"\u2591".repeat(empty)}]`;
|
|
1174
|
+
}
|
|
1175
|
+
function truncateLines(text, maxLen) {
|
|
1176
|
+
const firstLine = text.split("\n")[0];
|
|
1177
|
+
if (firstLine.length <= maxLen) return firstLine;
|
|
1178
|
+
return `${firstLine.slice(0, maxLen)}\u2026`;
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
// src/commands/status.ts
|
|
1182
|
+
import { existsSync as existsSync8 } from "node:fs";
|
|
1183
|
+
import { readFile as readFile6 } from "node:fs/promises";
|
|
1184
|
+
var VERSION = "0.1.6";
|
|
1185
|
+
async function status() {
|
|
1186
|
+
console.log(`agentnote v${VERSION}`);
|
|
1187
|
+
console.log();
|
|
1188
|
+
const repoRoot2 = await root();
|
|
1189
|
+
const adapter = claudeCode;
|
|
1190
|
+
const hooksActive = await adapter.isEnabled(repoRoot2);
|
|
1191
|
+
if (hooksActive) {
|
|
1192
|
+
console.log("hooks: active");
|
|
1193
|
+
} else {
|
|
1194
|
+
console.log("hooks: not configured (run 'agentnote init')");
|
|
1195
|
+
}
|
|
1196
|
+
const sessionPath = await sessionFile();
|
|
1197
|
+
if (existsSync8(sessionPath)) {
|
|
1198
|
+
const sid = (await readFile6(sessionPath, "utf-8")).trim();
|
|
1199
|
+
console.log(`session: ${sid.slice(0, 8)}\u2026`);
|
|
1200
|
+
} else {
|
|
1201
|
+
console.log("session: none");
|
|
1202
|
+
}
|
|
1203
|
+
const { stdout } = await gitSafe([
|
|
1204
|
+
"log",
|
|
1205
|
+
"-20",
|
|
1206
|
+
`--format=%(trailers:key=${TRAILER_KEY},valueonly)`
|
|
1207
|
+
]);
|
|
1208
|
+
const linked = stdout.split("\n").filter((line) => line.trim().length > 0).length;
|
|
1209
|
+
console.log(`linked: ${linked}/20 recent commits`);
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1100
1212
|
// src/cli.ts
|
|
1101
|
-
var VERSION2 = "0.1.
|
|
1213
|
+
var VERSION2 = "0.1.6";
|
|
1102
1214
|
var HELP = `
|
|
1103
|
-
agentnote \u2014 remember why your code changed
|
|
1215
|
+
agentnote v${VERSION2} \u2014 remember why your code changed
|
|
1104
1216
|
|
|
1105
1217
|
usage:
|
|
1106
1218
|
agentnote init set up hooks, workflow, and notes auto-fetch
|
|
@@ -1131,6 +1243,9 @@ switch (command) {
|
|
|
1131
1243
|
case "status":
|
|
1132
1244
|
await status();
|
|
1133
1245
|
break;
|
|
1246
|
+
case "session":
|
|
1247
|
+
await session(args[0]);
|
|
1248
|
+
break;
|
|
1134
1249
|
case "hook":
|
|
1135
1250
|
await hook();
|
|
1136
1251
|
break;
|