測試使用 SharpLensMcp:Roslyn 語意分析實戰與 CodeGraph 三層架構整合
安裝 SharpLensMcp dotnet tool install -g SharpLensMcp D:\OlgCase\RS\XXXX>dotnet tool install -g SharpLensMcp 工具 'sharplensmcp' 已安裝。 安裝完成后啓動 sharplens sharplens D:\OlgCase\RS\XXXX>sharplens [10:33:08] [Information] SharpLens MCP server starting... 啓動 claude CLI 並嘗試使用 SharpLen 發現找不到對應的 SharpLens 工具 ❯ Use SharpLens to load the current C# solution. I don't have a SharpLens tool available in this session. It isn't among the connected MCP servers or built-in tools. What is connected for this repo: codegraph (mcp__codegraph__codegraph_explore) — a pre-built code-intelligence graph over the whole workspace, including all the C# under unity/. It returns verbatim symbol source + call paths + blast radius in one call. For "understand/locate/analyze C#" this covers what SharpLens/Roslyn-style loading would give you. UnityMCP (still connecting) — drives the Unity Editor. A couple of options: If you have a SharpLens MCP server you want to use, it needs to be added to your Claude Code MCP config (e.g. claude mcp add sharplens ... or in .mcp.json / settings). Once it reconnects I can drive it. Tell me how it's installed and I'll help wire it up. If the goal is just "load and analyze the current C# solution," I can do that now via codegraph — the index is already built for this repo (there's a .codegraph/ dir). Which do you want — set up the actual SharpLens server, or should I use codegraph to load/analyze the C# solution? And if the latter, what are you trying to find? 出現這個，應該還是要按照 mcp config 這邊發現必須手動加入設定到 mcp.json，如下： { "mcpServers": { "UnityMCP": { "type": "http", "url": "http://127.0.0.1:8088/mcp" }, "sharplens": { "type": "stdio", "command": "sharplens", "args": [], "env": { "DOTNET_SOLUTION_PATH": "d:\OlgCase\RS\HSLR_3\unity\unity.sln" } } } } 執行時發現錯誤 [Failed to parse] Project config (shared via .mcp.json)，必須將單反斜線 \改成雙反斜線 \： "DOTNET_SOLUTION_PATH": "d:\\OlgCase\\RS\\HSLR_3\\unity\\unity.sln" 將 SharpLens 改到 unity 項目啓動，再執行 reconnect 終於連上 SharpLens 連接上 SharpLen 後我們來測試看看，首先讓 claude code 使用 SharpLens 分析看看 Use SharpLens only. Check whether the configured C# solution is loaded successfully. Run the SharpLens project/solution health check and report: - solution path - projects loaded - number of documents - diagnostics/errors - whether Roslyn semantic analysis is available Do not modify any files. 分析得到結果： 接著我們來測試搜索 Fontmanager 看看 Use SharpLens only. Search the loaded solution for the C# type Fontmanager . Show: 1. its namespace 2. source file path 3. base type / interfaces 4. all methods and properties Do not use CodeGraph. Do not modify files. 可以看到分析的結果，這邊内容非常多就不全部列出： 最後進行一個 compile 測試 Use SharpLens only. Run compiler diagnostics for the loaded solution. Summarize: - compile errors - warnings - unresolved references - projects that failed to load Do not modify any files. 結果如下： 接著我們來測試下常規的需求，修改 NormalAtk 這個函式 Find the method `DoNrlAtk`. I am considering changing its method signature. Do not modify any code. Use SharpLens semantic analysis to determine: 1. All references 2. All direct callers 3. Implementations / overrides / interface relationships 4. One-level incoming call graph 5. One-level outgoing call graph 6. Change impact / blast radius 7. Which files would require modification if I added one parameter Clearly state which SharpLens tools were used. 分析結果： 分析完成。目標方法：StageManager.DoNrlAtk(int centerChunkId, int doSkillId = 0) → Assets/Scripts/Module/StageManager_Fight.cs:1219，回傳 Cysharp.Threading.Tasks.UniTask<bool>（async）。未修改任何檔案。 使用的 SharpLens 工具 ┌─────────────────────────────────────────────────┬──────────────────────────────────────┐ │ 工具 │ 用途 │ ├─────────────────────────────────────────────────┼──────────────────────────────────────┤ │ search_symbols │ 定位方法 │ ├─────────────────────────────────────────────────┼──────────────────────────────────────┤ │ get_method_signature │ 取得目前簽章與參數 │ ├─────────────────────────────────────────────────┼──────────────────────────────────────┤ │ find_references │ (1) 全部引用 │ ├─────────────────────────────────────────────────┼──────────────────────────────────────┤ │ find_callers │ (2) 直接呼叫者 │ ├─────────────────────────────────────────────────┼──────────────────────────────────────┤ │ find_implementations │ (3) 實作/覆寫/介面關係 │ ├─────────────────────────────────────────────────┼──────────────────────────────────────┤ │ get_call_graph (direction=both, depth=1) │ (4) 一層 incoming、(5) 一層 outgoing │ ├─────────────────────────────────────────────────┼──────────────────────────────────────┤ │ analyze_change_impact (changeType=addParameter) │ (6) blast radius、(7) 需修改的檔案 │ └─────────────────────────────────────────────────┴──────────────────────────────────────┘ 跟之前使用 codegraph 區別如下，兩者面相的目的是不一樣的： 核心差異 ||CodeGraph|SharpLens MCP| |---|---|---| |核心技術|Tree-sitter + Code Graph + Embedding|Microsoft Roslyn| |語言|約 37 種|C# / .NET 專用| |主要目的|理解大型 codebase 結構|精確理解 C# 語意| |是否建立索引|是|主要載入 .sln/.slnx| |Symbol 搜尋|✅|✅| |Find References|✅|✅| |Call Graph|✅ 強|✅ 強| |Semantic Search|✅ Embedding|❌ 不是重點| |跨語言|✅|❌| |Compiler Diagnostics|❌ 不是真正編譯器|✅ Roslyn| |Type Resolution|Tree-sitter / graph 層級|✅ 編譯器級| |Control/Data Flow|較有限|✅ Roslyn CFG/DataFlow| |Refactor|不是核心|✅ 很強| |Change Impact|Graph 推導|Roslyn semantic analysis| |Dead Code|Graph 分析|Roslyn semantic/CFG| |AI Token 節省|很強|強| |Unity/C# 精準度|不錯|更適合| CodeGraph 會解析程式碼，把 function、class、import、call chain 等建立成圖，並可用 embedding 做 semantic search；目前 README 表示支援約 37 種語言。 SharpLens 則直接建立在 Microsoft Roslyn 上，目前提供 91 個 MCP tools，包括 compiler diagnostics、data/control flow、change impact、refactoring、API breaking-change analysis 等。 兩者最大的差距其實是「編譯器」 這是 SharpLens 真正的優勢。 例如： void Move(int x) {} void Move(float x) {} void Move(Vector3 x) {} 單純： 搜尋 Move 很容易拿到一堆結果。 Roslyn 知道： Move(10); 實際綁定： Move(int) 而： Move(transform.position); 實際是： Move(Vector3) 這就是 semantic model。 Partial class、extension method、generic、override、interface implementation、implicit conversion 等地方，SharpLens 的優勢會越來越大。官方也特別強調 Roslyn semantic analysis 對 overloaded methods、partial classes、inheritance hierarchy 比文字搜尋精準。 但 CodeGraph 有一個 SharpLens 很難取代的優勢：跨語言 假設專案是： Unity Client C# Backend Node.js / TypeScript AI Service Python Tool Rust CodeGraph可以： CodeGraph Unity C# ───────────────┐ │ Node TypeScript ────────┼──→ Project Graph │ Python AI ──────────────┤ │ Rust Tool ──────────────┘ 因為它支援數十種語言。GitHub SharpLens： Unity C# ───→ Roslyn ───→ ❤️ TypeScript ─────────────→ ❌ Python ─────────────────→ ❌ Rust ───────────────────→ ❌ 所以如果目標是整個產品 repo 的架構理解，CodeGraph 更合理。 它們不是完全替代關係： 以現在的 Unity 專案，反而會考慮兩個一起用 Codex │ ┌───────────┴───────────┐ ↓ ↓ CodeGraph SharpLens │ │ 「專案地圖」 「C# 顯微鏡」 │ │ 架構 / 搜尋 / 關係 Roslyn Semantic Semantic Search Compiler Call Graph Refactor 跨語言 Data Flow 快速定位 Diagnostics 第一階段用 CodeGraph 「跟 Undo 系統相關的 class、method、call chain 有哪些？」 快速縮小到： UndoManager BattleManager CharacterController SaveState RestoreState 然後進第二階段。 第二階段用 SharpLens 「分析 RestoreState() 的 callers、outgoing calls、data flow，以及改變 CharacterState 結構會影響哪些 symbol。」 這時 Roslyn 精確分析。 最後才： Codex ↓ 修改 .cs ↓ SharpLens validate_code ↓ Roslyn diagnostics ↓ 確認沒有 compiler error 這種組合其實比只裝其中一個更合理。 回到 unity 項目，因爲之前已經使用 codegraph，所以我們可以稍加修改下，升級成三層架構 wiki + codegraph + SharpLens 三層偵察 工單 / Requirement │ ▼ ┌─────────────────────────┐ │ ① Wiki：知識面 │ │ /stopic │ │ │ │ 這是什麼功能？ │ │ 屬於什麼模組？ │ │ 過去為什麼這樣設計？ │ └───────────┬─────────────┘ │ 候選 symbol ▼ ┌─────────────────────────┐ │ ② CodeGraph：結構面 │ │ │ │ 系統從哪裡進？ │ │ 呼叫鏈大概怎麼走？ │ │ 涉及哪些檔案/系統？ │ │ blast radius 多大？ │ └───────────┬─────────────┘ │ 精確 symbol ▼ ┌─────────────────────────┐ │ ③ SharpLens：語意面 │ │ Roslyn │ │ │ │ 這個 reference 真的是誰？│ │ 哪個 overload？ │ │ 哪些 override/interface？│ │ 改 signature 會炸哪裡？ │ │ compiler 是否接受？ │ └───────────┬─────────────┘ ▼ 修改 / 驗證 另外由於 codegraph 索引有個備注如下： CodeGraph 是索引、可能落後最新 commit，所以最後必須回 Read / Grep 真實 .cs 核對。 有 SharpLens 後，可以把這條重新定義如下： Wiki = 策展知識，不是權威 CodeGraph = 結構索引，不一定反映最新 working tree SharpLens = 當前 solution 的 Roslyn 語意模型 Source Code = 最終事實 變成： Wiki ↓ CodeGraph ↓ SharpLens ↓ Source Code
CodeGraph 實戰心得：用「地圖導航＋精確搜索」提升 Claude Code 程式碼理解效率
以下將 codegraph 納入實際項目運作查看使用效益 $ (py3.10) D:\OlgCase\RS\HXXR_3>codegraph init ┌ Initializing CodeGraph │ ◆ Initialized in D:\OlgCase\RS\HXXR_3 | | * Scanning files - 2,371 found | * Parsing code - done | * Resolving refs - done │ ◆ Indexed 2,371 files │ ● 69,568 nodes, 192,594 edges in 53.1s │ └ Done $ (py3.10) D:\OlgCase\RS\HXXR_3>codegraph status CodeGraph Status Project: D:\OlgCase\RS\HXXR_3 Index Statistics: Files: 2,371 Nodes: 69,568 Edges: 192,594 DB Size: 147.56 MB Backend: node:sqlite - built-in (full WAL) Journal: wal Nodes by Kind: method 25,000 field 16,000 import 8,000 enum_member 3,000 property 3,737 class 3,500 constant 2,515 file 2,365 namespace 1,581 struct 527 enum 421 variable 276 function 253 interface 207 Files by Language: csharp 2,285 python 42 xml 14 javascript 12 php 10 yaml 6 c 1 objc 1 [OK] Index is up to date 進入項目后提出需求給 cc cc先是透過llm分析出 keyword，然後查詢 codegraph 可用工具，再將關鍵詞輸入給codegraph "query": "UIAIDebugMenu AIDebugMenu aiwnd close hide AICondBlock refresh ai decision" 對話内容如下： 後續都是 Grep、Read ，如圖： codegraph 的實際使用流程并不是全面替代 grep、read，而是將項目 code roadmap 查找出來 先用 codegraph 做「地圖導航」 用戶提出需求 ↓ mcp__codegraph__codegraph_explore ← 只用一次 ↓ 目的：快速了解整個專案結構 "讓我先知道這個 codebase 長什麼樣" codegraph 的作用是給 Claude 一個高層次的程式碼地圖，知道哪些檔案、哪些 class、哪些 function 存在。 再來用 Grep + Read 做「精確搜索」 已知地圖後 ↓ Grep × 8 ← 搜尋具體關鍵字（如 "aiwnd", "CloseUI", 事件名稱） Read × 4 ← 讀取找到的具體檔案內容 後面因爲 Claude 已經知道方向了，就不需要再用 codegraph，改用更精準、更輕量的工具。 本質差異 工具 用途 比喻 codegraph 理解整體結構、依賴關係 看地圖 Grep 搜尋特定字串/pattern 用關鍵字查字典 Read 讀取完整檔案內容 打開具體頁面閱讀 沒有全部使用 codegraph 的原因大概也是如下： codegraph 執行成本 每次呼叫需要分析程式碼結構，在 CPU、IO 上比 grep 慢且耗資源。 必要性 第一次 codegraph 已給出足夠的「索引」，後續只需要針對性搜尋。 Claude 的工具選擇策略 不確定結構 → codegraph（掃描全局） 已知方向 → grep（精確定位） 找到目標 → read（深入閱讀） 如下是 Claude 的工作流程： codegraph（1次）→ 建立認知地圖 ↓ grep（8次）→ 精確定位關鍵程式碼 ↓ read（4次）→ 深入理解細節
Giving an AI Agent a Memory: Building a Self-Curating Knowledge Base for Game Development
Giving an AI Agent a Memory: A Self-Curating Knowledge Base for Game Development Why I stopped letting my AI assistant re-learn the whole codebase every morning — and built it a memory that curates itself. The problem I kept hitting On a fast-moving commercial Unity mobile-game project, the hardest part of plugging in an AI assistant wasn't the model — it was the context. Every session, the assistant started from zero: re-reading the codebase, re-deriving how a feature worked, often landing on a slightly different answer than yesterday. Meanwhile the actual knowledge was scattered across three places that quietly contradicted each other — daily commits, code-review notes, and ticket specs — and the game designers, who couldn't read C#, still needed to know "what does this feature touch, and is this request even feasible?" I didn't want a smarter prompt. I wanted the system to have a memory that stayed current on its own. The bet: three systems, one knowledge flywheel Instead of one monolith, I split the work across three cooperating systems: Game Source — the game client. It only produces material: a daily code review that emits pure "code does X + file:line" facts, with no opinions. Game Agent — the brain. It curates that raw material into a knowledge base, answers feasibility questions, and is deliberately kept outside the game repo so the whole mechanism is portable to the next project. Task — a self-built ticketing system. Requests the agent works out flow into it as tickets; completed tickets flow back into curation. That last loop matters: specs feed in, completed work feeds back, and the knowledge base gets stronger the longer the project runs. A flywheel, not a snapshot. The non-obvious trade-offs A few decisions went against my first instinct, and those are the ones worth writing down. I didn't use RAG. The reflexive move is to chunk everything, embed it, and retrieve by similarity. I tried that on a predecessor system and it was a poor fit for game-dev data: structured specs got flattened into soup, superseded discussion got confused with final decisions, and "who decided this" became guesswork. So I moved the hard work from query-time to write-time. When the agent curates, it preserves structure and writes clean, human-readable knowledge pages — each with machine-readable frontmatter, a plain-prose reading layer, and footnotes citing exact file.cs:line. One file, three audiences, no forking. The LLM never infers authority. Knowledge from a real code review (Tier 1) always outranks a ticket spec (Tier 2), regardless of date — a two-month-old ticket should never overwrite a fact I just read from live code. But the key discipline is that humans and rules decide the tiers and the "final decision" status; the model only executes and writes structured content correctly. It never guesses which source wins. That makes the whole pipeline predictable and auditable — when something's wrong, it's a rule that's wrong, not the model's mood that day. I built my own ticketing instead of reaching for Jira or Trello. Game-content tickets are highly custom and need to plug into the agent flow. I kept the ticketing system intentionally minimal and fully decoupled — it holds no LLM logic at all. All the intelligence stays on the agent side; the ticketing layer just persists and tracks work. There's also a routing index the agent uses for retrieval. I made it impossible to hand-edit: it's regenerated from each page's frontmatter, and CI fails if it drifts. A routing table that can rot silently is worse than no routing table. Making it runnable Design notes are easy to wave at; running code is harder to fake. So alongside the write-up, I packaged a de-identified starter kit — three folders mirroring the three systems, with the portable pieces actually runnable: regenerate the routing index from sample knowledge pages, dry-run the config deploy, run the daily code-review collector. The content is entirely synthetic; the mechanism is real. What I'd take to the next project The lesson that generalized best wasn't a clever prompt or a framework. It was this: the scarce resource in an agent system is the signal-to-noise ratio of what enters the knowledge base. I once planned to auto-convert design spreadsheets into AI-readable docs, measured a ~30% useful conversion rate, and cut the whole feature. Knowing when not to add a source turned out to matter as much as any algorithm. Give your agent a memory — but be ruthless about what you let it remember. The design showcase lives at GamePlusAIAgent (de-identified). A runnable starter kit mirrors the three systems in a separate repo.
Claude Code 摩擦點偵測實作：自動分析 Session 低效行為與改善建議
Lab 2：摩擦點偵測器（Day 17-19） 要找出高摩擦 session，分類摩擦原因，輸出改善建議 2-1 摩擦偵測模組 新增 lib/friction.mjs： import { extractToolCalls } from './analytics.mjs'; const FRICTION_TYPES = { F1: { name: '工具執行失敗', severity: 'high' }, F2: { name: '重複修改同一檔案', severity: 'medium' }, F3: { name: 'Session 過長', severity: 'medium' }, F4: { name: 'Token 燃耗過高', severity: 'medium' }, F7: { name: '/compact 觸發', severity: 'low' }, }; /** * 偵測單一 session 的摩擦點 * @returns { type, severity, detail, recommendation }[] */ export function detectFrictions(messages) { const frictions = []; const turns = messages.filter(m => m.type === 'assistant').length; const toolCalls = extractToolCalls(messages); // F1：工具執行失敗 let toolErrors = 0; const errorMessages = []; for (const msg of messages) { if (msg.type !== 'user') continue; for (const c of msg.message?.content ?? []) { if (c.type === 'tool_result' && c.is_error) { toolErrors++; errorMessages.push(String(c.content ?? '').slice(0, 80)); } } } if (toolErrors > 0) { frictions.push({ type: 'F1', severity: toolErrors >= 5 ? 'high' : 'medium', detail: `${toolErrors} 次工具錯誤`, examples: errorMessages.slice(0, 3), recommendation: '在 CLAUDE.md 中補充路徑規範和常見錯誤處理方式', }); } // F2：重複修改同一檔案（3 次以上） const editCounts = {}; for (const call of toolCalls) { if (['Edit', 'Write', 'MultiEdit'].includes(call.name)) { const fp = call.input?.file_path ?? 'unknown'; editCounts[fp] = (editCounts[fp] ?? 0) + 1; } } const repeatedFiles = Object.entries(editCounts) .filter(([, n]) => n >= 3) .sort(([, a], [, b]) => b - a); if (repeatedFiles.length > 0) { frictions.push({ type: 'F2', severity: 'medium', detail: `${repeatedFiles.length} 個檔案被重複修改 3+ 次`, examples: repeatedFiles.slice(0, 3).map(([f, n]) => `${f} (${n}次)`), recommendation: '提問時一次說清楚所有需求，或使用 MultiEdit 批量修改', }); } // F3：Session 過長 if (turns > 50) { frictions.push({ type: 'F3', severity: 'medium', detail: `Session 長達 ${turns} 輪（建議上限 30 輪）`, examples: [], recommendation: '將大任務拆成多個獨立 session，每個 session 只處理一個子任務', }); } // F4：Token 燃耗過高 let totalTokens = 0; for (const msg of messages) { if (msg.type !== 'assistant') continue; const u = msg.message?.usage; if (u) totalTokens += (u.input_tokens ?? 0) + (u.output_tokens ?? 0); } const burnRate = turns > 0 ? totalTokens / turns : 0; if (burnRate > 20000) { frictions.push({ type: 'F4', severity: 'medium', detail: `每輪平均 ${burnRate.toFixed(0)} tokens（過高）`, examples: [], recommendation: '使用 /compact 壓縮 context，或拆分 session', }); } // F7：/compact 觸發 const summaries = messages.filter(m => m.type === 'summary').length; if (summaries > 0) { frictions.push({ type: 'F7', severity: 'low', detail: `觸發 ${summaries} 次 /compact`, examples: [], recommendation: '考慮在任務中途主動拆分 session，而非等到 context 撐滿', }); } return frictions; } /** * 分析多個 sessions，統計摩擦類型頻率 */ export function frictionSummary(sessionFrictions) { const typeCounts = {}; for (const { frictions } of sessionFrictions) { for (const f of frictions) { typeCounts[f.type] = (typeCounts[f.type] ?? 0) + 1; } } return Object.entries(typeCounts) .map(([type, count]) => ({ type, ...FRICTION_TYPES[type], count })) .sort((a, b) => b.count - a.count); } 2-2 摩擦偵測報告 新增 friction-report.mjs： import { scanProjects, scanProjectSessions } from './lib/scanner.mjs'; import { parseJsonl } from './lib/parser.mjs'; import { detectFrictions, frictionSummary } from './lib/friction.mjs'; import { calcEfficiencyMetrics } from './lib/efficiency.mjs'; const DAYS = parseInt(process.argv[2] ?? '30', 10); const cutoff = new Date(); cutoff.setDate(cutoff.getDate() - DAYS); const sessionFrictions = []; for (const proj of scanProjects()) { for (const session of scanProjectSessions(proj)) { if (new Date(session.first_message_time) < cutoff) continue; const messages = parseJsonl(session.file_path); const frictions = detectFrictions(messages); const metrics = calcEfficiencyMetrics(messages); if (frictions.length > 0) { sessionFrictions.push({ session, messages, frictions, metrics }); } } } console.log(`\n 摩擦點分析報告（過去 ${DAYS} 天）\n`); console.log(` 有摩擦的 session：${sessionFrictions.length} 個\n`); // 摩擦類型統計 const summary = frictionSummary(sessionFrictions); console.log('摩擦類型排行：\n'); for (const item of summary) { const bar = '█'.repeat(Math.min(item.count, 20)); console.log(` [${item.type}] ${item.name.padEnd(16)} ${bar} ${item.count} 次`); } // 最高摩擦 sessions const ranked = sessionFrictions .sort((a, b) => b.frictions.length - a.frictions.length) .slice(0, 5); console.log('\n\n 摩擦最多的 Sessions：\n'); for (const { session, frictions, metrics } of ranked) { const date = new Date(session.first_message_time).toLocaleDateString(); const summary = (session.summary ?? '（無摘要）').slice(0, 50); console.log(` [${date}] 效率分 ${metrics.efficiency_score.toFixed(0)} | ${frictions.length} 個摩擦點`); console.log(` ${summary}`); for (const f of frictions) { console.log(` → [${f.type}] ${f.detail}`); if (f.examples.length > 0) { console.log(` 例：${f.examples[0]}`); } } console.log(); } // 改善建議 const recommendations = new Set(); for (const { frictions } of sessionFrictions) { for (const f of frictions) { recommendations.add(`[${f.type}] ${f.recommendation}`); } } console.log(' 改善建議：\n'); for (const rec of recommendations) { console.log(` • ${rec}`); } 開始執行 node friction-report.mjs # 過去 30 天 D:\Git\mathTalk\class\Claude-Code-Analytics\src>node friction-report.mjs 摩擦點分析報告（過去 30 天） 有摩擦的 session：99 個 摩擦類型排行： [F2] 重複修改同一檔案 ████████████████████ 89 次 [F1] 工具執行失敗 ████████████████████ 80 次 [F3] Session 過長 ████████████████████ 75 次 摩擦最多的 Sessions： [2026/6/10] 效率分 0 | 3 個摩擦點 （無摘要） → [F1] 1 次工具錯誤 例：File does not exist. Note: your current working directory is D:\OlgCase\RS\HSLR_ → [F2] 1 個檔案被重複修改 3+ 次 例：unknown (10次) → [F3] Session 長達 127 輪（建議上限 30 輪） [2026/6/9] 效率分 0 | 3 個摩擦點 （無摘要） → [F1] 4 次工具錯誤 例：<tool_use_error>File has been modified since read, either by the user or by a li → [F2] 1 個檔案被重複修改 3+ 次 例：unknown (56次) → [F3] Session 長達 385 輪（建議上限 30 輪） [2026/6/8] 效率分 0 | 3 個摩擦點 （無摘要） → [F1] 8 次工具錯誤 例：Exit code 2 total 312 drwxr-xr-x 1 joker0820 1049089 0 Jun 8 17:31 . drwxr- → [F2] 1 個檔案被重複修改 3+ 次 例：unknown (8次) → [F3] Session 長達 172 輪（建議上限 30 輪） [2026/6/8] 效率分 0 | 3 個摩擦點 （無摘要） → [F1] 9 次工具錯誤 例：Exit code 1 Traceback (most recent call last): File "<stdin>", line 3, in <mo → [F2] 1 個檔案被重複修改 3+ 次 例：unknown (32次) → [F3] Session 長達 501 輪（建議上限 30 輪） [2026/6/10] 效率分 0 | 3 個摩擦點 （無摘要） → [F1] 11 次工具錯誤 例：Ripgrep search timed out after 20 seconds. The search may have matched files but → [F2] 1 個檔案被重複修改 3+ 次 例：unknown (146次) → [F3] Session 長達 879 輪（建議上限 30 輪） 改善建議： • [F1] 在 CLAUDE.md 中補充路徑規範和常見錯誤處理方式 • [F2] 提問時一次說清楚所有需求，或使用 MultiEdit 批量修改 • [F3] 將大任務拆成多個獨立 session，每個 session 只處理一個子任務
Claude Code 效率分析指標工具開發實作：Session 評分與效率報告生成
下面我們來開發使用 claude code 的效率指標圖表功能 效率指標計算器 目標：計算每個 session 的效率分數，找出你的高效與低效 session 新增效率指標模組 新增 lib/efficiency.mjs： import { extractToolCalls } from './analytics.mjs'; /** * 計算 session 效率指標 */ export function calcEfficiencyMetrics(messages) { const turns = messages.filter(m => m.type === 'assistant').length; const userMsgs = messages.filter(m => m.type === 'user').length; // 工具呼叫統計 const allToolCalls = extractToolCalls(messages); const toolCalls = allToolCalls.length; // 工具錯誤數 let toolErrors = 0; for (const msg of messages) { if (msg.type !== 'user') continue; for (const c of msg.message?.content ?? []) { if (c.type === 'tool_result' && c.is_error) toolErrors++; } } // 重複修改的檔案數 const editCounts = {}; for (const call of allToolCalls) { if (['Edit', 'Write', 'MultiEdit'].includes(call.name)) { const fp = call.input?.file_path ?? 'unknown'; editCounts[fp] = (editCounts[fp] ?? 0) + 1; } } const repeatedEdits = Object.values(editCounts).filter(n => n >= 3).length; // Token 統計 let totalTokens = 0; for (const msg of messages) { if (msg.type !== 'assistant') continue; const u = msg.message?.usage; if (u) totalTokens += (u.input_tokens ?? 0) + (u.output_tokens ?? 0); } const burnRate = turns > 0 ? totalTokens / turns : 0; const errorRate = toolCalls > 0 ? toolErrors / toolCalls : 0; // 效率分數 let score = 100; score -= Math.max(0, (turns - 15) * 2); score -= Math.max(0, (burnRate - 5000) / 1000); score -= errorRate * 200; score -= repeatedEdits * 10; score = Math.max(0, Math.min(100, score)); return { turns, user_msgs: userMsgs, tool_calls: toolCalls, tool_errors: toolErrors, error_rate: errorRate, repeated_edits: repeatedEdits, total_tokens: totalTokens, burn_rate: burnRate, efficiency_score: score, }; } /** * 將效率分數轉為等級 */ export function scoreGrade(score) { if (score >= 80) return { grade: 'A', label: '高效', emoji: '$' }; if (score >= 60) return { grade: 'B', label: '正常', emoji: 'O' }; if (score >= 40) return { grade: 'C', label: '偏低', emoji: '！' }; return { grade: 'D', label: '高摩擦', emoji: 'X' }; } 新增效率報告腳本 新增 efficiency-report.mjs： import { scanProjects, scanProjectSessions } from './lib/scanner.mjs'; import { parseJsonl } from './lib/parser.mjs'; import { calcEfficiencyMetrics, scoreGrade } from './lib/efficiency.mjs'; const DAYS = parseInt(process.argv[2] ?? '30', 10); const cutoff = new Date(); cutoff.setDate(cutoff.getDate() - DAYS); const sessions = []; for (const proj of scanProjects()) { for (const session of scanProjectSessions(proj)) { if (new Date(session.first_message_time) < cutoff) continue; const messages = parseJsonl(session.file_path); const metrics = calcEfficiencyMetrics(messages); sessions.push({ ...session, ...metrics }); } } // 按效率分數排序 sessions.sort((a, b) => b.efficiency_score - a.efficiency_score); console.log(`\n Session 效率分析（過去 ${DAYS} 天，共 ${sessions.length} 個）\n`); // 分布統計 const grades = { A: 0, B: 0, C: 0, D: 0 }; for (const s of sessions) { grades[scoreGrade(s.efficiency_score).grade]++; } console.log('等級分布：'); console.log(` A（高效）：${grades.A} 個`); console.log(` B（正常）：${grades.B} 個`); console.log(` ️ C（偏低）：${grades.C} 個`); console.log(` D（高摩擦）：${grades.D} 個`); // TOP 5 高效 console.log('\n\n 效率最高的 Sessions：\n'); for (const s of sessions.slice(0, 5)) { const { emoji, label } = scoreGrade(s.efficiency_score); const date = new Date(s.first_message_time).toLocaleDateString(); const summary = (s.summary ?? '（無摘要）').slice(0, 50); console.log(` ${emoji} [${s.efficiency_score.toFixed(0)}分] [${date}] ${s.turns}輪 ${s.total_tokens.toLocaleString()}tokens`); console.log(` ${summary}`); } // TOP 5 低效 console.log('\n\n 需要改善的 Sessions：\n'); for (const s of sessions.slice(-5).reverse()) { const { emoji, label } = scoreGrade(s.efficiency_score); const date = new Date(s.first_message_time).toLocaleDateString(); const summary = (s.summary ?? '（無摘要）').slice(0, 50); console.log(` ${emoji} [${s.efficiency_score.toFixed(0)}分] [${date}] ${s.turns}輪 ${s.tool_errors}錯誤 ${s.repeated_edits}重複修改`); console.log(` ${summary}`); } // 平均指標 const avg = sessions.reduce((acc, s) => ({ score: acc.score + s.efficiency_score, turns: acc.turns + s.turns, burnRate: acc.burnRate + s.burn_rate, errorRate: acc.errorRate + s.error_rate, }), { score: 0, turns: 0, burnRate: 0, errorRate: 0 }); const n = sessions.length || 1; console.log('\n\n 平均指標：'); console.log(` 效率分數：${(avg.score / n).toFixed(1)} 分`); console.log(` 平均輪數：${(avg.turns / n).toFixed(1)} 輪`); console.log(` 平均燃耗率：${(avg.burnRate / n).toFixed(0)} tokens/輪`); console.log(` 平均工具錯誤率：${(avg.errorRate / n * 100).toFixed(1)}%`); 接著來執行 node efficiency-report.mjs # 過去 30 天 node efficiency-report.mjs 7 # 過去 7 天 D:\Git\mathTalk\class\Claude-Code-Analytics\src>node monthly-report.mjs 生成 30 天報告中... 月報已生成：ce-report-2026-06-12.md Sessions：130 個 總費用：$12460.32 USD 平均效率分：32.9 最後的報告如下： 一、整體概覽 指標 數值 總 Session 數 130 總 Token 用量 33,902,068 估算總費用 $12460.32 USD 平均每 Session 費用 $95.8486 USD 估算月費用 $12460.32 USD 平均效率分數 32.9 / 100 二、效率分布 等級 數量 比例 A（高效，80+） 31 24% B（正常，60-79） 10 8% ️ C（偏低，40-59） 7 5% D（高摩擦，<40） 82 63%
Workflow vs Agent 架構解析：Anthropic Building Effective Agents 重點整理
今天來看下這篇文章，第一次看的時候沒意識到重要性，後來看某個面試官在闡述 agent 工程師的區別時特地説到這篇文章才想起來 Anthropic building-effective-agents Anthropic 觀察了幾十個在生產環境落地的 LLM 系統，發現一個反直覺的規律：最成功的實作往往不是用最複雜的框架，而是用最簡單、可組合的模式。 他們把所有涉及 LLM 自主行動的系統統稱為 agentic system，但在這個概念下做了一個關鍵的架構區分—— Workflow 控制流由工程師寫死在程式碼中 LLM 是其中一個執行節點 行為可預測、可重複 適合任務邊界清晰的場景 Agent LLM 自己決定下一步怎麼做 工程師設計工具與邊界，不寫路徑 行為具有彈性、開放性 適合任務路徑不可預先知道的場景 核心差異不是「有沒有用 LLM」，而是控制流的所有權歸誰。Workflow 是工程師擁有控制流，Agent 是 LLM 擁有控制流。 Workflow 的五種標準模式 文章列出了五種在生產環境被反覆驗證的 workflow 模式，每一個都有清晰的適用場景： Prompt chaining 任務拆解成固定的串列步驟，前一步的輸出作為下一步的輸入。適合需要換精確度換延遲的場景。 Routing 先分類輸入，再導向不同的下游處理路徑。讓不同類型的任務各自優化，互不干擾。 Parallelization 同時跑多個 LLM call，結果再彙整。分為任務切分（sectioning）和多數決（voting）兩種形式。 Orchestrator-workers 一個 LLM 負責拆解任務、分配工作，多個 worker LLM 執行。子任務動態決定，不是預先寫死。 Evaluator-optimizer 一個 LLM 生成、另一個評估並給回饋，形成迴圈。適合輸出品質有明確評判標準的任務。 值得注意的是：orchestrator-workers 雖然看起來很像「agent」，但如果整個 orchestration 邏輯是由工程師寫死，LLM 只是按指令執行，它本質上仍是 workflow，只是更靈活的那種。 Agent 的本質：LLM 在環境回饋中自主循環 文章對 agent 的定義很精簡：LLM 在一個迴圈中，根據環境回饋（工具呼叫結果、程式執行結果）自主決策，直到任務完成或達到停止條件。 Agents can handle sophisticated tasks, but their implementation is often straightforward. They are typically just LLMs using tools based on environmental feedback in a loop. Agent 的適用場景有兩個核心特徵：任務所需的步驟數量無法預先知道，以及正確路徑不能被硬編碼。 代價也很清楚：更高的延遲與成本、錯誤可能累積、需要更多測試與 guardrail。 我認為 workflow 和 agent 的區別，不只是技術架構的選擇，更是工程師思維方式的分水嶺。 Workflow 工程師的核心能力 Workflow 工程師的工作本質是系統設計與流程分解。他們需要把模糊的業務需求拆解成 LLM 可以處理的固定步驟，在每個節點插入適當的驗證與 gate，設計出可預測、可觀測、可維護的流程。這更接近傳統軟體工程的思維。 Agent 工程師的核心能力 Agent 工程師的工作核心則轉向工具介面設計（ACI）與信任邊界管理。你不再寫「做 A 然後做 B 然後做 C」，而是設計「給 LLM 什麼工具、工具的文件怎麼寫、失敗的時候怎麼恢復、在哪些節點需要人工確認」。 文章最犀利的觀察之一：他們在開發 SWE-bench agent 時，花在優化工具上的時間比花在優化 prompt 上的時間還要多。這個細節揭示了 agent 工程的重心所在——不是提示詞魔法，而是介面設計。 兩者都需要的判斷力 文章反覆強調的一個原則是：不要過度建構。很多時候，一個被良好優化的單一 LLM 呼叫就夠了。真正困難的判斷是——什麼時候從 workflow 升級到 agent？ 判斷標準很明確：如果任務的正確路徑可以被預先寫下來，用 workflow。如果連「需要哪些步驟」本身都是未知的，才需要 agent 的自主性。 三個核心原則 文章最後給出三條設計 agent 的指導方針，我認為這三條同樣適用於評估一個 agent 工程師的成熟度： 1、Simplicity：保持 agent 設計的簡潔。複雜性應該因需求增長，而非因工程師的偏好增長。 2、Transparency：明確展示 agent 的規劃步驟。可觀測性是信任的基礎，也是除錯的前提。 3、ACI（Agent-Computer Interface）：像對待 HCI 一樣用心設計工具介面。工具文件、參數命名、錯誤防護，是 agent 可靠性的根本。
Claude Code Token 費用分析：費用計算、工具統計與 Web Dashboard 實作
這個章節要説明關於 Claude Code Analytics 的部分内容 計算 token 費用、分析工具使用模式、建立 Web Dashboard 首先加入 Token 計算器 這邊要查詢所有 session，計算每個 session 的 token 用量與美金費用 新增費用計算模組 新增 lib/costs.mjs： const PRICING = { 'claude-opus-4': { in: 15, out: 75, cw: 18.75, cr: 1.50 }, 'claude-sonnet-4': { in: 3, out: 15, cw: 3.75, cr: 0.30 }, 'claude-haiku-4': { in: 0.80, out: 4, cw: 1.00, cr: 0.08 }, }; /** * 從 model ID 推斷定價方案 */ function getPricing(modelId) { const id = modelId?.toLowerCase() ?? ''; if (id.includes('opus')) return PRICING['claude-opus-4']; if (id.includes('haiku')) return PRICING['claude-haiku-4']; return PRICING['claude-sonnet-4']; } /** * 計算單條 assistant 訊息的費用 */ export function calcMessageCost(msg) { if (msg.type !== 'assistant') return 0; const usage = msg.message?.usage; if (!usage) return 0; const p = getPricing(msg.message?.model); const M = 1_000_000; return ( (usage.input_tokens ?? 0) / M * p.in + (usage.output_tokens ?? 0) / M * p.out + (usage.cache_creation_input_tokens ?? 0) / M * p.cw + (usage.cache_read_input_tokens ?? 0) / M * p.cr ); } /** * 計算整個 session 的 token 統計與費用 */ export function calcSessionStats(messages) { let input = 0, output = 0, cacheWrite = 0, cacheRead = 0, costUSD = 0; let models = new Set(); let toolCalls = 0, toolErrors = 0; for (const msg of messages) { if (msg.type !== 'assistant') continue; const usage = msg.message?.usage; if (usage) { input += usage.input_tokens ?? 0; output += usage.output_tokens ?? 0; cacheWrite += usage.cache_creation_input_tokens ?? 0; cacheRead += usage.cache_read_input_tokens ?? 0; costUSD += calcMessageCost(msg); } if (msg.message?.model) models.add(msg.message.model); for (const c of msg.message?.content ?? []) { if (c.type === 'tool_use') toolCalls++; } } // 計算 tool 錯誤數（在 user 的 tool_result 中） for (const msg of messages) { if (msg.type !== 'user') continue; for (const c of msg.message?.content ?? []) { if (c.type === 'tool_result' && c.is_error) toolErrors++; } } return { input_tokens: input, output_tokens: output, cache_write_tokens: cacheWrite, cache_read_tokens: cacheRead, total_tokens: input + output + cacheWrite + cacheRead, cost_usd: costUSD, models: [...models], tool_calls: toolCalls, tool_errors: toolErrors, }; } 新增 Token 報告腳本 新增 token-report.mjs： import { scanProjects, scanProjectSessions } from './lib/scanner.mjs'; import { parseJsonl } from './lib/parser.mjs'; import { calcSessionStats } from './lib/costs.mjs'; const DAYS = parseInt(process.argv[2] ?? '30', 10); const cutoff = new Date(); cutoff.setDate(cutoff.getDate() - DAYS); console.log(`\n Claude Code Token 報告（過去 ${DAYS} 天）\n`); let totalCost = 0; let totalTokens = 0; let totalSessions = 0; const projectStats = []; for (const proj of scanProjects()) { let projCost = 0; let projTokens = 0; let projSessions = 0; for (const session of scanProjectSessions(proj)) { const sessionStart = new Date(session.first_message_time); if (sessionStart < cutoff) continue; const messages = parseJsonl(session.file_path); const stats = calcSessionStats(messages); projCost += stats.cost_usd; projTokens += stats.total_tokens; projSessions++; } if (projSessions === 0) continue; projectStats.push({ name: proj.name.slice(0, 35), sessions: projSessions, tokens: projTokens, cost: projCost, }); totalCost += projCost; totalTokens += projTokens; totalSessions += projSessions; } // 排序並輸出 projectStats.sort((a, b) => b.cost - a.cost); console.log('專案費用排行：\n'); console.log(' ' + '專案名稱'.padEnd(36) + 'Sessions'.padStart(9) + ' Tokens'.padStart(12) + ' 費用 (USD)'.padStart(12)); console.log(' ' + '─'.repeat(72)); for (const p of projectStats) { const name = p.name.padEnd(36); const sess = String(p.sessions).padStart(9); const tok = p.tokens.toLocaleString().padStart(12); const cost = `$${p.cost.toFixed(4)}`.padStart(12); console.log(` ${name}${sess} ${tok} ${cost}`); } console.log('\n' + ' ' + '─'.repeat(72)); console.log(` ${'合計'.padEnd(36)}${String(totalSessions).padStart(9)} ${totalTokens.toLocaleString().padStart(12)} ${'$'+totalCost.toFixed(4)}`); // 平均費用 if (totalSessions > 0) { console.log(`\n平均每 session 費用：$${(totalCost / totalSessions).toFixed(4)}`); console.log(`平均每天費用： $${(totalCost / DAYS).toFixed(4)}`); console.log(`估算月費用： $${(totalCost / DAYS * 30).toFixed(2)}`); } 過去 7 天 node token-report.mjs 7 專案費用排行： 專案名稱 Sessions Tokens 費用 (USD) ──────────────────────────────────────────────────────────────────────── D--OlgCase-R****R-3 7 184,270,495 $498.5703 D--Git-Cla*-***deEg 2 102,513,672 $252.1710 d--Git-obsi-w****e-brain 3 89,158,775 $248.8697 ... ──────────────────────────────────────────────────────────────────────── 合計 28 463,693,230 $1297.4101 平均每 session 費用：$46.3361 平均每天費用： $185.3443 估算月費用： $5560.33 這章節新增 Session Analytics 分析工具使用頻率統計，加上用活躍時段直方圖來表現 新增 Analytics 模組 新增 lib/analytics.mjs： import { parseJsonl } from './parser.mjs'; /** * 提取所有工具呼叫 */ export function extractToolCalls(messages) { const calls = []; for (const msg of messages) { if (msg.type !== 'assistant') continue; for (const c of msg.message?.content ?? []) { if (c.type === 'tool_use') { calls.push({ name: c.name, timestamp: msg.timestamp }); } } } return calls; } /** * 計算工具使用頻率 */ export function toolFrequency(sessions) { const freq = {}; for (const { messages } of sessions) { for (const call of extractToolCalls(messages)) { freq[call.name] = (freq[call.name] ?? 0) + 1; } } return Object.entries(freq) .sort(([, a], [, b]) => b - a) .map(([name, count]) => ({ name, count })); } /** * 計算活躍時段（0-23 時） */ export function activeHours(sessions) { const hours = new Array(24).fill(0); for (const { first_message_time } of sessions) { const h = new Date(first_message_time).getHours(); hours[h]++; } return hours; } /** * 計算活躍星期（0=週日...6=週六） */ export function activeDays(sessions) { const days = new Array(7).fill(0); const dayNames = ['週日','週一','週二','週三','週四','週五','週六']; for (const { first_message_time } of sessions) { const d = new Date(first_message_time).getDay(); days[d]++; } return days.map((count, i) => ({ day: dayNames[i], count })); } /** * 命令列直方圖（最大寬度 30 格） */ export function cliHistogram(data, labelKey, valueKey, title) { const max = Math.max(...data.map(d => d[valueKey]), 1); console.log(`\n${title}\n`); for (const item of data) { const bar = '█'.repeat(Math.round(item[valueKey] / max * 30)); const label = String(item[labelKey]).padStart(6); console.log(` ${label} ${bar.padEnd(30)} ${item[valueKey]}`); } } 新增 Analytics 報告腳本 新增 analytics-report.mjs： import { scanProjects, scanProjectSessions } from './lib/scanner.mjs'; import { parseJsonl } from './lib/parser.mjs'; import { toolFrequency, activeHours, activeDays, cliHistogram } from './lib/analytics.mjs'; // 載入所有近 30 天的 sessions const DAYS = 30; const cutoff = new Date(); cutoff.setDate(cutoff.getDate() - DAYS); const allSessions = []; for (const proj of scanProjects()) { for (const session of scanProjectSessions(proj)) { if (new Date(session.first_message_time) < cutoff) continue; const messages = parseJsonl(session.file_path); allSessions.push({ ...session, messages }); } } console.log(`\n Claude Code 使用分析（過去 ${DAYS} 天，${allSessions.length} 個 sessions）\n`); // 1. 工具頻率 const freqData = toolFrequency(allSessions).slice(0, 12); cliHistogram(freqData, 'name', 'count', ' 工具使用頻率 TOP 12'); // 2. 活躍時段 const hours = activeHours(allSessions); const hourData = hours.map((count, h) => ({ label: `${String(h).padStart(2,'0')}:00`, count })); cliHistogram(hourData, 'label', 'count', ' 活躍時段分析（每小時 session 數）'); // 3. 活躍星期 const dayData = activeDays(allSessions); cliHistogram(dayData, 'day', 'count', ' 活躍星期分析'); // 4. 最高效 session（工具數 / 來回輪數 比例） const ranked = allSessions .map(s => { const turns = s.messages.filter(m => m.type === 'assistant').length; const tools = s.messages.flatMap(m => m.type === 'assistant' ? (m.message?.content ?? []).filter(c => c.type === 'tool_use') : [] ).length; return { ...s, turns, tools, efficiency: turns > 0 ? tools / turns : 0 }; }) .sort((a, b) => b.efficiency - a.efficiency) .slice(0, 5); console.log('\n 工具密度最高的 Sessions（每輪工具數）\n'); for (const s of ranked) { const date = new Date(s.first_message_time).toLocaleDateString(); const summary = (s.summary ?? '（無摘要）').slice(0, 50); console.log(` [${date}] ${s.turns}輪 ${s.tools}個工具 效率:${s.efficiency.toFixed(1)}`); console.log(` ${summary}`); } 結果如下： Claude Code 使用分析（過去 30 天，129 個 sessions） 工具使用頻率 TOP 12 Bash ██████████████████████████████ 1730 Read █████████████████████████████ 1699 Edit █████████████████████████ 1463 Write ███████████████ 838 Grep ██████████████ 829 TaskUpdate ███████ 417 Glob ████ 244 TaskCreate ████ 224 ToolSearch ███ 159 AskUserQuestion ███ 148 mcp__UnityMCP__read_console ██ 142 Agent ██ 126 活躍時段分析（每小時 session 數） 00:00 0 01:00 0 02:00 0 03:00 0 04:00 0 05:00 0 06:00 0 07:00 0 08:00 0 09:00 ███████ 7 10:00 ██████████████████████████████ 30 11:00 █████ 5 12:00 ██████ 6 13:00 ████████ 8 14:00 ██████████ 10 15:00 █████████ 9 16:00 ████████████ 12 17:00 █████████████████ 17 18:00 █████████ 9 19:00 ██████████████ 14 20:00 █ 1 21:00 █ 1 22:00 0 23:00 0 活躍星期分析 週日 0 週一 ██████████████████████████████ 39 週二 ███████████████ 19 週三 ███████████ 14 週四 █████████████████ 22 週五 ██████████████████ 24 週六 ████████ 11 ⚡ 工具密度最高的 Sessions（每輪工具數） [2026/5/11] 30輪 24個工具 效率:0.8 （無摘要） [2026/5/13] 321輪 236個工具 效率:0.7 （無摘要） [2026/5/14] 137輪 100個工具 效率:0.7 （無摘要） [2026/5/21] 22輪 16個工具 效率:0.7 （無摘要） [2026/5/28] 51輪 37個工具 效率:0.7 （無摘要） 最後面建立 Dashboard HTML 這邊就不多説了直接展示效果 node server.mjs # 開啟瀏覽器：http://localhost:3000/dashboard.html
深入解析 Claude Code Session History：JSONL 格式、訊息結構與快取費用優化
session保存格式結構如下： json格式化如下： 為什麼選擇 JSONL？ Claude Code 沒有使用資料庫，而是選擇了最簡單的格式：每個 session 一個 .jsonl 檔案，每行一條 JSON 訊息。 比較面向 JSONL 格式 關聯式資料庫 讀取方式 任何文字編輯器、cat 需要 DB 驅動 追加速度 極快（append-only） 需要 transaction 查詢能力 低（需全掃） 高（SQL） 跨機器遷移 複製檔案即可 需要 dump/restore 故障恢復 行級別，單行壞掉不影響其他行 依賴 transaction log 版本控制 可用 git 追蹤 不適合 結論：AI 對話是只增不改、需要人類可讀、不需要複雜查詢的應用場景，所以JSONL 是最合適的選擇。 然後可以看到jsonl裏面的chat内容包含以下 session 啟動 ├── 寫入第一條 user 訊息 ├── 寫入 assistant 回覆 ├── 寫入 tool_result ├── 寫入下一條 user 訊息 ... └── session 結束（檔案不再修改） 這説明在 ~/.claude/projects/ 裡每一個 .jsonl 檔案都對應一個獨立的 session。如下： ~/.claude/ ├── projects/ │ └── -Users-jack-myproject/ ← 專案目錄（路徑編碼） │ ├── abc123.jsonl ← session 1 │ ├── def456.jsonl ← session 2 │ └── ghi789.jsonl ← session 3 └── settings.json 然後可以看到訊息結構如下： 頂層訊息結構 { "uuid": "3a7f-...", // 每條訊息的唯一 ID "parentUuid": "2b1e-...", // 父訊息 ID（形成對話樹） "sessionId": "abc-123", // 所在 session 的 ID "timestamp": "2025-06-15T10:30:00.000Z", "type": "user", // 訊息類型（見 §3） "isSidechain": false, // 是否為旁支對話 "cwd": "/Users/jack/myproject", "version": "1.0.35", "message": { ... }, // 核心內容（見下） "toolUseResult": { ... } // 工具執行結果 } User 訊息： { "message": { "role": "user", "content": "幫我分析這段程式碼" } } Assistant 訊息（含 token 統計）： { "message": { "role": "assistant", "id": "msg_01...", "model": "claude-opus-4-20250514", "content": [ ... ], "stop_reason": "tool_use", "usage": { "input_tokens": 5420, "output_tokens": 312, "cache_creation_input_tokens": 20238, "cache_read_input_tokens": 18500 } } } 然後訊息分爲以下幾類 type 觸發時機 包含的關鍵資訊 user 你送出任何訊息 你的文字、tool_result assistant Claude 回覆 文字、tool_use、usage summary 執行 /compact 後 msg.summary（摘要文字） system Session 初始化 系統提示詞 然後 Assistant message 的 usage 欄位 欄位 說明 費用倍數（相對 input） input_tokens 本次送出的 token 數 1× output_tokens Claude 生成的 token 數 5× (opus-4) cache_creation_input_tokens 首次建立 cache 的 token 1.25× cache_read_input_tokens 從 cache 讀取的 token 0.1× 這裏面關於 token 的重要信息 Claude Code 會自動快取你的 CLAUDE.md、專案檔案等長文件。當同一 session 中多次發送相似的上下文，後面的訊息可以從快取讀取，費用只有原來的 10%。 實際例子： 沒有快取：20,000 input tokens × $15/MTok = $0.30 有快取命中：20,000 cache_read tokens × $1.50/MTok = $0.03（省 90%）
在大型遊戲專案中使用 Claude Code：分析 Session 並設計通用 Skills
本文以一個中型 Unity 單機遊戲專案為基礎，探討在程式碼總量逼近百萬 token 的情境下， 如何透過設計專案專屬的 Claude Code Skills，有效降低 token 消耗並提升查詢效率。 一、Session 數據初步觀察 從 token 總量與增長趨勢來看，本次 session 並無明顯異常。 然而，考慮到大型遊戲專案的程式碼總量可能突破百萬 token，核心問題在於： 當專案規模達到這個等級時，Claude Code 的 token 消耗是否會出現急劇增加的情況？ 然而，考慮到大型遊戲專案的程式碼總量可能突破百萬 token，核心問題在於： 當專案規模達到這個等級時，Claude Code 的 token 消耗是否會出現急劇增加的情況？ 回頭檢視 Loading Trace（CC 每次啟動時記錄的工具載入紀錄）後發現， 本次 session 中沒有任何 Skills 被觸發。另一個 session 也僅觸發了一個 Skill。 另外一個 session 也只有觸發一個 skill 這是因為目前的專案規模只屬於中型：無大型底層架構、無 Client/Server 架構（目前為單機遊戲）， 整體框架較小，尚未到需要 Skills 介入的門檻。 二、為什麼需要設計專案專屬 Skills？ 將專案截圖與目錄結構提供給 Claude Code 分析後，得到的關鍵洞察是： 我目前沒有設計對應skill，這個項目嚴格說算中型而已，所以我才提這個需求，以後應該會用上，你有辦法根據這個去設計 skill 嗎？需要修改對應項目設計嗎？（譬如script都放到Assets\\Scripts），然後這是另外的session，但都是同一個項目 這上面已經間接説明了，項目目錄規劃是能夠極大的優化 cc 的查詢效益，當然這是基於目前的 agent runtime 而言。 這是「用 200 行 markdown 救 8 次整檔 Read」的最高 ROI。長這樣（草稿）： --- name: unity-script-index description: | 在 D:\OlgCase\**** Unity 專案中定位 C# 腳本。 當使用者問「某某邏輯在哪 / 哪個檔處理 X / 我要改 Y 系統」時觸發。 返回目錄分區索引 + 一行用途，避免整檔 Read。 --- ## Assets\Scripts 目錄速查 - `Module/` — 遊戲核心系統。StageManager*（關卡）, SkinUnlockManager（外觀解鎖）... - `AI/` — AI 行為。AIUtils 是共用工具 - `UI/` / `NewUI/` — UI 控制（NewUI 較新） - `Data/` — 設定資料載入 - `Utils/` — 通用工具，含 GmCommand* GM 命令系統 - `EventCtrl/` — 事件 / 條件處理 - `Luban/def/` — Luban 生成的設定 schema（**勿手改**） ## 使用流程 1. 先 Grep 關鍵字（類別名 / 方法名）找精準位置 2. 再 Read with offset/limit，不要整檔 3. 如果關鍵字落在 Luban\def\，那是生成檔 — 真正的設定在 Resources\design\*.json 三、根據工單類型設計 Skill 觸發邏輯 實務上，工單通常分為兩類：修 Bug 與 新增需求。 兩者的資訊結構不同，對應的 Skill 觸發邏輯也應有所區別。 下列是發送給 cc 的内容 可行，不過我建議你參考下我在工單系統填寫的方式分析下 觸發邏輯是否需要修改：（下面有分 bug、需求兩種，我覺得你應該需要分析觸發區別） Case1. 【修bug】 在切換到我方角色時會瞬間卡頓，測試發現是計算威脅值太多了，如果敵方有40個entity，就要計算40次，單次都必須2~3ms，全部加起來就必須 50~60 ms Assets\Scripts\AI\AIUtils.cs line.596 AIUtils.GetMoveRangeChunkId 從這裏觸發 Assets\Scripts\Module\StageManager.cs line.1324 genFlowMap 這個執行的次數太多 Case2. 【新增需求類】 在 AICondition 的每一個 AICondBlock 都加入 Params5 參數，這是用來代表該 block 觸發後的冷卻效果，參數設定規則如下： colddown: -1,只會使用一次 colddown: 0 or 沒填, 正常使用(機率骰到 符合條件就會用) colddown: >0, 用一次後進入CD,CD歸0後才可以再度使用 Case3. 【新增需求類】 播放op功能 這是播放video的ui脚本 unity\Assets\Scripts\NewUI\NewUIPlayVideo.cs 這邊要改成，就是播放影片（無聲音），然後加上 字母、語音、bgm 等素材内容 我的想法是類似 youtube 的概念，不確定是否跟下面的一樣，在時間點 00:00:35,760 播放 字幕/語音/bgm 00:00:35,760 --> 00:00:45,120 那么今天所讲那个命理啊叫子平法子平是个徐 PLUS. 很多時候我們使用 AI（cc）一定要有一個清晰的認知就是，在cc回答你之前你就已經有一個具備雛形的答案，cc給的内容通常不會太少，更多的是掃描過後訓練自己在 1分鐘内容消化完並確認方案，當然這只是防止被 cc 拐騙 討論的過程通常是繁瑣的，更多只是厘清自己跟cc之間的想法是否同步到位， 最終規劃如下： 設計重點(回應你最初的觀察) - 觸發語按你工單的填寫習慣寫:bug 工單慣附 file:line → unity-bugfix 描述明確說「位置工單已給,不要掃目錄」;需求工單只給概念 → unity-modify-all 反而靠掃描找全。三個 description 彼此區隔,Loading Trace 的 SKILL INVOCATIONS 就能直接看出每張工單是哪一類。 - 與既有 skill 對齊:沿用 version + origin: project frontmatter,跟 unity-driver / auto-test 一致。 - 塞進了探索時發現的真實知識: - unity-modify-all 內含 Luban 生成檔警示(AICondBlock 在 Luban/bean/,手改會被覆蓋,要改設定源頭),並提醒 Params5 冷卻在 wp#102 已做過,沿用既有模式 - unity-bugfix 標出三個已知熱區:AI/Smart/ThreatAnalyzer.cs、AIUtils.GetMoveRangeChunkId、StageManager.genFlowMap(遞迴) - unity-new-feature 給了媒體/演出系統地圖,並建議 Case 3 用 Timeline/ Playable 而非自己輪詢 SRT 接下來比較花時間的就是測試跟搜集數據了 重新進入 cc 確認 skills 載入 前面都是獲取 mcp 這段就能看出工單可以優化，因爲我目前會將 cc 内容同步到工單備注，好處是不需要搜索項目也能理解，但缺點就是 token 可能會多幾百 可以看到 cc 找到了對應 skill 然後 cc 組裝出一份結構 等執行完成后我們回頭來檢查上下文消耗，加入了 skills 后竟然跑了 16m，雖然這個功能跨度比較大 實際還是看看統計數據 從這邊看到 skill 被載入了 不過實際上呢必須使用一段時間才能分析出 token 使用消耗情況，另外，因爲目前項目並不大可能在觀測數據上也較難發現效益。 只是原則上 cc 的使用是必須根據項目做調調整的，cc 跟 codex、kiro 都不一樣，他是輕度設定概念，也就是必須根據使用建構合適的配置 總結 Skill 路由成功：新 session 在第 7 輪即觸發 unity-bugfix，路由精準。 整檔讀取率從 14% 降至 5.6%：Skills 的「精準讀取」指示直接改變了 CC 的讀檔行為。 搜尋效率大幅提升：搜尋次數從 145 次降至 13 次，因為 CC 有了目錄地圖與精確位置，不再需要盲目摸索。 目前專案規模偏中型，部分效益尚未完整顯現。後續將持續觀察 token 使用數據，進行二次分析與 Skill 迭代優化。 Claude Code 的使用方式本就需要根據專案調整——不同於 Codex 或 Kiro， CC 是輕度設定概念，核心在於根據實際使用情境，逐步建構出最合適的配置。
Anthropic 改條款砍掉 96% AFK 額度：衝擊、競品動向與 Infra 因應策略
Anthropic 上週宣布，6/15 起訂閱額度一分為二。 人類在場、有互動的 Human-in-the-loop 用途（像是 Claude Code 日常使用）繼續吃原本的額度 自動化工作流 Away From Keyboard（AFK）則改吃獨立的月度 credit——Pro 給 $20、Max 5x 給 $100、Max 20x 給 $200 這對 AFK 工作流和 harness engineering 來說是一記重拳，但同時也是重新審視自己 agent infra 的好時機。 社群估算，Max 20x 的重度用戶，每個月 AFK 跑下來消耗的算力大約等值 $5,000 API credit。現在切成 $200 credit。$5,000 變 $200，是 96% 的砍幅，不是 40%。 Anthropic 的真正算盤，並不是省成本。這個動作和 4 月封殺 OpenClaw 是同一條邏輯線——把「訂閱」重新定義為「人類在場、有互動」的場景，把 AFK 推到按量計費的 API。訂閱是給你坐在螢幕前用的，不是給機器人跑的。 對高階個人用戶：harness engineering 直接中槍 高階工程師已經炸鍋了。賣出第一門 Claude Code 付費課程的 Matt Pocock 公開說： I have never experienced such a frustrating lack of clarity over the basic terms of usage. 然後直接在 X 上把學員導流到 Codex。這不是情緒發洩，是他的課程商業模式受到直接衝擊——工具鏈綁死在一家供應商，對方一改條款，學員手上全是過期教材。 對在公司裡建立 harness engineering 的人來說，這更是一記重傷。 Harness 的核心價值，就是把 AI 變成「可被工程化調度的工作流」，具體來說包括： 批次跑任務 自動讀 issue 自動開 PR 多輪 code review 背景研究 多 agent 並行 CI/CD 裡自動修 bug 半夜自己跑測試、整理結果、回報狀態 這些全都是 AFK。Anthropic 這次等於是把 harness engineering 最重要的那條路切出來，丟進一個小得可憐的 credit pool。 競爭對手趁虛而入 對手當然不會放過這個空檔。 ChatGPT Pro 從 $200 直接砍到 $100；Codex 企業版提供兩個月免費試用，明確鎖定從 Anthropic 遷移的客戶，0 seat fee、30 天遷移窗口。Altman 甚至在公告底下回了一句「ok boomer」。 廣大網友一片歡呼——還好 GPT-4.1 夠猛，換過去反而有升級感。 老實說，我最近也覺得 Codex 出的 code 品質又快又好。 重新思考你的 Agent Infra 話說回來，別以為跳過去 Codex 就解決問題了。這件事更值得讓你想清楚一件事：你的 agent infra 夠健壯嗎？ 第一，vendor lock-in 就是風險。 供應商可以在週四傍晚改條款，你的自動化流程隔天就停一半。以前 vendor lock-in 的風險是理論，現在是帳單。如果你的核心業務壓在單一供應商的 API 上，這個週末應該花兩小時認真評估一下切換成本。 第二，個人開發者可以考慮 Claude + Codex 雙棲。 互動式寫程式留 Claude，AFK 自動化任務走 Codex 或 OpenAI API，費用結構比較適合批次跑。兩家都養著，哪家改條款你都有退路。別忘了，ChatGPT 以前也搞過不少讓人不爽的事⋯⋯ 第三，地端才是企業 harness engineering 的最終歸宿。 地端不只是資安問題，更是風險控管。現在 LLM 廠商太不穩定，把 infra 壓在雲端、跟著廠商反覆橫跳，只是暫時解法。有地端算力，才是真正的核心競爭力。 Qwen3 27B、Gemma 4 31B 這個量級的 dense 開源模型，在多數 coding 任務上已經堪用。這不是在省 token 費，而是你願意花多少代價，押在一份不知道哪天又會改的條款上。