@theronap/cortex-mcp 0.9.18 → 0.9.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cortex-mcp.mjs +6 -0
- package/lib/precompact.mjs +16 -0
- package/lib/server.mjs +84 -0
- package/lib/setup.mjs +16 -0
- package/package.json +1 -1
- package/skills/log/SKILL.md +10 -0
package/bin/cortex-mcp.mjs
CHANGED
|
@@ -137,6 +137,12 @@ if (cmd === 'setup') {
|
|
|
137
137
|
// Install / repair the managed Cortex skills repository. No network — exits naturally.
|
|
138
138
|
const { runSkills } = await import('../lib/skills.mjs')
|
|
139
139
|
process.exitCode = await runSkills(rest)
|
|
140
|
+
} else if (cmd === 'precompact') {
|
|
141
|
+
// PreCompact hook (③ live wiki authoring, best-effort): print an "author now" reminder so the session
|
|
142
|
+
// sweeps its understanding into the wiki BEFORE compaction drops it. A hook cannot force a model turn
|
|
143
|
+
// (D2) — this is the documented fallback; /log is the hard backstop. No network; prints + exits.
|
|
144
|
+
const { runPrecompactReminder } = await import('../lib/precompact.mjs')
|
|
145
|
+
runPrecompactReminder()
|
|
140
146
|
} else {
|
|
141
147
|
// Default: run the MCP server (stays alive; never exits).
|
|
142
148
|
const { runServer } = await import('../lib/server.mjs')
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// PreCompact "author now" reminder (③ live wiki authoring, [[cortex-wiki-authoring-spec]] D2).
|
|
2
|
+
//
|
|
3
|
+
// Wired by setup.mjs as a PreCompact hook. A hook CANNOT force a model turn — it can only inject text
|
|
4
|
+
// the model sees on its next turn (best-effort). So this prints a reminder to sweep understanding into
|
|
5
|
+
// the wiki BEFORE compaction discards the session's hot mental model. The HARD backstop is the /log
|
|
6
|
+
// skill (cortex-log step 5); this catches the in-session compaction that would otherwise lose the magic.
|
|
7
|
+
//
|
|
8
|
+
// Output goes to stdout, which the Claude Code harness surfaces as additional context for the next turn.
|
|
9
|
+
export function runPrecompactReminder() {
|
|
10
|
+
process.stdout.write(
|
|
11
|
+
'Cortex: context is about to compact. If your understanding of any node (the project(s) you worked ' +
|
|
12
|
+
'on, people you coordinated with, or yourself) advanced this session, AUTHOR it into the wiki NOW ' +
|
|
13
|
+
'before it is lost: call `authoring_context` then `author` for each. This is a synthesis of your ' +
|
|
14
|
+
'compiled understanding with inline [[links]], not a transcript dump. Skip nodes you did not advance.\n',
|
|
15
|
+
)
|
|
16
|
+
}
|
package/lib/server.mjs
CHANGED
|
@@ -580,5 +580,89 @@ export async function runServer(version) {
|
|
|
580
580
|
},
|
|
581
581
|
)
|
|
582
582
|
|
|
583
|
+
// ── ③ LIVE WIKI AUTHORING ([[cortex-wiki-authoring-spec]]) ────────────────────────────────────────
|
|
584
|
+
// Two tools the working session uses to AUTHOR its understanding into the org wiki while it's hot:
|
|
585
|
+
// authoring_context → the companion call (§3): fetch the visible NAMESPACE + the node-type connection
|
|
586
|
+
// rules BEFORE writing, so the page links canonically (the L2 lever).
|
|
587
|
+
// author → the write (§9 step 3/4): hand Cortex a finished page (summary + sections WITH
|
|
588
|
+
// inline [[links]]); the server runs the resolution pass + tier-safe 2B write.
|
|
589
|
+
|
|
590
|
+
server.registerTool(
|
|
591
|
+
'authoring_context',
|
|
592
|
+
{
|
|
593
|
+
title: 'Authoring context (call before author)',
|
|
594
|
+
description:
|
|
595
|
+
'Fetch the scaffolding to author a Cortex wiki node: the canonical NAMESPACE (existing node names — link to these with the EXACT name inside [[ ]]) and the node-type CONNECTION RULES (what kinds of links to look for). ALWAYS call this BEFORE `author` so the page links to real nodes by their established names instead of minting synonyms. 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.',
|
|
596
|
+
inputSchema: {
|
|
597
|
+
kind: z.enum(['project', 'person', 'org', 'user']).optional().describe('the node type you are about to author (default project)'),
|
|
598
|
+
},
|
|
599
|
+
},
|
|
600
|
+
async ({ kind }) => {
|
|
601
|
+
const k = kind ?? 'project'
|
|
602
|
+
let res
|
|
603
|
+
try {
|
|
604
|
+
res = await fetchCortex(`${BASE}/api/brain/authoring-context?kind=${k}`, { headers: { Authorization: `Bearer ${TOKEN}` } })
|
|
605
|
+
} catch (e) {
|
|
606
|
+
return { content: [{ type: 'text', text: `Could not fetch authoring context: ${e.message}` }] }
|
|
607
|
+
}
|
|
608
|
+
if (!res.ok) {
|
|
609
|
+
const d = classify(res.status, res.headers.get('content-type'), await res.text(), res.headers.get('x-vercel-id'))
|
|
610
|
+
return { content: [{ type: 'text', text: `Could not fetch authoring context: ${d.message}` }] }
|
|
611
|
+
}
|
|
612
|
+
const { connectionRules, namespace } = await res.json()
|
|
613
|
+
const ns = Array.isArray(namespace) ? namespace : []
|
|
614
|
+
const nsList = ns.map((n) => `[[${n}]]`).join(', ')
|
|
615
|
+
const text =
|
|
616
|
+
`Authoring a "${k}" node. Connection rules (kinds of links to look for):\n${connectionRules}\n\n` +
|
|
617
|
+
`NAMESPACE — ${ns.length} existing nodes; link with the EXACT name inside [[ ]]:\n${nsList}\n\n` +
|
|
618
|
+
`Now author the page (summary + sections) with inline [[links]] woven into the prose. Link, do not restate. ` +
|
|
619
|
+
`For something real that is not in this namespace, still write [[Name]] (a red-link). Then call \`author\`.`
|
|
620
|
+
return { content: [{ type: 'text', text }] }
|
|
621
|
+
},
|
|
622
|
+
)
|
|
623
|
+
|
|
624
|
+
server.registerTool(
|
|
625
|
+
'author',
|
|
626
|
+
{
|
|
627
|
+
title: 'Author a wiki node (live, while it is hot)',
|
|
628
|
+
description:
|
|
629
|
+
'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. Use this continuously whenever your understanding of a node meaningfully advanced, and at session end (/log).',
|
|
630
|
+
inputSchema: {
|
|
631
|
+
kind: z.enum(['project', 'person', 'org', 'user']).describe('the node type'),
|
|
632
|
+
name: z.string().describe('the EXACT canonical node name (from the namespace), e.g. "Cortex" or "Theron Peterson"'),
|
|
633
|
+
summary: z.string().describe('one-sentence summary of what this is and its current state (may contain [[links]])'),
|
|
634
|
+
sections: z.array(z.object({
|
|
635
|
+
heading: z.string().describe('e.g. Overview, Current state, Decisions, Open threads, People'),
|
|
636
|
+
body: z.string().describe('dense markdown WITH inline [[links]] where the prose references another node'),
|
|
637
|
+
})).describe('3-5 sections; the page body'),
|
|
638
|
+
tier: z.enum(['accessible', 'scoped', 'confidential']).optional().describe('visibility tier (default accessible — the shareable page)'),
|
|
639
|
+
},
|
|
640
|
+
},
|
|
641
|
+
async ({ kind, name, summary, sections, tier }) => {
|
|
642
|
+
const pages = [{ tier: tier ?? 'accessible', summary, sections: Array.isArray(sections) ? sections : [] }]
|
|
643
|
+
let res
|
|
644
|
+
try {
|
|
645
|
+
res = await fetchCortex(`${BASE}/api/brain/author`, {
|
|
646
|
+
method: 'POST',
|
|
647
|
+
headers: { Authorization: `Bearer ${TOKEN}`, 'Content-Type': 'application/json' },
|
|
648
|
+
body: JSON.stringify({ kind, name, pages }),
|
|
649
|
+
})
|
|
650
|
+
} catch (e) {
|
|
651
|
+
return { content: [{ type: 'text', text: `Could not author "${name}": ${e.message}` }] }
|
|
652
|
+
}
|
|
653
|
+
if (!res.ok) {
|
|
654
|
+
const d = classify(res.status, res.headers.get('content-type'), await res.text(), res.headers.get('x-vercel-id'))
|
|
655
|
+
return { content: [{ type: 'text', text: `Could not author "${name}": ${d.message}` }] }
|
|
656
|
+
}
|
|
657
|
+
const out = await res.json()
|
|
658
|
+
const blue = out?.links?.blue ?? 0
|
|
659
|
+
const red = out?.links?.red ?? 0
|
|
660
|
+
const redList = Array.isArray(out?.redLinks) && out.redLinks.length ? `\nRed-links (wanted nodes): ${out.redLinks.map((r) => `[[${r}]]`).join(', ')}` : ''
|
|
661
|
+
const note = out?.built ? `Authored "${name}" (${out.built} tier${out.built === 1 ? '' : 's'}). Links: ${blue} resolved, ${red} red.${redList}`
|
|
662
|
+
: `No change to "${name}"${out?.skipped?.length ? ` (${out.skipped.join(', ')})` : ''}.`
|
|
663
|
+
return { content: [{ type: 'text', text: note }] }
|
|
664
|
+
},
|
|
665
|
+
)
|
|
666
|
+
|
|
583
667
|
await server.connect(new StdioServerTransport())
|
|
584
668
|
}
|
package/lib/setup.mjs
CHANGED
|
@@ -181,6 +181,22 @@ export async function runSetup(argv, version) {
|
|
|
181
181
|
}
|
|
182
182
|
sgrp.hooks.push({ type: 'command', command: snapshotCmd })
|
|
183
183
|
|
|
184
|
+
// PreCompact "author now" reminder (③ live wiki authoring, best-effort). Merges into the PreCompact
|
|
185
|
+
// array WITHOUT clobbering other hooks (filters only prior cortex entries, then appends to the ''
|
|
186
|
+
// matcher group). A hook can't force a turn — this just nudges the session to sweep understanding
|
|
187
|
+
// into the wiki before compaction; the /log skill is the hard backstop.
|
|
188
|
+
s.hooks.PreCompact = Array.isArray(s.hooks.PreCompact) ? s.hooks.PreCompact : []
|
|
189
|
+
const precompactCmd = `npx -y ${spec} precompact`
|
|
190
|
+
for (const pg of s.hooks.PreCompact) {
|
|
191
|
+
if (Array.isArray(pg.hooks)) {
|
|
192
|
+
pg.hooks = pg.hooks.filter((h) => !/cortex-mcp(@[^ ]*)? precompact/.test(h.command ?? ''))
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
let pgrp = s.hooks.PreCompact.find((g) => (g.matcher ?? '') === '')
|
|
196
|
+
if (!pgrp) { pgrp = { matcher: '', hooks: [] }; s.hooks.PreCompact.push(pgrp) }
|
|
197
|
+
pgrp.hooks = pgrp.hooks ?? []
|
|
198
|
+
pgrp.hooks.push({ type: 'command', command: precompactCmd })
|
|
199
|
+
|
|
184
200
|
ensureDir(settingsJson)
|
|
185
201
|
writeFileSync(settingsJson, JSON.stringify(s, null, 2))
|
|
186
202
|
log(` ✓ Capture hook + status line → ${settingsJson}${bak ? ' (backup saved)' : ''}`)
|
package/package.json
CHANGED
package/skills/log/SKILL.md
CHANGED
|
@@ -39,6 +39,15 @@ No arguments. Read the conversation context.
|
|
|
39
39
|
4. **Confirm + flag privacy** — `log_session` returns a confirmation; if it errors, tell the user to
|
|
40
40
|
run `npx -y @theronap/cortex-mcp doctor`. If any record from this session should be confidential,
|
|
41
41
|
note it so the user can mark it (`set_record_privacy`). Default is org-visible under access rules.
|
|
42
|
+
5. **Sweep the wiki (author what you now understand)** — the HARD backstop for live authoring
|
|
43
|
+
([[cortex-wiki-authoring-spec]] D2). For each node whose understanding meaningfully advanced this
|
|
44
|
+
session (the project(s) worked on, people you coordinated with, and yourself when your own focus
|
|
45
|
+
shifted): call `authoring_context` for its kind, then `author` to write the page from your compiled
|
|
46
|
+
understanding — what it IS, where it stands, dated decisions, open threads, key people — with inline
|
|
47
|
+
`[[links]]` to other nodes (canonical names from the namespace; red-links for wanted-but-absent
|
|
48
|
+
nodes). This is a synthesis, not a transcript dump. Skip nodes you didn't actually advance. If you
|
|
49
|
+
already authored a node mid-session and nothing changed since, `author` will report "no change" —
|
|
50
|
+
that's fine.
|
|
42
51
|
|
|
43
52
|
## Output
|
|
44
53
|
|
|
@@ -52,6 +61,7 @@ After calling `log_session`, show a short structured summary:
|
|
|
52
61
|
**Open / blocked:** anything unresolved or waiting on someone
|
|
53
62
|
**Coordinated with:** people involved
|
|
54
63
|
**Logged:** ✅ persisted as the session's record (authoritative) (or ⚠ log_session errored — run doctor)
|
|
64
|
+
**Wiki authored:** [[Node A]], [[Node B]] — pages updated (or "— nothing advanced this session")
|
|
55
65
|
```
|
|
56
66
|
|
|
57
67
|
## Safety rules
|