contextos-agents 1.5.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/AGENTS.md +421 -0
- package/.agents/adapters/aider/export.js +137 -0
- package/.agents/adapters/claude/export.js +55 -0
- package/.agents/adapters/copilot/export.js +107 -0
- package/.agents/adapters/cursor/export.js +210 -0
- package/.agents/adapters/gemini/export.js +115 -0
- package/.agents/adapters/shared.js +119 -0
- package/.agents/adapters/zed/export.js +111 -0
- package/.agents/core/profiles/backend.yaml +47 -0
- package/.agents/core/profiles/enterprise.yaml +46 -0
- package/.agents/core/profiles/frontend.yaml +46 -0
- package/.agents/core/profiles/hackathon.yaml +45 -0
- package/.agents/core/profiles/mvp.yaml +43 -0
- package/.agents/core/profiles/startup.yaml +48 -0
- package/.agents/core/skills/adapters/EXAMPLES.md +19 -0
- package/.agents/core/skills/adapters/SKILL.md +105 -0
- package/.agents/core/skills/adapters/TROUBLESHOOTING.md +7 -0
- package/.agents/core/skills/adapters/VALIDATION.json +12 -0
- package/.agents/core/skills/adapters/skill.yaml +10 -0
- package/.agents/core/skills/architecture-diagrams/SKILL.md +108 -0
- package/.agents/core/skills/architecture-diagrams/VALIDATION.json +12 -0
- package/.agents/core/skills/architecture-diagrams/skill.yaml +8 -0
- package/.agents/core/skills/brutalist-design/SKILL.md +150 -0
- package/.agents/core/skills/brutalist-design/VALIDATION.json +12 -0
- package/.agents/core/skills/brutalist-design/skill.yaml +8 -0
- package/.agents/core/skills/context-manager/EXAMPLES.md +19 -0
- package/.agents/core/skills/context-manager/SKILL.md +126 -0
- package/.agents/core/skills/context-manager/TROUBLESHOOTING.md +7 -0
- package/.agents/core/skills/context-manager/VALIDATION.json +12 -0
- package/.agents/core/skills/context-manager/skill.yaml +10 -0
- package/.agents/core/skills/context-os/EXAMPLES.md +21 -0
- package/.agents/core/skills/context-os/SKILL.md +169 -0
- package/.agents/core/skills/context-os/TROUBLESHOOTING.md +7 -0
- package/.agents/core/skills/context-os/VALIDATION.json +12 -0
- package/.agents/core/skills/context-os/packs.yaml +59 -0
- package/.agents/core/skills/context-os/references/context-rules.md +68 -0
- package/.agents/core/skills/context-os/references/pipeline.md +119 -0
- package/.agents/core/skills/context-os/references/project-graph.md +103 -0
- package/.agents/core/skills/context-os/rules.yaml +135 -0
- package/.agents/core/skills/context-os/skill.yaml +10 -0
- package/.agents/core/skills/database/EXAMPLES.md +74 -0
- package/.agents/core/skills/database/SKILL.md +101 -0
- package/.agents/core/skills/database/TROUBLESHOOTING.md +18 -0
- package/.agents/core/skills/database/VALIDATION.json +11 -0
- package/.agents/core/skills/database/skill.yaml +25 -0
- package/.agents/core/skills/ddd/EXAMPLES.md +42 -0
- package/.agents/core/skills/ddd/SKILL.md +247 -0
- package/.agents/core/skills/ddd/TROUBLESHOOTING.md +19 -0
- package/.agents/core/skills/ddd/VALIDATION.json +12 -0
- package/.agents/core/skills/ddd/ddd.md +178 -0
- package/.agents/core/skills/ddd/skill.yaml +10 -0
- package/.agents/core/skills/decisions/EXAMPLES.md +35 -0
- package/.agents/core/skills/decisions/SKILL.md +90 -0
- package/.agents/core/skills/decisions/TROUBLESHOOTING.md +13 -0
- package/.agents/core/skills/decisions/VALIDATION.json +12 -0
- package/.agents/core/skills/decisions/skill.yaml +10 -0
- package/.agents/core/skills/docker/EXAMPLES.md +56 -0
- package/.agents/core/skills/docker/SKILL.md +63 -0
- package/.agents/core/skills/docker/TROUBLESHOOTING.md +18 -0
- package/.agents/core/skills/docker/VALIDATION.json +11 -0
- package/.agents/core/skills/docker/skill.yaml +23 -0
- package/.agents/core/skills/engineering-workflow/EXAMPLES.md +57 -0
- package/.agents/core/skills/engineering-workflow/SKILL.md +343 -0
- package/.agents/core/skills/engineering-workflow/TROUBLESHOOTING.md +19 -0
- package/.agents/core/skills/engineering-workflow/VALIDATION.json +12 -0
- package/.agents/core/skills/engineering-workflow/skill.yaml +13 -0
- package/.agents/core/skills/fastapi/EXAMPLES.md +36 -0
- package/.agents/core/skills/fastapi/SKILL.md +148 -0
- package/.agents/core/skills/fastapi/TROUBLESHOOTING.md +19 -0
- package/.agents/core/skills/fastapi/VALIDATION.json +12 -0
- package/.agents/core/skills/fastapi/fastapi.md +112 -0
- package/.agents/core/skills/fastapi/skill.yaml +10 -0
- package/.agents/core/skills/gemini-precision/SKILL.md +169 -0
- package/.agents/core/skills/gemini-precision/VALIDATION.json +12 -0
- package/.agents/core/skills/gemini-precision/skill.yaml +9 -0
- package/.agents/core/skills/generators/EXAMPLES.md +19 -0
- package/.agents/core/skills/generators/SKILL.md +112 -0
- package/.agents/core/skills/generators/TROUBLESHOOTING.md +7 -0
- package/.agents/core/skills/generators/VALIDATION.json +12 -0
- package/.agents/core/skills/generators/skill.yaml +10 -0
- package/.agents/core/skills/generators/templates/API.md +77 -0
- package/.agents/core/skills/generators/templates/ARCHITECTURE.md +70 -0
- package/.agents/core/skills/generators/templates/DATABASE.md +42 -0
- package/.agents/core/skills/generators/templates/DECISION.md +46 -0
- package/.agents/core/skills/generators/templates/PRD.md +67 -0
- package/.agents/core/skills/generators/templates/PROJECT_GRAPH.md +56 -0
- package/.agents/core/skills/generators/templates/ROADMAP.md +51 -0
- package/.agents/core/skills/generators/templates/TASKS.md +43 -0
- package/.agents/core/skills/generators/templates/UI.md +73 -0
- package/.agents/core/skills/graphify/EXAMPLES.md +73 -0
- package/.agents/core/skills/graphify/SKILL.md +130 -0
- package/.agents/core/skills/graphify/VALIDATION.json +12 -0
- package/.agents/core/skills/graphify/skill.yaml +13 -0
- package/.agents/core/skills/gstack-roles/EXAMPLES.md +23 -0
- package/.agents/core/skills/gstack-roles/SKILL.md +152 -0
- package/.agents/core/skills/gstack-roles/TROUBLESHOOTING.md +13 -0
- package/.agents/core/skills/gstack-roles/VALIDATION.json +12 -0
- package/.agents/core/skills/gstack-roles/skill.yaml +12 -0
- package/.agents/core/skills/impeccable-design/EXAMPLES.md +26 -0
- package/.agents/core/skills/impeccable-design/SKILL.md +201 -0
- package/.agents/core/skills/impeccable-design/TROUBLESHOOTING.md +19 -0
- package/.agents/core/skills/impeccable-design/VALIDATION.json +12 -0
- package/.agents/core/skills/impeccable-design/skill.yaml +14 -0
- package/.agents/core/skills/interview-me/SKILL.md +97 -0
- package/.agents/core/skills/interview-me/VALIDATION.json +12 -0
- package/.agents/core/skills/interview-me/skill.yaml +8 -0
- package/.agents/core/skills/microservices/EXAMPLES.md +38 -0
- package/.agents/core/skills/microservices/SKILL.md +164 -0
- package/.agents/core/skills/microservices/TROUBLESHOOTING.md +19 -0
- package/.agents/core/skills/microservices/VALIDATION.json +12 -0
- package/.agents/core/skills/microservices/microservices.md +119 -0
- package/.agents/core/skills/microservices/skill.yaml +10 -0
- package/.agents/core/skills/minimalist-design/SKILL.md +113 -0
- package/.agents/core/skills/minimalist-design/VALIDATION.json +12 -0
- package/.agents/core/skills/minimalist-design/skill.yaml +8 -0
- package/.agents/core/skills/nestjs/EXAMPLES.md +40 -0
- package/.agents/core/skills/nestjs/SKILL.md +139 -0
- package/.agents/core/skills/nestjs/TROUBLESHOOTING.md +19 -0
- package/.agents/core/skills/nestjs/VALIDATION.json +12 -0
- package/.agents/core/skills/nestjs/nestjs.md +103 -0
- package/.agents/core/skills/nestjs/skill.yaml +10 -0
- package/.agents/core/skills/nextjs/EXAMPLES.md +40 -0
- package/.agents/core/skills/nextjs/SKILL.md +163 -0
- package/.agents/core/skills/nextjs/TROUBLESHOOTING.md +19 -0
- package/.agents/core/skills/nextjs/VALIDATION.json +12 -0
- package/.agents/core/skills/nextjs/nextjs.md +67 -0
- package/.agents/core/skills/nextjs/skill.yaml +10 -0
- package/.agents/core/skills/node/EXAMPLES.md +80 -0
- package/.agents/core/skills/node/SKILL.md +128 -0
- package/.agents/core/skills/node/TROUBLESHOOTING.md +19 -0
- package/.agents/core/skills/node/VALIDATION.json +12 -0
- package/.agents/core/skills/node/node.md +87 -0
- package/.agents/core/skills/node/skill.yaml +10 -0
- package/.agents/core/skills/performance/EXAMPLES.md +30 -0
- package/.agents/core/skills/performance/SKILL.md +75 -0
- package/.agents/core/skills/performance/TROUBLESHOOTING.md +19 -0
- package/.agents/core/skills/performance/VALIDATION.json +12 -0
- package/.agents/core/skills/performance/performance.md +52 -0
- package/.agents/core/skills/performance/skill.yaml +10 -0
- package/.agents/core/skills/ponytail-mindset/EXAMPLES.md +45 -0
- package/.agents/core/skills/ponytail-mindset/SKILL.md +193 -0
- package/.agents/core/skills/ponytail-mindset/TROUBLESHOOTING.md +19 -0
- package/.agents/core/skills/ponytail-mindset/VALIDATION.json +12 -0
- package/.agents/core/skills/ponytail-mindset/skill.yaml +14 -0
- package/.agents/core/skills/react/EXAMPLES.md +79 -0
- package/.agents/core/skills/react/SKILL.md +132 -0
- package/.agents/core/skills/react/TROUBLESHOOTING.md +19 -0
- package/.agents/core/skills/react/VALIDATION.json +12 -0
- package/.agents/core/skills/react/react.md +93 -0
- package/.agents/core/skills/react/skill.yaml +10 -0
- package/.agents/core/skills/react-best-practices/SKILL.md +155 -0
- package/.agents/core/skills/react-best-practices/VALIDATION.json +12 -0
- package/.agents/core/skills/react-best-practices/skill.yaml +10 -0
- package/.agents/core/skills/redesign-audit/SKILL.md +117 -0
- package/.agents/core/skills/redesign-audit/VALIDATION.json +12 -0
- package/.agents/core/skills/redesign-audit/skill.yaml +8 -0
- package/.agents/core/skills/security/EXAMPLES.md +64 -0
- package/.agents/core/skills/security/SKILL.md +158 -0
- package/.agents/core/skills/security/TROUBLESHOOTING.md +19 -0
- package/.agents/core/skills/security/VALIDATION.json +12 -0
- package/.agents/core/skills/security/security.md +106 -0
- package/.agents/core/skills/security/skill.yaml +10 -0
- package/.agents/core/skills/soft-design/SKILL.md +108 -0
- package/.agents/core/skills/soft-design/VALIDATION.json +12 -0
- package/.agents/core/skills/soft-design/skill.yaml +8 -0
- package/.agents/core/skills/state-management/EXAMPLES.md +56 -0
- package/.agents/core/skills/state-management/SKILL.md +48 -0
- package/.agents/core/skills/state-management/TROUBLESHOOTING.md +18 -0
- package/.agents/core/skills/state-management/VALIDATION.json +11 -0
- package/.agents/core/skills/state-management/skill.yaml +22 -0
- package/.agents/core/skills/subagent-orchestrator/SKILL.md +100 -0
- package/.agents/core/skills/subagent-orchestrator/VALIDATION.json +12 -0
- package/.agents/core/skills/subagent-orchestrator/skill.yaml +8 -0
- package/.agents/core/skills/system-design/EXAMPLES.md +75 -0
- package/.agents/core/skills/system-design/SKILL.md +419 -0
- package/.agents/core/skills/system-design/TROUBLESHOOTING.md +19 -0
- package/.agents/core/skills/system-design/VALIDATION.json +12 -0
- package/.agents/core/skills/system-design/skill.yaml +13 -0
- package/.agents/core/skills/system-design/system-design.md +112 -0
- package/.agents/core/skills/testing/EXAMPLES.md +71 -0
- package/.agents/core/skills/testing/SKILL.md +70 -0
- package/.agents/core/skills/testing/TROUBLESHOOTING.md +18 -0
- package/.agents/core/skills/testing/VALIDATION.json +11 -0
- package/.agents/core/skills/testing/skill.yaml +26 -0
- package/.agents/core/skills/typescript/EXAMPLES.md +64 -0
- package/.agents/core/skills/typescript/SKILL.md +112 -0
- package/.agents/core/skills/typescript/TROUBLESHOOTING.md +19 -0
- package/.agents/core/skills/typescript/VALIDATION.json +12 -0
- package/.agents/core/skills/typescript/skill.yaml +10 -0
- package/.agents/core/skills/typescript/typescript.md +71 -0
- package/.agents/core/skills/ui-design/EXAMPLES.md +21 -0
- package/.agents/core/skills/ui-design/SKILL.md +124 -0
- package/.agents/core/skills/ui-design/TROUBLESHOOTING.md +19 -0
- package/.agents/core/skills/ui-design/VALIDATION.json +12 -0
- package/.agents/core/skills/ui-design/skill.yaml +10 -0
- package/.agents/core/skills/ui-design/ui.md +88 -0
- package/.agents/core/skills/ui-ux-pro/EXAMPLES.md +62 -0
- package/.agents/core/skills/ui-ux-pro/SKILL.md +375 -0
- package/.agents/core/skills/ui-ux-pro/TROUBLESHOOTING.md +19 -0
- package/.agents/core/skills/ui-ux-pro/VALIDATION.json +12 -0
- package/.agents/core/skills/ui-ux-pro/skill.yaml +13 -0
- package/.agents/core/skills/ux-design/EXAMPLES.md +36 -0
- package/.agents/core/skills/ux-design/SKILL.md +116 -0
- package/.agents/core/skills/ux-design/TROUBLESHOOTING.md +19 -0
- package/.agents/core/skills/ux-design/VALIDATION.json +12 -0
- package/.agents/core/skills/ux-design/skill.yaml +10 -0
- package/.agents/core/skills/ux-design/ux.md +80 -0
- package/.agents/core/skills/vercel-optimize/SKILL.md +83 -0
- package/.agents/core/skills/vercel-optimize/VALIDATION.json +12 -0
- package/.agents/core/skills/vercel-optimize/skill.yaml +10 -0
- package/.agents/core/skills/web-accessibility/EXAMPLES.md +39 -0
- package/.agents/core/skills/web-accessibility/SKILL.md +170 -0
- package/.agents/core/skills/web-accessibility/TROUBLESHOOTING.md +19 -0
- package/.agents/core/skills/web-accessibility/VALIDATION.json +12 -0
- package/.agents/core/skills/web-accessibility/accessibility.md +63 -0
- package/.agents/core/skills/web-accessibility/skill.yaml +10 -0
- package/.agents/ctx.js +370 -0
- package/.agents/generated/claude/skills/adapters/SKILL.md +126 -0
- package/.agents/generated/claude/skills/architecture-diagrams/SKILL.md +101 -0
- package/.agents/generated/claude/skills/brutalist-design/SKILL.md +145 -0
- package/.agents/generated/claude/skills/context-manager/SKILL.md +147 -0
- package/.agents/generated/claude/skills/context-os/SKILL.md +191 -0
- package/.agents/generated/claude/skills/database/SKILL.md +191 -0
- package/.agents/generated/claude/skills/ddd/SKILL.md +305 -0
- package/.agents/generated/claude/skills/decisions/SKILL.md +134 -0
- package/.agents/generated/claude/skills/docker/SKILL.md +135 -0
- package/.agents/generated/claude/skills/engineering-workflow/SKILL.md +414 -0
- package/.agents/generated/claude/skills/fastapi/SKILL.md +200 -0
- package/.agents/generated/claude/skills/gemini-precision/SKILL.md +161 -0
- package/.agents/generated/claude/skills/generators/SKILL.md +133 -0
- package/.agents/generated/claude/skills/graphify/SKILL.md +198 -0
- package/.agents/generated/claude/skills/gstack-roles/SKILL.md +184 -0
- package/.agents/generated/claude/skills/impeccable-design/SKILL.md +241 -0
- package/.agents/generated/claude/skills/interview-me/SKILL.md +90 -0
- package/.agents/generated/claude/skills/microservices/SKILL.md +218 -0
- package/.agents/generated/claude/skills/minimalist-design/SKILL.md +108 -0
- package/.agents/generated/claude/skills/nestjs/SKILL.md +195 -0
- package/.agents/generated/claude/skills/nextjs/SKILL.md +219 -0
- package/.agents/generated/claude/skills/node/SKILL.md +224 -0
- package/.agents/generated/claude/skills/performance/SKILL.md +121 -0
- package/.agents/generated/claude/skills/ponytail-mindset/SKILL.md +252 -0
- package/.agents/generated/claude/skills/react/SKILL.md +227 -0
- package/.agents/generated/claude/skills/react-best-practices/SKILL.md +146 -0
- package/.agents/generated/claude/skills/redesign-audit/SKILL.md +112 -0
- package/.agents/generated/claude/skills/security/SKILL.md +237 -0
- package/.agents/generated/claude/skills/soft-design/SKILL.md +103 -0
- package/.agents/generated/claude/skills/state-management/SKILL.md +120 -0
- package/.agents/generated/claude/skills/subagent-orchestrator/SKILL.md +93 -0
- package/.agents/generated/claude/skills/system-design/SKILL.md +507 -0
- package/.agents/generated/claude/skills/testing/SKILL.md +157 -0
- package/.agents/generated/claude/skills/typescript/SKILL.md +192 -0
- package/.agents/generated/claude/skills/ui-design/SKILL.md +161 -0
- package/.agents/generated/claude/skills/ui-ux-pro/SKILL.md +451 -0
- package/.agents/generated/claude/skills/ux-design/SKILL.md +168 -0
- package/.agents/generated/claude/skills/vercel-optimize/SKILL.md +76 -0
- package/.agents/generated/claude/skills/web-accessibility/SKILL.md +225 -0
- package/.agents/generated/gemini/skills/adapters/SKILL.md +135 -0
- package/.agents/generated/gemini/skills/architecture-diagrams/SKILL.md +107 -0
- package/.agents/generated/gemini/skills/brutalist-design/SKILL.md +151 -0
- package/.agents/generated/gemini/skills/context-manager/SKILL.md +156 -0
- package/.agents/generated/gemini/skills/context-os/SKILL.md +200 -0
- package/.agents/generated/gemini/skills/database/SKILL.md +200 -0
- package/.agents/generated/gemini/skills/ddd/SKILL.md +314 -0
- package/.agents/generated/gemini/skills/decisions/SKILL.md +143 -0
- package/.agents/generated/gemini/skills/docker/SKILL.md +144 -0
- package/.agents/generated/gemini/skills/engineering-workflow/SKILL.md +423 -0
- package/.agents/generated/gemini/skills/fastapi/SKILL.md +209 -0
- package/.agents/generated/gemini/skills/gemini-precision/SKILL.md +167 -0
- package/.agents/generated/gemini/skills/generators/SKILL.md +142 -0
- package/.agents/generated/gemini/skills/graphify/SKILL.md +205 -0
- package/.agents/generated/gemini/skills/gstack-roles/SKILL.md +193 -0
- package/.agents/generated/gemini/skills/impeccable-design/SKILL.md +250 -0
- package/.agents/generated/gemini/skills/interview-me/SKILL.md +96 -0
- package/.agents/generated/gemini/skills/microservices/SKILL.md +227 -0
- package/.agents/generated/gemini/skills/minimalist-design/SKILL.md +114 -0
- package/.agents/generated/gemini/skills/nestjs/SKILL.md +204 -0
- package/.agents/generated/gemini/skills/nextjs/SKILL.md +298 -0
- package/.agents/generated/gemini/skills/node/SKILL.md +323 -0
- package/.agents/generated/gemini/skills/performance/SKILL.md +185 -0
- package/.agents/generated/gemini/skills/ponytail-mindset/SKILL.md +261 -0
- package/.agents/generated/gemini/skills/react/SKILL.md +332 -0
- package/.agents/generated/gemini/skills/react-best-practices/SKILL.md +152 -0
- package/.agents/generated/gemini/skills/redesign-audit/SKILL.md +118 -0
- package/.agents/generated/gemini/skills/security/SKILL.md +355 -0
- package/.agents/generated/gemini/skills/soft-design/SKILL.md +109 -0
- package/.agents/generated/gemini/skills/state-management/SKILL.md +129 -0
- package/.agents/generated/gemini/skills/subagent-orchestrator/SKILL.md +99 -0
- package/.agents/generated/gemini/skills/system-design/SKILL.md +631 -0
- package/.agents/generated/gemini/skills/testing/SKILL.md +166 -0
- package/.agents/generated/gemini/skills/typescript/SKILL.md +275 -0
- package/.agents/generated/gemini/skills/ui-design/SKILL.md +170 -0
- package/.agents/generated/gemini/skills/ui-ux-pro/SKILL.md +460 -0
- package/.agents/generated/gemini/skills/ux-design/SKILL.md +177 -0
- package/.agents/generated/gemini/skills/vercel-optimize/SKILL.md +82 -0
- package/.agents/generated/gemini/skills/web-accessibility/SKILL.md +300 -0
- package/.agents/mcp/runtime.py +454 -0
- package/.agents/mcp/server.mjs +189373 -0
- package/.agents/plugins.js +574 -0
- package/.agents/profiles.js +267 -0
- package/.agents/resolver.js +336 -0
- package/.agents/skills-index.json +200 -0
- package/.agents/skills.json +6 -0
- package/.agents/validate.js +500 -0
- package/LICENSE +21 -0
- package/README.md +415 -0
- package/benchmarks/gemini-issues.js +533 -0
- package/bin/index.js +458 -0
- package/package.json +70 -0
- package/registry.json +53 -0
- package/registry.schema.json +32 -0
|
@@ -0,0 +1,533 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* A reproducible, paired benchmark for ContextOS skills.
|
|
5
|
+
*
|
|
6
|
+
* It discovers closed JavaScript GitHub Issues linked to merged pull requests,
|
|
7
|
+
* checks out the parent of each merge commit, and gives the same task to Gemini
|
|
8
|
+
* twice: once with a neutral instruction and once with ContextOS skills. The
|
|
9
|
+
* coding loop is intentionally controller-driven: the model may read files and
|
|
10
|
+
* return a unified diff, but it never receives shell access.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
'use strict';
|
|
14
|
+
|
|
15
|
+
const crypto = require('crypto');
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const https = require('https');
|
|
18
|
+
const os = require('os');
|
|
19
|
+
const path = require('path');
|
|
20
|
+
const { spawn } = require('child_process');
|
|
21
|
+
|
|
22
|
+
// The benchmark belongs to the package but operates on the project from which
|
|
23
|
+
// it is invoked, including that project's installed .agents directory.
|
|
24
|
+
const ROOT = process.cwd();
|
|
25
|
+
const DEFAULT_COUNT = 20;
|
|
26
|
+
const DEFAULT_MAX_ITERATIONS = 6;
|
|
27
|
+
const DEFAULT_MODEL = 'gemini-2.5-flash';
|
|
28
|
+
const DEFAULT_QUERY = 'is:issue is:closed language:JavaScript label:bug';
|
|
29
|
+
const MAX_FILE_BYTES = 24 * 1024;
|
|
30
|
+
const MAX_OUTPUT_BYTES = 16 * 1024;
|
|
31
|
+
const DEFAULT_SKILL_FILES = [
|
|
32
|
+
'.agents/AGENTS.md',
|
|
33
|
+
'.agents/core/skills/engineering-workflow/SKILL.md',
|
|
34
|
+
'.agents/core/skills/system-design/SKILL.md',
|
|
35
|
+
'.agents/core/skills/node/SKILL.md',
|
|
36
|
+
'.agents/core/skills/security/SKILL.md',
|
|
37
|
+
'.agents/core/skills/typescript/SKILL.md',
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
function parseArgs(argv) {
|
|
41
|
+
const options = {
|
|
42
|
+
count: DEFAULT_COUNT,
|
|
43
|
+
maxIterations: DEFAULT_MAX_ITERATIONS,
|
|
44
|
+
model: DEFAULT_MODEL,
|
|
45
|
+
query: DEFAULT_QUERY,
|
|
46
|
+
output: path.join(ROOT, 'benchmarks', 'results'),
|
|
47
|
+
tasks: null,
|
|
48
|
+
allowCommands: false,
|
|
49
|
+
dryRun: false,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
for (let index = 0; index < argv.length; index++) {
|
|
53
|
+
const arg = argv[index];
|
|
54
|
+
const next = () => {
|
|
55
|
+
const value = argv[++index];
|
|
56
|
+
if (!value || value.startsWith('--')) throw new Error(`${arg} requires a value`);
|
|
57
|
+
return value;
|
|
58
|
+
};
|
|
59
|
+
if (arg === '--count') options.count = Number(next());
|
|
60
|
+
else if (arg === '--max-iterations') options.maxIterations = Number(next());
|
|
61
|
+
else if (arg === '--model') options.model = next();
|
|
62
|
+
else if (arg === '--query') options.query = next();
|
|
63
|
+
else if (arg === '--output') options.output = path.resolve(next());
|
|
64
|
+
else if (arg === '--tasks') options.tasks = path.resolve(next());
|
|
65
|
+
else if (arg === '--allow-commands') options.allowCommands = true;
|
|
66
|
+
else if (arg === '--dry-run') options.dryRun = true;
|
|
67
|
+
else if (arg === '--help' || arg === '-h') options.help = true;
|
|
68
|
+
else throw new Error(`Unknown option: ${arg}`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!Number.isInteger(options.count) || options.count < 1 || options.count > 100) {
|
|
72
|
+
throw new Error('--count must be an integer from 1 to 100');
|
|
73
|
+
}
|
|
74
|
+
if (!Number.isInteger(options.maxIterations) || options.maxIterations < 1 || options.maxIterations > 12) {
|
|
75
|
+
throw new Error('--max-iterations must be an integer from 1 to 12');
|
|
76
|
+
}
|
|
77
|
+
return options;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function printHelp() {
|
|
81
|
+
console.log(`ContextOS Gemini Issue Benchmark
|
|
82
|
+
|
|
83
|
+
Usage:
|
|
84
|
+
npm run benchmark -- [options]
|
|
85
|
+
|
|
86
|
+
Options:
|
|
87
|
+
--count <n> Issues to discover (default: ${DEFAULT_COUNT})
|
|
88
|
+
--tasks <file> Re-run a saved, reproducible task manifest
|
|
89
|
+
--query <github-query> GitHub issue search query
|
|
90
|
+
--model <name> Gemini model (default: ${DEFAULT_MODEL})
|
|
91
|
+
--max-iterations <n> Maximum model turns per run (default: ${DEFAULT_MAX_ITERATIONS})
|
|
92
|
+
--output <directory> Report directory (default: benchmarks/results)
|
|
93
|
+
--allow-commands Required before cloning repos or running setup/tests
|
|
94
|
+
--dry-run Discover and validate tasks, but do not call Gemini or clone
|
|
95
|
+
|
|
96
|
+
Environment:
|
|
97
|
+
GEMINI_API_KEY Required for benchmark runs
|
|
98
|
+
GITHUB_TOKEN Strongly recommended; discovery of 20 linked issues exceeds anonymous API limits
|
|
99
|
+
|
|
100
|
+
The benchmark writes an immutable task manifest containing issue URLs and base
|
|
101
|
+
commits before it executes. Re-run that manifest with --tasks for comparable results.`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function truncate(value, limit = MAX_OUTPUT_BYTES) {
|
|
105
|
+
const text = String(value || '');
|
|
106
|
+
return text.length <= limit ? text : `${text.slice(0, limit)}\n…[truncated]`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function safeRelativePath(repository, requestedPath) {
|
|
110
|
+
if (typeof requestedPath !== 'string' || requestedPath.length === 0 || requestedPath.length > 240) return null;
|
|
111
|
+
const resolved = path.resolve(repository, requestedPath);
|
|
112
|
+
const relative = path.relative(repository, resolved);
|
|
113
|
+
if (relative === '' || relative.startsWith('..') || path.isAbsolute(relative)) return null;
|
|
114
|
+
return resolved;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function readRequestedFiles(repository, requestedPaths) {
|
|
118
|
+
const files = {};
|
|
119
|
+
for (const requestedPath of (requestedPaths || []).slice(0, 8)) {
|
|
120
|
+
const resolved = safeRelativePath(repository, requestedPath);
|
|
121
|
+
if (!resolved || !fs.existsSync(resolved) || !fs.statSync(resolved).isFile()) continue;
|
|
122
|
+
if (fs.statSync(resolved).size > MAX_FILE_BYTES) continue;
|
|
123
|
+
files[requestedPath] = fs.readFileSync(resolved, 'utf8');
|
|
124
|
+
}
|
|
125
|
+
return files;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function parseJsonObject(text) {
|
|
129
|
+
const clean = String(text || '').trim().replace(/^```(?:json)?\s*/i, '').replace(/\s*```$/, '');
|
|
130
|
+
const first = clean.indexOf('{');
|
|
131
|
+
const last = clean.lastIndexOf('}');
|
|
132
|
+
if (first < 0 || last <= first) throw new Error('Model response did not contain a JSON object');
|
|
133
|
+
return JSON.parse(clean.slice(first, last + 1));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function parseAgentReply(text) {
|
|
137
|
+
const reply = parseJsonObject(text);
|
|
138
|
+
if (!Array.isArray(reply.read)) reply.read = [];
|
|
139
|
+
if (typeof reply.patch !== 'string') reply.patch = '';
|
|
140
|
+
if (typeof reply.ready !== 'boolean') reply.ready = false;
|
|
141
|
+
if (typeof reply.summary !== 'string') reply.summary = '';
|
|
142
|
+
if (reply.patch && !reply.patch.startsWith('diff --git ')) {
|
|
143
|
+
throw new Error('Model patch must be a unified git diff starting with "diff --git"');
|
|
144
|
+
}
|
|
145
|
+
return reply;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function run(command, args, options = {}) {
|
|
149
|
+
const { cwd, timeoutMs = 120_000, allowFailure = false } = options;
|
|
150
|
+
return new Promise((resolve, reject) => {
|
|
151
|
+
const child = spawn(command, args, { cwd, shell: false, windowsHide: true });
|
|
152
|
+
let stdout = '';
|
|
153
|
+
let stderr = '';
|
|
154
|
+
let timedOut = false;
|
|
155
|
+
const timer = setTimeout(() => {
|
|
156
|
+
timedOut = true;
|
|
157
|
+
child.kill('SIGTERM');
|
|
158
|
+
}, timeoutMs);
|
|
159
|
+
const append = (current, chunk) => truncate(current + chunk.toString(), MAX_OUTPUT_BYTES);
|
|
160
|
+
child.stdout.on('data', chunk => { stdout = append(stdout, chunk); });
|
|
161
|
+
child.stderr.on('data', chunk => { stderr = append(stderr, chunk); });
|
|
162
|
+
child.on('error', error => {
|
|
163
|
+
clearTimeout(timer);
|
|
164
|
+
reject(error);
|
|
165
|
+
});
|
|
166
|
+
child.on('close', code => {
|
|
167
|
+
clearTimeout(timer);
|
|
168
|
+
const result = { code, stdout, stderr, timedOut, passed: code === 0 && !timedOut };
|
|
169
|
+
if (!result.passed && !allowFailure) {
|
|
170
|
+
const error = new Error(`${command} exited with code ${code}${timedOut ? ' (timed out)' : ''}`);
|
|
171
|
+
error.result = result;
|
|
172
|
+
reject(error);
|
|
173
|
+
} else {
|
|
174
|
+
resolve(result);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function requestJson(url, { method = 'GET', headers = {}, body = null } = {}) {
|
|
181
|
+
return new Promise((resolve, reject) => {
|
|
182
|
+
const request = https.request(url, { method, headers }, response => {
|
|
183
|
+
let raw = '';
|
|
184
|
+
response.setEncoding('utf8');
|
|
185
|
+
response.on('data', chunk => { raw = truncate(raw + chunk, MAX_OUTPUT_BYTES); });
|
|
186
|
+
response.on('end', () => {
|
|
187
|
+
try {
|
|
188
|
+
resolve({ status: response.statusCode, body: JSON.parse(raw) });
|
|
189
|
+
} catch {
|
|
190
|
+
resolve({ status: response.statusCode, body: { message: raw } });
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
request.setTimeout(30_000, () => request.destroy(new Error('HTTP request timed out')));
|
|
195
|
+
request.on('error', reject);
|
|
196
|
+
if (body) request.write(body);
|
|
197
|
+
request.end();
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async function githubJson(endpoint, token) {
|
|
202
|
+
const response = await requestJson(`https://api.github.com${endpoint}`, {
|
|
203
|
+
headers: {
|
|
204
|
+
Accept: 'application/vnd.github+json',
|
|
205
|
+
'X-GitHub-Api-Version': '2022-11-28',
|
|
206
|
+
'User-Agent': 'contextos-agents-benchmark',
|
|
207
|
+
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
if (response.status < 200 || response.status >= 300) {
|
|
211
|
+
throw new Error(`GitHub API ${response.status}: ${truncate(JSON.stringify(response.body), 500)}`);
|
|
212
|
+
}
|
|
213
|
+
return response.body;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function parseRepositoryFromApiUrl(repositoryUrl) {
|
|
217
|
+
const match = String(repositoryUrl).match(/\/repos\/([^/]+)\/([^/]+)$/);
|
|
218
|
+
if (!match) throw new Error(`Unexpected GitHub repository URL: ${repositoryUrl}`);
|
|
219
|
+
return { owner: match[1], repo: match[2] };
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async function resolveIssueTask(issue, token) {
|
|
223
|
+
const { owner, repo } = parseRepositoryFromApiUrl(issue.repository_url);
|
|
224
|
+
const prefix = `/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}`;
|
|
225
|
+
const timeline = await githubJson(`${prefix}/issues/${issue.number}/timeline`, token);
|
|
226
|
+
const source = timeline.find(event => event.event === 'cross-referenced' && event.source?.issue?.pull_request)?.source?.issue;
|
|
227
|
+
if (!source?.number) return null;
|
|
228
|
+
|
|
229
|
+
const pull = await githubJson(`${prefix}/pulls/${source.number}`, token);
|
|
230
|
+
if (!pull.merged_at || !pull.merge_commit_sha) return null;
|
|
231
|
+
const mergeCommit = await githubJson(`${prefix}/commits/${pull.merge_commit_sha}`, token);
|
|
232
|
+
const baseSha = mergeCommit.parents?.[0]?.sha;
|
|
233
|
+
if (!baseSha) return null;
|
|
234
|
+
|
|
235
|
+
let packageJson;
|
|
236
|
+
try {
|
|
237
|
+
const content = await githubJson(`${prefix}/contents/package.json?ref=${encodeURIComponent(baseSha)}`, token);
|
|
238
|
+
packageJson = JSON.parse(Buffer.from(content.content, 'base64').toString('utf8'));
|
|
239
|
+
} catch {
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
if (!packageJson.scripts?.test) return null;
|
|
243
|
+
|
|
244
|
+
return {
|
|
245
|
+
id: `${owner}/${repo}#${issue.number}`,
|
|
246
|
+
repository: `${owner}/${repo}`,
|
|
247
|
+
issueNumber: issue.number,
|
|
248
|
+
issueUrl: issue.html_url,
|
|
249
|
+
title: issue.title,
|
|
250
|
+
body: issue.body || '',
|
|
251
|
+
baseSha,
|
|
252
|
+
linkedPullRequest: source.html_url,
|
|
253
|
+
setup: ['npm', 'ci', '--ignore-scripts'],
|
|
254
|
+
test: ['npm', 'test'],
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
async function discoverTasks(count, query, token) {
|
|
259
|
+
if (!token && count > 5) {
|
|
260
|
+
throw new Error('Set GITHUB_TOKEN to discover 20 tasks without GitHub API rate-limit failures.');
|
|
261
|
+
}
|
|
262
|
+
const tasks = [];
|
|
263
|
+
let page = 1;
|
|
264
|
+
while (tasks.length < count && page <= 10) {
|
|
265
|
+
const search = await githubJson(`/search/issues?q=${encodeURIComponent(query)}&per_page=100&page=${page}`, token);
|
|
266
|
+
const items = search.items || [];
|
|
267
|
+
if (items.length === 0) break;
|
|
268
|
+
for (const issue of items) {
|
|
269
|
+
if (tasks.length === count) break;
|
|
270
|
+
if (issue.pull_request) continue;
|
|
271
|
+
try {
|
|
272
|
+
const task = await resolveIssueTask(issue, token);
|
|
273
|
+
if (task) tasks.push(task);
|
|
274
|
+
} catch (error) {
|
|
275
|
+
console.warn(`[skip] ${issue.html_url}: ${error.message}`);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
page++;
|
|
279
|
+
}
|
|
280
|
+
if (tasks.length < count) {
|
|
281
|
+
throw new Error(`Only resolved ${tasks.length}/${count} reproducible issues. Broaden --query and try again.`);
|
|
282
|
+
}
|
|
283
|
+
return tasks;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function writeJson(file, value) {
|
|
287
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
288
|
+
fs.writeFileSync(file, `${JSON.stringify(value, null, 2)}\n`);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function readTaskManifest(file) {
|
|
292
|
+
const parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
293
|
+
const tasks = Array.isArray(parsed) ? parsed : parsed.tasks;
|
|
294
|
+
if (!Array.isArray(tasks) || tasks.length === 0) throw new Error('Task manifest must contain a non-empty tasks array');
|
|
295
|
+
for (const task of tasks) {
|
|
296
|
+
if (!/^[\w.-]+\/[\w.-]+$/.test(task.repository || '') || !/^[a-f0-9]{40}$/i.test(task.baseSha || '')) {
|
|
297
|
+
throw new Error(`Invalid repository or baseSha in task: ${task.id || task.issueUrl || 'unknown'}`);
|
|
298
|
+
}
|
|
299
|
+
if (!Array.isArray(task.setup) || !Array.isArray(task.test) || !task.issueUrl) {
|
|
300
|
+
throw new Error(`Task ${task.id || task.issueUrl} lacks setup, test, or issueUrl`);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return tasks;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function loadSkillContext() {
|
|
307
|
+
return DEFAULT_SKILL_FILES
|
|
308
|
+
.filter(file => fs.existsSync(path.join(ROOT, file)))
|
|
309
|
+
.map(file => `\n===== ${file} =====\n${fs.readFileSync(path.join(ROOT, file), 'utf8')}`)
|
|
310
|
+
.join('\n');
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function buildAgentPrompt(task, mode, initialFiles, feedback = '') {
|
|
314
|
+
const skills = mode === 'with_skills'
|
|
315
|
+
? `\nContextOS skills are authoritative guidance for this run:\n${loadSkillContext()}`
|
|
316
|
+
: '';
|
|
317
|
+
return `You are repairing a real JavaScript repository. You have no shell or network access.\n\nIssue: ${task.title}\n${task.body}\nIssue URL: ${task.issueUrl}\n\nReturn exactly one JSON object, with no markdown fence:\n{"read":["relative/file.js"],"patch":"diff --git ... or empty string","ready":false,"summary":"short explanation"}\n\nRules:\n- Request at most 8 repository files per turn before editing.\n- Only return a valid unified git diff in patch; do not invent command output.\n- Do not change lockfiles, CI, generated files, or dependencies unless the issue requires it.\n- Set ready to true only after you have supplied all required changes.\n- The controller will apply your patch and return test output.\n\nInitial files:\n${JSON.stringify(initialFiles, null, 2)}${feedback ? `\n\nController feedback:\n${feedback}` : ''}${skills}`;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function extractText(payload) {
|
|
321
|
+
if (typeof payload?.output_text === 'string') return payload.output_text;
|
|
322
|
+
const chunks = [];
|
|
323
|
+
const visit = value => {
|
|
324
|
+
if (Array.isArray(value)) return value.forEach(visit);
|
|
325
|
+
if (!value || typeof value !== 'object') return;
|
|
326
|
+
if (typeof value.text === 'string' && (!value.type || /text/.test(value.type))) {
|
|
327
|
+
chunks.push(value.text);
|
|
328
|
+
}
|
|
329
|
+
for (const key of ['output', 'outputs', 'content', 'parts', 'candidates', 'message', 'delta']) {
|
|
330
|
+
if (value[key]) visit(value[key]);
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
visit(payload.output || payload.outputs || payload.candidates);
|
|
334
|
+
return chunks.join('\n');
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
async function callGemini(apiKey, model, input, systemInstruction) {
|
|
338
|
+
const started = Date.now();
|
|
339
|
+
const response = await requestJson('https://generativelanguage.googleapis.com/v1beta/interactions', {
|
|
340
|
+
method: 'POST',
|
|
341
|
+
headers: { 'Content-Type': 'application/json', 'x-goog-api-key': apiKey },
|
|
342
|
+
body: JSON.stringify({
|
|
343
|
+
model,
|
|
344
|
+
input,
|
|
345
|
+
system_instruction: systemInstruction,
|
|
346
|
+
generation_config: { temperature: 0 },
|
|
347
|
+
}),
|
|
348
|
+
});
|
|
349
|
+
const payload = response.body;
|
|
350
|
+
if (response.status < 200 || response.status >= 300) throw new Error(`Gemini API ${response.status}: ${truncate(JSON.stringify(payload), 800)}`);
|
|
351
|
+
const text = extractText(payload);
|
|
352
|
+
if (!text) throw new Error('Gemini returned no text output');
|
|
353
|
+
return { text, latencyMs: Date.now() - started, usage: payload.usage_metadata || payload.usageMetadata || null };
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
async function gitOutput(repository, args) {
|
|
357
|
+
const result = await run('git', args, { cwd: repository, allowFailure: true });
|
|
358
|
+
return result.stdout;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
async function cloneTask(task, workspace) {
|
|
362
|
+
const destination = path.join(workspace, task.id.replace(/[^a-zA-Z0-9._-]/g, '-'));
|
|
363
|
+
await run('git', ['clone', '--filter=blob:none', '--no-checkout', `https://github.com/${task.repository}.git`, destination], { cwd: workspace, timeoutMs: 300_000 });
|
|
364
|
+
await run('git', ['checkout', '--detach', task.baseSha], { cwd: destination, timeoutMs: 120_000 });
|
|
365
|
+
return destination;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
async function applyPatch(repository, patch) {
|
|
369
|
+
const patchFile = path.join(os.tmpdir(), `contextos-benchmark-${crypto.randomUUID()}.patch`);
|
|
370
|
+
fs.writeFileSync(patchFile, patch);
|
|
371
|
+
try {
|
|
372
|
+
return await run('git', ['apply', '--whitespace=nowarn', patchFile], { cwd: repository, allowFailure: true });
|
|
373
|
+
} finally {
|
|
374
|
+
fs.rmSync(patchFile, { force: true });
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
async function runTaskCommand(repository, command, timeoutMs = 300_000) {
|
|
379
|
+
if (!Array.isArray(command) || command.length === 0) throw new Error('Task command must be a non-empty argument array');
|
|
380
|
+
return run(command[0], command.slice(1), { cwd: repository, timeoutMs, allowFailure: true });
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
async function runAgent(task, mode, options, workspace) {
|
|
384
|
+
const repository = await cloneTask(task, workspace);
|
|
385
|
+
const setup = await runTaskCommand(repository, task.setup);
|
|
386
|
+
if (!setup.passed) return { task, mode, status: 'setup_failed', setup, iterations: 0, score: 0 };
|
|
387
|
+
|
|
388
|
+
const initialFiles = readRequestedFiles(repository, ['package.json', 'README.md']);
|
|
389
|
+
let feedback = '';
|
|
390
|
+
let lastTest = null;
|
|
391
|
+
let lastReply = null;
|
|
392
|
+
const turns = [];
|
|
393
|
+
for (let iteration = 1; iteration <= options.maxIterations; iteration++) {
|
|
394
|
+
const response = await callGemini(
|
|
395
|
+
options.apiKey,
|
|
396
|
+
options.model,
|
|
397
|
+
buildAgentPrompt(task, mode, initialFiles, feedback),
|
|
398
|
+
'You are a careful software engineer. Follow the controller protocol exactly.'
|
|
399
|
+
);
|
|
400
|
+
let reply;
|
|
401
|
+
try {
|
|
402
|
+
reply = parseAgentReply(response.text);
|
|
403
|
+
} catch (error) {
|
|
404
|
+
return { task, mode, status: 'invalid_model_response', error: error.message, iterations: iteration, turns, score: 0 };
|
|
405
|
+
}
|
|
406
|
+
lastReply = reply;
|
|
407
|
+
const files = readRequestedFiles(repository, reply.read);
|
|
408
|
+
const turn = { iteration, latencyMs: response.latencyMs, usage: response.usage, requestedFiles: Object.keys(files), summary: reply.summary };
|
|
409
|
+
|
|
410
|
+
if (reply.patch) {
|
|
411
|
+
const applied = await applyPatch(repository, reply.patch);
|
|
412
|
+
turn.patchApplied = applied.passed;
|
|
413
|
+
if (!applied.passed) {
|
|
414
|
+
feedback = `Patch was rejected:\n${truncate(applied.stderr || applied.stdout)}`;
|
|
415
|
+
} else {
|
|
416
|
+
lastTest = await runTaskCommand(repository, task.test);
|
|
417
|
+
turn.test = lastTest;
|
|
418
|
+
feedback = `Patch applied. Test result (exit ${lastTest.code}):\n${truncate(`${lastTest.stdout}\n${lastTest.stderr}`)}`;
|
|
419
|
+
}
|
|
420
|
+
} else if (Object.keys(files).length > 0) {
|
|
421
|
+
feedback = `Requested file contents:\n${JSON.stringify(files, null, 2)}`;
|
|
422
|
+
} else {
|
|
423
|
+
feedback = 'No patch or readable file request was supplied. Return a valid next action.';
|
|
424
|
+
}
|
|
425
|
+
turns.push(turn);
|
|
426
|
+
|
|
427
|
+
const diff = await gitOutput(repository, ['diff', '--no-ext-diff', '--unified=3']);
|
|
428
|
+
if (reply.ready && lastTest?.passed && diff.trim()) break;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const diff = await gitOutput(repository, ['diff', '--no-ext-diff', '--unified=3']);
|
|
432
|
+
const filesChanged = (await gitOutput(repository, ['diff', '--name-only'])).trim().split('\n').filter(Boolean);
|
|
433
|
+
const ready = Boolean(lastReply?.ready && lastTest?.passed && diff.trim());
|
|
434
|
+
const judge = await judgeChange(task, diff, lastTest, options);
|
|
435
|
+
const score = scoreRun({ ready, test: lastTest, filesChanged, judge, iterations: turns.length });
|
|
436
|
+
return { task, mode, status: ready ? 'ready' : 'not_ready', setup, test: lastTest, iterations: turns.length, turns, filesChanged, diff, judge, score };
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
async function judgeChange(task, diff, test, options) {
|
|
440
|
+
if (!diff.trim()) return { score: 0, reason: 'No source change was produced.' };
|
|
441
|
+
const prompt = `Evaluate a proposed fix for this GitHub Issue. Score only issue fidelity and code quality from 0 to 30. Do not reward tests merely passing; explain missing behavior. Return exactly JSON: {"score":number,"reason":"short"}.\n\nIssue: ${task.title}\n${task.body}\n\nDiff:\n${truncate(diff, 18_000)}\n\nTest output:\n${truncate(`${test?.stdout || ''}\n${test?.stderr || ''}`, 4_000)}`;
|
|
442
|
+
try {
|
|
443
|
+
const response = await callGemini(options.apiKey, options.model, prompt, 'You are an impartial senior code reviewer. Return JSON only.');
|
|
444
|
+
const result = parseJsonObject(response.text);
|
|
445
|
+
return { score: Math.max(0, Math.min(30, Number(result.score) || 0)), reason: String(result.reason || ''), latencyMs: response.latencyMs };
|
|
446
|
+
} catch (error) {
|
|
447
|
+
return { score: 0, reason: `Judge unavailable: ${error.message}` };
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
function scoreRun({ ready, test, filesChanged, judge, iterations }) {
|
|
452
|
+
const testScore = test?.passed ? 45 : 0;
|
|
453
|
+
const scopeScore = filesChanged.length > 0 && filesChanged.length <= 6 ? 15 : filesChanged.length <= 12 ? 8 : 0;
|
|
454
|
+
const safetyScore = filesChanged.every(file => !/(^|\/)(\.env|node_modules|\.github\/workflows)\b|(?:package-lock|yarn\.lock|pnpm-lock)/.test(file)) ? 10 : 0;
|
|
455
|
+
const iterationScore = ready ? Math.max(0, 10 - Math.max(0, iterations - 1) * 2) : 0;
|
|
456
|
+
return { total: testScore + scopeScore + safetyScore + iterationScore + (judge?.score || 0), testScore, scopeScore, safetyScore, iterationScore, judgeScore: judge?.score || 0 };
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function average(values) {
|
|
460
|
+
return values.length ? values.reduce((sum, value) => sum + value, 0) / values.length : 0;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function summarize(results) {
|
|
464
|
+
const byMode = {};
|
|
465
|
+
for (const mode of ['without_skills', 'with_skills']) {
|
|
466
|
+
const runs = results.filter(result => result.mode === mode);
|
|
467
|
+
byMode[mode] = {
|
|
468
|
+
runs: runs.length,
|
|
469
|
+
readyRate: runs.length ? runs.filter(run => run.status === 'ready').length / runs.length : 0,
|
|
470
|
+
testPassRate: runs.length ? runs.filter(run => run.test?.passed).length / runs.length : 0,
|
|
471
|
+
averageScore: average(runs.map(run => run.score?.total || 0)),
|
|
472
|
+
averageIterations: average(runs.map(run => run.iterations || 0)),
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
const paired = results.reduce((map, result) => {
|
|
476
|
+
const entry = map.get(result.task.id) || {};
|
|
477
|
+
entry[result.mode] = result;
|
|
478
|
+
map.set(result.task.id, entry);
|
|
479
|
+
return map;
|
|
480
|
+
}, new Map());
|
|
481
|
+
const deltas = [...paired.values()]
|
|
482
|
+
.filter(pair => pair.without_skills && pair.with_skills)
|
|
483
|
+
.map(pair => pair.with_skills.score.total - pair.without_skills.score.total);
|
|
484
|
+
return { byMode, pairedTasks: deltas.length, meanSkillScoreDelta: average(deltas) };
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function markdownReport(report) {
|
|
488
|
+
const row = (name, values) => `| ${name} | ${values.runs} | ${(values.readyRate * 100).toFixed(1)}% | ${(values.testPassRate * 100).toFixed(1)}% | ${values.averageScore.toFixed(1)} | ${values.averageIterations.toFixed(2)} |`;
|
|
489
|
+
return `# ContextOS Skills Benchmark\n\nModel: \`${report.model}\` \nTasks: ${report.tasks.length} \nPaired tasks: ${report.summary.pairedTasks}\n\n| Mode | Runs | Ready | Tests pass | Mean quality (0–110) | Mean turns |\n| --- | ---: | ---: | ---: | ---: | ---: |\n${row('Without skills', report.summary.byMode.without_skills)}\n${row('With ContextOS skills', report.summary.byMode.with_skills)}\n\nMean paired score delta (with skills − without): **${report.summary.meanSkillScoreDelta.toFixed(1)}**\n\n## Per task\n\n| Issue | Mode | Status | Turns | Score |\n| --- | --- | --- | ---: | ---: |\n${report.results.map(run => `| [${run.task.id}](${run.task.issueUrl}) | ${run.mode} | ${run.status} | ${run.iterations} | ${run.score?.total || 0} |`).join('\n')}\n`;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
async function main() {
|
|
493
|
+
const options = parseArgs(process.argv.slice(2));
|
|
494
|
+
if (options.help) return printHelp();
|
|
495
|
+
const token = process.env.GITHUB_TOKEN;
|
|
496
|
+
const tasks = options.tasks ? readTaskManifest(options.tasks) : await discoverTasks(options.count, options.query, token);
|
|
497
|
+
const manifestPath = options.tasks || path.join(options.output, `tasks-${new Date().toISOString().replace(/[:.]/g, '-')}.json`);
|
|
498
|
+
if (!options.tasks) writeJson(manifestPath, { generatedAt: new Date().toISOString(), query: options.query, tasks });
|
|
499
|
+
console.log(`Prepared ${tasks.length} reproducible GitHub Issue tasks: ${manifestPath}`);
|
|
500
|
+
|
|
501
|
+
if (options.dryRun || !options.allowCommands) {
|
|
502
|
+
console.log(options.dryRun ? 'Dry run complete; no model calls or repositories were executed.' : 'Pass --allow-commands to clone task repositories and run their declared setup/tests.');
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
if (!process.env.GEMINI_API_KEY) throw new Error('GEMINI_API_KEY is required for a benchmark run');
|
|
506
|
+
|
|
507
|
+
const workspace = fs.mkdtempSync(path.join(os.tmpdir(), 'contextos-benchmark-'));
|
|
508
|
+
try {
|
|
509
|
+
const results = [];
|
|
510
|
+
for (const task of tasks) {
|
|
511
|
+
for (const mode of ['without_skills', 'with_skills']) {
|
|
512
|
+
console.log(`\n[${mode}] ${task.id}`);
|
|
513
|
+
results.push(await runAgent(task, mode, { ...options, apiKey: process.env.GEMINI_API_KEY }, workspace));
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
const report = { generatedAt: new Date().toISOString(), model: options.model, tasks, results, summary: summarize(results) };
|
|
517
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
518
|
+
writeJson(path.join(options.output, `report-${stamp}.json`), report);
|
|
519
|
+
fs.writeFileSync(path.join(options.output, `report-${stamp}.md`), markdownReport(report));
|
|
520
|
+
console.log(`\nBenchmark complete. Reports written to ${options.output}`);
|
|
521
|
+
} finally {
|
|
522
|
+
fs.rmSync(workspace, { recursive: true, force: true });
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
module.exports = { buildAgentPrompt, parseAgentReply, parseArgs, scoreRun, summarize, safeRelativePath };
|
|
527
|
+
|
|
528
|
+
if (require.main === module) {
|
|
529
|
+
main().catch(error => {
|
|
530
|
+
console.error(`[ERROR] ${error.message}`);
|
|
531
|
+
process.exit(1);
|
|
532
|
+
});
|
|
533
|
+
}
|