@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.
- package/lib/filing_obligations.mjs +38 -3
- package/lib/folder_decisions.mjs +12 -7
- package/lib/intake_batch_render.mjs +12 -0
- package/lib/locked_tools.mjs +1 -1
- package/lib/server.mjs +69 -13
- package/package.json +1 -1
|
@@ -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
|
-
|
|
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.
|
|
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
|
}
|
package/lib/folder_decisions.mjs
CHANGED
|
@@ -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 =
|
|
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
|
-
|
|
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
|
-
|
|
48
|
-
|
|
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.
|
|
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
|
|
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
|
+
}
|
package/lib/locked_tools.mjs
CHANGED
|
@@ -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', '
|
|
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
|
-
|
|
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
|
|
1308
|
-
'Name the governing page with governingPage (one of documentIds, never a person page)
|
|
1309
|
-
'Pass mode "preview"
|
|
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
|
|
1311
|
-
'⚠ A meeting or lecture TRANSCRIPT files with NOTES in `summary
|
|
1312
|
-
'⚠ ADR-0077: a GitHub commit or PR does not finish until every changed
|
|
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
|
|
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'
|
|
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