@theronap/agnoclast-mcp 0.9.256 → 0.9.258

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.
@@ -74,16 +74,42 @@ export function renderFilingObligations(o) {
74
74
  }
75
75
  if (o.write?.undated) lines.push(`⚠ ${o.write.undated} of them has no date, so nothing will raise it by time.`)
76
76
  if (o.write?.unhomed) lines.push(`⚠ ${o.write.unhomed} could not be placed: the record is on no page.`)
77
- if (Array.isArray(o.unverified) && o.unverified.length) {
77
+ // 2026-10-23: the server reports EACH candidate. Name every one it will not write as sent, and why.
78
+ const notAsSent = renderObligationItems(o.items, { onlyNotProposed: true })
79
+ if (notAsSent.length) {
80
+ lines.push(...notAsSent)
81
+ } else if (Array.isArray(o.unverified) && o.unverified.length) {
78
82
  lines.push(
79
83
  `⚠ NOT written — not in this text word for word: ${o.unverified.map((e) => `"${e}"`).join('; ')}. ` +
80
84
  "Quote the text's own sentence (propose_obligation with this record id).",
81
85
  )
82
86
  }
83
- if (o.malformed) lines.push(`⚠ ${o.malformed} malformed obligation(s) were dropped.`)
87
+ if (o.malformed && !Array.isArray(o.items)) lines.push(`⚠ ${o.malformed} malformed obligation(s) were dropped.`)
88
+ if (o.write?.kept) lines.push(`${o.write.kept} matched an obligation the person already acted on — left as they left it, not written again.`)
84
89
  return lines
85
90
  }
86
91
 
92
+ /**
93
+ * PURE. One line per obligation the server judged (`items`, server 2026-10-23): proposed, deduped (and against
94
+ * what), or rejected (and why). `onlyNotProposed` keeps just the ones that will NOT be written as sent. Empty
95
+ * for a server that sends no `items`, so an older server never reads as "nothing dropped".
96
+ */
97
+ export function renderObligationItems(items, { onlyNotProposed = false } = {}) {
98
+ if (!Array.isArray(items) || items.length === 0) return []
99
+ const shown = onlyNotProposed ? items.filter((x) => x?.outcome !== 'proposed') : items
100
+ if (shown.length === 0) return []
101
+ const label = (x) => `obligations[${x.index}]${x.subject ? ` "${x.subject}"` : ''}`
102
+ const line = (x) => x.outcome === 'proposed'
103
+ ? ` · ${label(x)} — proposed`
104
+ : x.outcome === 'deduped'
105
+ ? ` · ${label(x)} — deduped: same sentence as ${x.against ?? 'another in this filing'} (one proposal, not two)`
106
+ : ` ⚠ ${label(x)} — REJECTED, not written: ${x.reason ?? 'refused'}`
107
+ const p = items.filter((x) => x?.outcome === 'proposed').length
108
+ const d = items.filter((x) => x?.outcome === 'deduped').length
109
+ const r = items.filter((x) => x?.outcome === 'rejected').length
110
+ return [`Obligations sent: ${items.length} — ${p} proposed, ${d} deduped, ${r} rejected:`, ...shown.map(line)]
111
+ }
112
+
87
113
  /** PURE. The same, folded onto one line for a batch's per-unit detail. */
88
114
  export function filingObligationsBrief(o) {
89
115
  if (!o || typeof o !== 'object') return ''
@@ -91,6 +117,15 @@ export function filingObligationsBrief(o) {
91
117
  const bits = []
92
118
  if (o.sent === 0) bits.push('asks nothing')
93
119
  else if (o.write?.proposed) bits.push(`${o.write.proposed} obligation(s) proposed`)
94
- if (Array.isArray(o.unverified) && o.unverified.length) bits.push(`⚠ ${o.unverified.length} quote(s) not in the text, not written`)
120
+ if (Array.isArray(o.items)) {
121
+ // 2026-10-23: name each one not written as sent, so a batch never hides which was dropped.
122
+ for (const x of o.items) {
123
+ if (x?.outcome === 'deduped') bits.push(`obligations[${x.index}] deduped (same sentence as ${x.against ?? 'another'})`)
124
+ if (x?.outcome === 'rejected') bits.push(`⚠ obligations[${x.index}] rejected: ${x.reason ?? 'refused'}`)
125
+ }
126
+ } else if (Array.isArray(o.unverified) && o.unverified.length) {
127
+ bits.push(`⚠ ${o.unverified.length} quote(s) not in the text, not written`)
128
+ }
129
+ if (o.write?.kept) bits.push(`${o.write.kept} already acted on, left as is`)
95
130
  return bits.length ? ` · ${bits.join(', ')}` : ''
96
131
  }
@@ -29,13 +29,16 @@ export function renderFolderQuestions(body) {
29
29
  if (open.length) {
30
30
  lines.push('', `Open (${open.length}):`)
31
31
  for (const x of open) {
32
- const what = x.kind === 'file' ? `file in grab-bag \`${x.grabBag ?? '?'}\`` : `folder (${x.files?.length ?? 0} changed file${x.files?.length === 1 ? '' : 's'})`
32
+ const what = `folder (${x.files?.length ?? 0} changed file${x.files?.length === 1 ? '' : 's'})`
33
33
  lines.push(`- \`${x.identifier}\` — ${what}`)
34
34
  if (Array.isArray(x.answerWith) && x.answerWith.length > 1) {
35
35
  lines.push(` may also be answered at: ${x.answerWith.slice(1).map((a) => `\`${a}\``).join(', ')}`)
36
36
  }
37
37
  if (x.earlier) {
38
- lines.push(` asked before: declined ${x.earlier.kind} on \`${x.earlier.identifier}\`${x.earlier.decidedBy ? ` by ${x.earlier.decidedBy}` : ''} ${String(x.earlier.decidedAt).slice(0, 10)} — "${x.earlier.reason ?? ''}". That decision has lapsed.`)
38
+ const why = x.earlier.kind === 'not_yet'
39
+ ? '"not yet" is no longer an answer for a GitHub folder — choose its page, or never.'
40
+ : 'That decision has lapsed.'
41
+ lines.push(` asked before: declined ${x.earlier.kind} on \`${x.earlier.identifier}\`${x.earlier.decidedBy ? ` by ${x.earlier.decidedBy}` : ''} ${String(x.earlier.decidedAt).slice(0, 10)} — "${x.earlier.reason ?? ''}". ${why}`)
39
42
  }
40
43
  }
41
44
  }
@@ -44,11 +47,10 @@ export function renderFolderQuestions(body) {
44
47
  for (const p of pages) lines.push(`- ${q(p.title)} (${p.tier}) — ref ${p.ref}${p.tier !== 'accessible' ? ' — needs confirmTier: true' : ''}`)
45
48
  }
46
49
  if (open.length) {
47
- const example = open.slice(0, 3).map((x) => (pages[0]
48
- ? { identifier: x.identifier, claim: pages[0].ref }
49
- : { identifier: x.identifier, decline: 'not_yet', reason: 'why it belongs to no page' }))
50
+ // Never a `never` in the example: it is permanent, so it must be typed on purpose, not copied.
51
+ const example = open.slice(0, 3).map((x) => ({ identifier: x.identifier, claim: pages[0]?.ref ?? '<ref of the page about this folder>' }))
50
52
  lines.push('', `Answer shape: folderDecisions: ${JSON.stringify(example)}`)
51
- lines.push('No page fits? Author a small area page about the folder (`author`), then claim onto its ref. Generated/vendored/lockfiles/fixtures: decline "never".')
53
+ lines.push('No page fits? Author a small area page about the folder (`author`), then claim onto its ref. Belongs to no page (generated/vendored/lockfiles/fixtures)? {identifier, decline: "never", reason} — permanent until someone claims it. There is no "not yet".')
52
54
  if (body?.cold) lines.push('You are a cold judge here: {identifier, defer: true} hands a question to the next session that touches it.')
53
55
  }
54
56
  return lines.join('\n')
@@ -73,7 +75,10 @@ export function renderDeclines(declines, { heading = 'Declines' } = {}) {
73
75
  if (!list.length) return []
74
76
  const lines = [`${heading} (${list.length}) — a decline covers the identifier and everything under it:`]
75
77
  for (const d of list) {
76
- const state = d.active ? (d.kind === 'never' ? 'never' : d.kind === 'not_yet' ? 'not yet' : 'snoozed') : `${d.kind}, lapsed — will be asked again`
78
+ const retiredPath = d.kind === 'not_yet' && String(d.identifier).startsWith('file:')
79
+ const state = retiredPath
80
+ ? 'not yet — retired for GitHub paths, asked again at the next commit there'
81
+ : d.active ? (d.kind === 'never' ? 'never' : d.kind === 'not_yet' ? 'not yet' : 'snoozed') : `${d.kind}, lapsed — will be asked again`
77
82
  lines.push(` ${d.active ? '⊘' : '·'} \`${d.identifier}\`${d.brain ? ` · ${d.brain}` : ''} — ${state}${d.reason ? ` — "${d.reason}"` : ''}${d.decidedBy ? ` · ${d.decidedBy}` : ''}${d.cold ? ' (cold judge)' : ''} · ${String(d.decidedAt).slice(0, 10)}`)
78
83
  }
79
84
  return lines
@@ -24,3 +24,15 @@ export function renderConversationOffer(x, why = '', looked = '') {
24
24
  return ` - 📱 ${x.conversation.count} texts in one conversation · newest ${x.hoursAgo}h ago · ${ids}${why}${looked}\n` +
25
25
  ' decide them together: `intake_look` the newest, `intake_claim` them all, then file them in one `intake_materialize` (intakeItemIds) — one record each'
26
26
  }
27
+
28
+ /**
29
+ * A batch PREVIEW (2026-10-23), per id: until then a preview with intakeItemIds covered only the first id, so
30
+ * the other units' obligations were never checked and nothing said so. Each unit is previewed on its own call
31
+ * with its own obligations; `line` is that unit's rendered preview. Nothing is written by any of them.
32
+ */
33
+ export function renderPreviewBatch(results) {
34
+ const refuse = results.filter((r) => !r.ok || r.wouldApply === false)
35
+ return `PREVIEW of a batch of ${results.length} — nothing was written. ` +
36
+ `${results.length - refuse.length} would file as sent, ${refuse.length} would not.\n` +
37
+ results.map((r) => `\n── ${r.id}\n${r.ok ? r.line : `✗ preview failed — ${r.detail}`}`).join('\n')
38
+ }
@@ -22,7 +22,7 @@ export const REGULAR_ONLY_TOOLS = [
22
22
  'list_records', 'read_record', 'timeline_pull', 'record_status', 'set_record_notes', 'set_record_privacy',
23
23
  'relabel_record_people', 'supersede_record', 'move_record', 'contain_record', 'pending_records', 'claim_record',
24
24
  'route_record', 'unroute_record', 'request_file', 'get_file', 'create_custom_source', 'search_spam_email',
25
- 'gmail_modify', 'draft_email_reply', 'request_send_email', 'send_imessage', 'create_brain', 'add_to_brain',
25
+ 'gmail_modify', 'draft_email_reply', 'request_send_email', 'send_imessage', 'add_to_brain',
26
26
  ]
27
27
 
28
28
  /** What a locked tool answers when the check itself could not be made. Nothing was run. */
package/lib/server.mjs CHANGED
@@ -24,9 +24,9 @@ import { spawnObligationWorker } from './obligations_worker.mjs'
24
24
  import { renderObligations, renderPageObligations, tierReads, who } from './obligations_render.mjs'
25
25
  import { renderLeaveQuestion, renderLeft, renderRemovalQuestion, renderRemoved } from './member_remove_render.mjs'
26
26
  import { renderSettings } from './settings_render.mjs'
27
- import { renderConversationOffer, renderMaterializeBatch } from './intake_batch_render.mjs'
27
+ import { renderConversationOffer, renderMaterializeBatch, renderPreviewBatch } from './intake_batch_render.mjs'
28
28
  import { intakeIdFrom, requireIntakeId, INTAKE_ID_ALIAS_NOTE } from './intake_id_alias.mjs'
29
- import { obligationsForUnits, renderFilingObligations, filingObligationsBrief, OBLIGATIONS_UNANSWERED_TEXT } from './filing_obligations.mjs'
29
+ import { obligationsForUnits, renderFilingObligations, renderObligationItems, filingObligationsBrief, OBLIGATIONS_UNANSWERED_TEXT } from './filing_obligations.mjs'
30
30
  import { renderRecord, READ_RECORD_DEFAULT_CHARS, READ_RECORD_PARTS } from './record_render.mjs'
31
31
  import { formatCreated, formatDryRun, formatSources, homeFromArgs, pickSource, renderHomeProblem } from './custom_sources.mjs'
32
32
  import { formatSeriesRefusal, formatSeriesUpdated } from './series_render.mjs'
@@ -183,7 +183,26 @@ export function renderIntakePreviewLine(body) {
183
183
  const pages = Array.isArray(p.pages) ? p.pages.length : 0
184
184
  const g = p.governing
185
185
  const gov = g ? `${g.title || g.kind} (${g.tier})${p.governingJudged ? ', your choice' : ''}` : 'none'
186
- return `PREVIEW — nothing was written. Would attach ${pages} page(s); governing: ${gov}; born ${p.tier ?? 'unknown'}.`
186
+ const lines = [`PREVIEW — nothing was written. Would attach ${pages} page(s); governing: ${gov}; born ${p.tier ?? 'unknown'}.`]
187
+ // 2026-10-23: whether apply would take THIS body, and what it would do with each answer sent. Every line
188
+ // below is printed only when the server sent its field, so an older server renders exactly as before.
189
+ if (body?.wouldApply === false && Array.isArray(body.applyWouldRefuse)) {
190
+ for (const r of body.applyWouldRefuse) lines.push(`⚠ apply would REFUSE this call: ${r?.error}${r?.detail ? ` — ${r.detail}` : ''}`)
191
+ } else if (body?.wouldApply === true) {
192
+ lines.push('Apply would accept this call as sent.')
193
+ }
194
+ const fd = body?.folderDecisions
195
+ if (fd && Array.isArray(fd.answers)) {
196
+ const ok = fd.answers.filter((a) => a.verdict === 'accepted')
197
+ const bad = fd.answers.filter((a) => a.verdict === 'rejected')
198
+ const open = Array.isArray(fd.open) ? fd.open : []
199
+ lines.push(`Folder answers: ${ok.length} accepted, ${bad.length} rejected; ${open.length} question(s) still open.`)
200
+ for (const a of ok) lines.push(` ✓ folderDecisions[${a.index}] ${a.action} \`${a.identifier}\`${a.page ? ` onto "${a.page.title}" (${a.page.tier})` : ''} — answers ${(a.answers ?? []).length}`)
201
+ for (const a of bad) lines.push(` ✗ folderDecisions[${a.index}] ${a.action ?? '?'} \`${a.identifier ?? '?'}\` — ${a.error}${a.detail ? `: ${a.detail}` : ''}`)
202
+ for (const q of open) lines.push(` ? still open: \`${q.identifier}\``)
203
+ }
204
+ lines.push(...renderObligationItems(body?.obligations?.items))
205
+ return lines.join('\n')
187
206
  }
188
207
 
189
208
  // TOP-OF-PAGE NOTES — ADR-0065's size note, and ADR-0064 §3's "Also visible to you" callout.
@@ -1304,12 +1323,12 @@ function renderNudge(payload) {
1304
1323
  {
1305
1324
  title: 'Materialize a private intake item',
1306
1325
  description:
1307
- 'Atomically publish a claimed intake item into one brain — the disclosure moment (ADR-0062). Deterministic identifier homes in that brain are always attached; documentIds may add further pages. The record is born at the strictest of its source baseline, what the producer asked for, and every attached page; a source floor (iMessage, claude-code) pins it. ' +
1308
- 'Name the governing page with governingPage (one of documentIds, never a person page) — otherwise the ranking picks it. ' +
1309
- 'Pass mode "preview" first when your choice differs from the claim\'s proposal: it returns the pages, the governing page and the tier this exact call would produce, and writes nothing. Never writes private intake into search/history before this call. ' +
1310
- '⚠ ADR-0051 — BEFORE you file, look for this unit\'s OTHER homes: the pages it is about beyond what an identifier matched (the design it implements, the project, the person, the decision it bears on). grep or read for them, then name them in documentIds. A call that names nothing beyond the deterministic homes still FILES, on those homes only, and says so — add pages later with route_record. ' +
1311
- '⚠ A meeting or lecture TRANSCRIPT files with NOTES in `summary` (Theron, 2026-10-08): the gist, decisions, action items and dates, names spelled right. Without them the call is REFUSED; the transcript itself is kept on the record unchanged. ' +
1312
- '⚠ ADR-0077: a GitHub commit or PR does not finish until every changed path has an answer. If a changed file\'s deepest folder (or, in a grab-bag folder like web/lib/engine/ or db/supabase/, the file itself) has no claim and no decline, the call is REFUSED with the open questions; answer each in folderDecisions and call again.',
1326
+ 'Atomically publish a claimed intake item into one brain — the disclosure moment (ADR-0062). Deterministic identifier homes there are always attached; documentIds add pages. The record is born at the strictest of its source baseline, the producer\'s ask and every attached page; a source floor (iMessage, claude-code) pins it. ' +
1327
+ 'Name the governing page with governingPage (one of documentIds, never a person page), else the ranking picks it. ' +
1328
+ 'Pass mode "preview" when your choice differs from the proposal: it returns the pages, governing page and tier this exact call would produce, whether apply would accept it (folderDecisions and obligations included), and writes nothing. Never writes private intake into search/history before this call. ' +
1329
+ '⚠ ADR-0051 — BEFORE you file, look for this unit\'s OTHER homes: the pages it is about beyond what an identifier matched (the design, project, person or decision). grep or read for them and name them in documentIds. A call naming nothing beyond the deterministic homes still FILES on those only, and says so — add pages later with route_record. ' +
1330
+ '⚠ A meeting or lecture TRANSCRIPT files with NOTES in `summary`: gist, decisions, action items, dates, names spelled right — else REFUSED; the transcript is kept unchanged. ' +
1331
+ '⚠ ADR-0077: a GitHub commit or PR does not finish until every changed AREA FOLDER has an answer; unanswered, the call is REFUSED with the open questions. Answer each in folderDecisions (a page to claim it onto, or decline "never") and call again. A "never" on a folder also silences every unclaimed subfolder beneath it, so use it only when nothing under that folder deserves its own page. An earlier "not_yet" (`earlier`) no longer answers a GitHub folder question: claim the folder onto a page about it, or decline "never". A file directly in a grab-bag folder (e.g. db/supabase/) is never asked about.',
1313
1332
  inputSchema: {
1314
1333
  intakeItemId: z.string().optional().describe('intake item uuid (required — intake_item_id is accepted too)'),
1315
1334
  intake_item_id: z.string().optional().describe(INTAKE_ID_ALIAS_NOTE),
@@ -1318,7 +1337,7 @@ function renderNudge(payload) {
1318
1337
  'Any units you hold a live claim on may be batched, even from separate intake_claim calls — except that texts from ' +
1319
1338
  'DIFFERENT text conversations never share a batch. Claim them with `intake_claim`, then pass ' +
1320
1339
  'the first as intakeItemId and the rest here. title/summary/dedupeKey are ignored in a batch (each unit keeps its ' +
1321
- 'own), and a preview covers intakeItemId only. Each id is reported separately; a failure never hides behind a success.',
1340
+ 'own), and a preview covers every id, each with its own obligations. Each id is reported separately; a failure never hides behind a success.',
1322
1341
  ),
1323
1342
  orgId: z.string().describe('destination brain org uuid'),
1324
1343
  documentIds: z.array(z.string()).optional().describe('additional pages in orgId — each a page TITLE exactly as read_page shows it, its `ref:`, or a brain_documents id (deterministic homes are merged automatically). A title that matches no page, or more than one, is refused with the candidates listed'),
@@ -1347,12 +1366,12 @@ function renderNudge(payload) {
1347
1366
  folderDecisions: z.array(z.object({
1348
1367
  identifier: z.string().describe('the open question\'s identifier, or a folder above it that its answerWith lists'),
1349
1368
  claim: z.string().optional().describe('page `ref:` (or document id) of a project page ABOUT this path — never the repo\'s mother page, a person or a user page'),
1350
- decline: z.enum(['never', 'not_yet']).optional().describe('never = generated/build output, lockfiles, fixtures, vendored code; not_yet = asked again after 10 more commits or 90 days'),
1369
+ decline: z.enum(['never']).optional().describe('never = belongs to no page (generated/build output, lockfiles, fixtures, vendored code). Permanent until someone claims the folder onto a page. There is no "not yet" for a GitHub folder: pick the page it is about, or never'),
1351
1370
  reason: z.string().optional().describe('required with decline: why it belongs to no page — the next session asked will read it'),
1352
1371
  defer: z.boolean().optional().describe('cold judges only (a cleanup lease): hand the question to the next session that touches it'),
1353
1372
  confirmTier: z.boolean().optional().describe('required to claim onto a page narrower than accessible — its tier will govern every future commit there'),
1354
1373
  })).optional().describe(
1355
- 'ADR-0077: one answer per open folder question from a refused call — exactly one of claim, decline or defer each. ' +
1374
+ 'ADR-0077: one answer per open folder question from a refused call — exactly one of claim, decline ("never") or defer (cold judges only) each. ' +
1356
1375
  'Send only after the server has listed the questions; claims and declines are written when the unit is filed.',
1357
1376
  ),
1358
1377
  obligations: z.array(z.object({
@@ -1382,6 +1401,43 @@ function renderNudge(payload) {
1382
1401
  const ob = obligationsForUnits(batch, args.obligations)
1383
1402
  if (ob.errors.length) return toolError(`Nothing was filed — fix these obligations first:\n${ob.errors.map((e) => ` · ${e}`).join('\n')}`)
1384
1403
  const obligationsFor = (id) => (ob.byUnit ? { obligations: ob.byUnit.get(id) ?? [] } : {})
1404
+ // A BATCH PREVIEW (2026-10-23): every id, each on its own preview call with ITS OWN obligations — the
1405
+ // same body the batch apply below sends, so what it reports is what apply would do. Until then a preview
1406
+ // covered intakeItemId only, and the other units' obligations were silently never checked.
1407
+ if (batch.length > 1 && args.mode === 'preview') {
1408
+ const results = []
1409
+ for (const id of batch) {
1410
+ try {
1411
+ const r = await fetchCortex(`${BASE}${intakeMaterializePath('preview')}`, {
1412
+ method: 'POST',
1413
+ headers: { Authorization: `Bearer ${TOKEN}`, 'Content-Type': 'application/json' },
1414
+ body: JSON.stringify({
1415
+ intakeItemId: id,
1416
+ orgId: args.orgId,
1417
+ documentIds: args.documentIds ?? [],
1418
+ record: { source: args.source, record_type: args.recordType },
1419
+ attachmentMeta: (args.documentIds ?? []).map(() => ({ origin: args.origin ?? 'llm' })),
1420
+ ...(args.governingPage ? { governingDocumentId: args.governingPage } : {}),
1421
+ ...(args.searchedForOtherHomes ? { searchedForOtherHomes: true } : {}),
1422
+ sameBatchAs: batch[0],
1423
+ ...obligationsFor(id),
1424
+ }),
1425
+ })
1426
+ const o = await r.json().catch(() => null)
1427
+ if (r.status === 404 && !o?.error) {
1428
+ results.push({ id, ok: false, detail: 'this server cannot preview a materialization yet — nothing was written' })
1429
+ } else if (!r.ok) {
1430
+ results.push({ id, ok: false, detail: `${o?.error ?? `HTTP ${r.status}`}${o?.hint ? ` — ${o.hint}` : o?.detail ? ` — ${o.detail}` : ''}` })
1431
+ } else {
1432
+ results.push({ id, ok: true, wouldApply: o?.wouldApply, line: renderIntakePreviewLine(o), body: o })
1433
+ }
1434
+ } catch (e) {
1435
+ results.push({ id, ok: false, detail: e.message })
1436
+ }
1437
+ }
1438
+ const notes = ob.notes.length ? `\n${ob.notes.map((n) => `⚠ ${n}`).join('\n')}` : ''
1439
+ return { content: [{ type: 'text', text: `${renderPreviewBatch(results)}${notes}\n\n${JSON.stringify(results.map((r) => r.body ?? { id: r.id, error: r.detail }), null, 2)}` }] }
1440
+ }
1385
1441
  if (batch.length > 1 && args.mode !== 'preview') {
1386
1442
  const results = []
1387
1443
  for (const id of batch) {
@@ -2714,7 +2770,7 @@ function renderNudge(payload) {
2714
2770
  brain: z.string().optional().describe('which brain to stop surfacing it in. Required when you belong to more than one — the call refuses rather than picking.'),
2715
2771
  days: z.number().optional().describe('how long to stay quiet (1-365, default 90).'),
2716
2772
  reason: z.string().optional().describe('why it belongs nowhere, in one phrase — the next person to see it resurface will want this. REQUIRED with kind.'),
2717
- kind: z.enum(['never', 'not_yet']).optional().describe('ADR-0077: record a DECLINE instead of silencing the notice. never = generated/build output, lockfiles, fixtures, vendored code (a cold judge — a session holding cleanup leases — may not write it); not_yet = asked again after 10 more commits touch it or 90 days. A decline covers the identifier and everything under it, and claiming the identifier clears it.'),
2773
+ kind: z.enum(['never', 'not_yet']).optional().describe('ADR-0077: record a DECLINE instead of silencing the notice. never = generated/build output, lockfiles, fixtures, vendored code (a cold judge — a session holding cleanup leases — may not write it); not_yet = asked again after 10 more commits touch it or 90 days — NOT for a GitHub path (file:…), which takes a page or never (2026-10-22). A decline covers the identifier and everything under it, and claiming the identifier clears it.'),
2718
2774
  remove: z.boolean().optional().describe('ADR-0077: UN-decline — delete the decline (or snooze) on this identifier, so the next commit that touches it is asked again.'),
2719
2775
  },
2720
2776
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theronap/agnoclast-mcp",
3
- "version": "0.9.256",
3
+ "version": "0.9.258",
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": {