contextos-agents 1.5.0 → 1.6.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/.agents/adapters/cursor/export.js +34 -37
- package/.agents/ctx.js +6 -5
- package/.agents/doctor.js +269 -0
- package/.agents/mcp/runtime.py +17 -1
- package/.agents/mcp/server.mjs +42 -29
- package/.agents/plugins.js +21 -11
- package/.agents/resolver.js +112 -1
- package/.agents/stats.js +133 -0
- package/.agents/watch.js +161 -0
- package/README.md +46 -6
- package/bin/index.js +126 -36
- package/package.json +9 -2
|
@@ -22,42 +22,36 @@ function getSkillGlobsAndFlags(skillName) {
|
|
|
22
22
|
return { globs: '', alwaysApply: true };
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
'
|
|
28
|
-
'
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
if (skillName === 'performance' || skillName === 'vercel-optimize') {
|
|
56
|
-
return { globs: '**/*.{ts,tsx,js,jsx,json}', alwaysApply: false };
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if (skillName === 'fastapi') {
|
|
60
|
-
return { globs: '**/*.{py,requirements.txt,Pipfile,pyproject.toml}', alwaysApply: false };
|
|
25
|
+
// Precise surgical globs mapping to prevent context flooding
|
|
26
|
+
const GLOB_MAP = {
|
|
27
|
+
'react': '**/*.{tsx,jsx}',
|
|
28
|
+
'react-best-practices': '**/*.{tsx,jsx}',
|
|
29
|
+
'nextjs': 'app/**/*,pages/**/*,next.config.*',
|
|
30
|
+
'typescript': '**/*.{ts,tsx}',
|
|
31
|
+
'ui-design': '**/*.{tsx,jsx,css,scss}',
|
|
32
|
+
'ui-ux-pro': '**/*.{tsx,jsx,css,scss}',
|
|
33
|
+
'ux-design': '**/*.{tsx,jsx}',
|
|
34
|
+
'impeccable-design': '**/*.{tsx,jsx,css,scss}',
|
|
35
|
+
'brutalist-design': '**/*.{tsx,jsx,css,scss}',
|
|
36
|
+
'minimalist-design': '**/*.{tsx,jsx,css,scss}',
|
|
37
|
+
'soft-design': '**/*.{tsx,jsx,css,scss}',
|
|
38
|
+
'redesign-audit': '**/*.{tsx,jsx,css,scss}',
|
|
39
|
+
'web-accessibility': '**/*.{tsx,jsx,html}',
|
|
40
|
+
'ddd': '**/domain/**/*,**/entities/**/*,**/aggregates/**/*',
|
|
41
|
+
'microservices': '**/services/**/*,docker-compose.*,**/gateway/**/*',
|
|
42
|
+
'nestjs': '**/*.module.ts,**/*.controller.ts,**/*.service.ts,nest-cli.json',
|
|
43
|
+
'node': '**/*.{ts,js}',
|
|
44
|
+
'database': '**/*.{prisma,sql,sqlite,db,drizzle.config.*}',
|
|
45
|
+
'docker': '**/Dockerfile*,**/docker-compose*.{yml,yaml}',
|
|
46
|
+
'testing': '**/*.{test,spec}.{ts,js,tsx,jsx},vitest.config.*,jest.config.*',
|
|
47
|
+
'state-management': '**/*{store,state,slice,reducer,atom}*.{ts,js,tsx,jsx}',
|
|
48
|
+
'performance': '**/*.{ts,tsx,js,jsx,json}',
|
|
49
|
+
'vercel-optimize': '**/*.{ts,tsx,js,jsx,json}',
|
|
50
|
+
'fastapi': '**/*.{py,requirements.txt,Pipfile,pyproject.toml}',
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
if (GLOB_MAP[skillName]) {
|
|
54
|
+
return { globs: GLOB_MAP[skillName], alwaysApply: false };
|
|
61
55
|
}
|
|
62
56
|
|
|
63
57
|
return { globs: '', alwaysApply: false };
|
|
@@ -118,7 +112,10 @@ function generateCursorMdc(skillDir) {
|
|
|
118
112
|
const body = stripFrontmatter(raw);
|
|
119
113
|
const { globs, alwaysApply } = getSkillGlobsAndFlags(skillName);
|
|
120
114
|
|
|
121
|
-
|
|
115
|
+
let cleanDescription = description.replace(/\r?\n+/g, ' ').replace(/"/g, "'").trim();
|
|
116
|
+
if (!globs && !alwaysApply) {
|
|
117
|
+
cleanDescription = `Agent-requested: invoke when working on ${title}. ${cleanDescription}`.trim();
|
|
118
|
+
}
|
|
122
119
|
const mdcContent = `---
|
|
123
120
|
description: "${cleanDescription}"
|
|
124
121
|
globs: ${globs ? `"${globs}"` : '""'}
|
package/.agents/ctx.js
CHANGED
|
@@ -301,7 +301,8 @@ if (command === 'export') {
|
|
|
301
301
|
}
|
|
302
302
|
|
|
303
303
|
if (subcommand === 'add') {
|
|
304
|
-
|
|
304
|
+
const forceUnsafe = args.includes('--force-unsafe-prompts') || args.includes('--force-unsafe');
|
|
305
|
+
plugins.add(ref, { dryRun, checksum, forceUnsafe }).catch(err => {
|
|
305
306
|
console.error(`[ERROR] ${err.message}`);
|
|
306
307
|
process.exit(1);
|
|
307
308
|
});
|
|
@@ -323,7 +324,7 @@ if (command === 'export') {
|
|
|
323
324
|
|
|
324
325
|
// ── clean-worktrees ──────────────────────────────────────────────────────────
|
|
325
326
|
} else if (command === 'clean-worktrees' || command === 'clean') {
|
|
326
|
-
const { execSync } = require('child_process');
|
|
327
|
+
const { execSync, execFileSync } = require('child_process');
|
|
327
328
|
const fs = require('fs');
|
|
328
329
|
const path = require('path');
|
|
329
330
|
const rootDir = path.resolve(__dirname, '..');
|
|
@@ -331,16 +332,16 @@ if (command === 'export') {
|
|
|
331
332
|
|
|
332
333
|
console.log('Cleaning up ContextOS swarm worktrees and branches...');
|
|
333
334
|
try {
|
|
334
|
-
|
|
335
|
+
execFileSync('git', ['worktree', 'prune'], { cwd: rootDir, stdio: 'pipe' });
|
|
335
336
|
} catch {}
|
|
336
337
|
|
|
337
338
|
let branchCount = 0;
|
|
338
339
|
try {
|
|
339
|
-
const branches =
|
|
340
|
+
const branches = execFileSync('git', ['branch', '--list', 'swarm/*'], { cwd: rootDir, encoding: 'utf-8' });
|
|
340
341
|
const list = branches.split('\n').map(b => b.replace(/^[*+\s]+/, '').trim()).filter(Boolean);
|
|
341
342
|
for (const b of list) {
|
|
342
343
|
try {
|
|
343
|
-
|
|
344
|
+
execFileSync('git', ['branch', '-D', b], { cwd: rootDir, stdio: 'pipe' });
|
|
344
345
|
branchCount++;
|
|
345
346
|
} catch {}
|
|
346
347
|
}
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* .agents/doctor.js
|
|
3
|
+
* ContextOS — Project Diagnostics & Health Check Engine
|
|
4
|
+
*
|
|
5
|
+
* Inspects:
|
|
6
|
+
* 1. .agents/ directory and core runtime files
|
|
7
|
+
* 2. Node.js environment compatibility
|
|
8
|
+
* 3. Active profile and skill exclusion/preference state
|
|
9
|
+
* 4. Compiled AI agent adapters (Gemini, Claude, Cursor, Copilot, etc.)
|
|
10
|
+
* 5. MCP server status and worktree support
|
|
11
|
+
* 6. Security controls (pre-commit hooks, secret scanning)
|
|
12
|
+
* 7. Project tech stack detection and recommended profile alignment
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
'use strict';
|
|
16
|
+
|
|
17
|
+
const fs = require('fs');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
const { execFileSync } = require('child_process');
|
|
20
|
+
const profiles = require('./profiles.js');
|
|
21
|
+
|
|
22
|
+
const DOMAIN_MAP = {
|
|
23
|
+
frontend: new Set([
|
|
24
|
+
'react', 'react-best-practices', 'nextjs', 'typescript',
|
|
25
|
+
'ui-ux-pro', 'ui-design', 'ux-design', 'web-accessibility',
|
|
26
|
+
'brutalist-design', 'minimalist-design', 'soft-design', 'redesign-audit',
|
|
27
|
+
'impeccable-design', 'state-management',
|
|
28
|
+
]),
|
|
29
|
+
backend: new Set([
|
|
30
|
+
'node', 'fastapi', 'nestjs', 'database', 'system-design',
|
|
31
|
+
'ddd', 'microservices',
|
|
32
|
+
]),
|
|
33
|
+
cross: new Set([
|
|
34
|
+
'engineering-workflow', 'gstack-roles', 'ponytail-mindset',
|
|
35
|
+
'gemini-precision', 'interview-me', 'security', 'testing',
|
|
36
|
+
'performance', 'vercel-optimize', 'docker', 'decisions',
|
|
37
|
+
'architecture-diagrams', 'subagent-orchestrator', 'graphify',
|
|
38
|
+
'context-manager', 'context-os', 'adapters', 'generators',
|
|
39
|
+
]),
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
function checkNodeVersion() {
|
|
43
|
+
const version = process.version; // e.g. v22.12.0
|
|
44
|
+
const match = version.match(/^v?(\d+)\.(\d+)/);
|
|
45
|
+
if (!match) return { ok: false, version, message: `${version} (unknown)` };
|
|
46
|
+
|
|
47
|
+
const major = parseInt(match[1], 10);
|
|
48
|
+
const minor = parseInt(match[2], 10);
|
|
49
|
+
// Requires at least Node 18.0.0 or 16.7.0 for recursive cpSync
|
|
50
|
+
const ok = major > 16 || (major === 16 && minor >= 7);
|
|
51
|
+
return {
|
|
52
|
+
ok,
|
|
53
|
+
version,
|
|
54
|
+
message: ok ? `Node.js ${version} (>= 16.7.0 required)` : `Node.js ${version} (< 16.7.0, please upgrade)`,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function checkPreCommitHook(projectDir) {
|
|
59
|
+
const hookPath = path.join(projectDir, '.git', 'hooks', 'pre-commit');
|
|
60
|
+
if (!fs.existsSync(hookPath)) {
|
|
61
|
+
return { ok: false, message: 'not installed (run: npm run setup:hooks)' };
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
const content = fs.readFileSync(hookPath, 'utf8');
|
|
65
|
+
if (content.includes('check-secrets')) {
|
|
66
|
+
return { ok: true, message: 'installed (with secret check)' };
|
|
67
|
+
}
|
|
68
|
+
return { ok: false, message: 'installed but missing check-secrets' };
|
|
69
|
+
} catch {
|
|
70
|
+
return { ok: false, message: 'unreadable' };
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function checkSecretScanner(projectDir) {
|
|
75
|
+
const scriptPath = path.join(projectDir, 'scripts', 'check-secrets.js');
|
|
76
|
+
if (!fs.existsSync(scriptPath)) {
|
|
77
|
+
return { ok: true, message: 'scanner script not present (skipped)' };
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
execFileSync(process.execPath, [scriptPath, '--all'], {
|
|
81
|
+
cwd: projectDir,
|
|
82
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
83
|
+
});
|
|
84
|
+
return { ok: true, message: '0 findings' };
|
|
85
|
+
} catch (err) {
|
|
86
|
+
return { ok: false, message: 'potential secrets detected (run: npm run check:secrets)' };
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function checkCompiledAdapters(projectDir) {
|
|
91
|
+
const compiled = [];
|
|
92
|
+
const genGemini = path.join(projectDir, '.agents', 'generated', 'gemini', 'skills');
|
|
93
|
+
if (fs.existsSync(genGemini) && fs.readdirSync(genGemini).length > 0) {
|
|
94
|
+
compiled.push('gemini');
|
|
95
|
+
}
|
|
96
|
+
const genClaude = path.join(projectDir, '.agents', 'generated', 'claude', 'skills');
|
|
97
|
+
if (fs.existsSync(genClaude) && fs.readdirSync(genClaude).length > 0) {
|
|
98
|
+
compiled.push('claude');
|
|
99
|
+
}
|
|
100
|
+
if (fs.existsSync(path.join(projectDir, '.cursorrules')) || fs.existsSync(path.join(projectDir, '.cursor', 'rules'))) {
|
|
101
|
+
compiled.push('cursor');
|
|
102
|
+
}
|
|
103
|
+
if (fs.existsSync(path.join(projectDir, '.github', 'copilot-instructions.md'))) {
|
|
104
|
+
compiled.push('copilot');
|
|
105
|
+
}
|
|
106
|
+
if (fs.existsSync(path.join(projectDir, '.aider.conf.yml'))) {
|
|
107
|
+
compiled.push('aider');
|
|
108
|
+
}
|
|
109
|
+
if (fs.existsSync(path.join(projectDir, '.zed', 'rules.md'))) {
|
|
110
|
+
compiled.push('zed');
|
|
111
|
+
}
|
|
112
|
+
return compiled;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function inspectSkills(projectDir, activeProfile) {
|
|
116
|
+
const skillsDir = path.join(projectDir, '.agents', 'core', 'skills');
|
|
117
|
+
if (!fs.existsSync(skillsDir)) return { total: 0, active: 0, excluded: 0, byDomain: { frontend: [], backend: [], cross: [] } };
|
|
118
|
+
|
|
119
|
+
const entries = fs.readdirSync(skillsDir, { withFileTypes: true });
|
|
120
|
+
const allSkills = entries
|
|
121
|
+
.filter(e => e.isDirectory() && fs.existsSync(path.join(skillsDir, e.name, 'SKILL.md')))
|
|
122
|
+
.map(e => e.name);
|
|
123
|
+
|
|
124
|
+
const excludedSet = new Set(activeProfile ? activeProfile.exclude_skills || [] : []);
|
|
125
|
+
const activeSkills = allSkills.filter(s => !excludedSet.has(s));
|
|
126
|
+
|
|
127
|
+
const byDomain = { frontend: [], backend: [], cross: [] };
|
|
128
|
+
for (const s of activeSkills) {
|
|
129
|
+
if (DOMAIN_MAP.frontend.has(s)) byDomain.frontend.push(s);
|
|
130
|
+
else if (DOMAIN_MAP.backend.has(s)) byDomain.backend.push(s);
|
|
131
|
+
else byDomain.cross.push(s);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
total: allSkills.length,
|
|
136
|
+
active: activeSkills.length,
|
|
137
|
+
excluded: excludedSet.size,
|
|
138
|
+
byDomain,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function runDoctor(projectDir = process.cwd()) {
|
|
143
|
+
const version = (() => {
|
|
144
|
+
try {
|
|
145
|
+
return require('../package.json').version;
|
|
146
|
+
} catch {
|
|
147
|
+
return '1.5.0';
|
|
148
|
+
}
|
|
149
|
+
})();
|
|
150
|
+
|
|
151
|
+
const agentsDir = path.join(projectDir, '.agents');
|
|
152
|
+
const hasAgents = fs.existsSync(agentsDir);
|
|
153
|
+
const nodeCheck = checkNodeVersion();
|
|
154
|
+
const activeProfile = hasAgents ? profiles.getActiveProfile(projectDir) : null;
|
|
155
|
+
const skillsInfo = inspectSkills(projectDir, activeProfile);
|
|
156
|
+
const compiledAdapters = checkCompiledAdapters(projectDir);
|
|
157
|
+
|
|
158
|
+
const mcpServerPath = path.join(projectDir, '.agents', 'mcp', 'server.mjs');
|
|
159
|
+
const hasMcp = fs.existsSync(mcpServerPath);
|
|
160
|
+
|
|
161
|
+
const hookCheck = checkPreCommitHook(projectDir);
|
|
162
|
+
const secretCheck = checkSecretScanner(projectDir);
|
|
163
|
+
const stack = profiles.detectStack(projectDir);
|
|
164
|
+
|
|
165
|
+
console.log('\n┌─────────────────────────────────────────────────────────────┐');
|
|
166
|
+
console.log(`│ ContextOS Doctor — Project Health Check v${version.padEnd(16)}│`);
|
|
167
|
+
console.log('├─────────────────────────────────────────────────────────────┤');
|
|
168
|
+
console.log('│ │');
|
|
169
|
+
|
|
170
|
+
// .agents directory
|
|
171
|
+
if (hasAgents) {
|
|
172
|
+
console.log('│ ✓ .agents/ directory found │');
|
|
173
|
+
} else {
|
|
174
|
+
console.log('│ ✗ .agents/ directory missing (run: npx contextos-agents) │');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Node version
|
|
178
|
+
const nodeIcon = nodeCheck.ok ? '✓' : '✗';
|
|
179
|
+
console.log(`│ ${nodeIcon} ${nodeCheck.message.padEnd(58)}│`);
|
|
180
|
+
|
|
181
|
+
// Active profile
|
|
182
|
+
if (activeProfile) {
|
|
183
|
+
const profStr = `Active profile: ${activeProfile.name || activeProfile.profile}`;
|
|
184
|
+
console.log(`│ ✓ ${profStr.padEnd(58)}│`);
|
|
185
|
+
} else {
|
|
186
|
+
console.log('│ • Active profile: default (no profile locked) │');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Skills
|
|
190
|
+
const skillsStr = `Skills loaded: ${skillsInfo.active} / ${skillsInfo.total}` +
|
|
191
|
+
(skillsInfo.excluded > 0 ? ` (${skillsInfo.excluded} excluded by profile)` : '');
|
|
192
|
+
console.log(`│ ✓ ${skillsStr.padEnd(58)}│`);
|
|
193
|
+
|
|
194
|
+
// Adapters
|
|
195
|
+
if (compiledAdapters.length > 0) {
|
|
196
|
+
const adaptStr = `Adapters compiled: ${compiledAdapters.join(', ')}`;
|
|
197
|
+
console.log(`│ ✓ ${adaptStr.padEnd(58)}│`);
|
|
198
|
+
} else {
|
|
199
|
+
console.log('│ • Adapters compiled: none (run: contextos export gemini) │');
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// MCP
|
|
203
|
+
if (hasMcp) {
|
|
204
|
+
console.log('│ ✓ MCP server: installed (.agents/mcp/server.mjs) │');
|
|
205
|
+
} else {
|
|
206
|
+
console.log('│ • MCP server: not installed (run: contextos setup-mcp) │');
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Security
|
|
210
|
+
const hookIcon = hookCheck.ok ? '✓' : '•';
|
|
211
|
+
const hookStr = `Pre-commit hook: ${hookCheck.message}`;
|
|
212
|
+
console.log(`│ ${hookIcon} ${hookStr.padEnd(58)}│`);
|
|
213
|
+
|
|
214
|
+
const secretIcon = secretCheck.ok ? '✓' : '✗';
|
|
215
|
+
const secretStr = `Secret scanner: ${secretCheck.message}`;
|
|
216
|
+
console.log(`│ ${secretIcon} ${secretStr.padEnd(58)}│`);
|
|
217
|
+
|
|
218
|
+
console.log('│ │');
|
|
219
|
+
console.log('├─────────────────────────────────────────────────────────────┤');
|
|
220
|
+
console.log('│ Project Stack & Profile Recommendations │');
|
|
221
|
+
console.log('├─────────────────────────────────────────────────────────────┤');
|
|
222
|
+
console.log('│ │');
|
|
223
|
+
|
|
224
|
+
const detectedStr = `Detected Stack: ${stack.detected.length ? stack.detected.join(', ') : 'Generic JS'}`;
|
|
225
|
+
console.log(`│ • ${detectedStr.slice(0, 56).padEnd(56)} │`);
|
|
226
|
+
|
|
227
|
+
const recProfStr = `Recommended Profile: ${stack.recommendedProfile}`;
|
|
228
|
+
console.log(`│ • ${recProfStr.padEnd(56)} │`);
|
|
229
|
+
|
|
230
|
+
console.log('│ │');
|
|
231
|
+
console.log('│ Skills by Domain: │');
|
|
232
|
+
|
|
233
|
+
const feStr = `Frontend (${skillsInfo.byDomain.frontend.length}): ${skillsInfo.byDomain.frontend.slice(0, 4).join(', ')}${skillsInfo.byDomain.frontend.length > 4 ? '...' : ''}`;
|
|
234
|
+
console.log(`│ ${feStr.padEnd(57)}│`);
|
|
235
|
+
|
|
236
|
+
const beStr = `Backend (${skillsInfo.byDomain.backend.length}): ${skillsInfo.byDomain.backend.slice(0, 4).join(', ')}${skillsInfo.byDomain.backend.length > 4 ? '...' : ''}`;
|
|
237
|
+
console.log(`│ ${beStr.padEnd(57)}│`);
|
|
238
|
+
|
|
239
|
+
const crStr = `Cross (${skillsInfo.byDomain.cross.length}): ${skillsInfo.byDomain.cross.slice(0, 4).join(', ')}${skillsInfo.byDomain.cross.length > 4 ? '...' : ''}`;
|
|
240
|
+
console.log(`│ ${crStr.padEnd(57)}│`);
|
|
241
|
+
|
|
242
|
+
console.log('│ │');
|
|
243
|
+
console.log('└─────────────────────────────────────────────────────────────┘\n');
|
|
244
|
+
|
|
245
|
+
return {
|
|
246
|
+
hasAgents,
|
|
247
|
+
nodeOk: nodeCheck.ok,
|
|
248
|
+
activeProfile,
|
|
249
|
+
skillsInfo,
|
|
250
|
+
compiledAdapters,
|
|
251
|
+
hasMcp,
|
|
252
|
+
hookCheck,
|
|
253
|
+
secretCheck,
|
|
254
|
+
stack,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (require.main === module) {
|
|
259
|
+
runDoctor();
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
module.exports = {
|
|
263
|
+
runDoctor,
|
|
264
|
+
checkNodeVersion,
|
|
265
|
+
checkPreCommitHook,
|
|
266
|
+
checkSecretScanner,
|
|
267
|
+
checkCompiledAdapters,
|
|
268
|
+
inspectSkills,
|
|
269
|
+
};
|
package/.agents/mcp/runtime.py
CHANGED
|
@@ -38,6 +38,8 @@ _FORBIDDEN_ATTRIBUTES = {
|
|
|
38
38
|
'__globals__', '__code__', '__builtins__', '__import__',
|
|
39
39
|
'__getattr__', '__setattr__', '__delattr__',
|
|
40
40
|
'__reduce__', '__reduce_ex__', # pickle-based escapes
|
|
41
|
+
'create_subprocess_shell', 'create_subprocess_exec',
|
|
42
|
+
'open_connection', 'start_server', 'connect_read_pipe', 'connect_write_pipe',
|
|
41
43
|
}
|
|
42
44
|
|
|
43
45
|
def _safe_import(name, globals=None, locals=None, fromlist=(), level=0):
|
|
@@ -340,6 +342,20 @@ def merge_threads() -> str:
|
|
|
340
342
|
return raw
|
|
341
343
|
|
|
342
344
|
|
|
345
|
+
class _SafeAsyncio:
|
|
346
|
+
gather = staticmethod(asyncio.gather)
|
|
347
|
+
sleep = staticmethod(asyncio.sleep)
|
|
348
|
+
wait = staticmethod(asyncio.wait)
|
|
349
|
+
Event = asyncio.Event
|
|
350
|
+
Queue = asyncio.Queue
|
|
351
|
+
Lock = asyncio.Lock
|
|
352
|
+
Semaphore = asyncio.Semaphore
|
|
353
|
+
TimeoutError = asyncio.TimeoutError
|
|
354
|
+
as_completed = staticmethod(asyncio.as_completed)
|
|
355
|
+
|
|
356
|
+
_safe_asyncio = _SafeAsyncio()
|
|
357
|
+
|
|
358
|
+
|
|
343
359
|
def _runtime_symbols() -> dict:
|
|
344
360
|
"""Return the dict of runtime symbols injected into the user namespace."""
|
|
345
361
|
return {
|
|
@@ -352,7 +368,7 @@ def _runtime_symbols() -> dict:
|
|
|
352
368
|
'merge_threads': merge_threads,
|
|
353
369
|
'FINAL': FINAL,
|
|
354
370
|
'FINAL_VAR': FINAL_VAR,
|
|
355
|
-
'asyncio':
|
|
371
|
+
'asyncio': _safe_asyncio,
|
|
356
372
|
'json': json,
|
|
357
373
|
}
|
|
358
374
|
|
package/.agents/mcp/server.mjs
CHANGED
|
@@ -2240,8 +2240,8 @@ var require_resolve = __commonJS({
|
|
|
2240
2240
|
}
|
|
2241
2241
|
return count;
|
|
2242
2242
|
}
|
|
2243
|
-
function getFullPath(resolver, id = "",
|
|
2244
|
-
if (
|
|
2243
|
+
function getFullPath(resolver, id = "", normalize3) {
|
|
2244
|
+
if (normalize3 !== false)
|
|
2245
2245
|
id = normalizeId(id);
|
|
2246
2246
|
const p5 = resolver.parse(id);
|
|
2247
2247
|
return _getFullPath(resolver, p5);
|
|
@@ -3581,7 +3581,7 @@ var require_fast_uri = __commonJS({
|
|
|
3581
3581
|
"use strict";
|
|
3582
3582
|
var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizeComponentEncoding, isIPv4, nonSimpleDomain } = require_utils();
|
|
3583
3583
|
var { SCHEMES, getSchemeHandler } = require_schemes();
|
|
3584
|
-
function
|
|
3584
|
+
function normalize3(uri, options) {
|
|
3585
3585
|
if (typeof uri === "string") {
|
|
3586
3586
|
uri = /** @type {T} */
|
|
3587
3587
|
serialize(parse3(uri, options), options);
|
|
@@ -3817,7 +3817,7 @@ var require_fast_uri = __commonJS({
|
|
|
3817
3817
|
}
|
|
3818
3818
|
var fastUri = {
|
|
3819
3819
|
SCHEMES,
|
|
3820
|
-
normalize:
|
|
3820
|
+
normalize: normalize3,
|
|
3821
3821
|
resolve: resolve10,
|
|
3822
3822
|
resolveComponent,
|
|
3823
3823
|
equal,
|
|
@@ -6833,9 +6833,10 @@ var init_provider = __esm({
|
|
|
6833
6833
|
// src/security/secret-filter.ts
|
|
6834
6834
|
import * as path3 from "node:path";
|
|
6835
6835
|
function isBlockedPath(filePath) {
|
|
6836
|
-
const
|
|
6837
|
-
const
|
|
6838
|
-
const
|
|
6836
|
+
const normalized = path3.normalize(filePath);
|
|
6837
|
+
const basename5 = path3.basename(normalized).toLowerCase();
|
|
6838
|
+
const ext = path3.extname(normalized).toLowerCase();
|
|
6839
|
+
const parts = normalized.split(/[/\\]/).map((p5) => p5.toLowerCase());
|
|
6839
6840
|
if (BLOCKED_FILENAMES.some((f7) => basename5 === f7.toLowerCase())) {
|
|
6840
6841
|
return true;
|
|
6841
6842
|
}
|
|
@@ -6851,7 +6852,8 @@ function isBlockedPath(filePath) {
|
|
|
6851
6852
|
return false;
|
|
6852
6853
|
}
|
|
6853
6854
|
function containsSecrets(content) {
|
|
6854
|
-
|
|
6855
|
+
if (!content) return false;
|
|
6856
|
+
const sample = content.length > 1e6 ? content.slice(0, 1e6) : content;
|
|
6855
6857
|
return SECRET_CONTENT_PATTERNS.some((pattern) => pattern.test(sample));
|
|
6856
6858
|
}
|
|
6857
6859
|
function redactSecrets(text) {
|
|
@@ -6875,7 +6877,10 @@ var init_secret_filter = __esm({
|
|
|
6875
6877
|
"credentials",
|
|
6876
6878
|
"credentials.json",
|
|
6877
6879
|
"service-account.json",
|
|
6878
|
-
"serviceAccountKey.json"
|
|
6880
|
+
"serviceAccountKey.json",
|
|
6881
|
+
".netrc",
|
|
6882
|
+
".git-credentials",
|
|
6883
|
+
".npmrc"
|
|
6879
6884
|
];
|
|
6880
6885
|
BLOCKED_EXTENSIONS = [".pem", ".key", ".p12", ".pfx", ".jks", ".keystore"];
|
|
6881
6886
|
BLOCKED_DIRS = [".ssh", ".gnupg", ".aws", ".azure", ".gcp", ".kube"];
|
|
@@ -6887,12 +6892,18 @@ var init_secret_filter = __esm({
|
|
|
6887
6892
|
// GitHub personal access token
|
|
6888
6893
|
/gho_[A-Za-z0-9]{36}/,
|
|
6889
6894
|
// GitHub OAuth token
|
|
6895
|
+
/github_pat_[A-Za-z0-9_]{82}/,
|
|
6896
|
+
// GitHub fine-grained PAT
|
|
6890
6897
|
/sk-[A-Za-z0-9]{32,}/,
|
|
6891
6898
|
// OpenAI API key
|
|
6892
6899
|
/sk-ant-[A-Za-z0-9-]{90,}/,
|
|
6893
6900
|
// Anthropic API key
|
|
6894
|
-
/AIza[A-Za-z0-9_-]{35}
|
|
6901
|
+
/AIza[A-Za-z0-9_-]{35}/,
|
|
6895
6902
|
// Google API key
|
|
6903
|
+
/npm_[A-Za-z0-9]{32,36}/,
|
|
6904
|
+
// npm access token
|
|
6905
|
+
/xox[baprs]-[A-Za-z0-9_-]{10,48}/
|
|
6906
|
+
// Slack token
|
|
6896
6907
|
];
|
|
6897
6908
|
}
|
|
6898
6909
|
});
|
|
@@ -118880,7 +118891,7 @@ var require_util5 = __commonJS({
|
|
|
118880
118891
|
return url2;
|
|
118881
118892
|
}
|
|
118882
118893
|
exports.urlGenerate = urlGenerate;
|
|
118883
|
-
function
|
|
118894
|
+
function normalize3(aPath) {
|
|
118884
118895
|
var path16 = aPath;
|
|
118885
118896
|
var url2 = urlParse(aPath);
|
|
118886
118897
|
if (url2) {
|
|
@@ -118917,7 +118928,7 @@ var require_util5 = __commonJS({
|
|
|
118917
118928
|
}
|
|
118918
118929
|
return path16;
|
|
118919
118930
|
}
|
|
118920
|
-
exports.normalize =
|
|
118931
|
+
exports.normalize = normalize3;
|
|
118921
118932
|
function join10(aRoot, aPath) {
|
|
118922
118933
|
if (aRoot === "") {
|
|
118923
118934
|
aRoot = ".";
|
|
@@ -118943,7 +118954,7 @@ var require_util5 = __commonJS({
|
|
|
118943
118954
|
aRootUrl.host = aPath;
|
|
118944
118955
|
return urlGenerate(aRootUrl);
|
|
118945
118956
|
}
|
|
118946
|
-
var joined = aPath.charAt(0) === "/" ? aPath :
|
|
118957
|
+
var joined = aPath.charAt(0) === "/" ? aPath : normalize3(aRoot.replace(/\/+$/, "") + "/" + aPath);
|
|
118947
118958
|
if (aRootUrl) {
|
|
118948
118959
|
aRootUrl.path = joined;
|
|
118949
118960
|
return urlGenerate(aRootUrl);
|
|
@@ -119125,7 +119136,7 @@ var require_util5 = __commonJS({
|
|
|
119125
119136
|
}
|
|
119126
119137
|
sourceURL = join10(urlGenerate(parsed), sourceURL);
|
|
119127
119138
|
}
|
|
119128
|
-
return
|
|
119139
|
+
return normalize3(sourceURL);
|
|
119129
119140
|
}
|
|
119130
119141
|
exports.computeSourceURL = computeSourceURL;
|
|
119131
119142
|
}
|
|
@@ -162826,7 +162837,12 @@ function writeCodeBlocks(workDir, blocks) {
|
|
|
162826
162837
|
for (const block of blocks) {
|
|
162827
162838
|
const normalized = path13.normalize(block.filePath);
|
|
162828
162839
|
if (normalized.startsWith("..") || path13.isAbsolute(normalized)) {
|
|
162829
|
-
process.stderr.write(`[contextos-agent] Skipping unsafe path: ${block.filePath}
|
|
162840
|
+
process.stderr.write(`[contextos-agent] Skipping unsafe path (traversal): ${block.filePath}
|
|
162841
|
+
`);
|
|
162842
|
+
continue;
|
|
162843
|
+
}
|
|
162844
|
+
if (isBlockedPath(normalized) || !isPathAllowed(normalized, workDir)) {
|
|
162845
|
+
process.stderr.write(`[contextos-agent] Skipping forbidden/policy-blocked path: ${block.filePath}
|
|
162830
162846
|
`);
|
|
162831
162847
|
continue;
|
|
162832
162848
|
}
|
|
@@ -188780,20 +188796,8 @@ function buildContextPrompt(projectRoot, task, options = {}) {
|
|
|
188780
188796
|
// src/mcp/tools/contextos.ts
|
|
188781
188797
|
init_secret_filter();
|
|
188782
188798
|
init_command_exists();
|
|
188783
|
-
var ALLOWED_VERIFY_TOOLS = /* @__PURE__ */ new Set([
|
|
188784
|
-
|
|
188785
|
-
"npx",
|
|
188786
|
-
"pnpm",
|
|
188787
|
-
"yarn",
|
|
188788
|
-
"pytest",
|
|
188789
|
-
"python",
|
|
188790
|
-
"python3",
|
|
188791
|
-
"cargo",
|
|
188792
|
-
"go",
|
|
188793
|
-
"vitest",
|
|
188794
|
-
"jest",
|
|
188795
|
-
"node"
|
|
188796
|
-
]);
|
|
188799
|
+
var ALLOWED_VERIFY_TOOLS = /* @__PURE__ */ new Set(["npm", "npx", "pnpm", "yarn", "pytest", "cargo", "go", "vitest", "jest"]);
|
|
188800
|
+
var FORBIDDEN_VERIFY_ARGS = /* @__PURE__ */ new Set(["-e", "-c", "--eval", "--print", "-p", "--input-type"]);
|
|
188797
188801
|
var STRICT_ENV_ALLOWLIST = /* @__PURE__ */ new Set([
|
|
188798
188802
|
"PATH",
|
|
188799
188803
|
"HOME",
|
|
@@ -188846,6 +188850,15 @@ async function runWorktreeVerification(worktreePath, command) {
|
|
|
188846
188850
|
output: `Security error: Command "${rawTool}" is not in the allowed verification tools whitelist (${Array.from(ALLOWED_VERIFY_TOOLS).join(", ")})`
|
|
188847
188851
|
};
|
|
188848
188852
|
}
|
|
188853
|
+
const args = parts.slice(1);
|
|
188854
|
+
for (const arg of args) {
|
|
188855
|
+
if (FORBIDDEN_VERIFY_ARGS.has(arg.toLowerCase())) {
|
|
188856
|
+
return {
|
|
188857
|
+
verified: false,
|
|
188858
|
+
output: `Security error: Argument "${arg}" is not permitted in verify_command`
|
|
188859
|
+
};
|
|
188860
|
+
}
|
|
188861
|
+
}
|
|
188849
188862
|
const exePath = await resolveExecutablePath(rawTool);
|
|
188850
188863
|
const targetExecutable = exePath || rawTool;
|
|
188851
188864
|
return new Promise((resolveResult) => {
|