@theronap/cortex-mcp 0.9.62 → 0.9.63

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/lib/capture.mjs CHANGED
@@ -76,6 +76,97 @@ export function repoFullNameFrom(cwd) {
76
76
  }
77
77
  }
78
78
 
79
+ // ── SHR-01/T9 — stamp the repos the session WORKED IN, not the one it was launched from ─────────
80
+ //
81
+ // WHY THIS EXISTS. `repoFullNameFrom(cwd)` above reads the session's LAUNCH directory. Measured
82
+ // 2026-08-03, four days after the stamp shipped: 3,598 → 3,663 claude-code records, and **still zero
83
+ // carried an identifier**. Not because the code was broken — because Claude Code sessions launch from
84
+ // wherever the terminal happened to be. Theron's launch from `~/Documents/brain` (a git repo with no
85
+ // remote), while every bit of the work happened in `~/dev/cortex-worktrees/*` on theronap/cortex.
86
+ // One session that day touched 319+ paths under those worktrees and was stamped with nothing.
87
+ //
88
+ // So the launch directory is the wrong question. What the clearance rule (ADR-0019) actually asks is
89
+ // whether two people *participated in the same work context* — and the transcript already records
90
+ // that directly, as the files the session read and wrote.
91
+ //
92
+ // ⚠ THE THRESHOLD IS A SECURITY KNOB, NOT A TUNING PARAMETER. Every repo stamped here becomes a key
93
+ // that lets that repo's other workers read this session's SCOPED records. Too low and glancing at one
94
+ // file in repo X hands X's members a session that was really about Y; too high and genuine work goes
95
+ // unstamped, which is the bug above. 3 sits in a wide empty gap — real work in a repo touches dozens
96
+ // to hundreds of files, a drive-by check touches one or two — so the rule rarely makes a close call.
97
+ //
98
+ // Chosen deliberately conservative because the direction matters: LOWERING this later takes effect
99
+ // immediately and safely, while RAISING it does NOT revoke stamps already written to
100
+ // records.event_identifiers. Start tight, loosen once there is real collaborator data.
101
+ export const MIN_FILES_FOR_STAMP = 3
102
+
103
+ // Bounds the clearance surface a single session can claim, and the work done to compute it.
104
+ export const MAX_STAMPED_REPOS = 5
105
+ const MAX_DIRS_PROBED = 25
106
+
107
+ // Absolute paths this session touched: file_path/path/notebook_path on any tool call, plus absolute
108
+ // paths appearing in Bash commands. Best-effort and total — a malformed transcript yields [], never
109
+ // throws, because capture must never break a session.
110
+ export function touchedPaths(transcript) {
111
+ const out = []
112
+ if (!transcript) return out
113
+ for (const line of String(transcript).split('\n')) {
114
+ if (!line) continue
115
+ let d
116
+ try { d = JSON.parse(line) } catch { continue }
117
+ const content = d?.message?.content
118
+ if (!Array.isArray(content)) continue
119
+ for (const b of content) {
120
+ if (b?.type !== 'tool_use') continue
121
+ const inp = b.input ?? {}
122
+ for (const k of ['file_path', 'path', 'notebook_path']) {
123
+ if (typeof inp[k] === 'string' && inp[k].startsWith('/')) out.push(inp[k])
124
+ }
125
+ if (typeof inp.command === 'string') {
126
+ // Non-repo matches (/tmp, /usr, …) simply resolve to no repo and cost one cached probe.
127
+ for (const m of inp.command.match(/\/(?:Users|home|opt|srv|var)\/[^\s"'`;|&)<>]+/g) ?? []) out.push(m)
128
+ }
129
+ }
130
+ }
131
+ return out
132
+ }
133
+
134
+ // The repos this session actually worked in. Always a SUPERSET of the launch-cwd behaviour: the
135
+ // launch repo is included unconditionally when it resolves, so nothing that stamps correctly today
136
+ // stops stamping. Everything else must clear MIN_FILES_FOR_STAMP.
137
+ export function repoFullNamesFrom(transcript, cwd) {
138
+ const launch = repoFullNameFrom(cwd)
139
+ const filesByRepo = new Map() // repo -> Set(distinct file paths)
140
+ const touchesByDir = new Map() // dir -> touch count
141
+
142
+ for (const p of touchedPaths(transcript)) {
143
+ const dir = dirname(p)
144
+ touchesByDir.set(dir, (touchesByDir.get(dir) ?? 0) + 1)
145
+ }
146
+
147
+ // Probe the most-touched directories first, so a bounded budget still covers the real work.
148
+ const dirs = [...touchesByDir.entries()].sort((a, b) => b[1] - a[1]).slice(0, MAX_DIRS_PROBED)
149
+ const repoOfDir = new Map()
150
+ for (const [dir] of dirs) {
151
+ if (!repoOfDir.has(dir)) repoOfDir.set(dir, repoFullNameFrom(dir))
152
+ }
153
+
154
+ for (const p of touchedPaths(transcript)) {
155
+ const repo = repoOfDir.get(dirname(p))
156
+ if (!repo) continue
157
+ if (!filesByRepo.has(repo)) filesByRepo.set(repo, new Set())
158
+ filesByRepo.get(repo).add(p)
159
+ }
160
+
161
+ const earned = [...filesByRepo.entries()]
162
+ .filter(([repo, files]) => files.size >= MIN_FILES_FOR_STAMP || repo === launch)
163
+ .sort((a, b) => b[1].size - a[1].size)
164
+ .map(([repo]) => repo)
165
+
166
+ const all = launch ? [launch, ...earned.filter((r) => r !== launch)] : earned
167
+ return all.slice(0, MAX_STAMPED_REPOS)
168
+ }
169
+
79
170
  // (no bun, no repo clone). Always exits 0 — capture must never break a session.
80
171
 
81
172
  function readStdin() {
@@ -202,6 +293,17 @@ async function captureWork(stdinRaw) {
202
293
  // when the cwd is not a GitHub worktree — the record still lands, it just stays private (D2).
203
294
  const repoFullName = repoFullNameFrom(hook.cwd)
204
295
 
296
+ // SHR-01/T9: ...and the repos the session actually WORKED IN, which is usually not the same thing —
297
+ // the launch cwd stamped 0 of 3,663 sessions. Reads the RAW transcript, not the redacted tail above:
298
+ // the tail is truncated to ~6k chars and would miss most of the session's file paths. Nothing from
299
+ // this raw read is transmitted — only the derived 'owner/name' strings leave the machine.
300
+ let repoFullNames = repoFullName ? [repoFullName] : []
301
+ try {
302
+ if (hook.transcript_path) {
303
+ repoFullNames = repoFullNamesFrom(readFileSync(hook.transcript_path, 'utf8'), hook.cwd)
304
+ }
305
+ } catch { /* best-effort — a session with no derivable repo is private, not broken */ }
306
+
205
307
  const common = {
206
308
  source: 'claude-code',
207
309
  project: repo,
@@ -209,7 +311,8 @@ async function captureWork(stdinRaw) {
209
311
  title: `Worked in ${repo}`,
210
312
  payload: { session_id: hook.session_id, cwd: hook.cwd },
211
313
  captureSource: 'hook', // T8: fallback writer — never clobbers a cortex-log ('skill') record
212
- ...(repoFullName ? { repoFullName } : {}),
314
+ ...(repoFullName ? { repoFullName } : {}), // back-compat: older servers read only this
315
+ ...(repoFullNames.length ? { repoFullNames } : {}),
213
316
  ...(hydratedFrom.length ? { hydratedFrom } : {}),
214
317
  }
215
318
  const extracted = transcript ? extractSession(transcript) : null
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theronap/cortex-mcp",
3
- "version": "0.9.62",
3
+ "version": "0.9.63",
4
4
  "description": "Connect your AI assistant to Cortex — your org's projects, activity, gaps, and directives, scoped to you.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,189 @@
1
+ ---
2
+ name: cortex-walkthrough
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
+ ---
5
+
6
+ > **Cortex-managed skill.** This file is installed and kept up to date by Cortex. Local edits are
7
+ > restored on the next session (a backup of your version is saved alongside). Don't rely on changes here.
8
+
9
+ # The Agnoclast walkthrough
10
+
11
+ You are orienting someone who is probably **not technical** and did not ask for a lecture. Treat
12
+ this file as a script to perform, not a document to display.
13
+
14
+ ## The rules, in priority order
15
+
16
+ 1. **Never paste this file at them.** Say the first idea in your own words, ask one question, wait.
17
+ A wall of text is a failed walkthrough even if every word is correct.
18
+ 2. **No jargon. None.** Do not say *node, page, tier, MCP, grep, authoring, graph, retrieval,
19
+ scoped, red-link, records, timeline*. Say "I wrote that down", not "I authored a node". This is
20
+ not a style preference — it is the measured failure mode of this product. A new user was given
21
+ identifier-dense terminology and had to stop and ask what the assistant was talking about.
22
+ *Brain* is the one term you may introduce, and only in walkthrough 3.
23
+ 3. **Use their real life immediately.** Never demo with a made-up example. Ask what is actually on
24
+ their plate and build the walkthrough out of their answer. Someone who ends this holding a list
25
+ of their own real things is converted; someone who watched a demo is not.
26
+ 4. **Do it, don't describe it.** When they mention something, write it down for real, then show them
27
+ it came back. The first run must end with something true stored and retrieved — not with them
28
+ understanding an architecture.
29
+ 5. **One step at a time, and check in.** After each step: *"want to keep going, or is that enough for
30
+ now?"* Stopping early is a success. Say so.
31
+ 6. **When they ramble, that is the product working.** Do not redirect them to be more concise.
32
+ Extract from the mess yourself. Their willingness to talk sloppily is the behaviour you want.
33
+ 7. **Never make them feel behind.** No streaks, no "you haven't used this in a while", no half-done
34
+ setup checklists. If they return after three weeks, pick up like nothing happened.
35
+ 8. **If they ask what it costs or how it works underneath, answer plainly and briefly, then get back
36
+ to their stuff.** Do not pitch.
37
+
38
+ **You are done when** they have said something real, you stored it, and they saw it come back. Ten
39
+ minutes, one loop closed. Everything else is optional.
40
+
41
+ ## Opening
42
+
43
+ Say roughly this, in your own words:
44
+
45
+ > Agnoclast is a memory you and I share. Anything you tell me — a decision, something you need to do,
46
+ > how something works, what someone said — gets written down in one place I read before I answer you.
47
+ > So you stop re-explaining your own life every time you open a chat. It remembers; you don't have to.
48
+ > It's Jarvis, minus the flying suit.
49
+
50
+ Then offer the menu below and let them pick. If they don't care, start with walkthrough 1.
51
+
52
+ ---
53
+
54
+ ## Walkthrough 1 — keeping track of your life
55
+
56
+ *The one most people should do first. About ten minutes.*
57
+
58
+ **The problem it solves:** the cost of holding a hundred small things in your head isn't the
59
+ forgetting, it's the background hum of trying not to forget, running all day underneath everything
60
+ else. This puts that down. Unlike every to-do app they have quit, they never maintain it — they
61
+ mention things in passing and later ask what they were supposed to follow up on.
62
+
63
+ **Step 1 — empty their head.** Ask ONE question and then stop talking: *"What's actually on your
64
+ plate this week?"* Let them talk. Do not interrupt to confirm items or number things back at them
65
+ mid-flow. Interrupting the dump is the most common way to break this.
66
+
67
+ They will undersell it — most people give three things and stop. Follow up **once**, gently, with a
68
+ specific probe: *"anything you're overdue getting back to?"* That usually yields another five. Once.
69
+ Twice is an interrogation.
70
+
71
+ **Step 2 — store it for real while they are still talking**, not in a tidy summary at the end. Then
72
+ play it back grouped plainly: what needs doing, what they're waiting on someone else for, what's
73
+ just worth remembering. Do not invent priorities, due dates or categories they didn't give you —
74
+ inventing structure is how this starts feeling like an app they have to maintain.
75
+
76
+ Invite correction explicitly: *"Tell me what I got wrong."* Then actually change it. The first
77
+ correction is the moment it becomes theirs.
78
+
79
+ **Step 3 — close the loop, for real.** Start a fresh conversation (or at minimum ask the retrieval
80
+ question and answer it purely from what's stored, saying plainly that a brand-new conversation would
81
+ have seen the same): **"What was I supposed to follow up on?"**
82
+
83
+ That is the whole point and it must actually happen. They saved no file, named no document, picked
84
+ no folder. They talked, and it came back.
85
+
86
+ **Step 4 — hand them the one habit that matters**, then stop: *"From now on, whenever something
87
+ lands on you, just say 'don't let me forget X'. That's the whole thing."*
88
+
89
+ **Never** propose a tagging scheme, folder structure, priority system, daily review ritual or naming
90
+ convention. Every one of those is why they quit the last four apps. If they *ask* for structure,
91
+ give the least you can get away with.
92
+
93
+ ---
94
+
95
+ ## Walkthrough 2 — just tell it what's going wrong
96
+
97
+ *The one people are most surprised by, and the highest-converting. Also the easiest to run badly.*
98
+
99
+ **Open by inviting the complaint directly:** *"What's the most annoying thing on your plate right
100
+ now? Don't organise it — just tell me what's going wrong."* Explicit permission to be messy is the
101
+ entire opening move. Do not ask them to "describe a challenge".
102
+
103
+ **Why it works, if they ask:** a frustrated ramble is a *better* input than a careful question. It's
104
+ loaded with who's involved, what's blocked, what has a deadline and what they're worried about — and
105
+ it took nine seconds. A polished prompt would contain less.
106
+
107
+ **Then do all three steps in one turn:**
108
+
109
+ 1. **Untangle it.** Give the mess back as its actual separate parts. Most of the relief is here — a
110
+ mess is heavy mostly because it's undifferentiated.
111
+ 2. **Name the load-bearing piece.** Usually one thing blocks everything else, and usually it isn't
112
+ the part they're angriest about.
113
+ 3. **Produce the artifact.** Draft the email. Write the message. Give them the actual words. **Never
114
+ end on "would you like me to draft that?"** — draft it; they can tell you it's wrong. Stopping at
115
+ analysis is the single most common way to blow this walkthrough, because step 2 feels complete.
116
+
117
+ **Then make the persistence visible, once:** *"I've kept the background on this — next time you can
118
+ just say 'the Henderson thing' and I'll know."* One sentence about the benefit. Do not explain how
119
+ storage works.
120
+
121
+ **Do not clean up their language back at them.** If they were sarcastic or profane, engage with the
122
+ substance in your normal register. Sanitising their framing into corporate-speak reads as
123
+ disapproval and they will start self-editing, which destroys the exact input quality this unlocks.
124
+
125
+ **Do not moralise about venting.** No "sounds like you're under a lot of stress", no wellness pivot,
126
+ no suggesting a break. They came for leverage, not to be handled. Treat the complaint as information
127
+ and move. Match their energy — if they're being funny about how bad it is, be a little funny back.
128
+
129
+ **If they push back on the draft, take it and revise immediately without defending it.**
130
+
131
+ ---
132
+
133
+ ## Walkthrough 3 — personal and work, kept separate
134
+
135
+ *Only run this if they have raised work. Offer once and drop it otherwise — pushing it on someone
136
+ who didn't ask is how onboarding starts feeling like a sales funnel.*
137
+
138
+ **Lead with the guarantee, not the feature.** Their personal brain is private and unreachable from
139
+ anywhere else. Say that before they ask, because it's the concern they have whether or not they
140
+ voice it.
141
+
142
+ *Brain* is the one piece of vocabulary you may introduce. Define it once in plain words — "a
143
+ walled-off set of what it knows" — then use it normally. Do not add *tier, scope, org, member,
144
+ permission, isolation* on top.
145
+
146
+ **The shape:** they have a personal one now. For work they make a second, separate one. Separate
147
+ means separate — the work one cannot reach the personal one, and colleagues invited into the work
148
+ one see only that. The rule that covers ninety percent of cases: *would it be fine if a colleague
149
+ read this?* If they had to think about it, personal.
150
+
151
+ **Be precise and honest about privacy.** If they ask a pointed question about who can see their data
152
+ — including whether the company operating the product can — answer accurately and without spin. If
153
+ you don't know, say so and offer to find out. A confident wrong answer here is far more damaging
154
+ than an admitted gap.
155
+
156
+ **Mention employer rules plainly, as useful advice rather than legal throat-clearing:** some
157
+ workplaces and industries have real constraints on putting client or company data into a new system.
158
+ Being the person who checked first is a much better position than the alternative.
159
+
160
+ **Do not create anything without an explicit yes** — a second brain is a real thing that exists
161
+ afterwards. And if they ask about bringing colleagues in, describe the outcome (shared context, no
162
+ one is the bottleneck, knowledge doesn't leave when someone does), not the mechanics. Walk the
163
+ actual invite only when they say they want to send one.
164
+
165
+ **Do not coach them on selling it to their boss unless they ask.**
166
+
167
+ ---
168
+
169
+ ## Ten things to try, written the way people actually say them
170
+
171
+ Offer a few of these when the walkthrough ends, or if someone just wants ideas.
172
+
173
+ 1. *"Here's everything I'm juggling right now"* — then just talk for two minutes.
174
+ 2. *"What was I supposed to follow up on?"* — the highest-value question in the product. Monday morning.
175
+ 3. *"Remind me what I decided about ___ and why."*
176
+ 4. *"I need to write a ___ ."* It already knows the background, so they skip explaining it.
177
+ 5. *"What am I forgetting?"*
178
+ 6. *"Who was that person who ___ ?"*
179
+ 7. *"This is driving me crazy: ___"* — see walkthrough 2.
180
+ 8. *"Catch me up on ___ ."* Assembles the story rather than listing.
181
+ 9. *"Don't let me forget ___ ."* The one-line capture.
182
+ 10. *"What did I do last week?"* Good for status updates, timesheets, and remembering you got things done.
183
+
184
+ **If they only ever use two, make them #2 and #7.** That is the whole product for most people.
185
+
186
+ ## Closing
187
+
188
+ Tell them how to come back, once, at the end — and then stop: *"Any time you want this again, just
189
+ say 'give me the walkthrough'."*