deepflow 0.1.84 → 0.1.86
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 +15 -3
- package/bin/install.js +47 -9
- package/hooks/df-quota-logger.js +131 -0
- package/hooks/df-statusline.js +37 -2
- package/package.json +1 -1
- package/src/commands/df/execute.md +58 -0
- package/src/commands/df/report.md +230 -0
- package/templates/config-template.yaml +7 -0
package/README.md
CHANGED
|
@@ -148,6 +148,7 @@ $ git log --oneline
|
|
|
148
148
|
| `/df:consolidate` | Deduplicate and clean up decisions.md |
|
|
149
149
|
| `/df:resume` | Session continuity briefing |
|
|
150
150
|
| `/df:update` | Update deepflow to latest |
|
|
151
|
+
| `/df:report` | Generate session cost report (tokens, cache, quota) |
|
|
151
152
|
| `/df:auto` | Autonomous mode (plan → loop → verify, no human needed) |
|
|
152
153
|
|
|
153
154
|
## File Structure
|
|
@@ -163,6 +164,9 @@ your-project/
|
|
|
163
164
|
+-- decisions.md # auto-extracted + ad-hoc decisions
|
|
164
165
|
+-- auto-report.md # morning report (autonomous mode)
|
|
165
166
|
+-- auto-memory.yaml # cross-cycle learning
|
|
167
|
+
+-- token-history.jsonl # per-render token usage (auto)
|
|
168
|
+
+-- report.json # session cost report (/df:report)
|
|
169
|
+
+-- report.md # human-readable report (/df:report)
|
|
166
170
|
+-- experiments/ # spike results (pass/fail)
|
|
167
171
|
+-- worktrees/ # isolated execution
|
|
168
172
|
+-- upload/ # one worktree per spec
|
|
@@ -190,15 +194,23 @@ your-project/
|
|
|
190
194
|
|
|
191
195
|
Deepflow's design isn't opinionated — it's a direct response to measured LLM limitations:
|
|
192
196
|
|
|
193
|
-
**Focused tasks > giant context** — LLMs lose ~2% effectiveness per 100K additional tokens, even on trivial tasks ([Chroma "Context Rot", 2025](https://research.trychroma.com/context-rot), 18 models tested). Deepflow keeps each task's context minimal and focused instead of loading the entire codebase.
|
|
197
|
+
**Focused tasks > giant context** — LLMs lose ~2% effectiveness per 100K additional tokens, even on trivial tasks ([Chroma "Context Rot", 2025](https://research.trychroma.com/context-rot), 18 models tested). Accuracy drops from 89% at 8K tokens to 25% at 1M tokens ([Augment Code, 2025](https://www.augmentcode.com/tools/context-window-wars-200k-vs-1m-token-strategies)). Deepflow keeps each task's context minimal and focused instead of loading the entire codebase.
|
|
194
198
|
|
|
195
|
-
**
|
|
199
|
+
**Search efficiency > model capability** — Coding agents spend [60% of their time searching, not coding](https://cognition.ai/blog/swe-grep) (Cognition, 2025). Input tokens dominate cost with up to [10x variance driven entirely by search efficiency](https://openreview.net/forum?id=1bUeVB3fov), not coding ability. Deepflow's LSP-first search and 3-phase explore protocol (DIVERSIFY/CONVERGE/EARLY STOP) minimize search waste.
|
|
200
|
+
|
|
201
|
+
**The framework matters more than the model** — Same model, same tasks, different orchestration: [25.6 percentage point swing](https://arxiv.org/pdf/2509.16941) on SWE-Bench Lite (GPT-4: 2.7% with naive retrieval vs 28.3% with structured orchestration). On SWE-Bench Pro, three products using the same model scored 17 problems apart on 731 issues — the only difference was how they managed context, search, and edits. Deepflow is that orchestration layer.
|
|
202
|
+
|
|
203
|
+
**Tool use > context stuffing** — Information in the middle of context has up to 40% less recall than at the start/end ([Lost in the Middle, 2024](https://arxiv.org/abs/2307.03172), Stanford/TACL). [LongMemEval](https://arxiv.org/abs/2410.10813) (ICLR 2025) found GPT-4O scoring 60-64% at full context vs 87-92% with oracle retrieval. Agents access code on-demand via LSP (`findReferences`, `incomingCalls`) and grep — always fresh, no attention dilution.
|
|
204
|
+
|
|
205
|
+
**Fresh context beats long sessions** — Every AI agent's success rate decreases after [35 minutes of equivalent task time](https://zylos.ai/research/2026-01-16-long-running-ai-agents); doubling duration quadruples failure rate. Deepflow's autonomous mode (`/df:auto`) starts a fresh context each cycle — checkpoint state, not conversation history.
|
|
206
|
+
|
|
207
|
+
**Input:output ratio matters** — Agent token ratio is [~100:1 input to output](https://manus.im/blog/Context-Engineering-for-AI-Agents-Lessons-from-Building-Manus) (Manus, 2025). Deepflow truncates ratchet output (success = zero tokens), context-forks high-ratio skills, and strips prompt sections by effort level to keep the ratio low.
|
|
196
208
|
|
|
197
209
|
**Model routing > one-size-fits-all** — Mechanical tasks with cheap models (haiku), complex tasks with powerful models (opus). Fewer tokens per task = less degradation = better results. Effort-aware context budgets strip unnecessary sections from prompts for simpler tasks.
|
|
198
210
|
|
|
199
211
|
**Prompt order follows attention** — Execute prompts follow the attention U-curve: critical instructions (task definition, failure history, success criteria) at start and end, navigable data (impact analysis, dependency context) in the middle. Distractors eliminated by design.
|
|
200
212
|
|
|
201
|
-
**LSP-powered impact analysis** — Plan-time uses `findReferences` and `incomingCalls` to map blast radius precisely. Execute-time runs a freshness check before implementing — catching callers added after planning. Grep as fallback
|
|
213
|
+
**LSP-powered impact analysis** — Plan-time uses `findReferences` and `incomingCalls` to map blast radius precisely. Execute-time runs a freshness check before implementing — catching callers added after planning. Grep as fallback — though [embedding-based retrieval has a hard mathematical ceiling](https://arxiv.org/abs/2508.21038) (Google DeepMind, 2025) that LSP doesn't share.
|
|
202
214
|
|
|
203
215
|
## Skills
|
|
204
216
|
|
package/bin/install.js
CHANGED
|
@@ -183,7 +183,7 @@ async function main() {
|
|
|
183
183
|
console.log(`${c.green}Installation complete!${c.reset}`);
|
|
184
184
|
console.log('');
|
|
185
185
|
console.log(`Installed to ${c.cyan}${CLAUDE_DIR}${c.reset}:`);
|
|
186
|
-
console.log(' commands/df/ — /df:discover, /df:debate, /df:spec, /df:plan, /df:execute, /df:verify, /df:auto, /df:note, /df:resume, /df:update');
|
|
186
|
+
console.log(' commands/df/ — /df:discover, /df:debate, /df:spec, /df:plan, /df:execute, /df:verify, /df:auto, /df:note, /df:resume, /df:update, /df:report');
|
|
187
187
|
console.log(' skills/ — gap-discovery, atomic-commits, code-completeness, browse-fetch, browse-verify');
|
|
188
188
|
console.log(' agents/ — reasoner (/df:auto — autonomous execution via /loop)');
|
|
189
189
|
if (level === 'global') {
|
|
@@ -237,6 +237,7 @@ async function configureHooks(claudeDir) {
|
|
|
237
237
|
const statuslineCmd = `node "${path.join(claudeDir, 'hooks', 'df-statusline.js')}"`;
|
|
238
238
|
const updateCheckCmd = `node "${path.join(claudeDir, 'hooks', 'df-check-update.js')}"`;
|
|
239
239
|
const consolidationCheckCmd = `node "${path.join(claudeDir, 'hooks', 'df-consolidation-check.js')}"`;
|
|
240
|
+
const quotaLoggerCmd = `node "${path.join(claudeDir, 'hooks', 'df-quota-logger.js')}"`;
|
|
240
241
|
|
|
241
242
|
let settings = {};
|
|
242
243
|
|
|
@@ -286,10 +287,10 @@ async function configureHooks(claudeDir) {
|
|
|
286
287
|
settings.hooks.SessionStart = [];
|
|
287
288
|
}
|
|
288
289
|
|
|
289
|
-
// Remove any existing deepflow update check hooks
|
|
290
|
+
// Remove any existing deepflow update check / quota logger hooks from SessionStart
|
|
290
291
|
settings.hooks.SessionStart = settings.hooks.SessionStart.filter(hook => {
|
|
291
292
|
const cmd = hook.hooks?.[0]?.command || '';
|
|
292
|
-
return !cmd.includes('df-check-update') && !cmd.includes('df-consolidation-check');
|
|
293
|
+
return !cmd.includes('df-check-update') && !cmd.includes('df-consolidation-check') && !cmd.includes('df-quota-logger');
|
|
293
294
|
});
|
|
294
295
|
|
|
295
296
|
// Add update check hook
|
|
@@ -307,8 +308,36 @@ async function configureHooks(claudeDir) {
|
|
|
307
308
|
command: consolidationCheckCmd
|
|
308
309
|
}]
|
|
309
310
|
});
|
|
311
|
+
|
|
312
|
+
// Add quota logger to SessionStart
|
|
313
|
+
settings.hooks.SessionStart.push({
|
|
314
|
+
hooks: [{
|
|
315
|
+
type: 'command',
|
|
316
|
+
command: quotaLoggerCmd
|
|
317
|
+
}]
|
|
318
|
+
});
|
|
310
319
|
log('SessionStart hook configured');
|
|
311
320
|
|
|
321
|
+
// Configure SessionEnd hook for quota logging
|
|
322
|
+
if (!settings.hooks.SessionEnd) {
|
|
323
|
+
settings.hooks.SessionEnd = [];
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Remove any existing quota logger from SessionEnd
|
|
327
|
+
settings.hooks.SessionEnd = settings.hooks.SessionEnd.filter(hook => {
|
|
328
|
+
const cmd = hook.hooks?.[0]?.command || '';
|
|
329
|
+
return !cmd.includes('df-quota-logger');
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
// Add quota logger to SessionEnd
|
|
333
|
+
settings.hooks.SessionEnd.push({
|
|
334
|
+
hooks: [{
|
|
335
|
+
type: 'command',
|
|
336
|
+
command: quotaLoggerCmd
|
|
337
|
+
}]
|
|
338
|
+
});
|
|
339
|
+
log('Quota logger configured');
|
|
340
|
+
|
|
312
341
|
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
313
342
|
}
|
|
314
343
|
|
|
@@ -489,7 +518,7 @@ async function uninstall() {
|
|
|
489
518
|
];
|
|
490
519
|
|
|
491
520
|
if (level === 'global') {
|
|
492
|
-
toRemove.push('hooks/df-statusline.js', 'hooks/df-check-update.js', 'hooks/df-consolidation-check.js', 'hooks/df-invariant-check.js');
|
|
521
|
+
toRemove.push('hooks/df-statusline.js', 'hooks/df-check-update.js', 'hooks/df-consolidation-check.js', 'hooks/df-invariant-check.js', 'hooks/df-quota-logger.js');
|
|
493
522
|
}
|
|
494
523
|
|
|
495
524
|
for (const item of toRemove) {
|
|
@@ -518,17 +547,26 @@ async function uninstall() {
|
|
|
518
547
|
if (settings.hooks?.SessionStart) {
|
|
519
548
|
settings.hooks.SessionStart = settings.hooks.SessionStart.filter(hook => {
|
|
520
549
|
const cmd = hook.hooks?.[0]?.command || '';
|
|
521
|
-
return !cmd.includes('df-check-update') && !cmd.includes('df-consolidation-check');
|
|
550
|
+
return !cmd.includes('df-check-update') && !cmd.includes('df-consolidation-check') && !cmd.includes('df-quota-logger');
|
|
522
551
|
});
|
|
523
552
|
if (settings.hooks.SessionStart.length === 0) {
|
|
524
553
|
delete settings.hooks.SessionStart;
|
|
525
554
|
}
|
|
526
|
-
|
|
527
|
-
|
|
555
|
+
}
|
|
556
|
+
if (settings.hooks?.SessionEnd) {
|
|
557
|
+
settings.hooks.SessionEnd = settings.hooks.SessionEnd.filter(hook => {
|
|
558
|
+
const cmd = hook.hooks?.[0]?.command || '';
|
|
559
|
+
return !cmd.includes('df-quota-logger');
|
|
560
|
+
});
|
|
561
|
+
if (settings.hooks.SessionEnd.length === 0) {
|
|
562
|
+
delete settings.hooks.SessionEnd;
|
|
528
563
|
}
|
|
529
|
-
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
530
|
-
console.log(` ${c.green}✓${c.reset} Removed SessionStart hook`);
|
|
531
564
|
}
|
|
565
|
+
if (settings.hooks && Object.keys(settings.hooks).length === 0) {
|
|
566
|
+
delete settings.hooks;
|
|
567
|
+
}
|
|
568
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
569
|
+
console.log(` ${c.green}✓${c.reset} Removed SessionStart/SessionEnd hooks`);
|
|
532
570
|
} catch (e) {
|
|
533
571
|
// Fail silently
|
|
534
572
|
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* deepflow quota logger
|
|
4
|
+
* Logs Anthropic API quota/usage data to ~/.claude/quota-history.jsonl
|
|
5
|
+
* Runs on SessionStart and SessionEnd events.
|
|
6
|
+
* Exits silently (code 0) on non-macOS or when Keychain token is absent.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const os = require('os');
|
|
14
|
+
const { execFileSync } = require('child_process');
|
|
15
|
+
const https = require('https');
|
|
16
|
+
|
|
17
|
+
const QUOTA_LOG = path.join(os.homedir(), '.claude', 'quota-history.jsonl');
|
|
18
|
+
|
|
19
|
+
// Only supported on macOS (Keychain access)
|
|
20
|
+
if (process.platform !== 'darwin') {
|
|
21
|
+
process.exit(0);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Spawn background process so hook returns immediately
|
|
25
|
+
if (process.argv[2] !== '--background') {
|
|
26
|
+
const { spawn } = require('child_process');
|
|
27
|
+
const child = spawn(process.execPath, [__filename, '--background'], {
|
|
28
|
+
detached: true,
|
|
29
|
+
stdio: 'ignore'
|
|
30
|
+
});
|
|
31
|
+
child.unref();
|
|
32
|
+
process.exit(0);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// --- Background process ---
|
|
36
|
+
|
|
37
|
+
async function main() {
|
|
38
|
+
try {
|
|
39
|
+
const token = getToken();
|
|
40
|
+
if (!token) {
|
|
41
|
+
process.exit(0);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const data = await fetchQuota(token);
|
|
45
|
+
if (!data) {
|
|
46
|
+
process.exit(0);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
appendLog(data);
|
|
50
|
+
} catch (_e) {
|
|
51
|
+
// Never break session hooks
|
|
52
|
+
}
|
|
53
|
+
process.exit(0);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function getToken() {
|
|
57
|
+
try {
|
|
58
|
+
const raw = execFileSync(
|
|
59
|
+
'security',
|
|
60
|
+
['find-generic-password', '-s', 'Claude Code-credentials', '-w'],
|
|
61
|
+
{ stdio: ['ignore', 'pipe', 'ignore'], timeout: 5000 }
|
|
62
|
+
).toString().trim();
|
|
63
|
+
|
|
64
|
+
if (!raw) return null;
|
|
65
|
+
|
|
66
|
+
// The stored value may be a JSON blob with an access_token field
|
|
67
|
+
try {
|
|
68
|
+
const parsed = JSON.parse(raw);
|
|
69
|
+
return parsed.access_token || parsed.token || raw;
|
|
70
|
+
} catch (_e) {
|
|
71
|
+
return raw;
|
|
72
|
+
}
|
|
73
|
+
} catch (_e) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function fetchQuota(token) {
|
|
79
|
+
return new Promise((resolve) => {
|
|
80
|
+
const options = {
|
|
81
|
+
hostname: 'api.anthropic.com',
|
|
82
|
+
path: '/v1/organizations/me/usage',
|
|
83
|
+
method: 'GET',
|
|
84
|
+
headers: {
|
|
85
|
+
'Authorization': `Bearer ${token}`,
|
|
86
|
+
'anthropic-version': '2023-06-01',
|
|
87
|
+
'anthropic-beta': 'oauth-2025-04-20'
|
|
88
|
+
},
|
|
89
|
+
timeout: 10000
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const req = https.request(options, (res) => {
|
|
93
|
+
let body = '';
|
|
94
|
+
res.on('data', chunk => { body += chunk; });
|
|
95
|
+
res.on('end', () => {
|
|
96
|
+
try {
|
|
97
|
+
const json = JSON.parse(body);
|
|
98
|
+
resolve({ statusCode: res.statusCode, data: json });
|
|
99
|
+
} catch (_e) {
|
|
100
|
+
resolve({ statusCode: res.statusCode, raw: body.slice(0, 500) });
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
req.on('error', () => resolve(null));
|
|
106
|
+
req.on('timeout', () => { req.destroy(); resolve(null); });
|
|
107
|
+
req.end();
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function appendLog(payload) {
|
|
112
|
+
try {
|
|
113
|
+
const logDir = path.dirname(QUOTA_LOG);
|
|
114
|
+
if (!fs.existsSync(logDir)) {
|
|
115
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const event = process.env.CLAUDE_HOOK_EVENT || 'unknown';
|
|
119
|
+
const entry = JSON.stringify({
|
|
120
|
+
timestamp: new Date().toISOString(),
|
|
121
|
+
event,
|
|
122
|
+
...payload
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
fs.appendFileSync(QUOTA_LOG, entry + '\n');
|
|
126
|
+
} catch (_e) {
|
|
127
|
+
// Fail silently
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
main();
|
package/hooks/df-statusline.js
CHANGED
|
@@ -53,13 +53,13 @@ function buildStatusLine(data) {
|
|
|
53
53
|
parts.push(`${colors.cyan}${project}${colors.reset}`);
|
|
54
54
|
|
|
55
55
|
// Context window meter (Claude Code format: data.context_window)
|
|
56
|
-
const contextMeter = buildContextMeter(data.context_window || {});
|
|
56
|
+
const contextMeter = buildContextMeter(data.context_window || {}, data);
|
|
57
57
|
parts.push(contextMeter);
|
|
58
58
|
|
|
59
59
|
return parts.join(` ${colors.dim}│${colors.reset} `);
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
function buildContextMeter(contextWindow) {
|
|
62
|
+
function buildContextMeter(contextWindow, data) {
|
|
63
63
|
// Use pre-calculated percentage if available
|
|
64
64
|
let percentage = contextWindow.used_percentage || 0;
|
|
65
65
|
|
|
@@ -77,6 +77,9 @@ function buildContextMeter(contextWindow) {
|
|
|
77
77
|
// Write context usage to file for deepflow commands
|
|
78
78
|
writeContextUsage(percentage);
|
|
79
79
|
|
|
80
|
+
// Write token history for instrumentation
|
|
81
|
+
writeTokenHistory(contextWindow, data);
|
|
82
|
+
|
|
80
83
|
// Build 10-segment bar
|
|
81
84
|
const segments = 10;
|
|
82
85
|
const filled = Math.round((percentage / 100) * segments);
|
|
@@ -124,3 +127,35 @@ function writeContextUsage(percentage) {
|
|
|
124
127
|
// Fail silently
|
|
125
128
|
}
|
|
126
129
|
}
|
|
130
|
+
|
|
131
|
+
function writeTokenHistory(contextWindow, data) {
|
|
132
|
+
try {
|
|
133
|
+
const deepflowDir = path.join(process.cwd(), '.deepflow');
|
|
134
|
+
if (!fs.existsSync(deepflowDir)) {
|
|
135
|
+
fs.mkdirSync(deepflowDir, { recursive: true });
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const usage = contextWindow.current_usage || {};
|
|
139
|
+
const timestamp = new Date().toISOString();
|
|
140
|
+
const model = data.model?.id || data.model?.display_name || 'unknown';
|
|
141
|
+
const sessionId = data.session_id || 'unknown';
|
|
142
|
+
const contextWindowSize = contextWindow.context_window_size || 0;
|
|
143
|
+
const usedPercentage = contextWindow.used_percentage || 0;
|
|
144
|
+
|
|
145
|
+
const record = {
|
|
146
|
+
timestamp,
|
|
147
|
+
input_tokens: usage.input_tokens || 0,
|
|
148
|
+
cache_creation_input_tokens: usage.cache_creation_input_tokens || 0,
|
|
149
|
+
cache_read_input_tokens: usage.cache_read_input_tokens || 0,
|
|
150
|
+
context_window_size: contextWindowSize,
|
|
151
|
+
used_percentage: usedPercentage,
|
|
152
|
+
model,
|
|
153
|
+
session_id: sessionId
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const tokenHistoryPath = path.join(deepflowDir, 'token-history.jsonl');
|
|
157
|
+
fs.appendFileSync(tokenHistoryPath, JSON.stringify(record) + '\n');
|
|
158
|
+
} catch (e) {
|
|
159
|
+
// Fail silently
|
|
160
|
+
}
|
|
161
|
+
}
|
package/package.json
CHANGED
|
@@ -123,6 +123,13 @@ Context ≥50%: checkpoint and exit.
|
|
|
123
123
|
|
|
124
124
|
Before spawning: `TaskUpdate(taskId: native_id, status: "in_progress")` — activates UI spinner.
|
|
125
125
|
|
|
126
|
+
**Token tracking — record start:**
|
|
127
|
+
```
|
|
128
|
+
start_percentage = !`cat .deepflow/context.json 2>/dev/null | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('percentage',''))" 2>/dev/null || echo ''`
|
|
129
|
+
start_timestamp = !`date -u +%Y-%m-%dT%H:%M:%SZ`
|
|
130
|
+
```
|
|
131
|
+
Store both values in memory (keyed by task_id) for use after ratchet completes. Omit if context.json unavailable.
|
|
132
|
+
|
|
126
133
|
**NEVER use `isolation: "worktree"` on Task calls.** Deepflow manages a shared worktree so wave 2 sees wave 1 commits.
|
|
127
134
|
|
|
128
135
|
**Spawn ALL ready tasks in ONE message** — EXCEPT file conflicts (see below).
|
|
@@ -181,6 +188,57 @@ After ratchet checks complete, truncate command output for context efficiency:
|
|
|
181
188
|
- **Test failure:** Include failed test name(s) + last 20 lines of test output
|
|
182
189
|
- **Typecheck/lint failure:** Include error count + first 5 errors only
|
|
183
190
|
|
|
191
|
+
**Token tracking — write result (on ratchet pass):**
|
|
192
|
+
|
|
193
|
+
After all checks pass, compute and write the token block to `.deepflow/results/T{N}.yaml`:
|
|
194
|
+
|
|
195
|
+
```
|
|
196
|
+
end_percentage = !`cat .deepflow/context.json 2>/dev/null | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('percentage',''))" 2>/dev/null || echo ''`
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Parse `.deepflow/token-history.jsonl` to sum token fields for lines whose `timestamp` falls between `start_timestamp` and `end_timestamp` (ISO 8601 compare):
|
|
200
|
+
```bash
|
|
201
|
+
python3 - <<'EOF'
|
|
202
|
+
import json, sys
|
|
203
|
+
from datetime import datetime, timezone
|
|
204
|
+
|
|
205
|
+
start = "REPLACE_start_timestamp"
|
|
206
|
+
end = "REPLACE_end_timestamp" # current time: date -u +%Y-%m-%dT%H:%M:%SZ
|
|
207
|
+
|
|
208
|
+
totals = {"input_tokens": 0, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0}
|
|
209
|
+
try:
|
|
210
|
+
with open(".deepflow/token-history.jsonl") as f:
|
|
211
|
+
for line in f:
|
|
212
|
+
entry = json.loads(line)
|
|
213
|
+
ts = entry.get("timestamp", "")
|
|
214
|
+
if start <= ts <= end:
|
|
215
|
+
for k in totals:
|
|
216
|
+
totals[k] += entry.get(k, 0)
|
|
217
|
+
except FileNotFoundError:
|
|
218
|
+
sys.exit(0)
|
|
219
|
+
|
|
220
|
+
print(json.dumps(totals))
|
|
221
|
+
EOF
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Append (or create) `.deepflow/results/T{N}.yaml` with the following block. Use shell injection to read the existing file first:
|
|
225
|
+
```
|
|
226
|
+
!`cat .deepflow/results/T{N}.yaml 2>/dev/null || echo ''`
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Write the `tokens` block:
|
|
230
|
+
```yaml
|
|
231
|
+
tokens:
|
|
232
|
+
start_percentage: {start_percentage}
|
|
233
|
+
end_percentage: {end_percentage}
|
|
234
|
+
delta_percentage: {end_percentage - start_percentage}
|
|
235
|
+
input_tokens: {sum from jsonl}
|
|
236
|
+
cache_creation_input_tokens: {sum from jsonl}
|
|
237
|
+
cache_read_input_tokens: {sum from jsonl}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
**Omit entirely if:** context.json was unavailable at start OR end, OR token-history.jsonl is missing, OR python3 is unavailable. Never fail the ratchet due to token tracking errors.
|
|
241
|
+
|
|
184
242
|
**Evaluate:** All pass + no violations → commit stands. Any failure → attempt partial salvage before reverting:
|
|
185
243
|
|
|
186
244
|
**Partial salvage protocol:**
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: df:report
|
|
3
|
+
description: Generate session cost report with token usage, cache hit ratio, per-task costs, and quota impact
|
|
4
|
+
allowed-tools: [Read, Write, Bash]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# /df:report — Session Cost Report
|
|
8
|
+
|
|
9
|
+
## Orchestrator Role
|
|
10
|
+
|
|
11
|
+
You aggregate token usage data from multiple sources and produce a structured report.
|
|
12
|
+
|
|
13
|
+
**NEVER:** Spawn agents, use Task tool, use AskUserQuestion, run git, use EnterPlanMode, use ExitPlanMode
|
|
14
|
+
|
|
15
|
+
**ONLY:** Read data files, compute aggregates, write `.deepflow/report.json` and `.deepflow/report.md`
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Purpose
|
|
20
|
+
|
|
21
|
+
Produce a cost and context report for the current session. Reads token-history.jsonl, quota-history.jsonl, per-task YAML result files, and auto-memory.yaml. Outputs a machine-readable JSON report and a human-readable Markdown summary.
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
/df:report
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
No arguments. Operates on `.deepflow/` data written by the statusline hook, execute command, and quota logger.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Behavior
|
|
34
|
+
|
|
35
|
+
### 1. LOAD DATA SOURCES
|
|
36
|
+
|
|
37
|
+
Read each source gracefully — if a file does not exist, treat it as empty and continue.
|
|
38
|
+
|
|
39
|
+
**a. Token history** — `.deepflow/token-history.jsonl`
|
|
40
|
+
|
|
41
|
+
Parse each newline-delimited JSON object. Each line has fields:
|
|
42
|
+
`timestamp`, `input_tokens`, `cache_creation_input_tokens`, `cache_read_input_tokens`, `context_window_size`, `used_percentage`, `model`, `session_id`
|
|
43
|
+
|
|
44
|
+
Shell injection (use output directly):
|
|
45
|
+
- `` !`cat .deepflow/token-history.jsonl 2>/dev/null || echo ''` ``
|
|
46
|
+
|
|
47
|
+
Aggregate across all lines:
|
|
48
|
+
- `total_input_tokens` = sum of `input_tokens`
|
|
49
|
+
- `total_cache_creation` = sum of `cache_creation_input_tokens`
|
|
50
|
+
- `total_cache_read` = sum of `cache_read_input_tokens`
|
|
51
|
+
- `cache_hit_ratio` = `total_cache_read / (total_input_tokens + total_cache_creation + total_cache_read)` — clamp to `[0, 1]`, default `0` if denominator is 0
|
|
52
|
+
- `peak_context_percentage` = max of `used_percentage` across all lines
|
|
53
|
+
- `model` = value from the most recent line (last line)
|
|
54
|
+
|
|
55
|
+
**b. Quota history** — `~/.claude/quota-history.jsonl`
|
|
56
|
+
|
|
57
|
+
Parse the last 5 lines. Each line has `timestamp`, `event`, and API response payload fields.
|
|
58
|
+
|
|
59
|
+
Shell injection:
|
|
60
|
+
- `` !`tail -5 ~/.claude/quota-history.jsonl 2>/dev/null || echo ''` ``
|
|
61
|
+
|
|
62
|
+
Extract the most recent quota entry. If the file does not exist or is empty, set `quota.available = false`.
|
|
63
|
+
|
|
64
|
+
**c. Per-task results** — `.deepflow/results/T*.yaml`
|
|
65
|
+
|
|
66
|
+
Shell injection:
|
|
67
|
+
- `` !`ls .deepflow/results/T*.yaml 2>/dev/null || echo ''` ``
|
|
68
|
+
|
|
69
|
+
For each YAML file found, read and extract the `tokens` block:
|
|
70
|
+
```yaml
|
|
71
|
+
tokens:
|
|
72
|
+
start_percentage: N
|
|
73
|
+
end_percentage: N
|
|
74
|
+
delta_percentage: N
|
|
75
|
+
input_tokens: N
|
|
76
|
+
cache_creation_input_tokens: N
|
|
77
|
+
cache_read_input_tokens: N
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Derive `task_id` from the filename (e.g., `T3.yaml` → `"T3"`).
|
|
81
|
+
|
|
82
|
+
If a file has no `tokens` block, skip it without error.
|
|
83
|
+
|
|
84
|
+
**d. Session metadata** — `.deepflow/auto-memory.yaml`
|
|
85
|
+
|
|
86
|
+
Shell injection:
|
|
87
|
+
- `` !`cat .deepflow/auto-memory.yaml 2>/dev/null || echo ''` ``
|
|
88
|
+
|
|
89
|
+
Read for context (session_id, start time, etc.) if available. Optional — do not fail if absent.
|
|
90
|
+
|
|
91
|
+
### 2. COMPUTE AGGREGATES
|
|
92
|
+
|
|
93
|
+
Using data from step 1:
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
total_tokens_all = total_input_tokens + total_cache_creation + total_cache_read
|
|
97
|
+
cache_hit_ratio = total_cache_read / total_tokens_all (0 if total_tokens_all == 0)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Round `cache_hit_ratio` to 4 decimal places.
|
|
101
|
+
|
|
102
|
+
### 3. WRITE `.deepflow/report.json`
|
|
103
|
+
|
|
104
|
+
Generate an ISO 8601 timestamp for the `generated` field (current time).
|
|
105
|
+
|
|
106
|
+
Schema:
|
|
107
|
+
```json
|
|
108
|
+
{
|
|
109
|
+
"version": 1,
|
|
110
|
+
"generated": "2026-03-17T12:00:00Z",
|
|
111
|
+
"session_summary": {
|
|
112
|
+
"total_input_tokens": 0,
|
|
113
|
+
"total_cache_creation": 0,
|
|
114
|
+
"total_cache_read": 0,
|
|
115
|
+
"cache_hit_ratio": 0.0,
|
|
116
|
+
"peak_context_percentage": 0,
|
|
117
|
+
"model": "claude-sonnet-4-5"
|
|
118
|
+
},
|
|
119
|
+
"tasks": [
|
|
120
|
+
{
|
|
121
|
+
"task_id": "T1",
|
|
122
|
+
"start_percentage": 0,
|
|
123
|
+
"end_percentage": 0,
|
|
124
|
+
"delta_percentage": 0,
|
|
125
|
+
"input_tokens": 0,
|
|
126
|
+
"cache_creation": 0,
|
|
127
|
+
"cache_read": 0
|
|
128
|
+
}
|
|
129
|
+
],
|
|
130
|
+
"quota": {
|
|
131
|
+
"available": false
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Rules:
|
|
137
|
+
- `version` is always `1`
|
|
138
|
+
- `tasks` is an empty array `[]` if no task result files were found or none had a `tokens` block
|
|
139
|
+
- `quota.available` is `false` if quota data is missing or could not be read; `true` with additional fields from the API payload if data was found
|
|
140
|
+
- All token fields are integers >= 0
|
|
141
|
+
- `cache_hit_ratio` is a float in `[0, 1]`
|
|
142
|
+
|
|
143
|
+
### 4. WRITE `.deepflow/report.md`
|
|
144
|
+
|
|
145
|
+
Generate a human-readable Markdown report. Use actual values from step 2.
|
|
146
|
+
|
|
147
|
+
Required section headings (exact text):
|
|
148
|
+
|
|
149
|
+
```markdown
|
|
150
|
+
## Session Summary
|
|
151
|
+
|
|
152
|
+
| Metric | Value |
|
|
153
|
+
|--------|-------|
|
|
154
|
+
| Model | {model} |
|
|
155
|
+
| Total Input Tokens | {total_input_tokens} |
|
|
156
|
+
| Cache Creation Tokens | {total_cache_creation} |
|
|
157
|
+
| Cache Read Tokens | {total_cache_read} |
|
|
158
|
+
| Cache Hit Ratio | {cache_hit_ratio} ({percentage}%) |
|
|
159
|
+
| Peak Context Usage | {peak_context_percentage}% |
|
|
160
|
+
|
|
161
|
+
## Per-Task Costs
|
|
162
|
+
|
|
163
|
+
| Task | Start % | End % | Delta % | Input Tokens | Cache Creation | Cache Read |
|
|
164
|
+
|------|---------|-------|---------|-------------|----------------|------------|
|
|
165
|
+
| T1 | 0 | 5 | 5 | 12000 | 3000 | 1000 |
|
|
166
|
+
|
|
167
|
+
_(No task data available)_ if tasks array is empty
|
|
168
|
+
|
|
169
|
+
## Quota Impact
|
|
170
|
+
|
|
171
|
+
{quota data table or "Not available (non-macOS or no token)"}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
For **Quota Impact**:
|
|
175
|
+
- If `quota.available = true`: render a table with the quota fields from the API payload
|
|
176
|
+
- If `quota.available = false`: write exactly `Not available (non-macOS or no token)`
|
|
177
|
+
|
|
178
|
+
### 5. CONFIRM
|
|
179
|
+
|
|
180
|
+
Report to the user:
|
|
181
|
+
|
|
182
|
+
```
|
|
183
|
+
Report generated:
|
|
184
|
+
.deepflow/report.json — machine-readable (version=1)
|
|
185
|
+
.deepflow/report.md — human-readable summary
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
If any data source was missing, list them as a note:
|
|
189
|
+
```
|
|
190
|
+
Note: Missing data sources: token-history.jsonl, quota-history.jsonl
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## Rules
|
|
196
|
+
|
|
197
|
+
- **Graceful degradation** — any missing file yields zero/empty values for that source; never error out
|
|
198
|
+
- **No hallucination** — only write values derived from actual file contents; use 0 for missing numeric fields
|
|
199
|
+
- **Idempotent** — re-running overwrites `.deepflow/report.json` and `.deepflow/report.md` with fresh data
|
|
200
|
+
- **cache_hit_ratio always in [0,1]** — clamp if arithmetic produces out-of-range value
|
|
201
|
+
- **ISO 8601 timestamps** — `generated` field uses UTC
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Example
|
|
206
|
+
|
|
207
|
+
```
|
|
208
|
+
USER: /df:report
|
|
209
|
+
|
|
210
|
+
CLAUDE: [Reads .deepflow/token-history.jsonl — 42 lines found]
|
|
211
|
+
[Reads ~/.claude/quota-history.jsonl — last 5 lines found]
|
|
212
|
+
[Reads .deepflow/results/T1.yaml, T2.yaml, T3.yaml — tokens blocks extracted]
|
|
213
|
+
[Reads .deepflow/auto-memory.yaml — session metadata found]
|
|
214
|
+
|
|
215
|
+
[Computes:
|
|
216
|
+
total_input_tokens = 185000
|
|
217
|
+
total_cache_creation = 45000
|
|
218
|
+
total_cache_read = 320000
|
|
219
|
+
cache_hit_ratio = 320000 / (185000 + 45000 + 320000) = 0.5818
|
|
220
|
+
peak_context_percentage = 73
|
|
221
|
+
model = claude-sonnet-4-5
|
|
222
|
+
]
|
|
223
|
+
|
|
224
|
+
[Writes .deepflow/report.json]
|
|
225
|
+
[Writes .deepflow/report.md]
|
|
226
|
+
|
|
227
|
+
Report generated:
|
|
228
|
+
.deepflow/report.json — machine-readable (version=1)
|
|
229
|
+
.deepflow/report.md — human-readable summary
|
|
230
|
+
```
|
|
@@ -95,3 +95,10 @@ quality:
|
|
|
95
95
|
|
|
96
96
|
# Timeout in seconds to wait for the dev server to become ready (default: 30)
|
|
97
97
|
browser_timeout: 30
|
|
98
|
+
|
|
99
|
+
# Recommended .gitignore entries
|
|
100
|
+
# Add these entries to your .gitignore to exclude instrumentation artifacts
|
|
101
|
+
gitignore_entries:
|
|
102
|
+
- "# Deepflow instrumentation artifacts"
|
|
103
|
+
- ".deepflow/token-history.jsonl # Token usage history from autonomous mode"
|
|
104
|
+
- ".deepflow/report.json # Instrumentation metrics summary"
|