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.
@@ -22,42 +22,36 @@ function getSkillGlobsAndFlags(skillName) {
22
22
  return { globs: '', alwaysApply: true };
23
23
  }
24
24
 
25
- const FRONTEND_SKILLS = [
26
- 'react', 'nextjs', 'typescript', 'ui-ux-pro', 'impeccable-design',
27
- 'ui-design', 'ux-design', 'web-accessibility', 'brutalist-design',
28
- 'minimalist-design', 'soft-design', 'redesign-audit', 'react-best-practices',
29
- ];
30
- if (FRONTEND_SKILLS.includes(skillName)) {
31
- return { globs: '**/*.{ts,tsx,js,jsx,css,scss,html}', alwaysApply: false };
32
- }
33
-
34
- const BACKEND_SKILLS = ['system-design', 'node', 'nestjs', 'microservices', 'ddd'];
35
- if (BACKEND_SKILLS.includes(skillName)) {
36
- return { globs: '**/*.{ts,js,json,prisma,sql}', alwaysApply: false };
37
- }
38
-
39
- if (skillName === 'database') {
40
- return { globs: '**/*.{prisma,sql,sqlite,db,drizzle.config.*}', alwaysApply: false };
41
- }
42
-
43
- if (skillName === 'docker') {
44
- return { globs: '**/Dockerfile*,**/docker-compose*.{yml,yaml}', alwaysApply: false };
45
- }
46
-
47
- if (skillName === 'testing') {
48
- return { globs: '**/*.{test,spec}.{ts,js,tsx,jsx},vitest.config.*,jest.config.*', alwaysApply: false };
49
- }
50
-
51
- if (skillName === 'state-management') {
52
- return { globs: '**/*{store,state,slice,reducer,atom}*.{ts,js,tsx,jsx}', alwaysApply: false };
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
- const cleanDescription = description.replace(/\r?\n+/g, ' ').replace(/"/g, "'").trim();
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
- plugins.add(ref, { dryRun, checksum }).catch(err => {
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
- execSync('git worktree prune', { cwd: rootDir, stdio: 'pipe' });
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 = execSync('git branch --list "swarm/*"', { cwd: rootDir, encoding: 'utf-8' });
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
- execSync(`git branch -D "${b}"`, { cwd: rootDir, stdio: 'pipe' });
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
+ };
@@ -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': asyncio,
371
+ 'asyncio': _safe_asyncio,
356
372
  'json': json,
357
373
  }
358
374
 
@@ -2240,8 +2240,8 @@ var require_resolve = __commonJS({
2240
2240
  }
2241
2241
  return count;
2242
2242
  }
2243
- function getFullPath(resolver, id = "", normalize2) {
2244
- if (normalize2 !== false)
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 normalize2(uri, options) {
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: normalize2,
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 basename5 = path3.basename(filePath).toLowerCase();
6837
- const ext = path3.extname(filePath).toLowerCase();
6838
- const parts = filePath.split(path3.sep).map((p5) => p5.toLowerCase());
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
- const sample = content.slice(0, 5e3);
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 normalize2(aPath) {
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 = normalize2;
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 : normalize2(aRoot.replace(/\/+$/, "") + "/" + 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 normalize2(sourceURL);
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
- "npm",
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) => {