ziprin-context-optimizer 8.0.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 (56) hide show
  1. package/README.md +48 -0
  2. package/bin/ziprin-context-mcp.js +34 -0
  3. package/bin/ziprin-context-setup.js +10 -0
  4. package/install-source.json +5 -0
  5. package/media/context-activity.svg +7 -0
  6. package/package.json +154 -0
  7. package/releases/ziprin-context-optimizer-8.0.0.vsix +0 -0
  8. package/scripts/install.mjs +204 -0
  9. package/scripts/package-vsix.mjs +38 -0
  10. package/scripts/paths.mjs +40 -0
  11. package/scripts/publish.mjs +37 -0
  12. package/scripts/update.mjs +101 -0
  13. package/src/adaptive-budget.js +46 -0
  14. package/src/analyzer.js +456 -0
  15. package/src/audit-history.js +137 -0
  16. package/src/bm25.js +132 -0
  17. package/src/collector.js +343 -0
  18. package/src/compression.js +67 -0
  19. package/src/config.js +28 -0
  20. package/src/context-memory.js +151 -0
  21. package/src/context-profiles.js +124 -0
  22. package/src/dependency-graph.js +90 -0
  23. package/src/estimate.js +109 -0
  24. package/src/eval-harness.js +76 -0
  25. package/src/extension.js +322 -0
  26. package/src/firewall.js +35 -0
  27. package/src/fts-index.js +319 -0
  28. package/src/gateway-api.js +404 -0
  29. package/src/git-recency.js +43 -0
  30. package/src/glob.js +42 -0
  31. package/src/identifier.js +37 -0
  32. package/src/inspector-panel.js +470 -0
  33. package/src/language.js +157 -0
  34. package/src/mcp-event-stream.js +179 -0
  35. package/src/mcp-health-cli.js +34 -0
  36. package/src/mcp-lifecycle-manager.js +427 -0
  37. package/src/mcp-server.js +372 -0
  38. package/src/mmr.js +57 -0
  39. package/src/pagerank.js +176 -0
  40. package/src/profiles.js +74 -0
  41. package/src/pruner.js +130 -0
  42. package/src/quality-guard.js +122 -0
  43. package/src/query-rewrite.js +32 -0
  44. package/src/relevance-scorer.js +197 -0
  45. package/src/repo-map.js +134 -0
  46. package/src/retrieve.js +350 -0
  47. package/src/serena.js +126 -0
  48. package/src/session-ledger.js +83 -0
  49. package/src/session-store.js +78 -0
  50. package/src/skeleton.js +129 -0
  51. package/src/slice-pack.js +70 -0
  52. package/src/supervisor.js +113 -0
  53. package/src/task-analyzer.js +189 -0
  54. package/src/tool-router.js +92 -0
  55. package/src/version.js +5 -0
  56. package/templates/ziprin-context-mcp.mjs +68 -0
@@ -0,0 +1,372 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Ziprin AI Context MCP v8 — 8 tools, compact JSON, Serena-first.
5
+ * Intelligence via MCP; native Chat remains the UI.
6
+ */
7
+
8
+ const path = require('path');
9
+ const { z } = require('zod');
10
+ const { McpServer } = require('@modelcontextprotocol/sdk/server/mcp.js');
11
+ const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js');
12
+ const api = require('./gateway-api');
13
+ const { publishEvent, EVENT_TYPES } = require('./mcp-event-stream');
14
+ const { VERSION } = require('./version');
15
+
16
+ function parseArgs(argv) {
17
+ const out = { project: process.env.ZIPRIN_WORKSPACE || process.cwd() };
18
+ for (let i = 2; i < argv.length; i++) {
19
+ if (argv[i] === '--project' && argv[i + 1]) {
20
+ out.project = path.resolve(argv[++i]);
21
+ }
22
+ }
23
+ return out;
24
+ }
25
+
26
+ const cfg = parseArgs(process.argv);
27
+ let ROOT = cfg.project;
28
+
29
+ function textResult(obj) {
30
+ const body = typeof obj === 'string' ? obj : JSON.stringify(obj);
31
+ return { content: [{ type: 'text', text: body }] };
32
+ }
33
+
34
+ /** Stable tool catalog — max 8. quality_guard alias dropped. */
35
+ const TOOLS = [
36
+ {
37
+ name: 'ziprin_optimize_context',
38
+ description:
39
+ 'PRIMARY. Call BEFORE Serena/search/read/@folder. Returns lean map: filesIncluded, readNow, skeletons, serenaEmit. Skips greetings.',
40
+ inputSchema: {
41
+ type: 'object',
42
+ properties: {
43
+ prompt: { type: 'string' },
44
+ profileId: { type: 'string' },
45
+ force: { type: 'boolean' },
46
+ sessionId: { type: 'string' },
47
+ },
48
+ required: ['prompt'],
49
+ },
50
+ },
51
+ {
52
+ name: 'ziprin_analyze_task',
53
+ description: 'Classify task: category, complexity, token budget, FA/EN expansion.',
54
+ inputSchema: {
55
+ type: 'object',
56
+ properties: { prompt: { type: 'string' }, profileId: { type: 'string' } },
57
+ required: ['prompt'],
58
+ },
59
+ },
60
+ {
61
+ name: 'ziprin_rank_context',
62
+ description: 'Score and select relevant files; drop noise; keep/drop lists.',
63
+ inputSchema: {
64
+ type: 'object',
65
+ properties: {
66
+ prompt: { type: 'string' },
67
+ profileId: { type: 'string' },
68
+ limit: { type: 'number' },
69
+ },
70
+ required: ['prompt'],
71
+ },
72
+ },
73
+ {
74
+ name: 'ziprin_find_symbol',
75
+ description:
76
+ 'Serena-first find_symbol emit + local regex fallback. Call before read_file.',
77
+ inputSchema: {
78
+ type: 'object',
79
+ properties: {
80
+ name: { type: 'string' },
81
+ paths: { type: 'array', items: { type: 'string' } },
82
+ },
83
+ required: ['name'],
84
+ },
85
+ },
86
+ {
87
+ name: 'ziprin_unfold_symbol',
88
+ description: 'Unfold one function/class body from a skeleton map file.',
89
+ inputSchema: {
90
+ type: 'object',
91
+ properties: {
92
+ path: { type: 'string' },
93
+ name: { type: 'string' },
94
+ },
95
+ required: ['path'],
96
+ },
97
+ },
98
+ {
99
+ name: 'ziprin_validate_context',
100
+ description: 'Quality guard: validate optimized set; restore risky removals.',
101
+ inputSchema: {
102
+ type: 'object',
103
+ properties: {
104
+ prompt: { type: 'string' },
105
+ selected: { type: 'array', items: { type: 'string' } },
106
+ restored: { type: 'array', items: { type: 'string' } },
107
+ budget: { type: 'number' },
108
+ usedTokens: { type: 'number' },
109
+ },
110
+ },
111
+ },
112
+ {
113
+ name: 'ziprin_context_memory',
114
+ description: 'Read/learn path usefulness + co-occurrence from successful sessions.',
115
+ inputSchema: {
116
+ type: 'object',
117
+ properties: {
118
+ action: { type: 'string', enum: ['read', 'learn', 'clear'] },
119
+ filesIncluded: { type: 'array', items: { type: 'string' } },
120
+ taskClassification: { type: 'string' },
121
+ },
122
+ },
123
+ },
124
+ {
125
+ name: 'ziprin_health',
126
+ description: 'MCP health: version, FTS index, session count, memory.',
127
+ inputSchema: { type: 'object', properties: {} },
128
+ },
129
+ ];
130
+
131
+ function handleTool(name, args) {
132
+ const a = args || {};
133
+ switch (name) {
134
+ case 'ziprin_optimize_context': {
135
+ const full = api.optimizeContext(ROOT, {
136
+ prompt: a.prompt,
137
+ profileId: a.profileId,
138
+ force: a.force,
139
+ sessionId: a.sessionId,
140
+ source: 'mcp',
141
+ });
142
+ return api.toLeanResult(full);
143
+ }
144
+ case 'ziprin_analyze_task':
145
+ return api.analyzeTask(a.prompt, a.profileId);
146
+ case 'ziprin_rank_context':
147
+ return api.rankContext(ROOT, a.prompt, {
148
+ profileId: a.profileId,
149
+ limit: a.limit,
150
+ });
151
+ case 'ziprin_find_symbol':
152
+ return api.findSymbol(ROOT, a.name, a.paths || a.searchPaths || []);
153
+ case 'ziprin_unfold_symbol':
154
+ return api.unfoldSymbol(ROOT, a.path, a.name);
155
+ case 'ziprin_dependency_graph':
156
+ return api.dependencyGraph(ROOT, a.seeds || []);
157
+ case 'ziprin_compress_context':
158
+ return api.compressContext(ROOT, a.paths || [], a.maxChars || 1800);
159
+ case 'ziprin_validate_context': {
160
+ const task = api.analyzeTask(a.prompt || '', a.profileId);
161
+ return api.validateContext({
162
+ taskTokens: task.tokens || [],
163
+ selected: a.selected || [],
164
+ restored: a.restored || [],
165
+ scores: (a.selected || []).map((p, i) => ({ path: p, final_score: 50 - i })),
166
+ budget: a.budget,
167
+ usedTokens: a.usedTokens,
168
+ symbols: task.symbols || [],
169
+ pathHints: task.pathHints || [],
170
+ domain: task.domain,
171
+ });
172
+ }
173
+ case 'ziprin_context_memory':
174
+ return api.contextMemory(ROOT, a.action || 'read', {
175
+ filesIncluded: a.filesIncluded,
176
+ taskClassification: a.taskClassification,
177
+ });
178
+ case 'ziprin_health':
179
+ return api.healthInfo(ROOT);
180
+ default:
181
+ throw new Error(`Unknown tool: ${name}`);
182
+ }
183
+ }
184
+
185
+ function createServer() {
186
+ const server = new McpServer({
187
+ name: 'ziprin-context',
188
+ version: VERSION,
189
+ });
190
+
191
+ const wrap = (toolName) => async (args) => {
192
+ try {
193
+ return textResult(handleTool(toolName, args || {}));
194
+ } catch (err) {
195
+ return {
196
+ isError: true,
197
+ content: [{ type: 'text', text: err.message || String(err) }],
198
+ };
199
+ }
200
+ };
201
+
202
+ server.registerTool(
203
+ 'ziprin_optimize_context',
204
+ {
205
+ title: 'Ziprin Optimize Context',
206
+ description: TOOLS[0].description,
207
+ inputSchema: {
208
+ prompt: z.string().describe('User request / task text'),
209
+ profileId: z.string().optional(),
210
+ force: z.boolean().optional(),
211
+ sessionId: z.string().optional(),
212
+ },
213
+ },
214
+ wrap('ziprin_optimize_context')
215
+ );
216
+
217
+ server.registerTool(
218
+ 'ziprin_analyze_task',
219
+ {
220
+ title: 'Ziprin Analyze Task',
221
+ description: TOOLS[1].description,
222
+ inputSchema: {
223
+ prompt: z.string(),
224
+ profileId: z.string().optional(),
225
+ },
226
+ },
227
+ wrap('ziprin_analyze_task')
228
+ );
229
+
230
+ server.registerTool(
231
+ 'ziprin_rank_context',
232
+ {
233
+ title: 'Ziprin Rank Context',
234
+ description: TOOLS[2].description,
235
+ inputSchema: {
236
+ prompt: z.string(),
237
+ profileId: z.string().optional(),
238
+ limit: z.number().optional(),
239
+ },
240
+ },
241
+ wrap('ziprin_rank_context')
242
+ );
243
+
244
+ server.registerTool(
245
+ 'ziprin_find_symbol',
246
+ {
247
+ title: 'Ziprin Find Symbol',
248
+ description: TOOLS[3].description,
249
+ inputSchema: {
250
+ name: z.string(),
251
+ paths: z.array(z.string()).optional(),
252
+ },
253
+ },
254
+ wrap('ziprin_find_symbol')
255
+ );
256
+
257
+ server.registerTool(
258
+ 'ziprin_unfold_symbol',
259
+ {
260
+ title: 'Ziprin Unfold Symbol',
261
+ description: TOOLS[4].description,
262
+ inputSchema: {
263
+ path: z.string(),
264
+ name: z.string().optional(),
265
+ },
266
+ },
267
+ wrap('ziprin_unfold_symbol')
268
+ );
269
+
270
+ server.registerTool(
271
+ 'ziprin_validate_context',
272
+ {
273
+ title: 'Ziprin Validate Context',
274
+ description: TOOLS[5].description,
275
+ inputSchema: {
276
+ prompt: z.string().optional(),
277
+ selected: z.array(z.string()).optional(),
278
+ restored: z.array(z.string()).optional(),
279
+ budget: z.number().optional(),
280
+ usedTokens: z.number().optional(),
281
+ },
282
+ },
283
+ wrap('ziprin_validate_context')
284
+ );
285
+
286
+ server.registerTool(
287
+ 'ziprin_context_memory',
288
+ {
289
+ title: 'Ziprin Context Memory',
290
+ description: TOOLS[6].description,
291
+ inputSchema: {
292
+ action: z.enum(['read', 'learn', 'clear']).optional(),
293
+ filesIncluded: z.array(z.string()).optional(),
294
+ taskClassification: z.string().optional(),
295
+ },
296
+ },
297
+ wrap('ziprin_context_memory')
298
+ );
299
+
300
+ server.registerTool(
301
+ 'ziprin_health',
302
+ {
303
+ title: 'Ziprin Health',
304
+ description: TOOLS[7].description,
305
+ inputSchema: {},
306
+ },
307
+ wrap('ziprin_health')
308
+ );
309
+
310
+ return server;
311
+ }
312
+
313
+ async function main() {
314
+ console.error(`[ziprin-context-mcp] v8 project=${ROOT} sdk=official`);
315
+ publishEvent(ROOT, {
316
+ service: 'ziprin-context',
317
+ type: EVENT_TYPES.server_started,
318
+ status: 'starting',
319
+ health: 'starting',
320
+ });
321
+
322
+ const server = createServer();
323
+ for (const t of TOOLS) {
324
+ publishEvent(ROOT, {
325
+ service: 'ziprin-context',
326
+ type: EVENT_TYPES.tool_registered,
327
+ status: 'starting',
328
+ health: 'starting',
329
+ toolsCount: TOOLS.length,
330
+ tool: t.name,
331
+ });
332
+ }
333
+
334
+ const transport = new StdioServerTransport();
335
+ try {
336
+ await server.connect(transport);
337
+ publishEvent(ROOT, {
338
+ service: 'ziprin-context',
339
+ type: EVENT_TYPES.server_connected,
340
+ status: 'connected',
341
+ health: 'healthy',
342
+ toolsCount: TOOLS.length,
343
+ tools: TOOLS.map((t) => t.name),
344
+ });
345
+ } catch (err) {
346
+ publishEvent(ROOT, {
347
+ service: 'ziprin-context',
348
+ type: EVENT_TYPES.server_failed,
349
+ status: 'failed',
350
+ health: 'failed',
351
+ error: err.message,
352
+ });
353
+ throw err;
354
+ }
355
+ }
356
+
357
+ if (require.main === module) {
358
+ main().catch((err) => {
359
+ console.error('[ziprin-context-mcp] fatal', err);
360
+ process.exit(1);
361
+ });
362
+ }
363
+
364
+ module.exports = {
365
+ TOOLS,
366
+ handleTool,
367
+ parseArgs,
368
+ createServer,
369
+ setProjectRoot(root) {
370
+ ROOT = path.resolve(root);
371
+ },
372
+ };
package/src/mmr.js ADDED
@@ -0,0 +1,57 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Maximal Marginal Relevance — folder diversity after lexical/graph rank.
5
+ * Max-normalize (not min-max) so low scores stay usable.
6
+ */
7
+
8
+ function folderOf(p) {
9
+ const s = String(p || '').replace(/\\/g, '/');
10
+ const i = s.lastIndexOf('/');
11
+ return i === -1 ? '' : s.slice(0, i);
12
+ }
13
+
14
+ function pathSim(a, b) {
15
+ const da = String(a || '').replace(/\\/g, '/').split('/').slice(0, -1);
16
+ const db = String(b || '').replace(/\\/g, '/').split('/').slice(0, -1);
17
+ let i = 0;
18
+ while (i < da.length && i < db.length && da[i] === db[i]) i++;
19
+ const denom = Math.max(da.length, db.length, 1);
20
+ return i / denom;
21
+ }
22
+
23
+ function mmrSelect(ranked, opts = {}) {
24
+ const k = opts.k || 20;
25
+ const lambda = opts.lambda == null ? 0.72 : opts.lambda;
26
+ const rest = (ranked || []).map((x) => (typeof x === 'string' ? { path: x, score: 1 } : { ...x }));
27
+ if (!rest.length) return [];
28
+ const max = Math.max(...rest.map((x) => Number(x.score) || 0), 1e-9);
29
+
30
+ const selected = [];
31
+ const folderCount = new Map();
32
+ while (selected.length < k && rest.length) {
33
+ let bestI = 0;
34
+ let best = -Infinity;
35
+ for (let i = 0; i < rest.length; i++) {
36
+ const cand = rest[i];
37
+ const rel = (Number(cand.score) || 0) / max;
38
+ let maxSim = 0;
39
+ for (const s of selected) maxSim = Math.max(maxSim, pathSim(cand.path, s.path));
40
+ const folder = folderOf(cand.path);
41
+ const same = folderCount.get(folder) || 0;
42
+ const folderPenalty = same >= 3 ? 0.25 * same : 0;
43
+ const mmr = lambda * rel - (1 - lambda) * maxSim - folderPenalty;
44
+ if (mmr > best) {
45
+ best = mmr;
46
+ bestI = i;
47
+ }
48
+ }
49
+ const pick = rest.splice(bestI, 1)[0];
50
+ selected.push({ ...pick, mmr: best });
51
+ const folder = folderOf(pick.path);
52
+ folderCount.set(folder, (folderCount.get(folder) || 0) + 1);
53
+ }
54
+ return selected;
55
+ }
56
+
57
+ module.exports = { mmrSelect, pathSim, folderOf };
@@ -0,0 +1,176 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Personalized PageRank on the import graph (Aider-style).
5
+ * Personalization: query seeds + dirty + open editor files.
6
+ * Graph cached by path:mtime fingerprint.
7
+ */
8
+
9
+ const fs = require('fs');
10
+ const path = require('path');
11
+ const crypto = require('crypto');
12
+ const { buildDependencyBoost, extractImports, resolveImport } = require('./dependency-graph');
13
+
14
+ const CACHE_REL = '.ziprin-context/graph-cache.json';
15
+ const cacheMem = new Map();
16
+
17
+ function fingerprint(root, files) {
18
+ const parts = [];
19
+ for (const rel of files.slice().sort()) {
20
+ let m = 0;
21
+ try {
22
+ m = Math.round(fs.statSync(path.join(root, rel)).mtimeMs);
23
+ } catch {
24
+ m = 0;
25
+ }
26
+ parts.push(`${rel}:${m}`);
27
+ }
28
+ return crypto.createHash('sha1').update(parts.join('|')).digest('hex').slice(0, 16);
29
+ }
30
+
31
+ function loadCache(root) {
32
+ if (cacheMem.has(root)) return cacheMem.get(root);
33
+ const abs = path.join(root, CACHE_REL);
34
+ if (!fs.existsSync(abs)) return null;
35
+ try {
36
+ const j = JSON.parse(fs.readFileSync(abs, 'utf8'));
37
+ cacheMem.set(root, j);
38
+ return j;
39
+ } catch {
40
+ return null;
41
+ }
42
+ }
43
+
44
+ function saveCache(root, data) {
45
+ cacheMem.set(root, data);
46
+ try {
47
+ const abs = path.join(root, CACHE_REL);
48
+ fs.mkdirSync(path.dirname(abs), { recursive: true });
49
+ fs.writeFileSync(abs, JSON.stringify(data), 'utf8');
50
+ } catch {
51
+ /* ignore */
52
+ }
53
+ }
54
+
55
+ function buildGraph(root, seedPaths, maxFiles = 80) {
56
+ const fp = fingerprint(root, seedPaths);
57
+ const cached = loadCache(root);
58
+ if (cached && cached.fingerprint === fp && Array.isArray(cached.edges)) {
59
+ return { ...cached, cached: true };
60
+ }
61
+
62
+ const nodes = new Set(seedPaths);
63
+ const edges = [];
64
+ const { boost, edges: depEdges } = buildDependencyBoost(root, seedPaths, maxFiles);
65
+ for (const e of depEdges) {
66
+ edges.push({ from: e.from, to: e.to });
67
+ nodes.add(e.from);
68
+ nodes.add(e.to);
69
+ }
70
+ for (const b of boost) nodes.add(b);
71
+
72
+ // one extra hop for package roots already in boost
73
+ for (const rel of [...nodes].slice(0, maxFiles)) {
74
+ const abs = path.join(root, rel);
75
+ if (!fs.existsSync(abs) || !fs.statSync(abs).isFile()) continue;
76
+ let text = '';
77
+ try {
78
+ text = fs.readFileSync(abs, 'utf8').slice(0, 80_000);
79
+ } catch {
80
+ continue;
81
+ }
82
+ for (const spec of extractImports(text).slice(0, 16)) {
83
+ const resolved = resolveImport(rel, spec, root);
84
+ if (!resolved) continue;
85
+ edges.push({ from: rel, to: resolved });
86
+ nodes.add(resolved);
87
+ }
88
+ }
89
+
90
+ const data = {
91
+ fingerprint: fp,
92
+ builtAt: new Date().toISOString(),
93
+ nodes: [...nodes],
94
+ edges,
95
+ cached: false,
96
+ };
97
+ saveCache(root, data);
98
+ return data;
99
+ }
100
+
101
+ function personalizedPageRank(nodes, edges, seeds, opts = {}) {
102
+ const damping = opts.damping == null ? 0.85 : opts.damping;
103
+ const iters = opts.iters || 20;
104
+ const list = [...new Set(nodes.filter(Boolean))];
105
+ const n = list.length;
106
+ if (!n) return [];
107
+ const index = new Map(list.map((p, i) => [p, i]));
108
+ const out = Array.from({ length: n }, () => []);
109
+ for (const e of edges || []) {
110
+ const i = index.get(e.from);
111
+ const j = index.get(e.to);
112
+ if (i == null || j == null || i === j) continue;
113
+ out[i].push(j);
114
+ }
115
+
116
+ const pers = new Array(n).fill(0);
117
+ let mass = 0;
118
+ for (const s of seeds || []) {
119
+ const i = index.get(s);
120
+ if (i == null) continue;
121
+ pers[i] += 1;
122
+ mass += 1;
123
+ }
124
+ if (!mass) {
125
+ for (let i = 0; i < n; i++) pers[i] = 1 / n;
126
+ } else {
127
+ for (let i = 0; i < n; i++) pers[i] /= mass;
128
+ }
129
+
130
+ let r = pers.slice();
131
+ for (let t = 0; t < iters; t++) {
132
+ const next = new Array(n).fill(0);
133
+ for (let i = 0; i < n; i++) {
134
+ next[i] += (1 - damping) * pers[i];
135
+ }
136
+ for (let i = 0; i < n; i++) {
137
+ const dests = out[i];
138
+ if (!dests.length) {
139
+ const share = damping * r[i] / n;
140
+ for (let j = 0; j < n; j++) next[j] += share;
141
+ } else {
142
+ const share = damping * r[i] / dests.length;
143
+ for (const j of dests) next[j] += share;
144
+ }
145
+ }
146
+ r = next;
147
+ }
148
+
149
+ const ranked = list
150
+ .map((p, i) => ({ path: p, score: r[i] }))
151
+ .sort((a, b) => b.score - a.score);
152
+
153
+ if (seeds && seeds.length) {
154
+ const seedSet = new Set(seeds);
155
+ const bonus = 0.2;
156
+ for (const item of ranked) {
157
+ if (seedSet.has(item.path)) item.score += bonus;
158
+ }
159
+ ranked.sort((a, b) => b.score - a.score);
160
+ }
161
+ return ranked;
162
+ }
163
+
164
+ function rankGraph(root, seedPaths, personalize, opts = {}) {
165
+ const graph = buildGraph(root, seedPaths, opts.maxFiles || 80);
166
+ const ranked = personalizedPageRank(graph.nodes, graph.edges, personalize || seedPaths, opts);
167
+ return { ranked, graph };
168
+ }
169
+
170
+ module.exports = {
171
+ fingerprint,
172
+ buildGraph,
173
+ personalizedPageRank,
174
+ rankGraph,
175
+ CACHE_REL,
176
+ };
@@ -0,0 +1,74 @@
1
+ 'use strict';
2
+
3
+ const PROFILES = {
4
+ minimal: {
5
+ id: 'minimal',
6
+ label: 'Minimal',
7
+ description: 'فقط stub ناظر + ignore سنگین. کمترین توکن همیشه-لود.',
8
+ charsPerToken: 4,
9
+ maxAlwaysLoadTokens: 400,
10
+ maxOnDemandTokens: 8000,
11
+ stripOtherMdc: true,
12
+ preferSerena: true,
13
+ preferToolsFirst: true,
14
+ allowAgentsInChat: false,
15
+ allowSkillsInChat: false,
16
+ allowFullIndex: false,
17
+ confidenceFloor: 'PROVEN',
18
+ },
19
+ standard: {
20
+ id: 'standard',
21
+ label: 'Standard',
22
+ description: 'تعادل کیفیت و توکن. stub ناظر + خلاصه ایندکس + Serena.',
23
+ charsPerToken: 4,
24
+ maxAlwaysLoadTokens: 600,
25
+ maxOnDemandTokens: 20000,
26
+ stripOtherMdc: true,
27
+ preferSerena: true,
28
+ preferToolsFirst: true,
29
+ allowAgentsInChat: false,
30
+ allowSkillsInChat: true,
31
+ allowFullIndex: false,
32
+ confidenceFloor: 'PROVEN',
33
+ },
34
+ strict: {
35
+ id: 'strict',
36
+ label: 'Strict',
37
+ description: 'حذف تقریباً همه always-load؛ فقط path مرتبط با تسک.',
38
+ charsPerToken: 4,
39
+ maxAlwaysLoadTokens: 300,
40
+ maxOnDemandTokens: 12000,
41
+ stripOtherMdc: true,
42
+ preferSerena: true,
43
+ preferToolsFirst: true,
44
+ allowAgentsInChat: false,
45
+ allowSkillsInChat: false,
46
+ allowFullIndex: false,
47
+ confidenceFloor: 'PROVEN',
48
+ },
49
+ maximum: {
50
+ id: 'maximum',
51
+ label: 'Maximum quality',
52
+ description: 'بیشتر context برای تسک‌های سخت؛ هنوز از dump کل ریپو جلوگیری می‌کند.',
53
+ charsPerToken: 4,
54
+ maxAlwaysLoadTokens: 1200,
55
+ maxOnDemandTokens: 40000,
56
+ stripOtherMdc: false,
57
+ preferSerena: true,
58
+ preferToolsFirst: true,
59
+ allowAgentsInChat: true,
60
+ allowSkillsInChat: true,
61
+ allowFullIndex: true,
62
+ confidenceFloor: 'REVIEW',
63
+ },
64
+ };
65
+
66
+ function getProfile(id) {
67
+ return PROFILES[id] || PROFILES.standard;
68
+ }
69
+
70
+ function listProfiles() {
71
+ return Object.values(PROFILES);
72
+ }
73
+
74
+ module.exports = { PROFILES, getProfile, listProfiles };