sinapse-ai 1.20.0 → 1.20.1
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/.sinapse-ai/core/atlas/render-html.js +10 -8
- package/.sinapse-ai/core/registry/service-registry.json +2 -32
- package/.sinapse-ai/data/entity-registry.yaml +440 -195
- package/.sinapse-ai/development/agents/analyst.md +18 -34
- package/.sinapse-ai/development/agents/architect.md +18 -34
- package/.sinapse-ai/development/agents/data-engineer.md +15 -28
- package/.sinapse-ai/development/agents/developer.md +0 -6
- package/.sinapse-ai/development/agents/devops.md +15 -28
- package/.sinapse-ai/development/agents/product-lead.md +18 -34
- package/.sinapse-ai/development/agents/project-lead.md +18 -34
- package/.sinapse-ai/development/agents/quality-gate.md +18 -34
- package/.sinapse-ai/development/agents/squad-creator.md +15 -27
- package/.sinapse-ai/development/agents/ux-design-expert.md +15 -27
- package/.sinapse-ai/development/scripts/populate-entity-registry.js +3 -0
- package/.sinapse-ai/development/tasks/execute-epic-plan.md +0 -4
- package/.sinapse-ai/development/workflows/epic-orchestration.yaml +0 -1
- package/.sinapse-ai/git-hooks/lib/staged-secret-scan.js +3 -1
- package/.sinapse-ai/install-manifest.yaml +40 -72
- package/.sinapse-ai/product/templates/personalized-agent-template.md +3 -5
- package/.sinapse-ai/workflow-intelligence/index.js +3 -21
- package/AGENTS.md +2 -2
- package/CHANGELOG.md +38 -19
- package/bin/commands/install.js +5 -4
- package/bin/lib/command-generator.js +4 -4
- package/bin/lib/fs-utils.js +18 -0
- package/bin/lib/setup-statusline.js +3 -1
- package/bin/modules/chrome-brain-installer.js +5 -3
- package/bin/modules/mcp-installer.js +3 -6
- package/bin/postinstall.js +2 -1
- package/bin/sinapse-init.js +8 -9
- package/bin/sinapse.js +3 -1
- package/bin/utils/staged-secret-scan.js +3 -1
- package/docs/framework/atlas/OPERATING-ATLAS.md +1 -1
- package/docs/framework/atlas/atlas-data.json +22 -22
- package/docs/framework/atlas/atlas.html +12 -10
- package/docs/framework/source-tree.md +1 -2
- package/package.json +1 -1
- package/packages/installer/src/config/configure-environment.js +8 -6
- package/packages/installer/src/installer/brownfield-upgrader.js +5 -3
- package/packages/installer/src/installer/file-hasher.js +29 -21
- package/packages/installer/src/installer/manifest-signature.js +61 -49
- package/packages/installer/src/installer/post-install-validator.js +24 -17
- package/packages/installer/src/merger/parsers/markdown-section-parser.js +3 -2
- package/packages/installer/src/updater/index.js +7 -1
- package/packages/installer/src/utils/atomic-write.js +35 -0
- package/packages/installer/src/wizard/index.js +5 -3
- package/packages/sinapse-install/src/capabilities/chrome-brain.js +5 -3
- package/packages/sinapse-install/src/installer.js +3 -1
- package/packages/sinapse-install/src/utils/atomic-write.js +67 -0
- package/scripts/apply-persona-disclaimer.js +2 -1
- package/scripts/sinapse-patch.js +2 -1
- package/scripts/sync-squad-yaml-components.js +2 -1
- package/scripts/validate-agents-md.js +2 -1
- package/scripts/validate-article-xi.js +10 -8
- package/scripts/validate-no-external-refs.js +14 -8
- package/squads/claude-code-mastery/agents/claude-mastery-chief.md +3 -8
- package/squads/claude-code-mastery/agents/config-engineer.md +5 -12
- package/squads/claude-code-mastery/agents/hooks-architect.md +5 -11
- package/squads/claude-code-mastery/agents/mcp-integrator.md +5 -12
- package/squads/claude-code-mastery/agents/project-integrator.md +5 -12
- package/squads/claude-code-mastery/agents/roadmap-sentinel.md +5 -12
- package/squads/claude-code-mastery/agents/skill-craftsman.md +5 -11
- package/squads/claude-code-mastery/agents/swarm-orqx.md +15 -28
- package/.sinapse-ai/core/execution/context-injector.js +0 -539
- package/.sinapse-ai/core/execution/parallel-executor.js +0 -301
- package/.sinapse-ai/core/execution/parallel-monitor.js +0 -432
- package/.sinapse-ai/core/execution/semantic-merge-engine.js +0 -1750
- package/.sinapse-ai/core/execution/wave-executor.js +0 -403
- package/.sinapse-ai/development/tasks/waves.md +0 -205
- package/.sinapse-ai/workflow-intelligence/engine/wave-analyzer.js +0 -692
|
@@ -1,539 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Context Injector
|
|
3
|
-
* Story 10.3 - Parallel Agent Execution
|
|
4
|
-
*
|
|
5
|
-
* @abandoned-path — ver docs/epics/epic-ultra-optimization/decisions/DEC-03 (2026-07-02)
|
|
6
|
-
*
|
|
7
|
-
* Injects rich context into subagent prompts including:
|
|
8
|
-
* - Project context
|
|
9
|
-
* - Relevant files
|
|
10
|
-
* - Memory/patterns
|
|
11
|
-
* - Gotchas
|
|
12
|
-
* - Recent decisions
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
const fs = require('fs');
|
|
16
|
-
const path = require('path');
|
|
17
|
-
|
|
18
|
-
// Import dependencies with fallbacks
|
|
19
|
-
let MemoryQuery, GotchasMemory, SessionMemory;
|
|
20
|
-
try {
|
|
21
|
-
MemoryQuery = require('../memory/memory-query');
|
|
22
|
-
} catch {
|
|
23
|
-
MemoryQuery = null;
|
|
24
|
-
}
|
|
25
|
-
try {
|
|
26
|
-
GotchasMemory = require('../memory/gotchas-memory');
|
|
27
|
-
} catch {
|
|
28
|
-
GotchasMemory = null;
|
|
29
|
-
}
|
|
30
|
-
try {
|
|
31
|
-
SessionMemory = require('../memory/session-memory');
|
|
32
|
-
} catch {
|
|
33
|
-
SessionMemory = null;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
class ContextInjector {
|
|
37
|
-
constructor(config = {}) {
|
|
38
|
-
// Token budget for context (prevents overwhelming the LLM)
|
|
39
|
-
this.tokenBudget = config.tokenBudget || 4000;
|
|
40
|
-
|
|
41
|
-
// Approximate characters per token
|
|
42
|
-
this.charsPerToken = config.charsPerToken || 4;
|
|
43
|
-
|
|
44
|
-
// Cache TTL (5 minutes)
|
|
45
|
-
this.cacheTTL = config.cacheTTL || 5 * 60 * 1000;
|
|
46
|
-
|
|
47
|
-
// Context cache
|
|
48
|
-
this.cache = new Map();
|
|
49
|
-
|
|
50
|
-
// Dependencies
|
|
51
|
-
this.memoryQuery = config.memoryQuery || (MemoryQuery ? new MemoryQuery() : null);
|
|
52
|
-
this.gotchasMemory = config.gotchasMemory || (GotchasMemory ? new GotchasMemory() : null);
|
|
53
|
-
this.sessionMemory = config.sessionMemory || (SessionMemory ? new SessionMemory() : null);
|
|
54
|
-
|
|
55
|
-
// Root path
|
|
56
|
-
this.rootPath = config.rootPath || process.cwd();
|
|
57
|
-
|
|
58
|
-
// Metrics
|
|
59
|
-
this.metrics = {
|
|
60
|
-
injections: 0,
|
|
61
|
-
cacheHits: 0,
|
|
62
|
-
avgContextSize: 0,
|
|
63
|
-
avgInjectionTime: 0,
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Inject context for a task
|
|
69
|
-
* @param {Object} task - Task to inject context for
|
|
70
|
-
* @param {Object} baseContext - Base context
|
|
71
|
-
* @returns {Promise<string>} - Formatted context string
|
|
72
|
-
*/
|
|
73
|
-
async inject(task, _baseContext = {}) {
|
|
74
|
-
const startTime = Date.now();
|
|
75
|
-
const cacheKey = this.getCacheKey(task);
|
|
76
|
-
|
|
77
|
-
// Build injection payload
|
|
78
|
-
const injection = {
|
|
79
|
-
// 1. Task description (required)
|
|
80
|
-
task: {
|
|
81
|
-
id: task.id,
|
|
82
|
-
description: task.description,
|
|
83
|
-
acceptanceCriteria: task.acceptanceCriteria || [],
|
|
84
|
-
verification: task.verification,
|
|
85
|
-
},
|
|
86
|
-
|
|
87
|
-
// 2. Project context (from cache or fresh)
|
|
88
|
-
project: await this.getProjectContext(cacheKey),
|
|
89
|
-
|
|
90
|
-
// 3. Relevant files (task-specific)
|
|
91
|
-
files: await this.getRelevantFiles(task),
|
|
92
|
-
|
|
93
|
-
// 4. Memory context
|
|
94
|
-
memory: await this.getRelevantMemory(task),
|
|
95
|
-
|
|
96
|
-
// 5. Gotchas
|
|
97
|
-
gotchas: await this.getRelevantGotchas(task),
|
|
98
|
-
|
|
99
|
-
// 6. Recent decisions
|
|
100
|
-
decisions: await this.getRecentDecisions(),
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
// Format for LLM consumption
|
|
104
|
-
const formatted = this.formatForLLM(injection);
|
|
105
|
-
|
|
106
|
-
// Ensure within token budget
|
|
107
|
-
const trimmed = this.trimToTokenBudget(formatted, this.tokenBudget);
|
|
108
|
-
|
|
109
|
-
// Update metrics
|
|
110
|
-
const injectionTime = Date.now() - startTime;
|
|
111
|
-
this.updateMetrics(trimmed, injectionTime);
|
|
112
|
-
|
|
113
|
-
return trimmed;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Get cache key for a task
|
|
118
|
-
* @param {Object} task - Task
|
|
119
|
-
* @returns {string} - Cache key
|
|
120
|
-
*/
|
|
121
|
-
getCacheKey(task) {
|
|
122
|
-
return `${task.type || 'default'}-${task.service || 'core'}`;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Get cached value if valid
|
|
127
|
-
* @param {string} key - Cache key
|
|
128
|
-
* @returns {any|null} - Cached value or null
|
|
129
|
-
*/
|
|
130
|
-
getCached(key) {
|
|
131
|
-
const cached = this.cache.get(key);
|
|
132
|
-
if (!cached) return null;
|
|
133
|
-
|
|
134
|
-
if (Date.now() - cached.timestamp > this.cacheTTL) {
|
|
135
|
-
this.cache.delete(key);
|
|
136
|
-
return null;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
this.metrics.cacheHits++;
|
|
140
|
-
return cached.value;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Set cache value
|
|
145
|
-
* @param {string} key - Cache key
|
|
146
|
-
* @param {any} value - Value to cache
|
|
147
|
-
*/
|
|
148
|
-
setCache(key, value) {
|
|
149
|
-
this.cache.set(key, {
|
|
150
|
-
value,
|
|
151
|
-
timestamp: Date.now(),
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Get project context
|
|
157
|
-
* @param {string} cacheKey - Cache key for type-specific context
|
|
158
|
-
* @returns {Promise<Object>} - Project context
|
|
159
|
-
*/
|
|
160
|
-
async getProjectContext(cacheKey) {
|
|
161
|
-
// Check cache
|
|
162
|
-
const cached = this.getCached(`project-${cacheKey}`);
|
|
163
|
-
if (cached) return cached;
|
|
164
|
-
|
|
165
|
-
const context = {
|
|
166
|
-
name: 'unknown',
|
|
167
|
-
framework: null,
|
|
168
|
-
patterns: [],
|
|
169
|
-
conventions: [],
|
|
170
|
-
};
|
|
171
|
-
|
|
172
|
-
try {
|
|
173
|
-
// Try to read codebase map
|
|
174
|
-
const codebaseMapPath = path.join(this.rootPath, '.sinapse', 'codebase-map.json');
|
|
175
|
-
if (fs.existsSync(codebaseMapPath)) {
|
|
176
|
-
const codebaseMap = JSON.parse(fs.readFileSync(codebaseMapPath, 'utf8'));
|
|
177
|
-
context.name = codebaseMap.name || codebaseMap.projectName || 'project';
|
|
178
|
-
context.framework = codebaseMap.framework || codebaseMap.mainFramework;
|
|
179
|
-
context.patterns = codebaseMap.patterns || [];
|
|
180
|
-
context.entryPoints = codebaseMap.entryPoints || [];
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// Try to read package.json
|
|
184
|
-
const packagePath = path.join(this.rootPath, 'package.json');
|
|
185
|
-
if (fs.existsSync(packagePath)) {
|
|
186
|
-
const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
|
|
187
|
-
context.name = pkg.name || context.name;
|
|
188
|
-
context.version = pkg.version;
|
|
189
|
-
context.dependencies = Object.keys(pkg.dependencies || {}).slice(0, 10);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// Try to detect conventions
|
|
193
|
-
context.conventions = await this.detectConventions();
|
|
194
|
-
} catch (_error) {
|
|
195
|
-
// Silently fail - context is optional enhancement
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// Cache the result
|
|
199
|
-
this.setCache(`project-${cacheKey}`, context);
|
|
200
|
-
|
|
201
|
-
return context;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* Detect project conventions
|
|
206
|
-
* @returns {Promise<Array>} - Detected conventions
|
|
207
|
-
*/
|
|
208
|
-
async detectConventions() {
|
|
209
|
-
const conventions = [];
|
|
210
|
-
|
|
211
|
-
try {
|
|
212
|
-
// Check for TypeScript
|
|
213
|
-
if (fs.existsSync(path.join(this.rootPath, 'tsconfig.json'))) {
|
|
214
|
-
conventions.push('TypeScript project');
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// Check for ESLint
|
|
218
|
-
const eslintFiles = ['.eslintrc.js', '.eslintrc.json', '.eslintrc.yaml'];
|
|
219
|
-
if (eslintFiles.some((f) => fs.existsSync(path.join(this.rootPath, f)))) {
|
|
220
|
-
conventions.push('Uses ESLint');
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// Check for Prettier
|
|
224
|
-
if (fs.existsSync(path.join(this.rootPath, '.prettierrc'))) {
|
|
225
|
-
conventions.push('Uses Prettier');
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// Check for tests directory
|
|
229
|
-
if (fs.existsSync(path.join(this.rootPath, 'tests'))) {
|
|
230
|
-
conventions.push('Tests in /tests directory');
|
|
231
|
-
} else if (fs.existsSync(path.join(this.rootPath, '__tests__'))) {
|
|
232
|
-
conventions.push('Tests in /__tests__ directory');
|
|
233
|
-
}
|
|
234
|
-
} catch (_error) {
|
|
235
|
-
// Ignore errors
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
return conventions;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
* Get relevant files for a task
|
|
243
|
-
* @param {Object} task - Task
|
|
244
|
-
* @returns {Promise<Array>} - Relevant files
|
|
245
|
-
*/
|
|
246
|
-
async getRelevantFiles(task) {
|
|
247
|
-
const files = [];
|
|
248
|
-
|
|
249
|
-
// Add explicitly specified files
|
|
250
|
-
if (task.files) {
|
|
251
|
-
for (const file of task.files) {
|
|
252
|
-
files.push({
|
|
253
|
-
path: file,
|
|
254
|
-
purpose: 'Specified in task',
|
|
255
|
-
exists: fs.existsSync(path.join(this.rootPath, file)),
|
|
256
|
-
});
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// Infer files from task description
|
|
261
|
-
const inferredFiles = this.inferFilesFromDescription(task.description);
|
|
262
|
-
for (const file of inferredFiles) {
|
|
263
|
-
if (!files.some((f) => f.path === file)) {
|
|
264
|
-
files.push({
|
|
265
|
-
path: file,
|
|
266
|
-
purpose: 'Inferred from description',
|
|
267
|
-
exists: fs.existsSync(path.join(this.rootPath, file)),
|
|
268
|
-
});
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
return files.slice(0, 10); // Limit to 10 files
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
/**
|
|
276
|
-
* Infer files from task description
|
|
277
|
-
* @param {string} description - Task description
|
|
278
|
-
* @returns {Array<string>} - Inferred file paths
|
|
279
|
-
*/
|
|
280
|
-
inferFilesFromDescription(description) {
|
|
281
|
-
if (!description) return [];
|
|
282
|
-
|
|
283
|
-
const files = [];
|
|
284
|
-
|
|
285
|
-
// Match file paths in backticks
|
|
286
|
-
const backtickMatches = description.match(/`([^`]+\.[a-zA-Z]+)`/g) || [];
|
|
287
|
-
for (const match of backtickMatches) {
|
|
288
|
-
files.push(match.replace(/`/g, ''));
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// Match quoted paths
|
|
292
|
-
const quotedMatches = description.match(/['"]([^'"]+\.[a-zA-Z]+)['"]/g) || [];
|
|
293
|
-
for (const match of quotedMatches) {
|
|
294
|
-
files.push(match.replace(/['"]/g, ''));
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
return [...new Set(files)];
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
/**
|
|
301
|
-
* Get relevant memory for a task
|
|
302
|
-
* @param {Object} task - Task
|
|
303
|
-
* @returns {Promise<Array>} - Relevant memory entries
|
|
304
|
-
*/
|
|
305
|
-
async getRelevantMemory(task) {
|
|
306
|
-
if (!this.memoryQuery) return [];
|
|
307
|
-
|
|
308
|
-
try {
|
|
309
|
-
const query = task.description || task.id;
|
|
310
|
-
const results = await this.memoryQuery.query(query, { limit: 5 });
|
|
311
|
-
return results.map((r) => ({
|
|
312
|
-
type: r.type,
|
|
313
|
-
content: r.content || r.summary,
|
|
314
|
-
relevance: r.score || r.relevance,
|
|
315
|
-
}));
|
|
316
|
-
} catch (_error) {
|
|
317
|
-
return [];
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
/**
|
|
322
|
-
* Get relevant gotchas for a task
|
|
323
|
-
* @param {Object} task - Task
|
|
324
|
-
* @returns {Promise<Array>} - Relevant gotchas
|
|
325
|
-
*/
|
|
326
|
-
async getRelevantGotchas(task) {
|
|
327
|
-
if (!this.gotchasMemory) return [];
|
|
328
|
-
|
|
329
|
-
try {
|
|
330
|
-
return await this.gotchasMemory.getContextForTask(task.description || task.id);
|
|
331
|
-
} catch (_error) {
|
|
332
|
-
return [];
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
/**
|
|
337
|
-
* Get recent decisions from session memory
|
|
338
|
-
* @returns {Promise<Array>} - Recent decisions
|
|
339
|
-
*/
|
|
340
|
-
async getRecentDecisions() {
|
|
341
|
-
if (!this.sessionMemory) return [];
|
|
342
|
-
|
|
343
|
-
try {
|
|
344
|
-
const decisions = await this.sessionMemory.getDecisions({ limit: 5 });
|
|
345
|
-
return decisions.map((d) => ({
|
|
346
|
-
decision: d.decision || d.content,
|
|
347
|
-
reason: d.reason,
|
|
348
|
-
timestamp: d.timestamp,
|
|
349
|
-
}));
|
|
350
|
-
} catch (_error) {
|
|
351
|
-
return [];
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
/**
|
|
356
|
-
* Format injection payload for LLM consumption
|
|
357
|
-
* @param {Object} injection - Injection payload
|
|
358
|
-
* @returns {string} - Formatted markdown
|
|
359
|
-
*/
|
|
360
|
-
formatForLLM(injection) {
|
|
361
|
-
let output = '';
|
|
362
|
-
|
|
363
|
-
// Task section (always included)
|
|
364
|
-
output += '## Task Context\n\n';
|
|
365
|
-
output += '### Current Task\n';
|
|
366
|
-
output += `**ID:** ${injection.task.id}\n`;
|
|
367
|
-
output += `**Description:** ${injection.task.description}\n\n`;
|
|
368
|
-
|
|
369
|
-
// Acceptance Criteria
|
|
370
|
-
if (injection.task.acceptanceCriteria && injection.task.acceptanceCriteria.length > 0) {
|
|
371
|
-
output += '### Acceptance Criteria\n';
|
|
372
|
-
const criteria = Array.isArray(injection.task.acceptanceCriteria)
|
|
373
|
-
? injection.task.acceptanceCriteria
|
|
374
|
-
: [injection.task.acceptanceCriteria];
|
|
375
|
-
criteria.forEach((ac, i) => {
|
|
376
|
-
output += `${i + 1}. ${ac}\n`;
|
|
377
|
-
});
|
|
378
|
-
output += '\n';
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
// Relevant Files
|
|
382
|
-
if (injection.files && injection.files.length > 0) {
|
|
383
|
-
output += '### Relevant Files\n';
|
|
384
|
-
injection.files.forEach((f) => {
|
|
385
|
-
const status = f.exists ? '✓' : '?';
|
|
386
|
-
output += `- [${status}] \`${f.path}\`: ${f.purpose}\n`;
|
|
387
|
-
});
|
|
388
|
-
output += '\n';
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
// Project Patterns
|
|
392
|
-
if (injection.project.patterns && injection.project.patterns.length > 0) {
|
|
393
|
-
output += '### Project Patterns\n';
|
|
394
|
-
injection.project.patterns.slice(0, 5).forEach((p) => {
|
|
395
|
-
const name = typeof p === 'string' ? p : p.name;
|
|
396
|
-
output += `- ${name}\n`;
|
|
397
|
-
});
|
|
398
|
-
output += '\n';
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
// Conventions
|
|
402
|
-
if (injection.project.conventions && injection.project.conventions.length > 0) {
|
|
403
|
-
output += '### Conventions\n';
|
|
404
|
-
injection.project.conventions.forEach((c) => {
|
|
405
|
-
output += `- ${c}\n`;
|
|
406
|
-
});
|
|
407
|
-
output += '\n';
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
// Active Gotchas
|
|
411
|
-
if (injection.gotchas && injection.gotchas.length > 0) {
|
|
412
|
-
output += '### Active Gotchas (Avoid These)\n';
|
|
413
|
-
injection.gotchas.slice(0, 5).forEach((g) => {
|
|
414
|
-
const title = g.title || g.pattern || 'Warning';
|
|
415
|
-
const workaround = g.workaround || g.description || 'Be careful';
|
|
416
|
-
output += `⚠️ **${title}**: ${workaround}\n\n`;
|
|
417
|
-
});
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
// Recent Decisions
|
|
421
|
-
if (injection.decisions && injection.decisions.length > 0) {
|
|
422
|
-
output += '### Recent Decisions\n';
|
|
423
|
-
injection.decisions.slice(0, 3).forEach((d) => {
|
|
424
|
-
output += `- ${d.decision}`;
|
|
425
|
-
if (d.reason) output += ` (${d.reason})`;
|
|
426
|
-
output += '\n';
|
|
427
|
-
});
|
|
428
|
-
output += '\n';
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
// Relevant Memory
|
|
432
|
-
if (injection.memory && injection.memory.length > 0) {
|
|
433
|
-
output += '### Relevant Context from Memory\n';
|
|
434
|
-
injection.memory.slice(0, 3).forEach((m) => {
|
|
435
|
-
output += `- [${m.type}] ${m.content}\n`;
|
|
436
|
-
});
|
|
437
|
-
output += '\n';
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
return output;
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
/**
|
|
444
|
-
* Trim content to fit token budget
|
|
445
|
-
* @param {string} content - Content to trim
|
|
446
|
-
* @param {number} tokenBudget - Maximum tokens
|
|
447
|
-
* @returns {string} - Trimmed content
|
|
448
|
-
*/
|
|
449
|
-
trimToTokenBudget(content, tokenBudget) {
|
|
450
|
-
const maxChars = tokenBudget * this.charsPerToken;
|
|
451
|
-
|
|
452
|
-
if (content.length <= maxChars) {
|
|
453
|
-
return content;
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
// Try to trim intelligently by sections
|
|
457
|
-
const sections = content.split(/(?=###)/);
|
|
458
|
-
let trimmed = '';
|
|
459
|
-
|
|
460
|
-
// Always include the task section
|
|
461
|
-
if (sections[0]) {
|
|
462
|
-
trimmed = sections[0];
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
// Add other sections until budget is reached
|
|
466
|
-
for (let i = 1; i < sections.length; i++) {
|
|
467
|
-
if (trimmed.length + sections[i].length <= maxChars) {
|
|
468
|
-
trimmed += sections[i];
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
// If still too long, hard truncate
|
|
473
|
-
if (trimmed.length > maxChars) {
|
|
474
|
-
trimmed = trimmed.substring(0, maxChars - 50) + '\n\n[Context truncated for token budget]\n';
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
return trimmed;
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
/**
|
|
481
|
-
* Update metrics
|
|
482
|
-
* @param {string} context - Generated context
|
|
483
|
-
* @param {number} time - Injection time
|
|
484
|
-
*/
|
|
485
|
-
updateMetrics(context, time) {
|
|
486
|
-
this.metrics.injections++;
|
|
487
|
-
this.metrics.avgContextSize =
|
|
488
|
-
(this.metrics.avgContextSize * (this.metrics.injections - 1) + context.length) /
|
|
489
|
-
this.metrics.injections;
|
|
490
|
-
this.metrics.avgInjectionTime =
|
|
491
|
-
(this.metrics.avgInjectionTime * (this.metrics.injections - 1) + time) /
|
|
492
|
-
this.metrics.injections;
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
/**
|
|
496
|
-
* Get metrics
|
|
497
|
-
* @returns {Object} - Current metrics
|
|
498
|
-
*/
|
|
499
|
-
getMetrics() {
|
|
500
|
-
return {
|
|
501
|
-
...this.metrics,
|
|
502
|
-
avgContextSize: Math.round(this.metrics.avgContextSize),
|
|
503
|
-
avgInjectionTime: Math.round(this.metrics.avgInjectionTime),
|
|
504
|
-
cacheSize: this.cache.size,
|
|
505
|
-
};
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
/**
|
|
509
|
-
* Clear cache
|
|
510
|
-
*/
|
|
511
|
-
clearCache() {
|
|
512
|
-
this.cache.clear();
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
/**
|
|
516
|
-
* Format status for CLI
|
|
517
|
-
* @returns {string} - Formatted status
|
|
518
|
-
*/
|
|
519
|
-
formatStatus() {
|
|
520
|
-
const metrics = this.getMetrics();
|
|
521
|
-
|
|
522
|
-
let output = '💉 Context Injector Status\n';
|
|
523
|
-
output += '━'.repeat(40) + '\n\n';
|
|
524
|
-
|
|
525
|
-
output += '**Metrics:**\n';
|
|
526
|
-
output += ` Total Injections: ${metrics.injections}\n`;
|
|
527
|
-
output += ` Cache Hits: ${metrics.cacheHits}\n`;
|
|
528
|
-
output += ` Cache Size: ${metrics.cacheSize}\n`;
|
|
529
|
-
output += ` Avg Context Size: ${metrics.avgContextSize} chars\n`;
|
|
530
|
-
output += ` Avg Injection Time: ${metrics.avgInjectionTime}ms\n`;
|
|
531
|
-
output += ` Token Budget: ${this.tokenBudget}\n`;
|
|
532
|
-
|
|
533
|
-
return output;
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
module.exports = ContextInjector;
|
|
538
|
-
module.exports.ContextInjector = ContextInjector;
|
|
539
|
-
|