contextos-agents 1.6.0 → 1.7.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 (88) hide show
  1. package/.agents/AGENTS.md +5 -0
  2. package/.agents/adapters/aider/export.js +4 -2
  3. package/.agents/adapters/copilot/export.js +4 -2
  4. package/.agents/adapters/cursor/export.js +48 -12
  5. package/.agents/adapters/zed/export.js +53 -14
  6. package/.agents/core/profiles/mvp.yaml +1 -0
  7. package/.agents/core/skills/adapters/skill.yaml +6 -0
  8. package/.agents/core/skills/architecture-diagrams/skill.yaml +4 -0
  9. package/.agents/core/skills/brutalist-design/skill.yaml +4 -0
  10. package/.agents/core/skills/context-manager/references/context-rules.md +59 -0
  11. package/.agents/core/skills/context-manager/skill.yaml +7 -0
  12. package/.agents/core/skills/context-os/skill.yaml +11 -0
  13. package/.agents/core/skills/database/skill.yaml +6 -0
  14. package/.agents/core/skills/ddd/skill.yaml +7 -0
  15. package/.agents/core/skills/decisions/skill.yaml +6 -0
  16. package/.agents/core/skills/docker/skill.yaml +6 -0
  17. package/.agents/core/skills/engineering-workflow/SKILL.md +1 -1
  18. package/.agents/core/skills/engineering-workflow/skill.yaml +6 -0
  19. package/.agents/core/skills/fastapi/skill.yaml +7 -0
  20. package/.agents/core/skills/gemini-precision/SKILL.md +4 -0
  21. package/.agents/core/skills/gemini-precision/skill.yaml +4 -0
  22. package/.agents/core/skills/generators/skill.yaml +15 -0
  23. package/.agents/core/skills/graphify/SKILL.md +2 -2
  24. package/.agents/core/skills/graphify/skill.yaml +6 -1
  25. package/.agents/core/skills/gstack-roles/SKILL.md +3 -1
  26. package/.agents/core/skills/gstack-roles/skill.yaml +6 -0
  27. package/.agents/core/skills/impeccable-design/skill.yaml +6 -0
  28. package/.agents/core/skills/interview-me/skill.yaml +4 -0
  29. package/.agents/core/skills/microservices/skill.yaml +7 -0
  30. package/.agents/core/skills/minimalist-design/skill.yaml +4 -0
  31. package/.agents/core/skills/nestjs/skill.yaml +7 -0
  32. package/.agents/core/skills/nextjs/skill.yaml +7 -0
  33. package/.agents/core/skills/node/skill.yaml +7 -0
  34. package/.agents/core/skills/performance/skill.yaml +7 -0
  35. package/.agents/core/skills/ponytail-mindset/skill.yaml +6 -0
  36. package/.agents/core/skills/react/skill.yaml +7 -0
  37. package/.agents/core/skills/react-best-practices/skill.yaml +4 -0
  38. package/.agents/core/skills/redesign-audit/skill.yaml +4 -0
  39. package/.agents/core/skills/security/skill.yaml +7 -0
  40. package/.agents/core/skills/soft-design/skill.yaml +4 -0
  41. package/.agents/core/skills/state-management/skill.yaml +6 -0
  42. package/.agents/core/skills/subagent-orchestrator/SKILL.md +17 -0
  43. package/.agents/core/skills/subagent-orchestrator/skill.yaml +4 -0
  44. package/.agents/core/skills/system-design/skill.yaml +7 -0
  45. package/.agents/core/skills/testing/skill.yaml +6 -0
  46. package/.agents/core/skills/typescript/skill.yaml +7 -0
  47. package/.agents/core/skills/ui-design/skill.yaml +7 -0
  48. package/.agents/core/skills/ui-ux-pro/skill.yaml +6 -0
  49. package/.agents/core/skills/ux-design/skill.yaml +7 -0
  50. package/.agents/core/skills/vercel-optimize/scripts/collect-signals.mjs +131 -0
  51. package/.agents/core/skills/vercel-optimize/scripts/gate-investigations.mjs +142 -0
  52. package/.agents/core/skills/vercel-optimize/scripts/merge-signals.mjs +143 -0
  53. package/.agents/core/skills/vercel-optimize/scripts/scan-codebase.mjs +174 -0
  54. package/.agents/core/skills/vercel-optimize/skill.yaml +8 -0
  55. package/.agents/core/skills/web-accessibility/skill.yaml +7 -0
  56. package/.agents/ctx.js +44 -0
  57. package/.agents/doctor.js +193 -4
  58. package/.agents/generated/claude/skills/engineering-workflow/SKILL.md +1 -1
  59. package/.agents/generated/claude/skills/gemini-precision/SKILL.md +4 -0
  60. package/.agents/generated/claude/skills/graphify/SKILL.md +1 -1
  61. package/.agents/generated/claude/skills/gstack-roles/SKILL.md +3 -1
  62. package/.agents/generated/claude/skills/subagent-orchestrator/SKILL.md +17 -0
  63. package/.agents/generated/gemini/skills/engineering-workflow/SKILL.md +1 -1
  64. package/.agents/generated/gemini/skills/gemini-precision/SKILL.md +4 -0
  65. package/.agents/generated/gemini/skills/graphify/SKILL.md +2 -2
  66. package/.agents/generated/gemini/skills/gstack-roles/SKILL.md +3 -1
  67. package/.agents/generated/gemini/skills/subagent-orchestrator/SKILL.md +17 -0
  68. package/.agents/plugins/contextos/hooks.json +25 -0
  69. package/.agents/plugins/contextos/plugin.json +19 -0
  70. package/.agents/plugins.js +180 -20
  71. package/.agents/profiles.js +26 -0
  72. package/.agents/resolver.js +150 -30
  73. package/.agents/stats.js +22 -9
  74. package/.agents/validate.js +152 -13
  75. package/README.md +77 -47
  76. package/bin/commands/uninstall.js +207 -0
  77. package/bin/commands/update.js +262 -0
  78. package/bin/commands.js +305 -0
  79. package/bin/index.js +275 -119
  80. package/bin/lib/detector.js +106 -0
  81. package/bin/lib/lockfile.js +251 -0
  82. package/bin/lib/safe-writer.js +259 -0
  83. package/package.json +6 -9
  84. package/registry.json +13 -5
  85. package/registry.schema.json +3 -1
  86. package/.agents/mcp/runtime.py +0 -470
  87. package/.agents/mcp/server.mjs +0 -189386
  88. package/benchmarks/gemini-issues.js +0 -533
@@ -20,6 +20,7 @@ const CORE_SKILLS_DIR = path.join(AGENTS_DIR, 'core', 'skills');
20
20
  const SKILL_RULES = [
21
21
  {
22
22
  skill: 'nextjs',
23
+ category: 'stack',
23
24
  strong: [/\bnext(?:\.js)?\b/i, /\bapp\s*router\b/i, /\bserver\s*actions?\b/i, /\brsc\b/i, /некст/i],
24
25
  medium: [/\bpage\.tsx\b/i, /\blayout\.tsx\b/i],
25
26
  weak: [],
@@ -27,6 +28,7 @@ const SKILL_RULES = [
27
28
  },
28
29
  {
29
30
  skill: 'react',
31
+ category: 'stack',
30
32
  strong: [/\breact\b/i, /\buseOptimistic\b/i, /компонент/i, /хук/i],
31
33
  medium: [/\bcomponent\b/i, /\bhooks?\b/i, /\buseState\b/i, /\buseEffect\b/i, /\buseMemo\b/i, /\bprops\b/i, /модал\w*/i],
32
34
  weak: [],
@@ -34,6 +36,7 @@ const SKILL_RULES = [
34
36
  },
35
37
  {
36
38
  skill: 'typescript',
39
+ category: 'stack',
37
40
  strong: [/\btypescript\b/i, /\btype-?safe\b/i, /\bgenerics?\b/i, /\binterface\b/i, /\btsconfig\b/i, /тайпскрипт/i, /типизац/i],
38
41
  medium: [],
39
42
  weak: [/\btypes?\b/i],
@@ -41,6 +44,7 @@ const SKILL_RULES = [
41
44
  },
42
45
  {
43
46
  skill: 'ui-ux-pro',
47
+ category: 'ui',
44
48
  strong: [/\bui\b/i, /\bux\b/i, /\btailwind\b/i, /\bstyling\b/i, /дизайн/i, /верстк/i, /макет/i, /интерфейс/i],
45
49
  medium: [/\bcss\b/i, /\btheme\b/i, /\bmodal\b/i, /\bbutton\b/i, /модал\w*/i, /кнопк/i],
46
50
  weak: [/\bdesign\b/i],
@@ -48,6 +52,7 @@ const SKILL_RULES = [
48
52
  },
49
53
  {
50
54
  skill: 'web-accessibility',
55
+ category: 'ui',
51
56
  strong: [/\baccessib\w*\b/i, /\ba11y\b/i, /\baria\b/i, /\bfocus\s*trap\b/i, /\bkeyboard\s*nav/i, /\bwcag\b/i, /\bscreen\s*reader\b/i, /доступност/i, /скринридер/i],
52
57
  medium: [],
53
58
  weak: [],
@@ -55,6 +60,7 @@ const SKILL_RULES = [
55
60
  },
56
61
  {
57
62
  skill: 'impeccable-design',
63
+ category: 'ui',
58
64
  strong: [/\bvisual\s*qa\b/i, /\bmicro-?animation\b/i, /\bglassmorphism\b/i, /\btypography\b/i, /анимац/i, /полировк/i],
59
65
  medium: [/\bpolish\b/i],
60
66
  weak: [],
@@ -62,6 +68,7 @@ const SKILL_RULES = [
62
68
  },
63
69
  {
64
70
  skill: 'database',
71
+ category: 'intent',
65
72
  strong: [/\bdatabase\b/i, /\bsql\b/i, /\bpostgres(?:ql)?\b/i, /\bprisma\b/i, /\bdrizzle\b/i, /\bmigration\b/i, /\borm\b/i, /баз.*данн/i, /миграц/i, /таблиц/i],
66
73
  medium: [/\bschema\b/i],
67
74
  weak: [/\bquery\b/i, /\bindex(?:ing)?\b/i],
@@ -69,6 +76,7 @@ const SKILL_RULES = [
69
76
  },
70
77
  {
71
78
  skill: 'security',
79
+ category: 'intent',
72
80
  strong: [/\bauth\b/i, /\bjwt\b/i, /\blogin\b/i, /\bcsrf\b/i, /\bxss\b/i, /\brate\s*limit\b/i, /авториз/i, /аутентифик/i, /парол/i, /безопасност/i],
73
81
  medium: [/\bpermission\b/i, /\bsession\b/i, /\btoken\b/i, /токен/i],
74
82
  weak: [],
@@ -76,6 +84,7 @@ const SKILL_RULES = [
76
84
  },
77
85
  {
78
86
  skill: 'performance',
87
+ category: 'intent',
79
88
  strong: [/\bperformance\b/i, /\blatency\b/i, /\blcp\b/i, /\bcls\b/i, /\binp\b/i, /\bcore\s*web\s*vitals\b/i, /\bwaterfall\b/i, /\bbundle\s*size\b/i, /производительн/i, /ускор/i],
80
89
  medium: [/\boptimize\b/i, /\bslow\b/i, /оптимиз/i, /медленн/i],
81
90
  weak: [],
@@ -83,6 +92,7 @@ const SKILL_RULES = [
83
92
  },
84
93
  {
85
94
  skill: 'testing',
95
+ category: 'intent',
86
96
  strong: [/\bvitest\b/i, /\bjest\b/i, /\bplaywright\b/i, /\btdd\b/i, /\bbdd\b/i, /\be2e\b/i, /тестирован/i, /покрыти/i, /юнит/i],
87
97
  medium: [/\btest(?:s|ing)?\b/i, /\bmock\b/i, /тест/i],
88
98
  weak: [],
@@ -90,6 +100,7 @@ const SKILL_RULES = [
90
100
  },
91
101
  {
92
102
  skill: 'docker',
103
+ category: 'intent',
93
104
  strong: [/\bdocker\b/i, /\bdockerfile\b/i, /\bcompose\b/i, /\bkubernetes\b/i, /\bk8s\b/i, /докер/i],
94
105
  medium: [],
95
106
  weak: [/\bcontainer\b/i, /контейнер/i],
@@ -97,6 +108,7 @@ const SKILL_RULES = [
97
108
  },
98
109
  {
99
110
  skill: 'fastapi',
111
+ category: 'stack',
100
112
  strong: [/\bfastapi\b/i, /\bpydantic\b/i, /\buvicorn\b/i, /\bpytest\b/i, /питон/i],
101
113
  medium: [/\bpython\b/i],
102
114
  weak: [],
@@ -104,6 +116,7 @@ const SKILL_RULES = [
104
116
  },
105
117
  {
106
118
  skill: 'nestjs',
119
+ category: 'stack',
107
120
  strong: [/\bnestjs\b/i, /\b@nestjs\b/i, /нест/i],
108
121
  medium: [],
109
122
  weak: [/\bmodule\b/i, /\bcontroller\b/i, /\binjectable\b/i],
@@ -111,6 +124,7 @@ const SKILL_RULES = [
111
124
  },
112
125
  {
113
126
  skill: 'node',
127
+ category: 'stack',
114
128
  strong: [/\bnode(?:\.js)?\b/i, /\bexpress\b/i, /\bfastify\b/i],
115
129
  medium: [/\bbackend\b/i, /\bapi\s*route\b/i, /бэкенд/i, /эндпоинт/i],
116
130
  weak: [],
@@ -118,6 +132,7 @@ const SKILL_RULES = [
118
132
  },
119
133
  {
120
134
  skill: 'ddd',
135
+ category: 'intent',
121
136
  strong: [/\bddd\b/i, /\bdomain-?driven\b/i, /\baggregate\b/i, /\bvalue\s*object\b/i, /\bbounded\s*context\b/i],
122
137
  medium: [/\bentity\b/i, /домен/i, /агрегат/i],
123
138
  weak: [],
@@ -125,6 +140,7 @@ const SKILL_RULES = [
125
140
  },
126
141
  {
127
142
  skill: 'microservices',
143
+ category: 'intent',
128
144
  strong: [/\bmicroservices?\b/i, /\bevent-?driven\b/i, /\brabbitmq\b/i, /\bkafka\b/i, /\bgrpc\b/i, /микросервис/i],
129
145
  medium: [/\bpub\/?sub\b/i, /очеред/i],
130
146
  weak: [],
@@ -132,6 +148,7 @@ const SKILL_RULES = [
132
148
  },
133
149
  {
134
150
  skill: 'system-design',
151
+ category: 'intent',
135
152
  strong: [/\bsystem\s*design\b/i, /\bhigh\s*concurrency\b/i, /\bcircuit\s*breaker\b/i, /масштабируем/i],
136
153
  medium: [/\barchitecture\b/i, /\bscalab(?:le|ility)\b/i, /архитектур/i],
137
154
  weak: [],
@@ -139,6 +156,7 @@ const SKILL_RULES = [
139
156
  },
140
157
  {
141
158
  skill: 'graphify',
159
+ category: 'intent',
142
160
  strong: [/\bgraphify\b/i, /\bknowledge\s*graph\b/i, /\bcodebase\s*graph\b/i, /\bproject\s*graph\b/i, /\bblast\s*radius\b/i, /\bmap\s*(?:the\s*)?codebase\b/i, /граф\s*проект/i, /граф\s*зависимост/i],
143
161
  medium: [],
144
162
  weak: [],
@@ -146,6 +164,7 @@ const SKILL_RULES = [
146
164
  },
147
165
  {
148
166
  skill: 'architecture-diagrams',
167
+ category: 'intent',
149
168
  strong: [/\bflowchart\b/i, /\bsequence\s*diagram\b/i],
150
169
  medium: [/\bdiagrams?\b/i, /диаграмм/i, /схем/i, /нарисуй/i],
151
170
  weak: [],
@@ -153,6 +172,7 @@ const SKILL_RULES = [
153
172
  },
154
173
  {
155
174
  skill: 'subagent-orchestrator',
175
+ category: 'intent',
156
176
  strong: [/\bsubagent\b/i, /\bparallel\s*tasks\b/i, /субагент/i, /распараллел/i],
157
177
  medium: [/\bdelegate\b/i, /делегируй/i],
158
178
  weak: [],
@@ -160,6 +180,7 @@ const SKILL_RULES = [
160
180
  },
161
181
  {
162
182
  skill: 'interview-me',
183
+ category: 'intent',
163
184
  strong: [/\binterview\b/i, /интервью/i, /расспроси/i],
164
185
  medium: [/\bclarify\b/i, /\bquestions\b/i, /уточни\b/i],
165
186
  weak: [],
@@ -335,72 +356,171 @@ function analyzeImportGraph(projectDir = process.cwd()) {
335
356
  */
336
357
  function resolveSkills({ prompt = '', files = [], phase = 'Build', domain = '', projectDir = process.cwd() } = {}) {
337
358
  const promptText = (prompt || '').toLowerCase();
338
- const scores = new Map();
359
+ const ruleMap = new Map();
360
+ const promptScores = new Map();
361
+ const fileScores = new Map();
339
362
 
340
363
  for (const rule of SKILL_RULES) {
341
- let score = 0;
364
+ ruleMap.set(rule.skill, rule);
365
+ let pScore = 0;
366
+ let fScore = 0;
342
367
 
343
368
  // Strong triggers (10 points each match)
344
369
  for (const pat of rule.strong || []) {
345
- if (pat.test(promptText)) score += 10;
370
+ if (pat.test(promptText)) pScore += 10;
346
371
  }
347
372
 
348
373
  // Medium triggers (5 points each match)
349
374
  for (const pat of rule.medium || []) {
350
- if (pat.test(promptText)) score += 5;
375
+ if (pat.test(promptText)) pScore += 5;
351
376
  }
352
377
 
353
378
  // Weak triggers (2 points each match)
354
379
  for (const pat of rule.weak || []) {
355
- if (pat.test(promptText)) score += 2;
380
+ if (pat.test(promptText)) pScore += 2;
356
381
  }
357
382
 
358
383
  // File matches (10 points per matching file)
359
384
  for (const file of files) {
360
385
  const normalized = file.replace(/\\/g, '/');
361
386
  for (const pat of rule.fileGlobs || []) {
362
- if (pat.test(normalized)) score += 10;
387
+ if (pat.test(normalized)) fScore += 10;
363
388
  }
364
389
  }
365
390
 
366
- // Minimum score threshold for domain skill activation
367
- if (score >= 5) {
368
- scores.set(rule.skill, score);
369
- }
391
+ if (pScore > 0) promptScores.set(rule.skill, pScore);
392
+ if (fScore > 0) fileScores.set(rule.skill, fScore);
370
393
  }
371
394
 
372
395
  // Phase 2: Merge AST & Project Dependency Graph Signals
373
- if (projectDir) {
374
- const astSignals = analyzeImportGraph(projectDir);
375
- for (const [skill, astScore] of astSignals) {
376
- scores.set(skill, (scores.get(skill) || 0) + astScore);
396
+ const astSignals = projectDir ? analyzeImportGraph(projectDir) : new Map();
397
+
398
+ // Active Profile Resolution Enforcement
399
+ const profiles = require('./profiles.js');
400
+ const activeProfile = projectDir ? profiles.getActiveProfile(projectDir) : null;
401
+ const profileExcluded = new Set(activeProfile?.exclude_skills || []);
402
+ const profilePreferred = new Set(activeProfile?.prefer_skills || []);
403
+ const profileEnforce = activeProfile?.enforce || {};
404
+ const profileDefaults = activeProfile?.defaults || {};
405
+
406
+ const MAX_DOMAIN_SKILLS = 4;
407
+ const selectedSkills = [];
408
+
409
+ // 1. INTENT SLOTS & INTENT PRECEDENCE (Immunity against eviction)
410
+ // Direct user intent in prompt MUST NOT be evicted by ambient stack/file signals.
411
+ // Profile-excluded skills are strictly omitted from candidate intents.
412
+ const candidateIntents = [];
413
+ for (const rule of SKILL_RULES) {
414
+ const s = rule.skill;
415
+ if (profileExcluded.has(s)) continue;
416
+
417
+ const p = promptScores.get(s) || 0;
418
+ const f = fileScores.get(s) || 0;
419
+ if (rule.category === 'intent' && (p >= 5 || (p > 0 && f >= 10))) {
420
+ candidateIntents.push({ skill: s, score: p * 2 + f, priority: 2 });
421
+ } else if (p >= 10) {
422
+ candidateIntents.push({ skill: s, score: p, priority: 1 });
423
+ }
424
+ }
425
+
426
+ candidateIntents.sort((a, b) => b.priority - a.priority || b.score - a.score);
427
+
428
+ // Reserve up to 2 guaranteed intent slots for directly requested capabilities
429
+ for (const item of candidateIntents) {
430
+ if (selectedSkills.length < 2 && !selectedSkills.includes(item.skill)) {
431
+ selectedSkills.push(item.skill);
432
+ }
433
+ }
434
+
435
+ // 2. CONTEXT SUPPRESSION
436
+ // If task is purely infrastructure (e.g. docker) or pure backend/architecture,
437
+ // suppress ambient UI skills from AST to avoid polluting non-UI tasks.
438
+ const hasPureInfraIntent = selectedSkills.includes('docker');
439
+ const hasBackendOnlyIntent = selectedSkills.some(s => ['database', 'fastapi', 'nestjs', 'ddd'].includes(s)) &&
440
+ !files.some(f => /\.(tsx|jsx|css|scss|html)$/.test(f)) &&
441
+ !/\b(ui|react|css|tailwind|frontend|макет|дизайн|кнопк|стил|компонент)/i.test(promptText);
442
+
443
+ const suppressAmbientUI = hasPureInfraIntent || hasBackendOnlyIntent;
444
+
445
+ // 3. REMAINING SLOTS: Rank candidate skills with user-intent weighting and profile preferences
446
+ const candidateScores = new Map();
447
+ for (const rule of SKILL_RULES) {
448
+ const s = rule.skill;
449
+ if (selectedSkills.includes(s) || profileExcluded.has(s)) continue;
450
+
451
+ const p = promptScores.get(s) || 0;
452
+ const f = fileScores.get(s) || 0;
453
+ const a = astSignals.get(s) || 0;
454
+
455
+ // If ambient UI is suppressed, skip UI skills unless explicitly requested in prompt
456
+ if (suppressAmbientUI && (rule.category === 'ui' || s === 'react') && p < 5 && f === 0) {
457
+ continue;
458
+ }
459
+
460
+ // Require minimum unweighted threshold of 5 points to filter casual weak mentions,
461
+ // then apply multiplier for strong/medium prompt matches to prioritize user intent over ambient signals.
462
+ const rawSum = p + f + a;
463
+ if (rawSum >= 5 || profilePreferred.has(s)) {
464
+ let weightedScore = (p >= 5 ? p * 3 : p) + f + a;
465
+ if (profilePreferred.has(s)) {
466
+ weightedScore += 20; // Profile prefer boost
467
+ }
468
+ candidateScores.set(s, weightedScore);
377
469
  }
378
470
  }
379
471
 
380
- // Sort matched domain skills by score descending
381
- const sortedDomainSkills = Array.from(scores.entries())
472
+ const remainingSorted = Array.from(candidateScores.entries())
382
473
  .sort((a, b) => b[1] - a[1])
383
474
  .map(([skill]) => skill);
384
475
 
385
- // Cap domain skills to top 4 max to prevent context bloat
386
- const MAX_DOMAIN_SKILLS = 4;
387
- const topDomainSkills = sortedDomainSkills.slice(0, MAX_DOMAIN_SKILLS);
476
+ for (const s of remainingSorted) {
477
+ if (selectedSkills.length >= MAX_DOMAIN_SKILLS) break;
478
+ if (!selectedSkills.includes(s)) {
479
+ selectedSkills.push(s);
480
+ }
481
+ }
482
+
483
+ // 4. Synergy rules (from AGENTS.md matrix)
484
+ const hasSynergyPrereq = selectedSkills.includes('database') ||
485
+ selectedSkills.includes('microservices') ||
486
+ selectedSkills.includes('ddd') ||
487
+ selectedSkills.includes('graphify');
488
+
489
+ if (hasSynergyPrereq && !selectedSkills.includes('system-design') && !profileExcluded.has('system-design')) {
490
+ if (selectedSkills.length < MAX_DOMAIN_SKILLS + 1) {
491
+ selectedSkills.push('system-design');
492
+ }
493
+ }
388
494
 
389
- // Synergy rules (from AGENTS.md matrix)
390
- const hasSynergyPrereq = topDomainSkills.includes('database') ||
391
- topDomainSkills.includes('microservices') ||
392
- topDomainSkills.includes('ddd') ||
393
- topDomainSkills.includes('graphify');
495
+ // 5. Enforce profile invariants (e.g. enterprise or startup compliance)
496
+ const normPhase = (phase || '').toLowerCase();
497
+ if (profileEnforce.testing === true && ['build', 'verify', 'test'].includes(normPhase)) {
498
+ if (!selectedSkills.includes('testing') && !profileExcluded.has('testing')) {
499
+ selectedSkills.push('testing');
500
+ }
501
+ }
502
+
503
+ if (profileEnforce.security_audit === true && (selectedSkills.includes('database') || selectedSkills.includes('node') || selectedSkills.includes('fastapi') || selectedSkills.includes('nestjs'))) {
504
+ if (!selectedSkills.includes('security') && !profileExcluded.has('security')) {
505
+ selectedSkills.push('security');
506
+ }
507
+ }
508
+
509
+ if (profileEnforce.adr === true && (normPhase === 'plan' || selectedSkills.includes('system-design') || selectedSkills.includes('microservices'))) {
510
+ if (!selectedSkills.includes('decisions') && !profileExcluded.has('decisions')) {
511
+ selectedSkills.push('decisions');
512
+ }
513
+ }
394
514
 
395
- if (hasSynergyPrereq && !topDomainSkills.includes('system-design')) {
396
- if (topDomainSkills.length < MAX_DOMAIN_SKILLS + 1) {
397
- topDomainSkills.push('system-design');
515
+ if (profileEnforce.accessibility_audit === true && (selectedSkills.includes('react') || selectedSkills.includes('ui-ux-pro'))) {
516
+ if (!selectedSkills.includes('web-accessibility') && !profileExcluded.has('web-accessibility')) {
517
+ selectedSkills.push('web-accessibility');
398
518
  }
399
519
  }
400
520
 
401
- // Foundational skills (always active)
402
- const allSkills = ['ponytail-mindset', 'engineering-workflow', ...topDomainSkills];
403
- const finalSkills = Array.from(new Set(allSkills));
521
+ // Foundational skills (always active unless explicitly excluded)
522
+ const allSkills = ['ponytail-mindset', 'engineering-workflow', ...selectedSkills];
523
+ const finalSkills = Array.from(new Set(allSkills)).filter(s => !profileExcluded.has(s));
404
524
 
405
525
  // Infer Domain if not specified
406
526
  let inferredDomain = domain;
package/.agents/stats.js CHANGED
@@ -64,8 +64,18 @@ function calculateContextStats(projectDir = process.cwd()) {
64
64
  }
65
65
  }
66
66
 
67
- // Resolved context: 2 foundational skills + average 2 domain skills (e.g. react + ui-ux-pro)
68
- const typicalResolvedSkills = ['engineering-workflow', 'ponytail-mindset', 'react', 'ui-ux-pro'];
67
+ // Resolved context: dynamically resolved skills for this project's stack
68
+ let typicalResolvedSkills = ['engineering-workflow', 'ponytail-mindset'];
69
+ try {
70
+ const resolver = require('./resolver.js');
71
+ const resolved = resolver.resolveSkills({ prompt: 'Implement standard feature', projectDir });
72
+ if (resolved && Array.isArray(resolved.skills)) {
73
+ typicalResolvedSkills = resolved.skills;
74
+ }
75
+ } catch (_) {
76
+ typicalResolvedSkills = ['engineering-workflow', 'ponytail-mindset', 'react', 'ui-ux-pro'];
77
+ }
78
+
69
79
  let resolvedChars = agentsMdChars;
70
80
  let resolvedSkillCount = 0;
71
81
  for (const s of typicalResolvedSkills) {
@@ -86,6 +96,7 @@ function calculateContextStats(projectDir = process.cwd()) {
86
96
  totalSkillCount,
87
97
  profiledSkillCount,
88
98
  resolvedSkillCount,
99
+ resolvedSkillList: typicalResolvedSkills,
89
100
  profileName,
90
101
  full: { chars: fullChars, tokens: fullTokens },
91
102
  profile: { chars: profileChars, tokens: profileTokens, savings: profileSavings },
@@ -96,28 +107,30 @@ function calculateContextStats(projectDir = process.cwd()) {
96
107
  function runStats(projectDir = process.cwd()) {
97
108
  const stats = calculateContextStats(projectDir);
98
109
 
99
- console.log('\nContextOS — Context Savings Report\n');
110
+ console.log('\nContextOS — Context Savings Report & Payload Reduction\n');
100
111
  console.log('┌──────────────────────────────────────┬─────────────┬──────────┐');
101
- console.log('│ Mode │ Tokens │ Savings │');
112
+ console.log('│ Context Mode │ Est. Tokens │ Savings │');
102
113
  console.log('├──────────────────────────────────────┼─────────────┼──────────┤');
103
114
 
104
- const fullLabel = `Full (${stats.totalSkillCount} skills)`;
115
+ const fullLabel = `Full (${stats.totalSkillCount} skills catalog)`;
105
116
  const fullTok = stats.full.tokens.toLocaleString().padStart(9);
106
- console.log(`│ ${fullLabel.padEnd(36)} │ ${fullTok} │ — │`);
117
+ console.log(`│ ${fullLabel.slice(0, 36).padEnd(36)} │ ${fullTok} │ baseline │`);
107
118
 
108
119
  const profLabel = `Profile: ${stats.profileName} (${stats.profiledSkillCount} skills)`;
109
120
  const profTok = stats.profile.tokens.toLocaleString().padStart(9);
110
121
  const profSav = `${stats.profile.savings}%`.padStart(7);
111
122
  console.log(`│ ${profLabel.slice(0, 36).padEnd(36)} │ ${profTok} │ ${profSav} │`);
112
123
 
113
- const resLabel = `Resolved: typical task (${stats.resolvedSkillCount} skills)`;
124
+ const resLabel = `Dynamic: resolved task (${stats.resolvedSkillCount} skills)`;
114
125
  const resTok = stats.resolved.tokens.toLocaleString().padStart(9);
115
126
  const resSav = `${stats.resolved.savings}%`.padStart(7);
116
127
  console.log(`│ ${resLabel.slice(0, 36).padEnd(36)} │ ${resTok} │ ${resSav} │`);
117
128
 
118
129
  console.log('└──────────────────────────────────────┴─────────────┴──────────┘');
119
- console.log('\nToken estimation: ~4 chars/token (GPT-4 / Claude / Gemini approximation)');
120
- console.log('Dynamic skill resolution prevents prompt bloat and cuts LLM API costs.\n');
130
+ console.log(`Resolved skills for project: ${stats.resolvedSkillList.join(', ')}`);
131
+ console.log('\nNote: Character-based token estimation (~4 chars/token heuristic).');
132
+ console.log('Measures static rule payload reduction against an uncurated full-catalog dump.');
133
+ console.log('Real workflow cost savings depend on prompt turns, model tokenizer, and task complexity.\n');
121
134
 
122
135
  return stats;
123
136
  }
@@ -17,15 +17,26 @@ const fs = require('fs');
17
17
  const path = require('path');
18
18
  const { collectAllSkillDirs } = require('./plugins.js');
19
19
 
20
- // ── Paths ─────────────────────────────────────────────────────────────────────
21
20
  const ROOT = process.cwd();
22
21
  const AGENTS_DIR = path.join(ROOT, '.agents');
23
22
  const CORE_SKILLS = path.join(AGENTS_DIR, 'core', 'skills');
23
+ const CORE_PROFILES = path.join(AGENTS_DIR, 'core', 'profiles');
24
24
  const GENERATED = path.join(AGENTS_DIR, 'generated');
25
25
 
26
26
  // Adapters that compile to generated/<name>/skills/
27
27
  const COMPILED_ADAPTERS = ['gemini', 'claude'];
28
28
 
29
+ // Well-known external frameworks/tools valid in conflicts/optional/profiles but not internal ContextOS skills
30
+ const EXTERNAL_NAMES = new Set([
31
+ 'vue', 'angular', 'svelte', 'remix', 'express', 'koa', 'hapi', 'sails',
32
+ 'tailwind', 'prisma', 'drizzle', 'mongoose', 'sequelize', 'typeorm',
33
+ 'next-auth', 'react-query', 'zustand', 'redux', 'mobx', 'jotai', 'valtio',
34
+ 'vitest', 'jest', 'mocha', 'cypress', 'playwright', 'storybook',
35
+ 'graphql', 'trpc', 'apollo', 'relay', 'urql',
36
+ 'sqlite', 'simple-auth', 'kubernetes', 'monitoring', 'cicd', 'cqrs',
37
+ 'redis', 'postgres', 'postgresql', 'jwt', 'auth'
38
+ ]);
39
+
29
40
  // ── Tiny ANSI helpers (no deps) ───────────────────────────────────────────────
30
41
  const NO_COLOR = process.env.NO_COLOR || !process.stdout.isTTY;
31
42
  const c = {
@@ -68,7 +79,7 @@ function yamlList(text, field) {
68
79
  const re = new RegExp(`^${field}:\\s*\\[([^\\]]*)]`, 'm');
69
80
  const m = text.match(re);
70
81
  if (!m || !m[1].trim()) return [];
71
- return m[1].split(',').map(s => s.trim()).filter(Boolean);
82
+ return m[1].split(',').map(s => s.replace(/\s*#.*$/, '').trim()).filter(Boolean);
72
83
  }
73
84
 
74
85
  /**
@@ -83,7 +94,7 @@ function yamlBlockList(text, field) {
83
94
  if (!m) return [];
84
95
  return m[1]
85
96
  .split('\n')
86
- .map(l => l.replace(/^[ \t]+-\s*/, '').trim())
97
+ .map(l => l.replace(/^[ \t]+-\s*/, '').replace(/\s*#.*$/, '').trim())
87
98
  .filter(Boolean);
88
99
  }
89
100
 
@@ -102,6 +113,8 @@ function parseSkillYaml(yamlText) {
102
113
  name: yamlField(yamlText, 'name'),
103
114
  description: yamlField(yamlText, 'description'),
104
115
  version: yamlField(yamlText, 'version'),
116
+ type: yamlField(yamlText, 'type'),
117
+ resources: yamlList(yamlText, 'resources').concat(yamlBlockList(yamlText, 'resources')),
105
118
  requires: yamlList(yamlText, 'requires').concat(yamlBlockList(yamlText, 'requires')),
106
119
  conflicts: yamlList(yamlText, 'conflicts').concat(yamlBlockList(yamlText, 'conflicts')),
107
120
  optional: yamlList(yamlText, 'optional').concat(yamlBlockList(yamlText, 'optional')),
@@ -204,6 +217,12 @@ function checkSkillYaml(sourceSkills) {
204
217
  error(`[yaml] ${name}/skill.yaml — missing 'name:' field`);
205
218
  continue;
206
219
  }
220
+ const VALID_TYPES = new Set(['instruction-only', 'compiler', 'runtime', 'experimental']);
221
+ if (!parsed.type) {
222
+ error(`[yaml] ${name}/skill.yaml — missing 'type:' field (expected instruction-only | compiler | runtime | experimental)`);
223
+ } else if (!VALID_TYPES.has(parsed.type)) {
224
+ error(`[yaml] ${name}/skill.yaml — invalid type '${parsed.type}' (expected instruction-only | compiler | runtime | experimental)`);
225
+ }
207
226
  if (false && !parsed.description) {
208
227
  // Some skills use block scalars — allow it, just warn
209
228
  warn(`[yaml] ${name}/skill.yaml — 'description:' not found or uses multiline block (check manually)`);
@@ -284,16 +303,6 @@ function checkDependencies(sourceSkills) {
284
303
  }
285
304
  }
286
305
 
287
- // externalNames: well-known external frameworks/tools that are valid in
288
- // conflicts:/optional: but are NOT ContextOS skills. We never error on these.
289
- const EXTERNAL_NAMES = new Set([
290
- 'vue', 'angular', 'svelte', 'remix', 'express', 'koa', 'hapi', 'sails',
291
- 'tailwind', 'prisma', 'drizzle', 'mongoose', 'sequelize', 'typeorm',
292
- 'next-auth', 'react-query', 'zustand', 'redux', 'mobx', 'jotai', 'valtio',
293
- 'vitest', 'jest', 'mocha', 'cypress', 'playwright', 'storybook',
294
- 'graphql', 'trpc', 'apollo', 'relay', 'urql',
295
- ]);
296
-
297
306
  let depChecked = 0;
298
307
 
299
308
  for (const [name, skill] of Object.entries(sourceSkills)) {
@@ -414,6 +423,133 @@ function checkValidationJson(sourceSkills) {
414
423
  info(`[validation] ${pass} VALIDATION.json files are valid`);
415
424
  }
416
425
 
426
+ // ═════════════════════════════════════════════════════════════════════════════
427
+ // CHECK 9 — MCP Bundle Synchronization
428
+ // ═════════════════════════════════════════════════════════════════════════════
429
+ function checkMcpBundleSync() {
430
+ const mcpSrcDir = path.join(ROOT, 'contextos-mcp', 'src');
431
+ const bundlePath = path.join(AGENTS_DIR, 'mcp', 'server.mjs');
432
+
433
+ if (!fs.existsSync(mcpSrcDir)) {
434
+ return;
435
+ }
436
+
437
+ if (!fs.existsSync(bundlePath)) {
438
+ warn(`[mcp-sync] Missing compiled MCP server bundle at .agents/mcp/server.mjs. Run: cd contextos-mcp && npm run build:bundle`);
439
+ return;
440
+ }
441
+
442
+ const bundleStat = fs.statSync(bundlePath);
443
+ const bundleMtime = bundleStat.mtimeMs;
444
+
445
+ let newestSrcFile = null;
446
+ let newestSrcMtime = 0;
447
+
448
+ function walk(dir) {
449
+ let entries;
450
+ try {
451
+ entries = fs.readdirSync(dir, { withFileTypes: true });
452
+ } catch {
453
+ return;
454
+ }
455
+ for (const entry of entries) {
456
+ const full = path.join(dir, entry.name);
457
+ if (entry.isDirectory()) {
458
+ walk(full);
459
+ } else if (entry.isFile()) {
460
+ const stat = fs.statSync(full);
461
+ if (stat.mtimeMs > newestSrcMtime) {
462
+ newestSrcMtime = stat.mtimeMs;
463
+ newestSrcFile = path.relative(ROOT, full);
464
+ }
465
+ }
466
+ }
467
+ }
468
+
469
+ walk(mcpSrcDir);
470
+
471
+ if (newestSrcMtime > bundleMtime) {
472
+ warn(`[mcp-sync] Compiled MCP bundle (.agents/mcp/server.mjs) is older than source file ${newestSrcFile}. Run: cd contextos-mcp && npm run build:bundle`);
473
+ } else {
474
+ info(`[mcp-sync] Compiled MCP bundle (.agents/mcp/server.mjs) is up to date with contextos-mcp/src/`);
475
+ }
476
+ }
477
+
478
+ // ═════════════════════════════════════════════════════════════════════════════
479
+ // CHECK 10 — Resources Manifest Integrity
480
+ // ═════════════════════════════════════════════════════════════════════════════
481
+ function checkResourcesManifest(sourceSkills) {
482
+ let totalResources = 0;
483
+ let skillsWithResources = 0;
484
+
485
+ for (const [name, skill] of Object.entries(sourceSkills)) {
486
+ if (!skill.hasYaml) continue;
487
+
488
+ const text = fs.readFileSync(skill.yamlPath, 'utf8');
489
+ const parsed = parseSkillYaml(text);
490
+
491
+ if (!parsed.resources || parsed.resources.length === 0) {
492
+ warn(`[resources] ${name}/skill.yaml — no resource files declared in manifest`);
493
+ continue;
494
+ }
495
+
496
+ skillsWithResources++;
497
+ for (const res of parsed.resources) {
498
+ const fullPath = path.join(skill.dir, res);
499
+ if (!fs.existsSync(fullPath)) {
500
+ error(`[resources] ${name}/skill.yaml — declared resource not found on disk: '${res}'`);
501
+ } else {
502
+ totalResources++;
503
+ }
504
+ }
505
+ }
506
+
507
+ info(`[resources] ${totalResources} resource files verified across ${skillsWithResources} skills`);
508
+ }
509
+
510
+ // ═════════════════════════════════════════════════════════════════════════════
511
+ // CHECK 11 — Profiles Integrity
512
+ // ═════════════════════════════════════════════════════════════════════════════
513
+ function checkProfilesIntegrity(sourceSkills) {
514
+ if (!fs.existsSync(CORE_PROFILES)) return;
515
+
516
+ const knownIds = new Set();
517
+ for (const [name, skill] of Object.entries(sourceSkills)) {
518
+ knownIds.add(name);
519
+ if (skill.hasYaml) {
520
+ const text = fs.readFileSync(skill.yamlPath, 'utf8');
521
+ const id = yamlField(text, 'id');
522
+ if (id) knownIds.add(id);
523
+ }
524
+ }
525
+
526
+ let profilesValidated = 0;
527
+ const files = fs.readdirSync(CORE_PROFILES).filter(f => f.endsWith('.yaml') || f.endsWith('.yml'));
528
+
529
+ for (const file of files) {
530
+ const filePath = path.join(CORE_PROFILES, file);
531
+ const text = fs.readFileSync(filePath, 'utf8');
532
+
533
+ const referenced = [
534
+ ...yamlList(text, 'skills'),
535
+ ...yamlBlockList(text, 'skills'),
536
+ ...yamlList(text, 'prefer_skills'),
537
+ ...yamlBlockList(text, 'prefer_skills'),
538
+ ...yamlList(text, 'exclude_skills'),
539
+ ...yamlBlockList(text, 'exclude_skills'),
540
+ ];
541
+
542
+ for (const skillName of referenced) {
543
+ if (!knownIds.has(skillName) && !EXTERNAL_NAMES.has(skillName)) {
544
+ error(`[profiles] ${file} — references unknown skill '${skillName}'`);
545
+ }
546
+ }
547
+ profilesValidated++;
548
+ }
549
+
550
+ info(`[profiles] ${profilesValidated} core profiles validated against skill registry`);
551
+ }
552
+
417
553
  // ═════════════════════════════════════════════════════════════════════════════
418
554
  // REPORT
419
555
  // ═════════════════════════════════════════════════════════════════════════════
@@ -487,6 +623,9 @@ function run() {
487
623
  checkContentQuality(sourceSkills);
488
624
  checkCostarHeaders(sourceSkills);
489
625
  checkValidationJson(sourceSkills);
626
+ checkMcpBundleSync();
627
+ checkResourcesManifest(sourceSkills);
628
+ checkProfilesIntegrity(sourceSkills);
490
629
 
491
630
  const passed = printReport();
492
631
  process.exit(passed ? 0 : 1);