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.
Files changed (40) hide show
  1. package/README.md +5 -1
  2. package/dist/assets/{TiptapBody-BqQFXHkk.js → TiptapBody-DWeWI8gw.js} +51 -51
  3. package/dist/assets/index-C2m4dco8.css +32 -0
  4. package/dist/assets/index-CFT773vM.js +3491 -0
  5. package/dist/index.html +2 -2
  6. package/package.json +1 -1
  7. package/plugins/session-manager-dev/skills/discover-features/SKILL.md +95 -0
  8. package/plugins/session-manager-dev/skills/optimize-kpi/SKILL.md +9 -9
  9. package/plugins/session-manager-dev/skills/process-feedback/SKILL.md +77 -62
  10. package/plugins/session-manager-dev/skills/project-status/SKILL.md +13 -0
  11. package/src/main/__tests__/chat-queue.test.cjs +65 -0
  12. package/src/main/__tests__/chat-stop-signal.test.cjs +52 -0
  13. package/src/main/__tests__/exchanges.test.cjs +177 -0
  14. package/src/main/__tests__/files-reject-credentials.test.cjs +40 -0
  15. package/src/main/__tests__/kg-augment.test.cjs +195 -0
  16. package/src/main/__tests__/memoryAggregate.test.cjs +70 -0
  17. package/src/main/agentMemory.cjs +0 -21
  18. package/src/main/chatRunner.cjs +393 -0
  19. package/src/main/exchanges.cjs +125 -0
  20. package/src/main/files.cjs +20 -8
  21. package/src/main/historyAggregator.cjs +0 -162
  22. package/src/main/index.cjs +76 -57
  23. package/src/main/ipcSchemas.cjs +70 -30
  24. package/src/main/lib/kgExchangePairing.cjs +75 -0
  25. package/src/main/lib/reaperHelpers.cjs +7 -1
  26. package/src/main/lib/summarize.cjs +114 -0
  27. package/src/main/memoryAggregate.cjs +250 -0
  28. package/src/main/pluginInstall.cjs +4 -2
  29. package/src/main/scheduler.cjs +66 -20
  30. package/src/main/supervisor.cjs +16 -0
  31. package/src/main/transcripts.cjs +22 -2
  32. package/src/main/voiceHotkey.cjs +0 -2
  33. package/src/main/webRemote.cjs +11 -78
  34. package/src/preload/api.d.ts +131 -125
  35. package/src/preload/index.cjs +45 -29
  36. package/dist/assets/index-1PpZBVUr.js +0 -3535
  37. package/dist/assets/index-AKeGl-VM.css +0 -32
  38. package/src/main/kg.cjs +0 -792
  39. package/src/main/lib/kgLite.cjs +0 -195
  40. package/src/main/lib/kgPrune.cjs +0 -87
@@ -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 };
@@ -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 };