cgraphx 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/README.md +0 -1
  2. package/dist/.claude-template/skills/cgraphx/SKILL.md +3 -3
  3. package/dist/.claude-template/skills/cgraphx/agent-prompt.md +1 -1
  4. package/dist/.claude-template/skills/cgraphx-guide/SKILL.md +94 -0
  5. package/dist/.claude-template/skills/cgraphx-guide/how-to-use.html +403 -0
  6. package/dist/.claude-template/skills/clarify-requirements/SKILL.md +19 -8
  7. package/dist/.claude-template/skills/code-impact-docgen/SKILL.md +186 -176
  8. package/dist/.claude-template/skills/code-impact-docgen/template-design-html.md +357 -0
  9. package/dist/.claude-template/skills/code-impact-docgen/template-design-md.md +164 -0
  10. package/dist/.claude-template/skills/code-impact-init/SKILL.md +47 -47
  11. package/dist/.claude-template/skills/developer-timeline/SKILL.md +9 -0
  12. package/dist/.claude-template/skills/write-api-doc/SKILL.md +317 -0
  13. package/dist/.claude-template/skills/write-api-doc/template-api-html.md +422 -0
  14. package/dist/.claude-template/skills/write-plan/SKILL.md +38 -16
  15. package/dist/.claude-template/skills/write-prd/SKILL.md +32 -8
  16. package/dist/.claude-template/skills/write-spec/SKILL.md +34 -9
  17. package/dist/bin/codegraph.js +0 -100
  18. package/dist/bin/codegraph.js.map +1 -1
  19. package/dist/resolution/index.d.ts.map +1 -1
  20. package/dist/resolution/index.js +13 -0
  21. package/dist/resolution/index.js.map +1 -1
  22. package/dist/resolution/scope-index.d.ts +86 -0
  23. package/dist/resolution/scope-index.d.ts.map +1 -0
  24. package/dist/resolution/scope-index.js +143 -0
  25. package/dist/resolution/scope-index.js.map +1 -0
  26. package/dist/resolution/stdlib-blocklist.d.ts +53 -0
  27. package/dist/resolution/stdlib-blocklist.d.ts.map +1 -0
  28. package/dist/resolution/stdlib-blocklist.js +143 -0
  29. package/dist/resolution/stdlib-blocklist.js.map +1 -0
  30. package/dist/search/ast-helpers.d.ts +42 -0
  31. package/dist/search/ast-helpers.d.ts.map +1 -0
  32. package/dist/search/ast-helpers.js +106 -0
  33. package/dist/search/ast-helpers.js.map +1 -0
  34. package/dist/search/call-sites.d.ts +398 -0
  35. package/dist/search/call-sites.d.ts.map +1 -0
  36. package/dist/search/call-sites.js +1433 -0
  37. package/dist/search/call-sites.js.map +1 -0
  38. package/dist/search/context.d.ts +134 -0
  39. package/dist/search/context.d.ts.map +1 -0
  40. package/dist/search/context.js +575 -0
  41. package/dist/search/context.js.map +1 -0
  42. package/dist/search/impact.d.ts +139 -0
  43. package/dist/search/impact.d.ts.map +1 -0
  44. package/dist/search/impact.js +646 -0
  45. package/dist/search/impact.js.map +1 -0
  46. package/dist/search/related.d.ts +178 -0
  47. package/dist/search/related.d.ts.map +1 -0
  48. package/dist/search/related.js +667 -0
  49. package/dist/search/related.js.map +1 -0
  50. package/dist/search/slice.d.ts +148 -0
  51. package/dist/search/slice.d.ts.map +1 -0
  52. package/dist/search/slice.js +460 -0
  53. package/dist/search/slice.js.map +1 -0
  54. package/dist/search/snr-constants.d.ts +41 -0
  55. package/dist/search/snr-constants.d.ts.map +1 -0
  56. package/dist/search/snr-constants.js +44 -0
  57. package/dist/search/snr-constants.js.map +1 -0
  58. package/dist/search/types.d.ts +28 -0
  59. package/dist/search/types.d.ts.map +1 -0
  60. package/dist/search/types.js +12 -0
  61. package/dist/search/types.js.map +1 -0
  62. package/dist/timeline/cli.d.ts.map +1 -1
  63. package/dist/timeline/cli.js +22 -3
  64. package/dist/timeline/cli.js.map +1 -1
  65. package/dist/timeline/store.d.ts +5 -0
  66. package/dist/timeline/store.d.ts.map +1 -1
  67. package/dist/timeline/store.js +23 -3
  68. package/dist/timeline/store.js.map +1 -1
  69. package/package.json +1 -1
  70. package/scripts/agent-eval/block-cgraphx-and-gitnexus-cli-hook.sh +43 -0
  71. package/scripts/agent-eval/block-cgraphx-cli-hook.sh +32 -0
  72. package/scripts/agent-eval/block-cgraphx-cli-settings.json +16 -0
  73. package/scripts/agent-eval/cli-vs-mcp-3arm.sh +121 -0
  74. package/scripts/agent-eval/multi-tool-eval.sh +171 -0
  75. package/scripts/agent-eval/parse-cli-vs-mcp.mjs +232 -0
  76. package/scripts/agent-eval/parse-multi-tool.mjs +242 -0
  77. package/scripts/agent-eval/subagent-token-cost.py +188 -0
  78. package/dist/.claude-template/skills/code-impact-docgen/template-business-html.md +0 -242
  79. package/dist/.claude-template/skills/code-impact-docgen/template-business-md.md +0 -107
  80. package/dist/.claude-template/skills/code-impact-docgen/template-technical-html.md +0 -205
  81. package/dist/.claude-template/skills/code-impact-docgen/template-technical-md.md +0 -155
@@ -0,0 +1,242 @@
1
+ #!/usr/bin/env node
2
+ // Parse multi-tool-eval transcripts (3 arms × 3 tasks × n reps).
3
+ //
4
+ // File-name contract: <repo>-<task>-<arm>-<rep>.jsonl
5
+ // task ∈ {flow, onboarding, impact}
6
+ // arm ∈ {cgraphx-cli, cgraphx-mcp, gitnexus-mcp}
7
+ //
8
+ // Output:
9
+ // 1. <out-dir>/results.jsonl — one JSON line per transcript
10
+ // 2. stdout:
11
+ // - PER TASK × ARM × REP (detail)
12
+ // - AGGREGATE PER TASK × ARM (mean across reps)
13
+ // - AGGREGATE PER ARM (mean across task × reps — overall tool ranking)
14
+ import { readFileSync, readdirSync, existsSync, writeFileSync } from 'fs';
15
+ import { join } from 'path';
16
+
17
+ const OUT = process.argv[2] || '/tmp/cg-multi-tool';
18
+ const RUNS_DIR = join(OUT, 'runs');
19
+ const RESULTS = join(OUT, 'results.jsonl');
20
+
21
+ if (!existsSync(RUNS_DIR)) {
22
+ console.error(`no runs dir at ${RUNS_DIR}`);
23
+ process.exit(1);
24
+ }
25
+
26
+ function parse(file) {
27
+ const lines = readFileSync(file, 'utf8').split('\n').filter(Boolean);
28
+ const toolCounts = {};
29
+ const cliCommands = { cgraphx: [], gitnexus: [] };
30
+ const hookDenies = { cgraphx: 0, gitnexus: 0 };
31
+ let result = null;
32
+
33
+ for (const line of lines) {
34
+ let ev;
35
+ try { ev = JSON.parse(line); } catch { continue; }
36
+
37
+ if (ev.type === 'assistant' && Array.isArray(ev.message?.content)) {
38
+ for (const b of ev.message.content) {
39
+ if (b.type !== 'tool_use') continue;
40
+ toolCounts[b.name] = (toolCounts[b.name] || 0) + 1;
41
+ if (b.name === 'Bash' && typeof b.input?.command === 'string') {
42
+ const cmd = b.input.command;
43
+ if (/^cgraphx /.test(cmd) || / cgraphx /.test(cmd) || /codegraph\.js/.test(cmd)) {
44
+ cliCommands.cgraphx.push(cmd.slice(0, 80));
45
+ }
46
+ if (/gitnexus\/dist\/cli\/index\.js/.test(cmd) || /gitnexus\/run\.cjs/.test(cmd) || /^gitnexus /.test(cmd) || / gitnexus /.test(cmd)) {
47
+ cliCommands.gitnexus.push(cmd.slice(0, 80));
48
+ }
49
+ }
50
+ }
51
+ }
52
+
53
+ if (ev.type === 'user' && Array.isArray(ev.message?.content)) {
54
+ for (const b of ev.message.content) {
55
+ if (b.type !== 'tool_result') continue;
56
+ const text = typeof b.content === 'string'
57
+ ? b.content
58
+ : Array.isArray(b.content)
59
+ ? b.content.map(c => (typeof c === 'string' ? c : c?.text || '')).join('')
60
+ : '';
61
+ if (/禁用 cgraphx CLI/.test(text)) hookDenies.cgraphx++;
62
+ if (/禁用 gitnexus CLI/.test(text)) hookDenies.gitnexus++;
63
+ }
64
+ }
65
+
66
+ if (ev.type === 'result') result = ev;
67
+ }
68
+
69
+ let skillCgraphx = 0, skillGitnexus = 0;
70
+ for (const line of lines) {
71
+ let ev;
72
+ try { ev = JSON.parse(line); } catch { continue; }
73
+ if (ev.type === 'assistant' && Array.isArray(ev.message?.content)) {
74
+ for (const b of ev.message.content) {
75
+ if (b.type !== 'tool_use' || b.name !== 'Skill') continue;
76
+ const s = b.input?.skill || '';
77
+ if (s === 'cgraphx-search' || s === 'cgraphx') skillCgraphx++;
78
+ if (s.startsWith('gitnexus')) skillGitnexus++;
79
+ }
80
+ }
81
+ }
82
+
83
+ const u = result?.usage || {};
84
+ const tokGen = u.output_tokens || 0;
85
+ const tokFresh = (u.input_tokens || 0) + (u.cache_creation_input_tokens || 0);
86
+ const tokCached = u.cache_read_input_tokens || 0;
87
+
88
+ const get = (n) => toolCounts[n] || 0;
89
+ const mcpCalls = Object.keys(toolCounts)
90
+ .filter(k => /^mcp__(cgraphx|gitnexus)__/.test(k))
91
+ .reduce((s, k) => s + toolCounts[k], 0);
92
+ // Per-tool MCP breakdown
93
+ const mcpByServer = {};
94
+ for (const k of Object.keys(toolCounts)) {
95
+ const m = k.match(/^mcp__(cgraphx|gitnexus)__/);
96
+ if (m) mcpByServer[m[1]] = (mcpByServer[m[1]] || 0) + toolCounts[k];
97
+ }
98
+
99
+ return {
100
+ read: get('Read'),
101
+ grep: get('Grep') + get('Glob'),
102
+ bash: get('Bash'),
103
+ cliCallsCgraphx: cliCommands.cgraphx.length,
104
+ cliCallsGitnexus: cliCommands.gitnexus.length,
105
+ mcpCalls,
106
+ mcpCgraphx: mcpByServer.cgraphx || 0,
107
+ mcpGitnexus: mcpByServer.gitnexus || 0,
108
+ skillCgraphx,
109
+ skillGitnexus,
110
+ hookDeniesCgraphx: hookDenies.cgraphx,
111
+ hookDeniesGitnexus: hookDenies.gitnexus,
112
+ durationSec: result ? Math.floor(result.duration_ms / 1000) : null,
113
+ numTurns: result?.num_turns ?? null,
114
+ costUsdMain: result ? +(result.total_cost_usd || 0).toFixed(4) : null,
115
+ tokGen,
116
+ tokFresh,
117
+ tokCached,
118
+ tokBillable: tokGen + tokFresh,
119
+ ok: result?.subtype === 'success',
120
+ };
121
+ }
122
+
123
+ // task -> arm -> [runs]
124
+ const data = {};
125
+ const allMetrics = [];
126
+ for (const f of readdirSync(RUNS_DIR)) {
127
+ const m = f.match(/^(.+)-(\w+)-(\w+-\w+)-(\d+)\.jsonl$/);
128
+ if (!m) continue;
129
+ const [, repo, task, arm, repStr] = m;
130
+ const parsed = parse(join(RUNS_DIR, f));
131
+ const rep = Number(repStr);
132
+ const record = { repo, task, arm, rep, file: f, ...parsed };
133
+ allMetrics.push(record);
134
+ (((data[task] ??= {})[arm]) ??= []).push(record);
135
+ }
136
+
137
+ writeFileSync(RESULTS, allMetrics
138
+ .sort((a, b) => a.task.localeCompare(b.task) || a.arm.localeCompare(b.arm) || a.rep - b.rep)
139
+ .map(r => JSON.stringify(r))
140
+ .join('\n') + '\n'
141
+ );
142
+
143
+ const pad = (s, n) => String(s).padEnd(n);
144
+ const TASKS = ['flow', 'onboarding', 'impact'];
145
+ const ARMS = ['cgraphx-cli', 'cgraphx-mcp', 'gitnexus-mcp'];
146
+ const LABEL = {
147
+ 'cgraphx-cli': 'A1 cgraphx-cli',
148
+ 'cgraphx-mcp': 'A2 cgraphx-mcp',
149
+ 'gitnexus-mcp': 'A3 gitnexus-mcp',
150
+ };
151
+
152
+ // ---- PER TASK × ARM × REP ----
153
+ console.log('\n=== PER TASK × ARM × REP (detail) ===');
154
+ console.log(
155
+ pad('task', 11), pad('arm', 18), pad('rep', 4),
156
+ pad('dur', 5), pad('read', 5), pad('grep', 5), pad('bash', 5),
157
+ pad('cgCli', 5), pad('gnCli', 5),
158
+ pad('cgMcp', 5), pad('gnMcp', 5),
159
+ pad('deny', 5),
160
+ pad('tokBill', 8), pad('cost', 8), 'ok'
161
+ );
162
+ for (const task of TASKS) {
163
+ for (const arm of ARMS) {
164
+ const runs = data[task]?.[arm] || [];
165
+ for (const r of runs) {
166
+ console.log(
167
+ pad(task, 11), pad(LABEL[arm], 18), pad(r.rep, 4),
168
+ pad(r.durationSec ?? '?', 5),
169
+ pad(r.read, 5), pad(r.grep, 5), pad(r.bash, 5),
170
+ pad(r.cliCallsCgraphx, 5), pad(r.cliCallsGitnexus, 5),
171
+ pad(r.mcpCgraphx, 5), pad(r.mcpGitnexus, 5),
172
+ pad(r.hookDeniesCgraphx + r.hookDeniesGitnexus, 5),
173
+ pad(r.tokBillable, 8),
174
+ pad('$' + (r.costUsdMain ?? 0).toFixed(4), 8),
175
+ r.ok ? '✓' : '✗'
176
+ );
177
+ }
178
+ }
179
+ }
180
+
181
+ const avg = (a, f) => a.length ? a.reduce((s, x) => s + (f(x) || 0), 0) / a.length : 0;
182
+
183
+ // ---- AGGREGATE PER TASK × ARM ----
184
+ console.log('\n=== AGGREGATE PER TASK × ARM (mean across reps) ===');
185
+ console.log(
186
+ pad('task', 11), pad('arm', 18),
187
+ pad('read', 6), pad('grep', 6), pad('bash', 6),
188
+ pad('cgCli', 5), pad('gnCli', 5),
189
+ pad('cgMcp', 5), pad('gnMcp', 5),
190
+ pad('dur', 6), pad('tokBill', 8), pad('cost', 8), 'n'
191
+ );
192
+ for (const task of TASKS) {
193
+ for (const arm of ARMS) {
194
+ const runs = data[task]?.[arm] || [];
195
+ if (!runs.length) continue;
196
+ console.log(
197
+ pad(task, 11), pad(LABEL[arm], 18),
198
+ pad(avg(runs, r => r.read).toFixed(1), 6),
199
+ pad(avg(runs, r => r.grep).toFixed(1), 6),
200
+ pad(avg(runs, r => r.bash).toFixed(1), 6),
201
+ pad(avg(runs, r => r.cliCallsCgraphx).toFixed(1), 5),
202
+ pad(avg(runs, r => r.cliCallsGitnexus).toFixed(1), 5),
203
+ pad(avg(runs, r => r.mcpCgraphx).toFixed(1), 5),
204
+ pad(avg(runs, r => r.mcpGitnexus).toFixed(1), 5),
205
+ pad(avg(runs, r => r.durationSec).toFixed(0) + 's', 6),
206
+ pad(avg(runs, r => r.tokBillable).toFixed(0), 8),
207
+ pad('$' + avg(runs, r => r.costUsdMain).toFixed(4), 8),
208
+ runs.length
209
+ );
210
+ }
211
+ }
212
+
213
+ // ---- AGGREGATE PER ARM (across all tasks × reps) ----
214
+ console.log('\n=== AGGREGATE PER ARM (mean across all tasks × reps) ===');
215
+ console.log(
216
+ pad('arm', 18),
217
+ pad('read', 6), pad('grep', 6), pad('bash', 6),
218
+ pad('cgCli', 5), pad('gnCli', 5),
219
+ pad('cgMcp', 5), pad('gnMcp', 5),
220
+ pad('dur', 6), pad('tokBill', 8), pad('cost', 8), 'n'
221
+ );
222
+ for (const arm of ARMS) {
223
+ const all = [];
224
+ for (const task of TASKS) for (const r of (data[task]?.[arm] || [])) all.push(r);
225
+ if (!all.length) continue;
226
+ console.log(
227
+ pad(LABEL[arm], 18),
228
+ pad(avg(all, r => r.read).toFixed(1), 6),
229
+ pad(avg(all, r => r.grep).toFixed(1), 6),
230
+ pad(avg(all, r => r.bash).toFixed(1), 6),
231
+ pad(avg(all, r => r.cliCallsCgraphx).toFixed(1), 5),
232
+ pad(avg(all, r => r.cliCallsGitnexus).toFixed(1), 5),
233
+ pad(avg(all, r => r.mcpCgraphx).toFixed(1), 5),
234
+ pad(avg(all, r => r.mcpGitnexus).toFixed(1), 5),
235
+ pad(avg(all, r => r.durationSec).toFixed(0) + 's', 6),
236
+ pad(avg(all, r => r.tokBillable).toFixed(0), 8),
237
+ pad('$' + avg(all, r => r.costUsdMain).toFixed(4), 8),
238
+ all.length
239
+ );
240
+ }
241
+
242
+ console.log(`\nresults.jsonl: ${RESULTS}`);
@@ -0,0 +1,188 @@
1
+ #!/usr/bin/env python3
2
+ """Measure real API token usage and $ cost from Claude Code subagent transcripts.
3
+
4
+ Reads the `.output` JSONL file Claude Code writes per background subagent
5
+ (the `output_file` returned by the Agent tool), sums the `usage` field on
6
+ each assistant message, and applies Sonnet list pricing to get a cache-aware
7
+ $ cost.
8
+
9
+ Why this exists: tool-output chars (the easy thing to count by eye) is a
10
+ poor proxy for real cost. The dominant cost driver in multi-turn sessions is
11
+ `cache_read_input_tokens` — every turn re-sends the growing conversation as
12
+ cached input, billed at ~10% of full input cost. A tool that returns tiny
13
+ output but forces many turns (grep) is more expensive than a tool that
14
+ returns rich output in few turns (codegraph_explore). chars hides this;
15
+ `usage` + $ pricing exposes it.
16
+
17
+ See docs/features/2026-07-01-calls-command/abac-comparison.md for the case
18
+ study that motivated this script.
19
+
20
+ Pricing (Sonnet, per million tokens):
21
+ input $3.00
22
+ output $15.00
23
+ cache_read $0.30 (10% of input)
24
+ cache_creation $3.75 (125% of input — first-time cache write)
25
+
26
+ Usage:
27
+ python3 subagent-token-cost.py <agent-id> [<agent-id> ...]
28
+ python3 subagent-token-cost.py --dir <tasks-dir> # all .output files
29
+
30
+ The agent-id form looks up `<tasks-dir>/<agent-id>.output` under the standard
31
+ session tasks directory. Pass `--dir` to point at a different directory
32
+ (e.g. when comparing across sessions).
33
+
34
+ Output: one line per arm with turns, tool_uses, input/output/cache_read/
35
+ cache_write token counts, raw total, and $ cost. Sorted in input order.
36
+
37
+ Notes:
38
+ - This reads ONLY the `usage` field and tool_use block counts — never message
39
+ content. Safe to run on large transcripts; won't overflow your context.
40
+ - For main-session cost (not subagent), use offload-eval-cost.mjs instead,
41
+ which reads the structured eval-runs format and uses Claude Code's own
42
+ authoritative costUSD accounting from the `result` event.
43
+ """
44
+ from __future__ import annotations
45
+
46
+ import argparse
47
+ import json
48
+ import os
49
+ import sys
50
+ from pathlib import Path
51
+
52
+ # Sonnet list pricing, per million tokens.
53
+ PRICE_INPUT_PER_M = 3.00
54
+ PRICE_OUTPUT_PER_M = 15.00
55
+ PRICE_CACHE_READ_PER_M = 0.30
56
+ PRICE_CACHE_WRITE_PER_M = 3.75
57
+
58
+
59
+ def default_tasks_dir() -> Path:
60
+ """Best-effort default for this session's tasks dir."""
61
+ # Claude Code writes subagent .output files under a session-scoped tasks dir.
62
+ # The exact path varies; common locations:
63
+ candidates = [
64
+ Path('/private/tmp/claude-501'),
65
+ Path.home() / '.claude' / 'tasks',
66
+ ]
67
+ for c in candidates:
68
+ if c.exists():
69
+ # Find the most recently modified session dir
70
+ session_dirs = sorted(
71
+ c.glob('*-Users-*'), key=lambda p: p.stat().st_mtime, reverse=True
72
+ )
73
+ for sd in session_dirs:
74
+ tasks = sd / 'tasks'
75
+ if tasks.exists():
76
+ return tasks
77
+ return Path('/private/tmp/claude-501/unknown-session/tasks')
78
+
79
+
80
+ def measure_file(path: Path) -> dict | None:
81
+ if not path.exists():
82
+ return None
83
+ totals = {'input': 0, 'output': 0, 'cache_read': 0, 'cache_creation': 0}
84
+ assistant_turns = 0
85
+ tool_uses = 0
86
+ with path.open() as f:
87
+ for line in f:
88
+ line = line.strip()
89
+ if not line:
90
+ continue
91
+ try:
92
+ obj = json.loads(line)
93
+ except json.JSONDecodeError:
94
+ continue
95
+ msg = obj.get('message') or {}
96
+ if msg.get('role') != 'assistant':
97
+ continue
98
+ usage = msg.get('usage') or {}
99
+ if not usage:
100
+ continue
101
+ assistant_turns += 1
102
+ totals['input'] += usage.get('input_tokens', 0) or 0
103
+ totals['output'] += usage.get('output_tokens', 0) or 0
104
+ totals['cache_read'] += usage.get('cache_read_input_tokens', 0) or 0
105
+ totals['cache_creation'] += usage.get('cache_creation_input_tokens', 0) or 0
106
+ content = msg.get('content') or []
107
+ if isinstance(content, list):
108
+ for block in content:
109
+ if isinstance(block, dict) and block.get('type') == 'tool_use':
110
+ tool_uses += 1
111
+ cost = (
112
+ totals['input'] * PRICE_INPUT_PER_M
113
+ + totals['output'] * PRICE_OUTPUT_PER_M
114
+ + totals['cache_read'] * PRICE_CACHE_READ_PER_M
115
+ + totals['cache_creation'] * PRICE_CACHE_WRITE_PER_M
116
+ ) / 1_000_000
117
+ return {
118
+ 'turns': assistant_turns,
119
+ 'tool_uses': tool_uses,
120
+ 'totals': totals,
121
+ 'all_tokens': sum(totals.values()),
122
+ 'cost_usd': cost,
123
+ }
124
+
125
+
126
+ def main() -> int:
127
+ ap = argparse.ArgumentParser(description=__doc__.split('\n')[0])
128
+ ap.add_argument('agent_ids', nargs='*', help='subagent IDs to measure')
129
+ ap.add_argument(
130
+ '--dir',
131
+ help='tasks directory containing <id>.output files (default: auto-detect latest session)',
132
+ )
133
+ ap.add_argument(
134
+ '--label',
135
+ action='append',
136
+ default=[],
137
+ help='label for an agent, in the same order as agent_ids (repeatable)',
138
+ )
139
+ args = ap.parse_args()
140
+
141
+ tasks_dir = Path(args.dir) if args.dir else default_tasks_dir()
142
+
143
+ if not args.agent_ids:
144
+ # Measure every .output file in the tasks dir
145
+ files = sorted(tasks_dir.glob('*.output'))
146
+ if not files:
147
+ print(f'no .output files in {tasks_dir}', file=sys.stderr)
148
+ return 1
149
+ targets = [(f.stem, f) for f in files]
150
+ else:
151
+ targets = [(aid, tasks_dir / f'{aid}.output') for aid in args.agent_ids]
152
+
153
+ rows = []
154
+ for i, (label, path) in enumerate(targets):
155
+ if args.label and i < len(args.label):
156
+ label = args.label[i]
157
+ r = measure_file(path)
158
+ if r is None:
159
+ print(f'{label}: transcript not found at {path}', file=sys.stderr)
160
+ continue
161
+ r['label'] = label
162
+ rows.append(r)
163
+
164
+ if not rows:
165
+ return 1
166
+
167
+ header = (
168
+ f'{"Arm":<32} {"turns":>6} {"tools":>6} {"input":>10} {"output":>10} '
169
+ f'{"cache_r":>10} {"cache_w":>10} {"total_tok":>11} {"$ cost":>9}'
170
+ )
171
+ print(header)
172
+ print('-' * len(header))
173
+ for r in rows:
174
+ t = r['totals']
175
+ print(
176
+ f'{r["label"]:<32} {r["turns"]:>6} {r["tool_uses"]:>6} {t["input"]:>10,} '
177
+ f'{t["output"]:>10,} {t["cache_read"]:>10,} {t["cache_creation"]:>10,} '
178
+ f'{r["all_tokens"]:>11,} ${r["cost_usd"]:>8.4f}'
179
+ )
180
+
181
+ print()
182
+ print('Pricing: Sonnet list. input $3/M, output $15/M, cache_read $0.30/M, cache_write $3.75/M.')
183
+ print('total_tok = raw token sum (ignores cache discount). $ cost = cache-aware (what actually bills).')
184
+ return 0
185
+
186
+
187
+ if __name__ == '__main__':
188
+ sys.exit(main())
@@ -1,242 +0,0 @@
1
- # Business 文档 HTML 模板
2
-
3
- 适用于面向业务读者的 HTML 格式文档。
4
-
5
- 生成前阅读此文件获取完整的 CSS 样式和章节结构参考。
6
-
7
- ## 完整 HTML 结构
8
-
9
- ```html
10
- <!DOCTYPE html>
11
- <html lang="zh-CN">
12
- <head>
13
- <meta charset="UTF-8">
14
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
15
- <title>{文档标题} — {项目名称}</title>
16
- <style>
17
- :root {
18
- --bg: #ffffff;
19
- --surface: #f8f9fa;
20
- --text: #1d1d1f;
21
- --text-secondary: #6e6e73;
22
- --accent: #0071e3;
23
- --accent-light: #e8f0fe;
24
- --border: #d2d2d7;
25
- --code-bg: #f5f5f7;
26
- --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
27
- --font-mono: "SF Mono", "Fira Code", "Cascadia Code", "Source Code Pro", monospace;
28
- --max-width: 800px;
29
- }
30
-
31
- *, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
32
-
33
- body {
34
- font-family: var(--font-sans);
35
- font-size: 16px;
36
- line-height: 1.7;
37
- color: var(--text);
38
- background: var(--bg);
39
- }
40
-
41
- .container {
42
- max-width: var(--max-width);
43
- margin: 0 auto;
44
- padding: 2rem 1.5rem;
45
- }
46
-
47
- h1 {
48
- font-size: 2rem;
49
- font-weight: 700;
50
- letter-spacing: -0.02em;
51
- margin-bottom: 0.5em;
52
- padding-bottom: 0.5em;
53
- border-bottom: 2px solid var(--border);
54
- }
55
-
56
- .meta {
57
- color: var(--text-secondary);
58
- font-size: 0.875rem;
59
- margin-bottom: 2em;
60
- }
61
-
62
- h2 {
63
- font-size: 1.5rem;
64
- font-weight: 600;
65
- margin-top: 2.5em;
66
- margin-bottom: 0.75em;
67
- padding-bottom: 0.3em;
68
- border-bottom: 1px solid var(--border);
69
- }
70
-
71
- h3 {
72
- font-size: 1.2rem;
73
- font-weight: 600;
74
- margin-top: 1.5em;
75
- margin-bottom: 0.5em;
76
- }
77
-
78
- p { margin-bottom: 1em; }
79
-
80
- ul, ol {
81
- margin-bottom: 1em;
82
- padding-left: 1.5em;
83
- }
84
-
85
- li { margin-bottom: 0.3em; }
86
-
87
- strong { font-weight: 600; }
88
-
89
- code {
90
- font-family: var(--font-mono);
91
- background: var(--code-bg);
92
- padding: 0.15em 0.4em;
93
- border-radius: 4px;
94
- font-size: 0.9em;
95
- }
96
-
97
- pre {
98
- background: var(--code-bg);
99
- border: 1px solid var(--border);
100
- border-radius: 8px;
101
- padding: 1em;
102
- margin-bottom: 1em;
103
- overflow-x: auto;
104
- }
105
-
106
- pre code {
107
- background: none;
108
- padding: 0;
109
- font-size: 0.875em;
110
- }
111
-
112
- table {
113
- width: 100%;
114
- border-collapse: collapse;
115
- margin-bottom: 1em;
116
- font-size: 0.95em;
117
- }
118
-
119
- th, td {
120
- padding: 0.6em 1em;
121
- text-align: left;
122
- border-bottom: 1px solid var(--border);
123
- }
124
-
125
- thead th {
126
- font-weight: 600;
127
- color: var(--accent);
128
- background: var(--surface);
129
- }
130
-
131
- tbody tr:nth-child(even) { background: var(--surface); }
132
-
133
- .toc {
134
- background: var(--surface);
135
- border: 1px solid var(--border);
136
- border-radius: 8px;
137
- padding: 1em 1.5em;
138
- margin-bottom: 2em;
139
- }
140
-
141
- .toc h3 {
142
- margin-top: 0;
143
- margin-bottom: 0.5em;
144
- font-size: 1rem;
145
- }
146
-
147
- .toc ul { list-style: none; padding-left: 0; }
148
- .toc li { margin-bottom: 0.25em; }
149
- .toc a { color: var(--accent); text-decoration: none; }
150
- .toc a:hover { text-decoration: underline; }
151
- .toc ul ul { padding-left: 1.5em; margin-top: 0.25em; }
152
-
153
- .callout {
154
- background: var(--accent-light);
155
- border-left: 4px solid var(--accent);
156
- padding: 0.75em 1em;
157
- border-radius: 0 8px 8px 0;
158
- margin-bottom: 1em;
159
- }
160
-
161
- @media print {
162
- body { font-size: 12pt; }
163
- .container { max-width: 100%; padding: 0; }
164
- .toc { break-after: page; }
165
- h2 { break-after: avoid; }
166
- }
167
- </style>
168
- </head>
169
- <body>
170
- <div class="container">
171
- <h1>{文档标题}</h1>
172
- <p class="meta">生成日期:YYYY-MM-DD | 主题范围:{范围描述}</p>
173
-
174
- <nav class="toc">
175
- <h3>目录</h3>
176
- <ul>
177
- <li><a href="#背景与目标">背景与目标</a></li>
178
- <li><a href="#核心能力">核心能力</a></li>
179
- <li><a href="#业务流程">业务流程</a></li>
180
- <li><a href="#关键决策">关键决策</a></li>
181
- <li><a href="#现状与展望">现状与展望</a></li>
182
- </ul>
183
- </nav>
184
-
185
- <article>
186
- <section id="背景与目标">
187
- <h2>背景与目标</h2>
188
- <p>说明项目或系统的定位、目标和解决的问题。</p>
189
- </section>
190
-
191
- <section id="核心能力">
192
- <h2>核心能力</h2>
193
- <p>描述系统提供的主要能力和价值。</p>
194
- </section>
195
-
196
- <section id="业务流程">
197
- <h2>业务流程</h2>
198
- <p>说明核心业务流程和操作步骤。</p>
199
- </section>
200
-
201
- <section id="关键决策">
202
- <h2>关键决策</h2>
203
- <p>列出重要的技术和业务决策及其理由。</p>
204
- </section>
205
-
206
- <section id="现状与展望">
207
- <h2>现状与展望</h2>
208
- <p>说明当前状态和未来方向。</p>
209
- </section>
210
- </article>
211
- </div>
212
- </body>
213
- </html>
214
- ```
215
-
216
- ## Business HTML 章节内容指南
217
-
218
- 每个章节应按照以下指引从 AI 知识文档中提取、转换和改写内容。
219
-
220
- | 章节 | 内容来源 | 改写方向 |
221
- |------|------------------------|----------|
222
- | 背景与目标 | Summary 文本 | 从"为什么分析"转为"项目/系统的目标和定位" |
223
- | 核心能力 | Summary 文本 | 从"发现"转为"能力描述",面向非技术读者 |
224
- | 业务流程 | Read 文件原文(该章节 cgraphx 不提取) | 从"操作步骤"转为"业务流程说明",去除技术细节 |
225
- | 关键决策 | Read 文件原文(该章节 cgraphx 不提取) | 从"决策记录"转为"决策解释",说明决策的业务原因 |
226
- | 价值与影响 | Read 文件原文(该章节 cgraphx 不提取) | 从"对未来变更的影响"转为"对业务的价值和影响" |
227
- | 现状与展望 | Read 文件原文(该章节 cgraphx 不提取) | 从"发现和后续"转为"当前状态和未来规划" |
228
- | 风险与注意事项 | Read 文件原文(该章节 cgraphx 不提取) | 从"故障排除"转为"风险提示和应对措施" |
229
- | 常见问题 | Read 文件原文(该章节 cgraphx 不提取) | 从"分析记录"转为"Q&A 格式" |
230
- | 术语表 | 各文本字段中的术语 | 提取关键术语,给出简洁的业务定义 |
231
- | 相关资源 | — | 列出相关的代码文件(相对路径)或外部文档 |
232
-
233
- ## HTML 格式要点
234
-
235
- - 所有 CSS 内联在 `<style>` 标签中,不引用外部样式表
236
- - `<title>` 包含文档标题和项目名称
237
- - 使用 `<nav class="toc">` 作为目录容器
238
- - 每个章节使用 `<section id="章节锚点">` 包裹
239
- - 章节锚点使用中文 ID(如 `id="背景与目标"`)
240
- - 重要提示或注意事项使用 `<div class="callout">` 样式
241
- - 代码示例使用 `<pre><code>` 标签
242
- - 表格使用 `<thead>` + `<tbody>` 结构