@minhpnq1807/contextos 0.5.19 → 0.5.21
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 +11 -0
- package/bin/ctx.js +15 -10
- package/package.json +1 -1
- package/plugins/ctx/lib/analyzer.js +1 -1
- package/plugins/ctx/lib/prompt-hook.js +26 -9
- package/plugins/ctx/lib/ruler-sync.js +10 -1
- package/plugins/ctx/lib/scheduler.js +18 -3
- package/plugins/ctx/lib/workflow-discoverer.js +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.21
|
|
4
|
+
|
|
5
|
+
- Makes prompt hooks fall back to direct scoring when the `ctx-mcp` bridge socket is missing, stale, or unavailable, avoiding empty `hook context` output.
|
|
6
|
+
- Prioritizes imperative and code-review-graph rules in injected critical context so `IMPORTANT` project rules appear before generic semantic matches.
|
|
7
|
+
- Improves workflow detection for CI, deployment, server, runtime, debugging, and issue-analysis prompts.
|
|
8
|
+
|
|
9
|
+
## 0.5.20
|
|
10
|
+
|
|
11
|
+
- Refreshes stale `ctx-mcp` Ruler entries that point at temporary paths such as `/tmp/contextos/...`.
|
|
12
|
+
- Keeps `ctx install` fast by skipping large skill/workflow discovery embedding warmup unless `CONTEXTOS_INSTALL_WARM_DISCOVERY=1` is set.
|
|
13
|
+
|
|
3
14
|
## 0.5.19
|
|
4
15
|
|
|
5
16
|
- Makes `ctx-mcp` startup non-mutating: the MCP server now verifies the local embedding model without warming or rewriting `embeddings.db` during agent initialization.
|
package/bin/ctx.js
CHANGED
|
@@ -299,16 +299,21 @@ async function warmInstallEmbeddings() {
|
|
|
299
299
|
dataDir,
|
|
300
300
|
allowRemote: !modelReady
|
|
301
301
|
});
|
|
302
|
-
const
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
302
|
+
const warmDiscovery = process.env.CONTEXTOS_INSTALL_WARM_DISCOVERY === "1";
|
|
303
|
+
const skillResult = warmDiscovery
|
|
304
|
+
? await warmSkillEmbeddings({
|
|
305
|
+
cwd: process.cwd(),
|
|
306
|
+
dataDir,
|
|
307
|
+
allowRemote: !modelReady
|
|
308
|
+
})
|
|
309
|
+
: { count: 0 };
|
|
310
|
+
const workflowResult = warmDiscovery
|
|
311
|
+
? await warmWorkflowEmbeddings({
|
|
312
|
+
cwd: process.cwd(),
|
|
313
|
+
dataDir,
|
|
314
|
+
allowRemote: !modelReady
|
|
315
|
+
})
|
|
316
|
+
: { count: 0 };
|
|
312
317
|
return { ...result, modelAlreadyCached: modelReady, fileCount: fileResult.count, skillCount: skillResult.count, workflowCount: workflowResult.count };
|
|
313
318
|
}
|
|
314
319
|
|
package/package.json
CHANGED
|
@@ -259,7 +259,7 @@ export function scoreRules(rules, task, openFiles = []) {
|
|
|
259
259
|
|
|
260
260
|
const lowerRule = rule.content.toLowerCase();
|
|
261
261
|
if (IMPORTANT_WORDS.some((word) => lowerRule.includes(word))) {
|
|
262
|
-
score += 0.
|
|
262
|
+
score += 0.5;
|
|
263
263
|
reasons.push("imperative");
|
|
264
264
|
}
|
|
265
265
|
|
|
@@ -2,6 +2,7 @@ import { scheduleContext } from "./scheduler.js";
|
|
|
2
2
|
import { appendJsonLine, writeJsonFile } from "./fs-utils.js";
|
|
3
3
|
import { callCtxScoreContext } from "./ctx-mcp-client.js";
|
|
4
4
|
import { resolveHookCwd } from "./hook-io.js";
|
|
5
|
+
import { scoreContext as scoreContextDirect } from "./score-context.js";
|
|
5
6
|
import path from "node:path";
|
|
6
7
|
|
|
7
8
|
export async function handlePromptPayload(
|
|
@@ -21,15 +22,31 @@ export async function handlePromptPayload(
|
|
|
21
22
|
const openFiles = payload.openFiles || payload.open_files || payload.files || [];
|
|
22
23
|
const dataDir = dataPath ? path.dirname(dataPath) : undefined;
|
|
23
24
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
25
|
+
let scored;
|
|
26
|
+
try {
|
|
27
|
+
scored = await scoreContextClient({
|
|
28
|
+
cwd,
|
|
29
|
+
prompt,
|
|
30
|
+
openFiles,
|
|
31
|
+
maxFiles: 3
|
|
32
|
+
}, {
|
|
33
|
+
dataDir: mcpDataDir || dataDir,
|
|
34
|
+
timeoutMs: Number(process.env.CONTEXTOS_MCP_BRIDGE_TIMEOUT_MS || 1000)
|
|
35
|
+
});
|
|
36
|
+
} catch (error) {
|
|
37
|
+
scored = await scoreContextDirect({
|
|
38
|
+
cwd,
|
|
39
|
+
prompt,
|
|
40
|
+
openFiles,
|
|
41
|
+
maxFiles: 3,
|
|
42
|
+
dataDir: mcpDataDir || dataDir
|
|
43
|
+
});
|
|
44
|
+
scored.telemetry = {
|
|
45
|
+
...(scored.telemetry || {}),
|
|
46
|
+
bridgeStatus: "fallback",
|
|
47
|
+
bridgeError: error?.message || String(error)
|
|
48
|
+
};
|
|
49
|
+
}
|
|
33
50
|
|
|
34
51
|
if (scored.error) throw new Error(scored.error);
|
|
35
52
|
const scoredRules = scored.scoredRules || [];
|
|
@@ -268,6 +268,10 @@ function readRulerMcpServers({ tomlPath } = {}) {
|
|
|
268
268
|
return servers;
|
|
269
269
|
}
|
|
270
270
|
|
|
271
|
+
function readRulerMcpServer({ tomlPath, name } = {}) {
|
|
272
|
+
return readRulerMcpServers({ tomlPath }).find((server) => server.name === name) || null;
|
|
273
|
+
}
|
|
274
|
+
|
|
271
275
|
function antigravityMcpConfigPaths() {
|
|
272
276
|
const home = process.env.HOME || process.cwd();
|
|
273
277
|
return [
|
|
@@ -415,7 +419,12 @@ export function injectCtxMcp({ tomlPath, mcpServerPath, agents = DEFAULT_AGENTS,
|
|
|
415
419
|
|
|
416
420
|
let content = fs.readFileSync(tomlPath, "utf8");
|
|
417
421
|
const sectionExists = hasTomlSection(content, `mcp_servers.${CTX_MCP_NAME}`);
|
|
418
|
-
if (sectionExists && !force)
|
|
422
|
+
if (sectionExists && !force) {
|
|
423
|
+
const existingServer = readRulerMcpServer({ tomlPath, name: CTX_MCP_NAME });
|
|
424
|
+
const existingPath = existingServer?.command === "node" ? existingServer.args?.[0] : existingServer?.command;
|
|
425
|
+
if (existingPath && isRunnableMcpCommand(existingPath)) return { changed: false, existed: true };
|
|
426
|
+
force = true;
|
|
427
|
+
}
|
|
419
428
|
|
|
420
429
|
if (force) {
|
|
421
430
|
content = removeTomlSection(content, "mcp");
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
const MAX_CONTEXT_CHARS = 4000;
|
|
2
2
|
|
|
3
3
|
export function scheduleContext({ rules = [], relevantFiles = [], suggestedSkills = [], suggestedWorkflows = [], maxChars = MAX_CONTEXT_CHARS } = {}) {
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const
|
|
4
|
+
const orderedRules = [...rules].sort(compareRulesForContext);
|
|
5
|
+
const high = orderedRules.filter((rule) => rule.score >= 0.5);
|
|
6
|
+
const mid = orderedRules.filter((rule) => rule.score >= 0.1 && rule.score < 0.5);
|
|
7
|
+
const dropped = orderedRules.filter((rule) => rule.score < 0.1);
|
|
7
8
|
|
|
8
9
|
const sections = [];
|
|
9
10
|
if (high.length) {
|
|
@@ -37,6 +38,20 @@ export function scheduleContext({ rules = [], relevantFiles = [], suggestedSkill
|
|
|
37
38
|
};
|
|
38
39
|
}
|
|
39
40
|
|
|
41
|
+
function compareRulesForContext(a, b) {
|
|
42
|
+
return rulePriority(b) - rulePriority(a)
|
|
43
|
+
|| Number(b.score || 0) - Number(a.score || 0)
|
|
44
|
+
|| Number(a.originalOrder || 0) - Number(b.originalOrder || 0);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function rulePriority(rule) {
|
|
48
|
+
const content = String(rule.content || "").toLowerCase();
|
|
49
|
+
let priority = 0;
|
|
50
|
+
if (/\b(important|always|must|required|mandatory|strictly|never)\b/.test(content)) priority += 10;
|
|
51
|
+
if (/\b(code-review-graph|query_graph|get_minimal_context|detect_changes|semantic_search_nodes)\b/.test(content)) priority += 4;
|
|
52
|
+
return priority;
|
|
53
|
+
}
|
|
54
|
+
|
|
40
55
|
function section(title, lines) {
|
|
41
56
|
if (!lines.length) return "";
|
|
42
57
|
return `## ${title}\n${lines.join("\n")}`;
|
|
@@ -320,12 +320,12 @@ function scoreWorkflowsByKeyword({ prompt, workflows }) {
|
|
|
320
320
|
|
|
321
321
|
function actionIntentBonus(normalizedPrompt, workflow) {
|
|
322
322
|
const name = normalize(`${workflow.name} ${workflow.title} ${workflow.description}`);
|
|
323
|
-
const implementationIntent = /\b(implement|feature|build|create|fix|debug|test|ci|failing)\b/.test(normalizedPrompt);
|
|
323
|
+
const implementationIntent = /\b(implement|feature|build|create|fix|debug|test|ci|cd|pipeline|failing|failure|error|issue|bug|analyze|analyse|solution|server|runtime|deploy|fly|flyio|loi|phan tich|giai phap)\b/.test(normalizedPrompt);
|
|
324
324
|
const docsIntent = /\b(doc|docs|documentation|readme|changelog|roadmap)\b/.test(normalizedPrompt);
|
|
325
325
|
const orchestrationIntent = /\b(parallel|sequential|chain|delegate|agent|subagent|orchestrat)\b/.test(normalizedPrompt);
|
|
326
|
-
if (implementationIntent && /\b(primary|implementation|testing|debugging|quality)\b/.test(name)) return 0.
|
|
327
|
-
if (docsIntent && /\b(documentation|docs|changelog|roadmap)\b/.test(name)) return 0.
|
|
328
|
-
if (orchestrationIntent && /\b(orchestration|parallel|sequential|chaining)\b/.test(name)) return 0.
|
|
326
|
+
if (implementationIntent && /\b(primary|implementation|testing|debugging|quality)\b/.test(name)) return 0.42;
|
|
327
|
+
if (docsIntent && /\b(documentation|docs|changelog|roadmap)\b/.test(name)) return 0.42;
|
|
328
|
+
if (orchestrationIntent && /\b(orchestration|parallel|sequential|chaining)\b/.test(name)) return 0.42;
|
|
329
329
|
return 0;
|
|
330
330
|
}
|
|
331
331
|
|