claude-code-workflow 6.3.13 → 6.3.15

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 (69) hide show
  1. package/.claude/agents/issue-plan-agent.md +57 -103
  2. package/.claude/agents/issue-queue-agent.md +69 -120
  3. package/.claude/commands/issue/new.md +217 -473
  4. package/.claude/commands/issue/plan.md +76 -154
  5. package/.claude/commands/issue/queue.md +208 -259
  6. package/.claude/skills/issue-manage/SKILL.md +63 -22
  7. package/.claude/workflows/cli-templates/schemas/discovery-finding-schema.json +3 -3
  8. package/.claude/workflows/cli-templates/schemas/issues-jsonl-schema.json +3 -3
  9. package/.claude/workflows/cli-templates/schemas/queue-schema.json +0 -5
  10. package/.codex/prompts/issue-plan.md +16 -19
  11. package/.codex/prompts/issue-queue.md +0 -1
  12. package/README.md +1 -0
  13. package/ccw/dist/cli.d.ts.map +1 -1
  14. package/ccw/dist/cli.js +3 -1
  15. package/ccw/dist/cli.js.map +1 -1
  16. package/ccw/dist/commands/cli.d.ts.map +1 -1
  17. package/ccw/dist/commands/cli.js +45 -3
  18. package/ccw/dist/commands/cli.js.map +1 -1
  19. package/ccw/dist/commands/issue.d.ts +3 -1
  20. package/ccw/dist/commands/issue.d.ts.map +1 -1
  21. package/ccw/dist/commands/issue.js +383 -30
  22. package/ccw/dist/commands/issue.js.map +1 -1
  23. package/ccw/dist/core/routes/issue-routes.d.ts.map +1 -1
  24. package/ccw/dist/core/routes/issue-routes.js +77 -16
  25. package/ccw/dist/core/routes/issue-routes.js.map +1 -1
  26. package/ccw/dist/tools/cli-executor.d.ts.map +1 -1
  27. package/ccw/dist/tools/cli-executor.js +117 -4
  28. package/ccw/dist/tools/cli-executor.js.map +1 -1
  29. package/ccw/dist/tools/litellm-executor.d.ts +4 -0
  30. package/ccw/dist/tools/litellm-executor.d.ts.map +1 -1
  31. package/ccw/dist/tools/litellm-executor.js +54 -1
  32. package/ccw/dist/tools/litellm-executor.js.map +1 -1
  33. package/ccw/dist/tools/ui-generate-preview.d.ts +18 -0
  34. package/ccw/dist/tools/ui-generate-preview.d.ts.map +1 -1
  35. package/ccw/dist/tools/ui-generate-preview.js +26 -10
  36. package/ccw/dist/tools/ui-generate-preview.js.map +1 -1
  37. package/ccw/src/cli.ts +3 -1
  38. package/ccw/src/commands/cli.ts +47 -3
  39. package/ccw/src/commands/issue.ts +442 -34
  40. package/ccw/src/core/routes/issue-routes.ts +82 -16
  41. package/ccw/src/tools/cli-executor.ts +125 -4
  42. package/ccw/src/tools/litellm-executor.ts +107 -24
  43. package/ccw/src/tools/ui-generate-preview.js +60 -37
  44. package/codex-lens/src/codexlens/__pycache__/config.cpython-313.pyc +0 -0
  45. package/codex-lens/src/codexlens/__pycache__/entities.cpython-313.pyc +0 -0
  46. package/codex-lens/src/codexlens/config.py +25 -2
  47. package/codex-lens/src/codexlens/entities.py +5 -1
  48. package/codex-lens/src/codexlens/indexing/__pycache__/symbol_extractor.cpython-313.pyc +0 -0
  49. package/codex-lens/src/codexlens/indexing/symbol_extractor.py +243 -243
  50. package/codex-lens/src/codexlens/parsers/__pycache__/factory.cpython-313.pyc +0 -0
  51. package/codex-lens/src/codexlens/parsers/__pycache__/treesitter_parser.cpython-313.pyc +0 -0
  52. package/codex-lens/src/codexlens/parsers/factory.py +256 -256
  53. package/codex-lens/src/codexlens/parsers/treesitter_parser.py +335 -335
  54. package/codex-lens/src/codexlens/search/__pycache__/chain_search.cpython-313.pyc +0 -0
  55. package/codex-lens/src/codexlens/search/__pycache__/hybrid_search.cpython-313.pyc +0 -0
  56. package/codex-lens/src/codexlens/search/__pycache__/ranking.cpython-313.pyc +0 -0
  57. package/codex-lens/src/codexlens/search/chain_search.py +30 -1
  58. package/codex-lens/src/codexlens/semantic/__pycache__/__init__.cpython-313.pyc +0 -0
  59. package/codex-lens/src/codexlens/semantic/__pycache__/embedder.cpython-313.pyc +0 -0
  60. package/codex-lens/src/codexlens/semantic/__pycache__/reranker.cpython-313.pyc +0 -0
  61. package/codex-lens/src/codexlens/semantic/__pycache__/vector_store.cpython-313.pyc +0 -0
  62. package/codex-lens/src/codexlens/semantic/embedder.py +6 -9
  63. package/codex-lens/src/codexlens/semantic/vector_store.py +271 -200
  64. package/codex-lens/src/codexlens/storage/__pycache__/dir_index.cpython-313.pyc +0 -0
  65. package/codex-lens/src/codexlens/storage/__pycache__/index_tree.cpython-313.pyc +0 -0
  66. package/codex-lens/src/codexlens/storage/__pycache__/sqlite_store.cpython-313.pyc +0 -0
  67. package/codex-lens/src/codexlens/storage/sqlite_store.py +184 -108
  68. package/package.json +6 -1
  69. package/.claude/commands/issue/manage.md +0 -113
@@ -2,55 +2,68 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- import json
6
- import sqlite3
7
- import threading
8
- import time
9
- from dataclasses import asdict
10
- from pathlib import Path
5
+ import json
6
+ import logging
7
+ import sqlite3
8
+ import threading
9
+ import time
10
+ from dataclasses import asdict
11
+ from pathlib import Path
11
12
  from typing import Any, Dict, Iterable, List, Optional, Tuple
12
13
 
13
- from codexlens.entities import IndexedFile, SearchResult, Symbol
14
- from codexlens.errors import StorageError
15
-
16
-
17
- class SQLiteStore:
18
- """SQLiteStore providing FTS5 search and symbol lookup.
19
-
20
- Implements thread-local connection pooling for improved performance.
21
- """
22
-
23
- # Maximum number of connections to keep in pool to prevent memory leaks
24
- MAX_POOL_SIZE = 32
25
- # Idle timeout in seconds (10 minutes)
26
- IDLE_TIMEOUT = 600
27
-
28
- def __init__(self, db_path: str | Path) -> None:
29
- self.db_path = Path(db_path)
30
- self._lock = threading.RLock()
31
- self._local = threading.local()
32
- self._pool_lock = threading.Lock()
33
- # Pool stores (connection, last_access_time) tuples
34
- self._pool: Dict[int, Tuple[sqlite3.Connection, float]] = {}
35
- self._pool_generation = 0
14
+ from codexlens.entities import IndexedFile, SearchResult, Symbol
15
+ from codexlens.errors import StorageError
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ class SQLiteStore:
21
+ """SQLiteStore providing FTS5 search and symbol lookup.
22
+
23
+ Implements thread-local connection pooling for improved performance.
24
+ """
25
+
26
+ # Maximum number of connections to keep in pool to prevent memory leaks
27
+ MAX_POOL_SIZE = 32
28
+ # Idle timeout in seconds (10 minutes)
29
+ IDLE_TIMEOUT = 600
30
+ # Periodic cleanup interval in seconds (5 minutes)
31
+ CLEANUP_INTERVAL = 300
32
+
33
+ def __init__(self, db_path: str | Path) -> None:
34
+ self.db_path = Path(db_path)
35
+ self._lock = threading.RLock()
36
+ self._local = threading.local()
37
+ self._pool_lock = threading.Lock()
38
+ # Pool stores (connection, last_access_time) tuples
39
+ self._pool: Dict[int, Tuple[sqlite3.Connection, float]] = {}
40
+ self._pool_generation = 0
41
+ self._cleanup_timer: threading.Timer | None = None
42
+ self._cleanup_stop_event = threading.Event()
43
+ self._start_cleanup_timer()
36
44
 
37
45
  def _get_connection(self) -> sqlite3.Connection:
38
46
  """Get or create a thread-local database connection."""
39
47
  thread_id = threading.get_ident()
40
48
  current_time = time.time()
41
49
 
42
- if getattr(self._local, "generation", None) == self._pool_generation:
43
- conn = getattr(self._local, "conn", None)
44
- if conn is not None:
45
- # Update last access time
46
- with self._pool_lock:
47
- if thread_id in self._pool:
48
- self._pool[thread_id] = (conn, current_time)
49
- return conn
50
-
51
- with self._pool_lock:
52
- pool_entry = self._pool.get(thread_id)
53
- if pool_entry is not None:
50
+ if getattr(self._local, "generation", None) == self._pool_generation:
51
+ conn = getattr(self._local, "conn", None)
52
+ if conn is not None:
53
+ with self._pool_lock:
54
+ pool_entry = self._pool.get(thread_id)
55
+ if pool_entry is not None:
56
+ pooled_conn, _ = pool_entry
57
+ self._pool[thread_id] = (pooled_conn, current_time)
58
+ self._local.conn = pooled_conn
59
+ return pooled_conn
60
+
61
+ # Thread-local connection is stale (e.g., cleaned up by timer).
62
+ self._local.conn = None
63
+
64
+ with self._pool_lock:
65
+ pool_entry = self._pool.get(thread_id)
66
+ if pool_entry is not None:
54
67
  conn, _ = pool_entry
55
68
  # Update last access time
56
69
  self._pool[thread_id] = (conn, current_time)
@@ -72,37 +85,91 @@ class SQLiteStore:
72
85
  self._local.generation = self._pool_generation
73
86
  return conn
74
87
 
75
- def _cleanup_stale_connections(self) -> None:
76
- """Remove connections for threads that no longer exist or have been idle too long."""
77
- current_time = time.time()
78
- # Get list of active thread IDs
79
- active_threads = {t.ident for t in threading.enumerate() if t.ident is not None}
80
-
81
- # Find connections to remove: dead threads or idle timeout exceeded
82
- stale_ids = []
83
- for tid, (conn, last_access) in list(self._pool.items()):
84
- is_dead_thread = tid not in active_threads
85
- is_idle = (current_time - last_access) > self.IDLE_TIMEOUT
86
- if is_dead_thread or is_idle:
87
- stale_ids.append(tid)
88
-
89
- # Close and remove stale connections
90
- for tid in stale_ids:
91
- try:
92
- conn, _ = self._pool[tid]
93
- conn.close()
94
- except Exception:
95
- pass
96
- del self._pool[tid]
97
-
98
- def close(self) -> None:
99
- """Close all pooled connections."""
100
- with self._lock:
101
- with self._pool_lock:
102
- for conn, _ in self._pool.values():
103
- conn.close()
104
- self._pool.clear()
105
- self._pool_generation += 1
88
+ def _cleanup_stale_connections(self) -> None:
89
+ """Remove connections for threads that no longer exist or have been idle too long."""
90
+ current_time = time.time()
91
+ # Get list of active thread IDs
92
+ active_threads = {t.ident for t in threading.enumerate() if t.ident is not None}
93
+
94
+ # Find connections to remove: dead threads or idle timeout exceeded
95
+ stale_ids: list[tuple[int, str]] = []
96
+ for tid, (conn, last_access) in list(self._pool.items()):
97
+ try:
98
+ is_dead_thread = tid not in active_threads
99
+ is_idle = (current_time - last_access) > self.IDLE_TIMEOUT
100
+
101
+ is_invalid_connection = False
102
+ if not is_dead_thread and not is_idle:
103
+ try:
104
+ conn.execute("SELECT 1").fetchone()
105
+ except sqlite3.ProgrammingError:
106
+ is_invalid_connection = True
107
+ except sqlite3.Error:
108
+ is_invalid_connection = True
109
+
110
+ if is_invalid_connection:
111
+ stale_ids.append((tid, "invalid_connection"))
112
+ elif is_dead_thread:
113
+ stale_ids.append((tid, "dead_thread"))
114
+ elif is_idle:
115
+ stale_ids.append((tid, "idle_timeout"))
116
+ except Exception:
117
+ # Never break cleanup for a single bad entry.
118
+ continue
119
+
120
+ # Close and remove stale connections
121
+ for tid, reason in stale_ids:
122
+ try:
123
+ conn, _ = self._pool[tid]
124
+ conn.close()
125
+ except Exception:
126
+ pass
127
+ del self._pool[tid]
128
+ logger.debug("Cleaned SQLiteStore connection for thread_id=%s (%s)", tid, reason)
129
+
130
+ def _start_cleanup_timer(self) -> None:
131
+ if self.CLEANUP_INTERVAL <= 0:
132
+ return
133
+
134
+ self._cleanup_stop_event.clear()
135
+
136
+ def tick() -> None:
137
+ if self._cleanup_stop_event.is_set():
138
+ return
139
+
140
+ try:
141
+ with self._pool_lock:
142
+ self._cleanup_stale_connections()
143
+ finally:
144
+ with self._pool_lock:
145
+ if self._cleanup_stop_event.is_set():
146
+ self._cleanup_timer = None
147
+ return
148
+
149
+ self._cleanup_timer = threading.Timer(self.CLEANUP_INTERVAL, tick)
150
+ self._cleanup_timer.daemon = True
151
+ self._cleanup_timer.start()
152
+
153
+ self._cleanup_timer = threading.Timer(self.CLEANUP_INTERVAL, tick)
154
+ self._cleanup_timer.daemon = True
155
+ self._cleanup_timer.start()
156
+
157
+ def _stop_cleanup_timer(self) -> None:
158
+ self._cleanup_stop_event.set()
159
+ with self._pool_lock:
160
+ if self._cleanup_timer is not None:
161
+ self._cleanup_timer.cancel()
162
+ self._cleanup_timer = None
163
+
164
+ def close(self) -> None:
165
+ """Close all pooled connections."""
166
+ with self._lock:
167
+ self._stop_cleanup_timer()
168
+ with self._pool_lock:
169
+ for conn, _ in self._pool.values():
170
+ conn.close()
171
+ self._pool.clear()
172
+ self._pool_generation += 1
106
173
 
107
174
  if hasattr(self._local, "conn"):
108
175
  self._local.conn = None
@@ -262,14 +329,20 @@ class SQLiteStore:
262
329
  ],
263
330
  )
264
331
 
265
- conn.commit()
266
- except Exception:
267
- conn.rollback()
268
- raise
269
-
270
- def remove_file(self, path: str | Path) -> bool:
271
- """Remove a file from the index."""
272
- with self._lock:
332
+ conn.commit()
333
+ except Exception as exc:
334
+ try:
335
+ conn.rollback()
336
+ except Exception as rollback_exc:
337
+ logger.error(
338
+ "Rollback failed after add_files() error (%s): %s", exc, rollback_exc
339
+ )
340
+ raise exc.with_traceback(exc.__traceback__) from rollback_exc
341
+ raise
342
+
343
+ def remove_file(self, path: str | Path) -> bool:
344
+ """Remove a file from the index."""
345
+ with self._lock:
273
346
  conn = self._get_connection()
274
347
  resolved_path = str(Path(path).resolve())
275
348
 
@@ -539,10 +612,10 @@ class SQLiteStore:
539
612
  """
540
613
  )
541
614
 
542
- def _migrate_fts_to_external(self, conn: sqlite3.Connection) -> None:
543
- """Migrate legacy files_fts (with duplicated content) to external content."""
544
- try:
545
- conn.execute("BEGIN")
615
+ def _migrate_fts_to_external(self, conn: sqlite3.Connection) -> None:
616
+ """Migrate legacy files_fts (with duplicated content) to external content."""
617
+ try:
618
+ conn.execute("BEGIN")
546
619
  conn.execute("DROP TRIGGER IF EXISTS files_ai")
547
620
  conn.execute("DROP TRIGGER IF EXISTS files_ad")
548
621
  conn.execute("DROP TRIGGER IF EXISTS files_au")
@@ -550,27 +623,30 @@ class SQLiteStore:
550
623
  conn.execute("ALTER TABLE files_fts RENAME TO files_fts_legacy")
551
624
  self._create_external_fts(conn)
552
625
  conn.execute("INSERT INTO files_fts(files_fts) VALUES('rebuild')")
553
- conn.execute("DROP TABLE files_fts_legacy")
554
- conn.commit()
555
- except sqlite3.DatabaseError:
556
- try:
557
- conn.rollback()
558
- except Exception:
559
- pass
560
-
561
- try:
562
- conn.execute("DROP TABLE IF EXISTS files_fts")
563
- except Exception:
564
- pass
565
-
566
- try:
567
- conn.execute("ALTER TABLE files_fts_legacy RENAME TO files_fts")
568
- conn.commit()
569
- except Exception:
570
- pass
571
- raise
572
-
573
- try:
574
- conn.execute("VACUUM")
575
- except sqlite3.DatabaseError:
576
- pass
626
+ conn.execute("DROP TABLE files_fts_legacy")
627
+ conn.commit()
628
+ except sqlite3.DatabaseError as exc:
629
+ try:
630
+ conn.rollback()
631
+ except Exception as rollback_exc:
632
+ logger.error(
633
+ "Rollback failed during FTS schema migration (%s): %s", exc, rollback_exc
634
+ )
635
+ raise exc.with_traceback(exc.__traceback__) from rollback_exc
636
+
637
+ try:
638
+ conn.execute("DROP TABLE IF EXISTS files_fts")
639
+ except Exception:
640
+ pass
641
+
642
+ try:
643
+ conn.execute("ALTER TABLE files_fts_legacy RENAME TO files_fts")
644
+ conn.commit()
645
+ except Exception:
646
+ pass
647
+ raise
648
+
649
+ try:
650
+ conn.execute("VACUUM")
651
+ except sqlite3.DatabaseError:
652
+ pass
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-workflow",
3
- "version": "6.3.13",
3
+ "version": "6.3.15",
4
4
  "description": "JSON-driven multi-agent development framework with intelligent CLI orchestration (Gemini/Qwen/Codex), context-first architecture, and automated workflow execution",
5
5
  "type": "module",
6
6
  "main": "ccw/src/index.js",
@@ -12,6 +12,7 @@
12
12
  "build": "tsc -p ccw/tsconfig.json",
13
13
  "start": "node ccw/bin/ccw.js",
14
14
  "test": "node --experimental-strip-types --test ccw/tests/*.test.js",
15
+ "test:visual": "node --experimental-strip-types --test ccw/tests/visual/**/*.visual.test.ts",
15
16
  "prepublishOnly": "npm run build && echo 'Ready to publish @dyw/claude-code-workflow'"
16
17
  },
17
18
  "keywords": [
@@ -74,10 +75,14 @@
74
75
  },
75
76
  "homepage": "https://github.com/catlog22/Claude-Code-Workflow#readme",
76
77
  "devDependencies": {
78
+ "@playwright/test": "^1.57.0",
77
79
  "@types/better-sqlite3": "^7.6.12",
78
80
  "@types/gradient-string": "^1.1.6",
79
81
  "@types/inquirer": "^9.0.9",
80
82
  "@types/node": "^25.0.1",
83
+ "pixelmatch": "^7.1.0",
84
+ "playwright": "^1.57.0",
85
+ "pngjs": "^7.0.0",
81
86
  "typescript": "^5.9.3"
82
87
  }
83
88
  }
@@ -1,113 +0,0 @@
1
- ---
2
- name: manage
3
- description: Interactive issue management (CRUD) via ccw cli endpoints with menu-driven interface
4
- argument-hint: "[issue-id] [--action list|view|edit|delete|bulk]"
5
- allowed-tools: TodoWrite(*), Bash(*), Read(*), Write(*), AskUserQuestion(*), Task(*)
6
- ---
7
-
8
- # Issue Manage Command (/issue:manage)
9
-
10
- ## Overview
11
-
12
- Interactive menu-driven interface for issue management using `ccw issue` CLI endpoints:
13
- - **List**: Browse and filter issues
14
- - **View**: Detailed issue inspection
15
- - **Edit**: Modify issue fields
16
- - **Delete**: Remove issues
17
- - **Bulk**: Batch operations on multiple issues
18
-
19
- ## CLI Endpoints Reference
20
-
21
- ```bash
22
- # Core endpoints (ccw issue)
23
- ccw issue list # List all issues
24
- ccw issue list <id> --json # Get issue details
25
- ccw issue status <id> # Detailed status
26
- ccw issue init <id> --title "..." # Create issue
27
- ccw issue task <id> --title "..." # Add task
28
- ccw issue bind <id> <solution-id> # Bind solution
29
-
30
- # Queue management
31
- ccw issue queue # List current queue
32
- ccw issue queue add <id> # Add to queue
33
- ccw issue queue list # Queue history
34
- ccw issue queue switch <queue-id> # Switch queue
35
- ccw issue queue archive # Archive queue
36
- ccw issue queue delete <queue-id> # Delete queue
37
- ccw issue next # Get next task
38
- ccw issue done <queue-id> # Mark completed
39
- ccw issue complete <item-id> # (legacy alias for done)
40
- ```
41
-
42
- ## Usage
43
-
44
- ```bash
45
- # Interactive mode (menu-driven)
46
- /issue:manage
47
-
48
- # Direct to specific issue
49
- /issue:manage GH-123
50
-
51
- # Direct action
52
- /issue:manage --action list
53
- /issue:manage GH-123 --action edit
54
- ```
55
-
56
- ## Implementation
57
-
58
- This command delegates to the `issue-manage` skill for detailed implementation.
59
-
60
- ### Entry Point
61
-
62
- ```javascript
63
- const issueId = parseIssueId(userInput);
64
- const action = flags.action;
65
-
66
- // Show main menu if no action specified
67
- if (!action) {
68
- await showMainMenu(issueId);
69
- } else {
70
- await executeAction(action, issueId);
71
- }
72
- ```
73
-
74
- ### Main Menu Flow
75
-
76
- 1. **Dashboard**: Fetch issues summary via `ccw issue list --json`
77
- 2. **Menu**: Present action options via AskUserQuestion
78
- 3. **Route**: Execute selected action (List/View/Edit/Delete/Bulk)
79
- 4. **Loop**: Return to menu after each action
80
-
81
- ### Available Actions
82
-
83
- | Action | Description | CLI Command |
84
- |--------|-------------|-------------|
85
- | List | Browse with filters | `ccw issue list --json` |
86
- | View | Detail view | `ccw issue status <id> --json` |
87
- | Edit | Modify fields | Update `issues.jsonl` |
88
- | Delete | Remove issue | Clean up all related files |
89
- | Bulk | Batch operations | Multi-select + batch update |
90
-
91
- ## Data Files
92
-
93
- | File | Purpose |
94
- |------|---------|
95
- | `.workflow/issues/issues.jsonl` | Issue records |
96
- | `.workflow/issues/solutions/<id>.jsonl` | Solutions per issue |
97
- | `.workflow/issues/queue.json` | Execution queue |
98
-
99
- ## Error Handling
100
-
101
- | Error | Resolution |
102
- |-------|------------|
103
- | No issues found | Suggest creating with /issue:new |
104
- | Issue not found | Show available issues, ask for correction |
105
- | Invalid selection | Show error, re-prompt |
106
- | Write failure | Check permissions, show error |
107
-
108
- ## Related Commands
109
-
110
- - `/issue:new` - Create structured issue
111
- - `/issue:plan` - Plan solution for issue
112
- - `/issue:queue` - Form execution queue
113
- - `/issue:execute` - Execute queued tasks