claude-code-session-manager 0.46.0 → 0.47.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/dist/index.html CHANGED
@@ -7,10 +7,10 @@
7
7
  <link rel="preconnect" href="https://fonts.googleapis.com">
8
8
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
9
  <link href="https://fonts.googleapis.com/css2?family=Newsreader:ital,opsz,wght@0,6..72,400;0,6..72,500;0,6..72,600;0,6..72,700;1,6..72,400&family=Geist:wght@300;400;500;600;700&family=IBM+Plex+Mono:wght@400;500;600&display=swap" rel="stylesheet">
10
- <script type="module" crossorigin src="./assets/index-DCjBivhu.js"></script>
10
+ <script type="module" crossorigin src="./assets/index-Jb71TRGj.js"></script>
11
11
  <link rel="modulepreload" crossorigin href="./assets/monaco-editor-BW5C4Iv1.js">
12
12
  <link rel="stylesheet" crossorigin href="./assets/monaco-editor-BTnBOi8r.css">
13
- <link rel="stylesheet" crossorigin href="./assets/index-DoZZVoph.css">
13
+ <link rel="stylesheet" crossorigin href="./assets/index-B6DLwpeA.css">
14
14
  </head>
15
15
  <body class="bg-bg text-fg font-sans antialiased">
16
16
  <div id="root"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-session-manager",
3
- "version": "0.46.0",
3
+ "version": "0.47.0",
4
4
  "description": "Local cockpit for the Claude Code CLI — multi-tab terminal, full config surface, scheduler, voice dictation, and live observability.",
5
5
  "type": "module",
6
6
  "main": "src/main/index.cjs",
@@ -37,6 +37,16 @@ one it had to guess at. Before doing anything else:
37
37
  inside that Epic. Do not mint one yourself, do not fall back to writing an epicless PRD, and
38
38
  do not proceed with authoring.
39
39
 
40
+ **Tag-aware default (2026-08-01).** The Epic's own intent tag (`feature` / `bug` /
41
+ `discussion` — CLAUDE.md's domain model) sets how eagerly this skill should fire, not just
42
+ what to write once it does:
43
+ - **`feature` / `bug`** — decomposition into PRDs is the expected default path for these once
44
+ scope is reasonably clear. Reach for `/develop` proactively; don't wait to be re-asked.
45
+ - **`discussion`** — whether development is even warranted is often still the open question.
46
+ `/develop` stays fully available inside a Discussion Epic (a discussion can conclude "yes,
47
+ build this"), but never assume it's the next step — don't jump straight to decomposing PRDs
48
+ just because the conversation is active. Keep exploring/deciding until that's actually settled.
49
+
40
50
  **Never** hand-implement the work inline in chat, and never restate rules that live elsewhere:
41
51
  the engineering rules belong to `standards.md`. Reference it; don't fork it.
42
52
 
@@ -0,0 +1,53 @@
1
+ /**
2
+ * toolUseClassify.test.cjs — unit tests for src/main/lib/toolUseClassify.cjs.
3
+ *
4
+ * Run: timeout 120 npx vitest run src/main/lib/__tests__/toolUseClassify.test.cjs
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ import { test, expect } from 'vitest';
10
+
11
+ const { classifyToolUse, MAX_DIFF_STR } = require('../toolUseClassify.cjs');
12
+
13
+ test('Edit tool_use produces a diff payload with filePath/oldText/newText', () => {
14
+ const block = {
15
+ name: 'Edit',
16
+ input: { file_path: '/tmp/foo.js', old_string: 'a', new_string: 'b' },
17
+ };
18
+ const result = classifyToolUse(block);
19
+ expect(result.kind).toBe('tool');
20
+ expect(result.diff).toEqual({ filePath: '/tmp/foo.js', oldText: 'a', newText: 'b' });
21
+ });
22
+
23
+ test('Write tool_use produces a diff payload with filePath/newText only', () => {
24
+ const block = { name: 'Write', input: { file_path: '/tmp/bar.js', content: 'hello' } };
25
+ const result = classifyToolUse(block);
26
+ expect(result.diff).toEqual({ filePath: '/tmp/bar.js', newText: 'hello' });
27
+ });
28
+
29
+ test('non-Edit/Write tool_use has no diff field', () => {
30
+ const block = { name: 'Bash', input: { command: 'ls' } };
31
+ const result = classifyToolUse(block);
32
+ expect(result.diff).toBeUndefined();
33
+ expect('diff' in result).toBe(false);
34
+ });
35
+
36
+ test('Skill/mcp tool_use never get a diff field', () => {
37
+ expect(classifyToolUse({ name: 'Skill', input: { skill: 'x' } }).diff).toBeUndefined();
38
+ expect(classifyToolUse({ name: 'mcp__foo__bar', input: {} }).diff).toBeUndefined();
39
+ });
40
+
41
+ test('oversized Edit strings are capped at MAX_DIFF_STR', () => {
42
+ const big = 'x'.repeat(MAX_DIFF_STR + 100);
43
+ const block = { name: 'Edit', input: { file_path: '/tmp/foo.js', old_string: big, new_string: big } };
44
+ const result = classifyToolUse(block);
45
+ expect(result.diff.oldText.length).toBe(MAX_DIFF_STR + 1); // + ellipsis char
46
+ expect(result.diff.oldText.endsWith('…')).toBe(true);
47
+ });
48
+
49
+ test('Edit block missing file_path yields no diff', () => {
50
+ const block = { name: 'Edit', input: { old_string: 'a', new_string: 'b' } };
51
+ const result = classifyToolUse(block);
52
+ expect(result.diff).toBeUndefined();
53
+ });
@@ -1,10 +1,46 @@
1
1
  'use strict';
2
2
 
3
+ // Same cap classifyTranscriptLine.cjs uses (MAX_RAW_STR) for raw tool_use
4
+ // content — reused here so a large Edit/Write payload can't bloat the
5
+ // in-memory ChatTurn / localStorage-persisted chat state.
6
+ const MAX_DIFF_STR = 4096;
7
+
8
+ function capStr(s) {
9
+ if (typeof s !== 'string') return s;
10
+ return s.length > MAX_DIFF_STR ? `${s.slice(0, MAX_DIFF_STR)}…` : s;
11
+ }
12
+
13
+ /**
14
+ * Pull a diff payload out of an Edit/Write tool_use block's `input`.
15
+ * Returns undefined for any other tool (including malformed Edit/Write
16
+ * blocks missing `file_path`).
17
+ * @param {{name: string, input?: Record<string, unknown>}} block
18
+ * @returns {{ filePath: string, oldText?: string, newText?: string } | undefined}
19
+ */
20
+ function extractDiff(block) {
21
+ const input = block?.input;
22
+ if (!input || typeof input !== 'object' || typeof input.file_path !== 'string') return undefined;
23
+ if (block.name === 'Edit') {
24
+ return {
25
+ filePath: input.file_path,
26
+ oldText: capStr(input.old_string),
27
+ newText: capStr(input.new_string),
28
+ };
29
+ }
30
+ if (block.name === 'Write') {
31
+ return {
32
+ filePath: input.file_path,
33
+ newText: capStr(input.content),
34
+ };
35
+ }
36
+ return undefined;
37
+ }
38
+
3
39
  /**
4
40
  * Classify a single stream-json tool_use block into a UI-facing kind.
5
41
  * O(1) — no loops, pure string/prefix checks.
6
42
  * @param {{name: string, input?: Record<string, unknown>}} block
7
- * @returns {{ kind: 'skill' | 'mcp' | 'tool', label: string }}
43
+ * @returns {{ kind: 'skill' | 'mcp' | 'tool', label: string, diff?: { filePath: string, oldText?: string, newText?: string } }}
8
44
  */
9
45
  function classifyToolUse(block) {
10
46
  if (block.name === 'Skill') {
@@ -13,7 +49,8 @@ function classifyToolUse(block) {
13
49
  if (block.name.startsWith('mcp__')) {
14
50
  return { kind: 'mcp', label: block.name.replace(/^mcp__/, '') };
15
51
  }
16
- return { kind: 'tool', label: block.name };
52
+ const diff = extractDiff(block);
53
+ return diff ? { kind: 'tool', label: block.name, diff } : { kind: 'tool', label: block.name };
17
54
  }
18
55
 
19
- module.exports = { classifyToolUse };
56
+ module.exports = { classifyToolUse, extractDiff, MAX_DIFF_STR };
@@ -1121,6 +1121,8 @@ export interface ChatRunToolUseEvent {
1121
1121
  id: string;
1122
1122
  kind: 'skill' | 'mcp' | 'tool';
1123
1123
  label: string;
1124
+ /** Populated only for Edit/Write tool_use blocks; undefined for every other kind. */
1125
+ diff?: { filePath: string; oldText?: string; newText?: string };
1124
1126
  }
1125
1127
 
1126
1128
  export interface ChatRunNeedsInputEvent {