oh-my-hi 0.1.2 β†’ 0.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.1.3] - 2026-03-31
4
+
5
+ ### Added
6
+ - Dashboard guide document (GUIDE.md) with detailed walkthrough of each section
7
+ - Update instructions in README and Help page (CLI + in-session commands)
8
+ - Shell-styled command block in Help page
9
+ - Privacy section in README
10
+ - Improved `--data-only` and `--enable-auto` descriptions with bookmark/refresh tips
11
+ - Version display in sidebar now auto-injected from package.json at build time
12
+
3
13
  ## [0.1.2] - 2026-03-31
4
14
 
5
15
  ### Added
package/GUIDE.md ADDED
@@ -0,0 +1,163 @@
1
+ # Dashboard Guide
2
+
3
+ A guide to each section of the oh-my-hi dashboard.
4
+
5
+
6
+ ## Sidebar
7
+
8
+ The left sidebar provides navigation, workspace selection, and date range filtering.
9
+
10
+ ### Workspace Selector
11
+
12
+ Switch between **Global** (your entire Claude Code configuration) and **per-project** scopes. Each scope shows only the harness components and usage data relevant to that workspace.
13
+
14
+ ### Date Range
15
+
16
+ Filter all usage data by time period:
17
+
18
+ | Button | Description |
19
+ |--------|-------------|
20
+ | **7d** | Last 7 days (today inclusive) |
21
+ | **30d** | Last 30 days (today inclusive) |
22
+ | **All** | All collected data since first session |
23
+ | **πŸ“…** | Custom date range picker |
24
+
25
+ Hover over a non-active button to preview the date range in a tooltip. The active range is shown below the buttons.
26
+
27
+ ### Search
28
+
29
+ Type to filter sidebar items by name. Matches are highlighted across all categories.
30
+
31
+ ### Categories
32
+
33
+ Below the main navigation, each harness component category is listed with an item count badge. Click to expand and see individual items; click an item to view its detail page.
34
+
35
+ ---
36
+
37
+ ## Main Pages
38
+
39
+ ### πŸ“Š Harness (Overview)
40
+
41
+ The landing page showing a high-level summary of your harness activity.
42
+
43
+ | Section | Description |
44
+ |---------|-------------|
45
+ | **Stat Cards** | Total usage, skill calls, agent calls, and command invocations for the selected period. Each card shows the change percentage vs. the previous period. |
46
+ | **Category Distribution** | Donut chart showing the proportion of skills, agents, and commands. |
47
+ | **Daily Trend** | Line chart of daily activity (skills + agents + commands combined). |
48
+ | **Popular Skills** | Top 5 most-used skills ranked by call count. |
49
+ | **Activity Heatmap** | GitHub-style calendar heatmap of daily activity intensity. |
50
+ | **Recent Activity** | Timeline of the 10 most recent skill, agent, and command invocations. |
51
+ | **Unused Items** | Skills and agents that were registered but never called in the selected period. |
52
+
53
+ ---
54
+
55
+ ### πŸͺ™ Tokens (Overview)
56
+
57
+ Token usage analytics across all Claude Code sessions.
58
+
59
+ | Section | Description |
60
+ |---------|-------------|
61
+ | **Stat Cards** | Total tokens, input tokens, output tokens, cache tokens β€” each with period-over-period change. |
62
+ | **Estimated Cost** | Total cost, daily average cost, and per-model cost cards. Based on Anthropic API token pricing (not actual CLI subscription billing). |
63
+ | **Cost Calculation** | Expandable section with the pricing formula, collapsible model pricing table (per 1M tokens), and a link to [anthropic.com/pricing](https://www.anthropic.com/pricing). |
64
+ | **Model Distribution** | Donut chart of token usage by model. |
65
+ | **Daily Token Usage** | Line chart of daily token consumption trend. |
66
+ | **Token Activity** | Calendar heatmap of daily token usage intensity. |
67
+ | **Token Usage by Model** | Table breakdown: input, output, cache, total tokens, and estimated cost per model. |
68
+ | **Token Insights** | Auto-generated analysis cards covering cache efficiency, response efficiency, model usage, cost breakdown, daily patterns, and peak hours. |
69
+
70
+ #### Cost Calculation Formula
71
+
72
+ ```
73
+ Cost = (Input Γ— input price + Output Γ— output price
74
+ + Cache Read Γ— cache read price + Cache Write Γ— cache write price) Γ· 1,000,000
75
+ ```
76
+
77
+ Prices are per 1M tokens (USD), sourced from Anthropic's official API pricing. Claude Code CLI is subscription-based, so this estimate is for reference only.
78
+
79
+ ---
80
+
81
+ ### πŸ“‹ Tokens > Analysis
82
+
83
+ Deeper analysis of token consumption patterns.
84
+
85
+ | Section | Description |
86
+ |---------|-------------|
87
+ | **Tokens by Task Category** | Horizontal bar chart grouping token usage by auto-classified work type (code editing, documentation, planning, etc.). Categories are derived from skill/agent descriptions and saved in `task-categories.json` for customization. |
88
+ | **Token Usage by Tool** | Horizontal bar chart of tokens attributed to specific skills, agents, or tools (top 10). |
89
+ | **Prompt Statistics** | Total prompts, average prompt length (chars), short prompt ratio (≀100 chars), long prompt ratio (β‰₯500 chars). |
90
+ | **Response Latency** | Average, median (P50), 95th percentile (P95), and max response time (human→assistant interval). |
91
+ | **Session Analysis** | Total sessions, average messages per session, average session duration, longest session. |
92
+ | **Hourly Token Distribution** | Bar chart of token usage by hour of day (24h). |
93
+ | **Cache Efficiency** | Fresh input, cache read, cache creation token counts with percentages and overall cache hit rate. |
94
+
95
+ ---
96
+
97
+ ### πŸ—‚οΈ Structure
98
+
99
+ Visual overview of your harness architecture.
100
+
101
+ | Section | Description |
102
+ |---------|-------------|
103
+ | **Component Flow** | SVG diagram showing how harness components relate to each other, grouped into three layers: **Context** (auto-loaded: CLAUDE.md, rules, principles, memory), **Event-driven** (hooks, MCP servers, plugins), and **User-invoked** (skills, agents, commands, teams, plans). |
104
+ | **File Tree** | Expandable tree view listing every registered component by category. Click any item to jump to its detail page. Plugins show active/inactive badges. |
105
+
106
+ ---
107
+
108
+ ### Category Pages
109
+
110
+ Click any category in the sidebar (Skills, Agents, Plugins, Hooks, etc.) to see a dedicated page for that category.
111
+
112
+ | Section | Description |
113
+ |---------|-------------|
114
+ | **Stat Cards** | Total items, usage count, and period-over-period change. |
115
+ | **Popular Items** | Top 5 most-used items within the category (ranked cards). |
116
+ | **Activity Heatmap** | Calendar heatmap filtered to this category only. |
117
+ | **Recent Activity** | Latest invocations of items in this category. |
118
+ | **Unused Items** | Registered items with zero calls in the selected period. |
119
+
120
+ ---
121
+
122
+ ### Item Detail Pages
123
+
124
+ Click any individual item (skill, agent, plugin, etc.) to see its detail page.
125
+
126
+ | Section | Description |
127
+ |---------|-------------|
128
+ | **Header** | Item name, type badge, file path, and description (from frontmatter). |
129
+ | **Metadata** | Parsed frontmatter fields displayed in a formatted card. |
130
+ | **Content Preview** | Full content of the item's definition file (markdown rendered). |
131
+ | **Usage Stats** | Call count and activity heatmap for this specific item (for skills, agents, commands). |
132
+ | **Related Items** | For plugins: list of skills provided by the plugin. For skills: parent plugin link if applicable. |
133
+
134
+ ---
135
+
136
+ ### ❓ Help
137
+
138
+ In-dashboard reference page.
139
+
140
+ | Section | Description |
141
+ |---------|-------------|
142
+ | **Usage** | How to run the `/omh` command. |
143
+ | **Parameters** | Table of all command parameters (`--data-only`, `--enable-auto`, etc.). |
144
+ | **Data Sources** | Reference for each data parser: what files are read and what data is extracted (config files, skills, agents, plugins, hooks, memory, MCP servers, rules, commands, teams, plans, todos, scopes). |
145
+ | **Token & Activity** | How token usage, prompt stats, latency, and activity data are parsed from transcript JSONL files. |
146
+
147
+ ---
148
+
149
+ ## General Features
150
+
151
+ ### Dark / Light Mode
152
+
153
+ Toggle via the theme button in the sidebar footer. Preference is persisted in `localStorage`.
154
+
155
+ ### Period Comparison
156
+
157
+ Stat cards with a change percentage compare the selected period against the immediately preceding period of equal length. For example, 7d compares the last 7 days vs. the 7 days before that.
158
+
159
+ ### Data Refresh
160
+
161
+ Run `/omh --data-only` to regenerate data and the web-ui without opening a new browser tab. Bookmark the generated local file (`output/index.html`) and refresh the page anytime to see the latest data.
162
+
163
+ Enable auto-refresh with `/omh --enable-auto` to rebuild data on every session end β€” just refresh the bookmarked tab to see updates.
package/README.md CHANGED
@@ -5,6 +5,7 @@
5
5
 
6
6
  Parses your entire Claude Code configuration and usage data, then generates an interactive single-file HTML dashboard you can open locally.
7
7
 
8
+ <img src="./assets/dashboard.png" alt="Dashboard preview" width="800">
8
9
 
9
10
  ## What it shows
10
11
 
@@ -14,6 +15,9 @@ Parses your entire Claude Code configuration and usage data, then generates an i
14
15
  - **Task categories** β€” auto-classified token usage by work type (code editing, docs, planning, etc.)
15
16
  - **Multi-workspace** β€” switch between global and per-project scopes
16
17
 
18
+ <img src="./assets/token-overview.png" alt="Tokens" width="800">
19
+ <img src="./assets/insights.png" alt="Insights" width="800">
20
+
17
21
  ## Installation
18
22
 
19
23
  #### From the Command Line
@@ -33,6 +37,18 @@ claude plugin install oh-my-hi
33
37
  /plugin install oh-my-hi@oh-my-hi-marketplace
34
38
  ```
35
39
 
40
+ ## Update
41
+
42
+ To update to the latest version, run the same install command:
43
+
44
+ ```bash
45
+ # From the Command Line
46
+ $ claude plugin install oh-my-hi
47
+
48
+ # Claude Code (in-session)
49
+ /plugin install oh-my-hi@oh-my-hi-marketplace
50
+ ```
51
+
36
52
  ## Usage
37
53
 
38
54
  Run in Claude Code:
@@ -64,6 +80,10 @@ Enable automatic data refresh so the dashboard stays up to date:
64
80
 
65
81
  This registers a Stop hook that rebuilds the dashboard data whenever a Claude Code session ends. Refresh the browser tab to see the latest data.
66
82
 
83
+ ## Guide
84
+
85
+ See **[GUIDE.md](GUIDE.md)** for a detailed walkthrough of each dashboard section β€” overview, token analytics, cost estimation, structure view, category pages, and more.
86
+
67
87
  ## How it works
68
88
 
69
89
  1. **Parse** β€” Reads your Claude Code config directory for skills, agents, plugins, hooks, memory, MCP servers, rules, principles, commands, teams, plans, and usage transcripts
@@ -77,6 +97,10 @@ This registers a Stop hook that rebuilds the dashboard data whenever a Claude Co
77
97
  - **English**: Built-in default
78
98
  - **Other languages**: A template locale file is auto-generated on first build. Translate it and rebuild
79
99
 
100
+ ## Privacy
101
+
102
+ All data stays on your machine. `oh-my-hi` reads only local Claude Code config files and transcripts β€” nothing is sent to external servers. The generated dashboard is a standalone HTML file opened via `file://` protocol, with no network requests. Your usage data, token statistics, and configuration details never leave your local environment.
103
+
80
104
  ## Browser support
81
105
 
82
106
  The dashboard opens as a local `file://` HTML file. No web server required.
Binary file
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oh-my-hi",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "description": "Claude Code harness insights dashboard",
5
5
  "repository": {
6
6
  "type": "git",
@@ -16,5 +16,9 @@
16
16
  "insights",
17
17
  "analytics"
18
18
  ],
19
- "homepage": "https://github.com/netil/oh-my-hi"
19
+ "homepage": "https://github.com/netil/oh-my-hi",
20
+ "devDependencies": {
21
+ "billboard.js": "^3.18.0",
22
+ "esbuild": "^0.27.4"
23
+ }
20
24
  }
@@ -4,6 +4,7 @@ import { fileURLToPath } from 'url';
4
4
  import path from 'path';
5
5
  import fs from 'fs';
6
6
  import { execSync } from 'child_process';
7
+ import { transformSync } from 'esbuild';
7
8
 
8
9
  import { parseSkills } from './parsers/skills.mjs';
9
10
  import { parseAgents } from './parsers/agents.mjs';
@@ -35,7 +36,7 @@ if (args.includes('--help')) {
35
36
 
36
37
  Usage:
37
38
  oh-my-hi Full build (index.html + data.json)
38
- oh-my-hi --data-only Data-only refresh (fast reload)
39
+ oh-my-hi --data-only Regenerate data + web-ui (skip browser open)
39
40
  oh-my-hi --enable-auto Enable auto data refresh on session end
40
41
  oh-my-hi --disable-auto Disable auto data refresh
41
42
  oh-my-hi --status Check auto-refresh status
@@ -180,8 +181,60 @@ async function main() {
180
181
  const safeJson = JSON.stringify(data);
181
182
  fs.writeFileSync(dataPath, safeJson, 'utf-8');
182
183
 
183
- // 5b) Load locale for system language
184
+ // 5a-2) Minify usage data for inline HTML (key shortening + sessionId indexing)
185
+ const USAGE_KEY_MAP = {
186
+ timestamp: 'ts', model: 'm', inputTokens: 'it', outputTokens: 'ot',
187
+ cacheRead: 'cr', cacheCreation: 'cc', rawInput: 'ri', context: 'cx',
188
+ contextName: 'cn', sessionId: 'sid', latencyMs: 'ms', charLen: 'cl',
189
+ name: 'n', tool: 't', count: 'c', date: 'd', command: 'cmd', project: 'p',
190
+ messageCount: 'mc', sessionCount: 'sc', toolCallCount: 'tc',
191
+ };
192
+ function minifyUsageData(scopeDataObj) {
193
+ const result = {};
194
+ for (const [scope, sdata] of Object.entries(scopeDataObj)) {
195
+ const copy = { ...sdata };
196
+ if (copy.usage) {
197
+ const u = copy.usage;
198
+ // Build sessionId lookup table
199
+ const sidSet = new Set();
200
+ for (const arr of Object.values(u)) {
201
+ if (!Array.isArray(arr)) continue;
202
+ for (const item of arr) {
203
+ if (item.sessionId != null) sidSet.add(item.sessionId);
204
+ }
205
+ }
206
+ const sidList = [...sidSet];
207
+ const sidIndex = Object.fromEntries(sidList.map((s, i) => [s, i]));
208
+
209
+ // Shorten keys and replace sessionId with index
210
+ const minUsage = {};
211
+ for (const [field, arr] of Object.entries(u)) {
212
+ if (!Array.isArray(arr)) { minUsage[field] = arr; continue; }
213
+ minUsage[field] = arr.map(item => {
214
+ const obj = {};
215
+ for (const [k, v] of Object.entries(item)) {
216
+ if (k === 'sessionId') {
217
+ obj.sid = v != null ? sidIndex[v] : null;
218
+ } else {
219
+ obj[USAGE_KEY_MAP[k] || k] = v;
220
+ }
221
+ }
222
+ return obj;
223
+ });
224
+ }
225
+ minUsage._sidList = sidList;
226
+ copy.usage = minUsage;
227
+ }
228
+ result[scope] = copy;
229
+ }
230
+ return result;
231
+ }
232
+ const inlineData = { ...data, scopeData: minifyUsageData(data.scopeData), _minified: true };
233
+ const inlineJson = JSON.stringify(inlineData);
234
+
235
+ // 5b) Load locales
184
236
  const LOCALES_DIR = path.join(TEMPLATES, 'locales');
237
+ const enObj = JSON.parse(fs.readFileSync(path.join(LOCALES_DIR, 'en.json'), 'utf-8'));
185
238
  let localeObj = {};
186
239
  const localePath = path.join(LOCALES_DIR, systemLocale + '.json');
187
240
  if (systemLocale !== 'en' && fs.existsSync(localePath)) {
@@ -191,42 +244,53 @@ async function main() {
191
244
  console.log(` locale: ${systemLocale} (${Object.keys(localeObj).length - 1} keys)`);
192
245
  } catch { /* fallback to English */ }
193
246
  }
194
- // For unknown locales: generate English template file on first run
247
+ // For unknown locales: copy en.json as template on first run
195
248
  if (systemLocale !== 'en' && !fs.existsSync(localePath)) {
196
- const enKeys = {};
197
- const appSrc = fs.readFileSync(path.join(TEMPLATES, 'app.js'), 'utf-8');
198
- const enMatch = appSrc.match(/I18N\.en\s*=\s*\{([\s\S]*?)\n \};/);
199
- if (enMatch) {
200
- for (const line of enMatch[1].split('\n')) {
201
- const m = line.match(/^\s*(\w+):\s*"(.*)"/);
202
- if (m) enKeys[m[1]] = m[2];
203
- }
204
- }
205
249
  fs.mkdirSync(LOCALES_DIR, { recursive: true });
206
- fs.writeFileSync(localePath, JSON.stringify(enKeys, null, 2), 'utf-8');
250
+ fs.writeFileSync(localePath, JSON.stringify(enObj, null, 2), 'utf-8');
207
251
  console.log(` locale: created template ${localePath} (translate and rebuild)`);
208
252
  }
209
253
 
210
254
  // 5c) index.html β€” always regenerated (data is inlined for file:// compatibility)
211
255
  const template = fs.readFileSync(path.join(TEMPLATES, 'dashboard.html'), 'utf-8');
212
- const styles = fs.readFileSync(path.join(TEMPLATES, 'styles.css'), 'utf-8');
213
- const appJs = fs.readFileSync(path.join(TEMPLATES, 'app.js'), 'utf-8');
256
+ const rawStyles = fs.readFileSync(path.join(TEMPLATES, 'styles.css'), 'utf-8');
257
+ const pkg = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf-8'));
258
+ const rawAppJs = fs.readFileSync(path.join(TEMPLATES, 'app.js'), 'utf-8')
259
+ .replace(/__VERSION__/g, JSON.stringify(pkg.version));
260
+
261
+ // billboard.js (pkgd includes d3) + CSS
262
+ const bbDir = path.join(ROOT, 'node_modules', 'billboard.js', 'dist');
263
+ if (!fs.existsSync(bbDir)) {
264
+ console.error('oh-my-hi: ❌ node_modules not found. Run `npm install` in', ROOT);
265
+ process.exit(1);
266
+ }
267
+ const bbJs = fs.readFileSync(path.join(bbDir, 'billboard.pkgd.min.js'), 'utf-8');
268
+ const bbCss = fs.readFileSync(path.join(bbDir, 'billboard.min.css'), 'utf-8');
269
+ const bbDarkCss = fs.readFileSync(path.join(bbDir, 'theme', 'dark.min.css'), 'utf-8');
270
+
271
+ const styles = transformSync(rawStyles, { loader: 'css', minify: true }).code;
272
+ const appJs = transformSync(rawAppJs, { loader: 'js', minify: true }).code;
214
273
 
215
274
  const escapeForScript = (str) => str
216
275
  .replaceAll('</', String.raw`<\u002f`)
217
276
  .replaceAll('\u2028', String.raw`\u2028`)
218
277
  .replaceAll('\u2029', String.raw`\u2029`);
219
278
 
220
- const escapedJson = escapeForScript(safeJson);
279
+ const escapedJson = escapeForScript(inlineJson);
221
280
  const escapedLocale = escapeForScript(JSON.stringify(localeObj));
222
281
 
223
282
  // Use a single-pass replacer to avoid cross-contamination when data payloads
224
283
  // contain placeholder-like strings (e.g. __LOCALE_DATA__ inside skill descriptions).
225
284
  const placeholders = {
285
+ __BB_CSS__: bbCss,
286
+ __BB_DARK_CSS_STR__: JSON.stringify(bbDarkCss),
287
+ __BB_JS__: bbJs,
226
288
  __STYLES__: styles,
289
+ __EN_DATA__: escapeForScript(JSON.stringify(enObj)),
227
290
  __LOCALE_DATA__: escapedLocale,
228
291
  __APP_JS__: appJs,
229
292
  __DATA__: escapedJson,
293
+ __VERSION__: pkg.version,
230
294
  };
231
295
  const placeholderRe = new RegExp(Object.keys(placeholders).map(k => k.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|'), 'g');
232
296
  const html = template.replace(placeholderRe, (match) => placeholders[match]);
@@ -394,53 +458,18 @@ async function collectProjectData(configPath, projectPath) {
394
458
  /**
395
459
  * Build task categories by classifying contextNames into work types.
396
460
  *
397
- * Classification is persisted in task-categories.json:
398
- * - Existing entries are preserved (user can edit the file to override).
399
- * - New contextNames are auto-classified from their description at build time.
461
+ * Classification is auto-generated at every build:
462
+ * - contextNames are classified from their description using keyword matching.
400
463
  * - Built-in tools (context='tool') use a fixed structural mapping.
464
+ * - Category labels come from locale files (taskCat_* keys).
401
465
  */
402
466
  const TASK_CAT_FILE = path.join(OUTPUT, 'task-categories.json');
403
467
 
404
- // Category schema β€” universal work types (fixed structure)
405
- const WORK_TYPE_META = {
406
- 'code-edit': { icon: '✏️', ko: 'μ½”λ“œ νŽΈμ§‘', en: 'Code Editing' },
407
- 'code-search': { icon: 'πŸ”', ko: 'μ½”λ“œ 탐색', en: 'Code Search' },
408
- 'execution': { icon: '▢️', ko: 'μ‹€ν–‰ / 디버깅', en: 'Execution & Debug' },
409
- 'review': { icon: 'πŸ”Ž', ko: 'μ½”λ“œ 리뷰', en: 'Code Review' },
410
- 'planning': { icon: 'πŸ“', ko: '섀계 / κ³„νš', en: 'Planning & Design' },
411
- 'docs': { icon: 'πŸ“', ko: 'λ¬Έμ„œ μž‘μ„±', en: 'Documentation' },
412
- 'browser': { icon: '🌐', ko: 'λΈŒλΌμš°μ €', en: 'Browser Automation' },
413
- 'workflow': { icon: 'βš™οΈ', ko: 'μ›Œν¬ν”Œλ‘œ μžλ™ν™”', en: 'Workflow Automation' },
414
- 'team': { icon: 'πŸ‘₯', ko: 'νŒ€ 관리', en: 'Team Management' },
415
- 'config': { icon: 'πŸ”§', ko: 'μ„€μ • / μœ μ§€λ³΄μˆ˜', en: 'Config & Maintenance' },
416
- 'general': { icon: 'πŸ’¬', ko: '일반 λŒ€ν™”', en: 'General' },
417
- 'other': { icon: 'πŸ“¦', ko: '기타', en: 'Other' },
418
- };
419
-
420
- // Built-in tool β†’ category (structural, not user data)
421
- const TOOL_CATEGORY = {
422
- Edit: 'code-edit', Write: 'code-edit', NotebookEdit: 'code-edit',
423
- Read: 'code-search', Grep: 'code-search', Glob: 'code-search',
424
- LSP: 'code-search', ToolSearch: 'code-search', Explore: 'code-search',
425
- AskUserQuestion: 'code-search',
426
- Bash: 'execution',
427
- EnterPlanMode: 'planning', ExitPlanMode: 'planning', Plan: 'planning',
428
- TaskCreate: 'planning', TaskUpdate: 'planning', TaskOutput: 'planning', TaskStop: 'planning',
429
- TeamCreate: 'team', TeamDelete: 'team', SendMessage: 'team',
430
- WebFetch: 'browser', WebSearch: 'browser',
431
- };
432
-
433
- // Category keyword seeds β€” used ONLY for auto-classifying new items
434
- const CAT_KEYWORDS = {
435
- 'review': ['review', 'lint', 'simplif', '리뷰', 'κ²€μˆ˜', 'κ²€ν† '],
436
- 'planning': ['plan', 'design', 'architect', 'brainstorm', 'κ³„νš', '섀계', '기획'],
437
- 'docs': ['doc', 'report', 'meeting', 'wiki', 'publish', 'humaniz', 'summary', 'λ¬Έμ„œ', '회의', '보고', '정리', 'μš”μ•½', 'μž‘μ„±', '기술곡유', '기둝'],
438
- 'browser': ['browser', 'chrome', 'scrape', 'page', 'λΈŒλΌμš°μ €'],
439
- 'workflow': ['workflow', 'n8n', 'cron', 'schedule', 'μžλ™ν™”', 'μ›Œν¬ν”Œλ‘œ'],
440
- 'team': ['team', 'leader', 'manager', 'νŒ€', 'λ§€λ‹ˆμ €'],
441
- 'config': ['config', 'setting', 'setup', 'hook', 'plugin', 'health', 'improve', 'skill-creator', 'μ„€μ •', 'κ°œμ„ '],
442
- 'execution':['debug', 'test', 'exec', 'build', 'implement', '디버그', 'μ‹€ν–‰', 'ν…ŒμŠ€νŠΈ', 'κ΅¬ν˜„'],
443
- };
468
+ // Work types loaded from external file (categories, tool mapping, keywords)
469
+ const WORK_TYPES = JSON.parse(fs.readFileSync(path.join(TEMPLATES, 'work-types.json'), 'utf-8'));
470
+ const WORK_TYPE_META = WORK_TYPES.categories;
471
+ const TOOL_CATEGORY = WORK_TYPES.toolMapping;
472
+ const CAT_KEYWORDS = WORK_TYPES.keywords;
444
473
 
445
474
  function buildTaskCategories(scopeData) {
446
475
  // 1. Collect descriptions from harness data
@@ -454,13 +483,7 @@ function buildTaskCategories(scopeData) {
454
483
  }
455
484
  }
456
485
 
457
- // 2. Load existing persistent mapping (user overrides preserved)
458
- let persisted = {};
459
- try {
460
- persisted = JSON.parse(fs.readFileSync(TASK_CAT_FILE, 'utf-8'));
461
- } catch { /* first run or missing file */ }
462
-
463
- // 3. Collect all contextNames from token data
486
+ // 2. Collect all contextNames from token data
464
487
  const allNames = new Set();
465
488
  for (const sd of Object.values(scopeData)) {
466
489
  for (const e of (sd.usage?.tokenEntries || [])) {
@@ -468,7 +491,7 @@ function buildTaskCategories(scopeData) {
468
491
  }
469
492
  }
470
493
 
471
- // 4. Auto-classify items not in persisted mapping
494
+ // 3. Auto-classify each contextName
472
495
  function autoClassify(name, contextType) {
473
496
  if (contextType === 'tool' && TOOL_CATEGORY[name]) return TOOL_CATEGORY[name];
474
497
  if (contextType === 'general') return 'general';
@@ -485,22 +508,17 @@ function buildTaskCategories(scopeData) {
485
508
  return bestCat || 'other';
486
509
  }
487
510
 
488
- // 5. Build final mapping: persisted β†’ auto-classified β†’ tool mapping
511
+ // 4. Build mapping: auto-classify all contextNames
489
512
  const mapping = {};
490
513
  for (const sd of Object.values(scopeData)) {
491
514
  for (const e of (sd.usage?.tokenEntries || [])) {
492
515
  const name = e.contextName || 'conversation';
493
516
  if (mapping[name]) continue;
494
-
495
- if (persisted[name]) {
496
- mapping[name] = persisted[name];
497
- } else {
498
- mapping[name] = autoClassify(name, e.context || 'general');
499
- }
517
+ mapping[name] = autoClassify(name, e.context || 'general');
500
518
  }
501
519
  }
502
520
 
503
- // 6. Save updated mapping to file (user-editable)
521
+ // 5. Save mapping for reference
504
522
  const sorted = {};
505
523
  for (const key of Object.keys(mapping).sort()) sorted[key] = mapping[key];
506
524
  fs.writeFileSync(TASK_CAT_FILE, JSON.stringify(sorted, null, 2), 'utf-8');