deepflow 0.1.85 → 0.1.87
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 +4 -0
- 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/discover.md +43 -3
- package/src/commands/df/execute.md +50 -0
- package/src/commands/df/report.md +230 -0
- package/src/commands/df/update.md +3 -1
- 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
|
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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: df:discover
|
|
3
3
|
description: Explore a problem space deeply through structured questioning to surface requirements and constraints
|
|
4
|
-
allowed-tools: [AskUserQuestion, Read]
|
|
4
|
+
allowed-tools: [AskUserQuestion, Read, Agent]
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# /df:discover — Deep Problem Exploration
|
|
@@ -10,9 +10,9 @@ allowed-tools: [AskUserQuestion, Read]
|
|
|
10
10
|
|
|
11
11
|
You are a Socratic questioner. Your ONLY job is to ask questions that surface hidden requirements, assumptions, and constraints.
|
|
12
12
|
|
|
13
|
-
**NEVER:** Read source files, use Glob/Grep, spawn agents, create files (except `.deepflow/decisions.md`), run git, use TaskOutput, use Task tool, use EnterPlanMode, use ExitPlanMode
|
|
13
|
+
**NEVER:** Read source files directly, use Glob/Grep directly, proactively spawn agents, create files (except `.deepflow/decisions.md`), run git, use TaskOutput, use Task tool, use EnterPlanMode, use ExitPlanMode
|
|
14
14
|
|
|
15
|
-
**ONLY:** Ask questions using `AskUserQuestion` tool, respond conversationally
|
|
15
|
+
**ONLY:** Ask questions using `AskUserQuestion` tool, respond conversationally, and spawn context-fetch agents **when the user explicitly requests it**
|
|
16
16
|
|
|
17
17
|
---
|
|
18
18
|
|
|
@@ -91,6 +91,46 @@ Example questions:
|
|
|
91
91
|
- Keep your responses short between questions — don't lecture
|
|
92
92
|
- Acknowledge answers briefly before asking the next question
|
|
93
93
|
|
|
94
|
+
### On-Demand Context Fetching
|
|
95
|
+
|
|
96
|
+
When the user explicitly asks you to look at code or a URL (e.g., "olha no código", "vê esse link", "look at src/auth/", "check https://docs.example.com"), fetch context using a sub-agent.
|
|
97
|
+
|
|
98
|
+
**Trigger:** Intent-based detection — the user must explicitly request it. NEVER proactively fetch context.
|
|
99
|
+
|
|
100
|
+
**For codebase context:**
|
|
101
|
+
```
|
|
102
|
+
Agent(subagent_type="Explore", model="haiku", prompt="""
|
|
103
|
+
Read and summarize the following: {what the user asked to see}
|
|
104
|
+
|
|
105
|
+
Rules:
|
|
106
|
+
- Return ONLY factual observations: what files exist, what functions/types are defined, what patterns are used
|
|
107
|
+
- Do NOT suggest solutions, improvements, or architectural changes
|
|
108
|
+
- Do NOT give opinions on code quality
|
|
109
|
+
- Keep response under 4000 tokens
|
|
110
|
+
- Format: bullet points of facts
|
|
111
|
+
""")
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**For URL context:**
|
|
115
|
+
```
|
|
116
|
+
Agent(subagent_type="Explore", model="haiku", prompt="""
|
|
117
|
+
Use the browse-fetch skill to fetch this URL: {url}
|
|
118
|
+
|
|
119
|
+
Then summarize what the page contains.
|
|
120
|
+
|
|
121
|
+
Rules:
|
|
122
|
+
- Return ONLY factual observations: what the documentation says, what APIs are described, what patterns are shown
|
|
123
|
+
- Do NOT suggest how to use this in the project
|
|
124
|
+
- Do NOT give opinions or recommendations
|
|
125
|
+
- Keep response under 4000 tokens
|
|
126
|
+
- Format: bullet points of facts
|
|
127
|
+
""")
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**After receiving context:** Briefly share the factual summary with the user, then **resume Socratic questioning** incorporating the new facts. Do NOT shift to suggesting solutions.
|
|
131
|
+
|
|
132
|
+
**Soft cap:** ~3 context fetches per discover session to protect context window.
|
|
133
|
+
|
|
94
134
|
### When the User Wants to Move On
|
|
95
135
|
When the user signals they want to advance (e.g., "I think that's enough", "let's move on", "ready for next step"):
|
|
96
136
|
|
|
@@ -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 = !`grep -o '"percentage":[0-9]*' .deepflow/context.json 2>/dev/null | grep -o '[0-9]*' || 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,49 @@ 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 = !`grep -o '"percentage":[0-9]*' .deepflow/context.json 2>/dev/null | grep -o '[0-9]*' || 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
|
+
awk -v start="REPLACE_start_timestamp" -v end="REPLACE_end_timestamp" '
|
|
202
|
+
{
|
|
203
|
+
ts=""; inp=0; cre=0; rd=0
|
|
204
|
+
if (match($0, /"timestamp":"[^"]*"/)) { ts=substr($0, RSTART+13, RLENGTH-14) }
|
|
205
|
+
if (ts >= start && ts <= end) {
|
|
206
|
+
if (match($0, /"input_tokens":[0-9]+/)) inp=substr($0, RSTART+15, RLENGTH-15)
|
|
207
|
+
if (match($0, /"cache_creation_input_tokens":[0-9]+/)) cre=substr($0, RSTART+30, RLENGTH-30)
|
|
208
|
+
if (match($0, /"cache_read_input_tokens":[0-9]+/)) rd=substr($0, RSTART+26, RLENGTH-26)
|
|
209
|
+
si+=inp; sc+=cre; sr+=rd
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
END { printf "{\"input_tokens\":%d,\"cache_creation_input_tokens\":%d,\"cache_read_input_tokens\":%d}\n", si+0, sc+0, sr+0 }
|
|
213
|
+
' .deepflow/token-history.jsonl 2>/dev/null || echo '{}'
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Append (or create) `.deepflow/results/T{N}.yaml` with the following block. Use shell injection to read the existing file first:
|
|
217
|
+
```
|
|
218
|
+
!`cat .deepflow/results/T{N}.yaml 2>/dev/null || echo ''`
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Write the `tokens` block:
|
|
222
|
+
```yaml
|
|
223
|
+
tokens:
|
|
224
|
+
start_percentage: {start_percentage}
|
|
225
|
+
end_percentage: {end_percentage}
|
|
226
|
+
delta_percentage: {end_percentage - start_percentage}
|
|
227
|
+
input_tokens: {sum from jsonl}
|
|
228
|
+
cache_creation_input_tokens: {sum from jsonl}
|
|
229
|
+
cache_read_input_tokens: {sum from jsonl}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
**Omit entirely if:** context.json was unavailable at start OR end, OR token-history.jsonl is missing, OR awk is unavailable. Never fail the ratchet due to token tracking errors.
|
|
233
|
+
|
|
184
234
|
**Evaluate:** All pass + no violations → commit stands. Any failure → attempt partial salvage before reverting:
|
|
185
235
|
|
|
186
236
|
**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
|
+
```
|
|
@@ -5,7 +5,7 @@ description: Update or uninstall deepflow, check installed version
|
|
|
5
5
|
|
|
6
6
|
# /df:update — Update deepflow
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
**ACTION REQUIRED:** Immediately run the update command below. Do NOT ask for confirmation — the user already confirmed by running `/df:update`.
|
|
9
9
|
|
|
10
10
|
```bash
|
|
11
11
|
npx deepflow@latest
|
|
@@ -15,6 +15,8 @@ Auto-detects existing installation and updates it.
|
|
|
15
15
|
|
|
16
16
|
## Uninstall
|
|
17
17
|
|
|
18
|
+
To uninstall instead, run:
|
|
19
|
+
|
|
18
20
|
```bash
|
|
19
21
|
npx deepflow --uninstall
|
|
20
22
|
```
|
|
@@ -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"
|