@towles/tool 0.0.41 → 0.0.49
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 +67 -109
- package/package.json +51 -41
- package/src/commands/base.ts +3 -18
- package/src/commands/config.ts +9 -8
- package/src/commands/doctor.ts +4 -1
- package/src/commands/gh/branch-clean.ts +10 -4
- package/src/commands/gh/branch.ts +6 -3
- package/src/commands/gh/pr.ts +10 -3
- package/src/commands/graph-template.html +1214 -0
- package/src/commands/graph.test.ts +176 -0
- package/src/commands/graph.ts +970 -0
- package/src/commands/install.ts +8 -2
- package/src/commands/journal/daily-notes.ts +9 -5
- package/src/commands/journal/meeting.ts +12 -6
- package/src/commands/journal/note.ts +12 -6
- package/src/commands/ralph/plan/add.ts +75 -0
- package/src/commands/ralph/plan/done.ts +82 -0
- package/src/commands/ralph/{task → plan}/list.test.ts +5 -5
- package/src/commands/ralph/{task → plan}/list.ts +28 -39
- package/src/commands/ralph/plan/remove.ts +71 -0
- package/src/commands/ralph/run.test.ts +521 -0
- package/src/commands/ralph/run.ts +126 -189
- package/src/commands/ralph/show.ts +88 -0
- package/src/config/settings.ts +8 -27
- package/src/{commands/ralph/lib → lib/ralph}/execution.ts +4 -14
- package/src/lib/ralph/formatter.ts +238 -0
- package/src/{commands/ralph/lib → lib/ralph}/state.ts +17 -42
- package/src/utils/date-utils.test.ts +2 -1
- package/src/utils/date-utils.ts +2 -2
- package/LICENSE.md +0 -20
- package/src/commands/index.ts +0 -55
- package/src/commands/observe/graph.test.ts +0 -89
- package/src/commands/observe/graph.ts +0 -1640
- package/src/commands/observe/report.ts +0 -166
- package/src/commands/observe/session.ts +0 -385
- package/src/commands/observe/setup.ts +0 -180
- package/src/commands/observe/status.ts +0 -146
- package/src/commands/ralph/lib/formatter.ts +0 -298
- package/src/commands/ralph/lib/marker.ts +0 -108
- package/src/commands/ralph/marker/create.ts +0 -23
- package/src/commands/ralph/plan.ts +0 -73
- package/src/commands/ralph/progress.ts +0 -44
- package/src/commands/ralph/ralph.test.ts +0 -673
- package/src/commands/ralph/task/add.ts +0 -105
- package/src/commands/ralph/task/done.ts +0 -73
- package/src/commands/ralph/task/remove.ts +0 -62
- package/src/config/context.ts +0 -7
- package/src/constants.ts +0 -3
- package/src/utils/anthropic/types.ts +0 -158
- package/src/utils/exec.ts +0 -8
- package/src/utils/git/git.ts +0 -25
- /package/src/{commands → lib}/journal/utils.ts +0 -0
- /package/src/{commands/ralph/lib → lib/ralph}/index.ts +0 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for graph command --days filtering and bar chart data
|
|
3
|
+
*/
|
|
4
|
+
import { describe, it, expect } from "vitest";
|
|
5
|
+
import { calculateCutoffMs, filterByDays, analyzeSession } from "./graph.js";
|
|
6
|
+
|
|
7
|
+
describe("graph --days filtering", () => {
|
|
8
|
+
describe("calculateCutoffMs", () => {
|
|
9
|
+
it("returns 0 when days <= 0", () => {
|
|
10
|
+
expect(calculateCutoffMs(0)).toBe(0);
|
|
11
|
+
expect(calculateCutoffMs(-1)).toBe(0);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("returns cutoff timestamp for positive days", () => {
|
|
15
|
+
const now = Date.now();
|
|
16
|
+
const cutoff = calculateCutoffMs(7);
|
|
17
|
+
// Should be roughly 7 days ago (within 100ms tolerance for test execution time)
|
|
18
|
+
const expected = now - 7 * 24 * 60 * 60 * 1000;
|
|
19
|
+
expect(Math.abs(cutoff - expected)).toBeLessThan(100);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe("filterByDays", () => {
|
|
24
|
+
it("filters sessions older than N days when days > 0", () => {
|
|
25
|
+
const now = Date.now();
|
|
26
|
+
const sessions = [
|
|
27
|
+
{ mtime: now - 1 * 24 * 60 * 60 * 1000 }, // 1 day ago - included
|
|
28
|
+
{ mtime: now - 2 * 24 * 60 * 60 * 1000 }, // 2 days ago - included
|
|
29
|
+
{ mtime: now - 5 * 24 * 60 * 60 * 1000 }, // 5 days ago - excluded
|
|
30
|
+
{ mtime: now - 10 * 24 * 60 * 60 * 1000 }, // 10 days ago - excluded
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
const filtered = filterByDays(sessions, 3);
|
|
34
|
+
expect(filtered).toHaveLength(2);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("returns all sessions when days=0", () => {
|
|
38
|
+
const now = Date.now();
|
|
39
|
+
const sessions = [
|
|
40
|
+
{ mtime: now - 1 * 24 * 60 * 60 * 1000 },
|
|
41
|
+
{ mtime: now - 100 * 24 * 60 * 60 * 1000 },
|
|
42
|
+
{ mtime: now - 365 * 24 * 60 * 60 * 1000 },
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
const filtered = filterByDays(sessions, 0);
|
|
46
|
+
expect(filtered).toHaveLength(3);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("default 7 days filters correctly", () => {
|
|
50
|
+
const now = Date.now();
|
|
51
|
+
const sessions = [
|
|
52
|
+
{ mtime: now - 1 * 24 * 60 * 60 * 1000 }, // 1 day ago - included
|
|
53
|
+
{ mtime: now - 6 * 24 * 60 * 60 * 1000 }, // 6 days ago - included
|
|
54
|
+
{ mtime: now - 8 * 24 * 60 * 60 * 1000 }, // 8 days ago - excluded
|
|
55
|
+
{ mtime: now - 30 * 24 * 60 * 60 * 1000 }, // 30 days ago - excluded
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
const filtered = filterByDays(sessions, 7);
|
|
59
|
+
expect(filtered).toHaveLength(2);
|
|
60
|
+
// Verify the right sessions were kept
|
|
61
|
+
expect(filtered[0].mtime).toBeGreaterThan(now - 7 * 24 * 60 * 60 * 1000);
|
|
62
|
+
expect(filtered[1].mtime).toBeGreaterThan(now - 7 * 24 * 60 * 60 * 1000);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("--days 1 filters to today only", () => {
|
|
66
|
+
const now = Date.now();
|
|
67
|
+
const sessions = [
|
|
68
|
+
{ mtime: now - 12 * 60 * 60 * 1000 }, // 12 hours ago - included
|
|
69
|
+
{ mtime: now - 25 * 60 * 60 * 1000 }, // 25 hours ago - excluded
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
const filtered = filterByDays(sessions, 1);
|
|
73
|
+
expect(filtered).toHaveLength(1);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("preserves additional properties on items", () => {
|
|
77
|
+
const now = Date.now();
|
|
78
|
+
const sessions = [
|
|
79
|
+
{ mtime: now, sessionId: "abc", tokens: 100 },
|
|
80
|
+
{ mtime: now - 10 * 24 * 60 * 60 * 1000, sessionId: "old", tokens: 50 },
|
|
81
|
+
];
|
|
82
|
+
|
|
83
|
+
const filtered = filterByDays(sessions, 7);
|
|
84
|
+
expect(filtered).toHaveLength(1);
|
|
85
|
+
expect(filtered[0].sessionId).toBe("abc");
|
|
86
|
+
expect(filtered[0].tokens).toBe(100);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe("analyzeSession (bar chart token aggregation)", () => {
|
|
92
|
+
// Helper to create JournalEntry with message.usage structure
|
|
93
|
+
function makeEntry(model: string, inputTokens: number, outputTokens: number) {
|
|
94
|
+
return {
|
|
95
|
+
type: "assistant",
|
|
96
|
+
sessionId: "test-session",
|
|
97
|
+
timestamp: new Date().toISOString(),
|
|
98
|
+
message: {
|
|
99
|
+
role: "assistant" as const,
|
|
100
|
+
model,
|
|
101
|
+
usage: { input_tokens: inputTokens, output_tokens: outputTokens },
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
it("returns zeros for empty entries array", () => {
|
|
107
|
+
const result = analyzeSession([]);
|
|
108
|
+
expect(result.opusTokens).toBe(0);
|
|
109
|
+
expect(result.sonnetTokens).toBe(0);
|
|
110
|
+
expect(result.haikuTokens).toBe(0);
|
|
111
|
+
expect(result.inputTokens).toBe(0);
|
|
112
|
+
expect(result.outputTokens).toBe(0);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("aggregates Opus tokens correctly", () => {
|
|
116
|
+
const entries = [
|
|
117
|
+
makeEntry("claude-opus-4-20250514", 100, 50),
|
|
118
|
+
makeEntry("claude-opus-4-20250514", 200, 100),
|
|
119
|
+
];
|
|
120
|
+
const result = analyzeSession(entries);
|
|
121
|
+
expect(result.opusTokens).toBe(450); // 100+50+200+100
|
|
122
|
+
expect(result.sonnetTokens).toBe(0);
|
|
123
|
+
expect(result.haikuTokens).toBe(0);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("aggregates Sonnet tokens correctly", () => {
|
|
127
|
+
const entries = [makeEntry("claude-sonnet-4-20250514", 500, 200)];
|
|
128
|
+
const result = analyzeSession(entries);
|
|
129
|
+
expect(result.sonnetTokens).toBe(700);
|
|
130
|
+
expect(result.opusTokens).toBe(0);
|
|
131
|
+
expect(result.haikuTokens).toBe(0);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("aggregates Haiku tokens correctly", () => {
|
|
135
|
+
const entries = [makeEntry("claude-3-5-haiku-20241022", 1000, 500)];
|
|
136
|
+
const result = analyzeSession(entries);
|
|
137
|
+
expect(result.haikuTokens).toBe(1500);
|
|
138
|
+
expect(result.opusTokens).toBe(0);
|
|
139
|
+
expect(result.sonnetTokens).toBe(0);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it("aggregates mixed model tokens correctly", () => {
|
|
143
|
+
const entries = [
|
|
144
|
+
makeEntry("claude-opus-4-20250514", 100, 50),
|
|
145
|
+
makeEntry("claude-sonnet-4-20250514", 200, 100),
|
|
146
|
+
makeEntry("claude-3-5-haiku-20241022", 300, 150),
|
|
147
|
+
];
|
|
148
|
+
const result = analyzeSession(entries);
|
|
149
|
+
expect(result.opusTokens).toBe(150);
|
|
150
|
+
expect(result.sonnetTokens).toBe(300);
|
|
151
|
+
expect(result.haikuTokens).toBe(450);
|
|
152
|
+
expect(result.inputTokens).toBe(600);
|
|
153
|
+
expect(result.outputTokens).toBe(300);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it("ignores entries without message.usage", () => {
|
|
157
|
+
const entries = [
|
|
158
|
+
{ type: "user", sessionId: "x", timestamp: "", message: { role: "user" as const } },
|
|
159
|
+
makeEntry("claude-opus-4-20250514", 100, 50),
|
|
160
|
+
{ type: "tool_result", sessionId: "x", timestamp: "" },
|
|
161
|
+
];
|
|
162
|
+
const result = analyzeSession(entries);
|
|
163
|
+
expect(result.opusTokens).toBe(150);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it("calculates modelEfficiency as opus ratio", () => {
|
|
167
|
+
const entries = [
|
|
168
|
+
makeEntry("claude-opus-4-20250514", 100, 100),
|
|
169
|
+
makeEntry("claude-sonnet-4-20250514", 200, 200),
|
|
170
|
+
];
|
|
171
|
+
const result = analyzeSession(entries);
|
|
172
|
+
// Opus: 200, Sonnet: 400, Total: 600
|
|
173
|
+
// modelEfficiency = 200/600 = 0.333...
|
|
174
|
+
expect(result.modelEfficiency).toBeCloseTo(0.333, 2);
|
|
175
|
+
});
|
|
176
|
+
});
|