@theronap/cortex-mcp 0.9.77 → 0.9.79

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/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # cortex-mcp
2
2
 
3
- Connect your AI assistant to **Agnoclast** — your org's projects, recent activity, gaps, and directives, scoped to exactly what you're permitted to see.
3
+ Connect your AI assistant to **Cortex** — your org's projects, recent activity, gaps, and directives, scoped to exactly what you're permitted to see.
4
4
 
5
5
  ## Setup
6
6
 
7
- 1. Get your personal token from the Agnoclast console → **Connect your AI**.
7
+ 1. Get your personal token from the Cortex console → **Connect your AI**.
8
8
  2. Add this to your Claude Code config (`~/.claude.json`, under `mcpServers`):
9
9
 
10
10
  ```json
@@ -21,7 +21,7 @@ Connect your AI assistant to **Agnoclast** — your org's projects, recent activ
21
21
 
22
22
  3. Restart Claude Code. Your AI now sees your org context automatically, the managed startup skill
23
23
  will prefer query-centered `session_context` on substantive session opens, and each session start
24
- will log the exact baseline Agnoclast context to `~/.cortex/context-snapshots/`.
24
+ will log the exact baseline Cortex context to `~/.cortex/context-snapshots/`.
25
25
 
26
26
  No clone, no path, no build step — `npx` fetches and runs it.
27
27
 
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * cortex-mcp — connect your AI assistant to Agnoclast.
3
+ * cortex-mcp — connect your AI assistant to Cortex.
4
4
  *
5
5
  * Subcommands:
6
6
  * (none) run the MCP server (stdio) — used by your Claude config
@@ -8,14 +8,14 @@
8
8
  * doctor live health check — is the token actually working? (no restart needed)
9
9
  * capture the Stop-hook capturer (invoked by Claude Code, not by hand)
10
10
  * ingest-folder <path> ingest a local markdown folder as your authored records
11
- * snapshot-context save the exact startup context served by Agnoclast to a local log file
11
+ * snapshot-context save the exact startup context served by Cortex to a local log file
12
12
  * --version | -v
13
13
  * --help | -h
14
14
  *
15
15
  * Zero-install onboarding:
16
16
  * npx -y @theronap/cortex-mcp setup <your-token>
17
17
  *
18
- * Get your token from the Agnoclast console → Connect your AI.
18
+ * Get your token from the Cortex console → Connect your AI.
19
19
  */
20
20
 
21
21
  import { readFileSync } from 'node:fs'
@@ -35,31 +35,31 @@ if (cmd === '--version' || cmd === '-v') {
35
35
 
36
36
  if (cmd === '--help' || cmd === '-h' || cmd === 'help') {
37
37
  process.stdout.write(
38
- `cortex-mcp ${VERSION} — connect your AI assistant to Agnoclast\n\n` +
38
+ `cortex-mcp ${VERSION} — connect your AI assistant to Cortex\n\n` +
39
39
  `Onboard (one command, no token to copy):\n` +
40
40
  ` npx -y @theronap/cortex-mcp login\n\n` +
41
41
  `This opens your browser, you click Approve, and it wires everything up. Restart\n` +
42
- `Claude Code after, and your AI sees your Agnoclast context while your sessions flow\n` +
42
+ `Claude Code after, and your AI sees your Cortex context while your sessions flow\n` +
43
43
  `into the org automatically.\n\n` +
44
44
  `Subcommands:\n` +
45
45
  ` login [--label <name>] browser-approved sign-in — gets a token for you, then runs setup\n` +
46
46
  ` setup <token> wire MCP server + capture hook into ~/.claude config (single editor)\n` +
47
- ` install [<token>] [--editor auto|all|<id,...>] wire Agnoclast into EVERY detected editor + write the capability manifest\n` +
47
+ ` install [<token>] [--editor auto|all|<id,...>] wire Cortex into EVERY detected editor + write the capability manifest\n` +
48
48
  ` repair re-run setup at the latest version using your existing token (no token needed)\n` +
49
- ` uninstall remove ALL Agnoclast wiring (MCP, hooks, skills, launchd, cron). --dry-run to preview, --purge to also wipe ~/.cortex + npx cache\n` +
49
+ ` uninstall remove ALL Cortex wiring (MCP, hooks, skills, launchd, cron). --dry-run to preview, --purge to also wipe ~/.cortex + npx cache\n` +
50
50
  ` doctor live health check — confirm your token works (no restart needed)\n` +
51
51
  ` status one-line connected/not-connected check (used by the SessionStart hook)\n` +
52
- ` skills install/repair the managed Agnoclast skills — bundled + org-published (also wired by setup)\n` +
52
+ ` skills install/repair the managed Cortex skills — bundled + org-published (also wired by setup)\n` +
53
53
  ` skills push <file> publish a SKILL.md to your org (owner/manager/admin)\n` +
54
- ` docs-scan detect new/changed local docs pending Agnoclast authoring (used by /cortex-author-docs)\n` +
55
- ` graphify-sync [path] [--brain <name-or-id>] rebuild the local code graph + log a timeline event\n` +
56
- ` snapshot-context save the exact startup context Agnoclast served to a local snapshot\n` +
54
+ ` docs-scan detect new/changed local docs pending Cortex authoring (used by /cortex-author-docs)\n` +
55
+ ` graphify-sync [path] rebuild the local code graph (graphify) + log an evidence-tier timeline event\n` +
56
+ ` snapshot-context save the exact startup context Cortex served to a local snapshot\n` +
57
57
  ` hydrate UserPromptSubmit hook — inject query-centered context on the first substantive turn\n` +
58
58
  ` statusline ambient presence line for the Claude Code statusline (local read only)\n` +
59
59
  ` capture Stop-hook capturer (invoked by Claude Code)\n` +
60
60
  ` ingest-folder <path> ingest a local markdown folder as your authored records\n` +
61
61
  ` (no args) run the MCP server (used by your Claude config)\n\n` +
62
- `Get your token from the Agnoclast console → Connect your AI.\n`,
62
+ `Get your token from the Cortex console → Connect your AI.\n`,
63
63
  )
64
64
  process.exit(0)
65
65
  }
@@ -84,14 +84,14 @@ if (cmd === 'login') {
84
84
  const { closeFetch } = await import('../lib/diagnose.mjs')
85
85
  await closeFetch()
86
86
  } else if (cmd === 'install') {
87
- // The cross-editor hub installer: wire Agnoclast into every detected editor via the adapter
87
+ // The cross-editor hub installer: wire Cortex into every detected editor via the adapter
88
88
  // registry, then write the capability manifest (~/.cortex/editors.json). Superset of `setup`.
89
89
  const { runInstall } = await import('../lib/install.mjs')
90
90
  await runInstall(rest, VERSION)
91
91
  const { closeFetch } = await import('../lib/diagnose.mjs')
92
92
  await closeFetch()
93
93
  } else if (cmd === 'uninstall' || cmd === 'remove') {
94
- // Full reverse of setup: strip every Agnoclast touch-point (MCP entries, hooks, skills, launchd, cron).
94
+ // Full reverse of setup: strip every Cortex touch-point (MCP entries, hooks, skills, launchd, cron).
95
95
  // --dry-run prints the plan and changes nothing; --purge also removes ~/.cortex, the npx cache, and
96
96
  // backups. No network — safe to run even when the token is dead or the server is unreachable.
97
97
  const { runUninstall } = await import('../lib/uninstall.mjs')
@@ -122,7 +122,7 @@ if (cmd === 'login') {
122
122
  } else if (cmd === 'resolve') {
123
123
  // Entity identity dedup: judge the server-flagged fuzzy duplicate pairs locally via `claude -p`.
124
124
  const { runResolve } = await import('../lib/resolve.mjs')
125
- await runResolve(rest)
125
+ await runResolve()
126
126
  const { closeFetch } = await import('../lib/diagnose.mjs')
127
127
  await closeFetch()
128
128
  } else if (cmd === 'materialize') {
@@ -153,7 +153,7 @@ if (cmd === 'login') {
153
153
  const { closeFetch } = await import('../lib/diagnose.mjs')
154
154
  await closeFetch()
155
155
  } else if (cmd === 'hydrate') {
156
- // UserPromptSubmit hook (① discovery): hydrate the model with query-centered Agnoclast context on the
156
+ // UserPromptSubmit hook (① discovery): hydrate the model with query-centered Cortex context on the
157
157
  // FIRST substantive turn, before it answers — then never again this session (topic-shift refresh stays
158
158
  // the cortex-context skill's job). Synchronous by necessity, but once-per-session + 8s + fail-open.
159
159
  const { runHydrate } = await import('../lib/hydrate.mjs')
@@ -166,7 +166,7 @@ if (cmd === 'login') {
166
166
  const { runStatusline } = await import('../lib/statusline.mjs')
167
167
  process.exitCode = runStatusline()
168
168
  } else if (cmd === 'skills') {
169
- // Install / repair the managed Agnoclast skills — bundled + org-published (`skills push` publishes).
169
+ // Install / repair the managed Cortex skills — bundled + org-published (`skills push` publishes).
170
170
  // Org sync is network-fail-soft so the SessionStart hook stays safe offline.
171
171
  const { runSkills } = await import('../lib/skills.mjs')
172
172
  process.exitCode = await runSkills(rest)
package/lib/capture.mjs CHANGED
@@ -35,7 +35,7 @@ export function projectFrom(cwd) {
35
35
  return base ?? 'general'
36
36
  }
37
37
 
38
- // Claude Code Stop hook → POSTs a session digest to Agnoclast cloud, which
38
+ // Claude Code Stop hook → POSTs a session digest to Cortex cloud, which
39
39
  // summarizes server-side and upserts ONE record per session. Node-native
40
40
  // SHR-01/T6 — parse a git remote URL into GitHub 'owner/name', or null.
41
41
  //
@@ -4,7 +4,7 @@ import { join } from 'path'
4
4
 
5
5
  // Thin local wrapper around the `graphify` CLI's read-only query subcommands. Deliberately NOT a
6
6
  // fetchCortex client like grep/read_page: this is LOCAL-MACHINE data (a tree-sitter AST graph of
7
- // whatever repo the session's cwd happens to be in), not org-shared Agnoclast content, and it never
7
+ // whatever repo the session's cwd happens to be in), not org-shared Cortex content, and it never
8
8
  // becomes the wiki graph — see cortex-wiki-primary-spec (structural/extracted data is evidence,
9
9
  // never auto-promoted into authored pages). No LLM, no network call; graphify already built the
10
10
  // graph on disk, this just queries it.
@@ -49,7 +49,7 @@ export async function runSnapshotContext() {
49
49
  const out = (m) => process.stdout.write(m + '\n')
50
50
  const token = resolveToken()
51
51
  if (!token) {
52
- out('Agnoclast: context snapshot skipped — no token found.')
52
+ out('Cortex: context snapshot skipped — no token found.')
53
53
  return 0
54
54
  }
55
55
 
@@ -58,13 +58,13 @@ export async function runSnapshotContext() {
58
58
  try {
59
59
  res = await fetchCortex(`${base}/api/mcp-context`, { headers: { Authorization: `Bearer ${token}` } })
60
60
  } catch (e) {
61
- out(`Agnoclast: context snapshot failed — ${e?.message ?? String(e)}`)
61
+ out(`Cortex: context snapshot failed — ${e?.message ?? String(e)}`)
62
62
  return 0
63
63
  }
64
64
 
65
65
  if (!res.ok) {
66
66
  const body = await res.text()
67
- out(`Agnoclast: context snapshot failed — ${classify(res.status, res.headers.get('content-type'), body, res.headers.get('x-vercel-id')).message}`)
67
+ out(`Cortex: context snapshot failed — ${classify(res.status, res.headers.get('content-type'), body, res.headers.get('x-vercel-id')).message}`)
68
68
  return 0
69
69
  }
70
70
 
@@ -76,7 +76,7 @@ export async function runSnapshotContext() {
76
76
  const capturedAt = new Date().toISOString()
77
77
  const file = join(dir, `${stamp()}.md`)
78
78
  const header = [
79
- '# Agnoclast startup context snapshot',
79
+ '# Cortex startup context snapshot',
80
80
  `- Captured: ${capturedAt}`,
81
81
  `- Source: ${base}/api/mcp-context`,
82
82
  '',
@@ -94,6 +94,6 @@ export async function runSnapshotContext() {
94
94
 
95
95
  pruneSnapshots(dir)
96
96
 
97
- out(`Agnoclast: logged startup context → ${file}`)
97
+ out(`Cortex: logged startup context → ${file}`)
98
98
  return 0
99
99
  }
package/lib/diagnose.mjs CHANGED
@@ -1,12 +1,12 @@
1
1
  // Shared health / diagnosis helpers for the cortex MCP client.
2
2
  //
3
3
  // The whole reason this file exists: a transient infrastructure block (Vercel firewall / bot
4
- // protection / deployment protection) once surfaced as a bare "Agnoclast API 403: unknown", which
4
+ // protection / deployment protection) once surfaced as a bare "Cortex API 403: unknown", which
5
5
  // read like an auth failure and sent everyone chasing token regeneration for hours. The fix is
6
6
  // to tell the truth about WHAT failed.
7
7
  //
8
- // KEY SIGNAL: the Agnoclast app ALWAYS returns JSON ({ error: ... }). So a NON-JSON body on a
9
- // 4xx/5xx means infrastructure handled the request, not Agnoclast auth — re-running setup or
8
+ // KEY SIGNAL: the Cortex app ALWAYS returns JSON ({ error: ... }). So a NON-JSON body on a
9
+ // 4xx/5xx means infrastructure handled the request, not Cortex auth — re-running setup or
10
10
  // regenerating the token will not help; it is usually transient and worth a retry.
11
11
 
12
12
  import { readFileSync } from 'fs'
@@ -85,24 +85,15 @@ const sleep = (ms) => new Promise((r) => setTimeout(r, ms))
85
85
  // A non-OK response → an actionable diagnosis: { kind, retriable, message }.
86
86
  // kind: 'auth' → token bad/revoked/wrong-deployment (NOT retriable)
87
87
  // 'infra' → blocked by infrastructure, non-JSON body (retriable, usually transient)
88
- // 'app' → a real Agnoclast API error with a JSON message (retriable only if 5xx)
88
+ // 'app' → a real Cortex API error with a JSON message (retriable only if 5xx)
89
89
  export function classify(status, contentType, bodyText, requestId) {
90
90
  const isJson = (contentType ?? '').includes('application/json')
91
91
  let appError = null
92
92
  let appHint = null
93
- let appMessage = null
94
- let appBrains = null
95
93
  if (isJson) {
96
94
  try {
97
95
  const parsed = JSON.parse(bodyText)
98
96
  appError = parsed?.error ?? null
99
- // Same lesson as `hint`, one layer up. brain_choice_response.ts writes a full explanation to
100
- // `message` and every brain's NAME / PAGE COUNT / SAMPLE TITLES to `brains` — its comment says
101
- // "THE BODY IS THE ANSWER TO ITS OWN QUESTION", precisely so a model can pick a brain from what
102
- // each one HOLDS. Only `error` survived here, so the agent got the bare code `brain_required`
103
- // and could not answer it. Measured on the CLI twin of this bug: `✗ brain_required`, full stop.
104
- appMessage = typeof parsed?.message === 'string' && parsed.message.trim() ? parsed.message.trim() : null
105
- appBrains = Array.isArray(parsed?.brains) ? parsed.brains : null
106
97
  // `hint` carries the RECOVERY instruction for the errors an agent is meant to act on, not
107
98
  // just report: 409 would_drop ("re-author including the dropped sections…") and 413 too_large
108
99
  // ("split the section…"). It used to be dropped here — only `error` survived — so the agent
@@ -118,39 +109,20 @@ export function classify(status, contentType, bodyText, requestId) {
118
109
  kind: 'auth', retriable: false,
119
110
  message: `Token rejected (HTTP ${status}: ${appError ?? 'unauthorized'}). The token is invalid, ` +
120
111
  `revoked, or for a different deployment — not an infrastructure problem. Get a fresh token from ` +
121
- `the Agnoclast console → Connect your AI, then re-run setup.${rid}`,
112
+ `the Cortex console → Connect your AI, then re-run setup.${rid}`,
122
113
  }
123
114
  }
124
115
  if (!isJson) {
125
116
  return {
126
117
  kind: 'infra', retriable: true,
127
- message: `Blocked by infrastructure (HTTP ${status}, non-JSON response) — NOT by Agnoclast auth. This is ` +
118
+ message: `Blocked by infrastructure (HTTP ${status}, non-JSON response) — NOT by Cortex auth. This is ` +
128
119
  `usually a transient firewall / bot-protection hiccup; retrying often clears it. If it persists, check ` +
129
120
  `Vercel firewall / deployment protection on the API route. Re-running setup will not help.${rid}`,
130
121
  }
131
122
  }
132
- // A brain-choice refusal is ANSWERABLE, so it must arrive as the question it is rather than as a
133
- // code. Deliberately narrow: only these two errors reshape the message, so every other classify()
134
- // output keeps its existing wording (and its tests).
135
- if (isJson && (appError === 'brain_required' || appError === 'unknown_brain') && (appMessage || appBrains)) {
136
- const list = (appBrains ?? []).map((b) => {
137
- const pages = typeof b?.pageCount === 'number' ? ` — ${b.pageCount} page${b.pageCount === 1 ? '' : 's'}` : ''
138
- const titles = Array.isArray(b?.sampleTitles) && b.sampleTitles.length
139
- ? `: ${b.sampleTitles.slice(0, 2).join('; ')}` : ''
140
- // Name AND id: brain names are NOT unique (one account holds two called "Personal"), so a name
141
- // alone can come back as unknown_brain. The id always resolves.
142
- return ` - ${b?.name ?? '(unnamed)'}${pages}${titles}${b?.orgId ? ` [${b.orgId}]` : ''}`
143
- })
144
- return {
145
- kind: 'app', retriable: false,
146
- message: `Agnoclast API ${status}: ${appMessage ?? appError}${list.length ? `\n${list.join('\n')}` : ''}` +
147
- `\nRe-run this tool with \`brain\` set to one of the names or ids above.${rid}`,
148
- }
149
- }
150
-
151
123
  return {
152
124
  kind: 'app', retriable: status >= 500,
153
- message: `Agnoclast API ${status}: ${appError ?? 'unknown error'}.${appHint ? ` ${appHint}` : ''}${rid}`,
125
+ message: `Cortex API ${status}: ${appError ?? 'unknown error'}.${appHint ? ` ${appHint}` : ''}${rid}`,
154
126
  }
155
127
  }
156
128
 
@@ -204,7 +176,7 @@ export async function fetchCortex(url, opts = {}, { retries = 2, baseDelayMs = 4
204
176
  }
205
177
  }
206
178
  throw new Error(
207
- `Could not reach Agnoclast at ${url} — ${lastErr?.message ?? 'network error'}. ` +
179
+ `Could not reach Cortex at ${url} — ${lastErr?.message ?? 'network error'}. ` +
208
180
  `Check your connection (and CORTEX_URL if you set it).`,
209
181
  )
210
182
  }
@@ -218,7 +190,7 @@ export async function checkToken(token, base) {
218
190
  }
219
191
  if (!isUuid(token)) {
220
192
  return { ok: false, status: 0, diagnosis: { kind: 'config', retriable: false,
221
- message: `Token "${String(token).slice(0, 8)}…" is not a valid Agnoclast token (expected a UUID). ` +
193
+ message: `Token "${String(token).slice(0, 8)}…" is not a valid Cortex token (expected a UUID). ` +
222
194
  `Re-run setup with the token from the console.` } }
223
195
  }
224
196
  const url = `${(base ?? 'https://cortex-console.vercel.app').replace(/\/$/, '')}/api/mcp-context`
@@ -243,41 +215,6 @@ export async function checkToken(token, base) {
243
215
  return { ok: true, status: 200, projectCount, requestId }
244
216
  }
245
217
 
246
- // Probe the org-skills surface. SEPARATE FROM checkToken ON PURPOSE.
247
- //
248
- // WHY THIS EXISTS. `doctor` checked exactly one endpoint — /api/mcp-context — and reported PASS on
249
- // the strength of it. Meanwhile /api/skills answered 409 to every multi-brain caller for three days
250
- // (measured 2026-08-08: 14 requests, 14 × 409, zero successes; the local cache sat frozen from Aug 5
251
- // to Aug 8). Org-published skills silently never installed, the client swallowed the status, and
252
- // doctor — the tool setup.txt tells people to run FIRST when something is wrong — said everything
253
- // was fine, every single time. A check that probes one surface is a health check for that surface,
254
- // not for the system, and must not be reported as the latter.
255
- //
256
- // Returns checkToken's shape so the caller renders both the same way. `skillCount` is
257
- // informational: an org with zero published skills is perfectly healthy, so it is NOT a failure.
258
- export async function checkSkills(token, base) {
259
- if (!token || !isUuid(token)) {
260
- return { ok: false, status: 0, diagnosis: { kind: 'config', retriable: false,
261
- message: 'no usable token — the context check above already covers this' } }
262
- }
263
- const url = `${(base ?? CANONICAL_BASE).replace(/\/$/, '')}/api/skills`
264
- let res
265
- try {
266
- res = await fetchCortex(url, { headers: { Authorization: `Bearer ${token}` } })
267
- } catch (e) {
268
- return { ok: false, status: 0, diagnosis: { kind: 'network', retriable: true, message: e.message } }
269
- }
270
- const requestId = res.headers.get('x-vercel-id') ?? null
271
- const contentType = res.headers.get('content-type')
272
- const body = await res.text()
273
- if (!res.ok) {
274
- return { ok: false, status: res.status, requestId, diagnosis: classify(res.status, contentType, body, requestId) }
275
- }
276
- let skillCount
277
- try { skillCount = (JSON.parse(body)?.skills ?? []).length } catch { /* shape drift — non-fatal */ }
278
- return { ok: true, status: 200, skillCount, requestId }
279
- }
280
-
281
218
  // Release the keep-alive sockets the global fetch (undici) holds, so a short-lived CLI command
282
219
  // can tear down cleanly. Without this, calling process.exit() right after a fetch can race a
283
220
  // lingering socket handle and trip a libuv assertion on Windows
package/lib/docs_scan.mjs CHANGED
@@ -6,7 +6,7 @@ import { join, resolve, dirname } from 'path'
6
6
  // Documentation ingestion — detection half (D1 of docs/documentation-ingestion-spec.md).
7
7
  //
8
8
  // Specs/plans/design docs get written to disk (repo docs/, ~/.gstack/projects/…) and never reach
9
- // Agnoclast as pages. This subcommand DETECTS new/changed markdown under registered roots by content
9
+ // Cortex as pages. This subcommand DETECTS new/changed markdown under registered roots by content
10
10
  // hash; the AUTHORING is done by the live session (the cortex-author-docs skill reads each pending
11
11
  // doc and calls the `author` MCP tool) — the agent is the pipe, never a raw-markdown dump.
12
12
  //
@@ -90,7 +90,7 @@ export function markFiles(state, paths, now = new Date().toISOString()) {
90
90
  }
91
91
 
92
92
  // Heuristic soak guardrail (spec D5): the Robin parity experiment forbids re-syncing the local
93
- // brain into Agnoclast during the window, so warn when a root looks like the brain repo.
93
+ // brain into Cortex during the window, so warn when a root looks like the brain repo.
94
94
  const looksLikeBrain = (dir) => /\/Documents\/brain(\/|$)/.test(dir)
95
95
 
96
96
  export async function runDocsScan(argv = []) {
@@ -111,7 +111,7 @@ export async function runDocsScan(argv = []) {
111
111
  const abs = resolve(dir)
112
112
  if (!existsSync(abs)) { process.stderr.write(`Not a directory: ${abs}\n`); return 1 }
113
113
  if (looksLikeBrain(abs)) {
114
- out(`⚠ ${abs} looks like the local brain repo — the Robin parity soak forbids re-syncing it into Agnoclast.`)
114
+ out(`⚠ ${abs} looks like the local brain repo — the Robin parity soak forbids re-syncing it into Cortex.`)
115
115
  out(' Registering anyway is on you; the cortex-author-docs skill will also warn.')
116
116
  }
117
117
  if (!state.roots.includes(abs)) state.roots.push(abs)
@@ -163,7 +163,7 @@ export async function runDocsScan(argv = []) {
163
163
  }
164
164
  if (!pending.length) out(`✓ up to date — ${scanned} doc(s) scanned, nothing pending`)
165
165
  else {
166
- out(`${pending.length} doc(s) pending Agnoclast authoring (of ${scanned} scanned):`)
166
+ out(`${pending.length} doc(s) pending Cortex authoring (of ${scanned} scanned):`)
167
167
  for (const p of pending) out(` ${p.status === 'new' ? '+ ' : '~ '}${p.path}`)
168
168
  out('Author them via the cortex-author-docs skill, then: docs-scan --mark <file>...')
169
169
  }
package/lib/doctor.mjs CHANGED
@@ -1,12 +1,12 @@
1
1
  import { readFileSync, existsSync } from 'fs'
2
2
  import { homedir } from 'os'
3
3
  import { join } from 'path'
4
- import { checkToken, checkSkills, resolveBase } from './diagnose.mjs'
4
+ import { checkToken, resolveBase } from './diagnose.mjs'
5
5
 
6
6
  // `npx @theronap/cortex-mcp doctor` — a live, one-command health check.
7
7
  //
8
8
  // This is the "is it ACTUALLY working?" tool that was missing: it reads your token, calls the
9
- // real Agnoclast API, and prints a clear PASS/FAIL with the actual cause. No Claude Code restart
9
+ // real Cortex API, and prints a clear PASS/FAIL with the actual cause. No Claude Code restart
10
10
  // needed — so onboarding can confirm the connection independently of "did the server load."
11
11
 
12
12
  // Token resolution: env first, then the Claude config the setup command wrote (so `doctor`
@@ -24,7 +24,7 @@ function resolveToken() {
24
24
  return { token: null, source: null }
25
25
  }
26
26
 
27
- // `status` — the one-line SessionStart variant of doctor: a visible "is Agnoclast capturing?"
27
+ // `status` — the one-line SessionStart variant of doctor: a visible "is Cortex capturing?"
28
28
  // signal inside Claude Code itself (three-machine dry-run finding 2026-06-09: with no
29
29
  // indicator, a user can't tell whether their sessions are flowing to the org).
30
30
  // Always returns 0 — a status line must never break a session start.
@@ -33,19 +33,19 @@ export async function runStatus() {
33
33
  const out = (m) => process.stdout.write(m + '\n')
34
34
  const { token } = resolveToken()
35
35
  if (!token) {
36
- out('Agnoclast: NOT connected — no token found. Run: npx -y @theronap/cortex-mcp setup <token>')
36
+ out('Cortex: NOT connected — no token found. Run: npx -y @theronap/cortex-mcp setup <token>')
37
37
  return 0
38
38
  }
39
39
  try {
40
40
  const r = await checkToken(token, base)
41
41
  if (r.ok) {
42
42
  const n = typeof r.projectCount === 'number' ? ` · ${r.projectCount} project${r.projectCount === 1 ? '' : 's'} visible` : ''
43
- out(`Agnoclast: connected — sessions on this machine are captured to your org${n}.`)
43
+ out(`Cortex: connected — sessions on this machine are captured to your org${n}.`)
44
44
  } else {
45
- out(`Agnoclast: NOT connected — ${r.diagnosis?.message ?? 'check failed'}. Run: npx -y @theronap/cortex-mcp doctor`)
45
+ out(`Cortex: NOT connected — ${r.diagnosis?.message ?? 'check failed'}. Run: npx -y @theronap/cortex-mcp doctor`)
46
46
  }
47
47
  } catch (e) {
48
- out(`Agnoclast: status check failed (${e?.message ?? String(e)}) — run doctor.`)
48
+ out(`Cortex: status check failed (${e?.message ?? String(e)}) — run doctor.`)
49
49
  }
50
50
  return 0
51
51
  }
@@ -55,7 +55,7 @@ export async function runDoctor() {
55
55
  const out = (m) => process.stdout.write(m + '\n')
56
56
 
57
57
  out('')
58
- out('Agnoclast doctor — checking your connection…')
58
+ out('Cortex doctor — checking your connection…')
59
59
  const { token, source } = resolveToken()
60
60
  out(` token source: ${source ?? 'NONE FOUND'}`)
61
61
  out(` endpoint: ${base}`)
@@ -64,35 +64,11 @@ export async function runDoctor() {
64
64
  const r = await checkToken(token, base)
65
65
 
66
66
  if (r.ok) {
67
- out(' ✓ context — your token authenticates and Agnoclast returned your context.')
67
+ out(' ✓ PASS — your token authenticates and Cortex returned your context.')
68
68
  if (typeof r.projectCount === 'number') out(` You can currently see ${r.projectCount} project(s).`)
69
69
  if (r.requestId) out(` (request id: ${r.requestId})`)
70
-
71
- // A SECOND surface, because one endpoint answering is not "the system is healthy". /api/skills
72
- // 409'd every multi-brain caller for three days while this command printed PASS on the strength
73
- // of /api/mcp-context alone. See checkSkills' header for the full record.
74
- const s = await checkSkills(token, base)
75
- if (s.ok) {
76
- const n = typeof s.skillCount === 'number'
77
- ? ` (${s.skillCount} org skill${s.skillCount === 1 ? '' : 's'} published)` : ''
78
- out(` ✓ skills — the org-skill surface answers${n}.`)
79
- } else {
80
- // NOT a total failure — context works, so capture and retrieval are fine. But it must never
81
- // again render as PASS, because org skills silently stop updating when this breaks.
82
- out(` ✗ skills — ${s.diagnosis?.kind ?? 'error'}${s.status ? ` (HTTP ${s.status})` : ''}`)
83
- out(` ${s.diagnosis?.message ?? 'unknown error'}`)
84
- out('')
85
- out(' ⚠ PARTIAL — your connection is fine and capture/retrieval work, but org-published')
86
- out(' skills cannot be fetched, so they will silently stop updating (the client falls')
87
- out(' back to its last-good cache). Report this rather than ignoring it.')
88
- out('')
89
- return 1
90
- }
91
-
92
- out('')
93
- out(' ✓ PASS — every surface checked is answering.')
94
70
  out('')
95
- out(' If your AI still does not see Agnoclast, the server just is not loaded yet —')
71
+ out(' If your AI still does not see Cortex, the server just is not loaded yet —')
96
72
  out(' fully quit and reopen Claude Code (the MCP server starts on launch).')
97
73
  out('')
98
74
  return 0
@@ -55,7 +55,7 @@ export function renderAntigravityPlist({ home = homedir() } = {}) {
55
55
  * NOTE the AGENT_DIR line is the portability blocker documented above. */
56
56
  export function renderAntigravitySyncSh() {
57
57
  return `#!/bin/bash
58
- # Agnoclast ⇄ Antigravity one-shot sync, triggered by launchd WatchPaths on the Antigravity
58
+ # Cortex ⇄ Antigravity one-shot sync, triggered by launchd WatchPaths on the Antigravity
59
59
  # trajectory store. There is no resident daemon — launchd wakes this on change and it exits.
60
60
  # Token is resolved from the wired MCP config at runtime (never stored in plist/script).
61
61
  # ⚠ AGENT_DIR points at a local dev checkout — see antigravity.mjs BLOCKER note (not coworker-portable).
@@ -6,7 +6,7 @@ import { existsSync } from 'node:fs'
6
6
  import { join } from 'node:path'
7
7
  import { readJson, backupFile, ensureDir, writeJson } from './_fsutil.mjs'
8
8
 
9
- /** Merge the Agnoclast MCP server into a ~/.claude.json object. Pure + idempotent: sets only the
9
+ /** Merge the Cortex MCP server into a ~/.claude.json object. Pure + idempotent: sets only the
10
10
  * `cortex` entry (type:'stdio'), preserves every other server. `spec` = the package@dist-tag string. */
11
11
  export function mergeClaudeMcp(existing, spec, token) {
12
12
  const cfg = existing && typeof existing === 'object' ? { ...existing } : {}
@@ -15,16 +15,14 @@ export function mergeClaudeMcp(existing, spec, token) {
15
15
  return cfg
16
16
  }
17
17
 
18
- /** The Agnoclast tools every seat may run WITHOUT a permission prompt: the read surface, the live
18
+ /** The Cortex tools every seat may run WITHOUT a permission prompt: the read surface, the live
19
19
  * authoring core, and trivially-reversible maintenance. The contract this enforces: authoring is
20
20
  * EXPECTED agent behavior — a page update must never stall on a yes/no dialog the user won't read
21
21
  * (the ask-permission failure mode is how pages go stale). Safe because every page edit is
22
22
  * CAS-protected + snapshotted (page_revisions → page_history/rollback_page).
23
23
  * Deliberately EXCLUDED — these keep prompting: send_imessage (external side effect),
24
24
  * set_page_privacy / set_record_privacy / grant_page_access (visibility widening — the
25
- * unowned-project accessible-default sharp edge, 2026-07-02), replace_variant (destroys a page body
26
- * and deletes a variant row — the one write here that is not merely CAS-protected but genuinely
27
- * lossy at the row level, so the prompt IS the guard D1 argued for), rollback_page, decide_page_merge /
25
+ * unowned-project accessible-default sharp edge, 2026-07-02), rollback_page, decide_page_merge /
28
26
  * decide_file_request / request_file / get_file, create_brain / set_active_brain, alias_page,
29
27
  * set_writing_style. */
30
28
  export const CORTEX_ALLOWED_TOOLS = [
@@ -38,7 +36,7 @@ export const CORTEX_ALLOWED_TOOLS = [
38
36
  'set_page_validity', 'snooze_red_link', 'attribute_thread',
39
37
  ].map((t) => `mcp__cortex__${t}`)
40
38
 
41
- /** Merge Agnoclast's Claude Code hooks into a ~/.claude/settings.json object. Pure + idempotent: drops
39
+ /** Merge Cortex's Claude Code hooks into a ~/.claude/settings.json object. Pure + idempotent: drops
42
40
  * any prior cortex entry (old token/path/version) from each hook array before appending the current
43
41
  * one — capture (Stop), status + skills-repair + snapshot-context (SessionStart), hydrate
44
42
  * (UserPromptSubmit), precompact (PreCompact). Commands carry NO inline token (each subcommand
@@ -6,7 +6,7 @@ import { existsSync, readFileSync, writeFileSync } from 'node:fs'
6
6
  import { join } from 'node:path'
7
7
  import { readJson, backupFile, ensureDir, writeJson } from './_fsutil.mjs'
8
8
 
9
- /** Merge the Agnoclast MCP server into a Codex config.toml. Pure + idempotent: strips any existing
9
+ /** Merge the Cortex MCP server into a Codex config.toml. Pure + idempotent: strips any existing
10
10
  * [mcp_servers.cortex] / [mcp_servers.cortex.env] tables (so re-runs update in place rather than
11
11
  * duplicating — a duplicate TOML table would break Codex's parser), preserves every other table,
12
12
  * and appends a fresh block. Only touches the cortex tables; never rewrites the user's config. */
@@ -6,7 +6,7 @@ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs'
6
6
  import { join } from 'node:path'
7
7
  import { backupFile } from './_fsutil.mjs'
8
8
 
9
- /** Merge the Agnoclast MCP server into a Cursor mcp.json object. Pure + idempotent: sets only the
9
+ /** Merge the Cortex MCP server into a Cursor mcp.json object. Pure + idempotent: sets only the
10
10
  * `cortex` entry, preserves every other server. `spec` is the package@dist-tag STRING — the same
11
11
  * convention claude/codex/setup.mjs use, so one install driver can pass a single spec to every adapter. */
12
12
  export function mergeCursorMcp(existing, spec, token) {
@@ -1,4 +1,4 @@
1
- // Editor adapter registry (P0-b task 1). One place that knows every editor Agnoclast can wire.
1
+ // Editor adapter registry (P0-b task 1). One place that knows every editor Cortex can wire.
2
2
  // Adding an editor = add a module here; the install driver + capability manifest fall out of it.
3
3
  //
4
4
  // EditorAdapter shape (see the four modules):
@@ -26,42 +26,8 @@ function repoFullNameFromRemote(cwd) {
26
26
  return m ? `${m[1]}/${m[2]}` : null
27
27
  }
28
28
 
29
- // `--brain <name-or-org-id>` — which brain an UNROUTED repo's graph event lands in.
30
- //
31
- // Deliberately a flag and nothing cleverer. /api/timeline/graphify routes deterministically by repo
32
- // (resolveSourceRoute) and only asks when the repo has no route; its comment is blunt about why it
33
- // must not be guessed: this writes a `records` row unique on (org_id, dedupe_key), so "a repo whose
34
- // graph updates land in two brains becomes two record sets that never reconcile, and re-pointing
35
- // later converges on nothing (ADR-0020). It is the one write class where a wrong answer is genuinely
36
- // unrecoverable." So: no sweep (unlike `resolve`, which is a maintenance pass over everything) and
37
- // no auto-pick.
38
- //
39
- // It does NOT create a source route as a side effect, though that would stop the question recurring:
40
- // routes are append-only precisely because "re-pointing a live source splits its history
41
- // irreparably", and a near-irreversible write should not fall out of a CLI flag. This command runs
42
- // from a per-repo cron/launchd job, so the flag lives in that job's definition — answered once,
43
- // where it is visible. (Route creation currently has NO client on any surface; that is a separate
44
- // gap, not this command's to paper over.)
45
- // Split argv into { cwd, brain }. Pure + exported so the ordering trap below is unit-testable
46
- // without a git repo, a graphify binary or a network.
47
- //
48
- // THE TRAP: argv[0] doubles as the optional repo path, and `--brain`'s VALUE has no leading '-'.
49
- // Parsed naively, `graphify-sync --brain Personal` reads "Personal" as the path and syncs whatever
50
- // happens to be there. So the flag and its value are stripped BEFORE the positional check.
51
- export function parseGraphifyArgs(argv = [], fallbackCwd = process.cwd()) {
52
- const bIdx = argv.indexOf('--brain')
53
- const brain = bIdx === -1 ? null : argv[bIdx + 1]
54
- if (bIdx !== -1 && (!brain || brain.startsWith('-'))) {
55
- return { error: 'Usage: graphify-sync [path] [--brain <name-or-org-id>]' }
56
- }
57
- const rest = bIdx === -1 ? argv : argv.filter((_, i) => i !== bIdx && i !== bIdx + 1)
58
- return { cwd: rest[0] && !rest[0].startsWith('-') ? rest[0] : fallbackCwd, brain }
59
- }
60
-
61
29
  export async function runGraphifySync(argv = []) {
62
- const parsed = parseGraphifyArgs(argv)
63
- if (parsed.error) { process.stderr.write(parsed.error + '\n'); return 1 }
64
- const { cwd, brain } = parsed
30
+ const cwd = argv[0] && !argv[0].startsWith('-') ? argv[0] : process.cwd()
65
31
  const TOKEN = process.env.CORTEX_TOKEN
66
32
  const BASE = resolveBase(process.env.CORTEX_URL)
67
33
  if (!TOKEN) {
@@ -107,27 +73,16 @@ export async function runGraphifySync(argv = []) {
107
73
  const res = await fetchCortex(`${BASE}/api/timeline/graphify`, {
108
74
  method: 'POST',
109
75
  headers: { Authorization: `Bearer ${TOKEN}`, 'Content-Type': 'application/json' },
110
- body: JSON.stringify({ repo, commitSha, nodeCount, edgeCount, communityCount, ...(brain ? { brain } : {}) }),
76
+ body: JSON.stringify({ repo, commitSha, nodeCount, edgeCount, communityCount }),
111
77
  })
112
78
  if (!res.ok) {
113
79
  const body = await res.text()
114
- const d = classify(res.status, res.headers.get('content-type'), body, res.headers.get('x-vercel-id'))
115
- process.stderr.write(d.message + '\n')
116
- // classify names the brains but speaks in MCP terms ("re-run this tool with `brain`"). Say the
117
- // actual flag, and where to put it — this runs unattended from cron, so the person reading this
118
- // is looking at a log after the fact, not a prompt.
119
- if (res.status === 409 && !brain) {
120
- process.stderr.write(
121
- `\n${repo} has no routing decision yet, so it cannot be filed without one.\n` +
122
- `Re-run with: cortex-mcp graphify-sync ${cwd === process.cwd() ? '' : `${cwd} `}--brain "<name-or-org-id>"\n` +
123
- `and add that flag to this repo's cron/launchd job so it stops asking.\n`,
124
- )
125
- }
80
+ process.stderr.write(classify(res.status, res.headers.get('content-type'), body, res.headers.get('x-vercel-id')).message + '\n')
126
81
  return 1
127
82
  }
128
83
  const payload = await res.json()
129
84
  process.stdout.write(
130
- `Logged to Agnoclast timeline: ${repo}@${commitSha.slice(0, 7)} (${nodeCount} nodes, ${edgeCount} edges, ${communityCount} communities)` +
85
+ `Logged to Cortex timeline: ${repo}@${commitSha.slice(0, 7)} (${nodeCount} nodes, ${edgeCount} edges, ${communityCount} communities)` +
131
86
  `${payload.inserted ? '' : ' (already logged)'}\n`,
132
87
  )
133
88
  return 0
package/lib/grep_cli.mjs CHANGED
@@ -56,7 +56,7 @@ export async function runGrep(rest = []) {
56
56
  const TOKEN = process.env.CORTEX_TOKEN
57
57
  const BASE = resolveBase(process.env.CORTEX_URL)
58
58
  if (!TOKEN) {
59
- process.stderr.write('cortex grep: CORTEX_TOKEN is required (get yours from the Agnoclast console).\n')
59
+ process.stderr.write('cortex grep: CORTEX_TOKEN is required (get yours from the Cortex console).\n')
60
60
  return 1
61
61
  }
62
62
  const { query, mode, max } = parseGrepArgs(rest)