@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.
Files changed (81) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/attention/bulk-accept.js +56 -15
  3. package/dist/attention/bulk-accept.js.map +1 -1
  4. package/dist/attention/serve/api.js +7 -1
  5. package/dist/attention/serve/api.js.map +1 -1
  6. package/dist/ground/file-candidates-map.d.ts +23 -0
  7. package/dist/ground/file-candidates-map.js +76 -0
  8. package/dist/ground/file-candidates-map.js.map +1 -0
  9. package/dist/ground/index.d.ts +4 -2
  10. package/dist/ground/index.js +4 -2
  11. package/dist/ground/index.js.map +1 -1
  12. package/dist/ground/paths.d.ts +2 -0
  13. package/dist/ground/paths.js +6 -0
  14. package/dist/ground/paths.js.map +1 -1
  15. package/dist/ground/rejected.d.ts +42 -0
  16. package/dist/ground/rejected.js +100 -0
  17. package/dist/ground/rejected.js.map +1 -0
  18. package/dist/ground/schemas.d.ts +80 -0
  19. package/dist/ground/schemas.js +54 -0
  20. package/dist/ground/schemas.js.map +1 -1
  21. package/dist/hooks/post-tool-use/index.d.ts +1 -1
  22. package/dist/hooks/post-tool-use/index.js +1 -1
  23. package/dist/hooks/post-tool-use/index.js.map +1 -1
  24. package/dist/hooks/post-tool-use/ledger-cache.d.ts +13 -0
  25. package/dist/hooks/post-tool-use/ledger-cache.js +48 -0
  26. package/dist/hooks/post-tool-use/ledger-cache.js.map +1 -1
  27. package/dist/hooks/post-tool-use/legend-builder.d.ts +10 -1
  28. package/dist/hooks/post-tool-use/legend-builder.js +27 -2
  29. package/dist/hooks/post-tool-use/legend-builder.js.map +1 -1
  30. package/dist/hooks/post-tool-use/read-enricher.js +8 -2
  31. package/dist/hooks/post-tool-use/read-enricher.js.map +1 -1
  32. package/dist/init/index.d.ts +2 -2
  33. package/dist/init/index.js +1 -1
  34. package/dist/init/index.js.map +1 -1
  35. package/dist/init/ingest-docs.d.ts +82 -22
  36. package/dist/init/ingest-docs.js +632 -108
  37. package/dist/init/ingest-docs.js.map +1 -1
  38. package/dist/init/init.d.ts +10 -1
  39. package/dist/init/init.js +113 -251
  40. package/dist/init/init.js.map +1 -1
  41. package/dist/init/mapper-parallel.js +8 -0
  42. package/dist/init/mapper-parallel.js.map +1 -1
  43. package/dist/init/phases/6-docs-ingest.d.ts +9 -4
  44. package/dist/init/phases/6-docs-ingest.js +13 -10
  45. package/dist/init/phases/6-docs-ingest.js.map +1 -1
  46. package/dist/init/phases/parallel-678.js +10 -4
  47. package/dist/init/phases/parallel-678.js.map +1 -1
  48. package/dist/init/sot-emit.d.ts +22 -0
  49. package/dist/init/sot-emit.js +50 -4
  50. package/dist/init/sot-emit.js.map +1 -1
  51. package/dist/init/source-comments/ingest.js +107 -7
  52. package/dist/init/source-comments/ingest.js.map +1 -1
  53. package/dist/init/topic-index/index.d.ts +14 -0
  54. package/dist/init/topic-index/index.js +83 -4
  55. package/dist/init/topic-index/index.js.map +1 -1
  56. package/dist/init/topic-index/judge.js +14 -1
  57. package/dist/init/topic-index/judge.js.map +1 -1
  58. package/dist/init/topic-index/resolve.d.ts +19 -0
  59. package/dist/init/topic-index/resolve.js +100 -14
  60. package/dist/init/topic-index/resolve.js.map +1 -1
  61. package/dist/init/topic-index/walk.d.ts +32 -0
  62. package/dist/init/topic-index/walk.js +70 -4
  63. package/dist/init/topic-index/walk.js.map +1 -1
  64. package/dist/mcp/history/summarizer.js +5 -0
  65. package/dist/mcp/history/summarizer.js.map +1 -1
  66. package/dist/mcp/schemas.d.ts +48 -0
  67. package/dist/mcp/schemas.js +43 -0
  68. package/dist/mcp/schemas.js.map +1 -1
  69. package/dist/mcp/tools/index.js +8 -0
  70. package/dist/mcp/tools/index.js.map +1 -1
  71. package/dist/mcp/tools/propose-decision.d.ts +34 -0
  72. package/dist/mcp/tools/propose-decision.js +200 -0
  73. package/dist/mcp/tools/propose-decision.js.map +1 -0
  74. package/dist/mcp/tools/reject-candidate.d.ts +24 -0
  75. package/dist/mcp/tools/reject-candidate.js +71 -0
  76. package/dist/mcp/tools/reject-candidate.js.map +1 -0
  77. package/dist/mcp/tools/search-candidates.d.ts +20 -0
  78. package/dist/mcp/tools/search-candidates.js +93 -0
  79. package/dist/mcp/tools/search-candidates.js.map +1 -0
  80. package/package.json +1 -1
  81. 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.6.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
- setStatus("bulk-accepting high-confidence drafts…");
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
- setStatus(`bulk-accepted ${r.decsAccepted} drafts`);
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(`bulk-accept failed`);
349
+ setStatus("bulk-accept failed");
313
350
  }
314
351
  await refresh();
315
352
  }