oioxo-mcp 0.1.1 → 0.1.3
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/dist/cli/agents.d.ts +2 -0
- package/dist/cli/agents.js +43 -0
- package/dist/core/capsule.js +46 -5
- package/dist/test/e2e.test.js +5 -0
- package/package.json +1 -1
package/dist/cli/agents.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/** The instruction files for the agents that read them at session start. */
|
|
2
|
+
export declare function writeAgentInstructions(root: string): Promise<string[]>;
|
|
1
3
|
export interface InitOptions {
|
|
2
4
|
/** Force-write the per-PROJECT files even when not detected. */
|
|
3
5
|
all?: boolean;
|
package/dist/cli/agents.js
CHANGED
|
@@ -123,6 +123,46 @@ async function appendToml(file, entry) {
|
|
|
123
123
|
await fs.writeFile(file, existing + block, 'utf8');
|
|
124
124
|
return true;
|
|
125
125
|
}
|
|
126
|
+
// ---- agent INSTRUCTION files — why OIOXO fires BEFORE the session starts ----
|
|
127
|
+
// MCP registration alone leaves it to the model's judgement whether to call the
|
|
128
|
+
// tool. Instructions files are read by the agent at session start, so the very
|
|
129
|
+
// first prompt already routes through OIOXO. Marked block, idempotent.
|
|
130
|
+
const OIOXO_START = '<!-- oioxo:start -->';
|
|
131
|
+
const OIOXO_END = '<!-- oioxo:end -->';
|
|
132
|
+
const OIOXO_INSTRUCTIONS = `${OIOXO_START}
|
|
133
|
+
## OIOXO context engine (use it FIRST)
|
|
134
|
+
This project is wired to the OIOXO MCP server. BEFORE reading, searching or listing files yourself, ALWAYS call the \`get_context\` tool from the \`oioxo\` MCP server with the task as the query — it returns the minimal relevant code slice (10-20x fewer tokens than reading files). Use \`get_impact\` before refactors, \`get_skeleton\` to learn an API cheaply, and \`remember\` to persist durable project facts.
|
|
135
|
+
${OIOXO_END}`;
|
|
136
|
+
/** Append/refresh the marked OIOXO block in an instructions file. */
|
|
137
|
+
async function writeInstructionBlock(file) {
|
|
138
|
+
let existing = '';
|
|
139
|
+
try {
|
|
140
|
+
existing = await fs.readFile(file, 'utf8');
|
|
141
|
+
}
|
|
142
|
+
catch { /* fresh file */ }
|
|
143
|
+
const start = existing.indexOf(OIOXO_START);
|
|
144
|
+
const end = existing.indexOf(OIOXO_END);
|
|
145
|
+
let next;
|
|
146
|
+
if (start >= 0 && end > start) {
|
|
147
|
+
next = existing.slice(0, start) + OIOXO_INSTRUCTIONS + existing.slice(end + OIOXO_END.length);
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
next = existing ? existing.trimEnd() + '\n\n' + OIOXO_INSTRUCTIONS + '\n' : OIOXO_INSTRUCTIONS + '\n';
|
|
151
|
+
}
|
|
152
|
+
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
153
|
+
await fs.writeFile(file, next, 'utf8');
|
|
154
|
+
}
|
|
155
|
+
/** The instruction files for the agents that read them at session start. */
|
|
156
|
+
export async function writeAgentInstructions(root) {
|
|
157
|
+
const targets = [
|
|
158
|
+
path.join(root, '.github', 'copilot-instructions.md'), // Copilot reads automatically
|
|
159
|
+
path.join(root, 'CLAUDE.md'), // Claude Code reads at start
|
|
160
|
+
path.join(root, '.cursor', 'rules', 'oioxo.mdc'), // Cursor project rules
|
|
161
|
+
];
|
|
162
|
+
for (const t of targets)
|
|
163
|
+
await writeInstructionBlock(t);
|
|
164
|
+
return targets;
|
|
165
|
+
}
|
|
126
166
|
export async function initAgents(root, opts = {}) {
|
|
127
167
|
const home = opts.home ?? os.homedir();
|
|
128
168
|
const entry = serverEntry(opts.command);
|
|
@@ -150,6 +190,9 @@ export async function initAgents(root, opts = {}) {
|
|
|
150
190
|
console.log(' No agents detected. Run with --all to write the project configs anyway.');
|
|
151
191
|
return;
|
|
152
192
|
}
|
|
193
|
+
// Instruction files make agents call OIOXO from the FIRST prompt of a session.
|
|
194
|
+
await writeAgentInstructions(root);
|
|
195
|
+
console.log(' ✓ Agent instructions: .github/copilot-instructions.md, CLAUDE.md, .cursor/rules/oioxo.mdc');
|
|
153
196
|
console.log('\nRestart your agent (or reload the window) and it will call OIOXO before reading files.');
|
|
154
197
|
console.log('Not connected yet? Run `oioxo-mcp login`.');
|
|
155
198
|
}
|
package/dist/core/capsule.js
CHANGED
|
@@ -6,6 +6,14 @@ export function buildCapsule(query, files, index, opts = {}) {
|
|
|
6
6
|
const maxFiles = opts.maxFiles ?? 10;
|
|
7
7
|
const maxChars = opts.maxChars ?? 24_000;
|
|
8
8
|
const byPath = new Map(files.map((f) => [f.path, f]));
|
|
9
|
+
// TINY-PROJECT fast path: when everything fits in one capsule, retrieval
|
|
10
|
+
// theater only adds headers. Ship the whole project verbatim and claim ZERO
|
|
11
|
+
// savings — honesty here is what makes the 90% on real repos believable.
|
|
12
|
+
const totalChars = files.reduce((n, f) => n + f.content.length, 0);
|
|
13
|
+
if (totalChars > 0 && totalChars <= maxChars * 0.8) {
|
|
14
|
+
const text = files.map((f) => `// ${f.path}\n${f.content}`).join('\n\n');
|
|
15
|
+
return { text, files: files.map((f) => f.path), savedTokens: 0, chunks: [] };
|
|
16
|
+
}
|
|
9
17
|
const chunks = index.retrieve(query, topChunks);
|
|
10
18
|
// Anchors: distinct files behind the top chunks (order of first appearance).
|
|
11
19
|
const anchors = [];
|
|
@@ -26,17 +34,50 @@ export function buildCapsule(query, files, index, opts = {}) {
|
|
|
26
34
|
}
|
|
27
35
|
const parts = [];
|
|
28
36
|
const anchorSet = new Set(anchors);
|
|
29
|
-
// 1) Anchor
|
|
37
|
+
// 1) Anchor excerpts. Chunks overlap by design (12-line windows), so emit
|
|
38
|
+
// MERGED line ranges per file — overlap shipped twice is pure waste. And
|
|
39
|
+
// when the merged range covers most of a small file, ship the whole file
|
|
40
|
+
// once instead (simpler for the agent, fewer tokens than range headers).
|
|
41
|
+
const rangesByFile = new Map();
|
|
30
42
|
for (const c of chunks) {
|
|
31
|
-
|
|
43
|
+
const list = rangesByFile.get(c.path) ?? [];
|
|
44
|
+
list.push({ start: c.startLine, end: c.endLine });
|
|
45
|
+
rangesByFile.set(c.path, list);
|
|
32
46
|
}
|
|
33
|
-
|
|
47
|
+
for (const [p, ranges] of rangesByFile) {
|
|
48
|
+
const f = byPath.get(p);
|
|
49
|
+
if (!f)
|
|
50
|
+
continue;
|
|
51
|
+
const lines = f.content.split('\n');
|
|
52
|
+
ranges.sort((a, b) => a.start - b.start);
|
|
53
|
+
const merged = [];
|
|
54
|
+
for (const r of ranges) {
|
|
55
|
+
const last = merged[merged.length - 1];
|
|
56
|
+
if (last && r.start <= last.end + 1)
|
|
57
|
+
last.end = Math.max(last.end, r.end);
|
|
58
|
+
else
|
|
59
|
+
merged.push({ ...r });
|
|
60
|
+
}
|
|
61
|
+
const covered = merged.reduce((n, r) => n + (r.end - r.start + 1), 0);
|
|
62
|
+
if (covered >= lines.length * 0.7) {
|
|
63
|
+
parts.push(`// ${p} (full)\n${f.content}`);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
for (const r of merged)
|
|
67
|
+
parts.push(`// ${p}:${r.start}-${r.end}\n${lines.slice(r.start - 1, r.end).join('\n')}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// 2) Neighbor files: skeletons only — unless the file is so small that its
|
|
71
|
+
// skeleton wouldn't be cheaper, in which case skip it (the agent can ask).
|
|
34
72
|
for (const p of slice) {
|
|
35
73
|
if (anchorSet.has(p))
|
|
36
74
|
continue;
|
|
37
75
|
const f = byPath.get(p);
|
|
38
|
-
if (f)
|
|
39
|
-
|
|
76
|
+
if (!f)
|
|
77
|
+
continue;
|
|
78
|
+
const skel = fileSkeleton(f.path, f.content);
|
|
79
|
+
if (skel.length < f.content.length * 0.8)
|
|
80
|
+
parts.push(skel);
|
|
40
81
|
}
|
|
41
82
|
let text = parts.join('\n\n');
|
|
42
83
|
if (text.length > maxChars)
|
package/dist/test/e2e.test.js
CHANGED
|
@@ -91,6 +91,11 @@ test('init writes + merges agent configs (project + global)', async () => {
|
|
|
91
91
|
const codex = await fs.readFile(path.join(home, '.codex', 'config.toml'), 'utf8');
|
|
92
92
|
assert.ok(codex.startsWith('model = "o4"'), 'existing TOML preserved');
|
|
93
93
|
assert.ok(codex.includes('[mcp_servers.oioxo]'));
|
|
94
|
+
// Instruction files (the "fires first" mechanism) — written and idempotent.
|
|
95
|
+
const copilotIns = await fs.readFile(path.join(root, '.github', 'copilot-instructions.md'), 'utf8');
|
|
96
|
+
assert.ok(copilotIns.includes('get_context'), 'copilot instructions mention the tool');
|
|
97
|
+
const claudeMd = await fs.readFile(path.join(root, 'CLAUDE.md'), 'utf8');
|
|
98
|
+
assert.ok(claudeMd.includes('oioxo:start'));
|
|
94
99
|
// Idempotent: second run must not duplicate the TOML section.
|
|
95
100
|
await initAgents(root, { all: true, command: 'oioxo-mcp', home });
|
|
96
101
|
const codex2 = await fs.readFile(path.join(home, '.codex', 'config.toml'), 'utf8');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oioxo-mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "OIOXO context engine for AI coding agents — feeds Claude Code, Copilot, Cursor and any MCP-capable agent the minimal relevant slice of your codebase, on-device, so your tokens go further.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
6
6
|
"type": "module",
|