@minhpnq1807/contextos 0.1.3 → 0.1.5
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 +10 -0
- package/README.md +3 -2
- package/bin/ctx.js +2 -2
- package/package.json +1 -1
- package/plugins/ctx/lib/analyzer.js +25 -0
- package/plugins/ctx/lib/reporter.js +25 -3
- package/plugins/ctx/lib/score-context.js +4 -2
- package/plugins/ctx/lib/stop-hook.js +15 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.5
|
|
4
|
+
|
|
5
|
+
- Sanitizes stale Stop reports at display time so previously recorded system-user rules no longer appear in `ctx report` or `ctx evidence` after upgrading.
|
|
6
|
+
- Filters system-user rules again inside the Stop hook to protect reports created from older prompt contexts.
|
|
7
|
+
|
|
8
|
+
## 0.1.4
|
|
9
|
+
|
|
10
|
+
- Filters host/session user rules such as `sudo -u user`, `sudo su - user`, and "commands must run as user X" before scoring and injection.
|
|
11
|
+
- Prevents system-user setup instructions from inflating `unknown` outcomes or skewing ContextOS efficiency reports.
|
|
12
|
+
|
|
3
13
|
## 0.1.3
|
|
4
14
|
|
|
5
15
|
- Separates runtime prompt/report/stats files per workspace under `~/.ctx/contextos/workspaces/<workspace-id>`.
|
package/README.md
CHANGED
|
@@ -53,7 +53,7 @@ With ContextOS, each prompt gets a compact block:
|
|
|
53
53
|
```text
|
|
54
54
|
## Critical ContextOS rules
|
|
55
55
|
- Use code-review-graph before reading files.
|
|
56
|
-
-
|
|
56
|
+
- Check upload moderation flows before editing approval code.
|
|
57
57
|
|
|
58
58
|
## Suggested files to check
|
|
59
59
|
- services/content-service/src/infrastructure/services/content-moderation.service.ts
|
|
@@ -66,6 +66,7 @@ With ContextOS, each prompt gets a compact block:
|
|
|
66
66
|
- Registers a `ctx-mcp` MCP server that owns model loading and semantic scoring.
|
|
67
67
|
- Reads the active `AGENTS.md` chain for the current workspace.
|
|
68
68
|
- Scores rules by relevance to the user prompt.
|
|
69
|
+
- Filters host/session setup rules such as "run commands as user X" or `sudo -u user` because they are environment instructions, not project guidance.
|
|
69
70
|
- Finds likely relevant files with a hybrid retriever:
|
|
70
71
|
- first, local prompt/file heuristics create seed candidates;
|
|
71
72
|
- then, if `.code-review-graph/graph.db` exists, ContextOS queries `code-review-graph` semantic search and re-ranks graph-backed matches;
|
|
@@ -288,7 +289,7 @@ unknown = the rule was relevant, but the diff does not prove either way
|
|
|
288
289
|
|
|
289
290
|
For runtime-only rules, ContextOS also checks `telemetry.jsonl` for hook-visible tool names, MCP server names, and command metadata. A rule like "use code-review-graph before reading files" can be marked `followed` when telemetry contains a matching `code-review-graph` signal.
|
|
290
291
|
|
|
291
|
-
|
|
292
|
+
Host/session setup rules such as "run shell commands as user X", `sudo su - user`, `sudo -i -u user`, and `sudo -u user` are filtered before scoring. They are not injected and do not count toward `unknown` outcomes because they describe the agent runtime environment rather than project behavior.
|
|
292
293
|
|
|
293
294
|
## Development
|
|
294
295
|
|
package/bin/ctx.js
CHANGED
|
@@ -5,7 +5,7 @@ import { fileURLToPath } from "node:url";
|
|
|
5
5
|
import { execFileSync } from "node:child_process";
|
|
6
6
|
|
|
7
7
|
import { readAgentsChain } from "../plugins/ctx/lib/reader.js";
|
|
8
|
-
import { parseRules, scoreRules } from "../plugins/ctx/lib/analyzer.js";
|
|
8
|
+
import { filterActionableRules, parseRules, scoreRules } from "../plugins/ctx/lib/analyzer.js";
|
|
9
9
|
import { scheduleContext } from "../plugins/ctx/lib/scheduler.js";
|
|
10
10
|
import { formatEvidence, formatReport } from "../plugins/ctx/lib/reporter.js";
|
|
11
11
|
import { installGlobalHooks } from "../plugins/ctx/lib/global-hooks.js";
|
|
@@ -222,7 +222,7 @@ async function debug(task) {
|
|
|
222
222
|
async function warmEmbeddings(task) {
|
|
223
223
|
const cwd = process.cwd();
|
|
224
224
|
const merged = readAgentsChain({ cwd });
|
|
225
|
-
const rules = scoreRules(parseRules(merged.content), task, []);
|
|
225
|
+
const rules = scoreRules(filterActionableRules(parseRules(merged.content)), task, []);
|
|
226
226
|
const result = await warmRuleEmbeddings({
|
|
227
227
|
rules,
|
|
228
228
|
task,
|
package/package.json
CHANGED
|
@@ -48,6 +48,20 @@ const SEMANTIC_ALIASES = {
|
|
|
48
48
|
|
|
49
49
|
const MODERATION_TOKENS = new Set(["moderation", "moderate", "content-moderation", "approval", "approved", "reject", "rejected", "needs_review"]);
|
|
50
50
|
|
|
51
|
+
const SYSTEM_USER_RULE_PATTERNS = [
|
|
52
|
+
/\ball\s+shell\s+commands?\s+must\s+run\s+as\b/i,
|
|
53
|
+
/\bcommands?\s+must\s+run\s+as\b/i,
|
|
54
|
+
/\bstrictly\s+follow\s+this\s+sequence\b/i,
|
|
55
|
+
/\bswitch\s+the\s+user\s+context\b/i,
|
|
56
|
+
/\bdo\s+not\s+prefix\b.*\bsudo\s+-u\b/i,
|
|
57
|
+
/\bsudo\s+su\s+-\s*[a-z_][a-z0-9_-]*\b/i,
|
|
58
|
+
/\bsudo\s+-i\s+-u\s+[a-z_][a-z0-9_-]*\b/i,
|
|
59
|
+
/\bsudo\s+-u\s+[a-z_][a-z0-9_-]*\b/i,
|
|
60
|
+
/\bsu\s+-\s+[a-z_][a-z0-9_-]*\b/i,
|
|
61
|
+
/[/\\]\.codex[/\\]RTK\.md\b/i,
|
|
62
|
+
/\bminh_dev\b/i
|
|
63
|
+
];
|
|
64
|
+
|
|
51
65
|
export function tokenize(value) {
|
|
52
66
|
const normalized = String(value || "")
|
|
53
67
|
.toLowerCase()
|
|
@@ -138,6 +152,17 @@ export function parseRules(markdown) {
|
|
|
138
152
|
return dedupeRules(rules);
|
|
139
153
|
}
|
|
140
154
|
|
|
155
|
+
export function filterActionableRules(rules = []) {
|
|
156
|
+
return rules
|
|
157
|
+
.filter((rule) => !isSystemUserRule(rule))
|
|
158
|
+
.map((rule, index) => ({ ...rule, id: `r${index + 1}`, originalOrder: index }));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function isSystemUserRule(rule) {
|
|
162
|
+
const content = typeof rule === "string" ? rule : rule?.content;
|
|
163
|
+
return SYSTEM_USER_RULE_PATTERNS.some((pattern) => pattern.test(String(content || "")));
|
|
164
|
+
}
|
|
165
|
+
|
|
141
166
|
function dedupeRules(rules) {
|
|
142
167
|
const seen = new Set();
|
|
143
168
|
return rules.filter((rule) => {
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import { isSystemUserRule } from "./analyzer.js";
|
|
2
|
+
|
|
1
3
|
export function buildReport({ cwd, prompt, relevantFiles, scheduled, gitSnapshot, compliance, runtimeEvidence }) {
|
|
2
|
-
const
|
|
3
|
-
const
|
|
4
|
-
const
|
|
4
|
+
const actionableCompliance = compliance.filter((item) => !isSystemUserRule(item.rule));
|
|
5
|
+
const followed = actionableCompliance.filter((item) => item.status === "followed");
|
|
6
|
+
const ignored = actionableCompliance.filter((item) => item.status === "ignored");
|
|
7
|
+
const unknown = actionableCompliance.filter((item) => item.status === "unknown");
|
|
5
8
|
const measured = followed.length + ignored.length;
|
|
6
9
|
const efficiencyScore = measured ? Math.round((followed.length / measured) * 100) : null;
|
|
7
10
|
|
|
@@ -24,6 +27,7 @@ export function buildReport({ cwd, prompt, relevantFiles, scheduled, gitSnapshot
|
|
|
24
27
|
}
|
|
25
28
|
|
|
26
29
|
export function formatReport(report) {
|
|
30
|
+
report = sanitizeReport(report);
|
|
27
31
|
const lines = [];
|
|
28
32
|
lines.push("ContextOS report");
|
|
29
33
|
lines.push(`Efficiency: ${report.efficiencyScore == null ? "unknown" : `${report.efficiencyScore}%`}`);
|
|
@@ -55,6 +59,7 @@ export function formatReport(report) {
|
|
|
55
59
|
}
|
|
56
60
|
|
|
57
61
|
export function formatEvidence(report) {
|
|
62
|
+
report = sanitizeReport(report);
|
|
58
63
|
const lines = [];
|
|
59
64
|
lines.push("ContextOS evidence");
|
|
60
65
|
lines.push(`Prompt: ${report.prompt || "(empty)"}`);
|
|
@@ -119,3 +124,20 @@ function summarizeRuntimeEvidence(runtimeEvidence = {}) {
|
|
|
119
124
|
sources: (runtimeEvidence.sources || []).slice(0, 10)
|
|
120
125
|
};
|
|
121
126
|
}
|
|
127
|
+
|
|
128
|
+
function sanitizeReport(report = {}) {
|
|
129
|
+
const followed = (report.followed || []).filter((item) => !isSystemUserRule(item.rule));
|
|
130
|
+
const ignored = (report.ignored || []).filter((item) => !isSystemUserRule(item.rule));
|
|
131
|
+
const unknown = (report.unknown || []).filter((item) => !isSystemUserRule(item.rule));
|
|
132
|
+
const measured = followed.length + ignored.length;
|
|
133
|
+
return {
|
|
134
|
+
...report,
|
|
135
|
+
injectedRuleCount: followed.length + ignored.length + unknown.length,
|
|
136
|
+
followed,
|
|
137
|
+
ignored,
|
|
138
|
+
unknown,
|
|
139
|
+
measuredRuleCount: measured,
|
|
140
|
+
unknownRuleCount: unknown.length,
|
|
141
|
+
efficiencyScore: measured ? Math.round((followed.length / measured) * 100) : null
|
|
142
|
+
};
|
|
143
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
|
|
3
3
|
import { readAgentsChain } from "./reader.js";
|
|
4
|
-
import { parseRules, scoreRules, findRelevantFiles } from "./analyzer.js";
|
|
4
|
+
import { filterActionableRules, parseRules, scoreRules, findRelevantFiles } from "./analyzer.js";
|
|
5
5
|
import { enhanceRuleScoresWithEmbeddings } from "./embedding-scorer.js";
|
|
6
6
|
|
|
7
7
|
export async function scoreContext({
|
|
@@ -15,7 +15,8 @@ export async function scoreContext({
|
|
|
15
15
|
} = {}) {
|
|
16
16
|
const started = Date.now();
|
|
17
17
|
const merged = readAgentsChain({ cwd });
|
|
18
|
-
const
|
|
18
|
+
const rawRules = parseRules(merged.content);
|
|
19
|
+
const parsedRules = filterActionableRules(rawRules);
|
|
19
20
|
const baseScoredRules = scoreRules(parsedRules, prompt, openFiles);
|
|
20
21
|
const embedding = await enhanceRuleScoresWithEmbeddings(baseScoredRules, prompt, {
|
|
21
22
|
dataDir,
|
|
@@ -47,6 +48,7 @@ export async function scoreContext({
|
|
|
47
48
|
model: embedding.model,
|
|
48
49
|
cachePath: embedding.cachePath,
|
|
49
50
|
rulesParsed: parsedRules.length,
|
|
51
|
+
rulesFiltered: rawRules.length - parsedRules.length,
|
|
50
52
|
rulesInjected: scoredRules.filter((rule) => Number(rule.score || 0) >= 0.1).length,
|
|
51
53
|
filesSuggested: suggestedFiles.length,
|
|
52
54
|
sources: merged.sources.map((source) => path.relative(cwd, source))
|
|
@@ -4,14 +4,27 @@ import { appendJsonLine, readJsonFile, writeJsonFile } from "./fs-utils.js";
|
|
|
4
4
|
import { readGitSnapshot, checkCompliance } from "./measure.js";
|
|
5
5
|
import { buildReport, formatReport } from "./reporter.js";
|
|
6
6
|
import { loadRuntimeEvidence } from "./telemetry.js";
|
|
7
|
+
import { filterActionableRules } from "./analyzer.js";
|
|
7
8
|
|
|
8
9
|
export function handleStopPayload(payload, { contextPath, reportPath, historyPath, telemetryPath } = {}) {
|
|
9
10
|
const cwd = payload.cwd || payload.working_directory || process.cwd();
|
|
10
11
|
const promptContext = contextPath && fs.existsSync(contextPath) ? readJsonFile(contextPath) : null;
|
|
11
|
-
const
|
|
12
|
+
const rawScheduledRules = [
|
|
12
13
|
...(promptContext?.scheduled?.highRules || []),
|
|
13
14
|
...(promptContext?.scheduled?.midRules || [])
|
|
14
15
|
];
|
|
16
|
+
const scheduledRules = filterActionableRules(rawScheduledRules);
|
|
17
|
+
const scheduled = promptContext?.scheduled
|
|
18
|
+
? {
|
|
19
|
+
...promptContext.scheduled,
|
|
20
|
+
highRules: filterActionableRules(promptContext.scheduled.highRules || []),
|
|
21
|
+
midRules: filterActionableRules(promptContext.scheduled.midRules || []),
|
|
22
|
+
droppedRules: [
|
|
23
|
+
...(promptContext.scheduled.droppedRules || []),
|
|
24
|
+
...rawScheduledRules.filter((rule) => !scheduledRules.some((item) => item.content === rule.content && item.sourcePath === rule.sourcePath))
|
|
25
|
+
]
|
|
26
|
+
}
|
|
27
|
+
: null;
|
|
15
28
|
const gitSnapshot = readGitSnapshot({ cwd });
|
|
16
29
|
const runtimeEvidence = loadRuntimeEvidence({
|
|
17
30
|
telemetryPath,
|
|
@@ -28,7 +41,7 @@ export function handleStopPayload(payload, { contextPath, reportPath, historyPat
|
|
|
28
41
|
cwd,
|
|
29
42
|
prompt: promptContext?.prompt || "",
|
|
30
43
|
relevantFiles: promptContext?.relevantFiles || [],
|
|
31
|
-
scheduled
|
|
44
|
+
scheduled,
|
|
32
45
|
gitSnapshot,
|
|
33
46
|
compliance,
|
|
34
47
|
runtimeEvidence
|