摘要(繁中)
toolCalls({toolName: count})是 dashboard 多處共用的契約(tc['Skill'] 偵測、tool chips、tool-utilization)。PR #94 早期版本把 Skill:<name> 展開塞進 toolCalls,污染了這個契約並造成 dashboard 連鎖問題;最終改為新增獨立的 skillCalls index 欄位,讓 toolCalls 維持乾淨、dashboard 零改動,per-skill 粒度只給 ccxray usage 用。這個「為何分兩個欄位」的決策目前只散落在 commit message 裡。建議寫一份輕量決策記錄(ADR),避免未來有人「為了少一個欄位」又把兩者合併、重蹈覆轍。此 issue 不在 PR #94 處理,優先度低。
Background
toolCalls is a shared contract consumed by the dashboard (public/miller-columns.js tc['Skill'] detection, entry-rendering.js tool chips, tool-utilization counting). An early PR #94 revision expanded Skill/Workflow tool calls to Skill:<name> keys inside extractToolCalls, which polluted that contract and broke dashboard consumers. The fix introduced a separate skillCalls index field, leaving toolCalls untouched.
This decision is currently only recorded in a commit message. An ADR would prevent a future "let's merge these two fields" regression.
Proposal
Add a lightweight decision record (e.g. docs/decisions/0001-toolcalls-vs-skillcalls.md) capturing:
- Context:
toolCalls is a dashboard contract; per-skill granularity is only needed by ccxray usage, which reads the summarized index (not raw messages).
- Decision: keep
toolCalls a plain {toolName: count} map; add a separate skillCalls {skillName: count} index field populated by extractSkillCalls (Anthropic only; Workflow excluded — it has no skill input).
- Consequences: dashboard needs zero changes; old entries without
skillCalls degrade to (pre-tracking); a small denormalization (toolCalls.Skill count == sum of skillCalls) is accepted as inherent to an index.
- Rejected alternative: expanding
Skill:<name> into toolCalls and teaching every consumer to collapse it (fragile; spreads contract knowledge).
Data flow
messages[].content[] (tool_use blocks)
│
┌───────────────┴────────────────┐
▼ ▼
extractToolCalls(msgs) extractSkillCalls(msgs)
{Skill:3, Bash:1} {"superpowers:brainstorming":2}
│ │
▼ ▼
toolCalls (index) skillCalls (index)
│ │
┌────┴───────────┐ │
▼ ▼ ▼
dashboard ccxray usage ccxray usage
tc['Skill'], "Tools" section "Skills" section
chips, util. (aggregate) (per-skill invocations/loads)
Acceptance
- A short ADR exists explaining why the two fields are separate and what NOT to do.
- Linked from
docs/normalization-map.md (tool-call extraction section).
Provider scope: why skillCalls is Anthropic-only (investigated)
繁中:經查證,這是結構性的,不是漏做。Codex 協議裡根本沒有「skill 呼叫」這個概念——skill 是 prompt 內的指令清單,不是工具呼叫,所以沒有東西可抽取。
extractSkillCalls relies on Claude Code's first-class Skill tool call:
{ type: "tool_use", name: "Skill", input: { skill: "<name>" } } // discrete, countable, attributable
Codex (OpenAI Responses) has no such tool. Evidence from test/fixtures/codex-sessions/*.jsonl:
- The only
function_call names are exec_command (×10) and write_stdin (×2) — no Skill / skill-* tool.
- Codex advertises skills as prompt instructions, not tools: a
<skills_instructions> block lists available skills with their SKILL.md paths ("A skill is a set of local instructions ... stored in a SKILL.md file"). Every SKILL.md occurrence is in that listing; no exec_command reads one in the fixtures.
So Codex follows a skill's instructions inline — there is nothing in the wire format to attribute to "skill X was invoked". Even a cat SKILL.md would surface as a generic exec_command (Bash), not a skill call.
Implication for the ADR: record that skillCalls is intentionally Anthropic-only because the concept does not exist in the Codex protocol. If Codex per-skill stats are ever wanted, they need a different, lossy mechanism (parse <skills_instructions> for the available set, then heuristically detect usage) — out of scope for the skillCalls field.
Not for PR #94 — optional follow-up; lower priority.
摘要(繁中)
toolCalls({toolName: count})是 dashboard 多處共用的契約(tc['Skill']偵測、tool chips、tool-utilization)。PR #94 早期版本把Skill:<name>展開塞進toolCalls,污染了這個契約並造成 dashboard 連鎖問題;最終改為新增獨立的skillCallsindex 欄位,讓toolCalls維持乾淨、dashboard 零改動,per-skill 粒度只給ccxray usage用。這個「為何分兩個欄位」的決策目前只散落在 commit message 裡。建議寫一份輕量決策記錄(ADR),避免未來有人「為了少一個欄位」又把兩者合併、重蹈覆轍。此 issue 不在 PR #94 處理,優先度低。Background
toolCallsis a shared contract consumed by the dashboard (public/miller-columns.jstc['Skill']detection,entry-rendering.jstool chips, tool-utilization counting). An early PR #94 revision expandedSkill/Workflowtool calls toSkill:<name>keys insideextractToolCalls, which polluted that contract and broke dashboard consumers. The fix introduced a separateskillCallsindex field, leavingtoolCallsuntouched.This decision is currently only recorded in a commit message. An ADR would prevent a future "let's merge these two fields" regression.
Proposal
Add a lightweight decision record (e.g.
docs/decisions/0001-toolcalls-vs-skillcalls.md) capturing:toolCallsis a dashboard contract; per-skill granularity is only needed byccxray usage, which reads the summarized index (not raw messages).toolCallsa plain{toolName: count}map; add a separateskillCalls{skillName: count}index field populated byextractSkillCalls(Anthropic only;Workflowexcluded — it has noskillinput).skillCallsdegrade to(pre-tracking); a small denormalization (toolCalls.Skillcount == sum ofskillCalls) is accepted as inherent to an index.Skill:<name>intotoolCallsand teaching every consumer to collapse it (fragile; spreads contract knowledge).Data flow
Acceptance
docs/normalization-map.md(tool-call extraction section).Provider scope: why
skillCallsis Anthropic-only (investigated)繁中:經查證,這是結構性的,不是漏做。Codex 協議裡根本沒有「skill 呼叫」這個概念——skill 是 prompt 內的指令清單,不是工具呼叫,所以沒有東西可抽取。
extractSkillCallsrelies on Claude Code's first-classSkilltool call:Codex (OpenAI Responses) has no such tool. Evidence from
test/fixtures/codex-sessions/*.jsonl:function_callnames areexec_command(×10) andwrite_stdin(×2) — noSkill/skill-*tool.<skills_instructions>block lists available skills with theirSKILL.mdpaths ("A skill is a set of local instructions ... stored in aSKILL.mdfile"). EverySKILL.mdoccurrence is in that listing; noexec_commandreads one in the fixtures.So Codex follows a skill's instructions inline — there is nothing in the wire format to attribute to "skill X was invoked". Even a
cat SKILL.mdwould surface as a genericexec_command(Bash), not a skill call.Implication for the ADR: record that
skillCallsis intentionally Anthropic-only because the concept does not exist in the Codex protocol. If Codex per-skill stats are ever wanted, they need a different, lossy mechanism (parse<skills_instructions>for the available set, then heuristically detect usage) — out of scope for theskillCallsfield.Not for PR #94 — optional follow-up; lower priority.