agenthud 0.8.4 → 0.9.0
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/README.md +106 -52
- package/dist/index.js +2 -1
- package/dist/{main-WBZ2KBF2.js → main-NHQJ23YJ.js} +969 -251
- package/dist/templates/summary-prompt.md +12 -0
- package/package.json +1 -1
- package/scripts/diff-heap.ts +74 -0
- package/scripts/memcheck-minimal.tsx +41 -0
- package/scripts/memcheck-ui.tsx +47 -0
- package/scripts/memcheck.ts +51 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
The following is an activity log of work done with Claude Code. Summarize it concisely in English using the format below.
|
|
2
|
+
|
|
3
|
+
## Completed Work
|
|
4
|
+
- (What was accomplished — focus on Response entries)
|
|
5
|
+
|
|
6
|
+
## Notable Changes
|
|
7
|
+
- (Which files were modified and how — from Edit/Write entries)
|
|
8
|
+
|
|
9
|
+
## Commits
|
|
10
|
+
- (Summary of the ◆ commit lines)
|
|
11
|
+
|
|
12
|
+
Activity log:
|
package/package.json
CHANGED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Compare two heap snapshots and show which object types grew the most.
|
|
3
|
+
|
|
4
|
+
import { readFileSync } from "node:fs";
|
|
5
|
+
|
|
6
|
+
const earlyPath = process.argv[2] ?? "/tmp/agenthud-early.heapsnapshot";
|
|
7
|
+
const latePath = process.argv[3] ?? "/tmp/agenthud-late.heapsnapshot";
|
|
8
|
+
|
|
9
|
+
interface Snapshot {
|
|
10
|
+
snapshot: {
|
|
11
|
+
node_count: number;
|
|
12
|
+
meta: {
|
|
13
|
+
node_fields: string[];
|
|
14
|
+
node_types: (string | string[])[];
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
nodes: number[];
|
|
18
|
+
strings: string[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function countByType(path: string): Map<string, { count: number; size: number }> {
|
|
22
|
+
const raw = JSON.parse(readFileSync(path, "utf-8")) as Snapshot;
|
|
23
|
+
const fields = raw.snapshot.meta.node_fields;
|
|
24
|
+
const typeEnum = raw.snapshot.meta.node_types[fields.indexOf("type")] as string[];
|
|
25
|
+
const fieldCount = fields.length;
|
|
26
|
+
const nameIdx = fields.indexOf("name");
|
|
27
|
+
const typeIdx = fields.indexOf("type");
|
|
28
|
+
const sizeIdx = fields.indexOf("self_size");
|
|
29
|
+
|
|
30
|
+
const counts = new Map<string, { count: number; size: number }>();
|
|
31
|
+
for (let i = 0; i < raw.nodes.length; i += fieldCount) {
|
|
32
|
+
const type = typeEnum[raw.nodes[i + typeIdx]];
|
|
33
|
+
const name = raw.strings[raw.nodes[i + nameIdx]];
|
|
34
|
+
const size = raw.nodes[i + sizeIdx];
|
|
35
|
+
const key = `${type}|${name}`;
|
|
36
|
+
const entry = counts.get(key) ?? { count: 0, size: 0 };
|
|
37
|
+
entry.count++;
|
|
38
|
+
entry.size += size;
|
|
39
|
+
counts.set(key, entry);
|
|
40
|
+
}
|
|
41
|
+
return counts;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const early = countByType(earlyPath);
|
|
45
|
+
const late = countByType(latePath);
|
|
46
|
+
|
|
47
|
+
interface Diff {
|
|
48
|
+
key: string;
|
|
49
|
+
earlyCount: number;
|
|
50
|
+
lateCount: number;
|
|
51
|
+
countDelta: number;
|
|
52
|
+
sizeDelta: number;
|
|
53
|
+
}
|
|
54
|
+
const diffs: Diff[] = [];
|
|
55
|
+
|
|
56
|
+
for (const [key, lateVal] of late) {
|
|
57
|
+
const earlyVal = early.get(key) ?? { count: 0, size: 0 };
|
|
58
|
+
diffs.push({
|
|
59
|
+
key,
|
|
60
|
+
earlyCount: earlyVal.count,
|
|
61
|
+
lateCount: lateVal.count,
|
|
62
|
+
countDelta: lateVal.count - earlyVal.count,
|
|
63
|
+
sizeDelta: lateVal.size - earlyVal.size,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
diffs.sort((a, b) => b.sizeDelta - a.sizeDelta);
|
|
68
|
+
console.log("Top growers by total size delta:");
|
|
69
|
+
console.log("size_delta_kb\tcount_delta\tearly\tlate\ttype|name");
|
|
70
|
+
for (const d of diffs.slice(0, 40)) {
|
|
71
|
+
console.log(
|
|
72
|
+
`${(d.sizeDelta / 1024).toFixed(0).padStart(8)}\t${d.countDelta.toString().padStart(8)}\t${d.earlyCount.toString().padStart(5)}\t${d.lateCount.toString().padStart(5)}\t${d.key}`,
|
|
73
|
+
);
|
|
74
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Minimal Ink test: only spinner-style re-renders, no other state.
|
|
3
|
+
|
|
4
|
+
import { Text } from "ink";
|
|
5
|
+
import { render } from "ink-testing-library";
|
|
6
|
+
import React, { useEffect, useState } from "react";
|
|
7
|
+
|
|
8
|
+
const seconds = Number(process.argv[2] ?? 60);
|
|
9
|
+
const mb = (b: number) => (b / 1024 / 1024).toFixed(1);
|
|
10
|
+
const printMem = (label: string) => {
|
|
11
|
+
const m = process.memoryUsage();
|
|
12
|
+
console.log(
|
|
13
|
+
`${label.padEnd(20)} rss=${mb(m.rss)}MB heap=${mb(m.heapUsed)}/${mb(m.heapTotal)}MB ext=${mb(m.external)}MB`,
|
|
14
|
+
);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
function App() {
|
|
18
|
+
const [i, setI] = useState(0);
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
const t = setInterval(() => setI((v) => v + 1), 100);
|
|
21
|
+
return () => clearInterval(t);
|
|
22
|
+
}, []);
|
|
23
|
+
return React.createElement(Text, null, `tick ${i}`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
printMem("start");
|
|
27
|
+
const instance = render(React.createElement(App));
|
|
28
|
+
printMem("after render");
|
|
29
|
+
|
|
30
|
+
const iv = setInterval(() => {
|
|
31
|
+
if (global.gc) global.gc();
|
|
32
|
+
printMem(`t=${process.uptime().toFixed(0)}s`);
|
|
33
|
+
}, 5_000);
|
|
34
|
+
|
|
35
|
+
setTimeout(() => {
|
|
36
|
+
clearInterval(iv);
|
|
37
|
+
instance.unmount();
|
|
38
|
+
if (global.gc) global.gc();
|
|
39
|
+
printMem("end");
|
|
40
|
+
process.exit(0);
|
|
41
|
+
}, seconds * 1000);
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Render App with ink-testing-library and watch memory while time passes.
|
|
3
|
+
// Spinner re-renders every 100ms, so this simulates the watch loop.
|
|
4
|
+
|
|
5
|
+
import { writeHeapSnapshot } from "node:v8";
|
|
6
|
+
import { render } from "ink-testing-library";
|
|
7
|
+
import React from "react";
|
|
8
|
+
import { App } from "../src/ui/App.js";
|
|
9
|
+
|
|
10
|
+
const seconds = Number(process.argv[2] ?? 60);
|
|
11
|
+
const mb = (b: number) => (b / 1024 / 1024).toFixed(1);
|
|
12
|
+
|
|
13
|
+
const printMem = (label: string) => {
|
|
14
|
+
const m = process.memoryUsage();
|
|
15
|
+
console.log(
|
|
16
|
+
`${label.padEnd(20)} rss=${mb(m.rss)}MB heap=${mb(m.heapUsed)}/${mb(m.heapTotal)}MB ext=${mb(m.external)}MB`,
|
|
17
|
+
);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
printMem("start");
|
|
21
|
+
|
|
22
|
+
const instance = render(React.createElement(App, { mode: "watch" }));
|
|
23
|
+
|
|
24
|
+
printMem("after render");
|
|
25
|
+
|
|
26
|
+
const interval = setInterval(() => {
|
|
27
|
+
if (global.gc) global.gc();
|
|
28
|
+
printMem(`t=${process.uptime().toFixed(0)}s`);
|
|
29
|
+
}, 5_000);
|
|
30
|
+
|
|
31
|
+
// Snapshot at start (after warm-up) and end so we can diff
|
|
32
|
+
setTimeout(() => {
|
|
33
|
+
if (global.gc) global.gc();
|
|
34
|
+
const p = writeHeapSnapshot("/tmp/agenthud-early.heapsnapshot");
|
|
35
|
+
console.log(`early snapshot: ${p}`);
|
|
36
|
+
}, 5_000);
|
|
37
|
+
|
|
38
|
+
setTimeout(() => {
|
|
39
|
+
clearInterval(interval);
|
|
40
|
+
if (global.gc) global.gc();
|
|
41
|
+
const p = writeHeapSnapshot("/tmp/agenthud-late.heapsnapshot");
|
|
42
|
+
console.log(`late snapshot: ${p}`);
|
|
43
|
+
instance.unmount();
|
|
44
|
+
if (global.gc) global.gc();
|
|
45
|
+
printMem("end");
|
|
46
|
+
process.exit(0);
|
|
47
|
+
}, seconds * 1000);
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Run data-layer loop and watch memory growth.
|
|
3
|
+
// Usage: tsx scripts/memcheck.ts [iterations]
|
|
4
|
+
|
|
5
|
+
import { loadGlobalConfig } from "../src/config/globalConfig.js";
|
|
6
|
+
import { parseGitCommits } from "../src/data/gitCommits.js";
|
|
7
|
+
import { parseSessionHistory } from "../src/data/sessionHistory.js";
|
|
8
|
+
import { discoverSessions } from "../src/data/sessions.js";
|
|
9
|
+
|
|
10
|
+
const iterations = Number(process.argv[2] ?? 1000);
|
|
11
|
+
|
|
12
|
+
const mb = (b: number) => (b / 1024 / 1024).toFixed(1);
|
|
13
|
+
const printMem = (label: string) => {
|
|
14
|
+
const m = process.memoryUsage();
|
|
15
|
+
console.log(
|
|
16
|
+
`${label.padEnd(20)} rss=${mb(m.rss)}MB heap=${mb(m.heapUsed)}/${mb(m.heapTotal)}MB ext=${mb(m.external)}MB`,
|
|
17
|
+
);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const config = loadGlobalConfig();
|
|
21
|
+
printMem("start");
|
|
22
|
+
|
|
23
|
+
const tree = discoverSessions(config);
|
|
24
|
+
const session = tree.sessions[0];
|
|
25
|
+
if (!session) {
|
|
26
|
+
console.error("No sessions found");
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
console.log(`Using session: ${session.projectName}/${session.id}`);
|
|
30
|
+
console.log(`File: ${session.filePath}`);
|
|
31
|
+
|
|
32
|
+
printMem("after discover");
|
|
33
|
+
|
|
34
|
+
for (let i = 1; i <= iterations; i++) {
|
|
35
|
+
// Simulate what refresh() + git effect do every cycle
|
|
36
|
+
discoverSessions(config);
|
|
37
|
+
parseSessionHistory(session.filePath);
|
|
38
|
+
if (session.projectPath) {
|
|
39
|
+
const today = new Date();
|
|
40
|
+
const day = new Date(today.getFullYear(), today.getMonth(), today.getDate());
|
|
41
|
+
parseGitCommits(session.projectPath, day);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (i % 100 === 0) {
|
|
45
|
+
if (global.gc) global.gc();
|
|
46
|
+
printMem(`iter ${i}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (global.gc) global.gc();
|
|
51
|
+
printMem("end");
|