claude-code-session-manager 0.33.1 → 0.34.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/README.md +5 -1
- package/dist/assets/{TiptapBody-BqQFXHkk.js → TiptapBody-DWeWI8gw.js} +51 -51
- package/dist/assets/index-C2m4dco8.css +32 -0
- package/dist/assets/index-CFT773vM.js +3491 -0
- package/dist/index.html +2 -2
- package/package.json +1 -1
- package/plugins/session-manager-dev/skills/discover-features/SKILL.md +95 -0
- package/plugins/session-manager-dev/skills/optimize-kpi/SKILL.md +9 -9
- package/plugins/session-manager-dev/skills/process-feedback/SKILL.md +77 -62
- package/plugins/session-manager-dev/skills/project-status/SKILL.md +13 -0
- package/src/main/__tests__/chat-queue.test.cjs +65 -0
- package/src/main/__tests__/chat-stop-signal.test.cjs +52 -0
- package/src/main/__tests__/exchanges.test.cjs +177 -0
- package/src/main/__tests__/files-reject-credentials.test.cjs +40 -0
- package/src/main/__tests__/kg-augment.test.cjs +195 -0
- package/src/main/__tests__/memoryAggregate.test.cjs +70 -0
- package/src/main/agentMemory.cjs +0 -21
- package/src/main/chatRunner.cjs +393 -0
- package/src/main/exchanges.cjs +125 -0
- package/src/main/files.cjs +20 -8
- package/src/main/historyAggregator.cjs +0 -162
- package/src/main/index.cjs +76 -57
- package/src/main/ipcSchemas.cjs +70 -30
- package/src/main/lib/kgExchangePairing.cjs +75 -0
- package/src/main/lib/reaperHelpers.cjs +7 -1
- package/src/main/lib/summarize.cjs +114 -0
- package/src/main/memoryAggregate.cjs +250 -0
- package/src/main/pluginInstall.cjs +4 -2
- package/src/main/scheduler.cjs +66 -20
- package/src/main/supervisor.cjs +16 -0
- package/src/main/transcripts.cjs +22 -2
- package/src/main/voiceHotkey.cjs +0 -2
- package/src/main/webRemote.cjs +11 -78
- package/src/preload/api.d.ts +131 -125
- package/src/preload/index.cjs +45 -29
- package/dist/assets/index-1PpZBVUr.js +0 -3535
- package/dist/assets/index-AKeGl-VM.css +0 -32
- package/src/main/kg.cjs +0 -792
- package/src/main/lib/kgLite.cjs +0 -195
- package/src/main/lib/kgPrune.cjs +0 -87
package/src/main/lib/kgLite.cjs
DELETED
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* kgLite.cjs — Heuristic knowledge-graph extraction (no LLM, no network).
|
|
5
|
-
*
|
|
6
|
-
* Exports `canonicalize` and `ENTITY_TYPES` as the shared vocabulary contract
|
|
7
|
-
* (kg.cjs imports these so there is ONE definition for both paths), and
|
|
8
|
-
* `liteExtract` which is the cheap alternative to the `claude -p` extractor.
|
|
9
|
-
*
|
|
10
|
-
* Precision vs LLM path:
|
|
11
|
-
* Higher precision on known-vocab entities (exact canonical-key match).
|
|
12
|
-
* Lower recall on novel entities — extracts capitalized phrases, quoted
|
|
13
|
-
* identifiers, CamelCase tokens, and tech keywords; misses entities stated
|
|
14
|
-
* only in plain lowercase prose. Relations are always generic `related_to`
|
|
15
|
-
* co-occurrences (no semantic labelling). A lite graph grows meaningfully
|
|
16
|
-
* but is sparser and less richly typed than the LLM path. Good for
|
|
17
|
-
* cost-sensitive setups where search/Q&A value matters but per-prompt
|
|
18
|
-
* claude -p cost does not.
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
/** Canonical dedup key: lowercase, strip leading article, collapse whitespace.
|
|
22
|
-
* This function is the SINGLE definition shared with kg.cjs — any change here
|
|
23
|
-
* affects both extraction paths equally. */
|
|
24
|
-
function canonicalize(name) {
|
|
25
|
-
return String(name || '')
|
|
26
|
-
.trim()
|
|
27
|
-
.toLowerCase()
|
|
28
|
-
.replace(/^(the|a|an)\s+/i, '')
|
|
29
|
-
.replace(/\s+/g, ' ')
|
|
30
|
-
.replace(/[.,;:!?]+$/, '');
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const ENTITY_TYPES = ['project', 'feature', 'tool', 'tech', 'concept', 'goal', 'person'];
|
|
34
|
-
|
|
35
|
-
// Tech terms that map directly to type 'tech' on first encounter.
|
|
36
|
-
const TECH_KEYWORDS = new Set([
|
|
37
|
-
'react', 'electron', 'typescript', 'javascript', 'python', 'node', 'nodejs',
|
|
38
|
-
'vite', 'tailwind', 'zustand', 'xterm', 'playwright', 'sqlite', 'postgres',
|
|
39
|
-
'postgresql', 'redis', 'docker', 'kubernetes', 'webpack', 'esbuild', 'jest',
|
|
40
|
-
'vitest', 'graphql', 'rest', 'api', 'http', 'https', 'websocket', 'pty',
|
|
41
|
-
'ipc', 'mcp', 'git', 'npm', 'npx', 'pnpm', 'yarn', 'bash', 'zsh', 'curl',
|
|
42
|
-
'json', 'yaml', 'tsx', 'jsx', 'cjs', 'esm', 'commonjs', 'llm', 'rag',
|
|
43
|
-
'openai', 'anthropic', 'claude', 'oauth', 'jwt', 'cors',
|
|
44
|
-
]);
|
|
45
|
-
|
|
46
|
-
// File extensions that mark a quoted/path identifier as type 'tech'.
|
|
47
|
-
const TECH_EXTS = new Set([
|
|
48
|
-
'.ts', '.tsx', '.cjs', '.mjs', '.js', '.jsx', '.py', '.go', '.rs', '.sh',
|
|
49
|
-
'.md', '.json', '.yaml', '.yml', '.toml', '.env',
|
|
50
|
-
]);
|
|
51
|
-
|
|
52
|
-
// Common English words that appear capitalized at sentence start or as pronouns
|
|
53
|
-
// and are NOT meaningful developer-domain entities. Filter before adding.
|
|
54
|
-
const STOP_CAPS = new Set([
|
|
55
|
-
'I', 'A', 'An', 'The', 'In', 'On', 'At', 'To', 'For', 'Of', 'With', 'By',
|
|
56
|
-
'From', 'As', 'Is', 'Was', 'Are', 'Were', 'Be', 'Been', 'Have', 'Has', 'Had',
|
|
57
|
-
'Do', 'Does', 'Did', 'Will', 'Would', 'Could', 'Should', 'May', 'Might',
|
|
58
|
-
'Can', 'If', 'But', 'And', 'Or', 'Not', 'So', 'This', 'That', 'These',
|
|
59
|
-
'Those', 'We', 'You', 'They', 'It', 'My', 'Your', 'Our', 'Its', 'Their',
|
|
60
|
-
'When', 'Where', 'Who', 'What', 'How', 'Why', 'Which', 'Then', 'Just', 'Now',
|
|
61
|
-
'Also', 'Here', 'There', 'Add', 'Get', 'Use', 'Run', 'Make', 'Fix', 'New',
|
|
62
|
-
'Yes', 'No', 'Ok', 'Let', 'See', 'Set', 'Put', 'Try', 'Keep', 'Show', 'Go',
|
|
63
|
-
'Tab', 'Now', 'Out', 'Up', 'Down', 'All', 'Any', 'Some', 'One', 'Two', 'Its',
|
|
64
|
-
]);
|
|
65
|
-
|
|
66
|
-
/** Infer entity type from surface form when not in known vocab. */
|
|
67
|
-
function inferType(raw) {
|
|
68
|
-
const lower = raw.toLowerCase();
|
|
69
|
-
if (TECH_KEYWORDS.has(lower)) return 'tech';
|
|
70
|
-
const extM = raw.match(/(\.[a-zA-Z0-9]+)$/);
|
|
71
|
-
if (extM && TECH_EXTS.has(extM[1].toLowerCase())) return 'tech';
|
|
72
|
-
return 'concept';
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Heuristic entity/relation extraction — no network, no child_process.spawn.
|
|
77
|
-
*
|
|
78
|
-
* Algorithm per prompt (Time: O(W) for extraction, O(k²) for edges):
|
|
79
|
-
* 1. Known-vocab scan: tokenize on whitespace/punctuation, look up each
|
|
80
|
-
* token and adjacent bigram in the vocab Map — O(W) with Map lookups.
|
|
81
|
-
* 2. Multi-word capitalized phrases + acronyms (regex pass): "Knowledge
|
|
82
|
-
* Graph", "Session Manager", "PRD". Higher precision than singles.
|
|
83
|
-
* 3. CamelCase identifiers (regex pass): `KnowledgeGraph`, `SchedulerDock`.
|
|
84
|
-
* 4. Quoted identifiers (regex pass): `scheduler.cjs`, "lite mode".
|
|
85
|
-
* 5. Single capitalized words (regex pass, STOP_CAPS filtered): "Scheduler".
|
|
86
|
-
* 6. Co-occurrence edges among all entities found in the same prompt.
|
|
87
|
-
* O(k²) where k = distinct entities per prompt — typically < 10.
|
|
88
|
-
*
|
|
89
|
-
* Dedup: canonicalize() maps every surface form to the same key ("The
|
|
90
|
-
* Scheduler" / "scheduler" / "Scheduler" all → key "scheduler"), so
|
|
91
|
-
* entityMap never accumulates duplicates.
|
|
92
|
-
*
|
|
93
|
-
* @param {Array<{prompt:string,ts?:string}>} prompts
|
|
94
|
-
* @param {Array<{key:string,name:string,type?:string}>} knownVocab top-N nodes from the graph
|
|
95
|
-
* @returns {{ entities: Array<{key,name,type,description}>, relations: Array<{src,dst,relation,description}> }}
|
|
96
|
-
*/
|
|
97
|
-
function liteExtract(prompts, knownVocab) {
|
|
98
|
-
// O(V) build once; per-prompt lookups are O(1).
|
|
99
|
-
const vocabByKey = new Map((knownVocab || []).map((n) => [n.key, n]));
|
|
100
|
-
|
|
101
|
-
// Canonical key → entity — ensures no duplicates across the whole batch.
|
|
102
|
-
const entityMap = new Map();
|
|
103
|
-
|
|
104
|
-
function addEnt(raw, typeHint) {
|
|
105
|
-
const key = canonicalize(raw);
|
|
106
|
-
// Skip empty, pure-digit, or single-char keys.
|
|
107
|
-
if (!key || key.length < 2 || /^\d+$/.test(key)) return null;
|
|
108
|
-
if (!entityMap.has(key)) {
|
|
109
|
-
const known = vocabByKey.get(key);
|
|
110
|
-
const type = (known && ENTITY_TYPES.includes(known.type)) ? known.type
|
|
111
|
-
: ENTITY_TYPES.includes(typeHint) ? typeHint
|
|
112
|
-
: 'concept';
|
|
113
|
-
entityMap.set(key, { key, name: known ? known.name : raw, type, description: '' });
|
|
114
|
-
}
|
|
115
|
-
return key;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const relationSet = new Set(); // "src relation dst" dedup key
|
|
119
|
-
const relations = [];
|
|
120
|
-
|
|
121
|
-
// Regexes used across prompts — reset lastIndex per prompt.
|
|
122
|
-
// Multi-word capitalized phrase OR all-caps acronym (>=2 chars).
|
|
123
|
-
const CAP_PHRASE_RE = /\b([A-Z][a-zA-Z]*(?:\s+[A-Z][a-zA-Z]*)+|[A-Z]{2,})\b/g;
|
|
124
|
-
// CamelCase: two or more capitalized segments joined without space.
|
|
125
|
-
const CAMEL_RE = /\b([A-Z][a-z]+(?:[A-Z][a-z]*)+)\b/g;
|
|
126
|
-
// Quoted identifier. Backreference \1 ensures the closing delimiter matches
|
|
127
|
-
// the opening one — prevents "`foo` isn't" from closing the backtick-span
|
|
128
|
-
// on the apostrophe in "isn't" and extracting a garbage entity.
|
|
129
|
-
const QUOTED_RE = /([`"'])([^`"'\n]{2,40})\1/g;
|
|
130
|
-
// Single capitalized word (Title Case, not an acronym — handled by CAP_PHRASE_RE).
|
|
131
|
-
const CAP_WORD_RE = /\b([A-Z][a-z]{1,})\b/g;
|
|
132
|
-
|
|
133
|
-
for (const p of prompts) {
|
|
134
|
-
const text = String(p.prompt || '');
|
|
135
|
-
const promptKeys = [];
|
|
136
|
-
|
|
137
|
-
function record(key) {
|
|
138
|
-
if (key && !promptKeys.includes(key)) promptKeys.push(key);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// 1. Known-vocab scan: split on whitespace + punctuation, check token and bigram.
|
|
142
|
-
const tokens = text.split(/[\s,;:.!?()\[\]{}<>|/\\]+/).filter(Boolean);
|
|
143
|
-
for (let i = 0; i < tokens.length; i++) {
|
|
144
|
-
const k1 = canonicalize(tokens[i]);
|
|
145
|
-
if (vocabByKey.has(k1)) record(addEnt(tokens[i], vocabByKey.get(k1).type));
|
|
146
|
-
if (i + 1 < tokens.length) {
|
|
147
|
-
const bigram = tokens[i] + ' ' + tokens[i + 1];
|
|
148
|
-
const k2 = canonicalize(bigram);
|
|
149
|
-
if (vocabByKey.has(k2)) record(addEnt(bigram, vocabByKey.get(k2).type));
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// 2. Multi-word capitalized phrases + acronyms — highest precision.
|
|
154
|
-
let m;
|
|
155
|
-
CAP_PHRASE_RE.lastIndex = 0;
|
|
156
|
-
while ((m = CAP_PHRASE_RE.exec(text)) !== null) {
|
|
157
|
-
if (!STOP_CAPS.has(m[1])) record(addEnt(m[1], inferType(m[1])));
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// 3. CamelCase identifiers: KnowledgeGraph, SchedulerDock, etc.
|
|
161
|
-
CAMEL_RE.lastIndex = 0;
|
|
162
|
-
while ((m = CAMEL_RE.exec(text)) !== null) {
|
|
163
|
-
record(addEnt(m[1], inferType(m[1])));
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// 4. Quoted identifiers: `scheduler.cjs`, "lite mode", etc.
|
|
167
|
-
QUOTED_RE.lastIndex = 0;
|
|
168
|
-
while ((m = QUOTED_RE.exec(text)) !== null) {
|
|
169
|
-
const raw = m[2].trim();
|
|
170
|
-
if (raw.length >= 2) record(addEnt(raw, inferType(raw)));
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// 5. Single capitalized words (mid-sentence entities like "Scheduler").
|
|
174
|
-
// STOP_CAPS filters common sentence-start words and English pronouns.
|
|
175
|
-
CAP_WORD_RE.lastIndex = 0;
|
|
176
|
-
while ((m = CAP_WORD_RE.exec(text)) !== null) {
|
|
177
|
-
if (!STOP_CAPS.has(m[1])) record(addEnt(m[1], inferType(m[1])));
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// Emit co-occurrence edges: O(k²) where k = promptKeys.length (typically <10).
|
|
181
|
-
for (let i = 0; i < promptKeys.length; i++) {
|
|
182
|
-
for (let j = i + 1; j < promptKeys.length; j++) {
|
|
183
|
-
const ek = `${promptKeys[i]} related_to ${promptKeys[j]}`;
|
|
184
|
-
if (!relationSet.has(ek)) {
|
|
185
|
-
relationSet.add(ek);
|
|
186
|
-
relations.push({ src: promptKeys[i], dst: promptKeys[j], relation: 'related_to', description: '' });
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
return { entities: [...entityMap.values()], relations };
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
module.exports = { canonicalize, ENTITY_TYPES, liteExtract };
|
package/src/main/lib/kgPrune.cjs
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* kgPrune.cjs — decay-based node eviction for per-project knowledge graphs.
|
|
5
|
-
*
|
|
6
|
-
* API:
|
|
7
|
-
* nodeEvictionScore(node, degree, nowMs) → number (higher = keep)
|
|
8
|
-
* pruneGraph(graph, maxNodes, nowMs) → { nodes, edges, evicted }
|
|
9
|
-
*
|
|
10
|
-
* Complexity:
|
|
11
|
-
* nodeEvictionScore: O(1)
|
|
12
|
-
* pruneGraph: O(E + N log N) where N=nodes, E=edges
|
|
13
|
-
* - one O(E) pass to build degree map
|
|
14
|
-
* - one O(N log N) sort to select top-maxNodes
|
|
15
|
-
* - one O(E) pass to drop dangling edges
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
const HALF_LIFE_MS = 30 * 24 * 60 * 60 * 1000; // 30 days
|
|
19
|
-
const W_RECENCY = 0.5; // freshness — recently-touched nodes stay relevant
|
|
20
|
-
const W_DEGREE = 0.3; // connectivity — hubs link disparate ideas together
|
|
21
|
-
const W_COUNT = 0.2; // frequency — often-mentioned = important to the dev
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Score a node for retention. Higher = more worth keeping.
|
|
25
|
-
*
|
|
26
|
-
* Weighting rationale:
|
|
27
|
-
* Recency (50%): a node last seen 30 days ago decays to ~50% of a just-seen
|
|
28
|
-
* node — exponential decay keeps the graph current without hard cutoffs.
|
|
29
|
-
* Degree (30%): hub nodes (many connections) preserve graph coherence; without
|
|
30
|
-
* them the remaining graph fragments into disconnected islands.
|
|
31
|
-
* Count (20%): how often the developer mentioned something matters, but a
|
|
32
|
-
* high-count stale node should still decay below a freshly-active low-count one.
|
|
33
|
-
*
|
|
34
|
-
* @param {object} node KgNode with lastTs (ISO string|null), count (number)
|
|
35
|
-
* @param {number} degree edge count touching this node (src or dst)
|
|
36
|
-
* @param {number} nowMs Date.now() equivalent from caller (kept pure/testable)
|
|
37
|
-
* @returns {number}
|
|
38
|
-
*/
|
|
39
|
-
function nodeEvictionScore(node, degree, nowMs) {
|
|
40
|
-
let recency = 0;
|
|
41
|
-
if (node.lastTs) {
|
|
42
|
-
const ageMs = nowMs - new Date(node.lastTs).getTime();
|
|
43
|
-
recency = Math.exp(-ageMs / HALF_LIFE_MS); // 1.0 = just seen, decays to 0
|
|
44
|
-
}
|
|
45
|
-
const degScore = Math.log1p(degree); // log scale: 0→0, 1→0.69, 10→2.4
|
|
46
|
-
const cntScore = Math.log1p(node.count ?? 0); // same — avoids linear blow-up
|
|
47
|
-
return W_RECENCY * recency + W_DEGREE * degScore + W_COUNT * cntScore;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Prune a graph to at most maxNodes nodes, evicting the lowest-scored ones and
|
|
52
|
-
* all edges that referenced evicted nodes.
|
|
53
|
-
*
|
|
54
|
-
* @param {{ nodes: object[], edges: object[] }} graph
|
|
55
|
-
* @param {number} maxNodes cap; 0 = disabled (returns graph unchanged)
|
|
56
|
-
* @param {number} nowMs caller-supplied timestamp (keeps scorer pure)
|
|
57
|
-
* @returns {{ nodes: object[], edges: object[], evicted: number }}
|
|
58
|
-
*/
|
|
59
|
-
function pruneGraph(graph, maxNodes, nowMs) {
|
|
60
|
-
if (!maxNodes || graph.nodes.length <= maxNodes) {
|
|
61
|
-
return { nodes: graph.nodes, edges: graph.edges, evicted: 0 };
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// O(E): build id → degree map in a single pass over edges.
|
|
65
|
-
const degree = new Map();
|
|
66
|
-
for (const e of graph.edges) {
|
|
67
|
-
degree.set(e.src, (degree.get(e.src) ?? 0) + 1);
|
|
68
|
-
degree.set(e.dst, (degree.get(e.dst) ?? 0) + 1);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// O(N log N): score + sort; keep the top maxNodes.
|
|
72
|
-
const scored = graph.nodes.map((n) => ({ n, s: nodeEvictionScore(n, degree.get(n.id) ?? 0, nowMs) }));
|
|
73
|
-
scored.sort((a, b) => b.s - a.s); // descending: highest score first
|
|
74
|
-
const kept = scored.slice(0, maxNodes);
|
|
75
|
-
const evicted = scored.length - kept.length;
|
|
76
|
-
|
|
77
|
-
if (evicted === 0) return { nodes: graph.nodes, edges: graph.edges, evicted: 0 };
|
|
78
|
-
|
|
79
|
-
const keptIds = new Set(kept.map((x) => x.n.id));
|
|
80
|
-
|
|
81
|
-
// O(E): drop every edge whose src or dst was evicted (no dangling references).
|
|
82
|
-
const edges = graph.edges.filter((e) => keptIds.has(e.src) && keptIds.has(e.dst));
|
|
83
|
-
|
|
84
|
-
return { nodes: kept.map((x) => x.n), edges, evicted };
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
module.exports = { nodeEvictionScore, pruneGraph };
|