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.
@@ -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;
@@ -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
  }
@@ -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 chunks: the exact code in play, full text, with line refs.
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
- parts.push(`// ${c.path}:${c.startLine}-${c.endLine}\n${c.text}`);
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
- // 2) Neighbor files: skeletons only.
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
- parts.push(fileSkeleton(f.path, f.content));
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)
@@ -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.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",