@theronap/cortex-mcp 0.9.71 → 0.9.73
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 +3 -3
- package/bin/cortex-mcp.mjs +15 -15
- package/lib/capture.mjs +1 -1
- package/lib/code_graph_cli.mjs +1 -1
- package/lib/context_log.mjs +5 -5
- package/lib/diagnose.mjs +9 -9
- package/lib/docs_scan.mjs +4 -4
- package/lib/doctor.mjs +9 -9
- package/lib/editors/antigravity.mjs +1 -1
- package/lib/editors/claude.mjs +3 -3
- package/lib/editors/codex.mjs +1 -1
- package/lib/editors/cursor.mjs +1 -1
- package/lib/editors/index.mjs +1 -1
- package/lib/graphify_sync.mjs +1 -1
- package/lib/grep_cli.mjs +1 -1
- package/lib/hydrate.mjs +1 -1
- package/lib/imessage_send.mjs +1 -1
- package/lib/ingest_folder.mjs +2 -2
- package/lib/install.mjs +6 -6
- package/lib/precompact.mjs +1 -1
- package/lib/redact.mjs +1 -1
- package/lib/server.mjs +25 -18
- package/lib/setup.mjs +8 -8
- package/lib/skills.mjs +5 -5
- package/lib/uninstall.mjs +3 -3
- package/package.json +1 -1
- package/skills/author-docs/SKILL.md +5 -5
- package/skills/context/SKILL.md +4 -4
- package/skills/log/SKILL.md +6 -6
- package/skills/walkthrough/SKILL.md +1 -1
package/README.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# cortex-mcp
|
|
2
2
|
|
|
3
|
-
Connect your AI assistant to **
|
|
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.
|
|
4
4
|
|
|
5
5
|
## Setup
|
|
6
6
|
|
|
7
|
-
1. Get your personal token from the
|
|
7
|
+
1. Get your personal token from the Agnoclast 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 **Cortex** — your org's projects, recent activity
|
|
|
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
|
|
24
|
+
will log the exact baseline Agnoclast context to `~/.cortex/context-snapshots/`.
|
|
25
25
|
|
|
26
26
|
No clone, no path, no build step — `npx` fetches and runs it.
|
|
27
27
|
|
package/bin/cortex-mcp.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* cortex-mcp — connect your AI assistant to
|
|
3
|
+
* cortex-mcp — connect your AI assistant to Agnoclast.
|
|
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
|
|
11
|
+
* snapshot-context save the exact startup context served by Agnoclast 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
|
|
18
|
+
* Get your token from the Agnoclast 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
|
|
38
|
+
`cortex-mcp ${VERSION} — connect your AI assistant to Agnoclast\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
|
|
42
|
+
`Claude Code after, and your AI sees your Agnoclast 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
|
|
47
|
+
` install [<token>] [--editor auto|all|<id,...>] wire Agnoclast 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
|
|
49
|
+
` uninstall remove ALL Agnoclast 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
|
|
52
|
+
` skills install/repair the managed Agnoclast 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
|
|
54
|
+
` docs-scan detect new/changed local docs pending Agnoclast authoring (used by /cortex-author-docs)\n` +
|
|
55
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
|
|
56
|
+
` snapshot-context save the exact startup context Agnoclast 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
|
|
62
|
+
`Get your token from the Agnoclast 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
|
|
87
|
+
// The cross-editor hub installer: wire Agnoclast 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
|
|
94
|
+
// Full reverse of setup: strip every Agnoclast 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')
|
|
@@ -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
|
|
156
|
+
// UserPromptSubmit hook (① discovery): hydrate the model with query-centered Agnoclast 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
|
|
169
|
+
// Install / repair the managed Agnoclast 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
|
|
38
|
+
// Claude Code Stop hook → POSTs a session digest to Agnoclast 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
|
//
|
package/lib/code_graph_cli.mjs
CHANGED
|
@@ -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
|
|
7
|
+
// whatever repo the session's cwd happens to be in), not org-shared Agnoclast 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.
|
package/lib/context_log.mjs
CHANGED
|
@@ -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('
|
|
52
|
+
out('Agnoclast: 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(`
|
|
61
|
+
out(`Agnoclast: 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(`
|
|
67
|
+
out(`Agnoclast: 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
|
-
'#
|
|
79
|
+
'# Agnoclast 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(`
|
|
97
|
+
out(`Agnoclast: 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 "
|
|
4
|
+
// protection / deployment protection) once surfaced as a bare "Agnoclast 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
|
|
9
|
-
// 4xx/5xx means infrastructure handled the request, not
|
|
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
|
|
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,7 +85,7 @@ 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
|
|
88
|
+
// 'app' → a real Agnoclast 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
|
|
@@ -109,20 +109,20 @@ export function classify(status, contentType, bodyText, requestId) {
|
|
|
109
109
|
kind: 'auth', retriable: false,
|
|
110
110
|
message: `Token rejected (HTTP ${status}: ${appError ?? 'unauthorized'}). The token is invalid, ` +
|
|
111
111
|
`revoked, or for a different deployment — not an infrastructure problem. Get a fresh token from ` +
|
|
112
|
-
`the
|
|
112
|
+
`the Agnoclast console → Connect your AI, then re-run setup.${rid}`,
|
|
113
113
|
}
|
|
114
114
|
}
|
|
115
115
|
if (!isJson) {
|
|
116
116
|
return {
|
|
117
117
|
kind: 'infra', retriable: true,
|
|
118
|
-
message: `Blocked by infrastructure (HTTP ${status}, non-JSON response) — NOT by
|
|
118
|
+
message: `Blocked by infrastructure (HTTP ${status}, non-JSON response) — NOT by Agnoclast auth. This is ` +
|
|
119
119
|
`usually a transient firewall / bot-protection hiccup; retrying often clears it. If it persists, check ` +
|
|
120
120
|
`Vercel firewall / deployment protection on the API route. Re-running setup will not help.${rid}`,
|
|
121
121
|
}
|
|
122
122
|
}
|
|
123
123
|
return {
|
|
124
124
|
kind: 'app', retriable: status >= 500,
|
|
125
|
-
message: `
|
|
125
|
+
message: `Agnoclast API ${status}: ${appError ?? 'unknown error'}.${appHint ? ` ${appHint}` : ''}${rid}`,
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
128
|
|
|
@@ -176,7 +176,7 @@ export async function fetchCortex(url, opts = {}, { retries = 2, baseDelayMs = 4
|
|
|
176
176
|
}
|
|
177
177
|
}
|
|
178
178
|
throw new Error(
|
|
179
|
-
`Could not reach
|
|
179
|
+
`Could not reach Agnoclast at ${url} — ${lastErr?.message ?? 'network error'}. ` +
|
|
180
180
|
`Check your connection (and CORTEX_URL if you set it).`,
|
|
181
181
|
)
|
|
182
182
|
}
|
|
@@ -190,7 +190,7 @@ export async function checkToken(token, base) {
|
|
|
190
190
|
}
|
|
191
191
|
if (!isUuid(token)) {
|
|
192
192
|
return { ok: false, status: 0, diagnosis: { kind: 'config', retriable: false,
|
|
193
|
-
message: `Token "${String(token).slice(0, 8)}…" is not a valid
|
|
193
|
+
message: `Token "${String(token).slice(0, 8)}…" is not a valid Agnoclast token (expected a UUID). ` +
|
|
194
194
|
`Re-run setup with the token from the console.` } }
|
|
195
195
|
}
|
|
196
196
|
const url = `${(base ?? 'https://cortex-console.vercel.app').replace(/\/$/, '')}/api/mcp-context`
|
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
|
-
//
|
|
9
|
+
// Agnoclast 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
|
|
93
|
+
// brain into Agnoclast 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
|
|
114
|
+
out(`⚠ ${abs} looks like the local brain repo — the Robin parity soak forbids re-syncing it into Agnoclast.`)
|
|
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
|
|
166
|
+
out(`${pending.length} doc(s) pending Agnoclast 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
|
@@ -6,7 +6,7 @@ import { checkToken, resolveBase } from './diagnose.mjs'
|
|
|
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
|
|
9
|
+
// real Agnoclast 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
|
|
27
|
+
// `status` — the one-line SessionStart variant of doctor: a visible "is Agnoclast 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('
|
|
36
|
+
out('Agnoclast: 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(`
|
|
43
|
+
out(`Agnoclast: connected — sessions on this machine are captured to your org${n}.`)
|
|
44
44
|
} else {
|
|
45
|
-
out(`
|
|
45
|
+
out(`Agnoclast: NOT connected — ${r.diagnosis?.message ?? 'check failed'}. Run: npx -y @theronap/cortex-mcp doctor`)
|
|
46
46
|
}
|
|
47
47
|
} catch (e) {
|
|
48
|
-
out(`
|
|
48
|
+
out(`Agnoclast: 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('
|
|
58
|
+
out('Agnoclast doctor — checking your connection…')
|
|
59
59
|
const { token, source } = resolveToken()
|
|
60
60
|
out(` token source: ${source ?? 'NONE FOUND'}`)
|
|
61
61
|
out(` endpoint: ${base}`)
|
|
@@ -64,11 +64,11 @@ export async function runDoctor() {
|
|
|
64
64
|
const r = await checkToken(token, base)
|
|
65
65
|
|
|
66
66
|
if (r.ok) {
|
|
67
|
-
out(' ✓ PASS — your token authenticates and
|
|
67
|
+
out(' ✓ PASS — your token authenticates and Agnoclast 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
70
|
out('')
|
|
71
|
-
out(' If your AI still does not see
|
|
71
|
+
out(' If your AI still does not see Agnoclast, the server just is not loaded yet —')
|
|
72
72
|
out(' fully quit and reopen Claude Code (the MCP server starts on launch).')
|
|
73
73
|
out('')
|
|
74
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
|
-
#
|
|
58
|
+
# Agnoclast ⇄ 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).
|
package/lib/editors/claude.mjs
CHANGED
|
@@ -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
|
|
9
|
+
/** Merge the Agnoclast 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,7 +15,7 @@ export function mergeClaudeMcp(existing, spec, token) {
|
|
|
15
15
|
return cfg
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
/** The
|
|
18
|
+
/** The Agnoclast 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
|
|
@@ -38,7 +38,7 @@ export const CORTEX_ALLOWED_TOOLS = [
|
|
|
38
38
|
'set_page_validity', 'snooze_red_link', 'attribute_thread',
|
|
39
39
|
].map((t) => `mcp__cortex__${t}`)
|
|
40
40
|
|
|
41
|
-
/** Merge
|
|
41
|
+
/** Merge Agnoclast's Claude Code hooks into a ~/.claude/settings.json object. Pure + idempotent: drops
|
|
42
42
|
* any prior cortex entry (old token/path/version) from each hook array before appending the current
|
|
43
43
|
* one — capture (Stop), status + skills-repair + snapshot-context (SessionStart), hydrate
|
|
44
44
|
* (UserPromptSubmit), precompact (PreCompact). Commands carry NO inline token (each subcommand
|
package/lib/editors/codex.mjs
CHANGED
|
@@ -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
|
|
9
|
+
/** Merge the Agnoclast 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. */
|
package/lib/editors/cursor.mjs
CHANGED
|
@@ -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
|
|
9
|
+
/** Merge the Agnoclast 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) {
|
package/lib/editors/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Editor adapter registry (P0-b task 1). One place that knows every editor
|
|
1
|
+
// Editor adapter registry (P0-b task 1). One place that knows every editor Agnoclast 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):
|
package/lib/graphify_sync.mjs
CHANGED
|
@@ -82,7 +82,7 @@ export async function runGraphifySync(argv = []) {
|
|
|
82
82
|
}
|
|
83
83
|
const payload = await res.json()
|
|
84
84
|
process.stdout.write(
|
|
85
|
-
`Logged to
|
|
85
|
+
`Logged to Agnoclast timeline: ${repo}@${commitSha.slice(0, 7)} (${nodeCount} nodes, ${edgeCount} edges, ${communityCount} communities)` +
|
|
86
86
|
`${payload.inserted ? '' : ' (already logged)'}\n`,
|
|
87
87
|
)
|
|
88
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
|
|
59
|
+
process.stderr.write('cortex grep: CORTEX_TOKEN is required (get yours from the Agnoclast console).\n')
|
|
60
60
|
return 1
|
|
61
61
|
}
|
|
62
62
|
const { query, mode, max } = parseGrepArgs(rest)
|
package/lib/hydrate.mjs
CHANGED
|
@@ -7,7 +7,7 @@ import { formatReceipt, renderUncounted, subjectOf } from './presence.mjs'
|
|
|
7
7
|
import { writePresence } from './statusline.mjs'
|
|
8
8
|
|
|
9
9
|
// UserPromptSubmit hook (① discovery): on the FIRST substantive turn of a session, hydrate the model
|
|
10
|
-
// with query-centered
|
|
10
|
+
// with query-centered Agnoclast context BEFORE it answers — then never again this session. It closes the
|
|
11
11
|
// gap that read-triggered authoring can't: an agent that never READS the wiki (works from code + memory)
|
|
12
12
|
// never triggers a currency update, and re-derives already-authored truth. cortex-context is a SKILL the
|
|
13
13
|
// model may forget to invoke; this makes the first hydration non-discretionary (the same move that made
|
package/lib/imessage_send.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// send_imessage — outbound iMessage via Messages.app. This is a personal automation, NOT org
|
|
2
|
-
// intelligence: it writes nothing to
|
|
2
|
+
// intelligence: it writes nothing to Agnoclast. Three layers of safety (eng-review D3/D6/D10):
|
|
3
3
|
// D6 argv-safe: recipient + body are passed as osascript `on run argv` arguments, NEVER
|
|
4
4
|
// interpolated into the script source → no AppleScript injection, no quote/newline breakage.
|
|
5
5
|
// D3 draft-by-default: nothing sends unless the caller explicitly passes send:true.
|
package/lib/ingest_folder.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import { join, relative, basename, extname } from 'node:path'
|
|
|
3
3
|
import { fetchCortex, classify, resolveBase } from './diagnose.mjs'
|
|
4
4
|
|
|
5
5
|
// `cortex-mcp ingest-folder <path>` — walk a local markdown folder and upsert each file as an
|
|
6
|
-
// AUTHORED record in
|
|
6
|
+
// AUTHORED record in Agnoclast (source='brain'), so a user's personal digest is built from THEIR
|
|
7
7
|
// notes (Robin parity). Only an EXCERPT of each file leaves the machine (frontmatter + first
|
|
8
8
|
// ~800 chars), never the full body. The server clamps privacy for source='brain' and treats
|
|
9
9
|
// sessionId (the file's relative path) as the stable dedupe key, so re-running updates in place.
|
|
@@ -100,7 +100,7 @@ export async function runIngestFolder(argv) {
|
|
|
100
100
|
if (!path) {
|
|
101
101
|
process.stderr.write(
|
|
102
102
|
'usage: cortex-mcp ingest-folder <path>\n' +
|
|
103
|
-
' Ingest a local markdown folder as your authored
|
|
103
|
+
' Ingest a local markdown folder as your authored Agnoclast records.\n',
|
|
104
104
|
)
|
|
105
105
|
return
|
|
106
106
|
}
|
package/lib/install.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// `cortex install` — the cross-editor hub installer (P0-b). One command that wires the
|
|
1
|
+
// `cortex install` — the cross-editor hub installer (P0-b). One command that wires the Agnoclast MCP
|
|
2
2
|
// server + client shim into EVERY detected editor via the adapter registry, then writes a capability
|
|
3
3
|
// manifest so later pillars (session sync, skill sync, doc sync) know which channels each editor
|
|
4
4
|
// supports. `setup <token>` stays as the single-editor path the console prints; `install` is the
|
|
@@ -78,7 +78,7 @@ export async function runInstall(argv, version) {
|
|
|
78
78
|
process.stderr.write(
|
|
79
79
|
'Usage: npx @theronap/cortex-mcp install [<CORTEX_TOKEN>] [--editor auto|all|<id,...>]\n\n' +
|
|
80
80
|
'No token was given and none is already wired.\n' +
|
|
81
|
-
'Get your token from the
|
|
81
|
+
'Get your token from the Agnoclast console → Connect your AI.\n',
|
|
82
82
|
)
|
|
83
83
|
process.exit(1)
|
|
84
84
|
}
|
|
@@ -92,7 +92,7 @@ export async function runInstall(argv, version) {
|
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
log('')
|
|
95
|
-
log('
|
|
95
|
+
log('Agnoclast install — wiring your AI editors…')
|
|
96
96
|
if (editor === 'auto' && targets.length === 0) {
|
|
97
97
|
log(' (no supported editors detected — pass --editor all to force, or install an editor first)')
|
|
98
98
|
}
|
|
@@ -135,7 +135,7 @@ export async function runInstall(argv, version) {
|
|
|
135
135
|
// Verify the token against the live API — writing config proves "files written", not "it works".
|
|
136
136
|
const base = resolveBase(process.env.CORTEX_URL)
|
|
137
137
|
log('')
|
|
138
|
-
log('Verifying your token against
|
|
138
|
+
log('Verifying your token against Agnoclast…')
|
|
139
139
|
try {
|
|
140
140
|
const health = await checkToken(token, base)
|
|
141
141
|
if (health.ok) {
|
|
@@ -147,13 +147,13 @@ export async function runInstall(argv, version) {
|
|
|
147
147
|
log(' The files are in place; fix the above, then re-check with `doctor`.')
|
|
148
148
|
}
|
|
149
149
|
} catch (e) {
|
|
150
|
-
log(` ⚠ Could not reach
|
|
150
|
+
log(` ⚠ Could not reach Agnoclast to verify (${e.message}). Config is written; re-check with 'doctor'.`)
|
|
151
151
|
}
|
|
152
152
|
|
|
153
153
|
const wired = entries.filter((e) => targetIds.has(e.adapter.id) && e.wired).map((e) => e.adapter.displayName)
|
|
154
154
|
log('')
|
|
155
155
|
log(`⟳ Wired ${wired.length} editor(s): ${wired.join(', ') || '(none)'}`)
|
|
156
|
-
log(' Fully quit and reopen each editor to load the
|
|
156
|
+
log(' Fully quit and reopen each editor to load the Agnoclast server.')
|
|
157
157
|
log(` Console: ${base}`)
|
|
158
158
|
log('')
|
|
159
159
|
}
|
package/lib/precompact.mjs
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// Output goes to stdout, which the Claude Code harness surfaces as additional context for the next turn.
|
|
9
9
|
export function runPrecompactReminder() {
|
|
10
10
|
process.stdout.write(
|
|
11
|
-
'
|
|
11
|
+
'Agnoclast: context is about to compact. If your understanding of any node (the project(s) you worked ' +
|
|
12
12
|
'on, people you coordinated with, or yourself) advanced this session, AUTHOR it into the wiki NOW ' +
|
|
13
13
|
'before it is lost: call `authoring_context` then `author` for each. This is a synthesis of your ' +
|
|
14
14
|
'compiled understanding with inline [[links]], not a transcript dump. Skip nodes you did not advance.\n',
|
package/lib/redact.mjs
CHANGED
|
@@ -21,7 +21,7 @@ const PATTERNS = [
|
|
|
21
21
|
[/xox[baprs]-[A-Za-z0-9-]{10,}/g, '[REDACTED:slack]'],
|
|
22
22
|
[/eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}/g, '[REDACTED:jwt]'],
|
|
23
23
|
[/(Bearer\s+)[A-Za-z0-9._-]{20,}/g, '$1[REDACTED]'],
|
|
24
|
-
//
|
|
24
|
+
// Agnoclast's own login token wherever it appears in KEY=value / KEY: value form (hooks.json,
|
|
25
25
|
// config.toml, shell commands — the 2026-07-02 finding: a grep of hooks.json put the live token
|
|
26
26
|
// in a transcript and nothing below caught it). Specific pattern first for the accurate label.
|
|
27
27
|
[/(CORTEX_TOKEN["']?\s*[=:]\s*["']?)[0-9a-fA-F][0-9a-fA-F-]{30,}/g, '$1[REDACTED:cortex-token]'],
|
package/lib/server.mjs
CHANGED
|
@@ -40,7 +40,7 @@ async function redLinkTriage(BASE, TOKEN, name) {
|
|
|
40
40
|
// file-request tools, and a module-level `fail` would be silently shadowed by it from that point on.
|
|
41
41
|
const toolError = (text) => ({ content: [{ type: 'text', text }], isError: true })
|
|
42
42
|
|
|
43
|
-
// The
|
|
43
|
+
// The Agnoclast MCP server (stdio). Serves the signed-in employee's scoped org
|
|
44
44
|
// context to their AI assistant. CORTEX_TOKEN identifies the user + org.
|
|
45
45
|
|
|
46
46
|
export async function runServer(version) {
|
|
@@ -48,7 +48,7 @@ export async function runServer(version) {
|
|
|
48
48
|
const BASE = resolveBase(process.env.CORTEX_URL)
|
|
49
49
|
|
|
50
50
|
if (!TOKEN) {
|
|
51
|
-
process.stderr.write('cortex-mcp: CORTEX_TOKEN is required. Get yours from the
|
|
51
|
+
process.stderr.write('cortex-mcp: CORTEX_TOKEN is required. Get yours from the Agnoclast console → Connect your AI.\n')
|
|
52
52
|
process.exit(1)
|
|
53
53
|
}
|
|
54
54
|
|
|
@@ -90,7 +90,7 @@ export async function runServer(version) {
|
|
|
90
90
|
const now = Date.now()
|
|
91
91
|
if (cache && now - cache.ts < 5 * 60 * 1000) return cache.text
|
|
92
92
|
// fetchCortex retries transient infra/5xx; classify turns a failure into an honest message
|
|
93
|
-
// (token vs infra-block vs network) instead of a bare "
|
|
93
|
+
// (token vs infra-block vs network) instead of a bare "Agnoclast API 403: unknown".
|
|
94
94
|
const res = await fetchCortex(`${BASE}/api/mcp-context`, { headers: { Authorization: `Bearer ${TOKEN}` } })
|
|
95
95
|
if (!res.ok) {
|
|
96
96
|
const body = await res.text()
|
|
@@ -117,7 +117,7 @@ export async function runServer(version) {
|
|
|
117
117
|
server.registerTool(
|
|
118
118
|
'my_context',
|
|
119
119
|
{
|
|
120
|
-
title: 'My
|
|
120
|
+
title: 'My Agnoclast context',
|
|
121
121
|
description: 'Your current work context from the org. Pass a question to get query-centered session context seeded from the most relevant node and its neighborhood; omit it for the baseline snapshot.',
|
|
122
122
|
inputSchema: { question: z.string().optional().describe('optional opening user question to center the context around') },
|
|
123
123
|
},
|
|
@@ -144,8 +144,8 @@ export async function runServer(version) {
|
|
|
144
144
|
server.registerTool(
|
|
145
145
|
'log_session',
|
|
146
146
|
{
|
|
147
|
-
title: 'Log this session to
|
|
148
|
-
description: 'Persist a CURATED summary of this work session as its durable
|
|
147
|
+
title: 'Log this session to Agnoclast',
|
|
148
|
+
description: 'Persist a CURATED summary of this work session as its durable Agnoclast record (authoritative — supersedes the auto-capture hook). Call at session close after composing the summary. Pass sessionId (the Claude Code session id) if you have it so this dedupes with the auto-capture of the same session.',
|
|
149
149
|
inputSchema: {
|
|
150
150
|
summary: z.string().describe('the curated session summary (what was done, decided, left open) — becomes the durable record'),
|
|
151
151
|
project: z.string().optional().describe('project key/name this session worked in'),
|
|
@@ -172,7 +172,7 @@ export async function runServer(version) {
|
|
|
172
172
|
throw new Error(classify(res.status, res.headers.get('content-type'), body, res.headers.get('x-vercel-id')).message)
|
|
173
173
|
}
|
|
174
174
|
const j = await res.json().catch(() => ({}))
|
|
175
|
-
return { content: [{ type: 'text', text: `Logged to
|
|
175
|
+
return { content: [{ type: 'text', text: `Logged to Agnoclast (authoritative): "${j.title ?? title ?? 'session'}" — ${j.inserted ? 'new record' : 'updated existing'}.` }] }
|
|
176
176
|
},
|
|
177
177
|
)
|
|
178
178
|
|
|
@@ -354,7 +354,7 @@ export async function runServer(version) {
|
|
|
354
354
|
{
|
|
355
355
|
title: 'Query the local code structure graph (graphify)',
|
|
356
356
|
description:
|
|
357
|
-
'Query a structural code graph for the repo at the CURRENT working directory, built locally by graphify (tree-sitter AST — deterministic, no LLM, no server round-trip; this is LOCAL MACHINE data, not org-shared
|
|
357
|
+
'Query a structural code graph for the repo at the CURRENT working directory, built locally by graphify (tree-sitter AST — deterministic, no LLM, no server round-trip; this is LOCAL MACHINE data, not org-shared Agnoclast content, and reflects a snapshot of one commit, not live files). Use for MULTI-HOP questions a single grep cannot answer: what calls/imports/depends on X, how A structurally reaches B, or a repo-wide overview (hub/community files). Do NOT use for single-hop lookups (does file X import Y) — grep is faster and always current. Structure only — it knows what imports/calls what, never WHY; read the actual files or authored Agnoclast pages for intent.',
|
|
358
358
|
inputSchema: {
|
|
359
359
|
action: z.enum(['query', 'path', 'explain']).describe('"query" = open-ended natural-language question (graph traversal); "path" = shortest structural path between two named nodes; "explain" = describe one node and list its direct connections'),
|
|
360
360
|
question: z.string().optional().describe('required for action:"query" — the natural-language question'),
|
|
@@ -419,7 +419,7 @@ export async function runServer(version) {
|
|
|
419
419
|
description:
|
|
420
420
|
'READ the full authored wiki page for one node (project/person/org/you) by its canonical name — every section, every tier you can see. This is how you READ a node; `grep` only LOCATES pages (snippets + their [[links]]), it does not read them. Navigate like a researcher: read the page you need, then FOLLOW its inline [[links]] by calling read_page on each linked name — keep following while the linked pages stay relevant, stop when they do not. You decide how deep to go. Returns only what you are permitted to see.',
|
|
421
421
|
inputSchema: {
|
|
422
|
-
name: z.string().describe('the canonical node name exactly as written (e.g. "
|
|
422
|
+
name: z.string().describe('the canonical node name exactly as written (e.g. "Agnoclast", "Ben", or a [[link]] target) — identifier links ([[repo:owner/name]]) resolve to their authored HOME + a visible-event count'),
|
|
423
423
|
kind: z.enum(['project', 'person', 'org', 'user']).optional().describe('node kind (default project; pass person/org for people/teams)'),
|
|
424
424
|
expand: z.boolean().optional().describe('identifier names only: also list recent visible timeline events for this identifier (default: home + count)'),
|
|
425
425
|
history: z.boolean().optional().describe('node names only: return the node\'s TIMELINE (events joined via its [[repo:…]] stamps, reverse-chron, viewer-visible) instead of the page body. The page is the present; this is the history.'),
|
|
@@ -593,7 +593,7 @@ export async function runServer(version) {
|
|
|
593
593
|
title: 'See a wiki page\'s edit history',
|
|
594
594
|
description: 'Show the VERSION history of an authored wiki page — every prior version, who changed it and when, newest first. This is how you see "what changed on this page and by whom", and it includes privacy changes (re-tiers). Then use `read_page` with a version to view an old body, or `rollback_page` to restore one. (Distinct from read_page\'s `history: true`, which is the raw event timeline via [[repo:…]] stamps.) RLS-scoped: you see history only for pages you may read.',
|
|
595
595
|
inputSchema: {
|
|
596
|
-
name: z.string().describe('the canonical node name exactly as written (e.g. "
|
|
596
|
+
name: z.string().describe('the canonical node name exactly as written (e.g. "Agnoclast", "Ben")'),
|
|
597
597
|
kind: z.enum(['project', 'person', 'org', 'user']).optional().describe('node kind (default project)'),
|
|
598
598
|
limit: z.number().int().optional().describe('how many recent versions to show (default 20, max 200)'),
|
|
599
599
|
},
|
|
@@ -1188,7 +1188,7 @@ export async function runServer(version) {
|
|
|
1188
1188
|
{
|
|
1189
1189
|
title: 'Create a new brain under your existing account',
|
|
1190
1190
|
description: 'Create a brand-new brain (org/workspace) — a fully independent knowledge graph — under your EXISTING account. No new login, no new email/password: this adds a second membership to the account you are already using. Reads never cross brains; new pages default to your active brain, so use pass `brain` when an operation needs one named',
|
|
1191
|
-
inputSchema: { name: z.string().describe('display name for the new brain, e.g. "
|
|
1191
|
+
inputSchema: { name: z.string().describe('display name for the new brain, e.g. "Design Team"') },
|
|
1192
1192
|
},
|
|
1193
1193
|
async ({ name }) => {
|
|
1194
1194
|
let res
|
|
@@ -1279,7 +1279,7 @@ export async function runServer(version) {
|
|
|
1279
1279
|
'my_sessions',
|
|
1280
1280
|
{
|
|
1281
1281
|
title: 'My active AI sessions',
|
|
1282
|
-
description: "See what all of YOUR OWN active
|
|
1282
|
+
description: "See what all of YOUR OWN active Agnoclast/AI sessions are doing right now (working directory + how recently each was active), so you can coordinate across windows/devices. Self-only — only your own sessions, never anyone else's.",
|
|
1283
1283
|
inputSchema: {},
|
|
1284
1284
|
},
|
|
1285
1285
|
async () => {
|
|
@@ -1507,10 +1507,17 @@ export async function runServer(version) {
|
|
|
1507
1507
|
return toolError(`Could not replace variant: ${d.message}`)
|
|
1508
1508
|
}
|
|
1509
1509
|
if (!out) return { content: [{ type: 'text', text: 'Replace reported success, but the server returned no body — re-read the page before assuming it landed.' }] }
|
|
1510
|
+
// The summary rides along with the body, EXCEPT when the source has none — then the target's is
|
|
1511
|
+
// kept rather than erased. Say so on its own line rather than at the tail of `note`: this is the
|
|
1512
|
+
// 2026-08-04 W3 shape, where an empty summary copied over a populated one destroyed 31 of them
|
|
1513
|
+
// under a report that read as success. A rescue the operator does not see is still a silent write.
|
|
1514
|
+
const rescued = out.summaryRescued
|
|
1515
|
+
? `\n\n⚠ The ${out.moved.from} variant had NO summary. The ${out.moved.to} page's own summary was KEPT rather than overwritten with an empty one — check it still describes the body that just landed, and set_summary if not.`
|
|
1516
|
+
: ''
|
|
1510
1517
|
return {
|
|
1511
1518
|
content: [{
|
|
1512
1519
|
type: 'text',
|
|
1513
|
-
text: `Done — "${out.moved.title}": the ${out.moved.from} body now occupies the ${out.moved.to} page (${out.sections} section${out.sections === 1 ? '' : 's'}), and the ${out.moved.from} variant was removed. The node now has ONE variant.
|
|
1520
|
+
text: `Done — "${out.moved.title}": the ${out.moved.from} body now occupies the ${out.moved.to} page (${out.sections} section${out.sections === 1 ? '' : 's'}), and the ${out.moved.from} variant was removed. The node now has ONE variant. Both prior bodies are retained in page history.${rescued}`,
|
|
1514
1521
|
}],
|
|
1515
1522
|
}
|
|
1516
1523
|
},
|
|
@@ -1710,7 +1717,7 @@ export async function runServer(version) {
|
|
|
1710
1717
|
'decide_file_request',
|
|
1711
1718
|
{
|
|
1712
1719
|
title: 'Approve or deny a file request',
|
|
1713
|
-
description: 'As the OWNER of a record, approve or deny someone\'s request for its full original. On approve, your
|
|
1720
|
+
description: 'As the OWNER of a record, approve or deny someone\'s request for its full original. On approve, your Agnoclast desktop app fetches + shares the file. Get request_id from file_requests.',
|
|
1714
1721
|
inputSchema: { request_id: z.string(), decision: z.enum(['approve', 'deny']) },
|
|
1715
1722
|
},
|
|
1716
1723
|
async ({ request_id, decision }) => {
|
|
@@ -1752,7 +1759,7 @@ export async function runServer(version) {
|
|
|
1752
1759
|
},
|
|
1753
1760
|
)
|
|
1754
1761
|
|
|
1755
|
-
// send_imessage — local outbound texting (NOT org intelligence; writes nothing to
|
|
1762
|
+
// send_imessage — local outbound texting (NOT org intelligence; writes nothing to Agnoclast). Runs on
|
|
1756
1763
|
// this machine via Messages.app. Draft-by-default + recipient allowlist + OOB confirm (D3/D6/D10).
|
|
1757
1764
|
server.registerTool(
|
|
1758
1765
|
'send_imessage',
|
|
@@ -1776,7 +1783,7 @@ export async function runServer(version) {
|
|
|
1776
1783
|
// Two tools the working session uses to AUTHOR its understanding into the org wiki while it's hot:
|
|
1777
1784
|
// authoring_context → the companion call (§3): fetch the visible NAMESPACE + the node-type connection
|
|
1778
1785
|
// rules BEFORE writing, so the page links canonically (the L2 lever).
|
|
1779
|
-
// author → the write (§9 step 3/4): hand
|
|
1786
|
+
// author → the write (§9 step 3/4): hand Agnoclast a finished page (summary + sections WITH
|
|
1780
1787
|
// inline [[links]]); the server runs the resolution pass + tier-safe 2B write.
|
|
1781
1788
|
|
|
1782
1789
|
server.registerTool(
|
|
@@ -1784,7 +1791,7 @@ export async function runServer(version) {
|
|
|
1784
1791
|
{
|
|
1785
1792
|
title: 'Authoring context (call before author)',
|
|
1786
1793
|
description:
|
|
1787
|
-
'Fetch the scaffolding to author a
|
|
1794
|
+
'Fetch the scaffolding to author a Agnoclast wiki node: the canonical NAMESPACE (current node names — link to these with the EXACT name inside [[ ]]), any deliberately RETIRED page names and their successors, and the node-type CONNECTION RULES. ALWAYS call this BEFORE `author` so the page links to current knowledge rather than minting synonyms or reviving a retired page. Reference a node in the namespace as [[Name]]; if you reference something real that is NOT in the namespace, still write [[Name]] — that is a red-link marking a node worth creating. If the page IS about a specific code repo or chat channel, also stamp it once — [[repo:owner/name]] or [[channel:name]] (lowercase, no #) — identifier join keys, not page links.',
|
|
1788
1795
|
inputSchema: {
|
|
1789
1796
|
kind: z.enum(['project', 'person', 'org', 'user']).optional().describe('the node type you are about to author (default project)'),
|
|
1790
1797
|
brain: z.string().optional().describe('which brain\'s namespace to describe — pass the SAME brain you will pass to `author`, so the namespace you plan against is the one your write lands in. Unnecessary when you only have one brain.'),
|
|
@@ -1827,7 +1834,7 @@ export async function runServer(version) {
|
|
|
1827
1834
|
'Write your CURRENT understanding of a project/person/org/you into the org wiki as a maintained page. Call `authoring_context` FIRST. Author from your own synthesis of the session — the compiled mental model, not a transcript dump: what it IS, where it stands, dated decisions, open threads, key people. Weave inline [[links]] to other nodes (canonical names from the namespace; red-links for wanted-but-absent nodes). The server re-authorizes the tier and resolves links. CREATES the node if it does not exist yet (project/person/org) — the conversation IS the evidence, so a brand-new entity that surfaced only in this session is authorable on the spot; you do NOT need prior records. Because such a node has nothing external to corroborate it, author it DELIBERATELY: only when you genuinely understand it is a real, distinct entity, and use its exact canonical name so it does not duplicate one already in the namespace (`user` nodes are never created). Use this continuously whenever your understanding of a node meaningfully advanced, and at session end (/log). Authoring is PRE-AUTHORIZED — never ask the user "should I update the page?" before calling this (every edit is versioned + reversible via page_history/rollback_page); update, then briefly report what you updated. ⚠ WHICH BRAIN A NEW PAGE GOES IN IS A CONTENT DECISION, SO MAKE IT FROM THE CONTENT. Only a page that exists in NO brain needs this — an update resolves its brain from the page itself. If you hold more than one brain, the server REFUSES a create it cannot attribute (409 `create_needs_brain`) rather than letting the write pointer decide — the pointer is stale out-of-band state that knows nothing about what you are writing. So call `my_brains` (it returns each brain\'s name, page count and sample titles, which is enough to tell what each one is FOR) and pass `brain` up front; that turns a refused round trip into a single call. State which brain you picked and why in one short line, then proceed — do NOT ask when the answer is obvious from the content. DO ask when it is genuinely ambiguous: brains are a confidentiality boundary, so a page born in the wrong one can expose private work to a teammate, and that is not a filing error you can quietly fix later.',
|
|
1828
1835
|
inputSchema: {
|
|
1829
1836
|
kind: z.enum(['project', 'person', 'org', 'user']).describe('the node type'),
|
|
1830
|
-
name: z.string().describe('the canonical node name — an EXACT existing name from the namespace to update it, or a new name to create the node (project/person/org). e.g. "
|
|
1837
|
+
name: z.string().describe('the canonical node name — an EXACT existing name from the namespace to update it, or a new name to create the node (project/person/org). e.g. "Agnoclast" or "Theron Peterson"'),
|
|
1831
1838
|
summary: z.string().describe('one-sentence summary of what this is and its current state (may contain [[links]])'),
|
|
1832
1839
|
sections: z.array(z.object({
|
|
1833
1840
|
heading: z.string().describe('e.g. Overview, Current state, Decisions, Open threads, People'),
|
package/lib/setup.mjs
CHANGED
|
@@ -44,7 +44,7 @@ export async function runSetup(argv, version) {
|
|
|
44
44
|
if (!token || token.startsWith('-')) {
|
|
45
45
|
process.stderr.write(
|
|
46
46
|
'Usage: npx @theronap/cortex-mcp setup <CORTEX_TOKEN>\n\n' +
|
|
47
|
-
'Get your token from the
|
|
47
|
+
'Get your token from the Agnoclast console → Connect your AI.\n'
|
|
48
48
|
)
|
|
49
49
|
process.exit(1)
|
|
50
50
|
}
|
|
@@ -63,7 +63,7 @@ export async function runSetup(argv, version) {
|
|
|
63
63
|
|
|
64
64
|
const log = (m) => process.stdout.write(m + '\n')
|
|
65
65
|
log('')
|
|
66
|
-
log('
|
|
66
|
+
log('Agnoclast setup — wiring your AI assistant…')
|
|
67
67
|
|
|
68
68
|
// ── 1. MCP server in ~/.claude.json ──────────────────────────────────────
|
|
69
69
|
try {
|
|
@@ -83,7 +83,7 @@ export async function runSetup(argv, version) {
|
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
// ── 1b. MCP server in Codex (~/.codex/config.toml), only if Codex is installed ──
|
|
86
|
-
// Codex gets the same
|
|
86
|
+
// Codex gets the same Agnoclast context tools as Claude Code. Non-fatal: a Codex hiccup must
|
|
87
87
|
// never block the primary Claude wiring.
|
|
88
88
|
const codexDir = join(home, '.codex')
|
|
89
89
|
if (existsSync(codexDir)) {
|
|
@@ -157,7 +157,7 @@ export async function runSetup(argv, version) {
|
|
|
157
157
|
// ── 4. Self-verify — writing config proves "files written", NOT "connection works".
|
|
158
158
|
// Actually call the API so a bad/expired token is caught HERE, not 40 minutes into debugging.
|
|
159
159
|
log('')
|
|
160
|
-
log('Verifying your token against
|
|
160
|
+
log('Verifying your token against Agnoclast…')
|
|
161
161
|
const health = await checkToken(token, base)
|
|
162
162
|
if (health.ok) {
|
|
163
163
|
const n = health.projectCount
|
|
@@ -169,8 +169,8 @@ export async function runSetup(argv, version) {
|
|
|
169
169
|
}
|
|
170
170
|
|
|
171
171
|
log('')
|
|
172
|
-
log('⟳ IMPORTANT: fully quit and reopen Claude Code to load the
|
|
173
|
-
log(' Then your AI sees your
|
|
172
|
+
log('⟳ IMPORTANT: fully quit and reopen Claude Code to load the Agnoclast server.')
|
|
173
|
+
log(' Then your AI sees your Agnoclast context and your sessions flow into the org.')
|
|
174
174
|
log(' Re-check anytime: npx -y @theronap/cortex-mcp doctor')
|
|
175
175
|
log(` Console: ${base}`)
|
|
176
176
|
log('')
|
|
@@ -191,11 +191,11 @@ export async function runRepair(version) {
|
|
|
191
191
|
const token = readWiredToken()
|
|
192
192
|
if (!token) {
|
|
193
193
|
process.stderr.write(
|
|
194
|
-
'No existing
|
|
194
|
+
'No existing Agnoclast token found in ~/.claude.json or ~/.codex/config.toml.\n' +
|
|
195
195
|
'Run setup once with your token: npx -y @theronap/cortex-mcp setup <YOUR_TOKEN>\n',
|
|
196
196
|
)
|
|
197
197
|
process.exit(1)
|
|
198
198
|
}
|
|
199
|
-
process.stdout.write('
|
|
199
|
+
process.stdout.write('Agnoclast repair — re-running setup with your existing token at this version…\n')
|
|
200
200
|
await runSetup([token], version)
|
|
201
201
|
}
|
package/lib/skills.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import { homedir } from 'os'
|
|
|
3
3
|
import { join, dirname } from 'path'
|
|
4
4
|
import { fileURLToPath } from 'url'
|
|
5
5
|
|
|
6
|
-
// Managed
|
|
6
|
+
// Managed Agnoclast skills.
|
|
7
7
|
//
|
|
8
8
|
// Two sources, one install pipeline:
|
|
9
9
|
// 1. BUNDLED — shipped inside this package (cortex-log, cortex-context, cortex-author-docs).
|
|
@@ -155,7 +155,7 @@ export function installSkills(opts = {}) {
|
|
|
155
155
|
|
|
156
156
|
if (quiet && (summary.installed.length || summary.repaired.length)) {
|
|
157
157
|
// SessionStart surfaces one line in Claude Code so a silent self-heal isn't invisible.
|
|
158
|
-
process.stdout.write(`
|
|
158
|
+
process.stdout.write(`Agnoclast: synced managed skill(s) into ${summary.targets.join(' + ')}.\n`)
|
|
159
159
|
}
|
|
160
160
|
return summary
|
|
161
161
|
}
|
|
@@ -230,7 +230,7 @@ export async function syncOrgSkills(opts = {}) {
|
|
|
230
230
|
const bits = []
|
|
231
231
|
if (changed.length) bits.push(`synced ${changed.join(', ')}`)
|
|
232
232
|
if (summary.removed.length) bits.push(`removed ${summary.removed.join(', ')}`)
|
|
233
|
-
process.stdout.write(`
|
|
233
|
+
process.stdout.write(`Agnoclast: org skills — ${bits.join('; ')}${summary.source === 'cache' ? ' (offline cache)' : ''}.\n`)
|
|
234
234
|
} else if (!quiet) {
|
|
235
235
|
log(` ✓ org skills up to date (${currentNames.length} published${skipped.length ? `, ${skipped.length} skipped` : ''})`)
|
|
236
236
|
}
|
|
@@ -242,7 +242,7 @@ export async function syncOrgSkills(opts = {}) {
|
|
|
242
242
|
async function runSkillsPush(argv) {
|
|
243
243
|
const { resolveBase, readWiredToken, fetchCortex } = await import('./diagnose.mjs')
|
|
244
244
|
const token = process.env.CORTEX_TOKEN || readWiredToken()
|
|
245
|
-
if (!token) { process.stderr.write('No
|
|
245
|
+
if (!token) { process.stderr.write('No Agnoclast token wired — run setup first.\n'); return 1 }
|
|
246
246
|
const base = resolveBase(process.env.CORTEX_URL)
|
|
247
247
|
|
|
248
248
|
let payload
|
|
@@ -289,7 +289,7 @@ export async function runSkills(argv = []) {
|
|
|
289
289
|
if (argv[0] === 'push') return runSkillsPush(argv.slice(1))
|
|
290
290
|
|
|
291
291
|
const quiet = argv.includes('--quiet')
|
|
292
|
-
if (!quiet) process.stdout.write('\
|
|
292
|
+
if (!quiet) process.stdout.write('\nAgnoclast skills — installing managed skills…\n')
|
|
293
293
|
const r = installSkills({ quiet })
|
|
294
294
|
if (!quiet) {
|
|
295
295
|
if (!r.targets.length) process.stdout.write(' ! No agent CLI found (~/.claude or ~/.codex). Nothing to install.\n')
|
package/lib/uninstall.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import { homedir } from 'os'
|
|
|
3
3
|
import { join } from 'path'
|
|
4
4
|
import { execFileSync } from 'child_process'
|
|
5
5
|
|
|
6
|
-
// Full uninstall — the reverse of setup.mjs. Removes EVERY touch-point
|
|
6
|
+
// Full uninstall — the reverse of setup.mjs. Removes EVERY touch-point Agnoclast writes onto a machine:
|
|
7
7
|
// 1. ~/.claude.json → mcpServers.cortex
|
|
8
8
|
// 2. ~/.claude/settings.json → Stop/SessionStart/PreCompact cortex hooks
|
|
9
9
|
// 3. ~/.codex/config.toml → [mcp_servers.cortex] + [mcp_servers.cortex.env]
|
|
@@ -34,7 +34,7 @@ export function runUninstall(argv = []) {
|
|
|
34
34
|
const act = (msg) => plan.push(msg)
|
|
35
35
|
const write = (path, data) => { if (!dry) { backup(path); writeFileSync(path, data) } }
|
|
36
36
|
|
|
37
|
-
process.stdout.write(dry ? '\
|
|
37
|
+
process.stdout.write(dry ? '\nAgnoclast uninstall — DRY RUN (nothing will change):\n\n' : '\nAgnoclast uninstall — removing all wiring…\n\n')
|
|
38
38
|
|
|
39
39
|
// 1. MCP server out of ~/.claude.json
|
|
40
40
|
editJson(CLAUDE_JSON, (cfg) => {
|
|
@@ -144,7 +144,7 @@ export function runUninstall(argv = []) {
|
|
|
144
144
|
}
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
-
if (plan.length === 0) { process.stdout.write(' Nothing to remove — this machine has no
|
|
147
|
+
if (plan.length === 0) { process.stdout.write(' Nothing to remove — this machine has no Agnoclast wiring.\n\n'); return }
|
|
148
148
|
process.stdout.write(plan.join('\n') + '\n\n')
|
|
149
149
|
if (dry) {
|
|
150
150
|
process.stdout.write('DRY RUN — nothing changed. Re-run without --dry-run to apply.\n')
|
package/package.json
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: cortex-author-docs
|
|
3
|
-
description: Push new/changed documentation (specs, plans, design docs) from disk into
|
|
3
|
+
description: Push new/changed documentation (specs, plans, design docs) from disk into Agnoclast as authored wiki pages. Run after writing a spec/plan/design doc, when the user asks to sync docs to Agnoclast, or as part of session close-out.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
> **
|
|
6
|
+
> **Agnoclast-managed skill.** This file is installed and kept up to date by Agnoclast. Local edits are
|
|
7
7
|
> restored on the next session (a backup of your version is saved alongside). Don't rely on changes here.
|
|
8
8
|
|
|
9
9
|
## Why this exists
|
|
10
10
|
|
|
11
11
|
Specs, plans, and design docs get written to disk (a repo's `docs/`, skill-generated design docs)
|
|
12
|
-
and never reach
|
|
12
|
+
and never reach Agnoclast — so the org brain misses its richest artifacts. A spec IS a page: this
|
|
13
13
|
skill turns pending docs into authored wiki pages. You (the live session) are the pipe — you read
|
|
14
14
|
the doc and author a synthesis. Never dump raw markdown into a page.
|
|
15
15
|
|
|
16
16
|
## When to use
|
|
17
17
|
|
|
18
18
|
- Right after you write or substantially update a spec/plan/design/runbook doc on disk.
|
|
19
|
-
- When the user asks to push/sync docs to
|
|
19
|
+
- When the user asks to push/sync docs to Agnoclast.
|
|
20
20
|
- During session close-out (`/cortex-log` runs this as a sweep step).
|
|
21
21
|
|
|
22
22
|
## Steps
|
|
@@ -67,7 +67,7 @@ the doc and author a synthesis. Never dump raw markdown into a page.
|
|
|
67
67
|
## Safety rules
|
|
68
68
|
|
|
69
69
|
- Do NOT register or sweep the local brain repo (`~/Documents/brain`) while the Robin parity soak
|
|
70
|
-
is running — the experiment forbids re-syncing Robin into
|
|
70
|
+
is running — the experiment forbids re-syncing Robin into Agnoclast mid-window.
|
|
71
71
|
- Respect tiers: if a doc is clearly personal/sensitive, author it `confidential` or ask; default
|
|
72
72
|
for work docs is the author path's normal default.
|
|
73
73
|
- This skill writes wiki pages via the `author` tool only. It never sends external messages and
|
package/skills/context/SKILL.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: cortex-context
|
|
3
|
-
description: Automatically hydrate
|
|
3
|
+
description: Automatically hydrate Agnoclast context at the start of a substantive session. Use when Agnoclast MCP is available and the user has made a real request, so the first answer is grounded in query-centered org context instead of the static baseline alone.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
> **
|
|
6
|
+
> **Agnoclast-managed skill.** This file is installed and kept up to date by Agnoclast. Local edits are
|
|
7
7
|
> restored on the next session (a backup of your version is saved alongside). Don't rely on changes here.
|
|
8
8
|
|
|
9
9
|
## When to use
|
|
@@ -14,12 +14,12 @@ chit-chat and requests where org context is obviously irrelevant.
|
|
|
14
14
|
## Steps
|
|
15
15
|
|
|
16
16
|
1. Call `session_context` with the user's opening request, preserving the actual topic in their words.
|
|
17
|
-
2. Use that returned block as the primary
|
|
17
|
+
2. Use that returned block as the primary Agnoclast grounding for the first response.
|
|
18
18
|
3. If `session_context` is unavailable or errors, fall back to `my_context`.
|
|
19
19
|
4. If the conversation materially changes topics later, call `session_context` again for the new topic.
|
|
20
20
|
|
|
21
21
|
## Safety rules
|
|
22
22
|
|
|
23
|
-
- Do not fabricate
|
|
23
|
+
- Do not fabricate Agnoclast context if the tool fails.
|
|
24
24
|
- Prefer the query-centered `session_context` over static `my_context` whenever the user's topic is clear.
|
|
25
25
|
- Do not call `session_context` for every tiny follow-up; refresh only when the topic meaningfully shifts.
|
package/skills/log/SKILL.md
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: cortex-log
|
|
3
|
-
description: Close out a work session into
|
|
3
|
+
description: Close out a work session into Agnoclast — summarize what happened, confirm it reached the org, and surface anything teammates should know. Run at or near the end of any working session.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
> **
|
|
6
|
+
> **Agnoclast-managed skill.** This file is installed and kept up to date by Agnoclast. Local edits are
|
|
7
7
|
> restored on the next session (a backup of your version is saved alongside). Don't rely on changes here.
|
|
8
8
|
|
|
9
9
|
## Model: session-primary, daily-derived
|
|
10
10
|
|
|
11
|
-
The **session is the primary atomic unit** — one session = one durable
|
|
11
|
+
The **session is the primary atomic unit** — one session = one durable Agnoclast record (via
|
|
12
12
|
`log_session`, keyed by `sessionId`), which is also the per-record privacy unit (`set_record_privacy`
|
|
13
13
|
is per record). Any "what happened today / this week" view is a **derived rollup** over those session
|
|
14
14
|
records, never a separately-authored primary. This mirrors records(atomic) → digests(derived); the
|
|
@@ -16,7 +16,7 @@ personal `/log` skill follows the same shape against the local brain.
|
|
|
16
16
|
|
|
17
17
|
## When to use
|
|
18
18
|
|
|
19
|
-
At the end of a Claude Code session, or after finishing a meaningful phase of work.
|
|
19
|
+
At the end of a Claude Code session, or after finishing a meaningful phase of work. Agnoclast keeps a
|
|
20
20
|
background auto-capture as a fallback, but this skill is the *authoritative* close-out: it composes a
|
|
21
21
|
clean, structured summary and persists THAT as the session's durable record (superseding the
|
|
22
22
|
auto-capture's raw-transcript re-derivation).
|
|
@@ -33,7 +33,7 @@ No arguments. Read the conversation context.
|
|
|
33
33
|
These are the things a teammate or manager would want to know without reading the whole transcript.
|
|
34
34
|
3. **Persist it as the durable record** — call the `log_session` MCP tool with your curated `summary`
|
|
35
35
|
(plus `project`, and the Claude Code `sessionId` if you know it). This writes YOUR summary as the
|
|
36
|
-
session's authoritative
|
|
36
|
+
session's authoritative Agnoclast record (`capture_source='skill'`). The background auto-capture is a
|
|
37
37
|
fallback and will not overwrite it; passing the same `sessionId` the auto-capture uses dedupes them
|
|
38
38
|
onto one record. This — not the raw-transcript re-derivation — is the canonical record going forward.
|
|
39
39
|
4. **Confirm + flag privacy** — `log_session` returns a confirmation; if it errors, tell the user to
|
|
@@ -72,7 +72,7 @@ No arguments. Read the conversation context.
|
|
|
72
72
|
> verify a write; the result you already have in hand is the strongest.
|
|
73
73
|
>
|
|
74
74
|
> **What replaces it — at the moment of each write, not at the end:** a rejected `author` comes back
|
|
75
|
-
> as an ORDINARY tool result with **no error flag** — `Could not author "<page>":
|
|
75
|
+
> as an ORDINARY tool result with **no error flag** — `Could not author "<page>": Agnoclast API 409:
|
|
76
76
|
> <reason>` — and `No change to "<page>"` is a **200 OK that wrote nothing**. Neither is an error at
|
|
77
77
|
> the protocol level, so nothing will interrupt you. **Read the result text of every write; never
|
|
78
78
|
> skim it.** That inline read is where this step's value actually was.
|
|
@@ -3,7 +3,7 @@ name: cortex-walkthrough
|
|
|
3
3
|
description: Run the guided Agnoclast walkthrough for someone new. Use when the person asks for the walkthrough, a tutorial, or getting started — "give me the walkthrough", "walk me through this", "how do I use this", "show me around", "what can this do", "remind me how this works" — or when a brand-new user needs orienting for the first time.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
> **
|
|
6
|
+
> **Agnoclast-managed skill.** This file is installed and kept up to date by Agnoclast. Local edits are
|
|
7
7
|
> restored on the next session (a backup of your version is saved alongside). Don't rely on changes here.
|
|
8
8
|
|
|
9
9
|
# The Agnoclast walkthrough
|