@theronap/cortex-mcp 0.9.92 → 0.9.93

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.
Files changed (2) hide show
  1. package/lib/server.mjs +86 -0
  2. package/package.json +1 -1
package/lib/server.mjs CHANGED
@@ -461,6 +461,36 @@ export async function runServer(version) {
461
461
  },
462
462
  )
463
463
 
464
+ server.registerTool(
465
+ 'gate2_status',
466
+ {
467
+ title: 'Gate 2 edit-accountability monitor',
468
+ description: 'Read the aggregate-only Gate 2 status for this brain — whether every edit records WHICH SESSION made it. It counts only the REPAIRED write paths (absorb, retier, replace) since the 2026-08-17 fix, because the author path was never broken and would certify a repair it never exercised. Operator and migration writes are excluded: they legitimately have no session, and imputing one would violate the 0093 don\'t-impute rule. It never exposes page titles, refs, reasons or session keys. "regressed" means a repaired path lost its session attribution again and the gate must NOT be closed; "machine_evidence_ready" means enough ordinary-work evidence has accumulated to request a human closure decision.',
469
+ inputSchema: {
470
+ days: z.number().optional().describe('rolling window in days (1-90, default 14)'),
471
+ brain: z.string().optional().describe('brain name or org id when you belong to more than one brain; omit for a sole brain'),
472
+ },
473
+ },
474
+ async ({ days, brain }) => {
475
+ const qs = new URLSearchParams()
476
+ if (days != null) qs.set('days', String(days))
477
+ if (brain) qs.set('brain', brain)
478
+ const suffix = qs.size ? `?${qs}` : ''
479
+ const res = await fetchCortex(`${BASE}/api/gates/2/status${suffix}`, { headers: { Authorization: `Bearer ${TOKEN}` } })
480
+ if (!res.ok) {
481
+ const body = await res.text()
482
+ throw new Error(classify(res.status, res.headers.get('content-type'), body, res.headers.get('x-vercel-id')).message)
483
+ }
484
+ const status = await res.json()
485
+ const text = status.status === 'regressed'
486
+ ? `\u26a0 Gate 2 has REGRESSED: ${status.unattributedMcpRevisions} MCP-authored revisions since ${status.since} carry NO session (${status.unattributedRepairedRevisions} of them on the repaired absorb/retier/replace paths). Attribution is being dropped again — do not close this gate; find the writer.`
487
+ : status.machineEvidenceReady
488
+ ? `Gate 2 machine evidence is ready: ${status.repairedPathSessions} distinct sessions exercised the repaired write paths across ${status.repairedPathRevisions} revisions since ${status.since}, and NONE lost its session. A human should confirm these were ordinary work before closing the gate.`
489
+ : `Gate 2 is still collecting evidence: ${status.repairedPathSessions}/${status.requiredRepairedPathSessions} distinct sessions have exercised the repaired write paths (absorb/retier/replace) since ${status.since}, across ${status.repairedPathRevisions} revisions, 0 unattributed. No gate decision has been made.`
490
+ return { content: [{ type: 'text', text }] }
491
+ },
492
+ )
493
+
464
494
  server.registerTool(
465
495
  'session_context',
466
496
  {
@@ -2224,6 +2254,62 @@ export async function runServer(version) {
2224
2254
  },
2225
2255
  )
2226
2256
 
2257
+ server.registerTool(
2258
+ 'set_governing_page',
2259
+ {
2260
+ title: 'Choose which attached page sets a record\'s tier',
2261
+ description:
2262
+ 'Move a record\'s GOVERNING page — the one attached page whose tier the record takes. A record can sit on several pages, but exactly one of them decides how visible it is; the others confer access without authority (ADR-0027). Use this when a record is on the right pages but the WRONG one is deciding its tier — most often a record governed by your own user node when it plainly belongs to a project. The page must already be attached: run route_record first if it is not, because attaching is a relevance judgement and this is not. It applies immediately in BOTH directions, tightening or widening, because you asking for it IS the human confirmation a widening requires — so read the tier you are moving to before you move. Every move is recorded as a session judgment and is the signal the placement heuristics are calibrated against, which is why the reason matters.',
2263
+ inputSchema: {
2264
+ recordId: z.string().describe('record id (from pending_records or my_records)'),
2265
+ documentId: z.string().describe('document id of the ATTACHED page that should govern the tier'),
2266
+ reason: z.string().describe('why this page should set the tier — recorded, and read as calibration signal'),
2267
+ },
2268
+ },
2269
+ async ({ recordId, documentId, reason }) => {
2270
+ let res
2271
+ try {
2272
+ res = await fetchCortex(`${BASE}/api/brain/triage`, {
2273
+ method: 'POST',
2274
+ headers: { Authorization: `Bearer ${TOKEN}`, 'Content-Type': 'application/json' },
2275
+ body: JSON.stringify({ action: 'regovern', recordId, documentId, reason }),
2276
+ })
2277
+ } catch (e) {
2278
+ return toolError(`Could not set the governing page: ${e.message}`)
2279
+ }
2280
+ const out = await res.json().catch(() => null)
2281
+ if (!res.ok) {
2282
+ // A guard rail, not a fault — say what to do instead of naming the code.
2283
+ if (out?.error === 'not_attached') {
2284
+ return toolError(
2285
+ 'That page is not attached to this record, so it cannot govern it. Attach it first with route_record.',
2286
+ )
2287
+ }
2288
+ return toolError(
2289
+ `Could not set the governing page: ${out?.error ?? res.status}${out?.detail ? ` — ${out.detail}` : ''}`,
2290
+ )
2291
+ }
2292
+ if (out?.reaffirmed) {
2293
+ return {
2294
+ content: [{
2295
+ type: 'text',
2296
+ text: `That page already governed this record; recorded your confirmation. Tier: ${out?.toPrivacy ?? 'unchanged'}.`,
2297
+ }],
2298
+ }
2299
+ }
2300
+ const moved =
2301
+ out?.fromPrivacy && out?.toPrivacy && out.fromPrivacy !== out.toPrivacy
2302
+ ? `Tier ${out.fromPrivacy} -> ${out.toPrivacy} (${out?.direction}).`
2303
+ : `Tier unchanged (${out?.toPrivacy ?? 'unknown'}).`
2304
+ return {
2305
+ content: [{
2306
+ type: 'text',
2307
+ text: `Governing page moved. ${moved} Recorded as a session judgment${out?.correctedAuto ? ' and counted as a correction to the placement heuristics' : ''}.`,
2308
+ }],
2309
+ }
2310
+ },
2311
+ )
2312
+
2227
2313
  server.registerTool(
2228
2314
  'snooze_red_link',
2229
2315
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theronap/cortex-mcp",
3
- "version": "0.9.92",
3
+ "version": "0.9.93",
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": {