@isaacriehm/cairn-core 0.6.0 → 0.7.0
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/dist/.tsbuildinfo +1 -1
- package/dist/attention/bulk-accept.js +56 -15
- package/dist/attention/bulk-accept.js.map +1 -1
- package/dist/attention/serve/api.js +7 -1
- package/dist/attention/serve/api.js.map +1 -1
- package/dist/ground/file-candidates-map.d.ts +23 -0
- package/dist/ground/file-candidates-map.js +76 -0
- package/dist/ground/file-candidates-map.js.map +1 -0
- package/dist/ground/index.d.ts +4 -2
- package/dist/ground/index.js +4 -2
- package/dist/ground/index.js.map +1 -1
- package/dist/ground/paths.d.ts +2 -0
- package/dist/ground/paths.js +6 -0
- package/dist/ground/paths.js.map +1 -1
- package/dist/ground/rejected.d.ts +42 -0
- package/dist/ground/rejected.js +100 -0
- package/dist/ground/rejected.js.map +1 -0
- package/dist/ground/schemas.d.ts +80 -0
- package/dist/ground/schemas.js +54 -0
- package/dist/ground/schemas.js.map +1 -1
- package/dist/hooks/post-tool-use/index.d.ts +1 -1
- package/dist/hooks/post-tool-use/index.js +1 -1
- package/dist/hooks/post-tool-use/index.js.map +1 -1
- package/dist/hooks/post-tool-use/ledger-cache.d.ts +13 -0
- package/dist/hooks/post-tool-use/ledger-cache.js +48 -0
- package/dist/hooks/post-tool-use/ledger-cache.js.map +1 -1
- package/dist/hooks/post-tool-use/legend-builder.d.ts +10 -1
- package/dist/hooks/post-tool-use/legend-builder.js +27 -2
- package/dist/hooks/post-tool-use/legend-builder.js.map +1 -1
- package/dist/hooks/post-tool-use/read-enricher.js +8 -2
- package/dist/hooks/post-tool-use/read-enricher.js.map +1 -1
- package/dist/init/index.d.ts +2 -2
- package/dist/init/index.js +1 -1
- package/dist/init/index.js.map +1 -1
- package/dist/init/ingest-docs.d.ts +82 -22
- package/dist/init/ingest-docs.js +632 -108
- package/dist/init/ingest-docs.js.map +1 -1
- package/dist/init/init.d.ts +10 -1
- package/dist/init/init.js +113 -251
- package/dist/init/init.js.map +1 -1
- package/dist/init/mapper-parallel.js +8 -0
- package/dist/init/mapper-parallel.js.map +1 -1
- package/dist/init/phases/6-docs-ingest.d.ts +9 -4
- package/dist/init/phases/6-docs-ingest.js +13 -10
- package/dist/init/phases/6-docs-ingest.js.map +1 -1
- package/dist/init/phases/parallel-678.js +10 -4
- package/dist/init/phases/parallel-678.js.map +1 -1
- package/dist/init/sot-emit.d.ts +22 -0
- package/dist/init/sot-emit.js +50 -4
- package/dist/init/sot-emit.js.map +1 -1
- package/dist/init/source-comments/ingest.js +107 -7
- package/dist/init/source-comments/ingest.js.map +1 -1
- package/dist/init/topic-index/index.d.ts +14 -0
- package/dist/init/topic-index/index.js +83 -4
- package/dist/init/topic-index/index.js.map +1 -1
- package/dist/init/topic-index/judge.js +14 -1
- package/dist/init/topic-index/judge.js.map +1 -1
- package/dist/init/topic-index/resolve.d.ts +19 -0
- package/dist/init/topic-index/resolve.js +100 -14
- package/dist/init/topic-index/resolve.js.map +1 -1
- package/dist/init/topic-index/walk.d.ts +32 -0
- package/dist/init/topic-index/walk.js +70 -4
- package/dist/init/topic-index/walk.js.map +1 -1
- package/dist/mcp/history/summarizer.js +5 -0
- package/dist/mcp/history/summarizer.js.map +1 -1
- package/dist/mcp/schemas.d.ts +48 -0
- package/dist/mcp/schemas.js +43 -0
- package/dist/mcp/schemas.js.map +1 -1
- package/dist/mcp/tools/index.js +8 -0
- package/dist/mcp/tools/index.js.map +1 -1
- package/dist/mcp/tools/propose-decision.d.ts +34 -0
- package/dist/mcp/tools/propose-decision.js +200 -0
- package/dist/mcp/tools/propose-decision.js.map +1 -0
- package/dist/mcp/tools/reject-candidate.d.ts +24 -0
- package/dist/mcp/tools/reject-candidate.js +71 -0
- package/dist/mcp/tools/reject-candidate.js.map +1 -0
- package/dist/mcp/tools/search-candidates.d.ts +20 -0
- package/dist/mcp/tools/search-candidates.js +93 -0
- package/dist/mcp/tools/search-candidates.js.map +1 -0
- package/package.json +1 -1
- package/templates/attention-ui/app.js +40 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/mcp/tools/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AACrE,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EACL,mBAAmB,EACnB,cAAc,EACd,cAAc,GACf,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE7C,MAAM,CAAC,MAAM,QAAQ,GAAqB;IACxC,yBAAyB;IACzB,eAAe;IACf,oBAAoB;IACpB,sBAAsB;IACtB,qBAAqB;IACrB,aAAa;IACb,mBAAmB;IACnB,gBAAgB;IAChB,qBAAqB;IACrB,6BAA6B;IAC7B,UAAU;IACV,YAAY;IACZ,WAAW;IACX,4BAA4B;IAC5B,gBAAgB;IAChB,QAAQ;IACR,kBAAkB;IAClB,cAAc;IACd,WAAW;IACX,qBAAqB;IACrB,oBAAoB;IACpB,uBAAuB;IACvB,kBAAkB;IAClB,oBAAoB;IACpB,kBAAkB;IAClB,iBAAiB;IACjB,qCAAqC;IACrC,cAAc;IACd,iDAAiD;IACjD,cAAc;IACd,mBAAmB;IACnB,GAAG,cAAc;CAClB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/mcp/tools/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AACrE,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EACL,mBAAmB,EACnB,cAAc,EACd,cAAc,GACf,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE7C,MAAM,CAAC,MAAM,QAAQ,GAAqB;IACxC,yBAAyB;IACzB,eAAe;IACf,oBAAoB;IACpB,sBAAsB;IACtB,qBAAqB;IACrB,aAAa;IACb,mBAAmB;IACnB,gBAAgB;IAChB,qBAAqB;IACrB,6BAA6B;IAC7B,UAAU;IACV,YAAY;IACZ,WAAW;IACX,4BAA4B;IAC5B,gBAAgB;IAChB,kEAAkE;IAClE,oBAAoB;IACpB,QAAQ;IACR,kBAAkB;IAClB,cAAc;IACd,WAAW;IACX,mEAAmE;IACnE,mBAAmB;IACnB,mBAAmB;IACnB,qBAAqB;IACrB,oBAAoB;IACpB,uBAAuB;IACvB,kBAAkB;IAClB,oBAAoB;IACpB,kBAAkB;IAClB,iBAAiB;IACjB,qCAAqC;IACrC,cAAc;IACd,iDAAiD;IACjD,cAAc;IACd,mBAAmB;IACnB,GAAG,cAAc;CAClB,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cairn_propose_decision` — promote a topic-index candidate (slug)
|
|
3
|
+
* into a DEC draft under `.cairn/ground/decisions/_inbox/`.
|
|
4
|
+
*
|
|
5
|
+
* Spec: PHASE_6_REDESIGN §4.6 / §5.4. The AI-curator path. Body is
|
|
6
|
+
* ALWAYS verbatim from `readSotBody` so the resulting draft's
|
|
7
|
+
* `sot_content_hash` matches the source paragraph the agent saw — no
|
|
8
|
+
* paraphrasing, no AI-generated prose. The drift sensor depends on
|
|
9
|
+
* this hash.
|
|
10
|
+
*
|
|
11
|
+
* Refusal modes (returned as `{ ok: false, reason: <kind> }`):
|
|
12
|
+
* - `not_found` — slug not present in the topic-index.
|
|
13
|
+
* - `rejected` — slug appears in `.cairn/ground/_rejected.yaml`.
|
|
14
|
+
* - `unreadable` — `readSotBody` returned null (anchor-map missing
|
|
15
|
+
* or the source file disappeared).
|
|
16
|
+
* - `drifted` — body hash no longer matches `entry.content_hash`.
|
|
17
|
+
* The operator (or another agent) edited the source
|
|
18
|
+
* paragraph after phase 5b walked it. Caller should
|
|
19
|
+
* re-run `cairn index` and re-evaluate.
|
|
20
|
+
*
|
|
21
|
+
* Idempotent: a slug that already has a `dec_id` returns
|
|
22
|
+
* `{ ok: true, dec_id, path, warning: "...already exists" }` rather
|
|
23
|
+
* than re-emitting. The locked "DO NOT enforce" wording goes back
|
|
24
|
+
* to the agent verbatim so it cannot accidentally cite the draft as
|
|
25
|
+
* accepted policy.
|
|
26
|
+
*/
|
|
27
|
+
import type { ToolDef } from "./types.js";
|
|
28
|
+
interface Input {
|
|
29
|
+
slug: string;
|
|
30
|
+
title?: string;
|
|
31
|
+
kind?: "decision" | "rule";
|
|
32
|
+
}
|
|
33
|
+
export declare const proposeDecisionTool: ToolDef<Input>;
|
|
34
|
+
export {};
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cairn_propose_decision` — promote a topic-index candidate (slug)
|
|
3
|
+
* into a DEC draft under `.cairn/ground/decisions/_inbox/`.
|
|
4
|
+
*
|
|
5
|
+
* Spec: PHASE_6_REDESIGN §4.6 / §5.4. The AI-curator path. Body is
|
|
6
|
+
* ALWAYS verbatim from `readSotBody` so the resulting draft's
|
|
7
|
+
* `sot_content_hash` matches the source paragraph the agent saw — no
|
|
8
|
+
* paraphrasing, no AI-generated prose. The drift sensor depends on
|
|
9
|
+
* this hash.
|
|
10
|
+
*
|
|
11
|
+
* Refusal modes (returned as `{ ok: false, reason: <kind> }`):
|
|
12
|
+
* - `not_found` — slug not present in the topic-index.
|
|
13
|
+
* - `rejected` — slug appears in `.cairn/ground/_rejected.yaml`.
|
|
14
|
+
* - `unreadable` — `readSotBody` returned null (anchor-map missing
|
|
15
|
+
* or the source file disappeared).
|
|
16
|
+
* - `drifted` — body hash no longer matches `entry.content_hash`.
|
|
17
|
+
* The operator (or another agent) edited the source
|
|
18
|
+
* paragraph after phase 5b walked it. Caller should
|
|
19
|
+
* re-run `cairn index` and re-evaluate.
|
|
20
|
+
*
|
|
21
|
+
* Idempotent: a slug that already has a `dec_id` returns
|
|
22
|
+
* `{ ok: true, dec_id, path, warning: "...already exists" }` rather
|
|
23
|
+
* than re-emitting. The locked "DO NOT enforce" wording goes back
|
|
24
|
+
* to the agent verbatim so it cannot accidentally cite the draft as
|
|
25
|
+
* accepted policy.
|
|
26
|
+
*/
|
|
27
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
28
|
+
import { join } from "node:path";
|
|
29
|
+
import { stringify as stringifyYaml } from "yaml";
|
|
30
|
+
import { bodyContentHash, decisionsDir, deriveDecId, readAnchorMap, readRejectedYaml, readTopicIndex, setTopic, writeFileCandidatesMap, writeTopicIndex, } from "../../ground/index.js";
|
|
31
|
+
import { scanExistingDecisionIds } from "../../decision-capture/index.js";
|
|
32
|
+
import { withWriteLock } from "../../lock.js";
|
|
33
|
+
import { readSotBody } from "../../init/sot-emit.js";
|
|
34
|
+
import { requireBootstrap } from "../bootstrap-guard.js";
|
|
35
|
+
import { proposeDecisionInput } from "../schemas.js";
|
|
36
|
+
const CAPTURE_SOURCE = "ai-proposed";
|
|
37
|
+
const DECIDED_BY = "ai-curator";
|
|
38
|
+
async function handler(ctx, input) {
|
|
39
|
+
const block = requireBootstrap(ctx.repoRoot);
|
|
40
|
+
if (block !== null)
|
|
41
|
+
return block;
|
|
42
|
+
return withWriteLock(ctx.repoRoot, () => {
|
|
43
|
+
const topicIndex = readTopicIndex(ctx.repoRoot);
|
|
44
|
+
const entry = topicIndex.topics[input.slug];
|
|
45
|
+
if (entry === undefined) {
|
|
46
|
+
return {
|
|
47
|
+
ok: false,
|
|
48
|
+
reason: "not_found",
|
|
49
|
+
detail: `slug "${input.slug}" not in topic-index`,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
if (entry.dec_id !== undefined) {
|
|
53
|
+
return {
|
|
54
|
+
ok: true,
|
|
55
|
+
dec_id: entry.dec_id,
|
|
56
|
+
path: relativeInboxPath(entry.dec_id, ctx.repoRoot),
|
|
57
|
+
warning: `DEC draft already exists for slug ${input.slug}; returning existing id ${entry.dec_id}.`,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
const rejected = readRejectedYaml(ctx.repoRoot);
|
|
61
|
+
const reject = rejected.get(input.slug);
|
|
62
|
+
if (reject !== undefined) {
|
|
63
|
+
return {
|
|
64
|
+
ok: false,
|
|
65
|
+
reason: "rejected",
|
|
66
|
+
detail: reject.reason,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
const anchorMap = readAnchorMap(ctx.repoRoot);
|
|
70
|
+
const body = readSotBody(ctx.repoRoot, entry, anchorMap);
|
|
71
|
+
if (body === null) {
|
|
72
|
+
return {
|
|
73
|
+
ok: false,
|
|
74
|
+
reason: "unreadable",
|
|
75
|
+
detail: "anchor-map missing or source body unreadable",
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
// Drift check — the topic-index records the body hash phase 5b
|
|
79
|
+
// saw. If the operator (or another agent) has since edited the
|
|
80
|
+
// paragraph, the AI may be promoting outdated content. Surface it
|
|
81
|
+
// and bounce the caller; correct path is `cairn index` then retry.
|
|
82
|
+
if (entry.content_hash !== undefined) {
|
|
83
|
+
const currentHash = bodyContentHash(body);
|
|
84
|
+
if (currentHash !== entry.content_hash) {
|
|
85
|
+
return {
|
|
86
|
+
ok: false,
|
|
87
|
+
reason: "drifted",
|
|
88
|
+
detail: "Source file modified since index build. Run 'cairn index' to refresh, then retry.",
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const titleSeed = input.title !== undefined && input.title.trim().length > 0
|
|
93
|
+
? input.title.trim()
|
|
94
|
+
: firstLineFallback(body);
|
|
95
|
+
const sotPath = entryToSotPath(entry);
|
|
96
|
+
const existingIds = scanExistingDecisionIds(ctx.repoRoot);
|
|
97
|
+
const decId = allocateUniqueDecId({ sot_path: sotPath, title: titleSeed, capture_source: CAPTURE_SOURCE }, existingIds);
|
|
98
|
+
writeDraftToInbox({
|
|
99
|
+
repoRoot: ctx.repoRoot,
|
|
100
|
+
id: decId,
|
|
101
|
+
title: titleSeed,
|
|
102
|
+
body,
|
|
103
|
+
sot_path: sotPath,
|
|
104
|
+
source_file: entry.sot_source,
|
|
105
|
+
});
|
|
106
|
+
const updatedTopicIndex = setTopic(topicIndex, input.slug, {
|
|
107
|
+
...entry,
|
|
108
|
+
dec_id: decId,
|
|
109
|
+
});
|
|
110
|
+
writeTopicIndex(ctx.repoRoot, updatedTopicIndex);
|
|
111
|
+
// Refresh the per-file candidate count map so the read-enrich
|
|
112
|
+
// hint reflects the post-promote state next time the agent reads
|
|
113
|
+
// the file.
|
|
114
|
+
writeFileCandidatesMap(ctx.repoRoot, updatedTopicIndex);
|
|
115
|
+
return {
|
|
116
|
+
ok: true,
|
|
117
|
+
dec_id: decId,
|
|
118
|
+
path: relativeInboxPath(decId, ctx.repoRoot),
|
|
119
|
+
warning: `Created draft from slug ${input.slug}. Status=draft, pending operator review via cairn-attention. ` +
|
|
120
|
+
`DO NOT enforce this rule yet — proposal only. You MAY cite as "proposed (${decId}, draft)".`,
|
|
121
|
+
};
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
function writeDraftToInbox(args) {
|
|
125
|
+
const inboxDir = join(decisionsDir(args.repoRoot), "_inbox");
|
|
126
|
+
mkdirSync(inboxDir, { recursive: true });
|
|
127
|
+
const abs = join(inboxDir, `${args.id}.draft.md`);
|
|
128
|
+
const now = new Date().toISOString();
|
|
129
|
+
const fm = {
|
|
130
|
+
id: args.id,
|
|
131
|
+
title: args.title,
|
|
132
|
+
type: "adr",
|
|
133
|
+
status: "draft",
|
|
134
|
+
audience: "dual",
|
|
135
|
+
generated: now,
|
|
136
|
+
"verified-at": now,
|
|
137
|
+
decided_at: now,
|
|
138
|
+
decided_by: DECIDED_BY,
|
|
139
|
+
sot_kind: "path",
|
|
140
|
+
sot_path: args.sot_path,
|
|
141
|
+
sot_content_hash: bodyContentHash(args.body),
|
|
142
|
+
capture_source: CAPTURE_SOURCE,
|
|
143
|
+
source_file: args.source_file,
|
|
144
|
+
};
|
|
145
|
+
const out = [];
|
|
146
|
+
out.push("---");
|
|
147
|
+
out.push(stringifyYaml(fm).trimEnd());
|
|
148
|
+
out.push("---");
|
|
149
|
+
out.push("");
|
|
150
|
+
out.push(args.body.trimEnd());
|
|
151
|
+
out.push("");
|
|
152
|
+
writeFileSync(abs, out.join("\n"), "utf8");
|
|
153
|
+
return abs;
|
|
154
|
+
}
|
|
155
|
+
function relativeInboxPath(id, _repoRoot) {
|
|
156
|
+
// Project-relative path so callers can hand it straight to Read or
|
|
157
|
+
// `cairn_decision_get` without prefix juggling.
|
|
158
|
+
return `.cairn/ground/decisions/_inbox/${id}.draft.md`;
|
|
159
|
+
}
|
|
160
|
+
function entryToSotPath(entry) {
|
|
161
|
+
const sot = entry.candidates.find((c) => c.file === entry.sot_source);
|
|
162
|
+
if (sot === undefined)
|
|
163
|
+
return entry.sot_source;
|
|
164
|
+
if (sot.anchor !== undefined && sot.anchor.length > 0) {
|
|
165
|
+
return `${entry.sot_source}#${sot.anchor}`;
|
|
166
|
+
}
|
|
167
|
+
return entry.sot_source;
|
|
168
|
+
}
|
|
169
|
+
function firstLineFallback(body) {
|
|
170
|
+
const first = body.split("\n").find((l) => l.trim().length > 0) ?? "";
|
|
171
|
+
return first.replace(/^#+\s*/, "").trim().slice(0, 120) || "(untitled)";
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Mirrors `phase 6`'s id allocator: derive a content-stable id, fall
|
|
175
|
+
* back to `<title> #N` suffixing on the (vanishingly rare) collision
|
|
176
|
+
* against an existing on-disk DEC.
|
|
177
|
+
*/
|
|
178
|
+
function allocateUniqueDecId(input, existingIds) {
|
|
179
|
+
const id = deriveDecId(input);
|
|
180
|
+
if (!existingIds.has(id)) {
|
|
181
|
+
existingIds.add(id);
|
|
182
|
+
return id;
|
|
183
|
+
}
|
|
184
|
+
for (let suffix = 2; suffix < 1_000; suffix += 1) {
|
|
185
|
+
const tagged = deriveDecId({ ...input, title: `${input.title} #${suffix}` });
|
|
186
|
+
if (!existingIds.has(tagged)) {
|
|
187
|
+
existingIds.add(tagged);
|
|
188
|
+
return tagged;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
existingIds.add(id);
|
|
192
|
+
return id;
|
|
193
|
+
}
|
|
194
|
+
export const proposeDecisionTool = {
|
|
195
|
+
name: "cairn_propose_decision",
|
|
196
|
+
description: "Promote a topic-index candidate (slug) to a DEC draft in `_inbox/`. Body is verbatim via readSotBody; AI may only supply a title. Refuses on rejected, drifted, or unreadable slugs. Returns a locked 'DO NOT enforce — proposal only' warning so the draft is never miscited as accepted policy.",
|
|
197
|
+
inputSchema: proposeDecisionInput,
|
|
198
|
+
handler,
|
|
199
|
+
};
|
|
200
|
+
//# sourceMappingURL=propose-decision.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"propose-decision.js","sourceRoot":"","sources":["../../../src/mcp/tools/propose-decision.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cairn_reject_candidate` — append a topic-index slug to
|
|
3
|
+
* `.cairn/ground/_rejected.yaml` so phase 6 / `cairn ingest` /
|
|
4
|
+
* `cairn_propose_decision` skip it on the next pass.
|
|
5
|
+
*
|
|
6
|
+
* Spec: PHASE_6_REDESIGN §4.6. Dedup by slug — first writer wins the
|
|
7
|
+
* `reason` string; subsequent writes only refresh `rejected_at`. The
|
|
8
|
+
* AI-curator path stamps `rejected_by: "ai-curator"`; operator-driven
|
|
9
|
+
* rejections (`cairn-attention` skill) reuse the same writer with
|
|
10
|
+
* `rejected_by: "operator"`.
|
|
11
|
+
*
|
|
12
|
+
* Refusal modes:
|
|
13
|
+
* - `not_found` — slug not present in the topic-index. We refuse so
|
|
14
|
+
* stale agent calls can't poison the rejection
|
|
15
|
+
* ledger with phantom slugs that the GC pass would
|
|
16
|
+
* immediately drop.
|
|
17
|
+
*/
|
|
18
|
+
import type { ToolDef } from "./types.js";
|
|
19
|
+
interface Input {
|
|
20
|
+
slug: string;
|
|
21
|
+
reason: string;
|
|
22
|
+
}
|
|
23
|
+
export declare const rejectCandidateTool: ToolDef<Input>;
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cairn_reject_candidate` — append a topic-index slug to
|
|
3
|
+
* `.cairn/ground/_rejected.yaml` so phase 6 / `cairn ingest` /
|
|
4
|
+
* `cairn_propose_decision` skip it on the next pass.
|
|
5
|
+
*
|
|
6
|
+
* Spec: PHASE_6_REDESIGN §4.6. Dedup by slug — first writer wins the
|
|
7
|
+
* `reason` string; subsequent writes only refresh `rejected_at`. The
|
|
8
|
+
* AI-curator path stamps `rejected_by: "ai-curator"`; operator-driven
|
|
9
|
+
* rejections (`cairn-attention` skill) reuse the same writer with
|
|
10
|
+
* `rejected_by: "operator"`.
|
|
11
|
+
*
|
|
12
|
+
* Refusal modes:
|
|
13
|
+
* - `not_found` — slug not present in the topic-index. We refuse so
|
|
14
|
+
* stale agent calls can't poison the rejection
|
|
15
|
+
* ledger with phantom slugs that the GC pass would
|
|
16
|
+
* immediately drop.
|
|
17
|
+
*/
|
|
18
|
+
import { appendRejected, readRejectedYaml, readTopicIndex, writeRejectedYaml, } from "../../ground/index.js";
|
|
19
|
+
import { withWriteLock } from "../../lock.js";
|
|
20
|
+
import { requireBootstrap } from "../bootstrap-guard.js";
|
|
21
|
+
import { rejectCandidateInput } from "../schemas.js";
|
|
22
|
+
const REJECTED_BY = "ai-curator";
|
|
23
|
+
async function handler(ctx, input) {
|
|
24
|
+
const block = requireBootstrap(ctx.repoRoot);
|
|
25
|
+
if (block !== null)
|
|
26
|
+
return block;
|
|
27
|
+
return withWriteLock(ctx.repoRoot, () => {
|
|
28
|
+
const topicIndex = readTopicIndex(ctx.repoRoot);
|
|
29
|
+
const entry = topicIndex.topics[input.slug];
|
|
30
|
+
if (entry === undefined) {
|
|
31
|
+
return {
|
|
32
|
+
ok: false,
|
|
33
|
+
reason: "not_found",
|
|
34
|
+
detail: `slug "${input.slug}" not in topic-index`,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
const sot = entry.candidates.find((c) => c.file === entry.sot_source);
|
|
38
|
+
const now = new Date().toISOString();
|
|
39
|
+
const record = {
|
|
40
|
+
slug: input.slug,
|
|
41
|
+
rejected_at: now,
|
|
42
|
+
rejected_by: REJECTED_BY,
|
|
43
|
+
reason: input.reason,
|
|
44
|
+
sot_source: entry.sot_source,
|
|
45
|
+
...(sot?.line_range !== undefined ? { line_range: sot.line_range } : {}),
|
|
46
|
+
};
|
|
47
|
+
const current = readRejectedYaml(ctx.repoRoot);
|
|
48
|
+
const existing = current.get(input.slug);
|
|
49
|
+
const next = appendRejected(current, record);
|
|
50
|
+
writeRejectedYaml(ctx.repoRoot, next);
|
|
51
|
+
if (existing !== undefined) {
|
|
52
|
+
return {
|
|
53
|
+
ok: true,
|
|
54
|
+
slug: input.slug,
|
|
55
|
+
warning: `Slug ${input.slug} was already rejected by ${existing.rejected_by} (${existing.reason}). ` +
|
|
56
|
+
`Refreshed rejected_at; original reason preserved.`,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
ok: true,
|
|
61
|
+
slug: input.slug,
|
|
62
|
+
};
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
export const rejectCandidateTool = {
|
|
66
|
+
name: "cairn_reject_candidate",
|
|
67
|
+
description: "Mark a topic-index slug as not-a-decision in `.cairn/ground/_rejected.yaml`. Phase 6 / cairn ingest / cairn_propose_decision skip rejected slugs. Dedup by slug; first writer wins the reason string. Use when the candidate is research, narrative, plan, status, or otherwise not a binding rule.",
|
|
68
|
+
inputSchema: rejectCandidateInput,
|
|
69
|
+
handler,
|
|
70
|
+
};
|
|
71
|
+
//# sourceMappingURL=reject-candidate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reject-candidate.js","sourceRoot":"","sources":["../../../src/mcp/tools/reject-candidate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EACL,cAAc,EACd,gBAAgB,EAChB,cAAc,EACd,iBAAiB,GAElB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAE9C,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAgBrD,MAAM,WAAW,GAAG,YAAY,CAAC;AAEjC,KAAK,UAAU,OAAO,CAAC,GAAe,EAAE,KAAY;IAClD,MAAM,KAAK,GAAG,gBAAgB,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC7C,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAEjC,OAAO,aAAa,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,EAAE;QACtC,MAAM,UAAU,GAAG,cAAc,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAChD,MAAM,KAAK,GAAG,UAAU,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC5C,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,MAAM,EAAE,WAAW;gBACnB,MAAM,EAAE,SAAS,KAAK,CAAC,IAAI,sBAAsB;aAClB,CAAC;QACpC,CAAC;QAED,MAAM,GAAG,GAAG,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,KAAK,CAAC,UAAU,CAAC,CAAC;QACtE,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QACrC,MAAM,MAAM,GAAkB;YAC5B,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,WAAW,EAAE,GAAG;YAChB,WAAW,EAAE,WAAW;YACxB,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,UAAU,EAAE,KAAK,CAAC,UAAU;YAC5B,GAAG,CAAC,GAAG,EAAE,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,GAAG,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACzE,CAAC;QAEF,MAAM,OAAO,GAAG,gBAAgB,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC/C,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACzC,MAAM,IAAI,GAAG,cAAc,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC7C,iBAAiB,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QAEtC,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC3B,OAAO;gBACL,EAAE,EAAE,IAAI;gBACR,IAAI,EAAE,KAAK,CAAC,IAAI;gBAChB,OAAO,EACL,QAAQ,KAAK,CAAC,IAAI,4BAA4B,QAAQ,CAAC,WAAW,KAAK,QAAQ,CAAC,MAAM,KAAK;oBAC3F,mDAAmD;aACtB,CAAC;QACpC,CAAC;QACD,OAAO;YACL,EAAE,EAAE,IAAI;YACR,IAAI,EAAE,KAAK,CAAC,IAAI;SACe,CAAC;IACpC,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,MAAM,mBAAmB,GAAmB;IACjD,IAAI,EAAE,wBAAwB;IAC9B,WAAW,EACT,qSAAqS;IACvS,WAAW,EAAE,oBAAoB;IACjC,OAAO;CACR,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cairn_search_candidates` — query topic-index entries that haven't
|
|
3
|
+
* been promoted to a DEC yet (`dec_id IS NULL`).
|
|
4
|
+
*
|
|
5
|
+
* Spec: PHASE_6_REDESIGN §4.6. Mirrors the response shape of
|
|
6
|
+
* `cairn_decisions_in_scope` so AI agents can use the two
|
|
7
|
+
* interchangeably during the daily read-enrich-driven curator flow.
|
|
8
|
+
*
|
|
9
|
+
* The slugs returned by this tool are exactly the slugs accepted by
|
|
10
|
+
* `cairn_propose_decision` and `cairn_reject_candidate`.
|
|
11
|
+
*/
|
|
12
|
+
import type { ToolDef } from "./types.js";
|
|
13
|
+
interface Input {
|
|
14
|
+
query?: string;
|
|
15
|
+
scope?: string;
|
|
16
|
+
kind?: "decision" | "rule";
|
|
17
|
+
limit?: number;
|
|
18
|
+
}
|
|
19
|
+
export declare const searchCandidatesTool: ToolDef<Input>;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cairn_search_candidates` — query topic-index entries that haven't
|
|
3
|
+
* been promoted to a DEC yet (`dec_id IS NULL`).
|
|
4
|
+
*
|
|
5
|
+
* Spec: PHASE_6_REDESIGN §4.6. Mirrors the response shape of
|
|
6
|
+
* `cairn_decisions_in_scope` so AI agents can use the two
|
|
7
|
+
* interchangeably during the daily read-enrich-driven curator flow.
|
|
8
|
+
*
|
|
9
|
+
* The slugs returned by this tool are exactly the slugs accepted by
|
|
10
|
+
* `cairn_propose_decision` and `cairn_reject_candidate`.
|
|
11
|
+
*/
|
|
12
|
+
import { matchGlob, readRejectedYaml, readTopicIndex, } from "../../ground/index.js";
|
|
13
|
+
import { readSotBody } from "../../init/sot-emit.js";
|
|
14
|
+
import { readAnchorMap } from "../../ground/index.js";
|
|
15
|
+
import { searchCandidatesInput } from "../schemas.js";
|
|
16
|
+
/** Matches the body-preview cap used elsewhere when surfacing prose to AI agents. */
|
|
17
|
+
const BODY_PREVIEW_CHARS = 280;
|
|
18
|
+
const DEFAULT_LIMIT = 50;
|
|
19
|
+
async function handler(ctx, input) {
|
|
20
|
+
const limit = Math.min(input.limit ?? DEFAULT_LIMIT, 200);
|
|
21
|
+
const topicIndex = readTopicIndex(ctx.repoRoot);
|
|
22
|
+
const anchorMap = readAnchorMap(ctx.repoRoot);
|
|
23
|
+
const rejected = readRejectedYaml(ctx.repoRoot);
|
|
24
|
+
const q = input.query !== undefined ? input.query.toLowerCase() : null;
|
|
25
|
+
const scope = input.scope ?? null;
|
|
26
|
+
const out = [];
|
|
27
|
+
for (const entry of Object.values(topicIndex.topics)) {
|
|
28
|
+
if (entry.dec_id !== undefined)
|
|
29
|
+
continue;
|
|
30
|
+
if (rejected.has(entry.slug))
|
|
31
|
+
continue;
|
|
32
|
+
if (input.kind !== undefined && entry.marker_kind !== input.kind)
|
|
33
|
+
continue;
|
|
34
|
+
if (scope !== null && !matchGlob(entry.sot_source, scope))
|
|
35
|
+
continue;
|
|
36
|
+
const body = readSotBody(ctx.repoRoot, entry, anchorMap) ?? "";
|
|
37
|
+
const title = deriveTitle(entry, body);
|
|
38
|
+
if (q !== null) {
|
|
39
|
+
const titleHit = title.toLowerCase().includes(q);
|
|
40
|
+
const bodyHit = body.toLowerCase().includes(q);
|
|
41
|
+
if (!titleHit && !bodyHit)
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
const sot = entry.candidates.find((c) => c.file === entry.sot_source);
|
|
45
|
+
const summary = {
|
|
46
|
+
slug: entry.slug,
|
|
47
|
+
title,
|
|
48
|
+
sot_source: entry.sot_source,
|
|
49
|
+
body_preview: previewBody(body),
|
|
50
|
+
};
|
|
51
|
+
if (sot?.line_range !== undefined)
|
|
52
|
+
summary.line_range = sot.line_range;
|
|
53
|
+
if (entry.marker_kind !== undefined)
|
|
54
|
+
summary.marker_kind = entry.marker_kind;
|
|
55
|
+
out.push(summary);
|
|
56
|
+
}
|
|
57
|
+
// Stable ordering: marker-kinded first, then alpha by sot_source so
|
|
58
|
+
// pagination via `limit` is predictable across runs.
|
|
59
|
+
out.sort((a, b) => {
|
|
60
|
+
const am = a.marker_kind !== undefined ? 0 : 1;
|
|
61
|
+
const bm = b.marker_kind !== undefined ? 0 : 1;
|
|
62
|
+
if (am !== bm)
|
|
63
|
+
return am - bm;
|
|
64
|
+
if (a.sot_source !== b.sot_source)
|
|
65
|
+
return a.sot_source.localeCompare(b.sot_source);
|
|
66
|
+
return a.slug.localeCompare(b.slug);
|
|
67
|
+
});
|
|
68
|
+
return out.slice(0, limit);
|
|
69
|
+
}
|
|
70
|
+
function deriveTitle(entry, body) {
|
|
71
|
+
const sot = entry.candidates.find((c) => c.file === entry.sot_source);
|
|
72
|
+
if (sot?.anchor !== undefined && sot.anchor.length > 0) {
|
|
73
|
+
return sot.anchor.replace(/[-_]+/g, " ").trim().slice(0, 120) || firstLineFallback(body);
|
|
74
|
+
}
|
|
75
|
+
return firstLineFallback(body);
|
|
76
|
+
}
|
|
77
|
+
function firstLineFallback(body) {
|
|
78
|
+
const first = body.split("\n").find((l) => l.trim().length > 0) ?? "";
|
|
79
|
+
return first.replace(/^#+\s*/, "").trim().slice(0, 120) || "(untitled)";
|
|
80
|
+
}
|
|
81
|
+
function previewBody(body) {
|
|
82
|
+
const trimmed = body.trim();
|
|
83
|
+
if (trimmed.length <= BODY_PREVIEW_CHARS)
|
|
84
|
+
return trimmed;
|
|
85
|
+
return `${trimmed.slice(0, BODY_PREVIEW_CHARS)}…`;
|
|
86
|
+
}
|
|
87
|
+
export const searchCandidatesTool = {
|
|
88
|
+
name: "cairn_search_candidates",
|
|
89
|
+
description: "List unpromoted topic-index candidates (slugs whose `dec_id` is null). Filter by `query` (substring on title/body), `scope` (glob on sot_source), or `kind` (marker_kind). Returned slugs feed `cairn_propose_decision` and `cairn_reject_candidate`.",
|
|
90
|
+
inputSchema: searchCandidatesInput,
|
|
91
|
+
handler,
|
|
92
|
+
};
|
|
93
|
+
//# sourceMappingURL=search-candidates.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search-candidates.js","sourceRoot":"","sources":["../../../src/mcp/tools/search-candidates.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@isaacriehm/cairn-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Cairn core — state + context layer. Curated `.cairn/ground/` (decisions, §INV invariants, canonical-map, brand, quality-grades), MCP server, init wizard, hook runners, sensors, GC drift sweep.",
|
|
5
5
|
"author": "Isaac Riehm",
|
|
6
6
|
"license": "MIT",
|
|
@@ -301,15 +301,52 @@ async function mergeCluster() {
|
|
|
301
301
|
}
|
|
302
302
|
|
|
303
303
|
async function bulkAcceptHigh() {
|
|
304
|
-
|
|
304
|
+
// Dry-run first so the operator sees the distribution before any
|
|
305
|
+
// writes hit disk. Without this the button silently mutated drafts
|
|
306
|
+
// even when zero met the high threshold.
|
|
307
|
+
setStatus("scoring drafts (dry run)…");
|
|
308
|
+
const preview = await api("/api/bulk-accept", {
|
|
309
|
+
method: "POST",
|
|
310
|
+
body: { threshold: "high", dryRun: true },
|
|
311
|
+
});
|
|
312
|
+
if (!preview.ok) {
|
|
313
|
+
setStatus("bulk-accept preview failed");
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
const dist = preview.decsByConfidence ?? { high: 0, medium: 0, low: 0 };
|
|
317
|
+
const invDist = preview.invariantsByConfidence ?? { high: 0, medium: 0, low: 0 };
|
|
318
|
+
const wouldPromote = preview.decsAccepted ?? 0;
|
|
319
|
+
const summary =
|
|
320
|
+
`Score preview\n` +
|
|
321
|
+
` drafts: ${dist.high} high · ${dist.medium} medium · ${dist.low} low\n` +
|
|
322
|
+
` invariants: ${invDist.high} high · ${invDist.medium} medium · ${invDist.low} low\n\n` +
|
|
323
|
+
`${wouldPromote} draft${wouldPromote === 1 ? "" : "s"} would be promoted to accepted.\n` +
|
|
324
|
+
`${dist.medium + dist.low} medium/low draft${dist.medium + dist.low === 1 ? "" : "s"} would stay in inbox (newly stamped only if not already scored).\n\n` +
|
|
325
|
+
`Continue?`;
|
|
326
|
+
if (wouldPromote === 0) {
|
|
327
|
+
setStatus(
|
|
328
|
+
`0 drafts meet 'high' threshold — nothing to promote (${dist.high}/${dist.medium}/${dist.low} h/m/l). Cancelled.`,
|
|
329
|
+
);
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
if (!window.confirm(summary)) {
|
|
333
|
+
setStatus("bulk-accept cancelled");
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
setStatus("bulk-accepting…");
|
|
305
337
|
const r = await api("/api/bulk-accept", {
|
|
306
338
|
method: "POST",
|
|
307
339
|
body: { threshold: "high" },
|
|
308
340
|
});
|
|
309
341
|
if (r.ok) {
|
|
310
|
-
|
|
342
|
+
const accepted = r.decsAccepted ?? 0;
|
|
343
|
+
const finalDist = r.decsByConfidence ?? dist;
|
|
344
|
+
setStatus(
|
|
345
|
+
`bulk-accepted ${accepted} draft${accepted === 1 ? "" : "s"}. ` +
|
|
346
|
+
`${finalDist.medium + finalDist.low} remain in inbox.`,
|
|
347
|
+
);
|
|
311
348
|
} else {
|
|
312
|
-
setStatus(
|
|
349
|
+
setStatus("bulk-accept failed");
|
|
313
350
|
}
|
|
314
351
|
await refresh();
|
|
315
352
|
}
|