claude-code-workflow 6.2.7 → 6.3.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/.claude/CLAUDE.md +16 -1
- package/.claude/workflows/cli-templates/protocols/analysis-protocol.md +11 -4
- package/.claude/workflows/cli-templates/protocols/write-protocol.md +10 -75
- package/.claude/workflows/cli-tools-usage.md +14 -24
- package/.codex/AGENTS.md +51 -1
- package/.codex/prompts/compact.md +378 -0
- package/.gemini/GEMINI.md +57 -20
- package/ccw/dist/cli.d.ts.map +1 -1
- package/ccw/dist/cli.js +21 -8
- package/ccw/dist/cli.js.map +1 -1
- package/ccw/dist/commands/cli.d.ts +2 -0
- package/ccw/dist/commands/cli.d.ts.map +1 -1
- package/ccw/dist/commands/cli.js +129 -8
- package/ccw/dist/commands/cli.js.map +1 -1
- package/ccw/dist/commands/hook.d.ts.map +1 -1
- package/ccw/dist/commands/hook.js +3 -2
- package/ccw/dist/commands/hook.js.map +1 -1
- package/ccw/dist/config/litellm-api-config-manager.d.ts +180 -0
- package/ccw/dist/config/litellm-api-config-manager.d.ts.map +1 -0
- package/ccw/dist/config/litellm-api-config-manager.js +770 -0
- package/ccw/dist/config/litellm-api-config-manager.js.map +1 -0
- package/ccw/dist/config/provider-models.d.ts +73 -0
- package/ccw/dist/config/provider-models.d.ts.map +1 -0
- package/ccw/dist/config/provider-models.js +172 -0
- package/ccw/dist/config/provider-models.js.map +1 -0
- package/ccw/dist/core/cache-manager.d.ts.map +1 -1
- package/ccw/dist/core/cache-manager.js +3 -5
- package/ccw/dist/core/cache-manager.js.map +1 -1
- package/ccw/dist/core/dashboard-generator.d.ts.map +1 -1
- package/ccw/dist/core/dashboard-generator.js +3 -1
- package/ccw/dist/core/dashboard-generator.js.map +1 -1
- package/ccw/dist/core/routes/cli-routes.d.ts.map +1 -1
- package/ccw/dist/core/routes/cli-routes.js +169 -0
- package/ccw/dist/core/routes/cli-routes.js.map +1 -1
- package/ccw/dist/core/routes/codexlens-routes.d.ts.map +1 -1
- package/ccw/dist/core/routes/codexlens-routes.js +234 -18
- package/ccw/dist/core/routes/codexlens-routes.js.map +1 -1
- package/ccw/dist/core/routes/hooks-routes.d.ts.map +1 -1
- package/ccw/dist/core/routes/hooks-routes.js +30 -32
- package/ccw/dist/core/routes/hooks-routes.js.map +1 -1
- package/ccw/dist/core/routes/litellm-api-routes.d.ts +21 -0
- package/ccw/dist/core/routes/litellm-api-routes.d.ts.map +1 -0
- package/ccw/dist/core/routes/litellm-api-routes.js +780 -0
- package/ccw/dist/core/routes/litellm-api-routes.js.map +1 -0
- package/ccw/dist/core/routes/litellm-routes.d.ts +20 -0
- package/ccw/dist/core/routes/litellm-routes.d.ts.map +1 -0
- package/ccw/dist/core/routes/litellm-routes.js +85 -0
- package/ccw/dist/core/routes/litellm-routes.js.map +1 -0
- package/ccw/dist/core/routes/mcp-routes.js +2 -2
- package/ccw/dist/core/routes/mcp-routes.js.map +1 -1
- package/ccw/dist/core/routes/status-routes.d.ts.map +1 -1
- package/ccw/dist/core/routes/status-routes.js +39 -0
- package/ccw/dist/core/routes/status-routes.js.map +1 -1
- package/ccw/dist/core/routes/system-routes.js +1 -1
- package/ccw/dist/core/routes/system-routes.js.map +1 -1
- package/ccw/dist/core/server.d.ts.map +1 -1
- package/ccw/dist/core/server.js +15 -1
- package/ccw/dist/core/server.js.map +1 -1
- package/ccw/dist/mcp-server/index.js +1 -1
- package/ccw/dist/mcp-server/index.js.map +1 -1
- package/ccw/dist/tools/claude-cli-tools.d.ts +82 -0
- package/ccw/dist/tools/claude-cli-tools.d.ts.map +1 -0
- package/ccw/dist/tools/claude-cli-tools.js +216 -0
- package/ccw/dist/tools/claude-cli-tools.js.map +1 -0
- package/ccw/dist/tools/cli-executor.d.ts.map +1 -1
- package/ccw/dist/tools/cli-executor.js +76 -14
- package/ccw/dist/tools/cli-executor.js.map +1 -1
- package/ccw/dist/tools/codex-lens.d.ts +9 -2
- package/ccw/dist/tools/codex-lens.d.ts.map +1 -1
- package/ccw/dist/tools/codex-lens.js +114 -9
- package/ccw/dist/tools/codex-lens.js.map +1 -1
- package/ccw/dist/tools/context-cache-store.d.ts +136 -0
- package/ccw/dist/tools/context-cache-store.d.ts.map +1 -0
- package/ccw/dist/tools/context-cache-store.js +256 -0
- package/ccw/dist/tools/context-cache-store.js.map +1 -0
- package/ccw/dist/tools/context-cache.d.ts +56 -0
- package/ccw/dist/tools/context-cache.d.ts.map +1 -0
- package/ccw/dist/tools/context-cache.js +294 -0
- package/ccw/dist/tools/context-cache.js.map +1 -0
- package/ccw/dist/tools/core-memory.d.ts.map +1 -1
- package/ccw/dist/tools/core-memory.js +33 -19
- package/ccw/dist/tools/core-memory.js.map +1 -1
- package/ccw/dist/tools/index.d.ts.map +1 -1
- package/ccw/dist/tools/index.js +2 -0
- package/ccw/dist/tools/index.js.map +1 -1
- package/ccw/dist/tools/litellm-client.d.ts +85 -0
- package/ccw/dist/tools/litellm-client.d.ts.map +1 -0
- package/ccw/dist/tools/litellm-client.js +188 -0
- package/ccw/dist/tools/litellm-client.js.map +1 -0
- package/ccw/dist/tools/litellm-executor.d.ts +34 -0
- package/ccw/dist/tools/litellm-executor.d.ts.map +1 -0
- package/ccw/dist/tools/litellm-executor.js +192 -0
- package/ccw/dist/tools/litellm-executor.js.map +1 -0
- package/ccw/dist/tools/pattern-parser.d.ts +55 -0
- package/ccw/dist/tools/pattern-parser.d.ts.map +1 -0
- package/ccw/dist/tools/pattern-parser.js +237 -0
- package/ccw/dist/tools/pattern-parser.js.map +1 -0
- package/ccw/dist/tools/smart-search.d.ts +1 -0
- package/ccw/dist/tools/smart-search.d.ts.map +1 -1
- package/ccw/dist/tools/smart-search.js +117 -41
- package/ccw/dist/tools/smart-search.js.map +1 -1
- package/ccw/dist/types/litellm-api-config.d.ts +294 -0
- package/ccw/dist/types/litellm-api-config.d.ts.map +1 -0
- package/ccw/dist/types/litellm-api-config.js +8 -0
- package/ccw/dist/types/litellm-api-config.js.map +1 -0
- package/ccw/src/cli.ts +258 -244
- package/ccw/src/commands/cli.ts +153 -9
- package/ccw/src/commands/hook.ts +3 -2
- package/ccw/src/config/.litellm-api-config-manager.ts.2025-12-23T11-57-43-727Z.bak +441 -0
- package/ccw/src/config/litellm-api-config-manager.ts +1012 -0
- package/ccw/src/config/provider-models.ts +222 -0
- package/ccw/src/core/cache-manager.ts +292 -294
- package/ccw/src/core/dashboard-generator.ts +3 -1
- package/ccw/src/core/routes/cli-routes.ts +192 -0
- package/ccw/src/core/routes/codexlens-routes.ts +241 -19
- package/ccw/src/core/routes/hooks-routes.ts +399 -405
- package/ccw/src/core/routes/litellm-api-routes.ts +930 -0
- package/ccw/src/core/routes/litellm-routes.ts +107 -0
- package/ccw/src/core/routes/mcp-routes.ts +1271 -1271
- package/ccw/src/core/routes/status-routes.ts +51 -0
- package/ccw/src/core/routes/system-routes.ts +1 -1
- package/ccw/src/core/server.ts +15 -1
- package/ccw/src/mcp-server/index.ts +1 -1
- package/ccw/src/templates/dashboard-css/12-cli-legacy.css +44 -0
- package/ccw/src/templates/dashboard-css/31-api-settings.css +2265 -0
- package/ccw/src/templates/dashboard-js/components/cli-history.js +15 -8
- package/ccw/src/templates/dashboard-js/components/cli-status.js +323 -9
- package/ccw/src/templates/dashboard-js/components/navigation.js +329 -313
- package/ccw/src/templates/dashboard-js/i18n.js +583 -1
- package/ccw/src/templates/dashboard-js/views/api-settings.js +3362 -0
- package/ccw/src/templates/dashboard-js/views/cli-manager.js +199 -24
- package/ccw/src/templates/dashboard-js/views/codexlens-manager.js +1265 -27
- package/ccw/src/templates/dashboard.html +840 -831
- package/ccw/src/tools/claude-cli-tools.ts +300 -0
- package/ccw/src/tools/cli-executor.ts +83 -14
- package/ccw/src/tools/codex-lens.ts +146 -9
- package/ccw/src/tools/context-cache-store.ts +368 -0
- package/ccw/src/tools/context-cache.ts +393 -0
- package/ccw/src/tools/core-memory.ts +33 -19
- package/ccw/src/tools/index.ts +2 -0
- package/ccw/src/tools/litellm-client.ts +246 -0
- package/ccw/src/tools/litellm-executor.ts +241 -0
- package/ccw/src/tools/pattern-parser.ts +329 -0
- package/ccw/src/tools/smart-search.ts +142 -41
- package/ccw/src/types/litellm-api-config.ts +402 -0
- package/ccw-litellm/README.md +180 -0
- package/ccw-litellm/pyproject.toml +35 -0
- package/ccw-litellm/src/ccw_litellm/__init__.py +47 -0
- package/ccw-litellm/src/ccw_litellm/__pycache__/__init__.cpython-313.pyc +0 -0
- package/ccw-litellm/src/ccw_litellm/__pycache__/cli.cpython-313.pyc +0 -0
- package/ccw-litellm/src/ccw_litellm/cli.py +108 -0
- package/ccw-litellm/src/ccw_litellm/clients/__init__.py +12 -0
- package/ccw-litellm/src/ccw_litellm/clients/__pycache__/__init__.cpython-313.pyc +0 -0
- package/ccw-litellm/src/ccw_litellm/clients/__pycache__/litellm_embedder.cpython-313.pyc +0 -0
- package/ccw-litellm/src/ccw_litellm/clients/__pycache__/litellm_llm.cpython-313.pyc +0 -0
- package/ccw-litellm/src/ccw_litellm/clients/litellm_embedder.py +251 -0
- package/ccw-litellm/src/ccw_litellm/clients/litellm_llm.py +165 -0
- package/ccw-litellm/src/ccw_litellm/config/__init__.py +22 -0
- package/ccw-litellm/src/ccw_litellm/config/__pycache__/__init__.cpython-313.pyc +0 -0
- package/ccw-litellm/src/ccw_litellm/config/__pycache__/loader.cpython-313.pyc +0 -0
- package/ccw-litellm/src/ccw_litellm/config/__pycache__/models.cpython-313.pyc +0 -0
- package/ccw-litellm/src/ccw_litellm/config/loader.py +316 -0
- package/ccw-litellm/src/ccw_litellm/config/models.py +130 -0
- package/ccw-litellm/src/ccw_litellm/interfaces/__init__.py +14 -0
- package/ccw-litellm/src/ccw_litellm/interfaces/__pycache__/__init__.cpython-313.pyc +0 -0
- package/ccw-litellm/src/ccw_litellm/interfaces/__pycache__/embedder.cpython-313.pyc +0 -0
- package/ccw-litellm/src/ccw_litellm/interfaces/__pycache__/llm.cpython-313.pyc +0 -0
- package/ccw-litellm/src/ccw_litellm/interfaces/embedder.py +52 -0
- package/ccw-litellm/src/ccw_litellm/interfaces/llm.py +45 -0
- package/codex-lens/src/codexlens/__pycache__/config.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/cli/__pycache__/commands.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/cli/__pycache__/embedding_manager.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/cli/__pycache__/model_manager.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/cli/__pycache__/output.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/cli/commands.py +378 -23
- package/codex-lens/src/codexlens/cli/embedding_manager.py +660 -56
- package/codex-lens/src/codexlens/cli/model_manager.py +31 -18
- package/codex-lens/src/codexlens/cli/output.py +12 -1
- package/codex-lens/src/codexlens/config.py +93 -0
- package/codex-lens/src/codexlens/search/__pycache__/chain_search.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/search/__pycache__/hybrid_search.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/search/__pycache__/ranking.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/search/chain_search.py +6 -2
- package/codex-lens/src/codexlens/search/hybrid_search.py +44 -21
- package/codex-lens/src/codexlens/search/ranking.py +1 -1
- package/codex-lens/src/codexlens/semantic/__init__.py +42 -0
- package/codex-lens/src/codexlens/semantic/__pycache__/__init__.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/__pycache__/base.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/__pycache__/chunker.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/__pycache__/embedder.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/__pycache__/factory.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/__pycache__/gpu_support.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/__pycache__/litellm_embedder.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/__pycache__/vector_store.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/base.py +61 -0
- package/codex-lens/src/codexlens/semantic/chunker.py +43 -20
- package/codex-lens/src/codexlens/semantic/embedder.py +60 -13
- package/codex-lens/src/codexlens/semantic/factory.py +98 -0
- package/codex-lens/src/codexlens/semantic/gpu_support.py +225 -3
- package/codex-lens/src/codexlens/semantic/litellm_embedder.py +144 -0
- package/codex-lens/src/codexlens/semantic/rotational_embedder.py +434 -0
- package/codex-lens/src/codexlens/semantic/vector_store.py +33 -8
- package/codex-lens/src/codexlens/storage/__pycache__/path_mapper.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/storage/migrations/__pycache__/migration_004_dual_fts.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/storage/path_mapper.py +27 -1
- package/package.json +15 -5
- package/.codex/prompts.zip +0 -0
- package/ccw/package.json +0 -65
|
@@ -1,294 +1,292 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync, statSync } from 'fs';
|
|
2
|
-
import { join, dirname } from 'path';
|
|
3
|
-
import { StoragePaths, ensureStorageDir } from '../config/storage-paths.js';
|
|
4
|
-
|
|
5
|
-
interface CacheEntry<T> {
|
|
6
|
-
data: T;
|
|
7
|
-
timestamp: number;
|
|
8
|
-
fileHashes: Map<string, number>; // file path -> mtime
|
|
9
|
-
ttl?: number;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
interface CacheOptions {
|
|
13
|
-
ttl?: number; // Time-to-live in milliseconds (default: 5 minutes)
|
|
14
|
-
cacheDir?: string; // Cache directory (default: .ccw-cache)
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* CacheManager class for storing and retrieving dashboard data
|
|
19
|
-
* Tracks file modification times to detect changes and invalidate cache
|
|
20
|
-
*/
|
|
21
|
-
export class CacheManager<T> {
|
|
22
|
-
private cacheFile: string;
|
|
23
|
-
private ttl: number;
|
|
24
|
-
private cacheDir: string;
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Create a new CacheManager instance
|
|
28
|
-
* @param cacheKey - Unique identifier for this cache (e.g., 'dashboard-data')
|
|
29
|
-
* @param options - Cache configuration options
|
|
30
|
-
*/
|
|
31
|
-
constructor(cacheKey: string, options: CacheOptions = {}) {
|
|
32
|
-
if (!options.cacheDir) {
|
|
33
|
-
throw new Error('CacheManager requires cacheDir option. Use StoragePaths.project(path).cache');
|
|
34
|
-
}
|
|
35
|
-
this.ttl = options.ttl || 5 * 60 * 1000; // Default: 5 minutes
|
|
36
|
-
this.cacheDir = options.cacheDir;
|
|
37
|
-
this.cacheFile = join(this.cacheDir, `${cacheKey}.json`);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Get cached data if valid, otherwise return null
|
|
42
|
-
* @param watchPaths - Array of file/directory paths to check for modifications
|
|
43
|
-
* @returns Cached data or null if invalid/expired
|
|
44
|
-
*/
|
|
45
|
-
get(watchPaths: string[] = []): T | null {
|
|
46
|
-
if (!existsSync(this.cacheFile)) {
|
|
47
|
-
return null;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
try {
|
|
51
|
-
const content = readFileSync(this.cacheFile, 'utf8');
|
|
52
|
-
const entry: CacheEntry<T> = JSON.parse(content, (key, value) => {
|
|
53
|
-
// Revive Map objects from JSON
|
|
54
|
-
if (key === 'fileHashes' && value && typeof value === 'object') {
|
|
55
|
-
return new Map(Object.entries(value));
|
|
56
|
-
}
|
|
57
|
-
return value;
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
// Check TTL expiration
|
|
61
|
-
if (this.ttl > 0) {
|
|
62
|
-
const age = Date.now() - entry.timestamp;
|
|
63
|
-
if (age > this.ttl) {
|
|
64
|
-
return null;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Check if any watched files have changed
|
|
69
|
-
if (watchPaths.length > 0) {
|
|
70
|
-
const currentHashes = this.computeFileHashes(watchPaths);
|
|
71
|
-
if (!this.hashesMatch(entry.fileHashes, currentHashes)) {
|
|
72
|
-
return null;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
return entry.data;
|
|
77
|
-
} catch (err) {
|
|
78
|
-
// If cache file is corrupted or unreadable, treat as invalid
|
|
79
|
-
console.warn(`Cache read error for ${this.cacheFile}:`, (err as Error).message);
|
|
80
|
-
return null;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Store data in cache with current timestamp and file hashes
|
|
86
|
-
* @param data - Data to cache
|
|
87
|
-
* @param watchPaths - Array of file/directory paths to track
|
|
88
|
-
*/
|
|
89
|
-
set(data: T, watchPaths: string[] = []): void {
|
|
90
|
-
try {
|
|
91
|
-
// Ensure cache directory exists
|
|
92
|
-
if (!existsSync(this.cacheDir)) {
|
|
93
|
-
mkdirSync(this.cacheDir, { recursive: true });
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const entry: CacheEntry<T> = {
|
|
97
|
-
data,
|
|
98
|
-
timestamp: Date.now(),
|
|
99
|
-
fileHashes: this.computeFileHashes(watchPaths),
|
|
100
|
-
ttl: this.ttl
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
// Convert Map to plain object for JSON serialization
|
|
104
|
-
const serializable = {
|
|
105
|
-
...entry,
|
|
106
|
-
fileHashes: Object.fromEntries(entry.fileHashes)
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
writeFileSync(this.cacheFile, JSON.stringify(serializable, null, 2), 'utf8');
|
|
110
|
-
} catch (err) {
|
|
111
|
-
console.warn(`Cache write error for ${this.cacheFile}:`, (err as Error).message);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Invalidate (delete) the cache
|
|
117
|
-
*/
|
|
118
|
-
invalidate(): void {
|
|
119
|
-
try {
|
|
120
|
-
if (existsSync(this.cacheFile)) {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
*
|
|
131
|
-
* @
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
*
|
|
140
|
-
* @
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
*
|
|
175
|
-
* @param
|
|
176
|
-
* @param
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
entry.name
|
|
205
|
-
entry.name === '
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
*
|
|
222
|
-
* @
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
const
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
const
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
};
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
*
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
*
|
|
284
|
-
* @
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
return new CacheManager('dashboard-data', { cacheDir, ttl });
|
|
294
|
-
}
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, statSync, unlinkSync, readdirSync } from 'fs';
|
|
2
|
+
import { join, dirname } from 'path';
|
|
3
|
+
import { StoragePaths, ensureStorageDir } from '../config/storage-paths.js';
|
|
4
|
+
|
|
5
|
+
interface CacheEntry<T> {
|
|
6
|
+
data: T;
|
|
7
|
+
timestamp: number;
|
|
8
|
+
fileHashes: Map<string, number>; // file path -> mtime
|
|
9
|
+
ttl?: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface CacheOptions {
|
|
13
|
+
ttl?: number; // Time-to-live in milliseconds (default: 5 minutes)
|
|
14
|
+
cacheDir?: string; // Cache directory (default: .ccw-cache)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* CacheManager class for storing and retrieving dashboard data
|
|
19
|
+
* Tracks file modification times to detect changes and invalidate cache
|
|
20
|
+
*/
|
|
21
|
+
export class CacheManager<T> {
|
|
22
|
+
private cacheFile: string;
|
|
23
|
+
private ttl: number;
|
|
24
|
+
private cacheDir: string;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Create a new CacheManager instance
|
|
28
|
+
* @param cacheKey - Unique identifier for this cache (e.g., 'dashboard-data')
|
|
29
|
+
* @param options - Cache configuration options
|
|
30
|
+
*/
|
|
31
|
+
constructor(cacheKey: string, options: CacheOptions = {}) {
|
|
32
|
+
if (!options.cacheDir) {
|
|
33
|
+
throw new Error('CacheManager requires cacheDir option. Use StoragePaths.project(path).cache');
|
|
34
|
+
}
|
|
35
|
+
this.ttl = options.ttl || 5 * 60 * 1000; // Default: 5 minutes
|
|
36
|
+
this.cacheDir = options.cacheDir;
|
|
37
|
+
this.cacheFile = join(this.cacheDir, `${cacheKey}.json`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Get cached data if valid, otherwise return null
|
|
42
|
+
* @param watchPaths - Array of file/directory paths to check for modifications
|
|
43
|
+
* @returns Cached data or null if invalid/expired
|
|
44
|
+
*/
|
|
45
|
+
get(watchPaths: string[] = []): T | null {
|
|
46
|
+
if (!existsSync(this.cacheFile)) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
const content = readFileSync(this.cacheFile, 'utf8');
|
|
52
|
+
const entry: CacheEntry<T> = JSON.parse(content, (key, value) => {
|
|
53
|
+
// Revive Map objects from JSON
|
|
54
|
+
if (key === 'fileHashes' && value && typeof value === 'object') {
|
|
55
|
+
return new Map(Object.entries(value));
|
|
56
|
+
}
|
|
57
|
+
return value;
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Check TTL expiration
|
|
61
|
+
if (this.ttl > 0) {
|
|
62
|
+
const age = Date.now() - entry.timestamp;
|
|
63
|
+
if (age > this.ttl) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Check if any watched files have changed
|
|
69
|
+
if (watchPaths.length > 0) {
|
|
70
|
+
const currentHashes = this.computeFileHashes(watchPaths);
|
|
71
|
+
if (!this.hashesMatch(entry.fileHashes, currentHashes)) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return entry.data;
|
|
77
|
+
} catch (err) {
|
|
78
|
+
// If cache file is corrupted or unreadable, treat as invalid
|
|
79
|
+
console.warn(`Cache read error for ${this.cacheFile}:`, (err as Error).message);
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Store data in cache with current timestamp and file hashes
|
|
86
|
+
* @param data - Data to cache
|
|
87
|
+
* @param watchPaths - Array of file/directory paths to track
|
|
88
|
+
*/
|
|
89
|
+
set(data: T, watchPaths: string[] = []): void {
|
|
90
|
+
try {
|
|
91
|
+
// Ensure cache directory exists
|
|
92
|
+
if (!existsSync(this.cacheDir)) {
|
|
93
|
+
mkdirSync(this.cacheDir, { recursive: true });
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const entry: CacheEntry<T> = {
|
|
97
|
+
data,
|
|
98
|
+
timestamp: Date.now(),
|
|
99
|
+
fileHashes: this.computeFileHashes(watchPaths),
|
|
100
|
+
ttl: this.ttl
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// Convert Map to plain object for JSON serialization
|
|
104
|
+
const serializable = {
|
|
105
|
+
...entry,
|
|
106
|
+
fileHashes: Object.fromEntries(entry.fileHashes)
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
writeFileSync(this.cacheFile, JSON.stringify(serializable, null, 2), 'utf8');
|
|
110
|
+
} catch (err) {
|
|
111
|
+
console.warn(`Cache write error for ${this.cacheFile}:`, (err as Error).message);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Invalidate (delete) the cache
|
|
117
|
+
*/
|
|
118
|
+
invalidate(): void {
|
|
119
|
+
try {
|
|
120
|
+
if (existsSync(this.cacheFile)) {
|
|
121
|
+
unlinkSync(this.cacheFile);
|
|
122
|
+
}
|
|
123
|
+
} catch (err) {
|
|
124
|
+
console.warn(`Cache invalidation error for ${this.cacheFile}:`, (err as Error).message);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Check if cache is valid without retrieving data
|
|
130
|
+
* @param watchPaths - Array of file/directory paths to check
|
|
131
|
+
* @returns True if cache exists and is valid
|
|
132
|
+
*/
|
|
133
|
+
isValid(watchPaths: string[] = []): boolean {
|
|
134
|
+
return this.get(watchPaths) !== null;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Compute file modification times for all watched paths
|
|
139
|
+
* @param watchPaths - Array of file/directory paths
|
|
140
|
+
* @returns Map of path to mtime
|
|
141
|
+
*/
|
|
142
|
+
private computeFileHashes(watchPaths: string[]): Map<string, number> {
|
|
143
|
+
const hashes = new Map<string, number>();
|
|
144
|
+
|
|
145
|
+
for (const path of watchPaths) {
|
|
146
|
+
try {
|
|
147
|
+
if (!existsSync(path)) {
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const stats = statSync(path);
|
|
152
|
+
|
|
153
|
+
if (stats.isDirectory()) {
|
|
154
|
+
// For directories, use directory mtime (detects file additions/deletions)
|
|
155
|
+
hashes.set(path, stats.mtimeMs);
|
|
156
|
+
|
|
157
|
+
// Also recursively scan for workflow session files
|
|
158
|
+
this.scanDirectory(path, hashes);
|
|
159
|
+
} else {
|
|
160
|
+
// For files, use file mtime
|
|
161
|
+
hashes.set(path, stats.mtimeMs);
|
|
162
|
+
}
|
|
163
|
+
} catch (err) {
|
|
164
|
+
// Skip paths that can't be accessed
|
|
165
|
+
console.warn(`Cannot access path ${path}:`, (err as Error).message);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return hashes;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Recursively scan directory for important files
|
|
174
|
+
* @param dirPath - Directory to scan
|
|
175
|
+
* @param hashes - Map to store file hashes
|
|
176
|
+
* @param depth - Current recursion depth (max 3)
|
|
177
|
+
*/
|
|
178
|
+
private scanDirectory(dirPath: string, hashes: Map<string, number>, depth: number = 0): void {
|
|
179
|
+
if (depth > 3) return; // Limit recursion depth
|
|
180
|
+
|
|
181
|
+
try {
|
|
182
|
+
const entries = readdirSync(dirPath, { withFileTypes: true });
|
|
183
|
+
|
|
184
|
+
for (const entry of entries) {
|
|
185
|
+
const fullPath = join(dirPath, entry.name);
|
|
186
|
+
|
|
187
|
+
if (entry.isDirectory()) {
|
|
188
|
+
// Track important directories
|
|
189
|
+
if (entry.name === '.task' || entry.name === '.review' || entry.name === '.summaries') {
|
|
190
|
+
const stats = statSync(fullPath);
|
|
191
|
+
hashes.set(fullPath, stats.mtimeMs);
|
|
192
|
+
this.scanDirectory(fullPath, hashes, depth + 1);
|
|
193
|
+
} else if (entry.name.startsWith('WFS-')) {
|
|
194
|
+
// Scan WFS session directories
|
|
195
|
+
const stats = statSync(fullPath);
|
|
196
|
+
hashes.set(fullPath, stats.mtimeMs);
|
|
197
|
+
this.scanDirectory(fullPath, hashes, depth + 1);
|
|
198
|
+
}
|
|
199
|
+
} else if (entry.isFile()) {
|
|
200
|
+
// Track important files
|
|
201
|
+
if (
|
|
202
|
+
entry.name.endsWith('.json') ||
|
|
203
|
+
entry.name === 'IMPL_PLAN.md' ||
|
|
204
|
+
entry.name === 'TODO_LIST.md' ||
|
|
205
|
+
entry.name === 'workflow-session.json'
|
|
206
|
+
) {
|
|
207
|
+
const stats = statSync(fullPath);
|
|
208
|
+
hashes.set(fullPath, stats.mtimeMs);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
} catch (err) {
|
|
213
|
+
// Skip directories that can't be read
|
|
214
|
+
console.warn(`Cannot scan directory ${dirPath}:`, (err as Error).message);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Compare two file hash maps
|
|
220
|
+
* @param oldHashes - Previous hashes
|
|
221
|
+
* @param newHashes - Current hashes
|
|
222
|
+
* @returns True if hashes match (no changes)
|
|
223
|
+
*/
|
|
224
|
+
private hashesMatch(oldHashes: Map<string, number>, newHashes: Map<string, number>): boolean {
|
|
225
|
+
// Check if any files were added or removed
|
|
226
|
+
if (oldHashes.size !== newHashes.size) {
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Check if any file mtimes changed
|
|
231
|
+
const entries = Array.from(oldHashes.entries());
|
|
232
|
+
for (let i = 0; i < entries.length; i++) {
|
|
233
|
+
const path = entries[i][0];
|
|
234
|
+
const oldMtime = entries[i][1];
|
|
235
|
+
const newMtime = newHashes.get(path);
|
|
236
|
+
if (newMtime === undefined || newMtime !== oldMtime) {
|
|
237
|
+
return false;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return true;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Get cache statistics
|
|
246
|
+
* @returns Cache info object
|
|
247
|
+
*/
|
|
248
|
+
getStats(): { exists: boolean; age?: number; fileCount?: number; size?: number } {
|
|
249
|
+
if (!existsSync(this.cacheFile)) {
|
|
250
|
+
return { exists: false };
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
try {
|
|
254
|
+
const stats = statSync(this.cacheFile);
|
|
255
|
+
const content = readFileSync(this.cacheFile, 'utf8');
|
|
256
|
+
const entry = JSON.parse(content);
|
|
257
|
+
|
|
258
|
+
return {
|
|
259
|
+
exists: true,
|
|
260
|
+
age: Date.now() - entry.timestamp,
|
|
261
|
+
fileCount: Object.keys(entry.fileHashes || {}).length,
|
|
262
|
+
size: stats.size
|
|
263
|
+
};
|
|
264
|
+
} catch {
|
|
265
|
+
return { exists: false };
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Extract project path from workflow directory
|
|
272
|
+
* @param workflowDir - Path to .workflow directory (e.g., /project/.workflow)
|
|
273
|
+
* @returns Project root path
|
|
274
|
+
*/
|
|
275
|
+
function extractProjectPath(workflowDir: string): string {
|
|
276
|
+
// workflowDir is typically {projectPath}/.workflow
|
|
277
|
+
return workflowDir.replace(/[\/\\]\.workflow$/, '') || workflowDir;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Create a cache manager for dashboard data
|
|
282
|
+
* @param workflowDir - Path to .workflow directory
|
|
283
|
+
* @param ttl - Optional TTL in milliseconds
|
|
284
|
+
* @returns CacheManager instance
|
|
285
|
+
*/
|
|
286
|
+
export function createDashboardCache(workflowDir: string, ttl?: number): CacheManager<any> {
|
|
287
|
+
// Use centralized storage path
|
|
288
|
+
const projectPath = extractProjectPath(workflowDir);
|
|
289
|
+
const cacheDir = StoragePaths.project(projectPath).cache;
|
|
290
|
+
ensureStorageDir(cacheDir);
|
|
291
|
+
return new CacheManager('dashboard-data', { cacheDir, ttl });
|
|
292
|
+
}
|
|
@@ -46,7 +46,8 @@ const MODULE_CSS_FILES = [
|
|
|
46
46
|
'27-graph-explorer.css',
|
|
47
47
|
'28-mcp-manager.css',
|
|
48
48
|
'29-help.css',
|
|
49
|
-
'30-core-memory.css'
|
|
49
|
+
'30-core-memory.css',
|
|
50
|
+
'31-api-settings.css'
|
|
50
51
|
];
|
|
51
52
|
|
|
52
53
|
const MODULE_FILES = [
|
|
@@ -95,6 +96,7 @@ const MODULE_FILES = [
|
|
|
95
96
|
'views/skills-manager.js',
|
|
96
97
|
'views/rules-manager.js',
|
|
97
98
|
'views/claude-manager.js',
|
|
99
|
+
'views/api-settings.js',
|
|
98
100
|
'views/help.js',
|
|
99
101
|
'main.js'
|
|
100
102
|
];
|