deepflow 0.1.85 → 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 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
- if (Object.keys(settings.hooks).length === 0) {
527
- delete settings.hooks;
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();
@@ -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,6 +1,6 @@
1
1
  {
2
2
  "name": "deepflow",
3
- "version": "0.1.85",
3
+ "version": "0.1.86",
4
4
  "description": "Doing reveals what thinking can't predict — spec-driven iterative development for Claude Code",
5
5
  "keywords": [
6
6
  "claude",
@@ -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"