@theronap/cortex-mcp 0.9.42 → 0.9.44
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/bin/cortex-mcp.mjs +1 -1
- package/lib/grep_cli.mjs +8 -4
- package/lib/grep_cli.test.mjs +8 -4
- package/lib/server.mjs +20 -10
- package/package.json +1 -1
package/bin/cortex-mcp.mjs
CHANGED
|
@@ -115,7 +115,7 @@ if (cmd === 'setup') {
|
|
|
115
115
|
const { closeFetch } = await import('../lib/diagnose.mjs')
|
|
116
116
|
await closeFetch()
|
|
117
117
|
} else if (cmd === 'grep') {
|
|
118
|
-
//
|
|
118
|
+
// Ranked keyword (fts) search by default; --substring/--literal for exact lookups. Thin client of /api/grep.
|
|
119
119
|
const { runGrep } = await import('../lib/grep_cli.mjs')
|
|
120
120
|
process.exitCode = await runGrep(rest)
|
|
121
121
|
const { closeFetch } = await import('../lib/diagnose.mjs')
|
package/lib/grep_cli.mjs
CHANGED
|
@@ -5,16 +5,20 @@ import { fetchCortex, classify, resolveBase } from './diagnose.mjs'
|
|
|
5
5
|
// Output is ASCII-only (outbound-message convention).
|
|
6
6
|
|
|
7
7
|
// Pure: parse argv after `grep` → { query, mode, max }.
|
|
8
|
-
// cortex grep <terms...> [--
|
|
8
|
+
// cortex grep <terms...> [--substring | --literal] [--max N]
|
|
9
|
+
// Default mode is 'fts' (ranked keyword — multi-word queries work); pass --substring/--literal for
|
|
10
|
+
// an exact literal match (identifiers, [[links]], code). (#220)
|
|
9
11
|
export function parseGrepArgs(argv = []) {
|
|
10
|
-
const out = { query: '', mode: '
|
|
12
|
+
const out = { query: '', mode: 'fts', max: undefined }
|
|
11
13
|
const terms = []
|
|
12
14
|
for (let i = 0; i < argv.length; i++) {
|
|
13
15
|
const a = argv[i]
|
|
14
16
|
if (a === '--mode') {
|
|
15
|
-
out.mode = argv[++i] === '
|
|
17
|
+
out.mode = argv[++i] === 'substring' ? 'substring' : 'fts'
|
|
16
18
|
} else if (a === '--fts') {
|
|
17
19
|
out.mode = 'fts'
|
|
20
|
+
} else if (a === '--substring' || a === '--literal') {
|
|
21
|
+
out.mode = 'substring'
|
|
18
22
|
} else if (a === '--max') {
|
|
19
23
|
const n = Number(argv[++i])
|
|
20
24
|
if (Number.isFinite(n)) out.max = Math.trunc(n)
|
|
@@ -50,7 +54,7 @@ export async function runGrep(rest = []) {
|
|
|
50
54
|
}
|
|
51
55
|
const { query, mode, max } = parseGrepArgs(rest)
|
|
52
56
|
if (!query) {
|
|
53
|
-
process.stderr.write('usage: cortex grep <query> [--
|
|
57
|
+
process.stderr.write('usage: cortex grep <query> [--substring|--literal] [--max N]\n')
|
|
54
58
|
return 1
|
|
55
59
|
}
|
|
56
60
|
const qs = new URLSearchParams({ q: query, mode })
|
package/lib/grep_cli.test.mjs
CHANGED
|
@@ -2,13 +2,17 @@ import { describe, it, expect } from 'bun:test'
|
|
|
2
2
|
import { parseGrepArgs, formatGrepHits } from './grep_cli.mjs'
|
|
3
3
|
|
|
4
4
|
describe('parseGrepArgs', () => {
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
// #220: default is 'fts' (ranked keyword); --substring/--literal opts back into exact matching.
|
|
6
|
+
it('joins free terms into the query, defaults fts', () => {
|
|
7
|
+
expect(parseGrepArgs(['hello', 'world'])).toEqual({ query: 'hello world', mode: 'fts', max: undefined })
|
|
7
8
|
})
|
|
8
|
-
it('honors --
|
|
9
|
+
it('honors --substring/--literal to force exact matching, else fts', () => {
|
|
10
|
+
expect(parseGrepArgs(['q', '--substring']).mode).toBe('substring')
|
|
11
|
+
expect(parseGrepArgs(['q', '--literal']).mode).toBe('substring')
|
|
12
|
+
expect(parseGrepArgs(['q', '--mode', 'substring']).mode).toBe('substring')
|
|
9
13
|
expect(parseGrepArgs(['q', '--mode', 'fts']).mode).toBe('fts')
|
|
10
14
|
expect(parseGrepArgs(['--fts', 'q']).mode).toBe('fts')
|
|
11
|
-
expect(parseGrepArgs(['q', '--mode', 'bogus']).mode).toBe('
|
|
15
|
+
expect(parseGrepArgs(['q', '--mode', 'bogus']).mode).toBe('fts')
|
|
12
16
|
})
|
|
13
17
|
it('parses --max as an integer, ignores non-numeric', () => {
|
|
14
18
|
expect(parseGrepArgs(['q', '--max', '25']).max).toBe(25)
|
package/lib/server.mjs
CHANGED
|
@@ -190,11 +190,17 @@ export async function runServer(version) {
|
|
|
190
190
|
'timeline_pull',
|
|
191
191
|
{
|
|
192
192
|
title: 'Pull unattributed messaging threads to triage',
|
|
193
|
-
description: "Surface messaging threads captured in the org (email, etc.) that AREN'T yet attributed to a project — the backlog awaiting your judgment. Call it as you work; when you recognize which KNOWN project a surfaced thread is about, attribute it with attribute_thread. Threads you don't recognize: leave them (they self-heal as the graph fills). Returns participants + subject + recency per thread — enough to recognize, not the message bodies.",
|
|
194
|
-
inputSchema: {
|
|
193
|
+
description: "Surface messaging threads captured in the org (email, etc.) that AREN'T yet attributed to a project — the backlog awaiting your judgment. Call it as you work; when you recognize which KNOWN project a surfaced thread is about, attribute it with attribute_thread. Threads you don't recognize: leave them (they self-heal as the graph fills). Pass a `project` you're working on to RANK the backlog by relevance (threads sharing participants with that project come first, marked ★). Returns participants + subject + recency per thread — enough to recognize, not the message bodies.",
|
|
194
|
+
inputSchema: {
|
|
195
|
+
limit: z.number().optional().describe('max threads to return (default 20)'),
|
|
196
|
+
project: z.string().optional().describe('optional KNOWN project slug to rank the backlog by relevance to what you are working on; omit for the whole backlog newest-first'),
|
|
197
|
+
},
|
|
195
198
|
},
|
|
196
|
-
async ({ limit }) => {
|
|
197
|
-
const
|
|
199
|
+
async ({ limit, project }) => {
|
|
200
|
+
const params = new URLSearchParams()
|
|
201
|
+
if (typeof limit === 'number') params.set('limit', String(limit))
|
|
202
|
+
if (typeof project === 'string' && project) params.set('project', project)
|
|
203
|
+
const qs = params.toString() ? `?${params}` : ''
|
|
198
204
|
const res = await fetchCortex(`${BASE}/api/timeline/pull${qs}`, { headers: { Authorization: `Bearer ${TOKEN}` } })
|
|
199
205
|
if (!res.ok) {
|
|
200
206
|
const body = await res.text()
|
|
@@ -205,9 +211,13 @@ export async function runServer(version) {
|
|
|
205
211
|
const lines = threads.map((t) => {
|
|
206
212
|
const who = (t.participants ?? []).map((p) => String(p).replace(/^email:/, '')).join(', ')
|
|
207
213
|
const when = t.lastAt ? String(t.lastAt).slice(0, 10) : '—'
|
|
208
|
-
|
|
214
|
+
const rel = t.relevance && t.relevance.score > 0 ? ` · ★${t.relevance.score} ${t.relevance.reason}` : ''
|
|
215
|
+
return `- ${t.threadKey} — ${t.subject ?? '(no subject)'} · ${who || 'unknown participants'} · ${t.eventCount} msg · ${when}${rel}`
|
|
209
216
|
})
|
|
210
|
-
|
|
217
|
+
const header = project
|
|
218
|
+
? `Unattributed threads (${threads.length}), ranked for project:${project} (★ = shares participants)`
|
|
219
|
+
: `Unattributed threads (${threads.length})`
|
|
220
|
+
return { content: [{ type: 'text', text: `${header} — attribute recognized ones with attribute_thread(threadKey, project):\n${lines.join('\n')}` }] }
|
|
211
221
|
},
|
|
212
222
|
)
|
|
213
223
|
|
|
@@ -256,14 +266,14 @@ export async function runServer(version) {
|
|
|
256
266
|
{
|
|
257
267
|
title: 'Grep the brain wiki',
|
|
258
268
|
description:
|
|
259
|
-
'
|
|
269
|
+
'Ranked keyword search across your visible brain wiki pages. Multi-word natural-language queries work (results are ranked by relevance). Pass mode:"substring" for an exact literal match of symbols, identifiers, or [[links]]. Returns matching sections with a context snippet and their outbound [[links]].',
|
|
260
270
|
inputSchema: {
|
|
261
|
-
query: z.string().describe('
|
|
262
|
-
mode: z.enum(['substring', 'fts']).optional().describe("'
|
|
271
|
+
query: z.string().describe('search terms (natural language is fine)'),
|
|
272
|
+
mode: z.enum(['substring', 'fts']).optional().describe("'fts' (default, ranked keyword) or 'substring' (exact literal — for identifiers / [[links]] / code)"),
|
|
263
273
|
},
|
|
264
274
|
},
|
|
265
275
|
async ({ query, mode }) => {
|
|
266
|
-
const qs = new URLSearchParams({ q: query, mode: mode === '
|
|
276
|
+
const qs = new URLSearchParams({ q: query, mode: mode === 'substring' ? 'substring' : 'fts' })
|
|
267
277
|
const res = await fetchCortex(`${BASE}/api/grep?${qs.toString()}`, { headers: { Authorization: `Bearer ${TOKEN}` } })
|
|
268
278
|
if (!res.ok) {
|
|
269
279
|
const body = await res.text()
|