@minhpnq1807/contextos 0.5.51 → 0.5.53
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/CHANGELOG.md +17 -2
- package/README.md +147 -13
- package/bin/ctx.js +59 -7
- package/eval/skill-routing/cases.yaml +366 -0
- package/eval/skill-routing/fixtures/docker-node/Dockerfile +4 -0
- package/eval/skill-routing/fixtures/docker-node/docker-compose.yml +5 -0
- package/eval/skill-routing/fixtures/docker-node/package.json +6 -0
- package/eval/skill-routing/fixtures/expo-eas/.github/workflows/eas.yml +1 -0
- package/eval/skill-routing/fixtures/expo-eas/app.json +5 -0
- package/eval/skill-routing/fixtures/expo-eas/eas.json +6 -0
- package/eval/skill-routing/fixtures/expo-eas/package.json +11 -0
- package/eval/skill-routing/fixtures/expo-with-vercel-json/app.json +6 -0
- package/eval/skill-routing/fixtures/expo-with-vercel-json/eas.json +5 -0
- package/eval/skill-routing/fixtures/expo-with-vercel-json/package.json +8 -0
- package/eval/skill-routing/fixtures/expo-with-vercel-json/vercel.json +3 -0
- package/eval/skill-routing/fixtures/express-mongo-jwt/package.json +8 -0
- package/eval/skill-routing/fixtures/firebase-hosting/firebase.json +11 -0
- package/eval/skill-routing/fixtures/firebase-hosting/package.json +6 -0
- package/eval/skill-routing/fixtures/flutter-firebase/pubspec.yaml +5 -0
- package/eval/skill-routing/fixtures/frontend-only-next/package.json +8 -0
- package/eval/skill-routing/fixtures/integration-test/jest.config.js +3 -0
- package/eval/skill-routing/fixtures/integration-test/package.json +10 -0
- package/eval/skill-routing/fixtures/jest-project/jest.config.js +3 -0
- package/eval/skill-routing/fixtures/jest-project/package.json +7 -0
- package/eval/skill-routing/fixtures/nest-prisma/package.json +10 -0
- package/eval/skill-routing/fixtures/nest-prisma/prisma/schema.prisma +4 -0
- package/eval/skill-routing/fixtures/next-vercel/.github/workflows/deploy.yml +1 -0
- package/eval/skill-routing/fixtures/next-vercel/package.json +8 -0
- package/eval/skill-routing/fixtures/next-vercel/vercel.json +3 -0
- package/eval/skill-routing/fixtures/oauth-google/.env.example +3 -0
- package/eval/skill-routing/fixtures/oauth-google/package.json +9 -0
- package/eval/skill-routing/fixtures/password-reset/package.json +8 -0
- package/eval/skill-routing/fixtures/playwright-project/package.json +6 -0
- package/eval/skill-routing/fixtures/playwright-project/playwright.config.ts +5 -0
- package/eval/skill-routing/fixtures/railway-render/package.json +6 -0
- package/eval/skill-routing/fixtures/railway-render/railway.json +6 -0
- package/eval/skill-routing/fixtures/railway-render/render.yaml +5 -0
- package/eval/skill-routing/fixtures/rbac-api/package.json +8 -0
- package/eval/skill-routing/fixtures/redis-cache/package.json +7 -0
- package/eval/skill-routing/fixtures/static-docs/README.md +3 -0
- package/eval/skill-routing/run-eval.js +278 -0
- package/package.json +3 -1
- package/plugins/ctx/.codex-plugin/plugin.json +1 -1
- package/plugins/ctx/lib/ctx-mcp-client.js +19 -0
- package/plugins/ctx/lib/embedding-scorer.js +34 -0
- package/plugins/ctx/lib/package-install.js +1 -1
- package/plugins/ctx/lib/prompt-hook.js +13 -2
- package/plugins/ctx/lib/setup-wizard.js +8 -3
- package/plugins/ctx/lib/skill-discoverer.js +439 -18
- package/plugins/ctx/mcp/contextos-server.js +29 -1
- package/plugins/ctx/mcp/server.js +50 -4
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
import { suggestSkills } from "../../plugins/ctx/lib/skill-discoverer.js";
|
|
7
|
+
|
|
8
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const evalRoot = __dirname;
|
|
10
|
+
|
|
11
|
+
export async function runSkillRoutingEval({
|
|
12
|
+
rootDir = path.resolve(evalRoot, "..", ".."),
|
|
13
|
+
casesPath = path.join(evalRoot, "cases.yaml"),
|
|
14
|
+
topK = 3,
|
|
15
|
+
threshold = 0.5
|
|
16
|
+
} = {}) {
|
|
17
|
+
const config = parseEvalYaml(fs.readFileSync(casesPath, "utf8"));
|
|
18
|
+
const skills = config.skills.map((skill) => ({
|
|
19
|
+
name: skill.id,
|
|
20
|
+
description: skill.description,
|
|
21
|
+
path: path.join(evalRoot, "skills", skill.id, "SKILL.md"),
|
|
22
|
+
metadata: skillToMetadata(skill)
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
const rows = [];
|
|
26
|
+
for (const testCase of config.cases) {
|
|
27
|
+
const cwd = testCase.fixture === "contextos"
|
|
28
|
+
? rootDir
|
|
29
|
+
: path.join(evalRoot, "fixtures", testCase.fixture);
|
|
30
|
+
const scores = semanticScores({ prompt: testCase.prompt, skills });
|
|
31
|
+
const suggestions = await suggestSkills({
|
|
32
|
+
cwd,
|
|
33
|
+
prompt: testCase.prompt,
|
|
34
|
+
skills,
|
|
35
|
+
limit: Math.max(topK, 10),
|
|
36
|
+
dataDir: path.join(evalRoot, ".tmp"),
|
|
37
|
+
indexedSearcher: async () => ({
|
|
38
|
+
status: "enabled",
|
|
39
|
+
items: skills.map((skill) => ({
|
|
40
|
+
id: normalizeSkillId(skill.name),
|
|
41
|
+
text: skill.name,
|
|
42
|
+
embeddingScore: scores.get(skill.name) || 0.45
|
|
43
|
+
}))
|
|
44
|
+
})
|
|
45
|
+
});
|
|
46
|
+
const selected = suggestions
|
|
47
|
+
.filter((skill) => Number(skill.confidence || skill.score || 0) >= threshold)
|
|
48
|
+
.slice(0, topK);
|
|
49
|
+
rows.push({
|
|
50
|
+
prompt: testCase.prompt,
|
|
51
|
+
fixture: testCase.fixture,
|
|
52
|
+
expected: testCase.expected,
|
|
53
|
+
allowed: testCase.allowed || [],
|
|
54
|
+
forbidden: testCase.forbidden,
|
|
55
|
+
selected,
|
|
56
|
+
selectedIds: selected.map((skill) => skill.name)
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return summarizeRows(rows, { topK });
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function formatSkillRoutingBenchmark(result) {
|
|
64
|
+
const percent = (value) => `${(value * 100).toFixed(1)}%`;
|
|
65
|
+
const failures = result.rows.filter((row) => row.failed);
|
|
66
|
+
const lines = [
|
|
67
|
+
"Skill Routing Benchmark",
|
|
68
|
+
`Cases: ${result.caseCount}`,
|
|
69
|
+
`Top-1 Accuracy: ${percent(result.top1Accuracy)}`,
|
|
70
|
+
`Top-3 Recall: ${percent(result.top3Recall)}`,
|
|
71
|
+
`False Positive Rate: ${percent(result.falsePositiveRate)}`,
|
|
72
|
+
`Confidence Calibration: ${percent(result.confidenceCalibration)}`,
|
|
73
|
+
`Negative Gate Accuracy: ${percent(result.negativeGateAccuracy)}`,
|
|
74
|
+
"",
|
|
75
|
+
failures.length ? "Failures:" : "Failures: none"
|
|
76
|
+
];
|
|
77
|
+
for (const row of failures) {
|
|
78
|
+
lines.push(`- ${row.fixture}: "${row.prompt}"`);
|
|
79
|
+
lines.push(` expected: ${row.expected.join(", ") || "(none)"}`);
|
|
80
|
+
if (row.allowed.length) {
|
|
81
|
+
lines.push(` allowed: ${row.allowed.join(", ")}`);
|
|
82
|
+
}
|
|
83
|
+
lines.push(` selected: ${row.selectedIds.join(", ") || "(none)"}`);
|
|
84
|
+
if (row.forbidden.length) {
|
|
85
|
+
lines.push(` rejected: ${row.forbidden.join(", ")}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return lines.join("\n");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function summarizeRows(rows, { topK }) {
|
|
92
|
+
let top1Hits = 0;
|
|
93
|
+
let recallHits = 0;
|
|
94
|
+
let recallTotal = 0;
|
|
95
|
+
let falsePositiveHits = 0;
|
|
96
|
+
let falsePositiveTotal = 0;
|
|
97
|
+
let negativeGateHits = 0;
|
|
98
|
+
let negativeGateTotal = 0;
|
|
99
|
+
let calibrationHits = 0;
|
|
100
|
+
let calibrationTotal = 0;
|
|
101
|
+
|
|
102
|
+
for (const row of rows) {
|
|
103
|
+
const expected = new Set(row.expected);
|
|
104
|
+
const accepted = new Set([...row.expected, ...row.allowed]);
|
|
105
|
+
const selected = row.selectedIds.slice(0, topK);
|
|
106
|
+
row.failed = false;
|
|
107
|
+
if (!row.expected.length) {
|
|
108
|
+
if (!selected.length) {
|
|
109
|
+
top1Hits += 1;
|
|
110
|
+
}
|
|
111
|
+
} else if (expected.has(selected[0])) {
|
|
112
|
+
top1Hits += 1;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
recallTotal += row.expected.length;
|
|
116
|
+
for (const skill of row.expected) {
|
|
117
|
+
if (selected.includes(skill)) {
|
|
118
|
+
recallHits += 1;
|
|
119
|
+
} else {
|
|
120
|
+
row.failed = true;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
falsePositiveTotal += selected.length;
|
|
125
|
+
for (const skill of selected) {
|
|
126
|
+
if (!accepted.has(skill)) {
|
|
127
|
+
falsePositiveHits += 1;
|
|
128
|
+
row.failed = true;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
negativeGateTotal += row.forbidden.length;
|
|
133
|
+
for (const skill of row.forbidden) {
|
|
134
|
+
if (!selected.includes(skill)) {
|
|
135
|
+
negativeGateHits += 1;
|
|
136
|
+
} else {
|
|
137
|
+
row.failed = true;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
for (const skill of row.selected) {
|
|
142
|
+
calibrationTotal += 1;
|
|
143
|
+
const acceptedSelected = accepted.has(skill.name);
|
|
144
|
+
const confident = Number(skill.confidence || 0) >= 0.65;
|
|
145
|
+
if (acceptedSelected === confident) calibrationHits += 1;
|
|
146
|
+
}
|
|
147
|
+
if (!row.selected.length && !row.expected.length) {
|
|
148
|
+
calibrationTotal += 1;
|
|
149
|
+
calibrationHits += 1;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
caseCount: rows.length,
|
|
155
|
+
top1Accuracy: rows.length ? top1Hits / rows.length : 0,
|
|
156
|
+
top3Recall: recallTotal ? recallHits / recallTotal : 1,
|
|
157
|
+
falsePositiveRate: falsePositiveTotal ? falsePositiveHits / falsePositiveTotal : 0,
|
|
158
|
+
confidenceCalibration: calibrationTotal ? calibrationHits / calibrationTotal : 1,
|
|
159
|
+
negativeGateAccuracy: negativeGateTotal ? negativeGateHits / negativeGateTotal : 1,
|
|
160
|
+
rows
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function skillToMetadata(skill) {
|
|
165
|
+
return {
|
|
166
|
+
id: skill.id,
|
|
167
|
+
name: skill.id,
|
|
168
|
+
positivePrompts: skill.positive_triggers?.prompts || [],
|
|
169
|
+
files: skill.positive_triggers?.files || [],
|
|
170
|
+
dependencies: skill.positive_triggers?.dependencies || [],
|
|
171
|
+
negativePrompts: skill.negative_triggers?.prompts || [],
|
|
172
|
+
negativeFiles: skill.negative_triggers?.files || [],
|
|
173
|
+
negativeDependencies: skill.negative_triggers?.dependencies || [],
|
|
174
|
+
relatedSkills: skill.related_skills || []
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function semanticScores({ prompt, skills }) {
|
|
179
|
+
const promptTokens = new Set(tokenize(prompt));
|
|
180
|
+
const scores = new Map();
|
|
181
|
+
for (const skill of skills) {
|
|
182
|
+
const tokens = new Set(tokenize(`${skill.name} ${skill.description}`));
|
|
183
|
+
const overlap = [...promptTokens].filter((token) => tokens.has(token)).length;
|
|
184
|
+
scores.set(skill.name, Math.max(0.45, Math.min(0.92, 0.5 + overlap * 0.1)));
|
|
185
|
+
}
|
|
186
|
+
return scores;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function tokenize(value) {
|
|
190
|
+
return String(value || "").toLowerCase().split(/[^a-z0-9@.-]+/).filter((token) => token.length > 2);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function normalizeSkillId(name) {
|
|
194
|
+
return String(name || "").toLowerCase().replace(/[^a-z0-9]+/g, " ").trim();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export function parseEvalYaml(content) {
|
|
198
|
+
const lines = String(content || "").split(/\r?\n/);
|
|
199
|
+
const result = { skills: [], cases: [] };
|
|
200
|
+
let section = null;
|
|
201
|
+
let current = null;
|
|
202
|
+
let triggerGroup = null;
|
|
203
|
+
|
|
204
|
+
for (const rawLine of lines) {
|
|
205
|
+
if (!rawLine.trim() || rawLine.trimStart().startsWith("#")) continue;
|
|
206
|
+
const line = rawLine.trim();
|
|
207
|
+
if (line === "skills:") {
|
|
208
|
+
section = "skills";
|
|
209
|
+
current = null;
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
if (line === "cases:") {
|
|
213
|
+
section = "cases";
|
|
214
|
+
current = null;
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
if (line.startsWith("- id:")) {
|
|
218
|
+
current = {
|
|
219
|
+
id: scalar(line.slice(5)),
|
|
220
|
+
description: "",
|
|
221
|
+
positive_triggers: {},
|
|
222
|
+
negative_triggers: {},
|
|
223
|
+
related_skills: []
|
|
224
|
+
};
|
|
225
|
+
result.skills.push(current);
|
|
226
|
+
triggerGroup = null;
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
if (line.startsWith("- prompt:")) {
|
|
230
|
+
current = {
|
|
231
|
+
prompt: scalar(line.slice(9)),
|
|
232
|
+
fixture: "",
|
|
233
|
+
expected: [],
|
|
234
|
+
allowed: [],
|
|
235
|
+
forbidden: []
|
|
236
|
+
};
|
|
237
|
+
result.cases.push(current);
|
|
238
|
+
triggerGroup = null;
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
if (!current) continue;
|
|
242
|
+
if (line === "positive_triggers:") {
|
|
243
|
+
triggerGroup = current.positive_triggers;
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
if (line === "negative_triggers:") {
|
|
247
|
+
triggerGroup = current.negative_triggers;
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
const match = line.match(/^([A-Za-z0-9_-]+):\s*(.*)$/);
|
|
251
|
+
if (!match) continue;
|
|
252
|
+
const [, key, rawValue] = match;
|
|
253
|
+
const normalizedKey = key === "rejected" ? "forbidden" : key;
|
|
254
|
+
const target = triggerGroup && ["prompts", "files", "dependencies"].includes(key)
|
|
255
|
+
? triggerGroup
|
|
256
|
+
: current;
|
|
257
|
+
target[normalizedKey] = parseValue(rawValue);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return result;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function parseValue(value) {
|
|
264
|
+
const trimmed = String(value || "").trim();
|
|
265
|
+
if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
|
|
266
|
+
return trimmed.slice(1, -1).split(",").map(scalar).filter(Boolean);
|
|
267
|
+
}
|
|
268
|
+
return scalar(trimmed);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function scalar(value) {
|
|
272
|
+
return String(value || "").trim().replace(/^["']|["']$/g, "");
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
276
|
+
const result = await runSkillRoutingEval();
|
|
277
|
+
console.log(formatSkillRoutingBenchmark(result));
|
|
278
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@minhpnq1807/contextos",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.53",
|
|
4
4
|
"description": "Task-aware AGENTS.md context injection and compliance reporting for Codex, Claude Code, and Antigravity.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"README.md",
|
|
15
15
|
"DEMO.md",
|
|
16
16
|
"LAUNCH.md",
|
|
17
|
+
"eval/",
|
|
17
18
|
"docs/",
|
|
18
19
|
"LICENSE",
|
|
19
20
|
"CHANGELOG.md"
|
|
@@ -22,6 +23,7 @@
|
|
|
22
23
|
"test": "vitest run test",
|
|
23
24
|
"build": "node bin/ctx.js --version",
|
|
24
25
|
"validate:plugin": "node test/validate-plugin.js",
|
|
26
|
+
"benchmark:skills": "node bin/ctx.js benchmark --skills",
|
|
25
27
|
"test:mcp": "node test/mcp-protocol-smoke.js"
|
|
26
28
|
},
|
|
27
29
|
"engines": {
|
|
@@ -29,6 +29,25 @@ export async function callCtxScoreContext(payload, {
|
|
|
29
29
|
connectTimeoutMs = Number(process.env.CONTEXTOS_MCP_CONNECT_TIMEOUT_MS || DEFAULT_CONNECT_TIMEOUT_MS),
|
|
30
30
|
createConnection = net.createConnection
|
|
31
31
|
} = {}) {
|
|
32
|
+
return callBridge(payload, { dataDir, timeoutMs, connectTimeoutMs, createConnection });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function callCtxHealth({
|
|
36
|
+
dataDir = defaultDataDir(),
|
|
37
|
+
timeoutMs = Number(process.env.CONTEXTOS_MCP_HEALTH_TIMEOUT_MS || 250),
|
|
38
|
+
connectTimeoutMs = Number(process.env.CONTEXTOS_MCP_CONNECT_TIMEOUT_MS || DEFAULT_CONNECT_TIMEOUT_MS),
|
|
39
|
+
createConnection = net.createConnection
|
|
40
|
+
} = {}) {
|
|
41
|
+
const response = await callBridge({ type: "health" }, { dataDir, timeoutMs, connectTimeoutMs, createConnection });
|
|
42
|
+
return response.health || {};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function callBridge(payload, {
|
|
46
|
+
dataDir,
|
|
47
|
+
timeoutMs,
|
|
48
|
+
connectTimeoutMs,
|
|
49
|
+
createConnection
|
|
50
|
+
}) {
|
|
32
51
|
const socketPath = ctxMcpSocketPath(dataDir);
|
|
33
52
|
if (!fs.existsSync(socketPath)) {
|
|
34
53
|
throw new Error(`ctx-mcp bridge socket not found: ${socketPath}`);
|
|
@@ -152,6 +152,40 @@ export async function warmIndexedEmbeddings({
|
|
|
152
152
|
}
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
+
export async function preloadEmbeddingPipeline({
|
|
156
|
+
dataDir = defaultDataRoot(),
|
|
157
|
+
allowRemote = false,
|
|
158
|
+
warmText = "contextos warmup"
|
|
159
|
+
} = {}) {
|
|
160
|
+
if (!allowRemote && !isModelCacheReady(dataDir)) {
|
|
161
|
+
return { status: "missing-model", loaded: false, cachePath: path.join(dataDir, "embeddings.db") };
|
|
162
|
+
}
|
|
163
|
+
const started = Date.now();
|
|
164
|
+
try {
|
|
165
|
+
const embedder = await getExtractor({ allowRemote, dataDir });
|
|
166
|
+
await embedder(String(warmText || "contextos warmup"), {
|
|
167
|
+
pooling: "mean",
|
|
168
|
+
normalize: true
|
|
169
|
+
});
|
|
170
|
+
return {
|
|
171
|
+
status: "loaded",
|
|
172
|
+
loaded: true,
|
|
173
|
+
model: DEFAULT_MODEL,
|
|
174
|
+
cachePath: path.join(dataDir, "embeddings.db"),
|
|
175
|
+
elapsedMs: Date.now() - started
|
|
176
|
+
};
|
|
177
|
+
} catch (error) {
|
|
178
|
+
return {
|
|
179
|
+
status: "load-failed",
|
|
180
|
+
loaded: false,
|
|
181
|
+
model: DEFAULT_MODEL,
|
|
182
|
+
cachePath: path.join(dataDir, "embeddings.db"),
|
|
183
|
+
elapsedMs: Date.now() - started,
|
|
184
|
+
error: error?.message || String(error)
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
155
189
|
async function enhanceRuleScores(rules, task, { dataDir, sources, allowRemote }) {
|
|
156
190
|
const cache = await openEmbeddingCache(dataDir);
|
|
157
191
|
const embedder = await getExtractor({ allowRemote, dataDir });
|
|
@@ -28,7 +28,7 @@ export function copyPath(src, dest) {
|
|
|
28
28
|
|
|
29
29
|
export function copyPackageRoot({ rootDir, targetRoot }) {
|
|
30
30
|
fs.rmSync(targetRoot, { recursive: true, force: true });
|
|
31
|
-
for (const entry of [".agents", "bin", "plugins", "package.json", "package-lock.json", "README.md", "LICENSE", "node_modules"]) {
|
|
31
|
+
for (const entry of [".agents", "bin", "plugins", "eval", "docs", "package.json", "package-lock.json", "README.md", "CHANGELOG.md", "DEMO.md", "LAUNCH.md", "LICENSE", "node_modules"]) {
|
|
32
32
|
const src = path.join(rootDir, entry);
|
|
33
33
|
if (fs.existsSync(src)) copyPath(src, path.join(targetRoot, entry));
|
|
34
34
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { scheduleContext } from "./scheduler.js";
|
|
2
2
|
import { appendJsonLine, writeJsonFile } from "./fs-utils.js";
|
|
3
3
|
import { maybeAutoWarmWorkspace } from "./auto-warm.js";
|
|
4
|
-
import { callCtxScoreContext } from "./ctx-mcp-client.js";
|
|
4
|
+
import { callCtxHealth, callCtxScoreContext } from "./ctx-mcp-client.js";
|
|
5
5
|
import { resolveHookCwd } from "./hook-io.js";
|
|
6
6
|
import { loadOutputConfig, outputConfigLimits } from "./output-config.js";
|
|
7
7
|
import { scoreContext as scoreContextDirect } from "./score-context.js";
|
|
@@ -17,11 +17,13 @@ export async function handlePromptPayload(
|
|
|
17
17
|
started = Date.now(),
|
|
18
18
|
injectContext = process.env.CONTEXTOS_INJECT !== "0",
|
|
19
19
|
scoreContextClient = callCtxScoreContext,
|
|
20
|
+
healthContextClient = callCtxHealth,
|
|
20
21
|
scoreContextDirectClient = scoreContextDirect,
|
|
21
22
|
autoWarmWorkspace = maybeAutoWarmWorkspace,
|
|
22
23
|
mcpDataDir,
|
|
23
24
|
outputConfig,
|
|
24
|
-
directFallbackTimeoutMs = Number(process.env.CONTEXTOS_DIRECT_FALLBACK_TIMEOUT_MS || 2500)
|
|
25
|
+
directFallbackTimeoutMs = Number(process.env.CONTEXTOS_DIRECT_FALLBACK_TIMEOUT_MS || 2500),
|
|
26
|
+
requireHotMcp = scoreContextClient === callCtxScoreContext
|
|
25
27
|
} = {}
|
|
26
28
|
) {
|
|
27
29
|
const prompt = payload.prompt || payload.message || payload.user_prompt || "";
|
|
@@ -34,6 +36,15 @@ export async function handlePromptPayload(
|
|
|
34
36
|
|
|
35
37
|
let scored;
|
|
36
38
|
try {
|
|
39
|
+
if (requireHotMcp) {
|
|
40
|
+
const health = await healthContextClient({
|
|
41
|
+
dataDir: mcpDataDir || dataDir,
|
|
42
|
+
timeoutMs: Number(process.env.CONTEXTOS_MCP_HEALTH_TIMEOUT_MS || 250)
|
|
43
|
+
});
|
|
44
|
+
if (!health.embedding_pipeline_loaded) {
|
|
45
|
+
throw new Error(`ctx-mcp scorer not hot: ${health.preload_status || "unknown"}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
37
48
|
scored = await scoreContextClient({
|
|
38
49
|
cwd,
|
|
39
50
|
prompt,
|
|
@@ -1,17 +1,22 @@
|
|
|
1
|
-
//
|
|
1
|
+
// Interactive setup starts empty so users choose intentionally. Non-interactive
|
|
2
|
+
// --yes needs a deterministic target, so it defaults to Codex.
|
|
2
3
|
const DEFAULT_AGENTS = [];
|
|
4
|
+
const DEFAULT_YES_AGENTS = ["codex"];
|
|
3
5
|
|
|
4
6
|
export function parseSetupArgs(args = []) {
|
|
5
7
|
const agentsFlag = args.indexOf("--agents");
|
|
6
8
|
const agentsProvided = agentsFlag >= 0;
|
|
9
|
+
const yes = args.includes("--yes") || args.includes("-y");
|
|
7
10
|
const agents = agentsFlag >= 0
|
|
8
11
|
? parseAgentList(args[agentsFlag + 1])
|
|
9
|
-
:
|
|
12
|
+
: yes
|
|
13
|
+
? DEFAULT_YES_AGENTS
|
|
14
|
+
: DEFAULT_AGENTS;
|
|
10
15
|
|
|
11
16
|
return {
|
|
12
17
|
agents,
|
|
13
18
|
agentsProvided,
|
|
14
|
-
yes
|
|
19
|
+
yes,
|
|
15
20
|
quiet: args.includes("--quiet"),
|
|
16
21
|
syncRules: !args.includes("--no-rules"),
|
|
17
22
|
syncSkills: !args.includes("--no-skills")
|