@isaacriehm/cairn-core 0.4.3 → 0.6.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 (186) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/align-undo/index.d.ts +6 -0
  3. package/dist/align-undo/index.js +6 -0
  4. package/dist/align-undo/index.js.map +1 -0
  5. package/dist/align-undo/log.d.ts +53 -0
  6. package/dist/align-undo/log.js +162 -0
  7. package/dist/align-undo/log.js.map +1 -0
  8. package/dist/align-undo/undo.d.ts +65 -0
  9. package/dist/align-undo/undo.js +397 -0
  10. package/dist/align-undo/undo.js.map +1 -0
  11. package/dist/attention/bulk-accept.js +9 -22
  12. package/dist/attention/bulk-accept.js.map +1 -1
  13. package/dist/attention/dedup.js +1 -47
  14. package/dist/attention/dedup.js.map +1 -1
  15. package/dist/attention/serve/api.js +3 -17
  16. package/dist/attention/serve/api.js.map +1 -1
  17. package/dist/attention/serve/index.js +3 -3
  18. package/dist/attention/serve/index.js.map +1 -1
  19. package/dist/drain/drain.d.ts +77 -0
  20. package/dist/drain/drain.js +464 -0
  21. package/dist/drain/drain.js.map +1 -0
  22. package/dist/drain/index.d.ts +5 -0
  23. package/dist/drain/index.js +5 -0
  24. package/dist/drain/index.js.map +1 -0
  25. package/dist/fix-align/index.d.ts +7 -0
  26. package/dist/fix-align/index.js +6 -0
  27. package/dist/fix-align/index.js.map +1 -0
  28. package/dist/fix-align/run.d.ts +99 -0
  29. package/dist/fix-align/run.js +258 -0
  30. package/dist/fix-align/run.js.map +1 -0
  31. package/dist/fix-align/sentinel.d.ts +59 -0
  32. package/dist/fix-align/sentinel.js +149 -0
  33. package/dist/fix-align/sentinel.js.map +1 -0
  34. package/dist/fs.d.ts +5 -0
  35. package/dist/fs.js +11 -0
  36. package/dist/fs.js.map +1 -0
  37. package/dist/gc/apply.js +4 -4
  38. package/dist/gc/apply.js.map +1 -1
  39. package/dist/ground/alignment-pending.d.ts +28 -0
  40. package/dist/ground/alignment-pending.js +83 -0
  41. package/dist/ground/alignment-pending.js.map +1 -0
  42. package/dist/ground/anchor-map.d.ts +14 -0
  43. package/dist/ground/anchor-map.js +56 -0
  44. package/dist/ground/anchor-map.js.map +1 -0
  45. package/dist/ground/frontmatter.d.ts +12 -0
  46. package/dist/ground/frontmatter.js +28 -0
  47. package/dist/ground/frontmatter.js.map +1 -1
  48. package/dist/ground/index.d.ts +10 -3
  49. package/dist/ground/index.js +9 -3
  50. package/dist/ground/index.js.map +1 -1
  51. package/dist/ground/paths.d.ts +21 -0
  52. package/dist/ground/paths.js +43 -0
  53. package/dist/ground/paths.js.map +1 -1
  54. package/dist/ground/schemas.d.ts +201 -0
  55. package/dist/ground/schemas.js +135 -10
  56. package/dist/ground/schemas.js.map +1 -1
  57. package/dist/ground/scope-index.js +4 -4
  58. package/dist/ground/scope-index.js.map +1 -1
  59. package/dist/ground/slug.d.ts +60 -0
  60. package/dist/ground/slug.js +103 -0
  61. package/dist/ground/slug.js.map +1 -0
  62. package/dist/ground/sot-bindings.d.ts +14 -0
  63. package/dist/ground/sot-bindings.js +79 -0
  64. package/dist/ground/sot-bindings.js.map +1 -0
  65. package/dist/ground/sot-cache.d.ts +18 -0
  66. package/dist/ground/sot-cache.js +62 -0
  67. package/dist/ground/sot-cache.js.map +1 -0
  68. package/dist/ground/topic-index.d.ts +27 -0
  69. package/dist/ground/topic-index.js +82 -0
  70. package/dist/ground/topic-index.js.map +1 -0
  71. package/dist/hooks/post-tool-use/index.d.ts +2 -0
  72. package/dist/hooks/post-tool-use/index.js +1 -0
  73. package/dist/hooks/post-tool-use/index.js.map +1 -1
  74. package/dist/hooks/post-tool-use/sot-align.d.ts +166 -0
  75. package/dist/hooks/post-tool-use/sot-align.js +1306 -0
  76. package/dist/hooks/post-tool-use/sot-align.js.map +1 -0
  77. package/dist/hooks/pre-commit/index.d.ts +8 -0
  78. package/dist/hooks/pre-commit/index.js +8 -0
  79. package/dist/hooks/pre-commit/index.js.map +1 -0
  80. package/dist/hooks/pre-commit/sot-align-precommit.d.ts +60 -0
  81. package/dist/hooks/pre-commit/sot-align-precommit.js +221 -0
  82. package/dist/hooks/pre-commit/sot-align-precommit.js.map +1 -0
  83. package/dist/hooks/runners/session-start.js +41 -0
  84. package/dist/hooks/runners/session-start.js.map +1 -1
  85. package/dist/hooks/sot-align-common.d.ts +39 -0
  86. package/dist/hooks/sot-align-common.js +152 -0
  87. package/dist/hooks/sot-align-common.js.map +1 -0
  88. package/dist/index.d.ts +5 -0
  89. package/dist/index.js +5 -0
  90. package/dist/index.js.map +1 -1
  91. package/dist/init/index.d.ts +4 -2
  92. package/dist/init/index.js +2 -1
  93. package/dist/init/index.js.map +1 -1
  94. package/dist/init/ingest-docs.d.ts +30 -47
  95. package/dist/init/ingest-docs.js +113 -410
  96. package/dist/init/ingest-docs.js.map +1 -1
  97. package/dist/init/init.d.ts +8 -0
  98. package/dist/init/init.js +58 -29
  99. package/dist/init/init.js.map +1 -1
  100. package/dist/init/mapper.js +6 -6
  101. package/dist/init/mapper.js.map +1 -1
  102. package/dist/init/phases/5-brand.js +1 -1
  103. package/dist/init/phases/5-brand.js.map +1 -1
  104. package/dist/init/phases/5b-topic-index.d.ts +30 -0
  105. package/dist/init/phases/5b-topic-index.js +62 -0
  106. package/dist/init/phases/5b-topic-index.js.map +1 -0
  107. package/dist/init/phases/6-docs-ingest.d.ts +4 -5
  108. package/dist/init/phases/6-docs-ingest.js +5 -6
  109. package/dist/init/phases/6-docs-ingest.js.map +1 -1
  110. package/dist/init/phases/index.d.ts +2 -0
  111. package/dist/init/phases/index.js +1 -0
  112. package/dist/init/phases/index.js.map +1 -1
  113. package/dist/init/phases/parallel-678.d.ts +14 -17
  114. package/dist/init/phases/parallel-678.js +77 -98
  115. package/dist/init/phases/parallel-678.js.map +1 -1
  116. package/dist/init/phases/source-comments-output-io.d.ts +16 -10
  117. package/dist/init/phases/source-comments-output-io.js +7 -10
  118. package/dist/init/phases/source-comments-output-io.js.map +1 -1
  119. package/dist/init/phases/types.d.ts +1 -1
  120. package/dist/init/phases/types.js +1 -0
  121. package/dist/init/phases/types.js.map +1 -1
  122. package/dist/init/rules-merge/discover.d.ts +8 -3
  123. package/dist/init/rules-merge/discover.js +7 -3
  124. package/dist/init/rules-merge/discover.js.map +1 -1
  125. package/dist/init/rules-merge/ingest.d.ts +81 -28
  126. package/dist/init/rules-merge/ingest.js +456 -162
  127. package/dist/init/rules-merge/ingest.js.map +1 -1
  128. package/dist/init/sot-emit.d.ts +84 -0
  129. package/dist/init/sot-emit.js +214 -0
  130. package/dist/init/sot-emit.js.map +1 -0
  131. package/dist/init/source-comments/classify.d.ts +12 -10
  132. package/dist/init/source-comments/classify.js +13 -25
  133. package/dist/init/source-comments/classify.js.map +1 -1
  134. package/dist/init/source-comments/index.d.ts +1 -1
  135. package/dist/init/source-comments/index.js +1 -1
  136. package/dist/init/source-comments/index.js.map +1 -1
  137. package/dist/init/source-comments/ingest.d.ts +91 -67
  138. package/dist/init/source-comments/ingest.js +392 -361
  139. package/dist/init/source-comments/ingest.js.map +1 -1
  140. package/dist/init/topic-index/index.d.ts +36 -0
  141. package/dist/init/topic-index/index.js +46 -0
  142. package/dist/init/topic-index/index.js.map +1 -0
  143. package/dist/init/topic-index/judge.d.ts +20 -0
  144. package/dist/init/topic-index/judge.js +65 -0
  145. package/dist/init/topic-index/judge.js.map +1 -0
  146. package/dist/init/topic-index/resolve.d.ts +50 -0
  147. package/dist/init/topic-index/resolve.js +196 -0
  148. package/dist/init/topic-index/resolve.js.map +1 -0
  149. package/dist/init/topic-index/walk.d.ts +43 -0
  150. package/dist/init/topic-index/walk.js +293 -0
  151. package/dist/init/topic-index/walk.js.map +1 -0
  152. package/dist/mcp/schemas.d.ts +45 -8
  153. package/dist/mcp/schemas.js +43 -7
  154. package/dist/mcp/schemas.js.map +1 -1
  155. package/dist/mcp/tools/align-drain.d.ts +7 -0
  156. package/dist/mcp/tools/align-drain.js +26 -0
  157. package/dist/mcp/tools/align-drain.js.map +1 -0
  158. package/dist/mcp/tools/index.d.ts +1 -1
  159. package/dist/mcp/tools/index.js +3 -0
  160. package/dist/mcp/tools/index.js.map +1 -1
  161. package/dist/mcp/tools/init-phases.js +4 -1
  162. package/dist/mcp/tools/init-phases.js.map +1 -1
  163. package/dist/mcp/tools/record-decision.js +5 -2
  164. package/dist/mcp/tools/record-decision.js.map +1 -1
  165. package/dist/mcp/tools/resolve-attention.d.ts +2 -2
  166. package/dist/mcp/tools/resolve-attention.js +781 -5
  167. package/dist/mcp/tools/resolve-attention.js.map +1 -1
  168. package/dist/status-line/event-queue.d.ts +40 -0
  169. package/dist/status-line/event-queue.js +195 -0
  170. package/dist/status-line/event-queue.js.map +1 -0
  171. package/dist/status-line/format.d.ts +1 -1
  172. package/dist/status-line/format.js +49 -6
  173. package/dist/status-line/format.js.map +1 -1
  174. package/dist/status-line/index.d.ts +41 -0
  175. package/dist/status-line/index.js +14 -0
  176. package/dist/status-line/index.js.map +1 -1
  177. package/dist/status-line/reader.js +23 -18
  178. package/dist/status-line/reader.js.map +1 -1
  179. package/dist/status-line/writer.d.ts +1 -1
  180. package/dist/status-line/writer.js +5 -0
  181. package/dist/status-line/writer.js.map +1 -1
  182. package/dist/text/jaccard.d.ts +19 -0
  183. package/dist/text/jaccard.js +68 -0
  184. package/dist/text/jaccard.js.map +1 -0
  185. package/package.json +1 -1
  186. package/templates/.cairn/git-hooks/pre-commit +16 -3
@@ -0,0 +1,464 @@
1
+ /**
2
+ * Layer C — SessionStart drain (plan §4.3).
3
+ *
4
+ * Reads the rich deferred logs written by Layer A
5
+ * (`.cairn/staleness/layer-a-deferred.jsonl`) and Layer B
6
+ * (`.cairn/staleness/pre-commit-deferred.jsonl`), re-checks each entry
7
+ * against the current source location, and applies one of three
8
+ * verdicts to each surviving entry:
9
+ *
10
+ * - `same` → strip-replace the prose block with `// §DEC-<id>`
11
+ * cite. Pure deterministic for Layer B `tier1`
12
+ * entries (the pre-commit hook already passed the
13
+ * Tier 1 floors); Haiku-judged for everything else.
14
+ * - `different` → drop the entry, no source change.
15
+ * - `ambiguous` → write to `.cairn/ground/alignment-pending/` so
16
+ * the cairn-attention skill surfaces a side-by-side
17
+ * review next session.
18
+ *
19
+ * Drain truncates both deferred logs after running. The lightweight
20
+ * drift events in `.cairn/staleness/log.jsonl` are an audit trail and
21
+ * stay.
22
+ *
23
+ * Cost: capped at `max_haiku_calls` (default 30 per plan §4.3 budget).
24
+ * Excess entries stay in the deferred logs for the next drain. Each
25
+ * Haiku call is verdict-cached at
26
+ * `.cairn/cache/haiku/drain-judge/<blockHash>-<decId>.json` keyed on
27
+ * `(block_content_hash, dec_body_hash)`, so re-running the same block
28
+ * against the same DEC body short-circuits without burning a call.
29
+ *
30
+ * Haiku unavailable fallback: drain attempts the deterministic re-check
31
+ * pass only (Layer B tier1 entries get applied; everything else stays
32
+ * deferred). `setHaikuAvailable(false)` raises the statusline banner.
33
+ */
34
+ import { existsSync, readFileSync, rmSync, } from "node:fs";
35
+ import { createHash } from "node:crypto";
36
+ import { join } from "node:path";
37
+ import { writeFileSafe } from "../fs.js";
38
+ import { z } from "zod";
39
+ import { runClaude, claudeIsAvailable } from "../claude/index.js";
40
+ import { bodyContentHash, haikuCacheDir, layerADeferredLogPath, preCommitDeferredLogPath, readSotCache, recordDriftEvent, writeAlignmentPending, } from "../ground/index.js";
41
+ import { applyStripReplace, formatBareCitation, } from "../init/source-comments/strip-replace.js";
42
+ import { TIER2_JACCARD_FLOOR, TOP_K_CANDIDATES, extractBlocks, isMarkdownPath, readEntityBody, topKCandidates, } from "../hooks/sot-align-common.js";
43
+ import { logger } from "../logger.js";
44
+ import { pushEvent, setHaikuAvailable } from "../status-line/event-queue.js";
45
+ import { tokenize } from "../text/jaccard.js";
46
+ const log = logger("drain");
47
+ /* -------------------------------------------------------------------------- */
48
+ /* Tunables */
49
+ /* -------------------------------------------------------------------------- */
50
+ const DEFAULT_MAX_HAIKU_CALLS = 30;
51
+ const PER_HAIKU_TIMEOUT_MS = 30_000;
52
+ const BLOCK_BODY_CAP = 1_500;
53
+ const SUMMARY_BLIP_THRESHOLD = 20;
54
+ /* -------------------------------------------------------------------------- */
55
+ /* Entry shape parsing */
56
+ /* -------------------------------------------------------------------------- */
57
+ const LayerADeferredEntry = z.object({
58
+ ts: z.string(),
59
+ file: z.string(),
60
+ startLine: z.number(),
61
+ endLine: z.number(),
62
+ startOffset: z.number(),
63
+ endOffset: z.number(),
64
+ prose: z.string(),
65
+ reason: z.string(),
66
+ });
67
+ const PreCommitCandidate = z.object({
68
+ id: z.string(),
69
+ similarity: z.number(),
70
+ body_hash: z.string(),
71
+ sot_path: z.string(),
72
+ });
73
+ const PreCommitEntry = z.object({
74
+ ts: z.string(),
75
+ file: z.string(),
76
+ block_start_line: z.number(),
77
+ block_end_line: z.number(),
78
+ block_content_hash: z.string(),
79
+ block_prose: z.string(),
80
+ tier: z.enum(["tier1", "tier2-3"]),
81
+ candidates: z.array(PreCommitCandidate),
82
+ });
83
+ function readJsonl(path, parser) {
84
+ if (!existsSync(path))
85
+ return [];
86
+ const text = readFileSync(path, "utf8");
87
+ if (text.trim().length === 0)
88
+ return [];
89
+ const out = [];
90
+ for (const line of text.split("\n")) {
91
+ const trimmed = line.trim();
92
+ if (trimmed.length === 0)
93
+ continue;
94
+ try {
95
+ const parsed = JSON.parse(trimmed);
96
+ out.push(parser.parse(parsed));
97
+ }
98
+ catch (err) {
99
+ log.warn({ path, err: err instanceof Error ? err.message : String(err) }, "skipping malformed deferred log entry");
100
+ }
101
+ }
102
+ return out;
103
+ }
104
+ function loadDeferredEntries(repoRoot) {
105
+ const out = [];
106
+ for (const e of readJsonl(layerADeferredLogPath(repoRoot), LayerADeferredEntry)) {
107
+ out.push({ source: "layer-a", file: e.file, prose: e.prose });
108
+ }
109
+ for (const e of readJsonl(preCommitDeferredLogPath(repoRoot), PreCommitEntry)) {
110
+ if (e.tier === "tier1") {
111
+ const top = e.candidates[0];
112
+ if (top !== undefined) {
113
+ out.push({
114
+ source: "pre-commit-tier1",
115
+ file: e.file,
116
+ prose: e.block_prose,
117
+ tier1Candidate: { id: top.id, body_hash: top.body_hash },
118
+ });
119
+ }
120
+ }
121
+ else {
122
+ out.push({
123
+ source: "pre-commit-tier2-3",
124
+ file: e.file,
125
+ prose: e.block_prose,
126
+ });
127
+ }
128
+ }
129
+ return out;
130
+ }
131
+ /* -------------------------------------------------------------------------- */
132
+ /* Drain pipeline */
133
+ /* -------------------------------------------------------------------------- */
134
+ export async function runDrain(args) {
135
+ const { repoRoot } = args;
136
+ const dryRun = args.dryRun === true;
137
+ const maxHaikuCalls = args.maxHaikuCalls ?? DEFAULT_MAX_HAIKU_CALLS;
138
+ const sessionId = args.sessionId ?? null;
139
+ const haikuAvailable = args.haikuAvailable ?? (args.mockJudge !== undefined ? true : claudeIsAvailable());
140
+ const result = {
141
+ totalEntries: 0,
142
+ droppedMissing: 0,
143
+ citedDeterministic: 0,
144
+ citedHaiku: 0,
145
+ droppedDifferent: 0,
146
+ pending: 0,
147
+ deferred: 0,
148
+ haikuCalls: 0,
149
+ haikuFallback: !haikuAvailable,
150
+ };
151
+ const entries = loadDeferredEntries(repoRoot);
152
+ result.totalEntries = entries.length;
153
+ if (entries.length === 0)
154
+ return result;
155
+ if (sessionId !== null) {
156
+ pushEvent(repoRoot, sessionId, {
157
+ kind: "drain-progress",
158
+ detail: `${entries.length} entries`,
159
+ });
160
+ if (!haikuAvailable) {
161
+ setHaikuAvailable(repoRoot, sessionId, false);
162
+ }
163
+ }
164
+ const cache = readSotCache(repoRoot);
165
+ const cacheEntries = Object.values(cache.entries).filter((e) => e.tokens.length > 0);
166
+ const cited = [];
167
+ const survivingEntries = [];
168
+ for (const entry of entries) {
169
+ if (isMarkdownPath(entry.file)) {
170
+ // Drain never auto-cites markdown — same rationale as Layer A/B.
171
+ result.droppedMissing += 1;
172
+ continue;
173
+ }
174
+ const block = relocateBlock(repoRoot, entry);
175
+ if (block === null) {
176
+ result.droppedMissing += 1;
177
+ continue;
178
+ }
179
+ if (entry.source === "pre-commit-tier1" && entry.tier1Candidate !== undefined) {
180
+ const candId = entry.tier1Candidate.id;
181
+ const candBody = readEntityBody(repoRoot, candId);
182
+ if (candBody === null) {
183
+ // Candidate DEC was deleted between defer and drain.
184
+ result.droppedMissing += 1;
185
+ continue;
186
+ }
187
+ // Verify the cached match still holds — body may have changed.
188
+ if (entry.tier1Candidate.body_hash !== bodyContentHash(candBody)) {
189
+ // Cached body diverged; demote to Haiku judge.
190
+ survivingEntries.push({ ...entry, source: "pre-commit-tier2-3" });
191
+ continue;
192
+ }
193
+ cited.push(buildCiteItem(block, candId));
194
+ result.citedDeterministic += 1;
195
+ continue;
196
+ }
197
+ survivingEntries.push(entry);
198
+ }
199
+ if (haikuAvailable) {
200
+ for (const entry of survivingEntries) {
201
+ if (result.haikuCalls >= maxHaikuCalls) {
202
+ result.deferred += 1;
203
+ continue;
204
+ }
205
+ const block = relocateBlock(repoRoot, entry);
206
+ if (block === null) {
207
+ result.droppedMissing += 1;
208
+ continue;
209
+ }
210
+ const blockTokens = tokenize(entry.prose, { codeAware: true });
211
+ const candidates = topKCandidates(blockTokens, cacheEntries, TIER2_JACCARD_FLOOR, TOP_K_CANDIDATES);
212
+ if (candidates.length === 0) {
213
+ // Pre-filter found no candidates — fresh creation territory.
214
+ // Drain doesn't run the creation judge (Layer A's surface);
215
+ // drop and let the next Layer A Write trigger creation.
216
+ result.droppedDifferent += 1;
217
+ continue;
218
+ }
219
+ let outcome = {
220
+ kind: "no-hit",
221
+ };
222
+ for (const cand of candidates) {
223
+ if (result.haikuCalls >= maxHaikuCalls) {
224
+ outcome = { kind: "no-hit" };
225
+ break;
226
+ }
227
+ const candBody = readEntityBody(repoRoot, cand.id);
228
+ if (candBody === null)
229
+ continue;
230
+ const candScope = `${cand.id}-${bodyContentHash(candBody).slice(0, 12)}`;
231
+ const cached = readVerdictCache(repoRoot, entry.prose, candScope);
232
+ let verdict;
233
+ if (cached !== null) {
234
+ verdict = cached;
235
+ }
236
+ else {
237
+ if (result.haikuCalls >= maxHaikuCalls) {
238
+ outcome = { kind: "no-hit" };
239
+ break;
240
+ }
241
+ result.haikuCalls += 1;
242
+ verdict = await runDrainJudge({
243
+ blockBody: entry.prose,
244
+ candidate: { id: cand.id, body: candBody },
245
+ mock: args.mockJudge,
246
+ });
247
+ writeVerdictCache(repoRoot, entry.prose, candScope, verdict);
248
+ }
249
+ if (verdict === "same") {
250
+ outcome = { kind: "cite", id: cand.id };
251
+ break;
252
+ }
253
+ if (verdict === "different")
254
+ continue;
255
+ // ambiguous — surface the highest-scoring ambiguous candidate.
256
+ outcome = { kind: "ambiguous", id: cand.id };
257
+ break;
258
+ }
259
+ if (outcome.kind === "cite") {
260
+ if (!dryRun)
261
+ cited.push(buildCiteItem(block, outcome.id));
262
+ result.citedHaiku += 1;
263
+ continue;
264
+ }
265
+ if (outcome.kind === "ambiguous") {
266
+ if (!dryRun) {
267
+ const existingBody = readEntityBody(repoRoot, outcome.id);
268
+ writeAlignmentPending({
269
+ repoRoot,
270
+ block,
271
+ kind: "tier2-ambiguous",
272
+ existingId: outcome.id,
273
+ existingBody: existingBody ?? "",
274
+ detector: "layer-c-drain-ambiguous",
275
+ });
276
+ }
277
+ result.pending += 1;
278
+ continue;
279
+ }
280
+ // no-hit — every candidate said `different`.
281
+ result.droppedDifferent += 1;
282
+ }
283
+ }
284
+ else {
285
+ // Haiku offline — anything that wasn't a deterministic Tier 1
286
+ // hit stays in the deferred log for the next session.
287
+ result.deferred += survivingEntries.length;
288
+ }
289
+ if (cited.length > 0 && !dryRun) {
290
+ applyStripReplace({
291
+ repoRoot,
292
+ items: cited,
293
+ });
294
+ }
295
+ if (!dryRun && haikuAvailable) {
296
+ // Truncate both deferred logs. Drift events in staleness/log.jsonl
297
+ // stay (audit trail). When Haiku is offline we leave the logs alone
298
+ // so the next session retries.
299
+ truncateIfExists(layerADeferredLogPath(repoRoot));
300
+ truncateIfExists(preCommitDeferredLogPath(repoRoot));
301
+ }
302
+ if (sessionId !== null) {
303
+ const totalAligned = result.citedDeterministic + result.citedHaiku;
304
+ const detail = totalAligned >= SUMMARY_BLIP_THRESHOLD
305
+ ? `${totalAligned} aligned · ${result.totalEntries} stale entries`
306
+ : `${totalAligned} aligned, ${result.pending} pending, ${result.droppedDifferent + result.droppedMissing} dropped`;
307
+ pushEvent(repoRoot, sessionId, { kind: "drain-done", detail });
308
+ }
309
+ // Audit-trail drift event so a future operator can grep through
310
+ // staleness/log.jsonl and see drains as well as detections.
311
+ recordDriftEvent(repoRoot, {
312
+ ts: new Date().toISOString(),
313
+ kind: "doc-drift",
314
+ path: "(drain)",
315
+ detail: `Layer C drain: cited=${result.citedDeterministic + result.citedHaiku} pending=${result.pending} dropped=${result.droppedDifferent + result.droppedMissing} deferred=${result.deferred}`,
316
+ severity: "soft",
317
+ });
318
+ return result;
319
+ }
320
+ /* -------------------------------------------------------------------------- */
321
+ /* Block relocation */
322
+ /* -------------------------------------------------------------------------- */
323
+ /**
324
+ * Re-walk the source file and find the block whose prose still matches
325
+ * the deferred entry. Returns null when the block is gone (operator
326
+ * deleted it, edited it, or Layer A already cited it between defer
327
+ * and drain).
328
+ */
329
+ function relocateBlock(repoRoot, entry) {
330
+ const targetHash = bodyContentHash(entry.prose);
331
+ let blocks;
332
+ try {
333
+ blocks = extractBlocks(repoRoot, entry.file);
334
+ }
335
+ catch {
336
+ return null;
337
+ }
338
+ for (const b of blocks) {
339
+ if (bodyContentHash(b.prose) === targetHash)
340
+ return b;
341
+ }
342
+ return null;
343
+ }
344
+ /* -------------------------------------------------------------------------- */
345
+ /* Cite builder */
346
+ /* -------------------------------------------------------------------------- */
347
+ function buildCiteItem(block, decId) {
348
+ return {
349
+ blockId: block.id,
350
+ file: block.file,
351
+ startOffset: block.startOffset,
352
+ endOffset: block.endOffset,
353
+ replacement: formatBareCitation(block.lang, decId),
354
+ expectedRaw: block.raw,
355
+ };
356
+ }
357
+ /* -------------------------------------------------------------------------- */
358
+ /* Haiku dedup judge — single-pass (plan §4.3) */
359
+ /* -------------------------------------------------------------------------- */
360
+ const DRAIN_JUDGE_SCHEMA = {
361
+ type: "object",
362
+ additionalProperties: false,
363
+ required: ["verdict"],
364
+ properties: {
365
+ verdict: { type: "string", enum: ["same", "different", "ambiguous"] },
366
+ },
367
+ };
368
+ const DRAIN_JUDGE_SYSTEM = `You compare two prose blocks and return a single verdict.
369
+
370
+ Reply ONLY the JSON: { "verdict": "same" | "different" | "ambiguous" }.
371
+
372
+ - "same" both blocks describe the same decision/rule (overlap is total)
373
+ - "different" they describe distinct topics
374
+ - "ambiguous" related but not clearly the same — escalate to operator review
375
+
376
+ Be conservative on "same" — only flag when the two blocks make the same
377
+ binding statement with compatible wording.`;
378
+ function capBody(body) {
379
+ return body.length > BLOCK_BODY_CAP
380
+ ? `${body.slice(0, BLOCK_BODY_CAP)}\n…[truncated]`
381
+ : body;
382
+ }
383
+ async function runDrainJudge(args) {
384
+ if (args.mock !== undefined) {
385
+ return args.mock({ blockBody: args.blockBody, candidate: args.candidate });
386
+ }
387
+ const a = capBody(args.blockBody);
388
+ const b = capBody(args.candidate.body);
389
+ const prompt = [
390
+ "Block A (deferred from a prior write):",
391
+ a,
392
+ "",
393
+ `Block B (existing ${args.candidate.id}):`,
394
+ b,
395
+ "",
396
+ "Are these the same decision/rule?",
397
+ ].join("\n");
398
+ try {
399
+ const result = await runClaude({
400
+ tier: "haiku",
401
+ system: DRAIN_JUDGE_SYSTEM,
402
+ prompt,
403
+ jsonSchema: DRAIN_JUDGE_SCHEMA,
404
+ timeoutMs: PER_HAIKU_TIMEOUT_MS,
405
+ isolateAmbientContext: true,
406
+ });
407
+ const parsed = result.parsed;
408
+ if (typeof parsed !== "object" || parsed === null)
409
+ return "ambiguous";
410
+ const v = parsed["verdict"];
411
+ if (v === "same" || v === "different")
412
+ return v;
413
+ return "ambiguous";
414
+ }
415
+ catch (err) {
416
+ log.warn({ err: err instanceof Error ? err.message : String(err) }, "drain judge failed; treating as ambiguous");
417
+ return "ambiguous";
418
+ }
419
+ }
420
+ /* -------------------------------------------------------------------------- */
421
+ /* Verdict cache */
422
+ /* -------------------------------------------------------------------------- */
423
+ function verdictCachePath(repoRoot, blockBody, scopeKey) {
424
+ const blockHash = createHash("sha256").update(blockBody, "utf8").digest("hex").slice(0, 12);
425
+ return join(haikuCacheDir(repoRoot), "drain-judge", `${blockHash}-${scopeKey}.json`);
426
+ }
427
+ function readVerdictCache(repoRoot, blockBody, scopeKey) {
428
+ const path = verdictCachePath(repoRoot, blockBody, scopeKey);
429
+ if (!existsSync(path))
430
+ return null;
431
+ try {
432
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
433
+ const v = parsed.verdict;
434
+ if (v === "same" || v === "different" || v === "ambiguous")
435
+ return v;
436
+ return null;
437
+ }
438
+ catch {
439
+ return null;
440
+ }
441
+ }
442
+ function writeVerdictCache(repoRoot, blockBody, scopeKey, verdict) {
443
+ const path = verdictCachePath(repoRoot, blockBody, scopeKey);
444
+ try {
445
+ writeFileSafe(path, JSON.stringify({ verdict }));
446
+ }
447
+ catch {
448
+ /* best-effort */
449
+ }
450
+ }
451
+ /* -------------------------------------------------------------------------- */
452
+ /* Log truncation */
453
+ /* -------------------------------------------------------------------------- */
454
+ function truncateIfExists(path) {
455
+ if (!existsSync(path))
456
+ return;
457
+ try {
458
+ rmSync(path, { force: true });
459
+ }
460
+ catch {
461
+ /* best-effort */
462
+ }
463
+ }
464
+ //# sourceMappingURL=drain.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"drain.js","sourceRoot":"","sources":["../../src/drain/drain.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Layer C SessionStart drain — barrel.
3
+ */
4
+ export { runDrain } from "./drain.js";
5
+ export type { DrainArgs, DrainResult, DrainJudgeVerdict } from "./drain.js";
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Layer C SessionStart drain — barrel.
3
+ */
4
+ export { runDrain } from "./drain.js";
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/drain/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Layer D — `cairn fix align` (plan §4.4) barrel.
3
+ */
4
+ export { runFixAlign } from "./run.js";
5
+ export type { FixAlignArgs, FixAlignResult, PreflightResult, AggregateAlignResult, } from "./run.js";
6
+ export { fixAlignSentinelPath, gitDirtyPathsInScope, hashFixAlignArgs, readGitHeadSha, validateFixAlignSentinel, writeFixAlignSentinel, } from "./sentinel.js";
7
+ export type { DirtyPath, FixAlignSentinelArgs, SentinelValidation, } from "./sentinel.js";
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Layer D — `cairn fix align` (plan §4.4) barrel.
3
+ */
4
+ export { runFixAlign } from "./run.js";
5
+ export { fixAlignSentinelPath, gitDirtyPathsInScope, hashFixAlignArgs, readGitHeadSha, validateFixAlignSentinel, writeFixAlignSentinel, } from "./sentinel.js";
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/fix-align/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAOvC,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EACpB,gBAAgB,EAChB,cAAc,EACd,wBAAwB,EACxB,qBAAqB,GACtB,MAAM,eAAe,CAAC"}
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Layer D — `cairn fix align` (plan §4.4).
3
+ *
4
+ * Operator-explicit full-repo Haiku-judge sweep over every prose
5
+ * block × every accepted DEC. Reuses the Layer A `alignFile` machinery
6
+ * per file with elevated caps so a single sweep can fully judge each
7
+ * file rather than deferring to staleness.
8
+ *
9
+ * Two phases:
10
+ *
11
+ * 1. Pre-flight (deterministic, free) — walk every staged source
12
+ * file, extract prose blocks, compute Tier 1 candidate counts +
13
+ * Haiku call estimate. Produces a cost preview the operator can
14
+ * eyeball before approving the spend.
15
+ *
16
+ * 2. Apply (calls Haiku) — invoke `alignFile` per file. Aggregate
17
+ * tier counts, pending, deferred, descriptive, Haiku spend.
18
+ *
19
+ * `--dry-run` exits after pre-flight. `--max-cost` aborts apply when
20
+ * the estimate exceeds the budget. `--no-creation` short-circuits the
21
+ * Tier 3 creation judge so a sweep only consolidates duplicates
22
+ * without proposing fresh DECs.
23
+ */
24
+ import { type AlignFileArgs } from "../hooks/post-tool-use/index.js";
25
+ export interface FixAlignArgs {
26
+ repoRoot: string;
27
+ /** Dry-run: pre-flight only, no Haiku, no source writes. */
28
+ dryRun?: boolean;
29
+ /**
30
+ * Token budget. Apply phase aborts before invoking Haiku when the
31
+ * pre-flight estimate exceeds this. Default 500k tokens.
32
+ */
33
+ maxCost?: number;
34
+ /** Glob includes — empty = include everything. */
35
+ include?: readonly string[];
36
+ /**
37
+ * Glob excludes appended on top of the built-in defaults
38
+ * (`.cairn/**`, `node_modules/**`, etc.).
39
+ */
40
+ exclude?: readonly string[];
41
+ /** Skip Tier 3 creation judge — duplicate consolidation only. */
42
+ skipCreation?: boolean;
43
+ /** Override per-file Pass-1 cap (default 200). */
44
+ pass1Cap?: number;
45
+ /** Override per-file Pass-2 cap (default 50). */
46
+ pass2Cap?: number;
47
+ /** Mock judges (smoke fixtures). Forwarded to alignFile. */
48
+ mocks?: Pick<AlignFileArgs, "mockDedupJudgePass1" | "mockDedupJudgePass2" | "mockDeltaExtract" | "mockDeltaClassify" | "mockCreationJudgePass1" | "mockCreationJudgePass2">;
49
+ }
50
+ export interface PreflightResult {
51
+ /** Source files scanned (post-glob, post-markdown filter). */
52
+ filesScanned: number;
53
+ /** Total prose blocks discovered. */
54
+ blocksConsidered: number;
55
+ /** Blocks below the prose-length / token-count floors. Skipped in apply. */
56
+ shortBlocks: number;
57
+ /** Blocks where Tier 1 Jaccard found at least one candidate. */
58
+ blocksWithTier1Candidates: number;
59
+ /** Blocks with NO Jaccard candidate — Tier 3 creation territory. */
60
+ blocksWithoutCandidates: number;
61
+ /** Estimated Pass-1 calls (one per first-survivor candidate per block, capped). */
62
+ estimatedPass1Calls: number;
63
+ /** Estimated Pass-2 calls (10% of Pass-1, plus Tier 3 escalations). */
64
+ estimatedPass2Calls: number;
65
+ /** Estimated Tier 3 creation calls (when skipCreation=false). */
66
+ estimatedCreationCalls: number;
67
+ /** Token estimate combining all three call buckets. */
68
+ estimatedTokens: number;
69
+ }
70
+ export interface FixAlignResult {
71
+ preflight: PreflightResult;
72
+ /** Aggregated alignFile results. Populated only on apply (not dry-run). */
73
+ apply: AggregateAlignResult | null;
74
+ /**
75
+ * True when apply was aborted because the estimate exceeded
76
+ * `maxCost`. The preflight is still populated.
77
+ */
78
+ abortedOverBudget: boolean;
79
+ /** Repo-relative paths the sweep visited. */
80
+ filesVisited: string[];
81
+ }
82
+ export interface AggregateAlignResult {
83
+ filesAligned: number;
84
+ blocksConsidered: number;
85
+ tier1Aligned: number;
86
+ tier2Aligned: number;
87
+ decsCreated: number;
88
+ invsCreated: number;
89
+ augmentsDecs: number;
90
+ augmentsInvs: number;
91
+ pending: number;
92
+ deferredToStaleness: number;
93
+ descriptive: number;
94
+ skipped: number;
95
+ haikuPass1Calls: number;
96
+ haikuPass2Calls: number;
97
+ haikuCalls: number;
98
+ }
99
+ export declare function runFixAlign(args: FixAlignArgs): Promise<FixAlignResult>;